From e448897f998183fbccfa465b694b672dd69a5d21 Mon Sep 17 00:00:00 2001 From: Andy Wu Date: Mon, 7 Oct 2024 19:54:44 -0700 Subject: [PATCH] [fix] go-testing workflow (#27) Co-authored-by: Kingster --- .dockerignore | 5 + .gitattributes | 3 + .github/CODEOWNERS | 1 + .github/CONTRIBUTING.md | 40 + .github/ISSUE_TEMPLATE/bug.md | 31 + .github/ISSUE_TEMPLATE/feature.md | 17 + .github/ISSUE_TEMPLATE/question.md | 9 + .github/no-response.yml | 11 + .github/stale.yml | 17 + .github/workflows/ci-ecr.yml | 97 + .github/workflows/ci-geth-s3.yml.DEPRECATED | 166 + .github/workflows/ci-s3.yml | 201 + .github/workflows/go.yml | 39 + .gitignore | 52 + .gitmodules | 8 + .golangci.yml | 75 + .mailmap | 236 + .travis.yml | 154 + AUTHORS | 591 + COPYING | 674 + COPYING.LESSER | 165 + Dockerfile | 33 + Dockerfile.alltools | 32 + Makefile | 57 + SECURITY.md | 175 + accounts/abi/abi.go | 311 + accounts/abi/abi_test.go | 1220 + accounts/abi/abifuzzer_test.go | 179 + accounts/abi/argument.go | 273 + accounts/abi/bind/auth.go | 179 + accounts/abi/bind/backend.go | 120 + accounts/abi/bind/backends/simulated.go | 52 + accounts/abi/bind/base.go | 562 + accounts/abi/bind/base_test.go | 589 + accounts/abi/bind/bind.go | 496 + accounts/abi/bind/bind_test.go | 2147 + accounts/abi/bind/source.go.tpl | 487 + accounts/abi/bind/template.go | 89 + accounts/abi/bind/util.go | 79 + accounts/abi/bind/util_test.go | 139 + accounts/abi/doc.go | 26 + accounts/abi/error.go | 92 + accounts/abi/error_handling.go | 89 + accounts/abi/event.go | 103 + accounts/abi/event_test.go | 395 + accounts/abi/method.go | 166 + accounts/abi/method_test.go | 148 + accounts/abi/pack.go | 85 + accounts/abi/pack_test.go | 216 + accounts/abi/packing_test.go | 990 + accounts/abi/reflect.go | 264 + accounts/abi/reflect_test.go | 265 + accounts/abi/selector_parser.go | 177 + accounts/abi/selector_parser_test.go | 80 + accounts/abi/topics.go | 173 + accounts/abi/topics_test.go | 409 + accounts/abi/type.go | 425 + accounts/abi/type_test.go | 380 + accounts/abi/unpack.go | 329 + accounts/abi/unpack_test.go | 1119 + accounts/abi/utils.go | 40 + accounts/accounts.go | 226 + accounts/accounts_test.go | 33 + accounts/errors.go | 67 + accounts/external/backend.go | 281 + accounts/hd.go | 180 + accounts/hd_test.go | 120 + accounts/keystore/account_cache.go | 308 + accounts/keystore/account_cache_test.go | 408 + accounts/keystore/file_cache.go | 105 + accounts/keystore/key.go | 237 + accounts/keystore/keystore.go | 503 + accounts/keystore/keystore_fuzzing_test.go | 34 + accounts/keystore/keystore_test.go | 463 + accounts/keystore/passphrase.go | 368 + accounts/keystore/passphrase_test.go | 61 + accounts/keystore/plain.go | 61 + accounts/keystore/plain_test.go | 261 + accounts/keystore/presale.go | 150 + accounts/keystore/testdata/dupes/1 | 1 + accounts/keystore/testdata/dupes/2 | 1 + accounts/keystore/testdata/dupes/foo | 1 + .../keystore/testdata/keystore/.hiddenfile | 1 + accounts/keystore/testdata/keystore/README | 21 + ...--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 | 1 + accounts/keystore/testdata/keystore/aaa | 1 + accounts/keystore/testdata/keystore/empty | 0 .../fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e | 1 + accounts/keystore/testdata/keystore/garbage | Bin 0 -> 300 bytes .../keystore/testdata/keystore/no-address | 1 + accounts/keystore/testdata/keystore/zero | 1 + accounts/keystore/testdata/keystore/zzz | 1 + .../cb61d5a9c4896fb9658090b597ef0e7be6f7b67e | 1 + .../keystore/testdata/v1_test_vector.json | 28 + .../keystore/testdata/v3_test_vector.json | 97 + .../keystore/testdata/very-light-scrypt.json | 1 + accounts/keystore/wallet.go | 150 + accounts/keystore/watch.go | 134 + accounts/keystore/watch_fallback.go | 35 + accounts/manager.go | 275 + accounts/scwallet/README.md | 106 + accounts/scwallet/apdu.go | 87 + accounts/scwallet/hub.go | 303 + accounts/scwallet/securechannel.go | 339 + accounts/scwallet/wallet.go | 1096 + accounts/sort.go | 31 + accounts/url.go | 103 + accounts/url_test.go | 102 + accounts/usbwallet/hub.go | 289 + accounts/usbwallet/ledger.go | 554 + accounts/usbwallet/trezor.go | 371 + .../usbwallet/trezor/messages-common.pb.go | 1198 + .../usbwallet/trezor/messages-common.proto | 149 + .../usbwallet/trezor/messages-ethereum.pb.go | 1002 + .../usbwallet/trezor/messages-ethereum.proto | 133 + .../trezor/messages-management.pb.go | 2276 + .../trezor/messages-management.proto | 291 + accounts/usbwallet/trezor/messages.pb.go | 1366 + accounts/usbwallet/trezor/messages.proto | 267 + accounts/usbwallet/trezor/trezor.go | 70 + accounts/usbwallet/wallet.go | 643 + appveyor.yml | 58 + beacon/blsync/block_sync.go | 163 + beacon/blsync/block_sync_test.go | 160 + beacon/blsync/client.go | 115 + beacon/blsync/config.go | 129 + beacon/blsync/engineclient.go | 150 + beacon/engine/errors.go | 88 + beacon/engine/gen_blockparams.go | 66 + beacon/engine/gen_ed.go | 158 + beacon/engine/gen_epe.go | 58 + beacon/engine/types.go | 319 + beacon/light/api/api_server.go | 114 + beacon/light/api/light_api.go | 578 + beacon/light/canonical.go | 125 + beacon/light/committee_chain.go | 529 + beacon/light/committee_chain_test.go | 356 + beacon/light/head_tracker.go | 161 + beacon/light/range.go | 78 + beacon/light/request/scheduler.go | 403 + beacon/light/request/scheduler_test.go | 126 + beacon/light/request/server.go | 451 + beacon/light/request/server_test.go | 182 + beacon/light/sync/head_sync.go | 202 + beacon/light/sync/head_sync_test.go | 183 + beacon/light/sync/test_helpers.go | 259 + beacon/light/sync/types.go | 47 + beacon/light/sync/update_sync.go | 398 + beacon/light/sync/update_sync_test.go | 219 + beacon/light/test_helpers.go | 152 + beacon/merkle/merkle.go | 67 + beacon/params/params.go | 46 + beacon/types/beacon_block.go | 110 + beacon/types/beacon_block_test.go | 77 + beacon/types/committee.go | 191 + beacon/types/config.go | 204 + beacon/types/exec_header.go | 80 + beacon/types/exec_payload.go | 141 + beacon/types/gen_header_json.go | 66 + beacon/types/gen_syncaggregate_json.go | 51 + beacon/types/header.go | 132 + beacon/types/light_sync.go | 236 + beacon/types/testdata/block_capella.json | 1703 + beacon/types/testdata/block_deneb.json | 2644 + build/bot/macos-build.sh | 22 + build/bot/ppa-build.sh | 17 + build/checksums.txt | 126 + build/ci-notes.md | 50 + build/ci.go | 1278 + build/deb/ethereum/completions/bash/geth | 16 + build/deb/ethereum/completions/zsh/_geth | 18 + build/deb/ethereum/deb.changelog | 5 + build/deb/ethereum/deb.control | 25 + build/deb/ethereum/deb.copyright | 14 + build/deb/ethereum/deb.docs | 1 + build/deb/ethereum/deb.install | 5 + build/deb/ethereum/deb.rules | 37 + build/goimports.sh | 18 + build/nsis.envvarupdate.nsh | 327 + build/nsis.geth.nsi | 70 + build/nsis.install.nsh | 103 + build/nsis.pathupdate.nsh | 153 + build/nsis.simplefc.dll | Bin 0 -> 179712 bytes build/nsis.simplefc.source.zip | Bin 0 -> 23209 bytes build/nsis.uninstall.nsh | 33 + build/tools/tools.go | 27 + build/travis_keepalive.sh | 46 + build/update-license.go | 426 + circle.yml | 32 + cmd/abidump/main.go | 74 + cmd/abigen/main.go | 241 + cmd/abigen/namefilter.go | 58 + cmd/abigen/namefilter_test.go | 39 + cmd/blsync/main.go | 104 + cmd/bootnode/main.go | 209 + cmd/clef/README.md | 922 + cmd/clef/consolecmd_test.go | 124 + cmd/clef/datatypes.md | 224 + cmd/clef/docs/clef_architecture_pt1.png | Bin 0 -> 69221 bytes cmd/clef/docs/clef_architecture_pt2.png | Bin 0 -> 81521 bytes cmd/clef/docs/clef_architecture_pt3.png | Bin 0 -> 101351 bytes cmd/clef/docs/clef_architecture_pt4.png | Bin 0 -> 117597 bytes cmd/clef/docs/qubes/clef_qubes_http.png | Bin 0 -> 12237 bytes cmd/clef/docs/qubes/clef_qubes_qrexec.png | Bin 0 -> 17443 bytes cmd/clef/docs/qubes/qrexec-example.png | Bin 0 -> 16166 bytes cmd/clef/docs/qubes/qubes-client.py | 23 + cmd/clef/docs/qubes/qubes.Clefsign | 16 + cmd/clef/docs/qubes/qubes_newaccount-1.png | Bin 0 -> 22348 bytes cmd/clef/docs/qubes/qubes_newaccount-2.png | Bin 0 -> 37250 bytes cmd/clef/docs/setup.md | 198 + cmd/clef/extapi_changelog.md | 104 + cmd/clef/intapi_changelog.md | 191 + cmd/clef/main.go | 1224 + cmd/clef/pythonsigner.py | 315 + cmd/clef/requirements.txt | 1 + cmd/clef/rules.md | 234 + cmd/clef/run_test.go | 109 + cmd/clef/sign_flow.png | Bin 0 -> 20537 bytes .../sign_1559_missing_field_exp_fail.json | 16 + ...gn_1559_missing_maxfeepergas_exp_fail.json | 16 + cmd/clef/testdata/sign_1559_tx.json | 17 + .../testdata/sign_bad_checksum_exp_fail.json | 17 + cmd/clef/testdata/sign_normal_exp_ok.json | 17 + cmd/clef/tests/testsigner.js | 89 + cmd/clef/tutorial.md | 353 + cmd/devp2p/README.md | 141 + cmd/devp2p/crawl.go | 226 + cmd/devp2p/discv4cmd.go | 421 + cmd/devp2p/discv5cmd.go | 150 + cmd/devp2p/dns_cloudflare.go | 188 + cmd/devp2p/dns_route53.go | 431 + cmd/devp2p/dns_route53_test.go | 192 + cmd/devp2p/dnscmd.go | 417 + cmd/devp2p/enrcmd.go | 209 + cmd/devp2p/internal/ethtest/chain.go | 353 + cmd/devp2p/internal/ethtest/chain_test.go | 200 + cmd/devp2p/internal/ethtest/conn.go | 362 + cmd/devp2p/internal/ethtest/engine.go | 69 + cmd/devp2p/internal/ethtest/mkchain.sh | 9 + cmd/devp2p/internal/ethtest/protocol.go | 87 + cmd/devp2p/internal/ethtest/snap.go | 982 + cmd/devp2p/internal/ethtest/suite.go | 856 + cmd/devp2p/internal/ethtest/suite_test.go | 153 + .../internal/ethtest/testdata/accounts.json | 62 + .../internal/ethtest/testdata/chain.rlp | Bin 0 -> 341951 bytes .../internal/ethtest/testdata/forkenv.json | 20 + .../internal/ethtest/testdata/genesis.json | 112 + .../internal/ethtest/testdata/headblock.json | 23 + .../internal/ethtest/testdata/headfcu.json | 13 + .../internal/ethtest/testdata/headstate.json | 4204 + .../internal/ethtest/testdata/newpayload.json | 13268 + .../internal/ethtest/testdata/txinfo.json | 3018 + cmd/devp2p/internal/ethtest/transaction.go | 178 + cmd/devp2p/internal/v4test/discv4tests.go | 519 + cmd/devp2p/internal/v4test/framework.go | 125 + cmd/devp2p/internal/v5test/discv5tests.go | 378 + cmd/devp2p/internal/v5test/framework.go | 254 + cmd/devp2p/keycmd.go | 163 + cmd/devp2p/main.go | 101 + cmd/devp2p/nodeset.go | 133 + cmd/devp2p/nodesetcmd.go | 274 + cmd/devp2p/rlpxcmd.go | 169 + cmd/devp2p/runtest.go | 98 + cmd/era/main.go | 325 + cmd/ethkey/README.md | 53 + cmd/ethkey/changepassword.go | 88 + cmd/ethkey/generate.go | 133 + cmd/ethkey/inspect.go | 95 + cmd/ethkey/main.go | 61 + cmd/ethkey/message.go | 160 + cmd/ethkey/message_test.go | 65 + cmd/ethkey/run_test.go | 54 + cmd/ethkey/utils.go | 56 + cmd/evm/README.md | 626 + cmd/evm/blockrunner.go | 100 + cmd/evm/compiler.go | 55 + cmd/evm/disasm.go | 55 + cmd/evm/internal/compiler/compiler.go | 39 + cmd/evm/internal/t8ntool/block.go | 340 + cmd/evm/internal/t8ntool/execution.go | 438 + cmd/evm/internal/t8ntool/flags.go | 157 + cmd/evm/internal/t8ntool/gen_header.go | 159 + cmd/evm/internal/t8ntool/gen_stenv.go | 158 + cmd/evm/internal/t8ntool/transaction.go | 177 + cmd/evm/internal/t8ntool/transition.go | 373 + cmd/evm/internal/t8ntool/tx_iterator.go | 194 + cmd/evm/internal/t8ntool/utils.go | 54 + cmd/evm/main.go | 258 + cmd/evm/runner.go | 312 + cmd/evm/staterunner.go | 126 + cmd/evm/t8n_test.go | 685 + cmd/evm/testdata/1/alloc.json | 12 + cmd/evm/testdata/1/env.json | 7 + cmd/evm/testdata/1/exp.json | 45 + cmd/evm/testdata/1/txs.json | 26 + cmd/evm/testdata/10/alloc.json | 23 + cmd/evm/testdata/10/env.json | 12 + cmd/evm/testdata/10/readme.md | 85 + cmd/evm/testdata/10/txs.json | 70 + cmd/evm/testdata/11/alloc.json | 25 + cmd/evm/testdata/11/env.json | 12 + cmd/evm/testdata/11/readme.md | 13 + cmd/evm/testdata/11/txs.json | 14 + cmd/evm/testdata/12/alloc.json | 11 + cmd/evm/testdata/12/env.json | 10 + cmd/evm/testdata/12/readme.md | 43 + cmd/evm/testdata/12/txs.json | 20 + cmd/evm/testdata/13/alloc.json | 23 + cmd/evm/testdata/13/env.json | 12 + cmd/evm/testdata/13/exp.json | 3 + cmd/evm/testdata/13/exp2.json | 42 + cmd/evm/testdata/13/readme.md | 4 + cmd/evm/testdata/13/signed_txs.rlp | 1 + cmd/evm/testdata/13/txs.json | 34 + cmd/evm/testdata/14/alloc.json | 12 + cmd/evm/testdata/14/env.json | 9 + cmd/evm/testdata/14/env.uncles.json | 10 + cmd/evm/testdata/14/exp.json | 13 + cmd/evm/testdata/14/exp2.json | 13 + cmd/evm/testdata/14/exp_berlin.json | 13 + cmd/evm/testdata/14/readme.md | 45 + cmd/evm/testdata/14/txs.json | 1 + cmd/evm/testdata/15/blockheader.rlp | 1 + cmd/evm/testdata/15/exp.json | 10 + cmd/evm/testdata/15/exp2.json | 12 + cmd/evm/testdata/15/exp3.json | 47 + cmd/evm/testdata/15/signed_txs.rlp | 1 + cmd/evm/testdata/15/signed_txs.rlp.json | 4 + cmd/evm/testdata/16/exp.json | 13 + cmd/evm/testdata/16/signed_txs.rlp | 1 + cmd/evm/testdata/16/unsigned_txs.json | 34 + cmd/evm/testdata/17/exp.json | 22 + cmd/evm/testdata/17/rlpdata.txt | 46 + cmd/evm/testdata/17/signed_txs.rlp | 1 + cmd/evm/testdata/18/README.md | 9 + cmd/evm/testdata/18/invalid.rlp | 1 + cmd/evm/testdata/19/alloc.json | 12 + cmd/evm/testdata/19/env.json | 9 + cmd/evm/testdata/19/exp_arrowglacier.json | 13 + cmd/evm/testdata/19/exp_grayglacier.json | 13 + cmd/evm/testdata/19/exp_london.json | 13 + cmd/evm/testdata/19/readme.md | 25 + cmd/evm/testdata/19/txs.json | 1 + cmd/evm/testdata/2/alloc.json | 16 + cmd/evm/testdata/2/env.json | 7 + cmd/evm/testdata/2/readme.md | 1 + cmd/evm/testdata/2/txs.json | 14 + cmd/evm/testdata/20/exp.json | 4 + cmd/evm/testdata/20/header.json | 14 + cmd/evm/testdata/20/ommers.json | 1 + cmd/evm/testdata/20/readme.md | 11 + cmd/evm/testdata/20/txs.rlp | 1 + cmd/evm/testdata/21/clique.json | 6 + cmd/evm/testdata/21/exp-clique.json | 4 + cmd/evm/testdata/21/exp.json | 4 + cmd/evm/testdata/21/header.json | 11 + cmd/evm/testdata/21/ommers.json | 1 + cmd/evm/testdata/21/readme.md | 23 + cmd/evm/testdata/21/txs.rlp | 1 + cmd/evm/testdata/22/exp-clique.json | 4 + cmd/evm/testdata/22/exp.json | 4 + cmd/evm/testdata/22/header.json | 11 + cmd/evm/testdata/22/ommers.json | 1 + cmd/evm/testdata/22/readme.md | 11 + cmd/evm/testdata/22/txs.rlp | 1 + cmd/evm/testdata/23/alloc.json | 16 + cmd/evm/testdata/23/env.json | 7 + cmd/evm/testdata/23/exp.json | 26 + cmd/evm/testdata/23/readme.md | 1 + cmd/evm/testdata/23/txs.json | 15 + cmd/evm/testdata/24/alloc.json | 14 + cmd/evm/testdata/24/env-missingrandom.json | 9 + cmd/evm/testdata/24/env.json | 9 + cmd/evm/testdata/24/exp.json | 56 + cmd/evm/testdata/24/txs.json | 28 + cmd/evm/testdata/25/alloc.json | 8 + cmd/evm/testdata/25/env.json | 11 + cmd/evm/testdata/25/exp.json | 39 + cmd/evm/testdata/25/txs.json | 15 + cmd/evm/testdata/26/alloc.json | 8 + cmd/evm/testdata/26/env.json | 17 + cmd/evm/testdata/26/exp.json | 20 + cmd/evm/testdata/26/txs.json | 1 + cmd/evm/testdata/27/exp.json | 4 + cmd/evm/testdata/27/header.json | 12 + cmd/evm/testdata/27/ommers.json | 1 + cmd/evm/testdata/27/txs.rlp | 1 + cmd/evm/testdata/27/withdrawals.json | 8 + cmd/evm/testdata/28/alloc.json | 16 + cmd/evm/testdata/28/env.json | 22 + cmd/evm/testdata/28/exp.json | 47 + cmd/evm/testdata/28/txs.rlp | 1 + cmd/evm/testdata/29/alloc.json | 16 + cmd/evm/testdata/29/env.json | 20 + cmd/evm/testdata/29/exp.json | 45 + cmd/evm/testdata/29/readme.md | 29 + cmd/evm/testdata/29/txs.json | 19 + cmd/evm/testdata/3/alloc.json | 16 + cmd/evm/testdata/3/env.json | 8 + cmd/evm/testdata/3/exp.json | 39 + cmd/evm/testdata/3/readme.md | 2 + cmd/evm/testdata/3/txs.json | 14 + cmd/evm/testdata/30/README.txt | 77 + cmd/evm/testdata/30/alloc.json | 23 + cmd/evm/testdata/30/env.json | 23 + cmd/evm/testdata/30/exp.json | 64 + cmd/evm/testdata/30/txs.rlp | 1 + cmd/evm/testdata/30/txs_more.rlp | 1 + cmd/evm/testdata/31/README.md | 1 + cmd/evm/testdata/31/alloc.json | 16 + cmd/evm/testdata/31/env.json | 20 + ...47543268a5aaf2a6b32a69d2c6d978c45dcfb.json | 1 + ...7543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl | 6 + cmd/evm/testdata/31/txs.json | 14 + cmd/evm/testdata/32/README.md | 1 + cmd/evm/testdata/32/alloc.json | 30 + cmd/evm/testdata/32/env.json | 12 + ...48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl | 61 + cmd/evm/testdata/32/txs.json | 17 + cmd/evm/testdata/4/alloc.json | 16 + cmd/evm/testdata/4/env.json | 8 + cmd/evm/testdata/4/readme.md | 3 + cmd/evm/testdata/4/txs.json | 14 + cmd/evm/testdata/5/alloc.json | 1 + cmd/evm/testdata/5/env.json | 11 + cmd/evm/testdata/5/exp.json | 23 + cmd/evm/testdata/5/readme.md | 1 + cmd/evm/testdata/5/txs.json | 1 + cmd/evm/testdata/7/alloc.json | 12 + cmd/evm/testdata/7/env.json | 7 + cmd/evm/testdata/7/readme.md | 375 + cmd/evm/testdata/7/txs.json | 1 + cmd/evm/testdata/8/alloc.json | 11 + cmd/evm/testdata/8/env.json | 7 + cmd/evm/testdata/8/readme.md | 59 + cmd/evm/testdata/8/txs.json | 58 + cmd/evm/testdata/9/alloc.json | 11 + cmd/evm/testdata/9/env.json | 8 + cmd/evm/testdata/9/readme.md | 79 + cmd/evm/testdata/9/txs.json | 37 + cmd/evm/testdata/9/txs_signed.json | 37 + cmd/evm/transition-test.sh | 518 + cmd/geth/accountcmd.go | 384 + cmd/geth/accountcmd_test.go | 378 + cmd/geth/attach_test.go | 83 + cmd/geth/chaincmd.go | 605 + cmd/geth/config.go | 386 + cmd/geth/consolecmd.go | 160 + cmd/geth/consolecmd_test.go | 160 + cmd/geth/dbcmd.go | 931 + cmd/geth/exportcmd_test.go | 46 + cmd/geth/genesis_test.go | 198 + cmd/geth/logging_test.go | 239 + cmd/geth/logtestcmd_active.go | 171 + cmd/geth/logtestcmd_inactive.go | 23 + cmd/geth/main.go | 465 + cmd/geth/misccmd.go | 105 + cmd/geth/run_test.go | 120 + cmd/geth/snapshot.go | 694 + cmd/geth/testdata/blockchain.blocks | Bin 0 -> 23287 bytes cmd/geth/testdata/clique.json | 25 + cmd/geth/testdata/empty.js | 1 + cmd/geth/testdata/guswallet.json | 6 + cmd/geth/testdata/key.prv | 1 + cmd/geth/testdata/logging/logtest-json.txt | 52 + cmd/geth/testdata/logging/logtest-logfmt.txt | 52 + .../testdata/logging/logtest-terminal.txt | 53 + cmd/geth/testdata/password.txt | 1 + cmd/geth/testdata/passwords.txt | 3 + cmd/geth/testdata/vcheck/data.json | 61 + .../vcheck/minisig-sigs-new/data.json.minisig | 4 + .../vulnerabilities.json.minisig.1 | 4 + .../vulnerabilities.json.minisig.2 | 4 + .../vulnerabilities.json.minisig.3 | 4 + cmd/geth/testdata/vcheck/minisign.pub | 2 + cmd/geth/testdata/vcheck/minisign.sec | 2 + .../vcheck/signify-sigs/data.json.sig | 2 + cmd/geth/testdata/vcheck/signifykey.pub | 2 + cmd/geth/testdata/vcheck/signifykey.sec | 2 + .../sigs/vulnerabilities.json.minisig.1 | 4 + .../sigs/vulnerabilities.json.minisig.2 | 4 + .../sigs/vulnerabilities.json.minisig.3 | 4 + cmd/geth/testdata/vcheck/vulnerabilities.json | 202 + cmd/geth/testdata/wrong-passwords.txt | 3 + cmd/geth/verkle.go | 214 + cmd/geth/version_check.go | 170 + cmd/geth/version_check_test.go | 186 + cmd/p2psim/main.go | 443 + cmd/rlpdump/main.go | 255 + cmd/rlpdump/rlpdump_test.go | 84 + cmd/utils/cmd.go | 866 + cmd/utils/diskusage.go | 44 + cmd/utils/diskusage_openbsd.go | 44 + cmd/utils/diskusage_windows.go | 38 + cmd/utils/export_test.go | 199 + cmd/utils/flags.go | 2307 + cmd/utils/flags_legacy.go | 173 + cmd/utils/flags_test.go | 67 + cmd/utils/history_test.go | 184 + cmd/utils/prompt.go | 62 + cmd/utils/prompt_test.go | 77 + common/big.go | 36 + common/bitutil/bitutil.go | 188 + common/bitutil/bitutil_test.go | 221 + common/bitutil/compress.go | 170 + common/bitutil/compress_test.go | 223 + common/bytes.go | 151 + common/bytes_test.go | 126 + common/compiler/helpers.go | 45 + common/compiler/solidity.go | 132 + common/debug.go | 52 + common/fdlimit/fdlimit_bsd.go | 68 + common/fdlimit/fdlimit_darwin.go | 71 + common/fdlimit/fdlimit_test.go | 44 + common/fdlimit/fdlimit_unix.go | 66 + common/fdlimit/fdlimit_windows.go | 50 + common/format.go | 82 + common/hexutil/hexutil.go | 241 + common/hexutil/hexutil_test.go | 215 + common/hexutil/json.go | 421 + common/hexutil/json_example_test.go | 45 + common/hexutil/json_test.go | 434 + common/lru/basiclru.go | 221 + common/lru/basiclru_test.go | 255 + common/lru/blob_lru.go | 84 + common/lru/blob_lru_test.go | 155 + common/lru/lru.go | 95 + common/math/big.go | 271 + common/math/big_test.go | 324 + common/math/integer.go | 109 + common/math/integer_test.go | 116 + common/mclock/alarm.go | 106 + common/mclock/alarm_test.go | 116 + common/mclock/mclock.go | 127 + common/mclock/mclock.s | 1 + common/mclock/simclock.go | 209 + common/mclock/simclock_test.go | 162 + common/path.go | 40 + common/prque/lazyqueue.go | 195 + common/prque/lazyqueue_test.go | 123 + common/prque/prque.go | 76 + common/prque/prque_test.go | 133 + common/prque/sstack.go | 113 + common/prque/sstack_test.go | 100 + common/size.go | 56 + common/size_test.go | 59 + common/test_utils.go | 53 + common/types.go | 488 + common/types_test.go | 624 + consensus/beacon/consensus.go | 474 + consensus/beacon/faker.go | 41 + consensus/clique/api.go | 235 + consensus/clique/clique.go | 781 + consensus/clique/clique_test.go | 125 + consensus/clique/snapshot.go | 311 + consensus/clique/snapshot_test.go | 508 + consensus/consensus.go | 119 + consensus/errors.go | 41 + consensus/ethash/consensus.go | 593 + consensus/ethash/consensus_test.go | 188 + consensus/ethash/difficulty.go | 191 + consensus/ethash/ethash.go | 85 + consensus/misc/dao.go | 88 + consensus/misc/eip1559/eip1559.go | 95 + consensus/misc/eip1559/eip1559_test.go | 131 + consensus/misc/eip4844/eip4844.go | 98 + consensus/misc/eip4844/eip4844_test.go | 114 + consensus/misc/gaslimit.go | 41 + console/bridge.go | 484 + console/bridge_test.go | 48 + console/console.go | 564 + console/console_test.go | 321 + console/prompt/prompter.go | 172 + console/testdata/preload.js | 1 + core/.gitignore | 12 + core/asm/asm.go | 136 + core/asm/asm_test.go | 58 + core/asm/compiler.go | 292 + core/asm/compiler_test.go | 79 + core/asm/lex_test.go | 93 + core/asm/lexer.go | 275 + core/asm/tokentype_string.go | 31 + core/bench_test.go | 331 + core/block_validator.go | 199 + core/block_validator_test.go | 264 + core/blockchain.go | 2552 + core/blockchain_insert.go | 181 + core/blockchain_reader.go | 450 + core/blockchain_repair_test.go | 2020 + core/blockchain_sethead_test.go | 2194 + core/blockchain_snapshot_test.go | 713 + core/blockchain_test.go | 4222 + core/bloom_indexer.go | 92 + core/bloombits/doc.go | 18 + core/bloombits/generator.go | 98 + core/bloombits/generator_test.go | 100 + core/bloombits/matcher.go | 649 + core/bloombits/matcher_test.go | 292 + core/bloombits/scheduler.go | 181 + core/bloombits/scheduler_test.go | 103 + core/chain_indexer.go | 523 + core/chain_indexer_test.go | 246 + core/chain_makers.go | 682 + core/chain_makers_test.go | 259 + core/dao_test.go | 159 + core/error.go | 115 + core/events.go | 43 + core/evm.go | 142 + core/forkchoice.go | 113 + core/forkid/forkid.go | 297 + core/forkid/forkid_test.go | 441 + core/gaspool.go | 59 + core/gen_genesis.go | 137 + core/genesis.go | 669 + core/genesis_alloc.go | 29 + core/genesis_test.go | 352 + core/headerchain.go | 662 + core/headerchain_test.go | 116 + core/mkalloc.go | 109 + core/rawdb/accessors_chain.go | 953 + core/rawdb/accessors_chain_test.go | 931 + core/rawdb/accessors_indexes.go | 181 + core/rawdb/accessors_indexes_test.go | 156 + core/rawdb/accessors_metadata.go | 189 + core/rawdb/accessors_snapshot.go | 210 + core/rawdb/accessors_state.go | 266 + core/rawdb/accessors_sync.go | 100 + core/rawdb/accessors_trie.go | 302 + core/rawdb/ancient_scheme.go | 93 + core/rawdb/ancient_utils.go | 146 + core/rawdb/ancienttest/testsuite.go | 325 + core/rawdb/chain_freezer.go | 344 + core/rawdb/chain_iterator.go | 363 + core/rawdb/chain_iterator_test.go | 208 + core/rawdb/database.go | 666 + core/rawdb/database_test.go | 17 + core/rawdb/freezer.go | 503 + core/rawdb/freezer_batch.go | 255 + core/rawdb/freezer_memory.go | 428 + core/rawdb/freezer_memory_test.go | 41 + core/rawdb/freezer_meta.go | 109 + core/rawdb/freezer_meta_test.go | 60 + core/rawdb/freezer_resettable.go | 243 + core/rawdb/freezer_resettable_test.go | 107 + core/rawdb/freezer_table.go | 990 + core/rawdb/freezer_table_test.go | 1369 + core/rawdb/freezer_test.go | 502 + core/rawdb/freezer_utils.go | 131 + core/rawdb/freezer_utils_test.go | 75 + core/rawdb/key_length_iterator.go | 47 + core/rawdb/key_length_iterator_test.go | 60 + core/rawdb/schema.go | 341 + core/rawdb/table.go | 307 + core/rawdb/table_test.go | 128 + core/rawdb/testdata/stored_receipts.bin | Bin 0 -> 99991 bytes core/rlp_test.go | 197 + core/sender_cacher.go | 105 + core/state/access_events.go | 320 + core/state/access_events_test.go | 153 + core/state/access_list.go | 167 + core/state/database.go | 282 + core/state/dump.go | 234 + core/state/iterator.go | 171 + core/state/iterator_test.go | 108 + core/state/journal.go | 397 + core/state/metrics.go | 36 + core/state/pruner/bloom.go | 125 + core/state/pruner/pruner.go | 493 + core/state/snapshot/context.go | 241 + core/state/snapshot/conversion.go | 376 + core/state/snapshot/difflayer.go | 543 + core/state/snapshot/difflayer_test.go | 399 + core/state/snapshot/disklayer.go | 177 + core/state/snapshot/disklayer_test.go | 574 + core/state/snapshot/generate.go | 747 + core/state/snapshot/generate_test.go | 969 + core/state/snapshot/holdable_iterator.go | 97 + core/state/snapshot/holdable_iterator_test.go | 163 + core/state/snapshot/iterator.go | 400 + core/state/snapshot/iterator_binary.go | 213 + core/state/snapshot/iterator_fast.go | 344 + core/state/snapshot/iterator_test.go | 1047 + core/state/snapshot/journal.go | 363 + core/state/snapshot/metrics.go | 53 + core/state/snapshot/snapshot.go | 892 + core/state/snapshot/snapshot_test.go | 491 + core/state/snapshot/utils.go | 152 + core/state/state_object.go | 639 + core/state/state_object_test.go | 46 + core/state/state_test.go | 213 + core/state/statedb.go | 1479 + core/state/statedb_fuzz_test.go | 406 + core/state/statedb_test.go | 1375 + core/state/stateupdate.go | 133 + core/state/sync.go | 55 + core/state/sync_test.go | 748 + core/state/transient_storage.go | 92 + core/state/trie_prefetcher.go | 403 + core/state/trie_prefetcher_test.go | 64 + core/state_prefetcher.go | 91 + core/state_processor.go | 209 + core/state_processor_test.go | 530 + core/state_transition.go | 522 + core/stateless.go | 73 + core/stateless/database.go | 60 + core/stateless/encoding.go | 129 + core/stateless/gen_encoding_json.go | 74 + core/stateless/witness.go | 159 + core/tracing/CHANGELOG.md | 79 + .../gen_balance_change_reason_stringer.go | 37 + core/tracing/hooks.go | 313 + core/txindexer.go | 219 + core/txindexer_test.go | 240 + core/txpool/blobpool/blobpool.go | 1656 + core/txpool/blobpool/blobpool_test.go | 1376 + core/txpool/blobpool/config.go | 50 + core/txpool/blobpool/evictheap.go | 141 + core/txpool/blobpool/evictheap_test.go | 320 + core/txpool/blobpool/interface.go | 44 + core/txpool/blobpool/limbo.go | 253 + core/txpool/blobpool/metrics.go | 105 + core/txpool/blobpool/priority.go | 90 + core/txpool/blobpool/priority_test.go | 87 + core/txpool/blobpool/slotter.go | 38 + core/txpool/blobpool/slotter_test.go | 60 + core/txpool/errors.go | 63 + core/txpool/legacypool/journal.go | 186 + core/txpool/legacypool/legacypool.go | 1961 + core/txpool/legacypool/legacypool2_test.go | 242 + core/txpool/legacypool/legacypool_test.go | 2673 + core/txpool/legacypool/list.go | 687 + core/txpool/legacypool/list_test.go | 111 + core/txpool/legacypool/noncer.go | 91 + core/txpool/subpool.go | 165 + core/txpool/txpool.go | 482 + core/txpool/validation.go | 250 + core/types.go | 58 + core/types/account.go | 87 + core/types/block.go | 498 + core/types/block_test.go | 319 + core/types/bloom9.go | 160 + core/types/bloom9_test.go | 155 + core/types/gen_access_tuple.go | 43 + core/types/gen_account.go | 73 + core/types/gen_account_rlp.go | 21 + core/types/gen_header_json.go | 167 + core/types/gen_header_rlp.go | 85 + core/types/gen_log_json.go | 90 + core/types/gen_log_rlp.go | 20 + core/types/gen_receipt_json.go | 128 + core/types/gen_withdrawal_json.go | 55 + core/types/gen_withdrawal_rlp.go | 17 + core/types/hashes.go | 56 + core/types/hashing.go | 134 + core/types/hashing_test.go | 231 + core/types/log.go | 61 + core/types/log_test.go | 132 + core/types/receipt.go | 377 + core/types/receipt_test.go | 529 + core/types/rlp_fuzzer_test.go | 147 + core/types/state_account.go | 121 + core/types/transaction.go | 611 + core/types/transaction_marshalling.go | 421 + core/types/transaction_signing.go | 577 + core/types/transaction_signing_test.go | 190 + core/types/transaction_test.go | 545 + core/types/tx_access_list.go | 129 + core/types/tx_blob.go | 244 + core/types/tx_blob_test.go | 95 + core/types/tx_dynamic_fee.go | 125 + core/types/tx_legacy.go | 125 + core/types/types_test.go | 148 + core/types/withdrawal.go | 56 + core/vm/analysis.go | 118 + core/vm/analysis_test.go | 107 + core/vm/common.go | 94 + core/vm/contract.go | 210 + core/vm/contracts.go | 1229 + core/vm/contracts_fuzz_test.go | 54 + core/vm/contracts_test.go | 427 + core/vm/doc.go | 24 + core/vm/eips.go | 535 + core/vm/errors.go | 193 + core/vm/evm.go | 633 + core/vm/gas.go | 53 + core/vm/gas_table.go | 504 + core/vm/gas_table_test.go | 182 + core/vm/instructions.go | 935 + core/vm/instructions_test.go | 930 + core/vm/interface.go | 106 + core/vm/interpreter.go | 315 + core/vm/interpreter_test.go | 77 + core/vm/ipgraph.go | 376 + core/vm/jump_table.go | 1083 + core/vm/jump_table_export.go | 76 + core/vm/jump_table_test.go | 35 + core/vm/memory.go | 116 + core/vm/memory_table.go | 121 + core/vm/memory_test.go | 69 + core/vm/opcodes.go | 562 + core/vm/operations_acl.go | 250 + core/vm/operations_verkle.go | 159 + core/vm/runtime/doc.go | 18 + core/vm/runtime/env.go | 46 + core/vm/runtime/runtime.go | 223 + core/vm/runtime/runtime_example_test.go | 34 + core/vm/runtime/runtime_fuzz_test.go | 29 + core/vm/runtime/runtime_test.go | 915 + core/vm/stack.go | 82 + core/vm/stack_table.go | 42 + core/vm/testdata/precompiles/blake2F.json | 37 + core/vm/testdata/precompiles/blsG1Add.json | 730 + core/vm/testdata/precompiles/blsG1Mul.json | 730 + .../testdata/precompiles/blsG1MultiExp.json | 723 + core/vm/testdata/precompiles/blsG2Add.json | 730 + core/vm/testdata/precompiles/blsG2Mul.json | 730 + .../testdata/precompiles/blsG2MultiExp.json | 793 + core/vm/testdata/precompiles/blsMapG1.json | 702 + core/vm/testdata/precompiles/blsMapG2.json | 702 + core/vm/testdata/precompiles/blsPairing.json | 702 + core/vm/testdata/precompiles/bn256Add.json | 114 + .../vm/testdata/precompiles/bn256Pairing.json | 100 + .../testdata/precompiles/bn256ScalarMul.json | 135 + core/vm/testdata/precompiles/ecRecover.json | 37 + .../vm/testdata/precompiles/fail-blake2f.json | 22 + .../testdata/precompiles/fail-blsG1Add.json | 32 + .../testdata/precompiles/fail-blsG1Mul.json | 32 + .../precompiles/fail-blsG1MultiExp.json | 32 + .../testdata/precompiles/fail-blsG2Add.json | 32 + .../testdata/precompiles/fail-blsG2Mul.json | 32 + .../precompiles/fail-blsG2MultiExp.json | 32 + .../testdata/precompiles/fail-blsMapG1.json | 22 + .../testdata/precompiles/fail-blsMapG2.json | 22 + .../testdata/precompiles/fail-blsPairing.json | 42 + core/vm/testdata/precompiles/modexp.json | 121 + .../testdata/precompiles/modexp_eip2565.json | 121 + .../testdata/precompiles/pointEvaluation.json | 9 + core/vm/testdata/testcases_add.json | 1 + core/vm/testdata/testcases_and.json | 1 + core/vm/testdata/testcases_byte.json | 1 + core/vm/testdata/testcases_div.json | 1 + core/vm/testdata/testcases_eq.json | 1 + core/vm/testdata/testcases_exp.json | 1 + core/vm/testdata/testcases_gt.json | 1 + core/vm/testdata/testcases_lt.json | 1 + core/vm/testdata/testcases_mod.json | 1 + core/vm/testdata/testcases_mul.json | 1 + core/vm/testdata/testcases_or.json | 1 + core/vm/testdata/testcases_sar.json | 1 + core/vm/testdata/testcases_sdiv.json | 1 + core/vm/testdata/testcases_sgt.json | 1 + core/vm/testdata/testcases_shl.json | 1 + core/vm/testdata/testcases_shr.json | 1 + core/vm/testdata/testcases_signext.json | 1 + core/vm/testdata/testcases_slt.json | 1 + core/vm/testdata/testcases_smod.json | 1 + core/vm/testdata/testcases_sub.json | 1 + core/vm/testdata/testcases_xor.json | 1 + crypto/blake2b/blake2b.go | 321 + crypto/blake2b/blake2bAVX2_amd64.go | 38 + crypto/blake2b/blake2bAVX2_amd64.s | 717 + crypto/blake2b/blake2b_amd64.go | 25 + crypto/blake2b/blake2b_amd64.s | 253 + crypto/blake2b/blake2b_f_fuzz_test.go | 75 + crypto/blake2b/blake2b_f_test.go | 59 + crypto/blake2b/blake2b_generic.go | 181 + crypto/blake2b/blake2b_ref.go | 12 + crypto/blake2b/blake2b_test.go | 863 + crypto/blake2b/blake2x.go | 177 + crypto/blake2b/register.go | 33 + crypto/bn256/LICENSE | 28 + crypto/bn256/bn256_fast.go | 26 + crypto/bn256/bn256_slow.go | 24 + crypto/bn256/cloudflare/LICENSE | 27 + crypto/bn256/cloudflare/bn256.go | 495 + crypto/bn256/cloudflare/bn256_test.go | 129 + crypto/bn256/cloudflare/constants.go | 62 + crypto/bn256/cloudflare/curve.go | 238 + crypto/bn256/cloudflare/example_test.go | 51 + crypto/bn256/cloudflare/gfp.go | 82 + crypto/bn256/cloudflare/gfp12.go | 160 + crypto/bn256/cloudflare/gfp2.go | 156 + crypto/bn256/cloudflare/gfp6.go | 213 + crypto/bn256/cloudflare/gfp_amd64.s | 129 + crypto/bn256/cloudflare/gfp_arm64.s | 113 + crypto/bn256/cloudflare/gfp_decl.go | 26 + crypto/bn256/cloudflare/gfp_generic.go | 174 + crypto/bn256/cloudflare/gfp_test.go | 60 + crypto/bn256/cloudflare/lattice.go | 115 + crypto/bn256/cloudflare/lattice_test.go | 29 + crypto/bn256/cloudflare/main_test.go | 71 + crypto/bn256/cloudflare/mul_amd64.h | 181 + crypto/bn256/cloudflare/mul_arm64.h | 133 + crypto/bn256/cloudflare/mul_bmi2_amd64.h | 112 + crypto/bn256/cloudflare/optate.go | 270 + crypto/bn256/cloudflare/twist.go | 204 + crypto/bn256/google/bn256.go | 460 + crypto/bn256/google/bn256_test.go | 311 + crypto/bn256/google/constants.go | 47 + crypto/bn256/google/curve.go | 286 + crypto/bn256/google/example_test.go | 43 + crypto/bn256/google/gfp12.go | 200 + crypto/bn256/google/gfp2.go | 227 + crypto/bn256/google/gfp6.go | 296 + crypto/bn256/google/main_test.go | 71 + crypto/bn256/google/optate.go | 397 + crypto/bn256/google/twist.go | 263 + crypto/crypto.go | 291 + crypto/crypto_test.go | 299 + crypto/ecies/.gitignore | 24 + crypto/ecies/LICENSE | 28 + crypto/ecies/README | 94 + crypto/ecies/ecies.go | 325 + crypto/ecies/ecies_test.go | 429 + crypto/ecies/params.go | 145 + crypto/kzg4844/kzg4844.go | 168 + crypto/kzg4844/kzg4844_ckzg_cgo.go | 127 + crypto/kzg4844/kzg4844_ckzg_nocgo.go | 62 + crypto/kzg4844/kzg4844_gokzg.go | 98 + crypto/kzg4844/kzg4844_test.go | 195 + crypto/kzg4844/trusted_setup.json | 4167 + crypto/secp256k1/.gitignore | 24 + crypto/secp256k1/LICENSE | 31 + crypto/secp256k1/curve.go | 297 + crypto/secp256k1/dummy.go | 21 + crypto/secp256k1/ext.h | 130 + crypto/secp256k1/libsecp256k1/.gitignore | 49 + crypto/secp256k1/libsecp256k1/.travis.yml | 69 + crypto/secp256k1/libsecp256k1/COPYING | 19 + crypto/secp256k1/libsecp256k1/Makefile.am | 177 + crypto/secp256k1/libsecp256k1/README.md | 61 + crypto/secp256k1/libsecp256k1/TODO | 3 + crypto/secp256k1/libsecp256k1/autogen.sh | 3 + .../build-aux/m4/ax_jni_include_dir.m4 | 140 + .../build-aux/m4/ax_prog_cc_for_build.m4 | 125 + .../libsecp256k1/build-aux/m4/bitcoin_secp.m4 | 69 + crypto/secp256k1/libsecp256k1/configure.ac | 493 + .../secp256k1/libsecp256k1/contrib/dummy.go | 8 + .../libsecp256k1/contrib/lax_der_parsing.c | 150 + .../libsecp256k1/contrib/lax_der_parsing.h | 91 + .../contrib/lax_der_privatekey_parsing.c | 113 + .../contrib/lax_der_privatekey_parsing.h | 90 + crypto/secp256k1/libsecp256k1/dummy.go | 8 + .../secp256k1/libsecp256k1/include/dummy.go | 8 + .../libsecp256k1/include/secp256k1.h | 577 + .../libsecp256k1/include/secp256k1_ecdh.h | 31 + .../libsecp256k1/include/secp256k1_recovery.h | 110 + .../secp256k1/libsecp256k1/libsecp256k1.pc.in | 13 + crypto/secp256k1/libsecp256k1/obj/.gitignore | 0 .../libsecp256k1/sage/group_prover.sage | 322 + .../libsecp256k1/sage/secp256k1.sage | 306 + .../libsecp256k1/sage/weierstrass_prover.sage | 264 + .../libsecp256k1/src/asm/field_10x26_arm.s | 919 + .../secp256k1/libsecp256k1/src/basic-config.h | 32 + crypto/secp256k1/libsecp256k1/src/bench.h | 66 + .../secp256k1/libsecp256k1/src/bench_ecdh.c | 54 + .../libsecp256k1/src/bench_internal.c | 382 + .../libsecp256k1/src/bench_recover.c | 60 + .../libsecp256k1/src/bench_schnorr_verify.c | 73 + .../secp256k1/libsecp256k1/src/bench_sign.c | 56 + .../secp256k1/libsecp256k1/src/bench_verify.c | 112 + crypto/secp256k1/libsecp256k1/src/dummy.go | 8 + crypto/secp256k1/libsecp256k1/src/ecdsa.h | 21 + .../secp256k1/libsecp256k1/src/ecdsa_impl.h | 315 + crypto/secp256k1/libsecp256k1/src/eckey.h | 25 + .../secp256k1/libsecp256k1/src/eckey_impl.h | 99 + crypto/secp256k1/libsecp256k1/src/ecmult.h | 31 + .../secp256k1/libsecp256k1/src/ecmult_const.h | 15 + .../libsecp256k1/src/ecmult_const_impl.h | 239 + .../secp256k1/libsecp256k1/src/ecmult_gen.h | 43 + .../libsecp256k1/src/ecmult_gen_impl.h | 210 + .../secp256k1/libsecp256k1/src/ecmult_impl.h | 406 + crypto/secp256k1/libsecp256k1/src/field.h | 132 + .../secp256k1/libsecp256k1/src/field_10x26.h | 47 + .../libsecp256k1/src/field_10x26_impl.h | 1140 + .../secp256k1/libsecp256k1/src/field_5x52.h | 47 + .../libsecp256k1/src/field_5x52_asm_impl.h | 502 + .../libsecp256k1/src/field_5x52_impl.h | 451 + .../libsecp256k1/src/field_5x52_int128_impl.h | 277 + .../secp256k1/libsecp256k1/src/field_impl.h | 315 + .../secp256k1/libsecp256k1/src/gen_context.c | 74 + crypto/secp256k1/libsecp256k1/src/group.h | 144 + .../secp256k1/libsecp256k1/src/group_impl.h | 700 + crypto/secp256k1/libsecp256k1/src/hash.h | 41 + crypto/secp256k1/libsecp256k1/src/hash_impl.h | 281 + .../src/java/org/bitcoin/NativeSecp256k1.java | 446 + .../java/org/bitcoin/NativeSecp256k1Test.java | 226 + .../java/org/bitcoin/NativeSecp256k1Util.java | 45 + .../java/org/bitcoin/Secp256k1Context.java | 51 + .../src/java/org_bitcoin_NativeSecp256k1.c | 377 + .../src/java/org_bitcoin_NativeSecp256k1.h | 119 + .../src/java/org_bitcoin_Secp256k1Context.c | 15 + .../src/java/org_bitcoin_Secp256k1Context.h | 22 + .../libsecp256k1/src/modules/dummy.go | 8 + .../src/modules/ecdh/Makefile.am.include | 8 + .../libsecp256k1/src/modules/ecdh/dummy.go | 8 + .../libsecp256k1/src/modules/ecdh/main_impl.h | 54 + .../src/modules/ecdh/tests_impl.h | 105 + .../src/modules/recovery/Makefile.am.include | 8 + .../src/modules/recovery/dummy.go | 8 + .../src/modules/recovery/main_impl.h | 193 + .../src/modules/recovery/tests_impl.h | 393 + crypto/secp256k1/libsecp256k1/src/num.h | 74 + crypto/secp256k1/libsecp256k1/src/num_gmp.h | 20 + .../secp256k1/libsecp256k1/src/num_gmp_impl.h | 288 + crypto/secp256k1/libsecp256k1/src/num_impl.h | 24 + crypto/secp256k1/libsecp256k1/src/scalar.h | 106 + .../secp256k1/libsecp256k1/src/scalar_4x64.h | 19 + .../libsecp256k1/src/scalar_4x64_impl.h | 949 + .../secp256k1/libsecp256k1/src/scalar_8x32.h | 19 + .../libsecp256k1/src/scalar_8x32_impl.h | 721 + .../secp256k1/libsecp256k1/src/scalar_impl.h | 370 + .../secp256k1/libsecp256k1/src/scalar_low.h | 15 + .../libsecp256k1/src/scalar_low_impl.h | 114 + crypto/secp256k1/libsecp256k1/src/secp256k1.c | 559 + crypto/secp256k1/libsecp256k1/src/testrand.h | 38 + .../libsecp256k1/src/testrand_impl.h | 110 + crypto/secp256k1/libsecp256k1/src/tests.c | 4525 + .../libsecp256k1/src/tests_exhaustive.c | 470 + crypto/secp256k1/libsecp256k1/src/util.h | 113 + crypto/secp256k1/panic_cb.go | 24 + crypto/secp256k1/scalar_mult_cgo.go | 53 + crypto/secp256k1/scalar_mult_nocgo.go | 14 + crypto/secp256k1/secp256.go | 182 + crypto/secp256k1/secp256_test.go | 240 + crypto/signature_cgo.go | 86 + crypto/signature_nocgo.go | 196 + crypto/signature_test.go | 160 + crypto/signify/signify.go | 100 + crypto/signify/signify_fuzz.go | 151 + crypto/signify/signify_test.go | 144 + docs/audits/2017-04-25_Geth-audit_Truesec.pdf | Bin 0 -> 124427 bytes docs/audits/2018-09-14_Clef-audit_NCC.pdf | Bin 0 -> 755237 bytes ...2019-10-15_Discv5_audit_LeastAuthority.pdf | Bin 0 -> 332622 bytes .../audits/2020-01-24_DiscV5_audit_Cure53.pdf | Bin 0 -> 86510 bytes .../2021-08-22-split-postmortem.md | 266 + eth/api_admin.go | 143 + eth/api_backend.go | 433 + eth/api_debug.go | 445 + eth/api_debug_test.go | 223 + eth/api_miner.go | 58 + eth/backend.go | 445 + eth/bloombits.go | 74 + eth/catalyst/api.go | 898 + eth/catalyst/api_test.go | 1682 + eth/catalyst/queue.go | 158 + eth/catalyst/simulated_beacon.go | 321 + eth/catalyst/simulated_beacon_api.go | 61 + eth/catalyst/simulated_beacon_test.go | 142 + eth/catalyst/tester.go | 97 + eth/downloader/api.go | 203 + eth/downloader/beacondevsync.go | 81 + eth/downloader/beaconsync.go | 394 + eth/downloader/downloader.go | 1108 + eth/downloader/downloader_test.go | 742 + eth/downloader/events.go | 25 + eth/downloader/fetchers.go | 70 + eth/downloader/fetchers_concurrent.go | 354 + eth/downloader/fetchers_concurrent_bodies.go | 104 + .../fetchers_concurrent_receipts.go | 104 + eth/downloader/metrics.go | 42 + eth/downloader/modes.go | 67 + eth/downloader/peer.go | 290 + eth/downloader/queue.go | 966 + eth/downloader/queue_test.go | 474 + eth/downloader/resultstore.go | 195 + eth/downloader/skeleton.go | 1258 + eth/downloader/skeleton_test.go | 970 + eth/downloader/statesync.go | 123 + eth/downloader/testchain_test.go | 230 + eth/ethconfig/config.go | 185 + eth/ethconfig/gen_config.go | 286 + eth/fetcher/tx_fetcher.go | 1005 + eth/fetcher/tx_fetcher_test.go | 2021 + eth/filters/api.go | 611 + eth/filters/api_test.go | 185 + eth/filters/bench_test.go | 189 + eth/filters/filter.go | 378 + eth/filters/filter_system.go | 443 + eth/filters/filter_system_test.go | 630 + eth/filters/filter_test.go | 389 + eth/gasestimator/gasestimator.go | 245 + eth/gasprice/feehistory.go | 358 + eth/gasprice/feehistory_test.go | 97 + eth/gasprice/gasprice.go | 277 + eth/gasprice/gasprice_test.go | 264 + eth/handler.go | 564 + eth/handler_eth.go | 77 + eth/handler_eth_test.go | 440 + eth/handler_snap.go | 50 + eth/handler_test.go | 184 + eth/peer.go | 59 + eth/peerset.go | 239 + eth/protocols/eth/broadcast.go | 166 + eth/protocols/eth/discovery.go | 66 + eth/protocols/eth/dispatcher.go | 253 + eth/protocols/eth/handler.go | 211 + eth/protocols/eth/handler_test.go | 501 + eth/protocols/eth/handlers.go | 453 + eth/protocols/eth/handshake.go | 133 + eth/protocols/eth/handshake_test.go | 90 + eth/protocols/eth/metrics.go | 81 + eth/protocols/eth/peer.go | 402 + eth/protocols/eth/peer_test.go | 90 + eth/protocols/eth/protocol.go | 345 + eth/protocols/eth/protocol_test.go | 248 + eth/protocols/eth/tracker.go | 26 + eth/protocols/snap/discovery.go | 32 + eth/protocols/snap/gentrie.go | 287 + eth/protocols/snap/gentrie_test.go | 553 + eth/protocols/snap/handler.go | 571 + eth/protocols/snap/handler_fuzzing_test.go | 163 + eth/protocols/snap/metrics.go | 69 + eth/protocols/snap/peer.go | 133 + eth/protocols/snap/progress_test.go | 154 + eth/protocols/snap/protocol.go | 218 + eth/protocols/snap/range.go | 81 + eth/protocols/snap/range_test.go | 143 + eth/protocols/snap/sort_test.go | 101 + eth/protocols/snap/sync.go | 3261 + eth/protocols/snap/sync_test.go | 1966 + eth/protocols/snap/tracker.go | 26 + eth/state_accessor.go | 266 + eth/sync.go | 37 + eth/sync_test.go | 96 + eth/tracers/api.go | 1076 + eth/tracers/api_test.go | 1084 + eth/tracers/dir.go | 98 + eth/tracers/internal/tracetest/README.md | 10 + .../internal/tracetest/calltrace_test.go | 399 + .../internal/tracetest/flat_calltrace_test.go | 201 + eth/tracers/internal/tracetest/makeTest.js | 53 + .../internal/tracetest/prestate_test.go | 141 + eth/tracers/internal/tracetest/supply_test.go | 613 + .../testdata/call_tracer/blob_tx.json | 67 + .../testdata/call_tracer/create.json | 58 + .../testdata/call_tracer/deep_calls.json | 409 + .../testdata/call_tracer/delegatecall.json | 97 + .../inner_create_oog_outer_throw.json | 77 + .../testdata/call_tracer/inner_instafail.json | 71 + .../call_tracer/inner_revert_reason.json | 86 + .../call_tracer/inner_throw_outer_revert.json | 81 + .../call_tracer/inner_throw_outer_revert.md | 19 + .../tracetest/testdata/call_tracer/oog.json | 60 + .../testdata/call_tracer/revert.json | 58 + .../testdata/call_tracer/revert_reason.json | 65 + .../testdata/call_tracer/selfdestruct.json | 74 + .../testdata/call_tracer/simple.json | 80 + .../testdata/call_tracer/simple_onlytop.json | 72 + .../tracetest/testdata/call_tracer/throw.json | 62 + .../testdata/call_tracer_flat/big_slow.json | 64 + .../callcode_precompiled_fail_hide.json | 89 + .../callcode_precompiled_oog.json | 94 + .../callcode_precompiled_throw.json | 90 + .../testdata/call_tracer_flat/create.json | 67 + .../testdata/call_tracer_flat/deep_calls.json | 635 + .../call_tracer_flat/delegatecall.json | 120 + .../delegatecall_parent_value.json | 103 + .../testdata/call_tracer_flat/gas.json | 95 + .../call_tracer_flat/include_precompiled.json | 832 + .../inner_create_oog_outer_throw.json | 88 + .../call_tracer_flat/inner_instafail.json | 86 + .../inner_precompiled_wrong_gas.json | 219 + .../inner_throw_outer_revert.json | 95 + .../call_tracer_flat/nested_create.json | 94 + .../nested_create2_action_gas.json | 94 + .../nested_create_action_gas.json | 90 + .../nested_create_inerror.json | 94 + .../nested_pointer_issue.json | 189 + .../testdata/call_tracer_flat/oog.json | 68 + .../option_convert_parity_errors.json | 71 + .../call_tracer_flat/result_output.json | 111 + .../testdata/call_tracer_flat/revert.json | 68 + .../call_tracer_flat/revert_reason.json | 74 + .../call_tracer_flat/selfdestruct.json | 104 + .../testdata/call_tracer_flat/simple.json | 97 + .../call_tracer_flat/simple_onlytop.json | 100 + .../skip_no_balance_error.json | 83 + .../staticcall_precompiled.json | 83 + .../testdata/call_tracer_flat/suicide.json | 92 + .../testdata/call_tracer_flat/throw.json | 70 + .../testdata/call_tracer_legacy/create.json | 58 + .../call_tracer_legacy/deep_calls.json | 415 + .../call_tracer_legacy/delegatecall.json | 97 + .../inner_create_oog_outer_throw.json | 77 + .../call_tracer_legacy/inner_instafail.json | 72 + .../inner_throw_outer_revert.json | 81 + .../testdata/call_tracer_legacy/oog.json | 60 + .../testdata/call_tracer_legacy/revert.json | 58 + .../call_tracer_legacy/revert_reason.json | 64 + .../call_tracer_legacy/selfdestruct.json | 73 + .../testdata/call_tracer_legacy/simple.json | 78 + .../testdata/call_tracer_legacy/throw.json | 62 + .../call_tracer_withLog/calldata.json | 117 + .../call_tracer_withLog/delegatecall.json | 413 + .../frontier_create_outofstorage.json | 190 + .../call_tracer_withLog/multi_contracts.json | 2327 + .../call_tracer_withLog/multilogs.json | 580 + .../testdata/call_tracer_withLog/notopic.json | 288 + .../testdata/call_tracer_withLog/simple.json | 85 + .../call_tracer_withLog/tx_failed.json | 244 + .../tx_partial_failed.json | 108 + .../call_tracer_withLog/with_onlyTopCall.json | 89 + .../testdata/prestate_tracer/blob_tx.json | 64 + .../prestate_tracer/create_create.json | 63 + .../create_existing_contract.json | 85 + .../prestate_tracer/create_post_eip158.json | 65 + .../testdata/prestate_tracer/simple.json | 83 + .../prestate_tracer_legacy/simple.json | 84 + .../create.json | 102 + .../create_failed.json | 95 + .../create_post_eip158.json | 83 + .../create_suicide.json | 104 + .../inner_create.json | 312 + .../simple.json | 103 + .../suicide.json | 107 + eth/tracers/internal/tracetest/util.go | 55 + eth/tracers/internal/util.go | 81 + eth/tracers/internal/util_test.go | 60 + eth/tracers/js/bigint.go | 20 + eth/tracers/js/goja.go | 1025 + .../internal/tracers/4byte_tracer_legacy.js | 86 + .../js/internal/tracers/bigram_tracer.js | 47 + .../js/internal/tracers/call_tracer_legacy.js | 250 + .../js/internal/tracers/evmdis_tracer.js | 93 + .../js/internal/tracers/noop_tracer_legacy.js | 29 + .../js/internal/tracers/opcount_tracer.js | 32 + .../tracers/prestate_tracer_legacy.js | 115 + eth/tracers/js/internal/tracers/tracers.go | 59 + .../js/internal/tracers/trigram_tracer.js | 49 + .../js/internal/tracers/unigram_tracer.js | 41 + eth/tracers/js/tracer_test.go | 323 + eth/tracers/live.go | 31 + eth/tracers/live/gen_supplyinfoburn.go | 49 + eth/tracers/live/gen_supplyinfoissuance.go | 49 + eth/tracers/live/noop.go | 96 + eth/tracers/live/supply.go | 308 + eth/tracers/logger/access_list_tracer.go | 165 + eth/tracers/logger/gen_callframe.go | 65 + eth/tracers/logger/gen_structlog.go | 118 + eth/tracers/logger/logger.go | 478 + eth/tracers/logger/logger_json.go | 177 + eth/tracers/logger/logger_test.go | 108 + eth/tracers/native/4byte.go | 135 + eth/tracers/native/call.go | 288 + eth/tracers/native/call_flat.go | 383 + eth/tracers/native/call_flat_test.go | 64 + eth/tracers/native/gen_account_json.go | 56 + eth/tracers/native/gen_callframe_json.go | 107 + eth/tracers/native/gen_flatcallaction_json.go | 110 + eth/tracers/native/gen_flatcallresult_json.go | 55 + eth/tracers/native/mux.go | 198 + eth/tracers/native/noop.go | 98 + eth/tracers/native/prestate.go | 283 + eth/tracers/tracers_test.go | 113 + eth/tracers/tracker.go | 109 + eth/tracers/tracker_test.go | 171 + ethclient/ethclient.go | 732 + ethclient/ethclient_test.go | 762 + ethclient/gethclient/gethclient.go | 350 + ethclient/gethclient/gethclient_test.go | 570 + ethclient/signer.go | 62 + ethclient/simulated/backend.go | 193 + ethclient/simulated/backend_test.go | 308 + ethclient/simulated/options.go | 55 + ethclient/simulated/options_test.go | 74 + ethdb/batch.go | 74 + ethdb/database.go | 204 + ethdb/dbtest/testsuite.go | 536 + ethdb/iterator.go | 61 + ethdb/leveldb/leveldb.go | 523 + ethdb/leveldb/leveldb_test.go | 52 + ethdb/memorydb/memorydb.go | 390 + ethdb/memorydb/memorydb_test.go | 50 + ethdb/pebble/pebble.go | 684 + ethdb/pebble/pebble_test.go | 56 + ethdb/remotedb/remotedb.go | 154 + ethdb/snapshot.go | 41 + ethstats/ethstats.go | 819 + ethstats/ethstats_test.go | 82 + event/event.go | 217 + event/event_test.go | 218 + event/example_feed_test.go | 73 + event/example_scope_test.go | 128 + event/example_subscription_test.go | 56 + event/example_test.go | 58 + event/feed.go | 238 + event/feed_test.go | 335 + event/feedof.go | 164 + event/feedof_test.go | 279 + event/multisub.go | 50 + event/multisub_test.go | 175 + event/subscription.go | 298 + event/subscription_test.go | 180 + go.mod | 149 + go.sum | 879 + graphql/graphiql.go | 88 + graphql/graphql.go | 1547 + graphql/graphql_test.go | 487 + graphql/internal/graphiql/build.go | 8 + graphql/internal/graphiql/graphiql.min.css | 337 + graphql/internal/graphiql/graphiql.min.js | 3 + graphql/internal/graphiql/index.html | 46 + .../graphiql/react-dom.production.min.js | 245 + .../internal/graphiql/react.production.min.js | 31 + graphql/schema.go | 394 + graphql/service.go | 132 + interfaces.go | 277 + internal/blocktest/test_hash.go | 59 + internal/build/archive.go | 296 + internal/build/azure.go | 120 + internal/build/download.go | 152 + internal/build/env.go | 177 + internal/build/gotool.go | 180 + internal/build/pgp.go | 71 + internal/build/util.go | 236 + internal/cmdtest/test_cmd.go | 300 + internal/debug/api.go | 266 + internal/debug/flags.go | 347 + internal/debug/loudpanic.go | 25 + internal/debug/trace.go | 61 + internal/era/accumulator.go | 91 + internal/era/builder.go | 225 + internal/era/e2store/e2store.go | 221 + internal/era/e2store/e2store_test.go | 150 + internal/era/era.go | 277 + internal/era/era_test.go | 142 + internal/era/iterator.go | 197 + internal/ethapi/addrlock.go | 53 + internal/ethapi/api.go | 2180 + internal/ethapi/api_test.go | 2055 + internal/ethapi/backend.go | 128 + internal/ethapi/dbapi.go | 43 + internal/ethapi/errors.go | 78 + .../testdata/eth_getBlockByHash-hash-1.json | 25 + .../eth_getBlockByHash-hash-empty-fullTx.json | 1 + .../eth_getBlockByHash-hash-genesis.json | 23 + ...h_getBlockByHash-hash-latest-1-fullTx.json | 41 + .../eth_getBlockByHash-hash-latest.json | 25 + ...th_getBlockByHash-hash-pending-fullTx.json | 1 + .../eth_getBlockByHash-hash-pending.json | 1 + .../eth_getBlockByNumber-number-0.json | 23 + .../eth_getBlockByNumber-number-1.json | 25 + .../eth_getBlockByNumber-number-latest+1.json | 1 + .../eth_getBlockByNumber-number-latest-1.json | 41 + .../eth_getBlockByNumber-tag-latest.json | 25 + ...h_getBlockByNumber-tag-pending-fullTx.json | 50 + .../eth_getBlockByNumber-tag-pending.json | 33 + .../eth_getBlockReceipts-block-notfound.json | 1 + ...h_getBlockReceipts-block-with-blob-tx.json | 20 + ...eceipts-block-with-contract-create-tx.json | 18 + ...ockReceipts-block-with-dynamic-fee-tx.json | 18 + ...ts-block-with-legacy-contract-call-tx.json | 34 + ...eceipts-block-with-legacy-transfer-tx.json | 18 + .../eth_getBlockReceipts-hash-empty.json | 1 + .../eth_getBlockReceipts-hash-notfound.json | 1 + .../eth_getBlockReceipts-number-0.json | 1 + .../eth_getBlockReceipts-number-1.json | 1 + .../eth_getBlockReceipts-tag-earliest.json | 1 + .../eth_getBlockReceipts-tag-latest.json | 20 + .../testdata/eth_getHeaderByHash-hash-0.json | 20 + .../testdata/eth_getHeaderByHash-hash-1.json | 20 + .../eth_getHeaderByHash-hash-empty.json | 1 + .../eth_getHeaderByHash-hash-latest-1.json | 20 + .../eth_getHeaderByHash-hash-latest.json | 20 + .../eth_getHeaderByHash-hash-pending.json | 1 + .../eth_getHeaderByNumber-number-0.json | 20 + .../eth_getHeaderByNumber-number-1.json | 20 + ...eth_getHeaderByNumber-number-latest+1.json | 1 + ...eth_getHeaderByNumber-number-latest-1.json | 20 + .../eth_getHeaderByNumber-tag-latest.json | 20 + .../eth_getHeaderByNumber-tag-pending.json | 20 + .../eth_getTransactionReceipt-blob-tx.json | 18 + ...TransactionReceipt-create-contract-tx.json | 16 + ...eipt-create-contract-with-access-list.json | 16 + ...ansactionReceipt-dynamic-tx-with-logs.json | 16 + ...TransactionReceipt-normal-transfer-tx.json | 16 + ...th_getTransactionReceipt-txhash-empty.json | 1 + ...getTransactionReceipt-txhash-notfound.json | 1 + .../eth_getTransactionReceipt-with-logs.json | 32 + internal/ethapi/transaction_args.go | 546 + internal/ethapi/transaction_args_test.go | 405 + internal/flags/categories.go | 45 + internal/flags/flags.go | 381 + internal/flags/flags_test.go | 61 + internal/flags/helpers.go | 310 + internal/guide/guide.go | 18 + internal/guide/guide_test.go | 97 + internal/jsre/completion.go | 93 + internal/jsre/completion_test.go | 93 + internal/jsre/deps/bignumber.js | 4 + internal/jsre/deps/deps.go | 28 + internal/jsre/deps/web3.js | 13668 + internal/jsre/jsre.go | 340 + internal/jsre/jsre_test.go | 130 + internal/jsre/pretty.go | 301 + internal/reexec/reexec.go | 35 + internal/reexec/self_linux.go | 14 + internal/reexec/self_others.go | 32 + internal/shutdowncheck/shutdown_tracker.go | 85 + internal/syncx/mutex.go | 64 + internal/testlog/testlog.go | 214 + internal/testrand/rand.go | 53 + internal/utesting/utesting.go | 339 + internal/utesting/utesting_test.go | 139 + internal/version/vcs.go | 52 + internal/version/version.go | 141 + internal/web3ext/web3ext.go | 889 + log/format.go | 363 + log/format_test.go | 24 + log/handler.go | 199 + log/handler_glog.go | 214 + log/logger.go | 216 + log/logger_test.go | 192 + log/root.go | 115 + metrics/FORK.md | 1 + metrics/LICENSE | 29 + metrics/README.md | 147 + metrics/config.go | 56 + metrics/counter.go | 112 + metrics/counter_float64.go | 126 + metrics/counter_float_64_test.go | 99 + metrics/counter_test.go | 77 + metrics/cpu.go | 25 + metrics/cpu_disabled.go | 24 + metrics/cpu_enabled.go | 44 + metrics/cputime_nop.go | 26 + metrics/cputime_unix.go | 36 + metrics/debug.go | 76 + metrics/debug_test.go | 48 + metrics/disk.go | 25 + metrics/disk_linux.go | 72 + metrics/disk_nop.go | 27 + metrics/ewma.go | 111 + metrics/ewma_test.go | 89 + metrics/exp/exp.go | 213 + metrics/gauge.go | 98 + metrics/gauge_float64.go | 73 + metrics/gauge_float64_test.go | 51 + metrics/gauge_info.go | 84 + metrics/gauge_info_test.go | 36 + metrics/gauge_test.go | 31 + metrics/graphite.go | 117 + metrics/graphite_test.go | 22 + metrics/healthcheck.go | 61 + metrics/histogram.go | 73 + metrics/histogram_test.go | 95 + metrics/inactive.go | 48 + metrics/influxdb/LICENSE | 19 + metrics/influxdb/README.md | 30 + metrics/influxdb/influxdb.go | 122 + metrics/influxdb/influxdb_test.go | 123 + metrics/influxdb/influxdbv1.go | 152 + metrics/influxdb/influxdbv2.go | 96 + metrics/influxdb/testdata/influxdbv1.want | 11 + metrics/influxdb/testdata/influxdbv2.want | 11 + metrics/init_test.go | 5 + metrics/internal/sampledata.go | 95 + metrics/internal/sampledata_test.go | 27 + metrics/json.go | 31 + metrics/json_test.go | 28 + metrics/log.go | 86 + metrics/memory.md | 285 + metrics/meter.go | 189 + metrics/meter_test.go | 89 + metrics/metrics.go | 225 + metrics/metrics_test.go | 105 + metrics/opentsdb.go | 128 + metrics/opentsdb_test.go | 66 + metrics/prometheus/collector.go | 170 + metrics/prometheus/collector_test.go | 65 + metrics/prometheus/prometheus.go | 52 + metrics/prometheus/testdata/prometheus.want | 73 + metrics/registry.go | 372 + metrics/registry_test.go | 335 + metrics/resetting_sample.go | 24 + metrics/resetting_timer.go | 171 + metrics/resetting_timer_test.go | 197 + metrics/runtimehistogram.go | 301 + metrics/runtimehistogram_test.go | 162 + metrics/sample.go | 445 + metrics/sample_test.go | 356 + metrics/syslog.go | 83 + metrics/testdata/opentsb.want | 23 + metrics/timer.go | 182 + metrics/timer_test.go | 114 + metrics/validate.sh | 10 + metrics/writer.go | 99 + metrics/writer_test.go | 22 + miner/miner.go | 165 + miner/miner_test.go | 169 + miner/ordering.go | 166 + miner/ordering_test.go | 196 + miner/payload_building.go | 246 + miner/payload_building_test.go | 275 + miner/pending.go | 67 + miner/worker.go | 457 + node/api.go | 352 + node/api_test.go | 355 + node/config.go | 480 + node/config_test.go | 153 + node/defaults.go | 134 + node/doc.go | 121 + node/endpoints.go | 91 + node/errors.go | 52 + node/jwt_auth.go | 45 + node/jwt_handler.go | 80 + node/lifecycle.go | 31 + node/node.go | 822 + node/node_auth_test.go | 237 + node/node_example_test.go | 59 + node/node_test.go | 624 + node/rpcstack.go | 652 + node/rpcstack_test.go | 616 + node/utils_test.go | 106 + oss-fuzz.sh | 227 + p2p/dial.go | 555 + p2p/dial_test.go | 672 + p2p/discover/common.go | 140 + p2p/discover/lookup.go | 208 + p2p/discover/metrics.go | 73 + p2p/discover/node.go | 133 + p2p/discover/ntp.go | 111 + p2p/discover/table.go | 696 + p2p/discover/table_reval.go | 244 + p2p/discover/table_reval_test.go | 119 + p2p/discover/table_test.go | 499 + p2p/discover/table_util_test.go | 351 + p2p/discover/v4_lookup_test.go | 348 + p2p/discover/v4_udp.go | 811 + p2p/discover/v4_udp_test.go | 656 + p2p/discover/v4wire/v4wire.go | 298 + p2p/discover/v4wire/v4wire_test.go | 132 + p2p/discover/v5_talk.go | 115 + p2p/discover/v5_udp.go | 910 + p2p/discover/v5_udp_test.go | 858 + p2p/discover/v5wire/crypto.go | 178 + p2p/discover/v5wire/crypto_test.go | 124 + p2p/discover/v5wire/encoding.go | 672 + p2p/discover/v5wire/encoding_test.go | 669 + p2p/discover/v5wire/msg.go | 229 + p2p/discover/v5wire/session.go | 135 + .../testdata/v5.1-ping-handshake-enr.txt | 27 + .../v5wire/testdata/v5.1-ping-handshake.txt | 23 + .../v5wire/testdata/v5.1-ping-message.txt | 10 + .../v5wire/testdata/v5.1-whoareyou.txt | 9 + p2p/dnsdisc/client.go | 389 + p2p/dnsdisc/client_test.go | 471 + p2p/dnsdisc/doc.go | 18 + p2p/dnsdisc/error.go | 63 + p2p/dnsdisc/sync.go | 329 + p2p/dnsdisc/sync_test.go | 83 + p2p/dnsdisc/tree.go | 424 + p2p/dnsdisc/tree_test.go | 151 + p2p/enode/idscheme.go | 161 + p2p/enode/idscheme_test.go | 74 + p2p/enode/iter.go | 295 + p2p/enode/iter_test.go | 291 + p2p/enode/localnode.go | 316 + p2p/enode/localnode_test.go | 137 + p2p/enode/node.go | 350 + p2p/enode/node_test.go | 307 + p2p/enode/nodedb.go | 501 + p2p/enode/nodedb_test.go | 472 + p2p/enode/urlv4.go | 203 + p2p/enode/urlv4_test.go | 200 + p2p/enr/enr.go | 335 + p2p/enr/enr_test.go | 348 + p2p/enr/entries.go | 251 + p2p/message.go | 325 + p2p/message_test.go | 141 + p2p/metrics.go | 136 + p2p/msgrate/msgrate.go | 465 + p2p/msgrate/msgrate_test.go | 28 + p2p/nat/nat.go | 240 + p2p/nat/nat_test.go | 63 + p2p/nat/natpmp.go | 131 + p2p/nat/natupnp.go | 250 + p2p/nat/natupnp_test.go | 249 + p2p/netutil/addrutil.go | 68 + p2p/netutil/error.go | 33 + p2p/netutil/error_test.go | 72 + p2p/netutil/iptrack.go | 134 + p2p/netutil/iptrack_test.go | 145 + p2p/netutil/net.go | 342 + p2p/netutil/net_test.go | 288 + p2p/netutil/toobig_notwindows.go | 27 + p2p/netutil/toobig_windows.go | 41 + p2p/peer.go | 548 + p2p/peer_error.go | 119 + p2p/peer_test.go | 362 + p2p/protocol.go | 93 + p2p/rlpx/buffer.go | 127 + p2p/rlpx/buffer_test.go | 51 + p2p/rlpx/rlpx.go | 683 + p2p/rlpx/rlpx_test.go | 453 + p2p/server.go | 1149 + p2p/server_nat.go | 187 + p2p/server_nat_test.go | 103 + p2p/server_test.go | 631 + p2p/simulations/README.md | 174 + p2p/simulations/adapters/exec.go | 567 + p2p/simulations/adapters/inproc.go | 344 + p2p/simulations/adapters/inproc_test.go | 202 + p2p/simulations/adapters/types.go | 325 + p2p/simulations/connect.go | 153 + p2p/simulations/connect_test.go | 172 + p2p/simulations/events.go | 110 + p2p/simulations/examples/README.md | 39 + p2p/simulations/examples/ping-pong.go | 173 + p2p/simulations/examples/ping-pong.sh | 40 + p2p/simulations/http.go | 743 + p2p/simulations/http_test.go | 869 + p2p/simulations/mocker.go | 197 + p2p/simulations/mocker_test.go | 174 + p2p/simulations/network.go | 1093 + p2p/simulations/network_test.go | 872 + p2p/simulations/pipes/pipes.go | 55 + p2p/simulations/simulation.go | 157 + p2p/simulations/test.go | 150 + p2p/tracker/tracker.go | 205 + p2p/transport.go | 183 + p2p/transport_test.go | 148 + p2p/util.go | 76 + p2p/util_test.go | 56 + params/bootnodes.go | 121 + params/config.go | 998 + params/config_test.go | 157 + params/dao.go | 158 + params/denomination.go | 27 + params/forks/forks.go | 42 + params/network_params.go | 67 + params/protocol_params.go | 196 + params/verkle_params.go | 36 + params/version.go | 67 + rlp/decode.go | 1202 + rlp/decode_tail_test.go | 49 + rlp/decode_test.go | 1286 + rlp/doc.go | 158 + rlp/encbuffer.go | 423 + rlp/encbuffer_example_test.go | 45 + rlp/encode.go | 495 + rlp/encode_test.go | 638 + rlp/encoder_example_test.go | 48 + rlp/internal/rlpstruct/rlpstruct.go | 213 + rlp/iterator.go | 59 + rlp/iterator_test.go | 59 + rlp/raw.go | 296 + rlp/raw_test.go | 338 + rlp/rlpgen/gen.go | 800 + rlp/rlpgen/gen_test.go | 107 + rlp/rlpgen/main.go | 144 + rlp/rlpgen/testdata/bigint.in.txt | 10 + rlp/rlpgen/testdata/bigint.out.txt | 49 + rlp/rlpgen/testdata/nil.in.txt | 30 + rlp/rlpgen/testdata/nil.out.txt | 289 + rlp/rlpgen/testdata/optional.in.txt | 17 + rlp/rlpgen/testdata/optional.out.txt | 153 + rlp/rlpgen/testdata/rawvalue.in.txt | 11 + rlp/rlpgen/testdata/rawvalue.out.txt | 64 + rlp/rlpgen/testdata/uint256.in.txt | 10 + rlp/rlpgen/testdata/uint256.out.txt | 44 + rlp/rlpgen/testdata/uints.in.txt | 10 + rlp/rlpgen/testdata/uints.out.txt | 53 + rlp/rlpgen/types.go | 124 + rlp/safe.go | 27 + rlp/typecache.go | 238 + rlp/unsafe.go | 30 + rpc/client.go | 725 + rpc/client_example_test.go | 89 + rpc/client_opt.go | 144 + rpc/client_opt_test.go | 25 + rpc/client_test.go | 882 + rpc/context_headers.go | 56 + rpc/doc.go | 109 + rpc/endpoints.go | 52 + rpc/errors.go | 156 + rpc/handler.go | 626 + rpc/http.go | 395 + rpc/http_test.go | 245 + rpc/inproc.go | 34 + rpc/ipc.go | 61 + rpc/ipc_js.go | 38 + rpc/ipc_unix.go | 64 + rpc/ipc_windows.go | 44 + rpc/json.go | 369 + rpc/metrics.go | 50 + rpc/server.go | 233 + rpc/server_test.go | 194 + rpc/service.go | 249 + rpc/stdio.go | 71 + rpc/subscription.go | 375 + rpc/subscription_test.go | 280 + rpc/testdata/internal-error.js | 7 + rpc/testdata/invalid-badid.js | 7 + rpc/testdata/invalid-badversion.js | 19 + rpc/testdata/invalid-batch-toolarge.js | 13 + rpc/testdata/invalid-batch.js | 17 + rpc/testdata/invalid-idonly.js | 7 + rpc/testdata/invalid-nonobj.js | 7 + rpc/testdata/invalid-syntax.json | 5 + rpc/testdata/reqresp-batch.js | 8 + rpc/testdata/reqresp-echo.js | 16 + rpc/testdata/reqresp-namedparam.js | 5 + rpc/testdata/reqresp-noargsrets.js | 4 + rpc/testdata/reqresp-nomethod.js | 4 + rpc/testdata/reqresp-noparam.js | 4 + rpc/testdata/reqresp-paramsnull.js | 4 + rpc/testdata/revcall.js | 6 + rpc/testdata/revcall2.js | 7 + rpc/testdata/subscription.js | 12 + rpc/testservice_test.go | 229 + rpc/types.go | 253 + rpc/types_test.go | 186 + rpc/websocket.go | 376 + rpc/websocket_test.go | 389 + signer/core/api.go | 673 + signer/core/api_test.go | 322 + .../apitypes/signed_data_internal_test.go | 242 + signer/core/apitypes/types.go | 944 + signer/core/apitypes/types_test.go | 143 + signer/core/auditlog.go | 127 + signer/core/cliui.go | 281 + signer/core/gnosis_safe.go | 117 + signer/core/signed_data.go | 345 + signer/core/signed_data_test.go | 1016 + signer/core/stdioui.go | 120 + signer/core/testdata/README.md | 5 + signer/core/testdata/arrays-1.json | 60 + signer/core/testdata/custom_arraytype.json | 54 + signer/core/testdata/eip712.json | 76 + .../testdata/expfail_arraytype_overload.json | 67 + .../core/testdata/expfail_datamismatch_1.json | 64 + signer/core/testdata/expfail_extradata.json | 77 + .../testdata/expfail_malformeddomainkeys.json | 64 + .../testdata/expfail_nonexistant_type.json | 64 + .../testdata/expfail_nonexistant_type2.json | 76 + .../core/testdata/expfail_toolargeuint.json | 38 + .../core/testdata/expfail_toolargeuint2.json | 38 + .../testdata/expfail_unconvertiblefloat.json | 38 + .../testdata/expfail_unconvertiblefloat2.json | 38 + .../2850f6ccf2d7f5f846dfb73119b60e09e712783f | 38 + .../36fb987a774011dc675e1b5246ac5c1d44d84d92 | 60 + .../37ec7b55c7ba014cced204c5f9989d2d0eb9ff6d | 38 + .../582fa92154b784daa1faa293b695fa388fe34bf1 | 1 + .../ab57cb2b2b5ce614efe13a47bc73814580f2cce8 | 54 + .../e4303e23ca34fbbc43164a232b2caa7a3af2bf8d | 64 + .../f658340af009dd4a35abe645a00a7b732bc30921 | 1 + signer/core/uiapi.go | 214 + signer/core/validation.go | 36 + signer/core/validation_test.go | 45 + signer/fourbyte/4byte.json | 268623 +++++++++++++++ signer/fourbyte/abi.go | 136 + signer/fourbyte/abi_test.go | 177 + signer/fourbyte/fourbyte.go | 140 + signer/fourbyte/fourbyte_test.go | 89 + signer/fourbyte/validation.go | 131 + signer/fourbyte/validation_test.go | 137 + signer/rules/rules.go | 240 + signer/rules/rules_test.go | 626 + signer/storage/aes_gcm_storage.go | 178 + signer/storage/aes_gcm_storage_test.go | 159 + signer/storage/storage.go | 86 + swarm/README.md | 7 + tests/block_test.go | 94 + tests/block_test_util.go | 381 + tests/difficulty_test.go | 111 + tests/difficulty_test_util.go | 68 + tests/evm-benchmarks | 1 + tests/fuzzers/README.md | 45 + tests/fuzzers/bls12381/bls12381_fuzz.go | 341 + tests/fuzzers/bls12381/bls12381_test.go | 112 + tests/fuzzers/bls12381/precompile_fuzzer.go | 103 + .../testdata/fuzz_g1_add_seed_corpus.zip | Bin 0 -> 41620 bytes .../testdata/fuzz_g1_mul_seed_corpus.zip | Bin 0 -> 37687 bytes .../testdata/fuzz_g1_multiexp_seed_corpus.zip | Bin 0 -> 521055 bytes .../testdata/fuzz_g2_add_seed_corpus.zip | Bin 0 -> 65856 bytes .../testdata/fuzz_g2_mul_seed_corpus.zip | Bin 0 -> 48203 bytes .../testdata/fuzz_g2_multiexp_seed_corpus.zip | Bin 0 -> 1377268 bytes .../testdata/fuzz_map_g1_seed_corpus.zip | Bin 0 -> 28135 bytes .../testdata/fuzz_map_g2_seed_corpus.zip | Bin 0 -> 31393 bytes .../testdata/fuzz_pairing_seed_corpus.zip | Bin 0 -> 55784 bytes tests/fuzzers/bn256/bn256_fuzz.go | 183 + tests/fuzzers/bn256/bn256_test.go | 37 + tests/fuzzers/difficulty/difficulty-fuzz.go | 146 + tests/fuzzers/difficulty/difficulty_test.go | 25 + ...1c14030f26872e57bf1481084f151d71eed8161c-1 | Bin 0 -> 16005 bytes ...27e54254422543060a13ea8a4bc913d768e4adb6-2 | Bin 0 -> 15965 bytes ...6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 | Bin 0 -> 15976 bytes ...a67e63bc0c0004bd009944a6061297cb7d4ac238-1 | Bin 0 -> 14980 bytes ...ae892bbae0a843950bc8316496e595b1a194c009-4 | Bin 0 -> 15977 bytes ...ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 | Bin 0 -> 16000 bytes ...f50a6d57a46d30184aa294af5b252ab9701af7c9-2 | Bin 0 -> 1748 bytes tests/fuzzers/rangeproof/corpus/random.dat | Bin 0 -> 16000 bytes tests/fuzzers/rangeproof/rangeproof-fuzzer.go | 199 + tests/fuzzers/rangeproof/rangeproof_test.go | 25 + tests/fuzzers/secp256k1/secp_test.go | 53 + ...0151ee1d0db4c74d3bcdfa4f7396a4c8538748c9-2 | 1 + ...020dd7b492a6eb34ff0b7d8ee46189422c37e4a7-6 | Bin 0 -> 14 bytes ...021d1144e359233c496e22c3250609b11b213e9f-4 | 12 + ...0d28327b1fb52c1ba02a6eb96675c31633921bb2-2 | 15 + ...0fcd827b57ded58e91f7ba2ac2b7ea4d25ebedca-7 | 1 + ...109bc9b8fd4fef63493e104c703c79bc4a5e8d34-6 | Bin 0 -> 470 bytes ...163785ab002746452619f31e8dfcb4549e6f8b6e-6 | Bin 0 -> 30 bytes ...1adfa6b9ddf5766220c8ff7ede2926ca241bb947-3 | 11 + ...1b9a02e9a48fea1d2fc3fb77946ada278e152079-4 | Bin 0 -> 17 bytes ...1e14c7ea1faef92890988061b5abe96db7190f98-7 | 1 + ...1e7d05f00e99cbf3ff0ef1cd7ea8dd07ad6dff23-6 | Bin 0 -> 19 bytes ...1ec95e347fd522e6385b5091aa81aa2485be4891-4 | Bin 0 -> 833 bytes ...1fbfa5d214060d2a0905846a589fd6f78d411451-4 | Bin 0 -> 22 bytes ...1fd84ee194e791783a7f18f0a6deab8efe05fc04-2 | 1 + ...21e76b9fca21d94d97f860c1c82f40697a83471b-8 | 3 + ...220a87fed0c92474923054094eb7aff14289cf5e-4 | Bin 0 -> 4 bytes ...23ddcd66aa92fe3d78b7f5b6e7cddb1b55c5f5df-3 | 12 + ...2441d249faf9a859e38c49f6e305b394280c6ea5-1 | Bin 0 -> 55 bytes ...2da1f0635e11283b1927974f418aadd8837ad31e-7 | Bin 0 -> 15 bytes ...2e1853fbf8efe40098b1583224fe3b5f335e7037-6 | Bin 0 -> 26 bytes ...2f25490dc49c103d653843ed47324b310ee7105e-7 | Bin 0 -> 23 bytes ...30494b85bb60ad7f099fa49d427007a761620d8f-5 | 10 + ...316024ca3aaf09c1de5258733ff5fe3d799648d3-4 | 15 + ...32a089e2c439a91f4c1b67a13d52429bcded0dd9-7 | Bin 0 -> 1665 bytes ...33ec1dc0bfeb93d16edee3c07125fec6ac1aa17d-2 | 1 + ...37a0d207700b52caa005ec8aeb344dcb13150ed2-5 | Bin 0 -> 55 bytes ...382f59c66d0ddb6747d3177263279789ca15c2db-5 | Bin 0 -> 14 bytes ...3a010483a4ad8d7215447ce27e0fac3791235c99-4 | 7 + ...3a3b717fcfe7ffb000b906e5a76f32248a576bf7-6 | Bin 0 -> 1141 bytes ...3c37f6d58b8029971935f127f53e6aaeba558445-6 | 2 + ...3c73b63bafa9f535c882ec17189adaf02b58f432-6 | 1 + ...3d11500c4f66b20c73bbdfb1a7bddd7bbf92b29c-5 | Bin 0 -> 453 bytes ...3d8b5bf36c80d6f65802280039f85421f32b5055-6 | Bin 0 -> 23 bytes ...3f99c546a3962256176d566c19e3fffb62072078-1 | 1 + ...408ec46539af27acd82b3d01e863597030882458-8 | Bin 0 -> 1884 bytes ...436154e5bb6487673f6642e6d2a582c01b083c08-8 | 1 + ...45f565cd14b8de1ba2e925047ce776c2682b4b8d-3 | Bin 0 -> 25 bytes ...4a0a12f5b033c8c160cc3b5133692ea1e92c6cdf-7 | 3 + ...550f15ef65230cc4dcfab7fea67de212d9212ff8-8 | Bin 0 -> 44 bytes ...5552213d659fef900a194c52718ffeffdc72d043-3 | Bin 0 -> 11 bytes ...5570ef82893a9b9b9158572d43a7de7537121d2d-1 | 1 + ...5e10f734f8af4116fbd164d96eec67aa53e6228c-5 | Bin 0 -> 40 bytes ...608200b402488b3989ec8ec5f4190ccb537b8ea4-4 | Bin 0 -> 24 bytes ...61e89c3fbdf9eff74bd250ea73cc2e61f8ca0d97-5 | 1 + ...62817a48c78fbf2c12fcdc5ca58e2ca60c43543a-7 | Bin 0 -> 27 bytes ...6782da8f1a432a77306d60d2ac2470c35b98004f-3 | 1 + ...68fb55290cb9d6da5b259017c34bcecf96c944aa-5 | Bin 0 -> 49 bytes ...6a5059bc86872526241d21ab5dae9f0afd3b9ae1-3 | 1 + ...717928e0e2d478c680c6409b173552ca98469ba5-6 | 1 + ...71d22f25419543e437f249ca437823b87ac926b1-6 | Bin 0 -> 27 bytes ...7312a0f31ae5d773ed4fd74abc7521eb14754683-8 | 2 + ...76e413a50dc8861e3756e556f796f1737bec2675-4 | Bin 0 -> 24 bytes ...78480977d5c07386b06e9b37f5c82f5ed86c2f09-3 | 14 + ...7a113cd3c178934cdb64353af86d51462d7080a4-5 | 10 + ...7ea9f71020f3eb783f743f744eba8d8ca4b2582f-3 | 9 + ...84f8c275f3ffbaf8c32c21782af13de10e7de28b-3 | 1 + ...85dfe7ddee0e52aa19115c0ebb9ed28a14e488c6-5 | Bin 0 -> 11 bytes ...87bba5b1e3da38fed8cb5a9bc5c8baa819e83d05-5 | Bin 0 -> 1137 bytes ...8a9ebedfbfec584d8b22761e6121dc1ca0248548-4 | Bin 0 -> 722 bytes ...8ff3bd49f93079e5e1c7f8f2461ba7ee612900c3-5 | Bin 0 -> 40 bytes ...9034aaf45143996a2b14465c352ab0c6fa26b221-2 | 1 + ...92cefdc6251d04896349a464b29be03d6bb04c3d-2 | 1 + ...9613e580ccb69df7c9074f0e2f6886ac6b34ca55-5 | Bin 0 -> 446 bytes ...98afc8970a680fdc4aee0b5d48784f650c566b75-6 | Bin 0 -> 31 bytes ...9dfc92f4ca2ece0167096fca6751ff314765f08b-8 | Bin 0 -> 23 bytes ...9ebcbbfdaf0e98c87652e57226a4d8a35170c67d-4 | 5 + ...9ff520eb8b8319a5fdafbe4d1cbb02a75058d93b-7 | 2 + ...a0b57a12e25ac5adcedb2a5c45915f0f62aee869-4 | Bin 0 -> 242 bytes ...a2684adccf16e036b051c12f283734fa803746e8-6 | Bin 0 -> 25 bytes ...a37305974cf477ecfe65fa92f37b1f51dea25910-4 | Bin 0 -> 15 bytes ...a7eb43926bd14b1f62a66a33107776e487434d32-7 | Bin 0 -> 516 bytes ...a8f7c254eb64a40fd2a77b79979c7bbdac6a760c-4 | 2 + ...a9a8f287d6af24e47d8db468e8f967aa44fb5a1f-7 | Bin 0 -> 106 bytes ...aa7444d8e326158046862590a0db993c07aef372-7 | 1 + ...ae4593626d8796e079a358c2395a4f6c9ddd6a44-6 | 8 + ...b2942d4413a66939cda7db93020dee79eb17788c-9 | Bin 0 -> 18 bytes ...b4614117cdfd147d38f4e8a4d85f5a2bb99a6a4f-5 | Bin 0 -> 838 bytes ...b631ef3291fa405cd6517d11f4d1b9b6d02912d4-2 | 1 + ...b7a91e338cc11f50ebdb2c414610efc4d5be3137-4 | Bin 0 -> 34 bytes ...b858cb282617fb0956d960215c8e84d1ccf909c6-2 | 1 + ...bc9d570aacf3acd39600feda8e72a293a4667da4-1 | 1 + ...be7eed35b245b5d5d2adcdb4c67f07794eb86b24-3 | 2 + ...c010b0cd70c7edbc5bd332fc9e2e91c6a1cbcdc4-5 | 4 + ...c1690698607eb0f4c4244e9f9629968be4beb6bc-8 | 2 + ...c1f435e4f53a9a17578d9e8c4789860f962a1379-6 | Bin 0 -> 1889 bytes ...c298a75334c3acf04bd129a8867447a25c8bacf8-7 | Bin 0 -> 27 bytes ...c42287c7d225e530e822f23bbbba6819a9e48f38-6 | Bin 0 -> 10 bytes ...4cdbb891f3ee76476b7375d5ed51691fed95421-10 | Bin 0 -> 16 bytes ...cc9572d72dfa2937074b1766dcbcff9cc58d1137-4 | Bin 0 -> 1522 bytes ...cd1d73b4e101bc7b979e3f6f135cb12d4594d348-5 | 1 + ...d0acdc8fca32bbd58d368eeac3bd9eaa46f59d27-5 | Bin 0 -> 9 bytes ...d0e43b715fd00953f7bdd6dfad95811985e81396-4 | Bin 0 -> 10 bytes ...d925fbd22c8bc0de34d6a9d1258ce3d2928d0927-8 | Bin 0 -> 22 bytes ...d9ba78cb7425724185d5fa300cd5c03aec2683bb-7 | Bin 0 -> 510 bytes .../da39a3ee5e6b4b0d3255bfef95601890afd80709 | 0 ...dcdb7758b87648b5d766b1b341a65834420cf621-7 | Bin 0 -> 22 bytes ...dd441bd24581332c9ce19e008260a69287aa3cbc-6 | 2 + ...def879fe0fd637a745c00c8f1da340518db8688c-2 | 1 + ...df6c30a9781b93bd6d2f5e97e5592d5945210003-7 | Bin 0 -> 1881 bytes ...dfc1c3a2e3ccdaf6f88c515fd00e8ad08421e431-6 | Bin 0 -> 10 bytes ...e1dcc4e7ead6dfd1139ece7bf57d776cb9dac72d-7 | Bin 0 -> 20 bytes ...e39c2de2c8937d2cbd4339b13d6a0ce94d94f8d2-8 | Bin 0 -> 1275 bytes ...e72f76b9579c792e545d02fe405d9186f0d6c39b-6 | Bin 0 -> 47 bytes ...eb70814d6355a4498b8f301ba8dbc34f895a9947-5 | Bin 0 -> 57 bytes ...ebdc17efe343e412634dca57cecd5a0e1ce1c1c7-5 | Bin 0 -> 54 bytes ...ec0a25eba8966b8f628d821b3cfbdf2dfd4bbb4c-3 | 13 + ...eebe3b76aeba6deed965d17d2b024f7eae1a43f1-5 | Bin 0 -> 10 bytes ...ef8741a9faf030794d98ff113f556c68a24719a5-6 | Bin 0 -> 31 bytes ...efb7410d02418befeba25a43d676cc6124129125-4 | 1 + ...f6f97d781a5a749903790e07db8619866cb7c3a1-6 | Bin 0 -> 22 bytes ...f7a3cd00fa0e57742e7dbbb8283dcaea067eaf7b-5 | 2 + ...f94d60a6c556ce485ab60088291760b8be25776c-6 | 2 + ...f9e627b2cb82ffa1ea5e0c6d7f2802f3000b18a8-6 | Bin 0 -> 11 bytes ...fb3775aa24e5667e658920c05ba4b7b19ff256fb-5 | 1 + ...fd6386548e119a50db96b2fa406e54924c45a2d5-6 | Bin 0 -> 28 bytes tests/fuzzers/txfetcher/txfetcher_fuzzer.go | 210 + tests/fuzzers/txfetcher/txfetcher_test.go | 25 + tests/gen_btheader.go | 160 + tests/gen_difficultytest.go | 68 + tests/gen_stenv.go | 85 + tests/gen_sttransaction.go | 120 + tests/init.go | 416 + tests/init_test.go | 292 + tests/rlp_test.go | 31 + tests/rlp_test_util.go | 172 + tests/solidity/bytecode.js | 22 + tests/solidity/contracts/Migrations.sol | 23 + tests/solidity/contracts/OpCodes.sol | 322 + .../migrations/1_initial_migration.js | 21 + .../migrations/2_opCodes_migration.js | 21 + tests/solidity/test/opCodes.js | 50 + tests/solidity/truffle-config.js | 124 + tests/state_test.go | 348 + tests/state_test_util.go | 503 + tests/testdata | 1 + tests/transaction_test.go | 54 + tests/transaction_test_util.go | 110 + trie/committer.go | 182 + trie/database_test.go | 152 + trie/encoding.go | 144 + trie/encoding_test.go | 146 + trie/errors.go | 52 + trie/hasher.go | 207 + trie/iterator.go | 838 + trie/iterator_test.go | 637 + trie/node.go | 254 + trie/node_enc.go | 64 + trie/node_test.go | 215 + trie/proof.go | 616 + trie/proof_test.go | 1002 + trie/secure_trie.go | 318 + trie/secure_trie_test.go | 149 + trie/stacktrie.go | 391 + trie/stacktrie_fuzzer_test.go | 147 + trie/stacktrie_test.go | 400 + trie/sync.go | 768 + trie/sync_test.go | 1002 + trie/tracer.go | 122 + trie/tracer_test.go | 376 + trie/trie.go | 683 + trie/trie_id.go | 55 + trie/trie_reader.go | 73 + trie/trie_test.go | 1209 + trie/trienode/node.go | 192 + trie/trienode/node_test.go | 61 + trie/trienode/proof.go | 162 + trie/triestate/state.go | 53 + trie/utils/verkle.go | 337 + trie/utils/verkle_test.go | 139 + trie/verkle.go | 374 + trie/verkle_test.go | 91 + triedb/database.go | 330 + triedb/database/database.go | 37 + triedb/hashdb/database.go | 646 + triedb/history.go | 72 + triedb/pathdb/database.go | 526 + triedb/pathdb/database_test.go | 671 + triedb/pathdb/difflayer.go | 156 + triedb/pathdb/difflayer_test.go | 172 + triedb/pathdb/disklayer.go | 316 + triedb/pathdb/errors.go | 42 + triedb/pathdb/execute.go | 186 + triedb/pathdb/history.go | 628 + triedb/pathdb/history_inspect.go | 151 + triedb/pathdb/history_test.go | 329 + triedb/pathdb/journal.go | 385 + triedb/pathdb/layertree.go | 214 + triedb/pathdb/metrics.go | 51 + triedb/pathdb/nodebuffer.go | 283 + triedb/pathdb/reader.go | 94 + triedb/preimages.go | 95 + 1988 files changed, 698315 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 .github/CODEOWNERS create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/no-response.yml create mode 100644 .github/stale.yml create mode 100644 .github/workflows/ci-ecr.yml create mode 100644 .github/workflows/ci-geth-s3.yml.DEPRECATED create mode 100644 .github/workflows/ci-s3.yml create mode 100644 .github/workflows/go.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .golangci.yml create mode 100644 .mailmap create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 COPYING.LESSER create mode 100644 Dockerfile create mode 100644 Dockerfile.alltools create mode 100644 Makefile create mode 100644 SECURITY.md create mode 100644 accounts/abi/abi.go create mode 100644 accounts/abi/abi_test.go create mode 100644 accounts/abi/abifuzzer_test.go create mode 100644 accounts/abi/argument.go create mode 100644 accounts/abi/bind/auth.go create mode 100644 accounts/abi/bind/backend.go create mode 100644 accounts/abi/bind/backends/simulated.go create mode 100644 accounts/abi/bind/base.go create mode 100644 accounts/abi/bind/base_test.go create mode 100644 accounts/abi/bind/bind.go create mode 100644 accounts/abi/bind/bind_test.go create mode 100644 accounts/abi/bind/source.go.tpl create mode 100644 accounts/abi/bind/template.go create mode 100644 accounts/abi/bind/util.go create mode 100644 accounts/abi/bind/util_test.go create mode 100644 accounts/abi/doc.go create mode 100644 accounts/abi/error.go create mode 100644 accounts/abi/error_handling.go create mode 100644 accounts/abi/event.go create mode 100644 accounts/abi/event_test.go create mode 100644 accounts/abi/method.go create mode 100644 accounts/abi/method_test.go create mode 100644 accounts/abi/pack.go create mode 100644 accounts/abi/pack_test.go create mode 100644 accounts/abi/packing_test.go create mode 100644 accounts/abi/reflect.go create mode 100644 accounts/abi/reflect_test.go create mode 100644 accounts/abi/selector_parser.go create mode 100644 accounts/abi/selector_parser_test.go create mode 100644 accounts/abi/topics.go create mode 100644 accounts/abi/topics_test.go create mode 100644 accounts/abi/type.go create mode 100644 accounts/abi/type_test.go create mode 100644 accounts/abi/unpack.go create mode 100644 accounts/abi/unpack_test.go create mode 100644 accounts/abi/utils.go create mode 100644 accounts/accounts.go create mode 100644 accounts/accounts_test.go create mode 100644 accounts/errors.go create mode 100644 accounts/external/backend.go create mode 100644 accounts/hd.go create mode 100644 accounts/hd_test.go create mode 100644 accounts/keystore/account_cache.go create mode 100644 accounts/keystore/account_cache_test.go create mode 100644 accounts/keystore/file_cache.go create mode 100644 accounts/keystore/key.go create mode 100644 accounts/keystore/keystore.go create mode 100644 accounts/keystore/keystore_fuzzing_test.go create mode 100644 accounts/keystore/keystore_test.go create mode 100644 accounts/keystore/passphrase.go create mode 100644 accounts/keystore/passphrase_test.go create mode 100644 accounts/keystore/plain.go create mode 100644 accounts/keystore/plain_test.go create mode 100644 accounts/keystore/presale.go create mode 100644 accounts/keystore/testdata/dupes/1 create mode 100644 accounts/keystore/testdata/dupes/2 create mode 100644 accounts/keystore/testdata/dupes/foo create mode 100644 accounts/keystore/testdata/keystore/.hiddenfile create mode 100644 accounts/keystore/testdata/keystore/README create mode 100644 accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 create mode 100644 accounts/keystore/testdata/keystore/aaa create mode 100644 accounts/keystore/testdata/keystore/empty create mode 100644 accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e create mode 100644 accounts/keystore/testdata/keystore/garbage create mode 100644 accounts/keystore/testdata/keystore/no-address create mode 100644 accounts/keystore/testdata/keystore/zero create mode 100644 accounts/keystore/testdata/keystore/zzz create mode 100644 accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e create mode 100644 accounts/keystore/testdata/v1_test_vector.json create mode 100644 accounts/keystore/testdata/v3_test_vector.json create mode 100644 accounts/keystore/testdata/very-light-scrypt.json create mode 100644 accounts/keystore/wallet.go create mode 100644 accounts/keystore/watch.go create mode 100644 accounts/keystore/watch_fallback.go create mode 100644 accounts/manager.go create mode 100644 accounts/scwallet/README.md create mode 100644 accounts/scwallet/apdu.go create mode 100644 accounts/scwallet/hub.go create mode 100644 accounts/scwallet/securechannel.go create mode 100644 accounts/scwallet/wallet.go create mode 100644 accounts/sort.go create mode 100644 accounts/url.go create mode 100644 accounts/url_test.go create mode 100644 accounts/usbwallet/hub.go create mode 100644 accounts/usbwallet/ledger.go create mode 100644 accounts/usbwallet/trezor.go create mode 100644 accounts/usbwallet/trezor/messages-common.pb.go create mode 100644 accounts/usbwallet/trezor/messages-common.proto create mode 100644 accounts/usbwallet/trezor/messages-ethereum.pb.go create mode 100644 accounts/usbwallet/trezor/messages-ethereum.proto create mode 100644 accounts/usbwallet/trezor/messages-management.pb.go create mode 100644 accounts/usbwallet/trezor/messages-management.proto create mode 100644 accounts/usbwallet/trezor/messages.pb.go create mode 100644 accounts/usbwallet/trezor/messages.proto create mode 100644 accounts/usbwallet/trezor/trezor.go create mode 100644 accounts/usbwallet/wallet.go create mode 100644 appveyor.yml create mode 100755 beacon/blsync/block_sync.go create mode 100644 beacon/blsync/block_sync_test.go create mode 100644 beacon/blsync/client.go create mode 100644 beacon/blsync/config.go create mode 100644 beacon/blsync/engineclient.go create mode 100644 beacon/engine/errors.go create mode 100644 beacon/engine/gen_blockparams.go create mode 100644 beacon/engine/gen_ed.go create mode 100644 beacon/engine/gen_epe.go create mode 100644 beacon/engine/types.go create mode 100755 beacon/light/api/api_server.go create mode 100755 beacon/light/api/light_api.go create mode 100644 beacon/light/canonical.go create mode 100644 beacon/light/committee_chain.go create mode 100644 beacon/light/committee_chain_test.go create mode 100644 beacon/light/head_tracker.go create mode 100644 beacon/light/range.go create mode 100644 beacon/light/request/scheduler.go create mode 100644 beacon/light/request/scheduler_test.go create mode 100644 beacon/light/request/server.go create mode 100644 beacon/light/request/server_test.go create mode 100644 beacon/light/sync/head_sync.go create mode 100644 beacon/light/sync/head_sync_test.go create mode 100644 beacon/light/sync/test_helpers.go create mode 100644 beacon/light/sync/types.go create mode 100644 beacon/light/sync/update_sync.go create mode 100644 beacon/light/sync/update_sync_test.go create mode 100644 beacon/light/test_helpers.go create mode 100644 beacon/merkle/merkle.go create mode 100644 beacon/params/params.go create mode 100644 beacon/types/beacon_block.go create mode 100644 beacon/types/beacon_block_test.go create mode 100644 beacon/types/committee.go create mode 100644 beacon/types/config.go create mode 100644 beacon/types/exec_header.go create mode 100644 beacon/types/exec_payload.go create mode 100644 beacon/types/gen_header_json.go create mode 100644 beacon/types/gen_syncaggregate_json.go create mode 100644 beacon/types/header.go create mode 100644 beacon/types/light_sync.go create mode 100644 beacon/types/testdata/block_capella.json create mode 100644 beacon/types/testdata/block_deneb.json create mode 100644 build/bot/macos-build.sh create mode 100644 build/bot/ppa-build.sh create mode 100644 build/checksums.txt create mode 100644 build/ci-notes.md create mode 100644 build/ci.go create mode 100755 build/deb/ethereum/completions/bash/geth create mode 100644 build/deb/ethereum/completions/zsh/_geth create mode 100644 build/deb/ethereum/deb.changelog create mode 100644 build/deb/ethereum/deb.control create mode 100644 build/deb/ethereum/deb.copyright create mode 100644 build/deb/ethereum/deb.docs create mode 100644 build/deb/ethereum/deb.install create mode 100644 build/deb/ethereum/deb.rules create mode 100755 build/goimports.sh create mode 100644 build/nsis.envvarupdate.nsh create mode 100644 build/nsis.geth.nsi create mode 100644 build/nsis.install.nsh create mode 100644 build/nsis.pathupdate.nsh create mode 100644 build/nsis.simplefc.dll create mode 100644 build/nsis.simplefc.source.zip create mode 100644 build/nsis.uninstall.nsh create mode 100644 build/tools/tools.go create mode 100755 build/travis_keepalive.sh create mode 100644 build/update-license.go create mode 100644 circle.yml create mode 100644 cmd/abidump/main.go create mode 100644 cmd/abigen/main.go create mode 100644 cmd/abigen/namefilter.go create mode 100644 cmd/abigen/namefilter_test.go create mode 100644 cmd/blsync/main.go create mode 100644 cmd/bootnode/main.go create mode 100644 cmd/clef/README.md create mode 100644 cmd/clef/consolecmd_test.go create mode 100644 cmd/clef/datatypes.md create mode 100644 cmd/clef/docs/clef_architecture_pt1.png create mode 100644 cmd/clef/docs/clef_architecture_pt2.png create mode 100644 cmd/clef/docs/clef_architecture_pt3.png create mode 100644 cmd/clef/docs/clef_architecture_pt4.png create mode 100644 cmd/clef/docs/qubes/clef_qubes_http.png create mode 100644 cmd/clef/docs/qubes/clef_qubes_qrexec.png create mode 100644 cmd/clef/docs/qubes/qrexec-example.png create mode 100644 cmd/clef/docs/qubes/qubes-client.py create mode 100644 cmd/clef/docs/qubes/qubes.Clefsign create mode 100644 cmd/clef/docs/qubes/qubes_newaccount-1.png create mode 100644 cmd/clef/docs/qubes/qubes_newaccount-2.png create mode 100644 cmd/clef/docs/setup.md create mode 100644 cmd/clef/extapi_changelog.md create mode 100644 cmd/clef/intapi_changelog.md create mode 100644 cmd/clef/main.go create mode 100644 cmd/clef/pythonsigner.py create mode 100644 cmd/clef/requirements.txt create mode 100644 cmd/clef/rules.md create mode 100644 cmd/clef/run_test.go create mode 100644 cmd/clef/sign_flow.png create mode 100644 cmd/clef/testdata/sign_1559_missing_field_exp_fail.json create mode 100644 cmd/clef/testdata/sign_1559_missing_maxfeepergas_exp_fail.json create mode 100644 cmd/clef/testdata/sign_1559_tx.json create mode 100644 cmd/clef/testdata/sign_bad_checksum_exp_fail.json create mode 100644 cmd/clef/testdata/sign_normal_exp_ok.json create mode 100644 cmd/clef/tests/testsigner.js create mode 100644 cmd/clef/tutorial.md create mode 100644 cmd/devp2p/README.md create mode 100644 cmd/devp2p/crawl.go create mode 100644 cmd/devp2p/discv4cmd.go create mode 100644 cmd/devp2p/discv5cmd.go create mode 100644 cmd/devp2p/dns_cloudflare.go create mode 100644 cmd/devp2p/dns_route53.go create mode 100644 cmd/devp2p/dns_route53_test.go create mode 100644 cmd/devp2p/dnscmd.go create mode 100644 cmd/devp2p/enrcmd.go create mode 100644 cmd/devp2p/internal/ethtest/chain.go create mode 100644 cmd/devp2p/internal/ethtest/chain_test.go create mode 100644 cmd/devp2p/internal/ethtest/conn.go create mode 100644 cmd/devp2p/internal/ethtest/engine.go create mode 100644 cmd/devp2p/internal/ethtest/mkchain.sh create mode 100644 cmd/devp2p/internal/ethtest/protocol.go create mode 100644 cmd/devp2p/internal/ethtest/snap.go create mode 100644 cmd/devp2p/internal/ethtest/suite.go create mode 100644 cmd/devp2p/internal/ethtest/suite_test.go create mode 100644 cmd/devp2p/internal/ethtest/testdata/accounts.json create mode 100644 cmd/devp2p/internal/ethtest/testdata/chain.rlp create mode 100644 cmd/devp2p/internal/ethtest/testdata/forkenv.json create mode 100644 cmd/devp2p/internal/ethtest/testdata/genesis.json create mode 100644 cmd/devp2p/internal/ethtest/testdata/headblock.json create mode 100644 cmd/devp2p/internal/ethtest/testdata/headfcu.json create mode 100644 cmd/devp2p/internal/ethtest/testdata/headstate.json create mode 100644 cmd/devp2p/internal/ethtest/testdata/newpayload.json create mode 100644 cmd/devp2p/internal/ethtest/testdata/txinfo.json create mode 100644 cmd/devp2p/internal/ethtest/transaction.go create mode 100644 cmd/devp2p/internal/v4test/discv4tests.go create mode 100644 cmd/devp2p/internal/v4test/framework.go create mode 100644 cmd/devp2p/internal/v5test/discv5tests.go create mode 100644 cmd/devp2p/internal/v5test/framework.go create mode 100644 cmd/devp2p/keycmd.go create mode 100644 cmd/devp2p/main.go create mode 100644 cmd/devp2p/nodeset.go create mode 100644 cmd/devp2p/nodesetcmd.go create mode 100644 cmd/devp2p/rlpxcmd.go create mode 100644 cmd/devp2p/runtest.go create mode 100644 cmd/era/main.go create mode 100644 cmd/ethkey/README.md create mode 100644 cmd/ethkey/changepassword.go create mode 100644 cmd/ethkey/generate.go create mode 100644 cmd/ethkey/inspect.go create mode 100644 cmd/ethkey/main.go create mode 100644 cmd/ethkey/message.go create mode 100644 cmd/ethkey/message_test.go create mode 100644 cmd/ethkey/run_test.go create mode 100644 cmd/ethkey/utils.go create mode 100644 cmd/evm/README.md create mode 100644 cmd/evm/blockrunner.go create mode 100644 cmd/evm/compiler.go create mode 100644 cmd/evm/disasm.go create mode 100644 cmd/evm/internal/compiler/compiler.go create mode 100644 cmd/evm/internal/t8ntool/block.go create mode 100644 cmd/evm/internal/t8ntool/execution.go create mode 100644 cmd/evm/internal/t8ntool/flags.go create mode 100644 cmd/evm/internal/t8ntool/gen_header.go create mode 100644 cmd/evm/internal/t8ntool/gen_stenv.go create mode 100644 cmd/evm/internal/t8ntool/transaction.go create mode 100644 cmd/evm/internal/t8ntool/transition.go create mode 100644 cmd/evm/internal/t8ntool/tx_iterator.go create mode 100644 cmd/evm/internal/t8ntool/utils.go create mode 100644 cmd/evm/main.go create mode 100644 cmd/evm/runner.go create mode 100644 cmd/evm/staterunner.go create mode 100644 cmd/evm/t8n_test.go create mode 100644 cmd/evm/testdata/1/alloc.json create mode 100644 cmd/evm/testdata/1/env.json create mode 100644 cmd/evm/testdata/1/exp.json create mode 100644 cmd/evm/testdata/1/txs.json create mode 100644 cmd/evm/testdata/10/alloc.json create mode 100644 cmd/evm/testdata/10/env.json create mode 100644 cmd/evm/testdata/10/readme.md create mode 100644 cmd/evm/testdata/10/txs.json create mode 100644 cmd/evm/testdata/11/alloc.json create mode 100644 cmd/evm/testdata/11/env.json create mode 100644 cmd/evm/testdata/11/readme.md create mode 100644 cmd/evm/testdata/11/txs.json create mode 100644 cmd/evm/testdata/12/alloc.json create mode 100644 cmd/evm/testdata/12/env.json create mode 100644 cmd/evm/testdata/12/readme.md create mode 100644 cmd/evm/testdata/12/txs.json create mode 100644 cmd/evm/testdata/13/alloc.json create mode 100644 cmd/evm/testdata/13/env.json create mode 100644 cmd/evm/testdata/13/exp.json create mode 100644 cmd/evm/testdata/13/exp2.json create mode 100644 cmd/evm/testdata/13/readme.md create mode 100644 cmd/evm/testdata/13/signed_txs.rlp create mode 100644 cmd/evm/testdata/13/txs.json create mode 100644 cmd/evm/testdata/14/alloc.json create mode 100644 cmd/evm/testdata/14/env.json create mode 100644 cmd/evm/testdata/14/env.uncles.json create mode 100644 cmd/evm/testdata/14/exp.json create mode 100644 cmd/evm/testdata/14/exp2.json create mode 100644 cmd/evm/testdata/14/exp_berlin.json create mode 100644 cmd/evm/testdata/14/readme.md create mode 100644 cmd/evm/testdata/14/txs.json create mode 100644 cmd/evm/testdata/15/blockheader.rlp create mode 100644 cmd/evm/testdata/15/exp.json create mode 100644 cmd/evm/testdata/15/exp2.json create mode 100644 cmd/evm/testdata/15/exp3.json create mode 100644 cmd/evm/testdata/15/signed_txs.rlp create mode 100644 cmd/evm/testdata/15/signed_txs.rlp.json create mode 100644 cmd/evm/testdata/16/exp.json create mode 100644 cmd/evm/testdata/16/signed_txs.rlp create mode 100644 cmd/evm/testdata/16/unsigned_txs.json create mode 100644 cmd/evm/testdata/17/exp.json create mode 100644 cmd/evm/testdata/17/rlpdata.txt create mode 100644 cmd/evm/testdata/17/signed_txs.rlp create mode 100644 cmd/evm/testdata/18/README.md create mode 100644 cmd/evm/testdata/18/invalid.rlp create mode 100644 cmd/evm/testdata/19/alloc.json create mode 100644 cmd/evm/testdata/19/env.json create mode 100644 cmd/evm/testdata/19/exp_arrowglacier.json create mode 100644 cmd/evm/testdata/19/exp_grayglacier.json create mode 100644 cmd/evm/testdata/19/exp_london.json create mode 100644 cmd/evm/testdata/19/readme.md create mode 100644 cmd/evm/testdata/19/txs.json create mode 100644 cmd/evm/testdata/2/alloc.json create mode 100644 cmd/evm/testdata/2/env.json create mode 100644 cmd/evm/testdata/2/readme.md create mode 100644 cmd/evm/testdata/2/txs.json create mode 100644 cmd/evm/testdata/20/exp.json create mode 100644 cmd/evm/testdata/20/header.json create mode 100644 cmd/evm/testdata/20/ommers.json create mode 100644 cmd/evm/testdata/20/readme.md create mode 100644 cmd/evm/testdata/20/txs.rlp create mode 100644 cmd/evm/testdata/21/clique.json create mode 100644 cmd/evm/testdata/21/exp-clique.json create mode 100644 cmd/evm/testdata/21/exp.json create mode 100644 cmd/evm/testdata/21/header.json create mode 100644 cmd/evm/testdata/21/ommers.json create mode 100644 cmd/evm/testdata/21/readme.md create mode 100644 cmd/evm/testdata/21/txs.rlp create mode 100644 cmd/evm/testdata/22/exp-clique.json create mode 100644 cmd/evm/testdata/22/exp.json create mode 100644 cmd/evm/testdata/22/header.json create mode 100644 cmd/evm/testdata/22/ommers.json create mode 100644 cmd/evm/testdata/22/readme.md create mode 100644 cmd/evm/testdata/22/txs.rlp create mode 100644 cmd/evm/testdata/23/alloc.json create mode 100644 cmd/evm/testdata/23/env.json create mode 100644 cmd/evm/testdata/23/exp.json create mode 100644 cmd/evm/testdata/23/readme.md create mode 100644 cmd/evm/testdata/23/txs.json create mode 100644 cmd/evm/testdata/24/alloc.json create mode 100644 cmd/evm/testdata/24/env-missingrandom.json create mode 100644 cmd/evm/testdata/24/env.json create mode 100644 cmd/evm/testdata/24/exp.json create mode 100644 cmd/evm/testdata/24/txs.json create mode 100644 cmd/evm/testdata/25/alloc.json create mode 100644 cmd/evm/testdata/25/env.json create mode 100644 cmd/evm/testdata/25/exp.json create mode 100644 cmd/evm/testdata/25/txs.json create mode 100644 cmd/evm/testdata/26/alloc.json create mode 100644 cmd/evm/testdata/26/env.json create mode 100644 cmd/evm/testdata/26/exp.json create mode 100644 cmd/evm/testdata/26/txs.json create mode 100644 cmd/evm/testdata/27/exp.json create mode 100644 cmd/evm/testdata/27/header.json create mode 100644 cmd/evm/testdata/27/ommers.json create mode 100644 cmd/evm/testdata/27/txs.rlp create mode 100644 cmd/evm/testdata/27/withdrawals.json create mode 100644 cmd/evm/testdata/28/alloc.json create mode 100644 cmd/evm/testdata/28/env.json create mode 100644 cmd/evm/testdata/28/exp.json create mode 100644 cmd/evm/testdata/28/txs.rlp create mode 100644 cmd/evm/testdata/29/alloc.json create mode 100644 cmd/evm/testdata/29/env.json create mode 100644 cmd/evm/testdata/29/exp.json create mode 100644 cmd/evm/testdata/29/readme.md create mode 100644 cmd/evm/testdata/29/txs.json create mode 100644 cmd/evm/testdata/3/alloc.json create mode 100644 cmd/evm/testdata/3/env.json create mode 100644 cmd/evm/testdata/3/exp.json create mode 100644 cmd/evm/testdata/3/readme.md create mode 100644 cmd/evm/testdata/3/txs.json create mode 100644 cmd/evm/testdata/30/README.txt create mode 100644 cmd/evm/testdata/30/alloc.json create mode 100644 cmd/evm/testdata/30/env.json create mode 100644 cmd/evm/testdata/30/exp.json create mode 100644 cmd/evm/testdata/30/txs.rlp create mode 100644 cmd/evm/testdata/30/txs_more.rlp create mode 100644 cmd/evm/testdata/31/README.md create mode 100644 cmd/evm/testdata/31/alloc.json create mode 100644 cmd/evm/testdata/31/env.json create mode 100644 cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json create mode 100644 cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl create mode 100644 cmd/evm/testdata/31/txs.json create mode 100644 cmd/evm/testdata/32/README.md create mode 100644 cmd/evm/testdata/32/alloc.json create mode 100644 cmd/evm/testdata/32/env.json create mode 100644 cmd/evm/testdata/32/trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl create mode 100644 cmd/evm/testdata/32/txs.json create mode 100644 cmd/evm/testdata/4/alloc.json create mode 100644 cmd/evm/testdata/4/env.json create mode 100644 cmd/evm/testdata/4/readme.md create mode 100644 cmd/evm/testdata/4/txs.json create mode 100644 cmd/evm/testdata/5/alloc.json create mode 100644 cmd/evm/testdata/5/env.json create mode 100644 cmd/evm/testdata/5/exp.json create mode 100644 cmd/evm/testdata/5/readme.md create mode 100644 cmd/evm/testdata/5/txs.json create mode 100644 cmd/evm/testdata/7/alloc.json create mode 100644 cmd/evm/testdata/7/env.json create mode 100644 cmd/evm/testdata/7/readme.md create mode 100644 cmd/evm/testdata/7/txs.json create mode 100644 cmd/evm/testdata/8/alloc.json create mode 100644 cmd/evm/testdata/8/env.json create mode 100644 cmd/evm/testdata/8/readme.md create mode 100644 cmd/evm/testdata/8/txs.json create mode 100644 cmd/evm/testdata/9/alloc.json create mode 100644 cmd/evm/testdata/9/env.json create mode 100644 cmd/evm/testdata/9/readme.md create mode 100644 cmd/evm/testdata/9/txs.json create mode 100644 cmd/evm/testdata/9/txs_signed.json create mode 100644 cmd/evm/transition-test.sh create mode 100644 cmd/geth/accountcmd.go create mode 100644 cmd/geth/accountcmd_test.go create mode 100644 cmd/geth/attach_test.go create mode 100644 cmd/geth/chaincmd.go create mode 100644 cmd/geth/config.go create mode 100644 cmd/geth/consolecmd.go create mode 100644 cmd/geth/consolecmd_test.go create mode 100644 cmd/geth/dbcmd.go create mode 100644 cmd/geth/exportcmd_test.go create mode 100644 cmd/geth/genesis_test.go create mode 100644 cmd/geth/logging_test.go create mode 100644 cmd/geth/logtestcmd_active.go create mode 100644 cmd/geth/logtestcmd_inactive.go create mode 100644 cmd/geth/main.go create mode 100644 cmd/geth/misccmd.go create mode 100644 cmd/geth/run_test.go create mode 100644 cmd/geth/snapshot.go create mode 100644 cmd/geth/testdata/blockchain.blocks create mode 100644 cmd/geth/testdata/clique.json create mode 100644 cmd/geth/testdata/empty.js create mode 100644 cmd/geth/testdata/guswallet.json create mode 100644 cmd/geth/testdata/key.prv create mode 100644 cmd/geth/testdata/logging/logtest-json.txt create mode 100644 cmd/geth/testdata/logging/logtest-logfmt.txt create mode 100644 cmd/geth/testdata/logging/logtest-terminal.txt create mode 100644 cmd/geth/testdata/password.txt create mode 100644 cmd/geth/testdata/passwords.txt create mode 100644 cmd/geth/testdata/vcheck/data.json create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs-new/data.json.minisig create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 create mode 100644 cmd/geth/testdata/vcheck/minisign.pub create mode 100644 cmd/geth/testdata/vcheck/minisign.sec create mode 100644 cmd/geth/testdata/vcheck/signify-sigs/data.json.sig create mode 100644 cmd/geth/testdata/vcheck/signifykey.pub create mode 100644 cmd/geth/testdata/vcheck/signifykey.sec create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 create mode 100644 cmd/geth/testdata/vcheck/vulnerabilities.json create mode 100644 cmd/geth/testdata/wrong-passwords.txt create mode 100644 cmd/geth/verkle.go create mode 100644 cmd/geth/version_check.go create mode 100644 cmd/geth/version_check_test.go create mode 100644 cmd/p2psim/main.go create mode 100644 cmd/rlpdump/main.go create mode 100644 cmd/rlpdump/rlpdump_test.go create mode 100644 cmd/utils/cmd.go create mode 100644 cmd/utils/diskusage.go create mode 100644 cmd/utils/diskusage_openbsd.go create mode 100644 cmd/utils/diskusage_windows.go create mode 100644 cmd/utils/export_test.go create mode 100644 cmd/utils/flags.go create mode 100644 cmd/utils/flags_legacy.go create mode 100644 cmd/utils/flags_test.go create mode 100644 cmd/utils/history_test.go create mode 100644 cmd/utils/prompt.go create mode 100644 cmd/utils/prompt_test.go create mode 100644 common/big.go create mode 100644 common/bitutil/bitutil.go create mode 100644 common/bitutil/bitutil_test.go create mode 100644 common/bitutil/compress.go create mode 100644 common/bitutil/compress_test.go create mode 100644 common/bytes.go create mode 100644 common/bytes_test.go create mode 100644 common/compiler/helpers.go create mode 100644 common/compiler/solidity.go create mode 100644 common/debug.go create mode 100644 common/fdlimit/fdlimit_bsd.go create mode 100644 common/fdlimit/fdlimit_darwin.go create mode 100644 common/fdlimit/fdlimit_test.go create mode 100644 common/fdlimit/fdlimit_unix.go create mode 100644 common/fdlimit/fdlimit_windows.go create mode 100644 common/format.go create mode 100644 common/hexutil/hexutil.go create mode 100644 common/hexutil/hexutil_test.go create mode 100644 common/hexutil/json.go create mode 100644 common/hexutil/json_example_test.go create mode 100644 common/hexutil/json_test.go create mode 100644 common/lru/basiclru.go create mode 100644 common/lru/basiclru_test.go create mode 100644 common/lru/blob_lru.go create mode 100644 common/lru/blob_lru_test.go create mode 100644 common/lru/lru.go create mode 100644 common/math/big.go create mode 100644 common/math/big_test.go create mode 100644 common/math/integer.go create mode 100644 common/math/integer_test.go create mode 100644 common/mclock/alarm.go create mode 100644 common/mclock/alarm_test.go create mode 100644 common/mclock/mclock.go create mode 100644 common/mclock/mclock.s create mode 100644 common/mclock/simclock.go create mode 100644 common/mclock/simclock_test.go create mode 100644 common/path.go create mode 100644 common/prque/lazyqueue.go create mode 100644 common/prque/lazyqueue_test.go create mode 100755 common/prque/prque.go create mode 100644 common/prque/prque_test.go create mode 100755 common/prque/sstack.go create mode 100644 common/prque/sstack_test.go create mode 100644 common/size.go create mode 100644 common/size_test.go create mode 100644 common/test_utils.go create mode 100644 common/types.go create mode 100644 common/types_test.go create mode 100644 consensus/beacon/consensus.go create mode 100644 consensus/beacon/faker.go create mode 100644 consensus/clique/api.go create mode 100644 consensus/clique/clique.go create mode 100644 consensus/clique/clique_test.go create mode 100644 consensus/clique/snapshot.go create mode 100644 consensus/clique/snapshot_test.go create mode 100644 consensus/consensus.go create mode 100644 consensus/errors.go create mode 100644 consensus/ethash/consensus.go create mode 100644 consensus/ethash/consensus_test.go create mode 100644 consensus/ethash/difficulty.go create mode 100644 consensus/ethash/ethash.go create mode 100644 consensus/misc/dao.go create mode 100644 consensus/misc/eip1559/eip1559.go create mode 100644 consensus/misc/eip1559/eip1559_test.go create mode 100644 consensus/misc/eip4844/eip4844.go create mode 100644 consensus/misc/eip4844/eip4844_test.go create mode 100644 consensus/misc/gaslimit.go create mode 100644 console/bridge.go create mode 100644 console/bridge_test.go create mode 100644 console/console.go create mode 100644 console/console_test.go create mode 100644 console/prompt/prompter.go create mode 100644 console/testdata/preload.js create mode 100644 core/.gitignore create mode 100644 core/asm/asm.go create mode 100644 core/asm/asm_test.go create mode 100644 core/asm/compiler.go create mode 100644 core/asm/compiler_test.go create mode 100644 core/asm/lex_test.go create mode 100644 core/asm/lexer.go create mode 100644 core/asm/tokentype_string.go create mode 100644 core/bench_test.go create mode 100644 core/block_validator.go create mode 100644 core/block_validator_test.go create mode 100644 core/blockchain.go create mode 100644 core/blockchain_insert.go create mode 100644 core/blockchain_reader.go create mode 100644 core/blockchain_repair_test.go create mode 100644 core/blockchain_sethead_test.go create mode 100644 core/blockchain_snapshot_test.go create mode 100644 core/blockchain_test.go create mode 100644 core/bloom_indexer.go create mode 100644 core/bloombits/doc.go create mode 100644 core/bloombits/generator.go create mode 100644 core/bloombits/generator_test.go create mode 100644 core/bloombits/matcher.go create mode 100644 core/bloombits/matcher_test.go create mode 100644 core/bloombits/scheduler.go create mode 100644 core/bloombits/scheduler_test.go create mode 100644 core/chain_indexer.go create mode 100644 core/chain_indexer_test.go create mode 100644 core/chain_makers.go create mode 100644 core/chain_makers_test.go create mode 100644 core/dao_test.go create mode 100644 core/error.go create mode 100644 core/events.go create mode 100644 core/evm.go create mode 100644 core/forkchoice.go create mode 100644 core/forkid/forkid.go create mode 100644 core/forkid/forkid_test.go create mode 100644 core/gaspool.go create mode 100644 core/gen_genesis.go create mode 100644 core/genesis.go create mode 100644 core/genesis_alloc.go create mode 100644 core/genesis_test.go create mode 100644 core/headerchain.go create mode 100644 core/headerchain_test.go create mode 100644 core/mkalloc.go create mode 100644 core/rawdb/accessors_chain.go create mode 100644 core/rawdb/accessors_chain_test.go create mode 100644 core/rawdb/accessors_indexes.go create mode 100644 core/rawdb/accessors_indexes_test.go create mode 100644 core/rawdb/accessors_metadata.go create mode 100644 core/rawdb/accessors_snapshot.go create mode 100644 core/rawdb/accessors_state.go create mode 100644 core/rawdb/accessors_sync.go create mode 100644 core/rawdb/accessors_trie.go create mode 100644 core/rawdb/ancient_scheme.go create mode 100644 core/rawdb/ancient_utils.go create mode 100644 core/rawdb/ancienttest/testsuite.go create mode 100644 core/rawdb/chain_freezer.go create mode 100644 core/rawdb/chain_iterator.go create mode 100644 core/rawdb/chain_iterator_test.go create mode 100644 core/rawdb/database.go create mode 100644 core/rawdb/database_test.go create mode 100644 core/rawdb/freezer.go create mode 100644 core/rawdb/freezer_batch.go create mode 100644 core/rawdb/freezer_memory.go create mode 100644 core/rawdb/freezer_memory_test.go create mode 100644 core/rawdb/freezer_meta.go create mode 100644 core/rawdb/freezer_meta_test.go create mode 100644 core/rawdb/freezer_resettable.go create mode 100644 core/rawdb/freezer_resettable_test.go create mode 100644 core/rawdb/freezer_table.go create mode 100644 core/rawdb/freezer_table_test.go create mode 100644 core/rawdb/freezer_test.go create mode 100644 core/rawdb/freezer_utils.go create mode 100644 core/rawdb/freezer_utils_test.go create mode 100644 core/rawdb/key_length_iterator.go create mode 100644 core/rawdb/key_length_iterator_test.go create mode 100644 core/rawdb/schema.go create mode 100644 core/rawdb/table.go create mode 100644 core/rawdb/table_test.go create mode 100644 core/rawdb/testdata/stored_receipts.bin create mode 100644 core/rlp_test.go create mode 100644 core/sender_cacher.go create mode 100644 core/state/access_events.go create mode 100644 core/state/access_events_test.go create mode 100644 core/state/access_list.go create mode 100644 core/state/database.go create mode 100644 core/state/dump.go create mode 100644 core/state/iterator.go create mode 100644 core/state/iterator_test.go create mode 100644 core/state/journal.go create mode 100644 core/state/metrics.go create mode 100644 core/state/pruner/bloom.go create mode 100644 core/state/pruner/pruner.go create mode 100644 core/state/snapshot/context.go create mode 100644 core/state/snapshot/conversion.go create mode 100644 core/state/snapshot/difflayer.go create mode 100644 core/state/snapshot/difflayer_test.go create mode 100644 core/state/snapshot/disklayer.go create mode 100644 core/state/snapshot/disklayer_test.go create mode 100644 core/state/snapshot/generate.go create mode 100644 core/state/snapshot/generate_test.go create mode 100644 core/state/snapshot/holdable_iterator.go create mode 100644 core/state/snapshot/holdable_iterator_test.go create mode 100644 core/state/snapshot/iterator.go create mode 100644 core/state/snapshot/iterator_binary.go create mode 100644 core/state/snapshot/iterator_fast.go create mode 100644 core/state/snapshot/iterator_test.go create mode 100644 core/state/snapshot/journal.go create mode 100644 core/state/snapshot/metrics.go create mode 100644 core/state/snapshot/snapshot.go create mode 100644 core/state/snapshot/snapshot_test.go create mode 100644 core/state/snapshot/utils.go create mode 100644 core/state/state_object.go create mode 100644 core/state/state_object_test.go create mode 100644 core/state/state_test.go create mode 100644 core/state/statedb.go create mode 100644 core/state/statedb_fuzz_test.go create mode 100644 core/state/statedb_test.go create mode 100644 core/state/stateupdate.go create mode 100644 core/state/sync.go create mode 100644 core/state/sync_test.go create mode 100644 core/state/transient_storage.go create mode 100644 core/state/trie_prefetcher.go create mode 100644 core/state/trie_prefetcher_test.go create mode 100644 core/state_prefetcher.go create mode 100644 core/state_processor.go create mode 100644 core/state_processor_test.go create mode 100644 core/state_transition.go create mode 100644 core/stateless.go create mode 100644 core/stateless/database.go create mode 100644 core/stateless/encoding.go create mode 100644 core/stateless/gen_encoding_json.go create mode 100644 core/stateless/witness.go create mode 100644 core/tracing/CHANGELOG.md create mode 100644 core/tracing/gen_balance_change_reason_stringer.go create mode 100644 core/tracing/hooks.go create mode 100644 core/txindexer.go create mode 100644 core/txindexer_test.go create mode 100644 core/txpool/blobpool/blobpool.go create mode 100644 core/txpool/blobpool/blobpool_test.go create mode 100644 core/txpool/blobpool/config.go create mode 100644 core/txpool/blobpool/evictheap.go create mode 100644 core/txpool/blobpool/evictheap_test.go create mode 100644 core/txpool/blobpool/interface.go create mode 100644 core/txpool/blobpool/limbo.go create mode 100644 core/txpool/blobpool/metrics.go create mode 100644 core/txpool/blobpool/priority.go create mode 100644 core/txpool/blobpool/priority_test.go create mode 100644 core/txpool/blobpool/slotter.go create mode 100644 core/txpool/blobpool/slotter_test.go create mode 100644 core/txpool/errors.go create mode 100644 core/txpool/legacypool/journal.go create mode 100644 core/txpool/legacypool/legacypool.go create mode 100644 core/txpool/legacypool/legacypool2_test.go create mode 100644 core/txpool/legacypool/legacypool_test.go create mode 100644 core/txpool/legacypool/list.go create mode 100644 core/txpool/legacypool/list_test.go create mode 100644 core/txpool/legacypool/noncer.go create mode 100644 core/txpool/subpool.go create mode 100644 core/txpool/txpool.go create mode 100644 core/txpool/validation.go create mode 100644 core/types.go create mode 100644 core/types/account.go create mode 100644 core/types/block.go create mode 100644 core/types/block_test.go create mode 100644 core/types/bloom9.go create mode 100644 core/types/bloom9_test.go create mode 100644 core/types/gen_access_tuple.go create mode 100644 core/types/gen_account.go create mode 100644 core/types/gen_account_rlp.go create mode 100644 core/types/gen_header_json.go create mode 100644 core/types/gen_header_rlp.go create mode 100644 core/types/gen_log_json.go create mode 100644 core/types/gen_log_rlp.go create mode 100644 core/types/gen_receipt_json.go create mode 100644 core/types/gen_withdrawal_json.go create mode 100644 core/types/gen_withdrawal_rlp.go create mode 100644 core/types/hashes.go create mode 100644 core/types/hashing.go create mode 100644 core/types/hashing_test.go create mode 100644 core/types/log.go create mode 100644 core/types/log_test.go create mode 100644 core/types/receipt.go create mode 100644 core/types/receipt_test.go create mode 100644 core/types/rlp_fuzzer_test.go create mode 100644 core/types/state_account.go create mode 100644 core/types/transaction.go create mode 100644 core/types/transaction_marshalling.go create mode 100644 core/types/transaction_signing.go create mode 100644 core/types/transaction_signing_test.go create mode 100644 core/types/transaction_test.go create mode 100644 core/types/tx_access_list.go create mode 100644 core/types/tx_blob.go create mode 100644 core/types/tx_blob_test.go create mode 100644 core/types/tx_dynamic_fee.go create mode 100644 core/types/tx_legacy.go create mode 100644 core/types/types_test.go create mode 100644 core/types/withdrawal.go create mode 100644 core/vm/analysis.go create mode 100644 core/vm/analysis_test.go create mode 100644 core/vm/common.go create mode 100644 core/vm/contract.go create mode 100644 core/vm/contracts.go create mode 100644 core/vm/contracts_fuzz_test.go create mode 100644 core/vm/contracts_test.go create mode 100644 core/vm/doc.go create mode 100644 core/vm/eips.go create mode 100644 core/vm/errors.go create mode 100644 core/vm/evm.go create mode 100644 core/vm/gas.go create mode 100644 core/vm/gas_table.go create mode 100644 core/vm/gas_table_test.go create mode 100644 core/vm/instructions.go create mode 100644 core/vm/instructions_test.go create mode 100644 core/vm/interface.go create mode 100644 core/vm/interpreter.go create mode 100644 core/vm/interpreter_test.go create mode 100644 core/vm/ipgraph.go create mode 100644 core/vm/jump_table.go create mode 100644 core/vm/jump_table_export.go create mode 100644 core/vm/jump_table_test.go create mode 100644 core/vm/memory.go create mode 100644 core/vm/memory_table.go create mode 100644 core/vm/memory_test.go create mode 100644 core/vm/opcodes.go create mode 100644 core/vm/operations_acl.go create mode 100644 core/vm/operations_verkle.go create mode 100644 core/vm/runtime/doc.go create mode 100644 core/vm/runtime/env.go create mode 100644 core/vm/runtime/runtime.go create mode 100644 core/vm/runtime/runtime_example_test.go create mode 100644 core/vm/runtime/runtime_fuzz_test.go create mode 100644 core/vm/runtime/runtime_test.go create mode 100644 core/vm/stack.go create mode 100644 core/vm/stack_table.go create mode 100644 core/vm/testdata/precompiles/blake2F.json create mode 100644 core/vm/testdata/precompiles/blsG1Add.json create mode 100644 core/vm/testdata/precompiles/blsG1Mul.json create mode 100644 core/vm/testdata/precompiles/blsG1MultiExp.json create mode 100644 core/vm/testdata/precompiles/blsG2Add.json create mode 100644 core/vm/testdata/precompiles/blsG2Mul.json create mode 100644 core/vm/testdata/precompiles/blsG2MultiExp.json create mode 100644 core/vm/testdata/precompiles/blsMapG1.json create mode 100644 core/vm/testdata/precompiles/blsMapG2.json create mode 100644 core/vm/testdata/precompiles/blsPairing.json create mode 100644 core/vm/testdata/precompiles/bn256Add.json create mode 100644 core/vm/testdata/precompiles/bn256Pairing.json create mode 100644 core/vm/testdata/precompiles/bn256ScalarMul.json create mode 100644 core/vm/testdata/precompiles/ecRecover.json create mode 100644 core/vm/testdata/precompiles/fail-blake2f.json create mode 100644 core/vm/testdata/precompiles/fail-blsG1Add.json create mode 100644 core/vm/testdata/precompiles/fail-blsG1Mul.json create mode 100644 core/vm/testdata/precompiles/fail-blsG1MultiExp.json create mode 100644 core/vm/testdata/precompiles/fail-blsG2Add.json create mode 100644 core/vm/testdata/precompiles/fail-blsG2Mul.json create mode 100644 core/vm/testdata/precompiles/fail-blsG2MultiExp.json create mode 100644 core/vm/testdata/precompiles/fail-blsMapG1.json create mode 100644 core/vm/testdata/precompiles/fail-blsMapG2.json create mode 100644 core/vm/testdata/precompiles/fail-blsPairing.json create mode 100644 core/vm/testdata/precompiles/modexp.json create mode 100644 core/vm/testdata/precompiles/modexp_eip2565.json create mode 100644 core/vm/testdata/precompiles/pointEvaluation.json create mode 100644 core/vm/testdata/testcases_add.json create mode 100644 core/vm/testdata/testcases_and.json create mode 100644 core/vm/testdata/testcases_byte.json create mode 100644 core/vm/testdata/testcases_div.json create mode 100644 core/vm/testdata/testcases_eq.json create mode 100644 core/vm/testdata/testcases_exp.json create mode 100644 core/vm/testdata/testcases_gt.json create mode 100644 core/vm/testdata/testcases_lt.json create mode 100644 core/vm/testdata/testcases_mod.json create mode 100644 core/vm/testdata/testcases_mul.json create mode 100644 core/vm/testdata/testcases_or.json create mode 100644 core/vm/testdata/testcases_sar.json create mode 100644 core/vm/testdata/testcases_sdiv.json create mode 100644 core/vm/testdata/testcases_sgt.json create mode 100644 core/vm/testdata/testcases_shl.json create mode 100644 core/vm/testdata/testcases_shr.json create mode 100644 core/vm/testdata/testcases_signext.json create mode 100644 core/vm/testdata/testcases_slt.json create mode 100644 core/vm/testdata/testcases_smod.json create mode 100644 core/vm/testdata/testcases_sub.json create mode 100644 core/vm/testdata/testcases_xor.json create mode 100644 crypto/blake2b/blake2b.go create mode 100644 crypto/blake2b/blake2bAVX2_amd64.go create mode 100644 crypto/blake2b/blake2bAVX2_amd64.s create mode 100644 crypto/blake2b/blake2b_amd64.go create mode 100644 crypto/blake2b/blake2b_amd64.s create mode 100644 crypto/blake2b/blake2b_f_fuzz_test.go create mode 100644 crypto/blake2b/blake2b_f_test.go create mode 100644 crypto/blake2b/blake2b_generic.go create mode 100644 crypto/blake2b/blake2b_ref.go create mode 100644 crypto/blake2b/blake2b_test.go create mode 100644 crypto/blake2b/blake2x.go create mode 100644 crypto/blake2b/register.go create mode 100644 crypto/bn256/LICENSE create mode 100644 crypto/bn256/bn256_fast.go create mode 100644 crypto/bn256/bn256_slow.go create mode 100644 crypto/bn256/cloudflare/LICENSE create mode 100644 crypto/bn256/cloudflare/bn256.go create mode 100644 crypto/bn256/cloudflare/bn256_test.go create mode 100644 crypto/bn256/cloudflare/constants.go create mode 100644 crypto/bn256/cloudflare/curve.go create mode 100644 crypto/bn256/cloudflare/example_test.go create mode 100644 crypto/bn256/cloudflare/gfp.go create mode 100644 crypto/bn256/cloudflare/gfp12.go create mode 100644 crypto/bn256/cloudflare/gfp2.go create mode 100644 crypto/bn256/cloudflare/gfp6.go create mode 100644 crypto/bn256/cloudflare/gfp_amd64.s create mode 100644 crypto/bn256/cloudflare/gfp_arm64.s create mode 100644 crypto/bn256/cloudflare/gfp_decl.go create mode 100644 crypto/bn256/cloudflare/gfp_generic.go create mode 100644 crypto/bn256/cloudflare/gfp_test.go create mode 100644 crypto/bn256/cloudflare/lattice.go create mode 100644 crypto/bn256/cloudflare/lattice_test.go create mode 100644 crypto/bn256/cloudflare/main_test.go create mode 100644 crypto/bn256/cloudflare/mul_amd64.h create mode 100644 crypto/bn256/cloudflare/mul_arm64.h create mode 100644 crypto/bn256/cloudflare/mul_bmi2_amd64.h create mode 100644 crypto/bn256/cloudflare/optate.go create mode 100644 crypto/bn256/cloudflare/twist.go create mode 100644 crypto/bn256/google/bn256.go create mode 100644 crypto/bn256/google/bn256_test.go create mode 100644 crypto/bn256/google/constants.go create mode 100644 crypto/bn256/google/curve.go create mode 100644 crypto/bn256/google/example_test.go create mode 100644 crypto/bn256/google/gfp12.go create mode 100644 crypto/bn256/google/gfp2.go create mode 100644 crypto/bn256/google/gfp6.go create mode 100644 crypto/bn256/google/main_test.go create mode 100644 crypto/bn256/google/optate.go create mode 100644 crypto/bn256/google/twist.go create mode 100644 crypto/crypto.go create mode 100644 crypto/crypto_test.go create mode 100644 crypto/ecies/.gitignore create mode 100644 crypto/ecies/LICENSE create mode 100644 crypto/ecies/README create mode 100644 crypto/ecies/ecies.go create mode 100644 crypto/ecies/ecies_test.go create mode 100644 crypto/ecies/params.go create mode 100644 crypto/kzg4844/kzg4844.go create mode 100644 crypto/kzg4844/kzg4844_ckzg_cgo.go create mode 100644 crypto/kzg4844/kzg4844_ckzg_nocgo.go create mode 100644 crypto/kzg4844/kzg4844_gokzg.go create mode 100644 crypto/kzg4844/kzg4844_test.go create mode 100644 crypto/kzg4844/trusted_setup.json create mode 100644 crypto/secp256k1/.gitignore create mode 100644 crypto/secp256k1/LICENSE create mode 100644 crypto/secp256k1/curve.go create mode 100644 crypto/secp256k1/dummy.go create mode 100644 crypto/secp256k1/ext.h create mode 100644 crypto/secp256k1/libsecp256k1/.gitignore create mode 100644 crypto/secp256k1/libsecp256k1/.travis.yml create mode 100644 crypto/secp256k1/libsecp256k1/COPYING create mode 100644 crypto/secp256k1/libsecp256k1/Makefile.am create mode 100644 crypto/secp256k1/libsecp256k1/README.md create mode 100644 crypto/secp256k1/libsecp256k1/TODO create mode 100755 crypto/secp256k1/libsecp256k1/autogen.sh create mode 100644 crypto/secp256k1/libsecp256k1/build-aux/m4/ax_jni_include_dir.m4 create mode 100644 crypto/secp256k1/libsecp256k1/build-aux/m4/ax_prog_cc_for_build.m4 create mode 100644 crypto/secp256k1/libsecp256k1/build-aux/m4/bitcoin_secp.m4 create mode 100644 crypto/secp256k1/libsecp256k1/configure.ac create mode 100644 crypto/secp256k1/libsecp256k1/contrib/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/contrib/lax_der_parsing.c create mode 100644 crypto/secp256k1/libsecp256k1/contrib/lax_der_parsing.h create mode 100644 crypto/secp256k1/libsecp256k1/contrib/lax_der_privatekey_parsing.c create mode 100644 crypto/secp256k1/libsecp256k1/contrib/lax_der_privatekey_parsing.h create mode 100644 crypto/secp256k1/libsecp256k1/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/include/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/include/secp256k1.h create mode 100644 crypto/secp256k1/libsecp256k1/include/secp256k1_ecdh.h create mode 100644 crypto/secp256k1/libsecp256k1/include/secp256k1_recovery.h create mode 100644 crypto/secp256k1/libsecp256k1/libsecp256k1.pc.in create mode 100644 crypto/secp256k1/libsecp256k1/obj/.gitignore create mode 100644 crypto/secp256k1/libsecp256k1/sage/group_prover.sage create mode 100644 crypto/secp256k1/libsecp256k1/sage/secp256k1.sage create mode 100644 crypto/secp256k1/libsecp256k1/sage/weierstrass_prover.sage create mode 100644 crypto/secp256k1/libsecp256k1/src/asm/field_10x26_arm.s create mode 100644 crypto/secp256k1/libsecp256k1/src/basic-config.h create mode 100644 crypto/secp256k1/libsecp256k1/src/bench.h create mode 100644 crypto/secp256k1/libsecp256k1/src/bench_ecdh.c create mode 100644 crypto/secp256k1/libsecp256k1/src/bench_internal.c create mode 100644 crypto/secp256k1/libsecp256k1/src/bench_recover.c create mode 100644 crypto/secp256k1/libsecp256k1/src/bench_schnorr_verify.c create mode 100644 crypto/secp256k1/libsecp256k1/src/bench_sign.c create mode 100644 crypto/secp256k1/libsecp256k1/src/bench_verify.c create mode 100644 crypto/secp256k1/libsecp256k1/src/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/ecdsa.h create mode 100644 crypto/secp256k1/libsecp256k1/src/ecdsa_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/eckey.h create mode 100644 crypto/secp256k1/libsecp256k1/src/eckey_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/ecmult.h create mode 100644 crypto/secp256k1/libsecp256k1/src/ecmult_const.h create mode 100644 crypto/secp256k1/libsecp256k1/src/ecmult_const_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/ecmult_gen.h create mode 100644 crypto/secp256k1/libsecp256k1/src/ecmult_gen_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/ecmult_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/field.h create mode 100644 crypto/secp256k1/libsecp256k1/src/field_10x26.h create mode 100644 crypto/secp256k1/libsecp256k1/src/field_10x26_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/field_5x52.h create mode 100644 crypto/secp256k1/libsecp256k1/src/field_5x52_asm_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/field_5x52_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/field_5x52_int128_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/field_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/gen_context.c create mode 100644 crypto/secp256k1/libsecp256k1/src/group.h create mode 100644 crypto/secp256k1/libsecp256k1/src/group_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/hash.h create mode 100644 crypto/secp256k1/libsecp256k1/src/hash_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1.java create mode 100644 crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java create mode 100644 crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java create mode 100644 crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/Secp256k1Context.java create mode 100644 crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_NativeSecp256k1.c create mode 100644 crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_NativeSecp256k1.h create mode 100644 crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_Secp256k1Context.c create mode 100644 crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_Secp256k1Context.h create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/ecdh/Makefile.am.include create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/ecdh/main_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/ecdh/tests_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/recovery/Makefile.am.include create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go create mode 100755 crypto/secp256k1/libsecp256k1/src/modules/recovery/main_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/recovery/tests_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/num.h create mode 100644 crypto/secp256k1/libsecp256k1/src/num_gmp.h create mode 100644 crypto/secp256k1/libsecp256k1/src/num_gmp_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/num_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/scalar.h create mode 100644 crypto/secp256k1/libsecp256k1/src/scalar_4x64.h create mode 100644 crypto/secp256k1/libsecp256k1/src/scalar_4x64_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/scalar_8x32.h create mode 100644 crypto/secp256k1/libsecp256k1/src/scalar_8x32_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/scalar_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/scalar_low.h create mode 100644 crypto/secp256k1/libsecp256k1/src/scalar_low_impl.h create mode 100755 crypto/secp256k1/libsecp256k1/src/secp256k1.c create mode 100644 crypto/secp256k1/libsecp256k1/src/testrand.h create mode 100644 crypto/secp256k1/libsecp256k1/src/testrand_impl.h create mode 100644 crypto/secp256k1/libsecp256k1/src/tests.c create mode 100644 crypto/secp256k1/libsecp256k1/src/tests_exhaustive.c create mode 100644 crypto/secp256k1/libsecp256k1/src/util.h create mode 100644 crypto/secp256k1/panic_cb.go create mode 100644 crypto/secp256k1/scalar_mult_cgo.go create mode 100644 crypto/secp256k1/scalar_mult_nocgo.go create mode 100644 crypto/secp256k1/secp256.go create mode 100644 crypto/secp256k1/secp256_test.go create mode 100644 crypto/signature_cgo.go create mode 100644 crypto/signature_nocgo.go create mode 100644 crypto/signature_test.go create mode 100644 crypto/signify/signify.go create mode 100644 crypto/signify/signify_fuzz.go create mode 100644 crypto/signify/signify_test.go create mode 100644 docs/audits/2017-04-25_Geth-audit_Truesec.pdf create mode 100644 docs/audits/2018-09-14_Clef-audit_NCC.pdf create mode 100644 docs/audits/2019-10-15_Discv5_audit_LeastAuthority.pdf create mode 100644 docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf create mode 100644 docs/postmortems/2021-08-22-split-postmortem.md create mode 100644 eth/api_admin.go create mode 100644 eth/api_backend.go create mode 100644 eth/api_debug.go create mode 100644 eth/api_debug_test.go create mode 100644 eth/api_miner.go create mode 100644 eth/backend.go create mode 100644 eth/bloombits.go create mode 100644 eth/catalyst/api.go create mode 100644 eth/catalyst/api_test.go create mode 100644 eth/catalyst/queue.go create mode 100644 eth/catalyst/simulated_beacon.go create mode 100644 eth/catalyst/simulated_beacon_api.go create mode 100644 eth/catalyst/simulated_beacon_test.go create mode 100644 eth/catalyst/tester.go create mode 100644 eth/downloader/api.go create mode 100644 eth/downloader/beacondevsync.go create mode 100644 eth/downloader/beaconsync.go create mode 100644 eth/downloader/downloader.go create mode 100644 eth/downloader/downloader_test.go create mode 100644 eth/downloader/events.go create mode 100644 eth/downloader/fetchers.go create mode 100644 eth/downloader/fetchers_concurrent.go create mode 100644 eth/downloader/fetchers_concurrent_bodies.go create mode 100644 eth/downloader/fetchers_concurrent_receipts.go create mode 100644 eth/downloader/metrics.go create mode 100644 eth/downloader/modes.go create mode 100644 eth/downloader/peer.go create mode 100644 eth/downloader/queue.go create mode 100644 eth/downloader/queue_test.go create mode 100644 eth/downloader/resultstore.go create mode 100644 eth/downloader/skeleton.go create mode 100644 eth/downloader/skeleton_test.go create mode 100644 eth/downloader/statesync.go create mode 100644 eth/downloader/testchain_test.go create mode 100644 eth/ethconfig/config.go create mode 100644 eth/ethconfig/gen_config.go create mode 100644 eth/fetcher/tx_fetcher.go create mode 100644 eth/fetcher/tx_fetcher_test.go create mode 100644 eth/filters/api.go create mode 100644 eth/filters/api_test.go create mode 100644 eth/filters/bench_test.go create mode 100644 eth/filters/filter.go create mode 100644 eth/filters/filter_system.go create mode 100644 eth/filters/filter_system_test.go create mode 100644 eth/filters/filter_test.go create mode 100644 eth/gasestimator/gasestimator.go create mode 100644 eth/gasprice/feehistory.go create mode 100644 eth/gasprice/feehistory_test.go create mode 100644 eth/gasprice/gasprice.go create mode 100644 eth/gasprice/gasprice_test.go create mode 100644 eth/handler.go create mode 100644 eth/handler_eth.go create mode 100644 eth/handler_eth_test.go create mode 100644 eth/handler_snap.go create mode 100644 eth/handler_test.go create mode 100644 eth/peer.go create mode 100644 eth/peerset.go create mode 100644 eth/protocols/eth/broadcast.go create mode 100644 eth/protocols/eth/discovery.go create mode 100644 eth/protocols/eth/dispatcher.go create mode 100644 eth/protocols/eth/handler.go create mode 100644 eth/protocols/eth/handler_test.go create mode 100644 eth/protocols/eth/handlers.go create mode 100644 eth/protocols/eth/handshake.go create mode 100644 eth/protocols/eth/handshake_test.go create mode 100644 eth/protocols/eth/metrics.go create mode 100644 eth/protocols/eth/peer.go create mode 100644 eth/protocols/eth/peer_test.go create mode 100644 eth/protocols/eth/protocol.go create mode 100644 eth/protocols/eth/protocol_test.go create mode 100644 eth/protocols/eth/tracker.go create mode 100644 eth/protocols/snap/discovery.go create mode 100644 eth/protocols/snap/gentrie.go create mode 100644 eth/protocols/snap/gentrie_test.go create mode 100644 eth/protocols/snap/handler.go create mode 100644 eth/protocols/snap/handler_fuzzing_test.go create mode 100644 eth/protocols/snap/metrics.go create mode 100644 eth/protocols/snap/peer.go create mode 100644 eth/protocols/snap/progress_test.go create mode 100644 eth/protocols/snap/protocol.go create mode 100644 eth/protocols/snap/range.go create mode 100644 eth/protocols/snap/range_test.go create mode 100644 eth/protocols/snap/sort_test.go create mode 100644 eth/protocols/snap/sync.go create mode 100644 eth/protocols/snap/sync_test.go create mode 100644 eth/protocols/snap/tracker.go create mode 100644 eth/state_accessor.go create mode 100644 eth/sync.go create mode 100644 eth/sync_test.go create mode 100644 eth/tracers/api.go create mode 100644 eth/tracers/api_test.go create mode 100644 eth/tracers/dir.go create mode 100644 eth/tracers/internal/tracetest/README.md create mode 100644 eth/tracers/internal/tracetest/calltrace_test.go create mode 100644 eth/tracers/internal/tracetest/flat_calltrace_test.go create mode 100644 eth/tracers/internal/tracetest/makeTest.js create mode 100644 eth/tracers/internal/tracetest/prestate_test.go create mode 100644 eth/tracers/internal/tracetest/supply_test.go create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/create.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.md create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/oog.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/revert.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/simple.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/simple_onlytop.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer/throw.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/big_slow.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_fail_hide.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_oog.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_throw.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/create.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/deep_calls.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/delegatecall.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/delegatecall_parent_value.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/gas.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/include_precompiled.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_create_oog_outer_throw.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_instafail.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_precompiled_wrong_gas.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_throw_outer_revert.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create2_action_gas.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_action_gas.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_inerror.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_pointer_issue.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/oog.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/option_convert_parity_errors.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/result_output.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/revert.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/revert_reason.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/selfdestruct.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/simple.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/simple_onlytop.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/skip_no_balance_error.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/staticcall_precompiled.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/suicide.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_flat/throw.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/create.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/deep_calls.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/delegatecall.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_create_oog_outer_throw.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_instafail.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_throw_outer_revert.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/oog.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert_reason.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/selfdestruct.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/simple.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_legacy/throw.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multi_contracts.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_failed.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_partial_failed.json create mode 100644 eth/tracers/internal/tracetest/testdata/call_tracer_withLog/with_onlyTopCall.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer/create_create.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer/create_existing_contract.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer/create_post_eip158.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer/simple.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_legacy/simple.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_failed.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_post_eip158.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_suicide.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/inner_create.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/simple.json create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/suicide.json create mode 100644 eth/tracers/internal/tracetest/util.go create mode 100644 eth/tracers/internal/util.go create mode 100644 eth/tracers/internal/util_test.go create mode 100644 eth/tracers/js/bigint.go create mode 100644 eth/tracers/js/goja.go create mode 100644 eth/tracers/js/internal/tracers/4byte_tracer_legacy.js create mode 100644 eth/tracers/js/internal/tracers/bigram_tracer.js create mode 100644 eth/tracers/js/internal/tracers/call_tracer_legacy.js create mode 100644 eth/tracers/js/internal/tracers/evmdis_tracer.js create mode 100644 eth/tracers/js/internal/tracers/noop_tracer_legacy.js create mode 100644 eth/tracers/js/internal/tracers/opcount_tracer.js create mode 100644 eth/tracers/js/internal/tracers/prestate_tracer_legacy.js create mode 100644 eth/tracers/js/internal/tracers/tracers.go create mode 100644 eth/tracers/js/internal/tracers/trigram_tracer.js create mode 100644 eth/tracers/js/internal/tracers/unigram_tracer.js create mode 100644 eth/tracers/js/tracer_test.go create mode 100644 eth/tracers/live.go create mode 100644 eth/tracers/live/gen_supplyinfoburn.go create mode 100644 eth/tracers/live/gen_supplyinfoissuance.go create mode 100644 eth/tracers/live/noop.go create mode 100644 eth/tracers/live/supply.go create mode 100644 eth/tracers/logger/access_list_tracer.go create mode 100644 eth/tracers/logger/gen_callframe.go create mode 100644 eth/tracers/logger/gen_structlog.go create mode 100644 eth/tracers/logger/logger.go create mode 100644 eth/tracers/logger/logger_json.go create mode 100644 eth/tracers/logger/logger_test.go create mode 100644 eth/tracers/native/4byte.go create mode 100644 eth/tracers/native/call.go create mode 100644 eth/tracers/native/call_flat.go create mode 100644 eth/tracers/native/call_flat_test.go create mode 100644 eth/tracers/native/gen_account_json.go create mode 100644 eth/tracers/native/gen_callframe_json.go create mode 100644 eth/tracers/native/gen_flatcallaction_json.go create mode 100644 eth/tracers/native/gen_flatcallresult_json.go create mode 100644 eth/tracers/native/mux.go create mode 100644 eth/tracers/native/noop.go create mode 100644 eth/tracers/native/prestate.go create mode 100644 eth/tracers/tracers_test.go create mode 100644 eth/tracers/tracker.go create mode 100644 eth/tracers/tracker_test.go create mode 100644 ethclient/ethclient.go create mode 100644 ethclient/ethclient_test.go create mode 100644 ethclient/gethclient/gethclient.go create mode 100644 ethclient/gethclient/gethclient_test.go create mode 100644 ethclient/signer.go create mode 100644 ethclient/simulated/backend.go create mode 100644 ethclient/simulated/backend_test.go create mode 100644 ethclient/simulated/options.go create mode 100644 ethclient/simulated/options_test.go create mode 100644 ethdb/batch.go create mode 100644 ethdb/database.go create mode 100644 ethdb/dbtest/testsuite.go create mode 100644 ethdb/iterator.go create mode 100644 ethdb/leveldb/leveldb.go create mode 100644 ethdb/leveldb/leveldb_test.go create mode 100644 ethdb/memorydb/memorydb.go create mode 100644 ethdb/memorydb/memorydb_test.go create mode 100644 ethdb/pebble/pebble.go create mode 100644 ethdb/pebble/pebble_test.go create mode 100644 ethdb/remotedb/remotedb.go create mode 100644 ethdb/snapshot.go create mode 100644 ethstats/ethstats.go create mode 100644 ethstats/ethstats_test.go create mode 100644 event/event.go create mode 100644 event/event_test.go create mode 100644 event/example_feed_test.go create mode 100644 event/example_scope_test.go create mode 100644 event/example_subscription_test.go create mode 100644 event/example_test.go create mode 100644 event/feed.go create mode 100644 event/feed_test.go create mode 100644 event/feedof.go create mode 100644 event/feedof_test.go create mode 100644 event/multisub.go create mode 100644 event/multisub_test.go create mode 100644 event/subscription.go create mode 100644 event/subscription_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 graphql/graphiql.go create mode 100644 graphql/graphql.go create mode 100644 graphql/graphql_test.go create mode 100644 graphql/internal/graphiql/build.go create mode 100644 graphql/internal/graphiql/graphiql.min.css create mode 100644 graphql/internal/graphiql/graphiql.min.js create mode 100644 graphql/internal/graphiql/index.html create mode 100644 graphql/internal/graphiql/react-dom.production.min.js create mode 100644 graphql/internal/graphiql/react.production.min.js create mode 100644 graphql/schema.go create mode 100644 graphql/service.go create mode 100644 interfaces.go create mode 100644 internal/blocktest/test_hash.go create mode 100644 internal/build/archive.go create mode 100644 internal/build/azure.go create mode 100644 internal/build/download.go create mode 100644 internal/build/env.go create mode 100644 internal/build/gotool.go create mode 100644 internal/build/pgp.go create mode 100644 internal/build/util.go create mode 100644 internal/cmdtest/test_cmd.go create mode 100644 internal/debug/api.go create mode 100644 internal/debug/flags.go create mode 100644 internal/debug/loudpanic.go create mode 100644 internal/debug/trace.go create mode 100644 internal/era/accumulator.go create mode 100644 internal/era/builder.go create mode 100644 internal/era/e2store/e2store.go create mode 100644 internal/era/e2store/e2store_test.go create mode 100644 internal/era/era.go create mode 100644 internal/era/era_test.go create mode 100644 internal/era/iterator.go create mode 100644 internal/ethapi/addrlock.go create mode 100644 internal/ethapi/api.go create mode 100644 internal/ethapi/api_test.go create mode 100644 internal/ethapi/backend.go create mode 100644 internal/ethapi/dbapi.go create mode 100644 internal/ethapi/errors.go create mode 100644 internal/ethapi/testdata/eth_getBlockByHash-hash-1.json create mode 100644 internal/ethapi/testdata/eth_getBlockByHash-hash-empty-fullTx.json create mode 100644 internal/ethapi/testdata/eth_getBlockByHash-hash-genesis.json create mode 100644 internal/ethapi/testdata/eth_getBlockByHash-hash-latest-1-fullTx.json create mode 100644 internal/ethapi/testdata/eth_getBlockByHash-hash-latest.json create mode 100644 internal/ethapi/testdata/eth_getBlockByHash-hash-pending-fullTx.json create mode 100644 internal/ethapi/testdata/eth_getBlockByHash-hash-pending.json create mode 100644 internal/ethapi/testdata/eth_getBlockByNumber-number-0.json create mode 100644 internal/ethapi/testdata/eth_getBlockByNumber-number-1.json create mode 100644 internal/ethapi/testdata/eth_getBlockByNumber-number-latest+1.json create mode 100644 internal/ethapi/testdata/eth_getBlockByNumber-number-latest-1.json create mode 100644 internal/ethapi/testdata/eth_getBlockByNumber-tag-latest.json create mode 100644 internal/ethapi/testdata/eth_getBlockByNumber-tag-pending-fullTx.json create mode 100644 internal/ethapi/testdata/eth_getBlockByNumber-tag-pending.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-block-notfound.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-hash-empty.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-hash-notfound.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-number-0.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-number-1.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-tag-earliest.json create mode 100644 internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByHash-hash-0.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByHash-hash-1.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByHash-hash-empty.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByHash-hash-latest-1.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByHash-hash-latest.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByHash-hash-pending.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByNumber-number-0.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByNumber-number-1.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByNumber-number-latest+1.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByNumber-number-latest-1.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByNumber-tag-latest.json create mode 100644 internal/ethapi/testdata/eth_getHeaderByNumber-tag-pending.json create mode 100644 internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json create mode 100644 internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json create mode 100644 internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json create mode 100644 internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json create mode 100644 internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json create mode 100644 internal/ethapi/testdata/eth_getTransactionReceipt-txhash-empty.json create mode 100644 internal/ethapi/testdata/eth_getTransactionReceipt-txhash-notfound.json create mode 100644 internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json create mode 100644 internal/ethapi/transaction_args.go create mode 100644 internal/ethapi/transaction_args_test.go create mode 100644 internal/flags/categories.go create mode 100644 internal/flags/flags.go create mode 100644 internal/flags/flags_test.go create mode 100644 internal/flags/helpers.go create mode 100644 internal/guide/guide.go create mode 100644 internal/guide/guide_test.go create mode 100644 internal/jsre/completion.go create mode 100644 internal/jsre/completion_test.go create mode 100644 internal/jsre/deps/bignumber.js create mode 100644 internal/jsre/deps/deps.go create mode 100644 internal/jsre/deps/web3.js create mode 100644 internal/jsre/jsre.go create mode 100644 internal/jsre/jsre_test.go create mode 100644 internal/jsre/pretty.go create mode 100644 internal/reexec/reexec.go create mode 100644 internal/reexec/self_linux.go create mode 100644 internal/reexec/self_others.go create mode 100644 internal/shutdowncheck/shutdown_tracker.go create mode 100644 internal/syncx/mutex.go create mode 100644 internal/testlog/testlog.go create mode 100644 internal/testrand/rand.go create mode 100644 internal/utesting/utesting.go create mode 100644 internal/utesting/utesting_test.go create mode 100644 internal/version/vcs.go create mode 100644 internal/version/version.go create mode 100644 internal/web3ext/web3ext.go create mode 100644 log/format.go create mode 100644 log/format_test.go create mode 100644 log/handler.go create mode 100644 log/handler_glog.go create mode 100644 log/logger.go create mode 100644 log/logger_test.go create mode 100644 log/root.go create mode 100644 metrics/FORK.md create mode 100644 metrics/LICENSE create mode 100644 metrics/README.md create mode 100644 metrics/config.go create mode 100644 metrics/counter.go create mode 100644 metrics/counter_float64.go create mode 100644 metrics/counter_float_64_test.go create mode 100644 metrics/counter_test.go create mode 100644 metrics/cpu.go create mode 100644 metrics/cpu_disabled.go create mode 100644 metrics/cpu_enabled.go create mode 100644 metrics/cputime_nop.go create mode 100644 metrics/cputime_unix.go create mode 100644 metrics/debug.go create mode 100644 metrics/debug_test.go create mode 100644 metrics/disk.go create mode 100644 metrics/disk_linux.go create mode 100644 metrics/disk_nop.go create mode 100644 metrics/ewma.go create mode 100644 metrics/ewma_test.go create mode 100644 metrics/exp/exp.go create mode 100644 metrics/gauge.go create mode 100644 metrics/gauge_float64.go create mode 100644 metrics/gauge_float64_test.go create mode 100644 metrics/gauge_info.go create mode 100644 metrics/gauge_info_test.go create mode 100644 metrics/gauge_test.go create mode 100644 metrics/graphite.go create mode 100644 metrics/graphite_test.go create mode 100644 metrics/healthcheck.go create mode 100644 metrics/histogram.go create mode 100644 metrics/histogram_test.go create mode 100644 metrics/inactive.go create mode 100644 metrics/influxdb/LICENSE create mode 100644 metrics/influxdb/README.md create mode 100644 metrics/influxdb/influxdb.go create mode 100644 metrics/influxdb/influxdb_test.go create mode 100644 metrics/influxdb/influxdbv1.go create mode 100644 metrics/influxdb/influxdbv2.go create mode 100644 metrics/influxdb/testdata/influxdbv1.want create mode 100644 metrics/influxdb/testdata/influxdbv2.want create mode 100644 metrics/init_test.go create mode 100644 metrics/internal/sampledata.go create mode 100644 metrics/internal/sampledata_test.go create mode 100644 metrics/json.go create mode 100644 metrics/json_test.go create mode 100644 metrics/log.go create mode 100644 metrics/memory.md create mode 100644 metrics/meter.go create mode 100644 metrics/meter_test.go create mode 100644 metrics/metrics.go create mode 100644 metrics/metrics_test.go create mode 100644 metrics/opentsdb.go create mode 100644 metrics/opentsdb_test.go create mode 100644 metrics/prometheus/collector.go create mode 100644 metrics/prometheus/collector_test.go create mode 100644 metrics/prometheus/prometheus.go create mode 100644 metrics/prometheus/testdata/prometheus.want create mode 100644 metrics/registry.go create mode 100644 metrics/registry_test.go create mode 100644 metrics/resetting_sample.go create mode 100644 metrics/resetting_timer.go create mode 100644 metrics/resetting_timer_test.go create mode 100644 metrics/runtimehistogram.go create mode 100644 metrics/runtimehistogram_test.go create mode 100644 metrics/sample.go create mode 100644 metrics/sample_test.go create mode 100644 metrics/syslog.go create mode 100644 metrics/testdata/opentsb.want create mode 100644 metrics/timer.go create mode 100644 metrics/timer_test.go create mode 100755 metrics/validate.sh create mode 100644 metrics/writer.go create mode 100644 metrics/writer_test.go create mode 100644 miner/miner.go create mode 100644 miner/miner_test.go create mode 100644 miner/ordering.go create mode 100644 miner/ordering_test.go create mode 100644 miner/payload_building.go create mode 100644 miner/payload_building_test.go create mode 100644 miner/pending.go create mode 100644 miner/worker.go create mode 100644 node/api.go create mode 100644 node/api_test.go create mode 100644 node/config.go create mode 100644 node/config_test.go create mode 100644 node/defaults.go create mode 100644 node/doc.go create mode 100644 node/endpoints.go create mode 100644 node/errors.go create mode 100644 node/jwt_auth.go create mode 100644 node/jwt_handler.go create mode 100644 node/lifecycle.go create mode 100644 node/node.go create mode 100644 node/node_auth_test.go create mode 100644 node/node_example_test.go create mode 100644 node/node_test.go create mode 100644 node/rpcstack.go create mode 100644 node/rpcstack_test.go create mode 100644 node/utils_test.go create mode 100644 oss-fuzz.sh create mode 100644 p2p/dial.go create mode 100644 p2p/dial_test.go create mode 100644 p2p/discover/common.go create mode 100644 p2p/discover/lookup.go create mode 100644 p2p/discover/metrics.go create mode 100644 p2p/discover/node.go create mode 100644 p2p/discover/ntp.go create mode 100644 p2p/discover/table.go create mode 100644 p2p/discover/table_reval.go create mode 100644 p2p/discover/table_reval_test.go create mode 100644 p2p/discover/table_test.go create mode 100644 p2p/discover/table_util_test.go create mode 100644 p2p/discover/v4_lookup_test.go create mode 100644 p2p/discover/v4_udp.go create mode 100644 p2p/discover/v4_udp_test.go create mode 100644 p2p/discover/v4wire/v4wire.go create mode 100644 p2p/discover/v4wire/v4wire_test.go create mode 100644 p2p/discover/v5_talk.go create mode 100644 p2p/discover/v5_udp.go create mode 100644 p2p/discover/v5_udp_test.go create mode 100644 p2p/discover/v5wire/crypto.go create mode 100644 p2p/discover/v5wire/crypto_test.go create mode 100644 p2p/discover/v5wire/encoding.go create mode 100644 p2p/discover/v5wire/encoding_test.go create mode 100644 p2p/discover/v5wire/msg.go create mode 100644 p2p/discover/v5wire/session.go create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-message.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-whoareyou.txt create mode 100644 p2p/dnsdisc/client.go create mode 100644 p2p/dnsdisc/client_test.go create mode 100644 p2p/dnsdisc/doc.go create mode 100644 p2p/dnsdisc/error.go create mode 100644 p2p/dnsdisc/sync.go create mode 100644 p2p/dnsdisc/sync_test.go create mode 100644 p2p/dnsdisc/tree.go create mode 100644 p2p/dnsdisc/tree_test.go create mode 100644 p2p/enode/idscheme.go create mode 100644 p2p/enode/idscheme_test.go create mode 100644 p2p/enode/iter.go create mode 100644 p2p/enode/iter_test.go create mode 100644 p2p/enode/localnode.go create mode 100644 p2p/enode/localnode_test.go create mode 100644 p2p/enode/node.go create mode 100644 p2p/enode/node_test.go create mode 100644 p2p/enode/nodedb.go create mode 100644 p2p/enode/nodedb_test.go create mode 100644 p2p/enode/urlv4.go create mode 100644 p2p/enode/urlv4_test.go create mode 100644 p2p/enr/enr.go create mode 100644 p2p/enr/enr_test.go create mode 100644 p2p/enr/entries.go create mode 100644 p2p/message.go create mode 100644 p2p/message_test.go create mode 100644 p2p/metrics.go create mode 100644 p2p/msgrate/msgrate.go create mode 100644 p2p/msgrate/msgrate_test.go create mode 100644 p2p/nat/nat.go create mode 100644 p2p/nat/nat_test.go create mode 100644 p2p/nat/natpmp.go create mode 100644 p2p/nat/natupnp.go create mode 100644 p2p/nat/natupnp_test.go create mode 100644 p2p/netutil/addrutil.go create mode 100644 p2p/netutil/error.go create mode 100644 p2p/netutil/error_test.go create mode 100644 p2p/netutil/iptrack.go create mode 100644 p2p/netutil/iptrack_test.go create mode 100644 p2p/netutil/net.go create mode 100644 p2p/netutil/net_test.go create mode 100644 p2p/netutil/toobig_notwindows.go create mode 100644 p2p/netutil/toobig_windows.go create mode 100644 p2p/peer.go create mode 100644 p2p/peer_error.go create mode 100644 p2p/peer_test.go create mode 100644 p2p/protocol.go create mode 100644 p2p/rlpx/buffer.go create mode 100644 p2p/rlpx/buffer_test.go create mode 100644 p2p/rlpx/rlpx.go create mode 100644 p2p/rlpx/rlpx_test.go create mode 100644 p2p/server.go create mode 100644 p2p/server_nat.go create mode 100644 p2p/server_nat_test.go create mode 100644 p2p/server_test.go create mode 100644 p2p/simulations/README.md create mode 100644 p2p/simulations/adapters/exec.go create mode 100644 p2p/simulations/adapters/inproc.go create mode 100644 p2p/simulations/adapters/inproc_test.go create mode 100644 p2p/simulations/adapters/types.go create mode 100644 p2p/simulations/connect.go create mode 100644 p2p/simulations/connect_test.go create mode 100644 p2p/simulations/events.go create mode 100644 p2p/simulations/examples/README.md create mode 100644 p2p/simulations/examples/ping-pong.go create mode 100755 p2p/simulations/examples/ping-pong.sh create mode 100644 p2p/simulations/http.go create mode 100644 p2p/simulations/http_test.go create mode 100644 p2p/simulations/mocker.go create mode 100644 p2p/simulations/mocker_test.go create mode 100644 p2p/simulations/network.go create mode 100644 p2p/simulations/network_test.go create mode 100644 p2p/simulations/pipes/pipes.go create mode 100644 p2p/simulations/simulation.go create mode 100644 p2p/simulations/test.go create mode 100644 p2p/tracker/tracker.go create mode 100644 p2p/transport.go create mode 100644 p2p/transport_test.go create mode 100644 p2p/util.go create mode 100644 p2p/util_test.go create mode 100644 params/bootnodes.go create mode 100644 params/config.go create mode 100644 params/config_test.go create mode 100644 params/dao.go create mode 100644 params/denomination.go create mode 100644 params/forks/forks.go create mode 100644 params/network_params.go create mode 100644 params/protocol_params.go create mode 100644 params/verkle_params.go create mode 100644 params/version.go create mode 100644 rlp/decode.go create mode 100644 rlp/decode_tail_test.go create mode 100644 rlp/decode_test.go create mode 100644 rlp/doc.go create mode 100644 rlp/encbuffer.go create mode 100644 rlp/encbuffer_example_test.go create mode 100644 rlp/encode.go create mode 100644 rlp/encode_test.go create mode 100644 rlp/encoder_example_test.go create mode 100644 rlp/internal/rlpstruct/rlpstruct.go create mode 100644 rlp/iterator.go create mode 100644 rlp/iterator_test.go create mode 100644 rlp/raw.go create mode 100644 rlp/raw_test.go create mode 100644 rlp/rlpgen/gen.go create mode 100644 rlp/rlpgen/gen_test.go create mode 100644 rlp/rlpgen/main.go create mode 100644 rlp/rlpgen/testdata/bigint.in.txt create mode 100644 rlp/rlpgen/testdata/bigint.out.txt create mode 100644 rlp/rlpgen/testdata/nil.in.txt create mode 100644 rlp/rlpgen/testdata/nil.out.txt create mode 100644 rlp/rlpgen/testdata/optional.in.txt create mode 100644 rlp/rlpgen/testdata/optional.out.txt create mode 100644 rlp/rlpgen/testdata/rawvalue.in.txt create mode 100644 rlp/rlpgen/testdata/rawvalue.out.txt create mode 100644 rlp/rlpgen/testdata/uint256.in.txt create mode 100644 rlp/rlpgen/testdata/uint256.out.txt create mode 100644 rlp/rlpgen/testdata/uints.in.txt create mode 100644 rlp/rlpgen/testdata/uints.out.txt create mode 100644 rlp/rlpgen/types.go create mode 100644 rlp/safe.go create mode 100644 rlp/typecache.go create mode 100644 rlp/unsafe.go create mode 100644 rpc/client.go create mode 100644 rpc/client_example_test.go create mode 100644 rpc/client_opt.go create mode 100644 rpc/client_opt_test.go create mode 100644 rpc/client_test.go create mode 100644 rpc/context_headers.go create mode 100644 rpc/doc.go create mode 100644 rpc/endpoints.go create mode 100644 rpc/errors.go create mode 100644 rpc/handler.go create mode 100644 rpc/http.go create mode 100644 rpc/http_test.go create mode 100644 rpc/inproc.go create mode 100644 rpc/ipc.go create mode 100644 rpc/ipc_js.go create mode 100644 rpc/ipc_unix.go create mode 100644 rpc/ipc_windows.go create mode 100644 rpc/json.go create mode 100644 rpc/metrics.go create mode 100644 rpc/server.go create mode 100644 rpc/server_test.go create mode 100644 rpc/service.go create mode 100644 rpc/stdio.go create mode 100644 rpc/subscription.go create mode 100644 rpc/subscription_test.go create mode 100644 rpc/testdata/internal-error.js create mode 100644 rpc/testdata/invalid-badid.js create mode 100644 rpc/testdata/invalid-badversion.js create mode 100644 rpc/testdata/invalid-batch-toolarge.js create mode 100644 rpc/testdata/invalid-batch.js create mode 100644 rpc/testdata/invalid-idonly.js create mode 100644 rpc/testdata/invalid-nonobj.js create mode 100644 rpc/testdata/invalid-syntax.json create mode 100644 rpc/testdata/reqresp-batch.js create mode 100644 rpc/testdata/reqresp-echo.js create mode 100644 rpc/testdata/reqresp-namedparam.js create mode 100644 rpc/testdata/reqresp-noargsrets.js create mode 100644 rpc/testdata/reqresp-nomethod.js create mode 100644 rpc/testdata/reqresp-noparam.js create mode 100644 rpc/testdata/reqresp-paramsnull.js create mode 100644 rpc/testdata/revcall.js create mode 100644 rpc/testdata/revcall2.js create mode 100644 rpc/testdata/subscription.js create mode 100644 rpc/testservice_test.go create mode 100644 rpc/types.go create mode 100644 rpc/types_test.go create mode 100644 rpc/websocket.go create mode 100644 rpc/websocket_test.go create mode 100644 signer/core/api.go create mode 100644 signer/core/api_test.go create mode 100644 signer/core/apitypes/signed_data_internal_test.go create mode 100644 signer/core/apitypes/types.go create mode 100644 signer/core/apitypes/types_test.go create mode 100644 signer/core/auditlog.go create mode 100644 signer/core/cliui.go create mode 100644 signer/core/gnosis_safe.go create mode 100644 signer/core/signed_data.go create mode 100644 signer/core/signed_data_test.go create mode 100644 signer/core/stdioui.go create mode 100644 signer/core/testdata/README.md create mode 100644 signer/core/testdata/arrays-1.json create mode 100644 signer/core/testdata/custom_arraytype.json create mode 100644 signer/core/testdata/eip712.json create mode 100644 signer/core/testdata/expfail_arraytype_overload.json create mode 100644 signer/core/testdata/expfail_datamismatch_1.json create mode 100644 signer/core/testdata/expfail_extradata.json create mode 100644 signer/core/testdata/expfail_malformeddomainkeys.json create mode 100644 signer/core/testdata/expfail_nonexistant_type.json create mode 100644 signer/core/testdata/expfail_nonexistant_type2.json create mode 100644 signer/core/testdata/expfail_toolargeuint.json create mode 100644 signer/core/testdata/expfail_toolargeuint2.json create mode 100644 signer/core/testdata/expfail_unconvertiblefloat.json create mode 100644 signer/core/testdata/expfail_unconvertiblefloat2.json create mode 100644 signer/core/testdata/fuzzing/2850f6ccf2d7f5f846dfb73119b60e09e712783f create mode 100644 signer/core/testdata/fuzzing/36fb987a774011dc675e1b5246ac5c1d44d84d92 create mode 100644 signer/core/testdata/fuzzing/37ec7b55c7ba014cced204c5f9989d2d0eb9ff6d create mode 100644 signer/core/testdata/fuzzing/582fa92154b784daa1faa293b695fa388fe34bf1 create mode 100644 signer/core/testdata/fuzzing/ab57cb2b2b5ce614efe13a47bc73814580f2cce8 create mode 100644 signer/core/testdata/fuzzing/e4303e23ca34fbbc43164a232b2caa7a3af2bf8d create mode 100644 signer/core/testdata/fuzzing/f658340af009dd4a35abe645a00a7b732bc30921 create mode 100644 signer/core/uiapi.go create mode 100644 signer/core/validation.go create mode 100644 signer/core/validation_test.go create mode 100644 signer/fourbyte/4byte.json create mode 100644 signer/fourbyte/abi.go create mode 100644 signer/fourbyte/abi_test.go create mode 100644 signer/fourbyte/fourbyte.go create mode 100644 signer/fourbyte/fourbyte_test.go create mode 100644 signer/fourbyte/validation.go create mode 100644 signer/fourbyte/validation_test.go create mode 100644 signer/rules/rules.go create mode 100644 signer/rules/rules_test.go create mode 100644 signer/storage/aes_gcm_storage.go create mode 100644 signer/storage/aes_gcm_storage_test.go create mode 100644 signer/storage/storage.go create mode 100644 swarm/README.md create mode 100644 tests/block_test.go create mode 100644 tests/block_test_util.go create mode 100644 tests/difficulty_test.go create mode 100644 tests/difficulty_test_util.go create mode 160000 tests/evm-benchmarks create mode 100644 tests/fuzzers/README.md create mode 100644 tests/fuzzers/bls12381/bls12381_fuzz.go create mode 100644 tests/fuzzers/bls12381/bls12381_test.go create mode 100644 tests/fuzzers/bls12381/precompile_fuzzer.go create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_multiexp_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_mul_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip create mode 100644 tests/fuzzers/bn256/bn256_fuzz.go create mode 100644 tests/fuzzers/bn256/bn256_test.go create mode 100644 tests/fuzzers/difficulty/difficulty-fuzz.go create mode 100644 tests/fuzzers/difficulty/difficulty_test.go create mode 100644 tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 create mode 100644 tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 create mode 100644 tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 create mode 100644 tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 create mode 100644 tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 create mode 100644 tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 create mode 100644 tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 create mode 100644 tests/fuzzers/rangeproof/corpus/random.dat create mode 100644 tests/fuzzers/rangeproof/rangeproof-fuzzer.go create mode 100644 tests/fuzzers/rangeproof/rangeproof_test.go create mode 100644 tests/fuzzers/secp256k1/secp_test.go create mode 100644 tests/fuzzers/txfetcher/corpus/0151ee1d0db4c74d3bcdfa4f7396a4c8538748c9-2 create mode 100644 tests/fuzzers/txfetcher/corpus/020dd7b492a6eb34ff0b7d8ee46189422c37e4a7-6 create mode 100644 tests/fuzzers/txfetcher/corpus/021d1144e359233c496e22c3250609b11b213e9f-4 create mode 100644 tests/fuzzers/txfetcher/corpus/0d28327b1fb52c1ba02a6eb96675c31633921bb2-2 create mode 100644 tests/fuzzers/txfetcher/corpus/0fcd827b57ded58e91f7ba2ac2b7ea4d25ebedca-7 create mode 100644 tests/fuzzers/txfetcher/corpus/109bc9b8fd4fef63493e104c703c79bc4a5e8d34-6 create mode 100644 tests/fuzzers/txfetcher/corpus/163785ab002746452619f31e8dfcb4549e6f8b6e-6 create mode 100644 tests/fuzzers/txfetcher/corpus/1adfa6b9ddf5766220c8ff7ede2926ca241bb947-3 create mode 100644 tests/fuzzers/txfetcher/corpus/1b9a02e9a48fea1d2fc3fb77946ada278e152079-4 create mode 100644 tests/fuzzers/txfetcher/corpus/1e14c7ea1faef92890988061b5abe96db7190f98-7 create mode 100644 tests/fuzzers/txfetcher/corpus/1e7d05f00e99cbf3ff0ef1cd7ea8dd07ad6dff23-6 create mode 100644 tests/fuzzers/txfetcher/corpus/1ec95e347fd522e6385b5091aa81aa2485be4891-4 create mode 100644 tests/fuzzers/txfetcher/corpus/1fbfa5d214060d2a0905846a589fd6f78d411451-4 create mode 100644 tests/fuzzers/txfetcher/corpus/1fd84ee194e791783a7f18f0a6deab8efe05fc04-2 create mode 100644 tests/fuzzers/txfetcher/corpus/21e76b9fca21d94d97f860c1c82f40697a83471b-8 create mode 100644 tests/fuzzers/txfetcher/corpus/220a87fed0c92474923054094eb7aff14289cf5e-4 create mode 100644 tests/fuzzers/txfetcher/corpus/23ddcd66aa92fe3d78b7f5b6e7cddb1b55c5f5df-3 create mode 100644 tests/fuzzers/txfetcher/corpus/2441d249faf9a859e38c49f6e305b394280c6ea5-1 create mode 100644 tests/fuzzers/txfetcher/corpus/2da1f0635e11283b1927974f418aadd8837ad31e-7 create mode 100644 tests/fuzzers/txfetcher/corpus/2e1853fbf8efe40098b1583224fe3b5f335e7037-6 create mode 100644 tests/fuzzers/txfetcher/corpus/2f25490dc49c103d653843ed47324b310ee7105e-7 create mode 100644 tests/fuzzers/txfetcher/corpus/30494b85bb60ad7f099fa49d427007a761620d8f-5 create mode 100644 tests/fuzzers/txfetcher/corpus/316024ca3aaf09c1de5258733ff5fe3d799648d3-4 create mode 100644 tests/fuzzers/txfetcher/corpus/32a089e2c439a91f4c1b67a13d52429bcded0dd9-7 create mode 100644 tests/fuzzers/txfetcher/corpus/33ec1dc0bfeb93d16edee3c07125fec6ac1aa17d-2 create mode 100644 tests/fuzzers/txfetcher/corpus/37a0d207700b52caa005ec8aeb344dcb13150ed2-5 create mode 100644 tests/fuzzers/txfetcher/corpus/382f59c66d0ddb6747d3177263279789ca15c2db-5 create mode 100644 tests/fuzzers/txfetcher/corpus/3a010483a4ad8d7215447ce27e0fac3791235c99-4 create mode 100644 tests/fuzzers/txfetcher/corpus/3a3b717fcfe7ffb000b906e5a76f32248a576bf7-6 create mode 100644 tests/fuzzers/txfetcher/corpus/3c37f6d58b8029971935f127f53e6aaeba558445-6 create mode 100644 tests/fuzzers/txfetcher/corpus/3c73b63bafa9f535c882ec17189adaf02b58f432-6 create mode 100644 tests/fuzzers/txfetcher/corpus/3d11500c4f66b20c73bbdfb1a7bddd7bbf92b29c-5 create mode 100644 tests/fuzzers/txfetcher/corpus/3d8b5bf36c80d6f65802280039f85421f32b5055-6 create mode 100644 tests/fuzzers/txfetcher/corpus/3f99c546a3962256176d566c19e3fffb62072078-1 create mode 100644 tests/fuzzers/txfetcher/corpus/408ec46539af27acd82b3d01e863597030882458-8 create mode 100644 tests/fuzzers/txfetcher/corpus/436154e5bb6487673f6642e6d2a582c01b083c08-8 create mode 100644 tests/fuzzers/txfetcher/corpus/45f565cd14b8de1ba2e925047ce776c2682b4b8d-3 create mode 100644 tests/fuzzers/txfetcher/corpus/4a0a12f5b033c8c160cc3b5133692ea1e92c6cdf-7 create mode 100644 tests/fuzzers/txfetcher/corpus/550f15ef65230cc4dcfab7fea67de212d9212ff8-8 create mode 100644 tests/fuzzers/txfetcher/corpus/5552213d659fef900a194c52718ffeffdc72d043-3 create mode 100644 tests/fuzzers/txfetcher/corpus/5570ef82893a9b9b9158572d43a7de7537121d2d-1 create mode 100644 tests/fuzzers/txfetcher/corpus/5e10f734f8af4116fbd164d96eec67aa53e6228c-5 create mode 100644 tests/fuzzers/txfetcher/corpus/608200b402488b3989ec8ec5f4190ccb537b8ea4-4 create mode 100644 tests/fuzzers/txfetcher/corpus/61e89c3fbdf9eff74bd250ea73cc2e61f8ca0d97-5 create mode 100644 tests/fuzzers/txfetcher/corpus/62817a48c78fbf2c12fcdc5ca58e2ca60c43543a-7 create mode 100644 tests/fuzzers/txfetcher/corpus/6782da8f1a432a77306d60d2ac2470c35b98004f-3 create mode 100644 tests/fuzzers/txfetcher/corpus/68fb55290cb9d6da5b259017c34bcecf96c944aa-5 create mode 100644 tests/fuzzers/txfetcher/corpus/6a5059bc86872526241d21ab5dae9f0afd3b9ae1-3 create mode 100644 tests/fuzzers/txfetcher/corpus/717928e0e2d478c680c6409b173552ca98469ba5-6 create mode 100644 tests/fuzzers/txfetcher/corpus/71d22f25419543e437f249ca437823b87ac926b1-6 create mode 100644 tests/fuzzers/txfetcher/corpus/7312a0f31ae5d773ed4fd74abc7521eb14754683-8 create mode 100644 tests/fuzzers/txfetcher/corpus/76e413a50dc8861e3756e556f796f1737bec2675-4 create mode 100644 tests/fuzzers/txfetcher/corpus/78480977d5c07386b06e9b37f5c82f5ed86c2f09-3 create mode 100644 tests/fuzzers/txfetcher/corpus/7a113cd3c178934cdb64353af86d51462d7080a4-5 create mode 100644 tests/fuzzers/txfetcher/corpus/7ea9f71020f3eb783f743f744eba8d8ca4b2582f-3 create mode 100644 tests/fuzzers/txfetcher/corpus/84f8c275f3ffbaf8c32c21782af13de10e7de28b-3 create mode 100644 tests/fuzzers/txfetcher/corpus/85dfe7ddee0e52aa19115c0ebb9ed28a14e488c6-5 create mode 100644 tests/fuzzers/txfetcher/corpus/87bba5b1e3da38fed8cb5a9bc5c8baa819e83d05-5 create mode 100644 tests/fuzzers/txfetcher/corpus/8a9ebedfbfec584d8b22761e6121dc1ca0248548-4 create mode 100644 tests/fuzzers/txfetcher/corpus/8ff3bd49f93079e5e1c7f8f2461ba7ee612900c3-5 create mode 100644 tests/fuzzers/txfetcher/corpus/9034aaf45143996a2b14465c352ab0c6fa26b221-2 create mode 100644 tests/fuzzers/txfetcher/corpus/92cefdc6251d04896349a464b29be03d6bb04c3d-2 create mode 100644 tests/fuzzers/txfetcher/corpus/9613e580ccb69df7c9074f0e2f6886ac6b34ca55-5 create mode 100644 tests/fuzzers/txfetcher/corpus/98afc8970a680fdc4aee0b5d48784f650c566b75-6 create mode 100644 tests/fuzzers/txfetcher/corpus/9dfc92f4ca2ece0167096fca6751ff314765f08b-8 create mode 100644 tests/fuzzers/txfetcher/corpus/9ebcbbfdaf0e98c87652e57226a4d8a35170c67d-4 create mode 100644 tests/fuzzers/txfetcher/corpus/9ff520eb8b8319a5fdafbe4d1cbb02a75058d93b-7 create mode 100644 tests/fuzzers/txfetcher/corpus/a0b57a12e25ac5adcedb2a5c45915f0f62aee869-4 create mode 100644 tests/fuzzers/txfetcher/corpus/a2684adccf16e036b051c12f283734fa803746e8-6 create mode 100644 tests/fuzzers/txfetcher/corpus/a37305974cf477ecfe65fa92f37b1f51dea25910-4 create mode 100644 tests/fuzzers/txfetcher/corpus/a7eb43926bd14b1f62a66a33107776e487434d32-7 create mode 100644 tests/fuzzers/txfetcher/corpus/a8f7c254eb64a40fd2a77b79979c7bbdac6a760c-4 create mode 100644 tests/fuzzers/txfetcher/corpus/a9a8f287d6af24e47d8db468e8f967aa44fb5a1f-7 create mode 100644 tests/fuzzers/txfetcher/corpus/aa7444d8e326158046862590a0db993c07aef372-7 create mode 100644 tests/fuzzers/txfetcher/corpus/ae4593626d8796e079a358c2395a4f6c9ddd6a44-6 create mode 100644 tests/fuzzers/txfetcher/corpus/b2942d4413a66939cda7db93020dee79eb17788c-9 create mode 100644 tests/fuzzers/txfetcher/corpus/b4614117cdfd147d38f4e8a4d85f5a2bb99a6a4f-5 create mode 100644 tests/fuzzers/txfetcher/corpus/b631ef3291fa405cd6517d11f4d1b9b6d02912d4-2 create mode 100644 tests/fuzzers/txfetcher/corpus/b7a91e338cc11f50ebdb2c414610efc4d5be3137-4 create mode 100644 tests/fuzzers/txfetcher/corpus/b858cb282617fb0956d960215c8e84d1ccf909c6-2 create mode 100644 tests/fuzzers/txfetcher/corpus/bc9d570aacf3acd39600feda8e72a293a4667da4-1 create mode 100644 tests/fuzzers/txfetcher/corpus/be7eed35b245b5d5d2adcdb4c67f07794eb86b24-3 create mode 100644 tests/fuzzers/txfetcher/corpus/c010b0cd70c7edbc5bd332fc9e2e91c6a1cbcdc4-5 create mode 100644 tests/fuzzers/txfetcher/corpus/c1690698607eb0f4c4244e9f9629968be4beb6bc-8 create mode 100644 tests/fuzzers/txfetcher/corpus/c1f435e4f53a9a17578d9e8c4789860f962a1379-6 create mode 100644 tests/fuzzers/txfetcher/corpus/c298a75334c3acf04bd129a8867447a25c8bacf8-7 create mode 100644 tests/fuzzers/txfetcher/corpus/c42287c7d225e530e822f23bbbba6819a9e48f38-6 create mode 100644 tests/fuzzers/txfetcher/corpus/c4cdbb891f3ee76476b7375d5ed51691fed95421-10 create mode 100644 tests/fuzzers/txfetcher/corpus/cc9572d72dfa2937074b1766dcbcff9cc58d1137-4 create mode 100644 tests/fuzzers/txfetcher/corpus/cd1d73b4e101bc7b979e3f6f135cb12d4594d348-5 create mode 100644 tests/fuzzers/txfetcher/corpus/d0acdc8fca32bbd58d368eeac3bd9eaa46f59d27-5 create mode 100644 tests/fuzzers/txfetcher/corpus/d0e43b715fd00953f7bdd6dfad95811985e81396-4 create mode 100644 tests/fuzzers/txfetcher/corpus/d925fbd22c8bc0de34d6a9d1258ce3d2928d0927-8 create mode 100644 tests/fuzzers/txfetcher/corpus/d9ba78cb7425724185d5fa300cd5c03aec2683bb-7 create mode 100644 tests/fuzzers/txfetcher/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709 create mode 100644 tests/fuzzers/txfetcher/corpus/dcdb7758b87648b5d766b1b341a65834420cf621-7 create mode 100644 tests/fuzzers/txfetcher/corpus/dd441bd24581332c9ce19e008260a69287aa3cbc-6 create mode 100644 tests/fuzzers/txfetcher/corpus/def879fe0fd637a745c00c8f1da340518db8688c-2 create mode 100644 tests/fuzzers/txfetcher/corpus/df6c30a9781b93bd6d2f5e97e5592d5945210003-7 create mode 100644 tests/fuzzers/txfetcher/corpus/dfc1c3a2e3ccdaf6f88c515fd00e8ad08421e431-6 create mode 100644 tests/fuzzers/txfetcher/corpus/e1dcc4e7ead6dfd1139ece7bf57d776cb9dac72d-7 create mode 100644 tests/fuzzers/txfetcher/corpus/e39c2de2c8937d2cbd4339b13d6a0ce94d94f8d2-8 create mode 100644 tests/fuzzers/txfetcher/corpus/e72f76b9579c792e545d02fe405d9186f0d6c39b-6 create mode 100644 tests/fuzzers/txfetcher/corpus/eb70814d6355a4498b8f301ba8dbc34f895a9947-5 create mode 100644 tests/fuzzers/txfetcher/corpus/ebdc17efe343e412634dca57cecd5a0e1ce1c1c7-5 create mode 100644 tests/fuzzers/txfetcher/corpus/ec0a25eba8966b8f628d821b3cfbdf2dfd4bbb4c-3 create mode 100644 tests/fuzzers/txfetcher/corpus/eebe3b76aeba6deed965d17d2b024f7eae1a43f1-5 create mode 100644 tests/fuzzers/txfetcher/corpus/ef8741a9faf030794d98ff113f556c68a24719a5-6 create mode 100644 tests/fuzzers/txfetcher/corpus/efb7410d02418befeba25a43d676cc6124129125-4 create mode 100644 tests/fuzzers/txfetcher/corpus/f6f97d781a5a749903790e07db8619866cb7c3a1-6 create mode 100644 tests/fuzzers/txfetcher/corpus/f7a3cd00fa0e57742e7dbbb8283dcaea067eaf7b-5 create mode 100644 tests/fuzzers/txfetcher/corpus/f94d60a6c556ce485ab60088291760b8be25776c-6 create mode 100644 tests/fuzzers/txfetcher/corpus/f9e627b2cb82ffa1ea5e0c6d7f2802f3000b18a8-6 create mode 100644 tests/fuzzers/txfetcher/corpus/fb3775aa24e5667e658920c05ba4b7b19ff256fb-5 create mode 100644 tests/fuzzers/txfetcher/corpus/fd6386548e119a50db96b2fa406e54924c45a2d5-6 create mode 100644 tests/fuzzers/txfetcher/txfetcher_fuzzer.go create mode 100644 tests/fuzzers/txfetcher/txfetcher_test.go create mode 100644 tests/gen_btheader.go create mode 100644 tests/gen_difficultytest.go create mode 100644 tests/gen_stenv.go create mode 100644 tests/gen_sttransaction.go create mode 100644 tests/init.go create mode 100644 tests/init_test.go create mode 100644 tests/rlp_test.go create mode 100644 tests/rlp_test_util.go create mode 100644 tests/solidity/bytecode.js create mode 100644 tests/solidity/contracts/Migrations.sol create mode 100644 tests/solidity/contracts/OpCodes.sol create mode 100644 tests/solidity/migrations/1_initial_migration.js create mode 100644 tests/solidity/migrations/2_opCodes_migration.js create mode 100644 tests/solidity/test/opCodes.js create mode 100644 tests/solidity/truffle-config.js create mode 100644 tests/state_test.go create mode 100644 tests/state_test_util.go create mode 160000 tests/testdata create mode 100644 tests/transaction_test.go create mode 100644 tests/transaction_test_util.go create mode 100644 trie/committer.go create mode 100644 trie/database_test.go create mode 100644 trie/encoding.go create mode 100644 trie/encoding_test.go create mode 100644 trie/errors.go create mode 100644 trie/hasher.go create mode 100644 trie/iterator.go create mode 100644 trie/iterator_test.go create mode 100644 trie/node.go create mode 100644 trie/node_enc.go create mode 100644 trie/node_test.go create mode 100644 trie/proof.go create mode 100644 trie/proof_test.go create mode 100644 trie/secure_trie.go create mode 100644 trie/secure_trie_test.go create mode 100644 trie/stacktrie.go create mode 100644 trie/stacktrie_fuzzer_test.go create mode 100644 trie/stacktrie_test.go create mode 100644 trie/sync.go create mode 100644 trie/sync_test.go create mode 100644 trie/tracer.go create mode 100644 trie/tracer_test.go create mode 100644 trie/trie.go create mode 100644 trie/trie_id.go create mode 100644 trie/trie_reader.go create mode 100644 trie/trie_test.go create mode 100644 trie/trienode/node.go create mode 100644 trie/trienode/node_test.go create mode 100644 trie/trienode/proof.go create mode 100644 trie/triestate/state.go create mode 100644 trie/utils/verkle.go create mode 100644 trie/utils/verkle_test.go create mode 100644 trie/verkle.go create mode 100644 trie/verkle_test.go create mode 100644 triedb/database.go create mode 100644 triedb/database/database.go create mode 100644 triedb/hashdb/database.go create mode 100644 triedb/history.go create mode 100644 triedb/pathdb/database.go create mode 100644 triedb/pathdb/database_test.go create mode 100644 triedb/pathdb/difflayer.go create mode 100644 triedb/pathdb/difflayer_test.go create mode 100644 triedb/pathdb/disklayer.go create mode 100644 triedb/pathdb/errors.go create mode 100644 triedb/pathdb/execute.go create mode 100644 triedb/pathdb/history.go create mode 100644 triedb/pathdb/history_inspect.go create mode 100644 triedb/pathdb/history_test.go create mode 100644 triedb/pathdb/journal.go create mode 100644 triedb/pathdb/layertree.go create mode 100644 triedb/pathdb/metrics.go create mode 100644 triedb/pathdb/nodebuffer.go create mode 100644 triedb/pathdb/reader.go create mode 100644 triedb/preimages.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0c013d1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +**/*_test.go + +build/_workspace +build/_bin +tests/testdata diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0269fab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Auto detect text files and perform LF normalization +* text=auto +*.sol linguist-language=Solidity diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..132233b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @kingster-will @LeoHChen diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..969b7f8 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing + +Thank you for considering to help out with the source code! We welcome +contributions from anyone on the internet, and are grateful for even the +smallest of fixes! + +If you'd like to contribute to go-ethereum, please fork, fix, commit and send a +pull request for the maintainers to review and merge into the main code base. If +you wish to submit more complex changes though, please check up with the core +devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum) to +ensure those changes are in line with the general philosophy of the project +and/or get some early feedback which can make both your efforts much lighter as +well as our review and merge procedures quick and simple. + +## Coding guidelines + +Please make sure your contributions adhere to our coding guidelines: + + * Code must adhere to the official Go +[formatting](https://golang.org/doc/effective_go.html#formatting) guidelines +(i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). + * Code must be documented adhering to the official Go +[commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. + * Pull requests need to be based on and opened against the `master` branch. + * Commit messages should be prefixed with the package(s) they modify. + * E.g. "eth, rpc: make trace configs optional" + +## Can I have feature X + +Before you submit a feature request, please check and make sure that it isn't +possible through some other means. The JavaScript-enabled console is a powerful +feature in the right hands. Please check our +[Geth documentation page](https://geth.ethereum.org/docs/) for more info +and help. + +## Configuration, dependencies, and tests + +Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/geth-developer/dev-guide) +for more details on configuring your environment, managing project dependencies +and testing procedures. diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..45bfd98 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,31 @@ +--- +name: Report a bug +about: Something with go-ethereum is not working as expected +title: '' +labels: 'type:bug' +assignees: '' +--- + +#### System information + +Geth version: `geth version` +CL client & version: e.g. lighthouse/nimbus/prysm@v1.0.0 +OS & Version: Windows/Linux/OSX +Commit hash : (if `develop`) + +#### Expected behaviour + + +#### Actual behaviour + + +#### Steps to reproduce the behaviour + + +#### Backtrace + +```` +[backtrace] +```` + +When submitting logs: please submit them as text and not screenshots. diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..aacd885 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,17 @@ +--- +name: Request a feature +about: Report a missing feature - e.g. as a step before submitting a PR +title: '' +labels: 'type:feature' +assignees: '' +--- + +# Rationale + +Why should this feature exist? +What are the use-cases? + +# Implementation + +Do you have ideas regarding the implementation of this feature? +Are you willing to implement this feature? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..8f460ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,9 @@ +--- +name: Ask a question +about: Something is unclear +title: '' +labels: 'type:docs' +assignees: '' +--- + +This should only be used in very rare cases e.g. if you are not 100% sure if something is a bug or asking a question that leads to improving the documentation. For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. diff --git a/.github/no-response.yml b/.github/no-response.yml new file mode 100644 index 0000000..903d4ce --- /dev/null +++ b/.github/no-response.yml @@ -0,0 +1,11 @@ +# Number of days of inactivity before an Issue is closed for lack of response +daysUntilClose: 30 +# Label requiring a response +responseRequiredLabel: "need:more-information" +# Comment to post when closing an Issue for lack of response. Set to `false` to disable +closeComment: > + This issue has been automatically closed because there has been no response + to our request for more information from the original author. With only the + information that is currently in the issue, we don't have enough information + to take action. Please reach out if you have more relevant information or + answers to our questions so that we can investigate further. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..6d921cc --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 366 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 42 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: "status:inactive" +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/ci-ecr.yml b/.github/workflows/ci-ecr.yml new file mode 100644 index 0000000..5581cc5 --- /dev/null +++ b/.github/workflows/ci-ecr.yml @@ -0,0 +1,97 @@ +name: Build and Upload geth Binary + +on: + workflow_dispatch: + push: + branches: + - main + +permissions: + id-token: write + contents: write + pull-requests: write + actions: write + +env: + NUM_BINARIES_TO_KEEP: 5 + ECR_REPOSITORY: geth-bootnode + +jobs: + # Add timestamp + Timestamp: + uses: storyprotocol/gha-workflows/.github/workflows/reusable-timestamp.yml@main + + # Build and upload the geth binary + build_and_push: + needs: Timestamp + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::478656756051:role/iac-max-role + aws-region: us-west-1 + role-session-name: github-actions + + - name: Set build arguments + run: | + echo "COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "VERSION=$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV + echo "BUILDNUM=$GITHUB_RUN_NUMBER" >> $GITHUB_ENV + + - name: Login to Amaon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Extract ECR repository URI + id: ecr-repo + run: | + echo "REPOSITORY_URI=$(aws ecr describe-repositories --repository-names ${{ env.ECR_REPOSITORY }} --query 'repositories[0].repositoryUri' --output text)" >> $GITHUB_ENV + + - name: Dockerize the geth and bootnode binary + env: + DOCKER_BUILDKIT: 1 + run: | + docker buildx create --use + docker buildx build \ + --build-arg COMMIT=$COMMIT \ + --build-arg VERSION=$VERSION \ + --build-arg BUILDNUM=$BUILDNUM \ + -t $REPOSITORY_URI:latest \ + -t $REPOSITORY_URI:$COMMIT \ + -t $REPOSITORY_URI:$VERSION \ + --cache-from=type=local,src=/tmp/.buildx-cache \ + --cache-to=type=local,dest=/tmp/.buildx-cache \ + --load \ + -f ./Dockerfile \ + . + + - name: Scan image for vulnerabilities using Trivy + uses: aquasecurity/trivy-action@0.20.0 + with: + image-ref: ${{ env.REPOSITORY_URI }}:latest + format: 'table' + exit-code: 1 + ignore-unfixed: true + vuln-type: 'os,library' + severity: 'HIGH,CRITICAL' + + - name: Push the Docker image to ECR + run: | + docker push $REPOSITORY_URI:latest + docker push $REPOSITORY_URI:$COMMIT diff --git a/.github/workflows/ci-geth-s3.yml.DEPRECATED b/.github/workflows/ci-geth-s3.yml.DEPRECATED new file mode 100644 index 0000000..87db55f --- /dev/null +++ b/.github/workflows/ci-geth-s3.yml.DEPRECATED @@ -0,0 +1,166 @@ +name: Build and Upload geth Binary + +on: + push: + branches: + - main + +permissions: + id-token: write + contents: write + pull-requests: write + actions: write + +env: + NUM_INTERNAL_BINARIES_TO_KEEP: 50 + NUM_PUBLIC_BINARIES_TO_KEEP: 400 + +jobs: + # Add timestamp + Timestamp: + uses: storyprotocol/gha-workflows/.github/workflows/reusable-timestamp.yml@main + + # Build and upload the geth binary + build_and_push: + needs: Timestamp + runs-on: ubuntu-latest + strategy: + matrix: + platform: [linux-386, linux-amd64, linux-arm, linux-arm64, darwin-amd64, darwin-arm64, windows-amd64, windows-386] + + steps: + - name: Checkout code + uses: actions/checkout@v4.1.5 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::478656756051:role/iac-max-role + aws-region: us-west-1 + role-session-name: github-actions + + - name: Extract the version + run: | + PARAMS_FILE="./params/version.go" + VERSION_MAJOR=$(awk -F= '/VersionMajor/ {gsub(/[^0-9]/, "", $2); printf "%s", $2}' $PARAMS_FILE) + VERSION_MINOR=$(awk -F= '/VersionMinor/ {gsub(/[^0-9]/, "", $2); printf "%s", $2}' $PARAMS_FILE) + VERSION_PATCH=$(awk -F= '/VersionPatch/ {gsub(/[^0-9]/, "", $2); printf "%s", $2}' $PARAMS_FILE) + VERSION_META=$(awk -F\" '/VersionMeta/ {print $2; exit}' $PARAMS_FILE) + + # Construct the full version string + VERSION="$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" + if [ "$VERSION_META" != "stable" ]; then + VERSION+="-${VERSION_META}" + fi + + echo "Version extracted: $VERSION" + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Build the geth binary + run: | + IFS="-" read -r GOOS GOARCH <<< "${{ matrix.platform }}" + output_name=./build/bin/geth + if [ "$GOOS" = "windows" ]; then + output_name+='.exe' + fi + + echo "Building for $GOOS/$GOARCH..." + env GOOS=$GOOS GOARCH=$GOARCH go build -o $output_name ./cmd/geth + + if [ $? -ne 0 ]; then + echo "Build failed!" + exit 1 + fi + + if [ ! -f "$output_name" ]; then + echo "Geth binary not found!" + exit 1 + fi + chmod +x "$output_name" + + - name: Upload the geth binary to S3 + run: | + export TZ=America/Los_Angeles + IFS="-" read -r GOOS GOARCH <<< "${{ matrix.platform }}" + TIMESTAMP=$(date +%Y%m%d%H%M%S) + HUMAN_READABLE_VERSION=$(date) + COMMIT_HASH=$(git rev-parse --short HEAD) + FOLDER_NAME="geth-${{ matrix.platform }}-${VERSION}-${COMMIT_HASH}" + ARCHIVE_NAME="${FOLDER_NAME}.tar.gz" + + binary_name=./build/bin/geth + if [ "$GOOS" = "windows" ]; then + binary_name+='.exe' + fi + + # For linux amd64 upload the binary for internal testing + if [ "${{ matrix.platform }}" = "linux-amd64" ]; then + + echo "Uploading binary for internal use..." + aws s3 cp $binary_name s3://iliad-geth-binaries/geth/geth-$TIMESTAMP --quiet + + # Update manifest file for linux-amd64 builds only + aws s3 cp s3://iliad-geth-binaries/geth/manifest.txt manifest.txt --quiet || touch manifest.txt + echo "$TIMESTAMP" >> manifest.txt + aws s3 cp manifest.txt s3://iliad-geth-binaries/geth/manifest.txt --quiet + fi + + mkdir $FOLDER_NAME + mv $binary_name $FOLDER_NAME/ + + echo "Archiving the geth binary..." + tar -czvf $ARCHIVE_NAME $FOLDER_NAME + + if [ $? -ne 0 ]; then + echo "Failed to create the archive: $ARCHIVE_NAME" + exit 1 + fi + + echo "Uploading $ARCHIVE_NAME to S3..." + aws s3 cp $ARCHIVE_NAME s3://iliad-geth-binaries/geth-public/$ARCHIVE_NAME --quiet + + if [ $? -ne 0 ]; then + echo "Failed to upload $ARCHIVE_NAME to S3!" + exit 1 + fi + + cleanup: + runs-on: ubuntu-latest + needs: build_and_push + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::478656756051:role/iac-max-role + aws-region: us-west-1 + role-session-name: github-actions + + - name: Cleanup old binaries + run: | + cleanup_s3() { + PREFIX=$1 + KEEP=$2 + + echo "Cleaning up in bucket iliad-geth-binaries with prefix: $PREFIX, keeping latest $KEEP binaries" + + aws s3api list-objects-v2 --bucket iliad-geth-binaries --prefix $PREFIX --query "sort_by(Contents,&LastModified)[*].Key" > all_binaries.json + + # Extract the list of keys, remove the latest $KEEP binaries + BINARIES_TO_DELETE=$(jq -r ".[0:-${KEEP}][]" all_binaries.json) + + if [ -n "$BINARIES_TO_DELETE" ]; then + # Delete old binaries + for key in $BINARIES_TO_DELETE; do + aws s3 rm s3://iliad-geth-binaries/$key --quiet + done + echo "Deleted old binaries: $BINARIES_TO_DELETE" + else + echo "No old binaries to delete." + fi + } + + # Cleanup internal geth binaries + cleanup_s3 "geth/" "${NUM_INTERNAL_BINARIES_TO_KEEP}" + + # Cleanup public geth binaries + cleanup_s3 "geth-public/" "${NUM_PUBLIC_BINARIES_TO_KEEP}" diff --git a/.github/workflows/ci-s3.yml b/.github/workflows/ci-s3.yml new file mode 100644 index 0000000..35c414e --- /dev/null +++ b/.github/workflows/ci-s3.yml @@ -0,0 +1,201 @@ +name: Build and Upload geth Binary to s3 + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + id-token: write + contents: write + pull-requests: write + actions: write + +env: + NUM_INTERNAL_BINARIES_TO_KEEP: 50 + NUM_PUBLIC_BINARIES_TO_KEEP: 400 + S3_BUCKET: story-geth-binaries + +jobs: + # Add timestamp + Timestamp: + uses: storyprotocol/gha-workflows/.github/workflows/reusable-timestamp.yml@main + + # Build and upload the geth binary + build_and_push: + needs: Timestamp + runs-on: ubuntu-latest + strategy: + matrix: + platform: + [linux-amd64, linux-arm64, darwin-amd64, darwin-arm64, windows-amd64] + + steps: + - name: Checkout code + uses: actions/checkout@v4.1.5 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_SERVICE_STAGING }}:role/iac-max-role + aws-region: us-west-1 + role-session-name: github-actions + + - name: Extract the version + run: | + PARAMS_FILE="./params/version.go" + VERSION_MAJOR=$(awk -F= '/VersionMajor/ {gsub(/[^0-9]/, "", $2); printf "%s", $2}' $PARAMS_FILE) + VERSION_MINOR=$(awk -F= '/VersionMinor/ {gsub(/[^0-9]/, "", $2); printf "%s", $2}' $PARAMS_FILE) + VERSION_PATCH=$(awk -F= '/VersionPatch/ {gsub(/[^0-9]/, "", $2); printf "%s", $2}' $PARAMS_FILE) + VERSION_META=$(awk -F\" '/VersionMeta/ {print $2; exit}' $PARAMS_FILE) + + # Construct the full version string + VERSION="$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" + if [ "$VERSION_META" != "stable" ]; then + VERSION+="-${VERSION_META}" + fi + + echo "Version extracted: $VERSION" + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "VERSION_META=$VERSION_META" >> $GITHUB_ENV + + - name: Build the geth binary + run: | + IFS="-" read -r GOOS GOARCH <<< "${{ matrix.platform }}" + output_name=./build/bin/geth + if [ "$GOOS" = "windows" ]; then + output_name+='.exe' + fi + + echo "Building for $GOOS/$GOARCH..." + env GOOS=$GOOS GOARCH=$GOARCH go build -o $output_name ./cmd/geth + + if [ $? -ne 0 ]; then + echo "Build failed!" + exit 1 + fi + + if [ ! -f "$output_name" ]; then + echo "Geth binary not found!" + exit 1 + fi + + # Apply chmod only for non-windows builds + if [ "$GOOS" != "windows" ]; then + chmod +x "$output_name" + fi + + - name: Upload the geth binary to S3 + run: | + export TZ=America/Los_Angeles + IFS="-" read -r GOOS GOARCH <<< "${{ matrix.platform }}" + TIMESTAMP=$(date +%Y%m%d%H%M%S) + echo "Timestamp: $TIMESTAMP" + HUMAN_READABLE_VERSION=$(date) + COMMIT_HASH=$(git rev-parse --short HEAD) + FOLDER_NAME="geth-${{ matrix.platform }}-${VERSION}-${COMMIT_HASH}" + ARCHIVE_NAME="${FOLDER_NAME}.tar.gz" + PUBLIC_DOWNLOAD_URL="https://$S3_BUCKET.s3.us-west-1.amazonaws.com/geth-public/$ARCHIVE_NAME" + + binary_name=./build/bin/geth + if [ "$GOOS" = "windows" ]; then + binary_name+='.exe' + fi + + # For linux amd64 upload the binary for internal testing + if [ "${{ matrix.platform }}" = "linux-amd64" ]; then + + echo "Uploading binary for internal use..." + aws s3 cp $binary_name s3://$S3_BUCKET/geth/geth-$TIMESTAMP --quiet + + # Update manifest file for linux-amd64 builds only + aws s3 cp s3://$S3_BUCKET/geth/manifest.txt manifest.txt --quiet || touch manifest.txt + echo "$TIMESTAMP" >> manifest.txt + aws s3 cp manifest.txt s3://$S3_BUCKET/geth/manifest.txt --quiet + + # Update version file + aws s3 cp s3://$S3_BUCKET/geth-public/version.txt version.txt --quiet || printf "File Name\t\t\tVerison\t\t\t\tCommit Hash\t\tTimestamp\n" > version.txt + + if [ "${VERSION_META}" != "stable" ]; then + printf "$VERSION-$COMMIT_HASH\t\t$VERSION\t\t\t$COMMIT_HASH\t\t\t$TIMESTAMP\n" >> version.txt + else + printf "$VERSION-$COMMIT_HASH\t\t\t$VERSION\t\t\t\t$COMMIT_HASH\t\t\t$TIMESTAMP\n" >> version.txt + fi + + aws s3 cp version.txt s3://$S3_BUCKET/geth-public/version.txt --quiet + fi + + mkdir $FOLDER_NAME + mv $binary_name $FOLDER_NAME/ + + echo "Archiving the geth binary..." + tar -czvf $ARCHIVE_NAME $FOLDER_NAME + + if [ $? -ne 0 ]; then + echo "Failed to create the archive: $ARCHIVE_NAME" + exit 1 + fi + + echo "Uploading $ARCHIVE_NAME to S3..." + aws s3 cp $ARCHIVE_NAME s3://$S3_BUCKET/geth-public/$ARCHIVE_NAME --quiet + + if [ $? -ne 0 ]; then + echo "Failed to upload $ARCHIVE_NAME to S3!" + exit 1 + fi + + echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_ENV + echo "PUBLIC_DOWNLOAD_URL=$PUBLIC_DOWNLOAD_URL" >> $GITHUB_ENV + + - name: Add binary version back to PR + if: matrix.platform == 'linux-amd64' + uses: mshick/add-pr-comment@v2 + with: + message: | + ### Binary uploaded successfully 🎉 + 📦 **Version Name:** ${{ env.VERSION }}-${{ env.COMMIT_HASH }} + 📦 **Download Source:** [AWS S3](${{ env.PUBLIC_DOWNLOAD_URL }}) + + cleanup: + runs-on: ubuntu-latest + needs: build_and_push + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::478656756051:role/iac-max-role + aws-region: us-west-1 + role-session-name: github-actions + + - name: Cleanup old binaries + run: | + cleanup_s3() { + PREFIX=$1 + KEEP=$2 + + echo "Cleaning up in bucket $S3_BUCKET with prefix: $PREFIX, keeping latest $KEEP binaries" + + aws s3api list-objects-v2 --bucket $S3_BUCKET --prefix $PREFIX --query "sort_by(Contents,&LastModified)[*].Key" > all_binaries.json + + # Extract the list of keys, remove the latest $KEEP binaries + BINARIES_TO_DELETE=$(jq -r ".[0:-${KEEP}][]" all_binaries.json) + + if [ -n "$BINARIES_TO_DELETE" ]; then + # Delete old binaries + for key in $BINARIES_TO_DELETE; do + aws s3 rm s3://$S3_BUCKET/$key --quiet + done + echo "Deleted old binaries: $BINARIES_TO_DELETE" + else + echo "No old binaries to delete." + fi + } + + # Cleanup internal geth binaries + cleanup_s3 "geth/" "${NUM_INTERNAL_BINARIES_TO_KEEP}" + + # Cleanup public geth binaries + cleanup_s3 "geth-public/" "${NUM_PUBLIC_BINARIES_TO_KEEP}" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..3303c37 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,39 @@ +name: Go tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.21.4 + + - name: Cache Go modules + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-mod-cache-${{ hashFiles('**/go.mod', '**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-mod-cache-latest + + - name: Install dependencies + run: | + go mod download + + - name: Run tests + run: go test -short ./... + env: + GOOS: linux + GOARCH: amd64 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f27cdc --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile ~/.gitignore_global + +/tmp +*/**/*un~ +*/**/*.test +*un~ +.DS_Store +*/**/.DS_Store +.ethtest +*/**/*tx_database* +*/**/*dapps* +build/_vendor/pkg + +#* +.#* +*# +*~ +.project +.settings + +# used by the Makefile +/build/_workspace/ +/build/cache/ +/build/bin/ +/geth*.zip + +# travis +profile.tmp +profile.cov + +# IdeaIDE +.idea + +# VS Code +.vscode + +# dashboard +/dashboard/assets/flow-typed +/dashboard/assets/node_modules +/dashboard/assets/stats.json +/dashboard/assets/bundle.js +/dashboard/assets/bundle.js.map +/dashboard/assets/package-lock.json + +**/yarn-error.log +logs/ + +tests/spec-tests/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..241c169 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "tests"] + path = tests/testdata + url = https://github.com/ethereum/tests + shallow = true +[submodule "evm-benchmarks"] + path = tests/evm-benchmarks + url = https://github.com/ipsilon/evm-benchmarks + shallow = true diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..2132f54 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,75 @@ +# This file configures github.com/golangci/golangci-lint. + +run: + timeout: 20m + tests: true + # default is true. Enables skipping of directories: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + skip-dirs-use-default: true + +linters: + disable-all: true + enable: + - goimports + - gosimple + - govet + - ineffassign + - misspell + - unconvert + - typecheck + - unused + - staticcheck + - bidichk + - durationcheck + - exportloopref + - whitespace + - revive # only certain checks enabled + + ### linters we tried and will not be using: + ### + # - structcheck # lots of false positives + # - errcheck #lot of false positives + # - contextcheck + # - errchkjson # lots of false positives + # - errorlint # this check crashes + # - exhaustive # silly check + # - makezero # false positives + # - nilerr # several intentional + +linters-settings: + gofmt: + simplify: true + revive: + enable-all-rules: false + # here we enable specific useful rules + # see https://golangci-lint.run/usage/linters/#revive for supported rules + rules: + - name: receiver-naming + severity: warning + disabled: false + exclude: [""] + +issues: + exclude-files: + - core/genesis_alloc.go + exclude-rules: + - path: crypto/bn256/cloudflare/optate.go + linters: + - deadcode + - staticcheck + - path: crypto/bn256/ + linters: + - revive + - path: internal/build/pgp.go + text: 'SA1019: "golang.org/x/crypto/openpgp" is deprecated: this package is unmaintained except for security fixes.' + - path: core/vm/contracts.go + text: 'SA1019: "golang.org/x/crypto/ripemd160" is deprecated: RIPEMD-160 is a legacy hash and should not be used for new applications.' + - path: accounts/usbwallet/trezor.go + text: 'SA1019: "github.com/golang/protobuf/proto" is deprecated: Use the "google.golang.org/protobuf/proto" package instead.' + - path: accounts/usbwallet/trezor/ + text: 'SA1019: "github.com/golang/protobuf/proto" is deprecated: Use the "google.golang.org/protobuf/proto" package instead.' + exclude: + - 'SA1019: event.TypeMux is deprecated: use Feed' + - 'SA1019: strings.Title is deprecated' + - 'SA1019: strings.Title has been deprecated since Go 1.18 and an alternative has been available since Go 1.0: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead.' + - 'SA1029: should not use built-in type string as key for value' diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..312e51d --- /dev/null +++ b/.mailmap @@ -0,0 +1,236 @@ +Aaron Buchwald + +Aaron Kumavis + +Abel Nieto +Abel Nieto + +Afri Schoedon <58883403+q9f@users.noreply.github.com> +Afri Schoedon <5chdn@users.noreply.github.com> <58883403+q9f@users.noreply.github.com> + +Alec Perseghin + +Aleksey Smyrnov + +Alex Leverington +Alex Leverington + +Alex Pozhilenkov +Alex Pozhilenkov + +Alexey Akhunov + +Alon Muroch + +Andrey Petrov +Andrey Petrov + +Arkadiy Paronyan + +Armin Braun + +Aron Fischer + +Austin Roberts +Austin Roberts + +Bas van Kervel +Bas van Kervel +Bas van Kervel +Bas van Kervel + +Boqin Qin +Boqin Qin + +Casey Detrio + +Cheng Li + +Chris Ziogas +Chris Ziogas + +Christoph Jentzsch + +Diederik Loerakker + +Dimitry Khokhlov + +Domino Valdano + +Edgar Aroutiounian + +Elliot Shepherd + +Enrique Fynn + +Enrique Fynn +Enrique Fynn + +Ernesto del Toro +Ernesto del Toro + +Everton Fraga + +Felix Lange +Felix Lange + +Frank Wang + +Gary Rong + +Gavin Wood + +Gregg Dourgarian + +Guillaume Ballet +Guillaume Ballet <3272758+gballet@users.noreply.github.com> + +Guillaume Nicolas + +Hanjiang Yu +Hanjiang Yu <42531996+de1acr0ix@users.noreply.github.com> + +Heiko Hees + +Henning Diedrich +Henning Diedrich Drake Burroughs + +Hwanjo Heo <34005989+hwanjo@users.noreply.github.com> + +Iskander (Alex) Sharipov +Iskander (Alex) Sharipov + +Jae Kwon + +JanoÅ¡ GuljaÅ¡ +JanoÅ¡ GuljaÅ¡ Janos Guljas + +Jared Wasinger + +Jason Carver +Jason Carver + +Javier Peletier +Javier Peletier + +Jeffrey Wilcke +Jeffrey Wilcke +Jeffrey Wilcke +Jeffrey Wilcke + +Jens Agerberg + +Joseph Chow +Joseph Chow ethers + + +Joseph Goulden + +Justin Drake + +Kenso Trabing +Kenso Trabing + +Liang Ma +Liang Ma + +Louis Holbrook +Louis Holbrook + +Maran Hidskes + +Marian Oancea + +Martin Becze +Martin Becze + +Martin Lundfall + +Matt Garnett <14004106+lightclient@users.noreply.github.com> + +Matthew Halpern +Matthew Halpern + +Michael Riabzev + +Nchinda Nchinda + +Nick Dodson + +Nick Johnson + +Nick Savers + +Nishant Das +Nishant Das + +Olivier Hervieu + +Pascal Dierich +Pascal Dierich + +RJ Catalano +RJ Catalano + +Ralph Caraveo + +Rene Lubov <41963722+renaynay@users.noreply.github.com> + +Robert Zaremba +Robert Zaremba + +Roman Mandeleil + +Sorin Neacsu +Sorin Neacsu + +Sven Ehlert + +Taylor Gerring +Taylor Gerring + +Thomas Bocek + +Tim Cooijmans + +Valentin Wüstholz +Valentin Wüstholz + +Victor Tran + +Viktor Trón + +Ville Sundell + +Vincent G + +Vitalik Buterin + +Vlad Gluhovsky +Vlad Gluhovsky + +Wenshao Zhong +Wenshao Zhong <11510383@mail.sustc.edu.cn> +Wenshao Zhong <374662347@qq.com> + +Will Villanueva + +Xiaobing Jiang + +Xudong Liu <33193253+r1cs@users.noreply.github.com> + +Yohann LeÌon + +Zachinquarantine +Zachinquarantine + +Ziyuan Zhong + +Zsolt Felföldi + +meowsbits +meowsbits <45600330+meowsbits@users.noreply.github.com> + +nedifi <103940716+nedifi@users.noreply.github.com> + +МакÑим ЧуÑовлÑнов diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2dc80f8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,154 @@ +language: go +go_import_path: github.com/ethereum/go-ethereum +sudo: false +jobs: + allow_failures: + - stage: build + os: osx + env: + - azure-osx + + include: + # 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 + if: type = push + os: linux + arch: amd64 + dist: noble + go: 1.22.x + env: + - docker + services: + - docker + git: + submodules: false # avoid cloning ethereum/tests + before_install: + - export DOCKER_CLI_EXPERIMENTAL=enabled + script: + - go run build/ci.go docker -image -manifest amd64,arm64 -upload ethereum/client-go + + - stage: build + if: type = push + os: linux + arch: arm64 + dist: noble + go: 1.22.x + env: + - docker + services: + - docker + git: + submodules: false # avoid cloning ethereum/tests + before_install: + - export DOCKER_CLI_EXPERIMENTAL=enabled + script: + - go run build/ci.go docker -image -manifest amd64,arm64 -upload ethereum/client-go + + # This builder does the Linux Azure uploads + - stage: build + if: type = push + os: linux + dist: noble + sudo: required + go: 1.22.x + env: + - azure-linux + git: + submodules: false # avoid cloning ethereum/tests + script: + # build amd64 + - go run build/ci.go install -dlgo + - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds + + # build 386 + - sudo -E apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib + - go run build/ci.go install -dlgo -arch 386 + - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds + + # Switch over GCC to cross compilation (breaks 386, hence why do it here only) + - sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross + - sudo ln -s /usr/include/asm-generic /usr/include/asm + + - GOARM=5 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc + - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds + - GOARM=6 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc + - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds + - GOARM=7 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabihf-gcc + - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc + - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds + + # This builder does the OSX Azure uploads + - stage: build + if: type = push + os: osx + osx_image: xcode14.2 + go: 1.22.x + env: + - azure-osx + 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 + if: type = push + os: linux + arch: amd64 + dist: noble + go: 1.22.x + script: + - travis_wait 45 go run build/ci.go test $TEST_PACKAGES + + - stage: build + if: type = push + os: linux + dist: noble + go: 1.21.x + script: + - travis_wait 45 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: noble + go: 1.22.x + env: + - ubuntu-ppa + git: + submodules: false # avoid cloning ethereum/tests + before_install: + - sudo -E apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot + script: + - echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts + - go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder " + + # This builder does the Azure archive purges to avoid accumulating junk + - stage: build + if: type = cron + os: linux + dist: noble + go: 1.22.x + env: + - azure-purge + git: + submodules: false # avoid cloning ethereum/tests + script: + - go run build/ci.go purge -store gethstore/builds -days 14 + + # This builder executes race tests + - stage: build + if: type = cron + os: linux + dist: noble + go: 1.22.x + env: + - racetests + script: + - travis_wait 60 go run build/ci.go test -race $TEST_PACKAGES diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..151c850 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,591 @@ +# This is the official list of go-ethereum authors for copyright purposes. + +6543 <6543@obermui.de> +a e r t h +Aaron Buchwald +Abel Nieto +Adam Babik +Adam Schmideg +Aditya +Aditya Arora +Adrià Cidre +Afanasii Kurakin +Afri Schoedon <5chdn@users.noreply.github.com> +Agustin Armellini Fischer +Ahyun +Airead +Alan Chen +Alejandro Isaza +Aleksey Smyrnov +Ales Katona +Alex Beregszaszi +Alex Leverington +Alex Mazalov +Alex Pozhilenkov +Alex Prut <1648497+alexprut@users.noreply.github.com> +Alex Wu +Alexander van der Meij +Alexander Yastrebov +Alexandre Van de Sande +Alexey Akhunov +Alexey Shekhirin +alexwang <39109351+dipingxian2@users.noreply.github.com> +Ali Atiia <42751398+aliatiia@users.noreply.github.com> +Ali Hajimirza +am2rican5 +AmitBRD <60668103+AmitBRD@users.noreply.github.com> +Anatole <62328077+a2br@users.noreply.github.com> +Andrea Franz +Andrei Maiboroda +Andrey Petrov +ANOTHEL +Antoine Rondelet +Antoine Toulme +Anton Evangelatov +Antonio Salazar Cardozo +Arba Sasmoyo +Armani Ferrante +Armin Braun +Aron Fischer +atsushi-ishibashi +Austin Roberts +ayeowch +b00ris +b1ackd0t +bailantaotao +baizhenxuan +Balaji Shetty Pachai <32358081+balajipachai@users.noreply.github.com> +Balint Gabor +baptiste-b-pegasys <85155432+baptiste-b-pegasys@users.noreply.github.com> +Bas van Kervel +Benjamin Brent +benma +Benoit Verkindt +Binacs +bloonfield +Bo +Bo Ye +Bob Glickstein +Boqin Qin +Brandon Harden +Brent +Brian Schroeder +Bruno Å kvorc +C. Brown +Caesar Chad +Casey Detrio +CDsigma +Ceelog +Ceyhun Onur +chabashilah +changhong +Chase Wright +Chen Quan +Cheng Li +chenglin <910372762@qq.com> +chenyufeng +Chris Pacia +Chris Ziogas +Christian Muehlhaeuser +Christoph Jentzsch +chuwt +cong +Connor Stein +Corey Lin <514971757@qq.com> +courtier +cpusoft +Crispin Flowerday +croath +cui <523516579@qq.com> +Dan DeGreef +Dan Kinsley +Dan Sosedoff +Daniel A. Nagy +Daniel Perez +Daniel Sloof +Darioush Jalali +Darrel Herbst +Dave Appleton +Dave McGregor +David Cai +David Huie +Denver +Derek Chiang +Derek Gottfrid +Di Peng +Diederik Loerakker +Diego Siqueira +Diep Pham +dipingxian2 <39109351+dipingxian2@users.noreply.github.com> +divergencetech <94644849+divergencetech@users.noreply.github.com> +dm4 +Dmitrij Koniajev +Dmitry Shulyak +Dmitry Zenovich +Domino Valdano +Dragan Milic +dragonvslinux <35779158+dragononcrypto@users.noreply.github.com> +Edgar Aroutiounian +Eduard S +Egon Elbre +Elad +Eli +Elias Naur +Elliot Shepherd +Emil +emile +Emmanuel T Odeke +Eng Zer Jun +Enrique Fynn +Enrique Ortiz +EOS Classic +Erichin +Ernesto del Toro +Ethan Buchman +ethersphere +Eugene Lepeico +Eugene Valeyev +Evangelos Pappas +Everton Fraga +Evgeny +Evgeny Danilenko <6655321@bk.ru> +evgk +Evolution404 <35091674+Evolution404@users.noreply.github.com> +EXEC +Fabian Vogelsteller +Fabio Barone +Fabio Berger +FaceHo +Felipe Strozberg <48066928+FelStroz@users.noreply.github.com> +Felix Lange +Ferenc Szabo +ferhat elmas +Ferran Borreguero +Fiisio +Fire Man <55934298+basdevelop@users.noreply.github.com> +flowerofdream <775654398@qq.com> +fomotrader <82184770+fomotrader@users.noreply.github.com> +ForLina <471133417@qq.com> +Frank Szendzielarz <33515470+FrankSzendzielarz@users.noreply.github.com> +Frank Wang +Franklin +Furkan KAMACI +Fuyang Deng +GagziW +Gary Rong +Gautam Botrel +George Ornbo +Giuseppe Bertone +Greg Colvin +Gregg Dourgarian +Gregory Markou <16929357+GregTheGreek@users.noreply.github.com> +Guifel +Guilherme Salgado +Guillaume Ballet +Guillaume Nicolas +GuiltyMorishita +Guruprasad Kamath <48196632+gurukamath@users.noreply.github.com> +Gus +Gustav Simonsson +Gísli Kristjánsson +Ha ÄANG +HackyMiner +hadv +Hanjiang Yu +Hao Bryan Cheng +Hao Duan +HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> +Harry Dutton +haryu703 <34744512+haryu703@users.noreply.github.com> +Hendrik Hofstadt +Henning Diedrich +henopied <13500516+henopied@users.noreply.github.com> +hero5512 +holisticode +Hongbin Mao +Hsien-Tang Kao +hsyodyssey <47173566+hsyodyssey@users.noreply.github.com> +Husam Ibrahim <39692071+HusamIbrahim@users.noreply.github.com> +Hwanjo Heo <34005989+hwanjo@users.noreply.github.com> +hydai +Hyung-Kyu Hqueue Choi +HÃ¥vard Anda Estensen +Ian Macalinao +Ian Norden +icodezjb +Ikko Ashimine +Ilan Gitter <8359193+gitteri@users.noreply.github.com> +ImanSharaf <78227895+ImanSharaf@users.noreply.github.com> +Isidoro Ghezzi +Iskander (Alex) Sharipov +Ivan Bogatyy +Ivan Daniluk +Ivo Georgiev +jacksoom +Jae Kwon +James Prestwich <10149425+prestwich@users.noreply.github.com> +Jamie Pitts +JanoÅ¡ GuljaÅ¡ +Jared Wasinger +Jason Carver +Javier Peletier +Javier Sagredo +Jay +Jay Guo +Jaynti Kanani +Jeff Prestes +Jeff R. Allen +Jeff Wentworth +Jeffery Robert Walsh +Jeffrey Wilcke +Jens Agerberg +Jeremy McNevin +Jeremy Schlatter +Jerzy Lasyk +Jesse Tane +Jia Chenhui +Jim McDonald +jk-jeongkyun <45347815+jeongkyun-oh@users.noreply.github.com> +jkcomment +JoeGruffins <34998433+JoeGruffins@users.noreply.github.com> +Joel Burget +John C. Vernaleo +John Difool +Johns Beharry +Jonas +Jonathan Brown +Jonathan Chappelow +Jonathan Gimeno +JoranHonig +Jordan Krage +Jorropo +Joseph Chow +Joshua Colvin +Joshua Gutow +jovijovi +jtakalai +JU HYEONG PARK +Julian Y +Justin Clark-Casey +Justin Drake +Justus +Kawashima <91420903+sscodereth@users.noreply.github.com> +ken10100147 +Kenji Siu +Kenso Trabing +Kevin +kevin.xu +KibGzr +kiel barry +kilic +kimmylin <30611210+kimmylin@users.noreply.github.com> +Kitten King <53072918+kittenking@users.noreply.github.com> +knarfeh +Kobi Gurkan +komika +Konrad Feldmeier +Kris Shinn +Kristofer Peterson +Kumar Anirudha +Kurkó Mihály +Kushagra Sharma +Kwuaint <34888408+kwuaint@users.noreply.github.com> +Kyuntae Ethan Kim +Lee Bousfield +Lefteris Karapetsas +Leif Jurvetson +Leo Shklovskii +LeoLiao +Lewis Marshall +lhendre +Li Dongwei +Liang Ma +Liang ZOU +libby kent +libotony +LieutenantRoger +ligi +LioæŽæ¬§ +lmittmann +Lorenzo Manacorda +Louis Holbrook +Luca Zeug +Lucas Hendren +lzhfromustc <43191155+lzhfromustc@users.noreply.github.com> +Magicking +manlio +Maran Hidskes +Marek Kotewicz +Mariano Cortesi +Marius van der Wijden +Mark +Mark Rushakoff +mark.lin +Martin Alex Philip Dawson +Martin Holst Swende +Martin Klepsch +Martin Lundfall +Martin Michlmayr +Martin Redmond <21436+reds@users.noreply.github.com> +Mason Fischer +Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com> +Mats Julian Olsen +Matt Garnett <14004106+lightclient@users.noreply.github.com> +Matt K <1036969+mkrump@users.noreply.github.com> +Matthew Di Ferrante +Matthew Halpern +Matthew Wampler-Doty +Max Sistemich +Maxim Zhiburt +Maximilian Meister +me020523 +Melvin Junhee Woo +meowsbits +Micah Zoltu +Michael Forney +Michael Riabzev +Michael Ruminer +michael1011 +Miguel Mota +Mike Burr +Mikhail Mikheev +milesvant +Miro +Miya Chen +Mohanson +mr_franklin +Mudit Gupta +Mymskmkt <1847234666@qq.com> +Nalin Bhardwaj +Natsu Kagami +Nchinda Nchinda +nebojsa94 +necaremus +nedifi <103940716+nedifi@users.noreply.github.com> +needkane <604476380@qq.com> +Nguyen Kien Trung +Nguyen Sy Thanh Son +Nic Jansma +Nick Dodson +Nick Johnson +Nicolas Feignon +Nicolas Guillaume +Nikita Kozhemyakin +Nikola Madjarevic +Nilesh Trivedi +Nimrod Gutman +Nishant Das +njupt-moon <1015041018@njupt.edu.cn> +nkbai +noam-alchemy <76969113+noam-alchemy@users.noreply.github.com> +nobody +Noman +nujabes403 +Nye Liu +Oleg Kovalov +Oli Bye +Oliver Tale-Yazdi +Olivier Hervieu +Or Neeman +Osoro Bironga +Osuke +Pantelis Peslis +Pascal Dierich +Patrick O'Grady +Pau +Paul Berg +Paul Litvak +Paul-Armand Verhaegen +Paulo L F Casaretto +PaweÅ‚ Bylica +Pedro Gomes +Pedro Pombeiro +Peter Broadhurst +peter cresswell +Peter Pratscher +Peter Simard +Petr Mikusek +Philip Schlump +Pierre Neter +Pierre R +piersy +PilkyuJung +Piotr Dyraga +ploui <64719999+ploui@users.noreply.github.com> +Preston Van Loon +Prince Sinha +Péter Szilágyi +qd-ethan <31876119+qdgogogo@users.noreply.github.com> +Qian Bin +Quest Henkart +Rachel Franks +Rafael Matias +Raghav Sood +Ralph Caraveo +Ramesh Nair +rangzen +reinerRubin +Rene Lubov <41963722+renaynay@users.noreply.github.com> +rhaps107 +Ricardo Catalinas Jiménez +Ricardo Domingos +Richard Hart +Rick +RJ Catalano +Rob +Rob Mulholand +Robert Zaremba +Roc Yu +Roman Mazalov <83914728+gopherxyz@users.noreply.github.com> +Ross <9055337+Chadsr@users.noreply.github.com> +Runchao Han +Russ Cox +Ryan Schneider +ryanc414 +Rémy Roy +S. Matthew English +salanfe +Sam <39165351+Xia-Sam@users.noreply.github.com> +Sammy Libre <7374093+sammy007@users.noreply.github.com> +Samuel Marks +sanskarkhare +Sarlor +Sasuke1964 +Satpal <28562234+SatpalSandhu61@users.noreply.github.com> +Saulius Grigaitis +Sean +Serhat Åževki Dinçer +Shane Bammel +shawn <36943337+lxex@users.noreply.github.com> +shigeyuki azuchi +Shihao Xia +Shiming +Shintaro Kaneko +shiqinfeng1 <150627601@qq.com> +Shuai Qi +Shude Li +Shunsuke Watanabe +silence +Simon Jentzsch +Sina Mahmoodi <1591639+s1na@users.noreply.github.com> +sixdays +SjonHortensius +Slava Karpenko +slumber1122 +Smilenator +soc1c +Sorin Neacsu +Sparty +Stein Dekker +Steve Gattuso +Steve Ruckdashel +Steve Waldman +Steven E. Harris +Steven Roose +stompesi +stormpang +sunxiaojun2014 +Suriyaa Sundararuban +Sylvain Laurent +Taeik Lim +tamirms +Tangui Clairet +Tatsuya Shimoda +Taylor Gerring +TColl <38299499+TColl@users.noreply.github.com> +terasum +tgyKomgo <52910426+tgyKomgo@users.noreply.github.com> +Thad Guidry +Thomas Bocek +thomasmodeneis +thumb8432 +Ti Zhou +tia-99 <67107070+tia-99@users.noreply.github.com> +Tim Cooijmans +Tobias Hildebrandt <79341166+tobias-hildebrandt@users.noreply.github.com> +Tosh Camille +tsarpaul +Tyler Chambers <2775339+tylerchambers@users.noreply.github.com> +tzapu +ucwong +uji <49834542+uji@users.noreply.github.com> +ult-bobonovski +Valentin Trinqué +Valentin Wüstholz +Vedhavyas Singareddi +Victor Farazdagi +Victor Tran +Vie +Viktor Trón +Ville Sundell +vim88 +Vincent G +Vincent Serpoul +Vinod Damle +Vitalik Buterin +Vitaly Bogdanov +Vitaly V +Vivek Anand +Vlad Bokov +Vlad Gluhovsky +Ward Bradt +Water <44689567+codeoneline@users.noreply.github.com> +wbt +weimumu <934657014@qq.com> +Wenbiao Zheng +Wenshao Zhong +Will Villanueva +William Morriss +William Setzer +williambannas +wuff1996 <33193253+wuff1996@users.noreply.github.com> +Wuxiang +Xiaobing Jiang +xiekeyang +xincaosu +xinluyin <31590468+xinluyin@users.noreply.github.com> +Xudong Liu <33193253+r1cs@users.noreply.github.com> +xwjack +yahtoo +Yang Hau +YaoZengzeng +YH-Zhou +Yihau Chen +Yohann LeÌon +Yoichi Hirai +Yole <007yuyue@gmail.com> +Yondon Fu +YOSHIDA Masanori +yoza +yumiel yoomee1313 +Yusup +yutianwu +ywzqwwt <39263032+ywzqwwt@users.noreply.github.com> +zaccoding +Zach +Zachinquarantine +zah +Zahoor Mohamed +Zak Cole +zcheng9 +zer0to0ne <36526113+zer0to0ne@users.noreply.github.com> +zgfzgf <48779939+zgfzgf@users.noreply.github.com> +Zhang Zhuo +zhangsoledad <787953403@qq.com> +zhaochonghe <41711151+zhaochonghe@users.noreply.github.com> +Zhenguo Niu +zhiqiangxu <652732310@qq.com> +Zhou Zhiyao +Ziyuan Zhong +Zoe Nolan +Zou Guangxian +Zsolt Felföldi +Åukasz Kurowski +Åukasz Zimnoch +ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com> +МакÑим ЧуÑовлÑнов +大彬 +沉风 +è´ºé¹é£ž +陈佳 +유용환 <33824408+eric-yoo@users.noreply.github.com> diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program is distributed in the hope that 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..63b92e0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# Support setting various labels on the final image +ARG COMMIT="" +ARG VERSION="" +ARG BUILDNUM="" + +# Build Geth in a stock Go builder container +FROM golang:1.22-alpine as builder + +RUN apk add --no-cache gcc musl-dev linux-headers git + +# Get dependencies - will also be cached if we won't change go.mod/go.sum +COPY go.mod /go-ethereum/ +COPY go.sum /go-ethereum/ +RUN cd /go-ethereum && go mod download + +ADD . /go-ethereum +RUN cd /go-ethereum && go run build/ci.go install -static ./cmd/geth + +# Pull Geth into a second stage deploy alpine container +FROM alpine:latest + +RUN apk add --no-cache ca-certificates +COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/ + +EXPOSE 8545 8546 30303 30303/udp +ENTRYPOINT ["geth"] + +# Add some metadata labels to help programmatic image consumption +ARG COMMIT="" +ARG VERSION="" +ARG BUILDNUM="" + +LABEL commit="$COMMIT" version="$VERSION" buildnum="$BUILDNUM" diff --git a/Dockerfile.alltools b/Dockerfile.alltools new file mode 100644 index 0000000..bdefd95 --- /dev/null +++ b/Dockerfile.alltools @@ -0,0 +1,32 @@ +# Support setting various labels on the final image +ARG COMMIT="" +ARG VERSION="" +ARG BUILDNUM="" + +# Build Geth in a stock Go builder container +FROM golang:1.22-alpine as builder + +RUN apk add --no-cache gcc musl-dev linux-headers git + +# Get dependencies - will also be cached if we won't change go.mod/go.sum +COPY go.mod /go-ethereum/ +COPY go.sum /go-ethereum/ +RUN cd /go-ethereum && go mod download + +ADD . /go-ethereum +RUN cd /go-ethereum && go run build/ci.go install -static + +# Pull all binaries into a second stage deploy alpine container +FROM alpine:latest + +RUN apk add --no-cache ca-certificates +COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ + +EXPOSE 8545 8546 30303 30303/udp + +# Add some metadata labels to help programmatic image consumption +ARG COMMIT="" +ARG VERSION="" +ARG BUILDNUM="" + +LABEL commit="$COMMIT" version="$VERSION" buildnum="$BUILDNUM" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..857cb8c --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +# This Makefile is meant to be used by people that do not usually work +# with Go source code. If you know what GOPATH is then you probably +# don't need to bother with make. + +.PHONY: geth all test lint fmt clean devtools help + +GOBIN = ./build/bin +GO ?= latest +GORUN = go run + +#? geth: Build geth. +geth: + $(GORUN) build/ci.go install ./cmd/geth + @echo "Done building." + @echo "Run \"$(GOBIN)/geth\" to launch geth." + +#? all: Build all packages and executables. +all: + $(GORUN) build/ci.go install + +#? test: Run the tests. +test: all + $(GORUN) build/ci.go test + +#? lint: Run certain pre-selected linters. +lint: ## Run linters. + $(GORUN) build/ci.go lint + +#? fmt: Ensure consistent code formatting. +fmt: + gofmt -s -w $(shell find . -name "*.go") + +#? clean: Clean go cache, built executables, and the auto generated folder. +clean: + go clean -cache + rm -fr build/_workspace/pkg/ $(GOBIN)/* + +# The devtools target installs tools required for 'go generate'. +# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'. + +#? devtools: Install recommended developer tools. +devtools: + env GOBIN= go install golang.org/x/tools/cmd/stringer@latest + env GOBIN= go install github.com/fjl/gencodec@latest + env GOBIN= go install github.com/golang/protobuf/protoc-gen-go@latest + env GOBIN= go install ./cmd/abigen + @type "solc" 2> /dev/null || echo 'Please install solc' + @type "protoc" 2> /dev/null || echo 'Please install protoc' + +#? help: Get more info on make commands. +help: Makefile + @echo '' + @echo 'Usage:' + @echo ' make [target]' + @echo '' + @echo 'Targets:' + @sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /' diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..41b900d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,175 @@ +# Security Policy + +## Supported Versions + +Please see [Releases](https://github.com/ethereum/go-ethereum/releases). We recommend using the [most recently released version](https://github.com/ethereum/go-ethereum/releases/latest). + +## Audit reports + +Audit reports are published in the `docs` folder: https://github.com/ethereum/go-ethereum/tree/master/docs/audits + +| Scope | Date | Report Link | +| ------- | ------- | ----------- | +| `geth` | 20170425 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25_Geth-audit_Truesec.pdf) | +| `clef` | 20180914 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2018-09-14_Clef-audit_NCC.pdf) | +| `Discv5` | 20191015 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2019-10-15_Discv5_audit_LeastAuthority.pdf) | +| `Discv5` | 20200124 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf) | + +## Reporting a Vulnerability + +**Please do not file a public ticket** mentioning the vulnerability. + +To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. Please read the [disclosure page](https://github.com/ethereum/go-ethereum/security/advisories?state=published) for more information about publicly disclosed security vulnerabilities. + +Use the built-in `geth version-check` feature to check whether the software is affected by any known vulnerability. This command will fetch the latest [`vulnerabilities.json`](https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json) file which contains known security vulnerabilities concerning `geth`, and cross-check the data against its own version number. + +The following key may be used to communicate sensitive information to developers. + +Fingerprint: `AE96 ED96 9E47 9B00 84F3 E17F E88D 3334 FA5F 6A0A` + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.1.6 +Comment: Hostname: pgp.mit.edu + +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 +=arte +-----END PGP PUBLIC KEY BLOCK------ +``` diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go new file mode 100644 index 0000000..c7bc2b4 --- /dev/null +++ b/accounts/abi/abi.go @@ -0,0 +1,311 @@ +// 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 abi + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// The ABI holds information about a contract's context and available +// invocable methods. It will allow you to type check function calls and +// packs data accordingly. +type ABI struct { + Constructor Method + Methods map[string]Method + Events map[string]Event + Errors map[string]Error + + // Additional "special" functions introduced in solidity v0.6.0. + // It's separated from the original default fallback. Each contract + // can only define one fallback and receive function. + Fallback Method // Note it's also used to represent legacy fallback before v0.6.0 + Receive Method +} + +// JSON returns a parsed ABI interface and error if it failed. +func JSON(reader io.Reader) (ABI, error) { + dec := json.NewDecoder(reader) + + var abi ABI + if err := dec.Decode(&abi); err != nil { + return ABI{}, err + } + return abi, nil +} + +// Pack the given method name to conform the ABI. Method call's data +// will consist of method_id, args0, arg1, ... argN. Method id consists +// of 4 bytes and arguments are all 32 bytes. +// Method ids are created from the first 4 bytes of the hash of the +// methods string signature. (signature = baz(uint32,string32)) +func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { + // Fetch the ABI of the requested method + if name == "" { + // constructor + arguments, err := abi.Constructor.Inputs.Pack(args...) + if err != nil { + return nil, err + } + return arguments, nil + } + method, exist := abi.Methods[name] + if !exist { + return nil, fmt.Errorf("method '%s' not found", name) + } + arguments, err := method.Inputs.Pack(args...) + if err != nil { + return nil, err + } + // Pack up the method ID too if not a constructor and return + return append(method.ID, arguments...), nil +} + +func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { + // since there can't be naming collisions with contracts and events, + // we need to decide whether we're calling a method or an event + var args Arguments + if method, ok := abi.Methods[name]; ok { + if len(data)%32 != 0 { + return nil, fmt.Errorf("abi: improperly formatted output: %q - Bytes: %+v", data, data) + } + args = method.Outputs + } + if event, ok := abi.Events[name]; ok { + args = event.Inputs + } + if args == nil { + return nil, fmt.Errorf("abi: could not locate named method or event: %s", name) + } + return args, nil +} + +// Unpack unpacks the output according to the abi specification. +func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) { + args, err := abi.getArguments(name, data) + if err != nil { + return nil, err + } + return args.Unpack(data) +} + +// UnpackIntoInterface unpacks the output in v according to the abi specification. +// It performs an additional copy. Please only use, if you want to unpack into a +// structure that does not strictly conform to the abi structure (e.g. has additional arguments) +func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + unpacked, err := args.Unpack(data) + if err != nil { + return err + } + return args.Copy(v, unpacked) +} + +// UnpackIntoMap unpacks a log into the provided map[string]interface{}. +func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + return args.UnpackIntoMap(v, data) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (abi *ABI) UnmarshalJSON(data []byte) error { + var fields []struct { + Type string + Name string + Inputs []Argument + Outputs []Argument + + // Status indicator which can be: "pure", "view", + // "nonpayable" or "payable". + StateMutability string + + // Deprecated Status indicators, but removed in v0.6.0. + Constant bool // True if function is either pure or view + Payable bool // True if function is payable + + // Event relevant indicator represents the event is + // declared as anonymous. + Anonymous bool + } + if err := json.Unmarshal(data, &fields); err != nil { + return err + } + abi.Methods = make(map[string]Method) + abi.Events = make(map[string]Event) + abi.Errors = make(map[string]Error) + for _, field := range fields { + switch field.Type { + case "constructor": + abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil) + case "function": + name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Methods[s]; return ok }) + abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs) + case "fallback": + // New introduced function type in v0.6.0, check more detail + // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function + if abi.HasFallback() { + return errors.New("only single fallback is allowed") + } + abi.Fallback = NewMethod("", "", Fallback, field.StateMutability, field.Constant, field.Payable, nil, nil) + case "receive": + // New introduced function type in v0.6.0, check more detail + // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function + if abi.HasReceive() { + return errors.New("only single receive is allowed") + } + if field.StateMutability != "payable" { + return errors.New("the statemutability of receive can only be payable") + } + abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil) + case "event": + name := ResolveNameConflict(field.Name, func(s string) bool { _, ok := abi.Events[s]; return ok }) + abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs) + case "error": + // Errors cannot be overloaded or overridden but are inherited, + // no need to resolve the name conflict here. + abi.Errors[field.Name] = NewError(field.Name, field.Inputs) + default: + return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name) + } + } + return nil +} + +// MethodById looks up a method by the 4-byte id, +// returns nil if none found. +func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { + if len(sigdata) < 4 { + return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) + } + for _, method := range abi.Methods { + if bytes.Equal(method.ID, sigdata[:4]) { + return &method, nil + } + } + return nil, fmt.Errorf("no method with id: %#x", sigdata[:4]) +} + +// EventByID looks an event up by its topic hash in the +// ABI and returns nil if none found. +func (abi *ABI) EventByID(topic common.Hash) (*Event, error) { + for _, event := range abi.Events { + if bytes.Equal(event.ID.Bytes(), topic.Bytes()) { + return &event, nil + } + } + return nil, fmt.Errorf("no event with id: %#x", topic.Hex()) +} + +// ErrorByID looks up an error by the 4-byte id, +// returns nil if none found. +func (abi *ABI) ErrorByID(sigdata [4]byte) (*Error, error) { + for _, errABI := range abi.Errors { + if bytes.Equal(errABI.ID[:4], sigdata[:]) { + return &errABI, nil + } + } + return nil, fmt.Errorf("no error with id: %#x", sigdata[:]) +} + +// HasFallback returns an indicator whether a fallback function is included. +func (abi *ABI) HasFallback() bool { + return abi.Fallback.Type == Fallback +} + +// HasReceive returns an indicator whether a receive function is included. +func (abi *ABI) HasReceive() bool { + return abi.Receive.Type == Receive +} + +// 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 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") + } + 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") + } +} diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go new file mode 100644 index 0000000..bc76df0 --- /dev/null +++ b/accounts/abi/abi_test.go @@ -0,0 +1,1220 @@ +// 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 abi + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" +) + +const jsondata = ` +[ + { "type" : "function", "name" : ""}, + { "type" : "function", "name" : "balance", "stateMutability" : "view" }, + { "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, + { "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, + { "type" : "function", "name" : "string", "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, + { "type" : "function", "name" : "bool", "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, + { "type" : "function", "name" : "address", "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, + { "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, + { "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, + { "type" : "function", "name" : "int8", "inputs" : [ { "name" : "inputs", "type" : "int8" } ] }, + { "type" : "function", "name" : "bytes32", "inputs" : [ { "name" : "inputs", "type" : "bytes32" } ] }, + { "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, + { "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, + { "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, + { "type" : "function", "name" : "slice256", "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "sliceAddress", "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] }, + { "type" : "function", "name" : "sliceMultiAddress", "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }, + { "type" : "function", "name" : "nestedArray", "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] }, + { "type" : "function", "name" : "nestedArray2", "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] }, + { "type" : "function", "name" : "nestedSlice", "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] }, + { "type" : "function", "name" : "receive", "inputs" : [ { "name" : "memo", "type" : "bytes" }], "outputs" : [], "payable" : true, "stateMutability" : "payable" }, + { "type" : "function", "name" : "fixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "fixedArrBytes", "stateMutability" : "view", "inputs" : [ { "name" : "bytes", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "mixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" } ] }, + { "type" : "function", "name" : "doubleFixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }, + { "type" : "function", "name" : "multipleMixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }, + { "type" : "function", "name" : "overloadedNames", "stateMutability" : "view", "inputs": [ { "components": [ { "internalType": "uint256", "name": "_f", "type": "uint256" }, { "internalType": "uint256", "name": "__f", "type": "uint256"}, { "internalType": "uint256", "name": "f", "type": "uint256"}],"internalType": "struct Overloader.F", "name": "f","type": "tuple"}]} +]` + +var ( + Uint256, _ = NewType("uint256", "", nil) + Uint32, _ = NewType("uint32", "", nil) + Uint16, _ = NewType("uint16", "", nil) + String, _ = NewType("string", "", nil) + Bool, _ = NewType("bool", "", nil) + Bytes, _ = NewType("bytes", "", nil) + Bytes32, _ = NewType("bytes32", "", nil) + Address, _ = NewType("address", "", nil) + Uint64Arr, _ = NewType("uint64[]", "", nil) + AddressArr, _ = NewType("address[]", "", nil) + Int8, _ = NewType("int8", "", nil) + // Special types for testing + Uint32Arr2, _ = NewType("uint32[2]", "", nil) + Uint64Arr2, _ = NewType("uint64[2]", "", nil) + Uint256Arr, _ = NewType("uint256[]", "", nil) + Uint256Arr2, _ = NewType("uint256[2]", "", nil) + Uint256Arr3, _ = NewType("uint256[3]", "", nil) + Uint256ArrNested, _ = NewType("uint256[2][2]", "", nil) + Uint8ArrNested, _ = NewType("uint8[][2]", "", nil) + Uint8SliceNested, _ = NewType("uint8[][]", "", nil) + TupleF, _ = NewType("tuple", "struct Overloader.F", []ArgumentMarshaling{ + {Name: "_f", Type: "uint256"}, + {Name: "__f", Type: "uint256"}, + {Name: "f", Type: "uint256"}}) +) + +var methods = map[string]Method{ + "": NewMethod("", "", Function, "", false, false, nil, nil), + "balance": NewMethod("balance", "balance", Function, "view", false, false, nil, nil), + "send": NewMethod("send", "send", Function, "", false, false, []Argument{{"amount", Uint256, false}}, nil), + "test": NewMethod("test", "test", Function, "", false, false, []Argument{{"number", Uint32, false}}, nil), + "string": NewMethod("string", "string", Function, "", false, false, []Argument{{"inputs", String, false}}, nil), + "bool": NewMethod("bool", "bool", Function, "", false, false, []Argument{{"inputs", Bool, false}}, nil), + "address": NewMethod("address", "address", Function, "", false, false, []Argument{{"inputs", Address, false}}, nil), + "uint64[]": NewMethod("uint64[]", "uint64[]", Function, "", false, false, []Argument{{"inputs", Uint64Arr, false}}, nil), + "uint64[2]": NewMethod("uint64[2]", "uint64[2]", Function, "", false, false, []Argument{{"inputs", Uint64Arr2, false}}, nil), + "int8": NewMethod("int8", "int8", Function, "", false, false, []Argument{{"inputs", Int8, false}}, nil), + "bytes32": NewMethod("bytes32", "bytes32", Function, "", false, false, []Argument{{"inputs", Bytes32, false}}, nil), + "foo": NewMethod("foo", "foo", Function, "", false, false, []Argument{{"inputs", Uint32, false}}, nil), + "bar": NewMethod("bar", "bar", Function, "", false, false, []Argument{{"inputs", Uint32, false}, {"string", Uint16, false}}, nil), + "slice": NewMethod("slice", "slice", Function, "", false, false, []Argument{{"inputs", Uint32Arr2, false}}, nil), + "slice256": NewMethod("slice256", "slice256", Function, "", false, false, []Argument{{"inputs", Uint256Arr2, false}}, nil), + "sliceAddress": NewMethod("sliceAddress", "sliceAddress", Function, "", false, false, []Argument{{"inputs", AddressArr, false}}, nil), + "sliceMultiAddress": NewMethod("sliceMultiAddress", "sliceMultiAddress", Function, "", false, false, []Argument{{"a", AddressArr, false}, {"b", AddressArr, false}}, nil), + "nestedArray": NewMethod("nestedArray", "nestedArray", Function, "", false, false, []Argument{{"a", Uint256ArrNested, false}, {"b", AddressArr, false}}, nil), + "nestedArray2": NewMethod("nestedArray2", "nestedArray2", Function, "", false, false, []Argument{{"a", Uint8ArrNested, false}}, nil), + "nestedSlice": NewMethod("nestedSlice", "nestedSlice", Function, "", false, false, []Argument{{"a", Uint8SliceNested, false}}, nil), + "receive": NewMethod("receive", "receive", Function, "payable", false, true, []Argument{{"memo", Bytes, false}}, []Argument{}), + "fixedArrStr": NewMethod("fixedArrStr", "fixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}}, nil), + "fixedArrBytes": NewMethod("fixedArrBytes", "fixedArrBytes", Function, "view", false, false, []Argument{{"bytes", Bytes, false}, {"fixedArr", Uint256Arr2, false}}, nil), + "mixedArrStr": NewMethod("mixedArrStr", "mixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}}, nil), + "doubleFixedArrStr": NewMethod("doubleFixedArrStr", "doubleFixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"fixedArr2", Uint256Arr3, false}}, nil), + "multipleMixedArrStr": NewMethod("multipleMixedArrStr", "multipleMixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}, {"fixedArr2", Uint256Arr3, false}}, nil), + "overloadedNames": NewMethod("overloadedNames", "overloadedNames", Function, "view", false, false, []Argument{{"f", TupleF, false}}, nil), +} + +func TestReader(t *testing.T) { + t.Parallel() + abi := ABI{ + Methods: methods, + } + + exp, err := JSON(strings.NewReader(jsondata)) + if err != nil { + t.Fatal(err) + } + + for name, expM := range exp.Methods { + gotM, exist := abi.Methods[name] + if !exist { + t.Errorf("Missing expected method %v", name) + } + if !reflect.DeepEqual(gotM, expM) { + t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM) + } + } + + for name, gotM := range abi.Methods { + expM, exist := exp.Methods[name] + if !exist { + t.Errorf("Found extra method %v", name) + } + if !reflect.DeepEqual(gotM, expM) { + t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM) + } + } +} + +func TestInvalidABI(t *testing.T) { + t.Parallel() + json := `[{ "type" : "function", "name" : "", "constant" : fals }]` + _, err := JSON(strings.NewReader(json)) + if err == nil { + t.Fatal("invalid json should produce error") + } + json2 := `[{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "typ" : "uint256" } ] }]` + _, err = JSON(strings.NewReader(json2)) + if err == nil { + t.Fatal("invalid json should produce error") + } +} + +// TestConstructor tests a constructor function. +// The test is based on the following contract: +// +// contract TestConstructor { +// 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 + abi, err := JSON(strings.NewReader(json)) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(abi.Constructor, method) { + t.Error("Missing expected constructor") + } + // Test pack/unpack + packed, err := abi.Pack("", big.NewInt(1), big.NewInt(2)) + if err != nil { + t.Error(err) + } + unpacked, err := abi.Constructor.Inputs.Unpack(packed) + if err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(unpacked[0], big.NewInt(1)) { + t.Error("Unable to pack/unpack from constructor") + } + if !reflect.DeepEqual(unpacked[1], big.NewInt(2)) { + t.Error("Unable to pack/unpack from constructor") + } +} + +func TestTestNumbers(t *testing.T) { + t.Parallel() + abi, err := JSON(strings.NewReader(jsondata)) + if err != nil { + t.Fatal(err) + } + + if _, err := abi.Pack("balance"); err != nil { + t.Error(err) + } + + if _, err := abi.Pack("balance", 1); err == nil { + t.Error("expected error for balance(1)") + } + + if _, err := abi.Pack("doesntexist", nil); err == nil { + t.Errorf("doesntexist shouldn't exist") + } + + if _, err := abi.Pack("doesntexist", 1); err == nil { + t.Errorf("doesntexist(1) shouldn't exist") + } + + if _, err := abi.Pack("send", big.NewInt(1000)); err != nil { + t.Error(err) + } + + i := new(int) + *i = 1000 + if _, err := abi.Pack("send", i); err == nil { + t.Errorf("expected send( ptr ) to throw, requires *big.Int instead of *int") + } + + if _, err := abi.Pack("test", uint32(1000)); err != nil { + t.Error(err) + } +} + +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 { + t.Error("signature mismatch", exp, "!=", m.Sig) + } + + idexp := crypto.Keccak256([]byte(exp))[:4] + if !bytes.Equal(m.ID, idexp) { + t.Errorf("expected ids to match %x != %x", m.ID, idexp) + } + + m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", Uint256, false}}, nil) + exp = "foo(uint256)" + if m.Sig != exp { + t.Error("signature mismatch", exp, "!=", m.Sig) + } + + // Method with tuple arguments + s, _ := NewType("tuple", "", []ArgumentMarshaling{ + {Name: "a", Type: "int256"}, + {Name: "b", Type: "int256[]"}, + {Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{ + {Name: "x", Type: "int256"}, + {Name: "y", Type: "int256"}, + }}, + {Name: "d", Type: "tuple[2]", Components: []ArgumentMarshaling{ + {Name: "x", Type: "int256"}, + {Name: "y", Type: "int256"}, + }}, + }) + m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil) + exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)" + if m.Sig != exp { + t.Error("signature mismatch", exp, "!=", m.Sig) + } +} + +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 { + t.Fatal(err) + } + check := func(name string, expect string, method bool) { + if method { + if abi.Methods[name].Sig != expect { + t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig) + } + } else { + if abi.Events[name].Sig != expect { + t.Fatalf("The signature of overloaded event mismatch, want %s, have %s", expect, abi.Events[name].Sig) + } + } + } + check("foo", "foo(uint256,uint256)", true) + check("foo0", "foo(uint256)", true) + check("bar", "bar(uint256)", false) + check("bar0", "bar(uint256,uint256)", false) +} + +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 { + t.Fatal(err) + } + check := func(name string, expect string) { + if abi.Errors[name].Sig != expect { + t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig) + } + } + check("MyError", "MyError(uint256)") +} + +func TestMultiPack(t *testing.T) { + t.Parallel() + abi, err := JSON(strings.NewReader(jsondata)) + if err != nil { + t.Fatal(err) + } + + sig := crypto.Keccak256([]byte("bar(uint32,uint16)"))[:4] + sig = append(sig, make([]byte, 64)...) + sig[35] = 10 + sig[67] = 11 + + packed, err := abi.Pack("bar", uint32(10), uint16(11)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } +} + +func ExampleJSON() { + const definition = `[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBar","outputs":[{"name":"","type":"bool"}],"type":"function"}]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + panic(err) + } + out, err := abi.Pack("isBar", common.HexToAddress("01")) + if err != nil { + panic(err) + } + + fmt.Printf("%x\n", out) + // Output: + // 1f2c40920000000000000000000000000000000000000000000000000000000000000001 +} + +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" } ] }, + { "type" : "function", "name" : "strTwo", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "str1", "type" : "string" } ] } + ]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + // test one string + strin := "hello world" + strpack, err := abi.Pack("strOne", strin) + if err != nil { + t.Error(err) + } + + offset := make([]byte, 32) + offset[31] = 32 + length := make([]byte, 32) + length[31] = byte(len(strin)) + value := common.RightPadBytes([]byte(strin), 32) + exp := append(offset, append(length, value...)...) + + // ignore first 4 bytes of the output. This is the function identifier + strpack = strpack[4:] + if !bytes.Equal(strpack, exp) { + t.Errorf("expected %x, got %x\n", exp, strpack) + } + + // test one bytes + btspack, err := abi.Pack("bytesOne", []byte(strin)) + if err != nil { + t.Error(err) + } + // ignore first 4 bytes of the output. This is the function identifier + btspack = btspack[4:] + if !bytes.Equal(btspack, exp) { + t.Errorf("expected %x, got %x\n", exp, btspack) + } + + // test two strings + str1 := "hello" + str2 := "world" + str2pack, err := abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 := make([]byte, 32) + offset1[31] = 64 + length1 := make([]byte, 32) + length1[31] = byte(len(str1)) + value1 := common.RightPadBytes([]byte(str1), 32) + + offset2 := make([]byte, 32) + offset2[31] = 128 + length2 := make([]byte, 32) + length2[31] = byte(len(str2)) + value2 := common.RightPadBytes([]byte(str2), 32) + + exp2 := append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } + + // test two strings, first > 32, second < 32 + str1 = strings.Repeat("a", 33) + str2pack, err = abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 = make([]byte, 32) + offset1[31] = 64 + length1 = make([]byte, 32) + length1[31] = byte(len(str1)) + value1 = common.RightPadBytes([]byte(str1), 64) + offset2[31] = 160 + + exp2 = append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } + + // test two strings, first > 32, second >32 + str1 = strings.Repeat("a", 33) + str2 = strings.Repeat("a", 33) + str2pack, err = abi.Pack("strTwo", str1, str2) + if err != nil { + t.Error(err) + } + + offset1 = make([]byte, 32) + offset1[31] = 64 + length1 = make([]byte, 32) + length1[31] = byte(len(str1)) + value1 = common.RightPadBytes([]byte(str1), 64) + + offset2 = make([]byte, 32) + offset2[31] = 160 + length2 = make([]byte, 32) + length2[31] = byte(len(str2)) + value2 = common.RightPadBytes([]byte(str2), 64) + + exp2 = append(offset1, offset2...) + exp2 = append(exp2, append(length1, value1...)...) + exp2 = append(exp2, append(length2, value2...)...) + + // ignore first 4 bytes of the output. This is the function identifier + str2pack = str2pack[4:] + if !bytes.Equal(str2pack, exp2) { + t.Errorf("expected %x, got %x\n", exp, str2pack) + } +} + +func TestInputFixedArrayAndVariableInputLength(t *testing.T) { + t.Parallel() + abi, err := JSON(strings.NewReader(jsondata)) + if err != nil { + t.Error(err) + } + + // test string, fixed array uint256[2] + strin := "hello world" + arrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)} + fixedArrStrPack, err := abi.Pack("fixedArrStr", strin, arrin) + if err != nil { + t.Error(err) + } + + // generate expected output + offset := make([]byte, 32) + offset[31] = 96 + length := make([]byte, 32) + length[31] = byte(len(strin)) + strvalue := common.RightPadBytes([]byte(strin), 32) + arrinvalue1 := common.LeftPadBytes(arrin[0].Bytes(), 32) + arrinvalue2 := common.LeftPadBytes(arrin[1].Bytes(), 32) + exp := append(offset, arrinvalue1...) + exp = append(exp, arrinvalue2...) + exp = append(exp, append(length, strvalue...)...) + + // ignore first 4 bytes of the output. This is the function identifier + fixedArrStrPack = fixedArrStrPack[4:] + if !bytes.Equal(fixedArrStrPack, exp) { + t.Errorf("expected %x, got %x\n", exp, fixedArrStrPack) + } + + // test byte array, fixed array uint256[2] + bytesin := []byte(strin) + arrin = [2]*big.Int{big.NewInt(1), big.NewInt(2)} + fixedArrBytesPack, err := abi.Pack("fixedArrBytes", bytesin, arrin) + if err != nil { + t.Error(err) + } + + // generate expected output + offset = make([]byte, 32) + offset[31] = 96 + length = make([]byte, 32) + length[31] = byte(len(strin)) + strvalue = common.RightPadBytes([]byte(strin), 32) + arrinvalue1 = common.LeftPadBytes(arrin[0].Bytes(), 32) + arrinvalue2 = common.LeftPadBytes(arrin[1].Bytes(), 32) + exp = append(offset, arrinvalue1...) + exp = append(exp, arrinvalue2...) + exp = append(exp, append(length, strvalue...)...) + + // ignore first 4 bytes of the output. This is the function identifier + fixedArrBytesPack = fixedArrBytesPack[4:] + if !bytes.Equal(fixedArrBytesPack, exp) { + t.Errorf("expected %x, got %x\n", exp, fixedArrBytesPack) + } + + // test string, fixed array uint256[2], dynamic array uint256[] + strin = "hello world" + fixedarrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)} + dynarrin := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + mixedArrStrPack, err := abi.Pack("mixedArrStr", strin, fixedarrin, dynarrin) + if err != nil { + t.Error(err) + } + + // generate expected output + stroffset := make([]byte, 32) + stroffset[31] = 128 + strlength := make([]byte, 32) + strlength[31] = byte(len(strin)) + strvalue = common.RightPadBytes([]byte(strin), 32) + fixedarrinvalue1 := common.LeftPadBytes(fixedarrin[0].Bytes(), 32) + fixedarrinvalue2 := common.LeftPadBytes(fixedarrin[1].Bytes(), 32) + dynarroffset := make([]byte, 32) + dynarroffset[31] = byte(160 + ((len(strin)/32)+1)*32) + dynarrlength := make([]byte, 32) + dynarrlength[31] = byte(len(dynarrin)) + dynarrinvalue1 := common.LeftPadBytes(dynarrin[0].Bytes(), 32) + dynarrinvalue2 := common.LeftPadBytes(dynarrin[1].Bytes(), 32) + dynarrinvalue3 := common.LeftPadBytes(dynarrin[2].Bytes(), 32) + exp = append(stroffset, fixedarrinvalue1...) + exp = append(exp, fixedarrinvalue2...) + exp = append(exp, dynarroffset...) + exp = append(exp, append(strlength, strvalue...)...) + dynarrarg := append(dynarrlength, dynarrinvalue1...) + dynarrarg = append(dynarrarg, dynarrinvalue2...) + dynarrarg = append(dynarrarg, dynarrinvalue3...) + exp = append(exp, dynarrarg...) + + // ignore first 4 bytes of the output. This is the function identifier + mixedArrStrPack = mixedArrStrPack[4:] + if !bytes.Equal(mixedArrStrPack, exp) { + t.Errorf("expected %x, got %x\n", exp, mixedArrStrPack) + } + + // test string, fixed array uint256[2], fixed array uint256[3] + strin = "hello world" + fixedarrin1 := [2]*big.Int{big.NewInt(1), big.NewInt(2)} + fixedarrin2 := [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + doubleFixedArrStrPack, err := abi.Pack("doubleFixedArrStr", strin, fixedarrin1, fixedarrin2) + if err != nil { + t.Error(err) + } + + // generate expected output + stroffset = make([]byte, 32) + stroffset[31] = 192 + strlength = make([]byte, 32) + strlength[31] = byte(len(strin)) + strvalue = common.RightPadBytes([]byte(strin), 32) + fixedarrin1value1 := common.LeftPadBytes(fixedarrin1[0].Bytes(), 32) + fixedarrin1value2 := common.LeftPadBytes(fixedarrin1[1].Bytes(), 32) + fixedarrin2value1 := common.LeftPadBytes(fixedarrin2[0].Bytes(), 32) + fixedarrin2value2 := common.LeftPadBytes(fixedarrin2[1].Bytes(), 32) + fixedarrin2value3 := common.LeftPadBytes(fixedarrin2[2].Bytes(), 32) + exp = append(stroffset, fixedarrin1value1...) + exp = append(exp, fixedarrin1value2...) + exp = append(exp, fixedarrin2value1...) + exp = append(exp, fixedarrin2value2...) + exp = append(exp, fixedarrin2value3...) + exp = append(exp, append(strlength, strvalue...)...) + + // ignore first 4 bytes of the output. This is the function identifier + doubleFixedArrStrPack = doubleFixedArrStrPack[4:] + if !bytes.Equal(doubleFixedArrStrPack, exp) { + t.Errorf("expected %x, got %x\n", exp, doubleFixedArrStrPack) + } + + // test string, fixed array uint256[2], dynamic array uint256[], fixed array uint256[3] + strin = "hello world" + fixedarrin1 = [2]*big.Int{big.NewInt(1), big.NewInt(2)} + dynarrin = []*big.Int{big.NewInt(1), big.NewInt(2)} + fixedarrin2 = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)} + multipleMixedArrStrPack, err := abi.Pack("multipleMixedArrStr", strin, fixedarrin1, dynarrin, fixedarrin2) + if err != nil { + t.Error(err) + } + + // generate expected output + stroffset = make([]byte, 32) + stroffset[31] = 224 + strlength = make([]byte, 32) + strlength[31] = byte(len(strin)) + strvalue = common.RightPadBytes([]byte(strin), 32) + fixedarrin1value1 = common.LeftPadBytes(fixedarrin1[0].Bytes(), 32) + fixedarrin1value2 = common.LeftPadBytes(fixedarrin1[1].Bytes(), 32) + dynarroffset = math.U256Bytes(big.NewInt(int64(256 + ((len(strin)/32)+1)*32))) + dynarrlength = make([]byte, 32) + dynarrlength[31] = byte(len(dynarrin)) + dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32) + dynarrinvalue2 = common.LeftPadBytes(dynarrin[1].Bytes(), 32) + fixedarrin2value1 = common.LeftPadBytes(fixedarrin2[0].Bytes(), 32) + fixedarrin2value2 = common.LeftPadBytes(fixedarrin2[1].Bytes(), 32) + fixedarrin2value3 = common.LeftPadBytes(fixedarrin2[2].Bytes(), 32) + exp = append(stroffset, fixedarrin1value1...) + exp = append(exp, fixedarrin1value2...) + exp = append(exp, dynarroffset...) + exp = append(exp, fixedarrin2value1...) + exp = append(exp, fixedarrin2value2...) + exp = append(exp, fixedarrin2value3...) + exp = append(exp, append(strlength, strvalue...)...) + dynarrarg = append(dynarrlength, dynarrinvalue1...) + dynarrarg = append(dynarrarg, dynarrinvalue2...) + exp = append(exp, dynarrarg...) + + // ignore first 4 bytes of the output. This is the function identifier + multipleMixedArrStrPack = multipleMixedArrStrPack[4:] + if !bytes.Equal(multipleMixedArrStrPack, exp) { + t.Errorf("expected %x, got %x\n", exp, multipleMixedArrStrPack) + } +} + +func TestDefaultFunctionParsing(t *testing.T) { + t.Parallel() + const definition = `[{ "name" : "balance", "type" : "function" }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if _, ok := abi.Methods["balance"]; !ok { + t.Error("expected 'balance' to be present") + } +} + +func TestBareEvents(t *testing.T) { + t.Parallel() + const definition = `[ + { "type" : "event", "name" : "balance" }, + { "type" : "event", "name" : "anon", "anonymous" : true}, + { "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] }, + { "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] } + ]` + + tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}}) + + expectedEvents := map[string]struct { + Anonymous bool + Args []Argument + }{ + "balance": {false, nil}, + "anon": {true, nil}, + "args": {false, []Argument{ + {Name: "arg0", Type: Uint256, Indexed: false}, + {Name: "arg1", Type: Address, Indexed: true}, + }}, + "tuple": {false, []Argument{ + {Name: "t", Type: tuple, Indexed: false}, + {Name: "arg1", Type: Address, Indexed: true}, + }}, + } + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if len(abi.Events) != len(expectedEvents) { + t.Fatalf("invalid number of events after parsing, want %d, got %d", len(expectedEvents), len(abi.Events)) + } + + for name, exp := range expectedEvents { + got, ok := abi.Events[name] + if !ok { + t.Errorf("could not found event %s", name) + continue + } + if got.Anonymous != exp.Anonymous { + t.Errorf("invalid anonymous indication for event %s, want %v, got %v", name, exp.Anonymous, got.Anonymous) + } + if len(got.Inputs) != len(exp.Args) { + t.Errorf("invalid number of args, want %d, got %d", len(exp.Args), len(got.Inputs)) + continue + } + for i, arg := range exp.Args { + if arg.Name != got.Inputs[i].Name { + t.Errorf("events[%s].Input[%d] has an invalid name, want %s, got %s", name, i, arg.Name, got.Inputs[i].Name) + } + if arg.Indexed != got.Inputs[i].Indexed { + t.Errorf("events[%s].Input[%d] has an invalid indexed indication, want %v, got %v", name, i, arg.Indexed, got.Inputs[i].Indexed) + } + if arg.Type.T != got.Inputs[i].Type.T { + t.Errorf("events[%s].Input[%d] has an invalid type, want %x, got %x", name, i, arg.Type.T, got.Inputs[i].Type.T) + } + } + } +} + +// TestUnpackEvent is based on this contract: +// +// contract T { +// event received(address sender, uint amount, bytes memo); +// event receivedAddr(address sender); +// function receive(bytes memo) external payable { +// received(msg.sender, msg.value, memo); +// receivedAddr(msg.sender); +// } +// } +// +// 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 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 { + t.Fatal(err) + } + + const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + + type ReceivedEvent struct { + Sender common.Address + Amount *big.Int + Memo []byte + } + var ev ReceivedEvent + + err = abi.UnpackIntoInterface(&ev, "received", data) + if err != nil { + t.Error(err) + } + + type ReceivedAddrEvent struct { + Sender common.Address + } + var receivedAddrEv ReceivedAddrEvent + err = abi.UnpackIntoInterface(&receivedAddrEv, "receivedAddr", data) + if err != nil { + t.Error(err) + } +} + +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 { + t.Fatal(err) + } + + const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + + receivedMap := map[string]interface{}{} + expectedReceivedMap := map[string]interface{}{ + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := abi.UnpackIntoMap(receivedMap, "received", data); err != nil { + t.Error(err) + } + if len(receivedMap) != 3 { + t.Error("unpacked `received` map expected to have length 3") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked `received` map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked `received` map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked `received` map does not match expected map") + } + + receivedAddrMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data); err != nil { + t.Error(err) + } + if len(receivedAddrMap) != 1 { + t.Error("unpacked `receivedAddr` map expected to have length 1") + } + if receivedAddrMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked `receivedAddr` map does not match expected map") + } +} + +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 { + t.Fatal(err) + } + const hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 != 0 { + t.Errorf("len(data) is %d, want a multiple of 32", len(data)) + } + + // Tests a method with no outputs + receiveMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(receiveMap, "receive", data); err != nil { + t.Error(err) + } + if len(receiveMap) > 0 { + t.Error("unpacked `receive` map expected to have length 0") + } + + // Tests a method with only outputs + sendMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(sendMap, "send", data); err != nil { + t.Error(err) + } + if len(sendMap) != 1 { + t.Error("unpacked `send` map expected to have length 1") + } + if sendMap["amount"].(*big.Int).Cmp(big.NewInt(1)) != 0 { + t.Error("unpacked `send` map expected `amount` value of 1") + } + + // Tests a method with outputs and inputs + getMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(getMap, "get", data); err != nil { + t.Error(err) + } + if len(getMap) != 1 { + t.Error("unpacked `get` map expected to have length 1") + } + expectedBytes := []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, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 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, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 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, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 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, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0} + if !bytes.Equal(getMap["hash"].([]byte), expectedBytes) { + t.Errorf("unpacked `get` map expected `hash` value of %v", expectedBytes) + } +} + +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)) + if err != nil { + t.Fatal(err) + } + var hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + getMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(getMap, "get", data); err == nil { + t.Error("naming conflict between two methods; error expected") + } + + // Two events have the same name + 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":"received","type":"event"}]` + abi, err = JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + data, err = hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + receivedMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(receivedMap, "received", data); err != nil { + t.Error("naming conflict between two events; no error expected") + } + + // Method and event have the same name + abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","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 { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + if err = abi.UnpackIntoMap(receivedMap, "received", data); err == nil { + t.Error("naming conflict between an event and a method; error expected") + } + + // Conflict is case sensitive + abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","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 { + t.Fatal(err) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + expectedReceivedMap := map[string]interface{}{ + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err = abi.UnpackIntoMap(receivedMap, "Received", data); err != nil { + t.Error(err) + } + if len(receivedMap) != 3 { + t.Error("unpacked `received` map expected to have length 3") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked `received` map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked `received` map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked `received` map does not match expected map") + } +} + +func TestABI_MethodById(t *testing.T) { + t.Parallel() + abi, err := JSON(strings.NewReader(jsondata)) + if err != nil { + t.Fatal(err) + } + for name, m := range abi.Methods { + a := fmt.Sprintf("%v", m) + m2, err := abi.MethodById(m.ID) + if err != nil { + t.Fatalf("Failed to look up ABI method: %v", err) + } + b := fmt.Sprintf("%v", m2) + if a != b { + t.Errorf("Method %v (id %x) not 'findable' by id in ABI", name, m.ID) + } + } + // test unsuccessful lookups + if _, err = abi.MethodById(crypto.Keccak256()); err == nil { + t.Error("Expected error: no method with this id") + } + // Also test empty + if _, err := abi.MethodById([]byte{0x00}); err == nil { + t.Errorf("Expected error, too short to decode data") + } + if _, err := abi.MethodById([]byte{}); err == nil { + t.Errorf("Expected error, too short to decode data") + } + if _, err := abi.MethodById(nil); err == nil { + t.Errorf("Expected error, nil is short to decode data") + } +} + +func TestABI_EventById(t *testing.T) { + t.Parallel() + tests := []struct { + name string + json string + event string + }{ + { + name: "", + json: `[ + {"type":"event","name":"received","anonymous":false,"inputs":[ + {"indexed":false,"name":"sender","type":"address"}, + {"indexed":false,"name":"amount","type":"uint256"}, + {"indexed":false,"name":"memo","type":"bytes"} + ] + }]`, + event: "received(address,uint256,bytes)", + }, { + name: "", + json: `[ + { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, + { "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, + { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, + { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, + { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" }, + { "constant": true, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "balance", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, + { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, + { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, + { "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" } ], "name": "allowance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, + { "payable": true, "stateMutability": "payable", "type": "fallback" }, + { "anonymous": false, "inputs": [ { "indexed": true, "name": "owner", "type": "address" }, { "indexed": true, "name": "spender", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, + { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" } + ]`, + event: "Transfer(address,address,uint256)", + }, + } + + for testnum, test := range tests { + abi, err := JSON(strings.NewReader(test.json)) + if err != nil { + t.Error(err) + } + + topic := test.event + topicID := crypto.Keccak256Hash([]byte(topic)) + + event, err := abi.EventByID(topicID) + if err != nil { + t.Fatalf("Failed to look up ABI method: %v, test #%d", err, testnum) + } + if event == nil { + t.Errorf("We should find a event for topic %s, test #%d", topicID.Hex(), testnum) + } else if event.ID != topicID { + t.Errorf("Event id %s does not match topic %s, test #%d", event.ID.Hex(), topicID.Hex(), testnum) + } + + unknowntopicID := crypto.Keccak256Hash([]byte("unknownEvent")) + unknownEvent, err := abi.EventByID(unknowntopicID) + if err == nil { + t.Errorf("EventByID should return an error if a topic is not found, test #%d", testnum) + } + if unknownEvent != nil { + t.Errorf("We should not find any event for topic %s, test #%d", unknowntopicID.Hex(), testnum) + } + } +} + +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"}, + {"inputs":[{"internalType":"uint256[]","name":"x","type":"uint256[]"}],"name":"MyError3","type":"error"} + ]`)) + if err != nil { + t.Fatal(err) + } + for name, m := range abi.Errors { + a := fmt.Sprintf("%v", &m) + var id [4]byte + copy(id[:], m.ID[:4]) + m2, err := abi.ErrorByID(id) + if err != nil { + t.Fatalf("Failed to look up ABI error: %v", err) + } + b := fmt.Sprintf("%v", m2) + if a != b { + t.Errorf("Error %v (id %x) not 'findable' by id in ABI", name, id) + } + } + // test unsuccessful lookups + if _, err = abi.ErrorByID([4]byte{}); err == nil { + t.Error("Expected error: no error with this id") + } +} + +// 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 { + t.Fatal(err) + } + if _, ok := contractAbi.Methods["transfer"]; !ok { + t.Fatalf("Could not find original method") + } + if _, ok := contractAbi.Methods["transfer0"]; !ok { + t.Fatalf("Could not find duplicate method") + } + if _, ok := contractAbi.Methods["transfer1"]; !ok { + t.Fatalf("Could not find duplicate method") + } + if _, ok := contractAbi.Methods["transfer2"]; ok { + t.Fatalf("Should not have found extra method") + } +} + +// TestDoubleDuplicateEventNames checks that if send0 already exists, there won't be a name +// conflict and that the second send event will be renamed send1. +// The test runs the abi of the following contract. +// +// contract DuplicateEvent { +// event send(uint256 a); +// event send0(); +// 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 { + t.Fatal(err) + } + if _, ok := contractAbi.Events["send"]; !ok { + t.Fatalf("Could not find original event") + } + if _, ok := contractAbi.Events["send0"]; !ok { + t.Fatalf("Could not find duplicate event") + } + if _, ok := contractAbi.Events["send1"]; !ok { + t.Fatalf("Could not find duplicate event") + } + if _, ok := contractAbi.Events["send2"]; ok { + t.Fatalf("Should not have found extra event") + } +} + +// TestUnnamedEventParam checks that an event with unnamed parameters is +// correctly handled. +// The test runs the abi of the following contract. +// +// contract TestEvent { +// 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 { + t.Fatal(err) + } + + event, ok := contractAbi.Events["send"] + if !ok { + t.Fatalf("Could not find event") + } + if event.Inputs[0].Name != "arg0" { + t.Fatalf("Could not find input") + } + if event.Inputs[1].Name != "arg1" { + t.Fatalf("Could not find input") + } +} + +func TestUnpackRevert(t *testing.T) { + t.Parallel() + + var cases = []struct { + input string + expect string + expectErr error + }{ + {"", "", 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 { + t.Fatalf("Expected non-nil error") + } + if err.Error() != c.expectErr.Error() { + t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err) + } + return + } + if c.expect != got { + t.Fatalf("Output mismatch, want %v, got %v", c.expect, got) + } + }) + } +} diff --git a/accounts/abi/abifuzzer_test.go b/accounts/abi/abifuzzer_test.go new file mode 100644 index 0000000..dbf6ab6 --- /dev/null +++ b/accounts/abi/abifuzzer_test.go @@ -0,0 +1,179 @@ +// 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 ( + "fmt" + "reflect" + "strings" + "testing" + + 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"} + 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", + "uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136", + "uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176", + "uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216", + "uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256", + "bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11", + "bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21", + "bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31", + "bytes32", "bytes"} +) + +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 { + // We have some false positives as we can unpack these type successfully, but not pack them + if err.Error() == "abi: cannot use []uint8 as type [0]int8 as argument" || + err.Error() == "abi: cannot use uint8 as type int8 as argument" { + return out, false + } + panic(err) + } + return out, true + } + return nil, false +} + +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) + if err != nil { + panic(err) + } + out := outptr.Elem().Interface() + if !reflect.DeepEqual(input, out) { + panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, out)) + } + return true + } + return false +} + +type arg struct { + name string + typ string +} + +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) + } + if payable != nil { + sig += fmt.Sprintf(`, "payable": %v `, *payable) + } + if len(inputs) > 0 { + sig += `, "inputs" : [ {` + for i, inp := range inputs { + sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ) + if i+1 < len(inputs) { + sig += "," + } + } + sig += "} ]" + sig += `, "outputs" : [ {` + for i, inp := range inputs { + sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ) + if i+1 < len(inputs) { + sig += "," + } + } + sig += "} ]" + } + sig += `}]` + //fmt.Printf("sig: %s\n", sig) + return JSON(strings.NewReader(sig)) +} + +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: + } + arguments = append(arguments, arg{name: argName, typ: argTyp}) + } + abi, err := createABI(name, stateM, payable, arguments) + if err != nil { + //fmt.Printf("err: %v\n", err) + panic(err) + } + structs, _ := unpackPack(abi, name, input) + _ = packUnpack(abi, name, &structs) +} + +func upTo(fuzzer *fuzz.Fuzzer, max int) int { + var i int + fuzzer.Fuzz(&i) + if i < 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 nil +} diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go new file mode 100644 index 0000000..227a088 --- /dev/null +++ b/accounts/abi/argument.go @@ -0,0 +1,273 @@ +// 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 abi + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" +) + +// Argument holds the name of the argument and the corresponding type. +// Types are used when packing and testing arguments. +type Argument struct { + Name string + Type Type + Indexed bool // indexed is only used by events +} + +type Arguments []Argument + +type ArgumentMarshaling struct { + Name string + Type string + InternalType string + Components []ArgumentMarshaling + Indexed bool +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (argument *Argument) UnmarshalJSON(data []byte) error { + var arg ArgumentMarshaling + err := json.Unmarshal(data, &arg) + if err != nil { + return fmt.Errorf("argument json err: %v", err) + } + + argument.Type, err = NewType(arg.Type, arg.InternalType, arg.Components) + if err != nil { + return err + } + argument.Name = arg.Name + argument.Indexed = arg.Indexed + + return nil +} + +// NonIndexed returns the arguments with indexed arguments filtered out. +func (arguments Arguments) NonIndexed() Arguments { + var ret []Argument + for _, arg := range arguments { + if !arg.Indexed { + ret = append(ret, arg) + } + } + return ret +} + +// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]. +func (arguments Arguments) isTuple() bool { + return len(arguments) > 1 +} + +// Unpack performs the operation hexdata -> Go format. +func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { + if len(data) == 0 { + if len(arguments.NonIndexed()) != 0 { + return nil, errors.New("abi: attempting to unmarshal an empty string while arguments are expected") + } + return make([]interface{}, 0), nil + } + return arguments.UnpackValues(data) +} + +// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value. +func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { + // Make sure map is not nil + if v == nil { + return errors.New("abi: cannot unpack into a nil map") + } + if len(data) == 0 { + if len(arguments.NonIndexed()) != 0 { + return errors.New("abi: attempting to unmarshal an empty string while arguments are expected") + } + return nil // Nothing to unmarshal, return + } + marshalledValues, err := arguments.UnpackValues(data) + if err != nil { + return err + } + for i, arg := range arguments.NonIndexed() { + v[arg.Name] = marshalledValues[i] + } + return nil +} + +// Copy performs the operation go format -> provided struct. +func (arguments Arguments) Copy(v interface{}, values []interface{}) error { + // make sure the passed value is arguments pointer + if reflect.Ptr != reflect.ValueOf(v).Kind() { + return fmt.Errorf("abi: Unpack(non-pointer %T)", v) + } + if len(values) == 0 { + if len(arguments.NonIndexed()) != 0 { + return errors.New("abi: attempting to copy no values while arguments are expected") + } + return nil // Nothing to copy, return + } + if arguments.isTuple() { + return arguments.copyTuple(v, values) + } + return arguments.copyAtomic(v, values[0]) +} + +// copyAtomic copies ( hexdata -> go ) a single value +func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error { + dst := reflect.ValueOf(v).Elem() + src := reflect.ValueOf(marshalledValues) + + if dst.Kind() == reflect.Struct { + return set(dst.Field(0), src) + } + return set(dst, src) +} + +// copyTuple copies a batch of values from marshalledValues to v. +func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface{}) error { + value := reflect.ValueOf(v).Elem() + nonIndexedArgs := arguments.NonIndexed() + + switch value.Kind() { + case reflect.Struct: + argNames := make([]string, len(nonIndexedArgs)) + for i, arg := range nonIndexedArgs { + argNames[i] = arg.Name + } + var err error + abi2struct, err := mapArgNamesToStructFields(argNames, value) + if err != nil { + return err + } + for i, arg := range nonIndexedArgs { + field := value.FieldByName(abi2struct[arg.Name]) + if !field.IsValid() { + return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name) + } + if err := set(field, reflect.ValueOf(marshalledValues[i])); err != nil { + return err + } + } + case reflect.Slice, reflect.Array: + if value.Len() < len(marshalledValues) { + return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) + } + for i := range nonIndexedArgs { + if err := set(value.Index(i), reflect.ValueOf(marshalledValues[i])); err != nil { + return err + } + } + default: + return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", value.Type()) + } + return nil +} + +// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, +// without supplying a struct to unpack into. Instead, this method returns a list containing the +// values. An atomic argument will be a list with one element. +func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { + nonIndexedArgs := arguments.NonIndexed() + retval := make([]interface{}, 0, len(nonIndexedArgs)) + virtualArgs := 0 + for index, arg := range nonIndexedArgs { + marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) + if err != nil { + return nil, err + } + if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { + // If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getTypeSize(arg.Type)/32 - 1 + } else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) { + // If we have a static tuple, like (uint256, bool, uint256), these are + // coded as just like uint256,bool,uint256 + virtualArgs += getTypeSize(arg.Type)/32 - 1 + } + retval = append(retval, marshalledValue) + } + return retval, nil +} + +// PackValues performs the operation Go format -> Hexdata. +// It is the semantic opposite of UnpackValues. +func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { + return arguments.Pack(args...) +} + +// Pack performs the operation Go format -> Hexdata. +func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { + // Make sure arguments match up and pack them + abiArgs := arguments + if len(args) != len(abiArgs) { + return nil, fmt.Errorf("argument count mismatch: got %d for %d", len(args), len(abiArgs)) + } + // variable input is the output appended at the end of packed + // output. This is used for strings and bytes types input. + var variableInput []byte + + // input offset is the bytes offset for packed output + inputOffset := 0 + for _, abiArg := range abiArgs { + inputOffset += getTypeSize(abiArg.Type) + } + var ret []byte + for i, a := range args { + input := abiArgs[i] + // pack the input + packed, err := input.Type.pack(reflect.ValueOf(a)) + if err != nil { + return nil, err + } + // check for dynamic types + if isDynamicType(input.Type) { + // set the offset + ret = append(ret, packNum(reflect.ValueOf(inputOffset))...) + // calculate next offset + inputOffset += len(packed) + // append to variable input + variableInput = append(variableInput, packed...) + } else { + // append the packed value to the input + ret = append(ret, packed...) + } + } + // append the variable input at the end of the packed input + ret = append(ret, variableInput...) + + return ret, nil +} + +// ToCamelCase converts an under-score string to a camel-case string +func ToCamelCase(input string) string { + parts := strings.Split(input, "_") + for i, s := range parts { + if len(s) > 0 { + parts[i] = strings.ToUpper(s[:1]) + s[1:] + } + } + return strings.Join(parts, "") +} diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go new file mode 100644 index 0000000..b5e6e34 --- /dev/null +++ b/accounts/abi/bind/auth.go @@ -0,0 +1,179 @@ +// 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 bind + +import ( + "context" + "crypto/ecdsa" + "errors" + "io" + "math/big" + + "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/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" +) + +// ErrNoChainID is returned whenever the user failed to specify a chain id. +var ErrNoChainID = errors.New("no chain id specified") + +// ErrNotAuthorized is returned when an account is not properly unlocked. +var ErrNotAuthorized = errors.New("not authorized to sign this account") + +// NewTransactor is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +// +// Deprecated: Use NewTransactorWithChainID instead. +func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { + log.Warn("WARNING: NewTransactor has been deprecated in favour of NewTransactorWithChainID") + json, err := io.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactor(key.PrivateKey), nil +} + +// NewKeyStoreTransactor is a utility method to easily create a transaction signer from +// a decrypted key from a keystore. +// +// Deprecated: Use NewKeyStoreTransactorWithChainID instead. +func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { + log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID") + signer := types.HomesteadSigner{} + return &TransactOpts{ + From: account.Address, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + Context: context.Background(), + }, nil +} + +// NewKeyedTransactor is a utility method to easily create a transaction signer +// from a single private key. +// +// Deprecated: Use NewKeyedTransactorWithChainID instead. +func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { + log.Warn("WARNING: NewKeyedTransactor has been deprecated in favour of NewKeyedTransactorWithChainID") + keyAddr := crypto.PubkeyToAddress(key.PublicKey) + signer := types.HomesteadSigner{} + return &TransactOpts{ + From: keyAddr, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != keyAddr { + return nil, ErrNotAuthorized + } + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + Context: context.Background(), + } +} + +// NewTransactorWithChainID is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.Int) (*TransactOpts, error) { + json, err := io.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactorWithChainID(key.PrivateKey, chainID) +} + +// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from +// 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 + } + signer := types.LatestSignerForChainID(chainID) + return &TransactOpts{ + From: account.Address, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + Context: context.Background(), + }, nil +} + +// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer +// from a single private key. +func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) { + if chainID == nil { + return nil, ErrNoChainID + } + keyAddr := crypto.PubkeyToAddress(key.PublicKey) + signer := types.LatestSignerForChainID(chainID) + return &TransactOpts{ + From: keyAddr, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != keyAddr { + return nil, ErrNotAuthorized + } + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + Context: context.Background(), + }, nil +} + +// NewClefTransactor is a utility method to easily create a transaction signer +// with a clef backend. +func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts { + return &TransactOpts{ + From: account.Address, + Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id + }, + Context: context.Background(), + } +} diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go new file mode 100644 index 0000000..38b3046 --- /dev/null +++ b/accounts/abi/bind/backend.go @@ -0,0 +1,120 @@ +// 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 bind + +import ( + "context" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + // ErrNoCode is returned by call and transact operations for which the requested + // recipient contract to operate on does not exist in the state db or does not + // have any code associated with it (i.e. self-destructed). + ErrNoCode = errors.New("no contract code at given address") + + // ErrNoPendingState is raised when attempting to perform a pending state action + // 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") +) + +// ContractCaller defines the methods needed to allow operating with a contract on a read +// only basis. +type ContractCaller interface { + // CodeAt returns the code of the given account. This is needed to differentiate + // between contract internal errors and the local chain being out of sync. + CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) + + // CallContract executes an Ethereum contract call with the specified data as the + // input. + CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) +} + +// PendingContractCaller defines methods to perform contract calls on the pending state. +// Call will try to discover this interface when access to the pending state is requested. +// If the backend does not support the pending state, Call returns ErrNoPendingState. +type PendingContractCaller interface { + // PendingCodeAt returns the code of the given account in the pending state. + PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) + + // PendingCallContract executes an Ethereum contract call against the pending state. + 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) + + // PendingCodeAt returns the code of the given account in the pending state. + PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) + + // PendingNonceAt retrieves the current pending nonce associated with an account. + PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) +} + +// DeployBackend wraps the operations needed by WaitMined and WaitDeployed. +type DeployBackend interface { + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + 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 + ContractTransactor + ContractFilterer +} diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go new file mode 100644 index 0000000..dfd9296 --- /dev/null +++ b/accounts/abi/bind/backends/simulated.go @@ -0,0 +1,52 @@ +// 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 backends + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/simulated" +) + +// SimulatedBackend is a simulated blockchain. +// Deprecated: use package github.com/ethereum/go-ethereum/ethclient/simulated instead. +type SimulatedBackend struct { + *simulated.Backend + simulated.Client +} + +// 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. +// +// Deprecated: please use simulated.Backend from package +// github.com/ethereum/go-ethereum/ethclient/simulated instead. +func NewSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64) *SimulatedBackend { + b := simulated.NewBackend(alloc, simulated.WithBlockGasLimit(gasLimit)) + return &SimulatedBackend{ + Backend: b, + Client: b.Client(), + } +} diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go new file mode 100644 index 0000000..c8972a9 --- /dev/null +++ b/accounts/abi/bind/base.go @@ -0,0 +1,562 @@ +// 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 bind + +import ( + "context" + "errors" + "fmt" + "math/big" + "strings" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" +) + +const basefeeWiggleMultiplier = 2 + +var ( + errNoEventSignature = errors.New("no event signature") + errEventSignatureMismatch = errors.New("event signature mismatch") +) + +// SignerFn is a signer function callback when a contract requires a method to +// sign the transaction before submission. +type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) + +// CallOpts is the collection of options to fine tune a contract call request. +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) +} + +// TransactOpts is the collection of authorization data required to create a +// valid Ethereum transaction. +type TransactOpts struct { + From common.Address // Ethereum account to send the transaction from + Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state) + Signer SignerFn // Method to use for signing the transaction (mandatory) + + Value *big.Int // Funds to transfer along the transaction (nil = 0 = no funds) + GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) + GasFeeCap *big.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle) + GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle) + GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) + + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) + + NoSend bool // Do all transact steps but do not send the transaction +} + +// FilterOpts is the collection of options to fine tune filtering for events +// within a bound contract. +type FilterOpts struct { + Start uint64 // Start of the queried range + End *uint64 // End of the range (nil = latest) + + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// WatchOpts is the collection of options to fine tune subscribing for events +// within a bound contract. +type WatchOpts struct { + Start *uint64 // Start of the queried range (nil = latest) + Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) +} + +// MetaData collects all metadata for a bound contract. +type MetaData struct { + mu sync.Mutex + Sigs map[string]string + Bin string + ABI string + ab *abi.ABI +} + +func (m *MetaData) GetAbi() (*abi.ABI, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.ab != nil { + return m.ab, nil + } + if parsed, err := abi.JSON(strings.NewReader(m.ABI)); err != nil { + return nil, err + } else { + m.ab = &parsed + } + return m.ab, nil +} + +// BoundContract is the base wrapper object that reflects a contract on the +// Ethereum network. It contains a collection of methods that are used by the +// higher level contract bindings to operate. +type BoundContract struct { + address common.Address // Deployment address of the contract on the Ethereum blockchain + abi abi.ABI // Reflect based ABI to access the correct Ethereum methods + caller ContractCaller // Read interface to interact with the blockchain + transactor ContractTransactor // Write interface to interact with the blockchain + filterer ContractFilterer // Event filtering to interact with the blockchain +} + +// NewBoundContract creates a low level contract interface through which calls +// and transactions may be made through. +func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract { + return &BoundContract{ + address: address, + abi: abi, + caller: caller, + transactor: transactor, + filterer: filterer, + } +} + +// DeployContract deploys a contract onto the Ethereum blockchain and binds the +// deployment address with a Go wrapper. +func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) { + // Otherwise try to deploy the contract + c := NewBoundContract(common.Address{}, abi, backend, backend, backend) + + input, err := c.abi.Pack("", params...) + if err != nil { + return common.Address{}, nil, nil, err + } + tx, err := c.transact(opts, nil, append(bytecode, input...)) + if err != nil { + return common.Address{}, nil, nil, err + } + c.address = crypto.CreateAddress(opts.From, tx.Nonce()) + return c.address, tx, c, nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error { + // Don't crash on a lazy user + if opts == nil { + opts = new(CallOpts) + } + if results == nil { + results = new([]interface{}) + } + // Pack the input, call and unpack the results + input, err := c.abi.Pack(method, params...) + if err != nil { + return err + } + var ( + msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input} + ctx = ensureContext(opts.Context) + code []byte + output []byte + ) + if opts.Pending { + pb, ok := c.caller.(PendingContractCaller) + if !ok { + return ErrNoPendingState + } + output, err = pb.PendingCallContract(ctx, msg) + 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 = pb.PendingCodeAt(ctx, c.address); err != nil { + return err + } else if len(code) == 0 { + 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 { + return err + } + if len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { + return err + } else if len(code) == 0 { + return ErrNoCode + } + } + } + + if len(*results) == 0 { + res, err := c.abi.Unpack(method, output) + *results = res + return err + } + res := *results + return c.abi.UnpackIntoInterface(res[0], method, output) +} + +// Transact invokes the (paid) contract method with params as input values. +func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + // Otherwise pack up the parameters and invoke the contract + input, err := c.abi.Pack(method, params...) + if err != nil { + return nil, err + } + // todo(rjl493456442) check whether the method is payable or not, + // reject invalid transaction at the first place + return c.transact(opts, &c.address, input) +} + +// RawTransact initiates a transaction with the given raw calldata as the input. +// It's usually used to initiate transactions for invoking **Fallback** function. +func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { + // todo(rjl493456442) check whether the method is payable or not, + // reject invalid transaction at the first place + return c.transact(opts, &c.address, calldata) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { + // todo(rjl493456442) check the payable fallback or receive is defined + // or not, reject invalid transaction at the first place + return c.transact(opts, &c.address, nil) +} + +func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Address, input []byte, head *types.Header) (*types.Transaction, error) { + // Normalize value + value := opts.Value + if value == nil { + value = new(big.Int) + } + // Estimate TipCap + gasTipCap := opts.GasTipCap + if gasTipCap == nil { + tip, err := c.transactor.SuggestGasTipCap(ensureContext(opts.Context)) + if err != nil { + return nil, err + } + gasTipCap = tip + } + // Estimate FeeCap + gasFeeCap := opts.GasFeeCap + if gasFeeCap == nil { + gasFeeCap = new(big.Int).Add( + gasTipCap, + new(big.Int).Mul(head.BaseFee, big.NewInt(basefeeWiggleMultiplier)), + ) + } + if gasFeeCap.Cmp(gasTipCap) < 0 { + return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", gasFeeCap, gasTipCap) + } + // Estimate GasLimit + gasLimit := opts.GasLimit + if opts.GasLimit == 0 { + var err error + gasLimit, err = c.estimateGasLimit(opts, contract, input, nil, gasTipCap, gasFeeCap, value) + if err != nil { + return nil, err + } + } + // create the transaction + nonce, err := c.getNonce(opts) + if err != nil { + return nil, err + } + baseTx := &types.DynamicFeeTx{ + To: contract, + Nonce: nonce, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Gas: gasLimit, + Value: value, + Data: input, + } + return types.NewTx(baseTx), nil +} + +func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) { + if opts.GasFeeCap != nil || opts.GasTipCap != nil { + return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet") + } + // Normalize value + value := opts.Value + if value == nil { + value = new(big.Int) + } + // Estimate GasPrice + gasPrice := opts.GasPrice + if gasPrice == nil { + price, err := c.transactor.SuggestGasPrice(ensureContext(opts.Context)) + if err != nil { + return nil, err + } + gasPrice = price + } + // Estimate GasLimit + gasLimit := opts.GasLimit + if opts.GasLimit == 0 { + var err error + gasLimit, err = c.estimateGasLimit(opts, contract, input, gasPrice, nil, nil, value) + if err != nil { + return nil, err + } + } + // create the transaction + nonce, err := c.getNonce(opts) + if err != nil { + return nil, err + } + baseTx := &types.LegacyTx{ + To: contract, + Nonce: nonce, + GasPrice: gasPrice, + Gas: gasLimit, + Value: value, + Data: input, + } + return types.NewTx(baseTx), nil +} + +func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) { + if contract != nil { + // Gas estimation cannot succeed without code for method invocations. + if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil { + return 0, err + } else if len(code) == 0 { + return 0, ErrNoCode + } + } + msg := ethereum.CallMsg{ + From: opts.From, + To: contract, + GasPrice: gasPrice, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Value: value, + Data: input, + } + return c.transactor.EstimateGas(ensureContext(opts.Context), msg) +} + +func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) { + if opts.Nonce == nil { + return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) + } else { + return opts.Nonce.Uint64(), nil + } +} + +// transact executes an actual transaction invocation, first deriving any missing +// authorization fields, and then scheduling the transaction for execution. +func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) { + if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) { + return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } + // Create the transaction + var ( + rawTx *types.Transaction + err error + ) + if opts.GasPrice != nil { + rawTx, err = c.createLegacyTx(opts, contract, input) + } else if opts.GasFeeCap != nil && opts.GasTipCap != nil { + rawTx, err = c.createDynamicTx(opts, contract, input, nil) + } else { + // Only query for basefee if gasPrice not specified + if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil { + return nil, errHead + } else if head.BaseFee != nil { + rawTx, err = c.createDynamicTx(opts, contract, input, head) + } else { + // Chain is not London ready -> use legacy transaction + rawTx, err = c.createLegacyTx(opts, contract, input) + } + } + if err != nil { + return nil, err + } + // Sign the transaction and schedule it for execution + if opts.Signer == nil { + return nil, errors.New("no signer to authorize the transaction with") + } + signedTx, err := opts.Signer(opts.From, rawTx) + if err != nil { + return nil, err + } + if opts.NoSend { + return signedTx, nil + } + if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { + return nil, err + } + return signedTx, nil +} + +// FilterLogs filters contract logs for past blocks, returning the necessary +// channels to construct a strongly typed bound iterator on top of them. +func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(FilterOpts) + } + // Append the event selector to the query parameters and construct the topic set + query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) + + topics, err := abi.MakeTopics(query...) + if err != nil { + return nil, nil, err + } + // Start the background filtering + logs := make(chan types.Log, 128) + + config := ethereum.FilterQuery{ + Addresses: []common.Address{c.address}, + Topics: topics, + FromBlock: new(big.Int).SetUint64(opts.Start), + } + if opts.End != nil { + config.ToBlock = new(big.Int).SetUint64(*opts.End) + } + /* TODO(karalabe): Replace the rest of the method below with this when supported + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + */ + buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config) + if err != nil { + return nil, nil, err + } + sub := event.NewSubscription(func(quit <-chan struct{}) error { + for _, log := range buff { + select { + case logs <- log: + case <-quit: + return nil + } + } + return nil + }) + + return logs, sub, nil +} + +// WatchLogs filters subscribes to contract logs for future blocks, returning a +// subscription object that can be used to tear down the watcher. +func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(WatchOpts) + } + // Append the event selector to the query parameters and construct the topic set + query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) + + topics, err := abi.MakeTopics(query...) + if err != nil { + return nil, nil, err + } + // Start the background filtering + logs := make(chan types.Log, 128) + + config := ethereum.FilterQuery{ + Addresses: []common.Address{c.address}, + Topics: topics, + } + if opts.Start != nil { + config.FromBlock = new(big.Int).SetUint64(*opts.Start) + } + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + if err != nil { + return nil, nil, err + } + return logs, sub, nil +} + +// UnpackLog unpacks a retrieved log into the provided output structure. +func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error { + // Anonymous events are not supported. + if len(log.Topics) == 0 { + return errNoEventSignature + } + if log.Topics[0] != c.abi.Events[event].ID { + return errEventSignatureMismatch + } + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return abi.ParseTopics(out, indexed, log.Topics[1:]) +} + +// UnpackLogIntoMap unpacks a retrieved log into the provided map. +func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error { + // Anonymous events are not supported. + if len(log.Topics) == 0 { + return errNoEventSignature + } + if log.Topics[0] != c.abi.Events[event].ID { + return errEventSignatureMismatch + } + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:]) +} + +// ensureContext is a helper method to ensure a context is not nil, even if the +// user specified it as such. +func ensureContext(ctx context.Context) context.Context { + if ctx == nil { + return context.Background() + } + return ctx +} diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go new file mode 100644 index 0000000..f7eb7d1 --- /dev/null +++ b/accounts/abi/bind/base_test.go @@ -0,0 +1,589 @@ +// 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 bind_test + +import ( + "context" + "errors" + "math/big" + "reflect" + "strings" + "testing" + + "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/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" +) + +func mockSign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { return tx, nil } + +type mockTransactor struct { + baseFee *big.Int + gasTipCap *big.Int + gasPrice *big.Int + suggestGasTipCapCalled bool + suggestGasPriceCalled bool +} + +func (mt *mockTransactor) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return &types.Header{BaseFee: mt.baseFee}, nil +} + +func (mt *mockTransactor) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + return []byte{1}, nil +} + +func (mt *mockTransactor) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + return 0, nil +} + +func (mt *mockTransactor) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + mt.suggestGasPriceCalled = true + return mt.gasPrice, nil +} + +func (mt *mockTransactor) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + mt.suggestGasTipCapCalled = true + return mt.gasTipCap, nil +} + +func (mt *mockTransactor) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { + return 0, nil +} + +func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transaction) error { + return nil +} + +type mockCaller struct { + codeAtBlockNumber *big.Int + callContractBlockNumber *big.Int + callContractBytes []byte + callContractErr error + codeAtBytes []byte + codeAtErr error +} + +func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + mc.codeAtBlockNumber = blockNumber + return mc.codeAtBytes, mc.codeAtErr +} + +func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + mc.callContractBlockNumber = blockNumber + return mc.callContractBytes, mc.callContractErr +} + +type mockPendingCaller struct { + *mockCaller + pendingCodeAtBytes []byte + pendingCodeAtErr error + pendingCodeAtCalled bool + pendingCallContractCalled bool + pendingCallContractBytes []byte + pendingCallContractErr error +} + +func (mc *mockPendingCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { + mc.pendingCodeAtCalled = true + return mc.pendingCodeAtBytes, mc.pendingCodeAtErr +} + +func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + mc.pendingCallContractCalled = true + 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}, + }, + } + + bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{ + Methods: map[string]abi.Method{ + "something": { + Name: "something", + Outputs: abi.Arguments{}, + }, + }, + }, mc, nil, nil) + + blockNumber := big.NewInt(42) + + bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, nil, "something") + + if mc.callContractBlockNumber != blockNumber { + t.Fatalf("CallContract() was not passed the block number") + } + + if mc.codeAtBlockNumber != blockNumber { + t.Fatalf("CodeAt() was not passed the block number") + } + + bc.Call(&bind.CallOpts{}, nil, "something") + + if mc.callContractBlockNumber != nil { + t.Fatalf("CallContract() was passed a block number when it should not have been") + } + + if mc.codeAtBlockNumber != nil { + t.Fatalf("CodeAt() was passed a block number when it should not have been") + } + + bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, nil, "something") + + if !mc.pendingCallContractCalled { + t.Fatalf("CallContract() was not passed the block number") + } + + if !mc.pendingCodeAtCalled { + t.Fatalf("CodeAt() was not passed the block number") + } +} + +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)")), + hash, + } + mockLog := newMockLog(topics, common.HexToHash("0x0")) + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + expectedReceivedMap := map[string]interface{}{ + "name": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) +} + +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"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + var received map[string]interface{} + err := bc.UnpackLogIntoMap(received, "received", mockLog) + if err == nil { + t.Error("unpacking anonymous event is not supported") + } + if err.Error() != "no event signature" { + t.Errorf("expected error 'no event signature', got '%s'", err) + } +} + +func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { + t.Parallel() + sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"}) + if err != nil { + t.Fatal(err) + } + hash := crypto.Keccak256Hash(sliceBytes) + topics := []common.Hash{ + crypto.Keccak256Hash([]byte("received(string[],address,uint256,bytes)")), + hash, + } + mockLog := newMockLog(topics, common.HexToHash("0x0")) + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + expectedReceivedMap := map[string]interface{}{ + "names": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) +} + +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) + } + hash := crypto.Keccak256Hash(arrBytes) + topics := []common.Hash{ + crypto.Keccak256Hash([]byte("received(address[2],address,uint256,bytes)")), + hash, + } + mockLog := newMockLog(topics, common.HexToHash("0x0")) + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + expectedReceivedMap := map[string]interface{}{ + "addresses": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) +} + +func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { + t.Parallel() + mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") + addrBytes := mockAddress.Bytes() + hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)")) + functionSelector := hash[:4] + functionTyBytes := append(addrBytes, functionSelector...) + var functionTy [24]byte + copy(functionTy[:], functionTyBytes[0:24]) + topics := []common.Hash{ + crypto.Keccak256Hash([]byte("received(function,address,uint256,bytes)")), + common.BytesToHash(functionTyBytes), + } + mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42")) + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + expectedReceivedMap := map[string]interface{}{ + "function": functionTy, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) +} + +func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { + t.Parallel() + bytes := []byte{1, 2, 3, 4, 5} + hash := crypto.Keccak256Hash(bytes) + topics := []common.Hash{ + crypto.Keccak256Hash([]byte("received(bytes,address,uint256,bytes)")), + hash, + } + mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42")) + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + expectedReceivedMap := map[string]interface{}{ + "content": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) +} + +func TestTransactGasFee(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + // GasTipCap and GasFeeCap + // When opts.GasTipCap and opts.GasFeeCap are nil + mt := &mockTransactor{baseFee: big.NewInt(100), gasTipCap: big.NewInt(5)} + bc := bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil) + opts := &bind.TransactOpts{Signer: mockSign} + tx, err := bc.Transact(opts, "") + assert.Nil(err) + assert.Equal(big.NewInt(5), tx.GasTipCap()) + assert.Equal(big.NewInt(205), tx.GasFeeCap()) + assert.Nil(opts.GasTipCap) + assert.Nil(opts.GasFeeCap) + assert.True(mt.suggestGasTipCapCalled) + + // Second call to Transact should use latest suggested GasTipCap + mt.gasTipCap = big.NewInt(6) + mt.suggestGasTipCapCalled = false + tx, err = bc.Transact(opts, "") + assert.Nil(err) + assert.Equal(big.NewInt(6), tx.GasTipCap()) + assert.Equal(big.NewInt(206), tx.GasFeeCap()) + assert.True(mt.suggestGasTipCapCalled) + + // GasPrice + // When opts.GasPrice is nil + mt = &mockTransactor{gasPrice: big.NewInt(5)} + bc = bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil) + opts = &bind.TransactOpts{Signer: mockSign} + tx, err = bc.Transact(opts, "") + assert.Nil(err) + assert.Equal(big.NewInt(5), tx.GasPrice()) + assert.Nil(opts.GasPrice) + assert.True(mt.suggestGasPriceCalled) + + // Second call to Transact should use latest suggested GasPrice + mt.gasPrice = big.NewInt(6) + mt.suggestGasPriceCalled = false + tx, err = bc.Transact(opts, "") + assert.Nil(err) + assert.Equal(big.NewInt(6), tx.GasPrice()) + assert.True(mt.suggestGasPriceCalled) +} + +func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]interface{}, mockLog types.Log) { + received := make(map[string]interface{}) + if err := bc.UnpackLogIntoMap(received, "received", mockLog); err != nil { + t.Error(err) + } + + if len(received) != len(expected) { + t.Fatalf("unpacked map length %v not equal expected length of %v", len(received), len(expected)) + } + for name, elem := range expected { + if !reflect.DeepEqual(elem, received[name]) { + t.Errorf("field %v does not match expected, want %v, got %v", name, elem, received[name]) + } + } +} + +func newMockLog(topics []common.Hash, txHash common.Hash) types.Log { + return types.Log{ + Address: common.HexToAddress("0x0"), + Topics: topics, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: txHash, + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } +} + +func TestCall(t *testing.T) { + t.Parallel() + var method, methodWithArg = "something", "somethingArrrrg" + tests := []struct { + name, method string + opts *bind.CallOpts + mc bind.ContractCaller + results *[]interface{} + wantErr bool + wantErrExact error + }{{ + name: "ok not pending", + mc: &mockCaller{ + codeAtBytes: []byte{0}, + }, + method: method, + }, { + name: "ok pending", + mc: &mockPendingCaller{ + pendingCodeAtBytes: []byte{0}, + }, + opts: &bind.CallOpts{ + 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), + method: "else", + wantErr: true, + }, { + name: "interface error, pending but not a PendingContractCaller", + mc: new(mockCaller), + opts: &bind.CallOpts{ + Pending: true, + }, + 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{ + pendingCallContractErr: context.DeadlineExceeded, + }, + opts: &bind.CallOpts{ + Pending: true, + }, + method: method, + wantErrExact: context.DeadlineExceeded, + }, { + name: "pending code at error", + mc: &mockPendingCaller{ + pendingCodeAtErr: errors.New(""), + }, + opts: &bind.CallOpts{ + Pending: true, + }, + method: method, + wantErr: true, + }, { + name: "no pending code at", + mc: new(mockPendingCaller), + opts: &bind.CallOpts{ + Pending: true, + }, + method: method, + wantErrExact: bind.ErrNoCode, + }, { + name: "call contract error", + mc: &mockCaller{ + callContractErr: context.DeadlineExceeded, + }, + method: method, + wantErrExact: context.DeadlineExceeded, + }, { + name: "code at error", + mc: &mockCaller{ + codeAtErr: errors.New(""), + }, + method: method, + wantErr: true, + }, { + name: "no code at", + 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{ + codeAtBytes: []byte{0}, + }, + method: methodWithArg, + wantErr: true, + }, { + name: "interface unpack error", + mc: &mockCaller{ + codeAtBytes: []byte{0}, + }, + method: method, + results: &[]interface{}{0}, + wantErr: true, + }} + for _, test := range tests { + bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{ + Methods: map[string]abi.Method{ + method: { + Name: method, + Outputs: abi.Arguments{}, + }, + methodWithArg: { + Name: methodWithArg, + Outputs: abi.Arguments{abi.Argument{}}, + }, + }, + }, test.mc, nil, nil) + err := bc.Call(test.opts, test.results, test.method) + if test.wantErr || test.wantErrExact != nil { + if err == nil { + t.Fatalf("%q expected error", test.name) + } + if test.wantErrExact != nil && !errors.Is(err, test.wantErrExact) { + t.Fatalf("%q expected error %q but got %q", test.name, test.wantErrExact, err) + } + continue + } + if err != nil { + t.Fatalf("%q unexpected error: %v", test.name, err) + } + } +} + +// 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":"----"}]}]}]`)) + abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"foo.Bar"}]}]}]`)) +} diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go new file mode 100644 index 0000000..e902345 --- /dev/null +++ b/accounts/abi/bind/bind.go @@ -0,0 +1,496 @@ +// 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 bind generates Ethereum contract Go bindings. +// +// Detailed usage document and tutorial available on the go-ethereum Wiki page: +// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts +package bind + +import ( + "bytes" + "fmt" + "go/format" + "regexp" + "strings" + "text/template" + "unicode" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/log" +) + +// Lang is a target programming language selector to generate bindings for. +type Lang int + +const ( + LangGo Lang = iota +) + +func isKeyWord(arg string) bool { + switch arg { + case "break": + case "case": + case "chan": + case "const": + case "continue": + case "default": + case "defer": + case "else": + case "fallthrough": + case "for": + case "func": + case "go": + case "goto": + case "if": + case "import": + case "interface": + case "iota": + case "map": + case "make": + case "new": + case "package": + case "range": + case "return": + case "select": + case "struct": + case "switch": + case "type": + case "var": + default: + return false + } + + return true +} + +// 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 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 ( + // contracts is the map of each individual contract requested binding + contracts = make(map[string]*tmplContract) + + // structs is the map of all redeclared structs shared by passed contracts. + structs = make(map[string]*tmplStruct) + + // isLib is the map used to flag each encountered library as such + isLib = make(map[string]struct{}) + ) + for i := 0; i < len(types); i++ { + // Parse the actual ABI to generate the binding for + evmABI, err := abi.JSON(strings.NewReader(abis[i])) + if err != nil { + return "", err + } + // Strip any whitespace from the JSON ABI + strippedABI := strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return -1 + } + return r + }, abis[i]) + + // Extract the call and transact methods; events, struct definitions; and sort them alphabetically + var ( + calls = make(map[string]*tmplMethod) + transacts = make(map[string]*tmplMethod) + events = make(map[string]*tmplEvent) + fallback *tmplMethod + receive *tmplMethod + + // identifiers are used to detect duplicated identifiers of functions + // and events. For all calls, transacts and events, abigen will generate + // corresponding bindings. However we have to ensure there is no + // identifier collisions in the bindings of these categories. + callIdentifiers = make(map[string]bool) + transactIdentifiers = make(map[string]bool) + eventIdentifiers = make(map[string]bool) + ) + + for _, input := range evmABI.Constructor.Inputs { + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + + for _, original := range evmABI.Methods { + // Normalize the method for capital cases and non-anonymous inputs/outputs + normalized := original + normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + // Ensure there is no duplicated identifier + var identifiers = callIdentifiers + if !original.IsConstant() { + identifiers = transactIdentifiers + } + // Name shouldn't start with a digit. It will make the generated code invalid. + if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) { + normalizedName = fmt.Sprintf("M%s", normalizedName) + normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool { + _, ok := identifiers[name] + return ok + }) + } + if identifiers[normalizedName] { + return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + identifiers[normalizedName] = true + + normalized.Name = normalizedName + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + if input.Name == "" || isKeyWord(input.Name) { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + normalized.Outputs = make([]abi.Argument, len(original.Outputs)) + copy(normalized.Outputs, original.Outputs) + for j, output := range normalized.Outputs { + if output.Name != "" { + normalized.Outputs[j].Name = capitalise(output.Name) + } + if hasStruct(output.Type) { + bindStructType[lang](output.Type, structs) + } + } + // Append the methods to the call or transact lists + if original.IsConstant() { + calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + } else { + transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + } + } + for _, original := range evmABI.Events { + // Skip anonymous events as they don't support explicit filtering + if original.Anonymous { + continue + } + // Normalize the event for capital cases and non-anonymous outputs + normalized := original + + // Ensure there is no duplicated identifier + normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) + // Name shouldn't start with a digit. It will make the generated code invalid. + if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) { + normalizedName = fmt.Sprintf("E%s", normalizedName) + normalizedName = abi.ResolveNameConflict(normalizedName, func(name string) bool { + _, ok := eventIdentifiers[name] + return ok + }) + } + if eventIdentifiers[normalizedName] { + return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) + } + eventIdentifiers[normalizedName] = true + normalized.Name = normalizedName + + used := make(map[string]bool) + normalized.Inputs = make([]abi.Argument, len(original.Inputs)) + copy(normalized.Inputs, original.Inputs) + for j, input := range normalized.Inputs { + if input.Name == "" || isKeyWord(input.Name) { + normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) + } + // Event is a bit special, we need to define event struct in binding, + // ensure there is no camel-case-style name conflict. + for index := 0; ; index++ { + if !used[capitalise(normalized.Inputs[j].Name)] { + used[capitalise(normalized.Inputs[j].Name)] = true + break + } + normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index) + } + if hasStruct(input.Type) { + bindStructType[lang](input.Type, structs) + } + } + // Append the event to the accumulator list + events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} + } + // Add two special fallback functions if they exist + if evmABI.HasFallback() { + fallback = &tmplMethod{Original: evmABI.Fallback} + } + if evmABI.HasReceive() { + receive = &tmplMethod{Original: evmABI.Receive} + } + contracts[types[i]] = &tmplContract{ + Type: capitalise(types[i]), + InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), + InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), + Constructor: evmABI.Constructor, + Calls: calls, + Transacts: transacts, + Fallback: fallback, + Receive: receive, + Events: events, + Libraries: make(map[string]string), + } + // Function 4-byte signatures are stored in the same sequence + // as types, if available. + if len(fsigs) > i { + contracts[types[i]].FuncSigs = fsigs[i] + } + // Parse library references. + for pattern, name := range libs { + matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin)) + if err != nil { + log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err) + } + if matched { + contracts[types[i]].Libraries[pattern] = name + // keep track that this type is a library + if _, ok := isLib[name]; !ok { + isLib[name] = struct{}{} + } + } + } + } + // Check if that type has already been identified as a library + for i := 0; i < len(types); i++ { + _, ok := isLib[types[i]] + contracts[types[i]].Library = ok + } + // Generate the contract template data content and render it + data := &tmplData{ + Package: pkg, + Contracts: contracts, + Libraries: libs, + Structs: structs, + } + buffer := new(bytes.Buffer) + + funcs := map[string]interface{}{ + "bindtype": bindType[lang], + "bindtopictype": bindTopicType[lang], + "namedtype": namedType[lang], + "capitalise": capitalise, + "decapitalise": decapitalise, + } + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) + if err := tmpl.Execute(buffer, data); err != nil { + return "", err + } + // For Go bindings pass the code through gofmt to clean it up + if lang == LangGo { + code, err := format.Source(buffer.Bytes()) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) + } + return string(code), nil + } + // For all others just return as is for now + return buffer.String(), nil +} + +// bindType is a set of type binders that convert Solidity types to some supported +// programming language types. +var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindTypeGo, +} + +// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. +func bindBasicTypeGo(kind abi.Type) string { + switch kind.T { + case abi.AddressTy: + return "common.Address" + case abi.IntTy, abi.UintTy: + parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) + switch parts[2] { + case "8", "16", "32", "64": + return fmt.Sprintf("%sint%s", parts[1], parts[2]) + } + return "*big.Int" + case abi.FixedBytesTy: + return fmt.Sprintf("[%d]byte", kind.Size) + case abi.BytesTy: + return "[]byte" + case abi.FunctionTy: + return "[24]byte" + default: + // string, bool types + return kind.String() + } +} + +// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping +// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly +// mapped will use an upscaled type (e.g. BigDecimal). +func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { + switch kind.T { + case abi.TupleTy: + return structs[kind.TupleRawName+kind.String()].Name + case abi.ArrayTy: + return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) + case abi.SliceTy: + return "[]" + bindTypeGo(*kind.Elem, structs) + default: + return bindBasicTypeGo(kind) + } +} + +// bindTopicType is a set of type binders that convert Solidity types to some +// supported programming language topic types. +var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindTopicTypeGo, +} + +// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same +// functionality as for simple types, but dynamic types get converted to hashes. +func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { + bound := bindTypeGo(kind, structs) + + // todo(rjl493456442) according 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. + // + // 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" + } + return bound +} + +// bindStructType is a set of type binders that convert Solidity tuple types to some supported +// programming language struct definition. +var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ + LangGo: bindStructTypeGo, +} + +// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping +// in the given map. +// Notably, this function will resolve and record nested struct recursively. +func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { + switch kind.T { + case abi.TupleTy: + // We compose a raw struct name and a canonical parameter expression + // together here. The reason is before solidity v0.5.11, kind.TupleRawName + // is empty, so we use canonical parameter expression to distinguish + // different struct definition. From the consideration of backward + // compatibility, we concat these two together so that if kind.TupleRawName + // is not empty, it can have unique id. + id := kind.TupleRawName + kind.String() + if s, exist := structs[id]; exist { + return s.Name + } + var ( + names = make(map[string]bool) + fields []*tmplField + ) + for i, elem := range kind.TupleElems { + name := capitalise(kind.TupleRawNames[i]) + name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] }) + names[name] = true + fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem}) + } + name := kind.TupleRawName + if name == "" { + name = fmt.Sprintf("Struct%d", len(structs)) + } + name = capitalise(name) + + structs[id] = &tmplStruct{ + Name: name, + Fields: fields, + } + return name + case abi.ArrayTy: + return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs) + case abi.SliceTy: + return "[]" + bindStructTypeGo(*kind.Elem, structs) + default: + return bindBasicTypeGo(kind) + } +} + +// namedType is a set of functions that transform language specific types to +// named versions that may be used inside method names. +var namedType = map[Lang]func(string, abi.Type) string{ + LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, +} + +// alias returns an alias of the given string based on the aliasing rules +// or returns itself if no rule is matched. +func alias(aliases map[string]string, n string) string { + if alias, exist := aliases[n]; exist { + return alias + } + return n +} + +// methodNormalizer is a name transformer that modifies Solidity method names to +// conform to target language naming conventions. +var methodNormalizer = map[Lang]func(string) string{ + LangGo: abi.ToCamelCase, +} + +// capitalise makes a camel-case string which starts with an upper case character. +var capitalise = abi.ToCamelCase + +// decapitalise makes a camel-case string which starts with a lower case character. +func decapitalise(input string) string { + if len(input) == 0 { + return input + } + + goForm := abi.ToCamelCase(input) + return strings.ToLower(goForm[:1]) + goForm[1:] +} + +// structured checks whether a list of ABI data types has enough information to +// operate through a proper Go struct or if flat returns are needed. +func structured(args abi.Arguments) bool { + if len(args) < 2 { + return false + } + exists := make(map[string]bool) + for _, out := range args { + // If the name is anonymous, we can't organize into a struct + if out.Name == "" { + return false + } + // If the field name is empty when normalized or collides (var, Var, _var, _Var), + // we can't organize into a struct + field := capitalise(out.Name) + if field == "" || exists[field] { + return false + } + exists[field] = true + } + return true +} + +// hasStruct returns an indicator whether the given type is struct, struct slice +// or struct array. +func hasStruct(t abi.Type) bool { + switch t.T { + case abi.SliceTy: + return hasStruct(*t.Elem) + case abi.ArrayTy: + return hasStruct(*t.Elem) + case abi.TupleTy: + return true + default: + return false + } +} diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go new file mode 100644 index 0000000..a390a3c --- /dev/null +++ b/accounts/abi/bind/bind_test.go @@ -0,0 +1,2147 @@ +// 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 bind + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +var bindTests = []struct { + name string + contract string + bytecode []string + abi []string + imports string + tester string + fsigs []map[string]string + libs map[string]string + aliases map[string]string + types []string +}{ + // Test that the binding is available in combined and separate forms too + { + `Empty`, + `contract NilContract {}`, + []string{`606060405260068060106000396000f3606060405200`}, + []string{`[]`}, + `"github.com/ethereum/go-ethereum/common"`, + ` + if b, err := NewEmpty(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("combined binding (%v) nil or error (%v) not nil", b, nil) + } + if b, err := NewEmptyCaller(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("caller binding (%v) nil or error (%v) not nil", b, nil) + } + if b, err := NewEmptyTransactor(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("transactor binding (%v) nil or error (%v) not nil", b, nil) + } + `, + nil, + nil, + nil, + nil, + }, + // Test that all the official sample contracts bind correctly + { + `Token`, + `https://ethereum.org/token`, + []string{`60606040526040516107fd3803806107fd83398101604052805160805160a05160c051929391820192909101600160a060020a0333166000908152600360209081526040822086905581548551838052601f6002600019610100600186161502019093169290920482018390047f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56390810193919290918801908390106100e857805160ff19168380011785555b506101189291505b8082111561017157600081556001016100b4565b50506002805460ff19168317905550505050610658806101a56000396000f35b828001600101855582156100ac579182015b828111156100ac5782518260005055916020019190600101906100fa565b50508060016000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017557805160ff19168380011785555b506100c89291506100b4565b5090565b82800160010185558215610165579182015b8281111561016557825182600050559160200191906001019061018756606060405236156100775760e060020a600035046306fdde03811461007f57806323b872dd146100dc578063313ce5671461010e57806370a082311461011a57806395d89b4114610132578063a9059cbb1461018e578063cae9ca51146101bd578063dc3080f21461031c578063dd62ed3e14610341575b610365610002565b61036760008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156104eb5780601f106104c0576101008083540402835291602001916104eb565b6103d5600435602435604435600160a060020a038316600090815260036020526040812054829010156104f357610002565b6103e760025460ff1681565b6103d560043560036020526000908152604090205481565b610367600180546020600282841615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156104eb5780601f106104c0576101008083540402835291602001916104eb565b610365600435602435600160a060020a033316600090815260036020526040902054819010156103f157610002565b60806020604435600481810135601f8101849004909302840160405260608381526103d5948235946024803595606494939101919081908382808284375094965050505050505060006000836004600050600033600160a060020a03168152602001908152602001600020600050600087600160a060020a031681526020019081526020016000206000508190555084905080600160a060020a0316638f4ffcb1338630876040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156102f25780820380516001836020036101000a031916815260200191505b50955050505050506000604051808303816000876161da5a03f11561000257505050509392505050565b6005602090815260043560009081526040808220909252602435815220546103d59081565b60046020818152903560009081526040808220909252602435815220546103d59081565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156103c75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f35b6060908152602090f35b600160a060020a03821660009081526040902054808201101561041357610002565b806003600050600033600160a060020a03168152602001908152602001600020600082828250540392505081905550806003600050600084600160a060020a0316815260200190815260200160002060008282825054019250508190555081600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b820191906000526020600020905b8154815290600101906020018083116104ce57829003601f168201915b505050505081565b600160a060020a03831681526040812054808301101561051257610002565b600160a060020a0380851680835260046020908152604080852033949094168086529382528085205492855260058252808520938552929052908220548301111561055c57610002565b816003600050600086600160a060020a03168152602001908152602001600020600082828250540392505081905550816003600050600085600160a060020a03168152602001908152602001600020600082828250540192505081905550816005600050600086600160a060020a03168152602001908152602001600020600050600033600160a060020a0316815260200190815260200160002060008282825054019250508190555082600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3939250505056`}, + []string{`[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"spentAllowance","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"},{"name":"tokenName","type":"string"},{"name":"decimalUnits","type":"uint8"},{"name":"tokenSymbol","type":"string"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]`}, + `"github.com/ethereum/go-ethereum/common"`, + ` + if b, err := NewToken(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } + `, + nil, + nil, + nil, + nil, + }, + { + `Crowdsale`, + `https://ethereum.org/crowdsale`, + []string{`606060408190526007805460ff1916905560a0806105a883396101006040529051608051915160c05160e05160008054600160a060020a03199081169095178155670de0b6b3a7640000958602600155603c9093024201600355930260045560058054909216909217905561052f90819061007990396000f36060604052361561006c5760e060020a600035046301cb3b20811461008257806329dcb0cf1461014457806338af3eed1461014d5780636e66f6e91461015f5780637a3a0e84146101715780637b3e5e7b1461017a578063a035b1fe14610183578063dc0d3dff1461018c575b61020060075460009060ff161561032357610002565b61020060035460009042106103205760025460015490106103cb576002548154600160a060020a0316908290606082818181858883f150915460025460408051600160a060020a039390931683526020830191909152818101869052517fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf6945090819003909201919050a15b60405160008054600160a060020a039081169230909116319082818181858883f150506007805460ff1916600117905550505050565b6103a160035481565b6103ab600054600160a060020a031681565b6103ab600554600160a060020a031681565b6103a160015481565b6103a160025481565b6103a160045481565b6103be60043560068054829081101561000257506000526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f8101547ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d409190910154600160a060020a03919091169082565b005b505050815481101561000257906000526020600020906002020160005060008201518160000160006101000a815481600160a060020a030219169083021790555060208201518160010160005055905050806002600082828250540192505081905550600560009054906101000a9004600160a060020a0316600160a060020a031663a9059cbb3360046000505484046040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506000604051808303816000876161da5a03f11561000257505060408051600160a060020a03331681526020810184905260018183015290517fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf692509081900360600190a15b50565b5060a0604052336060908152346080819052600680546001810180835592939282908280158290116102025760020281600202836000526020600020918201910161020291905b8082111561039d57805473ffffffffffffffffffffffffffffffffffffffff19168155600060019190910190815561036a565b5090565b6060908152602090f35b600160a060020a03166060908152602090f35b6060918252608052604090f35b5b60065481101561010e576006805482908110156100025760009182526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f0190600680549254600160a060020a0316928490811015610002576002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d40015460405190915082818181858883f19350505050507fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf660066000508281548110156100025760008290526002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f01548154600160a060020a039190911691908490811015610002576002027ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d40015460408051600160a060020a0394909416845260208401919091526000838201525191829003606001919050a16001016103cc56`}, + []string{`[{"constant":false,"inputs":[],"name":"checkGoalReached","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"deadline","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"beneficiary","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"tokenReward","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[],"name":"fundingGoal","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"amountRaised","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"price","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"funders","outputs":[{"name":"addr","type":"address"},{"name":"amount","type":"uint256"}],"type":"function"},{"inputs":[{"name":"ifSuccessfulSendTo","type":"address"},{"name":"fundingGoalInEthers","type":"uint256"},{"name":"durationInMinutes","type":"uint256"},{"name":"etherCostOfEachToken","type":"uint256"},{"name":"addressOfTokenUsedAsReward","type":"address"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"backer","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"isContribution","type":"bool"}],"name":"FundTransfer","type":"event"}]`}, + `"github.com/ethereum/go-ethereum/common"`, + ` + if b, err := NewCrowdsale(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } + `, + nil, + nil, + nil, + nil, + }, + { + `DAO`, + `https://ethereum.org/dao`, + []string{`606060405260405160808061145f833960e06040529051905160a05160c05160008054600160a060020a03191633179055600184815560028490556003839055600780549182018082558280158290116100b8576003028160030283600052602060002091820191016100b891906101c8565b50506060919091015160029190910155600160a060020a0381166000146100a65760008054600160a060020a031916821790555b505050506111f18061026e6000396000f35b505060408051608081018252600080825260208281018290528351908101845281815292820192909252426060820152600780549194509250811015610002579081527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6889050815181546020848101517401000000000000000000000000000000000000000002600160a060020a03199290921690921760a060020a60ff021916178255604083015180516001848101805460008281528690209195600293821615610100026000190190911692909204601f9081018390048201949192919091019083901061023e57805160ff19168380011785555b50610072929150610226565b5050600060028201556001015b8082111561023a578054600160a860020a031916815560018181018054600080835592600290821615610100026000190190911604601f81901061020c57506101bb565b601f0160209004906000526020600020908101906101bb91905b8082111561023a5760008155600101610226565b5090565b828001600101855582156101af579182015b828111156101af57825182600050559160200191906001019061025056606060405236156100b95760e060020a6000350463013cf08b81146100bb578063237e9492146101285780633910682114610281578063400e3949146102995780635daf08ca146102a257806369bd34361461032f5780638160f0b5146103385780638da5cb5b146103415780639644fcbd14610353578063aa02a90f146103be578063b1050da5146103c7578063bcca1fd3146104b5578063d3c0715b146104dc578063eceb29451461058d578063f2fde38b1461067b575b005b61069c6004356004805482908110156100025790600052602060002090600a02016000506005810154815460018301546003840154600485015460068601546007870154600160a060020a03959095169750929560020194919360ff828116946101009093041692919089565b60408051602060248035600481810135601f81018590048502860185019096528585526107759581359591946044949293909201918190840183828082843750949650505050505050600060006004600050848154811015610002575090527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19e600a8402908101547f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b909101904210806101e65750600481015460ff165b8061026757508060000160009054906101000a9004600160a060020a03168160010160005054846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020816007016000505414155b8061027757506001546005820154105b1561109257610002565b61077560043560066020526000908152604090205481565b61077560055481565b61078760043560078054829081101561000257506000526003026000805160206111d18339815191528101547fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c68a820154600160a060020a0382169260a060020a90920460ff16917fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c689019084565b61077560025481565b61077560015481565b610830600054600160a060020a031681565b604080516020604435600481810135601f81018490048402850184019095528484526100b9948135946024803595939460649492939101918190840183828082843750949650505050505050600080548190600160a060020a03908116339091161461084d57610002565b61077560035481565b604080516020604435600481810135601f8101849004840285018401909552848452610775948135946024803595939460649492939101918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024909101945090925082915084018382808284375094965050505050505033600160a060020a031660009081526006602052604081205481908114806104ab5750604081205460078054909190811015610002579082526003026000805160206111d1833981519152015460a060020a900460ff16155b15610ce557610002565b6100b960043560243560443560005433600160a060020a03908116911614610b1857610002565b604080516020604435600481810135601f810184900484028501840190955284845261077594813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a031660009081526006602052604081205481908114806105835750604081205460078054909190811015610002579082526003026000805160206111d18339815191520181505460a060020a900460ff16155b15610f1d57610002565b604080516020606435600481810135601f81018490048402850184019095528484526107759481359460248035956044359560849492019190819084018382808284375094965050505050505060006000600460005086815481101561000257908252600a027f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b01815090508484846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160070160005054149150610cdc565b6100b960043560005433600160a060020a03908116911614610f0857610002565b604051808a600160a060020a031681526020018981526020018060200188815260200187815260200186815260200185815260200184815260200183815260200182810382528981815460018160011615610100020316600290048152602001915080546001816001161561010002031660029004801561075e5780601f106107335761010080835404028352916020019161075e565b820191906000526020600020905b81548152906001019060200180831161074157829003601f168201915b50509a505050505050505050505060405180910390f35b60408051918252519081900360200190f35b60408051600160a060020a038616815260208101859052606081018390526080918101828152845460026001821615610100026000190190911604928201839052909160a08301908590801561081e5780601f106107f35761010080835404028352916020019161081e565b820191906000526020600020905b81548152906001019060200180831161080157829003601f168201915b50509550505050505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b600160a060020a03851660009081526006602052604081205414156108a957604060002060078054918290556001820180825582801582901161095c5760030281600302836000526020600020918201910161095c9190610a4f565b600160a060020a03851660009081526006602052604090205460078054919350908390811015610002575060005250600381026000805160206111d183398151915201805474ff0000000000000000000000000000000000000000191660a060020a85021781555b60408051600160a060020a03871681526020810186905281517f27b022af4a8347100c7a041ce5ccf8e14d644ff05de696315196faae8cd50c9b929181900390910190a15050505050565b505050915081506080604051908101604052808681526020018581526020018481526020014281526020015060076000508381548110156100025790600052602060002090600302016000508151815460208481015160a060020a02600160a060020a03199290921690921774ff00000000000000000000000000000000000000001916178255604083015180516001848101805460008281528690209195600293821615610100026000190190911692909204601f90810183900482019491929190910190839010610ad357805160ff19168380011785555b50610b03929150610abb565b5050600060028201556001015b80821115610acf57805474ffffffffffffffffffffffffffffffffffffffffff1916815560018181018054600080835592600290821615610100026000190190911604601f819010610aa15750610a42565b601f016020900490600052602060002090810190610a4291905b80821115610acf5760008155600101610abb565b5090565b82800160010185558215610a36579182015b82811115610a36578251826000505591602001919060010190610ae5565b50506060919091015160029190910155610911565b600183905560028290556003819055604080518481526020810184905280820183905290517fa439d3fa452be5e0e1e24a8145e715f4fd8b9c08c96a42fd82a855a85e5d57de9181900360600190a1505050565b50508585846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160070160005081905550600260005054603c024201816003016000508190555060008160040160006101000a81548160ff0219169083021790555060008160040160016101000a81548160ff02191690830217905550600081600501600050819055507f646fec02522b41e7125cfc859a64fd4f4cefd5dc3b6237ca0abe251ded1fa881828787876040518085815260200184600160a060020a03168152602001838152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015610cc45780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a1600182016005555b50949350505050565b6004805460018101808355909190828015829011610d1c57600a0281600a028360005260206000209182019101610d1c9190610db8565b505060048054929450918491508110156100025790600052602060002090600a02016000508054600160a060020a031916871781556001818101879055855160028381018054600082815260209081902096975091959481161561010002600019011691909104601f90810182900484019391890190839010610ed857805160ff19168380011785555b50610b6c929150610abb565b50506001015b80821115610acf578054600160a060020a03191681556000600182810182905560028381018054848255909281161561010002600019011604601f819010610e9c57505b5060006003830181905560048301805461ffff191690556005830181905560068301819055600783018190556008830180548282559082526020909120610db2916002028101905b80821115610acf57805474ffffffffffffffffffffffffffffffffffffffffff1916815560018181018054600080835592600290821615610100026000190190911604601f819010610eba57505b5050600101610e44565b601f016020900490600052602060002090810190610dfc9190610abb565b601f016020900490600052602060002090810190610e929190610abb565b82800160010185558215610da6579182015b82811115610da6578251826000505591602001919060010190610eea565b60008054600160a060020a0319168217905550565b600480548690811015610002576000918252600a027f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b01905033600160a060020a0316600090815260098201602052604090205490915060ff1660011415610f8457610002565b33600160a060020a031660009081526009820160205260409020805460ff1916600190811790915560058201805490910190558315610fcd576006810180546001019055610fda565b6006810180546000190190555b7fc34f869b7ff431b034b7b9aea9822dac189a685e0b015c7d1be3add3f89128e8858533866040518085815260200184815260200183600160a060020a03168152602001806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561107a5780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a1509392505050565b6006810154600354901315611158578060000160009054906101000a9004600160a060020a0316600160a060020a03168160010160005054670de0b6b3a76400000284604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156111225780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f15050505060048101805460ff191660011761ff00191661010017905561116d565b60048101805460ff191660011761ff00191690555b60068101546005820154600483015460408051888152602081019490945283810192909252610100900460ff166060830152517fd220b7272a8b6d0d7d6bcdace67b936a8f175e6d5c1b3ee438b72256b32ab3af9181900360800190a1509291505056a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688`}, + []string{`[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"proposals","outputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"},{"name":"description","type":"string"},{"name":"votingDeadline","type":"uint256"},{"name":"executed","type":"bool"},{"name":"proposalPassed","type":"bool"},{"name":"numberOfVotes","type":"uint256"},{"name":"currentResult","type":"int256"},{"name":"proposalHash","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"transactionBytecode","type":"bytes"}],"name":"executeProposal","outputs":[{"name":"result","type":"int256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"memberId","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"numProposals","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"members","outputs":[{"name":"member","type":"address"},{"name":"canVote","type":"bool"},{"name":"name","type":"string"},{"name":"memberSince","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"debatingPeriodInMinutes","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"minimumQuorum","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"targetMember","type":"address"},{"name":"canVote","type":"bool"},{"name":"memberName","type":"string"}],"name":"changeMembership","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"majorityMargin","outputs":[{"name":"","type":"int256"}],"type":"function"},{"constant":false,"inputs":[{"name":"beneficiary","type":"address"},{"name":"etherAmount","type":"uint256"},{"name":"JobDescription","type":"string"},{"name":"transactionBytecode","type":"bytes"}],"name":"newProposal","outputs":[{"name":"proposalID","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"minimumQuorumForProposals","type":"uint256"},{"name":"minutesForDebate","type":"uint256"},{"name":"marginOfVotesForMajority","type":"int256"}],"name":"changeVotingRules","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"supportsProposal","type":"bool"},{"name":"justificationText","type":"string"}],"name":"vote","outputs":[{"name":"voteID","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[{"name":"proposalNumber","type":"uint256"},{"name":"beneficiary","type":"address"},{"name":"etherAmount","type":"uint256"},{"name":"transactionBytecode","type":"bytes"}],"name":"checkProposalCode","outputs":[{"name":"codeChecksOut","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"type":"function"},{"inputs":[{"name":"minimumQuorumForProposals","type":"uint256"},{"name":"minutesForDebate","type":"uint256"},{"name":"marginOfVotesForMajority","type":"int256"},{"name":"congressLeader","type":"address"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"recipient","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"description","type":"string"}],"name":"ProposalAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"position","type":"bool"},{"indexed":false,"name":"voter","type":"address"},{"indexed":false,"name":"justification","type":"string"}],"name":"Voted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proposalID","type":"uint256"},{"indexed":false,"name":"result","type":"int256"},{"indexed":false,"name":"quorum","type":"uint256"},{"indexed":false,"name":"active","type":"bool"}],"name":"ProposalTallied","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"member","type":"address"},{"indexed":false,"name":"isMember","type":"bool"}],"name":"MembershipChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"minimumQuorum","type":"uint256"},{"indexed":false,"name":"debatingPeriodInMinutes","type":"uint256"},{"indexed":false,"name":"majorityMargin","type":"int256"}],"name":"ChangeOfRules","type":"event"}]`}, + `"github.com/ethereum/go-ethereum/common"`, + ` + if b, err := NewDAO(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } + `, + nil, + nil, + nil, + nil, + }, + // Test that named and anonymous inputs are handled correctly + { + `InputChecker`, ``, []string{``}, + []string{` + [ + {"type":"function","name":"noInput","constant":true,"inputs":[],"outputs":[]}, + {"type":"function","name":"namedInput","constant":true,"inputs":[{"name":"str","type":"string"}],"outputs":[]}, + {"type":"function","name":"anonInput","constant":true,"inputs":[{"name":"","type":"string"}],"outputs":[]}, + {"type":"function","name":"namedInputs","constant":true,"inputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}],"outputs":[]}, + {"type":"function","name":"anonInputs","constant":true,"inputs":[{"name":"","type":"string"},{"name":"","type":"string"}],"outputs":[]}, + {"type":"function","name":"mixedInputs","constant":true,"inputs":[{"name":"","type":"string"},{"name":"str","type":"string"}],"outputs":[]} + ] + `}, + ` + "fmt" + + "github.com/ethereum/go-ethereum/common" + `, + `if b, err := NewInputChecker(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } else if false { // Don't run, just compile and test types + var err error + + err = b.NoInput(nil) + err = b.NamedInput(nil, "") + err = b.AnonInput(nil, "") + err = b.NamedInputs(nil, "", "") + err = b.AnonInputs(nil, "", "") + err = b.MixedInputs(nil, "", "") + + fmt.Println(err) + }`, + nil, + nil, + nil, + nil, + }, + // Test that named and anonymous outputs are handled correctly + { + `OutputChecker`, ``, []string{``}, + []string{` + [ + {"type":"function","name":"noOutput","constant":true,"inputs":[],"outputs":[]}, + {"type":"function","name":"namedOutput","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"}]}, + {"type":"function","name":"anonOutput","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"}]}, + {"type":"function","name":"namedOutputs","constant":true,"inputs":[],"outputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}]}, + {"type":"function","name":"collidingOutputs","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"},{"name":"Str","type":"string"}]}, + {"type":"function","name":"anonOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"","type":"string"}]}, + {"type":"function","name":"mixedOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"str","type":"string"}]} + ] + `}, + ` + "fmt" + + "github.com/ethereum/go-ethereum/common" + `, + `if b, err := NewOutputChecker(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", b, nil) + } else if false { // Don't run, just compile and test types + var str1, str2 string + var err error + + err = b.NoOutput(nil) + str1, err = b.NamedOutput(nil) + str1, err = b.AnonOutput(nil) + res, _ := b.NamedOutputs(nil) + str1, str2, err = b.CollidingOutputs(nil) + str1, str2, err = b.AnonOutputs(nil) + str1, str2, err = b.MixedOutputs(nil) + + fmt.Println(str1, str2, res.Str1, res.Str2, err) + }`, + nil, + nil, + nil, + nil, + }, + // Tests that named, anonymous and indexed events are handled correctly + { + `EventChecker`, ``, []string{``}, + []string{` + [ + {"type":"event","name":"empty","inputs":[]}, + {"type":"event","name":"indexed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256","indexed":true}]}, + {"type":"event","name":"mixed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256"}]}, + {"type":"event","name":"anonymous","anonymous":true,"inputs":[]}, + {"type":"event","name":"dynamic","inputs":[{"name":"idxStr","type":"string","indexed":true},{"name":"idxDat","type":"bytes","indexed":true},{"name":"str","type":"string"},{"name":"dat","type":"bytes"}]}, + {"type":"event","name":"unnamed","inputs":[{"name":"","type":"uint256","indexed": true},{"name":"","type":"uint256","indexed":true}]} + ] + `}, + ` + "fmt" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" + `, + `if e, err := NewEventChecker(common.Address{}, nil); e == nil || err != nil { + t.Fatalf("binding (%v) nil or error (%v) not nil", e, nil) + } else if false { // Don't run, just compile and test types + var ( + err error + res bool + str string + dat []byte + hash common.Hash + ) + _, err = e.FilterEmpty(nil) + _, err = e.FilterIndexed(nil, []common.Address{}, []*big.Int{}) + + mit, err := e.FilterMixed(nil, []common.Address{}) + + res = mit.Next() // Make sure the iterator has a Next method + err = mit.Error() // Make sure the iterator has an Error method + err = mit.Close() // Make sure the iterator has a Close method + + fmt.Println(mit.Event.Raw.BlockHash) // Make sure the raw log is contained within the results + fmt.Println(mit.Event.Num) // Make sure the unpacked non-indexed fields are present + fmt.Println(mit.Event.Addr) // Make sure the reconstructed indexed fields are present + + dit, err := e.FilterDynamic(nil, []string{}, [][]byte{}) + + str = dit.Event.Str // Make sure non-indexed strings retain their type + dat = dit.Event.Dat // Make sure non-indexed bytes retain their type + hash = dit.Event.IdxStr // Make sure indexed strings turn into hashes + hash = dit.Event.IdxDat // Make sure indexed bytes turn into hashes + + sink := make(chan *EventCheckerMixed) + sub, err := e.WatchMixed(nil, sink, []common.Address{}) + defer sub.Unsubscribe() + + event := <-sink + fmt.Println(event.Raw.BlockHash) // Make sure the raw log is contained within the results + fmt.Println(event.Num) // Make sure the unpacked non-indexed fields are present + fmt.Println(event.Addr) // Make sure the reconstructed indexed fields are present + + fmt.Println(res, str, dat, hash, err) + + oit, err := e.FilterUnnamed(nil, []*big.Int{}, []*big.Int{}) + + arg0 := oit.Event.Arg0 // Make sure unnamed arguments are handled correctly + arg1 := oit.Event.Arg1 // Make sure unnamed arguments are handled correctly + fmt.Println(arg0, arg1) + } + // Run a tiny reflection test to ensure disallowed methods don't appear + if _, ok := reflect.TypeOf(&EventChecker{}).MethodByName("FilterAnonymous"); ok { + t.Errorf("binding has disallowed method (FilterAnonymous)") + }`, + nil, + nil, + nil, + nil, + }, + // Test that contract interactions (deploy, transact and call) generate working code + { + `Interactor`, + ` + contract Interactor { + string public deployString; + string public transactString; + + function Interactor(string str) { + deployString = str; + } + + function transact(string str) { + transactString = str; + } + } + `, + []string{`6060604052604051610328380380610328833981016040528051018060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10608d57805160ff19168380011785555b50607c9291505b8082111560ba57838155600101606b565b50505061026a806100be6000396000f35b828001600101855582156064579182015b828111156064578251826000505591602001919060010190609e565b509056606060405260e060020a60003504630d86a0e181146100315780636874e8091461008d578063d736c513146100ea575b005b610190600180546020600282841615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156102295780601f106101fe57610100808354040283529160200191610229565b61019060008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156102295780601f106101fe57610100808354040283529160200191610229565b60206004803580820135601f81018490049093026080908101604052606084815261002f946024939192918401918190838280828437509496505050505050508060016000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023157805160ff19168380011785555b506102619291505b808211156102665760008155830161017d565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156101f05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b820191906000526020600020905b81548152906001019060200180831161020c57829003601f168201915b505050505081565b82800160010185558215610175579182015b82811115610175578251826000505591602001919060010190610243565b505050565b509056`}, + []string{`[{"constant":true,"inputs":[],"name":"transactString","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":true,"inputs":[],"name":"deployString","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"str","type":"string"}],"name":"transact","outputs":[],"type":"function"},{"inputs":[{"name":"str","type":"string"}],"type":"constructor"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy an interaction tester contract and call a transaction on it + _, _, interactor, err := DeployInteractor(auth, sim, "Deploy string") + 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) + } + // Commit all pending transactions in the simulator and check the contract state + sim.Commit() + + if str, err := interactor.DeployString(nil); err != nil { + t.Fatalf("Failed to retrieve deploy string: %v", err) + } else if str != "Deploy string" { + t.Fatalf("Deploy string mismatch: have '%s', want 'Deploy string'", str) + } + if str, err := interactor.TransactString(nil); err != nil { + t.Fatalf("Failed to retrieve transact string: %v", err) + } else if str != "Transact string" { + t.Fatalf("Transact string mismatch: have '%s', want 'Transact string'", str) + } + `, + nil, + nil, + nil, + nil, + }, + // Tests that plain values can be properly returned and deserialized + { + `Getter`, + ` + contract Getter { + function getter() constant returns (string, int, bytes32) { + return ("Hi", 1, sha3("")); + } + } + `, + []string{`606060405260dc8060106000396000f3606060405260e060020a6000350463993a04b78114601a575b005b600060605260c0604052600260809081527f486900000000000000000000000000000000000000000000000000000000000060a05260017fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060e0829052610100819052606060c0908152600261012081905281906101409060a09080838184600060046012f1505081517fffff000000000000000000000000000000000000000000000000000000000000169091525050604051610160819003945092505050f3`}, + []string{`[{"constant":true,"inputs":[],"name":"getter","outputs":[{"name":"","type":"string"},{"name":"","type":"int256"},{"name":"","type":"bytes32"}],"type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a tuple tester contract and execute a structured call on it + _, _, getter, err := DeployGetter(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy getter contract: %v", err) + } + sim.Commit() + + if str, num, _, err := getter.Getter(nil); err != nil { + t.Fatalf("Failed to call anonymous field retriever: %v", err) + } else if str != "Hi" || num.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("Retrieved value mismatch: have %v/%v, want %v/%v", str, num, "Hi", 1) + } + `, + nil, + nil, + nil, + nil, + }, + // Tests that tuples can be properly returned and deserialized + { + `Tupler`, + ` + contract Tupler { + function tuple() constant returns (string a, int b, bytes32 c) { + return ("Hi", 1, sha3("")); + } + } + `, + []string{`606060405260dc8060106000396000f3606060405260e060020a60003504633175aae28114601a575b005b600060605260c0604052600260809081527f486900000000000000000000000000000000000000000000000000000000000060a05260017fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060e0829052610100819052606060c0908152600261012081905281906101409060a09080838184600060046012f1505081517fffff000000000000000000000000000000000000000000000000000000000000169091525050604051610160819003945092505050f3`}, + []string{`[{"constant":true,"inputs":[],"name":"tuple","outputs":[{"name":"a","type":"string"},{"name":"b","type":"int256"},{"name":"c","type":"bytes32"}],"type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a tuple tester contract and execute a structured call on it + _, _, tupler, err := DeployTupler(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy tupler contract: %v", err) + } + sim.Commit() + + if res, err := tupler.Tuple(nil); err != nil { + t.Fatalf("Failed to call structure retriever: %v", err) + } else if res.A != "Hi" || res.B.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("Retrieved value mismatch: have %v/%v, want %v/%v", res.A, res.B, "Hi", 1) + } + `, + nil, + nil, + nil, + nil, + }, + // Tests that arrays/slices can be properly returned and deserialized. + // Only addresses are tested, remainder just compiled to keep the test small. + { + `Slicer`, + ` + contract Slicer { + function echoAddresses(address[] input) constant returns (address[] output) { + return input; + } + function echoInts(int[] input) constant returns (int[] output) { + return input; + } + function echoFancyInts(uint24[23] input) constant returns (uint24[23] output) { + return input; + } + function echoBools(bool[] input) constant returns (bool[] output) { + return input; + } + } + `, + []string{`606060405261015c806100126000396000f3606060405260e060020a6000350463be1127a3811461003c578063d88becc014610092578063e15a3db71461003c578063f637e5891461003c575b005b604080516020600480358082013583810285810185019096528085526100ee959294602494909392850192829185019084908082843750949650505050505050604080516020810190915260009052805b919050565b604080516102e0818101909252610138916004916102e491839060179083908390808284375090955050505050506102e0604051908101604052806017905b60008152602001906001900390816100d15790505081905061008d565b60405180806020018281038252838181518152602001915080519060200190602002808383829060006004602084601f0104600f02600301f1509050019250505060405180910390f35b60405180826102e0808381846000600461015cf15090500191505060405180910390f3`}, + []string{`[{"constant":true,"inputs":[{"name":"input","type":"address[]"}],"name":"echoAddresses","outputs":[{"name":"output","type":"address[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"uint24[23]"}],"name":"echoFancyInts","outputs":[{"name":"output","type":"uint24[23]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"int256[]"}],"name":"echoInts","outputs":[{"name":"output","type":"int256[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"bool[]"}],"name":"echoBools","outputs":[{"name":"output","type":"bool[]"}],"type":"function"}]`}, + ` + "math/big" + "reflect" + + "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/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a slice tester contract and execute a n array call on it + _, _, slicer, err := DeploySlicer(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy slicer contract: %v", err) + } + sim.Commit() + + if out, err := slicer.EchoAddresses(nil, []common.Address{auth.From, common.Address{}}); err != nil { + t.Fatalf("Failed to call slice echoer: %v", err) + } else if !reflect.DeepEqual(out, []common.Address{auth.From, common.Address{}}) { + t.Fatalf("Slice return mismatch: have %v, want %v", out, []common.Address{auth.From, common.Address{}}) + } + `, + nil, + nil, + nil, + nil, + }, + // Tests that anonymous default methods can be correctly invoked + { + `Defaulter`, + ` + contract Defaulter { + address public caller; + + function() { + caller = msg.sender; + } + } + `, + []string{`6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`}, + []string{`[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a default method invoker contract and execute its default method + _, _, defaulter, err := DeployDefaulter(auth, sim) + 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) + } + sim.Commit() + + if caller, err := defaulter.Caller(nil); err != nil { + t.Fatalf("Failed to call address retriever: %v", err) + } else if (caller != auth.From) { + t.Fatalf("Address mismatch: have %v, want %v", caller, auth.From) + } + `, + nil, + nil, + nil, + nil, + }, + // Tests that structs are correctly unpacked + { + + `Structs`, + ` + pragma solidity ^0.6.5; + pragma experimental ABIEncoderV2; + contract Structs { + struct A { + bytes32 B; + } + + function F() public view returns (A[] memory a, uint256[] memory c, bool[] memory d) { + A[] memory a = new A[](2); + a[0].B = bytes32(uint256(1234) << 96); + uint256[] memory c; + bool[] memory d; + return (a, c, d); + } + + function G() public view returns (A[] memory a) { + A[] memory a = new A[](2); + a[0].B = bytes32(uint256(1234) << 96); + return a; + } + } + `, + []string{`608060405234801561001057600080fd5b50610278806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806328811f591461003b5780636fecb6231461005b575b600080fd5b610043610070565b604051610052939291906101a0565b60405180910390f35b6100636100d6565b6040516100529190610186565b604080516002808252606082810190935282918291829190816020015b610095610131565b81526020019060019003908161008d575050805190915061026960611b9082906000906100be57fe5b60209081029190910101515293606093508392509050565b6040805160028082526060828101909352829190816020015b6100f7610131565b8152602001906001900390816100ef575050805190915061026960611b90829060009061012057fe5b602090810291909101015152905090565b60408051602081019091526000815290565b815260200190565b6000815180845260208085019450808401835b8381101561017b578151518752958201959082019060010161015e565b509495945050505050565b600060208252610199602083018461014b565b9392505050565b6000606082526101b3606083018661014b565b6020838203818501528186516101c98185610239565b91508288019350845b818110156101f3576101e5838651610143565b9484019492506001016101d2565b505084810360408601528551808252908201925081860190845b8181101561022b57825115158552938301939183019160010161020d565b509298975050505050505050565b9081526020019056fea2646970667358221220eb85327e285def14230424c52893aebecec1e387a50bb6b75fc4fdbed647f45f64736f6c63430006050033`}, + []string{`[{"inputs":[],"name":"F","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"structStructs.A[]","name":"a","type":"tuple[]"},{"internalType":"uint256[]","name":"c","type":"uint256[]"},{"internalType":"bool[]","name":"d","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"structStructs.A[]","name":"a","type":"tuple[]"}],"stateMutability":"view","type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a structs method invoker contract and execute its default method + _, _, structs, err := DeployStructs(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy defaulter contract: %v", err) + } + sim.Commit() + opts := bind.CallOpts{} + if _, err := structs.F(&opts); err != nil { + t.Fatalf("Failed to invoke F method: %v", err) + } + if _, err := structs.G(&opts); err != nil { + t.Fatalf("Failed to invoke G method: %v", err) + } + `, + nil, + nil, + nil, + nil, + }, + // Tests that non-existent contracts are reported as such (though only simulator test) + { + `NonExistent`, + ` + contract NonExistent { + function String() constant returns(string) { + return "I don't exist"; + } + } + `, + []string{`6060604052609f8060106000396000f3606060405260e060020a6000350463f97a60058114601a575b005b600060605260c0604052600d60809081527f4920646f6e27742065786973740000000000000000000000000000000000000060a052602060c0908152600d60e081905281906101009060a09080838184600060046012f15050815172ffffffffffffffffffffffffffffffffffffff1916909152505060405161012081900392509050f3`}, + []string{`[{"constant":true,"inputs":[],"name":"String","outputs":[{"name":"","type":"string"}],"type":"function"}]`}, + ` + "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/types" + `, + ` + // Create a simulator and wrap a non-deployed contract + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{}, uint64(10000000000)) + defer sim.Close() + + nonexistent, err := NewNonExistent(common.Address{}, sim) + if err != nil { + t.Fatalf("Failed to access non-existent contract: %v", err) + } + // Ensure that contract calls fail with the appropriate error + if res, err := nonexistent.String(nil); err == nil { + t.Fatalf("Call succeeded on non-existent contract: %v", res) + } else if (err != bind.ErrNoCode) { + t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode) + } + `, + nil, + nil, + nil, + nil, + }, + { + `NonExistentStruct`, + ` + contract NonExistentStruct { + function Struct() public view returns(uint256 a, uint256 b) { + return (10, 10); + } + } + `, + []string{`6080604052348015600f57600080fd5b5060888061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063d5f6622514602d575b600080fd5b6033604c565b6040805192835260208301919091528051918290030190f35b600a809156fea264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033`}, + []string{`[{"inputs":[],"name":"Struct","outputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"stateMutability":"pure","type":"function"}]`}, + ` + "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/types" + `, + ` + // Create a simulator and wrap a non-deployed contract + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{}, uint64(10000000000)) + defer sim.Close() + + nonexistent, err := NewNonExistentStruct(common.Address{}, sim) + if err != nil { + t.Fatalf("Failed to access non-existent contract: %v", err) + } + // Ensure that contract calls fail with the appropriate error + if res, err := nonexistent.Struct(nil); err == nil { + t.Fatalf("Call succeeded on non-existent contract: %v", res) + } else if (err != bind.ErrNoCode) { + t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode) + } + `, + nil, + nil, + nil, + nil, + }, + // Tests that gas estimation works for contracts with weird gas mechanics too. + { + `FunkyGasPattern`, + ` + contract FunkyGasPattern { + string public field; + + function SetField(string value) { + // This check will screw gas estimation! Good, good! + if (msg.gas < 100000) { + throw; + } + field = value; + } + } + `, + []string{`606060405261021c806100126000396000f3606060405260e060020a600035046323fcf32a81146100265780634f28bf0e1461007b575b005b6040805160206004803580820135601f8101849004840285018401909552848452610024949193602493909291840191908190840183828082843750949650505050505050620186a05a101561014e57610002565b6100db60008054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281529291908301828280156102145780601f106101e957610100808354040283529160200191610214565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561013b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b505050565b8060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106101b557805160ff19168380011785555b506101499291505b808211156101e557600081556001016101a1565b82800160010185558215610199579182015b828111156101995782518260005055916020019190600101906101c7565b5090565b820191906000526020600020905b8154815290600101906020018083116101f757829003601f168201915b50505050508156`}, + []string{`[{"constant":false,"inputs":[{"name":"value","type":"string"}],"name":"SetField","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"field","outputs":[{"name":"","type":"string"}],"type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a funky gas pattern contract + _, _, limiter, err := DeployFunkyGasPattern(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy funky contract: %v", err) + } + sim.Commit() + + // Set the field with automatic estimation and check that it succeeds + if _, err := limiter.SetField(auth, "automatic"); err != nil { + t.Fatalf("Failed to call automatically gased transaction: %v", err) + } + sim.Commit() + + if field, _ := limiter.Field(nil); field != "automatic" { + t.Fatalf("Field mismatch: have %v, want %v", field, "automatic") + } + `, + nil, + nil, + nil, + nil, + }, + // Test that constant functions can be called from an (optional) specified address + { + `CallFrom`, + ` + contract CallFrom { + function callFrom() constant returns(address) { + return msg.sender; + } + } + `, []string{`6060604052346000575b6086806100176000396000f300606060405263ffffffff60e060020a60003504166349f8e98281146022575b6000565b34600057602c6055565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b335b905600a165627a7a72305820aef6b7685c0fa24ba6027e4870404a57df701473fe4107741805c19f5138417c0029`}, + []string{`[{"constant":true,"inputs":[],"name":"callFrom","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"}]`}, + ` + "math/big" + + "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/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a sender tester contract and execute a structured call on it + _, _, callfrom, err := DeployCallFrom(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy sender contract: %v", err) + } + sim.Commit() + + if res, err := callfrom.CallFrom(nil); err != nil { + t.Errorf("Failed to call constant function: %v", err) + } else if res != (common.Address{}) { + t.Errorf("Invalid address returned, want: %x, got: %x", (common.Address{}), res) + } + + for _, addr := range []common.Address{common.Address{}, common.Address{1}, common.Address{2}} { + if res, err := callfrom.CallFrom(&bind.CallOpts{From: addr}); err != nil { + t.Fatalf("Failed to call constant function: %v", err) + } else if res != addr { + t.Fatalf("Invalid address returned, want: %x, got: %x", addr, res) + } + } + `, + nil, + nil, + nil, + nil, + }, + // Tests that methods and returns with underscores inside work correctly. + { + `Underscorer`, + ` + contract Underscorer { + function UnderscoredOutput() constant returns (int _int, string _string) { + return (314, "pi"); + } + function LowerLowerCollision() constant returns (int _res, int res) { + return (1, 2); + } + function LowerUpperCollision() constant returns (int _res, int Res) { + return (1, 2); + } + function UpperLowerCollision() constant returns (int _Res, int res) { + return (1, 2); + } + function UpperUpperCollision() constant returns (int _Res, int Res) { + return (1, 2); + } + function PurelyUnderscoredOutput() constant returns (int _, int res) { + return (1, 2); + } + function AllPurelyUnderscoredOutput() constant returns (int _, int __) { + return (1, 2); + } + function _under_scored_func() constant returns (int _int) { + return 0; + } + } + `, []string{`6060604052341561000f57600080fd5b6103858061001e6000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806303a592131461009357806346546dbe146100c357806367e6633d146100ec5780639df4848514610181578063af7486ab146101b1578063b564b34d146101e1578063e02ab24d14610211578063e409ca4514610241575b600080fd5b341561009e57600080fd5b6100a6610271565b604051808381526020018281526020019250505060405180910390f35b34156100ce57600080fd5b6100d6610286565b6040518082815260200191505060405180910390f35b34156100f757600080fd5b6100ff61028e565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561014557808201518184015260208101905061012a565b50505050905090810190601f1680156101725780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b341561018c57600080fd5b6101946102dc565b604051808381526020018281526020019250505060405180910390f35b34156101bc57600080fd5b6101c46102f1565b604051808381526020018281526020019250505060405180910390f35b34156101ec57600080fd5b6101f4610306565b604051808381526020018281526020019250505060405180910390f35b341561021c57600080fd5b61022461031b565b604051808381526020018281526020019250505060405180910390f35b341561024c57600080fd5b610254610330565b604051808381526020018281526020019250505060405180910390f35b60008060016002819150809050915091509091565b600080905090565b6000610298610345565b61013a8090506040805190810160405280600281526020017f7069000000000000000000000000000000000000000000000000000000000000815250915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b6020604051908101604052806000815250905600a165627a7a72305820d1a53d9de9d1e3d55cb3dc591900b63c4f1ded79114f7b79b332684840e186a40029`}, + []string{`[{"constant":true,"inputs":[],"name":"LowerUpperCollision","outputs":[{"name":"_res","type":"int256"},{"name":"Res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_under_scored_func","outputs":[{"name":"_int","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UnderscoredOutput","outputs":[{"name":"_int","type":"int256"},{"name":"_string","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PurelyUnderscoredOutput","outputs":[{"name":"_","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UpperLowerCollision","outputs":[{"name":"_Res","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"AllPurelyUnderscoredOutput","outputs":[{"name":"_","type":"int256"},{"name":"__","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UpperUpperCollision","outputs":[{"name":"_Res","type":"int256"},{"name":"Res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"LowerLowerCollision","outputs":[{"name":"_res","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"}]`}, + ` + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a underscorer tester contract and execute a structured call on it + _, _, underscorer, err := DeployUnderscorer(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy underscorer contract: %v", err) + } + sim.Commit() + + // Verify that underscored return values correctly parse into structs + if res, err := underscorer.UnderscoredOutput(nil); err != nil { + t.Errorf("Failed to call constant function: %v", err) + } else if res.Int.Cmp(big.NewInt(314)) != 0 || res.String != "pi" { + t.Errorf("Invalid result, want: {314, \"pi\"}, got: %+v", res) + } + // Verify that underscored and non-underscored name collisions force tuple outputs + var a, b *big.Int + + a, b, _ = underscorer.LowerLowerCollision(nil) + a, b, _ = underscorer.LowerUpperCollision(nil) + a, b, _ = underscorer.UpperLowerCollision(nil) + a, b, _ = underscorer.UpperUpperCollision(nil) + a, b, _ = underscorer.PurelyUnderscoredOutput(nil) + a, b, _ = underscorer.AllPurelyUnderscoredOutput(nil) + a, _ = underscorer.UnderScoredFunc(nil) + + fmt.Println(a, b, err) + `, + nil, + nil, + nil, + nil, + }, + // Tests that logs can be successfully filtered and decoded. + { + `Eventer`, + ` + contract Eventer { + event SimpleEvent ( + address indexed Addr, + bytes32 indexed Id, + bool indexed Flag, + uint Value + ); + function raiseSimpleEvent(address addr, bytes32 id, bool flag, uint value) { + SimpleEvent(addr, id, flag, value); + } + + event NodataEvent ( + uint indexed Number, + int16 indexed Short, + uint32 indexed Long + ); + function raiseNodataEvent(uint number, int16 short, uint32 long) { + NodataEvent(number, short, long); + } + + event DynamicEvent ( + string indexed IndexedString, + bytes indexed IndexedBytes, + string NonIndexedString, + bytes NonIndexedBytes + ); + function raiseDynamicEvent(string str, bytes blob) { + DynamicEvent(str, blob, str, blob); + } + + event FixedBytesEvent ( + bytes24 indexed IndexedBytes, + bytes24 NonIndexedBytes + ); + function raiseFixedBytesEvent(bytes24 blob) { + FixedBytesEvent(blob, blob); + } + } + `, + []string{`608060405234801561001057600080fd5b5061043f806100206000396000f3006080604052600436106100615763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663528300ff8114610066578063630c31e2146100ff5780636cc6b94014610138578063c7d116dd1461015b575b600080fd5b34801561007257600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526100fd94369492936024939284019190819084018382808284375050604080516020601f89358b018035918201839004830284018301909452808352979a9998810197919650918201945092508291508401838280828437509497506101829650505050505050565b005b34801561010b57600080fd5b506100fd73ffffffffffffffffffffffffffffffffffffffff60043516602435604435151560643561033c565b34801561014457600080fd5b506100fd67ffffffffffffffff1960043516610394565b34801561016757600080fd5b506100fd60043560243560010b63ffffffff604435166103d6565b806040518082805190602001908083835b602083106101b25780518252601f199092019160209182019101610193565b51815160209384036101000a6000190180199092169116179052604051919093018190038120875190955087945090928392508401908083835b6020831061020b5780518252601f1990920191602091820191016101ec565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f3281fd4f5e152dd3385df49104a3f633706e21c9e80672e88d3bcddf33101f008484604051808060200180602001838103835285818151815260200191508051906020019080838360005b8381101561029c578181015183820152602001610284565b50505050905090810190601f1680156102c95780820380516001836020036101000a031916815260200191505b50838103825284518152845160209182019186019080838360005b838110156102fc5781810151838201526020016102e4565b50505050905090810190601f1680156103295780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a35050565b60408051828152905183151591859173ffffffffffffffffffffffffffffffffffffffff8816917f1f097de4289df643bd9c11011cc61367aa12983405c021056e706eb5ba1250c8919081900360200190a450505050565b6040805167ffffffffffffffff19831680825291517fcdc4c1b1aed5524ffb4198d7a5839a34712baef5fa06884fac7559f4a5854e0a9181900360200190a250565b8063ffffffff168260010b847f3ca7f3a77e5e6e15e781850bc82e32adfa378a2a609370db24b4d0fae10da2c960405160405180910390a45050505600a165627a7a72305820468b5843bf653145bd924b323c64ef035d3dd922c170644b44d61aa666ea6eee0029`}, + []string{`[{"constant":false,"inputs":[{"name":"str","type":"string"},{"name":"blob","type":"bytes"}],"name":"raiseDynamicEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"},{"name":"id","type":"bytes32"},{"name":"flag","type":"bool"},{"name":"value","type":"uint256"}],"name":"raiseSimpleEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"blob","type":"bytes24"}],"name":"raiseFixedBytesEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"number","type":"uint256"},{"name":"short","type":"int16"},{"name":"long","type":"uint32"}],"name":"raiseNodataEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"Addr","type":"address"},{"indexed":true,"name":"Id","type":"bytes32"},{"indexed":true,"name":"Flag","type":"bool"},{"indexed":false,"name":"Value","type":"uint256"}],"name":"SimpleEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"Number","type":"uint256"},{"indexed":true,"name":"Short","type":"int16"},{"indexed":true,"name":"Long","type":"uint32"}],"name":"NodataEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"IndexedString","type":"string"},{"indexed":true,"name":"IndexedBytes","type":"bytes"},{"indexed":false,"name":"NonIndexedString","type":"string"},{"indexed":false,"name":"NonIndexedBytes","type":"bytes"}],"name":"DynamicEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"IndexedBytes","type":"bytes24"},{"indexed":false,"name":"NonIndexedBytes","type":"bytes24"}],"name":"FixedBytesEvent","type":"event"}]`}, + ` + "math/big" + "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/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy an eventer contract + _, _, eventer, err := DeployEventer(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy eventer contract: %v", err) + } + sim.Commit() + + // Inject a few events into the contract, gradually more in each block + for i := 1; i <= 3; i++ { + for j := 1; j <= i; j++ { + if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil { + t.Fatalf("block %d, event %d: raise failed: %v", i, j, err) + } + } + sim.Commit() + } + // Test filtering for certain events and ensure they can be found + sit, err := eventer.FilterSimpleEvent(nil, []common.Address{common.Address{1}, common.Address{3}}, [][32]byte{{byte(1)}, {byte(2)}, {byte(3)}}, []bool{true}) + if err != nil { + t.Fatalf("failed to filter for simple events: %v", err) + } + defer sit.Close() + + sit.Next() + if sit.Event.Value.Uint64() != 11 || !sit.Event.Flag { + t.Errorf("simple log content mismatch: have %v, want {11, true}", sit.Event) + } + sit.Next() + if sit.Event.Value.Uint64() != 21 || !sit.Event.Flag { + t.Errorf("simple log content mismatch: have %v, want {21, true}", sit.Event) + } + sit.Next() + if sit.Event.Value.Uint64() != 31 || !sit.Event.Flag { + t.Errorf("simple log content mismatch: have %v, want {31, true}", sit.Event) + } + sit.Next() + if sit.Event.Value.Uint64() != 33 || !sit.Event.Flag { + t.Errorf("simple log content mismatch: have %v, want {33, true}", sit.Event) + } + + if sit.Next() { + t.Errorf("unexpected simple event found: %+v", sit.Event) + } + if err = sit.Error(); err != nil { + t.Fatalf("simple event iteration failed: %v", err) + } + // Test raising and filtering for an event with no data component + if _, err := eventer.RaiseNodataEvent(auth, big.NewInt(314), 141, 271); err != nil { + t.Fatalf("failed to raise nodata event: %v", err) + } + sim.Commit() + + nit, err := eventer.FilterNodataEvent(nil, []*big.Int{big.NewInt(314)}, []int16{140, 141, 142}, []uint32{271}) + if err != nil { + t.Fatalf("failed to filter for nodata events: %v", err) + } + defer nit.Close() + + if !nit.Next() { + t.Fatalf("nodata log not found: %v", nit.Error()) + } + if nit.Event.Number.Uint64() != 314 { + t.Errorf("nodata log content mismatch: have %v, want 314", nit.Event.Number) + } + if nit.Next() { + t.Errorf("unexpected nodata event found: %+v", nit.Event) + } + if err = nit.Error(); err != nil { + t.Fatalf("nodata event iteration failed: %v", err) + } + // Test raising and filtering for events with dynamic indexed components + if _, err := eventer.RaiseDynamicEvent(auth, "Hello", []byte("World")); err != nil { + t.Fatalf("failed to raise dynamic event: %v", err) + } + sim.Commit() + + dit, err := eventer.FilterDynamicEvent(nil, []string{"Hi", "Hello", "Bye"}, [][]byte{[]byte("World")}) + if err != nil { + t.Fatalf("failed to filter for dynamic events: %v", err) + } + defer dit.Close() + + if !dit.Next() { + t.Fatalf("dynamic log not found: %v", dit.Error()) + } + if dit.Event.NonIndexedString != "Hello" || string(dit.Event.NonIndexedBytes) != "World" || dit.Event.IndexedString != common.HexToHash("0x06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2") || dit.Event.IndexedBytes != common.HexToHash("0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18") { + t.Errorf("dynamic log content mismatch: have %v, want {'0x06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2, '0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18', 'Hello', 'World'}", dit.Event) + } + if dit.Next() { + t.Errorf("unexpected dynamic event found: %+v", dit.Event) + } + if err = dit.Error(); err != nil { + t.Fatalf("dynamic event iteration failed: %v", err) + } + // Test raising and filtering for events with fixed bytes components + var fblob [24]byte + copy(fblob[:], []byte("Fixed Bytes")) + + if _, err := eventer.RaiseFixedBytesEvent(auth, fblob); err != nil { + t.Fatalf("failed to raise fixed bytes event: %v", err) + } + sim.Commit() + + fit, err := eventer.FilterFixedBytesEvent(nil, [][24]byte{fblob}) + if err != nil { + t.Fatalf("failed to filter for fixed bytes events: %v", err) + } + defer fit.Close() + + if !fit.Next() { + t.Fatalf("fixed bytes log not found: %v", fit.Error()) + } + if fit.Event.NonIndexedBytes != fblob || fit.Event.IndexedBytes != fblob { + t.Errorf("fixed bytes log content mismatch: have %v, want {'%x', '%x'}", fit.Event, fblob, fblob) + } + if fit.Next() { + t.Errorf("unexpected fixed bytes event found: %+v", fit.Event) + } + if err = fit.Error(); err != nil { + t.Fatalf("fixed bytes event iteration failed: %v", err) + } + // Test subscribing to an event and raising it afterwards + ch := make(chan *EventerSimpleEvent, 16) + sub, err := eventer.WatchSimpleEvent(nil, ch, nil, nil, nil) + if err != nil { + t.Fatalf("failed to subscribe to simple events: %v", err) + } + if _, err := eventer.RaiseSimpleEvent(auth, common.Address{255}, [32]byte{255}, true, big.NewInt(255)); err != nil { + t.Fatalf("failed to raise subscribed simple event: %v", err) + } + sim.Commit() + + select { + case event := <-ch: + if event.Value.Uint64() != 255 { + t.Errorf("simple log content mismatch: have %v, want 255", event) + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("subscribed simple event didn't arrive") + } + // Unsubscribe from the event and make sure we're not delivered more + sub.Unsubscribe() + + if _, err := eventer.RaiseSimpleEvent(auth, common.Address{254}, [32]byte{254}, true, big.NewInt(254)); err != nil { + t.Fatalf("failed to raise subscribed simple event: %v", err) + } + sim.Commit() + + select { + case event := <-ch: + t.Fatalf("unsubscribed simple event arrived: %v", event) + case <-time.After(250 * time.Millisecond): + } + `, + nil, + nil, + nil, + nil, + }, + { + `DeeplyNestedArray`, + ` + contract DeeplyNestedArray { + uint64[3][4][5] public deepUint64Array; + function storeDeepUintArray(uint64[3][4][5] arr) public { + deepUint64Array = arr; + } + function retrieveDeepArray() public view returns (uint64[3][4][5]) { + return deepUint64Array; + } + } + `, + []string{`6060604052341561000f57600080fd5b6106438061001e6000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063344248551461005c5780638ed4573a1461011457806398ed1856146101ab575b600080fd5b341561006757600080fd5b610112600480806107800190600580602002604051908101604052809291906000905b828210156101055783826101800201600480602002604051908101604052809291906000905b828210156100f25783826060020160038060200260405190810160405280929190826003602002808284378201915050505050815260200190600101906100b0565b505050508152602001906001019061008a565b5050505091905050610208565b005b341561011f57600080fd5b61012761021d565b604051808260056000925b8184101561019b578284602002015160046000925b8184101561018d5782846020020151600360200280838360005b8381101561017c578082015181840152602081019050610161565b505050509050019260010192610147565b925050509260010192610132565b9250505091505060405180910390f35b34156101b657600080fd5b6101de6004808035906020019091908035906020019091908035906020019091905050610309565b604051808267ffffffffffffffff1667ffffffffffffffff16815260200191505060405180910390f35b80600090600561021992919061035f565b5050565b6102256103b0565b6000600580602002604051908101604052809291906000905b8282101561030057838260040201600480602002604051908101604052809291906000905b828210156102ed578382016003806020026040519081016040528092919082600380156102d9576020028201916000905b82829054906101000a900467ffffffffffffffff1667ffffffffffffffff16815260200190600801906020826007010492830192600103820291508084116102945790505b505050505081526020019060010190610263565b505050508152602001906001019061023e565b50505050905090565b60008360058110151561031857fe5b600402018260048110151561032957fe5b018160038110151561033757fe5b6004918282040191900660080292509250509054906101000a900467ffffffffffffffff1681565b826005600402810192821561039f579160200282015b8281111561039e5782518290600461038e9291906103df565b5091602001919060040190610375565b5b5090506103ac919061042d565b5090565b610780604051908101604052806005905b6103c9610459565b8152602001906001900390816103c15790505090565b826004810192821561041c579160200282015b8281111561041b5782518290600361040b929190610488565b50916020019190600101906103f2565b5b5090506104299190610536565b5090565b61045691905b8082111561045257600081816104499190610562565b50600401610433565b5090565b90565b610180604051908101604052806004905b6104726105a7565b81526020019060019003908161046a5790505090565b82600380016004900481019282156105255791602002820160005b838211156104ef57835183826101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555092602001926008016020816007010492830192600103026104a3565b80156105235782816101000a81549067ffffffffffffffff02191690556008016020816007010492830192600103026104ef565b505b50905061053291906105d9565b5090565b61055f91905b8082111561055b57600081816105529190610610565b5060010161053c565b5090565b90565b50600081816105719190610610565b50600101600081816105839190610610565b50600101600081816105959190610610565b5060010160006105a59190610610565b565b6060604051908101604052806003905b600067ffffffffffffffff168152602001906001900390816105b75790505090565b61060d91905b8082111561060957600081816101000a81549067ffffffffffffffff0219169055506001016105df565b5090565b90565b50600090555600a165627a7a7230582087e5a43f6965ab6ef7a4ff056ab80ed78fd8c15cff57715a1bf34ec76a93661c0029`}, + []string{`[{"constant":false,"inputs":[{"name":"arr","type":"uint64[3][4][5]"}],"name":"storeDeepUintArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"retrieveDeepArray","outputs":[{"name":"","type":"uint64[3][4][5]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"name":"deepUint64Array","outputs":[{"name":"","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + //deploy the test contract + _, _, testContract, err := DeployDeeplyNestedArray(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy test contract: %v", err) + } + + // Finish deploy. + sim.Commit() + + //Create coordinate-filled array, for testing purposes. + testArr := [5][4][3]uint64{} + for i := 0; i < 5; i++ { + testArr[i] = [4][3]uint64{} + for j := 0; j < 4; j++ { + testArr[i][j] = [3]uint64{} + for k := 0; k < 3; k++ { + //pack the coordinates, each array value will be unique, and can be validated easily. + testArr[i][j][k] = uint64(i) << 16 | uint64(j) << 8 | uint64(k) + } + } + } + + if _, err := testContract.StoreDeepUintArray(&bind.TransactOpts{ + From: auth.From, + Signer: auth.Signer, + }, testArr); err != nil { + t.Fatalf("Failed to store nested array in test contract: %v", err) + } + + sim.Commit() + + retrievedArr, err := testContract.RetrieveDeepArray(&bind.CallOpts{ + From: auth.From, + Pending: false, + }) + if err != nil { + t.Fatalf("Failed to retrieve nested array from test contract: %v", err) + } + + //quick check to see if contents were copied + // (See accounts/abi/unpack_test.go for more extensive testing) + if retrievedArr[4][3][2] != testArr[4][3][2] { + t.Fatalf("Retrieved value does not match expected value! got: %d, expected: %d. %v", retrievedArr[4][3][2], testArr[4][3][2], err) + } + `, + nil, + nil, + nil, + nil, + }, + { + `CallbackParam`, + ` + contract FunctionPointerTest { + function test(function(uint256) external callback) external { + callback(1); + } + } + `, + []string{`608060405234801561001057600080fd5b5061015e806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063d7a5aba214610040575b600080fd5b34801561004c57600080fd5b506100be6004803603602081101561006357600080fd5b810190808035806c0100000000000000000000000090049068010000000000000000900463ffffffff1677ffffffffffffffffffffffffffffffffffffffffffffffff169091602001919093929190939291905050506100c0565b005b818160016040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b15801561011657600080fd5b505af115801561012a573d6000803e3d6000fd5b50505050505056fea165627a7a7230582062f87455ff84be90896dbb0c4e4ddb505c600d23089f8e80a512548440d7e2580029`}, + []string{`[ + { + "constant": false, + "inputs": [ + { + "name": "callback", + "type": "function" + } + ], + "name": "test", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ]`}, ` + "strings" + `, + ` + if strings.Compare("test(function)", CallbackParamFuncSigs["d7a5aba2"]) != 0 { + t.Fatalf("") + } + `, + []map[string]string{ + { + "test(function)": "d7a5aba2", + }, + }, + nil, + nil, + nil, + }, { + `Tuple`, + ` + pragma solidity >=0.4.19 <0.6.0; + pragma experimental ABIEncoderV2; + + contract Tuple { + struct S { uint a; uint[] b; T[] c; } + struct T { uint x; uint y; } + struct P { uint8 x; uint8 y; } + struct Q { uint16 x; uint16 y; } + event TupleEvent(S a, T[2][] b, T[][2] c, S[] d, uint[] e); + event TupleEvent2(P[]); + + function func1(S memory a, T[2][] memory b, T[][2] memory c, S[] memory d, uint[] memory e) public pure returns (S memory, T[2][] memory, T[][2] memory, S[] memory, uint[] memory) { + return (a, b, c, d, e); + } + function func2(S memory a, T[2][] memory b, T[][2] memory c, S[] memory d, uint[] memory e) public { + emit TupleEvent(a, b, c, d, e); + } + function func3(Q[] memory) public pure {} // call function, nothing to return + } + `, + []string{`60806040523480156100115760006000fd5b50610017565b6110b2806100266000396000f3fe60806040523480156100115760006000fd5b50600436106100465760003560e01c8063443c79b41461004c578063d0062cdd14610080578063e4d9a43b1461009c57610046565b60006000fd5b610066600480360361006191908101906107b8565b6100b8565b604051610077959493929190610ccb565b60405180910390f35b61009a600480360361009591908101906107b8565b6100ef565b005b6100b660048036036100b19190810190610775565b610136565b005b6100c061013a565b60606100ca61015e565b606060608989898989945094509450945094506100e2565b9550955095509550959050565b7f18d6e66efa53739ca6d13626f35ebc700b31cced3eddb50c70bbe9c082c6cd008585858585604051610126959493929190610ccb565b60405180910390a15b5050505050565b5b50565b60405180606001604052806000815260200160608152602001606081526020015090565b60405180604001604052806002905b606081526020019060019003908161016d57905050905661106e565b600082601f830112151561019d5760006000fd5b81356101b06101ab82610d6f565b610d41565b915081818352602084019350602081019050838560808402820111156101d65760006000fd5b60005b8381101561020757816101ec888261037a565b8452602084019350608083019250505b6001810190506101d9565b5050505092915050565b600082601f83011215156102255760006000fd5b600261023861023382610d98565b610d41565b9150818360005b83811015610270578135860161025588826103f3565b8452602084019350602083019250505b60018101905061023f565b5050505092915050565b600082601f830112151561028e5760006000fd5b81356102a161029c82610dbb565b610d41565b915081818352602084019350602081019050838560408402820111156102c75760006000fd5b60005b838110156102f857816102dd888261058b565b8452602084019350604083019250505b6001810190506102ca565b5050505092915050565b600082601f83011215156103165760006000fd5b813561032961032482610de4565b610d41565b9150818183526020840193506020810190508360005b83811015610370578135860161035588826105d8565b8452602084019350602083019250505b60018101905061033f565b5050505092915050565b600082601f830112151561038e5760006000fd5b60026103a161039c82610e0d565b610d41565b915081838560408402820111156103b85760006000fd5b60005b838110156103e957816103ce88826106fe565b8452602084019350604083019250505b6001810190506103bb565b5050505092915050565b600082601f83011215156104075760006000fd5b813561041a61041582610e30565b610d41565b915081818352602084019350602081019050838560408402820111156104405760006000fd5b60005b83811015610471578161045688826106fe565b8452602084019350604083019250505b600181019050610443565b5050505092915050565b600082601f830112151561048f5760006000fd5b81356104a261049d82610e59565b610d41565b915081818352602084019350602081019050838560208402820111156104c85760006000fd5b60005b838110156104f957816104de8882610760565b8452602084019350602083019250505b6001810190506104cb565b5050505092915050565b600082601f83011215156105175760006000fd5b813561052a61052582610e82565b610d41565b915081818352602084019350602081019050838560208402820111156105505760006000fd5b60005b8381101561058157816105668882610760565b8452602084019350602083019250505b600181019050610553565b5050505092915050565b60006040828403121561059e5760006000fd5b6105a86040610d41565b905060006105b88482850161074b565b60008301525060206105cc8482850161074b565b60208301525092915050565b6000606082840312156105eb5760006000fd5b6105f56060610d41565b9050600061060584828501610760565b600083015250602082013567ffffffffffffffff8111156106265760006000fd5b6106328482850161047b565b602083015250604082013567ffffffffffffffff8111156106535760006000fd5b61065f848285016103f3565b60408301525092915050565b60006060828403121561067e5760006000fd5b6106886060610d41565b9050600061069884828501610760565b600083015250602082013567ffffffffffffffff8111156106b95760006000fd5b6106c58482850161047b565b602083015250604082013567ffffffffffffffff8111156106e65760006000fd5b6106f2848285016103f3565b60408301525092915050565b6000604082840312156107115760006000fd5b61071b6040610d41565b9050600061072b84828501610760565b600083015250602061073f84828501610760565b60208301525092915050565b60008135905061075a8161103a565b92915050565b60008135905061076f81611054565b92915050565b6000602082840312156107885760006000fd5b600082013567ffffffffffffffff8111156107a35760006000fd5b6107af8482850161027a565b91505092915050565b6000600060006000600060a086880312156107d35760006000fd5b600086013567ffffffffffffffff8111156107ee5760006000fd5b6107fa8882890161066b565b955050602086013567ffffffffffffffff8111156108185760006000fd5b61082488828901610189565b945050604086013567ffffffffffffffff8111156108425760006000fd5b61084e88828901610211565b935050606086013567ffffffffffffffff81111561086c5760006000fd5b61087888828901610302565b925050608086013567ffffffffffffffff8111156108965760006000fd5b6108a288828901610503565b9150509295509295909350565b60006108bb8383610a6a565b60808301905092915050565b60006108d38383610ac2565b905092915050565b60006108e78383610c36565b905092915050565b60006108fb8383610c8d565b60408301905092915050565b60006109138383610cbc565b60208301905092915050565b600061092a82610f0f565b6109348185610fb7565b935061093f83610eab565b8060005b8381101561097157815161095788826108af565b975061096283610f5c565b9250505b600181019050610943565b5085935050505092915050565b600061098982610f1a565b6109938185610fc8565b9350836020820285016109a585610ebb565b8060005b858110156109e257848403895281516109c285826108c7565b94506109cd83610f69565b925060208a019950505b6001810190506109a9565b50829750879550505050505092915050565b60006109ff82610f25565b610a098185610fd3565b935083602082028501610a1b85610ec5565b8060005b85811015610a585784840389528151610a3885826108db565b9450610a4383610f76565b925060208a019950505b600181019050610a1f565b50829750879550505050505092915050565b610a7381610f30565b610a7d8184610fe4565b9250610a8882610ed5565b8060005b83811015610aba578151610aa087826108ef565b9650610aab83610f83565b9250505b600181019050610a8c565b505050505050565b6000610acd82610f3b565b610ad78185610fef565b9350610ae283610edf565b8060005b83811015610b14578151610afa88826108ef565b9750610b0583610f90565b9250505b600181019050610ae6565b5085935050505092915050565b6000610b2c82610f51565b610b368185611011565b9350610b4183610eff565b8060005b83811015610b73578151610b598882610907565b9750610b6483610faa565b9250505b600181019050610b45565b5085935050505092915050565b6000610b8b82610f46565b610b958185611000565b9350610ba083610eef565b8060005b83811015610bd2578151610bb88882610907565b9750610bc383610f9d565b9250505b600181019050610ba4565b5085935050505092915050565b6000606083016000830151610bf76000860182610cbc565b5060208301518482036020860152610c0f8282610b80565b91505060408301518482036040860152610c298282610ac2565b9150508091505092915050565b6000606083016000830151610c4e6000860182610cbc565b5060208301518482036020860152610c668282610b80565b91505060408301518482036040860152610c808282610ac2565b9150508091505092915050565b604082016000820151610ca36000850182610cbc565b506020820151610cb66020850182610cbc565b50505050565b610cc581611030565b82525050565b600060a0820190508181036000830152610ce58188610bdf565b90508181036020830152610cf9818761091f565b90508181036040830152610d0d818661097e565b90508181036060830152610d2181856109f4565b90508181036080830152610d358184610b21565b90509695505050505050565b6000604051905081810181811067ffffffffffffffff82111715610d655760006000fd5b8060405250919050565b600067ffffffffffffffff821115610d875760006000fd5b602082029050602081019050919050565b600067ffffffffffffffff821115610db05760006000fd5b602082029050919050565b600067ffffffffffffffff821115610dd35760006000fd5b602082029050602081019050919050565b600067ffffffffffffffff821115610dfc5760006000fd5b602082029050602081019050919050565b600067ffffffffffffffff821115610e255760006000fd5b602082029050919050565b600067ffffffffffffffff821115610e485760006000fd5b602082029050602081019050919050565b600067ffffffffffffffff821115610e715760006000fd5b602082029050602081019050919050565b600067ffffffffffffffff821115610e9a5760006000fd5b602082029050602081019050919050565b6000819050602082019050919050565b6000819050919050565b6000819050602082019050919050565b6000819050919050565b6000819050602082019050919050565b6000819050602082019050919050565b6000819050602082019050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b600082825260208201905092915050565b600081905092915050565b600082825260208201905092915050565b600081905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b600082825260208201905092915050565b600061ffff82169050919050565b6000819050919050565b61104381611022565b811415156110515760006000fd5b50565b61105d81611030565b8114151561106b5760006000fd5b50565bfea365627a7a72315820d78c6ba7ee332581e6c4d9daa5fc07941841230f7ce49edf6e05b1b63853e8746c6578706572696d656e74616cf564736f6c634300050c0040`}, + []string{` +[{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[]","name":"c","type":"tuple[]"}],"indexed":false,"internalType":"struct Tuple.S","name":"a","type":"tuple"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"indexed":false,"internalType":"struct Tuple.T[2][]","name":"b","type":"tuple[2][]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"indexed":false,"internalType":"struct Tuple.T[][2]","name":"c","type":"tuple[][2]"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[]","name":"c","type":"tuple[]"}],"indexed":false,"internalType":"struct Tuple.S[]","name":"d","type":"tuple[]"},{"indexed":false,"internalType":"uint256[]","name":"e","type":"uint256[]"}],"name":"TupleEvent","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint8","name":"x","type":"uint8"},{"internalType":"uint8","name":"y","type":"uint8"}],"indexed":false,"internalType":"struct Tuple.P[]","name":"","type":"tuple[]"}],"name":"TupleEvent2","type":"event"},{"constant":true,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[]","name":"c","type":"tuple[]"}],"internalType":"struct Tuple.S","name":"a","type":"tuple"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[2][]","name":"b","type":"tuple[2][]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[][2]","name":"c","type":"tuple[][2]"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[]","name":"c","type":"tuple[]"}],"internalType":"struct Tuple.S[]","name":"d","type":"tuple[]"},{"internalType":"uint256[]","name":"e","type":"uint256[]"}],"name":"func1","outputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[]","name":"c","type":"tuple[]"}],"internalType":"struct Tuple.S","name":"","type":"tuple"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[2][]","name":"","type":"tuple[2][]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[][2]","name":"","type":"tuple[][2]"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[]","name":"c","type":"tuple[]"}],"internalType":"struct Tuple.S[]","name":"","type":"tuple[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[]","name":"c","type":"tuple[]"}],"internalType":"struct Tuple.S","name":"a","type":"tuple"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[2][]","name":"b","type":"tuple[2][]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[][2]","name":"c","type":"tuple[][2]"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"internalType":"uint256","name":"x","type":"uint256"},{"internalType":"uint256","name":"y","type":"uint256"}],"internalType":"struct Tuple.T[]","name":"c","type":"tuple[]"}],"internalType":"struct Tuple.S[]","name":"d","type":"tuple[]"},{"internalType":"uint256[]","name":"e","type":"uint256[]"}],"name":"func2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"components":[{"internalType":"uint16","name":"x","type":"uint16"},{"internalType":"uint16","name":"y","type":"uint16"}],"internalType":"struct Tuple.Q[]","name":"","type":"tuple[]"}],"name":"func3","outputs":[],"payable":false,"stateMutability":"pure","type":"function"}] + `}, + ` + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + + ` + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + _, _, contract, err := DeployTuple(auth, sim) + if err != nil { + t.Fatalf("deploy contract failed %v", err) + } + sim.Commit() + + check := func(a, b interface{}, errMsg string) { + if !reflect.DeepEqual(a, b) { + t.Fatal(errMsg) + } + } + + a := TupleS{ + A: big.NewInt(1), + B: []*big.Int{big.NewInt(2), big.NewInt(3)}, + C: []TupleT{ + { + X: big.NewInt(4), + Y: big.NewInt(5), + }, + { + X: big.NewInt(6), + Y: big.NewInt(7), + }, + }, + } + + b := [][2]TupleT{ + { + { + X: big.NewInt(8), + Y: big.NewInt(9), + }, + { + X: big.NewInt(10), + Y: big.NewInt(11), + }, + }, + } + + c := [2][]TupleT{ + { + { + X: big.NewInt(12), + Y: big.NewInt(13), + }, + { + X: big.NewInt(14), + Y: big.NewInt(15), + }, + }, + { + { + X: big.NewInt(16), + Y: big.NewInt(17), + }, + }, + } + + d := []TupleS{a} + + e := []*big.Int{big.NewInt(18), big.NewInt(19)} + ret1, ret2, ret3, ret4, ret5, err := contract.Func1(nil, a, b, c, d, e) + if err != nil { + t.Fatalf("invoke contract failed, err %v", err) + } + check(ret1, a, "ret1 mismatch") + check(ret2, b, "ret2 mismatch") + check(ret3, c, "ret3 mismatch") + check(ret4, d, "ret4 mismatch") + check(ret5, e, "ret5 mismatch") + + _, err = contract.Func2(auth, a, b, c, d, e) + if err != nil { + t.Fatalf("invoke contract failed, err %v", err) + } + sim.Commit() + + iter, err := contract.FilterTupleEvent(nil) + if err != nil { + t.Fatalf("failed to create event filter, err %v", err) + } + defer iter.Close() + + iter.Next() + check(iter.Event.A, a, "field1 mismatch") + check(iter.Event.B, b, "field2 mismatch") + check(iter.Event.C, c, "field3 mismatch") + check(iter.Event.D, d, "field4 mismatch") + check(iter.Event.E, e, "field5 mismatch") + + err = contract.Func3(nil, nil) + if err != nil { + t.Fatalf("failed to call function which has no return, err %v", err) + } + `, + nil, + nil, + nil, + nil, + }, + { + `UseLibrary`, + ` + library Math { + function add(uint a, uint b) public view returns(uint) { + return a + b; + } + } + + contract UseLibrary { + function add (uint c, uint d) public view returns(uint) { + return Math.add(c,d); + } + } + `, + []string{ + // Bytecode for the UseLibrary contract + `608060405234801561001057600080fd5b5061011d806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063771602f714602d575b600080fd5b604d60048036036040811015604157600080fd5b5080359060200135605f565b60408051918252519081900360200190f35b600073__$b98c933f0a6ececcd167bd4f9d3299b1a0$__63771602f784846040518363ffffffff1660e01b8152600401808381526020018281526020019250505060206040518083038186803b15801560b757600080fd5b505af415801560ca573d6000803e3d6000fd5b505050506040513d602081101560df57600080fd5b5051939250505056fea265627a7a72305820eb5c38f42445604cfa43d85e3aa5ecc48b0a646456c902dd48420ae7241d06f664736f6c63430005090032`, + // Bytecode for the Math contract + `60a3610024600b82828239805160001a607314601757fe5b30600052607381538281f3fe730000000000000000000000000000000000000000301460806040526004361060335760003560e01c8063771602f7146038575b600080fd5b605860048036036040811015604c57600080fd5b5080359060200135606a565b60408051918252519081900360200190f35b019056fea265627a7a723058206fc6c05f3078327f9c763edffdb5ab5f8bd212e293a1306c7d0ad05af3ad35f464736f6c63430005090032`, + }, + []string{ + `[{"constant":true,"inputs":[{"name":"c","type":"uint256"},{"name":"d","type":"uint256"}],"name":"add","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]`, + `[{"constant":true,"inputs":[{"name":"a","type":"uint256"},{"name":"b","type":"uint256"}],"name":"add","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]`, + }, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + //deploy the test contract + _, _, testContract, err := DeployUseLibrary(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy test contract: %v", err) + } + + // Finish deploy. + sim.Commit() + + // Check that the library contract has been deployed + // by calling the contract's add function. + res, err := testContract.Add(&bind.CallOpts{ + From: auth.From, + Pending: false, + }, big.NewInt(1), big.NewInt(2)) + if err != nil { + t.Fatalf("Failed to call linked contract: %v", err) + } + if res.Cmp(big.NewInt(3)) != 0 { + t.Fatalf("Add did not return the correct result: %d != %d", res, 3) + } + `, + nil, + map[string]string{ + "b98c933f0a6ececcd167bd4f9d3299b1a0": "Math", + }, + nil, + []string{"UseLibrary", "Math"}, + }, { + "Overload", + ` + pragma solidity ^0.5.10; + + contract overload { + mapping(address => uint256) balances; + + event bar(uint256 i); + event bar(uint256 i, uint256 j); + + function foo(uint256 i) public { + emit bar(i); + } + function foo(uint256 i, uint256 j) public { + emit bar(i, j); + } + } + `, + []string{`608060405234801561001057600080fd5b50610153806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806304bc52f81461003b5780632fbebd3814610073575b600080fd5b6100716004803603604081101561005157600080fd5b8101908080359060200190929190803590602001909291905050506100a1565b005b61009f6004803603602081101561008957600080fd5b81019080803590602001909291905050506100e4565b005b7fae42e9514233792a47a1e4554624e83fe852228e1503f63cd383e8a431f4f46d8282604051808381526020018281526020019250505060405180910390a15050565b7f0423a1321222a0a8716c22b92fac42d85a45a612b696a461784d9fa537c81e5c816040518082815260200191505060405180910390a15056fea265627a7a72305820e22b049858b33291cbe67eeaece0c5f64333e439d27032ea8337d08b1de18fe864736f6c634300050a0032`}, + []string{`[{"constant":false,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"nonpayable","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"}]`}, + ` + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Initialize test accounts + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // deploy the test contract + _, _, contract, err := DeployOverload(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy contract: %v", err) + } + // Finish deploy. + sim.Commit() + + resCh, stopCh := make(chan uint64), make(chan struct{}) + + go func() { + barSink := make(chan *OverloadBar) + sub, _ := contract.WatchBar(nil, barSink) + defer sub.Unsubscribe() + + bar0Sink := make(chan *OverloadBar0) + sub0, _ := contract.WatchBar0(nil, bar0Sink) + defer sub0.Unsubscribe() + + for { + select { + case ev := <-barSink: + resCh <- ev.I.Uint64() + case ev := <-bar0Sink: + resCh <- ev.I.Uint64() + ev.J.Uint64() + case <-stopCh: + return + } + } + }() + contract.Foo(auth, big.NewInt(1), big.NewInt(2)) + sim.Commit() + select { + case n := <-resCh: + if n != 3 { + t.Fatalf("Invalid bar0 event") + } + case <-time.NewTimer(3 * time.Second).C: + t.Fatalf("Wait bar0 event timeout") + } + + contract.Foo0(auth, big.NewInt(1)) + sim.Commit() + select { + case n := <-resCh: + if n != 1 { + t.Fatalf("Invalid bar event") + } + case <-time.NewTimer(3 * time.Second).C: + t.Fatalf("Wait bar event timeout") + } + close(stopCh) + `, + nil, + nil, + nil, + nil, + }, + { + "IdentifierCollision", + ` + pragma solidity >=0.4.19 <0.6.0; + + contract IdentifierCollision { + uint public _myVar; + + function MyVar() public view returns (uint) { + return _myVar; + } + } + `, + []string{"60806040523480156100115760006000fd5b50610017565b60c3806100256000396000f3fe608060405234801560105760006000fd5b506004361060365760003560e01c806301ad4d8714603c5780634ef1f0ad146058576036565b60006000fd5b60426074565b6040518082815260200191505060405180910390f35b605e607d565b6040518082815260200191505060405180910390f35b60006000505481565b60006000600050549050608b565b9056fea265627a7a7231582067c8d84688b01c4754ba40a2a871cede94ea1f28b5981593ab2a45b46ac43af664736f6c634300050c0032"}, + []string{`[{"constant":true,"inputs":[],"name":"MyVar","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_myVar","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/core/types" + `, + ` + // Initialize test accounts + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + // Deploy registrar contract + sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + _, _, _, err := DeployIdentifierCollision(transactOpts, sim) + if err != nil { + t.Fatalf("failed to deploy contract: %v", err) + } + `, + nil, + nil, + map[string]string{"_myVar": "pubVar"}, // alias MyVar to PubVar + nil, + }, + { + "MultiContracts", + ` + pragma solidity ^0.5.11; + pragma experimental ABIEncoderV2; + + library ExternalLib { + struct SharedStruct{ + uint256 f1; + bytes32 f2; + } + } + + contract ContractOne { + function foo(ExternalLib.SharedStruct memory s) pure public { + // Do stuff + } + } + + contract ContractTwo { + function bar(ExternalLib.SharedStruct memory s) pure public { + // Do stuff + } + } + `, + []string{ + `60806040523480156100115760006000fd5b50610017565b6101b5806100266000396000f3fe60806040523480156100115760006000fd5b50600436106100305760003560e01c80639d8a8ba81461003657610030565b60006000fd5b610050600480360361004b91908101906100d1565b610052565b005b5b5056610171565b6000813590506100698161013d565b92915050565b6000604082840312156100825760006000fd5b61008c60406100fb565b9050600061009c848285016100bc565b60008301525060206100b08482850161005a565b60208301525092915050565b6000813590506100cb81610157565b92915050565b6000604082840312156100e45760006000fd5b60006100f28482850161006f565b91505092915050565b6000604051905081810181811067ffffffffffffffff8211171561011f5760006000fd5b8060405250919050565b6000819050919050565b6000819050919050565b61014681610129565b811415156101545760006000fd5b50565b61016081610133565b8114151561016e5760006000fd5b50565bfea365627a7a72315820749274eb7f6c01010d5322af4e1668b0a154409eb7968bd6cae5524c7ed669bb6c6578706572696d656e74616cf564736f6c634300050c0040`, + `60806040523480156100115760006000fd5b50610017565b6101b5806100266000396000f3fe60806040523480156100115760006000fd5b50600436106100305760003560e01c8063db8ba08c1461003657610030565b60006000fd5b610050600480360361004b91908101906100d1565b610052565b005b5b5056610171565b6000813590506100698161013d565b92915050565b6000604082840312156100825760006000fd5b61008c60406100fb565b9050600061009c848285016100bc565b60008301525060206100b08482850161005a565b60208301525092915050565b6000813590506100cb81610157565b92915050565b6000604082840312156100e45760006000fd5b60006100f28482850161006f565b91505092915050565b6000604051905081810181811067ffffffffffffffff8211171561011f5760006000fd5b8060405250919050565b6000819050919050565b6000819050919050565b61014681610129565b811415156101545760006000fd5b50565b61016081610133565b8114151561016e5760006000fd5b50565bfea365627a7a723158209bc28ee7ea97c131a13330d77ec73b4493b5c59c648352da81dd288b021192596c6578706572696d656e74616cf564736f6c634300050c0040`, + `606c6026600b82828239805160001a6073141515601857fe5b30600052607381538281f350fe73000000000000000000000000000000000000000030146080604052600436106023575b60006000fdfea365627a7a72315820518f0110144f5b3de95697d05e456a064656890d08e6f9cff47f3be710cc46a36c6578706572696d656e74616cf564736f6c634300050c0040`, + }, + []string{ + `[{"constant":true,"inputs":[{"components":[{"internalType":"uint256","name":"f1","type":"uint256"},{"internalType":"bytes32","name":"f2","type":"bytes32"}],"internalType":"struct ExternalLib.SharedStruct","name":"s","type":"tuple"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"}]`, + `[{"constant":true,"inputs":[{"components":[{"internalType":"uint256","name":"f1","type":"uint256"},{"internalType":"bytes32","name":"f2","type":"bytes32"}],"internalType":"struct ExternalLib.SharedStruct","name":"s","type":"tuple"}],"name":"bar","outputs":[],"payable":false,"stateMutability":"pure","type":"function"}]`, + `[]`, + }, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/core/types" + `, + ` + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + // Deploy registrar contract + sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + _, _, c1, err := DeployContractOne(transactOpts, sim) + if err != nil { + t.Fatal("Failed to deploy contract") + } + sim.Commit() + err = c1.Foo(nil, ExternalLibSharedStruct{ + F1: big.NewInt(100), + F2: [32]byte{0x01, 0x02, 0x03}, + }) + if err != nil { + t.Fatal("Failed to invoke function") + } + _, _, c2, err := DeployContractTwo(transactOpts, sim) + if err != nil { + t.Fatal("Failed to deploy contract") + } + sim.Commit() + err = c2.Bar(nil, ExternalLibSharedStruct{ + F1: big.NewInt(100), + F2: [32]byte{0x01, 0x02, 0x03}, + }) + if err != nil { + t.Fatal("Failed to invoke function") + } + `, + nil, + nil, + nil, + []string{"ContractOne", "ContractTwo", "ExternalLib"}, + }, + // Test the existence of the free retrieval calls + { + `PureAndView`, + `pragma solidity >=0.6.0; + contract PureAndView { + function PureFunc() public pure returns (uint) { + return 42; + } + function ViewFunc() public view returns (uint) { + return block.number; + } + } + `, + []string{`608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806376b5686a146037578063bb38c66c146053575b600080fd5b603d606f565b6040518082815260200191505060405180910390f35b60596077565b6040518082815260200191505060405180910390f35b600043905090565b6000602a90509056fea2646970667358221220d158c2ab7fdfce366a7998ec79ab84edd43b9815630bbaede2c760ea77f29f7f64736f6c63430006000033`}, + []string{`[{"inputs": [],"name": "PureFunc","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "ViewFunc","outputs": [{"internalType": "uint256","name": "","type": "uint256"}],"stateMutability": "view","type": "function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000) + defer sim.Close() + + // Deploy a tester contract and execute a structured call on it + _, _, pav, err := DeployPureAndView(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy PureAndView contract: %v", err) + } + sim.Commit() + + // 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 { + t.Fatalf("Retrieved value mismatch: have %v, want %v", num, 42) + } + if num, err := pav.ViewFunc(nil); err != nil { + t.Fatalf("Failed to call anonymous field retriever: %v", err) + } else if num.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("Retrieved value mismatch: have %v, want %v", num, 1) + } + `, + nil, + nil, + nil, + nil, + }, + // Test fallback separation introduced in v0.6.0 + { + `NewFallbacks`, + ` + pragma solidity >=0.6.0 <0.7.0; + + contract NewFallbacks { + event Fallback(bytes data); + fallback() external { + emit Fallback(msg.data); + } + + event Received(address addr, uint value); + receive() external payable { + emit Received(msg.sender, msg.value); + } + } + `, + []string{"6080604052348015600f57600080fd5b506101078061001f6000396000f3fe608060405236605f577f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f885258743334604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1005b348015606a57600080fd5b507f9043988963722edecc2099c75b0af0ff76af14ffca42ed6bce059a20a2a9f98660003660405180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405180910390a100fea26469706673582212201f994dcfbc53bf610b19176f9a361eafa77b447fd9c796fa2c615dfd0aaf3b8b64736f6c634300060c0033"}, + []string{`[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Fallback","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Received","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"stateMutability":"payable","type":"receive"}]`}, + ` + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000) + defer sim.Close() + + opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + _, _, c, err := DeployNewFallbacks(opts, sim) + if err != nil { + t.Fatalf("Failed to deploy contract: %v", err) + } + sim.Commit() + + // Test receive function + opts.Value = big.NewInt(100) + c.Receive(opts) + sim.Commit() + + var gotEvent bool + iter, _ := c.FilterReceived(nil) + defer iter.Close() + for iter.Next() { + if iter.Event.Addr != addr { + t.Fatal("Msg.sender mismatch") + } + if iter.Event.Value.Uint64() != 100 { + t.Fatal("Msg.value mismatch") + } + gotEvent = true + break + } + if !gotEvent { + t.Fatal("Expect to receive event emitted by receive") + } + + // Test fallback function + gotEvent = false + opts.Value = nil + calldata := []byte{0x01, 0x02, 0x03} + c.Fallback(opts, calldata) + sim.Commit() + + iter2, _ := c.FilterFallback(nil) + defer iter2.Close() + for iter2.Next() { + if !bytes.Equal(iter2.Event.Data, calldata) { + t.Fatal("calldata mismatch") + } + gotEvent = true + break + } + if !gotEvent { + t.Fatal("Expect to receive event emitted by fallback") + } + `, + nil, + nil, + nil, + nil, + }, + // Test resolving single struct argument + { + `NewSingleStructArgument`, + ` + pragma solidity ^0.8.0; + + contract NewSingleStructArgument { + struct MyStruct{ + uint256 a; + uint256 b; + } + event StructEvent(MyStruct s); + function TestEvent() public { + emit StructEvent(MyStruct({a: 1, b: 2})); + } + } + `, + []string{"608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806324ec1d3f14602d575b600080fd5b60336035565b005b7fb4b2ff75e30cb4317eaae16dd8a187dd89978df17565104caa6c2797caae27d460405180604001604052806001815260200160028152506040516078919060ba565b60405180910390a1565b6040820160008201516096600085018260ad565b50602082015160a7602085018260ad565b50505050565b60b48160d3565b82525050565b600060408201905060cd60008301846082565b92915050565b600081905091905056fea26469706673582212208823628796125bf9941ce4eda18da1be3cf2931b231708ab848e1bd7151c0c9a64736f6c63430008070033"}, + []string{`[{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"indexed":false,"internalType":"struct Test.MyStruct","name":"s","type":"tuple"}],"name":"StructEvent","type":"event"},{"inputs":[],"name":"TestEvent","outputs":[],"stateMutability":"nonpayable","type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + defer sim.Close() + + _, _, d, err := DeployNewSingleStructArgument(user, sim) + if err != nil { + t.Fatalf("Failed to deploy contract %v", err) + } + sim.Commit() + + _, err = d.TestEvent(user) + if err != nil { + t.Fatalf("Failed to call contract %v", err) + } + sim.Commit() + + it, err := d.FilterStructEvent(nil) + if err != nil { + t.Fatalf("Failed to filter contract event %v", err) + } + var count int + for it.Next() { + if it.Event.S.A.Cmp(big.NewInt(1)) != 0 { + t.Fatal("Unexpected contract event") + } + if it.Event.S.B.Cmp(big.NewInt(2)) != 0 { + t.Fatal("Unexpected contract event") + } + count += 1 + } + if count != 1 { + t.Fatal("Unexpected contract event number") + } + `, + nil, + nil, + nil, + nil, + }, + // Test errors introduced in v0.8.4 + { + `NewErrors`, + ` + pragma solidity >0.8.4; + + contract NewErrors { + error MyError(uint256); + error MyError1(uint256); + error MyError2(uint256, uint256); + error MyError3(uint256 a, uint256 b, uint256 c); + function Error() public pure { + revert MyError3(1,2,3); + } + } + `, + []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" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + defer sim.Close() + + _, tx, contract, err := DeployNewErrors(user, sim) + if err != nil { + t.Fatal(err) + } + sim.Commit() + _, err = bind.WaitDeployed(context.Background(), sim, tx) + if err != nil { + t.Error(err) + } + if err := contract.Error(new(bind.CallOpts)); err == nil { + t.Fatalf("expected contract to throw error") + } + // TODO (MariusVanDerWijden unpack error using abigen + // once that is implemented + `, + nil, + nil, + nil, + nil, + }, + { + name: `ConstructorWithStructParam`, + contract: ` + pragma solidity >=0.8.0 <0.9.0; + + contract ConstructorWithStructParam { + struct StructType { + uint256 field; + } + + constructor(StructType memory st) {} + } + `, + 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" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + tester: ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + defer sim.Close() + + _, tx, _, err := DeployConstructorWithStructParam(user, sim, ConstructorWithStructParamStructType{Field: big.NewInt(42)}) + if err != nil { + t.Fatalf("DeployConstructorWithStructParam() got err %v; want nil err", err) + } + sim.Commit() + + 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) + } + `, + }, + { + name: `NameConflict`, + contract: ` + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.4.22 <0.9.0; + contract oracle { + struct request { + bytes data; + bytes _data; + } + event log (int msg, int _msg); + function addRequest(request memory req) public pure {} + function getRequest() pure public returns (request memory) { + return request("", ""); + } + } + `, + 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" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + tester: ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + defer sim.Close() + + _, tx, _, err := DeployNameConflict(user, sim) + if err != nil { + t.Fatalf("DeployNameConflict() got err %v; want nil err", err) + } + sim.Commit() + + 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) + } + `, + }, + { + name: "RangeKeyword", + contract: ` + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.4.22 <0.9.0; + contract keywordcontract { + function functionWithKeywordParameter(range uint256) public pure {} + } + `, + 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" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/ethconfig" + `, + tester: ` + var ( + key, _ = crypto.GenerateKey() + user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil) + ) + _, tx, _, err := DeployRangeKeyword(user, sim) + if err != nil { + t.Fatalf("error deploying contract: %v", err) + } + sim.Commit() + + if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil { + t.Errorf("error deploying the contract: %v", err) + } + `, + }, { + name: "NumericMethodName", + contract: ` + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.4.22 <0.9.0; + + contract NumericMethodName { + event _1TestEvent(address _param); + function _1test() public pure {} + function __1test() public pure {} + function __2test() public pure {} + } + `, + bytecode: []string{"0x6080604052348015600f57600080fd5b5060958061001e6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80639d993132146041578063d02767c7146049578063ffa02795146051575b600080fd5b60476059565b005b604f605b565b005b6057605d565b005b565b565b56fea26469706673582212200382ca602dff96a7e2ba54657985e2b4ac423a56abe4a1f0667bc635c4d4371f64736f6c63430008110033"}, + abi: []string{`[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_param","type":"address"}],"name":"_1TestEvent","type":"event"},{"inputs":[],"name":"_1test","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"__1test","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"__2test","outputs":[],"stateMutability":"pure","type":"function"}]`}, + imports: ` + "github.com/ethereum/go-ethereum/common" + `, + tester: ` + if b, err := NewNumericMethodName(common.Address{}, nil); b == nil || err != nil { + t.Fatalf("combined binding (%v) nil or error (%v) not nil", b, nil) + } +`, + }, +} + +// 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) { + t.Skip("go sdk not found for testing") + } + // Create a temporary workspace for the test suite + ws := t.TempDir() + + pkg := filepath.Join(ws, "bindtest") + if err := os.MkdirAll(pkg, 0700); err != nil { + t.Fatalf("failed to create package: %v", err) + } + // Generate the test suite for all the contracts + for i, tt := range bindTests { + t.Run(tt.name, func(t *testing.T) { + var types []string + if tt.types != nil { + types = tt.types + } else { + types = []string{tt.name} + } + // Generate the binding and create a Go source file in the workspace + bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases) + if err != nil { + t.Fatalf("test %d: failed to generate binding: %v", i, err) + } + if err = os.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+".go"), []byte(bind), 0600); err != nil { + t.Fatalf("test %d: failed to write binding: %v", i, err) + } + // Generate the test file with the injected test code + code := fmt.Sprintf(` + package bindtest + + import ( + "testing" + %s + ) + + func Test%s(t *testing.T) { + %s + } + `, tt.imports, tt.name, tt.tester) + if err := os.WriteFile(filepath.Join(pkg, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0600); err != nil { + t.Fatalf("test %d: failed to write tests: %v", i, err) + } + }) + } + // Convert the package to go modules and use the current source for go-ethereum + moder := exec.Command(gocmd, "mod", "init", "bindtest") + moder.Dir = pkg + if out, err := moder.CombinedOutput(); err != nil { + t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) + } + pwd, _ := os.Getwd() + replacer := exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ethereum/go-ethereum@v0.0.0", "-replace", "github.com/ethereum/go-ethereum="+filepath.Join(pwd, "..", "..", "..")) // Repo root + replacer.Dir = pkg + if out, err := replacer.CombinedOutput(); err != nil { + t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) + } + tidier := exec.Command(gocmd, "mod", "tidy") + tidier.Dir = pkg + if out, err := tidier.CombinedOutput(); err != nil { + t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) + } + // Test the entire package and report any failures + cmd := exec.Command(gocmd, "test", "-v", "-count", "1") + cmd.Dir = pkg + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to run binding test: %v\n%s", err, out) + } +} diff --git a/accounts/abi/bind/source.go.tpl b/accounts/abi/bind/source.go.tpl new file mode 100644 index 0000000..c84862d --- /dev/null +++ b/accounts/abi/bind/source.go.tpl @@ -0,0 +1,487 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package {{.Package}} + +import ( + "math/big" + "strings" + "errors" + + ethereum "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/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +{{$structs := .Structs}} +{{range $structs}} + // {{.Name}} is an auto generated low-level Go binding around an user-defined struct. + type {{.Name}} struct { + {{range $field := .Fields}} + {{$field.Name}} {{$field.Type}}{{end}} + } +{{end}} + +{{range $contract := .Contracts}} + // {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract. + var {{.Type}}MetaData = &bind.MetaData{ + ABI: "{{.InputABI}}", + {{if $contract.FuncSigs -}} + Sigs: map[string]string{ + {{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}", + {{end}} + }, + {{end -}} + {{if .InputBin -}} + Bin: "0x{{.InputBin}}", + {{end}} + } + // {{.Type}}ABI is the input ABI used to generate the binding from. + // Deprecated: Use {{.Type}}MetaData.ABI instead. + var {{.Type}}ABI = {{.Type}}MetaData.ABI + + {{if $contract.FuncSigs}} + // Deprecated: Use {{.Type}}MetaData.Sigs instead. + // {{.Type}}FuncSigs maps the 4-byte function signature to its string representation. + var {{.Type}}FuncSigs = {{.Type}}MetaData.Sigs + {{end}} + + {{if .InputBin}} + // {{.Type}}Bin is the compiled bytecode used for deploying new contracts. + // Deprecated: Use {{.Type}}MetaData.Bin instead. + var {{.Type}}Bin = {{.Type}}MetaData.Bin + + // Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it. + func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) { + parsed, err := {{.Type}}MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + {{range $pattern, $name := .Libraries}} + {{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend) + {{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:]) + {{end}} + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}}) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil + } + {{end}} + + // {{.Type}} is an auto generated Go binding around an Ethereum contract. + type {{.Type}} struct { + {{.Type}}Caller // Read-only binding to the contract + {{.Type}}Transactor // Write-only binding to the contract + {{.Type}}Filterer // Log filterer for contract events + } + + // {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract. + type {{.Type}}Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract. + type {{.Type}}Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events. + type {{.Type}}Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls + } + + // {{.Type}}Session is an auto generated Go binding around an Ethereum contract, + // with pre-set call and transact options. + type {{.Type}}Session struct { + Contract *{{.Type}} // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session + } + + // {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract, + // with pre-set call options. + type {{.Type}}CallerSession struct { + Contract *{{.Type}}Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + } + + // {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract, + // with pre-set transact options. + type {{.Type}}TransactorSession struct { + Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session + } + + // {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract. + type {{.Type}}Raw struct { + Contract *{{.Type}} // Generic contract binding to access the raw methods on + } + + // {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. + type {{.Type}}CallerRaw struct { + Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on + } + + // {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. + type {{.Type}}TransactorRaw struct { + Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on + } + + // New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) { + contract, err := bind{{.Type}}(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil + } + + // New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) { + contract, err := bind{{.Type}}(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &{{.Type}}Caller{contract: contract}, nil + } + + // New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) { + contract, err := bind{{.Type}}(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &{{.Type}}Transactor{contract: contract}, nil + } + + // New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract. + func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) { + contract, err := bind{{.Type}}(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &{{.Type}}Filterer{contract: contract}, nil + } + + // bind{{.Type}} binds a generic wrapper to an already deployed contract. + func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := {{.Type}}MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil + } + + // Call invokes the (constant) contract method with params as input values and + // sets the output to result. The result type might be a single field for simple + // returns, a slice of interfaces for anonymous returns and a struct for named + // returns. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...) + } + + // Transfer initiates a plain transaction to move funds to the contract, calling + // its default method if one is available. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts) + } + + // Transact invokes the (paid) contract method with params as input values. + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...) + } + + // Call invokes the (constant) contract method with params as input values and + // sets the output to result. The result type might be a single field for simple + // returns, a slice of interfaces for anonymous returns and a struct for named + // returns. + func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...) + } + + // Transfer initiates a plain transaction to move funds to the contract, calling + // its default method if one is available. + func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.contract.Transfer(opts) + } + + // Transact invokes the (paid) contract method with params as input values. + func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...) + } + + {{range .Calls}} + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) { + var out []interface{} + err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + {{if .Structured}} + outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + if err != nil { + return *outstruct, err + } + {{range $i, $t := .Normalized.Outputs}} + outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return *outstruct, err + {{else}} + if err != nil { + return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err + } + {{range $i, $t := .Normalized.Outputs}} + out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err + {{end}} + } + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + {{end}} + + {{range .Transacts}} + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) + } + + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) + } + {{end}} + + {{if .Fallback}} + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{.Fallback.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.RawTransact(opts, calldata) + } + + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{.Fallback.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) + } + + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{.Fallback.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) + } + {{end}} + + {{if .Receive}} + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{.Receive.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function + } + + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{.Receive.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) + } + + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{.Receive.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) + } + {{end}} + + {{range .Events}} + // {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}}Iterator struct { + Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration + } + // Next advances the iterator to the subsequent event, returning whether there + // are any more events found. In case of a retrieval or parsing error, false is + // returned and Error() can be queried for the exact failure. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool { + // If the iterator failed, stop iterating + if (it.fail != nil) { + return false + } + // If the iterator completed, deliver directly whatever's available + if (it.done) { + select { + case log := <-it.logs: + it.Event = new({{$contract.Type}}{{.Normalized.Name}}) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new({{$contract.Type}}{{.Normalized.Name}}) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } + } + // Error returns any retrieval or parsing error occurred during filtering. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error { + return it.fail + } + // Close terminates the iteration process, releasing any pending underlying + // resources. + func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error { + it.sub.Unsubscribe() + return nil + } + + // {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract. + type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}} + {{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}} + Raw types.Log // Blockchain specific contextual infos + } + + // Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) { + {{range .Normalized.Inputs}} + {{if .Indexed}}var {{.Name}}Rule []interface{} + for _, {{.Name}}Item := range {{.Name}} { + {{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item) + }{{end}}{{end}} + + logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}}) + if err != nil { + return nil, err + } + return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil + } + + // Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) { + {{range .Normalized.Inputs}} + {{if .Indexed}}var {{.Name}}Rule []interface{} + for _, {{.Name}}Item := range {{.Name}} { + {{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item) + }{{end}}{{end}} + + logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}}) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new({{$contract.Type}}{{.Normalized.Name}}) + if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil + } + + // Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}. + // + // Solidity: {{.Original.String}} + func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) { + event := new({{$contract.Type}}{{.Normalized.Name}}) + if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil + } + + {{end}} +{{end}} \ No newline at end of file diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go new file mode 100644 index 0000000..4a0062a --- /dev/null +++ b/accounts/abi/bind/template.go @@ -0,0 +1,89 @@ +// 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 bind + +import ( + _ "embed" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +// tmplData is the data structure required to fill the binding template. +type tmplData struct { + Package string // Name of the package to place the generated file in + Contracts map[string]*tmplContract // List of contracts to generate into this file + Libraries map[string]string // Map the bytecode's link pattern to the library name + Structs map[string]*tmplStruct // Contract struct type definitions +} + +// tmplContract contains the data needed to generate an individual contract binding. +type tmplContract struct { + Type string // Type name of the main contract binding + InputABI string // JSON ABI used as the input to generate the binding from + InputBin string // Optional EVM bytecode used to generate deploy code from + FuncSigs map[string]string // Optional map: string signature -> 4-byte signature + Constructor abi.Method // Contract constructor for deploy parametrization + Calls map[string]*tmplMethod // Contract calls that only read state data + Transacts map[string]*tmplMethod // Contract calls that write state data + Fallback *tmplMethod // Additional special fallback function + Receive *tmplMethod // Additional special receive function + Events map[string]*tmplEvent // Contract events accessors + Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs + Library bool // Indicator whether the contract is a library +} + +// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed +// and cached data fields. +type tmplMethod struct { + Original abi.Method // Original method as parsed by the abi package + Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns) + Structured bool // Whether the returns should be accumulated into a struct +} + +// tmplEvent is a wrapper around an abi.Event that contains a few preprocessed +// and cached data fields. +type tmplEvent struct { + Original abi.Event // Original event as parsed by the abi package + Normalized abi.Event // Normalized version of the parsed fields +} + +// tmplField is a wrapper around a struct field with binding language +// struct type definition and relative filed name. +type tmplField struct { + Type string // Field type representation depends on target binding language + Name string // Field name converted from the raw user-defined field name + SolKind abi.Type // Raw abi type information +} + +// tmplStruct is a wrapper around an abi.tuple and contains an auto-generated +// struct name. +type tmplStruct struct { + Name string // Auto-generated struct name(before solidity v0.5.11) or raw name. + Fields []*tmplField // Struct fields definition depends on the binding language. +} + +// tmplSource is language to template mapping containing all the supported +// programming languages the package can generate to. +var tmplSource = map[Lang]string{ + LangGo: tmplSourceGo, +} + +// tmplSourceGo is the Go source template that the generated Go contract binding +// is based on. +// +//go:embed source.go.tpl +var tmplSourceGo string diff --git a/accounts/abi/bind/util.go b/accounts/abi/bind/util.go new file mode 100644 index 0000000..b931fbb --- /dev/null +++ b/accounts/abi/bind/util.go @@ -0,0 +1,79 @@ +// 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 bind + +import ( + "context" + "errors" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// WaitMined waits for tx to be mined on the blockchain. +// It stops waiting when the context is canceled. +func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*types.Receipt, error) { + queryTicker := time.NewTicker(time.Second) + defer queryTicker.Stop() + + logger := log.New("hash", tx.Hash()) + for { + receipt, err := b.TransactionReceipt(ctx, tx.Hash()) + if err == nil { + return receipt, nil + } + + if errors.Is(err, ethereum.NotFound) { + logger.Trace("Transaction not yet mined") + } else { + logger.Trace("Receipt retrieval failed", "err", err) + } + + // Wait for the next round. + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-queryTicker.C: + } + } +} + +// WaitDeployed waits for a contract deployment transaction and returns the on-chain +// contract address when it is mined. It stops waiting when ctx is canceled. +func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) (common.Address, error) { + if tx.To() != nil { + return common.Address{}, errors.New("tx is not contract creation") + } + receipt, err := WaitMined(ctx, b, tx) + if err != nil { + return common.Address{}, err + } + if receipt.ContractAddress == (common.Address{}) { + return common.Address{}, errors.New("zero address") + } + // Check that code has indeed been deployed at the address. + // This matters on pre-Homestead chains: OOG in the constructor + // could leave an empty account behind. + code, err := b.CodeAt(ctx, receipt.ContractAddress, nil) + if err == nil && len(code) == 0 { + err = ErrNoCodeAfterDeploy + } + return receipt.ContractAddress, err +} diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go new file mode 100644 index 0000000..592465f --- /dev/null +++ b/accounts/abi/bind/util_test.go @@ -0,0 +1,139 @@ +// 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 bind_test + +import ( + "context" + "errors" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "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") + +var waitDeployedTests = map[string]struct { + code string + gas uint64 + wantAddress common.Address + wantErr error +}{ + "successful deploy": { + code: `6060604052600a8060106000396000f360606040526008565b00`, + gas: 3000000, + wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"), + }, + "empty code": { + code: ``, + gas: 300000, + wantErr: bind.ErrNoCodeAfterDeploy, + wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"), + }, +} + +func TestWaitDeployed(t *testing.T) { + t.Parallel() + for name, test := range waitDeployedTests { + backend := simulated.NewBackend( + types.GenesisAlloc{ + crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, + }, + ) + defer backend.Close() + + // Create the transaction + head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(params.GWei)) + + tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code)) + tx, _ = types.SignTx(tx, types.LatestSignerForChainID(big.NewInt(1337)), testKey) + + // Wait for it to get mined in the background. + var ( + err error + address common.Address + mined = make(chan struct{}) + ctx = context.Background() + ) + go func() { + address, err = bind.WaitDeployed(ctx, backend.Client(), tx) + close(mined) + }() + + // Send and mine the transaction. + backend.Client().SendTransaction(ctx, tx) + backend.Commit() + + select { + case <-mined: + if err != test.wantErr { + t.Errorf("test %q: error mismatch: want %q, got %q", name, test.wantErr, err) + } + if address != test.wantAddress { + t.Errorf("test %q: unexpected contract address %s", name, address.Hex()) + } + case <-time.After(2 * time.Second): + t.Errorf("test %q: timeout", name) + } + } +} + +func TestWaitDeployedCornerCases(t *testing.T) { + backend := simulated.NewBackend( + types.GenesisAlloc{ + crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, + }, + ) + defer backend.Close() + + 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.LatestSigner(params.AllDevChainProtocolChanges), testKey) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + backend.Client().SendTransaction(ctx, tx) + backend.Commit() + 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.LatestSigner(params.AllDevChainProtocolChanges), testKey) + + go func() { + contextCanceled := errors.New("context canceled") + if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != contextCanceled.Error() { + t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err) + } + }() + + backend.Client().SendTransaction(ctx, tx) + cancel() +} diff --git a/accounts/abi/doc.go b/accounts/abi/doc.go new file mode 100644 index 0000000..8242068 --- /dev/null +++ b/accounts/abi/doc.go @@ -0,0 +1,26 @@ +// 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 abi implements the Ethereum ABI (Application Binary +// Interface). +// +// The Ethereum ABI is strongly typed, known at compile time +// and static. This ABI will handle basic type casting; unsigned +// to signed and visa versa. It does not handle slice casting such +// as unsigned slice to signed slice. Bit size type casting is also +// handled. ints with a bit size of 32 will be properly cast to int256, +// etc. +package abi diff --git a/accounts/abi/error.go b/accounts/abi/error.go new file mode 100644 index 0000000..8e50112 --- /dev/null +++ b/accounts/abi/error.go @@ -0,0 +1,92 @@ +// 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 abi + +import ( + "bytes" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type Error struct { + Name string + Inputs Arguments + str string + + // Sig contains the string signature according to the ABI spec. + // e.g. error foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + + // ID returns the canonical representation of the error's signature used by the + // abi definition to identify event names and types. + ID common.Hash +} + +func NewError(name string, inputs Arguments) Error { + // sanitize inputs to remove inputs without names + // and precompute string and sig representation. + names := make([]string, len(inputs)) + types := make([]string, len(inputs)) + for i, input := range inputs { + if input.Name == "" { + inputs[i] = Argument{ + Name: fmt.Sprintf("arg%d", i), + Indexed: input.Indexed, + Type: input.Type, + } + } else { + inputs[i] = input + } + // string representation + names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name) + if input.Indexed { + names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name) + } + // sig representation + types[i] = input.Type.String() + } + + str := fmt.Sprintf("error %v(%v)", name, strings.Join(names, ", ")) + sig := fmt.Sprintf("%v(%v)", name, strings.Join(types, ",")) + id := common.BytesToHash(crypto.Keccak256([]byte(sig))) + + return Error{ + Name: name, + Inputs: inputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (e Error) String() string { + return e.str +} + +func (e *Error) Unpack(data []byte) (interface{}, error) { + if len(data) < 4 { + return "", fmt.Errorf("insufficient data for unpacking: have %d, want at least 4", len(data)) + } + if !bytes.Equal(data[:4], e.ID[:4]) { + 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/error_handling.go b/accounts/abi/error_handling.go new file mode 100644 index 0000000..c106e9a --- /dev/null +++ b/accounts/abi/error_handling.go @@ -0,0 +1,89 @@ +// 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 abi + +import ( + "errors" + "fmt" + "reflect" +) + +var ( + errBadBool = errors.New("abi: improperly encoded boolean value") + errBadUint8 = errors.New("abi: improperly encoded uint8 value") + errBadUint16 = errors.New("abi: improperly encoded uint16 value") + errBadUint32 = errors.New("abi: improperly encoded uint32 value") + errBadUint64 = errors.New("abi: improperly encoded uint64 value") + errBadInt8 = errors.New("abi: improperly encoded int8 value") + errBadInt16 = errors.New("abi: improperly encoded int16 value") + errBadInt32 = errors.New("abi: improperly encoded int32 value") + errBadInt64 = errors.New("abi: improperly encoded int64 value") +) + +// formatSliceString formats the reflection kind with the given slice size +// and returns a formatted string representation. +func formatSliceString(kind reflect.Kind, sliceSize int) string { + if sliceSize == -1 { + return fmt.Sprintf("[]%v", kind) + } + return fmt.Sprintf("[%d]%v", sliceSize, kind) +} + +// sliceTypeCheck checks that the given slice can by assigned to the reflection +// type in t. +func sliceTypeCheck(t Type, val reflect.Value) error { + if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { + return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type()) + } + + if t.T == ArrayTy && val.Len() != t.Size { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) + } + + if t.Elem.T == SliceTy || t.Elem.T == ArrayTy { + if val.Len() > 0 { + return sliceTypeCheck(*t.Elem, val.Index(0)) + } + } + + if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) + } + return nil +} + +// typeCheck checks that the given reflection value can be assigned to the reflection +// type in t. +func typeCheck(t Type, value reflect.Value) error { + if t.T == SliceTy || t.T == ArrayTy { + return sliceTypeCheck(t, value) + } + + // Check base type validity. Element types will be checked later on. + if t.GetType().Kind() != value.Kind() { + return typeErr(t.GetType().Kind(), value.Kind()) + } else if t.T == FixedBytesTy && t.Size != value.Len() { + return typeErr(t.GetType(), value.Type()) + } else { + return nil + } +} + +// typeErr returns a formatted type casting error. +func typeErr(expected, got interface{}) error { + return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected) +} diff --git a/accounts/abi/event.go b/accounts/abi/event.go new file mode 100644 index 0000000..f9457b8 --- /dev/null +++ b/accounts/abi/event.go @@ -0,0 +1,103 @@ +// 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 abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// Event is an event potentially triggered by the EVM's LOG mechanism. The Event +// holds type information (inputs) about the yielded output. Anonymous events +// don't get the signature canonical representation as the first LOG topic. +type Event struct { + // Name is the event name used for internal representation. It's derived from + // the raw name and a suffix will be added in the case of event overloading. + // + // e.g. + // These are two events that have the same name: + // * foo(int,int) + // * foo(uint,uint) + // The event name of the first one will be resolved as foo while the second one + // will be resolved as foo0. + Name string + + // RawName is the raw event name parsed from ABI. + RawName string + Anonymous bool + Inputs Arguments + str string + + // Sig contains the string signature according to the ABI spec. + // e.g. event foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + + // ID returns the canonical representation of the event's signature used by the + // abi definition to identify event names and types. + ID common.Hash +} + +// NewEvent creates a new Event. +// It sanitizes the input arguments to remove unnamed arguments. +// It also precomputes the id, signature and string representation +// of the event. +func NewEvent(name, rawName string, anonymous bool, inputs Arguments) Event { + // sanitize inputs to remove inputs without names + // and precompute string and sig representation. + names := make([]string, len(inputs)) + types := make([]string, len(inputs)) + for i, input := range inputs { + if input.Name == "" { + inputs[i] = Argument{ + Name: fmt.Sprintf("arg%d", i), + Indexed: input.Indexed, + Type: input.Type, + } + } else { + inputs[i] = input + } + // string representation + names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name) + if input.Indexed { + names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name) + } + // sig representation + types[i] = input.Type.String() + } + + str := fmt.Sprintf("event %v(%v)", rawName, strings.Join(names, ", ")) + sig := fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) + id := common.BytesToHash(crypto.Keccak256([]byte(sig))) + + return Event{ + Name: name, + RawName: rawName, + Anonymous: anonymous, + Inputs: inputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (e Event) String() string { + return e.str +} diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go new file mode 100644 index 0000000..fffe28e --- /dev/null +++ b/accounts/abi/event_test.go @@ -0,0 +1,395 @@ +// 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 abi + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "math/big" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var jsonEventTransfer = []byte(`{ + "anonymous": false, + "inputs": [ + { + "indexed": true, "name": "from", "type": "address" + }, { + "indexed": true, "name": "to", "type": "address" + }, { + "indexed": false, "name": "value", "type": "uint256" + }], + "name": "Transfer", + "type": "event" +}`) + +var jsonEventPledge = []byte(`{ + "anonymous": false, + "inputs": [{ + "indexed": false, "name": "who", "type": "address" + }, { + "indexed": false, "name": "wad", "type": "uint128" + }, { + "indexed": false, "name": "currency", "type": "bytes3" + }], + "name": "Pledge", + "type": "event" +}`) + +var jsonEventMixedCase = []byte(`{ + "anonymous": false, + "inputs": [{ + "indexed": false, "name": "value", "type": "uint256" + }, { + "indexed": false, "name": "_value", "type": "uint256" + }, { + "indexed": false, "name": "Value", "type": "uint256" + }], + "name": "MixedCase", + "type": "event" + }`) + +// 1000000 +var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240" + +// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd" +var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000" + +// 1000000,2218516807680,1000001 +var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241" + +func TestEventId(t *testing.T) { + t.Parallel() + var table = []struct { + definition string + expectations map[string]common.Hash + }{ + { + definition: `[ + { "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] }, + { "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] } + ]`, + expectations: map[string]common.Hash{ + "Balance": crypto.Keccak256Hash([]byte("Balance(uint256)")), + "Check": crypto.Keccak256Hash([]byte("Check(address,uint256)")), + }, + }, + } + + for _, test := range table { + abi, err := JSON(strings.NewReader(test.definition)) + if err != nil { + t.Fatal(err) + } + + for name, event := range abi.Events { + if event.ID != test.expectations[name] { + t.Errorf("expected id to be %x, got %x", test.expectations[name], event.ID) + } + } + } +} + +func TestEventString(t *testing.T) { + t.Parallel() + var table = []struct { + definition string + expectations map[string]string + }{ + { + definition: `[ + { "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] }, + { "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }, + { "type" : "event", "name" : "Transfer", "inputs": [{ "name": "from", "type": "address", "indexed": true }, { "name": "to", "type": "address", "indexed": true }, { "name": "value", "type": "uint256" }] } + ]`, + expectations: map[string]string{ + "Balance": "event Balance(uint256 in)", + "Check": "event Check(address t, uint256 b)", + "Transfer": "event Transfer(address indexed from, address indexed to, uint256 value)", + }, + }, + } + + for _, test := range table { + abi, err := JSON(strings.NewReader(test.definition)) + if err != nil { + t.Fatal(err) + } + + for name, event := range abi.Events { + if event.String() != test.expectations[name] { + t.Errorf("expected string to be %s, got %s", test.expectations[name], event.String()) + } + } + } +} + +// 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) + var b bytes.Buffer + var i uint8 = 1 + for ; i <= 3; i++ { + b.Write(packNum(reflect.ValueOf(i))) + } + unpacked, err := abi.Unpack("test", b.Bytes()) + require.NoError(t, err) + require.Equal(t, [2]uint8{1, 2}, unpacked[0]) + require.Equal(t, uint8(3), unpacked[1]) +} + +func TestEventTupleUnpack(t *testing.T) { + t.Parallel() + type EventTransfer struct { + Value *big.Int + } + + type EventTransferWithTag struct { + // this is valid because `value` is not exportable, + // so value is only unmarshalled into `Value1`. + value *big.Int //lint:ignore U1000 unused field is part of test + Value1 *big.Int `abi:"value"` + } + + type BadEventTransferWithSameFieldAndTag struct { + Value *big.Int + Value1 *big.Int `abi:"value"` + } + + type BadEventTransferWithDuplicatedTag struct { + Value1 *big.Int `abi:"value"` + Value2 *big.Int `abi:"value"` + } + + type BadEventTransferWithEmptyTag struct { + Value *big.Int `abi:""` + } + + type EventPledge struct { + Who common.Address + Wad *big.Int + Currency [3]byte + } + + type BadEventPledge struct { + Who string + Wad int + Currency [3]byte + } + + type EventMixedCase struct { + Value1 *big.Int `abi:"value"` + Value2 *big.Int `abi:"_value"` + Value3 *big.Int `abi:"Value"` + } + + bigint := new(big.Int) + bigintExpected := big.NewInt(1000000) + bigintExpected2 := big.NewInt(2218516807680) + bigintExpected3 := big.NewInt(1000001) + addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268") + var testCases = []struct { + data string + dest interface{} + expected interface{} + jsonLog []byte + error string + name string + }{{ + transferData1, + &EventTransfer{}, + &EventTransfer{Value: bigintExpected}, + jsonEventTransfer, + "", + "Can unpack ERC20 Transfer event into structure", + }, { + transferData1, + &[]interface{}{&bigint}, + &[]interface{}{&bigintExpected}, + jsonEventTransfer, + "", + "Can unpack ERC20 Transfer event into slice", + }, { + transferData1, + &EventTransferWithTag{}, + &EventTransferWithTag{Value1: bigintExpected}, + jsonEventTransfer, + "", + "Can unpack ERC20 Transfer event into structure with abi: tag", + }, { + transferData1, + &BadEventTransferWithDuplicatedTag{}, + &BadEventTransferWithDuplicatedTag{}, + jsonEventTransfer, + "struct: abi tag in 'Value2' already mapped", + "Can not unpack ERC20 Transfer event with duplicated abi tag", + }, { + transferData1, + &BadEventTransferWithSameFieldAndTag{}, + &BadEventTransferWithSameFieldAndTag{}, + jsonEventTransfer, + "abi: multiple variables maps to the same abi field 'value'", + "Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable", + }, { + transferData1, + &BadEventTransferWithEmptyTag{}, + &BadEventTransferWithEmptyTag{}, + jsonEventTransfer, + "struct: abi tag in 'Value' is empty", + "Can not unpack ERC20 Transfer event with an empty tag", + }, { + pledgeData1, + &EventPledge{}, + &EventPledge{ + addr, + bigintExpected2, + [3]byte{'u', 's', 'd'}}, + jsonEventPledge, + "", + "Can unpack Pledge event into structure", + }, { + pledgeData1, + &[]interface{}{&common.Address{}, &bigint, &[3]byte{}}, + &[]interface{}{ + &addr, + &bigintExpected2, + &[3]byte{'u', 's', 'd'}}, + jsonEventPledge, + "", + "Can unpack Pledge event into slice", + }, { + pledgeData1, + &[3]interface{}{&common.Address{}, &bigint, &[3]byte{}}, + &[3]interface{}{ + &addr, + &bigintExpected2, + &[3]byte{'u', 's', 'd'}}, + jsonEventPledge, + "", + "Can unpack Pledge event into an array", + }, { + pledgeData1, + &[]interface{}{new(int), 0, 0}, + &[]interface{}{}, + jsonEventPledge, + "abi: cannot unmarshal common.Address in to int", + "Can not unpack Pledge event into slice with wrong types", + }, { + pledgeData1, + &BadEventPledge{}, + &BadEventPledge{}, + jsonEventPledge, + "abi: cannot unmarshal common.Address in to string", + "Can not unpack Pledge event into struct with wrong filed types", + }, { + pledgeData1, + &[]interface{}{common.Address{}, new(big.Int)}, + &[]interface{}{}, + jsonEventPledge, + "abi: insufficient number of arguments for unpack, want 3, got 2", + "Can not unpack Pledge event into too short slice", + }, { + pledgeData1, + new(map[string]interface{}), + &[]interface{}{}, + jsonEventPledge, + "abi:[2] cannot unmarshal tuple in to map[string]interface {}", + "Can not unpack Pledge event into map", + }, { + mixedCaseData1, + &EventMixedCase{}, + &EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3}, + jsonEventMixedCase, + "", + "Can unpack abi variables with mixed case", + }} + + for _, tc := range testCases { + assert := assert.New(t) + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert) + if tc.error == "" { + assert.Nil(err, "Should be able to unpack event data.") + assert.Equal(tc.expected, tc.dest, tc.name) + } else { + assert.EqualError(err, tc.error, tc.name) + } + }) + } +} + +func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error { + data, err := hex.DecodeString(hexData) + assert.NoError(err, "Hex data should be a correct hex-string") + var e Event + assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI") + a := ABI{Events: map[string]Event{"e": e}} + return a.UnpackIntoInterface(dest, "e", data) +} + +// 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 + Value2 uint8 + } + abi, err := JSON(strings.NewReader(definition)) + require.NoError(t, err) + var b bytes.Buffer + b.Write(packNum(reflect.ValueOf(uint8(8)))) + var rst testStruct + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) + require.Equal(t, uint8(0), rst.Value1) + require.Equal(t, uint8(8), rst.Value2) +} + +// 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 + Value2 string + } + abi, err := JSON(strings.NewReader(definition)) + require.NoError(t, err) + var b bytes.Buffer + stringOut := "abc" + // number of fields that will be encoded * 32 + b.Write(packNum(reflect.ValueOf(32))) + b.Write(packNum(reflect.ValueOf(len(stringOut)))) + b.Write(common.RightPadBytes([]byte(stringOut), 32)) + + var rst testStruct + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) + require.Equal(t, [2]uint8{0, 0}, rst.Value1) + require.Equal(t, stringOut, rst.Value2) +} diff --git a/accounts/abi/method.go b/accounts/abi/method.go new file mode 100644 index 0000000..c5a1a71 --- /dev/null +++ b/accounts/abi/method.go @@ -0,0 +1,166 @@ +// 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 abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/crypto" +) + +// FunctionType represents different types of functions a contract might have. +type FunctionType int + +const ( + // Constructor represents the constructor of the contract. + // The constructor function is called while deploying a contract. + Constructor FunctionType = iota + // Fallback represents the fallback function. + // This function is executed if no other function matches the given function + // signature and no receive function is specified. + Fallback + // Receive represents the receive function. + // This function is executed on plain Ether transfers. + Receive + // Function represents a normal function. + Function +) + +// Method represents a callable given a `Name` and whether the method is a constant. +// If the method is `Const` no transaction needs to be created for this +// particular Method call. It can easily be simulated using a local VM. +// For example a `Balance()` method only needs to retrieve something +// from the storage and therefore requires no Tx to be sent to the +// network. A method such as `Transact` does require a Tx and thus will +// be flagged `false`. +// Input specifies the required input parameters for this gives method. +type Method struct { + // Name is the method name used for internal representation. It's derived from + // the raw name and a suffix will be added in the case of a function overload. + // + // e.g. + // These are two functions that have the same name: + // * foo(int,int) + // * foo(uint,uint) + // The method name of the first one will be resolved as foo while the second one + // will be resolved as foo0. + Name string + RawName string // RawName is the raw method name parsed from ABI + + // Type indicates whether the method is a + // special fallback introduced in solidity v0.6.0 + Type FunctionType + + // StateMutability indicates the mutability state of method, + // the default value is nonpayable. It can be empty if the abi + // is generated by legacy compiler. + StateMutability string + + // Legacy indicators generated by compiler before v0.6.0 + Constant bool + Payable bool + + Inputs Arguments + Outputs Arguments + str string + // Sig returns the methods string signature according to the ABI spec. + // e.g. function foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + // ID returns the canonical representation of the method's signature used by the + // abi definition to identify method names and types. + ID []byte +} + +// NewMethod creates a new Method. +// A method should always be created using NewMethod. +// It also precomputes the sig representation and the string representation +// of the method. +func NewMethod(name string, rawName string, funType FunctionType, mutability string, isConst, isPayable bool, inputs Arguments, outputs Arguments) Method { + var ( + types = make([]string, len(inputs)) + inputNames = make([]string, len(inputs)) + outputNames = make([]string, len(outputs)) + ) + for i, input := range inputs { + inputNames[i] = fmt.Sprintf("%v %v", input.Type, input.Name) + types[i] = input.Type.String() + } + for i, output := range outputs { + outputNames[i] = output.Type.String() + if len(output.Name) > 0 { + outputNames[i] += fmt.Sprintf(" %v", output.Name) + } + } + // calculate the signature and method id. Note only function + // has meaningful signature and id. + var ( + sig string + id []byte + ) + if funType == Function { + sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) + id = crypto.Keccak256([]byte(sig))[:4] + } + identity := fmt.Sprintf("function %v", rawName) + switch funType { + case Fallback: + identity = "fallback" + case Receive: + identity = "receive" + case Constructor: + identity = "constructor" + } + 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, + RawName: rawName, + Type: funType, + StateMutability: mutability, + Constant: isConst, + Payable: isPayable, + Inputs: inputs, + Outputs: outputs, + str: str, + Sig: sig, + ID: id, + } +} + +func (method Method) String() string { + return method.str +} + +// IsConstant returns the indicator whether the method is read-only. +func (method Method) IsConstant() bool { + return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant +} + +// IsPayable returns the indicator whether the method can process +// plain ether transfers. +func (method Method) IsPayable() bool { + return method.StateMutability == "payable" || method.Payable +} diff --git a/accounts/abi/method_test.go b/accounts/abi/method_test.go new file mode 100644 index 0000000..6322173 --- /dev/null +++ b/accounts/abi/method_test.go @@ -0,0 +1,148 @@ +// 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 abi + +import ( + "strings" + "testing" +) + +const methoddata = ` +[ + {"type": "function", "name": "balance", "stateMutability": "view"}, + {"type": "function", "name": "send", "inputs": [{ "name": "amount", "type": "uint256" }]}, + {"type": "function", "name": "transfer", "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]}, + {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple"}],"name":"tuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[]"}],"name":"tupleSlice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5]"}],"name":"tupleArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"stateMutability":"nonpayable","type":"fallback"}, + {"stateMutability":"payable","type":"receive"} +]` + +func TestMethodString(t *testing.T) { + t.Parallel() + var table = []struct { + method string + expectation string + }{ + { + method: "balance", + expectation: "function balance() view returns()", + }, + { + method: "send", + expectation: "function send(uint256 amount) returns()", + }, + { + method: "transfer", + expectation: "function transfer(address from, address to, uint256 value) returns(bool success)", + }, + { + method: "tuple", + expectation: "function tuple((uint256,uint256) a) returns()", + }, + { + method: "tupleArray", + expectation: "function tupleArray((uint256,uint256)[5] a) returns()", + }, + { + method: "tupleSlice", + expectation: "function tupleSlice((uint256,uint256)[] a) returns()", + }, + { + method: "complexTuple", + expectation: "function complexTuple((uint256,uint256)[5][] a) returns()", + }, + { + method: "fallback", + expectation: "fallback() returns()", + }, + { + method: "receive", + expectation: "receive() payable returns()", + }, + } + + abi, err := JSON(strings.NewReader(methoddata)) + if err != nil { + t.Fatal(err) + } + + for _, test := range table { + var got string + switch test.method { + case "fallback": + got = abi.Fallback.String() + case "receive": + got = abi.Receive.String() + default: + got = abi.Methods[test.method].String() + } + if got != test.expectation { + t.Errorf("expected string to be %s, got %s", test.expectation, got) + } + } +} + +func TestMethodSig(t *testing.T) { + t.Parallel() + var cases = []struct { + method string + expect string + }{ + { + method: "balance", + expect: "balance()", + }, + { + method: "send", + expect: "send(uint256)", + }, + { + method: "transfer", + expect: "transfer(address,address,uint256)", + }, + { + method: "tuple", + expect: "tuple((uint256,uint256))", + }, + { + method: "tupleArray", + expect: "tupleArray((uint256,uint256)[5])", + }, + { + method: "tupleSlice", + expect: "tupleSlice((uint256,uint256)[])", + }, + { + method: "complexTuple", + expect: "complexTuple((uint256,uint256)[5][])", + }, + } + abi, err := JSON(strings.NewReader(methoddata)) + if err != nil { + t.Fatal(err) + } + + for _, test := range cases { + got := abi.Methods[test.method].Sig + if got != test.expect { + t.Errorf("expected string to be %s, got %s", test.expect, got) + } + } +} diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go new file mode 100644 index 0000000..beef1fa --- /dev/null +++ b/accounts/abi/pack.go @@ -0,0 +1,85 @@ +// 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 abi + +import ( + "errors" + "fmt" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +// packBytesSlice packs the given bytes as [L, V] as the canonical representation +// bytes slice. +func packBytesSlice(bytes []byte, l int) []byte { + len := packNum(reflect.ValueOf(l)) + return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...) +} + +// packElement packs the given reflect value according to the abi specification in +// t. +func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { + switch t.T { + case IntTy, UintTy: + return packNum(reflectValue), nil + case StringTy: + return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()), nil + case AddressTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + + return common.LeftPadBytes(reflectValue.Bytes(), 32), nil + case BoolTy: + if reflectValue.Bool() { + return math.PaddedBigBytes(common.Big1, 32), nil + } + return math.PaddedBigBytes(common.Big0, 32), nil + case BytesTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + if reflectValue.Type() != reflect.TypeOf([]byte{}) { + return []byte{}, errors.New("bytes type is neither slice nor array") + } + return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil + case FixedBytesTy, FunctionTy: + if reflectValue.Kind() == reflect.Array { + reflectValue = mustArrayToByteSlice(reflectValue) + } + return common.RightPadBytes(reflectValue.Bytes(), 32), nil + default: + return []byte{}, fmt.Errorf("could not pack element, unknown type: %v", t.T) + } +} + +// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation. +func packNum(value reflect.Value) []byte { + switch kind := value.Kind(); kind { + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return math.U256Bytes(new(big.Int).SetUint64(value.Uint())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return math.U256Bytes(big.NewInt(value.Int())) + case reflect.Ptr: + return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int))) + default: + panic("abi: fatal error") + } +} diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go new file mode 100644 index 0000000..00bdae4 --- /dev/null +++ b/accounts/abi/pack_test.go @@ -0,0 +1,216 @@ +// 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 abi + +import ( + "bytes" + "encoding/hex" + "fmt" + "math" + "math/big" + "reflect" + "strconv" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +// 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) + } + inDef := fmt.Sprintf(`[{ "name" : "method", "type": "function", "inputs": %s}]`, test.def) + inAbi, err := JSON(strings.NewReader(inDef)) + if err != nil { + t.Fatalf("invalid ABI definition %s, %v", inDef, err) + } + var packed []byte + packed, err = inAbi.Pack("method", test.unpacked) + + if err != nil { + t.Fatalf("test %d (%v) failed: %v", i, test.def, err) + } + if !reflect.DeepEqual(packed[4:], encb) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, encb, packed[4:]) + } + }) + } +} + +func TestMethodPack(t *testing.T) { + t.Parallel() + abi, err := JSON(strings.NewReader(jsondata)) + if err != nil { + t.Fatal(err) + } + + sig := abi.Methods["slice"].ID + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + + packed, err := abi.Pack("slice", []uint32{1, 2}) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + var addrA, addrB = common.Address{1}, common.Address{2} + sig = abi.Methods["sliceAddress"].ID + sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrA[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrB[:], 32)...) + + packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + var addrC, addrD = common.Address{3}, common.Address{4} + sig = abi.Methods["sliceMultiAddress"].ID + sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrA[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrB[:], 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrC[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrD[:], 32)...) + + packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + sig = abi.Methods["slice256"].ID + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + + packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)}) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}} + sig = abi.Methods["nestedArray"].ID + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes(addrC[:], 32)...) + sig = append(sig, common.LeftPadBytes(addrD[:], 32)...) + packed, err = abi.Pack("nestedArray", a, []common.Address{addrC, addrD}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + sig = abi.Methods["nestedArray2"].ID + sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + packed, err = abi.Pack("nestedArray2", [2][]uint8{{1}, {1}}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } + + sig = abi.Methods["nestedSlice"].ID + sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) + sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) + packed, err = abi.Pack("nestedSlice", [][]uint8{{1, 2}, {1, 2}}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(packed, sig) { + t.Errorf("expected %x got %x", sig, packed) + } +} + +func TestPackNumber(t *testing.T) { + t.Parallel() + tests := []struct { + value reflect.Value + packed []byte + }{ + // Protocol limits + {reflect.ValueOf(0), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")}, + {reflect.ValueOf(1), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")}, + {reflect.ValueOf(-1), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, + + // Type corner cases + {reflect.ValueOf(uint8(math.MaxUint8)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000ff")}, + {reflect.ValueOf(uint16(math.MaxUint16)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000ffff")}, + {reflect.ValueOf(uint32(math.MaxUint32)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000ffffffff")}, + {reflect.ValueOf(uint64(math.MaxUint64)), common.Hex2Bytes("000000000000000000000000000000000000000000000000ffffffffffffffff")}, + + {reflect.ValueOf(int8(math.MaxInt8)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000007f")}, + {reflect.ValueOf(int16(math.MaxInt16)), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000007fff")}, + {reflect.ValueOf(int32(math.MaxInt32)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000007fffffff")}, + {reflect.ValueOf(int64(math.MaxInt64)), common.Hex2Bytes("0000000000000000000000000000000000000000000000007fffffffffffffff")}, + + {reflect.ValueOf(int8(math.MinInt8)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80")}, + {reflect.ValueOf(int16(math.MinInt16)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000")}, + {reflect.ValueOf(int32(math.MinInt32)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000")}, + {reflect.ValueOf(int64(math.MinInt64)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")}, + } + for i, tt := range tests { + packed := packNum(tt.value) + if !bytes.Equal(packed, tt.packed) { + t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed) + } + } +} diff --git a/accounts/abi/packing_test.go b/accounts/abi/packing_test.go new file mode 100644 index 0000000..eae3b0d --- /dev/null +++ b/accounts/abi/packing_test.go @@ -0,0 +1,990 @@ +// 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 ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +type packUnpackTest struct { + def string + unpacked interface{} + packed string +} + +var packUnpackTests = []packUnpackTest{ + // Booleans + { + def: `[{ "type": "bool" }]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: true, + }, + { + def: `[{ "type": "bool" }]`, + packed: "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: false, + }, + // Integers + { + def: `[{ "type": "uint8" }]`, + unpacked: uint8(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{ "type": "uint8[]" }]`, + unpacked: []uint8{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{ "type": "uint16" }]`, + unpacked: uint16(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{ "type": "uint16[]" }]`, + unpacked: []uint16{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint17"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: big.NewInt(1), + }, + { + def: `[{"type": "uint32"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: uint32(1), + }, + { + def: `[{"type": "uint32[]"}]`, + unpacked: []uint32{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint64"}]`, + unpacked: uint64(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint64[]"}]`, + unpacked: []uint64{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint256"}]`, + unpacked: big.NewInt(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint256[]"}]`, + unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int8"}]`, + unpacked: int8(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int8[]"}]`, + unpacked: []int8{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int16"}]`, + unpacked: int16(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int16[]"}]`, + unpacked: []int16{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int17"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: big.NewInt(1), + }, + { + def: `[{"type": "int32"}]`, + unpacked: int32(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int32"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: int32(1), + }, + { + def: `[{"type": "int32[]"}]`, + unpacked: []int32{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int64"}]`, + unpacked: int64(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int64[]"}]`, + unpacked: []int64{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int256"}]`, + unpacked: big.NewInt(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int256"}]`, + packed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + unpacked: big.NewInt(-1), + }, + { + def: `[{"type": "int256[]"}]`, + unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + // Address + { + def: `[{"type": "address"}]`, + packed: "0000000000000000000000000100000000000000000000000000000000000000", + unpacked: common.Address{1}, + }, + { + def: `[{"type": "address[]"}]`, + unpacked: []common.Address{{1}, {2}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000100000000000000000000000000000000000000" + + "0000000000000000000000000200000000000000000000000000000000000000", + }, + // Bytes + { + def: `[{"type": "bytes1"}]`, + unpacked: [1]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes2"}]`, + unpacked: [2]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes3"}]`, + unpacked: [3]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes4"}]`, + unpacked: [4]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes5"}]`, + unpacked: [5]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes6"}]`, + unpacked: [6]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes7"}]`, + unpacked: [7]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes8"}]`, + unpacked: [8]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes9"}]`, + unpacked: [9]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes10"}]`, + unpacked: [10]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes11"}]`, + unpacked: [11]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes12"}]`, + unpacked: [12]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes13"}]`, + unpacked: [13]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes14"}]`, + unpacked: [14]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes15"}]`, + unpacked: [15]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes16"}]`, + unpacked: [16]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes17"}]`, + unpacked: [17]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes18"}]`, + unpacked: [18]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes19"}]`, + unpacked: [19]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes20"}]`, + unpacked: [20]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes21"}]`, + unpacked: [21]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes22"}]`, + unpacked: [22]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes23"}]`, + unpacked: [23]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes24"}]`, + unpacked: [24]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes25"}]`, + unpacked: [25]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes26"}]`, + unpacked: [26]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes27"}]`, + unpacked: [27]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes28"}]`, + unpacked: [28]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes29"}]`, + unpacked: [29]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes30"}]`, + unpacked: [30]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes31"}]`, + unpacked: [31]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes32"}]`, + unpacked: [32]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes32"}]`, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + unpacked: [32]byte{1, 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}, + }, + { + def: `[{"type": "bytes"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0100000000000000000000000000000000000000000000000000000000000000", + unpacked: common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + def: `[{"type": "bytes32"}]`, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + unpacked: [32]byte{1, 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}, + }, + // Functions + { + def: `[{"type": "function"}]`, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + unpacked: [24]byte{1}, + }, + // Slice and Array + { + def: `[{"type": "uint8[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []uint8{1, 2}, + }, + { + def: `[{"type": "uint8[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: []uint8{}, + }, + { + def: `[{"type": "uint256[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: []*big.Int{}, + }, + { + def: `[{"type": "uint8[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]uint8{1, 2}, + }, + { + def: `[{"type": "int8[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]int8{1, 2}, + }, + { + def: `[{"type": "int16[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []int16{1, 2}, + }, + { + def: `[{"type": "int16[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]int16{1, 2}, + }, + { + def: `[{"type": "int32[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []int32{1, 2}, + }, + { + def: `[{"type": "int32[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]int32{1, 2}, + }, + { + def: `[{"type": "int64[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []int64{1, 2}, + }, + { + def: `[{"type": "int64[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]int64{1, 2}, + }, + { + def: `[{"type": "int256[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "int256[3]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003", + unpacked: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + // multi dimensional, if these pass, all types that don't require length prefix should pass + { + def: `[{"type": "uint8[][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: [][]uint8{}, + }, + { + def: `[{"type": "uint8[][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [][]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint8[][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003", + unpacked: [][]uint8{{1, 2}, {1, 2, 3}}, + }, + { + def: `[{"type": "uint8[2][2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2][2]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint8[][2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: [2][]uint8{{}, {}}, + }, + { + def: `[{"type": "uint8[][2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: [2][]uint8{{1}, {1}}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: [][2]uint8{}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [][2]uint8{{1, 2}}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [][2]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint16[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []uint16{1, 2}, + }, + { + def: `[{"type": "uint16[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]uint16{1, 2}, + }, + { + def: `[{"type": "uint32[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []uint32{1, 2}, + }, + { + def: `[{"type": "uint32[2][3][4]"}]`, + unpacked: [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "0000000000000000000000000000000000000000000000000000000000000007" + + "0000000000000000000000000000000000000000000000000000000000000008" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "000000000000000000000000000000000000000000000000000000000000000b" + + "000000000000000000000000000000000000000000000000000000000000000c" + + "000000000000000000000000000000000000000000000000000000000000000d" + + "000000000000000000000000000000000000000000000000000000000000000e" + + "000000000000000000000000000000000000000000000000000000000000000f" + + "0000000000000000000000000000000000000000000000000000000000000010" + + "0000000000000000000000000000000000000000000000000000000000000011" + + "0000000000000000000000000000000000000000000000000000000000000012" + + "0000000000000000000000000000000000000000000000000000000000000013" + + "0000000000000000000000000000000000000000000000000000000000000014" + + "0000000000000000000000000000000000000000000000000000000000000015" + + "0000000000000000000000000000000000000000000000000000000000000016" + + "0000000000000000000000000000000000000000000000000000000000000017" + + "0000000000000000000000000000000000000000000000000000000000000018", + }, + + { + def: `[{"type": "bytes32[]"}]`, + unpacked: [][32]byte{{1}, {2}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0100000000000000000000000000000000000000000000000000000000000000" + + "0200000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "uint32[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]uint32{1, 2}, + }, + { + def: `[{"type": "uint64[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []uint64{1, 2}, + }, + { + def: `[{"type": "uint64[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]uint64{1, 2}, + }, + { + def: `[{"type": "uint256[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "uint256[3]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003", + unpacked: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + { + def: `[{"type": "string[4]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "00000000000000000000000000000000000000000000000000000000000000c0" + + "0000000000000000000000000000000000000000000000000000000000000100" + + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "48656c6c6f000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "576f726c64000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000b" + + "476f2d657468657265756d000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000008" + + "457468657265756d000000000000000000000000000000000000000000000000", + unpacked: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"}, + }, + { + def: `[{"type": "string[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000008" + + "457468657265756d000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000b" + + "676f2d657468657265756d000000000000000000000000000000000000000000", + unpacked: []string{"Ethereum", "go-ethereum"}, + }, + { + def: `[{"type": "bytes[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "f0f0f00000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "f0f0f00000000000000000000000000000000000000000000000000000000000", + unpacked: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}}, + }, + { + def: `[{"type": "uint256[2][][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000e0" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "00000000000000000000000000000000000000000000000000000000000000c8" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "00000000000000000000000000000000000000000000000000000000000003e8" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "00000000000000000000000000000000000000000000000000000000000000c8" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "00000000000000000000000000000000000000000000000000000000000003e8", + unpacked: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}}, + }, + // struct outputs + { + def: `[{"components": [{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: struct { + Int1 *big.Int + Int2 *big.Int + }{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"components": [{"name":"int_one","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"components": [{"name":"int__one","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"components": [{"name":"int_one_","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"components": [{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: struct { + IntOne *big.Int + Intone *big.Int + }{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "string"}]`, + unpacked: "foobar", + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "666f6f6261720000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "string[]"}]`, + unpacked: []string{"hello", "foobar"}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2 + "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5 + "68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0] + "0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6 + "666f6f6261720000000000000000000000000000000000000000000000000000", // str[1] + }, + { + def: `[{"type": "string[2]"}]`, + unpacked: [2]string{"hello", "foobar"}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0 + "0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1 + "0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5 + "68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0] + "0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6 + "666f6f6261720000000000000000000000000000000000000000000000000000", // str[1] + }, + { + def: `[{"type": "bytes32[][]"}]`, + unpacked: [][][32]byte{{{1}, {2}}, {{3}, {4}, {5}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2 + "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2 + "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3 + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000", // array[1][2] + }, + { + def: `[{"type": "bytes32[][2]"}]`, + unpacked: [2][][32]byte{{{1}, {2}}, {{3}, {4}, {5}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2 + "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3 + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000", // array[1][2] + }, + { + def: `[{"type": "bytes32[3][2]"}]`, + unpacked: [2][3][32]byte{{{1}, {2}, {3}}, {{3}, {4}, {5}}}, + packed: "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2] + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000", // array[1][2] + }, + { + // static tuple + def: `[{"components": [{"name":"a","type":"int64"}, + {"name":"b","type":"int256"}, + {"name":"c","type":"int256"}, + {"name":"d","type":"bool"}, + {"name":"e","type":"bytes32[3][2]"}], "type":"tuple"}]`, + unpacked: struct { + A int64 + B *big.Int + C *big.Int + D bool + E [2][3][32]byte + }{1, big.NewInt(1), big.NewInt(-1), true, [2][3][32]byte{{{1}, {2}, {3}}, {{3}, {4}, {5}}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + // struct[a] + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c] + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[d] + "0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1] + "0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2] + "0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000", // struct[e] array[1][2] + }, + { + def: `[{"components": [{"name":"a","type":"string"}, + {"name":"b","type":"int64"}, + {"name":"c","type":"bytes"}, + {"name":"d","type":"string[]"}, + {"name":"e","type":"int256[]"}, + {"name":"f","type":"address[]"}], "type":"tuple"}]`, + unpacked: struct { + A string + B int64 + C []byte + D []string + E []*big.Int + F []common.Address + }{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a + "00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] + "0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset + "0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset + "0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset + "0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset + "0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length + "666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar" + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length + "0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1} + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length + "0000000000000000000000000000000000000000000000000000000000000040" + // foo offset + "0000000000000000000000000000000000000000000000000000000000000080" + // bar offset + "0000000000000000000000000000000000000000000000000000000000000003" + // foo length + "666f6f0000000000000000000000000000000000000000000000000000000000" + // foo + "0000000000000000000000000000000000000000000000000000000000000003" + // bar offset + "6261720000000000000000000000000000000000000000000000000000000000" + // bar + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length + "0000000000000000000000000000000000000000000000000000000000000001" + // 1 + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1 + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length + "0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1} + "0000000000000000000000000200000000000000000000000000000000000000", // common.Address{2} + }, + { + def: `[{"components": [{ "type": "tuple","components": [{"name": "a","type": "uint256"}, + {"name": "b","type": "uint256[]"}], + "name": "a","type": "tuple"}, + {"name": "b","type": "uint256[]"}], "type": "tuple"}]`, + unpacked: struct { + A struct { + A *big.Int + B []*big.Int + } + B []*big.Int + }{ + A: struct { + A *big.Int + B []*big.Int + }{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(2)}}, + B: []*big.Int{big.NewInt(1), big.NewInt(2)}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a + "0000000000000000000000000000000000000000000000000000000000000040" + // a offset + "00000000000000000000000000000000000000000000000000000000000000e0" + // b offset + "0000000000000000000000000000000000000000000000000000000000000001" + // a.a value + "0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset + "0000000000000000000000000000000000000000000000000000000000000002" + // a.b length + "0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value + "0000000000000000000000000000000000000000000000000000000000000002" + // a.b[1] value + "0000000000000000000000000000000000000000000000000000000000000002" + // b length + "0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value + "0000000000000000000000000000000000000000000000000000000000000002", // b[1] value + }, + + { + def: `[{"components": [{"name": "a","type": "int256"}, + {"name": "b","type": "int256[]"}], + "name": "a","type": "tuple[]"}]`, + unpacked: []struct { + A *big.Int + B []*big.Int + }{ + {big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(3)}}, + {big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}}, + }, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple length + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset + "00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value + "0000000000000000000000000000000000000000000000000000000000000003" + // tuple[0].B[1] value + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].B[1] value + }, + { + def: `[{"components": [{"name": "a","type": "int256"}, + {"name": "b","type": "int256"}], + "name": "a","type": "tuple[2]"}]`, + unpacked: [2]struct { + A *big.Int + B *big.Int + }{ + {big.NewInt(-1), big.NewInt(1)}, + {big.NewInt(1), big.NewInt(-1)}, + }, + packed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].b + }, + { + def: `[{"components": [{"name": "a","type": "int256[]"}], + "name": "a","type": "tuple[2]"}]`, + unpacked: [2]struct { + A []*big.Int + }{ + {[]*big.Int{big.NewInt(-1), big.NewInt(1)}}, + {[]*big.Int{big.NewInt(1), big.NewInt(-1)}}, + }, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset + "00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset + "0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0] + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1] + "0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0] + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].A[1] + }, +} diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go new file mode 100644 index 0000000..729ca93 --- /dev/null +++ b/accounts/abi/reflect.go @@ -0,0 +1,264 @@ +// 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 abi + +import ( + "errors" + "fmt" + "math/big" + "reflect" + "strings" +) + +// ConvertType converts an interface of a runtime type into an interface of the +// given type, e.g. turn this code: +// +// var fields []reflect.StructField +// +// fields = append(fields, reflect.StructField{ +// Name: "X", +// Type: reflect.TypeOf(new(big.Int)), +// Tag: reflect.StructTag("json:\"" + "x" + "\""), +// }) +// +// into: +// +// type TupleT struct { X *big.Int } +func ConvertType(in interface{}, proto interface{}) interface{} { + protoType := reflect.TypeOf(proto) + if reflect.TypeOf(in).ConvertibleTo(protoType) { + return reflect.ValueOf(in).Convert(protoType).Interface() + } + // Use set as a last ditch effort + if err := set(reflect.ValueOf(proto), reflect.ValueOf(in)); err != nil { + panic(err) + } + return proto +} + +// indirect recursively dereferences the value until it either gets the value +// or finds a big.Int +func indirect(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeOf(big.Int{}) { + return indirect(v.Elem()) + } + return v +} + +// reflectIntType returns the reflect using the given size and +// unsignedness. +func reflectIntType(unsigned bool, size int) reflect.Type { + if unsigned { + switch size { + case 8: + return reflect.TypeOf(uint8(0)) + case 16: + return reflect.TypeOf(uint16(0)) + case 32: + return reflect.TypeOf(uint32(0)) + case 64: + return reflect.TypeOf(uint64(0)) + } + } + switch size { + case 8: + return reflect.TypeOf(int8(0)) + case 16: + return reflect.TypeOf(int16(0)) + case 32: + return reflect.TypeOf(int32(0)) + case 64: + return reflect.TypeOf(int64(0)) + } + return reflect.TypeOf(&big.Int{}) +} + +// mustArrayToByteSlice creates a new byte slice with the exact same size as value +// and copies the bytes in value to the new slice. +func mustArrayToByteSlice(value reflect.Value) reflect.Value { + slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len()) + reflect.Copy(slice, value) + return slice +} + +// set attempts to assign src to dst by either setting, copying or otherwise. +// +// set is a bit more lenient when it comes to assignment and doesn't force an as +// strict ruleset as bare `reflect` does. +func set(dst, src reflect.Value) error { + dstType, srcType := dst.Type(), src.Type() + switch { + case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Ptr || dst.Elem().CanSet()): + return set(dst.Elem(), src) + case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeOf(big.Int{}): + return set(dst.Elem(), src) + case srcType.AssignableTo(dstType) && dst.CanSet(): + dst.Set(src) + case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice && dst.CanSet(): + return setSlice(dst, src) + case dstType.Kind() == reflect.Array: + return setArray(dst, src) + case dstType.Kind() == reflect.Struct: + return setStruct(dst, src) + default: + return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type()) + } + return nil +} + +// setSlice attempts to assign src to dst when slices are not assignable by default +// e.g. src: [][]byte -> dst: [][15]byte +// setSlice ignores if we cannot copy all of src' elements. +func setSlice(dst, src reflect.Value) error { + slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len()) + for i := 0; i < src.Len(); i++ { + if err := set(slice.Index(i), src.Index(i)); err != nil { + return err + } + } + if dst.CanSet() { + dst.Set(slice) + return nil + } + return errors.New("cannot set slice, destination not settable") +} + +func setArray(dst, src reflect.Value) error { + if src.Kind() == reflect.Ptr { + return set(dst, indirect(src)) + } + array := reflect.New(dst.Type()).Elem() + min := src.Len() + if src.Len() > dst.Len() { + min = dst.Len() + } + for i := 0; i < min; i++ { + if err := set(array.Index(i), src.Index(i)); err != nil { + return err + } + } + if dst.CanSet() { + dst.Set(array) + return nil + } + return errors.New("cannot set array, destination not settable") +} + +func setStruct(dst, src reflect.Value) error { + for i := 0; i < src.NumField(); i++ { + 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) + } + if err := set(dstField, srcField); err != nil { + return err + } + } + return nil +} + +// mapArgNamesToStructFields maps a slice of argument names to struct fields. +// +// first round: for each Exportable field that contains a `abi:""` tag and this field name +// exists in the given argument name list, pair them together. +// +// second round: for each argument name that has not been already linked, find what +// variable is expected to be mapped into, if it exists and has not been used, pair them. +// +// Note this function assumes the given value is a struct value. +func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) { + typ := value.Type() + + abi2struct := make(map[string]string) + struct2abi := make(map[string]string) + + // first round ~~~ + for i := 0; i < typ.NumField(); i++ { + structFieldName := typ.Field(i).Name + + // skip private struct fields. + if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) { + continue + } + // skip fields that have no abi:"" tag. + tagName, ok := typ.Field(i).Tag.Lookup("abi") + if !ok { + continue + } + // check if tag is empty. + if tagName == "" { + return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName) + } + // check which argument field matches with the abi tag. + found := false + for _, arg := range argNames { + if arg == tagName { + if abi2struct[arg] != "" { + return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName) + } + // pair them + abi2struct[arg] = structFieldName + struct2abi[structFieldName] = arg + found = true + } + } + // check if this tag has been mapped. + if !found { + return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName) + } + } + + // second round ~~~ + for _, argName := range argNames { + structFieldName := ToCamelCase(argName) + + if structFieldName == "" { + return nil, errors.New("abi: purely underscored output cannot unpack to struct") + } + + // this abi has already been paired, skip it... unless there exists another, yet unassigned + // struct field with the same field name. If so, raise an error: + // abi: [ { "name": "value" } ] + // struct { Value *big.Int , Value1 *big.Int `abi:"value"`} + if abi2struct[argName] != "" { + if abi2struct[argName] != structFieldName && + struct2abi[structFieldName] == "" && + value.FieldByName(structFieldName).IsValid() { + return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", argName) + } + continue + } + + // return an error if this struct field has already been paired. + if struct2abi[structFieldName] != "" { + return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName) + } + + if value.FieldByName(structFieldName).IsValid() { + // pair them + abi2struct[argName] = structFieldName + struct2abi[structFieldName] = argName + } else { + // not paired, but annotate as used, to detect cases like + // abi : [ { "name": "value" }, { "name": "_value" } ] + // struct { Value *big.Int } + struct2abi[structFieldName] = argName + } + } + return abi2struct, nil +} diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go new file mode 100644 index 0000000..6c7ae57 --- /dev/null +++ b/accounts/abi/reflect_test.go @@ -0,0 +1,265 @@ +// 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 abi + +import ( + "math/big" + "reflect" + "testing" +) + +type reflectTest struct { + name string + args []string + struc interface{} + want map[string]string + err string +} + +var reflectTests = []reflectTest{ + { + name: "OneToOneCorrespondence", + args: []string{"fieldA"}, + struc: struct { + FieldA int `abi:"fieldA"` + }{}, + want: map[string]string{ + "fieldA": "FieldA", + }, + }, + { + name: "MissingFieldsInStruct", + args: []string{"fieldA", "fieldB"}, + struc: struct { + FieldA int `abi:"fieldA"` + }{}, + want: map[string]string{ + "fieldA": "FieldA", + }, + }, + { + name: "MoreFieldsInStructThanArgs", + args: []string{"fieldA"}, + struc: struct { + FieldA int `abi:"fieldA"` + FieldB int + }{}, + want: map[string]string{ + "fieldA": "FieldA", + }, + }, + { + name: "MissingFieldInArgs", + args: []string{"fieldA"}, + struc: struct { + FieldA int `abi:"fieldA"` + FieldB int `abi:"fieldB"` + }{}, + err: "struct: abi tag 'fieldB' defined but not found in abi", + }, + { + name: "NoAbiDescriptor", + args: []string{"fieldA"}, + struc: struct { + FieldA int + }{}, + want: map[string]string{ + "fieldA": "FieldA", + }, + }, + { + name: "NoArgs", + args: []string{}, + struc: struct { + FieldA int `abi:"fieldA"` + }{}, + err: "struct: abi tag 'fieldA' defined but not found in abi", + }, + { + name: "DifferentName", + args: []string{"fieldB"}, + struc: struct { + FieldA int `abi:"fieldB"` + }{}, + want: map[string]string{ + "fieldB": "FieldA", + }, + }, + { + name: "DifferentName", + args: []string{"fieldB"}, + struc: struct { + FieldA int `abi:"fieldB"` + }{}, + want: map[string]string{ + "fieldB": "FieldA", + }, + }, + { + name: "MultipleFields", + args: []string{"fieldA", "fieldB"}, + struc: struct { + FieldA int `abi:"fieldA"` + FieldB int `abi:"fieldB"` + }{}, + want: map[string]string{ + "fieldA": "FieldA", + "fieldB": "FieldB", + }, + }, + { + name: "MultipleFieldsABIMissing", + args: []string{"fieldA", "fieldB"}, + struc: struct { + FieldA int `abi:"fieldA"` + FieldB int + }{}, + want: map[string]string{ + "fieldA": "FieldA", + "fieldB": "FieldB", + }, + }, + { + name: "NameConflict", + args: []string{"fieldB"}, + struc: struct { + FieldA int `abi:"fieldB"` + FieldB int + }{}, + err: "abi: multiple variables maps to the same abi field 'fieldB'", + }, + { + name: "Underscored", + args: []string{"_"}, + struc: struct { + FieldA int + }{}, + err: "abi: purely underscored output cannot unpack to struct", + }, + { + name: "DoubleMapping", + args: []string{"fieldB", "fieldC", "fieldA"}, + struc: struct { + FieldA int `abi:"fieldC"` + FieldB int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'FieldA'", + }, + { + name: "AlreadyMapped", + args: []string{"fieldB", "fieldB"}, + struc: struct { + FieldB int `abi:"fieldB"` + }{}, + err: "struct: abi tag in 'FieldB' already mapped", + }, +} + +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 { + t.Fatalf("Invalid error: expected %v, got %v", test.err, err) + } + } else { + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + for fname := range test.want { + if m[fname] != test.want[fname] { + t.Fatalf("Incorrect value for field %s: expected %v, got %v", fname, test.want[fname], m[fname]) + } + } + } + }) + } +} + +func TestConvertType(t *testing.T) { + t.Parallel() + // Test Basic Struct + type T struct { + X *big.Int + Y *big.Int + } + // Create on-the-fly structure + var fields []reflect.StructField + fields = append(fields, reflect.StructField{ + Name: "X", + Type: reflect.TypeOf(new(big.Int)), + Tag: "json:\"" + "x" + "\"", + }) + fields = append(fields, reflect.StructField{ + Name: "Y", + Type: reflect.TypeOf(new(big.Int)), + Tag: "json:\"" + "y" + "\"", + }) + val := reflect.New(reflect.StructOf(fields)) + val.Elem().Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val.Elem().Field(1).Set(reflect.ValueOf(big.NewInt(2))) + // ConvertType + out := *ConvertType(val.Interface(), new(T)).(*T) + if out.X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out.X, big.NewInt(1)) + } + if out.Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out.Y, big.NewInt(2)) + } + // Slice Type + val2 := reflect.MakeSlice(reflect.SliceOf(reflect.StructOf(fields)), 2, 2) + val2.Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val2.Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) + val2.Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) + val2.Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out2 := *ConvertType(val2.Interface(), new([]T)).(*[]T) + if out2[0].X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) + } + if out2[0].Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) + } + if out2[1].X.Cmp(big.NewInt(3)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) + } + if out2[1].Y.Cmp(big.NewInt(4)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) + } + // Array Type + val3 := reflect.New(reflect.ArrayOf(2, reflect.StructOf(fields))) + val3.Elem().Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val3.Elem().Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) + val3.Elem().Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) + val3.Elem().Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out3 := *ConvertType(val3.Interface(), new([2]T)).(*[2]T) + if out3[0].X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) + } + if out3[0].Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) + } + if out3[1].X.Cmp(big.NewInt(3)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) + } + if out3[1].Y.Cmp(big.NewInt(4)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) + } +} diff --git a/accounts/abi/selector_parser.go b/accounts/abi/selector_parser.go new file mode 100644 index 0000000..b8ddd7d --- /dev/null +++ b/accounts/abi/selector_parser.go @@ -0,0 +1,177 @@ +// 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 abi + +import ( + "errors" + "fmt" +) + +type SelectorMarshaling struct { + Name string `json:"name"` + Type string `json:"type"` + Inputs []ArgumentMarshaling `json:"inputs"` +} + +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func isAlpha(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +func isIdentifierSymbol(c byte) bool { + return c == '$' || c == '_' +} + +func parseToken(unescapedSelector string, isIdent bool) (string, string, error) { + if len(unescapedSelector) == 0 { + return "", "", errors.New("empty token") + } + firstChar := unescapedSelector[0] + position := 1 + if !(isAlpha(firstChar) || (isIdent && isIdentifierSymbol(firstChar))) { + return "", "", fmt.Errorf("invalid token start: %c", firstChar) + } + for position < len(unescapedSelector) { + char := unescapedSelector[position] + if !(isAlpha(char) || isDigit(char) || (isIdent && isIdentifierSymbol(char))) { + break + } + position++ + } + return unescapedSelector[:position], unescapedSelector[position:], nil +} + +func parseIdentifier(unescapedSelector string) (string, string, error) { + return parseToken(unescapedSelector, true) +} + +func parseElementaryType(unescapedSelector string) (string, string, error) { + parsedType, rest, err := parseToken(unescapedSelector, false) + if err != nil { + return "", "", fmt.Errorf("failed to parse elementary type: %v", err) + } + // handle arrays + for len(rest) > 0 && rest[0] == '[' { + parsedType = parsedType + string(rest[0]) + rest = rest[1:] + for len(rest) > 0 && isDigit(rest[0]) { + parsedType = parsedType + string(rest[0]) + rest = rest[1:] + } + if len(rest) == 0 || rest[0] != ']' { + return "", "", fmt.Errorf("failed to parse array: expected ']', got %c", unescapedSelector[0]) + } + parsedType = parsedType + string(rest[0]) + rest = rest[1:] + } + return parsedType, rest, nil +} + +func parseCompositeType(unescapedSelector string) ([]interface{}, string, error) { + if len(unescapedSelector) == 0 || unescapedSelector[0] != '(' { + return nil, "", fmt.Errorf("expected '(', got %c", unescapedSelector[0]) + } + parsedType, rest, err := parseType(unescapedSelector[1:]) + if err != nil { + return nil, "", fmt.Errorf("failed to parse type: %v", err) + } + result := []interface{}{parsedType} + for len(rest) > 0 && rest[0] != ')' { + parsedType, rest, err = parseType(rest[1:]) + if err != nil { + return nil, "", fmt.Errorf("failed to parse type: %v", err) + } + result = append(result, parsedType) + } + if len(rest) == 0 || rest[0] != ')' { + return nil, "", fmt.Errorf("expected ')', got '%s'", rest) + } + if len(rest) >= 3 && rest[1] == '[' && rest[2] == ']' { + return append(result, "[]"), rest[3:], nil + } + return result, rest[1:], nil +} + +func parseType(unescapedSelector string) (interface{}, string, error) { + if len(unescapedSelector) == 0 { + return nil, "", errors.New("empty type") + } + if unescapedSelector[0] == '(' { + return parseCompositeType(unescapedSelector) + } else { + return parseElementaryType(unescapedSelector) + } +} + +func assembleArgs(args []interface{}) ([]ArgumentMarshaling, error) { + arguments := make([]ArgumentMarshaling, 0) + for i, arg := range args { + // generate dummy name to avoid unmarshal issues + name := fmt.Sprintf("name%d", i) + if s, ok := arg.(string); ok { + arguments = append(arguments, ArgumentMarshaling{name, s, s, nil, false}) + } else if components, ok := arg.([]interface{}); ok { + subArgs, err := assembleArgs(components) + if err != nil { + return nil, fmt.Errorf("failed to assemble components: %v", err) + } + tupleType := "tuple" + if len(subArgs) != 0 && subArgs[len(subArgs)-1].Type == "[]" { + subArgs = subArgs[:len(subArgs)-1] + tupleType = "tuple[]" + } + arguments = append(arguments, ArgumentMarshaling{name, tupleType, tupleType, subArgs, false}) + } else { + return nil, fmt.Errorf("failed to assemble args: unexpected type %T", arg) + } + } + return arguments, nil +} + +// ParseSelector converts a method selector into a struct that can be JSON encoded +// and consumed by other functions in this package. +// Note, although uppercase letters are not part of the ABI spec, this function +// still accepts it as the general format is valid. +func ParseSelector(unescapedSelector string) (SelectorMarshaling, error) { + name, rest, err := parseIdentifier(unescapedSelector) + if err != nil { + return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err) + } + args := []interface{}{} + if len(rest) >= 2 && rest[0] == '(' && rest[1] == ')' { + rest = rest[2:] + } else { + args, rest, err = parseCompositeType(rest) + if err != nil { + return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': %v", unescapedSelector, err) + } + } + if len(rest) > 0 { + return SelectorMarshaling{}, fmt.Errorf("failed to parse selector '%s': unexpected string '%s'", unescapedSelector, rest) + } + + // Reassemble the fake ABI and construct the JSON + fakeArgs, err := assembleArgs(args) + if err != nil { + return SelectorMarshaling{}, fmt.Errorf("failed to parse selector: %v", err) + } + + return SelectorMarshaling{name, "function", fakeArgs}, nil +} diff --git a/accounts/abi/selector_parser_test.go b/accounts/abi/selector_parser_test.go new file mode 100644 index 0000000..6cb0ae0 --- /dev/null +++ b/accounts/abi/selector_parser_test.go @@ -0,0 +1,80 @@ +// 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 abi + +import ( + "fmt" + "log" + "reflect" + "testing" +) + +func TestParseSelector(t *testing.T) { + t.Parallel() + mkType := func(types ...interface{}) []ArgumentMarshaling { + var result []ArgumentMarshaling + for i, typeOrComponents := range types { + name := fmt.Sprintf("name%d", i) + if typeName, ok := typeOrComponents.(string); ok { + result = append(result, ArgumentMarshaling{name, typeName, typeName, nil, false}) + } else if components, ok := typeOrComponents.([]ArgumentMarshaling); ok { + result = append(result, ArgumentMarshaling{name, "tuple", "tuple", components, false}) + } else if components, ok := typeOrComponents.([][]ArgumentMarshaling); ok { + result = append(result, ArgumentMarshaling{name, "tuple[]", "tuple[]", components[0], false}) + } else { + log.Fatalf("unexpected type %T", typeOrComponents) + } + } + return result + } + tests := []struct { + input string + name string + args []ArgumentMarshaling + }{ + {"noargs()", "noargs", []ArgumentMarshaling{}}, + {"simple(uint256,uint256,uint256)", "simple", mkType("uint256", "uint256", "uint256")}, + {"other(uint256,address)", "other", mkType("uint256", "address")}, + {"withArray(uint256[],address[2],uint8[4][][5])", "withArray", mkType("uint256[]", "address[2]", "uint8[4][][5]")}, + {"singleNest(bytes32,uint8,(uint256,uint256),address)", "singleNest", mkType("bytes32", "uint8", mkType("uint256", "uint256"), "address")}, + {"multiNest(address,(uint256[],uint256),((address,bytes32),uint256))", "multiNest", + mkType("address", mkType("uint256[]", "uint256"), mkType(mkType("address", "bytes32"), "uint256"))}, + {"arrayNest((uint256,uint256)[],bytes32)", "arrayNest", mkType([][]ArgumentMarshaling{mkType("uint256", "uint256")}, "bytes32")}, + {"multiArrayNest((uint256,uint256)[],(uint256,uint256)[])", "multiArrayNest", + mkType([][]ArgumentMarshaling{mkType("uint256", "uint256")}, [][]ArgumentMarshaling{mkType("uint256", "uint256")})}, + {"singleArrayNestAndArray((uint256,uint256)[],bytes32[])", "singleArrayNestAndArray", + mkType([][]ArgumentMarshaling{mkType("uint256", "uint256")}, "bytes32[]")}, + {"singleArrayNestWithArrayAndArray((uint256[],address[2],uint8[4][][5])[],bytes32[])", "singleArrayNestWithArrayAndArray", + mkType([][]ArgumentMarshaling{mkType("uint256[]", "address[2]", "uint8[4][][5]")}, "bytes32[]")}, + } + for i, tt := range tests { + selector, err := ParseSelector(tt.input) + if err != nil { + t.Errorf("test %d: failed to parse selector '%v': %v", i, tt.input, err) + } + if selector.Name != tt.name { + t.Errorf("test %d: unexpected function name: '%s' != '%s'", i, selector.Name, tt.name) + } + + if selector.Type != "function" { + t.Errorf("test %d: unexpected type: '%s' != '%s'", i, selector.Type, "function") + } + if !reflect.DeepEqual(selector.Inputs, tt.args) { + t.Errorf("test %d: unexpected args: '%v' != '%v'", i, selector.Inputs, tt.args) + } + } +} diff --git a/accounts/abi/topics.go b/accounts/abi/topics.go new file mode 100644 index 0000000..7ce9b72 --- /dev/null +++ b/accounts/abi/topics.go @@ -0,0 +1,173 @@ +// 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 abi + +import ( + "encoding/binary" + "errors" + "fmt" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" +) + +// MakeTopics converts a filter query argument list into a filter topic set. +func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) { + topics := make([][]common.Hash, len(query)) + for i, filter := range query { + for _, rule := range filter { + var topic common.Hash + + // Try to generate the topic based on simple types + switch rule := rule.(type) { + case common.Hash: + copy(topic[:], rule[:]) + case common.Address: + copy(topic[common.HashLength-common.AddressLength:], rule[:]) + case *big.Int: + copy(topic[:], math.U256Bytes(rule)) + case bool: + if rule { + topic[common.HashLength-1] = 1 + } + case int8: + copy(topic[:], genIntType(int64(rule), 1)) + case int16: + copy(topic[:], genIntType(int64(rule), 2)) + case int32: + copy(topic[:], genIntType(int64(rule), 4)) + case int64: + copy(topic[:], genIntType(rule, 8)) + case uint8: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint16: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint32: + blob := new(big.Int).SetUint64(uint64(rule)).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case uint64: + blob := new(big.Int).SetUint64(rule).Bytes() + copy(topic[common.HashLength-len(blob):], blob) + case string: + hash := crypto.Keccak256Hash([]byte(rule)) + copy(topic[:], hash[:]) + case []byte: + hash := crypto.Keccak256Hash(rule) + copy(topic[:], hash[:]) + + default: + // 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. + // + // We only convert stringS and bytes to hash, still need to deal with + // array(both fixed-size and dynamic-size) and struct. + + // Attempt to generate the topic from funky types + val := reflect.ValueOf(rule) + switch { + // static byte array + case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8: + reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val) + default: + return nil, fmt.Errorf("unsupported indexed type: %T", rule) + } + } + topics[i] = append(topics[i], topic) + } + } + return topics, nil +} + +func genIntType(rule int64, size uint) []byte { + var topic [common.HashLength]byte + if rule < 0 { + // if a rule is negative, we need to put it into two's complement. + // extended to common.HashLength bytes. + topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} + } + for i := uint(0); i < size; i++ { + topic[common.HashLength-i-1] = byte(rule >> (i * 8)) + } + return topic[:] +} + +// ParseTopics converts the indexed topic fields into actual log field values. +func ParseTopics(out interface{}, fields Arguments, topics []common.Hash) error { + return parseTopicWithSetter(fields, topics, + func(arg Argument, reconstr interface{}) { + field := reflect.ValueOf(out).Elem().FieldByName(ToCamelCase(arg.Name)) + field.Set(reflect.ValueOf(reconstr)) + }) +} + +// ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs. +func ParseTopicsIntoMap(out map[string]interface{}, fields Arguments, topics []common.Hash) error { + return parseTopicWithSetter(fields, topics, + func(arg Argument, reconstr interface{}) { + out[arg.Name] = reconstr + }) +} + +// parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the +// provided set function. +// +// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256 +// hashes as the topic value! +func parseTopicWithSetter(fields Arguments, topics []common.Hash, setter func(Argument, interface{})) error { + // Sanity check that the fields and topics match up + if len(fields) != len(topics) { + return errors.New("topic/field count mismatch") + } + // Iterate over all the fields and reconstruct them from topics + for i, arg := range fields { + if !arg.Indexed { + return errors.New("non-indexed field in topic reconstruction") + } + var reconstr interface{} + switch arg.Type.T { + case TupleTy: + return errors.New("tuple type in topic reconstruction") + case StringTy, BytesTy, SliceTy, ArrayTy: + // Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash + // whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash + reconstr = topics[i] + case FunctionTy: + if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 { + return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes()) + } + var tmp [24]byte + copy(tmp[:], topics[i][8:32]) + reconstr = tmp + default: + var err error + reconstr, err = toGoType(0, arg.Type, topics[i].Bytes()) + if err != nil { + return err + } + } + // Use the setter function to store the value + setter(arg, reconstr) + } + + return nil +} diff --git a/accounts/abi/topics_test.go b/accounts/abi/topics_test.go new file mode 100644 index 0000000..9e1efd3 --- /dev/null +++ b/accounts/abi/topics_test.go @@ -0,0 +1,409 @@ +// 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 ( + "math" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestMakeTopics(t *testing.T) { + t.Parallel() + type args struct { + query [][]interface{} + } + tests := []struct { + name string + args args + want [][]common.Hash + wantErr bool + }{ + { + "support fixed byte types, right padded to 32 bytes", + args{[][]interface{}{{[5]byte{1, 2, 3, 4, 5}}}}, + [][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}}, + false, + }, + { + "support common hash types in topics", + args{[][]interface{}{{common.Hash{1, 2, 3, 4, 5}}}}, + [][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}}, + false, + }, + { + "support address types in topics", + args{[][]interface{}{{common.Address{1, 2, 3, 4, 5}}}}, + [][]common.Hash{{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5}}}, + false, + }, + { + "support 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, + }, + { + "support boolean types in topics", + args{[][]interface{}{ + {true}, + {false}, + }}, + [][]common.Hash{ + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, + {common.Hash{0}}, + }, + false, + }, + { + "support int/uint(8/16/32/64) types in topics", + args{[][]interface{}{ + {int8(-2)}, + {int16(-3)}, + {int32(-4)}, + {int64(-5)}, + {int8(1)}, + {int16(256)}, + {int32(65536)}, + {int64(4294967296)}, + {uint8(1)}, + {uint16(256)}, + {uint32(65536)}, + {uint64(4294967296)}, + }}, + [][]common.Hash{ + {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254}}, + {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253}}, + {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 252}}, + {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 251}}, + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}}, + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}}, + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}}, + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}}, + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}}, + {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}}, + }, + false, + }, + { + "support string types in topics", + args{[][]interface{}{{"hello world"}}}, + [][]common.Hash{{crypto.Keccak256Hash([]byte("hello world"))}}, + false, + }, + { + "support byte slice types in topics", + args{[][]interface{}{{[]byte{1, 2, 3}}}}, + [][]common.Hash{{crypto.Keccak256Hash([]byte{1, 2, 3})}}, + false, + }, + } + for _, tt := range tests { + 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) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("makeTopics() = %v, want %v", got, tt.want) + } + }) + } +} + +type args struct { + createObj func() interface{} + resultObj func() interface{} + resultMap func() map[string]interface{} + fields Arguments + topics []common.Hash +} + +type bytesStruct struct { + StaticBytes [5]byte +} +type int8Struct struct { + Int8Value int8 +} +type int256Struct struct { + Int256Value *big.Int +} + +type hashStruct struct { + HashValue common.Hash +} + +type funcStruct struct { + FuncValue [24]byte +} + +type topicTest struct { + name string + args args + wantErr bool +} + +func setupTopicsTests() []topicTest { + bytesType, _ := NewType("bytes5", "", nil) + int8Type, _ := NewType("int8", "", nil) + int256Type, _ := NewType("int256", "", nil) + tupleType, _ := NewType("tuple(int256,int8)", "", nil) + stringType, _ := NewType("string", "", nil) + funcType, _ := NewType("function", "", nil) + + tests := []topicTest{ + { + name: "support fixed byte types, right padded to 32 bytes", + args: args{ + createObj: func() interface{} { return &bytesStruct{} }, + resultObj: func() interface{} { return &bytesStruct{StaticBytes: [5]byte{1, 2, 3, 4, 5}} }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"staticBytes": [5]byte{1, 2, 3, 4, 5}} + }, + fields: Arguments{Argument{ + Name: "staticBytes", + Type: bytesType, + Indexed: true, + }}, + topics: []common.Hash{ + {1, 2, 3, 4, 5}, + }, + }, + wantErr: false, + }, + { + name: "int8 with negative value", + args: args{ + createObj: func() interface{} { return &int8Struct{} }, + resultObj: func() interface{} { return &int8Struct{Int8Value: -1} }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"int8Value": int8(-1)} + }, + fields: Arguments{Argument{ + Name: "int8Value", + Type: int8Type, + Indexed: true, + }}, + topics: []common.Hash{ + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + wantErr: false, + }, + { + name: "int256 with negative value", + args: args{ + createObj: func() interface{} { return &int256Struct{} }, + resultObj: func() interface{} { return &int256Struct{Int256Value: big.NewInt(-1)} }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"int256Value": big.NewInt(-1)} + }, + fields: Arguments{Argument{ + Name: "int256Value", + Type: int256Type, + Indexed: true, + }}, + topics: []common.Hash{ + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + wantErr: false, + }, + { + name: "hash type", + args: args{ + createObj: func() interface{} { return &hashStruct{} }, + resultObj: func() interface{} { return &hashStruct{crypto.Keccak256Hash([]byte("stringtopic"))} }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"hashValue": crypto.Keccak256Hash([]byte("stringtopic"))} + }, + fields: Arguments{Argument{ + Name: "hashValue", + Type: stringType, + Indexed: true, + }}, + topics: []common.Hash{ + crypto.Keccak256Hash([]byte("stringtopic")), + }, + }, + wantErr: false, + }, + { + name: "function type", + args: args{ + createObj: func() interface{} { return &funcStruct{} }, + resultObj: func() interface{} { + return &funcStruct{[24]byte{255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}} + }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"funcValue": [24]byte{255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}} + }, + fields: Arguments{Argument{ + Name: "funcValue", + Type: funcType, + Indexed: true, + }}, + topics: []common.Hash{ + {0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + wantErr: false, + }, + { + name: "error on topic/field count mismatch", + args: args{ + createObj: func() interface{} { return nil }, + resultObj: func() interface{} { return nil }, + resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, + fields: Arguments{Argument{ + Name: "tupletype", + Type: tupleType, + Indexed: true, + }}, + topics: []common.Hash{}, + }, + wantErr: true, + }, + { + name: "error on unindexed arguments", + args: args{ + createObj: func() interface{} { return &int256Struct{} }, + resultObj: func() interface{} { return &int256Struct{} }, + resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, + fields: Arguments{Argument{ + Name: "int256Value", + Type: int256Type, + Indexed: false, + }}, + topics: []common.Hash{ + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + wantErr: true, + }, + { + name: "error on tuple in topic reconstruction", + args: args{ + createObj: func() interface{} { return &tupleType }, + resultObj: func() interface{} { return &tupleType }, + resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, + fields: Arguments{Argument{ + Name: "tupletype", + Type: tupleType, + Indexed: true, + }}, + topics: []common.Hash{{0}}, + }, + wantErr: true, + }, + { + name: "error on improper encoded function", + args: args{ + createObj: func() interface{} { return &funcStruct{} }, + resultObj: func() interface{} { return &funcStruct{} }, + resultMap: func() map[string]interface{} { + return make(map[string]interface{}) + }, + fields: Arguments{Argument{ + Name: "funcValue", + Type: funcType, + Indexed: true, + }}, + topics: []common.Hash{ + {0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + wantErr: true, + }, + } + + return tests +} + +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) + } + resultObj := tt.args.resultObj() + if !reflect.DeepEqual(createObj, resultObj) { + t.Errorf("parseTopics() = %v, want %v", createObj, resultObj) + } + }) + } +} + +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) + } + resultMap := tt.args.resultMap() + if !reflect.DeepEqual(outMap, resultMap) { + t.Errorf("parseTopicsIntoMap() = %v, want %v", outMap, resultMap) + } + }) + } +} diff --git a/accounts/abi/type.go b/accounts/abi/type.go new file mode 100644 index 0000000..d57fa3d --- /dev/null +++ b/accounts/abi/type.go @@ -0,0 +1,425 @@ +// 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 abi + +import ( + "errors" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "github.com/ethereum/go-ethereum/common" +) + +// Type enumerator +const ( + IntTy byte = iota + UintTy + BoolTy + StringTy + SliceTy + ArrayTy + TupleTy + AddressTy + FixedBytesTy + BytesTy + HashTy + FixedPointTy + FunctionTy +) + +// Type is the reflection of the supported argument type. +type Type struct { + Elem *Type + Size int + T byte // Our own type checking + + stringKind string // holds the unparsed string for deriving signatures + + // Tuple relative fields + TupleRawName string // Raw struct name defined in source code, may be empty. + TupleElems []*Type // Type information of all tuple fields + TupleRawNames []string // Raw field name of all tuple fields + TupleType reflect.Type // Underlying struct of the tuple +} + +var ( + // typeRegex parses the abi sub types + typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?") + + // sliceSizeRegex grab the slice size + sliceSizeRegex = regexp.MustCompile("[0-9]+") +) + +// NewType creates a new reflection type of abi type given in t. +func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) { + // check that array brackets are equal if they exist + if strings.Count(t, "[") != strings.Count(t, "]") { + return Type{}, errors.New("invalid arg type in abi") + } + typ.stringKind = t + + // if there are brackets, get ready to go into slice/array mode and + // recursively create the type + if strings.Count(t, "[") != 0 { + // Note internalType can be empty here. + subInternal := internalType + if i := strings.LastIndex(internalType, "["); i != -1 { + subInternal = subInternal[:i] + } + // recursively embed the type + i := strings.LastIndex(t, "[") + embeddedType, err := NewType(t[:i], subInternal, components) + if err != nil { + return Type{}, err + } + // grab the last cell and create a type from there + sliced := t[i:] + // grab the slice size with regexp + intz := sliceSizeRegex.FindAllString(sliced, -1) + + if len(intz) == 0 { + // is a slice + typ.T = SliceTy + typ.Elem = &embeddedType + typ.stringKind = embeddedType.stringKind + sliced + } else if len(intz) == 1 { + // is an array + typ.T = ArrayTy + typ.Elem = &embeddedType + typ.Size, err = strconv.Atoi(intz[0]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + typ.stringKind = embeddedType.stringKind + sliced + } else { + return Type{}, errors.New("invalid formatting of array type") + } + return typ, err + } + // parse the type and size of the abi-type. + matches := typeRegex.FindAllStringSubmatch(t, -1) + if len(matches) == 0 { + return Type{}, fmt.Errorf("invalid type '%v'", t) + } + parsedType := matches[0] + + // varSize is the size of the variable + var varSize int + if len(parsedType[3]) > 0 { + var err error + varSize, err = strconv.Atoi(parsedType[2]) + if err != nil { + return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) + } + } else { + if parsedType[0] == "uint" || parsedType[0] == "int" { + // this should fail because it means that there's something wrong with + // the abi type (the compiler should always format it to the size...always) + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + } + // varType is the parsed abi type + switch varType := parsedType[1]; varType { + case "int": + typ.Size = varSize + typ.T = IntTy + case "uint": + typ.Size = varSize + typ.T = UintTy + case "bool": + typ.T = BoolTy + case "address": + typ.Size = 20 + typ.T = AddressTy + case "string": + typ.T = StringTy + case "bytes": + if varSize == 0 { + typ.T = BytesTy + } else { + if varSize > 32 { + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + typ.T = FixedBytesTy + typ.Size = varSize + } + case "tuple": + var ( + fields []reflect.StructField + elems []*Type + names []string + expression string // canonical parameter expression + used = make(map[string]bool) + ) + expression += "(" + for idx, c := range components { + cType, err := NewType(c.Type, c.InternalType, c.Components) + if err != nil { + return Type{}, err + } + name := ToCamelCase(c.Name) + if name == "" { + return Type{}, errors.New("abi: purely anonymous or underscored field is not supported") + } + fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] }) + used[fieldName] = true + if !isValidFieldName(fieldName) { + return Type{}, fmt.Errorf("field %d has invalid name", idx) + } + fields = append(fields, reflect.StructField{ + Name: fieldName, // reflect.StructOf will panic for any exported field. + Type: cType.GetType(), + Tag: reflect.StructTag("json:\"" + c.Name + "\""), + }) + elems = append(elems, &cType) + names = append(names, c.Name) + expression += cType.stringKind + if idx != len(components)-1 { + expression += "," + } + } + expression += ")" + + typ.TupleType = reflect.StructOf(fields) + typ.TupleElems = elems + typ.TupleRawNames = names + typ.T = TupleTy + typ.stringKind = expression + + const structPrefix = "struct " + // After solidity 0.5.10, a new field of abi "internalType" + // is introduced. From that we can obtain the struct name + // user defined in the source code. + if internalType != "" && strings.HasPrefix(internalType, structPrefix) { + // Foo.Bar type definition is not allowed in golang, + // convert the format to FooBar + typ.TupleRawName = strings.ReplaceAll(internalType[len(structPrefix):], ".", "") + } + + case "function": + typ.T = FunctionTy + typ.Size = 24 + default: + return Type{}, fmt.Errorf("unsupported arg type: %s", t) + } + + return +} + +// GetType returns the reflection type of the ABI type. +func (t Type) GetType() reflect.Type { + switch t.T { + case IntTy: + return reflectIntType(false, t.Size) + case UintTy: + return reflectIntType(true, t.Size) + case BoolTy: + return reflect.TypeOf(false) + case StringTy: + return reflect.TypeOf("") + case SliceTy: + return reflect.SliceOf(t.Elem.GetType()) + case ArrayTy: + return reflect.ArrayOf(t.Size, t.Elem.GetType()) + case TupleTy: + return t.TupleType + case AddressTy: + return reflect.TypeOf(common.Address{}) + case FixedBytesTy: + return reflect.ArrayOf(t.Size, reflect.TypeOf(byte(0))) + case BytesTy: + return reflect.SliceOf(reflect.TypeOf(byte(0))) + case HashTy: + // hashtype currently not used + return reflect.ArrayOf(32, reflect.TypeOf(byte(0))) + case FixedPointTy: + // fixedpoint type currently not used + return reflect.ArrayOf(32, reflect.TypeOf(byte(0))) + case FunctionTy: + return reflect.ArrayOf(24, reflect.TypeOf(byte(0))) + default: + panic("Invalid type") + } +} + +// String implements Stringer. +func (t Type) String() (out string) { + return t.stringKind +} + +func (t Type) pack(v reflect.Value) ([]byte, error) { + // dereference pointer first if it's a pointer + v = indirect(v) + if err := typeCheck(t, v); err != nil { + return nil, err + } + + switch t.T { + case SliceTy, ArrayTy: + var ret []byte + + if t.requiresLengthPrefix() { + // append length + ret = append(ret, packNum(reflect.ValueOf(v.Len()))...) + } + + // calculate offset if any + offset := 0 + offsetReq := isDynamicType(*t.Elem) + if offsetReq { + offset = getTypeSize(*t.Elem) * v.Len() + } + var tail []byte + for i := 0; i < v.Len(); i++ { + val, err := t.Elem.pack(v.Index(i)) + if err != nil { + return nil, err + } + if !offsetReq { + ret = append(ret, val...) + continue + } + ret = append(ret, packNum(reflect.ValueOf(offset))...) + offset += len(val) + tail = append(tail, val...) + } + return append(ret, tail...), nil + case TupleTy: + // (T1,...,Tk) for k >= 0 and any types T1, …, Tk + // enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k)) + // where X = (X(1), ..., X(k)) and head and tail are defined for Ti being a static + // type as + // head(X(i)) = enc(X(i)) and tail(X(i)) = "" (the empty string) + // and as + // head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)))) + // tail(X(i)) = enc(X(i)) + // otherwise, i.e. if Ti is a dynamic type. + fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, v) + if err != nil { + return nil, err + } + // Calculate prefix occupied size. + offset := 0 + for _, elem := range t.TupleElems { + offset += getTypeSize(*elem) + } + var ret, tail []byte + for i, elem := range t.TupleElems { + field := v.FieldByName(fieldmap[t.TupleRawNames[i]]) + if !field.IsValid() { + return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i]) + } + val, err := elem.pack(field) + if err != nil { + return nil, err + } + if isDynamicType(*elem) { + ret = append(ret, packNum(reflect.ValueOf(offset))...) + tail = append(tail, val...) + offset += len(val) + } else { + ret = append(ret, val...) + } + } + return append(ret, tail...), nil + + default: + return packElement(t, v) + } +} + +// requiresLengthPrefix returns whether the type requires any sort of length +// prefixing. +func (t Type) requiresLengthPrefix() bool { + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy +} + +// isDynamicType returns true if the type is dynamic. +// The following types are called “dynamicâ€: +// * bytes +// * string +// * T[] for any T +// * T[k] for any dynamic T and any k >= 0 +// * (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k +func isDynamicType(t Type) bool { + if t.T == TupleTy { + for _, elem := range t.TupleElems { + if isDynamicType(*elem) { + return true + } + } + return false + } + return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem)) +} + +// getTypeSize returns the size that this type needs to occupy. +// We distinguish static and dynamic types. Static types are encoded in-place +// and dynamic types are encoded at a separately allocated location after the +// current block. +// So for a static variable, the size returned represents the size that the +// variable actually occupies. +// For a dynamic variable, the returned size is fixed 32 bytes, which is used +// to store the location reference for actual value storage. +func getTypeSize(t Type) int { + if t.T == ArrayTy && !isDynamicType(*t.Elem) { + // Recursively calculate type size if it is a nested array + if t.Elem.T == ArrayTy || t.Elem.T == TupleTy { + return t.Size * getTypeSize(*t.Elem) + } + return t.Size * 32 + } else if t.T == TupleTy && !isDynamicType(t) { + total := 0 + for _, elem := range t.TupleElems { + total += getTypeSize(*elem) + } + return total + } + return 32 +} + +// isLetter reports whether a given 'rune' is classified as a Letter. +// This method is copied from reflect/type.go +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) +} + +// isValidFieldName checks if a string is a valid (struct) field name or not. +// +// According to the language spec, a field name should be an identifier. +// +// identifier = letter { letter | unicode_digit } . +// letter = unicode_letter | "_" . +// This method is copied from reflect/type.go +func isValidFieldName(fieldName string) bool { + for i, c := range fieldName { + if i == 0 && !isLetter(c) { + return false + } + + if !(isLetter(c) || unicode.IsDigit(c)) { + return false + } + } + + return len(fieldName) > 0 +} diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go new file mode 100644 index 0000000..9592254 --- /dev/null +++ b/accounts/abi/type_test.go @@ -0,0 +1,380 @@ +// 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 abi + +import ( + "math/big" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" +) + +// typeWithoutStringer is an alias for the Type type which simply doesn't implement +// the stringer interface to allow printing type details in the tests below. +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 + kind Type + }{ + {"bool", nil, Type{T: BoolTy, stringKind: "bool"}}, + {"bool[]", nil, Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}}, + {"bool[2]", nil, Type{Size: 2, T: ArrayTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}}, + {"bool[2][]", nil, Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}}, + {"bool[][]", nil, Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}}, + {"bool[][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}}, + {"bool[2][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}}, + {"bool[2][][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}}, + {"bool[2][2][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}}, + {"bool[][][]", nil, Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}}, + {"bool[][2][]", nil, Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}}, + {"int8", nil, Type{Size: 8, T: IntTy, stringKind: "int8"}}, + {"int16", nil, Type{Size: 16, T: IntTy, stringKind: "int16"}}, + {"int32", nil, Type{Size: 32, T: IntTy, stringKind: "int32"}}, + {"int64", nil, Type{Size: 64, T: IntTy, stringKind: "int64"}}, + {"int256", nil, Type{Size: 256, T: IntTy, stringKind: "int256"}}, + {"int8[]", nil, Type{T: SliceTy, Elem: &Type{Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}}, + {"int8[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}}, + {"int16[]", nil, Type{T: SliceTy, Elem: &Type{Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}}, + {"int16[2]", nil, Type{Size: 2, T: ArrayTy, Elem: &Type{Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}}, + {"int32[]", nil, Type{T: SliceTy, Elem: &Type{Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}}, + {"int32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}}, + {"int64[]", nil, Type{T: SliceTy, Elem: &Type{Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}}, + {"int64[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}}, + {"int256[]", nil, Type{T: SliceTy, Elem: &Type{Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}}, + {"int256[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}}, + {"uint8", nil, Type{Size: 8, T: UintTy, stringKind: "uint8"}}, + {"uint16", nil, Type{Size: 16, T: UintTy, stringKind: "uint16"}}, + {"uint32", nil, Type{Size: 32, T: UintTy, stringKind: "uint32"}}, + {"uint64", nil, Type{Size: 64, T: UintTy, stringKind: "uint64"}}, + {"uint256", nil, Type{Size: 256, T: UintTy, stringKind: "uint256"}}, + {"uint8[]", nil, Type{T: SliceTy, Elem: &Type{Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}}, + {"uint8[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}}, + {"uint16[]", nil, Type{T: SliceTy, Elem: &Type{Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}}, + {"uint16[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}}, + {"uint32[]", nil, Type{T: SliceTy, Elem: &Type{Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}}, + {"uint32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}}, + {"uint64[]", nil, Type{T: SliceTy, Elem: &Type{Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}}, + {"uint64[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}}, + {"uint256[]", nil, Type{T: SliceTy, Elem: &Type{Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}}, + {"uint256[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}}, + {"bytes32", nil, Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}}, + {"bytes[]", nil, Type{T: SliceTy, Elem: &Type{T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}}, + {"bytes[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}}, + {"bytes32[]", nil, Type{T: SliceTy, Elem: &Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}}, + {"bytes32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[2]"}}, + {"string", nil, Type{T: StringTy, stringKind: "string"}}, + {"string[]", nil, Type{T: SliceTy, Elem: &Type{T: StringTy, stringKind: "string"}, stringKind: "string[]"}}, + {"string[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: StringTy, stringKind: "string"}, stringKind: "string[2]"}}, + {"address", nil, Type{Size: 20, T: AddressTy, stringKind: "address"}}, + {"address[]", nil, Type{T: SliceTy, Elem: &Type{Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}}, + {"address[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}}, + // TODO when fixed types are implemented properly + // {"fixed", nil, Type{}}, + // {"fixed128x128", nil, Type{}}, + // {"fixed[]", nil, Type{}}, + // {"fixed[2]", nil, Type{}}, + // {"fixed128x128[]", nil, Type{}}, + // {"fixed128x128[2]", nil, Type{}}, + {"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{T: TupleTy, TupleType: reflect.TypeOf(struct { + A int64 `json:"a"` + }{}), stringKind: "(int64)", + TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}}, + {"tuple with long name", []ArgumentMarshaling{{Name: "aTypicalParamName", Type: "int64"}}, Type{T: TupleTy, TupleType: reflect.TypeOf(struct { + ATypicalParamName int64 `json:"aTypicalParamName"` + }{}), stringKind: "(int64)", + TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"aTypicalParamName"}}}, + } + + for _, tt := range tests { + typ, err := NewType(tt.blob, "", tt.components) + if err != nil { + t.Errorf("type %q: failed to parse type string: %v", tt.blob, err) + } + if !reflect.DeepEqual(typ, tt.kind) { + t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", tt.blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(tt.kind))) + } + } +} + +func TestTypeCheck(t *testing.T) { + t.Parallel() + for i, test := range []struct { + typ string + components []ArgumentMarshaling + input interface{} + err string + }{ + {"uint", nil, big.NewInt(1), "unsupported arg type: uint"}, + {"int", nil, big.NewInt(1), "unsupported arg type: int"}, + {"uint256", nil, big.NewInt(1), ""}, + {"uint256[][3][]", nil, [][3][]*big.Int{{{}}}, ""}, + {"uint256[][][3]", nil, [3][][]*big.Int{{{}}}, ""}, + {"uint256[3][][]", nil, [][][3]*big.Int{{{}}}, ""}, + {"uint256[3][3][3]", nil, [3][3][3]*big.Int{{{}}}, ""}, + {"uint8[][]", nil, [][]uint8{}, ""}, + {"int256", nil, big.NewInt(1), ""}, + {"uint8", nil, uint8(1), ""}, + {"uint16", nil, uint16(1), ""}, + {"uint32", nil, uint32(1), ""}, + {"uint64", nil, uint64(1), ""}, + {"int8", nil, int8(1), ""}, + {"int16", nil, int16(1), ""}, + {"int32", nil, int32(1), ""}, + {"int64", nil, int64(1), ""}, + {"uint24", nil, big.NewInt(1), ""}, + {"uint40", nil, big.NewInt(1), ""}, + {"uint48", nil, big.NewInt(1), ""}, + {"uint56", nil, big.NewInt(1), ""}, + {"uint72", nil, big.NewInt(1), ""}, + {"uint80", nil, big.NewInt(1), ""}, + {"uint88", nil, big.NewInt(1), ""}, + {"uint96", nil, big.NewInt(1), ""}, + {"uint104", nil, big.NewInt(1), ""}, + {"uint112", nil, big.NewInt(1), ""}, + {"uint120", nil, big.NewInt(1), ""}, + {"uint128", nil, big.NewInt(1), ""}, + {"uint136", nil, big.NewInt(1), ""}, + {"uint144", nil, big.NewInt(1), ""}, + {"uint152", nil, big.NewInt(1), ""}, + {"uint160", nil, big.NewInt(1), ""}, + {"uint168", nil, big.NewInt(1), ""}, + {"uint176", nil, big.NewInt(1), ""}, + {"uint184", nil, big.NewInt(1), ""}, + {"uint192", nil, big.NewInt(1), ""}, + {"uint200", nil, big.NewInt(1), ""}, + {"uint208", nil, big.NewInt(1), ""}, + {"uint216", nil, big.NewInt(1), ""}, + {"uint224", nil, big.NewInt(1), ""}, + {"uint232", nil, big.NewInt(1), ""}, + {"uint240", nil, big.NewInt(1), ""}, + {"uint248", nil, big.NewInt(1), ""}, + {"int24", nil, big.NewInt(1), ""}, + {"int40", nil, big.NewInt(1), ""}, + {"int48", nil, big.NewInt(1), ""}, + {"int56", nil, big.NewInt(1), ""}, + {"int72", nil, big.NewInt(1), ""}, + {"int80", nil, big.NewInt(1), ""}, + {"int88", nil, big.NewInt(1), ""}, + {"int96", nil, big.NewInt(1), ""}, + {"int104", nil, big.NewInt(1), ""}, + {"int112", nil, big.NewInt(1), ""}, + {"int120", nil, big.NewInt(1), ""}, + {"int128", nil, big.NewInt(1), ""}, + {"int136", nil, big.NewInt(1), ""}, + {"int144", nil, big.NewInt(1), ""}, + {"int152", nil, big.NewInt(1), ""}, + {"int160", nil, big.NewInt(1), ""}, + {"int168", nil, big.NewInt(1), ""}, + {"int176", nil, big.NewInt(1), ""}, + {"int184", nil, big.NewInt(1), ""}, + {"int192", nil, big.NewInt(1), ""}, + {"int200", nil, big.NewInt(1), ""}, + {"int208", nil, big.NewInt(1), ""}, + {"int216", nil, big.NewInt(1), ""}, + {"int224", nil, big.NewInt(1), ""}, + {"int232", nil, big.NewInt(1), ""}, + {"int240", nil, big.NewInt(1), ""}, + {"int248", nil, big.NewInt(1), ""}, + {"uint30", nil, uint8(1), "abi: cannot use uint8 as type ptr as argument"}, + {"uint8", nil, uint16(1), "abi: cannot use uint16 as type uint8 as argument"}, + {"uint8", nil, uint32(1), "abi: cannot use uint32 as type uint8 as argument"}, + {"uint8", nil, uint64(1), "abi: cannot use uint64 as type uint8 as argument"}, + {"uint8", nil, int8(1), "abi: cannot use int8 as type uint8 as argument"}, + {"uint8", nil, int16(1), "abi: cannot use int16 as type uint8 as argument"}, + {"uint8", nil, int32(1), "abi: cannot use int32 as type uint8 as argument"}, + {"uint8", nil, int64(1), "abi: cannot use int64 as type uint8 as argument"}, + {"uint16", nil, uint16(1), ""}, + {"uint16", nil, uint8(1), "abi: cannot use uint8 as type uint16 as argument"}, + {"uint16[]", nil, []uint16{1, 2, 3}, ""}, + {"uint16[]", nil, [3]uint16{1, 2, 3}, ""}, + {"uint16[]", nil, []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"}, + {"uint16[3]", nil, [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"}, + {"uint16[3]", nil, [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"}, + {"uint16[3]", nil, []uint16{1, 2, 3}, ""}, + {"uint16[3]", nil, []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"}, + {"address[]", nil, []common.Address{{1}}, ""}, + {"address[1]", nil, []common.Address{{1}}, ""}, + {"address[1]", nil, [1]common.Address{{1}}, ""}, + {"address[2]", nil, [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"}, + {"bytes32", nil, [32]byte{}, ""}, + {"bytes31", nil, [31]byte{}, ""}, + {"bytes30", nil, [30]byte{}, ""}, + {"bytes29", nil, [29]byte{}, ""}, + {"bytes28", nil, [28]byte{}, ""}, + {"bytes27", nil, [27]byte{}, ""}, + {"bytes26", nil, [26]byte{}, ""}, + {"bytes25", nil, [25]byte{}, ""}, + {"bytes24", nil, [24]byte{}, ""}, + {"bytes23", nil, [23]byte{}, ""}, + {"bytes22", nil, [22]byte{}, ""}, + {"bytes21", nil, [21]byte{}, ""}, + {"bytes20", nil, [20]byte{}, ""}, + {"bytes19", nil, [19]byte{}, ""}, + {"bytes18", nil, [18]byte{}, ""}, + {"bytes17", nil, [17]byte{}, ""}, + {"bytes16", nil, [16]byte{}, ""}, + {"bytes15", nil, [15]byte{}, ""}, + {"bytes14", nil, [14]byte{}, ""}, + {"bytes13", nil, [13]byte{}, ""}, + {"bytes12", nil, [12]byte{}, ""}, + {"bytes11", nil, [11]byte{}, ""}, + {"bytes10", nil, [10]byte{}, ""}, + {"bytes9", nil, [9]byte{}, ""}, + {"bytes8", nil, [8]byte{}, ""}, + {"bytes7", nil, [7]byte{}, ""}, + {"bytes6", nil, [6]byte{}, ""}, + {"bytes5", nil, [5]byte{}, ""}, + {"bytes4", nil, [4]byte{}, ""}, + {"bytes3", nil, [3]byte{}, ""}, + {"bytes2", nil, [2]byte{}, ""}, + {"bytes1", nil, [1]byte{}, ""}, + {"bytes32", nil, [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"}, + {"bytes32", nil, common.Hash{1}, ""}, + {"bytes31", nil, common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"}, + {"bytes31", nil, [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"}, + {"bytes", nil, []byte{0, 1}, ""}, + {"bytes", nil, [2]byte{0, 1}, "abi: cannot use array as type slice as argument"}, + {"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"}, + {"string", nil, "hello world", ""}, + {"string", nil, "", ""}, + {"string", nil, []byte{}, "abi: cannot use slice as type string as argument"}, + {"bytes32[]", nil, [][32]byte{{}}, ""}, + {"function", nil, [24]byte{}, ""}, + {"bytes20", nil, common.Address{}, ""}, + {"address", nil, [20]byte{}, ""}, + {"address", nil, common.Address{}, ""}, + {"bytes32[]]", nil, "", "invalid arg type in abi"}, + {"invalidType", nil, "", "unsupported arg type: invalidType"}, + {"invalidSlice[]", nil, "", "unsupported arg type: invalidSlice"}, + // simple tuple + {"tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, struct { + A *big.Int + B *big.Int + }{}, ""}, + // tuple slice + {"tuple[]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct { + A *big.Int + B *big.Int + }{}, ""}, + // tuple array + {"tuple[2]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct { + A *big.Int + B *big.Int + }{{big.NewInt(0), big.NewInt(0)}, {big.NewInt(0), big.NewInt(0)}}, ""}, + } { + typ, err := NewType(test.typ, "", test.components) + if err != nil && len(test.err) == 0 { + t.Fatal("unexpected parse error:", err) + } else if err != nil && len(test.err) != 0 { + if err.Error() != test.err { + t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err) + } + continue + } + + err = typeCheck(typ, reflect.ValueOf(test.input)) + if err != nil && len(test.err) == 0 { + t.Errorf("%d failed. Expected no err but got: %v", i, err) + continue + } + if err == nil && len(test.err) != 0 { + t.Errorf("%d failed. Expected err: %v but got none", i, test.err) + continue + } + + if err != nil && len(test.err) != 0 && err.Error() != test.err { + t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err) + } + } +} + +func TestInternalType(t *testing.T) { + t.Parallel() + components := []ArgumentMarshaling{{Name: "a", Type: "int64"}} + internalType := "struct a.b[]" + kind := Type{ + T: TupleTy, + TupleType: reflect.TypeOf(struct { + A int64 `json:"a"` + }{}), + stringKind: "(int64)", + TupleRawName: "ab[]", + TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, + TupleRawNames: []string{"a"}, + } + + blob := "tuple" + typ, err := NewType(blob, internalType, components) + if err != nil { + t.Errorf("type %q: failed to parse type string: %v", blob, err) + } + if !reflect.DeepEqual(typ, kind) { + t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(kind))) + } +} + +func TestGetTypeSize(t *testing.T) { + t.Parallel() + var testCases = []struct { + typ string + components []ArgumentMarshaling + typSize int + }{ + // simple array + {"uint256[2]", nil, 32 * 2}, + {"address[3]", nil, 32 * 3}, + {"bytes32[4]", nil, 32 * 4}, + // array array + {"uint256[2][3][4]", nil, 32 * (2 * 3 * 4)}, + // array tuple + {"tuple[2]", []ArgumentMarshaling{{Name: "x", Type: "bytes32"}, {Name: "y", Type: "bytes32"}}, (32 * 2) * 2}, + // simple tuple + {"tuple", []ArgumentMarshaling{{Name: "x", Type: "uint256"}, {Name: "y", Type: "uint256"}}, 32 * 2}, + // tuple array + {"tuple", []ArgumentMarshaling{{Name: "x", Type: "bytes32[2]"}}, 32 * 2}, + // tuple tuple + {"tuple", []ArgumentMarshaling{{Name: "x", Type: "tuple", Components: []ArgumentMarshaling{{Name: "x", Type: "bytes32"}}}}, 32}, + {"tuple", []ArgumentMarshaling{{Name: "x", Type: "tuple", Components: []ArgumentMarshaling{{Name: "x", Type: "bytes32[2]"}, {Name: "y", Type: "uint256"}}}}, 32 * (2 + 1)}, + } + + for i, data := range testCases { + typ, err := NewType(data.typ, "", data.components) + if err != nil { + t.Errorf("type %q: failed to parse type string: %v", data.typ, err) + } + + result := getTypeSize(typ) + if result != data.typSize { + t.Errorf("case %d type %q: get type size error: actual: %d expected: %d", i, data.typ, result, data.typSize) + } + } +} + +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 new file mode 100644 index 0000000..905b5ce --- /dev/null +++ b/accounts/abi/unpack.go @@ -0,0 +1,329 @@ +// 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 abi + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/common" +) + +var ( + // MaxUint256 is the maximum value that can be represented by a uint256. + MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1) + // MaxInt256 is the maximum value that can be represented by a int256. + MaxInt256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 255), common.Big1) +) + +// ReadInteger reads the integer based on its kind and returns the appropriate value. +func ReadInteger(typ Type, b []byte) (interface{}, error) { + ret := new(big.Int).SetBytes(b) + + if typ.T == UintTy { + u64, isu64 := ret.Uint64(), ret.IsUint64() + switch typ.Size { + case 8: + if !isu64 || u64 > math.MaxUint8 { + return nil, errBadUint8 + } + return byte(u64), nil + case 16: + if !isu64 || u64 > math.MaxUint16 { + return nil, errBadUint16 + } + return uint16(u64), nil + case 32: + if !isu64 || u64 > math.MaxUint32 { + return nil, errBadUint32 + } + return uint32(u64), nil + case 64: + if !isu64 { + return nil, errBadUint64 + } + return u64, nil + default: + // the only case left for unsigned integer is uint256. + return ret, nil + } + } + + // big.SetBytes can't tell if a number is negative or positive in itself. + // On EVM, if the returned number > max int256, it is negative. + // A number is > max int256 if the bit at position 255 is set. + if ret.Bit(255) == 1 { + ret.Add(MaxUint256, new(big.Int).Neg(ret)) + ret.Add(ret, common.Big1) + ret.Neg(ret) + } + i64, isi64 := ret.Int64(), ret.IsInt64() + switch typ.Size { + case 8: + if !isi64 || i64 < math.MinInt8 || i64 > math.MaxInt8 { + return nil, errBadInt8 + } + return int8(i64), nil + case 16: + if !isi64 || i64 < math.MinInt16 || i64 > math.MaxInt16 { + return nil, errBadInt16 + } + return int16(i64), nil + case 32: + if !isi64 || i64 < math.MinInt32 || i64 > math.MaxInt32 { + return nil, errBadInt32 + } + return int32(i64), nil + case 64: + if !isi64 { + return nil, errBadInt64 + } + return i64, nil + default: + // the only case left for integer is int256 + + return ret, nil + } +} + +// readBool reads a bool. +func readBool(word []byte) (bool, error) { + for _, b := range word[:31] { + if b != 0 { + return false, errBadBool + } + } + switch word[31] { + case 0: + return false, nil + case 1: + return true, nil + default: + return false, errBadBool + } +} + +// A function type is simply the address with the function selection signature at the end. +// +// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) +func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { + if t.T != FunctionTy { + return [24]byte{}, errors.New("abi: invalid type in call to make function type byte array") + } + if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 { + err = fmt.Errorf("abi: got improperly encoded function type, got %v", word) + } else { + copy(funcTy[:], word[0:24]) + } + return +} + +// ReadFixedBytes uses reflection to create a fixed array to be read from. +func ReadFixedBytes(t Type, word []byte) (interface{}, error) { + if t.T != FixedBytesTy { + return nil, errors.New("abi: invalid type in call to make fixed byte array") + } + // convert + array := reflect.New(t.GetType()).Elem() + + reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) + return array.Interface(), nil +} + +// forEachUnpack iteratively unpack elements. +func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { + if size < 0 { + return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) + } + if start+32*size > len(output) { + return nil, fmt.Errorf("abi: cannot marshal into go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size) + } + + // this value will become our slice or our array, depending on the type + var refSlice reflect.Value + + switch t.T { + case SliceTy: + // declare our slice + refSlice = reflect.MakeSlice(t.GetType(), size, size) + case ArrayTy: + // declare our array + refSlice = reflect.New(t.GetType()).Elem() + default: + return nil, errors.New("abi: invalid type in array/slice unpacking stage") + } + + // Arrays have packed elements, resulting in longer unpack steps. + // Slices have just 32 bytes per element (pointing to the contents). + elemSize := getTypeSize(*t.Elem) + + for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { + inter, err := toGoType(i, *t.Elem, output) + if err != nil { + return nil, err + } + + // append the item to our reflect slice + refSlice.Index(j).Set(reflect.ValueOf(inter)) + } + + // return the interface + return refSlice.Interface(), nil +} + +func forTupleUnpack(t Type, output []byte) (interface{}, error) { + retval := reflect.New(t.GetType()).Elem() + virtualArgs := 0 + for index, elem := range t.TupleElems { + marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) + if err != nil { + return nil, err + } + if elem.T == ArrayTy && !isDynamicType(*elem) { + // If we have a static array, like [3]uint256, these are coded as + // just like uint256,uint256,uint256. + // This means that we need to add two 'virtual' arguments when + // we count the index from now on. + // + // Array values nested multiple levels deep are also encoded inline: + // [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256 + // + // Calculate the full array size to get the correct offset for the next argument. + // Decrement it by 1, as the normal index increment is still applied. + virtualArgs += getTypeSize(*elem)/32 - 1 + } else if elem.T == TupleTy && !isDynamicType(*elem) { + // If we have a static tuple, like (uint256, bool, uint256), these are + // coded as just like uint256,bool,uint256 + virtualArgs += getTypeSize(*elem)/32 - 1 + } + retval.Field(index).Set(reflect.ValueOf(marshalledValue)) + } + return retval.Interface(), nil +} + +// toGoType parses the output bytes and recursively assigns the value of these bytes +// into a go type with accordance with the ABI spec. +func toGoType(index int, t Type, output []byte) (interface{}, error) { + if index+32 > len(output) { + return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) + } + + var ( + returnOutput []byte + begin, length int + err error + ) + + // if we require a length prefix, find the beginning word and size returned. + if t.requiresLengthPrefix() { + begin, length, err = lengthPrefixPointsTo(index, output) + if err != nil { + return nil, err + } + } else { + returnOutput = output[index : index+32] + } + + switch t.T { + case TupleTy: + if isDynamicType(t) { + begin, err := tuplePointsTo(index, output) + if err != nil { + return nil, err + } + return forTupleUnpack(t, output[begin:]) + } + return forTupleUnpack(t, output[index:]) + case SliceTy: + return forEachUnpack(t, output[begin:], 0, length) + case ArrayTy: + if isDynamicType(*t.Elem) { + offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]) + if offset > uint64(len(output)) { + return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output)) + } + return forEachUnpack(t, output[offset:], 0, t.Size) + } + return forEachUnpack(t, output[index:], 0, t.Size) + case StringTy: // variable arrays are written at the end of the return bytes + return string(output[begin : begin+length]), nil + case IntTy, UintTy: + return ReadInteger(t, returnOutput) + case BoolTy: + return readBool(returnOutput) + case AddressTy: + return common.BytesToAddress(returnOutput), nil + case HashTy: + return common.BytesToHash(returnOutput), nil + case BytesTy: + return output[begin : begin+length], nil + case FixedBytesTy: + return ReadFixedBytes(t, returnOutput) + case FunctionTy: + return readFunctionType(t, returnOutput) + default: + return nil, fmt.Errorf("abi: unknown type %v", t.T) + } +} + +// lengthPrefixPointsTo interprets a 32 byte slice as an offset and then determines which indices to look to decode the type. +func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { + bigOffsetEnd := new(big.Int).SetBytes(output[index : index+32]) + bigOffsetEnd.Add(bigOffsetEnd, common.Big32) + outputLength := big.NewInt(int64(len(output))) + + if bigOffsetEnd.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", bigOffsetEnd, outputLength) + } + + if bigOffsetEnd.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi offset larger than int64: %v", bigOffsetEnd) + } + + offsetEnd := int(bigOffsetEnd.Uint64()) + lengthBig := new(big.Int).SetBytes(output[offsetEnd-32 : offsetEnd]) + + totalSize := new(big.Int).Add(bigOffsetEnd, lengthBig) + if totalSize.BitLen() > 63 { + return 0, 0, fmt.Errorf("abi: length larger than int64: %v", totalSize) + } + + if totalSize.Cmp(outputLength) > 0 { + return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %v require %v", outputLength, totalSize) + } + start = int(bigOffsetEnd.Uint64()) + length = int(lengthBig.Uint64()) + return +} + +// tuplePointsTo resolves the location reference for dynamic tuple. +func tuplePointsTo(index int, output []byte) (start int, err error) { + offset := new(big.Int).SetBytes(output[index : index+32]) + outputLen := big.NewInt(int64(len(output))) + + if offset.Cmp(outputLen) > 0 { + return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen) + } + if offset.BitLen() > 63 { + return 0, fmt.Errorf("abi offset larger than int64: %v", offset) + } + return int(offset.Uint64()), nil +} diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go new file mode 100644 index 0000000..29891ec --- /dev/null +++ b/accounts/abi/unpack_test.go @@ -0,0 +1,1119 @@ +// 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 abi + +import ( + "bytes" + "encoding/hex" + "fmt" + "math" + "math/big" + "reflect" + "strconv" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +// 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 + def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(def)) + if err != nil { + t.Fatalf("invalid ABI definition %s: %v", def, err) + } + encb, err := hex.DecodeString(test.packed) + if err != nil { + t.Fatalf("invalid hex %s: %v", test.packed, err) + } + out, err := abi.Unpack("method", encb) + if err != nil { + t.Errorf("test %d (%v) failed: %v", i, test.def, err) + return + } + if !reflect.DeepEqual(test.unpacked, ConvertType(out[0], test.unpacked)) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.unpacked, out[0]) + } + }) + } +} + +type unpackTest struct { + def string // ABI definition JSON + enc string // evm return data + want interface{} // the expected output + err string // empty or error if expected +} + +func (test unpackTest) checkError(err error) error { + if err != nil { + if len(test.err) == 0 { + return fmt.Errorf("expected no err but got: %v", err) + } else if err.Error() != test.err { + return fmt.Errorf("expected err: '%v' got err: %q", test.err, err) + } + } else if len(test.err) > 0 { + return fmt.Errorf("expected err: %v but got none", test.err) + } + return nil +} + +var unpackTests = []unpackTest{ + // Bools + { + def: `[{ "type": "bool" }]`, + enc: "0000000000000000000000000000000000000000000000000001000000000001", + want: false, + err: "abi: improperly encoded boolean value", + }, + { + def: `[{ "type": "bool" }]`, + enc: "0000000000000000000000000000000000000000000000000000000000000003", + want: false, + err: "abi: improperly encoded boolean value", + }, + // Integers + { + def: `[{"type": "uint32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: uint16(0), + err: "abi: cannot unmarshal uint32 in to uint16", + }, + { + def: `[{"type": "uint17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: uint16(0), + err: "abi: cannot unmarshal *big.Int in to uint16", + }, + { + def: `[{"type": "int32"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: int16(0), + err: "abi: cannot unmarshal int32 in to int16", + }, + { + def: `[{"type": "int17"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000001", + want: int16(0), + err: "abi: cannot unmarshal *big.Int in to int16", + }, + { + def: `[{"type": "bytes"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", + want: [32]byte{1}, + }, + { + def: `[{"type": "bytes32"}]`, + enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", + want: []byte(nil), + err: "abi: cannot unmarshal [32]uint8 in to []uint8", + }, + { + def: `[{"name":"___","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + Intone *big.Int + }{IntOne: big.NewInt(1)}, + }, + { + def: `[{"name":"int_one","type":"int256"},{"name":"IntOne","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'IntOne'", + }, + { + def: `[{"name":"int","type":"int256"},{"name":"Int","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'Int'", + }, + { + def: `[{"name":"int","type":"int256"},{"name":"_int","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'Int'", + }, + { + def: `[{"name":"Int","type":"int256"},{"name":"_int","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: multiple outputs mapping to the same struct field 'Int'", + }, + { + def: `[{"name":"Int","type":"int256"},{"name":"_","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + Int1 *big.Int + Int2 *big.Int + }{}, + err: "abi: purely underscored output cannot unpack to struct", + }, + // Make sure only the first argument is consumed + { + def: `[{"name":"int_one","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"name":"int__one","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"name":"int_one_","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"type":"bool"}]`, + enc: "", + want: false, + 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 unmarshal an empty string while arguments are expected", + }, + { + def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`, + enc: "", + want: false, + }, +} + +// 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 + def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(def)) + if err != nil { + t.Fatalf("invalid ABI definition %s: %v", def, err) + } + encb, err := hex.DecodeString(test.enc) + if err != nil { + t.Fatalf("invalid hex %s: %v", test.enc, err) + } + outptr := reflect.New(reflect.TypeOf(test.want)) + err = abi.UnpackIntoInterface(outptr.Interface(), "method", encb) + if err := test.checkError(err); err != nil { + t.Errorf("test %d (%v) failed: %v", i, test.def, err) + return + } + out := outptr.Elem().Interface() + if !reflect.DeepEqual(test.want, out) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out) + } + }) + } +} + +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) + } + + var ( + marshalledReturn32 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000230783132333435363738393000000000000000000000000000000000000000003078303938373635343332310000000000000000000000000000000000000000") + marshalledReturn15 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000230783031323334350000000000000000000000000000000000000000000000003078393837363534000000000000000000000000000000000000000000000000") + + out32 [][32]byte + out15 [][15]byte + ) + + // test 32 + err = abi.UnpackIntoInterface(&out32, "testDynamicFixedBytes32", marshalledReturn32) + if err != nil { + t.Fatal(err) + } + if len(out32) != 2 { + t.Fatalf("expected array with 2 values, got %d", len(out32)) + } + expected := common.Hex2Bytes("3078313233343536373839300000000000000000000000000000000000000000") + if !bytes.Equal(out32[0][:], expected) { + t.Errorf("expected %x, got %x\n", expected, out32[0]) + } + expected = common.Hex2Bytes("3078303938373635343332310000000000000000000000000000000000000000") + if !bytes.Equal(out32[1][:], expected) { + t.Errorf("expected %x, got %x\n", expected, out32[1]) + } + + // test 15 + err = abi.UnpackIntoInterface(&out15, "testDynamicFixedBytes32", marshalledReturn15) + if err != nil { + t.Fatal(err) + } + if len(out15) != 2 { + t.Fatalf("expected array with 2 values, got %d", len(out15)) + } + expected = common.Hex2Bytes("307830313233343500000000000000") + if !bytes.Equal(out15[0][:], expected) { + t.Errorf("expected %x, got %x\n", expected, out15[0]) + } + expected = common.Hex2Bytes("307839383736353400000000000000") + if !bytes.Equal(out15[1][:], expected) { + t.Errorf("expected %x, got %x\n", expected, out15[1]) + } +} + +type methodMultiOutput struct { + Int *big.Int + String string +} + +func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) { + const definition = `[ + { "name" : "multi", "type": "function", "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` + var expected = methodMultiOutput{big.NewInt(1), "hello"} + + abi, err := JSON(strings.NewReader(definition)) + require.NoError(err) + // using buff to make the code readable + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) + buff.Write(common.RightPadBytes([]byte(expected.String), 32)) + return abi, buff.Bytes(), expected +} + +func TestMethodMultiReturn(t *testing.T) { + t.Parallel() + type reversed struct { + String string + Int *big.Int + } + + newInterfaceSlice := func(len int) interface{} { + slice := make([]interface{}, len) + return &slice + } + + abi, data, expected := methodMultiReturn(require.New(t)) + bigint := new(big.Int) + var testCases = []struct { + dest interface{} + expected interface{} + error string + name string + }{{ + &methodMultiOutput{}, + &expected, + "", + "Can unpack into structure", + }, { + &reversed{}, + &reversed{expected.String, expected.Int}, + "", + "Can unpack into reversed structure", + }, { + &[]interface{}{&bigint, new(string)}, + &[]interface{}{&expected.Int, &expected.String}, + "", + "Can unpack into a slice", + }, { + &[]interface{}{&bigint, ""}, + &[]interface{}{&expected.Int, expected.String}, + "", + "Can unpack into a slice without indirection", + }, { + &[2]interface{}{&bigint, new(string)}, + &[2]interface{}{&expected.Int, &expected.String}, + "", + "Can unpack into an array", + }, { + &[2]interface{}{}, + &[2]interface{}{expected.Int, expected.String}, + "", + "Can unpack into interface array", + }, { + newInterfaceSlice(2), + &[]interface{}{expected.Int, expected.String}, + "", + "Can unpack into interface slice", + }, { + &[]interface{}{new(int), new(int)}, + &[]interface{}{&expected.Int, &expected.String}, + "abi: cannot unmarshal *big.Int in to int", + "Can not unpack into a slice with wrong types", + }, { + &[]interface{}{new(int)}, + &[]interface{}{}, + "abi: insufficient number of arguments for unpack, want 2, got 1", + "Can not unpack into a slice with wrong types", + }} + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + err := abi.UnpackIntoInterface(tc.dest, "multi", data) + if tc.error == "" { + require.Nil(err, "Should be able to unpack method outputs.") + require.Equal(tc.expected, tc.dest) + } else { + require.EqualError(err, tc.error) + } + }) + } +} + +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 { + t.Fatal(err) + } + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008")) + + ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9} + ret2, ret2Exp := new(uint64), uint64(8) + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("array result", *ret1, "!= Expected", ret1Exp) + } + if *ret2 != ret2Exp { + t.Error("int result", *ret2, "!= Expected", ret2Exp) + } +} + +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 { + t.Fatal(err) + } + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000")) + temp, _ := new(big.Int).SetString("30000000000000000000", 10) + ret1, ret1Exp := new([3]*big.Int), [3]*big.Int{big.NewInt(1545304298), big.NewInt(6), temp} + ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f") + ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"} + ret4, ret4Exp := new(bool), false + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("big.Int array result", *ret1, "!= Expected", ret1Exp) + } + if !reflect.DeepEqual(*ret2, ret2Exp) { + t.Error("address result", *ret2, "!= Expected", ret2Exp) + } + if !reflect.DeepEqual(*ret3, ret3Exp) { + t.Error("string array result", *ret3, "!= Expected", ret3Exp) + } + if !reflect.DeepEqual(*ret4, ret4Exp) { + t.Error("bool result", *ret4, "!= Expected", ret4Exp) + } +} + +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 { + t.Fatal(err) + } + buff := new(bytes.Buffer) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0] offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000120")) // output[1] offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[0] length + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0][0] offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // output[0][1] offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008")) // output[0][0] length + buff.Write(common.Hex2Bytes("657468657265756d000000000000000000000000000000000000000000000000")) // output[0][0] value + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000b")) // output[0][1] length + buff.Write(common.Hex2Bytes("676f2d657468657265756d000000000000000000000000000000000000000000")) // output[0][1] value + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[1] length + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000064")) // output[1][0] value + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000065")) // output[1][1] value + ret1, ret1Exp := new([]string), []string{"ethereum", "go-ethereum"} + ret2, ret2Exp := new([]*big.Int), []*big.Int{big.NewInt(100), big.NewInt(101)} + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("string slice result", *ret1, "!= Expected", ret1Exp) + } + if !reflect.DeepEqual(*ret2, ret2Exp) { + t.Error("uint256 slice result", *ret2, "!= Expected", ret2Exp) + } +} + +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, + // so that it does not read content from the previous array argument. + const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]` + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + // construct the test array, each 3 char element is joined with 61 '0' chars, + // to from the ((3 + 61) * 0.5) = 32 byte elements in the array. + buff.Write(common.Hex2Bytes(strings.Join([]string{ + "", //empty, to apply the 61-char separator to the first element as well. + "111", "112", "113", "121", "122", "123", + "211", "212", "213", "221", "222", "223", + "311", "312", "313", "321", "322", "323", + "411", "412", "413", "421", "422", "423", + }, "0000000000000000000000000000000000000000000000000000000000000"))) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000009876")) + + ret1, ret1Exp := new([4][2][3]uint64), [4][2][3]uint64{ + {{0x111, 0x112, 0x113}, {0x121, 0x122, 0x123}}, + {{0x211, 0x212, 0x213}, {0x221, 0x222, 0x223}}, + {{0x311, 0x312, 0x313}, {0x321, 0x322, 0x323}}, + {{0x411, 0x412, 0x413}, {0x421, 0x422, 0x423}}, + } + ret2, ret2Exp := new(uint64), uint64(0x9876) + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*ret1, ret1Exp) { + t.Error("array result", *ret1, "!= Expected", ret1Exp) + } + if *ret2 != ret2Exp { + t.Error("int result", *ret2, "!= Expected", ret2Exp) + } +} + +func TestUnmarshal(t *testing.T) { + t.Parallel() + const definition = `[ + { "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] }, + { "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] }, + { "name" : "bytes", "type": "function", "outputs": [ { "type": "bytes" } ] }, + { "name" : "fixed", "type": "function", "outputs": [ { "type": "bytes32" } ] }, + { "name" : "multi", "type": "function", "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, + { "name" : "intArraySingle", "type": "function", "outputs": [ { "type": "uint256[3]" } ] }, + { "name" : "addressSliceSingle", "type": "function", "outputs": [ { "type": "address[]" } ] }, + { "name" : "addressSliceDouble", "type": "function", "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, + { "name" : "mixedBytes", "type": "function", "stateMutability" : "view", "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + buff := new(bytes.Buffer) + + // marshall mixed bytes (mixedBytes) + p0, p0Exp := []byte{}, common.Hex2Bytes("01020000000000000000") + p1, p1Exp := [32]byte{}, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff") + mixedBytes := []interface{}{&p0, &p1} + + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff")) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a")) + buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000")) + + err = abi.UnpackIntoInterface(&mixedBytes, "mixedBytes", buff.Bytes()) + if err != nil { + t.Error(err) + } else { + if !bytes.Equal(p0, p0Exp) { + t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0) + } + + if !bytes.Equal(p1[:], p1Exp) { + t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1) + } + } + + // marshal int + var Int *big.Int + err = abi.UnpackIntoInterface(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + if err != nil { + t.Error(err) + } + + if Int == nil || Int.Cmp(big.NewInt(1)) != 0 { + t.Error("expected Int to be 1 got", Int) + } + + // marshal bool + var Bool bool + err = abi.UnpackIntoInterface(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + if err != nil { + t.Error(err) + } + + if !Bool { + t.Error("expected Bool to be true") + } + + // marshal dynamic bytes max length 32 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + bytesOut := common.RightPadBytes([]byte("hello"), 32) + buff.Write(bytesOut) + + var Bytes []byte + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(Bytes, bytesOut) { + t.Errorf("expected %x got %x", bytesOut, Bytes) + } + + // marshall dynamic bytes max length 64 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) + bytesOut = common.RightPadBytes([]byte("hello"), 64) + buff.Write(bytesOut) + + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(Bytes, bytesOut) { + t.Errorf("expected %x got %x", bytesOut, Bytes) + } + + // marshall dynamic bytes max length 64 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f")) + bytesOut = common.RightPadBytes([]byte("hello"), 64) + buff.Write(bytesOut) + + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(Bytes, bytesOut[:len(bytesOut)-1]) { + t.Errorf("expected %x got %x", bytesOut[:len(bytesOut)-1], Bytes) + } + + // marshal dynamic bytes output empty + err = abi.UnpackIntoInterface(&Bytes, "bytes", nil) + if err == nil { + t.Error("expected error") + } + + // marshal dynamic bytes length 5 + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) + buff.Write(common.RightPadBytes([]byte("hello"), 32)) + + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) + if err != nil { + t.Error(err) + } + + if !bytes.Equal(Bytes, []byte("hello")) { + t.Errorf("expected %x got %x", bytesOut, Bytes) + } + + // marshal dynamic bytes length 5 + buff.Reset() + buff.Write(common.RightPadBytes([]byte("hello"), 32)) + + var hash common.Hash + err = abi.UnpackIntoInterface(&hash, "fixed", buff.Bytes()) + if err != nil { + t.Error(err) + } + + helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32)) + if hash != helloHash { + t.Errorf("Expected %x to equal %x", hash, helloHash) + } + + // marshal error + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) + if err == nil { + t.Error("expected error") + } + + err = abi.UnpackIntoInterface(&Bytes, "multi", make([]byte, 64)) + if err == nil { + t.Error("expected error") + } + + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003")) + // marshal int array + var intArray [3]*big.Int + err = abi.UnpackIntoInterface(&intArray, "intArraySingle", buff.Bytes()) + if err != nil { + t.Error(err) + } + var testAgainstIntArray [3]*big.Int + testAgainstIntArray[0] = big.NewInt(1) + testAgainstIntArray[1] = big.NewInt(2) + testAgainstIntArray[2] = big.NewInt(3) + + for i, Int := range intArray { + if Int.Cmp(testAgainstIntArray[i]) != 0 { + t.Errorf("expected %v, got %v", testAgainstIntArray[i], Int) + } + } + // marshal address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) + + var outAddr []common.Address + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) + if err != nil { + t.Fatal("didn't expect error:", err) + } + + if len(outAddr) != 1 { + t.Fatal("expected 1 item, got", len(outAddr)) + } + + if outAddr[0] != (common.Address{1}) { + t.Errorf("expected %x, got %x", common.Address{1}, outAddr[0]) + } + + // marshal multiple address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // size + buff.Write(common.Hex2Bytes("0000000000000000000000000200000000000000000000000000000000000000")) + buff.Write(common.Hex2Bytes("0000000000000000000000000300000000000000000000000000000000000000")) + + var outAddrStruct struct { + A []common.Address + B []common.Address + } + err = abi.UnpackIntoInterface(&outAddrStruct, "addressSliceDouble", buff.Bytes()) + if err != nil { + t.Fatal("didn't expect error:", err) + } + + if len(outAddrStruct.A) != 1 { + t.Fatal("expected 1 item, got", len(outAddrStruct.A)) + } + + if outAddrStruct.A[0] != (common.Address{1}) { + t.Errorf("expected %x, got %x", common.Address{1}, outAddrStruct.A[0]) + } + + if len(outAddrStruct.B) != 2 { + t.Fatal("expected 1 item, got", len(outAddrStruct.B)) + } + + if outAddrStruct.B[0] != (common.Address{2}) { + t.Errorf("expected %x, got %x", common.Address{2}, outAddrStruct.B[0]) + } + if outAddrStruct.B[1] != (common.Address{3}) { + t.Errorf("expected %x, got %x", common.Address{3}, outAddrStruct.B[1]) + } + + // marshal invalid address slice + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100")) + + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) + if err == nil { + t.Fatal("expected error:", err) + } +} + +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 { + t.Fatal(err) + } + buff := new(bytes.Buffer) + + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // ret[a] = 1 + buff.Write(common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) // ret[b] = -1 + + // If the result is single tuple, use struct as return value container directly. + type v struct { + A *big.Int + B *big.Int + } + type r struct { + Result v + } + var ret0 = new(r) + err = abi.UnpackIntoInterface(ret0, "tuple", buff.Bytes()) + + if err != nil { + t.Error(err) + } else { + if ret0.Result.A.Cmp(big.NewInt(1)) != 0 { + t.Errorf("unexpected value unpacked: want %x, got %x", 1, ret0.Result.A) + } + if ret0.Result.B.Cmp(big.NewInt(-1)) != 0 { + t.Errorf("unexpected value unpacked: want %x, got %x", -1, ret0.Result.B) + } + } + + // Test nested tuple + const nestedTuple = `[{"name":"tuple","type":"function","outputs":[ + {"type":"tuple","name":"s","components":[{"type":"uint256","name":"a"},{"type":"uint256[]","name":"b"},{"type":"tuple[]","name":"c","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}]}, + {"type":"tuple","name":"t","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}, + {"type":"uint256","name":"a"} + ]}]` + + abi, err = JSON(strings.NewReader(nestedTuple)) + if err != nil { + t.Fatal(err) + } + buff.Reset() + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // s offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")) // t.X = 0 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // t.Y = 1 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // a = 1 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.A = 1 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060")) // s.B offset + buff.Write(common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0")) // s.C offset + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.B length + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.B[0] = 1 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.B[0] = 2 + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C length + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.C[0].X + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C[0].Y + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C[1].X + buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.C[1].Y + + type T struct { + X *big.Int `abi:"x"` + Z *big.Int `abi:"y"` // Test whether the abi tag works. + } + + type S struct { + A *big.Int + B []*big.Int + C []T + } + + type Ret struct { + FieldS S `abi:"s"` + FieldT T `abi:"t"` + A *big.Int + } + var ret Ret + var expected = Ret{ + FieldS: S{ + A: big.NewInt(1), + B: []*big.Int{big.NewInt(1), big.NewInt(2)}, + C: []T{ + {big.NewInt(1), big.NewInt(2)}, + {big.NewInt(2), big.NewInt(1)}, + }, + }, + FieldT: T{ + big.NewInt(0), big.NewInt(1), + }, + A: big.NewInt(1), + } + + err = abi.UnpackIntoInterface(&ret, "tuple", buff.Bytes()) + if err != nil { + t.Error(err) + } + if reflect.DeepEqual(ret, expected) { + t.Error("unexpected unpack value") + } +} + +func TestOOMMaliciousInput(t *testing.T) { + t.Parallel() + oomTests := []unpackTest{ + { + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000003" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Length larger than 64 bits + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset very large (over 64 bits) + def: `[{"type": "uint8[]"}]`, + enc: "00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset very large (below 64 bits) + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000007ffffffffff00020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Offset negative (as 64 bit) + def: `[{"type": "uint8[]"}]`, + enc: "000000000000000000000000000000000000000000000000f000000000000020" + // offset + "0000000000000000000000000000000000000000000000000000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + + { // Negative length + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "000000000000000000000000000000000000000000000000f000000000000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + { // Very large length + def: `[{"type": "uint8[]"}]`, + enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset + "0000000000000000000000000000000000000000000000007fffffffff000002" + // num elems + "0000000000000000000000000000000000000000000000000000000000000001" + // elem 1 + "0000000000000000000000000000000000000000000000000000000000000002", // elem 2 + }, + } + for i, test := range oomTests { + def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(def)) + if err != nil { + t.Fatalf("invalid ABI definition %s: %v", def, err) + } + encb, err := hex.DecodeString(test.enc) + if err != nil { + t.Fatalf("invalid hex: %s" + test.enc) + } + _, err = abi.Methods["method"].Outputs.UnpackValues(encb) + if err == nil { + t.Fatalf("Expected error on malicious input, test %d", i) + } + } +} + +func TestPackAndUnpackIncompatibleNumber(t *testing.T) { + t.Parallel() + var encodeABI Arguments + uint256Ty, err := NewType("uint256", "", nil) + if err != nil { + panic(err) + } + encodeABI = Arguments{ + {Type: uint256Ty}, + } + + maxU64, ok := new(big.Int).SetString(strconv.FormatUint(math.MaxUint64, 10), 10) + if !ok { + panic("bug") + } + maxU64Plus1 := new(big.Int).Add(maxU64, big.NewInt(1)) + cases := []struct { + decodeType string + inputValue *big.Int + err error + expectValue interface{} + }{ + { + decodeType: "uint8", + inputValue: big.NewInt(math.MaxUint8 + 1), + err: errBadUint8, + }, + { + decodeType: "uint8", + inputValue: big.NewInt(math.MaxUint8), + err: nil, + expectValue: uint8(math.MaxUint8), + }, + { + decodeType: "uint16", + inputValue: big.NewInt(math.MaxUint16 + 1), + err: errBadUint16, + }, + { + decodeType: "uint16", + inputValue: big.NewInt(math.MaxUint16), + err: nil, + expectValue: uint16(math.MaxUint16), + }, + { + decodeType: "uint32", + inputValue: big.NewInt(math.MaxUint32 + 1), + err: errBadUint32, + }, + { + decodeType: "uint32", + inputValue: big.NewInt(math.MaxUint32), + err: nil, + expectValue: uint32(math.MaxUint32), + }, + { + decodeType: "uint64", + inputValue: maxU64Plus1, + err: errBadUint64, + }, + { + decodeType: "uint64", + inputValue: maxU64, + err: nil, + expectValue: uint64(math.MaxUint64), + }, + { + decodeType: "uint256", + inputValue: maxU64Plus1, + err: nil, + expectValue: maxU64Plus1, + }, + { + decodeType: "int8", + inputValue: big.NewInt(math.MaxInt8 + 1), + err: errBadInt8, + }, + { + decodeType: "int8", + inputValue: big.NewInt(math.MinInt8 - 1), + err: errBadInt8, + }, + { + decodeType: "int8", + inputValue: big.NewInt(math.MaxInt8), + err: nil, + expectValue: int8(math.MaxInt8), + }, + { + decodeType: "int16", + inputValue: big.NewInt(math.MaxInt16 + 1), + err: errBadInt16, + }, + { + decodeType: "int16", + inputValue: big.NewInt(math.MinInt16 - 1), + err: errBadInt16, + }, + { + decodeType: "int16", + inputValue: big.NewInt(math.MaxInt16), + err: nil, + expectValue: int16(math.MaxInt16), + }, + { + decodeType: "int32", + inputValue: big.NewInt(math.MaxInt32 + 1), + err: errBadInt32, + }, + { + decodeType: "int32", + inputValue: big.NewInt(math.MinInt32 - 1), + err: errBadInt32, + }, + { + decodeType: "int32", + inputValue: big.NewInt(math.MaxInt32), + err: nil, + expectValue: int32(math.MaxInt32), + }, + { + decodeType: "int64", + inputValue: new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(1)), + err: errBadInt64, + }, + { + decodeType: "int64", + inputValue: new(big.Int).Sub(big.NewInt(math.MinInt64), big.NewInt(1)), + err: errBadInt64, + }, + { + decodeType: "int64", + inputValue: big.NewInt(math.MaxInt64), + err: nil, + expectValue: int64(math.MaxInt64), + }, + } + for i, testCase := range cases { + packed, err := encodeABI.Pack(testCase.inputValue) + if err != nil { + panic(err) + } + ty, err := NewType(testCase.decodeType, "", nil) + if err != nil { + panic(err) + } + decodeABI := Arguments{ + {Type: ty}, + } + decoded, err := decodeABI.Unpack(packed) + if err != testCase.err { + t.Fatalf("Expected error %v, actual error %v. case %d", testCase.err, err, i) + } + if err != nil { + continue + } + if !reflect.DeepEqual(decoded[0], testCase.expectValue) { + t.Fatalf("Expected value %v, actual value %v", testCase.expectValue, decoded[0]) + } + } +} diff --git a/accounts/abi/utils.go b/accounts/abi/utils.go new file mode 100644 index 0000000..b1537ca --- /dev/null +++ b/accounts/abi/utils.go @@ -0,0 +1,40 @@ +// 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 abi + +import "fmt" + +// ResolveNameConflict returns the next available name for a given thing. +// This helper can be used for lots of purposes: +// +// - In solidity function overloading is supported, this function can fix +// the name conflicts of overloaded functions. +// - In golang binding generation, the parameter(in function, event, error, +// and struct definition) name will be converted to camelcase style which +// may eventually lead to name conflicts. +// +// Name conflicts are mostly resolved by adding number suffix. e.g. if the abi contains +// Methods "send" and "send1", ResolveNameConflict would return "send2" for input "send". +func ResolveNameConflict(rawName string, used func(string) bool) string { + name := rawName + ok := used(name) + for idx := 0; ok; idx++ { + name = fmt.Sprintf("%s%d", rawName, idx) + ok = used(name) + } + return name +} diff --git a/accounts/accounts.go b/accounts/accounts.go new file mode 100644 index 0000000..b995498 --- /dev/null +++ b/accounts/accounts.go @@ -0,0 +1,226 @@ +// 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 accounts implements high level Ethereum account management. +package accounts + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "golang.org/x/crypto/sha3" +) + +// Account represents an Ethereum account located at a specific location defined +// by the optional URL field. +type Account struct { + Address common.Address `json:"address"` // Ethereum account address derived from the key + URL URL `json:"url"` // Optional resource locator within a backend +} + +const ( + MimetypeDataWithValidator = "data/validator" + MimetypeTypedData = "data/typed" + MimetypeClique = "application/x-clique-header" + MimetypeTextPlain = "text/plain" +) + +// Wallet represents a software or hardware wallet that might contain one or more +// accounts (derived from the same seed). +type Wallet interface { + // URL retrieves the canonical path under which this wallet is reachable. It is + // used by upper layers to define a sorting order over all wallets from multiple + // backends. + URL() URL + + // Status returns a textual status to aid the user in the current state of the + // wallet. It also returns an error indicating any failure the wallet might have + // encountered. + Status() (string, error) + + // Open initializes access to a wallet instance. It is not meant to unlock or + // decrypt account keys, rather simply to establish a connection to hardware + // wallets and/or to access derivation seeds. + // + // The passphrase parameter may or may not be used by the implementation of a + // particular wallet instance. The reason there is no passwordless open method + // is to strive towards a uniform wallet handling, oblivious to the different + // backend providers. + // + // Please note, if you open a wallet, you must close it to release any allocated + // resources (especially important when working with hardware wallets). + Open(passphrase string) error + + // Close releases any resources held by an open wallet instance. + Close() error + + // Accounts retrieves the list of signing accounts the wallet is currently aware + // of. For hierarchical deterministic wallets, the list will not be exhaustive, + // rather only contain the accounts explicitly pinned during account derivation. + Accounts() []Account + + // Contains returns whether an account is part of this particular wallet or not. + Contains(account Account) bool + + // Derive attempts to explicitly derive a hierarchical deterministic account at + // the specified derivation path. If requested, the derived account will be added + // to the wallet's tracked account list. + Derive(path DerivationPath, pin bool) (Account, error) + + // SelfDerive sets a base account derivation path from which the wallet attempts + // to discover non zero accounts and automatically add them to list of tracked + // accounts. + // + // Note, self derivation will increment the last component of the specified path + // opposed to descending into a child path to allow discovering accounts starting + // from non zero components. + // + // Some hardware wallets switched derivation paths through their evolution, so + // this method supports providing multiple bases to discover old user accounts + // too. Only the last base will be used to derive the next empty account. + // + // You can disable automatic account discovery by calling SelfDerive with a nil + // chain state reader. + SelfDerive(bases []DerivationPath, chain ethereum.ChainStateReader) + + // SignData requests the wallet to sign the hash of the given data + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code to verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignDataWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignData(account Account, mimeType string, data []byte) ([]byte, error) + + // SignDataWithPassphrase is identical to SignData, but also takes a password + // NOTE: there's a chance that an erroneous call might mistake the two strings, and + // supply password in the mimetype field, or vice versa. Thus, an implementation + // should never echo the mimetype or return the mimetype in the error-response + SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error) + + // SignText requests the wallet to sign the hash of a given piece of data, prefixed + // by the Ethereum prefix scheme + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code to verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignTextWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + // + // This method should return the signature in 'canonical' format, with v 0 or 1. + SignText(account Account, text []byte) ([]byte, error) + + // SignTextWithPassphrase is identical to Signtext, but also takes a password + SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error) + + // SignTx requests the wallet to sign the given transaction. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the wallet requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code to verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) + + // SignTxWithPassphrase is identical to SignTx, but also takes a password + SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) +} + +// Backend is a "wallet provider" that may contain a batch of accounts they can +// sign transactions with and upon request, do so. +type Backend interface { + // Wallets retrieves the list of wallets the backend is currently aware of. + // + // The returned wallets are not opened by default. For software HD wallets this + // means that no base seeds are decrypted, and for hardware wallets that no actual + // connection is established. + // + // The resulting wallet list will be sorted alphabetically based on its internal + // URL assigned by the backend. Since wallets (especially hardware) may come and + // go, the same wallet might appear at a different positions in the list during + // subsequent retrievals. + Wallets() []Wallet + + // Subscribe creates an async subscription to receive notifications when the + // backend detects the arrival or departure of a wallet. + Subscribe(sink chan<- WalletEvent) event.Subscription +} + +// TextHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextHash(data []byte) []byte { + hash, _ := TextAndHash(data) + return hash +} + +// TextAndHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextAndHash(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(msg)) + return hasher.Sum(nil), msg +} + +// WalletEventType represents the different event types that can be fired by +// the wallet subscription subsystem. +type WalletEventType int + +const ( + // WalletArrived is fired when a new wallet is detected either via USB or via + // a filesystem event in the keystore. + WalletArrived WalletEventType = iota + + // WalletOpened is fired when a wallet is successfully opened with the purpose + // of starting any background processes such as automatic key derivation. + WalletOpened + + // WalletDropped + WalletDropped +) + +// WalletEvent is an event fired by an account backend when a wallet arrival or +// departure is detected. +type WalletEvent struct { + Wallet Wallet // Wallet instance arrived or departed + Kind WalletEventType // Event type that happened in the system +} diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go new file mode 100644 index 0000000..2c4138a --- /dev/null +++ b/accounts/accounts_test.go @@ -0,0 +1,33 @@ +// 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 accounts + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func TestTextHash(t *testing.T) { + t.Parallel() + hash := TextHash([]byte("Hello Joe")) + want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b") + if !bytes.Equal(hash, want) { + t.Fatalf("wrong hash: %x", hash) + } +} diff --git a/accounts/errors.go b/accounts/errors.go new file mode 100644 index 0000000..03cb569 --- /dev/null +++ b/accounts/errors.go @@ -0,0 +1,67 @@ +// 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 accounts + +import ( + "errors" + "fmt" +) + +// ErrUnknownAccount is returned for any requested operation for which no backend +// provides the specified account. +var ErrUnknownAccount = errors.New("unknown account") + +// ErrUnknownWallet is returned for any requested operation for which no backend +// provides the specified wallet. +var ErrUnknownWallet = errors.New("unknown wallet") + +// ErrNotSupported is returned when an operation is requested from an account +// backend that it does not support. +var ErrNotSupported = errors.New("not supported") + +// ErrInvalidPassphrase is returned when a decryption operation receives a bad +// passphrase. +var ErrInvalidPassphrase = errors.New("invalid password") + +// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the +// second time. +var ErrWalletAlreadyOpen = errors.New("wallet already open") + +// ErrWalletClosed is returned if a wallet is offline. +var ErrWalletClosed = errors.New("wallet closed") + +// AuthNeededError is returned by backends for signing requests where the user +// is required to provide further authentication before signing can succeed. +// +// This usually means either that a password needs to be supplied, or perhaps a +// one time PIN code displayed by some hardware device. +type AuthNeededError struct { + Needed string // Extra authentication the user needs to provide +} + +// NewAuthNeededError creates a new authentication error with the extra details +// about the needed fields set. +func NewAuthNeededError(needed string) error { + return &AuthNeededError{ + Needed: needed, + } +} + +// Error implements the standard error interface. +func (err *AuthNeededError) Error() string { + return fmt.Sprintf("authentication needed: %s", err.Needed) +} diff --git a/accounts/external/backend.go b/accounts/external/backend.go new file mode 100644 index 0000000..6232275 --- /dev/null +++ b/accounts/external/backend.go @@ -0,0 +1,281 @@ +// 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 external + +import ( + "errors" + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "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/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/signer/core/apitypes" +) + +type ExternalBackend struct { + signers []accounts.Wallet +} + +func (eb *ExternalBackend) Wallets() []accounts.Wallet { + return eb.signers +} + +func NewExternalBackend(endpoint string) (*ExternalBackend, error) { + signer, err := NewExternalSigner(endpoint) + if err != nil { + return nil, err + } + return &ExternalBackend{ + signers: []accounts.Wallet{signer}, + }, nil +} + +func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + return event.NewSubscription(func(quit <-chan struct{}) error { + <-quit + return nil + }) +} + +// ExternalSigner provides an API to interact with an external signer (clef) +// It proxies request to the external signer while forwarding relevant +// request headers +type ExternalSigner struct { + client *rpc.Client + endpoint string + status string + cacheMu sync.RWMutex + cache []accounts.Account +} + +func NewExternalSigner(endpoint string) (*ExternalSigner, error) { + client, err := rpc.Dial(endpoint) + if err != nil { + return nil, err + } + extsigner := &ExternalSigner{ + client: client, + endpoint: endpoint, + } + // Check if reachable + version, err := extsigner.pingVersion() + if err != nil { + return nil, err + } + extsigner.status = fmt.Sprintf("ok [version=%v]", version) + return extsigner, nil +} + +func (api *ExternalSigner) URL() accounts.URL { + return accounts.URL{ + Scheme: "extapi", + Path: api.endpoint, + } +} + +func (api *ExternalSigner) Status() (string, error) { + return api.status, nil +} + +func (api *ExternalSigner) Open(passphrase string) error { + return errors.New("operation not supported on external signers") +} + +func (api *ExternalSigner) Close() error { + return errors.New("operation not supported on external signers") +} + +func (api *ExternalSigner) Accounts() []accounts.Account { + var accnts []accounts.Account + res, err := api.listAccounts() + if err != nil { + log.Error("account listing failed", "error", err) + return accnts + } + for _, addr := range res { + accnts = append(accnts, accounts.Account{ + URL: accounts.URL{ + Scheme: "extapi", + Path: api.endpoint, + }, + Address: addr, + }) + } + api.cacheMu.Lock() + api.cache = accnts + api.cacheMu.Unlock() + return accnts +} + +func (api *ExternalSigner) Contains(account accounts.Account) bool { + api.cacheMu.RLock() + defer api.cacheMu.RUnlock() + if api.cache == nil { + // If we haven't already fetched the accounts, it's time to do so now + api.cacheMu.RUnlock() + api.Accounts() + api.cacheMu.RLock() + } + for _, a := range api.cache { + if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) { + return true + } + } + return false +} + +func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + return accounts.Account{}, errors.New("operation not supported on external signers") +} + +func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { + log.Error("operation SelfDerive not supported on external signers") +} + +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + var res hexutil.Bytes + var signAddress = common.NewMixedcaseAddress(account.Address) + if err := api.client.Call(&res, "account_signData", + mimeType, + &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined + hexutil.Encode(data)); err != nil { + return nil, err + } + // If V is on 27/28-form, convert to 0/1 for Clique + if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) { + res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use + } + return res, nil +} + +func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { + var signature hexutil.Bytes + var signAddress = common.NewMixedcaseAddress(account.Address) + if err := api.client.Call(&signature, "account_signData", + accounts.MimetypeTextPlain, + &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined + hexutil.Encode(text)); err != nil { + return nil, err + } + if signature[64] == 27 || signature[64] == 28 { + // If clef is used as a backend, it may already have transformed + // the signature to ethereum-type signature. + signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1 + } + return signature, nil +} + +// signTransactionResult represents the signinig result returned by clef. +type signTransactionResult struct { + Raw hexutil.Bytes `json:"raw"` + Tx *types.Transaction `json:"tx"` +} + +// SignTx sends the transaction to the external signer. +// If chainID is nil, or tx.ChainID is zero, the chain ID will be assigned +// by the external signer. For non-legacy transactions, the chain ID of the +// transaction overrides the chainID parameter. +func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + data := hexutil.Bytes(tx.Data()) + var to *common.MixedcaseAddress + if tx.To() != nil { + t := common.NewMixedcaseAddress(*tx.To()) + to = &t + } + args := &apitypes.SendTxArgs{ + Input: &data, + Nonce: hexutil.Uint64(tx.Nonce()), + Value: hexutil.Big(*tx.Value()), + Gas: hexutil.Uint64(tx.Gas()), + To: to, + From: common.NewMixedcaseAddress(account.Address), + } + switch tx.Type() { + case types.LegacyTxType, types.AccessListTxType: + args.GasPrice = (*hexutil.Big)(tx.GasPrice()) + case types.DynamicFeeTxType, types.BlobTxType: + args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) + args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) + default: + return nil, fmt.Errorf("unsupported tx type %d", tx.Type()) + } + // We should request the default chain id that we're operating with + // (the chain we're executing on) + if chainID != nil && chainID.Sign() != 0 { + args.ChainID = (*hexutil.Big)(chainID) + } + if tx.Type() != types.LegacyTxType { + // However, if the user asked for a particular chain id, then we should + // use that instead. + if tx.ChainId().Sign() != 0 { + args.ChainID = (*hexutil.Big)(tx.ChainId()) + } + accessList := tx.AccessList() + args.AccessList = &accessList + } + if tx.Type() == types.BlobTxType { + args.BlobHashes = tx.BlobHashes() + sidecar := tx.BlobTxSidecar() + if sidecar == nil { + return nil, errors.New("blobs must be present for signing") + } + args.Blobs = sidecar.Blobs + args.Commitments = sidecar.Commitments + args.Proofs = sidecar.Proofs + } + + var res signTransactionResult + if err := api.client.Call(&res, "account_signTransaction", args); err != nil { + return nil, err + } + return res.Tx, nil +} + +func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + return []byte{}, errors.New("password-operations not supported on external signers") +} + +func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + return nil, errors.New("password-operations not supported on external signers") +} +func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return nil, errors.New("password-operations not supported on external signers") +} + +func (api *ExternalSigner) listAccounts() ([]common.Address, error) { + var res []common.Address + if err := api.client.Call(&res, "account_list"); err != nil { + return nil, err + } + return res, nil +} + +func (api *ExternalSigner) pingVersion() (string, error) { + var v string + if err := api.client.Call(&v, "account_version"); err != nil { + return "", err + } + return v, nil +} diff --git a/accounts/hd.go b/accounts/hd.go new file mode 100644 index 0000000..daca75e --- /dev/null +++ b/accounts/hd.go @@ -0,0 +1,180 @@ +// 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 accounts + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + "strings" +) + +// DefaultRootDerivationPath is the root path to which custom derivation endpoints +// are appended. As such, the first account will be at m/44'/60'/0'/0, the second +// at m/44'/60'/0'/1, etc. +var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0} + +// DefaultBaseDerivationPath is the base path from which custom derivation endpoints +// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second +// at m/44'/60'/0'/0/1, etc. +var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0} + +// LegacyLedgerBaseDerivationPath is the legacy base path from which custom derivation +// endpoints are incremented. As such, the first account will be at m/44'/60'/0'/0, the +// second at m/44'/60'/0'/1, etc. +var LegacyLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0} + +// DerivationPath represents the computer friendly version of a hierarchical +// deterministic wallet account derivation path. +// +// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +// defines derivation paths to be of the form: +// +// m / purpose' / coin_type' / account' / change / address_index +// +// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and +// SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns +// the `coin_type` 60' (or 0x8000003C) to Ethereum. +// +// The root path for Ethereum is m/44'/60'/0'/0 according to the specification +// from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone +// yet whether accounts should increment the last component or the children of +// that. We will go with the simpler approach of incrementing the last component. +type DerivationPath []uint32 + +// ParseDerivationPath converts a user specified derivation path string to the +// internal binary representation. +// +// Full derivation paths need to start with the `m/` prefix, relative derivation +// paths (which will get appended to the default root path) must not have prefixes +// in front of the first element. Whitespace is ignored. +func ParseDerivationPath(path string) (DerivationPath, error) { + var result DerivationPath + + // Handle absolute or relative paths + components := strings.Split(path, "/") + switch { + case len(components) == 0: + return nil, errors.New("empty derivation path") + + case strings.TrimSpace(components[0]) == "": + return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones") + + case strings.TrimSpace(components[0]) == "m": + components = components[1:] + + default: + result = append(result, DefaultRootDerivationPath...) + } + // All remaining components are relative, append one by one + if len(components) == 0 { + return nil, errors.New("empty derivation path") // Empty relative paths + } + for _, component := range components { + // Ignore any user added whitespace + component = strings.TrimSpace(component) + var value uint32 + + // Handle hardened paths + if strings.HasSuffix(component, "'") { + value = 0x80000000 + component = strings.TrimSpace(strings.TrimSuffix(component, "'")) + } + // Handle the non hardened component + bigval, ok := new(big.Int).SetString(component, 0) + if !ok { + return nil, fmt.Errorf("invalid component: %s", component) + } + max := math.MaxUint32 - value + if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 { + if value == 0 { + return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max) + } + return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max) + } + value += uint32(bigval.Uint64()) + + // Append and repeat + result = append(result, value) + } + return result, nil +} + +// String implements the stringer interface, converting a binary derivation path +// to its canonical representation. +func (path DerivationPath) String() string { + result := "m" + for _, component := range path { + var hardened bool + if component >= 0x80000000 { + component -= 0x80000000 + hardened = true + } + result = fmt.Sprintf("%s/%d", result, component) + if hardened { + result += "'" + } + } + return result +} + +// MarshalJSON turns a derivation path into its json-serialized string +func (path DerivationPath) MarshalJSON() ([]byte, error) { + return json.Marshal(path.String()) +} + +// UnmarshalJSON a json-serialized string back into a derivation path +func (path *DerivationPath) UnmarshalJSON(b []byte) error { + var dp string + var err error + if err = json.Unmarshal(b, &dp); err != nil { + return err + } + *path, err = ParseDerivationPath(dp) + return err +} + +// DefaultIterator creates a BIP-32 path iterator, which progresses by increasing the last component: +// i.e. m/44'/60'/0'/0/0, m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, ... m/44'/60'/0'/0/N. +func DefaultIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[len(path)-1]-- + return func() DerivationPath { + path[len(path)-1]++ + return path + } +} + +// LedgerLiveIterator creates a bip44 path iterator for Ledger Live. +// Ledger Live increments the third component rather than the fifth component +// i.e. m/44'/60'/0'/0/0, m/44'/60'/1'/0/0, m/44'/60'/2'/0/0, ... m/44'/60'/N'/0/0. +func LedgerLiveIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[2]-- + return func() DerivationPath { + // ledgerLivePathIterator iterates on the third component + path[2]++ + return path + } +} diff --git a/accounts/hd_test.go b/accounts/hd_test.go new file mode 100644 index 0000000..118ec51 --- /dev/null +++ b/accounts/hd_test.go @@ -0,0 +1,120 @@ +// 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 accounts + +import ( + "fmt" + "reflect" + "testing" +) + +// 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 + }{ + // Plain absolute derivation paths + {"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, + {"m/44'/60'/0'/0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + {"m/44'/60'/0'/128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, + {"m/2147483692/2147483708/2147483648/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + + // Plain relative derivation paths + {"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}}, + {"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}}, + {"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + {"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}}, + {"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + + // Hexadecimal absolute derivation paths + {"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, + {"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + {"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, + {"m/0x8000002C/0x8000003c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + {"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, + + // Hexadecimal relative derivation paths + {"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}}, + {"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}}, + {"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + {"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}}, + {"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, + + // Weird inputs just to ensure they work + {" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, + + // Invalid derivation paths + {"", nil}, // Empty relative derivation path + {"m", nil}, // Empty absolute derivation path + {"m/", nil}, // Missing last derivation component + {"/44'/60'/0'/0", nil}, // Absolute path without m prefix, might be user error + {"m/2147483648'", nil}, // Overflows 32 bit integer + {"m/-1'", nil}, // Cannot contain negative number + } + for i, tt := range tests { + if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) { + t.Errorf("test %d: parse mismatch: have %v (%v), want %v", i, path, err, tt.output) + } else if path == nil && err == nil { + t.Errorf("test %d: nil path and error: %v", i, err) + } + } +} + +func testDerive(t *testing.T, next func() DerivationPath, expected []string) { + t.Helper() + for i, want := range expected { + if have := next(); fmt.Sprintf("%v", have) != want { + t.Errorf("step %d, have %v, want %v", i, have, want) + } + } +} + +func TestHdPathIteration(t *testing.T) { + t.Parallel() + testDerive(t, DefaultIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1", + "m/44'/60'/0'/0/2", "m/44'/60'/0'/0/3", + "m/44'/60'/0'/0/4", "m/44'/60'/0'/0/5", + "m/44'/60'/0'/0/6", "m/44'/60'/0'/0/7", + "m/44'/60'/0'/0/8", "m/44'/60'/0'/0/9", + }) + + testDerive(t, DefaultIterator(LegacyLedgerBaseDerivationPath), + []string{ + "m/44'/60'/0'/0", "m/44'/60'/0'/1", + "m/44'/60'/0'/2", "m/44'/60'/0'/3", + "m/44'/60'/0'/4", "m/44'/60'/0'/5", + "m/44'/60'/0'/6", "m/44'/60'/0'/7", + "m/44'/60'/0'/8", "m/44'/60'/0'/9", + }) + + testDerive(t, LedgerLiveIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/1'/0/0", + "m/44'/60'/2'/0/0", "m/44'/60'/3'/0/0", + "m/44'/60'/4'/0/0", "m/44'/60'/5'/0/0", + "m/44'/60'/6'/0/0", "m/44'/60'/7'/0/0", + "m/44'/60'/8'/0/0", "m/44'/60'/9'/0/0", + }) +} diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go new file mode 100644 index 0000000..f7cf688 --- /dev/null +++ b/accounts/keystore/account_cache.go @@ -0,0 +1,308 @@ +// 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 keystore + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "path/filepath" + "slices" + "sort" + "strings" + "sync" + "time" + + mapset "github.com/deckarep/golang-set/v2" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// Minimum amount of time between cache reloads. This limit applies if the platform does +// not support change notifications. It also applies if the keystore directory does not +// exist yet, the code will attempt to create a watcher at most this often. +const minReloadInterval = 2 * time.Second + +// byURL defines the sorting order for accounts. +func byURL(a, b accounts.Account) int { + return a.URL.Cmp(b.URL) +} + +// AmbiguousAddrError is returned when attempting to unlock +// an address for which more than one file exists. +type AmbiguousAddrError struct { + Addr common.Address + Matches []accounts.Account +} + +func (err *AmbiguousAddrError) Error() string { + files := "" + for i, a := range err.Matches { + files += a.URL.Path + if i < len(err.Matches)-1 { + files += ", " + } + } + return fmt.Sprintf("multiple keys match address (%s)", files) +} + +// accountCache is a live index of all accounts in the keystore. +type accountCache struct { + keydir string + watcher *watcher + mu sync.Mutex + all []accounts.Account + byAddr map[common.Address][]accounts.Account + throttle *time.Timer + notify chan struct{} + fileC fileCache +} + +func newAccountCache(keydir string) (*accountCache, chan struct{}) { + ac := &accountCache{ + keydir: keydir, + byAddr: make(map[common.Address][]accounts.Account), + notify: make(chan struct{}, 1), + fileC: fileCache{all: mapset.NewThreadUnsafeSet[string]()}, + } + ac.watcher = newWatcher(ac) + return ac, ac.notify +} + +func (ac *accountCache) accounts() []accounts.Account { + ac.maybeReload() + ac.mu.Lock() + defer ac.mu.Unlock() + cpy := make([]accounts.Account, len(ac.all)) + copy(cpy, ac.all) + return cpy +} + +func (ac *accountCache) hasAddress(addr common.Address) bool { + ac.maybeReload() + ac.mu.Lock() + defer ac.mu.Unlock() + return len(ac.byAddr[addr]) > 0 +} + +func (ac *accountCache) add(newAccount accounts.Account) { + ac.mu.Lock() + defer ac.mu.Unlock() + + i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 }) + if i < len(ac.all) && ac.all[i] == newAccount { + return + } + // newAccount is not in the cache. + ac.all = append(ac.all, accounts.Account{}) + copy(ac.all[i+1:], ac.all[i:]) + ac.all[i] = newAccount + ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) +} + +// note: removed needs to be unique here (i.e. both File and Address must be set). +func (ac *accountCache) delete(removed accounts.Account) { + ac.mu.Lock() + defer ac.mu.Unlock() + + ac.all = removeAccount(ac.all, removed) + if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { + delete(ac.byAddr, removed.Address) + } else { + ac.byAddr[removed.Address] = ba + } +} + +// deleteByFile removes an account referenced by the given path. +func (ac *accountCache) deleteByFile(path string) { + ac.mu.Lock() + defer ac.mu.Unlock() + i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path }) + + if i < len(ac.all) && ac.all[i].URL.Path == path { + removed := ac.all[i] + ac.all = append(ac.all[:i], ac.all[i+1:]...) + if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 { + delete(ac.byAddr, removed.Address) + } else { + ac.byAddr[removed.Address] = ba + } + } +} + +// watcherStarted returns true if the watcher loop started running (even if it +// has since also ended). +func (ac *accountCache) watcherStarted() bool { + ac.mu.Lock() + defer ac.mu.Unlock() + return ac.watcher.running || ac.watcher.runEnded +} + +func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account { + for i := range slice { + if slice[i] == elem { + return append(slice[:i], slice[i+1:]...) + } + } + return slice +} + +// find returns the cached account for address if there is a unique match. +// The exact matching rules are explained by the documentation of accounts.Account. +// Callers must hold ac.mu. +func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { + // Limit search to address candidates if possible. + matches := ac.all + if (a.Address != common.Address{}) { + matches = ac.byAddr[a.Address] + } + if a.URL.Path != "" { + // If only the basename is specified, complete the path. + if !strings.ContainsRune(a.URL.Path, filepath.Separator) { + a.URL.Path = filepath.Join(ac.keydir, a.URL.Path) + } + for i := range matches { + if matches[i].URL == a.URL { + return matches[i], nil + } + } + if (a.Address == common.Address{}) { + return accounts.Account{}, ErrNoMatch + } + } + switch len(matches) { + case 1: + return matches[0], nil + case 0: + return accounts.Account{}, ErrNoMatch + default: + err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} + copy(err.Matches, matches) + slices.SortFunc(err.Matches, byURL) + return accounts.Account{}, err + } +} + +func (ac *accountCache) maybeReload() { + ac.mu.Lock() + + if ac.watcher.running { + ac.mu.Unlock() + return // A watcher is running and will keep the cache up-to-date. + } + if ac.throttle == nil { + ac.throttle = time.NewTimer(0) + } else { + select { + case <-ac.throttle.C: + default: + ac.mu.Unlock() + return // The cache was reloaded recently. + } + } + // No watcher running, start it. + ac.watcher.start() + ac.throttle.Reset(minReloadInterval) + ac.mu.Unlock() + ac.scanAccounts() +} + +func (ac *accountCache) close() { + ac.mu.Lock() + ac.watcher.close() + if ac.throttle != nil { + ac.throttle.Stop() + } + if ac.notify != nil { + close(ac.notify) + ac.notify = nil + } + ac.mu.Unlock() +} + +// scanAccounts checks if any changes have occurred on the filesystem, and +// updates the account cache accordingly +func (ac *accountCache) scanAccounts() error { + // Scan the entire folder metadata for file changes + creates, deletes, updates, err := ac.fileC.scan(ac.keydir) + if err != nil { + log.Debug("Failed to reload keystore contents", "err", err) + return err + } + if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 { + return nil + } + // Create a helper method to scan the contents of the key files + var ( + buf = new(bufio.Reader) + key struct { + Address string `json:"address"` + } + ) + readAccount := func(path string) *accounts.Account { + fd, err := os.Open(path) + if err != nil { + log.Trace("Failed to open keystore file", "path", path, "err", err) + return nil + } + defer fd.Close() + buf.Reset(fd) + // Parse the address. + key.Address = "" + err = json.NewDecoder(buf).Decode(&key) + addr := common.HexToAddress(key.Address) + switch { + case err != nil: + log.Debug("Failed to decode keystore key", "path", path, "err", err) + case addr == common.Address{}: + log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") + default: + return &accounts.Account{ + Address: addr, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}, + } + } + return nil + } + // Process all the file diffs + start := time.Now() + + for _, path := range creates.ToSlice() { + if a := readAccount(path); a != nil { + ac.add(*a) + } + } + for _, path := range deletes.ToSlice() { + ac.deleteByFile(path) + } + for _, path := range updates.ToSlice() { + ac.deleteByFile(path) + if a := readAccount(path); a != nil { + ac.add(*a) + } + } + end := time.Now() + + select { + case ac.notify <- struct{}{}: + default: + } + log.Trace("Handled keystore changes", "time", end.Sub(start)) + return nil +} diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go new file mode 100644 index 0000000..41a3002 --- /dev/null +++ b/accounts/keystore/account_cache_test.go @@ -0,0 +1,408 @@ +// 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 keystore + +import ( + "errors" + "fmt" + "math/rand" + "os" + "path/filepath" + "reflect" + "slices" + "testing" + "time" + + "github.com/cespare/cp" + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" +) + +var ( + cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore")) + cachetestAccounts = []accounts.Account{ + { + Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")}, + }, + { + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")}, + }, + { + Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")}, + }, + } +) + +// waitWatcherStart waits up to 1s for the keystore watcher to start. +func waitWatcherStart(ks *KeyStore) bool { + // On systems where file watch is not supported, just return "ok". + if !ks.cache.watcher.enabled() { + return true + } + // The watcher should start, and then exit. + for t0 := time.Now(); time.Since(t0) < 1*time.Second; time.Sleep(100 * time.Millisecond) { + if ks.cache.watcherStarted() { + return true + } + } + return false +} + +func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { + var list []accounts.Account + 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 + select { + case <-ks.changes: + default: + return errors.New("wasn't notified of new accounts") + } + return nil + } + } + return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts) +} + +func TestWatchNewFile(t *testing.T) { + t.Parallel() + + dir, ks := tmpKeyStore(t) + + // Ensure the watcher is started before adding any files. + ks.Accounts() + if !waitWatcherStart(ks) { + t.Fatal("keystore watcher didn't start in time") + } + // Move in the files. + wantAccounts := make([]accounts.Account, len(cachetestAccounts)) + for i := range cachetestAccounts { + wantAccounts[i] = accounts.Account{ + Address: cachetestAccounts[i].Address, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))}, + } + if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil { + t.Fatal(err) + } + } + + // ks should see the accounts. + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Error(err) + } +} + +func TestWatchNoDir(t *testing.T) { + t.Parallel() + // Create ks but not the directory that it watches. + dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int())) + ks := NewKeyStore(dir, LightScryptN, LightScryptP) + list := ks.Accounts() + if len(list) > 0 { + t.Error("initial account list not empty:", list) + } + // The watcher should start, and then exit. + if !waitWatcherStart(ks) { + t.Fatal("keystore watcher didn't start in time") + } + // Create the directory and copy a key file into it. + os.MkdirAll(dir, 0700) + defer os.RemoveAll(dir) + file := filepath.Join(dir, "aaa") + if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { + t.Fatal(err) + } + + // ks should see the account. + wantAccounts := []accounts.Account{cachetestAccounts[0]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { + list = ks.Accounts() + if reflect.DeepEqual(list, wantAccounts) { + // ks should have also received change notifications + select { + case <-ks.changes: + default: + t.Fatalf("wasn't notified of new accounts") + } + return + } + time.Sleep(d) + } + t.Errorf("\ngot %v\nwant %v", list, wantAccounts) +} + +func TestCacheInitialReload(t *testing.T) { + t.Parallel() + cache, _ := newAccountCache(cachetestDir) + accounts := cache.accounts() + if !reflect.DeepEqual(accounts, cachetestAccounts) { + t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts)) + } +} + +func TestCacheAddDeleteOrder(t *testing.T) { + t.Parallel() + cache, _ := newAccountCache("testdata/no-such-dir") + cache.watcher.running = true // prevent unexpected reloads + + accs := []accounts.Account{ + { + Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"}, + }, + { + Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"}, + }, + { + Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"}, + }, + { + Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"}, + }, + { + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"}, + }, + { + Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"}, + }, + } + for _, a := range accs { + cache.add(a) + } + // Add some of them twice to check that they don't get reinserted. + cache.add(accs[0]) + cache.add(accs[2]) + + // Check that the account list is sorted by filename. + wantAccounts := make([]accounts.Account, len(accs)) + copy(wantAccounts, accs) + slices.SortFunc(wantAccounts, byURL) + list := cache.accounts() + if !reflect.DeepEqual(list, wantAccounts) { + t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) + } + for _, a := range accs { + if !cache.hasAddress(a.Address) { + t.Errorf("expected hasAccount(%x) to return true", a.Address) + } + } + if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) { + t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) + } + + // Delete a few keys from the cache. + for i := 0; i < len(accs); i += 2 { + cache.delete(wantAccounts[i]) + } + cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}}) + + // Check content again after deletion. + wantAccountsAfterDelete := []accounts.Account{ + wantAccounts[1], + wantAccounts[3], + wantAccounts[5], + } + list = cache.accounts() + if !reflect.DeepEqual(list, wantAccountsAfterDelete) { + t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete)) + } + for _, a := range wantAccountsAfterDelete { + if !cache.hasAddress(a.Address) { + t.Errorf("expected hasAccount(%x) to return true", a.Address) + } + } + if cache.hasAddress(wantAccounts[0].Address) { + t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address) + } +} + +func TestCacheFind(t *testing.T) { + t.Parallel() + dir := filepath.Join("testdata", "dir") + cache, _ := newAccountCache(dir) + cache.watcher.running = true // prevent unexpected reloads + + accs := []accounts.Account{ + { + Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")}, + }, + { + Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")}, + }, + { + Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")}, + }, + } + for _, a := range accs { + cache.add(a) + } + + nomatchAccount := accounts.Account{ + Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), + URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")}, + } + tests := []struct { + Query accounts.Account + WantResult accounts.Account + WantError error + }{ + // by address + {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]}, + // by file + {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]}, + // by basename + {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]}, + // by file and address + {Query: accs[0], WantResult: accs[0]}, + // ambiguous address, tie resolved by file + {Query: accs[2], WantResult: accs[2]}, + // ambiguous address error + { + Query: accounts.Account{Address: accs[2].Address}, + WantError: &AmbiguousAddrError{ + Addr: accs[2].Address, + Matches: []accounts.Account{accs[2], accs[3]}, + }, + }, + // no match error + {Query: nomatchAccount, WantError: ErrNoMatch}, + {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch}, + {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch}, + {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, + } + for i, test := range tests { + a, err := cache.find(test.Query) + if !reflect.DeepEqual(err, test.WantError) { + t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError) + continue + } + if a != test.WantResult { + t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult) + continue + } + } +} + +// TestUpdatedKeyfileContents tests that updating the contents of a keystore file +// is noticed by the watcher, and the account cache is updated accordingly +func TestUpdatedKeyfileContents(t *testing.T) { + t.Parallel() + + // Create a temporary keystore to test with + dir := t.TempDir() + + ks := NewKeyStore(dir, LightScryptN, LightScryptP) + + list := ks.Accounts() + if len(list) > 0 { + t.Error("initial account list not empty:", list) + } + if !waitWatcherStart(ks) { + t.Fatal("keystore watcher didn't start in time") + } + // Copy a key file into it + file := filepath.Join(dir, "aaa") + + // Place one of our testfiles in there + if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil { + t.Fatal(err) + } + + // ks should see the account. + wantAccounts := []accounts.Account{cachetestAccounts[0]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Error(err) + return + } + // needed so that modTime of `file` is different to its current value after forceCopyFile + 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 { + t.Fatal(err) + return + } + wantAccounts = []accounts.Account{cachetestAccounts[1]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Errorf("First replacement failed") + t.Error(err) + return + } + + // needed so that modTime of `file` is different to its current value after forceCopyFile + 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 { + t.Fatal(err) + return + } + wantAccounts = []accounts.Account{cachetestAccounts[2]} + wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file} + if err := waitForAccounts(wantAccounts, ks); err != nil { + t.Errorf("Second replacement failed") + t.Error(err) + return + } + + // needed so that modTime of `file` is different to its current value after os.WriteFile + 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 { + t.Fatal(err) + return + } + if err := waitForAccounts([]accounts.Account{}, ks); err != nil { + t.Errorf("Emptying account file failed") + t.Error(err) + return + } +} + +// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists. +func forceCopyFile(dst, src string) error { + data, err := os.ReadFile(src) + if err != nil { + return err + } + return os.WriteFile(dst, data, 0644) +} diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go new file mode 100644 index 0000000..63eb850 --- /dev/null +++ b/accounts/keystore/file_cache.go @@ -0,0 +1,105 @@ +// 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 keystore + +import ( + "os" + "path/filepath" + "strings" + "sync" + "time" + + mapset "github.com/deckarep/golang-set/v2" + "github.com/ethereum/go-ethereum/log" +) + +// fileCache is a cache of files seen during scan of keystore. +type fileCache struct { + all mapset.Set[string] // Set of all files from the keystore folder + lastMod time.Time // Last time instance when a file was modified + mu sync.Mutex +} + +// scan performs a new scan on the given directory, compares against the already +// cached filenames, and returns file sets: creates, deletes, updates. +func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string], mapset.Set[string], error) { + t0 := time.Now() + + // List all the files from the keystore folder + files, err := os.ReadDir(keyDir) + if err != nil { + return nil, nil, nil, err + } + t1 := time.Now() + + fc.mu.Lock() + defer fc.mu.Unlock() + + // Iterate all the files and gather their metadata + all := mapset.NewThreadUnsafeSet[string]() + mods := mapset.NewThreadUnsafeSet[string]() + + var newLastMod time.Time + for _, fi := range files { + path := filepath.Join(keyDir, fi.Name()) + // Skip any non-key files from the folder + if nonKeyFile(fi) { + log.Trace("Ignoring file on account scan", "path", path) + continue + } + // Gather the set of all and freshly modified files + all.Add(path) + + info, err := fi.Info() + if err != nil { + return nil, nil, nil, err + } + modified := info.ModTime() + if modified.After(fc.lastMod) { + mods.Add(path) + } + if modified.After(newLastMod) { + newLastMod = modified + } + } + t2 := time.Now() + + // Update the tracked files and return the three sets + deletes := fc.all.Difference(all) // Deletes = previous - current + creates := all.Difference(fc.all) // Creates = current - previous + updates := mods.Difference(creates) // Updates = modified - creates + + fc.all, fc.lastMod = all, newLastMod + t3 := time.Now() + + // Report on the scanning stats and return + log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2)) + return creates, deletes, updates, nil +} + +// nonKeyFile ignores editor backups, hidden files and folders/symlinks. +func nonKeyFile(fi os.DirEntry) bool { + // Skip editor backups and UNIX-style hidden files. + if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") { + return true + } + // Skip misc special files, directories (yes, symlinks too). + if fi.IsDir() || !fi.Type().IsRegular() { + return true + } + return false +} diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go new file mode 100644 index 0000000..9b2ac14 --- /dev/null +++ b/accounts/keystore/key.go @@ -0,0 +1,237 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" +) + +const ( + version = 3 +) + +type Key struct { + Id uuid.UUID // Version 4 "random" for unique id not derived from key data + // to simplify lookups we also store the address + Address common.Address + // we only store privkey as pubkey/address can be derived from it + // privkey in this struct is always in plaintext + PrivateKey *ecdsa.PrivateKey +} + +type keyStore interface { + // Loads and decrypts the key from disk. + GetKey(addr common.Address, filename string, auth string) (*Key, error) + // Writes and encrypts the key. + StoreKey(filename string, k *Key, auth string) error + // Joins filename with the key directory unless it is already absolute. + JoinPath(filename string) string +} + +type plainKeyJSON struct { + Address string `json:"address"` + PrivateKey string `json:"privatekey"` + Id string `json:"id"` + Version int `json:"version"` +} + +type encryptedKeyJSONV3 struct { + Address string `json:"address"` + Crypto CryptoJSON `json:"crypto"` + Id string `json:"id"` + Version int `json:"version"` +} + +type encryptedKeyJSONV1 struct { + Address string `json:"address"` + Crypto CryptoJSON `json:"crypto"` + Id string `json:"id"` + Version string `json:"version"` +} + +type CryptoJSON struct { + Cipher string `json:"cipher"` + CipherText string `json:"ciphertext"` + CipherParams cipherparamsJSON `json:"cipherparams"` + KDF string `json:"kdf"` + KDFParams map[string]interface{} `json:"kdfparams"` + MAC string `json:"mac"` +} + +type cipherparamsJSON struct { + IV string `json:"iv"` +} + +func (k *Key) MarshalJSON() (j []byte, err error) { + jStruct := plainKeyJSON{ + hex.EncodeToString(k.Address[:]), + hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)), + k.Id.String(), + version, + } + j, err = json.Marshal(jStruct) + return j, err +} + +func (k *Key) UnmarshalJSON(j []byte) (err error) { + keyJSON := new(plainKeyJSON) + err = json.Unmarshal(j, &keyJSON) + if err != nil { + return err + } + + u := new(uuid.UUID) + *u, err = uuid.Parse(keyJSON.Id) + if err != nil { + return err + } + k.Id = *u + addr, err := hex.DecodeString(keyJSON.Address) + if err != nil { + return err + } + privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey) + if err != nil { + return err + } + + k.Address = common.BytesToAddress(addr) + k.PrivateKey = privkey + + return nil +} + +func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf("Could not create random uuid: %v", err)) + } + key := &Key{ + Id: id, + Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), + PrivateKey: privateKeyECDSA, + } + return key +} + +// NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit +// into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we +// retry until the first byte is 0. +func NewKeyForDirectICAP(rand io.Reader) *Key { + randBytes := make([]byte, 64) + _, err := rand.Read(randBytes) + if err != nil { + panic("key generation: could not read from random source: " + err.Error()) + } + reader := bytes.NewReader(randBytes) + privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader) + if err != nil { + panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) + } + key := newKeyFromECDSA(privateKeyECDSA) + if !strings.HasPrefix(key.Address.Hex(), "0x00") { + return NewKeyForDirectICAP(rand) + } + return key +} + +func newKey(rand io.Reader) (*Key, error) { + privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand) + if err != nil { + return nil, err + } + return newKeyFromECDSA(privateKeyECDSA), nil +} + +func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { + key, err := newKey(rand) + if err != nil { + return nil, accounts.Account{}, err + } + a := accounts.Account{ + Address: key.Address, + URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}, + } + if err := ks.StoreKey(a.URL.Path, key, auth); err != nil { + zeroKey(key.PrivateKey) + return nil, a, err + } + return key, a, err +} + +func writeTemporaryKeyFile(file string, content []byte) (string, error) { + // Create the keystore directory with appropriate permissions + // in case it is not present yet. + const dirPerm = 0700 + if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil { + return "", err + } + // Atomic write: create a temporary hidden file first + // then move it into place. TempFile assigns mode 0600. + f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp") + if err != nil { + return "", err + } + if _, err := f.Write(content); err != nil { + f.Close() + os.Remove(f.Name()) + return "", err + } + f.Close() + return f.Name(), nil +} + +func writeKeyFile(file string, content []byte) error { + name, err := writeTemporaryKeyFile(file, content) + if err != nil { + return err + } + return os.Rename(name, file) +} + +// keyFileName implements the naming convention for keyfiles: +// UTC---
+func keyFileName(keyAddr common.Address) string { + ts := time.Now().UTC() + return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:])) +} + +func toISO8601(t time.Time) string { + var tz string + name, offset := t.Zone() + if name == "UTC" { + tz = "Z" + } else { + tz = fmt.Sprintf("%03d00", offset/3600) + } + return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", + t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) +} diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go new file mode 100644 index 0000000..df3dda6 --- /dev/null +++ b/accounts/keystore/keystore.go @@ -0,0 +1,503 @@ +// 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 keystore implements encrypted storage of secp256k1 private keys. +// +// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification. +// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information. +package keystore + +import ( + "crypto/ecdsa" + crand "crypto/rand" + "errors" + "math/big" + "os" + "path/filepath" + "reflect" + "runtime" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" +) + +var ( + ErrLocked = accounts.NewAuthNeededError("password or unlock") + ErrNoMatch = errors.New("no key for given address or file") + ErrDecrypt = errors.New("could not decrypt key with given password") + + // ErrAccountAlreadyExists is returned if an account attempted to import is + // already present in the keystore. + ErrAccountAlreadyExists = errors.New("account already exists") +) + +// KeyStoreType is the reflect type of a keystore backend. +var KeyStoreType = reflect.TypeOf(&KeyStore{}) + +// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs. +const KeyStoreScheme = "keystore" + +// Maximum time between wallet refreshes (if filesystem notifications don't work). +const walletRefreshCycle = 3 * time.Second + +// KeyStore manages a key storage directory on disk. +type KeyStore struct { + storage keyStore // Storage backend, might be cleartext or encrypted + cache *accountCache // In-memory account cache over the filesystem storage + changes chan struct{} // Channel receiving change notifications from the cache + unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys) + + wallets []accounts.Wallet // Wallet wrappers around the individual key files + updateFeed event.Feed // Event feed to notify wallet additions/removals + updateScope event.SubscriptionScope // Subscription scope tracking current live listeners + updating bool // Whether the event notification loop is running + + mu sync.RWMutex + importMu sync.Mutex // Import Mutex locks the import to prevent two insertions from racing +} + +type unlocked struct { + *Key + abort chan struct{} +} + +// NewKeyStore creates a keystore for the given directory. +func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore { + keydir, _ = filepath.Abs(keydir) + ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}} + ks.init(keydir) + return ks +} + +func (ks *KeyStore) init(keydir string) { + // Lock the mutex since the account cache might call back with events + ks.mu.Lock() + defer ks.mu.Unlock() + + // Initialize the set of unlocked keys and the account cache + ks.unlocked = make(map[common.Address]*unlocked) + ks.cache, ks.changes = newAccountCache(keydir) + + // TODO: In order for this finalizer to work, there must be no references + // to ks. addressCache doesn't keep a reference but unlocked keys do, + // so the finalizer will not trigger until all timed unlocks have expired. + runtime.SetFinalizer(ks, func(m *KeyStore) { + m.cache.close() + }) + // Create the initial list of wallets from the cache + accs := ks.cache.accounts() + ks.wallets = make([]accounts.Wallet, len(accs)) + for i := 0; i < len(accs); i++ { + ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks} + } +} + +// Wallets implements accounts.Backend, returning all single-key wallets from the +// keystore directory. +func (ks *KeyStore) Wallets() []accounts.Wallet { + // Make sure the list of wallets is in sync with the account cache + ks.refreshWallets() + + ks.mu.RLock() + defer ks.mu.RUnlock() + + cpy := make([]accounts.Wallet, len(ks.wallets)) + copy(cpy, ks.wallets) + return cpy +} + +// refreshWallets retrieves the current account list and based on that does any +// necessary wallet refreshes. +func (ks *KeyStore) refreshWallets() { + // Retrieve the current list of accounts + ks.mu.Lock() + accs := ks.cache.accounts() + + // Transform the current list of wallets into the new one + var ( + wallets = make([]accounts.Wallet, 0, len(accs)) + events []accounts.WalletEvent + ) + + for _, account := range accs { + // Drop wallets while they were in front of the next account + for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 { + events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped}) + ks.wallets = ks.wallets[1:] + } + // If there are no more wallets or the account is before the next, wrap new wallet + if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 { + wallet := &keystoreWallet{account: account, keystore: ks} + + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) + wallets = append(wallets, wallet) + continue + } + // If the account is the same as the first wallet, keep it + if ks.wallets[0].Accounts()[0] == account { + wallets = append(wallets, ks.wallets[0]) + ks.wallets = ks.wallets[1:] + continue + } + } + // Drop any leftover wallets and set the new batch + for _, wallet := range ks.wallets { + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + } + ks.wallets = wallets + ks.mu.Unlock() + + // Fire all wallet events and return + for _, event := range events { + ks.updateFeed.Send(event) + } +} + +// Subscribe implements accounts.Backend, creating an async subscription to +// receive notifications on the addition or removal of keystore wallets. +func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + // We need the mutex to reliably start/stop the update loop + ks.mu.Lock() + defer ks.mu.Unlock() + + // Subscribe the caller and track the subscriber count + sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink)) + + // Subscribers require an active notification loop, start it + if !ks.updating { + ks.updating = true + go ks.updater() + } + return sub +} + +// updater is responsible for maintaining an up-to-date list of wallets stored in +// the keystore, and for firing wallet addition/removal events. It listens for +// account change events from the underlying account cache, and also periodically +// forces a manual refresh (only triggers for systems where the filesystem notifier +// is not running). +func (ks *KeyStore) updater() { + for { + // Wait for an account update or a refresh timeout + select { + case <-ks.changes: + case <-time.After(walletRefreshCycle): + } + // Run the wallet refresher + ks.refreshWallets() + + // If all our subscribers left, stop the updater + ks.mu.Lock() + if ks.updateScope.Count() == 0 { + ks.updating = false + ks.mu.Unlock() + return + } + ks.mu.Unlock() + } +} + +// HasAddress reports whether a key with the given address is present. +func (ks *KeyStore) HasAddress(addr common.Address) bool { + return ks.cache.hasAddress(addr) +} + +// Accounts returns all key files present in the directory. +func (ks *KeyStore) Accounts() []accounts.Account { + return ks.cache.accounts() +} + +// Delete deletes the key matched by account if the passphrase is correct. +// If the account contains no filename, the address must match a unique key. +func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error { + // Decrypting the key isn't really necessary, but we do + // it anyway to check the password and zero out the key + // immediately afterwards. + a, key, err := ks.getDecryptedKey(a, passphrase) + if key != nil { + zeroKey(key.PrivateKey) + } + if err != nil { + return err + } + // The order is crucial here. The key is dropped from the + // cache after the file is gone so that a reload happening in + // between won't insert it into the cache again. + err = os.Remove(a.URL.Path) + if err == nil { + ks.cache.delete(a) + ks.refreshWallets() + } + return err +} + +// SignHash calculates a ECDSA signature for the given hash. The produced +// signature is in the [R || S || V] format where V is 0 or 1. +func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) { + // Look up the key to sign with and abort if it cannot be found + ks.mu.RLock() + defer ks.mu.RUnlock() + + unlockedKey, found := ks.unlocked[a.Address] + if !found { + return nil, ErrLocked + } + // Sign the hash using plain ECDSA operations + return crypto.Sign(hash, unlockedKey.PrivateKey) +} + +// SignTx signs the given transaction with the requested account. +func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + // Look up the key to sign with and abort if it cannot be found + ks.mu.RLock() + defer ks.mu.RUnlock() + + unlockedKey, found := ks.unlocked[a.Address] + if !found { + return nil, ErrLocked + } + // Depending on the presence of the chain ID, sign with 2718 or homestead + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, unlockedKey.PrivateKey) +} + +// SignHashWithPassphrase signs hash if the private key matching the given address +// can be decrypted with the given passphrase. The produced signature is in the +// [R || S || V] format where V is 0 or 1. +func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + defer zeroKey(key.PrivateKey) + return crypto.Sign(hash, key.PrivateKey) +} + +// SignTxWithPassphrase signs the transaction if the private key matching the +// given address can be decrypted with the given passphrase. +func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + defer zeroKey(key.PrivateKey) + // Depending on the presence of the chain ID, sign with or without replay protection. + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, key.PrivateKey) +} + +// Unlock unlocks the given account indefinitely. +func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error { + return ks.TimedUnlock(a, passphrase, 0) +} + +// Lock removes the private key with the given address from memory. +func (ks *KeyStore) Lock(addr common.Address) error { + ks.mu.Lock() + unl, found := ks.unlocked[addr] + ks.mu.Unlock() + if found { + ks.expire(addr, unl, time.Duration(0)*time.Nanosecond) + } + return nil +} + +// TimedUnlock unlocks the given account with the passphrase. The account +// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account +// until the program exits. The account must match a unique key file. +// +// If the account address is already unlocked for a duration, TimedUnlock extends or +// shortens the active unlock timeout. If the address was previously unlocked +// indefinitely the timeout is not altered. +func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error { + a, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return err + } + + ks.mu.Lock() + defer ks.mu.Unlock() + u, found := ks.unlocked[a.Address] + if found { + if u.abort == nil { + // The address was unlocked indefinitely, so unlocking + // it with a timeout would be confusing. + zeroKey(key.PrivateKey) + return nil + } + // Terminate the expire goroutine and replace it below. + close(u.abort) + } + if timeout > 0 { + u = &unlocked{Key: key, abort: make(chan struct{})} + go ks.expire(a.Address, u, timeout) + } else { + u = &unlocked{Key: key} + } + ks.unlocked[a.Address] = u + return nil +} + +// Find resolves the given account into a unique entry in the keystore. +func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) { + ks.cache.maybeReload() + ks.cache.mu.Lock() + a, err := ks.cache.find(a) + ks.cache.mu.Unlock() + return a, err +} + +func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) { + a, err := ks.Find(a) + if err != nil { + return a, nil, err + } + key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth) + return a, key, err +} + +func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) { + t := time.NewTimer(timeout) + defer t.Stop() + select { + case <-u.abort: + // just quit + case <-t.C: + ks.mu.Lock() + // only drop if it's still the same key instance that dropLater + // was launched with. we can check that using pointer equality + // because the map stores a new pointer every time the key is + // unlocked. + if ks.unlocked[addr] == u { + zeroKey(u.PrivateKey) + delete(ks.unlocked, addr) + } + ks.mu.Unlock() + } +} + +// NewAccount generates a new key and stores it into the key directory, +// encrypting it with the passphrase. +func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) { + _, account, err := storeNewKey(ks.storage, crand.Reader, passphrase) + if err != nil { + return accounts.Account{}, err + } + // Add the account to the cache immediately rather + // than waiting for file system notifications to pick it up. + ks.cache.add(account) + ks.refreshWallets() + return account, nil +} + +// Export exports as a JSON key, encrypted with newPassphrase. +func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) { + _, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return nil, err + } + var N, P int + if store, ok := ks.storage.(*keyStorePassphrase); ok { + N, P = store.scryptN, store.scryptP + } else { + N, P = StandardScryptN, StandardScryptP + } + return EncryptKey(key, newPassphrase, N, P) +} + +// Import stores the given encrypted JSON key into the key directory. +func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) { + key, err := DecryptKey(keyJSON, passphrase) + if key != nil && key.PrivateKey != nil { + defer zeroKey(key.PrivateKey) + } + if err != nil { + return accounts.Account{}, err + } + ks.importMu.Lock() + defer ks.importMu.Unlock() + + if ks.cache.hasAddress(key.Address) { + return accounts.Account{ + Address: key.Address, + }, ErrAccountAlreadyExists + } + return ks.importKey(key, newPassphrase) +} + +// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase. +func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { + ks.importMu.Lock() + defer ks.importMu.Unlock() + + key := newKeyFromECDSA(priv) + if ks.cache.hasAddress(key.Address) { + return accounts.Account{ + Address: key.Address, + }, ErrAccountAlreadyExists + } + return ks.importKey(key, passphrase) +} + +func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) { + a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}} + if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil { + return accounts.Account{}, err + } + ks.cache.add(a) + ks.refreshWallets() + return a, nil +} + +// Update changes the passphrase of an existing account. +func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error { + a, key, err := ks.getDecryptedKey(a, passphrase) + if err != nil { + return err + } + return ks.storage.StoreKey(a.URL.Path, key, newPassphrase) +} + +// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores +// a key file in the key directory. The key file is encrypted with the same passphrase. +func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) { + a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase) + if err != nil { + return a, err + } + ks.cache.add(a) + ks.refreshWallets() + return a, nil +} + +// isUpdating returns whether the event notification loop is running. +// This method is mainly meant for tests. +func (ks *KeyStore) isUpdating() bool { + ks.mu.RLock() + defer ks.mu.RUnlock() + return ks.updating +} + +// zeroKey zeroes a private key in memory. +func zeroKey(k *ecdsa.PrivateKey) { + b := k.D.Bits() + clear(b) +} diff --git a/accounts/keystore/keystore_fuzzing_test.go b/accounts/keystore/keystore_fuzzing_test.go new file mode 100644 index 0000000..793b463 --- /dev/null +++ b/accounts/keystore/keystore_fuzzing_test.go @@ -0,0 +1,34 @@ +// 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 keystore + +import ( + "testing" +) + +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 new file mode 100644 index 0000000..f8922a3 --- /dev/null +++ b/accounts/keystore/keystore_test.go @@ -0,0 +1,463 @@ +// 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 keystore + +import ( + "math/rand" + "os" + "runtime" + "slices" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" +) + +var testSigData = make([]byte, 32) + +func TestKeyStore(t *testing.T) { + t.Parallel() + dir, ks := tmpKeyStore(t) + + a, err := ks.NewAccount("foo") + if err != nil { + t.Fatal(err) + } + if !strings.HasPrefix(a.URL.Path, dir) { + t.Errorf("account file %s doesn't have dir prefix", a.URL) + } + stat, err := os.Stat(a.URL.Path) + if err != nil { + t.Fatalf("account file %s doesn't exist (%v)", a.URL, err) + } + if runtime.GOOS != "windows" && stat.Mode() != 0600 { + t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) + } + if !ks.HasAddress(a.Address) { + t.Errorf("HasAccount(%x) should've returned true", a.Address) + } + if err := ks.Update(a, "foo", "bar"); err != nil { + t.Errorf("Update error: %v", err) + } + if err := ks.Delete(a, "bar"); err != nil { + t.Errorf("Delete error: %v", err) + } + if common.FileExist(a.URL.Path) { + t.Errorf("account file %s should be gone after Delete", a.URL) + } + if ks.HasAddress(a.Address) { + t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address) + } +} + +func TestSign(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStore(t) + + pass := "" // not used but required by API + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + if err := ks.Unlock(a1, ""); err != nil { + t.Fatal(err) + } + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil { + t.Fatal(err) + } +} + +func TestSignWithPassphrase(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStore(t) + + pass := "passwd" + acc, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := ks.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + _, err = ks.SignHashWithPassphrase(acc, pass, testSigData) + if err != nil { + t.Fatal(err) + } + + if _, unlocked := ks.unlocked[acc.Address]; unlocked { + t.Fatal("expected account to be locked") + } + + if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil { + t.Fatal("expected SignHashWithPassphrase to fail with invalid password") + } +} + +func TestTimedUnlock(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStore(t) + + pass := "foo" + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + // Signing without passphrase fails because account is locked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) + } + + // Signing with passphrase works + if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // Signing fails again after automatic locking + time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + } +} + +func TestOverrideUnlock(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStore(t) + + pass := "foo" + a1, err := ks.NewAccount(pass) + if err != nil { + t.Fatal(err) + } + + // Unlock indefinitely. + if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil { + t.Fatal(err) + } + + // Signing without passphrase works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // reset unlock to a shorter period, invalidates the previous unlock + if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { + t.Fatal(err) + } + + // Signing without passphrase still works because account is temp unlocked + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != nil { + t.Fatal("Signing shouldn't return an error after unlocking, got ", err) + } + + // Signing fails again after automatic locking + time.Sleep(250 * time.Millisecond) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrLocked { + t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + } +} + +// This test should fail under -race if signing races the expiration goroutine. +func TestSignRace(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStore(t) + + // Create a test account. + a1, err := ks.NewAccount("") + if err != nil { + t.Fatal("could not create the test account", err) + } + + if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { + t.Fatal("could not unlock the test account", err) + } + end := time.Now().Add(500 * time.Millisecond) + for time.Now().Before(end) { + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked { + return + } else if err != nil { + t.Errorf("Sign error: %v", err) + return + } + time.Sleep(1 * time.Millisecond) + } + t.Errorf("Account did not lock within the timeout") +} + +// waitForKsUpdating waits until the updating-status of the ks reaches the +// desired wantStatus. +// It waits for a maximum time of maxTime, and returns false if it does not +// finish in time +func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time.Duration) bool { + t.Helper() + // Wait max 250 ms, then return false + for t0 := time.Now(); time.Since(t0) < maxTime; { + if ks.isUpdating() == wantStatus { + return true + } + time.Sleep(25 * time.Millisecond) + } + return false +} + +// Tests that the wallet notifier loop starts and stops correctly based on the +// addition and removal of wallet event subscriptions. +func TestWalletNotifierLifecycle(t *testing.T) { + t.Parallel() + // Create a temporary keystore to test with + _, ks := tmpKeyStore(t) + + // Ensure that the notification updater is not running yet + time.Sleep(250 * time.Millisecond) + + if ks.isUpdating() { + t.Errorf("wallet notifier running without subscribers") + } + // Subscribe to the wallet feed and ensure the updater boots up + updates := make(chan accounts.WalletEvent) + + subs := make([]event.Subscription, 2) + for i := 0; i < len(subs); i++ { + // Create a new subscription + subs[i] = ks.Subscribe(updates) + if !waitForKsUpdating(t, ks, true, 250*time.Millisecond) { + t.Errorf("sub %d: wallet notifier not running after subscription", i) + } + } + // Close all but one sub + for i := 0; i < len(subs)-1; i++ { + // Close an existing subscription + subs[i].Unsubscribe() + } + // Check that it is still running + time.Sleep(250 * time.Millisecond) + + if !ks.isUpdating() { + t.Fatal("event notifier stopped prematurely") + } + // Unsubscribe the last one and ensure the updater terminates eventually. + subs[len(subs)-1].Unsubscribe() + if !waitForKsUpdating(t, ks, false, 4*time.Second) { + t.Errorf("wallet notifier didn't terminate after unsubscribe") + } +} + +type walletEvent struct { + accounts.WalletEvent + a accounts.Account +} + +// 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) + + // Subscribe to the wallet feed and collect events. + var ( + events []walletEvent + updates = make(chan accounts.WalletEvent) + sub = ks.Subscribe(updates) + ) + defer sub.Unsubscribe() + go func() { + for { + select { + case ev := <-updates: + events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) + case <-sub.Err(): + close(updates) + return + } + } + }() + + // Randomly add and remove accounts. + var ( + live = make(map[common.Address]accounts.Account) + wantEvents []walletEvent + ) + for i := 0; i < 1024; i++ { + if create := len(live) == 0 || rand.Int()%4 > 0; create { + // Add a new account and ensure wallet notifications arrives + account, err := ks.NewAccount("") + if err != nil { + t.Fatalf("failed to create test account: %v", err) + } + live[account.Address] = account + wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account}) + } else { + // Delete a random account. + var account accounts.Account + for _, a := range live { + account = a + break + } + if err := ks.Delete(account, ""); err != nil { + t.Fatalf("failed to delete test account: %v", err) + } + delete(live, account.Address) + wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account}) + } + } + + // Shut down the event collector and check events. + sub.Unsubscribe() + for ev := range updates { + events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) + } + checkAccounts(t, live, ks.Wallets()) + checkEvents(t, wantEvents, events) +} + +// TestImportECDSA tests the import functionality of a keystore. +func TestImportECDSA(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStore(t) + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", key) + } + if _, err = ks.ImportECDSA(key, "old"); err != nil { + t.Errorf("importing failed: %v", err) + } + if _, err = ks.ImportECDSA(key, "old"); err == nil { + t.Errorf("importing same key twice succeeded") + } + if _, err = ks.ImportECDSA(key, "new"); err == nil { + t.Errorf("importing same key twice succeeded") + } +} + +// TestImportExport tests the import and export functionality of a keystore. +func TestImportExport(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStore(t) + acc, err := ks.NewAccount("old") + if err != nil { + t.Fatalf("failed to create account: %v", acc) + } + json, err := ks.Export(acc, "old", "new") + if err != nil { + t.Fatalf("failed to export account: %v", acc) + } + _, ks2 := tmpKeyStore(t) + if _, err = ks2.Import(json, "old", "old"); err == nil { + t.Errorf("importing with invalid password succeeded") + } + acc2, err := ks2.Import(json, "new", "new") + if err != nil { + t.Errorf("importing failed: %v", err) + } + if acc.Address != acc2.Address { + t.Error("imported account does not match exported account") + } + if _, err = ks2.Import(json, "new", "new"); err == nil { + t.Errorf("importing a key twice succeeded") + } +} + +// 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) + acc, err := ks.NewAccount("old") + if err != nil { + t.Fatalf("failed to create account: %v", acc) + } + json, err := ks.Export(acc, "old", "new") + if err != nil { + t.Fatalf("failed to export account: %v", acc) + } + _, ks2 := tmpKeyStore(t) + var atom atomic.Uint32 + var wg sync.WaitGroup + wg.Add(2) + for i := 0; i < 2; i++ { + go func() { + defer wg.Done() + if _, err := ks2.Import(json, "new", "new"); err != nil { + atom.Add(1) + } + }() + } + wg.Wait() + if atom.Load() != 1 { + t.Errorf("Import is racy") + } +} + +// checkAccounts checks that all known live accounts are present in the wallet list. +func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) { + if len(live) != len(wallets) { + t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live)) + return + } + liveList := make([]accounts.Account, 0, len(live)) + for _, account := range live { + liveList = append(liveList, account) + } + slices.SortFunc(liveList, byURL) + for j, wallet := range wallets { + if accs := wallet.Accounts(); len(accs) != 1 { + t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) + } else if accs[0] != liveList[j] { + t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j]) + } + } +} + +// checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times. +func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) { + for _, wantEv := range want { + nmatch := 0 + for ; len(have) > 0; nmatch++ { + if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a { + break + } + have = have[1:] + } + if nmatch == 0 { + t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address) + } + } +} + +func tmpKeyStore(t *testing.T) (string, *KeyStore) { + d := t.TempDir() + return d, NewKeyStore(d, veryLightScryptN, veryLightScryptP) +} diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go new file mode 100644 index 0000000..e7a7f8d --- /dev/null +++ b/accounts/keystore/passphrase.go @@ -0,0 +1,368 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +/* + +This key store behaves as KeyStorePlain with the difference that +the private key is encrypted and on disk uses another JSON encoding. + +The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition + +*/ + +package keystore + +import ( + "bytes" + "crypto/aes" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/crypto/scrypt" +) + +const ( + keyHeaderKDF = "scrypt" + + // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. + StandardScryptN = 1 << 18 + + // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. + StandardScryptP = 1 + + // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. + LightScryptN = 1 << 12 + + // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. + LightScryptP = 6 + + scryptR = 8 + scryptDKLen = 32 +) + +type keyStorePassphrase struct { + keysDirPath string + scryptN int + scryptP int + // skipKeyFileVerification disables the security-feature which does + // reads and decrypts any newly created keyfiles. This should be 'false' in all + // cases except tests -- setting this to 'true' is not recommended. + skipKeyFileVerification bool +} + +func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) { + // Load the key from the keystore and decrypt its contents + keyjson, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + key, err := DecryptKey(keyjson, auth) + if err != nil { + return nil, err + } + // Make sure we're really operating on the requested key (no swap attacks) + if key.Address != addr { + return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr) + } + return key, nil +} + +// StoreKey generates a key, encrypts with 'auth' and stores in the given directory +func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) { + _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth) + return a, err +} + +func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error { + keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP) + if err != nil { + return err + } + // Write into temporary file + tmpName, err := writeTemporaryKeyFile(filename, keyjson) + if err != nil { + return err + } + if !ks.skipKeyFileVerification { + // Verify that we can decrypt the file with the given password. + _, err = ks.GetKey(key.Address, tmpName, auth) + if err != nil { + msg := "An error was encountered when saving and verifying the keystore file. \n" + + "This indicates that the keystore is corrupted. \n" + + "The corrupted file is stored at \n%v\n" + + "Please file a ticket at:\n\n" + + "https://github.com/ethereum/go-ethereum/issues." + + "The error was : %s" + //lint:ignore ST1005 This is a message for the user + return fmt.Errorf(msg, tmpName, err) + } + } + return os.Rename(tmpName, filename) +} + +func (ks keyStorePassphrase) JoinPath(filename string) string { + if filepath.IsAbs(filename) { + return filename + } + return filepath.Join(ks.keysDirPath, filename) +} + +// 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 { + panic("reading from crypto/rand failed: " + err.Error()) + } + derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen) + if err != nil { + return CryptoJSON{}, err + } + encryptKey := derivedKey[:16] + + iv := make([]byte, aes.BlockSize) // 16 + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + panic("reading from crypto/rand failed: " + err.Error()) + } + cipherText, err := aesCTRXOR(encryptKey, data, iv) + if err != nil { + return CryptoJSON{}, err + } + mac := crypto.Keccak256(derivedKey[16:32], cipherText) + + scryptParamsJSON := make(map[string]interface{}, 5) + scryptParamsJSON["n"] = scryptN + scryptParamsJSON["r"] = scryptR + scryptParamsJSON["p"] = scryptP + scryptParamsJSON["dklen"] = scryptDKLen + scryptParamsJSON["salt"] = hex.EncodeToString(salt) + cipherParamsJSON := cipherparamsJSON{ + IV: hex.EncodeToString(iv), + } + + cryptoStruct := CryptoJSON{ + Cipher: "aes-128-ctr", + CipherText: hex.EncodeToString(cipherText), + CipherParams: cipherParamsJSON, + KDF: keyHeaderKDF, + KDFParams: scryptParamsJSON, + MAC: hex.EncodeToString(mac), + } + return cryptoStruct, nil +} + +// EncryptKey encrypts a key using the specified scrypt parameters into a json +// blob that can be decrypted later on. +func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { + keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) + cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP) + if err != nil { + return nil, err + } + encryptedKeyJSONV3 := encryptedKeyJSONV3{ + hex.EncodeToString(key.Address[:]), + cryptoStruct, + key.Id.String(), + version, + } + return json.Marshal(encryptedKeyJSONV3) +} + +// DecryptKey decrypts a key from a json blob, returning the private key itself. +func DecryptKey(keyjson []byte, auth string) (*Key, error) { + // Parse the json into a simple map to fetch the key version + m := make(map[string]interface{}) + if err := json.Unmarshal(keyjson, &m); err != nil { + return nil, err + } + // Depending on the version try to parse one way or another + var ( + keyBytes, keyId []byte + err error + ) + if version, ok := m["version"].(string); ok && version == "1" { + k := new(encryptedKeyJSONV1) + if err := json.Unmarshal(keyjson, k); err != nil { + return nil, err + } + keyBytes, keyId, err = decryptKeyV1(k, auth) + } else { + k := new(encryptedKeyJSONV3) + if err := json.Unmarshal(keyjson, k); err != nil { + return nil, err + } + keyBytes, keyId, err = decryptKeyV3(k, auth) + } + // Handle any decryption errors and return the key + if err != nil { + return nil, err + } + key, err := crypto.ToECDSA(keyBytes) + if err != nil { + return nil, fmt.Errorf("invalid key: %w", err) + } + id, err := uuid.FromBytes(keyId) + if err != nil { + return nil, fmt.Errorf("invalid UUID: %w", err) + } + return &Key{ + Id: id, + Address: crypto.PubkeyToAddress(key.PublicKey), + PrivateKey: key, + }, nil +} + +func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) { + if cryptoJson.Cipher != "aes-128-ctr" { + return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher) + } + mac, err := hex.DecodeString(cryptoJson.MAC) + if err != nil { + return nil, err + } + + iv, err := hex.DecodeString(cryptoJson.CipherParams.IV) + if err != nil { + return nil, err + } + + cipherText, err := hex.DecodeString(cryptoJson.CipherText) + if err != nil { + return nil, err + } + + derivedKey, err := getKDFKey(cryptoJson, auth) + if err != nil { + return nil, err + } + + calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) + if !bytes.Equal(calculatedMAC, mac) { + return nil, ErrDecrypt + } + + plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv) + if err != nil { + return nil, err + } + return plainText, err +} + +func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) { + if keyProtected.Version != version { + return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version) + } + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] + plainText, err := DecryptDataV3(keyProtected.Crypto, auth) + if err != nil { + return nil, nil, err + } + return plainText, keyId, err +} + +func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) { + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] + mac, err := hex.DecodeString(keyProtected.Crypto.MAC) + if err != nil { + return nil, nil, err + } + + iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV) + if err != nil { + return nil, nil, err + } + + cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText) + if err != nil { + return nil, nil, err + } + + derivedKey, err := getKDFKey(keyProtected.Crypto, auth) + if err != nil { + return nil, nil, err + } + + calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) + if !bytes.Equal(calculatedMAC, mac) { + return nil, nil, ErrDecrypt + } + + plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv) + if err != nil { + return nil, nil, err + } + return plainText, keyId, err +} + +func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) { + authArray := []byte(auth) + salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) + if err != nil { + return nil, err + } + dkLen := ensureInt(cryptoJSON.KDFParams["dklen"]) + + if cryptoJSON.KDF == keyHeaderKDF { + n := ensureInt(cryptoJSON.KDFParams["n"]) + r := ensureInt(cryptoJSON.KDFParams["r"]) + p := ensureInt(cryptoJSON.KDFParams["p"]) + return scrypt.Key(authArray, salt, n, r, p, dkLen) + } else if cryptoJSON.KDF == "pbkdf2" { + c := ensureInt(cryptoJSON.KDFParams["c"]) + prf := cryptoJSON.KDFParams["prf"].(string) + if prf != "hmac-sha256" { + return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf) + } + key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New) + return key, nil + } + + return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF) +} + +// TODO: can we do without this when unmarshalling dynamic JSON? +// why do integers in KDF params end up as float64 and not int after +// unmarshal? +func ensureInt(x interface{}) int { + res, ok := x.(int) + if !ok { + res = int(x.(float64)) + } + return res +} diff --git a/accounts/keystore/passphrase_test.go b/accounts/keystore/passphrase_test.go new file mode 100644 index 0000000..20ec0f5 --- /dev/null +++ b/accounts/keystore/passphrase_test.go @@ -0,0 +1,61 @@ +// 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 keystore + +import ( + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + veryLightScryptN = 2 + veryLightScryptP = 1 +) + +// 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) + } + password := "" + address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8") + + // Do a few rounds of decryption and encryption + for i := 0; i < 3; i++ { + // Try a bad password first + if _, err := DecryptKey(keyjson, password+"bad"); err == nil { + t.Errorf("test %d: json key decrypted with bad password", i) + } + // Decrypt with the correct password + key, err := DecryptKey(keyjson, password) + if err != nil { + t.Fatalf("test %d: json key failed to decrypt: %v", i, err) + } + if key.Address != address { + t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address) + } + // 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 re-encrypt key %v", i, err) + } + } +} diff --git a/accounts/keystore/plain.go b/accounts/keystore/plain.go new file mode 100644 index 0000000..f62a133 --- /dev/null +++ b/accounts/keystore/plain.go @@ -0,0 +1,61 @@ +// 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 keystore + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" +) + +type keyStorePlain struct { + keysDirPath string +} + +func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key, error) { + fd, err := os.Open(filename) + if err != nil { + return nil, err + } + defer fd.Close() + key := new(Key) + if err := json.NewDecoder(fd).Decode(key); err != nil { + return nil, err + } + if key.Address != addr { + return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr) + } + return key, nil +} + +func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error { + content, err := json.Marshal(key) + if err != nil { + return err + } + return writeKeyFile(filename, content) +} + +func (ks keyStorePlain) JoinPath(filename string) string { + if filepath.IsAbs(filename) { + return filename + } + return filepath.Join(ks.keysDirPath, filename) +} diff --git a/accounts/keystore/plain_test.go b/accounts/keystore/plain_test.go new file mode 100644 index 0000000..737eb7f --- /dev/null +++ b/accounts/keystore/plain_test.go @@ -0,0 +1,261 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package keystore + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) { + d := t.TempDir() + if encrypted { + ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true} + } else { + ks = &keyStorePlain{d} + } + return d, ks +} + +func TestKeyStorePlain(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStoreIface(t, false) + + pass := "" // not used but required by API + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + k2, err := ks.GetKey(k1.Address, account.URL.Path, pass) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.Address, k2.Address) { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { + t.Fatal(err) + } +} + +func TestKeyStorePassphrase(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStoreIface(t, true) + + pass := "foo" + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + k2, err := ks.GetKey(k1.Address, account.URL.Path, pass) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.Address, k2.Address) { + t.Fatal(err) + } + if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) { + t.Fatal(err) + } +} + +func TestKeyStorePassphraseDecryptionFail(t *testing.T) { + t.Parallel() + _, ks := tmpKeyStoreIface(t, true) + + pass := "foo" + k1, account, err := storeNewKey(ks, rand.Reader, pass) + if err != nil { + t.Fatal(err) + } + if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt { + t.Fatalf("wrong error for invalid password\ngot %q\nwant %q", err, ErrDecrypt) + } +} + +func TestImportPreSaleKey(t *testing.T) { + t.Parallel() + dir, ks := tmpKeyStoreIface(t, true) + + // file content of a presale key file generated with: + // python pyethsaletool.py genwallet + // with password "foo" + fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}" + pass := "foo" + account, _, err := importPreSaleKey(ks, []byte(fileContent), pass) + if err != nil { + t.Fatal(err) + } + if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") { + t.Errorf("imported account has wrong address %x", account.Address) + } + if !strings.HasPrefix(account.URL.Path, dir) { + t.Errorf("imported account file not in keystore directory: %q", account.URL) + } +} + +// Test and utils for the key store tests in the Ethereum JSON tests; +// testdataKeyStoreTests/basic_tests.json +type KeyStoreTestV3 struct { + Json encryptedKeyJSONV3 + Password string + Priv string +} + +type KeyStoreTestV1 struct { + Json encryptedKeyJSONV1 + Password string + Priv string +} + +func TestV3_PBKDF2_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t) +} + +var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests") + +func skipIfSubmoduleMissing(t *testing.T) { + if !common.FileExist(testsSubmodule) { + t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule) + } +} + +func TestV3_PBKDF2_2(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["test1"], t) +} + +func TestV3_PBKDF2_3(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["python_generated_test_with_odd_iv"], t) +} + +func TestV3_PBKDF2_4(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["evilnonce"], t) +} + +func TestV3_Scrypt_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["wikipage_test_vector_scrypt"], t) +} + +func TestV3_Scrypt_2(t *testing.T) { + skipIfSubmoduleMissing(t) + t.Parallel() + tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t) + testDecryptV3(tests["test2"], t) +} + +func TestV1_1(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV1("testdata/v1_test_vector.json", t) + testDecryptV1(tests["test1"], t) +} + +func TestV1_2(t *testing.T) { + t.Parallel() + ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP, true} + addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e") + file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e" + k, err := ks.GetKey(addr, file, "g") + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)) + expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d" + if privHex != expectedHex { + t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex)) + } +} + +func testDecryptV3(test KeyStoreTestV3, t *testing.T) { + privBytes, _, err := decryptKeyV3(&test.Json, test.Password) + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(privBytes) + if test.Priv != privHex { + t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex)) + } +} + +func testDecryptV1(test KeyStoreTestV1, t *testing.T) { + privBytes, _, err := decryptKeyV1(&test.Json, test.Password) + if err != nil { + t.Fatal(err) + } + privHex := hex.EncodeToString(privBytes) + if test.Priv != privHex { + t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex)) + } +} + +func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 { + tests := make(map[string]KeyStoreTestV3) + err := common.LoadJSON(file, &tests) + if err != nil { + t.Fatal(err) + } + return tests +} + +func loadKeyStoreTestV1(file string, t *testing.T) map[string]KeyStoreTestV1 { + tests := make(map[string]KeyStoreTestV1) + err := common.LoadJSON(file, &tests) + if err != nil { + t.Fatal(err) + } + return tests +} + +func TestKeyForDirectICAP(t *testing.T) { + t.Parallel() + key := NewKeyForDirectICAP(rand.Reader) + if !strings.HasPrefix(key.Address.Hex(), "0x00") { + t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex()) + } +} + +func TestV3_31_Byte_Key(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["31_byte_key"], t) +} + +func TestV3_30_Byte_Key(t *testing.T) { + t.Parallel() + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) + testDecryptV3(tests["30_byte_key"], t) +} diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go new file mode 100644 index 0000000..0664dc2 --- /dev/null +++ b/accounts/keystore/presale.go @@ -0,0 +1,150 @@ +// 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 keystore + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" + "golang.org/x/crypto/pbkdf2" +) + +// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON +func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) { + key, err := decryptPreSaleKey(keyJSON, password) + if err != nil { + return accounts.Account{}, nil, err + } + key.Id, err = uuid.NewRandom() + if err != nil { + return accounts.Account{}, nil, err + } + a := accounts.Account{ + Address: key.Address, + URL: accounts.URL{ + Scheme: KeyStoreScheme, + Path: keyStore.JoinPath(keyFileName(key.Address)), + }, + } + err = keyStore.StoreKey(a.URL.Path, key, password) + return a, key, err +} + +func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) { + preSaleKeyStruct := struct { + EncSeed string + EthAddr string + Email string + BtcAddr string + }{} + err = json.Unmarshal(fileContent, &preSaleKeyStruct) + if err != nil { + return nil, err + } + encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed) + if err != nil { + return nil, errors.New("invalid hex in encSeed") + } + if len(encSeedBytes) < 16 { + return nil, errors.New("invalid encSeed, too short") + } + iv := encSeedBytes[:16] + cipherText := encSeedBytes[16:] + /* + See https://github.com/ethereum/pyethsaletool + + pyethsaletool generates the encryption key from password by + 2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:(). + 16 byte key length within PBKDF2 and resulting key is used as AES key + */ + passBytes := []byte(password) + derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New) + plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) + if err != nil { + return nil, err + } + ethPriv := crypto.Keccak256(plainText) + ecKey := crypto.ToECDSAUnsafe(ethPriv) + + key = &Key{ + Id: uuid.UUID{}, + Address: crypto.PubkeyToAddress(ecKey.PublicKey), + PrivateKey: ecKey, + } + derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x" + expectedAddr := preSaleKeyStruct.EthAddr + if derivedAddr != expectedAddr { + err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr) + } + return key, err +} + +func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { + // AES-128 is selected due to size of encryptKey. + aesBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + stream := cipher.NewCTR(aesBlock, iv) + outText := make([]byte, len(inText)) + stream.XORKeyStream(outText, inText) + return outText, err +} + +func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { + aesBlock, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + decrypter := cipher.NewCBCDecrypter(aesBlock, iv) + paddedPlaintext := make([]byte, len(cipherText)) + decrypter.CryptBlocks(paddedPlaintext, cipherText) + plaintext := pkcs7Unpad(paddedPlaintext) + if plaintext == nil { + return nil, ErrDecrypt + } + return plaintext, err +} + +// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes +func pkcs7Unpad(in []byte) []byte { + if len(in) == 0 { + return nil + } + + padding := in[len(in)-1] + if int(padding) > len(in) || padding > aes.BlockSize { + return nil + } else if padding == 0 { + return nil + } + + for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { + if in[i] != padding { + return nil + } + } + return in[:len(in)-int(padding)] +} diff --git a/accounts/keystore/testdata/dupes/1 b/accounts/keystore/testdata/dupes/1 new file mode 100644 index 0000000..a3868ec --- /dev/null +++ b/accounts/keystore/testdata/dupes/1 @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/dupes/2 b/accounts/keystore/testdata/dupes/2 new file mode 100644 index 0000000..a3868ec --- /dev/null +++ b/accounts/keystore/testdata/dupes/2 @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/dupes/foo b/accounts/keystore/testdata/dupes/foo new file mode 100644 index 0000000..c57060a --- /dev/null +++ b/accounts/keystore/testdata/dupes/foo @@ -0,0 +1 @@ +{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/.hiddenfile b/accounts/keystore/testdata/keystore/.hiddenfile new file mode 100644 index 0000000..d91facc --- /dev/null +++ b/accounts/keystore/testdata/keystore/.hiddenfile @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} diff --git a/accounts/keystore/testdata/keystore/README b/accounts/keystore/testdata/keystore/README new file mode 100644 index 0000000..6af9ac3 --- /dev/null +++ b/accounts/keystore/testdata/keystore/README @@ -0,0 +1,21 @@ +This directory contains accounts for testing. +The password that unlocks them is "foobar". + +The "good" key files which are supposed to be loadable are: + +- File: UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 + Address: 0x7ef5a6135f1fd6a02593eedc869c6d41d934aef8 +- File: aaa + Address: 0xf466859ead1932d743d622cb74fc058882e8648a +- File: zzz + Address: 0x289d485d9771714cce91d3393d764e1311907acc + +The other files (including this README) are broken in various ways +and should not be picked up by package accounts: + +- File: no-address (missing address field, otherwise same as "aaa") +- File: garbage (file with random data) +- File: empty (file with no content) +- File: swapfile~ (should be skipped) +- File: .hiddenfile (should be skipped) +- File: foo/... (should be skipped because it is a directory) diff --git a/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 new file mode 100644 index 0000000..c57060a --- /dev/null +++ b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 @@ -0,0 +1 @@ +{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/aaa b/accounts/keystore/testdata/keystore/aaa new file mode 100644 index 0000000..a3868ec --- /dev/null +++ b/accounts/keystore/testdata/keystore/aaa @@ -0,0 +1 @@ +{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/empty b/accounts/keystore/testdata/keystore/empty new file mode 100644 index 0000000..e69de29 diff --git a/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e new file mode 100644 index 0000000..309841e --- /dev/null +++ b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e @@ -0,0 +1 @@ +{"address":"fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"8124d5134aa4a927c79fd852989e4b5419397566f04b0936a1eb1d168c7c68a5","cipherparams":{"iv":"e2febe17176414dd2cda28287947eb2f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"44b415ede89f3bdd6830390a21b78965f571b347a589d1d943029f016c5e8bd5"},"mac":"5e149ff25bfd9dd45746a84bb2bcd2f015f2cbca2b6d25c5de8c29617f71fe5b"},"id":"d6ac5452-2b2c-4d3c-ad80-4bf0327d971c","version":3} \ No newline at end of file diff --git a/accounts/keystore/testdata/keystore/garbage b/accounts/keystore/testdata/keystore/garbage new file mode 100644 index 0000000000000000000000000000000000000000..ff45091e714078dd7d3b4ea95964452e33a895f7 GIT binary patch literal 300 zcmV+{0n`3r1xkOa0KiH=0-y31ays31&4D+~b{#6-MH z)8?iosg+26q81!5ujp29iM}4_d}^;*-$8$htAbEpk(KDl*$;NvD$v8GZL@TRuT#)+ zq*|PXNljY5_xwCfoMayTjJ(vY;=t!uVJT5-Fn0O7W{#e;Ho?+NsQQi=!GV>j#9U#& zAbp7L1M-8N-V+7}EDxG9CNuhKbj?($B?=E1a1Xi%v;bYvR+C$EjApbg!W^>zB$Cd( z+NKd!El}@p)NJLnQ}B=D#e5uCh87_~lKd2z=idP7$. + +package keystore + +import ( + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +// keystoreWallet implements the accounts.Wallet interface for the original +// keystore. +type keystoreWallet struct { + account accounts.Account // Single account contained in this wallet + keystore *KeyStore // Keystore where the account originates from +} + +// URL implements accounts.Wallet, returning the URL of the account within. +func (w *keystoreWallet) URL() accounts.URL { + return w.account.URL +} + +// Status implements accounts.Wallet, returning whether the account held by the +// keystore wallet is unlocked or not. +func (w *keystoreWallet) Status() (string, error) { + w.keystore.mu.RLock() + defer w.keystore.mu.RUnlock() + + if _, ok := w.keystore.unlocked[w.account.Address]; ok { + return "Unlocked", nil + } + return "Locked", nil +} + +// Open implements accounts.Wallet, but is a noop for plain wallets since there +// is no connection or decryption step necessary to access the list of accounts. +func (w *keystoreWallet) Open(passphrase string) error { return nil } + +// Close implements accounts.Wallet, but is a noop for plain wallets since there +// is no meaningful open operation. +func (w *keystoreWallet) Close() error { return nil } + +// Accounts implements accounts.Wallet, returning an account list consisting of +// a single account that the plain keystore wallet contains. +func (w *keystoreWallet) Accounts() []accounts.Account { + return []accounts.Account{w.account} +} + +// Contains implements accounts.Wallet, returning whether a particular account is +// or is not wrapped by this wallet instance. +func (w *keystoreWallet) Contains(account accounts.Account) bool { + return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL) +} + +// Derive implements accounts.Wallet, but is a noop for plain wallets since there +// is no notion of hierarchical account derivation for plain keystore accounts. +func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + return accounts.Account{}, accounts.ErrNotSupported +} + +// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since +// there is no notion of hierarchical account derivation for plain keystore accounts. +func (w *keystoreWallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { +} + +// signHash attempts to sign the given hash with +// the given account. If the wallet does not wrap this particular account, an +// error is returned to avoid account leakage (even though in theory we may be +// able to sign via our shared keystore backend). +func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHash(account, hash) +} + +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed. +func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + return w.signHash(account, crypto.Keccak256(data)) +} + +// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed. +func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) +} + +// SignText implements accounts.Wallet, attempting to sign the hash of +// the given text with the given account. +func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.signHash(account, accounts.TextHash(text)) +} + +// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the +// hash of the given text with the given account using passphrase as extra authentication. +func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text)) +} + +// SignTx implements accounts.Wallet, attempting to sign the given transaction +// with the given account. If the wallet does not wrap this particular account, +// an error is returned to avoid account leakage (even though in theory we may +// be able to sign via our shared keystore backend). +func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignTx(account, tx, chainID) +} + +// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given +// transaction with the given account using passphrase as extra authentication. +func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + // Make sure the requested account is contained within + if !w.Contains(account) { + return nil, accounts.ErrUnknownAccount + } + // Account seems valid, request the keystore to sign + return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID) +} diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go new file mode 100644 index 0000000..1bef321 --- /dev/null +++ b/accounts/keystore/watch.go @@ -0,0 +1,134 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris +// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris + +package keystore + +import ( + "os" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/fsnotify/fsnotify" +) + +type watcher struct { + ac *accountCache + running bool // set to true when runloop begins + runEnded bool // set to true when runloop ends + starting bool // set to true prior to runloop starting + quit chan struct{} +} + +func newWatcher(ac *accountCache) *watcher { + return &watcher{ + ac: ac, + quit: make(chan struct{}), + } +} + +// enabled returns false on systems not supported. +func (*watcher) enabled() bool { return true } + +// starts the watcher loop in the background. +// Start a watcher in the background if that's not already in progress. +// The caller must hold w.ac.mu. +func (w *watcher) start() { + if w.starting || w.running { + return + } + w.starting = true + go w.loop() +} + +func (w *watcher) close() { + close(w.quit) +} + +func (w *watcher) loop() { + defer func() { + w.ac.mu.Lock() + w.running = false + w.starting = false + w.runEnded = true + w.ac.mu.Unlock() + }() + logger := log.New("path", w.ac.keydir) + + // Create new watcher. + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Error("Failed to start filesystem watcher", "err", err) + return + } + defer watcher.Close() + if err := watcher.Add(w.ac.keydir); err != nil { + if !os.IsNotExist(err) { + logger.Warn("Failed to watch keystore folder", "err", err) + } + return + } + + logger.Trace("Started watching keystore folder", "folder", w.ac.keydir) + defer logger.Trace("Stopped watching keystore folder") + + w.ac.mu.Lock() + w.running = true + w.ac.mu.Unlock() + + // Wait for file system events and reload. + // When an event occurs, the reload call is delayed a bit so that + // multiple events arriving quickly only cause a single reload. + var ( + debounceDuration = 500 * time.Millisecond + rescanTriggered = false + debounce = time.NewTimer(0) + ) + // Ignore initial trigger + if !debounce.Stop() { + <-debounce.C + } + defer debounce.Stop() + for { + select { + case <-w.quit: + return + case _, ok := <-watcher.Events: + if !ok { + return + } + // Trigger the scan (with delay), if not already triggered + if !rescanTriggered { + debounce.Reset(debounceDuration) + rescanTriggered = true + } + // The fsnotify library does provide more granular event-info, it + // would be possible to refresh individual affected files instead + // of scheduling a full rescan. For most cases though, the + // full rescan is quick and obviously simplest. + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Info("Filesystem watcher error", "err", err) + case <-debounce.C: + w.ac.scanAccounts() + rescanTriggered = false + } + } +} diff --git a/accounts/keystore/watch_fallback.go b/accounts/keystore/watch_fallback.go new file mode 100644 index 0000000..e3c133b --- /dev/null +++ b/accounts/keystore/watch_fallback.go @@ -0,0 +1,35 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris) +// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris + +// This is the fallback implementation of directory watching. +// It is used on unsupported platforms. + +package keystore + +type watcher struct { + running bool + runEnded bool +} + +func newWatcher(*accountCache) *watcher { return new(watcher) } +func (*watcher) start() {} +func (*watcher) close() {} + +// enabled returns false on systems not supported. +func (*watcher) enabled() bool { return false } diff --git a/accounts/manager.go b/accounts/manager.go new file mode 100644 index 0000000..cbe4f7c --- /dev/null +++ b/accounts/manager.go @@ -0,0 +1,275 @@ +// 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 accounts + +import ( + "reflect" + "sort" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" +) + +// managerSubBufferSize determines how many incoming wallet events +// the manager will buffer in its channel. +const managerSubBufferSize = 50 + +// Config contains the settings of the global account manager. +// +// TODO(rjl493456442, karalabe, holiman): Get rid of this when account management +// is removed in favor of Clef. +type Config struct { + InsecureUnlockAllowed bool // Whether account unlocking in insecure environment is allowed +} + +// newBackendEvent lets the manager know it should +// track the given backend for wallet updates. +type newBackendEvent struct { + backend Backend + processed chan struct{} // Informs event emitter that backend has been integrated +} + +// Manager is an overarching account manager that can communicate with various +// backends for signing transactions. +type Manager struct { + config *Config // Global account manager configurations + backends map[reflect.Type][]Backend // Index of backends currently registered + updaters []event.Subscription // Wallet update subscriptions for all backends + updates chan WalletEvent // Subscription sink for backend wallet changes + newBackends chan newBackendEvent // Incoming backends to be tracked by the manager + wallets []Wallet // Cache of all wallets from all registered backends + + feed event.Feed // Wallet feed notifying of arrivals/departures + + quit chan chan error + term chan struct{} // Channel is closed upon termination of the update loop + lock sync.RWMutex +} + +// NewManager creates a generic account manager to sign transaction via various +// supported backends. +func NewManager(config *Config, backends ...Backend) *Manager { + // Retrieve the initial list of wallets from the backends and sort by URL + var wallets []Wallet + for _, backend := range backends { + wallets = merge(wallets, backend.Wallets()...) + } + // Subscribe to wallet notifications from all backends + updates := make(chan WalletEvent, managerSubBufferSize) + + subs := make([]event.Subscription, len(backends)) + for i, backend := range backends { + subs[i] = backend.Subscribe(updates) + } + // Assemble the account manager and return + am := &Manager{ + config: config, + backends: make(map[reflect.Type][]Backend), + updaters: subs, + updates: updates, + newBackends: make(chan newBackendEvent), + wallets: wallets, + quit: make(chan chan error), + term: make(chan struct{}), + } + for _, backend := range backends { + kind := reflect.TypeOf(backend) + am.backends[kind] = append(am.backends[kind], backend) + } + go am.update() + + return am +} + +// 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 +} + +// Config returns the configuration of account manager. +func (am *Manager) Config() *Config { + return am.config +} + +// AddBackend starts the tracking of an additional backend for wallet updates. +// cmd/geth assumes once this func returns the backends have been already integrated. +func (am *Manager) AddBackend(backend Backend) { + done := make(chan struct{}) + am.newBackends <- newBackendEvent{backend, done} + <-done +} + +// update is the wallet event loop listening for notifications from the backends +// and updating the cache of wallets. +func (am *Manager) update() { + // Close all subscriptions when the manager terminates + defer func() { + am.lock.Lock() + for _, sub := range am.updaters { + sub.Unsubscribe() + } + am.updaters = nil + am.lock.Unlock() + }() + + // Loop until termination + for { + select { + case event := <-am.updates: + // Wallet event arrived, update local cache + am.lock.Lock() + switch event.Kind { + case WalletArrived: + am.wallets = merge(am.wallets, event.Wallet) + case WalletDropped: + am.wallets = drop(am.wallets, event.Wallet) + } + am.lock.Unlock() + + // Notify any listeners of the event + am.feed.Send(event) + case event := <-am.newBackends: + am.lock.Lock() + // Update caches + backend := event.backend + am.wallets = merge(am.wallets, backend.Wallets()...) + am.updaters = append(am.updaters, backend.Subscribe(am.updates)) + kind := reflect.TypeOf(backend) + am.backends[kind] = append(am.backends[kind], backend) + am.lock.Unlock() + close(event.processed) + case errc := <-am.quit: + // Manager terminating, return + errc <- nil + // Signals event emitters the loop is not receiving values + // to prevent them from getting stuck. + close(am.term) + return + } + } +} + +// Backends retrieves the backend(s) with the given type from the account manager. +func (am *Manager) Backends(kind reflect.Type) []Backend { + am.lock.RLock() + defer am.lock.RUnlock() + + return am.backends[kind] +} + +// Wallets returns all signer accounts registered under this account manager. +func (am *Manager) Wallets() []Wallet { + am.lock.RLock() + defer am.lock.RUnlock() + + return am.walletsNoLock() +} + +// walletsNoLock returns all registered wallets. Callers must hold am.lock. +func (am *Manager) walletsNoLock() []Wallet { + cpy := make([]Wallet, len(am.wallets)) + copy(cpy, am.wallets) + return cpy +} + +// Wallet retrieves the wallet associated with a particular URL. +func (am *Manager) Wallet(url string) (Wallet, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + parsed, err := parseURL(url) + if err != nil { + return nil, err + } + for _, wallet := range am.walletsNoLock() { + if wallet.URL() == parsed { + return wallet, nil + } + } + return nil, ErrUnknownWallet +} + +// Accounts returns all account addresses of all wallets within the account manager +func (am *Manager) Accounts() []common.Address { + am.lock.RLock() + defer am.lock.RUnlock() + + addresses := make([]common.Address, 0) // return [] instead of nil if empty + for _, wallet := range am.wallets { + for _, account := range wallet.Accounts() { + addresses = append(addresses, account.Address) + } + } + return addresses +} + +// Find attempts to locate the wallet corresponding to a specific account. Since +// accounts can be dynamically added to and removed from wallets, this method has +// a linear runtime in the number of wallets. +func (am *Manager) Find(account Account) (Wallet, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + for _, wallet := range am.wallets { + if wallet.Contains(account) { + return wallet, nil + } + } + return nil, ErrUnknownAccount +} + +// Subscribe creates an async subscription to receive notifications when the +// manager detects the arrival or departure of a wallet from any of its backends. +func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription { + return am.feed.Subscribe(sink) +} + +// merge is a sorted analogue of append for wallets, where the ordering of the +// origin list is preserved by inserting new wallets at the correct position. +// +// The original slice is assumed to be already sorted by URL. +func merge(slice []Wallet, wallets ...Wallet) []Wallet { + for _, wallet := range wallets { + n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) + if n == len(slice) { + slice = append(slice, wallet) + continue + } + slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) + } + return slice +} + +// drop is the counterpart of merge, which looks up wallets from within the sorted +// cache and removes the ones specified. +func drop(slice []Wallet, wallets ...Wallet) []Wallet { + for _, wallet := range wallets { + n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) + if n == len(slice) { + // Wallet not found, may happen during startup + continue + } + slice = append(slice[:n], slice[n+1:]...) + } + return slice +} diff --git a/accounts/scwallet/README.md b/accounts/scwallet/README.md new file mode 100644 index 0000000..28079c4 --- /dev/null +++ b/accounts/scwallet/README.md @@ -0,0 +1,106 @@ +# Using the smartcard wallet + +## Requirements + + * A USB smartcard reader + * A keycard that supports the status app + * PCSCD version 4.3 running on your system **Only version 4.3 is currently supported** + +## Preparing the smartcard + + **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) + + You also need to make sure that the PCSC daemon is running on your system. + + Then, you can install the application to the card by typing: + + ``` + keycard install -a keycard_v2.2.1.cap && keycard init + ``` + + At the end of this process, you will be provided with a PIN, a PUK and a pairing password. Write them down, you'll need them shortly. + + Start `geth` with the `console` command. You will notice the following warning: + + ``` + WARN [04-09|16:58:38.898] Failed to open wallet url=keycard://044def09 err="smartcard: pairing password needed" + ``` + + Write down the URL (`keycard://044def09` in this example). Then ask `geth` to open the wallet: + + ``` + > personal.openWallet("keycard://044def09", "pairing password") + ``` + + The pairing password has been generated during the card initialization process. + + The process needs to be repeated once more with the PIN: + + ``` + > personal.openWallet("keycard://044def09", "PIN number") + ``` + + If everything goes well, you should see your new account when typing `personal` on the console: + + ``` + > personal + WARN [04-09|17:02:07.330] Smartcard wallet account derivation failed url=keycard://044def09 err="Unexpected response status Cla=0x80, Ins=0xd1, Sw=0x6985" + { + listAccounts: [], + listWallets: [{ + status: "Empty, waiting for initialization", + url: "keycard://044def09" + }], + ... + } + ``` + + So the communication with the card is working, but there is no key associated with this wallet. Let's create it: + + ``` + > personal.initializeWallet("keycard://044def09") + "tilt ... impact" + ``` + + You should get a list of words, this is your seed so write them down. Your wallet should now be initialized: + + ``` + > personal.listWallets + [{ + accounts: [{ + address: "0x678b7cd55c61917defb23546a41803c5bfefbc7a", + url: "keycard://044d/m/44'/60'/0'/0/0" + }], + status: "Online", + url: "keycard://044def09" + }] + ``` + + You're all set! + +## Usage + + 1. Start `geth` with the `console` command + 2. Check the card's URL by checking `personal.listWallets`: + +``` + listWallets: [{ + status: "Online, can derive public keys", + url: "keycard://a4d73015" + }] +``` + + 3. Open the wallet, you will be prompted for your pairing password, then PIN: + +``` +personal.openWallet("keycard://a4d73015") +``` + + 4. Check that creation was successful by typing e.g. `personal`. Then use it like a regular wallet. + +## Known issues + + * Starting geth with a valid card seems to make firefox crash. + * PCSC version 4.4 should work, but is currently untested diff --git a/accounts/scwallet/apdu.go b/accounts/scwallet/apdu.go new file mode 100644 index 0000000..bd36606 --- /dev/null +++ b/accounts/scwallet/apdu.go @@ -0,0 +1,87 @@ +// 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 scwallet + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +// commandAPDU represents an application data unit sent to a smartcard. +type commandAPDU struct { + Cla, Ins, P1, P2 uint8 // Class, Instruction, Parameter 1, Parameter 2 + Data []byte // Command data + Le uint8 // Command data length +} + +// serialize serializes a command APDU. +func (ca commandAPDU) serialize() ([]byte, error) { + buf := new(bytes.Buffer) + + if err := binary.Write(buf, binary.BigEndian, ca.Cla); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.Ins); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.P1); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.P2); err != nil { + return nil, err + } + if len(ca.Data) > 0 { + if err := binary.Write(buf, binary.BigEndian, uint8(len(ca.Data))); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.Data); err != nil { + return nil, err + } + } + if err := binary.Write(buf, binary.BigEndian, ca.Le); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// responseAPDU represents an application data unit received from a smart card. +type responseAPDU struct { + Data []byte // response data + Sw1, Sw2 uint8 // status words 1 and 2 +} + +// deserialize deserializes a response APDU. +func (ra *responseAPDU) deserialize(data []byte) error { + if len(data) < 2 { + return fmt.Errorf("can not deserialize data: payload too short (%d < 2)", len(data)) + } + + ra.Data = make([]byte, len(data)-2) + + buf := bytes.NewReader(data) + if err := binary.Read(buf, binary.BigEndian, &ra.Data); err != nil { + return err + } + if err := binary.Read(buf, binary.BigEndian, &ra.Sw1); err != nil { + return err + } + if err := binary.Read(buf, binary.BigEndian, &ra.Sw2); err != nil { + return err + } + return nil +} diff --git a/accounts/scwallet/hub.go b/accounts/scwallet/hub.go new file mode 100644 index 0000000..1b1899d --- /dev/null +++ b/accounts/scwallet/hub.go @@ -0,0 +1,303 @@ +// 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 . + +// This package implements support for smartcard-based hardware wallets such as +// the one written by Status: https://github.com/status-im/hardware-wallet +// +// This implementation of smartcard wallets have a different interaction process +// to other types of hardware wallet. The process works like this: +// +// 1. (First use with a given client) Establish a pairing between hardware +// wallet and client. This requires a secret value called a 'pairing password'. +// You can pair with an unpaired wallet with `personal.openWallet(URI, pairing password)`. +// 2. (First use only) Initialize the wallet, which generates a keypair, stores +// it on the wallet, and returns it so the user can back it up. You can +// initialize a wallet with `personal.initializeWallet(URI)`. +// 3. Connect to the wallet using the pairing information established in step 1. +// You can connect to a paired wallet with `personal.openWallet(URI, PIN)`. +// 4. Interact with the wallet as normal. + +package scwallet + +import ( + "encoding/json" + "io" + "os" + "path/filepath" + "sort" + "sync" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + pcsc "github.com/gballet/go-libpcsclite" +) + +// Scheme is the URI prefix for smartcard wallets. +const Scheme = "keycard" + +// refreshCycle is the maximum time between wallet refreshes (if USB hotplug +// notifications don't work). +const refreshCycle = time.Second + +// refreshThrottling is the minimum time between wallet refreshes to avoid thrashing. +const refreshThrottling = 500 * time.Millisecond + +// smartcardPairing contains information about a smart card we have paired with +// or might pair with the hub. +type smartcardPairing struct { + PublicKey []byte `json:"publicKey"` + PairingIndex uint8 `json:"pairingIndex"` + PairingKey []byte `json:"pairingKey"` + Accounts map[common.Address]accounts.DerivationPath `json:"accounts"` +} + +// Hub is a accounts.Backend that can find and handle generic PC/SC hardware wallets. +type Hub struct { + scheme string // Protocol scheme prefixing account and wallet URLs. + + context *pcsc.Client + datadir string + pairings map[string]smartcardPairing + + refreshed time.Time // Time instance when the list of wallets was last refreshed + wallets map[string]*Wallet // Mapping from reader names to wallet instances + updateFeed event.Feed // Event feed to notify wallet additions/removals + updateScope event.SubscriptionScope // Subscription scope tracking current live listeners + updating bool // Whether the event notification loop is running + + quit chan chan error + + stateLock sync.RWMutex // Protects the internals of the hub from racey access +} + +func (hub *Hub) readPairings() error { + hub.pairings = make(map[string]smartcardPairing) + pairingFile, err := os.Open(filepath.Join(hub.datadir, "smartcards.json")) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer pairingFile.Close() + + pairingData, err := io.ReadAll(pairingFile) + if err != nil { + return err + } + var pairings []smartcardPairing + if err := json.Unmarshal(pairingData, &pairings); err != nil { + return err + } + + for _, pairing := range pairings { + hub.pairings[string(pairing.PublicKey)] = pairing + } + return nil +} + +func (hub *Hub) writePairings() error { + pairingFile, err := os.OpenFile(filepath.Join(hub.datadir, "smartcards.json"), os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + return err + } + defer pairingFile.Close() + + pairings := make([]smartcardPairing, 0, len(hub.pairings)) + for _, pairing := range hub.pairings { + pairings = append(pairings, pairing) + } + + pairingData, err := json.Marshal(pairings) + if err != nil { + return err + } + + if _, err := pairingFile.Write(pairingData); err != nil { + return err + } + + return nil +} + +func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing { + if pairing, ok := hub.pairings[string(wallet.PublicKey)]; ok { + return &pairing + } + return nil +} + +func (hub *Hub) setPairing(wallet *Wallet, pairing *smartcardPairing) error { + if pairing == nil { + delete(hub.pairings, string(wallet.PublicKey)) + } else { + hub.pairings[string(wallet.PublicKey)] = *pairing + } + return hub.writePairings() +} + +// NewHub creates a new hardware wallet manager for smartcards. +func NewHub(daemonPath string, scheme string, datadir string) (*Hub, error) { + context, err := pcsc.EstablishContext(daemonPath, pcsc.ScopeSystem) + if err != nil { + return nil, err + } + hub := &Hub{ + scheme: scheme, + context: context, + datadir: datadir, + wallets: make(map[string]*Wallet), + quit: make(chan chan error), + } + if err := hub.readPairings(); err != nil { + return nil, err + } + hub.refreshWallets() + return hub, nil +} + +// Wallets implements accounts.Backend, returning all the currently tracked smart +// cards that appear to be hardware wallets. +func (hub *Hub) Wallets() []accounts.Wallet { + // Make sure the list of wallets is up to date + hub.refreshWallets() + + hub.stateLock.RLock() + defer hub.stateLock.RUnlock() + + cpy := make([]accounts.Wallet, 0, len(hub.wallets)) + for _, wallet := range hub.wallets { + cpy = append(cpy, wallet) + } + sort.Sort(accounts.WalletsByURL(cpy)) + return cpy +} + +// refreshWallets scans the devices attached to the machine and updates the +// list of wallets based on the found devices. +func (hub *Hub) refreshWallets() { + // Don't scan the USB like crazy it the user fetches wallets in a loop + hub.stateLock.RLock() + elapsed := time.Since(hub.refreshed) + hub.stateLock.RUnlock() + + if elapsed < refreshThrottling { + return + } + // Retrieve all the smart card reader to check for cards + readers, err := hub.context.ListReaders() + if err != nil { + // This is a perverted hack, the scard library returns an error if no card + // readers are present instead of simply returning an empty list. We don't + // want to fill the user's log with errors, so filter those out. + if err.Error() != "scard: Cannot find a smart card reader." { + log.Error("Failed to enumerate smart card readers", "err", err) + return + } + } + // Transform the current list of wallets into the new one + hub.stateLock.Lock() + + events := []accounts.WalletEvent{} + seen := make(map[string]struct{}) + + for _, reader := range readers { + // Mark the reader as present + seen[reader] = struct{}{} + + // If we already know about this card, skip to the next reader, otherwise clean up + if wallet, ok := hub.wallets[reader]; ok { + if err := wallet.ping(); err == nil { + continue + } + wallet.Close() + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + delete(hub.wallets, reader) + } + // New card detected, try to connect to it + card, err := hub.context.Connect(reader, pcsc.ShareShared, pcsc.ProtocolAny) + if err != nil { + log.Debug("Failed to open smart card", "reader", reader, "err", err) + continue + } + wallet := NewWallet(hub, card) + if err = wallet.connect(); err != nil { + log.Debug("Failed to connect to smart card", "reader", reader, "err", err) + card.Disconnect(pcsc.LeaveCard) + continue + } + // Card connected, start tracking among the wallets + hub.wallets[reader] = wallet + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) + } + // Remove any wallets no longer present + for reader, wallet := range hub.wallets { + if _, ok := seen[reader]; !ok { + wallet.Close() + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + delete(hub.wallets, reader) + } + } + hub.refreshed = time.Now() + hub.stateLock.Unlock() + + for _, event := range events { + hub.updateFeed.Send(event) + } +} + +// Subscribe implements accounts.Backend, creating an async subscription to +// receive notifications on the addition or removal of smart card wallets. +func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + // We need the mutex to reliably start/stop the update loop + hub.stateLock.Lock() + defer hub.stateLock.Unlock() + + // Subscribe the caller and track the subscriber count + sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink)) + + // Subscribers require an active notification loop, start it + if !hub.updating { + hub.updating = true + go hub.updater() + } + return sub +} + +// updater is responsible for maintaining an up-to-date list of wallets managed +// by the smart card hub, and for firing wallet addition/removal events. +func (hub *Hub) updater() { + for { + // TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout + // <-hub.changes + time.Sleep(refreshCycle) + + // Run the wallet refresher + hub.refreshWallets() + + // If all our subscribers left, stop the updater + hub.stateLock.Lock() + if hub.updateScope.Count() == 0 { + hub.updating = false + hub.stateLock.Unlock() + return + } + hub.stateLock.Unlock() + } +} diff --git a/accounts/scwallet/securechannel.go b/accounts/scwallet/securechannel.go new file mode 100644 index 0000000..b3a7be8 --- /dev/null +++ b/accounts/scwallet/securechannel.go @@ -0,0 +1,339 @@ +// 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 scwallet + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/crypto" + pcsc "github.com/gballet/go-libpcsclite" + "golang.org/x/crypto/pbkdf2" + "golang.org/x/text/unicode/norm" +) + +const ( + maxPayloadSize = 223 + pairP1FirstStep = 0 + pairP1LastStep = 1 + + scSecretLength = 32 + scBlockSize = 16 + + insOpenSecureChannel = 0x10 + insMutuallyAuthenticate = 0x11 + insPair = 0x12 + insUnpair = 0x13 + + pairingSalt = "Keycard Pairing Password Salt" +) + +// SecureChannelSession enables secure communication with a hardware wallet. +type SecureChannelSession struct { + card *pcsc.Card // A handle to the smartcard for communication + secret []byte // A shared secret generated from our ECDSA keys + publicKey []byte // Our own ephemeral public key + PairingKey []byte // A permanent shared secret for a pairing, if present + sessionEncKey []byte // The current session encryption key + sessionMacKey []byte // The current session MAC key + iv []byte // The current IV + PairingIndex uint8 // The pairing index +} + +// NewSecureChannelSession creates a new secure channel for the given card and public key. +func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) { + // Generate an ECDSA keypair for ourselves + key, err := crypto.GenerateKey() + if err != nil { + return nil, err + } + cardPublic, err := crypto.UnmarshalPubkey(keyData) + if err != nil { + return nil, fmt.Errorf("could not unmarshal public key from card: %v", err) + } + secret, _ := crypto.S256().ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes()) + return &SecureChannelSession{ + card: card, + secret: secret.Bytes(), + publicKey: crypto.FromECDSAPub(&key.PublicKey), + }, nil +} + +// Pair establishes a new pairing with the smartcard. +func (s *SecureChannelSession) Pair(pairingPassword []byte) error { + secretHash := pbkdf2.Key(norm.NFKD.Bytes(pairingPassword), norm.NFKD.Bytes([]byte(pairingSalt)), 50000, 32, sha256.New) + + challenge := make([]byte, 32) + if _, err := rand.Read(challenge); err != nil { + return err + } + + response, err := s.pair(pairP1FirstStep, challenge) + if err != nil { + return err + } + + md := sha256.New() + md.Write(secretHash[:]) + md.Write(challenge) + + expectedCryptogram := md.Sum(nil) + cardCryptogram := response.Data[:32] + cardChallenge := response.Data[32:64] + + if !bytes.Equal(expectedCryptogram, cardCryptogram) { + return fmt.Errorf("invalid card cryptogram %v != %v", expectedCryptogram, cardCryptogram) + } + + md.Reset() + md.Write(secretHash[:]) + md.Write(cardChallenge) + response, err = s.pair(pairP1LastStep, md.Sum(nil)) + if err != nil { + return err + } + + md.Reset() + md.Write(secretHash[:]) + md.Write(response.Data[1:]) + s.PairingKey = md.Sum(nil) + s.PairingIndex = response.Data[0] + + return nil +} + +// Unpair disestablishes an existing pairing. +func (s *SecureChannelSession) Unpair() error { + if s.PairingKey == nil { + return errors.New("cannot unpair: not paired") + } + + _, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{}) + if err != nil { + return err + } + s.PairingKey = nil + // Close channel + s.iv = nil + return nil +} + +// Open initializes the secure channel. +func (s *SecureChannelSession) Open() error { + if s.iv != nil { + return errors.New("session already opened") + } + + response, err := s.open() + if err != nil { + return err + } + + // Generate the encryption/mac key by hashing our shared secret, + // pairing key, and the first bytes returned from the Open APDU. + md := sha512.New() + md.Write(s.secret) + md.Write(s.PairingKey) + md.Write(response.Data[:scSecretLength]) + keyData := md.Sum(nil) + s.sessionEncKey = keyData[:scSecretLength] + s.sessionMacKey = keyData[scSecretLength : scSecretLength*2] + + // The IV is the last bytes returned from the Open APDU. + s.iv = response.Data[scSecretLength:] + + return s.mutuallyAuthenticate() +} + +// mutuallyAuthenticate is an internal method to authenticate both ends of the +// connection. +func (s *SecureChannelSession) mutuallyAuthenticate() error { + data := make([]byte, scSecretLength) + if _, err := rand.Read(data); err != nil { + return err + } + + response, err := s.transmitEncrypted(claSCWallet, insMutuallyAuthenticate, 0, 0, data) + if err != nil { + return err + } + if response.Sw1 != 0x90 || response.Sw2 != 0x00 { + return fmt.Errorf("got unexpected response from MUTUALLY_AUTHENTICATE: %#x%x", response.Sw1, response.Sw2) + } + + if len(response.Data) != scSecretLength { + return fmt.Errorf("response from MUTUALLY_AUTHENTICATE was %d bytes, expected %d", len(response.Data), scSecretLength) + } + + return nil +} + +// open is an internal method that sends an open APDU. +func (s *SecureChannelSession) open() (*responseAPDU, error) { + return transmit(s.card, &commandAPDU{ + Cla: claSCWallet, + Ins: insOpenSecureChannel, + P1: s.PairingIndex, + P2: 0, + Data: s.publicKey, + Le: 0, + }) +} + +// pair is an internal method that sends a pair APDU. +func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error) { + return transmit(s.card, &commandAPDU{ + Cla: claSCWallet, + Ins: insPair, + P1: p1, + P2: 0, + Data: data, + Le: 0, + }) +} + +// transmitEncrypted sends an encrypted message, and decrypts and returns the response. +func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) { + if s.iv == nil { + return nil, errors.New("channel not open") + } + + data, err := s.encryptAPDU(data) + if err != nil { + return nil, err + } + meta := [16]byte{cla, ins, p1, p2, byte(len(data) + scBlockSize)} + if err = s.updateIV(meta[:], data); err != nil { + return nil, err + } + + fulldata := make([]byte, len(s.iv)+len(data)) + copy(fulldata, s.iv) + copy(fulldata[len(s.iv):], data) + + response, err := transmit(s.card, &commandAPDU{ + Cla: cla, + Ins: ins, + P1: p1, + P2: p2, + Data: fulldata, + }) + if err != nil { + return nil, err + } + + rmeta := [16]byte{byte(len(response.Data))} + rmac := response.Data[:len(s.iv)] + rdata := response.Data[len(s.iv):] + plainData, err := s.decryptAPDU(rdata) + if err != nil { + return nil, err + } + + if err = s.updateIV(rmeta[:], rdata); err != nil { + return nil, err + } + if !bytes.Equal(s.iv, rmac) { + return nil, errors.New("invalid MAC in response") + } + + rapdu := &responseAPDU{} + rapdu.deserialize(plainData) + + if rapdu.Sw1 != sw1Ok { + return nil, fmt.Errorf("unexpected response status Cla=%#x, Ins=%#x, Sw=%#x%x", cla, ins, rapdu.Sw1, rapdu.Sw2) + } + + return rapdu, nil +} + +// encryptAPDU is an internal method that serializes and encrypts an APDU. +func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) { + if len(data) > maxPayloadSize { + return nil, fmt.Errorf("payload of %d bytes exceeds maximum of %d", len(data), maxPayloadSize) + } + data = pad(data, 0x80) + + ret := make([]byte, len(data)) + + a, err := aes.NewCipher(s.sessionEncKey) + if err != nil { + return nil, err + } + crypter := cipher.NewCBCEncrypter(a, s.iv) + crypter.CryptBlocks(ret, data) + return ret, nil +} + +// pad applies message padding to a 16 byte boundary. +func pad(data []byte, terminator byte) []byte { + padded := make([]byte, (len(data)/16+1)*16) + copy(padded, data) + padded[len(data)] = terminator + return padded +} + +// decryptAPDU is an internal method that decrypts and deserializes an APDU. +func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) { + a, err := aes.NewCipher(s.sessionEncKey) + if err != nil { + return nil, err + } + + ret := make([]byte, len(data)) + + crypter := cipher.NewCBCDecrypter(a, s.iv) + crypter.CryptBlocks(ret, data) + return unpad(ret, 0x80) +} + +// unpad strips padding from a message. +func unpad(data []byte, terminator byte) ([]byte, error) { + for i := 1; i <= 16; i++ { + switch data[len(data)-i] { + case 0: + continue + case terminator: + return data[:len(data)-i], nil + default: + return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i]) + } + } + return nil, errors.New("expected end of padding, got 0") +} + +// updateIV is an internal method that updates the initialization vector after +// each message exchanged. +func (s *SecureChannelSession) updateIV(meta, data []byte) error { + data = pad(data, 0) + a, err := aes.NewCipher(s.sessionMacKey) + if err != nil { + return err + } + crypter := cipher.NewCBCEncrypter(a, make([]byte, 16)) + crypter.CryptBlocks(meta, meta) + crypter.CryptBlocks(data, data) + // The first 16 bytes of the last block is the MAC + s.iv = data[len(data)-32 : len(data)-16] + return nil +} diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go new file mode 100644 index 0000000..58cfc88 --- /dev/null +++ b/accounts/scwallet/wallet.go @@ -0,0 +1,1096 @@ +// 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 scwallet + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "encoding/asn1" + "encoding/binary" + "errors" + "fmt" + "math/big" + "regexp" + "sort" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + pcsc "github.com/gballet/go-libpcsclite" + "github.com/status-im/keycard-go/derivationpath" +) + +// ErrPairingPasswordNeeded is returned if opening the smart card requires pairing with a pairing +// password. In this case, the calling application should request user input to enter +// the pairing password and send it back. +var ErrPairingPasswordNeeded = errors.New("smartcard: pairing password needed") + +// ErrPINNeeded is returned if opening the smart card requires a PIN code. In +// this case, the calling application should request user input to enter the PIN +// and send it back. +var ErrPINNeeded = errors.New("smartcard: pin needed") + +// ErrPINUnblockNeeded is returned if opening the smart card requires a PIN code, +// but all PIN attempts have already been exhausted. In this case the calling +// application should request user input for the PUK and a new PIN code to set +// fo the card. +var ErrPINUnblockNeeded = errors.New("smartcard: pin unblock needed") + +// ErrAlreadyOpen is returned if the smart card is attempted to be opened, but +// there is already a paired and unlocked session. +var ErrAlreadyOpen = errors.New("smartcard: already open") + +// ErrPubkeyMismatch is returned if the public key recovered from a signature +// does not match the one expected by the user. +var ErrPubkeyMismatch = errors.New("smartcard: recovered public key mismatch") + +var ( + appletAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x01, 0x01} + // DerivationSignatureHash is used to derive the public key from the signature of this hash + DerivationSignatureHash = sha256.Sum256(common.Hash{}.Bytes()) +) + +var ( + // PinRegexp is the regular expression used to validate PIN codes. + pinRegexp = regexp.MustCompile(`^[0-9]{6,}$`) + + // PukRegexp is the regular expression used to validate PUK codes. + pukRegexp = regexp.MustCompile(`^[0-9]{12,}$`) +) + +// List of APDU command-related constants +const ( + claISO7816 = 0 + claSCWallet = 0x80 + + insSelect = 0xA4 + insGetResponse = 0xC0 + sw1GetResponse = 0x61 + sw1Ok = 0x90 + + insVerifyPin = 0x20 + insUnblockPin = 0x22 + insExportKey = 0xC2 + insSign = 0xC0 + insLoadKey = 0xD0 + insDeriveKey = 0xD1 + insStatus = 0xF2 +) + +// List of ADPU command parameters +const ( + P1DeriveKeyFromMaster = uint8(0x00) + P1DeriveKeyFromParent = uint8(0x01) + P1DeriveKeyFromCurrent = uint8(0x10) + statusP1WalletStatus = uint8(0x00) + statusP1Path = uint8(0x01) + signP1PrecomputedHash = uint8(0x00) + signP2OnlyBlock = uint8(0x00) + exportP1Any = uint8(0x00) + exportP2Pubkey = uint8(0x01) +) + +// Minimum time to wait between self derivation attempts, even it the user is +// requesting accounts like crazy. +const selfDeriveThrottling = time.Second + +// Wallet represents a smartcard wallet instance. +type Wallet struct { + Hub *Hub // A handle to the Hub that instantiated this wallet. + PublicKey []byte // The wallet's public key (used for communication and identification, not signing!) + + lock sync.Mutex // Lock that gates access to struct fields and communication with the card + card *pcsc.Card // A handle to the smartcard interface for the wallet. + session *Session // The secure communication session with the card + log log.Logger // Contextual logger to tag the base with its id + + deriveNextPaths []accounts.DerivationPath // Next derivation paths for account auto-discovery (multiple bases supported) + deriveNextAddrs []common.Address // Next derived account addresses for auto-discovery (multiple bases supported) + deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with + deriveReq chan chan struct{} // Channel to request a self-derivation on + deriveQuit chan chan error // Channel to terminate the self-deriver with +} + +// NewWallet constructs and returns a new Wallet instance. +func NewWallet(hub *Hub, card *pcsc.Card) *Wallet { + wallet := &Wallet{ + Hub: hub, + card: card, + } + return wallet +} + +// transmit sends an APDU to the smartcard and receives and decodes the response. +// It automatically handles requests by the card to fetch the return data separately, +// and returns an error if the response status code is not success. +func transmit(card *pcsc.Card, command *commandAPDU) (*responseAPDU, error) { + data, err := command.serialize() + if err != nil { + return nil, err + } + + responseData, _, err := card.Transmit(data) + if err != nil { + return nil, err + } + + response := new(responseAPDU) + if err = response.deserialize(responseData); err != nil { + return nil, err + } + + // Are we being asked to fetch the response separately? + if response.Sw1 == sw1GetResponse && (command.Cla != claISO7816 || command.Ins != insGetResponse) { + return transmit(card, &commandAPDU{ + Cla: claISO7816, + Ins: insGetResponse, + P1: 0, + P2: 0, + Data: nil, + Le: response.Sw2, + }) + } + + if response.Sw1 != sw1Ok { + return nil, fmt.Errorf("unexpected insecure response status Cla=%#x, Ins=%#x, Sw=%#x%x", command.Cla, command.Ins, response.Sw1, response.Sw2) + } + + return response, nil +} + +// applicationInfo encodes information about the smartcard application - its +// instance UID and public key. +type applicationInfo struct { + InstanceUID []byte `asn1:"tag:15"` + PublicKey []byte `asn1:"tag:0"` +} + +// connect connects to the wallet application and establishes a secure channel with it. +// must be called before any other interaction with the wallet. +func (w *Wallet) connect() error { + w.lock.Lock() + defer w.lock.Unlock() + + appinfo, err := w.doselect() + if err != nil { + return err + } + + channel, err := NewSecureChannelSession(w.card, appinfo.PublicKey) + if err != nil { + return err + } + + w.PublicKey = appinfo.PublicKey + w.log = log.New("url", w.URL()) + w.session = &Session{ + Wallet: w, + Channel: channel, + } + return nil +} + +// doselect is an internal (unlocked) function to send a SELECT APDU to the card. +func (w *Wallet) doselect() (*applicationInfo, error) { + response, err := transmit(w.card, &commandAPDU{ + Cla: claISO7816, + Ins: insSelect, + P1: 4, + P2: 0, + Data: appletAID, + }) + if err != nil { + return nil, err + } + + appinfo := new(applicationInfo) + if _, err := asn1.UnmarshalWithParams(response.Data, appinfo, "tag:4"); err != nil { + return nil, err + } + return appinfo, nil +} + +// ping checks the card's status and returns an error if unsuccessful. +func (w *Wallet) ping() error { + w.lock.Lock() + defer w.lock.Unlock() + + // We can't ping if not paired + if !w.session.paired() { + return nil + } + if _, err := w.session.walletStatus(); err != nil { + return err + } + return nil +} + +// release releases any resources held by an open wallet instance. +func (w *Wallet) release() error { + if w.session != nil { + return w.session.release() + } + return nil +} + +// pair is an internal (unlocked) function for establishing a new pairing +// with the wallet. +func (w *Wallet) pair(puk []byte) error { + if w.session.paired() { + return errors.New("wallet already paired") + } + pairing, err := w.session.pair(puk) + if err != nil { + return err + } + if err = w.Hub.setPairing(w, &pairing); err != nil { + return err + } + return w.session.authenticate(pairing) +} + +// Unpair deletes an existing wallet pairing. +func (w *Wallet) Unpair(pin []byte) error { + w.lock.Lock() + defer w.lock.Unlock() + + if !w.session.paired() { + return fmt.Errorf("wallet %x not paired", w.PublicKey) + } + if err := w.session.verifyPin(pin); err != nil { + return fmt.Errorf("failed to verify pin: %s", err) + } + if err := w.session.unpair(); err != nil { + return fmt.Errorf("failed to unpair: %s", err) + } + if err := w.Hub.setPairing(w, nil); err != nil { + return err + } + return nil +} + +// URL retrieves the canonical path under which this wallet is reachable. It is +// user by upper layers to define a sorting order over all wallets from multiple +// backends. +func (w *Wallet) URL() accounts.URL { + return accounts.URL{ + Scheme: w.Hub.scheme, + Path: fmt.Sprintf("%x", w.PublicKey[1:5]), // Byte #0 isn't unique; 1:5 covers << 64K cards, bump to 1:9 for << 4M + } +} + +// Status returns a textual status to aid the user in the current state of the +// wallet. It also returns an error indicating any failure the wallet might have +// encountered. +func (w *Wallet) Status() (string, error) { + w.lock.Lock() + defer w.lock.Unlock() + + // If the card is not paired, we can only wait + if !w.session.paired() { + return "Unpaired, waiting for pairing password", nil + } + // Yay, we have an encrypted session, retrieve the actual status + status, err := w.session.walletStatus() + if err != nil { + return fmt.Sprintf("Failed: %v", err), err + } + switch { + case !w.session.verified && status.PinRetryCount == 0 && status.PukRetryCount == 0: + return "Bricked, waiting for full wipe", nil + case !w.session.verified && status.PinRetryCount == 0: + return fmt.Sprintf("Blocked, waiting for PUK (%d attempts left) and new PIN", status.PukRetryCount), nil + case !w.session.verified: + return fmt.Sprintf("Locked, waiting for PIN (%d attempts left)", status.PinRetryCount), nil + case !status.Initialized: + return "Empty, waiting for initialization", nil + default: + return "Online", nil + } +} + +// Open initializes access to a wallet instance. It is not meant to unlock or +// decrypt account keys, rather simply to establish a connection to hardware +// wallets and/or to access derivation seeds. +// +// The passphrase parameter may or may not be used by the implementation of a +// particular wallet instance. The reason there is no passwordless open method +// is to strive towards a uniform wallet handling, oblivious to the different +// backend providers. +// +// Please note, if you open a wallet, you must close it to release any allocated +// resources (especially important when working with hardware wallets). +func (w *Wallet) Open(passphrase string) error { + w.lock.Lock() + defer w.lock.Unlock() + + // If the session is already open, bail out + if w.session.verified { + return ErrAlreadyOpen + } + // If the smart card is not yet paired, attempt to do so either from a previous + // pairing key or form the supplied PUK code. + if !w.session.paired() { + // If a previous pairing exists, only ever try to use that + if pairing := w.Hub.pairing(w); pairing != nil { + if err := w.session.authenticate(*pairing); err != nil { + return fmt.Errorf("failed to authenticate card %x: %s", w.PublicKey[:4], err) + } + // Pairing still ok, fall through to PIN checks + } else { + // If no passphrase was supplied, request the PUK from the user + if passphrase == "" { + return ErrPairingPasswordNeeded + } + // Attempt to pair the smart card with the user supplied PUK + if err := w.pair([]byte(passphrase)); err != nil { + return err + } + // Pairing succeeded, fall through to PIN checks. This will of course fail, + // but we can't return ErrPINNeeded directly here because we don't know whether + // a PIN check or a PIN reset is needed. + passphrase = "" + } + } + // The smart card was successfully paired, retrieve its status to check whether + // PIN verification or unblocking is needed. + status, err := w.session.walletStatus() + if err != nil { + return err + } + // Request the appropriate next authentication data, or use the one supplied + switch { + case passphrase == "" && status.PinRetryCount > 0: + return ErrPINNeeded + case passphrase == "": + return ErrPINUnblockNeeded + case status.PinRetryCount > 0: + if !pinRegexp.MatchString(passphrase) { + w.log.Error("PIN needs to be at least 6 digits") + return ErrPINNeeded + } + if err := w.session.verifyPin([]byte(passphrase)); err != nil { + return err + } + default: + if !pukRegexp.MatchString(passphrase) { + w.log.Error("PUK needs to be at least 12 digits") + return ErrPINUnblockNeeded + } + if err := w.session.unblockPin([]byte(passphrase)); err != nil { + return err + } + } + // Smart card paired and unlocked, initialize and register + w.deriveReq = make(chan chan struct{}) + w.deriveQuit = make(chan chan error) + + go w.selfDerive() + + // Notify anyone listening for wallet events that a new device is accessible + go w.Hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened}) + + return nil +} + +// Close stops and closes the wallet, freeing any resources. +func (w *Wallet) Close() error { + // Ensure the wallet was opened + w.lock.Lock() + dQuit := w.deriveQuit + w.lock.Unlock() + + // Terminate the self-derivations + var derr error + if dQuit != nil { + errc := make(chan error) + dQuit <- errc + derr = <-errc // Save for later, we *must* close the USB + } + // Terminate the device connection + w.lock.Lock() + defer w.lock.Unlock() + + w.deriveQuit = nil + w.deriveReq = nil + + if err := w.release(); err != nil { + return err + } + return derr +} + +// selfDerive is an account derivation loop that upon request attempts to find +// new non-zero accounts. +func (w *Wallet) selfDerive() { + w.log.Debug("Smart card wallet self-derivation started") + defer w.log.Debug("Smart card wallet self-derivation stopped") + + // Execute self-derivations until termination or error + var ( + reqc chan struct{} + errc chan error + err error + ) + for errc == nil && err == nil { + // Wait until either derivation or termination is requested + select { + case errc = <-w.deriveQuit: + // Termination requested + continue + case reqc = <-w.deriveReq: + // Account discovery requested + } + // Derivation needs a chain and device access, skip if either unavailable + w.lock.Lock() + if w.session == nil || w.deriveChain == nil { + w.lock.Unlock() + reqc <- struct{}{} + continue + } + pairing := w.Hub.pairing(w) + + // Device lock obtained, derive the next batch of accounts + var ( + paths []accounts.DerivationPath + nextAcc accounts.Account + + nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...) + nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) + + context = context.Background() + ) + for i := 0; i < len(nextAddrs); i++ { + for empty := false; !empty; { + // Retrieve the next derived Ethereum account + if nextAddrs[i] == (common.Address{}) { + if nextAcc, err = w.session.derive(nextPaths[i]); err != nil { + w.log.Warn("Smartcard wallet account derivation failed", "err", err) + break + } + nextAddrs[i] = nextAcc.Address + } + // Check the account's status against the current chain state + var ( + balance *big.Int + nonce uint64 + ) + balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil) + if err != nil { + w.log.Warn("Smartcard wallet balance retrieval failed", "err", err) + break + } + nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil) + if err != nil { + w.log.Warn("Smartcard wallet nonce retrieval failed", "err", err) + break + } + // If the next account is empty, stop self-derivation, but add for the last base path + if balance.Sign() == 0 && nonce == 0 { + empty = true + if i < len(nextAddrs)-1 { + break + } + } + // We've just self-derived a new account, start tracking it locally + path := make(accounts.DerivationPath, len(nextPaths[i])) + copy(path[:], nextPaths[i][:]) + paths = append(paths, path) + + // Display a log message to the user for new (or previously empty accounts) + if _, known := pairing.Accounts[nextAddrs[i]]; !known || !empty || nextAddrs[i] != w.deriveNextAddrs[i] { + w.log.Info("Smartcard wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce) + } + pairing.Accounts[nextAddrs[i]] = path + + // Fetch the next potential account + if !empty { + nextAddrs[i] = common.Address{} + nextPaths[i][len(nextPaths[i])-1]++ + } + } + } + // If there are new accounts, write them out + if len(paths) > 0 { + err = w.Hub.setPairing(w, pairing) + } + // Shift the self-derivation forward + w.deriveNextAddrs = nextAddrs + w.deriveNextPaths = nextPaths + + // Self derivation complete, release device lock + w.lock.Unlock() + + // Notify the user of termination and loop after a bit of time (to avoid trashing) + reqc <- struct{}{} + if err == nil { + select { + case errc = <-w.deriveQuit: + // Termination requested, abort + case <-time.After(selfDeriveThrottling): + // Waited enough, willing to self-derive again + } + } + } + // In case of error, wait for termination + if err != nil { + w.log.Debug("Smartcard wallet self-derivation failed", "err", err) + errc = <-w.deriveQuit + } + errc <- err +} + +// Accounts retrieves the list of signing accounts the wallet is currently aware +// of. For hierarchical deterministic wallets, the list will not be exhaustive, +// rather only contain the accounts explicitly pinned during account derivation. +func (w *Wallet) Accounts() []accounts.Account { + // Attempt self-derivation if it's running + reqc := make(chan struct{}, 1) + select { + case w.deriveReq <- reqc: + // Self-derivation request accepted, wait for it + <-reqc + default: + // Self-derivation offline, throttled or busy, skip + } + + w.lock.Lock() + defer w.lock.Unlock() + + if pairing := w.Hub.pairing(w); pairing != nil { + ret := make([]accounts.Account, 0, len(pairing.Accounts)) + for address, path := range pairing.Accounts { + ret = append(ret, w.makeAccount(address, path)) + } + sort.Sort(accounts.AccountsByURL(ret)) + return ret + } + return nil +} + +func (w *Wallet) makeAccount(address common.Address, path accounts.DerivationPath) accounts.Account { + return accounts.Account{ + Address: address, + URL: accounts.URL{ + Scheme: w.Hub.scheme, + Path: fmt.Sprintf("%x/%s", w.PublicKey[1:3], path.String()), + }, + } +} + +// Contains returns whether an account is part of this particular wallet or not. +func (w *Wallet) Contains(account accounts.Account) bool { + if pairing := w.Hub.pairing(w); pairing != nil { + _, ok := pairing.Accounts[account.Address] + return ok + } + return false +} + +// Initialize installs a keypair generated from the provided key into the wallet. +func (w *Wallet) Initialize(seed []byte) error { + go w.selfDerive() + // DO NOT lock at this stage, as the initialize + // function relies on Status() + return w.session.initialize(seed) +} + +// Derive attempts to explicitly derive a hierarchical deterministic account at +// the specified derivation path. If requested, the derived account will be added +// to the wallet's tracked account list. +func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + w.lock.Lock() + defer w.lock.Unlock() + + account, err := w.session.derive(path) + if err != nil { + return accounts.Account{}, err + } + + if pin { + pairing := w.Hub.pairing(w) + pairing.Accounts[account.Address] = path + if err := w.Hub.setPairing(w, pairing); err != nil { + return accounts.Account{}, err + } + } + + return account, nil +} + +// SelfDerive sets a base account derivation path from which the wallet attempts +// to discover non zero accounts and automatically add them to list of tracked +// accounts. +// +// Note, self derivation will increment the last component of the specified path +// opposed to descending into a child path to allow discovering accounts starting +// from non zero components. +// +// Some hardware wallets switched derivation paths through their evolution, so +// this method supports providing multiple bases to discover old user accounts +// too. Only the last base will be used to derive the next empty account. +// +// You can disable automatic account discovery by calling SelfDerive with a nil +// chain state reader. +func (w *Wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { + w.lock.Lock() + defer w.lock.Unlock() + + w.deriveNextPaths = make([]accounts.DerivationPath, len(bases)) + for i, base := range bases { + w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base)) + copy(w.deriveNextPaths[i][:], base[:]) + } + w.deriveNextAddrs = make([]common.Address, len(bases)) + w.deriveChain = chain +} + +// SignData requests the wallet to sign the hash of the given data. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +// +// If the wallet requires additional authentication to sign the request (e.g. +// a password to decrypt the account, or a PIN code o verify the transaction), +// an AuthNeededError instance will be returned, containing infos for the user +// about which fields or actions are needed. The user may retry by providing +// the needed details via SignDataWithPassphrase, or by other means (e.g. unlock +// the account in a keystore). +func (w *Wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + return w.signHash(account, crypto.Keccak256(data)) +} + +func (w *Wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { + w.lock.Lock() + defer w.lock.Unlock() + + path, err := w.findAccountPath(account) + if err != nil { + return nil, err + } + + return w.session.sign(path, hash) +} + +// SignTx requests the wallet to sign the given transaction. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +// +// If the wallet requires additional authentication to sign the request (e.g. +// a password to decrypt the account, or a PIN code o verify the transaction), +// an AuthNeededError instance will be returned, containing infos for the user +// about which fields or actions are needed. The user may retry by providing +// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock +// the account in a keystore). +func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + signer := types.LatestSignerForChainID(chainID) + hash := signer.Hash(tx) + sig, err := w.signHash(account, hash[:]) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, sig) +} + +// SignDataWithPassphrase requests the wallet to sign the given hash with the +// given passphrase as extra authentication information. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +func (w *Wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return w.signHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) +} + +func (w *Wallet) signHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { + if !w.session.verified { + if err := w.Open(passphrase); err != nil { + return nil, err + } + } + + return w.signHash(account, hash) +} + +// SignText requests the wallet to sign the hash of a given piece of data, prefixed +// by the Ethereum prefix scheme +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +// +// If the wallet requires additional authentication to sign the request (e.g. +// a password to decrypt the account, or a PIN code o verify the transaction), +// an AuthNeededError instance will be returned, containing infos for the user +// about which fields or actions are needed. The user may retry by providing +// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock +// the account in a keystore). +func (w *Wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.signHash(account, accounts.TextHash(text)) +} + +// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the +// given hash with the given account using passphrase as extra authentication +func (w *Wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + return w.signHashWithPassphrase(account, passphrase, crypto.Keccak256(accounts.TextHash(text))) +} + +// SignTxWithPassphrase requests the wallet to sign the given transaction, with the +// given passphrase as extra authentication information. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + if !w.session.verified { + if err := w.Open(passphrase); err != nil { + return nil, err + } + } + return w.SignTx(account, tx, chainID) +} + +// findAccountPath returns the derivation path for the provided account. +// It first checks for the address in the list of pinned accounts, and if it is +// not found, attempts to parse the derivation path from the account's URL. +func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationPath, error) { + pairing := w.Hub.pairing(w) + if path, ok := pairing.Accounts[account.Address]; ok { + return path, nil + } + + // Look for the path in the URL + if account.URL.Scheme != w.Hub.scheme { + return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme) + } + + url, path, found := strings.Cut(account.URL.Path, "/") + if !found { + return nil, fmt.Errorf("invalid URL format: %s", account.URL) + } + + 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(path) +} + +// Session represents a secured communication session with the wallet. +type Session struct { + Wallet *Wallet // A handle to the wallet that opened the session + Channel *SecureChannelSession // A secure channel for encrypted messages + verified bool // Whether the pin has been verified in this session. +} + +// pair establishes a new pairing over this channel, using the provided secret. +func (s *Session) pair(secret []byte) (smartcardPairing, error) { + err := s.Channel.Pair(secret) + if err != nil { + return smartcardPairing{}, err + } + + return smartcardPairing{ + PublicKey: s.Wallet.PublicKey, + PairingIndex: s.Channel.PairingIndex, + PairingKey: s.Channel.PairingKey, + Accounts: make(map[common.Address]accounts.DerivationPath), + }, nil +} + +// unpair deletes an existing pairing. +func (s *Session) unpair() error { + if !s.verified { + return errors.New("unpair requires that the PIN be verified") + } + return s.Channel.Unpair() +} + +// verifyPin unlocks a wallet with the provided pin. +func (s *Session) verifyPin(pin []byte) error { + if _, err := s.Channel.transmitEncrypted(claSCWallet, insVerifyPin, 0, 0, pin); err != nil { + return err + } + s.verified = true + return nil +} + +// unblockPin unblocks a wallet with the provided puk and resets the pin to the +// new one specified. +func (s *Session) unblockPin(pukpin []byte) error { + if _, err := s.Channel.transmitEncrypted(claSCWallet, insUnblockPin, 0, 0, pukpin); err != nil { + return err + } + s.verified = true + return nil +} + +// release releases resources associated with the channel. +func (s *Session) release() error { + return s.Wallet.card.Disconnect(pcsc.LeaveCard) +} + +// paired returns true if a valid pairing exists. +func (s *Session) paired() bool { + return s.Channel.PairingKey != nil +} + +// authenticate uses an existing pairing to establish a secure channel. +func (s *Session) authenticate(pairing smartcardPairing) error { + if !bytes.Equal(s.Wallet.PublicKey, pairing.PublicKey) { + return fmt.Errorf("cannot pair using another wallet's pairing; %x != %x", s.Wallet.PublicKey, pairing.PublicKey) + } + s.Channel.PairingKey = pairing.PairingKey + s.Channel.PairingIndex = pairing.PairingIndex + return s.Channel.Open() +} + +// walletStatus describes a smartcard wallet's status information. +type walletStatus struct { + PinRetryCount int // Number of remaining PIN retries + PukRetryCount int // Number of remaining PUK retries + Initialized bool // Whether the card has been initialized with a private key +} + +// walletStatus fetches the wallet's status from the card. +func (s *Session) walletStatus() (*walletStatus, error) { + response, err := s.Channel.transmitEncrypted(claSCWallet, insStatus, statusP1WalletStatus, 0, nil) + if err != nil { + return nil, err + } + + status := new(walletStatus) + if _, err := asn1.UnmarshalWithParams(response.Data, status, "tag:3"); err != nil { + return nil, err + } + return status, nil +} + +// derivationPath fetches the wallet's current derivation path from the card. +// +//lint:ignore U1000 needs to be added to the console interface +func (s *Session) derivationPath() (accounts.DerivationPath, error) { + response, err := s.Channel.transmitEncrypted(claSCWallet, insStatus, statusP1Path, 0, nil) + if err != nil { + return nil, err + } + buf := bytes.NewReader(response.Data) + path := make(accounts.DerivationPath, len(response.Data)/4) + return path, binary.Read(buf, binary.BigEndian, &path) +} + +// initializeData contains data needed to initialize the smartcard wallet. +type initializeData struct { + PublicKey []byte `asn1:"tag:0"` + PrivateKey []byte `asn1:"tag:1"` + ChainCode []byte `asn1:"tag:2"` +} + +// initialize initializes the card with new key data. +func (s *Session) initialize(seed []byte) error { + // Check that the wallet isn't currently initialized, + // otherwise the key would be overwritten. + status, err := s.Wallet.Status() + if err != nil { + return err + } + if status == "Online" { + return errors.New("card is already initialized, cowardly refusing to proceed") + } + + s.Wallet.lock.Lock() + defer s.Wallet.lock.Unlock() + + // HMAC the seed to produce the private key and chain code + mac := hmac.New(sha512.New, []byte("Bitcoin seed")) + mac.Write(seed) + seed = mac.Sum(nil) + + key, err := crypto.ToECDSA(seed[:32]) + if err != nil { + return err + } + + id := initializeData{} + id.PublicKey = crypto.FromECDSAPub(&key.PublicKey) + id.PrivateKey = seed[:32] + id.ChainCode = seed[32:] + data, err := asn1.Marshal(id) + if err != nil { + return err + } + + // Nasty hack to force the top-level struct tag to be context-specific + data[0] = 0xA1 + + _, err = s.Channel.transmitEncrypted(claSCWallet, insLoadKey, 0x02, 0, data) + return err +} + +// derive derives a new HD key path on the card. +func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error) { + startingPoint, path, err := derivationpath.Decode(path.String()) + if err != nil { + return accounts.Account{}, err + } + + var p1 uint8 + switch startingPoint { + case derivationpath.StartingPointMaster: + p1 = P1DeriveKeyFromMaster + case derivationpath.StartingPointParent: + p1 = P1DeriveKeyFromParent + case derivationpath.StartingPointCurrent: + p1 = P1DeriveKeyFromCurrent + default: + return accounts.Account{}, fmt.Errorf("invalid startingPoint %d", startingPoint) + } + + data := new(bytes.Buffer) + for _, segment := range path { + if err := binary.Write(data, binary.BigEndian, segment); err != nil { + return accounts.Account{}, err + } + } + + _, err = s.Channel.transmitEncrypted(claSCWallet, insDeriveKey, p1, 0, data.Bytes()) + if err != nil { + return accounts.Account{}, err + } + + response, err := s.Channel.transmitEncrypted(claSCWallet, insSign, 0, 0, DerivationSignatureHash[:]) + if err != nil { + return accounts.Account{}, err + } + + sigdata := new(signatureData) + if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil { + return accounts.Account{}, err + } + rbytes, sbytes := sigdata.Signature.R.Bytes(), sigdata.Signature.S.Bytes() + sig := make([]byte, 65) + copy(sig[32-len(rbytes):32], rbytes) + copy(sig[64-len(sbytes):64], sbytes) + + if err := confirmPublicKey(sig, sigdata.PublicKey); err != nil { + return accounts.Account{}, err + } + pub, err := crypto.UnmarshalPubkey(sigdata.PublicKey) + if err != nil { + return accounts.Account{}, err + } + return s.Wallet.makeAccount(crypto.PubkeyToAddress(*pub), path), nil +} + +// keyExport contains information on an exported keypair. +// +//lint:ignore U1000 needs to be added to the console interface +type keyExport struct { + PublicKey []byte `asn1:"tag:0"` + PrivateKey []byte `asn1:"tag:1,optional"` +} + +// publicKey returns the public key for the current derivation path. +// +//lint:ignore U1000 needs to be added to the console interface +func (s *Session) publicKey() ([]byte, error) { + response, err := s.Channel.transmitEncrypted(claSCWallet, insExportKey, exportP1Any, exportP2Pubkey, nil) + if err != nil { + return nil, err + } + keys := new(keyExport) + if _, err := asn1.UnmarshalWithParams(response.Data, keys, "tag:1"); err != nil { + return nil, err + } + return keys.PublicKey, nil +} + +// signatureData contains information on a signature - the signature itself and +// the corresponding public key. +type signatureData struct { + PublicKey []byte `asn1:"tag:0"` + Signature struct { + R *big.Int + S *big.Int + } +} + +// sign asks the card to sign a message, and returns a valid signature after +// recovering the v value. +func (s *Session) sign(path accounts.DerivationPath, hash []byte) ([]byte, error) { + startTime := time.Now() + _, err := s.derive(path) + if err != nil { + return nil, err + } + deriveTime := time.Now() + + response, err := s.Channel.transmitEncrypted(claSCWallet, insSign, signP1PrecomputedHash, signP2OnlyBlock, hash) + if err != nil { + return nil, err + } + sigdata := new(signatureData) + if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil { + return nil, err + } + // Serialize the signature + rbytes, sbytes := sigdata.Signature.R.Bytes(), sigdata.Signature.S.Bytes() + sig := make([]byte, 65) + copy(sig[32-len(rbytes):32], rbytes) + copy(sig[64-len(sbytes):64], sbytes) + + // Recover the V value. + sig, err = makeRecoverableSignature(hash, sig, sigdata.PublicKey) + if err != nil { + return nil, err + } + log.Debug("Signed using smartcard", "deriveTime", deriveTime.Sub(startTime), "signingTime", time.Since(deriveTime)) + + return sig, nil +} + +// confirmPublicKey confirms that the given signature belongs to the specified key. +func confirmPublicKey(sig, pubkey []byte) error { + _, err := makeRecoverableSignature(DerivationSignatureHash[:], sig, pubkey) + return err +} + +// makeRecoverableSignature uses a signature and an expected public key to +// recover the v value and produce a recoverable signature. +func makeRecoverableSignature(hash, sig, expectedPubkey []byte) ([]byte, error) { + var libraryError error + for v := 0; v < 2; v++ { + sig[64] = byte(v) + if pubkey, err := crypto.Ecrecover(hash, sig); err == nil { + if bytes.Equal(pubkey, expectedPubkey) { + return sig, nil + } + } else { + libraryError = err + } + } + if libraryError != nil { + return nil, libraryError + } + return nil, ErrPubkeyMismatch +} diff --git a/accounts/sort.go b/accounts/sort.go new file mode 100644 index 0000000..f467621 --- /dev/null +++ b/accounts/sort.go @@ -0,0 +1,31 @@ +// 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 accounts + +// AccountsByURL implements sort.Interface for []Account based on the URL field. +type AccountsByURL []Account + +func (a AccountsByURL) Len() int { return len(a) } +func (a AccountsByURL) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a AccountsByURL) Less(i, j int) bool { return a[i].URL.Cmp(a[j].URL) < 0 } + +// WalletsByURL implements sort.Interface for []Wallet based on the URL field. +type WalletsByURL []Wallet + +func (w WalletsByURL) Len() int { return len(w) } +func (w WalletsByURL) Swap(i, j int) { w[i], w[j] = w[j], w[i] } +func (w WalletsByURL) Less(i, j int) bool { return w[i].URL().Cmp(w[j].URL()) < 0 } diff --git a/accounts/url.go b/accounts/url.go new file mode 100644 index 0000000..39b00e5 --- /dev/null +++ b/accounts/url.go @@ -0,0 +1,103 @@ +// 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 accounts + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// URL represents the canonical identification URL of a wallet or account. +// +// It is a simplified version of url.URL, with the important limitations (which +// are considered features here) that it contains value-copyable components only, +// as well as that it doesn't do any URL encoding/decoding of special characters. +// +// The former is important to allow an account to be copied without leaving live +// references to the original version, whereas the latter is important to ensure +// one single canonical form opposed to many allowed ones by the RFC 3986 spec. +// +// As such, these URLs should not be used outside of the scope of an Ethereum +// wallet or account. +type URL struct { + Scheme string // Protocol scheme to identify a capable account backend + Path string // Path for the backend to identify a unique entity +} + +// parseURL converts a user supplied URL into the accounts specific structure. +func parseURL(url string) (URL, error) { + parts := strings.Split(url, "://") + if len(parts) != 2 || parts[0] == "" { + return URL{}, errors.New("protocol scheme missing") + } + return URL{ + Scheme: parts[0], + Path: parts[1], + }, nil +} + +// String implements the stringer interface. +func (u URL) String() string { + if u.Scheme != "" { + return fmt.Sprintf("%s://%s", u.Scheme, u.Path) + } + return u.Path +} + +// TerminalString implements the log.TerminalStringer interface. +func (u URL) TerminalString() string { + url := u.String() + if len(url) > 32 { + return url[:31] + ".." + } + return url +} + +// MarshalJSON implements the json.Marshaller interface. +func (u URL) MarshalJSON() ([]byte, error) { + return json.Marshal(u.String()) +} + +// UnmarshalJSON parses url. +func (u *URL) UnmarshalJSON(input []byte) error { + var textURL string + err := json.Unmarshal(input, &textURL) + if err != nil { + return err + } + url, err := parseURL(textURL) + if err != nil { + return err + } + u.Scheme = url.Scheme + u.Path = url.Path + return nil +} + +// Cmp compares x and y and returns: +// +// -1 if x < y +// 0 if x == y +// +1 if x > y +func (u URL) Cmp(url URL) int { + if u.Scheme == url.Scheme { + return strings.Compare(u.Path, url.Path) + } + return strings.Compare(u.Scheme, url.Scheme) +} diff --git a/accounts/url_test.go b/accounts/url_test.go new file mode 100644 index 0000000..f481a10 --- /dev/null +++ b/accounts/url_test.go @@ -0,0 +1,102 @@ +// 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 accounts + +import ( + "testing" +) + +func TestURLParsing(t *testing.T) { + t.Parallel() + url, err := parseURL("https://ethereum.org") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if url.Scheme != "https" { + t.Errorf("expected: %v, got: %v", "https", url.Scheme) + } + if url.Path != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path) + } + + for _, u := range []string{"ethereum.org", ""} { + if _, err = parseURL(u); err == nil { + t.Errorf("input %v, expected err, got: nil", u) + } + } +} + +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()) + } + + url = URL{Scheme: "", Path: "ethereum.org"} + if url.String() != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "ethereum.org", url.String()) + } +} + +func TestURLMarshalJSON(t *testing.T) { + t.Parallel() + url := URL{Scheme: "https", Path: "ethereum.org"} + json, err := url.MarshalJSON() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if string(json) != "\"https://ethereum.org\"" { + t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json)) + } +} + +func TestURLUnmarshalJSON(t *testing.T) { + t.Parallel() + url := &URL{} + err := url.UnmarshalJSON([]byte("\"https://ethereum.org\"")) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if url.Scheme != "https" { + t.Errorf("expected: %v, got: %v", "https", url.Scheme) + } + if url.Path != "ethereum.org" { + t.Errorf("expected: %v, got: %v", "https", url.Path) + } +} + +func TestURLComparison(t *testing.T) { + t.Parallel() + tests := []struct { + urlA URL + urlB URL + expect int + }{ + {URL{"https", "ethereum.org"}, URL{"https", "ethereum.org"}, 0}, + {URL{"http", "ethereum.org"}, URL{"https", "ethereum.org"}, -1}, + {URL{"https", "ethereum.org/a"}, URL{"https", "ethereum.org"}, 1}, + {URL{"https", "abc.org"}, URL{"https", "ethereum.org"}, -1}, + } + + for i, tt := range tests { + result := tt.urlA.Cmp(tt.urlB) + if result != tt.expect { + t.Errorf("test %d: cmp mismatch: expected: %d, got: %d", i, tt.expect, result) + } + } +} diff --git a/accounts/usbwallet/hub.go b/accounts/usbwallet/hub.go new file mode 100644 index 0000000..e22dffe --- /dev/null +++ b/accounts/usbwallet/hub.go @@ -0,0 +1,289 @@ +// 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 usbwallet + +import ( + "errors" + "runtime" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/karalabe/hid" +) + +// LedgerScheme is the protocol scheme prefixing account and wallet URLs. +const LedgerScheme = "ledger" + +// TrezorScheme is the protocol scheme prefixing account and wallet URLs. +const TrezorScheme = "trezor" + +// refreshCycle is the maximum time between wallet refreshes (if USB hotplug +// notifications don't work). +const refreshCycle = time.Second + +// refreshThrottling is the minimum time between wallet refreshes to avoid USB +// trashing. +const refreshThrottling = 500 * time.Millisecond + +// Hub is a accounts.Backend that can find and handle generic USB hardware wallets. +type Hub struct { + scheme string // Protocol scheme prefixing account and wallet URLs. + vendorID uint16 // USB vendor identifier used for device discovery + productIDs []uint16 // USB product identifiers used for device discovery + usageID uint16 // USB usage page identifier used for macOS device discovery + endpointID int // USB endpoint identifier used for non-macOS device discovery + makeDriver func(log.Logger) driver // Factory method to construct a vendor specific driver + + refreshed time.Time // Time instance when the list of wallets was last refreshed + wallets []accounts.Wallet // List of USB wallet devices currently tracking + updateFeed event.Feed // Event feed to notify wallet additions/removals + updateScope event.SubscriptionScope // Subscription scope tracking current live listeners + updating bool // Whether the event notification loop is running + + quit chan chan error + + stateLock sync.RWMutex // Protects the internals of the hub from racey access + + // TODO(karalabe): remove if hotplug lands on Windows + commsPend int // Number of operations blocking enumeration + commsLock sync.Mutex // Lock protecting the pending counter and enumeration + enumFails atomic.Uint32 // Number of times enumeration has failed +} + +// NewLedgerHub creates a new hardware wallet manager for Ledger devices. +func NewLedgerHub() (*Hub, error) { + return newHub(LedgerScheme, 0x2c97, []uint16{ + + // Device definitions taken from + // https://github.com/LedgerHQ/ledger-live/blob/38012bc8899e0f07149ea9cfe7e64b2c146bc92b/libs/ledgerjs/packages/devices/src/index.ts + + // Original product IDs + 0x0000, /* Ledger Blue */ + 0x0001, /* Ledger Nano S */ + 0x0004, /* Ledger Nano X */ + 0x0005, /* Ledger Nano S Plus */ + 0x0006, /* Ledger Nano FTS */ + + 0x0015, /* HID + U2F + WebUSB Ledger Blue */ + 0x1015, /* HID + U2F + WebUSB Ledger Nano S */ + 0x4015, /* HID + U2F + WebUSB Ledger Nano X */ + 0x5015, /* HID + U2F + WebUSB Ledger Nano S Plus */ + 0x6015, /* HID + U2F + WebUSB Ledger Nano FTS */ + + 0x0011, /* HID + WebUSB Ledger Blue */ + 0x1011, /* HID + WebUSB Ledger Nano S */ + 0x4011, /* HID + WebUSB Ledger Nano X */ + 0x5011, /* HID + WebUSB Ledger Nano S Plus */ + 0x6011, /* HID + WebUSB Ledger Nano FTS */ + }, 0xffa0, 0, newLedgerDriver) +} + +// NewTrezorHubWithHID creates a new hardware wallet manager for Trezor devices. +func NewTrezorHubWithHID() (*Hub, error) { + return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor HID */}, 0xff00, 0, newTrezorDriver) +} + +// NewTrezorHubWithWebUSB creates a new hardware wallet manager for Trezor devices with +// firmware version > 1.8.0 +func NewTrezorHubWithWebUSB() (*Hub, error) { + return newHub(TrezorScheme, 0x1209, []uint16{0x53c1 /* Trezor WebUSB */}, 0xffff /* No usage id on webusb, don't match unset (0) */, 0, newTrezorDriver) +} + +// newHub creates a new hardware wallet manager for generic USB devices. +func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) { + if !hid.Supported() { + return nil, errors.New("unsupported platform") + } + hub := &Hub{ + scheme: scheme, + vendorID: vendorID, + productIDs: productIDs, + usageID: usageID, + endpointID: endpointID, + makeDriver: makeDriver, + quit: make(chan chan error), + } + hub.refreshWallets() + return hub, nil +} + +// Wallets implements accounts.Backend, returning all the currently tracked USB +// devices that appear to be hardware wallets. +func (hub *Hub) Wallets() []accounts.Wallet { + // Make sure the list of wallets is up to date + hub.refreshWallets() + + hub.stateLock.RLock() + defer hub.stateLock.RUnlock() + + cpy := make([]accounts.Wallet, len(hub.wallets)) + copy(cpy, hub.wallets) + return cpy +} + +// refreshWallets scans the USB devices attached to the machine and updates the +// list of wallets based on the found devices. +func (hub *Hub) refreshWallets() { + // Don't scan the USB like crazy it the user fetches wallets in a loop + hub.stateLock.RLock() + elapsed := time.Since(hub.refreshed) + hub.stateLock.RUnlock() + + if elapsed < refreshThrottling { + return + } + // If USB enumeration is continually failing, don't keep trying indefinitely + if hub.enumFails.Load() > 2 { + return + } + // Retrieve the current list of USB wallet devices + var devices []hid.DeviceInfo + + if runtime.GOOS == "linux" { + // hidapi on Linux opens the device during enumeration to retrieve some infos, + // breaking the Ledger protocol if that is waiting for user confirmation. This + // is a bug acknowledged at Ledger, but it won't be fixed on old devices so we + // need to prevent concurrent comms ourselves. The more elegant solution would + // be to ditch enumeration in favor of hotplug events, but that don't work yet + // on Windows so if we need to hack it anyway, this is more elegant for now. + hub.commsLock.Lock() + if hub.commsPend > 0 { // A confirmation is pending, don't refresh + hub.commsLock.Unlock() + return + } + } + infos, err := hid.Enumerate(hub.vendorID, 0) + if err != nil { + failcount := hub.enumFails.Add(1) + if runtime.GOOS == "linux" { + // See rationale before the enumeration why this is needed and only on Linux. + hub.commsLock.Unlock() + } + log.Error("Failed to enumerate USB devices", "hub", hub.scheme, + "vendor", hub.vendorID, "failcount", failcount, "err", err) + return + } + hub.enumFails.Store(0) + + for _, info := range infos { + for _, id := range hub.productIDs { + // Windows and Macos use UsageID matching, Linux uses Interface matching + if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) { + devices = append(devices, info) + break + } + } + } + if runtime.GOOS == "linux" { + // See rationale before the enumeration why this is needed and only on Linux. + hub.commsLock.Unlock() + } + // Transform the current list of wallets into the new one + hub.stateLock.Lock() + + var ( + wallets = make([]accounts.Wallet, 0, len(devices)) + events []accounts.WalletEvent + ) + + for _, device := range devices { + url := accounts.URL{Scheme: hub.scheme, Path: device.Path} + + // Drop wallets in front of the next device or those that failed for some reason + for len(hub.wallets) > 0 { + // Abort if we're past the current device and found an operational one + _, failure := hub.wallets[0].Status() + if hub.wallets[0].URL().Cmp(url) >= 0 || failure == nil { + break + } + // Drop the stale and failed devices + events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped}) + hub.wallets = hub.wallets[1:] + } + // If there are no more wallets or the device is before the next, wrap new wallet + if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 { + logger := log.New("url", url) + wallet := &wallet{hub: hub, driver: hub.makeDriver(logger), url: &url, info: device, log: logger} + + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) + wallets = append(wallets, wallet) + continue + } + // If the device is the same as the first wallet, keep it + if hub.wallets[0].URL().Cmp(url) == 0 { + wallets = append(wallets, hub.wallets[0]) + hub.wallets = hub.wallets[1:] + continue + } + } + // Drop any leftover wallets and set the new batch + for _, wallet := range hub.wallets { + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + } + hub.refreshed = time.Now() + hub.wallets = wallets + hub.stateLock.Unlock() + + // Fire all wallet events and return + for _, event := range events { + hub.updateFeed.Send(event) + } +} + +// Subscribe implements accounts.Backend, creating an async subscription to +// receive notifications on the addition or removal of USB wallets. +func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + // We need the mutex to reliably start/stop the update loop + hub.stateLock.Lock() + defer hub.stateLock.Unlock() + + // Subscribe the caller and track the subscriber count + sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink)) + + // Subscribers require an active notification loop, start it + if !hub.updating { + hub.updating = true + go hub.updater() + } + return sub +} + +// updater is responsible for maintaining an up-to-date list of wallets managed +// by the USB hub, and for firing wallet addition/removal events. +func (hub *Hub) updater() { + for { + // TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout + // <-hub.changes + time.Sleep(refreshCycle) + + // Run the wallet refresher + hub.refreshWallets() + + // If all our subscribers left, stop the updater + hub.stateLock.Lock() + if hub.updateScope.Count() == 0 { + hub.updating = false + hub.stateLock.Unlock() + return + } + hub.stateLock.Unlock() + } +} diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go new file mode 100644 index 0000000..81836b3 --- /dev/null +++ b/accounts/usbwallet/ledger.go @@ -0,0 +1,554 @@ +// 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 . + +// This file contains the implementation for interacting with the Ledger hardware +// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo: +// https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/ethapp.adoc + +package usbwallet + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "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/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// ledgerOpcode is an enumeration encoding the supported Ledger opcodes. +type ledgerOpcode byte + +// ledgerParam1 is an enumeration encoding the supported Ledger parameters for +// specific opcodes. The same parameter values may be reused between opcodes. +type ledgerParam1 byte + +// ledgerParam2 is an enumeration encoding the supported Ledger parameters for +// specific opcodes. The same parameter values may be reused between opcodes. +type ledgerParam2 byte + +const ( + ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path + ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters + ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration + ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification + + ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet + ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data + ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing + ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing + ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address + + ledgerEip155Size int = 3 // Size of the EIP-155 chain_id,r,s in unsigned transactions +) + +// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange +// if the device replies with a mismatching header. This usually means the device +// is in browser mode. +var errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header") + +// errLedgerInvalidVersionReply is the error message returned by a Ledger version retrieval +// when a response does arrive, but it does not contain the expected data. +var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply") + +// ledgerDriver implements the communication with a Ledger hardware wallet. +type ledgerDriver struct { + device io.ReadWriter // USB device connection to communicate through + version [3]byte // Current version of the Ledger firmware (zero if app is offline) + browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch) + failure error // Any failure that would make the device unusable + log log.Logger // Contextual logger to tag the ledger with its id +} + +// newLedgerDriver creates a new instance of a Ledger USB protocol driver. +func newLedgerDriver(logger log.Logger) driver { + return &ledgerDriver{ + log: logger, + } +} + +// Status implements usbwallet.driver, returning various states the Ledger can +// currently be in. +func (w *ledgerDriver) Status() (string, error) { + if w.failure != nil { + return fmt.Sprintf("Failed: %v", w.failure), w.failure + } + if w.browser { + return "Ethereum app in browser mode", w.failure + } + if w.offline() { + return "Ethereum app offline", w.failure + } + return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure +} + +// offline returns whether the wallet and the Ethereum app is offline or not. +// +// The method assumes that the state lock is held! +func (w *ledgerDriver) offline() bool { + return w.version == [3]byte{0, 0, 0} +} + +// Open implements usbwallet.driver, attempting to initialize the connection to the +// Ledger hardware wallet. The Ledger does not require a user passphrase, so that +// parameter is silently discarded. +func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error { + w.device, w.failure = device, nil + + _, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath) + if err != nil { + // Ethereum app is not running or in browser mode, nothing more to do, return + if err == errLedgerReplyInvalidHeader { + w.browser = true + } + return nil + } + // Try to resolve the Ethereum app's version, will fail prior to v1.0.2 + if w.version, err = w.ledgerVersion(); err != nil { + w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1 + } + return nil +} + +// Close implements usbwallet.driver, cleaning up and metadata maintained within +// the Ledger driver. +func (w *ledgerDriver) Close() error { + w.browser, w.version = false, [3]byte{} + return nil +} + +// Heartbeat implements usbwallet.driver, performing a sanity check against the +// Ledger to see if it's still online. +func (w *ledgerDriver) Heartbeat() error { + if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply { + w.failure = err + return err + } + return nil +} + +// Derive implements usbwallet.driver, sending a derivation request to the Ledger +// and returning the Ethereum address located on that derivation path. +func (w *ledgerDriver) Derive(path accounts.DerivationPath) (common.Address, error) { + return w.ledgerDerive(path) +} + +// SignTx implements usbwallet.driver, sending the transaction to the Ledger and +// waiting for the user to confirm or deny the transaction. +// +// Note, if the version of the Ethereum application running on the Ledger wallet is +// too old to sign EIP-155 transactions, but such is requested nonetheless, an error +// will be returned opposed to silently signing in Homestead mode. +func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { + // If the Ethereum app doesn't run, abort + if w.offline() { + return common.Address{}, nil, accounts.ErrWalletClosed + } + // Ensure the wallet is capable of signing the given transaction + if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 { + //lint:ignore ST1005 brand name displayed on the console + return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) + } + // All infos gathered and metadata checks out, request signing + return w.ledgerSign(path, tx, chainID) +} + +// SignTypedMessage implements usbwallet.driver, sending the message to the Ledger and +// waiting for the user to sign or deny the transaction. +// +// Note: this was introduced in the ledger 1.5.0 firmware +func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { + // If the Ethereum app doesn't run, abort + if w.offline() { + return nil, accounts.ErrWalletClosed + } + // Ensure the wallet is capable of signing the given transaction + if w.version[0] < 1 && w.version[1] < 5 { + //lint:ignore ST1005 brand name displayed on the console + return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2]) + } + // All infos gathered and metadata checks out, request signing + return w.ledgerSignTypedMessage(path, domainHash, messageHash) +} + +// ledgerVersion retrieves the current version of the Ethereum wallet app running +// on the Ledger wallet. +// +// The version retrieval protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+----+----+--- +// E0 | 06 | 00 | 00 | 00 | 04 +// +// With no input data, and the output data being: +// +// Description | Length +// ---------------------------------------------------+-------- +// Flags 01: arbitrary data signature enabled by user | 1 byte +// Application major version | 1 byte +// Application minor version | 1 byte +// Application patch version | 1 byte +func (w *ledgerDriver) ledgerVersion() ([3]byte, error) { + // Send the request and wait for the response + reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil) + if err != nil { + return [3]byte{}, err + } + if len(reply) != 4 { + return [3]byte{}, errLedgerInvalidVersionReply + } + // Cache the version for future reference + var version [3]byte + copy(version[:], reply[1:]) + return version, nil +} + +// ledgerDerive retrieves the currently active Ethereum address from a Ledger +// wallet at the specified derivation path. +// +// The address derivation protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+----+-----+--- +// E0 | 02 | 00 return address +// 01 display address and confirm before returning +// | 00: do not return the chain code +// | 01: return the chain code +// | var | 00 +// +// Where the input data is: +// +// Description | Length +// -------------------------------------------------+-------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// +// And the output data is: +// +// Description | Length +// ------------------------+------------------- +// Public Key length | 1 byte +// Uncompressed Public Key | arbitrary +// Ethereum address length | 1 byte +// Ethereum address | 40 bytes hex ascii +// Chain code if requested | 32 bytes +func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + // Send the request and wait for the response + reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path) + if err != nil { + return common.Address{}, err + } + // Discard the public key, we don't need that for now + if len(reply) < 1 || len(reply) < 1+int(reply[0]) { + return common.Address{}, errors.New("reply lacks public key entry") + } + reply = reply[1+int(reply[0]):] + + // Extract the Ethereum hex address string + if len(reply) < 1 || len(reply) < 1+int(reply[0]) { + return common.Address{}, errors.New("reply lacks address entry") + } + hexstr := reply[1 : 1+int(reply[0])] + + // Decode the hex string into an Ethereum address and return + var address common.Address + if _, err = hex.Decode(address[:], hexstr); err != nil { + return common.Address{}, err + } + return address, nil +} + +// ledgerSign sends the transaction to the Ledger wallet, and waits for the user +// to confirm or deny the transaction. +// +// The transaction signing protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+----+-----+--- +// E0 | 04 | 00: first transaction data block +// 80: subsequent transaction data block +// | 00 | variable | variable +// +// Where the input for the first transaction block (first 255 bytes) is: +// +// Description | Length +// -------------------------------------------------+---------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// RLP transaction chunk | arbitrary +// +// And the input for subsequent transaction blocks (first 255 bytes) are: +// +// Description | Length +// ----------------------+---------- +// RLP transaction chunk | arbitrary +// +// And the output data is: +// +// Description | Length +// ------------+--------- +// signature V | 1 byte +// signature R | 32 bytes +// signature S | 32 bytes +func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + // Create the transaction RLP based on whether legacy or EIP155 signing was requested + var ( + txrlp []byte + err error + ) + if chainID == nil { + if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil { + return common.Address{}, nil, err + } + } else { + if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil { + return common.Address{}, nil, err + } + } + payload := append(path, txrlp...) + + // Send the request and wait for the response + var ( + op = ledgerP1InitTransactionData + reply []byte + ) + + // Chunk size selection to mitigate an underlying RLP deserialization issue on the ledger app. + // https://github.com/LedgerHQ/app-ethereum/issues/409 + chunk := 255 + for ; len(payload)%chunk <= ledgerEip155Size; chunk-- { + } + + for len(payload) > 0 { + // Calculate the size of the next data chunk + if chunk > len(payload) { + chunk = len(payload) + } + // Send the chunk over, ensuring it's processed correctly + reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk]) + if err != nil { + return common.Address{}, nil, err + } + // Shift the payload and ensure subsequent chunks are marked as such + payload = payload[chunk:] + op = ledgerP1ContTransactionData + } + // Extract the Ethereum signature and do a sanity validation + if len(reply) != crypto.SignatureLength { + return common.Address{}, nil, errors.New("reply lacks signature") + } + signature := append(reply[1:], reply[0]) + + // Create the correct signer and signature transform based on the chain ID + var signer types.Signer + if chainID == nil { + signer = new(types.HomesteadSigner) + } else { + signer = types.NewEIP155Signer(chainID) + signature[64] -= byte(chainID.Uint64()*2 + 35) + } + signed, err := tx.WithSignature(signer, signature) + if err != nil { + return common.Address{}, nil, err + } + sender, err := types.Sender(signer, signed) + if err != nil { + return common.Address{}, nil, err + } + return sender, signed, nil +} + +// ledgerSignTypedMessage sends the transaction to the Ledger wallet, and waits for the user +// to confirm or deny the transaction. +// +// The signing protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+-----------------------------+-----+--- +// E0 | 0C | 00 | implementation version : 00 | variable | variable +// +// Where the input is: +// +// Description | Length +// -------------------------------------------------+---------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// domain hash | 32 bytes +// message hash | 32 bytes +// +// And the output data is: +// +// Description | Length +// ------------+--------- +// signature V | 1 byte +// signature R | 32 bytes +// signature S | 32 bytes +func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + // Create the 712 message + payload := append(path, domainHash...) + payload = append(payload, messageHash...) + + // Send the request and wait for the response + var ( + op = ledgerP1InitTypedMessageData + reply []byte + err error + ) + + // Send the message over, ensuring it's processed correctly + reply, err = w.ledgerExchange(ledgerOpSignTypedMessage, op, 0, payload) + + if err != nil { + return nil, err + } + + // Extract the Ethereum signature and do a sanity validation + if len(reply) != crypto.SignatureLength { + return nil, errors.New("reply lacks signature") + } + signature := append(reply[1:], reply[0]) + return signature, nil +} + +// ledgerExchange performs a data exchange with the Ledger wallet, sending it a +// message and retrieving the response. +// +// The common transport header is defined as follows: +// +// Description | Length +// --------------------------------------+---------- +// Communication channel ID (big endian) | 2 bytes +// Command tag | 1 byte +// Packet sequence index (big endian) | 2 bytes +// Payload | arbitrary +// +// The Communication channel ID allows commands multiplexing over the same +// physical link. It is not used for the time being, and should be set to 0101 +// to avoid compatibility issues with implementations ignoring a leading 00 byte. +// +// The Command tag describes the message content. Use TAG_APDU (0x05) for standard +// APDU payloads, or TAG_PING (0x02) for a simple link test. +// +// The Packet sequence index describes the current sequence for fragmented payloads. +// The first fragment index is 0x00. +// +// APDU Command payloads are encoded as follows: +// +// Description | Length +// ----------------------------------- +// APDU length (big endian) | 2 bytes +// APDU CLA | 1 byte +// APDU INS | 1 byte +// APDU P1 | 1 byte +// APDU P2 | 1 byte +// APDU length | 1 byte +// Optional APDU data | arbitrary +func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { + // Construct the message payload, possibly split into multiple chunks + apdu := make([]byte, 2, 7+len(data)) + + binary.BigEndian.PutUint16(apdu, uint16(5+len(data))) + apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...) + apdu = append(apdu, data...) + + // Stream all the chunks to the device + header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended + chunk := make([]byte, 64) + space := len(chunk) - len(header) + + for i := 0; len(apdu) > 0; i++ { + // Construct the new message to stream + chunk = append(chunk[:0], header...) + binary.BigEndian.PutUint16(chunk[3:], uint16(i)) + + if len(apdu) > space { + chunk = append(chunk, apdu[:space]...) + apdu = apdu[space:] + } else { + chunk = append(chunk, apdu...) + apdu = nil + } + // Send over to the device + w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk)) + if _, err := w.device.Write(chunk); err != nil { + return nil, err + } + } + // Stream the reply back from the wallet in 64 byte chunks + var reply []byte + chunk = chunk[:64] // Yeah, we surely have enough space + for { + // Read the next chunk from the Ledger wallet + if _, err := io.ReadFull(w.device, chunk); err != nil { + return nil, err + } + w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk)) + + // Make sure the transport header matches + if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 { + return nil, errLedgerReplyInvalidHeader + } + // If it's the first chunk, retrieve the total message length + var payload []byte + + if chunk[3] == 0x00 && chunk[4] == 0x00 { + reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7]))) + payload = chunk[7:] + } else { + payload = chunk[5:] + } + // Append to the reply and stop when filled up + if left := cap(reply) - len(reply); left > len(payload) { + reply = append(reply, payload...) + } else { + reply = append(reply, payload[:left]...) + break + } + } + return reply[:len(reply)-2], nil +} diff --git a/accounts/usbwallet/trezor.go b/accounts/usbwallet/trezor.go new file mode 100644 index 0000000..9644dc4 --- /dev/null +++ b/accounts/usbwallet/trezor.go @@ -0,0 +1,371 @@ +// 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 . + +// This file contains the implementation for interacting with the Trezor hardware +// wallets. The wire protocol spec can be found on the SatoshiLabs website: +// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html + +package usbwallet + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor" + "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/log" + "github.com/golang/protobuf/proto" +) + +// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In +// this case, the calling application should display a pinpad and send back the +// encoded passphrase. +var ErrTrezorPINNeeded = errors.New("trezor: pin needed") + +// ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase +var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed") + +// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange +// if the device replies with a mismatching header. This usually means the device +// is in browser mode. +var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header") + +// trezorDriver implements the communication with a Trezor hardware wallet. +type trezorDriver struct { + device io.ReadWriter // USB device connection to communicate through + version [3]uint32 // Current version of the Trezor firmware + label string // Current textual label of the Trezor device + pinwait bool // Flags whether the device is waiting for PIN entry + passphrasewait bool // Flags whether the device is waiting for passphrase entry + failure error // Any failure that would make the device unusable + log log.Logger // Contextual logger to tag the trezor with its id +} + +// newTrezorDriver creates a new instance of a Trezor USB protocol driver. +func newTrezorDriver(logger log.Logger) driver { + return &trezorDriver{ + log: logger, + } +} + +// Status implements accounts.Wallet, always whether the Trezor is opened, closed +// or whether the Ethereum app was not started on it. +func (w *trezorDriver) Status() (string, error) { + if w.failure != nil { + return fmt.Sprintf("Failed: %v", w.failure), w.failure + } + if w.device == nil { + return "Closed", w.failure + } + if w.pinwait { + return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label), w.failure + } + return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure +} + +// Open implements usbwallet.driver, attempting to initialize the connection to +// the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation: +// - The first phase is to initialize the connection and read the wallet's +// features. This phase is invoked if the provided passphrase is empty. The +// device will display the pinpad as a result and will return an appropriate +// error to notify the user that a second open phase is needed. +// - The second phase is to unlock access to the Trezor, which is done by the +// user actually providing a passphrase mapping a keyboard keypad to the pin +// number of the user (shuffled according to the pinpad displayed). +// - If needed the device will ask for passphrase which will require calling +// open again with the actual passphrase (3rd phase) +func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { + w.device, w.failure = device, nil + + // If phase 1 is requested, init the connection and wait for user callback + if passphrase == "" && !w.passphrasewait { + // If we're already waiting for a PIN entry, insta-return + if w.pinwait { + return ErrTrezorPINNeeded + } + // Initialize a connection to the device + features := new(trezor.Features) + if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil { + return err + } + w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()} + w.label = features.GetLabel() + + // Do a manual ping, forcing the device to ask for its PIN and Passphrase + askPin := true + askPassphrase := true + res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success)) + if err != nil { + return err + } + // Only return the PIN request if the device wasn't unlocked until now + switch res { + case 0: + w.pinwait = true + return ErrTrezorPINNeeded + case 1: + w.pinwait = false + w.passphrasewait = true + return ErrTrezorPassphraseNeeded + case 2: + return nil // responded with trezor.Success + } + } + // Phase 2 requested with actual PIN entry + if w.pinwait { + w.pinwait = false + res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest)) + if err != nil { + w.failure = err + return err + } + if res == 1 { + w.passphrasewait = true + return ErrTrezorPassphraseNeeded + } + } else if w.passphrasewait { + w.passphrasewait = false + if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil { + w.failure = err + return err + } + } + + return nil +} + +// Close implements usbwallet.driver, cleaning up and metadata maintained within +// the Trezor driver. +func (w *trezorDriver) Close() error { + w.version, w.label, w.pinwait = [3]uint32{}, "", false + return nil +} + +// Heartbeat implements usbwallet.driver, performing a sanity check against the +// Trezor to see if it's still online. +func (w *trezorDriver) Heartbeat() error { + if _, err := w.trezorExchange(&trezor.Ping{}, new(trezor.Success)); err != nil { + w.failure = err + return err + } + return nil +} + +// Derive implements usbwallet.driver, sending a derivation request to the Trezor +// and returning the Ethereum address located on that derivation path. +func (w *trezorDriver) Derive(path accounts.DerivationPath) (common.Address, error) { + return w.trezorDerive(path) +} + +// SignTx implements usbwallet.driver, sending the transaction to the Trezor and +// waiting for the user to confirm or deny the transaction. +func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { + if w.device == nil { + return common.Address{}, nil, accounts.ErrWalletClosed + } + return w.trezorSign(path, tx, chainID) +} + +func (w *trezorDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { + return nil, accounts.ErrNotSupported +} + +// trezorDerive sends a derivation request to the Trezor device and returns the +// Ethereum address located on that path. +func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) { + address := new(trezor.EthereumAddress) + if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil { + return common.Address{}, err + } + if addr := address.GetAddressBin(); len(addr) > 0 { // Older firmwares use binary formats + return common.BytesToAddress(addr), nil + } + if addr := address.GetAddressHex(); len(addr) > 0 { // Newer firmwares use hexadecimal formats + return common.HexToAddress(addr), nil + } + return common.Address{}, errors.New("missing derived address") +} + +// trezorSign sends the transaction to the Trezor wallet, and waits for the user +// to confirm or deny the transaction. +func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { + // Create the transaction initiation message + data := tx.Data() + length := uint32(len(data)) + + request := &trezor.EthereumSignTx{ + AddressN: derivationPath, + Nonce: new(big.Int).SetUint64(tx.Nonce()).Bytes(), + GasPrice: tx.GasPrice().Bytes(), + GasLimit: new(big.Int).SetUint64(tx.Gas()).Bytes(), + Value: tx.Value().Bytes(), + DataLength: &length, + } + if to := tx.To(); to != nil { + // Non contract deploy, set recipient explicitly + hex := to.Hex() + request.ToHex = &hex // Newer firmwares (old will ignore) + request.ToBin = (*to)[:] // Older firmwares (new will ignore) + } + if length > 1024 { // Send the data chunked if that was requested + request.DataInitialChunk, data = data[:1024], data[1024:] + } else { + request.DataInitialChunk, data = data, nil + } + if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?) + id := uint32(chainID.Int64()) + request.ChainId = &id + } + // Send the initiation message and stream content until a signature is returned + response := new(trezor.EthereumTxRequest) + if _, err := w.trezorExchange(request, response); err != nil { + return common.Address{}, nil, err + } + for response.DataLength != nil && int(*response.DataLength) <= len(data) { + chunk := data[:*response.DataLength] + data = data[*response.DataLength:] + + if _, err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil { + return common.Address{}, nil, err + } + } + // Extract the Ethereum signature and do a sanity validation + if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 { + return common.Address{}, nil, errors.New("reply lacks signature") + } + signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV())) + + // Create the correct signer and signature transform based on the chain ID + var signer types.Signer + if chainID == nil { + signer = new(types.HomesteadSigner) + } else { + // Trezor backend does not support typed transactions yet. + signer = types.NewEIP155Signer(chainID) + signature[64] -= byte(chainID.Uint64()*2 + 35) + } + + // Inject the final signature into the transaction and sanity check the sender + signed, err := tx.WithSignature(signer, signature) + if err != nil { + return common.Address{}, nil, err + } + sender, err := types.Sender(signer, signed) + if err != nil { + return common.Address{}, nil, err + } + return sender, signed, nil +} + +// trezorExchange performs a data exchange with the Trezor wallet, sending it a +// message and retrieving the response. If multiple responses are possible, the +// method will also return the index of the destination object used. +func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Message) (int, error) { + // Construct the original message payload to chunk up + data, err := proto.Marshal(req) + if err != nil { + return 0, err + } + payload := make([]byte, 8+len(data)) + copy(payload, []byte{0x23, 0x23}) + binary.BigEndian.PutUint16(payload[2:], trezor.Type(req)) + binary.BigEndian.PutUint32(payload[4:], uint32(len(data))) + copy(payload[8:], data) + + // Stream all the chunks to the device + chunk := make([]byte, 64) + chunk[0] = 0x3f // Report ID magic number + + for len(payload) > 0 { + // Construct the new message to stream, padding with zeroes if needed + if len(payload) > 63 { + copy(chunk[1:], payload[:63]) + payload = payload[63:] + } else { + copy(chunk[1:], payload) + copy(chunk[1+len(payload):], make([]byte, 63-len(payload))) + payload = nil + } + // Send over to the device + w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk)) + if _, err := w.device.Write(chunk); err != nil { + return 0, err + } + } + // Stream the reply back from the wallet in 64 byte chunks + var ( + kind uint16 + reply []byte + ) + for { + // Read the next chunk from the Trezor wallet + if _, err := io.ReadFull(w.device, chunk); err != nil { + return 0, err + } + w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk)) + + // Make sure the transport header matches + if chunk[0] != 0x3f || (len(reply) == 0 && (chunk[1] != 0x23 || chunk[2] != 0x23)) { + return 0, errTrezorReplyInvalidHeader + } + // If it's the first chunk, retrieve the reply message type and total message length + var payload []byte + + if len(reply) == 0 { + kind = binary.BigEndian.Uint16(chunk[3:5]) + reply = make([]byte, 0, int(binary.BigEndian.Uint32(chunk[5:9]))) + payload = chunk[9:] + } else { + payload = chunk[1:] + } + // Append to the reply and stop when filled up + if left := cap(reply) - len(reply); left > len(payload) { + reply = append(reply, payload...) + } else { + reply = append(reply, payload[:left]...) + break + } + } + // Try to parse the reply into the requested reply message + if kind == uint16(trezor.MessageType_MessageType_Failure) { + // Trezor returned a failure, extract and return the message + failure := new(trezor.Failure) + if err := proto.Unmarshal(reply, failure); err != nil { + return 0, err + } + return 0, errors.New("trezor: " + failure.GetMessage()) + } + if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) { + // Trezor is waiting for user confirmation, ack and wait for the next message + return w.trezorExchange(&trezor.ButtonAck{}, results...) + } + for i, res := range results { + if trezor.Type(res) == kind { + return i, proto.Unmarshal(reply, res) + } + } + expected := make([]string, len(results)) + for i, res := range results { + expected[i] = trezor.Name(trezor.Type(res)) + } + return 0, fmt.Errorf("trezor: expected reply types %s, got %s", expected, trezor.Name(kind)) +} diff --git a/accounts/usbwallet/trezor/messages-common.pb.go b/accounts/usbwallet/trezor/messages-common.pb.go new file mode 100644 index 0000000..7380080 --- /dev/null +++ b/accounts/usbwallet/trezor/messages-common.pb.go @@ -0,0 +1,1198 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages-common.proto +// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: messages-common.proto + +package trezor + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Failure_FailureType int32 + +const ( + Failure_Failure_UnexpectedMessage Failure_FailureType = 1 + Failure_Failure_ButtonExpected Failure_FailureType = 2 + Failure_Failure_DataError Failure_FailureType = 3 + Failure_Failure_ActionCancelled Failure_FailureType = 4 + Failure_Failure_PinExpected Failure_FailureType = 5 + Failure_Failure_PinCancelled Failure_FailureType = 6 + Failure_Failure_PinInvalid Failure_FailureType = 7 + Failure_Failure_InvalidSignature Failure_FailureType = 8 + Failure_Failure_ProcessError Failure_FailureType = 9 + Failure_Failure_NotEnoughFunds Failure_FailureType = 10 + Failure_Failure_NotInitialized Failure_FailureType = 11 + Failure_Failure_PinMismatch Failure_FailureType = 12 + Failure_Failure_FirmwareError Failure_FailureType = 99 +) + +// Enum value maps for Failure_FailureType. +var ( + Failure_FailureType_name = map[int32]string{ + 1: "Failure_UnexpectedMessage", + 2: "Failure_ButtonExpected", + 3: "Failure_DataError", + 4: "Failure_ActionCancelled", + 5: "Failure_PinExpected", + 6: "Failure_PinCancelled", + 7: "Failure_PinInvalid", + 8: "Failure_InvalidSignature", + 9: "Failure_ProcessError", + 10: "Failure_NotEnoughFunds", + 11: "Failure_NotInitialized", + 12: "Failure_PinMismatch", + 99: "Failure_FirmwareError", + } + Failure_FailureType_value = map[string]int32{ + "Failure_UnexpectedMessage": 1, + "Failure_ButtonExpected": 2, + "Failure_DataError": 3, + "Failure_ActionCancelled": 4, + "Failure_PinExpected": 5, + "Failure_PinCancelled": 6, + "Failure_PinInvalid": 7, + "Failure_InvalidSignature": 8, + "Failure_ProcessError": 9, + "Failure_NotEnoughFunds": 10, + "Failure_NotInitialized": 11, + "Failure_PinMismatch": 12, + "Failure_FirmwareError": 99, + } +) + +func (x Failure_FailureType) Enum() *Failure_FailureType { + p := new(Failure_FailureType) + *p = x + return p +} + +func (x Failure_FailureType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Failure_FailureType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_common_proto_enumTypes[0].Descriptor() +} + +func (Failure_FailureType) Type() protoreflect.EnumType { + return &file_messages_common_proto_enumTypes[0] +} + +func (x Failure_FailureType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *Failure_FailureType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = Failure_FailureType(num) + return nil +} + +// Deprecated: Use Failure_FailureType.Descriptor instead. +func (Failure_FailureType) EnumDescriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{1, 0} +} + +// * +// Type of button request +type ButtonRequest_ButtonRequestType int32 + +const ( + ButtonRequest_ButtonRequest_Other ButtonRequest_ButtonRequestType = 1 + ButtonRequest_ButtonRequest_FeeOverThreshold ButtonRequest_ButtonRequestType = 2 + ButtonRequest_ButtonRequest_ConfirmOutput ButtonRequest_ButtonRequestType = 3 + ButtonRequest_ButtonRequest_ResetDevice ButtonRequest_ButtonRequestType = 4 + ButtonRequest_ButtonRequest_ConfirmWord ButtonRequest_ButtonRequestType = 5 + ButtonRequest_ButtonRequest_WipeDevice ButtonRequest_ButtonRequestType = 6 + ButtonRequest_ButtonRequest_ProtectCall ButtonRequest_ButtonRequestType = 7 + ButtonRequest_ButtonRequest_SignTx ButtonRequest_ButtonRequestType = 8 + ButtonRequest_ButtonRequest_FirmwareCheck ButtonRequest_ButtonRequestType = 9 + ButtonRequest_ButtonRequest_Address ButtonRequest_ButtonRequestType = 10 + ButtonRequest_ButtonRequest_PublicKey ButtonRequest_ButtonRequestType = 11 + ButtonRequest_ButtonRequest_MnemonicWordCount ButtonRequest_ButtonRequestType = 12 + ButtonRequest_ButtonRequest_MnemonicInput ButtonRequest_ButtonRequestType = 13 + ButtonRequest_ButtonRequest_PassphraseType ButtonRequest_ButtonRequestType = 14 + ButtonRequest_ButtonRequest_UnknownDerivationPath ButtonRequest_ButtonRequestType = 15 +) + +// Enum value maps for ButtonRequest_ButtonRequestType. +var ( + ButtonRequest_ButtonRequestType_name = map[int32]string{ + 1: "ButtonRequest_Other", + 2: "ButtonRequest_FeeOverThreshold", + 3: "ButtonRequest_ConfirmOutput", + 4: "ButtonRequest_ResetDevice", + 5: "ButtonRequest_ConfirmWord", + 6: "ButtonRequest_WipeDevice", + 7: "ButtonRequest_ProtectCall", + 8: "ButtonRequest_SignTx", + 9: "ButtonRequest_FirmwareCheck", + 10: "ButtonRequest_Address", + 11: "ButtonRequest_PublicKey", + 12: "ButtonRequest_MnemonicWordCount", + 13: "ButtonRequest_MnemonicInput", + 14: "ButtonRequest_PassphraseType", + 15: "ButtonRequest_UnknownDerivationPath", + } + ButtonRequest_ButtonRequestType_value = map[string]int32{ + "ButtonRequest_Other": 1, + "ButtonRequest_FeeOverThreshold": 2, + "ButtonRequest_ConfirmOutput": 3, + "ButtonRequest_ResetDevice": 4, + "ButtonRequest_ConfirmWord": 5, + "ButtonRequest_WipeDevice": 6, + "ButtonRequest_ProtectCall": 7, + "ButtonRequest_SignTx": 8, + "ButtonRequest_FirmwareCheck": 9, + "ButtonRequest_Address": 10, + "ButtonRequest_PublicKey": 11, + "ButtonRequest_MnemonicWordCount": 12, + "ButtonRequest_MnemonicInput": 13, + "ButtonRequest_PassphraseType": 14, + "ButtonRequest_UnknownDerivationPath": 15, + } +) + +func (x ButtonRequest_ButtonRequestType) Enum() *ButtonRequest_ButtonRequestType { + p := new(ButtonRequest_ButtonRequestType) + *p = x + return p +} + +func (x ButtonRequest_ButtonRequestType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ButtonRequest_ButtonRequestType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_common_proto_enumTypes[1].Descriptor() +} + +func (ButtonRequest_ButtonRequestType) Type() protoreflect.EnumType { + return &file_messages_common_proto_enumTypes[1] +} + +func (x ButtonRequest_ButtonRequestType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *ButtonRequest_ButtonRequestType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = ButtonRequest_ButtonRequestType(num) + return nil +} + +// Deprecated: Use ButtonRequest_ButtonRequestType.Descriptor instead. +func (ButtonRequest_ButtonRequestType) EnumDescriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{2, 0} +} + +// * +// Type of PIN request +type PinMatrixRequest_PinMatrixRequestType int32 + +const ( + PinMatrixRequest_PinMatrixRequestType_Current PinMatrixRequest_PinMatrixRequestType = 1 + PinMatrixRequest_PinMatrixRequestType_NewFirst PinMatrixRequest_PinMatrixRequestType = 2 + PinMatrixRequest_PinMatrixRequestType_NewSecond PinMatrixRequest_PinMatrixRequestType = 3 +) + +// Enum value maps for PinMatrixRequest_PinMatrixRequestType. +var ( + PinMatrixRequest_PinMatrixRequestType_name = map[int32]string{ + 1: "PinMatrixRequestType_Current", + 2: "PinMatrixRequestType_NewFirst", + 3: "PinMatrixRequestType_NewSecond", + } + PinMatrixRequest_PinMatrixRequestType_value = map[string]int32{ + "PinMatrixRequestType_Current": 1, + "PinMatrixRequestType_NewFirst": 2, + "PinMatrixRequestType_NewSecond": 3, + } +) + +func (x PinMatrixRequest_PinMatrixRequestType) Enum() *PinMatrixRequest_PinMatrixRequestType { + p := new(PinMatrixRequest_PinMatrixRequestType) + *p = x + return p +} + +func (x PinMatrixRequest_PinMatrixRequestType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PinMatrixRequest_PinMatrixRequestType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_common_proto_enumTypes[2].Descriptor() +} + +func (PinMatrixRequest_PinMatrixRequestType) Type() protoreflect.EnumType { + return &file_messages_common_proto_enumTypes[2] +} + +func (x PinMatrixRequest_PinMatrixRequestType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *PinMatrixRequest_PinMatrixRequestType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = PinMatrixRequest_PinMatrixRequestType(num) + return nil +} + +// Deprecated: Use PinMatrixRequest_PinMatrixRequestType.Descriptor instead. +func (PinMatrixRequest_PinMatrixRequestType) EnumDescriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{4, 0} +} + +// * +// Response: Success of the previous request +// @end +type Success struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message *string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` // human readable description of action or request-specific payload +} + +func (x *Success) Reset() { + *x = Success{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Success) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Success) ProtoMessage() {} + +func (x *Success) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Success.ProtoReflect.Descriptor instead. +func (*Success) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{0} +} + +func (x *Success) GetMessage() string { + if x != nil && x.Message != nil { + return *x.Message + } + return "" +} + +// * +// Response: Failure of the previous request +// @end +type Failure struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code *Failure_FailureType `protobuf:"varint,1,opt,name=code,enum=hw.trezor.messages.common.Failure_FailureType" json:"code,omitempty"` // computer-readable definition of the error state + Message *string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` // human-readable message of the error state +} + +func (x *Failure) Reset() { + *x = Failure{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Failure) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Failure) ProtoMessage() {} + +func (x *Failure) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Failure.ProtoReflect.Descriptor instead. +func (*Failure) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{1} +} + +func (x *Failure) GetCode() Failure_FailureType { + if x != nil && x.Code != nil { + return *x.Code + } + return Failure_Failure_UnexpectedMessage +} + +func (x *Failure) GetMessage() string { + if x != nil && x.Message != nil { + return *x.Message + } + return "" +} + +// * +// Response: Device is waiting for HW button press. +// @auxstart +// @next ButtonAck +type ButtonRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code *ButtonRequest_ButtonRequestType `protobuf:"varint,1,opt,name=code,enum=hw.trezor.messages.common.ButtonRequest_ButtonRequestType" json:"code,omitempty"` + Data *string `protobuf:"bytes,2,opt,name=data" json:"data,omitempty"` +} + +func (x *ButtonRequest) Reset() { + *x = ButtonRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ButtonRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ButtonRequest) ProtoMessage() {} + +func (x *ButtonRequest) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ButtonRequest.ProtoReflect.Descriptor instead. +func (*ButtonRequest) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{2} +} + +func (x *ButtonRequest) GetCode() ButtonRequest_ButtonRequestType { + if x != nil && x.Code != nil { + return *x.Code + } + return ButtonRequest_ButtonRequest_Other +} + +func (x *ButtonRequest) GetData() string { + if x != nil && x.Data != nil { + return *x.Data + } + return "" +} + +// * +// Request: Computer agrees to wait for HW button press +// @auxend +type ButtonAck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ButtonAck) Reset() { + *x = ButtonAck{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ButtonAck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ButtonAck) ProtoMessage() {} + +func (x *ButtonAck) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ButtonAck.ProtoReflect.Descriptor instead. +func (*ButtonAck) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{3} +} + +// * +// Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme +// @auxstart +// @next PinMatrixAck +type PinMatrixRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type *PinMatrixRequest_PinMatrixRequestType `protobuf:"varint,1,opt,name=type,enum=hw.trezor.messages.common.PinMatrixRequest_PinMatrixRequestType" json:"type,omitempty"` +} + +func (x *PinMatrixRequest) Reset() { + *x = PinMatrixRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PinMatrixRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PinMatrixRequest) ProtoMessage() {} + +func (x *PinMatrixRequest) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PinMatrixRequest.ProtoReflect.Descriptor instead. +func (*PinMatrixRequest) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{4} +} + +func (x *PinMatrixRequest) GetType() PinMatrixRequest_PinMatrixRequestType { + if x != nil && x.Type != nil { + return *x.Type + } + return PinMatrixRequest_PinMatrixRequestType_Current +} + +// * +// Request: Computer responds with encoded PIN +// @auxend +type PinMatrixAck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pin *string `protobuf:"bytes,1,req,name=pin" json:"pin,omitempty"` // matrix encoded PIN entered by user +} + +func (x *PinMatrixAck) Reset() { + *x = PinMatrixAck{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PinMatrixAck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PinMatrixAck) ProtoMessage() {} + +func (x *PinMatrixAck) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PinMatrixAck.ProtoReflect.Descriptor instead. +func (*PinMatrixAck) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{5} +} + +func (x *PinMatrixAck) GetPin() string { + if x != nil && x.Pin != nil { + return *x.Pin + } + return "" +} + +// * +// Response: Device awaits encryption passphrase +// @auxstart +// @next PassphraseAck +type PassphraseRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OnDevice *bool `protobuf:"varint,1,opt,name=on_device,json=onDevice" json:"on_device,omitempty"` // passphrase is being entered on the device +} + +func (x *PassphraseRequest) Reset() { + *x = PassphraseRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PassphraseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PassphraseRequest) ProtoMessage() {} + +func (x *PassphraseRequest) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PassphraseRequest.ProtoReflect.Descriptor instead. +func (*PassphraseRequest) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{6} +} + +func (x *PassphraseRequest) GetOnDevice() bool { + if x != nil && x.OnDevice != nil { + return *x.OnDevice + } + return false +} + +// * +// Request: Send passphrase back +// @next PassphraseStateRequest +type PassphraseAck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Passphrase *string `protobuf:"bytes,1,opt,name=passphrase" json:"passphrase,omitempty"` + State []byte `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` // expected device state +} + +func (x *PassphraseAck) Reset() { + *x = PassphraseAck{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PassphraseAck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PassphraseAck) ProtoMessage() {} + +func (x *PassphraseAck) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PassphraseAck.ProtoReflect.Descriptor instead. +func (*PassphraseAck) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{7} +} + +func (x *PassphraseAck) GetPassphrase() string { + if x != nil && x.Passphrase != nil { + return *x.Passphrase + } + return "" +} + +func (x *PassphraseAck) GetState() []byte { + if x != nil { + return x.State + } + return nil +} + +// * +// Response: Device awaits passphrase state +// @next PassphraseStateAck +type PassphraseStateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + State []byte `protobuf:"bytes,1,opt,name=state" json:"state,omitempty"` // actual device state +} + +func (x *PassphraseStateRequest) Reset() { + *x = PassphraseStateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PassphraseStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PassphraseStateRequest) ProtoMessage() {} + +func (x *PassphraseStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PassphraseStateRequest.ProtoReflect.Descriptor instead. +func (*PassphraseStateRequest) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{8} +} + +func (x *PassphraseStateRequest) GetState() []byte { + if x != nil { + return x.State + } + return nil +} + +// * +// Request: Send passphrase state back +// @auxend +type PassphraseStateAck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PassphraseStateAck) Reset() { + *x = PassphraseStateAck{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PassphraseStateAck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PassphraseStateAck) ProtoMessage() {} + +func (x *PassphraseStateAck) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PassphraseStateAck.ProtoReflect.Descriptor instead. +func (*PassphraseStateAck) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{9} +} + +// * +// Structure representing BIP32 (hierarchical deterministic) node +// Used for imports of private key into the device and exporting public key out of device +// @embed +type HDNodeType struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Depth *uint32 `protobuf:"varint,1,req,name=depth" json:"depth,omitempty"` + Fingerprint *uint32 `protobuf:"varint,2,req,name=fingerprint" json:"fingerprint,omitempty"` + ChildNum *uint32 `protobuf:"varint,3,req,name=child_num,json=childNum" json:"child_num,omitempty"` + ChainCode []byte `protobuf:"bytes,4,req,name=chain_code,json=chainCode" json:"chain_code,omitempty"` + PrivateKey []byte `protobuf:"bytes,5,opt,name=private_key,json=privateKey" json:"private_key,omitempty"` + PublicKey []byte `protobuf:"bytes,6,opt,name=public_key,json=publicKey" json:"public_key,omitempty"` +} + +func (x *HDNodeType) Reset() { + *x = HDNodeType{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_common_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HDNodeType) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HDNodeType) ProtoMessage() {} + +func (x *HDNodeType) ProtoReflect() protoreflect.Message { + mi := &file_messages_common_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HDNodeType.ProtoReflect.Descriptor instead. +func (*HDNodeType) Descriptor() ([]byte, []int) { + return file_messages_common_proto_rawDescGZIP(), []int{10} +} + +func (x *HDNodeType) GetDepth() uint32 { + if x != nil && x.Depth != nil { + return *x.Depth + } + return 0 +} + +func (x *HDNodeType) GetFingerprint() uint32 { + if x != nil && x.Fingerprint != nil { + return *x.Fingerprint + } + return 0 +} + +func (x *HDNodeType) GetChildNum() uint32 { + if x != nil && x.ChildNum != nil { + return *x.ChildNum + } + return 0 +} + +func (x *HDNodeType) GetChainCode() []byte { + if x != nil { + return x.ChainCode + } + return nil +} + +func (x *HDNodeType) GetPrivateKey() []byte { + if x != nil { + return x.PrivateKey + } + return nil +} + +func (x *HDNodeType) GetPublicKey() []byte { + if x != nil { + return x.PublicKey + } + return nil +} + +var File_messages_common_proto protoreflect.FileDescriptor + +var file_messages_common_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x68, 0x77, 0x2e, 0x74, 0x72, 0x65, 0x7a, + 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x22, 0x23, 0x0a, 0x07, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xd5, 0x03, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x12, 0x42, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x2e, 0x2e, 0x68, 0x77, 0x2e, 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x46, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0xeb, 0x02, 0x0a, 0x0b, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x55, 0x6e, 0x65, + 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x10, 0x01, + 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x42, 0x75, 0x74, 0x74, + 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, + 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x44, 0x61, 0x74, 0x61, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x10, 0x03, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x10, 0x04, + 0x12, 0x17, 0x0a, 0x13, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x50, 0x69, 0x6e, 0x45, + 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x10, 0x05, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x50, 0x69, 0x6e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, + 0x64, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x50, + 0x69, 0x6e, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x10, 0x07, 0x12, 0x1c, 0x0a, 0x18, 0x46, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x53, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x61, 0x69, + 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x10, 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x4e, + 0x6f, 0x74, 0x45, 0x6e, 0x6f, 0x75, 0x67, 0x68, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x10, 0x0a, 0x12, + 0x1a, 0x0a, 0x16, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x4e, 0x6f, 0x74, 0x49, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x10, 0x0b, 0x12, 0x17, 0x0a, 0x13, 0x46, + 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x50, 0x69, 0x6e, 0x4d, 0x69, 0x73, 0x6d, 0x61, 0x74, + 0x63, 0x68, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, + 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x63, 0x22, + 0xe6, 0x04, 0x0a, 0x0d, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x4e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x3a, 0x2e, 0x68, 0x77, 0x2e, 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x42, 0x75, 0x74, 0x74, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xf0, 0x03, 0x0a, 0x11, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x42, + 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x4f, 0x74, 0x68, + 0x65, 0x72, 0x10, 0x01, 0x12, 0x22, 0x0a, 0x1e, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x46, 0x65, 0x65, 0x4f, 0x76, 0x65, 0x72, 0x54, 0x68, 0x72, + 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x42, 0x75, 0x74, 0x74, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, + 0x6d, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x10, 0x03, 0x12, 0x1d, 0x0a, 0x19, 0x42, 0x75, 0x74, + 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x52, 0x65, 0x73, 0x65, 0x74, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x10, 0x04, 0x12, 0x1d, 0x0a, 0x19, 0x42, 0x75, 0x74, 0x74, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, + 0x6d, 0x57, 0x6f, 0x72, 0x64, 0x10, 0x05, 0x12, 0x1c, 0x0a, 0x18, 0x42, 0x75, 0x74, 0x74, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x57, 0x69, 0x70, 0x65, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x10, 0x06, 0x12, 0x1d, 0x0a, 0x19, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x43, 0x61, + 0x6c, 0x6c, 0x10, 0x07, 0x12, 0x18, 0x0a, 0x14, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x53, 0x69, 0x67, 0x6e, 0x54, 0x78, 0x10, 0x08, 0x12, 0x1f, + 0x0a, 0x1b, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x10, 0x09, 0x12, + 0x19, 0x0a, 0x15, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x0a, 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x75, + 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0x0b, 0x12, 0x23, 0x0a, 0x1f, 0x42, 0x75, 0x74, 0x74, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x4d, 0x6e, 0x65, 0x6d, 0x6f, 0x6e, 0x69, + 0x63, 0x57, 0x6f, 0x72, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x10, 0x0c, 0x12, 0x1f, 0x0a, 0x1b, + 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x4d, 0x6e, + 0x65, 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x10, 0x0d, 0x12, 0x20, 0x0a, + 0x1c, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x50, + 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x10, 0x0e, 0x12, + 0x27, 0x0a, 0x23, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x44, 0x65, 0x72, 0x69, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x10, 0x0f, 0x22, 0x0b, 0x0a, 0x09, 0x42, 0x75, 0x74, 0x74, + 0x6f, 0x6e, 0x41, 0x63, 0x6b, 0x22, 0xe9, 0x01, 0x0a, 0x10, 0x50, 0x69, 0x6e, 0x4d, 0x61, 0x74, + 0x72, 0x69, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x40, 0x2e, 0x68, 0x77, 0x2e, 0x74, 0x72, + 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x7f, 0x0a, 0x14, 0x50, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x50, 0x69, 0x6e, 0x4d, + 0x61, 0x74, 0x72, 0x69, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x50, 0x69, + 0x6e, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x4e, 0x65, 0x77, 0x46, 0x69, 0x72, 0x73, 0x74, 0x10, 0x02, 0x12, 0x22, 0x0a, + 0x1e, 0x50, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4e, 0x65, 0x77, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x10, + 0x03, 0x22, 0x20, 0x0a, 0x0c, 0x50, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x41, 0x63, + 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x03, + 0x70, 0x69, 0x6e, 0x22, 0x30, 0x0a, 0x11, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6e, 0x5f, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x6e, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x45, 0x0a, 0x0d, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, + 0x61, 0x73, 0x65, 0x41, 0x63, 0x6b, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, + 0x72, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x73, 0x73, + 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x2e, 0x0a, 0x16, + 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x14, 0x0a, 0x12, + 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x41, + 0x63, 0x6b, 0x22, 0xc0, 0x01, 0x0a, 0x0a, 0x48, 0x44, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0d, + 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, + 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x02, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x69, + 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x68, 0x69, + 0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x02, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x68, + 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x02, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x2f, 0x75, 0x73, 0x62, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x7a, + 0x6f, 0x72, +} + +var ( + file_messages_common_proto_rawDescOnce sync.Once + file_messages_common_proto_rawDescData = file_messages_common_proto_rawDesc +) + +func file_messages_common_proto_rawDescGZIP() []byte { + file_messages_common_proto_rawDescOnce.Do(func() { + file_messages_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_common_proto_rawDescData) + }) + return file_messages_common_proto_rawDescData +} + +var file_messages_common_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_messages_common_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_messages_common_proto_goTypes = []any{ + (Failure_FailureType)(0), // 0: hw.trezor.messages.common.Failure.FailureType + (ButtonRequest_ButtonRequestType)(0), // 1: hw.trezor.messages.common.ButtonRequest.ButtonRequestType + (PinMatrixRequest_PinMatrixRequestType)(0), // 2: hw.trezor.messages.common.PinMatrixRequest.PinMatrixRequestType + (*Success)(nil), // 3: hw.trezor.messages.common.Success + (*Failure)(nil), // 4: hw.trezor.messages.common.Failure + (*ButtonRequest)(nil), // 5: hw.trezor.messages.common.ButtonRequest + (*ButtonAck)(nil), // 6: hw.trezor.messages.common.ButtonAck + (*PinMatrixRequest)(nil), // 7: hw.trezor.messages.common.PinMatrixRequest + (*PinMatrixAck)(nil), // 8: hw.trezor.messages.common.PinMatrixAck + (*PassphraseRequest)(nil), // 9: hw.trezor.messages.common.PassphraseRequest + (*PassphraseAck)(nil), // 10: hw.trezor.messages.common.PassphraseAck + (*PassphraseStateRequest)(nil), // 11: hw.trezor.messages.common.PassphraseStateRequest + (*PassphraseStateAck)(nil), // 12: hw.trezor.messages.common.PassphraseStateAck + (*HDNodeType)(nil), // 13: hw.trezor.messages.common.HDNodeType +} +var file_messages_common_proto_depIdxs = []int32{ + 0, // 0: hw.trezor.messages.common.Failure.code:type_name -> hw.trezor.messages.common.Failure.FailureType + 1, // 1: hw.trezor.messages.common.ButtonRequest.code:type_name -> hw.trezor.messages.common.ButtonRequest.ButtonRequestType + 2, // 2: hw.trezor.messages.common.PinMatrixRequest.type:type_name -> hw.trezor.messages.common.PinMatrixRequest.PinMatrixRequestType + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_messages_common_proto_init() } +func file_messages_common_proto_init() { + if File_messages_common_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_messages_common_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Success); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*Failure); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*ButtonRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*ButtonAck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*PinMatrixRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*PinMatrixAck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*PassphraseRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*PassphraseAck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*PassphraseStateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*PassphraseStateAck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_common_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*HDNodeType); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_messages_common_proto_rawDesc, + NumEnums: 3, + NumMessages: 11, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_messages_common_proto_goTypes, + DependencyIndexes: file_messages_common_proto_depIdxs, + EnumInfos: file_messages_common_proto_enumTypes, + MessageInfos: file_messages_common_proto_msgTypes, + }.Build() + File_messages_common_proto = out.File + file_messages_common_proto_rawDesc = nil + file_messages_common_proto_goTypes = nil + file_messages_common_proto_depIdxs = nil +} diff --git a/accounts/usbwallet/trezor/messages-common.proto b/accounts/usbwallet/trezor/messages-common.proto new file mode 100644 index 0000000..1f524e2 --- /dev/null +++ b/accounts/usbwallet/trezor/messages-common.proto @@ -0,0 +1,149 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages-common.proto +// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. + +syntax = "proto2"; +package hw.trezor.messages.common; + +option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"; + +/** + * Response: Success of the previous request + * @end + */ +message Success { + optional string message = 1; // human readable description of action or request-specific payload +} + +/** + * Response: Failure of the previous request + * @end + */ +message Failure { + optional FailureType code = 1; // computer-readable definition of the error state + optional string message = 2; // human-readable message of the error state + enum FailureType { + Failure_UnexpectedMessage = 1; + Failure_ButtonExpected = 2; + Failure_DataError = 3; + Failure_ActionCancelled = 4; + Failure_PinExpected = 5; + Failure_PinCancelled = 6; + Failure_PinInvalid = 7; + Failure_InvalidSignature = 8; + Failure_ProcessError = 9; + Failure_NotEnoughFunds = 10; + Failure_NotInitialized = 11; + Failure_PinMismatch = 12; + Failure_FirmwareError = 99; + } +} + +/** + * Response: Device is waiting for HW button press. + * @auxstart + * @next ButtonAck + */ +message ButtonRequest { + optional ButtonRequestType code = 1; + optional string data = 2; + /** + * Type of button request + */ + enum ButtonRequestType { + ButtonRequest_Other = 1; + ButtonRequest_FeeOverThreshold = 2; + ButtonRequest_ConfirmOutput = 3; + ButtonRequest_ResetDevice = 4; + ButtonRequest_ConfirmWord = 5; + ButtonRequest_WipeDevice = 6; + ButtonRequest_ProtectCall = 7; + ButtonRequest_SignTx = 8; + ButtonRequest_FirmwareCheck = 9; + ButtonRequest_Address = 10; + ButtonRequest_PublicKey = 11; + ButtonRequest_MnemonicWordCount = 12; + ButtonRequest_MnemonicInput = 13; + ButtonRequest_PassphraseType = 14; + ButtonRequest_UnknownDerivationPath = 15; + } +} + +/** + * Request: Computer agrees to wait for HW button press + * @auxend + */ +message ButtonAck { +} + +/** + * Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme + * @auxstart + * @next PinMatrixAck + */ +message PinMatrixRequest { + optional PinMatrixRequestType type = 1; + /** + * Type of PIN request + */ + enum PinMatrixRequestType { + PinMatrixRequestType_Current = 1; + PinMatrixRequestType_NewFirst = 2; + PinMatrixRequestType_NewSecond = 3; + } +} + +/** + * Request: Computer responds with encoded PIN + * @auxend + */ +message PinMatrixAck { + required string pin = 1; // matrix encoded PIN entered by user +} + +/** + * Response: Device awaits encryption passphrase + * @auxstart + * @next PassphraseAck + */ +message PassphraseRequest { + optional bool on_device = 1; // passphrase is being entered on the device +} + +/** + * Request: Send passphrase back + * @next PassphraseStateRequest + */ +message PassphraseAck { + optional string passphrase = 1; + optional bytes state = 2; // expected device state +} + +/** + * Response: Device awaits passphrase state + * @next PassphraseStateAck + */ +message PassphraseStateRequest { + optional bytes state = 1; // actual device state +} + +/** + * Request: Send passphrase state back + * @auxend + */ +message PassphraseStateAck { +} + +/** + * Structure representing BIP32 (hierarchical deterministic) node + * Used for imports of private key into the device and exporting public key out of device + * @embed + */ +message HDNodeType { + required uint32 depth = 1; + required uint32 fingerprint = 2; + required uint32 child_num = 3; + required bytes chain_code = 4; + optional bytes private_key = 5; + optional bytes public_key = 6; +} diff --git a/accounts/usbwallet/trezor/messages-ethereum.pb.go b/accounts/usbwallet/trezor/messages-ethereum.pb.go new file mode 100644 index 0000000..a92123e --- /dev/null +++ b/accounts/usbwallet/trezor/messages-ethereum.pb.go @@ -0,0 +1,1002 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum.proto +// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: messages-ethereum.proto + +package trezor + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// * +// Request: Ask device for public key corresponding to address_n path +// @start +// @next EthereumPublicKey +// @next Failure +type EthereumGetPublicKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` // BIP-32 path to derive the key from master node + ShowDisplay *bool `protobuf:"varint,2,opt,name=show_display,json=showDisplay" json:"show_display,omitempty"` // optionally show on display before sending the result +} + +func (x *EthereumGetPublicKey) Reset() { + *x = EthereumGetPublicKey{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumGetPublicKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumGetPublicKey) ProtoMessage() {} + +func (x *EthereumGetPublicKey) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumGetPublicKey.ProtoReflect.Descriptor instead. +func (*EthereumGetPublicKey) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{0} +} + +func (x *EthereumGetPublicKey) GetAddressN() []uint32 { + if x != nil { + return x.AddressN + } + return nil +} + +func (x *EthereumGetPublicKey) GetShowDisplay() bool { + if x != nil && x.ShowDisplay != nil { + return *x.ShowDisplay + } + return false +} + +// * +// Response: Contains public key derived from device private seed +// @end +type EthereumPublicKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Node *HDNodeType `protobuf:"bytes,1,opt,name=node" json:"node,omitempty"` // BIP32 public node + Xpub *string `protobuf:"bytes,2,opt,name=xpub" json:"xpub,omitempty"` // serialized form of public node +} + +func (x *EthereumPublicKey) Reset() { + *x = EthereumPublicKey{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumPublicKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumPublicKey) ProtoMessage() {} + +func (x *EthereumPublicKey) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumPublicKey.ProtoReflect.Descriptor instead. +func (*EthereumPublicKey) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{1} +} + +func (x *EthereumPublicKey) GetNode() *HDNodeType { + if x != nil { + return x.Node + } + return nil +} + +func (x *EthereumPublicKey) GetXpub() string { + if x != nil && x.Xpub != nil { + return *x.Xpub + } + return "" +} + +// * +// Request: Ask device for Ethereum address corresponding to address_n path +// @start +// @next EthereumAddress +// @next Failure +type EthereumGetAddress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` // BIP-32 path to derive the key from master node + ShowDisplay *bool `protobuf:"varint,2,opt,name=show_display,json=showDisplay" json:"show_display,omitempty"` // optionally show on display before sending the result +} + +func (x *EthereumGetAddress) Reset() { + *x = EthereumGetAddress{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumGetAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumGetAddress) ProtoMessage() {} + +func (x *EthereumGetAddress) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumGetAddress.ProtoReflect.Descriptor instead. +func (*EthereumGetAddress) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{2} +} + +func (x *EthereumGetAddress) GetAddressN() []uint32 { + if x != nil { + return x.AddressN + } + return nil +} + +func (x *EthereumGetAddress) GetShowDisplay() bool { + if x != nil && x.ShowDisplay != nil { + return *x.ShowDisplay + } + return false +} + +// * +// Response: Contains an Ethereum address derived from device private seed +// @end +type EthereumAddress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AddressBin []byte `protobuf:"bytes,1,opt,name=addressBin" json:"addressBin,omitempty"` // Ethereum address as 20 bytes (legacy firmwares) + AddressHex *string `protobuf:"bytes,2,opt,name=addressHex" json:"addressHex,omitempty"` // Ethereum address as hex string (newer firmwares) +} + +func (x *EthereumAddress) Reset() { + *x = EthereumAddress{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumAddress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumAddress) ProtoMessage() {} + +func (x *EthereumAddress) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumAddress.ProtoReflect.Descriptor instead. +func (*EthereumAddress) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{3} +} + +func (x *EthereumAddress) GetAddressBin() []byte { + if x != nil { + return x.AddressBin + } + return nil +} + +func (x *EthereumAddress) GetAddressHex() string { + if x != nil && x.AddressHex != nil { + return *x.AddressHex + } + return "" +} + +// * +// Request: Ask device to sign transaction +// All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing. +// Note: the first at most 1024 bytes of data MUST be transmitted as part of this message. +// @start +// @next EthereumTxRequest +// @next Failure +type EthereumSignTx struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` // BIP-32 path to derive the key from master node + Nonce []byte `protobuf:"bytes,2,opt,name=nonce" json:"nonce,omitempty"` // <=256 bit unsigned big endian + GasPrice []byte `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice" json:"gas_price,omitempty"` // <=256 bit unsigned big endian (in wei) + GasLimit []byte `protobuf:"bytes,4,opt,name=gas_limit,json=gasLimit" json:"gas_limit,omitempty"` // <=256 bit unsigned big endian + ToBin []byte `protobuf:"bytes,5,opt,name=toBin" json:"toBin,omitempty"` // recipient address (20 bytes, legacy firmware) + ToHex *string `protobuf:"bytes,11,opt,name=toHex" json:"toHex,omitempty"` // recipient address (hex string, newer firmware) + Value []byte `protobuf:"bytes,6,opt,name=value" json:"value,omitempty"` // <=256 bit unsigned big endian (in wei) + DataInitialChunk []byte `protobuf:"bytes,7,opt,name=data_initial_chunk,json=dataInitialChunk" json:"data_initial_chunk,omitempty"` // The initial data chunk (<= 1024 bytes) + DataLength *uint32 `protobuf:"varint,8,opt,name=data_length,json=dataLength" json:"data_length,omitempty"` // Length of transaction payload + ChainId *uint32 `protobuf:"varint,9,opt,name=chain_id,json=chainId" json:"chain_id,omitempty"` // Chain Id for EIP 155 + TxType *uint32 `protobuf:"varint,10,opt,name=tx_type,json=txType" json:"tx_type,omitempty"` // (only for Wanchain) +} + +func (x *EthereumSignTx) Reset() { + *x = EthereumSignTx{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumSignTx) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumSignTx) ProtoMessage() {} + +func (x *EthereumSignTx) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumSignTx.ProtoReflect.Descriptor instead. +func (*EthereumSignTx) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{4} +} + +func (x *EthereumSignTx) GetAddressN() []uint32 { + if x != nil { + return x.AddressN + } + return nil +} + +func (x *EthereumSignTx) GetNonce() []byte { + if x != nil { + return x.Nonce + } + return nil +} + +func (x *EthereumSignTx) GetGasPrice() []byte { + if x != nil { + return x.GasPrice + } + return nil +} + +func (x *EthereumSignTx) GetGasLimit() []byte { + if x != nil { + return x.GasLimit + } + return nil +} + +func (x *EthereumSignTx) GetToBin() []byte { + if x != nil { + return x.ToBin + } + return nil +} + +func (x *EthereumSignTx) GetToHex() string { + if x != nil && x.ToHex != nil { + return *x.ToHex + } + return "" +} + +func (x *EthereumSignTx) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *EthereumSignTx) GetDataInitialChunk() []byte { + if x != nil { + return x.DataInitialChunk + } + return nil +} + +func (x *EthereumSignTx) GetDataLength() uint32 { + if x != nil && x.DataLength != nil { + return *x.DataLength + } + return 0 +} + +func (x *EthereumSignTx) GetChainId() uint32 { + if x != nil && x.ChainId != nil { + return *x.ChainId + } + return 0 +} + +func (x *EthereumSignTx) GetTxType() uint32 { + if x != nil && x.TxType != nil { + return *x.TxType + } + return 0 +} + +// * +// Response: Device asks for more data from transaction payload, or returns the signature. +// If data_length is set, device awaits that many more bytes of payload. +// Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present. +// @end +// @next EthereumTxAck +type EthereumTxRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DataLength *uint32 `protobuf:"varint,1,opt,name=data_length,json=dataLength" json:"data_length,omitempty"` // Number of bytes being requested (<= 1024) + SignatureV *uint32 `protobuf:"varint,2,opt,name=signature_v,json=signatureV" json:"signature_v,omitempty"` // Computed signature (recovery parameter, limited to 27 or 28) + SignatureR []byte `protobuf:"bytes,3,opt,name=signature_r,json=signatureR" json:"signature_r,omitempty"` // Computed signature R component (256 bit) + SignatureS []byte `protobuf:"bytes,4,opt,name=signature_s,json=signatureS" json:"signature_s,omitempty"` // Computed signature S component (256 bit) +} + +func (x *EthereumTxRequest) Reset() { + *x = EthereumTxRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumTxRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumTxRequest) ProtoMessage() {} + +func (x *EthereumTxRequest) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumTxRequest.ProtoReflect.Descriptor instead. +func (*EthereumTxRequest) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{5} +} + +func (x *EthereumTxRequest) GetDataLength() uint32 { + if x != nil && x.DataLength != nil { + return *x.DataLength + } + return 0 +} + +func (x *EthereumTxRequest) GetSignatureV() uint32 { + if x != nil && x.SignatureV != nil { + return *x.SignatureV + } + return 0 +} + +func (x *EthereumTxRequest) GetSignatureR() []byte { + if x != nil { + return x.SignatureR + } + return nil +} + +func (x *EthereumTxRequest) GetSignatureS() []byte { + if x != nil { + return x.SignatureS + } + return nil +} + +// * +// Request: Transaction payload data. +// @next EthereumTxRequest +type EthereumTxAck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DataChunk []byte `protobuf:"bytes,1,opt,name=data_chunk,json=dataChunk" json:"data_chunk,omitempty"` // Bytes from transaction payload (<= 1024 bytes) +} + +func (x *EthereumTxAck) Reset() { + *x = EthereumTxAck{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumTxAck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumTxAck) ProtoMessage() {} + +func (x *EthereumTxAck) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumTxAck.ProtoReflect.Descriptor instead. +func (*EthereumTxAck) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{6} +} + +func (x *EthereumTxAck) GetDataChunk() []byte { + if x != nil { + return x.DataChunk + } + return nil +} + +// * +// Request: Ask device to sign message +// @start +// @next EthereumMessageSignature +// @next Failure +type EthereumSignMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` // BIP-32 path to derive the key from master node + Message []byte `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` // message to be signed +} + +func (x *EthereumSignMessage) Reset() { + *x = EthereumSignMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumSignMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumSignMessage) ProtoMessage() {} + +func (x *EthereumSignMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumSignMessage.ProtoReflect.Descriptor instead. +func (*EthereumSignMessage) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{7} +} + +func (x *EthereumSignMessage) GetAddressN() []uint32 { + if x != nil { + return x.AddressN + } + return nil +} + +func (x *EthereumSignMessage) GetMessage() []byte { + if x != nil { + return x.Message + } + return nil +} + +// * +// Response: Signed message +// @end +type EthereumMessageSignature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AddressBin []byte `protobuf:"bytes,1,opt,name=addressBin" json:"addressBin,omitempty"` // address used to sign the message (20 bytes, legacy firmware) + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"` // signature of the message + AddressHex *string `protobuf:"bytes,3,opt,name=addressHex" json:"addressHex,omitempty"` // address used to sign the message (hex string, newer firmware) +} + +func (x *EthereumMessageSignature) Reset() { + *x = EthereumMessageSignature{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumMessageSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumMessageSignature) ProtoMessage() {} + +func (x *EthereumMessageSignature) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumMessageSignature.ProtoReflect.Descriptor instead. +func (*EthereumMessageSignature) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{8} +} + +func (x *EthereumMessageSignature) GetAddressBin() []byte { + if x != nil { + return x.AddressBin + } + return nil +} + +func (x *EthereumMessageSignature) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *EthereumMessageSignature) GetAddressHex() string { + if x != nil && x.AddressHex != nil { + return *x.AddressHex + } + return "" +} + +// * +// Request: Ask device to verify message +// @start +// @next Success +// @next Failure +type EthereumVerifyMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AddressBin []byte `protobuf:"bytes,1,opt,name=addressBin" json:"addressBin,omitempty"` // address to verify (20 bytes, legacy firmware) + Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"` // signature to verify + Message []byte `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"` // message to verify + AddressHex *string `protobuf:"bytes,4,opt,name=addressHex" json:"addressHex,omitempty"` // address to verify (hex string, newer firmware) +} + +func (x *EthereumVerifyMessage) Reset() { + *x = EthereumVerifyMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_ethereum_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EthereumVerifyMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EthereumVerifyMessage) ProtoMessage() {} + +func (x *EthereumVerifyMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_ethereum_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EthereumVerifyMessage.ProtoReflect.Descriptor instead. +func (*EthereumVerifyMessage) Descriptor() ([]byte, []int) { + return file_messages_ethereum_proto_rawDescGZIP(), []int{9} +} + +func (x *EthereumVerifyMessage) GetAddressBin() []byte { + if x != nil { + return x.AddressBin + } + return nil +} + +func (x *EthereumVerifyMessage) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *EthereumVerifyMessage) GetMessage() []byte { + if x != nil { + return x.Message + } + return nil +} + +func (x *EthereumVerifyMessage) GetAddressHex() string { + if x != nil && x.AddressHex != nil { + return *x.AddressHex + } + return "" +} + +var File_messages_ethereum_proto protoreflect.FileDescriptor + +var file_messages_ethereum_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2d, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x68, 0x77, 0x2e, 0x74, 0x72, + 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x1a, 0x15, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, + 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x56, 0x0a, + 0x14, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x5f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x08, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x4e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x77, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x62, 0x0a, 0x11, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x04, 0x6e, 0x6f, + 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x77, 0x2e, 0x74, 0x72, + 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x48, 0x44, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x70, 0x75, 0x62, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x78, 0x70, 0x75, 0x62, 0x22, 0x54, 0x0a, 0x12, 0x45, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0d, 0x52, 0x08, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4e, 0x12, 0x21, 0x0a, 0x0c, + 0x73, 0x68, 0x6f, 0x77, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, + 0x51, 0x0a, 0x0f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x69, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, + 0x69, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, + 0x65, 0x78, 0x22, 0xc2, 0x02, 0x0a, 0x0e, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x53, + 0x69, 0x67, 0x6e, 0x54, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x5f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x08, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x4e, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, + 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x61, 0x73, + 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x42, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x74, 0x6f, 0x42, 0x69, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x48, 0x65, + 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x48, 0x65, 0x78, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x10, 0x64, 0x61, 0x74, 0x61, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, + 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x4c, 0x65, 0x6e, + 0x67, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x17, + 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, 0x22, 0x97, 0x01, 0x0a, 0x11, 0x45, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x76, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x53, 0x22, 0x2e, 0x0a, 0x0d, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x54, 0x78, 0x41, + 0x63, 0x6b, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x64, 0x61, 0x74, 0x61, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x22, 0x4c, 0x0a, 0x13, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x53, 0x69, 0x67, + 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x08, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x4e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x78, 0x0a, 0x18, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0a, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x69, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, + 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x48, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x65, 0x78, 0x22, 0x8f, 0x01, 0x0a, 0x15, 0x45, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x69, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x42, 0x69, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x65, 0x78, 0x42, 0x77, 0x0a, 0x23, 0x63, + 0x6f, 0x6d, 0x2e, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x6c, 0x61, 0x62, 0x73, 0x2e, 0x74, + 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x42, 0x15, 0x54, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x67, + 0x6f, 0x2d, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x2f, 0x75, 0x73, 0x62, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x74, 0x72, + 0x65, 0x7a, 0x6f, 0x72, +} + +var ( + file_messages_ethereum_proto_rawDescOnce sync.Once + file_messages_ethereum_proto_rawDescData = file_messages_ethereum_proto_rawDesc +) + +func file_messages_ethereum_proto_rawDescGZIP() []byte { + file_messages_ethereum_proto_rawDescOnce.Do(func() { + file_messages_ethereum_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_ethereum_proto_rawDescData) + }) + return file_messages_ethereum_proto_rawDescData +} + +var file_messages_ethereum_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_messages_ethereum_proto_goTypes = []any{ + (*EthereumGetPublicKey)(nil), // 0: hw.trezor.messages.ethereum.EthereumGetPublicKey + (*EthereumPublicKey)(nil), // 1: hw.trezor.messages.ethereum.EthereumPublicKey + (*EthereumGetAddress)(nil), // 2: hw.trezor.messages.ethereum.EthereumGetAddress + (*EthereumAddress)(nil), // 3: hw.trezor.messages.ethereum.EthereumAddress + (*EthereumSignTx)(nil), // 4: hw.trezor.messages.ethereum.EthereumSignTx + (*EthereumTxRequest)(nil), // 5: hw.trezor.messages.ethereum.EthereumTxRequest + (*EthereumTxAck)(nil), // 6: hw.trezor.messages.ethereum.EthereumTxAck + (*EthereumSignMessage)(nil), // 7: hw.trezor.messages.ethereum.EthereumSignMessage + (*EthereumMessageSignature)(nil), // 8: hw.trezor.messages.ethereum.EthereumMessageSignature + (*EthereumVerifyMessage)(nil), // 9: hw.trezor.messages.ethereum.EthereumVerifyMessage + (*HDNodeType)(nil), // 10: hw.trezor.messages.common.HDNodeType +} +var file_messages_ethereum_proto_depIdxs = []int32{ + 10, // 0: hw.trezor.messages.ethereum.EthereumPublicKey.node:type_name -> hw.trezor.messages.common.HDNodeType + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_messages_ethereum_proto_init() } +func file_messages_ethereum_proto_init() { + if File_messages_ethereum_proto != nil { + return + } + file_messages_common_proto_init() + if !protoimpl.UnsafeEnabled { + file_messages_ethereum_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*EthereumGetPublicKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*EthereumPublicKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*EthereumGetAddress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*EthereumAddress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*EthereumSignTx); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*EthereumTxRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*EthereumTxAck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*EthereumSignMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*EthereumMessageSignature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_ethereum_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*EthereumVerifyMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_messages_ethereum_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_messages_ethereum_proto_goTypes, + DependencyIndexes: file_messages_ethereum_proto_depIdxs, + MessageInfos: file_messages_ethereum_proto_msgTypes, + }.Build() + File_messages_ethereum_proto = out.File + file_messages_ethereum_proto_rawDesc = nil + file_messages_ethereum_proto_goTypes = nil + file_messages_ethereum_proto_depIdxs = nil +} diff --git a/accounts/usbwallet/trezor/messages-ethereum.proto b/accounts/usbwallet/trezor/messages-ethereum.proto new file mode 100644 index 0000000..8e1150a --- /dev/null +++ b/accounts/usbwallet/trezor/messages-ethereum.proto @@ -0,0 +1,133 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum.proto +// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. + +syntax = "proto2"; +package hw.trezor.messages.ethereum; + +option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"; + +// Sugar for easier handling in Java +option java_package = "com.satoshilabs.trezor.lib.protobuf"; +option java_outer_classname = "TrezorMessageEthereum"; + +import "messages-common.proto"; + + +/** + * Request: Ask device for public key corresponding to address_n path + * @start + * @next EthereumPublicKey + * @next Failure + */ +message EthereumGetPublicKey { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bool show_display = 2; // optionally show on display before sending the result +} + +/** + * Response: Contains public key derived from device private seed + * @end + */ +message EthereumPublicKey { + optional hw.trezor.messages.common.HDNodeType node = 1; // BIP32 public node + optional string xpub = 2; // serialized form of public node +} + +/** + * Request: Ask device for Ethereum address corresponding to address_n path + * @start + * @next EthereumAddress + * @next Failure + */ +message EthereumGetAddress { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bool show_display = 2; // optionally show on display before sending the result +} + +/** + * Response: Contains an Ethereum address derived from device private seed + * @end + */ +message EthereumAddress { + optional bytes addressBin = 1; // Ethereum address as 20 bytes (legacy firmwares) + optional string addressHex = 2; // Ethereum address as hex string (newer firmwares) +} + +/** + * Request: Ask device to sign transaction + * All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing. + * Note: the first at most 1024 bytes of data MUST be transmitted as part of this message. + * @start + * @next EthereumTxRequest + * @next Failure + */ +message EthereumSignTx { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bytes nonce = 2; // <=256 bit unsigned big endian + optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei) + optional bytes gas_limit = 4; // <=256 bit unsigned big endian + optional bytes toBin = 5; // recipient address (20 bytes, legacy firmware) + optional string toHex = 11; // recipient address (hex string, newer firmware) + optional bytes value = 6; // <=256 bit unsigned big endian (in wei) + optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes) + optional uint32 data_length = 8; // Length of transaction payload + optional uint32 chain_id = 9; // Chain Id for EIP 155 + optional uint32 tx_type = 10; // (only for Wanchain) +} + +/** + * Response: Device asks for more data from transaction payload, or returns the signature. + * If data_length is set, device awaits that many more bytes of payload. + * Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present. + * @end + * @next EthereumTxAck + */ +message EthereumTxRequest { + optional uint32 data_length = 1; // Number of bytes being requested (<= 1024) + optional uint32 signature_v = 2; // Computed signature (recovery parameter, limited to 27 or 28) + optional bytes signature_r = 3; // Computed signature R component (256 bit) + optional bytes signature_s = 4; // Computed signature S component (256 bit) +} + +/** + * Request: Transaction payload data. + * @next EthereumTxRequest + */ +message EthereumTxAck { + optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes) +} + +/** + * Request: Ask device to sign message + * @start + * @next EthereumMessageSignature + * @next Failure + */ +message EthereumSignMessage { + repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node + optional bytes message = 2; // message to be signed +} + +/** + * Response: Signed message + * @end + */ +message EthereumMessageSignature { + optional bytes addressBin = 1; // address used to sign the message (20 bytes, legacy firmware) + optional bytes signature = 2; // signature of the message + optional string addressHex = 3; // address used to sign the message (hex string, newer firmware) +} + +/** + * Request: Ask device to verify message + * @start + * @next Success + * @next Failure + */ +message EthereumVerifyMessage { + optional bytes addressBin = 1; // address to verify (20 bytes, legacy firmware) + optional bytes signature = 2; // signature to verify + optional bytes message = 3; // message to verify + optional string addressHex = 4; // address to verify (hex string, newer firmware) +} diff --git a/accounts/usbwallet/trezor/messages-management.pb.go b/accounts/usbwallet/trezor/messages-management.pb.go new file mode 100644 index 0000000..983e2d2 --- /dev/null +++ b/accounts/usbwallet/trezor/messages-management.pb.go @@ -0,0 +1,2276 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages-management.proto +// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: messages-management.proto + +package trezor + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// * +// Structure representing passphrase source +type ApplySettings_PassphraseSourceType int32 + +const ( + ApplySettings_ASK ApplySettings_PassphraseSourceType = 0 + ApplySettings_DEVICE ApplySettings_PassphraseSourceType = 1 + ApplySettings_HOST ApplySettings_PassphraseSourceType = 2 +) + +// Enum value maps for ApplySettings_PassphraseSourceType. +var ( + ApplySettings_PassphraseSourceType_name = map[int32]string{ + 0: "ASK", + 1: "DEVICE", + 2: "HOST", + } + ApplySettings_PassphraseSourceType_value = map[string]int32{ + "ASK": 0, + "DEVICE": 1, + "HOST": 2, + } +) + +func (x ApplySettings_PassphraseSourceType) Enum() *ApplySettings_PassphraseSourceType { + p := new(ApplySettings_PassphraseSourceType) + *p = x + return p +} + +func (x ApplySettings_PassphraseSourceType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ApplySettings_PassphraseSourceType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_management_proto_enumTypes[0].Descriptor() +} + +func (ApplySettings_PassphraseSourceType) Type() protoreflect.EnumType { + return &file_messages_management_proto_enumTypes[0] +} + +func (x ApplySettings_PassphraseSourceType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *ApplySettings_PassphraseSourceType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = ApplySettings_PassphraseSourceType(num) + return nil +} + +// Deprecated: Use ApplySettings_PassphraseSourceType.Descriptor instead. +func (ApplySettings_PassphraseSourceType) EnumDescriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{4, 0} +} + +// * +// Type of recovery procedure. These should be used as bitmask, e.g., +// `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix` +// listing every method supported by the host computer. +// +// Note that ScrambledWords must be supported by every implementation +// for backward compatibility; there is no way to not support it. +type RecoveryDevice_RecoveryDeviceType int32 + +const ( + // use powers of two when extending this field + RecoveryDevice_RecoveryDeviceType_ScrambledWords RecoveryDevice_RecoveryDeviceType = 0 // words in scrambled order + RecoveryDevice_RecoveryDeviceType_Matrix RecoveryDevice_RecoveryDeviceType = 1 // matrix recovery type +) + +// Enum value maps for RecoveryDevice_RecoveryDeviceType. +var ( + RecoveryDevice_RecoveryDeviceType_name = map[int32]string{ + 0: "RecoveryDeviceType_ScrambledWords", + 1: "RecoveryDeviceType_Matrix", + } + RecoveryDevice_RecoveryDeviceType_value = map[string]int32{ + "RecoveryDeviceType_ScrambledWords": 0, + "RecoveryDeviceType_Matrix": 1, + } +) + +func (x RecoveryDevice_RecoveryDeviceType) Enum() *RecoveryDevice_RecoveryDeviceType { + p := new(RecoveryDevice_RecoveryDeviceType) + *p = x + return p +} + +func (x RecoveryDevice_RecoveryDeviceType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RecoveryDevice_RecoveryDeviceType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_management_proto_enumTypes[1].Descriptor() +} + +func (RecoveryDevice_RecoveryDeviceType) Type() protoreflect.EnumType { + return &file_messages_management_proto_enumTypes[1] +} + +func (x RecoveryDevice_RecoveryDeviceType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *RecoveryDevice_RecoveryDeviceType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = RecoveryDevice_RecoveryDeviceType(num) + return nil +} + +// Deprecated: Use RecoveryDevice_RecoveryDeviceType.Descriptor instead. +func (RecoveryDevice_RecoveryDeviceType) EnumDescriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{17, 0} +} + +// * +// Type of Recovery Word request +type WordRequest_WordRequestType int32 + +const ( + WordRequest_WordRequestType_Plain WordRequest_WordRequestType = 0 + WordRequest_WordRequestType_Matrix9 WordRequest_WordRequestType = 1 + WordRequest_WordRequestType_Matrix6 WordRequest_WordRequestType = 2 +) + +// Enum value maps for WordRequest_WordRequestType. +var ( + WordRequest_WordRequestType_name = map[int32]string{ + 0: "WordRequestType_Plain", + 1: "WordRequestType_Matrix9", + 2: "WordRequestType_Matrix6", + } + WordRequest_WordRequestType_value = map[string]int32{ + "WordRequestType_Plain": 0, + "WordRequestType_Matrix9": 1, + "WordRequestType_Matrix6": 2, + } +) + +func (x WordRequest_WordRequestType) Enum() *WordRequest_WordRequestType { + p := new(WordRequest_WordRequestType) + *p = x + return p +} + +func (x WordRequest_WordRequestType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WordRequest_WordRequestType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_management_proto_enumTypes[2].Descriptor() +} + +func (WordRequest_WordRequestType) Type() protoreflect.EnumType { + return &file_messages_management_proto_enumTypes[2] +} + +func (x WordRequest_WordRequestType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *WordRequest_WordRequestType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = WordRequest_WordRequestType(num) + return nil +} + +// Deprecated: Use WordRequest_WordRequestType.Descriptor instead. +func (WordRequest_WordRequestType) EnumDescriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{18, 0} +} + +// * +// Request: Reset device to default state and ask for device details +// @start +// @next Features +type Initialize struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + State []byte `protobuf:"bytes,1,opt,name=state" json:"state,omitempty"` // assumed device state, clear session if set and different + SkipPassphrase *bool `protobuf:"varint,2,opt,name=skip_passphrase,json=skipPassphrase" json:"skip_passphrase,omitempty"` // this session should always assume empty passphrase +} + +func (x *Initialize) Reset() { + *x = Initialize{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Initialize) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Initialize) ProtoMessage() {} + +func (x *Initialize) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Initialize.ProtoReflect.Descriptor instead. +func (*Initialize) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{0} +} + +func (x *Initialize) GetState() []byte { + if x != nil { + return x.State + } + return nil +} + +func (x *Initialize) GetSkipPassphrase() bool { + if x != nil && x.SkipPassphrase != nil { + return *x.SkipPassphrase + } + return false +} + +// * +// Request: Ask for device details (no device reset) +// @start +// @next Features +type GetFeatures struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetFeatures) Reset() { + *x = GetFeatures{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetFeatures) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFeatures) ProtoMessage() {} + +func (x *GetFeatures) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetFeatures.ProtoReflect.Descriptor instead. +func (*GetFeatures) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{1} +} + +// * +// Response: Reports various information about the device +// @end +type Features struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Vendor *string `protobuf:"bytes,1,opt,name=vendor" json:"vendor,omitempty"` // name of the manufacturer, e.g. "trezor.io" + MajorVersion *uint32 `protobuf:"varint,2,opt,name=major_version,json=majorVersion" json:"major_version,omitempty"` // major version of the firmware/bootloader, e.g. 1 + MinorVersion *uint32 `protobuf:"varint,3,opt,name=minor_version,json=minorVersion" json:"minor_version,omitempty"` // minor version of the firmware/bootloader, e.g. 0 + PatchVersion *uint32 `protobuf:"varint,4,opt,name=patch_version,json=patchVersion" json:"patch_version,omitempty"` // patch version of the firmware/bootloader, e.g. 0 + BootloaderMode *bool `protobuf:"varint,5,opt,name=bootloader_mode,json=bootloaderMode" json:"bootloader_mode,omitempty"` // is device in bootloader mode? + DeviceId *string `protobuf:"bytes,6,opt,name=device_id,json=deviceId" json:"device_id,omitempty"` // device's unique identifier + PinProtection *bool `protobuf:"varint,7,opt,name=pin_protection,json=pinProtection" json:"pin_protection,omitempty"` // is device protected by PIN? + PassphraseProtection *bool `protobuf:"varint,8,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` // is node/mnemonic encrypted using passphrase? + Language *string `protobuf:"bytes,9,opt,name=language" json:"language,omitempty"` // device language + Label *string `protobuf:"bytes,10,opt,name=label" json:"label,omitempty"` // device description label + Initialized *bool `protobuf:"varint,12,opt,name=initialized" json:"initialized,omitempty"` // does device contain seed? + Revision []byte `protobuf:"bytes,13,opt,name=revision" json:"revision,omitempty"` // SCM revision of firmware + BootloaderHash []byte `protobuf:"bytes,14,opt,name=bootloader_hash,json=bootloaderHash" json:"bootloader_hash,omitempty"` // hash of the bootloader + Imported *bool `protobuf:"varint,15,opt,name=imported" json:"imported,omitempty"` // was storage imported from an external source? + PinCached *bool `protobuf:"varint,16,opt,name=pin_cached,json=pinCached" json:"pin_cached,omitempty"` // is PIN already cached in session? + PassphraseCached *bool `protobuf:"varint,17,opt,name=passphrase_cached,json=passphraseCached" json:"passphrase_cached,omitempty"` // is passphrase already cached in session? + FirmwarePresent *bool `protobuf:"varint,18,opt,name=firmware_present,json=firmwarePresent" json:"firmware_present,omitempty"` // is valid firmware loaded? + NeedsBackup *bool `protobuf:"varint,19,opt,name=needs_backup,json=needsBackup" json:"needs_backup,omitempty"` // does storage need backup? (equals to Storage.needs_backup) + Flags *uint32 `protobuf:"varint,20,opt,name=flags" json:"flags,omitempty"` // device flags (equals to Storage.flags) + Model *string `protobuf:"bytes,21,opt,name=model" json:"model,omitempty"` // device hardware model + FwMajor *uint32 `protobuf:"varint,22,opt,name=fw_major,json=fwMajor" json:"fw_major,omitempty"` // reported firmware version if in bootloader mode + FwMinor *uint32 `protobuf:"varint,23,opt,name=fw_minor,json=fwMinor" json:"fw_minor,omitempty"` // reported firmware version if in bootloader mode + FwPatch *uint32 `protobuf:"varint,24,opt,name=fw_patch,json=fwPatch" json:"fw_patch,omitempty"` // reported firmware version if in bootloader mode + FwVendor *string `protobuf:"bytes,25,opt,name=fw_vendor,json=fwVendor" json:"fw_vendor,omitempty"` // reported firmware vendor if in bootloader mode + FwVendorKeys []byte `protobuf:"bytes,26,opt,name=fw_vendor_keys,json=fwVendorKeys" json:"fw_vendor_keys,omitempty"` // reported firmware vendor keys (their hash) + UnfinishedBackup *bool `protobuf:"varint,27,opt,name=unfinished_backup,json=unfinishedBackup" json:"unfinished_backup,omitempty"` // report unfinished backup (equals to Storage.unfinished_backup) + NoBackup *bool `protobuf:"varint,28,opt,name=no_backup,json=noBackup" json:"no_backup,omitempty"` // report no backup (equals to Storage.no_backup) +} + +func (x *Features) Reset() { + *x = Features{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Features) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Features) ProtoMessage() {} + +func (x *Features) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Features.ProtoReflect.Descriptor instead. +func (*Features) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{2} +} + +func (x *Features) GetVendor() string { + if x != nil && x.Vendor != nil { + return *x.Vendor + } + return "" +} + +func (x *Features) GetMajorVersion() uint32 { + if x != nil && x.MajorVersion != nil { + return *x.MajorVersion + } + return 0 +} + +func (x *Features) GetMinorVersion() uint32 { + if x != nil && x.MinorVersion != nil { + return *x.MinorVersion + } + return 0 +} + +func (x *Features) GetPatchVersion() uint32 { + if x != nil && x.PatchVersion != nil { + return *x.PatchVersion + } + return 0 +} + +func (x *Features) GetBootloaderMode() bool { + if x != nil && x.BootloaderMode != nil { + return *x.BootloaderMode + } + return false +} + +func (x *Features) GetDeviceId() string { + if x != nil && x.DeviceId != nil { + return *x.DeviceId + } + return "" +} + +func (x *Features) GetPinProtection() bool { + if x != nil && x.PinProtection != nil { + return *x.PinProtection + } + return false +} + +func (x *Features) GetPassphraseProtection() bool { + if x != nil && x.PassphraseProtection != nil { + return *x.PassphraseProtection + } + return false +} + +func (x *Features) GetLanguage() string { + if x != nil && x.Language != nil { + return *x.Language + } + return "" +} + +func (x *Features) GetLabel() string { + if x != nil && x.Label != nil { + return *x.Label + } + return "" +} + +func (x *Features) GetInitialized() bool { + if x != nil && x.Initialized != nil { + return *x.Initialized + } + return false +} + +func (x *Features) GetRevision() []byte { + if x != nil { + return x.Revision + } + return nil +} + +func (x *Features) GetBootloaderHash() []byte { + if x != nil { + return x.BootloaderHash + } + return nil +} + +func (x *Features) GetImported() bool { + if x != nil && x.Imported != nil { + return *x.Imported + } + return false +} + +func (x *Features) GetPinCached() bool { + if x != nil && x.PinCached != nil { + return *x.PinCached + } + return false +} + +func (x *Features) GetPassphraseCached() bool { + if x != nil && x.PassphraseCached != nil { + return *x.PassphraseCached + } + return false +} + +func (x *Features) GetFirmwarePresent() bool { + if x != nil && x.FirmwarePresent != nil { + return *x.FirmwarePresent + } + return false +} + +func (x *Features) GetNeedsBackup() bool { + if x != nil && x.NeedsBackup != nil { + return *x.NeedsBackup + } + return false +} + +func (x *Features) GetFlags() uint32 { + if x != nil && x.Flags != nil { + return *x.Flags + } + return 0 +} + +func (x *Features) GetModel() string { + if x != nil && x.Model != nil { + return *x.Model + } + return "" +} + +func (x *Features) GetFwMajor() uint32 { + if x != nil && x.FwMajor != nil { + return *x.FwMajor + } + return 0 +} + +func (x *Features) GetFwMinor() uint32 { + if x != nil && x.FwMinor != nil { + return *x.FwMinor + } + return 0 +} + +func (x *Features) GetFwPatch() uint32 { + if x != nil && x.FwPatch != nil { + return *x.FwPatch + } + return 0 +} + +func (x *Features) GetFwVendor() string { + if x != nil && x.FwVendor != nil { + return *x.FwVendor + } + return "" +} + +func (x *Features) GetFwVendorKeys() []byte { + if x != nil { + return x.FwVendorKeys + } + return nil +} + +func (x *Features) GetUnfinishedBackup() bool { + if x != nil && x.UnfinishedBackup != nil { + return *x.UnfinishedBackup + } + return false +} + +func (x *Features) GetNoBackup() bool { + if x != nil && x.NoBackup != nil { + return *x.NoBackup + } + return false +} + +// * +// Request: clear session (removes cached PIN, passphrase, etc). +// @start +// @next Success +type ClearSession struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ClearSession) Reset() { + *x = ClearSession{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClearSession) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearSession) ProtoMessage() {} + +func (x *ClearSession) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearSession.ProtoReflect.Descriptor instead. +func (*ClearSession) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{3} +} + +// * +// Request: change language and/or label of the device +// @start +// @next Success +// @next Failure +type ApplySettings struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Language *string `protobuf:"bytes,1,opt,name=language" json:"language,omitempty"` + Label *string `protobuf:"bytes,2,opt,name=label" json:"label,omitempty"` + UsePassphrase *bool `protobuf:"varint,3,opt,name=use_passphrase,json=usePassphrase" json:"use_passphrase,omitempty"` + Homescreen []byte `protobuf:"bytes,4,opt,name=homescreen" json:"homescreen,omitempty"` + PassphraseSource *ApplySettings_PassphraseSourceType `protobuf:"varint,5,opt,name=passphrase_source,json=passphraseSource,enum=hw.trezor.messages.management.ApplySettings_PassphraseSourceType" json:"passphrase_source,omitempty"` + AutoLockDelayMs *uint32 `protobuf:"varint,6,opt,name=auto_lock_delay_ms,json=autoLockDelayMs" json:"auto_lock_delay_ms,omitempty"` + DisplayRotation *uint32 `protobuf:"varint,7,opt,name=display_rotation,json=displayRotation" json:"display_rotation,omitempty"` // in degrees from North +} + +func (x *ApplySettings) Reset() { + *x = ApplySettings{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApplySettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplySettings) ProtoMessage() {} + +func (x *ApplySettings) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplySettings.ProtoReflect.Descriptor instead. +func (*ApplySettings) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{4} +} + +func (x *ApplySettings) GetLanguage() string { + if x != nil && x.Language != nil { + return *x.Language + } + return "" +} + +func (x *ApplySettings) GetLabel() string { + if x != nil && x.Label != nil { + return *x.Label + } + return "" +} + +func (x *ApplySettings) GetUsePassphrase() bool { + if x != nil && x.UsePassphrase != nil { + return *x.UsePassphrase + } + return false +} + +func (x *ApplySettings) GetHomescreen() []byte { + if x != nil { + return x.Homescreen + } + return nil +} + +func (x *ApplySettings) GetPassphraseSource() ApplySettings_PassphraseSourceType { + if x != nil && x.PassphraseSource != nil { + return *x.PassphraseSource + } + return ApplySettings_ASK +} + +func (x *ApplySettings) GetAutoLockDelayMs() uint32 { + if x != nil && x.AutoLockDelayMs != nil { + return *x.AutoLockDelayMs + } + return 0 +} + +func (x *ApplySettings) GetDisplayRotation() uint32 { + if x != nil && x.DisplayRotation != nil { + return *x.DisplayRotation + } + return 0 +} + +// * +// Request: set flags of the device +// @start +// @next Success +// @next Failure +type ApplyFlags struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Flags *uint32 `protobuf:"varint,1,opt,name=flags" json:"flags,omitempty"` // bitmask, can only set bits, not unset +} + +func (x *ApplyFlags) Reset() { + *x = ApplyFlags{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApplyFlags) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyFlags) ProtoMessage() {} + +func (x *ApplyFlags) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplyFlags.ProtoReflect.Descriptor instead. +func (*ApplyFlags) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{5} +} + +func (x *ApplyFlags) GetFlags() uint32 { + if x != nil && x.Flags != nil { + return *x.Flags + } + return 0 +} + +// * +// Request: Starts workflow for setting/changing/removing the PIN +// @start +// @next Success +// @next Failure +type ChangePin struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Remove *bool `protobuf:"varint,1,opt,name=remove" json:"remove,omitempty"` // is PIN removal requested? +} + +func (x *ChangePin) Reset() { + *x = ChangePin{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChangePin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangePin) ProtoMessage() {} + +func (x *ChangePin) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangePin.ProtoReflect.Descriptor instead. +func (*ChangePin) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{6} +} + +func (x *ChangePin) GetRemove() bool { + if x != nil && x.Remove != nil { + return *x.Remove + } + return false +} + +// * +// Request: Test if the device is alive, device sends back the message in Success response +// @start +// @next Success +type Ping struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message *string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` // message to send back in Success message + ButtonProtection *bool `protobuf:"varint,2,opt,name=button_protection,json=buttonProtection" json:"button_protection,omitempty"` // ask for button press + PinProtection *bool `protobuf:"varint,3,opt,name=pin_protection,json=pinProtection" json:"pin_protection,omitempty"` // ask for PIN if set in device + PassphraseProtection *bool `protobuf:"varint,4,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` // ask for passphrase if set in device +} + +func (x *Ping) Reset() { + *x = Ping{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Ping) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ping) ProtoMessage() {} + +func (x *Ping) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Ping.ProtoReflect.Descriptor instead. +func (*Ping) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{7} +} + +func (x *Ping) GetMessage() string { + if x != nil && x.Message != nil { + return *x.Message + } + return "" +} + +func (x *Ping) GetButtonProtection() bool { + if x != nil && x.ButtonProtection != nil { + return *x.ButtonProtection + } + return false +} + +func (x *Ping) GetPinProtection() bool { + if x != nil && x.PinProtection != nil { + return *x.PinProtection + } + return false +} + +func (x *Ping) GetPassphraseProtection() bool { + if x != nil && x.PassphraseProtection != nil { + return *x.PassphraseProtection + } + return false +} + +// * +// Request: Abort last operation that required user interaction +// @start +// @next Failure +type Cancel struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Cancel) Reset() { + *x = Cancel{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Cancel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Cancel) ProtoMessage() {} + +func (x *Cancel) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Cancel.ProtoReflect.Descriptor instead. +func (*Cancel) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{8} +} + +// * +// Request: Request a sample of random data generated by hardware RNG. May be used for testing. +// @start +// @next Entropy +// @next Failure +type GetEntropy struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Size *uint32 `protobuf:"varint,1,req,name=size" json:"size,omitempty"` // size of requested entropy +} + +func (x *GetEntropy) Reset() { + *x = GetEntropy{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetEntropy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetEntropy) ProtoMessage() {} + +func (x *GetEntropy) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetEntropy.ProtoReflect.Descriptor instead. +func (*GetEntropy) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{9} +} + +func (x *GetEntropy) GetSize() uint32 { + if x != nil && x.Size != nil { + return *x.Size + } + return 0 +} + +// * +// Response: Reply with random data generated by internal RNG +// @end +type Entropy struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entropy []byte `protobuf:"bytes,1,req,name=entropy" json:"entropy,omitempty"` // chunk of random generated bytes +} + +func (x *Entropy) Reset() { + *x = Entropy{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Entropy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Entropy) ProtoMessage() {} + +func (x *Entropy) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Entropy.ProtoReflect.Descriptor instead. +func (*Entropy) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{10} +} + +func (x *Entropy) GetEntropy() []byte { + if x != nil { + return x.Entropy + } + return nil +} + +// * +// Request: Request device to wipe all sensitive data and settings +// @start +// @next Success +// @next Failure +type WipeDevice struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *WipeDevice) Reset() { + *x = WipeDevice{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WipeDevice) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WipeDevice) ProtoMessage() {} + +func (x *WipeDevice) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WipeDevice.ProtoReflect.Descriptor instead. +func (*WipeDevice) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{11} +} + +// * +// Request: Load seed and related internal settings from the computer +// @start +// @next Success +// @next Failure +type LoadDevice struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mnemonic *string `protobuf:"bytes,1,opt,name=mnemonic" json:"mnemonic,omitempty"` // seed encoded as BIP-39 mnemonic (12, 18 or 24 words) + Node *HDNodeType `protobuf:"bytes,2,opt,name=node" json:"node,omitempty"` // BIP-32 node + Pin *string `protobuf:"bytes,3,opt,name=pin" json:"pin,omitempty"` // set PIN protection + PassphraseProtection *bool `protobuf:"varint,4,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` // enable master node encryption using passphrase + Language *string `protobuf:"bytes,5,opt,name=language,def=english" json:"language,omitempty"` // device language + Label *string `protobuf:"bytes,6,opt,name=label" json:"label,omitempty"` // device label + SkipChecksum *bool `protobuf:"varint,7,opt,name=skip_checksum,json=skipChecksum" json:"skip_checksum,omitempty"` // do not test mnemonic for valid BIP-39 checksum + U2FCounter *uint32 `protobuf:"varint,8,opt,name=u2f_counter,json=u2fCounter" json:"u2f_counter,omitempty"` // U2F counter +} + +// Default values for LoadDevice fields. +const ( + Default_LoadDevice_Language = string("english") +) + +func (x *LoadDevice) Reset() { + *x = LoadDevice{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoadDevice) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoadDevice) ProtoMessage() {} + +func (x *LoadDevice) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoadDevice.ProtoReflect.Descriptor instead. +func (*LoadDevice) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{12} +} + +func (x *LoadDevice) GetMnemonic() string { + if x != nil && x.Mnemonic != nil { + return *x.Mnemonic + } + return "" +} + +func (x *LoadDevice) GetNode() *HDNodeType { + if x != nil { + return x.Node + } + return nil +} + +func (x *LoadDevice) GetPin() string { + if x != nil && x.Pin != nil { + return *x.Pin + } + return "" +} + +func (x *LoadDevice) GetPassphraseProtection() bool { + if x != nil && x.PassphraseProtection != nil { + return *x.PassphraseProtection + } + return false +} + +func (x *LoadDevice) GetLanguage() string { + if x != nil && x.Language != nil { + return *x.Language + } + return Default_LoadDevice_Language +} + +func (x *LoadDevice) GetLabel() string { + if x != nil && x.Label != nil { + return *x.Label + } + return "" +} + +func (x *LoadDevice) GetSkipChecksum() bool { + if x != nil && x.SkipChecksum != nil { + return *x.SkipChecksum + } + return false +} + +func (x *LoadDevice) GetU2FCounter() uint32 { + if x != nil && x.U2FCounter != nil { + return *x.U2FCounter + } + return 0 +} + +// * +// Request: Ask device to do initialization involving user interaction +// @start +// @next EntropyRequest +// @next Failure +type ResetDevice struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DisplayRandom *bool `protobuf:"varint,1,opt,name=display_random,json=displayRandom" json:"display_random,omitempty"` // display entropy generated by the device before asking for additional entropy + Strength *uint32 `protobuf:"varint,2,opt,name=strength,def=256" json:"strength,omitempty"` // strength of seed in bits + PassphraseProtection *bool `protobuf:"varint,3,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` // enable master node encryption using passphrase + PinProtection *bool `protobuf:"varint,4,opt,name=pin_protection,json=pinProtection" json:"pin_protection,omitempty"` // enable PIN protection + Language *string `protobuf:"bytes,5,opt,name=language,def=english" json:"language,omitempty"` // device language + Label *string `protobuf:"bytes,6,opt,name=label" json:"label,omitempty"` // device label + U2FCounter *uint32 `protobuf:"varint,7,opt,name=u2f_counter,json=u2fCounter" json:"u2f_counter,omitempty"` // U2F counter + SkipBackup *bool `protobuf:"varint,8,opt,name=skip_backup,json=skipBackup" json:"skip_backup,omitempty"` // postpone seed backup to BackupDevice workflow + NoBackup *bool `protobuf:"varint,9,opt,name=no_backup,json=noBackup" json:"no_backup,omitempty"` // indicate that no backup is going to be made +} + +// Default values for ResetDevice fields. +const ( + Default_ResetDevice_Strength = uint32(256) + Default_ResetDevice_Language = string("english") +) + +func (x *ResetDevice) Reset() { + *x = ResetDevice{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResetDevice) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResetDevice) ProtoMessage() {} + +func (x *ResetDevice) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResetDevice.ProtoReflect.Descriptor instead. +func (*ResetDevice) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{13} +} + +func (x *ResetDevice) GetDisplayRandom() bool { + if x != nil && x.DisplayRandom != nil { + return *x.DisplayRandom + } + return false +} + +func (x *ResetDevice) GetStrength() uint32 { + if x != nil && x.Strength != nil { + return *x.Strength + } + return Default_ResetDevice_Strength +} + +func (x *ResetDevice) GetPassphraseProtection() bool { + if x != nil && x.PassphraseProtection != nil { + return *x.PassphraseProtection + } + return false +} + +func (x *ResetDevice) GetPinProtection() bool { + if x != nil && x.PinProtection != nil { + return *x.PinProtection + } + return false +} + +func (x *ResetDevice) GetLanguage() string { + if x != nil && x.Language != nil { + return *x.Language + } + return Default_ResetDevice_Language +} + +func (x *ResetDevice) GetLabel() string { + if x != nil && x.Label != nil { + return *x.Label + } + return "" +} + +func (x *ResetDevice) GetU2FCounter() uint32 { + if x != nil && x.U2FCounter != nil { + return *x.U2FCounter + } + return 0 +} + +func (x *ResetDevice) GetSkipBackup() bool { + if x != nil && x.SkipBackup != nil { + return *x.SkipBackup + } + return false +} + +func (x *ResetDevice) GetNoBackup() bool { + if x != nil && x.NoBackup != nil { + return *x.NoBackup + } + return false +} + +// * +// Request: Perform backup of the device seed if not backed up using ResetDevice +// @start +// @next Success +type BackupDevice struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *BackupDevice) Reset() { + *x = BackupDevice{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupDevice) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupDevice) ProtoMessage() {} + +func (x *BackupDevice) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupDevice.ProtoReflect.Descriptor instead. +func (*BackupDevice) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{14} +} + +// * +// Response: Ask for additional entropy from host computer +// @next EntropyAck +type EntropyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *EntropyRequest) Reset() { + *x = EntropyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EntropyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EntropyRequest) ProtoMessage() {} + +func (x *EntropyRequest) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EntropyRequest.ProtoReflect.Descriptor instead. +func (*EntropyRequest) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{15} +} + +// * +// Request: Provide additional entropy for seed generation function +// @next Success +type EntropyAck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entropy []byte `protobuf:"bytes,1,opt,name=entropy" json:"entropy,omitempty"` // 256 bits (32 bytes) of random data +} + +func (x *EntropyAck) Reset() { + *x = EntropyAck{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EntropyAck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EntropyAck) ProtoMessage() {} + +func (x *EntropyAck) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EntropyAck.ProtoReflect.Descriptor instead. +func (*EntropyAck) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{16} +} + +func (x *EntropyAck) GetEntropy() []byte { + if x != nil { + return x.Entropy + } + return nil +} + +// * +// Request: Start recovery workflow asking user for specific words of mnemonic +// Used to recovery device safely even on untrusted computer. +// @start +// @next WordRequest +type RecoveryDevice struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + WordCount *uint32 `protobuf:"varint,1,opt,name=word_count,json=wordCount" json:"word_count,omitempty"` // number of words in BIP-39 mnemonic + PassphraseProtection *bool `protobuf:"varint,2,opt,name=passphrase_protection,json=passphraseProtection" json:"passphrase_protection,omitempty"` // enable master node encryption using passphrase + PinProtection *bool `protobuf:"varint,3,opt,name=pin_protection,json=pinProtection" json:"pin_protection,omitempty"` // enable PIN protection + Language *string `protobuf:"bytes,4,opt,name=language,def=english" json:"language,omitempty"` // device language + Label *string `protobuf:"bytes,5,opt,name=label" json:"label,omitempty"` // device label + EnforceWordlist *bool `protobuf:"varint,6,opt,name=enforce_wordlist,json=enforceWordlist" json:"enforce_wordlist,omitempty"` // enforce BIP-39 wordlist during the process + // 7 reserved for unused recovery method + Type *RecoveryDevice_RecoveryDeviceType `protobuf:"varint,8,opt,name=type,enum=hw.trezor.messages.management.RecoveryDevice_RecoveryDeviceType" json:"type,omitempty"` // supported recovery type + U2FCounter *uint32 `protobuf:"varint,9,opt,name=u2f_counter,json=u2fCounter" json:"u2f_counter,omitempty"` // U2F counter + DryRun *bool `protobuf:"varint,10,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` // perform dry-run recovery workflow (for safe mnemonic validation) +} + +// Default values for RecoveryDevice fields. +const ( + Default_RecoveryDevice_Language = string("english") +) + +func (x *RecoveryDevice) Reset() { + *x = RecoveryDevice{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RecoveryDevice) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecoveryDevice) ProtoMessage() {} + +func (x *RecoveryDevice) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecoveryDevice.ProtoReflect.Descriptor instead. +func (*RecoveryDevice) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{17} +} + +func (x *RecoveryDevice) GetWordCount() uint32 { + if x != nil && x.WordCount != nil { + return *x.WordCount + } + return 0 +} + +func (x *RecoveryDevice) GetPassphraseProtection() bool { + if x != nil && x.PassphraseProtection != nil { + return *x.PassphraseProtection + } + return false +} + +func (x *RecoveryDevice) GetPinProtection() bool { + if x != nil && x.PinProtection != nil { + return *x.PinProtection + } + return false +} + +func (x *RecoveryDevice) GetLanguage() string { + if x != nil && x.Language != nil { + return *x.Language + } + return Default_RecoveryDevice_Language +} + +func (x *RecoveryDevice) GetLabel() string { + if x != nil && x.Label != nil { + return *x.Label + } + return "" +} + +func (x *RecoveryDevice) GetEnforceWordlist() bool { + if x != nil && x.EnforceWordlist != nil { + return *x.EnforceWordlist + } + return false +} + +func (x *RecoveryDevice) GetType() RecoveryDevice_RecoveryDeviceType { + if x != nil && x.Type != nil { + return *x.Type + } + return RecoveryDevice_RecoveryDeviceType_ScrambledWords +} + +func (x *RecoveryDevice) GetU2FCounter() uint32 { + if x != nil && x.U2FCounter != nil { + return *x.U2FCounter + } + return 0 +} + +func (x *RecoveryDevice) GetDryRun() bool { + if x != nil && x.DryRun != nil { + return *x.DryRun + } + return false +} + +// * +// Response: Device is waiting for user to enter word of the mnemonic +// Its position is shown only on device's internal display. +// @next WordAck +type WordRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type *WordRequest_WordRequestType `protobuf:"varint,1,opt,name=type,enum=hw.trezor.messages.management.WordRequest_WordRequestType" json:"type,omitempty"` +} + +func (x *WordRequest) Reset() { + *x = WordRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WordRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WordRequest) ProtoMessage() {} + +func (x *WordRequest) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WordRequest.ProtoReflect.Descriptor instead. +func (*WordRequest) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{18} +} + +func (x *WordRequest) GetType() WordRequest_WordRequestType { + if x != nil && x.Type != nil { + return *x.Type + } + return WordRequest_WordRequestType_Plain +} + +// * +// Request: Computer replies with word from the mnemonic +// @next WordRequest +// @next Success +// @next Failure +type WordAck struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Word *string `protobuf:"bytes,1,req,name=word" json:"word,omitempty"` // one word of mnemonic on asked position +} + +func (x *WordAck) Reset() { + *x = WordAck{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WordAck) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WordAck) ProtoMessage() {} + +func (x *WordAck) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WordAck.ProtoReflect.Descriptor instead. +func (*WordAck) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{19} +} + +func (x *WordAck) GetWord() string { + if x != nil && x.Word != nil { + return *x.Word + } + return "" +} + +// * +// Request: Set U2F counter +// @start +// @next Success +type SetU2FCounter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + U2FCounter *uint32 `protobuf:"varint,1,opt,name=u2f_counter,json=u2fCounter" json:"u2f_counter,omitempty"` // counter +} + +func (x *SetU2FCounter) Reset() { + *x = SetU2FCounter{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_management_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetU2FCounter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetU2FCounter) ProtoMessage() {} + +func (x *SetU2FCounter) ProtoReflect() protoreflect.Message { + mi := &file_messages_management_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetU2FCounter.ProtoReflect.Descriptor instead. +func (*SetU2FCounter) Descriptor() ([]byte, []int) { + return file_messages_management_proto_rawDescGZIP(), []int{20} +} + +func (x *SetU2FCounter) GetU2FCounter() uint32 { + if x != nil && x.U2FCounter != nil { + return *x.U2FCounter + } + return 0 +} + +var File_messages_management_proto protoreflect.FileDescriptor + +var file_messages_management_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, 0x68, 0x77, 0x2e, + 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x15, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x73, 0x2d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x4b, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x70, 0x61, + 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, + 0x73, 0x6b, 0x69, 0x70, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x22, 0x0d, + 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x8c, 0x07, + 0x0a, 0x08, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x65, + 0x6e, 0x64, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x76, 0x65, 0x6e, 0x64, + 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x6a, 0x6f, 0x72, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x6f, 0x72, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, + 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, + 0x70, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x70, 0x61, 0x74, 0x63, 0x68, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x6f, 0x6f, 0x74, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x5f, + 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x62, 0x6f, 0x6f, 0x74, + 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x69, 0x6e, 0x5f, 0x70, + 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0d, 0x70, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, + 0x0a, 0x15, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x70, + 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x62, 0x6f, 0x6f, 0x74, 0x6c, 0x6f, 0x61, 0x64, 0x65, + 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x62, 0x6f, + 0x6f, 0x74, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, + 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, + 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x69, 0x6e, 0x5f, + 0x63, 0x61, 0x63, 0x68, 0x65, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x69, + 0x6e, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x61, 0x73, 0x73, 0x70, + 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x10, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, + 0x5f, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, + 0x66, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, + 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, + 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x19, + 0x0a, 0x08, 0x66, 0x77, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x66, 0x77, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x77, 0x5f, + 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x66, 0x77, 0x4d, + 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x77, 0x5f, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x18, 0x18, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x66, 0x77, 0x50, 0x61, 0x74, 0x63, 0x68, 0x12, + 0x1b, 0x0a, 0x09, 0x66, 0x77, 0x5f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x18, 0x19, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x66, 0x77, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x12, 0x24, 0x0a, 0x0e, + 0x66, 0x77, 0x5f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x1a, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x66, 0x77, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x4b, 0x65, + 0x79, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, + 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x75, + 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, + 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x1c, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x0e, 0x0a, 0x0c, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x87, 0x03, 0x0a, + 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x1a, + 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x12, 0x25, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, + 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x50, 0x61, 0x73, + 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x6f, 0x6d, 0x65, 0x73, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x68, 0x6f, 0x6d, + 0x65, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x12, 0x6e, 0x0a, 0x11, 0x70, 0x61, 0x73, 0x73, 0x70, + 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x41, 0x2e, 0x68, 0x77, 0x2e, 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x2e, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x10, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, + 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x6f, 0x5f, + 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x61, 0x75, 0x74, 0x6f, 0x4c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x6c, + 0x61, 0x79, 0x4d, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, + 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0x35, 0x0a, 0x14, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x53, 0x4b, 0x10, 0x00, + 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, + 0x48, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x22, 0x22, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x46, + 0x6c, 0x61, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x23, 0x0a, 0x09, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x50, 0x69, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, + 0xa9, 0x01, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x62, + 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x25, 0x0a, 0x0e, 0x70, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x70, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x74, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x15, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, + 0x72, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, + 0x65, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x08, 0x0a, 0x06, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x22, 0x20, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, + 0x6f, 0x70, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x02, 0x28, + 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x23, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x6f, + 0x70, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x18, 0x01, 0x20, + 0x02, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x22, 0x0c, 0x0a, 0x0a, + 0x57, 0x69, 0x70, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0xab, 0x02, 0x0a, 0x0a, 0x4c, + 0x6f, 0x61, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x6e, 0x65, + 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6e, 0x65, + 0x6d, 0x6f, 0x6e, 0x69, 0x63, 0x12, 0x39, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x68, 0x77, 0x2e, 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x48, 0x44, 0x4e, 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, + 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x70, + 0x69, 0x6e, 0x12, 0x33, 0x0a, 0x15, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, + 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x14, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x50, 0x72, 0x6f, + 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x3a, 0x07, 0x65, 0x6e, 0x67, 0x6c, 0x69, + 0x73, 0x68, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x73, 0x75, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x32, 0x66, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x75, 0x32, + 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x22, 0xcb, 0x02, 0x0a, 0x0b, 0x52, 0x65, 0x73, + 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x5f, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x52, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x12, + 0x1f, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x3a, 0x03, 0x32, 0x35, 0x36, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x6e, 0x67, 0x74, 0x68, + 0x12, 0x33, 0x0a, 0x15, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x70, + 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x14, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x6f, + 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x70, + 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x08, + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x3a, 0x07, + 0x65, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x32, 0x66, 0x5f, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x75, 0x32, + 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6b, 0x69, 0x70, + 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, + 0x6b, 0x69, 0x70, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x5f, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x22, 0x0e, 0x0a, 0x0c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x10, 0x0a, 0x0e, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x26, 0x0a, 0x0a, 0x45, 0x6e, 0x74, 0x72, + 0x6f, 0x70, 0x79, 0x41, 0x63, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, + 0x22, 0xdd, 0x03, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x77, 0x6f, 0x72, 0x64, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x33, 0x0a, 0x15, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, + 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x14, 0x70, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x50, 0x72, 0x6f, + 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x69, 0x6e, 0x5f, 0x70, + 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0d, 0x70, 0x69, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x23, + 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x3a, 0x07, 0x65, 0x6e, 0x67, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x65, 0x6e, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x77, 0x6f, 0x72, 0x64, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0f, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x57, 0x6f, 0x72, 0x64, + 0x6c, 0x69, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x40, 0x2e, 0x68, 0x77, 0x2e, 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x32, + 0x66, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0a, 0x75, 0x32, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x64, + 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, + 0x79, 0x52, 0x75, 0x6e, 0x22, 0x5a, 0x0a, 0x12, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x21, 0x52, 0x65, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x53, 0x63, 0x72, 0x61, 0x6d, 0x62, 0x6c, 0x65, 0x64, 0x57, 0x6f, 0x72, 0x64, 0x73, 0x10, + 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x10, 0x01, + 0x22, 0xc5, 0x01, 0x0a, 0x0b, 0x57, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x4e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3a, + 0x2e, 0x68, 0x77, 0x2e, 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x57, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x57, 0x6f, 0x72, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x66, 0x0a, 0x0f, 0x57, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x57, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x10, 0x00, 0x12, 0x1b, + 0x0a, 0x17, 0x57, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x39, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x57, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, + 0x61, 0x74, 0x72, 0x69, 0x78, 0x36, 0x10, 0x02, 0x22, 0x1d, 0x0a, 0x07, 0x57, 0x6f, 0x72, 0x64, + 0x41, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x02, 0x28, + 0x09, 0x52, 0x04, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x30, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x55, 0x32, + 0x46, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x32, 0x66, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x75, + 0x32, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x42, 0x79, 0x0a, 0x23, 0x63, 0x6f, 0x6d, + 0x2e, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x6c, 0x61, 0x62, 0x73, 0x2e, 0x74, 0x72, 0x65, + 0x7a, 0x6f, 0x72, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x42, 0x17, 0x54, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x67, + 0x6f, 0x2d, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x2f, 0x75, 0x73, 0x62, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x74, 0x72, + 0x65, 0x7a, 0x6f, 0x72, +} + +var ( + file_messages_management_proto_rawDescOnce sync.Once + file_messages_management_proto_rawDescData = file_messages_management_proto_rawDesc +) + +func file_messages_management_proto_rawDescGZIP() []byte { + file_messages_management_proto_rawDescOnce.Do(func() { + file_messages_management_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_management_proto_rawDescData) + }) + return file_messages_management_proto_rawDescData +} + +var file_messages_management_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_messages_management_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_messages_management_proto_goTypes = []any{ + (ApplySettings_PassphraseSourceType)(0), // 0: hw.trezor.messages.management.ApplySettings.PassphraseSourceType + (RecoveryDevice_RecoveryDeviceType)(0), // 1: hw.trezor.messages.management.RecoveryDevice.RecoveryDeviceType + (WordRequest_WordRequestType)(0), // 2: hw.trezor.messages.management.WordRequest.WordRequestType + (*Initialize)(nil), // 3: hw.trezor.messages.management.Initialize + (*GetFeatures)(nil), // 4: hw.trezor.messages.management.GetFeatures + (*Features)(nil), // 5: hw.trezor.messages.management.Features + (*ClearSession)(nil), // 6: hw.trezor.messages.management.ClearSession + (*ApplySettings)(nil), // 7: hw.trezor.messages.management.ApplySettings + (*ApplyFlags)(nil), // 8: hw.trezor.messages.management.ApplyFlags + (*ChangePin)(nil), // 9: hw.trezor.messages.management.ChangePin + (*Ping)(nil), // 10: hw.trezor.messages.management.Ping + (*Cancel)(nil), // 11: hw.trezor.messages.management.Cancel + (*GetEntropy)(nil), // 12: hw.trezor.messages.management.GetEntropy + (*Entropy)(nil), // 13: hw.trezor.messages.management.Entropy + (*WipeDevice)(nil), // 14: hw.trezor.messages.management.WipeDevice + (*LoadDevice)(nil), // 15: hw.trezor.messages.management.LoadDevice + (*ResetDevice)(nil), // 16: hw.trezor.messages.management.ResetDevice + (*BackupDevice)(nil), // 17: hw.trezor.messages.management.BackupDevice + (*EntropyRequest)(nil), // 18: hw.trezor.messages.management.EntropyRequest + (*EntropyAck)(nil), // 19: hw.trezor.messages.management.EntropyAck + (*RecoveryDevice)(nil), // 20: hw.trezor.messages.management.RecoveryDevice + (*WordRequest)(nil), // 21: hw.trezor.messages.management.WordRequest + (*WordAck)(nil), // 22: hw.trezor.messages.management.WordAck + (*SetU2FCounter)(nil), // 23: hw.trezor.messages.management.SetU2FCounter + (*HDNodeType)(nil), // 24: hw.trezor.messages.common.HDNodeType +} +var file_messages_management_proto_depIdxs = []int32{ + 0, // 0: hw.trezor.messages.management.ApplySettings.passphrase_source:type_name -> hw.trezor.messages.management.ApplySettings.PassphraseSourceType + 24, // 1: hw.trezor.messages.management.LoadDevice.node:type_name -> hw.trezor.messages.common.HDNodeType + 1, // 2: hw.trezor.messages.management.RecoveryDevice.type:type_name -> hw.trezor.messages.management.RecoveryDevice.RecoveryDeviceType + 2, // 3: hw.trezor.messages.management.WordRequest.type:type_name -> hw.trezor.messages.management.WordRequest.WordRequestType + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_messages_management_proto_init() } +func file_messages_management_proto_init() { + if File_messages_management_proto != nil { + return + } + file_messages_common_proto_init() + if !protoimpl.UnsafeEnabled { + file_messages_management_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Initialize); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*GetFeatures); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*Features); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*ClearSession); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*ApplySettings); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*ApplyFlags); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*ChangePin); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*Ping); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*Cancel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*GetEntropy); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*Entropy); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*WipeDevice); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[12].Exporter = func(v any, i int) any { + switch v := v.(*LoadDevice); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*ResetDevice); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*BackupDevice); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[15].Exporter = func(v any, i int) any { + switch v := v.(*EntropyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[16].Exporter = func(v any, i int) any { + switch v := v.(*EntropyAck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[17].Exporter = func(v any, i int) any { + switch v := v.(*RecoveryDevice); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[18].Exporter = func(v any, i int) any { + switch v := v.(*WordRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[19].Exporter = func(v any, i int) any { + switch v := v.(*WordAck); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_management_proto_msgTypes[20].Exporter = func(v any, i int) any { + switch v := v.(*SetU2FCounter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_messages_management_proto_rawDesc, + NumEnums: 3, + NumMessages: 21, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_messages_management_proto_goTypes, + DependencyIndexes: file_messages_management_proto_depIdxs, + EnumInfos: file_messages_management_proto_enumTypes, + MessageInfos: file_messages_management_proto_msgTypes, + }.Build() + File_messages_management_proto = out.File + file_messages_management_proto_rawDesc = nil + file_messages_management_proto_goTypes = nil + file_messages_management_proto_depIdxs = nil +} diff --git a/accounts/usbwallet/trezor/messages-management.proto b/accounts/usbwallet/trezor/messages-management.proto new file mode 100644 index 0000000..55eb589 --- /dev/null +++ b/accounts/usbwallet/trezor/messages-management.proto @@ -0,0 +1,291 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages-management.proto +// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. + +syntax = "proto2"; +package hw.trezor.messages.management; + +option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"; + +// Sugar for easier handling in Java +option java_package = "com.satoshilabs.trezor.lib.protobuf"; +option java_outer_classname = "TrezorMessageManagement"; + +import "messages-common.proto"; + +/** + * Request: Reset device to default state and ask for device details + * @start + * @next Features + */ +message Initialize { + optional bytes state = 1; // assumed device state, clear session if set and different + optional bool skip_passphrase = 2; // this session should always assume empty passphrase +} + +/** + * Request: Ask for device details (no device reset) + * @start + * @next Features + */ +message GetFeatures { +} + +/** + * Response: Reports various information about the device + * @end + */ +message Features { + optional string vendor = 1; // name of the manufacturer, e.g. "trezor.io" + optional uint32 major_version = 2; // major version of the firmware/bootloader, e.g. 1 + optional uint32 minor_version = 3; // minor version of the firmware/bootloader, e.g. 0 + optional uint32 patch_version = 4; // patch version of the firmware/bootloader, e.g. 0 + optional bool bootloader_mode = 5; // is device in bootloader mode? + optional string device_id = 6; // device's unique identifier + optional bool pin_protection = 7; // is device protected by PIN? + optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase? + optional string language = 9; // device language + optional string label = 10; // device description label + optional bool initialized = 12; // does device contain seed? + optional bytes revision = 13; // SCM revision of firmware + optional bytes bootloader_hash = 14; // hash of the bootloader + optional bool imported = 15; // was storage imported from an external source? + optional bool pin_cached = 16; // is PIN already cached in session? + optional bool passphrase_cached = 17; // is passphrase already cached in session? + optional bool firmware_present = 18; // is valid firmware loaded? + optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup) + optional uint32 flags = 20; // device flags (equals to Storage.flags) + optional string model = 21; // device hardware model + optional uint32 fw_major = 22; // reported firmware version if in bootloader mode + optional uint32 fw_minor = 23; // reported firmware version if in bootloader mode + optional uint32 fw_patch = 24; // reported firmware version if in bootloader mode + optional string fw_vendor = 25; // reported firmware vendor if in bootloader mode + optional bytes fw_vendor_keys = 26; // reported firmware vendor keys (their hash) + optional bool unfinished_backup = 27; // report unfinished backup (equals to Storage.unfinished_backup) + optional bool no_backup = 28; // report no backup (equals to Storage.no_backup) +} + +/** + * Request: clear session (removes cached PIN, passphrase, etc). + * @start + * @next Success + */ +message ClearSession { +} + +/** + * Request: change language and/or label of the device + * @start + * @next Success + * @next Failure + */ +message ApplySettings { + optional string language = 1; + optional string label = 2; + optional bool use_passphrase = 3; + optional bytes homescreen = 4; + optional PassphraseSourceType passphrase_source = 5; + optional uint32 auto_lock_delay_ms = 6; + optional uint32 display_rotation = 7; // in degrees from North + /** + * Structure representing passphrase source + */ + enum PassphraseSourceType { + ASK = 0; + DEVICE = 1; + HOST = 2; + } +} + +/** + * Request: set flags of the device + * @start + * @next Success + * @next Failure + */ +message ApplyFlags { + optional uint32 flags = 1; // bitmask, can only set bits, not unset +} + +/** + * Request: Starts workflow for setting/changing/removing the PIN + * @start + * @next Success + * @next Failure + */ +message ChangePin { + optional bool remove = 1; // is PIN removal requested? +} + +/** + * Request: Test if the device is alive, device sends back the message in Success response + * @start + * @next Success + */ +message Ping { + optional string message = 1; // message to send back in Success message + optional bool button_protection = 2; // ask for button press + optional bool pin_protection = 3; // ask for PIN if set in device + optional bool passphrase_protection = 4; // ask for passphrase if set in device +} + +/** + * Request: Abort last operation that required user interaction + * @start + * @next Failure + */ +message Cancel { +} + +/** + * Request: Request a sample of random data generated by hardware RNG. May be used for testing. + * @start + * @next Entropy + * @next Failure + */ +message GetEntropy { + required uint32 size = 1; // size of requested entropy +} + +/** + * Response: Reply with random data generated by internal RNG + * @end + */ +message Entropy { + required bytes entropy = 1; // chunk of random generated bytes +} + +/** + * Request: Request device to wipe all sensitive data and settings + * @start + * @next Success + * @next Failure + */ +message WipeDevice { +} + +/** + * Request: Load seed and related internal settings from the computer + * @start + * @next Success + * @next Failure + */ +message LoadDevice { + optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words) + optional hw.trezor.messages.common.HDNodeType node = 2; // BIP-32 node + optional string pin = 3; // set PIN protection + optional bool passphrase_protection = 4; // enable master node encryption using passphrase + optional string language = 5 [default='english']; // device language + optional string label = 6; // device label + optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum + optional uint32 u2f_counter = 8; // U2F counter +} + +/** + * Request: Ask device to do initialization involving user interaction + * @start + * @next EntropyRequest + * @next Failure + */ +message ResetDevice { + optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy + optional uint32 strength = 2 [default=256]; // strength of seed in bits + optional bool passphrase_protection = 3; // enable master node encryption using passphrase + optional bool pin_protection = 4; // enable PIN protection + optional string language = 5 [default='english']; // device language + optional string label = 6; // device label + optional uint32 u2f_counter = 7; // U2F counter + optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow + optional bool no_backup = 9; // indicate that no backup is going to be made +} + +/** + * Request: Perform backup of the device seed if not backed up using ResetDevice + * @start + * @next Success + */ +message BackupDevice { +} + +/** + * Response: Ask for additional entropy from host computer + * @next EntropyAck + */ +message EntropyRequest { +} + +/** + * Request: Provide additional entropy for seed generation function + * @next Success + */ +message EntropyAck { + optional bytes entropy = 1; // 256 bits (32 bytes) of random data +} + +/** + * Request: Start recovery workflow asking user for specific words of mnemonic + * Used to recovery device safely even on untrusted computer. + * @start + * @next WordRequest + */ +message RecoveryDevice { + optional uint32 word_count = 1; // number of words in BIP-39 mnemonic + optional bool passphrase_protection = 2; // enable master node encryption using passphrase + optional bool pin_protection = 3; // enable PIN protection + optional string language = 4 [default='english']; // device language + optional string label = 5; // device label + optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process + // 7 reserved for unused recovery method + optional RecoveryDeviceType type = 8; // supported recovery type + optional uint32 u2f_counter = 9; // U2F counter + optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation) + /** + * Type of recovery procedure. These should be used as bitmask, e.g., + * `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix` + * listing every method supported by the host computer. + * + * Note that ScrambledWords must be supported by every implementation + * for backward compatibility; there is no way to not support it. + */ + enum RecoveryDeviceType { + // use powers of two when extending this field + RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order + RecoveryDeviceType_Matrix = 1; // matrix recovery type + } +} + +/** + * Response: Device is waiting for user to enter word of the mnemonic + * Its position is shown only on device's internal display. + * @next WordAck + */ +message WordRequest { + optional WordRequestType type = 1; + /** + * Type of Recovery Word request + */ + enum WordRequestType { + WordRequestType_Plain = 0; + WordRequestType_Matrix9 = 1; + WordRequestType_Matrix6 = 2; + } +} + +/** + * Request: Computer replies with word from the mnemonic + * @next WordRequest + * @next Success + * @next Failure + */ +message WordAck { + required string word = 1; // one word of mnemonic on asked position +} + +/** + * Request: Set U2F counter + * @start + * @next Success + */ +message SetU2FCounter { + optional uint32 u2f_counter = 1; // counter +} diff --git a/accounts/usbwallet/trezor/messages.pb.go b/accounts/usbwallet/trezor/messages.pb.go new file mode 100644 index 0000000..4518db6 --- /dev/null +++ b/accounts/usbwallet/trezor/messages.pb.go @@ -0,0 +1,1366 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto +// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.1 +// source: messages.proto + +package trezor + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// * +// Mapping between TREZOR wire identifier (uint) and a protobuf message +type MessageType int32 + +const ( + // Management + MessageType_MessageType_Initialize MessageType = 0 + MessageType_MessageType_Ping MessageType = 1 + MessageType_MessageType_Success MessageType = 2 + MessageType_MessageType_Failure MessageType = 3 + MessageType_MessageType_ChangePin MessageType = 4 + MessageType_MessageType_WipeDevice MessageType = 5 + MessageType_MessageType_GetEntropy MessageType = 9 + MessageType_MessageType_Entropy MessageType = 10 + MessageType_MessageType_LoadDevice MessageType = 13 + MessageType_MessageType_ResetDevice MessageType = 14 + MessageType_MessageType_Features MessageType = 17 + MessageType_MessageType_PinMatrixRequest MessageType = 18 + MessageType_MessageType_PinMatrixAck MessageType = 19 + MessageType_MessageType_Cancel MessageType = 20 + MessageType_MessageType_ClearSession MessageType = 24 + MessageType_MessageType_ApplySettings MessageType = 25 + MessageType_MessageType_ButtonRequest MessageType = 26 + MessageType_MessageType_ButtonAck MessageType = 27 + MessageType_MessageType_ApplyFlags MessageType = 28 + MessageType_MessageType_BackupDevice MessageType = 34 + MessageType_MessageType_EntropyRequest MessageType = 35 + MessageType_MessageType_EntropyAck MessageType = 36 + MessageType_MessageType_PassphraseRequest MessageType = 41 + MessageType_MessageType_PassphraseAck MessageType = 42 + MessageType_MessageType_PassphraseStateRequest MessageType = 77 + MessageType_MessageType_PassphraseStateAck MessageType = 78 + MessageType_MessageType_RecoveryDevice MessageType = 45 + MessageType_MessageType_WordRequest MessageType = 46 + MessageType_MessageType_WordAck MessageType = 47 + MessageType_MessageType_GetFeatures MessageType = 55 + MessageType_MessageType_SetU2FCounter MessageType = 63 + // Bootloader + MessageType_MessageType_FirmwareErase MessageType = 6 + MessageType_MessageType_FirmwareUpload MessageType = 7 + MessageType_MessageType_FirmwareRequest MessageType = 8 + MessageType_MessageType_SelfTest MessageType = 32 + // Bitcoin + MessageType_MessageType_GetPublicKey MessageType = 11 + MessageType_MessageType_PublicKey MessageType = 12 + MessageType_MessageType_SignTx MessageType = 15 + MessageType_MessageType_TxRequest MessageType = 21 + MessageType_MessageType_TxAck MessageType = 22 + MessageType_MessageType_GetAddress MessageType = 29 + MessageType_MessageType_Address MessageType = 30 + MessageType_MessageType_SignMessage MessageType = 38 + MessageType_MessageType_VerifyMessage MessageType = 39 + MessageType_MessageType_MessageSignature MessageType = 40 + // Crypto + MessageType_MessageType_CipherKeyValue MessageType = 23 + MessageType_MessageType_CipheredKeyValue MessageType = 48 + MessageType_MessageType_SignIdentity MessageType = 53 + MessageType_MessageType_SignedIdentity MessageType = 54 + MessageType_MessageType_GetECDHSessionKey MessageType = 61 + MessageType_MessageType_ECDHSessionKey MessageType = 62 + MessageType_MessageType_CosiCommit MessageType = 71 + MessageType_MessageType_CosiCommitment MessageType = 72 + MessageType_MessageType_CosiSign MessageType = 73 + MessageType_MessageType_CosiSignature MessageType = 74 + // Debug + MessageType_MessageType_DebugLinkDecision MessageType = 100 + MessageType_MessageType_DebugLinkGetState MessageType = 101 + MessageType_MessageType_DebugLinkState MessageType = 102 + MessageType_MessageType_DebugLinkStop MessageType = 103 + MessageType_MessageType_DebugLinkLog MessageType = 104 + MessageType_MessageType_DebugLinkMemoryRead MessageType = 110 + MessageType_MessageType_DebugLinkMemory MessageType = 111 + MessageType_MessageType_DebugLinkMemoryWrite MessageType = 112 + MessageType_MessageType_DebugLinkFlashErase MessageType = 113 + // Ethereum + MessageType_MessageType_EthereumGetPublicKey MessageType = 450 + MessageType_MessageType_EthereumPublicKey MessageType = 451 + MessageType_MessageType_EthereumGetAddress MessageType = 56 + MessageType_MessageType_EthereumAddress MessageType = 57 + MessageType_MessageType_EthereumSignTx MessageType = 58 + MessageType_MessageType_EthereumTxRequest MessageType = 59 + MessageType_MessageType_EthereumTxAck MessageType = 60 + MessageType_MessageType_EthereumSignMessage MessageType = 64 + MessageType_MessageType_EthereumVerifyMessage MessageType = 65 + MessageType_MessageType_EthereumMessageSignature MessageType = 66 + // NEM + MessageType_MessageType_NEMGetAddress MessageType = 67 + MessageType_MessageType_NEMAddress MessageType = 68 + MessageType_MessageType_NEMSignTx MessageType = 69 + MessageType_MessageType_NEMSignedTx MessageType = 70 + MessageType_MessageType_NEMDecryptMessage MessageType = 75 + MessageType_MessageType_NEMDecryptedMessage MessageType = 76 + // Lisk + MessageType_MessageType_LiskGetAddress MessageType = 114 + MessageType_MessageType_LiskAddress MessageType = 115 + MessageType_MessageType_LiskSignTx MessageType = 116 + MessageType_MessageType_LiskSignedTx MessageType = 117 + MessageType_MessageType_LiskSignMessage MessageType = 118 + MessageType_MessageType_LiskMessageSignature MessageType = 119 + MessageType_MessageType_LiskVerifyMessage MessageType = 120 + MessageType_MessageType_LiskGetPublicKey MessageType = 121 + MessageType_MessageType_LiskPublicKey MessageType = 122 + // Tezos + MessageType_MessageType_TezosGetAddress MessageType = 150 + MessageType_MessageType_TezosAddress MessageType = 151 + MessageType_MessageType_TezosSignTx MessageType = 152 + MessageType_MessageType_TezosSignedTx MessageType = 153 + MessageType_MessageType_TezosGetPublicKey MessageType = 154 + MessageType_MessageType_TezosPublicKey MessageType = 155 + // Stellar + MessageType_MessageType_StellarSignTx MessageType = 202 + MessageType_MessageType_StellarTxOpRequest MessageType = 203 + MessageType_MessageType_StellarGetAddress MessageType = 207 + MessageType_MessageType_StellarAddress MessageType = 208 + MessageType_MessageType_StellarCreateAccountOp MessageType = 210 + MessageType_MessageType_StellarPaymentOp MessageType = 211 + MessageType_MessageType_StellarPathPaymentOp MessageType = 212 + MessageType_MessageType_StellarManageOfferOp MessageType = 213 + MessageType_MessageType_StellarCreatePassiveOfferOp MessageType = 214 + MessageType_MessageType_StellarSetOptionsOp MessageType = 215 + MessageType_MessageType_StellarChangeTrustOp MessageType = 216 + MessageType_MessageType_StellarAllowTrustOp MessageType = 217 + MessageType_MessageType_StellarAccountMergeOp MessageType = 218 + // omitted: StellarInflationOp is not a supported operation, would be 219 + MessageType_MessageType_StellarManageDataOp MessageType = 220 + MessageType_MessageType_StellarBumpSequenceOp MessageType = 221 + MessageType_MessageType_StellarSignedTx MessageType = 230 + // TRON + MessageType_MessageType_TronGetAddress MessageType = 250 + MessageType_MessageType_TronAddress MessageType = 251 + MessageType_MessageType_TronSignTx MessageType = 252 + MessageType_MessageType_TronSignedTx MessageType = 253 + // Cardano + // dropped Sign/VerifyMessage ids 300-302 + MessageType_MessageType_CardanoSignTx MessageType = 303 + MessageType_MessageType_CardanoTxRequest MessageType = 304 + MessageType_MessageType_CardanoGetPublicKey MessageType = 305 + MessageType_MessageType_CardanoPublicKey MessageType = 306 + MessageType_MessageType_CardanoGetAddress MessageType = 307 + MessageType_MessageType_CardanoAddress MessageType = 308 + MessageType_MessageType_CardanoTxAck MessageType = 309 + MessageType_MessageType_CardanoSignedTx MessageType = 310 + // Ontology + MessageType_MessageType_OntologyGetAddress MessageType = 350 + MessageType_MessageType_OntologyAddress MessageType = 351 + MessageType_MessageType_OntologyGetPublicKey MessageType = 352 + MessageType_MessageType_OntologyPublicKey MessageType = 353 + MessageType_MessageType_OntologySignTransfer MessageType = 354 + MessageType_MessageType_OntologySignedTransfer MessageType = 355 + MessageType_MessageType_OntologySignWithdrawOng MessageType = 356 + MessageType_MessageType_OntologySignedWithdrawOng MessageType = 357 + MessageType_MessageType_OntologySignOntIdRegister MessageType = 358 + MessageType_MessageType_OntologySignedOntIdRegister MessageType = 359 + MessageType_MessageType_OntologySignOntIdAddAttributes MessageType = 360 + MessageType_MessageType_OntologySignedOntIdAddAttributes MessageType = 361 + // Ripple + MessageType_MessageType_RippleGetAddress MessageType = 400 + MessageType_MessageType_RippleAddress MessageType = 401 + MessageType_MessageType_RippleSignTx MessageType = 402 + MessageType_MessageType_RippleSignedTx MessageType = 403 + // Monero + MessageType_MessageType_MoneroTransactionInitRequest MessageType = 501 + MessageType_MessageType_MoneroTransactionInitAck MessageType = 502 + MessageType_MessageType_MoneroTransactionSetInputRequest MessageType = 503 + MessageType_MessageType_MoneroTransactionSetInputAck MessageType = 504 + MessageType_MessageType_MoneroTransactionInputsPermutationRequest MessageType = 505 + MessageType_MessageType_MoneroTransactionInputsPermutationAck MessageType = 506 + MessageType_MessageType_MoneroTransactionInputViniRequest MessageType = 507 + MessageType_MessageType_MoneroTransactionInputViniAck MessageType = 508 + MessageType_MessageType_MoneroTransactionAllInputsSetRequest MessageType = 509 + MessageType_MessageType_MoneroTransactionAllInputsSetAck MessageType = 510 + MessageType_MessageType_MoneroTransactionSetOutputRequest MessageType = 511 + MessageType_MessageType_MoneroTransactionSetOutputAck MessageType = 512 + MessageType_MessageType_MoneroTransactionAllOutSetRequest MessageType = 513 + MessageType_MessageType_MoneroTransactionAllOutSetAck MessageType = 514 + MessageType_MessageType_MoneroTransactionSignInputRequest MessageType = 515 + MessageType_MessageType_MoneroTransactionSignInputAck MessageType = 516 + MessageType_MessageType_MoneroTransactionFinalRequest MessageType = 517 + MessageType_MessageType_MoneroTransactionFinalAck MessageType = 518 + MessageType_MessageType_MoneroKeyImageExportInitRequest MessageType = 530 + MessageType_MessageType_MoneroKeyImageExportInitAck MessageType = 531 + MessageType_MessageType_MoneroKeyImageSyncStepRequest MessageType = 532 + MessageType_MessageType_MoneroKeyImageSyncStepAck MessageType = 533 + MessageType_MessageType_MoneroKeyImageSyncFinalRequest MessageType = 534 + MessageType_MessageType_MoneroKeyImageSyncFinalAck MessageType = 535 + MessageType_MessageType_MoneroGetAddress MessageType = 540 + MessageType_MessageType_MoneroAddress MessageType = 541 + MessageType_MessageType_MoneroGetWatchKey MessageType = 542 + MessageType_MessageType_MoneroWatchKey MessageType = 543 + MessageType_MessageType_DebugMoneroDiagRequest MessageType = 546 + MessageType_MessageType_DebugMoneroDiagAck MessageType = 547 + MessageType_MessageType_MoneroGetTxKeyRequest MessageType = 550 + MessageType_MessageType_MoneroGetTxKeyAck MessageType = 551 + MessageType_MessageType_MoneroLiveRefreshStartRequest MessageType = 552 + MessageType_MessageType_MoneroLiveRefreshStartAck MessageType = 553 + MessageType_MessageType_MoneroLiveRefreshStepRequest MessageType = 554 + MessageType_MessageType_MoneroLiveRefreshStepAck MessageType = 555 + MessageType_MessageType_MoneroLiveRefreshFinalRequest MessageType = 556 + MessageType_MessageType_MoneroLiveRefreshFinalAck MessageType = 557 + // EOS + MessageType_MessageType_EosGetPublicKey MessageType = 600 + MessageType_MessageType_EosPublicKey MessageType = 601 + MessageType_MessageType_EosSignTx MessageType = 602 + MessageType_MessageType_EosTxActionRequest MessageType = 603 + MessageType_MessageType_EosTxActionAck MessageType = 604 + MessageType_MessageType_EosSignedTx MessageType = 605 + // Binance + MessageType_MessageType_BinanceGetAddress MessageType = 700 + MessageType_MessageType_BinanceAddress MessageType = 701 + MessageType_MessageType_BinanceGetPublicKey MessageType = 702 + MessageType_MessageType_BinancePublicKey MessageType = 703 + MessageType_MessageType_BinanceSignTx MessageType = 704 + MessageType_MessageType_BinanceTxRequest MessageType = 705 + MessageType_MessageType_BinanceTransferMsg MessageType = 706 + MessageType_MessageType_BinanceOrderMsg MessageType = 707 + MessageType_MessageType_BinanceCancelMsg MessageType = 708 + MessageType_MessageType_BinanceSignedTx MessageType = 709 +) + +// Enum value maps for MessageType. +var ( + MessageType_name = map[int32]string{ + 0: "MessageType_Initialize", + 1: "MessageType_Ping", + 2: "MessageType_Success", + 3: "MessageType_Failure", + 4: "MessageType_ChangePin", + 5: "MessageType_WipeDevice", + 9: "MessageType_GetEntropy", + 10: "MessageType_Entropy", + 13: "MessageType_LoadDevice", + 14: "MessageType_ResetDevice", + 17: "MessageType_Features", + 18: "MessageType_PinMatrixRequest", + 19: "MessageType_PinMatrixAck", + 20: "MessageType_Cancel", + 24: "MessageType_ClearSession", + 25: "MessageType_ApplySettings", + 26: "MessageType_ButtonRequest", + 27: "MessageType_ButtonAck", + 28: "MessageType_ApplyFlags", + 34: "MessageType_BackupDevice", + 35: "MessageType_EntropyRequest", + 36: "MessageType_EntropyAck", + 41: "MessageType_PassphraseRequest", + 42: "MessageType_PassphraseAck", + 77: "MessageType_PassphraseStateRequest", + 78: "MessageType_PassphraseStateAck", + 45: "MessageType_RecoveryDevice", + 46: "MessageType_WordRequest", + 47: "MessageType_WordAck", + 55: "MessageType_GetFeatures", + 63: "MessageType_SetU2FCounter", + 6: "MessageType_FirmwareErase", + 7: "MessageType_FirmwareUpload", + 8: "MessageType_FirmwareRequest", + 32: "MessageType_SelfTest", + 11: "MessageType_GetPublicKey", + 12: "MessageType_PublicKey", + 15: "MessageType_SignTx", + 21: "MessageType_TxRequest", + 22: "MessageType_TxAck", + 29: "MessageType_GetAddress", + 30: "MessageType_Address", + 38: "MessageType_SignMessage", + 39: "MessageType_VerifyMessage", + 40: "MessageType_MessageSignature", + 23: "MessageType_CipherKeyValue", + 48: "MessageType_CipheredKeyValue", + 53: "MessageType_SignIdentity", + 54: "MessageType_SignedIdentity", + 61: "MessageType_GetECDHSessionKey", + 62: "MessageType_ECDHSessionKey", + 71: "MessageType_CosiCommit", + 72: "MessageType_CosiCommitment", + 73: "MessageType_CosiSign", + 74: "MessageType_CosiSignature", + 100: "MessageType_DebugLinkDecision", + 101: "MessageType_DebugLinkGetState", + 102: "MessageType_DebugLinkState", + 103: "MessageType_DebugLinkStop", + 104: "MessageType_DebugLinkLog", + 110: "MessageType_DebugLinkMemoryRead", + 111: "MessageType_DebugLinkMemory", + 112: "MessageType_DebugLinkMemoryWrite", + 113: "MessageType_DebugLinkFlashErase", + 450: "MessageType_EthereumGetPublicKey", + 451: "MessageType_EthereumPublicKey", + 56: "MessageType_EthereumGetAddress", + 57: "MessageType_EthereumAddress", + 58: "MessageType_EthereumSignTx", + 59: "MessageType_EthereumTxRequest", + 60: "MessageType_EthereumTxAck", + 64: "MessageType_EthereumSignMessage", + 65: "MessageType_EthereumVerifyMessage", + 66: "MessageType_EthereumMessageSignature", + 67: "MessageType_NEMGetAddress", + 68: "MessageType_NEMAddress", + 69: "MessageType_NEMSignTx", + 70: "MessageType_NEMSignedTx", + 75: "MessageType_NEMDecryptMessage", + 76: "MessageType_NEMDecryptedMessage", + 114: "MessageType_LiskGetAddress", + 115: "MessageType_LiskAddress", + 116: "MessageType_LiskSignTx", + 117: "MessageType_LiskSignedTx", + 118: "MessageType_LiskSignMessage", + 119: "MessageType_LiskMessageSignature", + 120: "MessageType_LiskVerifyMessage", + 121: "MessageType_LiskGetPublicKey", + 122: "MessageType_LiskPublicKey", + 150: "MessageType_TezosGetAddress", + 151: "MessageType_TezosAddress", + 152: "MessageType_TezosSignTx", + 153: "MessageType_TezosSignedTx", + 154: "MessageType_TezosGetPublicKey", + 155: "MessageType_TezosPublicKey", + 202: "MessageType_StellarSignTx", + 203: "MessageType_StellarTxOpRequest", + 207: "MessageType_StellarGetAddress", + 208: "MessageType_StellarAddress", + 210: "MessageType_StellarCreateAccountOp", + 211: "MessageType_StellarPaymentOp", + 212: "MessageType_StellarPathPaymentOp", + 213: "MessageType_StellarManageOfferOp", + 214: "MessageType_StellarCreatePassiveOfferOp", + 215: "MessageType_StellarSetOptionsOp", + 216: "MessageType_StellarChangeTrustOp", + 217: "MessageType_StellarAllowTrustOp", + 218: "MessageType_StellarAccountMergeOp", + 220: "MessageType_StellarManageDataOp", + 221: "MessageType_StellarBumpSequenceOp", + 230: "MessageType_StellarSignedTx", + 250: "MessageType_TronGetAddress", + 251: "MessageType_TronAddress", + 252: "MessageType_TronSignTx", + 253: "MessageType_TronSignedTx", + 303: "MessageType_CardanoSignTx", + 304: "MessageType_CardanoTxRequest", + 305: "MessageType_CardanoGetPublicKey", + 306: "MessageType_CardanoPublicKey", + 307: "MessageType_CardanoGetAddress", + 308: "MessageType_CardanoAddress", + 309: "MessageType_CardanoTxAck", + 310: "MessageType_CardanoSignedTx", + 350: "MessageType_OntologyGetAddress", + 351: "MessageType_OntologyAddress", + 352: "MessageType_OntologyGetPublicKey", + 353: "MessageType_OntologyPublicKey", + 354: "MessageType_OntologySignTransfer", + 355: "MessageType_OntologySignedTransfer", + 356: "MessageType_OntologySignWithdrawOng", + 357: "MessageType_OntologySignedWithdrawOng", + 358: "MessageType_OntologySignOntIdRegister", + 359: "MessageType_OntologySignedOntIdRegister", + 360: "MessageType_OntologySignOntIdAddAttributes", + 361: "MessageType_OntologySignedOntIdAddAttributes", + 400: "MessageType_RippleGetAddress", + 401: "MessageType_RippleAddress", + 402: "MessageType_RippleSignTx", + 403: "MessageType_RippleSignedTx", + 501: "MessageType_MoneroTransactionInitRequest", + 502: "MessageType_MoneroTransactionInitAck", + 503: "MessageType_MoneroTransactionSetInputRequest", + 504: "MessageType_MoneroTransactionSetInputAck", + 505: "MessageType_MoneroTransactionInputsPermutationRequest", + 506: "MessageType_MoneroTransactionInputsPermutationAck", + 507: "MessageType_MoneroTransactionInputViniRequest", + 508: "MessageType_MoneroTransactionInputViniAck", + 509: "MessageType_MoneroTransactionAllInputsSetRequest", + 510: "MessageType_MoneroTransactionAllInputsSetAck", + 511: "MessageType_MoneroTransactionSetOutputRequest", + 512: "MessageType_MoneroTransactionSetOutputAck", + 513: "MessageType_MoneroTransactionAllOutSetRequest", + 514: "MessageType_MoneroTransactionAllOutSetAck", + 515: "MessageType_MoneroTransactionSignInputRequest", + 516: "MessageType_MoneroTransactionSignInputAck", + 517: "MessageType_MoneroTransactionFinalRequest", + 518: "MessageType_MoneroTransactionFinalAck", + 530: "MessageType_MoneroKeyImageExportInitRequest", + 531: "MessageType_MoneroKeyImageExportInitAck", + 532: "MessageType_MoneroKeyImageSyncStepRequest", + 533: "MessageType_MoneroKeyImageSyncStepAck", + 534: "MessageType_MoneroKeyImageSyncFinalRequest", + 535: "MessageType_MoneroKeyImageSyncFinalAck", + 540: "MessageType_MoneroGetAddress", + 541: "MessageType_MoneroAddress", + 542: "MessageType_MoneroGetWatchKey", + 543: "MessageType_MoneroWatchKey", + 546: "MessageType_DebugMoneroDiagRequest", + 547: "MessageType_DebugMoneroDiagAck", + 550: "MessageType_MoneroGetTxKeyRequest", + 551: "MessageType_MoneroGetTxKeyAck", + 552: "MessageType_MoneroLiveRefreshStartRequest", + 553: "MessageType_MoneroLiveRefreshStartAck", + 554: "MessageType_MoneroLiveRefreshStepRequest", + 555: "MessageType_MoneroLiveRefreshStepAck", + 556: "MessageType_MoneroLiveRefreshFinalRequest", + 557: "MessageType_MoneroLiveRefreshFinalAck", + 600: "MessageType_EosGetPublicKey", + 601: "MessageType_EosPublicKey", + 602: "MessageType_EosSignTx", + 603: "MessageType_EosTxActionRequest", + 604: "MessageType_EosTxActionAck", + 605: "MessageType_EosSignedTx", + 700: "MessageType_BinanceGetAddress", + 701: "MessageType_BinanceAddress", + 702: "MessageType_BinanceGetPublicKey", + 703: "MessageType_BinancePublicKey", + 704: "MessageType_BinanceSignTx", + 705: "MessageType_BinanceTxRequest", + 706: "MessageType_BinanceTransferMsg", + 707: "MessageType_BinanceOrderMsg", + 708: "MessageType_BinanceCancelMsg", + 709: "MessageType_BinanceSignedTx", + } + MessageType_value = map[string]int32{ + "MessageType_Initialize": 0, + "MessageType_Ping": 1, + "MessageType_Success": 2, + "MessageType_Failure": 3, + "MessageType_ChangePin": 4, + "MessageType_WipeDevice": 5, + "MessageType_GetEntropy": 9, + "MessageType_Entropy": 10, + "MessageType_LoadDevice": 13, + "MessageType_ResetDevice": 14, + "MessageType_Features": 17, + "MessageType_PinMatrixRequest": 18, + "MessageType_PinMatrixAck": 19, + "MessageType_Cancel": 20, + "MessageType_ClearSession": 24, + "MessageType_ApplySettings": 25, + "MessageType_ButtonRequest": 26, + "MessageType_ButtonAck": 27, + "MessageType_ApplyFlags": 28, + "MessageType_BackupDevice": 34, + "MessageType_EntropyRequest": 35, + "MessageType_EntropyAck": 36, + "MessageType_PassphraseRequest": 41, + "MessageType_PassphraseAck": 42, + "MessageType_PassphraseStateRequest": 77, + "MessageType_PassphraseStateAck": 78, + "MessageType_RecoveryDevice": 45, + "MessageType_WordRequest": 46, + "MessageType_WordAck": 47, + "MessageType_GetFeatures": 55, + "MessageType_SetU2FCounter": 63, + "MessageType_FirmwareErase": 6, + "MessageType_FirmwareUpload": 7, + "MessageType_FirmwareRequest": 8, + "MessageType_SelfTest": 32, + "MessageType_GetPublicKey": 11, + "MessageType_PublicKey": 12, + "MessageType_SignTx": 15, + "MessageType_TxRequest": 21, + "MessageType_TxAck": 22, + "MessageType_GetAddress": 29, + "MessageType_Address": 30, + "MessageType_SignMessage": 38, + "MessageType_VerifyMessage": 39, + "MessageType_MessageSignature": 40, + "MessageType_CipherKeyValue": 23, + "MessageType_CipheredKeyValue": 48, + "MessageType_SignIdentity": 53, + "MessageType_SignedIdentity": 54, + "MessageType_GetECDHSessionKey": 61, + "MessageType_ECDHSessionKey": 62, + "MessageType_CosiCommit": 71, + "MessageType_CosiCommitment": 72, + "MessageType_CosiSign": 73, + "MessageType_CosiSignature": 74, + "MessageType_DebugLinkDecision": 100, + "MessageType_DebugLinkGetState": 101, + "MessageType_DebugLinkState": 102, + "MessageType_DebugLinkStop": 103, + "MessageType_DebugLinkLog": 104, + "MessageType_DebugLinkMemoryRead": 110, + "MessageType_DebugLinkMemory": 111, + "MessageType_DebugLinkMemoryWrite": 112, + "MessageType_DebugLinkFlashErase": 113, + "MessageType_EthereumGetPublicKey": 450, + "MessageType_EthereumPublicKey": 451, + "MessageType_EthereumGetAddress": 56, + "MessageType_EthereumAddress": 57, + "MessageType_EthereumSignTx": 58, + "MessageType_EthereumTxRequest": 59, + "MessageType_EthereumTxAck": 60, + "MessageType_EthereumSignMessage": 64, + "MessageType_EthereumVerifyMessage": 65, + "MessageType_EthereumMessageSignature": 66, + "MessageType_NEMGetAddress": 67, + "MessageType_NEMAddress": 68, + "MessageType_NEMSignTx": 69, + "MessageType_NEMSignedTx": 70, + "MessageType_NEMDecryptMessage": 75, + "MessageType_NEMDecryptedMessage": 76, + "MessageType_LiskGetAddress": 114, + "MessageType_LiskAddress": 115, + "MessageType_LiskSignTx": 116, + "MessageType_LiskSignedTx": 117, + "MessageType_LiskSignMessage": 118, + "MessageType_LiskMessageSignature": 119, + "MessageType_LiskVerifyMessage": 120, + "MessageType_LiskGetPublicKey": 121, + "MessageType_LiskPublicKey": 122, + "MessageType_TezosGetAddress": 150, + "MessageType_TezosAddress": 151, + "MessageType_TezosSignTx": 152, + "MessageType_TezosSignedTx": 153, + "MessageType_TezosGetPublicKey": 154, + "MessageType_TezosPublicKey": 155, + "MessageType_StellarSignTx": 202, + "MessageType_StellarTxOpRequest": 203, + "MessageType_StellarGetAddress": 207, + "MessageType_StellarAddress": 208, + "MessageType_StellarCreateAccountOp": 210, + "MessageType_StellarPaymentOp": 211, + "MessageType_StellarPathPaymentOp": 212, + "MessageType_StellarManageOfferOp": 213, + "MessageType_StellarCreatePassiveOfferOp": 214, + "MessageType_StellarSetOptionsOp": 215, + "MessageType_StellarChangeTrustOp": 216, + "MessageType_StellarAllowTrustOp": 217, + "MessageType_StellarAccountMergeOp": 218, + "MessageType_StellarManageDataOp": 220, + "MessageType_StellarBumpSequenceOp": 221, + "MessageType_StellarSignedTx": 230, + "MessageType_TronGetAddress": 250, + "MessageType_TronAddress": 251, + "MessageType_TronSignTx": 252, + "MessageType_TronSignedTx": 253, + "MessageType_CardanoSignTx": 303, + "MessageType_CardanoTxRequest": 304, + "MessageType_CardanoGetPublicKey": 305, + "MessageType_CardanoPublicKey": 306, + "MessageType_CardanoGetAddress": 307, + "MessageType_CardanoAddress": 308, + "MessageType_CardanoTxAck": 309, + "MessageType_CardanoSignedTx": 310, + "MessageType_OntologyGetAddress": 350, + "MessageType_OntologyAddress": 351, + "MessageType_OntologyGetPublicKey": 352, + "MessageType_OntologyPublicKey": 353, + "MessageType_OntologySignTransfer": 354, + "MessageType_OntologySignedTransfer": 355, + "MessageType_OntologySignWithdrawOng": 356, + "MessageType_OntologySignedWithdrawOng": 357, + "MessageType_OntologySignOntIdRegister": 358, + "MessageType_OntologySignedOntIdRegister": 359, + "MessageType_OntologySignOntIdAddAttributes": 360, + "MessageType_OntologySignedOntIdAddAttributes": 361, + "MessageType_RippleGetAddress": 400, + "MessageType_RippleAddress": 401, + "MessageType_RippleSignTx": 402, + "MessageType_RippleSignedTx": 403, + "MessageType_MoneroTransactionInitRequest": 501, + "MessageType_MoneroTransactionInitAck": 502, + "MessageType_MoneroTransactionSetInputRequest": 503, + "MessageType_MoneroTransactionSetInputAck": 504, + "MessageType_MoneroTransactionInputsPermutationRequest": 505, + "MessageType_MoneroTransactionInputsPermutationAck": 506, + "MessageType_MoneroTransactionInputViniRequest": 507, + "MessageType_MoneroTransactionInputViniAck": 508, + "MessageType_MoneroTransactionAllInputsSetRequest": 509, + "MessageType_MoneroTransactionAllInputsSetAck": 510, + "MessageType_MoneroTransactionSetOutputRequest": 511, + "MessageType_MoneroTransactionSetOutputAck": 512, + "MessageType_MoneroTransactionAllOutSetRequest": 513, + "MessageType_MoneroTransactionAllOutSetAck": 514, + "MessageType_MoneroTransactionSignInputRequest": 515, + "MessageType_MoneroTransactionSignInputAck": 516, + "MessageType_MoneroTransactionFinalRequest": 517, + "MessageType_MoneroTransactionFinalAck": 518, + "MessageType_MoneroKeyImageExportInitRequest": 530, + "MessageType_MoneroKeyImageExportInitAck": 531, + "MessageType_MoneroKeyImageSyncStepRequest": 532, + "MessageType_MoneroKeyImageSyncStepAck": 533, + "MessageType_MoneroKeyImageSyncFinalRequest": 534, + "MessageType_MoneroKeyImageSyncFinalAck": 535, + "MessageType_MoneroGetAddress": 540, + "MessageType_MoneroAddress": 541, + "MessageType_MoneroGetWatchKey": 542, + "MessageType_MoneroWatchKey": 543, + "MessageType_DebugMoneroDiagRequest": 546, + "MessageType_DebugMoneroDiagAck": 547, + "MessageType_MoneroGetTxKeyRequest": 550, + "MessageType_MoneroGetTxKeyAck": 551, + "MessageType_MoneroLiveRefreshStartRequest": 552, + "MessageType_MoneroLiveRefreshStartAck": 553, + "MessageType_MoneroLiveRefreshStepRequest": 554, + "MessageType_MoneroLiveRefreshStepAck": 555, + "MessageType_MoneroLiveRefreshFinalRequest": 556, + "MessageType_MoneroLiveRefreshFinalAck": 557, + "MessageType_EosGetPublicKey": 600, + "MessageType_EosPublicKey": 601, + "MessageType_EosSignTx": 602, + "MessageType_EosTxActionRequest": 603, + "MessageType_EosTxActionAck": 604, + "MessageType_EosSignedTx": 605, + "MessageType_BinanceGetAddress": 700, + "MessageType_BinanceAddress": 701, + "MessageType_BinanceGetPublicKey": 702, + "MessageType_BinancePublicKey": 703, + "MessageType_BinanceSignTx": 704, + "MessageType_BinanceTxRequest": 705, + "MessageType_BinanceTransferMsg": 706, + "MessageType_BinanceOrderMsg": 707, + "MessageType_BinanceCancelMsg": 708, + "MessageType_BinanceSignedTx": 709, + } +) + +func (x MessageType) Enum() *MessageType { + p := new(MessageType) + *p = x + return p +} + +func (x MessageType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (MessageType) Descriptor() protoreflect.EnumDescriptor { + return file_messages_proto_enumTypes[0].Descriptor() +} + +func (MessageType) Type() protoreflect.EnumType { + return &file_messages_proto_enumTypes[0] +} + +func (x MessageType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Do not use. +func (x *MessageType) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) + if err != nil { + return err + } + *x = MessageType(num) + return nil +} + +// Deprecated: Use MessageType.Descriptor instead. +func (MessageType) EnumDescriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{0} +} + +var file_messages_proto_extTypes = []protoimpl.ExtensionInfo{ + { + ExtendedType: (*descriptorpb.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50002, + Name: "hw.trezor.messages.wire_in", + Tag: "varint,50002,opt,name=wire_in", + Filename: "messages.proto", + }, + { + ExtendedType: (*descriptorpb.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50003, + Name: "hw.trezor.messages.wire_out", + Tag: "varint,50003,opt,name=wire_out", + Filename: "messages.proto", + }, + { + ExtendedType: (*descriptorpb.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50004, + Name: "hw.trezor.messages.wire_debug_in", + Tag: "varint,50004,opt,name=wire_debug_in", + Filename: "messages.proto", + }, + { + ExtendedType: (*descriptorpb.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50005, + Name: "hw.trezor.messages.wire_debug_out", + Tag: "varint,50005,opt,name=wire_debug_out", + Filename: "messages.proto", + }, + { + ExtendedType: (*descriptorpb.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50006, + Name: "hw.trezor.messages.wire_tiny", + Tag: "varint,50006,opt,name=wire_tiny", + Filename: "messages.proto", + }, + { + ExtendedType: (*descriptorpb.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50007, + Name: "hw.trezor.messages.wire_bootloader", + Tag: "varint,50007,opt,name=wire_bootloader", + Filename: "messages.proto", + }, + { + ExtendedType: (*descriptorpb.EnumValueOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50008, + Name: "hw.trezor.messages.wire_no_fsm", + Tag: "varint,50008,opt,name=wire_no_fsm", + Filename: "messages.proto", + }, +} + +// Extension fields to descriptorpb.EnumValueOptions. +var ( + // optional bool wire_in = 50002; + E_WireIn = &file_messages_proto_extTypes[0] // message can be transmitted via wire from PC to TREZOR + // optional bool wire_out = 50003; + E_WireOut = &file_messages_proto_extTypes[1] // message can be transmitted via wire from TREZOR to PC + // optional bool wire_debug_in = 50004; + E_WireDebugIn = &file_messages_proto_extTypes[2] // message can be transmitted via debug wire from PC to TREZOR + // optional bool wire_debug_out = 50005; + E_WireDebugOut = &file_messages_proto_extTypes[3] // message can be transmitted via debug wire from TREZOR to PC + // optional bool wire_tiny = 50006; + E_WireTiny = &file_messages_proto_extTypes[4] // message is handled by TREZOR when the USB stack is in tiny mode + // optional bool wire_bootloader = 50007; + E_WireBootloader = &file_messages_proto_extTypes[5] // message is only handled by TREZOR Bootloader + // optional bool wire_no_fsm = 50008; + E_WireNoFsm = &file_messages_proto_extTypes[6] // message is not handled by TREZOR unless the USB stack is in tiny mode +) + +var File_messages_proto protoreflect.FileDescriptor + +var file_messages_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x12, 0x68, 0x77, 0x2e, 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2a, 0xb9, 0x3f, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x10, 0x00, 0x1a, 0x08, 0x90, 0xb5, 0x18, 0x01, 0xb0, 0xb5, 0x18, 0x01, 0x12, 0x1a, 0x0a, 0x10, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x50, 0x69, 0x6e, 0x67, + 0x10, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x1d, 0x0a, 0x13, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x10, + 0x02, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x1d, 0x0a, 0x13, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x10, 0x03, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x1f, 0x0a, 0x15, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x69, 0x6e, 0x10, + 0x04, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x57, 0x69, 0x70, 0x65, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x10, 0x05, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, + 0x6f, 0x70, 0x79, 0x10, 0x09, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x1d, 0x0a, 0x13, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x6e, 0x74, 0x72, 0x6f, + 0x70, 0x79, 0x10, 0x0a, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4c, 0x6f, 0x61, 0x64, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x10, 0x0d, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x21, 0x0a, 0x17, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x52, 0x65, 0x73, 0x65, + 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x10, 0x0e, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, + 0x1e, 0x0a, 0x14, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x10, 0x11, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, + 0x26, 0x0a, 0x1c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x50, + 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, + 0x12, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x2a, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x50, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, + 0x41, 0x63, 0x6b, 0x10, 0x13, 0x1a, 0x0c, 0x90, 0xb5, 0x18, 0x01, 0xb0, 0xb5, 0x18, 0x01, 0xc0, + 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x12, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x10, 0x14, 0x1a, 0x08, 0x90, 0xb5, 0x18, + 0x01, 0xb0, 0xb5, 0x18, 0x01, 0x12, 0x22, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x10, 0x18, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x19, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x10, 0x19, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x23, + 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x75, + 0x74, 0x74, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x1a, 0x1a, 0x04, 0x98, + 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x15, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x41, 0x63, 0x6b, 0x10, 0x1b, 0x1a, 0x0c, + 0x90, 0xb5, 0x18, 0x01, 0xb0, 0xb5, 0x18, 0x01, 0xc0, 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x41, 0x70, 0x70, 0x6c, + 0x79, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x10, 0x1c, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x22, + 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x10, 0x22, 0x1a, 0x04, 0x90, 0xb5, + 0x18, 0x01, 0x12, 0x24, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x10, 0x23, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x41, + 0x63, 0x6b, 0x10, 0x24, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1d, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, + 0x72, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x29, 0x1a, 0x04, 0x98, + 0xb5, 0x18, 0x01, 0x12, 0x2b, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x41, 0x63, 0x6b, + 0x10, 0x2a, 0x1a, 0x0c, 0x90, 0xb5, 0x18, 0x01, 0xb0, 0xb5, 0x18, 0x01, 0xc0, 0xb5, 0x18, 0x01, + 0x12, 0x2c, 0x0a, 0x22, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x50, 0x61, 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x4d, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x30, + 0x0a, 0x1e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x50, 0x61, + 0x73, 0x73, 0x70, 0x68, 0x72, 0x61, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x41, 0x63, 0x6b, + 0x10, 0x4e, 0x1a, 0x0c, 0x90, 0xb5, 0x18, 0x01, 0xb0, 0xb5, 0x18, 0x01, 0xc0, 0xb5, 0x18, 0x01, + 0x12, 0x24, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x10, 0x2d, + 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x21, 0x0a, 0x17, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x57, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x10, 0x2e, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x1d, 0x0a, 0x13, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x57, 0x6f, 0x72, 0x64, 0x41, 0x63, 0x6b, + 0x10, 0x2f, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x21, 0x0a, 0x17, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x47, 0x65, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x10, 0x37, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x19, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x65, 0x74, 0x55, 0x32, + 0x46, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x10, 0x3f, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, + 0x12, 0x27, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x45, 0x72, 0x61, 0x73, 0x65, 0x10, 0x06, 0x1a, + 0x08, 0x90, 0xb5, 0x18, 0x01, 0xb8, 0xb5, 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1a, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, + 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x07, 0x1a, 0x08, 0x90, 0xb5, 0x18, 0x01, 0xb8, + 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x10, 0x08, 0x1a, 0x08, 0x98, 0xb5, 0x18, 0x01, 0xb8, 0xb5, 0x18, 0x01, 0x12, 0x22, + 0x0a, 0x14, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x65, + 0x6c, 0x66, 0x54, 0x65, 0x73, 0x74, 0x10, 0x20, 0x1a, 0x08, 0x90, 0xb5, 0x18, 0x01, 0xb8, 0xb5, + 0x18, 0x01, 0x12, 0x22, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0x0b, + 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x1f, 0x0a, 0x15, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, + 0x0c, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x1c, 0x0a, 0x12, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x69, 0x67, 0x6e, 0x54, 0x78, 0x10, 0x0f, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x1f, 0x0a, 0x15, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x15, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x1b, 0x0a, 0x11, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x78, 0x41, 0x63, 0x6b, 0x10, 0x16, 0x1a, 0x04, 0x90, + 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x1d, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x1d, 0x0a, 0x13, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x1e, 0x1a, 0x04, + 0x98, 0xb5, 0x18, 0x01, 0x12, 0x21, 0x0a, 0x17, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x5f, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x10, + 0x26, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x10, 0x27, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1c, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x10, 0x28, 0x1a, 0x04, + 0x98, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x5f, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x10, 0x17, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1c, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, + 0x65, 0x64, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x10, 0x30, 0x1a, 0x04, 0x98, 0xb5, + 0x18, 0x01, 0x12, 0x22, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x53, 0x69, 0x67, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x10, 0x35, + 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x10, 0x36, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1d, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x47, 0x65, 0x74, 0x45, + 0x43, 0x44, 0x48, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x10, 0x3d, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x43, 0x44, 0x48, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x4b, 0x65, 0x79, 0x10, 0x3e, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x6f, 0x73, 0x69, 0x43, + 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x10, 0x47, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, + 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x6f, 0x73, + 0x69, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x10, 0x48, 0x1a, 0x04, 0x98, + 0xb5, 0x18, 0x01, 0x12, 0x1e, 0x0a, 0x14, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x43, 0x6f, 0x73, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x10, 0x49, 0x1a, 0x04, 0x90, + 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x43, 0x6f, 0x73, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x10, 0x4a, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x2f, 0x0a, 0x1d, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x69, 0x6e, + 0x6b, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x10, 0x64, 0x1a, 0x0c, 0xa0, 0xb5, 0x18, + 0x01, 0xb0, 0xb5, 0x18, 0x01, 0xc0, 0xb5, 0x18, 0x01, 0x12, 0x2b, 0x0a, 0x1d, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x69, + 0x6e, 0x6b, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x10, 0x65, 0x1a, 0x08, 0xa0, 0xb5, + 0x18, 0x01, 0xb0, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x69, 0x6e, 0x6b, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x10, 0x66, 0x1a, 0x04, 0xa8, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x19, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, + 0x67, 0x4c, 0x69, 0x6e, 0x6b, 0x53, 0x74, 0x6f, 0x70, 0x10, 0x67, 0x1a, 0x04, 0xa0, 0xb5, 0x18, + 0x01, 0x12, 0x22, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x69, 0x6e, 0x6b, 0x4c, 0x6f, 0x67, 0x10, 0x68, 0x1a, + 0x04, 0xa8, 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x69, 0x6e, 0x6b, 0x4d, 0x65, + 0x6d, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x61, 0x64, 0x10, 0x6e, 0x1a, 0x04, 0xa0, 0xb5, 0x18, 0x01, + 0x12, 0x25, 0x0a, 0x1b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x69, 0x6e, 0x6b, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x10, + 0x6f, 0x1a, 0x04, 0xa8, 0xb5, 0x18, 0x01, 0x12, 0x2a, 0x0a, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x69, 0x6e, 0x6b, + 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x57, 0x72, 0x69, 0x74, 0x65, 0x10, 0x70, 0x1a, 0x04, 0xa0, + 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x69, 0x6e, 0x6b, 0x46, 0x6c, 0x61, 0x73, + 0x68, 0x45, 0x72, 0x61, 0x73, 0x65, 0x10, 0x71, 0x1a, 0x04, 0xa0, 0xb5, 0x18, 0x01, 0x12, 0x2b, + 0x0a, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x10, 0xc2, 0x03, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1d, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0xc3, 0x03, 0x1a, + 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x47, 0x65, 0x74, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x38, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, + 0x25, 0x0a, 0x1b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x39, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x53, 0x69, + 0x67, 0x6e, 0x54, 0x78, 0x10, 0x3a, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1d, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x54, 0x78, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x3b, 0x1a, + 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x54, 0x78, 0x41, + 0x63, 0x6b, 0x10, 0x3c, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1f, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x10, 0x40, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2b, 0x0a, 0x21, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x10, 0x41, 0x1a, 0x04, 0x90, 0xb5, + 0x18, 0x01, 0x12, 0x2e, 0x0a, 0x24, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x10, 0x42, 0x1a, 0x04, 0x98, 0xb5, + 0x18, 0x01, 0x12, 0x23, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x4e, 0x45, 0x4d, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, + 0x43, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4e, 0x45, 0x4d, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x10, 0x44, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x1f, 0x0a, 0x15, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4e, 0x45, 0x4d, 0x53, 0x69, 0x67, 0x6e, + 0x54, 0x78, 0x10, 0x45, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x21, 0x0a, 0x17, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4e, 0x45, 0x4d, 0x53, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x54, 0x78, 0x10, 0x46, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, + 0x1d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4e, 0x45, 0x4d, + 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x10, 0x4b, + 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4e, 0x45, 0x4d, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x10, 0x4c, 0x1a, 0x04, 0x98, 0xb5, 0x18, + 0x01, 0x12, 0x24, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x4c, 0x69, 0x73, 0x6b, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, + 0x72, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x21, 0x0a, 0x17, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4c, 0x69, 0x73, 0x6b, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x10, 0x73, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x20, 0x0a, 0x16, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4c, 0x69, 0x73, 0x6b, 0x53, 0x69, + 0x67, 0x6e, 0x54, 0x78, 0x10, 0x74, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x22, 0x0a, 0x18, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4c, 0x69, 0x73, 0x6b, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x78, 0x10, 0x75, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x25, 0x0a, 0x1b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x4c, 0x69, 0x73, 0x6b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x10, + 0x76, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2a, 0x0a, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4c, 0x69, 0x73, 0x6b, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x10, 0x77, 0x1a, 0x04, 0x98, + 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x4c, 0x69, 0x73, 0x6b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x10, 0x78, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1c, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4c, 0x69, 0x73, 0x6b, + 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0x79, 0x1a, 0x04, + 0x90, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x5f, 0x4c, 0x69, 0x73, 0x6b, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x10, 0x7a, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1b, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x65, 0x7a, 0x6f, 0x73, 0x47, 0x65, + 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x96, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, + 0x01, 0x12, 0x23, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x54, 0x65, 0x7a, 0x6f, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x97, 0x01, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x22, 0x0a, 0x17, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x65, 0x7a, 0x6f, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x54, + 0x78, 0x10, 0x98, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, 0x19, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x65, 0x7a, 0x6f, 0x73, 0x53, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x78, 0x10, 0x99, 0x01, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x28, 0x0a, 0x1d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x54, 0x65, 0x7a, 0x6f, 0x73, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x10, 0x9a, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x25, 0x0a, 0x1a, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x65, 0x7a, 0x6f, 0x73, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0x9b, 0x01, 0x1a, 0x04, 0x98, 0xb5, 0x18, + 0x01, 0x12, 0x24, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x54, 0x78, 0x10, 0xca, + 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1e, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x54, 0x78, + 0x4f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xcb, 0x01, 0x1a, 0x04, 0x98, 0xb5, + 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x10, 0xcf, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x25, 0x0a, 0x1a, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, + 0x6c, 0x61, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0xd0, 0x01, 0x1a, 0x04, 0x98, + 0xb5, 0x18, 0x01, 0x12, 0x2d, 0x0a, 0x22, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x70, 0x10, 0xd2, 0x01, 0x1a, 0x04, 0x90, 0xb5, + 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x4f, 0x70, 0x10, 0xd3, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2b, 0x0a, 0x20, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, + 0x61, 0x72, 0x50, 0x61, 0x74, 0x68, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x70, 0x10, + 0xd4, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2b, 0x0a, 0x20, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x4f, 0x70, 0x10, 0xd5, 0x01, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x32, 0x0a, 0x27, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x4f, 0x70, + 0x10, 0xd6, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2a, 0x0a, 0x1f, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, + 0x53, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4f, 0x70, 0x10, 0xd7, 0x01, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2b, 0x0a, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x4f, 0x70, 0x10, 0xd8, 0x01, 0x1a, 0x04, 0x90, 0xb5, + 0x18, 0x01, 0x12, 0x2a, 0x0a, 0x1f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x4f, 0x70, 0x10, 0xd9, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2c, + 0x0a, 0x21, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, + 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x65, 0x72, 0x67, + 0x65, 0x4f, 0x70, 0x10, 0xda, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2a, 0x0a, 0x1f, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, + 0x6c, 0x61, 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x4f, 0x70, 0x10, + 0xdc, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2c, 0x0a, 0x21, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x42, + 0x75, 0x6d, 0x70, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4f, 0x70, 0x10, 0xdd, 0x01, + 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x53, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x72, 0x53, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x54, 0x78, 0x10, 0xe6, 0x01, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x25, + 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x72, + 0x6f, 0x6e, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0xfa, 0x01, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x22, 0x0a, 0x17, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x72, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x10, 0xfb, 0x01, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x21, 0x0a, 0x16, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x72, 0x6f, 0x6e, 0x53, 0x69, 0x67, + 0x6e, 0x54, 0x78, 0x10, 0xfc, 0x01, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x18, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x72, 0x6f, 0x6e, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x78, 0x10, 0xfd, 0x01, 0x1a, 0x04, 0x98, 0xb5, 0x18, + 0x01, 0x12, 0x24, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x43, 0x61, 0x72, 0x64, 0x61, 0x6e, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x54, 0x78, 0x10, 0xaf, + 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1c, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x61, 0x72, 0x64, 0x61, 0x6e, 0x6f, 0x54, 0x78, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xb0, 0x02, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x2a, 0x0a, 0x1f, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x43, 0x61, 0x72, 0x64, 0x61, 0x6e, 0x6f, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x10, 0xb1, 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1c, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x61, 0x72, 0x64, + 0x61, 0x6e, 0x6f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0xb2, 0x02, 0x1a, + 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x61, 0x72, 0x64, 0x61, 0x6e, 0x6f, 0x47, 0x65, 0x74, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0xb3, 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, + 0x25, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, + 0x61, 0x72, 0x64, 0x61, 0x6e, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0xb4, 0x02, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x61, 0x72, 0x64, 0x61, 0x6e, 0x6f, 0x54, 0x78, 0x41, + 0x63, 0x6b, 0x10, 0xb5, 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1b, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x43, 0x61, 0x72, 0x64, 0x61, + 0x6e, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x78, 0x10, 0xb6, 0x02, 0x1a, 0x04, 0x98, + 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x47, 0x65, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0xde, 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x26, + 0x0a, 0x1b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, + 0x74, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0xdf, 0x02, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x2b, 0x0a, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x47, 0x65, + 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0xe0, 0x02, 0x1a, 0x04, 0x90, + 0xb5, 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x10, 0xe1, 0x02, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x2b, 0x0a, + 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, + 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, + 0x72, 0x10, 0xe2, 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2d, 0x0a, 0x22, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, 0x6f, + 0x67, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, + 0x10, 0xe3, 0x02, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x2e, 0x0a, 0x23, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, 0x6f, 0x67, + 0x79, 0x53, 0x69, 0x67, 0x6e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x4f, 0x6e, 0x67, + 0x10, 0xe4, 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x30, 0x0a, 0x25, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, 0x6f, 0x67, + 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x4f, + 0x6e, 0x67, 0x10, 0xe5, 0x02, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x30, 0x0a, 0x25, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, + 0x6f, 0x67, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x6e, 0x74, 0x49, 0x64, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x10, 0xe6, 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x32, 0x0a, + 0x27, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, + 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x6e, 0x74, 0x49, 0x64, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x10, 0xe7, 0x02, 0x1a, 0x04, 0x98, 0xb5, 0x18, + 0x01, 0x12, 0x35, 0x0a, 0x2a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x4f, 0x6e, 0x74, + 0x49, 0x64, 0x41, 0x64, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x10, + 0xe8, 0x02, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x37, 0x0a, 0x2c, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4f, 0x6e, 0x74, 0x6f, 0x6c, 0x6f, 0x67, 0x79, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4f, 0x6e, 0x74, 0x49, 0x64, 0x41, 0x64, 0x64, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x10, 0xe9, 0x02, 0x1a, 0x04, 0x98, 0xb5, 0x18, + 0x01, 0x12, 0x27, 0x0a, 0x1c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x52, 0x69, 0x70, 0x70, 0x6c, 0x65, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x10, 0x90, 0x03, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, 0x19, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x52, 0x69, 0x70, 0x70, 0x6c, 0x65, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x91, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x23, 0x0a, 0x18, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x52, 0x69, 0x70, 0x70, 0x6c, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x54, 0x78, 0x10, 0x92, 0x03, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x25, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x52, 0x69, 0x70, 0x70, 0x6c, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x54, 0x78, 0x10, 0x93, 0x03, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x33, 0x0a, 0x28, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, + 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x69, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xf5, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, + 0x01, 0x12, 0x2f, 0x0a, 0x24, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x6e, 0x69, 0x74, 0x41, 0x63, 0x6b, 0x10, 0xf6, 0x03, 0x1a, 0x04, 0x98, 0xb5, + 0x18, 0x01, 0x12, 0x37, 0x0a, 0x2c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x10, 0xf7, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x33, 0x0a, 0x28, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, + 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x41, 0x63, 0x6b, 0x10, 0xf8, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x40, 0x0a, 0x35, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xf9, 0x03, 0x1a, 0x04, 0x98, 0xb5, + 0x18, 0x01, 0x12, 0x3c, 0x0a, 0x31, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x75, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x6b, 0x10, 0xfa, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x38, 0x0a, 0x2d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x56, 0x69, 0x6e, 0x69, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x10, 0xfb, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x34, 0x0a, 0x29, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, + 0x56, 0x69, 0x6e, 0x69, 0x41, 0x63, 0x6b, 0x10, 0xfc, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x3b, 0x0a, 0x30, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x41, 0x6c, 0x6c, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x10, 0xfd, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x37, 0x0a, + 0x2c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, + 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6c, + 0x6c, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x53, 0x65, 0x74, 0x41, 0x63, 0x6b, 0x10, 0xfe, 0x03, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x38, 0x0a, 0x2d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xff, 0x03, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x34, 0x0a, 0x29, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x41, 0x63, 0x6b, 0x10, 0x80, 0x04, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x38, 0x0a, 0x2d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6c, 0x6c, 0x4f, 0x75, 0x74, 0x53, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x81, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x34, 0x0a, 0x29, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x41, 0x6c, 0x6c, 0x4f, 0x75, 0x74, 0x53, 0x65, 0x74, 0x41, 0x63, 0x6b, 0x10, 0x82, 0x04, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x38, 0x0a, 0x2d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x83, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x34, 0x0a, 0x29, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x41, 0x63, 0x6b, 0x10, 0x84, 0x04, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x34, 0x0a, 0x29, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x10, 0x85, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x30, 0x0a, 0x25, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, + 0x72, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6e, + 0x61, 0x6c, 0x41, 0x63, 0x6b, 0x10, 0x86, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x36, + 0x0a, 0x2b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, + 0x6e, 0x65, 0x72, 0x6f, 0x4b, 0x65, 0x79, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x45, 0x78, 0x70, 0x6f, + 0x72, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x92, 0x04, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x32, 0x0a, 0x27, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x4b, 0x65, 0x79, 0x49, + 0x6d, 0x61, 0x67, 0x65, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x41, 0x63, + 0x6b, 0x10, 0x93, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x34, 0x0a, 0x29, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, + 0x4b, 0x65, 0x79, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x65, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0x94, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, + 0x12, 0x30, 0x0a, 0x25, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, + 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x4b, 0x65, 0x79, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x79, + 0x6e, 0x63, 0x53, 0x74, 0x65, 0x70, 0x41, 0x63, 0x6b, 0x10, 0x95, 0x04, 0x1a, 0x04, 0x98, 0xb5, + 0x18, 0x01, 0x12, 0x35, 0x0a, 0x2a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x4b, 0x65, 0x79, 0x49, 0x6d, 0x61, 0x67, 0x65, + 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x10, 0x96, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x31, 0x0a, 0x26, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x4b, + 0x65, 0x79, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x61, 0x6c, + 0x41, 0x63, 0x6b, 0x10, 0x97, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1c, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, + 0x72, 0x6f, 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x9c, 0x04, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x24, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x10, 0x9d, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1d, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, + 0x6f, 0x47, 0x65, 0x74, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4b, 0x65, 0x79, 0x10, 0x9e, 0x04, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x25, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x57, 0x61, 0x74, 0x63, 0x68, + 0x4b, 0x65, 0x79, 0x10, 0x9f, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x2d, 0x0a, 0x22, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, + 0x67, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x44, 0x69, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x10, 0xa2, 0x04, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1e, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x44, 0x65, 0x62, 0x75, 0x67, + 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x44, 0x69, 0x61, 0x67, 0x41, 0x63, 0x6b, 0x10, 0xa3, 0x04, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x2c, 0x0a, 0x21, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x47, 0x65, 0x74, 0x54, + 0x78, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xa6, 0x04, 0x1a, 0x04, + 0x90, 0xb5, 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x47, 0x65, 0x74, 0x54, 0x78, 0x4b, + 0x65, 0x79, 0x41, 0x63, 0x6b, 0x10, 0xa7, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x34, + 0x0a, 0x29, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, + 0x6e, 0x65, 0x72, 0x6f, 0x4c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xa8, 0x04, 0x1a, 0x04, + 0x90, 0xb5, 0x18, 0x01, 0x12, 0x30, 0x0a, 0x25, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x4c, 0x69, 0x76, 0x65, 0x52, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x63, 0x6b, 0x10, 0xa9, 0x04, + 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x33, 0x0a, 0x28, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x4c, 0x69, 0x76, 0x65, + 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x10, 0xaa, 0x04, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x2f, 0x0a, 0x24, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, + 0x6f, 0x4c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x53, 0x74, 0x65, 0x70, + 0x41, 0x63, 0x6b, 0x10, 0xab, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x34, 0x0a, 0x29, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, + 0x72, 0x6f, 0x4c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x46, 0x69, 0x6e, + 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xac, 0x04, 0x1a, 0x04, 0x90, 0xb5, + 0x18, 0x01, 0x12, 0x30, 0x0a, 0x25, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x5f, 0x4d, 0x6f, 0x6e, 0x65, 0x72, 0x6f, 0x4c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x6b, 0x10, 0xad, 0x04, 0x1a, 0x04, + 0x98, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x5f, 0x45, 0x6f, 0x73, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x10, 0xd8, 0x04, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x23, 0x0a, 0x18, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x6f, 0x73, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0xd9, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, + 0x01, 0x12, 0x20, 0x0a, 0x15, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x45, 0x6f, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x54, 0x78, 0x10, 0xda, 0x04, 0x1a, 0x04, 0x90, + 0xb5, 0x18, 0x01, 0x12, 0x29, 0x0a, 0x1e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x45, 0x6f, 0x73, 0x54, 0x78, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xdb, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x25, + 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x6f, + 0x73, 0x54, 0x78, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x6b, 0x10, 0xdc, 0x04, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x22, 0x0a, 0x17, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x45, 0x6f, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x78, + 0x10, 0xdd, 0x04, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x28, 0x0a, 0x1d, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, + 0x47, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0xbc, 0x05, 0x1a, 0x04, 0x90, + 0xb5, 0x18, 0x01, 0x12, 0x25, 0x0a, 0x1a, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x5f, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x10, 0xbd, 0x05, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x2a, 0x0a, 0x1f, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, + 0x65, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0xbe, 0x05, + 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0xbf, 0x05, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, + 0x24, 0x0a, 0x19, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, + 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x54, 0x78, 0x10, 0xc0, 0x05, 0x1a, + 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x27, 0x0a, 0x1c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x78, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xc1, 0x05, 0x1a, 0x04, 0x98, 0xb5, 0x18, 0x01, 0x12, 0x29, + 0x0a, 0x1e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x69, + 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x4d, 0x73, 0x67, + 0x10, 0xc2, 0x05, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1b, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x10, 0xc3, 0x05, 0x1a, 0x04, 0x90, 0xb5, 0x18, + 0x01, 0x12, 0x27, 0x0a, 0x1c, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x5f, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4d, 0x73, + 0x67, 0x10, 0xc4, 0x05, 0x1a, 0x04, 0x90, 0xb5, 0x18, 0x01, 0x12, 0x26, 0x0a, 0x1b, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x42, 0x69, 0x6e, 0x61, 0x6e, 0x63, + 0x65, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x78, 0x10, 0xc5, 0x05, 0x1a, 0x04, 0x98, 0xb5, + 0x18, 0x01, 0x3a, 0x3c, 0x0a, 0x07, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x69, 0x6e, 0x12, 0x21, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0xd2, 0x86, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x77, 0x69, 0x72, 0x65, 0x49, 0x6e, + 0x3a, 0x3e, 0x0a, 0x08, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x12, 0x21, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0xd3, 0x86, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x77, 0x69, 0x72, 0x65, 0x4f, 0x75, 0x74, + 0x3a, 0x47, 0x0a, 0x0d, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, + 0x6e, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd4, 0x86, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x69, + 0x72, 0x65, 0x44, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x3a, 0x49, 0x0a, 0x0e, 0x77, 0x69, 0x72, + 0x65, 0x5f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x6f, 0x75, 0x74, 0x12, 0x21, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, + 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd5, + 0x86, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x77, 0x69, 0x72, 0x65, 0x44, 0x65, 0x62, 0x75, + 0x67, 0x4f, 0x75, 0x74, 0x3a, 0x40, 0x0a, 0x09, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6e, + 0x79, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd6, 0x86, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, + 0x72, 0x65, 0x54, 0x69, 0x6e, 0x79, 0x3a, 0x4c, 0x0a, 0x0f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x62, + 0x6f, 0x6f, 0x74, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd7, 0x86, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x77, 0x69, 0x72, 0x65, 0x42, 0x6f, 0x6f, 0x74, 0x6c, 0x6f, + 0x61, 0x64, 0x65, 0x72, 0x3a, 0x43, 0x0a, 0x0b, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x6e, 0x6f, 0x5f, + 0x66, 0x73, 0x6d, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd8, 0x86, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x77, 0x69, 0x72, 0x65, 0x4e, 0x6f, 0x46, 0x73, 0x6d, 0x42, 0x6f, 0x0a, 0x23, 0x63, 0x6f, 0x6d, + 0x2e, 0x73, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x6c, 0x61, 0x62, 0x73, 0x2e, 0x74, 0x72, 0x65, + 0x7a, 0x6f, 0x72, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x42, 0x0d, 0x54, 0x72, 0x65, 0x7a, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5a, + 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x75, 0x73, 0x62, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x7a, 0x6f, 0x72, +} + +var ( + file_messages_proto_rawDescOnce sync.Once + file_messages_proto_rawDescData = file_messages_proto_rawDesc +) + +func file_messages_proto_rawDescGZIP() []byte { + file_messages_proto_rawDescOnce.Do(func() { + file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_proto_rawDescData) + }) + return file_messages_proto_rawDescData +} + +var file_messages_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_messages_proto_goTypes = []any{ + (MessageType)(0), // 0: hw.trezor.messages.MessageType + (*descriptorpb.EnumValueOptions)(nil), // 1: google.protobuf.EnumValueOptions +} +var file_messages_proto_depIdxs = []int32{ + 1, // 0: hw.trezor.messages.wire_in:extendee -> google.protobuf.EnumValueOptions + 1, // 1: hw.trezor.messages.wire_out:extendee -> google.protobuf.EnumValueOptions + 1, // 2: hw.trezor.messages.wire_debug_in:extendee -> google.protobuf.EnumValueOptions + 1, // 3: hw.trezor.messages.wire_debug_out:extendee -> google.protobuf.EnumValueOptions + 1, // 4: hw.trezor.messages.wire_tiny:extendee -> google.protobuf.EnumValueOptions + 1, // 5: hw.trezor.messages.wire_bootloader:extendee -> google.protobuf.EnumValueOptions + 1, // 6: hw.trezor.messages.wire_no_fsm:extendee -> google.protobuf.EnumValueOptions + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 0, // [0:7] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_messages_proto_init() } +func file_messages_proto_init() { + if File_messages_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_messages_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 7, + NumServices: 0, + }, + GoTypes: file_messages_proto_goTypes, + DependencyIndexes: file_messages_proto_depIdxs, + EnumInfos: file_messages_proto_enumTypes, + ExtensionInfos: file_messages_proto_extTypes, + }.Build() + File_messages_proto = out.File + file_messages_proto_rawDesc = nil + file_messages_proto_goTypes = nil + file_messages_proto_depIdxs = nil +} diff --git a/accounts/usbwallet/trezor/messages.proto b/accounts/usbwallet/trezor/messages.proto new file mode 100644 index 0000000..c232bef --- /dev/null +++ b/accounts/usbwallet/trezor/messages.proto @@ -0,0 +1,267 @@ +// This file originates from the SatoshiLabs Trezor `common` repository at: +// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto +// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. + +syntax = "proto2"; +package hw.trezor.messages; + +/** + * Messages for TREZOR communication + */ + +option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"; + +// Sugar for easier handling in Java +option java_package = "com.satoshilabs.trezor.lib.protobuf"; +option java_outer_classname = "TrezorMessage"; + + +import "google/protobuf/descriptor.proto"; + +/** + * Options for specifying message direction and type of wire (normal/debug) + */ +extend google.protobuf.EnumValueOptions { + optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR + optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC + optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR + optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC + optional bool wire_tiny = 50006; // message is handled by TREZOR when the USB stack is in tiny mode + optional bool wire_bootloader = 50007; // message is only handled by TREZOR Bootloader + optional bool wire_no_fsm = 50008; // message is not handled by TREZOR unless the USB stack is in tiny mode +} + +/** + * Mapping between TREZOR wire identifier (uint) and a protobuf message + */ +enum MessageType { + + // Management + MessageType_Initialize = 0 [(wire_in) = true, (wire_tiny) = true]; + MessageType_Ping = 1 [(wire_in) = true]; + MessageType_Success = 2 [(wire_out) = true]; + MessageType_Failure = 3 [(wire_out) = true]; + MessageType_ChangePin = 4 [(wire_in) = true]; + MessageType_WipeDevice = 5 [(wire_in) = true]; + MessageType_GetEntropy = 9 [(wire_in) = true]; + MessageType_Entropy = 10 [(wire_out) = true]; + MessageType_LoadDevice = 13 [(wire_in) = true]; + MessageType_ResetDevice = 14 [(wire_in) = true]; + MessageType_Features = 17 [(wire_out) = true]; + MessageType_PinMatrixRequest = 18 [(wire_out) = true]; + MessageType_PinMatrixAck = 19 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; + MessageType_Cancel = 20 [(wire_in) = true, (wire_tiny) = true]; + MessageType_ClearSession = 24 [(wire_in) = true]; + MessageType_ApplySettings = 25 [(wire_in) = true]; + MessageType_ButtonRequest = 26 [(wire_out) = true]; + MessageType_ButtonAck = 27 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; + MessageType_ApplyFlags = 28 [(wire_in) = true]; + MessageType_BackupDevice = 34 [(wire_in) = true]; + MessageType_EntropyRequest = 35 [(wire_out) = true]; + MessageType_EntropyAck = 36 [(wire_in) = true]; + MessageType_PassphraseRequest = 41 [(wire_out) = true]; + MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; + MessageType_PassphraseStateRequest = 77 [(wire_out) = true]; + MessageType_PassphraseStateAck = 78 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; + MessageType_RecoveryDevice = 45 [(wire_in) = true]; + MessageType_WordRequest = 46 [(wire_out) = true]; + MessageType_WordAck = 47 [(wire_in) = true]; + MessageType_GetFeatures = 55 [(wire_in) = true]; + MessageType_SetU2FCounter = 63 [(wire_in) = true]; + + // Bootloader + MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true]; + MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true]; + MessageType_FirmwareRequest = 8 [(wire_out) = true, (wire_bootloader) = true]; + MessageType_SelfTest = 32 [(wire_in) = true, (wire_bootloader) = true]; + + // Bitcoin + MessageType_GetPublicKey = 11 [(wire_in) = true]; + MessageType_PublicKey = 12 [(wire_out) = true]; + MessageType_SignTx = 15 [(wire_in) = true]; + MessageType_TxRequest = 21 [(wire_out) = true]; + MessageType_TxAck = 22 [(wire_in) = true]; + MessageType_GetAddress = 29 [(wire_in) = true]; + MessageType_Address = 30 [(wire_out) = true]; + MessageType_SignMessage = 38 [(wire_in) = true]; + MessageType_VerifyMessage = 39 [(wire_in) = true]; + MessageType_MessageSignature = 40 [(wire_out) = true]; + + // Crypto + MessageType_CipherKeyValue = 23 [(wire_in) = true]; + MessageType_CipheredKeyValue = 48 [(wire_out) = true]; + MessageType_SignIdentity = 53 [(wire_in) = true]; + MessageType_SignedIdentity = 54 [(wire_out) = true]; + MessageType_GetECDHSessionKey = 61 [(wire_in) = true]; + MessageType_ECDHSessionKey = 62 [(wire_out) = true]; + MessageType_CosiCommit = 71 [(wire_in) = true]; + MessageType_CosiCommitment = 72 [(wire_out) = true]; + MessageType_CosiSign = 73 [(wire_in) = true]; + MessageType_CosiSignature = 74 [(wire_out) = true]; + + // Debug + MessageType_DebugLinkDecision = 100 [(wire_debug_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; + MessageType_DebugLinkGetState = 101 [(wire_debug_in) = true, (wire_tiny) = true]; + MessageType_DebugLinkState = 102 [(wire_debug_out) = true]; + MessageType_DebugLinkStop = 103 [(wire_debug_in) = true]; + MessageType_DebugLinkLog = 104 [(wire_debug_out) = true]; + MessageType_DebugLinkMemoryRead = 110 [(wire_debug_in) = true]; + MessageType_DebugLinkMemory = 111 [(wire_debug_out) = true]; + MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true]; + MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true]; + + // Ethereum + MessageType_EthereumGetPublicKey = 450 [(wire_in) = true]; + MessageType_EthereumPublicKey = 451 [(wire_out) = true]; + MessageType_EthereumGetAddress = 56 [(wire_in) = true]; + MessageType_EthereumAddress = 57 [(wire_out) = true]; + MessageType_EthereumSignTx = 58 [(wire_in) = true]; + MessageType_EthereumTxRequest = 59 [(wire_out) = true]; + MessageType_EthereumTxAck = 60 [(wire_in) = true]; + MessageType_EthereumSignMessage = 64 [(wire_in) = true]; + MessageType_EthereumVerifyMessage = 65 [(wire_in) = true]; + MessageType_EthereumMessageSignature = 66 [(wire_out) = true]; + + // NEM + MessageType_NEMGetAddress = 67 [(wire_in) = true]; + MessageType_NEMAddress = 68 [(wire_out) = true]; + MessageType_NEMSignTx = 69 [(wire_in) = true]; + MessageType_NEMSignedTx = 70 [(wire_out) = true]; + MessageType_NEMDecryptMessage = 75 [(wire_in) = true]; + MessageType_NEMDecryptedMessage = 76 [(wire_out) = true]; + + // Lisk + MessageType_LiskGetAddress = 114 [(wire_in) = true]; + MessageType_LiskAddress = 115 [(wire_out) = true]; + MessageType_LiskSignTx = 116 [(wire_in) = true]; + MessageType_LiskSignedTx = 117 [(wire_out) = true]; + MessageType_LiskSignMessage = 118 [(wire_in) = true]; + MessageType_LiskMessageSignature = 119 [(wire_out) = true]; + MessageType_LiskVerifyMessage = 120 [(wire_in) = true]; + MessageType_LiskGetPublicKey = 121 [(wire_in) = true]; + MessageType_LiskPublicKey = 122 [(wire_out) = true]; + + // Tezos + MessageType_TezosGetAddress = 150 [(wire_in) = true]; + MessageType_TezosAddress = 151 [(wire_out) = true]; + MessageType_TezosSignTx = 152 [(wire_in) = true]; + MessageType_TezosSignedTx = 153 [(wire_out) = true]; + MessageType_TezosGetPublicKey = 154 [(wire_in) = true]; + MessageType_TezosPublicKey = 155 [(wire_out) = true]; + + // Stellar + MessageType_StellarSignTx = 202 [(wire_in) = true]; + MessageType_StellarTxOpRequest = 203 [(wire_out) = true]; + MessageType_StellarGetAddress = 207 [(wire_in) = true]; + MessageType_StellarAddress = 208 [(wire_out) = true]; + MessageType_StellarCreateAccountOp = 210 [(wire_in) = true]; + MessageType_StellarPaymentOp = 211 [(wire_in) = true]; + MessageType_StellarPathPaymentOp = 212 [(wire_in) = true]; + MessageType_StellarManageOfferOp = 213 [(wire_in) = true]; + MessageType_StellarCreatePassiveOfferOp = 214 [(wire_in) = true]; + MessageType_StellarSetOptionsOp = 215 [(wire_in) = true]; + MessageType_StellarChangeTrustOp = 216 [(wire_in) = true]; + MessageType_StellarAllowTrustOp = 217 [(wire_in) = true]; + MessageType_StellarAccountMergeOp = 218 [(wire_in) = true]; + // omitted: StellarInflationOp is not a supported operation, would be 219 + MessageType_StellarManageDataOp = 220 [(wire_in) = true]; + MessageType_StellarBumpSequenceOp = 221 [(wire_in) = true]; + MessageType_StellarSignedTx = 230 [(wire_out) = true]; + + // TRON + MessageType_TronGetAddress = 250 [(wire_in) = true]; + MessageType_TronAddress = 251 [(wire_out) = true]; + MessageType_TronSignTx = 252 [(wire_in) = true]; + MessageType_TronSignedTx = 253 [(wire_out) = true]; + + // Cardano + // dropped Sign/VerifyMessage ids 300-302 + MessageType_CardanoSignTx = 303 [(wire_in) = true]; + MessageType_CardanoTxRequest = 304 [(wire_out) = true]; + MessageType_CardanoGetPublicKey = 305 [(wire_in) = true]; + MessageType_CardanoPublicKey = 306 [(wire_out) = true]; + MessageType_CardanoGetAddress = 307 [(wire_in) = true]; + MessageType_CardanoAddress = 308 [(wire_out) = true]; + MessageType_CardanoTxAck = 309 [(wire_in) = true]; + MessageType_CardanoSignedTx = 310 [(wire_out) = true]; + + // Ontology + MessageType_OntologyGetAddress = 350 [(wire_in) = true]; + MessageType_OntologyAddress = 351 [(wire_out) = true]; + MessageType_OntologyGetPublicKey = 352 [(wire_in) = true]; + MessageType_OntologyPublicKey = 353 [(wire_out) = true]; + MessageType_OntologySignTransfer = 354 [(wire_in) = true]; + MessageType_OntologySignedTransfer = 355 [(wire_out) = true]; + MessageType_OntologySignWithdrawOng = 356 [(wire_in) = true]; + MessageType_OntologySignedWithdrawOng = 357 [(wire_out) = true]; + MessageType_OntologySignOntIdRegister = 358 [(wire_in) = true]; + MessageType_OntologySignedOntIdRegister = 359 [(wire_out) = true]; + MessageType_OntologySignOntIdAddAttributes = 360 [(wire_in) = true]; + MessageType_OntologySignedOntIdAddAttributes = 361 [(wire_out) = true]; + + // Ripple + MessageType_RippleGetAddress = 400 [(wire_in) = true]; + MessageType_RippleAddress = 401 [(wire_out) = true]; + MessageType_RippleSignTx = 402 [(wire_in) = true]; + MessageType_RippleSignedTx = 403 [(wire_in) = true]; + + // Monero + MessageType_MoneroTransactionInitRequest = 501 [(wire_out) = true]; + MessageType_MoneroTransactionInitAck = 502 [(wire_out) = true]; + MessageType_MoneroTransactionSetInputRequest = 503 [(wire_out) = true]; + MessageType_MoneroTransactionSetInputAck = 504 [(wire_out) = true]; + MessageType_MoneroTransactionInputsPermutationRequest = 505 [(wire_out) = true]; + MessageType_MoneroTransactionInputsPermutationAck = 506 [(wire_out) = true]; + MessageType_MoneroTransactionInputViniRequest = 507 [(wire_out) = true]; + MessageType_MoneroTransactionInputViniAck = 508 [(wire_out) = true]; + MessageType_MoneroTransactionAllInputsSetRequest = 509 [(wire_out) = true]; + MessageType_MoneroTransactionAllInputsSetAck = 510 [(wire_out) = true]; + MessageType_MoneroTransactionSetOutputRequest = 511 [(wire_out) = true]; + MessageType_MoneroTransactionSetOutputAck = 512 [(wire_out) = true]; + MessageType_MoneroTransactionAllOutSetRequest = 513 [(wire_out) = true]; + MessageType_MoneroTransactionAllOutSetAck = 514 [(wire_out) = true]; + MessageType_MoneroTransactionSignInputRequest = 515 [(wire_out) = true]; + MessageType_MoneroTransactionSignInputAck = 516 [(wire_out) = true]; + MessageType_MoneroTransactionFinalRequest = 517 [(wire_out) = true]; + MessageType_MoneroTransactionFinalAck = 518 [(wire_out) = true]; + MessageType_MoneroKeyImageExportInitRequest = 530 [(wire_out) = true]; + MessageType_MoneroKeyImageExportInitAck = 531 [(wire_out) = true]; + MessageType_MoneroKeyImageSyncStepRequest = 532 [(wire_out) = true]; + MessageType_MoneroKeyImageSyncStepAck = 533 [(wire_out) = true]; + MessageType_MoneroKeyImageSyncFinalRequest = 534 [(wire_out) = true]; + MessageType_MoneroKeyImageSyncFinalAck = 535 [(wire_out) = true]; + MessageType_MoneroGetAddress = 540 [(wire_in) = true]; + MessageType_MoneroAddress = 541 [(wire_out) = true]; + MessageType_MoneroGetWatchKey = 542 [(wire_in) = true]; + MessageType_MoneroWatchKey = 543 [(wire_out) = true]; + MessageType_DebugMoneroDiagRequest = 546 [(wire_in) = true]; + MessageType_DebugMoneroDiagAck = 547 [(wire_out) = true]; + MessageType_MoneroGetTxKeyRequest = 550 [(wire_in) = true]; + MessageType_MoneroGetTxKeyAck = 551 [(wire_out) = true]; + MessageType_MoneroLiveRefreshStartRequest = 552 [(wire_in) = true]; + MessageType_MoneroLiveRefreshStartAck = 553 [(wire_out) = true]; + MessageType_MoneroLiveRefreshStepRequest = 554 [(wire_in) = true]; + MessageType_MoneroLiveRefreshStepAck = 555 [(wire_out) = true]; + MessageType_MoneroLiveRefreshFinalRequest = 556 [(wire_in) = true]; + MessageType_MoneroLiveRefreshFinalAck = 557 [(wire_out) = true]; + + // EOS + MessageType_EosGetPublicKey = 600 [(wire_in) = true]; + MessageType_EosPublicKey = 601 [(wire_out) = true]; + MessageType_EosSignTx = 602 [(wire_in) = true]; + MessageType_EosTxActionRequest = 603 [(wire_out) = true]; + MessageType_EosTxActionAck = 604 [(wire_in) = true]; + MessageType_EosSignedTx = 605 [(wire_out) = true]; + + // Binance + MessageType_BinanceGetAddress = 700 [(wire_in) = true]; + MessageType_BinanceAddress = 701 [(wire_out) = true]; + MessageType_BinanceGetPublicKey = 702 [(wire_in) = true]; + MessageType_BinancePublicKey = 703 [(wire_out) = true]; + MessageType_BinanceSignTx = 704 [(wire_in) = true]; + MessageType_BinanceTxRequest = 705 [(wire_out) = true]; + MessageType_BinanceTransferMsg = 706 [(wire_in) = true]; + MessageType_BinanceOrderMsg = 707 [(wire_in) = true]; + MessageType_BinanceCancelMsg = 708 [(wire_in) = true]; + MessageType_BinanceSignedTx = 709 [(wire_out) = true]; +} diff --git a/accounts/usbwallet/trezor/trezor.go b/accounts/usbwallet/trezor/trezor.go new file mode 100644 index 0000000..4324258 --- /dev/null +++ b/accounts/usbwallet/trezor/trezor.go @@ -0,0 +1,70 @@ +// 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 . + +// This file contains the implementation for interacting with the Trezor hardware +// wallets. The wire protocol spec can be found on the SatoshiLabs website: +// https://docs.trezor.io/trezor-firmware/common/message-workflows.html + +// !!! STAHP !!! +// +// Before you touch the protocol files, you need to be aware of a breaking change +// that occurred between firmware versions 1.7.3->1.8.0 (Model One) and 2.0.10-> +// 2.1.0 (Model T). The Ethereum address representation was changed from the 20 +// byte binary blob to a 42 byte hex string. The upstream protocol buffer files +// only support the new format, so blindly pulling in a new spec will break old +// devices! +// +// The Trezor devs had the foresight to add the string version as a new message +// code instead of replacing the binary one. This means that the proto file can +// actually define both the old and the new versions as optional. Please ensure +// that you add back the old addresses everywhere (to avoid name clash. use the +// addressBin and addressHex names). +// +// If in doubt, reach out to @karalabe. + +// To regenerate the protocol files in this package: +// - Download the latest protoc https://github.com/protocolbuffers/protobuf/releases +// - Build with the usual `./configure && make` and ensure it's on your $PATH +// - Delete all the .proto and .pb.go files, pull in fresh ones from Trezor +// - Grab the latest Go plugin `go get -u github.com/golang/protobuf/protoc-gen-go` +// - Vendor in the latest Go plugin `govendor fetch github.com/golang/protobuf/...` + +//go:generate protoc -I/usr/local/include:. --go_out=paths=source_relative:. messages.proto messages-common.proto messages-management.proto messages-ethereum.proto + +// Package trezor contains the wire protocol. +package trezor + +import ( + "reflect" + + "github.com/golang/protobuf/proto" +) + +// Type returns the protocol buffer type number of a specific message. If the +// message is nil, this method panics! +func Type(msg proto.Message) uint16 { + return uint16(MessageType_value["MessageType_"+reflect.TypeOf(msg).Elem().Name()]) +} + +// Name returns the friendly message type name of a specific protocol buffer +// type number. +func Name(kind uint16) string { + name := MessageType_name[int32(kind)] + if len(name) < 12 { + return name + } + return name[12:] +} diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go new file mode 100644 index 0000000..0fd0415 --- /dev/null +++ b/accounts/usbwallet/wallet.go @@ -0,0 +1,643 @@ +// 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 usbwallet implements support for USB hardware wallets. +package usbwallet + +import ( + "context" + "fmt" + "io" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/karalabe/hid" +) + +// Maximum time between wallet health checks to detect USB unplugs. +const heartbeatCycle = time.Second + +// Minimum time to wait between self derivation attempts, even it the user is +// requesting accounts like crazy. +const selfDeriveThrottling = time.Second + +// driver defines the vendor specific functionality hardware wallets instances +// must implement to allow using them with the wallet lifecycle management. +type driver interface { + // Status returns a textual status to aid the user in the current state of the + // wallet. It also returns an error indicating any failure the wallet might have + // encountered. + Status() (string, error) + + // Open initializes access to a wallet instance. The passphrase parameter may + // or may not be used by the implementation of a particular wallet instance. + Open(device io.ReadWriter, passphrase string) error + + // Close releases any resources held by an open wallet instance. + Close() error + + // Heartbeat performs a sanity check against the hardware wallet to see if it + // is still online and healthy. + Heartbeat() error + + // Derive sends a derivation request to the USB device and returns the Ethereum + // address located on that path. + Derive(path accounts.DerivationPath) (common.Address, error) + + // SignTx sends the transaction to the USB device and waits for the user to confirm + // or deny the transaction. + SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) + + SignTypedMessage(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error) +} + +// wallet represents the common functionality shared by all USB hardware +// wallets to prevent reimplementing the same complex maintenance mechanisms +// for different vendors. +type wallet struct { + hub *Hub // USB hub scanning + driver driver // Hardware implementation of the low level device operations + url *accounts.URL // Textual URL uniquely identifying this wallet + + info hid.DeviceInfo // Known USB device infos about the wallet + device hid.Device // USB device advertising itself as a hardware wallet + + accounts []accounts.Account // List of derive accounts pinned on the hardware wallet + paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations + + deriveNextPaths []accounts.DerivationPath // Next derivation paths for account auto-discovery (multiple bases supported) + deriveNextAddrs []common.Address // Next derived account addresses for auto-discovery (multiple bases supported) + deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with + deriveReq chan chan struct{} // Channel to request a self-derivation on + deriveQuit chan chan error // Channel to terminate the self-deriver with + + healthQuit chan chan error + + // Locking a hardware wallet is a bit special. Since hardware devices are lower + // performing, any communication with them might take a non negligible amount of + // time. Worse still, waiting for user confirmation can take arbitrarily long, + // but exclusive communication must be upheld during. Locking the entire wallet + // in the mean time however would stall any parts of the system that don't want + // to communicate, just read some state (e.g. list the accounts). + // + // As such, a hardware wallet needs two locks to function correctly. A state + // lock can be used to protect the wallet's software-side internal state, which + // must not be held exclusively during hardware communication. A communication + // lock can be used to achieve exclusive access to the device itself, this one + // however should allow "skipping" waiting for operations that might want to + // use the device, but can live without too (e.g. account self-derivation). + // + // Since we have two locks, it's important to know how to properly use them: + // - Communication requires the `device` to not change, so obtaining the + // commsLock should be done after having a stateLock. + // - Communication must not disable read access to the wallet state, so it + // must only ever hold a *read* lock to stateLock. + commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked + stateLock sync.RWMutex // Protects read and write access to the wallet struct fields + + log log.Logger // Contextual logger to tag the base with its id +} + +// URL implements accounts.Wallet, returning the URL of the USB hardware device. +func (w *wallet) URL() accounts.URL { + return *w.url // Immutable, no need for a lock +} + +// Status implements accounts.Wallet, returning a custom status message from the +// underlying vendor-specific hardware wallet implementation. +func (w *wallet) Status() (string, error) { + w.stateLock.RLock() // No device communication, state lock is enough + defer w.stateLock.RUnlock() + + status, failure := w.driver.Status() + if w.device == nil { + return "Closed", failure + } + return status, failure +} + +// Open implements accounts.Wallet, attempting to open a USB connection to the +// hardware wallet. +func (w *wallet) Open(passphrase string) error { + w.stateLock.Lock() // State lock is enough since there's no connection yet at this point + defer w.stateLock.Unlock() + + // If the device was already opened once, refuse to try again + if w.paths != nil { + return accounts.ErrWalletAlreadyOpen + } + // Make sure the actual device connection is done only once + if w.device == nil { + device, err := w.info.Open() + if err != nil { + return err + } + w.device = device + w.commsLock = make(chan struct{}, 1) + w.commsLock <- struct{}{} // Enable lock + } + // Delegate device initialization to the underlying driver + if err := w.driver.Open(w.device, passphrase); err != nil { + return err + } + // Connection successful, start life-cycle management + w.paths = make(map[common.Address]accounts.DerivationPath) + + w.deriveReq = make(chan chan struct{}) + w.deriveQuit = make(chan chan error) + w.healthQuit = make(chan chan error) + + go w.heartbeat() + go w.selfDerive() + + // Notify anyone listening for wallet events that a new device is accessible + go w.hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened}) + + return nil +} + +// heartbeat is a health check loop for the USB wallets to periodically verify +// whether they are still present or if they malfunctioned. +func (w *wallet) heartbeat() { + w.log.Debug("USB wallet health-check started") + defer w.log.Debug("USB wallet health-check stopped") + + // Execute heartbeat checks until termination or error + var ( + errc chan error + err error + ) + for errc == nil && err == nil { + // Wait until termination is requested or the heartbeat cycle arrives + select { + case errc = <-w.healthQuit: + // Termination requested + continue + case <-time.After(heartbeatCycle): + // Heartbeat time + } + // Execute a tiny data exchange to see responsiveness + w.stateLock.RLock() + if w.device == nil { + // Terminated while waiting for the lock + w.stateLock.RUnlock() + continue + } + <-w.commsLock // Don't lock state while resolving version + err = w.driver.Heartbeat() + w.commsLock <- struct{}{} + w.stateLock.RUnlock() + + if err != nil { + w.stateLock.Lock() // Lock state to tear the wallet down + w.close() + w.stateLock.Unlock() + } + // Ignore non hardware related errors + err = nil + } + // In case of error, wait for termination + if err != nil { + w.log.Debug("USB wallet health-check failed", "err", err) + errc = <-w.healthQuit + } + errc <- err +} + +// Close implements accounts.Wallet, closing the USB connection to the device. +func (w *wallet) Close() error { + // Ensure the wallet was opened + w.stateLock.RLock() + hQuit, dQuit := w.healthQuit, w.deriveQuit + w.stateLock.RUnlock() + + // Terminate the health checks + var herr error + if hQuit != nil { + errc := make(chan error) + hQuit <- errc + herr = <-errc // Save for later, we *must* close the USB + } + // Terminate the self-derivations + var derr error + if dQuit != nil { + errc := make(chan error) + dQuit <- errc + derr = <-errc // Save for later, we *must* close the USB + } + // Terminate the device connection + w.stateLock.Lock() + defer w.stateLock.Unlock() + + w.healthQuit = nil + w.deriveQuit = nil + w.deriveReq = nil + + if err := w.close(); err != nil { + return err + } + if herr != nil { + return herr + } + return derr +} + +// close is the internal wallet closer that terminates the USB connection and +// resets all the fields to their defaults. +// +// Note, close assumes the state lock is held! +func (w *wallet) close() error { + // Allow duplicate closes, especially for health-check failures + if w.device == nil { + return nil + } + // Close the device, clear everything, then return + w.device.Close() + w.device = nil + + w.accounts, w.paths = nil, nil + return w.driver.Close() +} + +// Accounts implements accounts.Wallet, returning the list of accounts pinned to +// the USB hardware wallet. If self-derivation was enabled, the account list is +// periodically expanded based on current chain state. +func (w *wallet) Accounts() []accounts.Account { + // Attempt self-derivation if it's running + reqc := make(chan struct{}, 1) + select { + case w.deriveReq <- reqc: + // Self-derivation request accepted, wait for it + <-reqc + default: + // Self-derivation offline, throttled or busy, skip + } + // Return whatever account list we ended up with + w.stateLock.RLock() + defer w.stateLock.RUnlock() + + cpy := make([]accounts.Account, len(w.accounts)) + copy(cpy, w.accounts) + return cpy +} + +// selfDerive is an account derivation loop that upon request attempts to find +// new non-zero accounts. +func (w *wallet) selfDerive() { + w.log.Debug("USB wallet self-derivation started") + defer w.log.Debug("USB wallet self-derivation stopped") + + // Execute self-derivations until termination or error + var ( + reqc chan struct{} + errc chan error + err error + ) + for errc == nil && err == nil { + // Wait until either derivation or termination is requested + select { + case errc = <-w.deriveQuit: + // Termination requested + continue + case reqc = <-w.deriveReq: + // Account discovery requested + } + // Derivation needs a chain and device access, skip if either unavailable + w.stateLock.RLock() + if w.device == nil || w.deriveChain == nil { + w.stateLock.RUnlock() + reqc <- struct{}{} + continue + } + select { + case <-w.commsLock: + default: + w.stateLock.RUnlock() + reqc <- struct{}{} + continue + } + // Device lock obtained, derive the next batch of accounts + var ( + accs []accounts.Account + paths []accounts.DerivationPath + + nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...) + nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) + + context = context.Background() + ) + for i := 0; i < len(nextAddrs); i++ { + for empty := false; !empty; { + // Retrieve the next derived Ethereum account + if nextAddrs[i] == (common.Address{}) { + if nextAddrs[i], err = w.driver.Derive(nextPaths[i]); err != nil { + w.log.Warn("USB wallet account derivation failed", "err", err) + break + } + } + // Check the account's status against the current chain state + var ( + balance *big.Int + nonce uint64 + ) + balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil) + if err != nil { + w.log.Warn("USB wallet balance retrieval failed", "err", err) + break + } + nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil) + if err != nil { + w.log.Warn("USB wallet nonce retrieval failed", "err", err) + break + } + // We've just self-derived a new account, start tracking it locally + // unless the account was empty. + path := make(accounts.DerivationPath, len(nextPaths[i])) + copy(path[:], nextPaths[i][:]) + if balance.Sign() == 0 && nonce == 0 { + empty = true + // If it indeed was empty, make a log output for it anyway. In the case + // of legacy-ledger, the first account on the legacy-path will + // be shown to the user, even if we don't actively track it + if i < len(nextAddrs)-1 { + w.log.Info("Skipping tracking first account on legacy path, use personal.deriveAccount(,, false) to track", + "path", path, "address", nextAddrs[i]) + break + } + } + paths = append(paths, path) + account := accounts.Account{ + Address: nextAddrs[i], + URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, + } + accs = append(accs, account) + + // Display a log message to the user for new (or previously empty accounts) + if _, known := w.paths[nextAddrs[i]]; !known || (!empty && nextAddrs[i] == w.deriveNextAddrs[i]) { + w.log.Info("USB wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce) + } + // Fetch the next potential account + if !empty { + nextAddrs[i] = common.Address{} + nextPaths[i][len(nextPaths[i])-1]++ + } + } + } + // Self derivation complete, release device lock + w.commsLock <- struct{}{} + w.stateLock.RUnlock() + + // Insert any accounts successfully derived + w.stateLock.Lock() + for i := 0; i < len(accs); i++ { + if _, ok := w.paths[accs[i].Address]; !ok { + w.accounts = append(w.accounts, accs[i]) + w.paths[accs[i].Address] = paths[i] + } + } + // Shift the self-derivation forward + // TODO(karalabe): don't overwrite changes from wallet.SelfDerive + w.deriveNextAddrs = nextAddrs + w.deriveNextPaths = nextPaths + w.stateLock.Unlock() + + // Notify the user of termination and loop after a bit of time (to avoid trashing) + reqc <- struct{}{} + if err == nil { + select { + case errc = <-w.deriveQuit: + // Termination requested, abort + case <-time.After(selfDeriveThrottling): + // Waited enough, willing to self-derive again + } + } + } + // In case of error, wait for termination + if err != nil { + w.log.Debug("USB wallet self-derivation failed", "err", err) + errc = <-w.deriveQuit + } + errc <- err +} + +// Contains implements accounts.Wallet, returning whether a particular account is +// or is not pinned into this wallet instance. Although we could attempt to resolve +// unpinned accounts, that would be an non-negligible hardware operation. +func (w *wallet) Contains(account accounts.Account) bool { + w.stateLock.RLock() + defer w.stateLock.RUnlock() + + _, exists := w.paths[account.Address] + return exists +} + +// Derive implements accounts.Wallet, deriving a new account at the specific +// derivation path. If pin is set to true, the account will be added to the list +// of tracked accounts. +func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + // Try to derive the actual account and update its URL if successful + w.stateLock.RLock() // Avoid device disappearing during derivation + + if w.device == nil { + w.stateLock.RUnlock() + return accounts.Account{}, accounts.ErrWalletClosed + } + <-w.commsLock // Avoid concurrent hardware access + address, err := w.driver.Derive(path) + w.commsLock <- struct{}{} + + w.stateLock.RUnlock() + + // If an error occurred or no pinning was requested, return + if err != nil { + return accounts.Account{}, err + } + account := accounts.Account{ + Address: address, + URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, + } + if !pin { + return account, nil + } + // Pinning needs to modify the state + 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)) + copy(w.paths[address], path) + } + return account, nil +} + +// SelfDerive sets a base account derivation path from which the wallet attempts +// to discover non zero accounts and automatically add them to list of tracked +// accounts. +// +// Note, self derivation will increment the last component of the specified path +// opposed to descending into a child path to allow discovering accounts starting +// from non zero components. +// +// Some hardware wallets switched derivation paths through their evolution, so +// this method supports providing multiple bases to discover old user accounts +// too. Only the last base will be used to derive the next empty account. +// +// You can disable automatic account discovery by calling SelfDerive with a nil +// chain state reader. +func (w *wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { + w.stateLock.Lock() + defer w.stateLock.Unlock() + + w.deriveNextPaths = make([]accounts.DerivationPath, len(bases)) + for i, base := range bases { + w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base)) + copy(w.deriveNextPaths[i][:], base[:]) + } + w.deriveNextAddrs = make([]common.Address, len(bases)) + w.deriveChain = chain +} + +// signHash implements accounts.Wallet, however signing arbitrary data is not +// supported for hardware wallets, so this method will always return an error. +func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { + return nil, accounts.ErrNotSupported +} + +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { + // Unless we are doing 712 signing, simply dispatch to signHash + if !(mimeType == accounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) { + return w.signHash(account, crypto.Keccak256(data)) + } + + // dispatch to 712 signing if the mimetype is TypedData and the format matches + w.stateLock.RLock() // Comms have own mutex, this is for the state fields + defer w.stateLock.RUnlock() + + // If the wallet is closed, abort + if w.device == nil { + return nil, accounts.ErrWalletClosed + } + // Make sure the requested account is contained within + path, ok := w.paths[account.Address] + if !ok { + return nil, accounts.ErrUnknownAccount + } + // All infos gathered and metadata checks out, request signing + <-w.commsLock + defer func() { w.commsLock <- struct{}{} }() + + // Ensure the device isn't screwed with while user confirmation is pending + // TODO(karalabe): remove if hotplug lands on Windows + w.hub.commsLock.Lock() + w.hub.commsPend++ + w.hub.commsLock.Unlock() + + defer func() { + w.hub.commsLock.Lock() + w.hub.commsPend-- + w.hub.commsLock.Unlock() + }() + // Sign the transaction + signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66]) + if err != nil { + return nil, err + } + return signature, nil +} + +// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given +// data with the given account using passphrase as extra authentication. +// Since USB wallets don't rely on passphrases, these are silently ignored. +func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { + return w.SignData(account, mimeType, data) +} + +func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { + return w.signHash(account, accounts.TextHash(text)) +} + +// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger +// wallet to request a confirmation from the user. It returns either the signed +// transaction or a failure if the user denied the transaction. +// +// Note, if the version of the Ethereum application running on the Ledger wallet is +// too old to sign EIP-155 transactions, but such is requested nonetheless, an error +// will be returned opposed to silently signing in Homestead mode. +func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + w.stateLock.RLock() // Comms have own mutex, this is for the state fields + defer w.stateLock.RUnlock() + + // If the wallet is closed, abort + if w.device == nil { + return nil, accounts.ErrWalletClosed + } + // Make sure the requested account is contained within + path, ok := w.paths[account.Address] + if !ok { + return nil, accounts.ErrUnknownAccount + } + // All infos gathered and metadata checks out, request signing + <-w.commsLock + defer func() { w.commsLock <- struct{}{} }() + + // Ensure the device isn't screwed with while user confirmation is pending + // TODO(karalabe): remove if hotplug lands on Windows + w.hub.commsLock.Lock() + w.hub.commsPend++ + w.hub.commsLock.Unlock() + + defer func() { + w.hub.commsLock.Lock() + w.hub.commsPend-- + w.hub.commsLock.Unlock() + }() + // Sign the transaction and verify the sender to avoid hardware fault surprises + sender, signed, err := w.driver.SignTx(path, tx, chainID) + if err != nil { + return nil, err + } + if sender != account.Address { + return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) + } + return signed, nil +} + +// SignTextWithPassphrase implements accounts.Wallet, however signing arbitrary +// data is not supported for Ledger wallets, so this method will always return +// an error. +func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { + return w.SignText(account, accounts.TextHash(text)) +} + +// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given +// transaction with the given account using passphrase as extra authentication. +// Since USB wallets don't rely on passphrases, these are silently ignored. +func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + return w.SignTx(account, tx, chainID) +} diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..9236953 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,58 @@ +clone_depth: 5 +version: "{branch}.{build}" + +image: + - Ubuntu + - Visual Studio 2019 + +environment: + matrix: + - GETH_ARCH: amd64 + GETH_MINGW: 'C:\msys64\mingw64' + - GETH_ARCH: 386 + GETH_MINGW: 'C:\msys64\mingw32' + +install: + - git submodule update --init --depth 1 --recursive + - go version + +for: + # Linux has its own script without -arch and -cc. + # The linux builder also runs lint. + - matrix: + only: + - image: Ubuntu + build_script: + - go run build/ci.go lint + - go run build/ci.go generate -verify + - go run build/ci.go install -dlgo + test_script: + - go run build/ci.go test -dlgo -short + + # linux/386 is disabled. + - matrix: + exclude: + - image: Ubuntu + GETH_ARCH: 386 + + # Windows builds for amd64 + 386. + - matrix: + only: + - image: Visual Studio 2019 + environment: + # We use gcc from MSYS2 because it is the most recent compiler version available on + # AppVeyor. Note: gcc.exe only works properly if the corresponding bin/ directory is + # contained in PATH. + GETH_CC: '%GETH_MINGW%\bin\gcc.exe' + PATH: '%GETH_MINGW%\bin;C:\Program Files (x86)\NSIS\;%PATH%' + build_script: + - 'echo %GETH_ARCH%' + - 'echo %GETH_CC%' + - '%GETH_CC% --version' + - go run build/ci.go install -dlgo -arch %GETH_ARCH% -cc %GETH_CC% + after_build: + # Upload builds. Note that ci.go makes this a no-op PR builds. + - 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% -short diff --git a/beacon/blsync/block_sync.go b/beacon/blsync/block_sync.go new file mode 100755 index 0000000..ff689a9 --- /dev/null +++ b/beacon/blsync/block_sync.go @@ -0,0 +1,163 @@ +// 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 blsync + +import ( + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/light/sync" + "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/event" + "github.com/ethereum/go-ethereum/log" +) + +// beaconBlockSync implements request.Module; it fetches the beacon blocks belonging +// to the validated and prefetch heads. +type beaconBlockSync struct { + recentBlocks *lru.Cache[common.Hash, *types.BeaconBlock] + locked map[common.Hash]request.ServerAndID + serverHeads map[request.Server]common.Hash + headTracker headTracker + + lastHeadInfo types.HeadInfo + chainHeadFeed event.FeedOf[types.ChainHeadEvent] +} + +type headTracker interface { + PrefetchHead() types.HeadInfo + ValidatedOptimistic() (types.OptimisticUpdate, bool) + ValidatedFinality() (types.FinalityUpdate, bool) +} + +// newBeaconBlockSync returns a new beaconBlockSync. +func newBeaconBlockSync(headTracker headTracker) *beaconBlockSync { + return &beaconBlockSync{ + headTracker: headTracker, + recentBlocks: lru.NewCache[common.Hash, *types.BeaconBlock](10), + locked: make(map[common.Hash]request.ServerAndID), + serverHeads: make(map[request.Server]common.Hash), + } +} + +func (s *beaconBlockSync) SubscribeChainHead(ch chan<- types.ChainHeadEvent) event.Subscription { + return s.chainHeadFeed.Subscribe(ch) +} + +// Process implements request.Module. +func (s *beaconBlockSync) Process(requester request.Requester, events []request.Event) { + for _, event := range events { + switch event.Type { + case request.EvResponse, request.EvFail, request.EvTimeout: + sid, req, resp := event.RequestInfo() + blockRoot := common.Hash(req.(sync.ReqBeaconBlock)) + log.Debug("Beacon block event", "type", event.Type.Name, "hash", blockRoot) + if resp != nil { + s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock)) + } + if s.locked[blockRoot] == sid { + delete(s.locked, blockRoot) + } + case sync.EvNewHead: + s.serverHeads[event.Server] = event.Data.(types.HeadInfo).BlockRoot + case request.EvUnregistered: + delete(s.serverHeads, event.Server) + } + } + s.updateEventFeed() + // request validated head block if unavailable and not yet requested + if vh, ok := s.headTracker.ValidatedOptimistic(); ok { + s.tryRequestBlock(requester, vh.Attested.Hash(), false) + } + // request prefetch head if the given server has announced it + if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) { + s.tryRequestBlock(requester, prefetchHead, true) + } +} + +func (s *beaconBlockSync) tryRequestBlock(requester request.Requester, blockRoot common.Hash, needSameHead bool) { + if _, ok := s.recentBlocks.Get(blockRoot); ok { + return + } + if _, ok := s.locked[blockRoot]; ok { + return + } + for _, server := range requester.CanSendTo() { + if needSameHead && (s.serverHeads[server] != blockRoot) { + continue + } + id := requester.Send(server, sync.ReqBeaconBlock(blockRoot)) + s.locked[blockRoot] = request.ServerAndID{Server: server, ID: id} + return + } +} + +func blockHeadInfo(block *types.BeaconBlock) types.HeadInfo { + if block == nil { + return types.HeadInfo{} + } + return types.HeadInfo{Slot: block.Slot(), BlockRoot: block.Root()} +} + +func (s *beaconBlockSync) updateEventFeed() { + optimistic, ok := s.headTracker.ValidatedOptimistic() + if !ok { + return + } + + validatedHead := optimistic.Attested.Hash() + headBlock, ok := s.recentBlocks.Get(validatedHead) + if !ok { + return + } + + var finalizedHash common.Hash + if finality, ok := s.headTracker.ValidatedFinality(); ok { + he := optimistic.Attested.Epoch() + fe := finality.Attested.Header.Epoch() + switch { + case he == fe: + finalizedHash = finality.Finalized.PayloadHeader.BlockHash() + case he < fe: + return + case he == fe+1: + parent, ok := s.recentBlocks.Get(optimistic.Attested.ParentRoot) + if !ok || parent.Slot()/params.EpochLength == fe { + return // head is at first slot of next epoch, wait for finality update + } + } + } + + headInfo := blockHeadInfo(headBlock) + if headInfo == s.lastHeadInfo { + return + } + s.lastHeadInfo = headInfo + + // new head block and finality info available; extract executable data and send event to feed + execBlock, err := headBlock.ExecutionPayload() + if err != nil { + log.Error("Error extracting execution block from validated beacon block", "error", err) + return + } + s.chainHeadFeed.Send(types.ChainHeadEvent{ + BeaconHead: optimistic.Attested.Header, + Block: execBlock, + Finalized: finalizedHash, + }) +} diff --git a/beacon/blsync/block_sync_test.go b/beacon/blsync/block_sync_test.go new file mode 100644 index 0000000..3d3b9e5 --- /dev/null +++ b/beacon/blsync/block_sync_test.go @@ -0,0 +1,160 @@ +// 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 blsync + +import ( + "testing" + + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/light/sync" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/common" + zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/deneb" +) + +var ( + testServer1 = testServer("testServer1") + testServer2 = testServer("testServer2") + + testBlock1 = types.NewBeaconBlock(&deneb.BeaconBlock{ + Slot: 123, + Body: deneb.BeaconBlockBody{ + ExecutionPayload: deneb.ExecutionPayload{ + BlockNumber: 456, + BlockHash: zrntcommon.Hash32(common.HexToHash("905ac721c4058d9ed40b27b6b9c1bdd10d4333e4f3d9769100bf9dfb80e5d1f6")), + }, + }, + }) + testBlock2 = types.NewBeaconBlock(&deneb.BeaconBlock{ + Slot: 124, + Body: deneb.BeaconBlockBody{ + ExecutionPayload: deneb.ExecutionPayload{ + BlockNumber: 457, + BlockHash: zrntcommon.Hash32(common.HexToHash("011703f39c664efc1c6cf5f49ca09b595581eec572d4dfddd3d6179a9e63e655")), + }, + }, + }) +) + +type testServer string + +func (t testServer) Name() string { + return string(t) +} + +func TestBlockSync(t *testing.T) { + ht := &testHeadTracker{} + blockSync := newBeaconBlockSync(ht) + headCh := make(chan types.ChainHeadEvent, 16) + blockSync.SubscribeChainHead(headCh) + ts := sync.NewTestScheduler(t, blockSync) + ts.AddServer(testServer1, 1) + ts.AddServer(testServer2, 1) + + expHeadBlock := func(expHead *types.BeaconBlock) { + t.Helper() + var expNumber, headNumber uint64 + if expHead != nil { + p, _ := expHead.ExecutionPayload() + expNumber = p.NumberU64() + } + select { + case event := <-headCh: + headNumber = event.Block.NumberU64() + default: + } + if headNumber != expNumber { + t.Errorf("Wrong head block, expected block number %d, got %d)", expNumber, headNumber) + } + } + + // no block requests expected until head tracker knows about a head + ts.Run(1) + expHeadBlock(nil) + + // set block 1 as prefetch head, announced by server 2 + head1 := blockHeadInfo(testBlock1) + ht.prefetch = head1 + ts.ServerEvent(sync.EvNewHead, testServer2, head1) + + // expect request to server 2 which has announced the head + ts.Run(2, testServer2, sync.ReqBeaconBlock(head1.BlockRoot)) + + // valid response + ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testBlock1) + ts.AddAllowance(testServer2, 1) + ts.Run(3) + // head block still not expected as the fetched block is not the validated head yet + expHeadBlock(nil) + + // set as validated head, expect no further requests but block 1 set as head block + ht.validated.Header = testBlock1.Header() + ts.Run(4) + expHeadBlock(testBlock1) + + // set block 2 as prefetch head, announced by server 1 + head2 := blockHeadInfo(testBlock2) + ht.prefetch = head2 + ts.ServerEvent(sync.EvNewHead, testServer1, head2) + // expect request to server 1 + ts.Run(5, testServer1, sync.ReqBeaconBlock(head2.BlockRoot)) + + // req2 fails, no further requests expected because server 2 has not announced it + ts.RequestEvent(request.EvFail, ts.Request(5, 1), nil) + ts.Run(6) + + // set as validated head before retrieving block; now it's assumed to be available from server 2 too + ht.validated.Header = testBlock2.Header() + // expect req2 retry to server 2 + ts.Run(7, testServer2, sync.ReqBeaconBlock(head2.BlockRoot)) + // now head block should be unavailable again + expHeadBlock(nil) + + // valid response, now head block should be block 2 immediately as it is already validated + ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testBlock2) + ts.Run(8) + expHeadBlock(testBlock2) +} + +type testHeadTracker struct { + prefetch types.HeadInfo + validated types.SignedHeader +} + +func (h *testHeadTracker) PrefetchHead() types.HeadInfo { + return h.prefetch +} + +func (h *testHeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) { + return types.OptimisticUpdate{ + Attested: types.HeaderWithExecProof{Header: h.validated.Header}, + Signature: h.validated.Signature, + SignatureSlot: h.validated.SignatureSlot, + }, h.validated.Header != (types.Header{}) +} + +// TODO add test case for finality +func (h *testHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) { + finalized := types.NewExecutionHeader(new(deneb.ExecutionPayloadHeader)) + return types.FinalityUpdate{ + Attested: types.HeaderWithExecProof{Header: h.validated.Header}, + Finalized: types.HeaderWithExecProof{PayloadHeader: finalized}, + Signature: h.validated.Signature, + SignatureSlot: h.validated.SignatureSlot, + }, h.validated.Header != (types.Header{}) +} diff --git a/beacon/blsync/client.go b/beacon/blsync/client.go new file mode 100644 index 0000000..39a1c6e --- /dev/null +++ b/beacon/blsync/client.go @@ -0,0 +1,115 @@ +// Copyright 2024 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 blsync + +import ( + "strings" + + "github.com/ethereum/go-ethereum/beacon/light" + "github.com/ethereum/go-ethereum/beacon/light/api" + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/light/sync" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" +) + +type Client struct { + urls []string + customHeader map[string]string + chainConfig *lightClientConfig + scheduler *request.Scheduler + blockSync *beaconBlockSync + engineRPC *rpc.Client + + chainHeadSub event.Subscription + engineClient *engineClient +} + +func NewClient(ctx *cli.Context) *Client { + if !ctx.IsSet(utils.BeaconApiFlag.Name) { + utils.Fatalf("Beacon node light client API URL not specified") + } + var ( + chainConfig = makeChainConfig(ctx) + customHeader = make(map[string]string) + ) + for _, s := range ctx.StringSlice(utils.BeaconApiHeaderFlag.Name) { + kv := strings.Split(s, ":") + if len(kv) != 2 { + utils.Fatalf("Invalid custom API header entry: %s", s) + } + customHeader[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1]) + } + + // create data structures + var ( + db = memorydb.New() + threshold = ctx.Int(utils.BeaconThresholdFlag.Name) + committeeChain = light.NewCommitteeChain(db, chainConfig.ChainConfig, threshold, !ctx.Bool(utils.BeaconNoFilterFlag.Name)) + headTracker = light.NewHeadTracker(committeeChain, threshold) + ) + headSync := sync.NewHeadSync(headTracker, committeeChain) + + // set up scheduler and sync modules + scheduler := request.NewScheduler() + checkpointInit := sync.NewCheckpointInit(committeeChain, chainConfig.Checkpoint) + forwardSync := sync.NewForwardUpdateSync(committeeChain) + beaconBlockSync := newBeaconBlockSync(headTracker) + scheduler.RegisterTarget(headTracker) + scheduler.RegisterTarget(committeeChain) + scheduler.RegisterModule(checkpointInit, "checkpointInit") + scheduler.RegisterModule(forwardSync, "forwardSync") + scheduler.RegisterModule(headSync, "headSync") + scheduler.RegisterModule(beaconBlockSync, "beaconBlockSync") + + return &Client{ + scheduler: scheduler, + urls: ctx.StringSlice(utils.BeaconApiFlag.Name), + customHeader: customHeader, + chainConfig: &chainConfig, + blockSync: beaconBlockSync, + } +} + +func (c *Client) SetEngineRPC(engine *rpc.Client) { + c.engineRPC = engine +} + +func (c *Client) Start() error { + headCh := make(chan types.ChainHeadEvent, 16) + c.chainHeadSub = c.blockSync.SubscribeChainHead(headCh) + c.engineClient = startEngineClient(c.chainConfig, c.engineRPC, headCh) + + c.scheduler.Start() + for _, url := range c.urls { + beaconApi := api.NewBeaconLightApi(url, c.customHeader) + c.scheduler.RegisterServer(request.NewServer(api.NewApiServer(beaconApi), &mclock.System{})) + } + return nil +} + +func (c *Client) Stop() error { + c.engineClient.stop() + c.chainHeadSub.Unsubscribe() + c.scheduler.Stop() + return nil +} diff --git a/beacon/blsync/config.go b/beacon/blsync/config.go new file mode 100644 index 0000000..93ed813 --- /dev/null +++ b/beacon/blsync/config.go @@ -0,0 +1,129 @@ +// 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 blsync + +import ( + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/urfave/cli/v2" +) + +// lightClientConfig contains beacon light client configuration +type lightClientConfig struct { + *types.ChainConfig + Checkpoint common.Hash +} + +var ( + MainnetConfig = lightClientConfig{ + ChainConfig: (&types.ChainConfig{ + GenesisValidatorsRoot: common.HexToHash("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"), + GenesisTime: 1606824023, + }). + AddFork("GENESIS", 0, []byte{0, 0, 0, 0}). + AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}). + AddFork("BELLATRIX", 144896, []byte{2, 0, 0, 0}). + AddFork("CAPELLA", 194048, []byte{3, 0, 0, 0}). + AddFork("DENEB", 269568, []byte{4, 0, 0, 0}), + Checkpoint: common.HexToHash("0x388be41594ec7d6a6894f18c73f3469f07e2c19a803de4755d335817ed8e2e5a"), + } + + SepoliaConfig = lightClientConfig{ + ChainConfig: (&types.ChainConfig{ + GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"), + GenesisTime: 1655733600, + }). + AddFork("GENESIS", 0, []byte{144, 0, 0, 105}). + AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}). + AddFork("BELLATRIX", 100, []byte{144, 0, 0, 113}). + AddFork("CAPELLA", 56832, []byte{144, 0, 0, 114}). + AddFork("DENEB", 132608, []byte{144, 0, 0, 115}), + Checkpoint: common.HexToHash("0x1005a6d9175e96bfbce4d35b80f468e9bff0b674e1e861d16e09e10005a58e81"), + } + + GoerliConfig = lightClientConfig{ + ChainConfig: (&types.ChainConfig{ + GenesisValidatorsRoot: common.HexToHash("0x043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb"), + GenesisTime: 1614588812, + }). + AddFork("GENESIS", 0, []byte{0, 0, 16, 32}). + AddFork("ALTAIR", 36660, []byte{1, 0, 16, 32}). + AddFork("BELLATRIX", 112260, []byte{2, 0, 16, 32}). + AddFork("CAPELLA", 162304, []byte{3, 0, 16, 32}). + AddFork("DENEB", 231680, []byte{4, 0, 16, 32}), + Checkpoint: common.HexToHash("0x53a0f4f0a378e2c4ae0a9ee97407eb69d0d737d8d8cd0a5fb1093f42f7b81c49"), + } +) + +func makeChainConfig(ctx *cli.Context) lightClientConfig { + var config lightClientConfig + customConfig := ctx.IsSet(utils.BeaconConfigFlag.Name) + utils.CheckExclusive(ctx, utils.MainnetFlag, utils.GoerliFlag, utils.SepoliaFlag, utils.BeaconConfigFlag) + switch { + case ctx.Bool(utils.MainnetFlag.Name): + config = MainnetConfig + case ctx.Bool(utils.SepoliaFlag.Name): + config = SepoliaConfig + case ctx.Bool(utils.GoerliFlag.Name): + config = GoerliConfig + default: + if !customConfig { + config = MainnetConfig + } + } + // Genesis root and time should always be specified together with custom chain config + if customConfig { + if !ctx.IsSet(utils.BeaconGenesisRootFlag.Name) { + utils.Fatalf("Custom beacon chain config is specified but genesis root is missing") + } + if !ctx.IsSet(utils.BeaconGenesisTimeFlag.Name) { + utils.Fatalf("Custom beacon chain config is specified but genesis time is missing") + } + if !ctx.IsSet(utils.BeaconCheckpointFlag.Name) { + utils.Fatalf("Custom beacon chain config is specified but checkpoint is missing") + } + config.ChainConfig = &types.ChainConfig{ + GenesisTime: ctx.Uint64(utils.BeaconGenesisTimeFlag.Name), + } + if c, err := hexutil.Decode(ctx.String(utils.BeaconGenesisRootFlag.Name)); err == nil && len(c) <= 32 { + copy(config.GenesisValidatorsRoot[:len(c)], c) + } else { + utils.Fatalf("Invalid hex string", "beacon.genesis.gvroot", ctx.String(utils.BeaconGenesisRootFlag.Name), "error", err) + } + if err := config.ChainConfig.LoadForks(ctx.String(utils.BeaconConfigFlag.Name)); err != nil { + utils.Fatalf("Could not load beacon chain config file", "file name", ctx.String(utils.BeaconConfigFlag.Name), "error", err) + } + } else { + if ctx.IsSet(utils.BeaconGenesisRootFlag.Name) { + utils.Fatalf("Genesis root is specified but custom beacon chain config is missing") + } + if ctx.IsSet(utils.BeaconGenesisTimeFlag.Name) { + utils.Fatalf("Genesis time is specified but custom beacon chain config is missing") + } + } + // Checkpoint is required with custom chain config and is optional with pre-defined config + if ctx.IsSet(utils.BeaconCheckpointFlag.Name) { + if c, err := hexutil.Decode(ctx.String(utils.BeaconCheckpointFlag.Name)); err == nil && len(c) <= 32 { + copy(config.Checkpoint[:len(c)], c) + } else { + utils.Fatalf("Invalid hex string", "beacon.checkpoint", ctx.String(utils.BeaconCheckpointFlag.Name), "error", err) + } + } + return config +} diff --git a/beacon/blsync/engineclient.go b/beacon/blsync/engineclient.go new file mode 100644 index 0000000..97ef6f5 --- /dev/null +++ b/beacon/blsync/engineclient.go @@ -0,0 +1,150 @@ +// Copyright 2024 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 blsync + +import ( + "context" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/common" + ctypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +type engineClient struct { + config *lightClientConfig + rpc *rpc.Client + rootCtx context.Context + cancelRoot context.CancelFunc + wg sync.WaitGroup +} + +func startEngineClient(config *lightClientConfig, rpc *rpc.Client, headCh <-chan types.ChainHeadEvent) *engineClient { + ctx, cancel := context.WithCancel(context.Background()) + ec := &engineClient{ + config: config, + rpc: rpc, + rootCtx: ctx, + cancelRoot: cancel, + } + ec.wg.Add(1) + go ec.updateLoop(headCh) + return ec +} + +func (ec *engineClient) stop() { + ec.cancelRoot() + ec.wg.Wait() +} + +func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) { + defer ec.wg.Done() + + for { + select { + case <-ec.rootCtx.Done(): + log.Debug("Stopping engine API update loop") + return + + case event := <-headCh: + if ec.rpc == nil { // dry run, no engine API specified + log.Info("New execution block retrieved", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "finalized", event.Finalized) + continue + } + + fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch()) + forkName := strings.ToLower(fork.Name) + + log.Debug("Calling NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash()) + if status, err := ec.callNewPayload(forkName, event); err == nil { + log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status) + } else { + log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err) + } + + log.Debug("Calling ForkchoiceUpdated", "head", event.Block.Hash()) + if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil { + log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status) + } else { + log.Error("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err) + } + } + } +} + +func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) (string, error) { + execData := engine.BlockToExecutableData(event.Block, nil, nil).ExecutionPayload + + var ( + method string + params = []any{execData} + ) + switch fork { + case "deneb": + method = "engine_newPayloadV3" + parentBeaconRoot := event.BeaconHead.ParentRoot + blobHashes := collectBlobHashes(event.Block) + params = append(params, blobHashes, parentBeaconRoot) + case "capella": + method = "engine_newPayloadV2" + default: + method = "engine_newPayloadV1" + } + + ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5) + defer cancel() + var resp engine.PayloadStatusV1 + err := ec.rpc.CallContext(ctx, &resp, method, params...) + return resp.Status, err +} + +func collectBlobHashes(b *ctypes.Block) []common.Hash { + list := make([]common.Hash, 0) + for _, tx := range b.Transactions() { + list = append(list, tx.BlobHashes()...) + } + return list +} + +func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHeadEvent) (string, error) { + update := engine.ForkchoiceStateV1{ + HeadBlockHash: event.Block.Hash(), + SafeBlockHash: event.Finalized, + FinalizedBlockHash: event.Finalized, + } + + var method string + switch fork { + case "deneb": + method = "engine_forkchoiceUpdatedV3" + case "capella": + method = "engine_forkchoiceUpdatedV2" + default: + method = "engine_forkchoiceUpdatedV1" + } + + ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5) + defer cancel() + var resp engine.ForkChoiceResponse + err := ec.rpc.CallContext(ctx, &resp, method, update, nil) + return resp.PayloadStatus.Status, err +} diff --git a/beacon/engine/errors.go b/beacon/engine/errors.go new file mode 100644 index 0000000..62773a0 --- /dev/null +++ b/beacon/engine/errors.go @@ -0,0 +1,88 @@ +// 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 engine + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" +) + +// EngineAPIError is a standardized error message between consensus and execution +// clients, also containing any custom error message Geth might include. +type EngineAPIError struct { + code int + msg string + err error +} + +func (e *EngineAPIError) ErrorCode() int { return e.code } +func (e *EngineAPIError) Error() string { return e.msg } +func (e *EngineAPIError) ErrorData() interface{} { + if e.err == nil { + return nil + } + return struct { + Error string `json:"err"` + }{e.err.Error()} +} + +// With returns a copy of the error with a new embedded custom data field. +func (e *EngineAPIError) With(err error) *EngineAPIError { + return &EngineAPIError{ + code: e.code, + msg: e.msg, + err: err, + } +} + +var ( + _ rpc.Error = new(EngineAPIError) + _ rpc.DataError = new(EngineAPIError) +) + +var ( + // VALID is returned by the engine API in the following calls: + // - newPayloadV1: if the payload was already known or was just validated and executed + // - forkchoiceUpdateV1: if the chain accepted the reorg (might ignore if it's stale) + VALID = "VALID" + + // INVALID is returned by the engine API in the following calls: + // - newPayloadV1: if the payload failed to execute on top of the local chain + // - forkchoiceUpdateV1: if the new head is unknown, pre-merge, or reorg to it fails + INVALID = "INVALID" + + // SYNCING is returned by the engine API in the following calls: + // - newPayloadV1: if the payload was accepted on top of an active sync + // - forkchoiceUpdateV1: if the new head was seen before, but not part of the chain + SYNCING = "SYNCING" + + // ACCEPTED is returned by the engine API in the following calls: + // - newPayloadV1: if the payload was accepted, but not processed (side chain) + ACCEPTED = "ACCEPTED" + + GenericServerError = &EngineAPIError{code: -32000, msg: "Server error"} + UnknownPayload = &EngineAPIError{code: -38001, msg: "Unknown payload"} + InvalidForkChoiceState = &EngineAPIError{code: -38002, msg: "Invalid forkchoice state"} + 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} + INVALID_TERMINAL_BLOCK = PayloadStatusV1{Status: INVALID, LatestValidHash: &common.Hash{}} +) diff --git a/beacon/engine/gen_blockparams.go b/beacon/engine/gen_blockparams.go new file mode 100644 index 0000000..b1f01b5 --- /dev/null +++ b/beacon/engine/gen_blockparams.go @@ -0,0 +1,66 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package engine + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*payloadAttributesMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (p PayloadAttributes) MarshalJSON() ([]byte, error) { + type PayloadAttributes struct { + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + 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) +} + +// UnmarshalJSON unmarshals from JSON. +func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { + type PayloadAttributes struct { + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + 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 { + return err + } + if dec.Timestamp == nil { + return errors.New("missing required field 'timestamp' for PayloadAttributes") + } + p.Timestamp = uint64(*dec.Timestamp) + if dec.Random == nil { + return errors.New("missing required field 'prevRandao' for PayloadAttributes") + } + p.Random = *dec.Random + if dec.SuggestedFeeRecipient == nil { + return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributes") + } + p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient + if dec.Withdrawals != nil { + p.Withdrawals = dec.Withdrawals + } + if dec.BeaconRoot != nil { + p.BeaconRoot = dec.BeaconRoot + } + return nil +} diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go new file mode 100644 index 0000000..6893d64 --- /dev/null +++ b/beacon/engine/gen_ed.go @@ -0,0 +1,158 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package engine + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*executableDataMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (e ExecutableData) MarshalJSON() ([]byte, error) { + type ExecutableData struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + } + var enc ExecutableData + enc.ParentHash = e.ParentHash + enc.FeeRecipient = e.FeeRecipient + enc.StateRoot = e.StateRoot + enc.ReceiptsRoot = e.ReceiptsRoot + enc.LogsBloom = e.LogsBloom + enc.Random = e.Random + enc.Number = hexutil.Uint64(e.Number) + enc.GasLimit = hexutil.Uint64(e.GasLimit) + enc.GasUsed = hexutil.Uint64(e.GasUsed) + enc.Timestamp = hexutil.Uint64(e.Timestamp) + enc.ExtraData = e.ExtraData + enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas) + enc.BlockHash = e.BlockHash + if e.Transactions != nil { + enc.Transactions = make([]hexutil.Bytes, len(e.Transactions)) + for k, v := range e.Transactions { + enc.Transactions[k] = v + } + } + enc.Withdrawals = e.Withdrawals + enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) + enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (e *ExecutableData) UnmarshalJSON(input []byte) error { + type ExecutableData struct { + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random *common.Hash `json:"prevRandao" gencodec:"required"` + Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` + BlockHash *common.Hash `json:"blockHash" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + } + var dec ExecutableData + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ParentHash == nil { + return errors.New("missing required field 'parentHash' for ExecutableData") + } + e.ParentHash = *dec.ParentHash + if dec.FeeRecipient == nil { + return errors.New("missing required field 'feeRecipient' for ExecutableData") + } + e.FeeRecipient = *dec.FeeRecipient + if dec.StateRoot == nil { + return errors.New("missing required field 'stateRoot' for ExecutableData") + } + e.StateRoot = *dec.StateRoot + if dec.ReceiptsRoot == nil { + return errors.New("missing required field 'receiptsRoot' for ExecutableData") + } + e.ReceiptsRoot = *dec.ReceiptsRoot + if dec.LogsBloom == nil { + return errors.New("missing required field 'logsBloom' for ExecutableData") + } + e.LogsBloom = *dec.LogsBloom + if dec.Random == nil { + return errors.New("missing required field 'prevRandao' for ExecutableData") + } + e.Random = *dec.Random + if dec.Number == nil { + return errors.New("missing required field 'blockNumber' for ExecutableData") + } + e.Number = uint64(*dec.Number) + if dec.GasLimit == nil { + return errors.New("missing required field 'gasLimit' for ExecutableData") + } + e.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed == nil { + return errors.New("missing required field 'gasUsed' for ExecutableData") + } + e.GasUsed = uint64(*dec.GasUsed) + if dec.Timestamp == nil { + return errors.New("missing required field 'timestamp' for ExecutableData") + } + e.Timestamp = uint64(*dec.Timestamp) + if dec.ExtraData == nil { + return errors.New("missing required field 'extraData' for ExecutableData") + } + e.ExtraData = *dec.ExtraData + if dec.BaseFeePerGas == nil { + return errors.New("missing required field 'baseFeePerGas' for ExecutableData") + } + e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) + if dec.BlockHash == nil { + return errors.New("missing required field 'blockHash' for ExecutableData") + } + e.BlockHash = *dec.BlockHash + if dec.Transactions == nil { + return errors.New("missing required field 'transactions' for ExecutableData") + } + e.Transactions = make([][]byte, len(dec.Transactions)) + for k, v := range dec.Transactions { + e.Transactions[k] = v + } + if dec.Withdrawals != nil { + e.Withdrawals = dec.Withdrawals + } + if dec.BlobGasUsed != nil { + e.BlobGasUsed = (*uint64)(dec.BlobGasUsed) + } + if dec.ExcessBlobGas != nil { + e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } + return nil +} diff --git a/beacon/engine/gen_epe.go b/beacon/engine/gen_epe.go new file mode 100644 index 0000000..e69f9a5 --- /dev/null +++ b/beacon/engine/gen_epe.go @@ -0,0 +1,58 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package engine + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*executionPayloadEnvelopeMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) { + type ExecutionPayloadEnvelope struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + Override bool `json:"shouldOverrideBuilder"` + } + var enc ExecutionPayloadEnvelope + enc.ExecutionPayload = e.ExecutionPayload + enc.BlockValue = (*hexutil.Big)(e.BlockValue) + enc.BlobsBundle = e.BlobsBundle + enc.Override = e.Override + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { + type ExecutionPayloadEnvelope struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + Override *bool `json:"shouldOverrideBuilder"` + } + var dec ExecutionPayloadEnvelope + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ExecutionPayload == nil { + return errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope") + } + e.ExecutionPayload = dec.ExecutionPayload + if dec.BlockValue == nil { + return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope") + } + e.BlockValue = (*big.Int)(dec.BlockValue) + if dec.BlobsBundle != nil { + e.BlobsBundle = dec.BlobsBundle + } + if dec.Override != nil { + e.Override = *dec.Override + } + return nil +} diff --git a/beacon/engine/types.go b/beacon/engine/types.go new file mode 100644 index 0000000..1dfcf5b --- /dev/null +++ b/beacon/engine/types.go @@ -0,0 +1,319 @@ +// 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 engine + +import ( + "fmt" + "math/big" + "slices" + + "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/trie" +) + +// PayloadVersion denotes the version of PayloadAttributes used to request the +// building of the payload to commence. +type PayloadVersion byte + +var ( + PayloadV1 PayloadVersion = 0x1 + PayloadV2 PayloadVersion = 0x2 + PayloadV3 PayloadVersion = 0x3 +) + +//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go + +// PayloadAttributes describes the environment context in which a block should +// be built. +type PayloadAttributes struct { + Timestamp uint64 `json:"timestamp" gencodec:"required"` + 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. +type payloadAttributesMarshaling struct { + Timestamp hexutil.Uint64 +} + +//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go + +// ExecutableData is the data necessary to execute an EL payload. +type ExecutableData struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom []byte `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number uint64 `json:"blockNumber" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Timestamp uint64 `json:"timestamp" gencodec:"required"` + ExtraData []byte `json:"extraData" gencodec:"required"` + BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + Transactions [][]byte `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + BlobGasUsed *uint64 `json:"blobGasUsed"` + ExcessBlobGas *uint64 `json:"excessBlobGas"` +} + +// JSON type overrides for executableData. +type executableDataMarshaling struct { + Number hexutil.Uint64 + GasLimit hexutil.Uint64 + GasUsed hexutil.Uint64 + Timestamp hexutil.Uint64 + BaseFeePerGas *hexutil.Big + ExtraData hexutil.Bytes + LogsBloom hexutil.Bytes + Transactions []hexutil.Bytes + BlobGasUsed *hexutil.Uint64 + ExcessBlobGas *hexutil.Uint64 +} + +//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go + +type ExecutionPayloadEnvelope struct { + ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` + BlockValue *big.Int `json:"blockValue" gencodec:"required"` + BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + Override bool `json:"shouldOverrideBuilder"` +} + +type BlobsBundleV1 struct { + Commitments []hexutil.Bytes `json:"commitments"` + Proofs []hexutil.Bytes `json:"proofs"` + Blobs []hexutil.Bytes `json:"blobs"` +} + +// JSON type overrides for ExecutionPayloadEnvelope. +type executionPayloadEnvelopeMarshaling struct { + BlockValue *hexutil.Big +} + +type PayloadStatusV1 struct { + Status string `json:"status"` + LatestValidHash *common.Hash `json:"latestValidHash"` + ValidationError *string `json:"validationError"` +} + +type TransitionConfigurationV1 struct { + TerminalTotalDifficulty *hexutil.Big `json:"terminalTotalDifficulty"` + TerminalBlockHash common.Hash `json:"terminalBlockHash"` + TerminalBlockNumber hexutil.Uint64 `json:"terminalBlockNumber"` +} + +// PayloadID is an identifier of the payload build process +type PayloadID [8]byte + +// Version returns the payload version associated with the identifier. +func (b PayloadID) Version() PayloadVersion { + return PayloadVersion(b[0]) +} + +// Is returns whether the identifier matches any of provided payload versions. +func (b PayloadID) Is(versions ...PayloadVersion) bool { + return slices.Contains(versions, b.Version()) +} + +func (b PayloadID) String() string { + return hexutil.Encode(b[:]) +} + +func (b PayloadID) MarshalText() ([]byte, error) { + return hexutil.Bytes(b[:]).MarshalText() +} + +func (b *PayloadID) UnmarshalText(input []byte) error { + err := hexutil.UnmarshalFixedText("PayloadID", input, b[:]) + if err != nil { + return fmt.Errorf("invalid payload id %q: %w", input, err) + } + return nil +} + +type ForkChoiceResponse struct { + PayloadStatus PayloadStatusV1 `json:"payloadStatus"` + PayloadID *PayloadID `json:"payloadId"` +} + +type ForkchoiceStateV1 struct { + HeadBlockHash common.Hash `json:"headBlockHash"` + SafeBlockHash common.Hash `json:"safeBlockHash"` + FinalizedBlockHash common.Hash `json:"finalizedBlockHash"` +} + +func encodeTransactions(txs []*types.Transaction) [][]byte { + var enc = make([][]byte, len(txs)) + for i, tx := range txs { + enc[i], _ = tx.MarshalBinary() + } + return enc +} + +func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { + var txs = make([]*types.Transaction, len(enc)) + for i, encTx := range enc { + var tx types.Transaction + if err := tx.UnmarshalBinary(encTx); err != nil { + return nil, fmt.Errorf("invalid transaction %d: %v", i, err) + } + txs[i] = &tx + } + return txs, nil +} + +// ExecutableDataToBlock constructs a block from executable data. +// It verifies that the following fields: +// +// len(extraData) <= 32 +// uncleHash = emptyUncleHash +// difficulty = 0 +// if versionedHashes != nil, versionedHashes match to blob transactions +// +// and that the blockhash of the constructed block matches the parameters. Nil +// Withdrawals value will propagate through the returned block. Empty +// Withdrawals value must be passed via non-nil, length 0 value in params. +func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { + txs, err := decodeTransactions(params.Transactions) + if err != nil { + return nil, err + } + if len(params.ExtraData) > 32 { + return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) + } + if len(params.LogsBloom) != 256 { + return nil, fmt.Errorf("invalid logsBloom length: %v", len(params.LogsBloom)) + } + // Check that baseFeePerGas is not negative or too big + if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) { + return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas) + } + var blobHashes = make([]common.Hash, 0, len(txs)) + for _, tx := range txs { + blobHashes = append(blobHashes, tx.BlobHashes()...) + } + if len(blobHashes) != len(versionedHashes) { + return nil, fmt.Errorf("invalid number of versionedHashes: %v blobHashes: %v", versionedHashes, blobHashes) + } + for i := 0; i < len(blobHashes); i++ { + if blobHashes[i] != versionedHashes[i] { + return nil, fmt.Errorf("invalid versionedHash at %v: %v blobHashes: %v", i, versionedHashes, blobHashes) + } + } + // Only set withdrawalsRoot if it is non-nil. This allows CLs to use + // ExecutableData before withdrawals are enabled by marshaling + // Withdrawals as the json null value. + var withdrawalsRoot *common.Hash + if params.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(params.Withdrawals), trie.NewStackTrie(nil)) + 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, + ParentBeaconRoot: beaconRoot, + } + block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: params.Withdrawals}) + if block.Hash() != params.BlockHash { + return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) + } + return block, nil +} + +// BlockToExecutableData constructs the ExecutableData structure by filling the +// fields from the given block. It assumes the given block is post-merge block. +func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope { + data := &ExecutableData{ + BlockHash: block.Hash(), + ParentHash: block.ParentHash(), + FeeRecipient: block.Coinbase(), + StateRoot: block.Root(), + Number: block.NumberU64(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + BaseFeePerGas: block.BaseFee(), + Timestamp: block.Time(), + ReceiptsRoot: block.ReceiptHash(), + LogsBloom: block.Bloom().Bytes(), + Transactions: encodeTransactions(block.Transactions()), + Random: block.MixDigest(), + ExtraData: block.Extra(), + Withdrawals: block.Withdrawals(), + BlobGasUsed: block.BlobGasUsed(), + ExcessBlobGas: block.ExcessBlobGas(), + } + bundle := BlobsBundleV1{ + Commitments: make([]hexutil.Bytes, 0), + Blobs: make([]hexutil.Bytes, 0), + Proofs: make([]hexutil.Bytes, 0), + } + for _, sidecar := range sidecars { + for j := range sidecar.Blobs { + bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:])) + bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:])) + bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) + } + } + return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false} +} + +// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1 +type ExecutionPayloadBodyV1 struct { + TransactionData []hexutil.Bytes `json:"transactions"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` +} + +// Client identifiers to support ClientVersionV1. +const ( + ClientCode = "GE" + ClientName = "go-ethereum" +) + +// ClientVersionV1 contains information which identifies a client implementation. +type ClientVersionV1 struct { + Code string `json:"code"` + Name string `json:"name"` + Version string `json:"version"` + Commit string `json:"commit"` +} + +func (v *ClientVersionV1) String() string { + return fmt.Sprintf("%s-%s-%s-%s", v.Code, v.Name, v.Version, v.Commit) +} diff --git a/beacon/light/api/api_server.go b/beacon/light/api/api_server.go new file mode 100755 index 0000000..2579854 --- /dev/null +++ b/beacon/light/api/api_server.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 api + +import ( + "reflect" + + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/light/sync" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// ApiServer is a wrapper around BeaconLightApi that implements request.requestServer. +type ApiServer struct { + api *BeaconLightApi + eventCallback func(event request.Event) + unsubscribe func() +} + +// NewApiServer creates a new ApiServer. +func NewApiServer(api *BeaconLightApi) *ApiServer { + return &ApiServer{api: api} +} + +// Subscribe implements request.requestServer. +func (s *ApiServer) Subscribe(eventCallback func(event request.Event)) { + s.eventCallback = eventCallback + listener := HeadEventListener{ + OnNewHead: func(slot uint64, blockRoot common.Hash) { + log.Debug("New head received", "slot", slot, "blockRoot", blockRoot) + eventCallback(request.Event{Type: sync.EvNewHead, Data: types.HeadInfo{Slot: slot, BlockRoot: blockRoot}}) + }, + OnOptimistic: func(update types.OptimisticUpdate) { + log.Debug("New optimistic update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount()) + eventCallback(request.Event{Type: sync.EvNewOptimisticUpdate, Data: update}) + }, + OnFinality: func(update types.FinalityUpdate) { + log.Debug("New finality update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount()) + eventCallback(request.Event{Type: sync.EvNewFinalityUpdate, Data: update}) + }, + OnError: func(err error) { + log.Warn("Head event stream error", "err", err) + }, + } + s.unsubscribe = s.api.StartHeadListener(listener) +} + +// SendRequest implements request.requestServer. +func (s *ApiServer) SendRequest(id request.ID, req request.Request) { + go func() { + var resp request.Response + var err error + switch data := req.(type) { + case sync.ReqUpdates: + log.Debug("Beacon API: requesting light client update", "reqid", id, "period", data.FirstPeriod, "count", data.Count) + var r sync.RespUpdates + r.Updates, r.Committees, err = s.api.GetBestUpdatesAndCommittees(data.FirstPeriod, data.Count) + resp = r + case sync.ReqHeader: + var r sync.RespHeader + log.Debug("Beacon API: requesting header", "reqid", id, "hash", common.Hash(data)) + r.Header, r.Canonical, r.Finalized, err = s.api.GetHeader(common.Hash(data)) + resp = r + case sync.ReqCheckpointData: + log.Debug("Beacon API: requesting checkpoint data", "reqid", id, "hash", common.Hash(data)) + resp, err = s.api.GetCheckpointData(common.Hash(data)) + case sync.ReqBeaconBlock: + log.Debug("Beacon API: requesting block", "reqid", id, "hash", common.Hash(data)) + resp, err = s.api.GetBeaconBlock(common.Hash(data)) + case sync.ReqFinality: + log.Debug("Beacon API: requesting finality update") + resp, err = s.api.GetFinalityUpdate() + default: + } + + if err != nil { + log.Warn("Beacon API request failed", "type", reflect.TypeOf(req), "reqid", id, "err", err) + s.eventCallback(request.Event{Type: request.EvFail, Data: request.RequestResponse{ID: id, Request: req}}) + } else { + log.Debug("Beacon API request answered", "type", reflect.TypeOf(req), "reqid", id) + s.eventCallback(request.Event{Type: request.EvResponse, Data: request.RequestResponse{ID: id, Request: req, Response: resp}}) + } + }() +} + +// Unsubscribe implements request.requestServer. +// Note: Unsubscribe should not be called concurrently with Subscribe. +func (s *ApiServer) Unsubscribe() { + if s.unsubscribe != nil { + s.unsubscribe() + s.unsubscribe = nil + } +} + +// Name implements request.Server +func (s *ApiServer) Name() string { + return s.api.url +} diff --git a/beacon/light/api/light_api.go b/beacon/light/api/light_api.go new file mode 100755 index 0000000..6f60fc0 --- /dev/null +++ b/beacon/light/api/light_api.go @@ -0,0 +1,578 @@ +// 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 detaiapi. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "sync" + "time" + + "github.com/donovanhide/eventsource" + "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" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" +) + +var ( + ErrNotFound = errors.New("404 Not Found") + ErrInternal = errors.New("500 Internal Server Error") +) + +type CommitteeUpdate struct { + Version string + Update types.LightClientUpdate + NextSyncCommittee types.SerializedSyncCommittee +} + +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate +type committeeUpdateJson struct { + Version string `json:"version"` + Data committeeUpdateData `json:"data"` +} + +type committeeUpdateData struct { + Header jsonBeaconHeader `json:"attested_header"` + NextSyncCommittee types.SerializedSyncCommittee `json:"next_sync_committee"` + NextSyncCommitteeBranch merkle.Values `json:"next_sync_committee_branch"` + FinalizedHeader *jsonBeaconHeader `json:"finalized_header,omitempty"` + FinalityBranch merkle.Values `json:"finality_branch,omitempty"` + SyncAggregate types.SyncAggregate `json:"sync_aggregate"` + SignatureSlot common.Decimal `json:"signature_slot"` +} + +type jsonBeaconHeader struct { + Beacon types.Header `json:"beacon"` +} + +type jsonHeaderWithExecProof struct { + Beacon types.Header `json:"beacon"` + Execution json.RawMessage `json:"execution"` + ExecutionBranch merkle.Values `json:"execution_branch"` +} + +// UnmarshalJSON unmarshals from JSON. +func (u *CommitteeUpdate) UnmarshalJSON(input []byte) error { + var dec committeeUpdateJson + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + u.Version = dec.Version + u.NextSyncCommittee = dec.Data.NextSyncCommittee + u.Update = types.LightClientUpdate{ + AttestedHeader: types.SignedHeader{ + Header: dec.Data.Header.Beacon, + Signature: dec.Data.SyncAggregate, + SignatureSlot: uint64(dec.Data.SignatureSlot), + }, + NextSyncCommitteeRoot: u.NextSyncCommittee.Root(), + NextSyncCommitteeBranch: dec.Data.NextSyncCommitteeBranch, + FinalityBranch: dec.Data.FinalityBranch, + } + if dec.Data.FinalizedHeader != nil { + u.Update.FinalizedHeader = &dec.Data.FinalizedHeader.Beacon + } + return nil +} + +// fetcher is an interface useful for debug-harnessing the http api. +type fetcher interface { + Do(req *http.Request) (*http.Response, error) +} + +// BeaconLightApi requests light client information from a beacon node REST API. +// Note: all required API endpoints are currently only implemented by Lodestar. +type BeaconLightApi struct { + url string + client fetcher + customHeaders map[string]string +} + +func NewBeaconLightApi(url string, customHeaders map[string]string) *BeaconLightApi { + return &BeaconLightApi{ + url: url, + client: &http.Client{ + Timeout: time.Second * 10, + }, + customHeaders: customHeaders, + } +} + +func (api *BeaconLightApi) httpGet(path string) ([]byte, error) { + req, err := http.NewRequest("GET", api.url+path, nil) + if err != nil { + return nil, err + } + for k, v := range api.customHeaders { + req.Header.Set(k, v) + } + resp, err := api.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200: + return io.ReadAll(resp.Body) + case 404: + return nil, ErrNotFound + case 500: + return nil, ErrInternal + default: + return nil, fmt.Errorf("unexpected error from API endpoint \"%s\": status code %d", path, resp.StatusCode) + } +} + +func (api *BeaconLightApi) httpGetf(format string, params ...any) ([]byte, error) { + return api.httpGet(fmt.Sprintf(format, params...)) +} + +// GetBestUpdatesAndCommittees fetches and validates LightClientUpdate for given +// period and full serialized committee for the next period (committee root hash +// equals update.NextSyncCommitteeRoot). +// Note that the results are validated but the update signature should be verified +// by the caller as its validity depends on the update chain. +func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64) ([]*types.LightClientUpdate, []*types.SerializedSyncCommittee, error) { + resp, err := api.httpGetf("/eth/v1/beacon/light_client/updates?start_period=%d&count=%d", firstPeriod, count) + if err != nil { + return nil, nil, err + } + + var data []CommitteeUpdate + if err := json.Unmarshal(resp, &data); err != nil { + return nil, nil, err + } + if len(data) != int(count) { + return nil, nil, errors.New("invalid number of committee updates") + } + updates := make([]*types.LightClientUpdate, int(count)) + committees := make([]*types.SerializedSyncCommittee, int(count)) + for i, d := range data { + if d.Update.AttestedHeader.Header.SyncPeriod() != firstPeriod+uint64(i) { + return nil, nil, errors.New("wrong committee update header period") + } + if err := d.Update.Validate(); err != nil { + return nil, nil, err + } + if d.NextSyncCommittee.Root() != d.Update.NextSyncCommitteeRoot { + return nil, nil, errors.New("wrong sync committee root") + } + updates[i], committees[i] = new(types.LightClientUpdate), new(types.SerializedSyncCommittee) + *updates[i], *committees[i] = d.Update, d.NextSyncCommittee + } + return updates, committees, nil +} + +// GetOptimisticUpdate fetches the latest available optimistic update. +// Note that the signature should be verified by the caller as its validity +// depends on the update chain. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate +func (api *BeaconLightApi) GetOptimisticUpdate() (types.OptimisticUpdate, error) { + resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update") + if err != nil { + return types.OptimisticUpdate{}, err + } + return decodeOptimisticUpdate(resp) +} + +func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) { + var data struct { + Version string + Data struct { + Attested jsonHeaderWithExecProof `json:"attested_header"` + Aggregate types.SyncAggregate `json:"sync_aggregate"` + SignatureSlot common.Decimal `json:"signature_slot"` + } `json:"data"` + } + if err := json.Unmarshal(enc, &data); err != nil { + return types.OptimisticUpdate{}, err + } + // Decode the execution payload headers. + attestedExecHeader, err := types.ExecutionHeaderFromJSON(data.Version, data.Data.Attested.Execution) + if err != nil { + return types.OptimisticUpdate{}, fmt.Errorf("invalid attested header: %v", err) + } + if data.Data.Attested.Beacon.StateRoot == (common.Hash{}) { + // workaround for different event encoding format in Lodestar + if err := json.Unmarshal(enc, &data.Data); err != nil { + return types.OptimisticUpdate{}, err + } + } + + if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize { + return types.OptimisticUpdate{}, errors.New("invalid sync_committee_bits length") + } + if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize { + return types.OptimisticUpdate{}, errors.New("invalid sync_committee_signature length") + } + return types.OptimisticUpdate{ + Attested: types.HeaderWithExecProof{ + Header: data.Data.Attested.Beacon, + PayloadHeader: attestedExecHeader, + PayloadBranch: data.Data.Attested.ExecutionBranch, + }, + Signature: data.Data.Aggregate, + SignatureSlot: uint64(data.Data.SignatureSlot), + }, nil +} + +// GetFinalityUpdate fetches the latest available finality update. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate +func (api *BeaconLightApi) GetFinalityUpdate() (types.FinalityUpdate, error) { + resp, err := api.httpGet("/eth/v1/beacon/light_client/finality_update") + if err != nil { + return types.FinalityUpdate{}, err + } + return decodeFinalityUpdate(resp) +} + +func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) { + var data struct { + Version string + Data struct { + Attested jsonHeaderWithExecProof `json:"attested_header"` + Finalized jsonHeaderWithExecProof `json:"finalized_header"` + FinalityBranch merkle.Values `json:"finality_branch"` + Aggregate types.SyncAggregate `json:"sync_aggregate"` + SignatureSlot common.Decimal `json:"signature_slot"` + } + } + if err := json.Unmarshal(enc, &data); err != nil { + return types.FinalityUpdate{}, err + } + // Decode the execution payload headers. + attestedExecHeader, err := types.ExecutionHeaderFromJSON(data.Version, data.Data.Attested.Execution) + if err != nil { + return types.FinalityUpdate{}, fmt.Errorf("invalid attested header: %v", err) + } + finalizedExecHeader, err := types.ExecutionHeaderFromJSON(data.Version, data.Data.Finalized.Execution) + if err != nil { + return types.FinalityUpdate{}, fmt.Errorf("invalid finalized header: %v", err) + } + // Perform sanity checks. + if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize { + return types.FinalityUpdate{}, errors.New("invalid sync_committee_bits length") + } + if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize { + return types.FinalityUpdate{}, errors.New("invalid sync_committee_signature length") + } + + return types.FinalityUpdate{ + Attested: types.HeaderWithExecProof{ + Header: data.Data.Attested.Beacon, + PayloadHeader: attestedExecHeader, + PayloadBranch: data.Data.Attested.ExecutionBranch, + }, + Finalized: types.HeaderWithExecProof{ + Header: data.Data.Finalized.Beacon, + PayloadHeader: finalizedExecHeader, + PayloadBranch: data.Data.Finalized.ExecutionBranch, + }, + FinalityBranch: data.Data.FinalityBranch, + Signature: data.Data.Aggregate, + SignatureSlot: uint64(data.Data.SignatureSlot), + }, nil +} + +// GetHeader fetches and validates the beacon header with the given blockRoot. +// If blockRoot is null hash then the latest head header is fetched. +// The values of the canonical and finalized flags are also returned. Note that +// these flags are not validated. +func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, bool, bool, error) { + var blockId string + if blockRoot == (common.Hash{}) { + blockId = "head" + } else { + blockId = blockRoot.Hex() + } + resp, err := api.httpGetf("/eth/v1/beacon/headers/%s", blockId) + if err != nil { + return types.Header{}, false, false, err + } + + var data struct { + Finalized bool `json:"finalized"` + Data struct { + Root common.Hash `json:"root"` + Canonical bool `json:"canonical"` + Header struct { + Message types.Header `json:"message"` + Signature hexutil.Bytes `json:"signature"` + } `json:"header"` + } `json:"data"` + } + if err := json.Unmarshal(resp, &data); err != nil { + return types.Header{}, false, false, err + } + header := data.Data.Header.Message + if blockRoot == (common.Hash{}) { + blockRoot = data.Data.Root + } + if header.Hash() != blockRoot { + return types.Header{}, false, false, errors.New("retrieved beacon header root does not match") + } + return header, data.Data.Canonical, data.Finalized, nil +} + +// GetCheckpointData fetches and validates bootstrap data belonging to the given checkpoint. +func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types.BootstrapData, error) { + resp, err := api.httpGetf("/eth/v1/beacon/light_client/bootstrap/0x%x", checkpointHash[:]) + if err != nil { + return nil, err + } + + // See data structure definition here: + // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientbootstrap + type bootstrapData struct { + Data struct { + Header jsonBeaconHeader `json:"header"` + Committee *types.SerializedSyncCommittee `json:"current_sync_committee"` + CommitteeBranch merkle.Values `json:"current_sync_committee_branch"` + } `json:"data"` + } + + var data bootstrapData + if err := json.Unmarshal(resp, &data); err != nil { + return nil, err + } + if data.Data.Committee == nil { + return nil, errors.New("sync committee is missing") + } + header := data.Data.Header.Beacon + if header.Hash() != checkpointHash { + return nil, fmt.Errorf("invalid checkpoint block header, have %v want %v", header.Hash(), checkpointHash) + } + checkpoint := &types.BootstrapData{ + Header: header, + CommitteeBranch: data.Data.CommitteeBranch, + CommitteeRoot: data.Data.Committee.Root(), + Committee: data.Data.Committee, + } + if err := checkpoint.Validate(); err != nil { + return nil, fmt.Errorf("invalid checkpoint: %w", err) + } + if checkpoint.Header.Hash() != checkpointHash { + return nil, errors.New("wrong checkpoint hash") + } + return checkpoint, nil +} + +func (api *BeaconLightApi) GetBeaconBlock(blockRoot common.Hash) (*types.BeaconBlock, error) { + resp, err := api.httpGetf("/eth/v2/beacon/blocks/0x%x", blockRoot) + if err != nil { + return nil, err + } + + var beaconBlockMessage struct { + Version string + Data struct { + Message json.RawMessage `json:"message"` + } + } + if err := json.Unmarshal(resp, &beaconBlockMessage); err != nil { + return nil, fmt.Errorf("invalid block json data: %v", err) + } + block, err := types.BlockFromJSON(beaconBlockMessage.Version, beaconBlockMessage.Data.Message) + if err != nil { + return nil, err + } + computedRoot := block.Root() + if computedRoot != blockRoot { + return nil, fmt.Errorf("Beacon block root hash mismatch (expected: %x, got: %x)", blockRoot, computedRoot) + } + return block, nil +} + +func decodeHeadEvent(enc []byte) (uint64, common.Hash, error) { + var data struct { + Slot common.Decimal `json:"slot"` + Block common.Hash `json:"block"` + } + if err := json.Unmarshal(enc, &data); err != nil { + return 0, common.Hash{}, err + } + return uint64(data.Slot), data.Block, nil +} + +type HeadEventListener struct { + OnNewHead func(slot uint64, blockRoot common.Hash) + OnOptimistic func(head types.OptimisticUpdate) + OnFinality func(head types.FinalityUpdate) + OnError func(err error) +} + +// StartHeadListener creates an event subscription for heads and signed (optimistic) +// head updates and calls the specified callback functions when they are received. +// The callbacks are also called for the current head and optimistic head at startup. +// They are never called concurrently. +func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func() { + var ( + ctx, closeCtx = context.WithCancel(context.Background()) + streamCh = make(chan *eventsource.Stream, 1) + wg sync.WaitGroup + ) + + // When connected to a Lodestar node the subscription blocks until the first actual + // event arrives; therefore we create the subscription in a separate goroutine while + // letting the main goroutine sync up to the current head. + wg.Add(1) + go func() { + defer wg.Done() + stream := api.startEventStream(ctx, &listener) + if stream == nil { + // This case happens when the context was closed. + return + } + // Stream was opened, wait for close signal. + streamCh <- stream + <-ctx.Done() + stream.Close() + }() + + wg.Add(1) + go func() { + defer wg.Done() + + // Request initial data. + log.Trace("Requesting initial head header") + if head, _, _, err := api.GetHeader(common.Hash{}); err == nil { + log.Trace("Retrieved initial head header", "slot", head.Slot, "hash", head.Hash()) + listener.OnNewHead(head.Slot, head.Hash()) + } else { + log.Debug("Failed to retrieve initial head header", "error", err) + } + log.Trace("Requesting initial optimistic update") + if optimisticUpdate, err := api.GetOptimisticUpdate(); err == nil { + log.Trace("Retrieved initial optimistic update", "slot", optimisticUpdate.Attested.Slot, "hash", optimisticUpdate.Attested.Hash()) + listener.OnOptimistic(optimisticUpdate) + } else { + log.Debug("Failed to retrieve initial optimistic update", "error", err) + } + log.Trace("Requesting initial finality update") + if finalityUpdate, err := api.GetFinalityUpdate(); err == nil { + log.Trace("Retrieved initial finality update", "slot", finalityUpdate.Finalized.Slot, "hash", finalityUpdate.Finalized.Hash()) + listener.OnFinality(finalityUpdate) + } else { + log.Debug("Failed to retrieve initial finality update", "error", err) + } + + log.Trace("Starting event stream processing loop") + // Receive the stream. + var stream *eventsource.Stream + select { + case stream = <-streamCh: + case <-ctx.Done(): + log.Trace("Stopping event stream processing loop") + return + } + + for { + select { + case event, ok := <-stream.Events: + if !ok { + log.Trace("Event stream closed") + return + } + log.Trace("New event received from event stream", "type", event.Event()) + switch event.Event() { + case "head": + slot, blockRoot, err := decodeHeadEvent([]byte(event.Data())) + if err == nil { + listener.OnNewHead(slot, blockRoot) + } else { + listener.OnError(fmt.Errorf("error decoding head event: %v", err)) + } + case "light_client_optimistic_update": + optimisticUpdate, err := decodeOptimisticUpdate([]byte(event.Data())) + if err == nil { + listener.OnOptimistic(optimisticUpdate) + } else { + listener.OnError(fmt.Errorf("error decoding optimistic update event: %v", err)) + } + case "light_client_finality_update": + finalityUpdate, err := decodeFinalityUpdate([]byte(event.Data())) + if err == nil { + listener.OnFinality(finalityUpdate) + } else { + listener.OnError(fmt.Errorf("error decoding finality update event: %v", err)) + } + default: + listener.OnError(fmt.Errorf("unexpected event: %s", event.Event())) + } + + case err, ok := <-stream.Errors: + if !ok { + return + } + listener.OnError(err) + } + } + }() + + return func() { + closeCtx() + wg.Wait() + } +} + +// startEventStream establishes an event stream. This will keep retrying until the stream has been +// established. It can only return nil when the context is canceled. +func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadEventListener) *eventsource.Stream { + for retry := true; retry; retry = ctxSleep(ctx, 5*time.Second) { + path := "/eth/v1/events?topics=head&topics=light_client_finality_update&topics=light_client_optimistic_update" + log.Trace("Sending event subscription request") + req, err := http.NewRequestWithContext(ctx, "GET", api.url+path, nil) + if err != nil { + listener.OnError(fmt.Errorf("error creating event subscription request: %v", err)) + continue + } + for k, v := range api.customHeaders { + req.Header.Set(k, v) + } + stream, err := eventsource.SubscribeWithRequest("", req) + if err != nil { + listener.OnError(fmt.Errorf("error creating event subscription: %v", err)) + continue + } + log.Trace("Successfully created event stream") + return stream + } + return nil +} + +func ctxSleep(ctx context.Context, timeout time.Duration) (ok bool) { + timer := time.NewTimer(timeout) + defer timer.Stop() + select { + case <-timer.C: + return true + case <-ctx.Done(): + return false + } +} diff --git a/beacon/light/canonical.go b/beacon/light/canonical.go new file mode 100644 index 0000000..b537149 --- /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 0000000..a8d032b --- /dev/null +++ b/beacon/light/committee_chain.go @@ -0,0 +1,529 @@ +// 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 + changeCounter uint64 + + 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() }) +} + +// NewTestCommitteeChain creates a new CommitteeChain for testing. +func NewTestCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, clock *mclock.Simulated) *CommitteeChain { + return newCommitteeChain(db, config, signerThreshold, enforceTime, dummyVerifier{}, clock, func() int64 { return int64(clock.Now()) }) +} + +// 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) + } + s.changeCounter++ +} + +// CheckpointInit initializes a CommitteeChain based on a 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 + } + s.changeCounter++ + 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 + } + } + s.changeCounter++ + 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 +} + +func (s *CommitteeChain) ChangeCounter() uint64 { + s.chainmu.RLock() + defer s.chainmu.RUnlock() + + return s.changeCounter +} + +// 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 0000000..57b6d71 --- /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 = NewTestCommitteeChain(c.db, &config, signerThreshold, enforceTime, c.clock) + return c +} + +func (c *committeeChainTest) reloadChain() { + c.chain = NewTestCommitteeChain(c.db, &c.config, c.signerThreshold, c.enforceTime, c.clock) +} + +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/head_tracker.go b/beacon/light/head_tracker.go new file mode 100644 index 0000000..7ef93fe --- /dev/null +++ b/beacon/light/head_tracker.go @@ -0,0 +1,161 @@ +// 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" + "sync" + "time" + + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/log" +) + +// HeadTracker keeps track of the latest validated head and the "prefetch" head +// which is the (not necessarily validated) head announced by the majority of +// servers. +type HeadTracker struct { + lock sync.RWMutex + committeeChain *CommitteeChain + minSignerCount int + optimisticUpdate types.OptimisticUpdate + hasOptimisticUpdate bool + finalityUpdate types.FinalityUpdate + hasFinalityUpdate bool + prefetchHead types.HeadInfo + changeCounter uint64 +} + +// NewHeadTracker creates a new HeadTracker. +func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int) *HeadTracker { + return &HeadTracker{ + committeeChain: committeeChain, + minSignerCount: minSignerCount, + } +} + +// ValidatedOptimistic returns the latest validated optimistic update. +func (h *HeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) { + h.lock.RLock() + defer h.lock.RUnlock() + + return h.optimisticUpdate, h.hasOptimisticUpdate +} + +// ValidatedFinality returns the latest validated finality update. +func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) { + h.lock.RLock() + defer h.lock.RUnlock() + + return h.finalityUpdate, h.hasFinalityUpdate +} + +// ValidateOptimistic validates the given optimistic update. If the update is +// successfully validated and it is better than the old validated update (higher +// slot or same slot and more signers) then ValidatedOptimistic is updated. +// The boolean return flag signals if ValidatedOptimistic has been changed. +func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) { + h.lock.Lock() + defer h.lock.Unlock() + + if err := update.Validate(); err != nil { + return false, err + } + replace, err := h.validate(update.SignedHeader(), h.optimisticUpdate.SignedHeader()) + if replace { + h.optimisticUpdate, h.hasOptimisticUpdate = update, true + h.changeCounter++ + } + return replace, err +} + +// ValidateFinality validates the given finality update. If the update is +// successfully validated and it is better than the old validated update (higher +// slot or same slot and more signers) then ValidatedFinality is updated. +// The boolean return flag signals if ValidatedFinality has been changed. +func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) { + h.lock.Lock() + defer h.lock.Unlock() + + if err := update.Validate(); err != nil { + return false, err + } + replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader()) + if replace { + h.finalityUpdate, h.hasFinalityUpdate = update, true + h.changeCounter++ + } + return replace, err +} + +func (h *HeadTracker) validate(head, oldHead types.SignedHeader) (bool, error) { + signerCount := head.Signature.SignerCount() + if signerCount < h.minSignerCount { + return false, errors.New("low signer count") + } + if head.Header.Slot < oldHead.Header.Slot || (head.Header.Slot == oldHead.Header.Slot && signerCount <= oldHead.Signature.SignerCount()) { + return false, nil + } + sigOk, age, err := h.committeeChain.VerifySignedHeader(head) + if err != nil { + return false, err + } + if age < 0 { + log.Warn("Future signed head received", "age", age) + } + if age > time.Minute*2 { + log.Warn("Old signed head received", "age", age) + } + if !sigOk { + return false, errors.New("invalid header signature") + } + return true, nil +} + +// PrefetchHead returns the latest known prefetch head's head info. +// This head can be used to start fetching related data hoping that it will be +// validated soon. +// Note that the prefetch head cannot be validated cryptographically so it should +// only be used as a performance optimization hint. +func (h *HeadTracker) PrefetchHead() types.HeadInfo { + h.lock.RLock() + defer h.lock.RUnlock() + + return h.prefetchHead +} + +// SetPrefetchHead sets the prefetch head info. +// Note that HeadTracker does not verify the prefetch head, just acts as a thread +// safe bulletin board. +func (h *HeadTracker) SetPrefetchHead(head types.HeadInfo) { + h.lock.Lock() + defer h.lock.Unlock() + + if head == h.prefetchHead { + return + } + h.prefetchHead = head + h.changeCounter++ +} + +// ChangeCounter implements request.targetData +func (h *HeadTracker) ChangeCounter() uint64 { + h.lock.RLock() + defer h.lock.RUnlock() + + return h.changeCounter +} diff --git a/beacon/light/range.go b/beacon/light/range.go new file mode 100644 index 0000000..76ebe23 --- /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/request/scheduler.go b/beacon/light/request/scheduler.go new file mode 100644 index 0000000..e80daf8 --- /dev/null +++ b/beacon/light/request/scheduler.go @@ -0,0 +1,403 @@ +// 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 request + +import ( + "sync" + + "github.com/ethereum/go-ethereum/log" +) + +// Module represents a mechanism which is typically responsible for downloading +// and updating a passive data structure. It does not directly interact with the +// servers. It can start requests using the Requester interface, maintain its +// internal state by receiving and processing Events and update its target data +// structure based on the obtained data. +// It is the Scheduler's responsibility to feed events to the modules, call +// Process as long as there might be something to process and then generate request +// candidates using MakeRequest and start the best possible requests. +// Modules are called by Scheduler whenever a global trigger is fired. All events +// fire the trigger. Changing a target data structure also triggers a next +// processing round as it could make further actions possible either by the same +// or another Module. +type Module interface { + // Process is a non-blocking function responsible for starting requests, + // processing events and updating the target data structures(s) and the + // internal state of the module. Module state typically consists of information + // about pending requests and registered servers. + // Process is always called after an event is received or after a target data + // structure has been changed. + // + // Note: Process functions of different modules are never called concurrently; + // they are called by Scheduler in the same order of priority as they were + // registered in. + Process(Requester, []Event) +} + +// Requester allows Modules to obtain the list of momentarily available servers, +// start new requests and report server failure when a response has been proven +// to be invalid in the processing phase. +// Note that all Requester functions should be safe to call from Module.Process. +type Requester interface { + CanSendTo() []Server + Send(Server, Request) ID + Fail(Server, string) +} + +// Scheduler is a modular network data retrieval framework that coordinates multiple +// servers and retrieval mechanisms (modules). It implements a trigger mechanism +// that calls the Process function of registered modules whenever either the state +// of existing data structures or events coming from registered servers could +// allow new operations. +type Scheduler struct { + lock sync.Mutex + modules []Module // first has the highest priority + names map[Module]string + servers map[server]struct{} + targets map[targetData]uint64 + + requesterLock sync.RWMutex + serverOrder []server + pending map[ServerAndID]pendingRequest + + // eventLock guards access to the events list. Note that eventLock can be + // locked either while lock is locked or unlocked but lock cannot be locked + // while eventLock is locked. + eventLock sync.Mutex + events []Event + stopCh chan chan struct{} + + triggerCh chan struct{} // restarts waiting sync loop + // if trigger has already been fired then send to testWaitCh blocks until + // the triggered processing round is finished + testWaitCh chan struct{} +} + +type ( + // Server identifies a server without allowing any direct interaction. + // Note: server interface is used by Scheduler and Tracker but not used by + // the modules that do not interact with them directly. + // In order to make module testing easier, Server interface is used in + // events and modules. + Server interface { + Name() string + } + Request any + Response any + ID uint64 + ServerAndID struct { + Server Server + ID ID + } +) + +// targetData represents a registered target data structure that increases its +// ChangeCounter whenever it has been changed. +type targetData interface { + ChangeCounter() uint64 +} + +// pendingRequest keeps track of sent and not yet finalized requests and their +// sender modules. +type pendingRequest struct { + request Request + module Module +} + +// NewScheduler creates a new Scheduler. +func NewScheduler() *Scheduler { + s := &Scheduler{ + servers: make(map[server]struct{}), + names: make(map[Module]string), + pending: make(map[ServerAndID]pendingRequest), + targets: make(map[targetData]uint64), + stopCh: make(chan chan struct{}), + // Note: testWaitCh should not have capacity in order to ensure + // that after a trigger happens testWaitCh will block until the resulting + // processing round has been finished + triggerCh: make(chan struct{}, 1), + testWaitCh: make(chan struct{}), + } + return s +} + +// RegisterTarget registers a target data structure, ensuring that any changes +// made to it trigger a new round of Module.Process calls, giving a chance to +// modules to react to the changes. +func (s *Scheduler) RegisterTarget(t targetData) { + s.lock.Lock() + defer s.lock.Unlock() + + s.targets[t] = 0 +} + +// RegisterModule registers a module. Should be called before starting the scheduler. +// In each processing round the order of module processing depends on the order of +// registration. +func (s *Scheduler) RegisterModule(m Module, name string) { + s.lock.Lock() + defer s.lock.Unlock() + + s.modules = append(s.modules, m) + s.names[m] = name +} + +// RegisterServer registers a new server. +func (s *Scheduler) RegisterServer(server server) { + s.lock.Lock() + defer s.lock.Unlock() + + s.addEvent(Event{Type: EvRegistered, Server: server}) + server.subscribe(func(event Event) { + event.Server = server + s.addEvent(event) + }) +} + +// UnregisterServer removes a registered server. +func (s *Scheduler) UnregisterServer(server server) { + s.lock.Lock() + defer s.lock.Unlock() + + server.unsubscribe() + s.addEvent(Event{Type: EvUnregistered, Server: server}) +} + +// Start starts the scheduler. It should be called after registering all modules +// and before registering any servers. +func (s *Scheduler) Start() { + go s.syncLoop() +} + +// Stop stops the scheduler. +func (s *Scheduler) Stop() { + stop := make(chan struct{}) + s.stopCh <- stop + <-stop + s.lock.Lock() + for server := range s.servers { + server.unsubscribe() + } + s.servers = nil + s.lock.Unlock() +} + +// syncLoop is the main event loop responsible for event/data processing and +// sending new requests. +// A round of processing starts whenever the global trigger is fired. Triggers +// fired during a processing round ensure that there is going to be a next round. +func (s *Scheduler) syncLoop() { + for { + s.lock.Lock() + s.processRound() + s.lock.Unlock() + loop: + for { + select { + case stop := <-s.stopCh: + close(stop) + return + case <-s.triggerCh: + break loop + case <-s.testWaitCh: + } + } + } +} + +// targetChanged returns true if a registered target data structure has been +// changed since the last call to this function. +func (s *Scheduler) targetChanged() (changed bool) { + for target, counter := range s.targets { + if newCounter := target.ChangeCounter(); newCounter != counter { + s.targets[target] = newCounter + changed = true + } + } + return +} + +// processRound runs an entire processing round. It calls the Process functions +// of all modules, passing all relevant events and repeating Process calls as +// long as any changes have been made to the registered target data structures. +// Once all events have been processed and a stable state has been achieved, +// requests are generated and sent if necessary and possible. +func (s *Scheduler) processRound() { + for { + log.Trace("Processing modules") + filteredEvents := s.filterEvents() + for _, module := range s.modules { + log.Trace("Processing module", "name", s.names[module], "events", len(filteredEvents[module])) + module.Process(requester{s, module}, filteredEvents[module]) + } + if !s.targetChanged() { + break + } + } +} + +// Trigger starts a new processing round. If fired during processing, it ensures +// another full round of processing all modules. +func (s *Scheduler) Trigger() { + select { + case s.triggerCh <- struct{}{}: + default: + } +} + +// addEvent adds an event to be processed in the next round. Note that it can be +// called regardless of the state of the lock mutex, making it safe for use in +// the server event callback. +func (s *Scheduler) addEvent(event Event) { + s.eventLock.Lock() + s.events = append(s.events, event) + s.eventLock.Unlock() + s.Trigger() +} + +// filterEvent sorts each Event either as a request event or a server event, +// depending on its type. Request events are also sorted in a map based on the +// module that originally initiated the request. It also ensures that no events +// related to a server are returned before EvRegistered or after EvUnregistered. +// In case of an EvUnregistered server event it also closes all pending requests +// to the given server by adding a failed request event (EvFail), ensuring that +// all requests get finalized and thereby allowing the module logic to be safe +// and simple. +func (s *Scheduler) filterEvents() map[Module][]Event { + s.eventLock.Lock() + events := s.events + s.events = nil + s.eventLock.Unlock() + + s.requesterLock.Lock() + defer s.requesterLock.Unlock() + + filteredEvents := make(map[Module][]Event) + for _, event := range events { + server := event.Server.(server) + if _, ok := s.servers[server]; !ok && event.Type != EvRegistered { + continue // before EvRegister or after EvUnregister, discard + } + + if event.IsRequestEvent() { + sid, _, _ := event.RequestInfo() + pending, ok := s.pending[sid] + if !ok { + continue // request already closed, ignore further events + } + if event.Type == EvResponse || event.Type == EvFail { + delete(s.pending, sid) // final event, close pending request + } + filteredEvents[pending.module] = append(filteredEvents[pending.module], event) + } else { + switch event.Type { + case EvRegistered: + s.servers[server] = struct{}{} + s.serverOrder = append(s.serverOrder, nil) + copy(s.serverOrder[1:], s.serverOrder[:len(s.serverOrder)-1]) + s.serverOrder[0] = server + case EvUnregistered: + s.closePending(event.Server, filteredEvents) + delete(s.servers, server) + for i, srv := range s.serverOrder { + if srv == server { + copy(s.serverOrder[i:len(s.serverOrder)-1], s.serverOrder[i+1:]) + s.serverOrder = s.serverOrder[:len(s.serverOrder)-1] + break + } + } + } + for _, module := range s.modules { + filteredEvents[module] = append(filteredEvents[module], event) + } + } + } + return filteredEvents +} + +// closePending closes all pending requests to the given server and adds an EvFail +// event to properly finalize them +func (s *Scheduler) closePending(server Server, filteredEvents map[Module][]Event) { + for sid, pending := range s.pending { + if sid.Server == server { + filteredEvents[pending.module] = append(filteredEvents[pending.module], Event{ + Type: EvFail, + Server: server, + Data: RequestResponse{ + ID: sid.ID, + Request: pending.request, + }, + }) + delete(s.pending, sid) + } + } +} + +// requester implements Requester. Note that while requester basically wraps +// Scheduler (with the added information of the currently processed Module), all +// functions are safe to call from Module.Process which is running while +// the Scheduler.lock mutex is held. +type requester struct { + *Scheduler + module Module +} + +// CanSendTo returns the list of currently available servers. It also returns +// them in an order of least to most recently used, ensuring a round-robin usage +// of suitable servers if the module always chooses the first suitable one. +func (s requester) CanSendTo() []Server { + s.requesterLock.RLock() + defer s.requesterLock.RUnlock() + + list := make([]Server, 0, len(s.serverOrder)) + for _, server := range s.serverOrder { + if server.canRequestNow() { + list = append(list, server) + } + } + return list +} + +// Send sends a request and adds an entry to Scheduler.pending map, ensuring that +// related request events will be delivered to the sender Module. +func (s requester) Send(srv Server, req Request) ID { + s.requesterLock.Lock() + defer s.requesterLock.Unlock() + + server := srv.(server) + id := server.sendRequest(req) + sid := ServerAndID{Server: srv, ID: id} + s.pending[sid] = pendingRequest{request: req, module: s.module} + for i, ss := range s.serverOrder { + if ss == server { + copy(s.serverOrder[i:len(s.serverOrder)-1], s.serverOrder[i+1:]) + s.serverOrder[len(s.serverOrder)-1] = server + return id + } + } + log.Error("Target server not found in ordered list of registered servers") + return id +} + +// Fail should be called when a server delivers invalid or useless information. +// Calling Fail disables the given server for a period that is initially short +// but is exponentially growing if it happens frequently. This results in a +// somewhat fault tolerant operation that avoids hammering servers with requests +// that they cannot serve but still gives them a chance periodically. +func (s requester) Fail(srv Server, desc string) { + srv.(server).fail(desc) +} diff --git a/beacon/light/request/scheduler_test.go b/beacon/light/request/scheduler_test.go new file mode 100644 index 0000000..5cd4965 --- /dev/null +++ b/beacon/light/request/scheduler_test.go @@ -0,0 +1,126 @@ +package request + +import ( + "reflect" + "testing" +) + +func TestEventFilter(t *testing.T) { + s := NewScheduler() + module1 := &testModule{name: "module1"} + module2 := &testModule{name: "module2"} + s.RegisterModule(module1, "module1") + s.RegisterModule(module2, "module2") + s.Start() + // startup process round without events + s.testWaitCh <- struct{}{} + module1.expProcess(t, nil) + module2.expProcess(t, nil) + srv := &testServer{} + // register server; both modules should receive server event + s.RegisterServer(srv) + s.testWaitCh <- struct{}{} + module1.expProcess(t, []Event{ + {Type: EvRegistered, Server: srv}, + }) + module2.expProcess(t, []Event{ + {Type: EvRegistered, Server: srv}, + }) + // let module1 send a request + srv.canRequest = 1 + module1.sendReq = testRequest + s.Trigger() + // in first triggered round module1 sends the request, no events yet + s.testWaitCh <- struct{}{} + module1.expProcess(t, nil) + module2.expProcess(t, nil) + // server emits EvTimeout; only module1 should receive it + srv.eventCb(Event{Type: EvTimeout, Data: RequestResponse{ID: 1, Request: testRequest}}) + s.testWaitCh <- struct{}{} + module1.expProcess(t, []Event{ + {Type: EvTimeout, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}}, + }) + module2.expProcess(t, nil) + // unregister server; both modules should receive server event + s.UnregisterServer(srv) + s.testWaitCh <- struct{}{} + module1.expProcess(t, []Event{ + // module1 should also receive EvFail on its pending request + {Type: EvFail, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}}, + {Type: EvUnregistered, Server: srv}, + }) + module2.expProcess(t, []Event{ + {Type: EvUnregistered, Server: srv}, + }) + // response after server unregistered; should be discarded + srv.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}}) + s.testWaitCh <- struct{}{} + module1.expProcess(t, nil) + module2.expProcess(t, nil) + // no more process rounds expected; shut down + s.testWaitCh <- struct{}{} + module1.expNoMoreProcess(t) + module2.expNoMoreProcess(t) + s.Stop() +} + +type testServer struct { + eventCb func(Event) + lastID ID + canRequest int +} + +func (s *testServer) Name() string { + return "" +} + +func (s *testServer) subscribe(eventCb func(Event)) { + s.eventCb = eventCb +} + +func (s *testServer) canRequestNow() bool { + return s.canRequest > 0 +} + +func (s *testServer) sendRequest(req Request) ID { + s.canRequest-- + s.lastID++ + return s.lastID +} + +func (s *testServer) fail(string) {} +func (s *testServer) unsubscribe() {} + +type testModule struct { + name string + processed [][]Event + sendReq Request +} + +func (m *testModule) Process(requester Requester, events []Event) { + m.processed = append(m.processed, events) + if m.sendReq != nil { + if cs := requester.CanSendTo(); len(cs) > 0 { + requester.Send(cs[0], m.sendReq) + } + } +} + +func (m *testModule) expProcess(t *testing.T, expEvents []Event) { + if len(m.processed) == 0 { + t.Errorf("Missing call to %s.Process", m.name) + return + } + events := m.processed[0] + m.processed = m.processed[1:] + if !reflect.DeepEqual(events, expEvents) { + t.Errorf("Call to %s.Process with wrong events (expected %v, got %v)", m.name, expEvents, events) + } +} + +func (m *testModule) expNoMoreProcess(t *testing.T) { + for len(m.processed) > 0 { + t.Errorf("Unexpected call to %s.Process with events %v", m.name, m.processed[0]) + m.processed = m.processed[1:] + } +} diff --git a/beacon/light/request/server.go b/beacon/light/request/server.go new file mode 100644 index 0000000..a06dec9 --- /dev/null +++ b/beacon/light/request/server.go @@ -0,0 +1,451 @@ +// 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 request + +import ( + "math" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/log" +) + +var ( + // request events + EvResponse = &EventType{Name: "response", requestEvent: true} // data: RequestResponse; sent by requestServer + EvFail = &EventType{Name: "fail", requestEvent: true} // data: RequestResponse; sent by requestServer + EvTimeout = &EventType{Name: "timeout", requestEvent: true} // data: RequestResponse; sent by serverWithTimeout + // server events + EvRegistered = &EventType{Name: "registered"} // data: nil; sent by Scheduler + EvUnregistered = &EventType{Name: "unregistered"} // data: nil; sent by Scheduler + EvCanRequestAgain = &EventType{Name: "canRequestAgain"} // data: nil; sent by serverWithLimits +) + +const ( + softRequestTimeout = time.Second // allow resending request to a different server but do not cancel yet + hardRequestTimeout = time.Second * 10 // cancel request +) + +const ( + // serverWithLimits parameters + parallelAdjustUp = 0.1 // adjust parallelLimit up in case of success under full load + parallelAdjustDown = 1 // adjust parallelLimit down in case of timeout/failure + minParallelLimit = 1 // parallelLimit lower bound + defaultParallelLimit = 3 // parallelLimit initial value + minFailureDelay = time.Millisecond * 100 // minimum disable time in case of request failure + maxFailureDelay = time.Minute // maximum disable time in case of request failure + maxServerEventBuffer = 5 // server event allowance buffer limit + maxServerEventRate = time.Second // server event allowance buffer recharge rate +) + +// requestServer can send requests in a non-blocking way and feed back events +// through the event callback. After each request it should send back either +// EvResponse or EvFail. Additionally, it may also send application-defined +// events that the Modules can interpret. +type requestServer interface { + Name() string + Subscribe(eventCallback func(Event)) + SendRequest(ID, Request) + Unsubscribe() +} + +// server is implemented by a requestServer wrapped into serverWithTimeout and +// serverWithLimits and is used by Scheduler. +// In addition to requestServer functionality, server can also handle timeouts, +// limit the number of parallel in-flight requests and temporarily disable +// new requests based on timeouts and response failures. +type server interface { + Server + subscribe(eventCallback func(Event)) + canRequestNow() bool + sendRequest(Request) ID + fail(string) + unsubscribe() +} + +// NewServer wraps a requestServer and returns a server +func NewServer(rs requestServer, clock mclock.Clock) server { + s := &serverWithLimits{} + s.parent = rs + s.serverWithTimeout.init(clock) + s.init() + return s +} + +// EventType identifies an event type, either related to a request or the server +// in general. Server events can also be externally defined. +type EventType struct { + Name string + requestEvent bool // all request events are pre-defined in request package +} + +// Event describes an event where the type of Data depends on Type. +// Server field is not required when sent through the event callback; it is filled +// out when processed by the Scheduler. Note that the Scheduler can also create +// and send events (EvRegistered, EvUnregistered) directly. +type Event struct { + Type *EventType + Server Server // filled by Scheduler + Data any +} + +// IsRequestEvent returns true if the event is a request event +func (e *Event) IsRequestEvent() bool { + return e.Type.requestEvent +} + +// RequestInfo assumes that the event is a request event and returns its contents +// in a convenient form. +func (e *Event) RequestInfo() (ServerAndID, Request, Response) { + data := e.Data.(RequestResponse) + return ServerAndID{Server: e.Server, ID: data.ID}, data.Request, data.Response +} + +// RequestResponse is the Data type of request events. +type RequestResponse struct { + ID ID + Request Request + Response Response +} + +// serverWithTimeout wraps a requestServer and introduces timeouts. +// The request's lifecycle is concluded if EvResponse or EvFail emitted by the +// parent requestServer. If this does not happen until softRequestTimeout then +// EvTimeout is emitted, after which the final EvResponse or EvFail is still +// guaranteed to follow. +// If the parent fails to send this final event for hardRequestTimeout then +// serverWithTimeout emits EvFail and discards any further events from the +// parent related to the given request. +type serverWithTimeout struct { + parent requestServer + lock sync.Mutex + clock mclock.Clock + childEventCb func(event Event) + timeouts map[ID]mclock.Timer + lastID ID +} + +// Name implements request.Server +func (s *serverWithTimeout) Name() string { + return s.parent.Name() +} + +// init initializes serverWithTimeout +func (s *serverWithTimeout) init(clock mclock.Clock) { + s.clock = clock + s.timeouts = make(map[ID]mclock.Timer) +} + +// subscribe subscribes to events which include parent (requestServer) events +// plus EvTimeout. +func (s *serverWithTimeout) subscribe(eventCallback func(event Event)) { + s.lock.Lock() + defer s.lock.Unlock() + + s.childEventCb = eventCallback + s.parent.Subscribe(s.eventCallback) +} + +// sendRequest generated a new request ID, emits EvRequest, sets up the timeout +// timer, then sends the request through the parent (requestServer). +func (s *serverWithTimeout) sendRequest(request Request) (reqId ID) { + s.lock.Lock() + s.lastID++ + id := s.lastID + s.startTimeout(RequestResponse{ID: id, Request: request}) + s.lock.Unlock() + s.parent.SendRequest(id, request) + return id +} + +// eventCallback is called by parent (requestServer) event subscription. +func (s *serverWithTimeout) eventCallback(event Event) { + s.lock.Lock() + defer s.lock.Unlock() + + switch event.Type { + case EvResponse, EvFail: + id := event.Data.(RequestResponse).ID + if timer, ok := s.timeouts[id]; ok { + // Note: if stopping the timer is unsuccessful then the resulting AfterFunc + // call will just do nothing + timer.Stop() + delete(s.timeouts, id) + if s.childEventCb != nil { + s.childEventCb(event) + } + } + default: + if s.childEventCb != nil { + s.childEventCb(event) + } + } +} + +// startTimeout starts a timeout timer for the given request. +func (s *serverWithTimeout) startTimeout(reqData RequestResponse) { + id := reqData.ID + s.timeouts[id] = s.clock.AfterFunc(softRequestTimeout, func() { + s.lock.Lock() + if _, ok := s.timeouts[id]; !ok { + s.lock.Unlock() + return + } + s.timeouts[id] = s.clock.AfterFunc(hardRequestTimeout-softRequestTimeout, func() { + s.lock.Lock() + if _, ok := s.timeouts[id]; !ok { + s.lock.Unlock() + return + } + delete(s.timeouts, id) + childEventCb := s.childEventCb + s.lock.Unlock() + if childEventCb != nil { + childEventCb(Event{Type: EvFail, Data: reqData}) + } + }) + childEventCb := s.childEventCb + s.lock.Unlock() + if childEventCb != nil { + childEventCb(Event{Type: EvTimeout, Data: reqData}) + } + }) +} + +// unsubscribe stops all goroutines associated with the server. +func (s *serverWithTimeout) unsubscribe() { + s.lock.Lock() + for _, timer := range s.timeouts { + if timer != nil { + timer.Stop() + } + } + s.lock.Unlock() + s.parent.Unsubscribe() +} + +// serverWithLimits wraps serverWithTimeout and implements server. It limits the +// number of parallel in-flight requests and prevents sending new requests when a +// pending one has already timed out. Server events are also rate limited. +// It also implements a failure delay mechanism that adds an exponentially growing +// delay each time a request fails (wrong answer or hard timeout). This makes the +// syncing mechanism less brittle as temporary failures of the server might happen +// sometimes, but still avoids hammering a non-functional server with requests. +type serverWithLimits struct { + serverWithTimeout + lock sync.Mutex + childEventCb func(event Event) + softTimeouts map[ID]struct{} + pendingCount, timeoutCount int + parallelLimit float32 + sendEvent bool + delayTimer mclock.Timer + delayCounter int + failureDelayEnd mclock.AbsTime + failureDelay float64 + serverEventBuffer int + eventBufferUpdated mclock.AbsTime +} + +// init initializes serverWithLimits +func (s *serverWithLimits) init() { + s.softTimeouts = make(map[ID]struct{}) + s.parallelLimit = defaultParallelLimit + s.serverEventBuffer = maxServerEventBuffer +} + +// subscribe subscribes to events which include parent (serverWithTimeout) events +// plus EvCanRequestAgain. +func (s *serverWithLimits) subscribe(eventCallback func(event Event)) { + s.lock.Lock() + defer s.lock.Unlock() + + s.childEventCb = eventCallback + s.serverWithTimeout.subscribe(s.eventCallback) +} + +// eventCallback is called by parent (serverWithTimeout) event subscription. +func (s *serverWithLimits) eventCallback(event Event) { + s.lock.Lock() + var sendCanRequestAgain bool + passEvent := true + switch event.Type { + case EvTimeout: + id := event.Data.(RequestResponse).ID + s.softTimeouts[id] = struct{}{} + s.timeoutCount++ + s.parallelLimit -= parallelAdjustDown + if s.parallelLimit < minParallelLimit { + s.parallelLimit = minParallelLimit + } + log.Debug("Server timeout", "count", s.timeoutCount, "parallelLimit", s.parallelLimit) + case EvResponse, EvFail: + id := event.Data.(RequestResponse).ID + if _, ok := s.softTimeouts[id]; ok { + delete(s.softTimeouts, id) + s.timeoutCount-- + log.Debug("Server timeout finalized", "count", s.timeoutCount, "parallelLimit", s.parallelLimit) + } + if event.Type == EvResponse && s.pendingCount >= int(s.parallelLimit) { + s.parallelLimit += parallelAdjustUp + } + s.pendingCount-- + if s.canRequest() { + sendCanRequestAgain = s.sendEvent + s.sendEvent = false + } + if event.Type == EvFail { + s.failLocked("failed request") + } + default: + // server event; check rate limit + if s.serverEventBuffer < maxServerEventBuffer { + now := s.clock.Now() + sinceUpdate := time.Duration(now - s.eventBufferUpdated) + if sinceUpdate >= maxServerEventRate*time.Duration(maxServerEventBuffer-s.serverEventBuffer) { + s.serverEventBuffer = maxServerEventBuffer + s.eventBufferUpdated = now + } else { + addBuffer := int(sinceUpdate / maxServerEventRate) + s.serverEventBuffer += addBuffer + s.eventBufferUpdated += mclock.AbsTime(maxServerEventRate * time.Duration(addBuffer)) + } + } + if s.serverEventBuffer > 0 { + s.serverEventBuffer-- + } else { + passEvent = false + } + } + childEventCb := s.childEventCb + s.lock.Unlock() + if passEvent && childEventCb != nil { + childEventCb(event) + } + if sendCanRequestAgain && childEventCb != nil { + childEventCb(Event{Type: EvCanRequestAgain}) + } +} + +// sendRequest sends a request through the parent (serverWithTimeout). +func (s *serverWithLimits) sendRequest(request Request) (reqId ID) { + s.lock.Lock() + s.pendingCount++ + s.lock.Unlock() + return s.serverWithTimeout.sendRequest(request) +} + +// unsubscribe stops all goroutines associated with the server. +func (s *serverWithLimits) unsubscribe() { + s.lock.Lock() + if s.delayTimer != nil { + s.delayTimer.Stop() + s.delayTimer = nil + } + s.childEventCb = nil + s.lock.Unlock() + s.serverWithTimeout.unsubscribe() +} + +// canRequest checks whether a new request can be started. +func (s *serverWithLimits) canRequest() bool { + if s.delayTimer != nil || s.pendingCount >= int(s.parallelLimit) || s.timeoutCount > 0 { + return false + } + if s.parallelLimit < minParallelLimit { + s.parallelLimit = minParallelLimit + } + return true +} + +// canRequestNow checks whether a new request can be started, according to the +// current in-flight request count and parallelLimit, and also the failure delay +// timer. +// If it returns false then it is guaranteed that an EvCanRequestAgain will be +// sent whenever the server becomes available for requesting again. +func (s *serverWithLimits) canRequestNow() bool { + var sendCanRequestAgain bool + s.lock.Lock() + canRequest := s.canRequest() + if canRequest { + sendCanRequestAgain = s.sendEvent + s.sendEvent = false + } + childEventCb := s.childEventCb + s.lock.Unlock() + if sendCanRequestAgain && childEventCb != nil { + childEventCb(Event{Type: EvCanRequestAgain}) + } + return canRequest +} + +// delay sets the delay timer to the given duration, disabling new requests for +// the given period. +func (s *serverWithLimits) delay(delay time.Duration) { + if s.delayTimer != nil { + // Note: if stopping the timer is unsuccessful then the resulting AfterFunc + // call will just do nothing + s.delayTimer.Stop() + s.delayTimer = nil + } + + s.delayCounter++ + delayCounter := s.delayCounter + log.Debug("Server delay started", "length", delay) + s.delayTimer = s.clock.AfterFunc(delay, func() { + log.Debug("Server delay ended", "length", delay) + var sendCanRequestAgain bool + s.lock.Lock() + if s.delayTimer != nil && s.delayCounter == delayCounter { // do nothing if there is a new timer now + s.delayTimer = nil + if s.canRequest() { + sendCanRequestAgain = s.sendEvent + s.sendEvent = false + } + } + childEventCb := s.childEventCb + s.lock.Unlock() + if sendCanRequestAgain && childEventCb != nil { + childEventCb(Event{Type: EvCanRequestAgain}) + } + }) +} + +// fail reports that a response from the server was found invalid by the processing +// Module, disabling new requests for a dynamically adjusted time period. +func (s *serverWithLimits) fail(desc string) { + s.lock.Lock() + defer s.lock.Unlock() + + s.failLocked(desc) +} + +// failLocked calculates the dynamic failure delay and applies it. +func (s *serverWithLimits) failLocked(desc string) { + log.Debug("Server error", "description", desc) + s.failureDelay *= 2 + now := s.clock.Now() + if now > s.failureDelayEnd { + s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay)) + } + if s.failureDelay < float64(minFailureDelay) { + s.failureDelay = float64(minFailureDelay) + } + s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay) + s.delay(time.Duration(s.failureDelay)) +} diff --git a/beacon/light/request/server_test.go b/beacon/light/request/server_test.go new file mode 100644 index 0000000..fef5d06 --- /dev/null +++ b/beacon/light/request/server_test.go @@ -0,0 +1,182 @@ +package request + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common/mclock" +) + +const ( + testRequest = "Life, the Universe, and Everything" + testResponse = 42 +) + +var testEventType = &EventType{Name: "testEvent"} + +func TestServerEvents(t *testing.T) { + rs := &testRequestServer{} + clock := &mclock.Simulated{} + srv := NewServer(rs, clock) + var lastEventType *EventType + srv.subscribe(func(event Event) { lastEventType = event.Type }) + evTypeName := func(evType *EventType) string { + if evType == nil { + return "none" + } + return evType.Name + } + expEvent := func(expType *EventType) { + if lastEventType != expType { + t.Errorf("Wrong event type (expected %s, got %s)", evTypeName(expType), evTypeName(lastEventType)) + } + lastEventType = nil + } + // user events should simply be passed through + rs.eventCb(Event{Type: testEventType}) + expEvent(testEventType) + // send request, soft timeout, then valid response + srv.sendRequest(testRequest) + clock.WaitForTimers(1) + clock.Run(softRequestTimeout) + expEvent(EvTimeout) + rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}}) + expEvent(EvResponse) + // send request, hard timeout (response after hard timeout should be ignored) + srv.sendRequest(testRequest) + clock.WaitForTimers(1) + clock.Run(softRequestTimeout) + expEvent(EvTimeout) + clock.WaitForTimers(1) + clock.Run(hardRequestTimeout) + expEvent(EvFail) + rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}}) + expEvent(nil) + srv.unsubscribe() +} + +func TestServerParallel(t *testing.T) { + rs := &testRequestServer{} + srv := NewServer(rs, &mclock.Simulated{}) + srv.subscribe(func(event Event) {}) + + expSend := func(expSent int) { + var sent int + for sent <= expSent { + if !srv.canRequestNow() { + break + } + sent++ + srv.sendRequest(testRequest) + } + if sent != expSent { + t.Errorf("Wrong number of parallel requests accepted (expected %d, got %d)", expSent, sent) + } + } + // max out parallel allowance + expSend(defaultParallelLimit) + // 1 answered, should accept 1 more + rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}}) + expSend(1) + // 2 answered, should accept 2 more + rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 2, Request: testRequest, Response: testResponse}}) + rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 3, Request: testRequest, Response: testResponse}}) + expSend(2) + // failed request, should decrease allowance and not accept more + rs.eventCb(Event{Type: EvFail, Data: RequestResponse{ID: 4, Request: testRequest}}) + expSend(0) + srv.unsubscribe() +} + +func TestServerFail(t *testing.T) { + rs := &testRequestServer{} + clock := &mclock.Simulated{} + srv := NewServer(rs, clock) + srv.subscribe(func(event Event) {}) + expCanRequest := func(expCanRequest bool) { + if canRequest := srv.canRequestNow(); canRequest != expCanRequest { + t.Errorf("Wrong result for canRequestNow (expected %v, got %v)", expCanRequest, canRequest) + } + } + // timed out request + expCanRequest(true) + srv.sendRequest(testRequest) + clock.WaitForTimers(1) + expCanRequest(true) + clock.Run(softRequestTimeout) + expCanRequest(false) // cannot request when there is a timed out request + rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}}) + expCanRequest(true) + // explicit server.Fail + srv.fail("") + clock.WaitForTimers(1) + expCanRequest(false) // cannot request for a while after a failure + clock.Run(minFailureDelay) + expCanRequest(true) + // request returned with EvFail + srv.sendRequest(testRequest) + rs.eventCb(Event{Type: EvFail, Data: RequestResponse{ID: 2, Request: testRequest}}) + clock.WaitForTimers(1) + expCanRequest(false) // EvFail should also start failure delay + clock.Run(minFailureDelay) + expCanRequest(false) // second failure delay is longer, should still be disabled + clock.Run(minFailureDelay) + expCanRequest(true) + srv.unsubscribe() +} + +func TestServerEventRateLimit(t *testing.T) { + rs := &testRequestServer{} + clock := &mclock.Simulated{} + srv := NewServer(rs, clock) + var eventCount int + srv.subscribe(func(event Event) { + eventCount++ + }) + expEvents := func(send, expAllowed int) { + eventCount = 0 + for sent := 0; sent < send; sent++ { + rs.eventCb(Event{Type: testEventType}) + } + if eventCount != expAllowed { + t.Errorf("Wrong number of server events passing rate limitation (sent %d, expected %d, got %d)", send, expAllowed, eventCount) + } + } + expEvents(maxServerEventBuffer+5, maxServerEventBuffer) + clock.Run(maxServerEventRate) + expEvents(5, 1) + clock.Run(maxServerEventRate * maxServerEventBuffer * 2) + expEvents(maxServerEventBuffer+5, maxServerEventBuffer) + srv.unsubscribe() +} + +func TestServerUnsubscribe(t *testing.T) { + rs := &testRequestServer{} + clock := &mclock.Simulated{} + srv := NewServer(rs, clock) + var eventCount int + srv.subscribe(func(event Event) { + eventCount++ + }) + eventCb := rs.eventCb + eventCb(Event{Type: testEventType}) + if eventCount != 1 { + t.Errorf("Server event callback not called before unsubscribe") + } + srv.unsubscribe() + if rs.eventCb != nil { + t.Errorf("Server event callback not removed after unsubscribe") + } + eventCb(Event{Type: testEventType}) + if eventCount != 1 { + t.Errorf("Server event callback called after unsubscribe") + } +} + +type testRequestServer struct { + eventCb func(Event) +} + +func (rs *testRequestServer) Name() string { return "" } +func (rs *testRequestServer) Subscribe(eventCb func(Event)) { rs.eventCb = eventCb } +func (rs *testRequestServer) SendRequest(ID, Request) {} +func (rs *testRequestServer) Unsubscribe() { rs.eventCb = nil } diff --git a/beacon/light/sync/head_sync.go b/beacon/light/sync/head_sync.go new file mode 100644 index 0000000..dd05d39 --- /dev/null +++ b/beacon/light/sync/head_sync.go @@ -0,0 +1,202 @@ +// 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 sync + +import ( + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/log" +) + +type headTracker interface { + ValidateOptimistic(update types.OptimisticUpdate) (bool, error) + ValidateFinality(head types.FinalityUpdate) (bool, error) + ValidatedFinality() (types.FinalityUpdate, bool) + SetPrefetchHead(head types.HeadInfo) +} + +// HeadSync implements request.Module; it updates the validated and prefetch +// heads of HeadTracker based on the EvHead and EvSignedHead events coming from +// registered servers. +// It can also postpone the validation of the latest announced signed head +// until the committee chain is synced up to at least the required period. +type HeadSync struct { + headTracker headTracker + chain committeeChain + nextSyncPeriod uint64 + chainInit bool + unvalidatedOptimistic map[request.Server]types.OptimisticUpdate + unvalidatedFinality map[request.Server]types.FinalityUpdate + serverHeads map[request.Server]types.HeadInfo + reqFinalityEpoch map[request.Server]uint64 // next epoch to request finality update + headServerCount map[types.HeadInfo]headServerCount + headCounter uint64 + prefetchHead types.HeadInfo +} + +// headServerCount is associated with most recently seen head infos; it counts +// the number of servers currently having the given head info as their announced +// head and a counter signaling how recent that head is. +// This data is used for selecting the prefetch head. +type headServerCount struct { + serverCount int + headCounter uint64 +} + +// NewHeadSync creates a new HeadSync. +func NewHeadSync(headTracker headTracker, chain committeeChain) *HeadSync { + s := &HeadSync{ + headTracker: headTracker, + chain: chain, + unvalidatedOptimistic: make(map[request.Server]types.OptimisticUpdate), + unvalidatedFinality: make(map[request.Server]types.FinalityUpdate), + serverHeads: make(map[request.Server]types.HeadInfo), + headServerCount: make(map[types.HeadInfo]headServerCount), + reqFinalityEpoch: make(map[request.Server]uint64), + } + return s +} + +// Process implements request.Module. +func (s *HeadSync) Process(requester request.Requester, events []request.Event) { + nextPeriod, chainInit := s.chain.NextSyncPeriod() + if nextPeriod != s.nextSyncPeriod || chainInit != s.chainInit { + s.nextSyncPeriod, s.chainInit = nextPeriod, chainInit + s.processUnvalidatedUpdates() + } + + for _, event := range events { + switch event.Type { + case EvNewHead: + s.setServerHead(event.Server, event.Data.(types.HeadInfo)) + case EvNewOptimisticUpdate: + update := event.Data.(types.OptimisticUpdate) + s.newOptimisticUpdate(event.Server, update) + epoch := update.Attested.Epoch() + if epoch < s.reqFinalityEpoch[event.Server] { + continue + } + if finality, ok := s.headTracker.ValidatedFinality(); ok && finality.Attested.Header.Epoch() >= epoch { + continue + } + requester.Send(event.Server, ReqFinality{}) + s.reqFinalityEpoch[event.Server] = epoch + 1 + case EvNewFinalityUpdate: + s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate)) + case request.EvResponse: + _, _, resp := event.RequestInfo() + s.newFinalityUpdate(event.Server, resp.(types.FinalityUpdate)) + case request.EvUnregistered: + s.setServerHead(event.Server, types.HeadInfo{}) + delete(s.serverHeads, event.Server) + delete(s.unvalidatedOptimistic, event.Server) + delete(s.unvalidatedFinality, event.Server) + } + } +} + +// newOptimisticUpdate handles received optimistic update; either validates it if +// the chain is properly synced or stores it for further validation. +func (s *HeadSync) newOptimisticUpdate(server request.Server, optimisticUpdate types.OptimisticUpdate) { + if !s.chainInit || types.SyncPeriod(optimisticUpdate.SignatureSlot) > s.nextSyncPeriod { + s.unvalidatedOptimistic[server] = optimisticUpdate + return + } + if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil { + log.Debug("Error validating optimistic update", "error", err) + } +} + +// newFinalityUpdate handles received finality update; either validates it if +// the chain is properly synced or stores it for further validation. +func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) { + if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod { + s.unvalidatedFinality[server] = finalityUpdate + return + } + if _, err := s.headTracker.ValidateFinality(finalityUpdate); err != nil { + log.Debug("Error validating finality update", "error", err) + } +} + +// processUnvalidatedUpdates iterates the list of unvalidated updates and validates +// those which can be validated. +func (s *HeadSync) processUnvalidatedUpdates() { + if !s.chainInit { + return + } + for server, optimisticUpdate := range s.unvalidatedOptimistic { + if types.SyncPeriod(optimisticUpdate.SignatureSlot) <= s.nextSyncPeriod { + if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil { + log.Debug("Error validating deferred optimistic update", "error", err) + } + delete(s.unvalidatedOptimistic, server) + } + } + for server, finalityUpdate := range s.unvalidatedFinality { + if types.SyncPeriod(finalityUpdate.SignatureSlot) <= s.nextSyncPeriod { + if _, err := s.headTracker.ValidateFinality(finalityUpdate); err != nil { + log.Debug("Error validating deferred finality update", "error", err) + } + delete(s.unvalidatedFinality, server) + } + } +} + +// setServerHead processes non-validated server head announcements and updates +// the prefetch head if necessary. +func (s *HeadSync) setServerHead(server request.Server, head types.HeadInfo) bool { + if oldHead, ok := s.serverHeads[server]; ok { + if head == oldHead { + return false + } + h := s.headServerCount[oldHead] + if h.serverCount--; h.serverCount > 0 { + s.headServerCount[oldHead] = h + } else { + delete(s.headServerCount, oldHead) + } + } + if head != (types.HeadInfo{}) { + h, ok := s.headServerCount[head] + if !ok { + s.headCounter++ + h.headCounter = s.headCounter + } + h.serverCount++ + s.headServerCount[head] = h + s.serverHeads[server] = head + } else { + delete(s.serverHeads, server) + } + var ( + bestHead types.HeadInfo + bestHeadInfo headServerCount + ) + for head, headServerCount := range s.headServerCount { + if headServerCount.serverCount > bestHeadInfo.serverCount || + (headServerCount.serverCount == bestHeadInfo.serverCount && headServerCount.headCounter > bestHeadInfo.headCounter) { + bestHead, bestHeadInfo = head, headServerCount + } + } + if bestHead == s.prefetchHead { + return false + } + s.prefetchHead = bestHead + s.headTracker.SetPrefetchHead(bestHead) + return true +} diff --git a/beacon/light/sync/head_sync_test.go b/beacon/light/sync/head_sync_test.go new file mode 100644 index 0000000..d095d6a --- /dev/null +++ b/beacon/light/sync/head_sync_test.go @@ -0,0 +1,183 @@ +// 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 sync + +import ( + "testing" + + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/common" +) + +var ( + testServer1 = testServer("testServer1") + testServer2 = testServer("testServer2") + testServer3 = testServer("testServer3") + testServer4 = testServer("testServer4") + testServer5 = testServer("testServer5") + + testHead0 = types.HeadInfo{} + testHead1 = types.HeadInfo{Slot: 123, BlockRoot: common.Hash{1}} + testHead2 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{2}} + testHead3 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{3}} + testHead4 = types.HeadInfo{Slot: 125, BlockRoot: common.Hash{4}} + + testOptUpdate1 = types.OptimisticUpdate{SignatureSlot: 0x0124, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}}} + testOptUpdate2 = types.OptimisticUpdate{SignatureSlot: 0x2010, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}}} + // testOptUpdate3 is at the end of period 1 but signed in period 2 + testOptUpdate3 = types.OptimisticUpdate{SignatureSlot: 0x4000, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x3fff, StateRoot: common.Hash{3}}}} + testOptUpdate4 = types.OptimisticUpdate{SignatureSlot: 0x6444, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x6443, StateRoot: common.Hash{4}}}} +) + +func finality(opt types.OptimisticUpdate) types.FinalityUpdate { + return types.FinalityUpdate{ + SignatureSlot: opt.SignatureSlot, + Attested: opt.Attested, + Finalized: types.HeaderWithExecProof{Header: types.Header{Slot: (opt.Attested.Header.Slot - 64) & uint64(0xffffffffffffffe0)}}, + } +} + +type testServer string + +func (t testServer) Name() string { + return string(t) +} + +func TestValidatedHead(t *testing.T) { + chain := &TestCommitteeChain{} + ht := &TestHeadTracker{} + headSync := NewHeadSync(ht, chain) + ts := NewTestScheduler(t, headSync) + + ht.ExpValidated(t, 0, nil) + + ts.AddServer(testServer1, 1) + ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate1) + ts.Run(1, testServer1, ReqFinality{}) + // announced head should be queued because of uninitialized chain + ht.ExpValidated(t, 1, nil) + + chain.SetNextSyncPeriod(0) // initialize chain + ts.Run(2) + // expect previously queued head to be validated + ht.ExpValidated(t, 2, []types.OptimisticUpdate{testOptUpdate1}) + + chain.SetNextSyncPeriod(1) + ts.ServerEvent(EvNewFinalityUpdate, testServer1, finality(testOptUpdate2)) + ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate2) + ts.AddServer(testServer2, 1) + ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate2) + ts.Run(3) + // expect both head announcements to be validated instantly + ht.ExpValidated(t, 3, []types.OptimisticUpdate{testOptUpdate2, testOptUpdate2}) + + ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate3) + ts.AddServer(testServer3, 1) + ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4) + // finality should be requested from both servers + ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{}) + // future period announced heads should be queued + ht.ExpValidated(t, 4, nil) + + chain.SetNextSyncPeriod(2) + ts.Run(5) + // testOptUpdate3 can be validated now but not testOptUpdate4 + ht.ExpValidated(t, 5, []types.OptimisticUpdate{testOptUpdate3}) + + ts.AddServer(testServer4, 1) + ts.ServerEvent(EvNewOptimisticUpdate, testServer4, testOptUpdate3) + // new server joined with recent optimistic update but still no finality; should be requested + ts.Run(6, testServer4, ReqFinality{}) + ht.ExpValidated(t, 6, []types.OptimisticUpdate{testOptUpdate3}) + + ts.AddServer(testServer5, 1) + ts.RequestEvent(request.EvResponse, ts.Request(6, 1), finality(testOptUpdate3)) + ts.ServerEvent(EvNewOptimisticUpdate, testServer5, testOptUpdate3) + // finality update request answered; new server should not be requested + ts.Run(7) + ht.ExpValidated(t, 7, []types.OptimisticUpdate{testOptUpdate3}) + + // server 3 disconnected without proving period 3, its announced head should be dropped + ts.RemoveServer(testServer3) + ts.Run(8) + ht.ExpValidated(t, 8, nil) + + chain.SetNextSyncPeriod(3) + ts.Run(9) + // testOptUpdate4 could be validated now but it's not queued by any registered server + ht.ExpValidated(t, 9, nil) + + ts.ServerEvent(EvNewFinalityUpdate, testServer2, finality(testOptUpdate4)) + ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate4) + ts.Run(10) + // now testOptUpdate4 should be validated + ht.ExpValidated(t, 10, []types.OptimisticUpdate{testOptUpdate4}) +} + +func TestPrefetchHead(t *testing.T) { + chain := &TestCommitteeChain{} + ht := &TestHeadTracker{} + headSync := NewHeadSync(ht, chain) + ts := NewTestScheduler(t, headSync) + + ht.ExpPrefetch(t, 0, testHead0) // no servers registered + + ts.AddServer(testServer1, 1) + ts.ServerEvent(EvNewHead, testServer1, testHead1) + ts.Run(1) + ht.ExpPrefetch(t, 1, testHead1) // s1: h1 + + ts.AddServer(testServer2, 1) + ts.ServerEvent(EvNewHead, testServer2, testHead2) + ts.Run(2) + ht.ExpPrefetch(t, 2, testHead2) // s1: h1, s2: h2 + + ts.ServerEvent(EvNewHead, testServer1, testHead2) + ts.Run(3) + ht.ExpPrefetch(t, 3, testHead2) // s1: h2, s2: h2 + + ts.AddServer(testServer3, 1) + ts.ServerEvent(EvNewHead, testServer3, testHead3) + ts.Run(4) + ht.ExpPrefetch(t, 4, testHead2) // s1: h2, s2: h2, s3: h3 + + ts.AddServer(testServer4, 1) + ts.ServerEvent(EvNewHead, testServer4, testHead4) + ts.Run(5) + ht.ExpPrefetch(t, 5, testHead2) // s1: h2, s2: h2, s3: h3, s4: h4 + + ts.ServerEvent(EvNewHead, testServer2, testHead3) + ts.Run(6) + ht.ExpPrefetch(t, 6, testHead3) // s1: h2, s2: h3, s3: h3, s4: h4 + + ts.RemoveServer(testServer3) + ts.Run(7) + ht.ExpPrefetch(t, 7, testHead4) // s1: h2, s2: h3, s4: h4 + + ts.RemoveServer(testServer1) + ts.Run(8) + ht.ExpPrefetch(t, 8, testHead4) // s2: h3, s4: h4 + + ts.RemoveServer(testServer4) + ts.Run(9) + ht.ExpPrefetch(t, 9, testHead3) // s2: h3 + + ts.RemoveServer(testServer2) + ts.Run(10) + ht.ExpPrefetch(t, 10, testHead0) // no servers registered +} diff --git a/beacon/light/sync/test_helpers.go b/beacon/light/sync/test_helpers.go new file mode 100644 index 0000000..b331bf7 --- /dev/null +++ b/beacon/light/sync/test_helpers.go @@ -0,0 +1,259 @@ +// 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 sync + +import ( + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/beacon/light" + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/types" +) + +type requestWithID struct { + sid request.ServerAndID + request request.Request +} + +type TestScheduler struct { + t *testing.T + module request.Module + events []request.Event + servers []request.Server + allowance map[request.Server]int + sent map[int][]requestWithID + testIndex int + expFail map[request.Server]int // expected Server.Fail calls during next Run + lastId request.ID +} + +func NewTestScheduler(t *testing.T, module request.Module) *TestScheduler { + return &TestScheduler{ + t: t, + module: module, + allowance: make(map[request.Server]int), + expFail: make(map[request.Server]int), + sent: make(map[int][]requestWithID), + } +} + +func (ts *TestScheduler) Run(testIndex int, exp ...any) { + expReqs := make([]requestWithID, len(exp)/2) + id := ts.lastId + for i := range expReqs { + id++ + expReqs[i] = requestWithID{ + sid: request.ServerAndID{Server: exp[i*2].(request.Server), ID: id}, + request: exp[i*2+1].(request.Request), + } + } + if len(expReqs) == 0 { + expReqs = nil + } + + ts.testIndex = testIndex + ts.module.Process(ts, ts.events) + ts.events = nil + + for server, count := range ts.expFail { + delete(ts.expFail, server) + if count == 0 { + continue + } + ts.t.Errorf("Missing %d Server.Fail(s) from server %s in test case #%d", count, server.Name(), testIndex) + } + + if !reflect.DeepEqual(ts.sent[testIndex], expReqs) { + ts.t.Errorf("Wrong sent requests in test case #%d (expected %v, got %v)", testIndex, expReqs, ts.sent[testIndex]) + } +} + +func (ts *TestScheduler) CanSendTo() (cs []request.Server) { + for _, server := range ts.servers { + if ts.allowance[server] > 0 { + cs = append(cs, server) + } + } + return +} + +func (ts *TestScheduler) Send(server request.Server, req request.Request) request.ID { + ts.lastId++ + ts.sent[ts.testIndex] = append(ts.sent[ts.testIndex], requestWithID{ + sid: request.ServerAndID{Server: server, ID: ts.lastId}, + request: req, + }) + ts.allowance[server]-- + return ts.lastId +} + +func (ts *TestScheduler) Fail(server request.Server, desc string) { + if ts.expFail[server] == 0 { + ts.t.Errorf("Unexpected Fail from server %s in test case #%d: %s", server.Name(), ts.testIndex, desc) + return + } + ts.expFail[server]-- +} + +func (ts *TestScheduler) Request(testIndex, reqIndex int) requestWithID { + if len(ts.sent[testIndex]) < reqIndex { + ts.t.Errorf("Missing request from test case %d index %d", testIndex, reqIndex) + return requestWithID{} + } + return ts.sent[testIndex][reqIndex-1] +} + +func (ts *TestScheduler) ServerEvent(evType *request.EventType, server request.Server, data any) { + ts.events = append(ts.events, request.Event{ + Type: evType, + Server: server, + Data: data, + }) +} + +func (ts *TestScheduler) RequestEvent(evType *request.EventType, req requestWithID, resp request.Response) { + if req.request == nil { + return + } + ts.events = append(ts.events, request.Event{ + Type: evType, + Server: req.sid.Server, + Data: request.RequestResponse{ + ID: req.sid.ID, + Request: req.request, + Response: resp, + }, + }) +} + +func (ts *TestScheduler) AddServer(server request.Server, allowance int) { + ts.servers = append(ts.servers, server) + ts.allowance[server] = allowance + ts.ServerEvent(request.EvRegistered, server, nil) +} + +func (ts *TestScheduler) RemoveServer(server request.Server) { + ts.servers = append(ts.servers, server) + for i, s := range ts.servers { + if s == server { + copy(ts.servers[i:len(ts.servers)-1], ts.servers[i+1:]) + ts.servers = ts.servers[:len(ts.servers)-1] + break + } + } + delete(ts.allowance, server) + ts.ServerEvent(request.EvUnregistered, server, nil) +} + +func (ts *TestScheduler) AddAllowance(server request.Server, allowance int) { + ts.allowance[server] += allowance +} + +func (ts *TestScheduler) ExpFail(server request.Server) { + ts.expFail[server]++ +} + +type TestCommitteeChain struct { + fsp, nsp uint64 + init bool +} + +func (tc *TestCommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error { + tc.fsp, tc.nsp, tc.init = bootstrap.Header.SyncPeriod(), bootstrap.Header.SyncPeriod()+2, true + return nil +} + +func (tc *TestCommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error { + period := update.AttestedHeader.Header.SyncPeriod() + if period < tc.fsp || period > tc.nsp || !tc.init { + return light.ErrInvalidPeriod + } + if period == tc.nsp { + tc.nsp++ + } + return nil +} + +func (tc *TestCommitteeChain) NextSyncPeriod() (uint64, bool) { + return tc.nsp, tc.init +} + +func (tc *TestCommitteeChain) ExpInit(t *testing.T, ExpInit bool) { + if tc.init != ExpInit { + t.Errorf("Incorrect init flag (expected %v, got %v)", ExpInit, tc.init) + } +} + +func (tc *TestCommitteeChain) SetNextSyncPeriod(nsp uint64) { + tc.init, tc.nsp = true, nsp +} + +func (tc *TestCommitteeChain) ExpNextSyncPeriod(t *testing.T, expNsp uint64) { + tc.ExpInit(t, true) + if tc.nsp != expNsp { + t.Errorf("Incorrect NextSyncPeriod (expected %d, got %d)", expNsp, tc.nsp) + } +} + +type TestHeadTracker struct { + phead types.HeadInfo + validated []types.OptimisticUpdate + finality types.FinalityUpdate +} + +func (ht *TestHeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) { + ht.validated = append(ht.validated, update) + return true, nil +} + +func (ht *TestHeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) { + ht.finality = update + return true, nil +} + +func (ht *TestHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) { + return ht.finality, ht.finality.Attested.Header != (types.Header{}) +} + +func (ht *TestHeadTracker) ExpValidated(t *testing.T, tci int, expHeads []types.OptimisticUpdate) { + for i, expHead := range expHeads { + if i >= len(ht.validated) { + t.Errorf("Missing validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got none)", tci, i, expHead.Attested.Header.Slot, expHead.Attested.Header.Hash()) + continue + } + if !reflect.DeepEqual(ht.validated[i], expHead) { + vhead := ht.validated[i].Attested.Header + t.Errorf("Wrong validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, i, expHead.Attested.Header.Slot, expHead.Attested.Header.Hash(), vhead.Slot, vhead.Hash()) + } + } + for i := len(expHeads); i < len(ht.validated); i++ { + vhead := ht.validated[i].Attested.Header + t.Errorf("Unexpected validated head in test case #%d index #%d (expected none, got {slot %d blockRoot %x})", tci, i, vhead.Slot, vhead.Hash()) + } + ht.validated = nil +} + +func (ht *TestHeadTracker) SetPrefetchHead(head types.HeadInfo) { + ht.phead = head +} + +func (ht *TestHeadTracker) ExpPrefetch(t *testing.T, tci int, exp types.HeadInfo) { + if ht.phead != exp { + t.Errorf("Wrong prefetch head in test case #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, exp.Slot, exp.BlockRoot, ht.phead.Slot, ht.phead.BlockRoot) + } +} diff --git a/beacon/light/sync/types.go b/beacon/light/sync/types.go new file mode 100644 index 0000000..97a3fb2 --- /dev/null +++ b/beacon/light/sync/types.go @@ -0,0 +1,47 @@ +// 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 sync + +import ( + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/common" +) + +var ( + EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo + EvNewOptimisticUpdate = &request.EventType{Name: "newOptimisticUpdate"} // data: types.OptimisticUpdate + EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate +) + +type ( + ReqUpdates struct { + FirstPeriod, Count uint64 + } + RespUpdates struct { + Updates []*types.LightClientUpdate + Committees []*types.SerializedSyncCommittee + } + ReqHeader common.Hash + RespHeader struct { + Header types.Header + Canonical, Finalized bool + } + ReqCheckpointData common.Hash + ReqBeaconBlock common.Hash + ReqFinality struct{} +) diff --git a/beacon/light/sync/update_sync.go b/beacon/light/sync/update_sync.go new file mode 100644 index 0000000..9549ee5 --- /dev/null +++ b/beacon/light/sync/update_sync.go @@ -0,0 +1,398 @@ +// 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 sync + +import ( + "sort" + + "github.com/ethereum/go-ethereum/beacon/light" + "github.com/ethereum/go-ethereum/beacon/light/request" + "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/log" +) + +const maxUpdateRequest = 8 // maximum number of updates requested in a single request + +type committeeChain interface { + CheckpointInit(bootstrap types.BootstrapData) error + InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error + NextSyncPeriod() (uint64, bool) +} + +// CheckpointInit implements request.Module; it fetches the light client bootstrap +// data belonging to the given checkpoint hash and initializes the committee chain +// if successful. +type CheckpointInit struct { + chain committeeChain + checkpointHash common.Hash + locked request.ServerAndID + initialized bool + // per-server state is used to track the state of requesting checkpoint header + // info. Part of this info (canonical and finalized state) is not validated + // and therefore it is requested from each server separately after it has + // reported a missing checkpoint (which is also not validated info). + serverState map[request.Server]serverState + // the following fields are used to determine whether the checkpoint is on + // epoch boundary. This information is validated and therefore stored globally. + parentHash common.Hash + hasEpochInfo, epochBoundary bool + cpSlot, parentSlot uint64 +} + +const ( + ssDefault = iota // no action yet or checkpoint requested + ssNeedHeader // checkpoint req failed, need cp header + ssHeaderRequested // cp header requested + ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary + ssParentRequested // cp parent header requested + ssPrintStatus // has all necessary info, print log message if init still not successful + ssDone // log message printed, no more action required +) + +type serverState struct { + state int + hasHeader, canonical, finalized bool // stored per server because not validated +} + +// NewCheckpointInit creates a new CheckpointInit. +func NewCheckpointInit(chain committeeChain, checkpointHash common.Hash) *CheckpointInit { + return &CheckpointInit{ + chain: chain, + checkpointHash: checkpointHash, + serverState: make(map[request.Server]serverState), + } +} + +// Process implements request.Module. +func (s *CheckpointInit) Process(requester request.Requester, events []request.Event) { + if s.initialized { + return + } + + for _, event := range events { + switch event.Type { + case request.EvResponse, request.EvFail, request.EvTimeout: + sid, req, resp := event.RequestInfo() + if s.locked == sid { + s.locked = request.ServerAndID{} + } + if event.Type == request.EvTimeout { + continue + } + switch s.serverState[sid.Server].state { + case ssDefault: + if resp != nil { + if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) { + s.chain.CheckpointInit(*checkpoint) + s.initialized = true + return + } + requester.Fail(event.Server, "invalid checkpoint data") + } + s.serverState[sid.Server] = serverState{state: ssNeedHeader} + case ssHeaderRequested: + if resp == nil { + s.serverState[sid.Server] = serverState{state: ssPrintStatus} + continue + } + newState := serverState{ + hasHeader: true, + canonical: resp.(RespHeader).Canonical, + finalized: resp.(RespHeader).Finalized, + } + s.cpSlot, s.parentHash = resp.(RespHeader).Header.Slot, resp.(RespHeader).Header.ParentRoot + if s.cpSlot%params.EpochLength == 0 { + s.hasEpochInfo, s.epochBoundary = true, true + } + if s.hasEpochInfo { + newState.state = ssPrintStatus + } else { + newState.state = ssNeedParent + } + s.serverState[sid.Server] = newState + case ssParentRequested: + s.parentSlot = resp.(RespHeader).Header.Slot + s.hasEpochInfo, s.epochBoundary = true, s.cpSlot/params.EpochLength > s.parentSlot/params.EpochLength + newState := s.serverState[sid.Server] + newState.state = ssPrintStatus + s.serverState[sid.Server] = newState + } + + case request.EvUnregistered: + delete(s.serverState, event.Server) + } + } + + // start a request if possible + for _, server := range requester.CanSendTo() { + switch s.serverState[server].state { + case ssDefault: + if s.locked == (request.ServerAndID{}) { + id := requester.Send(server, ReqCheckpointData(s.checkpointHash)) + s.locked = request.ServerAndID{Server: server, ID: id} + } + case ssNeedHeader: + requester.Send(server, ReqHeader(s.checkpointHash)) + newState := s.serverState[server] + newState.state = ssHeaderRequested + s.serverState[server] = newState + case ssNeedParent: + requester.Send(server, ReqHeader(s.parentHash)) + newState := s.serverState[server] + newState.state = ssParentRequested + s.serverState[server] = newState + } + } + + // print log message if necessary + for server, state := range s.serverState { + if state.state != ssPrintStatus { + continue + } + switch { + case !state.hasHeader: + log.Error("blsync: checkpoint block is not available, reported as unknown", "server", server.Name()) + case !state.canonical: + log.Error("blsync: checkpoint block is not available, reported as non-canonical", "server", server.Name()) + case !s.hasEpochInfo: + // should be available if hasHeader is true and state is ssPrintStatus + panic("checkpoint epoch info not available when printing retrieval status") + case !s.epochBoundary: + log.Error("blsync: checkpoint block is not first of epoch", "slot", s.cpSlot, "parent", s.parentSlot, "server", server.Name()) + case !state.finalized: + log.Error("blsync: checkpoint block is reported as non-finalized", "server", server.Name()) + default: + log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name()) + } + s.serverState[server] = serverState{state: ssDone} + } +} + +// ForwardUpdateSync implements request.Module; it fetches updates between the +// committee chain head and each server's announced head. Updates are fetched +// in batches and multiple batches can also be requested in parallel. +// Out of order responses are also handled; if a batch of updates cannot be added +// to the chain immediately because of a gap then the future updates are +// remembered until they can be processed. +type ForwardUpdateSync struct { + chain committeeChain + rangeLock rangeLock + lockedIDs map[request.ServerAndID]struct{} + processQueue []updateResponse + nextSyncPeriod map[request.Server]uint64 +} + +// NewForwardUpdateSync creates a new ForwardUpdateSync. +func NewForwardUpdateSync(chain committeeChain) *ForwardUpdateSync { + return &ForwardUpdateSync{ + chain: chain, + rangeLock: make(rangeLock), + lockedIDs: make(map[request.ServerAndID]struct{}), + nextSyncPeriod: make(map[request.Server]uint64), + } +} + +// rangeLock allows locking sections of an integer space, preventing the syncing +// mechanism from making requests again for sections where a not timed out request +// is already pending or where already fetched and unprocessed data is available. +type rangeLock map[uint64]int + +// lock locks or unlocks the given section, depending on the sign of the add parameter. +func (r rangeLock) lock(first, count uint64, add int) { + for i := first; i < first+count; i++ { + if v := r[i] + add; v > 0 { + r[i] = v + } else { + delete(r, i) + } + } +} + +// firstUnlocked returns the first unlocked section starting at or after start +// and not longer than maxCount. +func (r rangeLock) firstUnlocked(start, maxCount uint64) (first, count uint64) { + first = start + for { + if _, ok := r[first]; !ok { + break + } + first++ + } + for { + count++ + if count == maxCount { + break + } + if _, ok := r[first+count]; ok { + break + } + } + return +} + +// lockRange locks the range belonging to the given update request, unless the +// same request has already been locked +func (s *ForwardUpdateSync) lockRange(sid request.ServerAndID, req ReqUpdates) { + if _, ok := s.lockedIDs[sid]; ok { + return + } + s.lockedIDs[sid] = struct{}{} + s.rangeLock.lock(req.FirstPeriod, req.Count, 1) +} + +// unlockRange unlocks the range belonging to the given update request, unless +// same request has already been unlocked +func (s *ForwardUpdateSync) unlockRange(sid request.ServerAndID, req ReqUpdates) { + if _, ok := s.lockedIDs[sid]; !ok { + return + } + delete(s.lockedIDs, sid) + s.rangeLock.lock(req.FirstPeriod, req.Count, -1) +} + +// verifyRange returns true if the number of updates and the individual update +// periods in the response match the requested section. +func (s *ForwardUpdateSync) verifyRange(request ReqUpdates, response RespUpdates) bool { + if uint64(len(response.Updates)) != request.Count || uint64(len(response.Committees)) != request.Count { + return false + } + for i, update := range response.Updates { + if update.AttestedHeader.Header.SyncPeriod() != request.FirstPeriod+uint64(i) { + return false + } + } + return true +} + +// updateResponse is a response that has passed initial verification and has been +// queued for processing. Note that an update response cannot be processed until +// the previous updates have also been added to the chain. +type updateResponse struct { + sid request.ServerAndID + request ReqUpdates + response RespUpdates +} + +// updateResponseList implements sort.Sort and sorts update request/response events by FirstPeriod. +type updateResponseList []updateResponse + +func (u updateResponseList) Len() int { return len(u) } +func (u updateResponseList) Swap(i, j int) { u[i], u[j] = u[j], u[i] } +func (u updateResponseList) Less(i, j int) bool { + return u[i].request.FirstPeriod < u[j].request.FirstPeriod +} + +// Process implements request.Module. +func (s *ForwardUpdateSync) Process(requester request.Requester, events []request.Event) { + for _, event := range events { + switch event.Type { + case request.EvResponse, request.EvFail, request.EvTimeout: + sid, rq, rs := event.RequestInfo() + req := rq.(ReqUpdates) + var queued bool + if event.Type == request.EvResponse { + resp := rs.(RespUpdates) + if s.verifyRange(req, resp) { + // there is a response with a valid format; put it in the process queue + s.processQueue = append(s.processQueue, updateResponse{sid: sid, request: req, response: resp}) + s.lockRange(sid, req) + queued = true + } else { + requester.Fail(event.Server, "invalid update range") + } + } + if !queued { + s.unlockRange(sid, req) + } + case EvNewOptimisticUpdate: + update := event.Data.(types.OptimisticUpdate) + s.nextSyncPeriod[event.Server] = types.SyncPeriod(update.SignatureSlot + 256) + case request.EvUnregistered: + delete(s.nextSyncPeriod, event.Server) + } + } + + // try processing ordered list of available responses + sort.Sort(updateResponseList(s.processQueue)) + for s.processQueue != nil { + u := s.processQueue[0] + if !s.processResponse(requester, u) { + break + } + s.unlockRange(u.sid, u.request) + s.processQueue = s.processQueue[1:] + if len(s.processQueue) == 0 { + s.processQueue = nil + } + } + + // start new requests if possible + startPeriod, chainInit := s.chain.NextSyncPeriod() + if !chainInit { + return + } + for { + firstPeriod, maxCount := s.rangeLock.firstUnlocked(startPeriod, maxUpdateRequest) + var ( + sendTo request.Server + bestCount uint64 + ) + for _, server := range requester.CanSendTo() { + nextPeriod := s.nextSyncPeriod[server] + if nextPeriod <= firstPeriod { + continue + } + count := maxCount + if nextPeriod < firstPeriod+maxCount { + count = nextPeriod - firstPeriod + } + if count > bestCount { + sendTo, bestCount = server, count + } + } + if sendTo == nil { + return + } + req := ReqUpdates{FirstPeriod: firstPeriod, Count: bestCount} + id := requester.Send(sendTo, req) + s.lockRange(request.ServerAndID{Server: sendTo, ID: id}, req) + } +} + +// processResponse adds the fetched updates and committees to the committee chain. +// Returns true in case of full or partial success. +func (s *ForwardUpdateSync) processResponse(requester request.Requester, u updateResponse) (success bool) { + for i, update := range u.response.Updates { + if err := s.chain.InsertUpdate(update, u.response.Committees[i]); err != nil { + if err == light.ErrInvalidPeriod { + // there is a gap in the update periods; stop processing without + // failing and try again next time + return + } + if err == light.ErrInvalidUpdate || err == light.ErrWrongCommitteeRoot || err == light.ErrCannotReorg { + requester.Fail(u.sid.Server, "invalid update received") + } else { + log.Error("Unexpected InsertUpdate error", "error", err) + } + return + } + success = true + } + return +} diff --git a/beacon/light/sync/update_sync_test.go b/beacon/light/sync/update_sync_test.go new file mode 100644 index 0000000..8329bf2 --- /dev/null +++ b/beacon/light/sync/update_sync_test.go @@ -0,0 +1,219 @@ +// Copyright 2024 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 sync + +import ( + "testing" + + "github.com/ethereum/go-ethereum/beacon/light/request" + "github.com/ethereum/go-ethereum/beacon/types" +) + +func TestCheckpointInit(t *testing.T) { + chain := &TestCommitteeChain{} + checkpoint := &types.BootstrapData{Header: types.Header{Slot: 0x2000*4 + 0x1000}} // period 4 + checkpointHash := checkpoint.Header.Hash() + chkInit := NewCheckpointInit(chain, checkpointHash) + ts := NewTestScheduler(t, chkInit) + // add 2 servers + ts.AddServer(testServer1, 1) + ts.AddServer(testServer2, 1) + + // expect bootstrap request to server 1 + ts.Run(1, testServer1, ReqCheckpointData(checkpointHash)) + + // server 1 times out; expect request to server 2 + ts.RequestEvent(request.EvTimeout, ts.Request(1, 1), nil) + ts.Run(2, testServer2, ReqCheckpointData(checkpointHash)) + + // invalid response from server 2; expect init state to still be false + ts.RequestEvent(request.EvResponse, ts.Request(2, 1), &types.BootstrapData{Header: types.Header{Slot: 123456}}) + ts.ExpFail(testServer2) + ts.Run(3) + chain.ExpInit(t, false) + + // server 1 fails (hard timeout) + ts.RequestEvent(request.EvFail, ts.Request(1, 1), nil) + ts.Run(4) + chain.ExpInit(t, false) + + // server 3 is registered; expect bootstrap request to server 3 + ts.AddServer(testServer3, 1) + ts.Run(5, testServer3, ReqCheckpointData(checkpointHash)) + + // valid response from server 3; expect chain to be initialized + ts.RequestEvent(request.EvResponse, ts.Request(5, 1), checkpoint) + ts.Run(6) + chain.ExpInit(t, true) +} + +func TestUpdateSyncParallel(t *testing.T) { + chain := &TestCommitteeChain{} + chain.SetNextSyncPeriod(0) + updateSync := NewForwardUpdateSync(chain) + ts := NewTestScheduler(t, updateSync) + // add 2 servers, head at period 100; allow 3-3 parallel requests for each + ts.AddServer(testServer1, 3) + ts.ServerEvent(EvNewOptimisticUpdate, testServer1, types.OptimisticUpdate{SignatureSlot: 0x2000*100 + 0x1000}) + ts.AddServer(testServer2, 3) + ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*100 + 0x1000}) + + // expect 6 requests to be sent + ts.Run(1, + testServer1, ReqUpdates{FirstPeriod: 0, Count: 8}, + testServer1, ReqUpdates{FirstPeriod: 8, Count: 8}, + testServer1, ReqUpdates{FirstPeriod: 16, Count: 8}, + testServer2, ReqUpdates{FirstPeriod: 24, Count: 8}, + testServer2, ReqUpdates{FirstPeriod: 32, Count: 8}, + testServer2, ReqUpdates{FirstPeriod: 40, Count: 8}) + + // valid response to request 1; expect 8 periods synced and a new request started + ts.RequestEvent(request.EvResponse, ts.Request(1, 1), testRespUpdate(ts.Request(1, 1))) + ts.AddAllowance(testServer1, 1) + ts.Run(2, testServer1, ReqUpdates{FirstPeriod: 48, Count: 8}) + chain.ExpNextSyncPeriod(t, 8) + + // valid response to requests 4 and 5 + ts.RequestEvent(request.EvResponse, ts.Request(1, 4), testRespUpdate(ts.Request(1, 4))) + ts.RequestEvent(request.EvResponse, ts.Request(1, 5), testRespUpdate(ts.Request(1, 5))) + ts.AddAllowance(testServer2, 2) + // expect 2 more requests but no sync progress (responses 4 and 5 cannot be added before 2 and 3) + ts.Run(3, + testServer2, ReqUpdates{FirstPeriod: 56, Count: 8}, + testServer2, ReqUpdates{FirstPeriod: 64, Count: 8}) + chain.ExpNextSyncPeriod(t, 8) + + // soft timeout for requests 2 and 3 (server 1 is overloaded) + ts.RequestEvent(request.EvTimeout, ts.Request(1, 2), nil) + ts.RequestEvent(request.EvTimeout, ts.Request(1, 3), nil) + // no allowance, no more requests + ts.Run(4) + + // valid response to requests 6 and 8 and 9 + ts.RequestEvent(request.EvResponse, ts.Request(1, 6), testRespUpdate(ts.Request(1, 6))) + ts.RequestEvent(request.EvResponse, ts.Request(3, 1), testRespUpdate(ts.Request(3, 1))) + ts.RequestEvent(request.EvResponse, ts.Request(3, 2), testRespUpdate(ts.Request(3, 2))) + ts.AddAllowance(testServer2, 3) + // server 2 can now resend requests 2 and 3 (timed out by server 1) and also send a new one + ts.Run(5, + testServer2, ReqUpdates{FirstPeriod: 8, Count: 8}, + testServer2, ReqUpdates{FirstPeriod: 16, Count: 8}, + testServer2, ReqUpdates{FirstPeriod: 72, Count: 8}) + + // server 1 finally answers timed out request 2 + ts.RequestEvent(request.EvResponse, ts.Request(1, 2), testRespUpdate(ts.Request(1, 2))) + ts.AddAllowance(testServer1, 1) + // expect sync progress and one new request + ts.Run(6, testServer1, ReqUpdates{FirstPeriod: 80, Count: 8}) + chain.ExpNextSyncPeriod(t, 16) + + // server 2 answers requests 11 and 12 (resends of requests 2 and 3) + ts.RequestEvent(request.EvResponse, ts.Request(5, 1), testRespUpdate(ts.Request(5, 1))) + ts.RequestEvent(request.EvResponse, ts.Request(5, 2), testRespUpdate(ts.Request(5, 2))) + ts.AddAllowance(testServer2, 2) + ts.Run(7, + testServer2, ReqUpdates{FirstPeriod: 88, Count: 8}, + testServer2, ReqUpdates{FirstPeriod: 96, Count: 4}) + // finally the gap is filled, update can process responses up to req6 + chain.ExpNextSyncPeriod(t, 48) + + // all remaining requests are answered + ts.RequestEvent(request.EvResponse, ts.Request(1, 3), testRespUpdate(ts.Request(1, 3))) + ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testRespUpdate(ts.Request(2, 1))) + ts.RequestEvent(request.EvResponse, ts.Request(5, 3), testRespUpdate(ts.Request(5, 3))) + ts.RequestEvent(request.EvResponse, ts.Request(6, 1), testRespUpdate(ts.Request(6, 1))) + ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testRespUpdate(ts.Request(7, 1))) + ts.RequestEvent(request.EvResponse, ts.Request(7, 2), testRespUpdate(ts.Request(7, 2))) + ts.Run(8) + // expect chain to be fully synced + chain.ExpNextSyncPeriod(t, 100) +} + +func TestUpdateSyncDifferentHeads(t *testing.T) { + chain := &TestCommitteeChain{} + chain.SetNextSyncPeriod(10) + updateSync := NewForwardUpdateSync(chain) + ts := NewTestScheduler(t, updateSync) + // add 3 servers with different announced head periods + ts.AddServer(testServer1, 1) + ts.ServerEvent(EvNewOptimisticUpdate, testServer1, types.OptimisticUpdate{SignatureSlot: 0x2000*15 + 0x1000}) + ts.AddServer(testServer2, 1) + ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*16 + 0x1000}) + ts.AddServer(testServer3, 1) + ts.ServerEvent(EvNewOptimisticUpdate, testServer3, types.OptimisticUpdate{SignatureSlot: 0x2000*17 + 0x1000}) + + // expect request to the best announced head + ts.Run(1, testServer3, ReqUpdates{FirstPeriod: 10, Count: 7}) + + // request times out, expect request to the next best head + ts.RequestEvent(request.EvTimeout, ts.Request(1, 1), nil) + ts.Run(2, testServer2, ReqUpdates{FirstPeriod: 10, Count: 6}) + + // request times out, expect request to the last available server + ts.RequestEvent(request.EvTimeout, ts.Request(2, 1), nil) + ts.Run(3, testServer1, ReqUpdates{FirstPeriod: 10, Count: 5}) + + // valid response to request 3, expect chain synced to period 15 + ts.RequestEvent(request.EvResponse, ts.Request(3, 1), testRespUpdate(ts.Request(3, 1))) + ts.AddAllowance(testServer1, 1) + ts.Run(4) + chain.ExpNextSyncPeriod(t, 15) + + // invalid response to request 1, server can only deliver updates up to period 15 despite announced head + truncated := ts.Request(1, 1) + truncated.request = ReqUpdates{FirstPeriod: 10, Count: 5} + ts.RequestEvent(request.EvResponse, ts.Request(1, 1), testRespUpdate(truncated)) + ts.ExpFail(testServer3) + ts.Run(5) + // expect no progress of chain head + chain.ExpNextSyncPeriod(t, 15) + + // valid response to request 2, expect chain synced to period 16 + ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testRespUpdate(ts.Request(2, 1))) + ts.AddAllowance(testServer2, 1) + ts.Run(6) + chain.ExpNextSyncPeriod(t, 16) + + // a new server is registered with announced head period 17 + ts.AddServer(testServer4, 1) + ts.ServerEvent(EvNewOptimisticUpdate, testServer4, types.OptimisticUpdate{SignatureSlot: 0x2000*17 + 0x1000}) + // expect request to sync one more period + ts.Run(7, testServer4, ReqUpdates{FirstPeriod: 16, Count: 1}) + + // valid response, expect chain synced to period 17 + ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testRespUpdate(ts.Request(7, 1))) + ts.AddAllowance(testServer4, 1) + ts.Run(8) + chain.ExpNextSyncPeriod(t, 17) +} + +func testRespUpdate(request requestWithID) request.Response { + var resp RespUpdates + if request.request == nil { + return resp + } + req := request.request.(ReqUpdates) + resp.Updates = make([]*types.LightClientUpdate, int(req.Count)) + resp.Committees = make([]*types.SerializedSyncCommittee, int(req.Count)) + period := req.FirstPeriod + for i := range resp.Updates { + resp.Updates[i] = &types.LightClientUpdate{AttestedHeader: types.SignedHeader{Header: types.Header{Slot: 0x2000*period + 0x1000}}} + resp.Committees[i] = new(types.SerializedSyncCommittee) + period++ + } + return resp +} diff --git a/beacon/light/test_helpers.go b/beacon/light/test_helpers.go new file mode 100644 index 0000000..f537d96 --- /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/merkle/merkle.go b/beacon/merkle/merkle.go new file mode 100644 index 0000000..30896f9 --- /dev/null +++ b/beacon/merkle/merkle.go @@ -0,0 +1,67 @@ +// 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 merkle implements proof verifications in binary merkle trees. +package merkle + +import ( + "crypto/sha256" + "errors" + "reflect" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// Value represents either a 32 byte leaf value or hash node in a binary merkle tree/partial proof. +type Value [32]byte + +// Values represent a series of merkle tree leaves/nodes. +type Values []Value + +var valueT = reflect.TypeOf(Value{}) + +// UnmarshalJSON parses a merkle value in hex syntax. +func (m *Value) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(valueT, input, m[:]) +} + +// VerifyProof verifies a Merkle proof branch for a single value in a +// binary Merkle tree (index is a generalized tree index). +func VerifyProof(root common.Hash, index uint64, branch Values, value Value) error { + hasher := sha256.New() + for _, sibling := range branch { + hasher.Reset() + if index&1 == 0 { + hasher.Write(value[:]) + hasher.Write(sibling[:]) + } else { + hasher.Write(sibling[:]) + hasher.Write(value[:]) + } + hasher.Sum(value[:0]) + if index >>= 1; index == 0 { + return errors.New("branch has extra items") + } + } + if index != 1 { + return errors.New("branch is missing items") + } + if common.Hash(value) != root { + return errors.New("root mismatch") + } + return nil +} diff --git a/beacon/params/params.go b/beacon/params/params.go new file mode 100644 index 0000000..e4e0d00 --- /dev/null +++ b/beacon/params/params.go @@ -0,0 +1,46 @@ +// 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 params + +const ( + EpochLength = 32 + SyncPeriodLength = 8192 + + BLSSignatureSize = 96 + BLSPubkeySize = 48 + + SyncCommitteeSize = 512 + SyncCommitteeBitmaskSize = SyncCommitteeSize / 8 + SyncCommitteeSupermajority = (SyncCommitteeSize*2 + 2) / 3 +) + +const ( + StateIndexGenesisTime = 32 + StateIndexGenesisValidators = 33 + StateIndexForkVersion = 141 + StateIndexLatestHeader = 36 + StateIndexBlockRoots = 37 + StateIndexStateRoots = 38 + StateIndexHistoricRoots = 39 + StateIndexFinalBlock = 105 + StateIndexSyncCommittee = 54 + StateIndexNextSyncCommittee = 55 + StateIndexExecPayload = 56 + StateIndexExecHead = 908 + + BodyIndexExecPayload = 25 +) diff --git a/beacon/types/beacon_block.go b/beacon/types/beacon_block.go new file mode 100644 index 0000000..3701521 --- /dev/null +++ b/beacon/types/beacon_block.go @@ -0,0 +1,110 @@ +// Copyright 2024 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 ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/protolambda/zrnt/eth2/beacon/capella" + zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/deneb" + "github.com/protolambda/zrnt/eth2/configs" + "github.com/protolambda/ztyp/tree" +) + +type blockObject interface { + HashTreeRoot(spec *zrntcommon.Spec, hFn tree.HashFn) zrntcommon.Root + Header(spec *zrntcommon.Spec) *zrntcommon.BeaconBlockHeader +} + +// BeaconBlock represents a full block in the beacon chain. +type BeaconBlock struct { + blockObj blockObject +} + +// BlockFromJSON decodes a beacon block from JSON. +func BlockFromJSON(forkName string, data []byte) (*BeaconBlock, error) { + var obj blockObject + switch forkName { + case "deneb": + obj = new(deneb.BeaconBlock) + case "capella": + obj = new(capella.BeaconBlock) + default: + return nil, fmt.Errorf("unsupported fork: " + forkName) + } + if err := json.Unmarshal(data, obj); err != nil { + return nil, err + } + return &BeaconBlock{obj}, nil +} + +// NewBeaconBlock wraps a ZRNT block. +func NewBeaconBlock(obj blockObject) *BeaconBlock { + switch obj := obj.(type) { + case *capella.BeaconBlock: + return &BeaconBlock{obj} + case *deneb.BeaconBlock: + return &BeaconBlock{obj} + default: + panic(fmt.Errorf("unsupported block type %T", obj)) + } +} + +// Slot returns the slot number of the block. +func (b *BeaconBlock) Slot() uint64 { + switch obj := b.blockObj.(type) { + case *capella.BeaconBlock: + return uint64(obj.Slot) + case *deneb.BeaconBlock: + return uint64(obj.Slot) + default: + panic(fmt.Errorf("unsupported block type %T", b.blockObj)) + } +} + +// ExecutionPayload parses and returns the execution payload of the block. +func (b *BeaconBlock) ExecutionPayload() (*types.Block, error) { + switch obj := b.blockObj.(type) { + case *capella.BeaconBlock: + return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot) + case *deneb.BeaconBlock: + return convertPayload(&obj.Body.ExecutionPayload, &obj.ParentRoot) + default: + panic(fmt.Errorf("unsupported block type %T", b.blockObj)) + } +} + +// Header returns the block's header data. +func (b *BeaconBlock) Header() Header { + switch obj := b.blockObj.(type) { + case *capella.BeaconBlock: + return headerFromZRNT(obj.Header(configs.Mainnet)) + case *deneb.BeaconBlock: + return headerFromZRNT(obj.Header(configs.Mainnet)) + default: + panic(fmt.Errorf("unsupported block type %T", b.blockObj)) + } +} + +// Root computes the SSZ root hash of the block. +func (b *BeaconBlock) Root() common.Hash { + return common.Hash(b.blockObj.HashTreeRoot(configs.Mainnet, tree.GetHashFn())) +} diff --git a/beacon/types/beacon_block_test.go b/beacon/types/beacon_block_test.go new file mode 100644 index 0000000..d5920e8 --- /dev/null +++ b/beacon/types/beacon_block_test.go @@ -0,0 +1,77 @@ +// Copyright 2024 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 ( + "os" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestBlockFromJSON(t *testing.T) { + type blocktest struct { + file string + version string + wantSlot uint64 + wantBlockNumber uint64 + wantBlockHash common.Hash + } + tests := []blocktest{ + { + file: "block_deneb.json", + version: "deneb", + wantSlot: 8631513, + wantBlockNumber: 19431837, + wantBlockHash: common.HexToHash("0x4cf7d9108fc01b50023ab7cab9b372a96068fddcadec551630393b65acb1f34c"), + }, + { + file: "block_capella.json", + version: "capella", + wantSlot: 7378495, + wantBlockNumber: 18189758, + wantBlockHash: common.HexToHash("0x802acf5c350f4252e31d83c431fcb259470250fa0edf49e8391cfee014239820"), + }, + } + + for _, test := range tests { + t.Run(test.file, func(t *testing.T) { + data, err := os.ReadFile(filepath.Join("testdata", test.file)) + if err != nil { + t.Fatal(err) + } + beaconBlock, err := BlockFromJSON(test.version, data) + if err != nil { + t.Fatal(err) + } + if beaconBlock.Slot() != test.wantSlot { + t.Errorf("wrong slot number %d", beaconBlock.Slot()) + } + execBlock, err := beaconBlock.ExecutionPayload() + if err != nil { + t.Fatalf("payload extraction failed: %v", err) + } + if execBlock.NumberU64() != test.wantBlockNumber { + t.Errorf("wrong block number: %v", execBlock.NumberU64()) + } + if execBlock.Hash() != test.wantBlockHash { + t.Errorf("wrong block hash: %v", execBlock.Hash()) + } + }) + } +} diff --git a/beacon/types/committee.go b/beacon/types/committee.go new file mode 100644 index 0000000..5f89c27 --- /dev/null +++ b/beacon/types/committee.go @@ -0,0 +1,191 @@ +// 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 types + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "math/bits" + + "github.com/ethereum/go-ethereum/beacon/params" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + bls "github.com/protolambda/bls12-381-util" +) + +// SerializedSyncCommitteeSize is the size of the sync committee plus the +// aggregate public key. +const SerializedSyncCommitteeSize = (params.SyncCommitteeSize + 1) * params.BLSPubkeySize + +// SerializedSyncCommittee is the serialized version of a sync committee +// plus the aggregate public key. +type SerializedSyncCommittee [SerializedSyncCommitteeSize]byte + +// jsonSyncCommittee is the JSON representation of a sync committee. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate +type jsonSyncCommittee struct { + Pubkeys []hexutil.Bytes `json:"pubkeys"` + Aggregate hexutil.Bytes `json:"aggregate_pubkey"` +} + +// MarshalJSON implements json.Marshaler. +func (s *SerializedSyncCommittee) MarshalJSON() ([]byte, error) { + sc := jsonSyncCommittee{Pubkeys: make([]hexutil.Bytes, params.SyncCommitteeSize)} + for i := range sc.Pubkeys { + sc.Pubkeys[i] = make(hexutil.Bytes, params.BLSPubkeySize) + copy(sc.Pubkeys[i][:], s[i*params.BLSPubkeySize:(i+1)*params.BLSPubkeySize]) + } + sc.Aggregate = make(hexutil.Bytes, params.BLSPubkeySize) + copy(sc.Aggregate[:], s[params.SyncCommitteeSize*params.BLSPubkeySize:]) + return json.Marshal(&sc) +} + +// UnmarshalJSON implements json.Marshaler. +func (s *SerializedSyncCommittee) UnmarshalJSON(input []byte) error { + var sc jsonSyncCommittee + if err := json.Unmarshal(input, &sc); err != nil { + return err + } + if len(sc.Pubkeys) != params.SyncCommitteeSize { + return fmt.Errorf("invalid number of pubkeys %d", len(sc.Pubkeys)) + } + for i, key := range sc.Pubkeys { + if len(key) != params.BLSPubkeySize { + return fmt.Errorf("pubkey %d has invalid size %d", i, len(key)) + } + copy(s[i*params.BLSPubkeySize:], key[:]) + } + if len(sc.Aggregate) != params.BLSPubkeySize { + return fmt.Errorf("invalid aggregate pubkey size %d", len(sc.Aggregate)) + } + copy(s[params.SyncCommitteeSize*params.BLSPubkeySize:], sc.Aggregate[:]) + return nil +} + +// Root calculates the root hash of the binary tree representation of a sync +// committee provided in serialized format. +// +// TODO(zsfelfoldi): Get rid of this when SSZ encoding lands. +func (s *SerializedSyncCommittee) Root() common.Hash { + var ( + hasher = sha256.New() + padding [64 - params.BLSPubkeySize]byte + data [params.SyncCommitteeSize]common.Hash + l = params.SyncCommitteeSize + ) + for i := range data { + hasher.Reset() + hasher.Write(s[i*params.BLSPubkeySize : (i+1)*params.BLSPubkeySize]) + hasher.Write(padding[:]) + hasher.Sum(data[i][:0]) + } + for l > 1 { + for i := 0; i < l/2; i++ { + hasher.Reset() + hasher.Write(data[i*2][:]) + hasher.Write(data[i*2+1][:]) + hasher.Sum(data[i][:0]) + } + l /= 2 + } + hasher.Reset() + hasher.Write(s[SerializedSyncCommitteeSize-params.BLSPubkeySize : SerializedSyncCommitteeSize]) + hasher.Write(padding[:]) + hasher.Sum(data[1][:0]) + hasher.Reset() + hasher.Write(data[0][:]) + hasher.Write(data[1][:]) + hasher.Sum(data[0][:0]) + return data[0] +} + +// Deserialize splits open the pubkeys into proper BLS key types. +func (s *SerializedSyncCommittee) Deserialize() (*SyncCommittee, error) { + sc := new(SyncCommittee) + for i := 0; i <= params.SyncCommitteeSize; i++ { + key := new(bls.Pubkey) + + var bytes [params.BLSPubkeySize]byte + copy(bytes[:], s[i*params.BLSPubkeySize:(i+1)*params.BLSPubkeySize]) + + if err := key.Deserialize(&bytes); err != nil { + return nil, err + } + if i < params.SyncCommitteeSize { + sc.keys[i] = key + } else { + sc.aggregate = key + } + } + return sc, nil +} + +// SyncCommittee is a set of sync committee signer pubkeys and the aggregate key. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate +type SyncCommittee struct { + keys [params.SyncCommitteeSize]*bls.Pubkey + aggregate *bls.Pubkey +} + +// VerifySignature returns true if the given sync aggregate is a valid signature +// or the given hash. +func (sc *SyncCommittee) VerifySignature(signingRoot common.Hash, signature *SyncAggregate) bool { + var ( + sig bls.Signature + keys = make([]*bls.Pubkey, 0, params.SyncCommitteeSize) + ) + if err := sig.Deserialize(&signature.Signature); err != nil { + return false + } + for i, key := range sc.keys { + if signature.Signers[i/8]&(byte(1)<<(i%8)) != 0 { + keys = append(keys, key) + } + } + return bls.FastAggregateVerify(keys, signingRoot[:], &sig) +} + +//go:generate go run github.com/fjl/gencodec -type SyncAggregate -field-override syncAggregateMarshaling -out gen_syncaggregate_json.go + +// SyncAggregate represents an aggregated BLS signature with Signers referring +// to a subset of the corresponding sync committee. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#syncaggregate +type SyncAggregate struct { + Signers [params.SyncCommitteeBitmaskSize]byte `gencodec:"required" json:"sync_committee_bits"` + Signature [params.BLSSignatureSize]byte `gencodec:"required" json:"sync_committee_signature"` +} + +type syncAggregateMarshaling struct { + Signers hexutil.Bytes + Signature hexutil.Bytes +} + +// SignerCount returns the number of signers in the aggregate signature. +func (s *SyncAggregate) SignerCount() int { + var count int + for _, v := range s.Signers { + count += bits.OnesCount8(v) + } + return count +} diff --git a/beacon/types/config.go b/beacon/types/config.go new file mode 100644 index 0000000..7706e85 --- /dev/null +++ b/beacon/types/config.go @@ -0,0 +1,204 @@ +// 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 types + +import ( + "crypto/sha256" + "fmt" + "math" + "os" + "slices" + "sort" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/beacon/merkle" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "gopkg.in/yaml.v3" +) + +// syncCommitteeDomain specifies the signatures specific use to avoid clashes +// across signing different data structures. +const syncCommitteeDomain = 7 + +var knownForks = []string{"GENESIS", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB"} + +// Fork describes a single beacon chain fork and also stores the calculated +// signature domain used after this fork. +type Fork struct { + // Name of the fork in the chain config (config.yaml) file + Name string + + // Epoch when given fork version is activated + Epoch uint64 + + // Fork version, see https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types + Version []byte + + // index in list of known forks or MaxInt if unknown + knownIndex int + + // calculated by computeDomain, based on fork version and genesis validators root + domain merkle.Value +} + +// computeDomain returns the signature domain based on the given fork version +// and genesis validator set root. +func (f *Fork) computeDomain(genesisValidatorsRoot common.Hash) { + var ( + hasher = sha256.New() + forkVersion32 merkle.Value + forkDataRoot merkle.Value + ) + copy(forkVersion32[:], f.Version) + hasher.Write(forkVersion32[:]) + hasher.Write(genesisValidatorsRoot[:]) + hasher.Sum(forkDataRoot[:0]) + + f.domain[0] = syncCommitteeDomain + copy(f.domain[4:], forkDataRoot[:28]) +} + +// Forks is the list of all beacon chain forks in the chain configuration. +type Forks []*Fork + +// domain returns the signature domain for the given epoch (assumes that domains +// have already been calculated). +func (f Forks) domain(epoch uint64) (merkle.Value, error) { + for i := len(f) - 1; i >= 0; i-- { + if epoch >= f[i].Epoch { + return f[i].domain, nil + } + } + return merkle.Value{}, fmt.Errorf("unknown fork for epoch %d", epoch) +} + +// SigningRoot calculates the signing root of the given header. +func (f Forks) SigningRoot(header Header) (common.Hash, error) { + domain, err := f.domain(header.Epoch()) + if err != nil { + return common.Hash{}, err + } + var ( + signingRoot common.Hash + headerHash = header.Hash() + hasher = sha256.New() + ) + hasher.Write(headerHash[:]) + hasher.Write(domain[:]) + hasher.Sum(signingRoot[:0]) + + return signingRoot, nil +} + +func (f Forks) Len() int { return len(f) } +func (f Forks) Swap(i, j int) { f[i], f[j] = f[j], f[i] } +func (f Forks) Less(i, j int) bool { + if f[i].Epoch != f[j].Epoch { + return f[i].Epoch < f[j].Epoch + } + return f[i].knownIndex < f[j].knownIndex +} + +// ChainConfig contains the beacon chain configuration. +type ChainConfig struct { + GenesisTime uint64 // Unix timestamp of slot 0 + GenesisValidatorsRoot common.Hash // Root hash of the genesis validator set, used for signature domain calculation + Forks Forks +} + +// ForkAtEpoch returns the latest active fork at the given epoch. +func (c *ChainConfig) ForkAtEpoch(epoch uint64) Fork { + for i := len(c.Forks) - 1; i >= 0; i-- { + if c.Forks[i].Epoch <= epoch { + return *c.Forks[i] + } + } + return Fork{} +} + +// AddFork adds a new item to the list of forks. +func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainConfig { + knownIndex := slices.Index(knownForks, name) + if knownIndex == -1 { + knownIndex = math.MaxInt // assume that the unknown fork happens after the known ones + if epoch != math.MaxUint64 { + log.Warn("Unknown fork in config.yaml", "fork name", name, "known forks", knownForks) + } + } + fork := &Fork{ + Name: name, + Epoch: epoch, + Version: version, + knownIndex: knownIndex, + } + fork.computeDomain(c.GenesisValidatorsRoot) + c.Forks = append(c.Forks, fork) + sort.Sort(c.Forks) + return c +} + +// LoadForks parses the beacon chain configuration file (config.yaml) and extracts +// the list of forks. +func (c *ChainConfig) LoadForks(path string) error { + file, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read beacon chain config file: %v", err) + } + config := make(map[string]string) + if err := yaml.Unmarshal(file, &config); err != nil { + return fmt.Errorf("failed to parse beacon chain config file: %v", err) + } + var ( + versions = make(map[string][]byte) + epochs = make(map[string]uint64) + ) + epochs["GENESIS"] = 0 + + for key, value := range config { + if strings.HasSuffix(key, "_FORK_VERSION") { + name := key[:len(key)-len("_FORK_VERSION")] + if v, err := hexutil.Decode(value); err == nil { + versions[name] = v + } else { + return fmt.Errorf("failed to decode hex fork id %q in beacon chain config file: %v", value, err) + } + } + if strings.HasSuffix(key, "_FORK_EPOCH") { + name := key[:len(key)-len("_FORK_EPOCH")] + if v, err := strconv.ParseUint(value, 10, 64); err == nil { + epochs[name] = v + } else { + return fmt.Errorf("failed to parse epoch number %q in beacon chain config file: %v", value, err) + } + } + } + for name, epoch := range epochs { + if version, ok := versions[name]; ok { + delete(versions, name) + c.AddFork(name, epoch, version) + } else { + return fmt.Errorf("fork id missing for %q in beacon chain config file", name) + } + } + for name := range versions { + return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name) + } + return nil +} diff --git a/beacon/types/exec_header.go b/beacon/types/exec_header.go new file mode 100644 index 0000000..dce101b --- /dev/null +++ b/beacon/types/exec_header.go @@ -0,0 +1,80 @@ +// Copyright 2024 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 ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/beacon/merkle" + "github.com/ethereum/go-ethereum/common" + "github.com/protolambda/zrnt/eth2/beacon/capella" + zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/deneb" + "github.com/protolambda/ztyp/tree" +) + +type headerObject interface { + HashTreeRoot(hFn tree.HashFn) zrntcommon.Root +} + +type ExecutionHeader struct { + obj headerObject +} + +// ExecutionHeaderFromJSON decodes an execution header from JSON data provided by +// the beacon chain API. +func ExecutionHeaderFromJSON(forkName string, data []byte) (*ExecutionHeader, error) { + var obj headerObject + switch forkName { + case "capella": + obj = new(capella.ExecutionPayloadHeader) + case "deneb": + obj = new(deneb.ExecutionPayloadHeader) + default: + return nil, fmt.Errorf("unsupported fork: " + forkName) + } + if err := json.Unmarshal(data, obj); err != nil { + return nil, err + } + return &ExecutionHeader{obj: obj}, nil +} + +func NewExecutionHeader(obj headerObject) *ExecutionHeader { + switch obj.(type) { + case *capella.ExecutionPayloadHeader: + case *deneb.ExecutionPayloadHeader: + default: + panic(fmt.Errorf("unsupported ExecutionPayloadHeader type %T", obj)) + } + return &ExecutionHeader{obj: obj} +} + +func (eh *ExecutionHeader) PayloadRoot() merkle.Value { + return merkle.Value(eh.obj.HashTreeRoot(tree.GetHashFn())) +} + +func (eh *ExecutionHeader) BlockHash() common.Hash { + switch obj := eh.obj.(type) { + case *capella.ExecutionPayloadHeader: + return common.Hash(obj.BlockHash) + case *deneb.ExecutionPayloadHeader: + return common.Hash(obj.BlockHash) + default: + panic(fmt.Errorf("unsupported ExecutionPayloadHeader type %T", obj)) + } +} diff --git a/beacon/types/exec_payload.go b/beacon/types/exec_payload.go new file mode 100644 index 0000000..b159687 --- /dev/null +++ b/beacon/types/exec_payload.go @@ -0,0 +1,141 @@ +// Copyright 2024 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 ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" + "github.com/protolambda/zrnt/eth2/beacon/capella" + zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common" + "github.com/protolambda/zrnt/eth2/beacon/deneb" +) + +type payloadType interface { + *capella.ExecutionPayload | *deneb.ExecutionPayload +} + +// convertPayload converts a beacon chain execution payload to types.Block. +func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*types.Block, error) { + var ( + header types.Header + transactions []*types.Transaction + withdrawals []*types.Withdrawal + expectedHash [32]byte + err error + ) + switch p := any(payload).(type) { + case *capella.ExecutionPayload: + convertCapellaHeader(p, &header) + transactions, err = convertTransactions(p.Transactions, &header) + if err != nil { + return nil, err + } + withdrawals = convertWithdrawals(p.Withdrawals, &header) + expectedHash = p.BlockHash + case *deneb.ExecutionPayload: + convertDenebHeader(p, common.Hash(*parentRoot), &header) + transactions, err = convertTransactions(p.Transactions, &header) + if err != nil { + return nil, err + } + withdrawals = convertWithdrawals(p.Withdrawals, &header) + expectedHash = p.BlockHash + default: + panic("unsupported block type") + } + + block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals}) + if hash := block.Hash(); hash != expectedHash { + return nil, fmt.Errorf("sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash) + } + return block, nil +} + +func convertCapellaHeader(payload *capella.ExecutionPayload, h *types.Header) { + // note: h.TxHash is set in convertTransactions + h.ParentHash = common.Hash(payload.ParentHash) + h.UncleHash = types.EmptyUncleHash + h.Coinbase = common.Address(payload.FeeRecipient) + h.Root = common.Hash(payload.StateRoot) + h.ReceiptHash = common.Hash(payload.ReceiptsRoot) + h.Bloom = types.Bloom(payload.LogsBloom) + h.Difficulty = common.Big0 + h.Number = new(big.Int).SetUint64(uint64(payload.BlockNumber)) + h.GasLimit = uint64(payload.GasLimit) + h.GasUsed = uint64(payload.GasUsed) + h.Time = uint64(payload.Timestamp) + h.Extra = []byte(payload.ExtraData) + h.MixDigest = common.Hash(payload.PrevRandao) + h.Nonce = types.BlockNonce{} + h.BaseFee = (*uint256.Int)(&payload.BaseFeePerGas).ToBig() +} + +func convertDenebHeader(payload *deneb.ExecutionPayload, parentRoot common.Hash, h *types.Header) { + // note: h.TxHash is set in convertTransactions + h.ParentHash = common.Hash(payload.ParentHash) + h.UncleHash = types.EmptyUncleHash + h.Coinbase = common.Address(payload.FeeRecipient) + h.Root = common.Hash(payload.StateRoot) + h.ReceiptHash = common.Hash(payload.ReceiptsRoot) + h.Bloom = types.Bloom(payload.LogsBloom) + h.Difficulty = common.Big0 + h.Number = new(big.Int).SetUint64(uint64(payload.BlockNumber)) + h.GasLimit = uint64(payload.GasLimit) + h.GasUsed = uint64(payload.GasUsed) + h.Time = uint64(payload.Timestamp) + h.Extra = []byte(payload.ExtraData) + h.MixDigest = common.Hash(payload.PrevRandao) + h.Nonce = types.BlockNonce{} + h.BaseFee = (*uint256.Int)(&payload.BaseFeePerGas).ToBig() + // new in deneb + h.BlobGasUsed = (*uint64)(&payload.BlobGasUsed) + h.ExcessBlobGas = (*uint64)(&payload.ExcessBlobGas) + h.ParentBeaconRoot = &parentRoot +} + +func convertTransactions(list zrntcommon.PayloadTransactions, execHeader *types.Header) ([]*types.Transaction, error) { + txs := make([]*types.Transaction, len(list)) + for i, opaqueTx := range list { + var tx types.Transaction + if err := tx.UnmarshalBinary(opaqueTx); err != nil { + return nil, fmt.Errorf("failed to parse tx %d: %v", i, err) + } + txs[i] = &tx + } + execHeader.TxHash = types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)) + return txs, nil +} + +func convertWithdrawals(list zrntcommon.Withdrawals, execHeader *types.Header) []*types.Withdrawal { + withdrawals := make([]*types.Withdrawal, len(list)) + for i, w := range list { + withdrawals[i] = &types.Withdrawal{ + Index: uint64(w.Index), + Validator: uint64(w.ValidatorIndex), + Address: common.Address(w.Address), + Amount: uint64(w.Amount), + } + } + wroot := types.DeriveSha(types.Withdrawals(withdrawals), trie.NewStackTrie(nil)) + execHeader.WithdrawalsHash = &wroot + return withdrawals +} diff --git a/beacon/types/gen_header_json.go b/beacon/types/gen_header_json.go new file mode 100644 index 0000000..9b3ffea --- /dev/null +++ b/beacon/types/gen_header_json.go @@ -0,0 +1,66 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" +) + +var _ = (*headerMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (h Header) MarshalJSON() ([]byte, error) { + type Header struct { + Slot common.Decimal `gencodec:"required" json:"slot"` + ProposerIndex common.Decimal `gencodec:"required" json:"proposer_index"` + ParentRoot common.Hash `gencodec:"required" json:"parent_root"` + StateRoot common.Hash `gencodec:"required" json:"state_root"` + BodyRoot common.Hash `gencodec:"required" json:"body_root"` + } + var enc Header + enc.Slot = common.Decimal(h.Slot) + enc.ProposerIndex = common.Decimal(h.ProposerIndex) + enc.ParentRoot = h.ParentRoot + enc.StateRoot = h.StateRoot + enc.BodyRoot = h.BodyRoot + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (h *Header) UnmarshalJSON(input []byte) error { + type Header struct { + Slot *common.Decimal `gencodec:"required" json:"slot"` + ProposerIndex *common.Decimal `gencodec:"required" json:"proposer_index"` + ParentRoot *common.Hash `gencodec:"required" json:"parent_root"` + StateRoot *common.Hash `gencodec:"required" json:"state_root"` + BodyRoot *common.Hash `gencodec:"required" json:"body_root"` + } + var dec Header + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Slot == nil { + return errors.New("missing required field 'slot' for Header") + } + h.Slot = uint64(*dec.Slot) + if dec.ProposerIndex == nil { + return errors.New("missing required field 'proposer_index' for Header") + } + h.ProposerIndex = uint64(*dec.ProposerIndex) + if dec.ParentRoot == nil { + return errors.New("missing required field 'parent_root' for Header") + } + h.ParentRoot = *dec.ParentRoot + if dec.StateRoot == nil { + return errors.New("missing required field 'state_root' for Header") + } + h.StateRoot = *dec.StateRoot + if dec.BodyRoot == nil { + return errors.New("missing required field 'body_root' for Header") + } + h.BodyRoot = *dec.BodyRoot + return nil +} diff --git a/beacon/types/gen_syncaggregate_json.go b/beacon/types/gen_syncaggregate_json.go new file mode 100644 index 0000000..1547ec5 --- /dev/null +++ b/beacon/types/gen_syncaggregate_json.go @@ -0,0 +1,51 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*syncAggregateMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s SyncAggregate) MarshalJSON() ([]byte, error) { + type SyncAggregate struct { + Signers hexutil.Bytes `gencodec:"required" json:"sync_committee_bits"` + Signature hexutil.Bytes `gencodec:"required" json:"sync_committee_signature"` + } + var enc SyncAggregate + enc.Signers = s.Signers[:] + enc.Signature = s.Signature[:] + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *SyncAggregate) UnmarshalJSON(input []byte) error { + type SyncAggregate struct { + Signers *hexutil.Bytes `gencodec:"required" json:"sync_committee_bits"` + Signature *hexutil.Bytes `gencodec:"required" json:"sync_committee_signature"` + } + var dec SyncAggregate + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Signers == nil { + return errors.New("missing required field 'sync_committee_bits' for SyncAggregate") + } + if len(*dec.Signers) != len(s.Signers) { + return errors.New("field 'sync_committee_bits' has wrong length, need 64 items") + } + copy(s.Signers[:], *dec.Signers) + if dec.Signature == nil { + return errors.New("missing required field 'sync_committee_signature' for SyncAggregate") + } + if len(*dec.Signature) != len(s.Signature) { + return errors.New("field 'sync_committee_signature' has wrong length, need 96 items") + } + copy(s.Signature[:], *dec.Signature) + return nil +} diff --git a/beacon/types/header.go b/beacon/types/header.go new file mode 100644 index 0000000..c8388df --- /dev/null +++ b/beacon/types/header.go @@ -0,0 +1,132 @@ +// 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 types implements a few types of the beacon chain for light client usage. +package types + +import ( + "crypto/sha256" + "encoding/binary" + + "github.com/ethereum/go-ethereum/beacon/merkle" + "github.com/ethereum/go-ethereum/beacon/params" + "github.com/ethereum/go-ethereum/common" + zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common" +) + +//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go + +const ( + headerIndexSlot = 8 + headerIndexProposerIndex = 9 + headerIndexParentRoot = 10 + headerIndexStateRoot = 11 + headerIndexBodyRoot = 12 +) + +// Header defines a beacon header. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblockheader +type Header struct { + // Monotonically increasing slot number for the beacon block (may be gapped) + Slot uint64 `gencodec:"required" json:"slot"` + + // Index into the validator table who created the beacon block + ProposerIndex uint64 `gencodec:"required" json:"proposer_index"` + + // SSZ hash of the parent beacon header + ParentRoot common.Hash `gencodec:"required" json:"parent_root"` + + // SSZ hash of the beacon state (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beacon-state) + StateRoot common.Hash `gencodec:"required" json:"state_root"` + + // SSZ hash of the beacon block body (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beaconblockbody) + BodyRoot common.Hash `gencodec:"required" json:"body_root"` +} + +func headerFromZRNT(zh *zrntcommon.BeaconBlockHeader) Header { + return Header{ + Slot: uint64(zh.Slot), + ProposerIndex: uint64(zh.ProposerIndex), + ParentRoot: common.Hash(zh.ParentRoot), + StateRoot: common.Hash(zh.StateRoot), + BodyRoot: common.Hash(zh.BodyRoot), + } +} + +// headerMarshaling is a field type overrides for gencodec. +type headerMarshaling struct { + Slot common.Decimal + ProposerIndex common.Decimal +} + +// Hash calculates the block root of the header. +// +// TODO(zsfelfoldi): Remove this when an SSZ encoder lands. +func (h *Header) Hash() common.Hash { + var values [16]merkle.Value // values corresponding to indices 8 to 15 of the beacon header tree + binary.LittleEndian.PutUint64(values[headerIndexSlot][:8], h.Slot) + binary.LittleEndian.PutUint64(values[headerIndexProposerIndex][:8], h.ProposerIndex) + values[headerIndexParentRoot] = merkle.Value(h.ParentRoot) + values[headerIndexStateRoot] = merkle.Value(h.StateRoot) + values[headerIndexBodyRoot] = merkle.Value(h.BodyRoot) + hasher := sha256.New() + for i := 7; i > 0; i-- { + hasher.Reset() + hasher.Write(values[i*2][:]) + hasher.Write(values[i*2+1][:]) + hasher.Sum(values[i][:0]) + } + return common.Hash(values[1]) +} + +// Epoch returns the epoch the header belongs to. +func (h *Header) Epoch() uint64 { + return h.Slot / params.EpochLength +} + +// SyncPeriod returns the sync period the header belongs to. +func (h *Header) SyncPeriod() uint64 { + return SyncPeriod(h.Slot) +} + +// SyncPeriodStart returns the first slot of the given period. +func SyncPeriodStart(period uint64) uint64 { + return period * params.SyncPeriodLength +} + +// SyncPeriod returns the sync period that the given slot belongs to. +func SyncPeriod(slot uint64) uint64 { + return slot / params.SyncPeriodLength +} + +// SignedHeader represents a beacon header signed by a sync committee. +// +// This structure is created from either an optimistic update or an instant update: +// - https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate +// - https://github.com/zsfelfoldi/beacon-APIs/blob/instant_update/apis/beacon/light_client/instant_update.yaml +type SignedHeader struct { + // Beacon header being signed + Header Header + + // Sync committee BLS signature aggregate + Signature SyncAggregate + + // Slot in which the signature has been created (newer than Header.Slot, + // determines the signing sync committee) + SignatureSlot uint64 +} diff --git a/beacon/types/light_sync.go b/beacon/types/light_sync.go new file mode 100644 index 0000000..3e9b13d --- /dev/null +++ b/beacon/types/light_sync.go @@ -0,0 +1,236 @@ +// 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 types + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/beacon/merkle" + "github.com/ethereum/go-ethereum/beacon/params" + "github.com/ethereum/go-ethereum/common" + ctypes "github.com/ethereum/go-ethereum/core/types" +) + +// HeadInfo represents an unvalidated new head announcement. +type HeadInfo struct { + Slot uint64 + BlockRoot common.Hash +} + +// 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 +// header from the same period, and the finalized header referring to the next +// sync committee root. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate +type LightClientUpdate struct { + AttestedHeader SignedHeader // Arbitrary header out of the period signed by the sync committee + NextSyncCommitteeRoot common.Hash // Sync committee of the next period advertised in the current one + NextSyncCommitteeBranch merkle.Values // Proof for the next period's sync committee + + FinalizedHeader *Header `rlp:"nil"` // Optional header to announce a point of finality + FinalityBranch merkle.Values // Proof for the announced finality + + score *UpdateScore // Weight of the update to compare between competing ones +} + +// Validate verifies the validity of the update. +func (update *LightClientUpdate) Validate() error { + period := update.AttestedHeader.Header.SyncPeriod() + if SyncPeriod(update.AttestedHeader.SignatureSlot) != period { + return errors.New("signature slot and signed header are from different periods") + } + if update.FinalizedHeader != nil { + if update.FinalizedHeader.SyncPeriod() != period { + return errors.New("finalized header is from different period") + } + if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexFinalBlock, update.FinalityBranch, merkle.Value(update.FinalizedHeader.Hash())); err != nil { + return fmt.Errorf("invalid finalized header proof: %w", err) + } + } + if err := merkle.VerifyProof(update.AttestedHeader.Header.StateRoot, params.StateIndexNextSyncCommittee, update.NextSyncCommitteeBranch, merkle.Value(update.NextSyncCommitteeRoot)); err != nil { + return fmt.Errorf("invalid next sync committee proof: %w", err) + } + return nil +} + +// Score returns the UpdateScore describing the proof strength of the update +// Note: thread safety can be ensured by always calling Score on a newly received +// or decoded update before making it potentially available for other threads +func (update *LightClientUpdate) Score() UpdateScore { + if update.score == nil { + update.score = &UpdateScore{ + SignerCount: uint32(update.AttestedHeader.Signature.SignerCount()), + SubPeriodIndex: uint32(update.AttestedHeader.Header.Slot & 0x1fff), + FinalizedHeader: update.FinalizedHeader != nil, + } + } + return *update.score +} + +// UpdateScore allows the comparison between updates at the same period in order +// to find the best update chain that provides the strongest proof of being canonical. +// +// UpdateScores have a tightly packed binary encoding format for efficient p2p +// protocol transmission. Each UpdateScore is encoded in 3 bytes. +// When interpreted as a 24 bit little indian unsigned integer: +// - the lowest 10 bits contain the number of signers in the header signature aggregate +// - the next 13 bits contain the "sub-period index" which is he signed header's +// slot modulo params.SyncPeriodLength (which is correlated with the risk of the chain being +// re-orged before the previous period boundary in case of non-finalized updates) +// - the highest bit is set when the update is finalized (meaning that the finality +// header referenced by the signed header is in the same period as the signed +// header, making reorgs before the period boundary impossible +type UpdateScore struct { + SignerCount uint32 // number of signers in the header signature aggregate + SubPeriodIndex uint32 // signed header's slot modulo params.SyncPeriodLength + FinalizedHeader bool // update is considered finalized if has finalized header from the same period and 2/3 signatures +} + +// finalized returns true if the update has a header signed by at least 2/3 of +// the committee, referring to a finalized header that refers to the next sync +// committee. This condition is a close approximation of the actual finality +// condition that can only be verified by full beacon nodes. +func (u *UpdateScore) finalized() bool { + return u.FinalizedHeader && u.SignerCount >= params.SyncCommitteeSupermajority +} + +// BetterThan returns true if update u is considered better than w. +func (u UpdateScore) BetterThan(w UpdateScore) bool { + var ( + uFinalized = u.finalized() + wFinalized = w.finalized() + ) + if uFinalized != wFinalized { + return uFinalized + } + return u.SignerCount > w.SignerCount +} + +// HeaderWithExecProof contains a beacon header and proves the belonging execution +// payload header with a Merkle proof. +type HeaderWithExecProof struct { + Header + PayloadHeader *ExecutionHeader + PayloadBranch merkle.Values +} + +// Validate verifies the Merkle proof of the execution payload header. +func (h *HeaderWithExecProof) Validate() error { + return merkle.VerifyProof(h.BodyRoot, params.BodyIndexExecPayload, h.PayloadBranch, h.PayloadHeader.PayloadRoot()) +} + +// OptimisticUpdate proves sync committee commitment on the attested beacon header. +// It also proves the belonging execution payload header with a Merkle proof. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate +type OptimisticUpdate struct { + Attested HeaderWithExecProof + // Sync committee BLS signature aggregate + Signature SyncAggregate + // Slot in which the signature has been created (newer than Header.Slot, + // determines the signing sync committee) + SignatureSlot uint64 +} + +// SignedHeader returns the signed attested header of the update. +func (u *OptimisticUpdate) SignedHeader() SignedHeader { + return SignedHeader{ + Header: u.Attested.Header, + Signature: u.Signature, + SignatureSlot: u.SignatureSlot, + } +} + +// Validate verifies the Merkle proof proving the execution payload header. +// Note that the sync committee signature of the attested header should be +// verified separately by a synced committee chain. +func (u *OptimisticUpdate) Validate() error { + return u.Attested.Validate() +} + +// FinalityUpdate proves a finalized beacon header by a sync committee commitment +// on an attested beacon header, referring to the latest finalized header with a +// Merkle proof. +// It also proves the execution payload header belonging to both the attested and +// the finalized beacon header with Merkle proofs. +// +// See data structure definition here: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate +type FinalityUpdate struct { + Attested, Finalized HeaderWithExecProof + FinalityBranch merkle.Values + // Sync committee BLS signature aggregate + Signature SyncAggregate + // Slot in which the signature has been created (newer than Header.Slot, + // determines the signing sync committee) + SignatureSlot uint64 +} + +// SignedHeader returns the signed attested header of the update. +func (u *FinalityUpdate) SignedHeader() SignedHeader { + return SignedHeader{ + Header: u.Attested.Header, + Signature: u.Signature, + SignatureSlot: u.SignatureSlot, + } +} + +// Validate verifies the Merkle proofs proving the finalized beacon header and +// the execution payload headers belonging to the attested and finalized headers. +// Note that the sync committee signature of the attested header should be +// verified separately by a synced committee chain. +func (u *FinalityUpdate) Validate() error { + if err := u.Attested.Validate(); err != nil { + return err + } + if err := u.Finalized.Validate(); err != nil { + return err + } + return merkle.VerifyProof(u.Attested.StateRoot, params.StateIndexFinalBlock, u.FinalityBranch, merkle.Value(u.Finalized.Hash())) +} + +// ChainHeadEvent returns an authenticated execution payload associated with the +// latest accepted head of the beacon chain, along with the hash of the latest +// finalized execution block. +type ChainHeadEvent struct { + BeaconHead Header + Block *ctypes.Block + Finalized common.Hash +} diff --git a/beacon/types/testdata/block_capella.json b/beacon/types/testdata/block_capella.json new file mode 100644 index 0000000..fa6149a --- /dev/null +++ b/beacon/types/testdata/block_capella.json @@ -0,0 +1,1703 @@ +{ + "slot": "7378495", + "proposer_index": "806393", + "parent_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "state_root": "0xb699414b8cae77b7cc01cb3ea5d218062dc534fee640759ef504f1f1f43cf693", + "body": { + "randao_reveal": "0xb9b9101090eabc8d0060ddb91f88bcf579c236883e8b3da0e0192466f5b5739af17b8b7a942036edb28637d1ede61e6c1388e62999b34ea9d54c3b9f1c3683cb58c6dae377b49bc3f604ba7698137c69f7c94108ad29b8de48cd74fc6f173ac1", + "eth1_data": { + "deposit_root": "0x79a2ad4067ee252dc60760a40c00ca5536906668eba5a9e7f7a30fa3b078fddc", + "deposit_count": "970997", + "block_hash": "0xf4fe68e4dab126c3c8fded4c7c825c6cda8b460c81b1a8c3c0b6e10b33e3a4c4" + }, + "graffiti": "0x0000000000000000000000000000000000000000000000000000000000000000", + "proposer_slashings": [], + "attester_slashings": [], + "attestations": [ + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "20", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x89a2fdc5d13638100251a3a447bfbebb205b23e87671f2d0744a1685eed149e5377f9b893ce2cbba559df9ca48cfa075160dbe5d531ed7e32f8ae0a371c38d46c15eedfc1f73dd824fd81607dc84660b97552137af6e7b28ddfe58f457c70091" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "5", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb1c2789fa06b3c2e0019597c54df891c24ef30c8a0c3a2aa2d0ab95b332af97525d4f42518705eebeff6176c87d401e8135711345620a17364f8ad9b7c96ec9973f749d2d05208012e3d25699565f8046752c33c4508ee6d3d3955ca01942a83" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "37", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x825e16bb91abafd316b9d1be810435bb07635a8cd28204e5a1b60f4b2604b085fc6b51850e32d86c137720c1b9e515ae0fc882551f037ba4549d74f686efe48517261eced01174ca699e32e1a42b98d1e2eb523db1e03d7d40be5543c8338645" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "35", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xaff3a1152b91ee89a5557bc852374ce81497663e9a4a18df47cebe59bc926d5845c7f550bb6e55e172c14b12afd7ae3e13c78dc7f256637daa1f0ad66d1d859ff025a379d7c021ff1b4825d6a044a9775254ac0674665d4b361d605fbc6fce18" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "60", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xac7a6651bc4a755414570d441298a7840755bda19fa8b77393e544ece6c917dcb0d0cd092a2b7ec347745c504a626fb70ab0951f48743d872fe21af088668dc29c96f7550d70c34f8f11844c091ed8536696180f5ccc3a9c55b287ce6c709853" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "24", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x92d538e3c9fffd7beb7e408b590c4cf084bc9d9e55fb49d1f84c3da36453c6754b434f47c84139c962c8b0fef8ead19901338417ee0c157e946b987c65babdf767f508981174986a89a5505061406e63f2630dbb5553542dfb7b23993aa27aa5" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "13", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8511a4bb585749da9b9d309172d33358092a07574f213459b618259e38daf493dcad876be2370006c132b384d6997cfe190fa9005f151eb489a5bb1d3cca9491ead28bf3b1874a00612f6a5616757b99109abf42e08fbc7d5a9d4808a72b161f" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "14", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xab5c1637d860eaf8700e1da67df1ffc763cdc0bea2c31261329c6d7d619c9566b0bcdcba1d6a0d2a546dd730d233469d0eeb21dbdba538eae77c9dce5e6953b89a0b47c5a25378b89aefb8a3cb387f10b8ad5b3d4e22b4c7ed6bc99e418ad393" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "54", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb1528f2cb78dda3644816272ec4c24bcc5c554075d3499f48f66f8a3056b5b644acc68733427309567876955199520e50ce72c462d9b4641fd0bd9ff6310a1456b2160d3a12055d37a2d44b8a8739ac04a3f3d498e2b67695f6bd514a9988567" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "16", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb5c2376070b13b792d91ddd3afcb98a15a59f6582a5654264e850064984b2c8f0ae5df8b45b7fe469caa55dedc9ab8950b7bbd375ab1a5de3f20e1aea85be31c3d8c27f3aea8fbd85769ac39e2718ee3aaa4060f7b6290abd3d8a7969a129a8b" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "3", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8e587c9dee7f54e2d9c1058a4afbc28b32f4e45d51303078fa7405cb1a50b8dba1a70dcd4babea8ba37bf9c9e2a1504e007eefc13e2040ee7d11e741e6fa58474347dc490af39868caf2c7c6ffbab90fa4429b068b0673fa7b811ccd847ff9a1" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "31", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x95eb7f201e93f10df263956eb26f3f0e9f0d8db2588d845f0f50314f76ab5b33c87849d7546d56ee99ce6a1b0fe6e67b11e6a813aad3f04f61763772091471f65a1f976a0d2180e58f5db3e54dc16ef7f2b3f20445be68ea1a6701f7d6c6b4a4" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "28", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x97e8426a39404686826704f6f7ab5eb31c532f64352b0e756aa95f4a6742e733053d8bc7e8b44076eed03802dd6a154a177933582503fdfce82b0472e9a7b8c250f0cb7a2c153ad43736c0613004f5b4e915109ed64eeb445453c59a7f51cbb6" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffffff3f", + "data": { + "slot": "7378494", + "index": "62", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x861eda1e26cb7866100650538319a266f2c946986db3c90349ac88de7e8ba30d5970454d19b91543d29c1616197e98c2020c3fd54b726cb06249075a0969f16a492282ff60e7d7e656e206eef371ea6cd51cbeb4aed25809077a3c389d505e33" + }, + { + "aggregation_bits": "0xfffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "11", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xadf1e3e7a66cff8cd606f8142fa2341583870f0ee406bf9ce9dc1b85ce004ce42b1425f27c18986afac16b80df70264d0f3ae1b22f5d7d3b34ae88f5f18a37a74ee67c7c1eb0d593d98dcf30b8b18dfe8db9359dbf9737c018ad78da4a07fe55" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "61", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x85dabe8ab392970b7ab10a6415ce2d2c928dee022a148c096c6626c1431a305912a96d267c173d3a388e167fad9586fc0367703c97f6e3d80de3576ce2acc136c69d86f14e611860a29f425593ac0cf1639e8adb49a580051823603bc4fa1871" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff3f", + "data": { + "slot": "7378494", + "index": "9", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x91370c8bfd2b5a47aeeac66693bc824ccc4ddbfe4bb90520efdd60ea0c93083638b21d1574fbf6cc3bbf88094ad9e2e5019c3d1dc316c68ed81129cfae87860a45c7801ad180b2c69df9375b6a8b48ba3e33faf72d48745d2dffcce3199da875" + }, + { + "aggregation_bits": "0xffffffffffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "22", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x974181ffb8c8820580b1bb35a58c8cb2fbc5704a2b1f9c14101314eb3bcfddf558aad925785b0ce13dd4d3fd58adaf10168d9f06ac198161bd73e351b7fc37a5a9b6b62a4aa3b028a54779d9f16cbf6872a8339cb58c564808fade87ad7cf3cc" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffffff3f", + "data": { + "slot": "7378494", + "index": "7", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x81ca3a2e57e8ce743313c8e83d42b9847d18f48835e1fe445e689fdaf7d72568984de9444a88ba9e7a01b4716581723a0cab39f739f6580ad7f0348c7c5a32d069b1c86efee80161142af6dcd84469f4dd5f19b53318dada988ce2304f7195c5" + }, + { + "aggregation_bits": "0xfffffffffffdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "1", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x86757289ea9294712661b0c4da2afd17894bd43b1ac2e11a1627a50b0ebdeced615b11058919058356158005ff8d2363157d9be67f4a4600100f4547e7d2bc4b9ef14f81cd21dc42bdc207e3e22375b9737147b5a1cfdbef8959cf1d16d64beb" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "4", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8b8c158686252d39fe3c6edbefdcdedb99ebb7df7f66f6c18f2ab45baaf076c1ad889eca5d3621210f81dafa8055284b01da2e1c0e0309fdacee0dede2f4d33a707d539841488e40e45bd14bd38dc5df1758ec73a0a3dfb34d7be2b361bd5ee1" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "33", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x86ebe26268beda78609de2009ab037c042a8b96acecab1c001ec587fb9dbdb5693595d13b7c61e05cfee9eab79c400090a0c7222d08932956580e5ff0a76028da6375045c7e52ea4a8e0a547aa0464d6ebbcc2a72901e606232a2ff349f59cec" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "6", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x91caf7efebe5c9e719e69d69fb7985348f488391f8885368355fc6bfccd5923d9bbfeaacf6e88d060e6876cf388a3da808e63950e76a9f25b25b548b4e1fab691c7f4bad81021154eeb8066383b2519fb594e15f37938b8c821dd2742193c963" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "46", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x880458dab1b2c5f63576cc5d6de51cc53842c3f8cd491a045317e5cac9010aee197cd578bbc276c487c735b98cf2aa6303651e54475ac7dbd4dcdb5552300d369cd9ee5cab8d9741722d8452957844de93edb8d357f503717d179dca6faa7e78" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "19", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xae536d3961e78c85bf1756622f09ca64d0aca31d39042f32a28f8e7c72ce7778edf53cc997407735c97ecbb06ef49f8c08c33246ea80e9cccf86aa0644298b9ed2398336f88ed054521b94f0ed7d61af4e28c516889e2177ebef4f180bbf2ab1" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "26", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x9822678731a8db092b5e007e53c7e5e7f396a102bec255d3773613a93773bac1608b2530a83d467825872bebf6403d9d0696ab837309b82fbd0fb09365d7d1ce20c19f1abe44bc17550483c5e2e8ec5b630443ab338c522626b9dc692100177a" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "45", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8cf67eaa40f2095c91c19f4a8eea844266d0a9b91211c38d8fa0552cbe41578db864463b292184239eb8a4c64139510c0cb4b7f419d5447f812e4f6b2f3665fb8f717cf07f7e76ea196de7f16711d32d3a9cc357cb9d95b5599e31cf698b88eb" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "17", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x94ebbd88f4aa545d7a7fe44b4aadb74a31a84c38a7ac519ebbc26730a4046ba85a5f64585891b8a67b0822b454892ee816b92dd53fc81799b5f77e3771ac6db169ae99415c6d86cbdb973c826ee5c6869f5840ca86273254c4211981da262001" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "30", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x85b54e1443aeb87d78cfe00681c9d30a619b850a454c3b7e126c23c9bf2df0188f59c715dbac64adf1d93453eeeec7d605f5297b178107bae8ef2293bcbd0c3d8a73f924558b4331661951f5706472fd355a45589e3fea173db238355d0b8d60" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "18", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xafc699d33be631418d821a44f64648697e0921cf1dad336c04d3a4424b293ea70a680683f414e67565d837b55b027f1d04f3d0a0e9a42a4cf61ff977cfd23695e5f91890da6eea51bd46eb22085e1241e970788c17ae8f13288c04724caff17c" + }, + { + "aggregation_bits": "0xfeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "59", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb5d6da99f07a3afe87593ab930848c99fa7ddb352298c210faa0d131ccaea3727cf3389a8dc07d64c6312ffe93660293088e4332aeb60d0df51fa25bd7c922d15cd084db19ea8d69a42661738ae02a0370e99573c2db8b4ba7b6748a0fe15b74" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffeffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "58", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xafaa8a5ad8082a05db922f78205ae39fe45c364176f732f4697a35659d9cb803ac10b90729d11a3f35141095b478a939086b0a195430fc4efcbfc61ef4b79ff770c2b50e3949cb51f977541253877ed319b60d27027e29363b5fa4d8bf37f24a" + }, + { + "aggregation_bits": "0xfffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "42", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb3f70d94e80376f986498cf8c41eeb3376730c19fa0c14ca03dbff1d85180aa475865f4bd85485d0ba5c97f9918f38320ed3e0a2edd1ab159493c5956f1dd111ef85e0921105c7b77dc940401a94db4b62929e839035559072260aaa78f21bf1" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "32", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x886736358ded1b56826412fd3e604cd71832d88a597656a65ca1eae5121fa037148a2e2678f3c2369868e30937cfe7b011763f6bc9bb78107d9158b4048ad7eaf31348a2622541be4942e4f522fbffba520ee58002e8a4d71a3c5f7f040cdafe" + }, + { + "aggregation_bits": "0xfffffffffffffffbfffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "39", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8a8a8641c5c757b6a46dbf2500adb236cb100781588c9b156cf928f237f338620b1856fbc200c95c197b7f08ba89a8a606880a64b2b47ebbde066bb4694ea38795b54cc55f907e09f43eaa0cd4d868b57f0805d3aefd1538102acf295078cefc" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "53", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xa26e6c52b9e82f286ea699339e7156ad6e62590855063365c8beb4923e7b5f9bafcbbd40d476a70da97d452989b7b6c307ae8265e31d8ab35cd9d680b5ef4f277ee18c9167a81ec68a58c13447a23cc995807d6a2866d44e2afaa39333cfec71" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdffffffff1f", + "data": { + "slot": "7378494", + "index": "36", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x888547c6d5d294f66ce31b96a2357f7e27e72c8f76865310f83125f158bce9a1ad1506835086cc0bfe88bc2f94ff42de1823bbf61973372da2133f93d1a8d202f3a2d5ea75c446fbda5a834b2f143a7f24d30b1934e511ec8b218668705d2f0f" + }, + { + "aggregation_bits": "0xffffffffffffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "47", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xa35f8f9fd4848615e331c08e9088c434d27f885c1794e138cb6f2dacfbed501df2b07de14c9d0e7704fbb597e6bc8668058e0e838d7a2cd64301068db3e8f0b74991b1a81d2d4591a55ea6489a3b80f5ba2ba818f7907273bfa3aec702cab0f1" + }, + { + "aggregation_bits": "0xffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "29", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xa480ba3e3af31e33277863c46165f18ebd710838949b051496eaf1bd79ea361586d37959e398368905bb9fc55bcb48c80a8aff7420234640a34149905726653cb283a4d0f348f572ad0afaa8e42c74cae152ad20bad5615b2a65bc1657260e7a" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "21", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb0be1593527fe7e9bae75a9686dc8027610ffc969dab11c6f4a5b94e6c46e2e4f57022630da357da98c61fc877c8697010e2e15456fc2c4e74932f07ddbccd3fb3ba756f52365df96f9e7aea690554be43d03cb720d2c03d79af35d3b2a1f456" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffdffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "52", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8019f790aeb9e5d5de1c179a02b648b9936a5bced2e3f64eaeb0baa8ed794dcd55ef5a861e7401deb78af96cb83470340c8ff39e320db4e445359519a477740aa5f836aad885759d3759ecf4c56dcdea31b9299a5d476334778ad203656bcf4a" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffefffffffffffffffffbffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "56", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xabc0dfdbb7ea5255f92fae93243569d1362d47d4f67d44c25b38185d4ae2e3b02f812223531e901388f003dbdd4ec1841181e5bafe8f873c06b4e9f01b4701362bd89917c563ae077745e81a922371aed4e17669e3bf5e529743dccb8a5036b3" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "55", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x828866f494c35c712f5ac17f1dd259d9e527509cae8947089fbb1bbc10e6ba9f13cc237caa217c280d060b200cbb122807486307a42ff96c940ae27fd175c5337fa093ff3a64153fe7723e6a54981372875a5a82ff41bc675d4eadccc9e5884d" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "34", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8c77a3007d6fa118fd801dec53dbb8c58b133b319b03befddd022f001dd0b0480841161d38e8f833d128cbb959fab7e90bb1ab60aab531f6af4b77b102d713b2aee48536129b7a309dae9bb0371af7ce683748646087dae41fa528ecb579506c" + }, + { + "aggregation_bits": "0xffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "23", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xa790ea43694fce44a777b9071e1a9c7ff6b20783f9c6ed59dd90ceefa71ef770a1b4cbb8c23a295e019055e7fb004b99025dd54fd95b050dd13162ed1d861090db80fda95307ee0196d0faeaf8f83caabc33b332d603061d8de8b44ac9956e8b" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffdfffffffffffffffffffffffffffffffffffffffffffdfffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "25", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb4aa421f9d93f03a62f004c9b24e66253957f237fc548b9d60d5d51dbf6e99656c16aabfec33ab6c0765ed16745e33440650c42ce90e0ec4021524610d6ec7211b1bbff23ba913fbdd2505970213a9b890c0347f35f671b01d9bf0387e97631c" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffdffffffffffffffffffffffbffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "0", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x89726e95f463d159279178dd7abb677c507575ad4b0e59f52303af95983e1c5bba45e49df86f5ba88ea8c433b9b5beeb02ddbe75bbc3404d85355a0af642ce615051414897b6bb3fdf14787cac832f3a93ee3a83e00d412af3f5d513ee12152b" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffbfffffffff1f", + "data": { + "slot": "7378494", + "index": "57", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x818dab21ea1a34d31179a3db511b93f538399b1f898744b01b5c569a3c27107641d5c16c3d3a3367df8dfd28e8686143035b5334d595e30b0eab3739f248462bf2bfaa69a067cb2ce9c279630196f554d73912e38702fb1e5991b7003d1c3546" + }, + { + "aggregation_bits": "0xfffffffdfffffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "12", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb8cfc57942d455cd6a93d9a0cddd662c99d3f317a6e2585bc36399192d17111f9aa2fc5f7a5c80ba532c3a8a1bc3c46b08a2bb3b2db007eb7f78056332d8a9d903d11488022927f1a7c9d60c8c509089ca8ab30cf913a9aa7c45c91275287774" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffdfffffffffffffffffbfffffffffffffffeffffffffff3f", + "data": { + "slot": "7378494", + "index": "48", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb5ec81bc92ea85c105acb162bd95daaa7daff124f5effa1b399ff6d92beddbc10d9c8a217b8a03e047f8c9644200e27605a7ee60068fbb01a1517bbfe383759ec24a553d621a5e5290a41e9b121fa13e82769ac40a450b30cac877871f90841e" + }, + { + "aggregation_bits": "0xffffffff7ffdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdf3f", + "data": { + "slot": "7378494", + "index": "43", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xa40d82c6c4e71c5ee93e31d4987c016fb3933bc348ee33a7a9b6e9cd592377af9461138d40c1f1188541acc9b6a10bf4035820563a467a4c345125660188ad9f5ba433b4a6168757c37ba8f895faae87dca56b4e1940834ddd5765ebc7cd7dae" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffd7ffffff1f", + "data": { + "slot": "7378494", + "index": "27", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x9439508713ae98aa4a8614b4fe4de5738bf4935d00f769e3a8afadbc1880e87854918cc6f1cc1e4beb61672fa9ef944e147c469aca29bd2d0a924730cf618729269a11b3c65edc1b8dcb836bd670ab2fc0af20ee139ab2bc57de07412771d26d" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffbffffffffffffffffffffffff7fffffffff1f", + "data": { + "slot": "7378494", + "index": "49", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb67ecdb8a80153ee5f63d2f1afb1a5e9fbcadd280f015b3eaddd420126b5f550e0d2f1115781e29da657662c6e821a2d0014076059eadb67468e269d6fa277555f553e54d4810525ed31277644967c8f2a8ac98f95a406f055172ebc78f81650" + }, + { + "aggregation_bits": "0xffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffdffffffffffffffffffffffbffffffff1f", + "data": { + "slot": "7378494", + "index": "10", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x99ab8d38886fc33b205acd97c0c069c0fb3f55e5dccd70611c1f159b73e0d3d20a91997f4b3e93f80bac42c99ea92b5d1460a78b53f335c0de7f9d04f77ac59bbbc141fdfeabadb1125a5ecff7d9d421c6d553837d34eece99ac2a59f3938af1" + }, + { + "aggregation_bits": "0xffeffffffffffffffffffffffffffffbffffffffffffffffffffffffffffffffffffffffdfffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "15", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x922c40a03459860e7b32df69fb2f1153e0c52601a08e7716d8a637a0ad2eb717ddd8f90d95d71c77f1baff1d9c386c2d1627c9ae755698ba3a1d5bd62dd4343c708da623db4c8aead3c52d309aaf3fff7b90a8ea739ab076f2e623227efe57a1" + }, + { + "aggregation_bits": "0xfffffffffffffffffffdfffffffffffffffffffffffffdffffffffffffffffffffffffffffffffffffffffffdfffffffff1f", + "data": { + "slot": "7378494", + "index": "40", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb6b43e392a1ee37d435a900be9fdcc1e4802c87fdfe441570f6869a61c8e2de9efcf80e266d3dc4bab4aa70f0fc99dff1424d5eddbe179b4abe8c356f948a7309290ce791de8af16410727ddede4b8a8fdf20fcc3496495825a15a9cca07f04e" + }, + { + "aggregation_bits": "0xfffdfffffffffffffffffffeffffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "2", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x979301b9636dc5decc492df123920c28464ea97c2630e300d44c4b6d700a2f21d46f1b922763517b0e8234c7724f146505c18b0397b613aa2e1bd2fbca5266f03fc113af517726c7fc70aa930ee95365f32cc552d779447622b1b27dbd9394f2" + }, + { + "aggregation_bits": "0xfeffffffffffffffffffffffffffffffffffefffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffef3f", + "data": { + "slot": "7378494", + "index": "50", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb06352fe674ca8af724db3c28eece93c8ffd29b12d1db38f04dc7a4d74c454899aa0a17e821d7b768ed3c5c849b559270126a0f2572cc7076b9278289d79415817691d7c3519666603f2b0ca8f80dfdfdbbed87ed8fe328bced445aa9764b684" + }, + { + "aggregation_bits": "0xfffffffff7fffffdfffffffffffffffdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fff1f", + "data": { + "slot": "7378494", + "index": "44", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xad45143668483fc7aaae6cd3f46caf4461bd543a062679ee3c8c786947d580fcc20b358858606edf65d21c512965aeec11fbfb0f9dd3212e9b04b4fc683fd69a2dc2991a9297963fa683c0806948310b645cadc76f7208ce44c05c5f127ea110" + }, + { + "aggregation_bits": "0xfdbffffffffffffffffffffffffffffeffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffff1f", + "data": { + "slot": "7378494", + "index": "38", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xa34e85836b67b56d722c02c0cbbfd44492f0f8ffeacc9deed8e1ec8efb2180d1818e102451eb9571f4e265a20e73ac4702fcb55ef2012aea7bc2a73efdaf24e6e81d01aed51d937a17df21b4bb9cc5945dd5a5fb2d7a14d727283ec2d5cd3ee6" + }, + { + "aggregation_bits": "0xfffffffffffffffffffeffffffffffffffffbffffffffffdfffffffffffffffffffffffffeffffffffffdfffffffffffff3f", + "data": { + "slot": "7378494", + "index": "63", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x95697e4ab24617346af013bdb607a4cd9ba68647ecf77adff969fb25cd2849a0f0947a71dac14cc75bac28f13b88490701cc5c1e3d8578a147c9fb2ea5ea0641464d4702bf2ce4ff733331d6946787f142fc2dbc2a31c5d4bdb859d5464c6c41" + }, + { + "aggregation_bits": "0xfffeefffffffffffffffffffbfffffffffffffffffffffffffffffffffdfffffffff7fffffffffffffffffffffffffffff3f", + "data": { + "slot": "7378494", + "index": "41", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xabc2f9ce23e03325784b4fa68950f36f93c9b51df97040402e72b4edd7fbed17f624632e666ca725a8044906b2643ce10cf1eaf55cf661e0ef9d6024a013ae4eecf14d2952cdde2169d8f2dba7b28dc7d48465f0a87f2b3c6b3a287c4ea5e63d" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffdfffdffffffffffff7fffdffffffffffffffffffffffffff7fffffffffef1f", + "data": { + "slot": "7378494", + "index": "51", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xaf2d02761082aff8842a44321c687d8722aebea6611d1d65873f50515b751cf67eb5929fb99c3a8818929d2d7e8bda97189c3589a5b99f8b027464017208e7ec79e3fd0a4b88c5df1ce277d6da7ae9d9b675cbe00ec0085e85f36e15d9eea1c9" + }, + { + "aggregation_bits": "0xffffefffffffffffffffefffeffffffffffffffffffffffffffffdffffffffeffffffffffffffffffffbffbffffffffffe1f", + "data": { + "slot": "7378494", + "index": "8", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x93669ada664de0740ee57235fc4aeff39daedca3d99190c4bf0f1603636ed4fb8d2289aa63362f2d0ef109c2e01c54bc0c01fc17f71c5ebcefabdf5237803a4608e621ea0f576d0209a96186ab548b5f8a35ed44dd8ae034abc7290424aef1d8" + }, + { + "aggregation_bits": "0x0000100000000000000010001000000000000000000400000000020000000010000000000000080000040040000000000010", + "data": { + "slot": "7378494", + "index": "8", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8136d4d491ac7d49fc966091ff31631e7da9dbc33ca7d5ccdcc8836c3c797ace914c8cc512a371440e0352ccdfe104231059453e21f915d16d9ba64463f6943963f7515f7bb6e823218852d583109ff99d99a58928820020f36cb0ea7987acce" + }, + { + "aggregation_bits": "0x0000020000000000000100000000000000004000000004000000000002000020000004000000000000082000000000000020", + "data": { + "slot": "7378494", + "index": "63", + "beacon_block_root": "0x8d93e82f4ccae01a237d9c1bbfe4deb546aea2c02f3a5d8fa6f8befe96c9a537", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x80996d8bfad4ce23481fdfb8824b8e60ef828847174dc64e824a12542680572fd38eaad95f2d99c8bb9cbbcdaae094f9066d3ed62875bdf402f678808e6b545d843ad872b3a14e3d69e5b04a910877b92299fe6586f1a0768a83639c76814872" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000001020", + "data": { + "slot": "7378494", + "index": "50", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb649f1829fbf1ab8c2737fa4bb5a2db3a9f4d9ae8dbf378b9f9e5a0f927a0314bbcfea4cdbdb71d78d43922cf0d489860b7570e02489defe2276c88c574388dbc32e926ca124f25c20cc6e8ad0951ecb8f2f1ef70291bceaf5bee6ace949b235" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000020", + "data": { + "slot": "7378494", + "index": "9", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xa71226b5430c675b00c95baadd0d051d83745f995e449e00a2d29b5d3e45210841b8023bf1a6f32d5756751d8f51623e162ebd1e2faf773552e5900152bee0cf9e59c7e955707e99a2fadda7859d304c5f12d328990df642006aba9d777db1d8" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000020", + "data": { + "slot": "7378494", + "index": "33", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xad1f93841542a300585d51d034d2b4c906054eb89b5193efabc7ac8cd0461ce31af127d973361cb1f9c98d3ee1fbd9480d8b3786df4c631619510ea75c8b1065e2077dda4f51053e9a36096b10b5e0b781bb0be24e0cc8ba7d2dfc564fc96618" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000010", + "data": { + "slot": "7378494", + "index": "38", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x88b2b792e1f1ed648e645fae6e0bc6c7021a99f11e05e9a994120887c65280a2e5e49011e06f263a666c1186cf73252507b6efe0b049b876ef686deee017fac76d363b687dd53c6dae6926b6a831304cd15b83b7d46b33fa9ad798fea3f7204e" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000010", + "data": { + "slot": "7378494", + "index": "27", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x886c27c1f86d771936ed595e7f145b0d9fee329f6beab5e7e04be10ae3043cc964612e8e9e60e0a1eb65acbe58a3e93300df489d2a782708db1de1918e7ffbaf276594df1a73746805a4e321c52a61b4af6422058cf85cbc322b6be791287b27" + }, + { + "aggregation_bits": "0x0000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000020", + "data": { + "slot": "7378494", + "index": "1", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x93ed986d6948d557e631c70abc77a8b18b82900b5d95fe91fdc9c6d53e239b0095a28133ba21a05779758299545d64ec145e3e7c805ba4c26c3a71647597fc5923098f196c1d514b45db59f109b77238b0bf98c8dc5749c6426be4b2aad9d827" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000010", + "data": { + "slot": "7378494", + "index": "21", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb41891824051794a18ab11a12a524c7857b327567304109dc83abddb49475ee6e639525ebb505488a631b84354a97a6b130bb559cbe773578969c2608da8854b76283a48f1b1af6090f13af65436c444ac4fa3e023a55716990386f61be6e30b" + }, + { + "aggregation_bits": "0x0000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000020", + "data": { + "slot": "7378494", + "index": "41", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8145a8db62dab4c43d075f212357edf2395a50da11173b0455a88e0aef96acbbb403258265da20ed30b2bd8cf741f9320a659f3bb58f22bd51b011c1f15ec1bf9c23d5471dd40b12c939abfdf97ebd6a81305451dbb970cb3d8064621f3eb14d" + }, + { + "aggregation_bits": "0x0000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000010", + "data": { + "slot": "7378494", + "index": "25", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb29d54c853e60e0e29f59ebbf00a729f04d7e948a4a063bfc66b273b0cc3de66dbf9df090afc48e472a0ab8242ead35306ecf170f5dbcfe299927201c395ac18f42afdc0ce3974c88a06ed7849cd8cfcfe56db4853c9490fef930a8f8dccdc55" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000010", + "data": { + "slot": "7378494", + "index": "15", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x99ab43ce9475ea56c6cec9f284782a7b2234745ce68e3d25c2a2b9044ad7fb2a1f574fa329e849b64c8eb0224acd428510131378b3c3d7d9a8bb50a147939bcb439dc808e153217e3a0ae2ad31e3c570fab4ca851d6d9d9397a60cb8c769aeec" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000110", + "data": { + "slot": "7378494", + "index": "8", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x953635b952cd3c924d91b80cc8fe725bf256eea9d28a7325c713bba8cb6f0ac2a4b8161dd865a2dd460531046be9bc4a0e4f461002d9d51d43d20c3d3cd61c18738a0254769502e915d5235dcc0f59f65d962e660b24a11896cdd133c68c001e" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000010", + "data": { + "slot": "7378494", + "index": "32", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8d442fe70c41b582ad3bb0c8ae2044dd6f8990cf2030288e9e8d2816f3ecaac2c74db82c6ebf730ccfaf40b8a61b18d619729f0bd439b0b638168c15d167a8e12f5f3ca9e609582b0e70525d9aedaed4f4cad6fc186e509af58e99d8d847b53c" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000010", + "data": { + "slot": "7378494", + "index": "36", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb250f21b8113d831e77df1a84ace39a7b44befdad1090f6d653ea687ee96a8f3ab061389c7d7d67f8488a2cb79259c29070b7f4ba7dba087fa1051ddb6278bc377db21e7bdc42cab6f7c1bab75a9ce7bd7a8d5645294e16d253f01a73df6f50e" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000010", + "data": { + "slot": "7378494", + "index": "51", + "beacon_block_root": "0x27c3f56f32193ea28595e76681ea327d4a165cec76e7416b8ea63f8706ec9391", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb01b9b75b99e2e22b0a6f29c6d89c3c0aaf5e917d322fc487ff0fa4a32a74b498c6c6bc823f2247e84fb0849dd79f8511293d78ea24e671fd803e5894466d70cbe6b997d59a5fc530435551e41e5a3ab713eb42586375db8ee3349221f23516a" + }, + { + "aggregation_bits": "0x0000000000000010000000000000000000000000000000000000000000000000000000000000000000000000008000000020", + "data": { + "slot": "7378492", + "index": "9", + "beacon_block_root": "0xeab9fe966f136db09c1a42dcaac1b8bf6e58a9e722612a5ac73ab4b2f48b001d", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xafbd61d25ab5fdcb3c8a263b08a20e3d315b209eadf2db068300075baea8f39a7afdcf7e7453733ae9ba9eae8422aa0508ad01fd943fe694a474282d66dfb32c9f2c6669f17bfa4ee01a11281060e310a7d8e834fb9d6e43676501e013a1c204" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000020", + "data": { + "slot": "7378492", + "index": "43", + "beacon_block_root": "0xeab9fe966f136db09c1a42dcaac1b8bf6e58a9e722612a5ac73ab4b2f48b001d", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x8a55078263eea258c83d53073dc9bfd783741914eeda57dcfe1dfe4e935daab5320b46f0ee60488a1eef6bb427a4fe490c4c347dd99f986e246478071923488b37c7e63fde9fe0c1b6d259423cc9afc0e22f38abf172e335e5a426229a54155c" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000020", + "data": { + "slot": "7378491", + "index": "11", + "beacon_block_root": "0x7da3491e9e1ca60297512f8c2304b13f570f395665236a24a968fae6dd44e402", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0x86a7aeadf1031d01003400c5912323b659ed472ea4b913239a12fc494388884ff898b8f9c0fd400e935917021aa42fe517e6594d304e57930f501938adbb5f759890bbb2d2eff9a4a46b1e6dcdfe64a01d6106037b735fc4d721ebdece7cfd11" + }, + { + "aggregation_bits": "0xffffffffffffefffffffffffffffffffffffff7ffff6fffffffffffffffffffffffdfefffffffffffffffffff7ffffffff1f", + "data": { + "slot": "7378486", + "index": "41", + "beacon_block_root": "0xa1946350486b0ca91a93fae2de443411901a9ac824a8cc93dd046ba15f7c8ea6", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb7d6275bda2982202a8c75ce31190bf726a43ec197bab381e4ea61997b8fcbaebc1fe8a94b6f865b297fbd723709222b0621da909edc0fdbdbf5eb085961c142afd07e73dc7075f23221d934eef0e835dd5b042f1c74bae569e6446c5238e1a0" + }, + { + "aggregation_bits": "0x0000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000020", + "data": { + "slot": "7378478", + "index": "55", + "beacon_block_root": "0x96a3675a608ca14556c06d35ac4783492cf033cdd328973788909444256a8c9a", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb98e95ffcfe118d1d5b5a4f310d1871dcaff5e65cde31355828787bedb27146c96689ca9ed3f32fa204ec81b99523f4e0848161ce7b9b3fa5fa38d0a9d96ba50b1ecdccad3e2a17d3598b9c64017d7589617f3a8009366664a884bf91e2049eb" + }, + { + "aggregation_bits": "0xfffffffffffffdfffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffef1f", + "data": { + "slot": "7378469", + "index": "33", + "beacon_block_root": "0xe91d6c3eaf1b381dff1bbd0558d9e6dcae714efffc72890391b3ac8fcd56f51d", + "source": { + "epoch": "230576", + "root": "0x8457e7b625df2b40a671fb135acc0fef14d29c39a2f8ffb80fbe0d274bcbcf8c" + }, + "target": { + "epoch": "230577", + "root": "0x4d12215357069641e9aca3d7b03851bde21faa001110c6204dd88d317696f3b4" + } + }, + "signature": "0xb7cb86960dc0dd66f3bf556de8f71a09ec8d2e0d0d0a483006e19671b3fa74470492b30e9bb7e3458e080577b031b1560aeaccd0861092f05a0116144d838931b78ecaf1ed4b8a5dda64bdd18758956b1f01f8c4d579c614d68880f9c49b0834" + } + ], + "deposits": [], + "voluntary_exits": [], + "sync_aggregate": { + "sync_committee_bits": "0xffffffffdfffffffbffffffffffffffffffffffffffffffffffff7ffffffffffffffbffffffffffffffffffffffffffffffffffffffffdff7fffffffffff7fff", + "sync_committee_signature": "0xa3e0ea489cfc3f2370aa2587f486e99a3ae405bc1d46466c2cd373cd9669bec5818914ade2a465096eb4528a1d1f368817ed65262a195206ef87503ecbb22e17dd90f6155fa61f4288bf44baa088e50ed3776fd9e005b45b15a016ec8fb050bb" + }, + "execution_payload": { + "parent_hash": "0xf08c1d3dd9cc49d708e89dfe8543dead59bda12ebc714c9df0a5902259dd4fb4", + "fee_recipient": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97", + "state_root": "0x7a4d9731f6fbcb9135225b82edb9418b8bf9407957a524cd3d3f0e60dd520974", + "receipts_root": "0x4e30ab0d1b712b4b4b93864f956287dfcd688f3c077dd356d1b78b6d316d1622", + "logs_bloom": "0xdaa17125c458582c508070b48993d338a9aaab4f0f902129981d200a8110108262b67dd54282243420d2138b013505390a9333083f917cc0d660958ab12ea300e013a1dc040bdc18890f7a19d95a80e43e8326e289c79c880ddaecc69e62a0c019087924d209c18730c210b24c265c0f02974088880844b29754921a52793855874822d02a468aa0114dc4c84a230c96600e6485ed1d8c8eee6900ce14d8166d82a0f0c14aac2042e10600e851d68c31260a0ea844b32833244d056711105941c7c1129239c51d395142886aac98f20748382938044ea6534a04513a42303063a83eb1960b326db1c3a7609a8881c801aaa09a9b5b0038f3806bbd475f971c43", + "prev_randao": "0xf25f7763261cdf5ba7a89b400998a1403f12dde232c5d9ed85caeac1f30974b2", + "block_number": "18189758", + "gas_limit": "29970705", + "gas_used": "10355584", + "timestamp": "1695365963", + "extra_data": "0x546974616e2028746974616e6275696c6465722e78797a29", + "base_fee_per_gas": "8339352708", + "block_hash": "0x802acf5c350f4252e31d83c431fcb259470250fa0edf49e8391cfee014239820", + "transactions": [ + "0x02f9081b018314470d808501f1106c848305bc1a946b75d8af000000e20b7a7ddf000ba900b4009a80840efa8910b884be341de2523740544851b599aafe5870c5997e5c8addecc2649caa3918b54ce26f2e30f64c5b684b141311ce138ab5e00e71d6ffdc00059448e5de5cd0ad98ba6288ed7819246a1ebc0386c32c314bc4189840ffa4c5e25dbfc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2d0d56273290d339aaf1417d9bfa1bb8cfe8a093301f42df90725f901e6947e52eb9fadb02f95de1eb8634dc0b4bbd4628f38f901cea0ab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50a00000000000000000000000000000000000000000000000000000000000000007a0404e955b4f11522f99577dfc88d0dda82da90992492b18491843775f5a1cdc61a0000000000000000000000000000000000000000000000000000000000000000ca04729effceb34e32ea7539c2827046bdcb467a191dfa169688430ec34d1dd2963a029cb8bd4e192d16f51155329ce8b0f5eb88a1d9e4d3b93ce07efbac9e1c4d175a00000000000000000000000000000000000000000000000000000000000000011a02dee8fee0050f9b50254bb2dce2adbf1d1176c39619cfda08a9fcd208972e273a0000000000000000000000000000000000000000000000000000000000000001aa0000000000000000000000000000000000000000000000000000000000000000ba04cf2bd51af1a8ac56b4fb0e23da1717ba813b99917e5e36de6e3ae319a316b3ba00000000000000000000000000000000000000000000000000000000000000009a04c39b3fdaf585b5ee5622d9ec0cb4cf2bc86694673ab95e5a63f084e37d4e9b8a00000000000000000000000000000000000000000000000000000000000000018f8dd94b54ce26f2e30f64c5b684b141311ce138ab5e00ef8c6a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000af901c59475c97384ca209f915381755c582ec0e2ce88c1baf901ada0404e955b4f11522f99577dfc88d0dda82da90992492b18491843775f5a1cdc61a04c29a58e6ae8e8d5675a8f982d2b7b5003c687633919a622b92973af39bb0548a0000000000000000000000000000000000000000000000000000000000000000aa05a0dc5d4d49c845a7e5c8f30d3eb17f36afd4610ee030b6b45acdef0e06b51fda0a1d95ad0e500f5e4b1bd149186814df18eb98e8780bf676e8f3db3a0f3face33a0d6cd76e208ea80eb6f706515ebcfc15fc94f57f3e18452883d9478107143d407a0000000000000000000000000000000000000000000000000000000000000000ca0154bb98efc83b034ad81fbf23cc88c9737739df170c146ea18e8113dac893665a00000000000000000000000000000000000000000000000000000000000000010a0f2c891cab2af1155379e2cb5a591b3e1f3859d3ef1c231d4987204c1fe7ea115a09bb3e24e1534bce24e9896f3377327d742d6c1d430477b7ebc070c2eb64e3147a0000000000000000000000000000000000000000000000000000000000000000fa0000000000000000000000000000000000000000000000000000000000000000bf8bc945cd0ad98ba6288ed7819246a1ebc0386c32c314bf8a5a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000001a0f09b457c15826396efb730bf67656e5debac76c904fafa6861ed5765cea4df44a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000000f85994d0d56273290d339aaf1417d9bfa1bb8cfe8a0933f842a0b17349740b669941baf55dc09d27353d5066f7515a585f533b40596bae334695a0577b913a3c8810dd10161c9ae11e2ee31042564c62114c83b0bc5d3a3e71b362f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f884a012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8a0b1aa816c3c240e8935aa44133611887ed238c7d51f01f8b123b6f452e8272eb4a009d0a653d028a303e3445ad078cd9784c32b672ecd784e05dfa863f177744f2ea027902350b23dab8e343168a9c4efe515d63cf66808c513bd6a00ee1036192055f8dd94e2523740544851b599aafe5870c5997e5c8addecf8c6a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000680a0b4686af228e16c5e21f2b62f7896e62b8e47e9a81c89cdfc8c804880880030c8a0606201c4f426d1864e52a0833c31f7b6e74f828a1b5e425ba2c01acef3635bf0", + "0x02f9015a015f85037e11d600850667aa78c683035925947a250d5630b4cf539739df2c5dacb4c659f2488d8802c68af0bb140000b8e4b6f9de950000000000000000000000000000000000000000000000000021d6a5778fff4e00000000000000000000000000000000000000000000000000000000000000800000000000000000000000002e0ab608813dc3a413481d8a600ccb4f5704545200000000000000000000000000000000000000000000000000000000650d3bc00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007e52eb9fadb02f95de1eb8634dc0b4bbd4628f38c080a0c616f500f8735ac3ca85feacca898cb12b655633124e6781d4594259db78255fa02bbea542ddda2bbccbc45c9729b006ad7929a768adc8d3de97eba872dc9b8f64", + "0x02f902fb018201c78405f5e1008502ceb580f5830326ef943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad876a94d74f430000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000650d423b00000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000006a94d74f43000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000006a94d74f4300000000000000000000000000000000000000000000000000000004dde6c0c64ea600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007e52eb9fadb02f95de1eb8634dc0b4bbd4628f38c001a027bb6379a22d41fe5cf6aad6c448e7fe2980e1844b246d3a338fc8904b9ba882a05a438e766d3cc916550c63157b7ec6f654ecc94b30009ad54039393bea47e7ea", + "0x02f901da01818a8411e1a30085036d589cd58303455e94b517850510997a34b4ddc8c3797b4f83fad510c48801f161421c8e0000b9016466b210ac000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000001f161421c8e00000000000000000000000000000000000000000000000000000015e5073bf5771200000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000b619d517c47fa807bb19e6a4e66bf4552fd2e6210000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007e52eb9fadb02f95de1eb8634dc0b4bbd4628f380000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d2a52f45c74b358abe1428bc43f0ce9ddf130780c080a03d2813afeabbb404e687b0749061af0f17ca73f95c8b2813fca9bbbaedc929aa9f3b3da0c7b741f3badb07c45c805c5a186507d2842612688b02ecef3848fdb3", + "0x02f905d8018204b784070c4719850239295ca88304ebeb941111111254eeb25477b68fb85ed929f73a96058280b9056812aa3caf00000000000000000000000074f33228ced53754d0e3fe7ba92e46abd5b15763000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000075c97384ca209f915381755c582ec0e2ce88c1ba00000000000000000000000074f33228ced53754d0e3fe7ba92e46abd5b1576300000000000000000000000019f4d695952cef25328686ac7db05bddaba81e1e000000000000000000000000000000000000000000000000000000009502f9000000000000000000000000000000000000000001b74e3d0196b6e1d324e40efc000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003c472501348bab121842e674cbb95ce7116199c57adc865b22220a8326716986d3f7026efe4e32c5b5788b54ef177118af7b39a2aa632ec79bd480a6a462a2e423500000000000000000000000000000000000000000000000000000000036600a007e5c0d20000000000000000000000000000000000000000000003420002b300029900a0860a32ec000000000000000000000000000000000000000000000000000000009502f9000002705120f6a94dfd0e6ea9ddfdffe4762ad4236576136613dac17f958d2ee523a2206206994597c13d831ec700e4f02109290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000bfa899c1ad97229d9c604e9ea927c7acb988c05c00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f00000000000000000000000074f33228ced53754d0e3fe7ba92e46abd5b1576300000000000000000000000019f4d695952cef25328686ac7db05bddaba81e1e000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009502f90000000000000000000000000000000000000000000000000015cb4e8892f0860000000000000000000000000000000000000000000000000000000000650d3b680000000000000000000000000000000000000000000000000000018abbaf4c47002000000000000000000000000000ffffffffffffff001b5d4864463ec6000100000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000041d2aaac950ed27cd9eafc88901ba8fecb9a9e787076ed7ccaad7a5b2ac743e3f76774db49ca7e585494c45ef5361da23f9b2ac2abe1f04b97c3a67575af4160a21b000000000000000000000000000000000000000000000000000000000012340020d6bdbf78c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20c20c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b54ce26f2e30f64c5b684b141311ce138ab5e00e6ae4071138002dc6c0b54ce26f2e30f64c5b684b141311ce138ab5e00e1111111254eeb25477b68fb85ed929f73a9605820000000000000000000000000000000000000001b51926d602a7b1bb5bc8f7c7c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000e26b9977c080a01b70f49b8caa36113ad532d50e5bfe8106213089870f11918531e888fe8ca111a0087c38a323105c67801b3a260ce1015ff467cac1695397bd371b3f16e928e0ab", + "0x02f9021a0160841a483f6e850242357375830372d09468b3465833fb72a70ecdf485e0e4c7bd8665fc458828a97379e7e50000b901a45ae401dc00000000000000000000000000000000000000000000000000000000650d3ff500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000d0d56273290d339aaf1417d9bfa1bb8cfe8a093300000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000741f485b010da3f2c9d4131f867155f1b3a99d6c00000000000000000000000000000000000000000000000028a97379e7e50000000000000000000000000000000000000000000144eba8f77fc518b23de7e0e4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c001a0ad879c7b36b6756e998558fb6f4af076035b4d8aea7558a50bad0146254cf541a0143d22a8ae3c317bc879511c43db0c8bd93006b9b0935696477c3e49ce74b4ee", + "0x02f907e7018314470e8521fda6fa928521fda6fa9283055234946b75d8af000000e20b7a7ddf000ba900b4009a80840f6920dcb8aebe753de2523740544851b599aafe5870c5997e5c8addec7e52eb9fadb02f95de1eb8634dc0b4bbd4628f38c2649ca9607a38b54ce26f2e30f64c5b684b141311ce138ab5e00e75c97384ca209f915381755c582ec0e2ce88c1ba71d6ffdb0005a7869f60e85cd0ad98ba6288ed7819246a1ebc0386c32c314ba4c5e25dffc418e5a880c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2d0d56273290d339aaf1417d9bfa1bb8cfe8a093301f42df906c2f901a49475c97384ca209f915381755c582ec0e2ce88c1baf9018ca0000000000000000000000000000000000000000000000000000000000000000aa0a1d95ad0e500f5e4b1bd149186814df18eb98e8780bf676e8f3db3a0f3face33a0404e955b4f11522f99577dfc88d0dda82da90992492b18491843775f5a1cdc61a09bb3e24e1534bce24e9896f3377327d742d6c1d430477b7ebc070c2eb64e3147a0000000000000000000000000000000000000000000000000000000000000000ca04c29a58e6ae8e8d5675a8f982d2b7b5003c687633919a622b92973af39bb0548a05a0dc5d4d49c845a7e5c8f30d3eb17f36afd4610ee030b6b45acdef0e06b51fda00000000000000000000000000000000000000000000000000000000000000010a0f2c891cab2af1155379e2cb5a591b3e1f3859d3ef1c231d4987204c1fe7ea115a0d6cd76e208ea80eb6f706515ebcfc15fc94f57f3e18452883d9478107143d407a0000000000000000000000000000000000000000000000000000000000000000fa0afa9712ae32b996e680ddfb579f88c5714eff15e4f29153eadd3decaad54ebcaf89b94b54ce26f2e30f64c5b684b141311ce138ab5e00ef884a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f8bc945cd0ad98ba6288ed7819246a1ebc0386c32c314bf8a5a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000002a0f09b457c15826396efb730bf67656e5debac76c904fafa6861ed5765cea4df44a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000000f85994d0d56273290d339aaf1417d9bfa1bb8cfe8a0933f842a0b17349740b669941baf55dc09d27353d5066f7515a585f533b40596bae334695a0577b913a3c8810dd10161c9ae11e2ee31042564c62114c83b0bc5d3a3e71b362f90228947e52eb9fadb02f95de1eb8634dc0b4bbd4628f38f90210a0000000000000000000000000000000000000000000000000000000000000000ba04cf2bd51af1a8ac56b4fb0e23da1717ba813b99917e5e36de6e3ae319a316b3ba00000000000000000000000000000000000000000000000000000000000000012a00000000000000000000000000000000000000000000000000000000000000018a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa02dee8fee0050f9b50254bb2dce2adbf1d1176c39619cfda08a9fcd208972e273a029cb8bd4e192d16f51155329ce8b0f5eb88a1d9e4d3b93ce07efbac9e1c4d175a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a04729effceb34e32ea7539c2827046bdcb467a191dfa169688430ec34d1dd2963a0ab2e97a75db32eb3b19136ac5fcb6d7a64d182e81eb81decf514e3d877434a50a04c39b3fdaf585b5ee5622d9ec0cb4cf2bc86694673ab95e5a63f084e37d4e9b8a00000000000000000000000000000000000000000000000000000000000000019a0404e955b4f11522f99577dfc88d0dda82da90992492b18491843775f5a1cdc61f89b94e2523740544851b599aafe5870c5997e5c8addecf884a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f884a0b1aa816c3c240e8935aa44133611887ed238c7d51f01f8b123b6f452e8272eb4a012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8a009d0a653d028a303e3445ad078cd9784c32b672ecd784e05dfa863f177744f2ea027902350b23dab8e343168a9c4efe515d63cf66808c513bd6a00ee103619205501a01099ee4dda8320e58fa87e38ad5c4766544c04e9254ece6d1a3c4ccc274ee2dca07090040021e1b7f9ccbc624e5da07f116d614fc01f09a9fff9486206f9ee979e", + "0x02f9015c018202678506fc23ac008509e5bc4ec683043206947a250d5630b4cf539739df2c5dacb4c659f2488d88058d15e176280000b8e4b6f9de95000000000000000000000000000000000000000014bdac5c38b84104abdb58400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000f6ab629ecafe852cb118ecfcb769d07be76ff84f00000000000000000000000000000000000000000000000000000000650d3bc00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c6980fa29a42e44852e29492268d9285d89c9dacc001a07c0b8eb74b0376c57aba5629769a2d859b374448e4a4af1e712a306abe80da3fa0288f7c9958856d9756d758412924b5c3be08b307bdac7e431e77aaad5d771060", + "0x02f9015a014f850342770c0085062c0faec683042bbe947a250d5630b4cf539739df2c5dacb4c659f2488d8802c68af0bb140000b8e4b6f9de95000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000005ad7881a995c530d519ca843bb1e5c61441c0f4200000000000000000000000000000000000000000000000000000000650d3bbc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000bcd657377d4086cc582b215294c3611b997ef1bec001a0e4c47bef5e5ea64705bdab7477c89e220a29c3402d17edaebef86894b36c8c1ca05ae62df6e69158e03ae480d2415bd511af7dd9612b64d87f4ec735a5cf294fc5", + "0x02f90175018203bc850271d949008504685640288303d0909468b3465833fb72a70ecdf485e0e4c7bd8665fc4580b90104b858183f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000080000000000000000000000000bbb34ffb832146d599ae08091b096d982c76a2e2000000000000000000000000000000000000000000000005b12aefafa80400000000000000000000000000000000000000000000000000000b7eeb4a764743c6000000000000000000000000000000000000000000000000000000000000002b9e32b13ce7f2e80a01932b42553652e053d6ed8e000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000c080a03c983e7673809a7272afe748c4242806f7830a2e2de3f3601c8f07b3240b21d6a02240632441b63caee132602f48cdf69795f449116ef6677dac7a8a05b598ef3b", + "0x02f901750182013f8501dcd650008504e808dcde8303ac91947a250d5630b4cf539739df2c5dacb4c659f2488d80b90104791ac9470000000000000000000000000000000000000000000000249e29cb37a9ce051f000000000000000000000000000000000000000000000000000432db12e2353000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000001630d8aff69591bc1e7e0226b55867e4587e495800000000000000000000000000000000000000000000000000000000650d3bb60000000000000000000000000000000000000000000000000000000000000002000000000000000000000000089453742936dd35134383aee9d78bee63a69b01000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c001a0b373e83ac5f99059fc700e20e7f2acfe059bfb57731d7d5478b2342101e93619a07cbd8831561f65d1629fbab4836bdf4216871925c7356ec364f34f1fbd00f49c", + "0x02f9015c01820151850165a0bc0085044f395ec68303beef947a250d5630b4cf539739df2c5dacb4c659f2488d8802c68af0bb140000b8e4b6f9de950000000000000000000000000000000000000000000000000009664a6852aa790000000000000000000000000000000000000000000000000000000000000080000000000000000000000000481104920a3170954144d97f0a38757ca92c928200000000000000000000000000000000000000000000000000000000650d3bbf0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000007e52eb9fadb02f95de1eb8634dc0b4bbd4628f38c001a04c7b5d7e2454abc29c2a93aa65d4264e7d678d7a0ca79343024803ef2523fdf6a0239df32468a6e2a8fe914ed76ba2d959d10aa6daf4d5b678abd99607ead4986d", + "0x02f8b10139849502f9008504b6f005708306cdd8947a1957ea071eddd490d3a5eda903eaa0dc76a1b880b844c83ec04d00000000000000000000000000000000000000000000001b1ae4d6e2ef50000000000000000000000000000000000000000000000000001b1ae4d6e2ef500000c080a0ee6bd76834fe37d3248dd7bdb36476036f459be43264d0a162dd2deff8e49e16a0096ad979fac82231507a3370f7cba185910575d39448656974545041dfb7df8e", + "0xf9015269850306dc4200830497d1947a250d5630b4cf539739df2c5dacb4c659f2488d88016345785d8a0000b8e47ff36ab5000000000000000000000000000000000000000000000000000a8e0c17312bfc00000000000000000000000000000000000000000000000000000000000000800000000000000000000000008b8eafa96fddf5ecc8e13f5c9668eb6d1b69e6720000000000000000000000000000000000000000000000000000018ac0d5e26c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000404d4a815ea854bc0666cee8041af8fd1add1a0125a01c14cccee71797a25705f50d74232fcaac27cce9dd776abaac6b4bc16603da20a073f8671fbc40d82219e604413e38e3aff8f72f7cd565d9fb6b3863d6012f51a9", + "0x02f908b3016184b2d05e00852e90edd00084011a49a094260552861d45681d7a2789ea29981f184aac43da80b9084412514bba0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000002696459e63520de63d10f8bffa89c1fbd0ab67b000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000002696459e63520de63d10f8bffa89c1fbd0ab67b000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000002696459e63520de63d10f8bffa89c1fbd0ab67b000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000002696459e63520de63d10f8bffa89c1fbd0ab67b000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000002696459e63520de63d10f8bffa89c1fbd0ab67b000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000002696459e63520de63d10f8bffa89c1fbd0ab67b000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000002696459e63520de63d10f8bffa89c1fbd0ab67b000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000002696459e63520de63d10f8bffa89c1fbd0ab67b000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000130f7fa60923711db8a5b57b1da930c83cccf494000000000000000000000000130f7fa60923711db8a5b57b1da930c83cccf4940000000000000000000000005b5a6fd70a7e7df8580331f0389e95bafa6c16f40000000000000000000000005b5a6fd70a7e7df8580331f0389e95bafa6c16f4000000000000000000000000130fc0d30181fd072d2d47f57e9f99f9db97f494000000000000000000000000130fc0d30181fd072d2d47f57e9f99f9db97f494000000000000000000000000a5b5408340fb28dbc20833af0a2fd28cbd39dbbf000000000000000000000000a5b5408340fb28dbc20833af0a2fd28cbd39dbbf0000000000000000000000006836f0fccb1473833c4e6a174c626afcdae441320000000000000000000000006836f0fccb1473833c4e6a174c626afcdae441320000000000000000000000005b5a6fdafa5ecf6bfef4ce654957abf4fa6c16f40000000000000000000000005b5a6fdafa5ecf6bfef4ce654957abf4fa6c16f40000000000000000000000009e2c3c4d1c69c1124a68ed427f1f8336e6001bea0000000000000000000000009e2c3c4d1c69c1124a68ed427f1f8336e6001bea000000000000000000000000a5b5408efc081bf3e475b4661993bccdbd39dbbf000000000000000000000000a5b5408efc081bf3e475b4661993bccdbd39dbbf000000000000000000000000dcac4d02bf15d84d87de85e7c3ef45632335d924000000000000000000000000dcac4d02bf15d84d87de85e7c3ef45632335d924000000000000000000000000ea2402baa40d3cb80ea47000f238ac24f72cc452000000000000000000000000ea2402baa40d3cb80ea47000f238ac24f72cc452000000000000000000000000dcac4d020a47ec66da0e2c23632d35df2835d924000000000000000000000000dcac4d020a47ec66da0e2c23632d35df2835d924000000000000000000000000e4bc15674dd27cdfb960eb1d9439ec796d2a5fa2000000000000000000000000e4bc15674dd27cdfb960eb1d9439ec796d2a5fa200000000000000000000000068d985eec63bd7826f70fb3add66a5c098b5368000000000000000000000000068d985eec63bd7826f70fb3add66a5c098b53680000000000000000000000000ea2402ba035899397f09fc91e61e854df72cc452000000000000000000000000ea2402ba035899397f09fc91e61e854df72cc452000000000000000000000000de06285d8a040612d0dbd05d4399f0a3dcbc1bb5000000000000000000000000de06285d8a040612d0dbd05d4399f0a3dcbc1bb5000000000000000000000000e4bc156b3576af8b257599923d810ee6632a5fa2000000000000000000000000e4bc156b3576af8b257599923d810ee6632a5fa20000000000000000000000000000000000000000000000020f5b1eaad8d80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014d1120d7b16000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020f5b1eaad8d80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000554a4fe826a7c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c629bcf4aaf2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014d1120d7b1600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000554a4fe826a7c80000000000000000000000000000000000000000000000000000000000000000000c001a0ab7d5cedaf8addf1751c2f6d2b580de1c01206cbd9ec9db3ff88b45abb4361d1a03102bf37fa598ccd40bd2462ef7afaa86fcb8e0005468d11730f8826bfe456ac", + "0x02f902fa0181ab849b4a5b248504840300dc8304028e943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad874a9b6384488000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000650d422f00000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000004a9b638448800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000004a9b63844880000000000000000000000000000000000000000000000004586c5c7355b6aa875700000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000055559d9b47fff7b7f891de11e9ef56654b42ffbdc080a08d3ceb25f1579ea7be864c88a06d5b3248d9c8b531250b401ecd5775ba75a0d3a0084a0f03552dfe5a19cea0219bbcdbd001d2e7018c9dccc6c14a73debe72106c", + "0xf8a955850424bec27a82ea609457b9d10157f66d8c00a815b5e289a152dedbe7ed80b844a9059cbb00000000000000000000000005a479d8b3c72821d41a9c802a492a832582d2c800000000000000000000000000000000000000000000000000000000000186a01ca01d03b929585ed25b52fcda511ddba993d5c33089a6979a91322979c84d719227a07eaf12e88497e5e0605ab09e79c5071d31b2cd4e722d8d9e4bbd361b9a458dc3", + "0x02f9015a0182024184b2d05e0085039c6900c68303e88f947a250d5630b4cf539739df2c5dacb4c659f2488d87b1a2bc2ec50000b8e4b6f9de95000000000000000000000000000000000000000000000000000001347e08055c00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000e0a01fdf17141ca25fcdf03e0549899da1f7c4700000000000000000000000000000000000000000000000000000000650d3bbc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000005041f018b4c130e32ae985edea8e76d2195001a6c080a010bf2f3323e4ab64986f19c242cfefa8f3337cdf51aefa6bd14c2162e0841d85a039b874de823db572e680d2cd2977947cd53e97cae3de37f28e2e2ec82be58d7b", + "0x02f904320149846b49d2008502540be40083057e99943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000650d422f00000000000000000000000000000000000000000000000000000000000000020a080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000014fee680690900ba0cccfc76ad70fd1b95d10e16000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000006534c83200000000000000000000000000000000000000000000000000000000000000010000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad00000000000000000000000000000000000000000000000000000000650d423a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041c51a446e5b38de3265e4aac64cf330db3b161068817c2528156883bf6d37974a4cccf0ba1de588cb06198574a5e078996a302c3fc44e485658a820a1e1ee34711b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000003828eda98d800000000000000000000000000000000000000000000000000000000033c38fb00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000014fee680690900ba0cccfc76ad70fd1b95d10e16000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c080a05c0b5c4fec450d7bdbad29101e73841a790a5ca301c216cf2b1e2fe4364a176aa05bfb1a25a5696803ba38712379c43348b0335781e677bcaa372387a63f0b27fb", + "0x02f8b2016285016a53e9ae8503cd844cd28307e76f9441c2ad4add42a83eb74701cc8b132501a991a93380b844095ea7b30000000000000000000000003999d2c5207c06bbc5cf8a6bea52966cabb76d41ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a07d54a4d6c40115cd4b473c549c4e3e777c07409ab76240af7a02c583c776ed8ba067a3dc9cb4d5de0388b4f10ab6ff4d16738fc7d8cadbea1dbdcd0be56c2fc1fd", + "0x02f901d3016385016a53e9ae8503cd844cd28307e76f943999d2c5207c06bbc5cf8a6bea52966cabb76d4180b901648ee938a90000000000000000000000000000000000000005535f8d310d4b800000000000000000000000000000000000000000000000000000000000001d81dec19f649700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000038400000000000000000000000000000000000000000000000000000000650d3b5e0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000041c2ad4add42a83eb74701cc8b132501a991a933000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000067863757276650000000000000000000000000000000000000000000000000000c001a0deb6157d8706b9e2b6c0f563880118a8d1af08b0ae0fc5c5143d08074fb91751a07d4d3ef21f4e10facabcbbf35ff1d6058333ba0309818f15febe38f02f5d4906", + "0x02f8b40183020a3c8501c4a33e8085043c98d81482ad0b947d1afa7b718fb893db30a3abc0cfc608aacfebb080b844a9059cbb000000000000000000000000de77e98e58dbb7e77e253c090843508eecb3d74d00000000000000000000000000000000000000000000000274a9edfd85320000c001a0393071e73830abb485f7c44cf466fa0623cd75dbf55aea004c4f1f8b459b82cea02e5b3fd2945a43df2e863267cb7ad0923f1606ec85019e566f6c9a281aadc2f2", + "0x02f9019201028432a9f88085033ff5448183046ba094889edc2edab5f40e902b864ad4d7ade8e412f9b180b90124acf41e4d00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000ed12c3837fa789b8bc37ffec8b2d19f05262396b000000000000000000000000000000000000000000000000048e7fb600addc0bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000001ce6fc6b5b56cd00e9ba034105888c40e78af8d31afb2146c9b06da5c504162b451c4c7f47a49bcb9f8065f95b39d88513111b3ae650f2bee3b831eeda243ba0320000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000048e7fb600addc0bc001a0a733075c6d25de1b3e400a40e908e7a3bee8027f1a0e055145cb0060a179da6aa01f5af45659a9f2c7972d590f3b7aaf9f8783d2847fbecefcc2d633d582dc748a", + "0x02f8d90102849502f9008504b4ef743283012bbf94f5c9f957705bea56a7e806943f98f7777b9958268802315429b2830000b8646ce5d95704a2e178341aa53fd0c0852851ce5338d293401da5e2101d4316304bfe656e3900b333e3142fe16b78628f19bb15afddaef437e72d6d7f5c6c20c6801a27fba600000000000000000000000000000000000000000000000000000000002688e5c080a096545335507fdbe249d1a93a4c7d8bf85ca933b4b22d137919d911fab7107590a00300ebb0895223b288c4dcc4486bd92f9503c947ce10128535c6cc7168c2623f", + "0xf8ac827b0b8502a4a6930483013880941a3496c18d558bd9c6c8f609e1b129f67ab0816380b844a9059cbb000000000000000000000000b02ed88986b74574650de87e8f6a578b1e2427ad0000000000000000000000000000000000000000000009b588922c49ec28000025a0c07fcabdae75efa779e9237bae6a42cecd95f20eda89cb106c6183934d38da6ea036dc93263ff67eeee085dc92497390199bc7b5e734d65931009992860b30fac9", + "0x02f8ba0182ed82843de47d0d85029346fa1e83028c5694fb071837728455c581f370704b225ac9eabdfa4a872c934b294cd400b8445173ffaa0000000000000000000000005c5d5202d8cd871614c86ee7586cf27f7ded92750000000000000000000000000000000000000000000000000000000000000245c001a0649da1987303cd516dbfe574df1107223df0ab5b828b9cfdb8dbbb3fe40c880ba02284a3e8563423761dc74f078a5cfec479936dd84aefd28ea91b1dc9897c5b51", + "0x02f8b1012484773594008502b96b6cdb8301117094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000cf3aa1a77fa8c221f80bd15f4d7a36186eeb7df10000000000000000000000000000000000000000000000000000000007270e00c080a0d5701426adcbf17f20353389eeedff7c30420dc3b95b1a105b42f67f45994f8fa0040e87ced08417fa84ca69d6886c06d34cc35655eecd745f70633553cc17884e", + "0xf9018b08850218711a008303717a94be6fee3756f7be3a0cd492059341cb5b77dd81f980b90124f01e063a0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000069df738dfc2d1e2ea3e1314f00000000000000000000000000000000000000000000000000801277b814c28a00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000020000000000000000000000008a9e6d160d7c0087121e40e398fa3f67a4598b75000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc225a0915fd2529c30cde9428210ce48f96ccc6ba1f46b2ea0a17bd50e2a0471b6a969a07bb3f7e47bb7f1832586634194c777af4c7b09390eb8ba8c0849d3716f6a2f22", + "0x02f8b30182014c84773594008502baa6aeb78301117094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000001207fc953ca19e470063a9d3c944fcd5509fdfd600000000000000000000000000000000000000000000000000000000b8c63f00c080a0ed29e0284c913d8c1fa3d249a7325ba87efd8e74df2e60922fbb8b21022c5c3ea06ff2aac75a35a5bcc92d437b8856d4123d8b53b02f7e597e046b495f341452c1", + "0x02f8b4018376feeb84773594008517bfac7c008303291894430ef9263e76dae63c84292c3409d61c598e968280b844a9059cbb00000000000000000000000019267f3000ad73223dd7a8fa9b9b5ce58c28712100000000000000000000000000000000000000000000011578c3544a26250000c080a0553dbd4c1d4227a24041d09bbb6b782b0b61502d4a5a6161694b2b59c3f237b2a07c155a16a056e8079870843155b5a9ce3d28bd8c2371ab18c35f8cf57bde7e93", + "0x02f8b201820d9c8459682f0085046856402882c992942960d71855a521c8414d29a27218efdb67c3418080b844a9059cbb000000000000000000000000781c876ce98abca880f304c5a3934f65e64302730000000000000000000000000000000000000000000002e2b4737ca62f6e0000c001a0b4c3620cb8b4fce3aff26c6016de8b9633530915ba752e1de440d69fb7d1b5b1a005bb710b536b214b076d430e03e9e360cbfa53da7ce7df02aad3a25b7f8e1d78", + "0x02f8b001598459682f0085046856402882b5f394b92e40c0bd1a135c5cb19ea98d2d729909ceab6180b844095ea7b3000000000000000000000000e1ce310e3cb20073ff25b1a76faa7e032f41cf7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a06a91c0e1442a2be601ac13be71946b026c0e9b60c7c36b8c3b595bcca611947ca05fce31d09568abc84ef55ac29b90c078b9518fb033d62ac5289a5e45174d5336", + "0x02f8d301821db8843b9aca00850430e2340083010323943506424f91fd33084466f402d5d97f05f8e3b4af80b86423b872dd00000000000000000000000072b83a114e3254849679673e97b2ea3bd9a3920a000000000000000000000000dcff7bdd67eb501f214faf41c9d596b53dbffc5f00000000000000000000000000000000000000000000000bcee26cd2632f8657c080a04a69ef73e530864823505230de965a2b356f98a73d925486f4f67d2b86f0c358a0533047aa4de7d29814677b06933138b74cdcd994b3d12199cbbd655e31724c9f", + "0x02f902db018205a68405f5e1008502d00f7c9983095d7f94c36442b4a4522e871399cd717abdd847ab11fe8887470de4df820000b90264ac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001648831645600000000000000000000000020561172f791f915323241e885b4f7d5187c36e1000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000002710fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe10b0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe2a7800000000000000000000000000000000000000000000002a1f12d4e0aeba9d7700000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000002811653334d531c09600000000000000000000000000000000000000000000000000465205e1b4d892000000000000000000000000560805d557eba6a00e5618e019a216efa47775d900000000000000000000000000000000000000000000000000000000650d3b6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000412210e8a00000000000000000000000000000000000000000000000000000000c080a0afc9c3292b7fdbbcd16941d4fc65d344e0d302943e2a59490593b838ef1b1293a05902fe30e723dce269bb1f0860c70185af61a46c8f90577ded2d63ad1e9c61d4", + "0x02f8b20182019f843b9aca008502b4998a8982f35294fa1a856cfa3409cfa145fa4e20eb270df3eb21ab80b844a9059cbb0000000000000000000000008cce8709a5fbd78a27aec1e7174cc5276fcc68fa000000000000000000000000000000000000000000000cb8e39d1bd0d55c0000c001a0eb27acf651a0ac3afdcfbbd8a8dab0c857f93c993dee146e1dbb0691a2aef6aaa00d8fea3ea755e14ccb60a96ca51758820e7eecea035423a14d6e910154bdda7e", + "0x02f8760183021d35847735940085048623a528830329189445e7d523dcf83269f8b8586655a966a733fe1b38870e4c533842e3c080c001a02572c1abf8481b58339d759464a03efbc5d1cb131bf6a307fcb9d8f6634977baa0488ae4caa36e4458e9691db0cc546761a6fd6012fd34a26f87203b0cc7b6959a", + "0x02f8720101847735940085026846008482520894dce92f40cadde2c4e3ea78b8892c540e6bfe2f81878e4be056c093e080c080a0cab09875ed6df6893ac90891df5252bb0063bdd5b179b3fdce5e403b34d46d2ea0239c71539b9a304b712197b1472c248dcb0e52841fc4aa5887e288fe567b66c9", + "0x02f8730168847735940085026846008482520894dce92f40cadde2c4e3ea78b8892c540e6bfe2f818802a6c88a9741b23880c001a0c683b1ed551072e7db8937ad58dd78b4ed01a17e0b2bdd1efc2bd67a792f3828a0261297f9861d626d3ab4b1bc70b55252a708a308af1d2a557215f086c7149de6", + "0x02f876018301a9658477359400850459566d0882520894b2943be603e11b493b20411692a2e2efbfa82aad88010fc90b84e4d40080c001a0592edcd0217bc3c65ef4d35d9c9da691e50489a409e7c1b51cbd6a309477a78aa02aff224db05486243416bab5f1a876e789ec7ac6d443c55ab201572b4e49db16", + "0x02f877018372bf4e84773594008517bfac7c0083032918948745d208d684a61a5023b9a96c1f28890d20a064880558f9e74f19580080c001a07f9b8ba8a93d671036ddc7f70c72e7f78d90fb3b512f16d57e48b26ec8d4c0d6a04987db6930bdca36415a3e3394d975d0d62631d0433cc7ac4f38ae3163254180", + "0x02f874018201ab8459682f0085039c6900c682cf0894cac0f1a06d3f02397cfb6d7077321d73b504916e872386f26fc1000080c080a053d7a48f67ef1d604f88d930ce6e7f9b3aa5259292a66b23dbf2b331fc789967a040dfc3dcd1a9009e93987ec6cf0cf5e7632dab5a09138211aae44f324a5c8efa", + "0x02f901b201058405f5e1008502ceb580f58305f0e2947a250d5630b4cf539739df2c5dacb4c659f2488d80b901445b0d5984000000000000000000000000df98398d12eecd6275ff3c906686ff7aabb4513500000000000000000000000000000000000000000000001ac42dc434e9683659000000000000000000000000000000000000000000003c49e9764603dc9f33960000000000000000000000000000000000000000000000000ab9aeb24e319a9c000000000000000000000000484219de75a791cd83d613e14408a433848576f600000000000000000000000000000000000000000000000000000000650d46070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b9a9af484bcba44a3085ac4180e942823d5060a722e9b7b5802ff83ae116cc656397a4bc869fb7ce4ac178414ec2fb454c588ff36e25363adda87c8e8a6301bb7c001a032255120bf16f7ec8ad6cc0d1a54f90f32a3c45e54c05504e97c9b46594e6ac2a0361d40030b950ec6b9e571ef065b46f678e6a03732c3469f1c6fc215d8f2cf77", + "0x02f9089e01068405f5e10085025048a8558304f81b94def1c0ded9bec7f1a1670819833240f027b25eff8852d9b35e9d150000b90828415565b0000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000052d9b35e9d150000000000000000000000000000000000000000000000000000000000022483477300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000005e0000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000052d9b35e9d15000000000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000052d9b35e9d15000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000002360b0cea00000000000000000000000000000000000000000000000052d9b35e9d150000000000000000000000000000bb289bc97591f70d8216462df40ed713011b968a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000533f6f812421b9271db6edf0e46fac24ff9d6aad00000000650d3b760000000000000000000000000000000000000000650d3b380000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001bfba32863e0e5c402ddb4184ea18bf566eca381c20c49835abf88888deb33f4636aa077425d4e30877a69365a4e27f5f5c57c254c4348123a9509d9e09f0f520000000000000000000000000000000000000000000000000052d9b35e9d150000000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000008c8f51000000000000000000000000ad01c20d5886137e056775af56915de824c8fce5000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000000869584cd000000000000000000000000382ffce2287252f930e1c8dc9328dac5bf282ba10000000000000000000000000000000006937218260a6fe77fb37f7d4df81cc9c001a03bb4473cc91acdff066f03c76fcf96aef9bd697c23df960da88042d620e0f0b6a002ca2df45f6862adfaaac674ae1043d54dac16fc46c18ecc5d6d6868fc425e1e", + "0x02f902d4018201ee8405f5e1008502ceb580f58303f8e294ba12222222228d8ba445958a75a0704d566bf2c880b902648bdb3913e7e2c68d3b13d905bbb636709cf4dfd21076b9d20000000000000000000005ca00000000000000000000000001717b7ee44c3723b4803a11ee843b697ce6c10300000000000000000000000001717b7ee44c3723b4803a11ee843b697ce6c103000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e7e2c68d3b13d905bbb636709cf4dfd21076b9d2000000000000000000000000f951e335afb289353dc249e82926178eac7ded780000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000006e7491a814db77000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c6ae2cbe30784f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000778b18beaa1367ec080a0050b4419f0b5d0f3b5f401b140005dd239e3942ca214489de4dd3a6f9e813bfca00d2d58a2513c3e3a313271e323c582501fa6551b239aa653f074f1d054f3ba19", + "0x02f874018209ff843b9aca008502c6aedeab825208943f4833b244c7dccf034da7d733c3a485f0c121cb870254dd702e280080c001a0682d91b2afdb8fdb79e9e557824eafafc13cbc3938b59f9e2069271e0c63b46ba038b4ea5e7fe315e2fe7d7ab753ea7e71426bad774e63ea74cfe05ba1c228ebf5", + "0x02f877018309113a843b9aca00855d21dba00083033450946fddb91b1e3cacec85b8b8c568e950744a0c9037880de0b6b3a764000080c080a0a159e0e7479d0ce98decae732245a2cf4ba9895fd6599c05739bfb2d0c0b1777a002f81af2bce29a0eaa3bd5b2a2110681aeeaf116956a0bebaaa1c4da430e5250", + "0x02f876018317771c843b9aca00855d21dba0008303345094605f78cd9fd82433dc1fd9c3b331aaea445708e08723b8084e6eb40080c001a064278ac9b8eaf6ec3a6491403dd3360ab49396d33520c8487200ec41095cf979a00cbc0c2ab4910246ac613f1e80e8bd946fdd377fdd7089c3348a46e415109129", + "0x02f876018317771d843b9aca00855d21dba000830334509469e28c8d85d25ba1cd0544e76bcd6d24fd4313a8872386f26fc1000080c001a06803dec8fec9bd1a9a833bde86397f3fc9209fe6786a45cd122601abad8e7a8ca0690337d320e450419489bc6e5bc1547e84e69607707d22a6e774e3f43739f555", + "0x02f877018317771e843b9aca00855d21dba0008303345094ab477e5d4cc2d975ae082be6252813d8146eb77f8801305350ef75c00080c001a0fb61c6e7d898b87df03cb61b2a95b1ecdef0501fa5b28edb9a933ef52181a167a03d66b75f2bd8855fc0a9419b1bea646e946d715ef49199e8690c415d6644284b", + "0x02f876018317771f843b9aca00855d21dba0008303345094fba5a6c47c5477a48e151f6e0d7bd00b025ad096872386f26fc1000080c001a01617cbe439398443fa1ddf8db7423cf96b210fa744a9d557fdfd127ba28dd793a02f34a3b89a0265ec8b26f3318ae2c781be64081f57a4207308cefd1f52ad1615", + "0x02f8760183177720843b9aca00855d21dba0008303345094601092bd5dca1d80f7ab81e858a001b699f3360f87b5303ad38b800080c080a05a8be1066f4ad8bb8d013f5d8671cd0cdeebc3b58ece98d1b294be1a8d062a44a0136be11303edf7cdebbe64fb287a09bcd8fd27a84ed2ac4759cb3753f8115734", + "0x02f877018303de21843b9aca00853c89352800830186a0945af99d79d74a2f14e7f71af444dac47ab0f8edc188025b5b7c3634602380c001a0f2b74ae6aaa3aa430b91b952def69c7f86a7be3d7426ddcee2d7128c47b4c60ba029a970d3b242f15c5f4041f77add145458f65dd53ad226d5f6388977cd385e31", + "0x02f8730102843b9aca008502540be400830186a094c902fc03248c7024456cd2ae6f21eb804495bcd787d8b72d434c800080c080a0e2e167824f28238ea5e48bd18d94c3872c1b2f891df1f968c08c34efa6c461d4a061e2793b77dcd67d6370ebef6baacb8600b08574ebd25342df115603ab775fca", + "0xf90193808501fafa22af830247759432400084c286cf3e17e7b677ea9583e60a0003248804e0bf754f744f00b90124eb6724190000000000000000000000000e8abd54de0a63797f59a9bd150ca91088fc242200000000000000000000000000000000000000000000000004df6dc79989000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000b54a3000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000e8abd54de0a63797f59a9bd150ca91088fc24220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026a071f36d2a326722e5b7fdc463f812f58ba4d65eda867e1cadc41df67bcb13a73ba00830771d975236296a0ed54c3b062cf8305f43b814f54b7f9ad2756a1c621a04", + "0xf86a81ec8501fafa22af83020fc99437476750a31266557609212e9707895e06e36ca480841f83bf4425a0bae5b977cecf7b90264dec4307e611d4305ae02ac714179968f9357ae421b5afa05985a78500bbc35b18c6914b47a682a24f5c4e4c0ad7afef7a32155f7c199676", + "0x02f90574018202b38405f5e1008502ceb580f58303978c9417b5a77d6e7cde0e8d1f59bd1edb26d9badf6e9e80b9050487151b880000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000117f385a0d4aec94000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000003b06bc7b205f1a827f6504244db7a8f5b0bf7dfa00000000000000000000000000000000000000000000000003c57c4c7d3bcb5e0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000cb83e1143173140c8a2314ff90df5b68574a6c9bf5b6bd23f1ca6a0c04faf71e675fa4a7d9eea857686f38887307c74f310dd9d5d7ad0f03f766b4143974dfa099632d0c373e0c1de94476e5ef74b6bfb7890f153d65fed04e17ce6f7a071e9a271efa2ab9f7d8427716e425df8119373c5e55910575b9c5a3b232dd75a647b185704492b0bfc912392ab9748398d6590290c9289d49cbe4f9234d2da2d483f7aef656ccb10b9f033832ec9c9985711c1edeed643b652143ed632b91fbf5a26a99bfa4f414bf756586214ae1629b9472d84611e9261a117cc7550c12269bc8e7bc87d19f9d86a184ba374c1b266062d4482c39f1865f634c74309d3afb0734cf3f291e8709c6caee62ee9e873506d7c640761259dae43539a776213b8642f7bb0a226e0fb9373a97a95565aaf5f2982abe18d9a20a2a00c6ee435dc4d0c9acc21f89de707b46bc7636728f0d0c1e1dc032091d72eadae6455bddeaf8ceb6f39ef2a0d596396598f6876744405716f180ae880c5f158098efa1360f85568da00b1000000000000000000000000c55126051b22ebb829d00368f4b12bde432de5da0000000000000000000000003b06bc7b205f1a827f6504244db7a8f5b0bf7dfa0000000000000000000000000000000000000000000000026d8e645dfd3559940000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d847b709d8cf1ce1bfcfbd95b61af94558dd54972ec8aa7b4fdcb0092a8a3e8523118a915a1557371cf10f5c8dc24a60fea45f7d1d1b3350162e952dba5bbc259b4e1a3edb86654bc8f8292a95ac01b8b581f9ddc5e632d4c57f4c345380686d7e11ae992e09634ea7998437703fdc39d512ed56b41c43d1b672d9ddbff2cb9132724ddfcd6fd2c1a1db26f45de6f271d02d7a48d92a4b0c50ddddaee7f6d6cfaa1635f5e826379d2afda090ec54c462f5cb22a66cccd3252132f2771c0f2e38b6326cafe5ed2c21e287f1cf5adaf409e62b4b9b2d3459d9b70a0708f919b55cd1d93cf68330451403808e0bca32236fb54c5c33f274b6c4b151b9ad77c970a7cc8c1a3f5db80adde3acc78401a26e94eae5d51e672083ca6ab126a34ba2955f03164bcfb9afbbb86f2fad7153fae146b396b7a402e49c5b954cdf3c56c4969337b970128cd940cdef8bb9ad944bddb6077db30908b48bd26054273916895bd008abe7f481a8aedddab03befb792704804cbe6a51588fbbb0cc38127a904166338c90b1fc0319ba61d5ba86eb9737921c05509afff5f27c9233780e9881b117bdc080a0e76e6674393dcb18e1448fecf3d10fcb44fe68a3eda7d30fe9b91956bce9e015a01801e00a6d848a81c471a563b9acdaa664f4c6a638f7c2be37186915a9739ca5", + "0x02f9011301820fc98402faf080850212f12e1983069bcc9487870bca3f3fd6335c3f4ce8392d69350b4fa4e280b8a4a415bcad000000000000000000000000ae78736cd615f374d3085123a210448e74fc63930000000000000000000000000000000000000000000000005a0d8f1eab8280000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014b30b46ec4fa1a993806bd5dda4195c5a82353ec080a0f7c36d6285912b8f627c437b18d009a67183870d8ecf0fc73480f4758613008ea06ac686e66613a7dabc54502ab69fc335406d0d6fd2cab9a74b47097c55174d0c", + "0x02f9043c01820c568405f5e1008502ceb580f58303cde6943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad88012dfb0cb5e88000b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000650d422300000000000000000000000000000000000000000000000000000000000000040b080604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000012dfb0cb5e8800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000012dfb0cb5e88000000000000000000000000000000000000000000000b3cc654d78fe95e73ba70600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006982508145454ce325ddbe47a25d4ec3d231193300000000000000000000000000000000000000000000000000000000000000600000000000000000000000006982508145454ce325ddbe47a25d4ec3d231193300000000000000000000000017cc6042605381c158d2adab487434bde79aa61c000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000600000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000b3cc654d78fe95e73ba706c080a03d105d8ca3dffbe1f993d1962e60df96904976af7666274e6b72536ed06eabbfa060efdaf5966babcbfcf7355af6356f4563e15e172cfb8e6a27db596c292ecd9b", + "0x02f902fc018203cb8405f5e1008502ceb580f58302c93c943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad880140c7a6f6948c9fb902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000650d423b00000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000140c7a6f6948c9f000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000140c7a6f6948c9f0000000000000000000000000000000000000000000002f1024c33a47334524b00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc200271020561172f791f915323241e885b4f7d5187c36e1000000000000000000000000000000000000000000c080a0d46cd893e8374cfd37b258f92d666499174bc4206d6490d7db55517f9954e0aba0256038e0d6f705a1a6f8c155eaf1051fc4736cf90a4e3b128bdd7c55bac9a63f", + "0xf86b028502125f613d825208943e180d55386f7fe1441c0e0d7b1b79b768eef31f871550f7dca700008025a094572925a303a4831e4fef20210cafe26266fb97688ac02e5a9c8a70b4966fd9a01cb943314ccb59045804c15500f57e04e3875228a63cbd548cde6369d66a0793", + "0x02f8b901028405f5e1008502ceb580f58301c9ee94ae0ee0a63a2ce6baeeffe56e7714fb4efe48d419881bc213e3cf2118a8b844e2bbb1580000000000000000000000000000000000000000000000001bc16d674ec8000001ff494ffcedaf5691d5d737fbfd8a8b1fcf6f04dd096799dd59e016537b4a3dc001a0f7039fa4032a12cb3d5a2599e38315f8217b2f8e1cc3ef15ad34881a01f1097ca06244f87e4541f839a97ddd86f285855254969bb0731eb4109476a3c2f8831bf5", + "0x02f8b801018405f5e1008502ceb580f58301c9e294ae0ee0a63a2ce6baeeffe56e7714fb4efe48d41987104843555c18a8b844e2bbb158000000000000000000000000000000000000000000000000000fa1c6d503000004bf4d8c999b4c2df6432edd5d615f6d0929ed7bfc6d082144e74e8d6c917bb2c001a0f282c15d1cd33b9e9e3270d9505545dfffa02362d32c1630337d3916db387affa0605452016445e413a0c534c17cd43808a356d4276a6e5cdd0dec8b1ffc4b3d21", + "0x02f8b801808405f5e1008502ceb580f58301c9e294ae0ee0a63a2ce6baeeffe56e7714fb4efe48d41987242d6ef01a18a8b844e2bbb158000000000000000000000000000000000000000000000000002386f26fc1000001d31527f66aa942b93e2276f98db82099fbe704edca8df182800d771db456f7c080a07b4ee84124626997bc8a9bd2e253a546c69812f2ffd0a8c049b2f56a06c907b6a039fd8216ab2f6ad53dcfb02fcd0031472e3bf17ae3ca23b04205f4dc1e3bd59e", + "0x02f88f01298411e1a3008503936aa551829ab394c02aaa39b223fe8d0a0e5c4f27ead9083c756cc280a42e1a7d4d00000000000000000000000000000000000000000000000000b1a2bc2ec50000c001a0b157dc7a49f31bc8ec7622051e0484c5cb71d2ab262946e816931850d333e86ca055f477aa21b1890fd28451945c843613f5830281079f8d3e1b02afd957b77b59", + "0x02f9013101028405f5e1008502ceb580f58301d7d5940000000000664ceffed39244a8312bd89547080380b8c4b510391f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000416c900627e982831c8a4026c3af1a44415170c2ae9241abf0ecfea9a4c9d62c9a1e3b7ca03456ecab60a53beec75011810bab14580efe9397e52851f138eb1e8a1c00000000000000000000000000000000000000000000000000000000000000c001a049d132b84645a86e88c15f29d92637f8e6b934ed5a0cdacdee6bd734769f3ac5a0079c35ded64a74cbb12a638505dcfe6c4e1b0de90e7b5a975f5b1f19cde64f17", + "0x02f8b101348405f5e1008502ceb580f583021b3a9406450dee7fd2fb8e39061434babcfc05599a6fb880b8441c5603050000000000000000000000006a79acf27a5a7eb7a94ffd34be7540e34b216a7d0000000000000000000000000000000000000000000000000000000000000064c080a0747ff3e0ca333bf7fb2b045888aaa619135e9a7a18f771c2ac62ffc2d793635da048cde2afe81eb019f520cb86a8469a6168bfb9ecdd52c77d4032739e8549a563", + "0x02f8f801018405f5e1008502ceb580f5830183e594d19d4b5d358258f05d7b411e21a1460d11b0876f87adf0b4bc3365c0b8849f3ce55a000000000000000000000000be68ef12a001181f9ac477efec411029cffe1add00000000000000000000000000000000000000000000000000007f2cb64425c000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000c080a01882dcc7b988693da16b43e59079f93d4da54eb9b2be5cd71cc6c47e24589d29a018528b1840bea8aa7e24912adee3d7de376eb84df7ec501e1263d64e9a5f929b", + "0x02f9013801108402faf0808501f3cc49d28302acc0941eb73fee2090fb1c20105d5ba887e3c3ba14a17e8701c6bf52634000b8c4fa2b068f000000000000000000000000d2bdd497db05622576b6cb8082fb08de042987ca000000000000000000000000000000000000000000000000000000000485a0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000037ce8f01b71942e0dd12e81ebea73dcd4e1afb70000000000000000000000000000000000000000000000000000000000000000c001a04b9a337c9bcb9ef9a9271817abc644b033613af4d8fb902f01e30e59b2a69aa2a01b0a0fc78a641c27dd48a80401a9c9db6062a28bf2ce417150ce351e1fbae103", + "0x02f90138010e8402faf0808501f3cc49d28302acc0941eb73fee2090fb1c20105d5ba887e3c3ba14a17e8701c6bf52634000b8c4fa2b068f000000000000000000000000d2bdd497db05622576b6cb8082fb08de042987ca000000000000000000000000000000000000000000000000000000000485a0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000bae146ad179cde9b8d6a512687503ef8746b79ce0000000000000000000000000000000000000000000000000000000000000000c080a03abfcfa49081cd0db4e6daf97c9b456eee4ac7fbd3f3dea5dfd991faebef3dbea05a29a607d7b3941e960f011f03e17e1f19a01b2fd06a8c2b1116eec8663765a1", + "0x02f9019a01018402faf0808501f4add4008301f7789432400084c286cf3e17e7b677ea9583e60a000324880ac3347f23902f00b90124eb672419000000000000000000000000d4254e71937d2fc36c8679a911f62b1aeeb320430000000000000000000000000000000000000000000000000ac1e2d16da4e00000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000b54a300000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000d4254e71937d2fc36c8679a911f62b1aeeb3204300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c001a03a6a7e95d52947ed00f9c23543a5a6d782208802b6c9e955aacdf877d2773bc0a00ef75c8398317269241f31e9d4cb6893bedd47052e3a461cc34dc27d9051b02e", + "0x02f8b801018402faf0808501f757435c8301c9ee94ae0ee0a63a2ce6baeeffe56e7714fb4efe48d419871aeee3cbde6088b844e2bbb158000000000000000000000000000000000000000000000000001a4a42c3568000034c3acea1ced1cc9fd27ea3ad5a9388b8061e5eaf70d855baca46f127cc93a3c001a0ceae79abf8494af8bc6c155fd2a462fb617604e5f5cf5bdc8bc9891f6940520fa05832e9d7053f35ee54d76fde5982e2d919c2b7ab4e0e064e3bc389614e6746e5", + "0x02f9013501468405f5e1008502ceb580f5830120b19487df0306f147e752805261156d5a00d912786b1880b8c8f242432a00000000000000000000000046365df48693de2bf9da6e7e13f84b96689a05dd000000000000000000000000098c19790299f2704c4306ae58aa0f4bdf7e8ad00000000000000000000000000000000000000000000000000000000000000056000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000360c6ebec001a06ba992260842c6b6fb79dcf8f09d978276d08f8c1387384eb1524e07544a4ba3a014fff713157d371432127c390119a51383294c4eb4d66f69bd28ebf72a070e73", + "0x02f9049901078405f5e1008502ceb580f5830120329400000000000000adc04c56bf30ac9d3c0aaf14dc80b9042cfd9f1e100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000009e17d5748636fb9440eae5ee5504d4e902013457000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c0000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000650d381c000000000000000000000000000000000000000000000000000000006534c51c0000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000c7d1bceb8ab790d90000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000071d1e9741da1e25ffd377be56d133359492b9c3b00000000000000000000000000000000000000000000000000000000000013dc00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0bda27d97a80000000000000000000000000000000000000000000000000000f0bda27d97a8000000000000000000000000009e17d5748636fb9440eae5ee5504d4e90201345700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062c3f3e4c180000000000000000000000000000000000000000000000000000062c3f3e4c18000000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000360c6ebec0809f7c8666d4d7a13d2030362ff414d41c09f15d3d042bb2d1563b1f36765967d7a012cc4e0716be4dbff5ec448f72dfe824c4fab0e87a0aba3407546ff55ac77ee6", + "0x02f8b101118405f5e1008502b07a01c083013e6f94fa11f91aa636ef5b0cb62597a0fc49e859beff2380b844a9059cbb0000000000000000000000001866ae7c471022c5551e999c8dc207a56ce323c6000000000000000000000000000000000000001a8c9d0f39bb51ae0ada000000c001a0115e50b731e69007fddc53aea34099d045788bffbe288eb01168eae9600ab0a2a05e0b7cf1571a9722136576a25420dae3e12e0af46adf1e69ed72db1cba89e44e", + "0x02f8b801808402faf0808501f3cc49d28301c9e294ae0ee0a63a2ce6baeeffe56e7714fb4efe48d419877a25590bd96088b844e2bbb158000000000000000000000000000000000000000000000000007980b80351800005e14eeec8882ecd790083b12c4c2ea86b632e79747b63a6689dcf2d787f3bc9c001a059672fb40dc32347bf98f5bc888af7017211bf329affb2f6dfd8c752ff4d05c1a00f8875985194ac2680a987c64a349821bfe56d71efec0ab97bfa2cefd2614ed3", + "0x02f8b3018201eb8405f5e1008502ceb580f5830132fc94876a76c80b32e5cfbb27fd840a1a530ef828ebec80b844a9059cbb00000000000000000000000093628ac572b92d5561ad19446761394fdad22fc100000000000000000000000000000000000000000000010f0cf064dd59200000c001a0fad9b6f6e14d2cd3d10518ebfddb216209586add598416dd053388826fb7962ba03bbba27f988ef668cbb4dcbbf49b1fa6e140e4bb9c18f851a38b1c8083ee2c03", + "0x02f87301048405f5e1008502ceb580f58301348894ca1de18ab658d8fe3439b538cf361b30c500d02387208d9273d85a4e80c080a0eb96e050cf314770227a4f33a669d2aca841ea3c890c989650720405c7d22469a0342aecc7158b4e077b0ca44530f5dfb0ed66056938617f1dfe39506ead93539e", + "0x02f903d201078402faf0808501f757435c8301bec694d4b80c3d7240325d18e645b49e6535a3bf95cc5880b9036408635a950000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000b58500000000000000000000000077f801db98b34b03d4da3dbb2ed3b61258e62f7800000000000000000000000077f801db98b34b03d4da3dbb2ed3b61258e62f7800000000000000000000000000000000000000000000000000000000013c9a110000000000000000000000000000000000000000000000000000000001144a070000000000000000000000000000000000000000000000000000000064fde17a000000000000000000000000000000000000000000000000008e1bc9bf04000000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000010e549f3fd0cdeeff94c4a7d5348cb0146fb3cfab2062a0ab9ce95f8b69b14d1aa8e858cad3c5b8de18ddddc3cd7ee5e445c871dd9c2b680daf181172b6d30fe5cd9ead1f5c897a2811eb5a75d91fa0fde64e250ee86399f092c2f28432b169912890589a222e30125f94fe0ecc62ce6a64a55173ce05961f0082ea3cfe540d267f70a5729dbb70cd0e90a619912cf09fb37dc7f05c82e49738dd947c42038ad236ae9f5506526e51bc67795a8622635072ff71d508823ea78de1c905838d633f7649c270d85cf1fa6e686975513d1f7b4c2ead0d07524b39062971e29ccfd059c0fef6a8d93dc135030919d239ebba31bcade5c84a675ae01f8c11eabc66c377ae604865d1b9e763776b41044a8e922f8a20d24dc67169a6c4d24b4c8d2565329dc405b0ea72b2fa26146cfb479acd302fc8e2f49cd2dc7d239eb55f77b5add227604ae62fa7ae8bde0b15be58c0296febfd0bb5a88d8cba7d5b66029eaffabea0000000000000000000000000000000000000000000000000000000000000000f47f4f4df7da36596545f2152e25f53ab42298f7f0654416b1aeeabc340bdc8b0330e9dc43a98d1a70a990f7a3754ede2b7a64de2df85ab95577ecb4fb6d4d990000000000000000000000000000000000000000000000000000000000000000381c1afe39558ac38a213df9c4b61f4bd79ce80fff5dc5ac773715cb19e3b9be0000000000000000000000000000000000000000000000000000000000000000c001a0466af3380fef0d5741fe8484f3940642533fb69968afd47987c1785138550473a023b2a9f07bbdc45b093b362c2f80fab216f086be63a4183768ea12b82a6f1da1", + "0x02f894011a8402faf0808501f3cc49d283028d1794de9d2181451620bac2dbd80f98d8412a6da60fe580a8efef39a1000000000000000000000000000000000000000000000000000000000000000372db8c0bc001a092617c3ccbb9ace9d815cbd079272ac41bd466c696d3aa375d1f3174de56858ea07cc7cc6ddca2b470d4dfa2ac5a58b71fa49d20b60bd39b46f048c041def789fd", + "0x02f8b2018201e48405f5e1008502ceb580f582b4969496610186f3ab8d73ebee1cf950c750f3b1fb79c280b844095ea7b300000000000000000000000021dd761cac8461a68344f40d2f12e172a18a297f00000000000000000000000000000000000000000001041cccd61fd4fc220000c001a0314ab6d563bd638aee7fa43d1bc4d4ee2417fa5bcd8e2b181eb0b2a386bc3b7ba03112a43fec744b462831c7409f9b5610faf083ba5a418cd843177eda3e6b7736", + "0x02f8b30182065f8405e69ec08502b82ea800830110b9947e52eb9fadb02f95de1eb8634dc0b4bbd4628f3880b844095ea7b300000000000000000000000000000047bb99ea4d791bb749d970de71ee0b1a34ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a0edbffb0a196cd02dacc675918736f76d8f4b78d3e2b5b82f57b21852324779e5a01f3166b6dbfca7a56c40e5558f102b42a61be892045b997f218f30c440ff2b22", + "0x02f901160182029b8405f5e1008502ceb580f582ec4e94f4b84cbeeda78c960eda07da4ae8828594ea515380b8a8b88d4fde0000000000000000000000002725bc53a2f792d4fff5397092ad631f51700aaa0000000000000000000000005a98db5d98a9716ec48012c364d42768d7b1e243000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000360c6ebec001a061e295684da6c6c058934d827e9cff65d34356ccb63a4015076680b404c871d0a0169facd30968f911a233caff684db10bdf371264a0b51eaf93d590f91705c3ce", + "0x02f9035b010484010fabe385023c3b4746830479a294881d40237659c251811cec9c364ef91dc08d300c872386f26fc10000b902e65f57552900000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563546656544796e616d69630000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000023375dc15608000000000000000000000000000000000000000000000000000000000000eb7d1f000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000004f94ae6af800000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f1915000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c80502b1c500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023375dc15608000000000000000000000000000000000000000000000000000000000000eb7d1f0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000003b6d034006da0fd433c1a5d7a4faa01111c044910a184553ab4991fe00000000000000000000000000000000000000000000000000e0c080a03e307cbdd556823f6c1e62e32b4968deb6fdd1d572cbee7eac07411ede411e3da05fa59938d2dc7ae668f83e6f16ba330661687d6113efaf0f81b5472f7b5cf17d", + "0x02f88f01088405f5e1008502ceb580f5828caf94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc280a42e1a7d4d000000000000000000000000000000000000000000000000000c6f3b40b6c000c080a0e34071b9b7a00001e33dc9ece3c868e1eefb21c2b0d210cc7b0f4670dc622acda05e3e16c226c7fc252dcd4d1d6112211d930e99aa04beb82413912069249f8dea", + "0x02f8b701058405f5e1008502ceb580f582701694b584d4be1a5470ca1a8778e9b86c81e1652045998727147114878000b844e56461ad000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000db4af0457279effffa5a4be6e3b941ea240d8f9dc080a0d752b19bfbb31c0cadc48022d4a9d1426fd17e849c1ca4b3a87c4e6188185fc7a07b5a012d8fb7b37e79bc7c0633a87110eef72166c9f7dca71b358f111b9c3c54", + "0x02f902fd0182026e83bebc2285020835c4b68305221094881d40237659c251811cec9c364ef91dc08d300c8810a741a462780000b902865f5755290000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010a741a46278000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000001c616972737761704c696768743446656544796e616d696346697865640000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000018ab2786a7200000000000000000000000000000000000000000000000000000000650d3bc700000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000070e75c990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000108794965da6c000000000000000000000000000000000000000000000000000000000000000001cf085811d0d1a14f1b4da598717dfe9f697e8d756b8f2386172102f3b32cf95fb13679fb740c53e2110ae831a5c9668246d7fe3d7483afd30c671d24f30fc0e94000000000000000000000000000000000000000000000000001fad0e04d14000000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f1915000000000000000000000000000000000000000000000000000000000000000000afc080a0af47adc6df9c8da3b40abfd6a9fd576f52f30c320f92f4717327adef91df066ca02276054e598f1998d4d8836c93f5956b6147fca6a3d437a04e88d2210c54c6b3", + "0x02f872013c8405f5e1008502ceb580f58252089437adf7b1a95a3309fbc58f80320d32a5b72caca287a327cb389b310080c080a0562adda6d257d3c47a4d34f4f94eae088e0e0ba833507f7741d4d45c0fdacc19a0389bcf9af90f8620c3f55fec3ef29ce5b08e576f842db64a390c1fc58e434e94", + "0x02f87201128405f5e1008502ceb580f58252089423392d66721cf9e8c23e346139e81ccad62b92e2878e1bc9bf04000080c001a002312d85c66cf17b6294db8c74b55534c187740323c440dafb5c744d7cdb3f76a048018cf5276bc2caaa3bc1d60d14a9ee998959a7581063e41685599e81ec8d74", + "0x02f8b701038402faf0808501fc8c382a82701694b584d4be1a5470ca1a8778e9b86c81e165204599870221b262dd8000b844e56461ad0000000000000000000000000000000000000000000000000000000000000089000000000000000000000000445fbcdfef289f7912d28825edc7bfb74f419e5dc001a089b4cf16fab2259337030947f546ac39c34242638c6226ead7037fb8ba943eeea03e0682ba6675e121fcf5760f3458a65cf57b44f1bb12a11520f2f1e29b7d3cd8", + "0x02f872010c8402faf0808501f3cc49d2825208943780f6ca38dec5a83edfb8826486fb1ec9b182918708e1bc9bf0400080c080a019ebd7842667fa13d5443dd4fc0eb5a550d295b2f016640c48069720c4cca5b7a06613cbf7bac311b61db3237875b8d09e8b3a779d9544ab6895c90e71e0d0d3ab", + "0xf8ee048501f19233e28301c9b69400005ea00ac477b1030ce78506496e8c2de24bf580b888161ac21f000000000000000000000000a460051def6ec25bded4164722fbe6230fbdcaa90000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050021fb3f25a036f34b67e18b41aa1fb43ab94867a892a0a9fd400fd7f1aa52b227cd47065d02a053b3f27507e7a9523e54bb4070fd8ec31908812d0e475990a1823b93761b8e19", + "0x02f8790182013184010fabe385023c3b474682afee94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2881d012bed3c91000084d0e30db0c001a0298573a2670e93f4cab424e66e4d8be46a5fcc160dcb7f472952b441307b9468a07414351600bf1f3c367431c79caa7b3d69f8623b572ba8afb0c5d648e4ad9671", + "0x02f86f01020185028954caba82c18594c02aaa39b223fe8d0a0e5c4f27ead9083c756cc28428e3878084d0e30db0c080a02583325121bc262f83f83839c5faa0b1172605d971746e342705572400b449aca0589fcbba5567bcadc1050bb24e0ee22bfef7ad803f66e6bcce646ce72dca46b0", + "0x02f8b1012284851ca9a08502d044dd02830131249472bab498fa50a33a03362d0024bb27efbc50a7b780b844a9059cbb000000000000000000000000ef811bbb9b8a2ce8f598ba04329b6db8b36d95be000000000000000000000000000000000000000000084595161401484a000000c080a0898e328e73116724d0d0e3ad2f0dc95401cb5c7c3abad90e770f634c0b28ae71a00e563a05814bca0570ca44e1cf26a06d08ab6695a28ee0c75b1f566aba4627fc", + "0x02f8910181838405f5e1008502ceb580f583011cf594fc8f838d593bce8da977c83bdae3a6df00db9ca280a4074306c2000000000000000000000000a848a1d33d8ef1633397a6acf617620fab8e5da8c080a0bb7d0b5d028076fc5b0ce6accc9cc24486cb31afb30a444b590f0b9de9e4a419a01a473551dd6f6d3c93fa9da2214dbef2b343bf09198446fe637173b2ac6aa40e", + "0x02f9015a01648506fc23ac008509e5bc4ec683043206947a250d5630b4cf539739df2c5dacb4c659f2488d8803782dace9d90000b8e4b6f9de9500000000000000000000000000000000000000000bad97982994a61d7d504b94000000000000000000000000000000000000000000000000000000000000008000000000000000000000000014c0c7031e0fcbdd0db81c32a90b29ee5c41d1d200000000000000000000000000000000000000000000000000000000650d3bc10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c6980fa29a42e44852e29492268d9285d89c9dacc001a0abefec6763bba15dcf373f8ef4d68be877afeda8afafdf93d9665659fe34cc91a03556acdf4c8c6fd7cd4c54308aa7d59b55f33e9d6b63f88b537b4b57238d6cf7", + "0xf86c0a8502710caab782ea6094897b425dab19eb886dc6ae2010fe2a0de85308fa8802ee03111e5f95608025a03997154468e725f5c74e3482479eaab55706fadbd77c11a52d952b538ead2fdca03cb969906b9a7bbad27798e074eb5d9b9a1143de8a327fb8925bd2c8ee0f0116", + "0x02f901980101839896808503b9aca000830202059432400084c286cf3e17e7b677ea9583e60a000324879fcbb8fc976611b90124eb67241900000000000000000000000000037fae997dc49e357f6d717f397b14241472b9000000000000000000000000000000000000000000000000009e04f9aa34261100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000b71b00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000037fae997dc49e357f6d717f397b14241472b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c080a0040420abee74512bb5355caad3f177779c5556dd3d276b92c88191f65fbe1178a04fe64531b6e0216ba29edc217489bbce8298ea71ee4eb8b4b0a64245a0e100fb", + "0xf901538204fe850251cc0894830f4240947a250d5630b4cf539739df2c5dacb4c659f2488d879fdf42f6e48000b8e4b6f9de95000000000000000000000000000000000000000000000000005340a142a486a800000000000000000000000000000000000000000000000000000000000000800000000000000000000000009e1b2e13d5adadd4f18a84396ba3825e9f8665770000000000000000000000000000000000000000000000000000018abbaf99d70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000011a15d6ba4c27c89e468e959ba2230337317184c25a08e6c0aac59fc29108246cf7b05b3a133fc6f87d2a84e757f4cb257d2037ed369a05afd77fe4804d67098e13d1abb9bc0585d54dbfbaf2c4c4c9846e55fb65ff6ff", + "0x02f8700182ecb8808501f1106c848252089413f2241aa64bb6da2b74553fa9e12b713b74f33487d17a925100884f80c001a0f60e642a491338ca56b7975712bb0ef2c3fdaf3631f53bd16f17704002b92688a0593d2ec21ecd01a46982ffa35732a7ac04a1eeabfb0b8366f23274160d68f020" + ], + "withdrawals": [ + { + "index": "18476769", + "validator_index": "711858", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "16008754" + }, + { + "index": "18476770", + "validator_index": "711859", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "15964023" + }, + { + "index": "18476771", + "validator_index": "711860", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "55978346" + }, + { + "index": "18476772", + "validator_index": "711861", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "16018825" + }, + { + "index": "18476773", + "validator_index": "711862", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "55701351" + }, + { + "index": "18476774", + "validator_index": "711863", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "16048658" + }, + { + "index": "18476775", + "validator_index": "711864", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "16109594" + }, + { + "index": "18476776", + "validator_index": "711865", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "55192849" + }, + { + "index": "18476777", + "validator_index": "711866", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "16034174" + }, + { + "index": "18476778", + "validator_index": "711867", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "15996922" + }, + { + "index": "18476779", + "validator_index": "711868", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "15988508" + }, + { + "index": "18476780", + "validator_index": "711869", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "15991175" + }, + { + "index": "18476781", + "validator_index": "711870", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "16040454" + }, + { + "index": "18476782", + "validator_index": "711871", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "54619862" + }, + { + "index": "18476783", + "validator_index": "711872", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "16119355" + }, + { + "index": "18476784", + "validator_index": "711873", + "address": "0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f", + "amount": "16122912" + } + ] + }, + "bls_to_execution_changes": [] + } +} diff --git a/beacon/types/testdata/block_deneb.json b/beacon/types/testdata/block_deneb.json new file mode 100644 index 0000000..6dedcfc --- /dev/null +++ b/beacon/types/testdata/block_deneb.json @@ -0,0 +1,2644 @@ +{ + "slot": "8631513", + "proposer_index": "1124880", + "parent_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "state_root": "0x855b6335a3b955443fb14111738881680817a2de050a1e2534904ce2ddd8e5e0", + "body": { + "randao_reveal": "0x8c290463d6e68154d171deeca3a4d8d8fa276c72e9c34094f8b6bf89e551e99d63162e362a936b628af4840d69b10c24191e892d0a282bb5358a5669f44e42b627ebeb63fd6467c7aad62636a348b5f4edfb8ce01650e4d079339d9dc5700f05", + "eth1_data": { + "deposit_root": "0x636ab1747c976fe08cf337b437ccbb5f543e0d0c6b5d70097c3ab7737c1748d5", + "deposit_count": "1342638", + "block_hash": "0x429813f0390a9e104740e8a24ebb83ac03929dff4a9702385f2bf24391ba754b" + }, + "graffiti": "0x526f636b617761795820496e6672610000000000000000000000000000000000", + "proposer_slashings": [], + "attester_slashings": [], + "attestations": [ + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "19", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x903146f136e4df8200be0229eb96bc9a2409d04763df61ebba51f54cfbd9eca2c88274cb94828c2705bff1454c50322e03372883c2dd47ee329cd17a3653f44314fa8693c73fa2097f622e7f2e163f7b7cb688aebad93e14c273d406743ec7ad" + }, + { + "aggregation_bits": "0xffffffffffbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "27", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x99d3c97b5036025d1b30ac32efd469a815269e2575a7525b1cc8323db85556aef7af7464d965ab9b6ee1804005436a0b05faf870cb213dff04552ddffcfe355987d35201e58dce3897c0de27a19016321fba9ac346452755ae9340f60cea895d" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "44", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb2f0775dd77d2969cc57c0d03ccdf0c79e9f4d34150a539f79d9f090cdf918a4092f1170008aca3c5c7d6ddc743f79d317f8300dd58ce040ac7a9e50940b3bae964426a7883d143012e504091bb669510d5901f11d008b8b630d8c42ade6863a" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "22", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8fa4c08ee7406d44034e6925dc65e1ed9b08d9fa32260f0e49d7477b5ba9762c413d7385692c498a57217eefc4f11b4b0c6a470df5f1c1e98f890975424af15a6925e657628e518fbfd80db38553790e8ae5dc6704de1cb727011ee084bc1af5" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "35", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x81ec8b97197bc59634b30a2356035f664a648f6aec4d30c7f357ad33d39f28205596683defcddf1ba6fcf1f3fced1a470b8a78ea360d7f1f1db4e2e5d6f98045071e5fe04338865d986c6b8f4aeff0d01ce19952d9a7084ee21da0d557b17f38" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f7f", + "data": { + "slot": "8631512", + "index": "63", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x950881fcc3a1d4d88feef09eb6cf4e72bdc68b76754ce5d496b2f1232b9ea9851e453e45eeb8e23524acbc756cc7b9f614ecd1e34aef281487d72e73078e0116ac30a846f2b085aacae17a5066aa6eb383579e35ed70508127f19e8caff78ce1" + }, + { + "aggregation_bits": "0xffffffffffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "58", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x940c731c48b8ff0d522fb38f301228f47272b89b5bd1f1ecf44d79bf762616baf05be5ad0f389a9524de812646ea5a50096ed04747bf642f8d8a75b60015d5c690414ee4d87b19d8fcc111b1cbd594aa78d939205fc5ed28e78b82afdec0f92c" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff77", + "data": { + "slot": "8631512", + "index": "53", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8c528252b23858ec845a50f6fd0d001639c8dcc8c665cf10bc52c2076ef0b97711ca1348bdfceebefc45ea9065376da90217d1ec09ae4409683b6d461c80458f3f9bc0308e5337ebd856bd8217a8b530aa56f0b8804cc181b636b990e88853c7" + }, + { + "aggregation_bits": "0xfffffffffffffffbfffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "3", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x95b64b1648464197415f13f02601e0100318af579c8774fc4192124dc2ed181496c09304a7f3a342541b3da1a82affe9199d3f4ef40285dee2dc082d6783cd84e5df15ee29c0a4436eabdeebe236a2973b9eae91ef9c929406e14de1ad78a7e4" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "2", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb40357603fb9b486e6c37d0152e52a76dd5e385f63bac25816bc210bad5501071474745ba808e1767b95c5a7202eabc512010e470d351ed49089de3e0f602ef3d6a4cab8603ac27217cb26d523517d340bc784270191573b18c5c7f4f68e70b2" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffeffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "9", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x866b139ee2b3d7771212031592f284836624427883ea7d9f9e247eb507c18a1044bdd71a27121fdde10a0898ba536d4400a9af47a470f61fb7367f038db35e1dcc4f9567a6251e9c01f1cc43624829811485eff6e64f5052f2f1632d6beb3728" + }, + { + "aggregation_bits": "0xffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "16", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb0742e0f82acb1366a6a1887388a80461c077e705fd1a5776491e80b34bb2c60f1626e2f4fafe3f0361da79731d465f40667b2abbd2abfda557a845eae6bd414593a4bcc9a82f3cc1a4a0fbc86bf5255caa794cfbcee4c87619b44ee5be8e713" + }, + { + "aggregation_bits": "0xffffdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "24", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa606e4d20408f30f4552e3e1d62f6964140d2dc4a297f2c300ecaa35594e658a55801d427c8166a1033bb8d46daeeb371779c4c1c89cbadd019411177ad63b22d42f083e0882c73df093523bb2184f5bcfec544366c3180a3c6d5d4e4715bf01" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "20", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x886d4565a820a4cf546207ea013939830add03e5147ae9bd6257ef886dae1119edc6e3677edee1937c433f33646264440d7c06c91642cff4f8f875fbc706d590bf51105ab8e7c3ee7779ec9fa40058935ae30227c338608f05650df94157422c" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "32", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb2ec56e1cafb774c4f3e86447b9e69c997699d4a611846f582ba6d60886aff12cfb87442cc650996cbe30f35e0c7c15f034037762103bae2a8b8461ed21a6e6000a3f1afec40eeee45ed82243400086d3e6527d9cd00954d661392d492ca93be" + }, + { + "aggregation_bits": "0xfffffffffffffffffefffffffffffdfffffffffffffffffffffffffffffffffbffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "13", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x872a82d58281f34caf12e34046ffd1a137415090ac37b84e797e1f9800b2ca339d5d6fbfab056662bfdbe201634b2c5c0f3001a22e181ff38ec6841b804a3aa214ee0ae863de8db9ded627280e05784f0c715dc6256df492aacc5185dd602369" + }, + { + "aggregation_bits": "0xff7ffffffffffffffffffffffffffffffffffffffffffffffffbfffffffffbffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "34", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa491e4ee4f0dff0b0282b4fa3dd6e6a5efad861a33f1ecddd2eb9cbbdf663a88a19de6643276ba341739b9a0d6fed65a09b33d499e947d2836bac1c098a012d9096d4bc95eb86953dd6ea425b973418de05fbe3e439835bae81d61db3c85e098" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbffffffe3f", + "data": { + "slot": "8631512", + "index": "15", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8663ea9804a9a07f32291db4018a20024b06b5e45fa55de6a9a9d6db7f75f2af1e71af643eb6bc56f15da20ba74af1d80e6d0d459c69e2971d71a72d83a3807f269898669e850fdf49dbae277dfe3e48dfb5b34436c9476e137a34f2f56a97b4" + }, + { + "aggregation_bits": "0xffffffffffffffffbffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "8", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa9d69c457435763e8afd1e2c26b2b39e063e951ecdeefc1a3dcca848d75c617aaf25c5f79f2185f64a54c13d7883ee1315b18d1510b35e194b0502cd1e56ea470cb9eebb5592601cee169e4b65c79ad34efa1080e55523cf08060550f092db62" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffbffffffffffffffffffffffffdffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "21", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa42469725f212b6319a9113eb681dbbef08297b4fc458e16c8b157a77c426136c1215d3e885753ee5270f0e6dd53e58717800baa56e74a50d59c975a6e6838ec3d4e7e7df71a3ea02d490dcf496b96a16ced37467e746308b39f7ba11c3d417e" + }, + { + "aggregation_bits": "0xfffffffffffffffffffbfffffffffffffffffffffffffffffffdffffffffffbfffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "40", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x918ae9885a56fe78cdb92a710af61738bad108f08f63ced0e12419930dfee4191c91bf7b32c8189a206f73fa497ef88308320d4fa1e1437e45946ab8f372e292e0edd897696a6a93b7ea2f6b6f97e9d2e2c10b400a70591b1cb482f7f15e383c" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff3f", + "data": { + "slot": "8631512", + "index": "51", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb0ce301a6e464261782af4300275f915fd1e9bc33dd3c1d8d79476e9d3fd2098fc1b4d967c9773864afcf7de12e579260b0cf8a7048a03c4b8b2715e5b8dd7587fbf5e570e4b77e31fe92fcec7db52ef909106122a9ee1ac56c856a9022fd54e" + }, + { + "aggregation_bits": "0xfffffffffffffffffffeffffffffffffffdffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "11", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x99767e9d12d961d8c2845c230a6a56b753e8c9eb9d228a178d8aefb93a96c320d87ba648199db301af51ce55628cf1a70f5df1a5c150130ba81113544bb3cf361a8be881590a7fc123cc82023ea5e4ea115e59a1c765ef8f926f199a27ee4d6e" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffefffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefff7f", + "data": { + "slot": "8631512", + "index": "52", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8452d2819e0e02ed4bf803a31d9a0f2b2b2b249d4944f4729e3d8538a1dd16231061706bc82d56b0f3365b867dd3d2840bf813e116efaef44a8dc81ed37c4662503931f479e1ee33095195726ddd06f843ce4f1be58009a9209528650a8ec40f" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffdfffffffffffffffffffffffffdfffffffffffffffffffffffffffffffffffffbffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "1", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xaef55f46a3fe443eca0644769a0e1ee3805c60feff89335493789c8814223fccc863cd42250a64ef7e6e026ab8171ec315eff1f2a6d080ffc288e266aac71c41622c714578c10942bff43c11e283c41bff28fa29e691f71f19e1f994d7d35045" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffbffffffff3f", + "data": { + "slot": "8631512", + "index": "59", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa3503124a73c7e04e7a4912364f445b6dac529c9058d7633fafddcc837bd23464d26cf2f65d5f368b9707d3b4bfe13501028d3543f1f0b50fb5069ca377b7ba0e0cf54734f581e5fc19ffd6eb67481f0895781dcf677977986b79f106d2eeaac" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffff7fffffffffffffbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "18", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa47b9fa745d4120436e297fefcd31c16169be9d4f20f55589c0d4e5cd4d84c8d3b88bb0a94778ce44dad47dd9f79e2b90ae0956a1530105f450ae10ac229c2de887fbcad2b7ae339f3bc1a8ae505bda878bf5b9e021229d797345a483dd04239" + }, + { + "aggregation_bits": "0xfffffffffffffffdffffffffffdfffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffbffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "55", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x979245abb880bccc2c3f67a31e913def8e090cc821aa3be3744f1c503316421cdf1711d6287a6789b5c48d92bf424e1b0c7dc776ad5f9c559f59d4ef98d495d25321ac2c8f33ff943c442662c691bf348494480757e3867a1d20f07e21eafc4b" + }, + { + "aggregation_bits": "0xffffffffffffffffffbfffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "54", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa3cec14a422ae48d91b03da27937ac6acd1e258dbc6e33133307fa1077da8bee97608a0a806400b819fb46d06f23417f0c8e5541ecc438e783ce27cd202bd0d69ab92092df7b8bd372b2a18f4ea30ef39883c769a9eb2c2bca520dc44c0e6eb7" + }, + { + "aggregation_bits": "0xffffffffffbfffffffffffffffffffffffffffffffffffbfffffffffffffffffffffffffffffffffffffffffff7ffbffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "49", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb356179743682d9df75eed9cb2398b3a7d91df8bcdc1abba65aff74b61c31bd91935893ed6223d5d61b1b7f5f3e5f819068ca705c824fa9860faf19022208b24fa3d9cc38e304da3722308dfab3535cc4460ed416513e52e87ac56c3d217b3fc" + }, + { + "aggregation_bits": "0xfffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffeff7fffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "50", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa20edf17e5b0f1fa35a3e4327432db00c1957e5ea6eaea5798c442b3de5b01686f8b82901ebda7860428e554afda95760e729d9d917f36f74ce1094fde666665b7f5b4b11e71e80566da43e0e597a0b3e23bd5a8d57e380885622822aa0a14df" + }, + { + "aggregation_bits": "0xfbfffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffdffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "4", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xad119153e2744c66c9b8066d694e49db4ec0ffb8a2bb984db630bc00c7a6e6ebb343b7a6f7e1ac1f40762bfbccdcbbca09ed57f768a2baa18073e3c144d79bb19c7f1ada89cd44297d51aa9a399963ca99bf5c91c653bcc2502d575df557c732" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfffffffffffffffffffffffffffffffffffdfffffffffefffffffffff7f", + "data": { + "slot": "8631512", + "index": "26", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb9a04ea00e249207cfafe9693d77e87cdfa34512db9781dd32a7fb80dcf5f4334f848574966d56a6447e54031e0eab2a08b75f915698d2cdf5266ee292a57bf265135887303998fd99fa2b654117667649e0ae74db015ec954c9ad18988c92d0" + }, + { + "aggregation_bits": "0xfefffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "28", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x88133f0ab0d7cdff20b844feb4275ae15b832c91e46afae8704e18539da6596fdd8a37f95559172b1afdfd40b340a23a180900e7fc2679e1b257206e879db706e0c9d393ccbcbbd1793c24834862f6d9fa3d9614118b27def456148fb2cc6217" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffffffbfffffffffffffffffffffffffffffffffffffbfffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "7", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8c379743152b7330835e9a740a0b102a762225c2e5108faf02df7ff9e6438018f5331c3ab5e8b1424388f226250e622c145580ff9f972a66767bce5d9d74e34fd809c12cf404940f3eff0596cf4a2187cbc0b614a9f25a9f5111fe54685e5ea2" + }, + { + "aggregation_bits": "0xfeffffffffffffffffffffffffffffdfffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffdfffffffffffff7f", + "data": { + "slot": "8631512", + "index": "29", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xadd685e2acc665e03c10eec1777b1470650b41c931fd3856cfffd2413709db3e77a163da695f2e79a06e447f8331ca5f12c8f8737e26fe56c2ff203e884e0cc7fb36c53e1e7e517a9c2628805d583c1e14963077abf03a5dce22145b50ac1b37" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffdfffffefffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "25", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x994f6497b6671372784ee8ad0876f147dc724d0074b8799925f4f21d318d1e2b8c78ff571a234d32da916dc94dae967b194073192ffca59c5bcc767b5455472edfaf46e7bd4afaf143f9ee562e4fda8ea187d809e2e68b803918d266223cb938" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffdffffffffffffffffffffffffbfffffffffffffffffffffffffffffffffffffbffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "23", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8efd6e9c8c41beebbab07a4952739123a554b16d527ae4e15982d339fe50388b6169d617c83e11a29230e7ef464e31880bd21ce4f9154faf101bc6233ed09d4ca8f51ec143c437428f47f64cc0b104754f3c19292a2bea3bc4d292bcd5a9da1c" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffdfffffffff7ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6f", + "data": { + "slot": "8631512", + "index": "37", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa8b0c046f9cff0a03e328c384b96df4ef7408276ebb2731f3ca95d12239db218f4feb4669a053b307d991d77ba4692530d6b4f27292e79d51b458cc13b109e15d63504a4cc16ecaa15f4e396a3860bb5f5977a8c8da37a786beb88a48187363e" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffbfffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "10", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x87651e4014f8c00148f196776463092f1574eb24816cae2b681ebf944f74957757b5e90550a73b2d1cbc3aa9f9a9b0ca05966502ba0a1d23182fe2a99c86e35e1116069ce4c61aa5e8548a7a42ca1465c30f9c3b3e0689d2ef87dfebfa78f2fc" + }, + { + "aggregation_bits": "0xfffffffffffffdffffffffffffffffbfffffffffffffffffffffffffff7ffffdffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "17", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x801a1d09004c0fc95ed510b9deb1a98c4abd88e91c46179ff5a7f17bfa1f2db48a0b42c3110cddc71ec0097185d6cd7407e36483cdb36745a6a0a23d3fad3123c0ffa5008f26c9a75b8bdabd2753fe9dbbf08d292d79a73a2736d533aa9fcad2" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffbfffffeffffffeffffffffffffdffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "46", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb564bc7299fa09ae6d355ad187719ee147965f3cedfacc0a0f2b2b627c06e0535067b4548d810d9f609e5d55369e9b870dce515bb482202f324c93d8ba100f1893073ac010399918d642294f08b3dca311f56be91395a6bd4b5c43666aff123a" + }, + { + "aggregation_bits": "0xffffffffffefffffffffff7ffffffffffffffffffffffffffdffffffffdffffffffffffffffffffffffffffffeffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "6", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x910fb8a623531872777c69cd9098b4599840000067fe94eebb61a5aad67520718e3c5d1b0e674ba334e501d5414443e3132ffc00a8bc087d1c7c24c1110d2ab3eae5eb52fdc1542d2e85973eb94193735ce397bd55db2adaba1c1c5d80dd9ef1" + }, + { + "aggregation_bits": "0xffffffffffffffffffff7dfffffffffffffff7fffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffff7f", + "data": { + "slot": "8631512", + "index": "39", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa74a52e52313fa31a6d69f139de567134459d9eeec28eefbe5d0f05bbba2c755a3d854d058ccf2ebfc45e45209d381681964e6a419bfdfc8921f366bad44759fbe7c768cb55bc5353600b8b51f08b6f5f7448f42bde2de52466255a1a255145f" + }, + { + "aggregation_bits": "0xffffff7ffffffffffffffffffffffffffffffffffffffbffffffffefffffffd7ffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "62", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xab1adcc9c42316e0dd3d02d056c6dba3baff880f217cb256f88262fb265d813c0704fbf08e2e60e1af25618bfd637d3918161bf3750803103fb91df250c281126fa3119ea020ff56ccee61cf4b210ec8227cb90390be77258037f46c15d5e6d2" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffeffffffffffffffbfffffffffffffffffffffffbffffffffff7ffffffffffffffffffffffffffffffffffffffbffff7f", + "data": { + "slot": "8631512", + "index": "47", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x84180e4ced9a7260462715bfa0a474693bc6e56ae4d4595d72abd6b2fc16afc775bdafbc4690e30f5495be40dbd5442609848e264ca48e7bcd6895ce3340b57521a266c3481f01792f1e7cb7215105345f6d05d192969b07520f02f1f61d04bc" + }, + { + "aggregation_bits": "0xffffffffffffffffff7fffffffffffffffffffffdffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffdfffffffffffff3f", + "data": { + "slot": "8631512", + "index": "12", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x901c3f60fbc25644d817e3964f29cb3c5a090cb7c9c843a23b39a1211635ebf4d0a7599ec511b5a637e60a05d6b809600bf096a151434ef9e35645ebbf943b442cecb3745d2a91dcaea4cd71e93e334827c558a78f7ea9101db95deb5773fe13" + }, + { + "aggregation_bits": "0xfffffeffffffffffffffff7fffffdffffffffffffbfffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "45", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8303e3e0aa2ffe77dd162a3660c8916a964aefee66152f27be11da6ae97e3704573b5a7c9282a5e5a9f7fe340621629e03ff9a4988b286a895e2568314f151ad250518d356b2ed33d6017b9405c2b341213d7654fdc4924b9d789f3568f860f7" + }, + { + "aggregation_bits": "0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffbffefffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "38", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb9697d52d9483c8770a73a7b28cc6f4fe2fbd9e269181483445582a18b0e580f8ef1680ed68506ac52e4bcf5285a1ae108f97d2baa542d39b1efca6f509c1344cca433af5ccefebe31d1765b5e697db7fc40a917f995982e6d71c17c074f265a" + }, + { + "aggregation_bits": "0xffffffffffffbfffffffdfffffffffffffbffffffffffffbfffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "14", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x873a15e74e683d9a128b822ed8c73a592a6abdf5b6b67d0391614cb2772f4b6ccff445895ea34f754e7436986fa3539d0875cb6db0210480f118bffde9cf04504990a57e040384ba5fbfb921525a1fab7b2eda325df5d49a99208578f175433b" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffb7fffffffffffffffffffffffffdfffffffffffdfffffffffffffffbfffffffffffffffffffffffffffffffffffffff7f7f", + "data": { + "slot": "8631512", + "index": "60", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x98e3833f9dd9f4a50342789ed1cd462072d0e3e04e734d352c8366bfeea49a0e94def4c54461a5c603ce53ba1efa07c60ec81de0625f0b770f858b90b5d8762b3111c26357b026384b2f1d66676500d5090f7f9b781882bc949a722f8a2bdd0f" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffffffffefffffffffffffffffeffffff7fffeffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "48", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8af584ee4533db14726f67b0045ce97a185fed5c60cf3a1a5092a7335fb7fd804399362373613d9032fdff3d6993f0b8024d46db51119fcfa9d631657075c1c7a2d86551df69034d821a8b2a587374b4c827c9031ba2274d772ab3cb35360cc5" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffdffffffffffffffffffffffbfffffffffffffffffffffffbfffb7ffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "43", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x909dbbd5a35c63d3584bdc244753df1afad73d36e322f4781d8f729bf859b3c059aeaaa496b1eac32aadb21a0718de5114517884dc2333373b20fbd38f6a44c5a1e4783c4cbcde6f47fb2bf88ad1e5cafcf83ed6a21da26c25b67cc7db90fb96" + }, + { + "aggregation_bits": "0xfffff7ffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffff6ffffffffffff7ffffffffffffffffffffffffffbfffffffff7f", + "data": { + "slot": "8631512", + "index": "31", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8addf5882ae131192defac2c0fd0fb2823b175ef9c3935eac43fa6ac2ce0799320cc7ddf5198cac4fca3194bb6c350c50d5bd7961585da8aee3947fd70bcf35850afa2422335a9dabe1ea355ef8656621151a64c454a2f151b218f865d3208d9" + }, + { + "aggregation_bits": "0xfffffffefffffffffffffffffffffffffbffffffffffffffffff7dffffffffffffffffffffbfffffffffffffffdfffffffffffffffffffffffffff7f", + "data": { + "slot": "8631512", + "index": "57", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x934c856940d84c4f14a733bee6274d9cc2169f204ea0b6ffe56ac508a6415bf35205fd375345e77d8a5f2300efe4ded2132d832b056ea87652113e53a5b9b0f4e9c2d5fe12d71c7c469dc249a58d5d0ff6b3ec1a38f7b2357193dcbf77e08667" + }, + { + "aggregation_bits": "0xfffffffffffffffeffffffffdfffffffffff7ffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffdffff3f", + "data": { + "slot": "8631512", + "index": "33", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xadbd39160d68eade93f8c1c347d5f279f5da8ed5dcd1707f6c4baae397c522351a13a3fdd88dea0669767d87937d771b055be2e0788d1b71bd94e37d453418c5f14fd633ce1cab4948b4f0a7ee8389d44af96023550add95e53166387e8a5651" + }, + { + "aggregation_bits": "0xfff7fffffffffffffffefffffffffffffffffffffffffddffffffffffffffffffffffffffffffffffffffffffffffffffffffbffff7fffffffffff3f", + "data": { + "slot": "8631512", + "index": "36", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xaaa9a9ad6e62fb6d72fff1f8adbfbff847c7daed439f2d61f427eafdf9e635bfd737b8c50b22f5254e7aeeaffe0aca4207e0cc9789ff8673222c94fcb00d565dacd1b4d7174d1d8d46a94afee1823a9e253ba34512416f5b4b9e6ab0ec7a4603" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffefffffffffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffd7fffeefffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "5", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8796bf82191746e96ed7a4815b529d58b89637a73a257f91a37e5efb24f45928d73874e070baeede5dc307192a66adb61622a9e1cee830a42f9dba769b5f2faacb1ee48b45c4b3ef36b250d507b1e1d98da615f2e449f4e31f2399ea967e597d" + }, + { + "aggregation_bits": "0xefffbefffffffffffffffffffffffffffffffffeffffffffffffffff7fffffbfffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "56", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa8e03e06c2cb4c547a999470566efb71762b3a1c6799dcaa88a87fbcac801ebc6b8e6c6a3356240751a002050d96c7e805d8ee44b371694bf134b14a73f66c98a3d4635d7de9c87d66d8129d2b2baf6555f3dc89587e9ed8170a67430d967dc7" + }, + { + "aggregation_bits": "0xffffffffdffffffffffffffffffffffffffffffffffffffffe7fbffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffb3f", + "data": { + "slot": "8631512", + "index": "30", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb123ca42495b24ba8dde57f7de4076a3f16862e9bf6e4553d4362a6f7a79734ed4e42e11e61645436751c96ea4232395061a0f14a6b581f07f690256d87c266f73f6b1184ad38beeb32fe07839b2ea7d3d4a0fcc90642896d92ff4004c1b5dce" + }, + { + "aggregation_bits": "0xffffffffffffffffffffffffffeffffffffffffffffffffffffdfffdff7fffffffffffffffffffbfffffffffffffff7fffffffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "61", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8de31ecbc01fd8fb43d4aacfcc2a03b6e936931dae2c5e183d76ec80d2529344167fc2b2c9418ecb55c6aa28bed977660be65b96f3af37a5da57f8555cc6c4eda2048c86f49035ec627d52b4bf8df4661a750528eedfd68e50917d70bdd2d32a" + }, + { + "aggregation_bits": "0xfffffdffeffffffffffffffffff7fffff7fffffffffffffffffffffffffffffffffffffffffffffeffffffefffffffffffbfffffffffffffffffff3f", + "data": { + "slot": "8631512", + "index": "0", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb74ed75a3bfe483a2481cc43e8cb7b3cc8d5f24d8d9e94857a507a1dabe821551012ebde3164a8d6696cf5e27e424626036eb52dcc1c306e656c776994805865c14b9e8b68e387095d76da3ba18522a5616fbe3434874b5bf2aa655996191799" + }, + { + "aggregation_bits": "0xfffffffffffffffffdffffffffffffffffffffffffffbfffffffffffffffffffffffffbfffff7fffffffffefffffffdfffffffbffffffdefffffff7f", + "data": { + "slot": "8631512", + "index": "42", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8f17caa5bef643c0ad87233155725f33f74561eadafcd08be133e3294d5f203258d6b0ba931885dc4a539baa2731971801c7ae96585a57798074ea0fb66a75413fca184466b88f30a12a18d44243c5342e7422b1487162dfaed5d27d8a88a8be" + }, + { + "aggregation_bits": "0xeffff7fdffffffffffffffbfffffffffffffffffeffffffbffffffffffffffdfffffffefffffffffffffffffffffffffffffffffefffffffffffff3f", + "data": { + "slot": "8631512", + "index": "41", + "beacon_block_root": "0x5a585679198d1bae7f337f987496d22c9f0db95fb1bcd4d8069a74be0e76a5ae", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x83d26a533885e91d048c8afb165513641e3290db26884d4bd9f8c250af11174a40272e38fbb676e7bcf9adcf35b6571808623f517ad35935dd3c7d44faf12769bdeb37e692e2176c6af442c170b8a787e154e7aeb219fa3da054377a59785036" + }, + { + "aggregation_bits": "0x000000000000020400040040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000040", + "data": { + "slot": "8631511", + "index": "19", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa8fa520c646b127498e1148bc60cfb946ac50527b5a370533c4b7f1386cbb537872f35e684d4778708aab0c505a7079f06d318b0871899ebd802c3e0fd7ac75cd8a9e1ce68a434aa5056b83606125bc6b0511f87104ad56e487b44b0cc9f2d18" + }, + { + "aggregation_bits": "0x000000000000000010000000000000000000001000000000080000000000000000200000000000000000000000000000000000000000000000010040", + "data": { + "slot": "8631511", + "index": "60", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x941256861b17a7842620bd000ccc6b3927d80a9b8057895133404d78f4a2e3f89f031cfcdcda558990758b394789b8e6013d85b889b1e843710885a8bdb6f2a0f8fa1ae6ef87e8e80b9c33208538f4d93e2317b863f2eb1a54fafea716bb969f" + }, + { + "aggregation_bits": "0x000000000000010000001000000000000000000000000000000000000000000100000000100010000000000000000000000000000000000000000040", + "data": { + "slot": "8631511", + "index": "5", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x929bf36ba7957d5c34c8d7f07d722be4e5802cdb112e4453e450df0210e1744d8aa936a5c003aa0ad08c3f469819915b05c0966b5bf989530205d9452588fff8a17cb6265fc29343d585249d2dc8e8147dec0039b61a5aa2552a72d213bccce1" + }, + { + "aggregation_bits": "0x000000000000001000000004000000000000000020000000000000000000000001000000000000000000000000000000000000010000000000000020", + "data": { + "slot": "8631511", + "index": "22", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa76f0c0d444a02d38967477fb80cdf171597303fe26287f0976e3289e502d9dfdb69f9909f96b4e3585f53563ff1f0a5067c0563c61f0a96497dd263eac1269e2b0b225c06dd26347230933e627e0f5bf5ac885f0952323f54de3e874a7393db" + }, + { + "aggregation_bits": "0x000000000001000000000000001200000000000000000000000000000000000000000002000000000000000020000000000000000000000000000020", + "data": { + "slot": "8631511", + "index": "4", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xac3da2d3a7bbdf5649e13fbae50c7a70dd215754b731e9d644947948384e050926231850273047cb711c3680cff91aef07aad7233a6a5184c940f798204753ba0a09c6774a1fec7fbf200331a7252b1f54315219d3c12c030fe8d814078835dd" + }, + { + "aggregation_bits": "0x000200000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000040000000000020000020", + "data": { + "slot": "8631511", + "index": "35", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xad0193b75611d846c91a8ee3709158c93e716ed635437668b5a6518d5deadd43ea76d669d341de52042d54b5e4f932b107e042f5187db2bf08cbd38cfc10cc6d5759652dbaeb801860bae78f734d6d56600cd514b18be9e7b487b17fae791590" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000010000000000200000020000000000000000000040000000000000000000000000020", + "data": { + "slot": "8631511", + "index": "25", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8a2f2208e25bba65d32e402d293eb4cd533b08750ff4184791b1b4846e08ab8ba0fc405429040418abc14fa880c8af7300978d3146a85a1fbda1467749c6c6aaa62c752183bf66391760b2fddcc84c1646530541aa592bf95f10ff05ea0f63a9" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000800000000000000000000000000000000000000000000030000000000000000000001000000000000040", + "data": { + "slot": "8631511", + "index": "18", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb97f6842a1472ac1f96646de52f131d6aa0a68cf440e0caffb0bb7d073c07a6fe416dc3664601a5dcefd4ae1916778a30799dcad6b283713c4ad79d8a2086ccd3fe5bcb4a3f95ad363bc0ff309f75e2004c8811469bb4c82d1601c05ed23255c" + }, + { + "aggregation_bits": "0x800000000000000000020000000002000000000000000000000000000000000000000000000000000000000000000002000000000000000000000020", + "data": { + "slot": "8631511", + "index": "20", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xab3a064a02d7e06fbf1332fda21aa144afb7f56f51054fd9df07df17433a66b7e68536d6866a8e92c542cd7d4c2e4e410ee8efe1ce8e3e5fe30fadfc4097d0eece0b2e6854403e8410470b3b7fdd632e20ca17b917f8dd8d920d4d04d4438cbf" + }, + { + "aggregation_bits": "0x000008000080000000000000000000000000000000000000000000100000000000000000000000000000000000000000100000000000000000000040", + "data": { + "slot": "8631510", + "index": "26", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb582bdb2e6fc782ac3398a41249b751714ee06c5ccb9c1d4fb8fd1d2eeda4e7e024a0070ca7100e4dd9a167553d1f14414bada9ca70d29844742f3a03e35283b167d3e02f1ba091a30ff95ca48b141061f1580d7ae4ace2b561ea29769a601c4" + }, + { + "aggregation_bits": "0x000000000000000000000000002000000000000000000000000000000000000000000000000000000000100000000000000000000200000000020040", + "data": { + "slot": "8631511", + "index": "13", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x903a63dc8287b453fbe6e7a5346042ab3953ea9aeb4f97e6938963632386a7138e2ea57186e4bc448127524833bd5b0607f2032e835fb603b3c4c8461b62776dfe91ac8d361ae4ac799d30550b238d1cabbc03d17792d7e1ce21bb4773793559" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000002000080400000000000000002000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631511", + "index": "49", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xac50ce515c5097b44b5abf3ba8eadab663acac8342956b4a9c81d43139726d2db3508ba086d91643017453a194b9223f0ed6165375ae8d1e8b838bcc67e1e12c1d7748c2c7add7c10171cc32c8f1008c086ad0d510cf2a52dfa112bda6fb263b" + }, + { + "aggregation_bits": "0x000400000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000004000000000020", + "data": { + "slot": "8631510", + "index": "32", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xaa923c5ab1e1d9bd56fd533d2079936f0de80465cc981588ea5d7bfc17216b165e7399ad57dc36177066fe8c25c1b10401edb3f5afc471586cc00fbaa6bef2c9c5e73f1526aa9a0110f31d05a663931db95dcf1d2be0a8db6708b44679bbdd9a" + }, + { + "aggregation_bits": "0x000000000400000000000000000000000000200000000000000000000000000000000000000000000000002000000000000000000000000000400040", + "data": { + "slot": "8631511", + "index": "10", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa71aab6347798a06e86bbb73ad7048816218deffbd49d3317c5eb4785530cde15aa10b1d0ea76b4484461b10bb19ed2c18f618bb293a8c02b35fec12615eabf84a6a385e152fad263e5dafee8754cd7e4a031d0ddf4a3c8ae923bd69e324ff79" + }, + { + "aggregation_bits": "0x080000000000000000000000000000000000002000000000020000000000000000000000000000000000000000000000000000000000008000000040", + "data": { + "slot": "8631511", + "index": "46", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa993c440d798f29489b9b2149ba58a40b54e00590f403485221b257c2daeefb287f137911284ffc5a0f54b09f961641f0b1fdd7ada9bf01f41f03862c1253d92086134b14d7fa5915d3d5f2ccff25f44283f6624fabba31bec6df2c74787cb9a" + }, + { + "aggregation_bits": "0x000000000000000800000000040000000000000000000000000000000000000001000000000000000000000000000000000002000000000000000020", + "data": { + "slot": "8631511", + "index": "15", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8554ae2377e193ba5fd1d377c2f878d8f482acc20ed4a252effb4f1a493ecfc25f50fd458ccd5967b89110f6b944384410267966b24e05a30f3cc8c5fe03520bc0b85768f58413f8400ffbc6680f0227651d234cc700d5ee3f4e82383bf58cd3" + }, + { + "aggregation_bits": "0x000000020000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000400000020", + "data": { + "slot": "8631510", + "index": "25", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8fb874900df428621171d56646474315b1f86c13f9ec4a63cd5e993d08089adbc1c7343e0928c5a10cd69510ce4cc1b210c40515c0142099f373840c6941f84fc9dc82f069ec23d9b1f53b33ec0dfc65c2bfaeb0781fcb084824b9f79835f845" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000100000000000080000000000000000000004000000000000000000000000000000000000020", + "data": { + "slot": "8631511", + "index": "56", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8c8b5f32af81b739ff6ed6b3853013641902dbdc7479fb58f64469d1d414cab4ec029fa6e5f24d856f87c4602aa9f2d819b805f63f151e529178d3ccd108d38c71e87ce24a8e5d29023fcfbb8b05319c15c3a09c833875825efaee52e832f328" + }, + { + "aggregation_bits": "0x010000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000040", + "data": { + "slot": "8631511", + "index": "59", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb2545fd500913cb22353af3d7e69c4470a049d4f43ff7b20f9fdbbf75790d6f8acaa5911398414ba52fcb8539ddcc8450e23eca65a1bd336a6eb0fe94a403de50b5e6da9f892fecd00eaf8b61d690c6ca8ad40f9964e4f195d9f7d83a0f800a2" + }, + { + "aggregation_bits": "0x000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000042", + "data": { + "slot": "8631511", + "index": "36", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb7b798c12586933f6454b240658743062db77e6b2a05d99c4abfe5189f07e7dc94558e57d037625e28c18d61d0976eb1125e05fca38e563fdd092f02963077ebf545aec0246006c8024172eb5813f24c7d66f845b75cb5324755f1bcab76791d" + }, + { + "aggregation_bits": "0x000000000000000000000000080000000000000000000000000000000000000000400000002000000000000000000000000000000000000000000020", + "data": { + "slot": "8631510", + "index": "9", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8a15db0376937efdbf6c0583dfe593547f039b8bbdd4985551dc0f4b3cd7f1a045622deb071f61e54d3a0a31b4716b500b9fb9a3bdfe549a11264c2ffeed2cdd63fcd84dcbe33ae28b207b768bbd24ba2c43f843edd45813ebddcbad59bcca99" + }, + { + "aggregation_bits": "0x000001000000000000002000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "18", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x982cc06550a70787a073eea5d83815400ef4e83545bcb05f827df53558d9476eed3413644698748e6b4732fdd3641a50036a09d93a7084e0cba0122923a49bf628467026795a5159732a87450651c3e64fbd1b3076b417f0013e805f1bf288c5" + }, + { + "aggregation_bits": "0x000008000000000000000000000000000000000000000000000000000000000000040000000000000000000000000004000000000000000000000020", + "data": { + "slot": "8631511", + "index": "12", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb554a32185eb69e174694065a11af9ce5d4db924c997fd369ab83674d6d437362308ea9728d3f024df9bc414ba235e6817bf351c60a30d2d7b7d4e532b47840f08e1aef0287391afd840e1ba3522f82d6c70a6cf0fc36d3ea8d080971cba0050" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000010002000000000000000000000000000000000000000000000000000000000000040000000000000000040", + "data": { + "slot": "8631510", + "index": "51", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb643396eb85db6eefc67d810f21ff0dd54bb5e102460029dc2eb90c6561a2831b9217ea8ffb97e8f290a25db820492ef0cb87e8f281a4c4517bf5f973af4078c8d394e9dc37b398055ad1cacad65391cacd26bf01610db40acb695e84e6ef134" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000080000000000002000000000000000000000000000000000000000000000000000000000000000000021", + "data": { + "slot": "8631511", + "index": "27", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8cd635309ce8be6abdb592c6ac2430c702841031d47418acbe4edd62aa0e5bfa13f6014d5520943b131f88887c950dcd18dc1befd85a268220121f8b30677a4a4a31bb36ccc2c4b3c008822076bc4d2c308ffca97758849ef14ce4a0906c5a0c" + }, + { + "aggregation_bits": "0x000000000000008000000000000000000000000000000000000000000000000000000000000000200000008000000000000000000000000000000020", + "data": { + "slot": "8631511", + "index": "58", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb20259156b98e6cf14baf3e168cd21551746da97cf6a28094e08c7caf20e88487ee661468f2476012f593c0e5f373294099f692ad463ada9d3f1d746974bec9cb00b2a4852f8df2044c18f759f13f4e63f1e32ea2520de4950718b9455b83438" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000800000000000000000020000000000000000000000000000041", + "data": { + "slot": "8631511", + "index": "6", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x92cd0e974f0be31e3c7baabf6788678496807cd4592894dfdf63d044c91a74b7a5d85b6126fb02daf6f129ecf2fa69950ae228662b512fe5f3de4a0e8578ba2ce73114d90c0d9c58eef33314b5d3916ee465c055430297103241d4c7490eb033" + }, + { + "aggregation_bits": "0x000000000000000000000000800000000000200000000000000000000000000010000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "47", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb6b000777f0e5022994fd0dc8f4264b0ddbafc128daaa27f2cf8ccdcf45134c24b57bb0c719c427c52c80dfe0f73552f059139af95a1f9ae152073624d5b26c90e66cbc582db51328f37d9fa94de17932e3956a5946000d31e62ce39cd1f3cb5" + }, + { + "aggregation_bits": "0x000000000000000002000000000000000000000000000000000000000000000000000040000000000000001000000000000000000000000000000040", + "data": { + "slot": "8631512", + "index": "42", + "beacon_block_root": "0xeb0c08d706859c73e7172f788977b3cff4ad0e2936c4dd95582dd27dbbbdd69d", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x97a6cfc872eb64f6fe701fda43c418a4bf24b57a00bf0c06e37dd3825643949c8435c7124d22f61e003544ff7bfbc206080cf9abfb0589fd53fdffd866ad0dd2d67797ac1f70df6ca868ddb411abba17114c6018de2c6e7ddd5ac9f8bcd786e9" + }, + { + "aggregation_bits": "0x000000000000000000000000000000a00000000000000000000000000000000000000000800000000000000000000000000000000000000000000040", + "data": { + "slot": "8631511", + "index": "26", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb7101682aa16481f440b65471da4841f50806df7e0fb42f8377e6673d94127c27fa891045af4c4be5872798328e546ec035ec2d0ff8d6045afc7d3d882957a2c07584790e00cd584b74755fe59fa011606497f41efa8c1cda7b24f9f5b85bc0d" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000008020", + "data": { + "slot": "8631511", + "index": "51", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xab57f287032eaa8faba41f725604de4558e7a8b16535d1c6de0a0938742e1a236812dd92c4922b38bceaa6f2e8a32dcf195413f9532c2c325e052d77e24076237d33ec60246235b2fa9f43edccf1f1f59ec75e1e80cdf1852ea60b9acc1b634d" + }, + { + "aggregation_bits": "0x010000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000040", + "data": { + "slot": "8631511", + "index": "14", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb345079aa0fcf1fec97e0311d14ab06d82922c9687794a1cdad8ff8b862e10984e4f39bac677f1f28f9d93eaf0fc0e4013250514952d1249cf8b0b4e3936eac5cef46bf4a1316990a3e3b42f5594ed06268b90a6da5b7e957b495277ea522494" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000002000000000000020", + "data": { + "slot": "8631511", + "index": "9", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x90d714486d6af4450b192cb61a3d0f27b5f8d3224482ae5f4cdd094cc93f1656a59445cb7cb49e1d637fee67ebc57d240f4c6ae04af5a81df81c522c367b296110c8235506f51e8e7600ebd3ffea73ec0dcbd6995787344d3ec1b5ab61db9ecb" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000040000000000000000000000000040", + "data": { + "slot": "8631511", + "index": "16", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb1cc73e3063be66b25c580a794c228904af071144d3419ce67f4a553ff174e9980ac4b21d7ecefd9e4eb88a5049e55310083b7bd220b2cd5c3e3ef994113dd9843076377bfc66c862d4e120c138d8d004cbbe929a89e01a42c60d504a4fbb0d4" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000004000000000000000000000000000004000000000000000000000000000000000000000020", + "data": { + "slot": "8631511", + "index": "38", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa327b99a8052aa4bcb8b6b430d69ff5a9a38e70e6d72500cf56b05bc11ba2b5c67084aed2cfdecbef4428302279f3d2e10ebfc89434f98a961f7d1010fac135224b801a19bf86f0e266a24f17b2574bbe92d9f045434fb2f341076014ae74a52" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000400000000000000000000040", + "data": { + "slot": "8631511", + "index": "11", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x93c7c98fb3fe5849dee7e7b2496c8223279265f90ffe6667c9dbf12c945ad05761a194354f51ab4c6ac10f127fe893b202455a178cf05f88e4c25858705534f59c7e4e12710450e6de73cfa64d3a9baff5e142c4b1e6c7511617e1061ad0cd2f" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000800000000000000000000040", + "data": { + "slot": "8631510", + "index": "36", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb3c48536b6f029d4aa9af5b0d3925900712457674aaa3bea97690ea15cfc38b0bd591161ff24b024096bec8860424e650069a6ae1265c97fcff48b622a46c9ef9c7e042e000185f8fe758141eec8a72ced2abaad0e21136c1a0595634878efae" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000100100000000000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "3", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa9167a98dd2e7a1fd3e806abcb0a0ca6d7a2387d09082ef31148ce2b10f9d268e48962a76e43e417842a48bbe1b58fc6035a22dc5b217475dffd5b12eba3122fbdd5619aa739a236491e2c5f65e8f646606fbb6621819ffa9a531032f5bf74a4" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000001000000000000000000020", + "data": { + "slot": "8631510", + "index": "7", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x893819c979ca70b9bcfb02a022cb0ab9a30500194c993ffe3e28c83f15459d20be0c6fb0888862e18a91db6d1ad49df40fb59769675c7c00e6bd5de13d613d5fec2587a07e4c00986ed2cc5744661c72197fd02c635899f62abdf0601b8f5398" + }, + { + "aggregation_bits": "0x000000000000000000000002000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "62", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa450e8ccebd05fc7cb957b80d2b095cf6bd6a87ea47604ebe44bbe1b9611c6c701a47a39bc63b34246b72384c8ee3b5a1088bba7e62df199d0817c34795cb335b05fc195381362fe1578ecf482159c06f6c36bf074b174e56788474d299ed73f" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000200000000000000000000000000000000000000000100000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "34", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8f3e898455473c2a0ffe6f1e06eee8719cb27f104c03b480315959ca2ecf5003790c34c67c9564c48e0eb100109cec60127d3048e360634cf6baa7b6fe5dc52dc191266491170bec046f6528d03d26976fbba33ca5e5919489ac91c4887b17df" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000800000000000000000000000000000000000000000000000000000800000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "28", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8c6a20eb7479a966dad4a0355aad6cc6222ab3c4a3ed0633e83ac41987f28c330cf140a91c47166f4da91213f5d2f8dc18ad0766424066e86ca6e9ac1fe72fb0351ab5a5be2faa31295b4ccc080511d81acd15bc49c13ec922b566fe93b8c95e" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffff7fefffffffffffffff7ffffffeffffffffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffdff7f", + "data": { + "slot": "8631511", + "index": "41", + "beacon_block_root": "0xeb0c08d706859c73e7172f788977b3cff4ad0e2936c4dd95582dd27dbbbdd69d", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa0249d3c4552e9339862d138967e41f7c78bf5ad19981ee1ed573c2c2ced58899539086efb5a014b2fe1bc8a4b40a4d8157cc9e78330e418e3016f5467fe5c7ee9e0b13f10375720eee24936b4bf777008cfd1f08070db501e3aefb6c93673f6" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000000080001000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "39", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8ae1e3db8e3017986bea4d9fad3ec206d3ad28826588becc8623cf7108e11e66b3c27351d1cedae1bbfd9ada038de094116476826204dcde067968a673669dbd55ed9c029e03ef035746798322433b6a23dafa29a69eaf38d16387e7803a6798" + }, + { + "aggregation_bits": "0x000000000000080000000000000000000000000001010000000000000800000000002000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631511", + "index": "42", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb8bb8402eef655b5e5dd2b19757d3fbcdddd6872b40e18c9af1d3b8fda7d7123a79f0ecb116fd1f51c2a5dcfd463df1502803dd4d5f3a4e77defe4b7c0b979380282b6b9ac91bff0dc77a575aebff4a213ab88f02044756a11e22f4a3e8432eb" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000080000000000020", + "data": { + "slot": "8631512", + "index": "36", + "beacon_block_root": "0xeb0c08d706859c73e7172f788977b3cff4ad0e2936c4dd95582dd27dbbbdd69d", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xaa4b0c2e07e6b988eca22d355675cdceb52273b8adaa90c8a830541713eefe358bde676571bb8048c39e4c1dc61230e413daba8d34dbdc0c98eadd812ecfefd6f484c96db50ad82632aa13699f6510cc2493cac8143d0905aa1c0e5eaea47018" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000140", + "data": { + "slot": "8631510", + "index": "13", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa448bb31a03d8d4991c4d0bff68fedfcfaa56b2b715bd0df636ce97311139d22b7220520f8206060a6f2f14bfcfd94710ef60cc8ce79bf3407a92ac9562bb8f87b29cae92b192e4acdef407cf7af5b0a6b2c143d184a74b9457ea1547f73207b" + }, + { + "aggregation_bits": "0x000000002000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000004000000040", + "data": { + "slot": "8631511", + "index": "28", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8d5db320e083f09d225104d450de1af9faca98f133271f863f11cd85a6c34314298e45c95b66ad9e4812bbd10983cc4c035ce9542f91eae44669cbccb19033c874c4be3c50646bec9fce48013813ee764d8f0d0e11be9d7f77de6dc571f96164" + }, + { + "aggregation_bits": "0x000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000040", + "data": { + "slot": "8631510", + "index": "8", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x9918318a0d0e53213039d99b67bb3d4c8c8c6e760d2a318daf531cb503e77bb97ebb77bb52bb2eeda73cd83b17dc8a740fc8a8d8733131908af8bedb4721eff9782144291c40088a5a34dcf3f00b6ce8fc889912f7fd01cef80b407641795b6f" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffbffffffffffffffffffffffeff6fffffffffffffffffffff7f", + "data": { + "slot": "8631511", + "index": "32", + "beacon_block_root": "0xeb0c08d706859c73e7172f788977b3cff4ad0e2936c4dd95582dd27dbbbdd69d", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xaae3ec3d7f3f2b3fe4091a3c355f5e335ebf3a8d8cc1e3d159ce8922c49c1680bdbe416a2a6a845749ae109252a531eb071865d709e6b79467e15e501ed01ed7c2da20101443e3af6b3a38f91e7188187f6b505534b6eb8d6c0b434fdd5c5a0d" + }, + { + "aggregation_bits": "0x000000000000000000001000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000020", + "data": { + "slot": "8631511", + "index": "17", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8dac2f31202247c4794d9d97c081b5f0f187f4a6bcb6cae372d05f9143468951dcd9488ca31fbd4727f60e2dc9bc4b1d18a8d1f79ef19c64a0d035ef127dea40486ec2549e08218af66709be9a61ba41e8f07a5e20db93f18bfc0825e93eef7e" + }, + { + "aggregation_bits": "0x000200000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "33", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8600af9f38e1f010ef20400ce2255c5fd7716fe99541cd2fb83a0cc8865ff9b3baac450ad032d09580eb3b72e721faca16066a0d97fc152749cb60628ecd37118d2f61d1d5526374ab06edc61cd4b6b62d927c87254b26d29f22cb553ba63ee0" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000800000000020", + "data": { + "slot": "8631511", + "index": "33", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8254b51bf9a36d557afefd737fba80b97bf9d3e230518be396beaca728a32fabaf3a61041b670b254c3d2f31d110a73e197c6ba2ee93ea4f1492f8dc6944f92dfe1a18172b1ef78cf0ee221039b13886af835134225c850641bf9b8a036464a4" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000400000000000000000000000040", + "data": { + "slot": "8631511", + "index": "62", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb926adcc0d2d36128b2c9957171cac55ac3c9ee397c4e8f4651b8941210c381e938e0a7de217722c8669063e9c87ac40114813a5ba587fbe6b9ae74b13ac920d5bc1dc283196e1c6f8516ff4c5f60e10d5401a4ceedd4ae3a0831ec97db1795f" + }, + { + "aggregation_bits": "0x000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "21", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x914facfd0403c914108f1fa001ccc2b9bf3a54aa6e189f9d23c22254b785363a2ae1bbb158444ccc22a17c781b373f6e199dd12f27032653edf2b1a7ecba0b0fbebb7e261bb44d33fb40bf95acb3d41b47997b69f948797b548cf9b851b65409" + }, + { + "aggregation_bits": "0xfffffffffffffffffffffffffffffffffffdfbff7fbffffffffffffffffdffffffffefffffffffffffffffffffffffffffffffffffffffffffffff7f", + "data": { + "slot": "8631511", + "index": "49", + "beacon_block_root": "0xeb0c08d706859c73e7172f788977b3cff4ad0e2936c4dd95582dd27dbbbdd69d", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x856a03f298ba41988bc8aa2839c6bf1d387b24a086e73857939dcaf7902b37f022de604d00c11695b4de360932a86407016e6e91609abd7964f80f4169cb25b253b33ba0d47f555fa69228f3f11efc859098c470f18d046dd5701d01f0115c7e" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000020", + "data": { + "slot": "8631512", + "index": "38", + "beacon_block_root": "0xeb0c08d706859c73e7172f788977b3cff4ad0e2936c4dd95582dd27dbbbdd69d", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xab26e1fff20f4898e615bc5791e95a5753ecd292a05af731136dbb2835dc5a6e9a863d1e8df66a5202be497a32d429550b74271a06aa5b7432191b14ea8fd13aed719c9ff839ed9383d0bf86eaa4c83e933888443f591135e50d33ba35a7c0c3" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631512", + "index": "34", + "beacon_block_root": "0xeb0c08d706859c73e7172f788977b3cff4ad0e2936c4dd95582dd27dbbbdd69d", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x834b554ba91baa6ea4cf3d4e852a2885d6f04abb002a139e5dab1dbff60f48272af14027b76e3a4a391d406a56ec1ce701977951a43d5a09fac3d22aec58fb50d7274c2fdde568dece8b87a67418b41c68a24c89927942ad16762de0ef4b450d" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "10", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xa4d7dd332d16fefa5988346d73848e61bb40ace8da64f9a05163cf558644ef57974700e9fdffa56edfb8ecd8a37673e908b10eeb32b065391453e395bede891c5a939be445fff7627ef6bdf9bc3a90300dda43a6c2fa69ffa81d98b516062dfd" + }, + { + "aggregation_bits": "0x000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631510", + "index": "11", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x83c37557550d1178642e165c617c56d372ee8193197887e823bc466e3508d269d20ea16c144c94b1e95665e472622bb906fac5c0bb0b2ea70b144b683f46ff0b8de44f8c0f513c0ca7b962336fdc23f26d9c70af2d08c393a76257aec0bf9211" + }, + { + "aggregation_bits": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000040", + "data": { + "slot": "8631511", + "index": "52", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xb6b02a9c1c729948089dfb5f7d9fe974fae6cd229abca0b5ff9a52e587500941551f6f700fc2ae3510b7dc905a22cd2218f8825fc08b7b35f3484ac19341c4b222533a0f872a4a26b65d36b532408980fdf6fe056ac130c241be3c9e7d57c8c9" + }, + { + "aggregation_bits": "0x000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020", + "data": { + "slot": "8631510", + "index": "50", + "beacon_block_root": "0x5509ec83ef9b0cedaf406785234c03bdacbb0d7398e361cacceff28e9dcba9c9", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0xaf15aa9471b6951bff42fc87186422f5be3a9220b001d0a060ea2ef0ce79215d89e455edc8b26b7c180b9dede41a418e11fcdd5b7f46b7180f2dd447217151e666e221ee061df533059cf69e7fe3752fca19aea5b18899f008e711b39129c4de" + }, + { + "aggregation_bits": "0x000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631511", + "index": "62", + "beacon_block_root": "0x450216d36649e2c08db47a5bea94a17fd299d52b57981172e25792be1616eaad", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x92316b53d5ad6d7d8575e06777caf46af60b2f743fa29df4b60e9f6faa91f97dd82bade290ac6c767b69f05142d1e94a0fd205350b52afcc518546fb954629c937aba5ec16c59932b0140668a9a5058be802986fbd6168fc9606b71d7e995a2e" + }, + { + "aggregation_bits": "0x000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040", + "data": { + "slot": "8631511", + "index": "41", + "beacon_block_root": "0xe3e518a58e2685b72179c7fb86f63dca7a5717006a7ae0ffc9945495f4664573", + "source": { + "epoch": "269733", + "root": "0x508880ef7fe7cac1a601bcb00868cc41a523497b34d85fb71dc338f891eb049b" + }, + "target": { + "epoch": "269734", + "root": "0x89926ca6add36803c7239a78f78af0fb91df932f8af2ac34d4cf89998ea3ec68" + } + }, + "signature": "0x8eaf69445a5b52667d0af90420b2bcbf38920da1f5fe37be19a76975dfd59a932f5da17760751a699949f3b1157db8b50ff59a2ee8b76dd469e5a10ae2a05eeccfd44a940071abe53def172ae6df613a797010987887743e6ac97a6878d069ca" + } + ], + "deposits": [], + "voluntary_exits": [], + "sync_aggregate": { + "sync_committee_bits": "0xfffffffffffffffeffffffffffffffffffbffffffffffffffffffdfffffffbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0xae953c135ac95f1cd669a8caf9e89770483bdf3dbf138b2dfdd76198e9baac00ab4d807b518ebfa9e665a8a78dab9c210ff7a073b85fef1e75ccd49f0747c73fe850f9e87c63985f9bf5d795c28474c4ea67716e194a320382c6d9e560aebc9e" + }, + "execution_payload": { + "parent_hash": "0x5cb0f2822e542e2c6fbc0099aa8f996509c178bfaa634e04b728add8da42c65d", + "fee_recipient": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "state_root": "0xca4e0ab986d29ee5bddd8b4b9d9481e90d7bbd1ce7ee9e0d077c89ba03cdcf32", + "receipts_root": "0x09fdee17a2dafb2328798f9e47b44e50a5a8e5d9951929afa51f70fc222846c2", + "logs_bloom": "0xbffdca4be5945bfbba8a8ed5eadb7ff2dcefce7f6cb67b94cf81ad38dc9a943b76e541efe10b2768ded9de385ffdd9596b79a4ecffbafd407ffca3453cff2d9ebf7f57ffe3069abb7eebf66eddc460ecd9ef7ded9c67de1b1ccb7ce9e9f9cf7e3fdcdc2fbe974ae2be4cd35271d47b5bda4459fde93d3f0bead5c558997b18386ef38ff77e234f6eb7cda7d47bee4ab6b273b8f9ffb37d5be6ffb7dac9ffbd36ffc6eb33ffaa7f832f264dc5f9966fed1fc7c0fdf6fb719e7fb39b6e38dddfe3defbde6a7668fb7f2166e79fb8df91adbd73545fbf3ae59caeedf7df6937fc5039fafaff21fd720fd9f5d6a3e85798e0d7abde86f3a6afff6383fb0beefcdc0f", + "prev_randao": "0xb48f684132ba484557c07ea6964d6b3841607a44a540a24dd31cbbccb14f06a5", + "block_number": "19431837", + "gas_limit": "30000000", + "gas_used": "28138718", + "timestamp": "1710402179", + "extra_data": "0x6265617665726275696c642e6f7267", + "base_fee_per_gas": "44330915133", + "block_hash": "0x4cf7d9108fc01b50023ab7cab9b372a96068fddcadec551630393b65acb1f34c", + "transactions": [ + "0x02f904b40183222e6b80850a5254153d8303adab946b75d8af000000e20b7a7ddf000ba900b4009a808509bafeda9db83a7f381ce270557c1f68cfb577b856766310bf8b47fd9ce8d11a5b113a7a922aea89288d8c91777beecc68df4a17151df102bbfc4140e8c3d5e806f90407f8bc94e485e2f1bab389c08721b291f6b59780fec83fd7f8a5a0ddf68a16e33fcf794c93d34148c2e2c4391f4f3f27ff7a52703ddbcdb5c569f0a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470aa09edbdabec2e16ca41f1efb3c19f5f3d18c604847272628636ea866af352b901ca09bb3e24e1534bce24e9896f3377327d742d6c1d430477b7ebc070c2eb64e3147a0000000000000000000000000000000000000000000000000000000000000000bf859941ce270557c1f68cfb577b856766310bf8b47fd9cf842a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470aa094fe3377ad59f5716da176e7699b06460ce5b4208f8313f3d26113b1cf3d3170f8dd947054b0f980a7eb5b3a6b3446f3c947d80162775cf8c6a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a0051234925bf172ac8e2ccbd292c65330169d67445a0966551f13a5df19bb9321a012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8f8bc94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48f8a5a0d2764b6d304d6875dc1632274f53a7d27047ae66fe20f57cce9fb878c86ccdeaa010d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390ba07050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3a00000000000000000000000000000000000000000000000000000000000000001a0154bb98efc83b034ad81fbf23cc88c9737739df170c146ea18e8113dac893665d69443506849d7c04f9138d1a2050bbf3a0c054402ddc0f8dd947a922aea89288d8c91777beecc68df4a17151df1f8c6a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000c80a0bc7a0020d84346ad4ffdce908fbf7291f7f7f251a6381bd43583f02606a05471a04acbaeb9886f864bc654123665b91e527623fd2a9225e1371e061ecf5fa4dbf8", + "0x02f9017501829a308502540be400850f8839d18083061a80947a250d5630b4cf539739df2c5dacb4c659f2488d80b9010438ed17390000000000000000000000000000000000000003221ceed0394f7b8ef2b12118000000000000000000000000000000000000000000000000213a0fe9640f2a2d00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000001d283807630ffb876a5d78b8e0788e491449f2410000000000000000000000000000000000000000000000000000018e3bee84e100000000000000000000000000000000000000000000000000000000000000020000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2c001a036e76fbcbc86dd2d6fe3d37e65ca3e83aec7259a850b28686fece7105c1d2a24a06c4b4d44d359c9360b8ebf2554eacae621824441f00d99275be3bfd01f554239", + "0x02f906b4018201e28402321261850fae8bdf9a8307159c943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b906443593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acc300000000000000000000000000000000000000000000000000000000000000040a00000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000004600000000000000000000000000000000000000000000000000000000000000160000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000661a375e00000000000000000000000000000000000000000000000000000000000000010000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad0000000000000000000000000000000000000000000000000000000065f2b16600000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004106882e8110f296c60d47cdf7cdc88434fe9b82d97c4dd92b797f7e8d8a54321968be5ba5e9f6ba051f28d593966a89493c697b93af24a2532df7d3083a60d3ae1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000858b793d000000000000000000000000000000000000000000000184dba000c3fc6b755100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000042dac17f958d2ee523a2206206994597c13d831ec70001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48002710e485e2f1bab389c08721b291f6b59780fec83fd700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000007a922aea89288d8c91777beecc68df4a17151df100000000000000000000000000000000000000000000000000000004b1e74327000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bdac17f958d2ee523a2206206994597c13d831ec7000064a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dd04cee96c94d5a7ad100000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000e485e2f1bab389c08721b291f6b59780fec83fd7c080a0d70b4c189764065925757d8bd2e3969810dd6e3b440b8080f1b6feb7c5562baba042a71e23d5021929ae4371a3b2a7c55373d58cbc5b4b4a228463ec1e1802f6c3", + "0x02f904440183222e6c80850a5254153d83033bf2946b75d8af000000e20b7a7ddf000ba900b4009a808532577c909db84e09177054b0f980a7eb5b3a6b3446f3c947d80162775c04b3cd89213a7a922aea89288d8c91777beecc68df4a17151df1e485e2f1bab389c08721b291f6b59780fec83fd702bbfc4020f036d3f606f90383f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8a0051234925bf172ac8e2ccbd292c65330169d67445a0966551f13a5df19bb9321f89b947054b0f980a7eb5b3a6b3446f3c947d80162775cf884a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f859941ce270557c1f68cfb577b856766310bf8b47fd9cf842a094fe3377ad59f5716da176e7699b06460ce5b4208f8313f3d26113b1cf3d3170a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470af8bc94e485e2f1bab389c08721b291f6b59780fec83fd7f8a5a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470aa0ddf68a16e33fcf794c93d34148c2e2c4391f4f3f27ff7a52703ddbcdb5c569f0a09bb3e24e1534bce24e9896f3377327d742d6c1d430477b7ebc070c2eb64e3147a09edbdabec2e16ca41f1efb3c19f5f3d18c604847272628636ea866af352b901ca0000000000000000000000000000000000000000000000000000000000000000bf89b947a922aea89288d8c91777beecc68df4a17151df1f884a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cf8bc94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48f8a5a0154bb98efc83b034ad81fbf23cc88c9737739df170c146ea18e8113dac893665a010d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390ba07050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3a00000000000000000000000000000000000000000000000000000000000000001a0d2764b6d304d6875dc1632274f53a7d27047ae66fe20f57cce9fb878c86ccdead69443506849d7c04f9138d1a2050bbf3a0c054402ddc001a0d54fb22d1cbfa827b9fdd559512572d4524b1085d8f933823d76cd5c678415d8a0509fa03693e2371027c87ba95f77c2f916d6ba1ccb0bf94b9d715e053ae922b8", + "0x02f9015b01824c5d8501dcd650008513b4c10d008307a120947a250d5630b4cf539739df2c5dacb4c659f2488d8837f0fd2491998000b8e47ff36ab500000000000000000000000000000000000000051cd15f4c8240f4719d8880000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000479cd4c24c567cd18520f0d087acfa5f89fe6c890000000000000000000000000000000000000000000000000000000065f2aaf10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9cc0809f82bf8141d135009cc6b3327b566a18577812eadc58774dc0e20b8891c604c4a069fd890b9318af6a641380d6da52ef1632c8bc275ab891129718b66a4f76a66c", + "0x02f901e50183222e6d85db5c95740f85db5c95740f8301a3b5946b75d8af000000e20b7a7ddf000ba900b4009a80852960773c9d9b7f371ce270557c1f68cfb577b856766310bf8b47fd9c03cafc6d06f90153f859941ce270557c1f68cfb577b856766310bf8b47fd9cf842a0b39e9ba92c3c47c76d4f70e3bc9c3270ab78d2592718d377c8f5433a34d3470aa094fe3377ad59f5716da176e7699b06460ce5b4208f8313f3d26113b1cf3d3170f89b947054b0f980a7eb5b3a6b3446f3c947d80162775cf884a0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007f85994c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a012231cd4c753cb5530a43a74c45106c24765e6f81dc8927d4f4be7e53315d5a8a0051234925bf172ac8e2ccbd292c65330169d67445a0966551f13a5df19bb932101a0e432951488a733bc6f10ac87caeff25ebfa3693ee0362134588b580d99d358d3a0036bbca9bf15f825e87ed5d98bcc4d56039f1e72a6647b87a44be152bf7445a8", + "0x02f9015c01823437850f30220c2b851ac688be008305c7ea947a250d5630b4cf539739df2c5dacb4c659f2488d8829a2241af62c0000b8e4b6f9de9500000000000000000000000000000000000000030fd894eb7a45600000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000067bb6879b16ef6bc9035e92dd6c24facacb931b60000000000000000000000000000000000000000000000000000000065f2b1840000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9cc080a0a5110daab9292819ba68d08f3e8d1ff79b371f70c4c9bc83fe4d49bf9a917834a0665ac7171f7393d00c68657189fe84d8a09db79502524a5e5f92091acd997523", + "0x02f904930101850110e5e5f6850d422605738305357b943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b9042424856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030a000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000001600000000000000000000000008881562783028f5c1bcb985d2283d5e170d88888000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000661b88f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad0000000000000000000000000000000000000000000000000000000065f2b17f00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041ec33c6271e090e54429664d1291e04b4de51f1d1599563f11efc1ee3cef82a237ffe73ae66690769bccf49e0a4ce604881b75d31e53c8606b07177c88832940e1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000006fd727d954471cc8000000000000000000000000000000000000000000000000000371c7a0a17166eb00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000428881562783028f5c1bcb985d2283d5e170d88888000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000004402ba72aea4abd839a0d1db7d7273b1bc9493cd0000000000000000000000000000000000000000000000000371c7a0a17166ebc080a07ac12b4ea5fef6892b2c4f45d16d4fc4062d4f5ef59c17ca4e656776a91f779aa0767f63688afe873454cbcc9edfca54469a2c62c97b42c61653ada04adb84e9a7", + "0x02f9015401822b6b850aabbc443d850aabbc443d8307a12094360e051a25ca6decd2f0e91ea4c179a96c0e565e80b8e4ccf22927000000000000000000000000b62132e35a6c13ee1ee0f84dc5d40bad8d815206000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000001a590a1dcc7d1aa3388000000000000000000000000000000000000000000000000293cfb12b6a0d0860000000000000000000000004c54ff7f1c424ff5487a32aad0b48b19cbaf087f0000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000001c080a0a13a208f888da29bb1a436d468bffd0d96c6c8db1c1ef9d862906a112568354fa03a6bf177eaab40161405f299639309d96606ea537f2caa9094dafd28d4ce1dd4", + "0x02f9013b018204d68439d10680850cc9aa78c083031e1d94f3de3c0d654fda23dad170f0f320a921725091278802c68af0bb140000b8c49871efa4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c68af0bb1400000000000000000000000000000000000000000000000000000c0466456cecb64500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001b0000000000000003b6d0340f4041d29ad20219188cb6f4a3ea0fd1b8192a856c080a05a961ebd36a8bc3c8811d9f7c26085d96d91967b069844a9e6240c443aa776c7a0720e8ee65b702c4101f1f0359d0c74a4f64653a23c6bb559f0019742fec01829", + "0x02f9037a01658405f5e100850bdfd63e008307568e9468b3465833fb72a70ecdf485e0e4c7bd8665fc45881d24b2dfac520000b903045ae401dc0000000000000000000000000000000000000000000000000000000065f2aa9000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000124b858183f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000080000000000000000000000000637a7db1da1c54b628fa5d597632b8d3d9c266660000000000000000000000000000000000000000000000001d24b2dfac520000000000000000000000000000000000000000000000000361401f699b7406e8e40000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb88881562783028f5c1bcb985d2283d5e170d88888000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064df2ab5bb000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000637a7db1da1c54b628fa5d597632b8d3d9c2666600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000412210e8a00000000000000000000000000000000000000000000000000000000c001a0a4e5008b47b5e17669d14856dc4e6c622c8acdc12decf8f34dd2c76daafda05ca079ce55de2d7276772335d97833960b6ce6cbd973753367e1d9a1305f316b9599", + "0xf8ab8203a08522c334157f83030d4094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000095362c2df7b2afaff345a6adbb19ed68e9b1e5fa0000000000000000000000000000000000000000000000000000000000f8b7e0269fadb6373e31560e5957c588df5755e0ace18e74e558871d6549123ebb68fbaca020ef57772468b4a9b2629a69308b65823f16883a0539ad5b824ca913ea3cb86a", + "0xf8ad8303a84885151165cc3e83015f9094d26114cd6ee289accf82350c8d8487fedb8a0c0780b844a9059cbb000000000000000000000000710513b93c5290fe7d1cecc31d3e15a9628ee77500000000000000000000000000000000000000000000050a33598e3218e0000026a08c153454d4ab15fc9d650d5893b1b49fba29bc079f2ef8bca6526b4239fbb848a017e3640a6d6f2d2bea62f1fe1af39d1262e7e77a8c0d09ef1d74a23afb67ceb0", + "0xf86e8312edbd8510babc5f0082d6d894aca60a27d73947a6b70d9573dd854ef009bf094b8785e382247de0008026a0015d8946bcf580c4755617989d7ab1c65c2990b00d38cb3c4931ed3ea53268f0a06bf359f294159f30d8aa887426a852b12814bf8a8099e6271ad597d37ce94106", + "0x02f9015b01820188850f574a5a40850f574a5a4083034cc794f3de3c0d654fda23dad170f0f320a92172509127875029ef9204b429b8e49871efa40000000000000000000187cf0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004fec5eb942a44300000000000000000000000000000000000000000000005172739a75a59691fb00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001b0000000000000003b6d0340f0f93a837cce0054b7c0128fd5ae507f54aca0e73ca20afc2aaa00000000001e96c195f6643a3d797cb90cb6ba0ae2776d51b5f3c001a0d2167b629edb7e6a9d5386996814ebf56a7dad6beb0c90747ead2ba86e36ed29a0548b78d63706aa78207e50965eff54c999b7598f91531d2ca6f338892061403a", + "0x02f8b1010b850f0ecdfb42850f0ecdfb4282a30f94e41d2489571d322189246dafa5ebde1f4699f49880b844a9059cbb00000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000006b10a18400647c0000c080a0520b5000347377c4d1a5f29f76101a8b105551c0396a20f61518f9701841eb0fa040fb134c25d38c449f8d6c3c650fba9cd817e3b8c2bbb6fb50238b8c612d0ff8", + "0x02f8730101850e5fa55382850e5fa5538282520894ff7a1084b50dbf07fc970ec42f2fb4cf1de8d635870d63202bff8ece80c080a0d7583160a9eaffa2f48665f8f50161b29985364ef68b86bd9c2b853664e716dea066446e0957f5a7ca5f8fe756202318c923b303e1db4abf6c53d30050a9fb412d", + "0xf86c81a0850def3fd16b82520894c8700fa8338e990b4dc920568c9d43df748306bd870d23cf3baff7ea8026a0816e5b8e474a2f2286b1cf2b838e63915d0c6026e4d620ee90a453e907e3c528a0322756d81a9524bb20bf38bbd51238bb2d317db9eddf42cbf0fa3f06e92b272f", + "0xf8a980850da7cffa7f82d87a94f411903cbc70a74d22900a5de66a2dda6650725580b844a9059cbb000000000000000000000000af91f2d992f37450695bfdd15f26362c40d710880000000000000000000000000000000000000000000045640b8bfa7b44f9380026a00e124e09655f0e197aacdf3ed5979a8308449577c86192caa9dca6dd8e7d5c5fa01d7d9e637fa831426e9da71596108c98f8d1ec2f6d73ed493f212b90e61a65bf", + "0xf86f835d07d3850d4576fa0082c3509413d142b7cc2c5b02ba8053a7eb5a68d16f44b52f88016345785d8a00008025a0832f200ae1811363271ffa92ed822a1755cb0d741c49008197fcec4f5e367828a03d2e8ca2dd1818ff4edb43b7c9c492a6d3c64e2a747e5aa3e358b1966c94d003", + "0x02f8740180850d0920753f850d0920753f82520894c88f7666330b4b511358b7742dc2a3234710e7b18809b0389825dd240880c080a007b71ffcfa989a5b57707b32949ffda068f0598a425ef42770dcedb8f6b59412a010bf8db768ee58088f74e824806066af8631f48fad75a7c5dc0a4be46cc72aba", + "0x02f903b301808502f4fa9f00850cce4166008302e96594c059a531b4234d05e9ef4ac51028f7e6156e2cce80b90344e1c8455d00000000000000000000000000000000000000000000008a22af5c529b9f0000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000008a22b7e5f992b9062a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000125b25c828c914d3d336fe1db9bed49886729be2d88b0f21e8967f3e23ae8548315c8c08b6354ba860f6d1b680d4f6eed04e394bfde3ab96bf6d1c6203be68c17b99871806e775e36a9d27ed783034164f7cb2c2a7227f4218de0b86b33020daceaab8a9a40c5b986471067add1cd9b392ba37d0e195147c529b66d3e07f1f748acaa383752b3f2742cb99b986462899cd082947dbfe7c9430540b374ec23fcc435c212e72e8fc80fe0ddf1261af59ef66849c3497f4997af2932cb06feef87194ac6f37ca3b577a3cbe764030048784f2dc9bbd21ecd0121a7f6413b2fb77d47c51a3f065731a3122899c4f967475bae44e87792598aa0d1ce0cae2af3f8de75502593b78f42bd3f22887a4fda93d01e75bcdbee061cd4ae41fd92d17efc43ac2c6b091b1c5af4ec138bacecf17fe968408a5f6dd91eb09b6b649f5d579f2be42753be136a7c76aa63dfd5594a78eed1ff1bfc19681837c9d003a2ea9217941436754398af6fafcdec394c601386614891260b3ba9b0fc62571380a77d3e1c374a1777966baf36dc8cc5f4d0c7fe31876428aa616efe94616aeaf74627b3f00bfc003099ab80200205567d103920229171d9211ec23859d159fff215550e91f0267eb720753f9423aa17accea01ce7ac4767da5678c91eeb94e7ab9ef342146c266220d0d69d2ea75f1abaa361fa2325dcb8d95131a12081acc0dbca9d16b8856ecef3f38904031cbedf8f8d77f7533385b9176d0ebe964055755bea7fdd7a73b3bdd1e5318b1af41fa68346461d94bc4793ebd12fd81ce5d0b74b62d096838eac080a0a3899e36bfa7ae0aef62ce893a261a263b14d52e90cfa06692e329c4e0aaa9dba01dba59634e4cbc0c3eb9e7740ae5b4aec63c1210c6dd0896a2fafebaffee9b3c", + "0xf86e8308b61b850cc5fa7ff8825208943851451f023494a7852d95356c19bef700f11de9877a4e6c9a8d068f8026a04d69f82c3764f637bd77ea5ac1f8a1552193acff987f408d683dfd2b75b20183a065c65e4ba60c875164017511411ccf836ff53ce31b70849188564884cd6b267d", + "0xf8ad8303333e850cc06ca3d5830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000004d1ce86f72f03fdd683bc802c7f5c5fe948a027c0000000000000000000000000000000000000000000000000000000004a62f8025a0f24eaa90a2c97e748c719a71648293e24e01b74b11724b3d91006a57a18530f5a054c1a660581f6db579c889031eb9ecb06d530384110f6747907497e44e91b6cc", + "0x02f8740180850c96e98dff850c96e98dff825208943f6b569d627896c41711c1925d8070527c34e38988161f9c2a4b66220880c001a0b1e740a79b0567aad3bca673a6ced9ae7f2eb939f82063da4c529c2b003b725ea0111a29b43768cd3cfba84e9171eb23e630e08cbc254456410e61e50cc0392162", + "0xf8aa80850c92a69c008301117094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000a6fecf2344f5bc04ac6a67fcd1938d6eb5195c55000000000000000000000000000000000000000000000000000000006fe318f825a06b64fa4d2896c6aefafc7f0f944cd11440c13028455dfbdbcbe45e6090bedc94a00567579905de4c573dc9d5c3bf08368240dd8671f603f8c9a080c5b8688e6dc5", + "0x02f901f50182214f8502031b2cab8516e070d5ff8306387794767af52d988d1241a346851a1b39ccd11357376e80b901846ac56d4e951ceec2ea1b3b1b2ac1b07b819ea391c6fb5b75a470425499dd77dd956d78ad000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001208e349991b942ad58964ada2a7de1036823b30cbfbd3122ea8984fc38fdc544b589c2891c6485ab014452bd8a12c396d802409f49295f22ea8984fc2be2d00c827d3a161dac8be4ff2d79ba957fd847b367b2347b295f22ea8984fc38fdc545f38ede111dac8be4ff2d79ba947d3dd5f11db8d89204bccf04969c1c2d81c0c3e6eadf3ac76df47172039c99375fba412a2225f546aa41e5ea88703c1257fcf7d070531b13f0c4c315f47186e0131a3d966ae404cfe60cb5d356a8a19449031c01c65313356ca14ec69f5a441875d61bfc4058ed73152a4e2895668c6d81da2d3c3ba9a94bdae8f440a63e47097fd847b367b2347b295f22ea8984fc38fdc545f393648c27f6be0eb81c917b672b69270134d2579ec9b5d115dd220dd501f04d3cc080a05c211382dd4b2791049e1c2eb4bcd70a7ebac8653591aa7f470e425f0b59eed1a03154927a96ca6bce1c92ed5b99b128cdb5a740c2dab8731035ca30271a8183b3", + "0x02f9015d01830154918501dcd65000850eab17b600830aae60947a250d5630b4cf539739df2c5dacb4c659f2488d88106033bf82f60000b8e47ff36ab5000000000000000000000000000000000000000182cdc4f62c8ca30c7b140000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000021375e8ff94a9acad555e0d8a4f17c69c5e33bd50000000000000000000000000000000000000000000000000000000065f2adfc0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9cc001a04f64cce6e85bc3440041b3bd9f659d8e512414b78fac0c4a234f7068474d5979a0797bfc1c30fbecd372a2a4d11d8cc08c449f822428edb48e280d24bb28320138", + "0xf8ab82e083850c2ab8a11f82fcab94dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000ac26b31b4ead466dff55b967903b3caf9577df9b0000000000000000000000000000000000000000000000000000000014dc938026a080210bd7705207df754ed6e6d5c4ffd81c8a7198887e0ac13f15c2a6f7416266a04ce56a6684754c995f3662b2c12a16a122737c6faf76ba5a34d233b87cf93972", + "0x02f8b50183010e00850bba6f5042850bba6f5042831e848094bd100d061e120b2c67a24453cf6368e63f1be05680b844a9059cbb000000000000000000000000f5aae5276b1ed63544edb779dcd7e449a7d8017b000000000000000000000000000000000000000000002b39036ce57ccf130000c001a0d13bd010a8370eaba6c2b85cebc78758f216f8b80a7d5b2028d477f251b478c6a0442e6fa4310adb03d9958a3c7fac3fd9acad9f832f648e0cbe87e63eb885f054", + "0x02f8b1010a85015fe197c8850dd0a712a282fc8d9425b4f5d4c314bcd5d7962734936c957b947cb7cf80b844a9059cbb000000000000000000000000e7250ad3e16c6e951dbae9e7d178bc0ebaf9bce800000000000000000000000000000000000000000000003635c9adc5dea00000c080a083342b5b702f801b07c8c37ba9b358c8879fcdd995c40e350fa83e771b7a3f95a0671509edf7e32e1d9162d4b11f403838cb4d60d37ef2bb6451724333dc2f548a", + "0x02f8b1010a85015fe197c8850dd0a712a282dbed941ce270557c1f68cfb577b856766310bf8b47fd9c80b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a01741a609fbab5fc00b91ca5a7f0e90f5fa2da99c83b4f86339b97a066fec720fa01a76ad61adcd119512439e26ba65e859dc5e93b70d93a7ea085d49252f5442a2", + "0x02f8b1010585015fe197c8850dd0a712a282dbed941ce270557c1f68cfb577b856766310bf8b47fd9c80b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a077d3134cefefbdb13c6379aaa352eeb67f927b87494b84a4957b7f9b313568e3a05ef84f4583d7a6cce1aac7a54d33b8ef58dbcd17b7fe69d27e1e49a16f82325d", + "0x02f90473010b85015fe197c8850dd0a712a283035de9943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b9040424856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030a080c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001600000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000661b88da00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad0000000000000000000000000000000000000000000000000000000065f2b16200000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041eb0bebb9b24b4109bb4b5774799e230aaed199d6417881aada2590f90ebc03b87282a6db4529e45f57040f94c0e6020b800212d1cc98b80a9a9f71d9ca168c511c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000cb6ca86f57525e1052017fa5000000000000000000000000000000000000000000000000091384da0a48737300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c916f41c82aaa584ce0912e4c1e731c8259ca306000000000000000000000000000000000000000000000000091384da0a487373c001a09f22832ca93e3b46f6f4f6c8d8df7c351e6a17b892255a4fc0ef43c4ddad12d8a01c4ced5357eaaee98b52c1da26a7f828e10200ebd9de94b9dd109ab66092e7a0", + "0x02f90473010685015fe197c8850dd0a712a283035dd9943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b9040424856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030a080c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001600000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9c000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000661b7ac800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad0000000000000000000000000000000000000000000000000000000065f2b16000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000410125606f39d2916aaad8ce811674725e537a40131f8fbbc28b5380330c6368740fbed970b6c46a9f3622b9b1cf3086344be41be48001ee67c29132913fd1ea581b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000001d50debba86cf9c4a071f5c20000000000000000000000000000000000000000000000000140dfa843c70af500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ed564d4a96cf7aaf9770006147a597eae55f3c580000000000000000000000000000000000000000000000000140dfa843c70af5c001a0bf2a2178f25e1b82536c4e7b186d8be7414c844138fdea6a5ba1b9b0ebfec056a066f45ee787c89524e5c4225dd3facbf0910e5ea2e35ec16565f0b6dcafb6fab1", + "0x02f873010185015fe197c8850dd0a712a282627094e57d40f34f40b67c7b7b1dee0765018479849b8c871a4a42c356800080c001a08420ceef61483108d9d205f481faa4dcf638c7f2c385efc80de9afc999e8b202a03f4e3a47e527e127ad1b6fc00733475e2a987e58e07230e589c394eee8cb90b6", + "0x02f902da018085015fe197c8850dd0a712a283035fdf943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad873a248477002800b9026424856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000003a24847700280000000000000000000000000000000000000000000000000000000000000001000000000000000000000000009fe3e7148415e10a8812be17e405650d31f91a3a000000000000000000000000000000000000000000000000003a248477002800000000000000000000000000000000000000000000000002d86a4d20ee7faf9600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000001258d60b224c0c5cd888d37bbf31aa5fcfb7e870c001a0c57cf927cdc7c543566f58b447be7e0d61a1fd5b23b1ff5873f387031b4c70a7a042faf8d347a265ffbd36ad35ee962020ddb48e629bb99c96e0f8cc063f8ac4a7", + "0xf903ab02850b9cb52e848301f0d594c059a531b4234d05e9ef4ac51028f7e6156e2cce80b90344e1c8455d00000000000000000000000000000000000000000000008b80d253a5feba0000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000008b80d8228c84f7b00c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001225a94f1f723fc33d5167fc43903dc9353f4f19f428ac36b0a3fd477343cdfc4e9f8c406f1b977f765bc096344fd3d2980085057c671cabc763803db36b9d96804615f13139c222f054d34d67e42dd1fa049cfdff64f827de1cbc6d549e3eef89832a18674b2a0b983e8873f7f8ac67897b71776f2d2bc1ee8057e16b1fc9d280971ce2259366ae4df90c802d9d4e0f00ae7f7a0d34f6ad0348358fed204020ff1b920c3877bb5f57fe795d129914d48730b67edad54c0345f80989c36736680fb646c4d68523b00bfa5ad9b04fc5fe6a0af064af8355ab8af186ab4e944d00d6ef1c35da7929ed3924661dfd31e155dc17cb5fc940e62a91ab01481d7f7f6e527945bd2200c654b4a5d706b5f051425abd5f1a2cd28904d9505adfc4ed3edb0bb6b38a1585c891b98a146fe173dc9a113927d40b4a99dac98fe5d61b456fb938e9214ebcec741a12ebc24cadf7a2eec6aa832dcb37f8a2ea980f3776e0e04e061d8871a61d7eed512be66b0c5cb544e1def1aa1db20ee6f680e5a2d4f5c3a5663abe1c71045451b3f33a32d42af448881022424dcc910536e38de38666fd8612c99568fe3f7a990d0f00b2714b7105fecfa26db4a56489b71f088208342c09247751e549c0b5d29e5f473b4ce26846c3973dc05e90bae1daa1156eb69fb68c9d08a7d0a6af883df133f4dda40a2bf9d18ea6e9b3b90b30974c74803005be777e854f5656ba40bc7ae01e2b8cb95cffecd7cc1fa7a3a3e69fa5faf5d26f1771383bdd1e5318b1af41fa68346461d94bc4793ebd12fd81ce5d0b74b62d096838ea26a085e6009449a5343549691699bb5d772ec8efbdec71bd506b08377cd6fa6aa692a02c95599cff9cb2d8752868f37d43141233c8d2a521e2669fdf4471747d80b164", + "0xf8ad83a549fc850b965e63898305573094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000eb132715c8560fd3832baceb36699e4c275480ea000000000000000000000000000000000000000000000000000000001cd9410025a08ac4221d205e2615a3955ba50661f0eac5a099d2dbaf13898367f86511365966a06c39ae260706750e747e74aef8f42b36c7360ccdca60b4a1b27a35528e05655b", + "0x02f8730108850b964f2148850b964f2148825208946cc8dcbca746a6e4fdefb98e1d0df903b107fd21874c9adcb727bda680c001a00c2ee154ee471824c18edea9309f2f9480d905924ab956387cfb0a6252cb6d7ba058f4b65bd333222be823b34186061bf634622f53b9ff5146befa50445aef1fee", + "0x02f8b1010a8501330d149e850ee2688b4e82dbf694be9f61555f50dd6167f2772e9cf7519790d9662480b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a065730e98a96c93448e582150b3cdefd1d2472f54d08f997adf31e171828764faa01369bac5247c404c93795ae163f8611c4277b7215bc7c698f20b38a1e44bb80c", + "0x02f90493010b8501330d149e850ee2688b4e83061945943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b9042424856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030a000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000160000000000000000000000000be9f61555f50dd6167f2772e9cf7519790d96624000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000661b88ef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad0000000000000000000000000000000000000000000000000000000065f2b17700000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004134866262fbf32e9c6a99477564fcdb5b81308e3c6ca7fa2afc6d22f76278c5db60a39206de9436fc96f5072e07cf09bad8a9e6f852b0219ab419e2dec1bf03061c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000bca24a51b82889d831c0000000000000000000000000000000000000000000000002018643b999fdd1800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000042be9f61555f50dd6167f2772e9cf7519790d96624000bb8a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000006997d59f49ec919caa7c8b1c5bf1eaa03842dfd20000000000000000000000000000000000000000000000002018643b999fdd18c001a0e8f24ff09f347e6c0b15f6bd0a98e881fa543e351ba0c652827f5824ee555b67a04da8b7a7432c9c3ec967c81e1817e30d04e896c636b0a9634a4929e81cd6ae8d", + "0x02f9035a01058501330d149e850ee2688b4e8303a1df943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad87976c11aabce199b902e424856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030b090c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000976c11aabce19900000000000000000000000000000000000000000000000000000000000001000000000000000000000000004732dbebdf7e45faf5cbb0b5923de36392e551b500000000000000000000000000000000000000000000000000005af3107a400000000000000000000000000000000000000000000000000000976c11aabce19900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c3f8143212871014b472ea83285af7f25928dee400000000000000000000000000000000000000000000000000000000000000400000000000000000000000004732dbebdf7e45faf5cbb0b5923de36392e551b50000000000000000000000000000000000000000000000000000000000000000c001a0f251bcc144ee6c6728961ff9b607a34f0685f98e03ec75da8e419a7513c5173ba01b63fb36405c5fb3b3c1da8fcdd5d2e35fd34a6260c8d341347d890530fe4874", + "0xf86e832905b2850b7fb7760282520894c027ed26517deb1c5ab035575f2572f8d8162be487072293978f32e08025a06fcfa4b5b68cc408c008cb6d3c4bf25ce9de5ec57f4964403c7b145d7372e225a07db25c643e7ef418fb104f979d6a174c5165ec2002c8f707757f436e59224071", + "0x02f8b501830d046885012a05f200850d69a4441d83030d40949e976f211daea0d652912ab99b0dc21a7fd728e480b844a9059cbb000000000000000000000000082547bbf74f4eb48138e83fb1d8845e7c239a70000000000000000000000000000000000000000000001eaf49fa6a83627d8400c080a00eb64f6b37eebbe016ff20ceda921d786dbffd8d3d33c1f403ae74916b7e557ca01df31dc95ac330361ecf3fef85dc4d3cd273fc0343248516ca2d652ad8c2fc33", + "0xf86b01850b68a0aa00825208944e5b2e1dc63f6b91cb6cd759936495434c7e972f872aec0a10f272008026a0af770d803d6ff43bbf46c58195cb438a87bb497d4bba6656584655d67ffaeb54a06ca124c1bdb4b4a283f437629e73e9d0486dd81a589fddbecc33642a0d84dc88", + "0xf86d8208cd850b68a0aa0082520894890026952ed29515d96098db1250492669692f67871340c2cf1b2c008025a09a4cc2310ee789f86ec041761741a601063c43077451f2506ade19faad0fdb1ba048e9ce05a675712c238db18f738796b7e9e3799dc5c3f9767173f7e6ae6e80e0", + "0x02f902d40181a6850110e5e5f6850d4226057383036231943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b9026424856bc3000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002080c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000009580cf5d9870000000000000000000000000000000000000000000000000061d4e22009bf0600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000006a6aa13393b7d1100c00a57c76c39e8b6c835041000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000400000000000000000000000005a03ce41153697783d7ea8e0fb322f8bc61f82170000000000000000000000000000000000000000000000000061d4e22009bf06c080a0a7d3d023f9a2080ad2810049d7a2720c70789d61c934b545ed3e7166c9b2a25da05e748f58b1bce7b0c9ff172a98b187aeff4143d427ee90a495193635cff28475", + "0x02f87601830ecd7b84fa47a7c0850fbbdd93428252089405922b84abd2b3451f17913cd38ef2352177583d8803a38582c41b800080c080a0b66f717de0184280be5ff97dbee3305fdb12b2a079787a0a1e9601f727e4bda1a03d6dabcb515ec812463f311e002b81aa27a6151bef3f4900ed9a260ba922e743", + "0x02f8b40183032be684fa47a7c0850fbbdd93428301482094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000dc2f340ee359ac809b991160204ab92f6da62e18000000000000000000000000000000000000000000000000000000001df8e1d0c001a0090ed221942618d7d9d7cb8bda17028dcd1f45b6f1f7a0a1b1d158a504af867ea047519808a8384bcc0af521a1c84de12dddb70d5f732e0eba459a98488b728148", + "0x02f87501830a29b784e41b62c0850f529d556a8252089426544ae074f3f4a51f344c38825ddd32424c94bf877a369e2446000080c080a0a5956b53be8203030d87a786e099a62c26de8a11d4b5657d88b3dc45699c5cb0a01f32f207b9b2852cb5bb42dc5904c5ebe6b818ad9f4cbe5beb7f1a24e647e4f3", + "0xf86e830aa3ec850b2d05e00082520894e9e954671db1ad060398951b2be1841d293dd44987306e3b559400008026a0225af81b392d9f053749b318e51bb3d7b60da0be7bc67e4b00d9423bdd6f047fa015a94668988c165e91ab67cb9af4b574a4a67f24d70e7cf7674fa12dc1a3e371", + "0x02f90292011e84be740e988512289ef29e83036b76943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b902243593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acb700000000000000000000000000000000000000000000000000000000000000010800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000006acb2ccb83332e66c2d9670c000000000000000000000000000000000000000000000000000000005002231c00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c001a060ff4ed8117f97aefc70b422b635d594e607b4780abb274428426b41faffd915a06dfffd7e0a9554270e5b38671c985ccf0590d4c7e44edb53f40d3b682b9ecacb", + "0x02f90492012184be740e988512289ef29e830417f8943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b904243593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acb700000000000000000000000000000000000000000000000000000000000000030a080c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001600000000000000000000000009760aa5124b8255fff140a76994f91ca22d2647d000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000661a376400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad0000000000000000000000000000000000000000000000000000000065f2b16c00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041612fef4fb4ba56322a8d1e4cfc1635ee5f34deb6ab07db91f80015ecf50c717376450e9ff133713cd2d0f456ff507129b465d98185e4a129374646eb5fb9ccd21c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000a80715fbb4f80000000000000000000000000000000000000000000000000097fcac4a90ad9500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000009760aa5124b8255fff140a76994f91ca22d2647d000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000097fcac4a90ad95c080a0893245f2cbba47ca744104efcfbbe50f15706340b93e2ebacfee83024f26a7b6a054ca7ae631f571bdd654f16f06e6a02a83bd20b9a4c1f6108ba5135a698aaa7a", + "0x02f872010a84be740e9885108a9b860682520894b201d5c0e79fb09ba79a5d11950ab78f57bf3b70872386f26fc1000080c080a0d9b225d61474a24cb41f5c782eaa926c4926f7007d81e892ba579c390a139583a053035b248ea719fc3ae8a76d424e83fb9979a927eec03d4227f450f891ce2eda", + "0x02f8b1014284be740e9885108a9b86068301b9b194d9812f24f34e0d727bbf6ea7caaee05b7f7a260380b844a9059cbb000000000000000000000000a2d12ad88c21830f438620467241213e8e8616fc0000000000000000000000000000000000000000000005d723aaf284072c45d5c080a08428cf84fd778c498ee2362997f9e3671572e5ae3a7b4253f641e451e35666dfa0512bf68e37da709821827252354eb35a8b4e6dc75736f363c3fcdf043bd4c4c2", + "0x02f8b1010f84be740e9885108a9b86068301158194cae6ebacef456e5a942afb40fc99f2f38639ef0180b844095ea7b3000000000000000000000000b9b213d92253a405977ffe38fe8e2bd9c14457a10000000000000000000000000000000000000000000058f03ee118a13e800000c080a03aa2432066cbf8f82753a0f81019fe406f9a34a1e9c98648260e3de2386fd79ba0061ca71e72ae5ee0094364d735de68fd9fded576f027c0d77de7d52ab8955041", + "0x02f872010984be740e9885108a9b860682520894a40d8cbb65b546a1c1740fe35feddc1eec6983b2876a94d74f43000080c001a04bfc01b60f0d5ca4b4b8d9c9206e36271fec9f3854869182a6ea82374f194223a0595dc7d9eca0b94d88a5936f2ad7c065eed10bb85fa886ca15e33e1567cd7380", + "0x02f8b1012b84be740e9885108a9b86068301324694ddb3422497e61e13543bea06989c0789117555c580b844a9059cbb00000000000000000000000042a289b725980361669356e0fd6fd52ab386e1f200000000000000000000000000000000000000000000004b56763adf67a40000c080a07d853e7042e5454ed9920381453b775dc0331993baafba96bbafd50b002e2f29a020a24aa1988abce355a84e698a066f7378649607b57829808894ff7ab76daab4", + "0x02f902f9010584be740e9885121f2937978303b8e6943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad870aa87bee538000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2accf00000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000aa87bee53800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000aa87bee5380000000000000000000000000000000000000000000000000000d3e77830498ac2900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000cd24ba0e3364233ee9301c1d608a14753c8739c5c001a02a28422be21a9442f6e1283f86a52248e3b1a3b7599dcecb839c19a95e310597a038a4b35f2fe9cd57117e0edda565c3057507473c3f3e79fb9332e4aa9f69248c", + "0x02f875018302928d84b2d05e018513a7cc86ec826aa494816342193a94ae0bf0a4807976f78d5ab03d12ef872fce292419000080c080a05ec69593c99e139227d5015b70e0b5bbf68f955137b75edde3753efc703c6359a048e542994553c9a7535fa1dcaafd4b857dd9ef6c86b2049d4e64f14973fd91a9", + "0x02f8b401830adc9a84b2d05e00850d4576fa0083014c0894bbbbca6a901c926f240b89eacb641d8aec7aeafd80b844a9059cbb0000000000000000000000006767526a362ec6c6b1df185478e4f01506b73ff30000000000000000000000000000000000000000000006cccb85a7550fc00000c001a01ad1b4dfd6dc28ce7ac910abbacdeec4dcbf32d15208b85758d1d48d7498c072a037377aaec06ef2d5a626d6874eb040e2a3cd07c341dd65f53b3f30779fe7a4dd", + "0x02f9023a018084b2d05e0085104c533c0083021ed59444acfa1395e583b9fdc88137b7a03a8e173788b388034d8bbd308b0000b901c4b77a147b0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c45a4e4df40fe2bf63d9de141376f050f15916b17e3c729d28a0ab49ed8627800d27062894fa8fb51238dc11061d677f413e6b772ef39e06b1c22e0021eb8f880486f0f3fbce03139aec098665754ebfbe38380883968524693d94ac35f958ed9eda1fcd7a2e4b8db4c41fb63992acf6a950070d33546b08330ade10e7fd68a5cb0cb062db4acd87fdde77d5b891f58c57dd462de6991b7a6f340bc1f73c1ddb53ec90c20c54f6f0be2115cf8fc290dae6089852c57d6454706604c8544918ed392fcc8e2d922cd23f5f4bb61e6f661c4b93bb9404fea2179db3bb46ca1088d3687ca7ee737db680b4c814f5de84a0430f54e4bac146d94709abf0da7cf1878a29dae9a8fdcc2e3cdef8edcca1a1bb3d4c6bce50adb9005396a73a4e3e76e047676d7155dfa18871344ba028334f76f25081a871a310975ef8d4d333a21cc947390d4db92d61c9563f6649309b5ff90ecb576cc35e86f5320327060f36d22181d08cc612709bad88760231f5e7fb70d334443865091f8085cf14e0e97f120d247c001a0524fb2543274c82f343e5f55e944e2df33814f3823ece8d6ddd44c875ae3eeb9a0066b35ad284d1994e76ae4e8128768137d7c23d41fc06b1527d0a4e92b0e4cb1", + "0xf86c80850af16b1600825208947f58200b81d2885807da57589b1f255be825ff0e8806f05b59d3b200008026a029c4e6ebed80d23439a5641591335fb27b39a42ec4e737a2b8d6887fa4875a70a06810b74b2629f4ed9ac3d802fb48ce57623c93874de9a2f6cc723a7f074e9a2a", + "0xf86e830fae65850af16b16008255f094a62225f6008c092299637020fd97c25999f319d687183665bb1ea4008025a09ed1ce836f07fa12b2ecbce2ced394406d679940fa1a91456383d6f3b4757aada044d77a96e5e679d64d8d3f2f5e4deab4234f1a225157e7c28534855f9757a08e", + "0x02f8b00180849402f900850e81818c0582cac39495ad61b0a150d79219dcf64e1e6cc01f0b64c4ce80b844a9059cbb00000000000000000000000040e129cb3a203ae007a6411191fc0d57c350b3c000000000000000000000000000000000000000000006a1c870d47e5e274df800c001a0b1c0b14b8db85e946216205bc50a9a1fbe2d2187f5d0f2cb3104288c829bf8f0a0140a2b165388bf9908aacf6a793e19430ba918f22467b5d5852ac73ddb8a7c38", + "0x02f8b301830321aa847744d640850f20621b8882a4d89495ad61b0a150d79219dcf64e1e6cc01f0b64c4ce80b844a9059cbb00000000000000000000000077095982aac409f3fb448aad9e209c7c07815e7e000000000000000000000000000000000000000000526ddddf71b28034a31000c001a052383a32119afa16a15e8cfd2aaa180561e44d7ccbd0b58c016ed46b45a39a09a06bd599340379746b89c39e29a6a4cb3c94a49fc3039c5c0ec926be545b6985c8", + "0x02f87501830211b1847744d640850f20621b8882520894650993dc97634ccf05d362bead78b4add5d506c587246139ca80000080c080a004b465d6891f9fb80bbafa255b204a4e7572072f2b92031e3660b965dfb9125ca0665a80460becb67e705a29e07c71230813649210e63cd11f58b1321ae02c3051", + "0x02f8b1010d8477359400850ddf42a8cc83012e1a94467719ad09025fcc6cf6f8311755809d45a5e5f380b844a9059cbb0000000000000000000000000fb28aca9355a6aa973f6b2774c811c5d0320c850000000000000000000000000000000000000000000000000000000038ec24c0c080a0416d591a0f4f0264b62afabef8a7137dd5f81744300e5e2ae57cbb5249619257a068ac9dca55e16482aa4f959195819f4215138adaf216b2cc63c63b54ecce46c2", + "0x02f8b001808477359400850df847580082c35094b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb0000000000000000000000006cc5f688a315f3dc28a7781717a9a798a59fda7b00000000000000000000000000000000000000000000000cbd47b6eaa8cc0000c080a04890ebf991b2338963de4b50d01f768becdf51a5a98c6e1abe4a62c682fc7400a058396cf140403ec2fb171dcb82052bc00db3309b7bb2435ce205d9864d469654", + "0x02f8b001028477359400850df847580082c35094b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb0000000000000000000000006cc5f688a315f3dc28a7781717a9a798a59fda7b000000000000000000000000000000000000000000000007ea28327577080000c080a0106082d34ac82abed0039a1a6425b59eb1fec8da396517b47b8e2ac2e6fbf8b2a030f9381d899c51c1f7b82b653dfc1f82b8cf7fe1b08ea15a54f7f79871e6cb0f", + "0x02f87201808477359400850c0d03af5582520894808d0aee8db7e7c74faf4b264333afe8c9ccdba4873636e8f996a35880c001a029bf001cb0b6aa3a71024a3d60a92e3bbfb2feedd4f44c1c33d47c76080234d3a053395cb47773ebf4a876053f4a9397f697e8707457f08029b9a4875fe5129bb6", + "0x02f8750183029f2a84773594008517a2d1caaa82d6d8941bab31b520c8c312ff722477b7a1cc890473c883870a1d0c0facfdb880c080a07b8ac6701ca8162448221202bf26e8d16a20b84447c1cbfb144ceebdf2fe3db1a02ddcb2ea4c943aab10c370a423020f3b551d3a11fe18597e825ec0a2fbf4f93a", + "0x02f8b4018388941c84773594008517bfac7c0083035d1494dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000a5c0ae1c0d7838ebd6b0b9c9a50be5511e0aa11e000000000000000000000000000000000000000000000000000000001dcd9188c080a0b55571f4132d1baa4904720f50f2af59133fc98d7d81e7b58eedc43c2eedf316a076c80841a293cb799727689ef35c6350b42d5b2abadeb34c8671310ba45a324e", + "0x02f876018381e9ec84773594008517bfac7c008303291894c1e4f895e81c6fb82e4a9b043f4e0da61a29f7b4871b3bee858c300080c001a08fd3f48b41dc515b033bf466bf42b4fd835366adf5dcea4c67807b737a92db38a078f866574e2b97ac1453c2c1f6820d596a4ae215d121fc092d8200d85e80d9dd", + "0x02f8b4018381e9ed84773594008517bfac7c0083032918949be89d2a4cd102d8fecc6bf9da793be995c2254180b844a9059cbb000000000000000000000000cc4013a3afe4630eb4671d924b0084c01a3e5a64000000000000000000000000000000000000000000000000000000000000a8afc001a0c19152c83d612d923e27a7940a944fe0069915bae2d1a1fac44b9873bdd06cc3a0409cc2b5b59c3362a797f8a2737a5d01b0e58b5edeb705f9cefda28fe5db0a5b", + "0x02f874018204cb8477359400850df8475800827b0c94ab83a311ebcc5be7466edbcd5b798f18121f5196872bf55d2dab36bb80c001a0debfe435ce07e8a7f572adb752d41f385658a28b2459448b4ee53a91869652aaa06abab0000360f08ec7668a0e164edfcd9e89612e4aa7efd303f44eb141eae265", + "0x02f8b001808477359400850c2521caa982dc2494e28b3b32b6c345a34ff64674606124dd5aceca3080b844095ea7b3000000000000000000000000f955c57f9ea9dc8781965feae0b6a2ace2bad6f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a0e4224fd2900fd472113cc8db34a88bfefeb78c754b17f93fa3ec22e51e9a6d2da0522311d1bcdef7c15d66b358c00c2d9eb9976dbbd8cfea040c727e856984d05d", + "0x02f876018328af298477359400853a3529440083015f909488022e41f7c108e7fc786b6f15bc705d10d34dca873d7642229d400080c001a082c73d2c2492a2cf4613535516b3ed769cc3f08d4449b302506acc68234adcada0245cb18060599f93d0575a430a980dc44c3e662605fbf7fef843768fee04f86b", + "0x02f8b20181988477359400850e5fa553828301117094a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb0000000000000000000000009bf81cc31d0f1fa7ade83058509a4db154a182a2000000000000000000000000000000000000000000000000000000000dc4c7c0c001a050d9b55158f4a76ad549c5ac6e5ffa8767426e81298bb9a10ea35cc51ac4ebe8a0579dcefb0e6bfc3a067dce6000131aa3b1fe09bf290a09bcb3eb92b06b8234dd", + "0x02f8b001028477359400850e65be4a6b829ed194f411903cbc70a74d22900a5de66a2dda6650725580b844095ea7b3000000000000000000000000a7ca2c8673bcfa5a26d8ceec2887f2cc2b0db22affffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a05022e8b159eb3ebf3b8aa40863855a0fff534d9045de0327c7d50d5dfc4e3ca0a039c05e5cd887106d135ffc64b37cda928743950acf837ab298af7d3ece712119", + "0x02f8b001688477359400850df847580082ea609464bc2ca1be492be7185faa2c8835d9b824c8a19480b844a9059cbb0000000000000000000000006cc5f688a315f3dc28a7781717a9a798a59fda7b000000000000000000000000000000000000000000000266acc439ba50730000c080a0d8c067895655081946eac52305e0b11e2d310b38e1de9765da5bc9917b704d2ba007256e53ac4e6c32d25277011a7f110b6b91a3fc9ff81227c90d88d0f15a8f2b", + "0x02f872018084773594008517bfac7c008252089477696bb39917c91a0c3908d577d5e322095425ca871bb4d4b62cd6b080c001a05938f654a7077b081ed152d911e9505f659d23fb3b82569067413883bb653a70a032dda885f92738ba8c75661ccbc056bb4e7fb466d98c4960b48aadb34761b9d0", + "0x02f872018084773594008517bfac7c008252089477696bb39917c91a0c3908d577d5e322095425ca8738e708d3ee119080c080a03b8bc43d25584671f81e962959a228cabea7d4916b2b5da327f31d068290e910a0403dea2d3dc8b582cc4bcbcabbb18c3af3a951979dbd7b243ba521b44c4dbd7a", + "0x02f872018084773594008517bfac7c008252089477696bb39917c91a0c3908d577d5e322095425ca874dce0dcd5f92d080c001a02d50f46b5700a8f99273831ba1652ff79ac78e4b55fab216795fcbfdb79d499aa010be44e79c4444c73bb29d9c5e2c1e57bac925f6990ee177a3d51ad316cfc356", + "0x02f873010984773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e43880564febb0d6e6cf280c001a062f0679cdd027ae0293e2b460c80f3675b7c0d0ffa02b395a63eeeaa4367f78ba07252d94b81f0d35f314b3d1f6ef1915d8ce9a01e0090245b50aedb68d2d68621", + "0x02f873013b84773594008517bfac7c008252089477696bb39917c91a0c3908d577d5e322095425ca8802a5c9f4b52a525880c001a038a46b8582f9f92f65c7985150e5160ae27d9c04f8eb2e6bd084fc56b0897a33a0739cbdc2a82a452cf911a86bce6f363775d233f240e7dfb2144c42ff2e2f6f9e", + "0x02f874018084773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e43890207d4a0b06100358d80c080a0ac2bf9bcf27997ebbe1ee31bb91de46e9a97ae599b0b66e35bebee95caf9a9e2a049d3cbd468e87b6aa6e02486c29087e29e3df17428ff696c9453bf4023550754", + "0x02f872010384773594008517bfac7c008252089477696bb39917c91a0c3908d577d5e322095425ca87ac81d977bfb82b80c080a0775e385b916c681963114c5aa627e7e20eb087519f89600b643fe57bcc97ad47a062fb00c672cb2f6365866be21aaacb3a44d8d0b167e43e398b93e14cf96d11c6", + "0x02f873018084773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e438801077067cd1bdc0080c001a08ac07c8cf253fe07cfdf5651ed1156ed40c617e57dc8d4152afd7ecac3a9aae5a006ee7881bfd943ebc6f2c078c75bb1b9324e7992e8a1777ce9d17420b34ccd51", + "0x02f873018084773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e438802c409f4ad946c0080c080a0c87db5ed2e5f45ba65a98e3b8e4d37e0dedc97a5d0580ee2afb1ab8893c44e78a028d0da83975b13050467cdd7461fe926b53cd1dba09d9bf30b6db413b4704af6", + "0x02f873018084773594008517bfac7c008252089477696bb39917c91a0c3908d577d5e322095425ca8802aa4c08e8072f0180c001a0ef55f969d0c720f9fe95a6c1c7c551bbf066a006a256bcc6ee758c1ce27694c3a0685100f72084c8923f98ee90a9da40c0d768db33c88b9c6a8e1332be83e4769d", + "0x02f872010184773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e4387de86aa0b18a36080c001a0a217f7527ca9b1bdeed84be67aba1dec65a24b84d02a66de9d36f0e190d35d80a065225df53f59b593a71f6b10e37160aeea7193e4527fd0c36588b43d93853554", + "0x02f873011f84773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e43885b1d3e80723e434880c080a09774f1ad0a37a05948146ca07e6a6b069383c3707695b4975ee1fedbccc1ee7ca02a6e21ebbb427e870dcc9ceaa7978381ecec9efe5ee5a8a482e43b4550e5393e", + "0x02f873010684773594008517bfac7c008252089477696bb39917c91a0c3908d577d5e322095425ca880a7578444257064c80c001a02b0b4425133adc0c98f986bd8881e237f4cb91e177868862a39483ec443e37eca0039cf8dcc580114a394875fc26a7870e4fc30397f29c654387e2be0283b0ae88", + "0x02f875018301bcb684773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e43880523effeeb452f3180c001a0409fede92e8ed5f980492516402d600372db0b35cea0663ad7a8de262ba880719fa9de714f459f81513d413a83952d4cc093f93aefc04bc052177fe4fb0d2b62", + "0x02f872014a84773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e438782ab24ab6b5c8a80c001a05e31b9ac844d84f68bcd6aa2f5248d42846c06618df161ec49ca52472636628fa00c12d0e716b76f27f8c3d62e7ae87caeb683b7aaf8eb2b30d4f996e442f6fa97", + "0x02f872018084773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e4387198b6b551f687780c080a0a40bd0778d94c6c64c3b0b01682a73ee495f888a9750c01cb17d360eba6d240ca0701cb6c2be9e56ca8374393579b875040d093b6caf6d96f629df09f6229adaa0", + "0x02f872017684773594008517bfac7c00825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e43872709ce03efd1e580c001a04bfeb49efd34d0f8260ae341e5c8ff0ae23182b92c2235bde25f5772dbe1cf88a01848d5b95f3ccd5e5332e16c8843ea922a39855bc7b80885f4fa3c0fee3754de", + "0x02f8760183106eec8477359400855d21dba0008303345094215b4ab21d2296222c76cdb16588d585b169af6d874325732a41400080c080a0bfbfc3e114c3ca68887d8d77b1ae791c275852cba4b0120af30146120e680c12a02ecbbcbec039dfa0f9ab710daac89b8760b2b9fc451d0003d09d7fdb7487b867", + "0x02f8b001028477359400850df847580082c35094b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb0000000000000000000000006cc5f688a315f3dc28a7781717a9a798a59fda7b000000000000000000000000000000000000000000000004c53ecdc18a600000c080a0695e0c278594b3610d8e6b8606c0b8fca37a9a5df0473365bfb258163fb1abb9a0544fef1342e53370273231a1378b33774e81405fc78c69d23532cad8ceae3971", + "0x02f8b001018477359400850df847580082c35094b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb0000000000000000000000006cc5f688a315f3dc28a7781717a9a798a59fda7b00000000000000000000000000000000000000000000001fd242edf0d24c0000c080a0560e3dcfc62acecc0ab1316577003453775de1f38b17b672fa2e866903c65c34a06e9de5009852cd0108e4d50aa2983474be773a1e54ce2ca764dc9f4659b76dca", + "0x02f8b001808477359400850df847580082c35094b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb0000000000000000000000006cc5f688a315f3dc28a7781717a9a798a59fda7b00000000000000000000000000000000000000000000000fe2311b9e95740000c001a0d762e1840c43133ec0873c9463bf1d9edcbe2ed0701779ffb3467e50e8d5662da0090085f359fbcc7d2724cf1c676c3be582bd3205a22585c0a41d38ab70b3c9e1", + "0x02f8740182035e8477359400850e5fa5538282520894ee31993ab5310f1f85f01e3fe12f4dc8ed017542880de0b6b3a764000080c0809fc4ff9124a70b6c21181f55121f34253caea75beb53b51700d7e6b040999331a0647007cb4c06237905a35d2bd8d08251b01e908de31e688a0c9e13d22c868fed", + "0x02f874018202f28477359400850e5fa5538282520894cc6166d957115d4b2a93192c5060d763de913bd78756d2f298653dd980c001a002ab297ddb7ace9ec230f208522331dbc4f9d97b1ac90dd117de5043e3b41e07a02e744a9539e239f49cfa463fec1bf43b9aa4a86e914bc501e216aef43749925a", + "0x02f87701838ceae084773594008517bfac7c008303291894398132ae4b3f8a9409a5d9af816079c3b8ddee53880bdec7ed01e6000080c001a04bd782765419be32b76491b3a1aab2197d19f6a6a51649402295600713a19dd8a03fdcf5d50fae76476f7190d1b01ec1f33b75970b74d9ea8dae20e509f7a8f9fb", + "0x02f87701835c488484773594008517bfac7c0083032918943a4245a215a7af26438e42d97d21119b769842b388d0ea8d3d90bb000080c001a02fca9dcb60c8603084679d442df49d2a07d2a37036281292ecae8a7ecaca07fba014ef6f7e6c140d0733e0cf3a950a04b1cec57789dd660884dd4a0cec4407f266", + "0x02f87601833ab70384773594008517bfac7c0083032918946894e745915afae9dbb1b84c8bf11cb285a16e698722c5681358800080c001a0420441d003f0dca8f0fccd858211e34c9fc16ed1b1498e0e6a9b37b00b6719dea04e70b511251868b46a471ca1664ba52b453ccfcd8960d89affaad83397681742", + "0x02f8b4018381e9ee84773594008517bfac7c008303291894fa1a856cfa3409cfa145fa4e20eb270df3eb21ab80b844a9059cbb000000000000000000000000082f4d0b3e90aecb5b38143a50278bea1308ee5b000000000000000000000000000000000000000000000bf5559e13dbfa508000c001a066b3e6f72e613d95ed1fde25bd0991973ffe958447aa359ab0c13a0821a1b713a049ffa84a401cc799804a51bccdedce4eebc64c725b05c990d0c48f14df7ed5a8", + "0x02f8b4018381e9ef84773594008517bfac7c0083035d1494dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000002b88cec110af5376af0bb6ef8598c65bb87460290000000000000000000000000000000000000000000000000000000005c35f50c080a075d1a20346bf6d8c638300f788f69970ae166561290697d6ccdbbedbdddfd3ada00362acd337471e8f5619a4a134e20aceb11d988c94671b5cad7659f20c2feff4", + "0x02f8740182093384773594008515a73b6200825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e4387059a4ae4a761d080c001a025c5f09b3ebea185133df5a68124f8fe524d07cce849ee74fb31e0ef27999685a03abbf03958bad0a1ddabc778268af404bd735f93772561b3273a205bae0d2519", + "0x02f872010e84773594008515a73b6200825b0494a9d1e08c7793af67e9d92fe308d5697fb81d3e43870599cead3a4b8380c001a0137ee0d80cf0d1c764fba84c2ee695c8e133108ac1d7c182d1936c355af6e699a05a0ee602e18285c685812e12053e9cf06f76da18c2d337f840c33d740164498b", + "0x02f876018328af2a8477359400853a3529440083015f9094a93eb99caf4570eff900d1178c1d7d6a80b1c5d68710aa62945cd80080c080a04cf98bd8f135ba9e1f9a9dd1d7e5a50c65950d47937313836117736f38b0dc94a031785f37f35d8e76135191b5c28d1bf25191a266d4e6bc37690fb06d1b6d122c", + "0x02f87501831c5b2784773594008545d964b80082520894135f0b874d2a810ab545f18457574860a1d41817870cca2e5131000080c001a057fa557a78037aafa623a1bb1d917e9bac86e69228c40b9749f26ea8186d8ba1a0102ef4109e8ff9bd67e234919c4ed229d293eb0444efd68db54258368e2ee73e", + "0x02f87501831c5b2884773594008545d964b80082520894de0932875d9eeb403f327125f86614b70d57a903870cca2e5131000080c080a02064189b7fc268be45dc602bac349a611506b81be7d55448005b9eaa67d8679ba07f8cf3feec39cae186cf38afa3d53c1e120ef2e49f7e3187d26934db422016e9", + "0x02f87501831c5b2984773594008545d964b8008252089467006f2c487cd503539491b383c63032a8414397870cca2e5131000080c001a02bbc892b61976edb8b03229619ab408bb3f310513e36d2fa8184c5fc97e17b48a05ce6b50aa0d6e7f0837790923574e5590076680e35a6d040f69436a105d0de5e", + "0x02f87501831c5b2a84773594008545d964b80082520894b880725c8191ced33e344ee794d01d7df39d7fd1870cca2e5131000080c080a051d8d2705d2e2460bb187aeabb4986db6de4abb8daa965a10ce4c9163a2149bda069f970b13513455ddc34f76c4a0820adca2970811b9fb3fa4d8f36ecd44ce209", + "0x02f87501831c5b2b84773594008545d964b80082520894188469409bed858db50465c9e3b1a2d51e62afd0870cca2e5131000080c001a0bf669fc5a6f5782fb321065b96e7419acc29d0959cbd6975330f06df2c075ef7a05542db702f0ba5f8d38c47322af0f412dd8fba2c717f7ab1ff2473715cafc00a", + "0x02f8b3018221f884773594008517a2d1caaa830186a094cf0c122c6b73ff809c693db761e7baebe62b6a2e80b844a9059cbb00000000000000000000000086d2929645aa65b01931857d816b9fe3c7df1c3100000000000000000000000000000000000000000000000000f78176af9da400c001a04ee2ce0578a7fd506d7c0aa61b1fe468e28d77d8e4dfd8af21dee6c01754357ca062722f6ab2bda05bf3443774af3933e7f552ccdbd174c2da56a1eebff4666c1a", + "0x02f872018084773594008515a73b62008252089477696bb39917c91a0c3908d577d5e322095425ca872f470281e2500080c001a0179114a619096f5e3c451784184ed2e11897cd108e9c6e2c0d3446a2111446faa00f2f8428217ac1a36aef0952c8f18038df31a8830937d9d4cc8d34ad37a20353", + "0x02f8b4018388941d84773594008517bfac7c008303291894a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb00000000000000000000000004b888c81c747fd2fb04c96e2c314e59598f326000000000000000000000000000000000000000000000000000000087fe000880c080a00b27d31bc9ddfe8ff443f8b81fa502562109e37c6dbe0f4e61f3078fd0d82f41a03263f94c0b815003b9c0522b0318ab651e3f56fa691dd340ba9b7d5ed8765bd0", + "0x02f87601838ceae184773594008517bfac7c0083032918940b5b51be20f30d22024ee7595513fc7b11b31e2b871b0028e44b000080c001a059d21c8d2aecfa0b2ade92909ab54d61f93d64dd0c1a0c3be26caf0d8c95166ba042eaae0da63176afa7661d9156302a67c6feba7d576a59e56bb4ddca4c403cf4", + "0x02f8b401835c488584773594008517bfac7c0083035d1494dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000f6a78eafd03f38d1a7ddd3bc3db91a8e1b90072c0000000000000000000000000000000000000000000000000000000023957f40c080a09f58532c3d3c85a6ccb155e9a454b2df9cd64e92b8d557817e6d24e50b02a8aba0374bd38629cc102ce51151f0eb2a52bb017b3435904b157ab50c2cff8d810d4b", + "0x02f876018328af2b8477359400853a3529440083015f9094bcb6d31e3363ff68e213169b50a5b2650646f79b87b2c2fb32d6940080c001a009d20a97f8e39e9dc9b364686548505d073dc00d8e4dfada8ef727bbda392372a06ed4dcf9c2d5f9f32bb3179ea3e4de9c525c19b8fef7d700ae1df759a735b4a2", + "0x02f8b40183064e6c8477359400851087ee06008301d4c0944b9278b94a1112cad404048903b8d343a810b07e80b844a9059cbb00000000000000000000000065da6725d8be6090b07af197969ca64720a79853000000000000000000000000000000000000000000000439387a52dc6e040000c001a0f832350a8a3fb47e51d14c28480bdb6b2b2bd3338e57e6853a38dd4739118022a0446198ed466d78e9ef3a23efd697e1c7ba8ecaaa8abef2e8fd47d2cecb1eaa2e", + "0x02f8b1011e8459682f00851791a15f08830124f894514910771af9ca656af840dff83e8264ecf986ca80b844a9059cbb000000000000000000000000a2f90b06b1d36a0b075f5fbdd7ee2c091ef7610f00000000000000000000000000000000000000000000000244fc7dc57cab0420c080a06a79dd00aa022286b27ecf31c88fceaaa679dd217eedfec091175054cc939d35a02cd5f884ad8ccefca82afe2be8569942755ebebadd77b96d3f447e5b0076d428", + "0x02f8d3018302d8bf8459682f00851791a15f0882f2089446950ba8946d7be4594399bcf203fb53e1fd7d3780b8648f975a6400000000000000000000000064bc2ca1be492be7185faa2c8835d9b824c8a1940000000000000000000000007b68226938b4db2d74404caca4e8d9ce9ac6dfec00000000000000000000000000000000000000000000000ea5c73c6b468c0000c080a09fc0f6f254d609ff633b7a861d1b9ee2af583a97a905178f32cffe0265c10701a007b522edd88729c7451963d6da110c1eb1a932cf97d986bfed89ee1979a2be46", + "0x02f8b20182068b8459682f00851791a15f0882b770947dafe897a6ff1d7b0b64f908e60e8665b61d53af80b844095ea7b3000000000000000000000000ed12310d5a37326e6506209c4838146950166760ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a0356435050fcffeb158c1080d94b9ebaa18e351b38015de9d047785c857007415a025eff1463b90980c67bc18f7fa1d6f02b025e5c14b567ccfbe7d38e41e518f6e", + "0x02f8b001118459682f00850e47f0e56b82b5a694cc8fa225d80b9c7d42f96e9570156c65d6caaa2580b844095ea7b300000000000000000000000064192819ac13ef72bf6b5ae239ac672b43a9af08000000000000000000000000000000000000000000000000000000000000afc8c080a098818c0328568bff1b14d823a5dc4bba9341e17ce6f4f7226f02222903764800a031662073f64f3f46457c1e2592bdd682b1dcebf753b1c9acd349249f4460eef4", + "0x02f90374018252388459682f008515699cce3e830f424094787a0acab02437c60aafb1a29167a3609801e32080b903048c3152e9000000000000000000000000000000000000000000000000000000000000002000010000000000000000000000000000000000000000000000000000000028e6000000000000000000000000420000000000000000000000000000000000000700000000000000000000000011dd2d9b5ec142dbafbefea82a75985eae4e12b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000031b8000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e4d764ad0b00010000000000000000000000000000000000000000000000000000000028e600000000000000000000000042000000000000000000000000000000000000100000000000000000000000004082c9647c098a6493fb499eae63b5ce3259c5740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e40166a07a000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000b9d571c1ec576300f01ddfa5a082d9c571e45e360000000000000000000000004d44b9abb13c80d2e376b7c5c982aa972239d845000000000000000000000000dbab11a841ef6b2761acd76c3b9eaee847d7381b0000000000000000000000000000000000000000000000000019ef4fb2dc400000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c080a0a1dfb511b6859fd0ddfd7576a19469b12907866f287d624ea8f3c9039a52a80aa0052f682611c33e9d39e1fb9ce815ffbe30463142e366f8b1bbdc8d00c77dec48", + "0x02f8b201826a44843b9aca00850efd30b58282f88c948881562783028f5c1bcb985d2283d5e170d8888880b844a9059cbb000000000000000000000000e12670fd59c315cc97ef77cfb3d06065b78f85b80000000000000000000000000000000000000000000000c3a88f6c9983bbc800c001a09a61be61ade365fd0557a61c0dc454287ec70f13074f116831980f010f79c511a024c94f6dc0b710695d878c9040f2204792e1ffc61d2436e6f8391963e0a2c017", + "0x02f8b301823566843b9aca00850efd30b5828301482094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000005edcfa1187d1e0216a0dfc7c7898a9f0f556039100000000000000000000000000000000000000000000000000000000014acf80c001a01d5ad3c53e8ffe2cf267d68366ab3072e97d0ffe5ce22fc729b6e4cdde67afd6a04608dff99f61af02858d52cde53a559a6884cdd232b9e05fa9e20b0e5b38becd", + "0x02f8730104843b9aca00850efd30b582825208942df9b935c44057ac240634c7536511d8aa03028d8804f741fce15f881f80c001a0092f5e983fa9a4c057919ce3dbe03f77ecc26fa4c9f32e0247a1c3d5897565c2a0785370e61ee455422a2347c3730cb357eb5e123672c66373e2b936ed6f48e3fd", + "0x02f874018272d1843b9aca00850efd30b5828252089447b6897359d31b648a10cf9a3e886400288bea3987279e861cce1ff480c001a08f303dc0353ed26eef5574ec2cbff01e379c32cd79f26f0645b9e152748435b5a03ce267967e335b7ce2df87bab4892385f9a1c9932927e71e9cd95111a73cb50c", + "0x02f87501830f5966843b9aca00850b6cbf811082520894c406a13e82c5a57fee7a68b4508ee07ae0de74af875535d1cfbb478080c001a0dca00e515144ef862d5a790ed8dc27bd36d7b93cd14765c4537862d8bad3d9c6a058e8ff7ab4aad2c8682a910f666e643d432782d71cb9f4a49536f6d646fa3cf9", + "0x02f8b201826a45843b9aca00850eaa1cbcaa82f88c948881562783028f5c1bcb985d2283d5e170d8888880b844a9059cbb0000000000000000000000001417177c3ba9187d90ab76de062cff18b4b530b60000000000000000000000000000000000000000000000c146e25f2f4756d400c080a04be2fd58f9764c65133f67a069b1078b0545e21c523b4851f1e5ef5b869fc3c1a0665f85c3edaabbcd27428c2f2f30a8f9788905d43e17445962c305897620e123", + "0x02f8760183087e39843b9aca00850eaa1cbcaa82520894dc5a0c7470b5671567771901b080ce608749ce2b88014b62071e11c00080c001a0c842ad8893dc8b1fa3833f429dc21fbe59857ea78cbef9ab93811ac8caa92830a02f33ccf399e6f1aff96affdc3c760ebc3bec06bd2e553d57f187e5cb38038b77", + "0x02f87401824169843b9aca00850cdcbeca1f82c35094672feaaeff55ed395e43f93a875d5dedbe3692498709a6dd2c01f9f080c001a0db911b0abd3e61460d33631f8cbb3ebc3f05130409a6cae91edad110fd39c2f5a05459d7ff083a838812ab756655e617f91132e8a3ae38d83a71275c95935a4db9", + "0x02f8b201826a46843b9aca00850eaa1cbcaa82a660948881562783028f5c1bcb985d2283d5e170d8888880b844a9059cbb0000000000000000000000001e7127c81c8a58661a0811f026b6be66533934be000000000000000000000000000000000000000000000069bfd250c6a77b1000c080a01de08434bcb35698ce9f348edcea70b4b9d32874771d599affa459e79b87665fa052504ebdb3da3d075b1b12c3b5fa1ab146467c2f7005af524d7d621a3906d272", + "0x02f87501830f5967843b9aca00850b6e4de1cf8252089439d7e80ef17afe7261b947c9529b869ba7c88045872853aca2d8700080c001a00466eb79832050fae55fb33067b7df7d45853f6d6bf52602bd1a90dc5137452ea04914e94face76b18820ce5db22c4c8be10b0d44b5b416b9ab6e8352272120234", + "0x02f876018302b841843b9aca00850eaa1cbcaa82520894944311875ecc192445654b1794fe48ab54ae6a8f88054b7b77377136b080c080a03e7396b978a14081cd3e90f0fb592687d11c2559805d4444d5403e5420766d4aa078a7006e9899befd664a084f94de1c69a1da9977f694b94153476cb2a4fa9766", + "0x02f876018306c414843b9aca00852098a67800830186a09464f961eec2ba222dcba8fa354f03e27529b96f5b87153ec73fc1c01b80c001a0583dc990d273492b5c74d3c94524e058f7de47042e4f92cb35b11c3b4dcb0e1aa0180e393a76b3d6065d0c55e625cc82da05c7896801d95c64e99a91b7fa170a5f", + "0x02f8750183087e3a843b9aca00850ee4b80f4882520894136e2d4c689617daf7110c53a32b512a9acb20b8877ca8599fe1000080c001a053f72254417682846121f46cb0d828d3536b027ef6a4894a9339fae216e62291a05df50338a3afd9db52bee8fef6ba4563e7148259660d385bece9d58e99f31ebd", + "0x02f8b3018189850a88b2e61f850a88b2e61f830493e0943071be11f9e92a9eb28f305e1fa033cd102714e780b844441a3e700000000000000000000000005c17b7bd70a80ce3fa7221aa1bcbf62de3985d2e0079ee7d871797a521c7a0cd953e51a1f079ff3d3a0b02a4d63f995258fcf0efc080a05d6371050d017e140b78c4f1e77b4deef02960c04ab29c9a3540315adcd12f44a05b1a7e67963f6838afc484c2b742e70b9c6a019712c1d2f66ed712ca956b43b5", + "0xf86b01850a88b2e61f825208944d24eececb86041f47bca41265319e9f06ae2fcb8757edb39a8dad978026a03d319923d281a9a0e87f33c43f419f75ffff2d2ab346fd0034528a2fb1ead3aca0046b56de8266ab78e57c448e451c51f372ece5f9600228d09b01bf4e7387218e", + "0xf902eb44850a8590080183037460943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2ac630000000000000000000000000000000000000000000000000000000000000002080c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000152d04202d703370c47900000000000000000000000000000000000000000000000001d45d9126f3dc9300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000d1f17b7a6bff962659ed608bcd6d318bb5fbb249000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000001d45d9126f3dc9326a0fcce90823efc87c96d390627c0e1b553b98739665be61e32dbb619f62e0627f0a015432090f7de23806fa2f6bd067c92643043b198507ccd29744366661ca09816", + "0x02f8750109842a51bd80850fbc9e4b40827c4b94f70da97812cb96acdf810712aa562db8dfa3dbef8703c619ef1625608319b786c080a0e32c688e8524736de4a3dcd1b687fcd7275239d6c6b9a05db426f73656a073d7a07c50950254c39fb0aaf410e0af41c12c0a7ece32c93fe76a783dcfabc20d3a7f", + "0x02f8b00113842a51bd80850fbc9e4b4082b15894aa6a914c605f9134a8480745729c6d0e00be038480b844a9059cbb000000000000000000000000dc1a50161a07c451629356ad5dc2c488b4bd05f800000000000000000000000000000000000000000629404873eba963786b37dfc080a08b66989e3401856b05bde57c6822409b5697b06de8e49ee068105b2af9441c87a00bb782a16dcdc72223335211fb34b703c5f0a636530839f150ff88df372e041b", + "0x02f91874018234728429b92700850f28c4de2083114e6c94b2ecfe4e4d61f8790bbb9de2d1259b9e2410cea580b918047034d1200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000178000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000b4d24dacbdffa1bbf9a624044484b3feeb7fdf740000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000154000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000006f43b26ba724ae3ddb0c55536d64e5f985000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f9493c46d9b905e227ecfc266752eb7c7aef4cf5a8fb4e84018df3536ce8a172f9e700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d3d71500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e8849281b1971b7d614adce8504681ed0000000000000000000000000b0a206794611892587e3b0df04a64239e2a3490000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f949a0c92bad50dc3bc9b7103be708607112c3cb42d094333215bc030df903c51e7c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000065f2aab4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003bceac2eac4cc05b5332bc268aa69a140000000000000000000000000e71c843ce2c374e3b7bec768fd3abc8b7465b56000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f949c3fdd6f428a207dfcae4fd4e947ae2782b9af579b42ff22eff9c133e250a8b1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d355cd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000afef5ebe12de0c5895cb43a34c417d600000000000000000000000006d02ce0cd50bff383035e1de5c8b2235fb22e4e8000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f9493c46d9b905e227ecfc266752eb7c7aef4cf5a8fb4e84018df3536ce8a172f9e700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d3d6db00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f9badcb58f445cf9a7ccc096fba6cdac000000000000000000000000da714bfafbc2b139bf6e91d9809fac5a104a9798000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f949c3fdd6f428a207dfcae4fd4e947ae2782b9af579b42ff22eff9c133e250a8b1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d3d1a800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c44722ce8a6747a4096e0c79ccceaa9e000000000000000000000000f13532cc8f5c700dd30f9faf8b833b38cf78c7d6000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f949c9799823cd012e2adc57aa64008c7af4b388f9a6b0fb7387b1c94fe217238bef00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d311e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008583bb513a7f4bce368f190cb1e0d2df000000000000000000000000f4ce3f01788a6066d38145ca5995f68ed9128e61000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f94935952892cf0503d50413209caa900477ad2cd683d3d31d61b83e779afc67082700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d311e700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bc3bf7e6e1de2444e4eeed5863070e90000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000005c000000000000000000000000000000000000000000000000000000000000006e0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009200000000000000000000000000000000000000000000000000000000000000a400000000000000000000000000000000000000000000000000000000000000b600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000004c38000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006300000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000002f51000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000002ea11e32ad5000000000000000000000000000000000000000000000000000000000000000002c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000002eaf000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000002d08000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000002ea11e32ad5000000000000000000000000000000000000000000000000000000000000000021f4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000002ea11e32ad5000000000000000000000000000000000000000000000000000000000000000031c2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000003807000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000002ea11e32ad5000000000000000000000000000000000000000000000000000000000000000023cc000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000002ea11e32ad5000000000000000000000000000000000000000000000000000000000000000043280000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c73f69c4c2d9de81380ffa825e2723744a750ea770c2e3ef35a190cc0a7e651bb75ee9a54a45770d7a514920819c9705553754efae5ebfe25d4dca57bcff8fea051bd4f747cb65d2d2aeb491359c61ea62974182170bea195dbaa357bf5673d34a115bc8026d7fe931bf2f2de05956701057b186a862dbbc5e0f7f9bc5aec7edabcf1b4f8d7e9f4813bd2a40f2dc56fc307e44f13b15400b20363b9773e3cb55519221325bf18920f7ea9470aa9571b1dc14d88507846f8fa2f567972aef341535fa971bb4276ca35a2e0efcf6a9a439a2dae85ce62e0af254c3f32fc8c1e52f6b267cf642f87e72034d240cc0c72e797d6a0a66016150d84df2bda0afb639a03c1284541c3372aa12fdb1c96c09df3bcd793d31fb92bdafaec82bac791c22090ba6ba0ccd021ef04e8fee62420bbb46448bf6892fd7ab623b7c9c3aad3f2a1734351053331baf3f6f03d04af9b9693ef440db14efe5fbddf364d311386a8149aa6035f1c89e08cad3ea9669e663b95636a07406f0c1acdb6458099cf3d3cd1f86fdb51b8e531bab64a8552da0eb41c74617033a5ef07296ed49bdcfc38ada79a45193544beba46affea94c132d9f59d7209003fcb4b7555c114486168fa265a8814959afefa691c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000592d73154611fa1946c16daa7834137449258c80fa499f4ba58070ba1e9c0cfcd5709885a5e3404a4e9c79df8b1b0d87983bc23ecb90643c8cd07eb93c0ffc81ad1c0128819a6af68e5d010513ff70a3aaed9afeb8661116e6ce00000000000000c080a0f63d057c9061b69c3001a666186f91fbcddbce1afaf6ee053201f1f913cac64da018b61e9fd0dee1a2a31b0647540392dc5b139f4909a64857f2e7f51f341f2fb4", + "0x02f911f401823473842a51bd80850fbc9e4b40830c5d0a94b2ecfe4e4d61f8790bbb9de2d1259b9e2410cea580b911847034d1200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000660000000000000000000000000b4d24dacbdffa1bbf9a624044484b3feeb7fdf7400000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000f4000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000006f43b26ba724ae3ddb0c55536d64e5f985000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f9493c46d9b905e227ecfc266752eb7c7aef4cf5a8fb4e84018df3536ce8a172f9e700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d3d71500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e8849281b1971b7d614adce8504681ed0000000000000000000000000b0a206794611892587e3b0df04a64239e2a3490000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f949a0c92bad50dc3bc9b7103be708607112c3cb42d094333215bc030df903c51e7c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000065f2aab4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003bceac2eac4cc05b5332bc268aa69a14000000000000000000000000927192c4158bbfd98874ef9e781abfff43bbbb5b000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f949ed0e627242314a2d7fd604ff2614c48994ba23e090f4bbf517ce43210f2cc8d000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d31d8d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d0e19226ecede1101d9f454860067a4000000000000000000000000a3b3acf61034ccd05f204e24e5935cea4d291065000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f94913bb5cbedf2f3d8a64e1efa7a0ce0e1e21c62daca139a8ed77f521f00c7c5c5c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d3cf8e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ebc11bce224cc654fb2acc8f5588dc0000000000000000000000000ba3269e784c087c2c427c62499b5badca6775dcd000000000000000000000000306b1ea3ecdf94ab739f1910bbda052ed4a9f949a433df76e7d352a93c61a503f86ce9192b1857b4f25470be2d300b1fcc0aee6f00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000067d3d6da00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e82a9ff9a7afd0165c2f7a4f4c797d4e000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000560000000000000000000000000000000000000000000000000000000000000068000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000002ea11e32ad5000000000000000000000000000000000000000000000000000000000000000031c2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006300000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000002e03000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000002d08000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002f00000000000000000000000000000000000000000000000002ea11e32ad5000000000000000000000000000000000000000000000000000000000000000023cc000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002f00000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000002644000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f00000000000000000000000000000000000000000000000002ea11e32ad500000000000000000000000000000000000000000000000000000000000000004328000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f00000000000000000000000000000000000000000000000002ea11e32ad5000000000000000000000000000000000000000000000000000000000000000021f40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001453f69c4c2d9de81380ffa825e2723744a750ea770c2e3ef35a190cc0a7e651bb75ee9a54a45770d7a514920819c9705553754efae5ebfe25d4dca57bcff8fea051bd4f747cb65d2d2aeb491359c61ea62974182170bea195dbaa357bf5673d34a115bc8026d7fe931bf2f2de05956701057b186a862dbbc5e0f7f9bc5aec7edabcf1b37ededdda37a8706ecf2f1690cd77abdd471c03dd6ab290d02ba01b823c05c0a52811707d9bc26a609560435d87c4f65fcc62c2ee12cf47b091b2182ca5528e81ba7200a3acc4243d870eca358f2633b96d3a148c8b02cd9ddcdb38eed07c83e7e15fab344e41c7797e7c911aa6b9b22dd8545e1f7137e372fe1e6d9ee8fc17a451cf2bca54a737139fba92b3a95e37c503bf5421cfcd2c67b5119635233fb1263b6292d2390eeb32a5faf40d931b26705a5c4e08ff24fbda17cfb716cea782eec101c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000595568309a8db19f87180b74a21c4a665f80195e9107b8f7388d4e294c174d1fc00ccdc2d240c9a974c3626a307222dc9a05a8ea4551e90390f2f3b6cae42bde6d1b0128819b6af68e5d010513ff70a3aaed9afeb8661116e6ce00000000000000c001a09c538b5a2e4536a0b245e793dc7040a24d1e61893a9faba926263466dd3e3935a03134ee529876102b69b839f33061aee638fb0620c0b596aa3257668c80d89a23", + "0x02f90232014a8429b92700850f28c4de208307a73f94ba12222222228d8ba445958a75a0704d566bf2c880b901c452bbbe2900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000086b1fc73244a6df1a3cf0accdd1bde525932d630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086b1fc73244a6df1a3cf0accdd1bde525932d6300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a333e9b15e9800000000000000000000000000000000000000000000000000000000000065f2c1db596192bb6e41802428ac943d2f1476c1af25cc0e0000000000000000000006590000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bf5495efe5db9ce00f80364c8b423567e58d21100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a3065f8bf46c54a500000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000c080a01bd842010f6fab9cb92eee450a25cf5e359cfeabaf30314711241afe67ea3b40a028f8b7474cc42e1e2dd6d3f1c8e09baf2208cc2f49671a7378d439cbca280384", + "0x02f90259018205588429b92700850fbc9e4b408270189400000000000000000000000000000000000face780b901ea1f8b080000000000000355525d6fd43010fc2f79e554fcbdf6211e4a4b25242424a8c4f37a777d04ae4994f8505175ff1d27e52a9ac7d99d9df14c182bee719a8e3d61edc7e1edef81af0a92d4abfaf8e6e7320eefe6d351decbd24f61f7d48d53b7ef088fc76ed771e376fba7ae8e0d538fe4130b81f33a52a01872525a01d9e01c526224d42e09a8c62ca78156b5c6432299ea975264feded71fdffac380f5344bdbc2f9b0acf7c77578ff6792b6feb95f6a3f1cda74433ff1262dacb22d4a078739836072458b06efbd0b5e025fd665ded6db34dbe8b56129042441454b6255114a9a2805106fa3b8469b85faa997a13e3fd144e500cadd87ebe074bc16367297b48b51e0f6d6d80846df02c4d5fcb248bd19873a233d939d684162029d2403281320816305585a5258ac269f635017f2f6360dce5c807f117cfc7ad3642ee0f5c3785adde95d47e3b0f42cf356e4fdf84b864d5707b0de29e3ac40b2590900291bd9618cce4529c51bc7b426feeac2cbe5a85e7fbb6ea938d7fbfe4136875ad9149f8dcbc0ffc1abe8062f2fad6e41500066d6262150292d6fefb34d5685e27db081452144e6dc82454e31a7e4028362d78a31e25a62a684e6dea269bd91297a6d5c38a0581f122b5b0058a5ec20e8f62b72146daccf2914a6023a77e7f3f92f4b751e4efa020000c080a0e1cfd7ed04ba71a4b1b784a44fba06f4e708cb2613fa89197dcabe0e1ce1fe7da02aac895e125f07125572d4e28db67de2f441e762137f696a8cca3207f48e65b0", + "0x02f8b101028429b92700850f28c4de2083010ed994dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000092e929d8b2c8430bcaf4cd87654789578bb2b786000000000000000000000000000000000000000000000000000000001d77caf0c001a0ffe55b891ddeefdbc515ec2d5e98d303ee7deb39ed0954f7129310565dbdc3c5a073952d6a0c70886478da9d6d639f86c5721a6b255074ad4515489b2fa0e678c4", + "0x02f87201018429432e978510eb53889682520894d455f7c9898cacd7b1f820bb45dcaffd33d030d0873596be64fc0dda80c001a0ac5c94daa1e787f2919b8fbe5c959d3c2e4ac493d338e416d26b6175f9f5ce80a058052f807831749e38bae86b0927ba765e7c30307062866a16f3b60b5175b72b", + "0x02f873015f8429432e978510eb53889682520894fb228b655d106c81756b11ff07baf93ac8b9a8058842c1bab096b53fc180c001a0d8c479149bd51784e376abcd3ebbfb7700af5180f6103c77747c621fa7b0ff6da07040b9a753018bfa5f0d3709cad09be329c069bb11778292e9f5779268fdf6e9", + "0x02f87301088429432e978510eb538896825208949031943751e319da09ca948ae56b0a67118dc41988015dd990412daf5080c080a064d860d4f407c5d941d2f583795e75fcb74539dff0e21676903ce0d89e5afe1ba04434ef0234fe4871b82fe539e4bfea525bb4c4c536c2a64449abce36d05bdcee", + "0x02f8b001208427f3f16285108164a99382ca25944e3fbd56cd56c3e72c1403e103b45db9da5b9d2b80b844a9059cbb00000000000000000000000030f6759b7db6596897116ba606d7eb580cb1c1670000000000000000000000000000000000000000000001c2c84c5a9fa6a57aaac080a02ca94a07d61c94c0943da11b479bdcc6940601339aff80c090fca9f7b8c3836fa00c3aa253bac32d13159fce078080e9701e8d4b31b946c714deb80c36da1416de", + "0xf903ed82013b850a7a3582008309873294c36442b4a4522e871399cd717abdd847ab11fe8880b90384ac9650d80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000004b51500000000000000000000000000000000000000000000000a51e89a8d0bbd9b69000000000000000000000000000000000000000000000027fccd3250516a761400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065f2a51f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000004b515000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c00000000000000000000000000000000000000000000000000869046150d3310000000000000000000000000fa2da36193c5d80829529fc71bd0f2cf63594776000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064df2ab5bb0000000000000000000000005a98fcbea516cf06857215779fd812ca3bef1b32000000000000000000000000000000000000000000000029dd40184d25d9c21f000000000000000000000000fa2da36193c5d80829529fc71bd0f2cf635947760000000000000000000000000000000000000000000000000000000025a03dee2b757a75890d31b4e1679cb51c85d9809b2372989e5b313fca97f0686c22a0482b6532923cc89fe72d58c20a5334384edcb9b3bc7eea7c4192a25dcadc366c", + "0xf88e03850a7a35820082b2679400000000000e1a99dddd5610111884278bdbda1d872386f26fc10000a4497ecfc56d31396470707265773637000000000000000000000000000000000000000000259f04b55ff9c08d6a62858854e9441b9c3e477ac8f7b41b4e3a3fb1bfd5380ba0a0787f4bea2be6e65860a3cc6fe9282a6cdde15abe7206957b11ec1132ae0edc1e", + "0x02f8930182011d85010c388d00850a7a3582008302107d94feefe92e2192cf49cc0bb75f4c0f044d3313370780a40e5c011e00000000000000000000000044d585fc510c5d30d909d83563fec8a47d8d264dc080a0cd3f4c901dadc10dee9816c19f24f8b7b6a97e985ce54f441d15cc633167049ba036d90a8e25e7d2400b57f84cde0358535c4c06619649d36630e3d52692ac6f57", + "0x02f90332018084258d0980850f574a5a408301351b94d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef000000000000000000000000000000000000000000000000000000000000ac510000000000000000000000006be13e25bcfa44b43ed359c9a6e436eba0b083100000000000000000000000000000000000000000000000019274b259f654000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000011b72dc4e4d0b42a6839af67ca600078e35248b55f38301a4cb184cc526fc904aa3441c8b9dd1d18f328cdeb1811105930295f25af268937acd71ebab1d5dcd079abcb76e5db2538ae47c4e3538c86ba3d88df9f1f709a32eb611492f3388656feacd2140d70724bf1f7297d4cf7a027d6e643e43d939f9c522ec165bdfbad171e653f63ffa60a3204bd246211f0f2f99b37d6b1944f6f33d001a512502aa1a376829d388ec86b231c68730723bef51c856adf24670469cd9063d2fa37db902198b64ae6788fa19e5787a2c9281d6f98ea6cad20553c2fab031e9dd531cbb726f1aa35b2c66d55ecbefa599d6de6c8efd717848ebe6abbaf40cb6f9e07882a2b7ee9069f62d1461a0ba3fc962021e63a9d0a98898489d6bbd7220a7aaf4974f226a3c43ec7e0b18cc01a74c3f8e7603e3465925484e6c5834c0508cf43f278ea3607ad21985c0a7b220c77ec1c4d2ba832cd5e2b80964ba4b60ced2b5967de3919578d7be013075f6f887937e87075ad37f49a82d0be743328564015ddc49e3faa3a2daa97f744d166ab5c9b170efc3e9fcf70d2dc968e4cba43ba29ed414753c8439869e225fee3df7bead8c9c011e7e36996bef45fc334cec3a7b3dd000cc2801845c13f25cbba3d726231691cc1678de0936fe89df8d408c9bc344d58d0ae0a46e70d0e09debcd1ba9ca8ff28fa3dcc7b1318cdac64c0b387c56304d964a2f86643f31da169680b58a8548f4d3144a13fdb0eb53a2844abdea2d4a0c76a82ddc001a07cd7e54a4d194ef506043559379c2dd6550d67dfa1c8752cf9b9b5125d283c62a04b1e25140455769f5a453696600f4a764ed8879c36809ae69d57ca4798cc9223", + "0x02f8b10101841dcd6500850b398a3880830111e894c02aaa39b223fe8d0a0e5c4f27ead9083c756cc280b844095ea7b300000000000000000000000040aa958dd87fc8305b97f2ba922cddca374bcd7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a055c69b24a62cad13ca6d1a23f10aa931c025176fb61df1c5d4ffc06d5aa6b11da07592433cf4b012de8f5676c7a0fb9d88fa632b29d8e7c176236fb72c8cad1b48", + "0x02f8730181a7841dcd6500850bd22a8aa6825208943a9b6fe84c1d10549d234100e301c43db576ebbd870be1fb16c5e74580c080a02fd8afb784c727d3b0fcdbf9059b1f8b1da148073dac76d3dc1a97418fe64252a07bc8a7681d9aa2e4c8f4cc81e96decf5e1bad3e72f4dfad8e53961ac6f5a7cff", + "0x02f9063501820257850a6d3076f2850a6d3076f28307a12094fd9f795b4c15183bdba83da08da02d5f9536748f80b905c49ff802a7000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000e3c2881d0c899a44e610b0f6ad13e5a240eb28910000000000000000000000000000000000000000000000a030dcebbd2f4c000022fdde52ca166b82dbda5ed31925283641de12ef678c571eb359dd14941475a300000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000420f63c799bfedd6fc5dfd04b51c99001c0edc8171c4ccf30e4ed26e29dc468e9020000000000000000000000004f42cd614516409e9bedf91a6e94dfe8dedb3570000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000412dc0c42432bdb572655756bbfdf0199ea3bf178aef3fd7d3357872d2abfaf00b44d0f0f6bb07afdb697cd5e39eac02d44b9e703b8a39d714cf09c396c38c37491b00000000000000000000000000000000000000000000000000000000000000f63c799bfedd6fc5dfd04b51c99001c0edc8171c4ccf30e4ed26e29dc468e90200000000000000000000000079182fb1ebbc09f2b8aecfe2dfbf7dea40b4fc6a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000041e35381ed52125d436e4e3b649054197faaae99cf696f8257f4be2e59b832431e01b9e2c73b65f6e7f497902fe7b134e684e206c01fa89993b86c2d86a041908c1c00000000000000000000000000000000000000000000000000000000000000f63c799bfedd6fc5dfd04b51c99001c0edc8171c4ccf30e4ed26e29dc468e9020000000000000000000000007cdcf0e56aa0f34d844a03d388c1874b6edaf400000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000417b28ea1fb229ea7359f15dc0c4e42c803bd8823d59ea3375ed0ff55846aabe0a7531141aacd9e2d71f09ab7bbc37aa63c2b0c9bb4f7c44508eb44e42c1dd169c1c00000000000000000000000000000000000000000000000000000000000000f63c799bfedd6fc5dfd04b51c99001c0edc8171c4ccf30e4ed26e29dc468e902000000000000000000000000aac269d7d513b09bf721de748c4969fb01d3af06000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000411c4bec7856f808655ecc0fbdca9388e4b7b05b38e38be145520a0948753005e51812b1c1d14007deba833e8ad51f41f4879270d8d1b706fda5e2af875d77a9341b00000000000000000000000000000000000000000000000000000000000000f63c799bfedd6fc5dfd04b51c99001c0edc8171c4ccf30e4ed26e29dc468e902000000000000000000000000d8d8f676d5479c4cdfc27ee7ba370a3959308ff60000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004184b80bf38da6bb99aefaa2770c0394c4b7d4ecb948972aadfa8aa5f5ccf106ec7593e8bbd30f8729e7d45ddca1a3608da27bb3b4e54c860e91b052ff29c71a3a1b00000000000000000000000000000000000000000000000000000000000000c001a0f89dfb9dd5727a66d815d1f37ab709f8e925199dcfa1ff28c4e5ce9d384af582a0728d78422b1bddf958ce2462a61a7ed831ee7ec872095394ffb153aee4775cb5", + "0x01f87101830a6d8e850a6d3076f28307a12094b05178ed26b624875de845e07a8eb612d14097e1872ec391d29f000080c080a0ca6269197e71623f391827c2020871d611e341f74c12ee1d13e274348cf8c996a0189ff08439030629c395d59fbaaa626b4fdc37744b94a30f6773ed3e4a31420f", + "0xf86b02850a6d3076f282520894a32de314c33429f9b83d2f7b516b597b19d5d520872386f26fc100008026a05a7bfbdb0d54db91077ce176d6469fe54a69dbcbec926dd3f7dd028db500b6b7a0473734fb8fec5db3778b1338aa30803aba5528bfb9dd33d2284674974e475a29", + "0xf86b05850a6d3076f2825208940297567c6d98ac887a6e2abf7ca5beb65bf82663872386f26fc100008026a0f8e2555b04b2b4bb471d827f27c50777a72ccdd250ada84d485c71d14184a851a055b80cc36821435c8699fc33274a98446e575457ee6792fe6edceab500a9d080", + "0x02f86f0177843b9aca00850a6c97e07282b54b944679b663b018b6c944da502031634ec1ea96a6fb80841b55ba3ac080a06beb45f3e17dd7b13c6953de2a8da57e977b2d10ee93ba5f9c5b230ae5bca80da066a0b33596cc99ff61fb60721c115e01dfdd6fc8f603eb575bd93f0e2e6cedbc", + "0x02f8b001178414ddd4e78510d6ee2ee682b42994dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000003522965bf958bb753ef12de9629a8e453e0d71f700000000000000000000000000000000000000000000000000000026b545ae80c001a05fd964572f3d2b7857c84467e656f3cea3d9c99d25b76dbf97634e6509d66901a05c5ad6323f5c0114d0ab0a7e3be84032e8475444d3984b29bb2b2cd6da5445d1", + "0x02f877018226318414936b2c850cd2f622d6826270945a3fbda16754664800a638ae25c689c358ccb1a4874a9b63844880008319b77cc080a0c3ef28405b34c3d54c32e56987899212fe8106536d9742acd0aadef4b631e140a01e85eb7237502a2bd4b4b655892ea7a0f8f511cf4efa7fc167a46618d930ac07", + "0x02f877018226328414936b2c850cd2f622d682627094a7a50fea91fba3860fb86ed3610a5150f84ab7fc8740a8cdb6e980008319b781c080a056364c8dfea21baac1cd2643de6596d091e5ad91bad0c956e75fd3395e817825a00f347b03af3be43c67a543f0449d885345d6148df5e7499f577ff334e7a65814", + "0x02f9013e0180850a662dad61850a662dad6183023463941111111254eeb25477b68fb85ed929f73a96058288016345785d8a0000b8c80502b1c50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000d97e29537a876cad750000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000180000000000000003b6d0340106b3f201f7b152b3239d2d443b1b1743b108b743c748c39c080a07c17d816c90cc71a19c56c4818d7c97112fde84d0b10b6e0e092e54b7da5d18aa051cef64e0fecba40c2ad98875987d1a2f557896fc7f0e59401301a305cd7e173", + "0xf8a949850a662dad6182f2ec94db82c0d91e057e05600c8f8dc836beb41da6df1480b844a9059cbb0000000000000000000000005c549e582566fc28f88aa54b0421d5f9093c90c1000000000000000000000000000000000000000000000001a055690d9db8000026a08ad6d0f6fd5b12e0c7d89dd6715569de7179d6a6e24cffc568bb56adc2401363a013dd090887c2197d89cb811a1f6aa6f78fbd1afeef710a6923b55876539b536b", + "0x02f8b10171840a21fe80850a773f59d98301132f94dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000063e3506625e417776e3f83d1121daefbe81ea6340000000000000000000000000000000000000000000000000000000625900800c080a0790f6ef76840fe24032a2591212b926b3925eaf3374a878360fa9fd3c3aee796a06a1fec622e81eb6967c75f7848b711b2a934f7ba09b6a02c57a23797638cbec5", + "0x02f875018202aa8407bfa480850f66404f008252089466c578190fc157e230a7810b1a6db67e13925cfa880214e8348c4f000080c080a009318f77023c606cf6e2ed00d1990ae450047c3f40018629f2f1a1a72feeb8daa076380b6f8553a6f71e66b8d321c5a055fe4869372a9a1ec6eacc3e0ae5e5e46f", + "0x02f902d40182ecf58405f5e1008522ecb25c0083b71b009440864568f679c10ac9e72211500096a5130770fa80b902645578ceae000000000000000000000000000000000000000000000000000000000021015000000000000000000000000000000000000000000000000000000000000000a007295d94422783d4fe7fc43c5e20a5776f9e46e12735c9aa8d47979e40954c74022c2bb7ba287e18bbb1bcad42710f34f0505d45cc102632660dc3580b81dd7b0800000000000011000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000d030d8763f5ee5e9f912a63c2e44a9a4b0719c27d9226863d5e0c8ba4e687471007f8b374db3a04403df6611d21e1bc998a0e3e36767edd75507a14923bb7784e0000000000000000000000000000000000000000000000000000000000095de801dd2216b6b224cefba3913dc83de22a9d3b642daa52abff17d46b561c25951405ba2078240f1585f96424c2d1ee48211da3b3f9177bf2b9880b4fc91d59e9a200000000000000000000000000000000000000000000000000000000000000010000000000000000c131ca811505599ca4957903c258741b31aabc4f79ece9f90000000000000000876c27b18bcddf530b38fd396edcfdb15c140c435359d33e0334c5d4f6189e872ad078beac2e8cd9375bf73b5583f722ae89dddfe99d52e6000000000000000000000000000000008c5109350c693c3a2e974a6ea38a07d200000000000000000000000000000000019464e94991fce56f27e855f2c1e5fa00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c001a05880cc75fe51c36c156414a3cb71c8bb0e73a0cf62b692dc057b0f15df6f1807a02b3e069c603a9beff43f4be59c0afd2002ee0f27c8d81775eec52a0c43afafe4", + "0x02f902d40182ecf68405f5e1008522ecb25c0083b71b009440864568f679c10ac9e72211500096a5130770fa80b902645578ceae000000000000000000000000000000000000000000000000000000000021015f00000000000000000000000000000000000000000000000000000000000000a007295d94422783d4fe7fc43c5e20a5776f9e46e12735c9aa8d47979e40954c74022c2bb7ba287e18bbb1bcad42710f34f0505d45cc102632660dc3580b81dd7b0800000000000011000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000d039cbb700c264ffede35259f488b494a2139ac52f72081eafc46383768e6f54e036455b87817766982a35fe43819b54990ed473b33d1e06da6243dd0f1e5b8000000000000000000000000000000000000000000000000000000000000095df2041d22eb19105f41e51e88f421ff7a4f55af77c78408af37641a15b5db4f889c05ba2078240f1585f96424c2d1ee48211da3b3f9177bf2b9880b4fc91d59e9a2000000000000000000000000000000000000000000000000000000000000000100000000000000003f9a24475b02b33e946ade4cfc4fa7d4deeb303cbb437b6200000000000000008666b93ba6dd4f173c6b5325f26e15f46bbc58160f1e8117001abff59eb9692eb2fe7a6c3f4c46a3bdf97c22689d103b43031a60efcf1592000000000000000000000000000000002a32a2aff1b150e918c96156d91faf280000000000000000000000000000000015b983311b9371ced1cccbe7953e047200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c080a04bf9ba9a85c626beb6d447ad26e53f70185d9de883f678484d77e8609c189cbea0073cb98e3f4999049403deb809ec6c9c0068aedee833df6e614d0cd1445c26e3", + "0x02f8b3018203778405f5e100850b68a0aa00830111c8941bbe973bef3a977fc51cbed703e8ffdefe001fed80b844095ea7b30000000000000000000000001111111254eeb25477b68fb85ed929f73a96058200000000000000000000000000000000000000000000000e706091ac5abd5591c001a08ecd4015a2c7ac2d89419ff0d895b8fcda713ed3f247929505d803d68eae20e9a00cb7a59bfc73663891daf7a216d9b5b5641ceb68e6c18dd22cf110e1208915cd", + "0x02f9033201098405f5e100850b68a0aa008301cfa094d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef0000000000000000000000000000000000000000000000000000000000013e4a000000000000000000000000f5d7e9c98b50f79f95cf1ffe4958152001f678040000000000000000000000000000000000000000000000019274b259f654000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000011a00fd0174c1ce8ab4546d6b0eaef4cf67a009c8a94c19d12ca849eb71fd192282608c7bbdc2544905d5ae9e9930bd059aa42d46a0f959dad4ac9faca4f798f0abc411d8912717a1eb90b30a4d317818bdc883d9cd8a7190127fe47567fb82acec3eae723a4148abc7691b2dce46761e28cf2f87f5232fed73e590dc52ff30b53b47ab1b175b5b5e551b0ed445fbe36420411d97b9e2a93107106d03b9dd6acc3ccbce6478c3784fd9b4a3621f42f370cef10b05d547896b14ec35c45c7d482805648abc7a4e6d0b16c1a1507e1994556e4e2acce89374a35ae0c061b645091425fe80ef85bc3e331bbaeb3161c2fb1dda6fc18f857831e23c927451f437d1e7d5937f65a1900a8336a53cda2bec24004a9d1456f78ae70d1f73465fd4cf919a4380350982e0479c032ce002d60c2a6ef7ae43f20642b726bd71cca1c3a49f4508013f45562d05e53b3a266edb09d6c36fe1dd603f311d68fe6f3f0e0b955dd735361874dbb87e350500fe9bc850acd8dfd03d626be0e60ec97ed189d09a0056c5f22909cc52b3cf1e69c3f1fa49062129331fc72283a842f3555e1bb19543955591b25fbbf6e15798754c79aa53f46bb2c72ba25bd77457c639d37bf351fb0973817ee8a14f49b7efe1f8351d9e474eca711776c9d4b2f80a01e0889a1e8a3497dbbba44476a13c02cfed0b524896278c0bfc2ede44fcc65c1b9efe49e33a5ae340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec001a022ae5e44a99bb95e8a1722a50fdfe3cf64956bcd4c15fe2a708bfb1aee4831eea04c1b962f1ac20f8d77f59fc96c0816790812a325d65f998b4ef9b776bd6f5f78", + "0x02f876018307c4838405f5e100850e75d6a3118252089496b48748b24b91498d44fc8cd84ec389b54e798a88116d89737007300080c080a01906902fc22e12725dba875cb92b9c189ea61f180fb7de62ac29346360616ca8a03df0354777b23d762a4521459378e2b74a2ba89eb9388b976a0933061b157aa2", + "0x02f8b001038405f5e100850ba43b740082cb0794b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb00000000000000000000000067006f2c487cd503539491b383c63032a841439700000000000000000000000000000000000000000000000657b3801b80b40000c001a0ffbb920cfdce35101bbf5c7f009fab0ee43e9366686ad698be97fedd490e4289a0764c9c151084f7b5530ba85f581ccf9de86810002dc3210902a9d659fd255d1a", + "0x02f8b3018201418405f5e100850d09dc300083012d7a94b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb000000000000000000000000498d3b772abf0d83be724a95518e7201126fa4dd00000000000000000000000000000000000000000000003487938e0499840000c001a0f09582e0fc733b9d6952c2aa9ef94274c94d3bcb8362f0d57d13ca89a21dc232a020e44fbe4dc03bec5eb08a1821a5549c2b23368a3f7963b33ea9f923ca3c8416", + "0x02f875018207478405f5e100850f5de814008252089443a61be362f1c4faafc7f1573e5bda4619bbb0f48845a805a27464780080c001a0fd17102e6557f82743863977af9fedf62f214db05bab0a458128d555876449d2a00a471be82cca8be9570c13738f31e0b33ec2b7298b8bd3626f9d5ee60b14b555", + "0x02f904a0018202918405f5e100850b68a0aa00830480c094b2ecfe4e4d61f8790bbb9de2d1259b9e2410cea58803782dace9d90000b9042870bce2d6000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000a00f2a39633e4106ad37cc4c4e10c7f30d77c2300000000000000000000000028a11da34a93712b1fde4ad15da217a3b14d94652719600d335e65eb5bf5c0d037479e9086f9efe57069d205f886cbe3ebe32b2200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000065f3c6a400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c57a69b180a4367e796e4811d19b7b4400000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000005d22babfdc8047c4a91070aa04759bda4ea77f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000028f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000003782dace9d90000000000000000000000000000000000000000000000000000000000010000028f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000417b9d43d62383e3bd5dad9411e62388543e7fb56de53ba9e91776c7e0b002e0770d735e2e0b9b7be59ce8bbd4db583acc243e0d45ed61cc14bda0ce68381816e61b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059e1c5e24c8ccc22d545feb2ee523984f41249b0dfc0a3aad6210f4e33c44656554911616f2beb995495963488a1009ed1bbf275e3dda76a691c891a9919b19abe1c0128819a6af68e5d010513ff70a3aaed9afeb8661116e6ce00000000000000332d1229c001a05780991d65d7cbf54a7f7b2933e56d1d3ef409d1970dfc5459f7f0c87deb4ae0a07536a692c253276ee377987675e9db6d6819d52ee6be0cc1af644de67ef3b83e", + "0x03f902fd018309544e8405f5e1008522ecb25c008353ec6094c662c410c0ecf747543f5ba90660f6abebd9c8c480b90264b72d42a100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000d02a4e2a181575ab70a9d8404b79a32169d68df616485ca2461fc75012eb719fa033bbbe635ce2226f0b6eb17ef629cbbc493f591df3382262d9b51a9f08ee426000000000000000000000000000000000000000000000000000000000009544d0095d2cc27eec6a3d3f322887081372fcacaa45e0bf613bcbd1eee0f9cb2be9505ba2078240f1585f96424c2d1ee48211da3b3f9177bf2b9880b4fc91d59e9a20000000000000000000000000000000000000000000000000000000000000001000000000000000046d28d2e52040577a77957256c530ca25974f6a814511b1a000000000000000097d62d4572935295f909f243714201d9221215bfcc91af650500bc56e61cc10fda276c872277f0eb212b54000c8ef146f5d7f1b2a6d176a100000000000000000000000000000000f1095b16b9bc2e06de338ad6bbf6ee810000000000000000000000000000000017e5d40332f9657814a4deb4d81127b4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030b220fe303275c35177980f7a03cfea1b71092701195fb3cbde91fe2389d0c0797f4cb6976711cf4bb184b0372d48930a00000000000000000000000000000000c08522ecb25c00e1a0017f8d5e53298d8d6c73bac47ffcf2ec1eaef1d9874c402a4f4a7c187b2fd57401a0cf8f0152da9400b324b56b5c14b52fbd5ffeb7f46e4a15736aa0888ff9e47037a07f40ae77195347f761f2131338a78c624de434f7414713f236b08fcd5ac0ed8e", + "0x02f9039601178408583b00850a583bff808303d4e9941111111254eeb25477b68fb85ed929f73a96058280b9032812aa3caf000000000000000000000000e37e799d5077682fa0a244d46e5649f71457bd09000000000000000000000000a5f2211b9b8170f694421f2046281775e8468044000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000e37e799d5077682fa0a244d46e5649f71457bd09000000000000000000000000446c8de76c4d32b607acd88c56380bc9bb732d710000000000000000000000000000000000000000000000fec99a4a552ff000000000000000000000000000000000000000000000000000000000000076b71256000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000018200006800004e802026678dcda5f2211b9b8170f694421f2046281775e8468044b4f34d09124b8c9712957b76707b42510041ecbb0000000000000000000000000000000000000000000000008273823258ac00000020d6bdbf78a5f2211b9b8170f694421f2046281775e846804400a007e5c0d20000000000000000000000000000000000000000000000000000f600008f0c20a5f2211b9b8170f694421f2046281775e84680443d3f13f2529ec3c84b2940155effbf9b39a8f3ec6ae40711b8002dc6c03d3f13f2529ec3c84b2940155effbf9b39a8f3ec06da0fd433c1a5d7a4faa01111c044910a18455300000000000000000000000000000000000000000000000006fd1772e66b5cc7a5f2211b9b8170f694421f2046281775e846804400206ae40711b8002dc6c006da0fd433c1a5d7a4faa01111c044910a1845531111111254eeb25477b68fb85ed929f73a9605820000000000000000000000000000000000000000000000000000000076b71256c02aaa39b223fe8d0a0e5c4f27ead9083c756cc213dbfa98c001a001697eb858418a3989ee6ff94d2a7a22e16c615dec996a0d47898cb0bd55603ea00dd83d692700edcca09da2d4ba150852507df921bd372fb6dcb17bb555767337", + "0x02f902fc018201a38405e69ec0850e20cf5200830356a4943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad8802c68af0bb140000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acb700000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000002c68af0bb1400000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000000044e46be818a9e00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000088490ce333e3f3aa384e5183836142490600da8cc080a008207e2bd0ea1ebf384169461e7775ff60099963e09cb519547c76225a3da239a01756fec9550faf2b48f0180db30e2e019915c2d9c0881d405f16ab2e036d4719", + "0x02f8b10181e98405e69ec0850f896afe8082b77794c668695dcbcf682de106da94bde65c9bc79362d380b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a0906def09dceb8606d66f1f346b528e31291c036742049b45dc5c52914f262288a03436327bda153fda3c63c0939256a239e0297e75609a944f748e240ebf08d622", + "0x02f8b20182011284055d4a80850d14fd758482c3ae94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000d3b5e8704ca157ca747890162cec428cf71c1b0e00000000000000000000000000000000000000000000000000000000089c5e32c080a09c8ef1cfa82438957d36aacf2d46583fb083b4c921b13f13b8584e2582561d55a004f1e185bb4ace351de31d541ba40fc80cd932a56034185734292e836b36b49e", + "0x02f902b40182014984055d4a80850e2036bb80830460e094def171fe48cf0115b1d80b88dc8eab59176fee5780b902443865bde60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e0000000000000000000000004591dbff62656e7859afe5e45f6f47d3669fbb280000000000000000000000003de254a0f838a844f727fee81040e0fa7884b9350000000000000000000000000000000000000000000005c38daab6873ea764d90000000000000000000000000000000000000000000005c924c7af1d52c03c030000000000000000000000000000000000000000000005d0962bbe25306ede6801000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020096bef91365f7415d82b2e1499e8e5d2e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c080a0a3f62e50c347fff9f263d8eaa39573a51450b6f844c939c45052c9ef0f70db05a056b79404a999551f31cc78502b6d77b7492175addfb307d9941e5576e2d47817", + "0x02f87201558404c4b400850e1f9e2500825a3c94f2566618d6d4f63bec3091602a678d59e1624bfb877c27fd1ff8ce6880c001a055ee943aaa726c9c2dadf8a4120ba3f20e3ac1ea7b400b49e23d4742b2a90654a074e9a6c93b9fddbe6a3e8263b49e2a96865745efbf100fdfd5bf975a33b5684c", + "0x02f8b101168404c4b400850c963a23008303486194b9f599ce614feb2e1bbe58f180f370d05b39344e80b844095ea7b3000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a00219612083352645f4ced91c957c510fc470e3425fe0b485a11e5aa835dabd92a001991f629910c577cfdfeb5c1f9b455347e766b9cf7db2efe359a54b687796a3", + "0x02f8b101248402faf080850d09dc300083012fa894db82c0d91e057e05600c8f8dc836beb41da6df1480b844a9059cbb000000000000000000000000d74d21ae35ff1337a0d6d79989228a8ea0a83904000000000000000000000000000000000000000000000001c9f78d2893e40000c080a080aabe18ee85ac238ac6c33ce3df02e99518d12a2edcfe57b7b126af9b4526c2a0141c9b35a2b5f9725d85bcf86540e0850ad4a2af63e3156d8b578474c573039a", + "0x02fa0186b80183080e6e8402faf080850e8a27214b831d252e941c479675ad559dc151f6ec7ed3fbf8cee79582b680ba0183848f111f3c000000000000000000000000000000000000000000000000000000000008d67c00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000161257000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb5000000000000000000000000000000000000000000000000000000000a039428000000000000000000000000000000000000000000000000000000000a0395360000000000000000000000000000000000000000000000000000000000018291005b1713353b50bb4d0480b19daf979a3df50937c6d9ed2cef7023ef935148af28ab7733feab041b0ed516bc1f5112591a87ba60ca4623e0060d456880a53150ea20da8610ca03f797c76bad37f5aeffbe33c01d566e71fe3cadadf7feff33dbc492924ba552a1ce1246e48260a1b4d1236515468fdd608e7127177a8795ab9c62a3bd18c7005df3fefb482c115bf9c3e2f257cb1f96b0e0c5e5c52d6c416bc14b18a06b7ec60288452e680997c805adbc006c6a53be7f4a9fe566680b2ccf851e0890509a2ba5cf09fb8572df46364b555f1b838541cabc8875a7efb66c2e56c4de220cd38bfdfdbfaaf6cb071194e83155b62bb529adfc120c1f21c67896bc0359c35d6a6794ce19c265161a8b009f6051022543628a4a12956d2e6952aab805beb83c9429f0b981549620351b52e116d1da428a5b446b6b2168d12b13d4b64200f60c05113b29954ea942f01e9f1b2d3745e4a3194a604ae30c40a5c0e416a6cbded2beb62bd9a6ecfffeef6ffbfb48fef6d3e547d9fee98cfef6e3afb4665fcb6ffa072570a2afbe24a43443dc4f7e647b52dd9b7a663f6497840969e62e1b07c6613cc270382cee840f7108c985b44b9c5db2bae00d4223141ef84f2f02a097a10381f3036d3e350346e77e6d73c9efac6c977fbebfefdf774e33d24f0b6a944eaddae94e5d3af5308201e1162c1a81b6a3807afb4d3f4c1126a4db45c5e79f39fbee7d6f5a79bfee76b69bce262661ff30094e30121b3a8aa006b18315937e16fe5f979ff75cb3a02f9e82fb80976ef0c04831d86260305276374e9f94ca4af3ff1fddbedfe7a2f34acbfcd235d399f52d191f2acf878a0db151fa4369d24a422024900e38ba521534c6e1aa5a2116120362898cea4253010aea31b498812dde51e5cd60680c0a7ac3403426513dac4154b23f77a634afb7da688daa5ac8ed180de1441c168782fab70f02d001281cfd67ea4ed16e2d6e83305ffd5629b4e1c12d8264af947bfcf068c22676d62a16ce9b8ebca121a11cdf0997c78eff078c1f56b259ec78e48d842072cd10d3fc2b05f04593c10cdc08362e296f8e027d3a900d06160943d8a6f34a20a57d6fa63afa1eaa76e151122dc1734d1c086795d77300a8c6bd09d2881380140c906998113fe9ef835afab03246d3f8da653bf9b5606a845d8c0a207d4438a42082022ebd7359340064ee0ab1473b77406e6e15105beb1401b84b657fac71029c4206014bd6a6033b45fd5fdb9d0769a4954273659071f90b6c1f8c99ed5a1f5156699ee082fb81d97313c8354b1414b09ebbbf5fe73013abb92a98219dd8f121019a1bd9dbadefbf28852524b851a11b5ad28c72bead226f04186ce36e6067a36ffcaed01a96349a4de496faabd1ff58bd07d460911607d281aca6654346efa41e59f1a7299e53c3cfa2f15ae853ba49a4405764c17a7824eed03051324ea2859a0b38edc2040920050a3068ca2d46315dc830a3044d2c5380189a96a41c70de31297339d227af28695491088881d9ed4bebc3347c50135513ee6ab4349a46615620cfa5ec2403a680d4252b017932237a9eee36b6ea5ea62e84b75532a284c42b5dc19b9f1ae6322ee2b50b3d3be2c262a5b33ca16d298089a5510084550cda484a6086dc15a39229601c1dad16a7222be6c7f13b4c5b5157effa52b7ff49fcd34ec251045cc725250c04039e346945437ce8f0102ba198ca0909ee043b382c00ce0816437df9244699b33be0c8554a03e42f4c42a9ad2a8aa317c09dfd13d17512f5fe418e08b796f5e9d1a313dafa9bab237935bbf46915cb0d145adbfc54bdfe30314307c5cde844972186e8c907ad122cccc8938ca56417d14e7086e27e9ccf4fd21c2e4158d98e2277c1f656e391f572e0c86c3d809f21fd2919059f3f792dac991ba276f90335868624067a32011405136094bdf1b132c2207ec5d0feb299b3d7b02c9e0c3fe6b2fb12a8372193f0617a0d77971671aeff9ac7dc3a0400faf346b64c37424bdecb9e5e197dd33c7875085a72c75b9a38caad2a19940243d8e84aaed91bb92d2582ce8f4166c602959e3d3cc3569c94aac9a7431e599ddf1e75d12ec0fdb2c767823bc131cdf2906a0d0e482991cb299dfd6adbd987ff72fdb2ba5ab86dc20c072b093c801192bb4ee1abc4283c4b8a52e62d8e75acddf8e461f8e80931bcb7831d5ab4c725443edabaeef7903c5fe16bd3a930b4295ed3c54872f640ea2f029464e7e07f65ac066aabce59853c9c59978eceb13dd6a06a83993206b53f86378b5837bf60cb96e4ba1f15f9a92ca6980a3e64a4cf48122608211f810793049939f039556c8ca0b26ac44e2d7e05fdcdc45fa5db65941922ef48ce69dab8d316ee24b366c3ca909518d6eb5f36a8612f3ed7b761dd0d29cb238a208ce5017ef8691b10ecadb39482542200a357b6ef602dab497e25158426f0b8d1f9e2fad17f4365df493aa40dd445d0960199cdddf3b3c5bda4e1c00ed5dc55fae3538fa2433fe47be7287106183b6b9e07100fac82802d47ebde67edce90f0a6392350d9b75e1a5029de8e5b2a3741e0d83a44c1dd6f3ebaa2844ee6ffdb37ccc8c19d0a9550053d005965c8677b71a1febb34286853048dc81c51eb9bb4b7f39a53037c07edd97bc08ed716b15ec22bea39eedbed2fc2287d3ba57f1168f865352658597f7b5f91df0c12a140508c6a8e301eb6ec66e35cc3e55350f64cff13e30205d20621f5a420e3e5e58f50085035f26e91330058b78eb5feb7aea786371ceae6497fe2b5f0336dd71440e573d653cc2fff921ea17fc88c2a089dcb3459d705c15d1005a1eb642dbb6c901d889fb33cf18f1b22ff4d3d7ad55cc24795a0c37af566e5fe65c3d305699e6e9aad9148c9eedc38031c56868c3dcc778f18160f16c2f5c7f88022e134d0a2a3c11b34a96cad0d31425a0a1e9be9f5b6deb2e5e66cca3263a0c8a240684a082e957b5a2a1b13c6188a51eac11c549dd167850894babc29c9034246bbc3f8064e09b5f57af641a5de2634eb5093dd1bd9885b954b26b17e3eff56226d02d492b02c1dce0acc07d03037bb61656cfd98b029eacda37c42876fe046c767a6e178e14294ae018f6ae96ea554611c4952c7b6603baa3392a21635f69483a1ff1d9ffbddb8a0f16c57bf96b1d08d4524a05cc3395c078201294e4e46523a4f2e3db5a51ac65eba331c6c18f6b34502ed0f641e1c758d1565807b9cc548f4e61e472fed1aa7caac4d4681f08791f87e9c5f6dfda6629aefa636ec4189216f14832f1cd6981f54aa2d1e9f4a329be2b2b55de1288df894b4dda84541fcbf7a2772911951c104540d2e2f5f6611c5b9242377b80f9b70ca30a88dab5bd98d2d877590704ac4d0ea5a96dcac063c5313505a58e1a141e6552ff651fb93226943409d93c7a026240cf39c9abbebcb07a1a434cb144f663084061366fe4335d7aa81e457c879fcb140823264eb15397e88cb522ccee7cf5c263a952f0629615a3127ca0c6d0ed8f7253463dbd763c644de71f4f0109346d87c0c288e61946ca3e941ace4f7ddfc810c9629144c263a7347467f4d5e4e6e3590f62f2f708d509f3e8434dfa9d749537e1efbb1fc4eea63f22b6223e577e04418067b3f2bd95947e4d7cb61c96fbb354cc16a517edd0791a95acaf04acbdf80782dc44917d85b15e1810d8e43d3571112baad279bf316321350f883d2f83c9301a780b542be890a6719b072f368e386b6c854a3d3ec9c233f24c76f980b3293de0e3f10bbcf038f11cf9e02958178394195f7797eb9915a3a39fd7a15a450a8efc99a8daa44a244092a60ee2780c468904a24c0baee0c759511f06f8c27b13bb08b86a4a1d9dcdc89e040e1beaab75b7e01560b3044584f2f949da0f6d58fe2ed07cf2061199dbf91b3cc2f7cdcd84bdf227ff8dbd100b0fbbd3031c8fde5d068c6f9d5d42f58fbf1590319c299dc88e323efc281b682a9cee04e0538cbf266c1e64fcecb17405b9c2f9de61faa87bef68af576047b4067f5cfc4d13d652e115c1ec18389616a0ef9e0c45cb061b47abaa868c8fdd3ea0fb839630e4b113b24a0a4d6e0bc2ffcb8651aa65b8986c2553c6dfe6521179b653f9b2119778171b4d30923e62d2b5221e43e0fecf8dd088b075a8a52b2e3bb73244984cb53ee6aaa5edcb4739fd71b5fddadc3bb7346d6a11007852cc29900f8750b8d5108f45b22a763e3d83f02f7facf5281e029e7cf26911c846bc6d22b6b6a0a046c99105ad21c8de5e517eec5990a2a538a8c034ac918f552f935a9a5f91395f7343df682180f06de90a0cbc6a3682fe1324d8241d7921cdfb23e4f34665592492bd0870f0e9827af8d4b9fcc9676817126d860605dd8cc00caaaae6c8d46b639d21b08c2c3b494ab4a95d95e24aa4fd080c01f43b59298ec5089e68d86b5883148291f82c3f88cb652c96c7b2e8495e0bf48328ff0e3fb1b186234e29606772128889e53e4044e292074030007227aeb17d77471cb073457293e9af334ca4f7923b6425e6dffce06ab0847a098c86ca434a442ce364ec165d95ee2a212be7b4effb1673b396dd7bd9e5a601d42e9ada053e09ef042388c9b8d15a4c65947638c893bb17bba834a11afa7222aae733046a9b3bda5ac89966a27627f933423e6086fbbd85df637f1dd1b9ab9e7f2c361af552f3c964fafe4460c4d38b8b8a6af12957831287cfcf140d6746c4a2d6cdad3b1c0f1ab836f69668fe1011b3a12ec35243cef3f8ac33c5cd786d9fe0eea773907357eb405ef1f9c0b3ca403187257a01ce9bff10dd77d9c92dcb88ca1f8f83f22f8bfe4a16120df0954114fa23462fde150c01965941a5b42c929c51ae56a4a50c6fba8ecc3d287b562d1d0861e075ec19a8fc16d7dff8cc993cf397b3ea6e8cbfcb8949b8c122eae74d4da78f2ade8d2ab0120b2b258b9a02c3886985e35c71932324d0e12ebffcda413fa1f61676c05cb18ec59e80ba6ef03d53acbb1226d680e22897c9930d952eb34a8161276956c3704b8bf696f0949ec1a85792435dd3405c840ca698c31d77c03c1c07c7ce0a150ad80486c10871f34ab697b81c2237a6ccac2d3812633274eeb973fc0fd1b964fb998d969ec5f6166be63006369d012ba7098694a78a74b66659db6829c1b8d211f5023b859b1c1c0541a05eba7bbbd8be9993aa78a7f882a29be214f5e12128946c317ac2117e5b9e93842c39ca50389c14f00bc24b369d9ddaca45c3a5be697829600046c6e6dd58f16c862b425b9f3fe909125688b80fc33ee4360194b3f7595cbfea40547b86b9623cc059c4464064e162c961e9d85e3bb90e6ffac0a36e37f0cae02543a653e4c368df87b907fa9d609cee2f6c36fdad910718a3fbe3a4da9a6c1e82ba6a4c0521189e4d89496c3a07be01d9e540ba4db1bbe9d1088068a2176448c30b0efbcdba4b5f5a7c7c53783b3f100cea045ddb633e919b0c2241ca7a1872465928ae9ae47f9aeeb7c6c88b69dba99e6ae0b04025ad9fac038a8de9d10af4865cae763267a7ae762199ccff8dbbef43e0571426d7bc41bde784320c0a76d5b9177464d71fb23f628d498755ea076a3a49748fb5ef05337aadcb67404b84a227accb760c1824bc391ca68eaa23914bd5eb82331c7c3649f35b01e9ccce0a142e149abe4e0614fbbcad82d098ebddc6fab556bc20dff7112476da215ff7d5f548f97087cce9d8d9de46dc9adb52e8afc96fc3a5ee2604ab62f5635e2d98bcfca9a231f0d97fd41c14ae1b6bf961a61fc01ed459b7afae965577710d1a21be3967795bcef5017e4dad51d045d76c47273c3da500e175ee3901e7db47899b5f594c3276d04d8339b6a65ea2bac27bebf38de894e1470b96462bdfeb306c5b9818c7a55fe53288e99590c10b98820b786b7bdcad6a938df4bfd19f28070110dce60bdc8d61bd6ec7e859de1dab074d5d4f7cedf0dc6a27cc7d53251bf59827621c444871affb84402b8a2bc72c6435b893438aa7f3631d6ba20fb4e70b1d80af1f5269c4e831780218ea1b9bbdeaaac5e58324a740edbb032ba1708a4b59d215c277824c2642ce9d6d44b084e7af82593423c47444b8784dbda9ee9993a39825c2f235fd624c3fef2161e538f82da4adffebf00409b9a9e819a1de0221f88031ff89314781d0900d440013a0da66d01ac3a379def00aaed6f4fb9803216d0cf03dc2200800477e0b03f70fb06382f0b7c8f056a1480b7536f17f720343760dc5d009a3e80a63e2da0d9016812402a1381ce6e40b701c0dbfab79864c0be0800b07d0b0c78011c5903843400c1a781dd3780dc59c0fa5460c57760c84a009adeed80e19bde94d0de427481111b81c812c0c7e9edb1db157f44d832e547cd1fea80a7de5f174fd3ff877a2ff4ffb3c303681ac2a4695ca0695b4264a427f45c55e163e07949becb5b08414594f8b60eb0f65e2e378eb3e42f31ccb6d42326a1db10b8d65ed324aad71f91e2dd7891378eb1355f171c99027257904a52d5d395da0eef0bc0f028d540f1af6db2f5cfe8103afa52abbd12d71763bfb41027a8ea5659d2bcad799643c990e513912558a0a443ccbdd08006e3e7c63271d0e4a75e1eb9f467613638c2946abdd27b52d05cf396b1d9de177bf7bcd7dffcda4e895f3c22bf60a4e88d5452fa22966b3a2dbb7fc508f472343e27c4cd5b6ddf9da9534b96d423e7debd733dc39a295f31fcafe1021cd08753f82a98506784cfbf007a2b39a8b84223de944342e8ef83f5dad1b742eff50bbebb49acde0616867ea375249e3f2fcf1179157c8c6b2fd7642751a28ecc8963c2ea289b56a99c7af94566c73b0249a0a3c0e2b97a4929e186e8c68ae78d8af255b1834541405ccdecd073ad65f6a79614f19f594c90a76b47a1c74515c779343302dc9486d3e5836a64ea48d6007cd83ffc2f1654fbb154bcb9d21463ef8a282bc1eb7add24435ba16e46a6f515391f14f0ee5b75514b29dd417cdb953d4f79f6ed11fb57003be27ec0aebed721256e7f1874a4a8a128ca9a0aca8776f79d772ee5f27c4d6de1358a12ec7973fe829f4d2991b6c98ba3f9608987aed6f0786401a01accd06475ee8f4759811a7743638c13741441e0c6a5fc4990d494fc5eb885be93e87447164ba1218b14302877d85101c2a875bf1a5fd6cc341a4185f4da6275486d2e078281457836fd5d459c3adaa0e171c98a30fb562a3a3e2aca84591344fca199f1d26b00b67f21e67b124075e3edfac32a690c87237bfa37343da13b6f23e5cbcbd72ea81322aab24a0a6210b8c3d0742f5c678804885695660b65c87ff5e1cd8b3d5d545f27fe4da6bea4144d8a5dcf40ee1a8b1a05a4495f200a33f9d2553323ef2bb0aef99d8aeb8e2a66a995fa1a1b1c124b975ea023af39a6d79bd6b0ccaa2ae7356e2a7926f1b092edf5ee416f365af1140cee2bdcd67334d868bf9ab9088b162371c8ff3f4740a8313b04fcca3f37069dbbad95ada885a98b87a59b16db3ecd072727e5dee391ec1fb10dfdda46c27342083ced7eabe2d4f4cb7e90a50987461ad0f0407587b8e8e366352a10704a9f496b032ef619ac5e1bd98316d6e5d31d23f27e9efa6944f8c762963356893ea50bb5fc2241a9135b716fa815dc5a564679d6eb3f581e8d71e3ee590a661c6e9a6582a55d8daed32bfe019db58c7e723e02055fb57353716f21248df3c8ddfd690f2e1096cc80960373abcb851dc0d19df4c211306033418e730c0a9b4faadd3951b82527bc1841b676f09a8fcee902f5010cdc88ed51e5cd94b6f02223aaabc254f29a211275747571cf89c56002eaa530174b87fff148dbeff86c714360ba400a53e4dd0aa1ad39cc9102577289964d123f58db789830f470dddb7ee775fe4e78f030fedc40dcbaadf6e64a7b103f4c827bec6d49950d61fc5aa32f26a4b04d1ebfaf02290a12605466819a9b001c10314f069629bd61dd3403edc62f38d84e844fc9310b61f01d63777706c9ca172bf3810c6503a180dab55936a887874a0f4284e508d1c17093a618b2f9a3a7b280fe1685caf9cb45276448adb7fb0720e02b7ae8355aa6fda98fae840726882968ac426968b2a3e4086b2f4b9a60e409c827d9c86f67c0ca088c1ddff89f0fffac368be56c2ce224906a84f9d597292446038b407db029fc09f0eeb89119966087ae7da17f933330ffbf43e8de78321adc443b868c889c98175cffc025cfb11ce61a2b5640dcb5186ae9f82dedf6aa9bae260015b220d575a37db3c0b42235b5b6e89c421fd1778535cfdcffb746fb37d83009b39e813c2a9fe07eb5e41f082f8579170f7842730a86651fe6b96b2763c50df1761bb5753322d1588cb87c3e0de0714549baded81c811741d2df3dc2c03b4e26e0790de52f4f64b4453180f4e9ec8d5e182c04e916b198b4000a3615e7865761905d05eec1ad55f23c51da37afecb3e0be24ef37eff6a0ecd9b168f76c368214d7b66c11fc0cb0ae5f446bd3446b4ec7d380026c3ffac8139499f24d227e6a33bf5450c5a9bc183df97f410d500f4f4f02f6a16e913d860743a440cbe119b1aab11e7a7d1f766eec003edebd04a9b9fd4f1e741402b61c7449c4441e4710d70de3a6a3146fb713b5d228f0ad66f2ecbbadfbd447f1a4b372638eacfb85bcd26229d727e12aeb912318f7c7350c04e8356d7b2fe4d125aef6b4abfca286e8a9ec8d0461f42fddfba94cdab35b0dd5f19d404d40f2840cc14cc3aeda920ad6ed57add498672c58d6c98be77057febdec61def25b8780ce00e44e11ca5a85570fc840ea9ada8547b66aa0c5c796bf4b8b09005c14b5ecef27823b417bbadd7dac031038a944605be9dfdcada9b94d2bcd7ecb9506ced415ac00cf082d4b4235841772e0087319e20d036d161ae8585dffbf4f124a05bb5d3f26a9c308a1d2008c4ae7674d19f036803c41a10c4789b0e191ba50ef96c104482cca1d2b03954c63b1d7b9f3e8cf3f2ebe214dd8bb852dfd44d31dc008f8355e5121971f397792c749dfefc50a10779cfd673182ded3c535ca780d8ee83224770ddfc5ea2a9a50ce5a11fc4127d3b10862f757428e9bb0abcbf0d9981a2467ca196f23a9ab9cba4dd8404749af2f83591bdc699fdafa7e477cb6c0cde1be9d1efcbf5a0380539edc4b5eb41c7cd7d23c41d5d82c54829bf76ce5911fa18839f9b0ab0b491415769f926cbbd41423e884881c60ed7e606bdb2c85e0b54e9a82f13db861bb812bb49ca655c9c92bcc88a7e91bdb526ba55bb8a8f50776ca72ef3e6852fda3acdab0f805808473c5a0b6883ceca31d799f9c398d83901d3fbc06b22c4bf28b9d73d2c53d74b881d87d2edce1a93c63ab637696eed00fb31c2a0df7f4ec60d754653502c5208ae95823080ef8ceb366b10bfb78df72bcd48db522a44312d5a7a25e63e17e4ed3ebfdc32b3f7b94c48a03deadb654c412c88965c1217e613cc723ffdea49b894fadff3c9f30d3790db11c77c2ae03ec41b91e9dba66e222ac4e19780f9f30153b806beb801a4f718b6e9d4531d42ca193762d29aa095242d528f65028951db61279ac1ec319ea5975b83724a25860f33cb717defaad651db9f276ce0c4f870c5f97307382ad0946304ec8b90a8ebda04eb9f9c6f739c1eeba345da1efaa9c1e4347d44079de78692ebee0697549692d3ef9097f9b50b960c3805ce3f8afaf7e115955002a7e29ffd99f6c49d01d8f5a82daa0ad97640230a1c595ae3dd9657b3451c3c8fe64301f3b804213affcca7eec5bcb69cacd61a2121ecac627514f438d2befb6a05567652ae18232b65e4c0dffd4b020eab286701f1a0da5c9b20356a2b203039762ec3466d8d1cb11a2b89d411287c96793a22caab04f9f828325245e698b8b86ae71dda4e1d596d869b941fbc2970d15358478a478bc2a817a4eac9bee93a7e265c30c14292c3b69c9be601af5c25d51f18d81bf2b56eea4faba354a6183cb52e4c78cf7d43301cde4d52e160335db6b8c4d25308b49d62dcaa475b065c1b7ff62710e0312f2df81a5b7052d06a06bd7e9e9d6ce2d4b0209a3ffb5f9e031a80759855cf3e7c88041750dc33657287b665433d3ffae87ab924cb87f4fffd1782a7502d5db74225f1b289b24e25b424b01ab13f9dd05a8b2b252638641bd96a891aa10a5598e60e5941bea1b14ac1288f6e96a999e99224c15441c5a146af68348c4285a883c81e61243c4313213f17bc5d53c67eb79e308fcf0ccf81fc5d68caa3da04e51e633399c7ea5bae2c69d72cd6a0c9095f286c17f66f492f8b40aa611ee0ff1bff4b672d3a3fd890f2fe13bc04c88ff7d64178c8050adb318bddcd74447a9200080813dc837cb5d8fc371de4a38fd6c00e2c080f4978b9e71cb1dfbd4870d2eaddae9a9d13aad7ff40357a401ce6095279e19914df63c92f6906a42a4ae6434b553c0225644b28b34e9947df852e5a762449953f78d0c6fc0fc05dcc1bc6e8909408529419ef4baf90e6e9ee2f5e25a9a57ec13bcf0cf9b21d75980db67595e3c7433bd6768614798292bd100392583b7f57d3fbfe461c3e27c40244102b6dd8ebb9552149731defe8078e12c50c54b6dd75772d9c70524dcf945c92b9c4ae7b49f4f357c9180c39ea8309a01540860a141e04ad90b72484eb0e5bd3839ff383b5f4c2cfaa917dbf6e24b22ac5f1769eb68ea9140ea7f58a939bec62539440138f728ee91eb991ba746c72aadd86718311fc5bd500ecc1daf4d2e8bd506a91106698a3c2ef0ee9be5255bc3006eca3b9f113dfb20e61679c66574ad90df503d3171c65b34ee003e1f2cbcb8a3aa8639eb4fc5c6dccbedf375349241600fc8fd861c76066909d109803e81685576a4953ac203b76973d659c0d138122886b435e330abfe2b67e3ed14b2dba6bc7f3e32f81ce443d6ed4efeb75af3d547bd07d7161ea812ed7d64c8cc79f290524030fdebf736f7ede57d14311bcd24168fd79710f5bc92f75ce26e9670a69a2b71ff75f4883ca9eecfe6c072a9b73b1db3abfdfdff47cb55ffe258fe30edb82eef76f2bf38e26f75f62ed91a305aca0fd8f7a11d71ce5cda03ef6c292dbc874674e7710b45a578542fcb10292e54f2f4b7225cea0ef9f1c66761087c96e33d43f8540dedcdcdac3bcdb602edc66481263e69d31921f55a0e2f37aea429401626c507331a4ce2abc5860634e65db0074f8ce3ce82778a9b3aefbe877ab69550809347ccdf723e78032972bd8f7fc7f6ab10fc95dd262a6baa4bd4b9cf70d8c1e6a39b00cf5b4990f5e9cda3d2fb3bbc0f56c3443720f0d72f9ab8a72967fcf9ae0fe67d3716cc5339caa190cdbfd2a22d56651c6b62b8adad359180df1eeda5bdc2260372c4b9e62131e454a97574ebad6fd1476fe2d19474c5b76d24e174943827b4e74c9f1eb10916df4d60080a92050480d4f3c23f8e1c0c5f4637b60ecc46088b9382c0a0d49a4789427df93a1013a67af77967fc94ad37280a6bd2e1b871dd6170551feaad59f9bdcc2deb017931da8dc2aac8d86ed13e4be32dd2164b31fb7f990148661a2d67dcf231b925fa831017d666f2d77b88a148528dcc8c2e38f1c82f8d7a9805a589a6644acf382205d4f67b58774d34958527bc413b15343021c5494ff821ba99702d491199cd8359d12011a9e24b031a69432e086e7c2f5030e67e8f8c1859ac0fcba0e5a2ada3f495871127134c32d65a363869ef857055538e75b236cf11909f541f5dba7bf6f6fd75256c3a430aa92107a9ba228b80b4926f6840cd86cdf47a137fdf690a845668c4adbf175f5b9cc2d3bfaf2b6f1f009d87c8510807f32ce68a392a69c17faae647927bf2d1f82002da71df216a1a4e2758fed7626e4c873c60bc7e2befa9aef9dd973adb6f6f342dbf372701b17089e925f60125518e19dc096270f6fce1691a360a2165f08a25e4d851d07c66876ca399780f2db44e960059b937d369de97106b6614fcbb081a43f74e9064d2d609d8ded5e9bf301245d83bcbb12a5a13fa168ef21782911953cf9666001af32d79e3a32731dd99124c7b81268b4194077052d5825aea1f763600e7a89e551dacdc0095ed79772af3e405ac791f4bda2ee0dbe782bb2c81dbc907794b8b1ec5063cb0de63636e4340147fe47d54a3e8b2ac9706a21eb644109f43af4dcfcee644a250164444cee0dfd10e30b0e57068793374aa8bfeb8fbc57942325d313885953345b87957e3175e2550053b761bf4027756baff61a097f525c825c2aa0f315a9cb717cbc45299b16ab072f0bf592a6f6a086d20c3be733946f066004cc255b4f09df1d134093e5f9b79ca576c1d88ef572c882ad75087f4a52c88169d60328e467bfc683e9768a6bd60b08fa0b5251572ee20078db3459195c9d23e341e719481b4f088f746a38e9bdab7f30efa2bd5a1eb0002dd64a7fa66953f550e2255b9438a6d14326f168c125516ab3fea54d0392b8ce400c394efbb762d23c1998bcde438b64db74238a936af298e2b968abd49e7a19cdd1d381102b6141fcafb89cc9758fb391b0a7b0c2f88f14db7446e588ba82380b06efa815e611cc560538fd02cd31fb8e6dd945c2b2a0457edd687d01520152c1e0bd69ee0110660387f3b5d65a918c8a20214d7ca4532967778acadd868bf821ced87ffa388e331b99e4b61131883cb4e5da32f282208a6805bde77b3a640842222cb4d1a550f236547425d9d8952254da8d7398d36848a95db8b718963bcfea828c9c5d1db510fbc96e039ed35bbceb2e72b83fba0c104abc4338670f2625bdad36e6d00034e9a9c3dd83e013d4116964fd88de11dc3aeb99d34c57919eb4af8862f1b28d092d4e051f990685efae6eb21d2c7b8f593481d34a14cefff7ee30bd97adadaa34779f02408d832ce1c12a5561508db448b7cb722b0a427c76e452d193bdc65cf46634fea5ef04281c6c1b40b8b5ad2ee60424f5e9572d9f1e778d03b47337c6caefe7235c53956cebf8ce61b0392f395923a8c8ae929bf23d4548b18048edb4a95885c651b257f2eccfc526c40e8c2eeb6688990b4b82859791bb2e50fcdaa3b3ee582206dcfa0cc86adffc5bc18684b2e39e0f820436594f689f809477643fa8c73e61452af9810a239537f3942f1acf9d8112fc06ef7afd6a67e655da00aab77c1df64495c1baf3d3df4c82a5d83107906e44e211e5c00b9cfc51c295d6dcc6f42a94f88b317c76d83eb947b921af0368174010ba4a85150d601617b3ae11d78f66f33f774c8e1eabf2bea4a39e4f905ae933af7ec9494f918422e5f3c39ff6f7c1df8d96ac842cda75a7376d4baec93ccf971391b9ccf6351f7ec6ee6c7eadb31b1c72f84fe82bcd1c5e8d435d34ebc0f1e445d305b52c15d08f694cc7e9769e64e74dfe58af189f4d2c6d204acc24623fe2b15bfe18aaeec2be10bb61424594ab47df65f56affa0f2ef66b29004ebcbbabd45314e6e152133608b316f0095254a4e2ef9e6352270d7954e5471daa3e461f1c9536bfb8533f9334f336427f06dc617fd73dd07ad411258eb702b48cdf293c5458acb19c8ff9563224bd9df667f9542d009f03c8ebaef51b0111ff303ecfb14446a59b884572a9b905ffb5e3e7213ad4b1cefafabf74c4d65d5535b9db7a3161913308bbe37cb8593c607f55021c562127ce63117c9c0f4fee5e95fce16ecf7be04e6c65b7315728b1f05af83d1d66d29f084c62757634a8bed25d519753a71a2bef622a6a200ecd8fd17d8b3d63448e6bef9a23e5659b8ae02e903d61e4b23c38256eda5a321432df28cdf5e0ff7a5ac9ea6fcde72e19c842234f027865bf0b7b527bd12a2dc6b1333e4dd52b710805bcbe28ff17031a670c69167bf073da7311b17bd88987cb9ae4de13f55f2ff8ead583cb90fcaf9aa21fa4cde4145882a17f530cc1cb70b70b132f8853e8ae190a244dea4d59b2b9246824d89f1bf4b4c21cc3c2a4dcce6c5a40fb3f985e0cedb78e96f309070c0ddfbd8b4804c0c3133547e46e75dbb9b9f98e235ae4c3133d02cd13bb807bec273ce8989f38cb07527641c08dcaa4712991b1959055926f214fe5237f13c7c1843d1fa140b37b69f741844d659442bb5094a300fc24a512ac9365981106c12dd734acdf129dc45bc1e6c97827a43aa687855c14412f7134233fc7249eac9948e24a092918ce121162db72c1a6cc2a1cddf3235f7df1033129bc721c74ffcc4e61ea5a478418240408d62c8e78a8b5d4544a121a4293b46529ed74a2c15b6fe064eb3e78418b5d6de9ed82434042e694674bc57ce14f0e190fafae8971f67ef843c5125a804993a11620c4fa3227ebbc7bb5419c71a459fc8ecf33328c605b534fffb512e955dca7eeba1cce00e212cd4ee8515d42af83455eb60e62d3b6e1ccf0792f5ee342873fd9ddea8c363e26015df8183e864c2940bce00a88499d59b382365dc2f54b81463aacffe70d8ba4ad1304aecb13134445d341db452e4d95a9c2836190120315b9e7d9f5474492c6e39d27eac79482bf5110c9e12563c1902d3a2d4f9354f29f13d3d478193323eedb7fedb5a2aa486e7e034c43fa6359ca8c31f12f809f92cfebd8b25fcb85ea00245e87929a7156d9698b00f2579b8d8bf61fbbebe9ad1912655681a7ae7c66c20d4e536483370761b43ff8a8fa9ca2c1ded3c9a9e3adfb94bee3371a1b96a85902a9a5a79f59b77d63a894208bcdeff6114b27799fd09c591f970119d9f0d72fc7282dc1ff23642b15d53334d1075b2173262c3c957c8289f290aaa3de529a48fd30041a8b31eb285359c5c80fdeb9ad32987e24811ffb569a831d1691b1b81278422e936ea1d9aed0b942fe5c9a66171b0003fed024f02612c995b5e1fc937baf612a9953f992f3c02d3640850a0ec48c8686c2c54a28aaf03563b1b69b8f4cdd57da7efae69b7104f6266e68fd62b4e9a569e10ec8499457c3edb47b47309f4694ed82e2c9f2338bd70e12615c54b2a6a6ba0a6c9f615b6e42fb2c5dab49dd94e375332f26b9593e89d7d338ca6122ee7cc1c6d90610be3f050e00909678f175e1e9ef315cfcb95036b04ecc032412cfb755e49031e86d9eea1a7bfd270cec2f8a5fe5fc1631423cac81cb92bbd0cde256d4635b9d7baf871cd2e0c238b8c37e0c766b73e0c6be96425556209329d1b460e7c83e15ed8f29c845801fc401149141902c5e521a791e468c9aac630ef946119a66ae665dcbb3a657252e83a86a648094f2983e39f48c1ced9e8cf316d1149f1408a7751bb40ee0ea504eedaf334de28afe520863f509ca6e4f746582ea841e249418ea0da7ba9e6c890a6bf27a2cbd42fab943e24c74a6fc28c57add616505a36092c3abd45f3d6e2ef1c84f46dd850c5a3ef1731bffa3454c5088e6d8125acd8afe03092ea066c4d01a6051685a51cbf68c54ba624c7d3bb8459115b5de5134604da27b8fb3352518afed44a40271843890125977b67a1cfaa44b6a3656ee14af3cda057639b9285e57add5f2acdbe8948cac3cb825d324950ee4379acdcd2dba839c024399c7c614c0e8b3c5f1fd0fe10ba9ddc359444916d1dd0ff3d07fe9abd57742e82cb71f7e447bf5905702e8c10fda4df57b8663105d7db9553e326a4c8a41b6398abc2129c927b48c903e823836fe506113ae384d7c85acee468526db72021e31a42773d3bc538bc679ef038e01dbc590f6272d69ad0ce48109abbd8ff9630443c6f053c2e38849fa97d00da8ddda6b1a462bc0fd151d9de4a6670c8df9699422461f5a0123a64482c943de8b5a8444a1928539be384a0e016fa4585125cd526389779b22fa81a27ea8bc6c97bf205f0e427a87fe2c240638a96ee548b4d044fef2671e6522e8f7392eb1d459939a18ec915e88d83b81032d8fc82ccfde149af7e7ac4e583fc694b711be723393d10127bbba96670d79f0f51645ed40eaa7b248406d75ecbf2c639c6cc8e440cb0056b7e57db9f77f7586f22fe6f7db9ddabef35f6c17e45d1bbd5b06af984c2fa13ea5020efcae358a962777bcc1ccc14cdc8b35ffccc5bf95c693f7256e6295f58718858e7c27b3abf40cc033d5c7db0421790abd875cea216332c549836c2a945897e25345f2ad94283c599e4b545af5eb6c0572973155e44066199e86be13d687a536291e319970ff270d082ed1e6c7d3c76bed9fcffe77d737abc12b6171eae833ccb9ee01f472650662cef23f805affbbf1b05bb1a827d17376fd97c7157c652bfaf05fae3a8fb62b086af120cd4e0861a43a0cb93f1086d0c35f1695db2c3e188623eac501b2b53dc7c88f85687cd3fce9684a9ce3132aa8eb73f6166035fb8ba6fcb3cac980c4d73b9ce38cbbc489b7c169ce6562de591af569a8a3f0fd703b237e7eb88dcd2f5393752ce36c4a17c2de4bdd96bbc9a30efacc08d7bb07582c578f7b3515c8fe20d6498764fa97b69340bc8d53b9ccd83a2b95b8193d644a66363d0abc5c50e68bb970911ae2eb84f1209e540f2a24bc94f0b943a48681600079e12c39ff11347a0b8a48f316a77800597db83e98d45b25db88dd279748e802606e163233596692eaaee4f0f453ec316d3d76a5845a8e52e6b98610b8d53ea7800efd7d9784b316b43fe6f06f6646dbc601827cabf33ce5a51fd7887112e3ca1c7ec50b31b1f00dacd07f6525cf0385e713035446613407ae4b2e7c75f19b0b43547b0cbea41954a457bd58f6277836ff57200598d44ff436b6079000b26648dbd374b87e75d5e5bed791a88e407314671dffe844854131c00fc53437a95278b42a5906677fac21a1cb22bd5a841735f7d06f3e340b8f22af796e9bd9c3476dc08de3dd9a21d3deeb84a85d50b0d1dc4015bcedf5f8a54bb393bc5f715a861f0a7867e6bd81b4749495edef6b250444cee946847ca063eae546e61318f8d68c203a57dbd3891b5ac4dffe797312739ed4156b8eb5b78cbc55e53a41ebf1331a7225d75985c84f7e3b1d1950957d48cb41cbe8ef23fabbbd65f51503791918ff30ed354a24b9feb0b0620b7f9ae54317367a8d6f6d9193f875cbed0d5d3f54cae46daf768a935ad605a932851d6c79f8dd54aea8ff13204f3b3fb9b127efde44e32f33f17a141ab4e6954ddafe5da972a48a4d728665170a01aa0a001de3f048558a94c082c272da593162e870366f9b0921fde5d6180542def5516750b1e6561b6aac17fb5cf873840cfe90f536f0c8fcda855faab8fe1d815ac2f08fb1592cfc1240855b156c444384993f444c6550f0fa1698276f9f40f165576d93739b5464e0d4e1cd71451775b2f45e4ce41cb61a933d6973d3333eff03e827fc93bce8dcf4d48678f08aa6edfd63822c97c001fb2b649aedae37fb3ad729cc923c71d060184ab27fd0afdfb5b216b2d47109f867f92c992937418a9537a0c92bb0121e50e6f215f18c139e3a2ad1dfe72fc97742fc7a9344367877dc8f3dea50b008077f7c0f0dc7daff1a1ccdcd4d9dea3ef573a341cad444f94de523bea798e4ae43fd2eb81354ae3ff7cbb411e6e8c9d5fd5539c0e03a49581d9e06d59ebdedc41e4a2b866ead24371297882f776c86b4c0806ceff1408cc6a186c13769c46075ce92db9041d03c49b2f21fb31545bfbdeb9dc6cab99083e823323fca81ed29dbe59f47537185f93311cef72a6d574c507f797595aa188e687c5c0938d7b11fdee74ae53ca53e2a242c6ad43d6b726baf9bd58274ded79c93baecb976880e8001ab46d04e2b7fcc4b8ac29d59f8a49c9cd0b8a6534dbf645544698acf8c80223d4012ff60a32757c191a47a5019cab71ec65e91807c89662f1da98e5411713e890f1ffd2304dcf22eb4a419d55acd21f26b198bbbb4626cfc006ebfe9488cc7dd3e9f535d323999d27eeccc41590a2815f91b36bba2454847769df8ac7dd66a90a9fa94390995cb9c31f59235f00a6a827af78d7ef6f6c42cc2982180b92887edfb58a3fd9de13b2cd169bafc0c013712fe691f9d1b22e27bb3afcfaf23990eb9dfebf8f284225aa7d70d2bdf1d21c8f3165513c1998589deb2d42f74e47b5f8169b02a3be166bb1012e68b7fed3a81627bbb36ce4268b350971289f69c933d6f7bfa13e9c77f312c0049869c6ed3bff070edb4ade0324b3677fc11503f306072ab916c3f402e4c2ceb022bc43d8d48aa52e8ba91cb4e3355d5178e4adfb92b2b4eae379c442d372290e3b7bb840c3c0c0cd15e9a5a086359e32b9e99e54c611b12c20eaf89dfc6c779e30066a799a7f6053d3523344ef062debeb36b5702fb75883564bf2743988f3ff004f0a08720f7fb43c1b3c7d3d3e27ec12aa83d283c344b1040070d0a9ea91544dfdfe9caeaa3dcb2dde504586e3e7a97a9673cb55f9e75a5ea53c2f8afd48df996279b300c2dfef0133abcac6a679bc2586f018db24ddee96d03b868346d678ae304cc97e76353e5131a77d8c09baea78bf73d4d45cf6aec20ec71b1a168338f7bdb13f247e46f8de063cf70dced137f2d22eb27d189cf0f83c533b328781a6ccd5c92cfc25fbdfe5b065e7c07577da5af4c208e32d237d69068ef0799534134f4f3868baf0de0b7fad6adcb8df79b757b4699b33bf7de545f901034233d0caee4a412267e7a7930b72bc6c32d5506d642c3dc7f44255be8cb9171d8d5396d2400d4bc1dbd09fa8c3d49c10be12b79b8229c7aeda444387d5c76b55ef1c0d34af9a60fa043930f4473bd8e3e87d1c9864d314a537808e0dd9d57b1232e2e3931da3921f96dfae23dcdc92f41c01bf9c5d814e2867c13ca9160a37adef809ee1a793380140c3e36daca1530bd3735d3603cd937c2fe9d57a4503c43d7bd07c7cdd9f5231c3ddefb2f07d1507a901429435dc0eb364fe434dc4d0fcd2137a89f24c1e73b9aa648d7186cd8cdf365432f4f6f2c68438ad11ce17bf01b3f2964835dcdad8cd6230fe706e0c38609ed5b6eb20ab01fe2eac7599997c12d42786be6c4ea51148ccd7971b7918ac77dec874e886df655ed25ba0e7e957fc8efe33f5404b1eba5a280af1ff2603bcd9137c8d3680897634038f6cad3d159c576de3e46d41d4378eaf8879aacfa33c3dfcf02c44a5ed4e9751efadbcf059e332565d53672b831e8bffe503e4b8a4c5f2d1150424594d1af2978755799d31d554621129683a35b88236dfcd950e3ef7ecf2c328c4a9e09e1dd9dc83d77b2f904d6d50bfa67f5a3357c675efd3225a5d2cf2a6bb423ddcfcd8d69d32777a8418e986852ca318af4a1fb328aef62538d803a663279be59c3c1f529721d71b7b582227a94424e0b7300c2e5ec064d4d24ee521c6a1885a5039966f3f4f5ca2ad93cc123a77e2a782cbaa48073987e46032f47a4d0cb8734bb5d66654d498fa312c570b1df8ff989bcad91eee6f620f30d33568bbe33216ed83725139e9f74c57e936a6021e0c88f86c5150975597e2c2590beadea2a315bb53f0c58aa0dc2cb58223d7708b696de037b112a3b3cf9f5b235458957e20ad0e7cad097aab6d7fcebf0fffeaff6e50aa40064d750a2bd0281e630dc678b74d65d99714a0f248651b574c0cfd5b5d7a06d898e83279c7f8318553249610ae50111f6a95775f359a02f54e2f48524c72012867efa273599e338590297001df2df3bddef5075e028df52c65de1f4934e4fd012afaf5bca823aa3521e1262fade05bf61ff26e7a562c623720187a22c577bf68cdb1b8339705138bf57273d14cb03cb121340345f0c0aadb6df4a9d4bbd380a2a9e83db1b6d21e1ab2f990e3df1fddc31190b3af2d480239eb5d0d2d90c2ea807aff3f5a9b699ccae04076e0ce81d6e9127acdc3b41b737a32de8357b7b3881ca36b47c9a6864d16021aed66c7f6f3fa3e6f2d881cc285d8f9044e7a789d3d3a5c48d39cde7198dad3691510251e9a25598ca368e3f0ecdecff39227d8e6cfeb898302099037feb394ce66de2c4a1732e90983d3b048eefa46a43cebab4a36efc2a6dec822ae584a5dc7334e02406872bdd012fdf8bad77ddc49eaccdd62ac1ae786369e7c82cd8b7a634e0bb896dd8007b9bbe721d49e6d11710a510796fce0d19231718c334297082ccbc1823fb6375c9c5d217ea7798f14e0f55327e70fd47ef984cb942ad149a790b8354884042ce462a6435f36842325ed4fd328bf26862b695e36370fc47abe20f31da6c11b97c9b8c0bf95fe2ada2668dc2e93afc401780de6ee0348c6ffbdc726e98040020cd11c18ba1aa3f6209f0a02808e004c6089eda0a4c32c9128970db57913ecaefabd81c34d9ef02d009508f57aca2ac1c443a1c2bbcac278d8c6bf6fe2cbf5c253186855d55fe4feaf7577340ef74c01821a75f0e0926216012d4906d8f9bb2ac5f525c1207f58e74e32cb9ded85840f4779cbff04d25ffb2fbbe9ac034838dfed334a7c91f2b85e172e87b885dc567f4abe55c52840537ccb0d84c501952808c08b7ecf4521cee222400a8b657a3e655efdf2cbb02aaf535bc8174088a08fc39d93aa3b9bdc149e0fed44fd9298768801fdeb8defef4e3fbd35d219798c953b19ff13a81a0efb313699323fdd51bc3aaa5e18fcd702ea49b710362e2d71438ad78929ccf73ac33b09dd2e509783d0677f96164704310c01ad8aeba2db2e978592862ec95159910c96500938a2b297acea0d789a3d7e8088580ab1640ecfe6a28d8790de35466cb224cbd5cee65257dfbb2567e1f63ed7714bd44bf7b7f8276d807ab207d01480f799a25c03e4eab186c83fcd1a7ba3673b4c3494c7e13c8ec54da7a896bc3930de888c0f974d7fb1f692c7727a3c55fd8bd1bbc76d8e84e7a019231483687803a9a4033116c9eeb82c8f2cb912be7b2010eadd488fa1f8266114fefd6702bf18ee66e27627f327e748da76d85a61afeafa4e5c8824f6c409318bb812ce6f28ba01e8747f778fdada02d213cba64393236c915e9c93d7ada8154540bd68b9fbd2dbcbee6120f5b4b98c1d3433b47c395d7de96bac448174aa2ea11fd82bf14f6b1855de95d00b2e7781ffbb00dc3d7a0b039895bef468d2ff0c4b6f52f81bfd6bbce0381bec79d50f0966d6209b140ebf6dd27675498b4eeef25849673e1050c71f0a6f252c838ef83413202081df8aedbd5325f86b3c33724606d4d7d118c98fc1e9908d5fc4cb5379e2195703a545d4c480b5f28b1f0e8f298a39d020323d85c6628e58c070c03d25ab9df48e13faa8987c3235a6e4df22b95872cef715be7c18b2c433cf15555138f0652503ed8d87f39cbba5b941aa7ff05db6f0dd19772d0eb9ba2064f3691ef16de4f3434b141f0059b39f11ca56753dafc0bdbe973c245c65f4e6820fc850b56c9a1ca12886e429316818ce443497bc2fd2bad2578c86c3d6661f4d1b56ff89c319e49260e2c6960804438e1a09aea14d92b696c08aff3ee71feff67d1c79a22aa67b4012560c663be8947dd87ac11fd774535b4f82048a268f86be1d7af13d17503ca363d7427ddad9f1eb71d9625ed8d56dc3f3aaf88de6ea2b57e7d8237ffeb4fcc11b18eef95024b20f59f97cedb482920cd846a0e10f3b03ea4d3f8aaf4461053d961a013d9e4d44ea4149a7ca28ccd32ff6d9c7167fe1103a9f904ad9c8cf3f9fb98e8652994a69fde244b0aca3bc1a451da7e88bdbbe2c362c7a2195ac50b18f4ad77a607b16677b70b4ba7f844965523a0fd5f7764d4ae20ea8fd298170daaf9900ddcafbf92fd7de9d64d8958920205e27ac3f353384f8d28b35d69b5f17d173cd835f53b2cde51470c178a22a913914f7b36a00d76c0e1f7791762b8eee49f4ecf5c11d27c1cad42e40a92a9fd90e1b373f4946d2be74ee371f829675ac5762e78194dc954a5691ef7a3a9dddc3bc99dc67411a6b99a2324158d378312324fd7972a7284f08962638366b73d07d7a4d48512c609930c86e7b3e9aad4eb667dd592502d5ed021683c1d5aad4a3dbe8732f877ba9dfe1abd0158d46d92166c0d435de1a7b8fbf76588fa6e75f5f7e2151e1cb164e48ca14f6f6bd72b1b6b1a23754dc9f2f5653b6daea7820c3e4b232fb1c3438269c430a32d1e868a5b318832a3979661d4865e05dbbfd804ffb11cdd15dc0f07828163922a781fe32ec8cb47c1304128bd17522497bc8f82e2d438f8ceed4bd9637180e9d7c628021f1fc4b8cb9176a978cfe361998d1fc241b527f637a24153fd7c0c75f8bbe708b132352a907a71e8d93179f79bfd7f2a74b3d1f3f061d6443c6577f063328c7419856f70619c5bc10487545d070815830a43fb0566b07c5a483eea8a684bfe57d3d265c68fa750f90b3cfa1e7fed32f8f5034edd4023853f343219fccc4c4e9de7ea2e06535166d83fc5f6c47b3f07aa397499c711695b90a9fd355e479b7df266b2aa1d1103f380ba8a470095e896083dce49b210bbb5b6541fcf684a9a2032fd3d2a7762fc0c6c45b471684a11138afa1588247bf1686bf3ed8d354ceb55a9c316622d25ba4c511859b3aebebdebd2d568dfd15104f611aeb3507b5d602375e9daeb5549f8b14a545066c92f79151862bf964fcff1411fb54154dad084e9a4faf229c9e2d24fc5ba417932fcd53219a22e6ec53b040db7922869691685e1d122c7b4c13b9e691bd3dd906649c8c79b5a24bca6237b60406319bb9b66d7ffacf0096951b304e061f2d070235641c2ab0a275b7023ebf6d06d54ad7661c589fe30a2d2d43858c681f719b8e57614b3b8716309f6214a6a49f1af6a87f8f9773a5663efd26ed551f1aaa7c3681cf7fd2a22411054d9cb3dec61a74244ff9b98fd59d70628a0b0d24b45343b075aeebcc79b967e9586859719aebccd3a3fa8afda4750d5aa0cd0be2594fbf259e9c205c167331ac251af4117285a85512f204c07d6dd9dfe8bc65c8b20f7f3435e2b656730f40119a9e5c2fc30b9573649a92333fb325590b058c762d3e092f6362b4a3ea88dbc165e342dcb120ac26718ace76fa3c1c6bab9c9f876a8b698c9eb0d3989162a96e47b6be847bd1a710878b7d25de8bdd78db7e85c6d05a468ac458a5e63ff1bf8749ce52850f372de448a37100066906a1ed6ec18589ac0027f0af0ae511d430ce08e1600af737a8d4033c83abce74688406e5ac9b27537436924afaa6ade15302b1cc68f9fe58aeae67ac46b31f68a858b4400fe10bf13d6aef68a5d3fea2eb69c529e7c30a5951b151430ffdafc86168d84eb8413662de4a4e2fd7afbda7b55461ddaf8c5968e2e8f5042af2e88b0c5e968b58fdd09971ca74a35637c2f9c34e95710a9d730f3299636d8cca4ba23aa48de82c59f62f3b670df31170375ff343d35236e8b74ef371ec5f7b80039a153fbed0df285b670b0653d9e93a3e1b269c69f8d628e4aed17a3c4adf28a06828103519bed56f1bb631e6a5ffc9842c49fdb302deb8bfa3ba3f85be658c2eb7f5280b0f130457446bda28a4f901f987df808eaced2cc3e901831be23d6c645881b3c8200bff7456d70954c2188ff4e8732b684620fb0af7bdcd6364d9da045818558f5ad758a84f738bef5cb00e3df87f490cf5d227f2ddc74c5b6885f1f1d49271399c98bd2b0465c830085f698fde7948c2ecae3b7f298ce354dd3b1e2afcfc8b9c41459641a120d8a823341b4356eab521e110df2043c03912492889615215d207b5ba3e77e72d116dc9729a1a68f4c74bee0a09c04b5eeadeda6e190995f95d1c985242d3d8392cd4d02e1708551d2aeb0342187fb1ae3fd3a7714538860143eebbe3c627fdfe2752e654850b5d2b0a3669c9b0640a50d872209f6b87bbcbcc36dbcc6d198735895ff0b9d9df435ddb51b718fb1392db59a1b87d8b29b875d4f5b5b7af99ecd55da4d354297874a10c64b8518d3f618920466a5f5a360c85e64e34100fb33d589400444a84e439d21862f531eb0ca4e1de4a09859689fd6f550fc3b8f900702e26705a2c80588a7b42f4ac277602567d57cdc1bc3f5eb2e0ecbeeec1caafe3394a3dd8523d0217d3bcb66c760c4188fb5d820dab873fd969907fbd1bc8e10bd96506d82ed7cad1d0dc93a678b930f826a4879b5a6f74d8300a40fd1981da2595f30200cca79a4506465a54373df0e7a4c9b0a4f0dd48778e90636cbcaba1203fb72d1b66183156361dfdaeea684fbcca107c804669d6f5e8c5a5db6c97e5f1b39a89c542a95c125ec3d95e4f147e7ab900c1893aebc28368f792b19499e191e269ecc48905a9b21ca1a633940781130d820e695472937c2dd911aa36e46307f1de08de719f19bf664958c44064849f8576df8c4461cdb79cff39dffafa23ea7eb15eec375cd3fe2422da73799e0499de08885e061cafb0ca0d38c2bc6ed26e0563eaec456f69720cc8b9c2882fc637f025859d7a78590e1f1f5dc6374e6e3260502a3c8576e86f3c97a162bc62dde8bda1d3ea0e1c21f40559204edef00f458ed3a3f906aa434233bf07d589e97f2aed981935a7c467bdc3a0aec5f87d3d048dc0786d46f12843df04c0b52ed68f7c3c8b122f3b6f26549b03d07297c3b962078ea031637876b7ffe1184b53134244f18f137414bac450db982efab218cac48367b0347af85a50b1402d5416987c60b9d185677b8fa4e34468c26edb6b86b08b321d05f51f39c1181aa35b3923e51785518ee9e44b45b83d1f4292d21bdb51b81d5d8138a6b70fdc4c2ff8e15d655bcb857b967f68f8e517b3a95eb1e51b76a14cf6811d3fe2d78cefaa259a4c0e1ce192d56b5d0e3f00a4861e4c7f3a67f46b5b8a98e428520cbe5cd622ce30564d761284a24fd6fbb894c26d79db2ce97816264df0e50aa2872e7904180ca3d5bd2845d735f4010dff58717f5e8f2fef6dc89a0540cda1bc75ee43d511eb815e63d436cff6766f4fb9a38bc0a31d70c13157ebaae83b881d88f4616863bae00e72e67f75151fb922174a757ced101d8d92133f5354dd4fc734208cb82669112864af510b410c284c5f3766c9d0581190770950e904a453a8a3edf2b978d6fbd6ed6fff2a2c4668d1656aa342f3cbf6c19b68a291642e3400b7439e6ef1c19fcea87a01c4bd292ad25460149d35116ada194b100c4817b18d85299b3be893049408865b269b54069a1f72e0c80837f02004e37e5c781b32946b6590a4f3ad04e3180a7dfa04373603080db4f0d4712bee5177dc401bf1746fd86fb6c821c01000c050a09e718000121832802345a37d606db5f2aaa90aaf6c81d953f4a51d3aa26c82db3e9c664d6c1492745bf9f941c1f63551d2834fb8285ee51837f681a505df8def47e510a47b6021ef83f046c79bc18a7e8be81ad6015a2a23fb7fe81c905f2844332560ec5fe46a9ddf59ca1ff8d2403f3194c0100b690138adc63e479fe39c4168ac796137040478edcc53db1c979bece0fba78d62ea78e73f791dab69607035078c568c20737231c721fcffde8cb3a06e082281eb2f5535376579efd333aba823cb8749ab5439b39439ea99861e6476ed990c643659ace569c5f557ab224bfce28f0c51ab68cdda74523664fc448f05b43a3d5d56460d69316e7415c012cf76f32ed0d1518547402ac6ad00a7d21a9305a1450f6fff423af9264842fe860a8c1161380001b3c3c220c64fa0200aac76b36db5fbb85da81f5e41eee0e917910d757e4eee2cc6575a8dbbbf14f663f3b7cd06a4736005ace30cf8154463d34c64427628fbe47616b9a1ae9cd9079bfb13bb6176f997b918aef843cf02d91b0943536127e4645aac44d9bb77ce517d3b6791849ff03e8ad8a3258b4472a7029e83780aa911410d69d3d8ba9c0e2aa09e739b76fb06c0a9e492f54232a9d730ca019abf60d137739268415732c674d69172a612759bffb3d178cb06f95fc1884bddb8ac423d6232fbd9639f7c3308aa5353549ec2f9ae8ccb9f33c5e78800547938c35096834b685ecef8b95161a8e3fc4a7d54cb5705a5af0ec9df6f47cfb3333a69e8a4d1c6f6c38ec7dd93a5cd201fee9d3bb16cf78c52f8f4042f6bb64a926592b9992492be5806af107fed65e83a54946fe0648a12bcd323a63b39c014e6c2ac8ec21d01ec309d1e649385c3fa3eb0c12ae2d0625d0903981b5644210439e24f91f3eb556391cfd5f50d87804784354356733b260482f06a9e938ce31ba070593e66496906c591774395b88070c1d99e12996134c0b8180fb4f0072c0888efe18a9f6626063c70e67ef21c9becfcf7c168f863458eb3f55a4691aa514323a087528b6c1f493646b43f55915bbef678a0ddcb40a0987b4debcbcbfa773b4551a7c409bd256018bc00ffa363d8f41ee2522aa55002a5e6c5347189348e2644ca6d39cd76cc3e14df996cac9a5530ea7ce4cbdbd4049d5f066277a1d0b63b11d3a6145f3f7932621981d8e4fb46f29ebf3934c358bb4a7156dc250de3df7f5a3bc1b0e9ea8dff526db12f9ed1df40656b57451be39ff9eab0ed17e29635e7927485734be86993bb618d354315d42aecb45f04926317c92886746ccfeb8eafec4099988cb3ad3eea7cd3675462d2e5c5f5dd0be76f2e7eac7e9fc4646cc7f97d59529c883fdd1e5d3e74358c6b06c6517a84024df893940b3dca4c28f3044b69617fec9cdc2623a5f7d46ceb2876c79ea00af268b75caaebc581096b9181f095894abd794d4db827f0004300ca994df7147f8fb4acae257cbe1385e3beaa4062d1d269cbb75350aeb26fac36545b90f30b118fe469c8b07b9d86889bc142a0fc4998f4138f5d5b3a5c869adea00c998319ab2a41c67a56cc815a01acc199dfd4872ab4e102bc2225d29d8ad2ca29ba423c86d959f6ccd417274e89c0e45a40ebea02c7c0ac184eb0d36127c4ff744975f105b8daa2d137bac522e0cccdd45b6ae8a398d1230b875827ff8901a5b200f581f4576c7468379403f35dff3e5c5743d5e694b218b7252fe67ae3e926d48701748e1c64137fd3774042933b272854949949d6da7e48f0a1399efa0accd2efbec808f8f32e77fea27bbe295879d35c0905c6b2d61fdbb67751572a9b0fed38538cf17031bd979c16e3eeb1e1a67346c32f7bfa7723cd84cca3681e7aa8a1337a2716776394032a850c4d12bb8d30db89fc5839dcc6e0fb48c2c2d8b45103ab7fd6709caf924886e14be607404e5d3c47f1f6abb74a07d47dcdaff85cd179d038fb107e8046eddc4fc6d0087d83fc9d91f283fb41139d2eb95ba08d7ae7bd5f0e40413e13e1aa1e3cf627b9f856290aa09971683db59b98e9d6162c5fb10b5ca9f0091431d82ecc479d6984118596f637bc9ed8adc6aa92dfc3a35b6dd098c1fab3d55d411a121aaee6cffbacaa58e3fac01c62f8fe15f6b7e43a932eca6ca1bbf2dc10450d5ffb161b09d213b45f906dc33ce2f9f7203eb5ae8fe1522d01bd0f8e4237cefae5199c13c60f4d64173511f6d0fafebe9409fc5093d90431b1055998704135c46f8ecf89e70bd26d74fd2342a3d247da9bc736d5d23b6bac2686dac8ccc708d997b354fb1a443071eb023a4eb4bf9f353fb53569c9ac36bec37d0c22eedf21d650da69e08ce0633da9f65a9e6b644c924eb04aa147fba4e7925cc588275993421c56657e9aa91b2585e627f22b4d0882f62498752da62ca6c39ce61833de6077c20357e7f42d32b9fcf6f4b99c902ed45e76c0f3883f3fbfcee4a77de44a6c27bcd0cb9334d9906b83c485ba4754a9783f68f48109397abde4b9eb5b5b311376e207f1e0a5997455ed5c5f239a3df3826c343151bac3cd3cb8d82a225f792da7cc2b1321cbbcfd4d4fa0c03f9aa751597c08ae392326078ed9a29ac16f2d1cacbbbf255cbcc759798cc156923e6d48a0a835b2a4afb7c6a5e6b9f16dc506dcfec5be4832972ffab3f44be86e799c5337a7d75c27d411269c051843989ee6d532f742283fd77d3a8f4635766a5f33937af9092496bd1ac04a873fcfb19dc512e27ece8008a0e015dcfb3e1db92fbedc9aace90c925cb816060ba9d679d82a72e880a2533214f0ed78343eb5113096f9e385f7a82d29786871cf8b9b4dda5a6e6c2c443da95cfbe5b09dad2acf4c2daeffa28c1957dbe1363b4dbd32415e4037109d3ff2b55c0f59996b6089866ffeb2c83bbec4e66a6ed43f74d8e353c1ac4d2848c91cca62960c888641aa52dd3788c619d2d7c271df91bf4595731fa0c6e469c082b2024e7068e39786e440d3c26d6082621242330fe0550e7b66ea258a3d5748fb81f33a570be44e1b23e91b57fc57d701f0f3e9ad07eb4898136e3ce20999a51a666fe280464c2f5bb847c59bb9f3d088cfd0acd6354102878d9bf457f89624da5f89efd29565f8e35ef9e3cf2b8e8a33a949c6f9656578e711195d6efa817cc740b7a11e2c32e009223702b48dc82bddf3b6de66bc276daf16b05c217c2fd1e8db6ea31926d3d35261b412ca3563ee1bbd1d8f474ebd8752a6c7b82010455bccf7e2d1d0af56c64887d9d4ac415c943b135e242ca5404a35a66123eca404cb4a71044dbb17241a3fbedb8dfb8b912cf6ea3d69b27b87e28b73254134b25687f0ae8c952ebc3458a1a77b4fb821945ce13ae497b9a50f72a65e0cf56f5041c3d9c8a9dbff8d0fcfce0d3d38e81a5d98da14832c6100eb1db9fe7fadf7b8cca83b33294c88412fec4d1db89fee80d37a5439554761e364b5a48d231c8b1a602b57bff638f6d31887804bf9eb99cbd3d50356884ad46a006a4f03b9af135d9655c3e807ff69dd31ff605c1a9a2cf60aac9fb4e048831078c2500477b668b1762477a49519fe85297f4e760a7440daf5e6de175ed8e61bc54715e5371207fa4fe6be88928ad0cc7734a871d27dc8adfbbffb08b3c508c44f6e2867a18db2a0cebbc377388638a6f01686dfeaca251308c03e9df9f30a4c474568bff63371f6119f3a4e6670c0b366cb728d173fa17d7b8ca2cc3e8a9466c469bc1162d5a8fdea82e03eef1e10551723ad160b45667fc111e63e94744f0b796028326578f4295d38517087a40ae4782541cd3a71cf47c610277b57d8e35b23b7e2ae15ed9ea0ccf38a42d92a5349d6831a72f282c7caaaae604276518308bd7abb8c5f6503b862f2e6eae1958df4e108a4ef2f31b29fea7c6e68ea4f0069857c70b0ba6d2bf019ebf065d36f85789d7f7b0d1a09c38eb2941f35a66268160c229cf37e09e5fab13ca0414b143a8cf8e06910fa4bda86ede9fba950fab8a1a7f980e7ae7aafa2b30d4c513c824a817823b217bf5aef1748e848bd65f52a3cffc53f56835fd810dd6afdf99cb3061c4177b9cb366757049367c8b62e4650cb49dd5f3d03bcf56b835ffa646fff665975333edd9da177b79ed06b7fa4d213880e9bfdcb0388b073add75775f1891abf25ec1a3387affe71b96bd9cc3e0b4c185dbd6fbcd4445f92163eab8f8d4c0f5575d30074b25830c3b3c596ae831295db3d02727747dd4b589f07508153b2adb59120dd1fc23147a82c8d3028f7171729405cff03573167bc8f6b7e1e4c95eff5a66e7c4d1ac1fefa24b70d42ecb7a223f6a024953102e252259f991876e6c882d8c9fa464748785bda1d2259bb6106253f9e49f5052b36ce691cb5420cbdb68aa9f95923a5d92c74863b90617f3e498f21b66b8a02f94011fb0be587a65d75a6395c0b7317a3bcd0696dda1fae2c9d348c0bbcd33ea997d0e3a2f2812ba17f508f943e70323212a7e7e5ed0425b3b0f4bfe43fc2f4fb72f5f9e8c034fdd72e59f9260525f6f6bd36ede7fa72dad99f06fff8fde344644d702e3c9cabf07583636f39336dc1aa5edff3bfe1bcd4895c72b877488bc908bd0e2566c4874a17b22adc6380f813145787cb6ad7338c1f36dc68c096fb7aaf393d25f7c00dd2e47cd23611e14bdc27ee0449d54166d972dbe8c20561e1e3e869d442a0a0ca6e882ea851b1f70f4455273a3ef18b7a34350992b3a7f467c8a20936c99399a0ba62fd34e06af4221847331d468b06f5e511ee536d5f44865962cf757a1b8d070ca0f9230a6724092f2f987a54210ca27823d791914f3a04f81028020024ce5bea36ffe7b117b049705f22afd2e23bd625418103b79858240c6a94001e695117e9156173c157cebab293f3a3c547eace7d1c38f72b361f8d1a861c94faba9320c0c948071afa9a474f8595070479863bff80404a772430febb5685d09f52c3a40ba7bd6d3f9203e7daf49a54e0b9be1b9e1634ac5de55760a78026b38addd10ff60bb8aeed125cb1c080b520a0bd2ce62647875f62f658bb4916f8e09c2c3c889766aa39de62b2f15c0156e87d67a012b5570370842fc5b401271cf40f0fb756904e44be9043e6ee1e43f14ed313dcdd7e327e05eabac5f644090d225ef564a5a290fab16194c3b10f22cba5b6f59c39d6aa9b230614a05e17dc53082c5a3982abdca1e75d3c488962043ba2ae94eb67332edcfb11aa5baefee8ad655aa87297ebdf01af6bd41e184beedf7194bef3534fc0f338d8b6b7010fe80c3c43156d240b875f346bdd48c6a22875cbc3eb8e712bce105fbfba17aa78464c3ec7febea9511103bf49c9c37fcefba8efd6350e5de02bcdf1ccdedd1387c8085ae67c0e27392cd5747dde939745775037d1319b52b5be8841e5e7dbd4852c4930ceab4cb99477afdfbc83beb51ca25ebb76e8b109d08232fc87804b121c543c830322293e6b092c084fa13e92f0ca2ac86dfd58f3e4efe75b83ed323f07ade320913d61638e5c65562fc4891c1cc30eb32b7bc6cbf093d44681919b5d6058746a1a9bca2013040c7b4866946205537179d5d8aa565fc4ccbcc6f7f3df96a09057b963fe47dab0390991bec0a0c2ccff95e18f9ce4b0a694df879ee953da0e92ae5ba63d81479bddb9f7cc19746c037a1d17ab5003914bcc67c66384a39405d3ed3b57b2cc86914fa949c2874f572c447ce05564660ecf8166875d7fe62f0687f4b94bc12f89890a8ca7b81b6307a6c564d2a5a7746bb2eb3f080ad3fe1114c112e3066e9272d12c14cf1b771564c36bc343c270ea4b373c6b79a4a966eb0b5de52d1fc3d186c46ddfe539bfd5fe83b13ee88696a40f1c3903f8b4b03d14e15c4092c92e203b987a8a5ebe85c3a82bd1c994108d11ae99c1c0cae701977ffa11545a67cd9809fa7cd0f5b7792c385873f8c55081ab252fa752b8500b840d7f410d287b9098f8659e34e4ddfcf470999e24ec97fe29a1cc4d3ed55897464866e9c775e08b99fc7108e703c042a251dba4dd56d2fc49ee6b1fd954d4ca66cbaaa6591c738c81f5c52017be6fb7266811d2b90cada3ae135ac4332f4731af6040d6ede06c31f2c2db07a9dd3a1a2fed9219dd52b920a1dc076fda4839cf6b0d5778fb2907a06edc35bcacd22a6c0ab90c7f9f96f8b64c94b278c344239f2500e6b324ef09ae4ab2a77db90ea2205e53d299c777c70d5c3a2d68f8a6e8c58ca1985657766106d3218e9836c0e43f67ceef5383afb8733a61871b2679599a637131648b1d3ff9565f016452efad7987cd568517bafd200073a10e2d3d156313f791ff4769932bfefd6170426b1a12f8117cb9cf537c58ac2a6b1d52b16920954bb30e5e5f99dd51046761fe069faa5a22d05b0aad4e3b1bc4b04c69c747671342125a57a25a43924801f96abf29aea0cee027812d0144a04236e888ac29f207f85e3fb5dff6e172eceb7d94d53ab0659bba376ca245ba4b02e92871b23535ae1346a039c898f637272a87a9459117457bdc5aeff87a4d57e16139655073cebfbd4fb71182cc9acb39ed04a264ffbf3685c5ca43d3ecad9019238856cc73d893c5ed2bef0f73c55666238ebfd9f4911c7046d9a7e7961c463141c47cec2437151197baf9687c158fa6d126511c0161f977e12bd442dd4da8a6ae79617d16c415b825bdc56a0c21928916b107005a47b73af3416a407be74a06983ff8ea9fc1415aaff4cb2922af012e7e8b390de4e323d1e794881c5144c73a949e2098469fbd75ab4437f62ddaeb0cd231ad72b8d51c4d06289bc8b0dac1ca02e4e6b3e77aab896c8191ed7f33e20f558ed49ce45f27b58a1aa4df6114ea5440d85880ad0325d5d836dcdd509e35a36b3e8f0d3092082e8173142896bc7c8a69678f4dff1641d3942e76642bf7ba4a81756e3953773517b7c65b2e1c7001a055e39f962b78bba3e1fb3707d95da73cc8b09851822564660ecf87caf5cac0d9dd968384d8be396f0637525957b47bba587367f15e1f92283a3e8b61bc1984565d760886f440e918587b06b395f42031afc20fdf52d381caa058f105fc710536d2c9b3d7ab73ff5b6e06df6b87d943323b6df7370dd5de6ec027541d8275c6e5bdfd145e494b67afd5945df6d51beee5e3c082f632e7cec1f8720a91dbca148c0049f93806c035a2c24b82619253942ffadf38054ea82a65d779b328f67524206803be1f7c3ea0a9aac08b4cce8a3d8f6fba87cfc056a4950abbc97127b2fb6cd1c2da1254b9c982f3e58356c966854241672730706b316b91763960d80011d5f697e47085da11e50fbe5da3faefeda203fe1dc7b8a79199a11992041c2c703308b82f3bcb61bf8deed4073dedca6ed124bf7d674a7c50e72ad72a0d534910f7604f41f23268e8356d013abe9396358db2c72783d51a82de512b30796fdb157939a016813b8b3252c163f55302c00bd57d4b2f4c2f6ac9639101baa3af0498974b396a30a08c036c7d8101d20627e7a10dece6f94ca0d4532581e064dd0e47bbb26cfde84eb08bf19c243ee0b42e1af4fe2ea8e94a6ca10835b053caf21df6f584595b540116430d0ec88e64909f298dc01ad38be5c6a3703903527afd01502459fadd4bc13f379a1cc3997f9bd2d42a2b6722058580ef781a2ec577c9e13fab0c3723ffc4e6d6d5d4066da01dd7025aa253643674756eb04939809692a4910f63ebd2c2d472f17f7009562e7f5c1fcfd095167ea4decb5aa75ae273ab2cee0121dea23753687600a778207c000fec2db5c2dbf873f791084095ecd3068bbbfa011106b70818afa71d9e48e582700285ca3c735a102f925e93ce8ff03d1f1675e247015cb716fa8aa7e19febaf170290782c172665cd498b81265459c9e3fd5e17afa803231436e929f8f5515624fc6acb3c440cdff13e69175b671ebd8a8c09caaa06f143a4b663ff8c4fcf4306d83f507c11c331fc0198c52a94d1377720d656c1dbb9a3fa4d819247dce64da40b368e34425337f97fd504a93c4c22508ce85a3603a312ecb5d70d0912a87b14ca767ff27e1add0aad167f1c1e4bfde585edec68046944b77377ae272527dbb95f4a115973eaf674fc3242d2f29d9e18f594db8961e9356654fe91ec871b352be18d068b4608261f296328a794b76f3eb2d6e19836dcc8cf67c70860245f632df128359b0f8128646a3f2630adf9827f087f338cbb58b604a892034833a3eddd8f9324050d374fc5420c20f24ec32a9d4ff4657ce09dce9bff2549b03fa351db1efdd525e9a1ba3e1847f42aceb77cf353cde6bb23ec6b307e226c6d7b188d70e2f309f19e822b22b41944fa4cf8a46c04aae4e24ba14e5a6ac134bfd8fc560ab54a54d6ca3c262918949c354a27d678b42400cf61ecd59816c5a16940c32773ea3e1a74c74751685abe90018208f960011e0548aaa7d1ba08a075a53f90a98c0210afcc7a165e980f7f4ab0140448a3ad2fdcfa690f117dfcf5601993eaecb83627d7788d1d9d4423cbca4fa4331891174590e056d38768984d48ed51d84786b4edd70c05bf3b3ffc2cc206931b47822a0592555c77b7094c7e1c7272567c1a08527dd2092370201ae50f28008d72dfd8b85a8956759d0a4c07b28bbf6de3028fcd0e8c1460da060b26c92db043bbc3231826ba33d032b466f4c39fe31cdeda3a62f54419c2fc72d48a3a45a34ef8ec8bb37b0de6b0d6be9a4726422784e0a7dfba6eab04e134cdd683c686dfd33c88b0c32ef2b2b7bf6ba5af0c3efbefce5571b411d526e0f6fddc0a3b871cf3d759dcec234de25148fc490f296309dcda5847980a7fcd7b02730f620e520370f9f188ff1882d5ab8ba8b0a75a091a61af7d8e5c1a66fa1926fc40cbcd13c01b9f68c51bd6ec8b36533f2098318932c06b599d82157daad3508bbb382d537ad0e21c74b66ee0e4358d3f15c47157ebab7a015b9f1bcefc9730dd5ed1f655b688adab070b6787948962bb20f8702889d0f7bf2638655344d259816c94bd3035c92a9cdbd602a05277e52df5daa925643e41f52c7a66f142761cecd63af75ebbb7a197a702d028859422010075c9af6ef9e81f58deeaf1a9a275793b19ed7de65442e53873e330cd0d5c5a1281f0302b62c0b2d6f969d927343d42e3a756bfdeaa8e1efaf16d4ff11c7f03769dd07e1989e91b7f71755347e253737bc85f1345d42200d83b3fd40eec348da9326aadcb821b3e792d1a149c6cdbc4e105fb027f10950f86f5ecbfad9691d566c1995c5034933d047481be4766079e613569f32972adb096e596149ad0ff94054c252d7cc8b19735a725c12575205ed49e473d2f6e5857f2a5522ffcb19c3f53815d0877ae09bad26fbc21a0183a50928a9c3e7f243ac0e285ff390ff91cc0541c48069a8386f550c520226774a5a641fde799649289e1ebac82dbf1ad9dd5e01efa92722392e2d16b19e26c397f899744d166114cea6205fcd78f18e72fd471951b4fcf8cfcf1484657ae2fa206ae4caf66fea6b7678e580d5ade484f9d960a525cadddb0051cba5aa4d01b382a1c29384be3844c2c290982ec8bda3108dc4172d6b8e7801197ea16e2ad5a60ecdafa7fcaac34d6268fe2d4cc40d293be528f0127e680285858ef084d2c8f6ee664ed4bd36e3475b1add44fc2b5ff9daa83fadcb4db75c5dd8ed71d9cea4ae0ab9560f06c922b0524bba525a69ec670d8dbd58cc01e225cfedb715c9a828a307c5a111cd313d3a409510a3326ab9411e887f6f3a0191a3dec9c60a2d2b0837649c933537b8c4f80e1db96d1088ee5250d68d51613ed02018e8f227581afe93f4d296bd5edd670be647c5d91f83f100e33ac3df722b091a525884b267676fcbb714e6471a2ae3cb8d123ba18007b5b4f9cd080872fd04f3d92c599a4b0f679388cb0a6bde9872262c3f12b3a3d37f7369d428af7cd7d75b4f4e4b6e870f80d423c9daa46bdd98381b4528bea86297c791e921f544aabe5118ab17b46005d3ff2322cae1073dd1b5aec80808094b4064f7df518ebaf17a93e4c3d2356032bc50f027e54c8ab3753967fe6506ec525dc627d6bd818fb5def80a6f6608c367d2089ad47525fd2341e8d4f99db38e5660b32761483a16e37bfba5a6fc8decf8d9affb08728490e4d152918e63a967139bc9bdfbc0b260c6b00280436e28fe6a7ac7f12fc16c73675fb3900cf3d59d97ebdaba6ca2f2822cc22d343252f75598b44f17e5371700996caf4a85f66dc30bdd1dcea14cc4a30921b8335b487275284561be2651836628e2fc7b3e19da97f58d82255c925a637ce2c6737b546ac9c1f7ab3c5ff5ef9ac4aa6ce4539214d61ea80c4f7634d6a048bb0798488a292579f4bc068ab0a865ad476c2440d31f853febf8326225fb48d0025483d595e05301712e9902a86c0daad147ed0da3267fe87d3d5f6afffdd7bf8f52470b96c81065bc0f82ecaf55b44e14049581aed7a3a7ae1915221da571e21e944fd917da6cdeb669c655f45289c09021903dbe240e563836ccba8a6862bd93e090eb77c79c960c7fd21afcd60ce4f82e1c11492e5ef9dadae2f198220cf24a2541acf0cb1c4d5770ee6643b4e622b9cd38cd0add5a13c8aa82b40a6467a0fdeba33018f22973eabb607f853c5d4d6603f492fcb6203543d561cee94b08bb42e11f66d7ab7ebf7523670858a3413b4812d02becc6e3d6c44f97b649834197a116a10f9c9c6439de1f51ec66032cf866d84875ed6ea18ac5dba1aeadf8581604b37223bb5ae4a171252b00aa5260aa7f0d07d115b27cb3842787955b2600cf01175f7ed9e0c35c294a6c609cff85cfadb5a63b7a000cf8daa926fabc2fdbc138d7f199e5f84707e740e7009d28555fd11d142a69cec30428a5c609b8ad92e4fae33f41d558b70af18b986952e1cf75c8837f499441c55fed04ef6f1d44a6a5ae59d405e4fa57b741b65c620cf2e6270c67ae1c774d06aa6269420aeb77ebd70ab2d47bb52165e6ed6afd33713c2bd9b4d61106efb45dfa737f1422a39484734b467e061a73457f7ac00d04a2e36b6de0c912b5f76c2e3e25f950551e87f1bc5900f8d8efa05c43641a285da60a05422f2c44983b233cfba798e95ab037351b1cad51e0192b9c781d39ae4051bd3b27ce69e230fdd4ffc5f9adb9509602ecfb0f0afb5fe3f86d4341871fdd00df888564da1ba50f9ef446eb1f8b7fedb154ff6cf736074634ea44f734f768b03be20aeb834a2060cbf4abd197eb5317068a85c7c8a6b00261b4457d30bec24c151e90e9e0d32bd70595429b9cee97c189980ef795eaa041eec1279720321433100ce06576fdfcca7111fa218235515a5765b1ace8c10fd333d476ef27c718fdfc6b0cf03b4e8c2ee725bf9c88ac348b1489e7764ff27381a1e27b256a7dd79a4e928aee44998bfd980c1e7f7b3404a5da971064d52e8c8dcfc60fd19e5023ab6c8fc9604799e6ce99b83a320838e3dbff0186ae1416f334e678cbff1efb9f5fe527a25f1399ff1492ee73f843b72eea4700b5279e9008d35f649ae644e4813d1e70a7a7b762d9e3c8c65ab734be57801c3e4219a18ea1cecf216ece593064346a59e535f4f5feca64ac53081afd71e23212039987fd4d919d3f38cbfe459912d2a438b7e743468b8cf9c500c68b2699c317ca94fd092922fb90a2d603e3ec1e5d5bc6e8b240884fd393ec3eb8d45e9bb3eb09ee6e9f5b43913710f8663973901e9127d999b45ffbba58c1e16de4801d506af8270ac5768b32bf7285a4d000ebc99c2cd74f759f550ebf634718df6304bba83ded3aa1d4675520421d67e89e6a08e066ff48db1c6403f0b9ffcfeddb4313120c7bdcea3e3a0c5ce20ea93d659ed433ee9fa36c691c141e7a52b6b740941c06d235872574eaf62f560fcc90b4c58bc1b66bb6cd77c5b04a26fa1b8a63df721b074b4714440044a37d9c36286dba0a454c97025d53cde49b00f3df907b40b7268662522403e63ecf0097225ed9226a865c2ba14c86cc80c2da52b0133943bb0a98303b12a41e71459cd7036d3fc25661c47650c5f9fe887634d86b85915e26ea627c729195523c8a0d040341efd28f28ce3174175143c8de70ccbf6bcc2dbbab918e048cd3d72d56e8e9d5003b629df8e9678194ba5df1dd8e524fb569e145fadb345a51b54b9f151a8d5a927b353e3ce6699b76cef8f2414bfed30224ee291d175425848f06ca44d0f3c7abc732befc71dd2d87f7f6c1e81586df66b2d965b45862d3cb3210b43501d615d6cd264ceec123fbd84096c0f68930c810683d2136faebf9d36d7a01cc64ca4dcb7285794eaaebec57ecf2538db311e217190a2b014aa2dcdb47d2726cba359fda39298469fab2e93077616a115cfc8ede33f8f64686ec6ba7cdcde39ec14a0108ccc89723bf2c417be27e0253061da7821839c790a92f97954308537671af9ef3371c056efeec325ab2bc4095d0f7847c5616a8a9462ef7f7f67168304e314dc94277bf8bc44725bb99478eb9fb799e67de28a03d588af94599681c6c3db96558f185c4264e2b915b10b0f2d48295b1a3d5765476b58eaf666891769a80ca58739d4ef873583ea017466cae6b4e4833b1366f6c63dcdc89d5bc82bd1fd25b07324fbab2c023b422d0721006bcf08cd19a987e96ffe2fe68f7c829d115d6207b9f2cde387572df30e22f39e34deca569848a3c41de09184620c4494abc074040ca6b789eece7074eed140aca5a548c484d868b6da64b07c15ff657c882f916027014ceade441ea77886ea365c454514ddf897e093fb34f3ebbd251efb4810f7e5a47925534aa086ba128badd35902e49190af98abcdc1b61ee3d7e2e93865f3f370ec39482bf4e9cbb929a13d58b74cad9ecb101f2c9ca493d2c3932912f4a417ff5d4f779b0b1e6b2aecd53efd5f445bf3423f0b2c629a378c2d67ad2cdd729ee84853ee372cf2b6f1eb04645389acbf179363affe6b7ee3369e5e62e4e5b5f465d55e61e19c91b77715413a50cfb4ae66e525f026f39855f8044120ef55f7b0c206ab25a81dfd091cae91e0aeef531be93527fa45a2cbf422053b3765684379a23c031c48eeb473972149b2a53cf1f4202f4c6d49e78bfa9de52cf5578de04d0f72762a71de1b74270ce168833d56e17fefa16428955324011b4d7de2f9bb12339b8998ec48c1d74364326c3fb8a2324a7ae7d9f2b3599e2b75c5d4f5aec1c4140e2666065b599760f1efc6a3ce6e8406e0ac128b3a1b12c19874681a5e3085252abebd5bb92dfed0564973e68a6616f66dd7651789d36fff0f1b47f38541c6f86cc721832fce67bc6a734664e4154b6c7b9a68d89aefb6be858d19f5c495e2e4131dc8e4edd50973f5eafc8fe5f71dd0bcb28732d3bc3db297388712c56b65f9b8e9ef47fe292e9d9ca87ae8abab7cc69be293b2c5a248e87424a72dce92c8c6510064763a9c10ed7090a907ac47214a0dcf999c931b794b9f56c0e2de59d1e67e4ab2ddc471366d9e78b0519e50194e4a91f8714b3efe48ed8bf35b19613cf89762bb7b4cf91469b1277754e144a39d59ff76e92e33d7f77be846e44cbaa4a3d69150dac7b595dcaf5fc14801f7a36c77efc6d2819f1ac492b4b252e778ed4624d9d22977fd277addb25261aa65ade1f4a5ca7b64dcfe8475116334aee619d51c9d60d173ddaa32cb53a12063cd5fa36cec9f3fd617e5aee8f6296c9766e95a034c45b29d936f36d64a1504b02b8a3603ab656d8e1255631fdbbccca11224fdeee2452117bcd5bf7b2188dd4d3d0fdef8f0d899ac24ab9d43ea6440c1916846318f9224b34734cbd49fc8159b31a20964e7af96960d79fc3d0010313aeb17ae28574bdcb448bc3ccfa2f8b16fb0edf9a5e76776ecdabd1142cb55bf38836cf23636ecf7671073a65505cb87cb568fb29f8657e37213a0c65d69256717ebe20be7257da47267e7da74a28d68c4f01e7d07f82aa7f2a1998e68739ca80f5cc6374995a607525466dc4d100537788cc342c439e0973ddeee9d7c4c403749c821d5f081968b0cb777b83e3bbd1834e4d89e9f3680b8b19a8493434890f85683bc403c6565ec04e9a27936e64e89f9d1dd2c7550de4c23817cc22a1a6b1e95566965371e02708b8a5e13a86f6973f2567ea5eded9e94d0ceeec05b4073d1158a54645c6ef92f442f17183ffc14e278abbd19141e98061be7c2ccdb5478602496cb0bfeb43155aac31e06eb3ed7fd5f1bb79e26a6ef4611cf9c62abc9aaad200c696231c07a92ec98daeeeaf2338d68f6077b338d2313920d41a137f63e16a587622520294731741eef343f515539ecc978912489dccf541a32017b0942666ee39ccf128e92138867c375343f136507e37b0fc2fe84aa5e2121ca4087b2a9834d7920d00591827717c56485a89ba7e640dbb98c76f149bdd1503d8389b909a18b5ff3ccfe7f35c9f210f9a549491f8f91696bd4fbdd5f7e7197128cdf957f805739d080c69331e3520b5423edc2e3a7f24f73f2a9424f2327be03113ddb2cbcc6635b4fb33cdf6ba58b712425798cae9bf6eb072a9e0afcfd5abb6bd44057fb6e5177cfd5e80150385da3f28a47fad4e30a120f48dd5e3b553f9b1a2600626f9bb42c5aac7423ece130adc1615853553d4d4f077950249460d59f1634350411a3ad00a72ef63b43dfaaa02d213de9064a045dab9270bce65284867dadb84bbc7179f9f155b2db5ca1be868e4be18e0828dc4e0ddb97ea664b8980029b423f6674ba7bbca201ede016d2a6009bfa229ff7714178f1ef1f5d44c04bfe80e39d4cd899b87557b212ece0c52812cacf82a50319bf0a6288c1c723d2263f83fd5c35dea44136eed8c27d70f7d5296851f27e80f9561c95d911b1a511e3f07c830a6954fe10182e457580d9f4309b48b118070869f21abbf430d9756ef01229f78077a7103af13820df4e610d44b434710804b98399c69a67d462727e4b3b35b6adb36a0188921a4ae176c607b000127d127e90c14b83b65ab62f4d3e0ba9bda16e382a532fca486192c95c83997042048194f386a90041d5276ac46e0287edce674b9618751e76386f9795dcf02b6f4800624a4b30441651e10561d5ceb58f1e43cf638ede2cdc63c61edc1433b0383abd3facdab57e1e57e51cc8e0a127032de1c2c34d64ffaebc00e09a5e99a7933c3513997838c5c0f57ee834d295dc70d76612dfb321c1cef78dcf5ce56a751fde1c560e79df508aa5c87fc43816728c1c291fd7467554683d8830d865a50b2cc7fbaaf72456118d7b45c296755f6c7f97130cb903680e812ba15f1bc526be46095bc76794a19f97b355545916cac5b9e028ce5737aa2965380d02c574b55065ac83fcf1ba217f361054f0737ce1c6c254ba86fbfcc8a8ae3673ac1a9c61e1204046a1760197d8c35b12196a63f08a99ee3f77a3a4e470b3c712e6006801535329e5a557f1734af974351508e87066cec60cd9308ff71faa40d618d836ac58712ac55b2336b0cf4be0e47bbce3d80596146098107c4fd993a1f7b830c7a9d8df5628640e06d12c679d11b050cbde0b992966a507bc7f4c3194d45067943ac28f0f996a7891c8b0b646ab3a3e3ddd645f709bf81b40030d05dc40114652f72c8a9401710181340986fb45de9b665186d35c6a22937dbfbadd364990b0240af142135dfec81cf92a342a57d18ef8fdb1530010000119050d08000c1487800c0f388959b5b464db23217659f1a60e74b5a51ae25fda0cd1da8823e692d0f017a2532746c20b4efccd62cce528863d2e64326680a260757574d2b83a4a903fe6e44beb6ed142ff062fccc8c189f2e27f5d3b7154c08b8cdcfc18f519481d4cc3d4024527ec04218551c3bae9fd12665692340038aa77e84029af6151d5973aecc562fb75b7168810f89c5b763687b3cc0250c835e199a231ff947b5337b22bbaa0e1e8dd5ceef31925a8dd8226bf9c73560643c9ae988fcefe26d0b2e0471a141306ab3bab62fa5c06be4b7f2c3281201765cdf2bb8b4999d428fce7da88efc50dad88238cdd61ad076fcc431ef7bffe43be486c5cec74dcacdf4d5cf7d426e65b3dadd002d7274df44c69a3f40ff2429b80ff0e79915a28a9daaa26fd8b16671a06abf64f7601054fb3a62ce4d1b42fb673061b9a6a91380d43396a583b2f5532d4c3140b6eeab1b95b3979b1f870f2a965a994613641a8f5fe0f9acfa80841b27b98e3f4133288caee018c57a34c790aa2e248fd63a810e23ad238cf0b98981009dbaa12ef78cb7e07bff18aaf9a8e9762540cd7638b0f57e22ca3f1ae8eff2cb308cf7f14ccf9ac55b1ca878872e14f753f0f3cea7338174c8b1aeeaad0a954ac7c53eb71800a97b2c4701ca9d9f493218f67985b992af033612722e38ceebf70e5bc03e0ccafae2ee919769eff719ed6d43e9c2b13e86218f0adee48cd2efa20cbc76a27c27eaf693c11d4558aac2c7cec77838f9dce80c47e54dc560c21fb060048cffa743f820f241840bf88196567c8d8162bc5b54269988ded69496ff7f44b2fdc22f300d27605ebc6d8793555e6168c397dd9a67b43d76ec0c926c6a9b6a5393473159f6f1ffffe66fd0533e753fae8cea728011f1d5c5838c461e85904e609dc13f3037133bfa0b3ba737cd4b3f76a46c2e2b62b9449e005da60e58b90612667af0285339fbdb7e2ea5ac3e9537ef394255ddf665766beed1ea6e4b5bfe8ec4d5c98a180df9bfbe98fe155aa87a1130b5adfdac1f4abc8d106c24a3ce3d9fb3e15ae7a957a2d54d60127292eeb51d9e883534ac482ca34b6249804a2163bfda24836591e42024642cab92e5358e9d134797a90a561e703cabb232659b1edca25d0e9b3cf13cfe56b8827603495aec1c0003fcee170228fd2166537a0a5331d03f7713b8404a4f148bae88bba6701eed298c8753a02df578581738a2262a9186c6f4d42bdf4a4967f2928c5dce8e069dc90ab2b57c49c8640921e2202056ba775e3babe567b19b7b5ebb6db12fb3fe9c57001eae2f52626909fa7cb4abdc4813aae4edb7696b99b4ac960ebb9a21974f80af41e19ffc12013e81a0bad3e6ea1a324818b3266bb46c6b0d6ea226108a39dcbcd27453c829010662f1e78e8120c6110562b0ea1e114e1099e2ef8b7387c6755bf6b2b7331542d932b42a936824a978c3201b29d61630c0b6d7a842821985ed1b5fb3a19f3ae11f9cbbfd8dbec523c52fbda1f0c942f55e801de6b885552df34c866411e9b740b9e79281bae18a6dec0f7820c1c58b5c01a90b6109d80c5e576005acc98db134fd415c5034c0808eb05456ce0efcf9ed7e835983126dc8521b3e6d7983b6999e0d2c197620141b31b9ca11d8e1b6db86125941a76580bc6e79107e9602e5e8cf93ef39f9b960a064a57b2b049ce3c557eecca276dcae16e38d746590d7ff53514232eb45f2bd260899c3a3281a17c0dbefe1caf655fd1120817f305f7abe05e904e7c08d38fc451137574378631fa113d40aa47111fc7c6165c9fec2eea669c74b1d1eeaa27a839dccaae2960ef4b6e9360d5c4804d1eaa51bff27ba5b6232effb9e2cd05837c241e849cfc3c9b4bc66977eafc23bdc98c6ae025d1fd5179a48f26f798a3cd73bc7b8fe66f2a10f73dde70b1b267d806e82da1201c8b563b854107091b4d6345a6e683590eb054feb40ac614bbc2a29e451ffa9d202b447a49d890a60f38e50023afe9b9d18ec297a9f0a70739c1e731657b0712e3074c23cc15397239e168e42c4b8494079ea55495fcf916386cb3146c2a6e0537bc1a9b5eeb87ea90cf5561cc1bc02d3bd4ce4d765388b7d7da7b180f08b9b8bfe125b729358010b0abec731a5001334fe1d8ab16167339da5bfaabd4fc43f420c85221a0a45ed8eff1aebc86ca222ea412f0ab55c1e6e02060684b71db85efb1748904f0cf6610504448d18873b64275a071d48bd60990a94ad1fe9273eed59a9862948a47e49980fb389f4edac1d534b7aff7bac543a8a9b2d56458aeb40ccd75891e5d1a086e0025500e37709ca0126b1e3d6b91ef6dc5ba701013a75435dfe3991322fba9ddd7d68ef5daff52a28a8a6957d70fd64f0553d7e29eaea0854be226167fd181f644089038a33c31b464eca1c0e180b29090b1b20e238da52204b22b0755f6ed6766b5ed0e6d962fcc5e0eed2842924eac09a2819e7b3ff579c6e66ca65ecbf8663d126da018c51ee09ae4a6d77afe32f1275b8666c9c769fc05c957bc351efd1645d8b72ac8aa04b97a90b56eef2747d23f171641a266577d9de5e27dc8462e7809994dbb7e089d039dcf2f7df7dbfa65039c147b35b2383c2491d58b673567dcd7a2efa5983eb2002c062799b86faf09721392cbc575b85afa487ab965e8de0993b23c9f3c9cf021aa2db671bb3605308ba0ca5bf9771c6399176aee1c753c44bb274fbe54c3f3baa52331d2dac59fafa1fb87722108f61538fe1c8938a0b4bd7dc74fe1ec0d22fa1d6085a70091328bf164ad2d247ef6cfc1f124b8d05973458470c217ca3cbe1e66812801ad6b38ea797204a8661a8ed2cac56b9687a082b23f4c372ae78d14c83c5aa8f4e308b534d684f8f42506876afb2006f6e2a45795b569b04c6650b8045bfd9f2dbd91ecdd43d0a7ca8dee62b09348d91d71da3c12bdde6071f3ada835a91bef26ade4f5328cf00f6a4c571da5dce1b64dfb00b3b1504cc102b7ff2e5613ff69e3624f1ce5c4e2cfa5f7cf0422ad7a5bfc5b6f20036d4330ba12970a0bee313a21ce7c99ae774b495c4708766024513e250df4e34fb30d1469f931f33cce701b35ae2a1e4f0a00dc39e365e65beeeebbf6d662b772111da210b81883b313a0bf49210718561feaa348dc04aaa9a5996e90b0105a5c5681519374363e7637e97d4eada4a65d548d191e4effcf0e5f578cff11f2dbf377aea28a7ec7e3cb1f4c0b71e0b7031e4644c443f7ebc1726ca89db9973416b43c90db5361def6b58c7c1f5be3ff5325bf9c136323c8fea96c523d682e9b0c7764cff379ee9b14715d21287a4e0f8ddfc9d592d1a42b1ef4e07ba0ef014c42b5c46ce9477ea08b61dac5347af01858124ce55db7f7c75dbe5ab92fa19d38382511084271a28b99fb45e6ad1c2f39f4630873cba1c8e9d25d1003ff74049f15b4842bbea908a3afbbfb298cbee64ab610e956fdf1f951ff4fa30ce75ba0ff19d190c1c86b76354daba40107201f136623e468ba4444b4eb24e96fa4596ae4b64cae930a038063affb7b5e0f07b8c446254da556adc4dec574fecc7c7c9d96074ea7a0932a450298094666991d11de7c4e637fa728a10b8741266541544c177cb47d77e63841cb72bee2c19327b757178fae4df4fa884f3ae112735c033969e5cceacf4174add0809b784a4fa01948d0cb8c76df02b711af6009a1aedf54b0dc30525e221fc0925acfade43349f4b8483ceb1f1b7b80f7e5d4b9dc8e6a0b095e890cb3759202f62c7f5d361e060b8f2b232d6e841494a3dc797eb1fd9d3166d2f91605c8748024f55f512b439be3ad489b55625b6bef1dce2edce9500e72213224fb5dc586b022326017097abe0ac83e6b72aea7f1ec50fd50a8c496fff2472b7ebc59174125f129ed5e42fc847f91980f05baf85efb10a44064f95e976fc33d5308fcf5a49bcab77db6af924a6ab9ddb5dee7c9c9472686c703972d84e89eb8642aab44243537f84acfb53641463c4ed70a303bcff11d2341940cd90f1ee2cda80721e07d6f1078b3b1686e5b8acf360a6dccf121fa49eb02c1d94ad2e8f04b19721028d4beab307d2c06a5cf086ff5793732eb88a3eefb8f83aaf4d2374afff61765ebb879eb33e5a870bc32131a130d94b1ef1a0a7fb653b0b1cfc11d0a91beaf2bfddafaf99f88942fbdd394d5f3f18f985340e6f2c8d48c93c7883bdee44abe031131f14d95591faac83c9defd93dc287d6c89ae6a5954d2a068120edbc665c717850f2a0c009ea300e5cecf48a83db9c3cadd6d1267da34a7d9968aa444350fb74d9697c3534de659cd3a55b0db8a0c4790c5caf2e2de2548b5d60a2f7b732f1c08f89004b5e9f1817a6bc61260e76374064d5550291f73fa2ee55faaf62717ca122dba99cff5840749cf255ac0904e3815f994fa009d209a6c01ec4cfcc824d2debb3b24b0f1d33e84d98aa523462904b25bf38436cf1dd1ff196a0174a1e2eb6355fb68372c3d987542adbc6d3b754469c58c4cabd64a83c77393530296bf7537a3d30536072df8b8b12e724c34ffd7de1f6a40d54aa2cbd406ab7f1282d99289879b7be6f318c14c462e643319bf612beeeaddc61b350c005abfb9fed02641e81546d535b825aea0e1ffd2eb4f5850b4a31eec8412fabeb1bc437888e594fd4fcaed6db88001cb3c60d74b8333f2d0c2e826f2bae39ff170b5742140a5ecd67f2b104f146837fab6ee2bb6d2370468fba3a05b62809b2143b7fb05b46d786bc42fafabc2fa43a9a0418fb18b1367b7f650e610e6d6f5390f1e02c43f88257ba180def07ec78695ab066b89dc9f5f1f2665086fb0992c379924555ea35b7a9dd4ba9d6022f57c82ed90688407676833f3d40af316491d1bb8ff64fde99fadc1dd32c2b8f4d135812ec51cdc314222b5e0f98257aca08766ff508b4fada1906e30eb2ef6721e183dee46af2828df3ee5bfffaff568ec1a7a30686df639f45c838ee61fbb5bf3e02eceb35facc1371e0806a010879141c3bc546e523db3a61ec7d5e3653c3904b9014e43c8f4e95369a16fc05f371ea2ed0e9d7d6cc6791f6153f06b6a290cebf021d0d2e5ffb7601b85e6cb4e307260c0cd78c457f932c4cca58e6b2f93d881059c461150d500f3c4bf6310f7a3ba158b3a05391caaf003c779679fbdb3db7968dcd4084a518eb355b4723407dcadbe326522162719e1e8873b6dc61553c977af708364491efad0bb21f9cccd2b4edaeaa204f69a0005c30c08ccd6d7d098b8afdb19ad7fa4d9b44a30efc7ab15563d03efc494c79c3b711b41940fecd80e096e746cf666a4504efb91ca1d17da93ea17588f094dbc9f08f9a6b093446f5e3d42f3738252492481762d70e7a418d435d38e029539876d710faf6b500c049575d000410b0f5632c17a2bd842831d4db01f38d331915ffb38d207e29a0828e7f63e78e71e0ffe0b2c3834f78ba9f459edc962244616268225dc794a3e09c9043a77b2259076745081a52b49d262c90fab8e8e7cf2785e5391ac484c1acc768276270fe04124a0f49995e560c4430dbac1f865857331c1822737894be71e886c4708687f68a63af48c2c7226b64a00743e74aa85d08bcc4e4c42ea554fa1d8f901a920666eab60dcc9650b22124147ad725e55d681a23b970788ca89a3fb8eac0130400a2978bc1eecdde6ffa70b5f23ce8a458c1e9b296a65d2c3eaea1725ca88de4d8084995cf1e96065eadfc4cf795579299ea256ef152b89cb044ed2ab7d11e333757c7e80bcbe0b25736ef19f68ce9270a055e1974870474cb41ff55cca540c1b704f217b5672cacbc4d82ffbef91abde0e8d5fff5b369a2dc1405f2b9aea67a462970fd6ad0a60dbd717776606f4bd70395c506923aa566277df859ff7ee52755bdd2b7e52fb11925331c2fe64a50e85da8944acfdd104a6d4cb175afc391b0a13961db9e50033885ed6bf77e1506a2eabc4d54c4f6208a257e8b1da9fa15d748b42cdb4498de2bf168b3a7b70f8212d705ead3b067761f3f8eae96483940234d3cfce32bea06c5ff1eaedc1a27f59cec35415faeca9c2c188e7883f1436ab81952029eeae6e5e61290d5db7da3ac3ea4e5ecae6579fdda4fe314eac9f9d3a7a6d2a141e1bf014ca55f42453353fbeffb558d0500e3797c2a94921ccf0e64f8e06473017206c4baad3a654259d7d1fc973f8f5e3523ee68369389bd8a930e5093dc5ebe86f56b9a0d7a26895a90d5f708c48684f14fb9fa315c388eb9d2e54971c25dd9e787fc736cff4f72885a4db545f3fb07781e4cd4bba25eaea58c4eb0af774be847c282c06500b6aac578c99122d89fb4696901c235ad5ab73490ccb6a19a367d97cd548d5367010229f9af6401e687b1492f5dbb38d1a97686aa02f320df59f78becd4a35c1b24138c00767d22e1a02c7db89c80604d5080a433421d282d50f9adfa2f3ce33bfa566393359fcf146329a40f98b458352bc6b85a60fc867460f22b3ae47566a55dd506bf0a0dc181da579103d57f7693e9f948032a0d8bdb8ffd4092a852853a9ce0e1829493f1f959cac224d27247ed6cf8ebd1b7ac629800dcbb752e01912c6d4c54ff87562605ca185179aa7c321f0d2d5c53f3046e5a9f6e81c66718c5d3511c41445078a39f545ed789eee7f2f17dbde4291e2d6bb80153cf1bf2a58d9ee7c05fe3f1ff54b95797aac80c22c0b5f441cdfe19b89ec32fefa211d253164323d3e9fef7b9b74610a0597bd38703c046c295c8024fdee433ea88a353b6d1b8d8499a0b32eeb28a555af6e55843d5f3434e36f6af2203809181034d40bfa92e010d0bcf82f9bf2dcd699b6125e812385c3e8338380ad8293e10ec0d1675797a73a1f78765ac3bcbb1ba08d906438fbbd8e1d3b0597fdd5895afba1c317f4a825f41f7a92d5f8ab24a97022a3ac70c15447147c32be1d8aff4bbbcb8df3a37992090656ad013dbee38dd1bed7c17e67671ac7fccabafe3c9ba161e0649d29924fbea48acf77883f3604e8b5ad6ffa024d428a65f8e975f3ff3c4306ba22b932fbf6eefabdc1f94493642743b9e73d94d9a1cf2d8995ab4ef37b442add6d3231456b05d4ff4fa09f71266106acab5f51f82bbf9c4886958fb192ff6e4fba56ee6ded08f9619b816e5d1b76ab81d7f0ffd10e504dc6b07115b6e043af93666cecf6e098fc210b7bf6a78209b3ca562f475e84f4a9da9a1a487bf39a2a1ac87a4cc1afe7fede0bd99014b030b3adc85228cd34324452e18ec855efff5d0354fc1495ded26688b90b181798403ef601c025ac334f950633278d6c91d261ff702b47fe711db17734de1be9f029a90be063173c00af6b1349654fdbf2a64f88201c8ec7e288ff0141c1e5c7e6814950e61b3c94651b77670d5061f6177bbf0427d8e80ec890a83b188e0aac6063e528757fbd3ae507f4edd876421503f4a9238d47a80d541bc74027955aca50507b066ff23627e6fa069d3bb0249987260ab57c515c5f949b27ba318ad49818048d1a5478fff9ff1ed798f14f5a6b2fb45eda1dcf60c4dc75801a3001d730d395e56780229d762b8c0d45b243e0e02f933bb4668722ba7d06baafa453444160df2d72e9a50c01aff184b3f333c431f8b79d497521b86e250a8f119a1f83a77199298857e79f6713153a3f75527deafc4e83cb249bfcb4307dca8bcb2c1ec2966de40eff05c1984d02b4a574aa449b1324bbb4a67f4a5a17bfa6ed1ddcd63a8e903591d1eace5eaecc2a580229460bff18faf32dfb203e1c9637730330e8795d64f82fe2a8af3c9df8d7fde442beaf3e36098f73bf5d704ec71ed59b637f676852ed89ea3056be581812e8fe2fa45afaad7ca3ea473d0338ecf02e0c6ca34591d2f2e0d5545d2d8f7482ee3a6cdee77716e2dde3c86a22987abff9e71db5e165ce7402f25728c318e93411ee8c37dc5850b7efd88684ee9d639cf76b51da9d5924c3e89fcb8944557516c86964baa65f8b8d8e7b05941066af7fd45e934683afc4f070f63e1a5fe1776bf3d160a1b7fdb34161a0fdab5c7fe1942ae61f6166f31f639bc64a5c9dd13a0035938efcb44e0d96c1cd30167df26fb91b576734fa96cafd4341640dabfd53cd5cf641c6ea09f62d26bae14f85be3c00a0a65514c0a54397078bcb54251f8da78d01c5f9f8bd4e3d0f38ec5dd3e6c51b18a7534cef73febdc3a426c332ccfd38912a288720f3d54982f876daf82f93a1fc392dcf572219adab7522ec5c25f229b7ec0fd2a76f0ba72ead86fb7d38797f8554852a40e5a61821b29cbf9d37f879b2f12495952419ad4eab49be2b769cbe54161e980ec9f13759ccf262c7f52bc3af15544623314a8bb2c4062de1256d6df75030f0e57c68b940a4c12206a51ab4790696239939c74772bc55fe7280471a0d976380dd2676efb127da82989abc056d235fd62a8b5391959f978e6e5daa8b3f629074dd6b1f71be5714cf82965e83838c1dd76f951bf221fbd9f017fd533cbf8fb97372980b73985d2dd898eb95b5e97bdf0902b1f371c47e2a4e5bf4a957cfb15429a9a9f523f53a0a9b0f3eabd4c55a228e13a26522b0b30f30f4f77fe9b4e1546d74e67e626e7eb40eb124bd360f8c607b1853dc761080d42b96a583b2d565a6630eb4a6cca0cc083afd2a106c729bb2a2efcff5c82cd2538829acd609ea0d18c4fca68b2121fca91b2d8c451c08de1c35817d5ab0afd3f68fb8c74014421e9dbaa12ef78ccbedb5abf8796074e14b5a28ba03dd86f47b284a0b3be49a85aa11d6d2a54e98a43ddb3e871b69ecdc23b6c874562c666e487ef3371e9a1ec6689bab7ffba12f48bd61390a50ee744db5e0d210bea51b62db26e555f5a766e0d9de1f3265ebf12a32a11bcaa9c481623e4121ec32e9a6b4d837baad45e6a1ccbc1baef52e3c86dc9738b1b0ef155a7dec7cccd98864804206398d9cd2ff58050c4a435c82fce6265add25675616e84727ce6ee9f0f950a7eea960474ebe0fa23f5433abb83aebe32e7329ab358e29f2a4ae63f8b25bf38ab647e9553f8b7d7bcdb410a18b9b91570e0c2b2689aeb71287c79325a27a3a8f2154a17dcb573641647c28dc453333d127478f8e5d66b066d4f01f6cf7ecb9af62d90409a3bb2d7af891ac48582064b7e60d6d8fdd41705ed4c221443d1d5675a8dd5b1873bcf849a8b17a1929c2fb42b0370ab108970c23b8b24fb361f4c5272cd0f911d2e08b9de8cdc00f921a5d762846e69c005da63e58796bb2e6cf584e5c3a8be78b5f884345b173c5c499a85354a5db7e000c6851e41efbfb1c9ce7c47aa6288ed959f37402733278c350f45f20ebedc7d6890cc08bbc83bc885bb6449ebffe6bff736b126faac0e6d3153a3d7090cd44a64ae0aa133c1ff87f8008c19201be41e1938c7c76b6f254607494114edac7ab802b217b90c0c6059aceb667f757b8c864e9eef40ce68f6b3b0de90e02c3f8b8dc448840d933f31037ad0025131dd335c4587f20070b9a1ea5a3d16f7ec39fbf191f6ab401a6075096b0078783583027c8f170f1df83340b49a845043ffb1cf46afdb0f877473be0038bea7056ce451ab891e76c6368f72d1e23766cc767695a90c7c0a5636e4b2e81e0e359846861e56c78d8f7e35652099b6cbfd3a72a9eb4a1a074bd588fae4e0a308d079df79f65905f4ba59a8893cba56750c772bd6751d6697d56a553c424b6e884e9bf5b001119f163e12810c34aa154beb033655677dc3f1bbe6665b78b11439f02b563c03da87f057741caa96b533f5d4a2e96f1e32946ddc3aa5930b0fffde66d78399cf7ea67168ce7a45ea9e302ac618e1cbefa97c46261122867e4f1838534e45396893c34178a121a6982caad94f962b7c2999d9e14ab89ffd8d8285138d6f58218ed94d4cfacf676fb75b3d7e24078cdc8edc77ab85b13f50d7c18e19d4d1c08c9d74a8b65956bd0912d48a9c366ae5ea7f9511ef7d49b40a819656755061c0a8ef506c186260d57d9e9135b24afaaaf88b9190b273b8b25238f29170f230717b739a32987b905a3153bbba919ebb39f60e69ce66c20d130b3dfe641b8b71e252ca134d2ec51dd20da7c07e0d1aa62adcffd5559c3d9f9901afeaa26419c585570662d44cf735b18fb880fa6eae55b6b18d2629b8fcb36c1c52b72549ddf6545509f9ec1b1ea68ba77f27f65fc9da9d54ff88e5ef8b0bf2584feda1f7b1f11ce1980517993f90a98c4c94785541afce1545dc8ae450e20276e0d33b926dab566d2da9559982ae6d0f77976b107f9ebd865f9509eeb2ca5033c4fd4f4443834a28fcccf56350c9dfa28f5a1ce43aa036054a945ada0ada566746edffa5b1d5c2e3889c66e88f29a2448a148e441fde89e8d57bcd8ba634e7c51cc7e5a370b33cbfd8e7b54ce4193b0be935f8236ff30189f1f7038e0debf47011c5dbfcb9907306cb1c903b73ae0b7ded9d6922aa530a779b24a3b308e06ae52b4bfab2226891b2dfb24a339d302dc0f0da1910cee04c528454259c1df69874f451cbd9715cc7ca1174c90ccb8dfc9c050a9e1152ba16b3f56094a5bf3367afdb1bb5823186fa86550b566381997c24030507c0fcfe0afcdbdbd0a75ffad5d45798934147cbd3f9933e45f8a8c01a82b8d0a205437f91d744abd1aa76439f637e96049a582007aa0841cd40b2b5743836627b92bae6f4148eaca6bc8b7cadb503ef06e98f2836b1d6852193eeb1f68d2c54e92dd3bb468054de3384b7b1fe228d77a086131664436f48dac80a6030a8941671697e02f858ea28cde93cf305e0ecb4c472a81e744fc27e38a27e1746830e9c01dec864958448ef6f0b0e526686e71470483dbb3e21322a11226cd254222255e1225495a496b11c42829922ae9d246da4b07e9289da4b374956ed2537a4b1fe92f0364a00c92c13244868a4932245386499664cb70190123835febe0ffb604784a3084048828bc410431892892c8602e0a1083086212512491c15c94200611c424a2482283b9a8400c22884944914406735183184410938822890ce6a2013188202611451219cc450b3ff13300eabd16014053631448bf9ca65120f7720aa340e26534ecd072b84a9b9573458995588b8dd8ecb115410fa20f7682d8057b511c82831c1c4de2149ca4e02c0467736826061743709183ab49dc829b14dc85e06e0e1e62f034044f39184ce215bca4e02d046f73f0118baf4198b4910ed243c28549baf41677091326ada58f647ec8b18694f61ff2245cce36d2e372464a90f416fc982321433ee470487b51d5e51c819d3b2032dd5967cb287d645347063bf3242dfd7acbf11d8d9e99e9741e8ccad24dbd694b473d7e4d30b2736546439214d2c1d4837a92ee45776e2a8d2f2e4aa4d00bd47d5174d59b1aa957ce1249efca38474d065fb9045b29ad8f98794dbab2e965ddaeea9aaa3ed3d42397ca705194b16aa59da9f3cfc195895464abadf54369d1aad6bf92c26920cd5ee90c52501a66532831883471504bb3a9944860285a090c45274d0856d21e6bced25a9a5a890486622b0d3c9c628941a48983bd349b6a893429471c25c0509ca44909ced29e668466d26cea2512188aab34f0700a26069126fe1f643f00003da386588490e9ed5ea4b44e6c802b4f904d3b6d6d839736cdc3f405f6a32ef8e7768568f8f9bf133cd5343190e5ffb639b494e868bb8329fb35501b8b77a56dfba2ed293dfc88e695cca293c8ce57cf621e7505959bbbd5e97caeec5b0aa445ba17b9238247522cd2690e75a5b2ccecfa63406168dccaf9ff75f52cddf141910bebc759df9956a36324c1a76a8f8eb88fca98dfd4bc3c13731cc2275bfcca383e7cbf44d13cd716664381ffa0f5d79cf3481e0424658cf1e2411b8e95515d93aee7071f847eea97f6b1309fe1f5ad99adc785b5adea49fe9757a48689d079c8fa45b7fd525f7354d5d207fae55de4e52feb577d7dd92e73e64db8ed97ba69e79ea7b87db8f108b879cf1fc01165f7a9018487b0d4409c0b2e04a700dd8f30d76b8a3b8841991fb4e1c1551ff4d1c1e15e607f05ce23388605cdb4c25e4040e1532136287303c606fdce40bb05eb81411f1fd4aae0d62fa8ae056d7d30c405d5ff41bd3cc0d440b203db1ea02878360bdaf810d02e0407078798e03120b824062ee9f5664a2b20a20088456dde6302e457dbd45644813d264e294f71412c3666108aad7c4a61aa2b20a2808802220a80581ccd602a4e3208c5590661aee9d4a7bc2f050af43e982ce9360176d979ccedddf17c411b8cba62824940873fd468caf2add3f7d2fd2e86d48e4687a8c7259abff6fba38ce34664b5c06b7916979639c828cf1dd569644afbbbae10ad44d24afd7152efc4ab125c8f57e2d0994e3d48a3443bcadc24e956a8e5e6157c159df922ad4fb26cc53dce6135f4a1c3f4db264b904ec1622aaf67ba5f4d3c48024d41ed8bf30f2c72ab2776ef333607afc172ee7826cb4315fb6e2f9dfc09d8e329badf8752ba55bffa662aeedbecb076d99d88cda8a939295d82cd72105897506462770f325219cb7281a5c9952d884bb9d14b0fc472f955349ffe497c1d5889a7749efb043ec3ad32f5831839da315f303352afc65d153cb6d49b183ef4d55a3785e5ce3ce5ae8daed2abb1aa84f5abf5167975c6d0836d4a7fabd2ef9bc4ff3745fe5ff5ab733771a0eecc3619463060bf0b4120d3454818e89544c777fcd5af3ece33843d13a40ef0cff4a3c663834162a9f849153797d0cdbaa271b16c009d1b812df664f8e0fd9629ec27a7ec2480327b70fdcc7c83483e444246633e4c0ff215f2fa29f25ae318391ac3c35400ffaaf548f0bdd980939c8e183ec64d47d4821f42cbb829a76ea3b55d123849d2fe387eff969365f5a9f121655950eb92987f8f14c1b10c7112a6d300d40d141f5e6ab3d55a902bb52553c687f7cbdf99ab3585df72580705addd39b130482d7267eaec747f68785162b9a07cf64a65aa7ac8349588a7724fd83ebd19bf3befed9780aa3a5919ccfcc641e327ab64e7c5487467eca91ff74166049c88f9b7fbe51e417edf8852cbf55de3d656a2a66678b206f2eb9defd95129134ad0332e1c8cb8e768818d230dee6e08299d4ce8c64500250323802db73a543c90a50a36ef3a4617a15c316cfc2981223a3eae8743ea92aa3a0ef99a0af6dbd376166cc2c95623c19b49f504af8bc73cc865d162904115de37f1b176f06569e8daa0489d08f65f69628d4c30d1153761f4a84a7b5fd4fedadc4718e930eabaca2642bbe9a6960fb8719b5a9685bd31f5374a838dab1fe32039ca077523e79e70cf66702546c59678746c437574c4c68657947a48700c92d703a8548ce922f5849378cda5d82db493f287d0bfa436419fd5bf41d1902c571e8ce164a99d10fb611d8da65823258df98d8d06f55939e9975251f854721f17b1c225708afbc25604e1767be360465e9b9ae64cd1980c2b36780c72fdc66b758b85897e895a849b9c3764bd83023045fcc033df3894431e64a50c98788af9016ab316a3b0b5d92422e3d7184481409924aaf66192ce082b150a65497b4eb6073cc448f084a729c9d82df774ecd183561388c37fd0ee375eb1ba07fc7981ae10a48e25464cc678bc0e63d923f308f0fd029efc9fdb24c8087833b542c6b7ba7fa72624a3357af7b8f9cbc23c3caa5f47223d741ef8aa256f0750aafe360a49bb50cce13099d194c0973a60fd23769f24befa2af2cb262cfd8e1a557ff0f5f062ae4a8b26d1c909cac221f157aa8957f49e34990e5fff420219a450649996c786786998b1777c4f16c9688a53a6c6de2d432b7bdd2c885afd7ae0259ebe5a0b74b5ce432b3f03065e13ae2e3dae51a6b0b6371c1ebc5c03c9ed0ed6f170456f57253da258a531dcfac52b2b86b5c69748ed4b69ef3805c28be015e70980299866621e0e15d410ec222da249fe104f78feb18dec64962a3dda26dd0690577b2904eb74b23b353b9271b5c7adbcf904c0a025d5d85356f59c7908b4f0778209a96530d065f42c30a55e2259c5e44d1eb5a2be2089ff4d5f1895a6b380ce62861cdc1c3fe88db032d8dfe4c876e216701c747f6ebaec94ab7774bc7be4c262271ef4df3793fb961253823ceb21f5b3b4938674d093c3b05afdd577bfed2c9bc932a90f0a38fe9498213875f4c7834f8b4de77382c94b5280e347d13835f30dcd735a9f2cbe9ffb7dfce8bb205b984c522e9b4d389ba00ea8a0405259cfdea90beb693b14354adb277b545a1ea4edac7b38f0b603c92de33a91d44174821abee9d04065c8493c6361c811e4a9b20854ceb945e2a716c7fa52fd1029c014dd50d878f00ac3bc68e7403484a770153f9050ac6db652b79aa657e2cf40a2cc9ff1b86eb84af1bed2bb0c8cea1ca07b25a7f9dab6f928951efe03870f6c98879b8017026a0ef919519e7121381ef9b4dde559ca87602336b0dad73373039abf957a1e5d999f9101ed3720228c1ab4e6fad5475fcd1f19bc71182ce9eed806387c6ca768b72ce6445b4454a30bd2e671832ef96ebe010877d7a6cdeb9bd505b84a39202a06cbe203ffaa6a6249474fa41a8369bf54de9346dbae4381e11c5fa2a1b375ecfbcdced98ebb8819682ac9ead0338ad7891e0142ec685e0e78e67085532d6c706c430b1c3dfa83401d0a8da0e240894c188f40af69c2e6f22b0abbfd14fe255139e027a145e1e8a0271fd3c02867d6e1f521de27c03044a575b12e535e6db13e77fca7144349bece102bfb554694616ca36af90be2835f45e31f7eb160adcecc0e01c9e31417a6b2e242161f827d313197aca8736fb642d31e78d06c6b5a9c434d46af6d073001b7e64c64d33735be67d590b4acd649b495b963277127db485d68c409f403d78f996e1a1fd5caadb3b0b8788c4798e8c45a57893e7c0946777329b8884f26c8f3c810e47de7a7892bc876a8c64bce5680932a34bf62d40699185e22ecaa984df70743238b294dcbf4039efdaba7b9e8aa3442161c31aad000aa31c3fe77813ec1b4d10d2ac65fed56be9fdc9adeaa91067f98c433c76308eeccc5ec678f835a664a8aac7e81ed3c9579c63a439484138e7ff729afddf4730bc7f35030654290b0dc6a403f3079a19bf86c9a4f07accbf209809ab02eef1f33fbb8a1445ff6baae8e63963f9268d96f58eb73ef5d3b82e948a0541f21af0d61b0665f83e0378375584d94cde2ee88316ec84900b89c55aac6a43feb68682a2b8ae62be8a072150cf24a7d63b13d290fdea3c56f68e3e65caa8d6da122e17663cf9113a30d650ce96f709cacc54320c8aacc21cceceffc7699f5a81b9b31156928b742c5577db5d4ef1009f4c77ed751ef0487ea891b3a44c752afedf002d6e5dff46d75b98eedba02fe5e528e4c1194af35d786c0e96c7acf8531879d52dbee0a9f00a8fc2a46260260698c3357d0fa22976fd13c83aa91151d5fc3fc97167294ac8946e3f1cd163ac41cce5c0f0b9412679ba4c646d458d028eed141fe2172810a5c5a683e00af857e04769694d943f85c4762e35097664a06b360ff55e9c38c8565d3d24f2519df57cc5a5b8fb9ac0803ec3e822c5a21bf9e7bac32e415bdd702a42e8accc620c7e502937a1008a29c04c71293ab28f08ec1476f1071ad26719b04c6fe0f03c814c32c2df498ffdb6709d96885f18aee14c91a500f14c4f1fd842352ed0135fac41df3ef412c742beeb332f677e9213a122e35fa28ad9a68b2ca2305256f66623a6214433949625bc240de392bd3e95b7bd09717e7137be5350840358e7bdbc17e0143d94940e341891d19ec49456f931e35ac42738eb23da337ef6d8af45f220b2b994f75c0db0057a907a7de6d3fa928b822ebdc37f1d55ca723bc5a44c6dd126139cdc3a46a7aa5f0ba5184f5500182a5bf4f34793f7090a7ea39f6c96acdb91ca689bffa6a6bd07f4911fbfadd9e5c24f4830115f4ec7748dd5ace2d68d7da726f9b01cd794399fe2f4c83033f5f2ab70c5591ee6e044f8aa80745b9b4cc50cced09c3258d911aa058dc72cabb04746b57c332c101287cce793cca8dec98dd9a7e66515d8727ffaf89e20ff94c8ca82f89420dc8987f0ac189b5bf98067aafce71af9810b2b4d37dbb4ae99925d93af05c0c9656c792ccca31f6cd14b02535488521d09e41e23c89c2123f67e468ef7be7dd393cbc1fedfb63f6b63fe00e2dbf94c3a9088f9c6880cbf98077710004302575f714717df6dfc2707c27f38d880026fe8854edb167814ed3841db4357d142ef079ba7d923136595c17a73d8686f9a95cf0052d46e71c2c881f6f96fd87b3d1545dc68322d479d2494d669671597df2f131a1eecbdf438e35ad22312bacfed5f5d3b3987a5dc5807905a978c2020889cff61397e996ef7652f05496e5c693b0ebd693cca688ff7671f6c05dd01b22ba667381344bf1c44ec5a97dc2281f45f69f9bb0679f3fc47e2b9d245da075cd9e4d16f5ba7ea8a69068b5ba7bc3ff88b90a6b2a47ac88a14e39fe5d95b3558bdbc1dfca140220522bb80ebf0c6dbb77f5974d0b3282f660c62f074d8307efa78c94f622a89c3ed48dc2e5480cc4263b49f8fe4fb9a6ec6fe4feb40d2466735fbcfbe18d60a65f152c4d08b7a37f4725ff3608d07eb5c403371ad9d273451e5edaf250d68a8e7ba279f9e70a236d5266cfb07ce762b53bdd684abda6e521ce2f7471801394858cf6010d0c618b146fe21f75fd281739bf80cecbc63926358864d9ff43b57f22a19225e822a90656be46d2067b0e1a53d828e0e9eb1233cd5cec227dc40c3cd9bf678d075cd639bf135142933be8dbee23bee9d26ddd0e5fa6967fd36f3fcce7398dce818bd5b1394fa26bd8067e6e85d539e645ee22ef51f1c6f69c5a06026b57aa5abd380726d068715a35fd9f129ed6ea47a6a2c87e8ce7ca1fb430aaafb6e0645d49356cdef5448330eb1782e45f579b1e6b6cc35083dea4344f0bba905c7dde6b17bdc233d63b2113fc9870f04483673710c42e7a2a10ef1c88bf5518ce3474b545153d306d2176da1903f29d1d65ac2a4fe3d0ee6b0ae4f555771ebb9bd6012f6dadff54d890cac2a45de3fd841760b9fe70c1d58756fff48ccb47451a6e79e2c51066a1fd39b7c7c99b5d851314ad07ea5b33a684b9b5eb208b2c8d2dfc4cc93f1429d43ad4c08385038cb65dc6c1c0b562afc7bc37739cf4b73a12815e1d262bc8250cb746211bde4ea1ec1c057b6460f81dadb93bc59bb2806b8140ce729971521486e53f86b9017c8bd27a0b469afeef5cfa8d2953aa1ade5927180ada70fc12859b1ee7b84ca426c777a442158e54953e93e37d2ed0044fef661ba0cf41c803bc7cb40e46ecd07729688218f31f08285ea1229401a2e02c0d69897718d2a96f28288028380a5088b31982b9300a95b51486324ddacfa30e2fe4ff7f267724c60a4c5dc99247fc49f6b5a1b2625f5d5a1c2e94c0a9d694d72c77de5acdebc27cd9d069d69f80f2875779af49dcd2645d24d8e9cf89596770fcb57e7e3a3e45d4f21be56528fd3a30e9d15a3a4238f12726c040773b53c0ebe128eef77bdff5caf947ef6f41bf3af03814a44dc9a3755856d1864bf5ecb472f54c788c378ba37f0f53cfa7718d1b984c10152ecb10898c24781d91873484e79974e6b2b35ef30eeef838f62b8d9c66b051959e8c4e4011e58dad0c1b0c3aa6b547885d4778e9418b6298a9ff3391e109278c26cc7fb4bff62aeb71ad21f5d30dacaf19a7e2cf0985d0c240a3217b008ee1e58550f6247dc5cd66f6eb6059e9fd62ec9c3d4ee1344b92d9d5d1757f4ae673bd94bf2d949794a4728be491e76f8b343a3d428a7f380396a2d73a09e4c5836d1eaa2cb9de4da7f94d0b37a911ef4f8c3fe772b9e1a9063a1201763f0f6ca644f2c5ca2e86e9aeb398a0e5855b1821f7171f84ec68f5e046e5a2e50a3973c24ebb7e8e511f7bd9a358e66dc5367d251d89b4e675dc0722b17a8c941f004d73c0f45283e3051f798003cf6bd7485b8b407a0a5665fefed8d30e649ada9f4e183d188492fc5092ae451c6a941c2a96bbb4aec296d96a30c3942f827d183d6f69b27f113429a2089e2e1ffdd3d872fb13c415ab82c8874cbd8196987487ddb0081973be5a8e841528ada9d5a1a5512d95b60c63619712f2c7ef96d200f8c6d82d754332c53ecfa7d2b511fc44c21e4b1352438de9884d9fbb0efa78249453745ec9a7212bb5284cfd1c7da080a36fe5f866c7cba47efeaa2afd24e33053cc4d1c19582b6411c8e4290c5b6941a59b229d9f57f844639e9f1f6b3bd0aa3eb141425d4430ce4b0f38e8f92c0b2fed142b51536ad1112d1c26b2243a7df2d566793794901fe4feb73e502addbc8ba8966a90e1950fa80986e1b1eeb43d79e43fcfd98a177916fe39917156aa8b9aaaca0d956a047b291660fd8da104afb61d4b5cbb924c2ac1d41eaefe53bee54e5fa200f2dc3f1e34ad6e359669f20a2bebb2528257da0ff53c75ddc3ad179ae8b98c313db118b8fd9ac6a8a07592b5bd56ddcdd2cd26aa3313c47d4804f378b390004196d4c152851d94aaec0054bd8667ec4c783586d372d1430843072750d65bc60063d81694298705fd41aee3cd607cb4747b64763d68be2e371fbc6dcdc54fabe136022f7fad1aa93acff29a02c2b2ca571ba4936833cf31fbf44c6abfad3fe3a79cbc30497322523bb5eccb5d3d993669cc7cd78f29c958816769f3a2a060e03a684c9a14b986ffd88005399a093e42c7e12305ea8fb9b1e782135f37fcb535a09a58728cb0f2210dd69fe0c80a344405fc79b76745d8994c5804fbc8e8a863bb3166b2a4e85333bcacbc253e578f6e0e97b73acdf71c3ee77b2dd3e6b330cdbc9c20612cef9fcd5566cbc881402c6942da88bfc27133db785e9c6314db75848fa096ff9d64ad7b06dc518e72c939d5218d8b212fbade700c07a8170fdc74db22eeb21f23c49c384fd04c48065d76359082b2e11b8be9af1f61487d74f36e5e1d99d037c1e56214d076ae7598b8b155ffa58b4e8fa2016302a001e9004c2be1c5ed473377e09dbeda5feab80e7039c397535ecb949299bab631e4f966dbb29f03d75f108ae7a0cff2fbea65e01bd68e0d0a404a44d46db86a41b39ee3a3f8c3e4d4d0de21555972803dad4bc8118d1a462a1d0ddfb34372c076015a26b071d0a657ffc7bc4246f7aa3c4793d880e1f68f958ea30b7f160d1d90e3e1deaa3f0d0794d31a9a950c17ca8dd74f0b1b714c225b5045eb394c627e7c9511ee028918f47c61842fa48aa3578de6c2726d6f214776cc37820397b8c1a75bee80aff5477a50ae601818064fd3ff6d5cc411dc0ce85fcbae5e28e483307026efc99806f78eb5890c26a52c8a5500fb26d488d31226acc89c3ef91d467917cbe487da786539ba9a8e37f5150c2add0f75b5e0990e963a51a43eb0bd27b0e6d76d227edeada82dd7f01e64bc81d34c309ba0f88992c252327d52c16582e7f4fdb2aa630ef389da8591078fce567fe8a60bafc76e4e98c013b150b8afbccee860e36adc8b47af382d40955085c266c0ea0c753fbde7e9d13cf10f344447d50584504f1a68c7598e1f289d202a2dd34788b35229d7d83f08a105b5fe22179439d464c4763788453a4356926a38227c460bbf22e94fa44554515acbf053dad6891e2810bfcc87b4ecf6df450737f5fe809b3e82d039f84ddff7b982ba0454a264d8137b8c138e3fd84ba74e0e7589bfa32aa2d586eb11addcb4a58aa92ae052c3d722856ecbeec8e02d1c2c7d3d504f17478042de4bde7be6580782816896415275b4a13568a16736c24ea4c74e2e961439889bbd885bed2f66f497810033df6127fed182cc9441a1b9b5f0f2baa054a54c7b50df5e1bbdc33bf5aa536727f8a9c8407ad02828cb75a6827b5f93a0c888a371dffc65932d92c6a923cb0d4ef376cce3dfbddb5f7c8dd6230de6174c5352533775c58754572c397e4e65239afa7db7c5f49de715b0aad4d76d0c909679cc2c0383ea9a2361928f9382033f22ff27147f0bc5ba86258d712c2ef83208f43e341eab97b5493bbd3c07a3f623c077722692965d6ab38d2cae337fad23fb0d1340049ac22470f00a8c3abed54f23a7948ea73c8a40fdb260e48d2a7e5b7a1bc6ee89228ccb343cda95847dd5524ced51fe4b2e94382da8fcaec7dbfa0efcc7f87dbc7265e3c38f12d33ffc4e5027dc746ba829af005e20103774fcaf8e420f29c6d3c66bcfe3924f90641b1b8fb21576f7f376ef3ada05954d94787db6c9a9c77433d4fdaacd77e935792c0ebe995baaf946c2101f6eee071fd5dc237c0e63ba850bd833feb10a16f31daaaa544b09472bc0042b4aaa84add7b12e9091dfe7ce4f9ca22bd2e862c58f3f2d7f35f9b09621e32ad90fc1ec2b3802b93f1806ca0889604aba5a00155db1086d950666f3001db8d3461797427848a57ce4ef2027ec30c3cded13e77f32e83a0b4acef686b492c33ade26551a31dbeb153d420ba81308c8a31b858b1c783a8b6f12a9ca7bcfd6ae249464695761bf0024c8f18431acd6a91d191f15a95527a89887e3a897fa0688c7c6a4c094aa9323a0afdae11041d91f26d74998b65b57238116b7d46670b00b4be5a16f85ba04a2b1014e1a1cf5ae4cfb41567d956fb13f396ce37eadf8bb8b778b81dcf2b043b333eb2fe27b3d5b74e29971b60da1ce9f7fc10bdee46fb7149e97d82732660ca34e8ef8207c79ab403d0a9d5ed01dd2e128b625c6769090b1798e199f9e631e4e796b934ceed3e40ede2b36d0b36dc642fbc40cac8b40424c3cfd88d630b9425d8e8f836c7dd1530cab2000a44dd8af05a91e823385705be25b3ee169b346e7f4b049094eed0ba3ef9789e67622acdf68b498a9f4ea6284f1a6328f619fb197526350f81f68b33cdbea9234f7bd4643725a9cf45ae8b7dafeca04e7ceaee8ff4ceee07cd270d7dd61001b5f21969f246b788253f924910a5100867962f7d1da43466964e07b65b4c8a5a31d99221aa5fdb6caba7cc261967f1c74d43093609ceab7f4e2b3468b0eb53d7510bb9f15a217d58039db8d1821762653bc6aff3db813bc47d036321647858de30272812c0822c173e20a1b4e247a39daf27a47fbfe97b7521b5ceb98655e38d52733612c23e3f7b37309ef81aca060ac7d0e5cbb00291527473c0c68856c2c25ad5b649f8cfd01dfc32ad01a8c5cb09d57fa5d4b09e013f94e7bf0236efed765fb92687bd89055a66fa72df2612d34ae33eeec9890eb841a9a69ffbb81ff25933d4d212ff5372a685d9f05cc3ac6e5a8a9e9e7efb54990ae04acd29d0ab72aa149f49787a81d921fb4b0ec7d8b050fe6542fa7f4988e91fc31f64f549b836ca5594991dcfc610d5b298a1cc97dcd7c295fb6dafda8124a65bc320248d31c29870dd9563554ec61378ac7f901f88187cfa7e95f1a4c6ce819d0dfb35912d33193888999730e5cd04b471cedf3c0b5959f99378a6e9d9031d1ff71896112a1790ce20c0f69ce28b96afb924400436ddd5b65bba62ede60fe3ae5319b6ce161327c1854c9e0679a58c3f04f68560a7e6c69de464cbe8126e797b6b891dc8e636bdf8082700d474ef8841a9301e48a9bb771347573b627f84c56f80b3627f6203cfb22139f84836dddd7140bc68f63913a32e4850f6888801c92604e62d3610fc5183107d551005013aa0babe1964d10b92185008c75f0b035162ee8ed82df3b73bb5fccc72e439c73d67d2545eb8d2587c42dfe5e83bc053b525055951f00db5681de060194a3865dc57736ea22b14b53fe1ab2af3ac999c475417f1b03ef4e483f848146cc41bfcfb5661ad809349d0111af2fda5f40107b9037276f30df9b8ce9f7dcd9dd8141c729aad48a263d0fdcec3c77a11a40b62d550af1e1f3385af647159397e0b68b82147bcef51a42029652f14f1ba154d2e840a4fb578c80ef43a3a5de34150efb614dde25d8fb402d2b1077876c7deec893824b3e4bfa725771e804585e8151c73763a1af56b2f83b36d2c1b9bd910ab3abb3816324714fddfabfcd79af3dcf6a7a5a4fe5debacee7cebf891f35d2c756dbfe5b39390f2a59fccc12563f67e059e7f0595abb0a58cda17884bdfe075411bdce61657f6fdca555e776ae79994262f4cff669f34f6fe8f92fb61bbbb6f68db147b850a4469cea496b557ffce647df67dc693344cdcf900391144f8f2433ddb290a7701bc66fbca745216ded261dd192a48dedb70e32dca0b0e7fb146dfae56ceb7f6f0fb6f1ee081df6d8e51ada44d89167f78b0ff31ffa7b9faca97f1a64cdde7e951b56ee2bb2c7aad1ab011b43dfe6fdb092f8de6632578559bcfeaa37ab79c551c1d7a616c7b2b0ff28c2ec622993d6d55b2cb254b6d538025167bab3fc428023d7e624001ffdf71a952d2adfe70f021ee0e0d6754333a7d6111cca31a9c550904ef49e28d0220f295795fd5835e391160fe49e18756e40dc80122133dc399614251a0c8f335efb10014178c0f257a672626cd65e24a547a0b5fffd68a55f6e6011fdb23f1570adc0fcdd6c009eadcc85c16aafdea27a1c32456836fc1fa3bd12c2ede8d31edfcc609a446e7bdd5a4a357079623c66fd1142763a558bcedb6166478f6f56be294254eb50310cfe89133548489dd83e5fc0f1dd522d74994d30a78373b0b9e7709fbf5006e31821d38dca5087fd0a470cec8fbf82355c36faccebace80f418c0868d3ac7b72df7e2530a2066fcf0eef04fbfd5a87465f09a1a6254f233083aeeac8c92a749d07f566c7f027bae4b7c8d3a8a0a1c804ba2f5a700942e2ba014699d9421c98918117c11d9ac40462465dc9bb32bce2f9301c7bb6240d9e9483a69fe5a649c13446915b491436217cb76757b977fe3015f8dcce9de03774c334a0017871b43633ad9d39c0717af4489bb80f19ff54186cb6c0bca22d8dabedc06a18ae12311eac1a5ebea8d02c58edba545190520623b8a5c1a58f5da2400fc85a95b6738889ea9070715ec24cef32c215a6de2a8de534862e22c2da019020bd3ea07f05a17da61b0b8db9c1e8d29b6cb022af38fc3d2076741241f1e7e80467fca24556845e7a8832e4cce022ba9910b1510e4647aa95f01c1f2391c74b6e8c784cfb872b4cf9e59dc46cb2b27b207d77d00225211db6e3ce840bf628429a02aa8bfe41771ce1543138e1e5d99d2d3b9294ab26701c02d8914d5f139cbaaec363e86a59ab9b5121a9f15170cf15896a0c51b2c745b8bc4f6548bbc89a6b2174f0e6e7b40eba062904455083d1b7be606c4fdbe54c75ec27d56c307768fe52d7fd9cfb8861006f9b5205abe4805f7f7173413f7910b7af743461067cd962f561cda24ceaac15717353e20360c0bba8f01ecf2a2965d6e67e9800f3d98bbd06f3f10c9e82dd765729e593927b1d404cb07aa621909c330e5c3314b452860c89a4f1e79cfc993be3b508d09c5da1bc3f9d44508ef960945290166c705ddf223bee493d703deb459c36d1337336c2d21ec29a93f43f170fee280fccdc1b4a751721e248b4fceb424aa5d2a41eab94d44465159835d525ea8c71090678717b7fa3db30c54178944fd77283faebcfd45b9d2e277ca5bbc36d1af0860025f42796fac31afd88648cf0bf5b16ee07a4704e4caf658bbf87e1e285cca3c0ba10f3679e4ab4245a0ca3605c6f7425a44a45600063aa355b95b12dca87dd4b2ced1de0ffc90dee82d88f4545904650073012d0ff3b25c5441627a46699ca619964a7269375ef3b8b3d0957a1daf0cb4c70f7395ab6b0be96f298ec407b405f3b1631334a278a2d421d3c94ed06773852f152eefd77c50b2f087cb4e82c0a36d10af6670cabe8020aa71b09772a68bc75c7adf23de60351cad05fd8bde9cbc3e9289cc3dfb02801dc3ff222f630246107e8e0bfdb090d2875e3d78341faad6c6332eceed26d1ead7583578e7b776fd10b69fd394dc87a61173ea2d752312dc3453cad22381c21557d34cc583a3085d3df9c8d6650dc4ab542a00f362902433289b06e1f07a7c5e5ca66c86ace5ef7fd24bdfea255ae6c5fab839fb685ac3a725d5afa70bfe86698cace6a5ebe6c6e0aad3962a8776980881b525bd976451b072976d3fd2ae330a9d548c84b0c29f9adaf46451b993a9635619f9ec970a4975ac18787a242be19c5c35628d639637f397f44de67bcaed7533511bc83c79c12be1680a1544fae906eebb118269aaf923190f1e2ea971780376dc1b277026ee9d0c06666cbecbfc5f620e819f6f669b63ff7525118635ae6626bcaa4d48ca40fdad7361a83a19694ab700e5abd6d257005a74a7e29aa2c2efb7de4d945a79c0772dd468a2be59af40224218cef37f992d5e324f104e928cff52ea11368fef8450e07d1af083720a4b7628c7fb318e73a9f94d3a738a67cab35081509d6fa4d544fb6ff916a0a5755217bd9fa67fe307c9cfa717642f6b55321f3d6e59625ef617bbceb9faa90a52605b26afb1b39dd4b7df933410f6bccd766297715548abc01bd2842c99d6c69af6e3047b86b143a2e289463c3c1b0a570b357432a38b29d1ff86e9ecf67824e0ee08b00c3763709738ac69f7106bbb95de457aa816f711ec44f7469e39866bfee53cbb87f00a10725757b6a681d76aecfa7938798d36996ea5b1758df025e8ee6bf47afe606a402369bf4ff992b23bee5fd7d718d49e1888b6e6ee7c193e867c223e81995052daf11f33ca29819490457a9792a4bdd21078b5a0675a69a2685dd9f1af7665518ec3c00db02f40b7f73f97ce71f615c2a5ad52f1b8af85ab444588b1fd9b8ded96441e2993ddf2f571c25b562a9ae839993fc12f465f85dd4a85a903dd9e212144eccd92d0be83bb3cb70edbd5f1a9e78ea8f685370274119c68dff3b672d30214bdce00df8ccd07cbb08c1a30946248f18c6e538fabff7fea446c85b1c83a042a833ffa9ac7205c8fe588d0fe0940000dff8f9e65a988ffbd1e73ca12d9a8368b78eaa3199ee59be084cb6c9a7b794df000000da212e34a0bb8b38123e1d4011abdc64b763e38f8f4b49c8213b9644283d61ab97926f0be0440dac69d310335cb6ff3037e50f28621a7e718d49cc64069c9ae5687e9cf42fea44c596c8bcfc2b16e4fdd912dec11ec37fa49fca7ac4356b6dfffac1e02018a83f3b6c387c4dfdf2364b718cf41fde004a5f4300f9e7fb72bbb37fb3d6f75ea722cca95f093c370634310c8575352ad90e882e49f1201413e36dd67e00db2b67b9dba3426bc33c86554039ad18a49cf56beb6258963592853fbe6045a1e4901239b1880634ca7c80b904eb6efc0c2e37a0d8bc9f3f1581ed8ce7afdbf184675138fd367eed88ce97960057fbf97b26fbfdafe45ea3fc5f0407ac244fcce9c629a41351b35fb37243ba7aab9dd0341f8869c4a4f496857272c66c6e9cc19d8f150df9fa9db88302b42afc372cf6d1271fbe428a661998e955bce7ffd5dacd9dd51a4f093e60c5e27fb40d02c61b436365c4ff128895fbb6a67cb6982c6bf1430d7d1079fe5b8e88ff227f51bb59f4d5b9eff104cff80d8ba5ab58872fca9bc93e696709d2d022d12422450afc2a5badd0b547f06629758bba11eef0240350690e257ed99e0eeae02cc282133c3ae29a014c0a5ee5c4e2b760e0453651b2549fc6338aa2b1585289d7c3cfac4dafbfc87ef98b53287ca73fa3993bd2204916e5b7066c1e70cef87e39c6c94ac8cca64bf9e649b400c0a29d1a4880b9acda5fa6856c2828a1f665deef17ec5868cb5f73abae86d6a7968bc91921ec12796aaf3029b9e2b5e2d63cee27fa0cc8fbf653aabb24764aed3a6796e536b500fc4b9ff397635bbe377bc396e6e4f88903a4b28e36d88f0df09723e3dac57ab91c18d3de81d9ebff640604e7485cf5c75308b5ab4a687fce91b09dff185dc8705207435eb58d14861224ea74b205eeb0181cbee4f4b0090ebb664734738aa48aae4d5b4e2cc42244afc108ca405227a17e725f51f6230c53e633d3939c2d203e4ed3c718a4049e363423c762b6bf4f7beee0b3c5814e34bbb4edd4192fc794dc6006bb58ca74d4fe593e0bf5b830ac8a778a1faa0977a964f1eb5a5a73bc83c25c6d698ee700a43a1ab85d71bfae3f1b4bd7e3c10f4715dc2b0c794830a5d0e1e6a456814d1c16911b0efc6b301d00ade49cdf4504958537bdaaca526d32f4977fee84301327251ad0e9462f49e74d45b761f98f02266f352bb051212da30dec5bc413a68c863aaa16cc024d2babea26cefa5637a26a64337c9629fd9384de4fd9de23cbf6f98bfc7920ab56c2deb2e0b9d66ca8b303e4ff0a85c38a8edb5685f811dd6644067cab4a7c59c91c4b5cee32f4b5314716d6f347a2be8081f449b84561fc4b81b0a1dea93a5f30ec4da94f220e3205f4615d81781b88e542020e5a2677a6b97f59d8494e884c6c200cdb3bb9d5de1aaf9b8ffac6b625c2111310df92220c5df5faa493d5fe53e7c700365ba2fb227f1b47edbde687a39412773aa06386390b1d362cb82ddb5a310d40ddf42cfe408912ec31e139c855a4e6d1522ea26f4b61c0dc8d4c51e725928ac65f07d9d6e348ff4ddd33f61b59b1fcc1ae9eb6fb9fa87427dd4339a2624d7d3bc12a3bc9858e96f70fc5bf7ea54cbd94971de44c421cd96e68f9fc29ded48f4163515800f1543e130485d2e23306ab4f840fce8f11c8c784b23a973a2caaddda2c060fce2f9905e66f1337da32e27d13cd6742fc9eb8825dcce2cfafe7f3b7dd3c0d20fdd01124b8fb60ec882cc15e51056eec1598dfdda0caaebaf5dba8d04dc0d777808fbff6caeb417eef096f500486d8654aeb65ddba4bfe3ce4e71af2374cba919f4c46f64ef33acc337d9875c2c0b9b96ecbcf1222299694138c66638e8fa735bca3f3747d4d75a22338476e851e4b6e1a5dddfc0f3cd9dea549449a467624c22eb8973695e8b11df462dd05dce1fe7a311fdda29c82872ce0a56335114c4507c351d3e2c8b985956abd731f01e92682a19e5bc023479fa23a139a2281d9cc511038b34a648e560ba279ce291c349243a6f28d21093496f9c38169e23ad2f1aad2d890e576a4093025fa255dd9e13cebd731506bb4002dbd3360ec86a903491c727e93867ba98d7156916460bb308286a47b28e84835be6451c2c6448e66df8c2b783a794b8a8f07ec00705df9763c800218fb4f76baa0c8c1cdc1c581e7794c56acd5db6c9aace866b10f1911b4af8a4ed07f1d64a8ef323c166fa6237507c21812a2b633572c7ab39bf220151ff5f7799b7e523640b955c4110a5ff339497809731699a97b3062616e9b8b7968c59594f357f83072a0a18a3f52c046708b8d52dc31c049bb0d207d19a50f3d24d183b38cf3ebe0a0ae2ff4555d66fb17d9b0f3b19e9dfc82d51168cf8fbae9068efb9f44b2be24772219e76d969ce4586a01a7c48e1d908ea776bd6f7ea0b70efef93f714aacfca64488eaee54f8c6c4070797e07de14b8163888504e998e7f539d36fcdd34f75cec63c3802f43400aec87ed4c18b1ef2f50c6eb04c3d3c1d6831d19adc73714c5ed40c5ae36229cbe24ae9190bc3564d1592c5af711453e17706837f462461e04dc7ec4a341821453a871f085027d252858d06fa6497b0ae20b80818be487e26d4fed28dc86afecf2929329c709008a0cc7db7e2d2ecf83afdbf45f2134d756b40a9a2b0e26749dcb30bd2c130618e4e472730f30d9349581f27ef1f4dab9527c1e820628557f53c48cb33cf1ff398cab2478bffd2c5744a1e318f4a5279bb61e1b4e6e0bbc48fe3421260d6f7b7b194e8fcf2720062108fa40cc6a081ebb6d1f3dcf14a3f3d8040e24f2c33ca2ce86c1d872695f073c9c41a65b8a140ac1685c02298a7d95eaf133ceff0258ae6cb331b30eb804fb0067be95b4bf7fe35224e22dc7fa735eec971b6e8913ff93f96485684af1b32cd6bb481dc7aab2aea05ddc6b4c9fe13eb503b9587084c8b9bb53abfca09600fc427d84324de5df97808247fa81cc2897cf4672c36ca87d974c0446aec4c32a07478656bc6e7ec5188cfc9360623fc5c56ccc00fd3dd2143a274168db0cd82617f8858c7e79c1a7a6e708a7abba41aa488fb135c869562ba9452c00dc452abcb27f093f91f61336f0c34436ed8da436598ed4f4523c4d468a4e1cb08f6f5d8ccaa2a857408c5d5bb8060934194f11a75b65685d9927ec1402f4782222a93ac77815f1dc527272949c94fb11ab92d68c04ce33ea5ad6e1359f9801b2b554fa48f0cc3f079d46c196cac70bb32010577cd415be0cd85b1b834d78c426d93210e729265b878d6335c6070116d8d71fb0a4e2090c8e7f7ede4db9ac3feec8df31b35975cf0f9e2ebb7e4e23be7f918faad61aa697e5d4c6fff40acd6b0dd9833473dc41baedd5e3dd5aaae2203537d212b97431e72d2a391ccd2f41b7ac64f64eccf613d67c8cd077878cc8db3db3b9dccbe0cb64c7829d0ae1c069cff7e0aaf87a5ee2218f60b97d8c470f7f37ba2ffcbe1f2da492a71b3277ed182664202c5b61e31c60bf52dfd06f6594ea355bea545ddbcd9d9ecc1571527cd627507e69060d48b3ebf333dfe2611a0c3686c1e41239fa59530f68c953d9f8aa3388babb88b410cc107829f180221048a2108f684500887102e86e6109a8b211242b418e240124222846431b4866014431a8436e291f6c02b02c8a19310bac8a1bb107acaa18f10fac961a01006cbc124844c390c33876c08c30d61a41446c961b4398c35855c3114402812cb7800314c8230452cd301c43203400c2284123194422817c36c0873c5b200400c8b202c1143253c4862e4e977f53d0c303d87a010434989b518e1acdf3fec56aa5ea39b1d9f8bedf93af7421d59000b2bf43f833e807a725bd3f91805fd0a80a643fc5d178d1a0b7001e384ae2f53d56a5938d77de35db406fa015464a052037d3788a7dd5b93aad922d2156e157e114fadcffc4f2f45004ea86114a32623a1726a01912cefa747718db11294867910f0c4914fb1a29d3a552d31ef874a979daeaa86391dc4dfae02e99fdc6eaa6a27281ded4e40903a80faade577a52148f63b442557aaf2bb3990cb35b548510b6527a82ec290dd58f6026c5abac25250f1c7d53479125594137afaee15ea421b09cddca7daf3348fb8be1630efb535a0522fdd840a55a5c98a1ef5b59d38d505fab6a07f42e47368027c44a14c94aaee7948bcc699ae5a72d56540b2bc94012a53b6aafce71753c164d5e79777c7a151017423447ee401204114ca5c5567b769cc79dabd8665d27c730b01bf49bd4c3b925b0b9a39db4ad6715fd35b0d325da35855902a9817e02cac05cdf41f44134cd1dd366e35ff8b9a31a8549ee75bb900c2c53b3441e559c16a68441326981c00b599ac8777b29386119169fe32076c5245c33be53d31376f6e09e61b430468260f8b3ee3488930234895d6a85930ae85aa1e1428298b00c3518a0bc577799a56aa72080d28f30ed0ab72c8fac527084630ef5f0365d63e2f5dfbb2ee1760cbcb0b4895d64700e72205aac3215813e12ab0735e475570f7e52d99fb340052943f125bd27ad30bf7530fe9aabd961a2b7c4f4b0c8c2d47224dbc118445a12ceb9bbafff03dfebe37ca9be5dcb5006e928f6e29da5c15a578f235afd3e7a971611f3ae982b0fc429df70640a26b907956fff65f1cbab954be23f07672c7658078ce0a1859d38dca5aef81a102facc9e55f03151ed04eacdcdfed63134a9b8cbac742ff7971b005509520d6b0e9cd8c4aad49b87cb5f5cacf515be39781be2a041df65b9804b1ab05d950c94d961d33434f5312934fad023a5fc41be8b1a84f2d0673be5aaff678c07ed7cb14b4f5b6b58074a224577868a3a411c5deb2684553f5cc4c0110630ec0250e888fc5ad1071264f0e759046a332a8fd93180ea77a42d71157dd4ca9ecaefd7a877d57253ba7bad7d8d9e093e1b82a87688ea833860fb2ebe2794559cfdcbd61a3bb366f434cb85dab401c723414549a77891beb87bf8a4f18ac8ca9419a578d602361c71aa76b5ae90b111b10e835ecff90d4b0fb4484c11d50d45a433c659440d87327fa0d78bb7a331700dea34ab49037a0d41c863445e72e7b3f9b14df5ed2250ab1119f6afdc08389c6a17643f2a9f90452db4c5084b9ac0c7e423913a083f57fb5bc846e5bfecd715fe2191b288ea05a9d1e10d340d610a65a1cfb9615c756eac29b66659a85fa1a0c53e5a8a5fb0f63fc67f327a2ee7c01ea65096dd05baa7516bade318fd31f315f6b4fb5e67a2ad9aee9bcb9b4f4f6997efef4f49fc26f42a44e16b1960a8285468d34f46f5b35c630f804ff2906c02dbd5baa22456581557c2bfeca16fccaccfa1880c0d0a65b9e9bdbd8806602bbc4931cb44596f6fe4bb38143fcaa5d4db54c92ef3c4ac42cfdf84ffb6bf10f397550164122a0afaf3c5ae98cf232f7bc90e58c41cf0fcb7aaeab0fbec2f1e92576934a026416a11a421700c210ae55035654a445710c39135aa1b9a70ad0cc24ff5fe5dd4a245dd3f01e22d3b53f69416782540ef431455a201fc4570a790cb5767c53933829ae472cd171e60668a3f612b1c8eba005725d116077010b9fb129c16dd1c42997aff298ea5adb2256bc270d4df5966ea200e86ee10350b50eb030ee8fe4cc49e501611aa6c96a2a1358960ba6aefe9e64dc080142faa6a375b8dcc140cb508521b22430028946587c7fd2c6e6e00c38f297dc50344bd92d07cc8232dc6cbf0ae24b506d946836a97b068ae52d2d36d78a47576a3ed4260fb1ff43544b1766d20437b425976a63e907d5c8869d42acbb5b3064049f2916d92ecde36a69ed72de97ca56970eef0caad27e453a45026da54cbac8ca91161ebf7a6fa0b3099e40ba1d0c044d166bf5c619a1f4d2b9369ad4255ef72d31c02b26ac25016f406adb287de3c451f7b2812c07f3b2c38c2ac237cd9dea7167aa73150573191bf017cbbc9a4500e5523ed2792f7304e0ea006b94e44330de08b890f03d2b4df7e167ec74afdf754667f165efa4b55ecfd8ff9eae5327af78ec95929719a7a7217340815dad541866dd2fc790ed42e1ffd1511c4dee2b5300886ea3b52d98953f51b806d12f9ea0d78a748a5fb92e62fde08f6f232b9099b46f89944e6c518604a298f3c26a1a2a06b2f7665a70bf9e82b75e8a6f7e6c724a0efc9570113d38e077d0b519c680048e982649eddb1053d575727734635f2b9909735e84a13ee3480c71738fc3e7339972d39ed70a05091ae6bee7075306940289a99f004b12e421dc493006c0ad9af78714f4f7ca0aeebff42e2630a971aa5c53db93e99b924b3c55e1eab9445290cb8a76b253024cc29f8fbecdbf45e5ac99aad3f519ef9ff26fca5371cd00931e2e39662f5a8c06485ea5170ab5b00f60613ecb67bc781a5864339b6bbf5efa02cbb61a8d57a532c3c6605bee53a71741eaa1775d76b1606dcaf263f5085409f1d9bcccb50a97a1d994c346303accf4f546f236b4c2b3d712851280f7d5cb442b4e577686c9b92226a38a048f5fe5da3d0abd0aa907c4f7796c003250c9d4945edb248270307c199f5b1e794299cee9bb4341781232ee0a3738a1139fd6da24980110afade960cfffc455d861d92f8c083308e4da7bf437f21f9b7bc3cfd9d54e58e14692e2503b2295e0a6a1a5aab5f6899e35b536879e453340965a12ffffcb45f13061475c83209366bc0f195c8101f81db921e3d2104be963ece2e648b1a142b1b3051132bdc680ced10c7290ba86388ba6c70b0b529946967dafeaf947f931afd3b9355eba1fd72295652643d748dd2294c1d79b637cdc9cb015dc096aee56dff86d8967fe5b32a02f93cebd2903853bd27948375f738d729cea0b2c313ae869066d217c06001e40fd97af69ec517e5b6de748645e2f5e2e7fcad963643b5e61068c088ac6bfdc19a9e36424750f23859f8b19bced59e4dee236113726dd46ab54fd9c5755004750f52afc21b41a38badb7ee78591c4324e8c934abeb5ef4fc268a95c6a81c34f6c3c6adc7f7d555f051903dd05a67385cb55d723774e4013ed52c14d43389fa0a4de04dd09e50d6aabb4b6dc5ff49eb3fd46c67b994791ad08e61c776419f96ac9cbcd5dd560a4c6d9096a1ff4d849951cf10a513a5a8fba65454a4768118923bf5c968c0b62992e5582fe34a7ecf14a6bdca52e898500f3d13738e70b3fbe357b4f54c53163c3146bd42d3d7db0299607ea6c9d96286f6b5e600a84b7092dc696090e477e1af7fe3ce9f9968591ba7078de4f6cab0883132f5566dbbf62cbd76d27b18a959dc9ac6f64c7e2c4a557aa6304527044301d9bf0d4497c75dffcba76c5ebaf73f0dc794558870230f58df0c325028242d1548da95dfd123515fcd5377fb7c7246f25a9befd22c816db783d409f37fdb556be0a1e48aa3b9a3848f84bdf22e744bd5cfd55c7d5034b6f813acbc2a24b6771325b0790beca83da12cf4ede9e92391c48ea31e592ea53500f8968a1281aedcaed615e6745fc6a695ed28557414eb810e51d82779b317c0d64df09e50d18f8239e16acc521f3d50fa50c8ef926aa2ba193d3eec69bb284fb81adb4944dc024244a1b2c03eefee9d0c182afa1acb37febfa71b481577bfa177c5b29b4cef11450e3634193f381de85e65288a3fdd7ad352e1c2cc5b65690edb76415344fb32be56f52a54ebc07175be0212e3a2f8498a00a4f673077b984945ae3e72b63f67a72edafe6ac777131991e18962d6b8eb566d4defd91f28e752bb748be7033df67e28ff31bcf0dba785ab4235764b6fbbfec71ff068507dac4b6b60869075862b31fe0a2b88d480f8ff57ad2acd4f9c93253a21ea03b32c22dcd3a80e246aa174b3546a7847bec57abcb67f00ff34d8930e2f42c213bbca19bf27f23a23a85f21a6e885946f021eacb3756daaa8b0fd17b2fd1597b753bbff9d61f23829408cb1030ea4c9d88c2900652d8871c0de6569028918089ae06e0d4bb74c02d90e24298bc1fa3446973796b6ef910068c9c2ffa4a3458cdf144571c9445d30327ed7f47f3e34c517c8b11776933df7be97c60cc1412996ea28b6e059b4623321684d1b1b26df9c2e1bece034896596d8bfd0ab9a12b24b393452288be3ffe7ca0412dfa01d6ed204f584892b21994e75007095acba15dd5a3533b5689c63ae1788befef5b4f132af024bbb8cfdb73c980e151abb92d522bcb7986ce12cb571d5d405ff2080aae8e81d630a51b97c2460f587310eae2a77594dc97fc266b63d82bd9e18716a44d4f562038b6d445c1327170f0c41c0a9e56ffa8b497197a6eb141f728dc443ea7af53187617b5f4ead6b263343f23898f289a776cd842592122a0b37383659083cd2e308c226c09d89b86bedd6d060b649d466e1ff157bfdaa84a23ca935bd03666fabeda372d260950fc5d520c6838ff24f350ec5aae9ef46903a0d1ec65be84cb864d1d91e2640dc35444376d82ea3a0780e306788007e1b112ca50a894c14abcfbf6d35a98dfa38a1340a4a8679b2d33140977fb9bf83e3751b2c9866a2cf080d3319673f48ffa44e9fcb1c6f67574cb3aac09f2032e18266d6a2d2f30da8da620ab3ed54615170ba4e6384f5d266a925e40bb60bf4adcd006364ea223fb0f2ecf2203cd0362d110239f700871213dda25bf8160c040810ab134dc9bc6e21e921199aa02b3dcc74bc7d99893aafaae8b66da84699f80be98f87012db54526e48851459e3bf94d6fc321753f9311e8dfae36513ed1ff47e5a2f1903bd1a3766592728c5e22258b44e93c8413ce5a82ca416c88f750eab7466e9bb03781621f73cbfe04601bad0c8ad7b368bc0c7a00a53441cc421856841a72769408c490ee14cdeab3fd933680e12bf0601d07e20a31839d0e3334bce8c62d80b295c207f40859e10ec786184da943f709191909c269ce5a0d19a4764af4a7bbcbf21c04ed8bfca2e781bc691e1dad078494ce60aedf21b037e1bb7aaedfe11497e85af0a8fc06f4cc896351e87c840589f687db027e7dc801602cfc6e9110cba2b5684e39d8337b8792c0bafd83075196b5384b68639431eb5a74651b3fb195a9a4da2bc7cc0d175e3c3ad0f17b1dac70099f83f7c9c5eaba507911b00497576ced85428be1b0ec0f9c421f409f6b83f38e770a32dc07eea7bfb04dd943b748d9cf43f98c2c338ea41ce618a60e60fe916c62b35e8f8a18819d2c28aae480f570f7df1dc054eda3be9f195a4a70e12eeb10f2d06919869ee84c5337bb816b3dfeab7b1a5c083fbd97f0ce722f42f2e7fdd88d97055c0d4b652142849710b7b5c967a1bfdca347195f49b0e945d1480e3cc444c36311bbbc5ddcf60855ed76b8a9ed962b921b15cefb44edeb75ea44146686b0e9363f97b7d1ae276fbdb2c885a7bafefc88a2580f96a34f89f2163935024f8c0fb969899aeae360bce21b8e4739691888a7b56fe0ba1b26529b5b61349b65064169d1c944f0871874f8f07d9d5236318e7a7514bf0e29fee5dbc51b73c40377f8cddf172fbdc0cf42c68026d662d35ed06c37968e1ab83210a7c476e694c25f54643e14579793303075d3ffcd73d5d6e41471534600e7c5f2c17991988bae38a95681b4c8d75fe0edc854a83f0425ee8f2bf2babb3820ecceaf3f29f597fc2bdacae1f5cc2445f4f32fcb1e0339fd4fa6dcdac8bfb902d091ace138114929914b2b39332feb982f888afcdd8e3ec9d0dc095b315466fa6423028b1fc590677087cf4c6954e04cc42ffc3a313f9258675f5585e030ffb720119c384d9d8063c8fee7b11058c5df190d1220a737d9c61d53626350cd550fbdba30c4d27800544f6fed4dafb0cb1d49222e8d12dfeff2f868a21d742e739b37f3f9b02feeb1bac01f192d6fd9fba4ae591f8d5e39135c52112e49ace6a4a0b9f4599c814d4f5090152c67253bd61a24840bb06419c3e20564d19060a5865712cedd67a33cdd3f1c2b31296eeda53358f544b9fb4e835697d32610f36ecad5d6e4e05849c6b77a26e14fef90a6be4f539bd760e52b11551d78638866539d2c12e237c1f7bceede2b2834be691fc383ceee15beafdff3621a6a92477af41dfe8e041208f905b577f0973cb7fdb571b7cc2d567106e0bf9c55c5ea7ed0d2d9c13f5c0a1ea3c7924a62d635c449dc12882dc0d3fc293c58dff02cab041df560b374d4c554b879333b7d7fcdb0cceab98f8238f0691539e4614d080604adafc8f254cb82ae4f444d85195eb61c8be87fe8642cf858c5c8dde7dc77b203819e7c66e37d0bc49aaae93d2ed989d2bf36e8c08fd591452cf84945ab8f566613f3196d0dae9976e0c82c39c597039015b252972b011aa4612c96e075647494894004e17bcce3c0093ebe2df71ccc1caf092526d1a94c57dda0742bcbb8b464fbda91eab0177d3a1e62a3fcd276b71e0ebc6ffef526bad9f137a2a8fc7adaeeafc1a9a75f1d7bc29355a6cfac89945f658a20a68d9f24c79e246e95e76252d7fed193563456b7142eee2cf947398a3a4ce20b45432dee11b0445539be72bc8add6fa23646aeb6515ff261b4928c4b4477cd7a9da6b281abc024af68dba44cb8c1bf5f9b7c7f0f433ff9e555e97351d35fb0df0da351946a770a2a4107acc1be402353f2e141f4e912e5c6f9d39aea4139867cbb3513bd1c2b33fee27f40cb0d67d34f8b80767c3cb434016e6c8eb5831957cc787cbda99cc7d190575b15cd3467171f922e2afa63599f4cff00344e0597e42dc36db2451a3efc265e2416c074001bc11610b7487256d2e2a6d27988b4ea42200486fa45606bf9be334ba05271146cebbebccbbc8e65ec514c9059de88e662a77c50385a904fe3e7430b24fa73927b8f2efcac6c27d4c2e8ed61d47a12fc918d9745de2d06728b779c48a59288a45771545856b6a6464ae298fe0c7841ff49cb6d8abb29b25ae19fe974d4f6e4cba188aca86700f183a0dffdc6ec163c8af1ccc06ffe7d9d2b4d3952a6edb4a2f9d0cc379765b84f5536c8ecf012aae8cf951fa5b6f7176ef872ebdbcb84071247eab45ce5a76ebc731bb6c6e18e1f30f977b3e31f67479cbc865735bb43f845d9df5473f90b3af5567bd23cf096ef064ef6e5cd811b1504e974ad81b5a9028ef80b4df3b9eb584ac84cd307711adc6dcd8c0229eff7ceec24f5d73c9c9eabd57086ad3ab1e8f7ec4c5e73f6052c349e46a7479443bf85d47e4f101af74dd62bd475217f06582372a6a62ff21d18b607b383fffa171bb71621498cda85ccf062a2daeae0bea8af8856bb5aa5cbbae5aa034e8ce47182b6f98f3646fb34d38c2cc1aa6e209d611f22c34ed92147270bee71535bf31687bea05c61bdc7e2cec21fab042ce92db9da50b4398d2fc3c8adac56c8ab124fd6032b1e59b3ed6222fa6102e2b2d35ace428afa715b3ee83600a4ab40830334a183d3286afe8e9a57fc09824e8c6427d891beec833f0ae00a426d2bff47061a1c8d2a8044a7e93d66c336ac830f496407f08f5ec3e434fb140d4bbdfd816b19ba38036e1000e82eb1b867860c13698c57d8767ef6cd6072093ca1ce8f109788ff668cb31bd7ad007c38ac280550c3811ffa1e9ed27b27178763e0e12d056bb3349c7b33ac3ccb29ecb3f1bec34a3c8c96134e452c69af71aae397ea9bb4f0bddbc2e2a52039737107bb72d2db62fad4f774226f2ba21a3c68f86798849b0b92eccd07ff6447a4325f45305c4713626ab312fb4bd964b18662896ef45a7364fdb0508194c8a6d57c49a780542be27ea828303ff54f082cefc420941bfc89b44d6e8c3dc1544de414dfafedddeabb0de0a9b5f32ac8cb8a5ec1c5594bb84b2fba5ce8fb1f415525ec7fbe973eaf546d7f3b52abd7ecfc610f12ce11d2785b0cad75085def26bf1a1d6c021b88ff1acd4dd956d7c8c86f26a0ab1afa025ea5594ca5b456a7d44c2c9e8e29b18dbd8e14010627d6d62955e5995d762c6e9da85f20cae6b64e873c58c7cbbf33a7dcaf67aaab86243683bf5b51950994865e0d0a60a6285fb00e1a9aa553757015709fac1c45fd6d5300ee88a69b577aa457fae0db5aee945c9eacc89c72abcc1cd0bcdc8af2fe9a2546d08f942b72312f737bd1346f3a1599758ba3992212e78a6edcf66a212d3cd7c8198e82e753f045fd91596458213400e5d70168e5847ead48269f57c45737ec8f22a4fc5f10bf57e4127ebeb4fd0f1557c1a126e5ca7dd8191aa9d90756a26877a77dda9c08e3f60c4d600cd7970202b42763e5a46e2f3de8d676ac932115b2f13bb87edd0f5bd3d80f5f58b25d774187a6e921224d718df994b998764b7aa71c4d0228a1243c012d7dcc044ddaf04762adec15678aa123331ea9cbcdff902d186d8c6a619d82f7f88ce1a0b61fc4980c80017cdcb94f2ee6d6fa5d10deeb75431b15564b7398bc4daaff3aebf7f4f56f6f9800af27b309ea06e62aaa0e2732a8301708d902eda6e13f9a2be38fafbd1a748a3b9d50bd5fbcc63e81bb021b600d3b158bd71f5897af699b22fb11ec3b07e73a34545122e2d3df58f6d09d6c4309e5bba5720d2ac6400ea6227b3941b7d19a3c9bac8211c4eb38153b1ded251b5dcc886ab9f6a079dce294ab4068fa75e4b63421aa744ad4dec4440b89bcf4ded043d3ed8a11629eb5d15cf18d74fb808a0fadf91f8c0338091f7cffeed5dbd769c49ee9666fbe67999ec3d537e244d15a3164826f09c9cdc7c476361854f3e4fc607e18e88168372a0d84fd23ff62f7bad81c85af5846dedf3eb2e7e68b080102e716e8cfa2c3b7016c18eaf531cb6998fb0f8ebe1455bea0ceabe4b3a6f987f435e839c1a0185b0e6656e3659b490a89cc9da3d3c4327849e278245f1173cfbb6e42bf898750e4f27dbb043fe2cf3fa9eb9f023f5471feea894226b7341ce154d4f40a970890e4085a6ae1c7d5e00dc35a6187dfb5b85003afb93c82c53072239a5bb85c89dc162a62d75623c4afccfa5e5a5a662282fbafcce3a19a33adc3fc270286764c1a04fa0034da5768961c8a1797e296f5f73cffaf924ef4c67ffc65889a5b660fe13dd87fe312b4bd87cc596a5f1e93ee8ea9961881bc418f95f2405b69335ee199d3d92830f32e1ac0acebf1a899b869e510ea23b2f706be0e9fc7ea244975d2f82336145c9f533596c731bf801e8ed3e1cff9836ac5c60ecf900b2b268f9932d04f0e168e5f11f538c721ecb43bbcd36985ed87476bbf9ab719dae7690be513125b84563ef78d085adbadbf0e5188578238a35b2c012226dfc4f3f4e2c0c54e2dd6e7a204b576d6b183d8228621bc99d61007754633d063f14d01f53eaae5692cf9a99259300f5b6c6ae175fa4c5e36f933e653e1a256fc9093bcdce4e52b1f4e70dd6c22ffc8617b8453c77ed6417d7a79c5d16339da223f70329fa595dca00c52e699963bb2655a09d3fd6d7af80417a8fc00ea87e1293598e5027ba40e0dafc52c599481824944e8346af799ac19cf18ead14e3042b916494bdf24b809534d112cdef10ca68ca9d5ff7426bd20f0bd2c2e751e5e5819f3a23fd27011034448a35bae140025898911ee3dcf9685a3e45e1cc196ddcfac3c7f2ca55377820e8dafcf4884466aca1590fbd23090224938f4efa18130b40193543fd8d121b28c81fa4d8573c11d34645c21d72a8de02c102e0919e185e297a803c80e67e5913db1fcf703b35a4189206490811f3c360f2a693d0b4e14433573eac92efea9fbe75a42fa4218025e9292cf7c032487af13bb27d0f17c0424ef67cae95b61146f8ca345d11f3ec3a73aa13fbc0ee55db5e09322dfcfec3f538ff6320aee44421aaaad499b970516115ca40b5f5606ce3bd3cecbc972be251ea2c5296569b94856d32c3d1dd416940ca7582de88b6f58c1944ce5d4ce2ded1889f51828a388ceef5e1d455cb7edecd088d72883eb7fb7e9f4bf049a14e04ca607c55530fd13bfc8103ee3956a16fd8fde1ca96767ec65b7f093d7ac4eeddf2c81d195a1d36d53b929c20e104200a782be5f65e2c440e668ea90372b28c798036e30223bccbb05f2dbb12cc6f881cf2335756395ae812ee748a575c8c6bf7ae37e4ea0651c0b474314cce545701d856cc161db832e05cdf6d45c65d23f5123f06e2efc3ca5383fdcdde4a7eee74c784ab7aa28ea03ba0088c33a340d2ca802e7e3a7660637f9bd4e9074f5ee66a5cfe6db50eaf04eb1cb35681ebb58d04af1f9decc8069c99482ed5149fe26e7081b08068c4fe71255da45ab86611faa005fc7e109874c1cf297580d972ebd0a9ecd4e1680fe7c9bed12373b29d1ea89a8a8b1e71e61982b0ca510b91eb7cc61675f6685b5f82619e84111f6ecb67629fdd68408a0dd99cc35f64732699478452152e09804b9bdab1c3a4ad3b7f8d5d2ba7d681e67c0f154334188897340e75f00dc696ab6878a1e56fc5f52ffb98b9df5920e0e4d16d925df20ca826c35f532f53bcee85c214e4de0188623ce4297eac5beb467bdd1f4105d66a35a2fd1449cfac35db89dc89617ffc17d2d9ca8b4f66e643031bc4c7a07307c35603c8d7a716d7d1dbf32cdc69b7b048f4e5ea7e851e44748cbed20405576791f1e5a0d3d3bb2c05fca077a8add0b617d7aec07962f3d71710834a62fd656c26fff37af6bacc6f646d47e556c3ad38c6c27022f2604975d3dc494b5238ac7e3d0c37085d98316bab580e2e2d63e559d0ac676ac19ac9f4a23f74b5fa55ab1606608a9da410a0ece97b84721d5c92650c42abae55970d8df3e8ea64dca763d510fcf661663b5e016510127e17ffbe3e9756d607289a8392b43d699c986cf612c1741e6df364163cf123379538f3e96fb20ab00643a3a972a12fce6bd448bc701e3653c8b68c50087e1bb7c28015d180127e8c2ce0fbb6d7ce19209080faf54b54fdd762c94a257330b16370a7678165a70bbeef015459036ef83fc3ace36c171950fba0ad764ba3b3b18c5edef30a0e3583f51487963fb785e50bef936fcf6ef3c18572e3a94d6d515e9e055bfcd6ea86417822849f98fdeaed6334236351a55218d70fde033a0baae77cd1b0203c6104d45c550f8a2d660fdf950f2ea6a733364ed02a82eb75fa9c188c59bfe83809dc9cde6b40465675d349e1c11e44fd2782e87b84e9dff0adb1e70860d74e5bf36873c7490628692a3f03de173cf8152a85aaf154a590f1270a954ddf61427e8bef55936fc588c16df22b3e406e8eac3898db24906657f05c84cdd8fab3ea983a4097b2c6d7c5fb888bfb592f7208d362812e32e7988b98bed49dd7f8638292418764419d61a12a7441f55b52cb6fdec194f70d21276cfb5f603349b3d8d3e23a5999d9054112d641373f7c8fcde8635094da4d2ced38388e13ca1af8feca88e6065df3cb90ffb0571537c8985f58e0d828206e29e10380d7d70741f00039634737791325bf486822e7cc2f286f727413b4166e99b1dffc99a948aed6c650089ae4505a7247d9d5cc6c29810e73134bd21563ab4e8167e51c9cfb599787167e810962d43246a2135abf246d3874ab1776cb6e6cb80cd38c780451772996ed976f20418373b1491c8c322f9e74ff0b0245b1b93e19026d6bc9b2489ea09fe5f7fb3361909446472466ee6409ad9c3fa9224ebb45a169cf9d8dea2978f86236d467daba46c27f8834f8d7215c68be602d3ff0dc593fea75a3dbb25086e0e9dddacf863ace017ff0c807a2d2fc622477c31483ea0964ee2d8f4c2d4717102e611f2448f31375b8561dbb89ea8c63eb59e1b4e3ecdefd0d60361221dbfa72b3a41148dd79001bb51a8dba7b5681ddd8e9f19468dc83d278b5091f23780e728da9126b9d1df7a83b920d5d29e981f984c0628427d5f2f8c27b8eef03a6edec974c60926d282ac9e774fb8a1739861a20e6d41ac222a7b50e4b2331ad6097f709b98767f13349cbbdf67fdcfc5cce263267f263deb593412ee7f7f993019005a36ddf40e77a96dff17fea4cb3e5f8ed385b1aaaf32f8436d183a47b6d83a097782bd426fe11f48545bc388ef0552f8c9aa35ff3c2a6a9130a4dbf878d78e42e021553acd45cb934dd29caa87a5d11775d5e7ea2b2b59caf7d66cefbdcb0957fc0919a7c6413008306e8522ff202a929333baf93f32c30807f9a1dc1e7b04468b8196bbbe37a95298d10f09323b76616db072911df193eb5fda36b496b306a23b18f92abb9813061a17d702c2177b8659634df53b5c4cf9c9bfb2173d079b9f8e9bf1d5acf2a8fa4dfdfd2c120fce91e6c49c1a03e32c0ba1cc5500e1ddbebca8bdc4a1725f05e0b206b9bd0955ec487ad45a8116dbaf7d8005dbc57884523393fcd04fbed7f3772f3d20b759b030a6be8325e393739f392dbb2590c433ecd7265ff5ae4ea6c074d236a04399c60adbb91e8b285064ad7cfa2a9976d2baad4c0dcefd24463b876caa803c684cb4266caa1d5d4d53b8345f21c4edc7d3455a46973a57348a799900b253f2a8aebf191c85a71219f196b6b73213898c455e74c87ff9697a07126b920fd3ca30df5ebeff107311832f9beb01e77dd4d6a151cd70bc2a3ceeb73c4c4b82ff90dbeb1e8d9117bb64f5b94c2bcf80fe548f4bd45955aaf96f2f7723b84589caec4d3ccf62a82b34fc433c2469c7adaed81db6af42648deb915815bfd018afc4c59ef70f7160c2c79c3634d51104898e065283e1dc1a836930fc1ff218139ed7983cc3e4e162dca1871c4fa845b107d7cf76415c19abbd730f5ccac09694ea9f931628e677d1ecb294c1950b8b15ae526ae1f48eb8283527de4cee8a29ee4e795b74d0625cae5f2b6cd385d61ea9bc00583f2bc708b9633d0e42c014ed8cc400146e8e44b174b6d65458123d671a1c4caaa1f1a01e6f4c66335b27fd84837c1e800c7752df3f45d0f8a19eab29751d4c6a7bb89accc755f9b5d20b3b773255b17d426edb43ee7da311e66c254cb575ab25a98802deedf640334ec328ec94b32b50d5ee6b194f304555c14ca4fdaee561db465fe0c56d62809d56b61e708e5c95aacc9b57170f007f1938dfded4b327c3bdeb6b423659e494e3bdb2fdc68cace7211472b530d0e4b6976f26cdb0d220462822516fcdfbe0a2008a1122e98af5131348df770c3243dfda38fc449144f98188ef2641ff7f68022eea90a933f92af5c143974e38687a70b1b3bd70a43383b1396ae71eae186a53812ee4698db19aac6ff35d79dc658ce033bc29a2b44f7d0b7622e394d4190c1c27120d56d18f39a038be8a24de373f549328b4dc4453fa94198fd4a0d8ff7b132a44f34fcfb7f888869b541787707c2a4160defc55d75020f6c444d1a65cddb2759dd60c6e7b26c2879f7c3776e22a8b57499ae48aa957bcbafea6abd19caf3ce14e16f215128d56d9b59c084e5d08179cbb6355cfad0b4b318d431be245664fa36455fd8fd193defe4585e4860f056e0c25152488688277b9540e6b6269680c5d0943511dff73800b095f8f77444bba6424c81c6004bfef8b233b32181d4af5ec8b5c8ec65e529db1c268159368b2a6bf4eaba91c2756ad8f8b6eee79597e4ec71a1365964fe3cfcf7300d97efc727c897f7d49eacb9b3001fb4558c46f64a280fe165d7b065d3e4a9ade56eae91de04b50b93767609d716fa99c93960ab27d7084886aada110d2c862b2e01b02294e8f8c1eb41da25d89c6e709de498cb358b2ca1cd4063a01d6076df33114e823c62fe95c510f17ec6601edcf6c29837563ed4ffa777df15fc1a1e27668c93fc8bd2abac7c282ac5c947f9cd308228cbff9aaa801cbbde757a566ac313084baa7f312a6092c2ee484e2eccb960db438abd88e425b84227f283225732c0d81f29264a6ae7f35ca4da0fa81bab6143ee99a6853b097e8cd73971a8909568d4c82fc041c0e52f7d2f872cb7b039679bf6f5f6847d45b7a5531173bbd6f017e028acf3af38471011ee55123523d4006f84163023b6480180285b929a02c5acb3d73ff6f0f1be99ccbd253d0ddc450278c8ef7d3fd78c8542d72e4a02ea2fa9a7b345dacdabf2cb08e7ecbbaffe3a08dfea3476666c2feef235c780c5f5a22b75d82898bf285c4d625861a133c13f1e02b4a333cb28bd600ded284d9612bf9c1d06efbf9f15e402844f6b9882f650c6fb1622b8ec2a3be1e490aeaa5d04e1c821b890ca7cc53ad04070d05f0c684ef98de55db7c17df56840da94619a3630c9697ab7442976b849854a72ba981316914fbd4b2d9bb8095bc9843fb4d8dc1e3884cbb25aa7777ef6fff6e1ce64f76faf8dc82e3362329080fa735746d925b4836c3a624d614804abfbf1a63b9b0aa0f1790bbab35a7832f2b40c8af0f243068ff606cea94c356f15221143b199eb0a5c220d53cdced82d01327918d5517cdd999a0bf62986a68040dae60f74062ef3dd6b39eab222f87e622b1ce17c88a2e2468b293c89c6f4c1a3cebbf86c8fa38b72e292ce269b423fb4e428801962447d159dc9d45b0a05a2d88ab95a65e75e25c1f2368b471b7af0c84126e011fb13ba7c7c960afd7f6cfb42898080a97a3717552a0de6a081dc667de14d917dfc342fa1ef84f69f1b511906d7d015d092ae2e6edfee1690f43d0fad9f0cac4057de273065b4b9fe8e9fa273338184a1e82c3327cada5e017c2d8246096e6646029881f75e668f96348d4f9e66113b4bdb94339ae6b28fb2d04e83a00a61b13f3ceb2bf2f1e682896783f8ab60f4c5ea27ec577fde9dcd08f32d1295d09850095ba996036e12a242a304a8f184c0391107df03b5ee872c7cde7cdedc397ab7af2f159e0b3f6c814f1a3a419ebff91926df5ef262d002a80de56d6a6c3da3d486c29f544fe418c7bf1e9260bce95ad934028725027661631dd238dc6f97dd33851d5c68a4eba0596ba07b09967daf43371a2b5bfe075a2c1a63fe4741a02a270fc2f0b3f22e8690f79799fae207cafe30228b618c78b8b2b87c9f5aabe27751c4e0041dea874396f702e6f7599b2a95341da543a8e095d8b970fba9209330b69db16c4b25e8bb48243a6ffd50147ba3a352568c2c497e69b6858cd2779a280309f6bb16b23b8e4cc93a28416cf9579f604e5cc3bc2fb8e061602cf970f425eb5f1442d1c8cd285f7a43c1d0003feaba7a6b396e2f442116b19d57ad48231d1fc7d8f478b9878f45e0f20aab0170104b175e41a69e7efac606373329abeddb8792c7022c78fae4220afe27d5f6114ea84a3d9651a8bed1c2c3ff352b5fd142bd39e0509b49331e2d66d968f82d681d111a6a55bcede7ddca5b9e84259c98ac468ced96983203634030136fd008e57292f6040054752783aef30904104296056502050a140214081218602b3d510e4783449b28d3abcdc38b0a25395fd32cb613e27ef9b4b72f08c743e226c26493e8c88974eaa18bb8db892d3edd37207152cdba873c97daed88ccaf604552a12ef8f4f6396a1473f0cddbcbd74843c5c1c6e77e2c6f1449c03fe9eaeaf281a06dc7b798a6d4f1322bf0ce219f535309eefaf841af560896a75ecd64e35db90b55a7f57163e5c4b39e1319ecf1cd3485f367413d2d3a8d854788d0374f343924bb6fe6074d23dc7a61b5315f6d5903bfa4c0d460264898ebb8f33f991f948c861d1410eab4dc3182e7beda78f805a39aac2057186c02632584e4a6f6896b4bbd2e5b5f88ba08b5ef6553a18cf06a22aeff21158aa126e53388fadb3ab4ce1a8eb30e17c6b17e6911993cb3c84cfbb38d95ba5e326365696871f718f0ba1426562f16a87b4e0a191fd2777abb60f40beea2915c28308ffe1d3ea1e6bac9ef91fd6c77037f7e623a7f93bba795bdba9bae992f1d426e4698ad4a9bf279122a2235e598d673157f5b172b7c8f87149c1eabf0bcb0b50c5bc28479d3a499ba8a92fdad17be18dfd388b6091c4001820d2e0709c2778430c7efaad2483008dab60d4c118e69f48f4b386f62cfcd3eb3c003590e542fd09d3d41be325ad91e8f7bdf7a5e1a45e6afe52de5c095cf6df491b1c80124e42ee4bb016fce16c42636892fae1befb1ffe5f4daa410e3eb618c12313696c3c5d140a9d0e895c2558e7082ff54d0c63b5a8cc1afd085d06f44d5759082ab7d75eeae1cb6214c94af0f1375e8302d18e7190162f039af3e014b2086863e24b672bc9c4070731b9af7d7cd8f859704c65b6f3ad8af0f33872c86ee1009815c6b125fd52524248eaebcb581650c9108313cff1735d8a2aaa01eed4f92c465b34b25e7e49ceacbf83dfbd0388aa97e4d3c253ac63ddb23868619f8312c086dec5e336eb53845a0598838d88a502703870a3f415f9a78284ff166df94adbf97cb1b67ea3bd80c1cfd6e92c4112e59060445670f949b7ca4bb45615ee27a8b4cc8aea86a1d408b01212bbc8e144a6682d3d7dcd69d5ddcad847d8c95e126e363c34fffec62fb4130a09aa0685cf7f91905d937bcdadb755c33da77aff7487c0b6f5d8397b6ee11b8f2d3ea36d7f8889d524534702fd877d526bf90645d6462cbdcc6be91d93f76f3d82f3fc8f7840b9292f8dc24507635e7d5ba43935993b25cd79b4ad28c83c2be41dd41a8f7f13f4273131bc5c9e30a6852b7181158a88bb60f6a8deff6d564438e87820acb86f4cb8e0e6e1cca4f6befa2667709d4881f422ad5331b8f40e22e2db8627f6aad6271b755c2e6bea2690c1fda4ade5041006f71fecd402e52aabbc905bad559e55f3f9fd2b6176ddf4b8274ac42482953ac150a1e74215110558334577fc1f0ac74560875224dd9785d10f76f8b8398af3753bca62008e5f9f04ae4b06699f3cf50bb75fdbf0795d789e78f5c10df14ac1384494b9d94e004646d558b168cd6019496dcc7480ac2eed6d5b92b3c2329cb0572b48026f931ac4d4e08987e5a54d45abd290b4259211bb55d49ffff5e87a51077324704346236b248834f5a2c0f6965b8b1131caf897a8a904f859f0dd23070224244513074b2e074fa2de959307001451d656138427976316a88974b9c2eda9188149885fee6910ffd97bf1f0c8a8385c8663e6d4da15e982937a8fbbfd233a69f8e0a417276c74a8d6f4e5cbf9cfebcb14d0a568f5e259bee62df596dfc69f589fa2b9a8e2d842f233a5c08c801360f3ce1359be03eed8a02f9ef758bf67b55f38443ba2cf14dc7928c3c69a78f23ff0950bd17e3ae8e2ddf6dc93970e386cd18daa568af8bfaf70fe9fed1933709effbcb160a7eddbc2714303c0a68919ffe9d1fd493c92d9c859f5dcefe593f6998025fd266e29ff35be44d60efdf99c1191bc359afa7971cad921b0ffb6e54692f3873b2435328e514d010cbf7c650eedcde2bd00d7b8e356003568254a491814b0eb27d8675d50014f42f2cd96b70c3e7c263c822129edf47eba8c89c168d9f4f86117daa4472d03bfce0fdc442f2b7ef774a9133263d1a64c09f5e83ba4699371b688f36e9408ae6e36b7a6a3f7f314b698671096e04b0a75b4f7612a285b7dfde47a9d27a6315af417263b47f3fc05f352ee99304a06b89f6b145ff141893d226d7f21e4b7a122c7a3843cc281840936752d03c8dd8e450fd80cf75493622a7eff1c005a38c7f58deec43eb4a19dc7f7e6dff7413dfa34f7fa257965738e242273c3c7d04cfa52f7af04813e81c667f44af121d53fd528c7f40f52f17784e522f8033b1426b1d665b902b71afc6d536330a1f50a9f3a3e473dc6fa1ff14ef06795f74a27cd442525d18276437e73d48caeb6c4621aaf87fbdf13fea84d2e42bef444402d6c813d666cad790efca3ab9c2e42da6ad407a3245fe0260dc47e8950428a5320f8cc552f0ec8a9ad62c988dad1750a1b3fda05bba493f2f4feed8c5e8b62b013c582b9af0ff57be9b7f0dabe95e2544373cf41fc26bca148c04f10953169d9a02190440c6575713e193c51a79841cf39a1d8af152e796e9f1cd03dedf218bd61d2d46952a49d8392e05bdb38eeb44a65aaf68f6f1180ff7a5b97880cd1cb125af9f243acac686c301ca5c6ea66ad337742e37e841802633743157e24a45d98617b0a7b81e1c422b23dce7b70f8b22ce1eb6c30d72752f6abb1cd60373ce733dc29e9c1eb49db8c2cd650dcf65474ac6a513b50ae726747355daa1499c0a046b6137c6242eea2082f48cd1b0603e862b1324dd3faf21fdb5bcb6350fdfa15f0b6c7250ac1fe7c25181b97ca825405d4e6a804e184c2b552d69f9bee5cd3ff9522f2afc54c21e080f857f7a29c9eab221eb7cb2bb05a3f21fc50fff833ddf79ce874bc04931131c81c6f44c32de2aebf6286165f6e718e37c5f838ceeed2d2bdba1fc9ac8158d26b8d7baddf65506b2fef7b02760e80a593a21d47bb61b8d9ee322a17b726dc6864622d580103c73593be8de100ef34f21391b755b4a97db804bfa105b94d257f6d74fc4b013ddf5817cfd32a6d33c5bd278cfd51df0f7a52db8add2a15385575c1ccca48e6c2eb5ba0b6a6c1d8daed1dfba21900e6ee08d871124779403884fca6f246289d876cbcb6f4f280af58e09081faa0001e0405206fc83241b939331a3799e68fb9d0ce7145142a8bfdd25d7cf885430fec27ffff13e2cde999493c3881aed36f51b3da32fd43842c0dac1edd422c5f8b681519eba2bb579af57bdbb1e27ae4150734fc9a5ffdaad5431ac65ee829cbfed3e5ee438dbd7521f39c40306a91bfcb05f8685f3b6c709238ba5b2fc6a4375b796dbf14ccfeab8ac8d26d16622097fecfd2095e559e9a34744e107ed7812824fc60b63c4b42016a1ec9f07737439f77c56a2527dbbcc2a44677bcd4a1d4de31358d2db9011cc40ce2c8b85f65ebff8782014e9c03f510d3c7546bf492f824ae6e4f7ceacabc37d21191ba35577bfa75f9134cb6cf794ce935b0f34f2c0359dd715c748f47e112b0ff717b962dda87b772c0aa127ed26e09cf5d2f7d3be13184cd41c5081e5419b911ae1962e2a1a064ca9730108804ff589316a87f87d20f7c230d59127a098a9288271daec91a1a97a554a988222ca2f6a5e28aba75f29ad184079b18cfa2c6f31791b0b41806d4924d6eaed04a9392a5ee62793b3fd914d7593a2cbca211e9df7d0edf33938387beeaf792fc50c9bd55f2ab76dcbb8e2d10388b8b1b142d574d94898be87395fcb5ab279b957c4cc15c040b6394f7ff6b826d93f29eb2a6ef485a09c34db1735eeb3039ab977243f2e43756b34929a87ff1294d26f056391195b35857442180ab3281f27ce4f113633e15372c62eb529434e600bea8a45fc64baf66c880408a90b310edf480261f6b272deea46f2dbd58b835c74043d9d6abd4c046e9690387ee07eaf52ca4c2b969a00e7f167f8875ae148ed54fa1a6145d2e33a6b148ef99fb0c84543b6af799ec6a185a6bce7813a96a6343a50120db79302803fc619043df0415e76c39c625cc1f33bcdab9739e597ee9f33b8a60449d24ab5651c9d0d037ad2978f51cbcbec0edac8ac91bf33f0c56f29ed9111ddb889cfd9250d96bd1c170758b7d4ec00ffcda05316bea4465f52b22042dcae3c2f2d8a2b653068f82752af9a35815465445d983669fd004e3d3d2537e352a187bcc8d1485d1492cfb9c31e0933909e3dbd25af1737bafe5709cc24bc8bac00d7fdc917016def88fba9e17e30343d3ffeabfaa37f16c0cb5c596eeb294387a285bc566dd6465c011ec3eb2fc5f8d7874780aad952383fe2e58512979a77a16cc2e2caf8befa2d8dcadcc11861f95132b3bf7782f32a7eda4f919817304baded27d984d0a26cee3909a1f92c5257a26c5442cc9fdf5f6774ba17369414d3de5ee739f84bad0bf5fcb6353c27d21a5c4b36f6df733fc454bb19032dad9d17d57a5bb932c785dd5ebf43b12adb788e0c9b35f52992a85105a297fc49c53f8bfec1dc6c530460b8234863753a880c683a8a29046b538b0d966b181d2f233b4ed35c17a949f2810053ffa8abbba3e659fbb103dd545e7920d870f4ea7207fd0770a4fd81dcb71f165831a33063f3b0ef2259eaf76ed49fea652e8eb42ba2b76383b1a045ef30db795c1957330031b866953b3940597c3fac181fa40e707a69032684cc5558318d9dfbfa5c70127352c79030563e89929564a57046e42fcebaf4a35271b1a895721025ce2fb2f8a3b4c2ee1d954ff8138273a8668e256fc80b10d409b606e894fff7f0ea1ec7e286469a1a840b65c30f52dc4d72717e9d731d2f842e78bdef7dd22d911f44aeb15781d03e48935e940c638cd7c0abac9e749446a1b250479bb1e6cc81e5784e95ae129e49ae584a34bb1bdaa8a92051de5485f10a46da73132cf492fedd593e788267dafaf68e33976814fd61fa7e5eba3be0795cfda2c507f52aa8808272a4ffe5bf845d533a929986c6eaae4cced4bd4fbffadf438cc1b108ff8c61662408eb0b18d6a97c19683df93168a09c97d940154e96057de9f008e43c4b05cf3ea53978c83a80d88993d497d1bc8dcb23cb28486a1c62ad565440f83c138a14ac25936b77896cd9ca8458cff49157d154efa89b6f3a976e677afa9ced5823597051185e149688bdcea4efb75915972f1f9349a162ea965b8aecc11fe63060cd687fd8276baaf662f5cfeb94623f5d3bb2e67e50df39f4034ae7755c5ec10ed24dd4b4abeb7c66bfe7ce63dbffb2ad7caef13e7b1cd50ba51c5db9b235043e12018dcfc41d10c98bafbc6adc1f0506df50e7dbea85aec6c26cce568dbc3062acfd42f9ab0f3157763d4fe770d0f66a3e01f0a02047ebb9c7ea6bd0b2f317cab2ae7cf2075132525a3b6c1ba1a7e189328bebea8c85759f2ddc4db4961e7f4fcb22336dbbcd0af09e2e6da8c4c4ce202e2fc3087316eeb18fe5c2168f8e384b8316ba69774202d93924676acd9654278df298f6258df8f13ff33ca5d964b1ae5dcc40992582975791f5d861c0da48bef223644609246dd64e79552f47d7568f55bd22701ab166f604823b1682a7e1a0ac35efe0cfd34cfaf48773c915ce8487705b37f5d3d714f1c5066aea87dbaa963815fe7c8233a7b562afe4229de64d7def4532c74778e873bff0586dc66343e2ad3d795b970b3f337f26fd3d97095eb1091e13c99df47135ad179b99ea06e1903781f17dd90bfe47a237dda3fdea67e4b6496ff13c3379ca61d9da0a505437d7d6a9bd4cd6cbbcc7528618d6dabd71a4024e7d664ed5742f7149428c22ffc521a1cea1a1ae479e445b230970b2a1f1ddd694d514154e6e413d855c46b201b02d9224fb0cc2a6df09d933b1cd69ec397724185180eef0f1d96e2d46c8c6eb222040b05ca970746387a9e28f38540515adcd81351cc075c83f6b3d9dc2dcbcf936d82f4b57e25e3a5fd4657669596ad37d0c55e415899ab82937f0a17a66d9c7203dcce187e8cfcae093522a7e0d6130ffa41b5578a7f1cc67eb0cf97472d3ef270828401e29e068bef4ab0e374fc76dd2e3133a87222c882db66e366388eb0ec040554e42262535c806af8b52ef06efd7704d6fe7f265986510805a6906003b3bffc92ef1b45cfe383b46327fd474bad3f85fc682f9de0be9da17ecab8269f729421f8f12fdf3fb0375e9adf5a000dc6da4eefcf828079a8c6a387e46445a8c857a75acdcc0b5b23841a0ecd73ad624c36ba9f1c06aec81f8e7e82c7132658100173e223b37447ae217bcf0c10bfbd963a8a3114878d413774fb11078600e4f13d14aa0255ec6b88dff4173d47fadaaef467ed95f592ff077e6c9564355f3d8e8b152e75cd9fc8f0627702ec26666f6a33f9d40f5408114b73a204b766374efb04e91fc476b079c78af8b5abe97d581858114853c9e55d5d4fdd4084f563b20ee7d86ecac0e3b5b9c56bf7de0ee720c4baa61e3d125ef16ec775de3afbe15d1afa7a704644747312155636170df0c0e86052de752e1240177d47894cb6379606199acd8ca2df71fa5d889804187f5b2f624f1e43d7e456efd91de7bfbf34d86c97470f6adbc7c2b412560400575738631f886c260e01fe321259ee37366e563fda994f6b3b87dc9ef9b380a790b50a4014296906080a5d2eee3a9203db17a39532fb65f8c9d0031973c97770186f551540725553d3056600a80697c3c27f5b5a7790fa5bcf1438b10e8a8fb07a3ee773e52836a61a0ae58a85db8419cdef6bdf93e0764f56ce56e6ed60c0c1115ab9e5520f3aa0285a65662ff18fdf411e08c835c58c726b1c2cc7f3c1fba8dc5f71f7e6c63571289836dbdc76989525fdbd1961ae6996b6dea62ee2b30d3ca6c81854f447a4de0d2ec1e9cffee96456b7b489c42fa7f9993578a57192c7215d7e70fdbda295c2ffc3f68fabb3b2ac0ccc4d6be3c818815300faef55cce4edd8091b616aed0febaaf98d9f38a90c625887dabb136a99cb7ff9286c08fae8a5124d5af37b6f9780d7fc8a6b7f145a04ac5ddfdb8dbe63b05cff0e0639c7d098fa82a7f2d34cb6b46f24e0d9769a55dc63c0d60ee84d8fb47f208e7da88adeced6697db235a6ecad49a203add6cff1104a9dcd2513d473fe2ba4c323d77c1cdb5d851a3e0c4b7ce8abdcc78f27f1bf21813fc3b74caf15977c669de777b8006c97dca03f31fd0b1c45944aaa0f3a68dd6f84337b2f12ca81966387d75c70bb08037beb1dd65179f7e58c190eed015aa07133c5f021f043d107a7ce19c000cf0882fd5bc9ccb6a8d62bf8a73fc3f434bec38d7f5c0cba2d9ad9d4ca8b058e02a259898fce1c515f31b28d29810cb07015bd2e59c3419e7c03a5e1268d28f6829e29c807a9726312998b23ec901d91e666a44d05561feffd7d1f6b0d4285d96c0df8dc8456f0045ff130a9da13194c582b3eeb87807f13917af47c39328d8d362963b8a7d59f80c5fb9d9bf4d56395daff0da512b0333c2dd31aa12fc7d38c8dbe23aeb9c738f59a875185f4952b3d161ef718a76d40ada10c679d8903bbb9164195a3758ddebbdba7fc294efbe3a5e9aeebb9460ea709ae7e57b8c92de5eb78cd3de8662240f705e24672ada1577ef01d365b7dfd6d18fe6cbeac5f5472e4b9a52aa25c87b6139c1c70bb9cc151598f2b4f141cec4baa082aa5cfe6c96878043274bd545be239008b938a4b2c90ba65902df19e7d2a5d960bfb40065e538243592d381c3f3400bb5588c438c9b20a126f2efe2c626ecd6c18d32fed424938d5d9e34ded5a0eb51ab2f461acd537230afcf1159d9916c57208569e81870abf29ebfded27876b0f47f2ba02f3fe11f18145be88e7197725638d69de1076f7d7ace890b878b377524dc1857d2d132373de9679193adf1041f9d9b1e1e0d52d04538886aaed20469fc15dce7cf54f5a8e8ec0f928398704d7d57f4dbcf5e8fe70d1780e0bdc7f30b277b2b112ade9c362176b440af762e2291ebc84a9ad95654120a69fdfdf2489a219c9858caf20df0a41b93520c8b89d054014909a90a0c5691692a9da626caabb40df23d00c10abe9bf2d8feb8c03eab0db35344b2e0ce6909b56c451d07512557428b9ebecd706c966073ab5624747098e88dadce7dbd365d383dd70b2967707f26f11d5bc6d5a569c80995e1a605292648b3468c49b3bb3a2b9177f586de9b0fa65b0b161fffec389f1e2ef5957472cb0cc0fb2f18a52a04de2f58fc71fde3d0a365db0f37d63c19676eced3a1c9b1c21ac60cbfc74d54c1d810092296ce1e39d639a14974ad9206f3266761a1d9dadfddf61dd83f61aeed244ff500850465f5f5041968c093dac42deaf3698661498140355279a9912829e458c6013f596a1bfdf738c50785184e81df220e0190d8cdb403c22e04d0765578aebe476f70943421ba459838b12d378cf68d7e8564b898f0d1e3cd2899c04ba51c26c5337ddb5ce100164add7f116568cad50a1af4d1b9ae079ef28354f15c873694bd904603aee7787c23128ea9c393b3a97ff0e8fe1a884b0501f56867484cd0aa150ab0423253ebdf2b1b9741f20c18d3aed7207fb2e4938989592da120e8a031362da843571cbedcf9ad36ebff462357776991c92ccf263d01f3cc12ab96534fd215817effbd0932fd4eed4b201bfb9778c1ac276122e539a2f2d43420292f5e0d6fcd05bafd722bbfe6399deef16c822697b03a786800207a5a018b80a2b4545400e2882a69bb642ada6132bf8b37d411b74d323db15ed7965c95be99cbf4718b0847a9eafc3f9cb35ae331d0cac1bb004bab5be04b105b9d9604a7aabf15dbff456fa575b74399a2822cbf302ea5798e0a58f44ac93c20cdc3c41b020097919ebf4c3c81191f275a90b64dd0b41abdae4fa51c3967b683cca4cc0401a7f497722d44acd2fa447925c9dbfe62db341e9980a8c3f394f70451f37d09959588155b63bfec1eac7b6911425c9a984b366a199040e024425ffb7a8b347b6f8a49ffccc8e2c5eb0a55af9aef89991cfb5da90078657a836331d7c4e07038fac9ffd73d8c4d5998cf249cf9aaa999ce21b1f57f21634da10e706c598bcc6c16d5e7af5b70fae8a8dcd39e3ac9b6d19ecd37c1b7ead6c21a942e872f9c09f072140fbfae527ef982e331aeccaf4224ad0ac6b518bc69e07c93856d6b205d531d243987fcc09a2a8407cd116f5e1a46d5564d07e3543cc80108e6c9fff753c5802c76d7934ef59f1e0ed94882886c980a0e8e8ffd14b9f0e1181edd7df6dd1dda78565e44ea64d9e73c3545286a2816a839d06f85010c5393991a1be1f2c54b81b21dea50b545965087ee246f826af79959ef06f5700cbaaa7535ac47027232419fa590b05f5ffdf92cdedbcb324b44ffd9948de7d249329dad50c42998f6a08a9f83fd267011a97f6500ec95c543dff42a13d5c8f3d7b05105778cc6186b52df6317952db5ee0b6fa23bd756bc6f177382070a7459338fd08ddda4cda8fdfc0673e2df8212bb033e4fa8bd2562476b8443bdb22ae833a098b827d72db4c5ad90c5fdb280415f21eb16bf951d1406dd81a19ea70c1f656a93db1e48df382857998dd1f27c6768fb97e9bdb896258e2371f6e8cf093cffcd2c41a094fcb4932da7c3e61410d0deed86724a55cb522c852d23b3cf7b4a12e985f307a187240eec1d7655b17d6f90f80cbb66b56f4932df6403bc71bd9c481508b6218f9e9179306532ff483c5fc16f1b5e93c443fceee85baf5ecb1eda89fa3f3251705438e7946a1990d6eb35e6b554510e90a7e621423484239b54bf82f47a1e112090860b96838feb707e77f271fd455e53fe948ec698175597cd940ab6e7a0b2f8b1fab893fe892d6b83df001be8254a38086343956b63467220d31596859b6e5a27daa2d648ba4b8b4f613641701b626a7be7ef77561240e79dcc09c626add25a9622f3a8aba227c565edd0d445c6297b27565ab2b91b5ee98e8bfd062bb1d04526d1c22610ddc19fe6712f385c12640ba3eae5ab4f59d35f86578b51a7c7956a7d7f93f2b9d9f8e9ec2448b97838aa9139a44a596133b216d664b14ea75de30fd50b65c96823f9f0c8004bda28b636a98bbf785e36d5f5ae4085226bbf3ca1027fa69b0d6574fa7eaa08ff4b29c4f7224a0dfbf05cbfaa61d485da4ca3e1d79c111adadf2dafd1a6c379df0502fe08f7b6cc32d12771ae82fdf75a690f33b3a4bfcf884ec46018b79a80130bdc3b71b35123046113b63554d77b91c631c4afcb5489fa03b4e713c992a6df8f5f6ffca884f377ffea970503fd813907a36e0c2470bfa172b82d75c017ebed4d2924f505118059fd01ba45ac167d1e80bb46ce1358f1f92f06e8d5c3870f003066eb9e3f618e4c022b7ab23d65546c8737d873d894d7d0e013d38de9b3fa7863fe2893e0565c00068d3ee60b4ea9d6b827477dd4a0f226dafc3d641020480d77b3b712de79d2567188dfe284d0587c35b59887a7d66f89ca365000cb88f5b9757fe2b5f248b9be56839bb3ecd48703d7e262134f39c57d5914b94e10dfcec08d73b2418d6b055666e4b52bab54a173fb057aaadfd908cb84c835df2bc09e2e69e0db92f0fdd176a41da0c892a9279a6f1192c4041d32bfcff82ea0e18a486e067377819770dda10006ffbb511f840ac44ceff63d982564683a46fdfc65c3ac85e2f110099bbbc41fd655ee1d5d46cc723a598ad00f04cd139822953b899fcf3e85349cbdecc01b9df16ba3a3df16fea7ac3583756be7f46c9dae825772f53fdd386041646d15132b8430ac47d453f713f4c808110503bf083408dfcf7734883224120e51aaee3a30d46019f7fc42f902293a2e9663a2e97d1bf5aea96e1a75cb4f78d8af26c5b6a4fbc8803b4e938e1b2da747187de23707c98e2c4b37d2af1d64508533b3fd6be51377bfe0301d8cfd4646a82c21f1617340aa6def3fce464934a4df831fc40c976c6fbadc6b12f69be9f9032ca23ddaa47fdb5c02af290488fc47b3ce78fc3b191f4d42491a4179e2d6f503e3fd7d214a000abfadca78c9014c9c1a2ed8e572b9a4182098d7e1a246b794dfb9fd6306acf54f503f639aa9f80516b0c35d91a20aea1f974fd33c5eaadb7dcb07bc2e222514657f6ecf53ffd3b3fc27ee7400e1d65b205722f8521664fba9686a86dd558203ffaaca040c9e8e0a0c64e1101e1358f8ed4860e02c01d1982f023d1c09128dc1525f781deeaed161e06293e11bd0d881dd9d870081a57c630754627f4ae27027926fdcb4898a03404ceabebc105b3e2c6f0f37a9951bd6651eac781f9fd7359739d748dc38f3ae76be80c42b266375849faf8a529c6735d1b0c5dc63a178904be72e8d725f9c0bfec330ff7fbf9e6ece50e9f2c99e6a809ad0d30cee1c3d1590358f62887d6f36581c51139e6d6daad1de42c03aa97bcb05c678fa279ce88dd3f3ba1786e1204ee4148a5b8f3c98d4d9c7b8683f0aa826145bad4145e439f7d0c3cfde87fa1a78d783374ac833b4a7039d48621304120bc0ec0399ad005f8961104fcd7a951c3f0cf281608f111a871821af2fcffcc2120054bc8db3f6fbdc35fc7b31027088837658dff0a4479ab573483761a5c460d47007c2ab859e936052dae00af90d79d527bb1d820141a12844a43fdff558a92e717f1ba64ae080c9f4133aa41dc0dbe490ea197f3d57006152091d9a126b7bdc31806ac4997b9866a55fb4c73bae89f058b3b1b480b0073ca252cec1fd5b53afd3fb1cf8b4482b434461054a56b99c82d2a28f37d480ecab8306880808b67e263a2bd79ebb2fd7851793778be508e5f2d399ef81396e330ad41e8516505cdd2ab48062ff0aa1ee4ad0756779352978551177f1ca85adc2787bdd168aedbf4c180e9687acd9f8a605494c584dc05b0418a20988d5c783c11a728e173c3355971929d4aa79204c48c4b8bdb3bc249e8148da65d922ee56a0e4e88fd23da7f6f707e0cbd262dd95c1cc923220329cd88554e3079bbcbc9a61c5fa66952ac9364d46e1bf5a6e20e439f81c18be69f198c563209ac1109ee777bd165b1088e593ea25b0e63de577e831edf87c65b0498ce6c4cc66a2de9bfdac911f1bcffe65daa68245f0cf8c78f0a89df36a2e8305dc7a43773c5a1bef80ebbe9e2bd9918fc0a460ec768730eefb391612eabf9cf9768daf608a69d418d384fdc2c72803b67a367440ec0eb3c579c3d1cdbff46f8f28f11143fe980567273fccb67b7ca8926da7b2ce0f662be278074dde2d192802a07c57170ec5c710fdb378852a0cbaba61d961b8a4217c5754cac8cfd927cff0d529b4b1c2c5d08f843c2c359ae425ca022937e9093dfce84c854e250e8c8b10efa21af63c33df78eb304376244d030e2ee2c2a205b3d667510542e1b2274d5f0f6a79a9b51827b0acfdcc28ad79a7108e559084b5f13ab6357df7eb602e1d19294322fbb6090dac544a44d19164f17c47da7d4c5337a1efb1009998aee3d7106802da43a1aab2a2016afefb34befda601301a30bf1b16cc7f921045fa5ebf4c0ecabd337293b2516078dace91577748bec985358eca5306fa9ad17c5bb20b793e7d48f68299b6e12bc4e692b9e5e308f908933f3e78c2d945622550c8f02ea84b844fcfb118fff025164747bb3e2b37c97428095a50d2965beee4252ec6da51010f4be3e5af4101052ea14a8672afc4f42956e8c6720e290b81df4db757c24a8ee9d5091086564bbb01fc9a06aa1662870a357e8cbdcae3ef5a0cd191ab4bb32007390ae47dc1767a4b80afb02c72cfc8acc77eca3c1669d039616046d03fb9f18b414c127197db0a56b7973eab3de4707074777cf045ba1760f9b849e5f4d5bbb97a67df061ffcfa45c762c8011e0b6df42b318bb8c59dd7ac48ba524ecdf3c62950089bf9e793999b8e390c99b89b448e3dec6f663bef336199ff25f432ad3d26b170c8001af3f06a9145abd0c8f1e94c4ef106ce3a5770586f69f0d089486b16624521c5900f19fe3f08fd6659f7519c1e30cc98d5da521a4a7e1f247546e7d880e0cda1459e344bd1123bfbfb563d73aa0f9c7c7e3268edd4482e5b4b53911e3a593e80155c92ef3933c157a0c85ffaedd3301bb20c65b4e707d5032d6ca53c9458ebeab8694e94668863fe5ad23388bd20853a51df245bbe6793b1788abe0b3a53ea311464ae13c01f5ba7c53e72b12370f3ec5c519b6984d4fb81ef0f57eae6a0d9e30fc7fb6ea77c9ff0b5a51332d8b025f388374e335801a19a9b60b220b600f34291aa4dc9754a9b0d4c717acef150d83b10a99407cb25dde1e823d629aab48d14ac66318b56ed6a499b6ad154644bca83e514998a8904feba5b0210bfae16a8e41f4c0594126b67213972604d80741996dc08a9d4a41909f1d99c66cd82fbb3861f1c7eb45c7e49401031efd8a391f0cda5d03e37622333390c7b4bfed4164c609f2acf2b7ed446619216b1522184e5319b5217249d8d0a42d397357c6369f470e9ce909859f48f5b7088796c18deba052321be0ee098e794e94ba6543e0f109fefe99589c55fbd98fa06120cd3ad8b5e0c5a7b423a70ca268e9246dc392f589e5eb4fa757653db0870417d64f0c921c92d9620457bc5039c84fbfda82cb216e160e2d27ae69955fca88aea4015020e241d5001860654ac4c13abaf037c6b2f04384e2a8b628ce48eadd48268f81378c9c85759bb0e0a2bf95e23b5f16794a5ff25a98bf495b6cf63fa8d3c6aa02c360ab348bf1677712e3e4fbe14408a647eb3c276766bd128ba39d375266c83d0d0455134e473b1b408df4afc54921f7acc194ea3e562862e668b9f5b1cc136a9f7a05c1566d88079a9a0a4190fb70d00c54fd29d11c398452e5c26bd3aa4516dfbbf1cd30e8b710f528a04a03a3401cd6c0ef3c4aa28cb5fb26a03eaf474358bb682789e744829ef7dbb9a6c738e5571d6388bf0ae1317905c6bb104c5abc236e0b5b61d3c79cc7d3feceba09b075b83ecf60fac4447d4a6d6ae312c1bcc492d5ee120f2687bd792eda44df816e403993a95d2685555d4ee4e8af1c043c3cd9e9ffda1b4a25651551a4e30a4107abb06b84c19f3678325c1c6670b71fd50d7964cd7b662ac56697a6a2ae211699a271bf1801313d39564cb6ac9dddd44589a6e55ea3aaf838b41da4d6cbf3a678c1ff0d0ecb9de03785c9891badf967c859fae3f71cdb51b55c346215f11e95f8f63e84dfea17da85f2b01a963694ee584a30df9a5cdc9464dcbb0b0c58db14fcf8968f247a90d3e602ea818c67360065b24eab175597258df3f8e5e94c0a5e48002ee394c0cd06bc9ecf0d002db4d77c4efb4ea2872653f6ae75f7a31b7ed783e2d4756eed96e3c4f1b80e06f40ed2341c66fbd70c648ec1b4b4fb228b6844a55ffa30e3ec4fb2d593191a6e9eff10aaf362e1ba2b50866e0a38d385cfd25e8f5481aa03a37f58f713fb021e9bb018324c75c2b2f63e2285e7a17c19acd68bae0aba2c7535ca78480523b805c9e4fbe24f3714637325bd162d0032de94586ffaeabd26162b01b5ee3f274969227ee4c8087070935d7157b0a171c96ddb1d7ff2c2f9c6444bcfdb2e424ef73462e8f831064bd5837e819df1187cdb8450898722692c8194ee5f29027d0e8d4fb6d36fc67e004a7791221e5e43b69cf95c7d983536c8f9f455c2c1694280d17502a3acbd02a38ecf26c518e7d3799b91b66c9d0b9606ca7c183bb97536feb293da1eacc854a3dfac22c7e39b6e9fbafff912a73f6269f4a528655234f5b7ab7db5866dbf27a27e5d1220a0a1ffa85b1e625edd41ef20d814a77c07cbfc5eeed7c1a05a1e3e981ff17ae04c2b83fe95935f0a15e1cc17f2a71ab2a29c8dfd59fe20f6da4e8096f4af850e5e732440a214934f8bcac85c9cf1df67ed93715406ebf75ae33823746750f156195a4833dcac7c659a6ff5643e1dd3f7dccbe8e711253b56a9e533852ac4cf7f38fc04b43e9d605620dad483446e56908f1b09e88a8e35e22483faa29e3685d480bd211168682a1bf4e0f7122be747661961f3c06916c35c7b7dba736732bbe76a97660500e2c5ed33c5d569e2b888a4dba77585ef62e536cfdf92473f6971c5d4f412df25ba314c0f5ae82879828f9cd27c2bab4c3343568f2c4a34a4827b5d498925a405f9e9bf32e68c653e7d411716605e71decb010f6a2943328a98a27816fde06496a7d2eebf4eb67029e4daae840a594b406aba3de667db73bab6ae89941eff9b616565bd3f33a7b82591df1c98726df64927982c4d8f42a0fbf01884e97e925adbb514672af858c5c52f8645841e8ae6ef97961a8fd743bb7e3d0e82b2c32b41968160a0d52fd63bdd6d7c6d3bce29fc7400a790448c3131ce2cee2ca6832bc5d07a240610bcfa3572ab93eb58752b62fad1e35a36240b1df3a33775bc8e0e82f771722f94f49406df5b88e71da542627aaca5d92e7735cdab456deb29a33ed9697534e7f3f900ee117e659491c2ac354264f3ab53f0ca61adb9f8c94acd0eb2ffcc20a0c28917ae102d64d119d69b8579d600d2c6db148f40278fd41636de3b331f696f0a9c527a9d77649739662ea06067b237e4de7d1a8b239f0c6efe702cc174e11309c3fa13dd1d2f2b6726fd954e9bd649ef5d5c08207d78c9cec2e0d8c3cc11a0711ce5c73b5cd81ded975a0b71972f9fddb8f8a04e608049ef65abd3a71752f0e304234316a03633ecb995ac5fb870b4a6a7d2b2779f9bff5ba46089ec8bb05c2297c748a90ec4ffd93751faba5912157e174a999a51f085863a1c8d599a4744b06f74e1a173b3baf36d2c43791759e04d659d30d6dafc939afda7415c690180dc061fb946885e2ccad12faa470b25309185e784eaffc965531c1df8df1afacd6023e7440840f75cd07c81e7a0dc8e0dcafa3b4da43ea6dd2c60c0fe1f80877721c07796b01d0c957f7328bc2cfbd4665afb14622f57aaa1d8b4b0c0e1483bf07a454cf4ff7a6e14239837687409642897bf648c0b810669fe7d616a9029f167c61feccf7deaef09ab59a879c7c19a2d8a53f58efb94f8913a11dcf6b14b1e9f4f8be2f3670feda944ea1ad2a820824e66f13c913eb5f53352934a66ac55f165d223017e40c70f951178e69c493279930761e943753c067013d7c23f786782fd148840187ae41745d21bc9f4f8a58a60990dab1e6aa267c477ec6987948e446f57542f05e5edaf22bd116493cf71e90e01439f0db80f75fcb25d9441e93aee9210af1a22ee7a956b0937ee3d01639d2fbb1493e21a36d8f84e12d2e01f2535713118e95002e81620ec3ed53dfdd36c321cd3c87cda220c7970ee6d338bf1d90486eac574f112ce5cc3d16fc394b99ad925439621d02356bf11d792532fa5b0e7215e6cd94a6e7455371fe30ab382aa50efb6369331498c549172783a37b2654111bb562eabedff474f271bdb5bbada24a44fb34ae03ac69524fcf643115b93709ae99825e5fac9659c4572950be5b38fb7e14105129dd041205aa7a2ba554949fcaab6cea522466dc5e22b994148b20d83219ff06f8689ef7c8b42e8db96fac8f0a4e6270ace65571e699ce4c0e27afa12b17c2a1fad1ccdf691c1b16b62baed4184a406fccc143cd3d64df8d611a859e509058f9eb0c6f4b962a4bd0e4d62b9566fa3e6ed94340fc09897d224831455d4f4298e651bfbf9ec0dce31c81339af53b22f4657bbff918832c53b4c9caec4a3e3012a0a159a9693a7890b9922f7b7cd0a6f04740f18acbf930f5c46b69334abed466af38f47668b9002ea5265fbb81186939e8f55cfa62d5d742577a0cb2d60819edebc542d40ad29fc12d4df5d1547803e0250b685dd436eeb383c718814def657fc8ff9f96a2ea446ba30596b3463e2c946a09ee269e096abb225a87d912fa96f5baa029347bb34edd70d05a923bbf54d2d67ebe0634a2263ea609bf4b1bf877db9d2bcad71327dc1578deef9c49c11db085dda2e6c31f99ffecbf1ac8b40c05c2f07bb7a68e07de967c5fbb451895435492f73a1ed6bbef2729f631ab21607e9f073860ff13b3cc82c96c35fc619ba1318b814fb12a3892c724260b7f51f5ba9dbd251577df2f336707e745fb42f98a4bbbc21d07121b57a8b842747f6a4373b4c51943de0c7ade0d91adbef653717ea91f90b72cd3aeeccb1f2503a891f772022b7b8b3efd1c7bb75493b5d37716fcd11ed679aae17a8e59ae9b67d738591c7ab2b4495a6c948187e9a65610846719de7ef43142b351fe6f8519a2a406350af5b902857e108beec1b821f17f364aef21bdc16bb1903324ea7bc19ee30f4dd1e4b5c2707b3c5c5cf1fc8a08f3b350c9f43367b3ffa731b42cab3fe6a4877372d47411c3f091f41d7bc7e138f1a6b68fd7b752894cae4c0f5c59a8140fbb68e0889a1fcc2bc250e55799687b0f6fe723c52c15c0cb4afb898b94cec2fb85ea99f0449705ab8450f9514fb2fe2b049e10667e5bad21e6d41f2d55eda5c974e58317144ca6951833b0b71453df1fecd4adab33ef15c06eed5a61f2a368c1e22464d9f542ab81305a2facd9e15e67718f6b2cf45299f9b417c4e7daa5fc09b07de4226733cfdbfb6d718f5d574f092607754bb7d7ecdd8ce9d4b8b5e6662bf9b87d2bd5a157423fd4678f0b67a6a5e3de32d6bb742ab1b787adb71dc0eeb6e8cb751aeb2f3ab1ffecded74eb40230dadb1f9fb938b58d68847a2f149d1c53e817a75d1801ff16c2321ae7e4b4657ed4b7101bf577befa8e996f8bd1fee92bd0c712f5b470de3eae04308311d27e0854f8e47cb9e58ccb0f611351ee7fdb0535a24f56748533089d10802224a2ccdd2cf216eb2458933be37d5b66c1a0c2c98b2b708d3965a56804bbbc10b2e705ca92531f7f64bbeb5773a7192997f33ef1766df2f291923b044a1850466afa4d912b92ac558eaf644ea49ef461250481efee268686fe4d21563c53df9fea723389271a521bc978cc36b06c4c047a5938d1420c688b8893e3e1fe5a9208d3ef1ca33665076ffc06596eec37fe9b65417bb807c40171317103f12d9d5f4a09281f03d603129f0461e4c52052c7183cd229388f9619ca44804a5c14f8219629024615de5c3d5bac51bf38e00404ef5fe843b0c6d71bd2f74a0891e9019313f31bbd099fcf3f18db5158f5e6ae47d2e55f74317885fc28fc9919b294f55c5c4e345bc8e2bfb134b68805a703480fbb369b5fe4a4efd62c14ff4343bab25a21224198f46c79c22db0006e628abc841c13807e885fe3fabec303d342a3833476c63851d13cdd83b142e0a83a4e526e718d4a4535fab89fd302e89e85f8bef6e3c66ea26ad5c049b950facc617fade63f02d5e94e5c9207f09776f6b350ab3b47b30a83f83ed77d2c498c44b519ae5e2d94a5d172fef7e7e982271a124fd6872105e24e270290de7f32d1f0ffc25aa57b1194e55a06fb89defbcd79f950633bf8712079255b9a3e7c34fd87e74861830dbb004bcf0e18f2ac7eaedc172336eadf18382062723927085cf5d3437d555e20a89d61a88f92916a6dad3b1a7844ad375af460278c9a8134dfa63274b286f61c4480a118822237084c90589c6ec10fe3d30e1636e8315ffd6286e927795bcc4e6391837939041486484ae478f9cab721e27d80d80b6e4176efda765e69f03c5e77de8ee4376b5ae29377f732441151252cc9bf42bd33c3c6b759ca622752d407aa61f28563c93614b11db839904d1a2cc387942e0624ab8c4bf3082ebc067d522e77509ec6274c5036ba4fd73ca28e553d329fa1405453b46c0ae750eb3ef7fbdcbbb4a03754d16ee272736e41f024e30da3f835abc1593afcc3bc11c027b8299a4d3dd8b6ed8f0fb78b86b932e9c0793f92495c77716b1cdfb057f86eb60e50864fd576ff3db999b735f0b29547ad8da067522972da6ab7364da64a79a26bfd5e9b9b5f39be4e0131733af4250641ecf2f131e80e65188c8e12eea6404b0dee0b52135b0e2f46394703a4947eeb9920208f94b3b31e391a459300abd34f78688f8341b4d7739552e41661bb9ce335f73d242815b3e1b567b1bc2015dbce086193a034ccc4bd547dbdbfe0a0574d8fbae7684765cec8cdc78452a496ae8d7b44b2b70b0323554077d880fda40861ff78c6f0a1109cf64eefcb86fd86fc8a8e8d922e1d08733aff91e91006dbadc57a5b10da2186e8a23f4bdbcbbe831103ec7cb342a4bee7046cf5765b72a6bffb9aea16ad6ad5ebc20924ddab833f3fc75573529c2f84f13476caf42ccb721013850c9181893db645747fa1b1b3c1523e0de87426d6422465ac2f0d3cc9454adcaeb88422aa5bebfffc19b3b6328d23e05efb519aacd2bbff4ad49eeadb2e71b449a3e85b4e48fdf842ecfb88d3e54dba617f02add7544625e7cf288b076f2ba03dc5b1d707a05e453b5a7e38b3d4b08625ff9ff930816e96be8fd9f84113d92c68b73636fde64e6c1fc41f65bf0b58860d5fa6b8dca282c4205c967a5a1b1cef7cbf5faa1fbd9d792e2208f891f8ef0c44c7947f0d49c55b254b4c3f9a76dd718faa7f2fe4a3237ecdcf8df1dee55ef4bee5cfcc9a98bba530a854bb63cc3295094a7719709eb2d0f155ea8b4650bdae1493e172ed05e6b481e7b441b6fa4ae637fbc7ea9bdfda41aae87cfe9c2c4fd1d4b7b6f4549dba87075a5cdaa20a34c49e7de63c530d321483cb971f1cde384575598a549f8ab41428b187425b7257bbbe9bfe5fbfac58ca046377daee97b756334f938421efe8167c25fe464de40fbe509046e42aa4a9614ec0711080b071090f5b978061f7bc08429c4ca3e35b480b89dc1b1c19659cd2a88ee336c1ca1466b25cb1a17eebc1f38902268bd2d8b819acaff50e450cf106dccddeb3afd212dbcd8548936dcda405f96f29f68f30c198bc1dd1c21e4a5b011fe0dda6516eaf0d2039a2c2db3f5cc21fd7585cd840f319e24512e17d15b811bd054ca64b3c1f34dbde99d7b2b638909f944fefd71cdf43de5cbf13cd636fae8fe46afdff2a5112005abf8ed412ddc7f6e188b62967caca35623001d705964811b4c02f143f5dca9ef50ce7d9d947af863efb34a92adc5f3d020122cfde23b6458b9490c83aa63158ff6817e620a2865c9b33fbfecf0519b07db612c562e2f05123e083dc17fc478eb9d9b295e6c06ac19e496bb15a0f99e6e6bac3dfa2b216837a3c51de92b1a7e13e7213c4d42f6374979a655d491ab45b44592ec19bad069a591657a40aea095b745c0d8646a3ae876c7a17cfbef04a372c5fde5c7d2cf02c8a2d00b8c7502f5c67af3e9e3aa71e9e86bd9ac52138acf7b419c5fea93d72157cd41de8ef8c61ffa6b4cdbcbb000c7b44dcfe46bc2057e113b670d667ef18839610801a7f382516624852b0b1719e92de5ba6447b3b43b6a2894e561fd80b18508a1f2bbbe59ac9f957079dd3a9d614bb06ace8fa2623ed4c263ff75c51fba3beb68479ecda18747469038f3535c5e193178bf35785e794adf098a68d821f19766f112494be5126490db3e919061e78ff522535cfcbb37d4e41239348b1ef8900352595a58316fca7445817cd756f2c26816485d4c13ef3a16ac80667579cca9ef4defdbdc99fa8ddc8e59a0410e2bfd839d90f540fbb5bd6b987427144df257244a704c8f027d6f1822af5916b63b76d41867c7e0750b75a1f6d728c3b276723daa5416056827dd699bab7b3c5b096bc98789c7f1785e8d39e11b7fa45c2fca10609a43b89fa8c96a123e91788ad35ccd8204b3d0d4091361e51b60726d9df6fbd7243f6fb1da2a41c9ac7c3d09644e9edf1b16972db7a49528a17cadf900b08354b3b18aa662f1fa3ebbf42209b60365b33a6bbe7eb0ec6576182ff46c426f18344d0b6748ad8ccf96d71e070c59538b79be3752a21162ad5a0646ad1f6fa224d1d9024831d15b31d6f6a835305edd9fd1b3b03873303ecd78dd8fa897a1ea3735acafe303142b3cfa9602b6663bd83e689720e1dea00fbe91e5d7f604ed0b4ed07ea2fcd4d435ee8e8f358309d3f121c0511a5648687c44e707af2ef0735d9f2f95d762440fea6e9dba9d356b430cb6d21c3e9be05f755e249e9e4b524933b80bfe0ebee26d414c8a1a9e603068010775cf3d417f652004bfa10b23e52b2574752150c2fa1dbc3afa6473030eb196ff9fc0b3358a18425504e46331e10a824fe88052cd05be669f0c81db798a5ff62a6a88eec876d4d0a66687344deac943c33cbf51d896d80fe718bc0e9ec586f412b834934ae429cafaf22a94f3dfcc9c47f735a93b93ec51dc45e3600893bf5647399af1bf39f3d0829f4bbf5f1b224f50137348e00d4500dbe818de33916d11e5b348ebcc358307ef217b9bdd7e0a5cb32e9359dda784ad806893d74efc6f6551288f00c4d9cf0d0722f378ed309daa6b72f504c57eabb773ddbf8b91e541bfed85177969ad90a1408dfae0f41585e27de3d71bf53ecd1415b5abce850a5ea6be58f6b59abc6f382920893a714943b957e283fb584b1cd459fe3047c7bb9c2bf4e7c46c14b5a7e3c8ed40a57bef0ccd21f7d4bb69d27431926c97678dfd17ae4b9a5324529edfe9524e65d97c270d5ad53f0598ed68995a2ecf0cc3ddd8d440af67b42619dcd01cc1bc441d3ebe1b3d7a09d989c00cafa06ecc4cdffe14855c4a53f95ae927a222c7f1f94e87f4ab5fd6bf6addf8a6bc0acf117afa18d1e214d13da12754b62fdf2a850099a3c706dcfcb99494b1d13f3ef412b13f6015954c97b32eb40cb42dc93b678c4e05d581e84553c03889847cc57ba8538dff333a71204036743be2bae5d6133b6c0a3117b683c917f5c41cf549afd6be7820f50d84f4e81911e81e92f8f079466af689066ad893d8571429c51ba2ab7a9687bcb4322916aa9080828e2f19b97432f418d25f00fc1a210834870e487f94c36d54e15b3907a91f3c1205f569822ee173b9ca08fe3c9ff864bad88b79f15c09283d99263ebe742d818c8ca8a4492cd78f703bc3b9837f78c827664d1bca283f0b15bf878d72d3b3496d6d4da37e7f13d4c4ee0c04e6de6c58685d75ddb89c61a7be8b06ffcf0ebd146677e312a73faaaba4c52ec74182a6085e7f5ad6797f1cb45f2fcc241f5bb1fe8a5dcd6856179e9723a236320efdb27f782bf443f0a39fe1e8b5b7a4d6919f3f2b6c9ad739e40cda91cab7fc6dda28d070099edda84c9a67ea4272bcfbfd9e8ec1105e31a2beffb0e44e0d1ed126d49f17b4ffd21b2a9b8f76ca6e6d7464a01953fbcb70123c199befccade27f231d58d833751d2872876597bcc22bd38fb0a218f477aaa47c48964b4f3e81ae59b0643372224cfe9924f7c61d27d5c8740ffed137f458f362a966f96c47e4afb1a1a34447e400718d00a42b3466577c112067fa461d2c4e0dec8793a4cc43e37ab5bee20916b36f9392f8f78f7781f9fa349c6200045422a2b3911d6505a1dff5c58256d2ede97d0924d9b8fb97eee89b2a31d3f057e1dc8847f3bb832f50dcee7d350a6ac217ce937f8eed8e9a12d8ba29c0cec103573a0a1add308e03b5ebaafe9b279868fc2208986655a777c1df8ccde2c1c880428a76288203b9b928b0bb98a407e460e8005801a82afd717253450da77592c60c9f8f5b5f9733bc5bbe47117ea065ddf28722e0bfa6742b96dfe398f0b95c1f84ebafc4150803b2fe3051d9438103d2a0ea06ab13de73c1998aa844b85f290553380cca1c680cc6ebe8e03003fa9fb9d1ee229d721c515bfac52a160cdc87d35906772256219fd2fb80d386ea183ba8c9e34cb1bc780bb7c6ffec63ca823c198735be64f5f81b42eb47c91031dfcb002d2f377e7d6786246b725888abd1214bf7b68398e9f5ba5c0a985caaea902aeed5ff1325294c57fdf575310789e20994efdb012e7e59ab52c988c902d17cd56ad27d5afd65ee9fd4bfa3386b606b39ed8c5e88abe430913e6f826682e00b66d872df12533e8092c8f7870c344852d445f5880e75ef569a1d7c4e59827e7513a5740bf30bdaca59d23474b9da3f7689483d601dbd40e4a1ca243f33685a69c325bb6868061e9e44e4a83f9ed2467f6cb1d82df11c360823db417cff46894408675a06eac803240f19e434d32510367f1ced61975c16f0dde1beb22bc030b8b64fd7686bc2bbf48e7ff48ccc14cc74f9d34912fe39f523bacc4397af79558fd0e83ff5a34ce827720cb88f6bfd45199a6fb7f9291b8f3dc8f447232cc3bad2531f61aaddc5b3815756495c461da0e8bdac6e6212b91686ff1e11e1893ea7d63223c180bbd214f5af4586bf88ad33fc47d5cc6b7c9a93e5350006b4fd91e7cbc7e8e3fcda44d684a3ef777f450baa49385d171ced49b2afe2f96a051883ca31d7b879d99fb1c49aa65e4ff733c376d438d143d4ea4564fb3dd97fdf8ec12799538e93ddc65e3a164781fda439e2471b300819a73b3ced73b482da6b043c43ce4b61e3e946eb55fc047a35fea6363e31cbf640253491a11ba3004958c2d509190181dc3b74cd73958ef15fa7a3f374af560bbb43d5c41e50ad426eefa8aa94ed1780a52f7a1814b676acf4342cb16be432f8be38df1d6ecb5deacb44f7b23f9498dba81555b55cd3afcdc64fce02bd0defdf658f068f26de766382a01a6cbf4a622b0eb48f66b5d0829c8b35e8816c61ac6b0253d3aa4c2cd363f6bb21a41c2b628a6a9e6f79821ae50031c65244b91e7ab06fea054efed5a4f5566b4e79ba5104d393c9ddb8cb3505cf186b4db3282c3439816e28c157c37d89c81511e6430cacb8fe4fb1e37b96fd050b607364a7105cc875ee25e8d20a5a6fa18085a982d8d5c3635d6adf2722ba4c43a518ae3b70997752f78e99d59690f43ccd500890b3cc0e4a30d97dcf5e7ceb12deb0dd74ce993dc159798b4f9f0c070e0ac64da6d323e0a41985ca039b4f0ffd80881b58659af39261d5bdca4216f6236cc397c45f29a2897e9aad9e457e85faa99d68aac8bc49563b3b48f351cbea60b0f0cdc7a3ead4917e2e81215feedd5e0ae1b2f2ce32489a8450ae71e2bf678a5ac00582ee958871ff4ad7f08db386abb26f29bb1fcc74f1e89c81976469dda9d36b346d0e69d4419135e4d271e926ecdf5517953782c3b3b7d72af8dc1532c707d3ec72466555d782e2d82cc5a7bc540810907677b50329ccca0d899c79180a5dff159dc96a6afca73d4396a910661971f095f09f210f7c6ad40c6938da1da24ae764487d438ff64891589653332f6860c19a44d28ac239ee388a814f6b910f9f872cd55d8a1943bcc3066cedbcec1d3015156a1eee8f5b6654e89634eda42a031a505004ff2ca680082af004ef810feaf00ccd2029e390e095a50138c7d5963f43060a3c0170f3baf28956c959de577ab534247cd8ab2d2781df89a90b9d6dc893d88ee5ed6d1f43a9b6792bdffe300604f42d990074fda5d239015a43538d365eb0f46f6d6abf2db855ed7e3252dc7f47cec211135d2d4923cd83ba3047f618d8011aa4b0cd1f4d93cb6e3f0c635cefb95d073c14054c5458fbcbfbb48f69c8a1e09682cc00346ca7a6beffa92255fdd664ba9150f6757081f30fb47915fa7230f3ef51617fc3a059f093057260afcc14223955f0fb8dd7b46d058d0b741047f877523842a1e315ff98e32e85f863f231c473fd19a728867b5f8c336e7e2ce154832e6971803fa511046a04d02765709c73dade5998367632d43ba4f103eb114da5a5c6807ef7df67d69f32b1ad05763a14c3b7eefdcc95a656b8fc8d0b9018f746021aaca369c8eca8e174b0f27cd5969dd849e348559d424ffd47367b69b0837798d1466ed0445720f95c7e3a6189cec431711d72f9ffeca9341f0485b0876d61c637a2cf6a44811cd00a1b58c962471cfa3ae536b6da20f822a9f53d799cd8c917b47bf4b734ac76b4134ca415bd79eb9b2778a7f67d99e674189ea7708ba9dda614434772d6f8d94e647a03312097a64f986c45a3ebed59b5218623c0efd784b343ef5da9622fbf32d80d7c2882dd0815407025129dcdd9b4b0488e33ba0fc42fe581a7edba110e2ffd2ba5e4ef6ff133fe337c8660698bdbaa91c038c2a3eca9111e91236647f3e1d3a2c582231cd6c84d69d98024e4328397661e5db40bb1c81c19a61efe42aff3dfe236d97085a7fe4e48fe2dc1698ab7124c946957770d2799909c5b52aa2cebf5fa2736dddfbe4996b65ae9726f63baa06f13a630ab6edf0506868121d16c0dad17bf6ea604e6f8dcb1f59c63505fd9cac674761cee7090f016142d4f1fcde067b6fd266e14df878b348abe87ba526a88bf728341bb8fe1b3fa23255470968eb69194d2963a9a04d61e461e33b42bff5c179f71c793775278197e85235f10d5f1acacbc62c12995a533a39384bd7bb2a350439818818029b6ed31764478bde67c25a5d53e0c83774a983ed2ebe9ccb97254eafe8a815272448577b36efd839b27dc8ca2f67e92cf5417cd17dbb79fe275188d0d8f2e441faaf2f4d2bae83c6b3df30851d8f3f4c65317ad615c16a3c1783f66a1789a98ec70f2e0622d04e406644c6fb2fd2995e8f5747305918c528564fe84b4dcf11eeba5edf5b03a207ea266d11ddf11eb8c0a5f38532abb311333d29d0babf7f4e84f93e9010c035acd4a030fad9aab237b17593c2685b45d47c496fa7decd652b03fa60f8224c9e9dd397109a4cd0e43b26c96d2fa0c4a7f652135739f8aa9fe97a179b6bdcd6f13601bded2099c155a02b0b3da6d9c0704183fd53e2d729609efdf3438fb1f72ee52a302484f020979235976ff592c6f2ca1fa66244da92a770e803ad08a15b1fb8039b7289fd1f12fc984d7a260d540608b34fdec67a886c96ec9185bc46c7b8ec6f3042b1d3ed5087d8af3d7970fce7b92f59dc82a1d397f7f5b12bf88f216c05b7ed2b050358790b192a6cb9f0db81e4b20b5f0ae7ec2c42278c97af3dab972e1d16797c538438becb1fac8cc63ea42e8c1986b5042cadb2086354df01135f7b0b52a6d6ca21a52075674b354511f0d80019325c24ec2da0df301d00873bdfe48239b4a6bd37a6ece93b3d37d7d479a6fa20112514fd7aa96a475fb21f78a76c3c8b564b6242bf1873457864e9f2487f6063fe4f49068b2aa3d81bfe42941b5b45153c74b9a691d365e11df3a1adc471a4dc61b845a4169774076c5c976faf7934646d6a883f632fd7fcb9087ecdb1067a2c6fdc8c67f40462b85d56edb5e7224994375dab9ee7b5a14a1494321fd6740397e78525f304b871ba828e25010c79ceb54205620f59fd69cbbc438a5bca9b2306d7c14c20f78057f11a53233dfe09083c08ee8ea32aae35bd1d95b4d8dd4613688810c4126ce4980e89a74b407be98b992b283c3d1aa9338aef20e59e3cd44cd164d58bf144399522784fe9933144af566854ae73c694ab42ae944e0c834fac42a327759156b41d5a8d71b8a00fcb4c8d067fde0a7e72b739a6294209ff44888b50bd01a0099eb9beb6af64d0b60f1e0339faac2593115a8c53b3720ab4a274841697c348243ad1eb8374dc2a4ab83b47b45a16c1695747eb3b9d495acd1b6f8045a41a6cbbcbf9f29ff2727b1d3a682c31545290306c0800639571f2e4b6e3dabbb09120deeb6d6e5e0973c4f9229c8bb88412e03f6667cc0805fd334b35a6743fc2706518646be89dfc577fd1cdbc84393454d44249ff71c25913e018ac8f428b4b64f6631feb579820a44e908a79d2e5f8a3d16dfbe69ace89d524d837838294c420578afd0ac2828846896c8d6cda1181f06f9846bf5e47a7e83b366bfba3464ea48296519cb26a46888588faa63498408e85137a95127323e64a301780adda08e729027f2621e710137bf63a4c883c4d052fbba2cb9a201336911ee31f0a787c398cf8ae8944cf873c32e3359e608b68fdaee7fd6cace223bd1c2911a2fd91b5e4581cf45abccd06fbe75418de5d1098a8aa9fcff171cac139879112039ccb9ef6daec1deb7903a49b74e353745a36adbf62b27e61d04eeb46cbfe122e468df066c4887df40387f4f723a4affeb4b6148433481d769ab8ce6f6048466606c7338a43dfd7da6f9ed1c4e200060c4154be6208c9fad2d5304b5382a5f708c97b23cd694ba88ef01001099502550300082d1038c0040219dd03c512ae4780510304f80bd4671480191bef24823013e68818992ba365ec4a0daa1ca98e3bf14863b82eceb0a329190263912ea6b7ef37357f2b8bc3ec51458b4d1e24850fe5e68b456cb72d1e82054853a8bcbb16fe3064cbd929d171b4181b9e101bd939ae5fdd0849cf96dba186cfdbd6ef2e6426074d83a14c40aae42111a266461f626839e096536856db7527f4e4bf660fab94554a4f90f143b1bb2865bc65c9928057a86b42b26c3da27db870cb5cd24786e8d2627c803f615395f054d17d1c281c2b2a94badefea4512aabeb6b167096988e97dcc1baa8bfe6057072bb4f4f30c0da1237c3b99625c43931cfbbc3ad37519329b435c71bc6773a0f96f1940b4298ad0453acc476efe2ed0cc3bfa5bfadb01869bc8afc8bb2a74e3540cf01fd8b885f0d7374e2441baad9295c06c6a7a37c8f7830f460c6b890212ef5e7433075bf1ad102af85cbbf9c21e93ac2414a1c1ea17ebf42ace194c37feb5f784877ea171712977ed087f277231ea91b0fc2899c4f8b7a6b1d315f2fa2f6d4de1b8b9161ec2c766b4f22429c0c37332be0ee7db56f1e260ae34c018763e7f459ec436a943b8c906414ed482b64308f2c74175ac5fae5c3c0f3ecd1225a426c77724866a2d0e7f17dec5df1e8007834ad7a2da07c892b58c278f07d259e68750011aaeaf34266d29399522d62e10a7c7b906bf07b83f6eafbf3af745b3e2a9a440b3ec09a7c9f8343107338aa04f93b2a06c54c6c3f1059a2ec4f27de1de9705eb5753a572d25c172c94e99974e44da8b11734d266dc5f136ea180c854569ef52549e3e50f3f96db4bc6c1e68c3e50ef0745595e66a54ca0e17e0e1cd2e8be37046f2d4afc3147812c80b412343840133a38ed672e8a2313bb4200f07f9215737d6bc215e0f69ccfbf480502fb94814c7a029a8191263a62fba4a23f1c30580ab4fa1c6b38b09b22bdacae648962b7d19637314bbacd8cf4014e46aea0f3e23ce2218cc6a6333d1f617c696c38a3ecc6c277f8df0d2cdbeac423536482696c9438a16d110979c328d8b91de60813c01ea0f9bedfe5e0142359ba096a9aa16a8fed6f24daefc6ee02298413d2b72fcac8f0ece81551f78a534f48015a0770c699fb42d7a0485154cfc294f721083483af84b1c12b0519dd01c25d282ab9c54ecacacecdbfb452de8a6adde4a4903db8044d70ee2d2146377bd1081f977d5a400217d68c0c47b7b7afa88eed7b098126a1d76d6c9caae7c64d9cb27cc0fc4456879f7bb6a31415196cdfba429ca949e290987caaadc60b26712a8d8820a8201ba837f838ded91a73c5af205e849b524b8dc07dacae1caa1cb8fc255813b76b51780ea6550297a4aa2676b5f0ba8914526107ad11a332ceaf7d12693d5062af7adf5ae3d65495e5a65f8d3184d3d2932a987be51ff1ee2aa65c1a4efd7096ce79ba1d629c606248c2be9db27a8fd84d6770ae5a43503272af3acee10361078a6ee7f1936ec72cf9d3f37d0632e7734627ac05db5b4da6a56d545ad6561fc4fd2579086ead1d6b9cf152daac183034f3bb5957f654a073ad6f323ec3d07cf5b0581bb961fb1273c18498d187611d75b00c7a9ecb36a44024d50df6d5829be3251e338edd9883cb8615ae39d423bbf32afec3102928462610780c7d478a63dcc4e0d9e40b18c2a9a9f90793132ca4fcd1ba34c6b058c95f527840762b2b44cc89dc8a4d8e7ccd3c1b820090f90902407a0473517a2bef98e12c449ac99fafa3d400719e6c33cf4e648bd7713cd85ff40a99bb50563e1df020f37ff3505afedf8c4bf074a308d3cd9a521dcddfe802012e12e2978074db87e68839242cd1aaa5bf45de5072515ffcfb0a919a19505a96d428ed54e76edd6fffd57532c00c1f5795c303ab9177e4a3700595a80967db0511f95467e8f7891513b8d740a1f8b7bd9bc620291a4fdc45340486cb4af7f870cf6caf4e75bb62b9ccc03c65e27dc11cdf09c1015d51e996ec8c175a2413eef8f7e5c37deb965bdb198e478c4b5c82bf1fe72aa1939c7c62d730365c7ed03b930763620c58e2d0339ac240c2c38f1ec70072f6be1907afd0543247d9500cfb309b3c7ce1df8c6505fa1c0f5d275c0995b862bf67b332c1681e88140a40765a86f50eabbb32d88ca6f60002bccfd92714233299b0907927385056bf0688c0d26e0dceb320141f82fa607003007af5753542d60332231f9a8df6981beff2185da5d14fea5147e6e9ce4ad7fd6ff5c144485035fe2f531624999ebe3c4bf53d9e958e06b2c9f0a768b029596f1f6f15289ce8cb02edaebc03d51467d37d260a35881424765128bff44d578c632932a044d4dd0111189af8c87a13be773d8432b34355e396a5ab478f5ba5bb1ddf613c03f99b8e7144b7892e49a625ebf6a7dcbc59a3a963f6d35821d509d4a532aff611cf242e2cc93306c6018a691c4f4c77679fb0ae2a25b2a040545ab0e0b7b24a11612a2cb09a37fa112f32934ad28d7038f2c515ac447546d919c457503a56d4ae08e898f3e3959cf99650107d6962e4b26d8efb3314ecc5a5b317d6ab66e7f643a4d3ff1e51d640f8a6cc3542ef97bd3529e32f87d3e1d25583f7cca8a121eaf28ee6a20a4a2434b258c56978dc3aa5644a81e83f020af915077f6865ddb750a1b3dcc857a7c6ed88deb3cc97cc8be8f39b73565de288d478c0f6bf6a48093c65381e431d7315d62babd2e7c6188fb062632ba47cbb02668035aae5b8f3a8a62b9fc69f701d1efd1527abcd038df2e435a2f57fdcec2fc8cfe53635328f2e1c6527f838ac35b5a10f8b9469aa46aa5e1e9b5e2dcc6f0df4e1fe51b03216ee8f49a1e9a6e1d806284c78611d61b4b610b60c1374e93cc2c2178e732b500fef282355abba06c806600533271afa5af3626d8e77e67ff6e3321beeaaf876ee6abb970df98ef0778287802c825bcaf4ca072d285dcdf559fecf313c857473be7ba70db5010c2809d069982a26416e1caa6b46e6461c55a35ac5a8bb6119e442c674b9515c9c02788fd205d7e12097cb1f33d54be15081df5d43a203a172607f3d665474486fa78e72250ca15bcf25d3c2d0cf72e6d4fd513e7fcb888649ffe10854c14660143b151179ffb8ac359c5671942778e9512acb4c0e9712bb011faa72ecc06b702f92d46380d46a58c1e6c3ebaad719e68e04f58a64118839d6604ba657bdf9e95425bc09af796fe14c42f3a5cd0ae1629e50fba2ca4a05e83abfc4629314f3c54a9892d9e68bf6abd4064d3fefb220259f0c34b3d0e05d24034382b264676ec0c1c83a2b711bb0f18a2d85617cf7784c0b5d5b143e8d901bf773ae5a48f2ff86d0e3a6cf841542a998016febc0a7caa0b68cfbf707081babfe7dbd5faaf6b7a464bd062fa87bb344827227587622561cb0d5a211507f9604850f9b4f022fa59a716dab5f9f763af083b687d6f1423d536432c35f51714d9f661460803f90ed80b8f8903c95ea3a686d8b4b65fb36087ffcf7395b9a717fb329fa67578b5aa4b7e660e37889233c927d9643970dafbcd6b4d4752b152b0adbfcac4a8e52a7aadacf66d338f85856b63f94a54ec939b6f6e50d2126a4e3aba37b97226ac1358c34fbc34e131fb69ccc0e907ed9d5a865589d6d74f0c400054ad40809cbb513ac08a9322f0a36711d9383d957bd272698bb024632789e8b5f94b2b78b5183acee5451f9fdded330d3135c50668c46348c79ac0c2c3a1c1714b8dbccae6cb47d62bfd3400f2966021d86f00e0dfe8abdcff9ba7964e62cac3b10ecd290ed09f82403adec72f0e93e330e5f031e3a3c9b16b46e85353a7eaf923d97e7841088bb34aed8cbdb9c60b41322444b7342c4d47250adf6ecb551095e8ac0cf3191ee570242f16743716382370128ce8664dec240ee1343387568e915d4594b3f981ab815daf015e64f8cd5711262c94f0cd2d510680f0ebedcbe2044c2b9fa6f56fab354de4a16d0d2f139aa826b110de61610fc94a81de3b7ca032e953adc40cf26822aa904a7dfe4ec3f03a342afa129333a222511b34751d0329ce615ed3b583322003cdd2d487eb396bd360fdfce290f7d454b3b073a5b58fc928758faf6f21d8d6780a88b0fd7c0baed1fdccb58640f8202a83f98eecf4a5af5888e36b1317bf7af889d082ffb091115bf3747b2c34fa4dc38c07b88177ad46e114223846c58b6acb2326a627e98c8c4ba6055e183554faa46c43b2a6e41911a63c537a8be0bbc99ff9ca0a5882e477ff629db70eb325598563ccfc497392680c1736af25feafc4586f412f850061cfd2c3f8e50fefaa486f79596cb0b126de49cdc55ecb9b78abec0b7fe07f022e6f5f545bf841d509d186e829e272de6aec925daa636e8a218e61ed93e9d74424a00b47df660bd8b4e8532149ce0a1b13ecbb372a586ce484c2d3d211eafa2e464b9f4d3bcec05f1d5efeab5d836fe5178f70adfe2982b43e9a842f42b8bdf4bc6167cfe8f5661e670c6bf8c4b6044a5f994432069680d35780997b076edf9fe3bb351830dec1f49c3147e4de54ced6929a63ffe5d81a3a301ce989cc5cc9774a02bed65c091d4af7ac931fb496c2e72e2542e346dbf5a85bf6d04f85699f2f87f7dcc2fb59bd445644849d7d133bbb94a5f4b7908e23571233e2265e3256ef93460fbdd596257ed5ce123a4c89a4bd7d7aeb30a26d8029ac0a4ab5e339d096139af7326a511bef477aeaae0dce8524260fe66aecd263fd7c828f6506f718293d6d7fba82efa12f974f7feb8ade436ebaf958d0cbd3eba190e2758b65ca741d63c7d73ce469b1dff893d6fcbd8b85deb6703902cd8f68e80bb074e5a8825a98a532591a4d45b11aa2fa6614870c9641ccf30286ff185efd9c677cc3368d50b5460a7396a74614bffc4844b164ae57c0661a8b4d7866825016e48a18395c480b0d2f7f4f9c7659c9393f3f605877f2c90f10c67aebf986c781f911d5496b1f54c2e8e44f8aa3da62ac19102e0e550a1e5807ba7577f5d47be7dd296a0e83d2f1e14292614407de6c763a6af0b291fcaa94d78074b4564ae9ceb9e5048d0851f1ac2dd646ada12f68785f867430199980220a4880fb64730c3ce5789ecf1a4683a221b85a14f24d71a7ebab8aabe5ff78047e82ba5720704f4715435e491b2e5d1623e5649809cb7a6732b4b43c14a5a2be378ea4cfff9f65637bf607ff8d3053f692caab577d5e475443891ddf0bef056f6b1f8a61c74fa208a7a4a9bd39f5c05c2372109f11a46bad749989ad83f16b21b56b83141fa747407ab717b93cb9e66ec0908fc351fa1281cde0aac5e6b1b6fbc6d415523be27ac16fe130af6e9674651d46ad2474bb5251653e352a562d682fdba4e3f1b81d0312bb04b2b1f6e1d69b7b6b0eef7ed05b493e34a38de4d8235100c0cff17093d43b687e4d3d4095960e04f640cfb1bb15b4dfe3f70dadeed69031d1fa007b9d16d8b8941de33fda4b6fad3d187bb82b238a4e5595b62f36fcb9689be7d47f63e0c5a3c7b412878ebd51914aba35feaf9a9b624396dd83cdacf94c219a67941dcdab26868f91fd0e88068fe3ca63d3174581bdb397bd825ac396543f09be6028bc2a6608dea4f7b1e03821a7b732bb5760a91d4806a0f4e402d583e9ae7f69ebf0c6927b6968fbba467faca6ddc2b0efb90099d14c1b90aba0bb5bebe24d1a3e8e9f020cbda13e5f6949c4f15b055fd6a616fed5060f22da45a41e96f533316b31420c5d08a392866093c0904671b49b4fe1fcedb20904d7e184a6a97bd07bcd223ea7c4216ecc1f42dbed2b9f03784f4775cff447b9498ec808c907499220f5fe75346b2f4e460f400bd013a06d0b0c1a3c9a640e843b5485d88fc869bb35fb48cc320629a424b088bfc2a0dfccdb7607b3def06ce3d29e591e173693439a4586bc5bcf9d4f1267c4a223f1af3b27b0dde9991880b8890a1fd64a6c45bf7166508260e27920e657fcb168678a76021809a9f6164031047e7b044675e1f6369c67fe02773b8d1bb10825b41d99aa9a49c3890a916089ce25c997eafd1aa9b0be953e4e7c2d32abbad20f46e3b05a4a7f65968d0caf45f49de11997a6ade9a4c97cb4d68fde349d37494ecf356a12f43ea569d906b1944b791fca150ef98b187fd86e99f87fcd25e13e4af3590d2e64731b882cea3989ac89b3ef1efeb6e4fde10658ab89dbe855d2691fec41142fc25a6bc7c212bf7f480b6d7b505688d43b0e66c5f5bf3d8529dae9db6547e54cf27638d34bced3c3945768c078d4a8da8712d951444816c88ad9c64ba97be0bcae5d804b4a5ef10521655ed1cc62784276cca1c6608d1b494f4d9a1abcd0fa3d2730566f4f4bcbfb36ee1b886e2b97e3032b13618c43b3f9feca888fabf30d93ba0f0846a40ac66f0c9406a2b2dcc55b5055f7a7571862a62b00aa5e1752c8103f25ccdc5cf24c21791c97f6a6f4e8d2914207a79a3e327f17ff06c3e04cc27dd408c72252ac6e149c468a9fdab156d186f03cf013e5a01946058219bf606028bc4c8f4251d74a779aceaa8f8cf19734eae44e8280dcf32fb536449e8a6471261c70c66b0acd6b4f6ca440213f03057b6788d53fec0a8e41c2cfcd22fb682a29bc3d52a6deba5883f0f8148d27d1390a23c30b7a14e1a202ce1da0bb208909583d2d65ded58dd5e4bdc644afc0e42f0c9127dd74450b13233bf3c3a94c9038e0fdb35dd3953882c908815d1e418121606b9ae9fad2b7d7bb370a32e577e9c9d601df26986fce07b330da9f73c9fe89d76414f9f06dea8810e7d7ba298c124c4e74193a91a76fe9d9ba873227c2d2d199361ba9145674aba6779773c1cf926d16bc4ccbaee9e9fe8b28f95c5b880c696cc1802287576e0bff37468a9cfed45fb698760ad309e2abffc585395e2b9f698c313574706770fced6036ac27dc7eb0102bd82017f6e4a3a9a50f74b5efda192b3f0fc2ffd2a2330f8bb1406746e260ede609f25a1333bc31a3c2df42b6134ba417d85bb58b75fe8b647a4afcb5cf5e6b76f7bee6a50f08aae17ff5aa967e6218da60b108b855c491fbb905d83d2ab4a6e9a9268316a937680023740312efb3df6e9b2ba6dcefd23c1e10597c30ed7147a5771e921e4ca06e12f4f556b10d94b723103c519a06bdc670bfa33ea5a9f80bde8c1f89e0f9a45cc2ffe7fd4fa05494140e9586e91bcf327283fea9889af13f465cf5a2149db73a24548c33ec32e39ef569e7f6d0fd8a902e672ff1eeefec205a4c55bc1c3406face0f80e08710f0ffb99de2d5e253f7a1ae42d32ebb470c343ba104cde6a4c22e3822f8b40ff83466f634207d6c207f14ff3fc722cfd15962b5db99ceda37f77f0f389ef45217aa8daf2c9627905c63abe852a1c1c0efe51e6e6a955233edec49651dae3d053c8c7a8467cba0d5a9e5cb18037c11b6358fdf8d995fda67a76f8029ac2e65fb4d21b1a327b891f9f5db675288154e3ee6f2f93caec9da32219c8d5d75b91f6ef90a28f84f337d68a1a759dd2ff642f62bc32f159d165f4d02bb0e137ada841e5514b96990001211821119f263b1c7fb64c507fc67551111bfe9884bd812974104f99b0df5952e10c232d9466541f0367d26b1f2e7ea03ff77c4321f3f1985f8dbc8419c0fe29b8539bb0dfa35ae05ec00ebccec02dff8480e2b172635311a5c6a46e186abb19c3a0ffec3d0b4e9fa144c0350130927d7cef370f148995f85c1d5ea3b4ac13f3b9e28c687b9895d7d5d7a72fa76d3d100c78af2360b9d4ddbd943e0b24d511ee549fa31754507d5f9e182cd82ca3d7faf200083da025023996a79377e7fbe0ea247b199269f4dd3dea121895afc43ffd53b39d10ee55a6f7dc765818dce9316bdf1556564c491dd8a32b35443d8cd6dce7b7a8d8e7f18574c603d162206dac8713a885a89340f9da69a42c7383e5042cf5808692f8904fdfcd2578724d854423233ba67f8d474af381b53e948c03ef6ad8fe7511551d3583a60636bed6ac68c08ae3dc7ecffb356e51c197be9945f6552b52e2c1ede4db448cdd2353f7db7efe0035449faf32db51d484d31f6ce23c2df503685367e3fee17b805c14cc6622c95a83db5927f3067ad843f8af6793c82be3ffb6258358dfdc286b3437f240de9001f2c11cd9ea1e5b05b9ef089231b2b85d82b0f7d139a488984357483b468de65a2a85333e1e62e7cbd6b0198833d37d9cd7e745abaa26a0391ca13fe8b8ddd4b721c7c79041e53d48a8547b69907e5e4376176ee057dd6c7933f7aef45f23112bd0f5038a3c2271e07cdd3db71935564c2578146b1c6e453b19f866942552b694c758770d0cdc0bacb05b2ea9c8fc3b08292b6dd572136cce67f9710c2e898b5befc070a9bb07a760e16a3db79164f88ff739d32b35485dbb80ab2fd4dcc9aab8d1483c4c4c3f3dfe50844068d492b69041893db95fccf638fd5367027a6eb78c67f9dacc10c35da3510ee5377e6a1542c62251c54b72d968d3263b7003c087eaa22aff155bb1076c9f42499c5f797bfbef61e151c4b9d74a3bc728a36309a7ded3756292a8eaaa229945e6613f6dbde35f22b5978f328059947e633b585ba4c3d15c02472094dfc66ecd4195ebbe11b7627ea4e55c5abd9d70752713e6a689e9f04de9e942b7bbfc85aaa943d578e9a14d529d5db4b68353edd44a1daab6958928671d3c33502a9d8b59942ca2e0724a7bf70640d5a8e569b2786ce095c3451362a9c069f14316640726d978158d99c76b0a9d1939502d133f93105f50561b1b9245eba10dddd0d3cacfbd11948832ce2e190c7a9f18e451523ba1314f37324abc63593b15e9f9500b79540faf12ac396c47bd1ab70445d03493f0a89d4d24b81e6361f03d04020a4281c9642b679d85851590603e8534430b780e5236fc6352a120529fccbf234226d13f7b721ae0810cce23419e35f96ff1d231873d8cc1bf17e9fe1ed9ea49b2745aeaf02406f6e91a324c25211d1d08ef29d280cba7c8b6f3e72888c0319695684714713be1053112c408e3a9dd4392cd93422f7e8433fa5e4b40780fcf7cfa3cd0968715187eb58546eae03442f35628b930310e0d8391d6d4f1d6760784bf88939990be72646e4bea9deaa321c1939ddbd2166e5dd8a2d723e180e8e70fc263ed521e9f1c6b191ea75629a701118f15a3feab3c312f2ab253bd658fc679894c42f553d5e70df2e557164d99d30a7ff13b913a1feedb66ccaae2d3c834ebffd66b2dac9009029d82760ae3c1d2fd4c80c5c97f62b7bd8215be6461fb5098dfbf36e3e1a6f853f6b061c72575aea494c1d52bd27b35e3fe2c3c4072bd4897bc41a85fb747450360409c6e30aec479d1ddb9989c37ed7f3c1295efc49ae60fd77aece6178b6449021d008ab432d55d20fcd5cc8a7def154e4455f2edad6182f3f3ac3564b13b5ef73cf54eb07c6018bc8707fd8b6a57988e432595488868d137fec99f545ff2169169c4d58fbd52906274c66bedb885c9b81cc0c4bcad36a207a157bb4194e57324698213bee053e8e22aa36d50c424100c33ea3b934b8a3d77bcdb33bf2ca8a1b3881e7c2dde19515048419d080740c0c99a929d894e90203b0b9b85a3617d93f646154c042313f238efaf3150cb5c27000594ba69f69b046a78747ddbadfda5c9cac12b34d6ab8de62538b88952f435ba137110a0487c39d46879e39db29440287e93184342ed9fbd441eafa95da2eb5c901f0873f8e0266eb187870b698a4c83f8dbc45a3467f08e55d472e2899ed3dc3b96706a26bc7eed369d8c6dd2a58a6cefb78c78fc7c3382efe9c7c92264de3d2ee2ad3c905092041cc3f2b11af8cea997878ac72d3de10d4a6d4cd41279b02f4a1d7bf4b41f4e89f1108455be5f4ce53d0a4f6e29891a7e6ecb6221f4a82f48b4457c9c0d7388fefee40d04c422e8c1aef5a91847c75e5d1221069d1c136459b16c68a49bae118c9956355e48895a7e8e160af63bd46e681e30aba8c8d8ce86f8916542337a89d9904ee0a40b0aa7caa4fb081f83ec56ab3175d1e109e32796bcf7a8f7cff6444a99c8116aabf118fcb32c9f5dc0cc37773a18729d244c6d00c9ab4968eadb1ee2a01936c7139c6c6aa1d9bb9358f6f5ee5f1902fd0f1a9f2a68f3977e03b2e15bc48bef02d6f67123c55a072f679f2bb21541104dca56fb15304aed1518757c6b8323fdcc3e2498cf25d6e79194dde11afb5e4b843f865d505314bd55863a41b83d34689bc28c60457ed6dc08b0b8db5c979768df8b9cc51ae63620c5432049c7df6175d96efac2a8396382734e7fcc6d25ea071ba05e3440e256296dcc2d739c07fdf7fec5cfe2bc0b30c0819a10267db813bf0cd1c2a1cbf8db708dd2d4d20dcea3b0fd65c91b9564a86d879fae37c30cdd0b7603091dc4ff5e99bd084fffcfe3b1a11440c065d65dca09e381c4fdd8610f4591b434281f5b6d81555561a30889f963686496a1c8775747d8ad920355d595924622217f7ebbf0caf7b4b15fb0fb14d1e3271df5efeab0ef1e31bf76839485dd3de58e403375fe5c3eb016b15bda9c6bea2d277e5747cd862ad12a2e7d07efe6e9c4e1af58c6b4abad5d7e605fc180fccc02942ed0b14c5ec61ffb237f2361dd2f2a8bdad03c9f109e26c93073732c868727e033dd7e3a0c0335b2191c2aa648d8c27006200f11af36ca384980cf2931917cdc7cb1707e2ee168cf829b626c2a6d3272feef82b811759eb80dee974be47d5014035609377bbbd010a9cbf5c92389cb7e51befa07b76e558136592d5dccb6d7f4208470f7048b9069b257c8db23685f19b0be06ac2bd616d07e42ef07ac5bbd3ff5911f4a9a7a7518501b4cc11f8ec44ea16c2e9ffcbe622fae32f3910bb36ef9ef59bc91958a326e6f340fb86d421ec0b65d04c4f3ce98a71c13cf993fcc43b4a178cd917130aae3ec0b343b821b6f104d994489be9218129ca2acfb4aa94f3860ba70780a4c5f7939952b16c44edbcfff36621cb0d965dddb092280713f257cc31ed192090294e153f2b7b6dc0387ca8f58c1e45f5dfeb14da64a3280a38f0199c4092d9cb4f60b154a253808f4f2f5391ccde1e35232e9f114834e6127e883c67dc2407c18160501e32a930fe362497d7d2704409aff82462d3f4d0b0d5f6fbcd94e0e9c037dbee7291b381ab67e52007c46653afd388032de6550a75d08b9dcf44f48b156d7c84b5818f1343401c2d0b851898c6ec2552d70748bdd79424f2451cf3f2bafb527e1f1be576a987d3288732e5f880566bd8e31db173b5926fcaff19f61f65e5e0245306b338747fc923abb7dbb4590cc92626c623ca62cff030e021a0106badfd123adff288167c78415cc1f2cbc898c04ffba623d731feb99fe7bde764311090fe05097c103f2c8b3f50f5148f910b58ba7c4302e560d1bd0ab469914367a3df6181429818cbf2f8aa71812c165ca59926dd4d2206fd06a36f33928ee847f4e68272b5e9fdcf29ce92435a1be5b407616008b8a721810d0e0fc83cb04badb0c6245f92e1765a0a2f3146ee98e5ee3db5502e8fdb0a57ae5c60f77904912af080daffff4918ea87551e2bc5c0ee3e9afe39a0ed99bf26b9bae2f62bad7fdbc0f8040f74b9cb325b70bfde21c3a036be26918f56935570782c5be05994d37614e7e17844ab3334341e9e55ca211270f7bf7d7da4768fabb0881c56815a4b00a5378240c7d81de4de674a8387bf4793327bc064d6a2d0ff6116027f310d0a86084724a15969c9ebc4bbfe495bf32f5bdfcdb4991f23f020dc3edd55538f6a7c05361f8e91533e4ca923d9d3abc3ae08f277b2aa83bff1fc7bcd2599c1fc0c86789102b48d1ea47cc7aaf73f790a53160124472238a68d32b7e5890727abadeac1c4491c32bea4853a8cc83eed7c82d1ac433246ae4907790508e1a1aa8fd93c54090d4eee8da500a0fcde5745948e87e3d4e1b98de54c6237cf1f60d57b7208881f5616085a68638730b239588e3315e58483f377a91a5e3e9c4a9a2e4f767163ddac9b284adaf638e023ddfcbb408af9a913570882973ea56476b24db7f86a7dddddc64d3588abac6fa3d0d8daeefae3083336e4d3018bcf969945d400223ccd4c403b4482c54fcb6f23264ff7d0f453c28118789d6bdbe73e93bdbd292446bd1d62cda7ffd7b1fe14622523246ca3c165527569bfc8e4a8e76da4d341ca87798873acfee1005d8e8a00308d9ab5eb73ca191251c31186611c8ae4d25eb8b36a376e5636c509d7d145a1ce42ec106b9ef187a79c76740f4fd23435a188108ab2dc6272d4e7a577df6c57da8ddc39bb70f38fc58809a7b182dcea50ea82d3851c74ffdcb2be7b451217c56522f690e29b8cc1c25b80e97dbd7c447e8ff1e8519f40eb80c7963409eb09afcd9246643593104abd206d5d8a48707ecbe51cee555a5ed3c2d2fa6dd17204888b0aa644d73a92f489581e8d3e9359b213078355a8c1dbbc55b3751e057c166bc21b7a792b6d35f40db05710b9951ca7fe7716f87bf529af18ba838e5a2384af1977b310fdcee0b4dc23700779e61b106b0bfc2f7a56d7eaad89d997a682f53f4c3d0436deafacb63e011339de01f4c4f290cc46f3109e64aaaeb8518efbf8e81902698a5258561e17e36c4d8470a25f51481a19048bcb2fd2d4ce0332e28d87fb8aacfb6e8d6559fdc76a57aff28768a0af7ff11b642a88e650adf3723c71f3145d61df4d33ae63289e701251d5b7a09f13f6b930eeb0fb494e1140009711f5fee9220ab11b0c340b34311fe6f188afa9aa55b1cfa74af0137060734e4411404e13e1744f5cd87d5796a7883273590ef2978490a8e84bc23bd384d51e9fbc8690fab2ded4c2a75e1e4526a01018a130a8d60c5cc3c35a34d66b7c2592fa1f546e7f1da66ce2fc84416587074129cf7107fb61ead8e08824c670320643ff9db05728d6589a9dfb419cdf3d90d9cde3936b0ddbec904dbf43e45c09f27f9ce07e201a681d423ceab1f708b497be6d1a67a120e656c79960885b223cc1caa9b81fe4a9dd0b9553a32f9592669a4f986194c5e33bfa612380006687c1f71c474257e5f5c6e4578170b75736d61b378d8f3a7222d23ffb483e03e0ea0a22ed5be2f5cedd827496e2bd0afac5f97afa672f10b30b91f71d36d561527a9399582f0ee28daba1930e6e8d1724279ac26977652526c217bf2b3e2404f7748a123e4c59461fcd3749db3bf8a8320e8c7e6a8abd054b250bd8ac62d8a904a81bcb883e13e4a15f560b52dbb289691f39f5b3a78cb63bf1d461c330773defb3c3f3387bc59988f1fbddc02bda6f86c1be469eb01fbf54784a8407e8292bdc75901c3aa1e09bfd03ef34004aa6ee1ccc216a03bdefa06c7a7c4c751288c1caccb555d4c90f602687462f2a7c0b99aaf706ddccc75b80e2126bdfdbc1c977577fcd6a412e4c8289d9920b6ce9742b52bf5e556813a1658e97fe425878953a4f8df703b6db9b7dc5196d75d38b899b963e490bf0e40207ce82b960acc4d6734931c9bf087acbfa920c4d14270ff00a34c0911e4e4876266807d04e8db4749546a04df8cd7f7489a3ec48079c6ae835e9ec7c315eb4426249afec905803da45a8595b70195f7e123cf0f94cb7ee01fbe01558dfa7dfa4e9714abfd35973347e7cfc115f4bf333dca32e4e5cd8a7a3857bd2b916e236d65c403b00d559424ce0e80b8625cfca2682f0212f48ac5be7613becb7855184f90c365e4bd7b9dd68a557fa0c4175db1911fda8991c0ec479711c1719942fb5080454ad8a701b205d31729a1a283024d8bba85f9517f40fb2f5497b8de36d8c57300fb2f02048ef9a9f1c570dbedf97f4594adde480a3c9d504a4ffc8c8814912fd3a2078a82ff9b47e541ce0006fc46435fb33817f267db67c7cd8b8b1b129fb164493cdf44c38aec4f2db72d2506bc7c3f10776f2bf2b946101b2f7e8289649b5ce9fdfef115ce417c99bf8cc1498dd4ac1bf2c0daa22042abf9f29c30a4963c795e19a56046b65a3d00aa8628ec05adc729609d49b8da8403d97a747d12fca532d699bf89fd39bba6288430a4b6ee89eb4b7f3f6cc2db20fae56c9cb661a32999cd36b8cbdcff12de442947e253ff93889c0c6f94c203c67e0ccbbe4aa7e0cd9e22517a7b4a3bc9a5ca0a023ac48b5fa46dc029ddd6040266cadda66774825b877da35a94b37a44b80d4b432bbd5865cb89c80f5458800b39f6ab6b29f43cd55df9e2558d57da52caac5f6de29d0532e2fadcd3534d942e30699ddf04467acaf9ca9cb3b64966b7d4091646f993bcabf9296988440508a7316f250c5a9498f729a43362c5afda4a5bb840aca8c4edb0bdab693ee10923c4b0406adb5f8e9ce9d018296b1eb640ffdb81937222949e414f338daf220da8987ce3612ce6dd19b0d5a263a90e8410e445a08549c37d49443a5f5ff0e22274ceb48f9724887098285552fad7fb0f42bbbf6e1f2b912cc2d345460da59d248a3c609354c9d40aed029bc7ce0505f18182032be8b61fd1fb9850e32a6c291a97081ed6668bbe8b0dd700fe04ca7a9bcc54f839b95dbca5e87ee49695aa9f324d875ce87703196daeb96e2734e1a8888eac523b97881380e9b9a2f2c6c881551118bd953c738f94db73d9b819eba25e0e30008c667fbc11a80624f38334a48bd6062d8f548e053ce5ebffb90dde51b9d74eff33412fc1091f56831f663c03ea9e3a0c4e25203779edecbe8946a149a52e62060d11180e28355b84bcb1f928b0bed880392642cb2314db8b41ec350e1ff091a39903c93f543209c42349e32d1a8cb563376bf68f60c7652755b5b76519854a04e216ed60495aea3087e7009bf5a747c83e5f5beb4b23784d8c242366b7b54c4a32d2475df801992793991ee81194a929f28c97655fd511e133d273b0242b08275ec8eadf2d41f44a6481d4020e06f998867090c1da700a7b3ac40f8ee83f28cd47f9e71b9a93814c3d6105549af8039c04a8c2236f08f41bd47e6f85db50a3de78420a4ab3463d9eb3262214efa783eb24012a0062952cdbb79e26764c1ffaeb2e4ccd17a4443a0b7cdab49d96f6dca96e95b685f6cbc96e45a64f1b720b1e9747cdafd2baf7a8997acba653900251a7110f32c4e0d0cfefac8060aedd6c6c5c3eee1d201952cde3a0abedc9127c6c3b84a1360f99937a99bf346bf95febf66be30ff189c66ae9ae80c9c21b63b9a5e3164878c46c9ac85f89a9417d3cad5247b85c7d0c9e77a95befcb72a4eaf4d9478202e30041a63f815c166a0a5ef00bcb3aecd50ae9e5d6be90924a7f66a00d7167a3a24f3b96236b63bcef667a45a1a1ec5bbd603aeade2b086b5f66ca6163dfcd2b4307f9eeac8a3d0a9ca1aaa40221be2cd28517754fa5529e0789a32bfd623435d9d661b2fe602c315ed455f4819d60371dd29b7a75a8ad62e321b7b4e5a94a2684287016fd7490e4a5d114ac75970a16f8da25396201db3e36a78b8c5e041cf07a7fe833da45e54ed509e0c730370624a2e684b6d4f9b50cdbb56b7467544d0ad7fc4acfd1a628c1e3a4299a1ba5d602c7da1f1f463e417eff8a63c7774827db3041e199613ff9368b8a211f1de4abbd735bb7ff94ce63d4d574b59b4545d55f230c720468a8b7b2ada26abcbc9b96bb062aff489cffc38b4cf3dd8451328e2dd3c8bac7d9cbc6fed5a6b1f131678eedab7e9aa35cd7ca1ed2d7447b10c4d6ae1adee9d5d3a9fcac77168f673a367048020c4ed86bc44263c0f6b885bed3948ea5ec89e670ba7b65e85dd042ade2adccadcdffcf1fd36f8e6084ad9331ce7f1c1253eb157c4878aade598b9c2f9dcf5f7416bb3d856d6719c44310b1f031492676f6844aaf1323038e28e4a2904c3c81c22b8cc36263962fefba22b7f8b824238b51c2bebdaab94cdddd91c530aaf9e85593f06cb10463ec5c6b0b4961bee1ee1570598c99e98451e98c1051a9084d711ea12300a612939fa9ea268d0cbed632220192f274435c6d37a8c74d11e517a5f4add6d39250906d31beeeac129ee02daf84d6aff3a3d382ffc228079fa6128f2d98af2ebd2fcef7e1fad839399500a4e48891aa92361f15c61ffdf956b59ccf4c14f7337c5abfd9bde1bc7ac477c17afc26e1877f54f65a7589eeb8894c69cced518e1828e157d9b0323fdcfa0093a69da272c6fca613d76c3540a0e20087d7b92a843768cb59b64f54e3634a1da188e7de5dc090562293e812af1302fc3ae8e0b47d2107ab7b0a4522262b7fd8115855aff0bff70e24fa41e0712a5358d36c0acd7386c4f638de6ba556110d489db2f47ac523dde43ecbcdc7d9c6f97d449b31b7989ef62b003242820ae28f1a7b80516462862e6aba8f5d11e2c09e3c42c04cd4fc7f58aa2d75962f6280cbb4cf052da2ba5e298c811f951114060d56147dba9ec5a64004acf2b7a5c60f085b6b1ec02c538b591b76407bd1fbb8c3d1079ecc9a85eb0e390047f47d05159efb06d5c8132c34f818918481ba1116520356e573ba1ccb065a3165bf86071f9fd4b39bde108ec4a9491afcf13795749df6dabc6755989a5eacf2fd638a1446b99207550fe88f288d72dcfd52765d42897e4d51e0fb94885f1c0704b8b13602bb2d71ed8553eb2af32f9ed09d12b6945e2eb3e4fe5a0c4416595477427e01ec812d44b9576a344c95601d1162746c628017a3074455294eaa13eb12eaec0ff53319e5702d061ca9a339a4ce780da6d875f6a01b50048f9ffb8a26fe4a05a4d617fb4909c8ae77df39d71167f130f705783f9870931bac4aeafc82bbaf55d55445b70b836123d7b35467ac1bec048c3876e3a0adbac9f2f544cd16ca41ab29547cabbcb4ba366e353033c0c7f84cf6293c30c61cf9282c2714d267b96b751d16c9613789dbb9edca06621282089adf37d16ff4917ce4b9c88842ab438fef3dfe88b6c596936ea8d5f3170eaba2d6f399c36e9df9c9f58225bfda1d06ab543cca3db5c18c5818af254d3fd935099f77aa7a2512c5822d84867324ce419018d27669e7d62d8238f0f91f5a5fae551aa224d2e2d062df14e9e6e072aac732c075b39085650958f0b61319112879887bd5eff7427e084d8879e9c658f2df42ef132d8c874feebbd1141c542b6e4ffbc48f4715140d985b1a7c2cb60cdb9ed4b3815e5c6cd74aa5a39fdc9f70cbbd9158bb9a0cc2f928ed2a3e1f9c81a05b8bd772e0967edb0dff04920c7543d9946793618da9c83b3098c04bfdf9522160f89d8e9447d87cc69e59afcdc184901317eed3bcea1b74f38f7faf5d5cf8efc471ebbd089e4152d26800b6da7d025b95d11e387ada19ec3606a0fe523a6547a77cef76730f6d5f3f5f533189e0a79e70e8feff8a049e016672a510c6103e4fdb314bc519245e8a1cbd441b6e0b9bacc0680f52352f24bbbb60953dc5da08b57ecf8cce95ab82b92765f547bd9bc498138fbcff0d75291557687e123ddf4419adcbc570369ef17398ad2db38d2ed4aedb9fc9d0a424752c28833480b9fa556a56e03ac760588c54ebe4adbacf8262199c91f592b68f6fa43a5451fe73994523864b18e54cb9f1af4a538d038641f6db085a496edccbd1f0f9f79daa212a0c4c59d5ec74bc2224014e8c9b9aae5ba91fad46f0fe8df059e96b279f0e6f832c59d2b836d917c015ae59144ae80a699cec5a531f1a71f0fd49a6ede97b4907de060e4114ca4cc0c4af9036ad2e32394696b34280f48bd169b9433bc10a7412c275bdba9bff58b2ca9b67375258a12b2774acd5ddfef025ff4edaff773266c1f57ebd5ec69b0377540d5bacd378613a759508740e3328a9933a7522b5ca129aee4f22751b95e9efa5a695d42e8ed2cd9fc1f373aeec076c55e1da0300efa83c5f58bfac9d775f98759a570c9d0ca7fcdf59c21629a8feef957c91ffa5aa5a80c269c69c983612f4f80d6738bd540269feb844a2a1103fa684f776fb1f5d6571b464940d63570c4d9df0f1de6b2f20ee3300c62b390822953ee29bcd4bc76ff19102c89aac017e4ddc9d55864b5699e137c5304b8c9c27048994f120567e3be2833538c44722241ed39153bce173074d340aaa3860426ac51d6fdde93f351399a818b8e4e7602d974813832335a81fa8f27369aacf5f391b80b50163d0bd9e31c199f7794facb6bf39eb0fa9e4c8737940f05d50ddf814df56c71fd044d8bd1eb87da0baa85eb992efd64f1408bb1af6acbdcded7db2bb6b76a0ad508fae04c2195ff8f93eee158cf97086aad27959f4da6a0ba1e7b2b67b86adb1ec6e6c4b91739c5518f4a9ee03744a2971a90faedac98b9d7e042a8a221e8a985a382152b3a57221ffa00bf6ea3b11dae3a5e34bab6d791cf95c42aacc7990a3ad85a8a01c1550e4acc1585760f0332148217f023f8756f8fecee267776a22c70dbd8ab5f866c1554b6b571cb33918e741ab32b0b3fc5b9300ecdeab71a1dac3777774f54011c16e29927edf3ec41d666ce8d4a9434bc9f62b751b74ffca11f0bb68b4564cd68dce3e2491117587acb5840b14cfb8f2bb17b4b8c80a966b657b21f0e580e51518757c133bfe785d93a6690c8615dce7c88093caf7110ba2c767b7a0cc35f8ad946325b108b7f64ec4ef28e9a4f731bc34e7bfbbeb03eb09ae721d337f0ada060f118524187d1d6229757c7dd1f60a44bc704b0ff574e883fcb770219b6e6dbedbb11d6ded642fa3e02550a90c9fb8f6a1ceb799f6071f11372893c000ef9f889845489f9f1aefbce281d1d6fecf1da23ae4d2dcc7fb498195755e99e1fc23a928b313f80a2db12804708efa75c2b8cad94f01262834438c80c01bffe41887841916f28bc321a3208bd74e28931e663399e0e91ec249cf176db2faa8940bd3b06d118a5cd3e0604b152a2041fb330a4995263eea5ad35dfe8d9df051bb5dc3a5dd53f446914e3e8c3cad4453fe01507df2421e928d8f28621eabf23a34617997a5f7233315bff59e47c9ad4e2159a1c36f459489cef4de1d1a9d4de8937752630c8c9487a22c512ce30a3fc470fe9538fdbd5a1529e3390676f8a7134c4135498b8b38693225270e4946483e0bfeb05dfabe6f608964355483e120752e6f5bd3ed9243151e411c7e742b13ed7d8fcecc697972b2669cafb6ca51fece4d8c44b5becb1edeb12137ff3b15bf6b87cca7dcc1920db02793394147642516598a6edc05b030bba33f87fc4441d39dfa44c9d6db36632b8a5c66ffea431f2b97f155a4ff7a9c22cc8d76d7826c98de5be11addc60c873611116d7f44921bbb07b234049b21cc7d302a322c7c1735de5c284f750c6a8e3445d31b07a046d1ce35fe996ca0439f07e20da97238c92c92c2eccc89bd36d1a21b1831f180b624d160a73d47d3079955e233716060c29002e7e12a65c887b6e3592d5598a174e581e59cf579316844389fa69f6400a93ada7d79ef6f4f4a8aa66287b33ba2d467bc1b97135d7894155f18ae01c406da8e9559e4df2a501d2dfaaf0833f2e04c197c6ff90fcab428d87ec4a7cb50ef9ecbc3fc31dc162786b1355c6f962466277fa594ba2634f47c0cfc532d9f22a108b9faf6881c0828558447754223ff8fa4838beed2ee6d98df8e885f28a5f64e71ff496bcadc251e609f44f3f1ea0d1fa41d994fc43fb575b8e54d7ba009bb1e777f79bda8a8f93510dc0d5c449105b4b1773f36daee4dfdcec50f61c8ccf3f90afd93c44a226c0e40c575dea038a480cb1866efb47a4dd5f223c279223dabd9b4ce0f48e4c56bafa6382419416c131922ecf7e210d70b8575c97849c35ca54c0b1aa4bcf1dacf5b7fec914d7eeea674c423bd2eff72f9541c3d0f781a6372c1104e4d358f268e4627dce528981c20b9949b4793b1a19f2b99524ea478712f310b354daa06d094e2494ba22899bf1ed50fa0121509fefa4811708a6bf9795b110ca28148c5dd93eb8324850849323ba62f83ac3c836565311a3a8a0f3f9688a4f5ad1d184546ccc64c1b7bba01bd00095eda2a8fbe567657b7d1d306cfd6128bfde0e5130d0d09a49248e271af4e72025c876691e884d888e78face4a162ff846e41817395700282409c7facbe5d5c4a8bfa97198f70c4beb11ddaffabb436742edf0d9c717786af0d9f2e93bbec3116630ad5fbb0826d6a4f48e112605883b117ac5d698ba5332c9edbad3cf2965fe8fd3b4454735711f16168570a03746cbdeffec4fa533e2a0c161a83d45a4813c63784e184a52b649420d58255395fce8a167ee3993622a59534d59b9d62bb167d74acd24b3a442d9e4a7561d668b7241263e2b5903b62f5dee6075c0364d069edd1b23d3263f16aa488c366189ec71dce5251ef47a1757b0359c4dc2e965f6fd1d0a52a6307a8d84e6bd95f27ef498b10ab2d9eab527ea203ed934ed12bdf672e22021e1a8a1b3d828305ec9137a42370cd5aa89450b3326b5ff912eba1b72b68a20b80906ff40ee367d7fcf2080ac05e0617b7259ff10fa796f94e61815a54d635b5b90b09b1654e8268367a14f8f52b7338ec0c862d25aac2ea8cc930da7b762676c415e7c63305e12a66bc22eae2ae53f3ef85d59835a8247933e18a471877e7e04023b908fe6a0df811dd64b84c5cc5b109b8f2380dcbd33061cc8d72fe232caffac3fb60692e4ee0803c40b36f85b6f6f0e796a076c65571e845a970fe26a1dfbea62fd97e743407613fb46348e1f4d3db57b0d2bdebbb5ef3a36037e2a17437e02ae52d03e09f3dc7fdf95d33d9d16d341161dce1037765c79f5ccf361417d7a78cf2c7afbd74dfe1c40165ab76457ae0a473ffe24deb947f533a37656264ffe6d344fdbd8acaebcb14d08368f50562f4e4fdfe821bc8404fa3e56b8109f4397f37bb1f84dfb3d8e576f148334eb675080cfc1196b37dedfbbdfec7a50ca221ac3f76f196ddd5cc9bc8883c3093054e85c0f8c1ef6dbcef475999d3b9c41f39eea82c8f9b15f8d8d8647a866b3db718c56a80c404b6465dd8cb76b292094e955576323c8b143e1125c78dcd3b98086a9cc8ad13820f600f9411272271aaf1e561a53e89a2425fbf580d612c7d91f017fe11918c75a30aee048fb8fe440707bf9bc55e2b3a3fd1e54e1c427c1ecb8850b0c49516017017e9280302e0edf7d013faac426bbe44589abc48ddf0ddf11484f57c6316779ec40aa67fe09dc8dfec00b13f095c7c91acf0770480455ca0cffcc6aa37ee42cfaffb063a7155e1a39975a13a8ee4e3a3495975e0b37a13400e39e39fb7274ff9e76f2fab3f99abd48f37891d2f82477c6cd175d4d9ed10592763f6736caf7fd4d8f4d0818ca36f5b53e486f3ef37d6732a196bf8e81ebde069df6d4e1f2a2c364de2ebbbbbed8cacffaedf77e67ca513c3679c5633ed7e3343ce357e752aede58cc982837bb5e0dcbf31bf857309b1bfe2d3b5f5eb85d43a6845fdfdf3eb3ddb06e21aea3ca39086d74baaae24b180c24a3ee895a20df96c0fed817a72ef9d8e8df41033929f80bf7b960f4cf949b313ea31229ea4976946280551ab3f959bafb81dc64aee9672a73f6caaff39b0a133645965a116d44491f62a3433dd443ad0234e0b69fca1559ca2225308df464db1612983221a28ceebcf9cea5908647710ad1fc875f110d23f85e4df61a8030e96cb5113d98166d16d92abd772963fe0f76724fa67b8279390f146a0814b4a4f40e5385e152fd914bb52c5fcffbcaa22e85856345e3d1d70e21973446a89e75e7c4f8730804c4510958616be581ac8a873d10f49915d356b18c0dc8c0fe4004835a2cd7378ad6ec2d7e04344f4b8ff087920d3167d533f1f8dac2412b03cbcfea19af6016b3616808ca338edefa3daeb8993c9f399d250dbab410f35281a9b2229b86f031e61f5806d18db56fb8d0e6ba594e502197c7c45427d328251e8172aa9d3d78602a1e1f877c5f4101d18295514d594c29cc938493a9b3a8df326a44c0cbd00002a24c27f845f35757ca32fada554315b7ba39d771d2a8a998f76348ae7a93aae331750ccbf16ba72bcd1e84c53b9ed3c2deaa31918ed2332bfccfe65cad075c537350fd6a26e157813140e439b9e73700edb296a10214dd8fc77a388878bb2a708df5869252fc5242f47aec5fe0500d6cc19901a9b80874c9fa22673def7912bc574a3534c6299a4d304a3337c4351c62893e9dca11de9a48986d380df078bacf28a1c1ac99daf24eb5afa14878f01e6afdcf8a573adf987817134aebf43bd81295415c7592dd757a4071a8f325185ee2bd7360e9d2dadbee48a81e381a5b04e5c3739732498b7169e3a09ddb6be3df50277aed650e596b0b366ead74f9e3937665d6c9300e5dbfa28e4a01bb7e05ccefe9b9cff3dfe26be16f6a622e4fc0b156a1e53eeef9f96c76f463ee088741f7edd3878d70c7017f12d8f8448fd05ad7e15e3ab0ffa52d5aa9d2d3d04d01cad8efac491531f345456a51b2d2493371bc28866fa9b7f14d8b5e23822dc4451cf70ef390e277e38f1ff95fcf9bc3341fd2a25f964fe0fa6d9d51cb82b1d8d2afae202bdef5a1a99a9002870702db39cccb62bf60ea1f0bcacbab857bb8c864add919f62c9d24195bcd84dbcf6dc9037fc11a5e0d899f27934935997d41b169416063e94876ac44fb385548ade38749b2551242769a2d43f773a4aecbde28841ff4829e164e760e764bb306559c18f0a1bc7898efe927909dc252571d16792827b59dfd4df6477e7657f2568176c56c72dbd947e55e2ff7d5ef90dd14d0b870921ba6e45ba17f1307ef900c7c2e0d26c92a67f4ae6ee086e87f3d61c64f5f9100366b236b563359d8bf73725691c1c50a4991eb534f5844f58ff04dbea812a897077eced49ae487d751650b8bb96fd7f9a7f4f0577a2edfd586c32ecdc0d273e2c2b07c77685f773db716804f53b2e34279764d65ec73ba52bc216da4344e3abe898a3f1b10b17c24425e3f83fbaa4aadfbd2a461124f03043d41706d1c14a5656d8c48747a3b08c80b3344f82f3d18c0e20f31c3754d8b9513d22a72779f315aacd6f49bb5f6893bb21c92512e8337b9854b4cb0d1975c94e622d6737196bbca8419b57919d5982abaa60dac9b4246222342139c4c952d79c95b1b66c77707a510301395d58fc797baed28a6b611111b9e6af8ab2d21620ada1309ee077f55fef583803d482b4b9360c28a5cb916111f4cb25ce16baf6cd274722619e276f13d492af7b8187740c03afb18707325339d6604438679374e0d7427dd68627d1f1667c8a59cc8b76c5d0e6fa86beae4ac7c2286a5ae4f022144a22b710cbb4e92be66b8ed31c7b99b2bdeb44be28eb9ce13b6d7a4b4c561ca844d3c1982a3a8f8fbc2b56d4c24adc7324a68a3657834a70324cb903c63742fe8c421bdb1a132db93894807bf5625e2e495b28959f84bc319503a511928534372a7bda05b32423cd5d945ffb252c1dfa5a33f7708f68fbec557e723eba9116e9a04b06d0646f921f905c37601539cc69377d9a0448788c69337443ec43324e054bb71ca46bf9aa29a1a435f928bb676738ecb8e13345cb08d0f6e993d3e100af75ce44cf96d3558d63f7b053521b9110c83c176667ee55c6a89e48bf24da3f6580d6b9e1322a8490f9387417515fd1ab3e88f79c269a24e574488b0fc1cfb97f877f9c6a33517cf8765a6dfd5f33ab0dfa15dab36e275bd5bb004778f00fa7b3b8dd080722b1c7414fda7bd09ea925aa6b2c667a3af24e91069b1df1df3ea032a3dde24b860ac17ef6cef4cc5334c155f6df74e3e09804dc12c0c3b98f826a9f75537318b105b8729ecc246878dcd2fbbab07f54735ef38acd989ca770e754d953f11e6ee93b61076a7cf3f39b3678b3b82b4cfe9649db31c1dc320be3fea1616e42b38ce097939db4fae32a76290fdcba1ce4c955dbb2f5039b73ee4f165d50a912562f2caf3b54b73d64b0385a96bc477423f8951c067d4f97e09655874bd11e3cbc6776c150156d145e08fd1fa207e0d6358e1622e1a831f8437cb9b32aabab42ea70c9ed70e7a7c77955e1e695c3025db50993844c33c1b11b3c73be5327615c7d8f2b18d0fefc0cc35b50f3084bddb2ee172897fc7fe0e05a1ddb9b753c06310d83bc1cb86587387bae701d75dfae7f3b631e4883f2734e3a2adc72aef436f6add6645be083e01cd21db9e391928082dc72b9c354c740b1c853c402ecb42ea1c592a6a86b05dcf28ffe2fc79a6e00f45be6ab217d6ad8eacacb861adc52ab0d931687da46e4b3ef718fd38488bc0e819c845b3e13525dbce31744ada74c50f0e134769dc7e160c02d5fea83b8434fcbfcfe9766382b51cf17cc78e3efa341fff550a829da21fef8e02cf42d8f9e3b962c2d8f30df7c793557a6a711af86278daa034419cc5858c4d6c518bc03ca49ead674e65228654f544e6ffbaf119d14b2a6fde5d3fde8f4b433f5cc3197964fb31a1b6761d224a1628f724249ae80830654ff41653c30a07fce8055fd897c8a23728132eab84b8f3ce1d828d583aec9ac4684c1a5535800965782fcfdfdc77ce301b10d2da7f37665eb7e5ba97c863b560abd5cdc9debb9f3563db8b0d305780b9577fc550de52cf19e5cfb2f237a762e9fa404f2727f74c1f2795a3e535543667947b3a70c3dca492d2b606b06532c92595e1183032f831f5de9ce6c1eae85c30ebb80d323f5287c5c7ade61b243c2b8a98a6cf787075e996d1ac1ba4e7cccff1f58e924a3b93ba3e18c016749a162db5a9614abe7c643a2ae584e88eb8f306865919b6b156dbc05e622af1c3ef23e2e54395ad5021642850f5de6b93ae71f8b6652a9ed9275517f6611c68add2566bd8fadcd767dba2fe61e44ba1197e4ff9cee527590317b03f111ad08ae97e6004c6a1325dcdee6521183d15b12af316a37f68f8d69a13799e212c50e2f444eff4ee8bc74551117413b252ace63f5e3baa4dab9f6bc8c585e42ce68f36f07c733255543546858d34a7047574e8521a6b80e3d1ca63509284306a4d673fad687cb04ed71897187fc58ff04b8c9963402c5a40aa2013223d204a95d8119386f3b2c8e8c0fac7fa1d7f1f8603dbd736dcfb4825e46ed99a85fbde46ff0e485d9e94071f6c6242785c4ffca815991168bbf9460aa8332410a2057393c0e11505656991795240b0605890e04038b8770f56df387906b6f4af5e866f021d245df9fc5d5cfc98f7b1d7d92947f83017ea61187dda8632b2199e247b838340bb4c69fa08a40050e90dc3adf90f019ab9ee3aec001588f3960f1fa03eb0659104f07e7b5bc41ac4ad4639e13dc8f19a26c337d624255fcbd105fa4f457b587c0e1765a8fd3e04c128e650b5e45c5d05ccfec15369cf675c4ad8e8430127277cd0a75200228881d1763157e3309732ff087134e2f26c9632a98344de983573c8b9776abe2037c80febb3fba15ad43a647191e8f05d49fe833f97eefc53093b39e2351efeafe738bc208efe95120f75755a43f5e4fbd9dffe43cb50bb5701e9032f64f19c6c38b56248d02739b113d3345e09120a33238072e11bc95203774d458355517c88ea2041d2e2cf12ff57038504f43502c5870da13c29d01c6ab7d61ed61b3bf69852671e5a57ec91f827ba315258214e25b63d4567c154d3e03c140ab6dca6050d32ccbec18e239ecef25ff035b6db855d44fb6759e6c5409f42768c0d6c124864a46ac1e555961f206b1e539ec0049944ecf6421e33fe9142c524fb44ef0b02bd5bc3f216fb8109dbae678c92d48e9c027ee0a6950c8c6b47f52c8e4ca667c4f5c1f2c5e404f3498b652e70f19f253d15f0f1c6a19a04dee3d63a86d4e7351fe04021b7734e3dc35309591fd603b6dbb2997554f39e1a393ae76f07d422aef90dc8ea282c5c0cceead34fb8b49d672ed1fb3087e4c0009f07b56e8f5c05676af4c92144384772786ef3298b9f40cdf9e075d52fd0d7d3c6e5c54cbf07183147ff82d1504acd8ee06c9a4faa8e06fecc5bb9e4d28195924f5ae459bf36409e0c368d3fbba571ca2f496e79af08be8244e9c94480e8ced9b102652c4b5f32d8be7ef45bfaa8621c0dabcee0ebd23e5fc63bb91f83041ebace201e5a52baf784586c7de8267eead4d2724dd0b95307cc110aa841ea2f81eca0da4fce98a15e6972427c8a05a371dc770f61614a32905c71b7eb2690dbfc438697d41f8612c2c6f4bdf6a655a3ec2f00713a24cf2dbd4f723b8dadd59cee7a8c7252714830aa92c57fb78def1869a8d3143b73c8e783de4bf0a10fd12dd8781d15e502688215b79459afc3233db1e6ecd52673f9780cd96dbe3121007f9a8fa886a237481634c6037f43989ac7cf70dbb2fa9597e95dbe55f06d5c7bfeb081399fc88814f3e1af3d23bc3aac2f9abe6526f5793f49ba561d1d99c23aa972ea09c4ef86ab33315b9a3dc9ec2855d31c01925b1c00552a794843f0f99c8df3d7343b703e99bef428e2215cf5a3b4242e5a2aefe11dfbebe262abb7203504137eb44c9a415bd16b51e313f896eef225e19597c4d50ca88318604bfe5899a2096aece95793e0d21dbbfe722b464b76fba1285a53f4c1fff13fb41ae4d6047df23b4346b0826c4a43107106d42d106aca65f6eff2225b6d158d02d4bfc26cfac1acaf332d9f65f32d276b930e06177578b9d806835860179f1846599b1822c754fe16f1815146551dcb7b3123cb59b50707f117facf3aca9282cc5a893c37681a0c102e88c584df076190bb604a04573ad2bc5d925a9c92f621599e0d8df7e0f97b402835cef870c6da097c27264116b8c22fd562888809ca86a336dad822371f09241a5a8deac0ffb9f073f6b0b4883dfecbc4f52fe606fcbd145c233fd25f311a9308531ef1dc55792d6afeba705d5da4bcefdb7bc4abed4edf7bc5baeb4745b3f8209ffb7bb6bc86815d101e8aab98453f914d3b8657a2b2dd2e17b827840ce007b5e78db9d162f8a68b80eed2299b1e54006a523b8cf3082d62db86fc3c51345bfa061dfc8fc7d5769e628d8a7e45dfd954199d9f01193e0b9323fea9005bd1e76719950d89990cc9305f41d0ba61afcbbbbe20922cab03acaf554b37005a4870542f1d5c5b3285fa69a7f344a49243dc5a766c285471fa8a276c4559b783efe203de910adf674d7ed0e0f2e2e266039de8e31eee2b2b7d02ce1027b3107ac254a6db5276cafe8c4eac87267c06cc10d72506ae323885504f0f0336c426a2eea9dfba9ffaf4b7cca62825fb845eea07e2a50321504c2ffc5c19d2728a44b0449beeed8eadf547e8c411dfbb1d2f8815ef259e03a6ee9188058052bd6756e14ec14aed0cc625fadcca59263db5bc8d2d8109aab1c369e6c56f54af7a21535fa9d2bb2c7977cbf9aff3aa83bbcffd3ef02f7f3d173f113729562eff036d196ebf7cfe3ba5891d03b018279ae5be3b4814d8fc6ab69d966a8b47850b57647bb98161fafa7750b7cbe5577c2d6c3805c1376df7a91d5343ce62fd6a992769aee99b3096f37cb167c5df7cd6bb6cb66000000000000000000000000000000f902c0f8dd941c479675ad559dc151f6ec7ed3fbf8cee79582b6f8c6a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a10aa54071443520884ed767b0684edf43acec528b7da83ab38ce60126562660f90141948315177ab297ba92a06054ce80a67ed4dbd7ed3af90129a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873f9d03a0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873f9d04a0f652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f37921f95f89b94e64a54e2533fd126c2e452c5fab544d80e2e4eb5f884a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000005a0e85fd79f89ff278fc57d40aecb7947873df9f0beac531c8f71a98f630e1eab62a07686888b19bb7b75e46bb1aa328b65150743f4899443d722f0adf8e252ccda4180a0fa172c7225c779e82740fd5a7ee3c959b35f65da0030217bfdd9b7f491c92485a072ed12dc052358dc89ce62d77d3fb6cdc9a9c2f5d3039140bf60b02385dffba6", + "0x02fa0184580183080e6f8402faf080850e4f97cc0c831cf4a1941c479675ad559dc151f6ec7ed3fbf8cee79582b680ba0181248f111f3c000000000000000000000000000000000000000000000000000000000008d67d00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000161257000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb5000000000000000000000000000000000000000000000000000000000a039536000000000000000000000000000000000000000000000000000000000a03963e0000000000000000000000000000000000000000000000000000000000018037005b2ca73412216c1cc0181afef79b008c0c041b474000f13f4744552d5b8bbf38b01b3e3f42052760382a15100815a108d8ba8dee365ffed71bead97d429f4abae9111afb24f7e7f9b9fd39f7debd350c5a86383a8c1ed8055853698368f3e9663588f6148cc4023673c657ecfcfb82d5e8df446580aef981e0d22296746969d94bc0e28207e09cdb5acb7cd2331ffcfcf203b116e07f0f7af5e0473c3d080f0f9e6af3dfd3fd5de97c7c813a62099680df248b04954e1b180810e0dfffdbcc7e790d8d4013d226ac7108f997a7e2d192cd9366c6a8a6a8421b71d05633da803ce2356ff0201ac948386834517292c6519293e4c0de3e7d3582c76302626abb019711858334e32d8bc149d6067943042649c086aca2bb1d10c129c921337d1f6f525b4eb2a0e51e416b43a2aa5bb3a145cbb5484e38cbb33fda2cef97c3e7e6ef14befebd7ffbc96cd486f4376163fccbf1f777e9f9ba01d0b35bfc6018364781859a638001e6f7eeede7392b0c12639af017bae43b4aff6d42eb739e47e22c9333d0150e892c2d7302dd18b46451088bf064df9a58e42a4a4ba0ab2d142dc4cdde7f209404da222855ed93a9f4fb449c2b4dbfb4f0ef18433076648311208a400289e2f8ffb166fdbd0f33cbcaccfc9d33cd8f3492884a141111f662b30a3a876b1e0db7baaf8c890487039942a3da504b0114b88340acec91dc3a84c3741743c3387b51d38cd98d37422c9bb5fffb62bbf67b2484e7c33ee94643a65b89daec83c048c2f952b5154160a10184550f5746f275553960c72352604180851a20a63e921011fe62439838f831f859f252491298094ce0857a30ac4e5132301666b733bd13e3cb8dba94ba486703406aa0c9aced14e4892a32dd462a7453fd29eb9d2a7e4835448b8a1140d5a791684a54d472f464046a474d736235007d1a131d2d50c301efeda1b9180cc7c6f34c7d1bf0eed80cd390415dadb290bd923eeed6348c9518294fe7dd8ecba48f931481c1adc403565a87130e75fb1fe8ef4ffcb5e12892e905cecfcffa4b09ca9b8ad954e3cade790648e6a1216d20b06001109bb647bf6d9186eb65586bf86277ffcf00230429133a6634ca5919101762bace02541fcaca840f9188c63f2605713d340142895372163e13f1b1cf17028865d8afd2517fee1cad0c835d592ad6ffd526a8081447b5214b29975c09f4c60c771f30b8c57aa0856fa2469a5f2086cff36930c4b0afa37627456e2d323dd66625bc4e35010ae0fccd1256bc1963612cf400a53ea2cfbf0a92cde615564302bb304a1e23a8d901fae61f158611c22d5a9db795c3aabba5fea7703fe158b5a85f490d07302b1273315781120c6e1510c24aeb44428ae2f9fc245e9f66270de53a2a927f7cf0af9b55bd0f15dd04b1982daaa1b0837d78bf2f59715d10b2f3b3aefeab467726bd4a37b59d75fbd229ffb6419c687f7796c21be4b8195e62bf29458b39f894053ad2d3ac3c3426e2c04e1544df22ebcaaf68500f8210fc5086e923918563569d3ca88bbd475f4abc344be84e554069d2ab89e80d5492162dc86e450749360c1ea92d54be0fd8f7dc128708e5969c1712080930d34e0c2b58fe08fe49eb138142e1b9917c4a410c636e9cc5d7c8a9802702ce893eb7379f99eec02e802f094d284ff8d71d515ae2830173ca367a4412ae01dc831a8ec93e68cb9cac1832dbbec6a4a6f87180e5caf4b99bb48319f0583b1a3c6ff1aa6e9075dbee5eb30dad8435afaab5fe4b0a8319dbca1e9cd78522008da05b605672a9a580ae084762c404693f3d3ca5cd3b2b57534c86fe67b37dfdaf8e7309fa913b3a15d2010aaf938654b8bff0df9f68a24d9185fd3c6ada3e0ead73c758ff9bbb6dc40f07fbe17b504566f1799a05d450b4802e4b02fbd8f6189e4260601b999877cbfdef743ca4d75e6cfef251ac0d993090b3e4b0b5ce2f9b0d44904402e77f256c4e47572738a2e231e4561efd1a5d2dccabbd34b644dfd11eb2f553d63e997afd7ce228d2ec1ed92fa5476413adb41ee637efe8f48f32862f8cb42fa531b104c4bbe4536803f867c92fbb39cac96bb88ff48d572385430c01bf7fcd04acd63316b2703a10caed9d92bcef05084acc6f93156894c5cf0fc223b6541de01634a057b347230081afa2fe23e34840f4c64bba3aae8b755f0ff8e49d6d133aff9ecb20fc245f71786acb5c43a55e0b28f0eb48fce4fb5abe556867ccae8e280cde5d23c22d8c0b8b39302cf826b43f6ef7013162c98195d6c1bab9c1c28983d991c2815c6ca0921f35e6d53963d6567a9ca865b3a0e3fda1177c44239bbdb79a55ef437a42005c848e32aacb340233cb30ec3f5a98d399c73d31061c1c0bb5be1e7d26828b3480d39697460dcca16128e61a4d430d81ac1f9a0942788c8164fc02bfc19677865207780e8059493a6638ce7e1148db89ceb24a72f3d1e02141089ba9c76194d08dfb2328545b9f7d4c625885537ba8dc351232b888d1986781381f9b951af58ad020752659e21f1713193a081e9261ffcb1293024de84952aed8c6c9c7cf9cb7f90b9e37e5ad21bd35ff5f728f902d2c0a12c6b051e18b58b4b028f7cd0998df191380a6e156e5aa310fa2c96bb3f53105e7ffbcc9cedd22cd434e32075252176be1575ca165220b13353f3ab61eb57184078971d2099a7ecddc7b8bb40cb0737335385e5c3f35960a7b207e0754709edf0e3390e500acb8ce3b241c19ed734f18fa69435a0aeb718a241e213ae489acde57a816705460251bd22e03675cd60422152b997125d84ae9a9a46d11d5d3533118c862d6a046f998635d16aa7008f7038110fd822884d60a2e4b471b0489faf2d875bdfd687edb8c40f285f04ed8427f0cf049e5f58440bc42b0129b4027463f341a5833cbed5472a8bcb411afcef6802fc06e913d01835bd9c23d3e575fc6703d5b16902e59e30e749f2f164ae94098ca4dd6bfd9e795c3ee25ffee1d102ce687b4812d87a222484aa8ad982bf0ff4aa672b22677a114bee7636dbdc802d3a886073fc16a2307f4db03fc7fb21d2a5932789132a3f46c021e6a2a423d645bc1cb0a9c50bf6e0039bc9788c150c4b1567cc27f40c82fed860fd412d9314c4f312a27ac24aff66f16374cbcae7b5eb81b629e17c06315b3c380bb7e69a5de6bbdf45f4e40bfa1b55e17440d1adcd84c0422b18d6538105739109fb3f4fe4bfe87e337936a403d4e5121b1c106a7af9f5f3fd1d4b90a6513f95ef58e3e6736f24c92cf9e60c2e9d9022d0d9ddf9ace2fb91a84d21e4fa57330986e5333721e22d95fb22319951e6079e1fb437924a49ec29b09776cb0ab84b8dca63a872e8a903d35248f3c9dc3ab3ffaee2ee0d8a94fb9e6356fffc36e308e8a80c19e62b2915bce0edbc83c7ad0f843581a8013bfaec16339117d2721b65cae9f7822372986874180777a30c4b010252fa41858b04676797e5b86e494c65ff3b4f50d44b13eb903f167aa63f1341d2754db3dba1ad322aa4c42045742bcd71ef6996a88a16200750af73b26f6e0fcf4c7f890e612b60d63d4a9db247a08efd24fb5615c6088d1f3f794bea679d35c6f7f9f13786ab56fe741e54a7fed9675cd4cee20e8ed38f5223229481dd3cd3c19ff47c26465f9d068f3abb391d9ad9539f8ace3c19465907c4a822c591cb831afaa511674dbb0105433da241ff1f665441c96cc408731e82c5f43324ee43e6e9293b1704cdc7c69f65728fa759afd6bcb684478e390bdf539c866e9ea1341b244836b820553113cb36970fb21857b2913dddce63869ffeef5e7b0f28470fe77edfb8224fefa1498a10f5960e2e17739ac3cbe29b75f5b49d47b100ca5db46cf4ea2ae6cf15a9bc7228ed148c71e7101f29ade3e1897326d51d3d55e33442991d416d129926919214c83b3034191e5936b9d2af9577bf1100cbde1452948a17f25f01b251415d7c73e0d530fdae187f931367dd83b62dacf2cc6f0b296e006fee88cc55458db19e5f4d774081943d16627a67d21d82fbbc2b01c1dcdcb902c204ef77184ea4c3e13eaeb62117565e97ebc7a86d5532a73b4b1ff07bfa0b1631a0b2afdcb9159488681496cff365c5906311a976f451fc64a27cb37a65077d0ee19c8fee2d55ce6394e1e614520ce9febfa0f1ce10660f3cc5e6872026333656fdf507d6191aa4f766b7238ea9fc602e8eff309f60537f2a12801131a1497ad2633ca7f41c9d79541af9c602060c00f57f5cf35fba42cbfcab1604b1edbf59ba178a2dfa71445e13a242c1c00f68e90fe2b748cb86983bdffceaec8388ca0c8fe3344c77c5b40eaed8c8dac72bbe709e1c6f72c5cfe90e0dc44e5aecdadc66b908bec5e8d906209e5f887b9e0301e81a29631bc97d0d794780525251930770d6898ab986670a5e0aaeb09cdf71a6162f8ca7b1ac513d3edabc9320f9cc4c725d80bdecea6990e3051002356e4cfe546612c40dbd58eb86ab2334dd33cdbec999090ca72b88a9c87b1a1e3a9a05d3cb2cda0181ace171f49a5b22fb6a1e070778b0191f50515fff3f1a438f386bf1a596fc9ae6523503fa8998d34fc07ad6ad88bb6176cf33004e2923962a92334a1e00684c733f6a8fb82fac3d7a8cec415ea51ee6caf48d9f52fe6e669b621552e0749c2d695dc5763aab88d3c297c638016cbf90459c404a2b2058f6d0d49e105bca0d0e2868b581d3c4cbe8889b945f4604ca21e6fbe619e69885bad913f629a879913fd073cbe11e0ec9e30fac945f6dcfcea1736d9ff05bbabd0dced8ab408328312612ce1d26655ed4df29fe2e0b5805bcf376bbb314bab8b9eb63a0d881fa448a5193e6f2ae5a64ed46503996767ba535236e75dd1188e39558663751a98d99c83780db38d93ec0f01ec64deb1c3c4971f16fe38a0667f35b18d7b27d435fa93f416241a6394529e2000928f27e7deb7f5d51f6f87e8b4141889177b1ba0fede93e5d3d7ba8542ab3dc1b5d60298651fe89a07d8dfea23d1d2e2e9a0a050f9708ae76e1bb9288fe3194e13f751fbce34d5ec1651ac4bd81f951a1f4d5c780852aa426e3602720e3da8d6b8c4c31ea56f61df9e0688854c9bca8349e10bcea0b0c97942481a7c329da6967e542332fe7947abe5870e4500fed49b6260894e18ccefef2eec6fa63305dfe6624ffa5ee619963b571754289db156a010613f2c2f9929b04cd4051043c40d114814171760fe59828c5d10daf4c233017493d69c57e90cf909eaae27cf5a0cad269cb92d6f128d0e65bbd11cce403bf6b536002a305aa187c428f724aa0b17a1122aff37d1bb1ae2f194f246d1c6fd41aa3aa7117089ce09e7ae80265fea8f7b7d506d0c5893f4ff3d243e4f14c80db1a945c7011b07a1d9afbae4c4041f5dfa5a00084fab5e78917c2284863ab7c5c907eab255e69b6578e7cce064be19546c22e7b6b655bf6ebb923decc308473bed8b7fa8f012b57849b4e026d22f40371446db6c476e61ca50df0be304bb29e2d16f789d58823d23f1fd4fc2feb8bd7b30a35ab8d86c71146d96f27a2dfb1184a5a2702b0d378a519f38c0ab7cb186be97b1e5d0429c7275bc06461155d3a951fe780df7a0ee8078f823194bf2282ae9f9151d1a8e7c792b24ecf3226bda6c9f83f1a4c2de8c24c4c215a68bf83b944c1966480790ced60b2ba6649890baa17014f41dad5713e6a949284d8254d1c006e2314ecfd0af6132034151257d9cd041578674b8672d484caad6f8f6c1bd5a7d0f0353a7a8cdd050e84b10feffd7f663fcbb143f831f989c41ceee98c81ee657d62b38ed6806dce43fe9eb4f3b9cc4d4e1d542933b296a27ba35844649effbc18013f668b1972c06c4d0de01e5ab55d81261f0b114b1a6624b2a1d38260626c77540da11bc924c0193f234c167b47471048d7a237c49bfe8bea7a26ce7f2386d271d93563390e530acb88e42e113d7ea636bda43a8a63306855a9240bc0943a2c3b831c8e0234121045bd07699e2929ec937a1f39e7cf6b746e95c170d827a827a7e6b30806dffdd4f28d0b1cb12823743960461af911d12c064fba7632784a673a7e4225be41f132a2564616fd3d112651e36a154b052c5245efe8c7d869c922827b68cdeacddf7e37c404ae3c2afedcd10e4dcc323c6d1e703da9b147748ffc0e105184c9654eafed020fd1ec4d2a725d09c5f657a557a966b72d3c22073e3d028798068a7eeb10e168fdf108ffb7e1896ee4e7887c46e50b2e90197513d13adf2e52dce5045a42b48d80f050849b6df7c99370b65f073e2bb0f3435acdef0861f841be80efe7748d9699da3292f840fdc85376e35efa5df98fb5a42f6b91791a98075df1b6c2ee9bc2f8e5eb7e1b805f0c7d0827c9f1674b46ad977fef7e648d7c615443135f546e31c35d974a0268001d9487dc2cc276e8e97e21f84b0925bca1c9d2c1fe88776d203750752a0438f6a003c981a33521e4f6c5e3c24d36217acc8d0bb3cde58dc6a06011e8825204f744a908ccdc484c2d762e5fe2f1ef3449e48b79329a592128264d5213dc1bd9da4533f9a0b348f8c7e5080ce810c15a5e3ff7194c1c846b08ea439978ec54bd13455e337cec6ebb07a832620d063f7a3be759411b7e7d132ced01c852888982aa09b3248c202c118e1b670f51ae0d9d6895c9e34c022a466d382516184a89db28b2135768713df8919989ba85aeb00dbbb51542195ab0549418f3298d0f8e9cf4815bee893997f367fc2e0566a33b5ef966cde44e15f3d74592c0887e1b531f7430123fa13638e025e13b37f16f5c4699b913c36a826dcfb1493ab7feb0c56194f1c0185c1244e777d11589a345b38cb47954c8b8c8c9fc53528795fcb988ca426feebfca218497dd1e227624989fae326ad8fe6f62ebc63fc823ae121aaf8fb2d45c282b0683467b72bf22f1ae3bbf63d34cc6916d56aeacb2f47de704257ab84bcaa69c1ba5dd0fe915971a308520fcfc2980e95ed838077f5c5d4cc8ebb6a87f024c431615c71592fb49355f41ec1efc083e51b798a4ffd0df25d0437de2fda521eb96a09fdf82017e090a752a4f18c0e2d4a998f6bf4b7b06d544fdfaaad231f7fa6bad3647ae4bd6883fc7cb5275bc91e968521576cff7622a1bd3c975636ed455baa5d492f0a87afda2b5e91b5cba65413520c6023ba95579b54c6ef861b58954ce69f7ca6619212d7142bb2fdfa6897a2e4229c4625ace3dd9a40ddb9e406830b9d81e317fcbd4ca44a9d97d5825d18192c0cf2bc9695e2f20d6d28235f3b7d30fd154c29a4a1dbc1fbab84a75fc276d5c4b5831787b1753b0dfba8c67be1109644592eb9bd4b0a4c59a424458ca9ca4d031409366afb1cf8d8ebd7c0955a7c1b46ce0d368e352290fe5d4540cf158887358f35fbf82d63e1fe771f3c79b720bb31dc2d2d404d8d75b799296c7ae374086c82999729a3819c97e4a215b7e84fd7e8f2de6298b032a93538176315c5910bad9d1f5e60c36d14c82c5fcf2ba0ec7053be74303d46e30a8e5400a90ab87a35fdc575e58b53d581963002c8421dcc8a4816777c500a140f1e9cde937bf467a5975a4dbf36076838783fe0e01e93f71da45b0ff266c4620b07b7263a5be2bf4f35ddcf453799d2a85447bdb9aaf78988c26fb5ce5a0369606744eb182a9f1567084c99a4091394d4cf5da48924ebb4a23d7e376e14015364e5727656f7437c25b874d185a20786951f20a813202b4cbeac1ddea22166135efa5da15722c59b4509c3fb1af365bdaa85ff271f809b77e4438f327b6412b757f822027f710e708864a712eb0eef34eb448fc4e9a903748ecf5cc806bf4c2108874ef40909c3e8e24f4370d71644e2727534f9f7f3269681babf689153360adab5b90ed4cd1172e99a5f134214eb9df15665fcc9da425e0b15fcc6831116c983bcbfb40b82b7ca04e2105c4c31f8935dd024d8ad9d13a7dd8d88dc9b111f7e4acd565a577ea83ba29924a538db0ef82b5a9922544dc9c31c4c07bc127f9b15b5de49cf9e95ee81ede7afad6124d00529738a6615f9202b43bee8ab62f2893966ccad3483ae31dd6d5377be9d5a06c4e7e7186ea39256270a37f2fab222ecaf87ffb7b2ac1c40ffa031f8cf8bacca97d3f672607eb89892e24420c3f328f67edd520b102b05fb8570d1c78fb9c703bd144e35afb9d2e3c2e0c85c908925bd0a13dba060fa3a7173e99e5794eeff4e01d0b4bb60b1d356e663ffeaeaf9798302356b5d5c4b8c4d4af71cd765be94cb90251d6de9d80ca466751860d5e9ef59a8667005c22259ce5211a3230de19e4dc33110993ce3b4e9a2f2b32872d6e6677d5c1e1ba69c1145908d4286a406643a23f13b6d1d07fc1aad8dacb73f91aa5d6f1c98096eddf8de6c262daad06900e48469f95e51dc0adfbca8f91c7ed7e49f31640e180d271aef19eb0fd62f434601f8a4d3c1e0962258691da41e1cd7147ef891e7fd4fc316f3e104d9995f6014c48155df68375b1b63a7a3b8dda9b7cf5141ba270eead2fd218074e60fa313849eef755190e0c017f8869d478f20e4a56bfa9ada42d23ff4c9d170702046b336af8f124697bba9eebfcf4065dc669caf83fb5e4f3855f134f89a39a976ff287bf5f144248932571c0f4bfb4024dd0e070d5df3ba51d3a735a9d5649dee993f030aa67a69fe90467126380946c26da6a56bcbe71952a78fbb5d12a0dcc80241508342b5c30e50d071fb1de13c93b16f219777667f4a5eb728da5a662d9c2f0fdcde667a900408672fd757cb9409fbab6af50326a2571179d99e99f5d5ad4eb008598f8f93002c3e38f8a1c74c2ef42aed5c5a0fcb2d3d7ff2577d72100e2477a109a10d7bf81b02504f99f840601bc3ea17a7b42db354a5ae98472b5dd330a1f647b0b42ea2ca61186dd575ad94eb5872e00b0947a57e0b093325f24dcb5f717e58c71bef888e98d786cb2eb284643b3a08e9000d7dd5d1a203f15e7920f21f91c3f0b4a12cd1547ec4e9916f517f6be1784750882ce44388bee058707071b6323779636d2e692bab6542f5d67929863915701e25e63570062a7e1db62dc952268af521b7278e4f9787ef1a11d7cd3bda5513347fc375cb6e387f17414800b41eefaa9703674e696e8106a0b146bc3842d4f0391b662211eaca16712b81036495abbc9f8a295fd43afaf70001b68e22905907d7acab64305a403cfb5d97b73c617884a69b54afe449c64f745db8b9e5033fd639abc23c683827ca6656fbfd576f7a36365ad74c9d2c7d6abc7ddcddddcb92a7a4a382cf345bcced31cb494161e9ac7cb24d2de09e8312ab0991008c8ba9749cb43b288105e142cf2fbe6530252a736f34f143c3095f2b16454e09f08e2128085cbbdb0c0aa2f9175cfb45f0f63902b36c1c3f43f1d0bc9b939a968fb93047f02bc30e83ce4b4f5e890db43cd35a629f148e1b84629c7f2e731966103172e090665fd186503c1ae2c5a73bcf1ad1a8edf20a01350a829b6cc6e2fab768bc7c1c29a7f59f158e387610208ef78482e250cc085091101886af5d7a24ec7ec73adfae5477a51cd48752e7bdf6a450041295da5c2a84a12e93d5f8d0bb0a1be4adbea7a8ca6505d23d75be38c620983a016a73d21398985755dfabd8e4fa9b3b546d6f0c2e21fa85b3498d3e1d4ccd3b745938b679fdf383bec6ce43b2c07d14f879a49750737b5fe7354494aa00f85fdf42132a2ce46f85171b7b93a20c297bd2fc8b0be223f814ec228aac109689d5c7b2b9775071f564dbea9add651d214821b1d58c65291820ad3b87ff5abed50b6137745632e9e5aced5550f6851930732f2343d4b57d270de766b4bd523ce1e8dd8f4236de4fe4237cc6e21d43104ce28ed188338b809e1d44176b6bd9b4adaa6f370e12d8e9f6e10e94cbe142fa6dba011cd5a3be51d975895c83a71f70f7a908fc86ff504e7079979d05b85a33d8a33d73bd79b8466554e76fe4d18249f4239016c6798f4ba187bf4b30bd84eb75369604a0acff939bc7637f1ad2e6388f2568f3f00dbdc1a6d8d72a206a00ae76d8f12ae404299370afce524e6de576ee06f23ed63122becafa8674e7640f602a1eeb6613bdafed43a6139e6c0616131c88f6e39fd1fa998914b45c119d6d37888f2d6320cb6a8974fc81a87008a2b7c4df7f2e84a1d32ca73fd47a4ab703f4aa35a8157435344c2e6d9bdbd521c57ce3381c2010fd366ab411c6d77262efa012a8c11458858953a43e2fff75d7e9c4aae589fa0e3d7a30a5d66d7e05b2fd1595e537a9f941ad320958789d1ba937c623b18010bbcdbb23699c0590a222912246799bba7e7afd1c16ef8056422ffb1865b270a5aa7c3ce04516e8f2545efa6e20217cbc5102fb918dcdef0199fe6b26a8952dbeb1db70edd998250fc3c9b3253e3a6b9940640901b44317a0a37ffee22b4ebb917b362fce8e6826505408fdd0125929a88e8aab394d8b3e1abfed404c9aca947540cd5c17f3c522c7c136d514d882b77206a703fb4fd516ef3ea8b6d5ec8b60ad868d91bfa0de51564e3b1035ee5b19bc99e4428d8a8fbb04dca7fbd2d2fc7529a8b304a987ce5c477c81411703071d8b97ce05a3d54a0f9a1c73c1afe802938bae201f676b33a1e9fbbfc4fde1af8e990ecfaf4970f70802a022092c433a80e058e58718d620e12c54126df662d69a85fed5cc5ebdb7b52f0658a190535820f976f0066981856eaf60e254cc172677fbcaec3b40d9249d5cab695121864485294a4541d173600b10dafd78c637c8ed5f54131735987ee478ba99137dfe678f69097c25835b96781ee58c4a58e546352f60aeb9ef34530446d891e09f807bd25f7d57a16165c9a9f190332ea08c1b539ea544f3feaae0e957b03d19ffe085ab5c6a9cd3e90883fbe342ccceaca4c202edb51c425b87f047ffae219b420c592470fa6ba0ae5348dc2406df0f2a5259b7996bd867ec533322a68b8353dd398a4e5c1e110c68259b9aa4e2e4a92e7f57ed7d0a7f292fff2a7fafeb64b4a9fe6975653c645441c82d1ee34d769e2ad98683fed82d0363c5498d1da4c0ef17ce7ddd038c6447637dc9080a95d45fdcd16d81654d36faa7040911558e6f2ccb50d1b482283ff3324108fb5bd67df51cae2a92e466deba6874eadf92069c1d9b7528e47cfe5d25f7d0a5860ec87189946aff5b821f8f76520d088a8e00c8cf70ac45933b446e3610c4e83cf43f53bae7a22b24434bb24763a528e945870cf658cb3d2b570794568f08394fdafb88c31bf6bb7497beac8f85e942f0b55302033809d0d06666c036194decb95ec3ff4fe18612de9bb6ab7eee17901de13571caf48282b1ae84b2dd6bd80195a525697529371261759a280ff11e65685bc3bcc039d665f2ef9daf54d2ab28548a2fdf300d978103f5e2931013c59c8261a457d2d4fd8b53c44a1b8cf3c0df25523e25c5816a3fb5573e00f67906efa190308bb87d57c529e589e6c02ac5cd36451ddb49d6096268f0f4670748f33476350359965fa01484b8989cd8934e9ca655953b24c78aa8c223bd326325fecd67fd8b3fb3015a2c9001730bdc491047f50081152694dfcbedba165815c6220524102382351e4c04600472167d278173f281ce00cb647db39a27e95fefddc6a6594628f2b5262425a273fc4ec99549560c6c6b11a544b0c3655af03e4743680fde4aa11cd5d6a2c0b9f89965bf04f6a310cd6ff4b4162ddab58ce6ea23bb456f4e5ad96f667e7e6e6e3ec59782f64b5d91b702b04cd773451f9092f83b8abb5ac4db3b69abcc53cfe921568a8c6574ed4dae42c2c4e8087e456ce40e4383228569ea9b830e6dad8bc698fbdd7e5cb9d2bfbb0d0d3d1ee87ecd3c88c3bdb002e49cbb434568d3f6ce0b25c75592cdbf3e98bae5549141a6afece7d5d36afce15f35c5e854cff1bb2f972f84065d96960abdf6caa84f40761220b6be1188420d5ad4c1cd3de858018586f31ebacbda378696bf5d24482e73f2f5eb4e5dbacff07a61c0ed1f7dabf3ef111e927e9fe9d550d5f559592725fbf289ff7b5fdb3500230acde743da48153224f35b8ac2ab5f389dfa4f524af269ee4189fe459371caaa67915fa8fd9b41f43db50bb0f01299f2e232b3268177bff3e4f70d765f6dfe465c76d41ece0c308ca8461845001c4bb6547fb93f9696a970819c09d05ca28d7604d00dbd412be5506de844e976da30ac422ea807556ecb6669e560a1fe638000f50ee5ddf4865c204c01262060ebdbe37fd9f829967ad308eadb9fd2181c64f2052ef3c4cacaafbcdfa44d950a50393696f3e34db7b8fff3a1620cf14626ee8ed574dc6b77437b9d39da1a7ace6d03f2bfcfe50803592a61c575a1b3f7a8de862511f96e7b14b24bd374df91274956606c3abdc5a90b065a4c6f67429f7e30205fe9d11ea89837eae3bd0c83a903fab70c30f9278641e0b06b55c0091d6b2b084879e09e1f893d2be09f00a7883e64277387602735e843c24617ff5258a9bbc42afffda095468166a4df5d5b18d26e57e87b766665230164a700914134517a2517b065f550b48ae0cff1b8c85723fe9b1fbaddb9bf5d754b03d41f68a220ec3e7a340651dcfbcf03463a8942ec8da40d0a1a28d23201452b7bd1752e8bd4ba3330c1b1d243e5a8876df013edb0e7100f87d513c9d7775e3bad8a882249cf1a6f773b7b401999a4302d28c02c0f702067d185f9b97b85d2711f450fc56792bcafb20ce3472cab1997c918d37522ff672cd6512101207933c36b702647164fc56266b55657b52c96df0caef13c0fbf3f376966ceeb22191dbaba294ca5ee520956395049cbf3a53d727eaf637c9b2a0f349a710ac0a7f020a4bd18cd7461514b435202c4af5a2910481ed01f28ca03d4107f483f14b81942f95b22b5a66ec52eb993b57ba5f5d26386fd8f8e73d7328622381ccd910f608a208941142cf21763a72957b83d8443188a929d19c4256acc4f08a8afebedb10945adc1af221e32197629e12cc48792e84407cc888f57aa3a7066c8d70b3f70228c589b4883d122a4d4b036a48185f2acbd025408311bb004c069e4ed4fae489fd4d32e19646484c3fb27fe8d3b518c24fd0bdd6b8fac0f75fc5f8cecab38a4cacd35e156aa312844f74fe9fe71bd236df62fba043da7ac4226a742ca93b6275cac8fec090244d2db329706995a3d3b9971f0b94c5e6db4eaebaa8a556f6b3d02496810b4b55b620aa31b6182e573c960aadbf4eb5d48f261ac1e2c626c13f3172469b50310e826c133097e131be4d413723f19558404dc3584bfab3e7f38b8ff88654116de8010c8677d2e537a4f323885f08e0cab908bcf75451411f5a9fac72e5b5b73978c400b122412da53830c58d0631f57ad860ca5fc39301c35e96eb3effb0c64b1210288830491a74624f2b2c89a2cf265b2b8991f48bca5a962fa392d1b3dc0656ae3b26856323ac3ad2b564130896dd239751a72b4d08346410a0f0e4d5dc75b32ca9488008286f92c38236732591816d27878dcd312e3a76081ea2c8163c10f1fa3687994cf93a049c7729fb7a64143da8e70c5426f4f83471c58824b0e9c233519489270fb4b31e51615ec2b338bc344e874eb5faac2fab0be6d8b93b7a74d598e4a52dd34d961f6501986f09bb75a6ad830216478c0b89076190d03ad88784e962f8d13498e8a1e219e67520bc8f9ac76d009cb5c204ce88c5e96ef73c2abbeab16738b9bce53ad29b738e75393c64907b2129a30bf3bc25567507c29d0fe1ad3a6018699f4f67e3b87f18c18e29cbe268c3f8b00f5b5c186594729af15595f03d89fda1b00713fe161e4cd878b255703b47d1544d3acd33851faa9860f3aac6cc2858a77c11a27895b1b00443a78a9dcd193e5ea6f65e494132fcea7e8c39807be4ee94437b77ed1543917ddf4c1870d4800f7aadbffb5f90b87c9239b3aca0360d86e9cd4119acd8c8d1f4755c0570e5a3adb2ad27723956fd18c966ec0895d81fd03d79897e3e8aac14d92f1046342fdaf26c723e047fc05237d6a061ae3dfcf6dc647ed40997f8e7a19db639d0c25615e426994855d87145685a9ec5770ce46a7434ea0199c6966a17d1659b3f0c4c1feea7fa1ca0dd0648c816a7bad85edfade7297678e378b11e6754c31d38aa9128fe5a70786076bbdd6db905142ddff811ba7b374481646f4ab7ad0db7488866d21ad5f8ba828f8afae721cc75bf513c3e141a5ad87b4046a63fb3b705d7804159e3e04868a2d57d5976735750e8edc5283c5faa61ac5b627827e04eea4c1b7d638fb4041ee71116848d643b4b4cd5852a2b191ee51f043b086c8d75f333b3388484555aeab44098b3cd489cd96b397bc15b8898e19b9d0dd28f0c0120f9d7e10ec5f9ada0015966bd9715ce9d1323bcc6a2e09e4e632cb1e0267faeb8bff762f0c1cee20ad6b4ae8a1e6ec83773519a83c48eb032f34f6dd0394784025e07b3df2eda0b45e3d520ddb936275eecf18ca43761bd98c1ecfdeecf3265e85acbba31053961f17216915db3905311c238153db77a8487a4f3cf2c39490c1cac075c7f0e8f6220c4b403c138422ae618b52d8a9252a98021b4b4ca907cb7fa916c70b6b6eb365ecd6d3ef8cce88ec70b45af1722142268475b82be95e47b98e4b752440303b41d695a3efffbeefdf6473aaf32380522a975f190a6a998ef3d0ac21f185858f3b8e70ccab67fa880fdc8c4557257e012c1f042b7094a72fba452fdb7c1d64f515d94949b157bfbabef243cf7f5e101ed51730fb6070108e88fef135e44f6290984c0bb2aee8faaf6be7babfc20d8cdc3ef103483c9cfb87ee02116e9778d67321e1229fd5438c4d2e8adc12279f24e032e177a89785f01044d449a649842d7fcf02dc6ef196185c2b7e8ef08930adc97f7265a02be3f8a38c5ff102c864d50758c08b16daf62599bbc75b2c096c8b4272b9f36b1a0ced678b094fa175008301b4054e25d344293c199bd8266fa973df138f016dcd7f9d524132c1a5103b5255e7295851fbc39e35d5367b9a65afd4e15ef837dd3e5d6c1f8f65ed814a77e80ccd7b66948706d04a6594b6db2431f6d5b4e8306c55581787c59788fab5fc0f9f886797bf0ec5ff3a84ee2251e42be51ebd108d74b0f5b61167cf357c871931fb7129d5b5d60294c7f2d1752412a86fac42c0c7f3f0ecd03efae729c63d7b65f8fbb88fea86acc49d3848cdd18702b0737e1369281040966efb3975bf4bf41d6a45ad6a3719ce0c2cde18f74f603b75406c622ef41ef895ce84a5ad394fda7aee0d29f3a7d37002b9377768f09536e9d7389542cd133ba8579343da102ce9597e808d94207a4bb650afaac3bd4151a542dba799bdf096ae8675e48845095a9f0185c2f8f4dd1fc9a3c3d566c30d5dc55fd21f6cc54faa8c78ebf85b11570d27069cd50a32546af71a385f51ae74ffb99a3aba780a29a74dcd024c4939be2ab408728697df2f55b9f9ff172f3d32c82a4b515d29ce3f75dd8042c4de633380d446f56fd146a0e2e8bae1fdd32904a4e2c90a01229c23f2da9f1f51d118586b08accd4137f266fa91ff7280007d463ce8d102a6b8e9b0f9d5ae7045f7b5cd38cbf05d93ed4be4bbab8904a6166afd4033e018a75f259e0ccbbf52fa6d42d263d97d715a7ef154ea625b5393c5f74b56f54ad60a427789082984f27e79c1fb82295e92d83a306c71d7a494a2b84cf8b15a18e977ac647bd5e7220dbd7316a7fdd1494d56cf1bbdc1e885626fd0d0d8a7ec9283adc94c9c850190700b930a00977089b6d2cd99a9168b3daf690668353531bf5abd34e33632d6b0396839990c37ee3944632abdb2d566b146a3a4237db47a49a92b8ad12af845b6994e532756de701fd206dccb7848f431ee8ab33b3fc3f476f11aa4b57f0b363b2be6374894230291c4084e3039738e59e587cd6717b1020c143f03db63c4edfd22e2616d462c324fb1ee1ad74b9b129632a91a902c4a346cbd633870b0152316cdc9cb09e20e57ac9c6882a357710c85433d92803b2042ae4bfeebba532b4d54da89e1b7efedef057ddeb6aed4f5aa8278aef4e4bc6aff77991ed038bf3358848ad5571fb1a88dd132dcd3bbe96b54621811766f493f21a3d68f46fb8b8bcfc5e4f16612f3143ffea8edbf4be60224c0ba6699e6dfe0bd6f872f3b63936bbf48579af1e8accb28587ef620fbf4011d4ffc54994abb31e9a7c9b40b1965f68a3a7bbed048ff3ee10490548fbca268ae9f78e66d47c9294ea51031a277afc6c4d98ffdc1ae27b695f48353b7a77095ed37b3c2dfea4b5108c0489d9adbeaba61994ce21f0d6eff3ad6ff97ab5d71ae62dc0292a1dff08c471c1d9039d68f86128da0089542220fe90e13367254da884eb21b648c1e7fd02ec6c64410a4d542dbbf92516a92b2f94de7ff503ec5020387f1caf575f4e10d571e8f442e377f3fdd5f00075fdab7a2611e17fd3028fa0003c90b241c0a20c2f271d37658296087ad98a2d2e0012e50690d68f4182894ebb39f3c4a8e14006abadd551a2889f072ab8b1886d908fa052b29510b20461cffa597fa6e23aac7946ca33869e7c04299b87a696189a81a1970d5e26e0b2f3f44d5b9e32f7172d432d6b307d736dcb488f096aad315b2ca62c54190cbbb281336495ef08272219d622fed8e5632124869e0e73c6816b027c7fdfcd53d196b9e0baaec0a0573cfc36106278ed585361c30799cefc368746874baba86549eb7d8a928fbd553f6bbc8357171982b25eda5a10da1be533c11ad252e577ce303f408885cb90e8f7bf8b7566fb410edbb56de0adad1d67a2846cb2ef1b130cb5b1e556e25d5281762f5504ad04c755944af164d9370201d9b7933f3a0059c0601aa41233dfcfb2ce405f4fcc99911644ea3b3252e8a0b1a2fa23fa935622bbe94306d9e8e5470e97295d7e7660995118461d4e228c07157ad312994244611727beab206a0980630ff392596a076de8b77bad1b0ffd44766b801b889c3e27ebfd1f6a5819544c1fb09104499208d8b696c965e815343caf376c1b75010ad4cedd182e8f245e46476d7100150744d4bded63225e04b8ec8a5f2696797d45ea3ff958ff94c746bb7639e7579ab91aaa28043ddea16a2205d6b2ad05e5308a0239bb64410bfd65eb37435eff5a1e45ce6b3f1ba1b824645fdebe12e777ffe1ce0f3cec5b4f012b00017a2f30f86f18af493517bfe01ca6b17dd73d9d94a97c59890b719f256a9895591d344ab0e8c4fb0efc7f6053d37603124996b45e41cf408e8a6f040b259fd4d39f03dee958a46c2c7cfcf63cb5b1c18d7b46dbf1d7e268a2f0152f61ad4bfd6ef62307ef0ebc8d8c7d193fe9f04f430c16c6fbc042096db0a5fe17fec3324f9284a5ab1fcbb758bc7cc2d9f12bcc1aed3a20a197b5e940cd2d16628c809020306c55f967bc9cd32c5955d14f922c5bf2d77cd4a0aa97affcdda1ba80ec63cd47e2fd55c10e5095199d6ff5fc2438369be1c7e59614d83204a4b7af7e3de8c5dc372c9142a56e4f5562e236d40e84e5bad330638492f5f77cc9adeff5f08ebfa627e5321be356731ff95f81abab74589e28f24d2170ca85c829902ffae84baf8f6ca28234de3fe8f9e4975a0311d3c9b2f44de6062c3ef00eb03d0ac9c7b18a64b48b80f571a48d723325ec2a90805fda66b5f060a96f1b0fb1f5a35a3c08f9c77ff28ce8177cc13aab658cbdbd6af5e68e7e0df529353b8692dc40788befdb47ef192fdd047fd17e92141ad0640e9449621efc52f98d2e4403f3515702cc58cf970af87f6ce3f497ae1c0c2bc629431a12a26db652556c915df74c7f61c22d003a47e4e5f90802ea9681842d0062b3e0af9c2b8542ec125e3ee118002ec9fc3bf60128ee55f3c3753c74ed07c1b34e6c72eb6ed3b76d06bc96dc1542c3bdb0079f69f0dafa310dba8449202232c3a1351a481469a40fba05cf26fd77b4bf9b162243862f97ed5779cbd4bd87e842eb9fa71dc0adad35c94d04661aad8cdc77382f829a1551eec741b537c13f2887299e6c4481e51ca99b84985dea861e62c5911ab54130c9845fe96c5756da373ff036a80b062d424dcae225755518a50146cf1b0825a0f0c07ae195da316ee340c585d2e8ac75920df7c3a6a102e2dd6f0b6e59e0922e37fdfaa239525db8a9036a0e587c825059294d67f6ca44221f374d3dd0ebe97924b9753b59e3ae1c96afd6c1a484afca503b583589536d3c831bf96da3f090849d25e08d2d43b1c4ff50d5efee2b1e059d27cb519761cbedca03ff4b14327a460e88ce2d75e4d34d5f8d15fdf6d3e7c9da894fd27fd9316ce2ba5ccbd7965ffe4cb99188a94db02f708511fe4f718c4fe52740d8bee62edfbd0aab07bf51305d5dffaef24770b57b33ee5ce01a626d998b3e2320bd06b4d6212f7e44f90e5310b1aa0719a5d3e89224bf1603fda90f5689305bd660ca0649c1398115648049005f11eb4dbf369d22adf19290e88b6c8f30e9f2f6f576e33c259d738423ac00b08e07419b34eccb8b466148c9255436dfe16179a3b9fc81085e668855dc747ef1f4c6ec147d4f901925209901a07411f2254cf0f3409989b21cbf63b03521382453332a1af6902449a38300e1975b184a49284b39a9e14c0869a199805d1b384882f42b9b9b9a9cc32482f4f11de93700623afd8bb59dd57269df528d4973c2b03b561ec651020f77f254a651fcd73c20af27aa6a2c6d59a715c480c9fe8caebac1dfa9e5f8b6df16c6297405c96f5e93fe6ab2aab8b85dd4d476fe5d46f638cf0f864229f4953f3f9071ae01c523033f72df4181926e55c082d8883f39e65dad4bf0d376fd93b75688e840b3b0a6b7f3f66a62228d385616d6dc90bdcd41a665c961f7666556de75f7340deb11ceb5ad8dfdea6bb22cda805756c266ec90421ed563e3309c0a9a63e2204707cd4988a5de83a02ef5c543eac1e7efa09fa6aaf986e07cfa84b1602eb42d29848a6159586262d1b9546395e2a444828b11ad1d9464a3ff105cbec86331da5aa713329cd20c148132c73d20751b4dadf26e3b5117f6bfaec27c2dc064f7eb518ddac4eef4eb3629cfcfdfb08b82ca12420d587e86f8d41712b581f5c5987165e79e3568ae265f53d7aadb5ca6b9b19c35df9d9215662d5f9f9df8bed7f4b0fe15f8d65b8a32678f2accfa34912c595e9698780ab2e8001f9b8f8c98675937fdc7971284811a0cec7f2ce5444d7c0bbdf30319bfcc64f9201a10373332ede7b790e545bf930a2c590b6e6ef320beed63c5818aa1cb4feb24c8a80c9c0afc435cc605bbe4da9cc57659ed2e07f38b65d64f1963cc8cd75fce8bf2890878682e71f2acc49b239b4639698f04cee54adb63df73872fd3b5fa25f0fe6ccb1d359a4147eca732583d9bf00eb32550f084b9c86e8104310099cda3ecfc43fa66be2be7a8a9f77cbabdf5320cefe7b9c435eb695ec3c8db4836e74e435b7f20227f8d93ffd2f3b55eddf4617fed60766ef3c48af27ae4ca97fa90c68cf04db37d5abead4239c1e3612670101125b86608b6906b9971ac901b57869f482d141fbc52e007ed076fad6f35037d73af1bb682225363a67f7ec5450cc794e9b286a54243036ad0c799db4ec3e5c9999b2da047674c283cdbcc60651bbadb66d13a221fd13e0a2a66fe7d0d438e0ac1ed99a127cbd71d434486eeac6617a682191533ead57e9806c03302b6b042d3b645838b372f1b75b8ba26d9219b42ed7ced3657e6652341009a4286032260211f5555a1644950d442f7f609e5d4e0b571327a59f7c24af0e238b24023f43cb3459c58649bb70ca308dfc242977274908224507b2c24c7fbbe7e7788ce92386144d7410777522705de775981c7c2faff5f15d94b542ccdb61d431e1c7e6c1ca88d1fa763e6c91fe18577ff2ff144bb406322726877780fc6d0965927c53f70f87a6d75f3123f9ecf7de0b69edb67d81247b4f0501ae9f1d58e4f5a2adc06664779b2f9968bc1bbf33a24301a4ead34089b1f794552421a65e2b1e420244d98d7b9e4b60597f24a53515b3e4b2190c915e1209b2715d4c99a23f3f65fc6cd01e77c09112ac3279fab0f6e8ae436c700f0905a6749d3b086acd0faffb8b6055fdf6702629f78ee84283ad0dc5d99deb6c37384c7bf49a59584e4cf8cf07890d800ace0a2fae3c4c64236289744057fd713e796970f2074a938dd775fc6864c356455143e0158dcdd71eb9a7e691c0d5a45f26d0a5dff1ec77ae376517123f0a012099b0f11aa4e49f35b808ba28f26b26e501ae4edb84da33c421f81551b2563d6a0f4011e6c5f2c7bfc9676933731e4cc93ea32a87f7f9d9ec99652a49f7268b194a7aabfef581299eb8e1658391ce888505de1ec137266dfb6c9a19625147ecddea08a5c32b90ebe442c68489c84f3860414c59a26602093184048190431823bdc3858764711ad0d9eed61328e5147f21861601859cb68f0f9cb791473b81e3a5467b381a932c3dba38d4082a12f983bd0b8ff71752b3a9115f1a3b05f47e9e4b1460a59e49a779068fcaadc9f3f40b77da55555f926ef4fcbde9128e107c44c97d9a356b8b6efc40219fd141fc8c59184bb326a1ff5d2c95b96fd838f247e273b307e392c638001a8c40a042022b9de91a7cc59651c71f9d43f52fabe7beee7e54038c95029d3ba0ed0982df3980f15131b25454bd7fc2b6e443d30cad6a8d560ad8b4ba8f0b18a7ef773d565ecad7701f70fadf56b77bf4b89e98c8959c9687a41aa2d9127d3984737dd4b9b20406a8045fb05a9c05e8ea49cbd6c38465821cb24aa05bf5001a7dfee751368d3522f2bfec4d4384a57076127329ce488f2f8d79ea111ea2ef84b57b0024040ed7b8113b51ea699f0a59e2f5afccacddba94adf30289861f2e4c553aa9f5e3b73c347edbc4314dba1bb60da121f14fb82f06192dcc91d388f9c3d8205195909a0ab84c3e427635ff7f45f4191586a3ebfa230bd6714e3c4ace226c3774074efaf57bff6e00a7c7c03ce5444fa56d0a792034920db878ad5ff91f20e22ed577429df9f83f6942ead580e0f87abd701849e96a5111de5cbc68ffbb0eb0affefd8b22acf1b869dbef8751530eb3960a1e97661b95f3ba2c8bc72d974b5747161aad0bff8f2cb84804ac3be84755043852166ba98a2463df751afc5bf6955a18ea03750250d06eea8168b6b9fb7da66beba7e4744e0718aa9cc6843eb8b71817f4e3a6435951dcf46992d921e36f76f09e856309cdf5eafdd3d1be344ef48259bf9a840a5582ccf101fae206e10478b634063b1dd23dd34d0dbd192b35eb9622a00eacdcd10807f745ab2511a85d90af374e4cdaf4641423ea8624525edf7be5dae947c37897f2b93860cae718e7c6647837a0ea3d65b4601bb9293b965cbb365aba92a9e19a6ed0c11cb89634a8c396be098ea700ac94983ca8f3447ebb09cb4e97e6108c40df2e8af7929e7be16f6d52de9b0aa2a58d1437d4272dd82aca71e5c6a492d6b06ce64a7010445d7cf4863189ae52f63770052ba43dfce85fe56e403451f419b7f9d65f9cb5a4887363e2ce675b6494580cb0686cfec00824a9739b0c93c9f016c66b951ef24c247ed67668630ecdc4522f966e79bd488af9521b5cf9f25a85bc42bae83064b6aefdd6f55799ffa26043ece0fa7404282ca1c3dcd74364569590502db39facae19c63c37c14a6c5d38dab77f3be0da7b4b15ee14d1b5427316560f033579d4a20057bd7c7ec2d95d12ff6cbb4e56ee279bc63c0285c901d43aefea2361abebd79fa217f5fa93f15392f177d212f4bf5ced44e9962d1ea3d388e17be985c10bc94a95d037124b4af950ee19f089706bb27f826fb6a8a1822e7064ef559a79557ea5e70ee23099918b19f9bc8e4cf49aa2c300ad9bbbb563410c4f4afc496547a116d8f474717c1522962f817c8a6d9ca325a53eb607b842a529a0919da0505819bc7bf5ee2a6a184029d3615c8f894022c1ced58bc19c2dbabba0a8934e1fcf019d97a503908565864476e86ef840d6556c4af01a11254addbefe2e3aead9c0b54e0ce6c0e96fee745ce93ee38cf6bba55c67554d7352eab2404eb102611d05ff3d98686aac5c0159b10413e8a9b145a78838d15337680e372e0b3ca4cf2ddbe5eab9fd3897df3364d77343a78c4e68cb103361ec3f4657728f7ea643634d57726057511d15d3f3c7320b8de93b112d8b1c90e1eac1b309f90c9863e5d9cc64c0b67f78883023959cac573235dc5c1e6df9df816904e8d0a1dbbcdc8240021bafc1d5af5a21e3bd2cb4762171ad2773bebe0f093dbd1872138308bf9dca2272ac11b1dc1373ffc5bb6f5923b0f088fbe5d7b8b7522f6941d40218d826eff412692264753b4a2ea2867c2f011bb736595593a0a977fbbc7108970fb1d30a7fc2ab7050d741d8788d80e3667a690fceb4d4735c6e605cf92436042d795499274ab3303818122b223e88331f2750314d3fd1fa870122f44315123edde91ea17a60a5fdf1210e3bd66063a111245a36f47a252953b5c0a6dc32cc7dc0e9a8bc11774b28cbb0c5670bc330ff3f0902300283841c2b393e9f0ffd1ff343ae5104eb074079040610a0661179734da42da3ffeaa62998b4a435d1f5c4597625348257a62147b04771e44ff4252c51e2b4c3b7ccd8b857fb5e09b152cfa4d33ca3e84f0b8544a7729f6b50e94447157ba5c7a915be8e21f26e44a39f526defcd4179c85a09841a3c69a8891594cc99b09ae0f3ba10927d0d8624fbd48c7073c611230450218195cefc8cc2675971e8a946af8556bd5d987bd5d2df3a5bf2ef6569e62c9e5e860128af1e6cd37d71a535f44b659182fbc8da3493af9c334172692ad900c602afc23787076170e663ea329fcdfe7bff5983a0d79cc5e652f152b55470561ecda27f45b132e5ca91f071b71677bd3d44a93ad35030dae02c97db7323401318b864a26367b37d350c092220af060c87bb11d5a3551e1d9671ccdd0e9db3128fe62f6f0abda173316e68da1c66ada5b599ae45c857980f39a7d888965cc87bce7b5cba6daa3ff664e0ec15c14d20ff453a8e1412d6bb4d725856314e2222a6df55527c2671331268370d40e41da91bc7dad59148a7fbb2bb5849bd4d5e2a88ed58971dd0dfaae0d520a0ca5d42c619ea74ccb470ccd4a7465cedb27f4d2bfb244125ae4ad5592927d82326c89ae0348a7fcac393e4bd137c2f595a03592d68ab1c80e5136f287efa2eb602e0ea706c37f541e4585a23880d7fd90d5d8db437648897c5fdba9022ea13b73916a9070a8c22f6cec4043ccc1b3fce10f88cf6d71f572a39568452b0454a0e71e6ea345201baccd33d570dfa2fe07fe6f2598cfffd6d23bff47d3ebe59a1301c4f7b951f2ced00c3ac8328aa15759b81d16e9e0d6231bd1397fcf9d124c415cba6c77a01ae4ada0ef7637d8a4032719b7c18ecbbe65bfcff585768c452a1580facb670b175c54004bdd19392640eb23129d9c620419eafd7c41e801cc9a3d510848290983c31307b0eea1c4a52046c8a423bf1b724b921a58d95500772aa7a15302e09c7937876fd92dcf5f234b6e18c2057b42f5b74e138e7f588e58ac508d72769fa1cf9ca99a5d2ddc419d1c6e4cafbbf6f9d5d66eee9a2e973f5afcf93bf9acfec0112262a643aef104227f048fa2bff4912de26a23fb3bf7b70756a5f62953b5395b12540deb8ec30d40de29f3416462bb79695f9f7985197f3450c2afb70811cb6544bb21b8536e4c7b30705f1339c983d78a7eb413d2bbf9ba6af445d179ddbc673a5c71f0b1d1f8def0b2aac800f0940e326c9f2f04e9ccaa385100c7ab61e4b3fa3e2526fe23a2c1e6d50ecf62975c2e4d809fc8ac230f4ee8ec51247dd2da5fdb63b05969a8ede647f319d392c4dbb51f72a63a892e9b01e81579014ba206b2aeda822f0ad61c1f6ac29cde124a405416c0c0837e86e55ba6f1a455b8fb06ac94ff77ce9d65be59da5892ca7ff9974a7b05d7d9d523de63a04dd24624a6a0e0240460805212292b294f964df882c2d755f2de6abb946b4bed1e449da0e5bfb0e123ca2ebfbadb6481f268aa9046591ab291c49948620326b2fd788b4ec24060453856e66d050c62e8ca1d5e781e2ba2b2c450a97901517ac54b32200eced7c55cc15395dabe2aecee20f8ea2a5ad64bd16f8c7433d604bbc79bab5100a01f494c30f68752edd8a6919b4d01c3e3504349a8a1986d31ee2cf1741d1a3ea39a2fc582f74e29686e0c8d94004be2623ecd9374dbb894b76a06967cf6ce283dfa91e11407fce28d10b83cd3f2768c335c9a2a69fcf238c83e1c1e664fc8ed6ef1e0ab03870d456d1e01f05b9e14e46df9ff97d3a2b6e69b6db6bc4efd559e7eb55c2073d31ed69bf2b960dff8be82fc1185ddcc09a5da823682fac1044618d065b75e64aed289de86893b3471e52d83afdc5807ccc2ad9c9d37e3d28a9b5fbf7485908270851187c7ea8da0f063cf4357b82b1811325614c78fd6f14c0bdba802fd45cb43a13f38edec1dd552c00b726dd765fbc90a1dc0674f3d37c44c2d3f1552230314d070f2d704935711fe8401006502b7abb636f90a83ca709b1b6dd09a2c74d1587fff68cbf240ea673a7e3afae4f96e2830c76bb16c39ab1cd9f1d8cb3df7d73eba2458e3f89c597ca78e2ffacfe363d750045092e4b55caaf3100f36f538e04d752f27da7fe28239e5a3cbedbc4dde6c5fb82eb60fc3d4b3b21e9f8af774a5b73337ce176bc17862328d13288be27fed49df7f6a8cc8a933fa63c4f697b5572450e39e2dca0cb4081426164ecd1b1e4207906c8d862d5144813d85ad0ca44bb4ae09488197126d2864c39ce710d4cf8ae0dc77c0d77b1bc93ea5a5b06c19ff736422d9789c7bf3acd3ff84962b17991f9a74bafbba0845e72cefdfe26c6a0c7395c162525f43b8dbaf384616e1753d735c2e50203a98e0a502465c306a4050e6e8f94fb5f2beaf02c33942506ed93ee486d697ce12f06b20a6f65a129dfad068467bff0ee1a8e143e7a820501cf6686ec210d9e8dd74831161cb1ac6ee1fa6df06dc244e75f037966169b6a14b77e166620a6fd5ab5cc6f513eddb1e5e38fbb8a0b97859b29ec3d6eb88fdefe325c6f0c875d2c40439ed8c06ee32d45dac112474ff5f62deedf7e33a299a650cf53b835a4fde8844fbfcfe8c538e25a4cb213352d568c904037092b393e8a97265077cfb937341c5664d38e6cc4776febaa53c16560b48b3710e4e8ee99eecd97bcaf074bce4bf63091702e135378363b28b4bd9a69a8cad68294a8302942acd433e9b4cfe1df2bdeacb8fc429138ad6b76f50a17909dd034d28041904d7e45be663d09913bf6b88552d2a44fc847d275d49b25ddcce1cf267bb7c5e00f9addeddf57c74f040fb92bb78878354858eef9edce2f6ee3d2ff60bfd7089947f1b26e17479ca0fb5f0fd199448ec8d31ed83fe309e6e0520bdf90397a4c7761811d5175b9f9faebe0981db035f334e1d8c38ac1adde89316e5e14bce0ffde0f6aa1707dcfe57457ef112a925f9ea8fe77779eeceb26072126382f2b174199faa0e80930e359428929a0f5704e27fa060a1f45acd9f2b50660db7270a9b9752058b5e97580bd1a0d9af2de60d196a296a84b237e6ad93d26f1eac01d16fdd2a57b3dcebfb2fc1d8d884b064f303800326278cb888de7ece0e871d8b22c50344b648513f5bf381b04c2a2b3bb4543f78ad571dc85987cf38ef410f4550487d7842c1157b94d7ffd4841639a371dff84063ad5f554d2699b28e3836b8269e91feb32fded5682360d7f9334a986d7bd20d6a10c183a59f547755dd60a79a755bd5521d3339bc126ef5f62c2004e666d99c1c211fcee909db36754223ffe47571d137531034a0e1ff0cd71b1f3ae7a37f6d1116cc921bd7cb85e9d917989854e726e0e21c376a3a6ead188f223b53ae5854e9ab52e9f221017617cc68c55010a4a205441827a042218e4c92ff7ff90554ef798e84953e0ba29cd77d3713bb6a9e3b229e42091a64bd3e7b0fb2669614e4f9dfafd589e7f6701f8b1575e44ec68e8a8385726886748bb1ed05cf20e794496bcb348855265af6ae9f2a7ab63f23393dc09111481a1dced044722b40165d8dd608f5036d7886ccfb5d3fce8f9ed69c64ea2d8ab68f154d645b916a359916d3fad10fc1f4b5897bd6dab3c146931d6728abe86f9edf98b66299d9cb78f53cc6e5ad41a10570abc6308097f092afacf2dd94c7388a7dd72e80e72490b2f88dc78e7c2f7ef800c9ab97272c1d946b008e0da82ea4cd5472eb8503b99504598574d4fb104a79f42d4fd4c6a2aca3625677ac4ee59fda686e5f96df42c086bb349cea2a982ab6f11e78a0c7624f5c1cd71ef2822e63b18a8ab499d1c450f32285ef587e219c1ba9cd73743fdc3f5d8199864363363b7ce0f55e3ed61aeb84e969b5b23c278411ebe9584b917496ca1f6dd7afda09de5ae135c1a08de544f0449a55d9361f86f66957625f259969169e5832492597190a11a72acdd1bc1f8b72115db0f589ad5625dd4e92edb2f3bd58237fdd83c9cbde9ae4bf852d374c00b29cb83684e09e8419f2f66b602d717d25099ca4f125da47e7ce750950dbcfd8d602f8ef4e31db1791601635b2f41969e33ae695547bfd2cdd070588f545ef3e1eff6fc2be2bad19b8789f19158e7ee6943faf057e191790148b156177ac0808ab520ea918672917b6d2274b6991c6ffe42dbf5544a61e073008da97535ee1e08510958871c14e2190155ab82c6040cda74a06c740f1a7ae65e28509534253bcba418427dd6daf9e2616b68b6bfa4451da275779c93b0385d254d794e334363cc894486b4eed5fb5bb9be753c0ef83bc28aa17ba0e93c81bb791404ee11aa172862167a316e00bccc913449c22539dc5c6b7364ba600f5b38854e7472fd451d60bf4bcd3cac6c93e13a90be3a0fc1bd5ba1608a65fb4fffd777b4b07bfe89fb4107bcf9ec0d09665f47a8d5cca1254a80a5456d0d6fd27cff634a98c9b6ebc6ed1cf910975d7a6146da56452191beca2580c0482dab32d4d7aa92f88db188a9d24b12118cb50c00ba06f155dd11ed13cdb54c2c3f83431258b171918c3313d8811a3f0e3dba34b87444de7c2f5db52e1616bb54e32e7ae74968ee17f33cd3438a9de5b18574ae82137155f011c2bcc781f3994a12976eb23a8bcda18749b10d5e54214dff1b5ef9a44c0f1528fe9c93f5b23682c90de91fb0abd8cb4493dc84f718bf12dde2a26153d7b24e7f0d0ef5b73fc7e57412ab0f6b1ffd74260229e84e178f5915bad4be887dd54005a9bba98d1d40aee6fe843ff96d14afb58805fcaffb66cd92c46a7a17deafbeabcbc1aaf3b6f02a9e1f59ee7853be2c4b4d9289f352abcd3a86dfc1f43cb337184a88b8ef66989853e68b22fe9026fbdb5f971332ac3cb6b8a423a769ba917741a02d291d9bcedcd576945136640ef1aa2723973a75e897b1623e776da2ca9b55f4080bb0e27a9f9e9390e38b41b0b5f3b64cbc19c67bbe84ce94697d37a8341029c256b12d4655103f7414ce68038f2b63b5d82e87d5a873339ba6a1ee1541fd21c033a6d2209233c5ffd906de7475d6564deae14a28e9841fe01b6f53c45c1e9001702e47b27ee12b7fa61aafd7f740d81f39185f8952f896e8bc7d84103cce821d6524b036563fb6cb38bc5103195117706f28b209a7bfea0b316ce221c043388b554807381ae99cd60f8db2b11a268eea0ab8139ccfbe122d0973bdccd990677e9e813ab81aa4208bb6852e5caa9396b6ffe691579ce6fd3179908864133bda223b0b2525e07fdd9a5a0a18e77e57176ab83186a668fb82c7d590edcd29b8503ac1357ee401c4e105ff0a100dee9dfe452bb56c4e1480fe10fb3b92e7663c23af53c98777da20ca5020732dbe8975adb29e05aa2dc87e98e0a7b41a9fb499aaf51f953a0433735f2f2eb1b4766e0c23d76e9f2d3ed9b3c61fa017eef72c941af72d053c0ac0f592a4995b20062b7cc71b08aa5ce1b901175a1bac54245c411a24eba26d2e4da3bc2990fa8116b2be46b120c0104092d57de5bc004282e59fca7238622ba17b36c6f8606232c58580a6ba656293f85a359aa2d6f99a07bfd85e2a21039741b2378f2e8f2c74eac6d9d303ea1a733e371b89ce4fb287f93672e2410ac92aaefdf4d63d5cfe0b9d7dbed97b2cea1bdb26a0418d639c1089de163a41b627c0a8864d6eac3fec79f1c898845ee4bbabfff6e660fdbf75bc4bae46b936331cc7e413f49f6cf57ff7be50c0517982263a645e12f739f5f36790bc7579e190edd31f022450795ec8bbb72fa68bdeb70dc8ed1bd15af697bd3d1abd8f251ea46e18f01001ccb95eda946af11455cbfb7c299253617f506785adb059e54918f57d15b18f663ac0e600b4a9c9d575df85612f2e576ac840ddb444f6e0c83c5f86adf977475cce67f382bf252398fe03fb88eaea4927c935796fb4899f2632ad6243f4b25e763297458ef4989d2cb507d75f5752152df68d809b3a05c58cb46d4523b742bfaa6491b5f92d4ff11f9fec4b430a19153a35864c7d8bdd61609746dd2bfdb7240d3ae4c96ee5a487a978f36a7ef4f6f2cb2abbfb211af27f73578e138500997fcc762eedc2f17e62728b5ac9c4fee168b00735b73a9baab000a6a437d0c0fd17ae8ded5ddf3676c57a305f45a2cc7171264884b39e6863e3c223771878c21ac484c960057b580ac3e47e3bb1b1ad1b4c934a6941ac361bc0ec68c147b1a2749a17b395490d293ecfefed04f0499a0bd0a75915338012677a0f2505f396d89045941b12316ebc01a8a9e0faaddff17f988a13a94125bbad7a8040fa90b9c14d7cfd6b36f7e311a36a11a6c2ce758a2c56e3fd93fe8e8b07316381257fff40acee8e759b21c4975aca5337adaaf08bd225a7b7b0101436fbe2eda05564caff6ceba78cb5b976950549de250cbd75007445676e04979d3db626f50d69717d9e53fd16b925743525c388053fa7e50492e26e309d5d3ed2ae5f18bbf21d70a4cce2f643810e2476cee26cb6892dc69f957c2cbf1e3b496705ce14e25a3d701df555150af3e10e9fba651dcba27fb2820b275737d7c1d21c853d4ce45454e258bcc8e965fa6865433f72d9e74e6392d6d7f116edf145bb5b68829a4dcb009ba0d95593aff89b1c74c55ca5f378a97b42b8e0bbaf4ef21451b73127ffe91bd238d7069631231dbf36d899d685b021b2302045c14dbf1a746c6d8e14b9ad05b5ba18fd1438cc626c4b1ec835e710026f8bafa51f4e7ffb0098916c29ce5d4660a73ea29f6173a5c5229e20aded7478da687e0921b3057d0c8dfef62fb75826a2ea943bf368b0eeee6f88a8667c35dab2674c5bf033d1cbc1728aa82b9c228e2602473d22e51f955cfdff9d48778d91086c803c6169a7f31a52c2c4ec6209dcd332d20f7c2e871adc302210e44422c96cb8d812a512595939e54fb53893377f3f17196cfa084ed4d90f89dfea175d377a1b9316489c0cd4259802299a50aa166812dce88e7e4d859c94a46c274246517a7fc27ec908ba764883160b84ec0b77008f89bdcd15af2df6dc8fd29a630a027b19fa65ccbe5d59031684dbbc01ac68d10e38817e924031d718187777c8c3623b4a9d93d3ca8bb57f1c5c9d6c1f6ca7349754bfc98305a847a11dc8036a96137820b6f849ff8b392f47faf7b65513acf45d3ef17378ec1a5dc7cc649fd0df5a5e0fa1184addc3ce80ffbe0a91c7232c199ce1219aeaff119994ffb059358144c4404837b5cd4c5e4c3c2f51faa30fabbc33c92bcc9d158c0bee29ae59d6303f586a42806558a1e512e785482f71ee50d2a69e9004fdb5c52a05882383be7bc4e2bbb6a4b7cfe1790a6dd38be15ace9a35fc9f271bfd8999af4e032c4ba1b1033c504e224eab3dc56cc40a6d4820c5c6f505c52ffb982bea23666d40a311773eea8cade825880217d1febce5cd2fb6e68db912256a64bad4ebabfa1238463af02a708cf92d0de18292dc89952a8d8e7e1970b671261fa6a2e14768d6082096008e8f4c67f0ca920da2dacc86bd977f5cc07bc0813257e65670407bc3ac075070561dc1187002c97f4ffcffbae4e3776064fb61a7cbf252b122fd0e730e0f0004e6c302a536ee8bc290c831a53c453264d769387356c4c5d0abcee0cdb846ab813e78fdb8b4e06c7a514928876b5724f86e3eb99d0f3d4bf38c9d75913cbb930e21fcdfc73f82e3c017996860d03bbbbb1d6401c2b9ae95010991275071445e5e2af90e1427d4f809b08aeb92a42f78102af5c3dd354870c77fa0b3d1ea6de30efb243b8e8996da82045c6cf6ae8f83f4dd6902f9c7503c2014e7136779fb85d6eb5198eee166e8d7d700acebea4cae6951a23d6dbf57ac31522a88c21d175b41d8e4aaabe37269d45904c025089c902095cbab3b6fb4678e395e3517185941046fc901f6be12ea93a34e06f8284038f4df061b9bc9b52739be22bc79c0bd8fcbfa1394512b008d97afc68f04cc586e7d1ad0c8e7195a7c20fb88ee2899d9115764d603e614ddd35c0a31058d0387002d73ce568a7a6a610a5ebe9baccd5b3b8cd4390f1a9fe69ee0604cbd071b7044eaf6f0ffd8fcbf76ef28f9f5202fd71c2907caea9b988f228ddf2b321b81003e22e3ff50247f97f5772acf07f5fcc261d5c2901715a1f7d113dd8eb13205fb079f3734364ac8aaf6b1940af3f4365a44d5978685bb2210ec4c65059bb1d057dd0320a0f57d9182825bc6fe305d6b5cc7e92018e1425647f92e0d9c1c1d86d6fb760eeca49dff6b67909a8f80cad30dd173a7d7193cb7745ef4e46f13dca3f98c221606ea271f5c1cf8be098c300de8720fa0d18ec4dd288a5aeffefd37c5310c49dfbf03fe6ae4d893b393b11654d9f7ba979d45c1255a4c3c35fe09e5f8e149c31d082b06ffa6b7dff39653ed7349f8247068d3d9048b5349ee8808e0ca37ea4183c765ba81be42fea4ea802c7b842c06867db8d9ea007b90f313895460b629b2602a1f9e52efd727b7568ab65201184d818ed45741f15a46dd14fea234c87380269a529d0e3efc14ec479f810214cf966666a5390fb9e9d4aeff6043aa72bd4c1672a9a3d31dd6f55e395e5e2ce0dbf7ead5040723b97683f5b61f3c0955191fcbef2c068ab0691ff5551f299a6e00344ce835686f6cb8cb9e1cfa3ebd5aafe2e48d3b8d3d00220e22f6d1e1a4c33c5d56f95e379c2afd2ffa860c9124676527fa090f6455401c37dac61bee1f5f5e051accabb8890712873f6c4360f6dc3732131b90c28f0d0f75508127c356347e0a2d8d7d0887b7fe7c35e4b10ef0142dfd40d3a8e52b96e5d8dc53096ac8eb325abed71597138d8310d1c722b007adfdb2f2d2741b474d15650436df847c65f1d2ef311d86cd01994c3482f01a836e93c197cfb40bb42466d1c1b7249994753b82efa14c9077d3a004334801f81ec7b00a2eb61b31642c77e8f81fb05ee402011a4c3f052814f022d5171314e01f16449f79ce255d700f5fa8f0138f29f1e630c58aa20075f4e305782b2286015575858216c2cafc48299fd68f5ff77f728ce3475dad645e5c321a366857e3653ac01ea5f57df3c12f39e826b80c630e9839c733f728ca578fb1b71080eee62152bd231676273e43cfa685ee29709e4738f1a39ab48028af723a2c945b6b276d3c7de4d717f0852030957027ac13b749ce60176904b68b227c2003647c53fff1360a96859656d309dd0f44f136fc1d11aa5c739d3e910eb5d559cf999ce189818bfec6ffcf061a9e5b3e8fbdcddc283b236d82f0db697a131c6a5f65d6a101217c5d6361b69b273d6e372079201e4927b9889c267faf56c17c2a73187e50879c3dc5b56bbebb0fb9641ac8c61a18c87254fecc646fe71dfe71ae7558344d63361af1479f9e0a415746628013f817426e8047a0ed32386146842405d4831504031d40641506a043ea0aa47148fcfc0bce4365660f18c0f83619865a5dcb16cec597d5d54daf5117f2717397bf0b1a681c9a1eff638f0c78d6c1f9606dcd2418c7a8846bc60e9927371da9e1dd9645a58e3e7b54f12f81421180b63c6899afb3e07f8ac3c66b88fceb7f87d094a705956b76b37ed17fcb49ff4e7d4e39d8cdbae375bedb7cb2d3b0fcb19a2ae1b5dadaebf402ca0993a0608a27c5ab46ab2e8d5ad2d13aeed97887f2d19b4c5a36c6763a1e9c667abcd08d81d87b259985f7609d6430a633acb0e4c72821365e230b327165bfc639cf38b775dfba52045cbf085d8a51b93eeed71920fc21723682c1999eb338ffbdb046f3b8c74681bc408f12f9b9931fde809b8501b10884555714ff79ca594017794c0d7906db5c6132f9f4ced5be6797f7f75a0dd6539960eb04039704184140428e951c9f0f42d8eeaf5f36f3e0bdffd88b8d07d69ee79f29ca20690036b7d026e450505ff43b0b1c9005d839cf811c938649fb815dee7bb7eb6c4ce93bdb5f81416e1f906458a967d2699e0577fe635ca968857f8da45bd0319bc05841e5cf97fa572c76fe679baf4a0eaf81d395d8512f9eab3a159a66d19bfac615038e99771d37d45cb8f69407f607f5c10819544860a5335d83f0e2e7aee20dafdd253acf8e68a5124f2faffa312f2a1bdc71dae84ca5c5b384083d7ff7aff359640fa70b69235255989f9b90f6277bd3ff19cc4bd301e49a589cf918aae11dac83d4fbcea4152d66e07e2eae430fa6bb5b984c36995dc4882c4467d6145bb02c5ad412d7083e9d59fafa0c9b20887a9a9ff3e9ad9f49f3b59ecf4f8d30f06a20a87253773244cc63f764fb26e6c9de63be709ffa0df3023013ab6b2f861f28c7c3743a71e08aa65273cdda1d608e03cd0df2922be83e9b57df3ca90182f654f2c401c0353dc02b31a2b7c8b8e001a355d9f7095e0d32967b26e46c6013e037e115c6174dc8fec8f6a20dfcb062114e032f856fbad4d3fe9d30613ca7d0dfc589f29789aa3c163afa75768477127af3124f10e271aded1b23656ba6dd3401913f94ce13142edc4b51d7a5ee9154ec268f24158d626f38b3fcb396c0d37eaa585c8c31537ff9b27a01871aea7e137e8a5559e84fe909c5ac5c76035800458ecf1edc2274ccf008b3d4ca045e552559658d20cb4d80d98fad248d87e6a903e85124b5925aaa457a37880a93ae68882d72ecf8814ae312b389ec8627d7347d5fe2284314fe4892bbf60afe7deb860d4844f896ca2596f9b8fa3ec9b56f160d7c32b395ff37d12692093043de0780f3239c5354766936d14d4e14a30e6741254f4d48c4f5ffaab31106c6fa6631712bbef1eae11d605c709dc07993c425a75946fabc6e1756b1153f59ed9cf7e74b1df2f0e404cd5b1e5bb11720ec9289cd7c4ddb857923d53c68ad470fc6894d3813de322111a9d8f976a3a3d51e04ea55dd41e260e27943415853e484abdbca82fe8cf67f4c6cd0342753231aba7f6d652d4a54403d09014a9d8874c4fbab1844f5267c180768e8fa556a98c193afb3a9aed1b6db535b42e1442f412e9356ed16b33d65f80775894f4e1bcf79127322e739913a8a0364a1596dc812bac98a5fd4edc1619f085520e57a0e0759f209638945df6131f1b5c8bac16ffec1f45dc3892fa403215c2bd98dbd482d96a41efc7fac6121ec317c42e7a9378adeebabbcc9efffac6d18a5f4d0a2f8cd842820f0aa1b1b20f7d79b0fd4b7f620d237544c7199c889674423f9a4379983794a5e22fee4a501fb137bddd6529ceb2d44a69290cea099e0d3d1ff21d958d0cc5e9fdc7d3a1bc37614f290336a56a6a86bac97aef920f8d96dbca53f4e8488907c5d99d52f222127a6a614fafc2f0adc9824cdf77e10d8fa7ca10501ae25940c014127d760511048fc326f41c00d8c5908018103b78025080ab239534200705dae020420a8422b03008e4c38980381c8f39d0a00d8bbf30a04a2d79d536720e8bf68b496c9557e774b5ee0593a7d30f176df73b2ba5213e4f47267cf3b85d7c1d278b9f86b2a4152df6d7049cfcff892a0a86882facfa8a7eb3a5cc5b3cd10e89e88a1e058b10e708d5178d0eb8cbd029852c867646f1444f3fff6268b7c44c8886acdc3e0409f5345d9b85c01b0eda51d9f3f2d212a7ff352012fb88e9c35d3524737af5cec455efbdc78c5e8376438786b3de7a69d6f4dcbadb48fecdbd818cfd26efe134c264584f645daecebc228a402de1fffe2408fc22ca351fd2a02681259b1bd8705f91cb718795a4983d2e9bfbfb2fc59bba746e3793a689e88c47f1a5736e1143275f164d7d86c4b8b9b378725267f135e940de2f435e3e2e4e1e66067b764e560e7646367e7e334e330e7e034b3e2b1b2b4e0e4e4e530e3e3b5b4b6e231d1d3fc3d5da4035e1c7112e5035641b953cb8ac5a781a27cb91b9fba3b71067026a17e40a09f55aa22795eb81fc58f3f6f846dbda1162b327e461e5b28ea9cdaff7eba627a1237425577746199ebf7fbd3c63cbe2617351fcb9c41afb13b90ffab881a2513283255bca99d45f38ddf6bf6198c9eea90a5047501c81053f7040ebf1219fccb56a04372407e3b6ac60d54fd30b71bbaa9e3119d172e672e95b81fe271a7e1ebcd5fb16993ec1db634937002408a86c842509c2b4f203b91145f59a6a676eee5c16debb247ccfa9c58f054fca8fbe14089566786a58fbedc02c51f1baf1107a687aad9048e8d07b46ab78971aa2c198b17a17e4a9848cfd94cc4dbff49acc7724fee3555c2050f8a3f395a42ec075ac5c31e3a682f6557856491b22e8f8cc8dabfb85db77412df7c7e5dfa69502d2fda7ccf9f5ac7f33b287424ce059c0ec5f62360e3350a413f27ed0d4d90e2e693a5e2e07d13ec132df87b776716bbd126db42a33754079ce9b9d800feb483f0d0ccc699385d87e09849c4a5ea951cc3df2b79eb451bfaadec21b2d2527a782dfb744f78ec3b3ffab58b75257cad39c3e19a8ac37dfb22db12dec1081124e458c9f1b199b5a6424488c62308dae77319430de3aade58374dc81ec644377bd2b8b390d55a0bc4d958505c485184f11b1ebf69cc777d48dd0da970ad1a7dfe5d8189104e8e957a269de61989f98a8e7e1bcf2ee5cb61c08cbb46478d08bd8a0c7bfbb942e595907e29229193ef5ce6e9e3c78eaab8c05e1ab812493db7e7f1e36b2b6ea2d7855f567f5438314201151258e9cccf58bbadee2a7f6b06f1eef05f4a280c4a07776b8121868a2c98e1213c078ef18e51f325ec2394c0b8a154bed7d588fcccd22f8abc62a92c556679b89041206611230ccef4ccdb2eba4e57a228a04f6e13d2ebe6fe85d14c378ca43d0ca7c4f0fa55ec93c2488ae4a1c1dbe1987e1d229b02f1c9c4d3b252f93d014ae831c967efd14bfc4f9cbc1a442c7f641d2c36ad6ea84f74bf3dfbbb31dc5512f4d3e1cec7ee938b3abd1e9f7ebe3ec21b9d2bd685f1b41136f35d1b2da5a456f8dadccd1c4ca55d66fb4aa075a4a1830ceda621885c70990e82c3e2dcb3e92c2ef3b0a5026573e6d4b12e3b235f43e1d5a060b9671e6af44e66fc1e46e442a6df2039942c78f5b0ede083d3aac006cebff61e7246160832577c84f9442e6a4c4ed72ce4aa60fb3baa42adaca2dfeedceb0cb79fae1fa2dd34059197b465a655638967b677df7fd0c6d89db2282e802148d0a34fcefccf19d775befb6efbe7dc8e2ca9eea857f6aace666b1b2765b9fe2caa9ff7923980ebc748e8feb2a6ef8ca82ad28c3be67178771c6dcd8c9952741ef05d364a79866e1bd3c1af6805833bfa899ff863b56384bc1f0de1895ac597c73430775ff2875b3fb5a1ed4fe4b5e334b12d8eebdc88beefe98dec79581d7f3f52a780907f1eec25b395790274c90c8d1ae9189d0fce3486a928eb8955163334844e20dce30bcbdf232c1e403b2a24dc9a9f4c58d0d944c27a94d6780e747b6b9e9a50d28c896ec3119585b0790641442297ac115ca8ae87489e5117d8d4c3e7139b1915d5a178e3c1a3620ef825137622120efd8955348c69a427edfa9c86e43f69e8fc474c00edc2e7021944b9b6c190188477682e00a3d8a004e2da31509fd55c9876a2f7b877148c4a05806aef34fbd38a9762d1046461f4f700f34558dbc197bcc6ea832b336ae28d07c2b8b5cb8013cc33ca5d02df26241b298cbf8b3d6c872d4fbb18aab1d344aa48e9be29651be3e7bcad2c8d0f5b2a5818611fd990767176c9b4f252517c16d20f1c4f927e070610fd6b40b6657789b0d624dd813c8004701f937c4173fc63ee03929e466df69fe6766a09396d5fc845404df27d1c9656e080b5b24ba73f3f015103a39f42462a3e4d2666dfbb667c7417bd8b5d1794b72ec7af63f73290656797ce180c6208940b8f25ce713d4c03c1c1cb20009779e931d0a9381df374c977354384d90c04dd9f8183f99b14db8163c897a556f403017f8fdc3bae53a9d77c96f2919717ab4e8a9e6f9a4e2d7b5957cb05be20bd926500abcc23306109fd65a75144bb5a6244980515926c348258b7c818a350c05da58ec6fe6520cbaf569b91018ce05ef8cf5f6858765175fa275fd620c85f4be7fcc73085e7ae0b84ed507cf090d6c439d0ad44509171450aef7d89c5328d252e898af8a991915f174554e55f6981497123b105b90f6116b78495571cc9155549a37d82cd4bf233132b9063b6e5a4ce0a6704fba7fd831280aec443151608f4da3ab0b6c1a068b5e3ebd89308d4e4c63130663de84cfea38a09bfb2874fa1d40fc3969be4c65f7c69f7bc4b7cecd2698913430f54f4e7425feabcfbb221ede1f190d46217deb370777d34c7bce03ed244bfaf68a6623d1388bea2b20d67a36b5162d4793147ff05261858bbd8763a58c008c59f90193604e92f990a7e8567c104a39c2da38b07dcf26222d45f84f8c898ea16644d686a1aa6069812a52225eb0fa59d86271baaea8300dc7bf57df3d41bbeb02eab9bad0335cebafa4a5127074226a00356487c160b3e7cb6be90dd1c06ca080887b99ead13c6bd6097262e66e25894e480156e7d4162d56cd9d9a84e4411d20c02abcf8e6fb041923418c40603bbae9af85afe7d856cf0796ad7877aa7f7f8862cc2d492999a9f5b86f9fd6f77ac557a740915c0750dcced69c7c7a1372b55b96a91b73175877046974c35344e0a6b56e940de864fd94750128698391e2ec8611a6206bd6a4c278c6efc98bd685fbe155a6a4bd1296838e91442aab4b98503c960216e30eb8ef32f688246a18183c4364e12840d1c2e201a2dab035e488e406e858b350071b1aed44d5542d30aaf0434f3e9c0afe24b72ce46b47dd1251a5d0765bdc21a60cd4d91d164a14fcd0d81b0528475434819cffaa93f3da87e8b8dc8f9db2d920dc4b5abacb5df6e18b03a32fcc28116e425d8a7758eeb5edab3eb90008e65e5d8eddb2f7549ac38b80883e0ba9002524d8b714858d20f9a120e4ef8aab608eeb80a127741200dea00e55bde293f7ca3f9224d8e81f9ab588516ca7a49820a32a1fbc760aed1954d53a6cf3be01fec9e5775cff1002f8c6d7231a8493f124cb77e61de449cf588a50747cf9bfab04cea027e45bc30b4d9d42d362c5bc381b8e521e9df3469287d063f7a5979fbd0b72193ba7c99ba3621ede5389cea4310d400c4067f54e95647e315df3c53a4e73bd6fa10c97bed768e928f7522feff65d5d3250224d66652ea2f9c33ffb8f113d081f695359b1b7b21f85da59712f02982a6a752d84c048968052a39c95528e99c9d962fa44032f6b2274b38b4ec0a4cade8264348f3d932b5c14c5c71245eeeec30e305500eb8a5267343f3528ba97f6852069c6fce7c39fb3c64ee714e40b0b1c5578cdf7c74269d1d0624430c87acf4f58761ec71a6fd801de923cfecafdb50bc10dac141c6e377abd20cc9c446c785f4fad4da30c41b822b8c054fc4d4070070aea7a05b1efb25455f6b5155ee802d75f6493ee3d08452b1d253346c7689c96b4ac0a9ef0cc0add2b812282d37fbfa4b10d9ba4592ccb32a68709f288904d5eae1b71f14a710c6ad6020cb1293ff8c91a7c8afdf96a63b0e170708588bd9e99b2437540870941aae058ec706b2bbae2eb494dca4854b25571550e2f73f855238d4f9bb098565ad67da0b4856817e84892fbf1ad23ddf173d4a80751de73b3077988a05e2a3c705ed7ed90c12cedb469867a1c770240f7dd1b00d0ed334d43f0d8a6fe391ae52ffec5f1cea4f48a8c1df06e9010be6f39e7b20628800142a15f89c29942b322e8d92b2a02f85a9aa07fe6252935c01dc8f26a5cd972a70fa2da5e7c32d27d4d4f0bf27654b686958492bdc4ab0ac78cc30ff7dd00520117ad0040df9cd8f12315a5d5b1c7ac369493337f71dbed8853d6c8381c03a3229d79f61443410690080e830d0ee5d00a8b52d05cd3d643c07ba096d17b4baaf1c040997b1bb9662e30b8281bc23aa3c738be6f70dd75ce664a57378c2f0ee575ec484108e21030ba8d885dbda014f28a8fb1d17c09bbef0e0fd365d7051d088f9a3658376f92357afa944098d16fc7069c5c7367379587ead14a5457e2801a4e42ade62aab62ac7d2939ed77db45198dd401401dc599281488fa9b6f7f4c6d6715fb0acb38d6272c04eaa11109868d1450ac898a3b2c2fcc4bc87bd5ca6a4608644dcd580b48131c6038377240fb1c0e1bf5fcccb4ef0a670017950e96c9c5fbfdc76d2fc6a3ae8f041376acbdb39f1df6fc1a958931451ee904cd62fdd43656821eff9d2c18fa5ebce2c5a91e34cfcf6760a1de074dffbb017dc163343754323440e3ac99efc95e3a47a85faa082e5ced1c13f77868848af714bdb2bccd244f77583782a32e1e72c3400311d2d110876cc7888c1b5eaf2b90cd3feb7180c8f207c31ead502d1d2b39dd4d64a3e0a75fef4eaacbe014e68c0b9c14d7dad54f3e39e9d9eacea35e529ef37b38170358b45fcff98c81a94f4a550489011783ffea93b5ea52091d90d287164f6765a28916c1eb1b3dba5a2df7030e9d75aa417b6ef840100c37f8d246534c8f0a0ce2a79f2e020543ab090f7d20a41116a6c5246b4d4589bf50f565a1e0344b936357c9cd569c5165c9a658129d851bba2a271315f77a98339c1b0742267a50a75204eba5213116d4702697a36079b9f7b8747b95b84e27976fd5466698cf5b40796a11344902067801be2f438d4ff7ef8ca358e38b18cfbc88b1b5b0f5b567b45a36a449b099baf86b98592824b985f17d0ba9f0a47fe776ed286e83ac172c5318f3bbb3263ddd90f060c36c11fcca99f82d42a0817ae47968f84f2c6da99c42fce523a94f239f08671802ed8c01eb17b21ae19c1ab53c917518c83f05f9afa6a71068e3f05089372a6ec1dc86100ba3a3c8d4b7134b5f937884cd3b277afb67f70b76d749e757411805d4bbcedfc1d0da25200a8be42007ccdbf3e352ab24fec47e61b2e5f3adc1b95024a7c50affa4aaa1aa57e40fa779d820bb09373e417760ca48905a435a966e6fcf200d36af633cf1680fa88a9e3aa20c21ec69042c6e00afd9abafc803e6625214bf5f718969a423bc4e8702f8b9be5fe9dad7ee38ac1e1cd8256d9ad55e420f2479248e8451525a922e7e0fbf3c335631eca5cf1e9b1f6902550f04c420f3e4bbe1114bdd216de0cfaecea98699dfbb29e7af1be1d87af86158108a07b5035878f505c11fc72551f76fd8866eab5e53b425fc072600c94367e5a8d0fe03e39fc456e2feff84bb61bc7ab33df2c5c766d9f7056c06a6156726a1d9e78786116daff97d961106cbc468df8ccff1cd5693c4d5082f617156e599881cb60adee50c3f4b8725e5de63a5626ce787ce2e267e5b70e7aed1bbc17b005c154a3983d0f36118d01de42294d3cd8315b4b0d1e3e6f4ee8dde76962f062d7d6ea51e2c2c83c64e5eea9e1fdd94a50639d312118a1826e125672b8160938eaa42dbfa4c7e9a9d99d5b4857feb2c88c3e16741f3498064d3ed7152cf165c9bda27d9ab7d98a29d11773a70db243b97187d9c6bb19ac44924e6fb93061a59e4955aec6da6398ccd960a0607e6c687ccd1a129e270024e257c64e355402be87272bf0e8a5309fd7e04e6654e2af521f455ed016d4b535bbf1795cc73c5b72be1b5a63654d5e0d2a963ffabde1c368ae64213964e35538d8dd81059f591ca6eab9952479964ce6ad8890ca5085e2c618fa6579371b3fb72191d8d1bf872b54b5d697f79ef0a25059abfc2744bb690e491dd17175e7c8cc5a632ec8770ffe7f27d7d010afa5c341e69f0fa77052f43f48a64bd9171e122615e96b7ca0468abd6d2d1f750425a82d0e01ec74d6e8fa357882fe24d084d78fd03f8b843a93b2312feed8d089253cfd4e3b908c890d094a478b57d70da0b9406e3e78b647d9e7e3f1d839edbdd13265c919982515ebec8bc702771486c99fe66b959cca6ea81132d2575524a20f32d0ed9be3e0908471d96cfea5c59f54482a2afcb6697f138d674604ac889d1a8bc07dcb9a7f148c825b109ae0d24be7033dd5d54c6df68aa4e8a4bcd002e59e05cc21ac0ceb1919b399ee22a20824f703c9cceeb07513fbcc254efb204cd3a2c8d2a3ae42efd4ddd29f43daaac8f05d250f7218035986af1066d674a508cb478e31da742febd19eeb7e39f29839e651d6e49998a3d6b58f5f31bab5c0897fc4ce41f0a74274052be44d28b4bfc259abcf69c406dc39a459bb2923a13b2632bface5f5039a682ede10967a0af707be49f15d1a90a67a4c1ad693403fb5c767c254dc06440adc2caa97fc6d7cf8815442c029883b3d97676182398c7ad81f5e9a6211ccf30bb701eda6dcf18d4dda2fc69d0a46a2f0562eb7328dc9beabc65e15f3931a8232d15934497678bbf6e8c335ffa7bcf673f9f11e33e23585402ca521d86e9e2fc525199b7565e143db3579d3ef9c2a16dec8a8f618c47b342efec98c7db1486d0c154baf3f1b41f9b3da12590c370c30d82a1b0a57491fbb0cf88e745c7b9ad432cde3db595cc8cb68b06e266837d29afc0c5f71c56d666688515461ba2cbbaa833010627d5ae5408e2ae10043df5a3056b1ec5144914fabeebcdc5a9e2039304035285d3f10b4d5a73d60eba3a82d5beffe4fdee4b522b007ba55ebcc4b3f2de7bf2df77c279a7b7d6d58c0fffb7f6bca1cab97a1422249fbed1355002a475487f79a2dd7fd4df39c1dffd3898b28fcd7ccb51cee32fb91fa4775677a32f94fd204ddee5a343d921842e25deed1b938e1dce6f1b6dd16fe5b375517ad9de165735b59a62d6f644015a786acdb079aea562190883db2239ad7a49314512350c22a67b1ce2b17bc6855fd4a90d5d98f3da5958ea45f7230a70c37cd21fca8b9a895891f06df1883a961cf68237f23df96ba1d87e3be96f2fdb00598d3356557b3f51d76b4070c381d7385804cbc8b0d29aff63c811bd09586da7a49471d20a3908263d4f7c661071000756b98d9bfa6fd6b53737718f92e8c7cbd545cbb995b152ae7ff6fe7d8bf76805601d411f409c2284a53a0114e81e59b31db9fb6bdb6f1a3c00e5b9c07ffce0b65fe2007cc99f27f47a3b95c74018ea6c6720c6c56fef4d0b3ab5543ce8fea85244b70ade1a3bd76da7f96cf8e7b99655b82f7c1aa1f9b24d18d8f8134cce76bda3a2798900c02bf1255bed12fa56e9f7093c64e6e15cd13cafd891828a9915d185d856954e56366f60c8ec0b65a0757e136dfb18b25fab4eae28ae46d349e48ebaec9594575a733e920f176451c9317dcab9f0ac72186ed707193ea942483239094b97214a523a1ff323d0d7eaebe38e50d8cfbc9d3c17e9c150936c238411cbd61dd84150892b8b9ad24056fa0f202ead0a83c903d0aee17e21afe08d50b6cf9fa116ef6e60c403fc97be72b768934736ab8e24fd22690ffe7a75eb56174c15d2f4b51596d956519d16fbaff984b44a4682dcde25d518894a4766607757c3076808f795ea050532243a00379e2b479f2553e544df3b4ebc755d1081e1d33b97b7b20230e78ad45a43bd806196dd4ea65c5e5ec9140600333b0e870855d620b098593f5154fc44142b3a3cbec329ebc68b592b26eb4cfb50786c942c5c59f8a2385238d98c1476208c59e96650f75ce3ea0b251d00f512d199debce4bdd50c2b156add44178132fffe4c8cd1a06b05febacd3f40f0c4672bdf8eee7ec45bf24ec722f4d4b7ff56b6e277231c4f41495b45fc8c345b823aa0e214075ac0b2ccb4e9a683caabeabc8867747879c8140409a8326a5491abc21d41401208358c069f6fcc68a15a2bb53d18ab589e733e47f26745c1020344ef5cfdfdf93b3ba03f3ba634666e0513b6d89bd4ce6d3888589e7fd131e03a80da46e11d907dc7e70540a190090ae35416da74198daf50026f67da10e3d18bdda1da2248f35590b44ffe140a887a782b64e179b883fc466e0422125fe84ce69cbe7381151641edccdbe99a2842d91fc11022c70d638d69b678d893f03442bb2140f7237ac89a49a7dd54e3fb8987b50d2d128530025fc327b9b26c11fb4d3e13cc52398e0bb4738b8ef252c8223895f7eadce99b68017a40e073f902b8474460b4f10f9475b826fc61b8f0f9a4003a6571201dc9f9d0c6404713e0d29e63f3f38fc25e473f54545821df17362bb7db23cb32a94742c9eb55b29839d69c611c0f0f98192efbfd9abe42a2007b01fbf4f2a97877559a70ea4605c35bbbe7c6e21599c677c9c72d1125565f2dd3e369226a5bc3f216d2860c17b2d9a06a9fa3d66cf6270b36a01bd072f54b4de1f7f24b327a2fd479919596bc92be500a9204ee6464e5b3301cc4f8d3e54bb81305527542cb86cc70c68b0fc6adea8015483b4f4428529451a73b9f90a6918e7f3e15e9852ea85acbc3cfff33a1b70f8f85d100dddcce8ee9bdb11615611d1b04b781c57cbdb7e41e9e3bae545bf68a2246cab054f4be3e430c6b1179473c7128d2cb55b9fcfe3181595f41010a3a8702e9bd1793ad354a3612cc5b2464db5002e2b020341cf89ade9f3d58669113ae94abcaef08e2be5a55b4349b86f07c6b4e5ea965081c10b03b2740449f789b3f62e25c3d47e64dfc944533f2641dd89d5d5cf40133021138b716b3914625333650f811a117f5b600f323e2e8cb442e07c0884612add3718833fa6d17cbb8173e3272f2a07f69f16fefcf67330e337d27bdf957efca439e217cd9214c790380d188ac0c3ed696febe91c248b31d8050340d024c96a767ff3ba73da7e14fb1db7a399d758ce6be03bf56b941a5bcef57a1d9afa0d7fffefe709e3fe2a2ef2516e5d1e4a54ecf9ff1a3951d422ff681f79f55a6c7a6bbc94bba050650d1b187d7cb13b3abbde2550b5e68344990203dbb1ef1a2be5d9ddee7a772d90700dea727f4b555b739e7091e6fd19f4317f899a3cabddc9f1de617dd1e74301fa00eb275aa5c9630a3022801cc547b4a4122e0b6e4c4413755b5e666db648c6b5c6b1a53e0e5959e7b836fc3674e978829a1a0adeb49b6b6c67b319481b98ba4689ee502202cdea2b96f4df569ae3db4984eff67ef1f9e46bea062cde3106f3c5d58da167f50c69cfcd16af6276f5cc9c12f11711604922e4bb454888d9bad40b3564ee4b38cc08a3bdeb0bef35cf7ada3df414b9f02250ddc77bcaecc8cb14d2271ab371d58bafb449d68641b611feafbf7efeffdfe42188e31f22c002cc18896f60343da6e153046e4fb76706368e076c2ae75fb413be5f6753e6c7ba048835b7575c8a00ac01730866fb8d754c7acad1c53de72ac7664e1c988f06b7ff93abe4dd0fb6d116a4240ab9b18fc06912f97c464a8115cf803d78ee2a3adbfc582bb6402e1ff616fe4f65aa17bfd558df0487d23864beebe3deaface517bc16d3b76fa2603fb1fc39431dcf03e9c549b2ff2b087ec216b7f09cf8be6f6e7b429a88281ce822d2a2fd91fe495d20dc01298ef81933370352961a93923e4dd1331fce977b87e36a346606f773f8aec7db15d174113f40ef6eaca6281a139b8b7a2b08ca9de105653eb6da1deac8ef77c754a1d033d5c2ee190781eb8fbefe3ae51f144945efa2ec787f3000b8e9dcbddfa1e8b2df6caf422a30cb099ff974126ea2229bf13ff660bf27d10c34a7b8399de877098d3b11568e729ac52265c904fe67aacf4fa3ee871fb8b251c87c4fa9a4a08e6ff66fae0dad5896f7dafa948bed0830c7944c62514f33d1a83477dd667fc7d5c16201d01f08e163de6c1d13e8c0f3db64b86563b4aa7c8947ebf1811792939f9856136e02d0fbfb038f7fdd65229ba5fdb0f02d092bb36f007b45a7f277c00bef28efd7c9ecd942a8e2f3c5c4380213409160a3bf06eaa3546b17bc6889276f9f24e69b7714a94e38f177b019b46da5a53e3ae2f1bc296fa4a7e7fdef3173f7a29dff32dfd1d23db0e69b17f6a6587890ea620936ad5009e8e589486b213f76dd371ee56fb9b6373dae42aee5258fbababd2f0ffe3bf35634496135078fdbf92f5731143275eba0d7c77679c21b7e705bddba65d35936d14788aaffb8f44197bed7b37240ee6774baeab1972077b1a2940ffee7c7e5c56b9c44e820e0f10033cd0c65495e3b8d5119c1b4ff8b8974d5333607872430dec43b585e92b35de3cea1ad00e883b09aa40636eb62f6be1b37754bf0901a9cc52fbf99a37dda13f0288efc9c1f0e23c70c906e06a0001bfd35d0de95d9b772a85818549283dab9a113cabe8af3abfb9f655e36322b0240b94185c3f7eef96b225078a565f5373e2929b40cd834b9621ed9e78aaa277b5687538a6071c8e34fc677ac68174bd7854938a9092f0eecbcf52eaa7b9e327ccf47c65b758cfda311f40807d172b066fc3028e540c9b4f1a6d86917ba9fd1ea882598b747c96079f5ddf20ca20a67c3a5dfbba7391a8c5559736a7141a987a72d1696c61bfdfe5200c5b3f2d21f55111a724ffba3633999bed24393b73fd3dd0d0f41ba4ccd65023f1f00ca6152ac39aa7ae1d0e3d010d35fa5b26d92d6e578772f439ef81fb17534da1369f5214560711fc81a5c7d8d22827af091419df92d8c07fb906dd8609e2cd22fd25d97fac3ecd3be0bfd82d85f32bb7c58b902a9376e7adaf7a9fb38426c6e871c7397edc30676dc15bc2bc56f402c985c4b3a00c56f57f9e5ecb9e216415c5d67b0c149153765ba6578294a075c4ad941e2609272f1863bcddff627f2a93a93811db1ea67ed697dba445144debbf48228ecaf22ac426dc3463fd10dd6c9f2a16d6ee2a14c6038d4ef946bd6ca51caf6ff411180bead160af2dcd7a6135cb11084f2c965fac5effbf26712efb36072e148a2317d1b5539384209e95aa7b8e5746bbeaca3803523bc0bffcc2e6ae0fd89c5abb1e64423248a2812d5093c0dde130224a8f8882b976c76c40a2ec5695f1575cf058e5a163e740592d048d3f368898101fe41acda0b2693b6d0362fa11a3b2671f17ae6f6a2d2dd504620e8a960a9e0361620709a48801396b6e6ee316e7802efc7d9828b69f724c94544e65fef84b51ad5bf3e45d5dc2f4da30dd34a8fa540eb400185c2c8b24e8f6eddedeee3c01fa07b69143025e500869dcb3bbddedf777491d82532eb7fd22859610e27eb433c005942ef9568e37488573475e138fddd7b98fb29472d6df652f1f93e029adf65a7e4342128f3d50d6f1bc232533e9afb0869db71fb2ff45c35c5169256a571f3727799c222cff7de13a3cef3d3968756a79c127d270fcd87e37b6ee012519994950e52583005f59401bae29c952b3fb81534457ec033b083e45eebef44534ad2eb9f0efc194fd3c87fe428fb2bf9ed0b97120f97af138648783bc09df8b8774719f9241fc4852e2ed67626aeee2e202e806d521d5552583e6fbf3753b4949e8855d824c6d24331f792f67efc76e64b2de9c8950eca981a19300d6caf2039add375ea9e8ad7f2fa88304e42e789cfcbfb3e17c2b0f4522357877d4bd9c32a43c3b54be6349a83693cf65e9675d1744c0e11592553dbcc997a6e046651bb0bf4f1eb21a08a9857211efb190bc5a2a32f7daa4918fe56ca079ce9ce21735252a24885fe4799d07231eb6f048170c0eafac72fe20706846b82a298451adad0ddcea56e76c6690f9941b1f176f014ee0e30d34e4bca604ad0bb11e196843b5a73750c26f2b6d9e74b652a4bb6bfb99119b0c588c179fffafce3e54f4697de1d8b18c2f64bc257b521128308db49df3fb014688012565d4c40c06ff73bb5ea11cc1a9870b2e6da7f69d720f85e8998e281079bbd1191173aca0da0c1afe00daa95456e582d18ce55ab1c940a785965cf362e3f42826b1f3782233bb0b22e74a06dca1818981cfa6eb3aa9ea2d35922a6e7d144be09ada8b6655e549c4ddf44483b43dab0e884a5ffee57d082b8258fb1e05c5a0eb415eacb9e2ccee9a8d0dd22aee476b88447a87907a412cb647bf9a37bf1a2248df7eded9fb23d4a56ca86a45851f8da9cebe27a12c82d524e86bffc1dcc3dcaf5844e51b5bfcac87d9d6ec4d941ef5756439a08ac7db1a884b9884d87353ea3455e2b96201a2aa67e628ce3e5ae8efd883733c311c76c761eb52f239dee10d240991eb98afa719b65c398174e05b6e57a71fd839677e38f587c1e868cb0526491b0d4a5bac7b24dd97cdbcca7f98d0211886c0e53e269a910d24facd6d3a6d38ce32d6f376ae4eafae417d2a8298b6fcc18367412641644006321e38b489a26a565557f265c9d6351c236d11a4873a4bd08e6230e9f81a6a030a1226a16f6cc8a58cd44746a4cf64da1591004388f4503940a31f5a184a984081f175748182b4d53a5912ca2f63ace761e3e66aeda1c345d8f07177b82b730b2b34337ec8e2442ae87228fdfc325e7fde5291d7182eb19491b85c276132215b62d51f9760f7282d4f56dabfc11078a0d7b2c829d8678ef9ff5cece5ddec54a9bdd846d20e44987995a6eee0d4db2f268a4719450f7abcb2bfc4462a9846d7e3cd53beb59c484ce1a51694b215ed75e317e4e916ccf823fd45a8c0267c140909a403719e7a1d089a948a458a64a0efe9145182bd2030098571a3e718c9c07edc12aae1bfdd563f063191e50ad02b0a9e7f46c3d5251d1f0ddba48fcf163b50623fbd278e235d1dbdc1e376ff068e03b476013730481f3fea6a94a46a7dd549e918215051314307d38a900978c08c5865990dc1d9c1f4bed1216134b1d517501f18b13f7420200943985957e3a32816bff855da72f8748cdb7cde53e2ae37e53ec99a6dfe15b2f1d08a0c3edf0cf9c26d1baeb2220f2a65fb50d4cc48e3355dd452deef0ab3fbbe96a1dc0997e891df3979e15e93d8d96d41e5913f526cf8e786f73c8cb974ce625b837123d9bdd066901b0303223b461e0634dec1a99e3605bbe127e6acf8b9f7e274c23e1cac9c5c3039249c7d1134304f53b944886e91fa61d568e77d42c5a7040b3ab97996696327a7fb490bf3625cd1bbd1399497a50d4182c4246c61c099a621a1d32a3e636b39b320a967c3d00e397334e0ef66b194dfcd5c09687f1f782d97761dd122b108dc8655a06db37d0ad977613b9ddd113fa58d9164e6df2e2d4dc0103b34c297976d788939a543e7d700b6c11f777e1fcee2aa22b1f1351433fa2a3324943fc8fa4a2b563b306ef8be7cf345e731e1028764e3be74fcdc2a37125ef886d9efbee6ade3e62ba01ff7f86759941f9df14f12d7729005379894cd662cff1d8cd4e652193e605643a065f37ba254ce332448d12d48007ce4b1dbddbeb0cd0c8eb6a0c83e2956cc97f4b4698b2c4fb99135e3f6d0cd2fa510169bf74d9614129a8059e2a4f67c3e357a7b19f3a0451635f4e548f63933f16fcc16ec17b75fc2263749568d1a2e0bfb4b29a263567f6faa57339cbc985460b7a403da2a923e12b57e9f0702c76d3c1f6c8d5f3d674e1b3307c330ed77bc4f983cfe94b928077f994e7b94b29807b182b3c9f2e97d74a38bd85c114cba2d247e34220edc57e3ecc2d7af9af9e10000170a62e876df2543c4ad3788884ba56b5dfa8f37e14362c2bb792903bf1172bc0dc7a47d0b3cac17fdf7271be76bbc03f710261f07135e48a759e3d3c00f97d466b398059cac31033ffd8454d6e173c7d986cb050bbb3034b5add5c511228300092f647046f26f506446208d035ab21406a2527a5aca6603329668b23dc5d99289109d976fbc736078678bcf152517d7e4a58011097a744b4e620c489e3e0c80ccbd2f2f381474e493201a403a610d896e977a71d3044007a1848db8130b83f55a1883f339635f6d799f19f619c0cf12a6642e25a5cf03fe2a79fefd976e7ad57c246a78d5823bb691ed7fe14c42c57a160051438216162c4fd12c05e2eb6fb73196c28b52ac0b86f563e4791f074aae55bcd8af0a47064e6c892116ffe8d2aad9bd30fd8f59541bda21e8099e2250920c7a536afb48ba6d692946c205d8ce9c0daa7433a5bb8b65b6f6daae83e9a3c9f6c7f42711dd4ab5fc0154e766501b2b9b15549a99d31c8364f3cff65d0524534b7fffce63c465f822a847d555ef5c57bddfc1784b0572a6e7f2df88fdf1be6c941bd1b57af489904ffdae2a994dccb1fbd55df4aafbce5a1cccb7c2271e0f39a470844daf108ef21ef68346331f8357f37a4715e05bada76a281bf6354a6b906495e73b885208e60f140cafebb5f6f50f05faf6c4aa3657e879ddb94ed186aaa32f3ff98c01e0aefb91e298df10c214ad57dd45afcd1227c7827aae7bfbf5d1b502a7aca1ce85410c17c664e45c28b3d0e1cb2d2171d97931942270023cc1402723fbe3c69c75fb03cd58fa80a4977f24294e86648473265a9b7ba26f8d20698bf01b6c25d4b9ea625007e98eeef51ad42654d7a8a61d49b71d85a289716b07cba46e19f75c684b6f4883159e097c97e71565cab30d44de3d67d604a7b4b335febd413174715feb4a721708a04c4e3e9d540fcd92dff8f15b3f760e8f5515b5bcc68df81e868b244b231d351d62b9be6a29d025f5618e089d0b4de46bc0a9d13f43b0fa6dcc563ed3fd65df3a63558f2b455eec7d6af84cfec7d678681217431031c0eb43e8b4f3a0d308d0c85cf1af6d60992ec6324b5d4737fa6f98592ad1bc1f811e43e8a39c9f82c45d4d149e96e2a245cf165c2c63a52e43ccc03e019f284b69f70fff3feb6b4992192a4dae7dca578a844daa7644cef61ac1c396042fcc21ef1ccf254660e8998b4f87d8a591cd2f516575ee851d9d60c8be2702f4813966f320ab6d6d4bab00d42637bfaafc43968cd71a29722c4f03a82250f534094a0a612b0ebd2b9f39a18747bd12bd85eafdb8f2ecc99ca8800b5ad8f9e03f25ae0233febe3872dde6fd6f67ddeddf5beb77a4999bfbeec0487f127e42f5c92cd787168701967afa300f9534a929f80bb910b8ac03972b2c03f6e575385a10752bf50095ef431fb1fa546e1d0f37f181291eb96065fed591a2a7dc1d61f4ae47257151e3499f485071a93afebf3b81e64be524125b34ac44e303b092ea3c34d6a486d9e9cda09c1aa0ed4dadaff7027676d6eea6afa444c0955c131d04e6e768acac491279168f2507832817dd96988b7c3386ab27d5eede54aa5a0e208df6c113e4ffaa5a973b92029ee1f9cba82773fa861976d7e191145442d398818df2f7bccb0b7cdaff53920fd438b2afa02e7660bfe5cf49bdf0ff4f1c950d0acdc8db9ac718f567fb9783e60a4faf82b237d5adf4073427e00c04094e3a3fab748d305d5e69e545200d3efc3dabe68f43d117f0dc74a3a805ec144f416f8954e5836ce77d0ab72baa3afdc5696964acab1026be418facae73ddf8b4673d6299f8f59ca72b7c0271361e3aeb087d4a077ef0e4d5fc66940f48def1c875b229d9df0b1688ed8034ff799d9c257042b2f73298fdaf69e18a4697eae56ee8e766e5e766a5ee092ac19c1ca5ddce61bb2a20d0a056fbb843be386760817ebb817f1f048c1f884400449d443169c27fea9762b25d6f9fded0d1c973ddcb091e85c3b174fccb9ace843589c44bc7d09559f11ed019dc0ff8c15bfc38e6887fa55066f8dc2fcd90d1b24280bbf00e6755c02addad88f29b2550ee7e3128cef50ace952d1ebb6eddd26ac7e65fc376de0e900ba277b29a18da779269b80a7193765e9a7d1837c87727978c476907fa523cf6045817828bd2190bd8a40243fe02d09eb6d72c6f914d02e89ca29c4f03b6bbe2e4dbb0f6784a85aeb734c78537732b76073f5f3b1e1e2f5f5f6b7f1f572b4e5f2e0b0b7f774f372b7f4f6e273b2b7b5b7f7b7e0e57475b77770f3e072b7f0f7f5e3f2f67177e3b4e0f3767742d3cf32766caaf8f6961c666447bf6cd675904c5380e9cecacc603b99582d3ffc6307c02ae8429f7ae5e2ed1196e47049fe64191f2ae83970f7f4ff88f911654680e45ef8fbc9e48afb9d97a3a04459718d8cc6e145da51ac99e1fb0fe1779a9077090b26b58d660acb9317d86cfb9234fd75a39630b65f7c31c44937c0766d15ba1484d2fdb8efb46c0026cd362bd4b81d20d8ec59138c7994e5d33f719cf59b866f64cacd25d46d17e51a4dd261b0ea09afb48d824195fd5041a720a6342f2d3bacf4ac1bd78fdb41dfe63c2538035f16b743c2c5703bd0cf5e3d52e4184f543b2a8f86c31a0e06b8f0228c0b669534b788a876b0143e03bf9715013eded2f962877fe96a61213697446f430506bc11180bb7ddb34e0921d1f66e141d500d80ede551d56360c002e86fc6f0b39b4f9e628928292ce2c17e40400c54368bf1ccbb6417ef5159e16c65a3263c2ecd211ed4de2e99812248e9ed7e163877ab3617011d14c0c189366ee231194fc61545640f89a07a6c1d6c266d59964587dd56f8602c864047c6aec36713b97d541605c4126c770cf95a56ee2fe5d8759f9c92a4046dc4d74f675e26e81479b5c707c5c9647b4187c2f264847f864e1cc9060a8a0899ebf8afe3846014b46c03bd1fd9859a7cdd56ee23a4eb99592cae836fc9a866b649dc00c0ba3fac8f2c3731be3eeed4f4fe8b61bfa4ed5cefd9347cc1a47e0ef52b13741470eb0f016c7c4474317665f457a6b8070cd1fc88b3e79bb3e5ca374edc658f5deba387e500a8367d1b292b33bd7c0cf4d4b4191685510ce86b8dd140264e122817d7234edef62400b32cb6afddbe70200daa4fd366d526801b61a49692fb6c2a2550e47c9ce82d87160f2ca83f5a5d575f047f7fffdd9ec086a87d78aafa56b2f3f4bf0c9a857b266d730d9504c43a6aafeafe148b2154541ff2071b72c780e94da2ac526e1f450b5175f7d114602af3eefcb7c8488b16ab548861c09edcb3e975200846bb92b94b4f4f99a95f1ad2770f7fcf5ad33bfd6088353dab95b4700c0293f4a608c8a35ec8b2e4f6fbdf8e5be93831d88617355a945a25b53e76db23812c2267c10b13a4bb9b96ad3d3ded4e3bdcced276b1d90d9f29a1a49c08ddd00deb7d5de585cc1cd8a922ab1d6bac99d27d35a83b5babaa4dc50cb07234f9858d63861202701b3f62c7837fff170df54f0b3aa063bb376be48fd37a0ceac6d08fea76e777b76e5678baa766348ac0f9c5cbf9f0e721e0f642b5521cb893ca6699e2de6b3c30f75b0db44b5e906082f9988c38568df4f1601782432bc2b7f9110ce6ed655b4898d538739c21a1a7faff7f51e08bd9617f4130bf20373c8fdc7a02f78254bd76e2555fed22a103b1bb22ab4a4bad9d2af8918033e05059fde06391e571391dba845138b789f65ca6b03baa6191124e29ab668d534423b11d78274d4740c1aa1a86a34ffeafba2529fae804861bceafbec520009a224982fdf013a548ae5d74439b350e01b9f1afc8a54c5d0dab25c4df3ebece3a6b077b024d597d904471502a8a84018748e7ebacf520001009902f3e94fff258a5231fa7d4187bfeb3b30bd6f9c97c52f19c7e5c6bbe75b462dc4d4a73090371ef513c22d7b2d6cf8c8f555b1a117582857ffe8c867808d281bb1527ca341155362ff2dbc8b6ecaff2f44ed301689305085c3900558d9137e6aa19900718ed1c71b05bf0151c44d67ebb88d9f1684063b681b9e326b08f3decb60c394d46b827f71339f46a9818ae762aef4f4b47f6388eb85730b6d0339c65a82cde2300d65177f0ac6843be72fef0b46647e8a66f1ab00c0a10b67ff5a8b27810c35e1d0346fbc48dea599fe6d4290f65f1918d1e3dd784fd243ceab9772c96a1d9fe95e7d3fa9514b9820195d7754de4d896dcbe8c4eabdc32c77783f24765cd4ea8c50c04292d8b4dab450453ce5deba6d7785eef50468b16db755a22e4b76e15f3a3a05cd110f3609c1e60b9dc2d4121f4cb1719b69be8cd4471e44211996417ff4992b42bfbaa074f35410738eee1f1961b43ac43e257170d13e5954813447ba236da19645182ccdf9085c6f23091c2969e4c592070a6d0bb9a0dd49cc752d47114195145a4e4a0a1b5bad42c3fb9ed16199b7026c463f689f8352baba93ba546ade940ed955820fbe725459d79101695b9512746bae1d8bf4bd913a9ffd150aa4df4efd6282837c9911ff5cc1f911dfb79db6b03309f4617a8dc0dd8933628fb3b3b490beb745b3baa1191faa4e39e55caf54ddf751864e076876b43be0e570f259e77cba2c48c55d5e961c7e7ded6fbcbde55088bafa70036c3fcfe9ea662a8739487761e7b09847409c5a29b647622f4f08968d2d26221d108519c252b2e542cd49c7f4ada3c3fd436256a35c0ca62089796f7620f1d45dc27144647d4b320ed4e2c6456160e4e6c148b0c165860390be927169c8c8dcb6586fd6b3c7629bf793a9d59c3106bd60ba3473ec48f1d812d51b31c8f2fa6a4f58581293b89da6d0e31d5851803350b66622150776481fee863c54c390b13ca2499097969b6d44c50298111ce547e79662923a622d44bb3ca120162abb4ec2c5f9ed1086164fa239fc54965a802843457c702996d1c2d3409cc0cb2295333f4e42956eab2b4893ffe4bfbbff9826e4eafd0e593f9c18cdfccb3c88810e0f7f813f529c1d57ddf1531ef09b32472f2c09e9737088b23bd59be0e7f53e7936de8542177bbb76cc893e0ed0c701213011309131b260e4c5c98b8335131f1edc801d046a298432a219eac31732226bf1a274fd6ccbfcc08dd39126f469149de2c4295e4ab332f0064851093c12aa3f8dd0b3c1923242a859333c12bae223c44b0bb544cd524779c5aa24cf0b558340138c8ca009eddd0e489899e81f91599246a2b25abb3c18a892aa92a5195a92fcd969c0908c2954a34e14cc59767922a652fcdaa48c4ca8d3426d52d2fafd7e93266f819f342a7f476c6428d3836d23fd8427a10c2ba80a7f01a57f106d0b3e111a6a72325fdc332af73c56187fbccbd304bca66467f1b326ba70f872aa1672507d9c3fed0cb70081eed6d18de14b9b167220776ac0cda2fffac193d703619f1084883a85987c6682e40663bf1f6efc7915870352e426aa774648a0013c37510e4f3fb61e5a9f67d8b0034d722b241b8d342e9b253caf49527981389795bce2fd351649c02dae3ebd42efd4a21f3473eefabbcbf5a78fc076cd66f06c7a4e0c0dd4607ca5f1d1508555e31132ae7514479db1d8729e2a134bf32008a041bfd35d071fe96bcc858845a6c19593ef6655bf790e81bb51cfbfe4254bf1d41056eae3bf33b25cc6f2bc2d81d3a2726af3cc4357cc1dfbecf0d704634981605bff903d148d6ca5f0abbb578f384fa1f2154131f554dac86e102b7b9167f9d89c3509e3c7500beb1a6c6cae733eab693d737bada744c703eda7441c02acb598141c56090fd8b10cb9e38ad821c94b46fecf3f5d6cb4709196bea9ebda7b6c7be901a129c5106b4624c001f0bf8744e73d34fca9cb4c290cf0e7f8a499f5f3fb06a8e43c7dd94f99570305c6cea2969b910615b7104cae610422c4340e41acf2698e8d6f728b4ce6de9f7e40298befd29f9cb88cf5f3935340886afd3be9ca2b138f951a9c324682b6802b9cbcc3e802313ef45a36a0b5ad3ffbd204eac67e1b4ad25c8e63cbf867b63a5138422f3c3b918fcbf0712f7404294127a745a5ac3a2c69ea6a95016fd6f8d6fc18e6588a23265822f5b10c324aa7053fce9280464c462066999386746f8bf0cdb122c1124abd0a665e5bd2ab39ff04bef407f6ef06fb802f7b6f933670a63e627c4c314d8f40d02ae30fe58b6fc85f39721fc9e4f573b5143d00dbfc4b87fdae0394a9051a40996c1864152c3ae234b2ed8f73770a2a8ad6bd0a94b9b5283820493c562b11e25b121dd35c20404306560237205cc09bdba43f1b8b887ef8e8a643d93c6aae62674157318691d0870c3a2ede4a89f834874c38148762218a43d2cabc6ee320f4d91a630278f6af9f0343166835fd10978bccea53daf4dfb5207f8103813c373744361912d6f723d28046d6614fc78a9d7c0ff9348bb7e7607968ec2ec11bf42c860fe58f696c55bffec5c2a1f5327f8f7db9e392cd8639be1d78225b9ed0185efd604aa1c8eaf0c2ee4728df685928ec3f39c15a677cccf898306cbbbb654bc73e39e27827885dfea403c9c686ed3a8c3d58b2abfaec80c38316e0cc6858fc32b17426501965f38c5226b7fd4d2c930026d11888aa2ff5709a608d5bd8da273a138395fffa7100fd401826251f793d2df9cbb91dd0dd46b86f551be3774feab914d7e66c6eb89c03a5bec80fae2db6b12ce8b51b3b6f1b6b4177d52e77aefe2d11ae8739a718c203764ae6a781065c512d5b445394a6f13e22d9aa26db8b0acc1fc257cdee2928fffd6d26e1a3c6bfbc10097f2143439beeb185baa1c3239898dff832657d519b58f0a632d9c7146f3d44e00836a4d9a8a418a201b33f7643db5461add37b86c6ac7904068ae79756437c5e7bb010b1d4422addcbd960c65b3fbfa6639f0e48c013e48f4857908d1e1563b8ac1df02ea877dff74b2f12250a5b2ea954f15ea2181294b075028a79f9738868fe865dfbc32590656bf0f19002f4af71723a7b2ea229ecda473b69a69b25cf24e9c4909017018ee29c70fd5fa5bbc49de1bf4c0def20bf2e5b5897393c540f7f12fee69b7c92c8f2357c5d28fba1c6d6161dd823bdd9fc4c92f9bbc2e7087d1ffd53aaf7c56c723f550ccacfebd06fbdb5b20c9d102c54fdf2ba5bd6ce664ec4cebb7146f2ed5648ec2c89e76f3ef5993a946fb8af69a1cbff51c062a4638e3badf2ebe56146e85a40192a6d6c461a130d10c7655145508300e35805630abe7b72d79b4bc7b609a0ba97ce1ae1e5872f257ce67492c9c0900dd55d2267d4b1711c56ea29101b9d929fd35b96b5d3afdf7ee1b84a22fe634a4078f5fff5bfa6725b2be9880c0ab76ed2e091c7f5fee7efef443cd1817d063fbee22aa62abda944674856e3f01e8163ce125360cb45dff72a47b42d8eef0f34255d703febd0cd8b686442c4eb77720c848408a7614dfc161e9dacec38767e351fb9b19e08042d9ee3a24e96752f5b4cadd9e3615cd829876d682f7a59c4d4a287cb3dbe564ed1ae81667c45a26b8f7f98e27035217b41819d3dab50b9aba6d2e4bd23b4626d81c4ff11f446ae554fdfb1bbf458981fc3376ba8e72bd762438dfdf6cc92e7a287ce5a751f2a1a0e48a8f1ef384b03cdf0529e67e7d6f00afd0abdfc01ceb21e3d5fa0e0fdba48ea03bb2c3ba141c135cf9cf955c9c63621125a77a31218a2cb94577b9154d1c34bb5c3ed465cd131e07ddc8c2409686b4528b2de5db3cad21fd65e47dd97224f8de54e0696e7b3837d386d39b9180b8535c0f5308143fe19bfe93d8afa07f3a2e7bfa31d8f5765e049ca74634a97f8be198faf8883268019a7aca771fcde96a6dc8b59fa5e32687f74b5f590b7f8abab29807c46c3cb8b031c82cf3a66aedf5b0347da8b05d51b25cd298ab02679adb2f9ad20f063ccc0d0be5e1793f86b47afec3546e68304f45e2c85c506a715634652b624acc86e3e7e24bae3e634994f6e0b1a7af18a61359d0adb700dcf67c819f03d1959dfd1d604295147f54b938369b769fd018e623cc8dbd91f2f9f6c35834f98fe4949e1bf2b78dc798fe64af784beabfc6188b2364fd53b67f41e29dcd02bb58f4c5083b4cadb860acf8d7f4a5f61aaa0f0fa5779ca00effbb2568414145b9df3b85e8c3fdb7b4bd9072e9bbb93fffec8e37f415a87c2a3b4972aeb372b2f5a70b88db058ebf3f96b97627c7bbfb88d6c3999ed344c308d80937719eaa0318709af4e4a9fde510cce30a391a7410ecc50a79c60039d008538f17eaf30bc6d012e029f6fc02c74e639dd4102583fe76dadbe9a1f34aaa725d4cb43030c286a30cdc15371fefb818ec01b3109e7fbc6285fe697cb61a133ef57e9cf38c4765948334c009822562e49d2cdfa0e83c6b96ff43b551cbe0c8f74299fa4daaa808db353ff2deee54ce8bf11fdfd2d76549140b50ec16316ac0725b497a79cb8dd786dde19295152236571822d6c22dae20a480df8d37cdb280aeffcdbd34bded604bc403e7b1cf59ab45f8713171a6587bdc6eef3171132406d586c03b7116c0ee115b5dfe7fa13aa91925fe8e8a43c66dc6521adf5048e3e5f1a85d56be51ceb1ae6a112b92a5fe5848926699b4540950210d61b2faaf21a3238ff922f03c34ddece8cab9724884872a99a2b24cab772282aa688c9c04bd3dab598218d97ea92595654646074b8b49d3a77c2ac894aa4cc6f4fcc7df7c3f2ae8e2276507f3b2eefe04c1f9dbb7dfc2d1134000f5ef6b09772ac50a533a21e0c54871318c756bba8aa0ebbdbdc2525c0dd8f88afe32033fcec357116ab5425622c5773d8e991a17ac7973e9b436b1da28d4f2806b8d10b408c99af96c188d6e36467a9ca316299fc8475be3928879e87e995e58cd07071ac28048d2abb3690dcde818077596abf36a7c8303349b6540d3de1fcca497dc3f0e73a110c03f924896611b264d33a549c8bc36c3e5f1ef7958e9f8d8c9d7bc3755a16f9f21f3df8edfe16c4680788b553400b7403a69e34d3eb0bb3d35f6776d97600cdc0f0536ceeef6b26ec411273dc9b1af1b649c1f62e8258e703585f85aede5ce4a6b6c00d8566bafa7d74676e5bac3a3c99bfb209a755b460c9ad7a02c34af616d47849bb82ba9ac96b060370d56f6c0dd83e879c87fbce6d0157f4f70c3d5ef7b0f38499ad8e278c6344d40141e8db5436d9bcaa3c812811b051e2dc082aa26221a341c596cc2a3078148811952e068675fc196e147459febd812a13cb4505b709386e2982da7f17b4baf597e3a039da7f453a126bba5df6b61f5b4da860e5a671d605a00b6e74f23d07b8a8152b78abc4ecb4ebd33c5f3f31b98088e43cb9bb730d353e6fe79ad8bcaa8b49d950f3449eaae850152eafd5e67e13dad876d90a7d89bcc7d48c72d84a6f89dd003379feb0483604eb5d1ad2ec5f67e96ed6c2a4cee0999adeafdc8310c7ea7c1e8b926e70cdc32ab7dec1d8efd36a21e6f3723dfb695ebece46bad9b853ab9278d41580696b00dae1b3edcf51eb99ad0c5248d292c1768e21c2044c665f90649e98edea307bbf99ff39d66e2e0c8a1518c0e7b1a44f7c87266f8da4123f36ce4af8b5bf566328d55aac18e68ef812f48401d4505e0a72eb737fde69abd7f62b270800e9cde42fadc6f83ac0c0096e3a5fe40e27d51ac9ebee1f0458a2d3874afc1f5330a0dfd6eb674940b37ff8c961ade831cbc3864873a08611dc113b07f162bb927e68bc2813b9ea8d82cbd5ad1407b46129dd845cc163291627148167ed9413fd843040b201ca76d7b4a838b0e63789ead3d73fa41ec129687374b947786671ddea12228c4f176ab9f20bd08aaa8483489ac744e54e41f5bca05952e7638114580ce4490591fefab124496f4c65910a5ede2f45bc7971a28ce3b11b3f3ee1d7c649a3f5cad58a6e30ebb522355485578e86d98657cb9ae302e188f00c66f21bd1d3fc7838bb116d22394ace2246780831f4f6581c06ff12025ed905c4a268d0a0aea2e097c5c8f4735b4317dcb689958b9c5c245053931a437f484adf8ea065b0a2c2a23092fb7b194e89a0179f00592fd5331327d8ff226b411d06b2479daada82956f51d60ba4101713eb6ba535e1e7d23ae0884b460b0a9dea3a965e524727144c8867b162bbab19976fcafe0ad01c77747cc75fef3182698020671ded895c59791795ae55ce0e519e828f621cb90577d2ee996a2ecf1d73b7cfa172e2312ffe1a06d574caaad8de1cd6882fec0c6982580fa8f158a43cc6edbd7604ca1a7c7647bd4d7e80557ff44286726f6de7aaa10eeba888fc7b52c99df6fd3154fd1bd9d68a41c7b8c0a99c3d3c2f0b12f8f39b8b19558a989ce8e0dc17800776e1f565eeccf97718195499de10117601fe6847e7e0b0178bac4b401f80ff254815ba4e46e09f35bc8f8b83d556be1b103f41d7e2a8856e4ee3382c14ecdf7b09d3644da2b676d015492ae114754dd7f270e46c4abf1e13720c78256b18f803d1234e33f0d245025944a11b0ed4afce4145727b4ee1c8caa1a0f31d27ee6754aaf8ce044695e17bc748adca03286eb172f02ceb2c3bda7b83414367c684247846f2c932bc0e76bc89498c88dbf0e67a77d6a07b87c5d8482a1dfd0c34558f3850f69fc8dcdf21ec33e12f21aa14e7628b399d1cd9940345df7b5d23338fe4e1b62a7d0edb61fc04c603547cc388784722ce6ec255fbb58b5e464c6f7be65fc7b59763dc7d6c01009962e38eac62768fe9050b347c2986c2ed2400000657e16d421ef748bf7951ecaaca807662a3a27cfd2b2f518e4870cb6813085e9123d06b62513a02362e18b1e03dbd024b21670346447835817324921ff0feefa85a04f92326fe2c09b087734ad8e108415d49a077b4a1afd21404ac4c1e33829105731622f86cf48ff645fe1856387c96456c95878833bc6420f5895511bec677fb2d35d01be84f2b6b6c2362ce53a0b3d2e3263649286291cb12509851b45e08f7ce08c9b5b1c2afa4818b912da1090afdf6b8baa16c0a5494950c6d8a6ee8e39b4063cfad8d0c5257b2c0c7432627fe2f344c43957395f73c2c6f5bb32ac034df6184502dadb161177417523753bdf503363c7804173ac1ae5266477abb5312f3de15e5bf19bd46ce83bc4ca873eab2b94e7a7d5b4a632011e00b72a551cdc9c4e97f3a0cf86ac75b7575e13b267e77392c439a63ad3382124df01d3086056e12f180a450b49897b9d69a5adde6d1bc9915ac84d9cb544bcd44f0a65dcd9b928222499b07961b08461d1089a03b19890f3e7df328e69ad4cb8ece078c7098a4edaa6a14b466f8b981055f20cd478f9da02010dc28f1990805b37df437ccf57136d90d611e4532921597981b92ff292f94e3889c4c4abe6ac025b2b2a3b695c4c86a98404d9d0c4e978aa88faedb26437e9aaa3e1c1643f3ff8578e07c96cbc8481f39495fafcc20037c3af03022f4c91853a26b6922ac40a33f97ab839118e7f6c5aeeddb025166ce0b7f7872dbfaf784a51f0590aefba300a1ca75af4b08bd1145ded617122ac9b68e9707f5df576a7651d4fdd5f16ea7f8449d6f9953eca1350860656bd805cad03f344517d0db5964100e7d278e824a503985ad3f2ebbee17e065ec52fb8a3bd3b1d09ac7d627b5d2d3de52f2c5a99f89861275a1ea79cb9543b7094f6c3fabb9b433ce2fbb08383cbf734b42320538c7dfebff11cd1d7c3a3da31890f331a2c78e396cc74ed110f7c44bb1fe5dbe83a5c3734de75f5d04dfff4396d0538c53181d0e0842409ad8575c05134f87706a5505d0baccc87a685b32c0e1e242e338f5f029bcd8412ed1b12e77afa64e057a61196b6e6e11e7f6408a06c52c746d87b0946396a97b2b8310850289c2c341adac75fd871028aded555805cef42d413a8dee635c23b9b6efdeb693b8b4eb639a88d0db39e6f74dd7037af9fa974d006a15175d65b801348a9855ffecf61e44bdb79c2dd71ac1216cc65b639edcaafd445fbc3c15817a5bc14f49a2b48faf91b6fd2c3bae1270713c747faccd2366f466d2b557fe3971947fff024d83945c8da881d833ed773cb1e100554470f6780266600f8ff892127524c3fe7c86b96e6dc090251b146f1dbc0cdff412c450363e0a32b8321d916b88145eaa04cff3a45dced912a5c74df28cbb00f20798e8f1a0d23efbf1f5dc6f8d24befb19075eba727212d9d85aa1369cc0fe745003c1c6eb9ab03a1d43686b8075f6325ddb6bb5d731f8adbcaaae9cbbff1386f7b8c1495f755e01d6ef84ff02afa85da35cd10ceedf1a0456ebca749904d0f75ff9d61aeece4eca8b63f04e0ea1fff441722ac97a8aabfc37bf7095d1777f91f30bb569726472e899b5dae0278aefe06bb8b14d05ed7f2dea56269fe82b4dd8ef85e1b97199684f455472e0e753e4d8b774c57061db5d37c3d440dc521ca65ec339849e5dd26b4da846c259c5fd6e6b39931bb749b0b7d3a84a74579d64eb1063558c594602856b05bc2064a95d372ec408a0fc13ee5348f665efffe8e7e29607ecb1c17f7a8efa4eccf3d7bac195c1aa63ec02ab3c72193b274b944fe834cb7032e59e74b695e6a0c7acf343161a2a24d7e54ee3a0fb9770e0ddb074ad8700c8527737a43392471784714cd4fbb779173610b46b81bd9800dea6fe244b161bf097ac1f1ff8fc69b5fbfbdcb8219206743eac29981c52ae4ea6c78ac04ffc239061f918f4aa8f8b1ca19625605e14c3bf3faead0e1bfc060b38eff22d1339b73cf5733a3c8308fb35612ec50abef98f0547e56c5768719ab170028126cd4f6bd8042ad47d57969e81437f4e472f5fa8e8a2765d8d1a298ae76c4ca0015a752585bf10f1aa7591e1fb2c9e3073f48ef301afb9556dccd879229002f6af7fb349520f325e0aa92a097fec30e4a112be773e27d3699d8204381672ef5f4639dedcb59edbb442c52036b65aab248568569e22a59a7c4c54101873b6c739ef2cb80f01cf66fac744d00425395bcce1a06d268f76394c525c83b237e2ce825f9ed27b8c5d81d30aec16ad7d354fbd9d4b82c4edd80b6da5aac8d1f6e91d72fc7ea86b3297ca470f0ed1ee009951a40ab1902857287ef2ccb9b69045c6a8b411cfcd0b2361bb4b90125eb97ccd6fa494d91c12812846a6fa17108ebcc80a7d31564ddffcd1b4afdb34b4176e549d1ef6ba764fe83499c21032ff9c5aebfc169eda4334b9add4c78efd1d2b975316541c4d2b4779a2ee9c1ec5d44026a7481aebfea33845056925e939e7edb979a6f48aa0e3abcb09f75fce4d9176f5a0d57a7441f11f7fa6b6f41efaabdccbffd856370f6b5c78c86163ca1e26bb90f91d1639c370b4fe5e8e2d44c66027a0a673f21bd6a6f1893a32d284276ccf1163d7b636be30fded7c6a01931dbb5eab64700b61e6945eed3f5101e9b85b849755c05f98aa68df4df7fd02d05e668f234ff655a78ee1c41976609691f20959ef37a431e26328e36d998c72e2d1301fb272d47aca89c86ce7cf318ff2d39cfc9c5b1b901f07474e64dc7f238c3fe10001dcdfa9a8ff030f84f689cc55e607ef28b33121b4e8b1455bf27a6171353c028e0befde1784831e8027ee0f4b2086e37803881d255a3134895132a97c951357dde01e929992921860969dc2a96281120d21914060730c334bc1ca69982831c71827107ec24ba8d695a0f05f5ffae8f6b7eac1b357497c9235a3f04213aab5e22f831bc672d48e4e721bd7a93c6aa2ad9ad00d45d99895c86db8eddddb995771ee90f7c6d2e545696772c7abb5c95469764c2236b1b05a456df347eaef4551075f88cc8d341bd857b528970fe12c311747e2ec58eaec7451845fefc71c0d5fef2e861158f1e86eff921c8186234458a5cfdb067d922df1324adb7b2c12d4c3d01085ba982787285deed68bc141cfcce8a2601c606090fcdde37da86c0b6697b42173d93b0b72d7dc525290febd190829865691b275992d2e4dde3e2a808f6628c5b09a0142959449c9a841a591b88c5cff6e86351df1f712a41908471647ebd9561572eab15ff8795d0afb59cce227d12a917c436c7e93608d2336bd3f10bc03362f9d56a418855649c64e23c95a96545936ae493085fba5ef740a9b367f73746e26af018d214ccae9d3a2d6b496e3a4ab683329581a0668858093cfb2eee178e0ddbe0afeb0cdcd5bb516a97847ec786c4a464e007ec2158c9c3443f320b0983bbd09b625356189c2dbdd78b4b45a0e5d826d3923762c61fc13de8de0a0741757ba4ba415cd287e4ed2005767238f861c6967f8a66cbeaba83f854eed48bcaf8e1a0a2ec782998659dd7f907399f754733f15d6d556e131fe3bfe28a67e500fbe5d235356386d8af14fbd6f13104f5152924035aed757b28e69705b72c4dda0ea877270e87fedcff83cd104aac36c2c835cd4aa0e27bbe36ab16115c604dcbe85f1ea64dcf8406e9454c115c177dcf565505af96567421a63cb39502d1782fcb962816c093ee6e8db9a0d67d10be0dd71a6ae24ca87cb8fdd10734ff0ba6350c13644222e68f44387eca2fb647bfec29ec1228dbdb36ab2618d368276ea8b1342e1cb897a7c436761e3ecdebf3a5ae3b3bce44a16b389ebfb787cd35c424d02ac3ceb0f6920af7844f0aa8250c01167792a6f3ca07b7a8393917d05299e09fcd0a464df6c6bfc2e6ca35343028fec261c23f26cbf2a6f3241f6e5da1b062f5ae9b788677c1c5d1f190787108958ace8f3a6f74184cd9a77d2289db119423e5d95b7c0c3f52b011f5636043d8da7397fddd44fdfba7e2dd8736ff638640bdb55b89f8e2c6146aca389ea7b8808e5fbbeb3fe3e6ffbf23f297249a01346a1f29bc9862a1279d5dee6aaf49fa48d939ff2ac95289377be55f46a64c4fc8447eadfbda9b87c42a5684b3e85ebfef75f7b2ed4eccbb959bcbde5601f8b641036e7059b7e2cda50e8affb1d8bf15651de9d6871b85b75e8699eb4ec7ba650d0728e8d56e116c3ffd0d5c5ffe8694d7a05ea4ff356c90ff938d564ab5172d9951ad24114f30f38faad085cc969fd52a592c06acdd36fa5eef01f661cd3f2a8d5e913f3b7cec767f1a95b76a79bc09d01a428128abbc13144ac55677089c8fe8963f51f86b5022937150fdaf8dfa34372492d7000fd7c38bf4d55c04a40a3f9b330711d775e3d181294dac161c3e76254f136bfb75016b0ed397d65409fdc2241d329ecfc58882bffa252e7d8f04f1bfe3a3c0d296c35d303d360f1779a5ef666be3e2cdda2993c5675979bd17a4a2f448c8c797c242c2163220de244d84c333db908f1d2c9ad3fb790ffd61e367a3c7bdd5f18fab6a9cd552c1ae287fd3f85779b4ca2ba0c37f81c366b1c5a8fb63122d7331f27ce879266da57535558db3eac99f46fe59c3cbdfd4ee0228126cd4f69d2fa39afbfd4cc8796b689e063bcdc94ff8f45e2c48f5f3e1b4f78fe0f38d9a3f8e63b4fdc474507ef0479adc6e625f0a70f1f8cf4e4990bffa2ab72a978dbf2661562d2139ce12fbbce31bbb4b32ee03b2fc7e26a3aabb69e2ec3e93408be1e63915ae8edfbf4bddda96b4a4b6987d5c30af921c420793d9d296dfd32b338863b8b0bfa89242b5b207b09734cf65d555517a92c5abbef88edcf13e32f24d41771aa43980985018e1858ef8ae8895138ccc9f4f839cee90d0ab0b351d88807078928d01fa1df2f0270e83baab1a4e3317525cc55cd70829d1f3ac29e02b3f1bf036461c6837b70e19a9ef1da221fca1433dafd586c6d31220e3e164799e94147181403b04818143176d9f9d2c6262c6691daf2741f8ed0cbb5f2a35c400e162c1c3e3bfa711d771965e6468d7c15e259f8575f73f19a8c9a7f4fd8ad88ed80f6b036e0c2f0c42b226d3c9230c445f07ff43e1aee24499195aea0a78d835bde5b47c45c0dc544eed6142e86167dbfc3829b9e5a2b9a35ecad092d41417b54a9cc9dab1ba2f235953f2feccc4ff9cfa0a60e7ff4ab5f67ae343b21fcf903489a6a176e30f66d73881e924817567d86b057f5587412d6ababac5a1a52563530abeec446503b16d0194e45f78e20a819fdaddb6d5b2b12316925c986c12f26cccf9ff7bd112b4916176c2d6d4de4280cabdf35757e26920c7969ed858cd4a68f7c8cda51b17e77f1e3b95cc7c01fef1d66f002def68cd1509a15d9e8986867113c41193b6bcb6c187901193a3a0517d888945c744db9f65186c4234dff0b468ddccbb37bfeff343918aea3b8771c44675773a40d6c046408be10d67bef2965d0d60c215fe75ea1e894b98a1f5c0ac26307ace5010676526ef069746ae99fa83a09611c6e296245954425ec3e1f46f50f188e87a676c53c1d89bb7ff4e58a9f28c310c904116c1fecfb1f7946c5185a2f9cd2fcb57fa2ab9bbaf4606eb7c0045c57ec3b7fd13440f82470bf2d1dfa01f47daceb8df4e3418bb95a0147bc46ff6c70359f80527997f62c62dcc03b2e292839339d85ca3833d544dcbbb0a2ca1251d4e3f3445ecfcf4c9f3e820dc75ea20f7dba73b268f051bdefc92ec18d8fed036b20a0dee0ad05a2a03725a246db712333f0ef093f34044959ffa62228e8653d1f01009ab6a3889541d33c6f9ef3a712777542c556b3f6b9da38e2f31d4ca57ffcd15eaf3968f1d01444dfdeb64a587114b67c41e2d8ad7317316ea7f04f7db6564538dde0e9f29c33ac2be82e8fe402623e827813fe2194e6584b4858522a22c9a8bf9dc63acdc81fc719ebb26edfc0f73cc280a6ed34e16d7d914f5b40794f9437db3d9ca5fe875a13e4f0669bd4371e963fb13691469a30c902a4aa1a68da700219e148e5a36d93c9748340230d39cf43790c42521a4786e2875fcacf937cf733c8039a5e0c73929611043593122bdc50316a7ffbe80693a4247a7a99bdd3f68a771a734a7464f0c6983d1a13e7838773da9bbfcfb7df8b12270df9de33fc3c60f900b45a2dfa3dfab9aa82490a609a65b036134d5224f501d95347222cc8d3010440ad525145cb67779500acf94ba83373b1f515e89c3144e887f682792934b05bd6014ac825d700b1e1000c28018900564765238f954a8811ad0057480316006ac012be008b8006fc00f840211201e4802e9401628044a403550039a8136d00df481516002cc020b601958039bc006d807f6c0217002ae813bf0083c81d7c0e7e0bf1208211b80623900500100f398b5c7676a990b7eecb43b68d3faaca8995683dab0e74c8977780c07730ed1522e5476f45de52a7e208b6d5b8de1d4f2f652603728f9e6cdfd89baee7669a12583dbd2bfcce92b99cdf37cbd35e237aebd76ceb46f247ff2c37b46645f5e44931d94eeb5bb63c18807d5e44ab8f094eefb3d0d425da68867ead40f8559f6059536d1459f053d8452f220b1db60847479f9cd4a04c28a178af8a410c0805959802ef5bde415b475d5126dde6f0d2ec14154da04888473ba10da9b896047bc7d725a0040eaec9673b9c249c980244c0562c1081dcdf79f4b508fd0ce2ff68e822c3b205858672ce362782465f7d2adc37434904cd47b5ab589e78040f7199a3e68fae9e6978639ef5915c2a87a53bc55b6ede165cec6ce8312d6798c7ff8260ab088379a54389c69e786a647b23cd54c79720f0440e2463488b3a5633a54fab052d96b878f88087931c456b36fc8a158b14a3d233a07cbf934fb561189131339e4865213740f2cf83eb1126129dff7847f65d5092e50afef7525c1b48a7aed815abc2cced11676f65fd5dd639be8d3c0164e19a50d05ff131397e0997c29627065af938df29875fc28477d6a2656cd9c774fa8dd844c528ee3bcf94b930b11e987614fbc7f0a25cc8b26e68748620af223fb52e9286741fd6b26ae5922410f848de607e2fdb49173794c71c8d1351644e732b1851fb43eeaeefd55c80b45f20c0d2c506733044c3b11d0f852669b85d8d621d4490aea3dada206ac60a3ed2c167e833d66fd3ada6ffe8d8e8154b27fb0a3467f16a93bb107a69808a50aeb814ccb6df6bbdbd3d784c68d1c43836a26aee40a7102a4fb688649da79e303c7e22e58996a9d8aa40e8accb6ac6cd42a0434942739a41ac4f548826c25591e36f6a5e828b0b2bc8ddcfa773d27ebca0a7e4a6d507a78249c653b8492ec886af2dd05de25bb66392eab8cee9e58001a3dadfaa1a8103d0ab1fce3bebcc6e489d14cb4f0d5be468f8f20de3a6ea10c35938c078167240c790d65b2226d1e1d945fed0968e0046d633c980949628ad851787d3907587f6f42d93d5ca46ec3f10f8905a189451a1c03a037605f8ae82e010f07d4717c0bfd5933096c53f057401e2379e5732d6ee2f0eb16641970140492be5556726f8b253fb5b7981b8982bf8af544534a342cae07d45a8d2f2596e1a696e9d574bc82420c7675e86464698f60ed1f6d55f33569e1100efa506409d02b147e908bc9a0c19130fb5244774754c3cfbc8aa468e2b44aebdc82f9b6b35800d14ede3005b310bbe413a1362d69d35088fe2ad2088215410a6c89e830c54e15d79c004da9990c262ac49805f55c267432c9463957ef7ffbca8409e85d3e5e559d9e0a6d76534ad046754a947b5a656c6e415c571d49b74334e5d81c57c1129c22f924398f33b93e7394b32f8596992ce1447603cf42536b6e243207029821098507d1762198fe1bfd8ae31ef87a8a33e9d1f0dea5cd81623201fdd8f8524867e442ec6d173e9a5673d909c1a34464d92e15f8ff4db233c9db60b31f0a4759638948a9b8520c8fda14b2bca68f79242a28d0ca320ea1c479920cb5e321b41b970ef99df39994685a914689ace0360826cea33be8774be58e911b908628a10c84c2cd6c6f0ac23696097c7b478e620ea67261a2d9451acf988285a502b12f457417f0219944651b15c51bec3afe7bc82be02cec9f0af99cbf9b8fd96b6e8eb2b27713c51eecdae57358a9acab66f10baa955585d8f66ad63b9b7690a7a259237721e43d259a17526afb4408e1ff0009abbf4e4f580fb84ce4c8cf921fa24e08f8b915f281976438bb32bc9889055d124dc178d1fcfcac193307ea140a3572c132757381a3393597671ddaf8cb2c07c602a93666fdb96a8beba13cccf95ae31814eca8d1bc224df45dc1b952c16f70630e6efb6b295b3b514747ff6a0b53a2d93597bde907cbb6721485c2909e78961dda2f2b9659602125fe136322042d2ad1f4cf2d487116665f0a63196683ad3992e586824afcd0c937d703a243a18f5c1c727aaa4705d6f4f46d7d7af852c43df1ede75bd8a4fff547372d043014e9e7c209f43a3c914a3384314eaf39f7c638718d043fc85655882c615fea24f25950d2c1aa87d38fe45361bc4361ccaac83f7ca8eaa6ec6731cbb048904ad642b95040a9df5da13439ce499829fabb48b3155650e74408fb522476611ad8315778c902677ecc2345216a902363c807c1eff6815b738c583404bec026c81473c1b02f8596d97dd557a6990141cb6a6e56280a1a81ac6460d289fdb72074728bacb2849fa0b2252ac29b021222c1c69712ba7b7756f85954688d96d55ce1680e7c13590eb568c0c23bc1e2d71ec1c274835f83482e5b0986c2c692f008bcef9c948edcc4023eba7700880ae9d7c2b7634ca92273ca9483d46bc3cb276e3b3c5a51a2b9b12ae834d992895d5538ef8d2cbcab88a5ba8ab1fd103c4cbf1440a0ddc2577da7a4ef925d5aba63a9a7783fd1ea322dd054c135c0be948861ced9d9771fa7a055a7954007a5c585421f5e0964d48fc19506e331ca9af5e3b927e7120c2a5475c0d3aa85d19f4f0975748fb70d8d5b5c5756fa85060ef549b3ad9e853f5f9083309a9ea5f0a0c4315dde674042bef14b3211e4635d1cd211b31cd59d4888b29d622de4eccbb796a529aa26930a40bf168aa1bc6a1426db1aeabc8227a0a1cf2d58e21d0da21bb18fc25e5c0a96307e8b8c359cd3d9adc725f4df4654b544652c57502bb02f8574265982f518aa88d69567098b0a3a837a2d4c7fe7554bdf309ba160203e472e2a9f9c44b53394ba7a71261c617834d444fd658fc6ce5d11e7e309e682665fead6028cd6d55c4511330245b2542356996213773e1c9408411b8ab40c7405494925531c7d247902dc65f54e79d42342d13fd555ea98039b46faa530a399d85e1aff11920b9f89361669790e40b0932c6c527474f931466549f5e3a91f6f26c8c72c2519f03d1602451d7141f3b700b43565682a180bba12aa5272fe5ce4dd552fa7665f951e0c19e34d03f532529565e753c2c3102dea7f506419d6df101ffb1a45783e9fc541ef315bd4c44e12fb43b0d4400c7072a7ad0f02ed02ff7b9536b1f23743d697ab9bb560a8f25d8ba2589c7e222adae846f551878c561c50cd1255e400d050b02f758936d75cd56b6f76e02c9ae5c05434b28da532edba5d7bb6c8f327920c7e5682645fcac4eebe2f35423b0f10b4719a555848c175da5822e2a6ec103cd1946b91f723da5ca495ae290427d897228931499c235ee52124147ed231ec01bec247ae047231365c08975245cbee537387d0e3f50748f7a434027d091b6750d2b5a8b12f457467e2c926d8e6738baadf77fd55be01402ee01529996764b8d8a6b14a2568dd78242e0767b705d945a177a445f146f0fc6d242aa3266e95a5a2f9668f65958700a984b1bb7e6138724329059ade8abb599a4dd1acfe741c8a449c8213c1652a31a9382b0af25dbf25e2adc6d6eb87ce23a0b93a12a54c7bc08c82852402b8c39bd44041af761f256344ea34f17f214efd647af5da4221f5dd9160beadcd862b7933e4df56e7c66772a7a1e82635cb3e6c298aca6ab545c81cf731a545a81b77809b74b77a889ee262ca8479a6694f49dfb45df325b2ec4e55413842d171bf8823c3fca88a02622f7339f783ee28ec8de2055fb9a0223fb2268116aef8716d94b4eeabc37ca2316ffc2f033f31f9af0662b3e0e1d03b2f6bb7a875199f77fe5eafcda865c5235c151b1655c500e1065151130400be9be5419429f5c14246fe49fe3f6448220aed701f8efe677ae5a7d30ec3957e4c607e651cc0fa754b32938f8c2ec34c03db18511ae84aab2f5b7fae2759b73701cc912a74f988748621571b73e4764ab5eecdd87c09c29cd57153041709478ef00e54543885adce95b8dd44d1836e3a934aa9ca34c9efd50daf01d1e21cfc898d052b72fb93802b72b4d2e80b7857ab8d7192b406cdbdb0e49eb8832064ef76385e3ede94cb7907c47fb39ef27b79750d9084401fb34590a5b36b43be364b8f54e659f1da4e3e84e1169aa7dfe0f04abcec454d318e78cf10b893945d09d619c206e4672e99d99988f30aeeace4a32952f2d0616d3e6d59fd7faf41ccf458c677fbd15d2d14df6f00bccf1c55c8e8a0558c00ee4b9778039e05f718094ea8bb56600a91d073897964620ee78324219c49264201c1c3dd2e312d2a440bfbe1503b2820d799424c3db7377ff9b6f545d9dc1a2218d06994e0b247ea6f8e9f45195be99c1c92f65652459491efea9cfb8995a7c5d5c05e5edd9ccc6134cf44dd095a25c3f0b507528b671e421a88e08d4dec60780296c9531013a4bbde9a2343035d8c14839723b4bcca1da8945d3f758ecf44215240e20725136a321b7d2b840d5f1331c2a6587893a82a890458f2d36b6ee50c8f911eee6c457fbe7adf60ab5debaaa6ff81cdf1b533fd7061674071fc74a58412ffe311fcf97749a6700a18c48b3a380d909a79cbb4f615b4369740fb5368224ead171de83cb81ebdb3d884f18700962981f14ac585ae069127e395d6216d669d14af9ab5bea678aef6c30ad7a087bf76b388e9211171d56622de898f70e4db55b1f4f6873b38ad23679212d10b2d2efd079be90026ca7a4ddc9aa9939066d7d75846df4d5b9df081b9d9e516575c7b189a055f8ce988ecb6327495ee098416162735176765be81406dc72de17841770684c4b44b36c3fe10e03fe4dc94e2828f2c7bda22b6ecdfb263c88524f53b951104b336ce1ccd7f4c63705967374f3c62ef9d4793c50bd028bda0f7c06d450106f3b11699d7ad9e2717464c464e821f495b685677cf0c779f3d9b0ed27873ed1c0051c0ad12ac3b5a75c17ef9aa3eb62c40b1cc92b3b6dc33a64e0f1ca0ca2f37ca5a93078dac4114dea70ad31727b6327e6f3fb2c0bc0ad19a0640efcfed9835d0b7f1e9294aaba739e3053384ff6a74e1ce4fbe1b5a4e4aaff0af163e5c647c763a77d06781413749b18eb57233d36aa50a640ab95ff3dbca3621520a0ce06402d10f24efe54a7fa7a05afd7a8085a3fbf06314339bce83c9b178347d0e369bd84fe2592e909b105d45c2b0988f9a8f693cb4cdd06cc8e33a1640a29774367941eba86e466efef98df45497d9ebb925969b9ec43f513b6528adc2b1350c643972900d075d2b94b32d4230ce0ab2f3f9f878c936b79ba8c2f1777b40e62f9cf30ca3c692d37b5ee50997b6bd97648e08c3ebca68a12da7c676d78a4873053022dd9806debf776ffdb341aaf46d2e4e56e48852e36988227caf0f1e50ac95ddc4ab7ba8850e88275770cf3966011e39ab7fb0e96cad2760c0e407de65eb60e471aae89a5c068db6619c91cb588dac5dfe3f3be60d0449cf1f002ad008c45c825559507bfd6e3e74c3de18f28ab1ae92c9ebdeecad4b1ed82bd201b33611a9dc5ca4f8b28020b879833416508509dbb2a69d7291acba96b887767cc0123e496ee5f35bd90e56ccc096fdba9a41194e4b24d7a189db5a3f79c374b2dbedba4798f5defad9651c569611c057e01f20e432fc4e64f8a1724786a626ca30a303ee7e1ae8f4e630a96ed991047532c42143f063c82de6dc84a7b181852186feb8de19b6cea2e2104500810c200bc2671f316d8013ba26444398be1b1045f88368e60a595e6323d455618fe0f5b7b4f12007d0ae34ca44e5f234196f4c78d1f649184f31e27b840c39f400529c17872fdc5edcdfe6ccf8d1ec4d5b1242ac4569b9058111691c86e46b6de2a647bf7f15efd06f3186e61e3002aef646883f799d0c6ff2177184ff148b7aefad0f9c36662c6e0f5e174112a424235ec960efaf1e9e0e01db930259f238188e90ac0c5c5ea85a0ca349d885c39f643693a1916a287509540d6083188e169c6ddfbb2b7b1e17bd6bc8a06f32feb2118ef4df0c1dee53224c4f18498d629a92d098c9e6454d59eee7f405201e37a22d2938b0a08b3deca688a8b7edae1113ea4f5112cc040e76f954d8c0e1d654a484cae14dda8b4a31e50d52ddab5a107fcd09ac2292c4a192879ae7db8975b131408079cfe17921e85c05e92cbed36e99966688851950620d32f3dfaa3fb16961d9135a9919553f70f909bc67b01341749365dab254d90c3403aece61c7a5fc26ef9bcf03752ff4196281e4b84a623f6fde242e4b24a623aa3bdb80ad0066c506e7c481e15668174693b56845224ddf0e11917680c9b063514cbbbb18997be024824d6e87351d481f89cb66610634921c61f3d938494a0952921719f4f17c0e4746f11cd70008f13a84738a7d91df40a738e294ebca49c6ef14ac7c3c14d452fd78a561f02161a5c2067dc7ce91a88d9a0560f396e173d80277496decfc99605248d7f684e4ecc02e695a1ee9f867ae3249b2037af55908ac1e6988c06c97a1503f047bc0335f4301d589f6279470e9cf88ecbff15369b99403d7decad6a037c4e10a9c6ddf3ebb3fdd7f4413713d3e0d25ee07cba06fd7deee00ffc7cf6bab780a5f95cce578b0ec966f97e0d18ad86748f61a4b1dfc1f67468586864affec7e1f3420dd4c208713b37a73f1d83df9bd0ab679ee10afbd0987caed8ba01baf69a4d1933dc3f8cbc8045f8c06c942df8d8bf52805dd212ac0672e9f56b81c3c7c7e10fe680c750ee687078f5a89504eb52558e6dfda91939a45f6e1f5197217e31686bd8dc2241b9b01f23302ff71fe889e380d74d86da80175680d5b9d112faaa6d60c77ec6c7425d0885790ba3df002a8b7b52047ee20decf6fa88b9680e741adb43706218c495b6e7d0eb40185c489d96e1066903b2967ff9f1bea300e6763cc101065ddf260c57e97303d4d19f51512d1b0184e9b66e414aade722b92622e099a76a4da1361155b7aa325c5765b4e885d8b4d0058857efa8305c0a77a8f1b8af7c9e7e0f84ff8e2ab1a62ae655d209527c87752585ac1a1ac28cbe4e40d2033b58efaed0762ee84616759f02513daf9374364e1317870d20321b56cd8a22040363d0711bcf9947b885e1591a917da7805deab3d93a4a2536c91df919fba2a12c2a170daf1acf910c45a3c8e790dad672681e38aef2e5e7d969fe3ca7bdcf75ab91405fd419c123a9487f291e0f8114c7c2c6d42c58c93c7fdb0939e20073d8996aa14d6a68ea618e38ac2369e8ac3e9978110c7858d16075e1a8a2b535217bb6c32d4dec1403e67b9e606e0add511169fa8b9fe8b062e6a93c61986a2265d5ce905ab1be9533279f7e2807e2824d008949fc7b11dc27481ce57186d8a656cd587e2e19208e1141385747f3912f768d48bef4f1ecc9a6a0218408611dfcc0433fa8e4f6c8bf635bbb2bde638803cf523277439a7d54e65ca401cc043b43738b5ffab3fef3337404ee9023f0339d1479cf8488ab3d36c36f3efb7015269c35570f55cf143e0bd954fd492702cedaa15aa03b16829a61fb1a98d33386f42e09449e4deca7a9177969382ec4fc02e9bf0564f57fd536500adc6d6a7fcf7b2c8dbd6e442fb330510e03afd08ff7316bfa1b95e2a2bb843860c3c9f75f9db1e447e59d5f8a8d9f8771b92c2e554c2d4fbdb0aafccac96eae91a2e7e60bcc71ddb2a0fcedac6f92a58f95eeabdebaf7b25a5c3af250ca5ffdf11d01ba95da320fe53c462ce242be49b73e28fd6bcc11ad0341151f354cbd595a174b7513986e40f6bc4a3a1bf781e80db1bcd1fe692dd97ed8cd6af64da78ec6fdd8df7700c8f35df0d437173fd723b4fe29509f8334e550e466a7bc7bc73fb89b499e3628cedfe43fddee4484e1361a1f0c5efe2c4a410fc4a3cf0d40e3df0dde26eb1f286bc3738a660954c463b2271f9c5662f6cc93256a2c9819bd1dfeec67234bc9c7e490dcc9379b92f868442f45c95cd847d040303167a5569fafd713fdcf898ccc55a4d0df2a1dd204727da57d378a5630a50f30457b07e1e5600dc0d566a3efa2242d3b258f2358ddcabbf18f3f3d1526de1017707e892681bc7f8111ce752fa95f0575ae287f96d4441b40b28d43cf0d73f86fa96ce22696ec847434835276296e83f8305920d87d27738f012089c9e32fb687068e16b0d3f3418ea9017be593c439596a2e8454cfe8722305ac82108b763810e3af72f1ab0fbde1aba6e343875d0d97970c335ae0fca2c9dcaeaac905d3a694033157f6cf5949a0ed331782b65d8957fe466e777f8f77150e821cc0bb9cca83f7745444255f516c29ef920429e59fd7e489edd926da2c574bd4121ab621da3e2678289604df113469f4d0fe1ee6d20ee1ec680e268f7472a35d5e89f9585df4734de5a3fec6ee8df9589c5dbd1ea2b4c0cbb6d1c47c14a165d95cbfe37df7cf189112524e08accc84e671d91a8e01728665af17525700e6239964708878d5e992f497bf769ae7974638d8afdb9c87d08de55cf17acd2c351e3c0bdf1846bacf551c988f5272f958785c8d27516336edf68f9b77e472576aa7e992549fbb2d6c35019e1fd9b1bc94e707fbffaf3ae09c6f67a62e0493f5b870f404c3844bab8539eba0dd87c7a28ceadaf803182e4071baf78de08b1308a1e05b03978cd87043c921188320685c87540b54c3f05f59fec5cc9b99cd50ba7be3654c44bc499434cd6a4303356616b72ef7aa22632b89f8bfa6dfdd6b7cf5996c2234f5b8074d75cb979a721787c5925617f9f3c923e390a685cfced021df64b57912a8d25ba776408c910cabad470fd61fae00116aa89d8239fb43e762a04da771d6733747551252a4aa236dc9f01e858837088eef1e08c7705c5ad761d2f9a8a56286aa2bc72ed1ed2dce4d492ec12f1d23c53eb5956cf342a72bae7c99cdcfdd6102d60d80a38940abdcf055244419f870da8eca1ea11832209bedad4ef18c89d16f63da7cce9bcc1a1a37d074b59da7513c76ec0ade0cc492f79822eff6fcffc5670feadeaa11714f6b8eebc134a50de0d40682652b420552fbc9390275977cd6bb9c320709a75397cb571e9efaba3b71cde898819c5db316bd92c80a40cec909ec7ea90ea159d2bead911c17366094e54097f43dda19bc9e74b8236651d1af2497aab72f9636d5922bdad5d6f242bb94f006a476ca0f8f993865066da6902b351a94703f94aa4392d44ab8d3562179216f3540e40f65666fb008bd6f8a514de54cb5f14d73d26f3363c7cd53a94e020ab0777e7d0014508985b90f6c01ba06e69831cb983ac162ac47b68c68dd2d4e7fbb88f3455fc6ce098b6720d60cfe2334abd41d4bca8f75384b939eac3aaacbbccb3bc95d7bd7ab454a4292926c0fe785b689bd6cd2eefa4322773e1ae5ba9b052e7b3a0f48bb4ebaf49c0b8c983601d9165b49d714be2f2d98510f6b449e3659280e62e4982af3367d2ff30e8436e7e3300b3a3f4b425f9500406577876e6930f6d18c726b9d8f8d22d581f8a3813376b5123edd772b14e39f9714c86af11ad7bff210b4d239a98b8659fd9038c5a074bc8108ab15426e49a7954a49ff86c74ec6340d3060477a3993cc893dc0ea4a762fd00842dc1af42aeb0b4359f21668547f75b0f5bc880ea5272e4e6a79b590c728451a685e0aea60eadf28c804b1e891643a966d2549d198ad31f509552d781310961760b3c0583aa39499695d29e27505818cef43edced1046bb622ef682bb1be7dcfac4a4d7f3c933e4f3d5c297edff7219bf0d6b1ce6b6c94aa067336e6c1059f7d0937e0a35bf2b211595bfbb42989feba63890a43f64a56853f6ed811e35ccd12fb5e1b289b2e1d111c6a3a5efa1be1f80068b4bd2a8f0a5a1c1a7621bcd4183a50a51caed22a8fc02f8a42f1bae331ac535ab13f05086efce578bb085f3d978a3b39930b168be3418fcf119080d098159d3394846ad701187b45c0130c2d47e1f0609e57e5f681b044a2eba5f043bfc0ff53287eec3d86ff985c087d1de02d03f0a5abc3eeea31079fb1422daf4c1d3f756a2baff92da74d97d313ca2ac369ceba3690f181094bfe64b36202c9cec77c696286b9859aba685de23801a8f6054a9856c7ccf0098d6f1ffe67872e1d9f2e18ab64aa5e15d8d5696ac7cae29e80a1bb254a9aa164cfbd91c54908c6312af94fe94d0597cb9d241b1cff7c751727904da078e611b59c27ecf3cb8ff898e2dc83b0af85e9ce4de3ccb858618825e0117e58248bf0611f444a829da9ddd82788ca3e5cb35628a19b9d7a811338aa65cc1c8390db51fb96fddaf04f8cca0265c0cf02ee1ca5a2d87ea77824bfbf7e88ea89ca7ade1dd7abb980da469f39fd830e6ae886d1cff107b380023d81b10becb6a5b2ff71e6ac409f8c320e2fc4f3b10bf59315dd8f1b7d1c4a0cd1422da9cfe00d3d0497307c79f7ab812c4c8ae21e8a06b499f6da30430813c6c9cf1a44b59a3ce2ffb06cb70fec10e3442d9c1d0d09a6a715b5184d0dbafb945223756aac59e45951612bd8f0b7a50e583e72a0b1c6d93df7ee466cec762ccd7df81d79709c099c03e5a6ee2b8ae7dfe059ef8447b424f3f7224b983208efea19d7121e89f6d078448c9d54d289ff297619b169369507ff6180dbc1af78930110446d243b90644849950b619b029e55035348b89f52fa5f63fca68e3bef7e9dead2d364e75c185ca9cf137ff8d3b4d773a913a91ae5a906844513235e547dd45aa1ef7721c3a83df591061e589b8721675e199214595d26ff79f8ef296000c68ac7d76884655a9765555762a0e6e5089db167a3a08ba68998142f2cd29d4c20130fabc71be887a27a9d46278187b887107e7a2c3d614c34b01dfd67a93bf45cf0dd25fc96375e8932e69a36ffc5c34123aa4b707a5033c615cec4a4baf4da71d495dfb99fddf5f65c65feac67a757f650cd1c54bc0c2cbaf38c5aa04c5a18adc04d8f017eccd45d9534e7acfacf6f71ddca1f12bd0ce1f066263e12a8142a67bd23e17d5241fc59e6995bf85f987d8e7b4079760e30b0c7b153d7f7680de72497a7f8df07458d89e4cc7a5a0267f4e4354205c81a5a89a91aa3706413c5e37e12d404f386a2f6a23146238ba02e75f03daa11327975ce0802996a08c2a970ba3d149e01b6613ccae611d232a8d984bf94c6d81bf4500dece3270f87b44e2b27cdd525886e7f536ad1b3bb3569c4ebca0978c886551050d69adf0177e55d45cb5be87c99211a54d6657f221babe56f6d8dca0a4e9c7b299d64beb2d7833db49193868c6549a0734d487a654ff426248445d1439389303ad72b62504742d9974770696bb4cc68601b05f14b5e6ce227cedc1b6fe9b5908a2702fcc18d5e19c0b09d2cb0d7fb0cff4d8df3cfd0b0d2f1dbfab2d0ebcc763857a81327bc5f6c017a03e02821cb983787f7f724baa992eb9befb445d57b9dfde7f1015d055970cc007f18e9fb2eec33787e3fa487d51ce8a939008ad23cacd04431896fdc750e91a7217ceb51022c5f9fad59a921829d74a6d957b877234be0e81f984a370b45e7b2cc7edae2990f94728164c718547a1f4c90903a93a5383b6ec4e943fe9073a11d3b90b7752f1d55b3a09bc487d56ecf29de2562b09047cb3660c4c431654fa9b145f4b7916483a225fe0598ec13f454d850bebcbfeb8017c3eed6faead293c4f7c7c5adc440b0d8712291b92f3a572d425973fb2b1bc933b806ecfeb410b328099eca733eaeb1e9a9b2c2b9a5e71fd462aa655f5f39872ea418eff537184e5fdb1c98b9e38015ebb9db844ae00f0ae0ed6d31deab545ef3f5cc4d6be6c9c1b6cf755a7693ed56105afd775dd32b3f8a9da493ca6be3c4fe4af2f0457b42a02ee7d8389b1083dcf648bfed419a087d7ca3b52bdb0386d14bde16c2241bb29e6103015c424d52932a39b3461e7218e39fcac86e8c00585a5766ee3978ef94a3095368b08b6be4dfd4077b8b86fa084d843d60fe246ed80314a93a7068ffa1c1ae7877cf7ae53a6cd0be0295c481b646feefe58100d0ba63f1bce06213488d39ecc8601a7a03e1cdbede299115602f1879028f7a23d304c8a2dd00d849cdbe529c0fea7527dfacaccb7f6f33e512695a66f4902e74315b379890f0d84ce56f97bb93ea73ad1ff10f248b77f9afbc2c29e557c85b7aca606da8e4342f9151311fffb521dfc39212a8661da2f2436fc3d78e7d72a2ab8f5402fa6f0778ccae932f1f9d9c7f1335e5f77e20795788f07ac98583e150a2d246a788c3657b8e529750e80a7b9409f456cdd6b6e02013b166115805b465ae368eddc5e64f1be746706ff7ab7a39fd32cbb36b78e63dc2f962669ec8d33f9f534d89f386717c8e1144b604cfac02fa1457e0f45b637efa851767723e2a7016394460bb39bde47f789765bbe6014d8028836a6db73f20226a0e3e8291f49f03a79fead15f1095c82bb7ace0c6b4f6828239d1f3536dc41ca0c50579bea05a5eeda60a03c6a3b06d4f2ee59dd500ff921acf1b75d53dc3e086eed0573479f881ec8625e3ef1fa11a2c99112f3592e14c6ef8193408ce7ab4dcf3fdd7ea75356671a8a7c6632ee76eb0dddca258f9b5f3ef13137817b267db96a5b93956eb290866eefb8ca54a6fa7915aab33b1290d53af4d9e6158daf4679d73de9e172e5a3978149898e8dc1a5786f913dd07d9052e10d2feb738d73beca989e5b3475faa2fbbe82a20dcc84c63d13c902468dec516930d29af4cd2d818c7b995e252f44389cc6f31ebcb2db93aa54e7a76209058014ae8ec4a46542352ce99ab89d0d102afae87d5251caf83c16b5e573747d8fd22a2ede277e54e0b6fb1f1863a9f6395335c904004377bdd33030bc1c433a599c7c07f3ef08fb6a9e05a7f626b40cd534be8c3f452aa4bd78407861246d40b8e604896634bc6cd7d6ddf5195ef96d075587ffd7eb3558eacd8335b6e361764a151198013f82fa159d62e7fea45dc7b8583202163e44ad187d63f88d2306a92cd85b4a7ec4224919117752ebb12f4ec27b1b1ded9e294654a620d06dde0b530d9798d35fac0b5768e3ba9e9f7c6d4ddfdfaaa858dee1b83f9cbe52ae03917fdfa8cce36ac08a2c2477937c666c4620f4b2a15144df5f2ecfe944113d59187f244d512e3534cd6c6f03f4366a1350e2b6e072a23164af1bf171122e51b5d88815bb3f5f59dd50d2abe3293fc3a9284da9edbef276033c5a579b48246b3eb9c74c7b367dbb2ae04a445b69433d2d93bee32374b2d422f047422e99fd135370f26619870de4ff4bda407980e1b06949aaff3222b26b8c3b0152d6badf2677aa34d09088cf2760d5349f9a2ad52e4f9fbc6811213a37856901dadfd2d76aa9389e808087d3f4c6c32ba5510c118c1792ced9dad1622163232831f74ec24fb491f5c15327473980cafddf629544a793b707653cb5410d2ecbdfbd3997936b0bf5b2baf78572d825f9ba9514f15a09dc8b45d5a912faf50d3ba64a0aae5164dd82f3931974bacd7441e81488888dc479d3fd6a913652113ed40af47fb0b01840eccc0b148408c422ca1080b6e17d91d7d2b5a38137722d986cacef250f7c3ee28b2c107a418516feb65dba03c9ef55df91448189d2d26d8386d3b47fc4e44ff173f64d7586dc513061b2c94f61a28b8bad7a9845cf1c3a3c3399da4b3c0acf6452b6d12829e1d7036e439e812014facfcbbbadfd384e55d49268bd8573af5fc432047d9b9cc58d585fa95d38b3ae186a9958a8838dfb88281a7eae4e3738c6ffe9a0b8e02871b1a828a145b10f0ded4493ff117e68530679a73ac25fca2bd046c662a7c3132eff74ba0d8ec2255e6944927010292dfc7b738f974cd406b8ef60b6660ea3cca37c01f734bf933917ec41ef9f1e4affecf45511a096e98acae23e98ef1fad305e4cd69126abbc8d336e82ff6f0f77020cf614da0de6e3ad417ee263950c43d4ee14923734956ae54e3ab87edc81f6dbcd4daf305ef3a17b09f2bab481deff484eab613bd35c1ffaf8e95d3c23f9f0b5b72010306ffe553c025d7717d51babcd146b58f67c2a9b4bfc89ac98ba60e0c8e140b67b9768289271673269c3f26c3a6e248d5455aacffcf4f31f09ffc5d5c4aaa870d9e9bf28e7815168b0cb36b7532ada9eeacbea7a5e0350f5e6647439d617651949dfb398399cc7b3310db77a66e3a76c1e8458f3495327aac30fadf29821b4699abf2366515451bcbfbe931ad0c1ceea30fed7928e86649eb6c32d000156888ac870f8530b16c87a9ef4a074e11cfb06c28a72f8d670fcaaffba17bda3e1fde1629d3395fe737ed3e3847e69e163bfecc649964dedc51a690d73372931caf4fe7400459e816406639a023991cada3f9a4bfa589f06471b12ce9743630e411895c525dd57aafb0ab0fa3dd81a1a454559c0397dad93927fbcc5f018859a4cf8159254e641a79c34953c7c190c0e78c7fc24286ff49b5e66a179e6a1ae0b5d31ebb2e3d85bc03e9943eeb9eae73a07c0040604a3435fe80dee4263f5cfddd14a845ef2e14a323b5aef851665edac7e51b06d61c51b84f8408282d5db8c1867b13573ebe01e71670562aba707116ebf6391bd416d12457476319c785cc5f9dff515f50d91ef6bb4077e00d5eb0172e40ee2fdfd859a3b1f13404c52adab33da43891145f1d0c5f2b10d76e7ebd3f44710641da9a22cfea393720c46667f8c012be60645e9f5069ce2d45d999a2eaa5393509f4897deb34bc79b0772535a5206dde6b0596bec71c17cc03c77ae2c5901fe5c03dabe4fb2fa0edbe57062eee1b980754e0c0f1eabad72a6032ccce49492504f360741df46abb22f18fc3a98f02a9e7b57dedbf205257e2e8637244d5285a376fcee4edb1ab484883ec850a6333e9a68c006900558d26eb3782ad58d55fe8afe429feeb7da3ab210713ff46fb10c75404019989d6d51bf86fc8ca695db39244a2bab9679b91b4986d62fe0faf3c5c527952f931d76d72f2078b495b6bf5f91020e5d97d6fbd3b1f049f2df8f95553d0db6f860aa103045a9538da69165a532078167a319a50f04816d9c64ad08993a6aeb5a87fe0de93346c204700be0329aa6a0a85232da1f161e2f059111709b0acc0cc70261af5ef2a8f2e2a40bad42e9683d1d91f043038bf158b69849ecf64050cd1219eb65a49ad52004c003131a6ce439203331d9e7267d334386b493323fa39edb9b8a5b998464f55d0034a5afa005fd548df37f6fde3d9cf796a17c2a4dc3519bcd5a51a454afcd691c31873b3629f2144a6fad692ab51397771ac04198c92650a02a93eca5a193c3a6d115abc02582e8000a9ea4952f1583a42a554d81de55184c5520035c07e8f68ea9d610080c67317c0f08318e754c106dbc249eb082f568c72669e5235a8571dec89fd8730012275c3668a3ec49a3fbba1825be3261ef51cb417c1113bdfcbf2819b50bb59a24df0067cab4d196896d3ac1cc42aa878aaaaec4ec2a1b4851e8f8de03b3a1ade6fe3193052fcb28949dffc086961bb2443f9b7054986d200e9b5a6977fcc1c3d83d36c64b05307cfed1add45b8a37dd0619e455f32b8a65b7abea5ebb93bfa0a4bc86b443fd75034cf0933d7cff79fa35e430c4043ec46fb495d2fd4048727cf9dcec2d6d26746e055e61471eb2c247ece3502b365071b59caa201f8de702e57744d3b339378a8e03d28c6658e6fda95948a17798e46234db55c67ee47be9d5cb0b500624f6512c6f07b15005b30d42b5784c4a433eafffc53bc0a365c4e95bd250b1433634621a62799c0aa282799e443b76fea718f056dde9bc675145c16cd1a46f2d09a3ea0f85b0c618c6b1657bf68be395a132f3589092e621105a6cdcf2c7185e7c749f020c792ac154776a872d5f7fd07b5b07362b7fb31696db57d28537fec7677308390bebf1afaf45a81998e39113c99dce608f75a2d194c2feeb0a17eca79c76f837ea4e3747a7d83097e9fad9aeeed6fce7f8c347aa236a9e46f091a31c7818c2b673683ebd8f2660d337164a787f8b30425310825bc8cac537a30297cf9e19288f4d1a4364eafebf03fc9243cafb6b82a723c176a5b8cf6fdec836141fcce2b026b7aadd8a73a38a8f729be5dd85b275b6c9ae49db02e40a36b67dfb054c63a4ef4f8bc2a5adbe4fd0dc8817fa2cf2a27d2675b662378b7c7382c79d220acb698014921acecfc7f510e75bc3efabe8b41fec5262f5cb5968e1384884177341cb0bb77ae87d1ebf5d927f4d4a38b2ab83ee72908c975c5730ee90730e39c24e99e813610928dc543c52e3a6a670caa74d9732825fb134ddf92d2acb66d520677e83edbb76a370d5767467bd3ff7c665ef8fb643379d0346bbdfc6b1606932ba9a0009097cfdef86b2be5a4b36316c7e3b51dcf6263988fec0087505bb11e293701464c29f73a0ef4fd9580f974b932286264fbb18b4c7423544e648f8fdada26fdc0f44132ce8d41942e91dd1bbbb1d92098af8cdcde8c6043589cb0a3bf6fa0711e097bd86efd95026c8845acc0a4f387eeea0e7f181c9d25d289b9b25ddba494a1b82979d3f1eddb91eefd707d0fb1fedfaf80c36e4992b16dea9156a432cf39aded7b2c3e9284476e79ffc812458a30393ac2d2140214690ccb353497bc0690ee13003b2e83ae6ab72eb9f2dd551f5281d4f317967000f1485125165ac11e3e8e5e86f6e9dec8ff1ee2d422a55ffe223b5212e121ddc01490078aa62c11e4e15218d42ca913570c90f40766180a5d4c55b8df9d68247207ebe657ba439e62803d68381328333e69ea51e888da98486303111084184143ca79f2b049d3b0a7f51877c3f1497ff6571861ef6d7d97e986aa517f65ecaf04f9b3ce887e99664596ad59636af85bf2adf064553e4ec92c6b2cc560093ee24f650155bdb25ae4086e63117c6d8eb48855ad0c5df7083a29a88c5884b0f0f73fe927be66fd2c2bd1b07dccb416d0f64ae33246d1915ad97c20919fa7f48223bd51f7d99d728879288faa7f31a8d25f5c34c1abf7962dddd0df425e7a161c6f880ceab78cdc3afe2aa1ccdb0d7ede3113e43467075837e3c099dfe2fb02415c164f288e4e6d4004e5a5d49df863384f15830df7c2f2a91ec8fd16ee3a712f264235023260139ddb0b5a71c895bf6bf4840156af3b93375694a9478d9617141522d6565e70375d427f08d34d414f6bd5ad73cdc650e024832ff14468cedc40f3d27c39348487214e41b27a0b4e3bd7d288a0d36fd4ee37b8928bc21f1fe67f4cd3f45f68ba3c1c99a0145f3b6d82d766e47740a5b001672dc948ddcc5e7bf1aa736e649ea07215b956cc0802060ba0d7a11f7e64b8be6f6af1e698e13d92ec3d412e457bba947855074a7038ce7ab4ffeaaa9aef0a6d08286bf38ea0e99ec33c4e10a9cd5e6c0a4a10383e1375bbbe86a1b7f89ff68c1105c437e301344af8fa8316fb4123be43253c7b8a9a659a4e3b1441632a056abafd921c6ee86c58434b1b72e710795a0491fbcb9659b2893da318e7594fe1a315fb9848eb922e9df132d7d49fcafb20a17f6af111f1d21ddbd5ff24284c49f27acf4799df6485d19ef693072fc1e2231bf78d5abac124a3ff3b24255af6a9b27b59f10fe7c5afd4efaae19751abfea9102052500b092250f8c08cbb58e2bfed362223712bed68e536938cd051c7f818e7af90e2ce04080f9460fbd1912798951fdead2d6e7c43a51b74170f163435fc0763ec1e94ecc93fe60fb599e180860f9f63451eff2d5c1c8509f98edfa64aad163c470682cbabbc56490ac321e756b125cf744695bf14cafb9fe1b03715ba50505257b0452c8bb127b58b67987a4592f037cfe1fd3104ab05ff60d9e26b3d1962715e12e29494eb27a8105f5af85d3fd5f1611445201ed3f9d5f2c27a3b8795193a95c625ac37a51ba21593efa01322e1caf7fdf969213d2d65d37b03fe4caa1ed77a1377a72ff047830a483c380f41f2a26c4020d5c2d62f7fc781746c37817a8332587ffe19fec75402d66c45277c74cc3dc3f6aa9e45e29cd92c764b986580a93121d87a4089bc798aeeb24a9eaa81dedef5969177f6425a7aacc8422c36dd515473a3372511c27d062ca39e4bc9185ae8c06c787f9929d7cca4f6a54b5d1ed9af96492f34b20a68597611d4cf601aff8514ee06c200369d4185e6e5447ac20d5f332dddb70d7df5d185cfe88acecc22f54a06728f8e278293909f3443219bd3cb0f2a0c5d77d13b282391a119e5b7bf497f5eb464c72c2cc880549e69e73063b29a75ceea5b3346b56e4b97ccf3091a50fb5ddc5591d4096bf383c19a3f79cbf7e1083d3525784f2980f6031c309dd895d43695c9b546cc524fa230577946419228339332b60781b2e1a656f69a06a13e9ebeb24a20586a7bf2e68c2f1727769c0ae4334b0e171bedd19e9c23907de90299341bcf4fb43e179f6661c8f191308af768e71833dd7fc12eacdc8cedcf9e428bdd7f5eb187a27cd22f52553f9a72f4404b43483d57c9868ac413ed35a6e1ab53e6ae4021d38386e95ca47de3c1f6890c7b6d8bb3a8c220528cd8a7eef5cbea0fbc9abb96890f8edfa13c16423bd507695e914583d068a760c66c84a3fee348874bc0e27aa23227e13afec9726280164a7133c1a1d694e73f62fd09ee0760446116b44086c73e02f5d2dd8124bc527d6bf779793146794a92648b8f343b7538369931afcbce0b5e34c9eb4d733fb087efd12598e2b70eca971b15a44383842b9c42f4ffd2c58245cc2cca41b05a5498d8007eba6280477be0e670004fb274c327273b93121f5af39ce0d1ed39967e69b50b20cf54f36d2335bae19c4cf7d7041aa93fccc9f52c9ddfde5cbb6d577e0e23c641c74ac6c4aa530211e631a97f3f7363387466e4325e07e886d1d27f5570cf239e3e0ecd6d029e2d6825150da62cf74065dbcec613272ab08dda2d7d8c7395677634da7abc02afc42aa288ff52a5e036fa04460842351c68435187c74f3e7921c8f5177fb6d79f9d31dfa704d361deb35be8e19c34f809cd5a1fffd9ba68debd3dab4eeadc6c693767c1fa3cc82db0b630d22cd3b57eee93c2d2524d0083eeea13dd0b6f83b1511bb98d015779f6481c751d82a2bf9fd3fb32f24cd34c33eb7c87ee39e4ab2a6b873a660a08bca7303fc53f520c72005c9b41f6697dd3f34663a647dcdb49567c6a70238ba209f1a05268dc0ccec2d509b33e4b47133e4186de9de8bebeb9e8dcb8cf98b6bc826f631371cffa011f435ece8b3c4d2bf2f2ee8cd8147c3a3c653d61275004b9574a9935fedb8f93862df2ed5a0ca87c2a0cf5699be3026829722b6b6975f8c37f7aa29c5eb2bb03a5767b4b4e8623f7cd0f601a284f21e818ea5a67ac5b7f08d7e3846779602276f3541d82e6c125fc0bc64b9c4dd5c09c40372f864e63eaa14ca109574f46896e99df53ad4cb9bea7a4ccc265e3405ae6b3b54f37b38d97811c484a15253a90bbee1e2c107247d7f18bc03af5813752cdba6a38a6af479b3c6ff26550d89307c6affd723e56e44d4e06d3fc3c0675f5d3b2efe1a8d3a1a91a4f4332d8663672b285709c448725188025d7088a97a0baefb36e43c39a9ae4e5cf3b3d71370bf46f3a35dd959885573cbbaa5f5e4bb7d9581733179e65314af269701f36452c54527772429fe1f643fcf498fe1b032cb307c68d0b3c8bff2b04b8db411880f02f16fd287b6ae8fecec7b3a4326cd6df49533e985e6ac7e66c384d846c92dd353148eed7ca65b9b22b3e0957320c995f9a742def59e6f59760d9a00cd70373cad8975d52668f25b83a7587e71243d21568f7e88c5da3b04aa50aa7bdfaac4ac75b158c17d1e9837a5e477d48fa44096cf5fa33b28b80b41fa331ca713a83c79b88cb0cd2bf84bcf889c3f21ff9d2691035015e803469a7107a54b0e486d34cb8d896e132c2d5d5ea10bdd20ba710260453c3bf95df02b4e87a5adf95ff31bb3e2d4ee3112c6405a8a2405f5d56b76e34e2b663fc978545a1ffd8fcce7bdce350bbab90f7284d19a0630e30e4b89ed344129eae8e3589833c9a6dee6136192e8b0e895c516f9c17c5eaabecdb737c897ea22215675f8ce4e464b66998bebdd4452b9b02c76002f0cc267cefeb9965038ed50e7f82ac22cf68753e7015bbc66c4eab85fe8812876b100ff20257561d70eeb398e8212fa719cd5e68d849bf5e7cbc090f95e3796edf84107d433cef5156cb66e6a4c4533c4640e3a38221d58307dd8196b2d8867bdf97608284711594cd52dd567119deff6fcc128d396af7332161472799629e7b6d98cfe400f0d4de2edc31671f3c73593485613aaa6850f023a211a4df08fdbc6b676a340a47f7df7cc34e1f4c250e35bf14527ec7ec859cb3e61104916ee339ef43db2839a8bc54906e7a715d9d57031dc27c8980c48f004f42c9f7f743d8756a12040412af7380dc82acc1046b4a1bee868d6603a9bab18479fc3391a0002b6e210f96431700c6670267af8a2d619c2dfb26d970e9f1eaf33fffeee0538bdaab15345f6673d84fbf5e69d5d9c0cc8e66b50763c8366e51388a5df3af0f351ca7fe007bb5947385eb4c557f5b7dc9bce8e2b5c910659bc75ada0bad2f3e7aa430237d3ca5c521b550ba44c3ee0593f32db45f13d08e014536ddc1a34dec0269e0cc58cf5310aca0ff729fc22c234bccd6d2820be819ca6028115b60732f51dc5ffccca8669b5c526496e94e6ee7b3a42a49d17a7760f3816dac0c58c21488b19499758d11496be2cbbc9aa92ba10a7bb30dbb0517af1892768c95d84bf7e0329a56a9da977fd4b1c2073eb221713e25f62ed83af4760fed5fe88baba01aa78bf26023325eb2cb830ceae8c702e831cbc82159e6584b432b81d5ee4bf32ac692b71fd53658f67066be564662f87a557d70583d680830e9a0364c2f50613403b5e53151ad0576735608c376613104ff2d2124281a91f793b32965db6608ed2307c577b43afac73be60dd265dcbe5ce3d6911fe52804fdfe9820e95c5230954c508dd50b91717bda72d83137272e44f2a0fc723dde7bb34de5f0b3c1e15366bff675271f42fe5c4301be52030ce8615ffe15efb37ef1f26427063bfd2d8fd540172051b1faa8736f441c1ecf3614580bcce21832a65128c88461625f8762c97b52bc269f17576ddf0b5c906ae895355337d2888810655a0396db8b5f50e32bb891a0595a66208676eb4680e6c76f1a12326a770f283e3e12ffb7c9910257f9bd52ca4c62a976c64fca27cdfd6d821b54e51ab3bc3de93f7f41a4e44fb6d11fe81a8528e29f9261e59a9fa7f553234e7938d71bafc1de8110d2a94928853e161a2d3841c0d09bcd57b1f4380dfc7539c1c87c6693c86312aa99e98d04b0aa28068b9d0fe049e7b00b1c313baac65b1cb7513e1a7d42fa6d4fbc6069b55a1dc6e3f49dd8b35cf97bec583e96945d7977d98c71885001af71807cf227c9e07c9a23b69727b82c2692aacd854b8dd6a2b7630efc634744ee9eaeede0aa459ffa4cd6cb539b5dbe1cad2fcf8bb38e2a7b8c44faa9347b2e5e1f37df0bbaf99e8d1befe646e60d54a2e3d67f23f3cea48fd52ead174e2402262a2a416d5c012958fa5588c76f9dfc7b1d7de3735bcf133d2c3c7297ab63d3ab08911d83d99a7a084dd8d6025edcf73af8a4137b45167cb2abd42738f9279c6d3b3b55dec101c7d22cdbea88dc78f64d0b6cf6c4b3c61437c517ce55c2b2571b8e5f8fdb9f9abfbe608308622a6648ce6dd9977feb66559d341ec2e3ff62723ec88381de43681bc3afe6d4d264800bf0a74426f77e2af07ef7ab39616603b1a696ff94666fa9c78b24f8ef3dd1d45591185a8f5dc6bfb0c32b2bd89986503661a0fd6243aa245fe0b2be3fcdc6f4a3c2e0a492be259fbdd2db90c7e87e0e24c251dacd88f356682c3933ba71ef1b0bbfec29102e54542ea95f8f8c50443ab666e6ff3ef223e9e52b631e907a171357641331f02ac8524e3c5beb90aa626dc95d3a08390d14d277a5028c779b38f461ae4acd18da9d9acd1f1a53781ea91a720895190260c25ad470f1b8533de1c5d334bec892fb26489070e0cc4658fe5799667e8a02bb8172cca37f5a70f0787907424e4e67cd538698221c525ccf5d00056e5bc82733c2170d0e1bb4d7c22da917e289b060f026732882181d98724334ce427132dbe10d08bc6b816bc69cb369cab8f9905cb2ab54236a3e7b414ca6ed38a911f0216bab22043d2cfe6a3668930c5d3dd99e87774f02f4e6465844998b8fa2590d82f9bfcfe826d9ca59d0ffbfbc180ea74b3735baebba2580f6701a26a271c58f8a5ccf0d282fc03eb0f4df226795600d27549c7e1f3e2cf08c93b3e5f0d1476fb45b6d6024b371948dceeefd9390028c6e8013fa0d24c50c59b204758059e64fac9a321035f4099aef8e9aefee0c95571c90d6e0589fb8da8dcb0618c910e7ea9e55d1d822d31357107feea9d5c876d46bda26107fa04f8c9eba9c2eaa6770a6ac3d6c99374941b6ed10876d515f010c82a2296865b2256d9b80e7ed4e65cb31c247b547771388311c0798039cd5ab7c0240d0a6e7101fb6ea418f7f04522f1c5d65beb7ba8d3ba072cc066ac61dcefb6c2854768b9c61eb2d7aea00b90de7ed8cd5ba818331310a1b2b6e1b62aa36010042c2c62d59aac8bf6d53c75878f9899f5ef10c261e4491f544964e187b82aadbc63a65908c29610bc5ac891be6325423ff439f25bf4e27bf1ef63a9390e3fff644b7db3cecfa2a4717b7fbf7a3dc0490002051c02a8f667883d6762af355f2c5e6d05fb60c9b9c625832a9839fe86233496c019c82d50ec1f3e2e85102325f6b84acae26f5983c5393c8f2f861c894808739c410033b4ea5d54c38429bba6244b2abdfa74ccd3b33dfb8b9b6906a61b009faded17d8c2a66f88a35a727093343c009cb89160c816d5c25a43a9da00528e6cf1991d6865aae468d9a3b578b192d3f5bb825558d9cfb0e1fc15004ff2d43ede52574a58ac36ba5d06ed0e06725cd99ed03003e63a892730e7f77d7b3da07d8264a0b01a55718d5858d5b8bc5021c6d6231cdfb6b9e848ac93709934124590c8b229202f524d37b74fab820c0ad59bbcc9b80a134fd68f5332f99f7ae0560f729c285483383ce4a81a53d930625570f50ab58a91bee6780a8dcbaa28b1767e80a10a52f69b299457df5eecacd23a366a64905f8c48cae4e63b37eab0cdd8d64207d81dec198a4bed71ba7499fd0c1bd3023edd524f9dd261b987039ad1abcec420f313f28a03156647dda8cec55a881ce7a4d66a0f2ed2eec2e4205bbf1aea2e09cbae81eef70ce9e4877d331cfc29c0561cc359a69402997d0d68db9137b3aeea5d19271e877dc2b7cf3a459fe0bf46600322e5adbc5188388ab39da37dc67a32825f4cb099c3c6da9c85a570e742e419afaafbff4e1e7c459d7fa8993e2d00037b2a23705912f27978980e653bc059e69257b3ef721a55807c6331ca64bd7e212c5e4f83935e2adb3fb6af8675e462aa1970c983bef6f0a0e0fc4f39dc9a050ba01b65c0ea8c512807708003df991d79a7f4b82913955f958468b7c669b02a1c2d164e9e140f780ca8562cc98391a710ad55291415d66c648cbdeb0f01c40778149fc95b838e5490062d0c3effbb8c54a61dc9ac6630c92c5eda31c516c02dbda46255fc964bdfec21a6c3ac89ba1f2621f787a497db62997d9fa58e9be377bb6b1b7cabe65538563d5d3ebaa3062dbfb38a65e681497509beeebfb0e57c2512e954ba54361d44d94e009b3bdc33453661b607ac4028a1787b90d1f785d38ad56fddb0c775280fd9bcb1d06e721da03e60452c40c1019cdac2dd43fa510130fd9b100047ee1329e305d6ded7627b091723f31fe1521a4d290a128ec8e35056011cbeff4aae3cdedda1f91160fa67dfcec05e0c5c152ab453b0ec3b3ffb3ca0ff1df165f49e8ef60136f2ec610ff0873a83085c4306885789bf451407b3d0d59720d58bf67cd8a11a577bd6ff3c70776864009255ed5977e564139f06befbad996ba8f65eff998628ce7e8ce206a0058a247b8160249968ad954005772844f664d3e30573c4ec2d1482a19f78ff1305f94d77850bf542b6c8951d23ee307a58fd814d48e98bbdf7c8192a89d44046a88e99e45568a90fd07f366876dab7c50023eaa6170a9cf98e134e403cfebb322cf9ab4bbcaee969bd033cfc513b6ee2c214a8a53fdc9e4fb82bd4d240c0b658dc2099fe3c2adc8ebb7cd3b70d25278f4478b627b298e9000bd748d6e6a6a4542fe4297d4cd4286bf8aceb23c3bc869c204760032633426ece235f7c434fe06f644c4cc757ce232f00d635c8c71d973da6b360723f92eeded639eb229b6b7f95093f0e1d88a31b6f28dd3e3e3e6d234ec3fd23037767aab3679fd290fbb65f05ce08cdb63c56fb5d9bf8e83b9292c24ebcf7465a5b457cca9abfa1d916f2fef957ac9c2081d05f705d9f1b854bec28abdf8f5a9181924ee0ee147af2e1bed70ad2f65c3943c66c114f9780785e7ac233f1d9a21e90b23f630fc8947f925123f4ac8a9d60a0dd7956e5359d0838bb9e480d79f44bea4072065f1f79625a29985c61c0642791af1d068a11418d519166bb21e5fbd836ebde42b862f49b2035f81896c541ffd0e9ac5bda5e11b1ff1638f58366958f09c4f296e6b8621e42eb5e462e0401da48a778b0a78cf6357b543fe8ab642479b236c5a03c56b40af8cfe754b13e574501507a4dcbcabd1fdcfcb6270a4e3d6904599afbf2914caf870544e871895f5d9b4b2673438a6f362eae60538d77794d080108518f91074c8e64ade8627e3e7db73e4d41a4d10ba1fa3a3c0e431e2ac4aeb4017a3f75e32ff3e453181e7e5017f4533e9a5be2d3879811dbdfbaa3294d8d51ca2250101f28d09a12d98bc6a8a3bc66c93719bcf75c9637dab06b29741fcb2c39ee1bba7022787db5bb1066777be065de971979e1e13b275206511f2ae0ad7b30c30fd9de11236d0f642da9d83f97e0ebf552af86e95360ad8f1c54959d0b4b9649409e38ac3e05116b757f7842bafb4b186183a019b9dace2575344d69b04e50ad735bbb1e0cb8b49bc235548bf247baf365dff45b16678087c8374bb7bd5ce7e7e2dc229aa000d9af39a7cad43097fa849acf63f58e50fda73c9b35228d6e42f1641916e6d4f18072b3fc8d1074fc1a8cd8f0900e32b5333d7f5a9433489618cdfb5a514c09fd2bf6190c0db9a5b81924b3ed5a5d1d220081fdae0ea65854d6d2a1f5c8747204498fdfc6b27de9add94a9045bfea97b59d01626f28f7e23be8d6fb920e0eba5a6f5435b109f530ea2d2cf2c359356b72f982c72e36c7b70c74200f74c0b94118229bae6a307c01cce59a2d596ce53bcefd2dc88a046ff9cdd81c6c890a17423a704bd3845de433e88e86c2c630e412dbafc696a1e0f72576b2b875c32458dd7e7273f5d4c745a933cc04443535690071c444c544cd44c38467a2676212d3acc21e13d5cb714c003bc3d88d3a49ea912c67f111472b2a266e8eaeff9cd56665ac3bfd2188ad44133e0df4827f1121c21726d21781a906c0a5b94b8c76b5cc4bd5e9080874741519e83aa75bbee893c73c0a53f3bf17e050f47435c600d9629a23a13c3401c1ae716d4ea3d0df0c0c95121d7793548d74b4e1e2afdeea5be740ba712beb312a381534d21a95d25dcfe99775078f3b0a7fb7c5877077e72a5c22920ef7209ebf7ca418bd29fa1dcaac789b2902f3334205c8556216a377d78d334014d64577058f2ff9fdb4cd2b923d8949a6c85d82d8757044e77b71a3cd44dac168d35740120566764295d3ea0385e0fba82183539ebbccfc1214e908e27e68af2fc64581b74dec218df24073368edb1cfe7fc86f73d35cf88c3ac7e0bf9f0c2786314dc285b1b3b05c0854697f7d6b719170ee33c05cb4286466f414cae7ee91f83bdda60b4fde67fed60939f7649b67e18ec03b8546cf517dee908fcbbbfa17dca7ff0f5453c4df4738f7bfae1f6d3ab4949f3775fb3b7fd1f764e3e0c48a4039d464dc115ff58b6aae95fb39f8be0e3988a5cda68608a7876850661b0be3cd337b32164516c54df69fb2119a1e5dd0132d87174c26fca9cf33c0467e913b0d30549d6b20ebe9455e6b646e3afdba12390a4fe4e793bd0b7d15f6952c3f417f3fd57fb2096f2db1335ed7075b903225bdadc7ab755a0d0ab480172c5c6cc2922bd6e0b71c7d14f87c3c73f2c3265d17febc02ddd88eb192e5fb81a23c428859cced25a010e57214693cedd3935334c29bec08b679e5ff7d8680d8fc99a24f131d7ded54c7e5437912f3474fcd1ac57db483288a7aa85e379874b2e46833fa0f55d05ad94d0d54c9d5fe7f8306358288da787cee336b4b63fe4cefac717655d3f1a6f50adf52283677dbc30e165f1ca1a7b312bb562dfa872a1521bbaa7006f6fc6a8c23fbe1c43a74332b144a2d2e39adfa8e77991cb479c671464e4f996e8046bc77141d520f6647304d3f935f4dd1af1e19a9fdac41cb428f43c2467af879d84601fad66f5385eb35a41111f34e6dd3a9565b80d544a5b7a391a095bbbb5aa0ab6e995e113c879fa8dacfebd2be85549dfa41c5fc21f120a24e85ece67cbe1f4964295fe70618232c347982dbc0aa39a259b7febe272b4e9cc8b73a6240e2666698ec5b80da4a65dc6149ad64e64270b31883f8a3b6966e8a35dd65e52cd8d71017db322849b094383532c51af2f63acfb06444e6eb2b51fb1beb697fffba21de3d464601043f4e4385458deb5aed6232dbbcca94eae921840f1361c0f65ca1adade4bc93020aafee9ed6c507b9f61e51f1a6f3f4d36b73ac0c152c284f83deebed3002d36859301f6ba2f7c33522846dd91d55ab3211e2e775905a16ee447773f7dab22aa8c0d908eba336427b57cbb4ef7114af9ae5684b7cd5e8b52a756831ef8bf98eb1ec055943d6e84866c6b2273a17f0975e6760c791db34de0c1bb373f0bfbf627af2087c9479d3a90c3e865e9f60a7f2e333e81b19074d628bb87d6c604aefa239058f0cd1a3765108d01d7cb71a44bcb771b4b3d991f73d851c5f61f16f1b6e3e273841e5a8645e875c71fc099013d5886ba09cc67e642d827e0692aec13925b8c9e8ff280f4493439a2c14f4b2191a410035a21f0acc408e485751174d63055835dffed81bbf67a744307fa8cdc60d32e0d03c2e2d8113631b6f3f8b86dc70a329582d7a4b8fdf917611a513f5250e8bf1866efebe5047aa2723d253b272311f05c4607cbca09cf0595bd49d58f88c588c0b45c1d8f176803448f7c9ec9f79d93a3680cdea61742778ffa3d51d212d42c748fd939610823cbe3a65b4770c70bfc3966b5b41034259b65185e021cb6d673c731e6460d38d4f444b0bfd3bb00f44f8b34f93248c9f246996af7d1c6325510272a3a2b191e50f37d6cb9e8abc05b1f3bb22d956b797b8b53d972baaea4c68336a973f77f355e753448da9696052f9fbaed80146560f9ee87f291e85b1e114eec1ca2c1d689416aa9b5667dcc392c1133f7e737640e064d1f3b9226a4304a09b679ce6dfaec20c116ef0db9bd5396ed967c939f24519f60f615c06ed958d02b021989b329f441caae6098f8dedd944fd669f0a3d6ec88d63a19266a4c5b2cc22559578682d7b042bd4cbee27b23b059a5cf32978449fbeec4dd34e0d1ad7bd1f08417663dda5e62fc4b71c617d716aff9ab97829df1ffedb54dcc34940bdf356ea67eca3d9e61305f8747432e27d25f6b1fa3b174d367dc4b2062715acbabfff1c99629491030f36e564e80a7e45fcd0bead3fcaa107946988ec28af3b3581f18373b2d846a105dce983bac59e0c5c6d43855f997a1148963d972e8be93034e3fedb7245c94664e626b8bcc9b3e1f6019dec5916c3aa528434d5cbba49edc86de4ff6efc6359788b2daf66585c3735ba76be095abd78c477e77856e0e5e8c9c4869ab27a91330517810c6e7be1c80691779638b4e13f22c2ca6e523f2256cf266a58f1bdfb442d3e77360e2a773f999d9cac20e07b499e5bc653103b6e34ceffe6729f5b71a7840131f0488307547f519016a1ac08ae32202bd71211e8af9f3cea1a901c8b38a670ebe9954155e6e9adfec8666600548ef0b8163e07461212d278d1def67e90601810fd313cd809e77947289a8913d801befea42fda231d4852bf7c7a0b436223ed262b65a49bea335fb86a5c7347ae4c8fbe80e32914ba5df86a6e94d05e5425ca0af89912d2a4de284fbe7824baf6ada80d5cd85b2c6a3ed95f54392a22287d111486feceeb77808d2233e4229a6f0063b9d5a72580fde3cd0eaf0ce2544f06c3daffc046be213c5ca828f092bc86641e5162f46a153c09c0c6c7c69b7f9e6e2929fcbe1ab2997f17e1ac713bd71169ec0cb722f21bd93eae6e2089b442d38360233e2b0e33553a861f5ab3895dbf2bd4eeafdcafb39fc428d0573fac0ba93ff4f731ad862a2155a3536edbb6f133135dc0dda48696de1a25d8ae3f5aa1af04cd9d94294a913651b8fd6378fe757609512bdd01561bf16fd3fd2ec4f284c9656207a7274789d18d6afdd704d2284821832a992db78e23aee0d146fec240e0ba78b16c3943c65af8c02f995f18e418449f7e4ebfa9152352dbff26d3bdd981caa62627a99c1c6a3f69709ac5d8e0522b56db64ad897965365ac19be29a226648382a176f507f08c05aca4b38e071653ba935695d6bddcf80ba8b761d3a0281962cbf512e0c3d9e001832ef7eeeae9a19296a75f6fb1d1283169065fd0b7698a7d40ed44a452a4c89db8a5f7615bd022fcbb5f7afd1e788682cb7debbe856ee7fc9bf72d08937513fab1283952ce8607a6975ef86911fbf54fc2b3ef562da0ba505c75e08558da4c114b2bbfa7ba119e4018264efeef255cfe25374f47e177a5f0494db5390dbc6cbbafc7751e40f38b8f36aeea431a8e1035a8c8d11346ad153883bc8a9fc0b8fa1a2046f6835b2a461641d201810c6ef3592c4a47c13314f213d330735fafa6a346c27469faf82513813b628c4631c2ddf9ca9dd05f9f7c3e5ab095900ee7ec2c542a68b38746d7ebd1f188933798954061068de87c8f0f0d3b556f82e9756f377756e1c79d301fe0da5adb990b874e23512a878adb621841bc1e44a17c457c7e8703f917aa68b596489076b531da34fd850fae7118f0612441af870cd1a31648fc43997bd6a273a88ec2ec98c4668c51000165fc2ee77c49f226ea59de06ba179821cde7226f7c60e2bdf5aabd4d982ab99f0daf04169a4006c72f0301e69732b4a0a6d16a207df3922ab1b605171fb9fefc53c5b40d5c2a9f67a40c4339a3bacc5eaed150247bf9319e5a59f7f716028c80a6a2952fd342d34c7694486fd1ba56654ccc93000281dd96bc59d20f5e5af5ec11318c3b4b4f0833f6a7479d4f440c41d9443fa7107e48b4e58287f949a708e30adeff1bf1e99b8e08978c10b8496a86b6d2c42d7b99c08e74a1508ea8d1b5eaaedfe62a8f42689358ed2ebbb3b522d0827a54824357de3d3229e730c17a9e2d378969c9a5752a5b1f7b99dff8f88cbc19da7a71d055600f729dbb261a16ea71c73aefb0d551ed49fe6f8c5fd127b99e6ead18fe1797e9c13fe2a025ea540a6bfee2f76fff3cbd6935ce227c6753b4ededfc3f44e106b8de951c3fe3f4338df6300632581d965ca397f87ffe2fd79f98b16dff14b024ddd738fd0577fec6ff5132653eb0858cf342b231b64722f74951762870e9bc9630ee678e6a88a18cd4fbceb4a8206f333c2be2542c4d1c297f6569619b438824f5674bd1ca07a573b7450f49842662c00ccd877e5d7277767a0ec23d39ce08176d7bf5466dff812b50628bd8fa745758c4826bfe382d47d4ae85e32c37084b5b8fee7f317eceeb0eacf988d7a212268616763258e57794b4bf74ac9e7063f513d51aff31ed19251dfec251722318737fa278136c9e1a53fa9336cfd402042b06e799d771a94a4e9668e9da82a34d4650d3e165c70eb7b29e17113634b0b70c6779e352f1162d83dc8f1b8e3095329a36929bf6ede0e9ecee8aee6cb07577540cc2e1adde004be4d10b6a9f90baf35272174da2c6074c4150b176edb98c278957b6ed6fd3f78c2dba8462534014294332e949c1bf149ac561aa9ab8a10faffb016d53ac14614eae219e20aca180c1034dfcd2a20be784b62376ef430e4fc8e1a5e9a61b1f94f38db0e27fda52150cc0ac5f8f61f7afc5d056a6d161b8bcd4bfaf527f1ed54ba3dfc9464c9993f1e572334f75ce6c42b3b0296eae7577032c18792952ad1ef24169ff28edcd3e23de1d6343d1a2ff81c876711de4b302172431e4f1745d81cc6780e799ff966a3a1fb8ff83f40d03f532458a7c574a548b8ab77802e18548be8af4af6b33e4da4e1e4f607b408a917b443cf422b32803bf2e252617d234b52aedfe1a8ab5b1dae131e8501c577a70a81e9d9d54b9b672337f52538c0f61775f1e2346752ffd4460c10e219f90d2a05cc4800a2715f2b6a42e7feef3f8e1b0341e1fc0b8cb32e872a7c4f3fd350f4db0ced230c2174e852da9ba240e19682f62fb66d9879cef4b28b5383dbd5e35ba2feb9e0c07aba72c9f8cefd7889beaa629d71e18485891df8b6c904d3136cbc57fad1b59fd8b17f884a175fd2bce5732c112b32d627f8c54465f7a3272491f23b18b2fdff74dbc700cd3dc3a3e432d244dacbef5fa0fded6f534ce4ff9822e0139b4e4cbf406f02312a47690f025a7707b5320572e40ee2b94dc04ffcf60c573bb2833b7a20f27f3bdbf7869fbeff9ba3fe61e3162e57d66bfc6b3626a7aca36714f91315693bba27d22a9a0fd1d078c8872e05cb07e71deaa1f63da9fce62e492164f337ed6feff8151c316d4d8784bca39692db13f00f68e6eb99440f9bdf5c253b69b2d04a71eed648deee4109d6afc68a36218c7a03121c5531a4b0b414df1d67b2783c695639fca8993175df0f5a27b9790b4e83716186d5423a40c562182f9e4232a71199ae8b9a26f859c6f40fdcbfcbbcb162218d2c15db964b516ea6e1ed5bfed808bb696a4ac15e6a7dd0edc5263b4392659ad0d5c86d04e027f2967848025b4980ebc29c0c8f6ea854ffa0a254a772fcf030a987e27949c0abf08b6d006a748f86e1f5cadb257cdd8280e279dbced673dde3b996b4ae8813fa4ed67a5484a569f8246d1cbb53ddefa5bfff91ac7b38a5340b001ff9bf28462f8f22053a354d3b1a8dae14d430b82ba52e369079b3726415ad03ad54978c2e1844a5508b02a8fe2edf2207902e33d37838a68e638aa05d5fc1a43525b6f074dd972755ca3a71b52ff615a4c7e8e4374c78446d8be03777b62ccc3e8bf6a0eececafee666ecfb4fbf17d9a16c37a7714559adb543a3111f24bf412243e04820acd51d5d7edbfc1d4ba6a5ba9c4b379ccc14df2e4a858a5b66842be287013507a43c76bf673d1e8a963de60e2cb3154454cc82f385f013d9cdc43234125059226aa516b36114f4c79934a2f951ad6f84f2b70dc9a09d111eddf6ed0c7b3e4addfb32b7cd13219184954da41e774b4fa2cc8ce1cf3c68961784d9f67ffe66d61c0f7c2401eaab6dd2a301a8554a5dea35945ad99ccf96f643ff8f65a4d50148b6e7f5a7133e5271a8cbb74a261ff00a83425805073c38557368ce1385cb78ab5c976e5e024582609bf2c857771292dcd2c544ddff70a3d00f1a977851425a7b3dd9b066c51f960a8abdea2292fd46aef1f72bdf4a92733ba75c1e79cdf106fde2137c9f2c023687d9950457b3479697c9e7d8083e5a2dfbf95825d356dbe1598a5bf4f21bc7d5a39a3966fd21409587883cabbb4678d7af292715ce636c9dc8c417fd9fba1b479fba0de2f5f0897008764470a4346bc9f32fe6ff849338c3611bcbd2bcf29f1c91f7b8332fbfad4faf961523149a81dfbd341f8125469f6b9e6672c7b354fb392db7558218781ba41b0beec418235ac07b943569f134b52c25a94366440c2f1a1d12ee5bcdc233a89aafc3a1b1230c7afc00b4f077eddc7895881cf3ac871db62bdcfb04b271163462603e0e209ea0938fc7f8a05a5b38da638c0fe1e4fae0119cfb6b5fc714ba141e06f24bbc50c0419590c35d9d9506091b66feebc34cd53713d13c9525c549269af7d255483555cd2a64f51345316cf2da16f3bafba360749f9f181ba4f18e65a4ea8864422acb5eb1b66e1ec89286aabc4091f9b86437b85486f90321720b4b7a1f86a4044f56da62b2f805f905fa495f2dd1e63de210f0fef3e55f98132d2d560c7b99a20d33f7e46a08597a4e093f4a34d4325e66359b2709c41a8403653bdcf251594c0983bd3c0e26c203c05ca0c204f9011c4f8cf7a2ed683a9a8639422559a3427bed9eb11f643c7a1fc543146a98a1457c9c623a17a92eb57b839137d695f27eeb6809477ccdfcb8dfa0e9bdf944ba8bf255429cfea398479e9cb087c1bef5f73567bf2a4512d9afb747216377cefa130ed336ce405840dc024e49e829099093f350369ef46ba581ecf78f297edd4fb32b7c92db59f7cf3b5f231cadeeb6af289da33b2b63a5c834dacc92bf4b1fbc503e9af7968e149695fe159aab71914a4e80e3c3c6121fe5942b51d9a2c84c31e5063a85fe635cbe7eb530d0f639f86fff833814126804bd0e4d63b82fd34dd431e1b90f1b555094e976bdfbe1dda09e64c2fe006b3c8c6c098bbf775a22d1b14fc3ed8998a24f906ebba8241d040a3199d19226e1cea2f21a6f16084dd820cad015dfef8e1e6a01ca4a5f1973c4ecbf1897f890e41d2d3c293db54a118007750a9f6824c5dedec84bd2f875b4a699f8fc1914699e4f204f608c5c5b486c3085005bbf1917e7fea796a8b6e71eea915450219e44db4b91118bc35fa1079bb9d55a01fc45f95c7b261409a5ea12474cd5f6381c4ca7962e25f86a0c4a082474135a198b47854fbfe758ae03d397c06463850bd4029bdeb389cf6a2fcc5dca28b26a0952984b00ea492470e55ca6a7e681315f611fb79b95a1bbbc78573e2844759c3310fec6eda8d7260f683f12dd6d409ae9f5e6f48e7746ff7ffa43f4734720339160bab5d2aa1d1e657e1b9011d45aaa448968041abd99d6224541add5747a197903cff463a79f2971a7a738ff2672c7f366fd7569e8ce0f28f462f21e218f563af7fbe50102da9a18bacdfe60f8fce7fb8137234e31f98f66ac3c982d8cc72d03c0b07d8cd4e61ddcd3fbf2c9a95bb1a5f7eb56c3f969c762426ed262e20e874423fefdff773fcb1405553f2527cb79a6780e95d9c127ea398b6e838cf830f07a2ab1ef193ca9fa5cdd4d11a0ecb29c1fe0528f9f59d8d5646b38b5745345869d49cb0176a5b4016dedfbb7f4d72d100c146e0a82cc64bf96245a38060fb80da80358fcace0d62529f50b59fc54ca76860f5fa244f4ab52996cd4564ee8806100bb2894a4470b0737aa5298270bf2382f67d3dde27414d94e2fe7d2871a1e27590939dd153489b85ea3296dfb6f0f1864649c852132290512d0eb29785dbc5d9b190ffc0844ed125a2222b2fd042c8d2d94363fad51420438558dd0d32c0f3ed821a64c79b0eba363960a066416d44ff2a9d4e8a6a1b5a2efeca7115ccee86f8ea8b0105f047243ae163708d38ee91b2e59779426faf0dbb582be6f159c3d81dd0f8507f72c01d97b1fdd6dd1bf2c5f846ac7bcc42460179825944fe2979595a2f869f58879806f361058111e3fee0604e85ebd9c678ef11591118c618bbf69146b0e1f15530e25862fb0f03e2fec764068cefc125d0cf49f8c3f32dad5a2681b95fa57c74593f99fe52eb7968eef7f1e304e5fa45794d582ac1bcb6b7352e104cc8e69ba1de6ec0efbd28d694746e6d863d40fecf4fd5bf7374f48e96bf6fb1d600608446d2dc59cfc184fc3d9d26682aaebbff558a216f897fb807b84104acf28c176fec726084c1af78098f997886b97013f6955fc662388f77010813a36d2904b886b93f08d2b3a46b5dfc135aebc97b17b873e8c685459bbcbd2fbec22161c02fb06a63f0b28e94ba482e075e19ac8806c405b2c25486a9702d3397528c0bb616af6026de413cf8edbcf6dee09db38112126589028efe0795ec8d892820e7ba653f8f69b2124a24bb6d73c881e5e6492c05fd1782398b94f4374d9e58eb2b2c90b03d08df17acdcaf3edb44b87e83692b6623adb47e8fea8096dd90adf6757716ce1d401e7bdb8ce98ee84a4876a39379ef4ad13b47f67ed6af74e3eebdbe8990ced26d973faa714f71be6d5eaeb977a014e0c33c9b8803e0786a6bc112b12e7cb232189c1dd2a606547eb1436c9f2db325d68b4604fb0f79123425b6a89ba3f91db098b8661af9d3cea4ed461b216a38427c6b5e1f8dc8344d60f170bd7c109d8c757b2577417cfb3688772fa547d9082746ed816e542d154a00aa3ac2703fcebe60345dffba504af2acb73c2a2fe1cfec9b11b0716f1f8895ebfff8495788b76ad93f8cf07927d9c4824904aae14c40f43131b36c67721fbbe8e5a817b74110e43f8c7bb5f215ad9deb7295a904eda874e61ed4f337ee27f40a5c67e8d1858896e8664e8ac20ab087ddab7bd13b6e1f44301ac7f95c633d5cd9c16bf5062cf84ae3f44572a250eac66d27488c33f37148db501928de03783baaed55a096a383da0df6feab688f5ac2228cf854d07b72c7615673095d13c677442e7d589dc1ad6b14291b08768491ba2e749caf10d9831de87bdbb5618ae67fe97c6132afc471608ccfd20fccc3f424072ece3557ff5ff06fb687da17b7c1731403afa908ca0769eaaf76733e5b7edd4ffb96935527cf0b11f62bc60330d4c197b48608ac0f8b211df16d2dea64bbb9e877ad3de874395c8800ffbb668deb0d55baee0271ba2a7f1d86f8d210f07b7e24faadc366cbba92b047d62c0320dd20bb2a1645301d5052671820202fd24b7eca341e300b5b5b1e47531bd3c1237378fd668c7f9f4008d5c86f5b914f09f77327159823e6a9b231b4bdcbd7909b584c5783f40ed491d68ed29ebbaa2eeb824e9f04674e4e782cde708d48a2b08178e63197859d1b05286c4109c685e234dd8acdaa5e71e17e5a37eda4ff96f84dfaed29dcb4f8ef204ec4fb28e629845fa0b2c3636c9592f78aa3d369ad6218c126c9261630f3c19761ada022bca0e6203f5c2411107262700d546b4ec044f92cf3f41fe33ce631fde2bd7efc20fee10ac711285ab3daad17c1d10fc578e0a1705eec1f3d9437484b34c5daae234caff4624690ac7a4c6327469e639561bfde6ee4fad88011423d0bd7abebf7b25fe9252dfdb3227daa1e324b6e8778d8b668547c5e7ce887a9967561217438ae55d58c6818591b17e31309a526474613ad62406b3a262a1f6a956e3476e30bf22e12c2c95231eeb7f6d541b0d292432d86fb648c5dbedee72ae222d680e6392ef50845f916d4c2dd2ef193bdf2d8a4397f62f7b9f24137123d85fe0b8ef893292a7b3a46ca06cb9af8bc8acd75f7867a3c8054011ed5dcecf44ac261190c7a6147dfd49d269e28e0ec410b9612f2dd7d83354a90ce103e444c3725d6052a41179bc4a8455277b4d0ed42fa68c3b3445719f3b4d3d0b0ec2f43af8fb747bb39c63d49ddedd1f63141e447858d5ca5fb4214b583eda5079028580904977179b72cebfd9db950570b822d210065a6c11359dc33a2148569d24fd889b2e69d1e90c769fde4b24721154f4dfabdab76ee54a84c7841bb285225d2d218ce4537e5c5537ff4ee6a00a1ee2f399ca5ac9765089ba2ffc4ded3fac8360793f25bf2036fd9823e0fb01ca6479c474ad6ba9563af65b5e559748fce767d271761148a6c432bb7df10667f5fadaa266131e4fcb9b5ebeb164456a3a4f3636ebcd8d1be764c9e48831986f88e2322d231410d2f5c31d687c6e215951c4bb0826201652af3ecc49761c40ec125d37b3733f8305253ef8d543c6145358d3f671b43c3c5b881cfadcf5bbc29cae5336bab4e228f8c3101587b219e15396010e01492e8093e36d9d3d39c8986bfffaa7c6510ba32c3658b0fe54780a11cd6d3591fbb90bfc7a5163e0050db6dd82ef58c5f43e9be7b55b6b306c361ab3c682cf068009a44a15c15329b1a55f6ac84e84392180cfade8d7b137dc24219f8fc544b83e6da077e4e778d5dfbf6df4595c57cf3d7811bfa0e398a2a7df54760b2015a453d88e099ecec4cdc2dfcbcc8b9c8468272d6d44fccfc8224312c0d9be043d52e1061cfd9467354769767b1f9da31ba6776321c4fb556b2e1dbde90f54ae54561c193b2020cb1e29be7b1f47182bc03acd5f1bdc4e01f96171b40ef5d8efab2de9c8b53e7fb52baf93ff7d3b05f939c5d3781a4e77899391bf869f88fd13c489e2001de9ecdc2abeccd554f45e92233db761164626b9c278025898a84804209b485ec12a181c3c9849cb515b3b5b73603328ee171ac1350e12b0c581132355023e1a86910d5db4495c2a6f79c3d22e057f09f3a4538df7435c480b9f7e16ce4034e08986c0881913a9244517cb67f78b457bd5cff2afc7273192274ba7a5864dd03464439bc0544e7f21596ef3e0fa2e275a12481407b4618e23e75f61faaffc36dcdbe1ce2b9c27b35e7c9d29301f271c4b89a29ab4e83bd2088a5ac63ebbee04549fe1cb05aa423b2bf5c288726922176b94bb900b47756279573cf0ea9ecf0ed47715dc606d5b3a73c8cfd679dcc1785b17173e82766508d05e20878fb0ac867409229b110e2494f11e3cac65dda52e64e28a516c7a45a26b4312fe657c5919186495a5765738d1e3ed9c9170304e1e7193d91b003a9de906ac69833cf8af35ee1ea31fa7b5b3cbec986c5e108bf6d80244995244dbf713f3f24b0f6ecaa5fa9d7a2ee031f3869d231b6e47b34dba951cd157a4aad9d46b489658659836b1b6ce93315ccb897dc082e95970e6b3d1ebc81048bd07e57989cadbf6037de0ae9727760586833eebeaeb77c7e87cb6996682137862f4ed9354279bdfc14a6e9c6154a237f324d33e57d2e60e290e6401c2d387161806b2ea3b517fe1dd5d0578cb70b13cdc1526ee674c79cb52caa6192abde6cba6702bdd924db448520bb3d77cbf6d51dac683124e2f12ddc03e728fed28d3beac9e5f0f05367c4d40425b3332692a1ffab3107896cadf742a348e5d09f2e50335be0697b84323d3af1438fe9f47e8222f7144b6f325544c90d0959f649cb3e214dc2c14fdacf2796bfe5e97f3acc1d7f2cf4bc854e426b65ca6723fc31245094e929e40a5ea75f4247ac6de624b42378fd4a4e36ea690d459faed3d3b7d29d60e2b5ddea420dc3fb43e759b6a113b0cd0df45977fae1559c5df3a8f4300adbba61904e6c12375e13e44d13ffd40fa324deef1c8070e7b6d81cf1e97b8f3ac9635c970a51732326a740303f2bb18f6e42680076a0eea4dcf0ab71f53b7d076edc90893f25b844838b7db3766eac166ec996e6549b42fdd990cff1f163e81bf3e34fa178bb3186059923675e667dee12510048363244da58ff939b4b0efa108ea7679dc87421fe28eb486e142d4804a5fe7906c769e8d195abec8144fd567d7678cba8b39d826b406429e3e0ad3136117dec8d996b338480bc0dd1ea3c3149138348f216a3e97b96318d169cf70afab4de5907cb0971648bc383e589eea37d8ce614f58e5c29957a502d9d0e3a3f5b92834d9fc9dc7a5161ae37788a674d008459f3df383da85ca09f3eb06389986a88d1196651a985c4567fc8aa43e89cccb2c70412129e1c80d3327e7e9eb5389e2d82c55e4af508d599a22eb44776d52989bccda991c445763efc65b35748451405ce19db034efaca4c794a79b962fbe5ecf2fef53da149b71ba06045aa8b0500a3137a523acf4870d93ab1cf1b1a6f64a9c2cfeae4534f3ca0a8051ec97d60f5f8df72694a38a0e21daba4fb2f1aa65fd671d1a1f88715b771800a32bccbd82558374d114279c2240a669aa89a04651388d50f2ade437c67add02086071d4316d165e735ba85eb4f8e7d19fb30c97320714a8e424ac4e477256be727a5fff98d07ae262d4fcd90b279bfc16c76cbd4b8e42302da96f594b7f5d734f2a0b0d35906a86793f419ac2a90346bcfa9d6106f02e50230c65478f653b410750e1e5125c57ffdf73b7d69423cfd648227f45d02a2fadb00f354ade69e23e7ffd31839cdfd15d1020f8fb9224940df3586e5b03afd165fabc2190399b06075ba295aa001a5902fb6163411314fe0b2eb50cd7ab11a6c124f790be40378d6cfe431549472b8d1f1f2dcb039302284e7c5d4330d1556d350818da7ddd3aefbf4700a2b07795c947eeda6257dca73074036a4ab7711e6de335e81f8ed95288569a740880a7232c0f4d3db61b5248f6150ee740dbfdff21d143f64807d8c038343920d1c11cf7df6bedfc38a368f4ff320c70b85c954528ff4ff65f7036f78301f7dc09c58ec7e0ee5569910dafcf77495b45761b559ba74fcbda1ed57eb644ed80d95db02cb8c05d871ea1450540e310cc7e1e0f010e73906964ea1cb39b1cf6b7265ac01ee6c20e3ae761846415a9e1cfde938558d7f692df6f4d9d018394126c70078808498957861e8348d15319d7e047ddd3757b99c815e4847fdf8b7080c82e46402f6fe801a2aa4b881e82db77fd992afc18b386bae2e8e03d3ef9614d43fc3ddfda6ca9600f5bc3103327745e2ba16d73ef58a1c0ed2d5ceda1fa355c2b13a4d500606fd61e170d1c89c26f141d026098e6506f110633ae4893a117a4d5ec52a456b61bdd2c9b9944233b4f5c058db84b0751af04008f5677d989fb038dc3ff99bc6d7d06395ca3977c5380e546e1f3de1bcbe7f27d10804ec932ff8f8cabc7a6c517f47e302592232b5f520bc74edcfd278e38a3e862699deae9a99aa91005b0cefdee3e114d81f31e1e556fc1d827eddfe35576e7349db07a64c099eabdde63d9c695c0979630d9245a02d247a17a1e4d5ee20e59ade1d1307803ab1d11b37dae4c7857b9c7b061ea5fd59d88d7da5a15ccf0db3a5ab1d819ccdd934540ba5a7b836479509406d7eeaa3bf6a7cc714871fed7faa478a209a4533b79e55863bd450754385e39171c5130412f2f805b973c46151defc9a9a20abd5ae1fb4f8b73f987b957e4d915f79f081dac7898cc03a147561f5dfa21dc26b2a0d8f266fab3fe3053a9e19099e7a03d51f36b087b58b735dab4970163006e940c7111a04c670158e7b009c222380d489ba314bf64d164eabc2ba30eef259cacb6ab9e90b29f394c2dce6c9ae1c6fa166e8d090421f7291df441ed3ae0b73e7a3989df101b840e0821f738e409e0b184806c53000512bf2bac2c7f69fa6f81f525ad06256ce755fee28eafe6c8eceaa406e9d39d4965c78d56593e280370e7e4045dba6ba3c1afce63cd8e0373351bb9a59c42a5cffb1cb15f83f479caf02ac32935f9604a9963dd23ae120717bda61d055aef632f4c2eabf0afef08679ac9d8cfffb19ff316a77e6c9ffd28c4c16e41ce41ec6010d1ff7a3c8d3963ec2810d16748cd09379dec808cddca0674b4cc39520252ad278ec3117c285afe12aa9af1d24f9664501136d00a145ab228df68a63970ccb02994820f2a3de807a0a7fd5fdcea2dcfb8b1c45b210fd383ffd7eefce1b3fd1357edeec34a86272d16fb9e05835b4e9da39821fb6a04d947ff8340ee453a7e18a503e614c42df7c466e78bc9402ffd336bc8dc68d1999f2090ca77b27dc47117dd427a2aa45cf8180630d0f909f6ffd8e7c09a20baa847f8425180d3b0b2a4519488cdcd189971d9571e99b2a72142de70c9280c12a5c6cfd7435eeed7f155ba16e51a5566cdb6714f1ade6718de89df6cf9e0f2d34a9ab7792549468336ee5a4e5edd62327ccd81d62b84f2273b5435651840cc19bf4345aed0fc14ad852366fad037a775e1625504823b2e55016d577fc2173e3b7d2a54367be5c0b65c0d4dcb7f6fb23540dde08eb848ea32f055fef86f7f1adfb2200152ac841b1f1cb999ff01ac1fa46e7f0deb3ef4fa40ede6da4b1c5ab5a0acad40cbb6766be0a728554cec72b694b3077d1850d3e30ceddb7ac84216fd2b11d8585e384e5f942be6485aa2db556eee3fbfcf8084cfbde93534e6df7ec3275982dccc005c43a6808972138fac6adace23e5d4f5e775ccd3010d2fdf8c82d9e48c525bf8c983b29b390c2dce6cfae9b5fdf9817b38062e8c45fdb6718ac96c48ed4c0a65f679e99cb68b465e5e46f46a6d297903daed8abb8eb8eeb312e08aba3fbc83612a995755b32d23fdc4720a7216e70b4aef6d3bb8610f7ea64c8474a0310f48a03d0804a8eb4d20476e202ff3fd02c4a020f308acf7f05b647d951c167b62829aae08674be940be851664755e6750848940d639d3d3a58fb7e305820f5645951d965873046e9dc1fc4f3cf312d38839dd4a23e8b1e7cfedb5f1fb2e25347ee85797cc66c1535c52e480b8217ffac4b99d06b39633ae7b345d5661f350dcaabfc29e48d5006ac6561c9c240bd694cef74df1f39e1eedbee4cacdda1e910eaa2421e7733f708c2b9f30dc15fcc2409d8d4c25dd5107caa3ebca9c997a538a736b4b085fe819e0a863b07ba1d1f556589e1be56366122f2830549cf4956685dedd0ef60c3a52cc4de19f98665029e4a9c99ad239f747a328f56b6dc3b0eee392a927197676ad2add85c9c0eea90ee92443ab76c69822f8a780ebf3cb83908ea7d67b809f1fa1064384cb3806f65f26287a8ccb29a783f4693deb6cecdb04b31e19385704e30c06d8946f0f94b31fc6a9e2180253f651379a0bbd6e0f8bebde4709b1ec2146f49b466ae38145259b1486f25f8e525d11773633a1ba2c7827ce593befca85735bd69656dd314b4bbe2d50b117acfe1fa728a2bc1aef7b77a3c308c7ce76de49590c0d944e6f335f06b52104409e8bdd9a12c92aacd7fe879126d7e2b97b850035d304f9dfcfb0cc6155367d5b00433ab128f8520c3b9f0c777b4ce89b9f4171ace5266babe061b8590f4730370f3ea6279e9330e05238cc41df721801e58bb9fe525af7e4012878a47f2be68cf4c3e04900fbbd22d140906a0109cd5db2fb89dcd46728b56d341ef1cc315ef0fc53d0f97461f8cd60c23511142de5c5ee57e20929557a847c518d1bb9687551041974d33a430b40f0fceed4fbd2c6e2ede06bdc42d05420f5957d9b0d8a5a188c0852032992c3db401acaef4b528042f9d80c3361f4ced2adde9fcead248d86bda930bf9357fe2bb0dc1f1fb2f5b8564aed045e57d05c44246f3f2ff03df94664fbe2394315a6e3ded980afcd23bd111262c75a79e85fa1210936cc448306f9e0bad55cf4afd42189297308757cc00fa1ecbd1c447aa6d4e4c2164372af6f874e3235ccbe2edee1bd8d9d2d71af77f52a2e90ac694080edda5e4fdf6d1839b4c20a277e2bee6cd0709a4230b9f560478f8c5614e14f321c92f06976543570119aa219625e23a701b3b385710b6a58c5fd7f4a387506acb10dec056056e61a06a6d09baab1d8d6cb7c33adf70d0d91a91c195f64bc45c10182216492bb5829a4030c1ed1b4f1a789956cdda83ebb6689a67271747329d4e7df0ca3d8818ba26c5ce5261eed4fe00004baf181cbf623188cfcaa918303d4edcb7425cc12bc5cb68f4f1832e2332c5bf0c3a0ddd7b36da15ea225770b3a64ba01327d412729d1ae029f3f86a88a255d2b91bc810a98feb58494b9547d3e3e9605588180ae190bf2540c7ae4451dd274d33024009b3fc31219a4867b82c9cf1e3f5f85f8baf3ef112114ef765233495be7a502be6a14384e9b6127b08161490fe13e741f29a9c39d69ff254e35e2fd3c013f4a6b23f0e7897a0f45999f454f54c239a59148181feb445c89a27299eabd843762373ae586f6515ee97d278e418d12c618f3568ba9a02840174510a451915744b1bec0f8e03d98c923ec23e5d0c3a1a88de57167ac7f28d7de0c68292369caeb61b9e182eb4d1e68d2f162ab1bf62c5a16c121b788077d753fd267dd8e20d2a3ddb01b4e7191b3714b3864f8d6e15ac1860b44242626a26d65802bb95d8fbb1e76ea1327764a8c61fbe7a652cc576d6019806e7375435df58e5134c13c882cdab0bd92adeb9ae8f02e68030ba83632ddeffbf56774b1d4f55527fe59862b0b22829c8626f51137d28c5fe5fa8a0983f76b307b661424b7b1ef15222bdd29fdc94cb035ba9145e4496815d3d7eff4bd91420d3364927ad13b12c88dfdb4c279e524f1b09085e44856844a000cd749c99126de9a69ad14cf8799917e435def9fd0ea652407c5988173a1557f3a20d7e733ed332f0cac20309ca50e16a07559594a5b4784f565b009e7dcaacfc4e5b3bdc56ff5dd0a0f898090d78e0fa7ed9b9f88d512e71497cc526a12ee660cae3c06c9f5c9a27b86f53685cf634b65c9e41590de82ff9d3ff025a1c5d752374fac01f119d0bc77954f497e8b7c944d4bbf2887895802f895e9544264d89c537c603d17180db91ca9bfd838ab8d74af159103a58f3d01db9d7acb9588ec7a9e4ee4b74677bb20dcd7be7749463147fbc887d3d4bbb201d097d86b69c0ef0a13bdc8e64086d7c20067a32cc5666cabb0ef4c8c3880f8a05ee9dc14b330150b8da2aa993572ffd22ed608c6fe225f269f7bc5adc571201bfdb12dfcef328673bde90f2ebc9bb7c5485cab261d9e0fc799d3626f3f0aca58752e369708b45658eb88008afe255cf24f50b9ca1843f76b2b1b80a9130ee1c460e167f140f34b8df65214e99cbadf8088bcbf56783a1e6c576616b523e27a7ebd048ce2b5f2e53d9589ad5b9a28d1e1272b151cfbda1f74c82300b9828d6ddf85cb95388e0358e0066282559b477b8b5ae0ad6898af22c3f639765faeb22a930c95459e869afca6382482dccb0bce5094c54a8b9b899af6784ecf14ed16538408c4ff2f9cd0e0dfa4979d903fdedd2c7b6593f2944eebc2295d6c635730c7fd40471dabb50871993ff7b1bd89f458e0973f469d6daad8103f266e5869c85f8eaa1902c883fe710a2ff31d3522b26dd7feda9f72b3a3a5beed7a7ebe18692e81cad1deb1e50da063ac171bef8cd7c14de37521b4575b29115b886d35cff0659cc27db3c8a57e6bf075dc4b160c0c33fc6bca537f720aa39e42b9d01ad24d0c9f6a8c2213d206177eb14791f929a94fa157f5d7f4ce32a1a3fde916e2ea0a5f3ca929fe2095dd07b8f6c5c7a86da07aec34a26056c05185c715663e23e876d5c1abaf942f9cb58e5bb85d26834e0634513a801a37e7f16c2542ae10167f18711e51cbf657efbeccfd8d471ed1b1a09c58724daed88bc68da07921b562ad04ba8d2c36e1c9ef638d1044476a71f3fdfd07542ea5b089ea891b8997c5ac544540e4fa5829bf1a2e55fb1c8443a5d8d6b493cae0f8057e97499bb3433e9f92ffd6812608a043f1d7e2ce1360a91d1517d330046baf808c31cde8fc1612edd1675df38b55a008ae3bcf2908061e5da5fbef9af7dcf2d27e435f834023cd86afa90645acafe982cfcf6e40c5dbb0b85c2c5fa85a68225cd74d84ac3aabf1e2e9a631aec2e5374b46c2c593c92d8d64a2ddc692b1cddd6974477d1ff30f66607063a5cc5dbbf6d332f34ba868c2d158647845857a87e71e3a56aa3b7eea9f40be794e31d5ddd1a1e2b58a7fd2f07f0ce21a270072effdcffd97e2d9b20cfdf2e770912426b2c8b478dd897c0ee5d14e4034249653d081b1df2debbf1169cdd5d22bf21f797ba7c679ecf61e68f632c1662078b61e68d0f581fcb281ef3f0c174c17e555a16927f7b516657ef1b1ed26e25a1547e179224697b0fdd4d3319021f1ff32187658d8c619fe7a2f3bed6e04b387d66c9730ee778efdcf35ca0d772e772886fbafe0f7de27be0009d90916dfed6d90af6697d46e5dfcd5b9b17024f301a359211f1d381dc6b9f462f1e3627c847655c68a24c1564fb59c7a871e9300d8b7cf8d38152fccdf8f5acb60fa3a85f6fc11d8e4eb4a5a0e664830ea4e3b3f3dd65fdbb41488b223c09252ac9ac02fdca4a5ea10427582715776be126c51c6b8bfd4193fa36c164bc3c3d72812fe2f1c4144014fc34d5d700ccbc660774d3677a0aabf3980b9e8bf09508de3dc42e44bc3dacaa777f0ae9059c94be96ee35424ad700a1cc3c054fa3c0c857fde6d13a7c9f91886dae564c902ee4ced51aad1af993fd3faa0aff831918b6a88a0eed24f077826af1e28cb00e97536340e4c1771c6ec3fdd99ffed37cdef9fe16632b65b8f9d57e61672541841148c97b8cf6787d281925bf28b00db65c014f48ac776ada1a40ac82bedc20ae6ad9ea054a1e388a38e0220c8b14282b04e5d5f61667ff096a5f35db3da9b567ff4e0a20109dc87d43188428cc472c78d6f27874905935a11f18e9d2773da2175c1ac4e5d594c0b2c851a5604f20f5de4d2556f72d63627b26f6659b2a54d0c67207412d864cac125a8606827ff42376bd2d473f81b7f51bb90a8e047f6a17c8d529cf250d135bfd88209dec8fe7d8a00a2d3b76a76f53864b549b31bd2c8bc1a82e57aec22c7fc6ff1b844a2755fd8cc7f6ae3cf100c73fe7e0be8d608627ea9cc4cd5acfdeb9150ac623e6d0d138350a001621f799467a15405c3f511745629bd5e780c3edf0c58436bcf98fa6fc2ea634b6a14ce30e2a3141a7e615fee8b22e2768fde5b7a13677a1d46833e63bf659dfcc703713522a13fd4d4ad6a5a7990a003694ae97ff6fc47de8e93c22f545856d54c77bb52de7ac40e5e0d17ed937ae70c807520a3a103194ffed4e5ed3159832f99e3a80509d33b4ab7c29f44385afb503c9090627af7ff3164fcc1cba1852cde82f79a4cff2f6ef10b85670299902f88c3882571a4e8b056828bcdbd4176285004c9f1b57f1724fae7c9e89f18ba6f9d308512c589498049d079cf7655144418937027a37b14ecd87d3b18fae5ab7e2c697239cb01eb0e86a17c14845b5a98ec0ec55aadffb87f22186a1a3a7219d6458220835edb098de73a09bc5b65def196c96030bc21a6fcce54982457fe0c45e38f5103d9da739ccbd68b3ea7933630974b189e49c84bc7ba190430699a1bc9916fa701f8e7542b6cec94d3808a84af81e44b1708a9ff15cea3b6c7cfe0e71c8b4f96e54fa6eea4b85c47db1ad8f05f74117bc40351f179f60fd3b55f40b284b785dd0d4ccecb7d777383bd4efe3c783a1325f0ca1ff8cb32450103bdd1136443792f48379345de8def22433097a3eb7d2f2fc628ce09fe164b5fab8afb3081e49bc7e9789d7f61c2eb675d50f5cf16a4a5c5b493f185952c9db712b6402304169bb15174c28a6c4998cc83879faefeeede82cffe1a9fc41233f8349c63feb0d907b2b9c12f04f6390f8f6f6c507a0f3f5bc04bb1149b931a768eb66eca203117ddcd4298c48a928a389fb65b5426aadec2f69ef7707cb554fd19f37716c75a19e7e30b4851eb0029f03c36cfa76ee9cafab6b80039ead859c6ebebc168cd3abc6b30afa78e14dd3746b6046cbee8555f6d8258d4d2c217f5d006ede35056b97d28db4d3182a177ca157d457b33227c36944c05c21829814537c5b0d17dd444f5c49297fe78106487768bbe3b9f200ffe87fa12250dd97ddb36b4cb8f3f40578bdf6c749272e20166481bcbefb60d7cfbf84757ed902197a2e5558c349ee18f7cadf0d2fa8f9f44f365cca5556eb87452e9400126a6a3a35de54283d30b08fcb1416bef542bfec38b21085d7e788ade312533ff5f05040c039c27d84960ee0c6cc4560ffc56303d7b28e322f444aaa9e7d91747d70c22b751458fb0002eb0ebda3c7e7f063e00388ff1ffa4681d13568b92fef0d63f2710c4063f89962bdd966b9e6593fcc28defffe0335e93422ad3ae39b374e462feb7beaa9eb8328595382993557c165330218abb374ebb4b3cbaa01d1f9d8c5ab52f8297c18f21ceec6d1aedce94aebcfb18726696e66d85b22cf0a5fc9916cd16ba9f3d8f640edf0fddd4d3775b6fd2736f83e2bcffa75bb6b9ce8dcfe2160ee46ed16346670ae655dd9de087a7a8647c4d0c6e30efc44d436e40a279499d7deb47bad93dc7989fb59512f465ea4f02d0afbfff717e4eedc9e82a78b3e05b01d8ea8eff154632d0c5d13f2ff201b9a9e8f03215859df14a560b11f42a08f197a9a6c7eea996b47acabb539baeed3015c5fed07af42e3c409a39e0f389182573cca09b6039d9b023aec1bf55d14a5e720e7a46d33f8f80999817c1f1c412754d2f421ff1fde98d89dafdf237bbcaa271e9f156d51e90401deee9aff2f1ad6ed151b504572b961048c9360fb130c02c2c81a05a27eb9ac741af1fbae7e978204fc60c854c8104c76d6c1651e13d6b8b3a3a8ab5eb2f7385322d67080cf88bc109e679fe4fea9a35f734493f9a63de0cf375ed7294a392b557c4bd7f1e1c20d0f5c7cde08c490de95e12f9172380c32436cae36322347eb236c3e5afc1a0b4e8f80e06b3c9d8ac233a626eb00c5f88f663cf3e8d03f10a9fe4bf36bfadd523b9f235b01f548faf53d6fbcb70ef5bea4e773c234e7af520a0dbd4485aaaa7a1e1ed0a8969d9044b95ef5c4dc3f67302627eaa435dafba395e50964bc524026ed53b9f24bccc14f24a64b36fbeab807b5fc05430a023516436f3c79a5f87529df0437d0292918221ebbcf2a7b73757d4b6da401347fb8f3d4c517040d80ead2856e4af000b0f6909abb4c2cce71397dd6050d648104d1465d33b12d51f3ce897327888f3072fba4834f3836f5798f0ecd592eb70913e734c8637a81b45725001fe42430f391e508b0c38e99e7a6a32134974834d49328ca049f93865c441784fbff2b6038ddaf3548d7869d8e72babefa199b21182f3d1aa4881e566f0e037b859729763d5e9a38f9b1991550899a9025804807d988182a16238deb4988843ce387ddf9b14e128fc7d53c9e11f44ae0fff90cc8ff08fe515857fd20d36fc7f5d7dc3bd593c1df90f6c38213932ddee639b57f32d3ff8dffe9dbfe22aad0f027dff547de6cc5a436fdf753e646ed7aff4b087ccfb79e9adfe2b4da7f4135766b52ccb2ff75ac3c812aeeac422ef7056392a37e05931644e94930862f739dc3e808b58cccd7560fe68bafbb444ac8a69851ecbcee82113579179e8f330f70e35abac5fd879c4463152f11c424ac3ef6db98f767e3cc217eda383774a89939661aa88f71e687b8ecb55b12fae8fb6410b09a4dc3b93bfa8d6e7eef0a6c3057c97b69b7113d9d2be68765858eca176c8d7a27dc747c06df1eaac99753cbb3ca749ff8ba1e657879b6bcb31c7111aa6962e8d20b0ad7e8b62c6cc2738e0c4ee0657cd64fc0b371723247d5d75701f39cef0783e96e28e89060b3efea453b8b72f3180586ec599e9006348d834d3d484a6b03f7ed368bac33d391beeb0d6dbe33ad789eb227a232c2cbb599a33d4426cf65abfbae9cc4011ba7c4d696ac94335da14bdf7354187ff6640eb0c03aedc05334f4b8ff54162ff1860e7bb3eef63b294f1dbc27e59940b5a1650340fcf4108e6a8db2caad8925dc0d543d7e2a6bd1cf9f12b88aa90493339578c9263947a9e4f1c9e58d205d5b17ef4df4a10f290946f0b9c309b15579b26c2a69fa953a96b8732fbb5930d5c3825f0479862214ef3e030d20295f9493613e36c2a607f48f3d38d88e1fd8f68d880aac5c6de553b821d86438974dcb51f07d21f82b586d9b588a3f6789082bf8cd33c82da6f0490a47722e0fe089b30f81756465d6ccaf92dae3f52fe37e04cdfb236851dd15cd75d9ed994197a35bcde23a57bf294ef9d0db254ca903147b800d051860c1cd897d0801e95bb39af1f3fdf5eb303dcd49c4e529ec68ff724e8b8fa75deed21c1a90e6c0a3b25b4b82a4d635a069b570fdfd1118adbfb6532d21789c493498aa6fe7aebafd70132e55e0a29f7b502f2dd04bc0bd9595f52a5c5e783c4429ec56c9cc3ae1349ee6bcc1ac37ebd7ec3031d70f70dfe3c73d19e7e811d10d4c6ac0755242f87cb5535a73907ba8328139406179c68f165f70d626add7f1b080422f8feb7e9efe49497b6e9d4643b8bed31ebcb0850757ac0daf284d3590b3e8bade5b1b3be8b333db7d3fe6bd65cb439ef897447c669e12abc148069d4ac694fdeec3eb481426e0d2cc43464662935ab3adc2c05a2c054cf8ab60b13df02bf5c0f856489e20e8a0afb9a86be6a5aaeed7c62819acb4fe436e078933ff02aba058dfe27f547a75ce0e0b2eb41775c70319677eab4fc0305dc174d09086de7634cf65772506571b1e0889872099d65137219db1d236aa7b47d14a63becbf1aa2e3523d253f52251b6cc472453cee7a26e5068dd3c35341683288c3f0f5a16c05c080f820eea0437c8b686b8bb4b6f84052d78d03f86acd2ab94bf3670fda8c80d46f389dcb6867143ff1611c1b87d5960af58292ced48ee07c5b667c281281901b5beb5a53102dffe34a51fe2c887557ad7cc3a0d625b71ad34a41eb6dbc8f42a4aadb68f67ea5fde34ad9899d3c3d6ffb3b1a1d874c0a3c039a7b6f239d466f6d8eaf57a1c88ba703f8342f3ad7edc1264d907239cc0886f899f8ef83540505777831beee5aac665667cee0d0f205c1acc6accff388f3bf18f3e20dcc64cb47278e46c3f0bff3a8f000fda817dd25c6cc36bed3962c36ad07f493a206b23fa915e14615c8f9fe92dd6a8369c5016908ea2fa6196f0e60b479a07e01891bacb58807d9a319a643cfd8e8c3f4335c6b20a8adfb2b14ee5e898edbcb341f9beadd4567856cf3f01b64a44efca651273c622deecf26b6e9fd26da8a6ec94efcd4d2bcc59bab9b567b71fd1ca543a344e65f1089b815812f0ea0cbd0ae214f376e98a2ac6d1627e1adcbfbc5836b7d8d3f979a5e4d07523ee08db3ce9412cafee6aba2df4befcf5bec44c8d2437e45f60e4e67561b80c1a8fd50c0d5b73ca250902b895ba3f461d7c17a91164795234c2fdefedfa2c14d6ca9e43f40a98ae029c35373bf1b5199be708524487cd58fb0bb4c148a5ecdefdd20a33104070aa41af5b79ec12a34f813b9187b7149464d7a7d9564efd72671c0dc44345dc9c5866c9228ecff88c844166fc91b326df05565e44586542529a30427aad890956f7950c4846521a09c7d182f13cc15c933bf239b88444c77c1503b4969b59938da954932d46cac31de9da1097a37d8b1f5e552944fb596cdf0d9d9ddff39d7a023f2397f265dab810cfbe47d186dd9077b7858a85a278ed8d44f31f2c7fdc72ab8958aa3c5f34306c376548d27578d4e8b1313f7ff51b4e31ec461529cff297bad1d403100b3a220d1ca969d5775167f1a3fc5ab043126ecc500259afd739b1f62da78665934ce35194df68c4043ed5d39280dd5c1fd449628f1ae0bffd2809083321bb6ae01d2598869fc15c8e6dd0311b6129c01530ec5e2986478426004b9e043a30f21bd579552e91eab24a56eda28287cc7b06a843914af3ed9c914a9d1fd017018689cca39f3b1cefc2f4ec1dae645463dfbf1ce309761cb6d0abd60e823bda116d72c54c2b63a30b8dfcf27d16de26984e5ad194da218578d0026ed6663aaad039a994714785fb5ed6732c0509fdf15ae0a39ea3ed3fbd0dee2d78254cfa5753f49a93205382c17970cc38fdfc65cddefe2e287bdc721e647b890ec58d56818a3b8d5fc7ab866fe73b1ff6e9f45eaeb673fbf813664ac0d99df0fea15223e4aac022a223f7f19e497eb3950d4290163a7e05ceeb73084956b45a76a560b40f018da71d47256c34d1ba3901aa5a9df98b7da7a6f1a4c1c6cfdcd13613d95aa03d26d2400b46697e056a7fc5795569fe345001bc6bf6fb5c126c8714862106f6df1fb2108a2fc15feb51a3b434a5881db69cabbe309b5baeec4c525c75d178d2ca46a57809abeae2fc4b0435f25dabf43029ca40fc602a04ec4e16f7ef93dccd4b7ef7d2a9f31406bc153cdf7043e39c0d79c93f9a12a9149ce1827381b4b024ec42289f8c1412bf193c8f0c848e7f50d0b55869d13fbd7c5de3251da73005b61b64d23d292f393f9ac3a4f2d74fd708a52d70be414480f98fc91d343934bd827ed82e6f75880431a19927897a00e9984c214379489b92c90cfcbfb6b83c713c2661de7bb1dc5de060829f6fcbb6fca36c477105fe2c5a872862d1de9336a1d754903c8156c6cfbb8aa262794db6492a088e552f1d32a737fe0cf465b49cc83734d6f929e39866b1d64585b5d93d54021ac7bc235ef10846def5fa2e67caaa7bf735df7615a59d61d81daaf90f3f25be623321ac9d25ad316ff3dd9e3fc945bcf22709d67f8d9a0ab2ca7268ccb05ba147d4da3bf9af5acfee6628cac23609a5ef4a15d07fa70c3543df0a5d94d8e57d7347075220e1d2969e0d82e37f2eadac42045186c70cb0a6c9ee9ac0d00f087772cd466f1d0490cd10e2d5de7bf9121c78e38a64cb2cee5a3592cc4d2b23bb1e5cfcbaf3362edd6f26d7ccd30b02b8f9200cfb75fb8b3e3fe7898923f60ee265b452fc9c222cc954f91a21e9c093b857992c9eccf09a0d991683925a348895ce97c7e915b95bed877b5acfaffe297e3368814e2a7127f81805b2535abf9d0e46ceb68ab83963543f9bae14d8a0f65da1b8378bd65e0526973e0035a0415de0f12d4537e37fc463cc2496bc81618f58dea8506b79dca87e84ae78bb7e020d129f4618fb812b585776165c11cf0364a58a947bb3b72aac151f46d040fddda30fb9bfe9572f5c999e6f1b449d4dd1e580880af5788bb66a8e0fee70b685028d965c7fa7a3358dc0f1c6d1df4aa68c8609b3dbe25087869fa1124bee83e609d27ad088eddcdedfa4c93142ddf70f9e8e826ef9ff3f9ec7e9b800b26dcfc973a435393411d1234f074f3a2b038eadca0ce7b8614b5ca8063aad189004802b77d902b04e73163be4a94b3ff0438cfa0bfa4805854ef943a3fab4e4c0e23b9e4c6be70443c0e9964d27a51bacd2645bff374ff5ab17ff69e61ddb56af0175ca8c4b9180b29c0aa35317b13c04caffd35e45ca47219636b9ca0a366536ccf1e3028d220fee8be4ea6868c21d1017e9faf98cadddd05c6b5fb8c6a9b7a99db4ed56006ea00b81f2f948276ff6fecfa639c30c9af2adc9311c513d5678f8d3e9455a16e7f929b350b7d231b8c55bae2e11e6f8b8a91f4dd0e3ab66d2df6669fadc09a0280b62768ef83bee2edbe5cfedbdab897783a75f0bbee2ce1e0a423103b9be354bdad23dc86bf3b69a9a833a004b312b46b3fab686104aad5514f3fb7c5642816f5640e425d11435d5649bcb2ac76738c4812cb3f43dc112b73ac8983360119a65353f9ab22850e26cb7369f22cc3d23bebf15f83aa94c7bbf513bff06209a56cc61eaa1bad4f5e4ab73b76686d8ed1d3d4648cd85f478cf6c3e475a2f55e43450e0767a7afab2162ea763b078efc3f3452b9c49115f68aa1af27c32a2e896a15dfdbe31c8b3e42f1fd3da57cbde1d086266cd158c6355bdbe4e5379ca5dd5a6daab889e0c5a8c378fe0481b70cba449ea0d0abbe87587b0800b2188fc7a7c8c35f3eaec7e395ca5a7a8378bc2b9413569f99d5d99c9ccb1b640e5b2d63cbdd9536884c5b5135760a415a7b92f8ecb2a3c65f73ea51532b1a0d79f6f4e06e75b7fd62fa819a2f85df98feb84bf1c4a8ab2da1d957d82987c41d0284aa7cdbaeed187f83014e16dddb2b1ebb2509d593c3af09fe6530fa237bb84766d08ec492bfdf4ab8e3c7cd10bef2de148716216e6b4c5b76ad562e2e0638c093a3e5ae598a180df194043729bec82a26819f9995bf7f9ee1333b05480339d745060dc0e431c96a98de76c1bc396ca3597493c5bacb3029b85dc16877603da06ff2c08de34a9dc80df64a9775cd44eba02c35216ffbbea0d16931234268c4fee4108a0c1ad2065c08f3baf8a1929e32491aba8bf3a3ea09b2ea0c7317512135268c3bf3f25f2ddb2008000aa274230e1d0a228d459fa922e9e021a134ac4d441f8ba7ce6c5675c3c0db86a6132dcd33e4bf7d47c35420f43b16d4f7916fc33a1791a6f7f17813441ef82952fe31806c68fe2be15ea86a5b77358d1ad794ed9a9fa1406efb0e41022aa68b4cff60fc981fcd885b9e44541fec90a534504ac3ccadbbac970789170de15033ef77231b70abff37c971ea31247124ad0079a78de27be4ef8acd47bd67d2cb1e8e06cfdbf19874f562e063ad21ca0a224dd62de5371233034112a6c9cdc7744e53b6c970c427caff35cf9e5a845acdaa6eb2bcdf527da9240ee94519395c6c3a4ce6f3b7d0922e76e73b551002f671c0b0ff5c7dec5c1ad294a4834541c59df6393e830cc759f5ebac3423aa21d0e2f223bab28c51d924b26974182ef54ff6cf1d15504fcc1b3dff5bac460a66ba0314227f73f170bf38c83ea8120a8daf4e9b9da7b4f476575a58d982784bfeaf01da594be3eab343d1a277bb5f1155cd60b902f0af61ebccdfc1128d6230191128fd234f57c4d0fa33c362c3733eeb1e33e42e3f40add179107dcdc5b2985243436f241e8e7a3daf6eb1e25772eb4ebea55e9307249c0c5f429934d4d25c2978cf76f3737253d87a1eafd5c0c9c67d5a4b3b82fefe5551876dc832f8ef76e7ceab82479d7b7559383acbf1a1235de3c9da1913513ddabf678c2b8c0bf60ff704b95532bd30be54df664f3677ca81f99c69f23177f087e9af704f1d57f328b6560591700c336775c398f0077f91a370ebc69583926c45e3b0c79c275b540f30804ac1dbd6cafe7ebf5728bb0395e4f7c94f51087163646641129bcae84d914201c0fe6b4de95de79bfec3e068ec3d6b0644f846373bc27b1b55afb07c9364b24dbcf6c59810755d2b1aa51a766e8a1b2398e216d26e19d548f171b385cb0f958af5f9ccc9cf5a73562ea918c4ebff1f646c2bcc5f09c9fa8180e10ab8d295b1c87cc10ed9086abb5545bc033364e7d1c708bba2ff86a1751404fc4b062be6ba0857899d45def2c0f7341bccf3db5bca24150cafa5f51cefd0922f1dd0f9f1a404e773c6109cb410e56cee743f4a50584052aaeafebac4bd7c615fa0db28783ed53905b35b90439587f13ca74a4e0a3382bc135642caa20a1b471be668888dfdb7c868b3c91982028dbe3f16c479703132e8a1e95684b85fc63cb18f32d73e4089724403d5bb195600cecceb70c89d2dadf155f164e6afcf582bda6058207b5c308751afc8f16b66a8ea153d20082acd04153a033972878192b5fbba884100e1cded845f836afa7fe3f3e06faa7aa1e33b53b6f5c206148aef9afc422c3589e50334214336dcd009b96ec3519572b0dea98e985cc5697cf13af12cbf6c662348c494f65788092cf9afc3af8e84c66292998ef3328b69993b2a7ffc4529e86f54b7d2f31e80d9393a61841032800ac9d6ef568a76e621015a49af8b7e81001b91a2621af3366d5599497a8ae3945871bc95f6ff366725a6a371134f8bdab704dc83587bf990abe92bf7279d39406f31f2c578a4cc58dd2048e6752b462c1f5c3b2d73078cbf27daf7f0b2050a92fdce78cc79fc7b6fe137449ef2c2428c88876e588f270e6a552230ccafc3eee4eeaf55affc1d4c8cfa4d9e495c0388c2839a72add0c297f27594a66f43b5704f7eea833626a096682c3cafd0a6da3a0e099713a30aec06d90a80616614bcf16ed6f6ae323bc5b2ee5b413ea2557cb79c816392013ef599b003a0c5e76fd96f9c49a992c1487a81487a7b1a79128843cb6e4e32da4d47902517e980d009f624cc09639e2679781db520f204201fa1c8e9763dbe68b623d11f774ac2bf0716e0165357ec323fb458e919cb01dd0c9fc6d0baff1f33063600d7c0f8c0f5b7bda92d0cbdaad00b60be59f82b10a7e45837365a32991ee705b4f51139e49d1ac06570e4a7e4990b20fec9f2affa8af07412d89eaae2f462a47266b70d37dda35b13dd105ccb9f6259838d074b6ef52ae07f5298462088b8828d6d9f15d3b4ca91f2406010ea5f2512eb7eb6bd92893c42d971fe42cfbf38278fa4ef8cc83003061a6a33bd096d4efd84267286ec35495ea07b7b9e5ac4ee33a23598086d7f7704d7504dda8f25ce9f47dacc4d28d8c4cab17592174c08d58455fedcda03edaf0183748059aa9db57c0ea66bc17c1fe283733f657abab483b276524812e02446007c426f387fd0b40a9849df66d551d45be719b8ee49d3be6d2b6c43da9a5ab296017182c6dc47d48eff7513675c0fa90ecb845fa30dfbb3846144f5c0b222b7dfef0e10a132194c7743d24bf59c1b2270c2599bbfcd9fad3ed4ea0e721b660d3268f39b1390d3ca63e5829c35bd962a783fae45172028f19244a585964e7dfa3a9d50b3064bdff64be5d3113e1745b1fa6359ec44ebf67b4a6594e98eac1d2c632909a643a8ce181c662e2a5b8503ae583c8a009c281d84b9ea2c52601dd61add819b4e8e022e574e430490701775bcd5daa6969cf9e0936288f2dac71ea639214cd968df23eeafad335733e016054ccf4f1c77fc73cbfa695c343d4efc7f42654166b09008acb902a7c3c9b1037208409b33676a29dc6bffb03f9c50fd6f0b4ddd37c1f553d38a6ef87865b4dca2ec52c134c405f649a8c7ec39809f4201ac2a97b21e11ee90e679cfec1a3cdc0281fd3cd596415f1ba9e56cc6c8169c99620833db3e8ef6171ff2ea770a5f83cdf30701ca0ce89fe778129b541aedece5c08274cbdffa7f0380158e1eb3daa47af2f27ddf0267a86414c41315ff0d4f6818bcac900a5f6b2ad6f4f0d31f06660c87415e080d477fcb8086316c960d7975d790dcdc5c0f5e4043c59efac807ae4f712f498b018c64f85fd5d4170c15308d0fb8a4443ec26498055cf29a7ce57836e167677593b803fb3f7e24e74b1064effc3cdb11a4bf9bccad81b8710e5ae6249024b531c3eae28c324df64428ff638179b979938cffdbf2ca432426149a0ec7a3da2da5e816959c0bfbc043c678aa1108712f19de2bfb3513dac84986f71434742d5afb5192c9c8b79a79e96900f73ccc96decdbeb3c61a0b0c657f70f30e06f4eb758b8ba059caa145f8dcfb2e09c0b991af4ee9cb3040dba49deda930463ad68df6ce5c75503caa85442e771a7f7f3319bc4fbff3266ec81666db0afe02f701667b692d16d76d57457a483aea6e52106aa980014bbd65333d8b14e66405dc3f72b43fe12f6022d3f2a484ef1c673a3d1771a842431fdadd199218574fd8e174c4a42200c916dda7ecffc2ba454e0b66a05b5f51222a56beadab45705acd5afa21780d189ab9117abd797ce109c7a5e92f19957fe730a27b779acd414c2660b62fb7d48e9418982090beacd8d731a1d102d6763fbaa8bbc0e4d78465b64f65fe8570962c7b86b9e62fab121a2743d369249bef81523ebe357a06defada4efb27833c2003a5cf8f7f18d85abb75c8a26d7ca9371608a771a36f6254025abed60ec09abf0ab752b7ffe8e7317ab7015e27cb5887b37196495a400e4e8dc21fb603f92cbcbf76783c714fad6d96ffcbfabf70a51b8365a6069eec4f22e25027b3fab91a58ac5cc71ffceebfce570172051bdbbe5be38183fd93c4d18bfcc3795008eaa1ba1fb3bf09d1766e7c563a5bc06b61f2a7becda7a33c0b8b373b7a024281c62034a3809936bb483a8494aee10b0e838630e16fb6b4c1d669171adf965c8c37bef2f7bbcc0515fb32a6e8d1749ca1944ea68f6f6eeb3482d377ea93b5a532da70ab6f74718644f868ed5b044a2efe2a06f06a58fe53667d4029bbb83e3c3365029f2d84326b9fd24f983d9c860795418201fee77638775b3b63e3215d71c69f3b3885f1c447df1fdc07eb0952fff0ca52b07e7c154afd92d72f610f2b3f4f029684bc56d302ffc82291de902ffc438dd52bb59a6af14c75fb1bd97240fa3f0450a17763b20de11a0fda1fbc122bb3e1348be2b48b292d3f43f9e8cca2559c7fff784415569a9a3ee3a4ae028a6089ef50bb40d1ffcef64e33e1bfba22af21d027506845f884e2148cbd644faad5c0144b78f32590fda9c2f0fb2fa0f09f1abfbaba8c3436fe72b09d219f98d67472f9e217e2081bb33780e7c31222cbe421ec77253c10ee7df388cc45c2a7c1f1b307e938ab28dba235f6a1147f8576db12aa79dd0988e69b83742a9c840c6359587f55e84ad0e0d569b2bb44765b27f464862ffee9732ae3b2174be103d09a6c957ed4cd3ae3a62e2b92f98e8bb83f1e1fa0fdbbc02876bf2b9d782a0c3272e200daf027d316e1bff68f65cf102b872e3571cce0d3eb5ac40af8cd988446185e3a9c118bb4b36507cebcbb19e01c4768c95ee9ef42ab0f5d88df848ee2a30e8fc9650aedd672c4a5fb3b2b117e0143fb5d89e5b817886f0121607f1b1f1730a9de4682d2fda3a2c08fbd0fea3a456b7e9506d3ffe2ea7f57099c83bdd54f48231537f2cb4f581967e7588ec489ad9a31015b5d8e9d7f8e2ef2027041da340193ab812ab7d175a5571a035ed7dceef15e524c66e34a4851460787612c3c369e8338a9e11e5f33a7aeac00763b66ead175c723153da65e64f06df2f73e062f8fe4ef485f44aed708ecc7404b82eeaed9115d99ed06d759d72e99db9d5990cf51ece8d5c2315a58b0c531ceb67e3422cb4279e1d02b650e05936fea7d4f0a5c6f0e9a14722b7ce788f3d187e6ff1feea1f0b3f95488fa5cd59c0c32270ba2a221b883188983055e913ff4e9661ee0684c93382c1f76e9bc1daccf22816ee547c41c0c2d56096e6a9907e15e2f199ce21b9f6b466fa3ba29291e564711ec4fb399323e323e0f774f5b8eb76b04e0c4493a047d3ac5ead04ea5f18683f0ba98aea795b8bd945cc2cb12a3e779d0f08b3000d028329c7e4f88933da4d936d0c947c19a407a92e59523cec634dd32027ca3c93bc06f80fe1e1c7a94ca8118e6b943910ef598cd79bd642e3d9767f6d72eba731175f347d07a9dfd3b4ae3f9749ebb66787c64414e61a68005dd67060eaa1b33b3555205e4633e57e4f542f5fec7179d73cf92a952c8bb2bcf2173533f27b8348d1b5950cfb8c26664106baa8b2ffa9873832cd5e4fb233be1c708c5fe61e6c7f01d81847a22d50c877fa7550a9ce813b5c899af344a80dc879a03b13095d98e7e3198501ac7bdf1b8964e65e5be38226d3e4f6c6f07d9225a569bf4d065ede12da69d710a03e766d010ec9fc4f94f9c4bd3f888233b61888b9a94531ea61fbaded635b9168efbb79faced3fe1411654e76bc5cdde0bed3edffe8bae651676d4cd0a8816ecbf4b9ee6c374d5bdc37ed2671dd0ef9067a24b82796f0521bb75b542781bc10dbfc70932fdf7feae183e23ffd53caa20f2c63e816e10aed9fee04305dc73d792e5eca3b4dda060ad1876a4c313eb68967401cfd103df01e1454393744e07c68895dac41e681d95410db0a93d5ce8a0f7ffa70e03b07e9c2e891deb44e318f0a44bbe5fa4d81fcf1c3946dcdc5052ec7641fdc16c3f21f1ffdae578510deffd47ac32cbfafa06008f3f6288b40eab71bbacd0939176589cffe54db79258dd91d4233b0f68b02af48d0aa1475be2158e147b33ffda3c9e47eb12b3a3fe3064e116d315bbe13604647a3c85686fbee4ee4dfd533fc07e93e49afc2770e4200c4fbe9a9abfd646cc7b969449679b50929d4b0a7d7902c3a4d1ed2be580293a976e5c6cbb38564fbb614569a6e3477930a2c74f15512bf9e2d26f1487b4a10eb8d443acc4048a97a0fd5e291b2bc89e7649cf039d94832d18acc81ee5e86e5c35efedfa69c142822aad47ae78e22e0414a2a025879df42656cbbbb93b5c94523a18ec7614b4ee77ff0f6cc10eb4bb83ecf08a790058938841a5ea5df836d18720d4254be1b38bac4c114a8ca300a7c3fb3927d73fcecea764c2325a351135a29a0202e4496c21993bd220215180bd2b013b3e2e3b627bac50edaaa36b00d63cbc35580db0613abe13e1656dc02652b1156c471b1792701e4942f44c47d8d1938ea88366ad6907b059f0708825c5da13474414b2a0a52809c26852579f54d3b8f0f38f0f53863e0bc091ecd6020fe58764c5f7486df2b34968a210de526557d70e49fd35638d920d12b7abaf10e32b67abed368fc7dd436bf6a851434b95abcffd428aa69d601dc34aa5b6a9e398c1d3d93bb01be611343b70adb217fc6faf8381db3911f30789b8c1a1785e99a99cbb515f1ee5d5bee3d57f754a696a31c8153fe6dff5e11379898469a6d4f62b8c6508178ba3595065798e9016a371c77cfad6dbbf5bbaf5384550abca67b0d46e447ddf3ac82dcac7b1b3b99b74c2f2291352f07e78b02fd7fbad2212e3eb1b0ebf9e203342125952d40d046b29e322ce50258d4de3159d0fa5464aed56179efebc2677029a953734c5e4dca0d72fd5be650d6e2b32561fbc1e318d7a1d3ba4fc56008c4cf170bb8d61fade669a11a44465bd9d285a56cae61f2351c3d68aac3c198e8aa11f604de1859570148ab960b26c739c684f8e0f1cf9efdfdab2bc9a69ecb89559b180c53e13a8d8356b0c09ab17541194198235a51e86c9c96456b08a367a2c8562677d06dfefcd5718a40b8d34c8cb10a856dbdfa61e88ce5aa764f955173f1dedc5214943673f958436d7f0a68d4902bce33e2ddbeac39e5e05348f2220472b8a26200035210a8ec1b6833449cc1af1b474eddfa7efb3f1e6d2e995dcc43e44eebfcc821b3efde9e447f313ea27dd35d5e37d3ea08d4495163c4a6a414d2bff4c12e575de0195a93320b4ce12f5396568ad643d6c9ced0d74eada3da400fc91339dd395d2f266c9190003187acfa7c1a3c33d8ed5d53dc1f15cfe9c8eee6d64a878f3f17bc7646c92bc1c00c855ae3dc56ff644bad929545bca08b5da3244bc728ab1758fd7e4ff0f069b85da46f0337ccdd9ad2601f38d01157bada0c6c1fcfe4a7bfde9d27e3aad4de4dd7b04ffe0fe2d8b55da2afbdebf0c6da32d038fad8d4ff5efb3e2debe80d23debd900c1cc45318e2a64ddeb0ecfb51c93c18213aa9fd766766e42ab79770f7fa7f31201325526939ea0de85215124632bc1c56f03cecbee470e326e4c58757d55277d08bf79e139210dfa003eb423b53f3686c6d8b00c95c36e736a4d26d91b35f32d5628baf1594c377b79880b9f82e439085bab56aa33eeba9f9bdcac60e9410586e8228b2ae21ae2d3cfc4eb598ba0e2103bd313ef51ceb4e8f781b25fd516b03101bc39084a6ba1fe6f2ee48208e4322484bca1988ab23f48fd7afb352fc5ee051a0afb54b2b60254189dd188cc6679af3179ea430b5dc2e6f974f4a69895ca35dce5927ba2ec52930913be8797617e285510c2e33640e44b8284d0501babe31c7d4da7d11c4b89b1816721440a845ba92cefbb98b6baa4bd8b6e9eec663896f3ae54252e3bad265957ed14b24ca2df5a700e38403c6ddf3efe33165cd1803602c12984144a83457c8192d0e448504a45f0dfef00a0ec4660b4ca882d755bb1df69038dafbf142dbc47d0833ceec83055d99975a16d154af19c188c1953c0afda2243d20f4b2e9734e311b8b2bed8b5042e750f74fec50cdbb82a04ad488f0671790105cb395f896e9cf17c28ec8ff8a56ef7b8b05156d7f0f5630c6fc2a726cadfe0b21ad7e1ea6d1dfae4dc201d46462275f46c2d19ab0ba5c1cd46080d2095947184f67e2da8ae8d4c634cdaace19afc2c4864193ed6b52c739bdccd2f2ca6cf9d571215fbb888b0cfaf25440e59ba949d7bdfb205330aa8c3a7001fb954e9a96b02fd93d4474701465e44b7af35feaa42b8574ecf378ba3ee89904df4282d5cd518a1390d598f9d16410232e43c0d4bd593bca8c3da15c3e61fcf9bd14c20bbdd4beccf5a993882de5540de090b5987b77c7e0f36c6c25366c42099a9e7f002fabdf32c08b72faa4b7dbf3d1054b95b465198e89ce25bd5a3cfc553cd247a6b7a638d259348425947d7168cd57b815ad18f2aef803550e7012e2d434df508d051b871a7f0e63c72a1ecbb59ad74a75ccbdaeb2d9d52d66b0a1c30c8f051fe0ca6d66e31a23a7d9b941612225548a46ba22c267b5c57bbd2f4c65986ccfc4135ff592936a5051d5b6ee0b8c09b90990660a078c0ef90830c31334119a20b7d8b8233f27d42a9a824e8904811a0f747edeace765c521860336ccda4aa9a532f3ca73100cb9638300d1c0ec10d220ca321df4c17a3bf208cdde6e4c6ff7a2adefd004b4d6ac7958d642efa8edd12eb00a35b08e7cc05d91e5b597b2857d6a55125319c22c4856ac2117f33c9ef0f633b9048bf930841c33fe139c27833f978c4ee6a575e8136b53c38fdd857534b448b2594c3f9030807553224169ce2d2ff6354bf7b3f2cd46c9a782e2c627f202a5efb58e4aa6f27db77aeaefda185004042bb736976c25d55b3010b10615f7857ddfaa9e87eea7b8a45ddb849ebfe19eb00cf9e739ca4d0a70a635ba8cb94d254c185fb2f448bdc973c90c1cb59597bbba1ceae21a5e40a3e97acb54470ae070c50103b679250bb7b7d095aca38be077b97cfafea088af98c24d6504824f578948107338f685cc51b571879c03fee588b4777e80a1cedd171c87f5ecdcc5ea855c30f4354c3be97daf4b5e0b38a31652f6245f7fa2c309ce447331b1ee7346fce7960316c7b63cdb64f329bc811598b5d8f10c81146610f0bb78421f225daff5aae7ff6b9a1548aa078fe10fb678619b0ab08e9731ad851f2dd2c6a4ba92f9c3366553250b1c58e23648679fa38bbbe3c23f0e73fe97c255b2ffcd89bf6c0c86bd7bea76aa98667d5ab6330ee7e8ffdb4c4437c79cceadc947d0bf02faafd18a3ab62b6733fb6bea092b95f5fecacfff9a61529074edd779203f168c44a568a85eb38872f4d01a9478025e6b9ec4bd392d042b20a1524293717270fe8020a63997ddcba18043ac8a92eb9abadbe00cda729039eb77240b75848daf2112ebb94a3ad2bd95f1ca4d4379eef461c536fe184e48ec06a1f4e6dc9c2a786462430c460876416ecdf549504ac4e17e0262822d0dd84e8f812902e3c45bb0280ee1d089b9383b9a2d67b0bc2aebed9bd6cc790c596177688da0ea07a85dd0cfbc067c9eec5beed84f68f5441a545f380cd4d79ab7d91d1c5d9b841bbfdda6857ab44feeefea9ba4d077c0f309e70c0af68b5078440a555a042702047aeafb2ced70e70de06542d69754144f089b10abf5a46f5f7cf9645de824091617c422afb3e0c89f1d44942c26d33c36f9d2eb27f26a22c64f67bde8f2ad7ef898f8e29de6698948866840f87c5114b7fe349a149abc22ef7ee188e3efce1062d3a627fe07a061a3cd6c134860ac8d8366a924f0a5aa3b957e8195ff87231c023b8e97ba0f45422d7e6516af714787f682b653b36476de918557d90a413150b71c7125b370d151de36ae9108fc72448a981c315b85d901137fd2f727459c5f93128c6dcaac7d70aea6db5fa3464209306ca40805059e3723adace4d6b1b5e7186a0803e8f4ed72782b52391e80b30fac272c42ce33414875357d43e918a05278d09a7c7fa8d939c128c89224de89653a8da592cd38b53ebc2c971ebd3a09cf15a972d5f86c7fbeb80a793c176d52cb544e1872d6a16fa6fd98691d7a39a189c28ce364094c88efedf2e658aee42030e57e06cfb9c9316614d1ad4f71134bc82bf0ea78c64e1e10cfa25b23606f6bb01fe474be850307b1e817846c614c5a716e360f103de9675c4a9794c88159d9e71837e707188706ded84e84193e77457929f9716b4fa97517693b64f0dae216a080a659e8d96aad72e976f8cb38975790f06b11221930f1d1e8c31cdd6273f1415214f51a5f01b388311c0b1c40693059d1ff19c45df5c2b891033cd0f57b13d634e08392abab2fb1f4dc180989e07c30688c82ae75bd5f139bae640d46529dfa6076afc839b9ac97bd250531f4395e98671477ac6a62bb608d04b41e7443cfd65772065267e716dab3cb18a2b7941aaaed8f643eee6ce4e35ce099c667ff1c04b3b88f6f2e397bfe94b608cbf3c3b1396e61ee828362f3a2fb6a7933a7c0210ed3f635d15446349e586ff46c8bf1d52a1df1d3e7173c2aed3a9a0e3aaf62f54cacedecf611f1ce6e5a591c70cc2e1f9cc98a427cf52fd89c078cf92770d38751d3ce697b0f8356042962d8ad500abdfc8f8192d20e1bb38f9eb62b74fb7c60cfefee80fb881658e06267ea8ff8fd7bf4ef41c9490ab3d8a38a7b65cf6667dc1fde9ba5e55928cd3b3d9127c8256d9c6796d69c447e57765b5c8a5be9e4e564da609f60945e84ed9288a4450df5017e40e20f800e7d3f5b475cd26fbc19b8bd3bdbde02930a66d6d94d4a18cb41ed7e148b36fffad9bc46bf9afd337ebe4c815bb3fad59a53f2b40428f314b42aa61a149795efad7087b64e9a381760e62e706fc92cba5d4446e800b635086ee21b7669538eedded1004091363ef18e4bf29dedfe30335385fafaaad79cfa72bc3f0c3c9c7fe3d67da70710f17d4fbae172b3e1fa31d8e808ba885010ca09f5996e0f7e02689ae4106099dd73fa01866a2fffcd79a6a1160fe5f9721600210c267e5344d6b6b0c566bcc53f991c0fc2eb5f955a9e99fba4f093e93be11bf52ae55f9926a80bfc1e5cbe56323219d01cfb0d9df195293af3c839878f5fa378a078dfe0db20700f343522a643da66b51dba8cf5d00589b8ca99b9ca768d86a4ab57f61a7e717b10020b420c3984bd92d175eccaf5ee927361a855c7ba8abd9a6ff3417e24b2727ee2109dc428651704a2e637a7cb4eb56196b0818472744e6aca0c9331e3704dbdb41c2b5d91b5b3f15be66369e96db53de1b7787476ace2e99dec402412b3f0a309bfeeb77374e49fe231ffca6892ed512a557e06d14ff22393852e7529ddf4d6436b63c807f8a52b834314eb20fa2c8282e834c8713196100386deb078f34f7b75d5f37c854bae3f0ea874d79431af17108c52fa3ec7b6429f1ec96cc5c10abba868833512b34eaae1094bc43209d601ca4421d1895d86eff666805df3be9857011fc4148be47d637de2a72d3cf7caea7889b7a0937b8b0089cf84de05b85866d5da4c395357e24cef937bccc16e018978def527a855f6effe840dd2d39cf8ca449267862edfa8f8abc7f093fb351cdf93404897d53c1d0fb228dd87bfe71a3ce9957e57eaad9f6777261c075608f177e4bb7cf0b92009883cb5be147a8f8532dd6e7808bc3af3efe1dc347047217e575b6b479f64ef7859b39cbbaffc9a469853b7a27a190d3e1e3d7997bcab143c3217c46824b0d074357d10d8d3dd311221264bcb40b0ecccd3776c7e4e5b694adb2ba9dde4bab30a9542bb337b42024ba09debfd472175cf8c6927ca447d55fe7630f2a4c55b8cc5188c60261807f45ab4bd3415556ae19234a560624eb65d901b906f80c2359d598f1c3165f2f96f22fb04f1c9a62b69efe5fc966c8eab3c686e422bcba1fee26d716a13bb4e0700abd2bb52f322c3c3b4a2219d54522749e8494b9f3edec8e27d547649806d0ae95f358cd33d69ab0c02b6f035550add7fc84e5f96cdd7d7e8f7645bee8e821fea8472c77daecfaed66aa4a409ef0ea9fe0403446fba9ae341ab0d195008f2a109b67f0a1f31781ba3b07473c21f6bd77749767abada3b72d53f8c1e59bf12ba734b86ad970ab507eae40665d38429e2e3c9fb192d118aa6a4dfaea521361ecd2108813d9eff815d6c418ffe179407617c966d1cdfd24dbc8042799e353ff1f47b0e947d2ec01b7c21620327911e4dd6d6c8c0bd8f099a1738b06deb86dc2a25cb2ce07437a7ae817330b90e8a50e25f9fcd0013f0989bf925fdecba76366ced971611229968b77099febeb1d0c5e2c1bd373288c44e78b8966c33e8c68a3e76d0896d33307838d5d49c080cdc82e34f9b5feb25756b8af59972a9da601240b29d2600c69269d09515c2092fd838853722cca77ad1c9d4de126740dd1041c97b0335c290d4ab681a1ce009f133b59db0b561c50575795f16431c7b7a2ac80d67dfffefeabb87b9c40c8fad7c315468bddcf128ce1db57eddea27f0874306135febd73690a11e33d0e2b4876df0d3f2e24abbda4766a7a2d3c670e459f3424abd2dc93472c094181224cafceb5c377517045d20ccdb6b12bed3c492b40663263f4ae5f2179b44cfee1ff5c1748be472e84fff55ff0ac072de9f29c5b16592b534e44fe1262abb47090f84597fe1bcac4cfe62326d399a7ec5c93335ca1edb7ab2dac3d2668d7889ac2e26617951e904d65b7bf2d7ef84ed75c9fdebffeaed11e83f99246b54f32f2ebde190eedd45fef985e49c5a1e2be5dd22f3fc15a0b29dacb7951d093bc93dafe3aaea9a289925080a2d9ae2a02f74f6ff3ca3a224b9e46f1a5f34fbc117f73e467bfdf779b4d1e430f16262f342b7a1dff14078a7cda783b90e758ff35e5375133d3f023de2e7b19f90986d380e644a25e265201c7b1ec9c82bf9b5effdc8ffdcedda7efc4306ebc9bf8facede6a0db33defb52fdabe4f32346e4cbaf0f0133a43bb0985c0df6110a67a65044377106c39abf6898c789e69bb5dc67972dadb67ccf64b2acfbfbc47fed3f37385ca68ed8813b5425cf8626266d4e9dc6311c1f7aafc75b08e01b69fac16d290c316ef4e6da74e2006c86f3a7a44e3ce580880c02e1d13627f228269065970b5294ebc900b3b9d5ddbebc0d3b7330345298b5d3d60f3b3aa548f55bc99960e41baf5ff12bb27eb2a9c0cb9a6e2d391559165c1f31449a77e3e87dd125707e423c483fa477816db519542559e363fabe8b6e5b6f8e32819a4e2dccd10a433426b4de29919083dccff7305f434358400e838ad2a0352859e4e53d5b215aca0f3085dbce194d4dbd3c0ee295bc33b271efd570c2e522234e8685237638c483348d0c0da4e728ea71aae543d8c589844cd3fb504fb5cafb7c59dacf4e3984efc0c2e271caab298ef29bac7ca1be7fbbeef07701083ec25eabe2d90e78fc52f77cc49c44ef9138bdc5af0d05c347970b508583004aae2a1e6b160ab42d98e483e7a7670fcefd630edf5858f9fcfc0c8f0281591466e4690390f1c2658ea8eb966d3db1d2642dc7fc624d2a28540961fbd6356f3bf487234cdb7ea94f5a852fc988c6f6f55422832976aa1471fcb2594923b15f204f0f37e57a5da7be19b1250fff03b9dafb39d88c5a3afb6c0087e037d5fcdfedba51369198cd1c51d22bffab0369df077bb4ac07df835bdde17e50a1ddf11fb541de3f06c4ab156407edc427eb3a6b8672d097809e7961bf5c6da0064eb71339fed8c7dab0b9680a9699cf4e2cfee71f4d7e9d6413fcc89b379af2cec079e434cca6e8bc2c4d8990e88054016b107fc844a6b40a9dcb852c15de61b1e41bbff0d3184a1f4ad06e49d7d31b4e17bab761659d6ad7a5bf77ef97edd14bd2a4dfe654a7bab469c5c70c941aa36aeda9a67a576c3dd7a24002b65670fb28a828de37ef5c1a88c30d2590d313d34e96964220e2d450490a71adc179f369d3241becc58f7dfa26896bcb8f9867018ab6c9ad5acdcf294142099dc133d79c4c510cec93d1f52e492b4d64b8cca5a281514ee1589d47f87f66c030f00cc715670abd7694cf5fa83106f3925131eb07645b5b040d7973671698451377e97a4f5146c4fa3eb72953a34c72e7a7fe50f04b78801cc265e322f5be4f026906683c2fe0d124d84e4d37a895e840ac887c6f12312d2748071b5d03b82151c733026b6fa7e7cb4e335add0264068620ee61942885ff20bd77f441badddfbf7e16abfaeeb606bed5f43700e1bce697165f1e7d03ffecf91895e73dc9a5e8ea0d9873873acde1f3bf1928a6153864251dca17dc0e2041c6c45226f35ef39fc07b892a7b4d2fe3db87efbb48ffd2d716c23211358c79fd300e18c2cb5bd5fa0726a99ee415bc3fc6fd82f8883b5318c68aae2ff387c22f7c97d61219e677bfd7af98acdd18ca2c7516e765cb411b72cabd40726c04428447dd186d75654fe4c3b7eeeab5cba64a7e4304b0acd50966da473b6a3caba33d36f02fef20769a1324a6debee8e118abbc62eaacf414276bfc267e9aebf3fe8e4e61af5acbe9f689fb46928a0368001f8e571c68485a59a01ccc7c6f512cf3ffe7d13f8abe435d76b8b65b2a993f241027ca279a68c1f60ecff958e1fffc71850f3b031fec6db6fe9fa0483951134fc4734deab3968f4b6fe3f881cab6ed8c6b14f7449eb4e1fb4fa49f0e4789ca082d18ecfb8a0bdb65edc6df8b86d3130ff8436e34d45259eb076bfcd471fd1f232e9fa575c56f6f34e112e7e3bdeadefb8284884f2b5330135a4f336813891635c864c9236a494544a79a7036cb59d51ca6d0c210c7ceeab3026149e67dd3dfb56ed23036023fc3a1b9045d17f7172fcc95873cf65ca4c1556f7cfe9ff28dd2b52de87f17dbe5d03134420c52da508835f5c399b2b844d440e37891a676329ce3937c42990b2a65a4dc6a2cbab47129d57abe91f61b2bdca04fdd975bf436ba3e833b9f644af3aaa6a5c99886f3e569ab9ed99022c7cfebad1e92682836fe3f5a0df5dbe2ea0a7217ded88dcc6302d8c890e31262aee3b14f2ad8a412f656b88ecd698292cbb15c3b89e453c9308b6366c9b94448bd11ea8e9d8522f46c9877eada2ea9b559eaf75eb6c673f1690656272bb2e27461c39af4f50388e026a15ab4c500b1f59942336f942a3050c343e01780388522f1956b4038d7135a003b1addd26c531aa6997cfc1911c30ba40ad6c36c6322e53a59d24f589dc9c26715f6fb3ef8049b68a82e954ade0d5ac264a673945570073c9802b90e1d44fc063aa1addfe75a9b314135590cda875e6c3ef12a72292724eec287d224aa3b27445aa1be6f30155897bbb0044ec80e43a2539d5e54b5f6c19eaa84fb28a7c1b4e0b49e635eed1c7a03d20142a3580aa61063972874ebb7b41083511377df70731c7200cee6d9f39ddfabf01856dd796ea3fa1269993dc38f9d25b68183ce97a18513f085907fde3b01756c7be58eea304ab03fb2123bbf37908fb9b53ea52dc77a46dc4c7cd3f1cf580b75d7bfd4ff5372c861fa1a7e22796ebbb113cc07cef66bd32dc05b3eb27dafce92be8a56d22eeffa29132a316523758eff5999ee677230b1d2c22fd18c2fb2165c17fe7ee591c847e038a1801cd998681b7752b15bf145bc8ff7a362c93e526b53eb23cc172b516a54bb4b55427f167eb509ebdd504362902aecc616bc92a46c4ba7c3b28c1f658abd4766ca5549c866078bba0f69ddaff5ea8edfffdd519665f849fb0eaf521ab7818a7d5fc6ff70a894246562e39a7cbd13855e9861da6c3b83e26cba61fffeaf575ac8bceb0e286e7f768d5abee6cda2651549de0cb641c5f1c6a9eeb59d020668966597491caf4251cf717ef393b9a54b06d3c8bd2773d48fe0bac24bccdd68c7654c2a89b66c60ca7ab716cc34b87546b1a1e455892aadf10af84d11c182b3ee1124b88232228d33ea4fe9b7290426e25854a308663e18b4d506352c4e62c34a35a78bf089fd46528d47fffca624a389abc748350bfecfd63b46c6cb3dbb4f0b04720669ee911894eabd7005240d6408c04fe1b9d19316fa05e1502467fbc43af115e3ab7db545ad7f2d9929b29b231f01e089606a04d5bc1dd3d8cc3c63c5d20a138aeb118633bd5806c415f85feaa4e47c7e6546518adf274b61be08a7c48f8884cae5d63f60eedb392cab00a1e049dd1817733e46a15913bb60ab6a35644f4c4d1bf7632ab3e5adfc58e41eb00f863eb38527b46d01d6baa27d36e21de44ffe588971704cc77faa0103e88ae84434de6c2308d7c2cd099723159c3fc33dd5f8e2453a852436c72726bd7eff92f367eafe51bf62f19a66efaa6c6d1ddf050f891bd851adef9bcde541245e255e44e5e4100426f1c40997970a42a7ef74a9988c929278b7236063e5bf793e2be786190947291bb253de4edfd75c2e3693ac160b3dc91042faf291619fa9e18c8992f1f828c47e3f33c57ba9ce053a72dcd6992080072051b1734b77c1196e153288cf686832072faa5c760933ac8547676f7e2676cb0bdb368baeae15aeadbac509906cedb5099327bd4847b63ac6066284a85e512c051f6b816f828f9f2f1430b9e497a6890f79f2d2e7f1b3c2b3d59dd294c0f2db66a76a3ad251cac9720e116fdee21014af34a28bf96715f2c4946ab2218e859799a7f7e712020ab9ec02fa00c526cd03824c47965024e899f3de747bcfa6800c22b6c5b49b124ff011020ca9f2c77217a25343529e5954be9f113ba1a6a082c8d5aa87797f2155ea0617374d309a51bc120ab346b54d74241af3b1add2f39737692d26b6e87b214f1dd2c99f22a8ea88a129cde9156d8d2a56361e06f9ef01356e7f0a01159b91b7a6b93f1301385bd9344d4bf6439a6b0a43c94416642702e3f066e54b96cc48062d9e4593ff4fbe2a9c45fdf48c30858cf8a6ff94d0f1332ae0f88bccc47756ccd3643840ac1e670befe72dc79d8c9cafeda148b8d64a847bf477ff93b29378d0094e0b817420b8f10f6a9096c5ad2b5fde21c453aa60999b8202d7320ef697b4c1cf231aefab92d0f8e0be2d71ce2c48b89e9bb07a8219812916517148c1b93edc1c484515f9bd74c850fb19912629a878faa376a6b505ca2f306302c7b7835a7740aead1f20c55a86bc0322314ebc7603e88640b39f12c5f7ef6538337258ba227f7b23589aaaf4b00ec0c7112bc1bd08293cec0ce48949168aa48c31cb5f093396531fe39d2bf63ca5e658330dc3ad6359351c596472647dad6fba3a651961806f2a586601e0bfec4e30e82375fb1c84b398b2facab2f0a7f61b6701a38ae848b634f7da50add7bbb6716fabc17268c4d490a953d419d1e5ed43fdaa9c9fe1054c5b4f6ac41439448d24bfd8bb61cc28e246af3abc433a5ec0f44d22dc4f34b21ee9140bc11b34fe98a2e6c4b2a26c392bc5fd03e2f5453e7ced164ad415241d7adceab781c429fea497cacd1eb90e0e8ae64d0c7e27c1e8b13166bb5abcb47619b17955f4a3469fb87db7c71be822e3236bfb5f7beb4136fba3279bf3b46e744e2df03972ac81d0cb97cb5d36098f3d61251cd14f968efe3cf5f9053ca3de53f197108a91e1107d8ea3cd0e4b5a4432faca4d5a659255f0109b5b3c684ec5af254a7a67881f2b5ea79a15a5fbb4fb9d69c7962ad4010744320c575dd9fdd61b687c4770b1c881b18600be3f51b3bae88434ac1867d61bc903f1afe9ead176138ab279263638be029c3ad1defa8c15baee155dbc81d7f1346b151862c2398f93d1c4ac1b033f1291cc2af3a0a061587757ec248085dccda034df8fb32c4befed267e2c0f3666d9b97a7366dfc56c28c1c95a721619682510c81e662725c788f5c20fab38fb41f08d80e4677f915d313e4252f9afcadcf23cef7260e2686f27c9933724ba2a19e0a07ec2a822a723e10b993fe64c62b2d196b7a66a0aaa2a7f505c31ff6214ba125db845c10675d0ee80f2f69fae5745689bf64eb6749e23b9e79d28c18a02f183f9d6f38e5989a9dd30f36f64d5c36cefd47b636fedb8216969ae165b8f26234ee4d79768a4b53008f21c20ded0b533b61665f609a18754940fa2fddfe77797645b33f6c68ace64fdf0471860e2ddc9d50b1277261829b0144f088782a4cb0a9a7797263dc4f0204042d97bcb4ad1d8f51235d78369132ce0c4128ceb17eb683b6a2ac25d581b465e6aac47ed4e4d6802c42878af093b11b97ae0d15d1066d5b7e122e9f42bc2986b584264d333c70681b98216011dfc5ab1961843f55340fd3e6bc89d147ed53da246b8e4ca8a9cde949ab332742f71a1c2be906373655508d63cf0c4c6f42a26dac5baad01136c4631c9de29907cd779eeb95b41a784fefdd1d339478035367c575a124f786312f94ece41420f2710b049969fac95a6aada7d57cbf3d0f425d5cc0bb60335d9f3c84ff537ed601e65325dc50ddde19848a1f519f5ff5efacf78ede15d9c0e285b18bded2341a7fe39c740a1f99affd5519b788735340fd69bbbe74536932d809597465dd50928357455025bc97de0e817ffe1d961b62c15e8f15c004fa39aae53324b461bc8a54b183a04c99df4cbdaba378b432b9d4a4023fd39092b2bb62c91a7a2cfd729310abe0497aaabff34d5f227620cf0799fa5ee80d2393278edcbc674fc5db0a3f90e957d558bccd171af936d62e70921b5a0f2cfe96b26ceab854b90b7d6d27308d73011c350595a7677967caa0b527c73acf5aca98f1ffef93d69a16c8512cf9db7393a9f7de6d39e147c625759912faedc52836c5a369adf1f4703039d9d108c85c5adbc145404f8075bff4cda3bddab3e551eed6b4d7e8a9f2269ce6340a0a845ad10f065842a4d01717587d3fdcb5ec444b37618949d42e3cdfb1d4f34255898aac1c6c03c68e998f375a5bc89051f7a634cac95fe0a72b0c3194a1a112d6cff330053658d0de56232589572b2b0d9a6ca5cdce5aecf48f0430671d22f16c15b605e63d067f559f10ee05ef8ea83a763a3b755ca528eb1314daad073b2608191c7ed1056be96f59886c130050f1fe48aefd5f16f78f3c081aa6464057ac0af9674dce638e2d6eff21f371c19834f496238a16581ba251dc755dab205549a71bf3f750e2b8e193327b79c53d332e6ae6dbd152c8c22eab3cf6470b07996af69dee6e3fb43f6625ea0ed1616a5d87ceb4d09a23c169922515a669f2c987eda8a64c3678b3b37bc0808f0d10a1627ecefefa661c643dde50f13ecdd3e8cd60dc987f296136d4f28a0d00ec3a977fa41fe9b2feaadf14586f8ff00b2e7a71912761c4e707c42df9bbd5cb88ff6421095d7e222d79f408b331edbf39f7745e88f56e9fb3c3641e818b0271bf852dc856f2a58c60955cb915653343a1f19a49347d7a78941923006f26ecf4b4c3be1cb103ceb69adb7b89a8ec654cb5945494f47f54c56126a7be46676e593befd78d4735521b701e40fb74c94c2c5fa05cfd1e9cbb21200a68f91095711d8ccf3f6d1ec953fc955d4cec3ba323db8f552bac0cab76f3c3178b9580686ca2b4627e931896f6f4845f73c17a5c0e94ab2b72418945e2b39d41d5d77a8f1fdafe53cae75aa05f676497e7837d699dd73d6280cb711414f2a8fb6b4b0a6ed63dad35ac165198bf35ad6757d545e6dcc7b4da31fb023d62247a11eb861f74d0d5d52af1d03a9f8f5c8a1ff83184573609e5f4bbf49180b716733836ec5683a105f1b615c324a49bf5b053789d1480b1a788cdfc1be8af634ec17aa899cf00235435d72558ebf418f3807536e15a70798a4760022e7b12f3472e31fdcf1dd4e2181173c3d1961647dc86d6453f833de4d0acae41d083755abacf3d95ae9ad3cbb4ec4f59d976b6c18194f96063db0bdf0e010e4e5c30fd121935a3413515d2512c73b6cbd55ffdcd7cf2a2d8a9f8356ec44e476b74f04038e9e251b74f6de40169cc45ce38dd658a36bc3623538d8c97c9adab4651b30b4ac1aea9ffba8cf44792ab136e6962917dc3cecc145e1493fd280b545f501a5f9a49160cdfcd67f41a1dd436efd5ec2f2ce6b893bbd57d0c1f7e2f6c598446e2dcd21037fefd797b62c1e01c49c99309e0c85b1299ab6147e5c7f2fcdcffdc402060e092359fa3a587f1ac7ebee7523543e504c23cac7afc5c83918547dcb6edb3cbd0dc79a06386bdfe0989f8a69325a237a941123a22451fa04f7565a0c8cb95ae37f8de73395f12202c9553c539dc5b665f6105646bf26436fddad8b37cf8d73ef6d67d34348bb4ea982f9d32bc9f8f2e7439b742bd16c81541000fbf13e3b4ad5df0202bc4a3619d8215f7b0e35a185ba8a6f13c6809a693b55b11b094d0c715817d108a21a74b6d426ae3d887545a1d592110318eb88ac74dcf20c7e5319ad8ad3de93a4b6473d38b18a7735a2c74614c3704d001148f655d73e6cabf4d4b242c0f731ef9d56cca6badb61d44d613030ee9871d30263382181c271cc1faafd1cfb6b8a591c2f39f56236bfe5592e300cf9f4af2bc52e536225a3e382fd85885fac2a36a3e667e23465758ba7a55fbf82ac4631cedcf75bf69541f1db85b23e661edf85cbd412ecd7b7b3d65f0a44c310943f58a4a3b1090929be7caacd205661dabd6d08d91e6172988ebba8416e1fd29b5394f6f59d17685d268ed99b98bf94eaf344a686c530abebeefee6dc085fe58ff55aaec2eb04c9cc983326bb7ee9ecfbf086ff322c39622d887756524c41f026aa3cd594848667e743ecba84816c4a06555dd98b4e2c4d2e70a9a73a5d7f74330ad8a2c7f9445768b5084f62687dbc1110f1cbc52e7fa738aa21285d3eaf5217e820bb5e81e1a3dc751ff7b2661308496b010e576ec1fc543bc243bffdfffe6f75afc68f5c854ac737fe71148c994b456268548781340dbec85914a348533a48530a6fd1efb0fbd1a5f83678af247fda55ecf9869c1dfd29147fadcbc5078e75de8910b9757ec4d1caf094d6bc719424a53b9f8e38b839085d4a2aec4845a113090b088b0129df4fd694ce8c077922597133e18817a8785539516f35bce0f7f8e1badab7754ce16bddba848bb416a162352957d3495ae011eb709229fd75003551cc570d713ad986d3bee30d09cc11a43a4e8399a90e64f7867adaf44105c900ebcb9f432228684bcd0f2ae3b3fc698623c0242ef52051df058d1ef64379b2a5fe8afeaa2d687374d08c94a302fc02852c4a3dc08ed761d3d0052c04946eef665b84c7b33687ecd6c6f858101d947e5af06d87fcbd21cdc74c97fabeae64f2b3c5c43ee7984ac3eaeabc076afb8d03078d5df93712d6f2d047b55768f9b9eef4462f2a23b960f663e88f7518dae62addbe6a530c55f78c6e110364b8ceb2619abf3ad0f963f5cf5fb4ed743a38668a8c3a30d1330a551b83b3ac0ead2299852f3d556e8f6acebd6deec2e3c79002a1d3dddadc0e55f817fd7e3d51f5771eea5bc4324fe22e225b53c91cbca75a64b8a0905262488959d091aae66fdd52fffdf31f7a84283fe1b5c2657c91ffcc524adba765f42a49b81d7e0a76cae7b84345818804041f9f487a9f4ef06596ff98a9192f10c53baf1c0c075f51445a32a2d0424f3124057ce5c514fa2b36a4140749d8b36b4a13f69295feb27edd97abdacda3b267397641c02baa743bd1c1c861270e9bda414a6779ed6ee2fe190fa7dbc6a68f8f6a3eafbd192b33ecb36aad15a7ad8ca1564c470c3e8b391f318ce698f45c993ec27d5b6ec5d4be937ebfa82c0feb228d8780bb8b57f56904009704f14d955ed5c6c06dc15ffb8d18102b145ec87026d2de4ed25bea959c443077f3d3bae300e2aafc5cb8bfec5143748e12c3bad3d005a8d38cdfaa89d43a5a073c9a8c776bb28ecec6a42f1e48ced0b4efe9a67e7d5c380f38f9d263b35358b4992b26a9054035d43c26b6c3e84906621ccd5caf1db8e6dc1a37f9e86e9a7b9fe8715392bc13bb6607f4bc29482d84ab89b71ce2857c739730550ee9ffa5a2b4893ffd505ff016522c529cac7ebc24f0cac9152206a31941700561a5253fd9714e18b8ebcdf80e68f384be3d39781c4cdbec65669904ec29accd183b1f1435823a77a3a8db6256a1f60463acaf1d0df64381c7ec64272515d05df5946c07b39213f3cde5602ba20140a5a7319c14aac7cb1d323d3676dec1e8424949185243fa3b1ea100280034f7d467b4c22e5fb1e59559234dad738b1de8d62a2a051bf8a13396dccde0172c7dbc16a868a3b7a2cf2fe34b00374e25e4fc3c672f2518cd18e1559c53905000000000000000000f902c0f8dd941c479675ad559dc151f6ec7ed3fbf8cee79582b6f8c6a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a10aa54071443520884ed767b0684edf43acec528b7da83ab38ce60126562660f90141948315177ab297ba92a06054ce80a67ed4dbd7ed3af90129a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873f9d04a0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873f9d05a0f652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f37921f95f89b94e64a54e2533fd126c2e452c5fab544d80e2e4eb5f884a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000005a0e85fd79f89ff278fc57d40aecb7947873df9f0beac531c8f71a98f630e1eab62a07686888b19bb7b75e46bb1aa328b65150743f4899443d722f0adf8e252ccda4180a0e1968202c85e3b5d912a0b5370f48abef3d4ab876863b9b74dea5594779b62c2a07f23d9d32d6117f3c9484504682d2311d0f6231df9c98f254b3378049e03fa56", + "0x02fa0186580183080e708402faf080850e24f0b130831d1ec3941c479675ad559dc151f6ec7ed3fbf8cee79582b680ba0183248f111f3c000000000000000000000000000000000000000000000000000000000008d67e00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000161257000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb5000000000000000000000000000000000000000000000000000000000a03963e000000000000000000000000000000000000000000000000000000000a0397140000000000000000000000000000000000000000000000000000000000018223005bdf1e1445b071001088ec97d80344116c1c4200619f9651d46bd2ea7f8d9f0915ef9628ed4d12b41907698af2ef3f42639fe4fe3cbfcd3fe7defb125a106b82b161448073ddacc5c865c322ca8feb849f6ced5f973f2a1d80732e107c7a1097f1403d010338b7ef41b7703204893e2951122d925221917a0e4a4848f5090111a442d0932d0812155e411cd2cde62e2b6bed24fa0e1ed54d4420fffff7aada961f322829919c5275385d49a793beff70f009c911a414d83e3c43b7940a4ea58e457cc8b449918a11d1952932994a4f1599264d513e2f9e64f21374614958524886698ad3284e492f2414f9582cd30a01a4500553576915e4fb448a98ae18781898e4e79153790e404df92452a85116d25466aa3c559ee9fb59d6beda6776b3a4a72a53daca8a569a5559d5b62a1d9effbfefc7114638ed15ed955288519d759aeeba74d7c304134d3ccd120e3f48e0c33083f23401167814bf276ee7dd7d5111a292488c62aaffbb874bd29999f74be577fec47ee1d98a8513d926890a96551a024920902c092cd45a0a44fdebac9e0717b22efc257deda50d6cd646d85bb6c0199b6c5b8424429615c048c2f892598b20a14e0f95d3e1e0a10075646c3bf4034441b20b02f1077f073b87e86d2b84425191005c63522fa9c031b66d27699c653752532d1fda1f32c4a1d1c0a00e30b88744363500c2d0c3adb68d7f689b33caf27afeae093d14a430f80080719d07bad49d0f3f7480ed81084024b87dc4308f012b008f4247b67014665a3ebb0764d1400ca2b70e0a995342e593d4b8d569c307bc1ea100e0c7a942c4bf12bea0a9d975c61562859f7634fc74f131b10a1c8193c96114779132117f13e03e73cdf55bffe71f5062ce38df4b5535bff834570d6a3becf5fb39858b108e31df13159f0e31bd92813fcf54c1990b5150895ccd5035a6a5479ebd19ad16cbfd62c5bcce02646086c2d8ca42624cd47c5e9e98aaa587b8ad752ce17f33ddbf4b0bc19d283c0044920b9009034e24a52a22f7e0cd2ebbcf8f0aa1c979704d66e9aee97494c21b47ffac51776d022d757ab0626f89fa471e2b0d3b11201d6d94167d7228517e4da4962b6dca223e186180a48836eb4f33b272731bb8fc0f1e1b42f8a599d5e3c5cfd1a6e6cf4163c2199632e0e828400219bb3f53a944f931be7d31fa8528fd5520b143189195cc893c427828c93a99cb4e170919c0eee14d5cdbcae46e796001064436c4ebcde3f9c06837947411c6ea641751bc8483c2391e4dbdcaaf91f66881517e57ce131a177ff7f6268e812e6fb695ec27bd5e619b2e3c90f4610b53bed75cda3ab1af95d131ceca38ed63e5a087bb355a2c23e140ddf6aa45e125f6f37054dea75e444ef0518741eab66ea50c56855744bd8a292b10d405d867a430c42162f1b34a09a10f89eb0dce87a09bac527f209d78b5069fea00dbcd0887d1b973822c84a42e87ac08b801a2fc99542c7fcecd032a5c854dda35668560d3f80ed6ff8941ed10730aa63f200079ae06e2399321d8a3ea706f5058a1c0fa65762ff4dfb7c61500578498a8942b02fff90dd91265df0b1d29e3e55b80c1586caeff6a97036cca94b92955029e220f0079f8997be2ff656b8b1286b5010dceb869cebea089d5ef3f34fae6c7ef342173428780ea13cf45907ef9a91451c851618cffbea24cff1306b8dbd51cc0418990d1b05f09ed9abdafa21e8401eb69e1050a33805a63d92f2413019162f61f5c9205f042e30009cc1976fea8bc9bdeccb2950ac9465a682947f712b98a0204f4cbfe6a876dc38fed7fdb5b868371d86bb8c902beb22d1eb30f3d0a2cf66f103decb0f847404553d1be4bc51b14d2a54878b4d5478e9e353bbb7bb54da18b0ca9ba55d00cfc6008485b1d8861abc14261cf1b0017ed74626a6dec94c5a95fec20590ad5c87573892865bc364b50bb6a2544ee5781e7f63abdac01e9a5dc43508a46715a6a37c31629f60071e0ca71c187e352e23b360b385673a35d2ab60179a599b9da2c51215835dadb9b9729faaf336cc41a68188efd79d611e9af6a4ff4744b9371a6998268abf266fdcdf9d0193a3df56dd6011e0ca2e265d171e85b3578e8d3f058904b474e38f51bda6e6c465f9a603a37ece8c8bbb0bfb560b42311580e69c99091de8aa25fcf209c8d07d91ab34e486047f2fae4ea8e0fdff6ffcfc844930d5bd748a14cb82fed6fc8568c04374c0dc79878db486642dff62e80014ef83d84ad3be90afecb31ea696f40f66dafd3db5cf5132759510c87cbd15ba935720a0a8afd125c56ef10148a0777d61f824fda2cb07a4f57a0fadb064b89c5fe188eeed9c677637e571c961c44979c05f583008768a876fdaa0e25edc6d6131febba885b19f015e54593f0837f04704df6231481dec3d92b68ff82e8482dee4de08e62ef1b889681bbe4b246c1349df38e16f8e69c1567c1df81b9600835c3799e39f183a41e8d1b88cf7ea2a638ff417985ea91bb626f59f908ea00cce3193b618e85e1f98e113d98d58302da29a3070ffdc36ab40452004056c3061853043964829bba0bddee4c587ae95be145483d7ef6a7576745873c2e1066c0dbcca68b15885c8fc93d7c3f9a715d87433a17e507d80ff4677027f031f169212e3a8bf209c021aa1475c8476569f00fc6ae63fb69764c37ded951388455febd8698704dacd6f5e1cdc0dd39f75b98ea814354376beb3a961e42205341a70e838895e664b192704862ed71db6e5e71c5c6847b1c66eeeaee6b8000081ea53c06f143f587b5f8970d5f5d49c96f676cda408d02dfe65937142692993dccfa0de2653aeb5f1c54f39ace3e1978722373b5c19bb59f405686e57341a9c5b2e8275be6f13e95ae4d82d091046ec663690b1ef6cc0bea0fd2a3b40e0a8a4d0a2eab47cd888482b00802dcca036b5fb1d7dc6d05e502c82c51a0ab8e9d29ed25e3edadb2f748830ac81957d600202726849811e94c037afda10cfd1fb22b0c400097ff445db91f04483d00214e15147f6dd6dcc3fb17aa978a4f7189e26767ede3bb75eb8a34920a0168afb73db56effdf5925a3d213b383f15b34328b7e2713bf0d7def6051960face9d764ce183d419cd912105129f18c310c68bf0cb1c697cbdb0f405c30ff8535ed0296b1f4081db0d6a6a3a52d8586b68a5314d7ee6a97774cb0c028af83e4200105241c10e3de5be204f5baeffb333324cae376e0a8500311c32577f8c048407d28d901f227437ad83e2828b639b8ac3e4a805023d48b231590399508173a6bc68af35399ef2d7609c7223e1fd3eb44e7939eac09815c6aa652aaf4f78300ffb6eac2e61816f180adbdb40476dac09c0e318aaa5320719b267427327d695301893551b2591d9ae0a79bf345c5865daf3c751b9b734ba9ddbada2a3fbedb35c23979567202fa56f3f77d349e361fc0c519eb7701583e9e43c939d2a5d11b294dfda991b38a72b4b71e8c64be8f7ffef6b58b07d547a2090530c60fce5a2bc602156c9ddc779a5d56ce8ff6c5f5db9f86943640e7e5217657c896aeafd8a2d67d085b0410078f812ddab682e44df03cf53c50830ed0ad3afa7e7450d0351a0d5552cfa2af2e55db60863f953e20d89c08ec0337303244148d04361c1c88b1d5e5014aeee45cba04a50f214e557c9d7494bb882d821b2497d2438b3937fae105903c112482a912112f6ac0cd9293362f372abf698072711920460ab1336eb20288223f00bc1467ab902ca93cdf9d469538c30fb19591836450fe1836b9546bdb8daf7d8e0bc4ce5b6bde53df9433f5a73aaecd0d5d5a94fada5bb1502d4011eafe2536d64f2d7483606ce155638ad3f2d41481e324711c7c54f013099d9f6035e051be85b4703086e5cd589a80d789a1699e9feb73b04b58df2feacc654cf18a6570fa7de3fd845e10c08f04b61fbe4eeaf020bc1c86c5d1db366b5710d7e9cf61da0c31b1ac58a18216efd800152968ca753d8ea1f59517d91566fffddd9969a5dc6148255b0be0c7aa57fb810a8680b443a06ff7e168351cd08c272a3bbca28c0aab25f53bdbdd7d68f7e80565dc634449d10d59dca2a0702438f35585ac8ac35efb110afa0c6d634c500ee83cca5147364ef5b896fea7527a2bb854334f124a79b4c0448e2c4de958c54ae34f42c68f07569c21dae992ddf689055e726aa03509d62eee8c6050718393292ba74ecf68c52cc12eff18537a00a0f882c9820bb3d79e6da9d6b5dc26950b27348b1782d18177dc480e937bb39f5b72cbf2a87439e78a0a59a87eaaf7b1307c4380f1d72e54c1781ebff0efb2ad8d75043855b349d7556107409e848b510a5062f9b4502953140150a9d9f7e7ba7878cf207266fbd904909b9721442b01fcb4430be7744da37a17ba6e3ffd15f07e9a7e5d037cf5adf7bb34e52786adc484dee8c8c438a6e4538eb25657af5feda22005d211d08ef482abea790d020b25ee9cc604f5a02a2156d45f8834ed52661572e4d3fab72d15c4b049217895dba1d077d34121c62bf3a9a5531ed4259a95b4c230b8babda215995b2c0ab5416520663668bf4aac9cc946e30a1a2376e1c4caf0276545f05b2e6309c34f145ade4f9f0dd00de222d9f8f39feae4faa6245069226b938f1fde47561e8acddc3352b46e2475df217a3970e09cea6ced00cba703750ad73d0305c5d695541201f61b697983c16598ba5d3727075abcad97fec5e0c63391b1e7da30cf1ed4ae9adf5b9920855ecd157aca04050ac9286d2f64dbf6be87eb504bf876a32887a4744c209481a9a751f914437b21fdff5caa0068924c3eb18e7fd5781a021fbd5d99dcc33a288eb3a61fbe878047e68ebea5b20f6a16153c033894f07210f08b57a12ee29ceb5464112f7680cef4022d3f06aaf043bdd9de143f154116fee0340000f7929daa643f0870966e5a4595b8c5c46dff23b295eab575bbf79ef2df816172599e90d5e9e8633790219477fe1f91504f58b51542344aedb33998b997694893c939b8588f47ff13be31276d102437ce1b9b18c1d116cc554af8c4d6d93f96e6808438e5e7f84513d02c50681d3fe78f414c45b2a9abef6f81ebf44c16346cff5b31116a3c7f51889147d79ad0244b9c0d41205ae51b62d559b48ce44014db653ea3ffc5f30ee06c384e450ec0a6c618d937df3382a7bff6ae39525ab6ae086a273efac82c56144e65f6b4954430788e599acbc1ef5a62c9628ac64b67eec2c09f1b0aaf5f9914f1a047d7b8639dc80ba868fd344b2a9d4e17ee2116271dd3c59321fcf75bca072f09c84174b0eb83fa362f5031b7cc1b5288cd9aa8dbfbbb5bb058521b80d213a709a847a653f4689f2638d0525f94d5b1de7a83cdb847a78589c909cecade979dd6f824c8af41d377de3ef5bb341e64d29023c3d117930ed1893d68dda7fc14a55c0d4ed4140de35745694dfd8b13bad0c43518fa8463da113e8b80bb82cb566c8784e862e33561aed297172e2a2bbc9544af7fd3aaa555d384ac8594c88abb5e3e5cdac70ea64c589e92bf6fce8738ab41521bea8d3d7a84de6b923704e35b336eaf3678b4ca1541497e036d6ab5f2ac009c71c0720cecaedeb48fa6b8a42a0f85e682a6ca6a60c0c2071a614296101e9896d402c26b5dd72043869017183a4430f2785dd1ead6ece9da7fea8f69cd2c434a6dfdb71444c5e3c2791374d0ebf9ef9f95aedd4585c3e48b6acdaecfd9b32bb5be6aa929b6cebefdc7bb53ae6fe06d583ba9d0a3ebeaa4c7f955b4c1f6d5d666bed047993c561bad0bf1dcb8f6bcfd6f735c96ddb2e018aa81a77923dd05d45b705f92fad60524f931f4dd3abbcf0d8ecc91838d752b7e9bf8604408321548b9f933b6e7a3495f444c9834a91917ca6138f581526a7b81f68307b4be445fd1a0b4cf2ce04ee2f1b7ff9b465061876b87edee6b3a1746b486f59f3821d80f7d1e30d8e061d3df43d2eaf1ba3bb93d477cd9f09e15c46c323dc91c0ce9c702fd00a49184846b10d1f3b75c00fb22d3c04482e126fab87daf3de9538337784288387b4a673c29865f10e727592fc819a1d15de8da1c1e1a54ffa22cc864efdb6e05be81258aadb9d2dd23aa76eb224ba4221d69cb4edabcde2c1a8ab4d47f20e15c21a065cae5851c70b21e6d5c5eda436577272542521dc6e25fc7b5acd79c5a51789470910e7749a199e5d9090818caaa94788c6266c550a4494b00ab12bdb8eadff563d7359cd9e631242c47576d0a3c7146c12d9d0ee75d337331ef4b93688b812c957eae30d4808610deaf827285e0a2a26fecab0963d1880dc9efd54c99f8a547d5f8b31a9d01f6c84001257845edbaf99c2ef7aad569b07ef9a4bda0275bead7a0bbeaaaaf6f295fbd2e3e16ca612d459e68bc40abd1f7dfbec23fba067f2d3f24f49916c0daa0eeab7b7b1ae65a537e9e07d0a70a96a1516e73a4d2c4d725a3d640db2b83ab9c6eb92d23b092233cc98a28a3d0f190c691abaf8e192e84a834b8c873026d1a8ee1758ed6457a5ec406511fe61a571bfff584e9222bc1954c588b3d748a8bb8b50e90314e2ccc62488891050fcd63deabbfa3e4235408f409e2a085fa679eeee70c4ff8f37a68b79df0ffbb6eec2f4159498464785ac69f30cf1335ea6f775d3e2063af5cf74b5f75a2dbb85e0d1b11a2cd46583b9bb19f90fe92424a70a0c1b3ca795e04d4087181c2748d4943ec8c073ce2cbec0a5b6f2597ce4af6c0c494ca5f12c28111139dcb05adcfa21fb5acba85e8d986851532c890588f1acfecebf925438d4b80768f2868fe7903c23054fc19159beb1646834da0d3042ac25bc2e43d50e5cffc40601487be032c5190ed4e618c89d7f30037805a19944ebe4a0db571860abcd7421593847b57a178e26b0b6aebd3521ce805c8cf04d9f600e8350328c88406b7f1e9f48a1af7ca59cd95f84d374321d6daa17f652bf247dc49528e341367a93037fcc6f296da24fdc09d0b4074ef3181bf1ac0296f86ebb2c902d1c9b93fa7ab1314fed73bccd53feb358a6e8652e4723685acca4b323e6ebec074802340d4f8c67868366bd1f31c05aefdf55612f998ba0619d64575a6db32d4b786265e8e2dcd5a40071741926b2ccd03d6a8401e3e0785d6769ae06fd42d8d3b3ac42f908639a8ec48e2a8adc4e822703d986262be89eb6da73308ab67050ec1005c9f276dd2ac0cd48cb2c51fa8b06e79c4155b46e6779746fe42a8651ecfd2fdcf45308866d7feee367e86fbe089ec5d52634b3c419530404fbf44462e348eae0385fee6eabd56ac1310adc104bd7428d3fca2e5aaf38cdbae525ace507df4cf77e4186b5e828e0c3adb6a36bfc72823018161b2facd32bd8a7d8eb8c9ab47be2d3127789a16edfbe323a5c6dddd0aa32a9190344f8cdd3c4d86a9cf8b0abc03a0c1cf4e42cefbc7981febb00ab30f3152b838309c06bbd6554d3fc4c25349597fb483c453d8286b8c1ed2981e5c30919852973b85cfbee5aa323f9e7b2a765cd28a90a6375a24b3b195de31f8c9d47f1fafbfede33ba3c55e28b4d2f1a341c2a39132bfbaf9d80b599e1999cbf75fc594702cdc4d2eea9b52a5f80819715f0bfa920f2e2b922ee36802c8bfa1cd75c63e4c2f8946ab6663189a5dc6ca126b8dffa61017357cd528a12586e08cfb41aea1efe0bb2842c8ae36be6a9f3ebea4ae382a56934dfd7d62b17e7f5245b904fd933d365d950487d6ab80cb3d444d89aa4c59ef83a94a8faa6ddb1e2fb4394a554f22854052f0c4fb8654f1855999e5a34c15aa3a4f7fe69e431b6407bc642263f777b315a41c24b403de78ad19f2ce3190e26592a190357a6d2d3efbb04235c947d1690d5b7b283de3449c8e8c2ba099dea8468ec46f8348a5b5a9f9b0f657cc601e5f89fce5462f53dbe3566347f2ef4c3dfbdf479c87e4bbabb8a3bcd5453ec1e85a9c146ad83f9570e304f708b3565d79210bb9959992787da4b59faf3eef75de7ffc7fdd4fd951af03ef112bb4bfe212aa8c4f190a8493cd6e1cf30ea6389ead3cc1bb411048dd8651ff11597dba91408a92a9137d3114739239102c1c0421f48a5f34973e3287b9081cc376b332772919eba34ccdb2edac538c815bb0168ea034c8fd6983e913491d0ffb6a7a5b40fc7ea3ef1754989058d55490ae2790b990c243529abb4d6833a077622e308255d2c4f4dfd1796b46b86db6c506b55bb427037fa390bac3702ebc83b41244cd8c5cbec5a14e09a4b64ece459537f046f60d5bb2d6d0e639541baf8ab4b10acb667f8d307630a787034e360c491b3cc53688d4faffb218c48000d33524008da0ff0bc70f34a351ec2c83e29f90c3891b0d48b3a0f93fb387a85701b552a6816f592e07ca1edba69ef4c2add48f4c97fdd9524ebbfd323ffeb7e459a49cbb44504f9b5dc358ea2f571a79b6c6b368c33444589934ce93c58f6684a52aa431f7b4c0d1a0ab1f049838fc18b05a6b5949ffe921d8d6297a718126b3649b23adfa9b0013f19166c1074810506f4bc2c8142616a4d5ac5ebb8cd6b50b6e78ca864a28dff12b249a405a9cd663fd5b2c292739a1b934a44da9d247a1498dc5a8de97d62c1e02f7fa82787b785facdb079c6eb4feb8a35f9bdac28d6faa3dc08922e4261b83572ab02978042166c3a15950f2fd9f2c13c844eb3c37ada372aeac69cbb33a05bb8643eada1b62fb96094ce9c467fe7680de764562d0b447a7bda05653dc3f1aeab027ad8b58f351554455a8b31113047cd701d0ea551042f1398b9ed7e72b18ac64fdd6850861c7881bf60b1e3eb7f101d525106e9fb1642fafc6ff9a47b8123427bb5df5c61e7852c7ef8854b33176cbdef3c7384636cbcc99a2f2f9801b91ef9bbbe2f26f330efef3ad02d4b16a1b52607d01c219c7fa64816a5b1fca6cc80f91bce59a60b6c1013165c95d2d0747d05f7ba09f4526aa54acc52dc166321876198de84eda457b321fa8f7c38667915ed986048631aafdff4091963cbccd1198924e356435dc08326fd17cf24676ee778b0bcd47296b0775404ac45334e5850947ddb3d4a8ba5f878110b89ab20d734fe5744bce263873ad0c335c392ee73048e64c51404aa8d330cdabc551e07519f32dc6b7fb0afddb1ae1b2cb97c4b727e8f947d839c4b938924410281937cf0739c9b6f9ef77ae43800aa6b695647d8760d262b0975151d876e8b721b88e480f10a2fbeb65e75d18c18e8a655b1a2ca13cdf2cc41b0fdfb26e17315d5cac4f4a71d2be3d11eee94fde0e554d8d68ba95f30d85915bd8921d39a24f557e690524d046fbf377034a63183e15c3229e5014e4c1bb8e67e841c5b9573dddac63fa0fc385bf06dce7ceea3160c86571d514d01dade6b8a1508380d46268a4657e4873e1c66b6253cdab8418a56c3487714192f39f2c5e3b729b5b0ab5afe68994c70546d964d1f6d84d8ff566347850066da16db9dca21b7e3b4760c4f476d026895f850afbb996ff2c73c9a17364c46ebb2093d99af0eb96303ff6e3a905e5871a65efbc9094afeba01ee365c7a6b46def61440c4a29a4f4baca29aa3877e71dc27116cdad1ca02f3e0e024c2b6095a8454f20b07b390aba9974b47e552832419e0eef70cb7a7ea402da7d1cbb90d3ab0c86903810a28f3e96a047d71571488abc678d5f6918321c362093ed4fc0a5e536724117692f2fd219b396e473dfe1797efd6eb47fb4d7c6faf3754e27a0576d499f2534cfdd2ee0e6b991f4b355c3c7d2ba2fb7270434c4f07e83dfed38b4a806edebe4aa0ea6ebd056f40fd3b37c44f716ca18185a5836aac814800d50258f6e14df50c595fa3dd141af8f7661f7d9d3ceeb14cb942ed7f41b3c2c946bed5372588f41412891962f227f3dfbe4bbf812fa4c6b60b92e52b026e2560f3a9777a905f55061ccedf9515c9ace4398e32c1b4ed6e61658c37f207b55c318de7a37611e997cc4ba453aeb6ebc42a00526385a806a8f523dfef4e93414121893ff3ec06505c095a27631402cfe7e5722d9d5d96e76807800ea8ef174dda093c4828f3ca41fd2b02ec4e70c97e350ecf7128e3fd64011eef796c799309cbd1fb765d2bbfd6f06b9072cd0b4dc9f7826b105c8e80dd8274c80a0fe9cd8121524c889b991f4fdf1c74038b10631ad1360d92986e141909b7a62d9692cd40156db075b1d450d043f4c11b92c5fb54e29981c300061de423ef203be8978dfdba969f8b6e1a337fe35b822f0287438113755baf2d67640fef5547b3c8386e2f69521a338f77dfb5870591c9683576bceca82b85682ab25db7d1622275f8f20b6614093ada99f077fc1d62ec9778b99f5d9e1537e0e6aca42a3e3c9ca0c42f34828881f192a9cef6f6de1220c2c3a00ffdad7419a1e3d33eb4622512a2fc01b797a7651fe8a5bbd2c00dbc3a880d2021bf87543f28c68b3eadd83dd40b78bc99362b894fea1948ee6ea19ef69ed4cb50a5a1bfbda64f57a01a40a19bbbf6b8d308829d35e037f08f264c28b6ecfcf8b0d29fbbb8c329c12ec3a2d91d28f7d7c725f4e212875ad9aa020754a2bd6ed3411929709d248ceb7d2b3128ab15ef8131425e551d564dd933601b8b310ff441bf9775ae1845e941f8ebf26668d18458a5feacd42835cbd9d9026481e2de0ce8e640a11f93c6781f04f91d3e7a1a8c344e3bb169e07f617d5fe5842e7f5dea7098124da6ce7a748cfdaf0ef596e98a214bf014adc8565b59e687d1d83e4b11bbb973a98e4ff344e9bdaa2b0a95431bd81dbd4b7426c0d6da768102409447ad03fbadad281cfa9bd0f28f00c9588e8b2136d727660746fa6388b145c1e48a8d9bf8d1a2cb766c19a00e5f00ba5d94b840c8ed4047b36f5c7c1b367c1cc631473452efeb317251b339041d7b3f422074ef2d231956b38d47f81bb0070dbfbafd87e15338db9742c0d11aa8f55d8747cf54ea373f23f232f0c1395888917254fc7fffc13e1ab7770e75a15609dd5d2ce6d7343074028ec1747d1fbcc36ff0b4e9b2acabeaabc88ad1264eb5c054a26ee64007dbe38cd01f9bef7d5ea5932cce8f55ba51e17604ae01cefa47bd19610e370264ee712923cc00089c458e749f2f065a8723275df31a05230e1236a1d1ec487b4a3ef0f55c005e3d682a9813db5c32401ab8cf5e0d371f775714affe7938d7a11f49cb7bc14ef0af9d99300bb302cf4d637bbb89b86f56bdf458689116b567a9e504d8631180814a6a4f8f45e06b2e7e1c71637f3e451fd3bd37098b29c170ff9e030e86cbb054553f977124c29de0149df774c264b715e0545a4717acc2431bf7aaaaa6996c92b3e3e85f9481529177d63c7dffc4e8570714e79bd0680b7afe84822e5ee7a0d65f28760da82f6f91b7d0bbfc1ec897d78e6b6b95f5354b9e1a00999969ab25672a5a3670dfe90cc527df624d14c4ea322b89e7e018ed5c82ce6f5c2e86bc50435bd8e9732186bf237b7637364c88d6b514cd463019b04eef49cf3d9d365a75c2780a5aeebbb02a4a470745281e94e4416800c4a041177a059ffa276404fbf1aa7c8ebb27a025192bb53ae1516b2e15ecfdb16a2ede5fa24c91c9099e66c2432e1e41b633a7f870ab82ac2c2cb21eb211c4b4de096c4901151f107c360ae011b7634e2111a7b4fa81bef116eea333628ed6b587c625f3959e56b66f80efbaf5feba058d00190ac1bccb244f801606bdaa28478f126efab4a1636e16ad2a91db8d5ef049f8461c39727322b3a692a505aabc0a35a77695cb52feadb8cb1a9cd5b0b23285c8a604cdf3bb10bcf199f3b18084d3f570a651f14b427e382f2fead4ddfeeb412ae97b734a358ce61413df20871e1b67df5165c9d69b05634bb1308dd918b6aa65319dbf57ad931826e9f19c6ec59aa9a15b60d41426346143c0e39713b292c8266dc2f2032f23b72efd32eccbff5063caf1019bb6327599c007228a60c32a3f7d94beafafccac5b93b8654cc0ab2347140983fab46dcbd907a748ba7656da19fc8bd8ec2be3ff9a93fa9424a24641efedc9f706b45d384a06c0b41fb1a723aa49c8b26a8a6658e63679386ee7b1d425ce722a13e6e45eeaba17e270391b955641c7fb026dd3454c79a09e07652bb07b8f2f7db5395d1966975ed96e2df0b2d1b7d161e8d7fd51c78823a8061e9d2ddf5dba55596518392c592156e4db8f8a124f16057afac4049ad68dcbe8ac5d99289eca892324c957425a0bd9dc7be0e1c93b2c75ce1b404a1b58f8f1f92cbef0487d0203b0621c67d19ba0eca1b91062fcc34bb93f72a71724fd6be58d2a71dd07e56f5e60d46b91fe89e8d5765b50bda6755f428e93588d9eddc3865b07bdb88a8b365051658475658b56d7026c10a20770bb813fe687ee1ffb0016bee0b38c7756990d6ec5d05dea68bbe91827ef8890e900d049d4cf9120c10e7768533a56dbac5b91e7a58b27a1a3605c64474e144296977ad70ab171ecd29236d14be526a386fec03dbf6530fe3db453c4ba18d332bd6aead56fc3270884eab674b924240a76d27f7277e200f898353b0eaca08a45e11cfccd51fd3007ec96b4463ffd5d4a8d095defdb9e23733d342df01b42894b9b4ce597db7bf6a151a52917b0d7ef5d273f7a16e434e69f164685b753615e4ecaa4360f840ae4b2a64a772f708a793ce9dc692a9d1ad773158d7a508bea25a9711b92e286b7e944a5378b2264a45ce0eec1c0374944dd56baff6c241493f88cbcd50e5918ed8504c1b6e706765518100f09a1bd91c1f7080edacd5c52d8eb584a5e4d3d8fd782caf12f19442b6f31934b4ff8762c44dd05e5ab82bd1d845711b1e051fbfb1d8d66e25a81ca906e21e228428d9605458e6eb19daef7a926fa01ade691ebb9d6f47d950d67fea17d1beeaeeae52d53feb2916da5e870c4d9c9e1dd06dd7fa233e0705c5360497d5b3b70515855660be6c030f7c8075db581a828083206733e826a81090818b02c2ae1aac3ab4f6fe021776d3d36482b754b3152568bc7503209e95ac4922cc52b84ba897341f0e9b6d5d77fdc240a38b00022703787627a3a7d0678ccc8ec675d311fe45cb0d514e964d653acd5a23da9d16474eb9f847fb1ab93cedf4a21ce64150d6b5a6ad693b53a054d7409bf3e6dcbefb1a8766c1408190459eca91ca0a0d613b237ab7ac8a5c6f03afedf088b1d3dc4271ae960424b096a9abbf5cff1533d172a9426275f8c524cf2484193ce6e58b28e1a8f86f6d059a4bfd1faa2260b473cfb45733dd7bf3ccac08d67cc66510dce11fd689009886d7b81f1c15f41d861c05223d94951545c5e6bcc63e41a125133420520d46b44a542fe6fe145685f92b023da3075b91becf45ea2feb3881597ca553f871114f26ee64988dbf6fe173a8766213bec43ca2133f4804caa99f5f9d6389a15fb71a6992cbf0edd87d40b8043c125b14c76403e37ce7166141ba5e4b8db84ea2d1cc6bafb013fa35ae95a44048b3e663be5175daf1c519f67495362bbced03a6a2c5e13ad894c53542c1bd238cfc096702c8d4f55f4f41bf316f9235a6e5b3ff26a334d4d2c3c3f82d2ae4be11bccd443c4693469ab7da10b20dc25cb244d809354778c2f2fe9b72d198cef497acadba23790b6fc09699b273242b9603fb2fa2f175d5d2d648ecfcff6f937e7f6832a5ad9cca3626565a0c31f7e38a47e62472de00311eec93c7bf5477cc42d89e4150880b651df3eb1a7e615e166b5ac9d368ff2f76c66e8dbfaabf98c73ba44b3214323e2828c42a2e68848a68e1d4f438f4b1b4a500075627ab6cfad8065bba23f8d9d6a8e85301d292473f0dba67e6232bda7e35e2787051ea0b422eaf3a6934288042e1febb6d79f2b66e0fcf743c104c6f06283534444b5684878006e1fbf3f697e395fd6d614b3f2b0d4d9b852bd592e3c0aa3e16518ca1f8664046e388d20bffe21558218a87caa96cdcac1634e02483e0a30d05da0f7ec7ee5ba2609110b7eed99cbd3d800f18a6ade1af01c817ea7ba329e2ec17fe5be9a274b37d7ec8f66e196a977fc42d560d913dde3344801a920c90b98e804808b72691e11b59ea9afee231165c17a4adcfbd6edcd08c162b2e6a2af6e58ed4a2069f095f42db146d6fe7cf5a2b0b30795212d4eed9a9a919b1e13ad23aedb69a444c3247fc02265580b84b0d2be1c536667ae71296dd0f028f62fe9a78163c84bc088a77dabbf8ab48fcf499060e8af50f2ea98f929aeb050903b6569501aeec4c64ba7bf203d247900a85f8e33f9da71f3e73e4eecf8708b0f4cc2b1e62c0bccdf6fa31ffa19f3712bd8b36eeded6f8959aa7a9d00c6dd56c7ffc9e1a5f35f2053880b05b54be7798a408db497e81e50cc71170dc70c5610cd4b3b706e859c163ed30a44c047ff3690efb4471d17db8a6362725d01e8698d53bf64c09d4a1ee917304473d8a0247fe7fe2fe82a8211c9421904f4a75ff234fe3fab154c7e3420df726e294dec2d09ba13e5844400634717d7d098893a7580a72f8243f240c954f18c64241bda05605e9773e6f6ce4eb3508ea36659195e39508eaad436b2a96f52c3badcfeef92fb33662451e854ba7d24e8a751c259855e1dade8196ffc0be38ca4fced35e02a2f0200009848c158f6b2d56f9d5818c4151aa031801e85b563f03d1cc98915c3d4f09e361e2103eb43d1ff8ef8f01052c58634a601195a27e3f94a8d4e5d0519335f17b9d50175aa2f008eb263ea9ebde494b158aeb4179e59c1f75fd0de34a148b674046630729221463d1a605cad9714155f215f70559f5912825dc3aff954155c1ed468362c0a30bcdb4fe6fa010c6a753e841877a8f2d113a85675e274c9014648b303fc6fef7b94076c57cdc537fa9f94610259db227bd4ff2b05899567b0959e9434bea2c1eb4373de0e916862f809a94b312d44cac483a964a04709284889bd3fdc1d57c96a27c66f83632cddc215334fbab45bfbe8d6ca8080e72ecb0ea946f068a161d1d93ff58b2926f4b7642aeadc6ff7033cc1e952bc72ac6c81bf2ca5ae3a542a8a01838e5e2bb6d85406e22553d9863d35e1275921aaa521156414c923d8eb08c46932b5cf787dda53c9cacbcef70cc444d5c6cd469518ca80098d0f8fc7353f9bbb79120a2e279067818935e60c4b95d56dc99a21dbd8014f20d8c826077fc46db10dba9bb6fe141c13872d0d3e7b063ea0163633ff6951dc4627dfc02bf1e489d096d4fc3eff7ea5792568fcc63ba664c45030f76800e0ad5f10433f6675c43ab29032a3178195421e35f0335d0c23c90d2edee3a98288949e230ab79ce3d477c937d8eb7c385a72f25057caf6bb2651b68a3c561bca8a471e7c5b275dd4b6151b35ddfd175e99cf736739a8c77586ae627183a74370363fc87795da7d14fe5acb68d6e043a0add6dc3e34c4e85f9ed16c9e5fe2f36dfb6a879d39462a436f29e12d779d58d0bd5e7d8a87e11151faecf4f8ef2341ff773dc8f2af24785e34d57abcc15f07b2317e966d3283c9d947620d0fbf25ca686fcb5976e53ddc3aa8bb620db215ddc5b8f6bc2a2ebd1e3af85366470d864b75e8be3e4114fb54c6547ea271e39e9b0b00a1528da05699a206c6d9c7196c232c299c94e4e500976efa2290dbbdf2b1826cea2080f74431ca34dbbfce44097baf5f8075d10cf23f77b0aaf652e5b20972df12e12ec639a9f9e7f7cfaca269e5760e35c7be3454a9d882644bfcf10f66d0606c5a1df42172e98412a98dc1cddff94544fcafd52da63dd2a2dcf87100d721e08494f86deccf9cd74f8c31f53ea239ca4685988811ca7d2f3a3dc81acb77864b422f717dc66ebcdaf6001a07e4542267149778c2e1c09d670f6b864539983a7c934e3cb10a4fae45616021d88c90655407c082303e8d8beec4bd04de3fa7c8783bd87c1df6e33f57141b0b4eac383634e43108bcdc4d813d85b95d782f3408673e98bf0267ce149431bf71ac4be830ad33affff344b7bd90c08ea8e39fa019083165a0e9d6a4143acfb54a218d00a7a099e2f78e3980a1e798be159b3824f45d7ec11ce1bce19479be4e3462bf1e203455a1b8872ab28e92ff3be4ca54b065f186bfeddbdb09e7000a29475f08ab3de00f88dc4d09dbda8e554b59c1d04ff1e363b1ff4e872e54896ccae5b3051dc17de525dbbcfca076001c0f661f1ebdefd037fefaee4faf0d4e7d48f787b17728fb5960dd1a31de59a6d66c581949e5901469cc6a45e0b9c627b92d3c44f8f7869e7aca131dba02217a6059204089a746bbe21e02ea1559f7a39f297e8bb90c38c8d8157f8154b314c067e940f45405a60516706814edc2f657adf98c87a43a463d35e5ff2594c3fd79144b4a841e2dd498e1ede9040dd0208c1cd78dba1b48ea8f372a26f67fda7f86124ecd881d062138e777c49f14f7fab1649c81b44cd363a874743ece051cc6285d452f3f939497c22991c78390405239e1cf8a56301827c6fa369e221ef0d45e165f042a1e07211ca25115f0d7677fdfc48fbd54b4a5d2081b206e61978748b627467279efebd129f20dba13f8ceb72ddd9dfa34fe26ea026aa2e7d4118404ff00129fa8378bd500976d136c6fd41c779c3824df3eab5e0a67fe24ae0ff2b26ae5b4f0399d05f9ac52281ff220b48b3bacb253286615f8ceb565afa8ffaca6d791675806b59dd6b9756eb62601458ddcae1a51465a5bd2c71695b4a0a83fbffe428cf97f04cf574f2de3c24c7b93070ab1a8294f90f1611d95d994cf4ae65aa2685b4f3d4cfbb0b24aa58aecd273ca7f8458414e4e4646963e4ecea04d73bc66048fa70b4393cfcf2bc0a32fade2e9f6ca5bb1768872b2d512d33fb1de305da9d00291176b9fc5a6611b7ccf1ffd7d05217889a8f63d4a8b340a333384e990efed685a066baa3cc10353f3a84d319d4581468d9ab48561b9db73bc8130a6b9ea3c952cd7bb048fbdb6ceb9c56e4e2ecfffd0022081afa2318bf1f84fe1dfb08fabb04631e3356a89ce88c0853b495ff2469f5dc015ee1c591488158e8e622243b58114c6ea6a609c40e0993bc1daa9aecf6b77d171ab7ff903be84eb1f44536a7b1ae4849f2cd8af0be36a0dd815eb29632e339a7a847ddc8c52739502cc9b93287bf26cc50c1d56a4f522dd5c9a1ede5c41a50b6454f745880cd345d84fb56e1bc539234b190b67dba64e9b214209a5ed89973b13cc9072d4affae991ca5234fbae00321a00500b46369fdb442d28a86795e2a61553edb7f77a28c2da7ddbad24f7f8f8fe0821da76936f18150fefe94ce5529879ffe695c186a09f13af7e18896d6390558a66019e171477b0c83cd7b63d3f69950384ba14cf5d0805f2caf5c9765dde9b37189b313de555fbdb141b57bb87376fe879ff34125503b73fb379d8a921d5b020e058ec704018758bb20650eea8add50ed90fdb25baa1c4689aff58ef2647ae9ccaea70a935681fa59154a3e81a9156e3f48f5a95fcc0fdadef57628f0614d8271abd273af3d8aefe29154b2ce1e977c2a177ec0e81a58c1ef145edaa2f836eedffd672628dc00d1053d807d61ebc7923c369ce25f7effc96866ddba887d7d5d08013d87121c7e1736295e8a877c135c9280a1bba7fce804b7ec84167e2026f038947735b605b4b6ee6339ef55cf2f4428e1c3b331b5e4eb953d888170600cf39f2dfdbb578b1ba33f2ff1a7b74e1ee2a20fff80211c474e60147753d72a40a655379f9900ee1feb67553ef3a586565593c6dd9e51cbfa33e1520b6e8df8523245e26f966b89c277d47da40e88c690e05b29243478ffc8985a2f87f9fca99c4a6393e3a9fc359f802087ca79f7878232fe6acebf862aa3a83c67f42424e96837a3ea25271c3ad0ae1d2033d1c598b6f0874942f2e8c2439ce44f93125b1b3c3ab4b6f258b293e62be2f8a5ba3afbdd0ade19dd66007d69bf0c527c889d34cd325daeb2d7e4cb3433140e9fee0a6b7f2e8096792c1093e914ff84f3244fec7b2def2b29ed27de9a6fb2d9c120e9d77c78bac4c15f0d60159fb539bf750310070f1a1170c4468cb5fd0fec04b6ce3648164ec8e1364f190c7121e8fe6c94810cbca13c90ee29888d6f920c2636b4eb8bfd92a2e4c9c04cb66d15fcd35656922bcb6ad43ef94421d70c24fc305f58312629e7c9b4b510b0d63e24f5375ffe9aaaff3e351a99bc2f7cf86da91d093b8f19a9886907ffc6ab687d31e6e50bdd0e90b32122a586f0dfde0eef7e6567fe8189e8c604b22aacaa6d7c402bd0dcb5bd656601969ef9a92ab5aa3ab51e914613156223e8f26ace74ae2c7518c8ad1b36ee3e36835cb9282c281aa3a3f477702818edafbe202598d822d124ea846741da3d12dd00d850ab0b3fef3c71fd1d9d0317a778bbfbbb36d44a80efe9f4efd60de03d83fa97018ab62ea05c44e9d3c20b75b154f407010902326225c6d3f1722cd64e764c0cde80399dc66beac9de2f86eae944d8c138405b995d8d696e498497c542207e8d3dc8afdc5aaac83bfc8d3b3a75d0cecdec1752df8682625b3e0b72fb4f7a4cd8f2f4beeadaad4265f3f7431d1bb1d12c302a8d4636d5f043a34a1620c0abca58ff160a80dfa3773587f6f046c758f14c70191c47b9eca63060f43134e73d915091d0118f0101de82f9fe39c9f0ead7b2ba2e421dea8eff855dd6c6f772d781ce800bc772e1d307c4dba04ad0cb802220f9911a8aa9ca52266a21bc3e75cd00b58e6b60fa18aec01e7689290afff64cc6fa5448d085c2fb154519a44e865f7cca56337d2c74e6f1136b8c3f419d875ed7ba40b6ce0c639d32f0763dfc59827fe324a44773ef2f20e248456411102078d93806180693f6c6faecf1e093cf1d089835a978651addd631920f17ea4ad306b4ece24e449ae011e0f433c40b26e1a0fe20a01cf83bf1e8424bddd019ff91412de24043a092b261f290db2209d23e9bf7db3950a1e89c69646984e7ebc6b5108d08bafd8b30cb3978759219b5ca5a375686ee020e1e0ffee6851e10536fe25e8954a762fc4fd73d08c509089c29f1a63c55c77e48c6180859ca686cdf8c9cbf49f8f668b9c7545e882f3e0d2c2038216e7df36b78118b62ed6ade1bb82281d18281410f78510dddad14263085fcd7261b5075a94211a49121792dde077dbfebb47b1d305d03c987e3ff2b88620c8ab3566d75a4a2f45e73d1961243c608b4ac1917df9e35a96a949fc8f21b5ffc71142694cfa8ff8901820fafc96a4eca12c304d52020adc31e2299e95e0df5fe3d55ed206eccf29e2e402ece0aabdca1e0a8bf48780773df3b795b24b459966f0c4728a91a0ec40750524877d279db0ae479e2905a095344c4798b164db7b74e080aae0a1abab2dd971a2c62b848a1b2cc1cfccfe9947bcffd997d793702f96e28db75a9cd83d6d4797ddb2ca2d6dd89d35c1d7d791754d2652f7e24502912491b7f96d4d9cc85a9f216fba299076fa5659d6c32543eb0b06c1dd3121cf8420eaf3bf644e4f52699b6a4afb15df6adfd0711862c822df4368e87b5e9a9aa0310bba1afbb000a8a7ded51fa90212ef52140383a5a29990518c2ea6994ca05a1aa8f88b4e8a9959b824a890d0eb7aba004e2d274c7ea70f4916003909eb0fe20e03c366826a7b6b5ce676fd24752ec7a6c6e947193cea31a5e0b22344647346950205431f86b700cc3fa31741d33b3f11614cfd215c2e22937b247680f3cff5e32d04d9c00e14f20453fa4b415ee156e97df42c643c4f534ee7bb113eb631407e854c0d649956ebd62e4bf94cac7ce77163467ae40ad3f86583b4db418d7c329a2aec4f7b7e790b5c7fdbf851bc383ff63cee0c03fb5e3e8026800c74c55fb8e15fddc0d6089b07eb8490dc34f3d35ae51b748c56ec56b989d9909fcd08b1f733266e07cf2fe0229d03f96943d7d09d6eb5c6f06bdb8be9e919b2ef4d1e6f950b425532a85a9f59b84b174a4581944ce586d691c89548b6c5ee48ca8faa2ddd6294eeefcd3406ca811a52e0fb8441eedcd1d4b55a249b54fe630de32dd8739600e96cf2deda5e3714f99393af4465d1c0472aafcd7083ad023fc48ae86c10984255cf6b4c98c95666242868ac70ef8bf77c24191115b991cb4fa0fa2a76907bbc8f74b909ef3dd0764c842f1bd30dc10c2cd7fb672456203c963c99cfdd145c39e501bab4781db1c38af948ff3d68251260b96ad6a49e011ad25ac4a5aea3c3e9cf02ad9f7d71ca7db2c21680d32b62a2668a720eb0490fdf401ae6c5af36b8c2b6ca527acbf4fb4afbd1986c3d1cfdd1c54324abacdaa5476914faaeedd07ecea2bfa66b793c6606055943ace585b8dd808e2557f5a588bebded380d3ee0cf837fb40fe4d0b1a5df797693a17a86c5fe8564692ca893bd86c593855cba070443ed4aee9036df36c351575398034879f517248af791f140e2a682458401bc9c35fb6d08cd67c5c7adedc534d9ea5f9a249758000bbf4cfe44b4904b1e4acd5413db960f5cb969fffa9ef675ccceab6e680bdbb671bcd39cf193b8bb1de909fef0af567cdfc7d50c5bcb727bb74b9425e5786e82cd98105131211516cfb8777541f9d9b890e6402b12e7dfdef5152e8fe046f326b9818a27e2a9748769d0b068f3a427e8fecd97d8721301fbb2a06139f36347aa08dbc5d784214850a9568279980a0f0a98792bbf66e7e09d33739919aa568f7b0d5e0da0e728d7a02dd8a482b4828b7b0da93ce99b6633ca79e7acf19f9639c1ceaafe56c1ac1d22a27148a66e2a8aa1f158af5e53b0ab56c5a3d28ced2340c79cefeb0be5a3a32bf4c3cc784f352fbb79f87513c63b11d9aa9b9452bbb715c3da69e6dd20f027419bba76169dcd70a4fb2b6edd8e2baa7031b28327c55abce2fb268e2b1d9da06da9b468487c73cdb17e67e7e87640afb0a0a7bbec8689b2c18200ae67895e842df263112b6efec05a3a67ee9e23a3678168dd5425e934e2004b82072d4c0625630b2a0dff5c0bc413327d7d7eb97241db2393a9fbaf976a190afc86ed074d3993a9f4b76c9ae17c43ae5fb05ca7664190b2608264673cf201fb48d0b2db6fd6736f70c6c0db4de0f0847f2e1364b92a67201a62aa5f383d8e9fa6c5def016dd322ec140c48c5d7524cd11f4fecda1bb113741b43362abab645d568cb336a417c5bfa5f32fb5beed8dd8f37de92cac9c4227e17b88144546cdc02f48097dcfc1d79b46026503a26717652ea472e0de14071a81dbd9ef925a5a38f80c5a8064683b71fde9f6e13f8671b56d38f9518d6192092a0ba1e2d1bec797aff5cac444a8d452e83b513d052a0c88a9e671992fd249ad45a2a319ed49c3f8fc89c2a5217a5379995521663b417e71a374a2ebe71f8cd39834a667c06928e9aecb2fab560f74e4309e48907491dd422ee763099c47e10e0b112e921ae35b9b6da2083bef45bcaa9a7a8afaa0b851a72d9da0b5334f66337d038c24699d412ca666bc03d811f71aad1800d675b718e1550de60864ba2b6a4ea583f055e3e1b8f5faac93e6b17a49ef947118abbdb4f3e1894dc09fa6743744869317fb13587a0de4758aa2ed4d9c2d97e143426113135d03a00f19bab5eeac3f2e18b2f2c2a86f767ff234197cd67b2a4968ffc26bb7839a837f0eb85d459d0f6543bf8f108f24e7e149358357bd079b70e8d3f46f0b77363d6868bdb637d7dd8af8397521532767f2a254e862f6ad037202e26a9a1f99cf46848698968e63d45ee38504d9ab68db5e16ac2cd024ef4ba80da091572714d9ea08b6a1a0e5e83e8d43e9fcf6cb2eabde1bbda6cf668e0cf78e3c75857dfbc8cb9cc9c8b6c121966dd836d8edbacf12c1aa82b4e8087131d9c015ea0e31506adf0b33a4c7728e625b997363239585c1ecb2106c28172fc8520b661fef82a549f707d0264c353bcf68cadd70df3ee89a238252183e040dd6a4dc51a59e681147ead6cc558688d54841ab2d88934fee8f6ac4f73048b071e84b4b066618064ddce72aaef4711cda80f23bde082b6776e08d7772a00d5b89ccf7a91c8087be29770f68b7c928ac9f60f85903c63633c575b0e07c820c7e226f683fc5a0b14df3b43476d5f921b443e0d21678b0813cc4688c8242e0ce490fdb1ecce573415c2b87e4293cff69bf741940125e37147852d4ebca1072a71a73ba0d1efd25f71a6875ec9506cfe0182fa76ef26ffa13cb0fe37035d8826ae7535ebefa35c247199919d164b8cfe2c9473505729b7c17a86418f66abd5fe4dcfcaff08a70a456192e21616e81d997fecd8a8ee783190c3791698c3fe53e6f1da3f60c5bf1bc42d1120206ce0897398f0d5bc1cf5b27aa4501c1d51684ab0fe44369bfce8c0cf92aec0d75dfb8d4dfd90712cc5645df19fbb316da65da34b81065020fc4f8a27005e2db78d4ba2f4c08bdf72abb4e88d9fa3f96995dcf494e21a5b830d3ed959b99df6c32e0563050a1512d19eccb692f4e496cff9fccdf4601f498cad6c83de6fa1a5e385fe6b4499d744fda1c1ee165c7e6166ac9e7db818be7190fd641581b8fea8687f31f2b9efa6cd3f8b01c5c55270f7533118280eb283238e50b16d1ec66ca135802f5bff49af37ece6490b41c0ccc51146690bc6f8469deafc8848d8e5e64f05e9172ba4abc10f77d425e0dc6d213dbc95d3ff14c1a4000b3024a61fc2f5d8d237e278cf76f87f0de3c4dadbd774a3384e0cfeca13e7dde712117a86224424978a8eaa90f976f9f62e34de06115adf031620ddd63a477c11183338bcfe47af8e0ca0f2e3c3b012f2e1bd54747477754c82a1ec9b4b5b80220bcdbd6b66363a1f4753b99c2a5ec49e6d384a31489ead191ce59047ceecffdf33f21695ea04acc6cea9c9145d66e481f5df582232bf44888c9698ef08ea3d46d2ccdaf905a248c177c07e00e4bb52e3d2bce1277c99fa4ed21fb8bcfbecc1b12f8f324f1e000d797d43847a400b248c635b997490c62df7b5cbc11ae8fdecf93dad9014e43d57953a0fc51a988ce8df180f2e8f94081b257d7d18cbdc56b9f6f7621eb598324e8d046bc605572b9ea9b0271ae6c4d21f04e4020161635904aadb09e9ca799a9f582e74022ed082c204bc8b593f23e84342a35dc059a3d3794cec694ac2c52be7cc9bd099d978bf2d8e9aef0266e60b9b06b63a60b542ccc97e5073f1ba09315bc0173dd546aa844242009cd984f804f56e8a46f568dedf741130153749fe7d3672b0e65bdbe1db63f33c2b23cc361a9e02d469cc99a228dbf0f962ec35cf332c458c678eb8a235644423b7a2cad3cbc9dc7360ac4a0585ab2b4adb1e119d8066e32fc474852f928d765b858484c88c8cae886b4751cc86e2fc47516b2ccc445b3a5a1cd561596a263b245b502d1e95ba7cbafe8c9be8eb4540e4d8805b417a7dd1bfb3de3b765f68058d13c52a7c47d564f82bb382bce6fbb9645cd57dc51a611150f8fcc4fb630ff7fb527684eddf36c3ffa0ce506e4081a7aa2c650fcb942e83118e33ae34a4ad409a1a4add1e3f4f359b6f5ab6778a2e6fdb55612808a0395922724688de556144dd1522b34f14fbb3d9382f5ed4cd8019403aac2fb7bd410f4cebd9f893a50844dc0017cbeafc15d51efc368514f94ffe3118490b2884200554b7fb404da252d5b027e7dc79832a15071fd8ba1c359b6e382881ef8041601c8b83c28c27d6f3312666907aa996020306fcb8a342f78c12d5d63fc57715213d2a11dcb891fdea859f1d9025ba7f8d3b01f2fc9611078b9744f269a916241d0a0aaafa4d93ba576b429bcd3acd2b6f3a44706516d9e7201c0d4b9370c818cf6a015e252fa6b1689a5b21613af07e9b1e6508ca0ffa4db2f349a65814441afc10e9d8eb51e64bcb34c6c09609dc09b8de19745121717e7a885283d58729c4334aee6bea7434709f85baeaa2d8e706f611c7fbfc445217e918153b1a9cfa5e5f3ad076112e90acfb83cb6f86a758392df630330ef2083029d2589135e8ca1e10498ec6624d74b20fbe07a51784c1f9f948e0bdc9fb33b18d37f1a2af05aed383cdf5959853687bc9e8563db547766578e3227d81a3feec6588acb3e5adaa1e5c1e6607f9a880e01f569450d066f08da8067924627695fc0090e433b1323e68b403a6e27456004929e0b50083e5cdbd0a799b5ced4bd89f9002287b89c42ed3d127f3ad5f25c87d9eef228cd5df4b8e41246f80aeb4ac7e5238d001e1347cf1d6f8ddfd7baa07afaea7f00bddae49a2cee567da095d6515956804feb979f37b99310341dc37f47e25474516a1b92c9cfff71948174e7523eb43bb7ddf569362645d448af15946147b7f855324a18ae2c65ba2948d62136a1094cd4e58324d946bdadcdb9ead2552f36406309bba43378d8789f0e7c73fb5733f2fa007249634c2fd5944e05b71cf25ffc0973ac6175a787f34d270ef447fd5fc1daa7dfb8b6ee74304141c497c10ccd892cabf9e96682facd0b4a78a539b3a5dd8b3a62a76fa9bbf83559c639be34a647bbfbf7918abb5361fe65712787823ff3eab34e7cca3a931d97b311a74fbc580a36b720169bf23f7760a075fb845787a0e81ab52d33d0b6ecc7ff712f6e11e25380df65d156a16c454a26b733617096673caa1fdb024b38b045763056f96924ee2af0235957a758d2860bfb8b2546be6d0a0745b389fc3a0f84728f3d7f87f39a2fa6fc69f5a5e22c4822c79bb82a05decc8d76e6ab7807e7ee304d340f03b0d76fd9810c2673a8bed262fa9486c406dfd870d08d922393d3a83bc738b7a618a8b14a0678449bed7615350d73b95d19afca8707fa44c1a0fab0967cd804b0841188fcf609712a2c394c7c603edbc3bfeebcabd89dc9f68de21af1b4bb4063e154ef3ed7be76e76a38ff3fd1420077bb17a762c97a77c276a2200084334e351912815741d0ba6c4f5c31773acbd49d5d064e5cae5e38ac7d2cbbf3336a89cc86cfa9ab87446d08dd886a98ccb38ad428ff6d88818a92c0f48945dea048ba7f0d2c0401ce24918128a37e9713d8f401c5110ce7c6f5a30a888ceeba31032f37530a78a9faaad835a0de3467b4a638dc68e0f2eb762b44dd51999566611e63dc86fc9a3235f32807284ff8534e6785f16f9689b19d20bfcff3bb30e8c55f9cc164988c11a3cd062d0e0e9b6324b9c194f10f0c5dd050247e4eb8bfc754580e506d83cd7f2cf7da33e01275c7c4b8be3490513e9efbf1265a336c58a5c0e13c1e908143f4c02561ee757361625108f83e90d4896c3d3949dda059ae386d541c7ec959b61b05d4401d906efd09c90a28749fe068f799b34bee39b6424d8f192ac01a71ec660c84d2b505e6cf5d2c8f4d6cac7350e242876b1f3354b73d1715e1797f89f3d42353e01d001f03c46d6afa9e4faf68f965b2396ef6cff4f4715a639c49e5c15339a8dd708dc0bdf6290ceed70d7d9f1e696031847210e0c08abdce65dda5f8843de7a1c230bcf3c7bcf87cb244b06ad7129a65bebfd1b8f3da73060fdf28d441117d35157dad1b5d401d25a4306eacfd27efb7a96361747865876679d0818c1c90b89dbd0588d4fa646395b95d39ae4e6031f55b3ec58fecbcf6c341f0daa256b785522e557bc0664c39ad475caced09f31b9f49afe87fb191ef58ba5fe20f8fc6c8a9a88cee4d7d76db335215668a919fa8edf08946aa5849d5a2dfe48823af0a545342c1a5e7236b067239624fa82ec17e2d2f3a1f12a69f316fbb5b95a44eea5bd28d9fe2c61c32785f566ffe78a59d5c62b69dd40df2d4a0d1ec44830a1d11e9caf1ca2d88a342511761ba338648af9c3d08604977b153776ef13895ec3d1c6cdcbc45993ed3fb99b7e60b2f27ce5a813958e4cf1112cc7b05a3e570b9dcacf52457d61afce190eaf6f88b0f730fc20c2a9cd03634022f312e33c7c2bb2f5328fadc8b72b8a94885ae8bfcbdf4d7ea5cff58d0eec5d3f8a41860619e5a3a28ddc69cd8f65936a9eec81d43b0099290e47621c7904e5283088a01d8e160061e551af996470463efe754c536a75c1a8c59775cd9f1474f09159b9a5ddc0a28ce8f16699c3f47540edaafacebc3fbe9542bd31d2474d5788873a09aee420d78020cceedfd6327ae08f6ae393bc0d4102d4ece05e6e5886a8b108ce2c0b9a7a50edcd05aa5d88f84659ce1d66524055f46931c84b44b6795ac792a80ef365dedc4d8b017586d745591639f6da7b9e1684197356432121d811bfa87e082ad34aea639dbb643b9053b916b982a6968ac1a96d73bbda35c52750d87436a3ce37273056079d33ba2af61d3698a1efc668ee87e79ce7098cf73259d84d872b7e94db57a73b0cad6f3b289d85efacab1adda30774b05a3d027ca454c77ab57d7ef33e2a4a9e2da3e4cc64c2b4b2f38cccba1a7c584e1f898f7cf869a1b1391072963d9a49cc0b026dfdb40bd77fd51e5041d132a09de547978221fbcba6c72f9afdbb015c14de5bd8d052dc40bec9d3fbd27b7508d78f3e663df55ed312763f774bacae9076c5f06768a430830e180dbbceac62d07780290a3ae58f53a2da36b89aa9b989f16c121203cae453ca63fdf48f17d985df255a9ffb584cda5e3c83945b7bd71e92354b59b1da7f96f29c92b11c72f2545c88070a1f724e721a63fe23de13bd8854f8e87cff0757819752975156048b1db41a6d18be4a5ab9f6c77720b4fbaae439d8d3ddc377aa4bd82f631305ff45360f8935197bfd5f28d7a45ddd6ac6865e93cfa56b3612a1201443e1c0152cb1e8443ac47d2ba664a59c6a7c4cc766c8f300ce9142b69b9a8d1c621044b41f02a724786ab223f1109a5b7bdc2a88a8024ce439044da24b8879366b82ad257afd1d943b595965243ab0d9d4ff268683d3f64f4a90ee07c55f99f1e7b5c64ae99bb1a2076f82d468d38f83d54ef166d24293d02fe33ac86f28fafcdcee8c5f660e226a8936930b3ca497b7ab1215a98f58f96a4089528cbdfe1ecad569b7de3f52746342fdd26e4755f0424dbe023bfe235ed90ad73b3ed194b34c96fcfb4a568a3cddec7388527f10c8f4ae61a024c8a74cd0ea182954d3e82418bfb4a509132eed5b6c5c251b4d1b35038c69f00dd8ebff41b989da0e8dfff81c4f4c2bffd7826c47b91ef958e84c40fb83050a8cefdbc3c6e4be763ae49ece48cf1eb4819959a86f0faf21404d951078f717c8d963e77af39ce188ffaf1d962918444923b548c5f90e9aca23e8f50f50a6a17238f7314306336455c51fd6f25722df08ff3308a479f3a082c7f9b34f96759ca8ab5d338e6dec9f374cc65ad7043448e0e3da14ec441734e0be7f108d7ee1c7f19bafb5d404c1375879fe61f7bd9b0a247afd7fa8cebbfd3b6dedba5fc021141bf914d46fefa8e8708609feacd6bda97f3cc9b79d20ccc23e4bad4301504244d19b3ceab292178daa47a08d46941da6ce9d3f29047f1f1ebbcab0e286df5de2abded6e70b7da6da84e0723e233bd270a0d21eaa53349b229646f81f7f4bac524871ea09c1cd0c663feb3fca261312627140eb1a828a1cdddd3af584c8b39b2827f624761184c0e6094a7ca2a85978c49781c1e0a0ed38d200ea795a8c60f0765ffa0f11c949588fac20fa4af2fde08ee9e6cbe3b9fa7967ada17c924eb4ba161c21b0f1436d2a32248a04cce90632796330e5fb7333b77dcfd0e726b2ca0822b114f88eccb208da1115b7e150d54e055c02e150a917b4d820902252011f31f2972ff27fc1006fad71af9ee315f96a5fcdc5f4d1c54b50057b4a26e18c2a4b363561f3ef50006314a6ef32482960f403d6d372b82c8e796a4d485d36e872de80a79f523289bdc0a9aa5b9c1b31e536976f0ba3824fd52764470ba29d416710065c09de6f0ae9b02d719a91085b64d64c955da8368550f8974a431438f3bd2e7409e497b3a4d106a1174e3048f04786c05dab57b56104bd243907ba5922ecb9e29fcec5d4b7bb9527077a4d71903c4fcd80a5952e4603173d248487e78fcd2135645f19bf407c4e2c1939c5a3556ca004dba6f9763ae91790e54c14ea5b5b94a4b7c1110a2abef49916a4758e0878a1273e2dbc2168e3daf0097dfdd5ba0a708d18da2e5ad2a681ce0860b44fd54c18e4e3d54c54bbaf724cfb4b6813f6af51ecd968d07e218c44fbd6879d83808d5c2a0d081b0b8375f4388b2f11433a22be408dc9c8ccbeee66ba7ff3ea5cf8086f3ce9eb080c82b2333d289549ff37b5f9cf184f2a814e854914878ef111bc4b64322cd00f1ba6e588bf3a36708950ebdda1b985a23dbf8d04cf00f96ee0aaf14d606c4c023b4fee2072368995bdba5916490e0334dd0e4be9d4173bc07d1675df1744e4eca0990c8014fd41047c106817cc4fd17f7712d75328da168187d68055b92f1d6db8c1fcee36f91104a74e58a8abfa3226dd4f52a310c92acfa375d4be860be47d0167a636d1ae42bbbc8da57b706a3c3a2b477f3de87508e2a76c8abe9dd08f04849775e505f0b3052785977981170f587cbe0a64e0d359ee0fc8c3d4cb32f4512d9a7cbc4cef07a1196ad5c37369504f72408d8702d3d8ad640f7046e7273be66afcd14311c9a5648f521183c6b368c33f477cc5f5f39e87c264c355930a4d91858e402d5f7c307459eb58e37f8bee393eb851e201e58837d3474dbda28f2ad63c3ab41de95b03ed9683d9e06fa121a092fca168d0ee9621028c7ca956bca4166e88550c1f99fe6277379247d10662d0a1849a6e95b1001888fc3d0f82281df80e4c32d757f48010eb7121614d4811acfbf6bf8ccca1345c11a43b8ddd53f0693c2b8d05035985486babacea2c2ebc9a5da889f25c07adcff219cdb54cf5fee3d0389d9111e581d2a6f6860821e8461cb37d65b055e5b445edba4e72e81e3813cc7e2ce2808380f5b1fcd344184c21c9d816c88bbb7f6f0174689ee52ced91322efacb56898f672eb8bb3bf7fae47315dc64853d2738d83b04248fc7efbdaeb4abeda98bf0d09c5c56a1ec5b4d19143cd677b8adde5ba1416f71f01113fdaf3ac8caea0c81a6ea40a31631ee1370be81fb73bddf6fe412c8ca7e86210b3befa01e347e5de3a690f814fffc8652fdfecbbdc12cdefce4c73120d5f51a2aea265d51ba03d412402392c581c6b66343a1358b2995c28a38ce112df71df301b0dc5ce2c0b7dca7fcd7fde2e05f723fec07996bb23a2ed99a28d27abe8268f19602fe42cf3ba352021db640d9339b8de4e9e49321cd447a39bd9f4ba9ced081127381d699322ad19abf33f48fca4af7c30403a1158a0c9831fb01e42340295268ee0dd402e9dcf713fe2634f93c480ce91ec5075ed1c7888b43e710038952ebc4bdff443105f43f745f85a54dff7b418505a1edc8747a047cbccb1ed036849c9c68e1d18ac052e3b50a3d01e3d397e378c65341973cf879eacc072272309def0af9921cebe3631d14904edfe9e7d7888992127a925319b9c4927fee66c646f4eedf80e262c87836931a71bbd69921d53b6226b0787e9bb3c5851b317768afa6b2b9cfd55fb655416af6b880cdf2170f8633f300516ca37b391558fa975e5c737ceb32b87f9e635a5f702f51bff7d0fe2d6252ea581888e8cf7af48a9a6cada64a1c9bbffe719af075ff0ef34e781904932d58ddddced10119f114eb6b4671ed258c835722ad8c806e8e5876995f88731af50fd001da23c21ce210bb1c762be1005e787961f5e44b8641f0d37e3bc7572656cfa7b671a681796d6a9fb7532e6655b83fb01f5c67004943dea551f73f6268bc4863f7035be9865b8a26a55bab8b78b1ae30e55b58768a5b1288844aab41fd7f418abebf748ec13f2d1211a83a0f0fd3668c0f4ce041aa4e17c4bc3a7d8da2431cbd94b4b26b19bb87afdf4577428a2705e7b88fd42c96f0e692c8540de894b0507b3a7567980cf90c0902f4d5ab9020db63356339e82bdac5adf63fa694a62b7bbcc9b0eb40ce05475fb6f273c3438a313889215d57eeb3d81a981390ad59eea57c382bf2b94516a8f05a318c96efe98ff76e9708d3a2d51fdfc83083a06b2c1f487eb7636d34e967e0b077b42e1ea10718cff50fba366a96aebf599674f823bcee64c024d4af9d2c26d342d3abaa23a2ad6b73af9ae78f1a4f0404f7bafd69d7fc332ec3c3a2a7a1e533de7a49ac4ca83a1b34bd610e97b91c0e7e090944b650a9b1227d521d4d5f93f9ce95275f5c54099f9de8405183bad1fa9d42c386fe7e1aa7f0f0c712556fbdd2a5458d0b506f910edf266332d2ecad44f5cc83585289fd79a8e99706b7eab546c9e0d1ffe3fbe99cc8c0cf45115858d0e3f75eadf9f628523fd39a3aeef8857d88a13a8407e982d761060107e81e6350874a236bae70db4121c910ef786e921b6a6d4bd01a14ce4988a6e1407a76dd3fc619a8977f37032d6b6d85b5699a98f8786fc5ac76ac21e721741d223a8037109d0cf5e642c05d229e3b54c0613585a3bd89495f07843783b3f0c27e447ba4c7bfeb17b443619457db622fe46b4ebea267792050e682388acb1537df848a87a726888196b783d98986793173eb69296bae7aa72e563c8f0db55f9a767d05bd88097c8c8b7e7c70acbf17402ae0ff114b683f61fd827a121fe492a124cf970238dbb9aaf8483ece896c328147820fbfa0c497aab0c017f6e4525332e3048e975c9afd2483567951362ead0410d5716ba0fea0add2df48037f4141b137d60ef6bd3c0826cbe754428ba330d697b90c29af9b403ddebf6cd9dfade145802573a3e02401bcd13339b2dd41a5c87a68e9de53c638f9c562e107805f3ad3e63b2364b2f64feb456a72447d89d245d7e2fdb2b3c52bca1477a9e48d81e493089955b7ed3ef7b446f5c15cdba19abf9496282e05a8a427a64c28a546d6d311cdf4b84212d6895826cc00c2c3c709c2bdcce0496a0ba5145e69f9118c926719a75dff7a8be1fd9fa0d41cc32ae5637a5a8a495459d33e87470613ee70fa99c280090f505a93e92c954907865d8e25c8d2cfad3acef5ab1cc53eeefdaebbb0585db059a2ec3e25288470bdf18332d26f6fccf4a9b72b75dd3b33b37101ca6fa0547faa08913f3a15f44d41a177c897bbdcbb348ea1c0af0b6bfec19c8461f2ba07039c7660e3fb58ebe5c9f867840d046d7d9915b002d4a08783a168f83f81377d03b5d520d3fa53e264e158ed6bce3ac7fce428b294c7d79f500b094b64dd012ed296a954b7254170cd1e70ea7a3388e6ec9af2a2ea07b5b208f5cf16d1dcfea82fb2ebcf445bc544e582335be57b4fe637fbb8cd038229529c1876b32659087fb3019bd03a63c050087bb8f79f4ababfef9b3909e325f9277b4ae78b18b1d88a48163c2f42d24123268da63df96fc4aecf63bd680dcdf1d27f6970ea9272934700fbdeb57a1c0202c3603ac1d78ddd6506249a3f529e8331c825e3b68b12b3c3e24adfd331ab84992de2b4835bbf11f535bf046983db6643d4bab242e4eb34066173789b5f07feb599f05b7467012ed71dfde4d8a6682c68bacfc13e1600a5e7567298c89909235ba7684d178e0743d4aa956c406f218ff44ce996fc8a7feca30990899b0b9d8c4a9dd90017b132c32cd4c18f880a7b463dee4dcdddbee80ccf6411e78d1c4a71f7ec1e5253220935078f25c4ad766eb21771fc100ce8a58436295c072d031b66358584d7e95a0a54f7dbfe3572fc513efd70e338bfbb2da9e13e799bf698df66cb64cec7d9df1ced00f507d09b9777e08608ce8c6ff74cc91ecdfdda38f078c54cffa6242e80bf55a302aff4fff33cefd72fff7851e705287df0f300e1a6a9db170db8b0061310a3c9c64e1fb50e6a5fd5ee404163ef730db04d7e8ffdc0fa3563be85229596460fced3f6ab6bd4d8740557e955515ca81bdf40a661aaa3c1eb5bd44b4f8bd0650d56ec75a04b8327e55c524647ac158e4c984d0ac90d4d043090dba56cd4647117a5a2f3156f7d323828d677b51d85d3ded515d6371a0be5b1c39923e2d27f4c5ecb176dc11c81ce84dd79f662d2f71c7b934ee3469f7196b78b061eabc6877856a09702254ec10e6484b6a9c317ad7b1212b363d2eefe57bc9844f1888630272cf5525c0a823971e6445363fa0294d4a95857dc0afdec2a701779ada2c7a9e2be5965afb2fb127fdcbeb828f1eb66877daa6151f8f86125eedd174dbec31631c4fa5f701326085855ee96a00237a0f2992f5e65d85bb76023fe0eedd0a4fbec602fd811269fc901e4121b68d1c21500bfad23399d167693db32bf39f01b9fb1c56b1cfa41f80c879cf66e4c99674ed3a1e6eba2077df7d8f87d8571afa54e6c0d3b9dce84182b23022a663ed2057018789ee769f46d3c025ff856320a13bab256fc669e240b878f170956a7b96f300ad56c400143f3061a30e17540e40ee7bf9c61051759d02e09ebb442312fdc67944b12d5c22eac93b03c7e344a5dd1fccad1fceaa0b086f097d602845c15a2a10b349c082608e8cebde04f49aebbb8b81676194e6e54f3603fe4537152ae759ca458bb0af92f1fedb42f40f74c95974abff9e952edb7e5198cbf7257bfd57d0a879a190ab8e1877664f96893c736abb844dd3af4ceadff2a46bfc48af0dd6e4e6f1f69dadaeec56fbbf6fbca089168bd4ed433cf6de90448e128b5b78842ab98c50cee9a9f405584021a56229aca292a48fd52bcaa1f734c03e6f7e5a332ae8ac225587fc8228a3ca7d20e36a97763cfc864bd48b9555a1e3d85e73eb909bfc9a8fb313ab98773357ce913945e27cd23efe675fcabef5ea8ea9c52d85174a46a3045b9aabb4f7cd175b6e859d34ac399987910ca7c47f555b6bce1d52995f70c5d1356c7f299f2b87a6b54da941b35c7febf06d05b9f1c2b1aa4813246317bb9a023fdc388839597464081529f897e4d7c7527ad47d8b61deef6a32261e22aa04947025150690368326690a23fac7f4cb23a404ff4b236e913d095345b8096c98717d09da001cb11220028fac895394dd7eadef309c9a9a3b0c8bffd3f79806159a4250ad75d37c412f0ae840eb282b773fe9fb69ed6ad9ed4ef13af10359e8e0a7e3e12c5f4e1412f374a824719901de98420eb2207f404f6e88ffc8456e750b2306df5a2baed3cb2140fb2782b5407e00da0d56d2688e9b19dc41c34b160239392d7605432ff5917698e732e9fd18578e46c9b3251e36724c41911b07bbe708e1b1968321cedf318b06691cf45f533bc5dd869ad2b6b4abd146db5f2fd465e53733937cb8b8280df5109aa5a5e2b9008e59264e00eb91eab92232bfe7c564b8a9de3ecd4216d2237803cc7576e5d7dc18a661eb84e70e1199383dcb09347701382b2053d65e09342756520970293a3f76f55dc969fcc535381a83071d3c078eaacb91dcb2b7776fcaa1f6b570a5a64b3887d8862ba288764b6a9fbdf865622aca64ba57a18c6b049dcb1bb122618c1ccdebd1e6c91432e3afd326e64e7e1fbef57f57068ad5b07013c41150e671715124ddcffc20c67620f9081725a245f125358259aca088f634e7bed8da00e9d84b755342906238524977f61a7e89b46d616ac279568bf01d3f1c93550124f8168f9000aa18798c458529611e21d4d7c32fc928012d563c47a7024f312fd685e64817f1674f711ea985e4422fe21d9015b140e7c8a544b5889a041242602abb92268a90429b4b24edc2b20a280cd795053f67d8c06bf2c5d2c798e2d21830db2cdfe55528c0b731fcbfa2a0f7b543b972753416564e27e9f6b61026512125a940433129fafbce020b25d423b997c2cfc736bad2b9c75b9e8812e98a85efaf008eca361a220f7c1bd50002182d808d8077d59db34879e14f701bedcfe91e844fffe7fe064084afa28175012063a80c4e0bb8175dad282efb13651681331305b87d3f49d61c1464ce3b4ce4729445ad91bb6be799b20cf5639c346f4ada47299286a42df809a6a686cdef07297fd9024efb31d90e3894c31828d64ad1d5d050fa4f51895e02f0814dd54fc48cddb170701eb55200c42e71060545b62e4af630d1c8687276f1c20c9872b4e4453551a70bcd8ba0bd1778c5c3e47367e06dea7b753b8bc261e5f64a0155d65cde796ff0a06ce4632c29aea5090d2f3cbc37b11e38f54d104ac053936e1f9ef9fd2291f792fca8892eb65fd948fda0f79ad771b14fc65d7b6e119eae23e168459dcf8cf0543bb14d4f41c503fea677d266f480e014eedb07ec3e2c3df4628831b4b557564216828e27a928ee65c0ca4ff72473c39289aa0fa47a4e9090d2b6b88fa32894c73db3c7c4004a5e79e8c8c0c2055ad32230bf78d257bfc0774f6f91420eef2fb6039921f40faebe14510f8a69bff708a0deff9464d9265751d768448a0063e2206e8a9699bc3f0cdd9ce3df33acf5188a0edb3d1d0784d9101cdd4920fd098339a38490b6f5fc6780252c18ffb8a44ceecb634e62b169e548475b1207bba1c92c06b5f925a2ecedf38d78ce56682e94d68799d689a40c4ccc65162c676e799c9b83cce24ab309487028043dfb57466ec6d0468468723b47c00edffd7e9f49fb4e5926391ea1dfe04992bdc3cbbb28f46a1d7e2225683f50c786b671a721ff497b56b3faf1f8b04a5fe5a161c53cc325fec20cb32a51686ac7800ed140fea00426b8712d686e7908a68abf52f666a2e3d78990e155cacfff3f2ffb081354711b237cbe3fed781676d017dd0bbadf769f212b496d5ca751e1d349188d50b8edd020020e3edf290511ca7ca854a2354fbecb14a74bfb0a997f180f8dafe8ceeefcf6dd422248f24951937d216f1b2a345b9a1fe3ebbb7e4dae079ab77030f9257e9d5d6a2f00a97549d51073177b9c13f2eb59e9f11758239ef0b235e5f1bb5c13bcf305d312da0ce6dcc5a8e406bfd92260aef98eab78b72c3d3c9ff25b5a23b977916069dd35d7b87ccc75326fcd480905f09e7dad907b2463fc30db8e5b6f0d04b7346c8315709e26bc4d86c20ae131806a70dd8da8a68426994aa9ace3aa43e6551c290811bba43c22a700ab2d51cee8ec77f2a8f608e84a0f95668aed15e6e11645aa3f85a0fc9f5dd1d897f7017650838ac1fe2d2a3b722b6da49d04347b2b2a67ed33ba5248701394fc7b3f43115d0d4928f8677a1d615c0d3c6e13e17bf636106badc723d00abf96d358b1d0bbdfe3fb292d080d937b12c656679bca030e8615332c89fb5661b44492d22b98239bfe1fd84d5b20a62ab49f46bbcce4726799b36b6d06037857958194369522d67dbc16f91ef412475d9307a525ba081a8ce61d7a0b3c424b4272e976022613b9c484b2f23722777d7288103a9332978a11217f9f8202d06487d385cbcaec9c14c6493708cf320ecddfd04e55808bc28d0db59600f25bae5955ed48f99eb160b18ad9b0306a6a73c56afac0de5494f2b0ef4db499cf10fe6aa6bc8f74e48b1bbbfef6ac5e900fe3c19f6e48007fd6fa902b4bba9215874d1e7cab524489239a4369a1a10cb5ce1f513e30099c411ae6192228609a0eaca35b967eee16e0ee451e240f05b9cd45391d517f3b38ac066b256664a25668036ba303da40802519ca10019c802c79101c3d93f8dd4a9ed02e980392c5ea861c08730f0bf533b7500c57f15d88e05501b0e2daa596cfe2342053145b4c035bc4765f53d56b41ec07e85eb7ff890042eaeb35ccaa0673cc58dd083c8a15b16814b8386fddf237b863cbe9201ba3c36cae4fd5726f3c39e3e66e12b167c1bc44c1889c078df567d7194165f99b39c31094a8f6a383b785dd3fc6b748a573de413ffdad6d7aed55ec01d9213b79ab0932f2253c8c35621eb6dedbd9c9580f6e6f2edb429da22d871d0c6537708ae608651ca94ecacb8334f09b536a95e111d006b538ca4213af6471363c47b84eb4f322b3e1de43687a6499e7410ec4ab318bbffb0f5be54bc35146bc238eb609603c58bd9fdde5478daa711e2a908746926cee657aed7b0f4855e6f52d4f1170fa3fd31a660f419aa3703836e6a8361c5786b15a304d13d752bac2d549d6ac97256040016f8865eeeff01dade22fabe6fd9b7c8159a0fb5f95d82faa123106bb816b2f281340aafb8f5ca110c845fa2b2d961be94968e313539c941ecfe871358cb8342564b093290c1bcbe06dbdcadafc7dd7b5718bc83e52c92c381bf6d723c4ee5ef448cbf4ce8fe9893743b28ab7d8b1dcb4582ded97571f6a393007556d57290c898a5bae88aa814438d9c408d4571bd6dc0b22df369b390f00b073a409dd71912af70eb954a79f892ba7990e8b9d91d7435ae8b73bab208b48ffbe56d9a7888065e691dfe0d29b1fd7d51c4dfe8ef159e169a5b71352db80665ff01745a1ce4db7256b8a18e7b9f76727234218e58be579e91a33e3d18007709caa1f8aa160021315cda603d6e11b40439fcc5b33eb5082fb9e72584e30ab3f622b05cab84422906c38177bf94198c107fd91875c4a7bd0ad47ce18ff2f3e8bebff3409f5854e2ff579002e13cd7dd2bf502c4d48dd1cc985f5edeae861e19e8da6741e2fd9da849985323c324e6c42f7527d76afbbac9eb88603cbb21b0f04ee6a9f8b61cf0ee7e8421452e62ac038d63997c926f033c1e6eff1d4721d119ad8242d76b5b4aa9eec136f12928a80fa1a8094f8120ccf47f0223e69128deff590af9e47f31ecc7f8553bb01223f5c0c6bde4e26248bd04c017a4a53de4a60ad35288b5d98a58aa17f856337af4ded57de92101cb6dab05bf554fa420ff4e80f9ad486e19bbda482f711450897e51dc8607557298b0d88b8e513f582f2511a8a4287512d2fe49b3fdb0d1c6f0c36d1108d7c9cb11ed8f31dc5ee2b93ea6d4a550f5f657f8324811032b588baa67c36eb703c4b3239ea23dcddd74503dca339282786d5a01bf1473cf5d40efff8505d7288dc0c5fd271307f218a0725d7312a7698650ab29efb281c9e51754eb8be4cd0b03ee816dd09b785080aa729ea65b8c86699756c848125ebcbd06eff90f215cd043c07db2c0259f7e5ff34e8b760823cec5ebee0bec568759f800cf5fd9156c0bc1277406e1c0941ac2c59d59c01caf10c3454cbc8fb9c1e071ebde640a5455da12edc627350da0e3d1b9144e1329c9bc92445bddd483b9acf118f58fe758e51829dd79210a2da216051072166fde6601a62a781fc2f31eb0b16b57b070f06f7890af0e0b9d22b357dca7d432be055b2d1ee287ea280b3607aaef0c898f249811d1420f835f05d0502e18d24fe492c58a826466ccaf134d14e9cceeccce571672d26bdcb254cbbc037aba7789de64b139654332fccb170cffcefa5efd759e1b0eaeffd2fceea35604edeea8a7cb35c84b9ff1d5ca1c5099f4d4350ced9a6163ad9eb0456904df135857d1e3fc1630b203f4fdde510fa9ad5132cc501ca6f8975cdb0cfec7876b7214a4e3c4553be538e039259560967158e39be82066169508cc36f70a5ea7848c3ef84c87ea9f25cb046d0c40697df212f63a428be820e16d101d796817d00147890168c7d2faf38d62f37da9febb7839ca6a85783f3d888ba8eac3085f58ea9c98e5f6838c6320fafc24d5e0e5f0ee39d1c79e90c5407793c0e3c94b8549c330c99ea73f7d611c66e5aea0efb0db60bce0526351548fbae610b09ac342c18bbc1303f4061a1d2359cc354bbe59fe2f17e71f3cae6216937b506a59fecfb8786b47c4703a584bfbfc59b9737dc6cf9b61f26f72c97d9ceb227bba946292cc6496aa03c82855adc20975e076285c34a2420975d3b8f059c116f2ae1f49ee36f34acfa3a1a1c39730ca891863b1eb13c74fed382afae5fb31fe69e90d34292f5919f304afb9b5140c83b0e388eb46b9a2daadd89b2bf58c951fb7f2001ff07340d680ea154b403d5d66fd13423b848ac1d6c2afb4328f4f1d81c0f4fb7f673908ae3fb137e55372e080872cb491526c85f80bb9188d1b59fa310c2b01ec5bfa95ef1e4887964521124c9142f32d020ed1e8be6e1e7f485732a0e1d87fe2f49f0f5639a3caca04649bc8e538881859f28d3fce1404f9bdcd397751c30136538de22f55512a4a70a5fff7dcdfd2df79b8d101883b35dd06d1e106ba975c98f87f973b4e2a73f9f5d9e1543d724adca5c7fc35a329f614e0009904c5921a45c03e16e499ca2edeb35a9232c377eca0f4d257f51f9dd8141acbcab1356376d327805aa18bd32dc4fae239488fff3e861b3b13f7ce0f947b7ce7edf8721a0529be17eeef61525df450a14436d31b3b519bb90da151739d06922e671b6dcb378cbe16f61cee8a28430a6d99d29971d74e682a09a999e4ee60134d2f1eb329fa4180fb4c0b6b291b427279af6b812a082ec83599d53e6d66249679b5fb802d03b88940a37cddfee5cf524faf05ff7926103897741090a8984ced5da1bc42136acbf1ff0d8a60829636fe99ba4f94115ee7c82d61072e2748e2424a5d3b095c00e72a3732c42e0591c42ea3f51eb3f607013df92d74de5fb0a5408f0497c460a1c077a05fbce5f2235b75fb633ce1ff44f8003debb2aaa6bb12d8451a35cff392b81a9b5278f5c67dc184209ca3e27f388da41e7fd8d6a75ac1370bab47c7c5dc74c02a8cd5ba9e5e59290d834740425f6aaaedf9a353cccb5cf96379c147106623f572f2c79095f3ae5c97e9367c2b7ff53c0f1ea09c043230ae66b12feb1a30f741cd902706e54f048d70df42f4836b0d5bfdc9f1dd84ea2095b19a481a14a3714d598774d34781a405314224a42a16cf5df3878a764963ce5ebfd18079dbcc52d7910da85117a03b65ae559d4d7411445e78651d787d5c4447bf0969751974b97a8d6c4522d3509bec0883808689771eff0553cb94dbf863f1e296c0306f15eed24c24b12fc62ee47cff99be00d9f9b43b084ffec2077cf87c06bf5cb8eafa6e24bd592a0bbeb9312e6fa6bf067ff853c032be527939425337f2113d152a757a4bbec875d65c5bbf6b7d6d07176e8f30d33456386bef3c60f920f23a6042c588667c1cd26a6edf04e8fbc8138440ee41c0a38c574f10be16f0701114911817f857c611b96bb93ce8276654d14241ff10a306a32532d1ba8caa3d6caf976a74cfb5dcf03d23b3550ceff71ca0150dea4015d57c4654c2529b9a3032f1a0cc85768b4aa415c6f81352a6fe7d037072ecc3917cac6741f29ee4774b55ba5967a28bd4f2abfd4ec09d332412d49a8e6e93b845d7d680301789aaadd11a97d589eaae17425973691d901b19f1ca7152362ddd5f5ae3433d8d8a5fa90d4f9af762a7e8618c0bdf0c254139dba9be0bbe73484092ef698eca4873b3ae6c1783b66b1428edbdbc36b2e822c02a4082e4d06485462bc4a20ce87abe28eca258b589a31f433b1c2fa49bbf4396c61c5f30b3052e5e8e00fd81d38ebfd31078bb8a0ef9a89708f138eccc77fb2965aca9a32ab69fad6a108236704fe6ad8f2c98adf1322555b1d7342726a72e4e5bb4f2e5e6b7a9dfaa1400cec74d13ddffee68184c7b630a1041eeb8a18f3abc5d2f82d39b005460f9ee03d3e8e1f94890fec3eac03c902ac9e046c24e547ed7fac099428dc66f0e01636b87e9bdabf9ae5ac03f9d89a5fdc9be4abc932b5b24188749689bd4c8c95b86c1571c5c5260315ed91412110ab06284058dc7ee63b27c58031e7c9eead17249be4a8ca903ac7cfb03c6619b1d40695682857e912ca642bf13fae5a481634a012474867ce34b32589ffbe6e195bcec58739bb992880d8e26a321fce7fe650564a5a80164f4ba449408f45c2fcb84b3f82875a5e50e6649c1aa4f7ea4eb13a52e7d1ef6365395bf8eab57788f73df8e2444c3e736546139fa75569547eef3f989467c76be4e7a70b6eec9fa57f6459a97f003b66c0b7e56d200204de9e464c531087a760468ee0369fa26b0184c4132bc2141245c48a594c092883c61507aea559facefd519bf17ef88a35f12fcf05ed1ee99ab5edbe49c3505ead22370fa031d2b870026dac08a0606c1307117f4595936e03c2d85b3788bb6615511f6c6e3af0ccb835a5649b9ccce5c93040ad1e7d80f0c9a80bd93ebedf43fdeb7c7c3d0db0d88810e5ffaeee1948d2a33d7c897f249c324e58ea9aca41acea1f28697f9efd63070e7f80e6a20eff1fa89cc9be649f9dc4717071fe8e2942e13dd69390d6a6f836653627334fa1b83ef11b9ebd0ae959c03465bd4f85818a04968fb6c3b21891d07c6192f895b76aa0c4066cc7b58cff86fe6d124537c5f0f254c07d90e862be88613a2c59b9f1425901027f6db580415e0da1e674d37c3f96491bd3848a4e331a0c8940ed410d0fd58b4c504e5d791a6244a32c130c198086948fe4f55a05946fc4af600e9cdb2343385bdd98fcce38d336ff0faae44a6eb880f5ac47ffc123ddcef4f6c376e1e2e94b53c21f804109a620614adcc0b4d1db2e6cc5b198a3efb67aa5a57097617e98e21bb6f26f9eadf7ff32ab8af5540f701d2427d2d92caabd0999842d730033965c9fe894ec0b3e13d9e1564af7153fe33759ff70dd313d2ffcfe4b546ab234aaed888293bafad63fcadfe13792d83ec12c2ffd77701408e7fb8fde3de645e8924c01330c1b656d9790d0b466bccab2e6e2c185ad498276230296631972a94b1e34c36aaa00b87a2e691bf22d0b6161a194f152514ef832e519c8d0840f3a263766c3d785a41cf662996f654e01ceef84e72cbfb04db34cfe2cf1f716189fc25792e3fc243b38c5bf3ac155cc607ad1f0498fc1ba1fc824d4c0d15509f6b559cdf17df6d846c3bea26d4bf010bd13bdb7b0684d80f5c26914a718e9264d0dc459dd9b2c8ae9ca4be2efe78b75db22615b1eea081844ebd54e671e560a6f69094e85cec86ebd3ac9bf5eec74fbd26a249db038c8910926524deeef9bdea298b7c8f8a4b6670b726cb5a224c2ea2e457674556d0f7a04ecf940926d2128f82e090d0bfabdebf1dfd947ce495de52a6f059336897e8e01c08000a8800487926f565f983d295cd92da6c9faee588334f617b47633de14e5f120119ba8913adeed2a46267adad8eef44c402626537428331bfeda2a9e5bcdbb461010ab28e0f645bad8c95da211c293588ec56bcb724d2fe0a03379638fffed286a3f9a627dc0d0309bc1de35c663f2a272f122332d3dc5f250e0ed3565b837c79b366e8ce146184bc141ed008a63d9ddbabb12a6b19b21890f7e19d6d649db19edaf78e45eda899549ffe3843822f0434ee6a8a9f369bfb4007889ecc96c0cd03572e4cd1369822e75a4c232fc8ab2b9937c8d83e647f5f15ff711f404682454a043f81a2eb3d96bf88f9830ce33d14bb28afdf98e0cd55669aba1c892debdc46c66ff4070179a93b38b096b1f7d945ffc13cd3c434379a7383c974b5d409febf74189f93be06a23fde9814ee34543d5bc24aa6d86cbf48feb4e3a0e4e3156643fd9c93a729fee900b903d1a12292f2e3a262189247c6df141eab39ea69eaa6646e3b60d5a6760f7bd3eda63c179644b4a6662a4d0374b08742bdeddd26451773a11c18a3ef336e71cdccaafa7914ab31d9aa77402231fa5c8ce740ede1fd79522b0084f5e543a10ed5a8cd059d440e1da757d6f7489089147f41af3e09ef6df265f6341888438fb55e58429dc9d9ee98e834a37d1f7648329546b526e038e6af01215c0b12c285d8aa98379ad372cf791ecca2428ce54fc3d51a5789d4da0e6a87bb2d0483b367f4c067b05c7479e963c624e6146222d9c42f496c007cb4fc875150d61b6bf9044b2d74da10dc6413b9ab79be488890b767091fe5b78d504f4eed014559fa9040355d7bf2d17da348c691118a2271c90faf09c1f39c790b6b584bcaeb407d7aad55f650651cd389f3d1bd9f091128532fc9d7d3b32995085341b83ce4b54150b9c21c1d94af15ed359ed0ffe885debbc36407840182666fd34e247a52786f505a8254d1d9eaea3535dc55b0c5180b0a70cd2edf0272c8d92230f4a69e8ee33c2893c82b2db1796815aae1058cfa8be96628ec7c2ac9c91c71d0f65208b5db8a6f4619755a291a08f1582c6656120f2194a6808421616146b9d714e09bd11e4e9ba83bc15a67bc6caf4abc97d1ff0e465c262574d3be13b41ee54349a0cecaeaaf02b111e8a55222dcc4f03d85b1b2568d5388da77e9fba7a6f963df44777a602169196e04b507bc8027ec5b7ed9c47d53e6f2fcfeefb5248dec94e88b8247ec74e13eb1828446d3b45673d3b503bd1af832d5311bd2da2477a8bb4267d13c01d810e9ef9fc099fe42b9e153ed3872b208a9a9ae2bab3199c9da54332eedbf7fa17fb37e32bdd1b1915b84076a4d6fd70e131f2628ed53309409be9ae43d3937880eb03110848dc28b2c26c5a0fb5c8c66403c456a360c84a3f2da47a2156218cc2c29dc8d69f90be83bceb5b9ee1b1cc67490fac8ea896fc5a7d344f4b25b515ba6eb3dc0700b13d48e13da46c4840fb8ecd4c85b0901c8257c7a4c0de62db44c12e8539530b7659cbb7e475edbda8e89533b5d313a2de78b116ab4ade723c2bbb2f0e32d2528128aba98827e1733240cdf882e3825e0860667abec9d8706431bda11e83bb108b6eaab295c5d85a401b48a62bf40ee10ac63f625481c72fe2f614fe2f218d7aa268015d977638ff6724869781d09ce7b5b2a7887bd1fbc0cd3fdfb35f5697d3f94771b1c5315233843f5f83219f9d15ab27cb90e0b1fae5014461484c6f24ea2348cd59f6e480b997267e886240ac3c03154e24deb335b29b001f502efaa9d5286a2d0926534461d4e61ff76060d6fdd747bac793016b6c213a43ac0a8067ed3621c029b1874d847d2222be4fff0752e8c93312a784c8e65211c48e7a10c29a5667540dfc526cc222f8c805cb87a0222da5d705ec73f478c616a0bb4365c3f97dc7f858568293127c4a16b6589117b4086da3ee70649dff0177c1bf4ead279b5535d3923f316cad354b8fa1752f16a0689d550d5c43cc61782f0a916439c2dec7f23c25ffb42f474437500e293645df2beb9c3cd8d22b501debe296ceb2b4d0f08cec3deaf49b214f3ff82f4e1a51ce9c12d6ed9c2b347f1c3a819549e2b8588576a377d98561e100e766848903225b49c6bf01a736f52c38a9c2f005723a6b16a7bd716b2f80a91137189f94b6164eb24dc24623058a622ae2218f6ed901bff3015e09f8e84e3584f9c065fa2a4875e0d4feb8554620ac9b4dc0c0d8bc980ed47f02b50cfa6cc0d974dc7ad3675336f2a1e7bf7c8df158e25fe28a3ea59feb0fb3ebe4db676e38872d83de190b7783a075e537c10961a06df528dfc8a22fd5fcb2064c1ceac93d1482fbd84c20d82718f7545e4e69d5deac4020604b84d8f8e394747412641b33929db84f59719b15497a9c562e6befd9c3a2cbfa9112de6e1e8ed4b980f4ae6162defaca782c2191240365b2c5b3e7e957a7470a264d6012eb974b8b4c439bf424512abfbb5b628d64b1fd06534b9f768ea0eb34587539ffae4128a523a96ae7c570bd7311d62abda2f9135cf3833c188626c11e7a8ec4f799fe3052e0961c0d72a3229c48bfaa1f45e6c88cb34b94e88516e386c281007116616f3515fb2a440969ed04d7783f3cc5194079b5c0c035c61f9836efebfa18247314ddce85fcc84480347a06f9fec7db14b8462b2aa7a81953ec16080c636b2ae26d4e8c93af6d6f9b28c632bb628c3bee9b95ab5d681200c48e517a781aff52f726efe16f3ff81d58601e967428be565a72395c924ed8c21079078a06c80a59e7fd4be72cd3cdba0a8247f759caad323fdc440d07b6cdd84c77e9285d158e9635e63fd2d278d447b7ad4d271d898e011a27a25cad1464249330c39d03fcc3a85dc7c12407b18cc06cd6b244d6cbbe2a9fffdb3c3c75f5d47cb026e7c982aa9c82255b6a09815d3a0ef40020aacfddc6c29eedbd47cafd757ddb538c54df92c8a0939cf5c3d04d3bfa3cc445c48a137e1a298ca01ced7c8c613a4eddf68d20c2ec5b85e63998535ae6c7aa65c41335f336742ba267bef1bb02e923cc704681fd5f40b4c8a769f9fbdc61db0c46b95c599798333864207c6c40d56f89e4d3a5656c1fad6079583ccd4822f5884db8c95ffcd6b252ca60b8c37c612790dcfdb7b7370e43ab7c4032a79c3cf3ac37c5986437d779b29cf635b4a40c7dd817e1aa77e3b2326b9cf6aba6ea11a7f04b70f22369e432f33d802d77176f7b9101fa5294812faea30ad6acfa0179eda1870e2a585248d863df68fc069541775d984403d12a2214df8a8e60ec06562b4724c929014dad8dee3015b060e822971ac214d04a0a76b8e64681ff622e00032ece4e8017e7f267e7fcf8d3309de9ac5bc961993b484f0f1ccf94e8303b824d0014a8ab46806fe0ea8c11d17ce438437387cfe8e4099ea2286bcde7327a7a459dff8296a7c33a33b4827700cba56e61bb61d02ed816ec8876b2f28f24057d3fd6ae0ae59ab40adf6e39e0215f540809ba3b0fd612a57c88329ee795c785b208a238ca4b375baec9f7783301efa289a7fb33a3dfc0107b8d31d87fe02690a2a090a04a636b00cbe38d88f5f5d6c0b8409f55b30516233a75783887ea50f4d4b41334f08f8b341ac7cecbc69e0b55d828d09c2eff5d802ff21d5081d8b1b49dddef268fa8f376f57b13cadfb6cf8ff00188048d38aa2f55c19915d744513ca36bbef8f4d153f567cfd00340a1318a5e9869778b52679463240fcc221d1a8e546b8356e9263b30cd50d2719d334c893c33114c6825829e9d402a98ce858b013d84ac5229610652169b3c7f98410df290f6993209367ee0aa3a72d2024fe299f2beb09aafb7cbcee04c8bac44abeaf19da3757052511d0af4f50fff8157393d81acc0e246e3252ada0e590b8e90da1f1bf6bc45a898cb329f7651b70728e0a462397096771d787c120b506a9c9fab450bed0205336c7ae4be5d29709bc7f532b4f20d0687f6bcfd08d9ba3bf0eab4acbf3f85b2c89fdb002b39d735c8d4d3adad8ce695e1d1075d7c1c094b6bf82e22f88e2ba28a6fe4160c5d2b0f470ebb1705bf30f12e3d7f88b7d52acdfbe08968a08c8f3ec03dd29c007190e39006f8cf2418add94faf354dd638de7f96167806e84cdbbe978ff3011b030ec8caf81c9c9824abe9c6527abb6e300a722911d06b54aea903ff1b6c1fb24dff9f6a0592e47d3aa2694f5011f59d18fe65becab251b294ced6759b36e22f7504526366141db2c36f554015266ade704d215ca8d792885bc9b1307f1530bf797a226c17c2096e94b84d490d0cb6c3589bc5677b826081008bab0489b445dbf623d44b8b212436cab9a19d514fbbe9b8c51a0a7e85059584ad78397ecb16012e600426b2d8acece8ffd620ede3fe8ddcb7b2d1c8c9c5a032a8ad4f99c3d078330bb6543424ec97b3f04e0c8437fa7394b8fcdcbfe0b99dffe955887163332c72f5dbb222f2346e556099a18a21ddb081583e574b3133c91a7bfe3d94c2b8a57a9ffb199c9b24cbef08a0fdb137f4c2bf60039ad86090ac6905afeb8be73427cf72e8d59e727c84dc3dc808c15b9628a450e7580fcb168d0629b85c63e90831e23156b25e98062cb49d08c334f2c6c083bdb67e80d49ef45e81e7aacb0936cef7f70f8e1e1a7670a2789c263fa7332b5ba9003e126d2dee13d08399959ad0d7774b899bc7fd49770cafcc8ab49a77e7c74066610342ec5331804f5b48e44175ba9effa8f6fb402e28d2be2fc533826e8fb47c51cdb41a174427761af1ef3c17c0ed4866681e0d57b5453e9175fb3332a8e60499efccd04bb8670c9c42f5a4070afd72f0dc10b3f6a84186b0ef2dbfa02337096b96a4fe1f6aa4504091ceabdc9ce0f00c58694146e922e90bc6f78ade74353859636a23712628539e746877f9946ddc54c11226319071cac14c701d86a290a55f8c340f81fd8504db0d12267f81eb47d2c6cb82effedb8e3bad0925d36ce99033121e430e0fa96a8992e94d1857e48a3ffbbd3ee8b16d40c1419c32748ba0c677440ee56d41ab65258faf16e32c15ede2dc8fd4c6211314fc006326e9103a1211872976e2ad6f69c1345e336cbb72c7f2dee2921a538283a04a71ae6fcce119752fe032e7555df02d516638458dc23735916d4f706242acfad54d1e685c141144bead79d99ffb48ca5559922c52be72a9566023b358cfd33522cf0b5e21350f739dd3e7e2403cac83dfe49864545460c05563cec7991459acef6b243efe72487dfbb1112f1314ca25466d28e02299020f10eb53c1903027482770db5c58cc50139b38aa14ae682c46ed563084b694b566ab41f8646ee6005fa36cc1474b78f4c0d532df7da0d8248e8dfd2c24f05ba22f60c48e93e93f1c147e59803731329d552f6092beec00d8be90da9467ac8f01c2a6c8471148ae90e742691993c9e626218b14576ff2c1fe9b56beddf6bd98d223e9fba018fba23968840ff2bdd00e17ec7aaa413b03e9f9e3eb7bcd232e82579018d10aba2bd00126d3bfedcd2c9f94afbb2c520b92cc5d88e9e476a1c1751af28fd4a0fcbb1f557afa0eadd124fff15d5baf84ec3cd07cb1dc63090a09c0d37f6a7d870c5f18f177ee2581e0b674f0d3145f9e45e68a805709198b4981fc64258719e060cb9f44f6a5dcb64c11712a63f4b1a7f38c2f9cc73f3979275a550b6bcc8236ba78ee4e594ac1fc1d9e00e1f03b340a7e4ac929c33dbaed1117610f810d0c7f7089cec7eae7c4e93e931b8ca66578d020f0fc2c7d7d576db0e7137bff16f7b7ff0b0812e6f0080fa2f5729d5c3ec5fea22348e718c04bcd39d795900e083984b3203d790f46f62aa7ccbbc23e3c71980dcd539da9ad924acec3cfe929ca83a2366d54cce7e45c4f28906c13e283421231a9cae2580112daff7a9f61a2a32e8293a63bae5b19abda9dcc7c2f5c79fcc4e80bc73c4317b33fbb8a717a7598429422424dede7bdc5128bd759960fc81cc1e8936ac31a1148cb0ac8c39cca81559d7b7197cd59ae642bdeab1ba2a6bddd43ecdc6392d61d093fc83dac95c446d1c7902648335368ae3bf221893c9b5846863a3c63985dd1b6ac9a9b3046d2f8a968b2c8c7ac404f90260d83a9a65aa18226d4e668b88ebabf52edf9e34845238ed7e4204523d217c7c9f1e1f46c3357170de07c47f1c26b7828610378d60c6b89764122dff6790ac09c85ae22ce77a033fc6326e03b6e7d354cb4b160a6c1282242a1c0a357bd21f4237f198eabfc9e0d90e075b9cb75a125f2f8212fc347bdb1dd40e88b7438757d3294a34f067797179b3f7a6609be60c4447ce86a6a2f884e05c31da23a12c45b26c7b5e6058959e53c5cc7a13752f484192ae949adadce017a25f2bf50a3834389a061de414ad72e9094bd7a7f4597cd4a4b8b9e14bd65bf37ea8c5564aea08f11023453dc8f69eac247570f7e3af255d71aca1173be18360ac8d8a049ee7f21f0f3e535feb5d40a64dfca49c509f5e9a9ed29db7013713f46df2606cb2fc10fc15af4a3140edc483429647a5cb716ac91941960a0a39a3fcc036403eb86d54bee831623c0526cb15e152bf9c397f74e4904f9edb87fab24ffe1f977bccff483308703380aae8af451b7a8fd8770976515422606e22bb262f35d7b7ed575635ab61cbe8c2a006512c76a3815ac55f2f889fbcf6c953f6b2660216a92e3b6acabca26bf5c024161e49007e26cee487e833e3f2654f7857200be0892b0743c8ac3431ce75affbe8f2208b9ddc5e87b0882b7fdea60e111ac0bec4b7f3d46ce0f8ef810c70feefc67e5896f5dfbef5d9d0ef5d0451caef644ffa423a787ea3b8552ed5a58a879ac8d35a24562ba5a3502a62bb6e2081d2961ef69d2d8f480f3ead4bdb5d50fef7d03b7a089f7e1f313fe2b6a558860e1067007d225ae1b4ddf2d334e7225656105c7d41e1d631d29b2ddaff104a64d2ae2150e0fca922a09d360fe6fa469ebf45e35ee7270bbb82748becee3da929e16f38e1204172d6dc54d389d7ae72dc4075a559a39b885f221d8a158526e2e51a658280d2d096ca6f02e6069ead9abed5b5005fb6358724bd352877083c067a367837fa859f8bed2c7b60498c073ee9537c25933e9c35da692cb81e8a401a8f2c3f74b12c25cfe39fe0bcdd7e3ad77ae8608d0354c0567adcb9853964fbf6565015402ebc837065aa63650452e17a5a8abe934bcd842841d24417885ee42bce43d9f5b1ee1ffeeff2120200ddbd541a221ee3218811a99703fd60b89356b07a1ad4f8eea1dbd521e337ba8f81b81cab548f3885bbf13b87eb6f5caf3af63f566ea76f02b7ff593d0615297534db3d004e165a5cb328e1557c7f731905e515376617890ffcfa114a14f4167139c49a3682d21cb473f58b44e99c75bd41518811bb1d7192b682e3e994bdd24e0390f047d6b998fe5a9be347639641dbf0e0a8377375b7db6c46d3b062659eb5750731250d3eae632a3a01bb41fcb477fe4f6c83daed5aeff8cfa14ee24c55f28b4c3a060fa2e8f4cf515eeebc05c52490d7c9e6e84f4672d805027731b447928117e782914b606765fc99ca8b8ff9936299e2bb71e42225c295c06eb0582e441e6905597d42df877ce709f53f8becb6e91bc27448f332e679b4d78312395667136ea9143bfed3f1dff85b5be6fa3ad27e48656cba54f21634a2cdb6b683ddebd3636214e7a6fa82d26d2dd4a017552bc39288454779b2cc9b0ec74ce9d3f011f5d90663e701fcf336c6ebb6eaa4333a4200e8adfbf97bb906dd535049c2950a15d0321afb18d4a5eb51d76dbf9689ea63a900d4932da07603c1ce39fb7a7f559a98683cc454b9c6ef65c437cf73e38fd52f352ba33341bd00e8bf734870bb0b00f2c2b740c93c36fab4217f04ea6fbe17158f4a3a5f395d5af8d5e9233e6f2f5776a09fc4b7909eccf9efc626b90b841c56f4cf9ccb5d1b1e1041b7d0d3d3bdc93c494fab3b1e7972f4a9bd8252efdc767f614faa524d72f0b044f19f187162e2d82291c4cf8293992b4184e3a1ba8dc6928b3dc93cbaea51d14871984c92915c9aaf0c912b2a45a3f2450f7e235d947484ccb94b68084b78acd4292ad14f522e443bf89c2fbc677ddbe760adadeb8122e6308491e7e71b037e7263072a3a5b7a891f88c5019e12be8d51be19b67cee5dbbcb596e26dc49645ebfea717680e7e4c1d2067b908d6a37f540bc2484030d257aa37fc49eba8c317d27aed4af2e33352e6a3351685b0ae6ca21917f813105128aeb5fba86bdec716ea182461461cb749f02e464bb058f9e483fab2b15a0f568a0bf8af8d44abca5792b5c3175d7f9f341c3f1da3651028c0f15d94b5873da05fb454fd0dafd00bee03f605650265c8c927c8efbed1c12edd4f48f09865395b95ae0ea7d154f4648a375f114a4759209838fe2210794f6575bcd4195bd06457c90eb1386887e2c83e45871ef7675bbd38b44551479d858da309838dbf86e64bb8ae1c9bb01427fc7825212e5a5f0419097e1ecd0acca2e7e6fbd99b38e91f249c4f7b6a21a78664f606d896c0ece8de5f56a04960536f0800c14617cc4e2587df20ec87ca8b2d44cfc81434aac8601d6ce65324346bd061d1de022dcfa63282876e651b46ae37ffaa50e26fa8887e671a77477f12d2a69443127f74eabb41b17d150464487e496f7502e16ea3bbf8e9f8d9325c10e35213a6c4248b6d47a4c592e747e3e86d21881984d7467e9cde7f42252b2e28477eced0f023aab6108c15a43c50faaf1fed5f9d8327a3d10e28ec47153b0a707850f05ce1301b9ebd01c7528b2db851dc5e886f852ec7ebb611953747fb5b96c9da85ca1361ae6077f3a888d68ad198eb05389aca005c78492e55bf02cd07d95fd7336dd209d23b08338c5319f00f03caf56992894745c2d36a6aaecfb10422c16e66ee12b936b5d290f6b3a337d9aebd1fc258dfc33629a864f3d9e6fafd4f8d71a819c7737baf57386e17966580748758bd2ef353d780dbc4df2c5bc83b2972399fce8039683c408065d07013a6322dfdeeff94f15cfcb4cd9df8fd80699152b577a05893df662ac96dcb23f904ca85b12bd33986fc1a038fff2ae668db154acc2af33a95534d8fb10b3cbf863e2333124321663e550e64b80793d172f93104f583affb9ad3939bae0093a45fe409c0d18d938ef096ff6ee6857a99f968683149e76bb0ebee53234d4c9ba78b463da9c4dcaf16c5545b8b6af2de76d8bf584b76de02d2443d2b97ce793ea3a75301848b54a2e56223c6690852996f7b21b288543bb35ce2f319b4558830b53fbb026e120e82e27bcec15e2586691ba49a25e51baa372e2f4a0bd41ea0f0a2fb0f641dda07cf9a01d69dd00054d2acb57b4ef17a328a82fb1501e99a73e01a77defe21ecdab4a3e393b432d3e75cce7ab3949c2774b6da7ae7fea7416d84403052ba985ea6c1b6bcfb600f1963c53aca9b0da319f3cec8e788fc688a983264f041452830b3ba40bdcb4639564f5832c11993c31604c475b8f088929301d7ef526b1e84d65536c3c34a2a1bf2b8b5ff0c1f8b3caf22f3b9232029d685d412ad5b64e680f9ab17c218407dd7fe8fcace9af143dec2b1cda1fb0de01fb6ffe200e87d08ba603a512ca483e2d229a0a5acc863f70c0b36fb033163448de18ad134c503cb577b53ac06a57065c4a43b2d59c4e605b63693ed85417b0a1f430de49e2cf6f3671e9c0c770186b517808f7e89261c2216c6bc739e4d2f3da5603d42bad7435bbf570c5352fde6ef76160e9b7aa632a3bc2550ef1eb369fadff4732e56b207f1005443af86fd4282af09e8c361d27f56f8fb51782e865c9e07ef298f53232b443233e359b7ab9ea93a379c4769e4fc4a7e3c1e51fb63e76bfb31237f6a4257957552e7ec2bda6b55913024a7b39049c9074228182793c31968f75153c739fb7f71f1cb8b79c202e06ff02e5a05d57b8b12d5792c084213d6425e510d5d6a4d5c1f34c2a04b2293a53460b65338e2f2d08e585ada438116f77399c1359575def552c0015a6ff6340b57e26d299419877590bdacbd11ac529f5c871c92f5c24ae0e0515d6dde5e225c2f3386aa9c1526c06aedccc21516efd936b1fd31758d8dd94d52d9e97d8f98340f0a61bd029622d4b3b859285d8e5985ddd91044428b9fb78c8ce547d20a550d24bd6225933814c22bf2dd9edb6c5ff5c77eebef40eccd97e5379f50d86856072abefc149d5b03ac079cc8bb4a16a17c9e9ef1ac1d54bf2b5d9da081aa63b7ee2cb67c7f4689995aedf197aae90d748966c066e2018544d02e58dfaab211dbe3f90b634c29bbfb9215d036f8609717fb5f97edf6f1085d70d3cb4790333e4d915983ce8aaaee799fd61847b193526e7d1ac39c6ec07e27e938ff1d4387a7ad6d822ca910a4d4502459ef6def0b3e9e94864c36433e050c7a5aaab6a8cf2fc41d55ca7efd26a6a3f1d8685425f5d6a76738b68053b714ca9a406129b495565e24e4122d49beee7a0fa413e736a25c94e420209d439770f11478dfe03adb99324b79aff72325e3112337240a097ba21bb43974f842d9872c754042752e82cc6463bf9857e6b57a49b5ba9b6c22e2cf77e25485e106d960752a0d33fb7c3b490bf38c8f7cf6dc2a71dcf05cec1dbd722422ba7a59b34b700a942c6eeeffa6a239f88cc98ff574e99042eda501383a0366c59b4ffde880edba0a5553a6650bc1b6d9a20bd6e459d52f4a867983368ae2c606bbceeee9c3ff6933a270fbaf1185ef5def747e2512db01bbefbe102a3c57d2aac0a2ec8fa704d8eedaf58f1a4f9e075c6418b8de54836167158227553cce3bec58de3192b65665aaa4d9d9d4fb60ec1f2e4231d805feda6e82a54b419edd1759cfbdcc820e413a5aad032269d38f22c4081443b2262cf6106a95df42874db6c2cafd439fb494b0ca345eef4d56f14ff148c83ce2fa49f5ca244782e9673142ca76a98677d64f834ea791342e709a5d37b9121c64334f159c1ccfd20403992075e23523748b4a4c00b1e64b0f176a135243604a996222a79ffc10b792450e39a887d395f416929f5fc3073d4bdf26027d3920c0144f0a0a58152685a1de33799d3cb73bb44a8ce6b57dc21a8fb5c4f81cbcd471fd2f50c6776cb332e94bd8a688751d335d8e7ef55de903f99929698f204dfa6c828bf0471cbb8a020d7dd00f9e53bfaa12d10d43911b7c3d59c089b3ed8018639b4468bbbfaca055cf16db24daac51b8d75400041343752e8af0b7192fab307848106a6ce36fcdcb59af7083d7a7f0426756847e07c15c5facc2332029282ce3cce06357b200e47212ad4c595f03c9ca1812239e6780abf73a09e14ab40930a7b192ab19c55cb55d25f3f76b21d7aa73c5e58b752cbc7768c710ca756d3209a8ff3d1f196fb2e9681a9cca4794f5bda8f5ea41962096f2a1ab8d4e0022a411de0a3d29bac7b6e1f850b08ac96fc0b828ad8e9f44c9083269ac8760b1fc61b9af8c078434d1db98fd2b31f599f84ecf43ae220e2fd8c6fa08ec201a91450610e4035a64e0bc385647f4473ca1595d6183a68ba163d3ff2e4d23b74c110c7281d0b1283e927168a3fa8a63c43f44a1c1dc4edb87f5af8fdcafffcaf80ec11a94f0134e536dbc0a5ea455488dc5e51092ede0f142f1afa9e8f8a3a1659d3a768d54575d4d9f474bf6bb72869ab7dc185d1714df87e1ba919f505dabcd79790256515d7a300124fef372795b79e5c409f09d11820ea6e7548abfe5de90dc7abb9434f2bbcc1a195402138f14c119e659d6294716ff6190daef80ca99484fd95724cf8ba184cdd47679c2509e11c6fdc286537c8d65511b4d0be608a52f10a02392f617210637a7fb7b87886476851350c38d6b029ad7a0d567f98c6b4a05fcd8ba8ef76f39de4e74bbb6642c89c18c01fdbf33a3f59f0cc9022db85460bcf77de9d4d43afddfc0379d860be289c2501f1ba6b1d527f0714b6c6a710cd6527ff6c6afa9a80faf368152c579c5415439ad7d6c143a6b55c388ddbde53d6a48af542e02a9282b14ac32c16a3a9cdda3bd18dda6401aaf993c0df540296540af03c435fcaf0618267f29d75eaddb61c72e352670a2847745228ee6c9355c9b78dfaaa79c15adcc043c01452945ae57a3c7725d011f71c5303e61631ce802c8e3097e1141540879b24f716df8d6d326ee1c14799505d3172254955480d8cfc45f2d2fe830d9319b6ffcb449f239369f65025949292a43b7d3546fdd0c81bc42d58ea063a42cfb10f1fffc1ea3b74d43e8a57b8b1459310937bbebcad5caeadada80dd53424cad7966db5389f0b32442d0cbd440f0ff1a7e043cc36b9e92bee44c18acd5e5b86901b7a546db6b1f07f111ddce4dd0efb4180fd0e1f29c37c2daf5ce4a3eda5b51a095d05311bf68a775e84746936a07189d168367792f6f05666b9c0715bc91efd6ca4e5d17479cc433cb1d6cb2953821a2be402347d23f41da8473136008bc46d0dd14ff0d210bfb5d7491f4bfe2fd663a67aaf49df76c256ed6d8883eff7817945902894d8a9c7fd999cca11a0a1fee4d2c84ffb89a7abeec65039a8e7f2afbdf25702db0b026437d680ef8e08368b9464c256c46d23d589ceaf940a08e138c5ca454edc443c93fc892b9bcec7f74a7745fde8676368b1cf525b71a3e10762cd476b20258cbde6bb04a65017d3700b897814e0421f5004c912474d547585d0bf6abb7d415c635837ce813c501ff4b722aa37c1d7dc7122a1ea4492cf380e76f325562826d70b540556e71d10840dd19b8f026a3fdd770ae7e519def73a9da84882f065394ad9163e41019ccac17776247622559a0ca3e7bc1a2ad76260e2b8bdc1532b01edf765ec340dc7f444dfdca69773eabda078bef40b4c3902fddbd6f680cec9562eba56a15adad5901a7a717a3ba01bb04c826ac837d24899e5733c15e7bdcd22dc789650cf7b882451ac593938cfe9d3ec056b78cbdefe6a074143618620c677585ffe415d0cc885e4da85723b137bee3d423e8dd01f7c7d427026f378f101a2c8cade27d6c936f85892c148e2833ddca99e879d4495c421112ef0584b4bd4dc7ee53986b81bc697e9ebbcf29475c4ebf62c17c89a7088aa83ba7150abc840440eab6496f8202eba20843ffaa3abde38adf842162e53c5cc514df47cc7879742765469972ccf3ede7a7d0f3e44f1176a39c61ecebe90dcd47796913811717516451bd4e51465cc9b7dd2a6abdb5d40a6333714e49cfda0f24dc510697c78c7c934e84d76b1495dd2b09d77a4e9b9e40ef0cfe19b047e7d90ba00daaeba07436bc3b97b124ac536e27459812006b1bdf08b7c4145d837902acab15674da0e0852854c747fbfa695c3e22f0e20c95e250e8b88a8f97ea7405d42491923ffdf7c96a7cfee5040daf4863475b9f2aee635cb493ff6ea7ee87b35b9dcbedf6e95e5950c5353080a8b8703dc99e128c6192293c3dd1f7277c2b7abb3c7ab3fa1e49a35a570ed2bbf65925e48f99f702b91477aeb16330dd4f4845dfadc9d913fdd8f0f41d5e825086dd8d3f2c586ff319c04b98f5e7e9cbd17854fd4a09f6cfd4c9ae2ee048b780aaf2906b880dad9a4608fcd37d73509d2839596671634b5354012fdd27f61221ba31a0e61ec05bdcd7ddf24276d04a444b9a5452cfed8e57fff53ba52fa028e5f3ee5f54626ce563c748d49286cdc8ae22dfb6d78ffa403034a9de83c1f5a31de56ca78f01046517baf7ae402136b56b5ffbb4126066478810fa8157666f0fb48f3efc00ddd205aff6ee45068982c00b0e36b10b133444abe4e204f1ca5912df5a2e617b42eed613d90d89a84b11fbfd151fbfedf94de1449b5cadb7c1e3964dbd30328e074585a402a0da4a5109335ee03161c6ab7a6ac32a92d8f0d2304badf9dbe6167e24b14adf362e19fcc5dcae2b4e24275115efcd5baf35626fccd30fa9c0b42c561cdf965b291362673fd990efec58af9139c5926a046a33023e0e31748a3dd31d0a803c112258ae85750095bd1a7bdf48300e93a4c3a39445698688550a6c1d8e6464b24d09bb93d163b525a8f8fc16f149d81346cf07aceaf5c4c87a27e87bfb8f57e60b6415a07328bbf589afc4a75abbca41d20d8e77dddf550506cf2ed3c12477f398379b89f6adc3e2041a6cb729f9a1d4507e619f3e8efdb56852c5b4d7d243d99312121ba6ce90f76f1d6fd2c0b87a1c19be4c76d8a7024874f39b17a00df2bcb6851527192a46899e3a45a03fd540462648e135cd52d19e97955e87df1df38fd06a1722304cc7ea24017043b67197b5a50d58e06927c7d4e5d167c65351382419a5e3080364cd40b21ee90fb0aa7cbd6bad46b5f1109e6ed499909d99e1fa220ab5fffd1804b9c8204ffa34f943383f98838fc7407fa811e7b6a2be44d7b38b99bb2b57b82baf4d91cd037df0c678021a07267d050da231d603e00aa09adb3017119f412e137831145be3bedfcca19bef608e506e118e6606139209f12a8df6d3088a9b4ddf0da6713342765fce7ecd2fc33415b403a98233519163d90ad12a836843641b62e110ac7f4cdd95dcbe5f97c9332f6d736e96398d17970a06412f8f543f2422b086eb3da5b7ff1f3a1ad6fe0680f0534db7bd7fc8850eb3219dd55530002d519e195c00052858cdddf9f590886c374ea340a7f9ed172fd001bc4ad9ed35729f2e3d391bc331d88d25b81d5baf7733ffe8449ed90a37d84b07138f9fefb5e709dad8dcd2f796323bc27fc008b702117c91317282932eb71d95ed6b28d75aad5a037e0d74588c1cf10af3fa5d59d8f6abacfd3bbc51f269f282fe95a7c0a5074f651387ba510acec422eecfde6db424ecdcb7d7be86ecdfc6c1ebb10658d80a2dfcfdfe9c5fc708f4945f0a7b832dd7ba083e5b893b7a25de95b1b976f6a1a83a9369b3f280f54433ed7dce9ea3c87dfb403d4134087d34a66c7f505a661db37fd0ed7e586fc2fcac9c63f878a761a4c5a2e5cb390eb7af3a78ff3eaf07c740608d9a5632b7d40b23beb8680b2613119d73d9026b35a793feb9ec6e82a338430cf58699bcb10f69c4c74cdd2a54a0837a54e18b861086aef03051a51a565780c682c7d8f5d16537d4bfa74e73d1c9994d6ac07b572a9c4dd314072ab4a1923116cddadee57704b5a024be40c8f08f6e5446c0dff03b2b1cbcc246564d502a1b01d58933600e4d18b260c3d1ce8884f345a7d3e6f95d237afd199c4ce60b98ed0f1223110935bd1470e48b6dcb74190930f1df8da498ce2f877d4f139f290bf6c399db1807cb9fe6b9c3c1b1f6f63817b34b0c815f4c686bdaf6ff993c283136b4975df4caaeec0b0e3a2330e6d584910d865db2167b338d70293f2f6aaf0d4f6418fc49342e71c6577e986a7a44cbc2fd1c613ee64f9b119d7f42220f08eac76ed9b1b0a45a113e4700d9be34417aeec80dddf35f3266a82da09c9e974b703bdca625244df6f8dec0d5f364337ccd6d0bd4815af42c88c5c9dd90f5b1513652c67b08f04e55cd3240d9a8b1a44910992099a09c694eb934cf181f3cd3848903c17e1a0f241505775122e4682904fa2b086fb4cfb24a6a4c7d50933b0e58e62e6cab7bce6f54ba413e77c501f8ae6e2930b1942f098cf4aee788d0fe3d22f0f083c4c11129162018748423a173d1362ba87ad00205ce43dd97ee7712733eb4573341982be36ed1ec56557426e1a356088fd5e10356c99715c2bb76e6fb5b85cb7b762849f5cefdf4554bc765466c50414a66ea3a75ec607346f0dce5d1d7f41659f866d559e46a4f6f96244a4fb7723e590c49555996824a1acd4fdc7c6ffdf4deba66387b6bcaacf2ea1130aa57ad95226658a2ddbf28c80e2744fe69b80d2f96d75fe19e04ead92061bd46dde558c6b2c46d7fc244e8fac4ecc213c0cca9ef5609cbef4722c2ba0ade779bff2b7cebb7aac6ea2c82d289420961c53ea75a4a8a2164d7fe5df4d63d8fbbe26e22ae3cbedb898ce42b4e95cb75bb116deb69ceece5d2ebc2add8dd1d536a0bbecab77c43002da4e1fcbc141ede821a01525b00c4ecc5b1bd2b855c2301acb880e6c3d3f863893f65ea7ad0f6df3a0c306556ea1e94dfa93a5ad1cf2198ef16bcd7ae0ea84384994de8156870b2038b1bfc46b82fa174886f54b4ea3e292fcdd50597c17171517337cf02f535976e60fea71577c5a5cce4bec532270f2d24d0d2e426c25d3efd0a654f272c0b9988d83d5e8afa519631d77da42c20443d08b5c19e444180aec6a19228db9db9d671852420b4d9504432d0dec65c95784bbe7889807e06efab514d5e8bccacdf820375e8fd03a38b5dfc37b4318009bc3ddf5b1c2ab04501b6e3b3c487edfeb6196dab28cd0ed855f479c6c0e84a8f30cb643f7b2308e9d320ca9bf2d97943648a59db7c9c942ff2f7a6e63d81a2fe41352c9471ecc6930ab979fa5e1f1a46dc44307f8919752143512066dd3503d1bc6ea745e6426b6eafe78d0028f82ea707c2db8215710c5e9da4c1d5bd79159e3fb63f3b3ff500c10f2661626b8fdee672d4943ff90fe2bc03f0964dd335c5821ebc10b0fbd8454d2a95176e12797bc66d733f0928e2b01e52fbea4755a7f65d74a237dfd2ecf98f26981a0b774b3e66864358631cfd7ae1c1b34221f0b38f2bdef110be5f78773db023e768a02b796387ce80d3e2e116581bf0b6e525dc9e86dab8fbdb1b1dfac33fd5f1dfe966223b3a441c3f5f26fb97ff2594a3f0890cd6c341696dee94ab3d4927752f10c715bd0ac302f1ac2f0c6d82fcf1049e52f906094636c6be3cf086052ee02f167cfd8a848647188235f9deb15b503c46aaaf67253c70b950b962ee0d3b983e8a7e06433cce61d648be24dd86126e0bc5e915bcca280ef44bb5f2c445c00b141a46e207ebfd8ab2892e9f21dba4d1ac4903f77e233fef1fc1e0fa82496f70089a843bd9767829c70c72eaa99eabbe02c29356924eeb81b4ba3e7db740d69e86c13e273d74f9a50de299e470d2cfa82ed2ea9062929ac48aba77335861e8cfd52b62534313b94b2980a0015f9b79e4262f4fa2f83cc3d2e379fe230c0ea9adf26de40c9edd099fb73bbd6740057f3fb650279016081d6ebc8f0351e9bfe6879bcb4868f2b80f1abe08f48cf529a6ffcb39d78a718c3f722537bc50be232078a2f9cef9e6a3c5b6841e992ab3f92e14b605e27a8439ddc1f67feeba1c75ad68eae1a126ef119e4fe2bdc44859ecada517c92f8ad15a1c94ef77f9deda11acb56829329b5166b9f8a808601da5a213dfd69ea22bcf14ea21e56bb66e0f558c57482f3ab408d1a6b14e9edb29285b011d48db2ce12ceeb9c117101eb91a46ec1fc08fda2e02622e850033579052ec6225bfe1e0ff7a0e1da6f04ccedcd2d9242bef8d3a41c480a8eb91be078e03a750d83e8f83c38efe0fb04cbcacff1d8788c087128ba1cdf01602c4f0570eba58fa1c3323b5d1b69ce24df4f91ff614e42b747b4bbc9bacb822e89a4b798b0805ddfec61a47983ae917e5381c69c8394e5c9bccc64bc81d899fb81f28afe7ab9eb3ce5a0d03c01418c60a3f86bae498a1c0b7add46505c0da40285f13358f8f295a5123dd4b4b259d4baa23538824a86062245c5dee1b9238a99bb5bbb79bbbb7d59fe62d856d433c5bf4c99b37693771fa057a03e66b22d2b03f967a044f630f33dd687fa3cde26ee3aa1568e6a4689be8430e48b5538ed69fa3b20dd17539720f6f4a73c19a8b438617997acbf20226886200a40cca5e450b78785781b465d24e768d66fb6574b6c02d32aa479d78341686a5b7268f45407a03a36525c06690112100ad160bd311d99c11225802a6ecc75606f7f9e560f8f8edc1b9737aa54899d72e3021f9c138782fe81315fe5f8db441445e2b96f62c77e0e1d863a5577bb6104209ec88fb6b7dea4a48edf9bd1db632ce5ff50e9ee498f1ff7e3f6ce3976598866aef355c51170a46adafa95e7b8d7caba297d12995a24e804fccbf2c89be610ca9b59614ade3f1dec713b8a776ec2f03641f8f154c38cc083d22442e683fbeaeff570a9292583911e13798ad7e329a18f0134a89c71b5b10e033fcd76c2ff6285c40ba97a03fee1bfde555e9812a2a01f0418f70c3f9a64f83d608d00a9d72df0ed0903bac452c28001fd9ad5ebbfb7c5f91c90661b83937eeedad46ec940ad24a28149edff8c82506b9eeac7da32e53c75ac4ee71eadcba131cb5251e2c0567760805356b9c9b01ffaedaa2deca2ceceff54b5f81df63bf2234ae0d34b0fc2b9effe533b7c45b18f1e2a38fcf20e08cd547c8e53430ec3938f3fbfd74f37c023cc767d94d19e4e446fdfbecd6b7c1f0c0612918616ef9000f9128c415542979673465a7a56b688b8ae356988c430e0749abdbf4fb9f0491fa18d02df70503f21bcb614d6ee53fcc87aff29ef66830210c3c9f7212ca515cee2b51d71d135c19411d0f6225890f5b00a78078207a914e900436ad9a4751f2584b0c5e4008342dbf30c2efba4dd634276b84a19f421383caefdb1532e35f754e06f3daf027712e86147aab5469240423f559ea3e9140612f0910f132e37ec87993c6d7bae99497518bfad8867611f5607df7a421a1a934669969a520cc6c1b06ed07571b1666e6f32be8caadd67736c9df5f037bee23f53ca32a5db18d315c8f0bed3728b0a0cd0ca5694081498a494adbc3b52b164e0d60d7645d79106fc75811b44dad2a55efd5e47a6c1b89465cc4e8de44cf5ec141c79aeed63d29e200c5726656509b870dd614506fe9f37edcce1257d2f22a0ef650444d0f73e62b4ef75a455513228bdc0a54203058e646d46c82b7e23fdafade17b278173811831f3729acd7267d3b46ac496ae12f2b3ad663bba042f5c0872aad4fb80a158c86edff7b0c9a678146b083cf3ec8540459aa167c15820ed9a1b31d9cb54e74373d8e38aea365d4992ecf7bcbfbefbdcf4852df832a3d81dd0c53f50c4303f6f7daca19a46e6a623a4a4878224c8cb9754205557e03246933b89d4aac08a2d05475bf1de4aea219ffe41740b5e5b0aacdc88e14f8b312cdbad0b84936f0cd994974cd6433673b30f90eb2aa5e57f0a7850bcfcffc68df7820763c89c4c520b9c731d22ba79af3603d84081c06f00521746db73fe87d2ab2fcc68b8ab23318d3cd01fa50db52839ce4f04d3b2ec27730aff99d01abc6455c8d8fd59bf2288bc1264833061e8d420cea51a5a7bb805c85b5c0d6997e17e4e8bf709274b8712c7e16d7e95979e6446684d195f2158a74049c5ea545f111c912492aac08b19c4f05b0175ffffb1e2807a9d4c95e1f3931ac7821e163f91122aedb366628a3c45f709af2b2675d200e623e6692b0545e9634421570813f88eeb8d5ef5388f2de18403c5e8f419e42a45e6db9a76cb640c2887a0e5efb5e946912e70bd8492458e4ae4816a73753395aa639cbb72e85a067875283f64385a13c9a04c98d14a651596f85f874288b114bbe2d8b75d63eef7656fa3c0a1b43c5660436d124b411449c4f7af4c879c3e7a56e0f05768097568c9157228c8078ac2dc2e0ea74dc0f840ed289c722cc65645135af5780c2b4c3aaf8d92985bc882983c94bee7c895c6b8afab61b62ab9ce30668a8ec612a1f87d7e42185d36511ea566403bc155960e516521b19b1a0dd5efdd35e43038d41844102d7657f9c838013ce9c305911b7df1194c81459e7126b17a867e7e2936eab6dd423fef3af94137938539ff5528af478837a0c1dcbf086ffdd91a52b4db7c3db166dcd51cdcf6c4bcf010215ecbfc7d34e902dede3726fc9ec4b8ec15bc494fab2149f24b02293ac0720a043ac88922d1438c4b3533f5a0093fe880f8654b8b0ca3690b91528bfb84f34734a06847a57fe04d322c28fb89ea05ca8eefbfba97c5881805f04ad8f19c6987336fb9da3be0233bea211522123deaf8508c7f8f77047bec5166c5d13e7a61590b1feabb7bff0cad4048ec5bc329b4b283ca8dbc4a2734fc6a33d35e6db681f36219d30455eeca2750e863f0b13c7804840976b10cd14a54c61a58251717389e44cd0b87ba96a1a250c6c8b79dfc876087736468a468db150937bc171a5920b13a5956f18eca72b7b42cebb2d1511cce134072ebc0f209bcc9bfb1b6f723fee505eb5c44d7b471f423a6d02d1b214366c3fd877da96ff2964fc44d4b0bbce828026cccb2e2564881b7cd4de295e0a32d85464077dc47f24808ade7ea2fbb75176d52ab8ff4af07abadfa875f4ed1b1392861c941b6ec761ad04a8c623e04258c1045a6e51434ac4757fab679a71685e3c107f4201062c1f7575d246ec24bc7f019f69d46a4046ee14a65fa2caec786ac15c356dff68d8be6228701d5b661af3aba0e8e8f029795d799d01bae0c02fed756567e2ecdde66f1ecb5590d060769876295e3693d0d48f6d51d33ed9620caae7abbb2c7928861723b7b18ae1d11eba30cf2f651fae40fabc985cf5e873c7b1e942c95aaec5c5ab9f571a232edbac22d4ca900915024c030c424d16e97a87b4a96fe4f82dd9f434aceb80cdaa73d6df86a65aac59b302b0accaf1fda346508707feb84035c42955ba900b951a66a459cf2311bd3898f0c1521336905b50f03000a157015281da5b63e2e85b92b587b7d12cb99940077200024ab0f29b585fdc50f9454a17dceea255d7af5c0e6b8d08c3818d6f4aca75734b114db86b3f7c2a0428d892ee2e08cdcca672de62bb7f989ec2345e26f74843bf1a1792c07ed4718525c011c904e0ac0e29962e3d4e9a3cf83bd9acf7b0cf0483008a8b5a0da36bab2cc16e41a098055bf471eda1aea2dbff272d746667b171661416ec80098f5a32bbae6227b6311366ab2ca18249a0204b03377a2e233a124f112a32af1cf76d349836e7f1030cdc83b8a95fd1570a6f198ef78f5a0c17c1c7bde99daa7f1a37a49c0c7c2fb011888dcaf4a869ee231675225b11323bdb640b571d7f849580a0f63d5222cd81d90a4b2742a211eba905eb3f5b5c753e7faec0ca7e4f666c5c116efd19d443f21c4cbd143538673f1c0f0c0da9641686247459a8ffb342df608da9c677e2c865aa756255cefb4fea90eca8d8b711096d02ec6cbd3fea14af8b36118d9485bc3a70e4ac6eeb13d559959bf1824623eedc56fd5cf47077b4f0e97131def6e125d24f74176f433e2adc6670768fd44133353cffbaf3dde0baba5e45abafe20a04946fa678fda79f48baffeca9dbebb2d7b8959c9e877ee7eaa1957c9aa08157b209f39fabce8ffa7244a99259926e3afd29d3490fab5bbe6bd68e8f35fbcafd25e658b77810f25a21e6d71825899a9f8fb6189fbaf76f64f2ecd331e3fb8b87974f4747df0b1366eaadde5ec11697c96cc383f9fa422cc8e94499be72c14b3fcdef5f45b7eb1a154eb4aa6ff5e7140750efde6839de6279710f7ccfef5ff6fb9a5ecc2023a90d079fdecfdf0610c186bb6700d372c50bbf41d26446810fca0c4f1a8cf856d16062193bd9f41265efc5511b8aebd389f83ea63cae2792dd857904e7a48a855e1969136136c0668e26f42505be2c201c9ece62b8f98df43190dec11a49deebbe0218ebc5b25ff6630528c8e9ac2aca1b9c333310d274a3b6d5ace8c2b9746dd870c50b4ce0a61944566c2f58508e4d1df4f8716d4a75322d67fa5d3ae3e97f0b1f0c114d37a1b404d8e032510ebb27adec72e3b386f436a0f75ac176ed459d16703bb88cc8bd5eb4b56c79a33cbcc2a80434e4d8df50d3acf311213ad5713b3c90b78029196f50a02d1e8dae46e720942be5f9bfea5627fb2e038e67bbf3182785dc3b53a20830a82205f4a4b308ce1e8e545765f91d407f5500bf0d055010da586360a43cff4db92ea9c580011c83ebc018057b52014debc50914a4a68f2b582509a152b7b41b6e73a514fdfe3c3484960cde9961e88ba2e35482f5f6b89c4a4fdac069ec2ae5e013d04f4b47a1e7da27748955a66b6e9bd70662602b742167b5e79368899df390544eadf5050ec6d9efcd093824240b279422113c92b4258f0cc5301f2275cfce67d1fab4f2627d81555eee86e04e860adf57722c0fbc6726f0308080f0284f703da801283c2414e174cc154d19ad12c8776d50224215b6b4971b1433171538afe7c9da3bcae68dd559de981c7f6b6907f084d8f351ea6c895a6817624c5813e6f281050440c50c405c1d8390fc8df6b18d65b413a69c1f61f32d601f2400041e7b2d440709b487f1030a0dbc84a6c232d756ffacd7ecd4a7229746fb746c3ce675a556e133bb501490d247ce2fe295dde941b572914446fd8d908eed1cc01c375e5dc8d8d8f09b9c6aeb76ea49182be43d9a18be12c5cd2b9a1e3e6adf888c6924ea11100f8206d388c3e82087b29142605cf5506480a50fc7fec2786263f447fa69ba97c42b9b5e44c202bf161a47d627688b03634a46d1448354175bdd994d2d7e789731a73f55b502e5cb574fd27452135a5df6f7032df287c85dd7ae9afe7b8db0a9071fd760a6ad8921d17e5e421325d2ee7ae2b0b31139a23aff933eb679810057a18d1fa1cfab1c49afe01167bac16ccfdf2a32ef6a307f53a6f53be5d24470c1a74bcfce88895722f787d99eb708c142cd4d4d06b81390f30ef410cc701edcc46d5a7758e26943ac5167ab94559937194027cc425b99f6f7641f89e8be775c050fa4a534fcec41c95ba1c5d94b2218a555fa90b0c2b5473ee1a16e063c8950ef4a01b9022ca4820fc1f3a365f1091301354cde4043533c71199aafd344c3ba6f420f7ece89108a84f3c0ad375f74a90573e5a23be42b62f3e61fd6effc47443901a25e347f7e04d8caf41d9cf9cce71e779be27c97da779e74e3e17c4b67a64c5723ad5ae16d59673daae655f4d80e47e1a02e2d68e82dacde493e3db5ed063b9cef13d5cf729f199127eaaa0e6c2d3d2f67c44e6efdb0810e5884f18b975760be0baad80c6456780040eb3e9279f21ae072e2ae9d66a19a49e20bd2c424a8d9e96aba5b16866d67ef809c48c3e6c557a0e2ec38071d7d9717bee988daa9d5ba070e1d742dd1aa80b1cff8d455a0054b5dfcc34647505e58696d6d2dccc5667f690eec6f1a1491d37028cf57daa8e3037bdd9fbac0d5edaac8fc3119680a0f2bff8d676ef236000fa41b590d81fe5b5df3c0151d6682fd247c291353ccd86c0d1b2bc09fbe61477efeef1cb658683647fcea6a397a4c2328e45bf4bd8424eba2c67b2314ecf49091ac393c2e07403b393a7d39a3deec7904aa6608db8f330108dff1cae3e3e78bd6a4d62c1554c55e2a9e5b441975e4414f72870a4233a3cbd036207a00ee964955269e657d3e66d84ef49f525ea8ec903b2aafdc2d56412b823c39ac816913cd985a71edbbb3c40751c287aee83b283be88e8e79556d198f20edb067ca75a6d5cdf5384277b83bc12cc64c0061d4d53b79d683e398a5513306fa06a4d76a110c7b32f127b15f5be109cdf117de8f6b5ed607cc99400e8d9fdbbfe22adeb8a02b57f5177670589fc7424cee81e33ffe0c662c317d38f107facd235d019f25a5904b3d76d329d92eef1579c1b1d84add7b067f8df76d836ab883dfa8d5d9c116c5ae79aadd78742f657e489695e6f96feee8ef3dff08d11f9a34eacd050c2c23e2186d52bc68d955de08a00d45c8272bb8483ec8c6b8ab05bea624dda49549c918ed53ca21b09be1dd217f8285069030df03a7265c8616dd6d238f8bbb54eb7e1f71f43e5215c813ea0a3ed85e761b3e8d69a5a2ec6ce2531cde10d69ce12928d224d69f39a54161591bd2068b201e2119bebb8a5791609cb3ef7ebcd15ace2cd18335be18c8014a61c14d1540edcaf2830ac93b1fc0f6203d9a2a788d22935f5718c350deb3260f32e0ec0e1a14fcffd6e2e2ea777160894d27f5a2f68bd2c3eb3deafda8a83aa4f1f91eb60eb77a3d2d10808ec67d8717f07b544a93d9b52a226a8303e114325b1b5e1302867a77df54906e84dd3538ca4dc602e0a606ac09f6c01d6fe9924d8ba8605f0c349228d757ceb484d75cc7ea68f3d7efa5be9f83e640212e4a0d493d3d32ca0cb99852a81bf6dd9f83ddea53565ee9bcc8096791f99cdf91d32282d4c5af3bb297ae4d2bcb541246852f55703f22c1e96331d66db6f9b286db00130a5c7943a3567ef022b5881fa6fda939eecc6c1706940ba830cacafc07a6fa3dd8597e6803db0f756b4d2b471fa82f9333b6ef490b37f4644bb050634cc5358f663dd47f33326e5265d80f1a8d12a91fd023524d7620bc5da5c7dd0c1309860c07af9dfc9cb5e3b44359d4d3d5199d06492c23b091bf838bfad82d361cd10710d0a5517a1f0ef0f0266e63c1d08fce59e13937939780669a5dda9ce91a5f6a01be9ec668917b1a66c073af74f27a5646804cb8a4bb79a860af8b8731b1e94338efe8190cc24483e65f7ee00675b72293dc5289b951514aba53861c5d54c270b2e690235ac3f53c59a87de2e15ec0543fe377e6821af6207e58333fa47b03bbc152762d33f90f1ee3f07f73ed2ad70e2ba40f6027b3d5e40366fb85cc8251bdbcb15f637d6608f56e58d43fc456f2141c394cb5df53e0c686ccc50bbc413317326f426c45c539ac9498bc5035b097b020fc495d30e6c4983dfa27370ee03a510d2d25728de076d109836def5c1a36c34657a5bf7da03eb1fbd4ec33ad2038768430d58e0a5a018c80819ab8188f163595f545514b0db0997e21d6ca0313ef2957bcce0e7e547bb68daa0cc6a166747c5eac4175e9b0cae7595f19ca9b952c401dd1bbf1935c4cfcda4e3221f9731aa4b6f40c2a6865e22cfcbe91be85f0ac46a94bf551fa3d2f3a08e6c90225201df8cfce00f587901e79189e12eb574c51f6a7890d11c58a32c90ddf8329c589077e19c9ffc65b101abc9d2291b9a5929643755fc79390fc9fb86c08f3d7411c380683cc657f94414572008934e415056f7d5fd265bf8897bf007314feb8876bd1740b4bf832be7762e67a50b347bc75af04e1240795c43ff1b4017ce7eba2653fed30ef0cd038f0fd6ecdf02d278953c15f248c28001b9a3857c355261bd1adcea7367bc8a41e5c92ae9473ad3caed180f002f545d700b42bd7d196c736b486dc62c22fc22a7908da6c1b9a3e3c8c5b7a7683e0a72fb08eff60bddd792af87533f147d814e53e39436e8ed4f7094d3365300f308d66d7bd63795b4a633d9d6fa99a3e15396e4954bfe95cd3718b2fd47c5489e45c3fb22f0a7cda20a56cda3da653ee64422321dcd6903d65c8c52debac39c96b1c9ee610d75f04717bbb455525bc22834bc9636dc29baa18fc206044b410c3bef4f06a311f263f499ee62ec680e80a9e978cf9be5beced15404fa37df7505717f12570c6e9a948cf88de8a90f7a1e4ad1be2f4bcb26dcbbc0495b1a3ec826ee4d0b3f627de80d356bc4d83d7204768cfb1def3ac0e7d040d33dcdfd4881207587fa8bef4549565d9be734bf4ff4eec5cb3a95902d5d4a24be57119fd716b59ef86699f56fba56997fa1f34b6904904f1f4402e86a9113b36737ad630f70841cbe6b6e7f2ef0b8420ec7acd6d36223879a59953ff02559e4cf4e8887537dd69eaf504ab4ce6eb412b157787bcfe9a7cf192c5bc168c4fb4b3b2a0e18e31eb03583fd61dd479a7960f6ec6a4df9a8c015b3fadb28bb832829da391de756e8d37a4219fc958a754d1d059a642fc3103777c1c4556aa266a263f5dfeaf38302bfe2f997201ef357150d26d6a9df82159aa6126bf987bdb6a420268fd75d7d6836b8ea06e20cd6dee53d6b6201a994aaff348821087c5a9ad04c4dec5e4fc8f763f8b018eec9cfca2bb14fd40b44e3b76148df42466ef6efad1aded97db9dcecc54ced988931e2fb3577970704c729230ce332b7527981245e53b2e6d67d1aac4d61d0d766bff5697730031c2e6fbfa10768be390b0e76f35b1eceaed57fb3e64fbc9fd28a69a01faad937a927ea3df6fc9d7b6e8df61f9a8e08d76795ef58e0df7e384439253fe3b59f602682bea89c6285f1a96d75a4435a34ff98cbc9f7aa0a89f71b38c73eacfeca0acfef9c72b8e5b62458f68b4ef27c33f1591d665552430c0401c786067c5351fafeefb9dbc85bd4b4078b0b2176e2221eaa8adf779389fdc530006259f6e951eabf18742609eee7402f9c08716a05671efc43ac08fc25856f1aa4ad2a0110b1e11cdd435359ce59c144533870c0901e898d94841bf3fa205fac6deb44c1149b58367492634d875d2f6c293007a89d4df4ff00a32347e81683a6a610a3b544de4156388435a864442503498e63d401b50aa0db74db17c40214dd1da4e80fe2f542a5d84551ec07fa4a9f1fffe51718f0c9b704b25706501d30eec18b88e6a4f146d0146175e2b32c04e3916d7a5837b14e45cc903aefd0da7c66f69eeaebbfc4f743ba87cb921efd062662fd2e876f43b492e7bf2be13b8d8b1c57d8ccbd8556927aa167c5944ebee2ae43214c503effbfdccc6d24ac316e211ece0951a29634d19a0ed310552cd724aed277145a0c8501ff86e8aedae2cc0acfa3eb410f571dd8de2d550794f57afd34e6d17f3c1c7b5200c5de7cc14aab21f45d9fab7dd34a121cd98205146cbd3dc6b42faecae1176f518d08d5c7a9b40e346c2748d10f6cf7039bde00508789052ae9aa9029079a05ace7650fd6315a8f44354f397ee9315fbc9be81480823cc362415975d653b05721e1ebd91078bcb0c5d6cce315b46dd24411e79fe3cfda40f4856139a3a285fd77f5295576641cac5f7fe49298f884cc11a38e8a3f03e9f4f55fdab043ed906ac75b8045256605e8153a6da919930695cb8fcb58fee3fe39a50caea58177485ff366dc071bb199e06e7ceeaf87e72fc68f957dcb0a20ad0e4ca0c95f7138a6a983ee0d86b02c6219d77415ab43a037775b6f0bd36ded21ba1eb26954dec06f109217fdb635da2c741ffda7bdda019eb53c9c3827230491f86d749273917b9d8591f40e6ac8891a40aa90b1fb278a2b3eec7b390f4d8d7a89459c856ff1c8841c3b7d1bf503b079a65cf3bf5e9a689ab6c56c2dc4dfa9f96fe4c06daba15b4d3369e6cfe8dd197894af35d2290e781f95bdb6ea926826fb413c5cac90bf99251234a143c83312e6925b97093f6cb836c532ab2b86c0bc36c6ee4276f78f1abe6d4de459a19f49196ccbaf5088536eb092eb03c5191c725d89e24c471a9065470ad35f22a05c634d852661d2c1d0c2cb134d409234d59f8e7adce1419e11658b915ac4cf5f345f0415fe7ce45ac20e5c45ff9dda18829125690e9d4d8e2579107b1b5d6122e1c5268878d0566af4c2d5fa8783ff8eda767285583ff2f51f3140f6458320f94bb88f284e6f7e18c714e266d913f8f75ce9b156438adf9fb22c9f2c8436e25082e9d7be819d4ed67a70db103d777dd434884164b89f9e170d5be78c14e0accd1526f5f0ba6a1a85e24d7a998b78a6c32f5101c1210b902177a8455d1583bece890a6f26554e67e9fd83fd9fb85e6469de0bb9382a31ef7d909076578b52f262a2566b1efb554a50d36c095e5fcb2988fb13006b6af2b7d527f9b1fcc044f5036a796f56280e083cedfa3af7935bcee878bfc5dd0e222b8c2fa61beedbde9a4d54c2d549b8a784304e0f3aca67dfaeab7ef5ecc2f878e13ad49b9e6ab9adbb158752ff63e14256f47a462c2a0976ef3c43bb810c54d2cabc4766ffdb384d33176ed84220f41cbf864ba2c2e79674529bc535fc7fa0036274ce583204c59736f2433400876beba95404ad2bdd17dfee0755f9f18bfd823e597c874ec1b8758d886558fa5acd9e0c0ddde69b01b81b334e7c2f341711b9566a601c00e7f285c507db67949abddd2291d798646ec144a4f9264006e9ca1745d4f26fc16e8f38e0eea197565dbfb04009c490fc986200093c7e028355d9ed88feacf5a3ada05d09c30c9ec75d40760059f7f4830b4d5421011a52c34b2e2e3c772105568e3e7b6a209c32bd5ccd3e107c4ce2d04676d93ef8e703b451870a78f147f4ac47c1cc3b23703074cde9abc0b126803c01be1892b9d65e7f07fe52350900fe58d97f059a4407cf78735432e08fef0e3c01f9ab87ff3847fcc7bf46021554f5d8a671f5a5019594e4e13011499b3768c23bdd91859821818e365ea1422ae5863675d4737ed5c1061124df3a3afcbaa0ad520474829786c559bf928b2b892cc50b69ae08fb3584e67d9a61e6ddd070c825d8aa2fe6c4df177ef2ae7e937c3ac92ea8387d97e86ad9c5b0fd9badc88244ba94497ebf53d2142d9fd4640c79b71d38804eda9a8762153c03a48fefe93259f72d632c9790fdffa9c06a8189073a1a31ac0e88b266b158d6e14128fc8228e2ffe3fc14456d4f2f5b7f97db3b233fac350e6809b0987a1b522af2bddf1c7f501894562ab6d6d58d58019b2c68098a308942539649c9f5d044b1f3afbb98160faf08b743e5c90bc6943190d10c52467717b301e4bb5954b91e294e3054a462141cd423b4e6129c05a14f49389950ff19337950e67e57268e62042b6ab56094060674519c97b50fd5c2ec9c4a098190d7fe61ee5a88e37fafc96e5d07e838642282aae9fcf99618008016e5faf2ce665c1ea8fc4b596b9b94f9045080e20623c5165d0b98f0f4e9144c20ff49874ff5c4cfb8097ffa4dd01507d8142e3684cdd8f6e6b76fa61676f4741768630e0d7821e4ac0ffefb74f93151c983cd14613f35fad7a3561483619fa0da69efbec5d4b71041abd2ef5ba9c23aba6bee0e67c7e554718e778e80b8970a853d6e48f3eb2d55b0c1fb0ee9faf91572172c46a0dbdd72a3bf4d1b5507a8c0613a30663c37a87ba91860b5e73926898f699e185d95c1c09752bb8a1fec45f9796d4817f3ff58570f201a298e4013426f5edb0b6fc4964b5ed9ef9bf84848e6e7a9e5909fdb4484ac38624b1d22adc86c5b9f6b15365dc2c520fd7c09b59a54973191a0ebc515201521ed41fec1d2a81354449e8b46260459d4b9042aa48431fd8fe1f2d7830bcb533ddf4cf4084f4c655d264320f1774d86c02786e09a52e61ebd49fa85aa4a527e2ba5f635bad0f47c3e869dc0772baaefbbd42fd15919c214ba9fc843e97036aa2f43a7a9c7569ea578a38f11ba8bcae25d0017efb4cf1324e75dd6888659887edf8b03e288a4dc036eeafe5b0073ffbcd736cff2e2c12c99e73563730d8936de80a52d354d617e3490c1fed28edd51c3197003c1f6701d6d0c6bf7528fe54b1f5c7b243aa5fcb73e120561944f215b2f41e2fc10f73fe5a75c1d867f8a7f805e98f63d48a9ce3b1254675b1ea585e0d32fb3ff99d0b4d44e4ab1e53fa1a462fb7e65c613bd42f5be0cd8f069f3e4398b91651ccd8b78d332f704b5bf1a3b6677c0949cb790ec57b0557cb344323d0444653fbf76ad6beef62ea57be9314376cb7c9e9f92af6ba8bc573df3af319a62494ada8443b57bd7911f71a2df34349a8f7e7d04bdae21b116be8aa2c0af7592366315dba135c42c79b2765281b51dc3e9ca34c62642a49bd2816c2a051836a9d600e14c6484639ad51f7a77f4a501565c060dfe769615f0f76d7171c0a9034c7f0623be6d33489aea51b55c5ad7ff0906f3da83836cb63c0aac344e8bbb08b89d2519d43e3987c3e8c7673fff830354cb06b11a1f8e9ea3209e24f5aecc95415bead271ba006fc455ce81a02d19fd24a3a0d0e0345f15566058c9a3f1f25d803a0213390bed4e6d5d3ce5d47e82e8bedfad5ee35a86c9d1a1cea20eb46164f7e3813b8dc41bcc8e62493fc8a864dc6c76108dd24f6d364903b93b8bf512a64d3664f663b0759fa6bc5edf734d74d0d58f146507972d02f47f66c80ce0e6777d93d53a240703431c6f4a6ae8af9abbe8721fef4aa9ffcb5d46bf2bd9f55d4f0581544cd5518f726c9de78f5027356bbff862f13e39039c16d11a36be52e62963e4296d104fdb645a04cea2eb7b2990bba60745bcb5d75286a734394bd8d4d42ae9a640ca9ca14768483a054e00ed2f6208b1a732f9c2d19b54c9292b3897696dcc5a660c0b89253e06590613f8aedd27196528246a0a7808d1fb74aafc7e41efc6041edc90920932b5c8bf9d596624f858c24305e5ecbaa0eebb9d3c0da3c462b716fc3709554ffa18e1ee6f3de7a4c2c60ce879bf0d385a9f067fc2806f9c322b190e85570941f11904d077575d8d2e42245e39b6026c39af120eb2d856ad4a5b6ac8f4bdab9bc9726bcd7ba49536cb61a525e2a04d5344d46511eeb33044f13d310f6c218d4a7e81073485fb4409d27b0e0d1b91a3c8ab0a86f48cb3b5313fd0f19f8531cf98705eeb0dc5dd2702a2b5ce7660ef4c101116eab30733881ebf6e0758abc7e29cd0c6c85a463199b6e51df786135b90e331247d138c8efcbe75447ec706d4548ba5ca23df9360db7a104064bfb48a76d39618389634973410bf8521a8e821ca376d8510b599f0f55556ebe1503fae8cecf064795b9b2f817b73e023f1f05aed22c9bca9a37653987fabe3584aa5dfecc25e886d9893a6f521489170472c888c9a89b742a18877e398096a0c32d300f416434b263a4aed4722ac629ce803f77a393e06422a9882651b30f3dbf119f3919373885cfff5ab4a43ceedbbcb7bfffb246a1cda650969e639184ff0c33e2a1684a38b0dfa8017c42fd66f02318fb7c60ef2505d9dec8a10cd8c24a306fe8f9d49ddcadfc41d19d02ffa5b861d5ce8d9f56b02befaf5606cbe8518c553194f4a2efdaa3e4b4b9b71562dc2978bf959da324737351b4b5a82439c855bdd4f9ac8b8a4a36c6a55a42cc5207a96b02106c28142e4cdebeb7e49c10dcf5d691e63cf53db6b2ba369e37ea724a1937268c0e2ad81ee6be042ef83ba06cfd5cafda9c473dc0f3d08bbf24735850f2217f665752c9eb35cda54430c5ace59adff02fe4f8ab89be64484efad642f3c8d00bb0eebf96e0d7bb8897c025f48a84c5aae85b14dfd175b2d20230b923d942b5e7c8308def8a2ad13cd2d91647457fba79579877ffcde45a426c92957b9a9b7b350faf747498db65fa703ed208c6355718265c405504ffe1db1110bde369cfa1f97544afc4c9be1be0e66c5042ff28f2d6ab23e7407b1f96dbe217507dcd92b7b9515453440ae4cdb7ee767311203e4fa4e448f81248d2d0f2e0b3554e545a84f06e497af6271feb34312a4f3d18e7e556b2132b11bc3bb2d62906eeb8b323ccedca0e69ab3bb47e58d0311ab29fe00d2982649c3cd7e1ef16628ddbb04152e38881f4d5570b418db0ee3d499bf263044f475173ce96f105aed46949cf21fe1be24700ef7ecfdabb8c8e08853ed968c06a0f0dd0b2f6b56dc71234f763c2318ded678e4e7c8b42f499ec991df3e2ea49679bb60861e8008565a1a9a4df9d14b2b32af3f056406e7600808934df109ee20a4004c798e343661acf6dfc51f6986a9443bb2d6629b3fed512752ccd72a1e5cce2dec3e6c26d87f7a3189b249e116f8852ea32736e71bfa938d89a2c7a1af264f5a79017cab54a100bca3f86a3354452cf5518135cb72dd54c67bf2084e2c69ecb714ddba050899d33079a9dfc820057ef6d8b6eaa36b5f6c69edae3fe3fa88556499133240105ee51eb73e0efff81b6f5ccf1da7ed42d892a9e2321e353f23bffae496ac1722d06f7110b93c2b768dabe0dc020358f2b712014190d261323fbeadbead988c1d206b9668933dbea7748da02109c995e675363ea33bc4ab4c0b987238e9c49002c301d4a502d7d3d2ac3583cebfda19ff7aabbcb06d9d4ae86cc807023719e7a0ba01b8b60ce79ee9656e8684acabc0195812d2cfa1ba9d1214954ac2891b9140cd9652bc48c8ceb09f86a67689500c9bd115f746cfb4def67518b1ef8aa79d6b58791380a0d2a6e3e028cb4038e40c83b75c9252a36965dd88790cce94f3ff2dda2cf395453b2ff9b862d75e06e61c7dc32c22315f59387957d7786199a8ceb0fc781bd408a0fee183bf585f9fa84bf7779627c8e438b4a63289c72b018a30e8ecbe15b98bd21d8e3ce5233a0681697d8d51b6f5094ba672fdd9f29210b13ace793e1f945a3b0ed0b4e3f83dcb3b9edf5f84b4641a6d9fe9d2b61e409234bd92b2cda5c8e61b0d6cc82b0cab2f397dd64676afb31ab934e15df785bd49a8e85347f898dbf4fd40058036d59d8fa634cbab17269d0d23bf4db609f469755869954957a12139ff70f5b05eb228e9c4bb5ee8b2cc0e775078719d5e431e8198bcb414cd28edd9571ab22e0582aa68ba4d2cdcce5d7a33a179bbcc81609952b5a42e16323541ba56f90491d00d0deb711110a377593616b175b99f80dbbb7f4660f3ecfcc849cf4a274ae1ec4993370c39ac7dc83ec8c7498db6ca215d0ed9bf7711dc58ba1ad93ac1e5ed3f7f0916ca194baa7c71fea4d6dd164444434bb3c9086d5e6200b022474b19fa82136e8504e693babe95831a739434f8400306005aed6470535050b7ef7bfe3e9dc13347f3782dd0a64075b0540f8a5067ff869d0500389c734664716db500ecfbff753816a0d72ee60f921de97ab0d9c7f7bbb44e2cabb0a6732e57e0db59465b4ae862b69edaa425f61ff2f229d1c1b55e0a0213624f1a0c44a3fcf850f5615fb39883f37f06a9d97d014684ae545edca63feac7ae37e7ab16b2e797bc99ad4dbd8ae84c3017708fe3827a13af08812eda8988d53f27566ce847ac913a43c3797033a4c8c5d4fe0fa5bd3cc0a9ea1e9a8c0e42f4461c62baa84eb2fb65b8e0273747b00ec17bdea49e48ea46146e61df1b0656de03d71dafbec87ad51ca84dc1fa2812a00f68ed0ef208553ee87a685456628a11dd825b3fadf92b1c2a2329800180afd1c4a3f8c33e2ed46892f8fb6a51b2e96518389213f82feecc85f7caf309d949124664761dfe35565db1ca23dedefe6d93b73c61b911fda94256936631c3434c8791f50de07e7ab0e7eb42beeae878c26ea11a87a6a8b66d5c968a341d7dcd2d48d520c10c9a8b952947d7e8720f4a72d4fa312170ccc197d94b99c917de94c5efc3e36aaec3e39f3542056b4fe1cd663ea23ac8d4e74638f4496a2ff246186f05f357bf9ae2a91986a2b3d7a227a712eb78b7a361aa7924384a8eb4224addbeac737c54c772c26e01bb73b61eb29d141312cf57fbca59f0867f818c19989107c77c38ededab8071585dc572abbebfaab3dc0e760ee342294cc016569131a733e985616efa4afce95ed9eefbcabe5f58ea7cd9b497920e6c5ef00e7386b51a7f5573d6089ba4a2e3ccde72c075da6a402c9ca991993e9ce56974c7c4d8b23c1ccee790bcc1098a79765ba89b537e2dbd3048b7177cda24d19da154c7ceec9cd4c3cdf1d22e493cf7b9031286e2fc2f674113a648faac0fd82c278f8a8168efdd9b20a4a8504e6a25da23b4b844b0a894b152916d5ca861c7db655fec77e520037a6c6be2a310dd531eede685068fd2d114304ecb034eb6e5bd309c9ce711e2e20ed26b3452203bd3175b67c7573273cff35d522adf8c3ba5e328e99a25e3bf218b7ca7e8d98245da5582a2ec30e84db4b516c6a6bc2f68bba68609e5b0a21f046277fb545d9a4f1f3c57c7855dcc6e08e148ed0b4189bd04aec99c21e77fe00d3f390ca06cb2fcaf168ad68a83ae68fb1da72ebe705c46f487d213eeb7e7cd0b7bd3428becad6c5a71d166e4b15efae5c8893c3ff8ed1033524da85fd3989106113d05ab547446b98e869509030b1c5cf83ff5a16534a386379e38c8a504c53fd336a992970dee86b4c8752fc28fede9f20921e6af498faaed9c12682fd5f893864bf7afffc4a382f6427bb71a7a7bc6e488c4e0fe1b79b71ab9e226ccdf8c55fce1900fdbd6ebef595e3c7bd233d8fd83f10bd97eafd4790a49f1696d34d681a4a6bfda0789245e2f69f2c9bc3849544a5d047c28e9087eb78d9efab70e4676e22de7665942aafea1725f5c379f6b7bc326367ddb07a95220c240decdfdacbdc5b345cfb4f143b422e85d6c6f070f241d3ee81f8dbf70bd8e4870c7314f0a2dd9dee5b8ad6286ececef92b0329b15cccc44e5fe0287d3e74d70290792b3e6a108b00689ea58e4e2120a6ae9f7bb4269b8132a8912d3bcfc7a51dacbeae810427639be5cdcade7793750aa77e04600c82e46f49c567bc7f68fb1dbc782d55ee8c62647952c9417187da6dbf9edf6250049e98ef7d4954a7b6f16f21e10a47b4b8683a03e666695ddf8a4f90cc8fc5ba3d8f59d7a4ededfb389e55780d4b18e4ece85abebcef5ceabb2a396199016b6ef0a33a86802a58be6b9d2ebae5a6e3cca07281194c894bc5d5c11d944c4e9cbb6c65ed81f6a705aab758e91f97aca6b9bffdfe01365b0753fe32a3693c7d9b953147505061d4cc39c5d15123ca0fcaa113ff29a12bccd95adfea484a2bfb0f5aaad199bd76e9b1a8e50f67dea17794e8c0af76c88019897c244161c8d7bd8450d142f1f327225d28d104addb1564dbcd50b4289faa13dc2a7d274e29ea483cfebe73f8687c98548abd9c2dc948be44077a6516bee1fa5ba8b0a8fc586a8fd11359eb2aca8cef5e33e8de7e902c73719e7e32e70ee0ca6d414b486f4850cb2670481c737b106d9f069b15fda0eef73029972101b9189fe87a97d4f051ba3ed1dc6b81bd12e7d975dd13d0feac5797fd9267b5985ec9081e98b421e8c446499588fe5de224a6ba0f320e4b93fe6433b3db007d5f6ebad142013139ff1ab96ae286ba690535a24d177f2fc03f4e7a3816474bd700089b012658cb0c8ccf8d6f2d9939f3f2cb0d6d7a85017f518da742d640b9216ad4b3f41147fdd675a4fd8644f37a731c335c77a9794d0d52db53ab564e4451472fe21ebc3c7e59f63f6d87e88b0efb450a910d143d8e460fe15203fe2cc3ee1fdaece85bbbebc80df74596fb10eab75ed3bc51527424e2d1d6016b1ec4c7308346730cbb89a6c7710d5222407a36b375b96b12ec8853c882fee1de02de5f69e106a3e1a11463f8dd1150e7f0623d8de6e3aae555ceb1f7276e2a75fa177c96c83c3029c4c0339e2fd05ae06235286f2200c88d2a37f929ef921052c6f657df69cce29a62fa967ea644673b893b7d200aa3ba21f23d4c3a373db0537a433879fcfb5699153b3f79afe821a24d2bffac084e44d44ce28f6500d9147ae183cf25a595ae919b37b736863ae3d22a11275a60f60bbea318b3257efb6e2aaf025c7fefecbbd6fc3e39e3333ec1639c6b5047fbaf6154c38fddf823c502ae4405fcc38479d0d537ab72c0a925b1877525de97d21c0403c4a934eebc3ac95514468a5625cfd5fc44c6a584147a4149c8f370ec94f2de3110eafb4951133b929dab60b7f62eab3d0269d8950457f1a675dbfe2d9d98cbd2f1028d46f9a31db8172d84d28f6b95623da109ff96812444876da646e081543b82e4ed29a8532833dd34bccb96460c15017991aad2bbc8d1249176cd53cfe16482017402c755c86a23b68bd61b835b39aad73eaa3824cdbf5d14ebf6b4230967dae177cf8937b28192389c5f5172ff1c732ecf2f0f83e31794e7cea21dc088a170793811e20573f739863e63f264d6752a7998a0fce6456c2a9769ab8d71dd2633fda30a082e45244e8e70d3d25c837d7c6faa79a1d4836904a2a6f4ee2e1933bcdf32a94750b027fd2fa32c0242fcce60973c5247ede80238ea251df63133cd2e1888eabc369734379af412e02567d8cfba80e12ce29c34ad616033c6605639da2310beb34a59101aa3bcadfb407f96423229e788e7ecc0d933b6ef384e8171fb0b28d0e7e77764b0db0f93a99909dce44ca83b4df8648f0f1530b13850b173095ace79ba5c2f528f80035eb85c75b58d2ad91f17db0253a0657ced215691ed3aade8445e643dbea7e43aefe9f3fbdea3475d7b35322d4c16324a20c37f0e78392e79f19a3e9f4885dd46220c86b6e5c5a103e9522b9a52a5d0b3772be0bd7f9069d25ff0ea55c10b7abeed4cd04d8f4077b1f7ee14fb98254f68f4e201585085bb793837f422a5b0ca8d4f8d10374f9553cc0abf359bb3e182b61def4ebabc3b3916f42c39f17faa3c1676099c112fd4eef1ccbc939605c000bf709ac20098b2787564a59f0dbc4a9bedaa44b862c40df53b1857f7d1c9a4927db7e2cc4bd6d946951d66a75969ee42dfe6f3924440513f058681fc8d667d010d8ca40919d9139d5d10f915acf6b40a7656764a5e79406d24ddd4e35bc98b440e76ed76c1332432ac703cdb9acd76f876209ca4e63b7af90438004140dedc6e0e3d227b659dc75c6dcff65d4a41f019367fc1b6462bacd27f171b7adacdde4af93824d9f99a8bc418139f1da5d9b5b4d3700d32566cb53a051bfdd3d7c43fcdb1445a66da0adf2bb29f41f81aa272b015e3d42882a4300dfd02e1e7146aadcb4edb50239f2ec1dd2271974a660094b2c9cb5381fbf27847ccc213f9a6df5c1c6873eee5b4c9eb6f51982e48e946e0f7fe3c536f65a722cdf5cd1f763e71eac8b4c74193ec02bc2fdba3a4879bd013e0aff5984fae30671bc621457792306162c666dfce02e9a835f72e7fb312f139559cd86e41e64018eb1e9a4b5f58445a47e6c1f3e6869e3bc64fe8e165821877c74f9beeee17be1f7f501f93a1c01975b7b540501a8e6a1765a79a6cf8ebe75ba407343d3c66f146dfcb34e3447fbac8735cb568a5d5c9c47d532a10eaf267eb2b7c55ce58c8cf156433dcc574c212612beea4dab9773fe8d9d9ccddb3a334540a80bbcd24ceacbfda5d0dd91adc2b36adfd112241b4d74572833d58e9715f45f47a383c2ad858a460155775d6d9bcdbb97aeaa364bff127ab5c4d518be0107841aa7bf3109092ed68d2fbee9373b3c170beabedb03748b0a12565cffff8004453d1ab5c15b6984c69a80dde52c4966274aeb2f97b95e94ce6391e12a101b282034cca3ea9aee2dbe2a8ef5861445b04610cd1b62a664f198e41c102e37dcd95630f526d3d74e4d354cc344e7e56ec173097e6243db5c2c3018d0880013a7b36b1b003fffe6fe033bf64a8e7aed47723acb3da3ed0a65af8b65796098ea56e2ccfdbe9362a25fa02db3f8e6ebcf919d235044c1364670456c458723eed4a6a599c020114a6c71df9158315773df10c373b3c8a35d0f4708eb4774b445937fd90623341124d562c45097b87e3c864eaf49580d47c722167fd7bd79c5bbeb6482baf38d641b19bb287488ff09a8d746c62035979edabcfaddca72d4e783e01080e8d42c0b05284da076ae0e2a8417782bdc7408a35ea86893220a5eb9454dae7574a42e3bfeb77daac14c200857d1a1693fff84d73354591b7a5d02aa7e94209158c3d9d3e5b428cda74a62d9d70d34f7bbdddafadcdc13bab0bff774005c8eb21d01f4b638c649fa54812ca8f752eb5a58955ee3d70dbe8cb95498e085b78da047d8c9c9553e9ee2b85a872fc962b98752c57cd5b590234fcba0e6f337fa29c55e787999ba5ff487162acf723afa9de1d6f5b58ecb23200f28ec20b63fad4881a2e5ff25e1790bc713b490e79a5ce2b03880c501919d409272de8536e310613f29c8b98255a9c329145447f363e7ea75b8ad4bdb9d6418f0e505e85770216ab1cfe0914d94d41274fe9fc324fc3940456bde52584d5033331096d1e78f84a5c2c73c0cf78fdd727a3cb0cc1a6e71b4d38ded069227be3ba2860eb0656d84601069da721a8e5400790ad8e535371d7b32ffe91017873a295f7a9999660c3d44598ae7fbb7c9d1a3b800058f6b632e730c75e91a878e37539cf05e7451ecd40ad6dce8f972274c630b1d0d489b750876881973fc75dca67ea01266312ddde93f35af11c7850a0712b6f2f7cce7d9d6ce6ee71421ca886bdbc83672afe61582cd37b33c94ed75f5814ccef30ce0e3761bbc8d2dcf3a956c3042e0a9d212a2d651eeed44be6ea504536a42a40759aaddbfe10b52ae1fc1464099e1381236d4483d0283dd341c0e8879d9c11a3847b235ac92dc18a3056a72c2309181da92e2aa47bd74876b65b4bdbef5fd086d6290e4a9eba2255e7740ff5e37da424cb0a2c552229c82e2128be65b8b060ece30a267efec31b3fadb5bd41eaa493fe0317489a64bd54bacdab9ac376bd8f303b82327c30e537639d21aaad5788fdf73b80b8981121afeb96f78877b5a9a8bc109b8d5ca4dab91943bcba64d6498c81333c2231b1d1089758686fe515585f29c58c2d4664c10f01a03225a373a49e8cf2869fc5f519d4ee8cf0130fe9aa90ea77a2fabc25ff445a009a466d24ae0ee7d96d1965bc6698280f20a3d4ba2a712c3cce5013df5dbd2bb9fe824108f4abda0566e205d7f70c461e00745bba5f7cf31331713ebe9e21232e0305a93c971f94e1336d4a55ff11cb8bef838852415dea263f360c2e91bbb01ac432282d60311fb10c7abdb8fd71f03d168c4895c03f9a8cda80ab891e1f29256b40ec6bf66bac0064441406c10d736d18bff37ea48d265b577554e10fe103b2059dc67058813611f36bba025360b4f90e7ec9de12bda188e964c91bf746680f37c30b85ff15d56cccf3b34714d9d75854cb3f4d0db925f353c28f174415a5eb450787c6d207b018fa001d0ea043ce1dbd4e9772e4146bc0ec7dec81a6ebaa142062e562b5dce0d322a6e3afc2c25dfd27f6279439083d578dc7ac58e4d112a2add91bcb20df97b507a4c048308b783ad049307d0bd1e6a5c4a0735bee3244c9d8654006fe28aaf8f6df7c020a2f7f8f2afe9d69c29e75023ea59ecdcb1bebcbf546c8789d421569ee0c3bff8ccb4a5015ec7bf2dfbeb65ac1bd16536c1fa0f4ef41cbbfc7fe1351b6ae83ac386e13a0d7cce100b4231bc3f0898003f45e37a93c67b1d4fd5c0776c46c3dbbaf15bdc082bd0db648eb3ed6462010ac1c728a7065d970fc4ce954dc4bed98f53adb741cc612bf89452465c2ca0491e35d0e61a42fe068d5a548553f1990875e9016fdb5c02cf1b3502c4e80deca3e129b70b85a7d37755edec01ca02a446037ecfe0007d9857e3d339e4ffe1b037535305a9e86067a304f5ab020966dd0698f88513d030ad01e48ed20b442e6cce8ed5336b5907cef9a7692783bff98580cd5b2c644de21ad4a78a35b38483bcb6a3c436242886c6ae59cf5110c99aa9991663d32fe4eba1658a4ca8dcf133a56cb3d4de467620ff7f883bd7c721f529286660a21ceb0ae42729c3e81150feda827fd80393fca63abf1b8e5c1947d296df24c64ae9bb841d5d58ced6e2f0f37753a1254e5cdd085e877c276a0f9d71f7b8379c7d7ec16afeaabc63d3bf57189dc734578135e6ee4977856d9cb1df420df29200002bfe2d78a9433d9c743e3a13b6878edb57d7083ae8bc4088f63860f429e383003cb88ab9149acb4bccbf86383c32c55bd09f6538ee640c5dff6999c6311a36e662198e943901c4d9f3968a778b90ea408649ceae6c088b5f1ce434a20ce6a00689c315e94c1d00e743efa6b5f52319cd438f3b38c2aeeaaa6f52927775df7330ae6a818983fbbbff9229bdc19b567b4cee06a55c154f62befb02f14d91c7db869e1a762f8b28482bc4daeac0b8ec92d5a19505e9d38a0daabd2a12e59e5978d39e0868a690198dae6e87d80f02fc40e33f279c27632d6c9bd847b310d4e9afab5d5c7384eb4230f1a25d19c49803f40881112615561df8a9b7f7625c70de26b599416a4b6d8debe6f2da25192ecaf8006100fb2162a1584bf915f35e19d7633c989aeaa236636c3ec4f91a5772fb1002359757fd1da9b8cf107767375cfcc9b2c08df128d9ebb2f7771ff66ffacb582b79f4f6047232f5603eb17c13b15e9ee86b989bf61d8a1c05a6fb3e788cd9961aeea9923b96f76062ff72aa7bfddf9b5ffce6a707488abfacc8e1200f9ef19299d8c28e108cb57f6b2ba4d64050e8b580e3d392f320c0aae32e00c9ed0705c5c1c50321040ac317146dce9496cba1542b9cd79d70fd43db439df54e0d95cc7eac3fcdb8af018bf794b463161add5d2ef2a65491e5d46e020d821d200577f290d202a46d461c321d3d4bd6ecac59b459b9dc17c53524f72ae38077764412dbdfccbb3092e388f63d467953b8fd0f41c8a7d5f577d062f96f7803835c168c72097ff7db3e9b3a93c0a01f7fc27ccb848ae4699ae878f370ac3bd2754eba8a639c33a1a56a4d3ee3a5174c2c994340e673fa6ba4260eea6c43297f36d1700c308e20840de8fc4f9a501240dfd540f307d568d68c68eb2e21e88cf3b1c85eedeb714241308453e78c0f75b61edc8c26c22b04aad6916503da3718023dffe10d43928b6e9a80291f7858decdce60c783930f25d33c95dc2ddf63bf51e58cefbdd45c6b48f2aea4974061f10e0505062623e875771183d192a359669699313faa61e73e7a521dab3c843bcb5ee116208edf8620f70687030349964f2afc56ea7603ea1bd154ddd6a96dbf0987152d988c76062c314e87cedafd7bc6a5cd37203e6522fa26afbc8be5dffea0e68ee734070d34c2bb77dc8b8777b8de0f1ecb6db23f6879e21220b8d6124c282b086d210305eda42d31fa0b5b22945840a684503babeaaed63c7f97f2cdc49d5976553498fd21646c6f65c015874b4c81ae79e13e05d9264827f3b18e680f7914358a64c4cbc09295b0afdca54bdff9975562ed4be4ed90314c7f7e1d783015802caeae35fdb22f7bd18f57a96f70f38835ddc3c1789954576f6ecd38c5313b3d4f036da101095a2d3b311346e12ada851e7a4face652206712e126337621cf99b3e50d9918aae11f91f08ea33db3102ff774be1319bef7e1ef7f772458f4a40e2e23803825f6cf1e201a43c1685c20bec9eb06c53e89a0a4e9dbb86a58ebbf5dc775834775c1aa312be720c72995470835352a062629574bf30b8aa5997409fd4860511bfc1f3d61b7be9a7282397bf4a3a688114ac5380aa8edc314d968eda2682a908ea5ad90ef0e0f75f1c3d3e0fe1b0e430cd90196f040bdd6a05d8228942d17c2cb1d520c2615865873d11bb8933ca9d7c5db598b28cc8fc35cccc84480f62d55ad7dc4385541e5a06e0705a5e881bcc678fbfc0be16975af66cba7a8ffe8a046ec12cda41dc77487c6f9682c94887580a903e8743347323c4fa87ed9b114ac046191454984834d87044685b1f44f60d646a75db63f79e63d74574ee9ca3f9006a8cc2883c73c35a73d52224634264e76e2f5becf061c11682596ba69fdc475d0a4f008cc1cdf3437bfe94b86680853071bc948359990af9e2e7e542357ff3f2ae530561ec4cab11a966d32ce1a1de410ee44420c8401c1396641460e20ce2d0cac6cac274c4d4e2785bf43e2d87a54661f5dd730c6914046b3af49b4605946b3cb9088ae0e7d9dd83fd479a001f8b75477ab14de39858fcff06849cda2ecf4ec02100594ea9d9e9e9c80fcfa79112e533ad079fb3714b18c62c1d0809e182267afdf3938b22e9ff46beb9cb36233e8f2ee592d31050d7e93f6fda33d5aa6784bcdeb5d2f34cc2a8a2a535d37d7b52686bc992ba6fd9a8b7d67dfe620cb565b6ee7d0ae5690bcdb41a201db936685026c5f76ff4bfc836d3917c4f9001c7e25a87d304120e8550d42b2b693579f7a3e4b90dc7429f8cf530c8b043f821bed653646906aceba7604f2a945582fc99f2dbeb2786cc1c10274a32d15b2112cf90e5f58e4a746d8831036f50d47d6d7eb207067bfa3f5ba22291bc8b1406b32491638511f5d4ac8bac92b7880d2cce1e36136d22164e1ea5ff200b582567b33a85092cde16b852748f3bd5dc9c95b722fa8f4c71d2a43497b2c4dacce47f4b497fe20a0b7cef2882b57c85720572ece074babd84e868703d53b47072c4c92d5398d2d37d09400d9ea3eae5af45c4c261cfd589c6ed1af4050745ad543b8d162d4fb6c55a1036cffbcaa0c25f34f00bb0c2d892f34fb2aa71e21c9a4c3b0ea62148b6810101b4a6b11e9049eaaec0343e39e90f92c65053e7102b838a59ff622cb5d1446d085359aa48ded534405f8fb4af8b207a530808f0b9c09da145459a41061932b50b90b91707bc549aa9fe57c0961cfc81ddcb2dad3177cc7077dec9ee5b5281c970fdb3f54308ec41c5854f82b6062448dadae4bc905cbd891eb888d8c56d5d180d6d6c5c664428f2e96288b90778a51d47cc5ae241b1f39f1380f5bb73be58bcf2c08c6819c13c8a0e790d282bf6b9dd4171be43a6c0f0f77862488c99a5afdd5d2dd3bcb2bff9fa823e4fa9bace4a3e37fa0b1698f0f44018d57c71e30112a5946a218e6cd02fcf2a309066f6b480b230637b3dab6f0f92bea61dc940de7728db68ae39d25f5a951dae1bf0294b3b16ab100cbfffeaa6f489dfda076471bd6b8ab65ea1bcd3b15663912a7aa90780dc95135548d7ff0114e06ddfcbb3cf262f1d018c53db7c2676acda570b29fbc812ffc8c74d23bfcb98e595d0646b13548a05ee9d868cb33bad4a63487f768531d2e63c449d6c9e1ced5cc2951996bf564758e6a6833bcaae7fd839cd54a9af621e4939c6e485c9774b1ecb8fed368c79202cebfa916c20a6e8bc188c3c6a6f1234900b4aea866622956062b97fb18acb59b395af749c45829573fa4c7e4d1402d7ab5a976d7bb50931d09a596a9b1e1274286d143c0acf8d92001353649cbf1d7cc48a2c532aec6fe6fa42fc857a959a8e32118922eb25805fb4964e4dc08ea5a18143bd56af1811b4d7db24a0494a5cc8b190535170ba110c0048198974df5851b2893b38e890f4a80dd68e75f2c360a26ce93a22f4fce61f9984d395921269882d8aaa859e6b1512448fb541a079c7d4d3c3c31799f8427551e347630313df4f5762f5d19c6d4b26aeb59e078534deb9c88ad8f0902353818556331bdd38b4bd2fb766f2b37deb9d15ad8e231b060f72bf1b3c51ddfaebe461e345eac7cb1b2e1bb68f5e4723cc8976afbeb72c95fa4182f7519b3a24adba4dffec9bf232f4ab97ca5516058c4e027f87fe6726f46ce696ba95c495f572f565d8bb4b543d2b93320387f20a56926d65adaa5e9fe40e4792ac6f77d1338f6d4e940dbc090d699a0a8481bba2a36a9e4a1e6d25ea7d8eecee922e16c84103e434c95df32fa7e8d58e38a6c2e433e00c9abde9172580d34c90f1cf54318ddda440ee0ed116301af2daa3da842facbbcb88f4c237d285fd5011d3cf54b0a06d9b38cb9d0a42d93a98641d96fabe868b348ae176045ccdcf59a0f4d43c7929ca2cc92acff551fce5a6be726afaae25ac3072181e19a23c305fbf9f826812bbf2e64b98d0c289882caf47f3a595970c429f55544b3bf6ccb152d9a8827c13194028655e7401a03936fc829e34e24bcfba2085ca40a83591e1f1039fea6e0b2665e0d807dc51009fabbfa32ab1f834745f75ad94d7f10b0f87544b29fc59ff18f528503448cf9b9f72738499ee247f39af36ad3cb4de2232065ded5f4f3c09a5d973508147e1311afcb7861eadfc673cbbffca1de01e2d5271d7079a0a915ee6030068bdfb1c9696c5a539d2b47d9f19a03cb12562fd472053f175eee36c7ab716c3e2ef3b276382b114067cdfea9d04fed6eb5de01612f90abbf0a831335e615d5e8215567a3aaedb966fbc4c0bc5bb013be319b1244bcd156b7980b74621dd62261f48322083b074586c925a3936eaf9d6c5fef0cbced51a13df04d982a91c29ceeb3f367f6fc7b376ea6f52b42afc7feb365a4ea59b6c0cd6f07dea48386388f378a1fb230e6f874ee697f328a43f931e320114b703f06ff4771355cf9a0bfcac89cb8672c0e554b46db6a4771a8a765316ff89a290ed10b7287c2cd3c3fdcccb5a7d74395033dc1efb3a2783a2957e9f664622be0ade6875a88884d58f64ae3115e96d20c85f5b9dd2e8a27371b2b2d0ae0cff99f6bfcc3e7cd7bb0c73fb668cff039510b36ba2c4cfd2780dacc9c09d9c059f1e6d0d67cfec2d8636968937cb86660fa6369011bdce0321e11af2614c388000ab9d7f492744f10517bdf9cc9b5236f1eac0d52736f9c06d726d0822f5b8b2de7254da24f20da7d897f3d46c8a09d857c2eeb3be1be16ab1910577f2a7a5b59a7d36dc15125c88b5cfe356552247d01b538f9c9ba9404547ebd130ec894d51694fe16fa2a2d6a36e829f5f676b8124bb1e846d8c553236020bb4e721fa545955e777bc58d0bec7f76c1fcfff3382e85ffde56cb915aa34d29f9a2d762e8bb81d57bb048dc9ccb0f365d135339721197a46914c3b1e3cabbe017100db55e4561d812a1c8e1d5b35bf033fa7546d08d1bb097f30a111fb4ebbc482b8b20d533bdbbe78b4f2940b4d81e8044708152916e51ca259cf73c6ec858dca72a1591294261889ac167756a2fd08a7142d7a47e758af7dc5a55f1a281047e8d61b8bd3301f60072927d3fdfcf96dac8c5bfbf3ae4d60e421287e1c36f8a798f643a6a76615404caf55caed81333ca9f7addbe8d045281b881487effe973ba08ca6617a312e490d4b48f1c199b5f283bef2d13226f82829cb5a07866d3988858f32b7ed95ec3cda187ff35e36af4effb289b6290a76b4bd576dd7a88dd30758375bd3ccad9f602fa3ade8bbbc03a207588c608c8df81fdfd380140adce5e0c4aa824a3f50de5094c82aabbf6ffd26aceabb635836a0eef0b858c069030e9335649ef0d1b212e5ba47719473fad34da0ec4b5acd13a523ffa1cd76afe4ed042b4eacdfab336a99477807ef974120507749e1caa0d18dd51895e358e8813313de5371e2f90b28ea3d7493ac7d7dc33ac6c7f0560586f8892c4d001a5aaaec7a0cf223998ff697922e89b0082ac6b901bc75ee8185f1941f4f00e5c36a9a6d3613e530728de7f2d3f629c4324006f5dadb3734b99c00afeaa0c545dbb0874e5e3ef43fadb4e8a0fa291ae31160cf2cb2cb8705fd9784df2b6af781495fa7554affa7d0492927322742f8c194a45fb631dd878a27cd52345fb23dc5d36cc6b7b71e41c56deb5f19788368d9017d3aaf32d71151bd97ed13f58980b3c6ca0727ef3e4f3bb0711b6d5b287ba82cf64dd21347393caffdd5e09d5207c365e937ff6af0964385dfc5d8dc0debf5e9542e773b815e6ac36cca829daab6ac949c3b2f9710863fa2349b58f7fccbd35565807e608c442ca60c5e161986f7918cf3bf97a124c152a36a0132990f7e2e8f2582df6933c638f7041eb8b11500b60eccfa4f78ff06044024ae948c5fb29f1e6320f70f88c3b30ac176c82631ad8c68b4540681d1e30d4ce421c941cba23618f1eff764a4edc1a9fe1c63aa2e6d848a2adaa73749108357ecc3a086d4131d1a31bea24cf87c564ba49abce47a021d05327413113f428bd9ffac4a8d4a937c024da632552d4a7e38873bdc4968db8e5931b46babe044367165b0ce1380d51c94292c05f1d189183b20aa4b2f9333710b575cc1dbdf9e0c524b8a1567ec5d6841283f647067a19cc19385684e4a3613f2e523cfa57212f7413dc115c420037ed8f4c5165afa275ae0afcaadabb041e9b3d33360c1ca4f2445a953149828026207ced4384667a83fc9709633cfa996e76378e1333ababccd70ebdfe54d9a5993242f6cf9f22c421b0c48a089b87b7b91fe6fd58404342bbe72644c1f36a5fe2e9f7d1a15c769c32a1df9bb3a8a2cb6876782b1bdd75e6f430d1e303eb9a826115837f5c0f7492eb1deae699be618589b6631c263a13bd55338100d067da2108fb58c9655d2df19727db2d430c0abcff85706b6ad19fb99112d090d3ee69bd04d92efa16c9b3306ca8547baca0eb132fe9e3c85b878d7016d1eca9970486354263892df7ff348d5c9a51858b762cc88fa11bdfc99940e43c288def9f45387da613586b4e21da438d92bc9a2f43beacadaebc717ae3d5461f0a2a3a71fdd6f71f97c884ff1a62b5f5c2566cba16e24b866548359fecb00c1c5576e05920c017c54eb389291cb7c24c2bde2572f7867d491c8ed01c59f992724776afa541f4f517548f06da5c0b36afbc549cee9029f39e95ea7c903dd273c92dd1c5519b45493b98459dd88b98ffdb3b1000d22b94054e28b1b20547a6ebfec345e9703c18b3ff29a7545ca980cfe52f9b4c6e0f00f80c724ebcbe2c6a7530f94b90145096ab7aec367b89c9db84327ac21261f03e13a9d22f58c7c6f288cf25ad0507e252454b0dd0f75ab7185ab65cf5ec682d5338414cf71c274937d0fe39a3e894a0888616099e002ec91ec97babcd499d2ecede0df39df31e92286f91e510da4c2746f038d7db6dae3f755dbc7ce9d26d4ef081bb8f74dd0aa72956b9b12b06479d9bd2c91c80fb757eaa96aaceb6ca38adeaef289ce84fa64b510fb711ea03a4f642443f7ed216069263ba39eaf1988cb7f7d5fffa4acc375e9efe59f1d69f970fe984317de568d9b59230ed995efbeb5259f93f52edcfe37af5b0b0dd9198dbfb41b161e6593fd6a97c3e0638e975cc87a0579b13117224d643505a5fa748645753312da94557d947c18775028742515a955c6f9a0643d353077300054874d5bef29f1aaa550e0efbc690b3247825002fb858553abdb97c38237d573c1e03f9637e493de58cccb6e46eb608126bfa578044f1b9a3357d8980718b66c54cc826fc09e420ac25e3e6897abe08cdb29b7b720da55be06e7c92c1f2d04c2ba284b27f3bacd3d3e452472271d1738b177233139b9a29cffeeb600bbc03847fbd64ae9dea2b27f8950e9938163f8d0695e17e6f7165ec609e445989a90a5f48c58c4f506171fc7aa5dac526da8bf8bf3508df8ac658f64e29a01843f1aae6ce491f7d50e93cf9e89fe53af1ab5b6b52d962f4757652fda4a3cd11e5ae031a3b28b8e85febc7627fd267284a1fc612c07fd40b6fa1975510a79251cbd6f087b4235ff496ef3797850a916e9b65ae4a5882ef0ba9c1f53afada26f6640f22cfec7300d01f1f78fd4edd8cc3adf4b4308f8cf618929ecf7d36e13c4743e31873d324ae64cf5a221269727554dd8453f1105063b26ccbade7e916c96edd0c44590ad5a41d147c1df76e1c87cdd845c4085e0376793335e74a4ab1ebaedb2efb19085a31335a179dbf9e3cd9c8d60a30907fcbe9f38c06cc1dcd8f807e7327e58d9cba5576e504926968687c2b3de9ac0326ae3d446c66a3762ae92a28a8ce27d5a37ceb49dd3a2c9bb3a475f843176d7c56276e661f30b19e73260a8737e681e890eb7aa78b1d8e56eba354974d323020e61f87d17d7096ee666cddd523c3174ec9a2db3f285f6f479d3911e76fd9a5a101f501eaa3631edd0e7f374ab63a02ace93812f610d8ad8425e162a5e6c7d9616dccc2fbba798ab14e63050be1265be85651428756169bd01f2c5e6db863e4a1130870fd6c6d2bc096a99bb07fc32937795142aeeb1c075bd9658964e6342d02cdfccafb2bbd41cd23954b61fe837f62252d6382ebc7d32dc74db0bd09df91de9d8610b6d258bb98b84fb92fcd895f0b59247ec96381a9ca090724942178dd1d64d8f66a57e2ac801d7bfbf8b22bd87fb0ee4afaae509827746b948de4e8b6c110c294813a58dfc21c6e39a72263c10ad0dcff878abbf64d26319fb634860e3c70eadaf84923027d07e825dfe4cf2bbeddeed4cf17f9b575efd2b813366b39a614f909cc43c7bb82dafa4cff769ec5b0f449e3cf9f499c49afda8850bdcb6a6cbbab8f75c83ce43d18fb45d3fe08e9088508cad671198b88ee741e2d04ee6f6107a4f90faa978226804c10a6e014bcac9e2fc1b165f65573b3ce79d4a230293271cabbe7c92df19c865b122c4e36fa06e4c71e04cd3c60570fc6d4f94fbbfa5d4322df861e042b9d8a3d97e1636a86328581bb1a6651769362f51114660717216a96b5532bff53311f606de1e71ebe5edeaddea5870aac310575c0822020e0245eada80949f50828598bc6bafc750e14571e21800d45a6e7b77bf93bdc32901ac09c7bd52a09418180b1981ff45c1db6cea08517bb2ba5911596df999ba0c2e533239b703eed67b478a83cbfe619c0a84c9e897252a157b3e5b1684466ed998a49afd2c768399bd6d596d9bde2babc492fc7cde3514bd3b0476566e427802648dd868e79aa4aebe351b733915bd90d1b68a36c5e86041a2a07d27ddf9f207a99f3f33262a6434f37c40c0f7f03030dc8b9f87864aeb5db29cec74fe62612d65f74ac46e07b85690b5fa866cd86196c22f290f8488d272e4d4593aa1f3d74c0765e6dfdcbf63469bdc9fd5b403034d9215534b17760069ff3ae37cf5b4b1011c083ae5060d64ebd36ecdc15cf94a179220ecc088cfde3a8d90accba3aa8a9297e819d52cb8bcdf4c7c6a4aaede4675e383f5458efe20d2ac85a7557a459ff431e5f885936824bbd914d4f006954eb789706ad04b01116a7f59c7d9f9f78685156cc2164cf4cf4f5d2730a68064d53b14d2a09949512f8c87a60a63e77e2ddcea6488a23bf4e07d24f85ab2317362a67ddeb14e96f00c8342e7d640a62acac81f5ca68178fdeff3baaf4ac6623a2b2f0ee6f2447d3efac8693e9d28ec0c5734eb4fbac2101f15cbdad6b6308a3cfeae6d3dcfcb7b214610dfb7487e42478539b887609666d3bc0527266b2b421f17342fb77b7c6dbfbfda902b82bc6b6a3d78cd7681e517cf36fc52cc5e857bd2bc1ff3a8cce9994d707848917688fefaf1748555940f51d80035dcd24067f32ec4d86e8e0242d18d971c7c39ccd6738b7133408b40371b8e6dc3bef0d8bf9f909477f24e13b399857f2caf6e479a6409bd78e4d9f4a94178d6cdca57a622dbfc145a34f4d0a3ca1af1414ce9fe2fc0b6615a74784384dffd8c4673c091e570c06d0f58300b3378a0e6a3394d6782421f7587912cd5af299653f0f8853e16a579ffcadf2ee81019d08262ddf78024b4f60ca9c687dfcf8abe2db60fa5d2bcf201a0e94813475ed88d2d19f433665bb79719f7cf56d84e642bcfe8c071df03435a9fbb6fb6f26fc9042f026f37f01284e22ec5b4018ea7232cf011e11cbd5244eaee686422dea52f0a3143d1eaa9c7d8ce9025ab4963c5385226584b1578cf68c12b9ec068644eeb973fed2c23b16532ce72d690d8c60e30cf703c86b783b3206b77407645b6fc21f77a25b211cf1184d10e0770fc75b6f56fb78d500fac523c659a4c4fd43c51538395a2cb342094996f3996bd5109f58a721529483da04d953e03933ceb9dfcaa90f781fe846273a8e4354807a62103f51f17d3d14d91a6008c9f056db2ed1420808dee30b685631106879f328a3ba12330e68a40fb8e0379cec6449e7aa9040c9e24db319d901485d8f970a0a82fce4f6d8dfdba3db58240db0383d4a8741a21d8867661863416fbfcc8293ff8b8dd7646dacf7e8ef097313b51722b442314e1caf47c3aff331584b47fbac2ce8d43599b47b9295eb4f6f730d86828d0b5b8e9759124f079d54a75c9f6c69178a91e5ae3e561bb24f2f0c584233deda758e98f9d0e8fc3f4f25d588566cbf6f0a9d5ed85d4063e33529bfa67173d872ed8871ed0bda845dea17b35da4a078b2e27515999bf7cfb61e369b1fe7e411b0b9e52b6f9f95dfaab917cbcfe7c2f9f3d7db5930dced149d192c2a68926e1d209ce258b3b7e92fcd69686a88e5f19f7a2dc927112a96dd1da8989d4de3c3880c94d2918af7930e02394677aac4401d12e5e9c9008bcf10503ea6aea609ba09789d05b9c8cbcc9921045213e9de63dffb2d2757a9baca917ae47fd8288346bfd6f140cbe02586478f6ea8933c87e445b778fec0ff1516f8856111ccae91afd550a7b36427f206b77f551556a5e6d0af7258823b004cd503d6d31e7f4bcca41a00ea671391e4fc916ed29c22d860440aca2a90cae6cff8567034fcf85d2a660ba01adf3dc9bb07be64fe8d313087b1f484a4753d441f27d7d49e422aa097a437567d80b35dd07895f1f8ec6a043bcf542c4294df7bbfe0b3d93311673ebe4abdee01ce26aa8999dca175744b8f6d9acaa55a84560b49c5fafd0ca0c1621419f18fd9cc8973d5f677ad8204781a7fa91735af6ac67e9a83f4109b62010d19ed8f3c77ccefc1ca247269bd33a5e6677f0af5e7cbc5df9c62dee99c4cc32a9eb30e074af598195824c960c3f46b05e3f141ed6c0b57ef316da7fed8ff64d6fab01ce2d3673a82e5570fab0f023f5824e3ed789041cb4e2c39ffc79419c8eca29a9880869476cf3f1695e27354e9493f3f56cbbfe9362a834f6f468ffbb6e5877ab48945240c87cc51ca749034035e1414c99a1d351c4dd02833e2e36eb2487477137c015f6c319b09f5990e08c239bade582328d7ab0152c3259fd507ddbb046961f60ecfe27e7155effdef1ca3050cec3770c825a772efb5a0a362d9595249e0c05095429d674079ee1bec891b9f7322cf43b3e04ce12424699c1f2e71f2270551f7c5d70f82a6f992b28e971acb29a48e0cbaa96a0322dfa1755bde2d565a1ff340d2ffce1e8cf111c44154ae144171cb63f81b2a114cfe39724239dd87bb65e7318bbf59781af1308d6827bec50ac82a348461c868a68f793aa2ddfe6ecf0f995ae8e8962d16d9a9c51a37669eeac33f6e5e54431d62cdcfd3536a5348e36ccd8da2700ac92cff26528023f230bb2193da7c5a4fc3eb31ddf557e55e9e72042edc3ac416bdcc4544507f1219d8886cc14933594554e2f9b6edf8c46fc3dac2339ea7cce2e70f38a4d756ee7948f022305f7e9508bd48f04555d4e2e5c142fae92411c821250613d9cc8b17d8e3ced5649f48182ea242996db5a0c36492343174cdbabf16beb4064d588e2228c1b3bf0400608e0315eefa35e11d907f53de61e2346989e788e89316b47b60fa75b82353a64bd6494a85ff0079a4555ed145be4048243e2004effe40942ea7a5f417bb4bf5785cf6fed5c6f983b4836a3e9d36b91e9abccdb023411bfcc0580f4fc5514dc2f6cbad5881b035185ca192ae38b79094d556a07675aaf8a9ff53f35f903ae35ffbd93aadf15859c7aebab57fdc636b99e37fb98d051a6f33500f956c2e6b1d8fe40c507559ea7f479d3dcbc98ef66ccf6efef661a9463d24ae49893e87eafcbcd2de3918e88c5d8a08a2a4a9a7db5f0874da88eb6de6bb1dd8735ebb4ebc3fc9928ef836c4a53f970b2dd71efdfefdbe8b3df8bdd0b79260e037142af4675708ad33466a2f98e92180839520b8ee52025858ff7c1e7017d92e65f150850b4a3924c595ecea22060248ed8ebe197d0d92f14d3b90bc4c95793ef282c15c5ee5b17d4ec9eb8e21835e33d922763cf009fb79296fde28a3431b3c039e42fa7216a81988ad04b4691fe722acfd3d44305e1e665b9b774985f63c6d0af39d7380d7a8b32c567eca7c6f9a00a196afbb1689682a79433b6b662646766aef7a3a462f8de32822128c63cb7d32419d5806014aa369fda97364b9c409a4699560e8345775c6e8a811bb699a232c65d92121ca29d0ba4f0c4d93f3fe47c7a3cb094d3858d898c08cecf48f52e88d8c5f812791a8037119400f02db15ea5f17c0ef1a55ae5d69ba492ba9233894e07bb9a698b6119486d18f298007647d083d5ab322c4090ffc8f39a8edd1ac4fd3e9826bdbc4b0f17fecbae42ec5d430b47912322e980a1353668c6865f5b45aac3ac9f13d067711247d8f928900d0e05d19d8a2f51dd9b201a7ef8aa8bf678f260a350f0af1d9f2193cf1672f02eae84207ec0a6c1345302279572bdf8bbd7b159acd535f30de577df88b9a222a604aedd5024a4b865668716f3ad01b76bcb531dc4e0cd94ee93b7fec825f7c700144c505cac7b94f6d20e90f946e63d6055f617f93cf97225feb1fd79c4a73b2765a824bcc385b3b0d7283e6e3995a5f39420525b69fe9b88975e075e0083d7f5b10005652db85bb81f5016ef09cf7afcd5694776a17c1655c65989700330b19497e4b90f391ba054ade7555e1dd0e8c8f1552f67e24974acdee008cfe2d4d0fe8eb53170023c6dfb77a96fd09f5b9bacb6208b8b2fe57b54318b6dff6c158fe44d56d7a2fe4ed1db3c7f5cf92de5e3fe0f11fdff01137de90e405af95e034bcb7a7fdcc910d08647c7067ed5db0a097fe06b61cbe23ef7be6119c1a5d67252331574cf3017e98a9a6cf6c9e5a7d8554e3bf874905b682b57f2e676c1e7d76bcad275f8dbd93894d0094f3ca8cc354b973c59a8916ed3d994dae9220d24573d173550611ea344a6dee5f50161cf0b8dae0729d3ea2ae5076a63d74861d30729133b5ddecc2e1035c341a8088b79001e80ad7f385d0debf1fa5025dc0bf8b6f2112414bb12ac5a70f593f404717a2c2b1ccd108e14ad4d2093e50a4546be2d124bcd5871403efedb4f828130c209f8ffc95e645d080ec443859275df52ecc33b58897d74539bed909da14f4f7de91d5c731007a385093b52ce545aeca1e3144189c95d80e7338bd306c7225cbca02947a2e47c423c14f8bb4024a0d492673538fc5b66a55471c466c2c8abb98526da74634f74a1aeb6cd99177679f4f3c8711dc897b31df26596e5487c329c4320ad020c4229f5fb18d51e121ef2a685a1d09763bd8f66dd367545a02346afc312b4be5142ca97266db73b6fe5ba57ee9423f0aa9af73b3b8bf6e6f40a321c57c746ab9d740902d6fc8b10537484f417949529f8b01e61a4dddefa469ad2a3f8fb0d3b93863a37b816ebca6fe0456154e4c5359a60d5945a980685604d5f54fbbd97df4dca80602747fd37c56c1c2dd47053a8dae05e78458336b6bbedf7793ce46ed6898794cd428421bb5dab748548ecab96d4abc5cfe4878fc14e6e751fe59b604af93f678f1d103e80fe1214f44f6b9409dacb7ffab4138ecc3fb6076189b4b8db6b8a61961d23d5f68ecaf9ab3c73519a1c38bd9e9aa92bebcdf148638e064ec178f5c2ea570a44f7099d90313cb08529af03d207ca34175e5ddaa6be4b3a8ac2b36b12b17691dd25f3112f2adedaa83fc0340d9948fa7383a7863a68387f300bdbad9ec01f5244736c310053ebf5d5470a0bb2234edfe30a0d127453ac8825be814750a04f483623dfa37385e90d7ab4b923d63f2a97556fced3f01e3099c17a7518a94bb50e9d026b76b93759354b782af4b2c6d872b0f2092ab2650c7ed99cd82b8f63241646a3aec741241ece601a9b9f119787e2138eb8d684b17093f1d35bfdfd76263ae3384b78858546f83fa09dd665aa146737ba8f73157f524e6b588e67eb825660254603aec45a3bc5083d4f32c030f7d87d2e2cb9fb57d70e794b94a2520b15c7a94efd42d8efa0b33069adb583949081502b87b95b22f13b5a8b2d2b2696783b321ba0e251b922c11dd85bdd3262c9742f1592b557ee455e474c8943cc4f7ed0aa27f12a230a20b225f7f90a18bc8f5123709f0ef4e380c763e1e7bf9091d48b84a44d6b263136551139a72e0ea2f64c2f9134dd7905fc2456c6d518c0f4932706d844f43c7a929e604778a9781df105297a3ed69d0153b45bc57c0b40e1b9101932655debd582b810c7dcd36d0b6c71f2174705f0e2fc36f4226dddfafddfe6a76f1b6baad3fb07a0f3e975079a4237756c9b7dfbdcb8fbde150fe179783419c973ae8142b9bcf5d2f4ae6a568fdd7ab1e4a7aa80168ac85e127e97ee249e4170d0795102bd2bcee538b9fb9bb2ad430831037b09923db4ea436eb9a04894cc1f64de74ed14b78ed01f65d2dc7d4c6535190674f89a5257e2b780b6b069c96e17ea6656c65aefb7258643c88661febf5651a4f372d8b45491e663641d8495e878c40e1a0ce5ef75d04d68774373858ac75c1276c6d4adb3cce9fd0e0fa153ef9659f99b4e7e071e99cd420c7f3c6ea30d99ac4eecf12110cfee55fe73ab18c4818f15c4adf225044f14e77348b0ff40b43d03dfedd7531ea9d087b8ccebdc5c87dfda8ecfbcfeb2b2f503dfdb99ce9d267ac7c10acd714d1f25fb6eaa7336a3f2f6034892cd1cad20155074ca833f7e7c46fa6552be4803bab85830ebafcedbbe9d9c75bb245563736223af7d149819b32e45ba9037c3a158e1c8d1fede78d07171a11155bee2eb277cc993e7c6c5f0a02b30224ad1beea16321ba6a8f10d54ec6a3ca4771bf56269e4b258a8a2033d192099ce9649100198aedf041c8871a8e8bf1dd5ef4366aea10a1c1f33038e1810f8f2abc91c817f1b391f03c67ebd718e1fc5a54c5af374e54ea59a3004d7cfe27551a93518727845991e47f12a43471c593d9f5545c2e91a6941b65bd877c9002c21a7bfe151baf29d69f9ad4fd1c8086f6da8230703b64fd996c0ade1d923149ee8a801928f4080bbcfdf1bc3fabb831a4be83e48f51ac5cad466a5cb6acd8c9509d300b579abb773165f3807b7463770c31108f226f9296aba7e3ff33039d889c6fa313ae3d08fc209bb5f16144017aad908af773b14ddbc58331f5e52637a570dd8a4410e37d3911e7990653a07e811f03d4f94ba85caaade2e12e730818c9a782c734fc4911408024d1cc12075b5cf3b1d2de088f1edd506ff79c01a3cdb4fb02eb68cb60c836273724473d35c0735921ac4c163efe51491930602699dc6fa0b6d49d8eeb96e0c8e853e8901e5cd849718f8b0c6af9dfa72fbb4b021a0ada1f21904b817b20ca859ca4737a6c0eb302d9d9aad50c0c3892199bb137c3fd1e8dd4589cf1e42e47777415d3b4979527919da10327b3477b8330a2b4af4c2714f4f8f4992e08ea377dd0207030bd91d57280d4a649c475891049fba55df2371c414b00d6eb29dbbca62a3dd31960a12e58451992ad56e80ac6d55a618e7301e1aa23cb1b1258fb7572439ab2fed48e47b2fc0117e9826fc57dc15e8464d25438d271bb9c42ebd377cdca1a7ab854fbbfe5883a1752fa017f3b443912d7c0157a5ee60bbb48aabe36e8b6c4af78a9a634c289a4a7cc88e8b1c070c109447470eafb139abe08e465a781f2163ec0feecf71451ec3e1160ad22e0c05fed3aebabf381a4e5a410ee006b154a329e2dfa810a47688bbb6ef5a98820c0f1f6e7c60778fb108c10f9c0d390e2a1a982342f578d7fe138711e5cd8fdec9606006df21d665c6ea687ec6b2f0af301f97d8d6d84ef76f6a26e3cb1b68dd384681af4f75b8aee3ebcc3d4785c09eeab7ee1ef172fb1124038c80df6d0f31889f3c828de8b2f60876485bf2bfa7edfb339a0785c76787ffef1bc905a00a443f7205f9ac43ca2f63cb9fa428915be73e4a6b0a4ac9027d2bbde8b2c4d1bd133f17f127abdca8d5a97803a255ccd27922dcbb14bffdb97de71c72a5e50d17425badeee9fb7cd35b5dcc53cf448d336da906b58eeab0d237e6ba30b56fe9654428c03f9abd96f5694e9dc6e7e3ff2db9df3cdad95e0f174e212ffdbbde9eec089e1fd1698de6cb3009a637675cbd001b1273b4f794970c3f1b7d64fea646154e2356b7d72c51ffc763ad9dfa9511408832061a803d3befbc90bf1f40a2f156c4b41642379e2c14fe8da73bbb09f555daced3626f643cf182eeff775c494a3e47d8ee29edbe05b37e7a26838ab04c99c60089ced70d906254323acfeafe69c5a0e65d18a4234e44582c3f7457bf0be28ea63e64c1353e78de6f55a601c88b09ecd3deab5deb9ae6294f4ff006d890bdd1112f3f60dd8cec3ffed123f8d216df9c8b11593e0664dea847d10d32886b4c6318fb199c863f9a81d5644a12fcfe469405ca9c6f785a23360fad42628748db19c8e5e0658f7435ad85f28be4503fd87630f0150c4376e35317205289c529d239962fced9818a7f5ab5dbf8fde9468de7a82ebe294dee71ea19f2e3d29b0161f7df76ff7df2bdeeb815e53c26c361cd1964558999edeecd935867e3fe252aa95c7240ac2444cdab9851c8096e0369f4d035df9f8e88b47f3a766a5de1aa3f9f80c4c06c6a0e42e9899e82872fe8d947decc04f6b00416f318ac8d49dce67754aaaae34e88e1d2d209c85e0c39984c1f2cd40c142ad85eff188e0ce45b53ead550fbe0984573b4e3377bed8b4219ebd0a5e1e1cc9c65da1f9847fac62fe3510d4c385809080f3e0af063bd5072032735cd8be9a46e0aa968896a4ea14cf9b5962e5552a90115c1505beb5fda1bad0a2a903587d2c603df81a4cd9ae44703113996255fb1f9d00e30fa3b70e8f3a77363f05902665dbe65130b02d29660b1041db64c78140a74c535effd2aeeff5bdc81ac72a7a5917a14cc012803451fc5ffa3362b29a6ce35e6bce348bb2bb1a654bbe31cb99fc84eea504931a7e9fcf8853f5e5452a31de62f00c0bc969da35ea90670c056d28b1e0b298cf37117f73358e69c9a1ca07b5284797c57821400c4023e713a032ef36ffa95cb238bb4ba4d7fb1d59563ca003eadb9f644562d10f0031dc7f7b082166e9dda2e202623fc6fbf21d12112be056fbd23536ed70b6eb8140df37e7ba008713ae7b86c707f61293da123b6f16f2cf07679d3bfc8aefa3a4f10e30b9f1ace5a713aa6a752cb95641100b7ac4de2827de50bbd210edbe7e85f8d2087ee4992fed5d39a55f7cb6e34d7f9941ac5127efafe2b52d588e3f5c11efdb601115ff885959156b90c6209386bab912e3da74716cd100793325f42ef9375606b0a257144196cc311fd12187dfc877530319d6eab53827833d3aa6bddb750a1fcf7344298d3924c7ca40944d4fd1b48923184dd65cfdd384fc8fef354a4ac1d30e0c200875b292e96b26ef3a3ec1e57dc6d7acb59e894ac9c8cae7567b3fa26aea917d83685aa326ace67514f1d95f031c4eb5d01c4b47443f0890a970d2fddc738f519d13f08ffa35abca98abf9fe48e3f8e30ca77334a9539d15904a9a5f8d97ccb7d56d1d487fba3dca9ffeb7461615d91a95a84090ed15582ec7c1d3c141b101ae755218a8cfffa6d10887d2c27aae4a8dc27c6d04c647384d7e91a0a34cdf55599382f23cf6d75b148c4bc2abdfbdd8d04ec620880fd267dab3c9ba1e336bc152a86a46834012a173580f329bbf998269d321bae05070ad562fdb4b48d37f3bf6e04586b9b6f5720f49a73e6c5db24d82e3253b9e79b73f3fd0e240f012403393cf2260579e3d138145675d225ed94b1b2d1bf468c7784ef1941dd6ca7577ee5d2de70918d401b753b4d35450a0a3be9e44b13722daebb79b3f0ce0417cd7ea96876473bce38f0182094c56db381de0226abb9cc216ea8fe27aa915ffb1d6e7a7964fe09f93e298a11d122b6d6c82c8d3daf069932a51c45cf765557b45c3ca4156a109f13f26e9237d9d048be386a32010e1d42e11a469328e5bc6e305a0d82b3d46c3cfdddb9c42f2999b101244fd77eaa32e5f1e5c3fa45d2f73b8e779adae44c2d2dde9db120a11b5303dcaefacf24c0fb8578ea3f7b693d7ee97cc020a9254ce68ea0596dbb61757c9da66b17e83fd44e3c538d6bb3692ae01de062cfd2ba462d6a82778b246a1aae36aa3bf632a3634bf7e878eae0008b0f68919e5b799565e2123c5dfd04f51efb2188c4fffc679880b7605c7173ae81103917e291d073e1de8e516303c1212f51abd219a041c3ce98a06228be467f380dd75eab72da02ec938de16fc8574f948e3b30dd42b7090ade7f72111905331284e2c4da39b861b88cd6992b6881a92ce881c43d83f90553d52c703c3afdbc32258c1c8080cfc7f4d6fb16e5eed17204810ffbf774bfec799847a6d98b9653b4c3b82aabaa21dc60baa6d1ac75e9f9ed6afebbfaf46abf72e3fd2732018f56b7091cbf1d516584e375a2eade14a45ef5ba887dab9fbc9d3dec4456630ede0dedd8ca9d48e7132417cea5696ea4804161f8192692f5237459a0e8fbdf9303f8d0dc2d6486d40e66609052ba297c659853b703b369cdfccec7b4d8500c98c4aa8539a95cdb4998420c6040b25c5602840a5451f6185e65d67a49775905e1502c8c3f3289f28d2da2f92f59f1f2f97f3f3c8a9c62c608198488595c65f77bcf14bdf799cc357d03ee7f6cf850a38dbad77616d97c7d3b0c37fb5322b02bed1d9f63b9e5983acfc854422d18cb3547c60365f3714adaf81ab6ce0df20565a0572d834ad76f9047ec3485d91b6a73c00141dff5bcd819de4fe7d0f6d4e5d5741c9454766f06926b619c7c2b81c011d5e86df844cbabf674c13a5bfe88227b7a77f134fb372a3ff1218f7460f92f54e3f084fba8cc2062a34c93090b7d8aefd5e1d43267912db966a315ad752f07d71463f2704fa04dce18323ea709af0869b121e220a0d96f1222c0dc50216da5ab21de8ef3c499b3ebe4a54d45d93890d29fe321b1bc3817aae5fc83c720b2e73a3522470f81a80630c9a70924f2893d037f3aa161a520cef925600e275073f60ba71e092ccb129f24995cc9034104f9503e6c3e97cb9b56f6f7b9ae8377095cf0add29f6166208e7617f6bab84aed7b704ba21f2f2e49c2f48a004b55f5f888c81bdc2466482eb207de4c4a36609263ad62ff7fdb848109b76f84fb4c5c311e3fdc3fea818c3c566c2270ef8f74bfcecbd05f0cfb0909167a49d0c5519e0f80f445cabcef45f43cb6a3e5fa370b849c9b90c37cccad2a0efd0041464f009bcc818687998aa605fad4426c5d9b12f730a33eb24310b5ec8741cd401258f34a0c3780726d2f95997fea745472b83669c092b1ab0c3b93d16627042130690e7afb6ef4347712962847b1092c840e648817cc27094ffc43f9e999ba710e6eed637374ffa7b8d16197a97599920fafd1249153ecb4bc43a9956554466830d048c49105ce8c11074ae86a8ef93be19bdbffb9a90ba6bbbc1446886ba01dbacf7d27ce75d9ee4fbcf31de8cf854da16087825ba932527ba51816dc3b0638e3e5221bfd3d3bd10224814aaf56eb79cacce425490c82ccfa1de4c751748936e736a19e3a06027a49664b7bb1d26bb7fc49f0c02d9f692cd08f31ddae7f29b673c9ce4644517bea295f7d5b1874679f77cad1dd5136f06252b54b43975f9f5fce8251fe5ff993166edf48a8aaa960b74a7939b470540b052a673b5259510fd5d00dbeb30110f1c5fb31831fa5228a6f9e3b6edd35195a31cc2d78eb1a62f82ae994ccbb914b136c649328695e34f714976e6181e784b07021dd0fb9cb89447643fe45e54ba7df0c88ecb73cab3b305d22469c6720a6f5b0bc2b213fdf69496d7eaf8c147b3bdecfa076f25bf48eed03671502f6ba574133902205d1a990eb4fd4b6d390c8b4962ba9d62fd5579a011f332876b79ee229f1cbddbcd51bbb99310aded035d6a60c1ad77ddd956f6a1adb995f4218080d476ba107d0d20bf9c6385c6dd70927ed2b9654c4717e26ad523acc6f35df6ecb3da008324422cd8c37caf0737c789d9ebec48d6216057e01984abc47414e484fa197ffee0b63882c0d17796e7f96446f8696ef1a1a1526173fc1bc249eb37b7d4bf033f9d22660cc735fd4559ad5479be0f25ae75df149d5bfb56341eea43fade4ca58a6542460bd303a8ba27b4ee7775aee2847e70747bbf42fdec889356e1b98df984ca19a70c3c0e8949d394d6ae93207259a309d73790914f4a678afad4c2dfe8ab2420d3bad23dca4010c5891067b3583fa252b4d38c73e5b8c62196b9ad68536f65e2496ead60bfd406b1f177b908306297357cb8133d94760c176c5c517d8253156ee0ffd316956bd001b466b00af921b8589dc43c11255c7f9b9a46d12fec7bec423f7de7b51e98dd93ab03891bd4bc18ac195a108ae2c6714c2ec338bfa866f129180286b8ac182c207001d42fba5b8762392247bcfc0b91d25c70a34f9db85ac13a37615022323147c1ea4894e48e2a52f006c7c976ab7f04d388ff079c377958b4f8bff974fa08726a83520e9c5a776375f76effe0d80c8990bccd5b59d8edee40688c9710cb36460493a297bf9e60b8848259d70030dd858a4a5020c0540eabd3c940a042de79542ab146020fe403d6f3147fbca4b21c37fc24b6e93a590578236a0736f5defbf45d40dd02bdcc4d259380648a1465f438dce3ac6f207a4a187fee56f644fe23fe31d5a6b2e3e57233202519415301e2cb2c38316d6cbca6f9ec8384e1577211ea0c37cf7d25169b01914f2124218a7f6608c619d63b4daca0fd11c1ea244c38644319d2a2920efe5fee92da8deda983e196940468b3dfd5b27dab8025c796414d9fc810afa7ed0aa256b1d56a2640e43df8fe73c698139fc7738fc7c66bca26f70a33d5521e01b59b9295c3abdc3741afe0fb8fa88d0d52eac4689e200e34367b0e9f6cc7ea7a6b4bb411874be2d09cd0d31abb22608d1058ada3809256c5a24a05ddd30b0f6afe294e518d862d920bf1e6c9609d6a516d5cfea0f6a81dc9809101231a504a472a5e97c0281121f2cebfbc9213f5d62a92dddde1bfe433238883498152a6804c77893863436f8366b28693cb7a39dd48b4d2f1c38c3f250c50cef37eafad5bdbe1d78bf4e8863ae9b145351678c0d9a71571debf1dfa9b93ddeabce846a01b2950091f619fbe89fecc025bcf2f0f5b186593c0bd0af47f04f58096f9f3afc379de4ca5dbd38b01a491114694a0ac02a96c760d6d3acfb5c7832b42ea5d6dcba002bc254e5e92f237777c7c08a3a4f63e78df2cda73e86522ecd4679a7d47f82ea70bada1e9e9c312ddc9ad835887963938fa259b3d535155ac8759369ef8ff1f9b38ab65eab8a018ee643fa119d9570708ed21847a27a3f659573a293a73b212459f2fe7c0c53f12e851863d28465499cd9716ab05412ca0a1a1fd316694f5017504191a2a1c3dffeb466fac4c8424cc48fe25662b1fdd2c75abbc8670416aa8757062d5c75bab1ad6f9c68d99dfbfca604e11a6008f9f964f5c30029f3ed31bacbfbe35cb674423e1023f47f24c32cc6c242e11b5656d2647f110d050d2fe38e97e3df027cf956a4ed3d90a92d0ad86e6fbda07cffc39848b0bc25da664ed9d72b90ce301d64794255743c7ce785ed6dfe4a0b47d12dbf46d3383f2c10564148f3edd0d588838ae3c0080a50aa4497f197cde4f270557703d7f6b44d6972153b46cb6c0cc6d32536e67978052ff01da97e199654496c852d2c10e8f9b378e042b7a7d529831d2e8b417585fedae0086f51a2162d64711b72587a2cb567fe2aef7a9ddeb4f87946ae640b66dcbb8550afdebd9bb2a41cedf7927827230586edc1cb4603fddcd30e0a794ce94241ebf2890783141980001c92e8d972b6352def71804efc7736cf66e8960b69f4fc18dd325b9d06037259f38735f36cab3ed182541d5d3682e0167c1914d3c0e558ed2f4e90b2d91ae661388b14ab1c0786c75fe14b858fa80b0e5130cd6e200051e6ee2db83aa8f68720afe28828c170d17d6773c4498e42eb57bd855ecdde2aa97050a3be6df4a27ee763c296ce1dc99f6dfb01c35df2786f93c66dd1b15f59b481fc22500ed78855100a4c0f212c3a14149c3a88deda78f30660b2061ec7f2790863811ac28217dc515f94a6681066325b9af5e206bfa29693595080f57fdabf80f8f4627d378065b16af82a38ae7e5e03f5604d9de37d80082f23863b4aab9a9717777304da336095b3c82375cb6cf386e78d4aeb610b7fae66240d8df9581ad69a778e9d3d984ebaffb6f8ba966d839351efc7f67f4c2e4f425c6c35a285ebdd9021ec7c66b6ae1b747f557b79486efc7f01babace0e611220e651c6c1b129038a684528a6c79b47b426e28821b92e12cff58019af4a7be0e8b58b6776753de7b807562c0cc35013fdd2d3c88d2d4250d21e434413ad4e005722bc05f89fd6ba5c006d63b6e7a2814365e53d577906fb18298fe67c852b2778a17bd970aa98fcdee5fe953abdc9538d5f26d62b3e7b69f137978dfaa312351581c88f1388e0471565668c31e2a0b2261fd42c1daaae4855ad94de281dc447b732cbbed5824779daec8a2f62549542471a7bc09760e30a206a574a4e2ed757a1cad4302da78062f0732a644bf7f413f2dfc6aa049bfb98a7b2d7c5475aea32972cdae93a32b18d4c696b24cb2d6c8d3ba80523a1b78960127ed2eb911322c4b8f6ea8933c23ea3e644e51a0c185928eb67cbb2fd4c25096a946ab12e315c43d080e8e710ea8b1f76d720a5e10dde7c1a5cefddcf5836cc168f1f0be5cc81f3bc4d9346983c1880a94552095cdae79f87298a27e9a9c9484a95c2218df1d1fd969b7f7de2bab419c901de987569360617b8382e1b36a7562aa7a8bcf32e37165c1a720620747cdd900a11d1038696cf68c030ea553ece3e5dcdbb841229ae9fe15de6395a5e84aff2569f91ad1d74d510a210986c372f3a6d02eb0624fb492f5f5f80e5de925f179740aa575b3409d0db15224a0a1a6fdd16dc5553ee5803859f063de286ec745c9f5883ada467ac3ed3260c69c30193c96d74df7cda13b8c0d1f7c140b85ae4825e12787a27119a7ba9dfda93a967ecd018f3ed30b2cdb06c16c92021333259ca10a5f35b04cef1415463090297dc406010d15ed9e84c371c9b1aac041dd036f048ea8e3cdeccbe7d8250f1fd324cb7bf6eeb6d8716a44a28cd4c8b333043a2103735f39c85be1a18f9d9e9ca70570acc33e7f3d7a2aedfa4c4fb05e4cfeac16a58b75a09c51cb4da29c8c4bc91d64c7fe90106f96df1cd99f8ae5ec2872557d4c3578fa2bbfb2575ace930b08e155dc1274ffcebd072ad5db268337c4e8338b5d342869204220723f88372c994704ef99acf867ba80b3eecccc7b3bbe4e85737f58980c6059519444f608bfbd69857ed586091dd565a2c2b3c2a8d3237a91e45b7cf5909a7d06616dfcc79a085de9df6247642de033838bad4afdaef0078b0d0f28523aecf7e6786f655dd27458f451c740ea6945feea5ea12b60da8f8bdc2f6e5954215d5cbfafddb08a9b201c4f5b84fe915da81e908a714a25d73dd102fbeb42681f307c7b6ab82eaeae96450a29cadb40e007b494be1b512199f5f2933e04dfde4114992dd0e5e383c8ff3aa59d569a1ab076de013ac4ad8f0707c57a5ae1fb53a0226d5da57eaec99c00e6e60c4d333836eaf44bb46f59825a0785cacdadc2f58300b5c2cefb3c2ecf21a83f526be3c6f1e5b4e2073fe6ef654739eb581ce816f1c50222c26f8b27168b3463116a6ac1ab21c85499b7e592b17613b38242e5eaafaadb845cdb2f8b3e6d359211e6d6fca227c888bc01c5506c26d1c9cb09b66fd72f5bf50f9d2412c83f390c86aa95cf93748e962afc95efdc04b5f028f04260fb7f242dd495ac9500488774684db98d31f5d4450c93e8279bc583dd9230b0f51a9ed9d9b95f36e8f7dc01cee154363e70a5ace94f9cf8d81cb8001b764a3c52dce86f79a0847cf1bf81c25a92d66f2807295735828efd604242bd9b482920f73f61bb5066ee0b7ef0ae4cbeb4e81c016bbd3fec08737c0e79066308156ddeb59103bf1124afb430f8366b82bf54d807b3710c5877e45c83221916b7192252cf90d3c6ebdfa93b1434ca09e8372193ee6f992a7bd60a32ca8474548e3c6beaea372fec1513f87ff2cba09cc0f9134cff29be6db832b7a264e4c9d52588971c49ace3075ab02aae6186cd101249545632297e3a3413910ea6b7c45dd15818c69b1cf4e97ca74972bcb77f9848ff3b869fe9143d031b7ba33442c590acd73156bfaba501ed7f0afe2c3bbd70fe24791c7441c218f96815d0939b28a4e163ca3f30fc49851cb275f04cec955c6e7b7d60f38f265419054afbe89ef632f42d7371853d5e7b4938f97eafd43a27362847f5bfcdfd8dea06bfba4e3aac546ab1f5d4857346e395cb0b5e1fd50906a3480e02f6cc3492c7c87a8c568acf691272c2f3ab7aa2f29ebe985ffae7ff784e6dc40b03e90acb54ec2d64b2c879bbd039e7c3a0c9b345ddfeb6a6f1e0f1e0f0bda6498c773838ba47e48cc3e60df5f4d5a37ab2ec2746875c486b5ffb4d43559c55fca93c75a5d5b33b31dd7de56a24f489efa60c0e737ff7971e0a109e9295e539de51a316a99cf3541692c981e99323cfc4773ad71f1c9cb5965a2e133a1b1945ef6918b776284f7ff79bea5176c84f528c1f697f5c168efd33e7fc0ecce7732be7486f141954a571fd33f52fae37adc0478234f1ee0d3d387dce2d58fd1bd981b1139630fd479f62cd327bb2c1a5ff1c34adef01b05ac79bb010e26c30ccc3b894c9cc8bf772245112acf8715b7780d754e5646eab2e8ebdee2e18d0fc970f345e5f8071fbf94c76aac8250c87fa34c202b685ab32090b28054a6ed6e969b863c464ed78f680ad1c12b70e580ee1c0ef1b2404da373cdc967158ce485b17897b679c10c4dbf7c689e1b5e4624c66c5400256b790b75e4f9fe0129a56582bb7c4c38a1e93adff72741c5078da45eae9d1185ff904feee7217cc0bd63ab5e89e70b954faaffa47ea24d1d324b7764d1a0113ead61ed8680bd225540fd3846c6237605bc291c842df2f428d45264074352ee9a989e8472c2a8c5b2a011c10f926f393824815073b1278660fdde183d2099834721e551527624124051e0b5eca48e16040b4a935e1e4405c58f24484a0895c26ffc91a59271b640bbc992841224c64881c3014560313fb072c98387c9206b245fca9494420d860c0248312b48a03a6a68521486c0992a00906ac1b0e44e004416c0992a0092622b5f020123841105b8224688201eb4600113841105b8224688201eb46041138413c60600b900f3501002a2a60bd8fcb6bc8871a9223691be198c49c1f0819204b1442283044295420a2126a04510b0d926885b69fe8885ee88122405804ec09439030120b63bf0834262661028a206113b47794194820493cd60c0759684a62c9808b8c8ca67a4f9fa7995c1ee5585c68d0037b99a6e62867ba6a6a6202316a6202196a6202016a625ce93531ae1e8289482d32863511ae2080304a4c845daa30ae22820108a3c1d414762545448bae9f11a94b7dbfab89f403e11283f80759080040974049dfbf44a55eb45f3239b77ddd91f8c2ace920a0b3a10dd6eb52f7b6fbdddbbf86e29811ab6076d984b0558f7c085beefcad7cf2539226146898e786ec381c624ee0e086b920a51712c370bb7e13faa1ca21ddd8a943eaf260de9cabc8dda9c9b4483ecad460044c36bb15d37b8ddc432bf6fd8b30c1477b577c9299e483dca640a902b4301bc2a6e6be38424512f58ab4f4287356d065687b6f76ef63716baf7292edfb07b26e58d355f165404cf2200b6a50dd91348c2ddfd3c2c03be732e2bacc178fd2fb2f241911676e7a6e94ea2dffa2c7567ab6450469927aa6504a6de9940c316468a133a54aa952aa2689ae9c166aa8484c8191b0eab4a4e111f0890c21c1f10bab9588d410e2bc08fb2b84af221d57042e0a759fa0e2421c139662c1e444ea97b0aa089623241c815b82c90af84f686884d648c4f28a0c3c11022932c1af729aacab98a009a8a6ed40fa9ab603d14f725b573281133481133401112401015ba35aaf693b10312275a95977351320212640424c8084b804c8dfcf909f465c63ec96839fda8c29d65902b4f2e69379dd637daa6281e228f2aadf1c9bd0880a776ccee7acf65e03e55c718c1687ca765d5edaf9e3d0c753b42987923a67fafb3cdaa50d910f49f1b1cf97dac6d24011434f27993636414d9c66a62c4d1daff15dde2fe309b83dbd42954de40531fc31cd20218083ad0a25e85380a92fda1232ed71a5ff5c4f5282319da38aa6d3bf889e022a71152f729f41a7999427318810235fe60b64861bdab775d7ddf1988caaaae3d245e82c27fe3571054636b74043e5d12c6768e21c9982586c5753969fd01d9d3d8b66219c4672c951ad03b74a602d4e915d7b2af5208d02e5287383b84bbe968b27aded65a5f4ce9e9676b482e20ca7aba0cae576ade8c40bfabd8c82685727ddcee85245243e50916011a1fdf3654de416c013d8c8f78d04b76b82d365e93ba2bcb35fd74d257f19bb349d67819eed5e940ef9afcf0310accf973a68eeec3e107d5f7a4badc031e11b8c7e2e900a6bedd9967387eb7676fc32b9db5a8b9e3d3822db57269ae50b4da7754b4276cac550edba7fdcb1c9d4bb2b4757763cd5367662cf1039eb7c817095d734213f84936eec8e3702eb9e6a9a71f24b3f58a3b805dfc6bdbacb2658d17633f7dbcc28826125194c484481b3a8f57b903907d0315074c6a25a8bc6028c6b10026d3e582e115798eb7a0ae24da132d090e5703fd13089d6e269d880e857d6f96cd6855e9f53cb6b5e6341ccb2f8b74ca42592d077301b953bf25e42b80d1f534933eabaa4b8220a6db7110a7c95ff4a7ba6881e86c736b3041f777500c3f4bb706931613893ef857d09d50fd85485f0d0acda9ed7b8590e06686d462da7770072db08a753feed43233a7bbd719b8f669c6bee39c0f3358013d08fb282bf58a1fc0cba103189ff89a09fbc22fa904e003d9bd034bc045197f27d5f1ddba3e8344bb6dc4699bbd763332ca6e19b1f8178a01920b47b00819c62bacd946528f72caf6b4f641b6591db83a74b1115d6761cfeb196698da7b81d205920f60748154ffe2b2a82d0cba922b41798d1dbec9cb83f2f45875f20c9c91d14d21130a01fe582aee73b40c8e743d97394292f191954f9da4a32101d8c5861e6f14c8fd06450fdd6a44bd0d1762002343e035f47cde854e2f3931070a70b250cf874d1cc76e75a5e6b61ee9abb9c51cb7f80f32e726cb9631f1ec1c1aa8749bcfc75a5964a415e2af0fbaa531cd1dd186be5fe4678527b5cf207229b114eed83609efc6d19609ea1e1e2fe8490131a1d9508c1622bed428df004cecee5fd7b00420504efebd9cd3c16b310b5b272f09433019d0addac2430b988938374a36e3c3108eb9cd52701b45d0c7b6faff56845b8214a43f9e74c9d0058c63c09298d8b8cf15aa0c4cf78170c0ed20ebc82dc3bbb35ce5d6bfd5c2ab5543262ddd03cf532626ffa38e097e9da14c5691ba764a07f67d553d2c000641a90a0ff659552429498c4bc671d8ab218e22210c4c45aa9bb48ecb96473fac4edf5f60c717ee02c84a6aff7860b6fa8ee5105aba687bed05055241dd31eff1d171894700e17c196301177fbb30dde1f3b02f497d3cf4eeef7444508a45f06d3a231c7f234c8dd8cbdce5cd43383f7a127b57c7f9658d9273f927af47fde6dcfc06c32aad04f5162efd14ed0ea10694632dacdb755caf7dafac9d220021a4199e2fe6d10a51f14a7064fcf98ba7a87db6034c41a87a20236fceec4d0d03c9851f8113a7054b6455f7ceaf044ebe593d0571fdacffe0d0ad99bff226d2a0313b793973af41b7a2ca183f4dee6c0a8c9a0c51a19fde4ee6477b5b1703362665f7474569bd57a435e68856744e98c20989187a8f8b24b5e2b57ce0c65e8d785433fe66059014138c148fa099dbf6df5e5b19b288468f2bc762c28e9687361735277415b4dfd0093b2bc8d5e54baa86e4932ccd7def3a686b747d680c641437d9b62dfe4a70daf6cf6f10a1d810783bdd0f17630d5173e09edb56c0fa9ede7d3be81aec415fd2c1e239c9cfd789eac9ff21d7f314306a7b2d064cd3431b9ad9db1f613bf0f344aef7a9c570725fc40ed9131eb5f92d1e36579b980347703d636c190a08677d57759bbc9930cca4edad106c09ce27c28602398fa4101c4fa2996b856a501e23d183cc2ff38cab8100dcd50c21184e82942c0e37a35b45707812a1a01a918c3d532106f9721eb724acd4e30a6ac18e868ab76559ed6c7024dfcab012e69625221d4115bf839747d4c638e127473c5af0fb464b8e3ad986d10bab651e451bc1b8778aa38697e271320db67231aa22ce453c581107483375eac194c25f0b8c9f980e9237bc0b1fcf4c7292853c1130630337e3f551632e038deadc65056cc9511b0f2df8b1edda3bc4460a7bfc2fe7764042702cbda75245d6d756eb9df1ffec64c32c75d2feea9737b8edd50dc50a367ff917aded7abac33d99c2613861d6f838da7b0d5251c8be620dcc9216b5f719f1d80a4471f1ce87bbf9735b32c97eaeb961438ac7d02e188e8fc7c5afa1a5fcab3c1bd31eeecc56ecd07d9862ecc26a7b338613d2f49774060ae9a043dd528af03963fb6618fad0c339603fd7614a1da3753d2b0b098623a4f6e2eccf53db0b8c2efbd44e5ec787edd9ad4e6e81a720d2f588d7c17e387714080b39a814032961b2624cc6cbdac9853ee2894f2e7ce330d102c1f9ef00bb33bba83ba3f3ea3ad33413fa7b8997eb53b6c31140eec2a5d73ce37fd90153ab5421cf03baa820d4ede4d29f132fe19dd7d7bf817c607d4be59f420b52fac162e08618ff29e88a35ac22912ccea897bcd4fca153ffd99ed7d410924a0d206900d4a701b957c3ca8d302415d2e7508b2d7495c7b21051e4de987edf51a829c718370ea9e367c5eca5e383a2e1b8eb6fa103f8271e1d5b569fccad237e80dd22b310cac967e0ea547ec0fee09f40669ad846effee04f63da94c812c0c90030c4894b309b29d2e7ea79b4d0615fe5dc4eb9bfda9c735082f11fb48c745878dbb3faddbc06f0ca9abd0f6382b078f378c9e52cde6c145ec289385eebea50b98f4beaf6969339e7ba31523dee1a5f94dc8a4fb3bb3ce27842fe1a8d59f9b980a498388742b2b7dff0aafdf38a8d95f4e1a81ff10de2f06949b063e5926991ce994a711669dddbadf428487defdcfeb0ab88deb149e5969fad39fab4dfe8daa8bee3d5650b067e05549b18d533f7ae5448c27d890f1a24953bb800e6d01845b37d0ab6ffd012a423f064ccd34519fb94cb14bab1be4239b7b9e7ec9cd4b587219e1e701384e4e26147de92820316d763cd021b0f59c6a4cb70b54c42e3242db0ad5c42733bebaadab575347d9529c391afcc3f7c7d2306e617dde21adbd8d8d9fb5d8ac9838d020b1113cbc5049382c937aea807fb9c37a62318bebf27ac4adac5c8a3ff054a09def17c9029ef25efbdc22533fc56872618777fb712192f78f56dc180898641f9b15837b31393fc6f0b5dde8261f9e580d8663abc2b769348a7c408738b8f64ab19e3ef5e8e017eef6942681995c52e25131d3a4fa38127974581c8b7f3e08f892993afe8e740ca877fb808fc02b533958f67d51b33fd193e676f413b9c35f0dcacbeaa17a9ab10d98b6ee115995a6fa182df4d969077541cf36c453e667c41400d254ca814dfbda0e7447f0899d3560837a325da6055934e2c27daf7ce11c78e5e1c0b1892c3bf44a78f878f742b55ec435add508bfac164ff3518bad9bbb23a7021026544b2d5f5fa57edeff6bed067a7e96de55a33f0de33c38782219fe3ed56f3ae9a50586bc13936028e979b0571cca220295f7b3f0e236ff31efe6afe9209ec10244c3aaeadb943f3ca68e726934e723ab2e5bb1a79c37adfed7d66f0264f6133a04f0507b44a29f191161cfbf52a9da4235f837fb013d761d66c318720615905ec100e50f97d89c2c42c02e46fba3f3b70b5da2f508d743e0b51e76da657ce212f9e5975b31db1fd1e3b28059de36826dd21f33dfb0436e9d8ed25a11b8e5006cc6d1341004d149258ab573d97d889ec2904bdbc589cbe4b76a644dca857437a9a0f4202d2287ac5dba546a071a2f66debc557e265727a87bcfb7bcb75ab6d7bee9176e0e3a1044685b7c42d0d925623dd48e72396503991692bcd12622e30f1698e1bfc4b3c65c981e36c5ee5d896fd02b8b9ad958d52d7c4cd60130ae28fd1190fff33bf6a703a009fcc8229447c63e678fd01d2e23e1e6e598c0fd39b411c26d2466aaa75cce56a1643da4f28b3e34af3bfaa2d127712f7a1fe11a9ea6679c863be07a36e480890269196936a09b429e8c82f60964828d4018cb73a4cac3befe79b1034d97a4b5132173299e515592dbfb4751439d483ede58defe98f8cc2492fad3d8f165ced2f6531aa0cbecb408b375f61f97511e63a196f90f43d97a92c8a40345ac1ce219151d6721e6f02737c303acd6ea783b7f4127e9ff7f72a44137e6eb40c768877c11f35d047e90beacc2f4e2eabbed8839e6b8f6b063a6afe288f858d6984e60480741968a429a4656909da2c5a34b2db1c05e52e472b5b08025fde1badec8a3ec640ef3a46a87981dea70545679a2e38f3e2d0f8b171d041228f21ffac70307bb12ca0bb27fa4e02938b1903beea10b4f397314141f4bda1481d69c275c2400e0906f1e9cb365e3804da47b278a68901e4b8ad459cc8327de437b1be80c889e6614077a63393bb8e31759dc1ebc3959908e3b31db2fc07faa0f3191935282c404019341e7c61556f9e4b74a1719d7b6a7f1c3835b94e35030215b58c5a9a25e77d7815613f6dc920387213a53e119e1cf2ee415c62b41bc6b9bd9ae89f6af73fd6b5a1d20aefe251d010b69714c0e4fa91c1ec85025b8934eb73f41cda33c5e9c8b730abd08ea216ebfb624ed000df454874afa8e1d36fa13e45b57a46e09271ce8afea1a273adef39a2a33566a46f6903d9311f448c0d0ded8600f950d76db180c0881853f0d8a0ccc61017cb33e2cfdf01f6c7eabb9847ae8d0d728a443a7208a75357392820ba5cefe3c6f734d1523e55a29181d772f6b434ed6f4741964b609358c3f1fc4f98bed55bf3e901506393d6f65e927dc4f63407d85d5da1b82f55e653ba0eebe6db6330010767afb42492011957a41334881cf30c5c7ddee19f0c18842ce8981e65f9401607d950bd8d8f563975af3a7a8cde50d37f712c4d904188453e7932b0093ce5275b50568d5c27da592fe8e2d09a0f1558f392a9fc88ed7d6c9de86985e9b95884626a441ea6b78e6dc916c6419eccb816f52e56b7f6bce289e4f08c7bf8deee56fc6fbba39a0423dd085464546f4ed5bf280db0df17afdc8386d575df989f3d2542c074c6bf1edec7cee19b3be015757c9cd2e6860de58e4fe11a031f5817e1332d5fded109311b2569b90975c5844168ce4b098e3e113fad6f6b7804341e2567db3e3dcd12492d10826e3ecb9c4c8d50fa651fda5c137a0a8fca32a7c29830a8a0c4f299ee2376610813b1750e7d7ea96e8181c666652fac978f37e7e24986e483daaf88ccc437fdbbddee0ec2b498743ebff45a3e5b1009b7b2a1cc253d8ec394dcf6a9599f4b8be627ccdc5ad5f33c101b0461535a994376f69d056d45eedd3c1487d708c8a1617c89bf32e31afd8b973b5072a70acc714f26fea54145b778d8ad932d3178aa04bb0789509e436c383ccce117b0bbac67317cb3eaaee863ddc5e76d317874deef19fcd69b5ef3f962035596120d8c040b6a31dca9f1c513590941cacba3f01347299c00a2c8e66634990aa75876e7a793b5e11886ec71a7d68ced40ef9f73b7152e200b085f77c48d8d3176e8df7073912cc8f83919609b2596c4f4482a82232c8ffdad5dc2e6e6503899a5930e2704410147052def3476c4044000d5c2d15c0903816b4f5a1ccc91f309879af2efe264f761e4a9f0f0ede8457961fe6a966aa519ea411e28c1253718572ac3773896aecd7c18fc35f6c03068e0a0c4c5e53d7922b5c9fa03e4c17a5ca06f3b2a5d235b8a0f2479a69fdcf9403f3bed7b999649e651cd5b2724882dcf2814108b59148006589b0cd13016ef4fc294e5113297c614d165536de3f0fb1c190c062420813b8fef543f9d8e1ccac2efb666049297804b5dec2b9cb1359da5b3b9e4136c15968e55a895152378bbca8f360d774f36245d683eaa95b1466da54b230ebcfdd069bff0c9a838c80d3bf1a1f15ceaf1e868e78eca083289570be2e411fb40bf42f5691590caa8b3be61f2b14200a438c271a42287fd27ee3b42481345387d68681d9945dc5e954cbc94b0ae7c7ee5c2cf35bed912020794afc9c54001fe4c8017f7d392e4ce0924433f915038914e177f53db3e6b59dd28c735deb73c9be92d5ce1ae428db4006ece87a0eb50d7f356a62f295f27f02a02a06d57e03fc211f5f6332633f525ef85a2e11fd2040c7220106093874f593a9bf266e5fd3c9e2dbacf3a5bd0d7399115132c78f7d8f407679fe8ea64fd39e6669e0d9f70211f7bbcd6a02f4f4d43d1d04cce71379156bf514a40dcd694d1b49aeb787b882ec199dc5f4394a2c41fc27730dbbaa4a59f6d3136314433e3c4d04690268eb48a7e007e5006e9d9f0ae6245c5eaefa1fed12d7efaad074cadcee5a5773e9dc4cb985b16e582a2c4d9735e188b396d0514add2268a0a5a0d39346af65c6206ff63ad39afdb099b478de21ae832aef1ffd6c97052f684109372deb10b1ba51d15698c2b85f200a98409d2a35bee2e29491f98591dcbb49859489c4e86f4d79c554a08df686a7c8e3df64cabfc8fc6d0bac8744b680ca97a8ea68613d7bffc2a9d838dc87794cc42e97c83f575c5fd0681c573977135f89102b1b82a43b076b65025a35a03833a07a51ecdffd262590b836323eabd7c601764f762e591b2bc88cd9df8b3336365c55e6715a24abed67ce38cea3a12dfb00eac5e8774ef75d6b7a25c1b3557384145d1760e03d1881ee77a47a73abaa3c4fe2e1f0ff96416ffb62f490f55a063ebed12b9b614d0597892ecda72a6b24575ba049eaf85528be617375be59c1d6ffb44d7525af26f8ef4085792e2fdee179e4d7653d2986d74b37554986c9cbbadea1ad4820eec68fe4b4db6e925f310294a4c1741bf7efd573311e84e43d12de7f22377d27efa85e163ee047c8aa9aab555d5c9faa84057207724f04ac3ed4d769dfcc532cabc76d8de8e73f1f7eb0734eb8b6b509de10d9e93552886f82ad62a8b0459d3fcb926ec1ebfddb2026b9f8e9f66c1570f0475bd6f1a925aa9ee78f49b60cf80f07d6c669e416daced53d2f180d95fdbc966a7508097139b1f1285fcf5d69caca4320b800be1af6110e8c0fc5491df0b9524a815adae5c18a1790f82fa19dc2840ae6e7281c702d53ce0d44240efdc8121953f83baedbf0379249cc03665d71d53dda9068aec869935d22b603e69799999931f83b543998e7d633b68b108aa310a69e1acfb7412a14863e7872f209ed559848d7eaaaf9bb5e3e3e6adcb22801607e18938864eb21b1bfe8b3623a83dccb6590dc22f5294ce18a4a3fe53a0dbf2f18cdf940789fcd2f1a2ad98e2fb0330d08df70776399cfdbe5a72217a19215576ac71d8473241546410834eec85f846fa49a9f69f01c6b147608cd4abfcffd8351154f9cd462048f02e4affff003ae998a52ba808fb332dca6e85e71b0038cbe2b6b2cb87d656fd73a3eb9e6b0b02987af53b7879578b060ba53147eeb0ae485a6442049f418e36761eedf37daa72d1f92d526abf509af9f9dda1336196c931abc5c7a33abeca8f78efe2d94ee4f23e11f70fbef83c57a5b9f94ed021cd85499ea5ee34dfd09b44ae4a6035fbcbb4ac9da17b68ec92aec51b17083e8fcfebf0cca5b5a70fe69c0ca5ff8bfee67921034c8f5bdc66cd3c21fc49347cf4f7b9e34fc148def62795c96a4738d52ae1df93e9fa7fbc9695061f4be8719a8242b78bf8f3c5a9a745428645ea9785f60a0dbcc258039a4eaa8fe73596fef53c11cc7733714f06fe427c82ca0107bde2afa6fbc58b6f96304add20e69057a5c4ebd2e6058acda988f2ecfd6054c578eb7a1ebd3cc059bb11ea3c8cc425e1395a8f5804cb4f7c4d67c201e8b24fbeb13e5737190c8fa1f8e0da60c68bb9b28fa1ef556b0efd5bd23fab0a651096aa638847ae2ecd03b9d3d46ca27e7374843a676f2130b371df2d7326292d2adee09a35617c6467f5fc4bade80fcc606b6a52833906a9fb27f2b9f0cf35b6090be3f87286203cdcc8d5afe0d68c4f143c94823c562d95c6458b864fbcd65e5b0febf5b37e9f01e524e9a6d70ef1009abaf1eda8e12bc29e61382bc556450bacbbdbf2c7d438c3fb3953ffb8548824d6bf09ec591236abb427c1d1a4023d38b268fdd19ac81b9f74428ecf7e91e96127e3938151c8a6e3178de29cd6253341d498d6dac205a14a41d998c8afc0da7991f97af91fa91b0e1824831853edf8a1ef7bb90c05c9ca040f5e85c3c5a85f5426c5d0b7947317a835873c05a8e40cb7f3c06eae0b3a7275ab25605d93a25339fe174e182846425488dc2b22b9bacaf65ccd2063e5f3c5415ce04280eaa8b0f1a7ea519c75c0932e5aaa6199bdadd61a8da55b193638f8941b7dc45c6768b938f5e49d45eb86d1168bc590126be434a910339a1a5d51eb06cd86dda0cb9619cc7ae6051f424b0fb08c8b7f1bd4875672a2edd5e971f992adce7170ed1faa4d55bf35a70d18cca6270b4031e352c1f5241bf89f1f11740fef68dc45f2d24aaa4d33834c2b286ab2daf2e1f30ac48e43fc6089112e367b7d640168397f9dfbf7a541a6d42de96ef2c72bfab874a703fecf227260efdec70871e1eafda07e872a76e3e84a7459d0efa9c995a3e1d48c5eb1dfb8e9d7efea06ca180dd2a9ee39251d5d8d4fd00afe2c6bdca0ba675f0dab5c25553da6bac0588bf97d08198497f23b97ef980b83e0a73cd89320cc93d365ffa23ddfae581662ae56d57842f865184e11f073c56daf41eb30839fe01ebc3e4df0e578840b8545fe1797f426f3c574679d4068c22a6ecd433619cbbe3144a3e856f293c3e18911aa1eff8f52822db5004fc3380cb79dbca7eb6f18a2f8e8546b824dee0f044d00b131e559e8b9b285459a9e989d0446879fba12afbc32be2a21fb670d38de8a908e60a56fc38a686043a11e84783e46208766d507eb9f5b1c4b7ce018e6356d76bea0c6d5f1bfaaddf65b4bb1284338fd643dd10ea5137c3e85107b86379867c26dda8638196b227c6dfb2dc50f4cb4115981ec71e1f7cc70a7a1c2ed7388f37352744859a8eefd298906ebd2e3504bc9ecdbe39461edcd97b5e59910560bfe2aa6c56c30bc58753fa1a8d15998c5cdaf08d64abbf84d8e89cd5b725e93f7d19c76d913ef045ef80c44c8228da4d6786524e263251ecef40ae0d86b8a5d3b4926ff9ec7e585a68cb547113a7eb119eb29a86a0e0261aafeb1ebd943551b5fb46a65496f30870e93e829bb8e9bca0f3a3979fe06c4c9380f99c5507e414ab8b5fa9a91bb9ebf6b5495fedca31b8b8d98731c4d24c93ae02e5f528a766272089fdda6efbe035dd97c52f8c95dee1954bb330a1ff64dc0cc53a4070daf15f031d85daf749a17899a9e12dd1650f6aaf8aea927c0748201906c938d00570908ca50e32803d0a8079665b95ae9b069196a3cc1ab2333d3fda4d16b900004ce6fc9b702bb75f7d93543de23c04d92c6b4c6c0a0040080086804a40400030a855b05cb176fd97e812f38db244e6ff4c6fa795a2a458f0cbcf5d12fa692f4e3ce171019517f52edac1d8d15cd2231a4b3986d12d7bd39be778d17064a606483b5671ce5665ad316bb60d49794471bea91ca2f3cbf796a01aed8ad68b930a2479bbbeb48209443a8b51b2cfa3c67682d6574bb88c7ba92a0b6a44fe12a13f63fe1058513d5f95a079d89f40d07775ddd6013ab37dc8ba9736acdf46ccc95c8508b3290dd8ffbf0b7a335f0e4db9a22fc3b0c7c0c22e4a539ef18e676da52a7b55a44622664ce86622df2ae31330186bb5d53fb08bcd36f9c21e30b351c62bc2d2f297a54d3280beba82c26c176ff6a9b13d1e3e6fe6091ea05516c211f5b57addba9720efc7bafc8aa7de03e445a35536cfde9692dc9146635036b66948e4c51b7ad23608c8783dbf3a1c5384aae0034328fea22b7624ef3456f1cbbe3a1a0acecc507b8cae01ab1c7b0d9542dbbca11b306c7665d53c19c8a345805f14d06a218ce47f2c9388f10364212260d32365b8c991f8b808fc73bc63b320db340bdd4560560e22a322deb24c08ac29d95ed33084e753a5a377bb540344bee8aa3f529c5ef1b80c7d9507cbe140997e579e56397df68b5d47d3b4b69badfe96e471b81845a62ed498e3e702f3ee0e30e8a7fb626890484e19c94cd96baa7bab777db19b0e4cf0bb16b9bc2a181d118e47113ceec5f5cecd7f815b04a17bebdd2de2a5b25090453daea383c1b249c6d23a6ad595834ef340c544d1435ed19c07c7d7881e3ac1edaeaf0f0cf7505bfee3abcf842c6dc3ab82252fb9134ea01e6798a43c7b6afd72c01b1b2c616d0ad5b5ae4947ec9fee1cb5e93173098c50270ee474100e98daba4430253b218d16f2930a362affff138f029df06921024d1f11f1f55d15bce51f6d4b60295ea2e71e636598a0df800a56309b9827a5bd1e6877ecef78872b06808a9209ff51fee7d4d0625cdb7ffd1d9dc3183a64dd02bba55fe67950874d1ad0588ec1dcee45b7120fe2341c9835e48d0efa2e2163d6de43acfec63eeee0959cc0ab8b9493881584060cc5679179ae01ea93534ba87e26b34dafa9e82bc80f4e0b9f0f3461979c56e8acc877e4ebac4a44cee5d483d0fe0a13412c27de8f10c2d44de6d6b476668f62897a04620abc325d2e4a3d6717403c9e98f77c1d32fc388dc08613cefee761a7d96447487b83394feb68d28e31102f0badc19760a34e095a43bb42bbdf6f68b183e7ab9a22c8169e059f625095aa038df5eba0fbc993c16e552d40f5fe27e6bc8f5535a8dd71a4e771b2b8bfdeb7238857f755963ee893147dffea0b12d6a215f57d07a6201c06e553552f78ab5697d3ea9360ba84869aec1b6c444b3ef3c13fc2a370763d7746fffa594bed3970185b814f94f8dd10e160849418e8c15798bc85ac9519e8e8b0dcf1ab9a9f4362499f510c24d7d6032a2d9e3e8995cacb0b953a7ff667a270bba9de7aefe20e3079511fd9574d7694a55b0a88e51f78bdfecbfcf3da078b92583842deee043bd891a0ec7d5a31d1754b5e21e0fa3c38fad61fe8c5e8dbf133cc10667ce04505f25514322f8e4aa04ea7ad509e7a81c9fdda76c92fe97439a432d35cbdb324ea86005d39d7a9ec6deb3a49f03c902a933ef2dcad2eed2356550c97e85bfd87cb14176f6e268164545a076a6601d2f5fd3e46e645937a0ca88bf8df9e5fb44c40d3cb0ddc768294ed2dde7ff57b688c2f2b06b494b33f8d53170ed6f6d5d96c6fc269e6a9ff4fcab119eeafda03e40d1d5242a99d6f4aedf494132fa74813536c80d0b5d7d24d7d09b35dce9f7e3806dac96bccbeccedf02141ff7532a9706b89228ab483cac80c98fbdef0663339875885e4049250711a87fa89890ed6a78a229ac3cfadd0e09434c54df9ae6dffb350ffa4847bd1cd429f23b8f6418bf4bb6cd2845ee4d10eef7edb33202071ac90afe86e1f93f44b4b0f533fcb7a791fff4a7a80c37b5484ecb3878c493d52ab131dc81f6bcdc575123511f3bfe610366bc56adf08543853c5a2d1c776bd28c9f1e1e09a2eaa3e45a54e90651290aeefabc11fb0dc019cf52a490dab54b1aee1ab63d3b6ad5df1af3ae957995851caf343e99e72209758bf71e0634ed6ca41e0e25d0925a46a890e296ad2817c614ab6f15b4fa7b2c4ff636a8c3b24f61b55996e364ce6934e2874fece1b5cab00cd8d2fd0436c79421ebf60e21c54a329550a0d76b1e2517fd5e4d77cdc97162b9d7fc38b26f035cf965985b8ff818c73490001ee218afb9970f609801fb60dba88fbcde491eab5c39632c7250b95419f56b62800ed79ed6fd38cfb2f50162ced6fd751adb488701066ac74222adc395736559192f9904213f41db701e506be94846adb5bfda63af8b2783d2ac35d9827498a96e411eac3fad739b481c615270f7058b23ad476626c4a78b018bfda98ef5658a9c0e7afc28efb4ecc927b119cde0237da29f071011a60dbdaa30bd35675525420a20e5dff7f443fbaf0eca8f71958ad1ebd4ca53978bf3dc97dd6725dd9f1daea22ec9937744c1e9bc7952ad50b84f3c87df8abd492bb1c1e1baa43314cb878bd78d854454e79108d979d6482fea937a9021e7490d8183fada2d9826c5ed4e64abcfb841184678c72a65040fd8c69a6f46a69eab2fa9a469d4a2c0c0ff9f12897768b8474c370c8b8014f0d5dba990038899754ead0026bd4099ba2f069cb4d49b47f7036b2e9ae56aeae134afec9ffb7f7e56c52f5c008d494556aa464f0d601d5bdb9f9869e1d60062f7e3ca6c79afbb26def9aa176a81ef582d872be466269896795454c3aed9b5b0b991b54cd9ce03be5893a8e4299bbb2b86619f30e0703bc3f8951d772191c8be12dfe263bf59089ebbaeae328c03298ce8165f8bfaa22a1fc7c28a229519a5c322411a7e53aac2309635e82b2ebfcaca07ffaa7ab991c25ffb258fc866e564e30931fdfbe90903196bedc712933518c1ffc4384d27513cd8850252210d551dc0fc43840e437de7ec8ed4f65612b2fe54ca8c085b0c73e217ddd6456bf0532aea1ae711bbecafa6b055880c10d03a1ff40f074f7bdf18ad364b8c97f9bb297e6719553287df6999d70d73c547e952722a4afe9e17cdb20dc1b16be2f22ef4344abefdd2a7c7c840e7ef496892c4f9a8519d28ec0ec94b20cf832f880d2aa8d85aba9e854e415aa14b31c996016bc88d0bd7da8268b592bec5f9fc03f9bd1f92976b5f0ceeafff051bb4a9e6e125f3fbd1d92284d0eba8012a52cf5195804084214a83b74568e7b9aba89ceeb7072797c00d47608add560a36ea6c3d5e480a5185ee78d842666517c5758c11716bc294d18e1457a44e84f04b4752379cab6a8f3cf4955aa223e229ce830a5e7c8c5c70f106237f45f61ff8c5afa0f2a1059f3128d9017dadd2245e1f0c01a1c1b91d494468712a8aab0c3e55448b7fa0ab15416eaaa6c96c982d939155e0a873595d68883c3e4f0a4e57b0f632cfd56e311a27be05d77dde60df96c8bc0331db18d2a1c3b0e9405cd641d8b03f40f38fb4921d35aa294baa547e35449e087f4e2e8838b719ecadc7a14cb0f692813fbafff929b7298123fcb5287614cfe8fe252be4ee4093c3f589d2c9fd164ae63badc3722698d498e2e95717909495c5dedd5e4c979c09ac2103eafc4a91a4b253fa32682ff43a31eb4929204611532ede8c3c3375a8ac4e7c05246eb44a17742a2808b8d1fdf3636c8bd2f71d62aeb0cab75555173b780c893a2a3fd65e271c6048c6b14325b0b4b6fea7a57ed0345c998bea3db17527200572e3b9c317cdb1e0f80358815884967e4269067de16db56ce61e8fef1514a34c26408ea783600bd90eaa097ebaacb5271b04bac94d240e5fd80a00297cebba84e5bd5837a46989d78e0bb7e78fc20c087c06b52849ee199d53b9c1824b3638521d57bcc3c87061a32be7b6dc1d8e7c7d33fb69779766dbef7899e5744eaf22c99bb535632fd464e65192505e02a82f9bbb85393052cf605eea22e950cf8e4be03e18f5e4299b5ad98cf964955dd2d85c9add8e6eb2dc214d8782a0cf32e84ad3cb217a839b46fde82015db3f879b68abfae40ebf91fdf9cafcbd5bb3b4f02e25737c065fdd78ccb1431036ea5087361766a79206e89d4850be1721cb4624f26afbc234e5a6df4ac9b7cfb55016e613f7668534edbbddb5bc604abd48a55a5809940fe538769b6ce0963c15432b54dbe2e82a022ec462d0800f9bac755e9f8a2efa2174662f70fa549230105d7f1bf595ae46b70ae186087b5ade8f05bd694e632e6aec99c2652962fb57ca72500e635af90221cd6e16bdb91127b1c5300b5e8ba6a7a6cf7469d7c417aa1cff75f425d552f3ec3e66c0d70d6668ca6a0484c7f10f0721a3f2bccf4ab769c6b397917b4aa4d30fe4be42617ad6e2df0eecf10f08a0b08e698660ca3ca36966ac2e1e2989b9359e1647d77c0883ea3ddec9fb12c4f52d181c35bf5ca24681be64cfd945541e49d151ed3a906c7e2f9a5c30d805dffa6a057b2c8f947bda82935f78078967c4601a4d4a84670c8332e8d702b443316e80d43c8421d455ca41afd7d4ffc655edc5abfe1121323173e5a541a01c8d4442b3c4c04b68918fbfa032bd9bf499e6ca3bf70e1613c727eccee76ca8eb6a6a54277acb72c5c1f356c47b5e03f3ebe84bd4decda0f45f740439e6c9475ebb829414c421446096a7b7f5fd189576e38b92532456c9a5040841dc4a66e1679e92491061304cc1f463e1d11c73a2a6b10c696f7a4dfad83467df981dfab3abbda47d68543760d447e9183d2ea2b2354ae2be5bb4644a27df743f068f857e4b7f7c1dc90822d22ee1a7d5c04a460f9892ab12ad0afa3ff3f83d3d5c2f817229511fc2b4c60910240754de70a33475fb7b342ff21496ce540c240ec78110406ba4eb020000c0c12e5f6a7c703d6c2ba9aff8f54d67c0bea42f3d36d11cd5f074f34fe31a38ff80904d82bcde9936634e71ea32e167f268c747bb1deb534beabeca102c1dca4f86725621cbfe085046f81284c74bed6e47ae1a3f52bc8c21a4fb93e10948f6babbb48ba4849b5484367245215cc0967824ebb3c44550dd52309c4b8cbb8360a40b2b58d86de52bd2f31353a3cf7dc9d8903a1e6bf899149b7309b6136a6d1272e019badb87ed0897073a2e7bfbc5ec7bc039dd2e5b33d7b1b1481e2ba0b9dd317b1c6d46afbea6440a12562e2233b8c23e21806059f790e5d84e4f1a7971a12de56235b9b484c226a818a43255b41bc3fffa6fc582fe145ac57a518caefe9ce3180dc196b7e97e545bc11a16b782e21132e833c891708d24314a9bc157af9fc5143dfb4a74d4461ae6ea6cfdd2e85ae70b504f42bacd334be87b8e71fba1011a2dc7a74107b78976a7b3fe24da1dee093f45449e8327eae38d0f2d4460be47453dd72eace13b0b1bf45798879c369f900ca9e583b9c245a32fcbcdf61e380595f0afc2690ba266d8f77be6811627aaa1442d3d2b4444ceebadf1767485d0edb693c827becef67f5aae06f62242393ee6fcd7d9933bb873c2addcfd7338b3c6a5b1574e27e35f831ece955f7f6fc3ed74ee7fd256f151fa27fff215ff28d9722fb298a2713bcf9979b1caa0d6119b48c5ff06b82ff25dd18662dff097a05e996117fc2071bcdd5460621d22edb6b1301e9e7b1d6f3eb5c2d007437e21a304f509ca8b937ccf53da390679d82d4942f6f7847742d53adbf87c8c775ff76bcc125b747f0c9745500855318b0e0b83a4060cd049fae697b0a149971952cc18e9d1b2ab86e6254351308057eb5bcef05c3246eacebd6df6041c5801d273371ff562693be76f1f92f69741daffdfc16dd3a8600f65435bcc38e82aba1bbc5bf17987b3e2a9a1812dd0e020abca1974a28a328c6cb941f0f4ad6c432caa261cb297b7b67a243372f410a1eda19a3aa7be3714eafaf3921c14c33a068b3533d689c88fb683e320a28e3405d9eafb6cff59a6181aa4adf84ca9c1bf8716ddf56a648944e235761aa3384a3feefb39d0ee5db935f43e64470c331f1da0d9f3e96d7894eef9ff80d7455c9307f7dcc84fe39d10455df97297ebf07cc1b709091a29373d39b7555fceb7480b6a6cf7df1f61768fd198bea64310498b7c017b4bcc73c47a80bf15f3f99188d61349e1fd3a34f9b5464fa7c3fb226c83bc31d5d323dbafb3e81298934e08f5859264c92f42dd47ec88decd1982def4bd9bcd5a1f5adcb30c7156ff9b3b46a333ed101b1458ebb739f3990a84f6535f2ebd618e474afa477106a7c83790ac6dfd9b18c75a0e3ed138689cb69f0d642529753ab42df7df0111ac11e5b037cc3ebc33387804e671e67a15891d699f692a1b4a74ca145503e24517014d7d0dbe1aa272f3e42fb335d6eaf2bd27c223b8cc761e81bf5e8bfea727602ea14ccf75f924431202531a1fd7af1465a455703f958fee41ab8e5d776a33f9a8e93aef6e395b4503634897c166c5479bf09a932235d09e393b1f96a62f0e38cbd85e7376d1de435b95a9074c0b998cd0cbd9d224dde52e4d3aa8daf679b8b215b7f03deb1a1e2056cde0337065ce1bcbbecdbe608b9350e73d11f26dce7ee714846ef67ac1ca83d79636c7544dc7b3153471c377ef6aed90bf0e01ef72f7e56d62210f4eaf2ec0536406005e94131f4c66ce018d017b742b6f96bb85fbfba86638b33982d33a29eebc56de12ac4e36e3f534beacfa58e802fc83a109fe0044de271f53fab00ab964ae90469772f353fdea01cd3de84df7191d1de11582e79a67a46c22c066b1dab96ee9e2e4257864766a5429def22cc66b0b07703404b4ebd446414648d415cff7af000785bab88bc5c71a85c3f9b2bedcaba8d83d4c65d8d3373e941c07c7a0fa427dde60f6a37ee3e4fc1881370a05135f560c2bf5861dcb1d73a72d5a0b9ffdfb855e2209c865281dafadd0c0f201ad2d50f6f31aeb5a52e41cd5ffd0b8bdff04b7e5eca6b7d8d3b152b036d885cffcca6e9e2a7a149aac29d8b12c0479516152796bb1169978afff36feb2256de1e8f43084642a7abbd122b996a39e30f3feaf5c464696a4a6f5c7a82f8fc6ad3aa2395005cd637ceed5712e62e07e523434ed7263f84f53ec7270ea2f7fe2539a1bb5f8262fff611c554b91e21e8e2c1fff90325a0078c468101bb407ba82d4ccfcb8758795bf33eaa9525fbaa97971865d14c1306c902f5bcc05a22e1dfc4e334824fc5e0d21292963ac65d6bffa1a0d15c52e6759cc26d4a13a2ad3a7962f2572d3a0898d3237d73fd8ca5c574d5ea5ec94dec13f9e28d4991aa3f1d5ca014e1938dd562deb4a25da222301ac9d5c460a8ba7a1d1b54b777aed2e458bb658d29eca31cf306d7f906d91bc78e7555ce3a44b873eeb2ab23ccf05e2fbf1d3dab0dc154606fe469179879467b1a0f3a05c4a2eb76a813cf9fd293020fb9b831e48fd295d4ebba05a98da45342f0c229400884d1d0f1faf6e461d16996e1cf470b7dbf5e97541bde6b9c2ee166117992df90cb5fe0bf2c5e57e2b36fd8afff530cb642b2ac9370ab5f721e924318acb1b37a37e033e4da58d5ffb36e0bd49300ddfe1cf37006d9cc5a218a57b78c5ca1447f1b2af16daa1fa45bfa3050bc09b86fbb96be536f9bf5fa97a38cfa9e93939919d4ed4662e67aadffed274a723223a5b50a5e8c982ba7a6a3e70af5e2ae3d3fe243cacc3d8494d82ff41d5b4572e26d16e60f6df069784670b273beff27b1b35c0e4be9f3ea0f38b4b402aa389f1864b1e4aa040e073c99b26f61f8b4d8a70b247c428d3e53a7f87af4411b69b2a3a3725200751959633753d1206d511627684cbb2ff273a82acaa3620f1410a923cd163cf2fd9512972ae52be08dee5914e1b07bf380dd00aaf53006e9551e738a24d0c121b2a58fab4d11707b5670dbcc91490d5ed18e0dd14d58ffb7cd93612304ffd17e5bd4c44895f81f2f1b9833d1736aea2b768bfef8e9aaa87799951e0eb9c1b8f2394915c98c946eaaeda3f19e767080349e4866a168a76f49b495629c344690cd47f85f8ada56a38ca68000540a82fa9d009cb531841965ff55e79409c005f3f9be9fb080eea51e398faa72e722e6e441b0900588338bf7e3d37c6134f3dd0a57eb690f0ad730582783b893ad14fe7745e7fd5d6d293549d663ccfa71d715e5f5df56051c3bb392fcd00dd749ae56fd4008ef5e18774b38ed28c9922625ec2eeb1b5c6bf455d4defff513c2bf4ea6bcfd9ae0bc1e8a4404cb2e04ba072bc0f0c386f53d60f3ee82138d5f3801eb136edb20997ec9bac8b82b43d0361ac3302fa1f06575ed48a396faec0c11600731470f9d120359a20f853acad1c169db2c5c015cc32b18f613fec996dafca58af9a981e2db48383a58f05e158cefbb1f02c8353584dd98aad92d4ed352b229c83904214ae6a4051729d68643f0a646119651e35b025e9aa6c4593523fb9e83b994353273c432288ffedebca39d1a48afaa18dfe952cdf27a94e50eee1a45e683f4e7b844f4e468562a0624209656b11c5dc4fbbfc3fc9cfccf9c1f036af516cebc6715ffafff4abb4c97e0a4b57ff349439b5d4786d11a2c57273d0696b7d629d2f2b922b6afac89c03f90d75b37a8aa23eaf8b40870d66485931c7bf6729051c5c9797c4d6bb9a3b1d955f3fef24646ec2e4a1c515b54fa6e21d5618cc5a4edf8d80067a1cabf03fd48d861c088341d03bdfc854804c2f83b2a886b45cd2a4342d801dfa6ce53293885cb0f44abf97637851e8ed8a9a8ae674770abd143837c0adf5238975a058096fbf093c4ad530154eddc19b60cbd78513a184150b5c5d190f39d7253fb70cc166cf961cf6c16ebae1112cd876c8d1521eb81a74a6ef179396e8c17612221d5ed5431324df0b014bd2cf688992c5dfd9e91e56f37ada072675ef2e0cfca9db094ad0cadd0bee1ec28de48532358ca376e75123ec32c0c955e5a708b067cc914bd4a2361b2d403ae97a06c87c777d63d5e8ca27c9e36130c971984254812fdd16697b79a4d97346761df61caffe4d0b8253b379e6c6546bdc785d4aba40afe4de5aa5e7ce200ee4a4317a22c9e424aedc9d5323f45b3853c2f466b2124a6e7142cf9cded7e03652fc8924eed2e11e978dde9a5a4933f3ea6381aa01b09e10c1105b7a28f88a8d4f1268a3579551e7cef329d318308842248dd8b6fb0faffae04f1d74513970f06017908a637c3ed6fbeb3936578fe5db3fa54a12f3d7ca04396227385fe8468cbdcfbcee938cd37abbd6f7a540c9779f6cf0a01947dc4e8ad794be611144687476cc7a8ea4c4ee6bec5e883b8bf8308fb03baa838a0dd50ed88860d7bfbead686ad12b9cba9ebc11f99ce80f5e173c4bc8cebaf3d8c7b5bdd806765f0d40aa1bf60d3bad604864206d3e0e76e42ec9513d2a28c77b5d65306c1293253e78ffac74d26e256382bb8705340fbecd8b84d25fcb90d422583fb87684d9fb26c92c16bd3f357b16b212e27a30190dbfaf98912e54ecef0e1f7d5e8ab1b5fd6110e376ecc7bce281f2c224fe512fe9ad1cc4392bd9eee6ca0401f78b99815e5fe366c93db38e9a973d397c7a8c7d5482640a133f8eff90709dca764635d28f0ddc35af9d7e6c5de45638c22d7e0d25193b22f55c3ab1f04581f40eaa0a2ba6ef044c8d556d133203335cb6cc0be38a8edb6a6ca66e033cc083449359047f863e29e27dfb80030e172bba8f2ffc0afb4657f2371976a9a554119177d0e904dcbe3dc1c9d5922f00405229774ad65b118235a9482cbfc14748b7b3d2fa70238a2798655f98c6bb264a0c77b6874625f0a7422bf575907b7c4a813ee01213b09befdcc63a15eab329c3aaaec6ef8797583d801ec77dd16b7d91c71dbfe7236c3bb2fe1c4044dcdf4851d80796885a36933c80a4fef6fe26ed3b87bfd940111b17ab734aaf365c527b228b0789418d1f2a6f13b20e5b2eb4725ebf9db1a83f5b0398a3352acb5ac7db65b465779b6088ec9d84062d4ccccfef087e26f9bd661d6fbb871563b9af9c19e00a87a9bc776561398899e20e023ee9f934eeb60d1e15a13090756cb0bf38c8c1b2778bace257fc3c258bfe1a229657bdbf688d3a69fd0364daf01e9544985605556cba37ff9c00ccd45f964b57f609782d94f257f5a34e5089627ed00d5f8342758c9f7fb45924017df4d81bb3912e8c5d770c86c45033b7b5de6d07fa49f5ec82005782bfbb2da60ad01743c5c39c42fee27eac58a6cd85d30f78d6d06ed6590c7077832b8448e9dd3d17e4ca641c183457b78dfa023239907d9d410d222f10f848a660b912bec2aad69cc468965e53721d4fab1078409e7099bb445b1503a5a200fb7e2f1a2dd7a41223f22555f0ba2e65e4bd667b094a219acf28d2d5056ec3e41de9ed17b7bfa7a96a225ad3b958037aeca3d475bbc55344a4a9a46b90384c38395f144c93c63728e9890dbdf39c5b29b1e3d71e8b0678035fc3f119369e27bf41370dee6e0726f1d8d2f663c2733b6cff30be440071483a4c636525bde5ad4b9846a579d2241b0456e68e5fcb4aa54c5a40d36df4d0265661c9eaed14f46a48b8b184d965150b6d44d488aadd65ef5a90acd43cf9b554de2f3bf8b02f9b79b4743b606f543980ca11532f7a54c0d82f1ec5dcdc2ba5bdc3b07a0332b3284d8d92e61a889789d78b531e5d25339bd25ed0a7d2134c547f0c9d05d6514a9bfe80a5b6f1c59e2f29f5f82e57cba742b0d6775532d11aaf70eca803d7f453dd15b0ec4d8db4af4e2f28f8ccbf4ca820cfd4a40bf018578f43fb0c31c1d54e34fef052e2148bfc9d549fcc2ad725f8b10ca7d72d91c138473d20e720f9bf5124d61c081e2c716979c05402907e0ce27fe5e5e58eea236fc7c5da8155bcac17e4e7e032067d25a9e4a72334b99100efda705e253ed764803632a82c6d7ec1a06d1a68d7607782f88865f5901141872581fee4e15371a094aea22fde35eacacc8c3c68e5cefcd9f5db1d327e25d84862bce5ba3175120b8bce1f15a68a9f5927e05f4ce2d5793102874a0aa0adf28f8b4fbda0cf4742fd90d5a5b2bcdccb578ae4df12fda56373b1a0fa38c0a9bd4f187937413def7e7546ea5388e51e9047e85c82f223c8c850dc4e3f62d7ebbcfee3abb7ec87f40fdd984a5a0cffbf9f63747854b17b360fe94de5c62fd20ce1a2d7e006e30ed080fcf632bf70e5bdfb328672f07691c9f9d652089212d489a3e4a61bd2e7dfb8c534002a43926a513a2e3dbe4be6bb4f46afbd2c02ff8db6e147f11e4233ba98e1e6284eb913115b347356ee76fecc8d50667ab5dd2f94cfcfcfa1d2eec2408285e6e9ee26f83e4148d8a510d766e28f4f778ca892bec41fdd01d26003d860e16a8126e4dfc2f54a1067f308a4b5cd725c9abd2be306b583851f99eb1a4cb7616b403477fb5c065796c1d63f9a7851639814f254f9bb378e3e3d5b27e1c4e5f02eaae8ad09bb79977544d1f8c9c70dbdaa31f5149d99af0bcdce9b3a5f3e80b9075ae5dbcc375811cf384e399ffe5016dbfbd9a912e82462aabff6183f1bfdb617785d35faed8fc005545f48f7936e1dc5d60be79011bfd6a1ea94131feadf1a1b2fb10b3a5640ea460aabc24b2e8d3e9c7f7b7d6a4ea368c76b532d8dc286aeb6326bea1fda87b157dd61ebe25462dd58cc67a1f8d1dff77b627fcb0d8144ee08971cac654c2320403614a260c062485d894a6534046118a11430669cab2310159102741401028a60162e8208ec21034b1c7f35bb2363f080429a8fcdbd02b018f064388d4635efdf948b3de19283a6b7d9adc3ce3a96b6642f001814fba4612e050f6a0565f434ca8b46a34c76532b8a9043a0a78933cd57677d9ab85946812f0b03bf8a73e9bfef0b0335a4689134420e4d15bb6047e7568c22714ef4af3e9abdf29afc4b51a40000570d31a7eccdb775dfc952a63a4f81f4815125fc33b0d8adeb29b0938ef74165de818a53bf6bb130961c070ba19e7d13c6c0f5ecd992fc14ee12b13822eabab2bc80587bb384f1d39f634a07be0103e7adac0e10ad3bf3d4293d04cbd808b2a0d54ca4335f32a9d634e84cb7657400825d60b746e8dfac1940fd21d5c42a04d434bdb95de4e3302f4eb2a4119f6b7e2e1d89f09595621e9af545f17136957e284327928f591397bf6a4d7ce3f90d669734796406f22aa6a8b3ad6e48c5698c730cb2402de5e82376b8966eeac7256ca5f8dc685328fd00aea54134d3bc99ff6f0742dcbd439b488446c93509a5279e76949aec98c11110962df4e9ebedfbb3e50696cbb449f331e6911ffec1f7e544f204d7309ce9d35b36a0012f29de2a77f7c891e806847ebfdca485d8fb6a3365a3963ea3011b3f9c840ce60509e6f0b67fddef69fb8a9fc62c69d595cee5b2df203a1bf092e7f0d745ef2224b2480774bd160ae05d227a841f316f3af2b6357635ea0f80e967a0bb4eaeac495379bcaff713cea48b3cdbfa2732e73a1b3c14fe37ca8dcd91407591d8c53f7effcc942cf5fd76d7d7bbd739435a356ff9d5e17a1c4da74b6f5ae207b7bf0a71a18a0bd0512585287e037b031d5989716934ac27607d46f8b92a434cd84f1b08b49a6fa537db8c540e1a3d804e53de85e1c7c0d07a70dd0add3e895c9f9811e19a0bbb4e98e2ff8e6f6ac87d97ca88a5a9c1978f18be74ad3b64d4699ad30af12bb0e508c4faff108fa36739d4ed7229151edcd831424c5f5e903258e82b01fe0d6eb320d6d987eb6b7ccf98bb18f963c8d1ffca9850d1abfc862171db7b26242c8be3d8acaf18085ad3122c7f9d1ccb340b4c5bdb9ee8829d50f4e5ca2ad5876d1d6450b1540dce6229fe0404f1e4b42c35ae2d1929b45edaac855b74ceac7f3820fcf4e46035fcb80931fe439e793b9affbcdc7825aa01a6abc5b77d6c6f4ebae0cc2dea8536f099a41594307dafcf98ff925ecc829e7ac4e271875aea27a113c9bc063f3a094423531af7fdebeb917612b833513aa8eedfab81238937a0bc9316aa00e466e593aa1b456a07e52d896e735e0b1cc90b053ff9b40489b1f2efa8c93f427b0b994fa6ae37bbc649ae2cd19283a167ac7c2447b3a6aee724fcc61cb251c9ef2a43343fb0faa9db507b8adb9e5da971c527a683bb35f16cee348ee5cd4e82923884960b52d8dda8acf5e43024704cd18fa64c3c601d3fc58c83769a1423cbff884df3511cbdc04c99a8a6ce5c5b0837e53e682a4fdd9516b3109a42b318076a3845a18c0f8a332140f23498fd94a6db5670956caaf2661d231996e254a0269a8d40b6a0e02d2f5877908047c38685788f9ad24357cb79eaaa7e62a97d11ad2bfb1c9eede800b712c057ff171ea88c15b7bae7b978366442ecbfe58ff6393a7ea30c4fa107e4b7a9f7c890e6daecc04fa5f0689cc607cbd76926a706f0d0d34c7c72d01a0ba79c0696a558a2b200f36199be2e37cd370c5a9f067d24880c91fb612ee9031cca8b48308d1c9c0071d276e68f16a3e0549ee2028f501eca82cbfac36a8bfb801e4674839f37a3e4d2e2003c20346d00c35cc1bc1fb6c36d0fec8bab3b1f1fa39999a4e16b97b1d812fa32176403ebadc4e2b392d0f3bc0fdb34f727dc01fd499848eb3eefd831ccef93ce4ec7c3e97e4de32964b47b53c5ff5b8630968eba2dc23db8b780c6493bdc9ff43949106d5fd171608033a20bbbae6a9f82db33f4c0a06b98f174e395c7ab163d648c8a7c2de1c4f32be67e1d13eb8fc5169652e9392fab59893569e32aa8060c725acd24cc65871e1aec09e801ae845efef991af9bb69b5b3ca6c3b5b74bd3cca6a9a6eb3017cad6d9d725dbda8e9227597f28a8208fdb25483a7f1d4f9753c398f647685523331c3a4e8000d4c6f264485b6cb83d1bba3c4779c330b81094053a30c00c988c709a9a24815f5d3052522ebcf1eaf8acf08e64bf5aac5443fbe7ece3aebfa7bfc8d99bd10845100b0c61e4c2c91c9bc310a56c4e88cab4b38786ba9ddc189fa3f468d689b4bc79c2f198322ccfe07daa7b059ea68fe6c0e1748f69e66e0a8be898749e42487b15f6d03170b6ea420534e8f7bb57e7c32bc8bd50152b49de51406ebb25bd418dcdfb0da679af3cf4450e12e9fdd125e2302712da4875f1feb4bab3bf8178b61db8d7fd523c57d15ab06b7186087ffded9a7004815bdef054a40ade40a1c960fd0bdae01f09788803f35d2e99f3275a36466e67ef8f220d705833120dd8808899199f197c803a2547846fcf70a55e0c4c139497493f422eacc49fd520408f2228b633fc05b1652aa3aaeb453f65aa534aad3ca8473b8ed8a9c85c0c4857f65972b27a0b39c9b2189929ea21ba199fa7ed433ebf0d563cacf30ffa9be60961768dfaec8d3707c624055df5435b4b01afa7890b22631639f6e20ac1d62bc8a04d23c20edaf4f70688bc0d07db1c6d94597eecceeda54501951b50e008e20c9818ca81c2c1d6381f014f55c4aacbcc7065dd33cb767ef764adae12bd35f9413e2576c7ecee55e12ed522935a7a03db2a068f3f0141479e447156334923328714b851f09f4da874269293cfb978439d72110fe78f5910d96f17702fc7d9721de5b2f8a9c0714338a226dfa1ec9319af0a675db0544f12b2a223a1cbf24729f3452ea4f334b3edf324b4a2318ffc2ff0d9dbf232005bba4c7132300d429f9e5a14c88eb49e15525b26d2e29e3e1cbd39b9a08ed01bd8c279ed38e3ca0277ec5ecff008a7db63d8fc40f1a540641829feb0705e6d987cb7df355442eff981fcdf73d3cf44899b533d02e5fa356394fd1b29bdf23817b6b70f94308e156849d263f031aa46d38198beced4f85a13a815fbbcae79661ff83529aa81665de8058cb6701964053316cde7ac288e2370261b38568f1697247139a39f8ec98e5f456d4b94c624fcb7c0c76beeeaeb940a50a9677d5df57aabab297439ee554d538cdda489aa139468829fbe5b8cfa6ff3a60ef53a78b7bdaaf09fb830a0ba1e17bdb5ade507f1792271a8cb0ff0f2f483047ed3b1ccaf73685ba1f787bddb08ceb3fa66d1255f13815c82e3be515a67b892910a7674889d8528404cbbd3998777485a0fda96e70b7d3ba44bed5f1bacabd0abe719ce3cd3fcb29ed3a1b9c4ff6b4be5da94546b9276e1c1716aa848ed70bed0042c7098e7dbaa0d5794feb76359940b8a9df8ceb8abb1655205baf8a566bf11e6f9181d2b2b62b5e38a81842698741024979ff7533efc5f1f111d6fd604257201c1863b58f42824463f2a316df40082bcb216e852274a991bc8ffe78f96fa9fc9e0388c85b3eaff3622ae39f07c1817841147cfe8df394008e2bf374df7deb3949f07b3bba058faeae5177ac8955931395a2001317372523f7e0b7a3fd7001aba6d0446f88689e67712331352603837a64f666f2fd47584cd70c6db7faf7bfff5f1faa877a954c2a200678521e4343a108845fc65039b7ca271e8cedc092770c787f81b1d9e5ce0ffb71a7c2705d7cd15058347ebceba53b2296df54cf7c827a376523d07350ba9a908701bb68946039a4b1fe5ee643517cf8c5386dfb8df5bb33679a58d1c469ee89f0057505e23ea50883cb9bce08fd7efbaf8cf29bbf8c178224b7b2358eefe3bd1de262fe7440e7340e90b5532dd78c02ac3937b947a3c6a0e97c96005d8d9ed59603b1cb50cd2ebbcb9748350366c4c45023b7135d935a1a5ae7a323a97e0ad2767c95c32c55d4c7e949c810bf84596845353f4cc9f3ab716bd6f8cf3450dc1cb22751cf33860387d6fce5320a7e303f66aa9828539caa22add1e03126be458dcb279e36796fb971629a1b5843020dd55f5140315d6592c86d405f1646321e5f569d1b2a53cf1a96b9dec7f59c8559cb82f94a851e5384e382a1a8e0c8926852d1c571607c518b3bb9132dff1974093553fd3b19f4fb87a93ca2289ae90462e975ff6a040f544daf508ff7630b6d4e71e7fe6372b619930609e51767e99d68b9101d2d93980089f4cb548db34c64ba2f034af15c8ae240c9860cfdc0e68b3c51f6eae28e1abb89c8c07b24a5aab01950280808901514040480a83b27559bd92e0c82abf680e75bba3fdb624ac863256f35a154311cbca3ed0c70ad53530079a010679a4bb50f012a0887f7b745f3ac02a2375cfbf2e6a29720a0c56288bed3924ba650d15f027682bb4fb12a705dc0ebd356aab996d4f1bebd1344e672ffdd36179f4f421474f147f4b12c60c89fb10c806c27a6df63bf402670f8714b9955b0a8d775158cf77be744af8152c38dac60410c00c009a54226a7d4b0d6e03644d6c6a617165be58a28e8e14e08f81434e9bb463c5e40c5d8462c88bf73285ddc7787dff7113ce9c139fec7bdd9a8e310757c488af07135292da559c04744b09c8624c5c314f74650bd9d6805b9001d71fdcab6adacae4b5d20401ea88731fb4738540e9626d55294e49809df42894ed30324f2d321d5a4e577faf3d643515fdcd7d5f56552d4d837a201219d38f63f0a6bb0529c096868c1e7adc9297b409750e0c25af68a43d2cff82fac5f40b8a2c0f0df9a5a7abb88be513c2be918459b7ba09bd915d8b28d690bb074796fe85afcb40784cfddcb4369cc79c0b5e3f157c7b90384a7684e61a42edf6c775da7c23f0ad1830984398e4114887953c362ea9f4e10caf6b3e98f44713c1b2c5fbae2c4a7f221bb319306c1f64e87e7f4715436f9af5b408d821550ca292da0ef30b573c1df1a86c4b5c8a0fece7076da67ddefa677551d4616c3ad8f8324a1d1bb52dff20fe9105ed9857d01f73bba98365d6ea771cd11567b6bbc91c9b662eecd07d64640b32cbcb7fe619abb9ff6995e52e7946452c7a6f8a6ccac901b8d57bde1847283cd0309b2e498f9642fe3e06730021e55bcd01c0207db4f53ddb3d94cb34512bb8890bd2d94eff99e228b95897f5340e10353762184e112fc8a0f3f1aa852d2bf560f6a24acf9bb6d85411bf5b90501c8cfd075085fe2cf69d19fdc3ae3d3009c6f9f712a2551792bc3829f3f0adf697388a8c61d10d854aa82e07ee34d6965bff5cef4c7f78775d32269b49368a1497e00e8972c9bfa37015d9051676a3b7711c15588d03e93d7baf9169f6438718a46aa14937fd73d2c3c100d7ecebb6c669879e716e1784fd48684df9d39c353332b18902050c505cf822bad36ed39c2f8b80336cd9e15b22fcf78cf0b0e1f7e5873ee000b1e4a3f672b9dfb3eef787d9b3e91ac62ff9e8cdedd7f3081e320d9ef55957c6d8d0042576968d2fab1f702baea2c581fa478f8a6ed93a2970e35e67b4b315a454e1c9747271a256fd457775a4090f5cf01f9144e3ef4327abc87188690868c21a27e02087465ccb645d9aec1dfc82f6774ecab0f65dc4b25c7d5e3056f0cc2909ddca451d7949fa5e30f910287bacc959e3d5935551dd776b4a53a9af3b7663e8ef972d22f00a96b06e635a1ed8c397a1e0b237eb0959a8a26318e364e3e68f739d6cce009e78948f87140b51626de457e14f305267c53373aebc3bf73dc2d37d7a498906fa93e80fcad463b1a6e4fc28c4e06b7582587e0820d760317df7bf572111ac72f07da180c5f5c4eaee297ef68aeca4c90ec324f24ab889ce0f50f507f9e2b8f1b5da16e0504013d496d887793590df1e10afb2ca853ef55ff3f90fcc078b9c370b86e95ccb507051aa8a018c47c72e26444feef922d34afb8e6ed091f48b24664c1a2b1788f1708e24e9c0d1fe0564ca9d016b9cc5f3b09fa2e62e580650541cd7d907eb344f7cf4e2a2eecc3d5ab897ba3adc37dde5b83ceef3e13b0eed3f0a4e335ef0008b3bd018036a2683667158fc3d3009ea7a641df0f073839db5e3a2c9c47ac766b53265db12e1a3b9b238c20c84d1700f14b4e4f62e299012d87c58c9cc730a2154cf0a55109d07d133fc3c3c71252e036ed6c7a07aeee9c1759be60bd380a2f4c91b8854fa8f4d38e84ee9f45dad4335bf379d0c72b6d5c32b7b5535451d0bf0645fd7ddbb1590341559ea4624c235f281aeae3c46702049ab5d09141843e3831876a01627a03993795bcdd482f34c59b7141a4608af40aff5be18d3f4d5a5fdb0aee1ea1b6fe256e01b5433d9cbe284f727cd2e36ff5a7c3f3a459b3ede7f48ce7d7950a67d123d2fa26fecbcf6e213b5c5a4d17c43c1b32c600f247e5b5a0c4a4febd844e81df214b83fbfabf18573a841b47a575d00f384a194a531546ebf436fd6721dff246ce6867538994d06d2f586ace692442ded47dbef5341d02b62fde75fd7968223055cd97b5996fa1e29f40d60aa833217a881457b7301a38d06bf1686d9dc71cae6de01968f722999260e089471724c6a549501760add7a7d4ee0b4884c2587d32f284f6bc05e83f00523d86b5db39224f28dc39128cc85a71d1b3b7f42e2163375cb744a37158e3fb97e534d064b1623b77f472b95ad3b1c7908b8ea45b4140fe20a48dc2bf370737f506b01352ab8b5bc1c76c9aa26815acd510418fab44dc3a20fa76b4829cb8d52ea61f995ad492daca519b36e1dba50c389b70ae9e7d50288a53c1c576990c21f50ee9dcdf0d4905849589d2bb30f09e571fe4cfd086bcc8c45d99afc8d3327bda744f5678bfa177eda2a7f00b40c305636924cdfb7e16025cf7bfea131582b502b9790b65caa0c456aa07f53021112a67b0ced03dff4f38133569e650a15311793376e2cda6b19037e1174f8e07ab8633cd45ce82edce7155810e38968ad2028bbb9dc2b690a287151071d03f19863b6d577c2ee20d9c9ef6a6298e5462c06b844bb448415bdd709b22c08ae6c42b04aca33ce1e5c13bbc569ffee962259806c81824432676d65ce506af29578dabc0e904f1e01b0321955160cca6182bbe5fa6e058844845c1f710b4ae832035adf8014289521d8acadd37a20ef4800f3d7c80b23e39f3da71e59f7dced4b8a86c09e25c0286729c9d42ffa9680594596f8823d01920da81a6c06b022e0af60720313f1055d883cdee899ce139e3fc29e6f9f36ab189581248f43270122a5f38ae3f11ab964a55ccb3f2e1b00443c1a89478ae2ef55cfb6a835673a0c4c482090641b15e1feeef0ef27ecdf6dfbce43da30e2e92e77ceffb667364c279887e31205991dc0c5d4b2a8ff9fb5cf61b201886db092cf1c57c47b98b5b7afbd99113a2dcc22e7f4adae7506420efdfced58017b52da82d37e45e501d8dac401639c3d11fa5ed51e86da44489b4b020a3a53a842d77ffa09e5ef07b0d53b401ed1e35ed8f8c94018b4338f04c103a36f9e0a7f844818cdb9e2a3dd80f4e049fbf7a97572609f9d63fd4451ae8e5a0eaa2fed9f771568796ed2cd479fdc79a1bba0c016b2fbef8573ede420e625c62c5ab600849da922cf8c8031e0bf9247036d73ec69d8a8ac2b7aafcf7acdbd8b6b7a87ae527014ff99231c156ec7880facb205a4d96f55818051e167fdd2bdc7a128c6815b186a1604d8f6c864006ee86050f163cfcd3ddaf51fffd8b1e670adc6917fd41400399f278f4b342cb23b60491dbef65b2948ae90d3714d5041b59eff067830e12a0ba4627a13aa37cc980bf78a44703918a042cae34b341d18b64243bfa5e99205f3db3743c8c1757a5be2fbfebab7ab49e8c6a8eefea05f942370a3b462dfcdd30385c108a3e3dcc81dbefc092077a9a0f0b39ab07a465990f551d9b3fd45f4e8624ffc32a002747a7d014baf8e7cc0d229b6ba3701bcbf94e40be9561b32b7d70eefd4ef7e21b54f57468a61372a3794d21bcd24962923e2dfc6db48e9ab5962f69c999f7ce9d7d2c0933b2b199bb71761cfad9fbb905f68cac49c9712fd46b411c315c7b4cd66c87dd04cbe3ab7d186e986ffbe323a04ee32de3a5121577a58fe83ec991126df707c8b5d175ef0d1493b44755a2419c7f5b287c019d819a84a1a3f94a4eb21d0878c9eb8a996c06768109ba8790dbfb8e4e40ae81ce1214c8994ada6377d55d8188ffbb46dcb8b84ebda0dcb8e17c4fc4e21262ec529098d74e9206720be69c09bb58ba28472e93dd6a9c009390db7e855a1e36d451da9a10eeeaf228ca6c37a08d1dd2b49cf408461dd7e3e2a30c481f1f265e1ec8a084b3f14286e49e90885a70192012489f989848ace936bf7904e78c8c090b982b0eca7c38f7f7260be8fe5596178b5c4befee99df1162bc3c3b283c9ae5dfc77e7ec99f75f38b7ee17871e85607ae7ff35499ef78e826d1fc7964efe26c7b25976d8c5fdc8d4f84acc2b17456ebe54c5dd077af6252ec8c7b2a3f00323c16f4281938d825c659f35e1d9d30ce2f16128a7ee8c6b4f27864c611dc5c30bd4e20b02922b31b0d7463075c69ee2f634697e059ba66ac9d5bdc83313fb6bf09268301a99533c1281b3898123d670d53806e8681989707969ed169fc48772b28d38209f089fa859682bfc35a3afcd78dfac03f0e6ad2c6f5e3b48fc37f0c95eb70ad655cbe3c088868e20b234c520c02ccc3a453cc2f5f4d59e68fae0dbf58c4dcf724e63a98050433ecff258cfdc7a0be44bfe8fed75eec391df784fbecffe28bae9b8dc5fcfa87be72ed1fff0f4282bdc3e83f494691fdc38c9af4f61f1862b337ae2ec3d76c2c7b1dd2eff56626f8e28ad5349efde82a9b1497af7c26ec480b7c63278689b525e10c70ee6ab59fd9c59dbc2516ebbe36460a2151bf8ba6a79e1a83db4ec23bf52c8a8751b92c2b5b6ce03769d465aeb0785925057c5004787ebf8d8b13facbc3858b7fa54aad9b372e11da696a9f31e272c25cdd48ba67d1609ac2dc3c3aa7756c0e54566fe49d4cd6167913c9abc76d17ac513868868abd6d5ebd2f57428c366df787667eb0ef477014443ecc1e3ae8a9797897a855e3139a17f1c063e5bc42e28842a732ab976cb69356cb36b7d78fbd6997fa2a151d9d1dbf39ed64575cb18585a843963e737a1a72b7871626886a1a3dcf516b36486abdb99fb3dfbf0f22cc7fba210fa2b9f160e82ab2b1093313262446028986fea6a19723fda6a33bfaddfbb0c240d0dd6739f303c03f173f536f986305ff1915a9db504f7b689f4108712c6d6b2159f89d9c1f8e0d0af5f93932db518c173dcc18edd67516d4b8765aa83acf3cfa2a12b0f87f23ebbe305c89fbbadc2a8a528c54d2ebd85291ef46f42822e7384aaf44ee01600c5bddcfa46e532e038df64761b207de4f0b993d2ef5608e9a06d04575c08e011fd815d53516ab4ac489388e526e1068fc543133eabd9f25de9acf76b0aec878e576180f6c865f6c72a18096596ae103e06153fa5b331a5450f065f1e9be584c80051d56d86d51fd18a75b67fe08007bfe678e0053069fc8e060e2a71f2b5ce9183fbdc529fc2c68413ba8400e34e5ff16ead6dc9d45edd5aa2b95c5b996ba40f6cbe2847934d2a80f88685e1fbf281ec28f0b7a2d19de6c2f6e448b340c577d7285aee9ac301534b3ae1cffd4a0db0dbdf538702d8f80c8a03aaeb556dc084c8d9804fedaecbf59fbe8f58258e3b62322b81b7fb060a2daa118a0f13ac780d53c51a18f6965caa3209e75225accaad5e249c03820320cddc73e216feb1ad0c3559a4bc89991a44e84e2949dd60d603d73a020387850083e88e0c298074006d939dd20aca4854f99f0471bc57017114928a20245225653d295ac7b85c4523b67389882837f885fd3a7326fbf7f3cc4991c23d084ce221bf0b3ba26e8ee91e31cf61604455df1555a0f5b2ec4714cd240003119e7ffb2afc48d4c474afe9b8844ee8a0ef2254695a6486d33987a6c9aca87e7f0dbd90819d2a28cf1c4449d5f2f43290b118c40570885cf45747a80de914096d04321bedbaa10bc6b62c52b9765341392e3e288665d0b2e4dca2d25b3fafabd7ae6ed5d748013b8939deaf390cd3dcac9d95123bbc47ac0b64ca058ab6048458105c80e886b34c51d4693e9e57357f6074efd577489b2f0fb94a123ac1625a0e5c5999d4754348501fb9369e354c0e9aa6d432daeef609f5535a58958f3f4da581d21d69d5435ef5d8abc9fa5b79b9c26a54bba9339e2f49fd79164d9fbb3b56bdce01b15ecad9ed3460438de0d745da4c4b03ddf64f24c61eaff1bf71487c518759375ddef935305f41b7503e8b240ac99393c1479c4d96fa55c920dcbade3420501473ae6ddfa8419f9e995abca60046ad7689366e9f9423aa94c4d492142033bb1407c169a531311d3d9637fb927ee302bc50af4f1bf259339cba97beda9658457fa4355a7e5ea2e33a558ebffdaf5bafacbcbce880f88adf626c3164a4df493bebcd87af7d961a26ed5f47517b2ead0124647e62127031411a9791c460a5712cbb0c90c077e27fc90085f8cac92b743a6621c0fbd17934cc2d9501969a4b4eb570388dd2dbc168f50d81d1df5fb9f477ee5d4f2f2559e18fae6a33bab3ae905454a0c5e5a7c0ef443ad46820c0cf2c1cf463d0633fba3891f5a3653066bbeb817b628077534686531632851b2170a8a0dcee59c62a851baf1bca65c324e653418f5459d990b425544a043e3f91680ef808f06b5ab628518e02b21a18f0a01d0db144052cb400589a2e086b8c697ae970c907e1020ae6d7f303d65bc0c6015a5082c94d50fe55e499f0e60c78107a84c118e5a54395a44ab1d7f60655d3d9d06d48a5cf51982a03fb97d59b17b2907f5cc5de527a985fb42a8a0ef60e4a8d4e5f4be33cae2daab5b8fab0b9428d47286ae79c6a5588187c6967e40ba08d527021c32ae1560aef76a4548c9ddd23c3176382fd9b0d21602edb05f5c7a38fa2bad98c8cb4ac6780ea87da0bb3ee00206a0e269aee3ca10b675af2d3c27fa50561e8770bf9b03f1b03e00f58630de3a582cd0f231337f9f865a85aa4ce557df68e77bf998a6bf94d590fd00622d88bcbf93eb040b9aaab39d946ae21ffb11e7995435e29225c4518b761ad06556dc6a0169e2b1cda2e5ab1c5c68fb95fe0da39ae1a2bea54273720e479c3396c5b424de0520e68128ddeb8d82d34fb5d9db6086c94a9f0b7650601e406dea04bc817c7b334b2fdd4a79416d7d6b3b6160a038a9059d8f998a5b4b0c845f9470d6fdb6ca36359cc5fb0ae40df274eec0d2bef92143181e564cd2675c6262be7250ac96299f63d58ba79fa0facdfebab54aacc28fa10f0df95b0a1117134f8fa9c8fdc3705f51389287c12c2d3ed5dea88eb9ec9551b8fcd71cd4f1acc1d1f7eb15fad03f423daa1aed3a77b14765a501d807dd9c031a6b96e7184a0e6ff01bed06a7b214f54dc2a5b38984eb4c98fda391085c60441febfe20e0c276270986fe4122661b5ff7959b4b1ea46c092d923881a408bc9015cb1aa634d05c0ffad21f5cd141afade0effe702086c0e23bc9038e9726e4cca8f14f38c64b0768b4a30d438324401eca61aa2fdacacdcea456e23c4a4d91fcd5ef961bf79dcdb71e0f785bbb0f6adf666edba5847e1d62b9fcb16b904d7ff03ee21a7f2f015ab437ac33ccb6fd14b06e57da8ce2ad7d1e11fab2f26cf8d781766605e95d36d26074effb14ea63c9b0c37d1f3a02934ce0b2c6e48536b291e2acdc25f359ff0b1e43e387c86d403e0ed66ed1434f2e2dac0efa56f9d4d72f52d37b59c6ef12f1fdf0e702e90d186bdcd40c034a457256c2eaaba4b658e8d0d2b6db352199493d438fa594000000000000000000000000000000000000000000000000000000000000f902c0f8dd941c479675ad559dc151f6ec7ed3fbf8cee79582b6f8c6a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a10aa54071443520884ed767b0684edf43acec528b7da83ab38ce60126562660f90141948315177ab297ba92a06054ce80a67ed4dbd7ed3af90129a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873f9d05a0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a873f9d06a0f652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f37921f95f89b94e64a54e2533fd126c2e452c5fab544d80e2e4eb5f884a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000005a0e85fd79f89ff278fc57d40aecb7947873df9f0beac531c8f71a98f630e1eab62a07686888b19bb7b75e46bb1aa328b65150743f4899443d722f0adf8e252ccda4101a09f555619c4ed056b4f2385e5b6fb5a874d040bef66d6998283e391bd9a91221ca062ede1de03d21b277fe9406c914f83f0461057e1782c0a71d03cb10b950cda4a", + "0x02f8b101088402faf080850cce4166008301ec7b948707f238936c12c309bfc2b9959c35828acfc51280b844b3dd411d0000000000000000000000004c9edd5852cd905f086c759e8383e09bff1e68b300000000000000000000000000000000000000000000032a4fb7a05fba004f1ac001a0e9e0c4385db7f0f7254952e6ab4ffcf866ca32826d7ff6b03b0155bd828437c2a0686ecffd2315b97178a90308e3ad0dfdfbc05b2338f7a744f839f0db594d61e6", + "0x02f90332014b8402faf080850ab5d04c008301cf9494d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef00000000000000000000000000000000000000000000000000000000000151e7000000000000000000000000ae0d376d7a0c05a7d78ce1d6990adcbeba67314700000000000000000000000000000000000000000000000324e964b3eca8000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000011013ee13788b544e1cd9d4551b99fb67908c0af827367536a61c6577c08697943e2324bd61b8e4a36f2d1f3430cbe7ec39761d78043880041cf1bb61c108c8956a5aeedbe0687653aab42caa3d0242025d1b2397fe9caf1d9009a41af910168adb3788cea6ec4d59e48145c770ea33fccb805b64a9de0d222f3269e43650725efe0e8fee352be90eeb0db1aab9246705d45e3f6f474286c92b39c29d24e1540cbac6b511830578ec7670b4dff6193e87227dfc43a90f6646d4387c88db7fc37c4448445a78ffda7155f56089e57902f63c1c27d8e2ab2ed652e6d34499f7d191bc7bf9f7f765a5bc763a11afba72bfef3197e2a0ea92ae1518867da46e04e482db871b72a8cda4f4b6b15160104fadd87ffc251bae62d115fdd86cd63d640da0342ff59f2bb35f6825ce36c0c017d146b7d459e6b820ccb581d72dd281dd3dee0ad9751a222f9fc2a6abe41b9070595ef766eb6e478f38a433a495d4d0a0fa8f02765177d6493d379f8243cc6871811f8ad9c00a8e42218bdb8ec55dd8d7ed4be8c76c1c930a16790a27d5c56e6136892ff1bb31c5eb678bd7b29adccea97cd67d7fed8664df0b0ef45be009c7182631961ee883d8102e542af6eadd5a3098ed1866b8614da6d5761eba4253b79baf01b17cf793a6056a1204b3457acc3cc6264b2f1729c555dd226f68ac9786871adb4125748b57f44524ee6f108c017fa8db8340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec001a0666e3c97d134e6014f44091c633aa6566c713483482d9927a32b4861683f0074a03a2e8e0b3499c7e001295c9892ca060e1d1e0db7639116d13786116a320d6687", + "0x02f87901822f558402faf080850cce41660082c401940000000000a39bb272e79075ade125fd351887ac888ac7230489e8000084d0e30db0c080a09b87636ac160fb01cb7a14d27348b6cce0a9ce42785838be6d63c893aa82baa3a0112163d1c9448411fc3c1c864f441df6ab0da54d20d2c618651dfca39d47193d", + "0x02f902fc018203058402e40d20850fcff91b8d8302ed05943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad88011c37937e080000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2aad700000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000011c37937e08000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000594907df5a164abc86983000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000055a380d134d722006a5ce2d510562e1239d225b1c001a033bd3211f98ccc049ae4f90d948e326815d684251207cbd2c3eba6a06460b4bda069ebb2026ac1c3a4d3b6112c5ffa3db8fe6176180534d32e07e5a8a1791ea97f", + "0x02f8b30182048784028f297e850a5899c27c830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000f46fb27ec20ae6171b6484109b21ae9b723af74300000000000000000000000000000000000000000000000000000000017d7840c001a04a1c91c977440c133d353f3952196deb01a28aad1102174c50ffbe64d35a8d62a03794e47ac37354b4b516bab988a5ada1773c75fddbf0c607022af88d63daecd1", + "0x02f8b1013284027ae63b850a5899c27c83012d7a94b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb00000000000000000000000038647bcda8340a7ec10703ca423a7a632e5e8ea400000000000000000000000000000000000000000000000657b3801b80b40000c001a05b447cd2f77bb71cd6c3bb3b6d2fa2c08f1cbfd798b5b37fc220b04aaddc981aa04f4a33fea11833f4aebec8cda4b19f51f43c86b16f706198881b56cb8ecace7d", + "0x02f872010c84027ae63b850a5899c27c82520894813c16051667ded55e2cb86f63b6cc81218972b18701ca2ae5fc73cb80c001a0e3191e3e9a4fae9cbe054c71d271c74ad640fdfc8badff7c6827d4d73551f507a0468eb101f8746dfb264f07f0fb08af9977299f1f7d60097842c324ea00cdffb8", + "0x02f903b2018084027ae63b850a5899c27c8301f10694c059a531b4234d05e9ef4ac51028f7e6156e2cce80b90344e1c8455d0000000000000000000000000000000000000000000000a004bb4a965d8e000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000a004bbaf28b6a3a44a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000012791fb7a91b1b3c60815bf9bbbe54cdf00f2d3f084e77ac99942e036d3e01bd8057786c6928d7d897c1e7ec93b1d836084c3398014f766b0a4e857403dc95468703d7f29aa1ed31f46c0028ae2d565629c4a97798a8b976da0370d21fb95bbf47ce46f8f26a32c8449fcfd5c25a474469dd181689ce67188f3631258302ec94723efee3ee6093afd90c7dcaae7ecdc5639a9bff58587c6c52762594b635d3e8320c5922da3e335b38871f1225273aca5f1da9f1ca0be1f54594f9e4219f5c3fb96be551d7209903fc8512fc0e62917dce5a2c5639e173a61a7c9990f128638bea62b8f696c075a0d7480fdd0d8bc03d0834d2f5a86fc7c64e1f45a419bdbd4df3d038d5b291a1f627517cc2d75ca3e4bb5c2c3b28d68a0cb2c662b10e264f10fae687a10f3943c38de5f59624cb400f4b82a0b985ce78f9742ddee7d548aacc59b091ae32988a1af9bd4d0a68cd651be3e9a68ccfbd49e4fe4babfba1c8a009c639a5d127dbd4c142bb36f6b82c1d115684578f978c9b1c11c1478f5b5117e2754d4d89aa46b18ae213bb25157ee5fb8b20d88fe2b2e81f2629c93de9173cd7c8fd5e17bd6e2dbbe8a56472070b4af2c009dab576ece1cadc7355caca63d7af7b7dbb4216f2389571e08cbec5b3e3986681d0b9d88f13a47cc9a55aac7f7419d6411ee03323e3dc5c6c2e77f3d93d20d7e729991fae4bb544fda4ab659e5a01c5ecef3f38904031cbedf8f8d77f7533385b9176d0ebe964055755bea7fdd7a73b3bdd1e5318b1af41fa68346461d94bc4793ebd12fd81ce5d0b74b62d096838eac080a0cd94731ce42773b6b1b5830149d4e003d99dc8384ce6fae6a8e485ff843e6967a0243b33856f7db715612632e73a78a4fd53a0b31e962f2203489ad71dc54c0937", + "0x02f8b1012484027ae63b850a5899c27c830171bd94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb00000000000000000000000067d39ecddeb6a55c036bdf0295979a7b6bb5a7b3000000000000000000000000000000000000000000000000000000037ba039c0c001a02fffd250246b9daac1283d8113918fb1fe98afae9e83930bc616a5d5cd6d5fdca068d90ce7f7a39c23e5a8732f75cd0a140c8e44e1e34e162a844298c5f5751716", + "0x02f903b2010784027ae63b850a5899c27c8301f10694c059a531b4234d05e9ef4ac51028f7e6156e2cce80b90344e1c8455d00000000000000000000000000000000000000000000008b6dab94f5c0b00000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000008b80d8228c84f7b00c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000012c79d53c9a7a164161311911a3c33533c2c74ffbb4b88cf164e68779319dab8dd241012219023151cd4226dbcb1270eaddf920dec8919949f260f5375fd90c7ee208b31641e1d918ec4816b92a8b870b0410def69d5269974e9a40c65a5f48b76d7446741c70712ec3bc408c00315acba7df1a43d9e98d484b21546a68866bb06b08c33db93023668ac1a958153c3b519ce4488cfe2c1a9a27295d03618d262704cb60fc535d433cefba8d4e97c07e054749cd93d61b1fcf19419b702d8a0c786c2f1db2f6bff80bcb997960c0d03a323641bd0f37d6375d2a8740594be1810499813bb36a917a3801d1ed4228a19de027d3cd10c64e12398c8e836a4e25c01fd6b643f5d9ef94bb41ab758c49cc538e1510573fef366310385c2ffa1683237ae8e2981597c63559b53e7563e9b6917feacbc507590fcbcef99899f1aa9b7ba9a04ee2bdbb631e63e0dd22288842600cfcb55d8cc971725b082745bdad690cf40411952d1ef65dcb635cfcac9e2f74543cbeccae33aa05191f1422bc9085e9c43ae7ba5306728c30ee2345a3e3e81d025fce344f8e7e1f2b4a7b79c52ad7f6f327f5520e52bfea7881a8a82b097985d1012e306bad81a7f4ac189081dcf5d9de582d335e95c2a827957891e92ae6e48698388ab48babaadcefbb51b3c7515d2dfe613bfdf5deb667a1f9ae577ae021a46608d9a140d92c6e79a344f8b9d198102f8c71d637bbd325b87ab4c094dd4ef5df9a1d62c6dea6d0f573c21dbfb086bf7fbc617f85f57eb6ae6a910db84ffd7eea166608f0587b82a0d02fa84ac8abd81c001a0ecb9a0106aba31dafbedfe0c77a5054a867ec350ceaafde6655330e17c51aad9a05472964a01241224b46789835b7bb2eddb2659a3e1e3aff00539c7d9456e822f", + "0x02f90334018206c884027ae63b850a5899c27c8301350a94d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef0000000000000000000000000000000000000000000000000000000000016440000000000000000000000000c6cb96cc1727ec701e5483c565195b01e3c1da2b00000000000000000000000000000000000000000000000fe2311b9e95740000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000118732a908110171cdd152f0a3fc1abdfedd326cc62d279c4f28a9b5595ce81215f864fd8afddb03cbcc72cc8bdbac50ebb3ad793ea229920c2e0a33fa3d31403168e65f5a67a47911aeebae614c59384d9b710e06aeb53f8b397a05c84c12e037c758d2c4672089b0b54ef8003e632bf4ceab8f16fcf3f60aec362b30d52cc44b5ff0a66ce5a0cddee227f20435920ee5354c1a27f3a71113aeb5e4124dbeb7e9f684b97327d4179b5cc4f84d7bbe1a7016bcfb84c0d07c56698884b96b4a6d8a8e757818983df1d3b28746cd8d29174466c10786844bb9200baccbaaba67578cda6b884a929165392c4c002206bd42d59137e97cca3fe6a31099161f5f5e28307902cfd27c9a729e8cb6dc30a9180273dfa3d67a2b0bdfe98d15d80068eea87d55d80453cd5dd41b0a179fe877b90d8d8e6a44423ff131890fe611ed2ca7ee67a31f3ed77bb9215bacf9b47da600d53e372ebd97552ba37731a731b2b490c83e21a90b94a8d5891299de2a8fe58144ac34ba0b0f5226bbe54fd2cffaaba4bfcd67e931175e5b4c8b0a956f3d164cde10179a7583ba29bee3eadbc2acd1f0d6df9677a941d7cbbd94ffdf154c6736b146b862f9de6449d0d550ac055f19596abe3817ee8a14f49b7efe1f8351d9e474eca711776c9d4b2f80a01e0889a1e8a3497dbbba44476a13c02cfed0b524896278c0bfc2ede44fcc65c1b9efe49e33a5ae340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec001a0f413a42bbb6c7766e0bca787f8fd339bc0e9fdb5a170f37a469e15fd3e51f47ca064ba5cee705f894a66816f51749e824c6d06c5d61e63511889f1ccf2b656a6a7", + "0x02f8d1011484027ae63b850a5899c27c8302d57d94401f6c983ea34274ec46f84d70b31c151321188b80b8648b9e4f930000000000000000000000007d1afa7b718fb893db30a3abc0cfc608aacfebb0000000000000000000000000bdff5cc1df5fff6b01c4a8b0b8271328e92742da0000000000000000000000000000000000000000000000056bc75e2d63100000c001a07b84b299752ff51bbe9044c23d2e00eae36dcddf553f0c9b7ae8b15ef15d4d76a071178b276c2229e62a6c13d780d7fec337f50a25166d1ac649dfa85c1df34826", + "0x02f8b0012484027ae63b850a5899c27c82cb1a94b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb0000000000000000000000007c26a37ff4c8ba0c1b6168962357af069f5cf5c40000000000000000000000000000000000000000000000019274b259f6540000c080a03e9792907cc384488940415cd89f881a489e7de53941e6dce224e9cfd9eb16c3a046e768ba26b8d79b7efee4ef9630e1d5cb9c347ed2a5d99c9bca7a246bbeb9de", + "0x02f897015084027ae63b850a5899c27c83015f9094abea9132b05a70803a4e85094fd0e1800777fbef876a94d74f430000a42d2da8060000000000000000000000002fcf7eaeae8a981300e290f1cd38435a25fd8972c080a02abca7a9bc2e5893ef08421b904b44d7ff98ac11696ad65953a87dd0580fe644a00b6d8abce4d09e0d056aa4e07f752e899327d0f497dffd5f1175a6d75e1f250a", + "0x02f8b20181ac84027ae63b850a5899c27c8301120894badff0ef41d2a68f22de21eabca8a59aaf495cf080b844095ea7b3000000000000000000000000216b4b4ba9f3e719726886d34a177484278bfcae000000000000000000000000000000000000000000095e57656aad8fb25f4000c001a0888717178b845b5ba2021bfb48eed6373c9c1b35ab3c3199e9fe4c08c475ab7ba012497eceaa72947f3704b314f9599a88cde11852e59453994c02d0c13a5e317c", + "0x02f891018202f984025c1cf785138daa3288829b7194c02aaa39b223fe8d0a0e5c4f27ead9083c756cc280a42e1a7d4d000000000000000000000000000000000000000000000000004bbe6d529c8000c001a0a31ac549502c1065a233d3a8b2701c0696c5b5b72742d9ccc9d4c5ac4ef6d82ba0131dc6fd75219cd0c8d4403420a60acf2a3e5f666557031aa8bb2546da343cfb", + "0x02f8b301820488840255f2e1850a9a1ae67e830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000caff156bae012babafc155b87e8c7cbcc94fa73a0000000000000000000000000000000000000000000000000000000001312d00c001a08d3dbf07a9abe69f705971bfe4a9fb84f212464c0c2eb37a68b721b4361fbd7ea07351f95594f96ee8a2710879d90e0df71697ad94b0e5c6657a907ab6c9a3aa92", + "0x02f8b1014d840255f2e1850a7a35820083012e8894b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb000000000000000000000000a8f02eefbf996f74830b833ee15c8eb1480bf1fc000000000000000000000000000000000000000000000096684ed80d4a7d0176c080a03c04f09647d9697264f6046da50039823f3d3919f495cca499cd2b317a021188a07ba1f490e5f3a5f1e39a6e9cf52f74566960dab3094273e3ac530a0d414fbf3d", + "0x02f8b30182048984024402a2850a6d274f32830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb00000000000000000000000078680dbc25dee0c01b0897b8439345ce1683e3560000000000000000000000000000000000000000000000000000000002160ec0c001a0e7b7ddb074a2e81fe9221c661c7f04db1ef9c4cf3ed9f2bce1dee0e4bfdf3a69a03afa2e314f35665969eacf3c6380e976efee109d8fbe53a9f5eca43bd0bd755d", + "0x02f8b0011d84024402a2850e14ddd80982b66194c5190e7fec4d97a3a3b1ab42dfedac608e2d079380b844095ea7b30000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b5000000000000000000000000000000000000000000002f00399d4efa8be7bed4c001a06757aca218063c654b6963a2c5d00be2285f3d4dde4269f3f1be64365444b50fa071369dee66f770103d6f530b337cdbcb9b29172e8fd02bde63443a4721c410b9", + "0x02f90379010284024402a1850df0ccb90c8301e7479469460570c93f9de5e2edbc3052bf10125f0ca22d872386f26fc10000b90304b17d0e6e00000000000000000000000000000000c18702f6e8994fa8929ccb3bc11c16150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232bff5f46c000000000000000000000000000000000000000000000000000000000000000032b00000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e097c54bd6e689850ba559f911d839513a146c6c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000004d0e30db000000000000000000000000000000000000000000000000000000000c080a0c8fb0e9523d2e14a5c7cda5cf77e992406b00ee1e1c0858077570bce1cdc1e7da06ca7f945f2022058e7e8bc32d8863edac3c0207c1b5c9f9d4b6fca11bde37c2d", + "0x02f9035b0124840237ddc9851363ec0bf783034dbc94881d40237659c251811cec9c364ef91dc08d300c8761e9ac8a028000b902e65f575529000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000061e9ac8a02800000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563546656544796e616d696300000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004521c9ad6a3d4230803ab752ed238be11f8b342f00000000000000000000000000000000000000000000000000610e596dec14000000000000000000000000000000000000000000001038ba11f47e80675858e900000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000db531c166c00000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f1915000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c80502b1c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000610e596dec14000000000000000000000000000000000000000000001038ba11f47e80675858e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000180000000000000003b6d0340394cb9e147b8b288e38615ae04f442a037bcb99fab4991fe00000000000000000000000000000000000000000000000000fac080a02b8db4a6d4b2518400cb078e9c533f9c38b4209ef428ec341a41abe79342dcf7a046d12b626a1bc12d40f0e35738e7ec00cc8b4eac46505e3c401f992c26b0c3f8", + "0x02f8b00161840237ddc98513004a65eb82b70e94ac5b038058bcd0424c9c252c6487c25f032e5ddc80b844095ea7b3000000000000000000000000881d40237659c251811cec9c364ef91dc08d300cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a0aa10d5e5fc7223177ccf4d506104043b308579c83627e5e1a651b658bf4f2b82a079f7bc679ec874485ecc88bfbe609f96448b935648e389391d82c743cb3fadb7", + "0x02f903740162840237ddc98513004a65eb83048f5194881d40237659c251811cec9c364ef91dc08d300c80b903065f5755290000000000000000000000000000000000000000000000000000000000000080000000000000000000000000ac5b038058bcd0424c9c252c6487c25f032e5ddc0000000000000000000000000000000000000000000003fce4ee0d0a3114dfff00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563546656544796e616d6963000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000ac5b038058bcd0424c9c252c6487c25f032e5ddc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fce4ee0d0a3114dfff0000000000000000000000000000000000000000000000000175054bc1384109000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000035c251d8ca8a4000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f1915000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e80502b1c5000000000000000000000000ac5b038058bcd0424c9c252c6487c25f032e5ddc0000000000000000000000000000000000000000000003fce4ee0d0a3114dfff0000000000000000000000000000000000000000000000000178503ced89c7950000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000200000000000000003b6d03403802acec89594353c0cfafeb97b3368a1544edeec0000000000000003b6d034006da0fd433c1a5d7a4faa01111c044910a184553ab4991fe00000000000000000000000000000000000000000000000001e1c001a07df4ef996d2f377e92dbf974c93ba5d74f94542eb104dc18b88ff68fc8f3a38aa051944af075a6e0a09438dd4849ae54cd295cd863da47b470f39b37a43de82873", + "0x02f8b00153840237ddc9851363ec0bf782b71294761d38e5ddf6ccf6cf7c55759d5210750b5d60f380b844095ea7b3000000000000000000000000881d40237659c251811cec9c364ef91dc08d300cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a0d5fd4213ed6462f0020c5e17ac04d843b370c575cf30b998df33dfe1f1ac3b90a043e40f0efa5162262927d66562caf3ebf52eb5575b4da17d4840723696b241ea", + "0x02f903540154840237ddc9851363ec0bf78304d71b94881d40237659c251811cec9c364ef91dc08d300c80b902e65f5755290000000000000000000000000000000000000000000000000000000000000080000000000000000000000000761d38e5ddf6ccf6cf7c55759d5210750b5d60f300000000000000000000000000000000000000000684bb78722fdca33b5d149c00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563546656544796e616d6963000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000761d38e5ddf6ccf6cf7c55759d5210750b5d60f3000000000000000000000000b528edbef013aff855ac3c50b381f253af13b99700000000000000000000000000000000000000000684bb78722fdca33b5d149c00000000000000000000000000000000000000000000000c9df82cee9a43adef000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002acf35c9a3f4c5c3f4c78ef5fb64c3ee82f07c45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c8e449022e00000000000000000000000000000000000000000684bb78722fdca33b5d149c00000000000000000000000000000000000000000000000c9df82cee9a43adee00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000381fe4eb128db1621647ca00965da3f9e09f4fac800000000000000000000000d1a47332acad7498af1efdba16158e11317eca4aab4991fe0000000000000000000000000000000000000000000000000121c001a0deddaecb2790f66ea83aaf2c329b4892f04fd90c0f3590986d9fe27946b5f000a02a67a210d147b633543787a213f370a2f7bf1c3b5ebdddbc2ab2b907f431fba7", + "0x02f8b1018186840237ddc98513becee03782b9e6949625ce7753ace1fa1865a47aae2c5c2ce441856980b844095ea7b3000000000000000000000000881d40237659c251811cec9c364ef91dc08d300cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a0bbd2417497a0a84e6021ac253cfb25b3877190ca4dd54ded96dfb2c54045294ea00a8ccb7e0717172be7fc98eb2ea9837125955c02dd7ea3bbf817cb08f5e9eb46", + "0x02f90355018187840237ddc98513becee03783035a6d94881d40237659c251811cec9c364ef91dc08d300c80b902e65f57552900000000000000000000000000000000000000000000000000000000000000800000000000000000000000009625ce7753ace1fa1865a47aae2c5c2ce44185690000000000000000000000000000000000000000000001175e20984c25dc799700000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563546656544796e616d69630000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000009625ce7753ace1fa1865a47aae2c5c2ce441856900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001175e20984c25dc799700000000000000000000000000000000000000000000000001fe184bad716b8e000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000049839213ec5fc000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f1915000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c80502b1c50000000000000000000000009625ce7753ace1fa1865a47aae2c5c2ce44185690000000000000000000000000000000000000000000001175e20984c25dc7997000000000000000000000000000000000000000000000000020298fe8b769e380000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000140000000000000003b6d034048200057593487b93311b03c845afda306a90e2aab4991fe00000000000000000000000000000000000000000000000000e6c080a059274d263200b58de64bd5b6b4e2870a5d31980fde9b7a609e243e607d46d12ba028ddc7178997f8f8b9bef8dee39edc73128e15f2e2f39f391794d3411b3444eb", + "0x02f90232013c8402321261850a7a3582008302208794af9ba9f9d7db062a119371ea923ed274e398116380b901c4d4dfd6bc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000065f2ad0e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000041681f92e2672de5ccc985a0df77bfa70dc9724ac089e41aef7b1a6076b5811ea9518483a459ca5d247c3146c3505e053e3d690b9b6f22ca23eee349f4de123f871c00000000000000000000000000000000000000000000000000000000000000c001a0fb6c12ed3d1af23643dcd7c9a647aa22097674678e44fbea34b2b0a1149260b0a07486831acf77cfa39a2f60e3fb91cd16ca6dabc29848e1d4825b592d15578d15", + "0x02f8b101058402321261850f1740c9c7830176f794d1d2eb1b1e90b638588728b4130137d262c87cae80b844a9059cbb00000000000000000000000028c85b08a2454ef405b845e3d108baf5d8d6801f0000000000000000000000000000000000000000000000000000002540be4000c080a03fedc0abf2edd7ede2b85515ccb1ee771b508ab7379619f47dd3d33bc2e847d1a07297b706f5b35f8953d9ca97027432a0680e146f68a53baa6007177cc133205a", + "0x02f873012e8402321261850f87688ceb8252089463b6d51c562a9e2fb2650c28c05d27b11bbcae2288016345785d8a000080c080a0612dff6683243894502490cf89854b3caf33f00764aab02789e52cc26b5f430ba07b245fe3d7ace87946b836e244551e3d7d33a64b394bfcae5e0521596eaafc0c", + "0x02f8b2018204e98402321261850f1740c9c782b73494bf7bc9e63635dc11b335d52b0349d0100a53a1a780b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba300000000000000000000000000000000000000000000000000008d0a10c782e0c080a0eaf6bbaf2779a49ce771c0ed11563f5748230d2f0f47558be738e332f1100a28a045c11052b00a265c9bb02efdc66dfc858a908ef4d58fc628f8c0edc89687171b", + "0x02f87201198402321261850f1740c9c7825208949129fe4b97011d32b35281fe35c05d2fd36773e2875ffceeda38d67780c001a0d3676e754d5e0129f4fff2c026f22d183771abc22143fda675e0ecb960b765dda057674cbfe8a53020105b740243d675a240bd46da34de8453fec627420a735fed", + "0x02f87201328402321261850fae8bdf9a825208949664d678323cf4a682787ca7e9a2335e4730cdf687121acc68ebfb8c80c080a0e8e1a87d79920d8c2bbfd3e1f3a8911e715aa570ae8176e4a1c5b8ffbc8725b9a0093feabcff683bc7ede046b7eec213a9e1d63b850406126952df6b0024318b17", + "0x02f8b101628402321261850f1740c9c78301107a94dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000c66ea10b13d6d8de3fd2d76bbd3e180cb675d4e10000000000000000000000000000000000000000000000000000000253e6f6e0c001a0a5a4289bff6268cd720a5ed72b37259be39144f21a2f837b00c03ff0e1914fb5a04ba55a9752bd2bc6245641fe1718ca2bd0f5aa454a1b4537b849a30af9fc1dd5", + "0x02f896010e8402321261850f665f461d82b2789400000000000e1a99dddd5610111884278bdbda1d872386f26fc10000a4497ecfc5746172616e74756c333030330000000000000000000000000000000000000000c080a0aab15b74e697c021bdd41ab7c3349610f30e6caa18b378cd6d8232f1809ecd81a020c8c4d08051ba6b7ebbf59fb66d6a342db393f699a543d384d1a7e004669f71", + "0x02f903b201358402321261850f665f461d8301f10894c059a531b4234d05e9ef4ac51028f7e6156e2cce80b90344e1c8455d00000000000000000000000000000000000000000000007649553f44be58000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000764de477a6da2c66dd000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000122b9301f12211091b5c1439304c70ac05b9f731e4b92755e5b4dbb87d890840f857eff4b70c6b9355c6cbdaf703b95e55d08aadcfa2a9098881fb337310cfd4cd0a110fe6777b61957ad3dac33eb927038d72bd0cf174a4aa2b5175bf371b2c45fda40e189bb1d92070b809e2aebfb3ce647a7b9f1d20d2a42cf5d35d92c1dc91be3058238dba0224dc1aa0d2b878f84fbeb44c7efe01b4513c29e2fd111a22689b0552d665418aa10950d1af733f89963b93f7bb13d7e98a43ebbe27c267ced2693613bb2698a7faecf270600b396f443b8adcffb83e1c52220a4ff2936540187f9e8c9e3746eee08d3b0b785853ca6deade552f8c7047b088123db7d71c0c68bd8adae3ff0991e54e12f875737f233a101e9f9c432a35c29ca5f5e5406b973972753f61b1d1a1d53e57cb49ac4ba169ade3c9f832cfe9ca051e7ede3a33fd142a15c1ebb77497f2693f6d6f1c1a6ca12b7375776a1048963d8d68dbd8fff367b009585a7687772a860b48bb4c14475230a654fb77e00f0b892ec571cceab002616c23c7359f7394255e917c6f10afad171615c27142b5362c58c805e91554d5c99568fe3f7a990d0f00b2714b7105fecfa26db4a56489b71f088208342c09247751e549c0b5d29e5f473b4ce26846c3973dc05e90bae1daa1156eb69fb68c9d08a7d0a6af883df133f4dda40a2bf9d18ea6e9b3b90b30974c74803005be777e854f5656ba40bc7ae01e2b8cb95cffecd7cc1fa7a3a3e69fa5faf5d26f1771383bdd1e5318b1af41fa68346461d94bc4793ebd12fd81ce5d0b74b62d096838eac001a0fd3b957de505fb31d475834c4002bf2f7e30f3d0cde973d7190f63ab1265ce73a031aa05fe24c95420ad65d3646968a7c8ba10ed8c098a01053fd52d774defe80e", + "0x02f87201028402321261850f665f461d8252089484b2d08156c84c4b13e3bf2063ca67de2c4c134487dd2fe856e049a780c080a050eb5301a47738ac132057ff095d8faed6cb9851b6542458cd1ddfd3677f4e81a02e60ad73e18be1a79cd123f0786ee8678db91f54b1842e68a8dd65dae28c8159", + "0x02f878012a8402321261850f665f461d830181c0941f75881dc0707b5236f739b5b64a87c211294abb883782dace9d90000084d0e30db0c001a08ff2d140a8f881038cbaca7841060ae30b7a0287f8a67acbc13ce75cbe124eb5a0704a4495b1ee4bab7706891891975e8d1c3dd4c4cf1961f213264907298fce86", + "0x02f902fa01088402321261850f665f461d83033622943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad8802c68af0bb140000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2ac9f00000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000002c68af0bb1400000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000010be622fe2c756a700000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000f05897cfe3ce9bbbfe0751cbe6b1b2c686848dcbc001a0d135dc7244e37da9e1c2a04b15b14cc9eb737e9cbf71d2eef98bca360349eb99a07091c3c0e4a7b0f096a2bc27c2f91e520e3fc34ba7383f9a67e7016dde75cbde", + "0x02f8b30182048a8402321261850b2e2b96d4830186a094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000092064b31442367f069493f51ed4d14e032d6bb9000000000000000000000000000000000000000000000000000000000200b200c001a0888c2a41170e984ba0fede837f2a07cfbb16d3cebfad4fb21e7da4a5375f4e8aa07c40d7fa2e7837f609ecefdcf29f364c8e27f1c934aef3ae662fc4eff1d8f190", + "0x02f901130182018f8402321261850f1740c9c7830184c8945954ab967bc958940b7eb73ee84797dc8a2afbb980b8a4381b46820000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000060bc000000000000000000000000000000000000000000000000000000000000665a000000000000000000000000000000000000000000000000000000000000080cc080a0d6e97ab62a9e2e044fc02ae5218db2757bf45897a1ee026cc303db9c1d8d09a0a05382552b59535ecd7bedc1e4da05aa24855f0776fcda86fa4e79e4493f4ad23e", + "0x02f8b1010d8402321261850f87688ceb830163db946982508145454ce325ddbe47a25d4ec3d231193380b844a9059cbb0000000000000000000000007727bb09f657285e725622dfac1fbbc4cc4d53d5000000000000000000000000000000000000000000958d7fe0d736a62fa70000c001a0f01b03a797aeb89befb1c7140e083b7cbeb4395ceba8189c1ce0f2afab48ce4ca011136de86153e7c87766c59d296f4cd6b7600f241f88718a0e21caa04f82a36a", + "0x02f8b001038402321261850f665f461d82b9e4948881562783028f5c1bcb985d2283d5e170d8888880b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a02c4f89ac1bbc03e4461cb9929bdf07b55eb85324e18d5d7a93ee51d227fb2ab6a071b2f31fe8281fe87d1548211bfe53ea7f78d70733209c298758af43aec406ff", + "0x02f9043201548402321261850f665f461d8306f60d943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2ac9f00000000000000000000000000000000000000000000000000000000000000020a080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000443459d45c30a03f90037d011cbe22e2183d3b12000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000661a375100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad0000000000000000000000000000000000000000000000000000000065f2b15900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000418acf0eed453892c33c18956611fd7a453fe67a59c15fd5c282901aa18408b2490a64767efe3bc039078bd51e6523fc1b0a4c865573e251143aed0de81888d1d81b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000005347391d799baea63a00000000000000000000000000000000000000000000000000000000bfd3336d00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000443459d45c30a03f90037d011cbe22e2183d3b12000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7c001a0950ce9740b1731bc910d705f2f8d7ac4fda429b7d655b8b2a28f3fd35598806fa01ee72c60f665e808ea7abdf5689a53a2dd9f87ea32c7545d92cda728b0392064", + "0x02f9033201808402321261850f665f461d8301351f94d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef0000000000000000000000000000000000000000000000000000000000014b19000000000000000000000000a63226a58bc0a378345e1622d511fef821a59b0400000000000000000000000000000000000000000000000324e964b3eca800000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001194390f2af59eaddfeb83a00f3fb4cab380b45172291136b83fd65f440631df09c269d1e7bac8c3bad9dcdc53e93fb917005b8ba8460400614616102cb366e9d660a930137e79674b56407ec34603eb2de1de0f2e71ef4e14e10d136b0877205c134c47fccece94b8cb074c85e89f114ed00742cfdfb597fee4ce7df3a6fb3886c48cec8b35efcf717891800b365e63d0e671d78109237a781f2a68076e2f70b6f4248d05bbc39284bcdc74880596219f6339797f60b8a37b26bb551eb97061e5507ec8848681e807efebd3b96ba7eea42c9b927eee9bb1ab064bc16f92a6d1554766b12352d059d144d019d9874b20ed0bc28fe4d4840ab773e5f26bed046f701839fa2ea0726ec1e2ef4bc3a210984aba417da6ad2ba67cfcb41564cbe89b9a8c24af951808954788e3be16411bb7d2e53581e4e957bffaabdbe4677a74938c88903050e582f4a73ac9f7d26b7de55baa5fae65de19fbb7f810cd5caa79cdbbe9055d2b141476a1d8c3885d7bda017ec8dd69549650f4740cea4896f8452a6e09fbe6ad021b6bb92e73c49e57f4128f03b034b3919d0a71c72f6a584a153ae1591b25fbbf6e15798754c79aa53f46bb2c72ba25bd77457c639d37bf351fb0973817ee8a14f49b7efe1f8351d9e474eca711776c9d4b2f80a01e0889a1e8a3497dbbba44476a13c02cfed0b524896278c0bfc2ede44fcc65c1b9efe49e33a5ae340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec001a0f1a5877f11434609b67135e6df4fed2711197927b2f950d430f1f6133630317ca00fb410b65167655b67813c5f2fae1f0f0c638619df7c0dca84cea9ac249f5108", + "0x02f8b101068402321261850f665f461d83013157940581ddf7a136c6837429a46c6cb7b388a3e5297180b844a22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001c080a0f8a54ccda0ece2e5a204929251e1012df3edadc269518bdff0e7f7f4ca593249a016e81a85f7393e612d7da187e900eb91049de308c465ab3af66053df1a152b80", + "0x02f8b101038402321261850f665f461d830108179477e06c9eccf2e797fd462a92b6d7642ef85b0a4480b844095ea7b30000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a084e06bf2909bb553f239c628069e6c20635360500dd4173d5028ebd6524ff83fa05e04527a3c783d15e137489a74cd4bf68b7913f13cf5b0743df3c68b01563040", + "0x02f901f201058402321261850f1740c9c783020f0394762340b8a40cdd5bfc3edd94265899fda345d0e380b9018423dc86580000000000000000000000001ee2019472703d22dbcac3145801fd514394045a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000f0ef0e6a1fd61f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000c080a0fa014677216ad9bcb693bab30b366a9c2f8ab1247b20f480c6da1afb0e32533aa051687ec70efb085d30d94cccbc8a9a45e6609e8718ad8e7b079edce0911936bf", + "0x02f8b101018402321261850f665f461d83012e8894b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb0000000000000000000000009c036b0d0b39a0a411c0cd7df36211695103b65700000000000000000000000000000000000000000000008f4c4a17ac4a4fb00cc080a08d8c6c2c49aeae62f43940c12e58f5536ba7fbdc220c314ab497e75239bd678ba07c22d1c0bfe6d99cc349129d477f1964117e93f08a8cae3ae50d7bb02b849f11", + "0x02f9033201078402321261850f1740c9c78301352e94d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef0000000000000000000000000000000000000000000000000000000000013814000000000000000000000000eb661f50946347553a806e9aaee5bcfb9c8fc58300000000000000000000000000000000000000000000000cbd47b6eaa8cc000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000011b6b3ac0e6e42cf52b27202f6b03f9a3f2437bf5461304be35cc6b5ec4b21dfc0b4ed981b6908b70a43ff47851a37ca3257ac71c9dd8f38721784acf084bc62b7ea012460b205f5377bf2139e6804712c97f1dc6a360f19de92560c3e6d4971fb4a1de7b45fe51e2561b7410d9d3c068473cedaa81b54079e987ae664e6594ffb4fba75bbf5238ea11efdab4216f81103a5b4d2774dadabe17e661979086f6c38ce9e370619b475db925c715d75616759dc6bc50ab8e165d9ba012fe327063ae7e89d04777570f1d8d5f7d81cab855a0de5458fdb08350fb9c296bd4f24e1542f787c6412a2a82fc3a92835edb61bb023ce0f6c83fb7dcb9cecfb47183f58d30c8947ad445981bf9ed186485f714321853be4db42ce142369baa0790512b4e5ab9630148f919b4cb1785db3b6c3cc321a6c3e9167dcce46616075dcfb83a694c407ad21985c0a7b220c77ec1c4d2ba832cd5e2b80964ba4b60ced2b5967de3919578d7be013075f6f887937e87075ad37f49a82d0be743328564015ddc49e3faa3a2daa97f744d166ab5c9b170efc3e9fcf70d2dc968e4cba43ba29ed414753c8439869e225fee3df7bead8c9c011e7e36996bef45fc334cec3a7b3dd000cc2801845c13f25cbba3d726231691cc1678de0936fe89df8d408c9bc344d58d0ae0a46e70d0e09debcd1ba9ca8ff28fa3dcc7b1318cdac64c0b387c56304d964a2f86643f31da169680b58a8548f4d3144a13fdb0eb53a2844abdea2d4a0c76a82ddc080a0b0cb26b52bd73f1dfec7e0e428942e18fa7a7f9dd925b84c6e5d9add528b987ba06b1d2869e0ebe01dc7d0a4837a15c88cd2c9c130d5e6f1a45627f658d5c9a716", + "0x02f87301108402321261850f665f461d82520894fc1c0057ad6a3a645cccd87dfc720bc5cc1137d18803311fc80a57000080c001a0127ff81d34a94405b5521e4afba7f4ea406e9458640d10342364e6d254073696a05cf94fcd762510fb197cc9077483bd4b223d87265bbec688658a3bce0a072f56", + "0x02f8b20181888402321261850f665f461d83010ad694a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000f860c6f05b8b4ec01713f7b633e449d7842fe8d600000000000000000000000000000000000000000000000000000009df3ca800c080a0faa297d6e8ed0deffa1c0ae96c6ac7677eccf6963a7fec95970904fe5fc305f9a00d24e3bcaf3b750b5491f5efe90d1d6a5cd8dd519fd8ef6608f6a257a1e4555e", + "0x02f8b20182015e8402321261850f665f461d82d477949caae40dcf950afea443119e51e821d6fe2437ca80b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3000000000000000000000000000000000000000000000068a021666bf7e80000c080a0c49ff78e1ba68873b04719c0f3fba1bd8066327297575c2776073660fca27639a033d9e60e2da790459e57e8d9379a14b396d18c130697cea37b1a73362a7abaa6", + "0x02f8b3018207788402321261850f665f461d83010ed994dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000000cafe46342839aebfe3c1681858a63e7e001d12400000000000000000000000000000000000000000000000000000000c7ee7f3dc001a046371ed81f873bfb11de0d4395f81d2eee06539c322d325f10d41904ad7dbef3a05bab90e3a2258871f3f5feb86c8c3fb15126b04b3be7afbde919f4dd1a74e4a5", + "0x02f903340182026b8402321261850fae8bdf9a8301352394d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef00000000000000000000000000000000000000000000000000000000000054d2000000000000000000000000360b74a47f58405bdecf33811b1f2ca37dc6632000000000000000000000000000000000000000000000001e3fce3b96dbf80000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000117643c34564ee65554b935216a2fceeb1f20a5fa2c7ba3e0ad43d12495b0f2c779da74ed53b314419f0ee67673bc7d7c69faeeb8e6e145a3bab84e18546ccf9c388a670d0a3dd378436388898006318c7b29cc7b32e8e4d21e53d12415b8036e5b19ca4dfb85c3d81a67894640d96d9d04c35c25f6c7f2f0d4617511af7341d8b0f89e3d06b6b60eabea0c10c26f6c2f0ae052eee97dc56979d10934d667254f8ce58e2dbe02ac61991b93dd27420057aa9a5b788d2e6b26980e5b99ed22d62ccffcad20cb3a231eb9207b9d5ee49f21e33cc9fc9b49f5298ef95210b73031edff1de20088c7d63878caf5894054b43cbe49f36c335e2197241558d0f9320257667aca37604c499f561893e445d902b7350939f1d1c88cd4b50f402be2109027d457494fb896986cfe3f181244b524cde7eeb2d276a549017e92ccd0ab84e8579f2eaacc6dba099e82f6f93ad3d883a6a716e2fa31b35e9bceb0facfb2cb0129d7945c56895971f627fe10741a146767da1ac6d8523026e96448b5ba6cf8b2622cff34b45f6af108a4107137de77c7f9c0b613280b7edc9e12445fe2d195f2fed4cf815d551368b549329d7b042ede0c28c494ad193986346b46361f9af93ae17715588c2b8020767b738ded790362d7cc47224f2cf2342d412a9bf1d274da0ce7dbbba44476a13c02cfed0b524896278c0bfc2ede44fcc65c1b9efe49e33a5ae340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec080a011ab8a91c7641ba407d70b75dd58cf4bdc6c93f53019e2aa831e270c81979d5da04637e3711453560bbd56160381a063fa4f003e11f85de2c0291c52fc9931c65f", + "0x02f902fa0181958402321261850f665f461d8302e771943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad872386f26fc10000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acb700000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000002fb15de259f41666d587900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb86982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c001a0238952662913d078c6cc41f5c08a8ff28d7b9fbcb358bd419c882da293ae4fb6a062697ccd2dff92d0838a87f9a47000ee37c210fb12f0ce361706ed820459b624", + "0x02f902940182015e8402321261850fae8bdf9a83049f81943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b902243593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acb7000000000000000000000000000000000000000000000000000000000000000108000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000050d75868eb5367a808c00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009c7d4fb43919def524c1a9d92fe836169eaf0615c080a04b0829056df8d5a983429457be58ed9eafb0b5f8981135af1a046e703ec613fba024b45dcb081ad974933469c511b1b473a82f6492f182433ef647c219b0169d67", + "0x02f9033201148402321261850fae8bdf9a8301351694d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef0000000000000000000000000000000000000000000000000000000000004c340000000000000000000000003080feedb94968cff5d729fb55b45c247ea0c2d300000000000000000000000000000000000000000000000324e964b3eca8000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000011195b88203e4a838c3fb718e9c2cc16171d7b93cbfdcad09802188cf5acb2e8df6dd3e179fd476ca82414a6fb0402eb7e932871ff012e5acfe1682f6b1499ae29c1a734e4ba86efb452dbe3461e05c012708c648e33ff37cb9089d1ab8d0e3be68f6282c330423e1ef0039c384b96852eec776f20e32e2d7b5115e12ad6d06aa3ff426b08be3f4f00af57a3395f9e053a61b393ab060171dec92875c40efef1eb724ccbe9c6841f384292df597a050035e18db429f9bbcad88e66cbc50826d518f68208432406e8a3bc5214b9512a2879b0509efce80e9418149c93824ba5e089764d0168ff19e33702a6db13ccdab0faaeb78f06b164ba03733d50d19673b03baf510202f9fb2fa0341e20fbcccda0dd2c451ea8e8075ccaeed299da743b0d8478230e05e4cc67202d1fb5ed9cabbf75fb682fc5691e856a992e862676316e7505957b9a137898a6e3540b9ce378d86f6bf7bd68b963cb8eb6dc40a3941024ab4e0d0d831d1cd2d01aa888afbc977b387fefa75dd096c01e5a0e8090fce0f5085ef2cb32893e0a26c5d7f075f7ffa6f7fe9ea3258030ca715d74316c694bac6e9407c090febb3a91a6e3f839b6a54c1bcfcfc56dfba4d0ca8a3535168fa5b3b0866b8614da6d5761eba4253b79baf01b17cf793a6056a1204b3457acc3cc6264b2f1729c555dd226f68ac9786871adb4125748b57f44524ee6f108c017fa8db8340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec080a0a5b9c63ba5e1623e80d1e7ae86eb0e7e5fc524562675cc605bc457e71c257a81a03136b1acd168a591260c63c5978d2bcb8c57e25f9155ac3d6a970f17eacd5f93", + "0x02f8b901018402321261850fae8bdf9a83045f7f94d5ef0650ac086c630040981b604a0da99db03a8d8802c68af0bb140000b8442c65169e00000000000000000000000000000000000000000000000000000000000003530000000000000000000000000000000000000000000000000000000000000000c001a0365520ff5101529d569acb144a236b6a38a78e8fe965cd519ce0cf27eff2a1fea04882d68d6a99ca87973c6e964e4124eba2178f3790d1e3fefe97e43f2298ca03", + "0x02f87301048402321261850fae8bdf9a825208949508050753dc8290f0ee277b4fa4a6f6ef4a21ab880640b1c362f9253680c001a0eb00cad830ec22a2c8a0a31b15d2ea7464941fea00ab79aaa6870f9dbfaa18a5a01560f042fb9119ad01f0c43afaae30a4dc4618523e04064240f068f9821df739", + "0x02f8b001438402321261850fae8bdf9a82b5fe940ab87046fbb341d058f17cbc4c1133f25a20a52f80b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba30000000000000000000000000000000000000000000000000054f7866df32245c080a0778a20e2115ae2a7eac6c0de139fa6becb66b25dcc2592d3cd6d9355850e79d2a02f7a10195e35556da897c41fedbda5f9e414536c7edca6f7e4f567cb1d50e5a1", + "0x02f872010b8402321261850f665f461d8252089463c138ab7ae4e23838e144bbe30b1e57c1fb13b787354a6ba7a1800080c080a04f5ee5b0dd901bb31c48d8cc04e8d007f0ce2638338d30ef77df5969b0ee6d44a0422780cb971372138b8e98f841bf86081279726a0aba9ee6d0944a6e57fcf619", + "0x02f8b101048402321261850fae8bdf9a83012e8894b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb000000000000000000000000d539cbbc9b94ed0aa45ec11e8899ebecc4c3775100000000000000000000000000000000000000000000008df0a9582533a3062ac001a0446fd470dd2c2026ce8aad395e47ce5440816dd65beb06c325fca6dec73e10e6a032aaaa2f4091277f3b0250ab91868cf5cfc21cfafc3475542212c7297223b895", + "0x02f87201458402321261850f665f461d825208947035779ecce3e39c8a2d9b93e56c455f9d374a6a871aa535d3d0c00080c001a0fc5fa7e99a4312edfefcd871bbd93ed8f5764fb8eee2528209ec1ba2c071d163a02ee13da09ee628e447e7069891355a29cd038565909307eeb001b48dfe0ebd64", + "0x02f87301138402321261850f665f461d82520894740b03827195bc8514794228e198553d05da958388010bbda1790ca00080c080a019ad73040edff4b600feb890f422b67ec8c85d6b8a20688eb79d894a3ff41daba0427f80bfab9117fe63fde57cda3ccc006db04feff7f2ccc375a28ed4b9509e0a", + "0x02f902fa01078402321261850f665f461d83033913943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad88013fbe85edc90000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acab00000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000013fbe85edc9000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000013fbe85edc90000000000000000000000000000000000000000000000000019875e1fc7e1612a2400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000040e9187078032afe1a30cfcf76e4fe3d7ab5c6c5c080a0a55c8c8017fb1c975aa1b70377a47a6a5f9fa6c89592a46ff4abf56f7c15881ea071390df75d19121e064ff54aa7a4273a28571351cb087bee86e8a8aaffdc8300", + "0x02f9011901808402321261850fae8bdf9a83061bf794daf1695c41327b61b9b9965ac6a5843a3198cf07880e05113270345b6db8a48b886bf20000000000000000000000000000000000000000000000000e043da61725000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000d38c590f5b6d0000000000000000000000000000000000000000000000000000000000000014fe80d2652aa4ca20af5d837942e8914ef0cfb4e3000000000000000000000000c080a0760619a801b84b6895b003ef6ba2297c57fa99ae12ed38ac693d5c706913116ea030884a4619c877166355130809ca2389ff434e44bbfa602091535d181104fd27", + "0x02f8770181868402321261850f665f461d82b16a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2877c58508723800084d0e30db0c001a08a7489c7a293d32745bef66a0520f35b8bd4e3163e65f8178d1ba90430cbfb64a0455b4911858d8c37b35a02bb38d307f2ba552a5340dd909bd353d847c5ad932c", + "0x02f87301078402321261850f1740c9c7825208943215cbe32ea8ed8b814020d37679a1f3c1556a3d880de0b6b3a764000080c001a07f7583133d837914da04ad9ef2061992dd3e8be6d185e5c285872d4f4746b265a07716f7d4db4b8131bc9fda79c8cdaa7fffb5279158b7efc76d642c899cf709a6", + "0x02f902fa01808402321261850fae8bdf9a8302d7f3943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad880214e8348c4f0000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acab00000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000214e8348c4f0000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000214e8348c4f00000000000000000000000000000000000000000000009fbb4a25ab41e66571749d00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006e1649cef61fa17d647b9324d905c33fea71d020c080a0cddfcf6fe79417317dc0211e40f9ff1c3330f5b71035e6ec37a95b87d361abe1a00c369af641631aaee98f57f0ff8ad8788c608eafb962c20ac931c2f426bef1fd", + "0x02f8b1010a8402321261850e39ef311283010c1d94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844a9059cbb000000000000000000000000aa6f6b7cf8933c693d4f6db5ae1cc09ba46f040e00000000000000000000000000000000000000000000000000000000e1e56addc001a0d3506d0ae42af70ad1980d0427c536576d577c9178399ef129214ab5cbfa489ca0486c96e76a64be007467182af020afe697c31c8f38c00eccbf95607f28b5563b", + "0x02f8b001018402321261850fae8bdf9a82c95794b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb000000000000000000000000bdacd9db71c5d6693c459999011a42ef2338b9a70000000000000000000000000000000000000000000000821a476ac5f4ebdbcec080a0d62805f6da63d7748d6bf6580759260b0547e4b4f534d4b3bc9a0095afda522fa0488c353d4585e031f930c0f6d3b7b59b5a864298eca842d97bc1d540a463736a", + "0x02f87201188402321261850fae8bdf9a8252089441b4340518e7cf5c3b1c8aaa3cf12eb30f55ae3c8731406966e33d5980c080a04aa7eb08cee25ced72d3671b4ee325baf481425e8b4cd8ea6d6bef667bed825ea00e43cd2d919503b5cac05529d0e71d67bc4863ba4754d1a907876c74aab8b24d", + "0x02f87201268402321261850fae8bdf9a8252089417eb0db3ff2833bb8378ba07c2181182e043942f87581b77f66e000080c001a009c0527ea340cb11878b4a5131a382a7ed709102fac5ba1d7c71d593334d4831a02710b1644acfaa2d0d4c61220ea69b66545e0631a21fb1f7703331faba61e846", + "0x02f8af01548402321261850f665f461d82d5e8940fa0ed0cbe0412379cd181320c93448968c76c1c80b844095ea7b30000000000000000000000001111111254eeb25477b68fb85ed929f73a960582ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a0bbeaed1d2b16360bf416147f0c48880145f7c5b0047f62308e64b1bfb766d81b9fbbd0da1aae8d6ffa9bec0cc9996bee8ad52df23a3d5d5e9bbb7ea74d0ddb2a", + "0x02f872014a8402321261850fae8bdf9a82520894cdb464940593849637aeaef6dcee5bf84870e9ef8758d15e1762800080c080a0df64057ec01746587788e568dd88b08b359021909560a1c644af381d6e954321a0026f09f973356426576b3d1478b366b2ba2db214efc5948d890bf6ba6f8a728c", + "0x02f890012b8402321261850fae8bdf9a83012dfe94ddcf9101a653053caabb692de87e1f13396be09280a45926651d0000000000000000000000006521a22e4412450924294f8a46693ef4c7832bf8c080a0c4ca739952648435b50647e81ebe61150a130e8f1fdb8d7a862a9c1a14fa5e33a03eb3f4c186199c85874b4e770988b50574a5894597d833da03878469094b2a67", + "0x02f903b201098402321261850fae8bdf9a8301f0ee94c059a531b4234d05e9ef4ac51028f7e6156e2cce80b90344e1c8455d0000000000000000000000000000000000000000000000a6db469844dd54000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000a6db5cde0771dcf5b400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000012cdd74b9d5714df3629f3fcd00641d8d316e40279bc811e185f456a7808d155e36e52c93b17649808499dca6e9e56eef3a9683b8d153b8133f1dae24848d5aed7cdb2691f7d62ada7d47dce8a1f5ce763919ea139a6d50bb38767f4b00d9ff0c73cfc26e4af3c94e82c9393007e1dfc8a612143a7f63aab14622c2ea2248870d07f8ac339415f28673e70d865819abce96771946d9fd402d66c3a9dca92e7ac442e51f21b9ed332a971e452b7f4cc3e3572dacf58ef25d7691ddc797791c5379da7882dd925b43f381e0c13236402659c1496eb56a743375a3e46e7c84cfa25bbe0e4eb21c8aec1ea433c4435ddaed13c853e8fcb78ee48463b9b609cceea517dc2e6d92165ef7efc5f930d1d027d54b76a415d792516da7972a240898f2aa05cc7287899fa7801130a5cfe51000b39fc6274ec55e225c31b7bedf517a7853d95ce6f2dbb05e6f0f7c08b1309f9a611c1a4b497f62c72b491cb136b69985104c83a63eb04969561d7c36aef460924d0218bd47aab003dcfcb9f518e80f9eae339203ec2b486c6acd2e21b0bb2702207d3a21b35d4fb613114ee252ad6a97c953d7d39df87016ace7efeb8480eadf4df45f07bfeb149c2ccb6b25acbe0ca7ee734df4398f883c3ec5f9daa4a132dfa3bf9e0aa65c2fb4fefade4e7b7ba796d20dcaa9c4e09daa1573e7616f99d98baeb8da41b5022cae119a423c520f47a9a9f35dce35d50a6905ba18322eb5c4416a44e1e083d961e1e3f483dceeddb7251b8e8fbc617f85f57eb6ae6a910db84ffd7eea166608f0587b82a0d02fa84ac8abd81c080a0aced45c5a93c26c891be8df1ff0a9a48304722009579db27ccb96006d31e8629a04ada4f98c7048a3af2df6ef759c86ad77a1489f5703e8de8cb3924c3d6881519", + "0x02f90334011d8402321261850fae8bdf9a83038e3594881d40237659c251811cec9c364ef91dc08d300c80b902c65f57552900000000000000000000000000000000000000000000000000000000000000800000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59900000000000000000000000000000000000000000000000000000000001a93c600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563546656544796e616d69630000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000001a583e0000000000000000000000000000000000000000000000000000000049ce731d00000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000003b880000000000000000000000002acf35c9a3f4c5c3f4c78ef5fb64c3ee82f07c45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a8e449022e00000000000000000000000000000000000000000000000000000000001a583e0000000000000000000000000000000000000000000000000000000049ce731c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009a772018fbd77fcd2d25657e5c547baff3fd7d16ab4991fe00000000000000000000000000000000000000000000000000dbc080a0052aa2e2c98da00b5eb229fd856fe757a30e3343c88ab8f41dcf8092e0eb523fa066445249fd428f61e36a064bb5661f8d71e8e0e28ecbfbb16648d5f776d526a6", + "0x02f8b10181dc8402321261850fae8bdf9a82b56294c02aaa39b223fe8d0a0e5c4f27ead9083c756cc280b844095ea7b3000000000000000000000000367e59b559283c8506207d75b0c5d8c66c4cd4b7000000000000000000000000000000000000000000000000002e2f6e5e148000c001a0809603385b6eff734fbcedeece19358442c2140fe8f7f927db0626e2ad29116fa0505d09de9e5a2f1ae0e5740a0b518c47278abb398e2726bd47fcb47bc6edaefc", + "0x02f902fa01088402321261850e39ef311283034cf2943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad8804463c5e3ed20000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acc300000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000004463c5e3ed200000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000004463c5e3ed20000000000000000000000000000000000000000000000000049eb04a6aa2488eb2d00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d584a1edb3a70b3b07963f9a3ea5399e38b136c080a01f3c164efc72911c467fa444708a983edb6753bc1c2f38944a8e21f033e2b724a06da500c1a4f8eb0d601dfe4ed057dd0b01932e630b2e8ab97f13b54c8bb80030", + "0x02f87301168402321261850fae8bdf9a8252089494e23ef804909b4c85d90489aaada343bafb93b488017508f1956a800080c001a0a581e17beea46b95ce6d4d415e864bce39a63580817e83dc6c1ecb7229301003a01de35e5fd95f86fa37bcd2d79d29ea1ec2e0a24056aea2aacf91d1b2bd203767", + "0x02f9033201138402321261850fae8bdf9a8301350b94d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef0000000000000000000000000000000000000000000000000000000000009e8a00000000000000000000000064f8f38c4e39db50f76809acbf946bd6768818a6000000000000000000000000000000000000000000000007ea2832757708000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000011459ea156e342e9957607f69e5470ecc5750e233afdba5d26c09030180b09da68603d47bb83621572e33c6844d33c22ac6d9aee8d5e1df7aef7e9bb1d46f865af97e740e3096213cb9ac6046ac8c61367d74cae92196249bed25bf9dab3b14e812c67d0a792097b6b04157c69853966a55f7647ba08909f37952cf89bb3b2dd128f74a429e5306e7995f593c421da51e04d4bd0ec06844152144a84829b95b13d2a959f83fe00fb9fcdbcee3830f1eb3b4fc0577e442970ffe4242501f6e072bff5a862cff38ae60778ae4b561ed9a3fc4fdaa2d3424198a3ef80e5f8c2836211b1f9d7d013ded25f0395e37a937d83965dd2b8db715805cad78958f053f40900abc2decfd6081a33a7a8e2e1836f94ef10a167146f67afa9b85d8daa37aa36ba4870774461ab6eadbafd2c2abdb849249ccd5af4144df5443ebf2dfd342e553f789a8a5986783c101f8ef7e11a253d87a07ed759ef2f71e05bbfe394f43e68284e9248e641ac65ef0d5fdb167961f5e76aa1d90d2eb49580b5470dafb3019a3cd786957bb72b9c8b91cdd7cdd256f1f7a3c401d12ee4f26e629afc69ffb97c8ee4df041f6d001a4c29f64bcb7dc7037b295e66ea154b33e6058d3ef6551f90fc7391105bbfc6265234e9cebeffa9b2e702d2341a1364e88c56b2fde302cbd6fdb2f1729c555dd226f68ac9786871adb4125748b57f44524ee6f108c017fa8db8340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec001a0c99925d1128e1289f412238946c5b481ab27e59fe9b07d81dec02a78270c493da0572e4d32df8e4524232dc684686367909abd423091dc0783e1792daaeeb23e27", + "0x02f8da018203518402321261850e39ef31128303f0369445e563c39cddba8699a90078f42353a57509543a87023ce4e8c9728db8642556e453000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000010c97e6672ea772e6417b8cf679f4751f4006a80000000000000000000000000000000000000000000000000000007ff2fb10a2c080a0800719cd161e4acc2fc714942f46e0f45974d107a20609a69403bb44e8e4432ba05d41cc8ca167f8f5b3e6ed3907026ce84be5b384c0fe17df1e234f300f5f9644", + "0x02f87201018402321261850fae8bdf9a8252089440d6bb06eaaf0bd7515b484990874d74c9f7f1ea8703468b8360cd4880c001a0591cc2128d0b836f61ce7ad5591243f23aed0bbb644346ab3cf0faa5b6e81847a06faa75e33018613337c391be9ba715dfdd4bc8edd84689f50086978b467d417e", + "0x02f902f201588402321261850fae8bdf9a8302d102943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2af1b0000000000000000000000000000000000000000000000000000000000000002000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000bc437a25d298be5d000000000000000000000000000000000000000000000000d92dc384510bb70500000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0000064c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000d92dc384510bb705c080a0d5b0f49bd4594272a90dc96fc681c68dbb3aa2790bf00bb94a66b32ec9a72e4ca007b2aa900824a87c39aeb6de0952886529208513d69dda2aef071c023145efa2", + "0x02f8b001218402321261850e39ef311282b8f3947865ec47bef9823ad0010c4970ed90a5e8107e5380b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba30000000000000000000000000000000000000000000009a2c7453e23bfcbcf5ac080a0cec872d692a946f01c84c45013b8f29b1b43c0e73cc67d6e4c7545b013955b36a00845f954b1e99117e79d15325fad9352c5a35f71f2aa51f8508c6b0fb071eb94", + "0x02f8b20181e88402321261850fae8bdf9a83034129941fde0d2f44539789256d94d1784a86bf77d66dd080b844e2bbb1580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5949f4826178dc0c001a019e33ac01e4b6135deec9db4707428e16e9dfc9512061c923fde0f7ce836449da032a26b7be24065311f39efceadde5b10eeff49a4e9dbf78ae9395a095899576c", + "0x02f902fb01820bea8402321261850e39ef311283024cc3943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad881bc16d674ec80000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acc300000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000025381c2ccdc3c8eab7b6f590600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000001ce270557c1f68cfb577b856766310bf8b47fd9cc001a04a470a8d5731aa8dbf3097cea4a6dcd9c2bffc97b29d0b39892709a5b95ce2e49f1c17299853f33cf2bf3554b6b51bdd22eb18c1df6d22e393a639bd943db58b", + "0x02f8b101018402321261850e39ef311283012e7694b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb000000000000000000000000019f57fe47ee03764ec59e62512bde462bcbfb9300000000000000000000000000000000000000000000009ebb79bcfd6b808000c001a02c310cc3f06fc46e1b5dd7353bf6ee48f4766d1bdcda2e321807dd654aadd55fa020cce70e17f0b0195a71428854d13f44f6934955a6171bb120af149c5a2f168f", + "0x02f87201688402321261850fae8bdf9a82520894b5046b48d661d23262f00192c802dee3b167d4618710d807fba35dbc80c001a00afbb3c2fe4aeaa879871d537868ea6ea70ba36b0dd072eb1edfccf79316bf28a05cec9fac287501cc4bff0b9e7e07850647222d7be48f4cc46c397abab0bdcf7d", + "0x02f8710181d38402321261850ba43b7400830747ed949e87a268d42b0aba399c121428fce2c626ea01ff8084f9fc0d07c001a07544666461b880044a9fc37bbc7881c9a0a52ddb441aa751b5b612fe9fdfe8d0a05aa76d304d32185eb7b639d8ce415533da40d666c5734366c58803dd024ffd30", + "0x02f8b101038402321261850f665f461d8301771c94dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb0000000000000000000000003e62edf533e5e24d759500a181d1a91358b91f0d00000000000000000000000000000000000000000000000000000000b2d05e00c001a01c7616624043c0604cb53f6e7e92afac9951bb2a8ec76b588b473b5c8cd41845a07c37c73160dcdd4aab110ad23a5cb635599ded0b788bbae64acd8a0cd8286726", + "0x02f8b101048402321261850e39ef311283012e8894b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb00000000000000000000000080aef925cdacbdc872c86767e2127876f135603400000000000000000000000000000000000000000000008df2be4059bff2062ac001a07870f8bda58efb84836fbdd5ee18243dc6b490b0e62dc18421b002b4fa4ef060a0386f94ec97920be5c6973f9610af782ddaa582d159505bd5b0f8d83749a44792", + "0x02f87201018402321261850f665f461d82520894520d450e6888eccd6feab7ff7e0877f848a88b5c878686273f1dec0080c080a0643531f92d8b90845906b743483541a3f2ee552eb07fad8540b32f5d5c21425ea0405d0110a52158aa742e7bd30c57688c5e771e95021537ecfa14005287748dd6", + "0x02f8b3018201458402321261850fae8bdf9a83011340944c9edd5852cd905f086c759e8383e09bff1e68b380b844095ea7b30000000000000000000000008707f238936c12c309bfc2b9959c35828acfc512ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc080a0dff9745d53eb7ba30969d879635db561f9e1de02187bda51e8fbbdc52ba82c4aa0598533e3de1be5bdf61f20031991708a7329c58d8dcd5a2ba8883706b604e34c", + "0x02f902fc018201ae8402321261850fae8bdf9a8303a887943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad88018de76816d80000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acc300000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000018de76816d8000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000018de76816d8000000000000000000000000000000000000000000000000000000000126fc0ec1a300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000055a8f6c6b3aa58ad6d1f26f6afeded78f32e19f4c001a0e9c37333cc67dc844f0c0d9601858a218a4ea7a88031bddd9d8825b53b57f7c0a054800436e1d309c9e0e24af54e760cbaeac078e7cc828f6f3841671a3b918116", + "0x02f8b001428402321261850f1740c9c782dc2994a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4880b844095ea7b300000000000000000000000002950460e2b9529d0e00284a5fa2d7bdf3fa4d7200000000000000000000000000000000000000000000000000000004a817c800c080a0ecd1ef10f6a488aa9e47a55ca4b386617aeb0f05b132504cceadf93cbadaf782a0682ea66910c9794b9cc5ba164bc82244513e767a5448c3a3e479490e07faee29", + "0x02f8b101808402321261850f665f461d8301312b94d9016a907dc0ecfa3ca425ab20b6b785b42f237380b844a9059cbb00000000000000000000000090512978adfae4214f93cf1fd9a2f8cbeb787d48000000000000000000000000000000000000000000001503c1c8c53e5e3c0000c001a00b75bf0fee9f057d9238c9cfeb683b35064d390751ef2893260ebcce73167662a05da2a183650767de2471f7e82682bcc6b0d022a8a73432b8d6d195d8a68ac75e", + "0x02f909fb01819a8402321261850e39ef31128301d8c49400000000000000adc04c56bf30ac9d3c0aaf14dc80b9098cfd9f1e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000070effa80cdcef37d51bdf4d21687aea75a0197b000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065f01a44000000000000000000000000000000000000000000000000000000006613dbdc0000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000bf53845b1aac54050000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a7d8d9ef8d8ce8992df33d8b8cf4aebabd5bd27000000000000000000000000000000000000000000000000000000000002dc743000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019faae14eb88000000000000000000000000000000000000000000000000000019faae14eb88000000000000000000000000000070effa80cdcef37d51bdf4d21687aea75a0197b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b8bdb97852000000000000000000000000000000000000000000000000000000b8bdb978520000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022a392c68f60000000000000000000000000000000000000000000000000000022a392c68f60000000000000000000000000006c093fe8bc59e1e0cae2ec10f0b717d3d182056b000000000000000000000000070effa80cdcef37d51bdf4d21687aea75a0197b000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065eb0b6c000000000000000000000000000000000000000000000000000000006613dbdc0000000000000000000000000000000000000000000000000000000000000000360c6ebe0000000000000000000000000000000000000000ae0ac953653ac3130000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a7d8d9ef8d8ce8992df33d8b8cf4aebabd5bd27000000000000000000000000000000000000000000000000000000000002dc743000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045f1ad4c03f8000000000000000000000000000000000000000000000000000045f1ad4c03f8000000000000000000000000000070effa80cdcef37d51bdf4d21687aea75a0197b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000000000000000000000000000001f161421c8e0000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005d423c655aa000000000000000000000000000000000000000000000000000005d423c655aa0000000000000000000000000006c093fe8bc59e1e0cae2ec10f0b717d3d182056b00000000360c6ebec080a0f914337603ed396f49309eb469913d540bf8b219865b54d9f0bd6fca858dc4f8a059cbfa1bbfab5e2a64c869035ba9bb9ed4b38c65c8e9d56729b2b887da0848d8", + "0x02f902fc0182011a8402321261850fae8bdf9a8303850f943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad88089aaeb710be0000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acb700000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000089aaeb710be000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000089aaeb710be00000000000000000000000000000000000000000000000000021fd8d54c8a1a6aa900000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000092f419fb7a750aed295b0ddf536276bf5a40124fc080a02ca1283699af78c33a3495a916e6d2c80952a207d5b6b1926680c8189db422f1a075019e52fd77798dbf46645ec64a3ab0cc7a210d1b2af04b9a01a249f3a46cbc", + "0x02f9033201038402321261850e39ef31128301353194d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef00000000000000000000000000000000000000000000000000000000000187ed000000000000000000000000f4911146e6cdcc96ae815b56b8388298c537738200000000000000000000000000000000000000000000000657b3801b80b400000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001142bbdf020b113cf51b28f72251e1b3e56af83da9c0048420be1c2270b1ab3c46e509b711b43f1232760657f7251da5ecd0179d570b573da53980346917fc49c3ec343469e6cf9e24d3051c5a6c4984530ee0c901aeb1cdcbeb4253532843b95dce9231699f13e37785baf293bcb31782e86a18d029eb3b38f04786be62c1c32c403345924a187b9a26fd82a08b0bf266f98a6fd9911e5da8b1dd0b16ce1df0ea8468f44289d07a72c1c4f870094d10788d0fc84d8ea6a967cf0b7f61d4ce72aab9b8aac9c5cccb7e2808a1e689ec061ef63333593533be416dfaaf74b9f90c5aa83c6ff7ba48fb889e7d24d2a36f49754e03788b8b36c31706f71e2db2d2235e2c6d99045e917b4d3dda6d9e293b307ae53327b1820a3cf8a0c67605a5f289a2928682849565f421514661d8715330ad1a241928bf8d83523495ec2c4ebcc795a631ddd37b735aee465dea76255e51aea695a97df82ec9785ffb0435d4c45ef64e9248e641ac65ef0d5fdb167961f5e76aa1d90d2eb49580b5470dafb3019a3cd786957bb72b9c8b91cdd7cdd256f1f7a3c401d12ee4f26e629afc69ffb97c8ee4df041f6d001a4c29f64bcb7dc7037b295e66ea154b33e6058d3ef6551f90fc7391105bbfc6265234e9cebeffa9b2e702d2341a1364e88c56b2fde302cbd6fdb2f1729c555dd226f68ac9786871adb4125748b57f44524ee6f108c017fa8db8340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec080a06f23a1578049eb8f39cee2f5e7ab89755d486ca0f2bb392985845e4c4cf50de5a005c159f86dd33eb5fbbc258a91909e2c5a367d5a0838c8c05df21c0dd56ebdc6", + "0x02f90414018202ab8402321261850fae8bdf9a83039e7d943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b903a43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2acb700000000000000000000000000000000000000000000000000000000000000020a080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000661a376900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad0000000000000000000000000000000000000000000000000000000065f2b17100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000417ffcaddf095b0303ee401928a659fc673ce89f80aeeef0cc0c940335de8bc5f05c9e9888d3f054c1662a4a4504bdbd088d49d4b4e56f7f5b9a7c124e4e8e26401b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000002ee250abdfbfc88c000000000000000000000000000000000000000000006362d1c07eb49eb6f68000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000007e128e823d2b9b22edbda43820aa1a72de99613c001a00aa1cfdd24173f8776d9857822e57c41b9f134bc674988e8f15f8d52d93b16bda00d033b3fb17d3278c05148bec2223404932e8a662d6b969a4b0ce78dff568c62", + "0x02f87201078402321261850fae8bdf9a825208948f9a34c5655d655e0c72cc2ac2baff6237ec9eae8732f2c07088bd1a80c001a0716bf0fb605692dccf06dad462b78f51e4259d49af321ea21642482da596eb15a075df8c5132be45ef1575c56959f8b660204b79a5654044a8351a1059d1af2acf", + "0x02f87301808402321261850fae8bdf9a82520894adb6505de4bf5f7e87aa54eb1586518f81ca0bc88806f05b59d3b2000080c080a03feaceae0bc6a141cedcac08f607562e6614693e2102f97b4273b0233947245fa0020fc611bd8310bd2caf5af4aee7be7fae83e67d82f8c643614748e162837486", + "0x02f8b101378402321261850e39ef311283012d7a94b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb0000000000000000000000006559ed7d1e4c8eadd407b99743a04041b947f3ea000000000000000000000000000000000000000000000014b550a013c7380000c080a0a6fda06cb1ad38960010baaa8e7c4e96991887ec17dd57c044e98bb1d72d15f6a03a93c21f8104f447a94373429ecfcd0328ee4c5c40502a07189f6daeefdc89a2", + "0x02f9033201808402321261850fae8bdf9a8301352194d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef0000000000000000000000000000000000000000000000000000000000015e12000000000000000000000000bdca2d6d76fa87f07da633bed8aa5cd99f54147300000000000000000000000000000000000000000000000324e964b3eca800000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001174faf49ba37521b6299a3e122ffd0c10f1cb000b089bf1f3ca1d896634420e0691d721072dba71ae5aa4ab9ce65c43ac04be3884f2c58e67935e85d27afb9af6cec04ef55922a0b77226360667129b02d0aa526ebe84b4eec5065ac6a7a333f33e55412762011e4b76339829004ad810d11c3abf195f033ca723d9f1afe020a7fe6742148d063a197db588ffe6f1f98308511193cd1250041921bd29c43642a873e2796b2e2b2b4c199648bd88333d05da6d9d31e581323c5dc91984bb2578edc41d6274dc9d1831cc5d28ffca4132232e3943c7bddc8c466177e72e1f1340f9b46b02646e19bdf59d0d52c54d4c615453fc49575b3bafb7fe2c766e53f30e960306f3f0072da85007681ae3604fed525fb716fb74c09dd3a161c5d5338a15a5514fe5f0afdadb1a2e80520463204153130876ae643e71ddfc06db3fc9fc8c03ba74cd8b2f9eccd6cba6389f38e034aeca7d8689cb1654506a5098b9022733994e77e2b564c62994d516b9cd40d483a9f948b20974ee4745b46f8c6eeaa31b67cff34b45f6af108a4107137de77c7f9c0b613280b7edc9e12445fe2d195f2fed4cf815d551368b549329d7b042ede0c28c494ad193986346b46361f9af93ae17715588c2b8020767b738ded790362d7cc47224f2cf2342d412a9bf1d274da0ce7dbbba44476a13c02cfed0b524896278c0bfc2ede44fcc65c1b9efe49e33a5ae340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec080a0faa5d5268e85876488a433c7ca598406ea6290ca654ec1ab81f940d92c7d4cb2a01d57b38c084d14169ddfaccd7aa759cada15caac60636d439c5b9b5a54477f55", + "0x02f8b10112840220b02a850ef8b4cd3b830131f4947d29a64504629172a429e64183d6673b9dacbfce80b844a9059cbb0000000000000000000000002451b9dcf9b1279f560a9f586899c8e6c2d0d91c000000000000000000000000000000000000000000000002f6d89368268eaacac080a00d764ca9ae21e18e30e5fbe5f8dcf3a29de9878fbbf07afaf857c695e88cd148a02456f3d685688211923cb9c212ebd4a83b6af84506c5e908734085701d3a1dad", + "0x02f8730102840220b029850b68c6e97282520894aee4d040e7d1f3e6f4e007c45d288c4b7bd82d98880275aa22c24bca3e80c001a093b213f5aea575ae7d2473c51f7e174a54079008ed490b2768fc2c0583e49bada07dfbb0bf9f03bd8aec322d58568fbff79ecada55453fac8e5ce338c72961fd6b", + "0x02f902f90180840220b029850b2e2b96d483031c87943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad876a94d74f430000b902843593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f2ac9300000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000006a94d74f43000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000006a94d74f4300000000000000000000000000000000000000000000000000000000002394e7f30500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000014fee680690900ba0cccfc76ad70fd1b95d10e16c001a0dedd004eaed6394dbb0ace2360ad8aeca6761edd5c37942e22e95d002e2f5ba0a05cd64b717110f7a325753d35e090440ff008a16c4c1e219a3eb296a95dd1d704", + "0x02f8b10102840220b029850b2e2b96d4830221649406450dee7fd2fb8e39061434babcfc05599a6fb880b8441c5603050000000000000000000000001bd63db8c10a2ce6e5b446307eb4844138abfc320000000000000000000000000000000000000000000000000000000000000064c080a056cba33abbf6f0687c5395725cd3fb8b8cedf8f69a0238829264ff5b683d70cea0253e6ed353b10fba09ee59b257251955d3621710e6ed2d8e8f30e68186c0f8e6", + "0x02f903320128840220b029850ab5d04c008301351894d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef00000000000000000000000000000000000000000000000000000000000087b90000000000000000000000005623caecbf58946a0d48ff9318fa2557182240ab00000000000000000000000000000000000000000000000657b3801b80b4000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000011c46b990f036ae2d0bbc6f92afbef84165af0752306b24f59fab076ade5e33a5bb726ec2f8d8c06c8ae03285ff6187f299427706dd771227879fde474093dc2349d5ae5b08451bdc4e4cda8dff3f969a3450bbb5b85ca063750ce970dc5c45e3c7ee42591b0a66796986b0b98b1e47553393523eea18031c24ff7846394351ccb3d712b4046720470c6efeaaf6da20396728830864e458d115bf88a29e3982af2ed93ff9975f5c637adfdbaffa3969289b0cff21a73d5231e19397e6d9ae0c727eb3d2dd45dcf6dd79cfc42f7c84a46d38c4b047067cd029a20cb587d6ffa5c0b10a92d06fef594506e64c6ebb47736315b9db59c71d8b280845419e9e3b9006c5de4e53809eb97fb41bd529c09e512d8a72f7272f197ef95b352aaeba30140aea49b85e2162932173820a2b7154b9e0426201f0083910ab53ab7319ac686a56ea2260872ca3292b36fd35b2c10ce42a040de5744769e8e38cdbf33e75f449986bc2ce833985f7494bb689e96d7e4e4d3d7abe974050379da073c7b9796b00261ebb67e5256edc37569e7e11581c7e07b8d53d41a78984e41a812276041dbbdcbe65e3e9fd7ef63acd4ce98bc0621334261f731945adf4118d41eaaa662eeca3f1845c13f25cbba3d726231691cc1678de0936fe89df8d408c9bc344d58d0ae0a46e70d0e09debcd1ba9ca8ff28fa3dcc7b1318cdac64c0b387c56304d964a2f86643f31da169680b58a8548f4d3144a13fdb0eb53a2844abdea2d4a0c76a82ddc001a08707ba7d8a4348edac70661692dfc15d93998f970314f5952e03ea9ed637b07ba0571ab50ff584286e9020318ac277cfac528c7c6a038e37364f022f2f9619d143", + "0x02f90234018207bf840220b029850b68c6e9728302208794af9ba9f9d7db062a119371ea923ed274e398116380b901c4d4dfd6bc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000065f2ab75000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000415a4ea69da68b9aacdadba48b130c887c2285ec857221ba923063a0b0bfbccfa801484944382b81aee3537c71b763893b6385a363f2395e9ecc7f3ecafa899fbe1b00000000000000000000000000000000000000000000000000000000000000c080a0d234d32bdc0d78898539df7f6abe9c13342ed4b36cab9fbc5ff9d23385a844e3a0017d643dfda52cb190cf237999797e46e405dec578497470e4e185c7f9e7928d", + "0x02f8b10102840220b029850b68c6e97283012e6294b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb0000000000000000000000003996989b5ecb2e968e9cbabf1666aaeae21173fc0000000000000000000000000000000000000000000000914e05bea1d5080000c001a0a5c46c0bac56c65f6de1518e77158ade4e4e64c89c1e50bca25271413ffc6d7ca033d8a84aba11a9409fe9891ba8fb23334c22020bf9f62c078f3edd9c0cc272e7", + "0x02f90332010a840220b029850b68c6e9728301351594d4b812dd7134f632c947ca11a2fb0f49082a248380b902c42e7ba6ef000000000000000000000000000000000000000000000000000000000000cb9000000000000000000000000081a10688b294747d4cbc97c3cbcde68dd81b79fc00000000000000000000000000000000000000000000000b1cf24ddd0b140000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000118931c013269fa3868e5ebb9f99a2e634fb1b5500c953ea97325c997d3788aa715f1ff830392a163d437f6c1fc55e596da7d7b1571c001b47262fd979992ab1c972c01fdc16a5a86df15b6278833290870f4a02a990b99f2ddbc0423ac60bac0d50a3e859b29dd14f494ab8a77ac7aa36e39e0d9a39db658daa4c2994b391f75dea97cbb74734d1739d758e528c3de92aeaf9e191f9439fc2f59accf5cbae8eb10e8f84a19a8e363db712a02051654e15ae78ae31c8585180234e40ba9a0698f1c0fd87dfbaf9df473f78f39ddafcad6f2eead4fe54824f8bc3dba6df6fc327807fe9125520aa869739d06ee7e4169e75871e4325e2b642790c7b3a8090cfef4188fe4b376d05bc1d12a1bc91367d4a3eec9b43d3a66afb8aa29c443828e197e473bf64a203c428b430b04056ff7a1d5aec62aebc5f5df8c4260f29d8b342ab081e31b1fe1d272506080926e8adcc56748a87e4ffdb8e78688e89ff7a7e518aa921a90b94a8d5891299de2a8fe58144ac34ba0b0f5226bbe54fd2cffaaba4bfcd67e931175e5b4c8b0a956f3d164cde10179a7583ba29bee3eadbc2acd1f0d6df9677a941d7cbbd94ffdf154c6736b146b862f9de6449d0d550ac055f19596abe3817ee8a14f49b7efe1f8351d9e474eca711776c9d4b2f80a01e0889a1e8a3497dbbba44476a13c02cfed0b524896278c0bfc2ede44fcc65c1b9efe49e33a5ae340559eadadb3e2ca3d8f89f796bebe7b6ab26f40914495b23cb29e64b2dd29ec080a0e8238473c8e41ebfe341df72d402c8c0b35edaa96fa9393586b2ef1cc4057a16a0166dc8645fb72d8818cc7dff6bfe393b3e4a0cdfd959fa7377e7cb879c762c6a", + "0x02f8b00104840220b029850b68c6e97282b68c946982508145454ce325ddbe47a25d4ec3d231193380b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba300000000000000000000000000000000000000000528a0c1763c7ee5b9ef0000c001a0664178682c745e9b3f397333f97dc61599ff9ede8299bdfc15b8bf868dcc2e14a03a497d31c3c1bc9b12577d2d111916ac7a4f0eb2307950fbfe205bc5aa5ef3c5", + "0x02f8b1010c840220b029850ba43b740083012e7694b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb0000000000000000000000002fdc20f6fa8241734a75128c5fefad18abff48770000000000000000000000000000000000000000000000948751d51b30617400c001a066098701097d00d5fd400901979998b94f436f08bd4d50d07d534ea16a123741a064bd8771fbf422264534adb9f40722af08e4721235d1d39b9e0dee2c38cb00c4", + "0x02f902f401820161840220b029850b9e3d482e83048d0b94c36442b4a4522e871399cd717abdd847ab11fe8880b90284ac9650d8000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000009cd19000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c000000000000000000000000000000000000000000000000022bdf74a5001413000000000000000000000000f333087c317f6c7eab71af59d894edf55f79feb8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064df2ab5bb000000000000000000000000767fe9edc9e0df98e07454847909b5e959d7ca0e0000000000000000000000000000000000000000000000002fac65a3525eac0d000000000000000000000000f333087c317f6c7eab71af59d894edf55f79feb800000000000000000000000000000000000000000000000000000000c001a0437d3d1e56edeeb5d6dbffabb5de5b3fe60ce3c72bee37dd22df1cadff2cc2f3a02c3b6770de93dd30bddc41a06229448576b643da3a9d592cb0632b1dafdf1c7f", + "0x02f8b0010184021058f5850a8a2aa89482c95794b131f4a55907b10d1f0a50d8ab8fa09ec342cd7480b844a9059cbb0000000000000000000000009dcf825b01b3635623d901aef85cf431f09f79fe00000000000000000000000000000000000000000000007bcce5b6ed1332dbcec001a02308e4661a772da3b543bfabef345ef466e09dad64c74ad0867d8f2b8074e885a034911fc9e300f431b0fac2f0b9b0b21dec47655ad9b545d3a2efe83f56c01548", + "0x02f8b10181ae8401fa70b88516cf46e34882c8f094b528edbef013aff855ac3c50b381f253af13b99780b844a9059cbb0000000000000000000000004a300e437f98009b4a089bc9b4abdc135a7147d000000000000000000000000000000000000000000000000cbd47b6eaa8cc0000c001a02261daca00b379393cbb2fe127b15d8778a4b7e36430eb335b2bc16a1c194feca010a19a518c45589b5c4ade62ac228a50920722d5b7360f0d333c523437336185", + "0x02f8b00181a383e4e1c0850de7dc35bf82cb1c94614577036f0a024dbc1c88ba616b394dd65d105a80b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc001a02703d70b06b6e19a6db9be9479616e6623784db3b46ff994e360e8e572a59777a0248227295e09c5937b8a0a740bff7bb218f3e0b42bf9f195693c9e9466d554fb", + "0x02f8f2018216218398968085111ade37ac830301339477f0de655885dcf6b6942ea5b3de171dfd3f5da980b884eeb858b5000000000000000000000000000000000000000000000000000000000000997e000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000083bc1b65fb8397718c9ff6a786ec9d0117a0b6e1000000000000000000000000000000000000000000000000001c60d39552429cc080a04e9b2f821a25e502f584ac52716daa3837db0a353a5b61895067474c2b203a65a07220680fd91ce369e32cb25d85eabb51bcc001a83593e17a4f89e14ae2abcca6", + "0x02f876018309fe9383989680851510cd35be83015f90943a72e72939c80ab9413ad9c6ca790ed46b184a17880484a65efe3c000080c001a0f48c69a77f0fa660f97d4f64cdbbbdb5948e987a15088100a81562c069418126a059ffe793bf3e17d0d02fda78bc12adafd5b22d647a20f42f2570855dd076be73", + "0x02f8740183051017839896808515699cce3e82520894f48e53fa5cadd0728aaca90ee7e1c6132020993787175f60d3e2c15c80c080a0534c6a9d9aaeda4e9704b80b44339c040885f0d6bc5106c8c141aa487d7a841da06515ff0bebf9cf9c5a5fcee0f0dd7673bfce41f5b0e91cb0550a7e8ce7cdf818", + "0x02f87201830bbc2a80850a5254153d827d0094388c818ca8b9251b393131c08a736a67ccb19297880185a6d6be627f3280c080a038cbd7a4589d49f061b88da94c6d16e085faed4ea61f60a744d781bea95862a3a061eb4b1dc235a3fdb04c589150326d7e49089439428c22ced7bc7ddb559edd37" + ], + "withdrawals": [ + { + "index": "38350022", + "validator_index": "171011", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18545672" + }, + { + "index": "38350023", + "validator_index": "171012", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18561699" + }, + { + "index": "38350024", + "validator_index": "171013", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "62582115" + }, + { + "index": "38350025", + "validator_index": "171014", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18489815" + }, + { + "index": "38350026", + "validator_index": "171015", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18546820" + }, + { + "index": "38350027", + "validator_index": "171016", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18534476" + }, + { + "index": "38350028", + "validator_index": "171017", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18539498" + }, + { + "index": "38350029", + "validator_index": "171018", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18573549" + }, + { + "index": "38350030", + "validator_index": "171019", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18486098" + }, + { + "index": "38350031", + "validator_index": "171020", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18511761" + }, + { + "index": "38350032", + "validator_index": "171021", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18501732" + }, + { + "index": "38350033", + "validator_index": "171022", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "62418961" + }, + { + "index": "38350034", + "validator_index": "171023", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18490087" + }, + { + "index": "38350035", + "validator_index": "171024", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18515931" + }, + { + "index": "38350036", + "validator_index": "171025", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18534555" + }, + { + "index": "38350037", + "validator_index": "171026", + "address": "0x8626354048f90faafc212c07ec7f3613406b1b32", + "amount": "18550274" + } + ], + "blob_gas_used": "131072", + "excess_blob_gas": "0" + }, + "bls_to_execution_changes": [], + "blob_kzg_commitments": [ + "0x97d62d4572935295f909f243714201d9221215bfcc91af6546d28d2e52040577a77957256c530ca25974f6a814511b1a" + ] + } +} diff --git a/build/bot/macos-build.sh b/build/bot/macos-build.sh new file mode 100644 index 0000000..f1620bc --- /dev/null +++ b/build/bot/macos-build.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e -x + +# -- Check XCode version +xcodebuild -version +# xcrun simctl list + +# -- Build for macOS and upload to Azure +go run build/ci.go install -dlgo +go run build/ci.go archive -type tar # -signer OSX_SIGNING_KEY -upload gethstore/builds + +# # -- CocoaPods +# gem uninstall cocoapods -a -x +# gem install cocoapods +# mv ~/.cocoapods/repos/master ~/.cocoapods/repos/master.bak +# sed -i '.bak' 's/repo.join/!repo.join/g' $(dirname `gem which cocoapods`)/cocoapods/sources_manager.rb +# git clone --depth=1 https://github.com/CocoaPods/Specs.git ~/.cocoapods/repos/master +# pod setup --verbose + +# # -- Build for iOS and upload to Azure +# go run build/ci.go xcode -signer IOS_SIGNING_KEY -upload gethstore/builds diff --git a/build/bot/ppa-build.sh b/build/bot/ppa-build.sh new file mode 100644 index 0000000..ca04262 --- /dev/null +++ b/build/bot/ppa-build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e -x + +# Note: this script is meant to be run in a Debian/Ubuntu docker container, +# as user 'root'. + +# Install the required tools for creating source packages. +apt-get -yq --no-install-suggests --no-install-recommends install\ + devscripts debhelper dput fakeroot + +# Add the SSH key of ppa.launchpad.net to known_hosts. +mkdir -p ~/.ssh +echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts + +# Build the source package and upload. +go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder " diff --git a/build/checksums.txt b/build/checksums.txt new file mode 100644 index 0000000..15c7455 --- /dev/null +++ b/build/checksums.txt @@ -0,0 +1,126 @@ +# This file contains sha256 checksums of optional build dependencies. + +# version:spec-tests 2.1.0 +# https://github.com/ethereum/execution-spec-tests/releases +# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/ +ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz + +# version:golang 1.22.5 +# https://go.dev/dl/ +ac9c723f224969aee624bc34fd34c9e13f2a212d75c71c807de644bb46e112f6 go1.22.5.src.tar.gz +c82ba3403c45a4aa4b84b08244656a51e55b86fb130dcc500f5291d0f3b12222 go1.22.5.aix-ppc64.tar.gz +8a8872b1bac959b3b76f2e3978c46406d22a54a99c83ca55840ca08b4f2960bc go1.22.5.darwin-amd64.pkg +95d9933cdcf45f211243c42c7705c37353cccd99f27eb4d8e2d1bf2f4165cb50 go1.22.5.darwin-amd64.tar.gz +8c943512d1fa4e849f0078b03721df02aac19d8bb872dd17ab3ee7127ae6b732 go1.22.5.darwin-arm64.pkg +4cd1bcb05be03cecb77bccd765785d5ff69d79adf4dd49790471d00c06b41133 go1.22.5.darwin-arm64.tar.gz +1f1f035e968a877cd8ed62adae6edb2feeee62470660b7587ddcb904a3877a21 go1.22.5.dragonfly-amd64.tar.gz +d660698411465531d475ec1c617fdb415df68740f3511138a8d15506665a06f9 go1.22.5.freebsd-386.tar.gz +75f43ef46c2ad46c534ded25d26fba9bef036fc07074dfa45c0b3b90856a8151 go1.22.5.freebsd-amd64.tar.gz +75614714e7e4a4dd721f0eddd6555b3f6afc4c07e59c1b9b769cf663996165f9 go1.22.5.freebsd-arm.tar.gz +1377d0d7233f1b8f4cb8e3456f2e7ed44aca4a95daab79ae09605d34aa967c6b go1.22.5.freebsd-arm64.tar.gz +07baf198587abc05ea789dbe5810a2d6612ad56a51718bbf74de2c93bdbe676a go1.22.5.freebsd-riscv64.tar.gz +c0bd4f0d44252f3ec93ca850a41b167bb868179c7c283f8af9439e73b2654b17 go1.22.5.illumos-amd64.tar.gz +3ea4c78e6fa52978ae1ed2e5927ad17495da440c9fae7787b1ebc1d0572f7f43 go1.22.5.linux-386.tar.gz +904b924d435eaea086515bc63235b192ea441bd8c9b198c507e85009e6e4c7f0 go1.22.5.linux-amd64.tar.gz +8d21325bfcf431be3660527c1a39d3d9ad71535fabdf5041c826e44e31642b5a go1.22.5.linux-arm64.tar.gz +8c4587cf3e63c9aefbcafa92818c4d9d51683af93ea687bf6c7508d6fa36f85e go1.22.5.linux-armv6l.tar.gz +780e2eeb6376a763c564f776eaac6700f33f95e29302faa54b040b19cb1f6fd2 go1.22.5.linux-loong64.tar.gz +f784aa1adfb605da3bfe8cd534b545bddae3eb893e9302f7c2f5d44656b1cae2 go1.22.5.linux-mips.tar.gz +aaa3756571467768388f2ab641a02ff54f98f1684808cda047a7be3026e4b438 go1.22.5.linux-mips64.tar.gz +b7956d925c9ef5a4dc53017feaed2d78dba5d0a1036bad5ea513f1f15ba08fbc go1.22.5.linux-mips64le.tar.gz +7baf605be9b787acd750b6b48a91818a5590ec9289b14aea5696a46b41853888 go1.22.5.linux-mipsle.tar.gz +f09b2a6c1a409662e8e8fe267e1eabeba0a1fd00eb1422fd88297b013803952e go1.22.5.linux-ppc64.tar.gz +5312bb420ac0b59175a58927e70b4660b14ab7319aab54398b6071fabcbfbb09 go1.22.5.linux-ppc64le.tar.gz +f8d0c7d96b336f4133409ff9da7241cfe91e65723c2e8e7c7f9b58a9f9603476 go1.22.5.linux-riscv64.tar.gz +24c6c5c9d515adea5d58ae78388348c97614a0c21ac4d4f4c0dab75e893b0b5d go1.22.5.linux-s390x.tar.gz +39144c62acbaa85e4f1ab57bad8f5b3dc67d6fa24b711ec1fa593f4a0ea1fe91 go1.22.5.netbsd-386.tar.gz +118f79640588eb878529b46cdf56599012da6575f0ac07069ec1e9a8e78ddd0b go1.22.5.netbsd-amd64.tar.gz +d39c2b94ae3fd0a6399e545cbecb673496293075291bd98ef15f24d21625a490 go1.22.5.netbsd-arm.tar.gz +f7fb617d10c39248996521d72370db82d50724fa894089c76ae4298fbbe1fb0b go1.22.5.netbsd-arm64.tar.gz +e0f778a34746587ae7c18e8a24cfaba1b2eaabce75d0ceb470adf576ad1cd90f go1.22.5.openbsd-386.tar.gz +b417311df26ef7ae8b34fcb991519a5c496010561c12386d9469aea03c1bdf0b go1.22.5.openbsd-amd64.tar.gz +e78e8ad05605d530a4f79e55031c7c65f2020a9d442e05d490bd08f0d947a34f go1.22.5.openbsd-arm.tar.gz +8027898948f17742717786ead2ff2e960ee1fc82995d6edbad0050d551710f59 go1.22.5.openbsd-arm64.tar.gz +99c5b81d75bcc0d83d25dedc9535682c42c0e761276c88bcc4db6340344644fd go1.22.5.openbsd-ppc64.tar.gz +30d5dacdee0481f0b8cabb75b706465e2177c3a4a1d1c46293332f4b90a3d199 go1.22.5.plan9-386.tar.gz +65628650cd7665387cfe6fa386c381f4de1ef7b03a12067ae9ccf06d2feaea2c go1.22.5.plan9-amd64.tar.gz +322541cbfc9ae95b48b9eec4eb45df48299784592e23121084f790cf1082787e go1.22.5.plan9-arm.tar.gz +87c590e3eb81fcffa3dc1524c03c2847f0890e95c2a43586e82b56c262eb03d8 go1.22.5.solaris-amd64.tar.gz +3ec89ed822b38f4483977a90913fbe39d0857f0ed16c4642dec1950ddbe8c943 go1.22.5.windows-386.msi +c44fc421075022add78fbf8db38519dd5520a11832749be2189e64b3cf4f02f9 go1.22.5.windows-386.zip +86b0299ab8cb9c44882a9080dac03f7f4d9546f51ed1ba1015599114bcbc66d0 go1.22.5.windows-amd64.msi +59968438b8d90f108fd240d4d2f95b037e59716995f7409e0a322dcb996e9f42 go1.22.5.windows-amd64.zip +013d3b300e6b8f26482d6dd17b02830b83ee63795498bd8c0c9d80bb2c4d6cf7 go1.22.5.windows-arm.msi +8cc860630a84e2dbff3e84280f46a571741f26f8a1819aa4fbcb3164fdd51312 go1.22.5.windows-arm.zip +8f90519d9f305f2caa05d1d4fb0656b50f1bf89d76e194279f480e5a484c891f go1.22.5.windows-arm64.msi +6717d5841162aa8c05f932eb74a643f1310b8a88f80f0830e86d194289734bbf go1.22.5.windows-arm64.zip + +# version:golangci 1.59.0 +# https://github.com/golangci/golangci-lint/releases/ +# https://github.com/golangci/golangci-lint/releases/download/v1.59.0/ +418acf7e255ddc0783e97129c9b03d9311b77826a5311d425a01c708a86417e7 golangci-lint-1.59.0-darwin-amd64.tar.gz +5f6a1d95a6dd69f6e328eb56dd311a38e04cfab79a1305fbf4957f4e203f47b6 golangci-lint-1.59.0-darwin-arm64.tar.gz +8899bf589185d49f747f3e5db9f0bde8a47245a100c64a3dd4d65e8e92cfc4f2 golangci-lint-1.59.0-freebsd-386.tar.gz +658212f138d9df2ac89427e22115af34bf387c0871d70f2a25101718946a014f golangci-lint-1.59.0-freebsd-amd64.tar.gz +4c6395ea40f314d3b6fa17d8997baab93464d5d1deeaab513155e625473bd03a golangci-lint-1.59.0-freebsd-armv6.tar.gz +ff37da4fbaacdb6bbae70fdbdbb1ba932a859956f788c82822fa06bef5b7c6b3 golangci-lint-1.59.0-freebsd-armv7.tar.gz +439739469ed2bda182b1ec276d40c40e02f195537f78e3672996741ad223d6b6 golangci-lint-1.59.0-illumos-amd64.tar.gz +940801d46790e40d0a097d8fee34e2606f0ef148cd039654029b0b8750a15ed6 golangci-lint-1.59.0-linux-386.tar.gz +3b14a439f33c4fff83dbe0349950d984042b9a1feb6c62f82787b598fc3ab5f4 golangci-lint-1.59.0-linux-amd64.tar.gz +c57e6c0b0fa03089a2611dceddd5bc5d206716cccdff8b149da8baac598719a1 golangci-lint-1.59.0-linux-arm64.tar.gz +93149e2d3b25ac754df9a23172403d8aa6d021a7e0d9c090a12f51897f68c9a0 golangci-lint-1.59.0-linux-armv6.tar.gz +d10ac38239d9efee3ee87b55c96cdf3fa09e1a525babe3ffdaaf65ccc48cf3dc golangci-lint-1.59.0-linux-armv7.tar.gz +047338114b4f0d5f08f0fb9a397b03cc171916ed0960be7dfb355c2320cd5e9c golangci-lint-1.59.0-linux-loong64.tar.gz +5632df0f7f8fc03a80a266130faef0b5902d280cf60621f1b2bdc1aef6d97ee9 golangci-lint-1.59.0-linux-mips64.tar.gz +71dd638c82fa4439171e7126d2c7a32b5d103bfdef282cea40c83632cb3d1f4b golangci-lint-1.59.0-linux-mips64le.tar.gz +6cf9ea0d34e91669948483f9ae7f07da319a879344373a1981099fbd890cde00 golangci-lint-1.59.0-linux-ppc64le.tar.gz +af0205fa6fbab197cee613c359947711231739095d21b5c837086233b36ad971 golangci-lint-1.59.0-linux-riscv64.tar.gz +a9d2fb93f3c688ebccef94f5dc96c0b07c4d20bf6556cddebd8442159b0c80f6 golangci-lint-1.59.0-linux-s390x.tar.gz +68ab4c57a847b8ace9679887f2f8b2b6760e57ee29dcde8c3f40dd8bb2654fa2 golangci-lint-1.59.0-netbsd-386.tar.gz +d277b8b435c19406d00de4d509eadf5a024a5782878332e9a1b7c02bb76e87a7 golangci-lint-1.59.0-netbsd-amd64.tar.gz +83211656be8dcfa1545af4f92894409f412d1f37566798cb9460a526593ad62c golangci-lint-1.59.0-netbsd-arm64.tar.gz +6c6866d28bf79fa9817a0f7d2b050890ed109cae80bdb4dfa39536a7226da237 golangci-lint-1.59.0-netbsd-armv6.tar.gz +11587566363bd03ca586b7df9776ccaed569fcd1f3489930ac02f9375b307503 golangci-lint-1.59.0-netbsd-armv7.tar.gz +466181a8967bafa495e41494f93a0bec829c2cf715de874583b0460b3b8ae2b8 golangci-lint-1.59.0-windows-386.zip +3317d8a87a99a49a0a1321d295c010790e6dbf43ee96b318f4b8bb23eae7a565 golangci-lint-1.59.0-windows-amd64.zip +b3af955c7fceac8220a36fc799e1b3f19d3b247d32f422caac5f9845df8f7316 golangci-lint-1.59.0-windows-arm64.zip +6f083c7d0c764e5a0e5bde46ee3e91ae357d80c194190fe1d9754392e9064c7e golangci-lint-1.59.0-windows-armv6.zip +3709b4dd425deadab27748778d08e03c0f804d7748f7dd5b6bb488d98aa031c7 golangci-lint-1.59.0-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. +# +# version:ppa-builder-1 1.19.6 +# https://go.dev/dl/ +d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz + +# version:ppa-builder-2 1.21.9 +# https://go.dev/dl/ +58f0c5ced45a0012bce2ff7a9df03e128abcc8818ebabe5027bb92bafe20e421 go1.21.9.src.tar.gz + +# version:protoc 27.1 +# https://github.com/protocolbuffers/protobuf/releases/ +# https://github.com/protocolbuffers/protobuf/releases/download/v27.1/ +8809c2ec85368c6b6e9af161b6771a153aa92670a24adbe46dd34fa02a04df2f protoc-27.1-linux-aarch_64.zip +5d21979a6d27475e810b76b88863d1e784fa01ffb15e511a3ec5bd1924d89426 protoc-27.1-linux-ppcle_64.zip +84d8852750ed186dc4a057a1a86bcac409be5362d6af04770f42367fee6b7bc1 protoc-27.1-linux-s390_64.zip +2f028796ff5741691650e0eea290e61ff2f1c0d87f8d31fe45ef47fd967cef0c protoc-27.1-linux-x86_32.zip +8970e3d8bbd67d53768fe8c2e3971bdd71e51cfe2001ca06dacad17258a7dae3 protoc-27.1-linux-x86_64.zip +03b7af1bf469e7285dc51976ee5fa99412704dbd1c017105114852a37b165c12 protoc-27.1-osx-aarch_64.zip +f14d3973cf13283d07c520ed6f4c12405ad41b9efd18089a1c74897037d742b5 protoc-27.1-osx-universal_binary.zip +8520d944f3a3890fa296a3b3b0d4bb18337337e2526bbbf1b507eeea3c2a1ec4 protoc-27.1-osx-x86_64.zip +6263718ff96547b8392a079f6fdf02a4156f2e8d13cd51649a0d03fb7afa2de8 protoc-27.1-win32.zip +da531c51ccd1290d8d34821f0ce4e219c7fbaa6f9825f5a3fb092a9d03fe6206 protoc-27.1-win64.zip + +# version:protoc-gen-go 1.34.2 +# https://github.com/protocolbuffers/protobuf-go/releases/ +# https://github.com/protocolbuffers/protobuf-go/releases/download/v1.34.2/ +9b48d8f90add02e8e94e14962fed74e7ce2b2d6bda4dd42f1f4fbccf0f766f1a protoc-gen-go.v1.34.2.darwin.amd64.tar.gz +17aca7f948dbb624049030cf841e35895cf34183ba006e721247fdeb95ff2780 protoc-gen-go.v1.34.2.darwin.arm64.tar.gz +a191849433fd489f1d44f37788d762658f3f5fb225f3a85d4ce6ba32666703ed protoc-gen-go.v1.34.2.linux.386.tar.gz +b87bc134dee55576a842141bf0ed27761c635d746780fce5dee038c6dd16554f protoc-gen-go.v1.34.2.linux.amd64.tar.gz +63d400167e75ab9f6690688f6fdc6a9455aa20bc1faa71e32149dbd322f7f198 protoc-gen-go.v1.34.2.linux.arm64.tar.gz +56e7675816db6e62be4f833a51544d5716b8420c462515579e05ca8444ab06ed protoc-gen-go.v1.34.2.windows.386.zip +abafd39612177dd4e9a65207cadd5374a9352d8611e8e040f8462fcfa3010daf protoc-gen-go.v1.34.2.windows.amd64.zip diff --git a/build/ci-notes.md b/build/ci-notes.md new file mode 100644 index 0000000..edd9adc --- /dev/null +++ b/build/ci-notes.md @@ -0,0 +1,50 @@ +# Debian Packaging + +Tagged releases and develop branch commits are available as installable Debian packages +for Ubuntu. Packages are built for the all Ubuntu versions which are supported by +Canonical. + +Packages of develop branch commits have suffix -unstable and cannot be installed alongside +the stable version. Switching between release streams requires user intervention. + +## Launchpad + +The packages are built and served by launchpad.net. We generate a Debian source package +for each distribution and upload it. Their builder picks up the source package, builds it +and installs the new version into the PPA repository. Launchpad requires a valid signature +by a team member for source package uploads. + +The signing key is stored in an environment variable which Travis CI makes available to +certain builds. Since Travis CI doesn't support FTP, SFTP is used to transfer the +packages. To set this up yourself, you need to create a Launchpad user and add a GPG key +and SSH key to it. Then encode both keys as base64 and configure 'secret' environment +variables `PPA_SIGNING_KEY` and `PPA_SSH_KEY` on Travis. + +We want to build go-ethereum with the most recent version of Go, irrespective of the Go +version that is available in the main Ubuntu repository. In order to make this possible, +we bundle the entire Go sources into our own source archive and start the built job by +compiling Go and then using that to build go-ethereum. On Trusty we have a special case +requiring the `~gophers/ubuntu/archive` PPA since Trusty can't even build Go itself. PPA +deps are set at https://launchpad.net/%7Eethereum/+archive/ubuntu/ethereum/+edit-dependencies + +## Building Packages Locally (for testing) + +You need to run Ubuntu to do test packaging. + +Install any version of Go and Debian packaging tools: + + $ sudo apt-get install build-essential golang-go devscripts debhelper python-bzrlib python-paramiko + +Create the source packages: + + $ go run build/ci.go debsrc -workdir dist + +Then go into the source package directory for your running distribution and build the package: + + $ cd dist/ethereum-unstable-1.9.6+bionic + $ dpkg-buildpackage + +Built packages are placed in the dist/ directory. + + $ cd .. + $ dpkg-deb -c geth-unstable_1.9.6+bionic_amd64.deb diff --git a/build/ci.go b/build/ci.go new file mode 100644 index 0000000..db57633 --- /dev/null +++ b/build/ci.go @@ -0,0 +1,1278 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build none +// +build none + +/* +The ci command is called from Continuous Integration scripts. + +Usage: go run build/ci.go + +Available commands are: + + install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables + test [ -coverage ] [ packages... ] -- runs the tests + lint -- runs certain pre-selected linters + archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts + importkeys -- imports signing keys from env + debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package + nsis -- creates a Windows NSIS installer + purge [ -store blobstore ] [ -days threshold ] -- purges old archives from the blobstore + +For all commands, -n prevents execution of external programs (dry run mode). +*/ +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/base64" + "flag" + "fmt" + "io" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/cespare/cp" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/signify" + "github.com/ethereum/go-ethereum/internal/build" + "github.com/ethereum/go-ethereum/params" +) + +var ( + // Files that end up in the geth*.zip archive. + gethArchiveFiles = []string{ + "COPYING", + executablePath("geth"), + } + + // Files that end up in the geth-alltools*.zip archive. + allToolsArchiveFiles = []string{ + "COPYING", + executablePath("abigen"), + executablePath("bootnode"), + executablePath("evm"), + executablePath("geth"), + executablePath("rlpdump"), + executablePath("clef"), + } + + // A debian package is created for all executables listed here. + debExecutables = []debExecutable{ + { + BinaryName: "abigen", + Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.", + }, + { + BinaryName: "bootnode", + Description: "Ethereum bootnode.", + }, + { + BinaryName: "evm", + Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.", + }, + { + BinaryName: "geth", + Description: "Ethereum CLI client.", + }, + { + BinaryName: "rlpdump", + Description: "Developer utility tool that prints RLP structures.", + }, + { + BinaryName: "clef", + Description: "Ethereum account management tool.", + }, + } + + // A debian package is created for all executables listed here. + debEthereum = debPackage{ + Name: "ethereum", + Version: params.Version, + Executables: debExecutables, + } + + // Debian meta packages to build and push to Ubuntu PPA + debPackages = []debPackage{ + debEthereum, + } + + // Distros for which packages are created + debDistros = []string{ + "xenial", // 16.04, EOL: 04/2026 + "bionic", // 18.04, EOL: 04/2028 + "focal", // 20.04, EOL: 04/2030 + "jammy", // 22.04, EOL: 04/2032 + "noble", // 24.04, EOL: 04/2034 + + "mantic", // 23.10, EOL: 07/2024 + } + + // This is where the tests should be unpacked. + executionSpecTestsDir = "tests/spec-tests" +) + +var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) + +func executablePath(name string) string { + if runtime.GOOS == "windows" { + name += ".exe" + } + return filepath.Join(GOBIN, name) +} + +func main() { + log.SetFlags(log.Lshortfile) + + if !common.FileExist(filepath.Join("build", "ci.go")) { + log.Fatal("this script must be run from the root of the repository") + } + if len(os.Args) < 2 { + log.Fatal("need subcommand as first argument") + } + switch os.Args[1] { + case "install": + doInstall(os.Args[2:]) + case "test": + doTest(os.Args[2:]) + case "lint": + doLint(os.Args[2:]) + case "archive": + doArchive(os.Args[2:]) + case "docker": + doDocker(os.Args[2:]) + case "debsrc": + doDebianSource(os.Args[2:]) + case "nsis": + doWindowsInstaller(os.Args[2:]) + case "purge": + doPurge(os.Args[2:]) + case "sanitycheck": + doSanityCheck() + case "generate": + doGenerate() + default: + log.Fatal("unknown command ", os.Args[1]) + } +} + +// Compiling + +func doInstall(cmdline []string) { + var ( + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") + arch = flag.String("arch", "", "Architecture to cross build for") + cc = flag.String("cc", "", "C compiler to cross build with") + staticlink = flag.Bool("static", false, "Create statically-linked executable") + ) + flag.CommandLine.Parse(cmdline) + env := build.Env() + + // Configure the toolchain. + tc := build.GoToolchain{GOARCH: *arch, CC: *cc} + if *dlgo { + csdb := build.MustLoadChecksums("build/checksums.txt") + tc.Root = build.DownloadGo(csdb) + } + // Disable CLI markdown doc generation in release builds. + buildTags := []string{"urfave_cli_no_docs"} + + // Enable linking the CKZG library since we can make it work with additional flags. + if env.UbuntuVersion != "trusty" { + buildTags = append(buildTags, "ckzg") + } + + // Configure the build. + gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...) + + // arm64 CI builders are memory-constrained and can't handle concurrent builds, + // better disable it. This check isn't the best, it should probably + // check for something in env instead. + if env.CI && runtime.GOARCH == "arm64" { + gobuild.Args = append(gobuild.Args, "-p", "1") + } + // We use -trimpath to avoid leaking local paths into the built executables. + gobuild.Args = append(gobuild.Args, "-trimpath") + + // Show packages during build. + gobuild.Args = append(gobuild.Args, "-v") + + // Now we choose what we're even building. + // Default: collect all 'main' packages in cmd/ and build those. + packages := flag.Args() + if len(packages) == 0 { + packages = build.FindMainPackages("./cmd") + } + + // Do the build! + for _, pkg := range packages { + args := make([]string, len(gobuild.Args)) + copy(args, gobuild.Args) + args = append(args, "-o", executablePath(path.Base(pkg))) + args = append(args, pkg) + build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env}) + } +} + +// buildFlags returns the go tool flags for building. +func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) { + var ld []string + if env.Commit != "" { + ld = append(ld, "-X", "github.com/ethereum/go-ethereum/internal/version.gitCommit="+env.Commit) + ld = append(ld, "-X", "github.com/ethereum/go-ethereum/internal/version.gitDate="+env.Date) + } + // Strip DWARF on darwin. This used to be required for certain things, + // and there is no downside to this, so we just keep doing it. + if runtime.GOOS == "darwin" { + ld = append(ld, "-s") + } + if runtime.GOOS == "linux" { + // Enforce the stacksize to 8M, which is the case on most platforms apart from + // alpine Linux. + extld := []string{"-Wl,-z,stack-size=0x800000"} + if staticLinking { + extld = append(extld, "-static") + // Under static linking, use of certain glibc features must be + // disabled to avoid shared library dependencies. + buildTags = append(buildTags, "osusergo", "netgo") + } + ld = append(ld, "-extldflags", "'"+strings.Join(extld, " ")+"'") + } + if len(ld) > 0 { + flags = append(flags, "-ldflags", strings.Join(ld, " ")) + } + if len(buildTags) > 0 { + flags = append(flags, "-tags", strings.Join(buildTags, ",")) + } + return flags +} + +// Running The Tests +// +// "tests" also includes static analysis tools such as vet. + +func doTest(cmdline []string) { + var ( + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") + arch = flag.String("arch", "", "Run tests for given architecture") + cc = flag.String("cc", "", "Sets C compiler binary") + 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 { + tc.Root = build.DownloadGo(csdb) + } + gotest := tc.Go("test") + + // CI needs a bit more time for the statetests (default 10m). + gotest.Args = append(gotest.Args, "-timeout=20m") + + // Enable CKZG backend in CI. + gotest.Args = append(gotest.Args, "-tags=ckzg") + + // 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") + if *coverage { + gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") + } + if *verbose { + gotest.Args = append(gotest.Args, "-v") + } + if *race { + gotest.Args = append(gotest.Args, "-race") + } + if *short { + gotest.Args = append(gotest.Args, "-short") + } + + packages := []string{"./..."} + if len(flag.CommandLine.Args()) > 0 { + packages = flag.CommandLine.Args() + } + gotest.Args = append(gotest.Args, packages...) + 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) +} + +// hashSourceFiles iterates all files under the top-level project directory +// computing the hash of each file (excluding files within the tests +// subrepo) +func hashSourceFiles() (map[string]common.Hash, error) { + res := make(map[string]common.Hash) + err := filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error { + if strings.HasPrefix(path, filepath.FromSlash("tests/testdata")) { + return filepath.SkipDir + } + if !d.Type().IsRegular() { + return nil + } + // open the file and hash it + f, err := os.OpenFile(path, os.O_RDONLY, 0666) + if err != nil { + return err + } + hasher := sha256.New() + if _, err := io.Copy(hasher, f); err != nil { + return err + } + res[path] = common.Hash(hasher.Sum(nil)) + return nil + }) + if err != nil { + return nil, err + } + return res, nil +} + +// doGenerate ensures that re-generating generated files does not cause +// any mutations in the source file tree: i.e. all generated files were +// updated and committed. Any stale generated files are updated. +func doGenerate() { + var ( + tc = new(build.GoToolchain) + cachedir = flag.String("cachedir", "./build/cache", "directory for caching binaries.") + verify = flag.Bool("verify", false, "check whether any files are changed by go generate") + ) + + protocPath := downloadProtoc(*cachedir) + protocGenGoPath := downloadProtocGenGo(*cachedir) + + var preHashes map[string]common.Hash + if *verify { + var err error + preHashes, err = hashSourceFiles() + if err != nil { + log.Fatal("failed to compute map of source hashes", "err", err) + } + } + + c := tc.Go("generate", "./...") + pathList := []string{filepath.Join(protocPath, "bin"), protocGenGoPath, os.Getenv("PATH")} + c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator))) + build.MustRun(c) + + if !*verify { + return + } + // Check if files were changed. + postHashes, err := hashSourceFiles() + if err != nil { + log.Fatal("error computing source tree file hashes", "err", err) + } + updates := []string{} + for path, postHash := range postHashes { + preHash, ok := preHashes[path] + if !ok || preHash != postHash { + updates = append(updates, path) + } + } + for _, updatedFile := range updates { + fmt.Fprintf(os.Stderr, "changed file %s\n", updatedFile) + } + if len(updates) != 0 { + log.Fatal("One or more generated files were updated by running 'go generate ./...'") + } +} + +// doLint runs golangci-lint on requested packages. +func doLint(cmdline []string) { + var ( + cachedir = flag.String("cachedir", "./build/cache", "directory for caching golangci-lint binary.") + ) + flag.CommandLine.Parse(cmdline) + packages := []string{"./..."} + if len(flag.CommandLine.Args()) > 0 { + packages = flag.CommandLine.Args() + } + + linter := downloadLinter(*cachedir) + lflags := []string{"run", "--config", ".golangci.yml"} + build.MustRunCommandWithOutput(linter, append(lflags, packages...)...) + fmt.Println("You have achieved perfection.") +} + +// downloadLinter downloads and unpacks golangci-lint. +func downloadLinter(cachedir string) string { + csdb := build.MustLoadChecksums("build/checksums.txt") + version, err := build.Version(csdb, "golangci") + if err != nil { + log.Fatal(err) + } + arch := runtime.GOARCH + ext := ".tar.gz" + + if runtime.GOOS == "windows" { + ext = ".zip" + } + if arch == "arm" { + arch += "v" + os.Getenv("GOARM") + } + base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, arch) + url := fmt.Sprintf("https://github.com/golangci/golangci-lint/releases/download/v%s/%s%s", version, base, ext) + archivePath := filepath.Join(cachedir, base+ext) + if err := csdb.DownloadFile(url, archivePath); err != nil { + log.Fatal(err) + } + if err := build.ExtractArchive(archivePath, cachedir); err != nil { + log.Fatal(err) + } + return filepath.Join(cachedir, base, "golangci-lint") +} + +// protocArchiveBaseName returns the name of the protoc archive file for +// the current system, stripped of version and file suffix. +func protocArchiveBaseName() (string, error) { + switch runtime.GOOS + "-" + runtime.GOARCH { + case "windows-amd64": + return "win64", nil + case "windows-386": + return "win32", nil + case "linux-arm64": + return "linux-aarch_64", nil + case "linux-386": + return "linux-x86_32", nil + case "linux-amd64": + return "linux-x86_64", nil + case "darwin-arm64": + return "osx-aarch_64", nil + case "darwin-amd64": + return "osx-x86_64", nil + default: + return "", fmt.Errorf("no prebuilt release of protoc available for this system (os: %s, arch: %s)", runtime.GOOS, runtime.GOARCH) + } +} + +// downloadProtocGenGo downloads protoc-gen-go, which is used by protoc +// in the generate command. It returns the full path of the directory +// containing the 'protoc-gen-go' executable. +func downloadProtocGenGo(cachedir string) string { + csdb := build.MustLoadChecksums("build/checksums.txt") + version, err := build.Version(csdb, "protoc-gen-go") + if err != nil { + log.Fatal(err) + } + baseName := fmt.Sprintf("protoc-gen-go.v%s.%s.%s", version, runtime.GOOS, runtime.GOARCH) + archiveName := baseName + if runtime.GOOS == "windows" { + archiveName += ".zip" + } else { + archiveName += ".tar.gz" + } + + url := fmt.Sprintf("https://github.com/protocolbuffers/protobuf-go/releases/download/v%s/%s", version, archiveName) + + archivePath := path.Join(cachedir, archiveName) + if err := csdb.DownloadFile(url, archivePath); err != nil { + log.Fatal(err) + } + extractDest := filepath.Join(cachedir, baseName) + if err := build.ExtractArchive(archivePath, extractDest); err != nil { + log.Fatal(err) + } + extractDest, err = filepath.Abs(extractDest) + if err != nil { + log.Fatal("error resolving absolute path for protoc", "err", err) + } + return extractDest +} + +// downloadProtoc downloads the prebuilt protoc binary used to lint generated +// files as a CI step. It returns the full path to the directory containing +// the protoc executable. +func downloadProtoc(cachedir string) string { + csdb := build.MustLoadChecksums("build/checksums.txt") + version, err := build.Version(csdb, "protoc") + if err != nil { + log.Fatal(err) + } + baseName, err := protocArchiveBaseName() + if err != nil { + log.Fatal(err) + } + + fileName := fmt.Sprintf("protoc-%s-%s", version, baseName) + archiveFileName := fileName + ".zip" + url := fmt.Sprintf("https://github.com/protocolbuffers/protobuf/releases/download/v%s/%s", version, archiveFileName) + archivePath := filepath.Join(cachedir, archiveFileName) + + if err := csdb.DownloadFile(url, archivePath); err != nil { + log.Fatal(err) + } + extractDest := filepath.Join(cachedir, fileName) + if err := build.ExtractArchive(archivePath, extractDest); err != nil { + log.Fatal(err) + } + extractDest, err = filepath.Abs(extractDest) + if err != nil { + log.Fatal("error resolving absolute path for protoc", "err", err) + } + return extractDest +} + +// Release Packaging +func doArchive(cmdline []string) { + var ( + arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") + atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + ext string + ) + flag.CommandLine.Parse(cmdline) + switch *atype { + case "zip": + ext = ".zip" + case "tar": + ext = ".tar.gz" + default: + log.Fatal("unknown archive type: ", atype) + } + + var ( + env = build.Env() + basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + geth = "geth-" + basegeth + ext + alltools = "geth-alltools-" + basegeth + ext + ) + maybeSkipArchive(env) + if err := build.WriteArchive(geth, gethArchiveFiles); err != nil { + log.Fatal(err) + } + if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil { + log.Fatal(err) + } + for _, archive := range []string{geth, alltools} { + if err := archiveUpload(archive, *upload, *signer, *signify); err != nil { + log.Fatal(err) + } + } +} + +func archiveBasename(arch string, archiveVersion string) string { + platform := runtime.GOOS + "-" + arch + if arch == "arm" { + platform += os.Getenv("GOARM") + } + if arch == "android" { + platform = "android-all" + } + if arch == "ios" { + platform = "ios-all" + } + return platform + "-" + archiveVersion +} + +func archiveUpload(archive string, blobstore string, signer string, signifyVar string) error { + // If signing was requested, generate the signature files + if signer != "" { + key := getenvBase64(signer) + if err := build.PGPSignFile(archive, archive+".asc", string(key)); err != nil { + return err + } + } + if signifyVar != "" { + key := os.Getenv(signifyVar) + untrustedComment := "verify with geth-release.pub" + trustedComment := fmt.Sprintf("%s (%s)", archive, time.Now().UTC().Format(time.RFC1123)) + if err := signify.SignFile(archive, archive+".sig", key, untrustedComment, trustedComment); err != nil { + return err + } + } + // If uploading to Azure was requested, push the archive possibly with its signature + if blobstore != "" { + auth := build.AzureBlobstoreConfig{ + Account: strings.Split(blobstore, "/")[0], + Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), + Container: strings.SplitN(blobstore, "/", 2)[1], + } + if err := build.AzureBlobstoreUpload(archive, filepath.Base(archive), auth); err != nil { + return err + } + if signer != "" { + if err := build.AzureBlobstoreUpload(archive+".asc", filepath.Base(archive+".asc"), auth); err != nil { + return err + } + } + if signifyVar != "" { + if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil { + return err + } + } + } + return nil +} + +// skips archiving for some build configurations. +func maybeSkipArchive(env build.Environment) { + if env.IsPullRequest { + log.Printf("skipping archive creation because this is a PR build") + os.Exit(0) + } + if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { + log.Printf("skipping archive creation because branch %q, tag %q is not on the inclusion list", env.Branch, env.Tag) + os.Exit(0) + } +} + +// Builds the docker images and optionally uploads them to Docker Hub. +func doDocker(cmdline []string) { + var ( + image = flag.Bool("image", false, `Whether to build and push an arch specific docker image`) + manifest = flag.String("manifest", "", `Push a multi-arch docker image for the specified architectures (usually "amd64,arm64")`) + upload = flag.String("upload", "", `Where to upload the docker image (usually "ethereum/client-go")`) + ) + flag.CommandLine.Parse(cmdline) + + // Skip building and pushing docker images for PR builds + env := build.Env() + maybeSkipArchive(env) + + // Retrieve the upload credentials and authenticate + user := getenvBase64("DOCKER_HUB_USERNAME") + pass := getenvBase64("DOCKER_HUB_PASSWORD") + + if len(user) > 0 && len(pass) > 0 { + auther := exec.Command("docker", "login", "-u", string(user), "--password-stdin") + auther.Stdin = bytes.NewReader(pass) + build.MustRun(auther) + } + // Retrieve the version infos to build and push to the following paths: + // - ethereum/client-go:latest - Pushes to the master branch, Geth only + // - ethereum/client-go:stable - Version tag publish on GitHub, Geth only + // - ethereum/client-go:alltools-latest - Pushes to the master branch, Geth & tools + // - ethereum/client-go:alltools-stable - Version tag publish on GitHub, Geth & tools + // - ethereum/client-go:release-. - Version tag publish on GitHub, Geth only + // - ethereum/client-go:alltools-release-. - Version tag publish on GitHub, Geth & tools + // - ethereum/client-go:v.. - Version tag publish on GitHub, Geth only + // - ethereum/client-go:alltools-v.. - Version tag publish on GitHub, Geth & tools + var tags []string + + switch { + case env.Branch == "master": + tags = []string{"latest"} + case strings.HasPrefix(env.Tag, "v1."): + tags = []string{"stable", fmt.Sprintf("release-1.%d", params.VersionMinor), "v" + params.Version} + } + // If architecture specific image builds are requested, build and push them + if *image { + build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:TAG", *upload), ".") + build.MustRunCommand("docker", "build", "--build-arg", "COMMIT="+env.Commit, "--build-arg", "VERSION="+params.VersionWithMeta, "--build-arg", "BUILDNUM="+env.Buildnum, "--tag", fmt.Sprintf("%s:alltools-TAG", *upload), "-f", "Dockerfile.alltools", ".") + + // Tag and upload the images to Docker Hub + for _, tag := range tags { + gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, runtime.GOARCH) + toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, runtime.GOARCH) + + // If the image already exists (non version tag), check the build + // number to prevent overwriting a newer commit if concurrent builds + // are running. This is still a tiny bit racey if two published are + // done at the same time, but that's extremely unlikely even on the + // master branch. + for _, img := range []string{gethImage, toolImage} { + if exec.Command("docker", "pull", img).Run() != nil { + continue // Generally the only failure is a missing image, which is good + } + buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() + if err != nil { + log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) + } + buildnum = bytes.TrimSpace(buildnum) + + if len(buildnum) > 0 && len(env.Buildnum) > 0 { + oldnum, err := strconv.Atoi(string(buildnum)) + if err != nil { + log.Fatalf("Failed to parse old image build number: %v", err) + } + newnum, err := strconv.Atoi(env.Buildnum) + if err != nil { + log.Fatalf("Failed to parse current build number: %v", err) + } + if oldnum > newnum { + log.Fatalf("Current build number %d not newer than existing %d", newnum, oldnum) + } else { + log.Printf("Updating %s from build %d to %d", img, oldnum, newnum) + } + } + } + build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:TAG", *upload), gethImage) + build.MustRunCommand("docker", "image", "tag", fmt.Sprintf("%s:alltools-TAG", *upload), toolImage) + build.MustRunCommand("docker", "push", gethImage) + build.MustRunCommand("docker", "push", toolImage) + } + } + // If multi-arch image manifest push is requested, assemble it + if len(*manifest) != 0 { + // Since different architectures are pushed by different builders, wait + // until all required images are updated. + var mismatch bool + for i := 0; i < 2; i++ { // 2 attempts, second is race check + mismatch = false // hope there's no mismatch now + + for _, tag := range tags { + for _, arch := range strings.Split(*manifest, ",") { + gethImage := fmt.Sprintf("%s:%s-%s", *upload, tag, arch) + toolImage := fmt.Sprintf("%s:alltools-%s-%s", *upload, tag, arch) + + for _, img := range []string{gethImage, toolImage} { + if out, err := exec.Command("docker", "pull", img).CombinedOutput(); err != nil { + log.Printf("Required image %s unavailable: %v\nOutput: %s", img, err, out) + mismatch = true + break + } + buildnum, err := exec.Command("docker", "inspect", "--format", "{{index .Config.Labels \"buildnum\"}}", img).CombinedOutput() + if err != nil { + log.Fatalf("Failed to inspect container: %v\nOutput: %s", err, string(buildnum)) + } + buildnum = bytes.TrimSpace(buildnum) + + if string(buildnum) != env.Buildnum { + log.Printf("Build number mismatch on %s: want %s, have %s", img, env.Buildnum, buildnum) + mismatch = true + break + } + } + if mismatch { + break + } + } + if mismatch { + break + } + } + if mismatch { + // Build numbers mismatching, retry in a short time to + // avoid concurrent fails in both publisher images. If + // however the retry failed too, it means the concurrent + // builder is still crunching, let that do the publish. + if i == 0 { + time.Sleep(30 * time.Second) + } + continue + } + break + } + if mismatch { + log.Println("Relinquishing publish to other builder") + return + } + // Assemble and push the Geth manifest image + for _, tag := range tags { + gethImage := fmt.Sprintf("%s:%s", *upload, tag) + + var gethSubImages []string + for _, arch := range strings.Split(*manifest, ",") { + gethSubImages = append(gethSubImages, gethImage+"-"+arch) + } + build.MustRunCommand("docker", append([]string{"manifest", "create", gethImage}, gethSubImages...)...) + build.MustRunCommand("docker", "manifest", "push", gethImage) + } + // Assemble and push the alltools manifest image + for _, tag := range tags { + toolImage := fmt.Sprintf("%s:alltools-%s", *upload, tag) + + var toolSubImages []string + for _, arch := range strings.Split(*manifest, ",") { + toolSubImages = append(toolSubImages, toolImage+"-"+arch) + } + build.MustRunCommand("docker", append([]string{"manifest", "create", toolImage}, toolSubImages...)...) + build.MustRunCommand("docker", "manifest", "push", toolImage) + } + } +} + +// Debian Packaging +func doDebianSource(cmdline []string) { + var ( + cachedir = flag.String("cachedir", "./build/cache", `Filesystem path to cache the downloaded Go bundles at`) + signer = flag.String("signer", "", `Signing key name, also used as package author`) + upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`) + sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`) + workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) + now = time.Now() + ) + flag.CommandLine.Parse(cmdline) + *workdir = makeWorkdir(*workdir) + env := build.Env() + tc := new(build.GoToolchain) + maybeSkipArchive(env) + + // Import the signing key. + if key := getenvBase64("PPA_SIGNING_KEY"); len(key) > 0 { + gpg := exec.Command("gpg", "--import") + gpg.Stdin = bytes.NewReader(key) + build.MustRun(gpg) + } + // Download and verify the Go source packages. + var ( + gobootbundles = downloadGoBootstrapSources(*cachedir) + gobundle = downloadGoSources(*cachedir) + ) + // Download all the dependencies needed to build the sources and run the ci script + srcdepfetch := tc.Go("mod", "download") + srcdepfetch.Env = append(srcdepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) + build.MustRun(srcdepfetch) + + cidepfetch := tc.Go("run", "./build/ci.go") + cidepfetch.Env = append(cidepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) + cidepfetch.Run() // Command fails, don't care, we only need the deps to start it + + // Create Debian packages and upload them. + for _, pkg := range debPackages { + for _, distro := range debDistros { + // Prepare the debian package with the go-ethereum sources. + meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables) + pkgdir := stageDebianSource(*workdir, meta) + + // Add bootstrapper Go source code + for i, gobootbundle := range gobootbundles { + if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil { + log.Fatalf("Failed to extract bootstrapper Go sources: %v", err) + } + if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, fmt.Sprintf(".goboot-%d", i+1))); err != nil { + log.Fatalf("Failed to rename bootstrapper Go source folder: %v", err) + } + } + // Add builder Go source code + if err := build.ExtractArchive(gobundle, pkgdir); err != nil { + log.Fatalf("Failed to extract builder Go sources: %v", err) + } + if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil { + log.Fatalf("Failed to rename builder Go source folder: %v", err) + } + // Add all dependency modules in compressed form + os.MkdirAll(filepath.Join(pkgdir, ".mod", "cache"), 0755) + if err := cp.CopyAll(filepath.Join(pkgdir, ".mod", "cache", "download"), filepath.Join(*workdir, "modgopath", "pkg", "mod", "cache", "download")); err != nil { + log.Fatalf("Failed to copy Go module dependencies: %v", err) + } + // Run the packaging and upload to the PPA + debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d", "-Zxz", "-nc") + debuild.Dir = pkgdir + build.MustRun(debuild) + + var ( + basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString()) + source = filepath.Join(*workdir, basename+".tar.xz") + dsc = filepath.Join(*workdir, basename+".dsc") + changes = filepath.Join(*workdir, basename+"_source.changes") + buildinfo = filepath.Join(*workdir, basename+"_source.buildinfo") + ) + if *signer != "" { + build.MustRunCommand("debsign", changes) + } + if *upload != "" { + ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes, buildinfo}) + } + } + } +} + +// downloadGoBootstrapSources downloads the Go source tarball(s) that will be used +// to bootstrap the builder Go. +func downloadGoBootstrapSources(cachedir string) []string { + csdb := build.MustLoadChecksums("build/checksums.txt") + + var bundles []string + for _, booter := range []string{"ppa-builder-1", "ppa-builder-2"} { + gobootVersion, err := build.Version(csdb, booter) + 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) + if err := csdb.DownloadFile(url, dst); err != nil { + log.Fatal(err) + } + bundles = append(bundles, dst) + } + return bundles +} + +// 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) + if err := csdb.DownloadFile(url, dst); err != nil { + log.Fatal(err) + } + return dst +} + +func ppaUpload(workdir, ppa, sshUser string, files []string) { + p := strings.Split(ppa, "/") + if len(p) != 2 { + log.Fatal("-upload PPA name must contain single /") + } + if sshUser == "" { + sshUser = p[0] + } + incomingDir := fmt.Sprintf("~%s/ubuntu/%s", p[0], p[1]) + // Create the SSH identity file if it doesn't exist. + var idfile string + if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 { + idfile = filepath.Join(workdir, "sshkey") + if !common.FileExist(idfile) { + os.WriteFile(idfile, sshkey, 0600) + } + } + // Upload + dest := sshUser + "@ppa.launchpad.net" + if err := build.UploadSFTP(idfile, dest, incomingDir, files); err != nil { + log.Fatal(err) + } +} + +func getenvBase64(variable string) []byte { + dec, err := base64.StdEncoding.DecodeString(os.Getenv(variable)) + if err != nil { + log.Fatal("invalid base64 " + variable) + } + return []byte(dec) +} + +func makeWorkdir(wdflag string) string { + var err error + if wdflag != "" { + err = os.MkdirAll(wdflag, 0744) + } else { + wdflag, err = os.MkdirTemp("", "geth-build-") + } + if err != nil { + log.Fatal(err) + } + return wdflag +} + +func isUnstableBuild(env build.Environment) bool { + if env.Tag != "" { + return false + } + return true +} + +type debPackage struct { + Name string // the name of the Debian package to produce, e.g. "ethereum" + Version string // the clean version of the debPackage, e.g. 1.8.12, without any metadata + Executables []debExecutable // executables to be included in the package +} + +type debMetadata struct { + Env build.Environment + PackageName string + + // go-ethereum version being built. Note that this + // is not the debian package version. The package version + // is constructed by VersionString. + Version string + + Author string // "name ", also selects signing key + Distro, Time string + Executables []debExecutable +} + +type debExecutable struct { + PackageName string + BinaryName string + Description string +} + +// Package returns the name of the package if present, or +// fallbacks to BinaryName +func (d debExecutable) Package() string { + if d.PackageName != "" { + return d.PackageName + } + return d.BinaryName +} + +func newDebMetadata(distro, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata { + if author == "" { + // No signing key, use default author. + author = "Ethereum Builds " + } + return debMetadata{ + PackageName: name, + Env: env, + Author: author, + Distro: distro, + Version: version, + Time: t.Format(time.RFC1123Z), + Executables: exes, + } +} + +// Name returns the name of the metapackage that depends +// on all executable packages. +func (meta debMetadata) Name() string { + if isUnstableBuild(meta.Env) { + return meta.PackageName + "-unstable" + } + return meta.PackageName +} + +// VersionString returns the debian version of the packages. +func (meta debMetadata) VersionString() string { + vsn := meta.Version + if meta.Env.Buildnum != "" { + vsn += "+build" + meta.Env.Buildnum + } + if meta.Distro != "" { + vsn += "+" + meta.Distro + } + return vsn +} + +// ExeList returns the list of all executable packages. +func (meta debMetadata) ExeList() string { + names := make([]string, len(meta.Executables)) + for i, e := range meta.Executables { + names[i] = meta.ExeName(e) + } + return strings.Join(names, ", ") +} + +// ExeName returns the package name of an executable package. +func (meta debMetadata) ExeName(exe debExecutable) string { + if isUnstableBuild(meta.Env) { + return exe.Package() + "-unstable" + } + return exe.Package() +} + +// ExeConflicts returns the content of the Conflicts field +// for executable packages. +func (meta debMetadata) ExeConflicts(exe debExecutable) string { + if isUnstableBuild(meta.Env) { + // Set up the conflicts list so that the *-unstable packages + // cannot be installed alongside the regular version. + // + // https://www.debian.org/doc/debian-policy/ch-relationships.html + // is very explicit about Conflicts: and says that Breaks: should + // be preferred and the conflicting files should be handled via + // alternates. We might do this eventually but using a conflict is + // easier now. + return "ethereum, " + exe.Package() + } + return "" +} + +func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) { + pkg := meta.Name() + "-" + meta.VersionString() + pkgdir = filepath.Join(tmpdir, pkg) + if err := os.Mkdir(pkgdir, 0755); err != nil { + log.Fatal(err) + } + // Copy the source code. + build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator)) + + // Put the debian build files in place. + debian := filepath.Join(pkgdir, "debian") + build.Render("build/deb/"+meta.PackageName+"/deb.rules", filepath.Join(debian, "rules"), 0755, meta) + build.Render("build/deb/"+meta.PackageName+"/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta) + build.Render("build/deb/"+meta.PackageName+"/deb.control", filepath.Join(debian, "control"), 0644, meta) + build.Render("build/deb/"+meta.PackageName+"/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta) + build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta) + build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta) + for _, exe := range meta.Executables { + install := filepath.Join(debian, meta.ExeName(exe)+".install") + docs := filepath.Join(debian, meta.ExeName(exe)+".docs") + build.Render("build/deb/"+meta.PackageName+"/deb.install", install, 0644, exe) + build.Render("build/deb/"+meta.PackageName+"/deb.docs", docs, 0644, exe) + } + return pkgdir +} + +// Windows installer +func doWindowsInstaller(cmdline []string) { + // Parse the flags and make skip installer generation on PRs + var ( + arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) + signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) + ) + flag.CommandLine.Parse(cmdline) + *workdir = makeWorkdir(*workdir) + env := build.Env() + maybeSkipArchive(env) + + // Aggregate binaries that are included in the installer + var ( + devTools []string + allTools []string + gethTool string + ) + for _, file := range allToolsArchiveFiles { + if file == "COPYING" { // license, copied later + continue + } + allTools = append(allTools, filepath.Base(file)) + if filepath.Base(file) == "geth.exe" { + gethTool = file + } else { + devTools = append(devTools, file) + } + } + + // Render NSIS scripts: Installer NSIS contains two installer sections, + // first section contains the geth binary, second section holds the dev tools. + templateData := map[string]interface{}{ + "License": "COPYING", + "Geth": gethTool, + "DevTools": devTools, + } + build.Render("build/nsis.geth.nsi", filepath.Join(*workdir, "geth.nsi"), 0644, nil) + build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData) + build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools) + build.Render("build/nsis.pathupdate.nsh", filepath.Join(*workdir, "PathUpdate.nsh"), 0644, nil) + build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil) + if err := cp.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll"); err != nil { + log.Fatalf("Failed to copy SimpleFC.dll: %v", err) + } + if err := cp.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING"); err != nil { + log.Fatalf("Failed to copy copyright note: %v", err) + } + // Build the installer. This assumes that all the needed files have been previously + // built (don't mix building and packaging to keep cross compilation complexity to a + // minimum). + version := strings.Split(params.Version, ".") + if env.Commit != "" { + version[2] += "-" + env.Commit[:8] + } + installer, err := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe") + if err != nil { + log.Fatalf("Failed to convert installer file path: %v", err) + } + build.MustRunCommand("makensis.exe", + "/DOUTPUTFILE="+installer, + "/DMAJORVERSION="+version[0], + "/DMINORVERSION="+version[1], + "/DBUILDVERSION="+version[2], + "/DARCH="+*arch, + filepath.Join(*workdir, "geth.nsi"), + ) + // Sign and publish installer. + if err := archiveUpload(installer, *upload, *signer, *signify); err != nil { + log.Fatal(err) + } +} + +// Binary distribution cleanups + +func doPurge(cmdline []string) { + var ( + store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`) + limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`) + ) + flag.CommandLine.Parse(cmdline) + + if env := build.Env(); !env.IsCronJob { + log.Printf("skipping because not a cron job") + os.Exit(0) + } + // Create the azure authentication and list the current archives + auth := build.AzureBlobstoreConfig{ + Account: strings.Split(*store, "/")[0], + Token: os.Getenv("AZURE_BLOBSTORE_TOKEN"), + Container: strings.SplitN(*store, "/", 2)[1], + } + blobs, err := build.AzureBlobstoreList(auth) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Found %d blobs\n", len(blobs)) + + // Iterate over the blobs, collect and sort all unstable builds + for i := 0; i < len(blobs); i++ { + if !strings.Contains(*blobs[i].Name, "unstable") { + blobs = append(blobs[:i], blobs[i+1:]...) + i-- + } + } + for i := 0; i < len(blobs); i++ { + for j := i + 1; j < len(blobs); j++ { + if blobs[i].Properties.LastModified.After(*blobs[j].Properties.LastModified) { + blobs[i], blobs[j] = blobs[j], blobs[i] + } + } + } + // Filter out all archives more recent that the given threshold + for i, blob := range blobs { + if time.Since(*blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour { + blobs = blobs[:i] + break + } + } + fmt.Printf("Deleting %d blobs\n", len(blobs)) + // Delete all marked as such and return + if err := build.AzureBlobstoreDelete(auth, blobs); err != nil { + log.Fatal(err) + } +} + +func doSanityCheck() { + build.DownloadAndVerifyChecksums(build.MustLoadChecksums("build/checksums.txt")) +} diff --git a/build/deb/ethereum/completions/bash/geth b/build/deb/ethereum/completions/bash/geth new file mode 100755 index 0000000..a789527 --- /dev/null +++ b/build/deb/ethereum/completions/bash/geth @@ -0,0 +1,16 @@ +_geth_bash_autocomplete() { + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + if [[ "$cur" == "-"* ]]; then + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) + else + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi +} + +complete -o bashdefault -o default -o nospace -F _geth_bash_autocomplete geth diff --git a/build/deb/ethereum/completions/zsh/_geth b/build/deb/ethereum/completions/zsh/_geth new file mode 100644 index 0000000..119794c --- /dev/null +++ b/build/deb/ethereum/completions/zsh/_geth @@ -0,0 +1,18 @@ +_geth_zsh_autocomplete() { + local -a opts + local cur + cur=${words[-1]} + if [[ "$cur" == "-"* ]]; then + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + else + opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") + fi + + if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts + else + _files + fi +} + +compdef _geth_zsh_autocomplete geth diff --git a/build/deb/ethereum/deb.changelog b/build/deb/ethereum/deb.changelog new file mode 100644 index 0000000..83f804a --- /dev/null +++ b/build/deb/ethereum/deb.changelog @@ -0,0 +1,5 @@ +{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low + + * git build of {{.Env.Commit}} + + -- {{.Author}} {{.Time}} diff --git a/build/deb/ethereum/deb.control b/build/deb/ethereum/deb.control new file mode 100644 index 0000000..333e954 --- /dev/null +++ b/build/deb/ethereum/deb.control @@ -0,0 +1,25 @@ +Source: {{.Name}} +Section: science +Priority: extra +Maintainer: {{.Author}} +Build-Depends: debhelper (>= 8.0.0), golang-go +Standards-Version: 3.9.5 +Homepage: https://ethereum.org +Vcs-Git: https://github.com/ethereum/go-ethereum.git +Vcs-Browser: https://github.com/ethereum/go-ethereum + +Package: {{.Name}} +Architecture: any +Depends: ${misc:Depends}, {{.ExeList}} +Description: Meta-package to install geth and other tools + Meta-package to install geth and other tools + +{{range .Executables}} +Package: {{$.ExeName .}} +Conflicts: {{$.ExeConflicts .}} +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Built-Using: ${misc:Built-Using} +Description: {{.Description}} + {{.Description}} +{{end}} diff --git a/build/deb/ethereum/deb.copyright b/build/deb/ethereum/deb.copyright new file mode 100644 index 0000000..fe6e36a --- /dev/null +++ b/build/deb/ethereum/deb.copyright @@ -0,0 +1,14 @@ +Copyright 2018 The go-ethereum Authors + +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 . diff --git a/build/deb/ethereum/deb.docs b/build/deb/ethereum/deb.docs new file mode 100644 index 0000000..62deb04 --- /dev/null +++ b/build/deb/ethereum/deb.docs @@ -0,0 +1 @@ +AUTHORS diff --git a/build/deb/ethereum/deb.install b/build/deb/ethereum/deb.install new file mode 100644 index 0000000..5a62459 --- /dev/null +++ b/build/deb/ethereum/deb.install @@ -0,0 +1,5 @@ +build/bin/{{.BinaryName}} usr/bin +{{- if eq .BinaryName "geth" }} +build/deb/ethereum/completions/bash/geth etc/bash_completion.d +build/deb/ethereum/completions/zsh/_geth usr/share/zsh/vendor-completions +{{end -}} diff --git a/build/deb/ethereum/deb.rules b/build/deb/ethereum/deb.rules new file mode 100644 index 0000000..3287e15 --- /dev/null +++ b/build/deb/ethereum/deb.rules @@ -0,0 +1,37 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# Launchpad rejects Go's access to $HOME, use custom folders +export GOCACHE=/tmp/go-build +export GOPATH=/tmp/gopath +export GOROOT_BOOTSTRAP=/usr/lib/go + +override_dh_auto_clean: + # Don't try to be smart Launchpad, we know our build rules better than you + +override_dh_auto_build: + # We can't download a fresh Go within Launchpad, so we're shipping and building + # one on the fly. However, we can't build it inside the go-ethereum folder as + # bootstrapping clashes with go modules, so build in a sibling folder. + # + # We're also shipping the bootstrapper as of Go 1.20 as it had minimum version + # requirements opposed to older versions of Go. + (mv .goboot-1 ../ && cd ../.goboot-1/src && ./make.bash) + (mv .goboot-2 ../ && cd ../.goboot-2/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-1 ./make.bash) + (mv .go ../ && cd ../.go/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-2 ./make.bash) + + # We can't download external go modules within Launchpad, so we're shipping the + # entire dependency source cache with go-ethereum. + mkdir -p $(GOPATH)/pkg + mv .mod $(GOPATH)/pkg/mod + + # A fresh Go was built, all dependency downloads faked, hope build works now + ../.go/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}} -ubuntu {{.Distro}} + +override_dh_auto_test: + +%: + dh $@ diff --git a/build/goimports.sh b/build/goimports.sh new file mode 100755 index 0000000..1fcace6 --- /dev/null +++ b/build/goimports.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +find_files() { + find . ! \( \ + \( \ + -path '.github' \ + -o -path './build/_workspace' \ + -o -path './build/bin' \ + -o -path './crypto/bn256' \ + -o -path '*/vendor/*' \ + \) -prune \ + \) -name '*.go' +} + +GOFMT="gofmt -s -w" +GOIMPORTS="goimports -w" +find_files | xargs $GOFMT +find_files | xargs $GOIMPORTS diff --git a/build/nsis.envvarupdate.nsh b/build/nsis.envvarupdate.nsh new file mode 100644 index 0000000..95c2f1f --- /dev/null +++ b/build/nsis.envvarupdate.nsh @@ -0,0 +1,327 @@ +/** + * EnvVarUpdate.nsh + * : Environmental Variables: append, prepend, and remove entries + * + * WARNING: If you use StrFunc.nsh header then include it before this file + * with all required definitions. This is to avoid conflicts + * + * Usage: + * ${EnvVarUpdate} "ResultVar" "EnvVarName" "Action" "RegLoc" "PathString" + * + * Credits: + * Version 1.0 + * * Cal Turney (turnec2) + * * Amir Szekely (KiCHiK) and e-circ for developing the forerunners of this + * function: AddToPath, un.RemoveFromPath, AddToEnvVar, un.RemoveFromEnvVar, + * WriteEnvStr, and un.DeleteEnvStr + * * Diego Pedroso (deguix) for StrTok + * * Kevin English (kenglish_hi) for StrContains + * * Hendri Adriaens (Smile2Me), Diego Pedroso (deguix), and Dan Fuhry + * (dandaman32) for StrReplace + * + * Version 1.1 (compatibility with StrFunc.nsh) + * * techtonik + * + * http://nsis.sourceforge.net/Environmental_Variables:_append%2C_prepend%2C_and_remove_entries + * + */ + + +!ifndef ENVVARUPDATE_FUNCTION +!define ENVVARUPDATE_FUNCTION +!verbose push +!verbose 3 +!include "LogicLib.nsh" +!include "WinMessages.NSH" +!include "StrFunc.nsh" + +; ---- Fix for conflict if StrFunc.nsh is already includes in main file ----------------------- +!macro _IncludeStrFunction StrFuncName + !ifndef ${StrFuncName}_INCLUDED + ${${StrFuncName}} + !endif + !ifndef Un${StrFuncName}_INCLUDED + ${Un${StrFuncName}} + !endif + !define un.${StrFuncName} '${Un${StrFuncName}}' +!macroend + +!insertmacro _IncludeStrFunction StrTok +!insertmacro _IncludeStrFunction StrStr +!insertmacro _IncludeStrFunction StrRep + +; ---------------------------------- Macro Definitions ---------------------------------------- +!macro _EnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString + Push "${EnvVarName}" + Push "${Action}" + Push "${RegLoc}" + Push "${PathString}" + Call EnvVarUpdate + Pop "${ResultVar}" +!macroend +!define EnvVarUpdate '!insertmacro "_EnvVarUpdateConstructor"' + +!macro _unEnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString + Push "${EnvVarName}" + Push "${Action}" + Push "${RegLoc}" + Push "${PathString}" + Call un.EnvVarUpdate + Pop "${ResultVar}" +!macroend +!define un.EnvVarUpdate '!insertmacro "_unEnvVarUpdateConstructor"' +; ---------------------------------- Macro Definitions end------------------------------------- + +;----------------------------------- EnvVarUpdate start---------------------------------------- +!define hklm_all_users 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' +!define hkcu_current_user 'HKCU "Environment"' + +!macro EnvVarUpdate UN + +Function ${UN}EnvVarUpdate + + Push $0 + Exch 4 + Exch $1 + Exch 3 + Exch $2 + Exch 2 + Exch $3 + Exch + Exch $4 + Push $5 + Push $6 + Push $7 + Push $8 + Push $9 + Push $R0 + + /* After this point: + ------------------------- + $0 = ResultVar (returned) + $1 = EnvVarName (input) + $2 = Action (input) + $3 = RegLoc (input) + $4 = PathString (input) + $5 = Orig EnvVar (read from registry) + $6 = Len of $0 (temp) + $7 = tempstr1 (temp) + $8 = Entry counter (temp) + $9 = tempstr2 (temp) + $R0 = tempChar (temp) */ + + ; Step 1: Read contents of EnvVarName from RegLoc + ; + ; Check for empty EnvVarName + ${If} $1 == "" + SetErrors + DetailPrint "ERROR: EnvVarName is blank" + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ; Check for valid Action + ${If} $2 != "A" + ${AndIf} $2 != "P" + ${AndIf} $2 != "R" + SetErrors + DetailPrint "ERROR: Invalid Action - must be A, P, or R" + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ${If} $3 == HKLM + ReadRegStr $5 ${hklm_all_users} $1 ; Get EnvVarName from all users into $5 + ${ElseIf} $3 == HKCU + ReadRegStr $5 ${hkcu_current_user} $1 ; Read EnvVarName from current user into $5 + ${Else} + SetErrors + DetailPrint 'ERROR: Action is [$3] but must be "HKLM" or HKCU"' + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ; Check for empty PathString + ${If} $4 == "" + SetErrors + DetailPrint "ERROR: PathString is blank" + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ; Make sure we've got some work to do + ${If} $5 == "" + ${AndIf} $2 == "R" + SetErrors + DetailPrint "$1 is empty - Nothing to remove" + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + + ; Step 2: Scrub EnvVar + ; + StrCpy $0 $5 ; Copy the contents to $0 + ; Remove spaces around semicolons (NOTE: spaces before the 1st entry or + ; after the last one are not removed here but instead in Step 3) + ${If} $0 != "" ; If EnvVar is not empty ... + ${Do} + ${${UN}StrStr} $7 $0 " ;" + ${If} $7 == "" + ${ExitDo} + ${EndIf} + ${${UN}StrRep} $0 $0 " ;" ";" ; Remove ';' + ${Loop} + ${Do} + ${${UN}StrStr} $7 $0 "; " + ${If} $7 == "" + ${ExitDo} + ${EndIf} + ${${UN}StrRep} $0 $0 "; " ";" ; Remove ';' + ${Loop} + ${Do} + ${${UN}StrStr} $7 $0 ";;" + ${If} $7 == "" + ${ExitDo} + ${EndIf} + ${${UN}StrRep} $0 $0 ";;" ";" + ${Loop} + + ; Remove a leading or trailing semicolon from EnvVar + StrCpy $7 $0 1 0 + ${If} $7 == ";" + StrCpy $0 $0 "" 1 ; Change ';' to '' + ${EndIf} + StrLen $6 $0 + IntOp $6 $6 - 1 + StrCpy $7 $0 1 $6 + ${If} $7 == ";" + StrCpy $0 $0 $6 ; Change ';' to '' + ${EndIf} + ; DetailPrint "Scrubbed $1: [$0]" ; Uncomment to debug + ${EndIf} + + /* Step 3. Remove all instances of the target path/string (even if "A" or "P") + $6 = bool flag (1 = found and removed PathString) + $7 = a string (e.g. path) delimited by semicolon(s) + $8 = entry counter starting at 0 + $9 = copy of $0 + $R0 = tempChar */ + + ${If} $5 != "" ; If EnvVar is not empty ... + StrCpy $9 $0 + StrCpy $0 "" + StrCpy $8 0 + StrCpy $6 0 + + ${Do} + ${${UN}StrTok} $7 $9 ";" $8 "0" ; $7 = next entry, $8 = entry counter + + ${If} $7 == "" ; If we've run out of entries, + ${ExitDo} ; were done + ${EndIf} ; + + ; Remove leading and trailing spaces from this entry (critical step for Action=Remove) + ${Do} + StrCpy $R0 $7 1 + ${If} $R0 != " " + ${ExitDo} + ${EndIf} + StrCpy $7 $7 "" 1 ; Remove leading space + ${Loop} + ${Do} + StrCpy $R0 $7 1 -1 + ${If} $R0 != " " + ${ExitDo} + ${EndIf} + StrCpy $7 $7 -1 ; Remove trailing space + ${Loop} + ${If} $7 == $4 ; If string matches, remove it by not appending it + StrCpy $6 1 ; Set 'found' flag + ${ElseIf} $7 != $4 ; If string does NOT match + ${AndIf} $0 == "" ; and the 1st string being added to $0, + StrCpy $0 $7 ; copy it to $0 without a prepended semicolon + ${ElseIf} $7 != $4 ; If string does NOT match + ${AndIf} $0 != "" ; and this is NOT the 1st string to be added to $0, + StrCpy $0 $0;$7 ; append path to $0 with a prepended semicolon + ${EndIf} ; + + IntOp $8 $8 + 1 ; Bump counter + ${Loop} ; Check for duplicates until we run out of paths + ${EndIf} + + ; Step 4: Perform the requested Action + ; + ${If} $2 != "R" ; If Append or Prepend + ${If} $6 == 1 ; And if we found the target + DetailPrint "Target is already present in $1. It will be removed and" + ${EndIf} + ${If} $0 == "" ; If EnvVar is (now) empty + StrCpy $0 $4 ; just copy PathString to EnvVar + ${If} $6 == 0 ; If found flag is either 0 + ${OrIf} $6 == "" ; or blank (if EnvVarName is empty) + DetailPrint "$1 was empty and has been updated with the target" + ${EndIf} + ${ElseIf} $2 == "A" ; If Append (and EnvVar is not empty), + StrCpy $0 $0;$4 ; append PathString + ${If} $6 == 1 + DetailPrint "appended to $1" + ${Else} + DetailPrint "Target was appended to $1" + ${EndIf} + ${Else} ; If Prepend (and EnvVar is not empty), + StrCpy $0 $4;$0 ; prepend PathString + ${If} $6 == 1 + DetailPrint "prepended to $1" + ${Else} + DetailPrint "Target was prepended to $1" + ${EndIf} + ${EndIf} + ${Else} ; If Action = Remove + ${If} $6 == 1 ; and we found the target + DetailPrint "Target was found and removed from $1" + ${Else} + DetailPrint "Target was NOT found in $1 (nothing to remove)" + ${EndIf} + ${If} $0 == "" + DetailPrint "$1 is now empty" + ${EndIf} + ${EndIf} + + ; Step 5: Update the registry at RegLoc with the updated EnvVar and announce the change + ; + ClearErrors + ${If} $3 == HKLM + WriteRegExpandStr ${hklm_all_users} $1 $0 ; Write it in all users section + ${ElseIf} $3 == HKCU + WriteRegExpandStr ${hkcu_current_user} $1 $0 ; Write it to current user section + ${EndIf} + + IfErrors 0 +4 + MessageBox MB_OK|MB_ICONEXCLAMATION "Could not write updated $1 to $3" + DetailPrint "Could not write updated $1 to $3" + Goto EnvVarUpdate_Restore_Vars + + ; "Export" our change + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + EnvVarUpdate_Restore_Vars: + ; + ; Restore the user's variables and return ResultVar + Pop $R0 + Pop $9 + Pop $8 + Pop $7 + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Push $0 ; Push my $0 (ResultVar) + Exch + Pop $0 ; Restore his $0 + +FunctionEnd + +!macroend ; EnvVarUpdate UN +!insertmacro EnvVarUpdate "" +!insertmacro EnvVarUpdate "un." +;----------------------------------- EnvVarUpdate end---------------------------------------- + +!verbose pop +!endif \ No newline at end of file diff --git a/build/nsis.geth.nsi b/build/nsis.geth.nsi new file mode 100644 index 0000000..03710dd --- /dev/null +++ b/build/nsis.geth.nsi @@ -0,0 +1,70 @@ +# Builds a Windows installer with NSIS. +# It expects the following command line arguments: +# - OUTPUTFILE, filename of the installer (without extension) +# - MAJORVERSION, major build version +# - MINORVERSION, minor build version +# - BUILDVERSION, build id version +# +# The created installer executes the following steps: +# 1. install geth for all users +# 2. install optional development tools such as abigen +# 3. create an uninstaller +# 4. configures the Windows firewall for geth +# 5. create geth, attach and uninstall start menu entries +# 6. configures the registry that allows Windows to manage the package through its platform tools +# 7. adds the environment system wide variable ETHEREUM_SOCKET +# 8. adds the install directory to %PATH% +# +# Requirements: +# - NSIS, http://nsis.sourceforge.net/Main_Page +# - NSIS Large Strings build, http://nsis.sourceforge.net/Special_Builds +# - SFP, http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin (put dll in NSIS\Plugins\x86-ansi) +# +# After installing NSIS extra the NSIS Large Strings build zip and replace the makensis.exe and the +# files found in Stub. +# +# based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller +# +# TODO: +# - sign installer +CRCCheck on + +!define GROUPNAME "Ethereum" +!define APPNAME "Geth" +!define DESCRIPTION "Official Go implementation of the Ethereum protocol" +!addplugindir .\ + +# Require admin rights on NT6+ (When UAC is turned on) +RequestExecutionLevel admin + +# Use LZMA compression +SetCompressor /SOLID lzma + +!include LogicLib.nsh +!include PathUpdate.nsh +!include EnvVarUpdate.nsh + +!macro VerifyUserIsAdmin +UserInfo::GetAccountType +pop $0 +${If} $0 != "admin" # Require admin rights on NT4+ + messageBox mb_iconstop "Administrator rights required!" + setErrorLevel 740 # ERROR_ELEVATION_REQUIRED + quit +${EndIf} +!macroend + +function .onInit + # make vars are global for all users since geth is installed global + setShellVarContext all + !insertmacro VerifyUserIsAdmin + + ${If} ${ARCH} == "amd64" + StrCpy $InstDir "$PROGRAMFILES64\${APPNAME}" + ${Else} + StrCpy $InstDir "$PROGRAMFILES32\${APPNAME}" + ${Endif} +functionEnd + +!include install.nsh +!include uninstall.nsh diff --git a/build/nsis.install.nsh b/build/nsis.install.nsh new file mode 100644 index 0000000..9b73148 --- /dev/null +++ b/build/nsis.install.nsh @@ -0,0 +1,103 @@ +Name "geth ${MAJORVERSION}.${MINORVERSION}.${BUILDVERSION}" # VERSION variables set through command line arguments +InstallDir "$InstDir" +OutFile "${OUTPUTFILE}" # set through command line arguments + +# Links for "Add/Remove Programs" +!define HELPURL "https://github.com/ethereum/go-ethereum/issues" +!define UPDATEURL "https://github.com/ethereum/go-ethereum/releases" +!define ABOUTURL "https://github.com/ethereum/go-ethereum#ethereum-go" +!define /date NOW "%Y%m%d" + +PageEx license + LicenseData {{.License}} +PageExEnd + +# Install geth binary +Section "Geth" GETH_IDX + SetOutPath $INSTDIR + file {{.Geth}} + + # Create start menu launcher + createDirectory "$SMPROGRAMS\${APPNAME}" + createShortCut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\geth.exe" + createShortCut "$SMPROGRAMS\${APPNAME}\Attach.lnk" "$INSTDIR\geth.exe" "attach" + createShortCut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" + + # Firewall - remove rules (if exists) + SimpleFC::AdvRemoveRule "Geth incoming peers (TCP:30303)" + SimpleFC::AdvRemoveRule "Geth outgoing peers (TCP:30303)" + SimpleFC::AdvRemoveRule "Geth UDP discovery (UDP:30303)" + + # Firewall - add rules + SimpleFC::AdvAddRule "Geth incoming peers (TCP:30303)" "" 6 1 1 2147483647 1 "$INSTDIR\geth.exe" "" "" "Ethereum" 30303 "" "" "" + SimpleFC::AdvAddRule "Geth outgoing peers (TCP:30303)" "" 6 2 1 2147483647 1 "$INSTDIR\geth.exe" "" "" "Ethereum" "" 30303 "" "" + SimpleFC::AdvAddRule "Geth UDP discovery (UDP:30303)" "" 17 2 1 2147483647 1 "$INSTDIR\geth.exe" "" "" "Ethereum" "" 30303 "" "" + + # Set default IPC endpoint (https://github.com/ethereum/EIPs/issues/147) + ${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc" + ${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "A" "HKLM" "\\.\pipe\geth.ipc" + + # Add instdir to PATH + Push "$INSTDIR" + Call AddToPath +SectionEnd + +# Install optional develop tools. +Section /o "Development tools" DEV_TOOLS_IDX + SetOutPath $INSTDIR + {{range .DevTools}}file {{.}} + {{end}} +SectionEnd + +# Return on top of stack the total size (as DWORD) of the selected/installed sections. +Var GetInstalledSize.total +Function GetInstalledSize + StrCpy $GetInstalledSize.total 0 + + ${if} ${SectionIsSelected} ${GETH_IDX} + SectionGetSize ${GETH_IDX} $0 + IntOp $GetInstalledSize.total $GetInstalledSize.total + $0 + ${endif} + + ${if} ${SectionIsSelected} ${DEV_TOOLS_IDX} + SectionGetSize ${DEV_TOOLS_IDX} $0 + IntOp $GetInstalledSize.total $GetInstalledSize.total + $0 + ${endif} + + IntFmt $GetInstalledSize.total "0x%08X" $GetInstalledSize.total + Push $GetInstalledSize.total +FunctionEnd + +# Write registry, Windows uses these values in various tools such as add/remove program. +# PowerShell: Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, InstallLocation, InstallDate | Format-Table –AutoSize +function .onInstSuccess + # Save information in registry in HKEY_LOCAL_MACHINE branch, Windows add/remove functionality depends on this + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "DisplayName" "${GROUPNAME} - ${APPNAME} - ${DESCRIPTION}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "InstallDate" "${NOW}" + # Wait for Alex + #WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "DisplayIcon" "$\"$INSTDIR\logo.ico$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "Publisher" "${GROUPNAME}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "HelpLink" "${HELPURL}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "URLUpdateInfo" "${UPDATEURL}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "URLInfoAbout" "${ABOUTURL}" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "DisplayVersion" "${MAJORVERSION}.${MINORVERSION}.${BUILDVERSION}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "VersionMajor" ${MAJORVERSION} + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "VersionMinor" ${MINORVERSION} + # There is no option for modifying or repairing the install + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "NoRepair" 1 + + Call GetInstalledSize + Pop $0 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "EstimatedSize" "$0" + + # Create uninstaller + writeUninstaller "$INSTDIR\uninstall.exe" +functionEnd + +Page components +Page directory +Page instfiles diff --git a/build/nsis.pathupdate.nsh b/build/nsis.pathupdate.nsh new file mode 100644 index 0000000..f54b7e3 --- /dev/null +++ b/build/nsis.pathupdate.nsh @@ -0,0 +1,153 @@ +!include "WinMessages.nsh" + +; see https://support.microsoft.com/en-us/kb/104011 +!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' +; HKEY_LOCAL_MACHINE = 0x80000002 + +; AddToPath - Appends dir to PATH +; (does not work on Win9x/ME) +; +; Usage: +; Push "dir" +; Call AddToPath +Function AddToPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + + ; NSIS ReadRegStr returns empty string on string overflow + ; Native calls are used here to check actual length of PATH + ; $4 = RegOpenKey(HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\Session Manager\Environment", &$3) + System::Call "advapi32::RegOpenKey(i 0x80000002, t'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', *i.r3) i.r4" + IntCmp $4 0 0 done done + + ; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2)) + ; RegCloseKey($3) + System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4" + System::Call "advapi32::RegCloseKey(i $3)" + + IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA + DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}" + Goto done + + IntCmp $4 0 +5 ; $4 != NO_ERROR + IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND + DetailPrint "AddToPath: unexpected error code $4" + Goto done + StrCpy $1 "" + + ; Check if already in PATH + Push "$1;" + Push "$0;" + Call StrStr + Pop $2 + StrCmp $2 "" 0 done + Push "$1;" + Push "$0\;" + Call StrStr + Pop $2 + StrCmp $2 "" 0 done + + ; Prevent NSIS string overflow + StrLen $2 $0 + StrLen $3 $1 + IntOp $2 $2 + $3 + IntOp $2 $2 + 2 ; $2 = strlen(dir) + strlen(PATH) + sizeof(";") + IntCmp $2 ${NSIS_MAX_STRLEN} +4 +4 0 + DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, new length $2 > ${NSIS_MAX_STRLEN}." + Goto done + + ; Append dir to PATH + DetailPrint "Add to PATH: $0" + StrCpy $2 $1 1 -1 + StrCmp $2 ";" 0 +2 + StrCpy $1 $1 -1 ; remove trailing ';' + StrCmp $1 "" +2 ; no leading ';' + StrCpy $0 "$1;$0" + + WriteRegExpandStr ${Environ} "PATH" $0 + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +done: + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; RemoveFromPath - Removes dir from PATH +; +; Usage: +; Push "dir" +; Call RemoveFromPath +Function un.RemoveFromPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + + ; NSIS ReadRegStr returns empty string on string overflow + ; Native calls are used here to check actual length of PATH + ; $4 = RegOpenKey(HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Control\Session Manager\Environment", &$3) + System::Call "advapi32::RegOpenKey(i 0x80000002, t'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', *i.r3) i.r4" + IntCmp $4 0 0 done done + + ; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2)) + ; RegCloseKey($3) + System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4" + System::Call "advapi32::RegCloseKey(i $3)" + + IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA + DetailPrint "RemoveFromPath: original length $2 > ${NSIS_MAX_STRLEN}" + MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}" + Goto done + + IntCmp $4 0 +5 ; $4 != NO_ERROR + IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND + DetailPrint "RemoveFromPath: unexpected error code $4" + Goto done + StrCpy $1 "" + + ; length < ${NSIS_MAX_STRLEN} -> ReadRegStr can be used + ReadRegStr $1 ${Environ} "PATH" + StrCpy $5 $1 1 -1 + StrCmp $5 ";" +2 + StrCpy $1 "$1;" ; ensure trailing ';' + Push $1 + Push "$0;" + Call un.StrStr + Pop $2 ; pos of our dir + StrCmp $2 "" done + + DetailPrint "Remove from PATH: $0" + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove + StrCpy $3 "$5$6" + StrCpy $5 $3 1 -1 + StrCmp $5 ";" 0 +2 + StrCpy $3 $3 -1 ; remove trailing ';' + WriteRegExpandStr ${Environ} "PATH" $3 + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + diff --git a/build/nsis.simplefc.dll b/build/nsis.simplefc.dll new file mode 100644 index 0000000000000000000000000000000000000000..73b7d9634deddc41966698106dfcf3950c3cfb65 GIT binary patch literal 179712 zcmdSC4Oo;_`ak~63ozj5pklr!)mS2GBDIM?jlvMV4vww98bslhX91v2cw=EI4N z+IGM7t=-MFcH3$No23$Pccm z)y?|gn(FHLD_0f8t}k47cj2;oWAAkq$+35?h%I!kjdiYFzM?R8;i|QXlln$kZ}~a2 z|0ly7c1fD0i;#vcoi;X%G!@ZukWmDa78r9$)C`pDM zPrU>N_52|*rN^HD&*-IpQsQd8G}!`p0?0;C=nwvC^pa`J5B&arcukv|j*?5#!7SuU z1W{Q-h0*ZGO46^>r_P^BuckhDP}$9Z3jlll*(Av`EjwHI?2H0%Y2kN%KORCn6ILx> zCNGoJGNbGw?;pe?d=plv@dS^+D=8eQ3%`#K@f;HhSFBlg7w97%fmagM;-(*qS5$~- zDvv|F{_B6oqbQgEP?z-g?k~ktqy69bG)CJbL&UUo>(;DTwpNI%c)&L-W7(Rb6~_66 z&J|TQX&iiX5!2VoEAC!VNOIx|27{%{lMer=@NBto)!MrqaQBH?xN7+d_5Ig)H%Ba3 zR=8@}S{XRj(|gq1H7hi)U3mYwKMD_tWcsgck^{f__~qg^3%^~7Ht95eu{YVIi1~Bx zT)pBh`5@9ngkCOZ5T1R1k-XwwN$NnjC2Beqq_AMwT`MSmQDSm<=pjiNT5JO}qP0Kb zM@~;)RkVJYeAmi0B-cqsRUSMdc29I8qF3*8r(2By-}pI_q`GGIxN_jymj!%_@mqmk zPrkqt!nGd$CKa#5;V#DSr~W9Mnqx7DBK|D7!zQi7FT|fsaEJKQe|}Gqm#ds&9%+D0 z>fdMnv~|$UQws~1-5+8$iTxNL)%Xw`UHj-XiFtyfU)Nxb24B+P^BR0sgU@L2X$?N1 z!N)aNt-*&ixKD#yG`LHHD>|QU9jBASEZdh@ zCo-?QK7Fy5czu(WW}*hiX>g1Nts0Eg;0O&45ionfyQeFrN$y)E=aiM{P@bzL{yZ{c zyDD}&!xBA97kjs!Km_fv0Bjbj9%*h{YyfeAYIh;*1>e5Vj#-*9v51J)sCUQsuGXHJtL-v5t@R1hX85Z}T z**}&=0fS<;vIk9u2hH(aO0m@#-=-9q*+C?1x)h?qZ?4h#O>B7bMQ2%}XOT)$HE3c- zSzFm9>IF+8-X-8Smo?Bhtn3R3>O?6ruGX!TV$tQI{sP~D4FM&&A}C+S4%Bk+k*`tn z7`Re$Q5`dbgC9XO6x=|?raW@k5!4xPOhfaImR1`DJ`8K3r5yfT4@&2#A7-pZT#&6h<|M)dLHu zQ*gbKigdi}A|ZDEok1NV*JyXK)hx+F>6Oj5nv`g`%<&)#ZzQhn`j}pF(xK7Kn%J4L z&!x6t&=6xSs|#YFR(6tJQ4AB|QpVtEihp0j!fqDDNnuM(Y_r*LG1fB{$h#f91YdaVG2im3EQv0s&45|qE=t~U#csLfGZX<_ZCA)??5LBUN{ z<~L~6fuD+wy&n%S)u<6Mxrc}m)*=HNVNL#~NYB0l2_sfYHjRLPUhK_5>ehM)@TgLX zemm55&auG11K?^|WAHYR@ulPer}HI$V)BFL(s5`YhtaV34oD=_j_hC=qA_LkG^Sfo zJ7^pt}fmRw|V{)Zk5L89e%e?WXl>L^lZsQj7i9#isv zoKuLAH#3y$Fle|>&C5MmlE)w-vk7(zM@T(IgQNd@0UV9cFondJ+82UyC{#Ou3*rWI@>i( zlH@4WYXZDfz4kD<1S!8Em-V+uunDe}0ki<V-Kq4Hg)WQbQTjFkI z)^9p&sXAyp=h<2lywH0o(zY158p1rtU6<&|TeN7o620;oko5yHUIucn@4i?`@_+se z#m=WPWV=$yEm_L zl-3t23l#XNEGQ+HV1W}Z(Qj9H@m(SM0vu;NEH2*`*hSB4xg%!*9Lp z%|=pTmHDiLNs3-^7}5WS9MB_d5S76n&1RUAcNRt%W|#r4Vgt%9kiV0wYMiQ(MkDCE z*dDjfST#cDz7Sn(X*5J&*1`7kx1i`3d)*g|#b&mrY&VpaOED>Z7JGwXuY((`$$iEM zq$kn7O5xNEs)a9a50fN}U)&eW#r@oS^ym>hM!pA)QecX@jLA^vj=hBOf`$hHpHUWN zUG$W-zJ^B1>}c63$|K&(b|v=jtb^VQhQg7VbY7>EV+o6y6`JB3lGmF;p%{)yyb|#Z zpE55Rh^k{*OJa~2{(&%;+-EdlHUtbU%<#P4IgqtO$K)af%P^6z`?^c4@$$H)gA`$d z(JCRz;CiXt`9BZmF-x9wc#V<9oK>Q`v5dXeAU@nP4>()UDV7Ns( z=Y3Mo{e|CQ@UAMu$X|sQTVvH#d?nw+K8`ZefQinjvF#hMgBw8Dr_y z9T;au3xQLXRCg$gjmmKU6o`JBQ5lGlYM#+Q4#`t64eBd8lrta!LzBT;O zCZ>T7cj^!XOPg(~!(3ukr4KSD`Qx`zpnI2|4H`j6^jis#z>uCqGp1JniY3Ox(6w!> ztt!R(+;gm(KclL86O^fFw9)?oyOvl@6|FQP_0MAw$t^`u0#{sw?sg-KFyPT{kgewA zh9VPhLna|_j7s~vxp4;x9QQ*g-0O@;AShtB8wCk9I(L1vRxZpfXpnobD$U5#p)9B? zmHV)Iz7Rx#EkuC6Mz<(Wj14I=hVyJ+6?$4P-_|?9^*>0U@-4Bs!h;&}7Cv zy-L-&Z2oULDw>@pP3xXP0~TY?w)dz8{!Of6Bh7fCJGT6;oJ4b!7>93ukVIjz5&oqC zHdvWvN**N}l~Mk?G^ywdiK_rHB-GA1cp0Kn7Ln-jZ2_KSr&RiEoKtd_Nahlg!HJqt z?2@Q5-OW@}>`|F9F zXBdO1MF?Syy~HrV_z7%cw1$2HOhw6Ai`HE@Bl$(}wcI0{%E(c=If@AE_RWVLOWd8a za^JPoFD5KkuK7VP5_=By=Z$cm>ULi=mxR4dbN#Z5)KV;u{GJ%-y<8W)D?z>=mD*tN zLXTl8licJS_Pv;WMHd-Vipx6u*R#F7)VJtLmGnnCSd=n5z-9+%G>In$;B1~LRBX_$ z@zHP8jTFjtER4lIs?+3=RMDj~;sgw}t-c2FsN||dy}tK`P>;>*-DnJp7UferM}1_8+Cb(sNeETSC|-aL5DnrgwcV4= zrP1C^z_(1Rm_qAMRLn!jjkQCJ*wyE~dZ$H25lLzPgGaEh=!d*QhokYvg%Oj^dv&s% zk-}pR6LmwT&N}pUE?G-X1_9rjctADRB|A*t^Zzsa4VWwP`H&#B+o2j}f#g);qjOxG z;HOUTxh$0`fmGt=Fg_6CE#!??fIGel${_lDon9WH^^g~gA>BtNUjHp_lg=N<8r_?W zP#iS)40mrb_2@?tXCO*oILdc{gX{nrph6EHz7h~le=(V6T&BepnC+MBg zckR+E2AE}_{GUd7eVD9ENZU!EA-)kKV53IE-J;jP+|^i|O_K#JAGp-4L#<(mh^P7) zZYEN-hC%prLROev8cnj%r%iE6H9Fs4ksF_m;V+0fP6W>e-|H|uqdAb-nv9WqNk2?c zVNL)akfVPbAB&O*GEfF8P(SdmfuV@^LxlWO446NngcwNpCxk{F*QlcoFM0%;26_2W zWC_I;N!j}PLhYjW82XcBhU^-lHiw=i)Z7O0&UVqXg>WO2LM<%0twhfnU>E6+nbY1!`7k|d$IF9|eWm2G z;xHq(ALLVimPGQmG2atO8mClvHV+C88n+(Z2lYFBYINKRdGYHLTbUR3Bn-e2K2Zyo z`_$;Uu~#@VRqU20RW!>(9LlcXpqmhHQKUy6s_48mdf!nLABHjn5Q0dL)9*zs*TWYn zq8&<;nlq*N5-C6rpL;7T%IfNzY~fS!#$1RmF`LPo=s{o0$M=dcjE|xiH6q4R6Yq~V zXn_oqO268dD}#*4o$rU9rSUFR*Qru>s!5%Z5%+_G=%u{X*_VHm3VctX!0>YXEu#( ze@@xy{>;$+>>W$JRdPaiRdG@`1@!+;3vo9X+>HoF*WyiCRv4c0z()PJSZ}ratno;S z^+>T*|5QQQEUR9!99d)4AL)MT$T$ppjb8Uz)A1DRQ@MG>m(+2Bwc)5;IBJ_glJ5>^ zWuXI!{{e%BO23& zJTcaWvIVn;kf@4?&(R2-rWOS|UOmkJLd=HU1Tzeek`_%5^J=LoE!u$*7=AGhvK!jD zum$zw&%)sj-0KWa^el#K37$d3U{fF~RFS#jyckAa4-`-}js+nE<5UC@{4O{kXdduA z+ngt40(oci*S-t}GcAt;Bj$E31+Ylg9A*Z70_345j^Ob~r{-9_Kxs&TyqcAa=mHp$ z#=mkHbNoJ;a%`qN!FM;Ro|=M7$iQ1PV{FVAaW>8-`EMY9o9Yi{L0HMd^Sy}Uz^=v& z>m~tW#{jrCXJT6aDq^mdR=NPPR!j37RT-HM9s!!&fuN<_giMw2g0juV*A81Myogi~ zoMT)q1%JiczX%3xmhvy)H@*yyB7K5qHBdPaM(Gi@2i~hC9s+?Zja z02GVLqh!Pop|2j6jjPZ5`@y8>#vsACy2Gzu{U1IHNl_#@NiEJRhvmUQcs+cf)S}%1 zJ%lPFmbjGQS?Wd5#m|Gmg2oKgR_!We;y0$pq z8a%*S{KFGl{ex3S{AQXYN2IDWz$fhOyMaZM)QdFyR@)_WjNmnzUSN(`{;cMPZfZ$9 zueOa>4j0}?ZDV6x!RS{H7o@&=*olS_%pSqoAeO>uC8sj;f_x zIivgr8W;C;j2*{oQrOrS2~b z@~YVv3Kd@BhKi_-#N`YebGDp(RkBO z{to`t3zfz#A1J$ao`^0`b}9QwzeKjbsO%@j^THj%Rtd`Nctdhs953JLx)?9t;ksz0$0j+?bum}Yab29~94O*#k~7ICt-Mu^^&9aV@A6M{ zU5wl~Iw`R6nku79=h3wwCiM9imp+gqD?BB0{E->{@XnUa_DyMv<39CIz4SgJ_`UCe z^%fCTKJL1>tkB@{$EwIS^l|z7yDo-pY;t*{fOq%K6UGqMj~6AJS0>R+l-hUMMN#pX z^@y4ukmz|P7QID*k`@kCb{FcQpzJ}&C=>!~0ZmatxcC_w=0G5r2YUKYLD?n}V+-{o z&x5FPte)h`H?vJKhD|h(j(3=o_LRoz9Ok(Fjx1$=`?EpE2v`h^i*o%(`JaJnQ7%-r zF4HV@d$qEEN9>K5{&(3nFL(QFj;yzABo&VWH9z~So_VI#7>Ti_uQOB(>r5PrXhktHoi$GM8{zD{Pgk<7>BZ0qB9?}a* zmp`iY#qr43uRrqjV$yl^=L(jp4!E!oApV}w+XR%PU11^D1(W8oJ#E%p4zERXSvv-( zF4rHQxJq@!zVq%-)ivgxuSck^F8v>3h0C@*ZO3)O75&^ne!Xyw{j&T~Xem(C5&Oj( zkqf2+obB3)=t{=+lMl3((4POZo6vbKW5sbtzYf?aCqH;IksoYV{FT^ z7M0Jl?Rsu#^;^%adoIT|#S+_b!y|h?%(azAJ|5VVbX(aH+vk1W`qklxN4DN!J3i-b z;|IIX?Y`6Y$7{>%w&Hc&D{Oy$V}1Pd|9t;Xt85?t?W5rIQJd-x*tW$y_5M{azV=P( z)DsW;T)S`I@%!hezA*TU()g`cy*XgoJvlcdRdudEQ9Nzp?vzu(KhCTCa@y}c?fTP2 z|K=C6(jLBX^^&38*EheNw&B#ib8$D8mtCLU997*a-SF&Rs?!I*5cpU0srTzIq%V59 z#uxE)<+aOyn!l4HS1Td zS~73`EGfg0GkaOlJqwntajpOfQtPRwj*O?2Xy-LX-zOb38biy5iL4qr?l4<_FkyFP zzp3iz6Jz3p*2rdg1hbL1DS1Y>GkQj4if&R$f7h4CoyO_Tb7@;zz#C>ehR#CIg#N(D z4C^zTy7F&D_UA>VF_kWdiP#(FM^>KTRtSB0NbU1r3M!6}%?Hw`Qi9qKm>N;qB4`>_M zuh;?)+i(msq6n_QW}vj7hxZ}IL;JBRfk9AFe*rHQMp)k4Z7>C|34R9Uiq&)7PNj+U z_gTk+V(8c&n_Ob6k{`gPgPd*_AZb~7b4Xe+Bo1fr#@LnEM8oI=3~N!^frfx`tGYlL z?qE{TVe}8Kf^iIk?M7aR+#ntpYShMoU$;t@KVZPDEiA1Ac8DIYK#K*!q2tia`bJ zR(FHGa!&ZCSc)+b3Hh{88?P@SfHu_KB$UfMB+M7s!bbpg=LM*ZP@ATg z)9P5Myt!0|$u{}$yj*X!D>ViRN4KFrDL$ya;}^c_7aSCWfp92Hk_S5+^XAk1V&&zAxk_L7kAyJl^<7YiNF2X?XGVTGOl+^yop71;rH-sfxEyk(K zfw{cr%Z!oNcJsT)mNY0DOsQzLlz)qOK`d`r%D;pQ z7BDk|U9BwX!wcW^lW*Y-m{Pk^hk&cnhB!52X=5zv)ac0KaYv{_lX5NPFCr~eJF;e( zWqu zHcS@>4FR#RkBs5BjRUp9zN0*YEG4$i#O^RQ-fldBh;DBfF95y4Q@$5bZax5!*%lG4 zzfvvWRFiKaieHOh>Oa26t93oRl1Fv(R2q;EGHYDQMi_C)8Rg4_li7x#p0m>Qs%@b>`a$0F z=I1P20O^^(Is*I0Na*JUM~lV}nkF{djL9iBiFc!8VyZMmDXMf#GrNCn{7&UJx|L}r z-Ur0-e_^tg;K6o|$n6DHd>f+^ZAWL_DOF- zGa8oIPlK{7SMp27N1l)=56qOU{wd?Xqt_eVJXOBlKdxq% ze{9We|MfK=_^++`$bU87ht(YRUsdy=e?U!(zhBK!e^kv8e|XIvtdRysV@xWsd1ij_ zOfZ-a+v4}yh=RaSQ?>{@7`J{b1Wvw>B6jmIGQguMNoopHd!&Z8)s!U*MU)-G(uT+* zFRP#nXtPR-s)W9Vn`N6h?IB`)jnw>hqf6dTN~!h;k(L#sz|49zdWHni_o9cfcPwX7 zV5wq4C67a<%1YC=$yj0_ot)6Tm7HjpBM>WT7seM7;(A%6yDKQ0l621hU|6aXdtDYR zeD5a2Ww)CUJ(JyGX6vy-EoWsO=1l4)o{HAZ17<1+NdMG`IQn2yMQiETq0x^&##aLM zBJ4T*Av9Bd2oDHsz8KR(mba#;w9I4^VN%~Dup4`4Uh8d5iDI;xaG~iEeFFo4} z|472`fJE^d2>*s2{8s9T~JrXt_?~jBz@KnZU(sSf2Jhu@I%Aj;S zt7g|LeJNGKZg&axed)1nwJ+seI|~!lZPa*bmynAzFn^RP$8NU$bqQi*E@J(aLAh|r z5h;ceLolW+izFQNWkMvD6CK>Y2#yp31_CiKM4B4Sj0KY7`ta8Bfi?y!z^uL^>>& zkL?cz9lRdTP>_CQx6MXD8%5AR@XYhNQb!=ZQVT<-~cJr zx2f9Sr4qcC3A$Vlj**;Kxo?xOnlxBxf)RT#e~Q<#6E;=R2+UaUcJ?i zgb{&0AHA?>%%?^)qVynRMqqkzbX#ynz!~MJnik+Uz|M;?mzaMYVgnI#SR-bATFibT zrimh6It{YC#yQTT5P)jLvjW%D(qR~YPU{x)PNgn)9?R_J)i4p~<@$|n!D)eqDDN1N zYcE`{0++xMML0J9Ow275lg5^w%~pro6XiZ}$-%SXnOD%5uD^s<;W6bYj_yWV0L@6k zGLvdk^(u#ykCXtf2LqHYr4fVrNTp6`s$6?8}l{K;hd@S_)0v16@@U}#YUqb7U6x;{F z$UIt-M7_^bPSXB3i5n0x&s&|a8$7>e-jpbLFpSl5f0(J|K2v5Gk9f39w4?j64SC_vB?#%6RQUA>kr8(9uvdEJ=F_D?!etXGa~G1(sD zIjGsi-Uii%fp%hGwr($?-bQPymh#_1--Gio6&zysce4fEx(i7k%7gg@G;O7g&FN+X zbr*OvkWkI?Q}AX6rOVF=$1=i@4h+fB@{(ke+z(^;oAH{L>-%3Yj_@8(KE%>kw;`jO zGlVFO5SbP>vz!0>6y7d~EDYJ*{9ky@RW4wDZ3xZ{h&}0)p|Zz!8VwqN>XY>EJcn{1 zA;@Mu9)pe{JjvwLW2WlS-XjfDyOTb0>Jx%9oM(Y62Dt^;Eo?(Kj|1fk4Y$nno46i? zIA9t{e|m^=4lId66VrXj<3Ykqr!OxT<7YEUXF!wY1;{-Ql;l=f9}k|Ox}{QK;WMep zQvL`;1kGS2a=M3QbhCxsaZMZby1IEg&(Ly6KQ;<0Ah|QJ95Tm(U|8xX^^j_xLD$tIzt|eHIFGX|Rr4(dM;O@~lz|S?HrK+K zn9DMu)}UZA15Q(wEx@y2)?)(~D^0`$-*&1E=JCC(LR!Zk&vHrjuAfmnJn3l3sJH`I zYaW_(q_n?|CT}KQi%k6e6Po>>D@Xi(;q?Z_9*adEtxBNs5&tU?3Y|F-(mkhmY|@7% z*H;8LUmN#r(np(7)p1?Ab5+K{d^1c`I40A==5@zi;ImK>RC17EL(L^sFtr;SY()6>PvN`Qu)8W|BU1l^-r#?jkD|heu87VcsQN}Ogs#bgu9^Rn zx(52=vaY%5$8-()Eg13&*r)<1m203>Myg8XDpjdi^2k3@U2}z#8Ws3~J{x4PE;?uh zGwpYv&V5l=V#G3Z#LQ$bwhgsZ1o4WN6z@$A;2oW+q1kYV%{D#C=O8)|Q7;c%ga~_= z6I;QH8-Q#Cv@y(Z5&Z9nhC#_dbsG}f1uh+c_v4=+T=db1sL_+KJG*j*j+91~+&_vH z_YcRg9tUMw*Z{TPrU?s;?n}R<6xAdzmGB~7ig8Rrb7gqt0$sxHlqmVyls@v+DQ4N4 z(pOGQ=_k)lvB>>W`pZ!@WDWJn$IxcU+CH~5;I}J%l$l0lrb(G;c6+1emVV=(l%ssi z=1+)HS`14j=-I3Z;cNhBsQ0<0Jl`LKP&Gt3$L+$mO|92oEQ6(|3`&buZg-btDL(;! z#}7+_a*RefZbdn=P>v-i$A%P(rMw)GQ~F!V8KCcf@NjqOl$O#co`mhxzDMDq?BQAD zuILGj;5DSHHzMRca;#NaP*y$@W2CvI?fyG{tiX4M3Ou!Ufrn@X_Qw+nBlq-IQH)Sp zlyl?}#N=Ua>9_tqC}1wObVam^IwrfLs7PNFFo^MI4pm%&r*f*Us@N38)^(%PpBugpq>y1LBrbXQkjEc18ulh?btz!(>b z>*@k$$YfKar#kT|N0B^cqOOIn!|IaQYvnIdJ&r!G|?it-;4N_=ELCS>l`%mm| z-hXKS;iPX0`)o8OeOee6_vz4YY$v~5-06i#!}g?Mfv6A*I@>W~Z5MqYbzI-i`9|a% zuVMdLvQRzc8VO*M3UBx8i_^-m{TA|VaSG4i2BUvs z=>*pJq$=jHXvbKdtutcVm%(Tqh<)PjdXs`;<4CmGp4jRuKw4KZ3TP=mfXvY%=!~@) z0^Tfj9u39xZKsm;DnQYlmx-GAJ))+AC_52F8{(X1xJ+>E16?pQR2<=n8$ChJnfl+jAY$xssOC?d!kyf*On@` z=lE?1bLbeZ7;g)^1&1yVW(wOk6cyK@V-mI_-zlsTV9dZ@Mz0JAUFNPo0m-5RIOlMl zg5`I!;V?$A$rZuUVHm4M)EN2yHbb2w?2xc&`DQ@2p!Dr+n3DBz`;C|n+uHzP*UixB z8gP2Yp;vM^bA)4xiLD0Mn|q!$cHYYs`)OuB4I^-}oy&xHDiFKvr*Tvb#-3!I^JXRg zL~==6$w&;~hteqs9BQ+?oWl)uY<62bP7m{Bl&i{qg5{j%CRj09{t5rmU>{)RZ;^en zh1uH_``P4@vn2+lpzRj})GR*aAEUSRU&Ur4=4{Rm z!0trF#oiXs*Jz-&hOSjf#slX-a@qtwJGZ=SZ!>s-^D*Fbmjra1C%Ek3Q zxB1D|jIf5zdpl4!JOd_4)^eFaiC~cB<&q9vNjK&!H@i#PB&;YN0=qgG@o6>G$^z6R z@!rdSjcE!_=Y9yDcVGfz@8-=^g^<0R0AEa#9lNQAJ?LY_8HYr5-rg3R9&p}(vkvNz zaejXiwSF)oz}6LkX+<{;(71`$e@Hzi?Ozx|vPfoO$XZNWaAv^S&oNKg6HE*6ZK#jD zTqT<3KbXG?OAz$2yZ-d*5o9LC1mCp-=b%W-UvichEjGD44 z5Rff<$I7#LHi_pE3A9Vuwz3=V3wRbh6fZb&qe^TAU3vf^?REqYfCWPtwy;%Q_TGw?}Rv2W$1X;Wv|ol@`p2LH}=M` zPvi5utI~8LM=c-zc`fqTswEsk2?MM#zJWCv4ooY%Ks>cP@)rc#91GMW2CswHAW>Dt zo8W$kUY-gs*5BzutXM!4{(w!_A_iKz+M>PptSDT&Volqexlw^fIJwV?A=;roFZ~z$a)IK!v_1;w5!Xpawu-h!}^jfANvqNu8V(+ zP}G&}zd|8qF1}q2y|#DglAg3ricm`htt!*bv_VTkJJTBN9E;ylRRs~TgBZh~0gtNe z9Qzu-7W*32Fw|YZ!E<}Exg5T-82O0MDQc*Z3ejLchpe={Y9o-Jh+#quuW!R~Xo7D< z=}88)4ETXJvG5jFWp8(^Ht>Jp1?>>e#PhCy6LpX?n0_T!NxS5Xa+Q3ojQ~Qa|AeTl zMSU~0_;oW~$rI!#^*y=8*%oqH%Kro`X@o!?!w$qZsp3#4%A3vS5E0n46hb?J(4s9l zS5wCFa@GI)>DnFH{HhB&KN`0GPIUsYdgta5sptTK*aj+PuPcx@E-PHo zvk!FEy1SOGSy6-%{CAkPN3>TIN%SMoS^PRA5PGqtmPU~LL6`#BpfOND;zp%y`34pRB|MxhGJ?x7>|3T&^r3h+ zBOa6h>9HdeUmbtY`DIAIK#V=N=t(6xiNtc|(vVm(Y^20F;B1UBR1xw=`5wm9o`3TR z=(Y!_dlCtqbWH&r#>E~Zp-*uG6sc7;uPkIk+X_wy#B?_Zu;Qi@E~7PvP_O(Fq3}-* zql8W~MUey&|ER4(MSJ9{SafnuH?FUsQ3Nj131?U)be{40UW5_MU#I3z>yJHlW~L81 z)V*%-9g1P2ejYY=<}%YG*l2-6YD|}f2wv*slxS>KRbR>8{5oiZ{Z@2;ky%WKXgKc% ze6+$FQ8Uq0*3^Tt6_ZRL5yTTQw>b2>BbU_(6g!UFT3fLV+oDeVFz#+iQye+0gwIYeTcE~v*A zLXuXBh|}U%vA(fbK88Gmf#l%+njyY9M3B1A8ugB0C$`5XHdCEj%45TG`x?P_@*MoZr1Mn;qThH@o(p*fL@wIm2D2 zZgcH*ZCNr==Q??tYmaNo{E2!Tr}M{FE!9(kYt>kLJxI_KLq|I{XPb(`lAYa!mGm&8 ztD4hboGAIPrGSwTGEkC9lx&F7XwYS3WFWBcRMi3@Mkm zh7v<3-yqaLqaACvByWl5)e+l2P*&e>@L)L|iMuP!lQPclbjfSnpWT-1G+7?{3?b)t z#>x{ATp2SdgB^99^0+R#EEO~u5>eiT1BEF&8H8i!XW@PJvpxS6`^g_fDnP8+-33a_ zRLm%H;4n|K0b5aIGRpd!^&k-rFv1?lq~J#5G*n48Vr-nF;XYsIl2h0o*Jo|6t~NR4 ze4S0cg~BJX*&UVU1lMOCSC`9a%^<(4&)3Cbh$f2QI_Ys;a5?*AP<|U;wb7u`-WD1N zW9$VUu7(ks%ul2U8t?2;1P%Q8;}AJ-Vl(f_M6@zw9s=tmJ$8k4A$f<`KD4~>P$nlcy$Pp>cbr)hkVvDt_r-h`kFJ z)ivVc3TR$ zHj?WmxV*3tnjS;XiMtk!8k98Ht=**<&|rsvj@34fvI*O5Etz+yzvk!QBY6HRcWU%Q z*t&#`m?sg)E70>msu~cEuE?xovGWgDW+-T!X*X;1UhyYH+>=9U7da!AuRNYtW{_ z6b(++;7uBwsKId>9HT+224giiLW4sz7^A^x4H|#1wkb)2-I{+ugB==d)8JVRat)r= z;0X;L(_o7R4{ESUgZnhNOM~?q^l0!M4UW<1v1%|@gCjIJM1wIJjMku8K$sj9$i0LA z9#6;}Z8E4P4+nn|K$?OCT1c)b zSje@zzN2U4g06qtLT#c5ItWP#CV%{u=iX(IB3J-MGeOyl{zeIrS_# z0c-%{pBzCu7YJ~Q3FBtqckt)Y%h8BkjGG9DJC&kHa{5-v8-^y*gw$VSj4!-IADIQ! z21h^)PR}A#%Me-N1}!ZUC0d;eX#Duo>dXAF0e4S3e-;5=-`B7|h4?|iJ?;GCUg^?% zr#mLnz1b@r1?#+L;or|JFT2~Gn`U=RpOY~+7P`)W->hi$mT7_}?VXeq^$RJQ3{2MG zO&Xl2!AuRN3rM3z7fv&aH5awMOYXT`zRccqmLtIH>(`4;3f6f^UAAG&Nbp+?p3`8v z20JwP9}S+@V5bHH8tl^G1r1)*V7CUp)8HiyVg@hrJ)uE5s7!Rw7?$8E4W8EE=NkM% zgS6N{@wf(k8a$&xzXs20@GA{|t-)_J*rdVz8f@0!0SzA1;Kv#~q`|`)Y|-Em4Ib5C zs|Js0@DmMws=?zL^k{IW2J1BF)nL5_-`8M+1{*cFOM|;LxJQF~HMmcMA87DH4SuA- z8V$ap!B;i-ng(Cj;2RozQ-j+zSgXOeG`K^9Z)@-!4gOn$?`rTp4L+m6ts4BR2DfSO zSq(m?!M|zn?;3nwga6Rr3mSY;gD(Mw+W*?i?PC^1=W_c;0NVe)%k7_nb>43HhyHrI zLw^0%eg97MpSv@jmUwB93=^iN10wC4?pm>4UbSxRa^U&7KMIdVgc{KEQ+LyM{KY2j zrtfFZT(N9@59(^qFW{zd!aW4=mvEcybDi?sg4rwXT~~NN^!|wTwx1(gg#QxVp*!RI z*r%@rc6(vrxY)=3MafLu7K_+d-|#k)9%k(QMj)9*e~Fv@GHgJ$3Ab_+PhbX z`n##{=lMY4SBlq6e3-idA5d7cZX;x_`WJ9hIN`pMI*hWmVaJXCYlnexx`fx-#zmFOW$h`)|?tLWPr&;9~#3O57y&*{%lyC!)Pb(glRh`u8t zIe(sR3cpg^eeG%M)C;HGfm$(6`*w0-JbcdaNYTCi%}nq}0ffcAHO0Y8Ob zDgG$CqY#3(X2snrs$(|%JWnY6O7ZryXUog(x+f%n)$4x&H-%p*?nvUdT)1vc8uRS> z1-uk~<@Kn2R7R){=CN-4d45p%mEuM{78OB6P=guE!0zR!$MRpmPvKXJzn^{Tokeot zvb$6vtUmDzxGDU~aYI)ZP{UDmYRaac=L>~XU0zAMG}-573#{PB~UD8gIxnA ze!48?yO&_Q#J-Y5>}2QZw4_8=rfE2E*Eo#Et8v5vacO`1L$Ju=3X)r3cpg-#PnnAA z5%V_cjj>z8r)A8`&U={x^AS<23G2P+`H(a=`RfKOZtt|*nzT>Z3y3a#^}Ca+c*gl>B8%31Ga^GM~e_#ifgP` ze;?KsnDLF}0ygqmRJ?w;M(($E%hGl|wIq4gHl}3zZ`{c&Y4kepPil?zTswI{g|6Puj z4!t%UOL6Y{F=|^B(oXj>zkyuUydYLye&Ic8-$D)_&ZZmD=zttfH-CuxcJ1AU+0jZ# zfSeuJ1Av{o;8tIptB!mrWwhMyg&EFvB@k~9_&hM3!}}b0`>K{+Qu6s0`T=ylP+Kd5ELrA5)wyI1Kp*yyRi`aNJ(p zp4;jw`EtB7DsE4aQMVtm^6W!LLhhQ__7^U{wLdHNv_{RVvbXUOz@E2=Ia_?U!2i606TzLzS&jm#YHb8 zyfTOoCGlF1Z`mns*O=2ZghZkxfod z)$Et!zGXSb@I9UlI&a*b4NG{9*5|2QWCh1p?Y+K}l&3R(J5wV&*)nwE6ppu)9TCTxItv z?i5I#eQYDPhRr^v;+-kN(0V3$;CD)>%(+Te+_!fi++nGRM|h+C7>#rUFMK9&WxLL= z-!3T)ui=2n?ec9n^3i3NC%>k5b!Er{UNgA5=E%qyC+EIG-M?n69e~tedQ#nn(L1Oc zL6~#6<;_94Fo@$5CH7-G&=w_kV8Dj3S_@pb_o>SMh;q*I=0{Y`n{{YqJFA?Jc(bvC z@+^6Eq9Nv@kH?6V`Y6#)n8p1TH0<~m9}}W3^#<%QybfyU-(95H?PmP2Zyv|ZaLV`J z7fBTvFz%pj*T6x;Ip~xuIyU>zez9dWv^TBiYK)%kWf<|&Bolocx4s(;rqax?8H(q{F{HHe(CYHf@C;xh{bys*^)2xa@seDU7?Opbu(8trFMl~V0MJX~G400tF+ zo4HG{84y0G?9E|9Fcj2Ft>p$pDv8z?8i;LG_Je#GsgaUy%Xa(0@pM}+k`@h9cJlQ| zT=n|&`CCZ#&x)JVahLx1D(7L#oB7eMi#iz(YS-_UNX@PYvLTQtK^U*8*0YC;N{;!;4JUW zKA6y)JlkA4(%to4G0wu34y&2?0`_prDV6q%FLWNMnb`hIJ`<_l3Vd zjL!ziix;JxJmy`%oE>PVVbD$O&o1>^$J%VRu~s~+v2c!!g>#H`Y@BU8g;>YaW9)eN zjkUs$B90wT4=en}Q|$4S4vz7JfF4LPc02;c;5}{(LgHwTftk+33%Nc)a*YGd_;?Oz zBUzQBcmgEDCqV{ODRrYs80D)|2{^AU=*$R5r`4ZsO^U>Q^^_i$-X}CS zq!}dN2Y79rFg|#jB(3s9&&Iqb5RHwA0U`GogBFG8`9qT^22C|Ek~aE zq~!cl1)cWu(o{L}=yqzIL3`l1lUF@x*nt_O^i*Z~*e|!#d$`ec+ENfUabm%XGo1cO zo)=h2z))hkKLQ`9AjwHCF{4S9nBBnvMK`#LlMOc)j&4X#Hb`?f^}Pg0UP&9j@{hu& z3=9dBi9hB&k{~V8P>L{7{!Ok<50>!W;TaPR7{9i{Q;&S{r)OR#mFg|*V zZ|Lrf+;1u04sV>N44vpq*v(2zk--w&4&x3Qi$}djoSHfw$^Tw<^tCrn^es9!9TJ1L zW_nZXCzPDi$L~F%To6aGF+z?4dyc;T&_-sHR6aa~BdEjQB4$*k$Emz+&GZks4~g7%$fM)|?Igyt=22CbY> z6C^I8Z@>hdWCa!;O;$p>P}%#`k&?huttXF+e;RdlX7*%9B+LvSsTKlNIW19br8dllQ)kXAn5xte+B4)pgdzzzkb!8Vi&;kn2 zP?)~+s8{8RItHhkTrml^V0xW{?XDgS+$7_?#@7z!H za|MPab1qHIbaq;{&%QLB?G*Gq*?DQzw{#;Q+kg49h=z0VZEU9?_{q*LLz`v07hiP! zI`0orlHv@sk{!kL$&jZ&rmoWd9Y$tkYBR!W);6DSJdANNtTkjpQ}s8552A5wq!scE zK`I>nN!U0z`RFHu!Im&vwM{~>kg>wc(i9HAPli7wP9&h6-6nX{_C4tc@V}@+Gqj3~ zJ0u|nl8|UsLJDEp`2?hjo&>g>h4lSOHVP(CjN|&_d_{6gDJ-E^wH zVhH5@E7L~>jZNdCiXw19H8&J~fB|Fjkj*18sH77lW9u*?*rJE0GK4g@G7GH5kyX2^GxQlF@Sq}F{zoz#O!|y8GR5Rc;oxk7hKz#mHAP#KahwOw| zRYSrB+^q_G{U`3snaj|C5Pz@pXn>kY{R-kmq|m%Wl_CELb8CA<&o}ARWA+^p+QWzDY-aVyC>VsA=Dz%C*wyJdOPTiU&KP1lH zU*L*!zZpo^~Zsx)-_GoEvI( zp?yAx{QHQ44dqmoaoPDCmLYY^ISdtk(QVdR0WRA~wC3teWzJoxi~=5c}OyoK{kvI)kLQh&NPX)Pc-*1S)-VFxSy&2YL(3cv;o~`S_FLY6f%Eaz&3V#oBtwdPVO|r zlla>0m_M95Arfv|v|WHV?Rs2}W@9TXO-HDSREP-b_$5o>rl?7nUusk%mn#{70K0oHvmD4HNx1ZxDZ(=jO zJhJ==1To7FW!ZymAgDJ)ka6%DJnnHXt9Y1cN5`g8zDp{cX@|6 zqSMU3ZNp{7agFI2A7yI6qp`}?pXB1zRkkmlS7jT)9sQ{Gb?FWLvBbz8``k`uqoRY2 zgU4qV^?l5?L9X^DTN{0 ze-7A+W-L>gp~zaIvYmkWm@8r0dMYaasmLTv>pL-dq`3n6+zCU7&TM1FbcG6LnMD5@ z+wPcsTvV`kV$w)+h9xW(;VTfvIQ=tm5wp3P&6secX0bTEniKf0-72@(60~P#T|J~4 z+EE8NcH1JKb({AT<1qxHTjr*mD3;#?enk@{qeS8{r^Y2Cm1MXir2lac5iC|GjZTVS zT2c8AQVLZ+-0KxRY2KjY>bhPKY}d78unqNk-iYJs%md`AYV#U1LG^}8Hi~bb%DXm1 z5($ul0h`R1&mv9t!ny)zFgQ8G%2B=YYP-kkTrNIZ&`q@TP@Y;qOM*)pTN`->|10K` z3~lKxryC5IkDQQ953O18CiS*A}{FR={xwA3ZEM)(O4qa zT>gRG>^&(Sw^+ygbIm!R=r8Uea@J*{O}acLFTAl+R-zzY9%D|YfJ5sCmX&A`Y`Jbb zn}?`*Z(Q?OR^V;@uwdI-XN^oZW*p4xPCyx=X-vu6N#Q_fNe#O)Np)ugQ{Enj57%3i zd@JS_?`A&1b@5!wkh%CxF?=qGDqEm5v)cRULpVz0C@pyoFBK)~;gcOhr6o%&=v96j z(KJd+mRa|k{HByXR&E`ug)Dt*t#z!EgYfv|PE0N<6BkG$q=Q*$u=2f^3$C$Qf{H3| z24?o^H{rou#vIydh#5zIEFK53dzky z$@v%WDS>m}kC9AFoY3KMtN%P~QIkPM&z_itht%A~g9x=gcTRspp4(Gq6EZpmbgY84 zVxo{{i{?OQueOiO)YmIdKf3=!)R~kJ{WYf&%1~6e$)vFG7Fxr$dAhk7RQ;n-Azk@* zbx(5r3O|7Ag?<1M^{>OXd#cNB`E%=i81IamrP}_^t0fBa^iy$-=q<=4BKrUgM?QO0 zBGQXp;T{&d?YQHjE{H{4c11F=e7HKs`^!J~?epG%`?F#}i2GvB_SbXI?mdA{SD?&l zZBop)U&Ru#m98fy4MO-tJlm}tb3d3cFS1D*c&Rz@=lrQ;IM2_$H&_{)6=9|{y%>r?&K%97u95hJ;rzW`0KS(rS8VR5W!rPV>~^! zn&;*cdNmXDqJxcQHR;oHdv8V&1>einI+V7(FD_1$t-pnq{2om|dTeRWPV7*lM2W*G z&9kV8KKe;&e(997(N14svEpw!!X5NG0t09h2(3T#elQJ_aZ%x}Hd9-n8@=^7daG!c zQ0oc$o@9HLvgCJ1X=KCuoy29b)oHJfHa6!WokHt}hu(=){+YEjf9_aCR%-6Ztg+S| za8a?&U+fnv8(?YFH^ze3sylAZ;f%$(v{&=MJIZHt`RrsS*{rl+cSTNhv(lM+D{^Wm z*PXeHL-VQ@&ll+pA{|ym$}2g_YIaLlaDCp>x$-b^F~2l7(if;^U&}WfIY?8Aq8!T` zT9vLVMYY7_K%_m)JKjxSQdGu%#;Rlugtp*v9Ny{OW6NN`9rFZQBJkKj4c3;Y*Vdbt->I@=dj+C;m#7gbKyiz>%PPgs%^ zUXmPMk`i9hD7>U`cu8t_Nt5uBX5l4i;U&$(OIn1Nv|;XGW%+IJX$vak3d*kO%wDZa zlp6b$mdI9G@~ZuP!Tv7MFJn9k=A5Vfg3&U-sM?}CGXn+bDwM#TyW;Ef>j(S z(;6WpYz*G72ckR}TQGj9zKEdJsfK$wGcpY}k)l_Y?dQ_S4q!oRDQD zqEu~Mpp0Gog2LYPw!)U%4||jiJ60$Alg(weB+Q1#)N_gtyT5F{lACI}f-mELgiG`@ zJu$5bfVUn~J)8qed7K)tMV9nTeUa6g+i8!k_}3S`^Hb5TR2C+Vy{Kqc(`hFj)^baK zw8`z0XSU*JG%4>+N_BD7Yh!!qv9y?2JDnsYDM`bsNl)Pv@@lU2%tR5@IXE}ZcdNz9 zs_e8_GMmDh4hnpW;0;ISGDe^?vFD{Umob;tpvypF(7YysQ1cQft?CGDw7 zEX%g>!`eXR@D<7xrb;a}BQ*WX=mTm=+9uIUzbEwEvQ5 z!X+j9X0)1@*4;gqoBmXDt!Yk;gg)-_(QN6`EW9J-!lL&M4M0~<^2>?Qqvj>|ZV*x; z>Tlk0Z|u5LhCO(5&Q3>z>5N&Q{tMM+(?w1F6i#V&qGqTG^G#zeU zCo8(}EUB#=-X4WED6HG3J8|BK9(7LfOZro-?NY2_Jc}5UDux)wwu(_O4FE6^w!ES?Re_TFcdd=s2rIFsGksf6?%I|Z!kvg-RbHcm@^2Jp>F ztd-I8(UP@EG>C?|R0XtZCMO14Uy*mkaIFS0OEE95WTsV<7-ZA5Q!*#mva<;wZ0@uH z(6crVg}YRsfT+v*J_l}FqSfrtZqZohI=9g09xMK<8C$lZy0i#YX93e@B^x+<>``=j zZ)KjHGA1k78q~|9f8KJ|4bE8kY#mIDCR}g zFD+kXnm#2TD|`Q)S)3~Pa*Z2*c&4bI#6x0w$+I}kWRk{=?x>2CV2y6 zHSCODCci5b%1a&17sad?F9^>)R%DcXn&&Sv7Z1w{`6*iFZvy#xskxk)6v*6HN)|n zzX8-7t~pYkQ=Qx6y%m|nL96+27qGnQWjZNj=4(EyrM}(-wxYeG_`4|B#NA-d{G_h# zNgXA!D}9!xgQJ;aX$&;YMWbUzFLhm!?vSs8d{QTpUOHoiZbcL8Qv&I*lE$|Lg<47bEIpRt*TTMde?@;GXJOoE+;4;WhkM**`(Q^d%GGUATffnI=+8{AiD1liToqD}@_#2%N)@a^M(lKAFn$bLZyW5h8YVT#jHi9yrEcN)#n6Ml7aI+S` zQ=<@}Mg8=PO1ouKW}&5D4qZGrg1*y1H%_~0@YM0+hflfo`tdo_2L$YD@B`K9s}i%q zE0popJ|sLR!@ZYeos2(&OR~u2U;Yz*5a<~hzkV0{9_TVhjF>Q$%W75+X7()wx-Wc| zH>NQ?=8#I(n9xu?X(@})rGu6xGc;)tdY4(SDPa~_@=d{>!9|BM3cd(^TKJ{*{;w#a zr`dW}W31cmuD=&W9k|Tg^g61M71YLZul5nf8t6 zg+r6(BuA5oj9Ae=n9TXQZ_uy1#^jMau8VwA(8a!frKy3L7Nu(!M(8!iAhv{97^o*UMb5Ii43>VFzL;YFCh#!K^jw(OzrGc zmYu2+nPvB=`^m=1x<_9l*z9EX#M@JcwyF9sX7X3q?k%yI$CVJqXD9rTLHyt})^_QG zCf4yEFvrUK;0*Oj`Zvw3J9Ew8wuG-qVQ0$6d_7xK9YYm%aqoTtjax_3fdTEinNlw@ zqWXif#uf&9TX+r)&d4>3UzH^@*DSP_j;0)pEv5?73%@p;P!^PP;DRu%J>=W*ILou= zw!ORooppo1-Qm5q&b0HD1al{mS?z_W5$)6`r#ij(b1}!n^?p*QiDXu1WP(=gWbbIS zQmx*3=qtX2(DoTiWa$s*>@S?TKM>R;zNkFSXwf_-C+~#3K9CbA?QrD$xwP?Jj+&xF zfWC#76xmA3XEpAZ7&ufn%Ptn5>v$nl8X*1t0_aW`UBRjbQQin#d@`xd?PLS)Y%T;g zq)*Q7^asO0>asd`Ggqw-GPKuzN-S|Ly~JJjYJK8r>xOhtP}4gE3c6uYEp%b0Ii^!x zq-HvaqK-U&!17?m;`FknphbNYXV@sbHLV+orykV|R5)@fEjqeOYM|NwZSz?Od(o?0ya=*RHq*1pVJ80%y3M5kjlK z@}Gew?t-JNJ8YI(bb%L1+>~b|-WA&8gcgqU71yk#xETp8&-*KP-@zItcA&TGP7j*N zcP+~gX?(Solgw93thFrTPU5?l?-cO6`7YzTlW!$-+xTw8y^-&3zU%lt%6B#2C-}a} zcNzX)<*Q|{7x-55eVT7azK`-v=KCPu8ou}N-Nv_=?+f^!!M7ZDkndW)llbNWAIG;7 z-!Xgxz(?>M%6BN=D)<`2_f_0|`924o#-cs3KMG8L-LVIN>#r;Jz8<_Y_TBi`UnlH6 zJ(`Z#yL$Ex*i$`wTkKDJ_Dt*@yl^tGclY3F*lRp{D)tE;O)~Z|o?WK}I(zn7#taKR z`Woz$Jo{ek#^bFT`zVioH};(#jln+7v+u;d-m_O>PxI)vVXyMwmDp!^_Knzgdg1FN z#G4*`9rkse-?i8u^z5s#@AYWPu@Cj^Zwl^(vkdz^9{sD>lUX6w-xBN@9{mg0H+uBX zVSmn}e;RwWN29|V#)HqrzQltUV;|w!X=jo`udVPKS6#|A45!me^d`r$Nmxxk352%$ z_&H}cbJ8i~`tmlg6KY-f1fA`d3=fxSH^r_ngjxp#{7}F##AElnL#?$RW0naxLQI`a zN3(1UfrnbJn+|5`H4U38+&g@xV(3)G?OjcADT~ zKTp8vhXGF(P?HPu6jI*KLChnv*=f~%ve_wEiEMTTcc*N2;&-cTc2awTY+5-qV`W<_ z+t*AwmaUfULSQlB{U#ZW{?9_BjMe|?<$vcl{QNIEUHP9dEo<2n1e00Ds=~4{OR;Uj zR@kpOyas~lHXB#;#h2+%@a7mMrzV;CxVdjHKmDuYqU;7$IC8xgB?X6Iv~!j2+&s5? z8==-#mBOSySP9yASxO+W5FyMW^iVAgkMQA5fk(#+{W zYXH|#)z)@JdhZ=xye$Ha3sDz-1t0YRbtk{@C#neF=e+OJ-uDUb`>6L_=zSmbzVp5B zJ>GY&_bv9mh2D3D_nq#2gWh+F_nqW@CwSj+-uD{sJI4EtvR|((UwYFo%es^)lx3{! zmw^aJw$KszIw6Hl2(OcNjvB2>hXQpSts^p|m`hIrM=rwlE&W+xz?hfhm|;%%oGZ ztm%q9LwLT+Fs(*p7fcJ@GTGB?GvbaZMWb5YOuo5`jRfN6uuJ@mANBg8lNJ z@HQ>C{rbGEW6!KWn;b9bm?|x&Q)d2e#3B6u8b@nC4y88RS6dS*-}@VJ2>*W_hp*wt zCL>fn>g}vw&L}X#|F3Zz?We=bs{vDQu(7=P8*vE#e;vobYsXPC$}t;9$MRoKhw%Sv z9I^5sjtkKoHr>ms;@^lv`2RJIwtij>m^$^^J6ILCY5X`F4fS{TZ^R?~|64rSEI|Za zh7r$*RllBR!vC-FWcum39SjU$$4aq1uacsjoG>uC}Ge~l+r zkMnLGKYhyh>w{j=6#qsX!vC*v#OfHUQ&X-DUhmZZd2<+zuD&8L@30LU{<|4^nY6{s zU53@NVljx8Z}PU%Yu8MV>$ShZkESKfW@%dT45(-|Q&9RCo{{3f!F#2{c9#w_rNegG zZ8Tz;6|Rp(3xWkVY2Bdyev@IL<5~;j-L$wxLT&>r-o{YUOn*W=?euy2X$w4!i|6TX zVqiJl?WaFPKNgOpiLt2LMTfVE^mcg@HpOvLyI4~l)4_(V^j0D1&3J{K8Xxv{8+NT} zZ-ae}<-^}f?=7nvrnh9`&(nLWh5bCeJsB38xqtD~`<%+4xp)c^aje6>jTmC-{p(Dn zw}TivJiQfhoYXGXRL^v^VJp2)(85i@X2Nb8ANGef>{`>s2K(-9_35>jXSKS;zRP8d z&y=HqHGWz{;eHy1%{n)|_{BgV&`)EvS;q!%Iz1r^<;}%u3FzNB^tLVz=;l=1!5uQ1&#p^fhFU z&H9m||FHn7CYEaN;z&F!8UZ^%I)b67CR8;?lOP!$DE2>qNN+&3KE32D|>K~$d z%Tn!LoGPkCmTEUtsfa1F^K+=?i0WZWWftpLacTiv5d=SJvf zFpM$9qK^jSDq>@bU)SOfF@okukXvGJeWxm92)gLM{dM`-QPqcFdQmhb*m`z!U)@i$ z3feN3ekb&aS;ex9XZmUoCi= zo|ec?3;H=C9^CVZV5ly7mdUU*c1m$fJdId0SBR#CNfXU_)`XJapMYbsqt#l!rmJ#k zxx`d0Que4V^?rx!Idv1+vu;{k_8fyB$;ur}B*Kw9Kv~&g=Qif_$1*u7oR@jgVQu{o5xA3!0WM?h8$A)M-ovTHA*gb!=-ttl}A7WmFV6>8r z!U^vliJ>T1&@U~i#S*f=`?3-`vKOKTXm(R*4$5oCZZA7Mzvw_(ddZ2jl+XhC+P69@ zc$6awWuy;yGO49aQRt6u{)-EK$n&V^#D*Rs$5_@ot*k`H4|B^(l$$Y@W-HpY2gI?^<`Eim z9^P`Cw}RJP9^{&3--G=Nge>go#Fy?6Rpy6!e?_2UkUKr5Cy_;uvoFzna$6t(w`rW&;PeTP zS9L<$!Oj{k98$;gLm3oZ=Iv?eOW!YC-ZMCm+6`z5w9_3RTDw)GbhXsL3CIK_o@NpQ5dX z`>p>(6j?!Ig8UqLmLKZR{rAji zEzlcyr-HkZ;G$FWXV)Plj+T%}py_4vcwH)5GRzWnJ|n;)ERM=A?IW!rV2~Q~B{j~= z-@gW_k+Hn{M_B>(DZ=J2u+8=7jcm|9IMp_&mYg`JOSS7kG^Nfor6*a`yOK26zM5lW zeSQdzIuhq8s%*XTLydkZO%*1|rV0HPycnBWIyhB_atEhTDRuGmD+-5L=qKHnpwAav zkPe${{E+-&mVeDv|Ft9!n0I=Rk@F3|E{aDQD7;f?mPfdTj)k}wr)=DF~TxwyVRxj0> zT!C5&l%2X6xTDcx9DwZtTrP_5$PEBHIs>>&fG-@tQgj#KLjaPWVY8gf)@A()bB++N zic&n}hg#+D-_m}={w-$TED(!)MEc5BFV&d}!REwZueU;1#LK{AB=uqL-gttnr|w&=YFbEU+nQ0Mt})YETMF;$ z%gt2Y*lg)9WMUbkuy1bDDC~zDp>hw}QOuR0fvKkBT@oy${0E83HA$&~(rKwwx5(DI z`hKq(A?r5%bcDs!aOy}Er1J*9f`m4KFdAO)t!sS`%_@EA$IJ!@_>#t`!SL@Vbi<2C zONfp2Z_7_i4%m6L;?hK#_ldXv zOYW+gTtaNQ2mAWX>F6P`WXEW!lY+gz1(CnBFa38hNd9gHK zDjKGj!#WO7cQ)CX*{6BEl0ouL0WSPB({Eu0(q|ycDRIqa9q}<20aQ z6wec>d16fXg`p@EHhwkrAP8BTlDD2CF9gtc(~^^E`6Hu9NsOkG{YyI@G&{3 zdU@t#RpGtnExHWGKl!v5QD$)r5(+7b`@jv9K85ybj=7w%3EMQ7PTJ z$xkV5FSV%VsPs86PzmN0o(`g}Q^^26?rs<#?=LYaIb|L2(U?0ik6>QJtj5d^t3A!8 z=d;xp06FeUN2Nwb&JIH$3Vx}OUyh+dh6`bPF4*2N`1`;;;7PIIUUR^F1*BX1 z`lgr<*uaa+z1nMS?$IHTdsi@~HyjI&%0GsFpC=e3vZAvIxM^sV&M(%85iD@UU(xXD!gh+{};lG9u{6? zy3gP7z2d^##2`DdQtkj39`13vJRta}E46r?vO#hF@Vu#^}a~ zt}&%?4!gBfeK*eHET129m(aU3(ad1`{L{Y)1XJ@*KkEEo$LN`2foQhVk40~&o0e8L zjVqD_)B8sNBk95GrX<&Ox1f^+%4RhAt{KcpRGo(z$r$W&e+D#Jb-}dg2}<;W9DF9#C$CG)~vt`%y#Vh-s-t!-vca-@9!}EFe5Oq z@d8aE@(j^n&meCjk6@pO#ticZrj|^^Le0QDNfVCD5*2UuT6m~pIirU+0J1tV7R^%^F<_&WOJrQXaz-yPE5u^mnl_EIF5uAN&AW+$B39K~?r%}N4 z5LqqNhj5}tA0AQTfN3Ryts+qNc85DVPK3O+IY152rgeweWspqF0Q`vZ*-PLmd461& zmSASZVV)I+?c>12R$#`*VamWnCr>;I@Fj8J+XNm7Fohn-juPJ_kD`MtTj#=7^jvGb zOV%?&pUmE+5pXc&qG0oA8;e;@ZbvioS0p5u%f80y#))khCsNBS}-6++-3I-l=YeapU+SR&Kur4<R1Cj@<#k!mAXhzPfG1KXo?IoF4c3{y->*F+HhyHI**fVA6`Dnq+ zU%?Bpys#^rYk|ya8p@x@z*ZV*w3n_E>M^+zHl91)^=a?g+A4B?Jab(eBH=c zfXU>#9oc$k9529wh0|AuBNvOgYM(jQg0Il400-Puu91<3$518bXitDYo@bvxtP_($ zA4z|X6)N59Ms~oGR?0+?7Xc*J?ATg;xnd>P7yK)h>$XovlvoMz5`~L|SOY)pzo5nM z$O@dyw>4vc@Vv&3``JCGHx3f}N z4~tykoUD_|b)j=@&Pp{$UCe4jY&_gLPp5EeBAp$lC{H+1O3m(DSj=<__QN;grZlCQZJm*Ts_x+ypb?5w}=UnQX z)0~qumXzO-VlMQe7~_F+)40Yr2&h$Ct5_g&dnTu&oVyd0ov;>y8X!ya# z`E~CHTSYtK%RGog5XpYZRPJ2Q%hz(-sZx6JDre^g*2#5mGg#96=m}c~NV6J?3uAKQS9HKFycdM|ku;><3`Uz*=ECV$R061f{=C1E`5y<#;27_7&O4 z#WDA0-jTU5zIa;?cgg7-BiSA>)kES*_FV2rv_s~i+0ogn18W)(zUL8cqa8Xz!g{&^ z$zMH^<&MM*Mv98#=VM;HBrL24th8ye3jwy&(q9)KdWV-}mickr%LTy6_;eUF~_mO#@CnK#>^xz$}v9ejsC5)8}lOFVK{QnsQR?q zi0s;3LmA%pYBEwXyt9+xPgjy1nMao4bV8+QNAX3uJCm<<5>J+Ae;q5N2D1D~S^={B zPcZj2=8XR!kNGy=Nqffh!d#5G0W%ZR9=vGgV^87hy9Ij+*z1`0F<)T5$282|$oWaO zDM>m$;z}pd)q$wiKBS$4NVuP!wPM?{a-4&j?`<2lpk@LcCcIlS9iRbEtEc)88n9UW<{&ari_%aAl0eP(AQmA?&|dd7WX4J(*&UW;u`< z6Dzvt>3~eY%3>9G^jNKl0{X^@YP6Bontw6CcQMDr*)fp^FKZyqzF`Pzq(q>B0E2r^ zX1l9bc09e4jH@3{*P4l~1IVTW4aU>8rt@_QYj3V8+Q{H`c{4k7HkW9mTQu`PLi?@R zJ~I=d1SrA&JnNB~s5ht62uGf!HG^PD=~2sWtnyLuL(O5VHq}{*nl0 zS61K(zT+`dF>^4FVqU^jV9p0Gn$NIzdzGbb#9r2VM_w$4!S(YO47&%5woSk9rD$Gne=@ z=2Yx9D{yPfS?Dv@`B$e9?Ql43=7+J$B7wOk>umn>jE|9Qlu+~snI&5K^d znvFCV*|ucyH*9nVOwNh=S5l*{Lbv>2_;&3nk#NlIP=eDRb-(yd}o`E?9(;t(E znS}9a?!^9aoLlgDn1?V=V*ZJF149aUD#oSRO0n!z-}#*+jJBs5mQSSvZ$AvY%7Kx8 z4NCKGN2dOhz`U!7EKzP0J1RX|qbiG{(gI>e7o2KD^u{>**f@rxI>6v@%c27I>B&59 zWg#je6y;DpOZVptj|j_dmv>%gs%;=TvQ-So@J6Ih6A>x{O!ZZE_H9Nmf#m)7OC-3qPWJ9ib8?db;Gj-Tb4^ELiVaCM z_ivYLx&pJZiJHw#y2_3r8PtC9k&|5mzKNGzRb~roC?wIB zT~+2*vPNl%@j3(_P?$;E#p6Zdu{y@%VX`YQf^wJaI?>6l3B)=v>LG0+Pn4>+gn*Vy)9z7YRYCWc+ZLKuB-$}D( zV7~M)wxx4klSKIYf6QYUEap}5A_{{#cUt11?_*7#&G5bEoK})*HNI4{W39#?I8hd4 zvb&Rcs2Rv01x0bp-_09OTVZxKLO__#7E@K0XUv6<34^u*dwR^C9F;(cFhxcfDGaATBrAsA&uk4 zZEbT+cVJfbu^Qf!NLW(Lphfe^B%71B>(K`?#?41#^XM(_IT@I|%Tk|4~8Y@~1`2pI)0imbsQNoXA^Hi<&(_y-QjLhE=!ek2UL?n4+_kOqD+ zd?60%!dQtAi@gPaCl51`hnWsKT2Lnto| zF7$)sqlBVm;9Dm_%)Lm(?Hv%tP2@3b?uJF{{!s++Qt_zhiQxaN=4nHCK?Gs*SS(G8 z09d&=9ZLhbcqIVj;$4)$C;xS31NMJmsxft#7Qj!!oaNy~myHR>xdpolGYvBr^B86+ zMiRu!^`pfUrQ$hLofFAuS^iO?PKS@2)032vjrHaBSwor%Im6~Di5nZnYYy+kV_e2R z=E1FejDweWa4RG0;n68D;)G-z@G5{-N*-Q#(E(r(lVX8`7s$!aCXRBP#p5qECgtS2l9kB&%HU#7C2C7G&O zvJu6)-MsGT{yA+h=WFbULZ3Do`zZPZRsHc}Q(A>P=4YZbxk z#K-yUF-S&!z=S&-`Gm!!sNpT4Bgx1Bu}y5$P)4Q!vx#F%F@x}}u&I}kJ2XuExr}^$ zwUd!I?vJI>%E*^LKZ1@VV@wDjrkp>7iJJ*(n`8Dmp7Wmmwjo!^zIPdfEB7k2#X;y!L@O+1V)IWakSEA+1oUmz_*L z$%$r2K7X+34*HoT<;W(^$>gsdwb5B-^+;+P)>edpKvac7S?^YB!h zpW$X~jGVQ-49MAPX01(|>705RVLoI}abjwm$YeWxGcYSM0|{n3LrhM@vT<$BX7{aV zb%wuJO@E*u5Ygyz!iSawAF7QPm(_-YhRAO3z^N7mD(s7XF=6=_^pf#MGeKtj+h4-c z33h+XI+9%+w$*cLQA^1Mq0$U`4vuP@#|BZo-14CIh8n?ueIo$j$+IThmatpwtN`a# zo6B-YgQv7u;Vy4M-SqAXx}!SceOJN&uRC)GckI%khCm3_UOzFI#GF!>6^&jgsj)IV#Ka*vz>LZb@{7 zc|@ovTQAx^1T)!Uq;!6kFrR=y&Uy!D)I~mVpSOJ z0(Ue@M;k6jEV+kchO&PbnHq;Q=0=OuZP;!?=7F>arA#g&Qjgw;y$x&|=0{8$a6K?(d8^5N#-wKO3~{s?jz)>2 zzY(vVmDI4(oMv(5;Q9)8uP^3fb>vNRRhhiM3CS@cxyg}i_YY{5k! zh$FEF>dJCnHBAtjp}bc?C#ja{Y&5ZZ=q2Vji(UeHr?RrC(8}3RD>F@iA8DOsc`x9F zW8|ijrG5eG*M;0>kr(${z2ovB;-E<_h_wMLr?q(ttU^ zBA)=+Lr8CkuI(e$sz=S47XK*tqrgWe;LS_GLZdsRp}d9Q_Gf_`3eMK3#RqZqFUxz7 zYVinrMj>n!=2Ov7cg$^ApbMK5pkqZgvn+2uu3IeSd)Z2eQ~EPqp!q- zUO`-5r%-=S$;UVu2&HIVKVJdnD!^)K9@c%JSU*y-{OA41{lIKLk{U1zRuh5iNBYC% zFZ3hl<3qCeix_j-;;%M+*8Qv>nGT;Z7XM>etTrF75sUxmEf(w3Z0`mBLO+t%pW3H> zd*U-JCX(jKE&LVG9a*-?_Nxce?PPRyc&ptxd_aR;7jPFCb zSxWUGErp8jLnhp=7^*-uQuGb_kf9c*K4h1?#rGj+T9o>bkA*s{56QMz^&u|_+prJ$ zokgk-c~Hn-=|glIFJ$|WD!u}_Z-iIaO?uhA!xe{|broInJw3klQhdiN_H0VRVDiF8j;EIJi zQXg{k9FeFGxmF}c>O=P4EfV!1{Y7$kAM);Aq1A_^s~0-F57}bT>O-m}zT^9lS47{i z52>=$>OtS$e;Bg-&nl*kgLEq z>_a}WIQ1dt3wOjmXq#~_Z>hcFaDSDWoA z7OqHT${A{rpudB(k7_X}-2NRB*-2*1)`|C1x zUon1;aQ!Pzwr`8cNOvji_Z4xnowE(q&4bPJS2PdgFXK4$z!jvIeyPR`Wr;kNuz%Zx zG4W_ap!v%o*4gPZSBi~)o&_6UIySVY#+`NUOQtVPa#y&I3JxqX2~*n@nbhEMy4rJ0 zw6D9z-9fISnYbex^9WYr$_S*N#pPWWm!(gxfydWiV zGeq`mhPz#8x%;Jyzh5v1E9}=@H}r!uLu=AkmKT*L71cDpJ)ec&+u`D4%)^)`J^PD% zS9tb!`Et84F_6#o#^_+I8T4#(vs1bKyC$inXFmgbD@Sq?)}wS6GJ1^Fi5+bPAjtw! zeVfizyIJb5GJZ0Q`(E(j&%b}w&-c_d{A6+<%HCDZN#m&*JAJoxPF3z#Zxp{|{WoeB z;HmomwlB};QZZ5EH`g?Uol)EuPYLLLn1k(uT(HJ*urEDWqkt}$)!#qT;)RhlDmX&-g7P>`%yfjO(cWzFU3Z-q5BF0AGX3!YnZ zKG&A%|G>+!asHcl{y$^Y+4|2l4|G8roQ{#uE=Inlc7cqu_pxD2`4wv3Fu%t4_4Dh8 zLvi`_0A$Lq1IXZTBydK3ejOIx*2BWPpK@~fUlgDJUwYxS>8bFRoZcWj2Iwy3cAaL& zKg37gg#ImjPMr+iS{^5Z-L2rYfMP|Q1HH!Kx4fw+hIvuwi0*vDBtE7aK(*f9PKjM` z`VX8&`cs;}+8UmELo$)vtTG$XTeeO zE_zOAP5#N;ck7p)FirPvSpRt&a7XrR)|mH@x^^E~_y67fvW{GE-1TzYy&U%@aeW&d zli`;n86QX}I9*NkQ}ep*o^@ushDPulF8a_><`t@lt3lrbigsbq2;%!K@m`3lAuh7S z)uYS?dUeeN!e$+ySY5cD7@>3ngi8R=-FU6&>2fpQD zHlipT`Tc36hZV@ZyjE)u%R45zMsA?fw6T_Yu|6A%6~#;GOPi483g%lTrLo8=xhZ`N zkvT?4lliV`+(h=Jqg>RDY}7kXRn&T^f_w{`=eyagNik~`0PC(qU~^@+awS!{GRNi$ zE2M#Z)ZholS^gr=yIi@TRa~yH8)v!8g*)!bR$BTLGjoZUr7jGsRG#P9pu5Hj-{&u1 z-q|vHS%X7YX6Mj; zTsFdaexF1#a{WK!N-aVN%I*)AinZrE+fsA7b;#w?Cfo$n=7YC995z>-;Z?LhkmSgp zg*rk-3(kraonOiR{G7|q`zb58{~1@w66zVg8=)5GImMh1&+bC6q&rx451ex3N=EbF ziZAaBuRo^tbUo1GQJ7-&q)4Gd{_OrZhpS?kea|``PtEjsbb@{l<7o)Ib8aliheIEm z$w<3c3HQMV6sGufW#e^@FLbUzmjfL`DvABr7-{yub$j<<=51%?&pwF!X_Ws)Y9M+t z4tP06>H&t(Y`HaqlA~0fUN;DgI#QKFsyLhM-8-R=^3p^#-&e`*GsjL4j!|o^UR3m5 z!qle0%u9pEUdp2Z-8plzEvH&E(Nkp~@9f9QKFrx$%3k2?Ddyo92@+8lHvj4qr`P7N z`~>~S?XkFTw^kRov^}*|h+eEVbT8Hvb8am3P<`k&?7gvcRpji~c32x^kc41`NwYQ{ zBPptB>TccZ{A=q0f%h<5Fi}h`Cb13cM3{D%6EUY@&cd9Bp?c9664s~B&)H9~(a8>Q zY78*O0rv0mDK3VQglG=nNA}N@Jmzw$XV0tUH2kodPsi7C@QUx`+9*1-YEFPg`lp)k z<<@=8nS4`B4kHQsXhm}=y%eti@De_F_`ovK-C`X9#xh zZ~}obp=umFW=QZfrf!JuyxmDTRY!3)%*M@=vKH1Qr>cDnc643A&bopshcW3S#}WJO zB(fQS5qaZ8rxaYO{jMcO{f+1QN&;DQcPFj{e8x}YAAny;i2RjLgJ8GO!@6K2^A5+& zjYS!~HN=s#wH_;dsbU`AqvPR&>fr+%=}9?NC&kjkJqM+`Sx!~67>wpASdW~l9x+&o zx#{oB6SwjtL`-&$uBFLlIAZQMQ*g6r#a{8+vRzl-zw4Tu`tfZC(JaLV$h^X#%n0Y^ zpq~z18X&?xVxbUh{)BsMn3wlMS>^*}nRyccrN5q>SX9#_ea^dd{uKYq6zLYsPVf6Y z-(!y%5V#QYpLPQRr?lsbxv0Z{fcXb~apZnHip_HR$w)DdqM3V>c+?`6qB-yoxk*$p z&`=T?8Rck`XT;I=5W?P_iqYy`pLci4)~)8jGl!rEbn0c1pgKNczWNJxfoE1OQoX)h z_Q{oBtPi_OVrqI0-J|X(DD)uL4LcAp9EBHWohV zKSKDq^0MA$V~vS!@R>oUuSfi295W}0_$d*`%BJ`Lp!tA^GEm^vV)M>kpnq`C2Bn)P z@@pG}F!4t~mpIzgnOzB|fsd2qqgw+X(kT(qFjj(#Wt(P0f<1-=ud;esl?{_V)Y1zI z4oUwt)^3$u>a|j^gPAuwl~*_hI5e%T#`O9bX?u}sLL`4}Ky$JH!Zd7WRWh9bTEh@O zGT)Ney4f_?*4E8lXt;=FQW6YDZ?1Psw-i+^wcyIagQ*3!32Xf!dT~9;2uPxR>fId_ zL>DqA$w2n0G7C|7G{X@)Zh$YR%bzQYgs5{2B%3+mb*oJJ9M z)?l0KtC_-bM=965`nVfGeR^!HBjJ?WcV-DP#f`L@#4bCzf!GygIXiOA4A0-Q_5Rqx z+F_>KG5ZdGDcvhF7HC!k4HU{X?U#oyWp0&z(BjCl90DVrjoKTV4?DGE2*K zDo-x=O(;k!JdjY3yoA1}M|pTjVt9U0q(w00!eIO8S$z`=QVI_wa-^a>nz$sPsA|@{ z!ga{rFo-4~{3m}b;v*r<@Mr4kmL!DPKFp5|`dY;g`ch1HNc1>apY?Yo5oMqCGn1Dj zDttFyL^Dz-FSAcxT0!GJIjOANCC*g*ypPQEK}0xG)1I=9l6&5p zS=2^!+(e{Qf(vE|RAw`qK-8n!jx#^h*uD`L>tg9e*Y|jD-UnrArDbWuDs4xa*|O*W z9qGA5sV#Q-;}$slzlsfsqT}n=xkGQ^=7N$uw8-D>)mKx<6jx0LP*)06L&R{m)2sw^ZY*J(8Ne^+^m4Rb{=)Bzsh>*qfc3ER~LPLA9ygW#1N4SUu3}Q(-!J zeolsJ-5UH<%@X^oob^i6+p&o7y7I}oI$fC>OUv@k(pL^2iFPqM?Pn4f#TeRf=mY2U=oea=nC@|7&|Ss1E=uu-n{h7 z#gxEe;k5SfDOW(rOGa#*Y_cmLFXEQK>yxuLc&D9A2mb72)&(c}GK-lhD?FMOPhg&D z=lNn&UQ*2cPuMEUx(1KO(UI82m7Zh-@|&@kfs4g*hr=BERM-$-V#mt^>`+e;PIQkxCMYPnP$)E9EqFCXT{Cy zZ5=ntMP4y3X!$ZqM$KM&AZ79>4OviJP*mee;N?V+Q4iTi%-L=0oa4OQA4ldyPO?nf zej&#tei)AB9LA3W?>zM+%PvbTC=tIe74980AZ zuf{wMz%Hn%L`Kq|9;rkITMj!}4qerzZAChGd7DbgBWuXNU#Qv7#?@?>ufOw{JeK+w zTXXSh_PwvhvQ@SEjmQ3NTw+l^UW%aVfui!fXmT~E5K>_p|-Ba zoqZ60ZSq6y;+&1mry7W*m~oVZ44PI%hK}KBxItSe*f0e(S(qY{3sllbA1Nf)cJoA(Rff7vlsRIJ@`96eM zg!wDxCCrUiE!cYSpJD!m`4)2k6Z#YZwC9krFMt14sU$`}9O&F! zwfU;lMOTG6y1BV(yR~u>eRI{0I}d}B#x~afKKTrN%8g00*aAYI+M}4yc@7S!nHQX$ zO|ju=epYhyeR!IDia`2{u!qfY&Jut4k<~8{&(kGF3MEN6!w!+1?b^eRqXANY^%{!K z2f);QGHHK?{E%6N{d3H2OdX~bu#+*pJ-ld?caOxm1si}Ffw>NID`pmkA-`2SynhR8 zO}kEkl?a7pp`lh5_?dtk1=QVcTi?GDXq5wPcc4!NQa=jSj?R{9y+Df^Q2j%oxdMgF zGg=Fo<>FuIqM8UgI>~0@ynyLQCPlBdF0FQ)Ea#=xiR%=(bdSes5%P+Gfl=6;u0T$4 zCGnEtVCCHEhl)xIo%E%!DI&Gfg&i2rh0QCPE3MzZArvNWKKfmQHr_0w zzKu6I&1>Ul+)q1R&d|YhZgqGY|3bLOz#X}bA5HMI@k)Kv#v5bt+W2p@#qDj#PQNWS zSKj_=Tl@qH+TvUA+_DQJ)zb$APT+eArt1;RUBKpGdSRwwe79g1V{$Qhn5!_?U_$O0 zY!pR5cCu@b!_^@5Lzb(q>mdRu5vbwn{{Bj-+|6eb{i^Uj(b%7SOYZ!*Rz@K}6W7Gv z^qbgUeyNH5PR(kfYho*8zZ$!3V%b<4G_g|w(8Sim{+_PRbmH3|GyDj~r};g&uQ0w_ z-#ak(V*ZMG3-d9?R&lFUdqeu$8_@4vq?OE%Z7Z;cb@y+ee%&nK3fD9yQb}E7QtOo0 zBJ`9%iueI}dwsZ-n4O-ZYp;!gQ;(gGDy zUz#vOaO$(pR?w^3_vv#cPYK=uR&Gx12QDd?G>MOOYu9-AE>H*ug4D>fBlUl$KsiX1Uhve zZ0$Ck6KIpOnEWzj(Q&DzT%dt<9xjQ487&%8{%Q&ks!|Q$rpJxdvy)v!KelG z;$^aGETP5x72RU;?O^`wueIY;mw9`eP(}LE&qJ9D`WEos%_N2|R-va`l4u#KSg{^& zW`y8(6M@^3Y5}}T@V>yKw>qWrxm*QjeXdVgNiA(Iw`@VJB4=+UU@l8${!t?Lax!t2 z+vpzpWtct{hZO18%;*oCj_2i>UL7S~^nFA1t?bmF$S}BMkxVSrbNT8rXi%%7_?c8%ZKP7|!3n=vij;vTfRO|DTXcd#HHS)Z({DU=#Y(Sb6R(yQ}fQ?tirN@qp@ zCZb*Kq|pJyRkXdmNXrSmM7zqQU9U|xZjfA>wd(|kkfuC9mYU4_P0Cy9x6TTA+nlP^ z6=(XjTD7pkP`*0r_%X99b1$Q9^UZ-nb!N%FL-p0+L%itdP#5~~aOB-4q_*0BcAGeQ zyk{7b*M*1MDZ@dg5z<;|zEQxH(a!m$?e+~!=$O7B%ow;0)+}D)v3ea7SQAAJWGXx^)h}J)%kdkPfu%3nv6y6&;*43w7=}hs=|7K;(RiIrV|My6jI5-jP)FX`pm$ z4IBS@AnC;HvIVvgS1^+AUV3YCXi9RITayQup2>SfMep!r(bukKvszY@i~jLLxn^mp zI7O9AirL7>gDqoI%gj&FM5layX(CWGf7=PyJIQ3JcL$+tXs0x*U=^Jal&Q{; zxYwXGhM}(VfKWzg#QL;Ss$!QtgG+mM8ey+Pn~R8)+hr-{S_s|fg}g)uK@D|govpcy zeH#QBQjp1=Mx#c@(GhvFt?*i-LzFa8bj_OSHez>Mo3Zc6BJ0U*)o?h9^gXq z<{xo1Gc3(Y(Ht93^KybA-vj6xtl`nE7A|7blxRm^$rvS&e{!H>e!oCRsv>|;IhUA< zcD0$-im^jka#pDI5TsFI-#?;3NQ=U(=km@(`qCd}e^vK3X{snPZKh?ECEw-|&iG@3 zHc`UHyA#PuGVO0k0za-!>s`gNVWuqa+fYuLDMC=yXZ-m2Dndgx^vTHxHeIGy^Gq&4 zdqiRQ9JlU}+RJYS`Ju7j+No{&(yc`Y(+W;T2?vLg^okQ#Gd_Ucw6a0&46WA@g_9YV zqFYl-C_d3cBKqb&s!Zuv4$f4o1|6~sPTtCEIP3-%aAH z?^S@?5J-Q3c{^>f*}b#GW%rb`{p?m2w{piq{<+8&N@>nEhD~g9REIt==db`=6s{?X zCQeN$iYDpN4$cc~i#8&|`AYRuEZa{}%(oG9tmC?!Xq3z)t@?5isJc?zvKgY={-4vRcTKevpTT9-XnG~8FIJ11-1qlgjLdlQs zzacR+t5Hc^Ffnv~!n@h$esEV+snI&EiYrv!V*~9vgGKQn2?=XMLmHL52Vu+5JGQUu zu{GK#+r9~=#$VXGW$=3mJ{x2NlHV{)!|M8&U?ZuAi>6(0Nx`mA`#Qu#!8-0;S0-LhXsb z)a#$Wf)1~SLl%wA^kZRdeaVIyM-A*z9!f9jJDWb5^9jjW7Zp^~51b3l2xy9G0y9nz z9bJTIAn>E>(*B4hx6Hdk>7|*ohj{Ss9K6wxqMEt__T|*kiGMcps>veB$dC!Wn^ewH ze?0ro9cS4m$2)`?bBQ-|VRp$rR$(fv?h!&SP5yJ}>}>h$Q#oUsU1(pS&{7kV$7GpyEjv+lT|I@(YA_*Gu#9bJz0 zB1cuvo$C}_jQ7m>ELgH1P)NpxDvJ&#+?7&vu;pE8I@!eRtK?digPpyg@k$M7qluv( zpqE~#A$PT6Ux5vvu$6)7#XYrl%<8)ZZc@b_R_z>3tYf!}8)X>gLGqtF7l@p_W^H@o zUe8-r*r&6bs#~UT_74UGSFlKIo)dnp?pP7mRs@V*9ZN5MMHv$2)Xc2@?)_1Fg5oZq z%n|pV%at-RL1xl41I!joQBBKPUDAu2;j_pzOD|qSGC6a*(S3C4 zg@d{2#kxZiIo=YrN-zG#VgJjAM;OSe;u_&KonwtT{p~i)AG1k8L=-?mZJ0ZrCBRMq zn-M9M<^nlC!@1o$d&^mYbH^FNo-60l7`C09&*8LznJ#iZ5_5KyQzvTtsMav1j?Ta( zrA*#4Q6CF7K@^+JrLvE4VSOz-qOCH0WgF(G_sBT_CsnWwbJH1epDFaxHq1#U%Xy;p z`BJj^ySb`Wy2Gox*DonXVv|@E4!>8 zNaa;yzO-OFHma{Zq{|5KQ*$b5MMOPB8u@fnJ*_MoMx|v^#rBd(IY>%|8NUteHuLGN z$SeXeW;HuG)bWZC%C*MP1~fnv`~?ShVHmUhM^H7fTCdt>YyEYu*6->&Ado{cWv=VT zLJ(#)=6=j#%uAS6m=7^tt^ZL$sMf!Yg*ZRg|F`Y(k4 zH$N)&rpz$&OB+W%_1p&-;Nx8FIg>zdK7NsX!c62pfZmS#X%8R8z2p2vcHKnyPd$7w z_|O`vk=YCcUAWf1UG_<$`xCa(#?fnJe;7L~pya|5O%Txq&i=IQeMEE}wz-X?-DDr> z>|BPeG#6o)=QeWm!-2i2^Lzy^?#m*D%zO%glgA}lR9(rkN)tC5SfIs_yC!Ib65oEtH;kD;3kcF~Q2}(t&}+d-*YYr_p5C znLqw~RcO02-^kpF00>oHTy!unqg|*`D0yL@lyU54@-AN+)9t^9>cG?$d=+bHzr(K! z?Zu$Dn#2Sm`mHuQYHAXc88e*8?{jBz@OV#|g^oKBPR134p+na-I?vsKS8u|m7bt?zgHOqt zLz=E0FKXiok@Ep%IqbZ%r!8N-IZ&6w23=}d=H;{Bna|1yTezDPf&z$*H(Vmt*@G~h z>+;fwy_IIZE*0VW_;qADHdb-zFeUpB)y>U2h$L~DcI#AE1dFm_MWB7rtYqfV!8S}h zLakL^q2?+BJ?;B{SbG=vD2wZldqV;&5J=D{sHkYsqM)Lne`>G>LITPy5W>X^N+pPj zfRI&`TQHz(9;1|cueDO`&DK`YYNS?!ivh(dDwQZ+(Asv^r5ZKGSR?y>e>2bXWTCD4 zzMmI;vd=TuGiT16IdjgL8Mr}+`=61?6>}Or$^AzxdQ%_OR(x6gKA31T3)nkTz3j`# za^%l8^C_!gIY=PeHCS7`mV~M)j+cpAxE5cJ;>Lv%pK)hs*yc&T^`#%C?0Cg zQu8BKSpE9BUQ^C5?&E5;m^4v0{*;Sna!`^7I{y6>Fsp4 z#}q!B_ftRP@iu|Cs$OV~cdGe*q^hge$q{_u#*=vp^TCwt>gr+XI0tVQ@JcdST86ypAna3*+jYtP_#!$P);kJavygaP9nQpA*PzKRl9MIq)Hl81oRQbi&e6+o>P z-{pV2lSHchhLWK6nv+y}R$k3k#=9%zLG(EFTeonGtL_x8zbV0+ZdKlR&$FCPWAAjl zU-zZf|MmsGv+J8*;-@&LzW!zXM(ZzrL%+H8kL}WLe*LX)>$jl(;=k&*cl~ao-#+zs z{!_nw>o3d*(QMSGZ)$fVwcS!xMUGzJ&zHe9k4yzwze~aq- zcGLIB`it%F==yu?@7Vf>?QdEAX8Sw2zIU$DO{;IR-xt)+vELWhKW2Yt)wkH+IrT%> z=N2!XTYsnhT~I&T{#MmbW`|Y0cyaw6Tkq2PYW!g1HP!S({ad&T#A`HT*7wHb8n0QS z*9T72>rHyyAyn1KU9vtOn^L^yR=vJ>nqKeJ>*yhR)n>E$p{NexHTUVY3O!uBMl*GN zlYNya>L_DJR|pvWDL>r3zW7?`VB*|#`hF2mNCI{Ab0XOhW|nGYmg#-th{!{TFZ%tR z|NDFYcdh^XL;rW3|NA5V_g4S+Hvji_|Mw36_fG%!F8}v#|M$oK?>+wSPyF9|{okMZ zzt#RPSNB@@uICpXA!@BG4X8EUu{E=KbBRbKk7uG4g?kM(Cn-8Bq3K*Y|I(Rr7gh4B zZ%y@+7sm8yzajUCEcd5s#aA~nzk?f_f$kJFq^kBt1yxsnKP%RQ*^fK9PDkp$ovg^J zPgAm2)CC3pV|qIz!gwtFw5n6h;;hKB>4mT3gtY~M{xz%z56F6O@|VDRsJCVHuDsix zt$BY%5Pm4(>#YJmvhhCZH0uXjrgWNAww=viPor?&wpd+ls!!d5?(n!==HO06S&e8x zV=eZExt$0UVdt--94T>UGt;sEvxSuUC-zCIM{SRyx~fyNzSp|^ZKf}Zb$Wg2 zzpD#%6+34L3~^gp6Xs4hu*Jv_?+~GZ$W64!pU(Pwhy2O~{?-L{q(m%eoclltsM*>0 zVJbi`l}NuimD#>B{z{iutGd;m+g5wx%J1a^dXd!D+7qk)_HOO8!&C1+5vj$vbK2bA zhwpvv-Pk|I#Dp#B9Zr2d-Uw3SJ5R-XWNeSFpZ%tGJXQ7RP&=NgM)v4NSx3|@?a@8& ziHmyIPB^?~*xjf-cFY;$+Jrn{dzb!7UL)L|}v)~4F5>MuJi>^j@yH)ml7`?Kh1!WQLMf61waNMqJV z=A!cxdC#DoI!+gA^5NvS_)hQtyfdiw`>J+V?F_0ts)IK*f2CYjs$(7EFL>8Pt5#G zr}$R$2t^a*HT=KFL2LX4gK(kj^fCPq@H^iEYQ_Zot`1L24ZIdHI?HwjnZn^h{*VXG% z+od|$v8tQ?$spL+=Z0Yr@4H+RT1O3X6k{r|Dsojors=b0~u{ zd%M>Gok=R2Kk-94g(1UB7gqpb)1&24qR77cvDTlAr%m zeU+&r=TJ5Y;WV@b4XfYI%g6smJG;`(PPFsz)OAvD?ld7a$nz_$yUzSCO>9*YTjQ}duQ>lK+rq9=W45I$T2TkQjIPqYhpoB_00G0?$igU* zJjS+)p{8A}#V3oGBV2$#ksowr)TnD?n0OxbAEnYh11$HXyiHLW?<%tVJ- zPq8oZ?Bsc$r;R6P0?zB5?2+}_&;Pc2y-#Izieqgq&5^QxHoHV-^Afdg0Pmh`51L>8 z?#WH3-$Z`Lo{FR#jzAG!Tl(6iM}mm z?!-?U+~nQ(YlbQKX`(HAcs}RpP{wvqp5u79oBEBzf1Io#6|ZmeiU@?`a#V)Y-1rF4 zQ<875IKS8>dH2j8Co9}*Q}t(pbK4to`?oZdlq$^zy1f7{a4UfVvdo>D>L=Y0 zCbh2qF6mEPpo1HgOGec7L^-goxekyz)376WPU1P6XA;jWo&!NjjXMw?Jai8PCe`01 zYOgMJC?Wp^3W|jK?ZSJZ2|99?TXTt&W_l%tCzG!bdFRyoJRvojpz3JFDwY2|EACLWrl<2?~#HcBnrfHquv`Ur6x|J5aMs_5PjQWjh70N$VICAEE}o$rng%6 zhSv?y8F0D%s5!?Q;d5i{X5%|u&<`h$mn;X;$8@ALYhdsd7b7zzitmv#k(~YyrUw>tYimwY_N>HOg1_#c;&VwLck+>EaIO*0 zWS(g}V|lg?<~9_bzC$=dw**(V%9&SMe>Gs<^9^MyUS*Hu_nXS@UQ$|2*;SRcaIax> z#n;2F_-|Wwx02Ec3o7T?lJEP5auxsoqTI{oRxY&VwthpoidVUl`TZtbbS;@wIdhgR zc5T^|ug8PpziqkQN~SKn%*XJrzM)*jf7^0fa3yC{ z&L1Ax%zF{<9VZJ*Uc+18P>16CeTzC?r0i~!C(mKL&r-=lrtgoNi!;89cpuLr{nIzW zUUU^M;|qsJrt!?>y#uS!ed*jq!y^|E(rt2C<-$c&N0)dFs-tsekbu|@aw3t=mYORU zu@)(cY$rJQ*UI~l^V+|=^4^c9Hwg@F|2n+pR<*;nnD_11aIQ2@6VFzj7kTny!y`BG z^sp6X@mKok0)BHMU!~j9U(%X8iNA;ES3Do^bemi}f9~AMOGE{NjW)Fh2>1(Z1NLjv z1*FT%JI4)xMZ;&!Tu@cHu$ii|hMaQGqCJzg_q_KPk-on_>-sl?Y{De{Prl&a1?LZs zEal1kH;-@8*f-&#jkchmLsesdf0O=Xan-TgkhWNBHV}17m|{Gmr2!c}+XM*7ANI z&;P>LZpvumX(7!>>imH6TX+H#XdJ73&|f=a6#^E6r~}4SzYF)ri_n$%uZZ|>Gu|bW zY^9C6c>-FfzKOhb!y~`t=`2u?S8n}=`W_};-PZ)TU)K++Z|%*)BM+nylUPJDTuWx66;#Kcx>i@cWn{HuC8+H7zdb>?7MGkRot^S5O?;&1e z^xL#Gz-_@=hu^XM2Dm+Va&c@?)%+{RRbDxN;kB;rwr{Ar&qc#6Hyc8_uWRoD>UoPt z{i?ce{o(LPX5CS~yHC!nnfZ^hzwX}};`@`}o92*#>*!aWfS&Z*$>Mk6+P5<5$hr7G z>pPUNrPMKybYIuL-`?)($i0L4m1pwZ@PRuSQ;#yv`F-p$#xc*@f6z4K&A zhm{MxhPfFqRA;;gcvwf+*lz*PtS5nYBX#n-nJ2>gtUn!C_dr{>l_076KFaRegSo~R za>mcRvhu~6hNk3}Y8LP;bego<@gnJq``fSN{2)BJ+JD=zUy2>caY1>p zV++qKJdg0~;z{y+#v}X7wBFwPH|&{Sa?78um~j2@iT{kubbU-z_^-jemF5$t7U{w-tz ze*3%&Zzk^$-UsqT-h=;=zJ1=ghgN9x=lgSeI{okT7wOjY=jpxaf2CW}pQS%#zW*fM zoZd?L+j(~I?BVH4dCmN`^0e_p`F@ONE6-Z$Y$2?PXERSd-(B0gX|3AYIU_*0(CpSB z6ni>Ee8|$sHPV&pU$uL3)Xn`NL?+C%o-9SWXlB3c$ulpN98DcPdQ3iN7LwMunF~th zRV}<$EmdMsv3Ps`OWJYH=YRZDyd%I zQg{o>U1WXr(DLHTk1nj)YH8!pI&o(QLf$o@@w$oC9G#jp4m*Hvu=^)m0LV;T9Fn?cG>bN=1J9uYT0{D{x*#! zOQn?Nm@f7=>VM~&FJ8vYW!Lh~B$-E+K%c=GCGuQ0UJ2VfqVi8Zrc`IU<=_JN1CCro zok{c-bCj|-#EvT-AA29&qXO9qG+BE+mU3@q6je3fy}ed8OyGg*JdSjJFrZm)=SK6{ zXZzBHoIudN1A%z!|KyeCdefrT+v_+AD$O7}mAF})}hXmhb;SllKZZbQAfyy{xf;g__o&#gtP z^<%n3PJw4%Dy^4w3Iu%))Dr}%Mdl`uDMzEzK`>csmbKy`Xv+VBrt3R^`(|j;D5V%z z@OTPNB**R`2^(Ho0mFKu6>aTew+(Q{l7*?o-lyH-b}v3#?O3^- z^F~_MKfq=ja|Tu@tTlA^1?Wc1AJ1Sf#j(^7R-d2%SyWDcQx=t%8Gh}1RwlB~+JPqf znlybq<^-BP?==^54l&k+%ijZY2q|353Stt;Wh#;8w23PKe^}&XzsMQ?QRE~WU^r6~fy`hP1zKT)1<`;2Yb(_Z;dszo$#R?dzkWn*q?7KZdBo`tMn?YID)IiVT}Y zST|o564ngId5(cn;f+i|g^RKFctyFO+;kEujuNLqozO^a$(L?0$%j*X9XFqvv-0VbNYw!E5WQen0FjZUeV}W78;Fd5oMdlyJMxyvHyiTaO zsw~6!J(5TTwrte^9M9C~pKuRN`QsgktI#iQz*6=K?fj{)rAR3))uGA2JOR4zmecl<@5KgyT5xUb{xH zB*|mmtUEdqVLLn_Yj`WfE)mNvJ2F5a*r|2Mm{9j@tTg_i=&v=$D6!G z(ZWT!u}+vk^3w-lmX8m6I9(jYVW`{S=jGxhx8xkJ(e^P+$_hx@W@BGIMhdk1g|+vo z3A*iAxG1_b&pV_FCW~Pt+bmNb$g^RqURu**qL{@Co0lCnpwZj9>AeorSXc0{{*HV& z$87uKzO<@-q(fM>DrT0mc5K;k-ZUT4D`oMjemRYpej?ncn*(RJyro{e{Nq8mq)mYp zS!;~wQhpuEslIEtGU#Nt4gDmU?HO1vIdSiH$_{b7g|za&IF*SZk=(}2?31ALU-@s% zWjkJ4z707KYfN}$xi$YTTHeEZVfFLu)ZLW5>5C32j)#jb@BE~AaH^x-F@ml874rb! zR*o3_d@5^r;pphHC>FlL0x$L{AF=G3=hNOMTW0iJd)|;gciR#0-$2P0D0E%UhQijW ze%=^*b_32?!tHjtRVSJq(95om*_%pt#gtpV#LngHaM5?8+T_} z3>C??P%d}ZZN>vyQrcMTZj5m&>`x;wUYgTb+84JxE9~tfPy3W+F%a;dR`Usw&L>}t zDn$p1!cJZgP#B>a;ipk*yKj8~Um=B^PFm$Xm~q6fmCHVZA`{6A8KtQY+MV#Pq%Mg) zj?a2;ildV8YQNIWnUz*A?wdsq*xFeWJP1~baH|NuJK(d^u4^!V9oEK!z=N~I(NcEC-dh`!EqDf0G_7x?!0XrOL9}Mg&Nb? zYL(Zxx|zS-{G8e5N6KjrwTQ27vGGt?I;YMiF1tL->;5A6hU6ZrD#BlVAd=^=p3k6( zv#3FJ{t=#@NLCOPcqqvqFU|ZLnRnUT$U4IzU)+b{+nZO)uPWF-AR(YhsQ)BvTnJuE zL(ni5M2{kga4p2sz%Vxf>usnS?0;U!CmeD%!^}+Ny=GZkr0URgtZhyd6HJ;rHQdFB zbzmm!$yOiqhz0#_W=!j4D>ozyJ5+A{jQxEi{uBfk(h&%qfhUmyh+S2)d?1?WSur7w zU`}|4Ohdb2u2N?=RE^kM+$XY;?j1qY4b@ti?lF5 z?s5fna67Q!%40rMbdN&GM*rN;Mk!Ks}O0 zd5zXL5jQIH=`$qtX?-ta)1SxBrvzxdd?NfB!J09cF6$G|>8B$Rv`o&$rGirs?3hGS z(k4U%fkRfIA2I`=QLs2iP$L@jRWtVja5zkj#yc?d5Myf6YV+t~)}A`VGn*hJoHoNA zpY(aEDLQIQ?1Ql3oiSwj<@QS?Z(~HJxZ*?^Dghp>!#&`k>7v|3&EnihjLpd^q>me; z6U!ZE<7kYU#nH&}ahV`4r)URaO51=wVVk1a0fj2fv}g@C$gD6S9IN+(qq~J8sC#s- z-ES;7M2qsJL{5BBu94u&N(*dJCG1e40go2p)8FCF;T=WMU>+n9$GGjm-;AyhZ|3}t zjB%*lpZ-u>cXa?eM|6+`+32!AX(-x{Ev7#bi%<6N(cuimk^bkadV=eN!I6?LTXi}c6lClnc?<1XUD<8N!Wi73BdDoUm35e-l}(<%T@rbOCd z{XM7p*T*2KA|g#HFiXe>vMs|X1QcyqWf4D=ecEqYNSY>F{YgL`2L;+mO6fPlq9= z5n^t8ndYN~%3JkwB4(@K@>bo=YaVwoYd5U> zB~fjc{Xe$LN~Ruir=0kzhjxw~qtAjnwKwub5*C)U;nAb{wV-aw;pR_tH2Y^?3~}=? zl}OTFOTCHY`wT2dHf;0_%t$_xpcm0h<|DmKbuw#VC%kXFrFt^@L@S&E;?!;V_Kck= z<~bT33LgWzfgyiH_tRcZr{s&11~Rg8lE{W}Y!zV5czi;P;x89FNgNo&Jaq&>qv{(< zVy#&kCD!$GhXjzu_M`P@WybUG0@6@F;@Wnkwwa?C&>g||zyb4)IL>lV?o3*@k5KAOC-T~9zvEnW0 zi~MlXqns|!)l@#rn_14lX1}X2I3SFp)l^dx`$9g;((Mikv6x=nkoQyEvo$3J{nlEJ zK7geucrKf>W2KPE%PLIg6{d#4I-Ls-7hLCft=4E(D~1-If2b`Y&TzXZk{)dtX~1R& zh2`t0Qn%S~+@ALRhQ}-kh(P-dL4+d&9eHox3Y%hu-e)30vJ+P4dGj?TAS_vKH8Kt4Z~b>9ri z*l*pJarRSv*fcHuxaZD@m9#N%*fS5-LfD?Mu(g@*S+HJXR0dOb&^OJ(oppxCO`Me_ zbC&4wzNG>3L_v*K$gP;OJPh?mQJX`T$jpu)I$4hLR)(wCYnUDRuyn`R;X{|j4+=DcgM$> z__z<+$?%BfK`la(Kw+wZ4V8SbaR^c73_$i`&7#B8O1G4>K=5of*sZJ69Nu73yUie^ zE|gUzADH8L%WlnnH(k;Kj}kF&<7l(dDus9SQ+xcp|w$9@b!vgn>JX&oPXi=?V4 zgzO0mS&JENf!YPbwbS5!e3{JzZUME&&M0lt8CJ|$up;a(t7|IY)Sji&is-Zb!Li%r zbHQu<8giKXP_S8ya;DOrGH=^C8<{I0+7|PYdJZfDwap`_^ME=%t~vJTfpA|#SrlO6 zOe309R`SdFM}i+CS?m7tjkZ%jbrs%+P{tOYyGg(;kd5MXf;6ddh)`$D5g*sQgxbUE z65O0L)oECp1(?74|6(-Y!FxY&WjFoX_2xv*xVyZs_rm&y3uRd{&#}VwImOWG5Mkm) zs&j7G4`&mpA@#+hYDYPf2rEE;?QiwGV%x1D*LlCaEHafLPpC_tYY$DBFCipkg@k#U zmjN7J(CWP;`Ug>>5)S&4a1qtJ<4FTUU|#frd5m&7|9qo>2_ut4QkcnO;^h7}8|ln5 zl7*d4T71$f?);|zkg3c`bcLU>(gWn5K!yUjDvYV4d6w5%>J1qsJr=x=o|?(^W`z(f zu@hyR>1e_2wLiFb5-D&mW|l}K|NW%eq8_vQ4ADeVS^OKvP5#IZGjTbpqenz3KD4?Tno=nBoB6p5b>?<2%4OuosI2ed2((@ zmM^zM7m(%Ru-MoAx_|J9ihZg+AWKW@R>$hAR8FV~FtGm=u^uX660?)-m8)_|}Zt@z* zq*27BZ;4b^aqYqABfD0A*tM!-^@rK9kG*XJwz8I6XZ}c)?E`RVbz%2dR&jMPD7N2Z_LZ~t7X@x7R|rKG&V{1D&*8y|KX00@!~(q*I#j9dgh4%p8X@KPYK|l)38l{{9-O zg!0@P3iAb{by%Qxp6F~v)l2$BI99K?08I(kjt8f)+;$Vw0wb+YZSj!mK+PZ>f{T}>5FKRg~~Ter43otJ~T z+1eg-K45d4&B*eAX|)C*c@`9mW_wOa8pjsk0mejI(w;s>1v$VI5 ziPQY(Gti8EmD)Zv=P}`^HOS9Jy%Szh0d}kZ)j#JcBV9eRS$nvqcxAnvvFOv)dodO@ zW^dXT7>k~E#-gWl=!xu48HOVG8PY-7^Gz*H_t5-gUV=BX7C@E=j|72rlRu44(r>}11FUs0+k5sr^TbafM2HgN6_@Bl zeb{IKe5cvNOpz`w*pHA@b6BY`vtL90{z=%7{l>~mgl9r%tJ`TjD%h`d;q7b2z>NnM z6JOdae!;-jC=z8@xrp^+C^K+$uNRX5kX_R9ia}&%lJNW$V7elTAWg`4g^2yykQL2^ zpDpbRH@=Hl^%pDlZ$FxI*pi#t8UDOVEi%u-N} zKvsBR%hJ_WGPd|~a?hssv+IiU&9Co~$*woRGUl1Bx&(|VY>0GJj8`vroBlFt;j}}} zPi)+d6FJ2B!&_;r(pcKJqq z*a5IIN=w5C^aLEYz%L$#aDKv{NBw=VNFsKdqm_ZYtu@Q+Rp!g^Y~w2PUR-5ikJZAWeb*?a%(3t6Q7QP(YN>)}` z&4ndKG3O}8YrJbxSuMx$y2S*0t}|d@&$TA}|1YSzwI@wl@;e_@x_Hg;ul&2fgS=;0 z*HpAwfu3G>eESVFsd8sLo;8}1qae-2rIAiBa5p+3X9~zVQzaim$r_?oP6kR8J9FeK z)$Yr31RU63#N8q*bXRW^xd5CZFviE+McTeb%_3o=uG5jC$#g+ zqTa1uf{udZM8>Y&XnDfn;|2wWr7|<+4YWHC_2m!=Wv8Cb!2v0k{|vfSA#A*9%F**Y)o?V3wFvw< zk!6+fSW9C`D~^3Fmh`I~>E9>P|5YBnS^isaeztBaXf(ar3s@Qy(C>f(*0=)NRKTSw z;2;VJ-Fg4TXvk=W+iq{v=6Gtn41vbl zkhm<$Tat^|)Nl3kaP&pd0iScAaj(LcmUVzKigU8JcrTeh+=(7(Q7&$}d!71!b`1uH zE>*d+)6ZAsrt;%uxv6eh{OOck7Ucs!IL%XLVgOg8Ib0p$gS77c5TsrXB&qganGu*^SKL|<>btJol!jbZz8Bd|uC5$d&s23L(@No&RZ~c*TL#R* zO$foyr%E~8>EQLN=qS!N_YN}KacJ|lrV707F06~a^5F#=y88JTW4tMYAx_~yICB;{ zId797ad20F<_`hA7E}3ztNf_bgnJk2{J^x{{GUzJ!~caCO7K9`9MdMF}yElJZo^{j~AbSz+!sdMut(*_%*ry7{8Ac8APQqZqM_f z?{0IMp(!K)R=rbl&8N49{dN(tG$CHrJMqA};q;pO_p6NeAnaMfw5Rdg_oeTi4S)2RRJ_Rq-AbhqlYrQ>fp+twf+ z+|FZ;W)x|J#mD0|R}kk0+LHWuX>J|s!HQh-H~4m4X#u>#yveKYx>HP^4CGqIlW+YT zD`eOAt|{=|3D)B*#ZQj4R5RBd>!k*KQF$y`3ytH)8 z1q=A_73Hoo@BD#vxZusyH%z@Xm>#6qNMQuQJan>D6cHxyA8mk(HFV^}<2Lg$ad6_s z^UNd1SXV&KFGgRaP?U_&8ctOI_l&ehzE*t(MJvheBw_u}5cb{D1cR@0a|%MIob7sB z01Cspq>Z`oQg2`C{B~_bwEEfCsz2U)7O_#aqm@ zCQ=|};I3ULMhoq`j|VIdrx@dE>sZ}o30n7ig9X__g-o}XCX5B6u?fgLj+Kp9?ByiY&Y zygA(_OIqyj1R$F$rd)l=W$)LRGSw8UhXgdKP01fHr2rsjKbaMgH32}~wiVq&=}&Dh z-{&MJ%TsNxMpJ%RBywgGTKn<&V77u8$Z#g3*p?gHcF9q`fVfS6wLQ^mcFnSbbPztP znu2r%8omYQ@dw4|bS1H+?UwSuyUZ(F08_rv=FD652!vsC=dJn$uM)g=zvt>U8N zw)AK^yv@7{K>RiqEzUCU!_7gMbpif?mOIV`IC5vuQ|M%^Z>BOnhtHK&%*x=+YN}Nu zA9WZ}4=T!)Y1dbRJM6N~lqO0$l?oC`k7?IZeNDpx^A6IT6(HDq`!&d+|SZvJhW%gE< z&ZZLt%^Im@SieZ#t#mxj7H5Lu$x5y@AxhI>9AI2+uNUfQwqYw|VqdvAiXjFXck2eK z@>(73b+V&MO3waw2~}3f>3id!L}`s?_YR6E%PE|i!|^UpGci@rEx2jzIgM{kLJ;Tlf(uV4I)P_ zIm~B}44Z0Br>E-Wv(Zp>VpwE~X0e~avZnJjgl%0Y4qrFZ$eWUZNYZrA()75Nywz7R z4#M`I1k9v2WfdQy^kQofzX5C$EMHWU3mwo^QG%R-_~OFZLF}L zHNQs^^$f?EKWCP{NHdujs!q*&<|x_EPAJ|2Q2|Q?6>DJN`Ww#a6?*ZBN+WBi3p$qsFw{XG6k&3oCOJusuou2|#w?z}+O zhfTT^Z;U2rttzA12w1>OiP-tD6FV(ueV5H;rV%BU@R}ctsnDK2^`3y7t+B)`J;-tt zQw3p;Uf$6R6)Bdq)%FevW(GMl|KAO_X8xyS*ow8PySYdU;U-ZatGd3LB6EHcQ~@&9 zifcE-#){-_V!)+F5Qw2DowILX_97jBejc5u*q^!SDPg`Kz(0EQJj^yE8Dk5n+QaJy zN}Vv@b0LrN`YItIu-&NJhXrpy&9A<&0<0_;_ZFqIVqJKtS&S1~4stXX9R)rxpctFK zxx5ylJ4ENpm_?S5stUE#G#eR3(;0@HITik;+bw>j9Q2A>Fl|0h*PB7g39q(+((vSq zNSIGTz9#fseQ$^!5|#-p$}r#UMZ>^jl*?1fkX953JH9+pbp#(Z4Z3?F>MdXzAX5vv zh0?j{DRUIHxH%HL%1fjPt%$})C@<~ILFb@CnrGLH?ql7AcwITVPlVM&#hi2Z1EQ$K z8{OZfu6ZG?#ZzdaO7%+C-A%K6xXi7H3g)l;$tXO)e(F2PnJ%qh1!RNdC7f~B*9FS= z8~!lT_s2)?pJw6%P|61SWk21THX~W}@L4w+r4J4F5hDE_Te;|AYmP1qkVNM9)My5^EC4dlmfgK4|f7}ngu)nU+VBe}r$}DIA z4p3SZxGoUppZ+v$Y9JNcuk>epnwrL{@*oRb3rc&^dy_agx*g%)N%q+v#F@;J4vD=4 z;%xp{Bs%%~ErE&rje~@-vzGH?Tpu8{ZrlfTY&Ayj26+8+13HGXb8!u@cXqd!vw2l) z(ZG1gZu`=QtDn^H8^bb?Dp1(fEQ8Pv-CL5*s_IfT+-KdR4!Z4K>^j6=a{XN^5;aYx3}oX4k&bTljr)M~v!DJVZ3t@6XJs4a?Y!KznUSB?ObgS2Oso z;5z^;b!>7S)K_5|U^W{0QHnILt)mZahRn^|{0Y*|xtS)bn2OzT(JwD=0e>QyL!wms zH53n%j9WFFBh*~PSo}Zf?No}Q{fXB3a)gp+&Ei3k*aU<}yJh4!YDec7A2CRH?)9^v zmnFou88m_b!LPOc`vOmY5-IQptrB;tR9mx4F8hv*&AzOQ*d@<0PW-fmy+gF}$;LzD zKh*ndCx2REaq>!kX>@Vv|1xdv`jHPCv|KUgqjuPcZcCf%6s=70#r+X%dh=%KpT_i0 zar=|XKT!f9zvpGt`Kfk)1=8EUTw@TLESlROjCfi9MDiza>eSzT3_ATgQ^VQa5z0HV z*4gnS6N%=H%RoZvP^N6&-$Uq(W5?U2XIQ@<+XFl8TyEDcq|h-1!}4r?;fjzGGV-r? z`E$shqXRDAaJL1b2S$hy=QqzNf43^)|4)jS#LyeR}HM4r?#)?f2d*SnDKu!E7z z66PsMFgu>~yZ=KXu=6KOC1Bn~iThcTwEn?w>ja9_PNHMcwR3p#|2lV&A_ z^lKj096OAU3n;%|-kMH(JBe?iQ>EZ|X9j{LAgkM0x%n-)uHCNgd-HukQO=kk!ETdQ zOPVa({7o-QZ?OyHoR8g)AQiTq-qRRgtvVZ5 z@8)l}^=q$X$5$l2`U4wg6lN=I-ET`1f^Fs&rfzk%jIztKz3xwC2)Jjq!h8RQax!EJ zkzYCqwH02QNUkHw-#qX1C2p>@`*GLbDGkLig!#}$Wv6-RudMsCojhB96`)6pykFZd zE%NNbt;KZ5-&qmYhY^3Yqk?tQ$!a|^3U)63wrK6|eTmi&`)OAu(dwnW(`!?G+v4SA_MN%V`C>`2l=4 zHORHX=_Bj}jCkwXMI#n3;Km!ir5zzhE9Uv2Q&LAJSN;$?0z*-v^pC(*g_$o=!Tg+w zAWU|8^1O^>8%Y*t|7}b4;>EJ0v!9gGB}rQ$vKKkc?2PPlz9fSdtY5+Z6UjT69m6eP zz75izxy%S+X0vB5*EG<4xz!%7?%a}ojkzr^aPBiJ!2_fEH11(x3`tw)Xebr?C{i+?AbKWPDjuAi4uFD?=W6a@!omDnvm3;SuLCFpbGYtkZtb6PP|MOJN!q5=? z^J8wj2UVf>H6_2M-J=5VdzaZaS-LFEi#M1JXdxh08#THWfn>Ydz1i&F?t*7Cz+MkT z>Ya-aqf~P_H7AmvtOb12j@yY(YW7hp&WU}Ys~tmM1-0t_huT{O#o_ccTr>ec(axIz zPElqm?gb3DW$$TLVf1NFKc(N(Qg)k( zAvbv3h36dEE@2!b|IFC1vX1bIn<5?GYO;Vf`KTW=QtFlGYlFDI{e-wXh^wpkKpBrw znY<#W{5YeCrO_!xZuu1)-pFtvo_$b`F?B@`6WnIM%MScu6ym5NP`$>OU67{w`Q=+I{;_Qi3{hxM~xZDRR_jfDInX1oj zVm7_NI@&K6KS}X0*sB#E@U%G(LIZUrJDiYzBhwLA_?nNIoI*eRg#@zy2L_#b-D=nV z!f?K7)7iQ(STj&ihRwwJU&^CH{lbTu|h(6lop-EE8dF2olVhNxd4!cffiR$x{V3Dj=Uh z%J0F+n4xr-4f|}D4kyxBD*pj2x?MWly~l}ncI^R=c8;#TM{mXNgI3e5b)+14P*u*r z4l!Jp*<)ZSD%Wg?Ypp4R9tU=+>cn#}7j{;(1=8Ijijd!PTOhpsKmi@i z7;Y!QM}zZ$w0`gol?5zeN{9@begUAU>4R7|C6Z^{q^7%x!Okz@TA6mZ!iO^;Z-UhT zOU}Sff}CZN>7luow?;Ei>%waS$Zw}bw6PNc>Hsb&Z3py8aVrbqR7q*$+OnSq%!FA_ zcxq@y!nFhdr`qz0R3@OuHfFI3&<+&yG_I<&c`Db#gsmF^xN@9*ni)l{e4O7zvdkl; zacbX*zRYhZMC((Xfusto&}n?*4u@5XZ|mTBddM>vL%M26oMo=&{?<~fmPnExnV&sd)GcxL(E z3;A8bQ_FJ~&#!nM=Xs7NnWPaF=5cDAZm(6atyt8I_}t?-m|Ai*-xc*1T`^UZx<;A*O)sae^usT-;TKhyRrX~{SjASj zNov$ayY?ZIR#d5>;pc<;JfWhVpNKikW<4T|tW#vJUqKtSwR6{xXu4>%E31@5@_{uX zCkYe&c!W^IDo=eh=oLqo)6n5So@Y@{NSH%OBeA3bUYAIoXG;q8A^1#+CvpFbf5z92 zutZ#lPGrl7)N1x%6SQg?-2Xy#E}1T2QoKP}33Iu|d%}1h_=J@({g9DUc4Mcx%|*_0 zky37V&JdvI?6T1Ld*+5)5TCe_#->7hjOCrcY4--Ux@Dr;9)B!sI~?J4v)J--@yJDe zu%L8zyz>_)TIVu5SVv;0n}nfmD(EPaFpQyYa=N_CMzWt&>c7U47QCODt@qR7YppMv zH8?!@A=5<#kcD_%Oa1DhIZPDX|0~I|Hx@q5=omY)MlQI$yy8Ci z{36$rqz_4c=919LA4ICUO?H;ZS?q6?rLyWECpof=H~*}5YbH4Kp*T$tUUAn^0lV$95A0$b zb?pa^Scg3kbeLmh8@if)@3mWu2Q8PyxGgXsA7ekQH$ty4W4M@b%yA_(v&fU34g&w< zU-X60<7^XL;oNL4B859TYn)16dl3xkH@mciv}@o`9}htC^~B}KJyhamP^OHdGiqox z(`^mTQcYjCmo}JQ}!lUG~Xoi|zwVSbfkCnC#W0EZO63 ze$9p;p~{jEB(dd&g!TnMsBq!KJmRgU$uiNV*W}Gu@F@HZe)W9pe=9u5GoSYwo;!G+ z<>^Rz{i?c2bkMNwd8ymc%Kfom5eUI`KY9 z;aVIe%uI#KU~Vp@1&o4(`JI9eptKEtgVOH0K7)5%07g|-GpQvR4P}-QFT01?0r`)l zsokMHe2Te{vFJOXY!o(qUzG=&AbID8`5jU)?(rNjt#Fh;pWrpEdcNvVoJhJ#&yg@W zM=3YgHQ{k@9Z-C|gH3mYOIJX;w~mC6tj8@M&wxFf7#SQ^ik*7gD;(KVj1t=n037B- zjGA(lBNjyY?3B+7ogIOpsl>)ofJvPRMn3?Gv_5^+rXrN9AWt$d7M1z^;8!hF#n8!&aaV zxLfhHmf2}k_5EWwswDaeH577rSB}ZgpHKVSZqBTb9wmE;z4*?~q%3 zm*SMICp9FJSHS5dhgt_^hd-Iu^M#4QuPPhnbIY%-d=J|fNL~mMGbM*zRYDdBq44?} z>6oypbtIEyG5ZEp6|q&ZYG~irhgSFBnvnUws4s2cMRwo5s^i3`f@W{fDI(@{NP|O* zhT>)R(US}Ydp^#I_B2*x7nG66_gl;^McK78mt)&@K13AT<=#*k>!q^{1oBgBjN&XRHVROAAnU2)nDSryuJG*8~AEu}_*}SUqD|FV)TzYUiHN;2sCBlgY z=L&dx07g&NMo#Oqb+Og@oT4aUw3*EKJIZR#IV}ClyjUAkGCfr~GCie8sbpa6s}L(# z^qXCCNuMu66kP~RHjz+dU!0xkbVhUP?@o}>ntkQwtw@wsRIJlmyl#j6%ZXjQYcbHi zP$=3Iw6pHECSnk)(DuHDSUq@G?N5S`qNp)YO?FdpO`>B>B3qRgv4_2Fz=-_9CU-^p z1O#HjO=lP$NW8C%lGl`QC{3%ipCYhL`!-DIG4aYixf95Yxl#cg_3hJWDzr#>jduG?n* zVZYj}FP6whNRStkC6P%(3d^F)ur@oq;`h;(#(=E9Lp5iS-(z8=-~V&JWDXQdL*m-8 zLUBXm2rzm<)wJ8bFa)fr{k?{OGzbhw|X&Nb*rC;&1%&AG&74gtuH+{ z3sQEYVIxGvO17L_M1{fkyWrft#aP+Zi1@Vr@Lg+J-nPhcGAzr4m(5wrJgD4d{q?t~ z@9d|)N}DIh9}pS#b;m&aozW`cW&>*vpiW12^*D;X(8O9qw zzG9DSyH{c2j@~LhaILqNb=74p>zI5WR0-P%Fq>H{+QNGmmb6xV_r8lEr!z}-#g0_u zUlEHK>ow1ID125IceW_-?*xW*j}rB*7FF1ltN9D;I*}Zs;(a9uDAzw&ku_+X%sUe& zdoeV(#>e!JkLk+>Hs(jBoLdeB7)_ZSSaG`f5a>IrkROT(GDej3@XkMYy^cE!tG8t%XI)AS_GS9lbuEXR6sEdPK z-N|*0q7K@i)$m+iEozqFJ3}qPr&Rx6cUQQO5ZH2+9vy|X^ZI*5Sie;-?aSX4D-Pel zzB&_C9ibv}5`rR@*_EvFs?+JE$MTY15s9ogY(q9_-%?lY2n@Z)lQz|B1N$dU!bSKU z(A^bc^$YaT{yWKzW7FL+`)#_r=AcxE6-?aPU6(MEfjQNC!@;~>NvRqA@i>AleNUfZ z^PNbXY`*8{SL@qP*0AHa>AtoEq@2^Qsz0i?+~zeS`(0XN6oU9NZ!p-@vw^0Noeo_oRMYwymdzL>& z!3;yrYbCF_i5e5hcdllzeW=M8I9cZBAdQ6fEhlu?2@T#PRbS2G{t?cY^@WlC=Eixz zwmtan4YbDrEhfXz!%ySFS;?k77qm+foV>4EYo0ya^hOA>^;=cq)p$jQ{NW;UNSM28 zsX!epP@6OKO^9+9VIPJCTR>XOkt@Lz`uL#^unpbJ6}XFXc3eU_`0Tv<8y6j}tDd!e z;48?pz54U1zPRSgzOfxHD}*sR6QWOih&o${*28Fn6Qf)^HNa(|p4OmVza?Q-5(w0KG)DI#ShTmFQf&K|*!J7ntT1bF%81Z(^lw|0PQa&- zGFdiQiunk}e_){bjND52c4k8LWy3&oHgSI4ubDSdw1#zmKx!95IuK;$SRj7K%%1v3 zm?~?FgYa{BHs?Pm63ITWz1sZ1^oZ1m(B^S2z$=43X|HW^QD1Wq;{tjp9MOhPJnBS0 zj#IM>bcjzi?q4=mZlkFVavP=n(gkCEu*#T|wg!}?yngA;W+P;Ps9#~Of)&V*Zwb+5 z<_bY$D`}~X;E>XT=Q}*#ADE2|V5-vCSjHaz(eh)3($x3csbnUO0Gugd6Sg_qdg^E$ zWh2OOK*O%QTupKToKb^TGDO)BfYrKovt10$U`}>gRN7?o1K~<7r@0OQ-x?bpCq8z;}WXyL>*&P5;I8o*ey7z0I)e1L6;>&N|I zn@C2w6`zm_Q|Ch`Kio3&sA}Z&OC5YEytJ9WVZhbW$sc>_$}*gTlNO@;V2Tm*k-!sW$?gDC`px_U@JStR%UtNBU#AE-3qre1r@O! zHTExzyqiK@JiO62>CN93fa0*`6``Tc;h5+~yxPq=mIuk7K_(hvi5SbejjPCDu2Xoe z!k6gu@XDGMoLhU54o9pCAjL^^i@BPHK&kR{C=-8U4ZlQf(!crj%uYcCW5%vkX$xVEy z-Je>=l4_)unMTHnhDD(Z2*#W%tFQU)GDxk-PpqFZneKf4q=f-z!HWH^{@T9Y zQ}jLOrOe(8sp{pWmeo0{L4}z^TgCADP*j<@PB5wHqP7j)&BeI9Bp;CaiAlowD|Ng~ z&B|MKa}a7zVE%ZH6;CIS&bo1wR+xNOZrF@lL3lDVnd1`4 zUtS5((dl5&p0k{|)Z$7LN11t+!c_U`ej!CDLDbbg+Y%T@3ykf4{AjwV&E)??BZ-8g})G{`7{w;F8-*@ZkT4|Ho zi~|FyU4}l0_a9a6OIQC%v33`aV}k-pbMVT|Eksza5UXFj_m%dJvcwQt}`Wd1@dd(F%OlDgXzE?m9J0?Voxl+WC1 zx5xPu_j907l5!@BMDm~TI^Rjvt}7i~0W=s&;Wxoyq_25! zg{*DOWYDggJu`r^$%^y(>|#Gd(m$E<)xSMh@o@iv1q6~8_MFYZ!6H!Z2|)?G6CLwt zHs7Pd3RCQ3&JovLpMp6Dg<5|mTsExqe!tSYto$IJjt+# zka-_Jgw64BTjS$ijE~zMAGd?EYs2F>@7fuXOTs*Rk(Gn6a%G-2^WqCE`yXX)kdEN| zbb9u2>Abur8fscQuQ)Al)#3D0O>3vT8)LkHjqmRm9=V0*UY_^-=W~7!`pfXhNjzX~ z*}*j?T6Ip1$#xx;R9{1C1T4I?#&pQLz6${-r9f}hS;-@L-ymhPxdN#u*?_jr?e@4p zpO5f~vm8U|pIQYvN4=X-y(I6jG24mRW@DJBxDP=w%ZQ;ZH!0YLeuTqW@=_OiGodw` z+q5G?8ScmammKS&7TGB04N252MV;!RF1Asv+JT{5KUCDAF6t~7^|oK&-HQ5>HUYum zL@_Y)(2tSnCc*ZeT5C1R%k=rGOPEBmjF&XUE8a@S_`gZX9}>_Th}Td%E9^$hS+nss z`so$6D>2ODF0|UIZ1}Dab2%apSK@Ki$0WuIi5+URT0l&E^5cRcuw+UjTYi zCcwa~w&y|tpd0AyCiJ)Mr`1~w3~Tn?0hGvIrFG9ACC=>;c(1%3QW=(b7>%{W=W7)9 z_=8fJy4yspcBGp01!9W&t}EGW{)B|!Rcw~MUq0pd6ZlR9d(4KvDoeB7;Es-bk6BMZ z$TXU$RDl-*Y$CaFmSq~S(0d-UHDU{-rM=JywinrEF0w`18?W%UCBgX2A_vo00o|P< zs>ylAm5eW!roOSLDC=7i9Vq?h89d$`rg=2AV@HIwi0VVSI&*u9MJ^wIHO^|4w$}#T!tvex3cN}MzU~UF;2Ur)BlIgQ`mjDlrtet3 z5Y9^pb15C4-B8%N>@a422Ix)*LgE2Bnnv?9!y`3l!$G`iFFwhKlAJjZDBS>QH0yTL zJnQtS(+pPe6$1N}1-8Xx1FR(3FX8Xpe*)pAj89w>r9*bQa5I{`?rw%ZS28dfUAx{Pi-zB+N!F7e-DB7aM1yrku)WG zE(Hz$Q98~2GOuaJdcLRq>Q$X|`OJ!U>xv7^mu~_AdzjRv#b^)MTm70jl&{)?emeAn zd%oK4b1QAaG{IIxd6+b#Y$;wwLUiTUzkxHGUSML27wZ_oFf9GN4Lh8$yeEo! zuQQGKmL}Hne)u}G(Z0L1HB0g%vAn<()-a8JDCR^S&Ve+=KZM!L(2<$!@n-=MACaTr zku;27E1z^VPm{xU7=Tt77m!FRm}KYUztX1qJer9)8r2B8UZJ9|Rr(@*aw-T7yMvix z?$cY7qu2An~FpBjfJdy-XHZZl`8a4Z%CVGr!L*tM_u))X^_tco<_DP5z%j#IDM zZoEHv9ZLY=FP}Q zEPg_z=EKR=&L(a~Tex7Q4TWW~CpA95f+r?jO4Wxr+Pnmm62yxp#6tc^(T7H>YiOD}Z+Gm&tC=FOfH8K&`?)E`B*@AZC3 zpJy`O_vu|_tuyNq8V|a=Y+}Bx%bkDXPMvSk&7NuQ1!DDuefu6OYMfDcQBLes%R4Ip z6TBRFuw4PWY#2!krlP;Qxq%5&e8%31RiKmfs(MTj^W{8{969s#1rtcToC@kSaQ*^E zZ@xbJ>6Ww^3mMGt;e^iIeZ;NSU2|~F3ho|0NeAw_>wb?d9{f*z{l&a`GhJ_eQn;A- zjUQWxJC%&#@=}hnbKUF4k#_i6zbMw0=Be3CT=QW~Cqer+x^^BLAChr&QPZ{UZQA$@ zZK{M7Ih|x4CyQz{Zn2{=h1=>Fj&Q)_uG=*1h?Lv4MVL-Ev%_0%*VFA(wbs!%SK9%H zY+;VJ1)JqbxTd<(Z=I3;UUbnE1^hEp#rbgY_KZikrIJ-8Qd#2b+q;jbw$LU$$p(s+F>N<6bSKPPEi=@SCWsoPoR?~RV#5&e&6*nxh@nxL9Dq8)Y@lhAA z@#SD}SMd(J1r%B{(Hrjt;)sst;v}0x{F@+5!AY-}Uy!?Qwdud5iVd3|!_&7C`k+AzqqO z2%FPeC9hvan7zIC2*Sd4+zUvFWD7H8s`bJ|%}D|)Ytb~9=X5-CT!{%5F|d!t*A_+0 zmoM#0->(`87;*{Kt;1|_zknfN?KA@FK-vAe*EtASssJ?vT&Tr02(~^>PW@{;P6m-8 zgls4g`2YKmaS!B4iq1-mCOoUtTZ}#L#m5b)b7G6?0Zsxq1msT#cyel}s^ty6}9;1AA ze0*Pfwdf~wntvGKlp{mOLZCHcAVNfLH|Jn__Oc#Q!4K? z-QM$b$=nfhXI^rJou@<}m&_eEbK(5?^VH_{G@}+)R?f@#o-p^at1IodY#*1*9Y6Ex zYh8L*Mpg zXg{ATtNe;SzU#)Z)w4udsIb-MAQ6IWOkY1hg?PHex0u zVDq{5C{5U=jPewIV0q)AOPAN-r~HzI^XJZ0iNb@Uk-;NJUg3AJUyrMM@JJs$Hmz&N z)XV0H5d3tm-(6Vg!kHRs8Rfcg2ayEDW!;Vwhb%MGNY~fMc#|_& zT|WAp(#hwZQyxYTrZ|;9dieNJB^B)jDO~M6cf#o5BQi25T=k9`aoXThPiv1*_`!r1 zk3MI7NqNbLb4HyWMk-Eajha+aGA0aduX}REq%r4|j}Bv$UUiHv88NA3Y8Y%`Rrv5p z!$+Lc-n=k;#PG3`4-8k^CRLP|pEG=XSdhvWEleIg{6BDw+fk%sc=_a#ib)g7O2RB5 zUMG}{3j;6j`Q%O4(5G*$@cY#NzRCY)9Uh$Ok9XfJPFfk+9S|b1+ zUR=_-dNA|q`fBE4Q;Cm-)P88;Q(JWSnv$J$B~RfKQSZ{cV~YAIQH>>UMjA_+ zc;1liXAP~u`==96G?m*}va3U?D}S?6Is8Q{)qp?FQk9J-d^D6?+CYaxP#NZhMDq3t zk_~~Hl~I_eACLluGupNyB`1>elrH64eQaN{KOhotYA!{oCNKxs4XSUT5h}h9p<`l*;Ho%VCoDJ5vC@HfWyeEKg?Q~Q`IGvuh8byVPu7pBB^{{ zpe=j2lIh?wsF_`CGh4I?!Tdk$y?tC%)fP8CASx;y^>(qWth`++GBhf3NU6L-zrjoMA#GO+vLr$`;~kO5`(_54!UA8}zLU=iDjybe0;>_&I}8GGObI48I8^^ZB%sL{ zuic?->6>aPtJBNLPEXIyMJnlBpwhe3@Rr`yRk8|iSZVETIdp^~QALO+rCf)yJ>HQ_ zdbz7hT5m?aOjtGorD%_$fUZe0;nM>2(mc0(Sc63Dx8R{f>05{aLW#RLzMZD(mG-!& zpRT|~JQbMO4+SkWeU@xmEF=}tv~)sM;!opLr<+D4N0!fsv^Kd-+Y-UFv{|AG*={gvJ244MDV}h?y8CKy=aeUcpU3laBtI{~bN_jfW%DBa zdx3eaElRAyWν51={dZ1BY4A`^N;@_Z7WppVw&qD2_?OGw@eIc_w%^l<9ZW6-6g zfF*(WL&Y5wUo7UhL&ZWR^;NQu1}{DFqACvSe8PcHzQbC|X9%J!3Fnp>%vn;LODIkw zI`HWxDOMZy)Zj9El4CC(S>7ST*l&PdHe=ELb%a&D@NfnXIOh0~PT<{%V1{TlA+1V$ zfppMhB2!$DMieZ8u9 za^*UDP6I(6ya`R?IHM3}@M~Or<=S;EoW5`A@xJsCMw*B~Xyv*XDb{NDHSS`h`jLq!$hS1v63sECUOxs=p8=R@wUxGQ;CLqSl3de zAOG23X?jbC1B**LR4Z>L-Ol^2#Upo9RC+%yz6K?Duc@PCg+a;M#bj_tp=kwzDEa_Y z%j3O-2=aBMc4F^KSMl>otZj%T5%U)igY$nTu%c7;8~E>FmtOp@M**m(=nE1^d)rS2 zY43`}x#)xgiQ!oE#6CA{*5c>+@E*JbA}a^HDhJHLLKOUj1{BT^v&lETQzGWgRY4jp z8KqFXaVQnv{}P3~gLu;n@VknV9a?hB3^5i@PuZb5$a^;&wR6DaJ!DjgJc#&HdW6Zk zxU`}1l`uqGK&k2E;%g+OGfPCnhv=O-OZ?*I$B6Q|ae5pmC9){8r?kNgRe`m^*-IvD zqv9ZmVicmDKJ-)r%#3ZFSYs{0ZT4;xuhpZ^dd8F#U-xEQ`#9BGWQaVCpOUtp_`XO< zTXy+-2W!DAXOQ2Zs(G3a0@vazr@C~w@Ehfs?BJYr-2(|8gM z2t99i#o;@i1k?cU-#i+^B$hA;xA^zAPzOpq<pz-5byCOxKL}X+R@q=RUvF}>5oVt zz6O<2L2jj>C3srWR}26RPtgFpq_^mT*HbhAFBvFK(ld>|*{XZVI1#r{#L4gwQRqR7 zc8{U8yyaLX6Vccp@f6i}(e7x)^K2VDsp`R=-;*bCfffoOeWH>&BXQWjE|>`e<|)#M zevcXCL2TV!C9t)p8h^H>P^o+Fz_{W50e)-oXWLxzdj@`NZ&G`%I2~xu&B#FERS5z- z-XSRgUQuwEht?t2V(v?Los8GZ_ BC*}K5P`+D&^F74*?m_?Xw*du*dDI>MzQ0m9 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 literal 0 HcmV?d00001 diff --git a/build/nsis.simplefc.source.zip b/build/nsis.simplefc.source.zip new file mode 100644 index 0000000000000000000000000000000000000000..d7476022ad9949b5e84585ade092c4e499f28677 GIT binary patch literal 23209 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. + +//go:build tools +// +build tools + +package tools + +import ( + // Tool imports for go:generate. + _ "github.com/fjl/gencodec" + _ "github.com/golang/protobuf/protoc-gen-go" + _ "golang.org/x/tools/cmd/stringer" +) diff --git a/build/travis_keepalive.sh b/build/travis_keepalive.sh new file mode 100755 index 0000000..77cc623 --- /dev/null +++ b/build/travis_keepalive.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# travis_keepalive runs the given command and preserves its return value, +# while it forks a child process what periodically produces a log line, +# so that Travis won't abort the build after 10 minutes. + +# Why? +# `t.Log()` in Go holds the buffer until the test does not pass or fail, +# and `-race` can increase the execution time by 2-20x. + +set -euo pipefail + +readonly KEEPALIVE_INTERVAL=300 # seconds => 5m + +main() { + keepalive + $@ +} + +# Keepalive produces a log line in each KEEPALIVE_INTERVAL. +keepalive() { + local child_pid + # Note: We fork here! + repeat "keepalive" & + child_pid=$! + ensureChildOnEXIT "${child_pid}" +} + +repeat() { + local this="$1" + while true; do + echo "${this}" + sleep "${KEEPALIVE_INTERVAL}" + done +} + +# Ensures that the child gets killed on normal program exit. +ensureChildOnEXIT() { + # Note: SIGINT and SIGTERM are forwarded to the child process by Bash + # automatically, so we don't have to deal with signals. + + local child_pid="$1" + trap "kill ${child_pid}" EXIT +} + +main "$@" diff --git a/build/update-license.go b/build/update-license.go new file mode 100644 index 0000000..f548a59 --- /dev/null +++ b/build/update-license.go @@ -0,0 +1,426 @@ +// 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 . + +//go:build none +// +build none + +/* +This command generates GPL license headers on top of all source files. +You can run it once per month, before cutting a release or just +whenever you feel like it. + + go run update-license.go + +All authors (people who have contributed code) are listed in the +AUTHORS file. The author names are mapped and deduplicated using the +.mailmap file. You can use .mailmap to set the canonical name and +address for each author. See git-shortlog(1) for an explanation of the +.mailmap format. + +Please review the resulting diff to check whether the correct +copyright assignments are performed. +*/ + +package main + +import ( + "bufio" + "bytes" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "slices" + "strconv" + "strings" + "sync" + "text/template" + "time" +) + +var ( + // only files with these extensions will be considered + extensions = []string{".go", ".js", ".qml"} + + // paths with any of these prefixes will be skipped + skipPrefixes = []string{ + // boring stuff + "vendor/", "tests/testdata/", "build/", + + // don't relicense vendored sources + "common/bitutil/bitutil", + "common/prque/", + "crypto/blake2b/", + "crypto/bn256/", + "crypto/bls12381/", + "crypto/ecies/", + "graphql/graphiql.go", + "internal/jsre/deps", + "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 + } + + // paths with this prefix are licensed as GPL. all other files are LGPL. + gplPrefixes = []string{"cmd/"} + + // this regexp must match the entire license comment at the + // beginning of each file. + licenseCommentRE = regexp.MustCompile(`^//\s*(Copyright|This file is part of).*?\n(?://.*?\n)*\n*`) + + // this text appears at the start of AUTHORS + authorsFileHeader = "# This is the official list of go-ethereum authors for copyright purposes.\n\n" +) + +// this template generates the license comment. +// its input is an info structure. +var licenseT = template.Must(template.New("").Parse(` +// Copyright {{.Year}} The go-ethereum Authors +// This file is part of {{.Whole false}}. +// +// {{.Whole true}} is free software: you can redistribute it and/or modify +// it under the terms of the GNU {{.License}} as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// {{.Whole true}} is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU {{.License}} for more details. +// +// You should have received a copy of the GNU {{.License}} +// along with {{.Whole false}}. If not, see . + +`[1:])) + +type info struct { + file string + Year int64 +} + +func (i info) License() string { + if i.gpl() { + return "General Public License" + } + return "Lesser General Public License" +} + +func (i info) ShortLicense() string { + if i.gpl() { + return "GPL" + } + return "LGPL" +} + +func (i info) Whole(startOfSentence bool) string { + if i.gpl() { + return "go-ethereum" + } + if startOfSentence { + return "The go-ethereum library" + } + return "the go-ethereum library" +} + +func (i info) gpl() bool { + for _, p := range gplPrefixes { + if strings.HasPrefix(i.file, p) { + return true + } + } + return false +} + +func main() { + var ( + files = getFiles() + filec = make(chan string) + infoc = make(chan *info, 20) + wg sync.WaitGroup + ) + + writeAuthors(files) + + go func() { + for _, f := range files { + filec <- f + } + close(filec) + }() + for i := runtime.NumCPU(); i >= 0; i-- { + // getting file info is slow and needs to be parallel. + // it traverses git history for each file. + wg.Add(1) + go getInfo(filec, infoc, &wg) + } + go func() { + wg.Wait() + close(infoc) + }() + writeLicenses(infoc) +} + +func skipFile(path string) bool { + if strings.Contains(path, "/testdata/") { + return true + } + for _, p := range skipPrefixes { + if strings.HasPrefix(path, p) { + return true + } + } + return false +} + +func getFiles() []string { + cmd := exec.Command("git", "ls-tree", "-r", "--name-only", "HEAD") + var files []string + err := doLines(cmd, func(line string) { + if skipFile(line) { + return + } + ext := filepath.Ext(line) + for _, wantExt := range extensions { + if ext == wantExt { + goto keep + } + } + return + keep: + files = append(files, line) + }) + if err != nil { + log.Fatal("error getting files:", err) + } + return files +} + +var authorRegexp = regexp.MustCompile(`\s*[0-9]+\s*(.*)`) + +func gitAuthors(files []string) []string { + cmds := []string{"shortlog", "-s", "-n", "-e", "HEAD", "--"} + cmds = append(cmds, files...) + cmd := exec.Command("git", cmds...) + var authors []string + err := doLines(cmd, func(line string) { + m := authorRegexp.FindStringSubmatch(line) + if len(m) > 1 { + authors = append(authors, m[1]) + } + }) + if err != nil { + log.Fatalln("error getting authors:", err) + } + return authors +} + +func readAuthors() []string { + content, err := os.ReadFile("AUTHORS") + if err != nil && !os.IsNotExist(err) { + log.Fatalln("error reading AUTHORS:", err) + } + var authors []string + for _, a := range bytes.Split(content, []byte("\n")) { + if len(a) > 0 && a[0] != '#' { + authors = append(authors, string(a)) + } + } + // Retranslate existing authors through .mailmap. + // This should catch email address changes. + authors = mailmapLookup(authors) + return authors +} + +func mailmapLookup(authors []string) []string { + if len(authors) == 0 { + return nil + } + cmds := []string{"check-mailmap", "--"} + cmds = append(cmds, authors...) + cmd := exec.Command("git", cmds...) + var translated []string + err := doLines(cmd, func(line string) { + translated = append(translated, line) + }) + if err != nil { + log.Fatalln("error translating authors:", err) + } + return translated +} + +func writeAuthors(files []string) { + var ( + dedup = make(map[string]bool) + list []string + ) + // Add authors that Git reports as contributors. + // This is the primary source of author information. + for _, a := range gitAuthors(files) { + if la := strings.ToLower(a); !dedup[la] { + list = append(list, a) + dedup[la] = true + } + } + // Add existing authors from the file. This should ensure that we + // never lose authors, even if Git stops listing them. We can also + // add authors manually this way. + for _, a := range readAuthors() { + if la := strings.ToLower(a); !dedup[la] { + list = append(list, a) + dedup[la] = true + } + } + // Write sorted list of authors back to the file. + slices.SortFunc(list, func(a, b string) int { + return strings.Compare(strings.ToLower(a), strings.ToLower(b)) + }) + content := new(bytes.Buffer) + content.WriteString(authorsFileHeader) + for _, a := range list { + content.WriteString(a) + content.WriteString("\n") + } + fmt.Println("writing AUTHORS") + if err := os.WriteFile("AUTHORS", content.Bytes(), 0644); err != nil { + log.Fatalln(err) + } +} + +func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) { + for file := range files { + stat, err := os.Lstat(file) + if err != nil { + fmt.Printf("ERROR %s: %v\n", file, err) + continue + } + if !stat.Mode().IsRegular() { + continue + } + if isGenerated(file) { + continue + } + info, err := fileInfo(file) + if err != nil { + fmt.Printf("ERROR %s: %v\n", file, err) + continue + } + out <- info + } + wg.Done() +} + +func isGenerated(file string) bool { + fd, err := os.Open(file) + if err != nil { + return false + } + defer fd.Close() + buf := make([]byte, 2048) + n, err := fd.Read(buf) + if err != nil { + return false + } + buf = buf[:n] + for _, l := range bytes.Split(buf, []byte("\n")) { + if bytes.HasPrefix(l, []byte("// Code generated")) { + return true + } + } + return false +} + +// fileInfo finds the lowest year in which the given file was committed. +func fileInfo(file string) (*info, error) { + info := &info{file: file, Year: int64(time.Now().Year())} + cmd := exec.Command("git", "log", "--follow", "--find-renames=80", "--find-copies=80", "--pretty=format:%ai", "--", file) + err := doLines(cmd, func(line string) { + y, err := strconv.ParseInt(line[:4], 10, 64) + if err != nil { + fmt.Printf("cannot parse year: %q", line[:4]) + } + if y < info.Year { + info.Year = y + } + }) + return info, err +} + +func writeLicenses(infos <-chan *info) { + for i := range infos { + writeLicense(i) + } +} + +func writeLicense(info *info) { + fi, err := os.Stat(info.file) + if os.IsNotExist(err) { + fmt.Println("skipping (does not exist)", info.file) + return + } + if err != nil { + log.Fatalf("error stat'ing %s: %v\n", info.file, err) + } + content, err := os.ReadFile(info.file) + if err != nil { + log.Fatalf("error reading %s: %v\n", info.file, err) + } + // Construct new file content. + buf := new(bytes.Buffer) + licenseT.Execute(buf, info) + if m := licenseCommentRE.FindIndex(content); m != nil && m[0] == 0 { + buf.Write(content[:m[0]]) + buf.Write(content[m[1]:]) + } else { + buf.Write(content) + } + // Write it to the file. + if bytes.Equal(content, buf.Bytes()) { + fmt.Println("skipping (no changes)", info.file) + return + } + fmt.Println("writing", info.ShortLicense(), info.file) + if err := os.WriteFile(info.file, buf.Bytes(), fi.Mode()); err != nil { + log.Fatalf("error writing %s: %v", info.file, err) + } +} + +func doLines(cmd *exec.Cmd, f func(string)) error { + stdout, err := cmd.StdoutPipe() + if err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + s := bufio.NewScanner(stdout) + for s.Scan() { + f(s.Text()) + } + if s.Err() != nil { + return s.Err() + } + if err := cmd.Wait(); err != nil { + return fmt.Errorf("%v (for %s)", err, strings.Join(cmd.Args, " ")) + } + return nil +} diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..39ff5d8 --- /dev/null +++ b/circle.yml @@ -0,0 +1,32 @@ +machine: + services: + - docker + +dependencies: + cache_directories: + - "~/.ethash" # Cache the ethash DAG generated by hive for consecutive builds + - "~/.docker" # Cache all docker images manually to avoid lengthy rebuilds + override: + # Restore all previously cached docker images + - mkdir -p ~/.docker + - for img in `ls ~/.docker`; do docker load -i ~/.docker/$img; done + + # Pull in and hive, restore cached ethash DAGs and do a dry run + - go get -u github.com/karalabe/hive + - (cd ~/.go_workspace/src/github.com/karalabe/hive && mkdir -p workspace/ethash/ ~/.ethash) + - (cd ~/.go_workspace/src/github.com/karalabe/hive && cp -r ~/.ethash/. workspace/ethash/) + - (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6) + + # Cache all the docker images and the ethash DAGs + - for img in `docker images | grep -v "^" | tail -n +2 | awk '{print $1}'`; do docker save $img > ~/.docker/`echo $img | tr '/' ':'`.tar; done + - cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash + +test: + override: + # Build Geth and move into a known folder + - make geth + - cp ./build/bin/geth $HOME/geth + + # Run hive and move all generated logs into the public artifacts folder + - (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=go-ethereum:local --override=$HOME/geth --test=. --sim=.) + - cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/logs/* $CIRCLE_ARTIFACTS diff --git a/cmd/abidump/main.go b/cmd/abidump/main.go new file mode 100644 index 0000000..ae1ac64 --- /dev/null +++ b/cmd/abidump/main.go @@ -0,0 +1,74 @@ +// 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 ( + "encoding/hex" + "flag" + "fmt" + "os" + "strings" + + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/ethereum/go-ethereum/signer/fourbyte" +) + +func init() { + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "") + flag.PrintDefaults() + fmt.Fprintln(os.Stderr, ` +Parses the given ABI data and tries to interpret it from the fourbyte database.`) + } +} + +func parse(data []byte) { + db, err := fourbyte.New() + if err != nil { + die(err) + } + messages := apitypes.ValidationMessages{} + db.ValidateCallData(nil, data, &messages) + for _, m := range messages.Messages { + fmt.Printf("%v: %v\n", m.Typ, m.Message) + } +} + +// Example +// ./abidump a9059cbb000000000000000000000000ea0e2dc7d65a50e77fc7e84bff3fd2a9e781ff5c0000000000000000000000000000000000000000000000015af1d78b58c40000 +func main() { + flag.Parse() + + switch { + case flag.NArg() == 1: + hexdata := flag.Arg(0) + data, err := hex.DecodeString(strings.TrimPrefix(hexdata, "0x")) + if err != nil { + die(err) + } + parse(data) + default: + fmt.Fprintln(os.Stderr, "Error: one argument needed") + flag.Usage() + os.Exit(2) + } +} + +func die(args ...interface{}) { + fmt.Fprintln(os.Stderr, args...) + os.Exit(1) +} diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go new file mode 100644 index 0000000..0149dec --- /dev/null +++ b/cmd/abigen/main.go @@ -0,0 +1,241 @@ +// Copyright 2016 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 ( + "encoding/json" + "fmt" + "io" + "os" + "regexp" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common/compiler" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +var ( + // Flags needed by abigen + abiFlag = &cli.StringFlag{ + Name: "abi", + Usage: "Path to the Ethereum contract ABI json to bind, - for STDIN", + } + binFlag = &cli.StringFlag{ + Name: "bin", + Usage: "Path to the Ethereum contract bytecode (generate deploy method)", + } + typeFlag = &cli.StringFlag{ + Name: "type", + Usage: "Struct name for the binding (default = package name)", + } + jsonFlag = &cli.StringFlag{ + Name: "combined-json", + Usage: "Path to the combined-json file generated by compiler, - for STDIN", + } + excFlag = &cli.StringFlag{ + Name: "exc", + Usage: "Comma separated types to exclude from binding", + } + pkgFlag = &cli.StringFlag{ + Name: "pkg", + Usage: "Package name to generate the binding into", + } + outFlag = &cli.StringFlag{ + Name: "out", + Usage: "Output file for the generated binding (default = stdout)", + } + langFlag = &cli.StringFlag{ + Name: "lang", + Usage: "Destination language for the bindings (go)", + Value: "go", + } + aliasFlag = &cli.StringFlag{ + Name: "alias", + Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2", + } +) + +var app = flags.NewApp("Ethereum ABI wrapper code generator") + +func init() { + app.Name = "abigen" + app.Flags = []cli.Flag{ + abiFlag, + binFlag, + typeFlag, + jsonFlag, + excFlag, + pkgFlag, + outFlag, + langFlag, + aliasFlag, + } + app.Action = abigen +} + +func abigen(c *cli.Context) error { + utils.CheckExclusive(c, abiFlag, jsonFlag) // Only one source can be selected. + + if c.String(pkgFlag.Name) == "" { + utils.Fatalf("No destination package specified (--pkg)") + } + var lang bind.Lang + switch c.String(langFlag.Name) { + case "go": + lang = bind.LangGo + default: + utils.Fatalf("Unsupported destination language \"%s\" (--lang)", c.String(langFlag.Name)) + } + // If the entire solidity code was specified, build and bind based on that + var ( + abis []string + bins []string + types []string + sigs []map[string]string + libs = make(map[string]string) + aliases = make(map[string]string) + ) + if c.String(abiFlag.Name) != "" { + // Load up the ABI, optional bytecode and type name from the parameters + var ( + abi []byte + err error + ) + input := c.String(abiFlag.Name) + if input == "-" { + abi, err = io.ReadAll(os.Stdin) + } else { + abi, err = os.ReadFile(input) + } + if err != nil { + utils.Fatalf("Failed to read input ABI: %v", err) + } + abis = append(abis, string(abi)) + + var bin []byte + if binFile := c.String(binFlag.Name); binFile != "" { + if bin, err = os.ReadFile(binFile); err != nil { + utils.Fatalf("Failed to read input bytecode: %v", err) + } + if strings.Contains(string(bin), "//") { + utils.Fatalf("Contract has additional library references, please use other mode(e.g. --combined-json) to catch library infos") + } + } + bins = append(bins, string(bin)) + + kind := c.String(typeFlag.Name) + if kind == "" { + kind = c.String(pkgFlag.Name) + } + types = append(types, kind) + } else { + // Generate the list of types to exclude from binding + var exclude *nameFilter + if c.IsSet(excFlag.Name) { + var err error + if exclude, err = newNameFilter(strings.Split(c.String(excFlag.Name), ",")...); err != nil { + utils.Fatalf("Failed to parse excludes: %v", err) + } + } + var contracts map[string]*compiler.Contract + + if c.IsSet(jsonFlag.Name) { + var ( + input = c.String(jsonFlag.Name) + jsonOutput []byte + err error + ) + if input == "-" { + jsonOutput, err = io.ReadAll(os.Stdin) + } else { + jsonOutput, err = os.ReadFile(input) + } + if err != nil { + utils.Fatalf("Failed to read combined-json: %v", err) + } + contracts, err = compiler.ParseCombinedJSON(jsonOutput, "", "", "", "") + if err != nil { + utils.Fatalf("Failed to read contract information from json output: %v", err) + } + } + // Gather all non-excluded contract for binding + for name, contract := range contracts { + // fully qualified name is of the form : + nameParts := strings.Split(name, ":") + typeName := nameParts[len(nameParts)-1] + if exclude != nil && exclude.Matches(name) { + fmt.Fprintf(os.Stderr, "excluding: %v\n", name) + continue + } + abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse + if err != nil { + utils.Fatalf("Failed to parse ABIs from compiler output: %v", err) + } + abis = append(abis, string(abi)) + bins = append(bins, contract.Code) + sigs = append(sigs, contract.Hashes) + types = append(types, typeName) + + // Derive the library placeholder which is a 34 character prefix of the + // hex encoding of the keccak256 hash of the fully qualified library name. + // Note that the fully qualified library name is the path of its source + // file and the library name separated by ":". + libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x + libs[libPattern] = typeName + } + } + // Extract all aliases from the flags + if c.IsSet(aliasFlag.Name) { + // We support multi-versions for aliasing + // e.g. + // foo=bar,foo2=bar2 + // foo:bar,foo2:bar2 + re := regexp.MustCompile(`(?:(\w+)[:=](\w+))`) + submatches := re.FindAllStringSubmatch(c.String(aliasFlag.Name), -1) + for _, match := range submatches { + aliases[match[1]] = match[2] + } + } + // Generate the contract binding + code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) + if err != nil { + utils.Fatalf("Failed to generate ABI binding: %v", err) + } + // Either flush it out to a file or display on the standard output + if !c.IsSet(outFlag.Name) { + fmt.Printf("%s\n", code) + return nil + } + if err := os.WriteFile(c.String(outFlag.Name), []byte(code), 0600); err != nil { + utils.Fatalf("Failed to write ABI binding: %v", err) + } + return nil +} + +func main() { + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) + + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/abigen/namefilter.go b/cmd/abigen/namefilter.go new file mode 100644 index 0000000..eea5c64 --- /dev/null +++ b/cmd/abigen/namefilter.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "strings" +) + +type nameFilter struct { + fulls map[string]bool // path/to/contract.sol:Type + files map[string]bool // path/to/contract.sol:* + types map[string]bool // *:Type +} + +func newNameFilter(patterns ...string) (*nameFilter, error) { + f := &nameFilter{ + fulls: make(map[string]bool), + files: make(map[string]bool), + types: make(map[string]bool), + } + for _, pattern := range patterns { + if err := f.add(pattern); err != nil { + return nil, err + } + } + return f, nil +} + +func (f *nameFilter) add(pattern string) error { + ft := strings.Split(pattern, ":") + if len(ft) != 2 { + // filenames and types must not include ':' symbol + return fmt.Errorf("invalid pattern: %s", pattern) + } + + file, typ := ft[0], ft[1] + if file == "*" { + f.types[typ] = true + return nil + } else if typ == "*" { + f.files[file] = true + return nil + } + f.fulls[pattern] = true + return nil +} + +func (f *nameFilter) Matches(name string) bool { + ft := strings.Split(name, ":") + if len(ft) != 2 { + // If contract names are always of the fully-qualified form + // :, then this case will never happen. + return false + } + + file, typ := ft[0], ft[1] + // full paths > file paths > types + return f.fulls[name] || f.files[file] || f.types[typ] +} diff --git a/cmd/abigen/namefilter_test.go b/cmd/abigen/namefilter_test.go new file mode 100644 index 0000000..ccee712 --- /dev/null +++ b/cmd/abigen/namefilter_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNameFilter(t *testing.T) { + t.Parallel() + _, err := newNameFilter("Foo") + require.Error(t, err) + _, err = newNameFilter("too/many:colons:Foo") + require.Error(t, err) + + f, err := newNameFilter("a/path:A", "*:B", "c/path:*") + require.NoError(t, err) + + for _, tt := range []struct { + name string + match bool + }{ + {"a/path:A", true}, + {"unknown/path:A", false}, + {"a/path:X", false}, + {"unknown/path:X", false}, + {"any/path:B", true}, + {"c/path:X", true}, + {"c/path:foo:B", false}, + } { + match := f.Matches(tt.name) + if tt.match { + assert.True(t, match, "expected match") + } else { + assert.False(t, match, "expected no match") + } + } +} diff --git a/cmd/blsync/main.go b/cmd/blsync/main.go new file mode 100644 index 0000000..854c997 --- /dev/null +++ b/cmd/blsync/main.go @@ -0,0 +1,104 @@ +// 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 main + +import ( + "context" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/beacon/blsync" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" +) + +func main() { + app := flags.NewApp("beacon light syncer tool") + app.Flags = flags.Merge([]cli.Flag{ + utils.BeaconApiFlag, + utils.BeaconApiHeaderFlag, + utils.BeaconThresholdFlag, + utils.BeaconNoFilterFlag, + utils.BeaconConfigFlag, + utils.BeaconGenesisRootFlag, + utils.BeaconGenesisTimeFlag, + utils.BeaconCheckpointFlag, + //TODO datadir for optional permanent database + utils.MainnetFlag, + utils.SepoliaFlag, + utils.GoerliFlag, + utils.BlsyncApiFlag, + utils.BlsyncJWTSecretFlag, + }, + debug.Flags, + ) + 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 + } + app.Action = sync + + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func sync(ctx *cli.Context) error { + // set up blsync + client := blsync.NewClient(ctx) + client.SetEngineRPC(makeRPCClient(ctx)) + client.Start() + + // run until stopped + <-ctx.Done() + client.Stop() + return nil +} + +func makeRPCClient(ctx *cli.Context) *rpc.Client { + if !ctx.IsSet(utils.BlsyncApiFlag.Name) { + log.Warn("No engine API target specified, performing a dry run") + return nil + } + if !ctx.IsSet(utils.BlsyncJWTSecretFlag.Name) { + utils.Fatalf("JWT secret parameter missing") //TODO use default if datadir is specified + } + + engineApiUrl, jwtFileName := ctx.String(utils.BlsyncApiFlag.Name), ctx.String(utils.BlsyncJWTSecretFlag.Name) + var jwtSecret [32]byte + if jwt, err := node.ObtainJWTSecret(jwtFileName); err == nil { + copy(jwtSecret[:], jwt) + } else { + utils.Fatalf("Error loading or generating JWT secret: %v", err) + } + auth := node.NewJWTAuth(jwtSecret) + cl, err := rpc.DialOptions(context.Background(), engineApiUrl, rpc.WithHTTPAuth(auth)) + if err != nil { + utils.Fatalf("Could not create RPC client: %v", err) + } + return cl +} diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go new file mode 100644 index 0000000..350b85d --- /dev/null +++ b/cmd/bootnode/main.go @@ -0,0 +1,209 @@ +// Copyright 2015 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 . + +// bootnode runs a bootstrap node for the Ethereum Discovery Protocol. +package main + +import ( + "crypto/ecdsa" + "flag" + "fmt" + "net" + "os" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/p2p/netutil" +) + +func main() { + var ( + listenAddr = flag.String("addr", ":30301", "listen address") + genKey = flag.String("genkey", "", "generate a node key") + writeAddr = flag.Bool("writeaddress", false, "write out the node's public key and quit") + nodeKeyFile = flag.String("nodekey", "", "private key filename") + nodeKeyHex = flag.String("nodekeyhex", "", "private key as hex (for testing)") + 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", 3, "log verbosity (0-5)") + vmodule = flag.String("vmodule", "", "log verbosity pattern") + + nodeKey *ecdsa.PrivateKey + err error + ) + flag.Parse() + + glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false)) + slogVerbosity := log.FromLegacyLevel(*verbosity) + glogger.Verbosity(slogVerbosity) + glogger.Vmodule(*vmodule) + log.SetDefault(log.NewLogger(glogger)) + + natm, err := nat.Parse(*natdesc) + if err != nil { + utils.Fatalf("-nat: %v", err) + } + switch { + case *genKey != "": + nodeKey, err = crypto.GenerateKey() + if err != nil { + utils.Fatalf("could not generate key: %v", err) + } + if err = crypto.SaveECDSA(*genKey, nodeKey); err != nil { + utils.Fatalf("%v", err) + } + if !*writeAddr { + return + } + case *nodeKeyFile == "" && *nodeKeyHex == "": + utils.Fatalf("Use -nodekey or -nodekeyhex to specify a private key") + case *nodeKeyFile != "" && *nodeKeyHex != "": + utils.Fatalf("Options -nodekey and -nodekeyhex are mutually exclusive") + case *nodeKeyFile != "": + if nodeKey, err = crypto.LoadECDSA(*nodeKeyFile); err != nil { + utils.Fatalf("-nodekey: %v", err) + } + case *nodeKeyHex != "": + if nodeKey, err = crypto.HexToECDSA(*nodeKeyHex); err != nil { + utils.Fatalf("-nodekeyhex: %v", err) + } + } + + if *writeAddr { + fmt.Printf("%x\n", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:]) + os.Exit(0) + } + + var restrictList *netutil.Netlist + if *netrestrict != "" { + restrictList, err = netutil.ParseNetlist(*netrestrict) + if err != nil { + utils.Fatalf("-netrestrict: %v", err) + } + } + + addr, err := net.ResolveUDPAddr("udp", *listenAddr) + if err != nil { + utils.Fatalf("-ResolveUDPAddr: %v", err) + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + utils.Fatalf("-ListenUDP: %v", err) + } + defer conn.Close() + + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, nodeKey) + + listenerAddr := conn.LocalAddr().(*net.UDPAddr) + if natm != nil && !listenerAddr.IP.IsLoopback() { + natAddr := doPortMapping(natm, ln, listenerAddr) + if natAddr != nil { + listenerAddr = natAddr + } + } + + printNotice(&nodeKey.PublicKey, *listenerAddr) + cfg := discover.Config{ + PrivateKey: nodeKey, + NetRestrict: restrictList, + } + if *runv5 { + if _, err := discover.ListenV5(conn, ln, cfg); err != nil { + utils.Fatalf("%v", err) + } + } else { + if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { + utils.Fatalf("%v", err) + } + } + + select {} +} + +func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) { + if addr.IP.IsUnspecified() { + addr.IP = net.IP{127, 0, 0, 1} + } + n := enode.NewV4(nodeKey, addr.IP, 0, addr.Port) + fmt.Println(n.URLv4()) + fmt.Println("Note: you're using cmd/bootnode, a developer tool.") + fmt.Println("We recommend using a regular node as bootstrap node for production deployments.") +} + +func doPortMapping(natm nat.Interface, ln *enode.LocalNode, addr *net.UDPAddr) *net.UDPAddr { + const ( + protocol = "udp" + name = "ethereum discovery" + ) + newLogger := func(external int, internal int) log.Logger { + return log.New("proto", protocol, "extport", external, "intport", internal, "interface", natm) + } + + var ( + intport = addr.Port + extaddr = &net.UDPAddr{IP: addr.IP, Port: addr.Port} + mapTimeout = nat.DefaultMapTimeout + log = newLogger(addr.Port, intport) + ) + addMapping := func() { + // Get the external address. + var err error + extaddr.IP, err = natm.ExternalIP() + if err != nil { + log.Debug("Couldn't get external IP", "err", err) + return + } + // Create the mapping. + p, err := natm.AddMapping(protocol, extaddr.Port, intport, name, mapTimeout) + if err != nil { + log.Debug("Couldn't add port mapping", "err", err) + return + } + if p != uint16(extaddr.Port) { + extaddr.Port = int(p) + log = newLogger(extaddr.Port, intport) + log.Info("NAT mapped alternative port") + } else { + log.Info("NAT mapped port") + } + // Update IP/port information of the local node. + ln.SetStaticIP(extaddr.IP) + ln.SetFallbackUDP(extaddr.Port) + } + + // Perform mapping once, synchronously. + log.Info("Attempting port mapping") + addMapping() + + // Refresh the mapping periodically. + go func() { + refresh := time.NewTimer(mapTimeout) + defer refresh.Stop() + for range refresh.C { + addMapping() + refresh.Reset(mapTimeout) + } + }() + + return extaddr +} diff --git a/cmd/clef/README.md b/cmd/clef/README.md new file mode 100644 index 0000000..b7018a5 --- /dev/null +++ b/cmd/clef/README.md @@ -0,0 +1,922 @@ +# Clef + +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 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. + +Check out the + +* [CLI tutorial](tutorial.md) for some concrete examples on how Clef works. +* [Setup docs](docs/setup.md) for information on how to configure Clef on QubesOS or USB Armory. +* [Data types](datatypes.md) for details on the communication messages between Clef and an external UI. + +## Command line flags + +Clef accepts the following command line options: + +``` +COMMANDS: + init Initialize the signer, generate secret storage + attest Attest that a js-file is to be used + setpw Store a credential for a keystore file + delpw Remove a credential for a keystore file + gendoc Generate documentation about json-rpc format + help Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --loglevel value log level to emit to the screen (default: 4) + --keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore") + --configdir value Directory for Clef configuration (default: "$HOME/.clef") + --chainid value Chain id to use for signing (1=mainnet, 5=Goerli) (default: 1) + --lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength + --nousb Disables monitoring for and managing USB hardware wallets + --pcscdpath value Path to the smartcard daemon (pcscd) socket file (default: "/run/pcscd/pcscd.comm") + --http.addr value HTTP-RPC server listening interface (default: "localhost") + --http.vhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost") + --ipcdisable Disable the IPC-RPC server + --ipcpath Filename for IPC socket/pipe within the datadir (explicit paths escape it) + --http Enable the HTTP-RPC server + --http.port value HTTP-RPC server listening port (default: 8550) + --signersecret value A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash + --4bytedb-custom value File used for writing new 4byte-identifiers submitted via API (default: "./4byte-custom.json") + --auditlog value File used to emit audit logs. Set to "" to disable (default: "audit.log") + --rules value Path to the rule file to auto-authorize requests with + --stdio-ui Use STDIN/STDOUT as a channel for an external UI. This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user interface, and can be used when Clef is started by an external process. + --stdio-ui-test Mechanism to test interface between Clef and UI. Requires 'stdio-ui'. + --advanced If enabled, issues warnings instead of rejections for suspicious requests. Default off + --suppress-bootwarn If set, does not show the warning during boot + --help, -h show help + --version, -v print the version +``` + +Example: + +``` +$ clef -keystore /my/keystore -chainid 4 +``` + +## Security model + +The security model of Clef is as follows: + +* One critical component (the Clef binary / daemon) is responsible for handling cryptographic operations: signing, private keys, encryption/decryption of keystore files. +* Clef has a well-defined 'external' API. +* The 'external' API is considered UNTRUSTED. +* Clef also communicates with whatever process that invoked the binary, via stdin/stdout. + * This channel is considered 'trusted'. Over this channel, approvals and passwords are communicated. + +The general flow for signing a transaction using e.g. Geth is as follows: +![image](sign_flow.png) + +In this case, `geth` would be started with `--signer http://localhost:8550` and would relay requests to `eth.sendTransaction`. + +## TODOs + +Some snags and todos + +* [ ] Clef should take a startup param "--no-change", for UIs that do not contain the capability to perform changes to things, only approve/deny. Such a UI should be able to start the signer in a more secure mode by telling it that it only wants approve/deny capabilities. +* [x] It would be nice if Clef could collect new 4byte-id:s/method selectors, and have a secondary database for those (`4byte_custom.json`). Users could then (optionally) submit their collections for inclusion upstream. +* [ ] It should be possible to configure Clef to check if an account is indeed known to it, before passing on to the UI. The reason it currently does not, is that it would make it possible to enumerate accounts if it immediately returned "unknown account" (side channel attack). +* [x] It should be possible to configure Clef to auto-allow listing (certain) accounts, instead of asking every time. +* [x] Done Upon startup, Clef should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode), invoking methods with the following info: + * [x] Version info about the signer + * [x] Address of API (HTTP/IPC) + * [ ] List of known accounts +* [ ] Have a default timeout on signing operations, so that if the user has not answered within e.g. 60 seconds, the request is rejected. +* [ ] `account_signRawTransaction` +* [ ] `account_bulkSignTransactions([] transactions)` should + * only exist if enabled via config/flag + * only allow non-data-sending transactions + * all txs must use the same `from`-account + * let the user confirm, showing + * the total amount + * the number of unique recipients + +* Geth todos + - The signer should pass the `Origin` header as call-info to the UI. As of right now, the way that info about the request is put together is a bit of a hack into the HTTP server. This could probably be greatly improved. + - Relay: Geth should be started in `geth --signer localhost:8550`. + - Currently, the Geth APIs use `common.Address` in the arguments to transaction submission (e.g `to` field). This type is 20 `bytes`, and is incapable of carrying checksum information. The signer uses `common.MixedcaseAddress`, which retains the original input. + - The Geth API should switch to use the same type, and relay `to`-account verbatim to the external API. +* [x] Storage + * [x] An encrypted key-value storage should be implemented. + * See [rules.md](rules.md) for more info about this. +* Another potential thing to introduce is pairing. + * To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API). + * Thus Geth/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests. + * This feature would make the addition of rules less dangerous. + +* Wallets / accounts. Add API methods for wallets. + +## Communication + +### External API + +Clef listens to HTTP requests on `http.addr`:`http.port` (or to IPC on `ipcpath`), with the same JSON-RPC standard as Geth. The messages are expected to be [JSON-RPC 2.0 standard](https://www.jsonrpc.org/specification). + +Some of these calls can require user interaction. Clients must be aware that responses may be delayed significantly or may never be received if a user decides to ignore the confirmation request. + +The External API is **untrusted**: it does not accept credentials, nor does it expect that requests have any authority. + +### Internal UI API + +Clef has one native console-based UI, for operation without any standalone tools. However, there is also an API to communicate with an external UI. To enable that UI, the signer needs to be executed with the `--stdio-ui` option, which allocates `stdin` / `stdout` for the UI API. + +An example (insecure) proof-of-concept of has been implemented in `pythonsigner.py`. + +The model is as follows: + +* The user starts the UI app (`pythonsigner.py`). +* The UI app starts `clef` with `--stdio-ui`, and listens to the +process output for confirmation-requests. +* `clef` opens the external HTTP API. +* When the `signer` receives requests, it sends a JSON-RPC request via `stdout`. +* The UI app prompts the user accordingly, and responds to `clef`. +* `clef` signs (or not), and responds to the original request. + +## External API + +See the [external API changelog](extapi_changelog.md) for information about changes to this API. + +### Encoding +- number: positive integers that are hex encoded +- data: hex encoded data +- string: ASCII string + +All hex encoded values must be prefixed with `0x`. + +### account_new + +#### Create new password protected account + +The signer will generate a new private key, encrypt it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and store it in the keystore directory. +The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts. + +#### Arguments + +None + +#### Result + - address [string]: account address that is derived from the generated key + +#### Sample call +```json +{ + "id": 0, + "jsonrpc": "2.0", + "method": "account_new", + "params": [] +} +``` +Response +```json +{ + "id": 0, + "jsonrpc": "2.0", + "result": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133" +} +``` + +### account_list + +#### List available accounts + List all accounts that this signer currently manages + +#### Arguments + +None + +#### Result + - array with account records: + - account.address [string]: account address that is derived from the generated key + +#### Sample call +```json +{ + "id": 1, + "jsonrpc": "2.0", + "method": "account_list" +} +``` +Response +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result": [ + "0xafb2f771f58513609765698f65d3f2f0224a956f", + "0xbea9183f8f4f03d427f6bcea17388bdff1cab133" + ] +} +``` + +### account_signTransaction + +#### Sign transactions + Signs a transaction and responds with the signed transaction in RLP-encoded and JSON forms. + +#### Arguments + 1. transaction object: + - `from` [address]: account to send the transaction from + - `to` [address]: receiver account. If omitted or `0x`, will cause contract creation. + - `gas` [number]: maximum amount of gas to burn + - `gasPrice` [number]: gas price + - `value` [number:optional]: amount of Wei to send with the transaction + - `data` [data:optional]: input data + - `nonce` [number]: account nonce + 2. method signature [string:optional] + - The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected. + + +#### Result + - raw [data]: signed transaction in RLP encoded form + - tx [json]: signed transaction in JSON form + +#### Sample call +```json +{ + "id": 2, + "jsonrpc": "2.0", + "method": "account_signTransaction", + "params": [ + { + "from": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db", + "gas": "0x55555", + "gasPrice": "0x1234", + "input": "0xabcd", + "nonce": "0x0", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x1234" + } + ] +} +``` +Response + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "tx": { + "nonce": "0x0", + "gasPrice": "0x1234", + "gas": "0x55555", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x1234", + "input": "0xabcd", + "v": "0x26", + "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", + "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" + } + } +} +``` +#### Sample call with ABI-data + + +```json +{ + "id": 67, + "jsonrpc": "2.0", + "method": "account_signTransaction", + "params": [ + { + "from": "0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "gas": "0x333", + "gasPrice": "0x1", + "nonce": "0x0", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x0", + "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" + }, + "safeSend(address)" + ] +} +``` +Response + +```json +{ + "jsonrpc": "2.0", + "id": 67, + "result": { + "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "tx": { + "nonce": "0x0", + "gasPrice": "0x1", + "gas": "0x333", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x0", + "input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "v": "0x26", + "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", + "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" + } + } +} +``` + +Bash example: +```bash +> curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ + +{"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} +``` + +### account_signData + +#### Sign data + Signs a chunk of data and returns the calculated signature. + +#### Arguments + - content type [string]: type of signed data + - `text/validator`: hex data with custom validator defined in a contract + - `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers + - `text/plain`: simple hex data validated by `account_ecRecover` + - account [address]: account to sign with + - data [object]: data to sign + +#### Result + - calculated signature [data] + +#### Sample call +```json +{ + "id": 3, + "jsonrpc": "2.0", + "method": "account_signData", + "params": [ + "data/plain", + "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db", + "0xaabbccdd" + ] +} +``` +Response + +```json +{ + "id": 3, + "jsonrpc": "2.0", + "result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" +} +``` + +### account_signTypedData + +#### Sign data + Signs a chunk of structured data conformant to [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and returns the calculated signature. + +#### Arguments + - account [address]: account to sign with + - data [object]: data to sign + +#### Result + - calculated signature [data] + +#### Sample call +```json +{ + "id": 68, + "jsonrpc": "2.0", + "method": "account_signTypedData", + "params": [ + "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + { + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "Person": [ + { + "name": "name", + "type": "string" + }, + { + "name": "wallet", + "type": "address" + } + ], + "Mail": [ + { + "name": "from", + "type": "Person" + }, + { + "name": "to", + "type": "Person" + }, + { + "name": "contents", + "type": "string" + } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + ] +} +``` +Response + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" +} +``` + +### account_ecRecover + +#### Recover the signing address + +Derive the address from the account that was used to sign data with content type `text/plain` and the signature. + +#### Arguments + - data [data]: data that was signed + - signature [data]: the signature to verify + +#### Result + - derived account [address] + +#### Sample call +```json +{ + "id": 4, + "jsonrpc": "2.0", + "method": "account_ecRecover", + "params": [ + "0xaabbccdd", + "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" + ] +} +``` +Response + +```json +{ + "id": 4, + "jsonrpc": "2.0", + "result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db" +} +``` + +### account_version + +#### Get external API version + +Get the version of the external API used by Clef. + +#### Arguments + +None + +#### Result + +* external API version [string] + +#### Sample call +```json +{ + "id": 0, + "jsonrpc": "2.0", + "method": "account_version", + "params": [] +} +``` + +Response +```json +{ + "id": 0, + "jsonrpc": "2.0", + "result": "6.0.0" +} +``` + +## UI API + +These methods needs to be implemented by a UI listener. + +By starting the signer with the switch `--stdio-ui-test`, the signer will invoke all known methods, and expect the UI to respond with +denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented. +See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'. + +All methods in this API use object-based parameters, so that there can be no mixup of parameters: each piece of data is accessed by key. + +See the [ui API changelog](intapi_changelog.md) for information about changes to this API. + +OBS! A slight deviation from `json` standard is in place: every request and response should be confined to a single line. +Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make +things simpler for both parties. + +### ApproveTx / `ui_approveTx` + +Invoked when there's a transaction for approval. + + +#### Sample call + +Here's a method invocation: +```bash + +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ +``` +Results in the following invocation on the UI: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_approveTx", + "params": [ + { + "transaction": { + "from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "gas": "0x333", + "gasPrice": "0x1", + "value": "0x0", + "nonce": "0x0", + "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "input": null + }, + "call_info": [ + { + "type": "WARNING", + "message": "Invalid checksum on to-address" + }, + { + "type": "Info", + "message": "safeSend(address: 0x0000000000000000000000000000000000000012)" + } + ], + "meta": { + "remote": "127.0.0.1:48486", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + } + ] +} + +``` + +The same method invocation, but with invalid data: +```bash + +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000002000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ +``` + +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_approveTx", + "params": [ + { + "transaction": { + "from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "gas": "0x333", + "gasPrice": "0x1", + "value": "0x0", + "nonce": "0x0", + "data": "0x4401a6e40000000000000002000000000000000000000000000000000000000000000012", + "input": null + }, + "call_info": [ + { + "type": "WARNING", + "message": "Invalid checksum on to-address" + }, + { + "type": "WARNING", + "message": "Transaction data did not match ABI-interface: WARNING: Supplied data is stuffed with extra data. \nWant 0000000000000002000000000000000000000000000000000000000000000012\nHave 0000000000000000000000000000000000000000000000000000000000000012\nfor method safeSend(address)" + } + ], + "meta": { + "remote": "127.0.0.1:48492", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + } + ] +} + + +``` + +One which has missing `to`, but with no `data`: + + +```json + +{ + "jsonrpc": "2.0", + "id": 3, + "method": "ui_approveTx", + "params": [ + { + "transaction": { + "from": "", + "to": null, + "gas": "0x0", + "gasPrice": "0x0", + "value": "0x0", + "nonce": "0x0", + "data": null, + "input": null + }, + "call_info": [ + { + "type": "CRITICAL", + "message": "Tx will create contract with empty code!" + } + ], + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} +``` + +### ApproveListing / `ui_approveListing` + +Invoked when a request for account listing has been made. + +#### Sample call + +```json + +{ + "jsonrpc": "2.0", + "id": 5, + "method": "ui_approveListing", + "params": [ + { + "accounts": [ + { + "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42", + "address": "0x123409812340981234098123409812deadbeef42" + }, + { + "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42", + "address": "0xcafebabedeadbeef34098123409812deadbeef42" + } + ], + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} + +``` + + +### ApproveSignData / `ui_approveSignData` + +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "ui_approveSignData", + "params": [ + { + "address": "0x123409812340981234098123409812deadbeef42", + "raw_data": "0x01020304", + "messages": [ + { + "name": "message", + "value": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004", + "type": "text/plain" + } + ], + "hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310", + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} +``` + +### ApproveNewAccount / `ui_approveNewAccount` + +Invoked when a request for creating a new account has been made. + +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "ui_approveNewAccount", + "params": [ + { + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} +``` + +### ShowInfo / `ui_showInfo` + +The UI should show the info (a single message) to the user. Does not expect response. + +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 9, + "method": "ui_showInfo", + "params": [ + "Tests completed" + ] +} + +``` + +### ShowError / `ui_showError` + +The UI should show the error (a single message) to the user. Does not expect response. + +```json + +{ + "jsonrpc": "2.0", + "id": 2, + "method": "ui_showError", + "params": [ + "Something bad happened!" + ] +} + +``` + +### OnApprovedTx / `ui_onApprovedTx` + +`OnApprovedTx` is called when a transaction has been approved and signed. The call contains the return value that will be sent to the external caller. The return value from this method is ignored - the reason for having this callback is to allow the ruleset to keep track of approved transactions. + +When implementing rate-limited rules, this callback should be used. + +TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`. + +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_onApprovedTx", + "params": [ + { + "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "tx": { + "nonce": "0x0", + "gasPrice": "0x1", + "gas": "0x333", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x0", + "input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "v": "0x26", + "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", + "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" + } + } + ] +} +``` + +### OnSignerStartup / `ui_onSignerStartup` + +This method provides the UI with information about what API version the signer uses (both internal and external) as well as build-info and external API, +in k/v-form. + +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_onSignerStartup", + "params": [ + { + "info": { + "extapi_http": "http://localhost:8550", + "extapi_ipc": null, + "extapi_version": "2.0.0", + "intapi_version": "1.2.0" + } + } + ] +} + +``` + +### OnInputRequired / `ui_onInputRequired` + +Invoked when Clef requires user input (e.g. a password). + +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_onInputRequired", + "params": [ + { + "title": "Account password", + "prompt": "Please enter the password for account 0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "isPassword": true + } + ] +} +``` + + +### Rules for UI apis + +A UI should conform to the following rules. + +* A UI MUST NOT load any external resources that were not embedded/part of the UI package. + * For example, not load icons, stylesheets from the internet + * Not load files from the filesystem, unless they reside in the same local directory (e.g. config files) +* A Graphical UI MUST show the blocky-identicon for ethereum addresses. +* A UI MUST warn display appropriate warning if the destination-account is formatted with invalid checksum. +* A UI MUST NOT open any ports or services + * The signer opens the public port +* A UI SHOULD verify the permissions on the signer binary, and refuse to execute or warn if permissions allow non-user write. +* A UI SHOULD inform the user about the `SHA256` or `MD5` hash of the binary being executed +* A UI SHOULD NOT maintain a secondary storage of data, e.g. list of accounts + * The signer provides accounts +* A UI SHOULD, to the best extent possible, use static linking / bundling, so that required libraries are bundled +along with the UI. + + +### UI Implementations + +There are a couple of implementation for a UI. We'll try to keep this list up to date. + +| Name | Repo | UI type| No external resources| Blocky support| Verifies permissions | Hash information | No secondary storage | Statically linked| Can modify parameters| +| ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- | +| QtSigner| https://github.com/holiman/qtsigner/ | Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)| +| GtkSigner| https://github.com/holiman/gtksigner | Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: | +| Frame | https://github.com/floating/frame/commits/go-signer | Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: | +| Clef UI| https://github.com/ethereum/clef-ui | Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)| diff --git a/cmd/clef/consolecmd_test.go b/cmd/clef/consolecmd_test.go new file mode 100644 index 0000000..c8b37f5 --- /dev/null +++ b/cmd/clef/consolecmd_test.go @@ -0,0 +1,124 @@ +// 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 main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" +) + +// 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.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") + if out := string(clef.Output()); !strings.Contains(out, + "Key imported:\n Address 0x9160DC9105f7De5dC5E7f3d97ef11DA47269BdA6") { + t.Logf("Output\n%v", out) + t.Error("Failure") + } + }) + // 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() + if have, want := clef.StderrText(), "Passwords do not match\n"; have != want { + t.Errorf("have %q, want %q", have, want) + } + }) + // 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() + if have, want := clef.StderrText(), + "password requirements not met: password too short (<10 characters)\n"; have != want { + t.Errorf("have %q, want %q", have, want) + } + }) +} + +// 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.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) + t.Error("Failure") + } + }) + 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() + // Secondly, do a listing, using the same datadir + clef = runWithKeystore(t, clef.Datadir, "--suppress-bootwarn", "--lightkdf", "list-accounts") + if out := string(clef.Output()); !strings.Contains(out, "0x9160DC9105f7De5dC5E7f3d97ef11DA47269BdA6 (keystore:") { + t.Logf("Output\n%v", out) + t.Error("Failure") + } + }) +} + +// 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.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) + t.Error("Failure") + } + }) + 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() + // Secondly, do a listing, using the same datadir + clef = runWithKeystore(t, clef.Datadir, "--suppress-bootwarn", "--lightkdf", "list-wallets") + if out := string(clef.Output()); !strings.Contains(out, "Account 0: 0x9160DC9105f7De5dC5E7f3d97ef11DA47269BdA6") { + t.Logf("Output\n%v", out) + t.Error("Failure") + } + }) +} diff --git a/cmd/clef/datatypes.md b/cmd/clef/datatypes.md new file mode 100644 index 0000000..8456edf --- /dev/null +++ b/cmd/clef/datatypes.md @@ -0,0 +1,224 @@ +## UI Client interface + +These data types are defined in the channel between clef and the UI +### SignDataRequest + +SignDataRequest contains information about a pending request to sign some data. The data to be signed can be of various types, defined by content-type. Clef has done most of the work in canonicalizing and making sense of the data, and it's up to the UI to present the user with the contents of the `message` + +Example: +```json +{ + "content_type": "text/plain", + "address": "0xDEADbEeF000000000000000000000000DeaDbeEf", + "raw_data": "GUV0aGVyZXVtIFNpZ25lZCBNZXNzYWdlOgoxMWhlbGxvIHdvcmxk", + "messages": [ + { + "name": "message", + "value": "\u0019Ethereum Signed Message:\n11hello world", + "type": "text/plain" + } + ], + "hash": "0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68", + "meta": { + "remote": "localhost:9999", + "local": "localhost:8545", + "scheme": "http", + "User-Agent": "Firefox 3.2", + "Origin": "www.malicious.ru" + } +} +``` +### SignDataResponse - approve + +Response to SignDataRequest + +Example: +```json +{ + "approved": true +} +``` +### SignDataResponse - deny + +Response to SignDataRequest + +Example: +```json +{ + "approved": false +} +``` +### SignTxRequest + +SignTxRequest contains information about a pending request to sign a transaction. Aside from the transaction itself, there is also a `call_info`-struct. That struct contains messages of various types, that the user should be informed of. + +As in any request, it's important to consider that the `meta` info also contains untrusted data. + +The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, they must be identical, otherwise an error is generated. However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket) + +Example: +```json +{ + "transaction": { + "from": "0xDEADbEeF000000000000000000000000DeaDbeEf", + "to": null, + "gas": "0x3e8", + "gasPrice": "0x5", + "value": "0x6", + "nonce": "0x1", + "data": "0x01020304" + }, + "call_info": [ + { + "type": "Warning", + "message": "Something looks odd, show this message as a warning" + }, + { + "type": "Info", + "message": "User should see this as well" + } + ], + "meta": { + "remote": "localhost:9999", + "local": "localhost:8545", + "scheme": "http", + "User-Agent": "Firefox 3.2", + "Origin": "www.malicious.ru" + } +} +``` +### SignTxResponse - approve + +Response to request to sign a transaction. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction. + +Example: +```json +{ + "transaction": { + "from": "0xDEADbEeF000000000000000000000000DeaDbeEf", + "to": null, + "gas": "0x3e8", + "gasPrice": "0x5", + "value": "0x6", + "nonce": "0x4", + "data": "0x04030201" + }, + "approved": true +} +``` +### SignTxResponse - deny + +Response to SignTxRequest. When denying a request, there's no need to provide the transaction in return + +Example: +```json +{ + "transaction": { + "from": "0x", + "to": null, + "gas": "0x0", + "gasPrice": "0x0", + "value": "0x0", + "nonce": "0x0", + "data": null + }, + "approved": false +} +``` +### OnApproved - SignTransactionResult + +SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)` + +This occurs _after_ successful completion of the entire signing procedure, but right before the signed transaction is passed to the external caller. This method (and data) can be used by the UI to signal to the user that the transaction was signed, but it is primarily useful for ruleset implementations. + +A ruleset that implements a rate limitation needs to know what transactions are sent out to the external interface. By hooking into this methods, the ruleset can maintain track of that count. + +**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. + +The `OnApproved` method cannot be responded to, it's purely informative + +Example: +```json +{ + "raw": "0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed", + "tx": { + "nonce": "0x64", + "gasPrice": "0x1", + "gas": "0x1", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "value": "0x1", + "input": "0x", + "v": "0x26", + "r": "0x716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293", + "s": "0x4e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed", + "hash": "0x662f6d772692dd692f1b5e8baa77a9ff95bbd909362df3fc3d301aafebde5441" + } +} +``` +### UserInputRequest + +Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free) + +Example: +```json +{ + "prompt": "The question to ask the user", + "title": "The title here", + "isPassword": true +} +``` +### UserInputResponse + +Response to UserInputRequest + +Example: +```json +{ + "text": "The textual response from user" +} +``` +### ListRequest + +Sent when a request has been made to list addresses. The UI is provided with the full `account`s, including local directory names. Note: this information is not passed back to the external caller, who only sees the `address`es. + +Example: +```json +{ + "accounts": [ + { + "address": "0xdeadbeef000000000000000000000000deadbeef", + "url": "keystore:///path/to/keyfile/a" + }, + { + "address": "0x1111111122222222222233333333334444444444", + "url": "keystore:///path/to/keyfile/b" + } + ], + "meta": { + "remote": "localhost:9999", + "local": "localhost:8545", + "scheme": "http", + "User-Agent": "Firefox 3.2", + "Origin": "www.malicious.ru" + } +} +``` +### ListResponse + +Response to list request. The response contains a list of all addresses to show to the caller. Note: the UI is free to respond with any address the caller, regardless of whether it exists or not + +Example: +```json +{ + "accounts": [ + { + "address": "0x0000000000000000000000000000000000000000", + "url": ".. ignored .." + }, + { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "url": "" + } + ] +} +``` diff --git a/cmd/clef/docs/clef_architecture_pt1.png b/cmd/clef/docs/clef_architecture_pt1.png new file mode 100644 index 0000000000000000000000000000000000000000..e40e532f3051d6b583698b539ae97bf12560f740 GIT binary patch literal 69221 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

*@V)cs~^7_udH`|DL0Y7#oV`hCx? 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≶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

OgkN>Q6asVi zhGSAvxW>kDi9@VXRxQs}lIUUtJO*;wD|)WdR|n)q>Rl5w?HX~I#_W&G)@Msutqp4d z7_R=Bef8a-)k`<4BNx))5{QjJWb8PMtL;nFjyv5HTdT};F=62o$)Iz3Pyl>VK1aVC zMgT^bK3vB>whvu3O;MZ}cYW80$3WA)gd4z#FtX?VHp4y#L zXKMD_X=!iAgnGf2O2XQcTiZQlvQZ`ofR5aphcR9$f7~EFQ&zgpP$DvlEUKIe% zeR#Q6Z2)94pGG5db$a5)P~CM$<0>2f+o_3m>8`J715bO(%pfEpMR%+aJj+8Kk)U&} z%KkP@R}r-7t~ z^(Ou>VI%l=t9HcCp)y}fINz(b_Ak?Zo;fA!W41IA$40-;O77F=~ag$5}rIe_n^(F&VD#>!zLh0;I;_k%kXmm|}I` zO_pUp_GTqV%ekVrr$R7i6Bif^U+6{EHR=`tdry;Xx;K_QdbeyFO`>V7%3A8MiipHj z^2GY}2JK|<0Y&m8UyJEKs5VL%%HqX*U>ZAk`_Zap&4fb-ZhI*w= zCyIZVOg5}_T(Uhm`e4=JcfZ%RD$Y2LIo{banURvb(rSdC-H2huG)O->DD185xp8kH z#MM+?eN)$U7gtO$udRLww@=_|Ww-Uz2NETv?#e75F6ANwp5FUzAGA#@CB!sV0$6X^Bsd6a`R4bmN0K+JEN+Y$xmO6FvOZ~ z`3$QyIlIW>#K)QEt852dk?==Q=;38f#(=eRdUI8C?|o7z3~Px>`0Oa$pzux1Lk$`?*ZFR@kOqF|boSEmaGfzroHg^I zV5_CA^a9ls|HR3cc=>wkFbV_8dVo|g-wz4er$$^$wIn*7Ts_8C!jIHSvIGmX3yKxA zKrGM|VtQ$t5RSDD_;jp0_ef9=m3~av1qvk%hnzz7QQm?onzq*ljE z);vYAoU0H|_e^j~Efr%r-=lBfTz+H(IaBOxyw)a6ws{9vJlZefpe!NZvRGj>; zPyCFNJIEmk^qDAO3A@BdgX@CCw%^-YH9yRKRC+RfIwOxc;G{=7MS#B9C|P}Ge)(d8 z-Tdjm)6nYn#E-N}^|{CIsYbQiKWrE3t*nk!g4g3bxSCD(C~mQoH8Bb|^d4%5?*;-* z_r{(}>*>oTY`pVPk~aLkUnr`8mf2+aF!2hy9T)HY7`l9Qk-A8sm-MH&F|??mFOM4B zL%`fIO=;qldfZfptp2sOZHyZdEprZFR7x2^aW}zt1cKDP5sOP#OgncR;J8G!Scz`< zIJvxyh<1RjtgV$eY+f@~W` zIhlh+DE(dEy6~bgp89QUQsnv+JABVTIDhdTFO#pfmiJ>a2!*oCPeP>*?u6_VZ-|vf z>DMf{qLY7~lefH+L8+747;o)io0?%TL>Xc|(7kQ_V=$@AA5vWSYJ{CvE0mwmgkdIn z@(~;)_`=gFr1H9d*YNG6x%kPvf;g+suO8A%x)$ubFMA@BdJWtdG^eG&7rI@kOV?2m_ar=sMyTsCUm{ z{5js!z9C3&+}mjE;=~xn(^wQo(GRrloEyQ(VoRb%12LEJHEF(fu(p$Ss5))(x#Bm{ z&A{Gbibqkp1U2FLjh+ryH#0dF2p^qZXsziZk%|dxs{J|D*u5Fe*ve0@H3Ogq(}zv_ zk_ylOx`wsoh0cR-^ar~Qx`n<&JA2G==T1{!?M>BGGd?*kAtI72kcZDFUf zZ)&7!r%U!q$w$#&-_~^YGp?jweP4`;g*OHH8Jpa{Q&8d1KNDn7wK9$x87UN#%+Q|* zI>t~dsZX%i);=o>0(gEPg%oJ7L{5?7`kk9nKHwx#g{rJ&*dy><8OLpdY2h|2NREiG z^7};VGX8R{tydJOWqZtb-0po4|JK%HKB1_7+HLjps~YKeGVlE=&`5TV&`_C`W!2IJ zlgT!s-1e0XElqM~jyrdhIEz){f4;76-`F>_e&ksupsi$i~#AnJe#He6xPbz&*qPBgl?PuB_XQ&Kl=U^BkP z-(srJobzI#z6n1p@bC>?$94b`%EgX6*sX6u+`z{+EY+e1y@{w92|Ufw$?OKvrAc$U zZUi{i=Q-$hssg7(d)RarXaE%trG$trHk@fSF}VxzK-dLC$S`7?5>rL0{ww_WmF{-t2u zUfs!27-Z=~s&;V;aVl^aI#u|yZWohS<pqV9iX>$ z5vydr#47Srg3C7Q(kT|Y@7tre|MY&z5krLz85veGe0TD`l8RcKaXtS~ClBI^b!=+& zgn!4&!{FTj|AudnM95^W+-x7B)nI`%;gO?{;$qcZ($deYEl{hudFfUxF<>N&NGkOQ zCjH4IM-3}~#aPOz9)5|~BvZC;*@xK;0m%4-1SKV9cPqhLfs-OCOm;YuB{670!x?RSc6!34ys% zchr;pM&nr9MmyzHrmd0ox)t-XCWEz9XH=0*Q00QS?ax#FYM;_+ep^qW80WFTm08>B z5Zz*@jX3zC%yRtXA=vr@xP^KxCVtaL!b(r%vjM~E)u~@g>OTga_!v+f(dkN5fY+rS z5HomB_J=zxj6-Fe%wCS_%E%bw>;|fh>!YlHeKO?`^G*amJNzb}D~O1Sgl`>Ru*Os| zZJo3d&$VxE&n|3m=u^FCzyDN%s*$~KhuBen=Y;q|R@PCl(?o&*&%c{})9 z>_i!{c01zL;`2{$wpQOgot8Cw5t(*W-a#?W(as)c)mbH7CT5NZU%wgcMXIBhSfCv` zlSrFlVuC|wqc!NT-8<~xUyO| z!YmJj9GIffTrLzU@i9fa6-F3LxSqEh%EYlY_cI!7HTaFGh>#RmENU$#mk`&8k(R8z zW*X0pY5?|Ldz+jM@f9DB+&Y3=uEg_n9oT`og^A85lSacR#vGdl-0jq=VMg^U5)O-)l*|&4k7r^#az&-$UZKw0b_BKz zF&<^HQy0$l#I2We431Du&d1(eW6`-C_e-g$)6$7lt(-$I!m_qVS@PQT9y4dX^n*^{ zN11gs8GhSoiTYb&1FgA+AvUneS-;g`evQYIgCmZbE+})W9Zn_H?px5nh_}~yKL#3r zAr|f&z6vj$6uA=$KNH6qKjtJ&f?7i!fem1$D*DaKPwacsHfrHo>j6FY&Dmt z_yeB$@whobMmDqV=J2g@M91vFfc|36EG) ztn>$M^r=&LNTo!q_P4xxn_$73p~tU^^m_9X)+c-owUGn`J^t@MM>FDT>QLGwx9(Gv zh}cjtasr!!JMpK7Kl55d9|g-E*PYs*o_@h^>;cEgjhS#oNLjfOg1av^n01fU-6jsy z4Yz7KnSCoywi&I^JGM+;idV`Otk>^g_wtV%zN23t5SsoILYf(!mha8aTk)??Ez%EE zD#CB1j5WX33O5xWnp7Dhbb^%FgsMtZU_sSFp9_X2W}o^fZTa}xOgN;KYlgPV9f-YaI0`$z>ua4^H|t|mpks`ybQv=?@tf0p`D)nwz<-RJmp2jI zuG7{-Y1vUy5dL1Nw-QI7UkWyTKm`G_<#muO1!4}#nI+kvuiLDiy2TepL1G2v1Gpdq zUw>3}P$`XiG(XfBC*&RqayqZ~%;VSKrf}eQB7LSt$meRCw(nVuW!yy%Ei_(PTe`;K-e6dg^(+**n2Jdzb3Sh&;X~|5JGet+NYrpsSgtCUE_nq3 zb77jNNsqp148w#UhZ#_oJ6$F+>J)maGO08Jbk932KcZDc z&|WYqq5yB;WZ=9mSaF70R#xPaQh5m1tG#cp9U}>R78e(#Wq&f82~wmH6(P{ErIx&x zVMQKaz7#Yf-^qFr*y2QBZWHp=phNLg%k_QZ!zFDh*-c86PHmA&peV*sxJhl(OU9hbZQ<1&-{fw9SH<|kK6Vf4pA3sv_ z?@P~8nI?RZuJzkG{>m~Zeab5AF=sieqZtF6kBBLrBLyc5T&Qd82 z>%+I8m#@Y-zI<1US=9$q(Wiqqhpfv)a2dSb`Z317if?NYbP#Us)MK zXou<+8wmb8@doZ<191l9*8N<+H_0Xjh*#!Th^utjmb?rc&N^tdc>D5|Pk`qhwJ<>D1}NklEq`MR#3X*{{Uoa#>3Zc%miS*vxH{_)*TgKLpz6=DlP_grH~ad! zmU_B>bg`_(RT3AMls89&5;Ht}c7C8f%||?y5~vw8x??K@D}4H&VDH`&a~U^AHyJd)H+etmF)dmIhE1lE%-VPKxJP%(ROmxv;g3n`(Eed56VMKWTL+q0HV|dXU5$Vq(7&5 zGt~t_ssq`rk9v!!5=vTu4_Zm9%A36f-TZB;fP7)&VaqbckGXfA0lxRRp|Lp%5}N(XxXw-P!C}X=6e*} z?M~{6-^l_Fi#TCF`yT-D*6X&z771_;I3x1?LD9d?t$2;aG!0abyM=L^BQXPpohK7O zrg9!&>@@*5eV5JGXi;6s#=b4(6nmHO2=GQ?D7p)>tH;(&`$I zxz19jLC=t433f}e?F0Y5@lZRJAtWH|fMKfh;*y|WCh_%Bn~WLZS!ztvJ@)U=NRlA4 z0j@bau|ccQ`Wl^^OZglM+&g`5cNLS*rLA-jISOvzRQdNCAH~W~aG)$*6DqhVYLqj* zvU>+yPp5dEk#D*_u9M6-;gTGET`hhjFkA&gx&V_zf=wnu{7W1p#}c8=U%j$Y$Gz7QxdliOVls{3`&bPNZri}!t~mel21S~Jrm zd!Mgw76C#`)arh&noADRGnXRc@Psy@h(?GSJ`03-+l1)0uF9wds_t*Mgq88LDjh_n zamDFyw)I{{3B#}fNn+c!sM4>a(iaPln!$dlMvV>Na zb!Uq2JU}hu>YgIj6f|w@Gk!<`{pQ+Ub2e?FyeJ z+3|F#{v>yo$s{T6-F-bJOIHyhCyu>CDGu1dM+Hb`ZdJnNFod_^8!g#D>xu8;8`$R) zDk);`PY27a(TRK#?VFoRoa!<|3OYrv#13IJ#>QEiY0zC3o{3Il_H8x|>$xfzRqAB) zM2Ru{h%=%6d&RLv!|4lNLn8wn&Wp7C2`>gg+&87!qOG*%k$dDP$;MzuItp;&wJ9TU0Y zAX`HIt@jfCJaNu#gYJ*j(0l?x7V)m@1;#02t-CI+snUi9qfaB2PSx~FKL90F+j_dd zae0jCA+9Ukrh6|S6JhHdXEwI)h_ZOWemJ7|;rTe*l82OpF7xiKB$HP133O_9cT*0X zo=6SnHlq{D)h>v=W`7F4SA48NHUI;kqcoNJd>|RL`WVy1sz%9tqn1^T)MP(IuB6Cg zQ5mdBw%SI7ap15OERm)>h=9l59WT1%zw>5bPE|fK3jRQrU#h8I;XxGnSDwmgx$*H0 z#yRng4?0BwFg{jDkMNiR^Afn+_5w@!ydoqBt>7H{)8m2l(tH5vD?z~V#Ix3_XSJbr zQRFC%mmQU}C0P|lZ8(o`@I!*NHBX(%d{Rp zSp@VYR`{O1RpNh&C89(5Lqgg42*va#eHCc+Aeok$eCGT-ai(9y+Yv%TIhXwNz@JAn zu|M8DjmwHSH6wki7E2sKo^p)5&pOpBY8I|yRH+G9uvIjFl1C59DJyJj9$v>p8v%1= zqiH99{nBFpT6H5gE318P6ZxYNcQ6DE6#=q9W2e+c2O02jnkW+ZN=n8jG%F0TqbEsv zV6^)}Czk;Deql0Pt~dZVJv)lzvxre3+b|StvJ-C7J|>QU-^0{$US`n3fb{07H)&Mg zs-#sfznhQ>N|sg#c8miF!cMp9S_t2ml!Hot#2g=+4#}($#vS)}6zcm_Y^au(qYI_n zt%D{!c8(leU0?7c;WIE)X`+vI_d?bEad6aC855wH$Udr6O!85coQaG#2-^7 z1U?qNNCA2D_Rbr?9JTV-14E?(Yq(% z;T-!%+e$pm7O3?pouZ>EedOt(A4{4sA=aHYL_WBC?CPRa;@jNt(z;R+7g%!hK5D1tv zm6>7S!udx`IOu|Uxh<)ibBr0&6`GX^w+6~beTRqQN-5j1maYhTjXB3zUEFSRf&%0O z^KNgY?F1_VYs&ehU)}^?TpQacT6x33{)`b>J`EjmG+ukd;Qa88ti?NUi|IAeuKp%7 zNQSY#8T1Bx?Lt-8(#^2IF8%YzFUl_sw>mTI@GG42-BE>KoA6Sh^*vma0{&9LARWWt zhS2!5o55laFXXGd;a2_W$>Br-Zi0p1&+l0)X`v;{nmJ04PfQ9{Z3S;P5Z`~(b;qtP z5kjGuU^nk+sZywtRGy^_99jJJfd8e8PAIi@s1HkpjAD&-h)A1JWAo27{mJ)-R>spN zq!XpO0|r#r-y7K}XT;&+e~f=-OA>ZXD4@{ex{nyNLoKH@3fuis+2_8k#3}d^9@TVo z-{#p`yz$VZveU^-%Y(30b3Jcb{NE}db8~ku;{bz`Ms@-_sa(e!jK}?KKS^K31k>Ed zjm|*Nt$!E!y>Ep-eW$#g)2ZU>YMr^@5HMugfu^WzVj+}ieG*N}p=&g*cpit73+~>$ zYIk{+u;1jdAjK$gA<>ateHB-L%6myK75xe>;IBRG?XrlwKX|~?Xo_ZCs`UhGI8#pM z&(aZMjR=d+Q3c%ZX}!F5dDFFKOeK)~=(W>HRqe+PKYMY7rR~q?PAePT{cVJ?YSloY zB-z1W%yEC3DL>Ri)(9*Aw#*7mHLpHw)I*f`ool6LQw8ksTCdrFY>_2gAs?N&N z@-_@@tbeGc8j7n^DnSvkNakGiHyjAG6?9Abnj=xx%&7Uw*t%!+xg|s!dQ7{BsQ%CKo|o z+m^vg{-sygu9L_SFy=&cgjNO*Eg#4xzc zzMYwkn$uOp)EnwB_@Ax@A-{RSTE5bgY)FuIf*fgqpo=4NO8gwS@ttvN4~9d*I#beb zsllJzP4^E5PSdxgV~icnn`?U`)t+IuCV?}IO-0F~B8^SM6i$f`9CJ<$gM#p9VNq1N z3uz16OHpre#}t=~l>;fwx@pbS;gcJP;)8VH@YShzTjtPV%AMLGD=DtKj#zPzHC`K8 zMAAt^P5aNS1#Ol)=S`QwZGmslh5#cEbhz$-AOT$MO`V!W?>L{O1l?vUq%txiEqhgl zB0|txk$PrW^6a#k0$*3aVEPbMes%=BoBCmUEcDI+^ko=${f9;)bky?BRD5 zg8UjCujwe>3*(=XZa9gkmPoytP&80fKR&-Y#%}^%`KcfqO~d;^1&({TWrIqFfo+uiIW}LrC8Y5<25SUQ^cWO}~@#%DU&}TaCqd>Fj~+N|5g+ z*-Lf7Th<+q;47R~SKD%C8~=6e>0LToH@EiYv&1pO>jt9mAR$lkQSiO@rp0gU990^7cU>rU?a!nSV zEi}~%KApKT34H$-w5aEoMN~M~ExE$8WvIDUSXQuQSTV=NJuC^qIw!F{E7i=pWz)cp zr~F+eW}P<{lFjuRnPAPxgXC>V(4I9aRr5*kx-?Jog!UCPUe7ylnQ`s&(mAzIde12y z04I~q0G7E+@4cw25hp)-nW6{RJUg~T4lcKRmgo;{XXyY^CJ&Y^3 za^sgs3rdZ2fUjiiqS4c91348Mxde1lhrvJ_|I=4#VfzE8C8mv}uUyti>E@>=#ASYt z4YM?~?>B6Vc+_;xJL3o)Ndbp?%On*E8sUwjqV_@cWKw?{8PP3ozEJeSZmiCb8nlOd zrsj!-t#-y0EBC@$NH+Er^%8Q7wT`D5I1bPr z{f5Bj?q$N0an|OieUV4B&hMQ`H&ljfLB63W1QcGEzDULpbQj!c)>Id0mb?bJU<1Hh zDUQW+6-2%^l5zVhaw?^vb1MAqNsTEJa3B*4oc>*{$BnP`)^3|Y2Dj88flP8mZ+|rX zAC|s4tO@V?e{^@ZNOy;%5`v`CJyKLaK)OduNW(yo4v`w&Y=nRyEgc&uU86^T_x}8T zfAEZlKX~?>d(OS*zT(`FpWLc}{O<<6N9Wv5|Ndb(-Q8B*jLkXw0_Yry<3wc(BJ)+p z#domAGc`9Go|XJ&hgEWsV(9UUV|Gcu2?{%NJlZoer)CxDJWJpbkhe6!#?f9_x+u@P zZpDY7SJZf>&<$g)-K&BEfkOGG)A(wBYH?j@Ge^4vpPTYEq1C%0|G8@B2l*!Hti6jU z?9h=R^6)_q5US_@P}y}1??1=BT{%9qh$~fv{Ll`a0HX~%;^TNlM%Ue2H&7NEicEhk zsGpKlU=JYZD>h!($|TPMbloJgAEGY|7ILs)yf(s^vqSIWIm&(>=mAIEu!kYE%GaHl z0g=t-2;X88BFJA)0cyuIb6VU+jEM@G;`*m>U@?1diNiV=U*S2mm%q>tQ<)=eTbadU zrku3JcK4E{plfp7^~>af?X#3kbnEUmb}r<{-{ljJCA}PSO-n5q9L_#T-|UPtbVBWT zif_g4HgNh!x-RCh$C;}S5g-Zrb8yx&{m|$bwrmiT!nTuGZmEWBVH^;9xZrXLz_YBE z4Xz57z6&yM;WvtpPY6rDGcmzET*Gq(|96J90Nmj{t5GBMO58$1*0jP_Tx1G}eZlCX zuYVfKj=Ry`k%RY`E7!BW#xYsSb=KFaUxrPBIsgS^Tn*0XEH!=t+cx3V*9fIREOEi| zpZ3l%T}GrRk+97+*v>r4BefRAioU3V#+24%xEwCMvOvBb#J$t32z6gFkK_`86cuH$ z9IM6$O`hkL5tj6d<6?JOjXm|s7GjI3_s##z8V7JLId`NAEGSuaS_pSpw#fyrSP|RF zG>BI`I~cwNxQBCi%u@dtEkR^4=*;h1X8uw7e&%Z5B5hW?f=n3+>!c(XeWRs4s{blf zUmYYR(80m}W-uxD2|V~Jvf85cmGtqFQ2nbQM+f^PSFMrsfz{14V4xad!YG#lnvQCz z5bmuN-%+Rl9Qz()3H#Nm@+*EM&(e#S3xRo7-g5F)z4k40`qfhm7lt08555-#)nu~l z?7hRsSwfaN+~}`w!Oy~0@Z|nBjAL!h`X`#|Xunwc?Y7o6`lyN)AE2!(R)&3}{b)zd z{n)P<8{=*A>P8{-2|QZG7;D20+fD`))S~y)W_^(2iwKTR|9j{pD2BybHe+PHrmsqZl; zTXC24$uVp`#HnF0$r^^io!b=@pg)8V;(*u5kJddpajxiu=g-YiBBXipG0}N)K#e%h zIdq(v4^x)IABDbPcK7-offoP0o4VlEhWMHnb4@7B%%{~g%?O8NrRxOFCBAFn0zC`* zRMvC{qRu=58{4H)IuUrpkJP@j$+>H=jnnzsJnVT-t?Gxxv&km*U)<1mB!V2FQvs-G zCqRxrMVHMNF4)woXag%WGT{EmdRZ0mBHL>q0I;Yc~Z_gm~P-mVXUeZ`qG)}-i1+nhK$ zmyW9_o-L(b8()7xjRdZcYh22dLJ!1(K+SjUv0g|Mx*=9XK%Lwbz>-q_FTpk|0!>*L zcjL8Ha(-zqGr*Yh(X0)DNfcxWak#hQpfn+2{jLP(H(2gIvPp9_KNcQOCxT3)bUjG*p6)r&TUC%Jl;Md$R^8tm?mfGzGad zU~7_kIp^!2`ZVDGKK zba3$9e_Vf7-r=*&gJX9J+syufZ=I#^ih|K;h#7e2!#ln+^hgP>-O$@gwKViq_Cyv# zxXK80NbIq5iAk`QCF0^ot$7{Z?`#DUSjMx;1i0YJN=wgKwFX6VJe3}q(f4b`vp;Mp z#zN=Xe8 zcV<5&{#H0Ae(o44r0aRF=F|E%96j3Z924(Cs=Mne!T7um8i{I9lMP!1>+LoR_t)&N zN3!GqIiMIxYrw|G*4We!Lfzvw$c|r1@*@QWei_PaGJt(OZIF9a>EqQ7=ese+P80So zJ^R_lnlHjH_RmaW9p23hUg>AOzLsx*DsFZj(|%>0li-8iv_(X8pZ;yiIlcJ&=JY3W zN<46b`1-vkg!AZ`$5L_72xgu&JC>_fp)n}Ishc69S-&{_>Q$gk`+vfOX(cPncMyR`)^nA&!Dwpa^-gcY1R)` zK9BBYmekSBcJH3EEnYeWh$pedj<< zaV5j2evaBY2KfWdV6BF?A*&l#_#U5(84!tR6PptOGKO#1sj}12IO6%%0lruQQ5%S`8+9!8MbOSDvc;hkc>0Z2e zA!zg>@R+6ab9W&Kv`}v=avgRY;C$raO#e0F>DsNrcKEIYNLE?TvIu{K9GGCuZ5g1Z zTE}}PFeq}tBH=IK6n1^SozJ^6&wvx;{cm%k2K}RKOl*70<{6F<#zPWx(%J*y_|l<& z-F7vs<)~w`<_^L>d>GFjetuDESn+J3Yq9g?B76^kVpp|QF%Zu(cZJnQSQ1zACgf%g zH9CT#SrIeDFdh-bK2Kak2{9~|0YpYa4m$P#IB#WcSnP5%*TvzQvLK22nijbij=tev zwJ{&)j|_!pJdI-O044TU(C;0u-~D7lCMqSRP2*`Hjd48G(+jIGMR77UXD;;e6~532 zNWU;l=Rozew5%%;zzhhyRRJFJdT#4IqnKB9oZ5gaz@0&?Z9r68;T6ng=g&RaLgxbi zH#RWKVj+gBqMtC((l~wc!_r=c%#}#Q93j12Rgu5wj(zNWTNXb5)$c=*|i$vNlALPC@Jy=*!AJE z{_P7)WTX0e@88c?q%p}QMAMkXoB*h!tbybWBBbX^M)6}ZwrwRIDjG{xbPdnF=c0U? z^{Ah9a+OPIU?hp!LO%buQ924G{5Ae#3XpOM|WNj4!?QJ@bWst*b62$*tP5WqqnP8(FOIubGQ=&O}7YrGtH%5&i50uY^a zk(%%W3ySpsJOe_{9u(tc8yhTm-&#}22ZxMRNloPgZnXOs;O~ndXDKa_EcmO|2#HvAn3TK>ip%E zazQX*V|xNRq*24aF2ff$exV+g>uBJ~v}3vU`*N2B?`%r3ZQm1~6JbCeGQ?0!Jah z&a&GU2ou=5Hg*ivKo0#23adO{?5=Pyza^h zx;pMxmPHS{c=l9wUmR0hL>xfp1qdpm?v-u=7EFpIEhy75zT~*cALz^kcaJhFSg-`SU51Wj^bJRD#hw$~tPL4lA zxXIen=u%Sybk+frr$TP$c;6;5oQ<3V9-nn7i-ghY)vdKfz^_vKh(o_>749~*Np`Sf zv~|GYf+8ZJ_e&2Ca}>PKLglXxZe=;HUe-JcXRf`{0))2A({^l?)E%9{&9B3#gGl%VV$1Jg54fj2h*zhHNTZ~c(V`hA3UO=S zuPHh~p$x&-wZO0t^OhXe3Cl#d4X^j%2!^mg$j8{=z5CTSzxFtAKw*dpbAU&<3mIn( zN-Yb+=4KrWH`Hr)$V!e{BUgGx*%>4^)zdBZGRf*E$hUP6WsHZA_dBwWZ6vzHsX#0^ zA%0645h57v+R{=k9v_TN9)itz!$NuY9y3W`T7Tt1?i}kVuC^QXlG1Y}y?nVypIle5 z(E-YjZ-&U#0OM`LN;YacMcN8j2fcuULBK&Dtdj#p-|x=t8?$ZeY(|>br)S^%jmbpz zrF}Zwa{)W=x$WMwsFy!Eq}g%I7d24WJ6|zng@nF@mF{nDZJpYgt#;^An>@AtGbjBg z4Pe#n0a^PEZ<>^R=OfFy(y6=_c&6(Z%YzPcT+zuGomq!1X7K8q!*e$4E-n%6`@PXe zfNN5lTi@iz*YA3-3wEl+aRQ?rUY|3qXn1~Y=<$XdF$(2f9R(ftjl4CE z#72*#fU;j+5<=#-u3AZ-n>(Y*{I7tI{588!Jia|(FmhcZ`NOSqi4upb-8GM{12}YN zq_JdFNo8xvJz!UCJqw`2HJlM5w6Odp3BlT+19?q4HmT)La})C{>Y8=^MEoD_uSg~5 z=Ua>RV!`j1BuKh86rosZs6&&M%g(SZp4p#r%(j4tfC;@}gO~Y51W;KwF_Ss-% zU{E|We^`&oBD~=N?)nTQae91u;J(yuz`4Dma#u+iD*U_E~aDKe6A>xM`hR$j_X2E&cFdBT!i9O`vOe>sXWIg1i3G+Z*K+Umpl;@Df1vV z{IA)u7d7pXx#$`q`B#;d==*31LFT*E)R5{WMHnW?(9rPO#+E<2Tzu!5mx+quObWp2 zN10QX3Z}<}{ds}v)CL$R&~;~ICHF{{S}2Ht+QiJ(Dvq8p!tH)PX5~Hkp$G<7{O8_rEeTwg-)}Jgt0#U*GzpFd3t%!p2EKWPa;z}0psr9u3?@c z7%x531ag%KDHf=^Pb@7j05yY8-8r#Ckz zDz`s!#9_#uoa=Jtb%gg{%cO>_qYa@w3Abbgh^fC7cNYrAdt?tpWNeDP&C6sc&Ye|a zm>xC)$-EGo-`j$iSzz?1%)QsQ<-me8Ur`A9>J1s?%IYkTM|-;Ti@AAGE0mC$q7iif zA|r#^(b?GxKozyRLR#oFI;FzkotBfc`1lG?D;IOcX0&Mo+#@jov!k2BQ-F?Vi72jo z>sOV=vAR1cS$COqfMCsw9~XR+Zp*FfRE4T(ch|dfhZc6Hi3GB5@~pRm!r>|C5ivxt z$H2$Pm$6)TTI}p?cd2%dgK!63Mv~Iiws<9kTMa!c-Y0A-o#@@scpXvQnDPrv;wrlD zoL?JyF{Ai6bQT@sYwPpM!<0i<>aRA>`b zQ+$rz+1^f=PCf3I$9h-QMV)T*oQx{z6s`l)?$A{xCpTE|!G)=8c%;De?^-wBL=+GM zmdvU=*qbQ$TS_c8-5rzC>mx_i4nadg!3J;U3!n&=hxK!_towUy={qg1TTAJCbM%Vo znLOlqZ_!fHgBmBm8%O;^%x$v)-IBcfOEtInzXT&3{+S=x0@f&eaf6B{`lO`kEF<;o zv9KVs4;DG|FZud?maho9tuNY-LKq#sq90eP93(P#oX|6W&5Z;LJ3V7IhXa%XRk z$Z3*J&NuHO9UJi@GBEW00u4!@fnUI|jmQJ{Z--@J0@wvM?9Z*XBJ2;5V8#o}8qr#C ze?9uhTT+Ga&fIR!1C1<%uc2(vZTgR&0eZeZo1LJlcrQD<3kDcvXjuxm)lUkyn-?y) z1CA>8eW%Hu@9wXNOS=P4WjBwgKRs1f9 z-*tNX@7;f0NnOgtp&*<8Y)|z;&flwf{t6|5a%K6ur$I`mr`uNB$|=J(=?b?&%HvH_ z2CqV<*_D4k=o@<7=o9Pt`S>{ZPfuUuG(xE*|MBrkVm9iU^m8c?oj8B|Yun4=NUb!8 zt{snm(?D6zW(9OI^)>vO2xH&gGYmLBc3^e}C|TSAG^l3^j`>Fl3>iYxz3^c&sX2a3 zt;fo>Cobg&NaGj;>>Lvp?j`%iBk~xtyv^fK*g)U9H~%}cpts!TMdDsBzfybWVb46F zwnPEb+KuTl?KY2fm1c~*jEsRuVdbDp!OjD6Ze0t_^unSVH}A+05gtu+ffOrZ`6s(? zU$UhBV=DK~Y3*lC`YWPzn^&iq2Dtl#aThdN@XQxYtM67u!tQhTE}m4!tYKNLD%MYR z`gW%Q%85lVOe?(bl*qNE$|rLADpY0uNNGwPV${Y0wooi`Z#Osypaz=wwJ3Zb@)w<$ zwsKS2@v!>do`~m1Q#^*J?|Cz0Stkba{ak-4(<0M$E_qjGQo;NJPkuic)h;3EZ<5J= zUd>2&ewpSJyouaIw;I*gP21Lo3xAtvGZi0=eBgB{_fD-e4N#+pVDB^izH{e5H`BD= zN^b1!`f{kEDi%F2E931rVg1sFrs7vbkC%Wa<9sRlV-x)eGH zMXz;QR;AElBmR)%bqA|XA2J-UJ9}#z_I7-25pqMyN`!Ac5`qn&qozhSyj2m(ls3gh zL`7lS(CW@B2AxbLf`ZK^7!zgj*C-Y5POx=7x`%!pDZt8GE#sRM zVLt-C+HxA}kgK7A7fdMM(kE=u|5+|Fty`vkH}bIC^wC!#M-XxLj&=z}BIEvp*JjOhcak7b|Y3OnL zHu- zGjPT`VC2Cke0o;b+PJo!E8pdQL<7}#&m5ScC{3H2J3uVqvDcl!rHs%O>G6Hpfji@^ z|K?@M2e6{46{&6*wtq3}uzo&*{Z?+Z(~qpItgJ2IjJp;Hwc!K3U!9sN(Rk*ao|b-1 z$cX_vIXSuW>JZjVASH+!aEFp`1C;IH6RN5)!~urrMj|3b`yM7qo&XT6R{K%4J3$E& zNRGR~Le^c4T#C;?1g@-UnSa)ekRs5Q6H$_vdsS7VVt4%L*?QxET7IkQngASKH`1OT zTmWU6&}c1!KvH>y2$$exl!N*ts`h=fa>)f3ZBE@#u6uWxH!H?jLS0WKcmn*udr}Rj zBIwOz4U-q$2P&)Q1n=;%#$@iV)C=bBt!ZavIt_E?<--@#z`TZo#<=U3r_(#ij+;|a zmg?TLA+-V;?ixwkn9d_8an#X=emh;*R#nowC9h00PJDFF)y3BMo|`q)M}F{Mq#e2m zRP}87sCY#cQ@%jT^49kB-v#jp{n-t0p!M-ecS2vW{lC1ipVC9!3lJIXq@Gs`?aIv8 zbhV#Wu5qIsVu6HMNS1-k6?IHi4{=qG4-cy9e3I-aQB`D)%Lj5REMfr_-k|a#Q_N72 z(UU6rVD++z(_a$-QE&s!g6=Wx!UQc=#4oRn)*pq+jNdV{O`g!u0SA62(7iD-8t}WF z{=i`j;Nh;YE&s)MDV9kF)6&sVv$A5=5Y5z@@kw8FvhGNHbB@vK=JH1k)8n3$07lba zKZU*|zdL=+tb5^KHhC+T`P`HSo8yKNdueg@*Zuq@^-igSSn!UqZof?6qYD8d_kBa< zLekdO1p%x;;&ont&7G16C^9YiYEy7_Q`pf6B*^1ohg=UBU<0N?p4=)CM9D1tOi4F~5}c3?=?22dGX#j%paBg?Zrc znvp|&GQc~hQCg%VD^qS%tI7%KCOmqxWH{H#jdB|~R&6OUTf6PFG)=vCs@r|_dzy?& z(fRR~SLN&~K1N`9s9DRCRql?P(_R=l?`b*-g zsJKiaLP8N<3^N}*4;`zg5A@P_<)n6+nmR9abOeL%G0B;jM#FUgO8%l)4uikR0l~W3 zqmX25MEuobyeU%|j+))bj_13}etiWjogBpO4b}BE`cfV23U9(Lqx%aqM`@`-GQ!o$ zrt2_4e;YWiCj~sa1nSiOgF*yg%V-qD->@e%K`G1{L+~!Vq_zh-0YFT^ueUp|o5SJp z03F%HU*p>aRFu07iak^0SM*fVd0qY2Zk2i2u=1tI1GX?TPfMTdlH2m3qS2i|?+|1B zX$K=;HkH>5EgjphlBbN_0b%*)%+d446rf$P9i^R~KbgKWo0xjLo>rKDQ9ip}KYHfQ zvuUi88+QBVA>sf-J{d(;oYjIT#8-^$pYYZ%Zhyps-;`jf%t<=U(#bZe8a5exC&P2i zzt``Qd{&^^#t^$7V8ghoWx}fay$r#d^)rC=^z2(G*m|O#EArS~;cxE5`mNzuLr-zD zMs)2e%_1DL)n<5Y(kSn~o1&?3O7@cRQQx4z8yPv`*xX#vYG)g`RI?n6y5-5lLU!L? ztsk-_#x?5HkBCu|vhyJ5Lm9{4IM*v5Qj(Se!`-)}uvvs(9Mo$sK*ku>__iqmHl&LQ zdY$PbCf3xapPBPD@kb*YfE1^m^QA_G5aJ`NB6{s$QU{kty$M_{t$(2+65C--MC0c_9jar%%a zk-lGFz6KuO3J=XK(7>w0N4KCl6}xwexh-fESp^d*afv_U@TT&h$_$g`8^a)mPJgWt zk0MppdoIfGwj#ao+G@N7ml^J+d*haf#B%j{EHrMmr=N2Hkrv=G!qD05?S5oVTE{ia zzzq+%gp1IGRI23E%3`lj{N7O-0EgV|Ixn=%Snt+XV&~j=gneAo@>Im+V)sDq5TJ&Z zk9k*zZXX{&99lFz8_Pr!=?FoE7?k)3uBj1b_sUY1at!K5X!#vqZ(fg{cTe2r+u)j$ znx)%X-nW7_=5{UCmaR--t}1mMl<)S4OhOb0`|hhU_-gU+LtQjS!$S7#7BrRL?#4#< zsqN_Oa}O`is2dU142g{vb#zx&Yxv#j6Zb=TMRjuQB7`*RdHu7%k*)2_-DCgC#wRf0 z4Lv~q8d|VYLBr!WXUJw%GON1qwW!A{=#>)sO2raiVk+{twYixj)^HADTBlBIb*%;9 zhFs%l*pBo`neFEj4-G3Ed=ZJzwijDT7wMC@pzI>GiP0Z4MmbwYhHW{?;*@iU-tzY) z!&UzMTJEBgO=8d1DkA7r;m$^T5Q3wsi-%-GuL(4&iTlKfiJoxp5PF&z?Nx(BL;^yK zvoC{n65&rNLebj~Ulg3#eAnybORP^4(DShlfUs=+c=7ea!A1L#B32*| z@Xc40W!V~2)W2QiA-kSzDZN$IIbIQ0a3NARsDHsnpPhq)>TIhH-LK&WlV_Ee*GFR8 zZWiX-bksbgqy}G1)qkf>TJaSM6OqA@Ta%}MbU9^V`<$7c&4q!&>8~$mKg0+{ zir0{^;`QOrWK{syKT2DI4D635Ud72#`hmfLtY4IF%)-^!U9=Xstf;DYBzwVyn)4;w znu}I_Wk|;8>RNt~sr%ZmI@Nius2PXpfOEMWv7Mfufbw~9-w~bs)Uti5jNRbCF`AGX zzP~!HOtLCatGC(XJm*#W80Dk80e47kuZO^$_Nl+qY-HGp+Bt|1Y?3lt-O=~(+gax$ zjmuPSHf15E&lgmLR--Z6C!q*yH??3DjryeYxdes)OM>L}L%^!)fED>F3yddRbu|BQt>sjAm1X z00(KX+1kS)4l)$ICxGQ^?rMW*QD_USIM7$&gy$OR}El~2wQI27&U$CPk&NDaBuX@3A> ziaPnF8%!fu$z$ILbWMiTGG)14^bR=57eVcnBkU#CTi|q!bak=iTi%#R4q6II&?s1G z#x1%V$}}c;y&E56{f0o&T38sI1+cn$kzQaNX~I6`WvmEQN54A&0veEcI6Z%g^8K(( zdlGcn)er=FKl>)RXH}q^H1n0^Vs)U<6<~fgu!<40O%G|w0NPcUO>@LZ%e(+k4gPtN zGO6{Ni#Hxa2Wcp@S#}_yn6>A{A`_BiP@eNzufn*o4>PM(HpqAZtD4YOMfjV^Sow6w zj2mB&0Io*`Ysg3@_slish5(^wxv8waKMVGIA3*u4p!Pa z+)dPRAyMD;%J{`|rSSIAc~yq3>x4Zh#2vk7qZxvZi+^NI&0k#K1Lg+e_GRY=HYd1b zKHAsrGA=~_v&F-L7@h~n4$<0^q1PLf%ZtVwRPdNP#z%`(v9SwyRYgT&v&aTF77;6W z%zYUoJi7mu~J5m%Lguh>(KN14Q$H(6S#{W?jQl+P@AJGwv zvS8p7$H7{xStlh{;t$t;EgUf8pVjnC&Pk&$5l)_I_YaS`*oX#*SZfTbrCehBENy$_ zqw2oZjxTzlKV}Q%S6{XdVlQd~YGVxxfogxGK$Yh?fnj_zRY#wWJ^akcT(QOg^RdV7 zG>>HO{?LR9V2Or9e%Kl80dz!5&v<9FzzM3um_&*l*m@DxK7q~zjfZmH2AHCXS*V8( z=!fr!ZP$HXi!^pf>1UMDJ{JaLPyRS^ZulYo4&%?pCyyJ(;K%-n*?df0#l8$t7!^lj zs4PNki8Ov&!oXCuMO%w>HFCYNFwi+>j9H91>9Tf%)XN`5iEA3NeTX2l{0qg>;hPa; zkf^@ZrB%EdE*v!@lbYs5Rd&S&g`7HjK8j>2gEKNb05dlwjqTSSBx5j-gxB37=nhdrNkO+==<`eRB2Ch0(V9S1&Te=zZH7!Lwta z-Q64iz=_c&*u`>*e58*4gAb z$GM$AqdB1-VfG7ifKJ^Po7j{W<;v8Phg*Wy)_kDKrrZb6b8~Ocy0a=`1l<>eNT*qF zb&>Nkm6D18v%g29Y$2<&*wFJl64l5aeDPE#BZ92TDkzNaR+_jsFg|~oh&2SoYP)wF zGN-imHABJhvECn%nQ}e0wqO(v-zj73pKD`iy4AVa&#mSD3HO$;KJnQugTLBKC;eGe z;&8{;Z(zSY?gjCw(tiekw*@dI2)fzP1(IH_2PhDA$=3o1cLLzhY263@ie)soI?Adw>@g6UlzsAkJF6w#QaQ9u<0*J;1B0 z1#v*>>FKE{Dd=gT8tUrnpk%g(KLrFt+w=?moPA4T%!(IT`00frXlOT#2fhA;WUSj* zweLnGidtm%C2=ng`h!9UI)9jz7nymtOJZwdLX0r2#ge{Lw-npGI+udv%Y(y0C;Llm z^utZ)YB4&|17->;e~z?1y7Y;yq+9`Jt_~xw;HI}X@9|Bn?>$Qh0rSs7_X%M}8&zNL zX<}uus!3rgl~NaN)1<(_kAETs6imfH1W^-2w)~2<#ITL&!)+W<9H*}Ym*-!($IM>W z5?j&0EYEA9{J- z2;kZTT=qZPRz@GKV&!ZOS9fnGnIvg}e&!MG9YR=<_lJYsg05fBtyZnl&=;2y>&~2l z_a7L)O-|*+EjGH19s+s-_N7hSK1I0=Kg-AQO&rnqGbO;=eMMd|PGehp!5Z)%$dFdd zW=7Ndhl)AyoI4UJWJ4+B7)e83o(KWi1tfdmMk3dXV?S(!Gk5PHs?sjXLg9JKnkj!} zF8`1WWFzr{?k(Ut&T& zwYjbqW#3dYN+RR6iYpJyWs}JTok06!AU1F*n&;qAg?!gAx^pl4@kDc$JKF{u#K+s; zIb|or7Ox)Co9?UwDjB+z%YCo{0(9U4HxHqgCqoNejs!`lVSamQK{F5<9lR4)K@#C2 zlii{B<1|D}L$@LuWa`ZXwtE^hXn~r$K7^|O4GSnbk@o_lYAruTEe=C4&&1kUttyFE zUdz9h6jWK74SL03;uBdlFkDRnFuWGG2AAv2oEe2!83SfEy)BweEq3HXXECfQ@L4ym zw^~DfKhB#?^jWD+6hBv;prqElH7{DFXFAVsCsL(7jO@K5S%AC)MCkU!wDF*JvfeiA zP7)V6ksl?ffQ5rYU5sSqRL|#7_Y3<>kh+TiV(?MS}ecnhE2avggurSn7g@g5Hq(Co7RE(F|r%L z0qS%VW0RfoTM8RVz@KBCWGBdVj?z-LK4t;u16|&Iyh}~vWCYAL2GK9$|H}Kj0u!_6 zhA%_ZX4%vadcCo9j4N{W^$;BYYdzqzz<`1}{GQ6~cSChua4<6H>-yCnT~hcC71w7u zn+Vy<>)B^S1Lz~)y7x}bvMszyB-VqRnd>aVk79-Oav0Iy%fLh6?uo2LRpmIA>e5ApNTp7j9@zuOI%bbAjmal^=t;06*2d^MU^t;94N{+xiJGoicF`Vk*cON70@ z_qF>XgscikPbeb>0P>)ucio~3@Z6)0ByoXM?ull;09CNREuw3c-o6WWtMQh7_}mgF z;{Dj`xsK!1M#J3WRhqvUudV(qed%qEbUhZSQQi{SLmiLljfCk6pfo7I?Lyxbqe6wJ~gy+A8!tw;vp= zL_&s@l`@ZUFG-R>mEQ`w*vw1_4{LFICFv7PIB^`oPLZnq(qc*oW@8#?_pvVmtir{XAsL2&=oZ?^%7yFu~0!Mv1pnbQRyH5P&1^|RKKonJF@ zIFOEYQ2UvbxcA+LRnBUvpngHPts2JA2zDFXm(EF?!MFQ$w|@d+NQp`j{ZW4 zkBVFFr2iCQ85pc`O9sOL;QXJwHB&T7N`mGr$S^vnF7SBAOI(r2-qqPYGfa`@7-(~$Tl!E!?--nt_W(DeI%Gcg?{>dUVC!@ zllCX5{4Ai1DoDZ3zG2UW(~8irvhw`vYkkFG_wU3*KZF2=z7{(!CM+9egtm68)FFzy z&G-{vaGj?P-^-ezwS#U?+k9MZprd)`YKh? z>EC3Lv_ZvH^%6^r3q`kT+SO9CvB|p=pA$BxssDQ$=9StDBdL4MN)gn;+*TKLyX*5g zKv}g%stcJ?Q~YxsNSTt&*C#>=ExZe}q&S_;0U}~fDeuS9>|vSsv)flb{qJLanb(?1 z%wX=NyW0!h)l{^#W^s1fm-dsv&cx}C_Kah}71M0>2xy1OOt~*R-9Fo%;E<7_tI#Wm2{RLv(}xd|GlO<> z`^do=wMGv*i$FHzu*3o-I-!oFm&>VHI|^g0UNSchjV{c1NRNA|PXH$1{G2o+Cx%&w zi0C1Jwt()C0fgqn>>rvKAQrBJSLpV)R{6z(+vSv*B{!bske7 z8PYQ>jFrTRP?DrA@Gpb!>*EX0Kp~7^3862}pMe07{OgJ1gn>GUee5K+uz7i=GPddL zzQM0rekAYc5*nV3t?<_+W6yl@Do3x6d$r(0rT90VYC1~1i@|B9D2qN(Meg;lDOv?p zwPwZh&l#LMgcZq*^9NB7GF{Uy`1xn}$MckpXy;`5nf z+fG-&+v)|7*p|(XyffWH9u%)XtHa^nCwF4tQRMjGE%Gd7Y;wnq>p3d!rTb`hT>!V4 zoXx1Jw#y!vSo;cJCxjj}LA|PSRA!JT+!pisUw@m|RjtL!D)bRh|Ef|`@l9g27ncXV zVn2nI38|1O1oE^}kg3{=c8p!8Z0eMC{dK_GZ0FcbURu0NU5x_HfAN#K9F}ea+TWf9 zyL+Mrc@dTo_%U!K$2^7aKgKjYIKup>0~_+;(sUImz}4kJ-+@sM?zC~qHiJE za>}7zIVK11DLa?-n+Iklrr4TgmZ^5Q*`1OtspN%BaewLIj>0U4*WB$dK$iqD2Q@&N z-%boo`&^^%9v3WV0hh8a`O)`9n?oi`IUvml`jTy9^_xI-qIO9OUeb%zfjmR&EW7w7 zd!(i5C(DR>?r&TqA2x_2-Ip@J8uL;~E=;gL``XzweTzXnO4k!SsLI9Ug^i|gtemH4 zb?ljr^BKX^jp3l=0^g(xfp-=dYymK26a7XD)||%x$#-4PS(b?~qKWe<1i8Y%neLY! zT4m{}#)YM}Rwuq8cevC$#U;{vxi6@L^*szIUuCNV;8YVnrOWlEmlSP-0M!-Tk2CEf{T^DyAS-04)jLg ze#$LNAe5ma2OLO*|8ZsX>FIucmK!HYPdQ-jk7NgjK)7^oWnbPl3`dlWXPl5I2Y%_7IY&xivvrJ&cgDC#9X!_Xfo3lr2>a`J zZv}{JDA5+#z~W^qQ^6J5US#h0Xn}*hgIz<~ees?ct(!*e%iB2L z$(#l(x%AeO8KWRlgsyPCStEx|28Q$pdnn)7}Sp*YOzLodatupB(lb~t_4%oyeNU1vt^;~V0XDLB z5gW-Ax>AzV{k1zB_nq+z?TzCAt9eD(Xmu9SSOcxiowd=6gscVc>ymE7$YCe+fT{l+ z3lw^96uwZ9&?#Udrhi=#hA8}fEQd$;pU~pO3V#qT8EEn0-Hv&+9kR1B3a*alKLi$rj`*a{{hmcc>v&L^>qEl+uDOJ!si7FNo=b49k_Up4H?8|~ zh*~{3A8`e{llrI6M5P0qdzYrz^p3(mCu#SU37BR)vGP74(gK35VSSJwP%Q*jlBz8bebozr|3g6`x<2(ZXy+*!mjvL z8tR_zOwwS#wSGySaB!FFgO*jCHg^1{E(gZ|;~Jq&w_p=C_4+zbXpW_rr!~Gkl_3Qa zT`U(d6zVghAs?wbnX`xg?Y&^R>~rU7Y47|F7B3l;-QOO1vqOAi^azqY(=oQ_%?X2> zE6hIqhZBMXrqhW!MF9{&i~HwGfT47@-i(INe5+XN7I;Rhv;)&4HfNP3|6=DU*s$C>}_Le z&gXf5G4E&B&;`%Jp>t(Fu)&huW8hLH_oPzuwlgvU#}7a3TfX0R6KihvvAtA+{&XiPH5$-<^p62@pV)=%L(z zd$9jTtJw0SN*JDnzQ;?j`4N4`N-~x|u?9q0<3C*X3j3_rEYL&`cwv4~R@yheGdWen zH~I1>cDYSpLaNUVr5@dC-+C7lWmwmG(clDLCeYZ2e;J1i%ioD@i2MUzd z$F;#UlAwAHezv42L^h`E&P6l95@F)TE;4#qb+opB%li}iE+l4q#zQ?Z$A zF>5PyanyIKOBvWd06J3Xdm96b= zMHIqLJ5SWLS6V0AImwA=usJ77Gd$b7C0p;D-M$lTr{x;%gRRAVJ&9E@#~cwnbYj@7C)Y|` z@{tS{)4b9>7yPZf+qxC|faC&&&GZsB zg}^@1-~#Yds)&2>x+e$2J_Gq};V{*CdJCt|iceJPJIIB34k(KWW~jXjni8_P-x!iL z2&hlv*d%q!tYcn^rpOP`d@4NtBJkalwoMLT!V=7Na8JXnT(ysOD*swvg#R5%K3j!0 z-S7&t6TC=$g`c3Orshc96@rCeql^p5e?Zk->6l-il^znqT6X#XT?5?YOqXiC_Ce$~ zB$US((tiSfjusp@4i_F}E}^a57SPNvwljky*HbJn(SG>s1RXX|u(g zX)#-M)lpj*pUrOZo(fsAFWun{RKH7H*nCt!XwYZyP|m_p{d90TLGEx#qk87|xdBC! z>e57BUym2z_R^t$WOqmM4}AyKoV{pJ;yQPy*oOdy&>@LCy(Mx&to_$bxm^kCesmVP z8S7y2`I^OTbjAielhl9&0AAK2ReAaqQE-sYQ#7uEZhwm&2UFPHwd8_@#9@O4;=E-P z*@5R)pCPDpv?`Nokr;;7E8CadQZPhxJL0!oK8yQ5qP{XL>M!bc=#WMbkPxIhq*EFx z1?dgFp8t)PNr!`Iz9*agK=W^FS;3Fu{BrS{ zFwqOu5vTN>a0XBnlmBx`D8eyD^U>ry6aVwsMpa zsWk0t1f0OLfr$hOk{;0QhX7(D{y3S)3ZPEr{{HK^<`CAQjk$V$;7RJlQ&|sT2s0^p zLE$2It&qL~aI5ni?geZnnuWu;vef;%aNY5p{Xr)H{&bz)T+*eXNW0eH@}PlmN`lxy za?gxO)iJ##@eJcR!+d#W=m1&hCCA&-?X!<=8#kBQqfZpXfsYoQY7OZ}XdE)?7ksBZ zKQC!1J5k4Z*u;et!+FxKvcJS@zn#sS3W=?BJ(CSYAfbBAy>)I74)Pd&U%4zBG8ZGe zQzS{)T$RHvfGf3=nul36ZT;*?M&|{^3NjR=?e8PWUV)JDkD*KA>V89n>E&OuW5fu! z_UN8JR##gKLyGOA{*0%5b9vysP+J&UZGi@G4xt{)GnwlcG{E@COtX3N-;fvC0-NT36+CIPDS01z2 zUr3_#Mbu!bi^gU)kX;>#t>&^f`n-ZVE?K`Rqh_a(garL)m9}?N|9(iQ2H*k)6#1L9 z;T7zhE+>P$Z7vs#1LMm6eV={W3{b(vWaN+%*+p79{Kf9`N)C3Yb(udP6Fh8*&wdU6 zS2DKuKj((+2;e~;C%jeoo|P3lJgg#dtEcNvfAEBhgWbD99(`)?92#}DjkoSFo`N)I zW-WwK*;Fnm;-1a~0+s|SudBb_i9g~iNUBtBcm^jObGJUfskOTml~nDnv^gOuf1kha z_t>NJ;3w^DrC5e)!t#?}k=4}Bb`V@3)D7@_T>QlD_V&)OUj&_$Vw+(5zB1TxJAz}F zP54h)*zDnY;LaIY-=u)bexZ_IT!}vWy`j~`X0Z)I2(H|?E8zW)>|? zNKMUq`>jNI)_bms3B7L=u?q5t5{urfnAhzjupcF8)yL{&A`4PC3r4Wap=SsB{^g+* zbE90FIQ2^z>sH`jtWgeJ*&`aRX+YJdUHU97gb20v;Y!Za$v!MZGW;80h}WKQTWNi> z+&gXujLi&WM!Ohq*zX_Mk@+}RSGxNE{`6OblUCta*DBlKlg4h1?Uf)Ko3g`|pojk;Hb~ z!u23z%~7l5JxC%6?~@t1ufZE(+UR&U4z*Mn@zw9S%rP2oLrzig+QJK2drwZBLqbEp zt`f!UzD3J=MM&5Q5Meg8;wP$fE%!sgWPX{rM$uN?0=Ydq96K#=65?4j^Rr%Ol|`c9 z-Fhst76ELB;>O&|4S$@D`@hJIBq-dpjf?i4F^04#Cp#$+vdaFX3LB z(c-#KiA2D|TKuf6JmiIj)Cef8D;IWx3vL-yE4Ug+Z_@fyWLVwKzn7?cDc|l=?F{xYez2vK{QMnZrimCpQnK z@MEw5B$|5s_pfPa#sa*@xgs`8XV$hWk^nY*`8at88j6+3$pNg+oQzbj5o;dN$4>P6 zB;GNroY5zN_J35>?dZF}1R5M|&SRH~%uy>4KC|(66Z_|@2^4mu#uZRn93Iz;PM-PO zw0Gu6kefo`r_(D@v7K}BXdOC^?cbB`4%c_9o*Yz9Rln~G7pL6c@{N6HOv;}*QC3)Q z-%=&}XV33rAi?nWQOxG8&!|UmX9gzT>Zj7~30KoV%8qH&6O||_o2qzmQVjFSEg_Yf z$j+DOpZtg3xLH=(^RHlW;Ckfu==#>?w4xLl_}|{^(dNmJ#D*AP`#34T&zbTCc)=^z zML&O@(0`P*Cz9v%=!JC)uFeu>PWb)A?W-GoXRWe85VtE(#l4Uk@ugmAr>UQ&L!A-S z(vQ@t&JkE5dv_h#k}Eitv<9V3LrU{N^UP_&S0#CbYX|Vp)UZG3JB=Iz(Lc#6GZ2$uO}`9#-BK50|*W`QW%* zN*MSh|6)2ZQ^>2D>?>16r{H5Ea>FK_HoEbGs0vCsW?OBL($48J?eQteYKtl=c<|2A z>Sp<+Gy3ZXb=Ou+MqlOCFMlZ8{?s)2RbR$t&h4$nG$za7R0~$R_fbFPoLXHAh4j*Q zb1_qzCVa(dz4k)D0>NJqu)R^RdoX~oCY$rIgAL|wfyPjm0SwR0!y8oX996)qS{~Ip3i=!=gJzdypMT}RnW5M0= zR-r*AK4tPW{=+%SEqGbn0P!p)!zAK3;3hpsfd;F;>r%&YZgi|TUTfL2gSOzt9bj53 z!~Z)N_tW2TpDn(>?gSY!CrdQs0MCF7hjOR7@W6hBrBI)Ox2_M|bsm{r%Ovc6*VT#^MrVB_D zu-w>D*8VN0f56-l?>y_&E&U>w2<4`_qYvL;L>~yec(EA<*76KEdc=r#{%?}h|DJGirg;ajTWCV~ zy?asVsiMDB0<3Y#OE0c;v8FDeqvPj%P?^*TsWnXG*;AU^|GMq^DeArvJMtP`Nc$@NnRp{dH=d})^KK|Ro>ra59=>Sm}0~=N0CBU7$9?K8F*NCK&p0t{Z_4qLy6fFr1!Ys@*h;q!=|<|Jx8- zOVDxoOL%#&a9($zX}!{w$%973fu_R*6TnOjf<{kh_4#{X&7Hc%=TTLi zd?_e8m_Zc;$(x0F+EFSnu|;L^_IldARfE*a!byiNSjsh4oR<$(=@m_A-K=Ta!a?}A z0T1@M1h}y$D85&UQXuv`{8Q!Z&gOfG4}JDE%3frA&lFX2x14u&^U29dk^=7FgaUAhVc;73_PlA(!XzzRBjx8=bcWr zcaVV6-ln0OmoK)%)Sk00b_CNT!(&C4*;?^Zq@xJ|FOMi_6xgW7lVV>l_V0L2vtq3>Vh0YXe@n&fWJRgO={(#H$-O?aQCUii$s^^V z9C4#b)^g;V4$k{aa)`UkZS6{?mNW2)KH%3~LeL7vi=3(3iKasBdlO{UKm#BaE$UrvrKfQ_B2%GIsi6;f z{^QeLg6)Sb@?s<+SBn+eG>V_?bHnSe{yy8-QdnUp<+gQKC%7MpOsau<&;d1bBU|uW zuSm?!AtQYA(3W3ZX;9gmK$#o8CEt^qCM1=wr5AWV{MSv#Ozdc~negW?tnzBpg9Q(M z=wCQ$zd%%%!5CyjFZp0+V!b>aL3t~~{I8XNXjG2#kSkwVozAw63!|K8P2L84uJQb7 zS|r07(@+c_=OjO;oN~T7U_gWP$iP0|L$rz|qLp((J9UyDNj?C?1?8k1-0ieg=&1Qb zHlQ#7g8mr0!~2zQxB;Vcf#)S#1Eth;_KQuBKlAU784sszL`2y@sEyR?MRoB^ugHR-Xm2>~WAbso zui$Hb)QFH4QiV>q1C#TFAMO-JR!y#do<^mV8#l+Z_ToII#3vj~LFi?eP|F`F)g5M_ z<4;H@oD3k%kLg$No_q2@e{USVOX=uO1|)i`jBRDq{!?}%CQ4Db^*GJ;L~a0it?G*b zH}5>xb8s;XF3cOg?t~u)$q#cD?fT?2=&asfJgWqQo~Xlecsnj= zQph@{4ihWwokS`OJM!RMxNSu1I<04lOVzD7KB1x-p*<+u-C}lnmH^=xCDKlt6B^&9~In{9frYxW{ zG$w^0#`%4tzm>znvgIniKOPG8V3~7Bo0^b^5pL1$@Xm(6t;T`x4dnv=OWQZPz8(Z3 zqc1{d%1n$YM#i8rjad^&7lu3d#ep)d4BuB?YkstQ)22|AJI1o(=ksj%(T(uFAkJm3 zp5@w5e8M9G;}~2NUwr^G=y~h_I$IN_c)TeOU<%3_dezCyM{|+UrtU5149q{d?lRig zzEpMMb@Be_#V~dM5t@GP`Q{M^g*!U17I}C%JG(#qMe2h=Lc z_zED6PE!QlUTpTof6}-E6-PO)OW6{JSExGU*^{|7^als);QGu)e#k<*(T`d!?Z4yw z!)2aUJZoO969?W_iF*RnZdn-KTRJtzvS^Smdp`(KD*eYlF+4;uNtEKX5nVt2&b(vi z^pA^)2}(MbcsENGRbNmJ_%;kxcf1~-FhRM19t72yW3o43z@5LfeSCTdMDhwid9Era z36%FmhxaZu4+@c{(EYDu;XNjxng3TTr9=A7!(;Obwc|JF3eR(UwQz2yQv1`@kL;u+ zlVYhUx8wSbC;c2GLu<^HHNs(37sev66W(FZ;_B_qp8Kd>Z>)I>YE4Vn+BM73G<$&d(K883V<=^(|5g*wMU!7d zSQ^`~3?kd@=!w}c`MNVHMdO~3@mlm}9xlFq99_MTHCQA=!6=uStTKyRZ7g_2?#0Kg zAjOTcCT;ym=^e`3&WRuICk5*&wNFcN){1eu@Vx`76wJOMw;+2>=g)@c>3rH_hwEq` zwQAsj3(H`?HqzH;d9vCZ$syJY#jI~Z#ILxMNx4kv*O@5SK& zx~B36b-olAP>POEe~JB12K?<_kj2P@he-w5SsbOZyoBYMf*=GuO}+Y^KH#0*Y4if( zMH|LdS6*u^`U{i2!$RJxU}|bWH}4)~f&^bL8UylGuC0%Y3UO6yKLzkfr0cIeao_a` zn_T=h!COt(>f8~y4y24ZvaNxWK&4CH=q?c(;&ttr&;4em6gL=d`1x@%BCIDWc(fpQ zNPQdJ)jz#f9rLaa20gePI5j{wX`o1!6os+{ICdhnqHtVzO4(Ht2g3wWPLN`F&*jJT zd~aqe%U;cOg+6LNX43bfd+qED-@%HCCcdsNgS*gk%mLehC}EtS z&)8+mkshO;ErPLYLHqVtt-}&oH!T3>NLW_2JiNGY= zGo7+MstopFfG;l~RffXqSLg9h4y?3B7V4yiKi!k!DAs3DfB!+ljInM=@Y^(Iui26% zrsz(@@0U*O`Z{gYTOa_T-a5cxyi+Rm(LvIRs#$d*LTeG(WRn23M%DP>zFrD+LZYhL z4G{86@%~409hTMOqcWQlalPXI%F6W>o6moCR}Wr>iD6cf0pWU63LnJbFgOMMo#4wh zyID42w2bPOoPbku8aKJA>_a2_+rfhW{Ilq7jOi8|e1p-raXWb_g^7SQIDhT~S(0F! zqoJW~_5gG=<+gKcWoNsI=zof+bs^54mXkjMh*=t~ZV!NUL3XMPSs;vP0rKFA(mXbY z;UE2xIyemABTW#KzQA5A^d#)i$hB7bdvmM>LE)a3^1 zPl26ZUL$>In4&DJy^`K58B&z^N?uX5%^i&N{SM6X`U6AfKYQ-Z1P@fE>irr1KoSDT z1?sG$Bs_14=pb%%bG1Ff*-I7jlBX#p`Y+G^_;-B1Q@JTD8B2cT|6vzp<-@m8IJUSE468r+uUwfX zZ^7%{TIjY_;ASTa+D;diX=&qm#~+(*`-PmQsM-SyK1Ku3m7G=!iFWQtlwYa9<5l?q zLy8Sr-GPbpE7_M`EVYQB?@K%WtQnj2qJuHx~LI)(Uvm#%*smyNDtM;ezOGd zx;T@CXXU&o!>{bT;IUbHtfc(^>XNS0p}7CJ@k_E}W+k9{1Yntyh=S8W>-X$*94b-$ zUZabfyj?X>UNqd6@2z{D!mSK4UcyjiGmpIG*#7N zxo|t&{HBKfS;rb{JGMSFHIOWj+xOX=OYOk$I=O44+%oENWImC19@!GGhn+w+J9SB< zb*Um!WRiL!v{rO|FKe}Ov2ppTn8GudywR_Q#^0x{T~d#)ct zO?3g-XQBq@d+i)|e8(5t(aG^iUUoXg%bg}n>P|gHk*`=)3S4I8*M!+<{8n#%+Mlp{ z3#7pny$n59nU9tOBKqQK)(4W9xG1}HlpRgKO~;+>6s~Er7LFqOy<%N8kk~IxyRc{L zKHlJnHWy&6^t?GfYyXMmyMO zig9?6l;CGj$AOnTXzJA4;f)ZZXpXff!vDC7Hma^Ev{fRQMezrI5?}RLUb6=|3*!vEFB7nU zUh5H|BIefpDBR?}?#NQ3bKw8h-fHNY+-B9LDH(ctk_S<@P-VsbAOTUEHq(rRJCWjd zH&^hOn7@q#iiKQv#f%JjHPjMGw{4SMTS0p2j(*4lI7GpZvxowAwPwzZCTRdv(we!lD*!?Z5TFc^l~`qF?z^ z#Gq@QINit0VTc6n1a93|p>28LwlUz-@zP(vnBx%R;~%}eeE2}>A36O6zNbhKL)?4y z&4+`iD%~*u*osOH8}BiL11v5+8!YbVws`bD1J&AtZ=lSTPoC?2Ts4v8RTJv!9UodV z;9{`;F~1abRVNoi*N~14r{{NhouWIg3F}wJ)m}@5-)%pN-cOk~p&vK`@XEna7Z!0c zqqnBIpyjitf$oXB&LwbwJ;?6KSoGiNFP zpwY>VQH9g9DdXW?+ytqd%gome_(_2lDi@(9istZ^3wXs@yPf3h;y(lDlQ?JEG9bYG z=FwqTpqAWetXL{u|0pzJ=&E4d0)sG+zXM^!4iL5SP_6kXr}cZ!TY5CgD-o z#`RzL(R2S-84_)~W#clCX$Z_IX5%snY1~%2-mok+`Ql$6BXnM@HUBwDv18bg{E9Vp zkqT68eL7g}?>3?avsm_r)=Ga|ZZeD+{JX7W>w+Pzc^Z*J6u2Mo8e-tT_zCA$)Ah|C zCdb*3ItU2~DLa6Pkh3cSpQG*6Ryn6mq5M5laGh=~M3TMX-n_?(3x~#zW$cLpF)<@x ztUeE@((44#d;qgAz{UL)rByVO4q5HdF9|h2NLH~_PJB$6GXat5HN`!^NtK)YvfMl~ z{&^OpCPEq<5O5;V{&4rAtfuB)X}pGd&v`b&yQt>Yxs0?tQ*e$}H@U>=cV6dle3i7IBK>!2B>AKgfldsW?WYDA1UE)bSa5 z$QW&yimeOA{%qP#DBnkiQX{H0tcCpmu^lZCVE`2)`H*e-*v>NHqq%TiDsu9h_aUrGFg^qUhf6g!dP`}JNqxR!&%7%WU!b6pH!vNKDc5Zq zp&|8v=$dUJFORwcK3+pqBE$CA84qPM*Z^aTkG3)2dA=D1u!RIl0w8@<2`q5YaXy;- z%B-)?Z}pzP*cDUOg_3u@szM(Ch@=XY&607Ce-9MmHo~x1abJ+0+~=#noQ=oQuf8pJI{nGX4p1)}f_57T5> zdplG9mZ?PU(zIZhOy*}$aREX1aRIiai`87ISlp&v%pV?;FK>u=)y`7iPYd~pVK83t z?A5e?n95EW%AQ`+aoFXPId(EXAakKg{*VKNt_yG3`am1x)lVe&0DIQ|M02tSpQRtT z5J`Qn{tT_JKh;1c>3-R(x)PFh@FKzzgeT@jU2~A+&BczCkSnB$x~7MdBmhzP`q-iH!RnIM)`g#(2ce;T`Ae+c6XC+DsRbTfwE+V666G=Ely z%8j9>eIe2M=sK%%&#dIZDtYiYt@iw#&+Yxo>VN+FHP7qCso?CJvJ?bBOvbtu_dcUP zmm7#VYJBr(OQ-Q|>>0_VFI^$q7$?A6U-x z2A*mT2C$3_YH5R6rl@HJ7qrQBj2?p3S3gmGto1W)$eS7j`^U!UHOZ)7e%>VS`kjr; z5nR(=VwcDw#;bCHrXHCg+l2Odcj6lVw+aM|inN6=rP0wzvX`LAbb>at-3_uaZccFJ zf?i-b&MckAe>Vv!AK;qm4TyBzho|fpg03@a(;17he-7*xv(VoP-S!@Fk;u??YJ^xS zx#Xwvz0P}17~Cu+KHugHowC>}Xef@Sr zVmnwautjw^?`W+lAJL!!X`t9_$aDcCeu>d`5GUb(($yer3MreqLJBbFZfwKBs#nr5 zK0C`e%vU_2(G5ZgW6#eDcltOm)?R=waf|C9cKR`5t@ctUhE7a|#FR7II4vNPGvKUT z*Xj}4@z5h1Qm3~T6{7oEBaWj7{B)-?XxI_kJ3N|yLnRn;GN|HY*ktdd7Y}20pNVk# z(o}O>2CGLvm9u4dIma6>&Z8dM)eBzUQl-17C9JyEdi51rry}^QM=@{3ArC@_^9G94 zGK{5#WAnX}+wH_Ull?of$@RBF<^AGn%CnRoA(B|U_xHsT{$@1I`SkP$kF#OpQOU;fO6jwz37ce3XV@Bvq(0> zbRNMbvPQK`g))h`dVE|N(=&los7FVa>j$z$P3c5)TwyK-fx_{qY+KuD0l8|X8Q@!_clWNQHn@&uFL~tmS@{!cj4Nqmv`n??=d(u*|eV)>nVw*%dxOq~XCSs?H z_x{BHEul7f#G6W2b}%~3Wmth7(bI= zX1ewV-Dop*wD4Pw6`k1~+-pJF9oI5T?B>vEo0plKCM*J!H=)PJBvo>6;e-3>KMD>} zo^tM}6!^7Qu0_6^WDX;bIs7eFAJ!&iqd_#iGxy;1|JCm_5PZCIa|!-*kZLDQ@HP|^ z%ui^Vx|$+)z1xC#de2^$O81~6_qmSOUMZsR<zzo=zDrHhL* zDK_(>S(SR7tDEY|Ok!}=VuRsKL)nI~MGqPU+os?pC0g;1!dy#h8^Z-XRv~|xi+kWU zh(B6tl4(dzl6IZS9vL_;(JT&mpceIrxq5j}@Xb7b@IwyC?PW$~;sc}QNQ4j^qBDUd&~e zg#*L**1ys9{p26%ikuZje_RhaMjMonw(KRYK|}kzLu29yzbq!-*T!YQsz%mMwQ|H0 zxLe)uMVQxV*X^Olb1l)WB~s~Bet_Zi%(pMSB#POD2FhrL-2(kY z)kSm6m^Oo#tKM!6YsYezYrne&UML4gJ{CKCKyE|ap;SJQFTibwue9{6-L*AIH8HRJnSP zh<%_Ib_v10Ix0BX{klQY{=4=Mj0T2|;n$-(uy3w{Fi)q-^_U5Nh-K1U}Q*$Y9uQz=7R#hLz&|EUErY8mMwfd?+9{4Ux#M%*VBN@v>cF-;ZN$9es7xviDYBR$;c5vxoJlT5&n4z= zn3=$=Kk?VAHR?eJGlFEgZ|u<;fvR`GBZHOl5krwx>EmLYHWNH|C+qiSx@9q<*OG<9 z%h2R)mUlU){uznZBkMbsh98hCxo4rv_Gba73Wudv|NRU);%X7`n9VzLoSXjo zL&(90wP|ITz#`lAZFME|6Y=HPPL+F$FIs_LMOuHD*3ck&A%-%Pe3i#J63R@`le z;7|+I>!{0egY?InJ4jm>Z-2lUtA6EPAhW-kUO*b1wZj`Or}W3HOZbqTe9giY{@)r* z>(0AL5)$?%KXFM>1w+ZY``r1yR}S+GN82!h)xAijoE#EZq@3=GfJN@B(0CeKUWR6O zHU%Ky*C5v~eAj`-9L(G-k(=%cXHUO}K|0|e=#r@V#Me*K9=DBRx88;~WIU9mzlTb3 zxc~@}+M+jqsBZ&N{$NBiJW6V2sI_^h#SE9nq84)7a7{76vUhUhS_7twdmdBaez~@ zX*t1up)OnR!uND@@5gY&Q>u#xb*`sQ7HQ$E;X)aQ(^jHA@$R&RuUtHgalMqr#S)0P zg?}BxSa;Is_;_J4i}L`><~~c*vh#{rhm(?0WY0GppIdR~RZux1O@{hWff>2=V?S){ z4-R%tN8P*oOxGAv>x05?NG~j}uE(N4eH6A}z{CnFY1aW|5*M_atnE5pv&RphL2bMX z+7#eo8u0RWyg=QXE~q)Zn+#0=DmV-5ch?avsn8)yJ-;TV#*UVjomYqRO|B_dAe}mp zYkE!cO6Pl|TAlz7|GN#xy31D`n8?GYoAWjK!+L5r6{A#>S~pJpuF(G%+_Xone8`k> zl5Z`x;{02N^6L#lY;|!?dar}Yg1>^zQYqbj=INEp)dDjK%0=Ep6 zm>Jc$dgh)KdD`1v>Yp3suVjaEWAH0hidK6RhM^+QsHglK-U2oII)tkg!~z<{pN2}3 zzOVBC<+pDm06WC!H>z6%=mlboEPSDZbNK)3O1W+$e3q2u2$d(Dn_11)kP8&7wDG>q zQYB`D6%o^jYop%Gx;@+pCL1~{Bx9cI=PE^s0jWIn&hA`mP}n1r`uR-fq8ndw+==AN z<~Vkhag?Q^aQ#aJTi>FlTVhu#6+2Vw)=wxnp3<=grWtiCn{{J5l%-tLz8#~7c*DOx zG$~4dN{_}aq4aHVw&^uO(8~Yv%=h%4T(k39(AO~-!!5^L3v`P^@#hMp^DoDnsJoD& z1kX8a3SN%r)Cy6R-OTuj>bh{7e=Vh!$H$Vu)!x)Qf46Y+UtSjv-<%P!duO3tpY~mk zNIb2D3;tR6mLf%M^kI?`^t4g&taur*kKt)KUJ7fM4tRcapLlyP*~H;}b%c#nVT!bM zkeinX5#h|Lo8ec~2)unJL_vA9D+di>jW4Z_%=>Z1R^X9Wpy&B57b+aXvZ1Ilfwb`H zHyyn9LrD?)tP>}(js=E4&>0<46Ss;m9dyrlXyLR@|?Y!?_5*f0_8eXw)aQW4I zm!<7x6z@U<>hiaLtay8@WHcuagbTjY>HNrT@l;+!KW6><(8Yh2p=XnESpLz^GexPb zePdo0!e1J<0cYFXJ{00j5kcoF9yRA3q}L@hPhEE;7M$D(@t1~-SOy}_vP3_5BVNCK zVc#9|T5xo<+uTY}t`^cmv=jjM`!Fb`V(l2fmVYv7F2@V|m_`??5h5Hts4?KeV&Qej zkkPb@z4;PR=oIi6(tdv=N{9$}_7|p2)nJzytSY=YYB}ERF9Dp2g2ZTBG`yN{T3RnM zQiad=%T)YSef)*@GYwNOsn|EJQE^p-)Pi5p6gR4F52uPr#y*gekbH7H62PY9PcVFB z`o7p7x*-e^_71Jcg{+akW2=+8c;rNQg=}#S6hI*77FXAZ<>i7doVpsq=k#JpS7GqY z4#C9ikT{%b(LJ}SJE+I<&|EL-vCY5d@$UW&TuTxyBNPfY7$O~!?H_{8ma=hfTX2Mc zW}UI#9fxlkRTjIt%7uNTVOP~+QJm&MyXz|FV`La_B~DY$z0O(CWJ68E$oQJm@Dz;8 z=^q~-@oQ3iY??mV{r?9V{qo0{@rYFWL~c1)v=}K`M^VWYcUjS#l!geIGQ&^>W&0&I z4|~#TmNZ@yMygC1n#RwGTCoK;lT=IE3YD1al;ce;I-xZJqo!gQ5hGz0U zIck0!R{pA254J_(>Yq`)+(g>={7UFLOXRUUu#8}sC+M_Y>!inm*p-NwLq_DKVNu)K zvv$Eve44-bw3Va-q+ezo%~0^;cf924Asc1uCkX?|0S5wE?U!sK-sd{CyMMYhz75|< zz3qAxYVv{H>NIAw>4O62;}6K=J~-OzYy<0YOR$5GHdGfq8hDudxBl&eAu_vq2FyZ` zmK{j}LgVz^=~N-*QzI>rr9s1z8e_k@wsziqQLj@airQbFA&;*}HKn9^Mi!mxyArME zkN?v)5Z{V>oxCX@)mK;vmQUJWWp<%yP)Qc|4K2@YC)OShX*A?H6?T~V#B;-H^EojY z@qBgh!@Jfx2DCzqRK9KD z<9g@R7zWc{qnvO4`c7c?YIU2jlfVSinZF;VbX!B@)P7|Zoyu|7Gv-R2af+Y%4*GT9 zMt4a$a5}Tr06^HNwEYplBw?`^X5D+gV=pTV!Nx|c*}^*YRv3;uvI=} zplng)qA+nqf2)E@HoyO}IF^A86Pa)FE2$Aa<;b~o2dWxXZK}gzyEW{XKbBT@w34YBS2YVyDk49fJ2iF@VOIFXoJPt*Q3W}|V z8H5S!Z2zK*cwF*iHQ(Y=-R{_ZSn)MD=-D*=^h3_F-F6< z3?hes+?QjPzqISo9jt0vy*~;#W5J~m3}&nlvr9J8TNoZ=;%k%N>iIj zo_APLbmSO&$I9TQ=~T^^-Da8ORM+Ixa&bjMzG^uZpBxDuN!C+`*$UcrTP%4at)N5O ziQ^XmKm~ z05}ECzA3CwV$^AH6lg<_3<%Fhv$(V+W=XM&Z){!u*dx9&ed84M-6X#Kad%<6BNQYP z2$sD~L4K&#_gR$U=mAz!Ivs4xd?S~v{Mp9G8vS6srRinMqpD%HkuURC)Wy2w4QqbC z#tu=l8sTbPp-fKlej)ikzx7J?b6$J-2)2avN#aJ_I=my0L_HIO%-(ZEE#o0yNA zPy%jpA(*GXk=PRKRUtja6qkFmP7{rGvsj%`pyx=2xqwx_55q#TOcen#Opbmsp)?Q% zAAdbMJw`p&k%5zoOX$u*o5Dpj_#vqM>CWzMlPiEFC{*`Dm3GW!P+2UDTC!-V)qg{f zMJeKl{zX^m$VF>Dp(G5<2LXeJRq^m^KU-2t${adO_ zaywjO$b4xsdeQ88sDC65MII(M8^zPvOg^@K@|06_j$iT5Rtlp1Z!2S|5~L29{VpKI zCjXBCUfbIseO$FR?PlyRB;NA&d00bntVDh>y&S?S?GwH+^Pi}Fdgj$qP1=Vi=emE& zigf(Ifnrq&vpnstiPr>@mPxXp-W>GAe_8sM&%4IY)OM$_4a^tZ|CNGW|8y3P9)@$~ zdjzk?5-lkuta6%bYdohf#vqGHe3f*kNGcxy!EVYsPxRTe0w*x+yUz@C9yioQ_S*QBKcF$i)H6sN?>7`@@;? z-Cikdr;4tMlKqdye|6T3GB5b)Uo_Y5#{4yljTug?k_fwnXFh9-KF~ICbx=FhLyJXp z7~2o0GUw)X6I=m-ls@6+vu3g*+V8 zLNESJJ{&z7GaiQ+rHb1B8D8WYs2A9DF`oz;s?*VG+HOLBawwUa>b^g>P5>_D!88T`$xj#yiKJ)OX%A-R>e~4#ov_A3V*rdFH02=<9+lRY=?h_&2*hF{F zGUzX(jA7z#{q6H+b$EqNql)bL(8gbSnNq0vP@ccv@t2k_E`zkq-shxQUeBzT(Jahd zt)w+=B$2SqGMFI{=eDt9RIY-0r1KC|PqN>NmH+sS!p z{7hv&ZXmW^riIsse{D!hMraK9l!w;T=+Jmx|HAM2Y;!#Sj*~suBj+w8&i|o~IdtXY z10Mw}f>D`a$TA%VY86&&W%Tp4r(~f*JoUtOPC!M(!7Do5|3%H%*IhTi7LT^7ACD;g zOgQGu&D|aPqBty%tyMFu;h|Tn=cU)I*M94**Bd+8V#w6C=Ui$^5_SCb4E#}If6?ja zy&U9PvtvH=!$Cn+^z~GvW}zO~m0?W(cDccchUDeT@d~3Bo1VexjiXm8;$b5UIhAf1 z8I`dbf|P`3nYC7uv02}XS`lV@=aS-LR!`Ncub zAF2M7dNnHlQ}^9V5djdgVYg=Pwn_#){p_TyNvatH-%ByI4x2mL(a8NILt??xUO|W2 zuvQOgYg+pb0n-kQcUqvYI6s~bL)9I38Xv!x)cP;0)dNfQARO40{=un-RQDssb3Qh` z0aa!0lqqs3`R61^60Bwy`E+M~PE78ARrd4-QKFUzNI5}vc3?4fbD|={cJgL@b+Br_`F>2MHZo|2zgF%d2`J%%!!n0HojIM&#irw zd#+=67TLlL#ItUwKlL=c17dvzIg4FZXs^NU7qr61&29-7*r4GbxZ6|Cww!@(dRjvy zIE?ClEcu0fx(;MM!`720-f}3i+{b@$t2!znrCDSh*=`5v5En0aUMd{g35B(u`Mlb^ zo=}UN`B3}$nIMyF@G7zG_Y==THPH0fnPOcejg4sx{@XQ^``@7l1*wTExwQW~$G^V@ zc|)|8QHF_gy;oCQGqt5dny#VoSt*$;K7o2MMushOWgsx3156oxEqrdtQn>Vb7^TYV zxu#uq0q8yT=*fT`vZ#&d8#>v=P+}8?ADJzx6x(7#m&6mLzZDH&O2Y3T1GKd8Jf(0J z6E!6F%*t;f@gpx$Dw`vbbejrg3SwNEy3250htB1{e7;znt$F^#u<-Gu9c^t|3HG%y zrAcGE_$8R|^`C{e*$+z|v+$yLJW%28Q5p!+7AC4Fi zJAQjKN$NG)2Ygok94TVQ^EC<<5J9KWLij||0KaqUcf|Mk*DPNSX=yveE^K?3RC`kt z2Jaui;yx-1^SR%Gi_d&bkRag!G<(N|p?PIA@8c{RE;Vd0ojHz>sCmbud*diUNEK^D zJ73IuF@>w$B%#temhm=P>fhpw2slu6GdKBd!i*i*mh7iwDuuNB`l1oE*1`N5tU;U5 zS^D|kAUlSe$zbq%pi}1O=va_VQNphzDyOKux^mmg`MxjaR+xsq?5!ez8@H@V-1JH} z@)K6*U*Bpvh`A0qaDkhDeaWlG3N7L-BQzss5g=K&j(r+za-FGgt$nv0g0@!orY$8B z*1qH&BoCRMR%eI*hj1OP3KgJwQdNBqYlSLx7tBuR_Shj?9sW(0WaANfWb?g8Kje>O z!mNz74!BKXaJ+#SMB;sLgRIAD`?t8iFgV^7-p^UW7jqt$&dAYF#NY~$lY`P|cJ^@D z=yf`LG8R)IR83Wj`?|aRYxOUG)y>`0<2{|k?FKO+dyhWP3^p&> z`8|t7oPH^3=DZbMHIP&wib(nkE60 zt_|<&qh)CbFdSz=6>=L?Iisuz>Fsg+SvW6Oc-B%8Va2iZ;R$7{^h|U=z~?fm@n$Qt z9E0sR0H2|mbwUa2Nv7=A%Z1b$IKiquJNyUQ7KswUY6ex1JGZC-WppvpxfK1O|8+bi zVOuA=IyhV0E(Lk`u^j+AhLlRQy@0izTSab}BqsCw978{DW#Tt>c39c$0wz#(KREB# zQ|YRpdMZZ2is5*EffM1#OvnENKdUk%SLf%*uMPr5X?Y%hWXX zV@i&M@g!g8K8~Z(c2J?L|3dt+prA}wlPQK3<<1+F^!>rjSkqy9-ksxuq!gVIY3AA~ zT|ooZ3|$sKVZGXPf+$Rh^$Jb8qoP@?Xo|K&>^Ya_3&o2BA> zYL}hHioLeabjj$QhIRD679A4A@`flMm=G)nDe(9{cHyvbzM@U>vMpuCk>g$FrRD9& z*jTaHrmWdwy+d+FeUQ%uxk^C+FUhSq^M;^Y0-`B$(dHvuTKHm*rp;GD!r9rG9M*dM zTiLnd-2sOMJkvD&gdJ6TiE@vr^)A0;L%SXPq#K{ z$bHD?o2Lda=8Xln#)q+)%?i3CF^5{2vXcyOG|%a)MGPr_m2)xFONPMuYN=k3|(I8CmOpJj;2JwF)yz?sE zUW~KtQmUsi!>>%yRCo;bS(?Hw{t>g3%xNOx{R6ls)h zkW`6{bc51tI;D~BZrJdyjrX|^=X}rmT<_0s{orD)J=a`w1sk#|s&4(_t8 z7tlC~(Agx;IhQ%?ERG~2W975a@UIb1-es+EpEdXPukouZ{ql`t?U4d~N05|3ku+c} zRWdiT`d`WRVE@E38$k8GJW-g*Q*?Gh+G}y?a$nW2enr}3DQUa&0Q@#Xs{3p4kA-8>$?Iz<4`yW}j6vJ~Z>I2$W*|foBJa%CHlay|)cZuvs^! zw4Cmo5IqgrR&(SUF&UnOUgik$Zm>{ZVt29UZ*JCxyq2P8U1x8yE?cpe*ttKTO4^Qe z?m8W0B#X`vu(PCGrJkE?HkU&-1iX(2)pWa$v+4aR5y}gqdT^RJ9z!#*i-U$Z^jJEM zzpAc|1~BU!g>Q^nZwQ(2xy&1f%CY3k?D|Q1zc@=hnDY}az@5nsVeB7X3RiVmKQOCx zTE3^>;HBOwMFD>LHdiYsc2O>YMfDa2Mry|!*j84F z?f3FhH|jpzpD7H6P&KLYs>}r+FLAmGy>DNRgf*uyA0p^Qh<>S@UQG>N*gY80pHTmgtZm z%8gx&;GN5S7kkdrfOb|EL-&4qBkXhlNn7fZ+RMNz;4Q4x(|H zBpu(u3PmX+}JI!F}kgRqPd>5Wgtjs(PRGOkT;}o2Am! z`}P{|)mYn=7&~=)e4>bd$HNh%MBSi7+1PF3t{M|DA#OwO}B>CvbmHU22ea*=cZ_SlU5ZBXqcUJnW zZPJME1^9#%8z&^IaqwL72U&HAnnT5pEbC`=#J}T~dkLiNh2sqt)hy(Q=C*Se3gQKz zfP8)>>D2vXxXWPubvM?An5axK7zj5xJMj$nB-V&|y`)vl-TTUE*7}ptge#fw#?8ckojos|*&o(sCzQis< z>i)L-!wg5D@da`t^t~|3X2Inei8Ax7n*_PcWo4#W+@8%(XB-gO(lK%)Fi&EW(>bS{ zqk1T$;s|ue|J0Xd7ELb&FnCH#pc-mE>*UD4f>6#-;tZX&=Xoo%=QRyH8)9BU7(VqO zKq>Y?bn!LrlErzx-8Gw95?8cOA2B!vx3ls|2RNU8951&GeY_IA`II4<|D21lZ5d_4 z$Hq6bli?&Y7X zdTs1HU?mR6eu;>6UL)N0x}hY{I}2x-yOx&+o9{Px>}Lhz3SSL8R*4ioo2LS@;H1A} z#(W*ll&1X7R5{58SF_3Z2Ao3zy3`HHHju}5xsME&qw>o8dB?-gim7+ICYFS(u#eNq zlYvC6StGRfu(&UK2WoAdnY>)(zw*PuXTf~whGprGl;g_oCdISXZ z^I@fCa=h_|o^zBhVjc92X#A(iETcsn9ieGDH}qW0{>rWTyK8AknqAKrnXr}ASTLBT~#uW zVObmt{yJfnG#B0^fbul0V7xr6;Pcgz#r6iZsh_QgzoF+CR>?EVm)l!gl00_PuQg70 zKCfOLg`9uN?W>v-B<6ihV`f(;a@lv2N-lU64G%MZKTMEI>(3BYdc>l~7V=Um@|E&0 zmjhyMxd%{!rQBf4uOk~TWs4u`r)?3xPxzp1)t&Ml?#&Dm@27pHu%qE@)Y+f;fo=hdzw(P25*v7v#ib$=~XQ zoky+u*@1(PS4a9uN%ioyw+xap>oc;s5w9TT{;v>iDHXS0(93e4drYr(80I3QUEt!K zTIB*k0-+zU2B!L}Ndt(cCOVr`8h>sRBaoJJ0px7~u8AOWSXo{DhBPa)ioYi}p2de6 z)?dUZ9wyg1ls{bKZcxt?febBi1}@+Xc<@c;7^Xv4u|CzXp>B{-lF7l*#3QoVHa%62Bx)H~ z5x33u?PkAVL=>hsr$gzRkoa+k*}6ejbkseck+Qo>k|ehqZjilxt}oLUKy>q)i~d{; zY~lW5EP%KF#BSR3!dqfa3O(y1sf+;>ugWAYVW9fUG%@Z0cs?o$L&ea+?2 zHKo*YGP5+(WURlGf3L~fFlhtP@X247OOw0wk!y1=!qcYO+$1Ot-ffka@a61elz(tC zz;!^HBL8AB#>(l+T&H{m*lJtYX6rOU;g?uI3;tI~zYX^x39PBO`1LpmuU(z8=M9jU zz%Cy0N*0+&UM{mjs9D9ow@6G3cTZK)^w-ljV>t{$)qCthH83L2(}TG(cA-w& zJgSb^TM8WlGWqa&IxHB>`3%0Zi~>Pr6YQa3c8mQ3RVKKj#Me}Ucs?py!t{QuSb`&N zzOgKx4_EOrcMHS(w=+?S0xZsX_ERW&q^=%4N0wQ%<+C<3$1nH9gxsQE;Jg?|*LU)7 zXL*+MVBfn_>AVJ5?nyQWe=xsZ6Etn3NJC{}_ASaK!A*?=H#u!MQ!AL!ER9Q^`4J=L`!YG`%g?;v6n$yf1iL~ln3nCPWXKUdFY&p?NSZQ+imAICmlog~XwT|}9L2}+ z+4E%Vr>W33=4#K$e>!F+m~+|Sd=cUEbBMw8gf_JQmsHm#P6&aN-a-9SXNvby#Z)&4 z`wOu7)3z3q6+gF)hc5M^fmJIEyE9LVzo{4(;!(#vE^dT4p~t)3D|h?zm!B+8L=*4x z4-?C+5~IcA>F^1h#s#qY)s$DC`ZO#GgF;x%G#HRu#^(J855yd5 z9{oxMLGdOEP*5}kkfC}Gd|U|<&YoOKAuFribzqXv@W{+G3Ed1`H$X;(#Jcbbea;c` zW5$$bypFGDWC42%=f+6_uV;AxhkUo!kv!FFj?bZOF6XGBDA?hV#5*um)xZE>9gxJ( zV9d&Dp{V^>&f5$(`H_j-XM&Y{&nss_eeGF615X&!$uA zVrdvB;{A{fe`S1C02%YElEADAPZ!Gf(a>2D2)j5=ku&L_9sH(#?y|UI`w3;`>}XhN zeQ3FWi}S_%`kw?Lrd9-abxQc&z2X7SYP}}nhzyGMG^hYs+^r*V6>5xcR&;6Sy7I$hLiidv=R-F?D z-#{Q2zI6q=O}@A#HNqpBqe`f@2l$h#aR}txrq%oWr&po)C)pMeRig#E2|T$)K_E?4 zL0@GigW*;8C@mi>i-4M^#E5!jLd{iSq2ehbz*p7->jksRO?%ShFg|@+u($Y_kO0IC zeUqPi{eIG`Pb#-F+mNwo03=9pd;ps94QX_arTR3Vjmg)yIC|*?KiJ-jPL;Q*Xc$ zK>j!_KXn*5?Ky!Rh1%WA39svrHrCAc6!hNjm`KGAd~wKjz4Tw)Fq@!GnnGD`9-PW?aSMwl1zoS4aEPTG7iHszF2qO$Gc@5^ta0Pa=1B{;yJF2_fH{+ zlXQHI5xX5leKLQf&dan8)3EUUVae4DJd<=5{v=EZ2Z6|LJV*>3Uz3neoQxsF%JjMu zKRqr3##@BSeEI8hf4Btexb@(SvTtT#;}+3@ZrBT84b_FzV{%wEZ^D(}(#xPN<=@UYqXEjMh>i)M&P_Nfxgd zFkDJc^;&?rQ&2=^6X)xKuFg(swG*pCdSehOw#*ZX3_cYfzpa_e2H|pBfq5g?g*~(H zdTv5i?5V}K&*eva^57;(-OlTcd|}=0>&Dk5u!GfA+Gf(})y=V9b*$EfhGv#sm#n=( z%7ueiM|@d1{*1*PLv#n=K+-0rAMnUfM!FuytnaEU!uv?<`>-EQbS2@B9O*TH#c~|- zw@;y|`1wkYhClNO=#9QrBNJ-Y>m#julAJ8SpV%psAV_CHJGMcob>9eNpoK9;Tb^u* zr_)~9K2BV9YPrJ0SDSmY(9(zSHR?J3B)>zOp$=N{m7yGmP0?>bmu2DDjBQBh(e=Eb z$N3{gB)llT>L!wcU`W9R4*^XK+upZ$^MHz$lB0&^>9B60uhS`SzCWB))|nJIUryN0 zp7klycgj~3%@-6jzfun^f$WkJ^VmF)=Jp z_D<{|CCS29Y}(svqT)$?R=sx0y1oVFl=ZX5S$q!5V5$cERr_4N^-Fs8%lphznbP)s z?CL(a!^RRm)LErgodXid82zN2H+9zI-^Ww9y)7ylz(_l%V5JcEb1(z!V`l0Vj>cTS zFDkU5DgN5Xy5(K%Dplt6;iY*tue_qo@eikPhJjJmNMUXuE*AjObjEN|kODN7`T26G z`3nQOgvf{FM6{+JE|zQ+4o(lNJ}6)W0NEo59)nMR^%?nAMrHGmFhw&!MmEPv)FGn1 z-b!h6KY4xK3j*YXoyb!!a`EP#{X7G2Qd%`>E}hNk;>z5}eHAZ0!(A2OZe2dQxeD1g zBwWZRle**|8UG1U@=Wv7FxaI?L(7L%j?R}GRuQLO{IW7Sh#-Wm>{`t-8jCh)G@x1b zWt)vvWr^c0RT0K3aSef;MkbXV8Ok$0mz$2KA}i6y3DtFu8+bd<>e{>Z?~E}o5h{rg zzFkwV<*@49qv7(r@RIFj$%{Y}&QthM%;*@wL#Us<^|K(h<`OU0rb)W2OmUz!2Z_r? z@x{4fLRP^i{XrgL@Cxet<{F(hb-;v#dVyJFS2HtqQj6HGKZXR{k zh^OcVOY-BIWK=X-W#J~zrDd8n-isTrqr@XKX#9@A#+YsL*^nG-+m{1@(eNwOg54CF zbMC4OX~S=?HyfJz)%_p!hf@358?s}`QM~%C%HZrM8>iqO1ez-<={U`Ar=!^Y02=2I z{q_c7MO4$LkY*jjd^vGhQ!Q9ez$yl@XW}aVd`|{PXc5{M83`8yEQ3-9MTmk;*G8Y> z#<`h$3zYTLt73l&iP-hA;8&Tbj~-~J!34cRd8^O8+P0jw1TIA|GbW@+!7C31E?Uza zjx@6Dw`4jR%M=+LYu~%~Bn<_>fc{=11NRMDuUytXx2NnTZ<)(~Cx}CF$V_v_Cz~PX zspxB$G0~y>43e9>n`*-BvqG+6VE8%Py(e-deYh<71WU=M>@e7Uvtd-9FW)z*K%;KG zRZ~N`8=>@qP;BeG=)E5s$A&|FXa!bI#i`g@{fSnbqS4+ zy8i5csD1UJC;sBBFIV&>;b3A30hfRyZ_)I1BUKAUTzf#2Wgwn5aPTmPp4hr4h4R7J zsg=PAh8OGheFf=hIqF5Xq4~t3rBm`Sh=GUeP{F(1B_`s>HIYHR)8ZzpcAvY#4lK>z zAoLoli_49Xea^v_VlW$Kmz~pN%2M-*QW;7{;^Oq>j394HMa9y|gr2ouWu)k~LUgKj zs+vTRI${$HD>DkNra5C1&FLAv$1Hq%yC99bRg0>=#%7y-pfBp8%N*$rg2snF3y70uKsowX)D(D+%mYqKG2 z)Yy6NgGAxy8X0e(7KyXGaW9lO!D{A3q+D!8{yUCCRu#!v6j{}Ge6E|?I(dR_R()1F z<-eaZce3PFb4$_N9luVl+F)yAG<;-A@wsg?awr zGi5V65KPmg(j7$&3}Wm=h4OTu@;_J!cVIleDouM9mKLG? zd#FZOCFt_Y>X*iE9v20x#epM-z6@Bc!ptq(6S=L)Sa|3?d6Tuii!m%Snj zv-}+EyXSTDEkGWaznj0Vf>uRx_;%!{a(3AnceonM@h5(lo+A09Whb6q(hA_l=_MMc4xs?U^=>f zeQe@#`HCvuQ2SWgO-8x&ajCSsgF^GyL{KvonYVpJM{)5nlcUI{xFfhSbR8Dj|ngIi4e#ufg9R zJx0VrKy40{(2AFYI{PFM*x5@4d4E~WZ*Ucx))e{>r)|v8jx_o>N76s(<0Vw^pQs5EH}X*T^nLh_CA8dU2CFvrun6i*E>JjaXKr`7T86{puZ?4z=drO z1!>>YHl|1nuiCycb>(I56O?8k3AI`t<1E*CVEDju)so-?Ra26fuTp~S?wD$9x~hEF zghcGe$^>)PSqO}dR512H`&Mjqmy@=(}46M9;t({j+4e{o;3wVQLo{{^S+0o$bBCp^B zDLQ4e3KA51?_q890mUnOhmMzB`@Z|MK*5hfK&3juN}LoBPNL!EX9=w&u*+*`JI`A3 z{Pjg-UvTkfNAM;G&k6*vpAE<;SiQU$!=m-;o-~8BAn4GcHOb_|s}*z8>a-u_^0ftC z=^Nj=?#nTpsP$U__>O<8r`_4%^>uFdE4fm4anGLYZ`W7L_1Uz;jJcZWtWX=)3{K}L z*3*|wsQV~!G9{IywT&w=R4{4R8Y-E&M}_^Uxa`zFKNCNQWD8b#UAGRTA+0!hx*V>s zXp`|{vADUWoejX9^|hyJTLdeOuZ1N-`Q!xIj3`l%S@4_$xhyeIA{6sJEvi@@opBN4 zC)08O1yPnq9FD>X9EY`8K>bYX>7nD9hm$>oCwBFQg2i#?m*bizuuRY!L0lmZ-mVD3 zjH|MW|6Ja2+AuWE>KiGO91bz?`LI^nIO%Ixv+CnYe z_rbg^1H;ndiUy09+YXbP@Zn)NDO>X|Bsjx=i_wRC(|+B61?_Q=Q)klMd{><<8LF?w z9%nrLG9vw3y#}Gs=*Q?OeIWzQ$X|fSSntd--NTb_TQhq z&A-Tz-a(I#St7Q+rZ=4v@#dVEpG6&>oevcUq{=yJ>mj!{f5hqX

l6RLSeG*7gVi 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 literal 0 HcmV?d00001 diff --git a/cmd/clef/docs/clef_architecture_pt3.png b/cmd/clef/docs/clef_architecture_pt3.png new file mode 100644 index 0000000000000000000000000000000000000000..b9d695447308c0fe7c0dc3018618f7350f2a806c GIT binary patch literal 101351 zcmdSBc{J4h8$aBtyG8Ds)E%M4P#S4L*;}Z_WS_x|twOSA&oHzq6g3!S4}%%|U}R65 zt*BvaV=0lWQ!)m_%<~>p>i$0G{LXXE^PKbe!x_VTmiKjCuj}=CUGI-zecdzs8$>p& zSh0d1c2?7H#fr7?6)RRX{>2C0dB8q7ykf<*6)?>ch^w8W@$0Wf4s=ky&!-YJX11)p z8&aQ+aIoK41-&Tv{zk?YBZA?FQ;yYy3~2skwRUBD-^6?ts~c3e;ku1`LXDf)k?-R|$t?vekKPLxz}f#__*ywP znO@ZFNZZh>``5124N|IeVw|*9$4ch5Czjob1RJ3q9xZQQVh!xV&eEcFix{6&B|1lJ6&M-*Xv-p8)P{^&nZT79$313g% zg5oXevUg74SAjRcuXe2gzp6Dn`GCN?MeyRU6Znav!Yjy;yw8F!@B;j@)^LT*zFpuu z_yR8$4*c7n{6*ff0;SFS4!*z(-f_YCct>A4@eSzO<9I>v9lQZv@J_S}T)?j@xwR9% z@&SSO9sC7&!8`BrOEbKEcq(eh`@3vKw$3E-K)lQFrIdugSFQe~xwURh=i(fWdT}FU zEvkx6vSw-?Z5s{o7VAnQj2kcKl!O`Ynt^^p#&cw9s{$2I7TxNmB^b)jC(mv$Uw*TQmuC9RK) zieNV2Ynr-%w7tn<`;BXTk@}#>1^}F6Pu3rqd_w_AQ`J8=wKRk+n zCg+Y3xBEissS|gNk(C%Y2aY53RP>t7z=&-v%zAh85A*XWelsRre4*n{zU%d?FXR#x zci6Q@mF1Iy?^;LKz)W5)4y2~SPO$7QT|yfh8$TW=Sg$6GB&hj0y$Ish?ZkV#=<06% zm7&O^+vexC8Shsu*Mz0Z=rfNnU!Fhsk%;YfDQg(k)O?B!nZ;plAniR*SQo#sV*6M5 zjeEeFrsf=vbbX((VpLXhkLltYwY$%YDbM!=pxJh}Ye=_`A6%IgkjZIlal4K+f;?b4 z&GuN*oZIug*9gXha~+c%_ar&bb`1HgoARP5FvTi7n4I?yxiaqOa{V~NnuW7@vl`8i zXimLle95g|y@gE1?;{e4MlQ50o7wRmve$BL7eYdwMxJ|UE=pvusVw6n|=cfh`aJNen zB>Rly%ExW8ZHSba216J+_-q}#?yPg$x3^{%<6+GnSKC_hWSpA|hB3p5GVh7bnDJ+f zbVm~Z^&D1u+3Yq>mTI-kx8d$HZBvcWsWghC>-5dCaMtLB8noEe8hiby0CmAy!SvFE z@oYwhAG=s{=R&68BNd%HpH|sPD%OOFnGNfwUthg$>jq<{3R3=WZF=9QPaDIVlT!cc z?L`VElgybx(j`@TW|YF$_eE8zNhI2(w=pGSBu{1|gI=6VZ!K~(kv?0&zQyL`H2c}? z`vEAVIOzSc&hNKuh__PN;g3J+NffWKpe5jf1}uUknmNkSg7eXN@On&CLIO1Ee2bc- z{Axz_Czncs&&LwM2c*tTkq>Zq!N?x6KVv)O;OfjPMHV<(;J!)*d@?IBh>Vzpcii5%ZR^8qdwcua$?{*-1v5zqE&T2!T$Aj#A)3gFWCf6Ed|lgul8gp*dRAO1K;RqmRGNf zyz9_A{q0j|E%li;5*f)?ugjEWM#l8oKVoz`kLU z@mhEcEv6b1rhT3-L=&0lu)Tt0P&BDAYARi|VXU)gzX5(~n3ma6 zHA&kjSs&gL8bKKTw6D&@Tw7!_G7gGNd`Gn* z@|_);sTkmtR^t2NjeHRVG~TM~h;BUVL~xU-I7(zMT3F5FAWG!w4szr|w;2eQfia$b z3cJX1{5xVcOwqNc{O&pPA_t^?iO4xTpSX!QinPH9N#cWlc(;#xwP9AlhV<0B<9b8RYV(})coAIlf+9v#hQMn4o^D<(PgN{29ZZ#Rg$H8)1 zNFWwF_)d7;dTE6{P5kP(I0U2d*}RDQ7H_8w{%+5kM_z3|_BV`;{Dz{Iym}>C+AnuP zK@zq)W=<>lsI6u13$DLx11tJb&o${+ftMqzMv-gBg6j<}%z9BT;ym>j{~}w5JBpej z$hC$Gn3!-)7FIbaC8S*$!bI`IO9=`l8X*bINO>y$=!iK;d97vA4 zl0JDQB%Jw{fD|scUSYBb>VOYP?+C6=DDM4)c@!ijTDr4Z+D*i?Xy0RLw=Y&th%(wb zF|5=i8v@^f8M$h?@h)5f40{%$CcIuJH;@$gia0*P?~O|u^QK$qQieMqr{?#o(zYIN z8mPOVmfOhENPB9B5I_dEKO1bxFAd@<2Ad(HNw^lreSh!O@CvJX!%W`L`;2iGu&QfN zSy}XWjtqRgre2)GP=yTqrmRofVSg9`c~$$YLy2F~`IeIFhKQlgqD;}8{wf{eOl4#E z%zSaxbYG}0epua7%Q0{3idR~P5o##Nbtb{EyTc`Ra zniE?or?<)HP)o*x4j@mW3om<>#0OrmcE@>GI7Q3sCs`)uziVX8Z^jJyTbnermo`E2 zV@Cb1e2IW?bWNCn95|QtLPGaT;t+{5@6X-Lt;92WX|G-tbAeh}4S4(_#za2khTUy1 z@H-WT95lbexs5y-XL0r8+Dq$NA4Ke!H_s$N-!|1!a$B>At^0-&6Ti!;_L98=5!uaO zo`-PRPu{QH);XI^&-8NOj@ls-c)H*R>E@*ZXiOnckW;_EJ+u!N-I%}l`(^3F`^(*M zIaThQgB)B)9J>j8mX~jfLSx0!u|8iKT;N)uCVzC8-GF;79qj?H^YaMu!q1bpBI1tS zx1Hwwuq+)GFY2?M&5;l5dFMEe2mBWQs;0&a@Stp1gw$4HVOuvhH+(`uKv#aJy62a7 z8-nxRyg4@ls!GARv!(kKOU#A-d=A+KR0}EcmCR8}PESii)kB zh4$7TKYl#W$q4lS^!k3bMVYs-;00wpmVIw!h1YOP-PCA@$8&d`WJH^${EsHZTa*O- zQqM5n+5j|CFvaUWKKv^9G!m18E+W-d78c4&pN5>d00h)GaUQriCh z?t)|kD+rbj5EKlL$s}DQ7bYb=6H#=!>^0IV>#bBSnQBAA4@PU@hm}PYo%T{LBHAw2 zss2!R3oC06fgDv4;4Rg)4YkylFEyp**i&O>eQps_Ix>b{pyBE!s&^yPhcViknz#6O z9Lv*>3EQDWjUOCq_H*7pRH=M=-e7fcW*c!fVV0#&%fEYiG5dD@j2wuXFphw`hl|Fg z4Ln3$CnJ&(4I?xLqRmi&l#}gTfnmDWq^6~H0xHx!FR8+`q-&SznI1xBTxX@*{cJuU z(nBzqd!Ic1Gut~h zT|LQLrUwLJJOQ1M>$t}3KyJOkn5eSn-Ua3XQi~_5Z!F|0hzHUHkBLfz!C>I;dsbxN z`ea`TNF6Vdv}*gVcj=PU5;I#$$Ch;Xpy@(5Ht3(SB}x({KP8>GsjZrGCS2Sl$?(L9 z6ZvFBO@@Gd-kaE?xuz)6$S|5yS6{zyNxZCHsHVdof)f_<{TVTWfJ+2nEDGWm6>oEG z3B`B#y@t{=RXKkHubeZe5exCTWA z0R^OK4f5Z4q%~V>3g+%`)yVXj;rz4u#?NnK%|KX%GC6~+q2`+S*hh~{ccDm*ijJ*s zK*}0D)rlgN^8I$FOJ)BI7DWglorH)&q{}>q`tSoIMc!kbF6=$ws)l`srvce6^%!Z( zWrT^TcxRbeGGOgwL@0jJeAp4(c>zfqV)uK5q}A$zn@xkld63d_1x3o3HM(d7pMZ!% z)UBh6 zvknIt_vf-l3aV5gi#5CuoDgWX{=^aT_OG{iCt7B8{D7H7(D-SIEaH#VuOeM$%PbsF zWW(x&FT4-^2hC=2vGI3!X94iE*k>1Gym&~S-LkAus6x8GGW*YS#3Vj^drf*F#=ko9 zBBU-}_6jlrq!RhWO+FM@qHMQMuUpBs@R8>9Z~}oK=Hp~;9>*`HTzGOfR86hB)O*Z9 zCq~|lRr}@*v|JSVpYWX&)IdFC{n`}J*0(++p+{<%$vhw`O|M>k8al<6 zkbB&jV^Idc6NqV_L=a}QlT+4MM?p=U2@I{dYdOg9slG4ZrOn!7&SX#EJLp;hPTz&Z z&N9r39HKH;L>-Jj>e>T)NGLT<*NxYW@n4GA@daG0HLU7iY7F`-QD}Yr^ZcTUg}^k~ zTDD1DNvR}Hun_8Q)Yg%A;bxh4-h63mc5x5-5{gvc+?SIMnd?lq)@JAQ_(v!v6;Y>?7n~AOpz)H(Adm_j{ zha$*`rjAS#xnebS^`0@K@aCcBbo!geN4nLWjdE$nLH#=FM}A%L1{;(CDI*`&L+9WFMq$mANg2#7mB3WY1tBkjt_~DIGa>;1&!H3o5j$( zP@M#LbdvLG#4eGmaxlZO2uWr%fwY&u^(M`1TP03-u?n z6AsZ(_B6lZp{OPC7J~_AS z?a2t1oDN=lTziyna|AU{S}i;^r(bpnnXfIiEMe_s>1B$S!Ol*)tva-O_jB2jHXNox z`&!W2O_w)F!4<|V2T9sUtiHlE*x*PPu)qu<86;zfvWnL*Vz2Q{vB9Fv@A5HxYSC%* zk-2Asn!n#bVOL#K?WJczytC`Suq8_Gf*gbFf(VH!IP|MG#U5omwWd`ePAXg}2?!Tg z>zWZve;cFVF=2>IO!^TpsCgwpCSSk!vsK=`JMhN$F`| zNj@9{R-Q{)AOX4@Nfy##f!6(j&~g^aH9t!FmnXhZ>-@J@>P`Mn9LCnYZCs2EN_#Q! zek>#}?u#b<&#pSOURudbWQkZT6C?}g07QCeJ(l_Fi?nVbg_is9i%5VSqAG1&==rN! z&Z96R*~NT1iRxIVx#@4l`ubN+_U%_v>afT~6SpcOoOYjIM7|azNw|vQfde`1 zgWsCk^Q}YUj@nZtN2r9*1@4*xy1b%$|5@N??sxAZAv3#XzJGpuO8d9;lb2t&fX9H9 zMH4Dt!0+{2AIwLgLA$pG5PzjD!=j7=V$p+j=^)#Fq#Pe2`bABthTKZ`36msJXt`Fs zokfm=0LCMdnhv;Sj^Uo2h5ZJtL8;KUOAGs9jSaENe=?c$rV`68ozKtDAJ}S{u$Kmi zh*{{U8QCT{a}_Os=cwEV@cL40Pu+GWF0fR(IF=O{C8d!nSD@=_)XEZ0wbcWz^!tr0 z)zMCPgA{!JWmM)WSq9b5q8M0!uf$VAnm~s@12;R=V%RDMTJb4NrHcc&QxdzVUqe;3 z{9N`LcXxN5-EFIE3`~7$YG!{{SaT6P^SSIu#Sd?Q7sCCAele4s@X&6#j~UkG5C$&1 zTJj0LEfd{RMFL=5^wLjY$FX-}tiDh=i$o)G&Q$~=O*eUWzp=4#Qys3D3;VDnA~#+lRGw?O$x}t$ocMQ zw-a8je6~nw?1^2ew~(Xc!uDakx>HO}%V271>dESDdm%+w;PQ+YkhEy03Jd)l-{BgK zZeIqDNNVa!!Wx&h9E*5)Sz_lAeLM+glx*1c&WFl*pK@lKC3t7pZy&lZ*IMC0z0ebm zFsa9FxGn~d|C|6%L=r}dB){4a19acU9`!ku+cI^no>wE_YoP#sKLI-^`%x z?Br0+4XWr6in2*L2{ zQQ1Hn95zZ^y-vrs0vHrT%1{8O1|yu>W2cvN!2Idgr-LotFas_lCsCS~x{TGKwWtIS z=)#4p&G<+ze46px5Kf&BXW1lisUFC4+D~V#j2fC7WZ?4z8(&-EwqOG!Wmu`)c0%we z2bp)B+~Tt zkrkumI7((EI<6gI>y!lL_Xgjh{AFRs{o23^vpgi1-B}1!ZQl=+0>xwxTb&RhI@=G^ z;P)^TPOCCSzDmp4pO2<`A&Nw{+HChn*$^=|hf7wo(`V1DI{GFz+TkDy9oK>s=4Ak; zoP?3rSNHpdAM_(5djC1WqpiHb-y(7@fa?Z*9a|#i(Xc8q%AwBh!!;?SmqS&m{ReN@ z8Kx$aZx^f(b1J;v3`O#TpT{(huTEiq@n>4`OTlSvh{XBymi?H7^Q#l;cf%pt%!(Uo z5tt{ugY)uav6pUWp!UDChrT$&Ybg&112_~en3ROLZEyPE!f9|YG zbybwiCVSKMNCGgP6GjnUj!9SE*4Mi?Ne>M%P52{uBCZKztmiU}Y}&Ea=9v+=c@x=@ z^_|DvMv8Xu?&@dLwHH1Ya9VM7!h$BUu`jue=Mx&@O?UXZbJsHx{-qiEh~=^`$F{W@ z(QgK~lIsn-_sGbQuKXJ%)jT4OiF!ku!ttrj^q$&{pdx!~M!eAfph$KTlH{Cp8FQG0 zT4GM@AP~ka za9L3;Er)uprJXkbeb@+c!Al_6w1Twunha__oaR-oxZ--RR+D5M={nrupQORz7NXHZ znbz|WBh2}E0!jKD@Ie%u97u9N>P2Fra+RB`x|a0C!eywv{jJ}i-(P-Q@-nq* zMb=qKBcM>R=+rnHQqb`kf%FNRbQ^oni5^7%O2d-6MT-FhvX=S*8Hb75iF}nRb2_si zGGZ_PMpF`_w*VoWUJL(;2%w+RWJ)@G7eFd*6-ovs6a24t)|ty_G`a}yPTI@FBskQS z<6rsROz-RK`+M2R7MkKh($rG>QhMS^<>q7?*x9>ENF3=o0zOVt@%r%WdJR+gm#&L! z`5P5ec`WMxzz+I@H!SA-f0}k>p)*Znu{-~%5$*SGT4I#C=6ILxQrhXT9rp3cUSuhq z7*n42gJn-CdVbL_ZsLLlT~m7DEa6q`-rVc3S@W(BsmM&@l6hcwkh8L~b~mN69knYG z((RkmS_$xN@N?%>$3=RM>=Ip8Fk4XQ6kB^2Z*dUD4J)K~!Xt3FEO6S-Dzsq3^>sjk zDJv13d1GXr=n$7%FXKBzxbPxq{FudyKtBW`QY8RT3YRL&o56Tpn2ocrAyi| zCEsHd96u)z0b>|P73w4^)b*%XCL`z%mmIGprnQLcEA)4vN^R-{Hwp;&Ja_;6=J5$F zl4jaVTi|NEbCi^pRxy~V-4nj1+bRyq+(x*LOvgP@wgU`7i!!qPqP2B+SwaqB@VMf# zg4{m;Tu*c?yCz{DXJG zh3*Q*W7M{j3*`++mxn6wOg8HvGrT{CTY0|WYr0$Z z-iwF2nl4di@R($TVE!=!(pmW+v5}jd#jaM;>TpuurbxnYzRX8rWu^mUXNk-V(HW?d zm)U%rr zA^%O>0~+hAhVI1SM^I!@Mxa*)zUj_dD6{SBeu#eOu-}NO>tA>lmMF#ZHWuOT)*{6Q z0VV}QX%pZ2Y=~S#4>=M=72xFA08%o-XbkB~9DajKFj!@DGhGs0!6B7~HM06`4x(fX zNGJ4PsW~PA$!MRKc%2nrfnO{5=2g*=2QQi2N|L5lz^}}?0aYXXXoyLx$c3rZI_PA% znV|ujTEk#N*%xh!U}&Pql%m~YlSAZ@k=okYF{Jt|DLun}6O$vcO(eMV_pLd-l2}7V1~Xb71#N2>tAV3@3miNJeYV^m>_@2fg}+03?pimgY#Rf66&{x5f68r`FQ`Q1)wgZV_5 z&=3+Lve$eyea)9r!5G2EI0B~f@e#YNz<$zoYs&e)%53G?9PKxxlgOqNyXJ#X?yw1i zjc@I|w#+Bnw`lr-0zjNVi8vU*ub+3vlk|SJKc2=5D*P#fR-n+5BuQl8`@+J)-BBcT zP_FGHIxYN>=Fb9JREa#ka2y}xU%18+tD_*Ptp*1=dj512u;u?WlJsW#5KBgQE%m{@ z**uaRaW+Q=K&M7d+tOU+hX-=u|AFKA3qh7`|5~_8`7Min9&d{pZ)&(09v;_Wf>A=Q zp{;%WtXAcSyo?X-Gi0%k|JusN8~FrPLY?2`U7z6y+dO3MzUdO^{Mu$Ap_iMR5SkV~ zO-|Q#d%)T~+hz7CJ9OPmqAwKIa_t?7*n|2%a7Tacn_HBOvs7Ll*{}65srny>Jotfl zi3P=azIdC;-T zoqhiOgZ~=akXmXpzH78xJDOl((VTPTYIDi`#%~tI@7;VsR2C@ZuL1BDo_F(PKin?8 z@d~bx?HuSSoW_$EznlA#Yi#WoDkd72{yN;9>{(r}v|tMTM(-pMw)P7;LA#E=F-;kz z%(#jAU#C+r!OK*YjJCBF*=_r8=01^u(|P?@7BXz4y!T^3rm$th0mts=nBMPpp?-P2 zZodCB;Wu5~(49a42~%b}P0)J2ARd-&&fE>guakv~6*g2eGZKGLRyOl!`oh5U&+N*9 zs&(_|2M}1l%}lpQV%=mKzBaro@N9Yd&p_k4?eJ5E_yg#QR;mXDz}s)7E{G<}FR&+R zqMiMD-PUL6q}}kEs@YPDx$p2UpV%^gt&iTp&C}ASJ9WBC>G4>$*#w!jkPtt2t}c{& zGPL_C_P$W!z}vdv3us}Rp{I4ZrrBSwWCEjUe*AST5YQtocV%}6k7#PPXCCSKb1;*N zsiRV|(R<)SDD*{y5~k62P-(p~mXo}joq)B+38k9&l(&A)HMNziVumRkV>^|7O%4V1 z^4qFhcxIFVdR*>7UfpGZG%2K*Si0mN^`Y?U)OPj>6yEztp=P~-Y`#EO+s9d5m)?7? z;2VqiIW>P3%i53o{SN@J1ppvpfj?lXRKpqUdws$$!ohyl=+l|oSx_;~$S95&mmOJ7ST2c6x%u;-Qr?Zh{+Zm z_cX4+jQ{M$8?!DUU2$G)E{#(s%N(W}S)7jo(sA!i>9Ao2QV^zl+i1{B$A$b^a=9%k&? z4@Vv%X;TkYRU1c}V~F%|F(E$>liv9QR~o+R;rA|P+w$@0>I9O0lOcfzB{$xgIpnsb zRh7R)MrcPWvfs*uLYoB?P{Xnn5=Uo*%X~hpcA?JKXT^P*N<0T!G^Up=pP`MtQbcUL z$q;$+(;PF0TnK9p-Dpefw~xQ8?2;D|q>2pMo2?B&noIZ1wCWDKZ5NLjJ+w>7IS<8lr zi0BGe09amZtN3pR{!{xVOSQlKuuV&ED8d*6A2)H;^+*J}VpTP^*b-->96Bok1&pwE zFoY}p_BIQIJ#CTIEXtonA6E^sFxoNN{=M^FT`N05gPyWS+*_E?GZo1UmC?c(3!`RR zs-(9(BxuLq{j064h3yZD)EkuRfAP@~Xf+F^ZXiY&w4mccj0$I2G=;3uJrlh<@y}%; zwvr2-Gh1t^ho$T-lP%~oA}R&r+g-L}vcICeQ6pQYrjLXpI|b)RJK<4&>(tM0)EkN2`A^|Z%1bSfSt98)L@QRlqJ1kUa*F-4h@&m`A3hwD-c+M2E`%d{ z_O;mO*b#3L`bzbc<>;li#4QH@3ktv{7<7Q#c9Mx}tTeo$iKLjFHB1F1)qGeZ ze=<%sO6t^~s{#nuV#kKpWw`AD=1T9@y{)KN$E zm3TvX7)5}2^zJvmVPzPkp~m;y^B-DZ2ZH_H2CA2tapz;Nro2{@LNbUlXvg>IZSCEx z?g5@92%sM8^}4Kb#QbmhqUSHVeX2l~|9PRro*&`s|7IudVW@vIK^29TP7aG*l{;3! znDHRorIIQ)Sj=^B3?&k*|qOY=c^FT1^8k?~#52{G*ph~ut3@WBIC7g(G9cU~@? zDt{#shoy|rXGh@?f3gl@%A3l6sS`>IX`{<;u^9s3*Ts@MK#~zV z_~nUqrul!{5xEdVr0vodu)Gh(Gr%P&W;SG&`moY%>1WkgaBg&BF0m<3w=h60DoM0E{n^qJYLiwl{&dba{ zKLoUo50Cn2;T<~W0f%&EewHtOX-P{_K;qSzjP>9_k*)y~S@ba&>FguzI7^>XU^er6 zW!6&lqX^d0;chQtl!0mzx-kq#3)v2li$uHrPRB=E1(=5$A|rcLzOH0__nj#D_U9!a ztd?vQ!sLFe!`xmIr1HL@+amkmkQ0zoaFNfRb%bbiTSD=lq?Q-f1&03xaLJYkM8g-%{isWzifBpCzd{xMJj^a42@nXXFEa)7A$!f!m3vVED8_r+g4M%T$S>iq#TV*zzBUU_Y4Rw8Wl2YXYd>Qt14#zzT)= z0my)e%OrsI+xMXH52n-Dx&Y7%hQ4p>po!&r-9XUNT;If4n-rGSkLks*|I2qx@D!d; zkJd&Q$ZT&PdOgw&6s0|N@?sg~yTa{6X-Rd7!(_yZm`0LG?$_^t&v!Rj&t>LrGfqeV zq0mkHlR!Pcmj3rRYPgQq>9_cu15}~-={ojpnt)W`!@Z+JS{hy)y>)x;36b^|38g~ z$M~Wx1AL*jB$Ew`%v>oy~`f}^$TnEqUwK;cyR$*G^ zf<0}#hy8vTUWVB}gmEzGG*XAOOLwDjqmoN72RH}rP9NU41NttMuImw%BA7xtfYc%e z=8Rv>);a56V5rHnD*xPHfdmus)d`d6u0uM3zAyqZI>7}s3pxV4VJ?+wv-c0Q(83!B zFAQYR+NbY}egsp{8c!f}nkxW9 zkyh%)cv_)>XcYY83_Qo-F>oA=s0E0d@rJE($}YXns@-(c)D;2{wNp@j6~!*%(~RgL zKsc8}%1JGJkI!I*qRX^~Gn)h&8V@rc|A!c zd${2%(yNmvFsF_F(92*{n(O3oJ3{;)uVGWhcyVWWTiPBtthRwV`HBdAA!p6&>tO2B zoM$Z@HE7jd%LnOV$eeRbhBEn%581s3^yBn0Ot}>TwWvB@!Y}^ynnK0&;O*W0(b03y zgXW#{usE^^O8cbYqH#=I)$tUoL#TT{ECokekOHlyMzJc(7HB8Jh8VcD{LqU7E^`{U zsq*EmB4g>@&*#hXjITbOCuwc;yHa})-qc}e ze|YOMM=4`BxzH|JV{1UG;he$EI~~HZbWkdFtFWi>JoLa!Hrh2T-f5yra?t(*&j}1W@SUjuPKW+4v(* z%u)H%wl? z#NX&`xHDN-Y@~bM*z9rnzu@q`1Gg{S##%STgo+&HZ3TWvkT$@p&vad7HQ`h3-D=Ed zGp))TjF#cd@xL0VR+bYR%SPMYZq$lX57+(I#mv~r=2`Q;+R_2n_PlDJ=$Ms0tgrK% zM8W2j2TQ2~yx2~_A)UO3?>%=GEzVuHVD% zV0G*rEO%CwQ8Z+{-Eb=koj=+^u9Z@cKHuhj+41AYGk&#})c%vAOw%3Y!W~P}!5!GD z8@YNB=*r&Wn$rSqbb!OIyaE8iQqjhCsB2)qc@@|m8`1r?VGt;q7)D7{W&PLkGRd6334XLOThP2z%Z;E^3($8YE3V?E z@Nd2SxIz>`j`Q7$5>P#1Bm*|WLhPo@nzn8*WbV$^y%D)m?@4n3!Vpn)lSAJvZ#3ds#5wlVi|2I8 zn3aZ<)cD~0_inN&DePC8XIS%N!X{m9)vTG~Lx<*5=tZIIz_CuIb06G>HQw#`%_!5C zEN}fSWOoGPrWW=b*pjaZCL&tIqhlQM)~dX8af!?Hqi=cBOTAzyvb1;VOxbXZw@Y0G zSVCZK1Fzb`W_tj@@uiI;i_M9&iR(2pFy9!p;(o|*Z(DSFO3J66urTqj7tX!8X5QG5#7?pRML$dw4^Cdg3X3LfVdwGV0d1BCdf$^XnY-xlooJ?ZE6|zp1*a zj>fm&9}4_9Mr3*Y#@^*jEb zt!#D`-8E3Mi{A|{VH}NDsiA$=BhJ=SzoLsqA79w> zkbpdUm~_!|C~iIEbH(;gA&6Lqzj-T%Icz&L{MLTz^hD%y$aK2$l1C3Za}ueK9iGQU zbM$W>G3*<$j8o2X$+TVn&_q_0n)HG?UC=29%Lsf$y}%4C@oeAD-`XWMB@}>OA^mWF z&eL>zPfzZ;l$3R;)8y{x`I^*$K$+9leC_;4H&Oi9>W<;z{>@T)C>xK@>Gb+ZbM}`A zfA+ETXNuU;2K5=wVl3G1$Ew z+mfI5%Wc}9pDbqz_DsR)#@S$PRF~g&p3*q3lXz;6S*j|=m|=TrH}^44pAp}(>megu zs>Lh<_jk!5+p{*G$+v2CDp?Zjp1?w=y1RwL$^JW)<%4v^YM-uWwx=40^Dckg&b-6F z@kQ*4$UCjs3=W96mC7p+ep%(xDooh;chVUZpMaMxRr4@W`7gbS)9+!5Lj!B^F_bz* z=N|i#M63(ld%Rf8Z*yl(g%y3#|1AcODr7qva&GHX5_4oxwsIjR2W#7l`vR)_z zezziVi{x;X&B-b3`5Y`efcrMln-)n}ABcU2@t&~`9%^ID)l!Y(T)opfTA~y4wk6E_ zJtVT$`2YzCTA3zK&G1Hi$nas0yo_S|z4cU}@uSMbf)4x4Z1}<6jw{$a@6Hay9WLx) zHuUYW(h(DU{22GQ??FiAW#$+5Dbe;jdghlMYvc>=eajw;h|Vc|E9a9Jw@(aP(&}zm z@|rAd<(ydlukH4x;zDNA9M$-K={vlJxA#FenU!T&)N|BEBca_43k7JOIcH1KYawBU zBrNEIJ^;~1kF|!j1hDCB=h_F+V=n|WC^v3YObtX^bv!xM+x{fCic;anqHkkY7S6A$ zqPSM6IQ4SB-i(OX;9R}f!5vuXH}`n`)ZC=JpPFr~j^!M&;%m<((WBB0uNxc5-#iM& ziU^6FqcmU2ZpU!j07|c-b1%d4s7tqFrpCU!$jR)f*KeYxKBVNfGhF`Nu~$dofkU6L z$>e?9j?a3P4t3P#?KbvbN9mO<0W+bY8wNt9dUJJsX=7!q9F0y}*?@0*6OJ!U zz3;`VzMkj2HJ>oPKZ??}U9u{Uzug7lyh$6@DuwQH$7=icn{eQx(E=)TYNCUKMAg66 zCBaAADfZn?Y?b2nu*bzHPFaQ1U{(*et}Ku;_c)L&#+WQDFXmiMgm~p=2gl3$Xs@GB zo)k4>O+xTf;X!ybB1U{_;7BjUe&07{PNK$_xW71>Nw3{r3A=66JR|V$U_*KhA4Dnt z>i4&&Mze>A_(X1rn7aSVO7^%4oi(sBTCP&n#>_j=)=|CNHP$C!fOJ9+fx`gm)s>L& zHBH9?XJ63PPcO^#X;+bO@exjwljF1`#Kt=7y`667%rw?a6*8DJB`EwP#~5DMZabpU z?I_{g?I>D2TBOiXChOI$=;t_&_M6Pt3zr>GzI(I`#S-S%^k`kbA ztzvCA_B^`qDTvz)nTaA&ut+KLqoJcN^#(;&EeFilc3V7x&f;pI8e z_qB@Zz4h)w)?)?5uK9B)AJghCKc}|x%F0QP+_m*xe%(_yO0Zey8?Ca=PsJNQ@UJ=K zi-{d*DyL2K4qfndov)@;d{b$_&J^RnnQt_kc{zhuPPKd@ucLEUwLeHpO=PB)dS}?; zS>ooFzvTC+%gBuD9XhnS6@+A(Is(I-8a*${W!N-yO+LovGaiVkPTm)r8+N1zen=YY ziShdW?n6Ta^-Kq<3q3ahk2U&O*JG4&SLh?S3O0 zoW?uBGina|IQImK>1GoCmxA}1jlG(Qb)LKz?tGPcTJ(@}%KaG5L$#x9AwTxbY|6!E z7c=hAHp&5(hgm#fkbcR?E}eerTdrw(WB9S{JA?-?F%EyXsP?jB(Z!Wzo2!J8r|?qh zESyExbYPg7F3B=&k9Y{k|Gq9xy6DD%jEOEI0o2v2vc>XNiAY0AlCV}>&z4PY*zQ5PWZt7)a6I_+f!`1PU-F5|SjG}&~ zmFYxj1!bpoF2Q!J2pK!!G%^+MJ^l3~*izo5SKOWvSaI3)fVthqqob6yBR*onX+vjj z4`tC*oIcm9`q{nmj-9GjgrDo^OC8eatt))u>XNrBcw{vHXd6AES%VzC&6xrbop65p zSq(qla3|KgBbjFAymQ-) zs?PDXtDRZT+1;5k+8ZU$6mmc?@$0bizfLjy4;^lz;aPBn!GD!VTG!YUolRa9#w z`b5&oZUPumGccp1MN2+VbQzpvz@}^ zLj4&g{o;j1W>sYVL@~0LSyf?CiJz18lp=dJ)^)!myf;p&X+OU9$Qdu~`MRV3cy0C{rDr9O}KE{}N=*1E?Xu4-mab>QF5 zsBOIi&8}XZAX8>XJoH4y`l`JRb1Z%X-5m5!;Gj@Y3u4GIT3DW9?rjK_%u3_8I_ZDK7i zJ4>kN5N2*apB#z2Re&#-u;O->xJ9>o*4upY#&~x+EhI$b3O8S-e$N=a*EK@Asi@7; z%BDkU#`{AiW3+6~8NvW7pkYV=JAWU`9Q#LQ`Yp@*!^j@W0lVnz_Bld!w|A|g3%B#| z1SjG?9fX!XMc>u?Z9tgd#~LsChwI-eI2dple#D!O3!iFFzF+2C9b_@pGn>-zE&iZI z#fu&bUr!ikvaregHu2WbNCTxk^|sQ~5 z7ft~E>A*(z*9Z&BF>Gvwx<}C~wXew*&2A4&Y%#UbQ6N7na>^&39GT8mVv-TuCmPBZ zt_H&=z0SSv69#3wMYgyKm}R1h)8o&bMW*|)0fC(NmN+A;fK@2;P2P5V-gW>mH!XFb zQVmh7lSnt9SnJjuCMP;Sj23i%O#>4;t*Ma$ier@~DeUJE5^=#`_Cv>D+UDRLi9nCl z#ClfsecK2fi5hXL$x`W_m^}#__Ul$)^jOcI@e>0N%||~jE*W5|*iAPyYn0kfkq&xM zC*Sr0WXtu*YqG4USC6GOvN{`CB^C54>sg7g4MtWJz57LRFq6(0X}JkAd|+!aas5Mh zw186PYT-2fv4_*4Kq>hH5g%`JVRU-O zxLP8e)yqATFg`z#B#SDlQ*=@}|9GZ%M|3!LX7);lU-Wo;elsquDep1{M*mj zofr2>1iHYFXcWJa7|Xvc>Qgps$mI|UjOXo?DsS0SC!D7)4R3{;Q(jr3BCX}#G`Ls5 z+XhDSv)8g^1Dm3!UR9>NAWqJeRUW%+21I1Idbe|^MwgQwXT)NJ>XjjVPJoRQ66 z%*-ies|NV#WcvCLMGRL^>YrhrMSf&X1-~ z_5S0vV0?gWIRrpD!;2FMZ10j5YgMS|@zHkr;O>;TK&S?M9x+&+sL$OXx^*gWI^vc?9nyqfRZi-3L&LgJmQ**_`Q1i*L@Yy+_@Jt#qdX$}n_@3^*V#NJuwI zqXS5Xba&@_d(L{-bNm@31!aD4C0c4A-7bFY&+c z?Sq1jq|9s2MZkBLKH&M&7``;Y;VB=3%%-(VY;g~_h+&OI=l=3K2DyVx@7vl3-k)Kt z4oSo3ZlkxZ1$#1=z%`h8(n+JG9FMkYr8wg0&M4fyL4o$#yEX=i%k+@9hp8de4~1^C zZG#JeL0M;QA<#OPfC6(EBRP2nBOr$$Ha9~v4$XbH?YBY#7F?*sn>zjon1s&Bx{b*` z=@!1#&oH*4G1~p;bOtQXJ2qB`+8ys0r!E{oL6BtdfZYs z0!D(RDSO#Lqt$akylodg`~Eisj*Dx_a%K^Mu9;)A^KL)QpEK^+nsI5)HV>fO1k|SM zH7Y`!*NV5!?FLWs1oQmU+}_qjRfKHIb&Y2zA(Te4TYOFKtdC3aCz3NW8w%9aNK7^d zCqEY7-8>9B4ufi1T4dZ2tv`WVvu-D)=DUX6FAVOWfqC0JX>@)Whh)+i(@X66-nFIkpG; zw0NOmt#|t+%?LVMrawJT?GhceUXmG;+o6lmNz=TXoO;H^l~}|TpIF4y%Gw%``RQUI zX+!QHV1pPlGE!Lms%=AZR4`zFXii$B=;S2#&V)Z$h-i; zxh~-)a$_cAX8p{s(Ut-4hoXFYxj2KS-MkhJ%1@-#Z#PQBk@qqwvvP8NU29ny_sNnL z=BV6LM_;QM;MIBVFevCS&ed{rz4Hbh_8>iQb$P@uw3MJM6tqWbMwlqhNq2ubnsM{2 zO8U}rPaFwdKEskbSm&YiyS>G(e@O2B!2koUD#gJqEdKp4<}|O|5iMz;dllV4`)WM# z_YKyGM&CDbg)Q%^HQ}o>YkG!a7N%Bl#_36FmW?KU@7=Riv*L#FG>=IKr2@QsM^XvO zPY4OL#BfA9fgoJH?TV{7_m}NH1ksP1`ZOd%L4R8ueh=}|rI6IbtS5n}9?uhr9rc zGOmb=v6IMUNm`;Z9+@jrq4ewHu2g>MD8xdJN=?fl8C&73KKLR@zfF;0`Mx2S&OT%O zQ~kcb?)LsxOkNO)ZPjo5+&aIAJSQxRD2KzOK{a^@{_R>*mI5!3PLyS=S9v|=hp!`d3sjn4zN8KbjnU9R`>8J(-bkx}b84q>RuWb<{y2D09 zUV4=1hQ#1T30@-k)wMyw@We3jr$MV9+%dtu=sGEKM-rGt!wL6FDp)1O9%0udvz-uW zodTmhotxkHzH-FitL3C_2tWV7ALS>!&+kfc_1?`77kaSMub*!C#LL{{3s;Uf{JybJ zk#mO4TcAU&vOb3wKcXQVuD0e?K3%(6T`(yzARdE=qk+&$AH^i=duKylSW{-^KS1E! zr{u+%Dd9t!7}M3CfJl;{Sfgk~;a}SDH2ZC{J$LQq=8nH@@~4-300% z-|jFcC|_tL*ngrX@@853?)V-b0{KS!!oZT99X{1$C-AjuPX41Pw$q4ZM@f{^5{)U| zMKl2x+=!XG25&;Fj@OLwX&TzWFXcBly~|Ts;6p+!$7EFW99_xkEuDe57p$bX_~p*X zUBl86(SBt9%(YBkJUHqPz=>>zpH*!D$vX#^Nhs7!QuFX-}V7d9R^=%@uBfI z#qJW)joocwp&0ZS?CXnt|NdF%tyj&Io!Qrl3YSWF-J#O`5k!DDBy|~C`xZOM3~8$! zKrX549CrP7wBCUMRJaNmJ*0_enAJVp+c_pq?ohQ|#)UB^^*pNW z&wkjemcd2KHYqVK_-1RD-he!?(XT{ZYuFhJwB_{B@p!XpF_tfGw2NOJNB^1Hu0-I| zjJx7^^(y5zs$wr|Tpkx#CiM0AHUwRDY;Fo!cr_;cTE!@d&ZT2pn`9BHQh4id_C=rV z*2QJVbLo&Yx5*}@uoy%9mRqs3p88zx*^>-|kHat?APE7OLE;tIk z5}}D#eX9E?`<3shuBS~)g6WI={+T8DS@B32kIpesUdGH<W($+N8+O%-G85(b#Jm&uG*;c0(qe&zU^5^L^<;c246A4(*KG8c*u z=JTYPs&vGmJ;Q}5h@1#j1WkL&^MZKI=C=b8+7vmX#R}TSj*or^v2)$!q5pc3QAe1# z-u#ddoI>R7rrcJlcv^E4h@RU-$2936=2Q4_&;YIE#FS0$h>uub(`LgBmGgX zIXI`Rk$(mUBkeo;&(BkXFa5-C6IgD(wi{Hgl7C)mKch^SXp3zwSFpx^z&e@Of|Zmk z=+DeFIjxnE#Yy(tn}XCq`hWQ<_+P^R`tM5nJ%PDbdlp-vF1_Ed8FYwLlP&Zc_VFN; zS|ckyDxJnQ3q-*a8pYyA5ifNO%uGm3|MJCbPy0DXFg3}P7E^I*o4?$tjPg9hSMybs zd|6XYnr7G=?rg8h&VqXOShd4|^e1P4b9hnn`14PA3r8`B@A#tCG|}gZj??!calaTW zNSx!B)ru*EOlE31(_^KlrQR&1)=>}Tk$bJrXwl^4#e7bZ1{SZ-H-de9d_Lsmy?^(P zkfTW(3590QaaXKV$F}P~Q`Kq#x5S2pN_lP1l!{$1NICAI5rdn5vK&@-Z&Ebz2(BTaN!_Sq zO!#SYK{sl@dh@{ZdR`~2bSx~=ztqR)_kqa3usrL4mh}o1`j20C2dsO`I(_~oJr?lk z$+Oh{Sjl0sSV>FsaE7>EqC)6g@qR1@+u6)JjH(K*!-dKsx8pr?EYIzG&Zx4+RbhvY z)92@xu7_jkl6@_=tVt`S=H*RbIfVg@ZT^YKabLWoB~JR*ej9FuF(~ zr?M@Raspi_@8@@IgAL8lD(yNP67gVVFxaD1xyZbvq9TbgbSko;VO%vCbs}zzoWEqy zwjhbH^dz=;Ik-e+jh+$>dc$tm$=OJ!XDark(q+!&wYwMb)>{WIL{rS>IE(tD?No^G zj(C3}#Zqlyej_-~L$ymplvIdzO}(!|C0M(TEK`+K5dK9jX~h^g%j1@21Hj5Fc8YG! zHasgY0uwsE4pialODuNEPDvIu$<2NN*#ebA#KySReTroS7sYv^*vqbE0-O< zpocs5kjv7l5Fq5rZkjradXqyHQ*ET$vb%vxV#dVai7%Ir>8X0#bA(s+Bzarau%#Lo zpMNb@(8B8XuP`~1x7iZ#Sw}p9Y2SOZNUPD!*}rz~DVaBy&h2fga5+?b*qA=#Oxy`D zHUW8`lDs6nk-m#VMRON)v+{nM&#!sb0l|PjygBB^0d0u^P3@)w5-8XFe~wQiaC`s^ zx_EaQ5EOu3B3~6ZM<_}>V5sT&+vV<&`nO94)5-NmBfsg}g`RUOXbOdna?I(gA;t!9 zbhjJz!i#0YKK%-yCb;t-tW!%paKAc#Lh!1I_Uh>lwhhpuB**PQPf8}KzDdY78e<0_ zrYxoK5ma`j6a|G}cBj;c^nxSdan^PKyZDmm6+Dg5Aw`Bfo>K`xw8gd#&V(vmm1b6H zSC{A%+#~Dj_bn2XpQq%e=$CG@3JNCoq70=$qtjE%x#kSHMc^eebN+CYGB#JbQMZm! z)SU44t!oeHLsnKywppj!Ha$bkYRl_ls?W5D@UgBywpD`~6WN3&%@rRP(hU9Xa0JcI z6kRlZ!vI7(%F>MRK*BqNszas;ty1qQbsfg~tcvEmO{ih@S?@%lro`A1X9|<+yj`^A zPuC^Jyx4C8W`DGrCi@8KQ!HY$^f44a9_#5DK9kEZGcY!O)(q~K0q`hyY(aj0^7yy` zhj$Ljt$1pRZFkCxZso_Fc1NvMrKgtGe3nw|(4F)Hg};RpH#&qAc07U_nKm7;b)HQp zB@I|a3eV0zNs&3mi)}q2PQ1O90(w(W2f2dob*|oSTjT7!BE!-6n9j>ily$QhFQtk$ z>Ma;1EC_N(^;?I=^gc80{?QMgFDS3+22KUVr1ab0rdQcMCNr%z&i~VKpumB^1#8?4 zeU(3xnn%wJ+`b6vo-gzW2ilB`Px^icwKH{?fp0p$gA6HIM4b)?5>&mMdG#cbSCk%8fvVjj);PHBCQ}xEJq690)ni>m&lQDJ z^$q&1;+mSI90Ht8O=**sllCD(i8_S_9BD!cG9|u2>gu9@Dx@Pnuu>~+#!UD?)u}?e z=K6 zTxqmj7vAN-VDT-%SIGk>=lf(xXrRW{99Lo(=LX^&ui_~>SlTR)5|&sv{(~L)+hxSo z`63E#Nt)?xGSF-5{4)Xqv=vTX_TM*iPr?!95kD}VW4cQ_p`_^hsa)a$Y7#2|aWtzg zBc{5=58p@SB<@23FpUH}e`!5zDcq(FO-5Oe5YY->0El!nz38u$RFch0Z%Rr^!|xFg z+a*1sM9{Mj=Ad8SaL~CY4h_RmQ@K*B&_AE+p9WZDxK=T};HKvW@OmV)qgHoQNNcFv z&g&K@z*&q%q%5-;+5}e0ZUEnrVVi988Greze2?9TC+mgX_Y9#L{E@==1&e3DPVBP? z+J+@QB@=Ji%I3@E)rlOWXL9fFBv}djHT+T=+HVBUboI@vc7-b{DpU*&DgP66jG{8V zTPTzQ*g~Vklm5+(Mf2?Yr;ny6f8deUsM%d-Sy{dq z+TtekFT%TLS(W0qBfZ8k-tuSmKL|rpiTJ@qEUx19g%+f!(N1w74UQl)l3w=WmoG3SESQ_Qg-RR?{4L+Rk ztY%+J#xSQSS=KAgtSa1ks}REjy#(ce4CNx@iqD^aax89aP(P2%(TaPDk36K}aoN^0 z&`WW7o<^Ew+Rd>zIgbrRv1FQ<=|$xVGwtys4Jb2#GvdG?*Bsjj{tVqmf!6tnjDRAW zXx)V)^PyX`{0o6szjMRig79~@!s`TQ#0lMoa@_9G4&Cf8aI3f2)jv8g@o1e8k`bMw7Oy?VUHvM?Zz!jtzz~ug~02w>DhOmMyX;PA&!X#xQUq=%LNgqt2#hA~#vo>SLQo<}apH-9P?+1sg28YicC4QHgSwXcLw z#XT5>A~v7FpX%TA@3m{aerG|&MJz%M&~72j zvQ_@!i-p7&DMX4Ci*MO|%0IfKqw$@=$cl%NMyIg+Jhl$VNi4HYzgiEo4iOTHBL_Eq zM_F{~-AfaORX`*VuXhWX^a}~~6xyB03Cq6PJ`{Tmwe@)NWPY@+_SCzs?o7}voB5@k z{s93<1^+wd&x%Uc=7#I~d{lEq*GKQUcTg6|_7cMmgEF*|vV9bdcZMVWKFZTOql91| z#bl~Pv1m-%%lRB(081PbCk9?7sPWg>Q*=CYmnr3M-+tEa}@ z`FA$LE2O;W5=(=eocu#-YDXwXDyT!0TZ4f0^U39I4P~l2)xt-0-#aleYOVBkF%mD6 zz%UkL?g+@!?gJ^L=yTI>6N+8{I{9>vcaf2^**jsGWJH$c2F{!hFZVTm38{-mbjXL)JOp#=gKM54vKWo?xLWZKDU9142g zZ>(}sG%?_8;&WRYsv+>V{4Ue>H?es4vQ+Jy#?BgdC|Fh;&Bj;i_ zE-7!u9kKROv@q(mwB^W`)}HYn_cMDj=&($lpH~}>Jg#_nhOVZMa3s2*-he?d{k6oJ^IP0 zN+YD067CGYZ52%0(E_qd2N2Qcyu!&D566#qhJDq%d)6G+Y&|0J3to$atH}A74A>&1 zY`Ikq)l%rEUGwDIH?BLo&ZEu#B}pDGjEt)_?K6OA@-e~982=AzVt}0!n#>qRAa-Wj zb^yHVjn5J2@rG1*uqv}AtrPXEhW4xd2!w9Gm;jt-BpLyiHf2Phfm66`5bRNKb}TTf zvPqx;HVjqBX=u{)RfxA$v_^=xj>$yocmxSmITB0PyEVAFi}Y{9x$vVaYCf$6WK)kL z9TDp%O&r?Am#qs0qBff=Db%*}O)Fe{6|X3{+z7x)iAf%_B~5s2C|MPi-uR@HpS^?i z685f%-6iXuB#A1JP(DbXOquTBK19?z;|BmLC{1_>1Iu(fP~TLM<9saie=7fa;%loa z=TcNYwQ2}HlOjti9%2p@K-M}_v^#qsXnIQ*Al|SaKYj??fM%615Us7!q4#geYip?> z404vCjRq`Ndk+%4d)faa&MaEmg}Mvx8R`PxKwMX+Sa^i?{vHPJf%uR{9%g=MmWa+C z(g0f5ZTfY-;iJm2#!d2`A|QLr{O8kS0-ru)&ARH3G!Dny*+1^#)PWiy{@Vz^|J{xUj6<(m*%Z}Gxu+RADSz9-c92F~UJC{k-dZ6@m|k?YR27M@9sU~=yr3cXu8 z6%Xfh0$x7j4iuy}we;niUdk@k5;Uu7?1oJ;yqulN`1oX{w<;<*3J5MzSefck&JG`? zEq4TJbMw`@F#9C;UK$iiXlDo3%x@tyGiy2{>sM4uun$e)6#>kvn=X`%&Lf)2{MoPK zbi5sYId_&t{DmUjsCV%V{^+pQ8Nn%pF*W_+xPcxmGq3jn<3WXy`b?IMvyGzg{nR=2 z5AE!C01>$0GMS;$1&GCHBs`Dyf5Bi76)(HqK=CuQF`uy@ncaHr?%`qi&&l#Hr`iHO z`2RIWhgq33HAq#oeYCze7FyES<%Of`w6jjC2rc#duo49KpzGk$KIS79qQsFVWh0XA zmv{F1rAL9y;OKRSv6$Bt&Y5!7XNQ?@R7UuQOd8y-?+Z)B%$IH43FrJ{9%wJ<^C__v zt`{d4eQ~|f%M}_K7rKc5uS~^N1T5bD#ZR#RtC6^vLQ{>kzE14==67oYD95~3$5)f^ zs~i8{vZzNbn5?99oDY#yX*ds=gvzBELUFH5i5@?uV_(c4;h0bJIo(GZcxG^{O|mkl z*f{e6nl;@SES4t_Ov|gqVPaMk4p%u$c&4mqBx#>@)i=Z~Z|FBH6NQ2Wg7IPX0bvB=C09D&M>umAv@oeKGYsE10#>JKppZ`VI zDuCv4{E6*WypF7);y%8<@l!ks&e-j`ZBH>52!D6y>}vK-wc{tngYREz_c%%yDbIFdAqdk~lra zR9a+1-l!QfSjal$XSUg1pfG_!)w$S{i)iF`NnL5kl{CUfIa=Oj_1<&e7r5Vv?J#Od zi=ZQbTZC(?o_)zKim$H~l914Pcs||dXtu*nR8kcnAgIK1(_E#kF@b1Dz7)?*ullPfdJ7)aO5D=hWYy%o;a3Gs&h z=;>h+7XFB?@12X1U6qyb`9U>SQo&&YI!*%^)DK&rhHc zb}-@ek!@;rON#wUr@HLzHBedk0W;OE)JI)#B4TkpL)pY^=VhS~xy&0haqA}yzP?5; zxIb*pJWd7nnsZlLw@+_h-c{5mHyghlA}{6jJiayTaWv$zbv8TVo053LEY*r$nAjxE z+X~<0tI+R2(J7159#gPAQ<5h)*(TNRdwKb7j=*5@PdO26!MQID58U0|Jw1vA;;v+* zqeEBfR{HhZ7SQDrls&%JpSNsC0N)nc4RFVxf$&lQx_X+FshTv|Zt(lJ8J#D~l+>JB zDFD%s0Z57X{~;xfNCT{r(bJ%PD!881?3D!l>62Z@LQ=jil*HIy-s=&YwY7rigkx_9 zlduY!W43b`f!-~{PX|I@$UE5-9GQfUaVOjzHa@0Tpm2jw)1~im5eA7n)PlNA5h|Dc zhS`IF5CJ6(_C`8>YlHN9at`*Z*U=;yoam`bej=K#PGOR{e){qKqB|`tpMja7{q{cL zr&@={;2;ZcOit*cB!6sc;E9f?^=nI>O4Lib?c(xg5@0XxYjk*RSj4SB-)5$XMRjv5 zFeHY=lO=M3#1wg%rW1yW$G`&&KK(M!&J3eA;c0yWyJn|gW-P7y!6YDRS%c# zmJlgy#BpTA*V}g>-Te$+^xYtSnf#YMAceE{ecFd?ObjveppCtcbb%_2wPSgLGANoa z76xJ!5(=R>%J`OWFnu#f;Gu7cmnh>Uz#WWd1m`pyrLX|=P+WQrfes@!?n1HEkTx_r zgcI;iT#BKPldQMo!x?57$s@a*b#;EDWbyPef!w)yQRB-zL@a z0dX~9gk&eLncW^yxGA7uHV6=*oXKxfpuoysIVk#LdxOjLOst{+X>I(E7@Dprh@f)O zGdAJ2AtkR~)xw)+dwWOEz=DvZ-A1_=Y`**4?GZ1WSqIqS&Ks#Sf=W$&jHAt6qH_}y z8@rMsN36(lmEH&j_=kV+?NKlhF?g0E?^CwhQ{9^2dzSx}09yb<_2}I<+*SLp3;@E3 zo2AU5=fMKa>49?s&7d&729T4m1UR)`59?v|r=s=0xr_R7 z;~7F;Eqi2br|EXt;zb79idEP~!K=3M`v1tLus@1g3CiEYE#M~`2dd7_JC^W@7`S9( z;}p^XmtX3#LrgHgQ3JsWl~TormTfQCa?^FAa%WVLq6X1$;ZHTt<;$#?i6IR$>#&=* zYa7%{U$U_M8LE3e)g%kHE#a3k(RwG9>*|@tY+}*o-mX068&*&;)GD`iuDq=)biSad z=A7g@O9N8m)2B~_nJDq<+0#>J`rxaz1Hh-TG&c_*jDx$SbuO=E3Z3nUZ>>IM1)|X; z%1h+yeQ=CC&5%5iqJa6=N_B^q0%~Ucm0TKLra!yM%u}Fu0_hUxm z2Y-=Y@f6H2cS-q*&BMA`F0(S)oRre>48$A2?Y2jkLxVNZ60(>jfjq=xhP*~q(Ixw_ zL`6F+Y<^bn0atxUk0%@z)7N(W?3=68vnG&=@s?f5`m~M!2=!?2`cFA5CEil@N3D1Mx&$?7+kO>C`dK2x8LZLVai4yn=2ylUrbB{t*)=t!aZ>&UIGDV4w2uc!)ynSC z@Ry;Zb)*5&Kz#fPX1>`c7pn}R;Gv&GdQw+|35SWEdhUz0H#*U99A6g`U_V%MG5{!7 z%4rvuPn^dNcqR*f0diGPyQod7ThPCP1vFaT|E*U5Lh|W2Z6%SDIxHOh=`fvkH+L$g zsA59rx2aT|_mGVaUgxc&`6n%*`my96l1 zH_wJg0rGPZTdnMlWu42R(15gkwDu~Q2nv#`@=P~-=ajbB&&?w;7CCgPq@L39IezIo z7TDTJ+rU+7 z?m(eA)4DI9!+=Xb5;oohv&p$}jUimF!TPo<8N@CH2DvAoh!}Gg{=gs1?9Go$M=YZg z(nTOXpy`l31`E%n1~ElD`!AIU6)mmdkWN%bhrF_yT1`W!q9V(?cf5lBypmw*1qk74 zU|ct<{uwJ5RlTEAby&AQ2mRQXdMj-|AUdU{U-`qq6^kMvh0+)FSV3l<|KMp{7X;&z zz-d^12r)hkwRWSl;P8D*9admw;m9!$?J7wZ78(EHQ7|3#jnb4e}>|6_CI4}qpvp)C*lI}%7cZ11!ufFY;@U|GC4 zc)*9oVWIOz5n%PbKU;v;vQkY@ndz8Ay$CX$ve2xYnCUT39!L$Q3BMjLE^F_y$4n8!*O!tx6A zArRx!DxIIn98;6FQZ7ZR=a-n^uK*_tE&z0CjG` z9eLVvrotfDP8jAaN%qK-p3O~6)D80myGeGYb;{)n6)d@CoDW$OctE{0;(mxRijW7E ze}j2cQVlAA6^X-l8_DH={XY%T@7Dvze2tYAP5q~^(f z!c^F`lsL__O}iG;*H#C-#NiI@l^PSDUsSkN%H2W|M3ONM7eru83=F^^uyIyaW)t@i zk5oPu=|u6kxy^@sR^{93wsqc7CPpog$i2(BX^)<00()uRTj_Ih`(+W+9TG&bV(9~Z zjpxjDX+FvUl#wEHj_0UJAGafZ1N?UfW_~OQEz0q&xk>Gy593^zas|>L`g57Ke&lB? zjb1m3-;8n&7T;sx98RNu_W~AMD^j2xTkA46CNBz_w4U5WH34#8q3=*McOAM$%-*$A z;(yv9=Krf5ezZ_5E<7EG!YTnAfUtf;d8Xe1E05G=#k9j=)a?$Q03LbNajO5R8+r}K zS0oc`6;x_lyujG4y(_@TzK`U^_#`@=U-(6FR|>P(2m5*1CWoOvRNGhrq@pXO4TM|* z!WA0k`l`GMq_HaRFoAx&QoKU6aE3ECc}AHBr zRonL~jQ{ylM++6l1(%nVAzg=%zpJmm7og&eoyxPUeLn2#!b@*LqdNg)+B)ra9 zT&tv+#rZ!CBgZEvscmg-zcxdCk5}JnW_^n$3Pk<+tPx7<&>L(r6*=zWiY$7$zxb+-nI zyuV!4pK1!^^2iZfJ5fE@S9x-#z4>KbWc~n+p&bVW&;Lz4>M&f2G9tctD ziv<~C8@Oye?F$1&&VD>m;7&-+dYWW|*c~s$n-W*Yg0J$`ZXgbdP&z5ZiI z@t0#s+;IqEV&1%%R$j}^3*R|U9=WuO6T*O$J%(ZTtOMt~0=MLXKqm$-w3Q{&I_b+0 zowB_FD{m`^w6jY0JpGyj)*;h%FhYQz5@d2!G}O_G)TlC!Bw&f6zzC*VWS(b-;YK>qM}z>ATJg?*viA=;&dL z9jQx8o6=k>r>5=QyhoG854XCu5@OrNwSAR#c>>QGN*Ks3roNwD;$Sc3A@Y%u)+24D zR^UE^5=RZKMi~+ML6i?uTIRo0i7qHVYRkD!Le|c><^8DI2o2hLkeFBj^ChudnwdS~ z11RCqw3AC}etrO8hEu}+{Q0w7RPp5gb_b3BZ7&N~E7d2{?~k({u~89poLYU_npder zne&j|nyFy5H+35F=r@4UK!4M8F{&C^-t}0Ng6bsE@+-Up?!~7Ns<((RVz+!MyuE1wdgI0i3C?3M0bZs^kM-=b{N! zlrm2&>jP-70Z&tny4&m&3V7`7_8W@bQ zlPN|ZE;J0uM%;u#4uPzm zA%}5@`=vV~nn2G;p^B9#?>S8lV`l(61pc7x7f50nu8?=ovMe9Xuqr>j^`62l1sqdS z4&o!^@y9M%E+T()gv8p%_>hdxhd;NNcx9IdZMNp_o|@RL$0g-^se}4mMR?DSk6%u% ztgNhoI*wMHgq%@|LWyA-=0OW#61=U=T#jOB=B$@OdE=a_`Z;Oc?J%XevdamJqgrxe_cf+Btjvu>_1m``;G`j4!p<5VRst z2*NW+n=()%E6w*Kng+Lj*flxTnU3UDwQ!(-1Za^m5cGbZ7}Wn3psf&e5AH}gD)iY0 z>EoEAN_Z7r#rQR=}2(sCK~rll!3VyQ7+7Mc{Knf5Fhnxwg7)yJ$=9Dr-ah z?E9-XoW_40K|~m1uSs+D;(iU8$M?fCZ}&QCWjeT%7B?0ZE!kUphfElhYHX2^TLfZr${1+3S??MUp#^}~ z%M>jd;_2A#PC0y3tt;@1Fq4P~Iq)z}YQOyu-wXvTHaeO~Q1CJEAge(aAduk5kP5bX zuSq$Miiel12PZD$x;Srz^xFQb9FXfs9o_NHE*!*j)(@l8Y>B51A!`f<<_ITCT!Naq+-Gbw!_j*P+~&WH2%;c)W}sbeqrGGpElK zByaenuoj5o#3yaGZFcxxflkiCSMv1zNevrrO!(sMp$`!*+(HKXzlI7o@H>w>^B)xd z)G_YoXPrxO?}04jzaYVSh}vVS+`N(yjMMffn_`QOy@nVPnKC%@$1$Ug*0cwZrMdBAU)B zZ}Z83>O9-U(*G3Ro%w*UH;TZVJDoRsGW~Z3J-u&6ov7>U_3zd-0bM`>d-WR=jP?)! z^VaqghdS&wW^t*6-Wdc@`80y9n4(&*MYymr;YV-J;)g59<(-o%QNk+5fD>vbGN@-} zI=lR~4ky!*3MwLRraw#5U^F?-TwX|cKh&Ppn{R1y-!F`u(xNU zUk5Bz?#P3!t^RQCdDWYR7TRzz7yp-=zf6J2gAeTm&;GSes@qeaq(Y~eRRm!^An8=I z;DH38QX0Ls+9CO&WKBNp3Jp~brLoPHfCvdl{mp2DVm=!SB{7R3alm-Y-)gm|orlay zp4~mg;4Tzy1uhZT85QC)f&zq!=Z{o+?`B>4#zMLvIH2~s#g^=#5TX>bnd+0Qi`nd; zdx(ATkNUB&x1a5b5v|K-qorY^Bh58WtAdp2h_h#FUt8mxt!=PcG4jyzKhy{nnq&Sm z5CWxm*9xKKB1!irFckk2Y)vhviy)Lr`{>)}A@2z=l4R+vNftY3Bx5%Vqovdk7nuCB2)Q%ur@EIwj>q z1seR!)BmKWcc;?x>{!X(;N5ZkovIo@TB$0%91hjLgd0QHgZnIb%2nlGR|qFF=J`BI zz~_&0G1R#sN!Hi(>N+*}I_`c6ndKmd;J3hrQs20(pl7z7v<+nuhtBF=u1EI96?Ms``&F!S9f zfq6e1&5H9~y}=cLH$`_t#JV8a!8&30|7Pny?|HPyWV!{Ml0e)~@RICj0XxImwJRzX z*l+-v1#D@kKDJ+;p38SUOv0)_HGf)@>)WIU*R!3OEL%iVI3T}U$k4*Pbq9JQw%m4X zo7X}@ea9x%ZOK`*S{wSIgMFq5%Ce#zzu zdVD(06{Gt6R+AWlqppSrygUdFv;c2%IHaCp{*`=(`~7vMKCAK8kuBkNVkK&dwR@>X zZ;PiQKD{)GMF_`~iy}0C8=6HTp~9;fe+nzx?%U+I@|a(rAVoR& zpH?5?_x1J3zkbbA4B;jtC#OX-jDi2u$?LX`8}^!?re9cCI6OQI&&i>`y}dP|C^(n# zAHqoE6tHQ9dt1_oyZ+r_fcaYihK9G=mR*WWSCd9aZgK#(JOGI8LXP@5GbMr>%$@=y zsAArBOU`ZYF3_89{x$UKqye2ulttdrd6 z2JqULy&;7%_6BwQ`b2HO3?Xob(5?2oyr{s1`!a~egM7On#?HTzjngY3#v$*yZqn)W z-))>;{*fT35!ekPBA0V`+BRY|}0k_n&D5 zM3(TO8DLZ-5UA6sC8Kh2bm^wCpyJ7k8C}w!up&?pdurnvyw)apgdkQKv6z?`a9c_l z7*S13O8StNleFR_@V76kJU%gF9tVscsj94u{Hxte{N(35co4F4m2POnqT{|>&8z1W ziy<%9w3NFO;W~aq*?mHQrNSpU7Ew(bEg+RPF`7g%{i z{#HK(SpYUcExXC<6|(p=>b&Vu-$sKlau~gP_Z5BUK*;T~Px{5gQ!&_`=5MbF0d|yMC3nr<8+&G={kpR7|WbXCv2l_R&Ry+ zhL-&Vr@SqlGz`|3GOoTa1jw!r#bW!PwgO9{f8b$)-1Z-Ou%yj|(8adRls$qCoHa~* zl&!G@EAw=Cp5w zsJDU=tx%m8Sim*g^u-J*ov~?8?#L$n$8Qfq-c*EHF>D*`Ae%-0V47l4=dd==Zjv!o#Oj0UPoI3Tm(`X`kpoPhyvr#j4#f;5oxea%vUe5l3EcGI;A|5(!D z`6C|5fm37;$<68tdUJvHJ zef|EgnnUzb-E-^SQu3e3f}Dl6n?CC|r@z&JnS6F^4mP`=wsu42-zdUdZ+7Z4WmFDl z$v*<;)uHgT)H=i9`((j~bsO%8=R#JC1*rZe&glyVhss(HYyyTA+~M_|Qq1%aOJMUE{+#?ah?(T214Do+SN>uEv1cdhMlRLfNKBEC{ z%Oc!#aKPd@FDKuI<$8i+8iVMxgGyH31#pa9 zIdad#Ga`>|sLjGZX9Rokwcrtd==FT(PN4aWvcte(Q$LW8rDo@G4rlo28*B+TES(b% z?(e-x;ZVOcg1cg&;~TYv?p$G)$1vcsi48kfh3+X7*2(!n-Wea`BUsPCYB0BE8w>st ztH8!tMFkrKUK=$x=!umE_UvQ&JU4%N#2jGZnl~zs-hw^$wd_4?d{bq>mz+z zyG5qDo!1e6r6#!bTx96_giy1>l9c74E3p5Z&hepzN-1uH*(4iL(P~2*2Y+7&IM+B5 zE&MYzJF8r90o6G1gBCaI$l_JOC!gSeHjVr9aF#CSsYD?lRJiG?Dq8?J+4%ax!{{M+ zUve$T$LryqUvepdiR($bdAwcHjV5r#=!XvNWQFZk6omdCqXD0NH>lev_yDuvz6m)w zn54a=K<7gLuJ$zK9g*K_ZVYYfcsx2XEDBtT-{Rhx;vr8X1F?wkGs%?ye>{C>G+d9@ zHKGSeL=8feV2oa(w?vy!Vi3KDL??RhBm}`2q6n~1{yL0O2M7jo>}DhU;V`JKB<8A zQI3MA6AWSY3}qC`!%X%T+g!*D6?`KI$4ELKMvC)a8W7rh^pN>%Jt=8URze5jSGkia zd1y$B2&pY4()SQCWU51_{GTF8vxsYbr0a|Q)wP`{jT>t&?>ka!@x4cBiwQF!BG14` zDdl~GCP9z+XXS6SSA3}kR-ztlaC@duBKDdv^Q~SAenGt`->@_0_PX-ac{PF9zm{OX zz=twNQuZ+#Fh3Rk2bf$e)ajtPl8gbePSaImyS8gcQkW3@{_NOh#)2vo`2-t*s<=VC z^G0rCAZgH{7c?_Wl9?ZyMY_&Ix4cFfj96t3-t5kHVvBo=+tl42BnI!+S-sy33_=1@ z8`nUjQgcG=NlaC^QJK(hIS$2X*~8K;N7aI4zcdO>@}(Tyj~cCJYB;RlOs?~kp<$UL z&eQXJ5S1t;jvK1&XpVJ%CU;XO!1 zj4Z=*J|JhQd^aKGEUh(oAx)&&r;9gHotti={l8Y9Df)I?lYY2fzCGnbu|REMaYaD` zhQ#)E`=u<8Vse(?I=$60N53CaEi}&IR!@&;pgV2|L+>$LP$NELkwm- zpdK;Q=EqR$A=L@r+!fAFNS+~0VVEV0E8JuvWw=;>Q{KKA+p<=Ly3nA&pDB@L*?cCR$PV-ROf~x(8p+3<%6@N-?Q0YQ4B2R5Q1i8xMU31aS zPGk=NZBl@++ju?M4k)5$k>6Xtrpi!R%ryGu} zz&0BoHVJ5MmIy=wBcZ(`pz#WSoSd9ovg3#6^dR`%hFQP#p@11dncBu|s`;aw1kg9H zCnkoc0eM8KdEl1zTH+x9#R}o}+6LM52tka_BnDzw z52&8IpTnHen(p>PBlZ_i4pqQ99h&+sp$%wkrGW}eDx#`OoH7GAJ_u}!AWyP3KI}Q# z46sk5YQyfzfRHS7m#D}82l?jL0}L&I|6+1^ALrsi24@$l&Dxs@6Kl+7Tkw*=hLp`s zMlD)_x}3gcFKB&B(Qe``+f^UjnYajVcMJpC)xWQfNFvLh|M+rj3Jj(r97V$-o%?3p zp``<(PR}Mm)PSKtBk7|QdGqGRkm79Lz=^KH-_%?#DI0!AzPFcyDjy`;zr8fH{JzBL z1LzTyUhtYz{{0JFFhH)6#w9enkQ2=?14FPXGv$CA@)e+@-#514stQ;Cw(_Y}2iXiP zTp0i^4eEHw6kMcw+4Ou&IjmgIg{|k7y7sswq{8g%>qne4av4Ma8DrO;(t!#AEY)|xBAF2#m zwQtbg$qJmt!GInrx44+Ip`l@Mf1ik)oLo>?*yN(=Z9=mLvq0i(OH=tvtZKbST~zlt5V9!d@+)Jo)t~E}dS8Q)c7y z$ObtM8UGt~1?QPLBi6HwJlVaZFk!7+whA#Li;*Su)fQ4uIaaa-)DEO*VtqD)aoc7u zujJI=KhR?CKR{|M){hnWX`eKKh}!IOGQB6soG7`b(`+$|owQhS(oeIC(&UgliTlgB z_+NcXv~vDwdmgu>E~XMRcKp+#kVMwZ<1-STynO_S61HN< z0Y2|BOI;)2`}4>vucRU`?}ex00I!zEaE} z)F|Yp!A&ldBy%0r(c4y=zxGaVf&pK^MX`E_2E*kl2#M?C;v&Gq!}B9CG3@dZb6400 z&#+KDf_?qv;qem<*+V?Ue1rRQfZ97eJq-e2s^pXuV>`Pi?GZEFrv9()xtb!qKZ%xj z!bOEfi+|ghi~J7 zr+mB8=&-peZavo=-7~#ZFirO_Kt^RCKip1hjtZxJeK_$lA3Hm`p%g zR55?$(Z7e15|E@d8)`w&ei|K%^X8l1C~F+doH~<+8t{v0di))CLAZR-^5FchLha-K zt5Dk(iC5+OV(24p#23z2{d#oIu$8BY=`}0$E+jVfQVt2g-L(N_wjY#L+FvCz{(!y8`H%g2`MzU#SZprY^7OZ3H#-7=fKx{ zZo#o)O{0t8F;4~h;X;5CZhx~-AZKBw#GnN!;e7oHYo7b6|@Pe~UCT0X~@2N5_}+Q)iy#kGi4QP^_J*sJ=%y1!yzGLmLym3;toIAa=lFIt&3 zb17O?R8-_35d4_fScma-Ve>l+e<6`whwVJhfPlAvo*#y}(BA&2sHo_#hNagjMbo_n z_v`*Cw7_;u=v=AGjl0)4Xf%J&P`zl=t34r=V@WZ`EEt0*WWfE z)!(`&#*8g=|Chg>Azb)a8r2%*tmcmTg>FP*H!sy6Vz-o=+iPDYv&f8)83`L5lSTN) zRu#pt*$!o~S7Jg8ldj1_2E{^(Kj^N^NN|{h_>*QAmbG-qyuP%Z|3VG)xslkMEUj4= zA>>c%Zj@xe&8=pjmcKpn+8+EVBB#&SW&sdN~krFXwq^aNaGL;xM-RS^i`U|7nTVA z_=wqN{`k0q@L_*UDOC65%xL^At@r6nGb3@MM*wo9Sv%_V7Jqbn4%P-!jT>|R!;vSg zl=4)+xUjJOd@i))z~n1nw3I1i1ae9!%TQV6A$(?XwaU{-W{`!UL=~upt)hXJ=jlb+ z@Vt-m>6xZR8Lm7Qb8az+k#sJLyVE{`OfpxE(I4ei^Ny5`shF7G=KE4LDMEl|mr?ex z=U=?W_k5IxKmy_qlnQ3OiDETV+RNTX)@*Ku`kB*TVnU)45|?HS5hk0XOqbTt&&FRi zgfx=b4n6eY8!8%d0-&|cUN8f&`GXV$egCa8>f9;zp9O_d^@C;CsK7VsoH<~4&+_!}-4pfJscepjeLR!4pM$M6 zoskX{I#zVCVO6C8)o0&{CmH0{MQD9G-bp-{)JXAKfDsrbqm-*v`BH(sF-cM!VRoHH zgIZ2>)qvlO9-`c+BK(&3EMD*#e-+o)K=NC`p?a1de566!QJ zDXL1o62Lom=D!79A-o|{ET%hN^aK;9x!6J&K11wd{YQ9Lv=t@J zMn7jIMoDh>EPe3?>!F-aR57S4DRR)V(iYAIS;WuplUmy$>} z$O)9V*ZZc8{W_uP7&E(=r~8!u6PNpxg%@8RMX}P{Fq$H>or13!3bDuO-L$4bq8pf6HnjYmXr^#t1 zo3Xw`))>KBO^ZAl!Px;L>eLua!pbuL9>?3-&bT(-Cb*ECH>r2lUzy(Jb3$d9@z>Zk zr575Q8!YJX`LBOM-)f0Ve`I2@%FM4QEU6Ismp{q%0W8cA6i3g^{fwKNd(v?hTTL^$ zV`Im*vwFyXqM@&j#6uTgF*k(Jlg0wF!v zd16?bFR>lf)r%Vz9I<6_IVH?mjb-2%{J-4+t8AI_joiX~K&$K-8#Mype_Q0_o%;%> zyf%+7DpvtRH)B@T!I^pozWMpo&Jz(6H@L77KsMRde(Zd zyaZ17+3LKy#w%{Gd9NkoJOoLrCTAfR02?lqH}vSlJEPwvnM+b)9vN{N+d9F#WT96! z6xC>69T6vq&Ed=)Wrs+-ai)u&30>53?3VG1U1XU{Tl{?)>@{NWeB5-JFHHGns0mXc zDbV*+&*?k;rS%r|R_PY^X6a{N;f?J5q3q&#km%~+=1kX`z$IN#dUKR35=Z2K;;$7MU^{`uY{j8_*Pzjxs_lo4<$X+6na|s28;-cM;$NjkvQ`*=c({DYbi3b6%=^WuGhRFRivNLx zyNmXf+XU6&+~qIgn3y6e0>9|ik5|n~qLJ@_kkO8c%e}8zaAPH(2C=l@t)MZaO`@U{(#5$ zUz_X4p4r=c9$=d`|Mu%sPDuT7D}9MLFjslvb8-JCrqFE1k6ToCN)AIC z6aQ5cZTpU_U8oMTs1#EM3kUGo8LTmJdrBAA$DjSRRzXG{QhGzRbXD@Cuf1|e^j#LB z4213sfJ1snB^;4&&u)W)Fgv=smfJ{iZp)VVx(X{oT2~$X2$|lsE3VztD4-m`IH!f+1;6VWgF%Q5UDsri$f|~_ttb7`U+1K8E zFl{XC@K*~^lLA19!s>xbjLZ?eTvC8fR07Q2$0hlzAnN6Re&mPQu>D&7< zK{{bmkKV~qqWcsR>lvODNucZt0_pcp6{KEdNnv(&5S3Vs@sB&NoXtf(yxB-~5BU^A z`3_6HCT&NUT(RZvd*SA*G{@`n-7%xj+GNk$b@p^69=!UGGplTtKHC~RR!egNYsB#( z3olA3RjsFD?R}O~sR<~BaMQfT(QgLT`Jmv4!Rg+n_yO^zvpR8he~+hnh0F*dxAmz{ z2mJi-&(I3lK64{-2ld!~IhwcO1-4>tUr3!zhwsG#73F1F zNYNmSP%MoN1MZSa7Bn0lW4x;WT?pN*z}&;OT|TsMvzm^w@$d4msWM63;5?fCjo@be z>(&k?jJ}lJ(qRjhFnw`&JCa0cp;fB++fhQQ zsKnvUKGgy#$K4{rvO`iMCnpT~Qqfqjr5iX1C-cSXEu)=m3Y%fN=_IH&6NH$lR zX)Rm*Lk!P{JmZ)b;5_tG=>%9~;j+)23T)wztyNf-xHk!y@FJR{spL(y32tJIErW`C*OO*tr_yP3aiiw1Mj#-Z)AwZlX7LgHrRbLhvRloW zidJ$%SIZwWNXxmpGkplWC`@tneQ&s?SnGdL?uU%>{29xGz-Qlv7v-EgJNyoi^?Vu- zCg=H;^SL-qD^mnnM)WHu@kSK}rH&|7(H!kd>b3x}x|jNC1DUD}Z8v;AjNEoPZFl7B zY9m_D>x%3RaruL1p2quS_1auIc_-p1ZH(*<+u;2Hg&26uwz*NP4Jt(?JUa)fkzD5D0^M`uI-`%>KCf9HMF%tK`c*mkMw{8uOrLBCF|CEe> z&@;U`C}*)EeTt^{eK@)yZZ!LzKvuUXbJv@7%Mx)Apj8RNSt%Pxp`g z{R=7O*piN(XO4Pr+Ws(5z83$Pu$L0y|M=JN+-EVqep9ZlSESf%j$AWrD)`w0KQ|2@ zD|#0>4V~%F8aG^c&fZ)e$@4;Q&MWun{(@6@Y?uaoZg}(C$@kkt3h~7)n4kSzTn*>5a(@B!P^Da zbXF#%Trr88!wUlC>X(iAUvn%$nl35S!)GT^b29!}_)*a94t)|;n)p|_S@^h+D6i1j z_G)c?-~!zJ=d7PdIOq=8G2Kqq02%DQ%SAbE3@n{)jr`8bYs+RA;`*Z0a(veLXReCs z=vz-hr4iP(l77g&`3~0~9&BMU)gkUEBQ4=!acnl}lM;apg2?;a1i`VlEvV!BI~%9% ztMbD-{Dx6=jDc3rQ>P|;uzo&<#^bofNXx5wWR91|JnTv^=K_mw@hLts$Mw>9Y90?ZJu9CIw{52R{@AdRH%_MSA2ps|TSa}3 z%Wu1zyer-jMx37vSNo=!ha)qFOUsz8Uf@P8=_^XcdJJ#~#zArwWrE#Y9jn#VnH7OdP=o&$gbr>BFsyxB;r7H>#&uyulel0H_s z#AeDWCZ2ewNXz_PHRFVZ5;{R8Xzf&ZHOP0PlhCZRqPTgFGF4R`I`*jea{f4)S#c)n z=%=FY!Au`Q$eZ7cP?mF=UI|!=$#BY(;|}5I@y~W9yVf!LRcwZRt(BD?PoP*y-XXOh zMpG^}!qvx)$D6XuW#ukd5HHkbH3jY{7?RF7k(*pP&c?9EFIURd@J*Y*`F(ozgZ-26 z;zjA|UU$q0EjPtn{jpd`CUAQA{fz#+Oqd#Zfj&2S-v@A;UboTTFW)}Bx7F}E7v-*`60ta=#oll>Zl!tqN&*r<}?VSs) zI$oOxK*JcfdY(Ss*YI3G!q1F@bsF2pAgzfXfN$HgosO7gGvbRO7 z8)Z<3tIm4WyEEMP*Q2NLL3H9eIVAPKMd40aQ2xE3iLnVg3r(3D+qL#59+tgNDV=%6 zaRiDROuq&j+!P)AKJM**;c|{_KV4HDa>We}4t!2cvrgwW#j@&t-bM682lMO$`(=UM zp1mh6Q)dJyr!sO{FC8SJ+#Zd z_t6-}_KL6RAspOX5!3gC%WOr)2UGO~jKMA!fc-X(=KgKi+$W!oNHy2eo82fu`=d|P zwa1282ld%sb8wKjAi2@Es0-u_=j%0(7;dhr(GhCwGwkO zO=r$nAD-Rm!rWtCj6LOr5;Ci3$|IC%c>OCE^Xr>#mPy_A#snG&oAKCO0_&%lM7F~a zVh-R!Uz&M?qd!{O6wg|l&c}p=EKA|*2pH7I)M!goEnGHDd%D(>S)d#)FdzO2u`l`| ztcRDmq+2DP{}UJq;p8xr1TFr4ACxmdwyw_CD&|f+v#E?JHQdYX-+T8?&P4{-|Tb{J`PC9Wy=MsRiMc7@KBJEiO6xY;Q+kUUuLp9t!Mb4R!W#WFq;wjRHt5he?gS3<>D#M?y%4tTI`{qB4PUpM zje@pwQ?G+(1TNGYQLi2^bM=+K$41U@E8#(Icv$*nC6cVqKj3GN#-s(6vhf)2#)k_v zO(pEbb?F?R=`VA)bH5lt$eNr6(0%{86Hvlm^k<=lLdLjUwL$jD%xz`KTs?2^2bUJ2 z>Z-}5>&UG6n9&&mnLaUb*`E{SEN43rHu)hLf=<|B0@HMsdtVg!D?_vWMY_NQv9rnq z1bS~K0#NQ&5Uw_H%0@*~EqsJ|;dxd}L$QzqdL0y&agf?xGHP!|^qNEEt=$UYrl7ba zcD9eN<@clhTet6aoYQ0NYf-dMqQRBkD$u@b(WU7>s(mE~9U2G@F^4s|%WO?!vttFf zT+EU3Np%lAW{!o<2cIj_?!19t4cj8CG})LN)dY}(yF8DT+c{6)<@3SjmC%Q~9^#zl zyQ?>_YMPJV21g-VUTPUAGd%SVzW`zLk9iZx#Iggjn2Y&>;4Bzj;{dD(?t zDKdK7W9DLLcY8?nc2=$v768-Oy8s4&|BLD!NZd;im+!_~z&)K(-v=LoVyB5B{mD{O z9DICyhdGcQi1-D} z0S7RH0R{am;6I(P=Fgu8jyoq}8@XEeaMd(BOwW7O z1v1tBVBw1}iYrp%bfp*)$Ocn-ymkOJ6pL%@IdOKX5SLypscAf8W__!$7tgq1g5kBJ zr`UoYk%z%v?d{Que^hHAd2?Z*O=G;h%_S}liAJ*Q*;txZnUm5vYmJ?de;g~Qg-;hh zn<{)TWA~5Q?BzZVcG?@COHy*%`F7ti$=tf>+6y`pi1HXXc-j?eGrWcoX*vprHC;wlZWI^e6@)$%~&OF3+FKQK4K!Ftq&HFc!+F%xY~#=fu~m#oqHPRK1V{D zuhyyD^G}s-L2fM@7Sg5 z&qPstEnC_+XpHhtyyO0|_+FqJK9JdW8Nh5n4YeX?xBaD!OoQHYyi`yD4B=*Cnzj2B zm8K5fCr1scg(KCOi)aEabe@dcs+=g~5-6QT!xAPxzrSWQwNM*-T&O!ju)`X|9CRU= zaJ=m7`@fhp+FHRL3F$!LD0t=p#eRaB8fm8DM2Qp&CduK-PnqYGa0&LlW%ORrNlThO z=gV(gCFW*BRm@Q9wBy|bJ7M{0BGY7P?Do^pSum}5V;>ilG zO2)pj2w~AkWKxRbVTsdgdJl`K-sR~z%l3rCCY$HuQ)m(Ey(m=d{2DyGX-pC}z&l|w zdCO%z+d$gc$;1KzNlQtgA0Hq80LcA9-D(pnm#T^?^YW7~j*e4nPL0dUCU69TzB(Nf zn}}-2ERnV;bvxAdI-lFz0c`I1sf~~B$;k)uR(dxP@WfsR=BFNSy&o*WfYl+y=8CV~ zX}64z!p+*u$Z|0?BDyMR*usNL`1?_8KyJ~&+Ign8Vl&0T1873RM|6nWs3qV{MMVKG z-b4tIZUFn#jp|u{6alpT@jx5@R*eo9;a~B{8gSdEM)x6W?&hUbNO~BaL*s=Q7QhTUIHl$kN zz6Jmf-+K1ae;&k(u3mhS@cjGK0N!B~hm?^bHyQYwUt}a;8z+&ND6kZWlT@*;m8Vs4 zG;fv5`*xU9;x5_WLVv7S&Mp$!kVuL+-!nvwR;k}R6 zHVt*Bvd9(o#I+t@q#sB4AP0~OgWf}+DF$vQnZqwUd{_a1q8Zl_a)!t@WyfD%fa{yg;z zvIV#Gut_XWB59vi-(e)bp;ddFeg6|9AvJE)qTu^{NN^J#?$?D#+J~EFoHkhA=q-)> z{2gJtN`}Be9=fV~kNFRLJQ(+nu$jHMXujlETQWgp@)>+A2h`B+A~wOOl(Fig6Hebh z2YrUVcM(a|wky8Pw*k!$_c-Jn`mLS^zJ7L1jmRI;8mVfaiiHu0>ro$-_jyJCA`tj2 z7q!wI)u)Dxi;DwrJXQ*>m$8U&7&3!`?+vE;{x{t3?EVj37ztAakJxu&yd-?1 z;Zdu)x4zWSw7<(*y``rX28WEJ{%{hZu;~|+8Fuj;1WKKIR}rel8PO48NH~kqpRgvo zI2sOHgcc-ekJibBixilhxDaI%&a~tSsOPdCJlDUqAhgAnLZNlc+sn+&5gL51krH?| z;_rJ);hK)^sBWz6r~o9!)9c{Q-oZ>4XXEwULp!`iQZgpI35`%>TlYw|=TS2&oX#x8 z;g>I9SwLzSPxg+L0i+*lj>Nx^yW%G9Ky^2q4S3=^Lbt?G$)m;f=kT=d(h7@o_2bH0 z*xNz7CSVXE#AqQh_2RboAux9*5G4QO6jO{Cul-GUf zhvfQEb>=-n$pO6SVy-lGn>d;IQY>`?_-{F5<$(z+M8=-7pxw3Ayj@)JoF$-21K(o~ z2kQAZ!q_O!w31RSnMa#V+<%Lto9~o=$}e&y=n5&`{9eST8!vyx0ew}jG0LFvuOSLm zR>lCP;rskduQ5g|4_?A&I!G$`npy)dd){K~5D>WYbvxPY3o-6pWBcBpT|6?~TFajR z@}f$N#E`_Vf^ARhqM9x`b-kcp($6BtF<`+ip6CjkuM~r_THQCM?y_#6E0~-2#cYD1 z-k$lRrUO^I~r+I{p^efH6nC)lenQ6x9(s@obSHfBzOblGv zn5jFAzSQf5WC)SDq0tJ1Q9WNm}sjcg058$0`y@x43mQ9DUL;<-eI&#V?mG-F-mUD6Xc@Z-j) zf%NFNm1)GvZb*FZOIJJiWs;iT2P6`O7E`2Rr_0J70SiSULpixBNDxxP%?eJ5EoBRS zuLwAU$$&F2b;@j0FjnAWauMhKHSp!OJH`TyGm0&{Ct}#f;-IJFn__r!_VjPdt_ivI?3{6A41G%3Qh62*l!ydi!z zEoiz4x*>~`$oc|Eq)+&oL+BEP?>qeLZ#-N|;&_9jv4_g}0h~xhUaR#4Snp{|ZT*Ff zMRET9?F}tJEIeVfE0%SdG7HHl=*^Ph0tQS`DG)RoM872ylC=YLe@5~z7~jI`(%@mp zo4quLFtNyzHpfZVYk{)Iea1U=WackRaVN{jAA6f{%?RP!%zMyQEr}TKzPggpBl{Md z&%^XklicYlaU0*yN}8@Nnga~sc#zz?)O6sJ9i8oWxiAl`5QA?SHNr>;;MHbSJ4xF3 z8jRp@jzL0j5-_C(LDbVAnk0Z5!Uu(s3O=&i?s4dsMsaFKTx3 zql>Wk=SKjfLk(rpNQre^xX!LP&Fw&C#273TVAZsKL@N=`EPzFcD-jiQ2%i2+x4Po-J^ zfkvdzq!)_0bNU1bzJplhk=^%eJE#vHYv*p)jZ98ifD?AXe{P#n`I?UOyu^EbWxrsrFb;72qEhTIZU_*PN#3r;_GWEORt3C@cVxH~_Z_ zj}5_{by@5hbGkDuSxLo+gwtMUE(wEZ<++&WQbUnOd5yo(VnzE(>AYzFzI8GFvrJuumKP<% zt3)V$wVc1M}7 z;D6k^^tJka6fs7zal)i2=`(s_{}7d(y{4(7Gv2IrbKcQoD`>wi5NXg5-k4+8&V65k zs~FYBe?&1`MI9=4jOUgu->N=~piTB9TX?v=dHXiE#)URd`kfCceYL(h9Y{TkT8@*K zI)Wvd!zLz2K5lvsi&3BLEzmD+r@4pC+0{Lq3T_?+t*Wt){emETRbDL}8Ds0NbEbyMEDn?49e{v+Qmfq)H% zLP|g^1et`pL!MobV08=M*vbTJa=h+zYfNiUK;S2gE~PJ|F-Q@9{z4o_aAAFrnTj)M zKRa?K!~?rzgy=CDkO0rrO}CczNC`0zX9@=Y9}VgjF6S$z4;K?xgT|7DB2)7x7RLl^ zRxMB=iE27ez-C5}$3!eP!;{U-($ex{0!C}K4yQpsQ=L$;ur=luc-|`~B$>R2;qBF? zDeKrMbz0cq}WZ=mhVeVs+P;D^IWD(h^WMU+v;k2_E+_2pf{39eP}+|3{~N z2T0=(R!##Y{qupQ=8^Dr47d-^z=0_+x`^$ac|Fjd?g+6F@XmfQTGnhM6Nzvg2t+t1 zk_fD6B^m@GFOqaY7$g4BFs>0_^6wM7T~9+X&K*wOCpVb*QqqEkp{O6aJ|r8QdPY@J zH07&Q%fC)GdA5HeODKZ7FjicRzADjMC4HjW?_Z`2A`TUdsn3iHKGwr(1;J_rpL!5r+fr&Lrag%R2CrfiC%m*{o%9dSE|Kt z<+|5t*xB<>f~?!+1k>#=w5e;|$Z-be{8hH`z!5^~kFxRPmZXgm8L`xYt8Py%v-WDk zfC0B!s^qjg%|}-69^INnco6lApD9>1BvU#-9Xkt7-$)yS{V~;C9&$0kTnPE{pilGQl9DN z;jO4H<=FA(P|5`Hn_Wi1~X6H1dJSH0FS2c;L^UWW>V(MwOUfQEhf=X49>G#j+=-j1bz>9epm3C zQExtzG%L+s_oa^I=9Q1!nrP5k#ib$wpDg+ zev1A&e?m+dbe`Ib)yN(*+)a-q?DCVMk)HMunYsKRa9o)Jx)N8ymraH^Pk0`jA&|t~ z&J<|81-?)usE+K6R~!F|5W8aZDX8DJD)PJiJ;Ci(GPeWdq}(9uz|cw4w|bOBhL@?v z7Vnm{!QaGLutp}^wVwH6Dr06Z3uTwVw-O6;9G1GoZcpsmjabKe92e48CzXJf4z%!5 zrSPnvvFR`XS58JU(N$SIR-IDeZcSc?@Ic)n5x(DigIKYL3U|FE7Zb-umcm$o{r+z# z0Xy$>H00y9bGq7FaMAp&0Xn4famr{vtHzj5al4px8>imn1Gy(9O1~pc!_R7oO2r%EW)-^>* zMUik!YUKE!MAX$t&KoFrPEBJVebhEsDZB-hEgvh6hCm%LRp#)&P9UZa+vv&7^KX|e zA7x=5dODC~YGG*A8k#5I6)>qe)9`wMMX~4owO1jW%muek>^Bzdvo9k{dQ{GunslGJ zcPYGj>5a;N11^&hD~_5$KMh2;`4UCi6LlrmHh7a$w{OGebQi{V>h(akyHi99Qi{7K z;|0@{i-uMfJQpm>W6;;Br!k~dv>I~q^L|yaho5c{|Af8XYl{9i^;Wmer?%HM)S>yD zycg*|DE2;Q&_8-MpL4SHWxHn1^_EyymZ>o>&SU4NaP(;SD+{GW_)8qImpMTUG2N~SV#?Uan19)W zql`E(n-d1$gwFNRZPGyJjRE z&tGi*ok@p-^dq9zUzHrqx51G6Qj}Z?&*>@_7f|C>+Ox^N=2ZC5+{)DH?Hy1ta)26| zRATa`M&c0LHX>@~3ve%Id!}477HoWdIOhs5@`Gs};5m4if8~rysu22h*T~ZL#vedP z#eJ^Oipo}u6{kZ8fD&NgB1oCIPs6O_>~)`Wj=r@KyfHpcs+lvOgfdjEmKC!~{rZ@E3$tx?i;ee-+NOmd+kjE@pwR&6On!KT?i%yB1J!kJwAa@u(9XV z!vkcXB|i3s>|gJ-QB|PtuD$V01|!FDTzdernBjWT(6&b(Um$>>Lz+fH& zqRSonqGAD~j6vPGB9IEm_aK{k!?G>ERR_&BjxQ2quwPEzgz$kUb66)o@G(Epv`6!5 zI-HE#2*F{adIbMhLjD67W_=7ld5j?6fzYrfvZ!gYPM(nN7Z>79yOH8#hkSp1zf8iB+q1FBT4f??SE5@sp`N|aUROAY|Hj2=qmU8rp3SaD2*z>5Tw;`^fzV`qN5 zCD{2{h7&831VF0-jwRAM@bzCH!}uzq$66rCoVp-3DUc1zx>j$-zg7bggrrtSCb6IMh$6V1@1R=27e0(JJ+7BW*fY6mF-t9-#>#8 zexzAaxDu8|jmwF0qfc`qF^JnuwTb{Tf=plwM7;pd9iy>dR&D^0D*qG^t`oBnL%PYB zkE#hM>OPtDhJ8Hc(F-Lq{a6m|zSO9s-OFUYB7UHq0N(Y3O9>j9`X>OAS&ZulEw9$h zINo)&7HP2Cc9@j?Q0AU88d)@i;Dr3#FQ^;)tpyqdn4ZPMPTG>nV_^XjLtt>CW0c3D z>;H}5v^|cgp6UAmubid*RIZ7qkU|kNUNI-~XL^6@h=*ma1bE0JfZjs(nu`i10hR5A=568!Haw`^u+L7w0I`b7_RISpF>==ph&uoZ3mAFTBB zG)H}FcZ`BduMWGj{1(;(^d)&JU_dR#4K&dbSBY#Z+tL}?i|M>Pu-}1qhuGScKCt)`q z&R!yNUdFrKK9wmmrnswqVyG$LP{no2<6&hvmJfOI(!%mzi+a+y*(3Y*F7l}tia6^= zKMIwKnFi+|Z8Ct%)A0M`wf$l;12ffFBhB4M6MA^pv><-j1S!3^Y?x{>F~-R z8>hh*aJ3Ub)z`L-=bCt1s0IG6OOBC*&3oaTf0-YUbYw>$5K5xPGwD*{i{uTYH-qw} zd_||CRuVbxDVSfSb(;juAbUHSwhLCoz`w8dx9j~){c6vS{>R<=kdgpL0*LoVXi|e& zcv8^MtWjGP1z0EICUGjO&m@R1mrsTb{DnAult*vcg);;tWaZN0<}6gu1}Z5$E`YEg zgOyEKNI9Kg{5f!haAzm8Nn-9J4`qjt$SZ7%f6TUao)h6URbd;~2!Dyail5;}anZNs zMkcR>nwtrwwu%!IKjbH#G@!DRwJk;a(dI;)m$a^fNX2>#t1bV%{NIt=WTJR7?3e1N zP5DT(Kn_n!YcLZgGL1J%QZp|8Ktj!5pt!Mj`Xq?~(e=qD&MP(QWzBD!2W}&az(7{? ztj6+-N8SWt+OEfh=0F!Pm2s81T{7C+8mBKe_S!>QXYJ$G?o}1YE|$1ZRQvzl`>=oS zy;wg|qI+4z`8^~wq^LQ&2FtfXpPoSMgc70b*cXT}D|1qfDFIvflmQ#pmObR0-mviO zUi-YdE+KN`?j`7@^0-fNQ7@4}i{juf56RpxZKN|20{k7z>bZW%#`Sk5e`6T`nrxv6 zzUwA`E#eKp0;u#ouIYNV|H}E$SfzW0p@h|2K&#_>^F#~Rw0vw*-#O6O;%I3{i-c9* z`rY0pU-O6$L*?2;mZJBF@?y2beTT%u?Z`)?-l5YpUx$s21yzS*Y_BTnmflrU2rDY_ zXI8bw824bWjBi%R+KrFq3Ef>2Bt^53yHRsruDD?goIf&HslXLpxza*$t^5 zfMZl+nkxB$FjmC%WaY$32N)DLB+_;kudj9FjZ1xuK4r3U%+S@qJ$uXXo=oC(M4xQ9lf_8qehQTv>^Hzy(Ek>q8l}cE<_u>2T?}vqCa=O zzvrCuKX1J9;l;J@Yp=ETTKfXLEoQfTY1}lt)b;hBHSU@#l6e7bX;5m8c@-vU*zByk~iz{3*xgwAI zlKwur6!x$r6RirdueVcyj6Ns=zE+${*6Jk5@^IoSix$g@4tkd31XEu8@*gHltl4Ch zH0$katkc@2JpcP*5u%6(z|;-?wV{E6BS7C07DeUp7&KV0dyf9c^qz#pY2Et_SFp61 z*8A(&XUX-qlWB>E4L|+MChWcTv@#E)D0HBuAvU*MDK|Md=u`?bkGD-m z4`)+=*>qCB$3G=lt$==i34X|UF<^*u-Ze>mUz zAI{gHg0)`GZuer?oJ^(1UNp5Cl`X=vv-b(f!%1PHYG&@*YT*7ijl1;F!yop&1u`>G%e&~g#Fw;83X~+ z6k`F|AZs%Tz(N~NT?IDA22C)d!${8QFdpV8qr=l{$GJH7peSPi#!W<5lt5d;N1T4Y z5&uS@+`G4BOaLYw;NcZ1fHA%L3f$rEw%{Kg={w(kS_-0?u$E!iv^p%Q4;Xfa* zU&Pl0;b(VgvU7USvvRSq$yi#xlKB3R7d@?ujre+YHB&mWG(yKPIQe7NUBI8gZJ8KG znD0{x1VodJW#^*9NQqhjlktP^$<1H_-l~i2P(ppQGjs`FjAwvXd9R&nRD{& zZTGoS15JmKEToBWPy;c{(gQJ)XPT$`5}P|U_MZZP^7u~y5S4+Jnh_5}H!t@lMviYX zEh^e(6gQh)x*hlZDZc%Z3mRZ#V;yRyOT#O>j6&I_Ps(aziRgwDq1Yc{n{`k7D2v5u z>Sl|1QyxD6Gv*pACr>7_lGQJjhCu+@18k;-fKt2!jN)l~sQ&r*vCA<8;9kyXrBsZ= zTG?Nk6aP*@#Vh;wxPN*B;?=X~bEnYNIm{((xJ$Mae=X+PIf{#$!qNFt%atW0*bc9eqjPN zLGB20MZ@((Lhbu$tAwk^9R2Y`uTDW~NHfxO0Bn^!}< zkqD*jrbUgE6h~*MrF@rm2tp^K@N#GWM?zxg@8?_LVc*`tMTE@X^{=M@n-=CgnKeY= zQo@`mp%{rdn;?l_ZY&+&F3ob$nd9mjnkouT2e4}$=NWJO$JQ2<@B~D{Qv@Ris(Z`^ zAbrzoVd?$#q$HU`Nktx}pmd}WA(Kgb&%=>b1F96xUahr@Vh_nCHJkTqrylC|A+ zv?E`)m?n{}CxDrI0{?@#-0*o;urGt-3ZH`wVJDi6duF6o`E6b?L7|&qiVUOpFlQ?^ zIK?hK`nL|TM#@u^o(d0LEh~PyO|L8Fh)g(1vk`QrV;$kwx+PfB!OzkyU$T+~^d4lE zHv7Gj1a@Es5rJ6v+`%_4Mn0Ae;TsToq74VPGDm#bKb4Xs{V777pY=(SY>C#(TDuGDPbx2e znOZHkNa5sc^IO*OJ_^O=`B+$ZDxCx%!GhL52A7*Z(eHC7`etF%tTh0yuKR3{i=Q*e zX=;@GH((u5G=?fZ#rLx8&c2dlEgprG{+k`gA&Q)-I|V!0*v*wM{?rh%&L@wt0^Ph; zrc#Hn@Z?XJwASjx{6Esh1L#Qrc5BoW^Ehc}+rme9)Q%l*ahdH2dL5Ee_^w;kdDHfA z4Zm5sK>xAf(F0zzU;(z{7=x@y1gpo$JqHEUN=@NHpNyLA%b#ae6R~Jy_aeZl_PRP= zOg)?fda@8*r}HhN{Rz|(xT-dD6kBiHm&ds+Rkq|A_$pJ$k{`9wTqv_7ie=H zE8BFUiet)?VSE{s_29yFDYUNG0Xp;HDV&g^^69$X7gkBPSWBq$+G~RbOCC9SchQVJ ztv6#&E+!h>&y}vv+wQvW-oCrsu4qGsYxCtu5#*tJVKCh{OS2Cr+q(^3xNGLT1k9J} ziHei8a~uq84vqStxmg=3W`*I>ReZ>qv8m_>#+U*QLGOLV{NuLEvb6Emhb8fn7~ zegNO0*Zufl>uYP4+mmJQUC-M;8OA|aZQMcnGIJwzvo-#FKW%B?ScO0AZuB_MXFjuC z=Ex#X#GRZnZIbqv+yOQgw_<>7Wt9>0swIpd!VJtFg&%v9DM=pIfQ<3rfgBf9lb078 zGj&(N-7%x};OAbKR<3-V=rgA|{x^V^dYsk3_i+AHu@~~ii}B>0iKa5EY9o(2{OeTAHcyG3CQ;& zb~yy3HkAe(Hex(lc%R*cRk-7|P3|$(Zp7GTAdRpQv=A~L^JkD~$w;K@a!0t)8o6$q zOdZrd;UrM=1qTQ=83BBiI*=T6vRAwnSxLbPcN`yOsSub)G1a^j-K7aV$I&t{rvM0f;K$uw^?vW4%T!UDs@m@ z()WawkWT9QhvvVXD!_7{h7W%&!j|zavLH^K_udU3by`>lrHLtZ3UE^8d`YjYBv^!mSoDDwQg|vw-Pps!T2#a*0V4o#xf-&XE1YQz)L#z!Z!Wd z@rbSH*&}O#uOe`%8H4`YpLr~7z;`QT_Ya@N{12Wn!&;ATan^_)ARc+s#d`^ij~q8D zHY(G6yM#ehPPa)Ra#QZ~}rJ4^5+%Iwsjz_TE3pS+xZcVCfvOtJ;aWiZ?_%b81O4CDli$1bc-9*ta&xSjlY#q@x7bo?Z)rXv zLVg|)4oBp>&=*qqk^<`nZ#mhP;FZ%DttwdF$$*BmoyH}QgBYq26i&{cZ+2-5^>UI< z$}L<|{~yc`L(MNfs%Sf7mm6DZof|+S06|Wjup~dyA>VU*!*#D1W+G14M1$#%|K6_dpAJa78_~xCZ*Fx+EGnoojN-V*>(PC)9j2t>Y<^$2wK#YIw)7sEo!;-1jO!=F<(qJ)Ll#W?UL>C!SMAx*%d zjq_k^nIV;4c>A95QSEM@=eI$z`^gh)j?0Pk8KC6CM}wlB(R{-+l3UGFS3>k}fxy>>LGfmju242^zTC-vYG ze}2@Z2`5t<>GV7FOV4eZQVH7A@PJ^@q6EFnKodrO-C|?}&o@R7Z+2Qi^I}*Y#-a9F zIfX#D+G0*3K4NhxJ8*!6rTB7r+B$U!u&4r5FwBPEfx1wgnprxVdbP7v=|LR&Ba2ve z&eI=NC$Ih+0YW>AmWb>dpAU^qjJ9`KcK#`Hiv4WRR13U*%LXVto&fisjHnRs2)+)7 zRfq*e+3bNUi;&+HETG!@3>GC1($L9i5!czi>*;nChj)@vH#m~eK%@V+_p1>Vp1cmd zTl2ppx_o_&8v^;*w83-=xk}`QKY$1JR0=*(%n|(-G2<-5Bf!RpC!o^(uAd||Vwpg- zgv?xOOy~PO5YxX9EcsXVl#bIni@|kZtI%H&WNGPIN?JV;Dcr8uxv^r{A2Q0%;=0}9 zpAI~XDy~K(zd;G%O`$LOW?Po5!>TylWf9@dPhg_WYpX@wq-xI5i;h=n!f6{DJJ1Gd zq+9PhO)dcB#5}A06~F^xiM-`^!Qqvartu$dQZ?QFiq-8k6U;sFGdMzjNTJVw_(|O_ z2IDgq9R!9$^Z!(y=e_7c9Ki4a%$*&EF~%b= zM0WNggVB1`+x77AQaU1IK6AjGB5Y~a>v}(}nx0INqn@9W#yK_2tdL*zt;Gul9Bo+z zSy|w%EvlJ|X#6~T_#2gF9ZC(|`&&2cxMwoq)2&xe=N9P4k$?FK%wb>YRgl;!`}hbd z2N^aX>y_Y@u@zsz#Dt?AQ2-21U)a*bV$^@~zE^2)=yu%{le-UQSlqXrS2^+wN{6&a znEnF~g3Yxq=2SFlYNg_j3gsfWdoBmbSfxCkC`49NE2zV`g$P)+V+)gvo;Hz2G4;OR zx+a_d?ulRTvIPGZvrDH1B_}6WNzs$wlZ)lu8EBtm zR1(ZyE>eiTeyT_7dO$Gn(NFWmFGpEkk7+9q>=wA^YD$BGN{P94H;w@_e4b9$GhL}N z6UxLx0+p^3;0$7oYrCRm57=ZtMR%89l28jSaYt{9N`!LND~wSvYm9Z4VZSiCxuJ3? z9!Qx!7dSoPEpRc1Lj2}MPOdK<&RXNM7qp`gHjxMcgMXTwnt@KWFg z&6(#HAfsU(bgbqjh1!w^ZXs3+pa!fltN$tWB5+~JG@x@BAPEo)zUO`htbi`_$g3hi z@I%xWAoj1;@tx56nipyq{S3?`)E|iNn~ArJ89nmy{HQn?C@e3O2G+k+zL+Aa|?VwQe>JGa|`~zlE!y=631ssgTX>ImoeP8roAtR1{qmrKdS1I`}6zdh-RzCr6$SX7wTJ`*XiEOmaxO2q=6~lp+3_&&- zHiU|jC<)BD`@m-Au{wKa`GTJ=x7%!;6BV!qUdzDX)AhCgpY`?Xvh&(sD#2)ApW0vS zZxLV8xh_8o%AfOjjZB_*yp%Ji5OoX&Yrc?}vazT#7VOb*C7s>hw}%?~D7R`<04X!` zQ4*F6L-wq~v#UT!->Gy?glM6J;cTtKPspH@sX@lc;vk-I{rIfEnBP)BxTst@I_rHL zBAya#90h;R_8hgTK_d#u5p&wa!;f{;;amtRe30GjZ51DmUbAM#(9`n~04&jg*ub|< zLE}Wvzjb3vNP`q!9i+JVQ&lbm@W%mv?7vCwioxv3e-%=~4+|WrGd+}h!`4g%s|eXPt(hMvU>A-O5I1k)fKzD(&4#&?>o*7NGX6uF zx`}Vfbn%={GxEozC$z*|WO=!&c?5 zs3e5B`oWLXWC@2KfbsYxa7{S3;0k0F7>G@p@fprKhdGQlOEMF~Te&;8Hhh|3bf+To zcViVxAIkxAP3q$f4-d!XDlvSYE1vw{KXe&WawB~X)PJ1-p$&5&IZ_WE)qx8$rDZXc z#5P8{-lAVO1m1|X-kcfhy>?bU)S=W2CwYk~H`=Pgvkk!_P)u>HWtL&bg1k~-=X6HO zpq+rFXwHpxH7aasTg>Xoz!0#@jSWU0{%9Yik?Wz#Kduzr z6qv!Cy_DLl*pH2Z%NVSrJ{tB+e@{67fT89M0rfj2&C7Z?Tvw|taQ>3ePcsDf~yU z&%$z^!q$^$x#!z?J(f-Iy^I=Y2L%v~g^#2Jr(o95F$Q)tQI~ z|8)bp*Z*G(35W%~5I&f_$F;=TtNjNE3TWd9IO*%tabuSN$*PKo@^iBLClSWo9Y45| zEirFNDzYYqA!%z1Cchv~hc9>c^ zPJ+G<)JJtN4Dp^c1UA8r4$YC{ljST$Fu>@e^R3$iajsx<9nQkcXyxp*(mD>io#GOo6D+ zLVy5^Vfopv`0(xNRC61*?`eMbwX8HJ9I)T(-lqnhOx_+L&TjjQSJ5&Xk$rvy=Be5S zW;IRG3%}zQC0lJ{a&&;oj`g1*1%`n!i1?ZeJaG6|dFTiK#}V6vHf0wl=gY~%Viwx{ z5#-^B<-^QOQn)b1fA{@m!~YKz0)ie~_#Ujz9x?q)q&nN#$b@u6oLW4zZR(f>G%T z+1wV>8c9w(n#NDd&@d|Pg#WXrGWkNGM`ysUm!Gb(k+|CIKX8tRFtst>7aul>0nU9z zpoI*=o4pxTQS>yeT=sRS#pY}`FYxh0TU6X;>_y>$g1`9k29<847z&)bjSnvl?^V37 zeAQ_lC~71Jc96k(MT1(+G>V}2yn_DOYwLhZiuUOA|EQ8b^2#Jo2RpQvboMNWxbM+( zn>K|Fr1Grxr*M301=4S#d3c}LVZkLxdv`bN9^((~Kg2aMs7(=J`S!suP49?5x9aD5 z@dc|2%Iv?GZJf@l1u>NfLyDDzsCF%Ptv1YY67U@*#RYBmC( zmYWBB_OlO8vVQ-7i{fgEP4_)#9-#mI0v3_-3S+QB9I7%V`No3lLh5?9zQX5P-#m7k z&F^%TX@+i^{1~qfFxcz9eZ%YUvj|H=u)ek&xSeZH3s@8{KC*OweOm$$N8|PJRwrY< zCNZ(;KbW6z2_f=-H1ZvUlWZyn>Bi}9{&BeV`k62xoC^ULkXQFg0A*;b<`6@!^_tf#|`h7A8?y}?~6VFR0h7U(R{j%LphQ-W8raW z)e?jew{w4fYVpPOhm7k#D){=0&3N9!oUtrjP0Y%tPBz2i!0IDfkiZD`(U6vM8V324 zMABV%)s!d5#)hkvX=Slb`%$a#c0@Qqwj?g(we2)O4}Nd~tT8w_h~73G+v4}j$-p~} zwm@>6W{-5}%PYCdh#j&7av--8jnI&F1?I35B$c(C%n^tR+@FtCJbh2CVzR@InF*;T z-&xE~tu7fio&1*9S~7~_T@z02QG20=iuCD|C5QJW1)cL0FU_> z!pOT}$-N!Br^#+n!dr2orqaSv5-&wvJ+34-e5EAYqS1?E@llS%w$TnaSyJm%N15D4 zErMsbMB*Ce;p)}hiXIu3C9;`;9$X@2?b@Efmli@$Lw@v;7cZwNc~Yw8dX1lmntv$7S2VNnMtofM$uhW7`eqbT_(h7!c>+{ihVHhy|`N_IvLs zI?LeFuR{3T&hS({hv6N?xz7~ITyJ}FB-vCa}&jn3*%0>d2z}IY= z6(zDQp!v9ggz+$z7`IPJ= z0DB|jeX5^rC&?hE}Qx=7?m)evN-PEH~PtMh+sfH0PS7+8a-eQ+& z-K-1cP0ygF%0~Jb9@!l|R{;875m>O<6hYJG5~sx(hEy;&SRQ=0P&Ll^QR$H+_rO=B zRS__8s#JN@Zba1?9iIXw!&R9;Lri37Qnb_Cwn|RC+ad9V=JQ>LRypv%mbpDnF8>&n zmSJQ<;}uCM%CMJjpbhrw40+AJEe+ zPS(|ZY(^J~#qofz{TG%Pb{s{G2=-V@%=Ek#G#XVv9>>nC95a1`R7u2B&R~&)9Gajr zu+-6TzgKD*+!_G>6T}TAckX$!{fP5KL&3^Oz3iWs04N62)i(IR3t+%o-N6kFB4J_W zp^Yh$wH~pfep!+yzaChw633*xqI_?jiI=%XOd-&&f{oBS_dVsGxn7REiBN8(qhXv- zf08COH6PRn^k{QVu7133i)=GgG+Yejd?WENy@WJ!`Q!^QP>grAWxLN7z;o2mIbjx= z1gvnltF?lHEa_*zpCSFLNC$-iF>9%RFd*6S^*98ufbIh-)+55*MU88xOe&N%;Ii^K z%mez)%+VgQ4dl5l0) zcn4a4@d|m|n?KJnw|#M~P&wrUw2E@&h%bKFrHOU8n^2Mv^|6PeP@{WIzf3rc_iAOZ zt+e1HH@bTJQ`z1)nn<;sDVKHy;WnK)>_R=uPXp2+C#KI0eOPF$-l;$b zah;Rgcwnm~x_w^MVr~R1>RCU2CbJmo>Z%;-u*&Lud8CS%&B}^vA9KpHqYEv`33rr2 ze*fOZYZRLQ2qhcS@=O`u=8e%yaN;^RMCZ$b+2;bJ(VhdkAe5ZhD({I5R07+m+iI)& zQCQF0zOe%2>|VF#3v%+f#$%^Z_dbu4k(%~m(LjUWFP@zL_W1Cq$P(a;%xd0t?p6R` zyxke=b=)Y*jpK#+^R&Xoi%zoQZWBOTH+n(>!e)s#RZf#~QNZl*m+&=RMT+&xbUT4Y z?#WEyAPpFwfu167^cTc)>Yve5gX$r;Q20Z8em%^p(s?I-3d+9cS*Ju6F4&WVw zMrcp3x_k@6toESMp-L3dXls99Qz755?JGj_hF{zOX|tM~cY@1Sk2^#y|1-G&mg>MHt2YUI$ls zEhbY3H8(}4!e%&NTQ_79I@~MvW%aSKUT$o)QXA#o2+AO|1pJlP>B9Epwdt_p3uWVj zfV=B=m2$s1cALBaH*5SA4Up%k=nK?bgv-n;@uyv#Tx{gtHE9<`frW&)+E{pZmx5lA zi*~>IQjAw3&hxTt<$=ixJKtdGr;tA)D#V(QL1XQYEh$Ms{&BS3Ro1-*G}3`tWcQI2 zTHR?N8%GpDQ(a=(PU0}H4(pd)^I)x4qGF?_|Dq`4Lgs`8(QN41-LnTtgv1Ob0Z2fx zR(Pc*Ho{K$kjt9yy^LUlf%8Y>1M-4GjzX1`xjk!ZYhoIjC+8`P6nsAkBzvbV3!>=O z#g*E*SYu@{kYN$k*C$rTmOzo*Va9r|PXIV+emUl+9hYR!bF~(zf7Yuj z(N;Ti`w?IhjRN3`Y;Fq??F`Z(pz~N^5>GtN0+fmHAb!BgzjaPd!h83E@2SYk=q&Kb z8aHcI{GC~_ z82=&v?;1X$Ca*)FP@W_#5~(<#=40}7qz&|BzO_>9_d^h2i0oeO_LmGqn}bAM^3Crz zXMtF9y37&z^;v!kPf%XVkMFvrle@+f9@|G)8rPaW*F`BAXMU7@p0N9-MH@MOC3fd~ z*u;QK^NxX+hW7_Khd(+&(iEWXkM)#HQ|Ddj^CWXt}>xpUm#4)k1Y@BLHYX zo6SGw+3=hlv?*cS=PJP=QFPc(%;a~^@6NXETyM53iXOEd^@~4bGQXou+M+M1U_3SC znArUbNKSx(z=r1oeb?b}C16Sf6lo+7S^uzKRzGcmb{QC0+sN(f?-);Hpz^eZT3cg~ z@1?!&a20ed*~nTnHiVQonFvtcZiz)InN2y5ILUs%a;Qg>8UG8ky#4|SZ9HU{T$oCFiC6ZjQ@x1%Fuhd_>CuS!8<^_mYYhJ86ylC}nqh&t065j2b87SIPG z;h#M2Ck4k@eSY>^hbspeJZ_ZMq@@7SA(nq)TKA(znnv!M)!TZ8p3$Zd9m%Bf(%1s* zXl3^CR8q+UOmX*L$P{hHST^tcTPDTK3yaS-k84$B4`;&eeZjh0idPaTYMFq}Yo;SK z-yCph9qfq4A+DdsH9-LKk^d^RvEr@f6foL*?csYSxQIm9bdyUxp{{+*9b)*21K9yT za#v;-k0c{d#i!K7TYF|0z2J9V!v)15r2YZy`!S?RlDHrP1?#wSc^!59waMgL6pDBH zMz?`9u%ajn OKnBAQ$|>Zp*_c&yQS-fgjKF*%PT`ryIw?%?joqTJsws7p5S8#0 zD47J-)751G%-5$ITp55x`@FsLFFKirIhkWHd++qM z(zVCM9UZUw`%nAWY8MLP!61VG(bVBY$g@de8yyr7f>Aj3Rt}z?SMpe z+>2(R0h}yH|F5oCdtuEMaJS@pwGc!_48n@>Q9rxB5n_tG_%m2OR#Z#Z0?Fatl@g#x zhBM19iBf8lE6GgxKD?xe%%fuBF{~Ce!KI%9+Xb^PL!I7~5fvyR)nocW!`yMGW`|5A zwuPJ{YKZ3>By!>Q7C31A<1gWp` zrfGsbwI*=^D_-BrJ`wUq7stO+^1b`B)7`(He^~#Ud$G`>H`#n_TjNav%dS4Ykr>_& zVH9skxUp$*C@C#|sH((fongRd#Wvq2lk4`@R2O-xlpk@)?5S?LA+ev;!#u43<1%h> zQ0^-x8V}51-%+T~E1NmVIe8M-xL_hp5K5NIZkmLCi;0jdpC~vR*qgA2 zkb$%z5Jo6k5Hrr9uBdKksRyGS1%%MFx6rA;CNj|7V!_XZ&J^|ueU@63mpcANn0R$L z_n`AWn&kE}_O{px^PNq==6yvVkK5vqRZfM0I z7K(Mzjt4JVHcAb3FNgEuj?o-~TVp?Kxz9Kh>UL6pm);E)zuUTenEG3sUr#7PlwVJ@ zu3YoWS#V|LsJ?M++TR*7Rddse!X1$d@tL4A`1im)`1i9*m(!9{cB7uM55_|Q@3o{Z zHyJMeuEw95GYOqiXKPgohqJgOu|LB{dAL%;x7Aop>ogQU8`=;d?nVoKjwIi|AG?xGw?R0h)s|Q9 zq3*sBmiDe}1yv6|97y}So0_^JjWl>cMxMBqkTn16*I{xLLW&w?CD|KTJsZwErW6NTmh|16D6mf{!7B_0>LXoHKqY8UlJwbTG-B%qi|0YIufJ;i zkRkWjd9Ngx&TH}JZ?t(2L}9`3i2Pq6P=WEm|F;lg5nIQm2>ah{VqUwa_O#~|+mLGz z;nb_SxDEDk%zTOB23G3FX0EG9yuedm7Vwp8CStgjhTmcmcq4mNcQyzH#|E522HxnQmq(_G-zNR0Z9ef*6s& znl8p~f?rOApwe#XVT5XR=7Bsy&78Nwl?p5J^}2{jCSb;^aljF%D8jGz3SQlf=EM;f zFy}0vs&n;X-D!nWykN%K}E_} zE}}w&!M*-Fi`{V0OFI995dXi+k-mT$R;+}jb5)#x%#pJA!x-fixa^&AYODBIh;KY2 z&e?LD7H}4=7Nb6n&#|L96UR-Q@7^$?xEqH})Mo8HqHABEw7x2qrxrJ$_;*oU)lR+6 zEg3pk=vXT4x2GAXq^$3XwryhO5udYOM)9z0Ya(qYlO*{)#__HY=hDJZ{y#b?>`#)j z=$5#LFq>KLHsnQFIucJI{-~96!}>e2s$NLjyi4f7LCjeWBHS{%BL@-WaXar!^J0rH zBZb`%QX#_it4e3^>z|8DH$M(I^1MtY|MF-n$xsR(f#qWZE!(i)b`A1hYL~l~g4_Og z4N!vdwg2zwe@0w91#xu`-grBBLW9eC$8kSVi#OW-TDco;qHeQnn?UpNsa+1wa>%W> zEN6A111-cA?_;BjC$q%8&CYhDS(g`jL33;s&9cQ!pQTnI zzNGSM&rlu3+i2f&5#;%+Q)=ZoTD^*}(;_#Ow(!sSiW2`?lEnsrj{HF_jQ~`+O*Hg@mnu*2W$mo z)@eI&fFi%^1AaN+w5Mo?Fk4yb%iX@XrGYg$jWNokODZH@0-J zw4Z(+ZI9_vn1MHLEZwxZA8zHRsS&lda=MO7Cag;Vue&XBpL9Qi61N#Dq4&nl`hJMw z*ld26<9~N16-CpgINQUOoY2mJaIQ#6zk+!gpXJKXthX56hA$#3#@*g7l)`ioe*s%p zoH3`<9xXVV)VSPaq}X=Q?E4E!(Pe4fzYa5QNuNQpj1K2!u2313E zH+pM^<$BN=UFgVMo*~o3GcBi+>}$NW{(+|O0aJ|_DqbD+U1>$m!QO$x38r(x8i>}e z?krM4+b5vc3MhzAh@khk-}Q1+4G#~?drJlmCP3N_6POwq8U6<2*STHj=KB3BL3p{c zA-zp5H$zoGQ$N}>rkeUho|9{~Y86;C0khIr;z3~kTcSdGMfp(8Vs zd&HdG>O<0%?`qRAnFSYpI#A+V=CWSr#Ix0Ua4K7K!TGNnegg%oQ>jdhxO;_sydjSo zc6pMRB=8wEb6@>yN&EVQlu{#FUma;k^U99Ra=a?_*=XtEzD)$ub18@+%zBm?0;C(~ zFzeBWg_4C}X$uA5aHTLo_kZpsPpy?2FZJ!t)vp0w>Xz1VGb}V<^s`HUl3AzxE057f z$~H@os@A|rLn?vzO;KjL?I5wF;n!kXQpMBT{4a#bwrEwf zCFIc)BtrfvwbXU8Mueu^TsT}wh1tEJ{JKtrZkr+hhknKM#_Qnkb%$w-xhE5372p*x z8@jBQng+(ez;avTbL<-AJY9Kyc(mrA?h#0h)6k8MbFLB`Z|_Gdk$|r}Fy;HdBZZyW zWkM!SK@G=B>iynrH;_A1wacN0U1*7%<=PpDGz$-bB>GiJ5Mpu|%>d3CFF6tquUCriwYSCSR`b5_Mo zBBhAIF}9q#9r?42r4uY$|EeYksG2ALr)s4CRSl16p<`eqq)z6ayAdJd(gBQ`l`-3;D3rV6|fkhLT{`+L|b15(r7lTE->&VdxPyZJThQ> zH128Yi?-G2obgL3gFC+GQ7#+u$!{R~IT-K9qp`sP1@os@q7i~VHLLMixAmr>Gl z3s%r}3O}I~)-(ES!sVp%M`w8~pm260_Lo-ul=}~q+c=pLoq^1j4H*=!Y=6cW@G_~~ zJ*I#|fF6@zgLXS0+2Dk4E)Nvgtr)3fw1L+v#J|d6KB6Mm*1G*pWYl-Xhj>#LH2#W8 zNGt~~G?|{IavHjRKVE&^C3=%RHYd73lkNvcn$IqrbL!$qRM}xFC{Ptvka>!mb3c8l zRQ441frjOLlTske;V(m<5l##~e!nQyWTuo_q9V7p(PlPyRGWM2Vc4sEda@McmS&I< zk0Sx}U}R^`xXnfDCj{z#~1iy@A$zeTQ3K#l6{UtXnp_lfmZOcB=A6y9vXvNYczaj5 zjlNM5k2Ebct{M8GBxb)I(#o?x#ixtPWDcDI@8*;`UL_;R5a7FKEY{-{7EDw(#V*$qL8iowGOw4ACD=0+7!iy9 zSY{Re)3_n#j88;&EDdmtgBrY0Ev@9YN3heR+sP}p-PtEJYP!5P*T=)Bmg@Vhejsu# zbB0*)Zk5umY|3``=+tya{I9kqS{~@uU%d9+TH7R71*T}lZY`b=!DA1<8kUkN-j7kU^6iVclzhPLEvg)my_&HX+~ev{;wv7j%t&XV1;xhF*l z3C~?7zRj?o9`=HnMWmfOvjqA?Z?eV)M5~6PT-9-udf3fS8Tw8kPgk;f;yzEb+EX^p zx-;}$6R9xG-6j0peSMn`kb|L{L#M0iZ-P#wlxw~sPBSlBnqUSQ-pLe88e3CO&Mr^r zf*M#gq%Oi7jc)j>1nI0XZKe%*ZLgULXtwy^iy~gCIo#Tc72I6Ea3p+;J)4}3s|hF; zHIUfYo4E`^4igElTpzBL9V^|4!F$dy984fsLcG)+|G;Y5!yN7)pHk&+0FaRL-2R=q zW;gsRd7<;O?b)Ozh|}g1!iP>Zms|gqP+Ihc9)7!Td8nd-wL9qcr!uWfHMX+&$0Sb~ zlCtK&yL|zElS=kDce@IbL14t9l|PO-m_y9L7SP5tfhYaj07?mUzJHn!S&Xs#j;*<* zq$OmJ+W9eqgV_Hzh1fW0LE9X0N72@+(XR-rbl!JB)ZHXqeZEn-(_*k4O`Vtc0Nfr0q7{|FHY{(ORl`fBO0G&mUo_ zhymc`I9H?*nSDo5QTisi{I1qGnWAKuRzOq>S3(>L3(c82>vBS^qBPBbaU)ep1)Y4d zK?_M4nq@UAm>Hf%0L`R<5OF+3os?d&3vZ|@MObg7GbgEE+sVtXw7rJ%Z$jSuI0nK2NQt^`_pox?zxUIdsLHt*^%y@IArenhkwuYy|T@|dGAh_)MS8@ zBDq^M48QlE9_&Po73jrglt)f5{bj#rB7J(lN-*A5)x;&rnI>b`X?*Q(7y3FDG1;$m zdNYdhbkOH4qogP%@`rp8%(GHxsV`~khMfCcSvCDK5|J~ii-J)en;mT%G&$ZV-iW%4 zeaWX5574V%%x-pUe8ypBk`W#$T|K3pX2te~(J~g&e-9g1OPBX*l?e81MF^wQ|IIWmtel0~hyHk&2qStC2=F=>+gAgO(TGUmX z{wF^@tr@ceW!@QgvBue$Lw39?{mUg5hn%Q+a#@zRLHoGiGU+PU!OPHX_~KBxV%C4B zD`_I5#LLRupeGt~O`mhdHCR1A2N1MtlW*Lf1YSc2ML9^3D$CB+{qRVZ>k@?++`FKj zWerlB>4WfU&pBdI?_2AOlQAl{mECj3-2*Jc);k6j)j%cRHaBG}GhfrMrmsA_*Qc$E zg;p0ji;Mmti*Yh#r$m54CT`CIErd6zjph31Z1YAoica}H)of+wzTwsa|Hn(GPk^*BA#9yW@h9NElsM3$2^Gp4FeYW(z1 zr4aT+W3kPX{Uu)RTCM z_xnX5);O2G0Mc}#xWEUX1X;zz$FsY?%fUg_X9AvdWE8-sFB>`T+mehh%Y9q2WQQ8x z&y2w&H*4{X3AMPt_FO2^uMVV|0L|DnS5#)(-~Ap3xJ+*bUrei4>z#|PL;8t*&U>p6Jve^V(N1X)Yy0YoK9ZT}MCY`xncd;1+>`_juY?KoF zpFA>K&BfFg8JvSzXDsWet$!pUbEsE@H3#Ty+Q>blx517!=FC6{*#yzs6s*pg(Yr3 z;Cd#EQEYtHk(mJ&_xYtLit`hB9n~SnZ*YRmE7${I0LB~r5sVZ|D>rU1A#|KA-~U^Y z)}mPVE*x+uI>uWBQ_w}2(s44gN8r^-3OEcf-#a^9oi05>2bm1iR zTwAn322s`c3y0ImJ5y1cSwA+ooH&0Coc&g{`?<(;o>qWp;JRjF3__3B zw36Sjf*bfxgmF}Cm7`tdnc3@_;eL6|MB zS+SfCvYsTU*aD+~%!P^iFo?u6mr}4PxP`{>oLd*?Ny$XqUh1mflR|(Xq)q0@4A*yd z_Fg}w6uqUJL|p97Y9u#==~ln*#5HW$_+CKW%2=XQp}bY4!MN5(W>|atov};|5Jc(z zk)BbO5)@UN+^m7jct+SDrKRAj!8*6`uF(&qN=kNJqk0Sgqil`#kgw)QQWPy3{FH(Y z{KJl+ZZo=6on}3B_v+_FMSF~8R(Hvxv9?ti18ialDC*?D&Z#ATK6mP9%i7-viin82 zmdh5)j!$7DmYR2?PTvVgdLyCIrYtCfv}}CtVfN=Q3%>8;AAdE zJsx$`oIDLqr9_^U(OPCWbh z^;*c!^|L*mrO&&krS(d~ZK8Cyp~p@w+WBMGOE{ic=PH_!GyA3IMMmQ=&-<1Ph46qn z)16f&j@JU&{YI+fI?t$Zk8Xpw#_hY2aXUP{=g(AkuTpJs3x`ySO&V{jdtz|{(BChn z+vFA1|GIEwC<0@I|A)Qzj%qUP-bQg8yMls(ihzQONK>S@*bp%SO7AE|q)V>}R*DS~ z2vVcc0|7&o7DRe)p(Q{>Y6v0J1PJBa!I^pIRloE7zO~Lj=d8n;HEV{D=ef(?*S_|( z_x(Hq?A`Sl8L#V~Q*5jT*4aE{)#cuv=Vpb8yzokhOt;spd$ETVnoaUcY8>I|u>I<3 z3xU758ag22IOP{QdCu;8TE4H2xdl?{O~KJH(6)XPA?;GnuH>5UemLJqzB)T>I)vM5 zVnOt6-h=EWLq8qPRO~5eM_hOeFk#Z~~Up&`rl z-cNU_dw_yzHrs0%s_u%V4)gLy_{!tzcPAGu6WPlbLCIjGJ!D18FaJ3XR)EGImudB% z3J1h#QQ6BW#l}!7Cibp1cx?Oa+7347gs#-Vj60Jg(xjIEp_{t0qPsCVJpB;`RF7!d zq@r4!yn0Ggj3~K~>Tk6}{=koI9xXXs7sjw&rkPoaKFH15sTXvq{C#Zmu(iMqJ=h2P zWjs%yE;xLc?i&6m7bVGEkaokMzv1Dh+xA<(UUSN1S9aBoYzs@*{W{!%dRq25`9^Or zTKBT5!=APa{y_)@~`yYMfAjTQ1GkbLe+=Eh;__#^l_+IGcL*+B@lv zwlJS+biPb_zS0LjzM&jH#X0AT^vVP~xl*e6q|iIO{yWsUG**eDkq7qjahsR<8rg)+Ebxn(c zCtdn(A30C=AVDk(-I1m0#Tvc*=hb49=a+Oyk{ZpYNs`jla@>pVyd{?`;ySD4FvBu8 zi1q0b?qkww?+T4m#1ziB_3%ZVvK6%I8{^24q{!r)TtvUrO|tJg+p$%`aa|(>Y9e4- z(&|D{Lltn<1t;wafDb*}qW{MqM;*_Yl~zfPGcq)Kxyg0Ks_7o2GQTs9NcA)5DD^X< z62Qq(1~JGhB8ya^-5{kKKUpQXB8fnisl`L=7U0Q)P8@{P3^R|(R*X)(FT`QtaH#Qt zwA-P4O7O=fYh%WnJnOK9&^JGtZc6u%rd}?Fv#3Z_yUP(5S%PrV z4Z%*A;uu6{!Yp=Xn(Iuj(4+BPg*dFH#=ca zL~%82yY}Xm1}C$)I!JfxRwOH41+NLQ>&2@pbsp@mv~^P*`&!tYGI}-keqTH1t&(@a z_~xg$>3hCbU>_}-=RNYDwQHmtF?7j$6;ddwuD`o*o0k0{+pVf&x&;-lcj&)GiCLJs zZu}W`5c_?-{E*(ce7$$uOSWk&?hX=Am*g2=g=?aW^4li^ z_c2TWL7j(z>?D8*%zeAx+Tk9lm92<_m6dK-c*d}9qEd6 zu4{ZjztQosS;7Ilxv1OnHAtmZ9GpXqR91+S-%jTX+CJGnK+M)jZ$Z`a3eUb3eS2C~ zqJa07@qU90HFV>&Uku;T^RF|5Rbi_TC-Kd?i~IyyYI3N}`By!!_# ze+BnEjdJswgR_Orj!udG)F+~W1>hNoj%FJ6^R9_aWK3=Qy%f>1Z*`?jgLWtKZ?eE^ z{v2vZ(N=P_=fA2oVt6h;`<#dQAkS2t?}WDHygKJ}ok=$jrOtOIJikFlw3>J1L<|br zRU0Lw?bDLmTAix7Tw%1r+MRxn{`Ot!@5T#Es9vvIL!M_T;M7f za|;ixTlJ^~yC-A)!HqK-(j{^$)I9dU)$vn&-M8JlJJuvTrZv?3g>iC@wNEnC z2(f%eyCvMnI0l!)hMxt`*odvqa=406#CnW@{AYa#?0#o*p@xmmYQ0j>Lg$T0k#y>U z{uzC`Bo#A1-a%5<&%0^=|t)j7+Fm~ur`NV{N4z}WQ0Uo7^E z`0ErDG2;9I%wtZHKR}HQG3lPYjnr_zx31CAjURnOR+Hf9KC zb8CEin1{HwX1ip|)tWy!c*4)>q~$LG?TXh4?>=F}q!jw|W5esF@#mcN;@5@ko0{Gs zLhXRl!RiVGYF|u_(bP&@imz$t%n8;=c=@h z2QXee4-AbBp5WC$Bea_r;*J-38`V}dtfWvS^Hgv(DY}y9n^O(NQqo0(rKQDF!$NZR z9g7xLwU5m@=rEXY-1s=!VowrHH|f-|Xu*CCwJ>MmgKimPA%aXyYU3Ow<7R9$%YDkD zAzpQKWQtwo9rE02#c2stuWZND&hw|^UW@CZ=7cX@x*N+aIWg%Z&^%wDX{Uzq#%)N4$G)^U7jL8PDOv zZ&z^IqvkGf+0*h8dT&n0T(qO8+M)S*?Ed)V;jOI+-p^bi`nK5Fz=8N8F=d&^XCLk3 zGhdlyb$gu{ZtnJ_lWRQdI#7hmd~v&vP3AB_k-+tf_LdmrhOh1*R5R?*i_@g8#8|!Z zO#OIv09ba@gIH>(K@2gRh|vMW11^m^lu}f*R?nu~QK;Xlx@p)T3D~V83;W8c0gX*a zEWWIwI)>5~PC(IaRdU`!N|}Bv*p=?ljLTKgb+#O+@lB8)8&w=X@e$Z=x&asB=vBsiQM4V9Bve%>xoE!^fx{ zYpz|ZULH4(pO;~zeTkV}atYYQrnzK!e7=ZiNg*q?s*|a;>DYh&Y9yex^rMEia-!ZUwiAvNOuBZPe!Bw>Am57dwpoe-XdL! zxJHAPJqc|q&N|OeSq>S1ZPSIuUOfAggEu-@hzD)4qisOqtt5vL{7R_)i^>>#a)|mq(3+tMZ16rlt=SjU;asa9M zW+L_@-w6Ucy@byV@G67Yo*(Zq>-xfCJAjQ$q7Wu}Fu3Q)#9Ga|E(-ce0&e#Y5(Ied ztuw}cg3(22B8T$IosNIJKfHEDf-eVs=K82wirhB z!^&6=!VNN7XVTzUE=e?&%%@4}V%p?wMH`-W-sjLjH!hUK%%uI1ls}o5E#Nkqe&|+E zOt5@Er`GVzZ+T$15}SMZWI8ynnCES4zdL5lhI!V$<@7fr7hG`_Ha6-a@ab3QKJ>~D zs8c%cmiH_7&VH+RnvUh<8V}I&=YUe&#_kj=_t%4mL~PRmUVA#t(BZs#VN=N`S&WWqC*0`HUAQJWf^h0sI)5MUGeS!cwCIJKZLn8$Xu913Nx zJYu!(WgcHi5`C^bec~m%>JS%Er@HK_wtaHChY!~1ydfdXXMe==XWe$%ezN)5vW_-i z6TzCi_^~E_1b#aRht`f>i^=00^10k(Prbbq=RF@`P2>sdJ&BmpBQ@`2@$FX!@2if1 zrt9PRtd2Q)VcjxY`tYfh#r?`(peIE;qWZ@@MDop3%I%v^hj5<*1&g2yr?U#L2A01W zb)gxcQu;fs-E}s->kzbjWj)1A1qC|tAa2(oSw1?Sm1o!IsmhT~O!AZ*p zpq)M`&ToIX?(%ufcQSXaZsoxb`7t} zA@GT$MQ5tgTom;0n8b20`Toi~0kWO;A&o}gU8!B_*Z0!cHMxCurhPd5(J14VK(oPl zsfMtkv-y#tS@_!Zyfel-9q;=295{_Lx-D51R%Dll@hQQXW$wFq3~fKSZlk_e@AlFW zKnbk=u$AMuPRY3bLeAiMLMkYnuUl%B8l3kjzG+ydNt(fK^*FBfoLk`07Tv|p4lmy0 zh_Rv72jh9l{pniOu5;bo_9hq|(*z_at5I$6Wl(!o@3Y@A3)2*a@~dP8Hs^M8FPkjv zuoNN?)L21Ir^1gITl`yaWBEd?OwZ3&*~K=5E7mE09_5V^Gt?KpK<@p+ZY#%a^wz|q zvEH`TxO=DV0(-yOd0LjE^DpOx81Hwqdhu~_;A@(PZCR?UQD4d-m`ZTL#cHDx1MrpM%2+b>j3ei8(L*rFLwECQsj60tH! zK|rLLhf$k=5Rg8a|B^loBNWX8HZqjPz~5jdHiCjl!JI~v5yk%j8Qe`HFV=-#3|pw`7k#QHVY#h+4g{sVkP^Ma z`C=L3*N%xE!$_X_RJ?b(tJPlQ73dQ(PieJY^6}e+EygLQF0XW2f}5nbTH3<*Vg80O z7M41(_sU}@J|aFn!@a$i`QA>YOX9Qxe%zMu8G?Q(`WPWCo}DBbtQ@U=@YGAruLkly z?jOk)Qx*7#53035C#n35+S&j-S{Z|*TgH1*9dX#?3aLLv8;S5>I5;zN=wOJHIY3^UFG(fSs*l`?57U&15nF**I9MXmzxDk@f z1_@wHKQ4v2m*KLxML^36SqWI8ip*a?pcJs?UeG=mqjn*DRRV!!%pL;zrD$!R%S14C z?YoryVkIAJ{nRG3fieOM%ll6?TJ|(F@uNNhbq7QNF(02S6D6M(n-38ND~;o5841<_ zE#X_6cNi>#5rJ=q8*?qz(-W>y42?3q&xerO4F}}|aN?Hf&)*k?RG)o=A_S=vw6SH> z{Ydiw8)jiSGlG$X1cED$wM|>gH%#?Ph8o-uX)bJ>p{G(iDi&tax5AGAp=ch>ij;em z|8`+2pL=D9OdJ&WJKJYrIoJ(Sof7@1K^vKpZU=q@=lkVpvX^yz=}DNOGA3_=ca!f> zLCeRX6uV?0KSYiVvmOfg3^1h1+^6tnJG28{Cfg;a z;e#YoYul@jG}A=Lgw>{EJ!Cb+y|7-S}4kJG~cOD4qfhVL!F% zX-%q}HvHFoXPy|ubmoP;e4E-B5lMCSzanDYo13?@nyEH>VB;=kCtm)5&t3RdUk0Y# zWtMdIoo~!G;qX&uWDQnWdB0jO{ouGPEG#PE(WYI=XP@^kFKx##v%dEr>q9tW@@{tq zGTTg;G^SNAi*rEVfrhs5_0$M8dYz{!@=fvC%mj7HB4A7wA z!53|~#%2HS0-?zu^}o@P=h8Pg*1^SP)&2E|Rw~@FOr3(pVy%-#k)mv+}wUb0MhXrVZr&>n5HYJd+#N#NSPol23e%5ttQUR=!?m>@U zjWW6mpf+PM0bsUXuu!alz}0z*S*XdrWma1OUN_h9aA?in_mlRe zkYp;{UxhtYV_w!RN;E3M)Nf>^knZmp0{PEMFvnJn{r244$er)PQ7(~zUt5#~LgIrw5B`u#;7sIYE&l70lQVo{WV zUF@eiPT%AoOTOuX4}m8DgItznR+uNJkY?Xiq(7N)Ol#z<0UP zn4OTCZ6B(~NQ2G*P4`DJbQ52GT+Sao7=bN|@;yQJ0yX@^UY? z(zP3iv!{R(EUI6=jQG2j4x9uyGl)g2aiA5H0}49~JTm!g1fK0d4&Ou`Vtws?wA~*PQmEg|n0bwDPZ1h4qrEn-=>VV_{{GvYvSJ)Q z3RgS@r`bc}vJ;DSP$q`TpJx?*vZYTp>5$rvxG~BLlJBTBaB$|ZlaM9Hw{)S@%Eq~i zEz&D9)6k11j%)7bPyZ9MH+LC0P)E=^T!iB?{7blzDPHkxzq4ifQI9X-9zs}rtvfzr zexcHCG(G$BzYyq$-&v5ZGz}2wfVrEke19xyhrgB5k}%SPku3k!i+v^?t9Ru8#> z+D$HJ%NwOyuR47qal!UwGxdiCc~?WO`Slf5An{qNKjN=7?vR21qZG0HGnNKYPK0*=RPnVvtFXB}I| z5BBJ}*5WKl!F%T~HoGjBE=2G@3YD2Q%CP(f4nl#VC_9u}W=~jOwkVL*e~9aU+n^+5 z{09qnHow_}albw1FIGvHxEm%a`)wCUkNyo{>%jhHHz@(P%-6*S_pb)uj8{_Qo6$+X zkt`BLVrYvDM6Gj=H>M|0KqtxNh6vG&4YLw6CBxEtqCbsN_M1?);0fjSaZrDACrajF zHk)SFm)bkiY_G_9ke+H@w!IXb97Kd^W*=*I%DiQ_W(qUuXf-|l^Z0Q{R_~lv_v3lfW|Esu$ISb9@O!b9h&;1 z1dI#N7rvn@Vp`@S{L`3wb8(>k53~Ut`;SJsa~yF%!@!cRk6*SA_# zG+6Zv6TuOqxYcioB86ou&4nu+YoP7#?azMPQ5pxF2UsVx2eiHA`9GTj0GM$Tynlqv z&#v#Z7G%9oK7Zjklfi&qp;R`<9M$i46Kf|@n@tN$Aeom@*?Q=I=sOEDV&4ErH|F|7 z#9RRZf@!Ku7P009c$g+JS@Q`}-tR3yi9A>~`MA7GO{4EATL&bTiGXFUI;O6~bmL6; z3U-0GnSK2`0DxktJNc0g&sIpOPdC|dg>^bvKYGPd#I*DtIwIp%#p0*pDKnU&&Q);g zZu_-rbB8vB4ay}v_=~YdG&L2gre8*>^+rs z9_jejAKV?_!rw@)1yTN1U%Zz{u%@Mo#kXmihvig>TW-3MHGQ(}G})z9#%T&)sH2`c z^WZ1I1Htn=!l^%BuU69@7ea3Vq7)F%3_*TP)kf*Xy^cSYeZ>~oc3e3e4><2pj}RC* zGt`Lbv{|flv8+`azIS4U3jF?%cF|tG|MC!EAZ(VT3zr@lY6P@4gIyen0(}OP2fL*4 zi*VFA6z!aD0IG2wU7KE+@YBj%0W1X2K3W}kk*M~Lg~|4yBLSv{#lvj}Kt&}Ji&GZK zsot6>R!)R1s3U)m!pu_+?yViG39GM@qsmK@HB0Hbdw)cI;PgM-9blsEi5JO}?^CDr z;#TGplDpIHqr?jJGt|2tY5pc;9-Gm*b^3>!@+>8>y1E{$P=cbbZ<@W97ey6;HU`0u&{A#^9paeVQ(xI~@3lr(|bNRgG zcdT#YW@jt<$KEk50HC}C*aJ%d&$L}NlI$E=a^{RGn`r`ugPzekEo<# z$C(W>Kl;o53XlLXj2;61LKWJH!NmZG(#i_(Yqh!i(_lBVg_8)BZi?MD5=h^DH#)SV zG!8}^fP@mXHIx0LyA}(!0>(gUoY(fG{Jgk+2+qjOE;wuN(&D{ky<7bvfzDGGok3ml z5HT%>rDFdyFDqY6aGt=Y;_$T|3ro9xLF_Sl5;)c6Bcl63N2Oxy_K-wC^)cJFH0`EFI1!?qN@|?zGE14X%mrlc*GrFsl!-VF zB;!NLTLbBCN*XHK9%D_b5Iu8{cZErGOBHtbh9)_RC~e z=C^19FGE=l)tK5V)oA7G+a{llTv$&A|Mi7Uwc+pFcpc=%76||)flhMdTqzR11X}$U;mMIGowxFf_T+Mg z9TD8;S5SK-`;7z`w|t>>>58;nzsHlg#uu`&|Czv%oL^Y6Si#s!nx^s}_;}f6+B{<4@y0JEfCKKoZ1TLL zmUh9iBq9N14zCl1wGb0QsVxafd>Vwp+vbn6E$o2yu`vA04Nf4&2!FdwWFioRD&ugM zG$-!}=65ic&t!)Z%$3aln>m0J_JD_f67`Xi5PdFRdg`R0Y|W@w|6K;g04>T5P&ac z9`fJWLPGNTAt*@3(QGbS=XKD>7bW%1~KSxV4?qB&VS-RkYWNr zd^w;K?+Nq*eh__PPbIL1^6`@kPLWev-_hBBw`s4E{a#bs?k+TSdF33NNctl^KSr`Z zQy-+9qQCt4l;LDK#vQmM4|G63roe%NCFF%(grckOeG~1;~uag#kb+l{&Ld4O)nKNt1%rwENqYB4S=GA3y^QJUPks8r?Giy zZI%IlqCQ~i%+&OdT30gt>~j=GFU;sfmwu4Kfge`zFRTLn0a<_LCa;S3np4atGY%#F z>^#h+<($}>Fz3fakiYcaO`U-WHq5{N+39J20nvX0BRiDO0{GzOC^j3v{}Y&f=`J4PHzv^6nbEIAPla{n*wmKYbe~ zRr@-QxdlIez$|(FZp_Th`sr?46XH6R3b>dq_OpE*o4NYyUj1q-Kp+3zx3lCtNB;cX z2SA?`{LPquV;RS0M&hP1Wt-9SzfAFl$mm1CUw8U9(_^*={%puML_C@?0)oH0Y?&WJ z`FCUfbuqyJ#@$0HD$H2&-==5*3SywW`=`D7&Ge>#)c@gOQ->t3-#<-Uddy3@Z-&${(DhRhB%b(^Z&^}EYb2eF#J8S`%}L+)F> z+<>Lu@307{#lP;=Z>HzBi8?x$`+wJ- ze*_4i00wPM_@5))f`03w8yC~0|C=&0yg~v$!5v{6t;=_q;(g?jh zX=kPW*t)pzio_pK;-cNk@S_*;nztdlC1a_`hjzLi=R})2Wlv6V1;DqWwoTqKdL#JW zNJB;MCWX|KLS~x6rQXZqp7FddYF@*s2n- z8f^0ahks^lM&m9t%JIpKCq(aQ331bKDI~N=XfjUSP_W6RcvQ`Pphx42O5+*h7itvy z^Y&v~on>tX1pIv}mKS`>(x-QrN9j63j9qhD{2kobZOrcJ&Ogwee{fT|EVN6hv^>{; zX@z%`q0mUNH0&Lv*{6NNV!w)HmutGdCG>3A!*~3#p*%K5tewKLS}LxVhWZ2tY_*7Y zYhGoAzY^yL>rOS28{aV=yj*Qt=tff*SD2Sw++$!%pm@dBZgePopY+Lo7sXK%L=^lU zKd&w3OkQ7pd4Wxp>noixC{W5#GJV3VJJG*vaIafSD10XWhyxbOIr6=LtEpC6a8Fa? zUTHSf*r54gh-F7JJ%Yg~Ap~X5-^P}g3@9U>nL3pR?k1CkGv|gGCk2aOYvp5J$Cqj< zSN%*6ufv+A$>&-sTN$5C*12!tS9xa>mz4s}o6CbS4+fmqE|*LI!pzroYv#VCb|?)Zv+&_rY*A;DGX z7MeWT)bjwI`P#iak&)zMJjs~xxTX-CJP0cMr|H!2JE}t=$%{PC<{=zwhGwhWaZww} zD`yeiGKB(ff!I6pEp544sJpRK_gvX<95PsPqI>RkhI#G_ zul#26KDUu$7U;ru{U`?|+rigygJa*pt_#|ETuh4YU|V((voW)o5nqjyTzTd^A3o-G zgi>cJo1;ukcAG8AT-h^yPFJFpfNSiz=_#Hud8Ksl+E_s(e6*)ae0k8;%8&2G_;>sI z9;7xSzG7QJtD6-o(#F|2H(&RupGY&wM+s(XNGvS!G0tJcDLc%hM^v`nq_Td%WSG$IsC|;srHi!o+_K zAzjFjGB4>XTmNLD7|>;cMa!-AHwqzurIEfDAR%(F0bLoY`K*JJWx!fR`3$kBA3mTb zAzrR4w(hvoO%cCQ`U$@M9vwz`5s+P0CVIDMH3k*m+*4Iqqs1Vj*M#AkbtD_pq~W3U zVX@vJ0%!v%Ka}q_dme*ThS$?~0KMkJd$^%alpV3YTD&6PEm(RoiX=QX+ijA&_LgUg zCRtz2W3brR&sIRlxKq+cX=ypGI_9~hdv(J5L063L>Yu9jjunjQh{22KoED0vS?egD zHCm4gu9AFsJPuM)<}4)FAC@~EocxYp+{y?i)Q%<)$i(>O`Gh>!&C)XSZti^}{m#5#z?^z~UXlS0V^#9d+}WI=PSfWirKyE$j?1xbDW73wy@D;Y zQq=)7s=Ro1PiN1VP@#hQ;Pht$qWB(%Ej69vxLA^txw!@J>-m}dwQvGs{+bw8@Of-# zA)}|*H$Y)zuI1ak`3qmyVG2rIAMA?>td$`9uOA>kEh5k}pRAb%w zQi$KuAZtslL9s$?sP4O<&{-Q^Y(kYkyu1L04X6)j14wl6KDtR4k2CFBz-BqHB%=<4odc9BS zoi^x&v~=@?jux1ICC7#$l^%>AgrawLRFq~T<#Nm;9n?In%xKmylkd$up$fca)X*`X zkLqz4xRCRRr|O)|Jk-x|-h=*xqM_rNsc_>-)3{_4ZPw1(4;gTdYMDpT=o6avMTZ8w z6d(^`5%2;9)%LN1;U)$xpVFe@WNE7{j?~r+j3$)bO%U((KURLhxnvQ$kONyQ2`C$* z`SJJpo7u(Ehg1u9Mi0baBKy4$^HI?BT{8F_lqFk8jXziTDNI{rt=|6_SMT(KSOLsj zQiAV=729q84*ylGG?IR2UWuL?qM(U4qY4pvUKJXw$Z+iOizYbHXXa%GX{Fuc9nY^$ z!i4B=Fz!VJ2EL9nC9r7Cf1|xLK)tzh-2wDZ6&#{r9wVt-!R|7XRs<2xT8-#Kkm@#W(Fz0BYI{m*~>&yU|WDcCg6XU;F=(d8KZ#`Ce! z^m#*aX7seu_*6hEoHj@-*|-lwMlLzUl+JlBp7h8Z?y!G0qOH$`7HwKMgP^JTv&DJO zS*&I|tCq`MTGUYH=7NmEt{Qh3b~zalU~eU}gIsq}V*Cr89rWk(r@s`PuZJw&#>4Gm z&r$HLHW+qsCPZhPRi{p@FYVu7seh-(Lk<_fW25%o|a5y%2)yFzZS)|tlG~{Q; z9we7txIKEI*0j)@Dynu|e^mweVwv(rC1WZ5^Ywk@yjB0>5J zvQcGN=Qgt*M`VsOe&%+O-b?kf0_-*MvlrZIcr);19Y(f?L1*E~A^d75pXb=gn`B`! z>uq@sbIfrn-vvt3P{0|)>p(3XqNIUG)wT4frCjAd)e=djl?<}`PwKMM|#E;3%vMqN*xRULuBu3cQ)iP52QhwMBkEY_3} z{5~(zdvJX1gHG1+@gC-cHlPxx&JYjGLGRypqDnt>1^QE3zeJ*{&a} zC&G74WZ_6#hV3V57CN(S;&)3uNE-<=hlE4qjbsuhPiaAPzR0_22$PReYQ>v}n5hAb_J0FM&K9W46_ibROvyFbb!DK+& zEtej=dgegfgLQREEdhp7>~b=@vdB)`CPeQQQ8wRx_RT^`Dg417+F8U5l%Usv@^30_ z+(p@t+u|vvpBd5C>y=&Bqg9bEAWL<8yZ(8(d4!^#lu$6TR@>raxkD61mi4zj%|T$(O%30AitSr=LB9{*rNFNzMz{0n57Wy~^6) zpN{^q^Ql{R7G^f!j0h8_0C5V_fBT( zl$vD)H`<-C!HVbvYbhi1pB&?;pj}y)ST#t+lA5drYN<`58G+=emDjQgrt0ofx(+Dr zbENkZx4_db#u0q}Xjx1h+vAqFwdvCi3)R7*LSY#^#az%USNG=TM&+4|zbq4bHpqIs zL+NVh6|6z=(PbyXzB+W9?kHFH3&rdUZ_7Q};aOMxTSK2xYA0=86C2Tj;6YFp z)AJ-fg+5p7cd=|jcXd@-v!y&0B2V0`UcKASH4o{ZZ0dM(pA9We*z2snugZXxYO`0y zHd~XOvgmM)5Xs%*e#S#Q ziQ9`sX^@p051%{qB%yN#o?TZx14pVbvf`T_C`gUof0kHWO3@REkCGfzohupM-Y1@w zW#os>sT_SNfj+voMZecVkiSM}lE>X{(=up)xgsN>bOIZ@Am_?46-YG@!M^Qj7kmwLcRTKn$j?Sqwss4Yqz&e>UD} z#I;d!l+bJoouuiDbbvP%w5Ec|((R`5LpFDVL9$Bn%l^C%}bOHxY6rJDbPBOOK#O#+)kO4 zoJAtl&{v3JCS2)tkTMbJL(NDU6{yl$yEJTE#pf=7&~s$lKX2>@`b1g2@ueObq;f}3 zzB|OjNWVHxIu+rkQk_*F__)>ko{e8c@UvpBJ_8k-8L!}E{1Ig2qeXOxrEtWBK55~( zjQHE0DmMNa9ZCERYAVVBjr-HINGOhlN%F*9m9Xw#|y2HHaqczX7Hq4nP zPseBA=JJrGjw6i%QbXO2nz!~kh)c{DaAS0aeNMEt2D)mt%lqlNEEeCp=ad z@~|}i?u+3A74OfQmmGq6Am&_EV8`B;E{TK8YtapW9o)6ZLlU9t;kS4Ec{0xdmfah< zqU#2lJ?o|xq^Rev+;#ioCuO6cD(y$qki3tRF0ckkT+QkYeJMw|*WgL%pgG~$9LPh_q8aQ$!U`;hZ;s3xR9|fK3WR8h#P1~~uLPO0VmXDcB7x;}~X6VcZiJhDET)Ue-86Q#)P$6JgR zMeig~H@wF?JPpo!I|$B6Nj=QQn`^R3AC)LLWQ>v(Kx#_vf8<)#u|>^!3i}FRo9=mG zX~Kh^UVCsr=W!8TKXGUtOgLx!II=m=HQFB>u;i6Zgftm$q22s5aQ?YO<_)s$b-qAp z=Q%m+lKAIy$M!8kgR(0bu%(VA*bIC=Xt^QZs{paJ`iun%~p-FfDj7$X+n-5<2ThTs}ngAao`O3w+bM{v!ubR`$qB$idT zyyVWcV+WAiZ|?9(wP-7hJDiukA49k`)84{>dMoVt2q{%!8C6@SvXy;)S(VC#(djIl z_*&%hpR{gK%ZlLz~>g)%y=lC<3K`ea^}K4oww>W`GG zVG;W~TP9@5h~0z(6_n)0ik>%$enTBHe3NY6c51vPs#9ZwA$Ql0r#-rraA(k*V>)c- z4((mW@6JDtDuw0^mw^;Qr|8yj4udZ zlY0}xuNw{qjaj6x&y2Fw96_wOPxz}U@PF$&=dm4s@$$jdn;0E7UO6xRF#O{~M@#pS zc%RAnEli3)A;PYr5sh~G!VRC!qKmgx*31+NOR` zz~pU0rVv9;{b}y_SJE|+SIb;Vwq^PyYY7tq4o~j^+B$UmdqH?u!6i(OoBr6*TJ&?8 zZ(a~hzNYXg#p`lWd=NFjhv<2nUhqoEynE!59dccWBWoqs&nFdvn-bHZemH8(@7(E+ zyscM~LZqlXSi>wy4k1epK)bkn9_a_1#5c`=^E1FRPikKq`OJwKsq^=okne%NzhfiK z3su$894>SD2+cB(=YR50ko4L4sjGRZxJIdLBC59n{Egif1HQ{By*MSf>RAL6yyl})Y{s4yai2uM-S6a=}r;Ic4M8}Xy(UC(`P06qC!1kqrK1L5{#b zf$Vp@I5&tOsaU=7v3^<_-A0h`7j1;?vV=B}8-xFOAdc%q5g%eEEtEScXcF|;l*f4R|j zfS^n0O;d|1I52K8Zm)%iI>7fv7$H6DC%1Ug8X^$(Ad|R9DL7f?yLrK@03TLoHXd@f zQ~BXo(@0-83;$k^t(ZJZhX?!@VAuhjazBCf=7JuDnnDP7$-7k+(C#=wM&zmC5oRT& zINy(uq(1P(SNK8OZ0#ZJ_0N0Qr*qR~;CgfUy%mA=Pn@M-`i}ZN$xb}MnmE2*^1Ur= zG5&3iJ6kRnjdsAQs0;iEBXjp}D_Pa!YnxSwnT@75?%P{mJv4Avw&CEJE(?B`d#48+#+><)CvKrWxgVeX{Fe38 zbcwAEwYQRc`sEwK^Sx(02VnEj%MwB^MPW9f0`YXX&xWD50@+IEDVKWf$Gtj^(Kn0S zUo{Z5!&qTw&0$81``>XdZzTCp-T6)UT5eulk~_bZ^m^|_{R?w586$*vj3^R&dwI1f zu208R=Kn=BDy7Yy-0dHW9a$=P6-F;F52QLeDkBd8#aye zepS~d?}wO|NMdiYW%uoLt{#d&h9*n(P)Bj$s1=EkueTusC`{j3N#uA{f^&7sHAtJ2 zO?9iCk8_>dO3V6Dg&glkQb9uXUhyYL6i0*aE|wh->yEknMwhLo2Vfh<)(R0ISaD4Yt4l_&M{PDf}PUKi{C#712lr`8t6^B3kP-RKF{W*O1S zHfmVntGsP;)_Po_PuT2?WAZ$%Y=PU9%oP?8Zg4cwsI#$CrlJ`}AEub(L^$X-u`+2`rpS7LkRUKu>9;`bKetkz8vAS#}U|-7r`>bZ5vK z8oG1}=euFDGwn%>*wmuccSi`ZVMeZTZa{rP;|@--B;#`nB+aq@t`@#n;~}vtYqgm| zGhLFMH6v#=N68ac#dF^FC5){+Zk^t92$E&8R^w_H66(tdDc995To>kk{%ozXPnJ1d zb9G~R>*|ZXD2w-4#-*)BumUREk-9b8y*Hxs)^kdi#Ac67eeVO5)a zr_)s(77q=Y5=8ziK0og>o2Yi8Md?{+^!dK0mopjJ_fpd*3J=VhR%Z@#FT)F7NT4jV z2mOwHw-`nF2lRgOw^Z(nO5L42u7tzn20B*Tyhtv>j&4M@-q&9r^YoMT?-#KWKbc>- zk!lb_V9r6gzHwc1N?@bKwPU70V%o8OZRF;1>&AKAJ~-T7>}@(mFlXm8DflmQfM^4r>lYAskn8ePApTdLIt zQc;uNByYO&AWTc%Zi}V%x&`bD5achX!Vj|=_#Bwdhp!dmj#atqdj#!=UdGABdn^z7j5(*g8s`pP{M5`tg&d%Q@2GTEBTOM@jm9gz0 zw|Qhqt!{H`^}6sz*{zi;cMcmf9{M%g&=<2s!-cBy?7{i}tGzD|hr0d#S3W5rA|WBW ztl4X9*=g)zL`+6Z_Utr_r;KGRStEOvA!M2CdtoHoWM9S-&5&Ia24nb6&+mGk&%eLF zzt{Kv>wUei>pu5=pL1{Lyv{kdpg?>W$@VGDtjw$7ie{4Ga*j4yb$bL=6bzSjV&RmS zN;AW6m2#u&`qR_3oA(Z*@n1}yvfSAr);GmIngp^heVAgrd~*M}>8V3Po1lwwqg)f3 zF_v<;7usNBTf^S}Ncvu(CtGKNmN@>JtE0zbIy&OMZ*h!rRx5~)uFd~A)clX6Nv*n~x%;edzn<$x?V zyAr|E_wNNSUgaLnd}CKUYt;accGGPtJNUR_#ybW%uET*flnXRCOxFUQ?3DnI?4DN%}x3QI5%Vg{6` z1|=kpY~1RJJk zNr~i|ZPud=3a)sdR5S$q#>RGH`)0q&F%^EK`NY?s#ztx3`E~`Uf2@g!E2Jgq(uFgR zmTjh*j3L+V8!KI^^`!fiT|zr~g`jj^Y13{^a&im#LX_l6uB>K^DC{Y+pnm zYqgAwN|-(e@;2mWrWsq-Yu3C`5FR;&nz4EuSOXxabpBHZw9xHNw$H$_adJpu3 z6M(EM=Ee&h25srZZL3D`iWruGjgV#;s7U;AG@$OaH+MMcR>&m%5OknCj0AwyAZ^O` zR{^1eA156!qx@gHZE(b&C!=UrKD{2^VS@aQN=5?KU}EGU&R4ME-jdEMDE&?xqMd^X zf6U3j-TDTKW6f)j@Dx3-hBW&q-Q^lDoa*I_iBW9uU@nFSJDQTN$KUoaq~V^QDB4N9K9jkbGM{ z05PQV@Z91~YZA6M*E=}n`D8blk4Lg7c{j?4OO?BolFkAQfE7xGEWSVT5FUlQoxaWl zIQE3yVx zpvr7!XQq+=5YsOj`bZPgrz_KZ>xHqEO6#sX$6U~D|4yM(dCcfE>^T12)PuCu_gvHSdWiQkyAF?EOL0?^6wH2xJf{VAQZO~s z5gjlA^D4GZYLJ1f{>6P``1d|`;HqHP>sS?Tu8`KGKD)3*H-T!Xe9!38U}(dsmrT(W z{PT?6H!b@4opdj*0UHzgc3IV3RIY0H-3c55jjvTA!ue!<8kE)}WB3gRTq2E2)_;0+ ztvGqc#R9Am;}N!1?nHxd{0+l#9~t$z$YbHA?&KHYto?iu9a(qPxt9um^q6?#vKA@J zP^Yb-$cj>QVC(SNR`zp;oZwPd^s8ai$<_t9J-ytI{ZwAQ<4xXr&qDxyI z!{h0I*?c~~fB=B`snx(Pwu{VF9B02L{715ImD(kzo>A_#!7p^XL8GpftP{RSrWP-) z7n4&+e~S$|?Tlos==^;F;k(bEHRAbEbKjp%GsfoGU!0CZB`#4u8g6~Vwb?LuXVBmV zg5Z8XGd>?X98%LjNYxG*@cv|`9Y6-{Bfo#-L8B8{fbE>&y#3lKCjsmad|)WZ6CWOU zqD_kvqOyoiDNQ2XZyJ?JkMs9-e<(8eSlaG)L_=W zhocC(WaMQwp{jnx0>Y#q#vk(LqAX$_{(_VOo<4;m>m%Yi0fQ+!%8s zp^?dRYUiqra0Tb*3K6A+t95Kz0CqT0U(fzXJj7ZwJuljK(qr&U?JbPdjpMr(Ewp3x zo=2hMYbWr+Di7#1j%oc}?xW+UL~nqCon8(V&2sCS$RxEU@2y=%)=SUQ>@%>jE2((R zj@$5k=9Mnpk?0TPFD7kzil-l|Iy?`U#$~K*xZ!i{N~)9X^0o|4d9oY6WPHtR8%rna z(yAaMO9RQ3D+|J@9O@1m!}@NgFZeot5|t;uBv5Ys@O=Ek^TJGAQlkYeRcPNeoCW+F z`CIwEbwsUaY6mG6+DfA}r`#hs3mloI7^AMp0X{fyyJ9u6J006kFc`mPt$T% zxoVxuQflYv0h+TEE*awF9zVBi;S%QA0$nSTh~8PMWXbBs1sXRsvIi(Yv7@VIoz;Gq z2+W*zTmiQ(+DB<{UPY}8)pM!qNlNJ?rn9lXQ2S6c<$Nfk7=SP6)mWW=TK>A@X`R$o z)afmK9UuX$qCocXv!DHPPOv;7#@;^Dw*i*jyY3>M7eQ>3&TXts+2|R4e8vHDi1eZS z@|OEy534%qe7fT3fwgFD^hlm8if<5454R$?c5f*dfUP=RWtTW+*_G^^nKe)9dTYVU zj^qekhst}JRoS$8T>mV0qap^aJONj)47w% z^&gW+6x@3$IqCYxKeqT&G22Y7PqF@2RFEc`*#4eF$XDSXbXyo;VoE_5OOqBspF)SQ1_&E#q%m?#)gxt7kOAB>QpA{p1a6&oRnp zjXmh5+BI9Ird)J+pK$`%NDsR5nFwF8`TXg?A>+?O^i=)zP6MbyJMBM|;v) zz|kOd-B$mycn?%wu>gdA*eF+4A-ci7Upy>g8L%f?3=LhEr)8pOK6rljeP3q=qmNW? z)9p?vwnS$(lHDqId5>(i-h8b&GnZ{j3y#mwZ!XL=j16W|99%3{_sX>?4;ura;}Xm|FRD`|@`*1Z z)}t1=Lw&XP8Jf-DI?|+(7VQ*lt?dn2v%)V7?B0WvUCDB) z71Qs+pbqxaNZ7URDwfz37iBAZ(HboOXOI~NOiEnEgP zp{6T0_tM0_cuOaK(GH#2DFMP7A!XZYYZsq&^LroQjkLbmrl@9JCBH}}dIy;6$x=xM@`s2@))dN1|rctAvOv^((o}r`XwD_n5e#SvclEl#eA}eWAt^ zIkCtel2I)Ra1Sb7v7e{L36?@Mf|;U;yG0_Dl5+Kg`WbP)dn==A+-u==#k`bfHlpS0 z&Q&%yaDeZ3((C}^Z&m|!@9`iS?Ie#nW;~+2-xJAHDz-9C8a2lKBwD85q+4n7<4y#y zC^h58-TA0vzrSrBr<}6|bB$~6Pv7}myZSCv6d2i3j}ZH4H+Ie|x^1km-m;C89w)Pr z$*@0=7+|){yf@Mu!8y$W2F+tOpc-0DvN@DWL)bMg_h>KQVC?n$h}LKWxpZI{W)%s!RaU99$tPWu7d z;LadhWyo5vn5o{j{I9!icaqeU2qn82 z(Ee)tE|o326+QLWRF=tLdv4M#I1zL7Caq>=tci0O*gA3XG$uwE$aJ!3u5)eNd0vJJ z4W#PRb1(XC!Kh$OiIW6B-Gr3XfkxBAH>GFEx=>E?Oa+-AerVDsHg*#|9;LPtns}^} zkhE*M<_fr+Wa;HVR&VKRlu9sb-z>J8EU`cmDW=SVb#%XPn2(SCsag+RISEv;hXF z9NqQLh#+1=m=VoFHA_p|^MVkv_>zY~>2Hw~C{XVME=_YjOh`)hb-Y2N7*-u&kyeQ- zm9wq_JclQ}ZWvzQl{Z$W0&CY=!>$)R5loYR)F|+35nb$QNnDo`NZRCv*#>I$RPh9AZnQuc7?{jA-x|wnpsP5wVsVhpmmx{u~%3H3s=a#bsBy)Z{0Jdx#oNlD5 zTD8tzAq=v*x6=A9#^GO1I%@pfw3e*z%2=6+;A$fvrO7g)6++`534+Lr-8No`d3;3sjSCTj^VR4!9S4uIWl{DQ~U5MEH4(&aG=o<|>7# zUURKAk&9H!@8$miRmMc~B7A|ot<;>zjnOxbHNY7bu=qu8S&#YYEyt*zX@codF>0Fz zExcNx0Z1ycoFBJHy_COxz&WX3KD0T7z*X3kJK~h~#Yam_qJ2e9(%Zne7ZT;(*?~|B z)u#Z8B&GklfLZM3B7}JNTSBEb+)~V^WSX9zmRxeeYp?#BcbO~)mh?8!gM?}ohPbEBVaMY)SSV~K`Bi+`)- zU&VTlI;tr0SnwS*pX83+9%~mY72e9){%UZOBu=OEn^ne`Y-@nMIcB-i`4GSLjxBdm zDAB9fav`03Vw8HD%V?pmo^M&S>K9oHXI)cbjM=nbQg z6FJ1#kh3Ldv5xV+FY|++88FlMq5Q8<_z@+&r`i6Odi~N+{L96v>E$>05D7?Ycgg{f zthucqauUOU_KWbAX?x*fTyY#aypSsGdnWx%;}!TW9@8W|?-&P4w8UxxgL` zJ|JLSCo41SxX$bcE=s$(I&XHxx`gV=vamD6(W>V@n=U-n;32YHv5&Xj^pseR1_|+* z;^zYb{BIX|&M^E!RWG<$?tN`vEqN^iAoHa?ceyd0Fj>D9!}DeK-H1*rEAU6t*A4fw zRl$yTsbTt#`*oVm+lZ@#NtyvbNr- zNR&3^^ItSHo!t8y8&5HO5mjO1$poVkXTR&^Gu7RbSoLu~M zk38`EAyoZ5&52MbKmb+o3c=te*tp)Q)j|FjHpTHiCOT) zba)M%X@>e&6FM^;2QSl{-k&{(-ZRxCO-RqqUYYrGsoV{c^clC@QF&C`dvr0J0Ge2>EZu(Ibo~QIHG84RM+mhk5O`9d@P<16hI$$u#w>cox}cx&8?KfrG29|nlKu!F-LOqVKyTSc*Op~z=sYL$^Tyyiu} ze}RZY)mW~$p!}P9D%3r3!xxMTJ)k-vLfd}JYyPoR{=ag_^bm={_#T$+0*krdhhUrL zly4kW@lbVwi9@5)wqx-Hc>$ly2R-BX-w3MKsGi>q)EIdM(HOEL;J z4vvH%iL1j70_r#-rRL}Zc!7l9&P{=>4u-*etD`JcYgy<^S+w}2`FGpflBb#7caAbz=Ta3pL+iw#leT@yoq0#g! z*XA#mbHLiSpV`U=YZEo0Xx@Rv3f5`?W>UAErefb&cprt0bye38hit~ip=yb6#D6)% z+#}O1<6?1LYt9Gnz-}%|*wOk1qxXh_>5e*@yg{d4(F++%*cO;oo7Koi7Hx)+z^-}a z8}uqrE;zr|^+Li+;8`Ue8++8ol4D9Z;|U?@;lnUtq!lX6p3k8Q>az10k40TV{mYJg z1P72|WMsIf?){|}k#G*JHN)k0WncC>?Hx!N_!Qij2;~>LE)M`{1wmXjWx4mhqRMRY z!ktIeBzJ66I+gJ|)hSnCTm3;rtc}ZOi-IKM*1PVu4Cn)^E`728PMRleHnx`vZeRV# z(@!`$I4dJ{w5+~R`~H=c4H!otVd4kKX3`nqjPr4*79;p*aly3L6N!_o0NYFHEkQqP zU_lcT)I)K++#QRuBDsYz5jGwE9l_x(D(%4}NuB1kzIH*Py=<%Rt%q@_)uoIY(6=l3 zGU>wzumA-8q5bWA89A@FYnUClf4@}gzC+p>M}8PRD#q3W^qeb%VTpAARLbmXfOgkoc@p)Z+3MsV_Var=cx=*K5KFVS z0Wnx1dGf*$jN$ixId=E5?6*8|@Ci(2Dyg}e;_}!lg_|~wpWH~2YkKJPs1BUOkVtz z${{f|q@oI3X_adBuQpft;jg0}p6q{pM>PXVk{B(=UdPT54q*ky-~QgJ|L)5BcYl^> z6vjxm^|BElr6~0L=T*3~`U zWm_A?Z*c-i(4eSmSS2s?jDNc(v;1|dWm02RKjVgPL&0D|*k6OH-qabDquap}{%`&F z*R@YaUKa}p3N9Ty$cS?Fr&+{neVZ3P^iN=)WAmW}td?-VuIR{vh*`Gps=e z@Ecg5Z*KbYk3Xk8Ie_2w$9=#z!BQ^X{r`G%VLbb9EBY@NJz?wqwR+c^%avQXg@jyF zUc4}*mipHdQ5AKOq6X*m^lI40slGzKGh_1oA0Nndb#+0H!2YOsF+_GU%sNw{B6Hsr zAL}OSKwPt8j5Vh7q9bMV%<`wq44Ruu={JtX<1K=H@QlGCo3co=@-lvCZ)aGoYHDhZ z>*?v)egFJE)oZ-ZZm8+G-TPacQ{TRQt5=$Y$K##Gm8Rx$4Y9Rgr=bHH)@zuiQw*O< zBhSs&$_uD?=JKZBn~}wsJoOh+_v`%d@namtwXFAzGmT8}EaUnLtYeGMBc<#1P z_c-mmebSY=N=xTfS=kopum?hDoi`M#a(FH68s6MJ5S2_RKr6b}G<_LpHl#Q){Jcg7 zy@Wnf!@(Kd@V(t{{ODeQ#u;txn1IE(F5jDiYCfG;=#>1d$r4SiGc!W%z3xL;r}&ZR zgV2%Q2y1z~7p8Ld%$aVy`J0bVqN7dW$+5AH9XdKXT@s_+V}Z*{i_^C_q40Vh%RBA& zcXx7M&8K1CKn6S*tF$?!{#8Y04t1P6m5Pul?^ZLLSAR4JjuS-STg9JcG{ywYL=xwp zk*?_4=Pc@(@6x-I%nZ8EDN|py+ZziH_zuL{;|Oo7tE;~km8PXtTMdz6@C@YLyve9< z%$-?q`>WH;w5a&U(k{tdqtKJ>0al4bvO;8y3q`Xkqh84=@cdOXQG9Q<=oj>%5p+)C z_z&dt{-dN?MmBC*yxZ=qi`#<;tqg9E0iXMx>oR5wB@p z$SPXEv%>zj7zW2Vs+X;)m(1rRi}+~~XbH>=Gd#A1V?UACM(fV@^gKdU%PRv(Jx4~c zyDQIjl_k$p48O_D2Gc8fWRgp7ci!IS<6w<>=VOX6B2~M<6Stf=)FrWtpl|?&d7USK z4xEfU+c5XIA+ikj(PfvQOULjFK^5~1#@zBi_x+*C(nTAb>R6&dfMa(qdb}%n4sEUQ zPq%(KVldUeAXx`hWf4C#yM&0;edp`?y05p7azFV*#h6TgcD-i_#k7jt@%PB+>@+R) z%G^o+hTfZI{pQ{v`cN_IvB)H4Sgm+w2AmUeH9S+GBuU; z==o+w(M*vPHt=C&7dAd(Qk- zx>$HAsA{^tPM=fA=%Nj`Wk|P$M133S?l%YRI@oFU?$Z{> z@%;{g%6Xe|eQePxC1>)DNb=3HdY9=YZc;|YuSizO+PPK{wz+w&?zzaBq`_|gqDilk zvgEpeib=o3H_;Ca6Kn;&FK6^NBo=vz7JbMK{}TQJ!`P2`ee=nJ5CBSm#7@fldwF6V zh6*#gV73?A-7kGlp<@@byqre*G!L2AdLYN80!33qGhi$)=2?-;=pw5UpRPd%-`=MV zF5zRG#Sw&lhpxI4UK{>$=mr*G$r5BVA4p5=%Xv-XZso*HFdjH2%s|D>(1nAfKwN--)TAtY*_C{*FG{VFSA{uWsE4dvp%w zp9N(u&MMZe6Z$pvW}H}FJ(L`@v0<(uATF^5>_c7)1)+l)s>y+(4SHd z&j`=N)rX?DrTEXz3wjLRBZ@H>e_kZ=eY~RUbS~Y5!Xf1)rH14CJe1eIczY|#>014` zZ^UPdA9R_UtYwm!@(0)Rm;2saY4B{i%XeO+!`xOhOl|0|CTmM0B`C^*mYQ^6x@M$A z?F?*kz%qN$8MW>BWD1cw&Jmsnie#6@;`|GeMeQq{)@60JS8f;8}*hwNm;HKYoY z@=qy8NSy_TnE8nxWw-)AXWP0f<`KQt?WBOL`}^cRs+%U$MuJHErPskP!`g0+Dfaj! zXb#dFD!m@Q@M_btsM1`eJ`WD_AyUF}N5n9`+t3|cA9k2cB_7Ju-|nvs7sl%i4d(3X zn4`ZK?d>lZ^!ArvyA&vOO62~cn)O7d?*Wuy5b5zdTwEqN-3CL9yyw?FX38!x$Rnw0BV} z#;lSwt;JYzv7Z(Z93?mKl*ZT#hpc7S%XTr6wee3Uhl#ttSe{RWQiniZlbgm z$zUY@8xxj=5aj&8bDL{uk{+bBdxn9qxO=o!Bpot%r5Et91Wi7s)}p}wKVvW zSGfEx^1V$Z(diHFHQ3K36dAsjYs|?ydwM-Qy*v^#sREHjp!#Qb^gHpVp?@CJklNJy zS%>k4th#@P)BSt*a&GgLNG2O;!+n#Vs)Wy7LqDzqoLrGTS!uLniKFNq*D~22Xw@xnI2g zyDF9OWI8HyDT)8EQ{us4$!5tflI)ohE^1Vm7Ols_QoKX-g{U7^J+iYe>PzMzDM{uB zVR1x$E{~nvBfL8~pT#SxwVUjv`BIyN`@q0Dw`P|3XO@2Kqc~V2#Z){l=1o|@s|+db~TpQk3R z_pG7y(+0iv&xQGJ7p*Xwmpoo@Tg?x>>Mj3OazL5}XdtLy{O{W2|L-qC zwG7lFo0?vI=Tr~;{#2N0dS0Vys>e*uBSeh^YgZxs&!Z3Vt(`GgBX z%HRJUeDfBh)0kXz^^d`QAWrH#Kj-G2EGLIc zEGs&ED)9S2!Y<9wd-E3%X+x;SV*QaV#2wmkoO zp=#k^=B5oh$tytzZ8<35Oc-Z=wF4~fj~|_ZCnwmt`uzod3-C)2x6Ei~DW9{oYy1A? z6tCD!uebQhMGViwy~33quA1wdxE4l%0-yxyWk@Z>>q!@EG^EK69gdNy#{l2ic=V7bQ)%*QOy^QV9A&8^g zASx&+DcO|=v8TFHOTTq&M2=R)%Gt*)&d->2X1;yfd7odg!S>3P_TfUm`Dr>N->3I3 z&M`8cq8B%B+Y|HT$!=R2CUijAxAp)lUp+grjn>j~QPQ_I+3wJxLv;@iYT+M7pCji~ zSL?oUY!z#q=ITTQ%+vEb=He`?oT#NGYUe>|FGIPMbZPo$*JG97!iitEfqR)0wUUL;%g1VhmbXL73L!By9;s^z<$e? z`+)PA0(&D2h$0(pr$C`DC!|i>4B-X`LPA2~AxSY}A4tK&vr}%_X6nPyLWr!N6l!R- z%S!qNVRhMQHs0Rgkk5wQTY2tNa7E3n`k$DxJnc3y`ed|bLv=OV+xkbsXPwKO7@3*+ z$};9TAM)m#ly#!I!uCi_;LYN9UU27We8`tZchb zYR;XdkkxmJFn|gmkl`VOB+`2c;DKztKo+J1~EkE7F9^c|2N=AdE?z zb#;>tw6PZEcmr(8$BW<#9~2YdtEUhhgqGnk5MP4!Ac-R}FSD z(m5uF4e9*iZx-3nffVTHe!QS)Kwj8du$~LXPJa|%xsIkB4|&_3qr?d>Ac|ow9YtQQ zasdn1%UaAU_AuZ=qOprQjqD!YcI0|Ntbc>K}EtCiikn`#;wGLPjp z=a=(CRSP;K?BhM;s)>nP(&xfWkAK_GOg`ut-W#0K+sE8`>`wBhAa-s%i=dp{eI>u2 zSA(&Xd{;$aB<0`c7ENB%8x%ep-iZ;pA6A?hBKl4=QuIRxj>xf_Zb0GSSHPD~fLw$G z4KsGvHV{f|=r%0R5939nfv4#tdKH*ph9DH8-bcPeh?&#xh_OaC;8LPv!tdRaO;1nH z>+as+(bkVkvVip{4J}M19MrlK6E2{FI28^q2AQAbg?ig)iL{PZE~qF&H(0d&`h8dK zCMICTWfMJ(h0riLg9QmSpQ&RY+LAswjeoLdzfA3G!F`M#^=#}=C@sm}sHS+~URW4r z^K}r@qw28D(!&D4Wt?{{qKg?NIX^wPmWXykhqQQ^p3aA0tYg5zm4AT1UvX14*G_8% zh@tfWcwRA3e~T%~e+S|JkmLZTSHa3qA1+i|yRGn;65>)2^Lg3J3pZT6aPbavLsr9= zW&KRocIDSq&=6M!=NKzn1U-tzJ|(|RQm2?8@b81jdF)!f3cvcR+2HL+h>@>f=HEN4 z10Acw{&Hn;@v8em8hukE?HLO`&%%a#i}GVty>n{n>j^}pn_KoiMVEw{PoHqD*jdOX z_3+45I|F=Nwc0S8L)KX)&4Nn9O3JmI3;H#Q_K;t_dL?$`h#nO!2F%gz{0enjhu`1c z;v0R%&dw)~S{7J_T*c*u>PNzv_(t^z0_1Qn!ClNC;(G2$wsZUw@BB5`O`BXQxTG%;!5xeDg6fDNZAr<=yiI?EuC8o~I-W zq6@-#hk6zCAqySbR1@P;eH%m45dwTcf*&r2uu_FV~e(V?}a; z1y8o*jE*{gIJO#ZJ0$U5nbt-Ifa-#~J|Ip%epGR`iJ5Gy4nS^|ybyI;hjzqeMD54> z`)oYNdK@KV<%!L{Dl52dEOmtL8vEfbH<_z`BpnpMkw;V~WD2&s442xTCn7D~Al>TO zSB|)7F%QqN4ecES1}j}w@B0WYXD#qo;ty;tja7F&?{3&h;M=!qr{qifrzPH%mX^-r zf=^JqEOwZ-DPv6HqYs>i&j8T6XoQjKc&JOY(K&mTGA*W4MP&@(@lt}G|ls%}O6`zCTw0CobMn3o<#oX(6f_1G@;^esmln!@fR zj`!c9hpX4^{6QA3Mlq$quLYqxvg8Lla~F3soeL(Rv)m3zooZ82u6xZ%a&2jCrGWzQ zyd**m6ow+p#vf`jGyC;0t|$RrXJ{=3FLbmA; z@3v0(TiqB%@Am%5O>R`3ypMd1bAi?XF1M5Xz-kRke2T3Fna3-}MtdB;#^Oc};6(%4 zXUt3R8+RP^(Aov-9W^@U+E?7AZu*Ke?%sPrr^9ScJD4OvswhU>zg`9Nq4_aEgoWi_ z9E5NfN{Jg{V)&Ab=>C8Hkzn{XG&CrHfK+(&=#iMBVn^2B>es&B9R{%(dhg!ET}QK6 zI_p7G7>Z!y6x;AZ8AVt`-+giC_{iJ7+9HqP%Z(LMXCW*C4}t1U3ub)GJ%PBjy49rQ z`}@^zMIV4rNmFhQnX5LkJ@PGA>j+w^i^eMJ6ziq)Mq{`b&^XCd(fcMBFODk1Fd?(s z&ri)*>RhxuZ= zUsADe8~Q0IypowkSeji%lxUE+QPOBS-t!2EOj$U^SEjM1Cfh%=cM?=Tgk!vBBaELD zBZDGXwK<8V&@X+Vk6V%+G{CBwe|Yrt7I~RY=FArxycs!F6}chj=hDXv(tqT z_G3$Y7_y?GqNLKL=-tr?Wc8Gklyg8dK;;m!5s`cL?Q_h@$x)>UoxJ+2MahAG#&7(a zLG;n=#%6EEP;-TQlFi5a*~4T)Jk`ByahgJ(08&Tz&KfYl*G|lVCyHN>*YyZcg`y3+WzH z*+2kwi3a%3sCnw*&~@>P^5_33(5vUedgJj?JaV&p=MVjF-Il%?e<2!8Xc4&XN4oHg ztwovT2)f!O0l_qZ(3fu|i0Eq#MPB0yph25gsf(rT;pL@dA+b^2-LLanr`zd9Ttbh+ zp~DApD2ysOF7;p~nE^b}#f3v6o-y?z5P%$SACNP4gs4A#>jDVGt9y>2rlj!;6i&5h z5pw^?*Q~sbB~di(5WXF4LE8v(%#aF2Jh;@(rCsGIrClDLe^$@PC_kC@v(I`9`*pAL z(vbtDr{`krlhTPsq6!Lmr^lUVNKbpb2u8TbnblgqE+riIloy^Td$^)2SjxM{=j!7X zDGaWPzNprC1q_bZ8z+w*y@rwTrfIuitGq5>Z%)y{a*>q-VUCiKR6ZOW*8V1oNIqM0 znNdwH0DQgM-EFZ57?ycZua;gyuqm44*9h?2ykShDiex09RUyf+lyc9Z`qN34*48yw z0|}6G4#|YI9M}YjCZ?!AA&DZ4Sl3vMr@4}BabGhKSKg9&eDLq@^v&;6_DG8=jm&mp ze3A6cPziV@*abL;S^el8mBO?`en3v!jrWznwxSvN7x03lX%Eu1=wBWzQq*ShRjKKDFqKJuF9K1Bt*hbS|g$3s}X!YgReuj zLso}i>`@2Ef2Ym$vlEu{S97Q()0*v|MGW>p6CU!GnDpqOCZva%>)>d99tN@KjqgI) z(V^!4V=4DWQdl-tiV`b|C4!OiIT?1IxA{O}=bef^a5F{Um2~9M+h|L#_J-1O1nn=9 z^bb(WC2PTXOk1=Iu@t|`pYJZp5@sg;0epNcQ9b%8uLtVdiFZ0`%o~ET`8oC3%kAI* zSZ{FeB*S(fr^Iioc#b+`+-1P&C;h(VM(eT8=QXA^J`dyi>Qwdxd5RtnjEK&`jKN$D z6rtmb5uPSL$4y?seOzRxp(^gcer~+s)h4?cy5z6*bbX@%6=xj4^Dq)jY1=~W)U2ag z`e?mZ&rti?_>LZapRv^?)Iu@gSd$x$iwt_#X`XLiWGtB`qXWyk8S$o)oa2MT3nPc{ zJoLuoO&!Olu-6uIAS8o87c0k0=5K9Tv0|VT4UoO|lWnEM0!@xUS`?|b-;_v)J?{bf zyXb)kiMyPuimMpWSBOPR0v04lP8HP!vJRR2m+gY>a5o>NHOc{52Ovi+78?1Q2C{lYy5GfWA%3j%~suB0ipwtFkGl?CNruCx;G zxoK?76>$e)H@;j8_s0kD@+XduUSD=(I8Fmi5#^$9T=^DpJFOd0lJ@kd`DKp%CvTiwRY1ybyJDix{GvnBL-kfw z{{MM8QC~R496td>d5vxW0wgEL#Wl2kBnOo}9zh$+a?%Y|;x%q12ikwFz}eyE;eiDh zHdRj1IvP3PZA6`M45|0?``Z~0Kz)-m#SUwT3#Zx@(q=_yN3Y*`g=6prDJeo2pCC zNyrc!_HEkgTw{}8@`3m@+W;7$bkDh=k{)(jx-|_L9V2F@w6|O>eO1@N7|uCu?Vx(V zi|Df-Dl5IizuCSGYp|%&{&{GHXs;Pz>xM#H8u2_Rp?|^K+X@N_qR3eY(xIVa_PimF|;KibMlrmkB-+L$P03>XrGQ#|}YD~c?5uj^4zLe)m9n1fu z+=OoP9`7k=g$A06O1h(mR5Rv$9Fmo{&^wBPL(4|{N+^Q-yVikqU*X-=GAjSOw1lZi z{ZR25>ygN(0B<4c?b-1C>AWap6yDThKj|-sj9I%K{egF%h^|~h+#13o zNSYYs{`FR&yt~LAYs@3X=yAUUs$|=VYp?P!6eh*B0OKX18Az(5xOd3*eHUM#?)Durhc%8BEP)Y%rlp1|!X%hj z$zuf6kD)KX5~e(}@(XX?XTsjU6m6BA06;r{_rD`un8yov0-h5^7s(FmMhDIA(>)w3 zwE+oZU;&}U@N)!~D}&~M$qxFws@V~w1xjikp^B@+C%o*fK6b|EQR7OrmM>TS`wyk% zbeU_#&V&4si2X{JDc2lMK4WEL?LJStbr9b^Xb+ zfO9sbJ?!l4)NhPe^}d!EM4H3UvghpQU0$Dhz&A0pNd@2zFy^60`qv($#k4G(z9Y@_ zd+RU3CDCMj_BB|=jyE|u5{w9P4!|MrJ7VP5L{kM!_t#-P`kg&EdWcA2P_8!7t9GZq z^drI`@C?;IH|d1hgxNmOlL0M^oS);Ho$e)HGou>@ub39==)10Qn0zZ!{_du1 zkqmYHCETqpDHBo->t6i^GMGydDJNM;oi6^CGp=;R#T;;3LMMj^p41TP$^H%4|K{oV z4j39uZO-e%v40*Sz?}FVB9Q>v!ob>;kr8 zTi(0FxXY$tk88P#`Y>w6FC;JoDwuDeGMv6V-|(>e3|+qGnt)kVhxc3emRUWHc@C#b zWg}0XJkggjy7Zi&@62wu6$OIa6fHEA;WcaAu*r9{TY?z?(bWR<7<(&b~jxbCO?QhfizDpoD`^{j%9MuFDy zbd9#HTk^?ub-KFGpw|D~Oo^K2_^hXq+bj;su=VBJPpXW>lT zz?N*K*?X#Ak>+bFyI9CLNx3&XDKpyd#T%l@vGhhE7wnkULef)M9A*L^OK84Dp z9lze@Uxy@9M}KnLO0D{(al7W@fQ8UfkKzNfN2Q@1S>L)}GY0E_1vz&u2rKX+p#9Is zLDK%*q{_#=6?45>zIF01GIK5>X6p@%OyPl)@=JeSpCjQvcNNda5&NDbUV%1b>u`{s zVpjxLTOZ{^cyWuiwl)-oEMV|~DP#XQU4{&PMxY1da;~(`1eT~O+AO`?JUbX0{YVO< zL3=u_>Uwdo_i~5z!sGXUJ_TLl58)zzJT&@1yY`{E`)TJ*wev9@WLvyp)&(~y6kR*7 z1;=oV>IO9TkEN_Mb--g5#xYH)>^SGn9P+)PpV@ACE#1jYrj;bJQLKDTvGI5PlDvb= zR?~lMd6Vkhl`QbNnd4s`{JGLKKE;O4V%A-kufJc0wv(IuXObW9RKrIl7RZ1Jb}M@j zY}BcfuYG3&UPx3HyeJlfwY5iQ>Zk|$J%-+mrG zbMkdjfn6JHI@?u{8pj-?T^nl2R*z!6Z%1=-Uv2XIPQA@91Y@+29{_Ck3a9vG(`Zn1g0^d_xcCZ4 zwR{a?w0(FfCWGe|Pu)}!g8ejlgmnJ!R+^elrCn;cTw->zD}6>#^!N+h z@>0UWbo8WEsSQ;{4E2vltBmr;`-ZwFC{}^ozX!(3t(gxmupL!0{@F#6fR4Yw zMdrSkYGK^_WvaE_NZg2VG*D{!hT5I{w5M&EjS|jG@MHGI|FxK3#K-4$gMq!`SXp%BzUz9q1r8Si=Zzyss!xI$J-*TKBZ+wvkMwD|f$UC< zJkQ!TL2b(XC(Q5g?611o;@-mp%?LyD04_InKY6kl3D<~ZNVdx6eESj2D`gn=eNWP! zy}Nep60^bhRBxs62h7RME4MFI_|8T@wa%^$vGz9%{uU;vn_sI~_IBfc-AW`?4R1kG zulHLXl^)2Z;`3@7M)qI1XlHc1%zIeReJR)<{~_ST=ctFf$sPCkkd93$Uf&oAMZCYn zHP{1Mx2+8V0m5_YtL;j7#w8Gw;$qsq24nyE@B~z&T%H-khWN#gZ<4N@htnqW)vL41 z=kkKn4<5nyzSCjkOj~7satgfM4YLI;@n5R|=v=Q>tquuhKNl~?)d~Q)wm6t{?aPgA zbwm7F+w@S2x@2xs&Bj9aB>S$6@zH$31?5CdzJf`0IwE z%3Of2y91u^UI6cKRDT$*0gH-qpPxD|RbGK1F>cLAcUPg*>6+5}e7b%JQXdWcuunew z?-bHn#xR$eiDdFEv&U8Wc8Fan?&t4Wqoke_7~RsBf*Ild<1tBtNrU@Nq<;v%uUJqS zqDq;fQ>okMemwMv0<{fv4py0kvPdxFeozn~KRfpATLHpj9@!gmoUhnj01y7dh}A-m zUqhim81I|V+Ur4c%R>g(OfXJ8YJZ>umyB$6ntlHKUVL83)s}!|5dP~hPkYF{Hbn71 zBdpBVAwn<4<8i2f_p7sTgG&Z*D#;_D$rfwFMTY5cS-?7(OI(>5V4m~8h<0EbnmYQ4 zf|}6qQ%OrH%x0(YQhU&OB}=JP-9G9})7E)!|F9JzRJEI{Epp z`JV*NYpR%2<;6Z0At}TpwZP~84?t97T#}S(UHv*TI{q620VOpFGz`;3ZDook#KqL5}yV&V!8uF5u6z!AY@9%r>UPk06hD18EIRB4+Q2cYE)zi}bD5T+- zrC+?`zOJ0pjU#d?%3WL0Gw=#t(JU=vxT`-yUh;)|80Y4mGbI@rT;0F#dbcDfumFw~ zY;89o;WSLw&AxDf!wlFDXqJNej#s736)OQ=LyU;WKW1PR)NklMgp(D9@zygNpO za`O&6Kn?vfCsii@bbCqNR9N_N_c5zc^U|^O*Y*94>OyTAF~B?d&jABa$Urry{pwIu z*voZZnnRR6d}NzXFgAnyI<`DwLN$UhiLIR5KK~Q2uyYhojNwSJ#p%3q#O>WBf%$cB zG2uwXxjXTrHl!UO*niE;`U~(G)frU_dAfiU?dsexPjoK`ob^EKxJ?XjY@AHG=fp@~ zTIxbV)(=lRE^o~3yjSa?;$S*f;9fc68Y0VUGEBvp5@C?F*ABdkH0ZcFr3$2SeVFT* z&hIyrulbmp3vz6@rbb)`PNR30){^SX^mPxd<}?-?`wNJnD|eWB7{~9FS_Yi5dxTaL z-N1Pd@3OsoF3$Lc=BZEO>zomR{V7@VWrZ83;AC*X9i_O z8a-srEX13{+T1@o)3>VyI2b~{B}(&Kdg=E!Ed~_W4qX|T*p)rd?ln!h88(MF8tAd( z_u`nT2l}*|kTK5H)hpffpyy#b9$t}@1GG&1yiuNpiHgVQ-&Fo#^#RK$b2a zE?W5bm8NA1&#)&=_e+iV3&#?yAdhlwtOtZZA?up)@w=OQG_8E@m0BYu`E^BRY5CW{ zpLs|zW5WBe5$nWb!(v4NCZ49cTWLC+FDX+zHO{}w2leRwU~Mbl%>-|xw6}3eE{VjayOA?WL;4N%M)p3 zfC}>FFx%Q|{OJbKZs_tWXYbi4){G#?;n8#0N@Ipj(%-?P=#1A4%r+CPi*Q=Rt8ODK z#902=HYIji1F|U2F+HF4Nekb79u--^b#28sjQ1KSbwC8=ALHeC`z~Ni3jc?693XH$)=OU8650V9#mpSBN0;C{b@a!?TCnGk38Wz9%Ux_Zw9;DC7{B=RiH zZLK)m%#rq20tZ;&{sS{@kN=v>P4>a3%_#%saskv9SN0UD#t)k;#a`d(+~nVOl#Rv* zy45SZ_x`hOXqYKf;iH6cP8L_G7vDHiwxte&H)JGwiggrL&rC7od04Hf9V-$p$)AUp z2O7XtBLdX3f}i5I>CY;G3==wG-n-iejy63V_Wi0mRZH@Bx{5Wtwnmzbo4`1Q3>v(= za?yY*fsBhXpVIWX5WET83iL4_$OBmdV8bj5>8(;%6f%8+@;pr7yhd-2s{5Yblfosu z9MSGP0u%6DpbsTBsJ2dLt=SSm*b0!VYy~s#D?$9wW+DQ3>yyfM{!Z9BMAjL2@@{5e zNv6TfK}n?LFOWHGo*ZrJ*W9>oHJ!VyQ^myOy-|rLkBg?27k+=C>h52@SMt?H9j$$o zJTzKkoZRm`N#3G#P#E8##>fS#!=bFXXVaqk#S9!M+F`$gM0{(LPR)i;NA|nzMoZmq z{AO-))_u>&%BD#R?Fs`G2jb=O=b5uj88yP@WjPwwWr3c&?#<7Z0)$bL#&p3VtB&U& zkFH6K{#5btd(q+VlB{l5IrrdH#%HFfxHc=rMmr;c7ZnsqZRZksc&X_#bK;d5tR2Zj z(+tjU&;yWUDxya;Sy(A%?J3xfGts1rhSny71deGXI~+i9_4^jy51k?|k2KGR?y3L8) zZpRl!Bo|%xN%FM{jy~YDMRstk@>G2CjbHT?$jSAI$K4*N5)i9mp0>qNElY=vyCE`6 zld5WkkkH&Lw0{R>@Z0o1-8zIH2c&?2YY6B!=LY0%;c^$U-V`2D`bA7KO@7LZkbc7C zln?`6%P3<1JwO!YP`xGgMiIvz27P~EOJe=Ipvr%KCMzGO2%r;ay7+iv^fPr~+~e8i zS?VPo1BBvPR0*i~bXwV$*BTj9DmoD)OInBPtkfTYdsONakbPS~-DgX~t<5wPmdeT> zPX`nBA3~3uLbFeRSh52mQ~{R@0$mxXxcQJ4%|Mp+Ym4ta5F>=&@uE;^2eQo~bKE>> z*VV-l@j#64{k1JNTp4usft&4vU6>j6JrzaqIJxK{*Po~K=QY^TPXW*jJi?3P6HKQ( z>3?$+Lt-0*5~|lPn6$LF`;cl+gYox#t4XfhfZEzDX6^h}84rlzd3*BPN*RRX*NH3}zzh@hXE|`HuOH9Sx;BENVq|i0oQ5E1+Gl%#-Ct321{B(dhiy zmN-y{el^{;RIeK$r+CR~jcSc{AoWza-q)~6(fZ!J9;TFOtYMxc3JHzUa$JL+96|Zx zS$FOsj~FADte=-t@Fl?{*_kBKyidOxaC2Pb9Rj~9WkZ99%CiB{PG zHkv4UU)~y|%h#{#{}XSC=aV8GfN8NWryQ}7r@zX|Rl4MIOo6@k9s2^-<1W67hQDjv z-u)rcIBQbSsRJm&bNJx&@LC$I!U*~yWEKI%80RZwIjV@EkmO&u7B>VWWh)J0<10`kN zE1PqLz{;T>$;f!Pi_AA6d1Uc(cU2wGkBZ=koq>W(eO-=-00SWS&K0C$sxzpN*XzQ^ zk4Q}4bu3Y%kXVdGw%s!S0`c$1O}oiVGdCj?m&$Ng!1hT&sS(in+-8fMn@`T*4`8jG zJ_DyEs`YL37X`xs*MHQPBt^sK1ito4{1yV4?tQ-AH@&Y+He%HM=B<3-9miL~3uy{O z?VI^`Tk>+~1F>&FzYEuQt4nJ)fJ(9LNN4K^1jDH?`EZda*JeUF028pMwFK{o+ zh^FKvlX=W$!>_@fX~I#OUF|{d>HFl!PBYHtg?ke$RTlgvd#l-s#?B6E$3{?*_lPdq z)?atMhdnu02Y&cFi? zq(Z%P>K5D@-eW z4ji#J7s;Sr?~8xv5PVa8rK!V4i%VtBbhT^KHQFi=w-EGG#U1H6%BU8@>6ve-qj=!l z_za#0U_=+v2CmQp0)F~@`3JpeFJmI&BKSOwHKlhD%)-@1#7(^>D|9Qk)oBJ{g@Ns; zBYVnUdtE0W1IyN^Cp&6Ni0mI(% zeM>oEzOezzEnV$fI>#sCglXs4OB zOa9bvAy-e#$T~*VC<+tENh>A$6r*appVf?{HdP?TN1GcP{wBRV%{*(c4=gOF;qYY| zCudjQ0bGtL)#=;9q_WvgGQZG5`fJ3@b8Aa$nH8!2i`aUY{3~+yZL4*Utk4HB9=D%TKEcwH=zXlP-;>PQ%TcZWX9tL zW2)R6aU(?wA>Wpsy&502^PC;aN(n~jD_mzHoD*#?}5-5AbpX$MCqh!bj`O86aO+5$U*kYf}*JzzMN0oEZ%z zX4<3Zf*Cmo9-Fn8P{C=J@92jLK|2iiyO<+c)GuoRcK@ z8)UzExzzRceX3zbbkf%J@iS*)0BpzHc0IaP@85YJNI!khib?jAmInR{{qHkD*sDRL z$e9+JT|=(tC2RI}*w|V4mg89^Z@X2ezn3Gv1xs-rf~FOLtE2_rVhpEorp$SZ z>(O0HqR}PYU}Ao~qq37sif$kD(5UExFAXIemRo3<935=t^^gMqp7##|64DP$;Ns^J zY(pgG2JR#;)s>3+g8sxU(=X(NU{VIYRK(}uV8li)GGEj6p?bd{?9Rv6E81a8k3L%8 zvGjvJRswX;&!PxawZtM%LJsn7-40j5a~0?1JJ49+AaTuPXoyo?_V@hso1B( z-32B4{EWR2x7rT2K7Pz!9D+U8Y8i};coX<}7)jW0-=;rPu8Utm&LjH~3~t~YsF)*K zUKl$~Jf9H5<$+opd#+};RN@r_@U<>=^l0M*u*{Fo3Y7`~Eb3AB$MvLE48M*y_iE7k z7b_3o(Y!D-s$Z*UQWbbDNdxx6n0+;i33*64H)+b*d`$0%MpDZ#|`+ z*cPZ>b#H{&&r>vKS<6wn6wZDJOciz@?HUtgGZ)Yu%}XkYJ|jm4x-;&?3??DMiL3ow zxS_~P-^HPXtx*e(l4B$q=xttHn*4F%&1L^HpdW!XQo~qJ0(wvPT2!l*8x@*jqQ)-` zUSv_)Egx+6&l}}F)I62Zel6GD+eDY3a-Zgn!#qKHWkY)<2{XHSC`;|Y8^}74y^Bn$ zjnXLS3CedsmF3N)eYsa=PEvP>1Ux-3(x-n!XP~o637Wy&q63$)Ahj(8nqV0=W#*WM zGGfGVQ}*p63x^xkA+J!-jAz^x+Nx3p9Mh{Ckj z6R=1B_OWQ(nZ$sfrTosd4{ge7!IbxDRPS(|Ts;Jcf|m0!u@7Z?mni8iK7gd-Pc-%K z%dbBLz-ewy2diAc0lV~yQM0q1jW#klMvI6q$QO&xM5e?&c?l*?huF}eBQ9zU)2e;M zD}=?epOy`K=C6Zlm3qb2z`dho7St>MWjLbsx5$?tgXTXHiAxY^8Gt<=ZK?zr*i@mc zxwP;}a$rWLbjYjs&i(s=Ucil+N+GGLQ$VC2GP(=za$k_Qc(Y|&QT`&ITF%wyR7}Jkp3UGxwc5|d7;zfIV4w}>_DbSp5EgtC zSiRpH$8XA$pHt=+&(NS(JkncTbCzGX|D{dPY~y;FUgH#m{cdvM;+{(=!oe`7kb}nb z=tSV$-E*FSt7V?0UPl5;@Xo&`LX*hLsdCo{LAAolhX=P+$P{1w$E&oaJo+}PVHn>D zhY|1N$6L2>>^;VqxVcfh>daZA0-w+Co^(!pdwTY)5&wy+pQnTzi`4xu3irB`gy`e^ z&mYurTtN}idaFQbtt*nLC+*{!)N-ePpTx8?%9g%g0JfJ27kEXOAm{b^WsHGLVReGf zrz1)E3THtH_*9lYI(KI$SwFdyHwGm5q$+t3e6osQM)KkExepR}#`u2IN{gN?4E`HL z^%|v^{K}(Aij@hI6`6wGggpnHc%U~{leYs$(Wf$^{)*Ys2VsH@@LOLrw)4HzxgNIl zFXk(?t8c3~?74-wRuH&+B=N_*~ZVjPve9ocvCYIoTujir3mRMuwC zWeI9ya>@%6E6K*fn;T7B9(M|=UO)3xGV5HZ=HDTi#LV+{TWmgGwsB#fXc(v;cTMxhTsk>+No9w zd}avWgz>b!jI3y4lA=wXdD#N$%@sq_SECCIQ$S9K?4fCZadOwu1i5B?Ob~XGzG`_P zDt3n;{eu@ZUR$-}QlU=4=%5o%$_Sbrn!;dCY_E15f=XWso^UwR2%bi4`~H&{t#fs4 z)T$5X=5fjgk);^#i+&UJ6MWU9S+ypXN%)wUCwkw68XvtoW2MN6TI`(ouvKfXmj6)7 z%Umnh>tTCR8#x0V#2)OkksU9n+JAkz>x`0zv6RMnbo-|y_eS?dIbg}m`1KXg6x&}b zE=<|8(Ue61Pnl;}Toh z-$-az!d-;MC{%%NeO3)#WMGm=$PH`yoHAEDc_uj}S`dV6^pn>I#KgWCEMF{4c_^rS zqW%8;WY>@4J@#Toi?3ppXsE|NlotbJiWwzB&W>Yh`Bnh%fZ5OCcC9WPxfM)x%*1>1#43NL9yN*WE@V_P4k3T3>%2t8wLC zSE&za)}t{=nf>u&Fn}oNQ93uo&rbW9-4WlrMO_o#anM_)#HB0`mvRQKD1BtHvtnCD z-AKqlEdR1s(5%Z>MY`o``9)@x&w-|$^DWVrkO*062;4~3pRMf9_pDRO@NX#h92_fr zQ!kp;=<53|W0GQ3f_9glKuseO6 za2>U)aiI@ND$W^rSE~{i94FhNd+laQZx74_&K1qpiZ9CsE>`g>1kO%vbAPES^mMV- zZ>mOdc9N>07_vNFFiTVolvu3WuY9fUtB~qt&*G)hF5;1@qk6uddQ{~r?(aQbbyt&b`5;Ib-kv zR0)Blgd8&3%wi|$ujZ?jDHsZ06fpqQaQ<_9PlQAtu=koRja_Pu2LwlVO04fHHcG8> zaWkrS0dZ@S1zgbs(gZ8bWKEkdm2KxAWz}KiMC@HCN?8r)$+nEi?meS%9n&u+7D%dr zwXq%3fg&-~ei+T|FTvZaXGIqEV=NMmtSQj$o>T`GFXus204|Hw*?jVxPeU3jiq zw)`{Z$kNZW+0PZSZ_wl8^)G1F3i)+bS1xB2R+#>6wx{#RS!^HQ$VrDL!s{PdT_=LD zGnoPeyo-y}DI6FxnRuG8|1oxXalpO@4p;%CdT1>&2>tB0qn_rU3Q%f6$VIFb#D( zj>T`u0}-#FEnR92SPBuYDt$y25|A&GG-^&>M)fU!H<=uJUlu1NQ*1LJ^-0!EcrgCd z?TdaPiUF`C<-YsjNpV-3bxbIeS(LZIjESO;G2&+D+1@UF<`2W?a>zq;Ka2dAcEw)# zt3N=2q#sbPLHqj+yx+3u5}2EIc!=o;yVs{RlRizH?EoA#hRAnmoMS|hshl1hT^a5I z#!;-`phwp9U_}Y1G>0A-%Ps>3JdC!^dg2sZrJOrPNZxd@vA7P;e8Q6?%JgIc-_{W) zxqNzHtwz9-A^Lo&NuD_1@8NhTR^h-g}e~ok$P_LG)-LN+i0`MrTHi7NQFh zT@Wp5bc4}5BYM%3X7$lutK zy|-?=*jcQDT($Qyod(=rFM^a;t}Z7Rh1=)8+3H!*rF#h6cSxP~WDQ0h_**Sp%`d7i zhi9dQd_x7uKqANjILgRxiU(Wt|9+i5zw6yN;&V>ju4WcYHghelXto=TCfiUJ{Yva?CFSipXi@pCOtY3aa!_)^wd z%Q=cGLE@H6_#9>BR;UL> zv-1CExxdh;%Xg2R5@|R2Ti)pChFth5Wp+)pIZ2o4bT;o**B@|rpGwZmWECMSApF>n zV~{1Q;rGo^f2n%^D)57-u@hN&Ygfy?OF+W?fZI^pC&^^liV)eLvfF?rsQ;|jfbjO- z>`dK;*)k_3lk{a3&@-BUA2&Wg%HAFE+5E{R-MBr!w`;pt;Wc`Hdpc>BJ|c5Hce8hp zl`__zoT?e?=hSv}wYVYc5jTFvj@kBN^?kL&s^@VRG(ZULdDn5nbDizIRDY0uoyq?~ zt)4n9-0UJ_y5%}{qxM&O%K4pvr!go1LiAg9F<|S#v6E}D_wp%w^~Y@+x1ri&zwT2y zK|yXwJdzZN+)w9;1b6F?lHq!0J2ML|Tru=h&p^Q+>+#;xoaPBy4w5%}p87IL)H*vm zwOm1ry!Q^g0X}C>I0oFLp@3(;CwuK#c21(LbJD0-i&8p2aBT8I?|MhUo!X@T6OYq1 zC>+exx)VNpVPrqg_{rd4>O95}kdL7MRosF7=2zpZNbj<%gS5&0(AC?*`#rwziw(^Q z(^-FJ8I1!%FUOtlCT}k5SBI2GxYA0AlMh@=H ziO5-mvjQ#;eBzP6d7Dke+KrDBy)Ak_+006^q;I;(3K^Z>PD|aQ!fjT|sjB~mhHTvN zg>KMk(xh~!F9@H@`XOMm?;SkX{2Uf;Zx$BkAtSp1~iLNnUd=y`GZPlVu-x|!YRLF4Hd)Jr(} z_K}&mPv2gwZtN#gyB&qnrDzNz>6DP()IQ*9FwGVMnJ%2OWX`STDKHDdscX8y-|*GmARAI_*>Y0UpG zLClH0(gDs-;uLsy$=88~pI@)y4}C*-e7B=E7_MpNEHjhl5F;hqfAjcL#N_~SulC(# z{BD1*TeWzVqs67@gTeUCZ)GiaQ~hY$p48a7D8X>C-gI&?6Gv51V;iCXk2Y`geYF=C z_&w^=qq-VxvvsT)e~Ch*>VUd1{Y2H!GvR?6%T#Ma5Ei632`{2??OU-O4-XL!&zIob zr(Y28gX^{-1j%*Vfz0B~o$$ePR#sNY`}3wu=!S_63x1>b#+{=-&OW+E#Bb95TBtGy zRl(d&id?C9i_Z^S$HDZJYTJ*OYWoSV2fb>FngYpCjJSMeeC${RBr#DpyCIJRSLtyd z5A4-P2zR;C#;S!1Ge6Geitj60M&FgV?@8flGqc!}6H5!9mI%mxxOO3zNM43ntq{qf zUp=mATY3^+RzZR?<$mcSO&DPIrQ>y}%fl?P&MWPlDNlZ^CngC?%&Gl>9x7V=!BiiX zS<*?B4{#i&y_tn=>Fq_!p&lNm95jYfuDKl)cja6D;CU?~3(w{5O_tC*;QmnY$0}}! zG=}ksAI#Z;;P^29u|p*Ys2(@P9T!ffn)tlC_qn6v%a2!Z$GS6c_`Z8u@JK)B(jGsB$U`TSHHI+=!pzv7pX7l~2gq(wq-wRzf zMguqS{2#;&?^qHhdZ<~{1zZ+kX3Tyqu9$--ijVSLCdNleNiiB6hYGZMxi0=a#J1qz zQ&ak@<7AH)<0mQ|W>8%mH|-Zn5<96L~oWDQ+n1f8v9E3~jSs|Oxy}^ZpFTp-~BKrn!{PWZO5mnQ-FPHw3$|12%C#|I^;%9E)EL5MH=GTgS4`vuy@ zZm;4qN_3$Z{#U)D68&b%o^B|+cDbM8#-IHm`kBJv$ofNL(r+BBGT|a+w={(de^TKd zFuWH;d z9%`-3w6Z7gc6p~Ybv%VVuXw8Q$KEj_O@}36A?0@_@sR9J^v5O4!D}BjM!y?z_y)1r zOZdj}G9pdDcUvhkd3m&OKB~sJ`!&dVC+zrYnP5?jtm^nUe)$l)iZPk(dWJ~JUu(_Q zO0w)#fyJku?ogHbTVvd;&F2^yo@bVQWwOC@I|_Xxz8II=+i_WG1uu2GAb65oA^e^E z=cyKcb$^9muD7DUJflDNUg2e;iInD~qQY89(3selyr^L%PLQYx=UA*(9>|U%pxF+; z#n1Acfx65NgN`n5`dE!(w;E+iB6-DU_Ls_ZE2%NrU zncvzG+WSGfP(>ISSK6++}#vzs{)tHvuN2UQvR?3-6SsDj~^6DFtz?CD)@<*3B%9HWwq zZV=j#M4mV2^i!N+8|ievSiH{)Y2Vuxvgu+k`l7e8vdW}o<1kyff|oMG?pVH}GREX( zq_rj8VVyTpld&I>$d3rtyWh3usgjdE*)cI4pm-J8NXB99!n-K#!XK{F9ptIgN<~E_ zO1h*$md{1PG2)?6=zDFW!naW`9^HKnCDBpt8=y=r_+p$&&}fkkqwZc8aSTIxdwWl{ zeUgotiCCsh!k+A7G$tSEsf9m#8CC*9$>EfZKdh-cO4*POXA8LtlX(D;oxm?{`^`|FKQTpJEdO8E;?jjqwAh*MLhbnU-W){ zPH?T-|K?8sP!FNpag8g>;bE3&JZxYs6G$H&hx9ne)>dqLptnQlfNn6Ss7fr+YOXPQ zrN`!>Vup*#`;i*}eegsxs5M-P0u6@+3#nVHF8m%oxT8{=#g zTHelvcYtXgo-Lkn=Ap9Kj(=-e|4a+oLH2oG5vC>+>I8JVv*5sl`T5_yHfVp&LQi5b zVmV|VJV~NxG@?~krl}b|_?PK%jf@O#F>OK|q&#HeK?QG2YO#|ou_JoTvZC{TBmmk} z2Azwp@okL?kFMR&K%@9pdj*(Fv`nc9iQ*Om#1x#IC?SwU$DQ9hAf-NzGMv6f?@Jd? zN)BBveMW8aY_);kOB*bqOKZN0`TBE-c1o%|Mh8{j2gc1^|Gv<>fqc#ghq-i{VY#~V z&*gqtrb_*(6Y`nc2&HiedqZlmml`b0Oodmb*}&O3$c=gCU#z0St0?Cx9-;dCow0Fn z?urFXXmNC`N(x*+UFIg7}0o4LFj!9)WZA9-LrFM5q)P4 ziX1`?lKPwe6IMopdg;m)jI*0>M0t&tZJF1&&IVqtyKfJ=jybZx6{JCszxs=ABkj)} zw!nB*_SdBqQ7a1nPFn{&pY}x|gOdFSoq(9(dl}`2Z9T}lni5fxZ&}7zUX`Wr!YJtf zf6NUGvJ&zRdF#32#)5|xn*hdoTvg8Sd=Fow#I5{37{)8h-gyVj4@he)YM0x{ch)u3 z3CIlNCCY@L5O>DYEnF^T!J~hL52|fysX&^qBO1xHVl-a;{-bYrEHQNT)5ostrGWU6 zC|14n9}G}rbeMP~Da%LNhc5U5kBtlzbF-Mokw`D9uO|bzxP8vhm{(i_<}PWpSiSw+ z_D}gst!7*NZgb9$K{p-~WZ}bKUC$z(tkD}f6j@}fOzpN{_5J0c$kP3q5_Q#OqsLyD z7ba3^6g)vglXOT<@GDEw(|r9P9e%&2WPGajS=n7w%JO)LGNCuS7OF#L%&s#k@j|;8 zkMQ@qV!1Vl$d}P7nLYlFbqsN;vB>wPx}E#Z9v&jVr^dY3(+m6djf$FvCI=3uc$}Xf z;kX_#F0@8REw@XFb!*L)B4jPD^R8W8W7#0rtvj?O_`tn5hqxCNw zGT3aYfNemSZI*9ZZP}xP*>>wE*rIy)F|NSFZ}>8RD`Xs}giw8);`PwzT@;Qo8SJ{T z(`VTt#0ei2D}85|r5g-etP8nqu`k;R(P2CBezQoj@_*ZYVFz&M_Z(?$Ok{M;Z?yTT zN}mHA+&p4>SeDlaIm{ccR0g>YbpW%6j3>7%fA6%gGP#sXVnL5cJ~O|bcEzcfKI|4q ziblJ+#e1GTK;@N(qqO58g7ny9Ccz9EV@=8kZk~6vwW7Or;U*oO_@y45Ih3wdXMQUp- zEmzHL{IN<=43@{`3NH^B3C_Q`Uv#Z^h~;3&lqlxWcFI=1A}o7DO66M3plD~9hbL2NMN|d62^Qhflh4BB}rCmWtw%BWD&RAsogFGaKZF`>K8RC&L_N7g%RcBuAyGvbW-B)dbKBdrdwMtaVNf3d7 zuJ3LoNG8qv`rH$o@ql^Wd}-=>f_w0K3A;cmH$WNNY1ucX>0_lniAmI3@@{1}8LM|! z3)lOt>#Gbx?k?dwa!rp6M()of&!(sJ5RR)`gFzx@7r%l;$jSdWczdH+R-$D&o+xJ=8&mWvdC0) z+AA?`g%osJc5O5fh}b9Yfio*oJ~;@o9k0(See;c7K*4{939mHC5p5X996)>*AX83Q zrZ!I+PH%jz3w;`(^oXybt!Y=u#N?L3yn&tjm5|Wi6f8wIZeD@#=sl&i@o;BzHa!NX zpd_Pb#+GWVZ=Wg{>(Qw4qTlJ5V%vMt%Uf!*YL0ZZVTT`1$_QU{=dsB&r#=2z^m@I+ z=^>q%7wp=SS9JdZUoTmDiIP7gK%N;ZA(DcV`~44~42lW38`qx}XQPW0AU2r37S;4{ zPjl>tXe8??ANoZBu6Qp9Rop<$8qQgbQ_Dc+6pZ|is=Mg^h(FhYh2(>Nu#s%SUp`2f zbiD#SG!Fho8J`$B2$TBKEjKzo`1hKr9w2alO5~Lm^h`SJg(W2B|yiMAJTr!Ms_Z$Yt?t+E2-ISMX40P%XP? zvsWqG>7jY&liR74m2s1qS3O$TiK~LC9BBT1)oJ9~Iv2aRX@8_6nxgk-lE7 zdP=q!NV%6)opY>$Z42Vy5VO0Bh36uYoSSO_n{6OP(J@joB@-Mc*d9*CySb1Igehx@ z%daJ=s0zGNvZm1vRqWAL;vA zRL)CKB>N`BXP9-Jy;4b0s6dh5|0cLc0w><35)Cg#W<90^Q&8{izTZ821mk@nRAo36b71l zUEl9VYBirRM@WtQw+q}s&>jt6>P^WNiuTeb2g>wPgBI?YL&d06qM*Xl=FdUOO@nm7>e z)$mUK$QMPt4lLLLykmvSKx5PI1`F;`%T~u!)sc@uf0pI?D~4lp`f}?;Rnbl3Wv6?ktNPy2a_6+FuR))fso{vDAsn2})P z6we~Hic;lO&TBEoEmK2|O1#q|IoKkZGWP5^c;toXnj_Q4&ZAHkVL&`g3*}`?RpnCw zH(40bQVFZRGd29l{E&NXWxdp()t<#O!&AXPOH?~EF**5XO-*7g>=@wAnW@pE4b+NU zc~vM8OsF-sB#VVB>v#TJqo4L=2YEk#$|kiByoB!)h1U*LTJF+8Bxt0NHFvS`lt!9v z9^KlpvN5470dqXy55Kzd#)CvUu<$jrXjd62V9aAGXy)bj|Iu+unNZkHIcC`^>;hGz z-XDyBaZwB0LpA&KWX{>JGS`R^)l6pK&I4~-bBZ*J)~|i36VHI;!`xdSfrX|LnhBVW zm0)&oFn z56OyOBW&+8y!7V%&ZVdQXh6-k<)@8PDy1rRPs7t%qE6HnIcDt}sf2jPvJHp8wLw%i z+6go3Vrxz1O6BR4AG*=hhOx$8mP#nzN&9B>0gp>-nn?K@;oQ$C`SB|?(Jz8SHg#bP zYmRx$0ZiN*?79O!B%JtWfoljA74p`uw5i~6kf#lvyq64i>}e;iok>Aoa!e_6Q-xW( zcWKx^C`MOaJ2&|-1(nX zD5%QuK{dnGJ}fh_$u+!SdDDoDTbo2asrdfbkka0i^mj=jVyb@{_meu`5_8%hp3oS$ zrw0I&wbX3f`Rb%paXL67DGm*v``KcC2P-=;&xK+}m3*%S5mUh?f)(>=b6QHyxJU^} zjij7BDA8xrOP|%SaE1f6IPF)0MF=4Z*Bm|ma* z?E|n;Bzi2;n_V6|Fp?Llt3r1xKUVgcfl2aZpxm`v1!TUKdcjTWwaIfNlG#oIf6OrH+{ z>6U@K+iap2Qpwmn3TID`+iW!CNT2e=gU?4L4na45(Pp2<4%!dl3C{TCXodKaG$Kj* z-fD#GelD^0I}s7}eQzO=h6yDiwaQyfltIU`Ri+Rv4#h%B27aOz-`CH96 z$Gk?7$O>w_XWyzpfyksJUHD6HJlXOWzuJT+%~?|Ll2a@;iqF(b0`)&U_RUM`<0)2h z%oy#$`(Z3inW}{VzrY^5#Hn<)%OlfLZ4<&NA18)_eNJ#hZyJe*+unc4&xtbBCC&D* z0*SM)trH|9iP#w?49tjC637sS9!3ZRDm%z@{qc5*{@s20h0&OJ#Ob-OAZH^+_c~I8 z%!m3KXLU(jld(PX%TKzN%K0?{*oc`T$mey)cM-WA;C@^@*!bAklsAAXc6S+HzWlg? zt`IBDt~CvfiMjhaX*&L>UzQ(Wj_Tly_5I;Qmi^w?()ytv5=lx1PjveNj2=!+ArU^O z5|((`Mnpl797<)h^6)>w_&152QbIBw-&kd7fN>A%+-Yt&NJO(89~JQMq5Bp^cP0U> z;-g7dXROS`Zd|wa8Lc0K!FauwohO#`f?!3IWf}#;Uo}}v;1bSQZ!>|eB{qdAe}=KC zc-0b9j6ftjEz=F)GGy?A$>VgE03uyEwC#TdN|5EB#^YhhNYOnR&cYF*|9MfhfIM{4 z19WWvE(gQgl1%e;K__`%+v~j@U6QA^#y1h$uc{bnc{7l2d3VffrwF|FA3aPNKEHZ! zxiCvT9puZROO5u9*dsB0&_G);9*C0V&Oni=8H5-We)ZJ^QY=S$TUXKK1c&2X`(;xof_Q<}+LPjFCLNiAXRNSp>7+YOIrhMkby} zq0(ZKXr60&K$c1SsNe84bqs535$PVL1=g`+R@xJ249ofIJ*IMl#Ki%y4E2XT=?lBv z5D2wCXv_3XE7!rW6z};J7xgd6HjK>5OPI9=D30O{)KJ zyka3^4^Kea{Lo6()j>ELJJm5LNVX}RKoOr96#R?50GASkI>8PHp@6I8AYi_P{4)KuI@>F5+muhwjQZY`_xL~cCt!&r4Pn4}t^JllM4b5qmPlVfcKy0JilYbDFx zst)rqOs)Z1z7;aGDH$xJykTA_e$sEMk}$Kw!DQvZaFEB7p8b=z4=W6P2dMjufx>(p zUOU|rO`!O? zy+Usb%UWol3lP%9uRsv+`Frp@h8|G2=YSwzcF?dIW6-@34hY}>G;h|83~c-YVt5)h zx(y$8%}^6|6ew97`mClDus84;_ptEd3AqxS(^!SDQ9LvRq;ENu2*v{&=-EarvH!Nz zJ(a6HcuRe;FCVn%M167aUeR@@KGJdId|^07TLGFZLh>cSQ}JEM3*bPEQQr?(PrDwe zh)gO)OqVi%;%p7`3Vt#a$!Wc9!)_mnEjB_qg@KYyqQj;C94yqZ$KQR3SE+~t@4yUS ze~Ec3SN1MJPrOt>(lk6nQ|QOf$R%1c6o%flYUxrkDEfyML&{X3{!duT4-+~^GJ@#W z@;2O7N_OlFhfRTLC=rMTL1P&qsJArRL%kpyuNcgSKI8(S{N)EE_|4|4zFQ@;)n`AP zytQ;$5RSK(v!ht;M&B9KTZ@U_x-sl87e9Tb+f<@4q}e&zMYJ*bfbv^n_)Z#VVqzj& zb-jw8o7^A9+!zQ&Lks_R7(+D)yG1KQNPib+Jr6yEG{|SRZhDi zW_-`(A6ESE9ykHkUlWBTy{f*S_C3iAT%a>s8c0<&;}S;Sw%UlCV!joxtIpJ$@o}RTt#iP{#L(X~B0-Pa5%y44;{}D)l#agtj+yy}i5~55YXt_)jSi z!ai}-YykQ=;JAb!84b=g!1Kx3wP+tlCJir-nqo2`egV1f<3dUGK9p{1wle~X1*7FQ zJaJ|z3284_CVp@|%s9BNr_#dTRWM)jr^aJ?XC+I7biuT2cA5)mwR1Y!>VeR0g<7!kFZSQLmPg1K~Ji= zN~vns`nI&oZ~m|zTLXU%>AV0>;JSUOq@TQ0d>37;9S|0-{IaAJ%7B2VdV2ETf zn_-=QPB-@mgyDIwi#cJT(gteL<%f}p&mYcH*L;TtLdQZ?bYq~wsIQG8Dy()Hy3tdE zC3p~u{DCIef^~*5Nqb&;Eo(=AXdmzUxy_}g#)n^Q5NRM(;SUa1)MLg;tj7(zSy)D{ z)NiMZfA!4UOu3HX4(Dw5@jSB~Q7|#dm;rhaMUHhwdisD4>o#Bw4XmrYc@w0=V~w6b z6x@FOs+E#ruv}K}&u(W@sM5f0_S7^?5&#Sj!P2@fs&o$v+cD5a(&RX1b{7_2m}uZ~ zLatW9r1@k^+mUKVoRWjK>;3e7UXcX?#!>5+aHoLJ8}8bvfQ%BwBCOY%$;{Xb^p_{f zD=_AwqErGiUdFjr+RiYtu%WMe#)M--0HiqxA)m6P^y9U@{+$GK^y{9e>9!lD*y=~j zfKRnhxlY?DbiFY)PUt%fShIvQHHH{rDbS;u5l!`S(Enl^34@^^k40RRA1sL z_Ysza09jmNVb97Bq#2_Y90Je$>uBN%YH>tICl2`{s z)zOiUrxfxI^gc52hT(Hx=>%MYt&sL|-DC8rwOFIEM@!h~#S2unQ< z4Em^8u7o1P^s9>Qzdyt#)(9h}uOZXF;ozkCqG`-LM$E&irq)YSK``a>WL4{(1ph@p z1sh*rb+Z8A#JK(z!L&(MaM_Z_=vw};Mx)QeY?Q%Zpgqu3RrSqY?(_HB0@}}kfOSl0 zpvFmsYT^QG@a@9k4<9~a>B)PePO9~@v^2nwQ|MzfHaZ3qMb9S5dNe=AN@)6M+2Qno z|9Dmm<;;0!@7<(7HjJ<&t~0HrG&*5rca94@MvnkJ{Bd!*^7t?&qI==z#!g4Xgw0Pq zkx<6m6A|EsY_nE0q$Yy&ake?>8iXcGIsMS+KEWda!)kw1JYIu=9u z2r|!F^yuc?O{M-`51DHk6s5MvK7ZzxL}nGQg}+k6C_bq#h+o@evR56#{fY?t-rS|> z>DtqXU(en#`ZFpm(>^7VE|~Ivh#Pcmh@BpzUk8h(nLKVDv)-$bcfIxLYss4k&YSfQ zdXX+Z_=ZpRwk@pJCzr^eouEHxb0o11m>kOK9&~MA}ko0j;9j zMee`)@c8gSh*$&|W(gxRLqp^~K9Y7A9CB!?_&)F=v|^id6VY@mJ$|*=@^v%IyJ7Jq zOj78Hy1E3TLA&^#wE7PzrPjNv*@Gp(cZb#~Y4nc2dj?4D%pcw-FmFsh$+E$wN&g)= z(i+&ya$D~+|8boGIDT=$995do@Ks9Sd)F-sOKAe}vPnLZh}uZH`V^FZ$hzLN2O?KH zs1`}1b|zy~@8Otm-EXOJea<2V$TbJpXj*d-e3T!HfwoNMxvslrIO@MVx&wYSELrV; zYpML&EsGc!GmhZ-^qksjpE5xUjxws8P{sgv9wGjK3dZ2Anjf`43_Gt@CRGDYk@-93 zk&RC6rE|yt_Qi7nYMhkkxCBt$a3*EfE^cb&%=c^5HL-moz#XC(ZxrQpB7SLRKRqe3 zxR(RSbK%Mdqj>Zkz8hGW?@Pjm3yY2Q?uc2VzCe*<1EUcL4$Om9T8QkMf*3Y*dpSAf zIL%&;`s=hTZRR6pY5-w6y#{H+Hpw1N6)qM@NT}IHDqi8@l*-<^OXrQL<5@Z5mWsrR zd*AVleNY1d-zWcC(Ykoe$<9{V0;DBCE@`ASNlsnB#SUG2z-5|Ga1gh(JVZ4{(UERSfa z{MRt--=obKBuJPdoE$4p?JUCY4t%SLDXGElKVLGTn{8_F_PRlDhALh>~wZf}f5G2Rox+MTy5JcX?~ zA0#1so)BT3Jkf;?&-BB1%lB&%V@ML!CFaDYC~@a+LDV)elTyH2qCDn-#ktK(y4 zf4i6ajhkl1gujA33bD}?Us*zs8n?!*DSI^AGu;0$Au~422=`ximqmcEL7&bP?S=r& z6d%QeU5ZcE!n>^Z??rdm|A3>X*n|v^=vWC`3c7iHQKkKm1{;YJ^9EZUVK5Chogy%} z_fZ3zs3Rn1!ZLV*YQ>2{(y&qO9u#n=JY4Z(%$s{(V^SE+W$)reQ26z3x(?^qVvY(; zZK;@(RM59?fAuc!-p%$tA>h^D4~`ku3PveO zNL&SIW;C7L-CcA{zeh-P^;49K@}>{A4P~t^`9YCu6;8g-ykQ5e2QvHc{rrDA_p=|e)!nkJqzo@8l+Wmoaw6w< z;as)KpJX&8AzLfIy74l|V4)2-;=cfUW2(vPfp*Z#OOyQN$pC1gMFJP%uiqr66 zkHvT7F#9EoQMr67PGbc^D%Xgz1Yj=8VI_vprBZeMtfW%Z#5M@%aB63RXMRuvXIO}m zeJG{p;p=<$dDl0rwl|3x>iC3wHAnNbU#zHnKcbR7-k-l?CsjBgTqg}_L(^AAYp+Ta z!&r^j*lTvZ=rT+(GC>F`l{etCJr^ad+Lh0vLp?Qj=S|4f3gO&Dvc&^LT;$6@i47>O z2#~z)cwPi?+G^g$KlrO-3`ZuR5sJ1!8V@)-6Mc_;lP@)^vh9d<~b()84uXcx|r%> z#`m`r%3*6I|6s~f)cudQM}h(!G1lGtI#_D&j67kEoOObZEa=HeLX=F7-UJK!*+INJZnqSL0c(@UsQh7)df{IljkOkv-Lk!^aWAo zu6L{Sm0OH9<`Er|lZ9&>^;@Q6@2u%EfPQ{E7;_J9Nr5AAFnQ)u}<`8!fX4G=^) z;s8LbfIeSxhwu3>F1L-q6$K6}<-+aPKJ4)QzAz743}}Rr%fmL)7M!0wQRYj_dZ{wU z9Aq${tBH}J7rwQT!3d=mAqWdwGLv1%zeL$n?0uZFYixKymO@^7Zm;^}`SHQ)TU>*B z`hSXwY>y)BtUijw!b2LUg1l?B#8~q69H`=m@oRlClkQ*YDD-9v7}F}nzTDA z2WSP?5dVAk1A;ujA255+>fcYP|7RHOg(I{4k2Fk;WC1*}pM_{DRDePlfOrQF4miW= zNK{C7Czj;enYI2d4%194#A<3txxnI4ki%Fg0r1~3l{p!Ha@LlIwo4Cc4`FyLVXkwR zT_2dkB^GTZB{OSF=Sd!s8M_3i^B}T(fbVzV~O%b+aM1&GHCFKqeerSW5T4z z{O^c=9qwO8QlM*Q`(8H*9GD_l`qRSZ?h)Q&)hlASlh}Vc7Z&>U=*(+Znvj2@^|mhJ z>Mr0G&D?&5_y_vL;QRffGedS4I2vF+adC~ZyKL=^pt`%FoL&HC`zzMj41in=iAQHP z1w?fOv#8l{ifIZj{hJeKiw!k+C_d;%MpGx#%^(L-ofONk&F^*^NhA^$6g+pB@-jbS z1LjyBqaV#!rzNZSg>M2J@Hhs%Qm)eiJ$_M|e7rw``yA$`$D^pxjo5f+sbU2xHM@@S zD(z9A7Cbzzr`*L`)CL;8n;YvKFQga?%Z4omhW!pB;}t;fCNb2XtfQWHD_er+Ep!l| zLjy4kRCFRM4(wKh0d*HK4t_&PB|HFV3FFT`7ZAh%2g`7JwMS;^KmzOh^=nE(&QUAe zxOFGOxy_KAiAg#bMVP>~?FVQw*Ht^=_Cs~y{jMfJWP0;Iu)GjE!J_-RwmL|pSPsY8 z!)=)ieFj7zjnX-e2!Y@#pvgJC&8rE-M!&^Dom7W2R{(j&01Q<9f6&k#R>n|QG+k~ws)8@XB&3$qKI6-tXS2!*02ur% zD5gPs86N7ZP1E5*5hiSs^i#skydQx9+9a>Tf1>>CZ-f;nDh3bL=8rkmXe+BuW{CKE zLu2fDYzt58*scrQwqbV!V*bFOVo;3(k_epQ>rZ(~ZHE5EA15GBeGO-#}kmHhY~I31CXm4fhsjz~d4!7ou!R){o&NNoj> zC}>%z-h8Q;d!Wk)^&2SGPL>%A@|gV>THj563(>=W3Db2>zHl;+hR)QlT-RSS!9B%2 zNdQ|1yxhxTc^Qlgdy@g^DXW1ZLE)HuTdH{E^uL%;_3@Z{@49y98!~_dDNy9}@H#i~ z$Vo1Zz*J<1KPD_TLwZE!fH`1=umFF$;rKu=pAHK>`wyG}O-wuce_=BKK?7so8Dfz2 z5ucteafe(huxKvYCn0 zmdsC9ucp+eflq6*;@0i5Kj;Tc7Ma{us^b53Y9(8n!pU#H8r`z-V2uv)2sUJKGC=R} zGXZ-d$?ux4!ddQK8ycc#k!83j`c%UyxHo?J$It9f*&f9gD}5P&z53wK6oO2%mxYIc z2*0IhmtH(B#1k|`Lc%>#@ZbeGk!%s;uv{J;y9sKf)(@8(O{ za*kIarUKX~CM6k%eTA`mAFKCwd`fbL z8VM5Q_Xx1VLM47rb43qC7wC`1G*zez;*W(fJut_Bv;?v@;%7>0!^nYaeI>sv7XRhe|!xt{BvkMI1 zF#viFUs#EnralKEx&ZkRM))k>q&}3i;XJ!NRjUY_l%_Y>{H%PO6Is@`Ve%+DzpUHe zM%^1K)B<>5XScl?aEoY6c}{=8Xw_zv@`F1r`rq6%Q~Dbm9rKMx26_O9=HKjcA}LdD zmy-D@`9IHGJ(-?nx_C33L*}3Q05nc{^sChlj;`0Bbu4&{EG*>Y_&DWT2%>U#EnU^l zZ^Lh+>TUIi)s9TVgh=FjFqEs5K{k_iFP?EtmC){sFTdz3rm4%`S01bLD$n|jMFdGAQEDbDy%sp9%6KFm8 z`8_7aAFpGl(M>G7s0gM3Z+Z-7qN0sy#V0er-p77vkN)!99`oC=^Q*Ck2K89R^*qtQ zEz+bAdZgPazdhxt%Rs(NlMCF}e z{Sdc}HBokqPciM|hXx?&dn98K($8sWbVVRw6Cn1xvSM+D{Zve>9*K~6cNA%-qeBff z(0*_|m`Z$c*lCRebKcr|_)+~wgS!wus%%79Q;J|UNjQC8GlXtC+0}3fB|{D?Hqm&M zM$P)GPQZ4!3yA6=ud{q^T!F}Dh$%K~(s}kzPFdX@5$txi!V=A3=@IF3|2^Xi4L$(* zNT-`RFwWH9Mqf6^Py-zsk4FVoU6FiteFWe)Ha{itJ;-8 zK(16xRyMb*@FELY-+qHi|y7%&D{mOH&F5_yO z(ep@&-=3%OM)jrn;jc?|xKzPR=0XgaJseY(?qXvRG{DC(T%6Xo<6Juo36(Kw1IwwwBS%pnUVpR*S~>%yFE1@7sj?9w}uI3j_ud`&6NWentq14=T`1at}ez(6Lgx<%29d8V?)bvw%x(ErAIt-pB7tp^hCt%Cf51v?h`4RwCp()L^0`yvUf)(AX+`QM(>sAvoYa|N_9f&FH#7-MFP zEx|7<3E#~_T;}lthXQLi5Z%v^2qi4kU!rc=ln*#S>(5&8$5oA3evlS;!AYp?J;BYa zmb-FOH;)gt@TN(|5(cvXWP20fYHG$gDGm|H=!Fa-FBH;PN&L$B=+RWt7O#dcb#9C95E+90vlck7?Per65 zffayZ=8-(Hf)9XJdDCJLD;<8&e#-ysoXJK5dJZIF%x_oR1mJ#W8$cRnzlS2P?Y`~P z(V%v0>GCPC-0Awa@3oHACjd}s0GYqzllEAdnT;;-m+l1cx|&v0F<{kBOu%SSKrqMZ zp!GqWGII8xarNxh`%M-uO71JwR>f85?NRxWm^ZK$D0qMl{iv(lcj&t72ou8FBX2ioQzw^LA70U*OY0 z9|M5!bng|WXj)N#d@dfVsAoA>nrN^gGHl9PvL6L(m zk_TT&A_xtDJpwurky^(vqy{Eb3w%@OPy9o{nqUN`;LGep2s0urH|iUbh=V9v>#(tx zQJG`Gzkja`iu8ZeeW>v)82XVxiesFDI_b;LCvQ7HrW2kSOmzCfPA+t$oqPB9r7o>_T4Ly=V`UjcEj*hd#wCJI^}rDCe-P zJkGLw>3=09|BHQQ4bBDLL!$>kD0g+h%>v?C&%O3}%r7hgV}@f7fJ3~xn7S_s8&AvG z@UZRJ0Z`iGoE$OCm>2q!v`MUQVq!kpUAXw3Zx@zfado@80xBq>6p5WV0=cRwg*l5Z z2UMVH6F=b5wpf5^rq2l+5%wnBUkZdw0~)AV)a0|C2lw8I8Rw1uB(l%0r~x0`<{&^K z|CC}x8xTx|!A#tIz&>FsdX%jDRB8tND98w+M~u$HNtJfi1FGe?{Wj)S@+W%yMElcc z<>ee@X8XhHFL)S6;>LUZh^BF{i1h_FIa2DR&r+&YN+5>GZ1T@Rbou`hivA)LVkeG9 zh8ME_SoY|8_1u#-J^07b_-G#4>sFo}NjR!#wuIKWWdW{e0LjHzcBf8cf1F4liiLgX z$}(yEN*EPm!$A4LY#i}Ac=BwK{rKB!mFo`H(@FLFc)4s~;6@CL?+G@*{=mK$n~M~` zG7UZH8;E0M=)RfY>wpALf9Sqduht_oPE2XX%!-bkz=n3Odi*mx00JN(fk*uR5cQQ& zQGQ?Bbayuh0wOIP(j^EA2n=14L&Jb{hae>q(xK!4Lw746EigluG($-@@9ppZto456 z3v12HKKtxzUvcy%$i2OD30FP+Lq#gh+XFC-A0<$aey~Q^q1Cj=>Bu7sApR)cii~Q! zR&|afIfn6rXU`)_^bkn7cMpy$->g1xQuMGjq8tkPx)HtZXK!Npfbo+>f|uC$XDR^b zR9^XtsB?n>xE!wV(@!NxqTSllt)C5(itm^BUiOcWhF$mvhGvGim)>i$HWe|ZPrb9_ zx{tOxCl)*jWgiYu?w1T|9$@6z(tSzE$-e=Xyre!cq#-epIq24zljHSOu2~yTvYIO+ zHp2N$Ov)1{B3?^EaMuUkn%{Yxba%_lUYbPc-2`4i)b!1)Lv`2i@a9eCgF;#mX& zwt$4}8kZLs*rlj5hUUKxk=>I2?1%g&5|&d|=(m4WAAv?djEYbwHFWmz(LFiM(Wy6;hOl`Mb}l-aXoVF3lccdwipzM^!pL<=vQ zORD>UgurzcN@W2-zGHcf?Q!)T>tQeB-g>Q!u6d_Zt72qS4IF#Eh>Gqnf|3oqNGiw3 z7)T!mckR3;udTzM2iVL|5ki`JY3p#v!o{D@u4G{waGc@x&_O4lC%g?1tyPm`Q)rVk zfLD#_G*nE9%)_~+tMvEe0?6XlD9v#an-viCX}0Nwuy0|6>d?wc@I?&*0WZj}SO1$= z=>&>p7x&{$Zm7_#?zikxr~aHzw$_UUnn= z1b`IVtbe~ev4Wx^Ei56Pop<%Ps%sFse)hTNJT%^;+~)2{40>Ga!z}vzLI|{n_p2f?2rl)RP7CogX*t(zdsP1z4VVxt83`Fm8o$|7CDSFgpUiDzla(@0e zPNN;yO82%k8Wl`b;p?YlqWC>FSSpdyhvWZ#-YD43&-8)xTlS6PahBX5>`F!gtVLeY z$yhRJg#5xo#dOf@h^^e$J2;mrr@b6fj792N^u2J*-;vw$#&cD5IcO_EODS!)%%dz~ z+P1_gunJFSyb2o*)w`E@UsA%AGpE_RNBuWNLp<3SD0Z_j-;8| zfEnl;CTR#nH5}}y&)G0rrN5Xy?SY{_yvkqZYT(~b-OE`(Qr1FQ3atX4uYd3Pa?PH4 z95WOVsX;bU#lG6^gZCqu)-7(Z+wcwUg=uWm-!c*&*^M}6TG&NMK_oNZEe8ubZwe~1 zzFzoSaj^nxGOQq$7ZuFU&yVpui0ixc&TO3>|DTjMX*J&MRaK$URaLn$0IeL|4%by? zbN%`t;H5335SNK^PN}P_WZFJ!YcZ*(NL{Af0pP+B0B4>?;*CvosE$g&IyKp2E(CZ1 zzjB2kI}X509ccmFC^iZbFcdm1S%hq>$`m}XO`Z98xdV*O^u{e;M(Z`7YJaSGp;YSE=(ee$V3E^g777i`VSm+-qr;B3&}dda%{OLE)pigyhvbM zu*?$GpAyfsLlFNK4Qsp77B4^S2ndD2&knF2=K7}03mV#zFSKfs^+n`O->k1C<$kDR zlFPe~xh~QjF;gC@`KZ>A5iz}9t>}HCiD=}QfMU!D(XipXZLf$ldTq8& zF)^3^Q-hk9TtzfvPaI%w3D&vbIIy4_u|}vg8~q%x8b$-tmC`UW#sly7A&QCc@T}0O zF@a7m93c4Q2`V3-3#ieFjYG>sVM0<~)o%Ba0kk4{YOC2zo=Duqb-5UrK8Y%|u^fa8 z%^H?fQQ>)s2_olVk*nOrg=27F5K3y`PcldG(N&xD*(vmV;LAj|Y3OA6v}A;&l|Q-mT}d09=QQx*_MzJ&u=Z5`6xu z1zjSiRX0Ct&n^b!%^yo<63tXc2UNP72jq}#hc^HyWy(#!MkqGa6T5>(`wGrOP!qsW zuf2pOpHp_bY%KpysxY3)gV~DoSeMOx_~$J;XwYx0TzNlg55y1Xl_y<({Tz_Zw7-X?iGzwoJ7iC&}sQ(fj-RqNw1>&<}oowG+nx z;3+7uob6nqSnua$u`mUUL`^Ax6t;w86Q#2QD!3`fIY%SI2I6P+*V+M_0dy0ZO~!M&Og^*ScOs*ytf$2EDd z22{eVcBvgVv2AMYdBzwPdhg&bY2RH^v*Qq!IqwO1jn6lLPUh@lgJr`7?dI#v7GQ^m zUSco~S&{oTw)BQA?;AEQXIJTcpXC|+ucxio2= z3SaU1Dsyih8n?WPg0w8af0VXbHO@@@Bq06WO| z!Bp1i_giG`tlLgX2h&5@9h@2mG@fY_Q(nHO-%!CXDgI}WOKR54Bdj`@#8ZFDTUQ7_ zn{uF+y^-edH~-#@d2oF493 z{rHw%(A-{Zh!foQB*Ro!x9EdQ#alD8gnxE)Dyni|7BT(upNiJf%rR`Drl!Wx;(+6# zn{oh57}5aZu|njh!+D!i?M)Q8^`XKyNy?mv5`;HH5|NlP>YC`#&Lx zEHwYtb&VOp|CvBM>~EQ#e!%jf`$vIYLya2c?vGq&CweB#?`Y9yJ5Y6i)#dqL8AxnL z)Jy`y?iXYe07}=o=h+8qMsbs`&RF{O*>H4N?H}GOv)f43Bc-tlz;ern<9uNsmHXEM`_hR|4CS?Dw zCORExKkTMknzzvsRlMILK-31+0CJ?5{U7xInBhu0K;xF0NkZAIFIuH{d~-!X*&U>q zT1K~RZg5z#o^hl2tsS^Y*1zB-_3?AL*uAU3;z8IwZCe%we{Yi<^`mNg%nji1xve!(@mz!JR$w>9=Jv1s6cRY9RjhK97UadC6R zc^R`GB2-mG@S(4+Nr9M;xGB-Az$`94ao^sB1$rhXCIS$7J4dPJfRXXS+Ir&o*8w9^ z1Jfl+YhXs`2#^ls!2%!}reNC*1&j}uumjia?Nnk=XaU09vlH$Effz=dr&(yYo0yaU z*Or;`rNNZm5)BC7yzdaTJ5xUF_OMH~#B{7!>acX0?ysB;xB#Mx4#q=uoD5B(inxR4 z{L@mq$lz~}{{R9ldV7|@+Mm~!k#fMQ^PE=9kpgV&(+^Pv;!FTwyP5b^L5*~hI#k4F zIzy14J;)VCfNVPG_AFAV8)ye?(|gu9I{2;S2|!Q=7VJ>K?i?88iY*-85H!U5dt}{s z=gusj(2>+5nEuK+sQyP4-f+V>K=;#i<9crYE*XfTMaQ2Ge`a?xDIPf}@Ce)aKzI_U z-SDWWG51$@?T<5GtS{fA&2+-Rh-Rj0x{<3)4-jjpJwD|R{m5OJSs6ykZ@F(*N^K7g z(pe;g%Qf*xo&e%VdeYU_AAbn1T!LONRe4{VYB)9N_?ZylT}B25M_qt4D88x+ zm4`PcYdREowfs+>;sDH}&xJw!D4s1n47Mhzhij ztQz>zEmB4CC*sA2r??ZX<*4;U!=;KQE{x%$zj#BD94w-tE@<#q&)+h>fl0On4O+cp z11-J9)ImG?VTGZgkzCn49#6@Xg9b$=*lEcjEC;CGwOBU#LACDro*vSbz$Pqm&Ic4m#{0KF z!8d}~h$d=BeRF_T-~Pe^Y-Dcmr0&tI8{MtzNZtdaA`f5;${~dQ($u{48Fopr4L#q3 z^b=5c3BmLg!!FQ}q3GDZcEFvLk%%UADBw0rpw9D0HSv}}A zL45&eIujMeeW9;58hSpuVt?(iH3BT!3HAA%lWNunVB_IF%fa}_#wr`POs$pdizvS~ zz#lCGoQjvq6Gr3{Q^)3`Ex?mJZ?vVEk08&WE}XE#3Dh`ERAjK$Y*ld%CO*E9Pc zX5MlwsA@U4j``xJmsa=vSS}PUD*SLAU zA3NKMWqF)bK#6|TiUfE+z;@|!TUhzhYG(a0u(q62d=Eg^H1!-w{wuWimMbwp_6Oz! z{mG1OQqI?c0xVy1+%)2ZcVRY;3U&SGH0)k7C$Y6$npI$|8guyIhep%6Z$ZktKWz6B zsrBObwo_*ou-=ZoV~IshybDe0eENR7?ieI%0Amw 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≺q&1OsM+`-5d#d(xFDEigOByo#oojri`upr*@KgT1_W>{)JCO1Bw+Uul zicjlDvCuQ4qx%f2ffr_Y#e}A>W#`zO7V}o%6?r8obj@E{3o+1yuZ#HQ0vs;Nk!J_ zR(XAFYzI>0`7zAqNE|l7Mnn6gpk6Aj8qf)ylag9fvI7AcnCKT_D4cY|9@rPI_X@8^ zJ&Z#GCZ9OuS3-j&)&0h0EWbqZMMx&|P5Pn8vbnbM8>lQz`yau5_yAY8cfbgL_&@!L zGWfs#Wc$1gEnE^hGi>Ixwvd`CC?)36+0pm_vi}Gdb?dag;k;pG&r*T~=I;q5h+&us zaj@|&1LL_TxosFcET^Zq=&n3pd{-F@b&I#472rx^)mQ1zFXzozZFFFD z>{&$u=5H=rwkG>|ej7moQZ9R9uhVq1CVHOJ6sN8yZ^_nH&!9iFG~KmBPFA{k!uIgP z#+wJR@dSO2Z}|K<}pNlDt6RDp=Q z#|}H`0+ckslC==i2tWI@G+{R>HvAiePk)ay>=xD0kxM4#b@qJrDl6lG2GIn}Z|#fSAUdN`gXM8!3wZIg0h3yx%XFgl ziZVjKSb_%HAqVw?s~K$XPUjSbXsyBTZ2&e&_@DG2$}9 z{kEHxn_)$>1wWcw)yjteob*DQvtClw64sH2wS` zZlE~qNF;hYIzx*&o znH803@>h%Cc@N>273h1I5O>t_d7FiM2uO1TEQ->Yw{;ED&J4K0gygj6Ulmq^ooFIM zn&ld%_o0X7cTP0#xSDjxdR{f3k$~^tHIoTy(4-1WO?t`l#^9F7)_WcZtWHf-%BZhZ z``-)@EH)4?#+#}iPQV4|K3a)B!zmx|SCCZMt1Cp<#gj(3-mI*8|GgBwyS=c$D}z&+ z<}|+|_^?WfBwI2fDGB@L8;f|QM^;*J!q)R<2BM$2etKL`?9MzF`Y4~(O-r}c4Az)#(N_g8uG6uG7Gw*I%mJ(5FrxSAhT)H=!jB_4VCX66zj3R#y>AKQkC%szo zf6MoD3<*NrUa+*TbIlz44Ypb>ro|*{)qfF_las8%sZ8i zQTY~}5vBLS*&%lwOlUrYQ3oE_ax<^SE*dp|JJDHc;Jr)b{?XwjxWWJlVnUF;#)fMF zxQLxGaQx`pTh%E4c;0|PQWs^F$o`#d{qIQ@JKgu(;31VV-E&F4#-BDUEW|FX#A5Ew zB30yOw&QYA7nuruU!(B_jkm<=&uG3b`a18h?#shS$?pOpHRD-DU2iuAlK8M^KU}c< z5Eyd>UAfpKvb}#M*k5cr!M0Hh+$_JseWMYe#O6{T93ZRrZ8;Q((H!o2Oa@<`6djf% zwN?;E3OOn3X2}`mK`JFcf&y=}wY0iZ1WiBzwHXo8zH#ZCLuGVNgFpQa%1(UHlp)mn zJ# zY52u(BmzI4AqsFC`O?ow8+=wRUpx~9kyNa=+O?V)eEalBpED-FzSMAi8G!6k%)%yH zn*kRSh@&GQSt6XDyp_yQFxvbxe=a`nvFV3yB0^kw=|DD`(#a4x55Urw$DGMr5q!DU z!*o*0iqbeagd9`WSY2ID)Xl4&{fcu8;rk;uC4)8Q@v(H+M=6AYp*ihqjAd&#jCqODa) zvgltKdB@Sr|wts3+o9PHmOckHRWTKxEM)nF7wgukN(Kn z5OB(RcX#8W;8#pgE_u+TyYLwk-h&q5XOGx?H}vpqul4sQDBW!?&cB{S)DsNBrLt5^RH z(oss5N|I+7OZ_8;X{o(>5kbcvEp_lvkD_nBjRms>UIhoEeahV}mHZXJwGIQl_Y=2X zu^j--L0KA^qK0;RZL3_`@2*M7Y+(LaKwIFYXRR^h5n_LtfgnzMRByMJ+gO5Df_sZK zF`lPbN63D#xNuiO$io`Xpp%YC6J6w!>|PqiQ6xE{7L*oUv%MdO zPcblpFrGhEHQ3$J9{effK)(9)ElbOfOw_}7JZbB5+8lW0sr(^m#$ca0Pb7uc;Yznf z;4K&a?ET-y7IlP~3B8w1|UYHph3mFTDC1wQ{cn$4XP^`JMSa4T%A2 z)zXyck|3#?nf^)9DIJODi%yk@k+c>8>c^fq6j}hLq#wB?P!juJ2Eu z##2_?AQw!C;F>}qhXm3dtOuB{7a=lbk=8abwy#rj*N>*Yl{uXl{BG*m1u%hsQz0M& zON)qj(ch^>$Em0YxM+1c`f4lp=eM>35|zpPJirDg|FcxJl`z(n2d}5c3#LWQCW#CV zwZ|MA4uXO}LFAuv+m*}=99%KQJ^;3(Wg|0TvquwlMaMW}pBvrhy_<^m_Xgj;hU@>g zW=r-2Qxpf>WYyEgB`GBV{IMTx)jmMaZ0rD&l|{s6AA|l@3Y3=rH?>mz&(LZLDqF5} z*8|F6xo!97xZx|Z$j-V4B7>sAs_1O-*`akYuqffTvj!`Boo*a&ll1bGC%*^_Hccvo zc~7kp${zPzg{*t7Uq-!_ao^G=Y($J|^bMeKzkzjdA_s@}s@}t~PcjmP0RVr?Xi6kv zX5`V7{3r6RwMIRl460+E3{h&G2#!@sxhhS+3bc%2!j}iIw~XJvdz2&Ot=(7NY$0_A z+9Thn`Ur4XXTFpQoz-_0j3s<{-2B*AdDNmUxY_IIC%|zfF;(_HRuOD5!bS39Rll1j zJsA+G1b=*PK9E)ml}=*T=Wi$}V4T@gk!&pOb2F|@oi;3E#uQsJB!Wj!AUwZx?cEW7 z7VUq?IF#r9Mq&W4@f*8{^Xz+TIcatg_h!EYk3|J(&lF*(3A4QZTcAQZc(@ zcB4;Y@IY$_RxCC;x0icZ&n%rdZIB*#SZ*F+Ih^IS?8PfXJE}?*GYbd%J~WT0*yiN` z+!qXFp%BLPUr`pB1jR!5YXbeG>hvX)vZV8{((B!~-?)JwI`{g2t00;AQfMqA`hTrx zpfm=+x}yINhB}(A2z>GpaMFBnzqT|V-4qWwT4X!l|Ed6Ujs!m~P6s)uy{S8Kj(FD6dR9YG$|ltbTx(jm1SW+R1+f|`q>0Sxx7bevmd-L6p{ z8Wk$*E6z)n{ugj(&vvcs)ASyOMZk`%rNPgQD5Y#v<+|<3Th<1g_V4*3W>v{OZz<-u zq^l@QU)RAVN2eYGR1&YlrB1-?%ZS!Bmzq^(u4Psk48DjS}~pXdEITL)7o> zXrughaT^V3kwS7CQOE*oa|VcxT-N6hfT*-Dx_p72#1+@pPIy46_Yk5_+tH-A&TWs% z;{rRNza&`RV-EY2@j_k!m+{^v?@ugJpBszcrEw^4fwcRCSMU2Xla5+$rNEL7WX-%r z4Gn035(NhhDcFjNXdu&{#&=_}%+*)~Kn22VhxUOJ>PSa=RmtuKTh`?|5B6^D00Eh8 z_iR=5yvN-elZ@b6KBwiJ@Dg`mX{-GW*gCTE|DE$yvzDHb0yt9FM{TQXLI0pvoB#F~ zV0r@97bYUOMWA9G7V_FA%KSzn@TURb0vjZ>k&c8*u4b9JQF5%&GZD_oRDeRBRlKX95qSv9O+4mmP z>rFiKLGTY2yM(rOis#M39el1lz1}vWC0MiPmLFXI4%;j>xKRU^c~)#yrE9f-9x}3w zq)Ll=WWC0lOJnpbw5Ljd_gji=2`zhVht3G#(xXy~G|)>kEZZ;T1}UaNYC~y^E=&?W zDV-*be=z3|@YAn+2rE*k@CD4PZ0H0^LuP;&vd6)<^j-(e!a$7=>8 z0aSw_+|Jm{zW$2#rSqm}t@8=B9L&C_&`kDgh`FM?$c3a1bujD<@VP+J(M?k|6t)YR zSM_tM?eNj*_}%SYu)f*^xb-OWyO{A@_YYUJLac4&fIUDt)KCnGlBr2)F{fYs(B26w zC?+h;d-sMWmOz_R2Yj*ia$!kFfld4Yo~Qsxmwb1lZe)`=E<|$V#;E)7)KLa`SAN9t z$s;m}mz71ZYvR45NN7`Ohh!{|z1G)Jnj$@pRWMfz9mYNtyGa<%>8>s~LU0ji#yfh< z@}An|ABPopOV${WcGw?=7SsRMPm{ zmUoKCW1;kRpp+kvZiGMgmHExgjP5TNwkj7=<4O{NU(;39)%D=Au*BTUd1Mmh81Bwh zU0_X~({iw1F=)3IKvFS;gU!jx3-lf?Qnp-)5RKCOx<2CoY6x4}CFSGnlb|w^%0`)C zBd&C%d1ukyCg-=VV5W=8e4Ouv4OH*G{_@lYHJPP%mu(aq#oZ^^8UnX#HQaJ29p)3{ zf;kcV!-&?$Tqmr3V;gq=8zEW5N~1f^dw2Ir{~OP@c6O-`j4CYJSuulE8N^`^7f5QX z$H86=;!SA{)|m%Yg9ZT}9Qa|v``KO$f&e3pQB)XvYQR3jek zA8@e3#P`2T^cd+UpD01I%C0^=5(2X_4uQ%U8f1C&q`FWvrYMuV_V2HkLh@VWN`He$ z0is2KC5P%B2GS@9^+&Br?7mQ-p9o zv)+W$Vnm1>s^~~Nn5(~MM>nm5S--}v3UV6Cl+Fb0*#*tCi$V^ zNH7tCv@pV|*o-DzYuf$2NWjj~LwwY#M|H-T-jw-$lERV8i$A6NKU8&sQFNn|V#>rM zY@6Y+Cd5daBlSmza`BB1eMk=De)m%Zp{@FA3tY(luAjOcU%oMI^rfAxu+Y>?5Y2BxS zKr(`|rA}PUvx4_C6}CzPSV1hbv|*cV{30IvBofk@z3<_R@vgmV14AOS?kvoyeO7hW zkB7>){*{W>m{HmA3V^)#Y|1nXE8YK5Jm+k_lXv-MF}4NTl?rJ@px=kr%0X`RdEbni zZsH0U;v)Rjx`C(VcYr>Cew;o92({eOwT+b}erap4cPi;YQi7ZShxK*4d6tO#?`K!8 zUA`{o0qy>xNu)W7%zfbwz9}#>eAGT)(&6%ijxl|znE1TN=9?)IMWKYhygm1%Qn5p<}P5HYVN;qCT*?%9Il7-F)$!b1*Gs`lIC>R)*jDO5{tB3h& zx$7yT(G7nGbP&P)>~>0p{;xzosj|DhV9<;NLRk_=&54r{DV4_bi4C712xe?@gQxsw z`m@3j4e@W^*!OSHdtfOP0KlmVq0bL^x_zEAfA-AM99$tL&=C%r|6E3LOtrJQmR^;k z`Qf?Q?7Dwlm%Oj^Z?fzk1zM?YXM2XG&7QHH4i7tw!snRZ^2dZaQL)*z%Oqer*r*74 z=+mn~fp;gZr-yN7@Ee%nK6lhm2f2^{snt>N9gS7Hkj+@5`W&~P$-;M9IyE(wn4TW} zl7hAf3-3ou@aH2SqmcHL6=-wOopxZSl z`4u9~NmY66wQKD$G|1=Z`{AZ{-T)F?%jTCM_o3OiS_S5S=kV7JW;0Jgfzf^TXHH}7 z)O{OF3fg9s$7GR*g|yvX2ao+url^;U3S;YytS^DTHlXfgf-6~tbiGZjtnhvLi!Aw~ z@8;LhL!@(CreY;A-k&4v_B)Rxvw+jzSU0SmaE^Gh-VrDkI;8@qa|C)HMm%pF92obh zFFs=8`kJKVV-N}p<=w&2c5hbO+?AfTW-Du73FI&;>6Py9}G!=4Q%ai|8>i?0Z+x|w|snvD(;WS4rW!a}+F^7^KQdg$j?7XCg$p7rR5 zIE2o%2CoGD5gJwwJu#}ie<+K{e$=(M*G+rT={)*xhc;IR2>d^%shn5tzBn1S6C2Ufum)d_JYV}&%{0898d~X2(+zFrl7~Fk7JBsdOJyh> zCpB4UwHB7L{fR}vJk+MPgn6g zvd-|O`xoy@7CM>qFr-V46ao#=QV86Msabq}6L~B&UT<$#beKi9r46>bqiu0Z`_%^T zn!;jsM-who71`YMo&9U4gr`vio_7b&<{Ocb$!FKChOT6T3o^7EBGI2GvU#QG;F+ATrqgD=ON`rqd5cS>E`F6qxb4XRU-k zo-QeD{adtKj65&4L&OdhaEj=VGzx|&;yo!#T*VYI&$fUIJ^j=-h-t!THJiM*YTdkr zV_@1mv==HmHwB{w;&JGG%X*BV5&?qICu$ zcnezrFCokE9Bz*N7yvxIvs&trC!3`-RaCB_^P7w`9FiI&L94^NH)f<%m2DWKD|g|! zu=z3lmt% zaa~rww9u#U<7bUxihcKmNIx)&)*E5$bMuK0n+fdPS?IQ?24sWjPcXbnX9qS?L&-di6; z062tUvfb+ak^l>eU^;q+onT&8{p&u31{PY17$uXHPW1ox$XFpzzW2BLUVijh@0%%0I!aC$l354QKWnY)#&mqnNr)mB6HJah?5@YrO=8Sqrz(;{vPyZbA@VW zkLIhh5r@m45p1BlzuU^)0N)4nBXZSV}&ZV2z%%e-wwa>x)3AKY42y?zAp zb+Q8~&WIE48-G`QX7$iFppSmiHiOZ%iXZkaX#6K?9@H4& zgRBF5zuu*h%!us8=Q$P1M#=Z_avyJ$qirl1=GX; zRI(?tqlqyTUq}PzL$yWqly&a0N}ehB1MRa9K+ErxD+pX_uh4^(?H{Wvcu=-WuRZ(} zR&=^R3#tH97e0q9zqQwa=s=byLBI#l-~wv6k=#gUEJVsPnmv{86T6ev{jUBI8-cC{ zwdODazj}xu;JoN0grBt8buQXf9>^NHNXWV?G9i!6EjZs-Zj2x#=d17Mg7)B%OeJt{>VqAF}h+@C*!QRCOQnrwcgRbxo@DkjgJzHiT zFwF}|X$!NrKs6{w7O<`iyz)LImhrlf%xu13($`hO$(erPDU+-Dn=8B{vNJkk)>2mb z=R|eITJuqxwa@Lfq~HDRg@Dx$I<-26nx0NH8GTle`pH&6 z`HIU+?mNn%ZH9_kbEVm={&@R&x}jzpyr60{bEbkM-B{OGal3B_1c`38 z0wEU{7gC;+cqvuAn76J~PyN$!T;OL~V58L2PjG#UY(FY9A=jWOK9eT?2z={kF6P{hquD|N_P za15!U$Hel4-a#@u=*m)QB;Cn`f3agLD-Q=1JJj<_>{)cI9m~uaZhaLnSxj4&DQJF9 zdJ|ZC`JDAHZ*rLfs+hKdCDSZ}1>xfgm|zA3`eKM44nB-z#Km?@g*D{3NLz;6-cPnGIWgCoHv5C=4XEhr!ulZ#qP-o#y zgXbw3Hu~)AcBj~J^kJ{nuv6l@(%kySTzxhz5OF0 z;+k#i%=(uEu3D%|8YWp8k^5&*>+<-}%XM!{n!?!irRTGU8k7}or@oun4##UlsmEut z7S(>IBhtqQ^{Z8plo=7$byW(1CsbIR(xDEHN|)bFOXt6Bsbqmkr_JS`KFczVoo>)M zXe+xG>1q1+rsO)p7lcYl_EFh5i;?=SG0M1adf0~QN_%M8OOe-?C+mo2U-uVe0wgc6 z`H!H8x|8R46}2}IHD5w>S=^!?$;yy^#{xH2J^a3J$?t5Lka|hsqcWm3OqluLyyo5V zbOhkbyFyF1@IbYfj@;YV#=Tk5BQdn5bgeUnA)rx!GhdzDOnbEdZTxE53Euk|_D$n* z(c#|3VIqt^nkayA@@Fjxa+A`8wOaj~U@BHv+3sFLPqND9*-J*yjf*U9AwMzH%W1Y&$BfN@ z-_g)m4OZvcU`6-IEP;UYAfFK&zh!?=Me2xs9;~?}8a~77)@NCDVa{;)rAw(ojis)A zSK4MkVR!oj1o-yKggYf%?Dph8iSyeN6I~h#Z2DIP1=RxUGgt{TriOC&dyQY+?Rvd3 zc!O&xTi+pA!W2vyN_!&5zRtH4cu8X3>f57lXozd!e?}X_Q0XIMd#Z5x_u-eEW(<7d zxNbl+u+{+OZ*bxL(1lnIf$=3(<K5tS8`~XkjGDdH^{8}mg2c{UWgo|R(2LOMdAzE3 zZnDwlzwLz^)oHj=kKM~vlnCGo0ZKSHac>{WOIb0l|5^T9+h}S zNy5CCP@89hj<2=We~`caVi-8Ba<&g+<5Q7D1x`B}E{5k|EF#w@nQ=45IY5mEI2&yD zuXc?j`}U}LbUP}o`@S_aFvKy-Vl{gn1sCwEb#|?gYtugsbzc)AST({i5r5gewOl|! zRn5knDJ6oat0B3X|M7M{F&LX?@flmZp!FS3T*raH_Ttl+~{u@n^+1V9y0gtmeB9y5o7d&;GxP#2;%yC{8KZULhYm%j7aAmkVfa5| z*XeOQe?Urnlz*~3WoD7`KBkd>J(VmiAgES)gYegftVX^3IUQS(yN{|z*8kN4)uH(o zP0f*4SU1T(Yb8`a@c%@$MjUUZT687xyaXceiUh{86_XxHML4ql*r%V|ACt#A4{#F^ zE~QwiHw#i<_g`u&~+gsQ3JzfA12zhPy&E(>6-};9;6-D8dQ)8d52$Jsp2lgFJf>i~~ z1Q+cE2(r=VxTrG9|J(A25wb>JXq2E4DRFJB9AcvDk1b;CS3|TUU7SNqr`_qh zJDRl1gU}Oa`O?k>d2Eu@eQL7D!?aRmv_W%6ud*U<``#0XMJolX6{L2`6|2a;y4gf& zK$B|KOMU_o&``RR-8ek7&%?eMTsdlj-n2`!0qgmG`A_~RAKwp;n5 z-W9QSe$GlJ2^T$;4jleA=VFIz+IEG(s^muiSP%j>oNU$X5chv$G*DHY=?(u6TVEX( zWw*Ue4Ba5zD5BB|(xC{bfG9ci0Me;+r+^@(f(QcAHNXH8GSmP9N=ghJf`Tw~H{TxL z_nh;d?{}Dg9K0@`nde#i-fP|Wz1CivyW~S`Osinc)%zq>_0`arY9rT^rgf!=;poX> z);<*5ck^hALXbxrJ~-Y}y@g=rX^06{QmVoby&yaA%Hc zztQApHyi(mHpNuY!8ykM$5Vlf=X3js!|PR75rLhjJ4i>#G|nD-C2%-dP}2ElZ36ap zJKJ1RJa|Hji{HxH+EC|viyC=G;l(%Z1Qr!*$v`j5-e)l|4~;aV!A6#S2implXWa5vBHNbN&cQX>xQ4Rfvb8kic{b)p(?4S4}ZJzanYw zg^w~EOezU0eehLeR_2nmZ>Q2gzWdySK~c>VFz>;QEKLJVJI=|CPbA*~h7iS) z`STW=Txyp4T~`r&`#__wWhNpz`tJfX1GG$MuA*4DSwal`)0yC+)_VDKE1wlFqgXE1 zua^`$qFIX3glM+XNJ`G5=0i?hJb*svS0fPgB|OZcDi(@x(T5%%Rp5({ig*o+m3IcM zREqM%eFu6d_;#1_5JDK5M@2ABJz{y1f)=Ax0-DdTCU+`Kg*B1Tb3}4?h+(i`{Fuib zPs`V2-8FCTKHk1AVwT7kKXmh>GqDzb*a}=aE7fN86v8BBBdG2gO~{%FNvU$=A1hZU zjGS{6+3ySf(RZB<9yD4VqyC-a>C-C$!Uv50DKeG?i=HY?CZ)Qao_p7DAMicA^;x^% zK#=c|dp{rp&sjNIZJpS0QKFGQU`LFur}jx=W~jK=!ejY+4Tn4AF`_@-zx%k|Vd5#h zXpvXSnCc5jW1%(BRp2+ci!IXGvqpic2uePV^BO>o8lPl?P{Wf^-dAe&>h8 zjl#0(Cnm`A9N!X_ENLn@KGjWljcIk}S=Rl&8(Q_cvCx3SO>;^2-_*Gq6BKItkT$kj zMQF7U-R%vjuLx<+Nt(^=SzJ#~2LqSt&pvbUbXV(sxo^mKJvsKNO|Xs@19_x@ga11Z z?a)0{4s?sf=mlUDIPNu!*=TG#j>rn3RltwzqrQu4wh6=l$x=rh(mcSp}~+ z6Jfh{Zu9lHxZVl#*stG&FG=fGkHmAQzkrc?O7z}|iW;`2<3mq; zQfcAH&B>|3XB4IEE3+rS#6G}4ZK{hY9N+{f3e0Z}2l4ZPFZ<+Z3S|d8hh8EKI_^Ia z0^U3LpZe{t?#KQ z&yFuZvMSQc&tZMyVh)q@`bC+tl8Kwch<4Y<^iS_U>>Qia9n9-=MKODXe;K2K+;aA` z{AmU$t}oeQXBX`CHb;)$V{vm@ldmGDkRuZm2)$tcKGESR7~a0v7BWONXZwp^(zBXW zOO3O&a`z!&CvitiO(2Bvh6^fP#%peU8YytFK5coYogp&J^Gn`CS3_Fds@5Imz@6CT zl@F0Vto<>rhPZ_b#UU(yMZXg3J=ch2)SsZA^hy+ed2FJ6@q}U|5>hCSGo-iJ@`-+2 z#BWbNO0zpMC%e?%6gSjp1dN^!m-D;H#U;tM__;hh>B;g4H2oQ^MJ;`3Z4l|0M*LH# zlR3@q=u0}eXukuF3h41s$J$EKV9F0V!d5z>Xd|@0Zl;%km)%Png$Y{dtXy*(`==;a z`X^^xviZCFp28_N?XLMTOO#EvWyL@VLOF$J4^Pr|`>#3UtiOtW?L^MgRM-E0h)cw- zPl<7>vvfTong-G5-~YHcgr9`0d+pD z$UV-kNj~A!OYvI|6rSbLHocnF9C^-1C$Ez$z4wQ zK}%w{Q;E@%Z(eCsVL+|#&`XG^3I1nn5jyx!`R%P81dY5J@xMf6`gRYhHsj~A$~E~r zppO34m~@XQL4K(u4hJT>MR0{$AX*U88HJZJA0K(iy8E(g7>*Hcl}|D{k@uTKzMvil zq$F5LUIu;1x76t_<4%&mWt zM0}TJ%PTRjXDK)$tY*R@PU}?kd5@et$4WqZRgg6Vht?gX9Hw>%xeT{hd@&_%wSgv);w-wOEr5 zL+Vpa$#Rm4OlO=hqJ4WttW8GZ^5wUDA)L7p8P;~~&Os;juUqPq!0vCbaL~JKZ4Gm> zXbsg>TPen~6GC)A!gck*SVX%RNX`b16DlJ`+pC94MVOR}k#yvp^N>jm3%iOM?J(zQ{|{+#ha+E&HEzy+SKL>=pfUVkspt}w{o1ta%)_I zZdH#n+{gU0V@^FwNo{DX8L9g2{V*DFT`rNQeT!)Ue4k|=)VtHT=>Xf>*HE=Sk&y`dGBX(IzK8$M8s&ied_Q{&^0H5X>JK#JN+qj z$s~~Ey-joYz$z<(cZ1fr!Jod&teH~6c?$N~XD45PczoWt(U*du97OnHw+#~>yq>7$s?QcKT+g;q6f0xMEow0|E2p}mBGoMvgJKCYL3iAGm;dn=AT=54M89RUrxv95wmu{nE}$kgy0iXnCq z>}V(6Qf9c)={s>iDQcG5daZqHQvFeWNXO46R1tVxLbO67O#EuWHEq2Ua|>s3m8%hR zzm{-0rv>y`^=b(b9nEH&vXC#Ee2?hMJ27JH@V|ftf~g7rC(uAJ3@!FnM(D<0HE`b7 zPtG*X^eL)m&m}0!7mNpC`zvglQT&7(;k26*?z9Z;Y+=i&NR+o-9C=uYHm3zY(OP{~ z`TB6Ho$^OLMf;5oZt;#T2|Bpm8d2mXo~v09IYTKwBvSV}c}eNJbsQxEIJ#Ezcn7CD zIN`vTN;IKMLh{pZPjRr3cHA|Bpy$u+Ar63E$3a6iL&WKC<-Q+kJW+H}YEv%g?F~Ce zQCfUpAXLNOZ;hMkP6+yO{gPfoFg~Z;i|ubju4^5FbQ=z*xa#Dxqf9;8ute) zW$5I*Ak2~uRdKu)PU8G4;P@3~VxVcdGEB|{7e46`50V=`@sLKHUtTafA}Uf4{2~KvVdd8Wh*&<%=fVN3#G&9L1^U))%nlj{4jA<3~t!&xjs1~1f5ER zcB1%4H4fejMveG)AIl{gYzsMrkNnsO7;p$(|5SbbnDXnzPaKE2WI{J3%8=1~t<8=r zZ(Fk|I#HsPQK;&p?K6?D5^A{Rx?DBJ;cZk&cXyk71IuL}l@Qv0(O8MNQ=QmvDq>bs z?wcc>jbl7YA7*n(vfOEg$AKY51?t{VH+hXmH|5nzyfn-L&!|1KF-a?AH;H6Qi%K^4 z@x$LJTOZRhi;RX%*H*v;du5%R*fX~lrXTk9;LY+FYNpo{G92Y9ww6(GAFL`#YHg8o z$d`l)H=n-N4djBFHTl2KtE|*Y-dR!zZ9JiDtruKIz>cIrl0|r%hL7o9)ji!#p@%g2 z1RwNBhe^|h9U!^MC1;N%)t}Joly_O|aSkP01t(o_0J^zaEZuxhXI96RVumCn(FkQ} zN1W2XA8~y@WUj%HA3lSix}8tomi$UV(w7k387mKJgxz8FB_DQPM5P z;|B`BT~SfOqR>R5LI`Kct&`<6Jv~fmNo_HVKnEPJoc8?)Z85BBsTZ^WcAXdCBHzJ( zRkcW8?D8H09_V89E>zI7TL|ytw%Y-<-Z<{7jROfWPFcDAkLL|PFUsylqaqfT|{yBm8#Z4jb$;UYU{Z?nNxn+5TwZH897#6%8ui(bov z(>7C*OFXx#bX^1l?p`p~MzAlrgci0hB>1gR`X8$5*ehTu&V_j$*RuU_8m8bP z$F*VvRMGgNinK65hdsp7- z>J(G(PLM${yK6*^e*2+{vsNOWKW+D)b}%xQxAhJ&j_r9LSv+G7@wi2JQdCjSQZW(m zL;e25C35DXCqS=jJ)~Lfcky2(1uPJu1N=lWfx(QA(19E+cqA=T`v9TxK|b)D7@|s8 z$wGZvk13u`Vy~&=6~rwId~ydi9fxA+9R7Ay!zNfr37bUYVrruQxwqjuw66f_y180J zPYoinCXiT8_??D9;(MD)pon8}?;NveWP2Lgk|96j%b=s{t7{Ojxjv8Tz8x>|HhA)Q zVUE_19Bg?nj=q1HW7F;NElSR^!b>G z_esy35^Vkr2`JCl5K+u4h_f{qatxkv*=F+Lr%x)xgtCevKR8OiT>26zbi)%$Mc-n$ zoE3wY+s6Q-4;cXrNWFJFn-qU1Pzco2x)WxpOAGp=LKC|1T~~Ikyib2=d+)8#H~1gX z0s^tCzW7=9aqA1R$G+Q_1a=K5w9f`x2ovAqf3j#*z9v|5@VMQMa+uVE^IL*pgGMRw zNO#hmJt2a^8C>ngnl_#OD~Q+DH9@2Tl-;2SK~*mE{s&{JKGC|t_Z(#*8ES9LJ9@6^ z;aZr}oCf7(7U*AA>5&|(b|@bi?9Y5BwmvBEw&MYd-^&%S+iZPQV!VLy0HvK9?B(hO zkJ!27{WA;^^?4}iseaAt&y5AI+1?a+=H;#<16|nvloM0(4Zn=Kf&@M*NIY~X^)vFC z1uwC)0pFL7ZlTK-NEsoJDK9E9BHewcoOOSW)UVYx&k_Z3+C+?(p3QwhlcNccx4_KA zt(OXL!Y2giLplNe>8W8nG^pZx2hDrkHbyi&yRu7`@P3yJy$jf2lDB6%BoGQ|%Ojfm zn$|x_0A0Xs8CvSmB}E^eRRNN)Pau+jl?F%|;)H?|0{`%TiCap$uO6Do`R*UPu(Pw* z>TL_170Dy-ed2gZ_gQtyE|&ERot$Tz&PBi+buv{9no~O(xqZD*yX&+(v`i*7%nbZx(^k2?b06(bT2Gy zfA1-3_B(nl^_l2DRB$_+V#}93@xEforDGAf)zV^W5yKLYy zic2jlnvcG4zG>lnXZwBRtOf_2qWW%r92H$>6&op%ylR%09L8X@lNFPn82Rd5Y}=`6)b-Kt^C%_6ym^`2CH|M@$bUM60)$yE*S^9 zh{0m#P9ab`BSuqOj;FWxzs4w>o>BP~Z#^U4-F!@n_LI=bjBhz9om|O^DSm#eMI!U+ zd(XE^(w-|_2()j9Se^TYWh|kC>~BUY+>RZ(c1<`Xt0=8X9P`k6OKMK_?z@6rv-27S zFe3lBsPx~vo8d_qmFa-jBMKZ0CHuKkR|0pH(Iirr)6h~bs?2KhnA?q3VKnpIX)~Lh zs-$_)j!=!0tkajk6!s59j~vKQ;``SGW?UQeVpSZdoL?=RtktYDP1a=~uW~x#KS%Wl zvywAzn6r*lf1KbxQrNY2sTXOv$@K}-^71O}gLgkjq0`ze_A^G)^BISHxVL>c)iobz z#8+qFWanI=_+61~-IQA&G_J(5As?X9&cak@EFx~Q7X&sfsB#zFAd#BBkuf@2K76uv z0|oRg1Lga_k{qb(McF}t7;M=?f*_;A{bX^s{aSB{oVTlAy>*x&M`04zUNPH~HPsr& zv{d%|Lh|B(FOM79^L<<*4bF?mi}js)!sbWXjq7I z^tA={gD>DDZkp$e1Dy^j+71{=wrz zyrDWfdxZ1LPkM+Zw^K=h4^nhMD!4kS)6vF2V~Fso2Bq-)?a3MwqP-V0$}0k zPwUUP_4I~ zFK~FLniyTD8#}AH<$ECIHoPBht`=4;8E-lp`5{|ai__b3BD`v=>^g{*oCJS{!(MQd*AH-c87B zFQ;ppe{hgy@?8!2YU+m?mLTgnt2AG!WQ!QTT|ybWR%}ezLh-FQg22qh(xNmaX{5ri ziBsUrt;NyqHvZv*3})C|6Pb$Om;qT)39|Ujg_L{WqF?<`tM)NVPv*r85q<|*vLsZ? zud03o<`Kr}`qEzzplpo89qR}7P6leSd0Qic$FMyhUy<=?njZH6-61ooM!O&)GzLR> zf9g5)qqqL#5-!sr+ir}opa;CU>_`I96QQu6K?7)A6T~{=@*_rKqd@{vF2QdrZ2}{b zy~x5Zis}5aUBHboF*!_LeD>Bur(3DIbb}IM_MS4PwUXO)$b2lp6wj#Y(JlC+FQ5O-z5?1)LFp60| z2(B(H_8qUr80*B0G(7A~FEsWA{pleh+T_towWre~r=E;{d`#kw5NxtSrMo(w!y0C1 zmBa<0Ht~n;J~*lc$=e=e@aCiF{+a60NmZFWCHnN__xjivVJ6YQ1gd`1ontb;BaG?t zJX&$RBEViDxS~2#+*#Z0BZEytR@M|6B*PRGAR)esjf^W1U(i{Lew^qULORSQ+D2a{Skg82r}xsUy`iR4Z51m{=iy`!LuK|sP>~-~nXKbN zwtyCKrV_@?2DR?Lq%$c`Ni932seMJ`T3`@~vMBFOsN-eS@?uo%NYsRxjhl2y1#s@a zoeDXedPuA;M-(HG4(Tk+5s?$)gojM(u()K@uIvLpHUJvUIOYlM`4dV31gWxy3 znJkTnu%7dWiTLId-lyJ$^{!cHC#>forO~4|l`!N_86CQJR_rpikl3W4&kMC=zpuR? zQ6quqz^C65?JQeC{3#!O-(`FRY1`CQR-|LQf_gf2?M zdxh4sIsPBGMTHa@8bAT%LuI0q?81NJr*!Gad&^(%MkvwVhR zzGV$FujgH>;r)ASwP(6PGjSKXFB_di>mJB?VH_F_wnp8ZE>lq=h@;k=5Cf%d@mU?T zzmi&`ZRR{|OQ~v#1s!xv(1*AS=<-T?>O9-hype$3%^x*KDk?KnpfbKNH>de#f7j9J zatn}u@3nJ=lp1iNGIAuiPlip#_%!yvA`uH+9#LN#i1}+W%?r%>;)Oz`#`}9#-f_@x=1e{z4c*1NL%sr9BnE|({~~+$qMRu&!krL>MaDt z8w0|$!Dl(rnRoVDm0!XfYLk`rHmt326l0;C*MoDyq1oA(C$+w6IvpqJno^`@CI zfyE8k2O>|h>yd^FR7Q^Wh(I4pGV+1tKI-G;q~1zQMK=qTaeEkJRjpq|x2#L%y8X_d z61h5fp=wZzkOIikI-I$ZHk6<{V2n%TPkEfg<$HX*s#;D{PLDc9zgdD)wtqv%wH&i+ z&%TiuvpZY;W`c*YCbw|6%zWB514`&*BFth>)kYBDO%}MLb2{R}%O*_5t;Id{F2gK; zbtZp;cy3x8a7@n)*^Ex`LQ7;IXfZLYl<2_1!Xou)y|DErJ)>)P zJBpNfMwynIt_5Wt-sdo6$~JKEnSD>I{*f^QKNODadE_niRIgP+Y4M7`1fW(&2fI@? zN~)Gmva09;=MqiNtD+m8(C8jqRQ>h{G~3zFbzz+)vWw^&Kb;;JPis!ff9QLr-8jB= zuyw}a$Qs}~@Ch+>&JZ$r{9b?rBiCoQby80_bY-K_hAYv<=CK~%)YZP`1*%WUn~(VU zMfCM3TB%?g?J`Blv{C9V8?Wbo!sFNi7YWcA!)HG8P#Hq3Iu z?!J^e$$my1fk9s8LpRo&YR+wOttHg&0%-1Pb(=3Q(r!Y{$jP!v%}t>kJ(PU}$Y3@^ zneI*&Lb)_}cqq4GPc4$U+Y2?N>(_GKR=5uSm9V@EA7QazPOK?Sy5;u`Hre$2{%gW4 z-|iW0>e_{C;ZBmo=Otck{-YX|vG%9lJ-zrriGlvQIV$6sm=Oo4RwO0+-8@dekGc7T z_ZkdJNlWOd4B;iDxyW4W7tJ|WgH-KrAZh@q|EP*wcP7p@_T$+k)roj}Gs^(+lji_hy`CPYRc3 zrX!$|Udu%;{Ab>;Uc@_n=%W1o+(*B9zLNi-?RLec;q1f4Kz3V`+5GB);7{ixwv*fl zv*wDg3msAcj*bmKlRh7qUhC+RZvxWy4QkNBCdEn3_kGoid5c>~s8fk7M`Y8ZH` z3G;C_k#C&{DgG?0qJHa7o9rY{DbE@Wd*;Ypr@9p8@RUQ~l_Z3$jjA;iVQfd)a@Wqd zl`7b%9T)m(AIDpTGdvHo1N)VKmw4LqQnt35`OwU*gtt4~j8tRQH1W7~4SBPr11L!^7MA4oCL@eM!%D=@kys)CFR^%- z&}n8T4mhx&kC0+B)@n|V>CUcpYGXM@TQTTmodvr5*V2KlxLfttPXLi?XG@Ijj}sFU z^YFboTf^XG>mw&=_ChmfvA>oI1%8v&j?Ad=U<>LRig8jRnrh=CYlpSgEe7CVcSYcun=e z@^SiG6UsEN%y3ZQnmhS(DA{jV70pR1n10eQnSSB~8VY8wYMO38?@94tba(GqUm0FL zyT9DYC{Q$BtWwx1=r(oxfVxS9k=X3_e4qU@3S#qW7L;!*)MV4+mcEZ4@GR_e_GNP9 z*`h(H%t>R@N%(ZX|Mn$Bs>LJNbPqdv?7?)3zgC7aY&xnTU^}}vth%1c zWU9%AtOOrrPiRJ?4p7@1PF861)jU{VStekPq*OXea#;PU?O%DR|8Wc7 zqa(UPjd4rhA;sbhHzGePtH@3IzWv1I`Wr3c(k!>>f4_93D|Pxn+i;uy@tBsP1uHJ1 zAcoqnCioj$N#^S3R{Wc0Jk3sPmqRhSgA&Zc$b(J6 z%c^}hM8$UfMXbJUE2O0noe}LTx-r$%P!bUp#3H=yZoo^;X4=H0SdEBEJEiNKfB&r0mV^s9Sm>?Coa|} z<(#F(U13!F$*;IbV)pjd3t|5)+?a+cLu34G(vr!0OX?2zm|Tw$qE22FsWye??quXz zPWI#9lPCBH5ssOk&}l=|v6;&ZgvbG7n1<#>dWv>EBBpc}Nf864?R#Q0@JI?~Lq0`7 zAb`S^Q0!1Vd%BvxgaSMvN~}AH0F5n2f8<1W%MlN;oTA+0!?9yxP6vq}mZQXl zdj76SPKGfurC*3dA6??;y8b$q-nU6!c&4VK)Iy)$HMWKI@ccwBn~3UmFNcihIp3SB zN3x=#2Is;I3?)IolDK7u@?W&Ae#XE;{CK>N^Fo6LOo@$=oOef;?zHFQMNRobvTHBh z=|NiDQ8#>)OJU%CGNh3xLMBA9ZmP591pe$v>YJb0~)^QQ8Yv zXVywfVXf-+4k({#WHOA&xeiNDO>dJ|foIuwojq%-zg}`5TUz`J@i!o&8!!FoB;tu5`%cqgt(Ur`p?Fj(FqKI=AobyZXE8kd2`CT#4nxG%iL93|k+3 z=U5~X&v-}ZS;meUj>D)3ori~RDTp|K*m?2bO#?@2%3%<&FK?GGF5bB)A|8{)@($G+ zlIoc>U*>$`+I&-Fgeh0q7skrnR!7y!;}d5%zSiUyJTk%mW=ho*xIB3ztUaXblIL=O z1Wpvrcy^4A=+me#S?kk8SKNOOaI0%oY4pkzp8*FY6GxitDV#KbGG44Tp{hQlM3bhIH?C z&aO{|1f<c0UZk)Ao>v^F%cT_K>w>y6P&de9a*mp)l;BAh(2?2?nokvL zFL3rhS5+V^5{ZrKp%;lYc4-sRU@tCeOQke%tY_nI1}0abMsIMEhQyUrJTzzn8aq%1 z=WutOE!14CII(o1|L3yJZu&}PGprRlIweg+JR=3gMW{wor@5HBb!A+GW@N##-$2~; z7y^-5#Cd$gu?B>ydI941x3UEx$y& zfY$=#Dc8!{hyffbNk0q5)BS@JY-`u%>PaR@u>{kT0XKZV0<`l`G$|-h*A<>BVT(NA zFMiRI8j_G}xZ*Xn9qV7sr$I}|Se+usV*6Ol! zuyjq%*gO5Wb#hL!l)r&WI+)u{gtAjnLrP`EN5-IfkUcUPsZm=AC*yfSZdBvp;~ zyvC^z_~RGw&#kMc{ycNtemctso`sLIW5X^l-lvye2XCCxCmQ*lkzKw#Ti~s*e6|5t zVx2f<%OKA&kx3pClQ%sXa(i?#Ug!>ad-xHt4&JaD0rUVEKAJSvu6%5;*dx-*+l;mU za!j>_if#x}X+R6Fm#hz%Hdg@>g$SO$w#uZ%r+N>;LTzSaQv(QpA>Y2V&hs{tz7!2>- z)xglO=Ur%em`}o-w{JH2lc>WQRQ7BEe?uAW>r~(PCY}%LMUX}@ySSZtb{6%VA!&Vtit+F!LaQ6N1sJ-Z^(?+9q zsT<-fJzY?n$HXcV`jDx!OJxfO3(fI4?o>ZKyy#{aI_(7URyyQ@8zv2naLasVV={0M z4L6>C$j#6Fz}eSTTSimv1ey93Db|-XJRGsPhI#QimBKM@ECcVUUyO*~OPmR|YM)bO zoZc#(OL2qpt(YC!AG zM}@Hf8A145zVD;LJ;uPZ<*Ufk<8i5d$+>dxBgGEF&)rxJ0=Pqe4}2lK!sV#E_oKc3 z3^n2wvg>Cd!6acWK+b+U+$4U~?gsSoT`C|n0UfHnreLeG@&muE^;5YQ2 zvFvnQz*He~L$M}#E<5Fbpm_mdqJMZc!^oqu+y_FJO7zhiqTAac7$}kPPBArzRe3A7 zhTO$q6;aa|iE^5N3Y9Pyv;M?kN6>YWAsp(+i2G*x-p9uKEsOQ78%sjrIiF%SeHo)% zC!T<84|Mg*;7(PnKP|r3v>nEo+f`e1WFB4jLjInX_F0xTk&f3#VF^43kI=o|Y@Yp0 z`%_(dD3Px>LJ$K+w*YwrA-T)W0q^(J_-c>YSOt?G{h z8RGKzwp~Y>N19-tdK?onp{}KUV4e#p(sj~~ktrrA2F(c&_=V>ZCDy2SLjiHFL<6U( z-13~NbQXlgZgM>tTf6WX_+X|tJhxf2I=1hmacGVxDhd2zuZ-u z1TCx}Z27K2C>%(bwDZlcTIw?#)eVWTQ*c0oRaY5Zm^eCQZqdW_=|@oCEgrWCqeh0v z>OD5fKJ~Lu*|g&`rX6o^{v9;f?16$pW8p{YLBnPohU$tq!LI<_IdL8#XSR3-99GQD ztI;@<&|p~H7y}-y5edvu5LQ!)XKQIq`d(M{Fu*!WapF-FQ-0|^NhhJi*^dnP)gih0 z?|gIdv=%yhH4lDH4o?&OEQniSz`+1Xm)mX{uWM$VK%sdGOu)iAAIY%UQxcCVXwhgZ z16C4p1P%P;V7ZN&6&a(7!Bn*|vEp6Tf2anC^TP$ScT`g4RQa(=smJ9tTzCc2_r)k} zo+jb$Z*=+|=$Myqxr;CR(I(!RJRgv^z!nhs;*sa-e$e?IWr&B2y!+Olel+n`F}&(S zX|RBM;Ogdyia} zyAIU69d@+Qq{pqbXs_LwZM&1&{D^X>qjfSp7gEr91E;zvH-BA8|4{Pef&HVyp9OwR z!bRixR|}o35oH1HDO1-0OiH4`0<1)^cBbpEx)36tOpz%_g+k-4* zPpm4DJimXDsEWd&9rKo=Y_%pz_tv--Xq8?gg^M=OLPCy@KD%u|uJ#h3YcphRA`N2M zZbh&Mzd*fvNAv>0ISN|M4%Y0avOO!jUgJ9kcJb(izGAtJ_rYco#DUwo>Gj}>oIPfB zH-xoj-Gg$acIP!Plw*1yb-cnzKe$oNj%uGu*)xbi9p|L|TJ?osG&4;ugFBm_1pwml zAKArr0)hp_BfHdM99D_f6$?Hei7WOlarxB|SE-UF$QnW>*=jLl&B7OrIrqxl!gH-1 zdvqhD(XDB%dOf9WZWHU=ZP0l4_>Xu%11_9C%{h ztPOaH!znm~=~mV!YS@GU>cycm(g z`A-rH59@R1Y8X%(|NB8pZPqKX2lw*dle9Q^61{=);HDOrk;a}P3Eb0I>czttjM{jF z=uj#{oL_#va5d?G#Eh}iR}WMLnG%_DHknA^Tl3HhjpA1#hL2>v6?ACno5-E{w2$49 zdT}txr2qe|6z8hB*%hc6KF;<@%HDzOND(r%_hMGx2sdYz(NsazPB~>=QF7W7g%zpb z+xS^5!q{#C+`Z`?%$QZX<7&nEUWqy$MvoBfmCU{Ehw=d>$}nIZ~x6v1uYpHPcjf1*j0= zhr*W;WqjU+7SDVF8?yjY^q=ActaLfc+eA)r;DOKbvMVot#2E}9WFbU@0XnGr0qlfw z-iO)gzibJ|ELACCC=BBStKC#q48ZOY%0KXIN^)~%fSTb*;E4}1sg!-J=L0xREbdNj z2ZjBqTMNpN$9Bu80#G1sjKh! zg>82Q>>UFJA#d!91Cl*roAQk8ht$Q8tbc%#Eg)?sAA$n6?d)`z@+#&d|&F*x7i0 zh@ig&NuWvMVd|fk$VQC#gEk~WbgJub3$+5d;Og8Ar@a|Dzlt~zH_=x^_WsITuiiIH zZ-O)214I7!3m1{X#S&oh3C%z9e4r-)wbP?Oq^W|^n|oD4 z(<~Us>&(y+%$zcIo#SC@NYG#u+taj>LoeIA-w0zN4yShM#~A~3UZ6erk1YPDg81t% zQ}l(AvCiQ;2}V(65&?<#waEoW|9wKnTwe=IB1rnMx|(N492-SWL@XB~-}VwT@~?I9 z<=4?(=-Pm}(j1^iCvTx0+jm%qF&G)nhDT6D`35X?r$ylpO*>s;3U!t(3v?uHobYG$#-7-m#y2L=nWWe zMhq8cf(HR&qO+2!;*USTNIxUs!Y#%NT5`+j$H13!e4@Ly?gD>fJJw6`=La@j$He?Q z1esIuZBlK&+yW+8M#c@cyQ>JjAZ+U_$WIxe&4*jh!fNwfWXr-qcjFJ&QmcD(w=S=Hu4JH$gj+8yh=6dzTPPFcxTk z3LzLQ`Sd{ZFHHDGCF}jfz_o;BmN4+t{Ws6QEi-?8&9g^a2PrpXuGj}Z^3tOlEYc?O z^Z|72^8}4Zp_(QoMw+6NeT+V)MfmiYV>>q3{sdgd&$Ty3rdQq(jvD~&Y8cD@6*$0XUGtaN-) zY~KBuk33xMRm2lZRZ_*tL?^w1rCfxDgT@A;%zu-5`{AP-8mOXMu9&ZG)#u?<|FxL| zG-nU?Py-c0{6ztKmT&Y6-V(@@hPA#O{ndRi4CY@qpW+dgJ9gmp**%trza&Wx+yI`r zD;@7DN{(kr(y%u~XsU_qpri>~H2o(p!a@-1JCM%*Ff`C6p z$Uo~Htav$alnk(ButNDcI-*$7N|6-ivkujtJ`K;9O|!JHT%5m`w<#Okp?gcPG5S^< zkVyZrWB>$Yp&-O)#=QXT=t1D?&Hsowyt!`zvIJRZ4F8?CXJg{?EdVAM{th32SouT_ z&*Cx_@M8R@$FgFgKH<9q2bcq>M^Bg8PnkMjNdzCQ$GjzUsjaMjW72nB`r-dsOWKju z#o;GMMeOPV8tTK8Xkr|&ws1lwhfj|yqaXerM|w4~j{WPoMPL6byBizM9!D7(GlxbB zZ>Lg&%gK&0UIZ4EX77^`zctZ6n&Xynthg=#PAmn0wT2I$ zmG`*=*2(rKG3YzfL&!X!nC+=`55{X8Bu1Rt`GXpYkM*BN z$5o!p$2%_k`g;rub|z_wI%@sZZ7Os}J_FcnKphkI7M|j2Ta$Ev z`7xW3r-B9qbD}$o_(mltC^*g_j<>ff+f{pucFKr3khJmx(>y5 z?_*yc+I;On%z(zk77t7lJf;pjP|I)S#+GvEooU9e;4Vk4F;Wv_$AbQ^J7634FUa~X z0E@kr@FDSQ%>?2cKP))m*>91*pPL6#)_v>S^pYScno&Wna}(|L;vHaD`u8HuD$Ta| zyf+I1T~7?BHB9^0rzV)vizfI@YGC7^(gR zV*Ag)$WovEvwm<5@3DD*FFrlG>}A+A?cd=0?}YNtQqw|oa3WPwOslWHa{Bz^j(!x) z;Y~0X`GpGT&cX)8pTGbVNsh1OptP&|Gn`xe+7ZJf*I9?rCewy3(gnW@SNVKbNEMVq z{>M!z;{)y!dm=hDI=bpGkN%lPKq0Alz7G>`el^0iGLiqMQntMcWnAsE-GMnd+Glwp zIx<#F8~whX>5ypxx7*lSDk*yT#BCh&L|xy>X?zJ*S#?&!9AE*!wA^ggfP~ zO@Q`h-T+R6KdFrAQdruS zmT6$n#w1#LPrf32d%W=tnn7gzf4Kp;7-b6!vWO;Ut3_EC#{T&k-K016oBX{iNhA7t z$^&n1?&i^sdk~`?EJDY-B{=j$lam=ri0tCo(O|*%$SVL&`+vp$J|3EB3P$Hr@e+mg zB5rxYQRS{^Fiqsgt9+`*Tb}J?uTfOhz!9=6`^PhthK~ITH{% z+Z`0Z^n(uJB6Y{i{6F^IJF2PeYZvw$D{@4^5|ySPRU{%vFE)CU-a$l~K>?|y&06BtA>is#_hyH%Y0mwid zW~DurJ_a{&05oTX@!p|kl6gI}`&{mW^qBaMkQS0XhauXB@`9YMWAJsRMcu*w!f!u! zUV*%^LtSYMbTL~pdbKl1D0g6uAah+iNa>Ch}ELl~8 zOUAbsQeYW(f&ZWj#(xliLs_<`^4qmhNIR{Ns>~y^=v_O5oeji7Ksk50uh=n%Mu`-$ z-P3X=5;Dh{E*`K{zXbp7z<~pnjcdX9uYcd_3~3cU%HUXEI@v{=BaF-k??!Ic%7EdS zvcr1rmgJLY;^Tsa{7DO9Smy5f(WcRMBRRd#iWdG^9t&f_Bz?oc9cQY^Qlc!eY~ZWU zdVe=nZ3z_M^QNIw@pwqzK+zB*>cBCFpj(Cq4;;7zXGD>9a>F%Qr47&hX5UNFk&7%< z0H$R`)mlxVSk3DU<;PQ~`8ec|EkZlWSb4coB(zFtt`9_6=^knbUe}}&olpI1JR?KD z3lk&wjygLsWewc^8?~=_>NJ=G81tAz@Xe+=g{DzhC1p1ReXZLBN<7)361WwKylvQC zd0A3EK5!_A-Lg>+{F{Q|!ApQaZvv*Ft+HO%Fh7L7l|b-r$QC$pYJVt6=Rcr}AC6&I zT-{fhb2E2pDSezFim1+!-MecthB~f|%)wQOJ#t?&w&E;^n0)f~JhCHRj=CF?*p3cq z$7~&QI1qgE_MZKL5iv>)yCdBw)1(e_Bx1(hkV*mt#Ik>2D?T5NG;+Fa`%o;9KM^CG}33~><1s%oRv})TZKX`j$@Ed=B~~$_EW51P?Xs+ zPnsK()&k}iyGwTJ75XEASx=!30RT8~;1r6aY6$M^7%)w0@Y!08qQ_7-$MV~z4_NLy zgPq%wJjxt?^DN3-gM=8QUmo7F$k^8QnXLkikhBa*1$k_KV`FS;>r)oRi&O%YN(!W~ zpUlC>BQx*pS@Czv6Tr*b_8fZ^WiB(Uw?lO^Pq(X002ybDtjjYd|#Y$9HR-HA5wwItv^}n zsih87($V{ZM+{V~WE-c^=%M*W=av$46M$<)0 z^7Td!ty~X*U2k@l?fXlZl0f=zW^-?8Dw1mO5aL7-MrR+hZxilaCa-H!9{aFi9MJyO z7RMdr219{y6?I8p-HigLb&v>~V0_^AcHq5VttO||SSywOTq|I z>;h<(P0j)sboMTATz5UX@9!_&iBv)+b(n-UAQn0#5^_sXJpPjtecHryDsHFz9i{iK zoK`?x3l)tP!27t&hX+QK9=T=3Vp2D+GJ5}DJ8^$?d{`A3$!~s4GlC$L&0zhKPyH{vl(FM z8+#nF2Sb2+g7Jc9QIuT$0EXUP^o6T1JNgf>KS`5;gLU&2MZWi#zgfDzltPQHox|Y) zlx4$}1qt{~K~oA+g}PLuDRbV43@Y(I{OgmOIsI|+4~_C_AL+^N&|=w41lWw3tsyvEtC>~;MqR7=A$VxvF5Y9)Mo(M72c!g zQvgdG0C=K_Xz91~IvzGzX{^57%JKG-5%TBYLF!Rsslz`{QxVa zWP_=cyL|AY0j{M3L^2xo6KqeROwOtb3j_B%Pd0|{;R?uu3?Chw1yH)c!f$Ep5?fSZ zjo31v^w?Hf@~8(((5=uX?&M4Um%2CR@}=0u3ms6c8r)oNm44Qf$l)5895z%Kat0YQ-= z;j+)Gr~NbxGXt00{mc#JprwU*Kvqg4+>pFVS!E8aa1OzXQLT%mV~BoKJ%=S`Z|dCp zh0N`NlEe>zKR-OMtohvVy}WkOva!JMoo6VJDR9FlnnpjNDO*c@a|S%BC2G`}YL>uX zV*q%-eViY|d^Sa8l6P%Y4nCE$AOhr)NR=UnTFrHD)t3@D-vmx2hN_&hZy{&dXR@1?!Jbh`+oKjG#hBK`@j-P>M8&4TkRaB$aQ<(vv8r5{aHLq>w%}maM5-LMeSzql7+lUFI-T$(>9Kek%3pL3V!X ze+b-ko&*~kV7|!)KhU11ff!(YJ~U|vFDRuv1(VHfR12hU z?WWRHba?|UUC`8y_KY`lCWBL%3?S;PEl*D z8(vE2b^RhU$2bHe)W9!K-h&r zW}X7$bstng?izG%fIxcPG#a>y+-k=v0+fpb{XHrwyv2VlRlK^sC zcx3SyYh^N0Zfxt8p@WF@`aL7lq2=wTdqeNh;Ww5CfVckg7$)l^^t@#{EzdqOx=%}g zz-@j;q{VDlLcsh2QB`aKQC1RC1q1JnWF`hIApH@$fpYntK(^p*+)m9b9{K$E*I+%4 zaR3PdL=Gsi{Y*t?Uia?c`tl~`CJ1$r`n9Mefl9`GOl;Fq)oL6I#%mn@x03GbPy>{7 zs`uKy?)=@d@!&e3p~g7ijc^K(n^90N2TCSQ+Wjm6{|O{cc#WbVbrod6uSv~v*CRy_ z%Hd`vPUaon8L$mF0eI^dD|dmqw+Mi81QO4I{kSHimCF8;X4FIQ_~eqf+)e7}q&|@b z=$#5!AkLt@ypE>i8K_j3O z4NF}0_nUvOdR~q=O;4jDAWvTc$mReo)qW=i8VagTqrm>toYt3%0xd}MfHWPHBkv-i zUq?XUxo-ze=G@XkWMkCQaX|~6GN(~LsYDyk(kU* zSNSllaQG#q3D5l3i3Oj?gU~%Uz?mtlMGSR@R(X60%@ovRoTjcRnrz8z#m=o!H`_94 z(*KugWu#0*SjXKGH$PFo_jG_FS-aIh`1BA+RVU;7E>fJSwTT0iwZGnHmW08-ujrf1 z{$>xL;$lQOG-;C73%YlWeKMA5u28L+rxxtiQn%I+>tZ)!2frEeyWn9zj`{EN$9$7y zcjaXZKPRStgX5pLZtUIn<9r137Q#HELh>9SzbJDN6spO!Vw$A^Ebi$6n%vkY^wYpP z@CUUfCfA=j0B_h0S7e7)imX>5zdzXz`S63-%+#faNCHigoEp?AL!*0pYSee?G<(=5 zDat}kV!$*{;O={L;MZI85i05JoKr7=R55PdTK$dN$Gd)q+YIZYKg?-kp2oj-R!)F6yc8_@*MPX*#uR%rmD zre?lrHu>Pu{pzr6AsGL$edb4cr*WT(+~(xdIs4nb0n6l*kMKYsu{GGO2ks!&)L8*K z{ulZ;`sBa)q3d~wiT9JGhYGvv76DlTxq+BXXX)*Za0lv$g;~8WvBaX$)OWL2G5N#4 zV)%zHa|rfy3c$ z!nVX)Bw&5nZ4w}%5)=(c-QRHYPZ|66zl%CsIchS_e-OZF`R*2&(pFYpO4u+YA*r}E z3VfIB905K4Z~S&e{gC?dzycY8s@3=Cbd}gS2Y$HoIEhFrt#ILI$-Ix{Se5!?*Zi-K z0s`UF8bkmb`fro|NJbYe&4ewnA7FU|ZQrVPzIKcp5x15oha_|Y-~Iux(lzpg7$uLp zoE8N%s{NLA*6v!8{hginnW)qg@YgyQ@P>w(%@x~5!G4u+%h1`@ zwB99azUdKx=(%^DI#3|6J~zm>pb4qperor42jl;XB(+C9KQ}-2W&xOP-+DhMEX>>K z|DZ%pgJElXCAQ$+7Pa7;xsQe) z^bC}RQvVh7Bx%VFxbj;Gh-u`6C;n%}ckfp-eGed@LF^*r&^gVq8Pw+3G z0e-D}`{ci4JX#mz$WQqMY&N@JsFV$b;P)CMG*kSeV!ltYxAOlLXy2EjV3;^lJ88Sm z*84)^Z%gol0|!(ODBruSyJzUhu6JHjcYqjO!DIHJ`z1FVNCX;tV(#EiZG^RB|FjW) z<(55G`^RM8eL|~`X}|w)8Z9FAnl~C7f%>zXMSQ=>6(++2=P_qPZ!{5?o|^qDoP3rz zz-Ba*g*6I(BY?dtzKf>)A@(2eKY{N1Qkv>my79961@Dg_IS`EZqSa1MmT0b|A?8~W z`63~na-TO_b{@^8`m%Y*CYj1PpCoJs|(LXf#!-(HQ?uP|wEZ4s7 z?+kX|b2Ko8=M`>qHd~t5_^Bsw>$_orIVN`iS#@-8vLEUdcvjl=zpUUt>*DtRn#cdz zX9(I*`;hsuO2E%u^Z<4*{A-e*Z1BT~-xuJ8?af4Y}SD%x}yV+n`U3M1D>c3p@5sSpM z^0fE)f}3YFrPrx4lyr3M=B11$(&$_&K<klZ~U9_Wm1#B?$=fDAkOCRC|)_UjyCB$YGKGH0007XtIyH>>xJGG!rr$1Gb8zJX=yP>DTYrcO9aU?p_~6oR5nQ$9v}Z1}PWn zXyd_g`_v(=u_v?9WcP;OzKs64-#YqRnQw0|;t;K_{L|s!zHm>AW1_(mxC1Da{_nf? z5TF5%(RRjNoNnzGbYF7fXo7?;h-Q_)tA5+75dUoK@b+)<78n>BI{h(0KI{H=3$$Mr z*n_2#ss}|F58Xah4<_`l9|6o3H0<9n!@Bk)QvVM?8vp-4|DP=I^?rZpMYBRJ)5vq> z2Vc#LD4P`WTzx&DOc9Aad*DFn-Igo{2{A~~l-FprCG+0MelIcxlZ+#^Pi+(mHCzl) zhmVElan7%uGum2|?Pz(OHYo~F}@axv>;|^?0WmKDN&1W9T9 z?3L>1Tw~Qf5FTW|1GfrCAu~y@9w)_CN7q2BSIVTL@F#@0-3fA4v!yE^lxC#@Iep|2 zau;_HcM(#Jtq8kY6hX-K69MQHf{{g~S&L9&MMhU`D19JgG(+3gAyO_-rsiV%``UF= z7xVhF3xP8>WHE_rhiI#JSq)CR1cra&t96l`on#smo2w5`HKb}^I$_Mxbg_E%4%$LI zDjBJ%h?)Z*%J)(>wgQcFLL5RI{{YA66O`)d<+IV=fg6s6IRO;|sZ*g16LV)#R^^U2 zv&X&f${`bjJsT^#!#;Z}KB6WtMKyoGLA$w0uRD)nW(1K+#juv{j#!l5vY!QM@jyfY{=Ads)xcLfs?`}5;v}`)p@yEE$-$k_rrP~>HtRdfb5`1~W;m*GCfsvB6C72dTz z*fV{$%GqEddM$O<4_q-;{bd*Cu1H(XpMNsDHKmJMm=~(DotRTR9{Jfj;Nr7qUxLlS z;(4=F_4mzhsib$if!DX8OqA){irn~CXT{Tk{2p@89wnbH?^d}@C_KLtNtI?<9~I0T zT6ni;7mu}{9FR}gT9SF(cRkJJvQ7V&jrxmaBUgN+9^-2HV)A8T*h4$L>fR1(gpafs zLQ6qG2g^HLM-p@B$q*f6-D$z)BWfdC^=$*9gbg+jdnd}e&5kz)te!VH&!=etj!Kdi zXR|jMGeP zy=Sc|sa%?EU@0dcfY(;8gyjbGmND9PY#}<%SJ$?0xxKEpImFX2^zDVlal27Cgr{5g z0*~3Qa=4FjD4XVoy6Qy~`pK}8McfrQvzqzGWnb9lRjv^Y%}+b`hCw&mk!7N~#}NJ1 z4>7vNeoNQ!4||%OJ6hTVmxj?y_RNy%;WAOV>8T>g?!k|VzGqQ~-6vg|=KOB(*rzH( zvt(+RH6+ZdY+3GBb&k{}c`;aT;L~Oo`1+5INt$%@$}+3@mCUEve6~jGu9G~_ae*tl z_xb}xtS$VmL~@j6PZcYnsE2+vW*W?^Jp=sH2-Q z1#NxrQ>8!R60%>SqJm9I4%}|$<{ak1I`X*56c=1P6z#ftZ99w~9E z?-xL3{=upAf`)?avJ;7Os^yW-z63dH5!AP`t{|mYj-_&i*~jLe7WAvMN5TV@`<&ae zg+KT6)m~!Q*u2*jUVqkT?SxJS{rkgivC0c@ff`;UT}ZSBwrr7Kr{C0b$=y)1Rd$gj z*6Mvo0GzT+yqQVlob%yU^zo`~oB3er!=@g6&j^IwvSt-i+c6WdMhn;{og^qOe9$$L zbMAA<_g+l&0IQPgGJ6s{>Q%YTI%8_YuB;_KtrI?PFJ}yfBoUr)E?S9l!t0qv+jCkg z5}rr&5Z*l`zVJ~VGR-M6?d;VTO$9Jh3xgfOn`~5b_AOv^c>~aEtw>ewl7tCk`wOWb ze1|J{>|?tqmtZ`PEF9$m2twWfoc!BR4g8h zg~^B+MOE%RoE1kowXNi}a+(D4SSP36wqRzn>RZ<5)vQ7k6pu@XM|lzZ+I#zFOi~ z{_OiHo$@|UYhPN>C0tDkH-y#>wmy&0x=?jK`qqM*X{o?bkdqZ7qic6jCa8KsFQ>%q zZl6ATE44CF|7Mb>QMAwJqQv!uPWiY_oyIR3%-E4>e)p2mIo*IXq0EBMa%ZBsbCo74 z#m?15oKf~$dgxf`#S8aLwSp9F5AraK>ay4+uKmuK-xGJYW@|J{aV)u_$-p^d`SX{t7=7!WBBYQI^HTVzsXUGw zFP~Kkxq-9~Tj}pPLafR`EXIYy1d#$x6@qqGd7kc?F)XjVwAVSTqv$yrU&#nhw7zil zrXH_`LZ1+egqXc3v>p3pJm)-1%km@gR%clYAu{wSf)Hbr$YxcZcbu=zzo||p2AT5g za4gT$yfxmWIb9dA!}!;neQf2EkujoNpW%g{XO9@vE=@2k^%r@_W^6QgcQxlP6mY}n z%QFMyB%f}i#(y!rQQ%Jl&jB`-Bib4-u`?ZnG8-8>i$4TY;xnGVQer|)9m~7Zv~by1 z>wMHLt*lqs`CW$}esCN6ED|sE^sfVTOr~~AhvhAz=o5tBFG${4g)@S)A!L(z{A&-v zcP@2HsJ1UhMqriUt2WUGCg?y`LYL2K7`X6<2HPdhJ%n+Ml@CdaC*Eh%KW`QvK6yWm z-P?o{4jMNOgL@pB(^VT@`1GLO^TJ$h!*6BTOcm@Ca<%LDw2-j0&a+-9ydM~v9@eSXhekw zl@*Kq5lei1|J1V3O-*0Xt8lBieqQK8@~fhcI4x)C%9J`*6AjpweG|^mB`T{Ww_2WlJf)&*Rg7>yw;oS@`;*;-u2r`IE|ey*hP@QUx8hqNO=s@kM&}IZnKQ^huq+CQrwyugs$>D|bYinB#qpZM^Gl&J^jpQ6VNC z&Okef324{VhYxsh=_OPUQ!1cga%P`T^)m`}AOPJaC za&wi2&})!3mw}H1sgzM$DC}bW1BnEe-#lcerpYf&2huz-imC;#z6Rl5j}7lWbBZap z90_$1G45mO8&rB8G!Q8}()Hx!{0BZCcgf*^n4V>iR1nIT8l&*!?PA58gs!}gsA5}1 zh&F&SBj#Dru9plL2Zp2 zuVa~j!1U7ZY-fWeKl$y+J(Q>L;sSnNA1+spS5n+&j}q_`%8JYzyqw5Mmg)1h%S=`L zb7^tTM#n~fyzsf)Ltp)_DDB{4r3C-&(2la< z>UiPkN;WS{zDBZ&U7(TJC+4SQsmngzyxLn3$bOJ-p3l5VzSpM;>Ru4fJ7XqmX|}*G z<^pL)u)1NKlQ6m($~%SR>KlC zlWI~IMvhJ(S0iw=v)l)s@b*TF*q#>jFj4DBrRS0<(=WRiUUp_D&Z8{F;L#Pj?61={ zg7T+=K{5*w!xw~+^-KJmXKc*~i))qPQPBOwO)EUETDT&EVzm_VE+wp96Dk>%XQP;*febK8f z^*T6a8TkShc??tZd92~hE2$~|nEoN7_$TJu%auZ`62vh9%k=!rNk=*KEPs=3zF6%N zzfRB@;pI%og8z$MZ4LQ0kwOM8*8#oYOyF~qrU-lHw8Dsa{B#xgr7;`y_s{G#V@$;S z9E9DZ9T8eI0zWvt&DW~_tbp%rDF1?&mx>{V{`JE} zkHekwzgsj%C2~j}{%Bvzt0Pp}3t|r0d9_j8^+M@!m#fxWP>ffwQ_5^#?}Ppr#HTp_ z$%s-^bhyD4awMI-hM%E?)rOWpESa?-&so1%2Nd z4(Gz#7KeO>S3=t`r^_jR5~Q)nsM2Ehc&*o|*ac77oteJX54G-9nVBx@j3|xKM;VML zdocljR>_CUJ@=wSpWG#m=LK8YoeS@iOOLJ$5zFMhLiKzE)#>R<%!zo54R5kqJgcmk zQSi9>VQ`hom+)d-P|r+sH4~=Z)A)qCdtc{vL&UqbO0j^mTQ9%4W$>C=eCGUGxCXoOJbaGJo%t!Dlb*wuW`0bI!m@p zxAmp$w=&*)g)k63u{d`=P%aUdBEiX(z!WSWn%2Os5iOK@_JR-ixZca=`BKCJO2g`v z{c(%uc^oDSj-4@~_8?YK_Ne;pv|7X;n{%rCs)DueX7TOg8Y77nbRbp9i|&QNRZ&5X zYVVTzL6J9A*gh##s~;Uha?CMCK`we!rGKdFZ(bXO_M5N51`}k(A*y*UJfbMCs zdK18&dUU&T)+wtk;2MVI%j->YcFdZNg@tOi2@0tNV1o2? za4X4=Sz3$^cGXvytKo9-0$1V<;^^aG&*2ulBC#p#bKRz?-N()R4S;PRF*Dsl?(i33 zSYZi*k8qQ5K1w3(zITgRT}L%E5c;XMdn(v%utA1x2TR8{G|#d?`%DAS7~rBj)L@6E z#hhN@Hb{mIXX;|&m<&4BJi3~7=7UOm^%R<`j)Ib6-Tu;AsMa*BX-BA~hEE0+DlJ8S z+2Xfcd0yqaF*3-qlkC;Yn5yY1@J@U%9Fi&SaMkf5h(Yw?R0u^Wha#%D>^zB+W{I}+ z9e({seQs^Fme`BtkWwkdRKL0H>cp~1Q478 zmSz!^npi3*H*-Wyu7tdby>*UNi0leuR5cz-?L??4;`B({2&LNiAj!(}16hPu49z`> znDk@Ban<6w#CIoDQerZcddtn-Fqs~bEXn}!@k&2?JUQv^;Jmy1c{M!R)4K7G;c8@f z9$0&`^;Pqiv=7zs*I`xW5sITDMmmbj9XlEsqMn*<vlcdkopx+ zCqt5GPnzrQA0eKx5&@%L{sVJK!LNc~Pj zg>>aWt9(hiMb9T_tvKHyWUZs1fOFfL!8kG6tl!3r3?T(q#bU3iz6n;ks8tNQA$EGU z^FdUDTK!@vQ&yL$I6A>eK*fBi{Ygsa;n^5Q@rrqG`-iW3_1I<_FOQid(6=eq5Dv!x z(xgaRaT9noaG~^zK;d_P7mMxqbendVg*gTe>#;E-?1S$ml&*^5og}C3AiwbIKm?R1 z0`1jAvHKZ>Pu+07ZsuZ+*s-^tb!^)L-p6e<6lAOO1+^Uy5w>B+j@PB%X6}^;^7J#9 zFgAK;WI4ezW*CFE%go|D2c93+XRA|_LSD<7>ti`q(ff+6Wj+kKCdv&582hLxafT@L zUSH+S2l{AP=WL+n?aqBg_Q;=gy-D7EGqR>^Q0tnX$$k>&)4I9QUVLMys&j~2=Tn7t zbvT54noZer(OjY4=@@Z=&mdWjSl+%B7u8u`8E|LJO+00cAiouaSoBx0I3x9pP1MJ= z-_g6Rh}9BpMcDCDAjS|mOypuR1bfMbF`gaVE1K^&8&d*gq8Jt&HLd8JbT+d6o-S~g z&8XB!M2#Y*b)ygj|1PT+dA1|7>xMb$QHOV`zf6Pg1MiA>)b@zW(BP@P`$k@gp__v< zOLI?ZKwV_z%)+fZ8G<$}auw$_B*fTGj=IB5$JETx!qCk$6Tj2ESX1B=X9o^^$;!Vi zcuJ8=<8!mi<+}{&FXuu}3nuOQ!rHd5Zb56BZBiWVg!=87maLEA=>wRwgA3OP4W)gICyrs1*d{^<-0ace3?i@TZs8(+hjK3xe;r@ zLry6%xXQb--7x|IGIyh;Q4RF+O_mjo;UgL=L3$X2-hp&fod4h^i&#@O&+UL=qSPWv z)Ahc(&$%E%_FTb6RE=J8zGb5tKMA44c2#k%{?2{avmmdBm?5QmL0oh0#*?4=MjqqF zR-bBqh-MjcW9|@Uy;+olznxz0UmtC&BSFl}ahp(^tbP&Eb`@UFNxILI$@=y+5*zkB z({R){H$75edBBF`GqL&{jz+B;`ligD7v}dYc=@uVI-M;M-P|SuzpH49*m1#htSNL5 z#%4F>)2w|&>`q?}eFQCo9w)udNP}wi35j%sX9Uk^4zJm;UabNj?`Cc|W~!u=dVH2! zXNw`9<)l=HTgz!8?#ia&ct)@6TNyEB$>L#uSw?#)WvG+)*_Ja-I@OR*qxQBhos}2V zu@VVamDuGTM^*WRxQ13@tcr27s@fKD@%e*kQXvasik*@%eaX7To_z`Z#F$F=y_yDa z?UQ5cOdnL2TypZetb7BGZgs+>UQ+@9Q-`<^H?|)G=8uRh= z>>89UKk{mRk$$xS&Zc7Yt-7LJbMkBer0=zAM#b3!)ogP4C7C+4$?(0K|2O{mQ|5V> z!lx!lTUe8$Gh23SnAF3F51SyEuz=sdgTZe^BTuXthp>%_A;67 zQu0QS2PX%OI>Q_Pw%OP!=Nn_DYyac(R`bQYZ1Q{{`)kH~y4+u+PN5zQ| z0ZK?nOHlXx6^n1P5l$S#)GWp|BpF8DT`h`EtNPuNe2zI@`ykHb#M$J;r`@5}I}J^B zs<`~XlY?WS@`hssBQ^U>vi38xn=3r}rda34EMAy7zNWhBfx1=4!-XikNQxNwHYHCv z)OQ4#Bf@9K4byBDUFK*XYz;_DXjo~#EM+_=CKKHavyDS0IK&F1CPW3zhIOaL;_A~+ zp`dxic&>pg)C1(ncARZaLyFsqPEBFNK=l$t%=1$M)ansy=W3DGbpG7dhiIpkU{CGj z>RDc$F*c9XPQ5sJ#UA!y@-^>F_r-(TM|MBCVobZ8v-r?t-|~J^7Z3!UtIWBKj##Y6%71BW#rr2h!U0v4WCv zDDg(BS>)jzXpMhylW|S1Pyc(qlY9O{vlSK5k|Gaqfo-!j*YY2Jl1)$CeRlyTb^hzxqAO-5W#nB*2(sMqKZVD$92Wzb2*6NO~F>m)*NrBQ2(>d8d+Z^oWa%* z%0xYXEp^$wvOA~l{vXvol6~NgqvW7fYT8n_N?y^HBH?cB`AQu#h^SpYS8PV}t4_OG zhE!;SbF~#0iPE<9U#?hsVqAfvu{ZxS?`0GV$7}56T zU{7QLOef}9Oq|KZr{&{QpS`zC=b(qnw6N70nkl%HZt=V)DQW=|2~I^xqNtc&Syi_P zJ&}^ilgz@_1>|Mzo*|aGfus8_^}1T*w!X}D_i1gCewbYdUcFYlCYwL?pzHL#XKy_8 zUA1@uNlpc&j?r`a_i~;zIBFTm+6F&^ESTRjP4hUrK6=HzY>cnXjS@5&#Ug#7_A4w4 zS#!hbvtAAyQvY7>ilz6 zaAZE%o*FRbE1bt;*H3t+6RUbIyX=OzL*Jm|vd85Zr02Y!RLbV_m9^RkLUJ{GGe7CD z(+B;4$E-%8bEn2&oO!VFw5S16%t-OP4$R(Nd8rhCZ!A2Dz@XS?F65JO4_KOBg46TO z@D{bng6Boyv+cvN8~90u*d&>dCN@x*`Ts94@bNDJM39h^Oud=Adqfdh9!tSdFikVN>w7ZdI{72@P1xV z`O2H!2?6R_6x@5V=xikafz%uXP%~IpExb137U-AP5jCbh3!0NOBc3?l9alN%GI}_j zBcCq@NftB`jjDIlO4A3u&?C_M3Qu{In%4UpD2>2gSN0P>J3G}rPOSxvtc^j6o_BD< z$2dsiyjU@Z0atU&BGTZgfmF7B#mc%i571>xbRpE@?wwjzWu-#44-fHQ&F{#ass3y3 zRYcbjvP;rJfJr_FHjhiY_;M#5zN;1we4e~ob1XE}y7r8{-;~h)-u{6H211uXl|uTa zrt{S=fUQq`P*Au965&&8ck+Y9BQ%5HswU5|7Z1PIp5<0X>NT7nY^et;sjt~w`!hn(iTz)3JG;mRB)&8WD)h@GpL#aoKh*LdWL$l_pddV7hlnT(t~9^tbKzNn$z{a}DI3Dz5J^=cN*Uh=V~xg| zd5$y@T~uT)F!UwVKtC;qCCoWSmG3oN4jdTP`zYv!Z7x20T&9=<>A|24Zg4&VLh=jE zGu_Eovr~Tet{d>M@W(&TW;dU?UCL-V5wl*e%*kZzb}Zs+jgZk9NQF72#Qk}Rl7uM( z;pSR9ebIOW-yDddu<$%xH4N_GX(D(K;!W>R^b}+!KfBz})N+HMTx0Znw)4@Q+A*T= zBEMLeN0;dY?22Vkzd@yZb#jebqqsE4(eHh(W^HM&sMlo9HG5>Wis8Z%Xjf@W$RIi2 zFrP^xKIVv1{(_p6-c%s4bB*6&fqDS&)1+G*-x4z;*v;hlo+lu^Eb@Xj)eqxW?)50l zs}sAOU#-#izg^Q-D*Vu?mtz#-W}%Wns`eYzn=E=YCz+9rWPkG-U6=>uro%K?)9sUV zsYB9B`swqk3QQuc=zusUqzUP{o48(!c9XnY%=6*rrKpy-{R)PKX?%4>z;?SJG`I?J z@9MQk#zB>gDDzvQDOf?3T4MCetI;<$aWghu9;Y}VtFmkLIT4^MYkdSM<#Th4D9OZ4y%8QM=$+BHOU8NqkRo2>Lixh0sHuLb zX?|SYHR};BP$nVwb^r+2sTFAPHH#`Au}jXw(>rO);pZo!&~AyGp<`+9f~ z3gVoW!r83ETp{naSx`n;VSUXQVqB%{-Z>he#4vc}-rrT%X4S7g-(+-HCDR0~+fXS! z!w@hushNz2WLRT}rQ@RzNiiDJB3znaNGA_gw9^*v{ z#=p5MfxGD+VP=tJv%o0(csWiTZ5UPVM(1f?7*f@Sip^77&glZzrb^S(5%=D}4tTk0uw~5+L z7?%}_S+%(3nT$akGuBF17ga5tM#s$Lmu>Mn`5lO=cWoY0mdK&_3aDgj%~`&upW z3QkH|$eVvF!v@kI(Z5Tcd=o`U@piC~&I8Bwg+HJ&gCBJ%2u{M~bbh73SN+5+|1G6^}L~5)?@ zWa7MFW=&ZfyX|w<44Z#Qg0a%m>8_W`YhMG#UdiJKMVyxr&eZuli?>5F#5k31%qDh8 z6Dsm;GaRF> zk{L5_G~N_P64wPU6%2$VnJ*%nR)}_amQ?wZAr`9~E53|j~1^qI{E72JL>gVHzx3WKHw@4V;^=21&U!F9sl*~Pd_UcDjk&_`aZ*od{ z<5c0W#XoQo1)U#9ZNeSzlAa|5`>up_wywBs`aHc&?Pn^`#OGP`;tEuahDuv&P!BS# zplB^nrdL5yY5H&dEv#?LOM0qNDcP%wwjZLad$i=`1_j!^ly?^2kUz!EY=p%Te3Wos z?(bRrtPYC)o56t!-@f4ip7Srw=(F|5~{C=VeFsI(B;(k?7=bXqYxC@m=*x zuKMHY)Um{9_Fk;wZHx#j&u6wD0}X3#_tu*S5Pe{S`V&`z|x5%uqLYu+_*Da}cS z)tS1D>sp0%l@_E|#?IbuH|IsS$7Ch+ms}lqUTI!rvp?)AQb z2fsjh=&3Jw-)TF1b$!s#D3YsXv1hoM0{NIY_b4Y~E2R{YP-i;?qM+6eaI12u0Q|_LMS|HQ-oEim_UPz~w%aBfUIGI8j#cH_I{c)q2p0 zU1^Vq*7Lg~`AJ~|D{skaV$kc<_y}&3o_c{VtaQ?B;I$xjLDo);-hoim_ zt?}mJdrIj7GlAY-H=_=Z*~dCn-U}!ktI2Ljd1TSuO4;PfsedmZ#*cY!wnD;h+0ME) z3JmcJ@ncv$;%o0(?VO%w_mtx!xk@WlK&;;DDRk5v`MoKh9K0L&kqefx<#U6Z^kkf? z8##KdeaB5=IwqtF6}wSRCPeH$j>i%`frC9SRX#Bk2rGMPw)@`4iBvn8DJr9cUup%E zH4KX05ZG}M)D{)YTD2puC@WEY_}048oN5)@a(d%{sK7}*TJE>Ex1CxX2#UjAh^?NF zsPoY#++7l}wv!MT^N1E5JX_gp6@#>8B2<`+TE-5v>fFA7Ka6chaiyXH`tw=6cQOU| zlzcUm*&7`wHO7$xmO8M%}nmsnYIkK$6q@nbf8cy!ariQe5 za@p7#tkSgzOxPZYbK9+iVue8L@{^9l%Q6a;HgQ&=BhTjZsR3;)Qb%)2p1TgrrD}YW zv9I{cgYk?;^JqSY&Lb|YpNDf8|D@)~-= zG?KYI!-rFMbe7Z~4V43va=|Q{r0PX}cXv!(3y@|9_{YAErI<(hl{ih|tdgE@8+1#^mZ4qbz!762K>*;1? zt8Mj3V-ft-6IkzXI`T`WO!q!Hw3yQ+O>!0V?;!iJ>hbg?Z;+ZXB(;GrsZI){Oogi= zruxp>Ct3-Tm0@4`=$tBa<_&Z3ZdbOkEGLuM4@2k6m&6~B0-8gfU|d~ z+Plp1$2tH_ckd{N+g-pp6f4`COmbuR41eC-iei zns2KsQ7ZF}U9csLt>(|yq?_PPA4yy@s84@w0#4g?+X`w}d2uZAh!L8c=5sEU=}~J@ zyerRB>=`vS(Udu3v$;=q-*rW*(P@J*^;4)Qw$-@uYE3Rft$|0lSJ&Q*QM`ezxE65`o18!(yAWoe`U&tp zvnsh2IwE@`Uk}?r)%_sYy=7wMKvD6lk}&-3%Y(-q;ww_*CW<-mR`?;v%4T*D!;%!B)yls&Qhh)&A_7nMHGX*pH_fBQm75=BZ>`~PY0 zOyk+iw>UnxQ{&QNT2xiFw<4WVVyeax8l$SAwpO*agi;~Kk`|%1bf&asD6!RAOC=~G zG(u`iMp{HtR6>eaO6{Sh2{HEj%uMHV-`rRC^?mnz&hvkD{-5WZ|M{Ko|2$@+v-gM> zXh1Wzzx-eLm4Z!-Ab_!uc%;m$8o&CDqSnk&fb=v5vtC#W?r_Vq<6%esircqr&!AqX ze4jthVgYS@d1;lY=yvtgBm4uB-(HBpVcxYAv9qsfe7h9Y^Qy;-8UpmjLAj4qOO_GV1?7*96sWIdRJ@?bKg=M9fdFj4b)okKDXCP` zZMk3VtH~@i`Y2X2=2$cV{M2&_{nk_7hW==6;cA-7E14)>fTV4>qu2?ZZHVw6$?aL^ zWhm5#)GX1x7fbjR?bhztaEwbD1DS5-7A}M*o>1VH2cJ8>F)>N0s9V5Oy<2*HuD)Y# z{3ML;Ye05g9-IjC&?C)(gt_wUvr2_LnT;x=yHv6SoaJq?7<2zEr25I=i$?wwkX*eB zcm-eUiq?k{W}w801?zb)X7FN90Vh3!~ItVBe_(#YH7ZC<-nht;2hF>oJo!M>W-F8B5l_E&gM(32S-p!+MG>wKlF}*l2^p&>CwUh0Og4 zJ~K(#!e0^$T=Qy5&k_4;l(p8#`WIeb=w`rZILHU883)j*hz8Na^Bo&^E5Ec25hgTD z33GF-cQWpKPLW=|tSiEH>x;^lyhu;6RSyidB|c6b80OQYESGB|+quYYj{5b39SPR};0JU*?ao2FD_&0KSvUXi z{ci6#(d=nx?B}E9kbzB@Yq9OiJA(&83bR2+ZUYWK7y79HOoA)o0`-Raq=l)mWFsse8Mem4=9F8C@_ zy}c}tnzl+LS$dVc%fA}3yKV%~A{3)Cc{$dK8Uh8)IknO2fFy)vc3qx30jQ6aj&5&Q znI!6XWH+HzxiD}q*W>JNM)`g~v(Wyu1>zKjZ9K$!5qE;0)Okj98`Z3W<9ct*R)sj8 zi9cQ6CKX_b1YirHL<%|QJpVuV+H9hH=7dEKHi6s$=?M9iv_CP^cdEj8(Hrj? zee>h0xPjopI)|!2xe(y?mj3b7fdwm)@P~L(CS**_x0B{87z8QEs{Scz5%yvY)V?C7WXv$ z&SKb>7Xg9cO35;(Xpe<`g}6@0mu6R(yShf0`Dt znMc&uwcZ`Bv>2ILapOIbC2Mrn7yXcH5Oov7{xzpiy=kf9z;Bc~6E2w)tM=`3>n+i> zxicXduGwowQo;T79|sA9-*-e2%|*QO;0Ea^gR@(MiqryS+RaVynwGnCZEhoPL7Vpt z8VOzG_X)i81g>|5m}cYa#W`i32A3rLy;jISzPQX?bjP&UDOQEqPKs`(gNv#zU76Xe zmr?6(v=noIdQb&1)0t||nM_z7?4F1!|LT`A3*o(%ZpFeRe;{uQX0y6^mjhlkVp=us zN4{j+kKfr__2bA~e@Bfy@@7ROD|}|%WY|sjU9+@dMqhzdCkI9LcX&;G-nBZEuZ$4T znbPAf8$S<6=ud3z3A%=i&XNicR_v-E$=*6h|2BSJI6se4Dc%HR%2}tPNacX1@KmWaUmJ;OpY32s0X4j7o7vnupvLG4a8Vt69wi83kMv>-4cFp(Jc>KNs>ge> z7Y4qj7a`;qy-9Z1#lxoGSu>{FKDO-cm2!&oZdTm5+FgwuTOp{+-5(kN$K{QdR{sqA zlpgk2dd=I7*(knEfyK28F0x@l*l>RdbZXaA(>|E2ZzS0%=`oy{zy6dM2%fL*mBx0L zW3{XU`SV++#4TDpmj!F*w*IT(Ks0Kr9{uqxY@S~E&JEq-aW2Ym3E)Wuu}FDQ;I~eX z*6qAXv763pNe~WYFL&7X)ghNA?~-LezQ%x-_P6T_M=!7vBCkugrf=*ow6@z3XTH> zd}4C?wvf$YK;6iw%nhDK=`H8o;9qn;h&vA`>6t=%V4atO;7qz8bU9L3VNKHYu)FAK|L13 zr=JACiSh=l-FbTC^c%e=7%T-(pfDaLH2Vrd;sXGb*|3ar!j`q?x8Wc@6;Lbyw_(#I z{n_a&Ag*5vh@&}!$sIpr$#NM8 zjkAB7|D^!TIve_78y##u;y(BKqsC{c3Ydfb`OVYyh>y6xmjg(T`l~*Z-qykYSpNU? w-#qZ!9!)y&&wl5FU4|s#(0^O=zuRE{*w@?kF8iObKlj1L%Hb0E;;je&24%pw{{R30 literal 0 HcmV?d00001 diff --git a/cmd/clef/docs/qubes/clef_qubes_http.png b/cmd/clef/docs/qubes/clef_qubes_http.png new file mode 100644 index 0000000000000000000000000000000000000000..e95ad8da4af6fdd4f628ec4ea741c0b98147f00b GIT binary patch literal 12237 zcmch7XIPU<*Dh8>RNNwP!Jm^Bca`swk(ESJ?i-&1zpt4pJX0nk^JXO@d-w!t^T8Fz=OIQCiyuD6r_H?&2^8lzM!`XUcG}X2<~-%8^VBiU++DfZ zTn`mFs*%d?KWdGQbicF1IEm&Yj%E3^`egf{Vac)!gjt{My{;CPkKfM8POjm_eJ;MT zYBM&(`@Ar`apMO4OgrT4n>TNM!v6E?4D#)pH)oLK+)3Solau!T=mdALrbcM8#F{pgI=kOXhZYf>x>t}q{| zl)fCj@Uzg|SZWrd59+#*TJO>#%sdS(zSIC+QGM`rXixbBgLA1-LCppqs^k?mE+ljhm5v6c(j0!C! zqnhjarOwlSuVnKgqz;XEHsq91@O63n?b7Fhu4-jvWqq@p^pwtd@6BIHcv{|MegO#~ z^oX)s&UHu6wI^fSjR!*F`6-d_mA-d%on0nq{}lI-NS@D9Wy@nJ;@11&X3v0(_%53W*5tIRrON-#J5nsd{3yd(1sp^*s;mh5Pi9 zw$&B6yFA_R61=>;{AE}uub^97wW9i?Z7R99e9KwdRPyVoo!k3UaP7(-N%{%PVBLpOpe$hm%HrwA(Rnf#GaKO6d z9Q|v9ou>Kki?4%KH5nm<^R6Wfs}TY43QX{BbF&-?7D-6LtFc9bJ+F#{ZJKK#*9Nn1 zX=rG8NipxIHS{GdL{qc2IkAXiyWjDo+GL}CH4);C#EU1Z#Z3nNQ_Y*%^*@Ru!*}5D~G337CfT zVVWPPeXSN;MPITD&l$h_;P zrpJ1K>^@XdUL+YBY)rgSqnXe2=CH058rPK#)w=T+HdSu|%jGJ;RP!NKDYbqBqg;IN zFDw+k@zv=Xvh8#nz60HP?l%6;o6XsZt7r4_^N)q>4+>#}3)ngi%ZQ6uu8NI4!Z4Zh zr1Snt!G`vZG4UW~<7ywZmy84_}N+A4`-v6&@xm zdaW4e?+@kVeJ|N#m4>bQmGkTS$&~2U(~E>f&e%K_RNLv-E|G1SnkrxY>P~nLldU{o zvA%opSIFt2deQ{7jCjSuO6Rwx)0U{U@u^6(>4`=!13QgPbEdg3$KdS-(FHTfx|Tmj z%i!)-Jr}+kpV;O6(yo@XIYiJVxvV-@t9LkV$4Y?meHEUpQ>VN>-#^ha1tSrD%vIgD zo4S92VbQ~ygPWW26O1y%tR?2Nb681j-pnMEkiPK$R*Q32pi6=>@#Kc~fKK7u0+j{FN zw9x-A-FOn)vaFiAx*$7*L<}4OKzbL{8`oMAetVWZ10iO*)oAcgHkfvS#ALl^7z^R~ z1*@G{y`^!o=*u1DXvoJ}RBj~km(eQF2YusLy6-rv`Ov1sCNCc!AFFBGC!nZZO-K@% zeC*be65Axn(Qu0%6prrSZee0#I<~OC*fUf;3p?fLHU^EK^gQ+9GvA8j4yy;Ol+2zm zI`ME)_*6W8)L=|`FZDPL?dNg;>eW^v*Pa@UR+(v)I9^fLPCJBVC;T9pi=kmnhlxKe zD00UbrJ(fUC=~jknlf48IBon=&JWY@Rsj5xB)nEUwxBP=ds#*M zIU9@EX&`3=1rs?N_ZJ2R(F@3{uqN>a@gNt7g`DLXQz|;|W1x5`Mf`q!9 zM$0)Oe=>ra)Ua*;=!}_FIG=45ueb=c{xnPgk~59+TJ+mCVnj^%7+tAq1Wi>OFQCsM zuLijUx`6hFJ)cB_BG3yA^n;`M^UC7)7)|0}3PCP64yzuH2I_)}0uZ*NU+)fQ11f;2 zu>I5BfAs(VqLV3=6seKHK~|!G?BU1g;%gRStKC?InYlTyUIkc^BjHcI!{gB?)no~c z`~l9`M~Y^5FK&3lA7#~SZX?z^xITXTXkI#ED?0UyQcy^Ufu6oOQiF2o&vA2pG5HLONxDwO06xo1-nU?U|w{Pcb z7Oum?iL|nee?%t)xfE*dWx~WnMGt1^>aXlYeySY?t1=C15bsI*={u08fs(?%+i}v= z98A9-T7=%8YmcUq`t{OtMSp+F&>7*0n*r~r0kdR>7_P!g1qAND!)2n!#KVUyvX0_CxTV7rcpBuLo?VMkaii%1Q zvRK;m#vOA7O{m#a`mM{s!=+z;t6H5ZO0rU$The{`b75g@k(b!ct%-a7i$AX4z4)F< za$!%~1LNQ@xVKhcZ#O22c~swGf+J4%&d&W2tUo&FnP4&9W)gMV8N`L%8g>xeLD*pK||Vz#7d2yq}-I%7BT{=fP0= zU)C*T_i{9nj7~@$*`VD0SbXgoMRfa%vu>RKRwO5KLKd~^uS)t~`n#}^Dwu&OJ8|H8o^0Z%ahv3ZLlQ>Hl5r2hhr(?=*^x#;Xa6u;N@`aMgR(hu`#0hrJsK-~8k z_a{UDh6W*s#W^JC;I}sZbqE!QaPkK${(1EqPmaX-4MvA^IvjWQ*byAz(`&zw-wayY znTY>^17fzF{xg&_^$m5wgyo?I4_b9$N84{l_$G5}+p}Ttr{rqQUdorII2%TW-y28O zPO*pDU)QSls(ViP@YJ6!+`x(5$oy_DkD!2-Qk2$_;f)cPsDnUq(!YCosyF&=N3u*a zxU6Zve~l?+Vd#N%&d)-)t``q+1^l!>tV94)Vth5X>CJM5EffGH+YvSf9zBHuNr9|~ zV0rlPACHIC{uKOvd(`ef9zhHI-KUC*Z=0sC6pN_pmvbP4DoQ<9`~!#DTp9Uh5wmtkLJi z(c5Wi#y!iud@{YddIY;EDkcVviTOV7jO(m3EbcZJO6msYd^#vSKObu@TUSw0QC>dq zoWHEBY{$RXyp$sFxiEbT3ZO+$!xJN;HJoR&Y+Ovt>({Rp$kChLEABoT8nM^T^afK^ zju{vkIq+Y(l_$-tYHDigU2(8I$mqsJJ+D!31CHPU;~yz5UeG`U($s z4THf*%Wx3M6~u;Ygz0gz+2m-T5EoD4W@~CBaI78?^0Rv`j2A*Tctn$Uq@)h54G3T* z=VC9KwI~gA{4z$b0Eku9)6#2wm|TEltg^$~hyHmy?r|fkEKY;d3t(+HNKb5&cg%8l-z=1U28TrT(h2W^!iAnCnhF< zz+lC4)psW?Q&Env6`ysHYU+9g6 zFGZwJV=J^)Yx4nf{1=Y4_h%i;I(%*aq4*!8#W%!{t*myt+1E}W6;B;j@ja6|G-fM` z6HtbCnU}sT);jIeqia233yJ z<|xQ|VOCaFUERKU0K;E@T|c-u#YP7L_^mB>O!m;~s(o~OOiav+(vfD>FqkCch42>V zQs|XK=Wfl67uso3vQbt0ur6?QFM*PdSHH^fu0+z;5nHfFtkLb}rRU!Y-GHzhz{2mC zzj)yZpX})908?FGcc#z<^7dGEban#sYRNfG5t#Ob9sHs>`yg_AvI^I`Up@x5 zu6H|fCYS>3zf6xpojiy`3Wl(LMkmHiO4u)l0qchw7C&M4?>)bH1aSzk%D8hp92~g_ z#6bv%#F>*-px~2~#09VUa_x+BwSMu?9zv1~*5vh49BSB_UtO(JXK2rVg_Db`rT8zS zS>2lBJZAzuGeAs~`7jdEg7SbcB8o2z-o)HFn)77`=I=-(EuSzXS!D`JfyHr}0)5GBHCoYE`XWyHt2fN>fsxWQm1FB`5PRh3zz`N?fj;@NY^b zWRh||-OmLc+Jy)EKSM&DiKJY;{rcMJ^o5nJTW(zXY>P1xjbgeWr*ehFgi*rLz0`==zxU7qWXJC}!AY z58zFNN~??cj!iMcCcHqM7S4e<`)NWM3UUjbPg*ym;|O?DUTNbYt-K?&|$lwG&!PiAH)NKJs%X zOat1=UFf@pkU{=51t~J5>clwUDBPSLt=>S$Fh{m+a|j_|LqkJKYuH(zHQ)~s(v0PK z=}umkl$5-A^S(Mbr{Gmp+I6Z5z7w`M1irOZzFe1wllM%ZZedYzG3xvG4~BC1T?q*Z zfMxbXmTQQqteyO**x1|tT5@u)caT@3MtGrI+}s5f6`H;$51!2FOK5l^Myb%k@#377AS`k@rr|9! zdt`2vV|IcGy1Kf{?zKBx1G-f;eocKut~uP1B;Jqib9-Q9YI$ z#8^iNBgjPZ@bDm@fi37fMGvR9B)b{D$wDIu*Dp@U`_q;mTx5>3jPRzw70_BUrRJZS zU9NzEz(L3wy=HUc{98N=gyk>Tb~cvJe|OSD1(@$)n0Y^KaBz^>LCv^aHZ3T`S8lCHU>g`>KlBeXe%#rZ8!gav?Um;%;EnWzYAQnhXPmdji z0E8zKAoEL*yWMu;x4#`^A3BOC_S*aUh1BID!V^q$zEjDa7nvVlen(d5CYznVeVN1q z-N(Ki2BxbKZ%L%or>xo8-KgY*k3ts4lQVT;G^kb10?XklbbrMO3y_)d9dlqoIgvl8>&kFhLoU;w7}8P`xs@ zm93i^p`^b@CC%9dtYqeJZJ9|KroB;m|CEi-(C~}laP^R-KYD|JH<&=EQ~f}?9n@m_ zs}tq>B$~DgmgfcX<~VR1lyS6$BkN1y$^pQYvN%*wF+;#H6i!(smOf!gf0I8W3j@3UlU~sS5=F2J+?}VL)qsLHhXiOZE@VGdp^YJ0Qm2K z@Taew?OLr-^X+V5Mrna_*R~A?WHP<2t${?p16bfc?7{sFlryd3b$IRJUrN9D0&G7F z2=UYPdyR8Vz0Oz-tvw5)NG7pkN1NI}_%-Da7~li;t0@X!Sc?ttL9hJ|U}03Gi0W;B zH|Gv)w?#<8yide}obr<;S_w0i`d(uHiSlr(ciHuI^HT%LTK2HiWH#D#Habxv`i|t$F|4Y{up};kEd`@wtD(I) z#QvZYOUj>&CqT0ce-a6YV0V;#ynuc+tuLT+0i;O7!>&+)RBo&EWiUPFVQFCw&TlZA zOPSNJPdTn~xd~Y`AHTGjrC=rq{6jL2&ntVkx^IGPYyML&aL|H$E>@z_ju7Q~Cj%jT zTW0>tJUytFR)Jm2p`_!ZB2L-n#Zy?y`7MtI)UdXxfWv^QpY>tO=KM)+Z1L3V4dDo)2~80{ zshBvt8%RUh^JaIl|EkM2V(%1;q>dbuAvhy_lXp&A0@WZ4N6q&*;Z!ddRC~6JY_&sy z`84w(JX^&5%y&mn2=Js+RdGXt*B>YsK(R(I08yd?$w-3~S$2r5=W`>|P3j+w`?5`m z4^=A>=K%v@IP7+<*p>xGx^*~|pdjliK(C93N;G04tXEvatoOCfcZ?U{1kktr$G}iV zGj#^@ixTD4mSFF~wO~M(vQdvi@xZ<0);HX0Ah!z&bO~+xC@Os#&>A`FQa^E?X+S0| zt@^jmtfuR>DXX#eg!|N$t3`AqOJJ=5#H1;OwU@kxd{f)40zJ#QqdLW6sU?PPaA24_ zpT1{oCS_3`b3Aw=_ZTo-FNixy{6Zp=n^5>-q@z<#yoX=GRvjJkqO-yP1Fo z6pKMxCuUZ$QdYb;Z@hnF{p?HQ1AViR<9!T#L12(9l!go(qX8i)ZYLKD*{0?tyWzt6 zya#BmQiD^@622tSyKdanLp=kvn=l}aNv>X&b$~DF@-27i=-gUAEO?D?QML0Ye($;4 z?4_kO6j*J2I6{iyo5%qLiZy1xVLe|=7z==y7*G&2B-b6zg8a@=aNpu}U!93jDa4#VXslaZF zQ`Xb)B=;t&O+3v8s{RTE)f+qm8o)`41lz2ymKe;a9ys#R879dp=(O2!>}H%)=~qIz z#`C7r-hk{HTK2u*V|lGZ2iG;q#0o(_FeLZx)LbhV)#>A78$CeRf%L~yk&l(K7AGVu zER>^`j4Su*UxuE}a6k|v--f-OCT;cFZ+lbh*1T;h1be z086(FpWP#yPCbSD2be7B^P`W6U*>!#X99y9k{RP*4nRrf5}ASsJMeSYY8jJBzG$T2%~wIbXhQ}FT-fGE|% z$%~nfctw{Px+2|%)jcF+ymrZwBir|XCH)v7R7;2kVi%VyhJdXpoAg&s8Me@zRS($T zz=vkegv`6S;wHyul+SnU#Ec}=toYFL1ngr!JgM8WjSf|Z?enc)m}yK^rH?Z3fD>;wD#%fA7FOHfB4p7$0G zf8h69{q(u&VQ=Z+)e11cq`2RKKDvJI{90+MpPhtTQ4J#Zvnzf>=PIBUqtt|;wD!DX z1HRxaO?Q6Gs)8D?^1|Lpg_!vtm|03afyP~G;_CXsk_<@Hw@%YMZIiDr(%WjRS-p9$ z%G6rS??T){?6r*2Jr>WeKRDs*q<0y9s=AIL0GU?XUfRa-3%{w=4lZJ zqZCH!fC54li%OgziNp#M$2S1(wN+PIt8e5yxPhYu*U8aTsoTh*{C+p6bXz4kfnOjy zEyH-&NV&dcVueqXc*qR5G)~uj^$XY3?DDBw{P=F1gCe(QDaO*R18&;Z_F2tjhGb#d zru;$XeV8(0?G?ogv_(02Q$gO|9GUoli|Z^W{h+PnS0_|1*iNR?kplXidz1BnqXEjXTm3zku))iHRk(cycL#?<&|9?wo){KMd1Z>$O5i{M6FU7-W(a<>y&Y>T^svxpzjhJMMya>l#*Eq;pXv=LfobO4K?zQ?^Njcit&kw_Vh;WI^ihSe18SrB*cCO_ESZuU0Y6j1 zM2E$pPl`toX6Du=qZ=H1`OYhJ`HuF(^<27}<`eI%!Zs4ruv?mAEA9$u>4xBt!Rr|+ zz_#89PJg7y@(F3C{;bWCI#gL^xfin&gBcYU5%D?UBg4vITTgW%G!yrY3%`(Uo$~-H zD^zd4cM1P0aLW`6JhPwaF88<7?J6QYw7XD=Te2FpX1LOP!6@~=BC!hyT&s;r4)qDk z9YBUG8H)3pE@TQU#T2lm7$DrYfx`uykRq?W^*4Tto5QF>HZL_`B%AEqV)wj3<ov8p69GbR1{+5CBLl?4^j z%&PMK?RzDK$0-QUc50i!SdF#3t1z^wp#K!OW*6e^s3IqK5)BfA|9%}7fo#Zse*OFM z->3hsan#`7m;XNfsdlUrs_kA2|MKNSz!?x0-N+bIEgr&d?vgp%3)9a#i<2%o%3Kp?yihta zJ~09G%|JtAU}OXX?p)c~1zuCv(gON0RlK&ebm?SzW@Zz16XdG$^724hxuQaJY7Nw) zsUI>gCHNP2&gT^u)17SC4snfa>+9{kaVBsL^~&W&4ct~#&(5w8@Jxia1J(nU!t@cX zwxUYSK#sc?=@}S+!@DW_sk~f63QAo%w$gk1r4p1^LraVDkiAM)$=OlF)ji$y&g@~I zY~^K+YnuMv*~uX7UDjh+H{Afz37ho47JD-1oe%jDV`JkdPo5|#h3{xpS6725 zI}&+;f&zHrboDZTGyw^{%VB^+if+Hm$f&Qat^MrT4gV6$QlN*V&wZG{z(Bw}Wvywj zU)DMLWXeb66&BLe8-fX34(sjh1^05EX3A3plA+~Xeag$qPM$i2ajgL_e*AcX+ge#z zSon4H>Fs^?aTw4rfN>eZ&O28>%dI;=t7~e?%gHGOaD(LaQPO#HbF-nL0bJArmj-4~ zPPG$5MzhfN7c;T6IDB$)GJOOq1fpF$4X zgGP}n=gtNnN4{MY1$Qdb)6;wX=G<$g+1VB9M0;mj-3v9bfHKS<6-?-|F5lXD>R#*D z|M}?*x`kz|zaMa^v5(9E(YGT4)CT0o8iOfiJF1}!W7EEA;#6nPUQ+GrRmAsZDYdk; zOixd@2B>Rj+>UOSkd%C2;)kK(%L=Q^2v6+-Y+FZ1)P+WjYsZVPfCRz$WIgoUf|vnc z6c8NokFx<6g~J2=mn{dszrXmJm&b`&=8}@C0~Crk5)e2(Kd_DvZjwSB`e{vT@8~E! zBO|vVB^`Eo+1AP`x1gXd#SRjR|K_;rvksV&8O0~XL!lud)+7%=W|EgMN9Gh2spJp5 zrx)P_l1oj1jI=_%5P}WMJjIp#)qoeOk^^Xa=V~b~E-pz)2C!NcC;%Hek$7HQ$1V@^ zS+@wpw-^q-jU&F4%wco=+i;h zxw*TxTfHD`8DpK+tofE!|(u zlKFLp4u*z?o}N3Kbf-S4xf>g&ds=`9VP$po{hK#0fqAbqHZ=iOwPgz+R67Nag&be%^Pp8Q_|erk9nas!ii~V*D&(VxQY#6 z!eE~!U3e$1@*VWd;yz_%1;2j%!p!V}oZOpjr7+n?z9&mOo6Q`@gS#AN~CI z<-bq=XVngJNaS*FeUsANvn;?3_}i`zv|V2qyP8RuI-7w%WP0Rahq wF$sPVE`ELqe*TNUx(@H*|J%UM;f0mC*Z79_Mh$u)Aq)Af{5GfJq1jtc9 zIvk`+i9kZHp#}oE6ZD+(f8Tq)`@Hx0F7RaUti9S^Yi8EWZ)SLYOI_*kfzt=*=;#hB z|9)MQj&4Uf9o=u0`*uJ{i`(r~xa_jNs&+w24$NWRi@A&p6FGWot&mb+Dw=4VByP%+;D9wW81BN%R z-p;%h-({m!BUnL9X}T6Kw)*44i=8YomS?AW1`?t@>{B<=$AZQhmgUHtc*LM}vZKSi zUe?jatz*mUJKMvryhgGno9z#xJ9g~Y0Nq8eeb1gfV^97cee5GWfA;J@Ka|cD_YHJ) zjjq*XzE%*D-)M?nHlPF5h8n9Lq5ex$QB{ub+PK8-* zQhMcl@v=H>42^lh?D)A=92K>gUhXcf)}Knx%{VH6>5sgNAU$c!OP#CIoJ*$PTbXU~ z#bF)W;EP%|Zm*}j_6s{j;KxaLw^SSy+ifI%`SNA23RCSj&e8ZeDzS96TR*AHx24>- z{Ss?af0gq1`UeQP;lsS-;vl$65KY7EgI#k znQBJ0(=PVY5p&BvQyPi-8{__`mo0Qx`AZMWxi=W3_9{%lo~ucH|;2 zl2_T&vo|+zU7o+@3yV`fu9y1Fb;@nA6YC*2fX9cOIq7j2Mf7$VX)$nRkd4UCm|RlQ z_uAR=xmdnsz!Vi`g}QyajmaQ1M1bTcZ|2^)DBaw&ZlUpob%Z+I(>t|cWZoQ3lai9+FbfUgAmJq!Zhms`SYFK9Og^h1 zbKdK<1|l*+(kz0@W4WCPGa7Xw|M>Cl2bq#X>!)X8e_TX#y;o5^Fymsc8jjznW%bj% zru^AIY0!WEtFdh`wNqiYvhwqRfq{~179U&ie%W2`V%hM^4W&XuA*JKx3pm{1=Cuc! znwsyUE#C4_2VAY)U9DZ3l1g8b3q~Gth?SO?_s^|b912NPmueXh^Odiij>}>;YG+#x zW(d3|#b8e2GwYA6oMs)gsYUuXZ?=X-%?`%CeEC@&b(&Ldu|N2AfFe9H-b}FFSf8+7 z3Egtxzem{E+*&q3@HH8=y!0J4da)U0%_w9#z2G(f^jZ&^T8!8na;LX)AlAt(s*N>w z9XzFEzsLXXxDVne%;zNmDWFSTAG2ck+9Ux0Cs;xCAV_yMpss9}&Ed zsb9W9MtjJ27yey@4P<4_x;L`mi0jPa7WI3v*Mj-VeEYcP5Dw{D7`$e4cC_rhqcD@A zwG_h+Qn$SACpPaLxv^9Efw&?`PR^Z=Y+hl}mZMr&Gk?C(vL+QJ`_%k1EDuPHNqst+ z^X#`ox}9&Th->TWK0X>^81H#=wAS;tQ?6&jrsnujsFTqasW(wtRm}O$tYcFm-J1d% z!HnFCD8JK*>In`VlkHMwVWqQU^HYohtP{kZ@t4##997W3S7NT#LXjX!mibVyIOcjQ zS+R^>2F1pbT(_|hd&~z{shf4Sjo5SH<=pCq`)h+){PbQPt0$wK{_$MOU${<}Zf)Q; zfyB-tr!1Vz6>U6lw`5Wp^KPG`>Z|E^FI0W3elb4lw|Pwf?H8z+J7)Uc+pWq}-&}F& zgg0Y)zl=*`UK=}pYS~sSuTsPA&U5A=T%}gYU4pkS%1DO*IZ2t^-iBnM<$<8=%bFJg7m75~YYhs^3tzrV@(W6Jckyg&p@}knN zD{JW~83z}h=Q_@|R=!aAeU#tN>Q(5tUeZNg-UBKm3Bo;bTO8K!x%ilBHqwQAly#ga`-QCDu{bE!*ajWj$r9fu# z*R+fNayn2%;&TM_Moi z%BxTJlI4?y46?qkp4U#*2pVycH?d&cYGhZbo3`a7~p39h|id;dPtf4;I~|xv^##!e2o;y@$ zmc0CNLt=wA%_Yw$Lig08zGQxbBB&`bUI@haFmV46ljd>S zyZAD^WA#MsmQ~$n{rSs{yH_gZFaG8d`)A9rFGq52rosMlg|Ph#;}KsTQG7c@IRf|3 zGzEqHCQufQIxWEn^+HLx2GGbw@gza`?OEcBi*Igpe|z*9dRDB2N$v*VsiqL{Naj>wgu{p3+0%34-V$+TXuiY0r0(f42O)&nxoBpug>w zKL?==22W{&(E9v2=zqV`o^Ow_-BRJ#{QfWdk~mM6ZXOUdPrs!4)qjyA`O8a6kJNLm zx!4xriz=$&=z!;;m8rq+vs9$~juW&bBM{t^>ZZ7k&dyWrwU_Gxk&m~3|GDA$bzAoz zH$1dTQ1JJ4yAG}3@0*vkBQLp>Bi=oIx@Wr-KG=eL?Zu1x{6Zv#mwg_H8{NMBvc~)o zt*1cb=~lw(@^WEep|@<(=g(K2mC_H#03ZF6g7Wo35OAx@dx;P3Cx8pgAh zj+9SEj%;pj3dX2Zl5R+RsxWo*_4U=z2tyA2`0=K7Y-(!CwbU4eGc)e#?d=^L6r{>J z6w63Sl~z^`&UiGgUYJ$i@~#e>bSYh5Uni&_HU0ht?q&Ccn?q+=o3!|6{qVSu|Wg?L{d@Z;prF*KT^~Zu2 z1%+#~X{x9FD66O}Zfu%6mTP7f=<`Q#J%8Yr)yX@no~HR6J2gA2o7Kt9&FxcBSyUu` zbYIDUL)1+jZS7al(dU(q1Dz_!6WT+rRVt=V%*-6#wL?8aG?tQ{4zv*!6$R~v?cl{E zJ*5-(_;bKCOG^#xO|!Il^KO{x>gsA~aXKg&_Yz%nbahRGkT9zFYHCW#J<`F02Z>Xc zB_uL3G8(D%4#mNk!+ZQ2i4hov6DK%}=7>|)_wGG2s~%anXKA^6$0G^_>Ffy`kg46U zv6DTSosGni{8fs4f=B&~Ly~6ZA$diIJpJ<;N!b)V(u$9w;?p*pS2(}n>gOc)4_R3? zGsX*h!@26M^6OXm@TthRl2$^FFO70^$y0Yo8yg!$M^Eg)u$}*BVZt5seDN;yVcU zkxen4BtktSH!d^tqNu1AQou2t9^Via6m)#=a$jFx`?qh^H=mb0#?LX@aeyXAQh(gj z)AQ)jZ$C*pgXJ+YzBxJ}!Pv;iPMAsX#ugAD>@-*Ar%&JR2m|$g{``5UmL$NS0x#KV zD|R|$SQh*JyJ2eEg(G<~i7hHGG3^u8V#MZE4$z%YKwO})>FTEIW)UNo!HRjIXrZPS zQi3IsTvFTgjf}F|Y@RI5%*+gb6%`e&F;oGfznS(SGqb*-f%n3NaQr*#JpIe3L%5Vl z7lT0H(38V8MuLz-=6-&D3JU(JDBzQMgVkYv&%ml7C&_9eQ0(v_y1d;&7`B4K!rqyc zADscSv$Ku3DD{YUFwelMW9909_Vw{Oq8kxcpbN^*x~v^)YHG{N%cLk78JWQT#J-uL zzM0QF^hXXI>Y4HIUVSL}$jqT`^BPDoAjh!cRbrrRtH*raPw@iMU;(%W_C%42!eX(3 z?Y z_=*Uk)$)7o^YpV}DtbqNoRtm?L_%u>daJ+GbvPL$5rxP3IXg_J|KeYFb867R$5y6Gz&|Npo!tFTYvWST%dYY$)L9= z^^rV*#vE&Df({?@^4cccj^Jd-o*;lG#>e+nX1b2GCF@ns8GRcnm`hE#NfTL4I__t# zWIt65Y;lRw#ZFb^zz;cHS5m^5R!ED9iAhTvj`QujD43a*75w6bDv*h)s;aW`F=}Q> ziGFHZZ1ZSIiR3~@W^Qh1SeRLbPoZX}EQ+#rW3FYw?Q3l<#;}l=gX8YV*N~WV2pJip ztj=8hymKNVd8w&Y6K=0n(@J^4jac^UBw6{-?avh!Q%o-UU4hjwyx`|Rz z3D(UD2@XD3i9IXh@eI!`FK=4jl#-gNc0RKH-fL<14?US59R|v1(+G)K|4lKJGP`=S zOFrS%o7a2Dm!s>GFN=*n<*?uTz50lvlJ@T1vou`?hg{t(Xbc0I2fWG6by#1T*uQ_jVNOv&!AX*%B#Edn zV%?flafrOIus~XDvMrQOY=PAaF8l$co{@|)?C;px+^CtlDk}t3GPM<@-`3L7GFa}N z?9q4V1a3ezcg^yrHzYmLYl`@7At%AYgbeKKakBmfSyL+&fz_ z3}%w1y+6Dx6@E09t$2FqAaI7r+p)#6^Oh!PJ#tPl<(4c9QR7wn)Utx$OWXy1{*!EO zTKf7IQ5LhcwG})#dJ27! zYzij|(f62{@sjCNs%8v%M$kt{0R`pX`}#1Wrj-NKH$QyI9#sFwb7^35-0XPaZo~_^ zdykQjy?~#q-9-FwFqBN^Fhb!op(D7>djB}1nH^po zXP(@s_XH`$OscUfEi82Z_D*@MH5uVTcvgP$n{A=FofmAC`J9!GPG_8`b@eeh@7tU| zP6l7iI~fr}uMmCvbMTQg&5Ia0V5+@yR+1=6337y#gw8u7<39OHf1UZz9_Jsx(;{BC z`5DSdT^+R5-yr~8Q}@`d^MlT*Q>V<^BtRnGmXF*|sy#|J%+bxZ50X=lIcFvxEwq&s z+cVp3TWFXgE-x<+EGAn8)Oh){nukY`*Uiq(PU05dg3F~iRg&n2rpyYr^6Sg@BT5#4 zUC8(4o=`q~;3a>pQTaWq({yRRhe#8vk#mCb@~GO-vm_yT!rp`X$qf^c$mFgwd}fhv zUW>bv1ni0J`hyMB!Bx&#&HIboefzt+Q%j>Tw=O&_zaw6hiP(lF#}hPd5^ho&3&5 zSdb&ns~u-*6zb8*(tTc21F|_VQYaVayE+R6ho!CwF`NZG4J4?S$Z=z1PId%4;#2N3KZM-mpFr%ZO_nP#1=&hnF%K0 zRbm8BwU*Q7rve40w{AJLVP#xLUz9AkPjx-_n-h%TlaT1XBV1im^E~l4INVN$SOK3$ zaDhasW0}?C!hslqfg`wbOe=igd>ZyVrj!r+(ZkF>+j8$xy=Ql4XVmN0yj?dmwX|v) z)^5}TSNsky%ccBTX-?8Fj2%J9n0qi-E+r@My1Kq$`p{1!wi2M!n`_^kQyQloWgM_Zp^nn!tj&dT^ls!WRgoYl; z(UrM(^#C~`5ARMQD1VNf?gy6TQkI5Q(XW4S|9+lfj#B8Ems}UukkKFxIUKziyGtn> zYORSfJj$9nI#FJS>*gBiQF?YD`)}L`fETqcF>dZNJaZ%vG&G-LN+t5Pib`YJtU5(- z_)n90^~d?vt$WaS{%A{8;#Mx}cuYsyPf~FITB*Nw^5n@~b3!LE&UsfrkLZAjtjytY zXex^NM^{(Z=EkZ@1Xn0N*!6o2H|g7tOTlX1L+m&qGp?e1kl3I-z|TC(t7=6`Cq?k{ zLCrkXXvX+qX@$X6E)*}B@t8`{hYufq;4QBs?(HU3P56;Xp?;^euF7BC>Xe|&RGuWU zqxmC**s9Of@f^@11x`nEZrgh)3(CK=ytn11La+89PNQ&aFlUZroAQ_V{{DpIWb|_P z-0ZC2g$pI7tAlvFV}9@J*RP*^0^o8uvb&+NQHUA>cAaACZA^C~0_p3EZMHg*{DiZ+ zt4mx`axDb)E-8sMxMl}U!`HZ+Tes>Z3l+c1r7z@UjVu}rOEG;b^ij62dIJ}VH#Kgo)p-c=W zhAZ>!+ab->q`0`={(k;SbrlsBCZ?liefC9nNMM+P_Kx&4#dKTcPb{w|B%FzF?&|Jl z+_&r1&9pUhC#OH{#eDSM-x+P1W^vS-Jml$4b6a@mabxTGXgBO~87->{I7D818<51t1f!IWDWYz`Ri zQ8&eeg?}uP;mzA@_<4DOeXcE)mzOs+slKf&KX^5pxx`q|S=rh;<7S$zjm?FN7vqHQ zK3+3{-tEPWz}V2%)}EI-ES;#1{3N$+lV08OVmC|E+?^Vqlr-xmz4Ot-zM0BHKxUgS zUc7kv^eOBz*ri*tHYikO4<;!lhG*;0i|(n4jyAV6P463|`1ttUeISEGMccvpY!qM! zZnlcV)y%B|q1awN?f;p*ONx+HPZ%2;Ys5~SIuNjCLXf${bm+4kA}%hDB=O4{36vu% z6y;kAVIq)2xsAl2=g;#fe0e-(OkgdzOPdI%ea`H(B@fn&Za9$usIa%sMvj0QTUEch zu}Na^c6H6anO1gz%n*D-KGf+p$v>pEq2U)K!iFHQgna#B(L?fZ0<(;6aL8%jNuYpx z_uk#n%xJIo#rgX9?Ag25!rZ*tbOB7ruv$)a%n58iz!m=VO~u8B_mIVe1%tSwz`_xNYu>gR7@3-k}_BU&E!Mp zBmfssYvqLGnIj>J7J2#|wuO|hQkZU;yI*#ZpHu=mQ(>AM7nj*S@dQ7t=EEBWXJo@2 z;b$SFPoFL&s>j5|F(ZTU;b%7h9~~ak&eR_6kcFXynSd;+2T>dq>KS0LN}LXf;lYnB z?w;d)_!CT`_a*J!vuDZ2Z=M^4!vjJivqIE)#DO1QBtLF_!WeT%3a{4R5$;LX5KnRe zirb?wes{V%oO_%H5@_^D=aQv2~Twf__fdP_nP+6UxGE zM@Ni$#t9kL>ZFIkz=qc1@wL-?=RwNRI19b;7~dyOc~Y|Qs%OE8OwR-zGBqY&IkYW$ z!ZjqWZyVIRetxBg3r!?}iA}rN#vgv&{8cs*L6J#0U#p7U+>K^QMnPx$r;Xw_a3%GVnYGUQV#*{adl@iWx z&1g9!UYdwDa(w>#Ewz!~0Ih@m*tW*I;&~L+%a?gX%}>ypmqzG!&u?AcLp!MCwNhpY zS05txfcyuk%3No?En7K~A0gp(8wH{PLBD`MI6!DHBJJ{D@3xz9&}y>NS_YokK5A$^ zz&9GsxCgqX!Ij$;nSUZN(8WJPumO|+?YIB_x!b?pY)wA^f&V8x|N9F3 zJ=%XRMvCKARR5Wo4w>|@Ze{W8C%@L{G3DS&*TLJKs#B+N11KdL#!ma&1}SN9`!@do zzP`;xH0b?T5e>M4Z@`z^4QMDkt%wF^{oCEIXSByZZ)pQTW6^CipEerw`_I$=mt8k1 zM{p^Bd&#Mk>(!j96qajL>;T5l-xIprRWA|W3`V%Kvop;Ui2!vzvi@DCEvP-UVQhN5 zQJ_HrT!I}xrFuS+l3ZZ;z`|l=VWc)SHPx|_JX%**H$FZNpwC}R(Pk5F22hxIJhrS1 z0HopJVNfPZ%0c(uNB)WBkPvWq2rx8)W()9AqWYLl1St5*$Blk1;q_FdEkGd%XV;Dd zrUKNKLeSIJuCA>WGOLu8mmeg$>@Na51%3b|njOIYBy9M+uNsMgd6yZ<$5KG$%+$&# zDk}1DcYn}da$DXd84l?9c;7gdzt|%s?-~m5j#KrZI2HH104~#MTL=p=)s^GFO&!1n zUtUlE<)^_B$O@ljTZ8dCB$#{DP2&7q#W^xSDIeayKWe6tD6Mnm*u!5Wm2xy34Nz7| zNeL*uU=h^T)+Qs+rsc}5=6M&QZUSVVC=G^eA(HF@K#_^bgG+Itta3h4gk)LoOM-&A zVDf>Td_EEuJu5BkMg$ieb>i{d%E$K}{3SDzZ7vVK>;Cpl6M%kWV}8c~m8<(nGSbp; z+KD^#+Y}lS9c957U&F*qfE5xi;h6jWJ#12Veaz1n3q+>0aVBfvE&fAb{ioFXhpjl0 z_x%za|9SVf=-QT|u=0OPyZ`@!>Tl`wQ}F()K!G}ciRFK$%3PPx#)Bqx$fV51irc#O zy{k|4_RJr(`9&UZjO-HiHJxM{JFqR`K}g`bdRM=D2V7f#kc6MLppOOBtxtgx$T1mw zeT+JIy@0*JF*XuMslP3H@<2`hJIlWqxZUeKVmR;wCu%KqT*mq*`O~UWmMZe-d4IAr zjobfIdTcw{e#(h$>4D&&4WIxnxy2LLeoCId_y}asPFgRtzyER@Hr_98`Tf;CfW~O=-p&($*B0n;<_?TTJ=0{>5`)yuIX}I(x9-UoJXG2V$5fRw>Zo)6 zwYRj&3_1F>slSn%xNF;eL5Dq&p@GfW>WP#+4YH9fH1gw}2{nm zTw*hHnyP;GXg0>{!cT=@Y~nO11PHVU{ceJ&UMy~Ni+iA7#`_HIsp=Rs#BU7J_f!W` z_%uE_6uBsN9F}jNjcxJ|U-*Hn%W&dCc&HDbZJ#awJXWNv<$aVvfXYlTyMH$);q&m* z9gqGfEY~_ly7+{k@w_Z@Z#WbzG2h&#x?c`B+U3`Q`QLB;c0B%i@Q>f|C)v_Gk+iby z%YV7q?(n}=2Xl72*9v%NU|KvbrI(o?M2-f>``~=T=r{ zMMXu7NG15=wlW%tpY-`@M;)gUcr2p1CP>|2gh3g&6N32m0@c55y(7G9$8VT!Flvc~ z+F`X|6pk#C?>TIu(4ggl3l{TZ*=J*Y9b7HBUX0Dn%>e-cF$OG0g-*pZra_xiV_&!Qq-O!pXp@Lrp@xKAM0LYN7FZdKJ6?Gq<4y9x~x zqoUF@Gi~kcgwTv&l!0v%^WCG{=JIDD9_PE@B?UuWJ;Tn%X5X$Ia9Y@0R=jb;fS)06 z<%dftK$PG>>$UHiaI-M;gI!~5>|3%e{BE9C*O(9-kqnl5Qc@Dw`H_()_U-cV^#zX` z7)ot6;6RP(Ro)L?pwT*-*Ki7~TQINo?YepMWH*+@ z)R`lBO~(@Gd2nyva`5q~Aah`$o0hS8gxP0mO_F_TO)4o>AJ9zpmSLXa)avvu(D}9{t-xTHpJAKKc9lFM}rU4c{)^ z?u2S~LUhK1nApyA-h0a=Tx00>tp{4^HLAEpnqh0^=FeWow`6fQD`7V*qA|a+Hg^3yF;=E<3ELQsV3RCE}Y{T zsCa43^vW;Pj1Bvy{mnMr5vNyivcYN>I zH9$om;*h@ch&g5BbGaVWs@|}Bj8x>tqr)}st*k!9OHo}AT!s9yECxyqEg%VHBM#xx z9;n4d9@fUb{HAhLuAuu$|L*XaLAeVXYM9YZakYK2@Q63#th`h0+d*z?S|DT&KPTfX|gBakpn zr<&GFCzczNWWE9OgMN98K;IWa9mF#VNKla@D_scWvGkt3VT;9Rvcc&3Ubgxp_!CD+41uOM5 zMd8p`|6sfZ<&^g)sT9Q3HzObbl?Wzm;%abGRh+T^dltyr7jOvrWL zUt~{*R(|eFwf-5twu?+vs0=3&C0Yba5q|ZX6MJ)(Eyny;D%+GuGcxzoMED_`_Gx+h zj$h-m-K>lq4Mh+i@O25Os(llx@6)AB#<_k*@3YQUQ^MaFnD9hZI%C%!6~|1wZf+qS zW9WXZt62i&N62WEc<50{LpwPR5a{E~lBjF2u#KX=b7pZOj-S6gAs-wb zYcX>{`=nb|MO&}T%H8t)(~t_YTb@I$H7>HDu^J1sIib9_MkL}WBirHu;9h81ZOPz^~ug-Kw}FpA}EE#G}G)xv6*S&C7tw260jwv;P$u63wUSN_wA zby!YopKkg6u_Wk*zV8f7(<`d%r}YGC;*G5ywqGy2jYIu|Sp5w)?R<3Qik#7YDODTm z&`EU&@Vx)E$*V4q@@EUA5PW%v^wZoy5au|8&k$2#w4;S#_P#y|kB_!EkLlH~Z$J%y z(<$mS6D{}?f?2x}R=XhPa#ZklIS5w=jX+z=eLXc$4|8xQsoN^ojcSE}nx|tdl%3a5Sx_C1TSr-Kkxtx}#5#tgfwFhvvJhT`Z-Q0~}1806naZ^jjWc}S5M z^s2DJTWkleE=CuZo~G5DwBYG-lmYsJ(S=>@jEsz!!slE{gGg+&E+ff*KGvX;lxShE z(?V!VL+nlxuI+kl(?iOX*g7$+WMCF@D5jPLD`QqCL^xPx`mslYbMr9Nb{XG6zyV>V z(^B83HeTtz=+ELFD0z>|I^zq0z3^@l#N2EAWh(IozF0PxLL5UoO}h3RZ30<4nxekc*2xJS^1jDAWt&@w(^>C3sba9Y5%E z*Ry#edO8Fmkh9za?aYi1(jt&=S1xRh7XJv!tzi6M>YXxwlz`?WCE_+!DE(xRb4ev! zH6QKq9CqCqNkDskD**~HIMA0FYQ%nZY!G-YVxt|4SY<8f5-LShOlh@FIpT_KGLMlU z(m^+Opsk;T>Ip{(3x^lk2vUSo5vP~ka8zmlU)hTM2BlHTa3{fR^ZKB;vXrR$pM#sF z-OC%)60#Dap-(rVILC{~Y1ghB>z`&;&~c&b5S%nh)7fh?+#4WP!CydKyp8=(io$_f zNxy4{N(}$}7Fl?QNGhe&*N#oOinq+PS*5b|JIS*S*FfH)KSwG$RofE8=(`**vvtzD z?;Y?&&9jh=+184@{9!ZnMg=e?(b0ahigDwJK}>)0){m;Va5JFKsH`s2Ri4GV2s2Y% za|CG@Eoj^l%PQ?h+IOpM*>*$JQey^n81_Doq9(Iiw=p&{)`GBieWpg%YfU)8D6h9b zdUvsSPUhYzYQOIraYv)P9zLhW9K=(KlFLd0a?vgJB?K8-qps||)?}mpX+>}jjXDhB z=P5Qc1PnwKbyyDXZ z4FYIZ(kQ7Hdw+~|@SSHH;4B2kKC$DigkwB7%v@xFw)(Jzq6Ne!v=1z5wNgd*-F8cq zSfZ}ghd3R_zki7%Y%SeT2_c<_aE?|8*2o`iTJ(~=tBhhldZDh>!OM@6U4&B(B6xC9 z3(Uxrmo_se;)`c6fk=5L&v(`X(TuA)XPDk*EZBsD6&3p~WD)IgC^GgXi^kk%0@-r| zN!_h}`M4js8{MCyF1sx{(1g(_SF@Hah1S(%nFvqg z#C>&3V=j~pGwA6z8JGE5CYlXt*NC4>lB0&9r_b^b_U`y?iZAdJn@Yfi(e

>W^cs-?#mXItenZY6gYiNlBTK1l?7OkpEdc1 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 GkN+2l3G_?= literal 0 HcmV?d00001 diff --git a/cmd/clef/docs/qubes/qrexec-example.png b/cmd/clef/docs/qubes/qrexec-example.png new file mode 100644 index 0000000000000000000000000000000000000000..fc5d57725dd30f32415e543244c9c40110a31c8e GIT binary patch literal 16166 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 literal 0 HcmV?d00001 diff --git a/cmd/clef/docs/qubes/qubes-client.py b/cmd/clef/docs/qubes/qubes-client.py new file mode 100644 index 0000000..93a74b8 --- /dev/null +++ b/cmd/clef/docs/qubes/qubes-client.py @@ -0,0 +1,23 @@ +""" +This implements a dispatcher which listens to localhost:8550, and proxies +requests via qrexec to the service qubes.EthSign on a target domain +""" + +import http.server +import socketserver,subprocess + +PORT=8550 +TARGET_DOMAIN= 'debian-work' + +class Dispatcher(http.server.BaseHTTPRequestHandler): + def do_POST(self): + post_data = self.rfile.read(int(self.headers['Content-Length'])) + p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE) + output = p.communicate(post_data)[0] + self.wfile.write(output) + + +with socketserver.TCPServer(("",PORT), Dispatcher) as httpd: + print("Serving at port", PORT) + httpd.serve_forever() + diff --git a/cmd/clef/docs/qubes/qubes.Clefsign b/cmd/clef/docs/qubes/qubes.Clefsign new file mode 100644 index 0000000..9b5af7b --- /dev/null +++ b/cmd/clef/docs/qubes/qubes.Clefsign @@ -0,0 +1,16 @@ +#!/bin/bash + +SIGNER_BIN="/home/user/tools/clef/clef" +SIGNER_CMD="/home/user/tools/gtksigner/gtkui.py -s $SIGNER_BIN" + +# Start clef if not already started +if [ ! -S /home/user/.clef/clef.ipc ]; then + $SIGNER_CMD & + sleep 1 +fi + +# Should be started by now +if [ -S /home/user/.clef/clef.ipc ]; then + # Post incoming request to HTTP channel + curl -H "Content-Type: application/json" -X POST -d @- http://localhost:8550 2>/dev/null +fi diff --git a/cmd/clef/docs/qubes/qubes_newaccount-1.png b/cmd/clef/docs/qubes/qubes_newaccount-1.png new file mode 100644 index 0000000000000000000000000000000000000000..3bfc8b5b7e91330f1f773d9fef259906eab06c31 GIT binary patch literal 22348 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 IQcs@$AA2scU;qFB literal 0 HcmV?d00001 diff --git a/cmd/clef/docs/setup.md b/cmd/clef/docs/setup.md new file mode 100644 index 0000000..6cc7a41 --- /dev/null +++ b/cmd/clef/docs/setup.md @@ -0,0 +1,198 @@ +# Setting up Clef + +This document describes how Clef can be used in a more secure manner than executing it from your everyday laptop, +in order to ensure that the keys remain safe in the event that your computer should get compromised. + +## Qubes OS + + +### Background + +The Qubes operating system is based around virtual machines (qubes), where a set of virtual machines are configured, typically for +different purposes such as: + +- personal + - Your personal email, browsing etc +- work + - Work email etc +- vault + - a VM without network access, where gpg-keys and/or keepass credentials are stored. + +A couple of dedicated virtual machines handle externalities: + +- sys-net provides networking to all other (network-enabled) machines +- sys-firewall handles firewall rules +- sys-usb handles USB devices, and can map usb-devices to certain qubes. + +The goal of this document is to describe how we can set up clef to provide secure transaction +signing from a `vault` vm, to another networked qube which runs Dapps. + +### Setup + +There are two ways that this can be achieved: integrated via Qubes or integrated via networking. + + +#### 1. Qubes Integrated + +Qubes provides a facility for inter-qubes communication via `qrexec`. A qube can request to make a cross-qube RPC request +to another qube. The OS then asks the user if the call is permitted. + +![Example](qubes/qrexec-example.png) + +A policy-file can be created to allow such interaction. On the `target` domain, a service is invoked which can read the +`stdin` from the `client` qube. + +This is how [Split GPG](https://www.qubes-os.org/doc/split-gpg/) is implemented. We can set up Clef the same way: + +##### Server + +![Clef via qrexec](qubes/clef_qubes_qrexec.png) + +On the `target` qubes, we need to define the RPC service. + +[qubes.Clefsign](qubes/qubes.Clefsign): + +```bash +#!/bin/bash + +SIGNER_BIN="/home/user/tools/clef/clef" +SIGNER_CMD="/home/user/tools/gtksigner/gtkui.py -s $SIGNER_BIN" + +# Start clef if not already started +if [ ! -S /home/user/.clef/clef.ipc ]; then + $SIGNER_CMD & + sleep 1 +fi + +# Should be started by now +if [ -S /home/user/.clef/clef.ipc ]; then + # Post incoming request to HTTP channel + curl -H "Content-Type: application/json" -X POST -d @- http://localhost:8550 2>/dev/null +fi + +``` +This RPC service is not complete (see notes about HTTP headers below), but works as a proof-of-concept. +It will forward the data received on `stdin` (forwarded by the OS) to Clef's HTTP channel. + +It would have been possible to send data directly to the `/home/user/.clef/.clef.ipc` +socket via e.g `nc -U /home/user/.clef/clef.ipc`, but the reason for sending the request +data over `HTTP` instead of `IPC` is that we want the ability to forward `HTTP` headers. + +To enable the service: + +``` bash +sudo cp qubes.Clefsign /etc/qubes-rpc/ +sudo chmod +x /etc/qubes-rpc/ qubes.Clefsign +``` + +This setup uses [gtksigner](https://github.com/holiman/gtksigner), which is a very minimal GTK-based UI that works well +with minimal requirements. + +##### Client + + +On the `client` qube, we need to create a listener which will receive the request from the Dapp, and proxy it. + + +[qubes-client.py](qubes/qubes-client.py): + +```python + +""" +This implements a dispatcher which listens to localhost:8550, and proxies +requests via qrexec to the service qubes.EthSign on a target domain +""" + +import http.server +import socketserver,subprocess + +PORT=8550 +TARGET_DOMAIN= 'debian-work' + +class Dispatcher(http.server.BaseHTTPRequestHandler): + def do_POST(self): + post_data = self.rfile.read(int(self.headers['Content-Length'])) + p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE) + output = p.communicate(post_data)[0] + self.wfile.write(output) + + +with socketserver.TCPServer(("",PORT), Dispatcher) as httpd: + print("Serving at port", PORT) + httpd.serve_forever() + + +``` + +#### Testing + +To test the flow, if we have set up `debian-work` as the `target`, we can do + +```bash +$ cat newaccnt.json +{ "id": 0, "jsonrpc": "2.0","method": "account_new","params": []} + +$ cat newaccnt.json| qrexec-client-vm debian-work qubes.Clefsign +``` + +A dialog should pop up first to allow the IPC call: + +![one](qubes/qubes_newaccount-1.png) + +Followed by a GTK-dialog to approve the operation: + +![two](qubes/qubes_newaccount-2.png) + +To test the full flow, we use the client wrapper. Start it on the `client` qube: +``` +[user@work qubes]$ python3 qubes-client.py +``` + +Make the request over http (`client` qube): +``` +[user@work clef]$ cat newaccnt.json | curl -X POST -d @- http://localhost:8550 +``` +And it should show the same popups again. + +##### Pros and cons + +The benefits of this setup are: + +- This is the qubes-os intended model for inter-qube communication, +- and thus benefits from qubes-os dialogs and policies for user approval + +However, it comes with a couple of drawbacks: + +- The `qubes-gpg-client` must forward the http request via RPC to the `target` qube. When doing so, the proxy + will either drop important headers, or replace them. + - The `Host` header is most likely `localhost` + - The `Origin` header must be forwarded + - Information about the remote ip must be added as a `X-Forwarded-For`. However, Clef cannot always trust an `XFF` header, + since malicious clients may lie about `XFF` in order to fool the http server into believing it comes from another address. +- Even with a policy in place to allow RPC calls between `caller` and `target`, there will be several popups: + - One qubes-specific where the user specifies the `target` vm + - One clef-specific to approve the transaction + + +#### 2. Network integrated + +The second way to set up Clef on a qubes system is to allow networking, and have Clef listen to a port which is accessible +from other qubes. + +![Clef via http](qubes/clef_qubes_http.png) + + + + +## USBArmory + +The [USB armory](https://inversepath.com/usbarmory) is an open source hardware design with an 800 MHz ARM processor. It is a pocket-size +computer. When inserted into a laptop, it identifies itself as a USB network interface, basically adding another network +to your computer. Over this new network interface, you can SSH into the device. + +Running Clef off a USB armory means that you can use the armory as a very versatile offline computer, which only +ever connects to a local network between your computer and the device itself. + +Needless to say, while this model should be fairly secure against remote attacks, an attacker with physical access +to the USB Armory would trivially be able to extract the contents of the device filesystem. + diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md new file mode 100644 index 0000000..31554f0 --- /dev/null +++ b/cmd/clef/extapi_changelog.md @@ -0,0 +1,104 @@ +## Changelog for external API + +The API uses [semantic versioning](https://semver.org/). + +TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the: + +* MAJOR version when you make incompatible API changes, +* MINOR version when you add functionality in a backwards-compatible manner, and +* PATCH version when you make backwards-compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. + +### 6.1.0 + +The API-method `account_signGnosisSafeTx` was added. This method takes two parameters, +`[address, safeTx]`. The latter, `safeTx`, can be copy-pasted from the gnosis relay. For example: + +``` +{ + "jsonrpc": "2.0", + "method": "account_signGnosisSafeTx", + "params": ["0xfd1c4226bfD1c436672092F4eCbfC270145b7256", + { + "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3", + "to": "0xB372a646f7F05Cc1785018dBDA7EBc734a2A20E2", + "value": "20000000000000000", + "data": null, + "operation": 0, + "gasToken": "0x0000000000000000000000000000000000000000", + "safeTxGas": 27845, + "baseGas": 0, + "gasPrice": "0", + "refundReceiver": "0x0000000000000000000000000000000000000000", + "nonce": 2, + "executionDate": null, + "submissionDate": "2020-09-15T21:54:49.617634Z", + "modified": "2020-09-15T21:54:49.617634Z", + "blockNumber": null, + "transactionHash": null, + "safeTxHash": "0x2edfbd5bc113ff18c0631595db32eb17182872d88d9bf8ee4d8c2dd5db6d95e2", + "executor": null, + "isExecuted": false, + "isSuccessful": null, + "ethGasPrice": null, + "gasUsed": null, + "fee": null, + "origin": null, + "dataDecoded": null, + "confirmationsRequired": null, + "confirmations": [ + { + "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34", + "submissionDate": "2020-09-15T21:54:49.663299Z", + "transactionHash": null, + "confirmationType": "CONFIRMATION", + "signature": "0x95a7250bb645f831c86defc847350e7faff815b2fb586282568e96cc859e39315876db20a2eed5f7a0412906ec5ab57652a6f645ad4833f345bda059b9da2b821c", + "signatureType": "EOA" + } + ], + "signatures": null + } + ], + "id": 67 +} +``` + +Not all fields are required, though. This method is really just a UX helper, which massages the +input to conform to the `EIP-712` [specification](https://docs.gnosis.io/safe/docs/contracts_tx_execution/#transaction-hash) +for the Gnosis Safe, and making the output be directly importable to by a relay service. + + +### 6.0.0 + +* `New` was changed to deliver only an address, not the full `Account` data +* `Export` was moved from External API to the UI Server API + +#### 5.0.0 + +* The external `account_EcRecover`-method was reimplemented. +* The external method `account_sign(address, data)` was replaced with `account_signData(contentType, address, data)`. +The addition of `contentType` makes it possible to use the method for different types of objects, such as: + * signing data with an intended validator (not yet implemented) + * signing clique headers, + * signing plain personal messages, +* The external method `account_signTypedData` implements [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data. + +#### 4.0.0 + +* The external `account_Ecrecover`-method was removed. +* The external `account_Import`-method was removed. + +#### 3.0.0 + +* The external `account_List`-method was changed to not expose `url`, which contained info about the local filesystem. It now returns only a list of addresses. + +#### 2.0.0 + +* Commit `73abaf04b1372fa4c43201fb1b8019fe6b0a6f8d`, move `from` into `transaction` object in `signTransaction`. This +makes the `accounts_signTransaction` identical to the old `eth_signTransaction`. + + +#### 1.0.0 + +Initial release. diff --git a/cmd/clef/intapi_changelog.md b/cmd/clef/intapi_changelog.md new file mode 100644 index 0000000..eaeb2e6 --- /dev/null +++ b/cmd/clef/intapi_changelog.md @@ -0,0 +1,191 @@ +## Changelog for internal API (ui-api) + +The API uses [semantic versioning](https://semver.org/). + +TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the: + +* MAJOR version when you make incompatible API changes, +* MINOR version when you add functionality in a backwards-compatible manner, and +* PATCH version when you make backwards-compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. + +### 7.0.1 + +Added `clef_New` to the internal API callable from a UI. + +> `New` creates a new password protected Account. The private key is protected with +> the given password. Users are responsible to backup the private key that is stored +> in the keystore location that was specified when this API was created. +> This method is the same as New on the external API, the difference being that +> this implementation does not ask for confirmation, since it's initiated by +> the user + +### 7.0.0 + +- The `message` field was renamed to `messages` in all data signing request methods to better reflect that it's a list, not a value. +- The `storage.Put` and `storage.Get` methods in the rule execution engine were lower-cased to `storage.put` and `storage.get` to be consistent with JavaScript call conventions. + +### 6.0.0 + +Removed `password` from responses to operations which require them. This is for two reasons, + +- Consistency between how rulesets operate and how manual processing works. A rule can `Approve` but require the actual password to be stored in the clef storage. +With this change, the same stored password can be used even if rulesets are not enabled, but storage is. +- It also removes the usability-shortcut that a UI might otherwise want to implement; remembering passwords. Since we now will not require the +password on every `Approve`, there's no need for the UI to cache it locally. + - In a future update, we'll likely add `clef_storePassword` to the internal API, so the user can store it via his UI (currently only CLI works). + +Affected datatypes: +- `SignTxResponse` +- `SignDataResponse` +- `NewAccountResponse` + +If `clef` requires a password, the `OnInputRequired` will be used to collect it. + + +### 5.0.0 + +Changed the namespace format to adhere to the legacy ethereum format: `name_methodName`. Changes: + +* `ApproveTx` -> `ui_approveTx` +* `ApproveSignData` -> `ui_approveSignData` +* `ApproveExport` -> `removed` +* `ApproveImport` -> `removed` +* `ApproveListing` -> `ui_approveListing` +* `ApproveNewAccount` -> `ui_approveNewAccount` +* `ShowError` -> `ui_showError` +* `ShowInfo` -> `ui_showInfo` +* `OnApprovedTx` -> `ui_onApprovedTx` +* `OnSignerStartup` -> `ui_onSignerStartup` +* `OnInputRequired` -> `ui_onInputRequired` + + +### 4.0.0 + +* Bidirectional communication implemented, so the UI can query `clef` via the stdin/stdout RPC channel. Methods implemented are: + - `clef_listWallets` + - `clef_listAccounts` + - `clef_listWallets` + - `clef_deriveAccount` + - `clef_importRawKey` + - `clef_openWallet` + - `clef_chainId` + - `clef_setChainId` + - `clef_export` + - `clef_import` + +* The type `Account` was modified (the json-field `type` was removed), to consist of + +```go +type Account struct { + Address common.Address `json:"address"` // Ethereum account address derived from the key + URL URL `json:"url"` // Optional resource locator within a backend +} +``` + + +### 3.2.0 + +* Make `ShowError`, `OnApprovedTx`, `OnSignerStartup` be json-rpc [notifications](https://www.jsonrpc.org/specification#notification): + +> A Notification is a Request object without an "id" member. A Request object that is a Notification signifies the Client's lack of interest in the corresponding Response object, and as such no Response object needs to be returned to the client. The Server MUST NOT reply to a Notification, including those that are within a batch request. +> +> Notifications are not confirmable by definition, since they do not have a Response object to be returned. As such, the Client would not be aware of any errors (like e.g. "Invalid params","Internal error" +### 3.1.0 + +* Add `ContentType` `string` to `SignDataRequest` to accommodate the latest EIP-191 and EIP-712 implementations. + +### 3.0.0 + +* Make use of `OnInputRequired(info UserInputRequest)` for obtaining master password during startup + +### 2.1.0 + +* Add `OnInputRequired(info UserInputRequest)` to internal API. This method is used when Clef needs user input, e.g. passwords. + +The following structures are used: + +```go +UserInputRequest struct { + Prompt string `json:"prompt"` + Title string `json:"title"` + IsPassword bool `json:"isPassword"` +} +UserInputResponse struct { + Text string `json:"text"` +} +``` + +### 2.0.0 + +* Modify how `call_info` on a transaction is conveyed. New format: + +``` +{ + "jsonrpc": "2.0", + "id": 2, + "method": "ApproveTx", + "params": [ + { + "transaction": { + "from": "0x82A2A876D39022B3019932D30Cd9c97ad5616813", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "gas": "0x333", + "gasPrice": "0x123", + "value": "0x10", + "nonce": "0x0", + "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "input": null + }, + "call_info": [ + { + "type": "WARNING", + "message": "Invalid checksum on to-address" + }, + { + "type": "WARNING", + "message": "Tx contains data, but provided ABI signature could not be matched: Did not match: test (0 matches)" + } + ], + "meta": { + "remote": "127.0.0.1:54286", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + } + ] +} +``` + +#### 1.2.0 + +* Add `OnStartup` method, to provide the UI with information about what API version +the signer uses (both internal and external) as well as build-info and external api. + +Example call: +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "OnSignerStartup", + "params": [ + { + "info": { + "extapi_http": "http://localhost:8550", + "extapi_ipc": null, + "extapi_version": "2.0.0", + "intapi_version": "1.2.0" + } + } + ] +} +``` + +#### 1.1.0 + +* Add `OnApproved` method + +#### 1.0.0 + +Initial release. diff --git a/cmd/clef/main.go b/cmd/clef/main.go new file mode 100644 index 0000000..88d4c99 --- /dev/null +++ b/cmd/clef/main.go @@ -0,0 +1,1224 @@ +// Copyright 2018 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" + "context" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net" + "os" + "os/signal" + "path/filepath" + "runtime" + "strings" + "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/common/hexutil" + "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/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/signer/core" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/ethereum/go-ethereum/signer/fourbyte" + "github.com/ethereum/go-ethereum/signer/rules" + "github.com/ethereum/go-ethereum/signer/storage" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" + "github.com/urfave/cli/v2" +) + +const legalWarning = ` +WARNING! + +Clef is an account management tool. It may, like any software, contain bugs. + +Please take care to +- backup your keystore files, +- verify that the keystore(s) can be opened with your password. + +Clef is distributed in the hope that 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. +` + +var ( + logLevelFlag = &cli.IntFlag{ + Name: "loglevel", + Value: 3, + Usage: "log level to emit to the screen", + } + advancedMode = &cli.BoolFlag{ + Name: "advanced", + Usage: "If enabled, issues warnings instead of rejections for suspicious requests. Default off", + } + acceptFlag = &cli.BoolFlag{ + Name: "suppress-bootwarn", + Usage: "If set, does not show the warning during boot", + } + keystoreFlag = &cli.StringFlag{ + Name: "keystore", + Value: filepath.Join(node.DefaultDataDir(), "keystore"), + Usage: "Directory for the keystore", + } + configdirFlag = &cli.StringFlag{ + Name: "configdir", + Value: DefaultConfigDir(), + Usage: "Directory for Clef configuration", + } + chainIdFlag = &cli.Int64Flag{ + Name: "chainid", + Value: params.MainnetChainConfig.ChainID.Int64(), + Usage: "Chain id to use for signing (1=mainnet, 5=Goerli)", + } + rpcPortFlag = &cli.IntFlag{ + Name: "http.port", + Usage: "HTTP-RPC server listening port", + Value: node.DefaultHTTPPort + 5, + Category: flags.APICategory, + } + signerSecretFlag = &cli.StringFlag{ + Name: "signersecret", + Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash", + } + customDBFlag = &cli.StringFlag{ + Name: "4bytedb-custom", + Usage: "File used for writing new 4byte-identifiers submitted via API", + Value: "./4byte-custom.json", + } + auditLogFlag = &cli.StringFlag{ + Name: "auditlog", + Usage: "File used to emit audit logs. Set to \"\" to disable", + Value: "audit.log", + } + ruleFlag = &cli.StringFlag{ + Name: "rules", + Usage: "Path to the rule file to auto-authorize requests with", + } + stdiouiFlag = &cli.BoolFlag{ + Name: "stdio-ui", + Usage: "Use STDIN/STDOUT as a channel for an external UI. " + + "This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " + + "interface, and can be used when Clef is started by an external process.", + } + testFlag = &cli.BoolFlag{ + Name: "stdio-ui-test", + Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.", + } + initCommand = &cli.Command{ + Action: initializeSecrets, + Name: "init", + Usage: "Initialize the signer, generate secret storage", + ArgsUsage: "", + Flags: []cli.Flag{ + logLevelFlag, + configdirFlag, + }, + Description: ` +The init command generates a master seed which Clef can use to store credentials and data needed for +the rule-engine to work.`, + } + attestCommand = &cli.Command{ + Action: attestFile, + Name: "attest", + Usage: "Attest that a js-file is to be used", + ArgsUsage: "", + Flags: []cli.Flag{ + logLevelFlag, + configdirFlag, + signerSecretFlag, + }, + Description: ` +The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of +incoming requests. + +Whenever you make an edit to the rule file, you need to use attestation to tell +Clef that the file is 'safe' to execute.`, + } + setCredentialCommand = &cli.Command{ + Action: setCredential, + Name: "setpw", + Usage: "Store a credential for a keystore file", + ArgsUsage: "

", + Flags: []cli.Flag{ + logLevelFlag, + configdirFlag, + signerSecretFlag, + }, + Description: ` +The setpw command stores a password for a given address (keyfile). +`} + delCredentialCommand = &cli.Command{ + Action: removeCredential, + Name: "delpw", + Usage: "Remove a credential for a keystore file", + ArgsUsage: "
", + Flags: []cli.Flag{ + logLevelFlag, + configdirFlag, + signerSecretFlag, + }, + Description: ` +The delpw command removes a password for a given address (keyfile). +`} + newAccountCommand = &cli.Command{ + Action: newAccount, + Name: "newaccount", + Usage: "Create a new account", + ArgsUsage: "", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + utils.LightKDFFlag, + acceptFlag, + }, + Description: ` +The newaccount command creates a new keystore-backed account. It is a convenience-method +which can be used in lieu of an external UI. +`} + gendocCommand = &cli.Command{ + Action: GenDoc, + Name: "gendoc", + Usage: "Generate documentation about json-rpc format", + Description: ` +The gendoc generates example structures of the json-rpc communication types. +`} + listAccountsCommand = &cli.Command{ + Action: listAccounts, + Name: "list-accounts", + Usage: "List accounts in the keystore", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + utils.LightKDFFlag, + acceptFlag, + }, + Description: ` + Lists the accounts in the keystore. + `} + listWalletsCommand = &cli.Command{ + Action: listWallets, + Name: "list-wallets", + Usage: "List wallets known to Clef", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + utils.LightKDFFlag, + acceptFlag, + }, + Description: ` + Lists the wallets known to Clef. + `} + importRawCommand = &cli.Command{ + Action: accountImport, + Name: "importraw", + Usage: "Import a hex-encoded private key.", + ArgsUsage: "", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + utils.LightKDFFlag, + acceptFlag, + }, + Description: ` +Imports an unencrypted private key from and creates a new account. +Prints the address. +The keyfile is assumed to contain an unencrypted private key in hexadecimal format. +The account is saved in encrypted format, you are prompted for a password. +`} +) + +var app = flags.NewApp("Manage Ethereum account operations") + +func init() { + app.Name = "Clef" + app.Flags = []cli.Flag{ + logLevelFlag, + keystoreFlag, + configdirFlag, + chainIdFlag, + utils.LightKDFFlag, + utils.NoUSBFlag, + utils.SmartCardDaemonPathFlag, + utils.HTTPListenAddrFlag, + utils.HTTPVirtualHostsFlag, + utils.IPCDisabledFlag, + utils.IPCPathFlag, + utils.HTTPEnabledFlag, + rpcPortFlag, + signerSecretFlag, + customDBFlag, + auditLogFlag, + ruleFlag, + stdiouiFlag, + testFlag, + advancedMode, + acceptFlag, + } + app.Action = signer + app.Commands = []*cli.Command{initCommand, + attestCommand, + setCredentialCommand, + delCredentialCommand, + newAccountCommand, + importRawCommand, + gendocCommand, + listAccountsCommand, + listWalletsCommand, + } +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func initializeSecrets(c *cli.Context) error { + // Get past the legal message + if err := initialize(c); err != nil { + return err + } + // Ensure the master key does not yet exist, we're not willing to overwrite + configDir := c.String(configdirFlag.Name) + if err := os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) { + return err + } + location := filepath.Join(configDir, "masterseed.json") + if _, err := os.Stat(location); err == nil { + return fmt.Errorf("master key %v already exists, will not overwrite", location) + } + // Key file does not exist yet, generate a new one and encrypt it + masterSeed := make([]byte, 256) + num, err := io.ReadFull(rand.Reader, masterSeed) + if err != nil { + return err + } + if num != len(masterSeed) { + return errors.New("failed to read enough random") + } + n, p := keystore.StandardScryptN, keystore.StandardScryptP + if c.Bool(utils.LightKDFFlag.Name) { + n, p = keystore.LightScryptN, keystore.LightScryptP + } + text := "The master seed of clef will be locked with a password.\nPlease specify a password. Do not forget this password!" + var password string + for { + password = utils.GetPassPhrase(text, true) + if err := core.ValidatePasswordFormat(password); err != nil { + fmt.Printf("invalid password: %v\n", err) + } else { + fmt.Println() + break + } + } + cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p) + if err != nil { + return fmt.Errorf("failed to encrypt master seed: %v", err) + } + // Double check the master key path to ensure nothing wrote there in between + if err = os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) { + return err + } + if _, err := os.Stat(location); err == nil { + return fmt.Errorf("master key %v already exists, will not overwrite", location) + } + // Write the file and print the usual warning message + if err = os.WriteFile(location, cipherSeed, 0400); err != nil { + return err + } + fmt.Printf("A master seed has been generated into %s\n", location) + fmt.Printf(` +This is required to be able to store credentials, such as: +* Passwords for keystores (used by rule engine) +* Storage for JavaScript auto-signing rules +* Hash of JavaScript rule-file + +You should treat 'masterseed.json' with utmost secrecy and make a backup of it! +* The password is necessary but not enough, you need to back up the master seed too! +* The master seed does not contain your accounts, those need to be backed up separately! + +`) + return nil +} + +func attestFile(ctx *cli.Context) error { + if ctx.NArg() < 1 { + utils.Fatalf("This command requires an argument.") + } + if err := initialize(ctx); err != nil { + return err + } + + stretchedKey, err := readMasterKey(ctx, nil) + if err != nil { + utils.Fatalf(err.Error()) + } + configDir := ctx.String(configdirFlag.Name) + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) + confKey := crypto.Keccak256([]byte("config"), stretchedKey) + + // Initialize the encrypted storages + configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey) + val := ctx.Args().First() + configStorage.Put("ruleset_sha256", val) + log.Info("Ruleset attestation updated", "sha256", val) + return nil +} + +func initInternalApi(c *cli.Context) (*core.UIServerAPI, core.UIClientAPI, error) { + if err := initialize(c); err != nil { + return nil, nil, err + } + var ( + ui = core.NewCommandlineUI() + pwStorage storage.Storage = &storage.NoStorage{} + ksLoc = c.String(keystoreFlag.Name) + lightKdf = c.Bool(utils.LightKDFFlag.Name) + ) + am := core.StartClefAccountManager(ksLoc, true, lightKdf, "") + api := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage) + internalApi := core.NewUIServerAPI(api) + return internalApi, ui, nil +} + +func setCredential(ctx *cli.Context) error { + if ctx.NArg() < 1 { + utils.Fatalf("This command requires an address to be passed as an argument") + } + if err := initialize(ctx); err != nil { + return err + } + addr := ctx.Args().First() + if !common.IsHexAddress(addr) { + utils.Fatalf("Invalid address specified: %s", addr) + } + address := common.HexToAddress(addr) + password := utils.GetPassPhrase("Please enter a password to store for this address:", true) + fmt.Println() + + stretchedKey, err := readMasterKey(ctx, nil) + if err != nil { + utils.Fatalf(err.Error()) + } + configDir := ctx.String(configdirFlag.Name) + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) + pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) + + pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) + pwStorage.Put(address.Hex(), password) + + log.Info("Credential store updated", "set", address) + return nil +} + +func removeCredential(ctx *cli.Context) error { + if ctx.NArg() < 1 { + utils.Fatalf("This command requires an address to be passed as an argument") + } + if err := initialize(ctx); err != nil { + return err + } + addr := ctx.Args().First() + if !common.IsHexAddress(addr) { + utils.Fatalf("Invalid address specified: %s", addr) + } + address := common.HexToAddress(addr) + + stretchedKey, err := readMasterKey(ctx, nil) + if err != nil { + utils.Fatalf(err.Error()) + } + configDir := ctx.String(configdirFlag.Name) + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) + pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) + + pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) + pwStorage.Del(address.Hex()) + + log.Info("Credential store updated", "unset", address) + return nil +} + +func initialize(c *cli.Context) error { + // Set up the logger to print everything + logOutput := os.Stdout + if c.Bool(stdiouiFlag.Name) { + logOutput = os.Stderr + // If using the stdioui, we can't do the 'confirm'-flow + if !c.Bool(acceptFlag.Name) { + fmt.Fprint(logOutput, legalWarning) + } + } else if !c.Bool(acceptFlag.Name) { + if !confirm(legalWarning) { + return errors.New("aborted by user") + } + fmt.Println() + } + usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" + output := io.Writer(logOutput) + if usecolor { + output = colorable.NewColorable(logOutput) + } + verbosity := log.FromLegacyLevel(c.Int(logLevelFlag.Name)) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(output, verbosity, usecolor))) + + return nil +} + +func newAccount(c *cli.Context) error { + internalApi, _, err := initInternalApi(c) + if err != nil { + return err + } + addr, err := internalApi.New(context.Background()) + if err == nil { + fmt.Printf("Generated account %v\n", addr.String()) + } + return err +} + +func listAccounts(c *cli.Context) error { + internalApi, _, err := initInternalApi(c) + if err != nil { + return err + } + accs, err := internalApi.ListAccounts(context.Background()) + if err != nil { + return err + } + if len(accs) == 0 { + fmt.Println("\nThe keystore is empty.") + } + fmt.Println() + for _, account := range accs { + fmt.Printf("%v (%v)\n", account.Address, account.URL) + } + return err +} + +func listWallets(c *cli.Context) error { + internalApi, _, err := initInternalApi(c) + if err != nil { + return err + } + wallets := internalApi.ListWallets() + if len(wallets) == 0 { + fmt.Println("\nThere are no wallets.") + } + fmt.Println() + for i, wallet := range wallets { + fmt.Printf("- Wallet %d at %v (%v %v)\n", i, wallet.URL, wallet.Status, wallet.Failure) + for j, acc := range wallet.Accounts { + fmt.Printf(" -Account %d: %v (%v)\n", j, acc.Address, acc.URL) + } + fmt.Println() + } + return nil +} + +// accountImport imports a raw hexadecimal private key via CLI. +func accountImport(c *cli.Context) error { + if c.Args().Len() != 1 { + return errors.New(" must be given as first argument") + } + internalApi, ui, err := initInternalApi(c) + if err != nil { + return err + } + pKey, err := crypto.LoadECDSA(c.Args().First()) + if err != nil { + return err + } + readPw := func(prompt string) (string, error) { + resp, err := ui.OnInputRequired(core.UserInputRequest{ + Title: "Password", + Prompt: prompt, + IsPassword: true, + }) + if err != nil { + return "", err + } + return resp.Text, nil + } + first, err := readPw("Please enter a password for the imported account") + if err != nil { + return err + } + second, err := readPw("Please repeat the password you just entered") + if err != nil { + 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) + if err != nil { + return err + } + ui.ShowInfo(fmt.Sprintf(`Key imported: + Address %v + Keystore file: %v + +The key is now encrypted; losing the password will result in permanently losing +access to the key and all associated funds! + +Make sure to backup keystore and passwords in a safe location.`, + acc.Address, acc.URL.Path)) + return nil +} + +// 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 +} + +func signer(c *cli.Context) error { + // If we have some unrecognized command, bail out + if c.NArg() > 0 { + return fmt.Errorf("invalid command: %q", c.Args().First()) + } + if err := initialize(c); err != nil { + return err + } + var ( + ui core.UIClientAPI + ) + if c.Bool(stdiouiFlag.Name) { + log.Info("Using stdin/stdout as UI-channel") + ui = core.NewStdIOUI() + } else { + log.Info("Using CLI as UI-channel") + ui = core.NewCommandlineUI() + } + // 4bytedb data + fourByteLocal := c.String(customDBFlag.Name) + db, err := fourbyte.NewWithFile(fourByteLocal) + if err != nil { + utils.Fatalf(err.Error()) + } + embeds, locals := db.Size() + log.Info("Loaded 4byte database", "embeds", embeds, "locals", locals, "local", fourByteLocal) + + var ( + api core.ExternalAPI + pwStorage storage.Storage = &storage.NoStorage{} + ) + configDir := c.String(configdirFlag.Name) + if stretchedKey, err := readMasterKey(c, ui); err != nil { + log.Warn("Failed to open master, rules disabled", "err", err) + } else { + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) + + // Generate domain specific keys + pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) + jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey) + confkey := crypto.Keccak256([]byte("config"), stretchedKey) + + // Initialize the encrypted storages + pwStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) + jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey) + configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey) + + // Do we have a rule-file? + if ruleFile := c.String(ruleFlag.Name); ruleFile != "" { + ruleJS, err := os.ReadFile(ruleFile) + if err != nil { + log.Warn("Could not load rules, disabling", "file", ruleFile, "err", err) + } else { + shasum := sha256.Sum256(ruleJS) + foundShaSum := hex.EncodeToString(shasum[:]) + storedShasum, _ := configStorage.Get("ruleset_sha256") + if storedShasum != foundShaSum { + log.Warn("Rule hash not attested, disabling", "hash", foundShaSum, "attested", storedShasum) + } else { + // Initialize rules + ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage) + if err != nil { + utils.Fatalf(err.Error()) + } + ruleEngine.Init(string(ruleJS)) + ui = ruleEngine + log.Info("Rule engine configured", "file", c.String(ruleFlag.Name)) + } + } + } + } + var ( + chainId = c.Int64(chainIdFlag.Name) + ksLoc = c.String(keystoreFlag.Name) + lightKdf = c.Bool(utils.LightKDFFlag.Name) + advanced = c.Bool(advancedMode.Name) + nousb = c.Bool(utils.NoUSBFlag.Name) + scpath = c.String(utils.SmartCardDaemonPathFlag.Name) + ) + 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 + // it with the UI. + ui.RegisterUIServer(core.NewUIServerAPI(apiImpl)) + api = apiImpl + + // Audit logging + if logfile := c.String(auditLogFlag.Name); logfile != "" { + api, err = core.NewAuditLogger(logfile, api) + if err != nil { + utils.Fatalf(err.Error()) + } + log.Info("Audit logs configured", "file", logfile) + } + // register signer API with server + var ( + extapiURL = "n/a" + ipcapiURL = "n/a" + ) + rpcAPI := []rpc.API{ + { + Namespace: "account", + Service: api, + }, + } + if c.Bool(utils.HTTPEnabledFlag.Name) { + vhosts := utils.SplitAndTrim(c.String(utils.HTTPVirtualHostsFlag.Name)) + cors := utils.SplitAndTrim(c.String(utils.HTTPCORSDomainFlag.Name)) + + srv := rpc.NewServer() + srv.SetBatchLimits(node.DefaultConfig.BatchRequestLimit, node.DefaultConfig.BatchResponseMaxSize) + err := node.RegisterApis(rpcAPI, []string{"account"}, srv) + if err != nil { + utils.Fatalf("Could not register API: %w", err) + } + handler := node.NewHTTPHandlerStack(srv, cors, vhosts, nil) + + // set port + port := c.Int(rpcPortFlag.Name) + + // start http server + httpEndpoint := net.JoinHostPort(c.String(utils.HTTPListenAddrFlag.Name), fmt.Sprintf("%d", port)) + httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) + if err != nil { + utils.Fatalf("Could not start RPC api: %v", err) + } + extapiURL = fmt.Sprintf("http://%v/", addr) + log.Info("HTTP endpoint opened", "url", extapiURL) + + defer func() { + // Don't bother imposing a timeout here. + httpServer.Shutdown(context.Background()) + log.Info("HTTP endpoint closed", "url", extapiURL) + }() + } + if !c.Bool(utils.IPCDisabledFlag.Name) { + givenPath := c.String(utils.IPCPathFlag.Name) + ipcapiURL = ipcEndpoint(filepath.Join(givenPath, "clef.ipc"), configDir) + listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI) + if err != nil { + utils.Fatalf("Could not start IPC api: %v", err) + } + log.Info("IPC endpoint opened", "url", ipcapiURL) + defer func() { + listener.Close() + log.Info("IPC endpoint closed", "url", ipcapiURL) + }() + } + if c.Bool(testFlag.Name) { + log.Info("Performing UI test") + go testExternalUI(apiImpl) + } + ui.OnSignerStartup(core.StartupInfo{ + Info: map[string]interface{}{ + "intapi_version": core.InternalAPIVersion, + "extapi_version": core.ExternalAPIVersion, + "extapi_http": extapiURL, + "extapi_ipc": ipcapiURL, + }}) + + abortChan := make(chan os.Signal, 1) + signal.Notify(abortChan, os.Interrupt) + + sig := <-abortChan + log.Info("Exiting...", "signal", sig) + + return nil +} + +// DefaultConfigDir is the default config directory to use for the vaults and other +// persistence requirements. +func DefaultConfigDir() string { + // Try to place the data folder in the user's home dir + home := flags.HomeDir() + if home != "" { + if runtime.GOOS == "darwin" { + return filepath.Join(home, "Library", "Signer") + } else if runtime.GOOS == "windows" { + appdata := os.Getenv("APPDATA") + if appdata != "" { + return filepath.Join(appdata, "Signer") + } + return filepath.Join(home, "AppData", "Roaming", "Signer") + } + return filepath.Join(home, ".clef") + } + // As we cannot guess a stable location, return empty and handle later + return "" +} + +func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { + var ( + file string + configDir = ctx.String(configdirFlag.Name) + ) + if ctx.IsSet(signerSecretFlag.Name) { + file = ctx.String(signerSecretFlag.Name) + } else { + file = filepath.Join(configDir, "masterseed.json") + } + if err := checkFile(file); err != nil { + return nil, err + } + cipherKey, err := os.ReadFile(file) + if err != nil { + return nil, err + } + var password string + // If ui is not nil, get the password from ui. + if ui != nil { + resp, err := ui.OnInputRequired(core.UserInputRequest{ + Title: "Master Password", + Prompt: "Please enter the password to decrypt the master seed", + IsPassword: true}) + if err != nil { + return nil, err + } + password = resp.Text + } else { + password = utils.GetPassPhrase("Decrypt master seed of clef", false) + } + masterSeed, err := decryptSeed(cipherKey, password) + if err != nil { + return nil, errors.New("failed to decrypt the master seed of clef") + } + if len(masterSeed) < 256 { + return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed)) + } + // Create vault location + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10])) + err = os.Mkdir(vaultLocation, 0700) + if err != nil && !os.IsExist(err) { + return nil, err + } + return masterSeed, nil +} + +// checkFile is a convenience function to check if a file +// * exists +// * is mode 0400 (unix only) +func checkFile(filename string) error { + info, err := os.Stat(filename) + if err != nil { + return fmt.Errorf("failed stat on %s: %v", filename, err) + } + // Check the unix permission bits + // However, on windows, we cannot use the unix perm-bits, see + // https://github.com/ethereum/go-ethereum/issues/20123 + if runtime.GOOS != "windows" && info.Mode().Perm()&0377 != 0 { + return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) + } + return nil +} + +// confirm displays a text and asks for user confirmation +func confirm(text string) bool { + fmt.Print(text) + fmt.Printf("\nEnter 'ok' to proceed:\n> ") + + text, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + log.Crit("Failed to read user input", "err", err) + } + if text := strings.TrimSpace(text); text == "ok" { + return true + } + return false +} + +func testExternalUI(api *core.SignerAPI) { + ctx := context.WithValue(context.Background(), "remote", "clef binary") + ctx = context.WithValue(ctx, "scheme", "in-proc") + ctx = context.WithValue(ctx, "local", "main") + errs := make([]string, 0) + + a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef") + addErr := func(errStr string) { + log.Info("Test error", "err", errStr) + errs = append(errs, errStr) + } + + queryUser := func(q string) string { + resp, err := api.UI.OnInputRequired(core.UserInputRequest{ + Title: "Testing", + Prompt: q, + }) + if err != nil { + addErr(err.Error()) + } + return resp.Text + } + expectResponse := func(testcase, question, expect string) { + if got := queryUser(question); got != expect { + addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect)) + } + } + expectApprove := func(testcase string, err error) { + if err == nil || err == accounts.ErrUnknownAccount { + return + } + addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error())) + } + expectDeny := func(testcase string, err error) { + if err == nil || err != core.ErrRequestDenied { + addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err)) + } + } + var delay = 1 * time.Second + // Test display of info and error + { + api.UI.ShowInfo("If you see this message, enter 'yes' to next question") + time.Sleep(delay) + expectResponse("showinfo", "Did you see the message? [yes/no]", "yes") + api.UI.ShowError("If you see this message, enter 'yes' to the next question") + time.Sleep(delay) + expectResponse("showerror", "Did you see the message? [yes/no]", "yes") + } + { // Sign data test - clique header + api.UI.ShowInfo("Please approve the next request for signing a clique header") + time.Sleep(delay) + cliqueHeader := types.Header{ + ParentHash: common.HexToHash("0000H45H"), + UncleHash: common.HexToHash("0000H45H"), + Coinbase: common.HexToAddress("0000H45H"), + Root: common.HexToHash("0000H00H"), + TxHash: common.HexToHash("0000H45H"), + ReceiptHash: common.HexToHash("0000H45H"), + Difficulty: big.NewInt(1337), + Number: big.NewInt(1337), + GasLimit: 1338, + GasUsed: 1338, + Time: 1338, + Extra: []byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"), + MixDigest: common.HexToHash("0x0000H45H"), + } + cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader) + if err != nil { + utils.Fatalf("Should not error: %v", err) + } + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") + _, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp)) + expectApprove("signdata - clique header", err) + } + { // Sign data test - typed data + api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data") + time.Sleep(delay) + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") + data := `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}` + //_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data))) + var typedData apitypes.TypedData + json.Unmarshal([]byte(data), &typedData) + _, err := api.SignTypedData(ctx, *addr, typedData) + expectApprove("sign 712 typed data", err) + } + { // Sign data test - plain text + api.UI.ShowInfo("Please approve the next request for signing text") + time.Sleep(delay) + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") + _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) + expectApprove("signdata - text", err) + } + { // Sign data test - plain text reject + api.UI.ShowInfo("Please deny the next request for signing text") + time.Sleep(delay) + addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") + _, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world"))) + expectDeny("signdata - text", err) + } + { // Sign transaction + api.UI.ShowInfo("Please reject next transaction") + time.Sleep(delay) + data := hexutil.Bytes([]byte{}) + to := common.NewMixedcaseAddress(a) + tx := apitypes.SendTxArgs{ + Data: &data, + Nonce: 0x1, + Value: hexutil.Big(*big.NewInt(6)), + From: common.NewMixedcaseAddress(a), + To: &to, + GasPrice: (*hexutil.Big)(big.NewInt(5)), + Gas: 1000, + Input: nil, + } + _, err := api.SignTransaction(ctx, tx, nil) + expectDeny("signtransaction [1]", err) + expectResponse("signtransaction [2]", "Did you see any warnings for the last transaction? (yes/no)", "no") + } + { // Listing + api.UI.ShowInfo("Please reject listing-request") + time.Sleep(delay) + _, err := api.List(ctx) + expectDeny("list", err) + } + { // Import + api.UI.ShowInfo("Please reject new account-request") + time.Sleep(delay) + _, err := api.New(ctx) + expectDeny("newaccount", err) + } + { // Metadata + api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)") + time.Sleep(delay) + api.List(context.WithValue(ctx, "Origin", "origin.com")) + expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes") + } + + for _, e := range errs { + log.Error(e) + } + result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n")) + api.UI.ShowInfo(result) +} + +type encryptedSeedStorage struct { + Description string `json:"description"` + Version int `json:"version"` + Params keystore.CryptoJSON `json:"params"` +} + +// encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping, +// to encrypt the master seed +func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) { + cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP) + if err != nil { + return nil, err + } + return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct}) +} + +// decryptSeed decrypts the master seed +func decryptSeed(keyjson []byte, auth string) ([]byte, error) { + var encSeed encryptedSeedStorage + if err := json.Unmarshal(keyjson, &encSeed); err != nil { + return nil, err + } + if encSeed.Version != 1 { + log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version)) + } + seed, err := keystore.DecryptDataV3(encSeed.Params, auth) + if err != nil { + return nil, err + } + return seed, err +} + +// GenDoc outputs examples of all structures used in json-rpc communication +func GenDoc(ctx *cli.Context) error { + var ( + a = common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef") + b = common.HexToAddress("0x1111111122222222222233333333334444444444") + meta = core.Metadata{ + Scheme: "http", + Local: "localhost:8545", + Origin: "www.malicious.ru", + Remote: "localhost:9999", + UserAgent: "Firefox 3.2", + } + output []string + add = func(name, desc string, v interface{}) { + if data, err := json.MarshalIndent(v, "", " "); err == nil { + output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data)) + } else { + log.Error("Error generating output", "err", err) + } + } + ) + + { // Sign plain text request + desc := "SignDataRequest contains information about a pending request to sign some data. " + + "The data to be signed can be of various types, defined by content-type. Clef has done most " + + "of the work in canonicalizing and making sense of the data, and it's up to the UI to present" + + "the user with the contents of the `message`" + sighash, msg := accounts.TextAndHash([]byte("hello world")) + messages := []*apitypes.NameValueType{{Name: "message", Value: msg, Typ: accounts.MimetypeTextPlain}} + + add("SignDataRequest", desc, &core.SignDataRequest{ + Address: common.NewMixedcaseAddress(a), + Meta: meta, + ContentType: accounts.MimetypeTextPlain, + Rawdata: []byte(msg), + Messages: messages, + Hash: sighash}) + } + { // Sign plain text response + add("SignDataResponse - approve", "Response to SignDataRequest", + &core.SignDataResponse{Approved: true}) + add("SignDataResponse - deny", "Response to SignDataRequest", + &core.SignDataResponse{}) + } + { // Sign transaction request + desc := "SignTxRequest contains information about a pending request to sign a transaction. " + + "Aside from the transaction itself, there is also a `call_info`-struct. That struct contains " + + "messages of various types, that the user should be informed of." + + "\n\n" + + "As in any request, it's important to consider that the `meta` info also contains untrusted data." + + "\n\n" + + "The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, " + + "they must be identical, otherwise an error is generated. " + + "However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)" + + data := hexutil.Bytes([]byte{0x01, 0x02, 0x03, 0x04}) + add("SignTxRequest", desc, &core.SignTxRequest{ + Meta: meta, + Callinfo: []apitypes.ValidationInfo{ + {Typ: "Warning", Message: "Something looks odd, show this message as a warning"}, + {Typ: "Info", Message: "User should see this as well"}, + }, + Transaction: apitypes.SendTxArgs{ + Data: &data, + Nonce: 0x1, + Value: hexutil.Big(*big.NewInt(6)), + From: common.NewMixedcaseAddress(a), + To: nil, + GasPrice: (*hexutil.Big)(big.NewInt(5)), + Gas: 1000, + Input: nil, + }}) + } + { // Sign tx response + data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01}) + add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+ + ", because the UI is free to make modifications to the transaction.", + &core.SignTxResponse{Approved: true, + Transaction: apitypes.SendTxArgs{ + Data: &data, + Nonce: 0x4, + Value: hexutil.Big(*big.NewInt(6)), + From: common.NewMixedcaseAddress(a), + To: nil, + GasPrice: (*hexutil.Big)(big.NewInt(5)), + Gas: 1000, + Input: nil, + }}) + add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+ + "provide the transaction in return", + &core.SignTxResponse{}) + } + { // WHen a signed tx is ready to go out + desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" + + "\n\n" + + "This occurs _after_ successful completion of the entire signing procedure, but right before the signed " + + "transaction is passed to the external caller. This method (and data) can be used by the UI to signal " + + "to the user that the transaction was signed, but it is primarily useful for ruleset implementations." + + "\n\n" + + "A ruleset that implements a rate limitation needs to know what transactions are sent out to the external " + + "interface. By hooking into this methods, the ruleset can maintain track of that count." + + "\n\n" + + "**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time" + + " (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. " + + "\n\n" + + "The `OnApproved` method cannot be responded to, it's purely informative" + + rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed") + var tx types.Transaction + tx.UnmarshalBinary(rlpdata) + add("OnApproved - SignTransactionResult", desc, ðapi.SignTransactionResult{Raw: rlpdata, Tx: &tx}) + } + { // User input + add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)", + &core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"}) + add("UserInputResponse", "Response to UserInputRequest", + &core.UserInputResponse{Text: "The textual response from user"}) + } + { // List request + add("ListRequest", "Sent when a request has been made to list addresses. The UI is provided with the "+ + "full `account`s, including local directory names. Note: this information is not passed back to the external caller, "+ + "who only sees the `address`es. ", + &core.ListRequest{ + Meta: meta, + Accounts: []accounts.Account{ + {Address: a, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/a"}}, + {Address: b, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}}, + }) + + add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+ + "Note: the UI is free to respond with any address the caller, regardless of whether it exists or not", + &core.ListResponse{ + Accounts: []accounts.Account{ + { + Address: common.HexToAddress("0xcowbeef000000cowbeef00000000000000000c0w"), + URL: accounts.URL{Path: ".. ignored .."}, + }, + { + Address: common.MaxAddress, + }, + }}) + } + + fmt.Println(`## UI Client interface + +These data types are defined in the channel between clef and the UI`) + for _, elem := range output { + fmt.Println(elem) + } + return nil +} diff --git a/cmd/clef/pythonsigner.py b/cmd/clef/pythonsigner.py new file mode 100644 index 0000000..5d0eb18 --- /dev/null +++ b/cmd/clef/pythonsigner.py @@ -0,0 +1,315 @@ +import sys +import subprocess + +from tinyrpc.transports import ServerTransport +from tinyrpc.protocols.jsonrpc import JSONRPCProtocol +from tinyrpc.dispatch import public, RPCDispatcher +from tinyrpc.server import RPCServer + +""" +This is a POC example of how to write a custom UI for Clef. +The UI starts the clef process with the '--stdio-ui' option +and communicates with clef using standard input / output. + +The standard input/output is a relatively secure way to communicate, +as it does not require opening any ports or IPC files. Needless to say, +it does not protect against memory inspection mechanisms +where an attacker can access process memory. + +To make this work install all the requirements: + + pip install -r requirements.txt +""" + +try: + import urllib.parse as urlparse +except ImportError: + import urllib as urlparse + + +class StdIOTransport(ServerTransport): + """Uses std input/output for RPC""" + + def receive_message(self): + return None, urlparse.unquote(sys.stdin.readline()) + + def send_reply(self, context, reply): + print(reply) + + +class PipeTransport(ServerTransport): + """Uses std a pipe for RPC""" + + def __init__(self, input, output): + self.input = input + self.output = output + + def receive_message(self): + data = self.input.readline() + print(">> {}".format(data)) + return None, urlparse.unquote(data) + + def send_reply(self, context, reply): + reply = str(reply, "utf-8") + print("<< {}".format(reply)) + self.output.write("{}\n".format(reply)) + + +def sanitize(txt, limit=100): + return txt[:limit].encode("unicode_escape").decode("utf-8") + + +def metaString(meta): + """ + "meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""} + """ # noqa: E501 + message = ( + "\tRequest context:\n" + "\t\t{remote} -> {scheme} -> {local}\n" + "\tAdditional HTTP header data, provided by the external caller:\n" + "\t\tUser-Agent: {user_agent}\n" + "\t\tOrigin: {origin}\n" + ) + return message.format( + remote=meta.get("remote", ""), + scheme=meta.get("scheme", ""), + local=meta.get("local", ""), + user_agent=sanitize(meta.get("User-Agent"), 200), + origin=sanitize(meta.get("Origin"), 100), + ) + + +class StdIOHandler: + def __init__(self): + pass + + @public + def approveTx(self, req): + """ + Example request: + + {"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 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 + message = ( + "Sign transaction request:\n" + "\t{meta_string}\n" + "\n" + "\tFrom: {from_}\n" + "\tTo: {to}\n" + "\n" + "\tAuto-rejecting request" + ) + meta = req.get("meta", {}) + transaction = req.get("transaction") + sys.stdout.write( + message.format( + meta_string=metaString(meta), + from_=transaction.get("from", ""), + to=transaction.get("to", ""), + ) + ) + return { + "approved": False, + } + + @public + def approveSignData(self, req): + """ + Example request: + + {"jsonrpc":"2.0","id":8,"method":"ui_approveSignData","params":[{"content_type":"application/x-clique-header","address":"0x0011223344556677889900112233445566778899","raw_data":"+QIRoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIFOYIFOYIFOoIFOoIFOppFeHRyYSBkYXRhIEV4dHJhIGRhdGEgRXh0cqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAAAAAAAA==","messages":[{"name":"Clique header","value":"clique header 1337 [0x44381ab449d77774874aca34634cb53bc21bd22aef2d3d4cf40e51176cb585ec]","type":"clique"}],"call_info":null,"hash":"0xa47ab61438a12a06c81420e308c2b7aae44e9cd837a5df70dd021421c0f58643","meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]} + """ # noqa: E501 + message = ( + "Sign data request:\n" + "\t{meta_string}\n" + "\n" + "\tContent-type: {content_type}\n" + "\tAddress: {address}\n" + "\tHash: {hash_}\n" + "\n" + "\tAuto-rejecting request\n" + ) + meta = req.get("meta", {}) + sys.stdout.write( + message.format( + meta_string=metaString(meta), + content_type=req.get("content_type"), + address=req.get("address"), + hash_=req.get("hash"), + ) + ) + + return { + "approved": False, + "password": None, + } + + @public + def approveNewAccount(self, req): + """ + Example request: + + {"jsonrpc":"2.0","id":25,"method":"ui_approveNewAccount","params":[{"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]} + """ # noqa: E501 + message = ( + "Create new account request:\n" + "\t{meta_string}\n" + "\n" + "\tAuto-rejecting request\n" + ) + meta = req.get("meta", {}) + sys.stdout.write(message.format(meta_string=metaString(meta))) + return { + "approved": False, + } + + @public + def showError(self, req): + """ + Example request: + + {"jsonrpc":"2.0","method":"ui_showError","params":[{"text":"If you see this message, enter 'yes' to the next question"}]} + + :param message: to display + :return:nothing + """ # noqa: E501 + message = ( + "## Error\n{text}\n" + "Press enter to continue\n" + ) + text = req.get("text") + sys.stdout.write(message.format(text=text)) + input() + return + + @public + def showInfo(self, req): + """ + Example request: + + {"jsonrpc":"2.0","method":"ui_showInfo","params":[{"text":"If you see this message, enter 'yes' to next question"}]} + + :param message: to display + :return:nothing + """ # noqa: E501 + message = ( + "## Info\n{text}\n" + "Press enter to continue\n" + ) + text = req.get("text") + sys.stdout.write(message.format(text=text)) + input() + return + + @public + def onSignerStartup(self, req): + """ + Example request: + + {"jsonrpc":"2.0", "method":"ui_onSignerStartup", "params":[{"info":{"extapi_http":"n/a","extapi_ipc":"/home/user/.clef/clef.ipc","extapi_version":"6.1.0","intapi_version":"7.0.1"}}]} + """ # noqa: E501 + message = ( + "\n" + "\t\tExt api url: {extapi_http}\n" + "\t\tInt api ipc: {extapi_ipc}\n" + "\t\tExt api ver: {extapi_version}\n" + "\t\tInt api ver: {intapi_version}\n" + ) + info = req.get("info") + sys.stdout.write( + message.format( + extapi_http=info.get("extapi_http"), + extapi_ipc=info.get("extapi_ipc"), + extapi_version=info.get("extapi_version"), + intapi_version=info.get("intapi_version"), + ) + ) + + @public + def approveListing(self, req): + """ + Example request: + + {"jsonrpc":"2.0","id":23,"method":"ui_approveListing","params":[{"accounts":[{"address":... + """ # noqa: E501 + message = ( + "\n" + "## Account listing request\n" + "\t{meta_string}\n" + "\tDo you want to allow listing the following accounts?\n" + "\t-{addrs}\n" + "\n" + "->Auto-answering No\n" + ) + meta = req.get("meta", {}) + accounts = req.get("accounts", []) + addrs = [x.get("address") for x in accounts] + sys.stdout.write( + message.format( + addrs="\n\t-".join(addrs), + meta_string=metaString(meta) + ) + ) + return {} + + @public + def onInputRequired(self, req): + """ + Example request: + + {"jsonrpc":"2.0","id":1,"method":"ui_onInputRequired","params":[{"title":"Master Password","prompt":"Please enter the password to decrypt the master seed","isPassword":true}]} + + :param message: to display + :return:nothing + """ # noqa: E501 + message = ( + "\n" + "## {title}\n" + "\t{prompt}\n" + "\n" + "> " + ) + sys.stdout.write( + message.format( + title=req.get("title"), + prompt=req.get("prompt") + ) + ) + isPassword = req.get("isPassword") + if not isPassword: + return {"text": input()} + + return "" + + +def main(args): + cmd = ["clef", "--stdio-ui"] + if len(args) > 0 and args[0] == "test": + cmd.extend(["--stdio-ui-test"]) + print("cmd: {}".format(" ".join(cmd))) + + dispatcher = RPCDispatcher() + dispatcher.register_instance(StdIOHandler(), "ui_") + + # line buffered + p = subprocess.Popen( + cmd, + bufsize=1, + universal_newlines=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + + rpc_server = RPCServer( + PipeTransport(p.stdout, p.stdin), JSONRPCProtocol(), dispatcher + ) + rpc_server.serve_forever() + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/cmd/clef/requirements.txt b/cmd/clef/requirements.txt new file mode 100644 index 0000000..5381862 --- /dev/null +++ b/cmd/clef/requirements.txt @@ -0,0 +1 @@ +tinyrpc==1.1.4 diff --git a/cmd/clef/rules.md b/cmd/clef/rules.md new file mode 100644 index 0000000..cc4a645 --- /dev/null +++ b/cmd/clef/rules.md @@ -0,0 +1,234 @@ +# Rules + +The `signer` binary contains a ruleset engine, implemented with [OttoVM](https://github.com/robertkrimen/otto) + +It enables usecases like the following: + +* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period +* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei` + +The two main features that are required for this to work well are; + +1. Rule Implementation: how to create, manage, and interpret rules in a flexible but secure manner +2. Credential management and credentials; how to provide auto-unlock without exposing keys unnecessarily. + +The section below deals with both of them + +## Rule Implementation + +A ruleset file is implemented as a `js` file. Under the hood, the ruleset engine is a `SignerUI`, implementing the same methods as the `json-rpc` methods +defined in the UI protocol. Example: + +```js +function asBig(str) { + if (str.slice(0, 2) == "0x") { + return new BigNumber(str.slice(2), 16) + } + return new BigNumber(str) +} + +// Approve transactions to a certain contract if the value is below a certain limit +function ApproveTx(req) { + var limit = big.Newint("0xb1a2bc2ec50000") + var value = asBig(req.transaction.value); + + if (req.transaction.to.toLowerCase() == "0xae967917c465db8578ca9024c205720b1a3651a9") && value.lt(limit)) { + return "Approve" + } + // If we return "Reject", it will be rejected. + // By not returning anything, it will be passed to the next UI, for manual processing +} + +// Approve listings if request made from IPC +function ApproveListing(req){ + if (req.metadata.scheme == "ipc"){ return "Approve"} +} +``` + +Whenever the external API is called (and the ruleset is enabled), the `signer` calls the UI, which is an instance of a ruleset-engine. The ruleset-engine +invokes the corresponding method. In doing so, there are three possible outcomes: + +1. JS returns "Approve" + * Auto-approve request +2. JS returns "Reject" + * Auto-reject request +3. Error occurs, or something else is returned + * Pass on to `next` ui: the regular UI channel. + +A more advanced example can be found below, "Example 1: ruleset for a rate-limited window", using `storage` to `Put` and `Get` `string`s by key. + +* At the time of writing, storage only exists as an ephemeral unencrypted implementation, to be used during testing. + +### Things to note + +The Otto vm has a few [caveats](https://github.com/robertkrimen/otto): + +* "use strict" will parse, but does nothing. +* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification. +* Otto targets ES5. ES6 features (eg: Typed Arrays) are not supported. + +Additionally, a few more have been added + +* The rule execution cannot load external javascript files. +* The only preloaded library is [`bignumber.js`](https://github.com/MikeMcl/bignumber.js) version `2.0.3`. This one is fairly old, and is not aligned with the documentation at the GitHub repository. +* Each invocation is made in a fresh virtual machine. This means that you cannot store data in global variables between invocations. This is a deliberate choice -- if you want to store data, use the disk-backed `storage`, since rules should not rely on ephemeral data. +* Javascript API parameters are _always_ an object. This is also a design choice, to ensure that parameters are accessed by _key_ and not by order. This is to prevent mistakes due to missing parameters or parameter changes. +* The JS engine has access to `storage` and `console`. + +#### Security considerations + +##### Security of ruleset + +Some security precautions can be made, such as: + +* Never load `ruleset.js` unless the file is `readonly` (`r-??-??-?`). If the user wishes to modify the ruleset, he must make it writeable and then set back to readonly. + * This is to prevent attacks where files are dropped on the users disk. +* Since we're going to have to have some form of secure storage (not defined in this section), we could also store the `sha3` of the `ruleset.js` file in there. + * If the user wishes to modify the ruleset, he'd then have to perform e.g. `signer --attest /path/to/ruleset --credential ` + +##### Security of implementation + +The drawback of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement since it's already +implemented for `geth`. There are no known security vulnerabilities in it, nor have we had any security problems with it so far. + +The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered +an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit +to be gained from attacking the actual `signer` process from the `js` side would be if it could somehow extract cryptographic keys from memory. + +##### Security in usability + +Javascript is flexible, but also easy to get wrong, especially when users assume that `js` can handle large integers natively. Typical errors +include trying to multiply `gasCost` with `gas` without using `bigint`:s. + +It's unclear whether any other DSL could be more secure; since there's always the possibility of erroneously implementing a rule. + + +## Credential management + +The ability to auto-approve transactions means that the signer needs to have the necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass). + +### Example implementation + +Upon startup of the signer, the signer is given a switch: `--seed ` +The `seed` contains a blob of bytes, which is the master seed for the `signer`. + +The `signer` uses the `seed` to: + +* Generate the `path` where the settings are stored. + * `./settings/1df094eb-c2b1-4689-90dd-790046d38025/vault.dat` + * `./settings/1df094eb-c2b1-4689-90dd-790046d38025/rules.js` +* Generate the encryption password for `vault.dat`. + +The `vault.dat` would be an encrypted container storing the following information: + +* `ksp` entries +* `sha256` hash of `rules.js` +* Information about pair:ed callers (not yet specified) + +### Security considerations + +This would leave it up to the user to ensure that the `path/to/masterseed` is handled securely. It's difficult to get around this, although one could +imagine leveraging OS-level keychains where supported. The setup is however, in general, similar to how ssh-keys are stored in `.ssh/`. + + +# Implementation status + +This is now implemented (with ephemeral non-encrypted storage for now, so not yet enabled). + +## Example 1: ruleset for a rate-limited window + + +```js +function big(str) { + if (str.slice(0, 2) == "0x") { + return new BigNumber(str.slice(2), 16) + } + return new BigNumber(str) +} + +// Time window: 1 week +var window = 1000* 3600*24*7; + +// Limit: 1 ether +var limit = new BigNumber("1e18"); + +function isLimitOk(transaction) { + var value = big(transaction.value) + // Start of our window function + var windowstart = new Date().getTime() - window; + + var txs = []; + var stored = storage.get('txs'); + + if (stored != "") { + txs = JSON.parse(stored) + } + // First, remove all that has passed out of the time window + var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart}); + console.log(txs, newtxs.length); + + // Secondly, aggregate the current sum + sum = new BigNumber(0) + + sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum); + console.log("ApproveTx > Sum so far", sum); + console.log("ApproveTx > Requested", value.toNumber()); + + // Would we exceed the weekly limit ? + return sum.plus(value).lt(limit) + +} +function ApproveTx(r) { + if (isLimitOk(r.transaction)) { + return "Approve" + } + return "Nope" +} + +/** +* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter + * 'response_str' contains the return value that will be sent to the external caller. +* The return value from this method is ignore - the reason for having this callback is to allow the +* ruleset to keep track of approved transactions. +* +* When implementing rate-limited rules, this callback should be used. +* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user +* then accepts the transaction, this method will be called. +* +* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx. +*/ +function OnApprovedTx(resp) { + var value = big(resp.tx.value) + var txs = [] + // Load stored transactions + var stored = storage.get('txs'); + if (stored != "") { + txs = JSON.parse(stored) + } + // Add this to the storage + txs.push({tstamp: new Date().getTime(), value: value}); + storage.put("txs", JSON.stringify(txs)); +} +``` + +## Example 2: allow destination + +```js +function ApproveTx(r) { + if (r.transaction.from.toLowerCase() == "0x0000000000000000000000000000000000001337") { + return "Approve" + } + if (r.transaction.from.toLowerCase() == "0x000000000000000000000000000000000000dead") { + return "Reject" + } + // Otherwise goes to manual processing +} +``` + +## Example 3: Allow listing + +```js +function ApproveListing() { + return "Approve" +} +``` diff --git a/cmd/clef/run_test.go b/cmd/clef/run_test.go new file mode 100644 index 0000000..5fa6e02 --- /dev/null +++ b/cmd/clef/run_test.go @@ -0,0 +1,109 @@ +// 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 main + +import ( + "fmt" + "os" + "testing" + + "github.com/ethereum/go-ethereum/internal/cmdtest" + "github.com/ethereum/go-ethereum/internal/reexec" +) + +const registeredName = "clef-test" + +type testproc struct { + *cmdtest.TestCmd + + // template variables for expect + Datadir string + Etherbase string +} + +func init() { + reexec.Register(registeredName, func() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + }) +} + +func TestMain(m *testing.M) { + // check if we have been reexec'd + if reexec.Init() { + return + } + os.Exit(m.Run()) +} + +// runClef spawns clef with the given command line args and adds keystore arg. +// This method creates a temporary keystore folder which will be removed after +// the test exits. +func runClef(t *testing.T, args ...string) *testproc { + ddir, err := os.MkdirTemp("", "cleftest-*") + if err != nil { + return nil + } + t.Cleanup(func() { + os.RemoveAll(ddir) + }) + return runWithKeystore(t, ddir, args...) +} + +// runWithKeystore spawns clef with the given command line args and adds keystore arg. +// This method does _not_ create the keystore folder, but it _does_ add the arg +// to the args. +func runWithKeystore(t *testing.T, keystore string, args ...string) *testproc { + args = append([]string{"--keystore", keystore}, args...) + tt := &testproc{Datadir: keystore} + tt.TestCmd = cmdtest.NewTestCmd(t, tt) + // Boot "clef". This actually runs the test binary but the TestMain + // function will prevent any tests from running. + tt.Run(registeredName, args...) + return tt +} + +func (proc *testproc) input(text string) *testproc { + proc.TestCmd.InputLine(text) + return proc +} + +/* +// waitForEndpoint waits for the rpc endpoint to appear, or +// aborts after 3 seconds. +func (proc *testproc) waitForEndpoint(t *testing.T) *testproc { + t.Helper() + timeout := 3 * time.Second + ipc := filepath.Join(proc.Datadir, "clef.ipc") + + start := time.Now() + for time.Since(start) < timeout { + if _, err := os.Stat(ipc); !errors.Is(err, os.ErrNotExist) { + t.Logf("endpoint %v opened", ipc) + return proc + } + time.Sleep(200 * time.Millisecond) + } + t.Logf("stderr: \n%v", proc.StderrText()) + t.Logf("stdout: \n%v", proc.Output()) + t.Fatal("endpoint", ipc, "did not open within", timeout) + return proc +} +*/ diff --git a/cmd/clef/sign_flow.png b/cmd/clef/sign_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..e7010ab43f3a6f54d69958055b93b611f7853951 GIT binary patch literal 20537 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. + +// This file is a test-utility for testing clef-functionality +// +// Start clef with +// +// build/bin/clef --4bytedb=./cmd/clef/4byte.json --rpc +// +// Start geth with +// +// build/bin/geth --nodiscover --maxpeers 0 --signer http://localhost:8550 console --preload=cmd/clef/tests/testsigner.js +// +// and in the console simply invoke +// +// > test() +// +// You can reload the file via `reload()` + +function reload(){ + loadScript("./cmd/clef/tests/testsigner.js"); +} + +function init(){ + if (typeof accts == 'undefined' || accts.length == 0){ + accts = eth.accounts + console.log("Got accounts ", accts); + } +} +init() +function testTx(){ + if( accts && accts.length > 0) { + var a = accts[0] + var txdata = eth.signTransaction({from: a, to: a, value: 1, nonce: 1, gas: 1, gasPrice: 1}) + var v = parseInt(txdata.tx.v) + console.log("V value: ", v) + if (v == 37 || v == 38){ + console.log("Mainnet 155-protected chainid was used") + } + if (v == 27 || v == 28){ + throw new Error("Mainnet chainid was used, but without replay protection!") + } + } +} +function testSignText(){ + if( accts && accts.length > 0){ + var a = accts[0] + var r = eth.sign(a, "0x68656c6c6f20776f726c64"); //hello world + console.log("signing response", r) + } +} +function testClique(){ + if( accts && accts.length > 0){ + var a = accts[0] + var r = debug.testSignCliqueBlock(a, 0); // Sign genesis + console.log("signing response", r) + if( a != r){ + throw new Error("Requested signing by "+a+ " but got sealer "+r) + } + } +} + +function test(){ + var tests = [ + testTx, + testSignText, + testClique, + ] + for( i in tests){ + try{ + tests[i]() + }catch(err){ + console.log(err) + } + } + } diff --git a/cmd/clef/tutorial.md b/cmd/clef/tutorial.md new file mode 100644 index 0000000..3ea662b --- /dev/null +++ b/cmd/clef/tutorial.md @@ -0,0 +1,353 @@ +## Initializing Clef + +First things first, Clef needs to store some data itself. Since that data might be sensitive (passwords, signing rules, accounts), Clef's entire storage is encrypted. To support encrypting data, the first step is to initialize Clef with a random master seed, itself too encrypted with your chosen password: + +```text +$ clef init + +WARNING! + +Clef is an account management tool. It may, like any software, contain bugs. + +Please take care to +- backup your keystore files, +- verify that the keystore(s) can be opened with your password. + +Clef is distributed in the hope that 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. + +Enter 'ok' to proceed: +> ok + +The master seed of clef will be locked with a password. +Please specify a password. Do not forget this password! +Password: +Repeat password: + +A master seed has been generated into /home/martin/.clef/masterseed.json + +This is required to be able to store credentials, such as: +* Passwords for keystores (used by rule engine) +* Storage for JavaScript auto-signing rules +* Hash of JavaScript rule-file + +You should treat 'masterseed.json' with utmost secrecy and make a backup of it! +* The password is necessary but not enough, you need to back up the master seed too! +* The master seed does not contain your accounts, those need to be backed up separately! +``` + +*For readability purposes, we'll remove the WARNING printout, user confirmation and the unlocking of the master seed in the rest of this document.* + +## Remote interactions + +Clef is capable of managing both key-file based accounts as well as hardware wallets. To evaluate clef, we're going to point it to our Rinkeby testnet keystore and specify the Rinkeby chain ID for signing (Clef doesn't have a backing chain, so it doesn't know what network it runs on). + +```text +$ clef --keystore ~/.ethereum/rinkeby/keystore --chainid 4 + +INFO [07-01|11:00:46.385] Starting signer chainid=4 keystore=$HOME/.ethereum/rinkeby/keystore light-kdf=false advanced=false +DEBUG[07-01|11:00:46.389] FS scan times list=3.521941ms set=9.017µs diff=4.112µs +DEBUG[07-01|11:00:46.391] Ledger support enabled +DEBUG[07-01|11:00:46.391] Trezor support enabled via HID +DEBUG[07-01|11:00:46.391] Trezor support enabled via WebUSB +INFO [07-01|11:00:46.391] Audit logs configured file=audit.log +DEBUG[07-01|11:00:46.392] IPC registered namespace=account +INFO [07-01|11:00:46.392] IPC endpoint opened url=$HOME/.clef/clef.ipc +------- Signer info ------- +* intapi_version : 7.0.0 +* extapi_version : 6.0.0 +* extapi_http : n/a +* extapi_ipc : $HOME/.clef/clef.ipc +``` + +By default, Clef starts up in CLI (Command Line Interface) mode. Arbitrary remote processes may *request* account interactions (e.g. sign a transaction), which the user will need to individually *confirm*. + +To test this out, we can *request* Clef to list all account via its *External API endpoint*: + +```text +echo '{"id": 1, "jsonrpc": "2.0", "method": "account_list"}' | nc -U ~/.clef/clef.ipc +``` + +This will prompt the user within the Clef CLI to confirm or deny the request: + +```text +-------- List Account request-------------- +A request has been made to list all accounts. +You can select which accounts the caller can see + [x] 0xD9C9Cd5f6779558b6e0eD4e6Acf6b1947E7fA1F3 + URL: keystore://$HOME/.ethereum/rinkeby/keystore/UTC--2017-04-14T15-15-00.327614556Z--d9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 + [x] 0x086278A6C067775F71d6B2BB1856Db6E28c30418 + URL: keystore://$HOME/.ethereum/rinkeby/keystore/UTC--2018-02-06T22-53-11.211657239Z--086278a6c067775f71d6b2bb1856db6e28c30418 +------------------------------------------- +Request context: + NA -> NA -> NA + +Additional HTTP header data, provided by the external caller: + User-Agent: + Origin: +Approve? [y/N]: +> +``` + +Depending on whether we approve or deny the request, the original NetCat process will get: + +```text +{"jsonrpc":"2.0","id":1,"result":["0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3","0x086278a6c067775f71d6b2bb1856db6e28c30418"]} + +or + +{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Request denied"}} +``` + +Apart from listing accounts, you can also *request* creating a new account; signing transactions and data; and recovering signatures. You can find the available methods in the Clef [External API Spec](https://github.com/ethereum/go-ethereum/tree/master/cmd/clef#external-api-1) and the [External API Changelog](https://github.com/ethereum/go-ethereum/blob/master/cmd/clef/extapi_changelog.md). + +*Note, the number of things you can do from the External API is deliberately small, since we want to limit the power of remote calls by as much as possible! Clef has an [Internal API](https://github.com/ethereum/go-ethereum/tree/master/cmd/clef#ui-api-1) too for the UI (User Interface) which is much richer and can support custom interfaces on top. But that's out of scope here.* + +## Automatic rules + +For most users, manually confirming every transaction is the way to go. However, there are cases when it makes sense to set up some rules which permit Clef to sign a transaction without prompting the user. One such example would be running a signer on Rinkeby or other PoA networks. + +For starters, we can create a rule file that automatically permits anyone to list our available accounts without user confirmation. The rule file is a tiny JavaScript snippet that you can program however you want: + +```js +function ApproveListing() { + return "Approve" +} +``` + +Of course, Clef isn't going to just accept and run arbitrary scripts you give it, that would be dangerous if someone changes your rule file! Instead, you need to explicitly *attest* the rule file, which entails injecting its hash into Clef's secure store. + +```text +$ sha256sum rules.js +645b58e4f945e24d0221714ff29f6aa8e860382ced43490529db1695f5fcc71c rules.js + +$ clef attest 645b58e4f945e24d0221714ff29f6aa8e860382ced43490529db1695f5fcc71c +Decrypt master seed of clef +Password: +INFO [07-01|13:25:03.290] Ruleset attestation updated sha256=645b58e4f945e24d0221714ff29f6aa8e860382ced43490529db1695f5fcc71c +``` + +At this point, we can start Clef with the rule file: + +```text +$ clef --keystore ~/.ethereum/rinkeby/keystore --chainid 4 --rules rules.js + +INFO [07-01|13:39:49.726] Rule engine configured file=rules.js +INFO [07-01|13:39:49.726] Starting signer chainid=4 keystore=$HOME/.ethereum/rinkeby/keystore light-kdf=false advanced=false +DEBUG[07-01|13:39:49.726] FS scan times list=35.15µs set=4.251µs diff=2.766µs +DEBUG[07-01|13:39:49.727] Ledger support enabled +DEBUG[07-01|13:39:49.727] Trezor support enabled via HID +DEBUG[07-01|13:39:49.727] Trezor support enabled via WebUSB +INFO [07-01|13:39:49.728] Audit logs configured file=audit.log +DEBUG[07-01|13:39:49.728] IPC registered namespace=account +INFO [07-01|13:39:49.728] IPC endpoint opened url=$HOME/.clef/clef.ipc +------- Signer info ------- +* intapi_version : 7.0.0 +* extapi_version : 6.0.0 +* extapi_http : n/a +* extapi_ipc : $HOME/.clef/clef.ipc +``` + +Any account listing *request* will now be auto-approved by the rule file: + +```text +$ echo '{"id": 1, "jsonrpc": "2.0", "method": "account_list"}' | nc -U ~/.clef/clef.ipc +{"jsonrpc":"2.0","id":1,"result":["0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3","0x086278a6c067775f71d6b2bb1856db6e28c30418"]} +``` + +## Under the hood + +While doing the operations above, these files have been created: + +```text +$ ls -laR ~/.clef/ + +$HOME/.clef/: +total 24 +drwxr-x--x 3 user user 4096 Jul 1 13:45 . +drwxr-xr-x 102 user user 12288 Jul 1 13:39 .. +drwx------ 2 user user 4096 Jul 1 13:25 02f90c0603f4f2f60188 +-r-------- 1 user user 868 Jun 28 13:55 masterseed.json + +$HOME/.clef/02f90c0603f4f2f60188: +total 12 +drwx------ 2 user user 4096 Jul 1 13:25 . +drwxr-x--x 3 user user 4096 Jul 1 13:45 .. +-rw------- 1 user user 159 Jul 1 13:25 config.json + +$ cat ~/.clef/02f90c0603f4f2f60188/config.json +{"ruleset_sha256":{"iv":"SWWEtnl+R+I+wfG7","c":"I3fjmwmamxVcfGax7D0MdUOL29/rBWcs73WBILmYK0o1CrX7wSMc3y37KsmtlZUAjp0oItYq01Ow8VGUOzilG91tDHInB5YHNtm/YkufEbo="}} +``` + +In `$HOME/.clef`, the `masterseed.json` file was created, containing the master seed. This seed was then used to derive a few other things: + +- **Vault location**: in this case `02f90c0603f4f2f60188`. + - If you use a different master seed, a different vault location will be used that does not conflict with each other (e.g. `clef --signersecret /path/to/file`). This allows you to run multiple instances of Clef, each with its own rules (e.g. mainnet + testnet). +- **`config.json`**: the encrypted key/value storage for configuration data, currently only containing the key `ruleset_sha256`, the attested hash of the automatic rules to use. + +## Advanced rules + +In order to make more useful rules - like signing transactions - the signer needs access to the passwords needed to unlock keys from the keystore. You can inject an unlock password via `clef setpw`. + +```text +$ clef setpw 0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 + +Please enter a password to store for this address: +Password: +Repeat password: + +Decrypt master seed of clef +Password: +INFO [07-01|14:05:56.031] Credential store updated key=0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 +``` + +Now let's update the rules to make use of the new credentials: + +```js +function ApproveListing() { + return "Approve" +} + +function ApproveSignData(req) { + if (req.address.toLowerCase() == "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3") { + if (req.messages[0].value.indexOf("bazonk") >= 0) { + return "Approve" + } + return "Reject" + } + // Otherwise goes to manual processing +} +``` + +In this example: + +- Any requests to sign data with the account `0xd9c9...` will be: + - Auto-approved if the message contains `bazonk`, + - Auto-rejected if the message does not contain `bazonk`, +- Any other requests will be passed along for manual confirmation. + +*Note, to make this example work, please use you own accounts. You can create a new account either via Clef or the traditional account CLI tools. If the latter was chosen, make sure both Clef and Geth use the same keystore by specifying `--keystore path/to/your/keystore` when running Clef.* + +Attest the new rule file so that Clef will accept loading it: + +```text +$ sha256sum rules.js +f163a1738b649259bb9b369c593fdc4c6b6f86cc87e343c3ba58faee03c2a178 rules.js + +$ clef attest f163a1738b649259bb9b369c593fdc4c6b6f86cc87e343c3ba58faee03c2a178 +Decrypt master seed of clef +Password: +INFO [07-01|14:11:28.509] Ruleset attestation updated sha256=f163a1738b649259bb9b369c593fdc4c6b6f86cc87e343c3ba58faee03c2a178 +``` + +Restart Clef with the new rules in place: + +``` +$ clef --keystore ~/.ethereum/rinkeby/keystore --chainid 4 --rules rules.js + +INFO [07-01|14:12:41.636] Rule engine configured file=rules.js +INFO [07-01|14:12:41.636] Starting signer chainid=4 keystore=$HOME/.ethereum/rinkeby/keystore light-kdf=false advanced=false +DEBUG[07-01|14:12:41.636] FS scan times list=46.722µs set=4.47µs diff=2.157µs +DEBUG[07-01|14:12:41.637] Ledger support enabled +DEBUG[07-01|14:12:41.637] Trezor support enabled via HID +DEBUG[07-01|14:12:41.638] Trezor support enabled via WebUSB +INFO [07-01|14:12:41.638] Audit logs configured file=audit.log +DEBUG[07-01|14:12:41.638] IPC registered namespace=account +INFO [07-01|14:12:41.638] IPC endpoint opened url=$HOME/.clef/clef.ipc +------- Signer info ------- +* intapi_version : 7.0.0 +* extapi_version : 6.0.0 +* extapi_http : n/a +* extapi_ipc : $HOME/.clef/clef.ipc +``` + +Then test signing, once with `bazonk` and once without: + +``` +$ echo '{"id": 1, "jsonrpc":"2.0", "method":"account_signData", "params":["data/plain", "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3", "0x202062617a6f6e6b2062617a2067617a0a"]}' | nc -U ~/.clef/clef.ipc +{"jsonrpc":"2.0","id":1,"result":"0x4f93e3457027f6be99b06b3392d0ebc60615ba448bb7544687ef1248dea4f5317f789002df783979c417d969836b6fda3710f5bffb296b4d51c8aaae6e2ac4831c"} + +$ echo '{"id": 1, "jsonrpc":"2.0", "method":"account_signData", "params":["data/plain", "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3", "0x2020626f6e6b2062617a2067617a0a"]}' | nc -U ~/.clef/clef.ipc +{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Request denied"}} +``` + +Meanwhile, in the Clef output log you can see: +```text +INFO [02-21|14:42:41] Op approved +INFO [02-21|14:42:56] Op rejected +``` + +The signer also stores all traffic over the external API in a log file. The last 4 lines shows the two requests and their responses: + +```text +$ tail -n 4 audit.log +t=2019-07-01T15:52:14+0300 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"NA\",\"local\":\"NA\",\"scheme\":\"NA\",\"User-Agent\":\"\",\"Origin\":\"\"}" addr="0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 [chksum INVALID]" data=0x202062617a6f6e6b2062617a2067617a0a content-type=data/plain +t=2019-07-01T15:52:14+0300 lvl=info msg=SignData api=signer type=response data=4f93e3457027f6be99b06b3392d0ebc60615ba448bb7544687ef1248dea4f5317f789002df783979c417d969836b6fda3710f5bffb296b4d51c8aaae6e2ac4831c error=nil +t=2019-07-01T15:52:23+0300 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"NA\",\"local\":\"NA\",\"scheme\":\"NA\",\"User-Agent\":\"\",\"Origin\":\"\"}" addr="0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 [chksum INVALID]" data=0x2020626f6e6b2062617a2067617a0a content-type=data/plain +t=2019-07-01T15:52:23+0300 lvl=info msg=SignData api=signer type=response data= error="Request denied" +``` + +For more details on writing automatic rules, please see the [rules spec](https://github.com/ethereum/go-ethereum/blob/master/cmd/clef/rules.md). + +## Geth integration + +Of course, as awesome as Clef is, it's not feasible to interact with it via JSON RPC by hand. Long term, we're hoping to convince the general Ethereum community to support Clef as a general signer (it's only 3-5 methods), thus allowing your favorite DApp, Metamask, MyCrypto, etc to request signatures directly. + +Until then however, we're trying to pave the way via Geth. Geth v1.9.0 has built in support via `--signer ` for using a local or remote Clef instance as an account backend! + +We can try this by running Clef with our previous rules on Rinkeby (for now it's a good idea to allow auto-listing accounts, since Geth likes to retrieve them once in a while). + +```text +$ clef --keystore ~/.ethereum/rinkeby/keystore --chainid 4 --rules rules.js +``` + +In a different window we can start Geth, list our accounts, even list our wallets to see where the accounts originate from: + +```text +$ geth --rinkeby --signer=~/.clef/clef.ipc console + +> eth.accounts +["0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3", "0x086278a6c067775f71d6b2bb1856db6e28c30418"] + +> personal.listWallets +[{ + accounts: [{ + address: "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3", + url: "extapi://$HOME/.clef/clef.ipc" + }, { + address: "0x086278a6c067775f71d6b2bb1856db6e28c30418", + url: "extapi://$HOME/.clef/clef.ipc" + }], + status: "ok [version=6.0.0]", + url: "extapi://$HOME/.clef/clef.ipc" +}] + +> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[0]}) +``` + +Lastly, when we requested a transaction to be sent, Clef prompted us in the original window to approve it: + +```text +--------- Transaction request------------- +to: 0xD9C9Cd5f6779558b6e0eD4e6Acf6b1947E7fA1F3 +from: 0xD9C9Cd5f6779558b6e0eD4e6Acf6b1947E7fA1F3 [chksum ok] +value: 0 wei +gas: 0x5208 (21000) +gasprice: 1000000000 wei +nonce: 0x2366 (9062) + +Request context: + NA -> NA -> NA + +Additional HTTP header data, provided by the external caller: + User-Agent: + Origin: +------------------------------------------- +Approve? [y/N]: +> y +``` + +:boom: + +*Note, if you enable the external signer backend in Geth, all other account management is disabled. This is because long term we want to remove account management from Geth.* diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md new file mode 100644 index 0000000..284dfe0 --- /dev/null +++ b/cmd/devp2p/README.md @@ -0,0 +1,141 @@ +# The devp2p command + +The devp2p command line tool is a utility for low-level peer-to-peer debugging and +protocol development purposes. It can do many things. + +### ENR Decoding + +Use `devp2p enrdump ` to verify and display an Ethereum Node Record. + +### Node Key Management + +The `devp2p key ...` command family deals with node key files. + +Run `devp2p key generate mynode.key` to create a new node key in the `mynode.key` file. + +Run `devp2p key to-enode mynode.key -ip 127.0.0.1 -tcp 30303` to create an enode:// URL +corresponding to the given node key and address information. + +### Maintaining DNS Discovery Node Lists + +The devp2p command can create and publish DNS discovery node lists. + +Run `devp2p dns sign ` to update the signature of a DNS discovery tree. + +Run `devp2p dns sync ` to download a complete DNS discovery tree. + +Run `devp2p dns to-cloudflare ` to publish a tree to CloudFlare DNS. + +Run `devp2p dns to-route53 ` to publish a tree to Amazon Route53. + +You can find more information about these commands in the [DNS Discovery Setup Guide][dns-tutorial]. + +### Node Set Utilities + +There are several commands for working with JSON node set files. These files are generated +by the discovery crawlers and DNS client commands. Node sets also used as the input of the +DNS deployer commands. + +Run `devp2p nodeset info ` to display statistics of a node set. + +Run `devp2p nodeset filter ` to write a new, filtered node +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 +- `-les-server` filters nodes by LES server support +- `-snap` filters nodes by snap protocol support + +For example, given a node set in `nodes.json`, you could create a filtered set containing +up to 20 eth mainnet nodes which also support snap sync using this command: + + devp2p nodeset filter nodes.json -eth-network mainnet -snap -limit 20 + +### Discovery v4 Utilities + +The `devp2p discv4 ...` command family deals with the [Node Discovery v4][discv4] +protocol. + +Run `devp2p discv4 ping ` to ping a node. + +Run `devp2p discv4 resolve ` to find the most recent node record of a node in +the DHT. + +Run `devp2p discv4 crawl ` to create or update a JSON node set. + +### Discovery v5 Utilities + +The `devp2p discv5 ...` command family deals with the [Node Discovery v5][discv5] +protocol. This protocol is currently under active development. + +Run `devp2p discv5 ping ` to ping a node. + +Run `devp2p discv5 resolve ` to find the most recent node record of a node in +the discv5 DHT. + +Run `devp2p discv5 listen` to run a Discovery v5 node. + +Run `devp2p discv5 crawl ` to create or update a JSON node set containing +discv5 nodes. + +### Discovery Test Suites + +The devp2p command also contains interactive test suites for Discovery v4 and Discovery +v5. + +To run these tests against your implementation, you need to set up a networking +environment where two separate UDP listening addresses are available on the same machine. +The two listening addresses must also be routed such that they are able to reach the node +you want to test. + +For example, if you want to run the test on your local host, and the node under test is +also on the local host, you need to assign two IP addresses (or a larger range) to your +loopback interface. On macOS, this can be done by executing the following command: + + sudo ifconfig lo0 add 127.0.0.2 + +You can now run either test suite as follows: Start the node under test first, ensuring +that it won't talk to the Internet (i.e. disable bootstrapping). An easy way to prevent +unintended connections to the global DHT is listening on `127.0.0.1`. + +Now get the ENR of your node and store it in the `NODE` environment variable. + +Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.0.2 $NODE`. + +### Eth Protocol Test Suite + +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 +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 +2. import blocks from `chain.rlp` +3. run the client using the resulting database. For geth, use a command like the one below: + + geth \ + --datadir \ + --nodiscover \ + --nat=none \ + --networkid 3503995874084926 \ + --verbosity 5 \ + --authrpc.jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365 + +Note that the tests also require access to the engine API. +The test suite can now be executed using the devp2p tool. + + devp2p rlpx eth-test \ + --chain internal/ethtest/testdata \ + --node enode://.... \ + --engineapi http://127.0.0.1:8551 \ + --jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365 + +Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. + + +[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md +[dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup +[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md +[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md diff --git a/cmd/devp2p/crawl.go b/cmd/devp2p/crawl.go new file mode 100644 index 0000000..4288a5f --- /dev/null +++ b/cmd/devp2p/crawl.go @@ -0,0 +1,226 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "errors" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +type crawler struct { + input nodeSet + output nodeSet + disc resolver + iters []enode.Iterator + inputIter enode.Iterator + ch chan *enode.Node + closed chan struct{} + + // settings + revalidateInterval time.Duration + mu sync.RWMutex +} + +const ( + nodeRemoved = iota + nodeSkipRecent + nodeSkipIncompat + nodeAdded + nodeUpdated +) + +type resolver interface { + RequestENR(*enode.Node) (*enode.Node, error) +} + +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)), + disc: disc, + iters: iters, + inputIter: enode.IterNodes(input.nodes()), + ch: make(chan *enode.Node), + closed: make(chan struct{}), + } + c.iters = append(c.iters, c.inputIter) + // Copy input to output initially. Any nodes that fail validation + // will be dropped from output during the run. + for id, n := range input { + c.output[id] = n + } + return c, nil +} + +func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet { + var ( + timeoutTimer = time.NewTimer(timeout) + timeoutCh <-chan time.Time + statusTicker = time.NewTicker(time.Second * 8) + doneCh = make(chan enode.Iterator, len(c.iters)) + liveIters = len(c.iters) + ) + if nthreads < 1 { + nthreads = 1 + } + defer timeoutTimer.Stop() + defer statusTicker.Stop() + for _, it := range c.iters { + go c.runIterator(doneCh, it) + } + var ( + added atomic.Uint64 + updated atomic.Uint64 + skipped atomic.Uint64 + recent atomic.Uint64 + removed atomic.Uint64 + wg sync.WaitGroup + ) + wg.Add(nthreads) + for i := 0; i < nthreads; i++ { + go func() { + defer wg.Done() + for { + select { + case n := <-c.ch: + switch c.updateNode(n) { + case nodeSkipIncompat: + skipped.Add(1) + case nodeSkipRecent: + recent.Add(1) + case nodeRemoved: + removed.Add(1) + case nodeAdded: + added.Add(1) + default: + updated.Add(1) + } + case <-c.closed: + return + } + } + }() + } + +loop: + for { + select { + case it := <-doneCh: + if it == c.inputIter { + // Enable timeout when we're done revalidating the input nodes. + log.Info("Revalidation of input set is done", "len", len(c.input)) + if timeout > 0 { + timeoutCh = timeoutTimer.C + } + } + if liveIters--; liveIters == 0 { + break loop + } + case <-timeoutCh: + break loop + case <-statusTicker.C: + log.Info("Crawling in progress", + "added", added.Load(), + "updated", updated.Load(), + "removed", removed.Load(), + "ignored(recent)", recent.Load(), + "ignored(incompatible)", skipped.Load()) + } + } + + close(c.closed) + for _, it := range c.iters { + it.Close() + } + for ; liveIters > 0; liveIters-- { + <-doneCh + } + wg.Wait() + return c.output +} + +func (c *crawler) runIterator(done chan<- enode.Iterator, it enode.Iterator) { + defer func() { done <- it }() + for it.Next() { + select { + case c.ch <- it.Node(): + case <-c.closed: + return + } + } +} + +// updateNode updates the info about the given node, and returns a status +// about what changed +func (c *crawler) updateNode(n *enode.Node) int { + c.mu.RLock() + node, ok := c.output[n.ID()] + c.mu.RUnlock() + + // Skip validation of recently-seen nodes. + if ok && time.Since(node.LastCheck) < c.revalidateInterval { + return nodeSkipRecent + } + + // Request the node record. + status := nodeUpdated + node.LastCheck = truncNow() + if nn, err := c.disc.RequestENR(n); err != nil { + if node.Score == 0 { + // Node doesn't implement EIP-868. + log.Debug("Skipping node", "id", n.ID()) + return nodeSkipIncompat + } + node.Score /= 2 + } else { + node.N = nn + node.Seq = nn.Seq() + node.Score++ + if node.FirstResponse.IsZero() { + node.FirstResponse = node.LastCheck + status = nodeAdded + } + node.LastResponse = node.LastCheck + } + // Store/update node in output set. + c.mu.Lock() + defer c.mu.Unlock() + if node.Score <= 0 { + log.Debug("Removing node", "id", n.ID()) + delete(c.output, n.ID()) + return nodeRemoved + } + log.Debug("Updating node", "id", n.ID(), "seq", n.Seq(), "score", node.Score) + c.output[n.ID()] = node + return status +} + +func truncNow() time.Time { + return time.Now().UTC().Truncate(1 * time.Second) +} diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go new file mode 100644 index 0000000..3b5400c --- /dev/null +++ b/cmd/devp2p/discv4cmd.go @@ -0,0 +1,421 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "errors" + "fmt" + "net" + "net/http" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" +) + +var ( + discv4Command = &cli.Command{ + Name: "discv4", + Usage: "Node Discovery v4 tools", + Subcommands: []*cli.Command{ + discv4PingCommand, + discv4RequestRecordCommand, + discv4ResolveCommand, + discv4ResolveJSONCommand, + discv4CrawlCommand, + discv4TestCommand, + discv4ListenCommand, + }, + } + discv4PingCommand = &cli.Command{ + Name: "ping", + Usage: "Sends ping to a node", + Action: discv4Ping, + ArgsUsage: "", + Flags: discoveryNodeFlags, + } + discv4RequestRecordCommand = &cli.Command{ + Name: "requestenr", + Usage: "Requests a node record using EIP-868 enrRequest", + Action: discv4RequestRecord, + ArgsUsage: "", + Flags: discoveryNodeFlags, + } + discv4ResolveCommand = &cli.Command{ + Name: "resolve", + Usage: "Finds a node in the DHT", + Action: discv4Resolve, + ArgsUsage: "", + Flags: discoveryNodeFlags, + } + discv4ResolveJSONCommand = &cli.Command{ + Name: "resolve-json", + Usage: "Re-resolves nodes in a nodes.json file", + Action: discv4ResolveJSON, + Flags: discoveryNodeFlags, + ArgsUsage: "", + } + discv4ListenCommand = &cli.Command{ + Name: "listen", + Usage: "Runs a discovery node", + Action: discv4Listen, + Flags: flags.Merge(discoveryNodeFlags, []cli.Flag{ + httpAddrFlag, + }), + } + discv4CrawlCommand = &cli.Command{ + Name: "crawl", + Usage: "Updates a nodes.json file with random nodes found in the DHT", + Action: discv4Crawl, + Flags: flags.Merge(discoveryNodeFlags, []cli.Flag{crawlTimeoutFlag, crawlParallelismFlag}), + } + discv4TestCommand = &cli.Command{ + Name: "test", + Usage: "Runs tests against a node", + Action: discv4Test, + Flags: []cli.Flag{ + remoteEnodeFlag, + testPatternFlag, + testTAPFlag, + testListen1Flag, + testListen2Flag, + }, + } +) + +var ( + bootnodesFlag = &cli.StringFlag{ + Name: "bootnodes", + Usage: "Comma separated nodes used for bootstrapping", + } + nodekeyFlag = &cli.StringFlag{ + Name: "nodekey", + Usage: "Hex-encoded node key", + } + nodedbFlag = &cli.StringFlag{ + Name: "nodedb", + Usage: "Nodes database location", + } + listenAddrFlag = &cli.StringFlag{ + Name: "addr", + Usage: "Listening address", + } + extAddrFlag = &cli.StringFlag{ + Name: "extaddr", + Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag.", + } + crawlTimeoutFlag = &cli.DurationFlag{ + Name: "timeout", + Usage: "Time limit for the crawl.", + Value: 30 * time.Minute, + } + crawlParallelismFlag = &cli.IntFlag{ + Name: "parallel", + Usage: "How many parallel discoveries to attempt.", + Value: 16, + } + remoteEnodeFlag = &cli.StringFlag{ + Name: "remote", + Usage: "Enode of the remote node under test", + EnvVars: []string{"REMOTE_ENODE"}, + } + httpAddrFlag = &cli.StringFlag{ + Name: "rpc", + Usage: "HTTP server listening address", + } +) + +var discoveryNodeFlags = []cli.Flag{ + bootnodesFlag, + nodekeyFlag, + nodedbFlag, + listenAddrFlag, + extAddrFlag, +} + +func discv4Ping(ctx *cli.Context) error { + n := getNodeArg(ctx) + disc, _ := startV4(ctx) + defer disc.Close() + + start := time.Now() + if err := disc.Ping(n); err != nil { + return fmt.Errorf("node didn't respond: %v", err) + } + fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start)) + return nil +} + +func discv4Listen(ctx *cli.Context) error { + disc, _ := startV4(ctx) + defer disc.Close() + + fmt.Println(disc.Self()) + + httpAddr := ctx.String(httpAddrFlag.Name) + if httpAddr == "" { + // Non-HTTP mode. + select {} + } + + api := &discv4API{disc} + log.Info("Starting RPC API server", "addr", httpAddr) + srv := rpc.NewServer() + srv.RegisterName("discv4", api) + http.DefaultServeMux.Handle("/", srv) + httpsrv := http.Server{Addr: httpAddr, Handler: http.DefaultServeMux} + return httpsrv.ListenAndServe() +} + +func discv4RequestRecord(ctx *cli.Context) error { + n := getNodeArg(ctx) + disc, _ := startV4(ctx) + defer disc.Close() + + respN, err := disc.RequestENR(n) + if err != nil { + return fmt.Errorf("can't retrieve record: %v", err) + } + fmt.Println(respN.String()) + return nil +} + +func discv4Resolve(ctx *cli.Context) error { + n := getNodeArg(ctx) + disc, _ := startV4(ctx) + defer disc.Close() + + fmt.Println(disc.Resolve(n).String()) + return nil +} + +func discv4ResolveJSON(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("need nodes file as argument") + } + nodesFile := ctx.Args().Get(0) + inputSet := make(nodeSet) + if common.FileExist(nodesFile) { + inputSet = loadNodesJSON(nodesFile) + } + + // Add extra nodes from command line arguments. + var nodeargs []*enode.Node + for i := 1; i < ctx.NArg(); i++ { + n, err := parseNode(ctx.Args().Get(i)) + if err != nil { + exit(err) + } + nodeargs = append(nodeargs, n) + } + + disc, config := startV4(ctx) + defer disc.Close() + + 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) + return nil +} + +func discv4Crawl(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("need nodes file as argument") + } + nodesFile := ctx.Args().First() + inputSet := make(nodeSet) + if common.FileExist(nodesFile) { + inputSet = loadNodesJSON(nodesFile) + } + + disc, config := startV4(ctx) + defer disc.Close() + + 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) + return nil +} + +// discv4Test runs the protocol test suite. +func discv4Test(ctx *cli.Context) error { + // Configure test package globals. + if !ctx.IsSet(remoteEnodeFlag.Name) { + return fmt.Errorf("missing -%v", remoteEnodeFlag.Name) + } + v4test.Remote = ctx.String(remoteEnodeFlag.Name) + v4test.Listen1 = ctx.String(testListen1Flag.Name) + v4test.Listen2 = ctx.String(testListen2Flag.Name) + return runTests(ctx, v4test.AllTests) +} + +// startV4 starts an ephemeral discovery V4 node. +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, config +} + +func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) { + var cfg discover.Config + + if ctx.IsSet(nodekeyFlag.Name) { + key, err := crypto.HexToECDSA(ctx.String(nodekeyFlag.Name)) + if err != nil { + exit(fmt.Errorf("-%s: %v", nodekeyFlag.Name, err)) + } + cfg.PrivateKey = key + } else { + cfg.PrivateKey, _ = crypto.GenerateKey() + } + + if commandHasFlag(ctx, bootnodesFlag) { + bn, err := parseBootnodes(ctx) + if err != nil { + exit(err) + } + cfg.Bootnodes = bn + } + + dbpath := ctx.String(nodedbFlag.Name) + db, err := enode.OpenDB(dbpath) + if err != nil { + exit(err) + } + ln := enode.NewLocalNode(db, cfg.PrivateKey) + return ln, cfg +} + +func parseExtAddr(spec string) (ip net.IP, port int, ok bool) { + ip = net.ParseIP(spec) + if ip != nil { + return ip, 0, true + } + host, portstr, err := net.SplitHostPort(spec) + if err != nil { + return nil, 0, false + } + ip = net.ParseIP(host) + if ip == nil { + return nil, 0, false + } + port, err = strconv.Atoi(portstr) + if err != nil { + return nil, 0, false + } + return ip, port, true +} + +func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { + addr := ctx.String(listenAddrFlag.Name) + if addr == "" { + addr = "0.0.0.0:0" + } + socket, err := net.ListenPacket("udp4", addr) + if err != nil { + exit(err) + } + + // Configure UDP endpoint in ENR from listener address. + usocket := socket.(*net.UDPConn) + uaddr := socket.LocalAddr().(*net.UDPAddr) + if uaddr.IP.IsUnspecified() { + ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + } else { + ln.SetFallbackIP(uaddr.IP) + } + ln.SetFallbackUDP(uaddr.Port) + + // If an ENR endpoint is set explicitly on the command-line, override + // the information from the listening address. Note this is careful not + // to set the UDP port if the external address doesn't have it. + extAddr := ctx.String(extAddrFlag.Name) + if extAddr != "" { + ip, port, ok := parseExtAddr(extAddr) + if !ok { + exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, extAddr)) + } + ln.SetStaticIP(ip) + if port != 0 { + ln.SetFallbackUDP(port) + } + } + + return usocket +} + +func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { + s := params.MainnetBootnodes + if ctx.IsSet(bootnodesFlag.Name) { + input := ctx.String(bootnodesFlag.Name) + if input == "" { + return nil, nil + } + s = strings.Split(input, ",") + } + nodes := make([]*enode.Node, len(s)) + var err error + for i, record := range s { + nodes[i], err = parseNode(record) + if err != nil { + return nil, fmt.Errorf("invalid bootstrap node: %v", err) + } + } + return nodes, nil +} + +type discv4API struct { + host *discover.UDPv4 +} + +func (api *discv4API) LookupRandom(n int) (ns []*enode.Node) { + it := api.host.RandomNodes() + for len(ns) < n && it.Next() { + ns = append(ns, it.Node()) + } + return ns +} + +func (api *discv4API) Buckets() [][]discover.BucketNode { + return api.host.TableBuckets() +} + +func (api *discv4API) Self() *enode.Node { + return api.host.Self() +} diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go new file mode 100644 index 0000000..0dac945 --- /dev/null +++ b/cmd/devp2p/discv5cmd.go @@ -0,0 +1,150 @@ +// 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 ( + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/urfave/cli/v2" +) + +var ( + discv5Command = &cli.Command{ + Name: "discv5", + Usage: "Node Discovery v5 tools", + Subcommands: []*cli.Command{ + discv5PingCommand, + discv5ResolveCommand, + discv5CrawlCommand, + discv5TestCommand, + discv5ListenCommand, + }, + } + discv5PingCommand = &cli.Command{ + Name: "ping", + Usage: "Sends ping to a node", + Action: discv5Ping, + Flags: discoveryNodeFlags, + } + discv5ResolveCommand = &cli.Command{ + Name: "resolve", + Usage: "Finds a node in the DHT", + Action: discv5Resolve, + Flags: discoveryNodeFlags, + } + discv5CrawlCommand = &cli.Command{ + Name: "crawl", + Usage: "Updates a nodes.json file with random nodes found in the DHT", + Action: discv5Crawl, + Flags: flags.Merge(discoveryNodeFlags, []cli.Flag{ + crawlTimeoutFlag, + }), + } + discv5TestCommand = &cli.Command{ + Name: "test", + Usage: "Runs protocol tests against a node", + Action: discv5Test, + Flags: []cli.Flag{ + testPatternFlag, + testTAPFlag, + testListen1Flag, + testListen2Flag, + }, + } + discv5ListenCommand = &cli.Command{ + Name: "listen", + Usage: "Runs a node", + Action: discv5Listen, + Flags: discoveryNodeFlags, + } +) + +func discv5Ping(ctx *cli.Context) error { + n := getNodeArg(ctx) + disc, _ := startV5(ctx) + defer disc.Close() + + fmt.Println(disc.Ping(n)) + return nil +} + +func discv5Resolve(ctx *cli.Context) error { + n := getNodeArg(ctx) + disc, _ := startV5(ctx) + defer disc.Close() + + fmt.Println(disc.Resolve(n)) + return nil +} + +func discv5Crawl(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("need nodes file as argument") + } + nodesFile := ctx.Args().First() + inputSet := make(nodeSet) + if common.FileExist(nodesFile) { + inputSet = loadNodesJSON(nodesFile) + } + + disc, config := startV5(ctx) + defer disc.Close() + + 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) + return nil +} + +// discv5Test runs the protocol test suite. +func discv5Test(ctx *cli.Context) error { + suite := &v5test.Suite{ + Dest: getNodeArg(ctx), + Listen1: ctx.String(testListen1Flag.Name), + Listen2: ctx.String(testListen2Flag.Name), + } + return runTests(ctx, suite.AllTests()) +} + +func discv5Listen(ctx *cli.Context) error { + disc, _ := startV5(ctx) + defer disc.Close() + + fmt.Println(disc.Self()) + select {} +} + +// startV5 starts an ephemeral discovery v5 node. +func startV5(ctx *cli.Context) (*discover.UDPv5, discover.Config) { + ln, config := makeDiscoveryConfig(ctx) + socket := listen(ctx, ln) + disc, err := discover.ListenV5(socket, ln, config) + if err != nil { + exit(err) + } + return disc, config +} diff --git a/cmd/devp2p/dns_cloudflare.go b/cmd/devp2p/dns_cloudflare.go new file mode 100644 index 0000000..a3cc69c --- /dev/null +++ b/cmd/devp2p/dns_cloudflare.go @@ -0,0 +1,188 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/cloudflare/cloudflare-go" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/dnsdisc" + "github.com/urfave/cli/v2" +) + +var ( + cloudflareTokenFlag = &cli.StringFlag{ + Name: "token", + Usage: "CloudFlare API token", + EnvVars: []string{"CLOUDFLARE_API_TOKEN"}, + } + cloudflareZoneIDFlag = &cli.StringFlag{ + Name: "zoneid", + Usage: "CloudFlare Zone ID (optional)", + } +) + +type cloudflareClient struct { + *cloudflare.API + zoneID string +} + +// newCloudflareClient sets up a CloudFlare API client from command line flags. +func newCloudflareClient(ctx *cli.Context) *cloudflareClient { + token := ctx.String(cloudflareTokenFlag.Name) + if token == "" { + exit(errors.New("need cloudflare API token to proceed")) + } + api, err := cloudflare.NewWithAPIToken(token) + if err != nil { + exit(fmt.Errorf("can't create Cloudflare client: %v", err)) + } + return &cloudflareClient{ + API: api, + zoneID: ctx.String(cloudflareZoneIDFlag.Name), + } +} + +// deploy uploads the given tree to CloudFlare DNS. +func (c *cloudflareClient) deploy(name string, t *dnsdisc.Tree) error { + if err := c.checkZone(name); err != nil { + return err + } + records := t.ToTXT(name) + return c.uploadRecords(name, records) +} + +// checkZone verifies permissions on the CloudFlare DNS Zone for name. +func (c *cloudflareClient) checkZone(name string) error { + if c.zoneID == "" { + log.Info(fmt.Sprintf("Finding CloudFlare zone ID for %s", name)) + id, err := c.ZoneIDByName(name) + if err != nil { + return err + } + c.zoneID = id + } + log.Info(fmt.Sprintf("Checking Permissions on zone %s", c.zoneID)) + zone, err := c.ZoneDetails(context.Background(), c.zoneID) + if err != nil { + return err + } + if !strings.HasSuffix(name, "."+zone.Name) { + return fmt.Errorf("CloudFlare zone name %q does not match name %q to be deployed", zone.Name, name) + } + needPerms := map[string]bool{"#zone:edit": false, "#zone:read": false} + for _, perm := range zone.Permissions { + if _, ok := needPerms[perm]; ok { + needPerms[perm] = true + } + } + for _, ok := range needPerms { + if !ok { + return fmt.Errorf("wrong permissions on zone %s: %v", c.zoneID, needPerms) + } + } + return nil +} + +// uploadRecords updates the TXT records at a particular subdomain. All non-root records +// will have a TTL of "infinity" and all existing records not in the new map will be +// nuked! +func (c *cloudflareClient) uploadRecords(name string, records map[string]string) error { + // Convert all names to lowercase. + lrecords := make(map[string]string, len(records)) + for name, r := range records { + lrecords[strings.ToLower(name)] = r + } + records = lrecords + + log.Info(fmt.Sprintf("Retrieving existing TXT records on %s", name)) + entries, _, err := c.ListDNSRecords(context.Background(), cloudflare.ZoneIdentifier(c.zoneID), cloudflare.ListDNSRecordsParams{Type: "TXT"}) + if err != nil { + return err + } + existing := make(map[string]cloudflare.DNSRecord) + for _, entry := range entries { + if !strings.HasSuffix(entry.Name, name) { + continue + } + existing[strings.ToLower(entry.Name)] = entry + } + + // Iterate over the new records and inject anything missing. + log.Info("Updating DNS entries") + created := 0 + updated := 0 + skipped := 0 + for path, val := range records { + old, exists := existing[path] + if !exists { + // Entry is unknown, push a new one to Cloudflare. + log.Debug(fmt.Sprintf("Creating %s = %q", path, val)) + created++ + ttl := rootTTL + if path != name { + ttl = treeNodeTTLCloudflare // Max TTL permitted by Cloudflare + } + 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++ + + 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)) + } + if err != nil { + return fmt.Errorf("failed to publish %s: %v", path, err) + } + } + log.Info("Updated DNS entries", "new", created, "updated", updated, "untouched", skipped) + // Iterate over the old records and delete anything stale. + deleted := 0 + log.Info("Deleting stale DNS entries") + for path, entry := range existing { + if _, ok := records[path]; ok { + continue + } + // Stale entry, nuke it. + log.Debug(fmt.Sprintf("Deleting %s = %q", path, entry.Content)) + deleted++ + if err := c.DeleteDNSRecord(context.Background(), cloudflare.ZoneIdentifier(c.zoneID), entry.ID); err != nil { + return fmt.Errorf("failed to delete %s: %v", path, err) + } + } + log.Info("Deleted stale DNS entries", "count", deleted) + return nil +} diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go new file mode 100644 index 0000000..a6125b8 --- /dev/null +++ b/cmd/devp2p/dns_route53.go @@ -0,0 +1,431 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "context" + "errors" + "fmt" + "slices" + "strconv" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/route53" + "github.com/aws/aws-sdk-go-v2/service/route53/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/dnsdisc" + "github.com/urfave/cli/v2" +) + +const ( + // Route53 limits change sets to 32k of 'RDATA size'. Change sets are also limited to + // 1000 items. UPSERTs count double. + // https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-changeresourcerecordsets + route53ChangeSizeLimit = 32000 + route53ChangeCountLimit = 1000 + maxRetryLimit = 60 +) + +var ( + route53AccessKeyFlag = &cli.StringFlag{ + Name: "access-key-id", + Usage: "AWS Access Key ID", + EnvVars: []string{"AWS_ACCESS_KEY_ID"}, + } + route53AccessSecretFlag = &cli.StringFlag{ + Name: "access-key-secret", + Usage: "AWS Access Key Secret", + EnvVars: []string{"AWS_SECRET_ACCESS_KEY"}, + } + route53ZoneIDFlag = &cli.StringFlag{ + Name: "zone-id", + Usage: "Route53 Zone ID", + } + route53RegionFlag = &cli.StringFlag{ + Name: "aws-region", + Usage: "AWS Region", + Value: "eu-central-1", + } +) + +type route53Client struct { + api *route53.Client + zoneID string +} + +type recordSet struct { + values []string + ttl int64 +} + +// newRoute53Client sets up a Route53 API client from command line flags. +func newRoute53Client(ctx *cli.Context) *route53Client { + akey := ctx.String(route53AccessKeyFlag.Name) + asec := ctx.String(route53AccessSecretFlag.Name) + if akey == "" || asec == "" { + exit(errors.New("need Route53 Access Key ID and secret to proceed")) + } + creds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(akey, asec, "")) + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithCredentialsProvider(creds)) + if err != nil { + exit(fmt.Errorf("can't initialize AWS configuration: %v", err)) + } + cfg.Region = ctx.String(route53RegionFlag.Name) + return &route53Client{ + api: route53.NewFromConfig(cfg), + zoneID: ctx.String(route53ZoneIDFlag.Name), + } +} + +// deploy uploads the given tree to Route53. +func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { + if err := c.checkZone(name); err != nil { + return err + } + + // Compute DNS changes. + existing, err := c.collectRecords(name) + if err != nil { + return err + } + log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) + records := t.ToTXT(name) + changes := c.computeChanges(name, records, existing) + + // Submit to API. + comment := fmt.Sprintf("enrtree update of %s at seq %d", name, t.Seq()) + return c.submitChanges(changes, comment) +} + +// deleteDomain removes all TXT records of the given domain. +func (c *route53Client) deleteDomain(name string) error { + if err := c.checkZone(name); err != nil { + return err + } + + // Compute DNS changes. + existing, err := c.collectRecords(name) + if err != nil { + return err + } + log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) + changes := makeDeletionChanges(existing, nil) + + // Submit to API. + comment := "enrtree delete of " + name + return c.submitChanges(changes, comment) +} + +// submitChanges submits the given DNS changes to Route53. +func (c *route53Client) submitChanges(changes []types.Change, comment string) error { + if len(changes) == 0 { + log.Info("No DNS changes needed") + return nil + } + + var err error + batches := splitChanges(changes, route53ChangeSizeLimit, route53ChangeCountLimit) + changesToCheck := make([]*route53.ChangeResourceRecordSetsOutput, len(batches)) + for i, changes := range batches { + log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes))) + batch := &types.ChangeBatch{ + Changes: changes, + Comment: aws.String(fmt.Sprintf("%s (%d/%d)", comment, i+1, len(batches))), + } + req := &route53.ChangeResourceRecordSetsInput{HostedZoneId: &c.zoneID, ChangeBatch: batch} + changesToCheck[i], err = c.api.ChangeResourceRecordSets(context.TODO(), req) + if err != nil { + return err + } + } + + // Wait for all change batches to propagate. + for _, change := range changesToCheck { + log.Info(fmt.Sprintf("Waiting for change request %s", *change.ChangeInfo.Id)) + wreq := &route53.GetChangeInput{Id: change.ChangeInfo.Id} + var count int + for { + wresp, err := c.api.GetChange(context.TODO(), wreq) + if err != nil { + return err + } + + count++ + + if wresp.ChangeInfo.Status == types.ChangeStatusInsync || count >= maxRetryLimit { + break + } + + time.Sleep(30 * time.Second) + } + } + return nil +} + +// checkZone verifies zone information for the given domain. +func (c *route53Client) checkZone(name string) (err error) { + if c.zoneID == "" { + c.zoneID, err = c.findZoneID(name) + } + return err +} + +// findZoneID searches for the Zone ID containing the given domain. +func (c *route53Client) findZoneID(name string) (string, error) { + log.Info(fmt.Sprintf("Finding Route53 Zone ID for %s", name)) + var req route53.ListHostedZonesByNameInput + for { + resp, err := c.api.ListHostedZonesByName(context.TODO(), &req) + if err != nil { + return "", err + } + for _, zone := range resp.HostedZones { + if isSubdomain(name, *zone.Name) { + return *zone.Id, nil + } + } + if !resp.IsTruncated { + break + } + req.DNSName = resp.NextDNSName + req.HostedZoneId = resp.NextHostedZoneId + } + return "", errors.New("can't find zone ID for " + name) +} + +// computeChanges creates DNS changes for the given set of DNS discovery records. +// The 'existing' arg is the set of records that already exist on Route53. +func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []types.Change { + // Convert all names to lowercase. + lrecords := make(map[string]string, len(records)) + for name, r := range records { + lrecords[strings.ToLower(name)] = r + } + records = lrecords + + var ( + changes []types.Change + inserts int + upserts int + skips int + ) + + for path, newValue := range records { + prevRecords, exists := existing[path] + prevValue := strings.Join(prevRecords.values, "") + + // prevValue contains quoted strings, encode newValue to compare. + newValue = splitTXT(newValue) + + // Assign TTL. + ttl := int64(rootTTL) + if path != name { + ttl = int64(treeNodeTTL) + } + + if !exists { + // Entry is unknown, push a new one + log.Debug(fmt.Sprintf("Creating %s = %s", path, newValue)) + changes = append(changes, newTXTChange("CREATE", path, ttl, newValue)) + inserts++ + } else if prevValue != newValue || prevRecords.ttl != ttl { + // Entry already exists, only change its content. + log.Info(fmt.Sprintf("Updating %s from %s to %s", path, prevValue, newValue)) + changes = append(changes, newTXTChange("UPSERT", path, ttl, newValue)) + upserts++ + } else { + log.Debug(fmt.Sprintf("Skipping %s = %s", path, newValue)) + skips++ + } + } + + // Iterate over the old records and delete anything stale. + deletions := makeDeletionChanges(existing, records) + changes = append(changes, deletions...) + + log.Info("Computed DNS changes", + "changes", len(changes), + "inserts", inserts, + "skips", skips, + "deleted", len(deletions), + "upserts", upserts) + // Ensure changes are in the correct order. + sortChanges(changes) + return changes +} + +// makeDeletionChanges creates record changes which delete all records not contained in 'keep'. +func makeDeletionChanges(records map[string]recordSet, keep map[string]string) []types.Change { + var changes []types.Change + for path, set := range records { + if _, ok := keep[path]; ok { + continue + } + log.Debug(fmt.Sprintf("Deleting %s = %s", path, strings.Join(set.values, ""))) + changes = append(changes, newTXTChange("DELETE", path, set.ttl, set.values...)) + } + return changes +} + +// sortChanges ensures DNS changes are in leaf-added -> root-changed -> leaf-deleted order. +func sortChanges(changes []types.Change) { + score := map[string]int{"CREATE": 1, "UPSERT": 2, "DELETE": 3} + slices.SortFunc(changes, func(a, b types.Change) int { + if a.Action == b.Action { + return strings.Compare(*a.ResourceRecordSet.Name, *b.ResourceRecordSet.Name) + } + if score[string(a.Action)] < score[string(b.Action)] { + return -1 + } + if score[string(a.Action)] > score[string(b.Action)] { + return 1 + } + return 0 + }) +} + +// splitChanges splits up DNS changes such that each change batch +// is smaller than the given RDATA limit. +func splitChanges(changes []types.Change, sizeLimit, countLimit int) [][]types.Change { + var ( + batches [][]types.Change + batchSize int + batchCount int + ) + for _, ch := range changes { + // Start new batch if this change pushes the current one over the limit. + count := changeCount(ch) + size := changeSize(ch) * count + overSize := batchSize+size > sizeLimit + overCount := batchCount+count > countLimit + if len(batches) == 0 || overSize || overCount { + batches = append(batches, nil) + batchSize = 0 + batchCount = 0 + } + batches[len(batches)-1] = append(batches[len(batches)-1], ch) + batchSize += size + batchCount += count + } + return batches +} + +// changeSize returns the RDATA size of a DNS change. +func changeSize(ch types.Change) int { + size := 0 + for _, rr := range ch.ResourceRecordSet.ResourceRecords { + if rr.Value != nil { + size += len(*rr.Value) + } + } + return size +} + +func changeCount(ch types.Change) int { + if ch.Action == types.ChangeActionUpsert { + return 2 + } + return 1 +} + +// collectRecords collects all TXT records below the given name. +func (c *route53Client) collectRecords(name string) (map[string]recordSet, error) { + var req route53.ListResourceRecordSetsInput + req.HostedZoneId = &c.zoneID + existing := make(map[string]recordSet) + log.Info("Loading existing TXT records", "name", name, "zone", c.zoneID) + for page := 0; ; page++ { + log.Debug("Loading existing TXT records", "name", name, "zone", c.zoneID, "page", page) + resp, err := c.api.ListResourceRecordSets(context.TODO(), &req) + if err != nil { + return existing, err + } + for _, set := range resp.ResourceRecordSets { + if !isSubdomain(*set.Name, name) || set.Type != types.RRTypeTxt { + continue + } + s := recordSet{ttl: *set.TTL} + for _, rec := range set.ResourceRecords { + s.values = append(s.values, *rec.Value) + } + name := strings.TrimSuffix(*set.Name, ".") + existing[name] = s + } + + if !resp.IsTruncated { + break + } + // Set the cursor to the next batch. From the AWS docs: + // + // To display the next page of results, get the values of NextRecordName, + // NextRecordType, and NextRecordIdentifier (if any) from the response. Then submit + // another ListResourceRecordSets request, and specify those values for + // StartRecordName, StartRecordType, and StartRecordIdentifier. + req.StartRecordIdentifier = resp.NextRecordIdentifier + req.StartRecordName = resp.NextRecordName + req.StartRecordType = resp.NextRecordType + } + log.Info("Loaded existing TXT records", "name", name, "zone", c.zoneID, "records", len(existing)) + return existing, nil +} + +// newTXTChange creates a change to a TXT record. +func newTXTChange(action, name string, ttl int64, values ...string) types.Change { + r := types.ResourceRecordSet{ + Type: types.RRTypeTxt, + Name: &name, + TTL: &ttl, + } + var rrs []types.ResourceRecord + for _, val := range values { + var rr types.ResourceRecord + rr.Value = aws.String(val) + rrs = append(rrs, rr) + } + + r.ResourceRecords = rrs + + return types.Change{ + Action: types.ChangeAction(action), + ResourceRecordSet: &r, + } +} + +// isSubdomain returns true if name is a subdomain of domain. +func isSubdomain(name, domain string) bool { + domain = strings.TrimSuffix(domain, ".") + name = strings.TrimSuffix(name, ".") + return strings.HasSuffix("."+name, "."+domain) +} + +// splitTXT splits value into a list of quoted 255-character strings. +func splitTXT(value string) string { + var result strings.Builder + for len(value) > 0 { + rlen := len(value) + if rlen > 253 { + rlen = 253 + } + result.WriteString(strconv.Quote(value[:rlen])) + value = value[rlen:] + } + return result.String() +} diff --git a/cmd/devp2p/dns_route53_test.go b/cmd/devp2p/dns_route53_test.go new file mode 100644 index 0000000..af39c70 --- /dev/null +++ b/cmd/devp2p/dns_route53_test.go @@ -0,0 +1,192 @@ +// 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 ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/route53/types" +) + +// 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"`, + }}, + "fdxn3sn67na5dka4j2gok7bvqi.n": {ttl: treeNodeTTL, values: []string{`"enrtree-branch:"`}}, + "n": {ttl: rootTTL, values: []string{`"enrtree-root:v1 e=2KFJOGVXDQTXXUGBH7GS7NAAAI l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=0 sig=v_-J_q_9ICQg5ztExFvLQhDBGMb0lZPJLhe3ts9LAcgqhOhtT3YFJsl8BWNDSwGtamUdR-9xl88_w-X42SVpjwE"`}}, + } + + testTree1 := map[string]string{ + "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA", + "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", + "JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24", + "2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", + "H4FHT4B454P6UXFD7JCYQ5PWDY.n": "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI", + "MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o", + } + + wantChanges := []types.Change{ + { + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ + Name: sp("2xs2367yhaxjfglzhvawlqd4zy.n"), + ResourceRecords: []types.ResourceRecord{{ + Value: sp(`"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA"`), + }}, + TTL: ip(treeNodeTTL), + Type: "TXT", + }, + }, + { + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ + Name: sp("c7hrfpf3blgf3yr4dy5kx3smbe.n"), + ResourceRecords: []types.ResourceRecord{{ + Value: sp(`"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"`), + }}, + TTL: ip(treeNodeTTL), + Type: "TXT", + }, + }, + { + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ + Name: sp("h4fht4b454p6uxfd7jcyq5pwdy.n"), + ResourceRecords: []types.ResourceRecord{{ + Value: sp(`"enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI"`), + }}, + TTL: ip(treeNodeTTL), + Type: "TXT", + }, + }, + { + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ + Name: sp("jwxydbpxywg6fx3gmdibfa6cj4.n"), + ResourceRecords: []types.ResourceRecord{{ + Value: sp(`"enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24"`), + }}, + TTL: ip(treeNodeTTL), + Type: "TXT", + }, + }, + { + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ + Name: sp("mhtdo6tmubria2xwg5ludack24.n"), + ResourceRecords: []types.ResourceRecord{{ + Value: sp(`"enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o"`), + }}, + TTL: ip(treeNodeTTL), + Type: "TXT", + }, + }, + { + Action: "UPSERT", + ResourceRecordSet: &types.ResourceRecordSet{ + Name: sp("n"), + ResourceRecords: []types.ResourceRecord{{ + Value: sp(`"enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA"`), + }}, + TTL: ip(rootTTL), + Type: "TXT", + }, + }, + { + Action: "DELETE", + ResourceRecordSet: &types.ResourceRecordSet{ + Name: sp("2kfjogvxdqtxxugbh7gs7naaai.n"), + ResourceRecords: []types.ResourceRecord{ + {Value: sp(`"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-""vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`)}, + }, + TTL: ip(3333), + Type: "TXT", + }, + }, + { + Action: "DELETE", + ResourceRecordSet: &types.ResourceRecordSet{ + Name: sp("fdxn3sn67na5dka4j2gok7bvqi.n"), + ResourceRecords: []types.ResourceRecord{{ + Value: sp(`"enrtree-branch:"`), + }}, + TTL: ip(treeNodeTTL), + Type: "TXT", + }, + }, + } + + var client route53Client + changes := client.computeChanges("n", testTree1, testTree0) + if !reflect.DeepEqual(changes, wantChanges) { + t.Fatalf("wrong changes (got %d, want %d)", len(changes), len(wantChanges)) + } + + // Check splitting according to size. + wantSplit := [][]types.Change{ + wantChanges[:4], + wantChanges[4:6], + wantChanges[6:], + } + split := splitChanges(changes, 600, 4000) + if !reflect.DeepEqual(split, wantSplit) { + t.Fatalf("wrong split batches: got %d, want %d", len(split), len(wantSplit)) + } + + // Check splitting according to count. + wantSplit = [][]types.Change{ + wantChanges[:5], + wantChanges[5:], + } + split = splitChanges(changes, 10000, 6) + if !reflect.DeepEqual(split, wantSplit) { + t.Fatalf("wrong split batches: got %d, want %d", len(split), len(wantSplit)) + } +} + +// 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{ + `"enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA"`, + }}, + "2xs2367yhaxjfglzhvawlqd4zy.n": {ttl: treeNodeTTL, values: []string{ + `"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA"`, + }}, + } + // New set. + testTree1 := map[string]string{ + "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA", + "2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", + } + + var client route53Client + changes := client.computeChanges("n", testTree1, testTree0) + if len(changes) > 0 { + t.Fatalf("wrong changes (got %d, want 0)", len(changes)) + } +} + +func sp(s string) *string { return &s } +func ip(i int64) *int64 { return &i } diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go new file mode 100644 index 0000000..0fce7b1 --- /dev/null +++ b/cmd/devp2p/dnscmd.go @@ -0,0 +1,417 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "crypto/ecdsa" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/p2p/dnsdisc" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/urfave/cli/v2" +) + +var ( + dnsCommand = &cli.Command{ + Name: "dns", + Usage: "DNS Discovery Commands", + Subcommands: []*cli.Command{ + dnsSyncCommand, + dnsSignCommand, + dnsTXTCommand, + dnsCloudflareCommand, + dnsRoute53Command, + dnsRoute53NukeCommand, + }, + } + dnsSyncCommand = &cli.Command{ + Name: "sync", + Usage: "Download a DNS discovery tree", + ArgsUsage: " [ ]", + Action: dnsSync, + Flags: []cli.Flag{dnsTimeoutFlag}, + } + dnsSignCommand = &cli.Command{ + Name: "sign", + Usage: "Sign a DNS discovery tree", + ArgsUsage: " ", + Action: dnsSign, + Flags: []cli.Flag{dnsDomainFlag, dnsSeqFlag}, + } + dnsTXTCommand = &cli.Command{ + Name: "to-txt", + Usage: "Create a DNS TXT records for a discovery tree", + ArgsUsage: " ", + Action: dnsToTXT, + } + dnsCloudflareCommand = &cli.Command{ + Name: "to-cloudflare", + Usage: "Deploy DNS TXT records to CloudFlare", + ArgsUsage: "", + Action: dnsToCloudflare, + Flags: []cli.Flag{cloudflareTokenFlag, cloudflareZoneIDFlag}, + } + dnsRoute53Command = &cli.Command{ + Name: "to-route53", + Usage: "Deploy DNS TXT records to Amazon Route53", + ArgsUsage: "", + Action: dnsToRoute53, + Flags: []cli.Flag{ + route53AccessKeyFlag, + route53AccessSecretFlag, + route53ZoneIDFlag, + route53RegionFlag, + }, + } + dnsRoute53NukeCommand = &cli.Command{ + Name: "nuke-route53", + Usage: "Deletes DNS TXT records of a subdomain on Amazon Route53", + ArgsUsage: "", + Action: dnsNukeRoute53, + Flags: []cli.Flag{ + route53AccessKeyFlag, + route53AccessSecretFlag, + route53ZoneIDFlag, + route53RegionFlag, + }, + } +) + +var ( + dnsTimeoutFlag = &cli.DurationFlag{ + Name: "timeout", + Usage: "Timeout for DNS lookups", + } + dnsDomainFlag = &cli.StringFlag{ + Name: "domain", + Usage: "Domain name of the tree", + } + dnsSeqFlag = &cli.UintFlag{ + Name: "seq", + Usage: "New sequence number of the tree", + } +) + +const ( + rootTTL = 30 * 60 // 30 min + treeNodeTTL = 4 * 7 * 24 * 60 * 60 // 4 weeks + treeNodeTTLCloudflare = 24 * 60 * 60 // 1 day +) + +// dnsSync performs dnsSyncCommand. +func dnsSync(ctx *cli.Context) error { + var ( + c = dnsClient(ctx) + url = ctx.Args().Get(0) + outdir = ctx.Args().Get(1) + ) + domain, _, err := dnsdisc.ParseURL(url) + if err != nil { + return err + } + if outdir == "" { + outdir = domain + } + + t, err := c.SyncTree(url) + if err != nil { + return err + } + def := treeToDefinition(url, t) + def.Meta.LastModified = time.Now() + writeTreeMetadata(outdir, def) + writeTreeNodes(outdir, def) + return nil +} + +func dnsSign(ctx *cli.Context) error { + if ctx.NArg() < 2 { + return errors.New("need tree definition directory and key file as arguments") + } + var ( + defdir = ctx.Args().Get(0) + keyfile = ctx.Args().Get(1) + def = loadTreeDefinition(defdir) + domain = directoryName(defdir) + ) + if def.Meta.URL != "" { + d, _, err := dnsdisc.ParseURL(def.Meta.URL) + if err != nil { + return fmt.Errorf("invalid 'url' field: %v", err) + } + domain = d + } + if ctx.IsSet(dnsDomainFlag.Name) { + domain = ctx.String(dnsDomainFlag.Name) + } + if ctx.IsSet(dnsSeqFlag.Name) { + def.Meta.Seq = ctx.Uint(dnsSeqFlag.Name) + } else { + def.Meta.Seq++ // Auto-bump sequence number if not supplied via flag. + } + t, err := dnsdisc.MakeTree(def.Meta.Seq, def.Nodes, def.Meta.Links) + if err != nil { + return err + } + + key := loadSigningKey(keyfile) + url, err := t.Sign(key, domain) + if err != nil { + return fmt.Errorf("can't sign: %v", err) + } + + def = treeToDefinition(url, t) + def.Meta.LastModified = time.Now() + writeTreeMetadata(defdir, def) + return nil +} + +// directoryName returns the directory name of the given path. +// For example, when dir is "foo/bar", it returns "bar". +// When dir is ".", and the working directory is "example/foo", it returns "foo". +func directoryName(dir string) string { + abs, err := filepath.Abs(dir) + if err != nil { + exit(err) + } + return filepath.Base(abs) +} + +// dnsToTXT performs dnsTXTCommand. +func dnsToTXT(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("need tree definition directory as argument") + } + output := ctx.Args().Get(1) + if output == "" { + output = "-" // default to stdout + } + domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) + if err != nil { + return err + } + writeTXTJSON(output, t.ToTXT(domain)) + return nil +} + +// dnsToCloudflare performs dnsCloudflareCommand. +func dnsToCloudflare(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return errors.New("need tree definition directory as argument") + } + domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) + if err != nil { + return err + } + client := newCloudflareClient(ctx) + return client.deploy(domain, t) +} + +// dnsToRoute53 performs dnsRoute53Command. +func dnsToRoute53(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return errors.New("need tree definition directory as argument") + } + domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) + if err != nil { + return err + } + client := newRoute53Client(ctx) + return client.deploy(domain, t) +} + +// dnsNukeRoute53 performs dnsRoute53NukeCommand. +func dnsNukeRoute53(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return errors.New("need domain name as argument") + } + client := newRoute53Client(ctx) + return client.deleteDomain(ctx.Args().First()) +} + +// loadSigningKey loads a private key in Ethereum keystore format. +func loadSigningKey(keyfile string) *ecdsa.PrivateKey { + keyjson, err := os.ReadFile(keyfile) + if err != nil { + exit(fmt.Errorf("failed to read the keyfile at '%s': %v", keyfile, err)) + } + password, _ := prompt.Stdin.PromptPassword("Please enter the password for '" + keyfile + "': ") + key, err := keystore.DecryptKey(keyjson, password) + if err != nil { + exit(fmt.Errorf("error decrypting key: %v", err)) + } + return key.PrivateKey +} + +// dnsClient configures the DNS discovery client from command line flags. +func dnsClient(ctx *cli.Context) *dnsdisc.Client { + var cfg dnsdisc.Config + if commandHasFlag(ctx, dnsTimeoutFlag) { + cfg.Timeout = ctx.Duration(dnsTimeoutFlag.Name) + } + return dnsdisc.NewClient(cfg) +} + +// There are two file formats for DNS node trees on disk: +// +// The 'TXT' format is a single JSON file containing DNS TXT records +// as a JSON object where the keys are names and the values are objects +// containing the value of the record. +// +// The 'definition' format is a directory containing two files: +// +// enrtree-info.json -- contains sequence number & links to other trees +// nodes.json -- contains the nodes as a JSON array. +// +// This format exists because it's convenient to edit. nodes.json can be generated +// in multiple ways: it may be written by a DHT crawler or compiled by a human. + +type dnsDefinition struct { + Meta dnsMetaJSON + Nodes []*enode.Node +} + +type dnsMetaJSON struct { + URL string `json:"url,omitempty"` + Seq uint `json:"seq"` + Sig string `json:"signature,omitempty"` + Links []string `json:"links"` + LastModified time.Time `json:"lastModified"` +} + +func treeToDefinition(url string, t *dnsdisc.Tree) *dnsDefinition { + meta := dnsMetaJSON{ + URL: url, + Seq: t.Seq(), + Sig: t.Signature(), + Links: t.Links(), + } + if meta.Links == nil { + meta.Links = []string{} + } + return &dnsDefinition{Meta: meta, Nodes: t.Nodes()} +} + +// loadTreeDefinition loads a directory in 'definition' format. +func loadTreeDefinition(directory string) *dnsDefinition { + metaFile, nodesFile := treeDefinitionFiles(directory) + var def dnsDefinition + err := common.LoadJSON(metaFile, &def.Meta) + if err != nil && !os.IsNotExist(err) { + exit(err) + } + if def.Meta.Links == nil { + def.Meta.Links = []string{} + } + // Check link syntax. + for _, link := range def.Meta.Links { + if _, _, err := dnsdisc.ParseURL(link); err != nil { + exit(fmt.Errorf("invalid link %q: %v", link, err)) + } + } + // Check/convert nodes. + nodes := loadNodesJSON(nodesFile) + if err := nodes.verify(); err != nil { + exit(err) + } + def.Nodes = nodes.nodes() + return &def +} + +// loadTreeDefinitionForExport loads a DNS tree and ensures it is signed. +func loadTreeDefinitionForExport(dir string) (domain string, t *dnsdisc.Tree, err error) { + metaFile, _ := treeDefinitionFiles(dir) + def := loadTreeDefinition(dir) + if def.Meta.URL == "" { + return "", nil, fmt.Errorf("missing 'url' field in %v", metaFile) + } + domain, pubkey, err := dnsdisc.ParseURL(def.Meta.URL) + if err != nil { + return "", nil, fmt.Errorf("invalid 'url' field in %v: %v", metaFile, err) + } + if t, err = dnsdisc.MakeTree(def.Meta.Seq, def.Nodes, def.Meta.Links); err != nil { + return "", nil, err + } + if err := ensureValidTreeSignature(t, pubkey, def.Meta.Sig); err != nil { + return "", nil, err + } + return domain, t, nil +} + +// ensureValidTreeSignature checks that sig is valid for tree and assigns it as the +// tree's signature if valid. +func ensureValidTreeSignature(t *dnsdisc.Tree, pubkey *ecdsa.PublicKey, sig string) error { + if sig == "" { + return errors.New("missing signature, run 'devp2p dns sign' first") + } + if err := t.SetSignature(pubkey, sig); err != nil { + return errors.New("invalid signature on tree, run 'devp2p dns sign' to update it") + } + return nil +} + +// writeTreeMetadata writes a DNS node tree metadata file to the given directory. +func writeTreeMetadata(directory string, def *dnsDefinition) { + metaJSON, err := json.MarshalIndent(&def.Meta, "", jsonIndent) + if err != nil { + exit(err) + } + if err := os.Mkdir(directory, 0744); err != nil && !os.IsExist(err) { + exit(err) + } + metaFile, _ := treeDefinitionFiles(directory) + if err := os.WriteFile(metaFile, metaJSON, 0644); err != nil { + exit(err) + } +} + +func writeTreeNodes(directory string, def *dnsDefinition) { + ns := make(nodeSet, len(def.Nodes)) + ns.add(def.Nodes...) + _, nodesFile := treeDefinitionFiles(directory) + writeNodesJSON(nodesFile, ns) +} + +func treeDefinitionFiles(directory string) (string, string) { + meta := filepath.Join(directory, "enrtree-info.json") + nodes := filepath.Join(directory, "nodes.json") + return meta, nodes +} + +// writeTXTJSON writes TXT records in JSON format. +func writeTXTJSON(file string, txt map[string]string) { + txtJSON, err := json.MarshalIndent(txt, "", jsonIndent) + if err != nil { + exit(err) + } + if file == "-" { + os.Stdout.Write(txtJSON) + fmt.Println() + return + } + if err := os.WriteFile(file, txtJSON, 0644); err != nil { + exit(err) + } +} diff --git a/cmd/devp2p/enrcmd.go b/cmd/devp2p/enrcmd.go new file mode 100644 index 0000000..c9b6926 --- /dev/null +++ b/cmd/devp2p/enrcmd.go @@ -0,0 +1,209 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "io" + "net" + "os" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" + "github.com/urfave/cli/v2" +) + +var fileFlag = &cli.StringFlag{Name: "file"} + +var enrdumpCommand = &cli.Command{ + Name: "enrdump", + Usage: "Pretty-prints node records", + Action: enrdump, + Flags: []cli.Flag{ + fileFlag, + }, +} + +func enrdump(ctx *cli.Context) error { + var source string + if file := ctx.String(fileFlag.Name); file != "" { + if ctx.NArg() != 0 { + return errors.New("can't dump record from command-line argument in -file mode") + } + var b []byte + var err error + if file == "-" { + b, err = io.ReadAll(os.Stdin) + } else { + b, err = os.ReadFile(file) + } + if err != nil { + return err + } + source = string(b) + } else if ctx.NArg() == 1 { + source = ctx.Args().First() + } else { + return errors.New("need record as argument") + } + + r, err := parseRecord(source) + if err != nil { + return fmt.Errorf("INVALID: %v", err) + } + dumpRecord(os.Stdout, r) + return nil +} + +// dumpRecord creates a human-readable description of the given node record. +func dumpRecord(out io.Writer, r *enr.Record) { + n, err := enode.New(enode.ValidSchemes, r) + if err != nil { + fmt.Fprintf(out, "INVALID: %v\n", err) + } else { + fmt.Fprintf(out, "Node ID: %v\n", n.ID()) + dumpNodeURL(out, n) + } + kv := r.AppendElements(nil)[1:] + fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) + fmt.Fprint(out, dumpRecordKV(kv, 2)) +} + +func dumpNodeURL(out io.Writer, n *enode.Node) { + var key enode.Secp256k1 + if n.Load(&key) != nil { + return // no secp256k1 public key + } + fmt.Fprintf(out, "URLv4: %s\n", n.URLv4()) +} + +func dumpRecordKV(kv []interface{}, indent int) string { + // Determine the longest key name for alignment. + var out string + var longestKey = 0 + for i := 0; i < len(kv); i += 2 { + key := kv[i].(string) + if len(key) > longestKey { + longestKey = len(key) + } + } + // Print the keys, invoking formatters for known keys. + for i := 0; i < len(kv); i += 2 { + key := kv[i].(string) + val := kv[i+1].(rlp.RawValue) + pad := longestKey - len(key) + out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1) + formatter := attrFormatters[key] + if formatter == nil { + formatter = formatAttrRaw + } + fmtval, ok := formatter(val) + if ok { + out += fmtval + "\n" + } else { + out += hex.EncodeToString(val) + " (!)\n" + } + } + return out +} + +// parseNode parses a node record and verifies its signature. +func parseNode(source string) (*enode.Node, error) { + if strings.HasPrefix(source, "enode://") { + return enode.ParseV4(source) + } + r, err := parseRecord(source) + if err != nil { + return nil, err + } + return enode.New(enode.ValidSchemes, r) +} + +// parseRecord parses a node record from hex, base64, or raw binary input. +func parseRecord(source string) (*enr.Record, error) { + bin := []byte(source) + if d, ok := decodeRecordHex(bytes.TrimSpace(bin)); ok { + bin = d + } else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok { + bin = d + } + var r enr.Record + err := rlp.DecodeBytes(bin, &r) + return &r, err +} + +func decodeRecordHex(b []byte) ([]byte, bool) { + if bytes.HasPrefix(b, []byte("0x")) { + b = b[2:] + } + dec := make([]byte, hex.DecodedLen(len(b))) + _, err := hex.Decode(dec, b) + return dec, err == nil +} + +func decodeRecordBase64(b []byte) ([]byte, bool) { + if bytes.HasPrefix(b, []byte("enr:")) { + b = b[4:] + } + dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) + n, err := base64.RawURLEncoding.Decode(dec, b) + return dec[:n], err == nil +} + +// attrFormatters contains formatting functions for well-known ENR keys. +var attrFormatters = map[string]func(rlp.RawValue) (string, bool){ + "id": formatAttrString, + "ip": formatAttrIP, + "ip6": formatAttrIP, + "tcp": formatAttrUint, + "tcp6": formatAttrUint, + "udp": formatAttrUint, + "udp6": formatAttrUint, +} + +func formatAttrRaw(v rlp.RawValue) (string, bool) { + content, _, err := rlp.SplitString(v) + return hex.EncodeToString(content), err == nil +} + +func formatAttrString(v rlp.RawValue) (string, bool) { + content, _, err := rlp.SplitString(v) + return strconv.Quote(string(content)), err == nil +} + +func formatAttrIP(v rlp.RawValue) (string, bool) { + content, _, err := rlp.SplitString(v) + if err != nil || len(content) != 4 && len(content) != 6 { + return "", false + } + return net.IP(content).String(), true +} + +func formatAttrUint(v rlp.RawValue) (string, bool) { + var x uint64 + if err := rlp.DecodeBytes(v, &x); err != nil { + return "", false + } + return strconv.FormatUint(x, 10), true +} diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go new file mode 100644 index 0000000..2b503d6 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -0,0 +1,353 @@ +// 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 ( + "bytes" + "compress/gzip" + "crypto/ecdsa" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "os" + "path/filepath" + "slices" + "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" +) + +// Chain is a lightweight blockchain-like store which can read a hivechain +// created chain. +type Chain struct { + 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(filepath.Join(dir, "genesis.json")) + if err != nil { + return nil, err + } + gblock := gen.ToBlock() + + blocks, err := blocksFromFile(filepath.Join(dir, "chain.rlp"), gblock) + if err != nil { + return nil, err + } + state, err := readState(filepath.Join(dir, "headstate.json")) + if err != nil { + return nil, err + } + accounts, err := readAccounts(filepath.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. +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 { + sum := new(big.Int) + for _, block := range c.blocks[:c.Len()] { + 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() + } + return common.Hash{} +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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) +} + +// 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") + } + 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() + blockNumber = block.Number().Uint64() + } + } + 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 +} + +// 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]) + + config := *c.config + return &Chain{ + blocks: blocks, + config: &config, + } +} + +func loadGenesis(genesisFile string) (core.Genesis, error) { + chainConfig, err := os.ReadFile(genesisFile) + if err != nil { + return core.Genesis{}, err + } + var gen core.Genesis + if err := json.Unmarshal(chainConfig, &gen); err != nil { + return core.Genesis{}, err + } + 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) + if err != nil { + return nil, err + } + defer fh.Close() + var reader io.Reader = fh + if strings.HasSuffix(chainfile, ".gz") { + if reader, err = gzip.NewReader(reader); err != nil { + return nil, err + } + } + stream := rlp.NewStream(reader, 0) + var blocks = make([]*types.Block, 1) + blocks[0] = gblock + for i := 0; ; i++ { + var b types.Block + if err := stream.Decode(&b); err == io.EOF { + break + } else if err != nil { + return nil, fmt.Errorf("at block index %d: %v", i, err) + } + if b.NumberU64() != uint64(i+1) { + return nil, fmt.Errorf("block at index %d has wrong number %d", i, b.NumberU64()) + } + blocks = append(blocks, &b) + } + 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 new file mode 100644 index 0000000..62bd6d2 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -0,0 +1,200 @@ +// 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 ( + "path/filepath" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/p2p" + "github.com/stretchr/testify/assert" +) + +// 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 + expected uint32 + }{ + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 64, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: 64, + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 0}, + {Name: "eth", Version: 89}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 64, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "wrongProto", Version: 65}, + }, + expected: uint32(64), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "wrongProto", Version: 65}, + }, + expected: uint32(64), + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + tt.conn.negotiateEthProtocol(tt.caps) + assert.Equal(t, tt.expected, uint32(tt.conn.negotiatedProtoVersion)) + }) + } +} + +// TestChainGetHeaders tests whether the test suite can correctly +// respond to a GetBlockHeaders request from a node. +func TestChainGetHeaders(t *testing.T) { + t.Parallel() + + dir, err := filepath.Abs("./testdata") + if err != nil { + t.Fatal(err) + } + chain, err := NewChain(dir) + if err != nil { + t.Fatal(err) + } + + var tests = []struct { + req eth.GetBlockHeadersPacket + expected []*types.Header + }{ + { + req: eth.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{Number: uint64(2)}, + Amount: uint64(5), + Skip: 1, + Reverse: false, + }, + }, + expected: []*types.Header{ + chain.blocks[2].Header(), + chain.blocks[4].Header(), + chain.blocks[6].Header(), + chain.blocks[8].Header(), + chain.blocks[10].Header(), + }, + }, + { + req: eth.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)}, + Amount: uint64(3), + Skip: 0, + Reverse: true, + }, + }, + expected: []*types.Header{ + chain.blocks[chain.Len()-1].Header(), + chain.blocks[chain.Len()-2].Header(), + chain.blocks[chain.Len()-3].Header(), + }, + }, + { + req: eth.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{Hash: chain.Head().Hash()}, + Amount: uint64(1), + Skip: 0, + Reverse: false, + }, + }, + expected: []*types.Header{ + chain.Head().Header(), + }, + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + headers, err := chain.GetHeaders(&tt.req) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, headers, tt.expected) + }) + } +} diff --git a/cmd/devp2p/internal/ethtest/conn.go b/cmd/devp2p/internal/ethtest/conn.go new file mode 100644 index 0000000..757b137 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/conn.go @@ -0,0 +1,362 @@ +// 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) { + tcpEndpoint, _ := s.Dest.TCPEndpoint() + fd, err := net.Dial("tcp", tcpEndpoint.String()) + 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.NewPooledTransactionHashesPacket) + 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 0000000..0e94efa --- /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/filepath" + "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(filepath.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/mkchain.sh b/cmd/devp2p/internal/ethtest/mkchain.sh new file mode 100644 index 0000000..b9253e8 --- /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 0000000..f5f5f7e --- /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 new file mode 100644 index 0000000..4f1b6f8 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/snap.go @@ -0,0 +1,982 @@ +// 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 ( + "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/trie" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +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 { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } +} + +type accRangeTest struct { + 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 ( + 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) + ) + + tests := []accRangeTest{ + // Tests decreasing the number of bytes + { + 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 + { + 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 + + { + 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) + { + 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 + + { + 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. + { + 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 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 + origin []byte + limit []byte + nBytes uint64 + + expSlots [][]*snap.StorageData + + desc string +} + +// TestSnapGetStorageRanges various forms of GetStorageRanges requests. +func (s *Suite) TestSnapGetStorageRanges(t *utesting.T) { + var ( + acct = common.HexToAddress("0x8bebc8ba651aee624937e7d897853ac30c95a067") + acctHash = common.BytesToHash(s.chain.state[acct].AddressHash) + ffHash = common.MaxHash + zero = common.Hash{} + blockroot = s.chain.Head().Root() + ) + + // These are the storage slots of the test account, encoded as snap response data. + acctSlots := []*snap.StorageData{ + { + 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: + + "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] + 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: [][]*snap.StorageData{acctSlots}, + }, + + { // [slot1:] -> [slot1, slot2, slot3] + 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: 1000, + expSlots: [][]*snap.StorageData{acctSlots}, + }, + + { // [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: [][]*snap.StorageData{acctSlots[1:]}, + }, + + { // [slot1:slot2] -> [slot1, slot2] + 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: [][]*snap.StorageData{acctSlots[:2]}, + }, + + { // [slot1+:slot2+] -> [slot2, slot3] + 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: [][]*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(" failed: %v", err) + } + } +} + +type byteCodesTest struct { + nBytes uint64 + hashes []common.Hash + + expHashes int + + desc string +} + +// TestSnapGetByteCodes various forms of GetByteCodes requests. +func (s *Suite) TestSnapGetByteCodes(t *utesting.T) { + var ( + allHashes = s.chain.CodeHashes() + headRoot = s.chain.Head().Root() + genesisRoot = s.chain.RootAt(0) + ) + + tests := []byteCodesTest{ + // A few stateroots + { + 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, + }, + { + 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 + { + 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, + }, + { + 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, + }, + { + 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 + { + 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 + { + desc: `In this test, the request has a bytes limit of one. The server should deliver one item.`, + nBytes: 1, + hashes: allHashes, + expHashes: 1, + }, + { + 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. + { + 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("failed: %v", err) + } + } +} + +type trieNodesTest struct { + root common.Hash + paths []snap.TrieNodePathSet + nBytes uint64 + + expHashes []common.Hash // expected response + expReject bool // if true, request should be rejected + + desc string +} + +func decodeNibbles(nibbles []byte, bytes []byte) { + for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 { + bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1] + } +} + +// hasTerm returns whether a hex key has the terminator flag. +func hasTerm(s []byte) bool { + return len(s) > 0 && s[len(s)-1] == 16 +} + +func keybytesToHex(str []byte) []byte { + l := len(str)*2 + 1 + var nibbles = make([]byte, l) + for i, b := range str { + nibbles[i*2] = b / 16 + nibbles[i*2+1] = b % 16 + } + nibbles[l-1] = 16 + return nibbles +} + +func hexToCompact(hex []byte) []byte { + terminator := byte(0) + if hasTerm(hex) { + terminator = 1 + hex = hex[:len(hex)-1] + } + buf := make([]byte, len(hex)/2+1) + buf[0] = terminator << 5 // the flag byte + if len(hex)&1 == 1 { + buf[0] |= 1 << 4 // odd flag + buf[0] |= hex[0] // first nibble is contained in the first byte + hex = hex[1:] + } + decodeNibbles(hex, buf[1:]) + return buf +} + +// TestSnapTrieNodes various forms of GetTrieNodes requests. +func (s *Suite) TestSnapTrieNodes(t *utesting.T) { + 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, makeSnapPath(key, i)) + } + + tests := []trieNodesTest{ + { + desc: `In this test, we send an empty request to the node.`, + root: s.chain.Head().Root(), + paths: nil, + nBytes: 500, + expHashes: nil, + }, + + { + 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}}, + }, + nBytes: 5000, + expHashes: []common.Hash{}, + expReject: true, + }, + + { + 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, + expHashes: []common.Hash{s.chain.RootAt(int(s.chain.Head().NumberU64() - 1))}, + }, + + { // nonsensically long path + 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}}, + }, + nBytes: 5000, + expHashes: []common.Hash{types.EmptyCodeHash}, + }, + + { + // The leaf is only a couple of levels down, so the continued trie traversal causes lookup failures. + desc: `Here we request some known accounts from the state.`, + root: s.chain.Head().Root(), + paths: accPaths, + nBytes: 5000, + expHashes: []common.Hash{ + // 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, + 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}, + }, + + { + 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("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"), + common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"), + }, + }, + + // Storage tests. + // These use the known storage test account. + + { + desc: `This test requests the storage root node of a known account.`, + root: s.chain.Head().Root(), + paths: []snap.TrieNodePathSet{ + { + 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{ + { + storageAcctHash[:], + []byte{0}, + []byte{0x1b}, + }, + }, + nBytes: 5000, + expHashes: []common.Hash{ + common.HexToHash("0xbe3d75a1729be157e79c3b77f00206db4d54e3ea14375a015451c88ec067c790"), + common.HexToHash("0xf4984a11f61a2921456141df88de6e1a710d28681b91af794c5a721e47839cd7"), + }, + }, + } + + 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(" 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 { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // write request + req := &snap.GetAccountRangePacket{ + ID: uint64(rand.Int63()), + Root: tc.root, + Origin: tc.startingHash, + Limit: tc.limitHash, + Bytes: tc.nBytes, + } + msg, err := conn.snapRequest(snap.GetAccountRangeMsg, req) + if err != nil { + return fmt.Errorf("account range request failed: %v", err) + } + 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) + } + // Check that the encoding order is correct + for i := 1; i < len(res.Accounts); i++ { + if bytes.Compare(res.Accounts[i-1].Hash[:], res.Accounts[i].Hash[:]) >= 0 { + return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:]) + } + } + var ( + hashes []common.Hash + accounts [][]byte + proof = res.Proof + ) + hashes, accounts, err = res.Unpack() + if err != nil { + return err + } + if len(hashes) == 0 && len(accounts) == 0 && len(proof) == 0 { + return nil + } + if len(hashes) > 0 { + if exp, got := tc.expFirst, res.Accounts[0].Hash; exp != got { + return fmt.Errorf("expected first account %#x, got %#x", exp, got) + } + if exp, got := tc.expLast, res.Accounts[len(res.Accounts)-1].Hash; exp != got { + return fmt.Errorf("expected last account %#x, got %#x", exp, got) + } + } + // Reconstruct a partial trie from the response and verify it + keys := make([][]byte, len(hashes)) + for i, key := range hashes { + keys[i] = common.CopyBytes(key[:]) + } + nodes := make(trienode.ProofList, len(proof)) + for i, node := range proof { + nodes[i] = node + } + proofdb := nodes.Set() + + _, err = trie.VerifyRangeProof(tc.root, tc.startingHash[:], keys, accounts, proofdb) + return err +} + +func (s *Suite) snapGetStorageRanges(t *utesting.T, tc *stRangesTest) error { + conn, err := s.dialSnap() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + + // write request + req := &snap.GetStorageRangesPacket{ + ID: uint64(rand.Int63()), + Root: tc.root, + Accounts: tc.accounts, + Origin: tc.origin, + Limit: tc.limit, + Bytes: tc.nBytes, + } + msg, err := conn.snapRequest(snap.GetStorageRangesMsg, req) + if err != nil { + return fmt.Errorf("account range request failed: %v", err) + } + res, ok := msg.(*snap.StorageRangesPacket) + if !ok { + return fmt.Errorf("account range response wrong: %T %v", msg, msg) + } + + // Ensure the ranges are monotonically increasing + for i, slots := range res.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[:]) + } + } + } + + // 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 +} + +func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error { + conn, err := s.dialSnap() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // write request + req := &snap.GetByteCodesPacket{ + ID: uint64(rand.Int63()), + Hashes: tc.hashes, + Bytes: tc.nBytes, + } + msg, err := conn.snapRequest(snap.GetByteCodesMsg, req) + if err != nil { + return fmt.Errorf("getBytecodes request failed: %v", err) + } + 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 { + t.Logf("%d. %#x\n", i, c) + } + return fmt.Errorf("expected %d bytecodes, got %d", exp, got) + } + // Cross reference the requested bytecodes with the response to find gaps + // that the serving node is missing + var ( + bytecodes = res.Codes + hasher = crypto.NewKeccakState() + hash = make([]byte, 32) + codes = make([][]byte, len(req.Hashes)) + ) + + for i, j := 0, 0; i < len(bytecodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + + for j < len(req.Hashes) && !bytes.Equal(hash, req.Hashes[j][:]) { + j++ + } + if j < len(req.Hashes) { + codes[j] = bytecodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + return errors.New("unexpected bytecode") + } + + return nil +} + +func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error { + conn, err := s.dialSnap() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + + // write0 request + req := &snap.GetTrieNodesPacket{ + ID: uint64(rand.Int63()), + Root: tc.root, + Paths: tc.paths, + Bytes: tc.nBytes, + } + msg, err := conn.snapRequest(snap.GetTrieNodesMsg, req) + if err != nil { + if tc.expReject { + return nil + } + return fmt.Errorf("trienodes request failed: %v", err) + } + res, ok := msg.(*snap.TrieNodesPacket) + if !ok { + return fmt.Errorf("trienodes response wrong: %T %v", msg, msg) + } + + // Check the correctness + + // Cross reference the requested trienodes with the response to find gaps + // that the serving node is missing + hasher := crypto.NewKeccakState() + 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", 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[:]) { + 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) + } + } + return err +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go new file mode 100644 index 0000000..b5cc27a --- /dev/null +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -0,0 +1,856 @@ +// 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" + "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 + 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, 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, + engine: engine, + }, nil +} + +func (s *Suite) EthTests() []utesting.Test { + return []utesting.Test{ + // status + {Name: "Status", Fn: s.TestStatus}, + // get block headers + {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, + {Name: "SimultaneousRequests", Fn: s.TestSimultaneousRequests}, + {Name: "SameRequestID", Fn: s.TestSameRequestID}, + {Name: "ZeroRequestID", Fn: s.TestZeroRequestID}, + // get block bodies + {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, + // // malicious handshakes + status + {Name: "MaliciousHandshake", Fn: s.TestMaliciousHandshake}, + {Name: "MaliciousStatus", Fn: s.TestMaliciousStatus}, + // test transactions + {Name: "LargeTxRequest", Fn: s.TestLargeTxRequest, Slow: true}, + {Name: "Transaction", Fn: s.TestTransaction}, + {Name: "InvalidTxs", Fn: s.TestInvalidTxs}, + {Name: "NewPooledTxs", Fn: s.TestNewPooledTxs}, + {Name: "BlobViolations", Fn: s.TestBlobViolations}, + } +} + +func (s *Suite) SnapTests() []utesting.Test { + return []utesting.Test{ + {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}, + } +} + +func (s *Suite) TestStatus(t *utesting.T) { + t.Log(`This test is just a sanity check. It performs an eth protocol handshake.`) + + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } +} + +// headersMatch returns whether the received headers match the given request +func headersMatch(expected []*types.Header, headers []*types.Header) bool { + return reflect.DeepEqual(expected, headers) +} + +func (s *Suite) TestGetBlockHeaders(t *utesting.T) { + t.Log(`This test requests block headers from the node.`) + + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // Send headers request. + req := ð.GetBlockHeadersPacket{ + RequestId: 33, + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{Hash: s.chain.blocks[1].Hash()}, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + // 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) + } + 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.BlockHeadersRequest) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) + } +} + +func (s *Suite) TestSimultaneousRequests(t *utesting.T) { + t.Log(`This test requests blocks headers from the node, performing two requests +concurrently, with different request IDs.`) + + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + + // Create two different requests. + req1 := ð.GetBlockHeadersPacket{ + RequestId: uint64(111), + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + req2 := ð.GetBlockHeadersPacket{ + RequestId: uint64(222), + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 4, + Skip: 1, + Reverse: false, + }, + } + + // Send both requests. + if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req1); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req2); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + + // 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) + } + 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. + 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) + } + if expected, err := s.chain.GetHeaders(req2); err != nil { + t.Fatalf("failed to get expected headers for request 2: %v", err) + } else if !headersMatch(expected, headers2.BlockHeadersRequest) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2) + } +} + +func (s *Suite) TestSameRequestID(t *utesting.T) { + t.Log(`This test requests block headers, performing two concurrent requests with the +same request ID. The node should handle the request by responding to both requests.`) + + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + + // Create two different requests with the same ID. + reqID := uint64(1234) + request1 := ð.GetBlockHeadersPacket{ + RequestId: reqID, + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{ + Number: 1, + }, + Amount: 2, + }, + } + request2 := ð.GetBlockHeadersPacket{ + RequestId: reqID, + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{ + Number: 33, + }, + Amount: 2, + }, + } + + // 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(ethProto, eth.GetBlockHeadersMsg, request2); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + + // 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) + } + if got, want := headers2.RequestId, request2.RequestId; got != want { + t.Fatalf("unexpected request id: got %d, want %d", got, want) + } + + // 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) + } + if expected, err := s.chain.GetHeaders(request2); err != nil { + t.Fatalf("failed to get expected block headers: %v", err) + } else if !headersMatch(expected, headers2.BlockHeadersRequest) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2) + } +} + +func (s *Suite) TestZeroRequestID(t *utesting.T) { + t.Log(`This test sends a GetBlockHeaders message with a request-id of zero, +and expects a response.`) + + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + req := ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{Number: 0}, + Amount: 2, + }, + } + // 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) + } + if got, want := headers.RequestId, req.RequestId; got != want { + t.Fatalf("unexpected request id") + } + 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) + } +} + +func (s *Suite) TestGetBlockBodies(t *utesting.T) { + t.Log(`This test sends GetBlockBodies requests to the node for known blocks in the test chain.`) + + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // 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(ethProto, eth.GetBlockBodiesMsg, req); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // 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.BlockBodiesResponse + if len(bodies) != len(req.GetBlockBodiesRequest) { + t.Fatalf("wrong bodies in response: expected %d bodies, got %d", len(req.GetBlockBodiesRequest), len(bodies)) + } +} + +// randBuf makes a random buffer size kilobytes large. +func randBuf(size int) []byte { + buf := make([]byte, size*1024) + rand.Read(buf) + return buf +} + +func (s *Suite) TestMaliciousHandshake(t *utesting.T) { + t.Log(`This test tries to send malicious data during the devp2p handshake, in various ways.`) + + // Write hello to client. + var ( + key, _ = crypto.GenerateKey() + pub0 = crypto.FromECDSAPub(&key.PublicKey)[1:] + version = eth.ProtocolVersions[0] + ) + handshakes := []*protoHandshake{ + { + Version: 5, + Caps: []p2p.Cap{ + {Name: string(randBuf(2)), Version: version}, + }, + ID: pub0, + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: version}, + }, + ID: append(pub0, byte(0)), + }, + { + 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 _, handshake := range handshakes { + conn, err := s.dialAs(key) + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + + if err := conn.Write(ethProto, handshakeMsg, handshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // 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) + } + } + } +} + +func (s *Suite) TestMaliciousStatus(t *utesting.T) { + t.Log(`This test sends a malicious eth Status message to the node and expects a disconnect.`) + + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + 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) + } +} + +func (s *Suite) TestTransaction(t *utesting.T) { + t.Log(`This test sends a valid transaction to the node and checks if the +transaction gets propagated.`) + + // 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(t, []*types.Transaction{tx}); err != nil { + t.Fatal(err) + } + s.chain.IncNonce(from, 1) +} + +func (s *Suite) TestInvalidTxs(t *utesting.T) { + t.Log(`This test sends several kinds of invalid transactions and checks that the node +does not propagate them.`) + + // 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(t, []*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(t, txs); err != nil { + t.Fatalf("failed to send invalid txs: %v", err) + } +} + +func (s *Suite) TestLargeTxRequest(t *utesting.T) { + t.Log(`This test first send ~2000 transactions to the node, then requests them +on another peer connection using GetPooledTransactions.`) + + // 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 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()) + } + s.chain.IncNonce(from, uint64(count)) + + // Send txs. + if err := s.sendTxs(t, txs); err != nil { + t.Fatalf("failed to send txs: %v", err) + } + + // 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) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // Create and send pooled tx request. + req := ð.GetPooledTransactionsPacket{ + RequestId: 1234, + GetPooledTransactionsRequest: hashes, + } + 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. + 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()) + } + } +} + +func (s *Suite) TestNewPooledTxs(t *utesting.T) { + t.Log(`This test announces transaction hashes to the node and expects it to fetch +the transactions using a GetPooledTransactions request.`) + + // 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) + } + + 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() + txTypes[i] = tx.Type() + sizes[i] = uint32(tx.Size()) + } + s.chain.IncNonce(from, uint64(count)) + + // Connect to peer. + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + + // Send announcement. + ann := eth.NewPooledTransactionHashesPacket{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. + for { + msg, err := conn.ReadEth() + if err != nil { + t.Fatalf("failed to read eth msg: %v", err) + } + switch msg := msg.(type) { + 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 + case *eth.NewPooledTransactionHashesPacket: + continue + 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%3) + 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) { + t.Log(`This test sends some invalid blob tx announcements and expects the node to disconnect.`) + + 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.NewPooledTransactionHashesPacket + resp eth.PooledTransactionsResponse + }{ + // Invalid tx size. + { + ann: eth.NewPooledTransactionHashesPacket{ + 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.NewPooledTransactionHashesPacket{ + 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 new file mode 100644 index 0000000..d70adda --- /dev/null +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -0,0 +1,153 @@ +// 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 ( + crand "crypto/rand" + "fmt" + "os" + "path/filepath" + "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" +) + +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 := filepath.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) { + 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(), "./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) { + if test.Slow && testing.Short() { + t.Skipf("%s: skipping in -short mode", test.Name) + } + result := utesting.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) + if result[0].Failed { + t.Fatal() + } + }) + } +} + +func TestSnapSuite(t *testing.T) { + 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(), "./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.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) + if result[0].Failed { + t.Fatal() + } + }) + } +} + +// runGeth creates and starts a geth node +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, dir) + if err != nil { + stack.Close() + return nil, err + } + if err = stack.Start(); err != nil { + stack.Close() + return nil, err + } + return stack, nil +} + +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 + DatabaseCache: 10, + TrieCleanCache: 10, + TrieDirtyCache: 16, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 10, + }) + 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 0000000..c966623 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..2964c02bb1fb7f695fe6eb9c1c113f9db0b7c97b GIT binary patch literal 341951 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

BUlu*YmpL9FoEf$3g&@_?xj1~>XJ8%0Z+ETB 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. + +package ethtest + +import ( + "errors" + "fmt" + "os" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" +) + +// sendTxs sends the given transactions to the node and +// expects the node to accept and propagate them. +func (s *Suite) sendTxs(t *utesting.T, txs []*types.Transaction) error { + // Open sending conn. + sendConn, err := s.dial() + if err != nil { + return err + } + defer sendConn.Close() + if err = sendConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + + // Open receiving conn. + recvConn, err := s.dial() + if err != nil { + return err + } + defer recvConn.Close() + if err = recvConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + + if err = sendConn.Write(ethProto, eth.TransactionsMsg, eth.TransactionsPacket(txs)); err != nil { + return fmt.Errorf("failed to write message to connection: %v", err) + } + + var ( + got = make(map[common.Hash]bool) + end = time.Now().Add(timeout) + ) + + // 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 { + got[tx.Hash()] = true + } + case *eth.NewPooledTransactionHashesPacket: + for _, hash := range msg.Hashes { + got[hash] = true + } + case *eth.GetBlockHeadersPacket: + headers, err := s.chain.GetHeaders(msg) + if err != nil { + t.Logf("invalid GetBlockHeaders request: %v", err) + } + recvConn.Write(ethProto, eth.BlockHeadersMsg, ð.BlockHeadersPacket{ + RequestId: msg.RequestId, + BlockHeadersRequest: headers, + }) + default: + return fmt.Errorf("unexpected eth wire msg: %s", pretty.Sdump(msg)) + } + + // Check if all txs received. + allReceived := func() bool { + for _, tx := range txs { + if !got[tx.Hash()] { + return false + } + } + return true + } + if allReceived() { + return nil + } + } + + return errors.New("timed out waiting for txs") +} + +func (s *Suite) sendInvalidTxs(t *utesting.T, txs []*types.Transaction) error { + // Open sending conn. + sendConn, err := s.dial() + if err != nil { + return err + } + defer sendConn.Close() + if err = sendConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + sendConn.SetDeadline(time.Now().Add(timeout)) + + // Open receiving conn. + recvConn, err := s.dial() + if err != nil { + return err + } + defer recvConn.Close() + if err = recvConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + recvConn.SetDeadline(time.Now().Add(timeout)) + + if err = sendConn.Write(ethProto, eth.TransactionsMsg, txs); err != nil { + return fmt.Errorf("failed to write message to connection: %w", err) + } + + // Make map of invalid txs. + invalids := make(map[common.Hash]struct{}) + for _, tx := range txs { + invalids[tx.Hash()] = struct{}{} + } + + // 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.NewPooledTransactionHashesPacket: + for _, hash := range msg.Hashes { + if _, ok := invalids[hash]; ok { + return fmt.Errorf("received bad tx: %s", hash) + } + } + case *eth.GetBlockHeadersPacket: + headers, err := s.chain.GetHeaders(msg) + if err != nil { + t.Logf("invalid GetBlockHeaders request: %v", err) + } + recvConn.Write(ethProto, eth.BlockHeadersMsg, ð.BlockHeadersPacket{ + RequestId: msg.RequestId, + BlockHeadersRequest: headers, + }) + default: + return fmt.Errorf("unexpected eth message: %v", pretty.Sdump(msg)) + } + } +} diff --git a/cmd/devp2p/internal/v4test/discv4tests.go b/cmd/devp2p/internal/v4test/discv4tests.go new file mode 100644 index 0000000..ca55685 --- /dev/null +++ b/cmd/devp2p/internal/v4test/discv4tests.go @@ -0,0 +1,519 @@ +// 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 v4test + +import ( + "bytes" + "crypto/rand" + "errors" + "fmt" + "net" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p/discover/v4wire" +) + +const ( + expiration = 20 * time.Second + wrongPacket = 66 + macSize = 256 / 8 +) + +var ( + // Remote node under test + Remote string + // Listen1 is the IP where the first tester is listening, port will be assigned + Listen1 string = "127.0.0.1" + // Listen2 is the IP where the second tester is listening, port will be assigned + // Before running the test, you may have to `sudo ifconfig lo0 add 127.0.0.2` (on MacOS at least) + Listen2 string = "127.0.0.2" +) + +type pingWithJunk struct { + Version uint + From, To v4wire.Endpoint + Expiration uint64 + JunkData1 uint + JunkData2 []byte +} + +func (req *pingWithJunk) Name() string { return "PING/v4" } +func (req *pingWithJunk) Kind() byte { return v4wire.PingPacket } + +type pingWrongType struct { + Version uint + From, To v4wire.Endpoint + Expiration uint64 +} + +func (req *pingWrongType) Name() string { return "WRONG/v4" } +func (req *pingWrongType) Kind() byte { return wrongPacket } + +func futureExpiration() uint64 { + return uint64(time.Now().Add(expiration).Unix()) +} + +// BasicPing just sends a PING packet and expects a response. +func BasicPing(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + if err := te.checkPingPong(pingHash); err != nil { + t.Fatal(err) + } +} + +// checkPingPong verifies that the remote side sends both a PONG with the +// correct hash, and a PING. +// The two packets do not have to be in any particular order. +func (te *testenv) checkPingPong(pingHash []byte) error { + var ( + pings int + pongs int + ) + for i := 0; i < 2; i++ { + reply, _, err := te.read(te.l1) + if err != nil { + return err + } + switch reply.Kind() { + case v4wire.PongPacket: + if err := te.checkPong(reply, pingHash); err != nil { + return err + } + pongs++ + case v4wire.PingPacket: + pings++ + default: + return fmt.Errorf("expected PING or PONG, got %v %v", reply.Name(), reply) + } + } + if pongs == 1 && pings == 1 { + return nil + } + return fmt.Errorf("expected 1 PING (got %d) and 1 PONG (got %d)", pings, pongs) +} + +// checkPong verifies that reply is a valid PONG matching the given ping hash, +// and a PING. The two packets do not have to be in any particular order. +func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error { + if reply == nil { + return errors.New("expected PONG reply, got nil") + } + if reply.Kind() != v4wire.PongPacket { + return fmt.Errorf("expected PONG reply, got %v %v", reply.Name(), reply) + } + pong := reply.(*v4wire.Pong) + if !bytes.Equal(pong.ReplyTok, pingHash) { + return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash) + } + if want := te.localEndpoint(te.l1); !want.IP.Equal(pong.To.IP) || want.UDP != pong.To.UDP { + return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, want) + } + if v4wire.Expired(pong.Expiration) { + return fmt.Errorf("PONG is expired (%v)", pong.Expiration) + } + return nil +} + +// PingWrongTo sends a PING packet with wrong 'to' field and expects a PONG response. +func PingWrongTo(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: wrongEndpoint, + Expiration: futureExpiration(), + }) + if err := te.checkPingPong(pingHash); err != nil { + t.Fatal(err) + } +} + +// PingWrongFrom sends a PING packet with wrong 'from' field and expects a PONG response. +func PingWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + if err := te.checkPingPong(pingHash); err != nil { + t.Fatal(err) + } +} + +// PingExtraData This test sends a PING packet with additional data at the end and expects a PONG +// response. The remote node should respond because EIP-8 mandates ignoring additional +// trailing data. +func PingExtraData(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + pingHash := te.send(te.l1, &pingWithJunk{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + JunkData1: 42, + JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, + }) + + if err := te.checkPingPong(pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with additional data and wrong 'from' field +// and expects a PONG response. +func PingExtraDataWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + req := pingWithJunk{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + JunkData1: 42, + JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + pingHash := te.send(te.l1, &req) + if err := te.checkPingPong(pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with an expiration in the past. +// The remote node should not respond. +func PingPastExpiration(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: -futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatalf("Expected no reply, got %v %v", reply.Name(), reply) + } +} + +// This test sends an invalid packet. The remote node should not respond. +func WrongPacketType(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + te.send(te.l1, &pingWrongType{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatalf("Expected no reply, got %v %v", reply.Name(), reply) + } +} + +// This test verifies that the default behaviour of ignoring 'from' fields is unaffected by +// the bonding process. After bonding, it pings the target with a different from endpoint. +func BondThenPingWithWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + bond(t, te) + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + +waitForPong: + for { + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch reply.Kind() { + case v4wire.PongPacket: + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } + break waitForPong + case v4wire.FindnodePacket: + // FINDNODE from the node is acceptable here since the endpoint + // verification was performed earlier. + default: + t.Fatalf("Expected PONG, got %v %v", reply.Name(), reply) + } + } +} + +// This test just sends FINDNODE. The remote node should not reply +// because the endpoint proof has not completed. +func FindnodeWithoutEndpointProof(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + req := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(req.Target[:]) + te.send(te.l1, &req) + + for { + reply, _, _ := te.read(te.l1) + if reply == nil { + // No response, all good + break + } + if reply.Kind() == v4wire.PingPacket { + continue // A ping is ok, just ignore it + } + t.Fatalf("Expected no reply, got %v %v", reply.Name(), reply) + } +} + +// BasicFindnode sends a FINDNODE request after performing the endpoint +// proof. The remote node should respond. +func BasicFindnode(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read find nodes", err) + } + if reply.Kind() != v4wire.NeighborsPacket { + t.Fatalf("Expected neighbors, got %v %v", reply.Name(), reply) + } +} + +// This test sends an unsolicited NEIGHBORS packet after the endpoint proof, then sends +// FINDNODE to read the remote table. The remote node should not return the node contained +// in the unsolicited NEIGHBORS packet. +func UnsolicitedNeighbors(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + // Send unsolicited NEIGHBORS response. + fakeKey, _ := crypto.GenerateKey() + encFakeKey := v4wire.EncodePubkey(&fakeKey.PublicKey) + neighbors := v4wire.Neighbors{ + Expiration: futureExpiration(), + Nodes: []v4wire.Node{{ + ID: encFakeKey, + IP: net.IP{1, 2, 3, 4}, + UDP: 30303, + TCP: 30303, + }}, + } + te.send(te.l1, &neighbors) + + // Check if the remote node included the fake node. + te.send(te.l1, &v4wire.Findnode{ + Expiration: futureExpiration(), + Target: encFakeKey, + }) + + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read find nodes", err) + } + if reply.Kind() != v4wire.NeighborsPacket { + t.Fatalf("Expected neighbors, got %v %v", reply.Name(), reply) + } + nodes := reply.(*v4wire.Neighbors).Nodes + if contains(nodes, encFakeKey) { + t.Fatal("neighbors response contains node from earlier unsolicited neighbors response") + } +} + +// This test sends FINDNODE with an expiration timestamp in the past. +// The remote node should not respond. +func FindnodePastExpiration(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + findnode := v4wire.Findnode{Expiration: -futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + for { + reply, _, _ := te.read(te.l1) + if reply == nil { + return + } else if reply.Kind() == v4wire.NeighborsPacket { + t.Fatal("Unexpected NEIGHBORS response for expired FINDNODE request") + } + } +} + +// bond performs the endpoint proof with the remote node. +func bond(t *utesting.T, te *testenv) { + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + var gotPing, gotPong bool + for !gotPing || !gotPong { + req, hash, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch req.(type) { + case *v4wire.Ping: + te.send(te.l1, &v4wire.Pong{ + To: te.remoteEndpoint(), + ReplyTok: hash, + Expiration: futureExpiration(), + }) + gotPing = true + case *v4wire.Pong: + if err := te.checkPong(req, pingHash); err != nil { + t.Fatal(err) + } + gotPong = true + } + } +} + +// This test attempts to perform a traffic amplification attack against a +// 'victim' endpoint using FINDNODE. In this attack scenario, the attacker +// attempts to complete the endpoint proof non-interactively by sending a PONG +// with mismatching reply token from the 'victim' endpoint. The attack works if +// the remote node does not verify the PONG reply token field correctly. The +// attacker could then perform traffic amplification by sending many FINDNODE +// requests to the discovery node, which would reply to the 'victim' address. +func FindnodeAmplificationInvalidPongHash(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + // Send PING to start endpoint verification. + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + var gotPing, gotPong bool + for !gotPing || !gotPong { + req, _, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch req.(type) { + case *v4wire.Ping: + // Send PONG from this node ID, but with invalid ReplyTok. + te.send(te.l1, &v4wire.Pong{ + To: te.remoteEndpoint(), + ReplyTok: make([]byte, macSize), + Expiration: futureExpiration(), + }) + gotPing = true + case *v4wire.Pong: + gotPong = true + } + } + + // Now send FINDNODE. The remote node should not respond because our + // PONG did not reference the PING hash. + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + // If we receive a NEIGHBORS response, the attack worked and the test fails. + reply, _, _ := te.read(te.l1) + if reply != nil && reply.Kind() == v4wire.NeighborsPacket { + t.Error("Got neighbors") + } +} + +// This test attempts to perform a traffic amplification attack using FINDNODE. +// The attack works if the remote node does not verify the IP address of FINDNODE +// against the endpoint verification proof done by PING/PONG. +func FindnodeAmplificationWrongIP(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + // Do the endpoint proof from the l1 IP. + bond(t, te) + + // Now send FINDNODE from the same node ID, but different IP address. + // The remote node should not respond. + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l2, &findnode) + + // If we receive a NEIGHBORS response, the attack worked and the test fails. + reply, _, _ := te.read(te.l2) + if reply != nil { + t.Error("Got NEIGHBORS response for FINDNODE from wrong IP") + } +} + +var AllTests = []utesting.Test{ + {Name: "Ping/Basic", Fn: BasicPing}, + {Name: "Ping/WrongTo", Fn: PingWrongTo}, + {Name: "Ping/WrongFrom", Fn: PingWrongFrom}, + {Name: "Ping/ExtraData", Fn: PingExtraData}, + {Name: "Ping/ExtraDataWrongFrom", Fn: PingExtraDataWrongFrom}, + {Name: "Ping/PastExpiration", Fn: PingPastExpiration}, + {Name: "Ping/WrongPacketType", Fn: WrongPacketType}, + {Name: "Ping/BondThenPingWithWrongFrom", Fn: BondThenPingWithWrongFrom}, + {Name: "Findnode/WithoutEndpointProof", Fn: FindnodeWithoutEndpointProof}, + {Name: "Findnode/BasicFindnode", Fn: BasicFindnode}, + {Name: "Findnode/UnsolicitedNeighbors", Fn: UnsolicitedNeighbors}, + {Name: "Findnode/PastExpiration", Fn: FindnodePastExpiration}, + {Name: "Amplification/InvalidPongHash", Fn: FindnodeAmplificationInvalidPongHash}, + {Name: "Amplification/WrongIP", Fn: FindnodeAmplificationWrongIP}, +} diff --git a/cmd/devp2p/internal/v4test/framework.go b/cmd/devp2p/internal/v4test/framework.go new file mode 100644 index 0000000..958fb71 --- /dev/null +++ b/cmd/devp2p/internal/v4test/framework.go @@ -0,0 +1,125 @@ +// 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 v4test + +import ( + "crypto/ecdsa" + "fmt" + "net" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/discover/v4wire" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +const waitTime = 300 * time.Millisecond + +type testenv struct { + l1, l2 net.PacketConn + key *ecdsa.PrivateKey + remote *enode.Node + remoteAddr *net.UDPAddr +} + +func newTestEnv(remote string, listen1, listen2 string) *testenv { + l1, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen1)) + if err != nil { + panic(err) + } + l2, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen2)) + if err != nil { + panic(err) + } + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + node, err := enode.Parse(enode.ValidSchemes, remote) + if err != nil { + panic(err) + } + if !node.IPAddr().IsValid() || node.UDP() == 0 { + var ip net.IP + var tcpPort, udpPort int + if node.IPAddr().IsValid() { + ip = node.IPAddr().AsSlice() + } else { + ip = net.ParseIP("127.0.0.1") + } + if tcpPort = node.TCP(); tcpPort == 0 { + tcpPort = 30303 + } + if udpPort = node.UDP(); udpPort == 0 { + udpPort = 30303 + } + node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort) + } + addr := &net.UDPAddr{IP: node.IP(), Port: node.UDP()} + return &testenv{l1, l2, key, node, addr} +} + +func (te *testenv) close() { + te.l1.Close() + te.l2.Close() +} + +func (te *testenv) send(c net.PacketConn, req v4wire.Packet) []byte { + packet, hash, err := v4wire.Encode(te.key, req) + if err != nil { + panic(fmt.Errorf("can't encode %v packet: %v", req.Name(), err)) + } + if _, err := c.WriteTo(packet, te.remoteAddr); err != nil { + panic(fmt.Errorf("can't send %v: %v", req.Name(), err)) + } + return hash +} + +func (te *testenv) read(c net.PacketConn) (v4wire.Packet, []byte, error) { + buf := make([]byte, 2048) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { + return nil, nil, err + } + n, _, err := c.ReadFrom(buf) + if err != nil { + return nil, nil, err + } + p, _, hash, err := v4wire.Decode(buf[:n]) + return p, hash, err +} + +func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint { + addr := c.LocalAddr().(*net.UDPAddr) + return v4wire.Endpoint{ + IP: addr.IP.To4(), + UDP: uint16(addr.Port), + TCP: 0, + } +} + +func (te *testenv) remoteEndpoint() v4wire.Endpoint { + return v4wire.NewEndpoint(te.remoteAddr.AddrPort(), 0) +} + +func contains(ns []v4wire.Node, key v4wire.Pubkey) bool { + for _, n := range ns { + if n.ID == key { + return true + } + } + return false +} diff --git a/cmd/devp2p/internal/v5test/discv5tests.go b/cmd/devp2p/internal/v5test/discv5tests.go new file mode 100644 index 0000000..7dbd3c3 --- /dev/null +++ b/cmd/devp2p/internal/v5test/discv5tests.go @@ -0,0 +1,378 @@ +// 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 v5test + +import ( + "bytes" + "net" + "slices" + "sync" + "time" + + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/netutil" +) + +// Suite is the discv5 test suite. +type Suite struct { + Dest *enode.Node + Listen1, Listen2 string // listening addresses +} + +func (s *Suite) listen1(log logger) (*conn, net.PacketConn) { + c := newConn(s.Dest, log) + l := c.listen(s.Listen1) + return c, l +} + +func (s *Suite) listen2(log logger) (*conn, net.PacketConn, net.PacketConn) { + c := newConn(s.Dest, log) + l1, l2 := c.listen(s.Listen1), c.listen(s.Listen2) + return c, l1, l2 +} + +func (s *Suite) AllTests() []utesting.Test { + return []utesting.Test{ + {Name: "Ping", Fn: s.TestPing}, + {Name: "PingLargeRequestID", Fn: s.TestPingLargeRequestID}, + {Name: "PingMultiIP", Fn: s.TestPingMultiIP}, + {Name: "PingHandshakeInterrupted", Fn: s.TestPingHandshakeInterrupted}, + {Name: "TalkRequest", Fn: s.TestTalkRequest}, + {Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance}, + {Name: "FindnodeResults", Fn: s.TestFindnodeResults}, + } +} + +// TestPing sends PING and expects a PONG response. +func (s *Suite) TestPing(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + switch resp := conn.reqresp(l1, ping).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping, l1) + default: + t.Fatal("expected PONG, got", resp.Name()) + } +} + +func checkPong(t *utesting.T, pong *v5wire.Pong, ping *v5wire.Ping, c net.PacketConn) { + if !bytes.Equal(pong.ReqID, ping.ReqID) { + t.Fatalf("wrong request ID %x in PONG, want %x", pong.ReqID, ping.ReqID) + } + if !pong.ToIP.Equal(laddr(c).IP) { + t.Fatalf("wrong destination IP %v in PONG, want %v", pong.ToIP, laddr(c).IP) + } + if int(pong.ToPort) != laddr(c).Port { + t.Fatalf("wrong destination port %v in PONG, want %v", pong.ToPort, laddr(c).Port) + } +} + +// TestPingLargeRequestID sends PING with a 9-byte request ID, which isn't allowed by the spec. +// The remote node should not respond. +func (s *Suite) TestPingLargeRequestID(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + ping := &v5wire.Ping{ReqID: make([]byte, 9)} + switch resp := conn.reqresp(l1, ping).(type) { + case *v5wire.Pong: + t.Errorf("PONG response with unknown request ID %x", resp.ReqID) + case *readError: + if resp.err == v5wire.ErrInvalidReqID { + t.Error("response with oversized request ID") + } else if !netutil.IsTimeout(resp.err) { + t.Error(resp) + } + } +} + +// TestPingMultiIP establishes a session from one IP as usual. The session is then reused +// on another IP, which shouldn't work. The remote node should respond with WHOAREYOU for +// the attempt from a different IP. +func (s *Suite) TestPingMultiIP(t *utesting.T) { + conn, l1, l2 := s.listen2(t) + defer conn.close() + + // Create the session on l1. + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + resp := conn.reqresp(l1, ping) + if resp.Kind() != v5wire.PongMsg { + t.Fatal("expected PONG, got", resp) + } + checkPong(t, resp.(*v5wire.Pong), ping, l1) + + // Send on l2. This reuses the session because there is only one codec. + ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l2, ping2, nil) + switch resp := conn.read(l2).(type) { + case *v5wire.Pong: + t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l2).IP, laddr(l1).IP) + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for new session as expected") + resp.Node = s.Dest + conn.write(l2, ping2, resp) + default: + t.Fatal("expected WHOAREYOU, got", resp) + } + + // Catch the PONG on l2. + switch resp := conn.read(l2).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping2, l2) + default: + t.Fatal("expected PONG, got", resp) + } + + // Try on l1 again. + ping3 := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l1, ping3, nil) + switch resp := conn.read(l1).(type) { + case *v5wire.Pong: + t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l1).IP, laddr(l2).IP) + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for new session as expected") + default: + t.Fatal("expected WHOAREYOU, got", resp) + } +} + +// TestPingHandshakeInterrupted starts a handshake, but doesn't finish it and sends a second ordinary message +// packet instead of a handshake message packet. The remote node should respond with +// another WHOAREYOU challenge for the second packet. +func (s *Suite) TestPingHandshakeInterrupted(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + // First PING triggers challenge. + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l1, ping, nil) + switch resp := conn.read(l1).(type) { + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for PING") + default: + t.Fatal("expected WHOAREYOU, got", resp) + } + + // Send second PING. + ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} + switch resp := conn.reqresp(l1, ping2).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping2, l1) + default: + t.Fatal("expected WHOAREYOU, got", resp) + } +} + +// TestTalkRequest sends TALKREQ and expects an empty TALKRESP response. +func (s *Suite) TestTalkRequest(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + // Non-empty request ID. + id := conn.nextReqID() + resp := conn.reqresp(l1, &v5wire.TalkRequest{ReqID: id, Protocol: "test-protocol"}) + switch resp := resp.(type) { + case *v5wire.TalkResponse: + if !bytes.Equal(resp.ReqID, id) { + t.Fatalf("wrong request ID %x in TALKRESP, want %x", resp.ReqID, id) + } + if len(resp.Message) > 0 { + t.Fatalf("non-empty message %x in TALKRESP", resp.Message) + } + default: + t.Fatal("expected TALKRESP, got", resp.Name()) + } + + // Empty request ID. + resp = conn.reqresp(l1, &v5wire.TalkRequest{Protocol: "test-protocol"}) + switch resp := resp.(type) { + case *v5wire.TalkResponse: + if len(resp.ReqID) > 0 { + t.Fatalf("wrong request ID %x in TALKRESP, want empty byte array", resp.ReqID) + } + if len(resp.Message) > 0 { + t.Fatalf("non-empty message %x in TALKRESP", resp.Message) + } + default: + t.Fatal("expected TALKRESP, got", resp.Name()) + } +} + +// TestFindnodeZeroDistance checks that the remote node returns itself for FINDNODE with distance zero. +func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + nodes, err := conn.findnode(l1, []uint{0}) + if err != nil { + t.Fatal(err) + } + if len(nodes) != 1 { + t.Fatalf("remote returned more than one node for FINDNODE [0]") + } + if nodes[0].ID() != conn.remote.ID() { + t.Errorf("ID of response node is %v, want %v", nodes[0].ID(), conn.remote.ID()) + } +} + +// TestFindnodeResults pings the node under test from multiple nodes. After waiting for them to be +// accepted into the remote table, the test checks that they are returned by FINDNODE. +func (s *Suite) TestFindnodeResults(t *utesting.T) { + // Create bystanders. + nodes := make([]*bystander, 5) + added := make(chan enode.ID, len(nodes)) + for i := range nodes { + nodes[i] = newBystander(t, s, added) + defer nodes[i].close() + } + + // Get them added to the remote table. + timeout := 60 * time.Second + timeoutCh := time.After(timeout) + for count := 0; count < len(nodes); { + select { + case id := <-added: + t.Logf("bystander node %v added to remote table", id) + count++ + case <-timeoutCh: + t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes)) + t.Logf("this can happen if the node has a non-empty table from previous runs") + return + } + } + t.Logf("all %d bystander nodes were added", len(nodes)) + + // Collect our nodes by distance. + var dists []uint + expect := make(map[enode.ID]*enode.Node) + for _, bn := range nodes { + n := bn.conn.localNode.Node() + expect[n.ID()] = n + d := uint(enode.LogDist(n.ID(), s.Dest.ID())) + if !slices.Contains(dists, d) { + dists = append(dists, d) + } + } + + // Send FINDNODE for all distances. + conn, l1 := s.listen1(t) + defer conn.close() + foundNodes, err := conn.findnode(l1, dists) + if err != nil { + t.Fatal(err) + } + t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists) + for _, n := range foundNodes { + delete(expect, n.ID()) + } + if len(expect) > 0 { + t.Errorf("missing %d nodes in FINDNODE result", len(expect)) + t.Logf("this can happen if the test is run multiple times in quick succession") + t.Logf("and the remote node hasn't removed dead nodes from previous runs yet") + } else { + t.Logf("all %d expected nodes were returned", len(nodes)) + } +} + +// A bystander is a node whose only purpose is filling a spot in the remote table. +type bystander struct { + dest *enode.Node + conn *conn + l net.PacketConn + + addedCh chan enode.ID + done sync.WaitGroup +} + +func newBystander(t *utesting.T, s *Suite, added chan enode.ID) *bystander { + conn, l := s.listen1(t) + conn.setEndpoint(l) // bystander nodes need IP/port to get pinged + bn := &bystander{ + conn: conn, + l: l, + dest: s.Dest, + addedCh: added, + } + bn.done.Add(1) + go bn.loop() + return bn +} + +// id returns the node ID of the bystander. +func (bn *bystander) id() enode.ID { + return bn.conn.localNode.ID() +} + +// close shuts down loop. +func (bn *bystander) close() { + bn.conn.close() + bn.done.Wait() +} + +// loop answers packets from the remote node until quit. +func (bn *bystander) loop() { + defer bn.done.Done() + + var ( + lastPing time.Time + wasAdded bool + ) + for { + // Ping the remote node. + if !wasAdded && time.Since(lastPing) > 10*time.Second { + bn.conn.reqresp(bn.l, &v5wire.Ping{ + ReqID: bn.conn.nextReqID(), + ENRSeq: bn.dest.Seq(), + }) + lastPing = time.Now() + } + // Answer packets. + switch p := bn.conn.read(bn.l).(type) { + case *v5wire.Ping: + bn.conn.write(bn.l, &v5wire.Pong{ + ReqID: p.ReqID, + ENRSeq: bn.conn.localNode.Seq(), + ToIP: bn.dest.IP(), + ToPort: uint16(bn.dest.UDP()), + }, nil) + wasAdded = true + bn.notifyAdded() + case *v5wire.Findnode: + bn.conn.write(bn.l, &v5wire.Nodes{ReqID: p.ReqID, RespCount: 1}, nil) + wasAdded = true + bn.notifyAdded() + case *v5wire.TalkRequest: + bn.conn.write(bn.l, &v5wire.TalkResponse{ReqID: p.ReqID}, nil) + case *readError: + if !netutil.IsTemporaryError(p.err) { + bn.conn.logf("shutting down: %v", p.err) + return + } + } + } +} + +func (bn *bystander) notifyAdded() { + if bn.addedCh != nil { + bn.addedCh <- bn.id() + bn.addedCh = nil + } +} diff --git a/cmd/devp2p/internal/v5test/framework.go b/cmd/devp2p/internal/v5test/framework.go new file mode 100644 index 0000000..92a5048 --- /dev/null +++ b/cmd/devp2p/internal/v5test/framework.go @@ -0,0 +1,254 @@ +// 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 v5test + +import ( + "bytes" + "crypto/ecdsa" + "encoding/binary" + "fmt" + "net" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" +) + +// readError represents an error during packet reading. +// This exists to facilitate type-switching on the result of conn.read. +type readError struct { + err error +} + +func (p *readError) Kind() byte { return 99 } +func (p *readError) Name() string { return fmt.Sprintf("error: %v", p.err) } +func (p *readError) Error() string { return p.err.Error() } +func (p *readError) Unwrap() error { return p.err } +func (p *readError) RequestID() []byte { return nil } +func (p *readError) SetRequestID([]byte) {} + +func (p *readError) AppendLogInfo(ctx []interface{}) []interface{} { return ctx } + +// readErrorf creates a readError with the given text. +func readErrorf(format string, args ...interface{}) *readError { + return &readError{fmt.Errorf(format, args...)} +} + +// This is the response timeout used in tests. +const waitTime = 300 * time.Millisecond + +// conn is a connection to the node under test. +type conn struct { + localNode *enode.LocalNode + localKey *ecdsa.PrivateKey + remote *enode.Node + remoteAddr *net.UDPAddr + listeners []net.PacketConn + + log logger + codec *v5wire.Codec + idCounter uint32 +} + +type logger interface { + Logf(string, ...interface{}) +} + +// newConn sets up a connection to the given node. +func newConn(dest *enode.Node, log logger) *conn { + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + db, err := enode.OpenDB("") + if err != nil { + panic(err) + } + ln := enode.NewLocalNode(db, key) + + return &conn{ + localKey: key, + localNode: ln, + remote: dest, + remoteAddr: &net.UDPAddr{IP: dest.IP(), Port: dest.UDP()}, + codec: v5wire.NewCodec(ln, key, mclock.System{}, nil), + log: log, + } +} + +func (tc *conn) setEndpoint(c net.PacketConn) { + tc.localNode.SetStaticIP(laddr(c).IP) + tc.localNode.SetFallbackUDP(laddr(c).Port) +} + +func (tc *conn) listen(ip string) net.PacketConn { + l, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", ip)) + if err != nil { + panic(err) + } + tc.listeners = append(tc.listeners, l) + return l +} + +// close shuts down all listeners and the local node. +func (tc *conn) close() { + for _, l := range tc.listeners { + l.Close() + } + tc.localNode.Database().Close() +} + +// nextReqID creates a request id. +func (tc *conn) nextReqID() []byte { + id := make([]byte, 4) + tc.idCounter++ + binary.BigEndian.PutUint32(id, tc.idCounter) + return id +} + +// reqresp performs a request/response interaction on the given connection. +// The request is retried if a handshake is requested. +func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet { + reqnonce := tc.write(c, req, nil) + switch resp := tc.read(c).(type) { + case *v5wire.Whoareyou: + if resp.Nonce != reqnonce { + return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:]) + } + resp.Node = tc.remote + tc.write(c, req, resp) + return tc.read(c) + default: + return resp + } +} + +// findnode sends a FINDNODE request and waits for its responses. +func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error) { + var ( + findnode = &v5wire.Findnode{ReqID: tc.nextReqID(), Distances: dists} + reqnonce = tc.write(c, findnode, nil) + first = true + total uint8 + results []*enode.Node + ) + for n := 1; n > 0; { + switch resp := tc.read(c).(type) { + case *v5wire.Whoareyou: + // Handle handshake. + if resp.Nonce == reqnonce { + resp.Node = tc.remote + tc.write(c, findnode, resp) + } else { + return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:]) + } + case *v5wire.Ping: + // Handle ping from remote. + tc.write(c, &v5wire.Pong{ + ReqID: resp.ReqID, + ENRSeq: tc.localNode.Seq(), + }, nil) + case *v5wire.Nodes: + // Got NODES! Check request ID. + if !bytes.Equal(resp.ReqID, findnode.ReqID) { + return nil, fmt.Errorf("NODES response has wrong request id %x", resp.ReqID) + } + // Check total count. It should be greater than one + // and needs to be the same across all responses. + if first { + if resp.RespCount == 0 || resp.RespCount > 6 { + return nil, fmt.Errorf("invalid NODES response count %d (not in (0,7))", resp.RespCount) + } + total = resp.RespCount + n = int(total) - 1 + first = false + } else { + n-- + if resp.RespCount != total { + return nil, fmt.Errorf("invalid NODES response count %d (!= %d)", resp.RespCount, total) + } + } + // Check nodes. + nodes, err := checkRecords(resp.Nodes) + if err != nil { + return nil, fmt.Errorf("invalid node in NODES response: %v", err) + } + results = append(results, nodes...) + default: + return nil, fmt.Errorf("expected NODES, got %v", resp) + } + } + return results, nil +} + +// write sends a packet on the given connection. +func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou) v5wire.Nonce { + packet, nonce, err := tc.codec.Encode(tc.remote.ID(), tc.remoteAddr.String(), p, challenge) + if err != nil { + panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err)) + } + if _, err := c.WriteTo(packet, tc.remoteAddr); err != nil { + tc.logf("Can't send %s: %v", p.Name(), err) + } else { + tc.logf(">> %s", p.Name()) + } + return nonce +} + +// read waits for an incoming packet on the given connection. +func (tc *conn) read(c net.PacketConn) v5wire.Packet { + buf := make([]byte, 1280) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { + return &readError{err} + } + n, fromAddr, err := c.ReadFrom(buf) + if err != nil { + return &readError{err} + } + _, _, p, err := tc.codec.Decode(buf[:n], fromAddr.String()) + if err != nil { + return &readError{err} + } + tc.logf("<< %s", p.Name()) + return p +} + +// logf prints to the test log. +func (tc *conn) logf(format string, args ...interface{}) { + if tc.log != nil { + tc.log.Logf("(%s) %s", tc.localNode.ID().TerminalString(), fmt.Sprintf(format, args...)) + } +} + +func laddr(c net.PacketConn) *net.UDPAddr { + return c.LocalAddr().(*net.UDPAddr) +} + +func checkRecords(records []*enr.Record) ([]*enode.Node, error) { + nodes := make([]*enode.Node, len(records)) + for i := range records { + n, err := enode.New(enode.ValidSchemes, records[i]) + if err != nil { + return nil, err + } + nodes[i] = n + } + return nodes, nil +} diff --git a/cmd/devp2p/keycmd.go b/cmd/devp2p/keycmd.go new file mode 100644 index 0000000..98d7bd7 --- /dev/null +++ b/cmd/devp2p/keycmd.go @@ -0,0 +1,163 @@ +// 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 ( + "errors" + "fmt" + "net" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/urfave/cli/v2" +) + +var ( + keyCommand = &cli.Command{ + Name: "key", + Usage: "Operations on node keys", + Subcommands: []*cli.Command{ + keyGenerateCommand, + keyToIDCommand, + keyToNodeCommand, + keyToRecordCommand, + }, + } + keyGenerateCommand = &cli.Command{ + Name: "generate", + Usage: "Generates node key files", + ArgsUsage: "keyfile", + Action: genkey, + } + keyToIDCommand = &cli.Command{ + Name: "to-id", + Usage: "Creates a node ID from a node key file", + ArgsUsage: "keyfile", + Action: keyToID, + Flags: []cli.Flag{}, + } + keyToNodeCommand = &cli.Command{ + Name: "to-enode", + Usage: "Creates an enode URL from a node key file", + ArgsUsage: "keyfile", + Action: keyToURL, + Flags: []cli.Flag{hostFlag, tcpPortFlag, udpPortFlag}, + } + keyToRecordCommand = &cli.Command{ + Name: "to-enr", + Usage: "Creates an ENR from a node key file", + ArgsUsage: "keyfile", + Action: keyToRecord, + Flags: []cli.Flag{hostFlag, tcpPortFlag, udpPortFlag}, + } +) + +var ( + hostFlag = &cli.StringFlag{ + Name: "ip", + Usage: "IP address of the node", + Value: "127.0.0.1", + } + tcpPortFlag = &cli.IntFlag{ + Name: "tcp", + Usage: "TCP port of the node", + Value: 30303, + } + udpPortFlag = &cli.IntFlag{ + Name: "udp", + Usage: "UDP port of the node", + Value: 30303, + } +) + +func genkey(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return errors.New("need key file as argument") + } + file := ctx.Args().Get(0) + + key, err := crypto.GenerateKey() + if err != nil { + return fmt.Errorf("could not generate key: %v", err) + } + return crypto.SaveECDSA(file, key) +} + +func keyToID(ctx *cli.Context) error { + n, err := makeRecord(ctx) + if err != nil { + return err + } + fmt.Println(n.ID()) + return nil +} + +func keyToURL(ctx *cli.Context) error { + n, err := makeRecord(ctx) + if err != nil { + return err + } + fmt.Println(n.URLv4()) + return nil +} + +func keyToRecord(ctx *cli.Context) error { + n, err := makeRecord(ctx) + if err != nil { + return err + } + fmt.Println(n.String()) + return nil +} + +func makeRecord(ctx *cli.Context) (*enode.Node, error) { + if ctx.NArg() != 1 { + return nil, errors.New("need key file as argument") + } + + var ( + file = ctx.Args().Get(0) + host = ctx.String(hostFlag.Name) + tcp = ctx.Int(tcpPortFlag.Name) + udp = ctx.Int(udpPortFlag.Name) + ) + key, err := crypto.LoadECDSA(file) + if err != nil { + return nil, err + } + + var r enr.Record + if host != "" { + ip := net.ParseIP(host) + if ip == nil { + return nil, fmt.Errorf("invalid IP address %q", host) + } + r.Set(enr.IP(ip)) + } + if udp != 0 { + r.Set(enr.UDP(udp)) + } + if tcp != 0 { + r.Set(enr.TCP(tcp)) + } + + if err := enode.SignV4(&r, key); err != nil { + return nil, err + } + return enode.New(enode.ValidSchemes, &r) +} diff --git a/cmd/devp2p/main.go b/cmd/devp2p/main.go new file mode 100644 index 0000000..66974bb --- /dev/null +++ b/cmd/devp2p/main.go @@ -0,0 +1,101 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/urfave/cli/v2" +) + +var app = flags.NewApp("go-ethereum devp2p tool") + +func init() { + app.Flags = append(app.Flags, debug.Flags...) + 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 + } + app.CommandNotFound = func(ctx *cli.Context, cmd string) { + fmt.Fprintf(os.Stderr, "No such command: %s\n", cmd) + os.Exit(1) + } + + // Add subcommands. + app.Commands = []*cli.Command{ + enrdumpCommand, + keyCommand, + discv4Command, + discv5Command, + dnsCommand, + nodesetCommand, + rlpxCommand, + } +} + +func main() { + exit(app.Run(os.Args)) +} + +// commandHasFlag returns true if the current command supports the given flag. +func commandHasFlag(ctx *cli.Context, flag cli.Flag) bool { + names := flag.Names() + set := make(map[string]struct{}, len(names)) + for _, name := range names { + set[name] = struct{}{} + } + for _, ctx := range ctx.Lineage() { + if ctx.Command != nil { + for _, f := range ctx.Command.Flags { + for _, name := range f.Names() { + if _, ok := set[name]; ok { + return true + } + } + } + } + } + return false +} + +// getNodeArg handles the common case of a single node descriptor argument. +func getNodeArg(ctx *cli.Context) *enode.Node { + if ctx.NArg() < 1 { + exit("missing node as command-line argument") + } + n, err := parseNode(ctx.Args().First()) + if err != nil { + exit(err) + } + return n +} + +func exit(err interface{}) { + if err == nil { + os.Exit(0) + } + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/cmd/devp2p/nodeset.go b/cmd/devp2p/nodeset.go new file mode 100644 index 0000000..4fa862d --- /dev/null +++ b/cmd/devp2p/nodeset.go @@ -0,0 +1,133 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "slices" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +const jsonIndent = " " + +// nodeSet is the nodes.json file format. It holds a set of node records +// as a JSON object. +type nodeSet map[enode.ID]nodeJSON + +type nodeJSON struct { + Seq uint64 `json:"seq"` + N *enode.Node `json:"record"` + + // The score tracks how many liveness checks were performed. It is incremented by one + // every time the node passes a check, and halved every time it doesn't. + Score int `json:"score,omitempty"` + // These two track the time of last successful contact. + FirstResponse time.Time `json:"firstResponse,omitempty"` + LastResponse time.Time `json:"lastResponse,omitempty"` + // This one tracks the time of our last attempt to contact the node. + LastCheck time.Time `json:"lastCheck,omitempty"` +} + +func loadNodesJSON(file string) nodeSet { + var nodes nodeSet + if err := common.LoadJSON(file, &nodes); err != nil { + exit(err) + } + return nodes +} + +func writeNodesJSON(file string, nodes nodeSet) { + nodesJSON, err := json.MarshalIndent(nodes, "", jsonIndent) + if err != nil { + exit(err) + } + if file == "-" { + os.Stdout.Write(nodesJSON) + return + } + if err := os.WriteFile(file, nodesJSON, 0644); err != nil { + exit(err) + } +} + +// nodes returns the node records contained in the set. +func (ns nodeSet) nodes() []*enode.Node { + result := make([]*enode.Node, 0, len(ns)) + for _, n := range ns { + result = append(result, n.N) + } + // Sort by ID. + slices.SortFunc(result, func(a, b *enode.Node) int { + return bytes.Compare(a.ID().Bytes(), b.ID().Bytes()) + }) + return result +} + +// add ensures the given nodes are present in the set. +func (ns nodeSet) add(nodes ...*enode.Node) { + for _, n := range nodes { + v := ns[n.ID()] + v.N = n + v.Seq = n.Seq() + ns[n.ID()] = v + } +} + +// topN returns the top n nodes by score as a new set. +func (ns nodeSet) topN(n int) nodeSet { + if n >= len(ns) { + return ns + } + + byscore := make([]nodeJSON, 0, len(ns)) + for _, v := range ns { + byscore = append(byscore, v) + } + slices.SortFunc(byscore, func(a, b nodeJSON) int { + if a.Score > b.Score { + return -1 + } + if a.Score < b.Score { + return 1 + } + return 0 + }) + result := make(nodeSet, n) + for _, v := range byscore[:n] { + result[v.N.ID()] = v + } + return result +} + +// verify performs integrity checks on the node set. +func (ns nodeSet) verify() error { + for id, n := range ns { + if n.N.ID() != id { + return fmt.Errorf("invalid node %v: ID does not match ID %v in record", id, n.N.ID()) + } + if n.N.Seq() != n.Seq { + return fmt.Errorf("invalid node %v: 'seq' does not match seq %d from record", id, n.N.Seq()) + } + } + return nil +} diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go new file mode 100644 index 0000000..f0773ed --- /dev/null +++ b/cmd/devp2p/nodesetcmd.go @@ -0,0 +1,274 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "errors" + "fmt" + "net/netip" + "sort" + "strconv" + "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" + "github.com/ethereum/go-ethereum/rlp" + "github.com/urfave/cli/v2" +) + +var ( + nodesetCommand = &cli.Command{ + Name: "nodeset", + Usage: "Node set tools", + Subcommands: []*cli.Command{ + nodesetInfoCommand, + nodesetFilterCommand, + }, + } + nodesetInfoCommand = &cli.Command{ + Name: "info", + Usage: "Shows statistics about a node set", + Action: nodesetInfo, + ArgsUsage: "", + } + nodesetFilterCommand = &cli.Command{ + Name: "filter", + Usage: "Filters a node set", + Action: nodesetFilter, + ArgsUsage: " filters..", + + SkipFlagParsing: true, + } +) + +func nodesetInfo(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("need nodes file as argument") + } + + ns := loadNodesJSON(ctx.Args().First()) + fmt.Printf("Set contains %d nodes.\n", len(ns)) + showAttributeCounts(ns) + return nil +} + +// showAttributeCounts prints the distribution of ENR attributes in a node set. +func showAttributeCounts(ns nodeSet) { + attrcount := make(map[string]int) + var attrlist []interface{} + for _, n := range ns { + r := n.N.Record() + attrlist = r.AppendElements(attrlist[:0])[1:] + for i := 0; i < len(attrlist); i += 2 { + key := attrlist[i].(string) + attrcount[key]++ + } + } + + var keys []string + var maxlength int + for key := range attrcount { + keys = append(keys, key) + if len(key) > maxlength { + maxlength = len(key) + } + } + sort.Strings(keys) + fmt.Println("ENR attribute counts:") + for _, key := range keys { + fmt.Printf("%s%s: %d\n", strings.Repeat(" ", maxlength-len(key)+1), key, attrcount[key]) + } +} + +func nodesetFilter(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("need nodes file as argument") + } + // Parse -limit. + limit, err := parseFilterLimit(ctx.Args().Tail()) + if err != nil { + return err + } + // Parse the filters. + filter, err := andFilter(ctx.Args().Tail()) + if err != nil { + return err + } + + // Load nodes and apply filters. + ns := loadNodesJSON(ctx.Args().First()) + result := make(nodeSet) + for id, n := range ns { + if filter(n) { + result[id] = n + } + } + if limit >= 0 { + result = result.topN(limit) + } + writeNodesJSON("-", result) + return nil +} + +type nodeFilter func(nodeJSON) bool + +type nodeFilterC struct { + narg int + fn func([]string) (nodeFilter, error) +} + +var filterFlags = map[string]nodeFilterC{ + "-limit": {1, trueFilter}, // needed to skip over -limit + "-ip": {1, ipFilter}, + "-min-age": {1, minAgeFilter}, + "-eth-network": {1, ethFilter}, + "-les-server": {0, lesFilter}, + "-snap": {0, snapFilter}, +} + +// parseFilters parses nodeFilters from args. +func parseFilters(args []string) ([]nodeFilter, error) { + var filters []nodeFilter + for len(args) > 0 { + fc, ok := filterFlags[args[0]] + if !ok { + return nil, fmt.Errorf("invalid filter %q", args[0]) + } + if len(args)-1 < fc.narg { + return nil, fmt.Errorf("filter %q wants %d arguments, have %d", args[0], fc.narg, len(args)-1) + } + filter, err := fc.fn(args[1 : 1+fc.narg]) + if err != nil { + return nil, fmt.Errorf("%s: %v", args[0], err) + } + filters = append(filters, filter) + args = args[1+fc.narg:] + } + return filters, nil +} + +// parseFilterLimit parses the -limit option in args. It returns -1 if there is no limit. +func parseFilterLimit(args []string) (int, error) { + limit := -1 + for i, arg := range args { + if arg == "-limit" { + if i == len(args)-1 { + return -1, errors.New("-limit requires an argument") + } + n, err := strconv.Atoi(args[i+1]) + if err != nil { + return -1, fmt.Errorf("invalid -limit %q", args[i+1]) + } + limit = n + } + } + return limit, nil +} + +// andFilter parses node filters in args and returns a single filter that requires all +// of them to match. +func andFilter(args []string) (nodeFilter, error) { + checks, err := parseFilters(args) + if err != nil { + return nil, err + } + f := func(n nodeJSON) bool { + for _, filter := range checks { + if !filter(n) { + return false + } + } + return true + } + return f, nil +} + +func trueFilter(args []string) (nodeFilter, error) { + return func(n nodeJSON) bool { return true }, nil +} + +func ipFilter(args []string) (nodeFilter, error) { + prefix, err := netip.ParsePrefix(args[0]) + if err != nil { + return nil, err + } + f := func(n nodeJSON) bool { return prefix.Contains(n.N.IPAddr()) } + return f, nil +} + +func minAgeFilter(args []string) (nodeFilter, error) { + minage, err := time.ParseDuration(args[0]) + if err != nil { + return nil, err + } + f := func(n nodeJSON) bool { + age := n.LastResponse.Sub(n.FirstResponse) + return age >= minage + } + return f, nil +} + +func ethFilter(args []string) (nodeFilter, error) { + var filter forkid.Filter + switch args[0] { + case "mainnet": + filter = forkid.NewStaticFilter(params.MainnetChainConfig, core.DefaultGenesisBlock().ToBlock()) + case "goerli": + filter = forkid.NewStaticFilter(params.GoerliChainConfig, core.DefaultGoerliGenesisBlock().ToBlock()) + case "sepolia": + 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]) + } + + f := func(n nodeJSON) bool { + var eth struct { + ForkID forkid.ID + Tail []rlp.RawValue `rlp:"tail"` + } + if n.N.Load(enr.WithEntry("eth", ð)) != nil { + return false + } + return filter(eth.ForkID) == nil + } + return f, nil +} + +func lesFilter(args []string) (nodeFilter, error) { + f := func(n nodeJSON) bool { + var les struct { + Tail []rlp.RawValue `rlp:"tail"` + } + return n.N.Load(enr.WithEntry("les", &les)) == nil + } + return f, nil +} + +func snapFilter(args []string) (nodeFilter, error) { + f := func(n nodeJSON) bool { + var snap struct { + Tail []rlp.RawValue `rlp:"tail"` + } + return n.N.Load(enr.WithEntry("snap", &snap)) == nil + } + return f, nil +} diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go new file mode 100644 index 0000000..77f09e6 --- /dev/null +++ b/cmd/devp2p/rlpxcmd.go @@ -0,0 +1,169 @@ +// 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 ( + "errors" + "fmt" + "net" + + "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" +) + +var ( + rlpxCommand = &cli.Command{ + Name: "rlpx", + Usage: "RLPx Commands", + Subcommands: []*cli.Command{ + rlpxPingCommand, + rlpxEthTestCommand, + rlpxSnapTestCommand, + }, + } + rlpxPingCommand = &cli.Command{ + Name: "ping", + Usage: "ping ", + Action: rlpxPing, + } + rlpxEthTestCommand = &cli.Command{ + Name: "eth-test", + 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 snap protocol tests against a node", + ArgsUsage: "", + Action: rlpxSnapTest, + Flags: []cli.Flag{ + testPatternFlag, + testTAPFlag, + testChainDirFlag, + testNodeFlag, + testNodeJWTFlag, + testNodeEngineFlag, + }, + } +) + +func rlpxPing(ctx *cli.Context) error { + n := getNodeArg(ctx) + tcpEndpoint, ok := n.TCPEndpoint() + if !ok { + return fmt.Errorf("node has no TCP endpoint") + } + fd, err := net.Dial("tcp", tcpEndpoint.String()) + if err != nil { + return err + } + conn := rlpx.NewConn(fd, n.Pubkey()) + ourKey, _ := crypto.GenerateKey() + _, err = conn.Handshake(ourKey) + if err != nil { + return err + } + code, data, _, err := conn.Read() + if err != nil { + return err + } + switch code { + case 0: + var h ethtest.Hello + if err := rlp.DecodeBytes(data, &h); err != nil { + return fmt.Errorf("invalid handshake: %v", err) + } + fmt.Printf("%+v\n", h) + case 1: + var msg []p2p.DiscReason + if rlp.DecodeBytes(data, &msg); len(msg) == 0 { + return errors.New("invalid disconnect message") + } + return fmt.Errorf("received disconnect message: %v", msg[0]) + default: + return fmt.Errorf("invalid message code %d, expected handshake (code zero) or disconnect (code one)", code) + } + return nil +} + +// rlpxEthTest runs the eth protocol test suite. +func rlpxEthTest(ctx *cli.Context) error { + p := cliTestParams(ctx) + suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt) + if err != nil { + exit(err) + } + return runTests(ctx, suite.EthTests()) +} + +// rlpxSnapTest runs the snap protocol test suite. +func rlpxSnapTest(ctx *cli.Context) error { + 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 new file mode 100644 index 0000000..7e3723c --- /dev/null +++ b/cmd/devp2p/runtest.go @@ -0,0 +1,98 @@ +// 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 ( + "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" +) + +var ( + testPatternFlag = &cli.StringFlag{ + Name: "run", + Usage: "Pattern of test suite(s) to run", + Category: flags.TestingCategory, + } + testTAPFlag = &cli.BoolFlag{ + 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, + Category: flags.TestingCategory, + } + testListen2Flag = &cli.StringFlag{ + Name: "listen2", + Usage: "IP address of the second tester", + Value: v4test.Listen2, + Category: flags.TestingCategory, + } +) + +func runTests(ctx *cli.Context, tests []utesting.Test) error { + // Filter test cases. + if ctx.IsSet(testPatternFlag.Name) { + tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) + } + // Disable logging unless explicitly enabled. + if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") { + log.SetDefault(log.NewLogger(log.DiscardHandler())) + } + // Run the tests. + var run = utesting.RunTests + if ctx.Bool(testTAPFlag.Name) { + run = utesting.RunTAP + } + results := run(tests, os.Stdout) + if utesting.CountFailures(results) > 0 { + os.Exit(1) + } + return nil +} diff --git a/cmd/era/main.go b/cmd/era/main.go new file mode 100644 index 0000000..8b57fd6 --- /dev/null +++ b/cmd/era/main.go @@ -0,0 +1,325 @@ +// 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 ( + "encoding/json" + "errors" + "fmt" + "math/big" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/urfave/cli/v2" +) + +var app = flags.NewApp("go-ethereum era tool") + +var ( + dirFlag = &cli.StringFlag{ + Name: "dir", + Usage: "directory storing all relevant era1 files", + Value: "eras", + } + networkFlag = &cli.StringFlag{ + Name: "network", + Usage: "network name associated with era1 files", + Value: "mainnet", + } + eraSizeFlag = &cli.IntFlag{ + Name: "size", + Usage: "number of blocks per era", + Value: era.MaxEra1Size, + } + txsFlag = &cli.BoolFlag{ + Name: "txs", + Usage: "print full transaction values", + } +) + +var ( + blockCommand = &cli.Command{ + Name: "block", + Usage: "get block data", + ArgsUsage: "", + Action: block, + Flags: []cli.Flag{ + txsFlag, + }, + } + infoCommand = &cli.Command{ + Name: "info", + ArgsUsage: "", + Usage: "get epoch information", + Action: info, + } + verifyCommand = &cli.Command{ + Name: "verify", + ArgsUsage: "", + Usage: "verifies each era1 against expected accumulator root", + Action: verify, + } +) + +func init() { + app.Commands = []*cli.Command{ + blockCommand, + infoCommand, + verifyCommand, + } + app.Flags = []cli.Flag{ + dirFlag, + networkFlag, + eraSizeFlag, + } +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +// block prints the specified block from an era1 store. +func block(ctx *cli.Context) error { + num, err := strconv.ParseUint(ctx.Args().First(), 10, 64) + if err != nil { + return fmt.Errorf("invalid block number: %w", err) + } + e, err := open(ctx, num/uint64(ctx.Int(eraSizeFlag.Name))) + if err != nil { + return fmt.Errorf("error opening era1: %w", err) + } + defer e.Close() + // Read block with number. + block, err := e.GetBlockByNumber(num) + if err != nil { + return fmt.Errorf("error reading block %d: %w", num, err) + } + // Convert block to JSON and print. + val := ethapi.RPCMarshalBlock(block, ctx.Bool(txsFlag.Name), ctx.Bool(txsFlag.Name), params.MainnetChainConfig) + b, err := json.MarshalIndent(val, "", " ") + if err != nil { + return fmt.Errorf("error marshaling json: %w", err) + } + fmt.Println(string(b)) + return nil +} + +// info prints some high-level information about the era1 file. +func info(ctx *cli.Context) error { + epoch, err := strconv.ParseUint(ctx.Args().First(), 10, 64) + if err != nil { + return fmt.Errorf("invalid epoch number: %w", err) + } + e, err := open(ctx, epoch) + if err != nil { + return err + } + defer e.Close() + acc, err := e.Accumulator() + if err != nil { + return fmt.Errorf("error reading accumulator: %w", err) + } + td, err := e.InitialTD() + if err != nil { + return fmt.Errorf("error reading total difficulty: %w", err) + } + info := struct { + Accumulator common.Hash `json:"accumulator"` + TotalDifficulty *big.Int `json:"totalDifficulty"` + StartBlock uint64 `json:"startBlock"` + Count uint64 `json:"count"` + }{ + acc, td, e.Start(), e.Count(), + } + b, _ := json.MarshalIndent(info, "", " ") + fmt.Println(string(b)) + return nil +} + +// open opens an era1 file at a certain epoch. +func open(ctx *cli.Context, epoch uint64) (*era.Era, error) { + var ( + dir = ctx.String(dirFlag.Name) + network = ctx.String(networkFlag.Name) + ) + entries, err := era.ReadDir(dir, network) + if err != nil { + return nil, fmt.Errorf("error reading era dir: %w", err) + } + if epoch >= uint64(len(entries)) { + return nil, fmt.Errorf("epoch out-of-bounds: last %d, want %d", len(entries)-1, epoch) + } + return era.Open(filepath.Join(dir, entries[epoch])) +} + +// verify checks each era1 file in a directory to ensure it is well-formed and +// that the accumulator matches the expected value. +func verify(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + return errors.New("missing accumulators file") + } + + roots, err := readHashes(ctx.Args().First()) + if err != nil { + return fmt.Errorf("unable to read expected roots file: %w", err) + } + + var ( + dir = ctx.String(dirFlag.Name) + network = ctx.String(networkFlag.Name) + start = time.Now() + reported = time.Now() + ) + + entries, err := era.ReadDir(dir, network) + if err != nil { + return fmt.Errorf("error reading %s: %w", dir, err) + } + + if len(entries) != len(roots) { + return errors.New("number of era1 files should match the number of accumulator hashes") + } + + // Verify each epoch matches the expected root. + for i, want := range roots { + // Wrap in function so defers don't stack. + err := func() error { + name := entries[i] + e, err := era.Open(filepath.Join(dir, name)) + if err != nil { + return fmt.Errorf("error opening era1 file %s: %w", name, err) + } + defer e.Close() + // Read accumulator and check against expected. + if got, err := e.Accumulator(); err != nil { + return fmt.Errorf("error retrieving accumulator for %s: %w", name, err) + } else if got != want { + return fmt.Errorf("invalid root %s: got %s, want %s", name, got, want) + } + // Recompute accumulator. + if err := checkAccumulator(e); err != nil { + return fmt.Errorf("error verify era1 file %s: %w", name, err) + } + // Give the user some feedback that something is happening. + if time.Since(reported) >= 8*time.Second { + fmt.Printf("Verifying Era1 files \t\t verified=%d,\t elapsed=%s\n", i, common.PrettyDuration(time.Since(start))) + reported = time.Now() + } + return nil + }() + if err != nil { + return err + } + } + + return nil +} + +// checkAccumulator verifies the accumulator matches the data in the Era. +func checkAccumulator(e *era.Era) error { + var ( + err error + want common.Hash + td *big.Int + tds = make([]*big.Int, 0) + hashes = make([]common.Hash, 0) + ) + if want, err = e.Accumulator(); err != nil { + return fmt.Errorf("error reading accumulator: %w", err) + } + if td, err = e.InitialTD(); err != nil { + return fmt.Errorf("error reading total difficulty: %w", err) + } + it, err := era.NewIterator(e) + if err != nil { + return fmt.Errorf("error making era iterator: %w", err) + } + // To fully verify an era the following attributes must be checked: + // 1) the block index is constructed correctly + // 2) the tx root matches the value in the block + // 3) the receipts root matches the value in the block + // 4) the starting total difficulty value is correct + // 5) the accumulator is correct by recomputing it locally, which verifies + // the blocks are all correct (via hash) + // + // The attributes 1), 2), and 3) are checked for each block. 4) and 5) require + // accumulation across the entire set and are verified at the end. + for it.Next() { + // 1) next() walks the block index, so we're able to implicitly verify it. + if it.Error() != nil { + return fmt.Errorf("error reading block %d: %w", it.Number(), err) + } + block, receipts, err := it.BlockAndReceipts() + if it.Error() != nil { + return fmt.Errorf("error reading block %d: %w", it.Number(), err) + } + // 2) recompute tx root and verify against header. + tr := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)) + if tr != block.TxHash() { + return fmt.Errorf("tx root in block %d mismatch: want %s, got %s", block.NumberU64(), block.TxHash(), tr) + } + // 3) recompute receipt root and check value against block. + rr := types.DeriveSha(receipts, trie.NewStackTrie(nil)) + if rr != block.ReceiptHash() { + return fmt.Errorf("receipt root in block %d mismatch: want %s, got %s", block.NumberU64(), block.ReceiptHash(), rr) + } + hashes = append(hashes, block.Hash()) + td.Add(td, block.Difficulty()) + tds = append(tds, new(big.Int).Set(td)) + } + // 4+5) Verify accumulator and total difficulty. + got, err := era.ComputeAccumulator(hashes, tds) + if err != nil { + return fmt.Errorf("error computing accumulator: %w", err) + } + if got != want { + return fmt.Errorf("expected accumulator root does not match calculated: got %s, want %s", got, want) + } + return nil +} + +// readHashes reads a file of newline-delimited hashes. +func readHashes(f string) ([]common.Hash, error) { + b, err := os.ReadFile(f) + if err != nil { + return nil, errors.New("unable to open accumulators file") + } + s := strings.Split(string(b), "\n") + // Remove empty last element, if present. + if s[len(s)-1] == "" { + s = s[:len(s)-1] + } + // Convert to hashes. + r := make([]common.Hash, len(s)) + for i := range s { + r[i] = common.HexToHash(s[i]) + } + return r, nil +} diff --git a/cmd/ethkey/README.md b/cmd/ethkey/README.md new file mode 100644 index 0000000..a7f5316 --- /dev/null +++ b/cmd/ethkey/README.md @@ -0,0 +1,53 @@ +ethkey +====== + +ethkey is a simple command-line tool for working with Ethereum keyfiles. + + +# Usage + +### `ethkey generate` + +Generate a new keyfile. +If you want to use an existing private key to use in the keyfile, it can be +specified by setting `--privatekey` with the location of the file containing the +private key. + + +### `ethkey inspect ` + +Print various information about the keyfile. +Private key information can be printed by using the `--private` flag; +make sure to use this feature with great caution! + + +### `ethkey signmessage ` + +Sign the message with a keyfile. +It is possible to refer to a file containing the message. +To sign a message contained in a file, use the `--msgfile` flag. + + +### `ethkey verifymessage

` + +Verify the signature of the message. +It is possible to refer to a file containing the message. +To sign a message contained in a file, use the --msgfile flag. + + +### `ethkey changepassword ` + +Change the password of a keyfile. +use the `--newpasswordfile` to point to the new password file. + + +## Passwords + +For every command that uses a keyfile, you will be prompted to provide the +password for decrypting the keyfile. To avoid this message, it is possible +to pass the password by using the `--passwordfile` flag pointing to a file that +contains the password. + +## JSON + +In case you need to output the result in a JSON format, you shall use the `--json` flag. diff --git a/cmd/ethkey/changepassword.go b/cmd/ethkey/changepassword.go new file mode 100644 index 0000000..4298e2b --- /dev/null +++ b/cmd/ethkey/changepassword.go @@ -0,0 +1,88 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/urfave/cli/v2" +) + +var newPassphraseFlag = &cli.StringFlag{ + Name: "newpasswordfile", + Usage: "the file that contains the new password for the keyfile", +} + +var commandChangePassphrase = &cli.Command{ + Name: "changepassword", + Usage: "change the password on a keyfile", + ArgsUsage: "", + Description: ` +Change the password of a keyfile.`, + Flags: []cli.Flag{ + passphraseFlag, + newPassphraseFlag, + }, + Action: func(ctx *cli.Context) error { + keyfilepath := ctx.Args().First() + + // Read key from file. + keyjson, err := os.ReadFile(keyfilepath) + if err != nil { + utils.Fatalf("Failed to read the keyfile at '%s': %v", keyfilepath, err) + } + + // Decrypt key with passphrase. + passphrase := getPassphrase(ctx, false) + key, err := keystore.DecryptKey(keyjson, passphrase) + if err != nil { + utils.Fatalf("Error decrypting key: %v", err) + } + + // Get a new passphrase. + fmt.Println("Please provide a new password") + var newPhrase string + if passFile := ctx.String(newPassphraseFlag.Name); passFile != "" { + content, err := os.ReadFile(passFile) + if err != nil { + utils.Fatalf("Failed to read new password file '%s': %v", passFile, err) + } + newPhrase = strings.TrimRight(string(content), "\r\n") + } else { + newPhrase = utils.GetPassPhrase("", true) + } + + // Encrypt the key with the new passphrase. + newJson, err := keystore.EncryptKey(key, newPhrase, keystore.StandardScryptN, keystore.StandardScryptP) + if err != nil { + utils.Fatalf("Error encrypting with new password: %v", err) + } + + // Then write the new keyfile in place of the old one. + if err := os.WriteFile(keyfilepath, newJson, 0600); err != nil { + utils.Fatalf("Error writing new keyfile to disk: %v", err) + } + + // Don't print anything. Just return successfully, + // producing a positive exit code. + return nil + }, +} diff --git a/cmd/ethkey/generate.go b/cmd/ethkey/generate.go new file mode 100644 index 0000000..60d8b3c --- /dev/null +++ b/cmd/ethkey/generate.go @@ -0,0 +1,133 @@ +// 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 . + +package main + +import ( + "crypto/ecdsa" + "fmt" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" + "github.com/urfave/cli/v2" +) + +type outputGenerate struct { + Address string + AddressEIP55 string +} + +var ( + privateKeyFlag = &cli.StringFlag{ + Name: "privatekey", + Usage: "file containing a raw private key to encrypt", + } + lightKDFFlag = &cli.BoolFlag{ + Name: "lightkdf", + Usage: "use less secure scrypt parameters", + } +) + +var commandGenerate = &cli.Command{ + Name: "generate", + Usage: "generate new keyfile", + ArgsUsage: "[ ]", + Description: ` +Generate a new keyfile. + +If you want to encrypt an existing private key, it can be specified by setting +--privatekey with the location of the file containing the private key. +`, + Flags: []cli.Flag{ + passphraseFlag, + jsonFlag, + privateKeyFlag, + lightKDFFlag, + }, + Action: func(ctx *cli.Context) error { + // Check if keyfile path given and make sure it doesn't already exist. + keyfilepath := ctx.Args().First() + if keyfilepath == "" { + keyfilepath = defaultKeyfileName + } + if _, err := os.Stat(keyfilepath); err == nil { + utils.Fatalf("Keyfile already exists at %s.", keyfilepath) + } else if !os.IsNotExist(err) { + utils.Fatalf("Error checking if keyfile exists: %v", err) + } + + var privateKey *ecdsa.PrivateKey + var err error + if file := ctx.String(privateKeyFlag.Name); file != "" { + // Load private key from file. + privateKey, err = crypto.LoadECDSA(file) + if err != nil { + utils.Fatalf("Can't load private key: %v", err) + } + } else { + // If not loaded, generate random. + privateKey, err = crypto.GenerateKey() + if err != nil { + utils.Fatalf("Failed to generate random private key: %v", err) + } + } + + // Create the keyfile object with a random UUID. + UUID, err := uuid.NewRandom() + if err != nil { + utils.Fatalf("Failed to generate random uuid: %v", err) + } + key := &keystore.Key{ + Id: UUID, + Address: crypto.PubkeyToAddress(privateKey.PublicKey), + PrivateKey: privateKey, + } + + // Encrypt key with passphrase. + passphrase := getPassphrase(ctx, true) + scryptN, scryptP := keystore.StandardScryptN, keystore.StandardScryptP + if ctx.Bool(lightKDFFlag.Name) { + scryptN, scryptP = keystore.LightScryptN, keystore.LightScryptP + } + keyjson, err := keystore.EncryptKey(key, passphrase, scryptN, scryptP) + if err != nil { + utils.Fatalf("Error encrypting key: %v", err) + } + + // Store the file to disk. + if err := os.MkdirAll(filepath.Dir(keyfilepath), 0700); err != nil { + utils.Fatalf("Could not create directory %s", filepath.Dir(keyfilepath)) + } + if err := os.WriteFile(keyfilepath, keyjson, 0600); err != nil { + utils.Fatalf("Failed to write keyfile to %s: %v", keyfilepath, err) + } + + // Output some information. + out := outputGenerate{ + Address: key.Address.Hex(), + } + if ctx.Bool(jsonFlag.Name) { + mustPrintJSON(out) + } else { + fmt.Println("Address:", out.Address) + } + return nil + }, +} diff --git a/cmd/ethkey/inspect.go b/cmd/ethkey/inspect.go new file mode 100644 index 0000000..29b1c13 --- /dev/null +++ b/cmd/ethkey/inspect.go @@ -0,0 +1,95 @@ +// 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 . + +package main + +import ( + "encoding/hex" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/crypto" + "github.com/urfave/cli/v2" +) + +type outputInspect struct { + Address string + PublicKey string + PrivateKey string +} + +var ( + privateFlag = &cli.BoolFlag{ + Name: "private", + Usage: "include the private key in the output", + } +) + +var commandInspect = &cli.Command{ + Name: "inspect", + Usage: "inspect a keyfile", + ArgsUsage: "", + Description: ` +Print various information about the keyfile. + +Private key information can be printed by using the --private flag; +make sure to use this feature with great caution!`, + Flags: []cli.Flag{ + passphraseFlag, + jsonFlag, + privateFlag, + }, + Action: func(ctx *cli.Context) error { + keyfilepath := ctx.Args().First() + + // Read key from file. + keyjson, err := os.ReadFile(keyfilepath) + if err != nil { + utils.Fatalf("Failed to read the keyfile at '%s': %v", keyfilepath, err) + } + + // Decrypt key with passphrase. + passphrase := getPassphrase(ctx, false) + key, err := keystore.DecryptKey(keyjson, passphrase) + if err != nil { + utils.Fatalf("Error decrypting key: %v", err) + } + + // Output all relevant information we can retrieve. + showPrivate := ctx.Bool(privateFlag.Name) + out := outputInspect{ + Address: key.Address.Hex(), + PublicKey: hex.EncodeToString( + crypto.FromECDSAPub(&key.PrivateKey.PublicKey)), + } + if showPrivate { + out.PrivateKey = hex.EncodeToString(crypto.FromECDSA(key.PrivateKey)) + } + + if ctx.Bool(jsonFlag.Name) { + mustPrintJSON(out) + } else { + fmt.Println("Address: ", out.Address) + fmt.Println("Public key: ", out.PublicKey) + if showPrivate { + fmt.Println("Private key: ", out.PrivateKey) + } + } + return nil + }, +} diff --git a/cmd/ethkey/main.go b/cmd/ethkey/main.go new file mode 100644 index 0000000..25c0d10 --- /dev/null +++ b/cmd/ethkey/main.go @@ -0,0 +1,61 @@ +// 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 . + +package main + +import ( + "fmt" + "os" + + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/urfave/cli/v2" +) + +const ( + defaultKeyfileName = "keyfile.json" +) + +var app *cli.App + +func init() { + app = flags.NewApp("Ethereum key manager") + app.Commands = []*cli.Command{ + commandGenerate, + commandInspect, + commandChangePassphrase, + commandSignMessage, + commandVerifyMessage, + } +} + +// Commonly used command line flags. +var ( + passphraseFlag = &cli.StringFlag{ + Name: "passwordfile", + Usage: "the file that contains the password for the keyfile", + } + jsonFlag = &cli.BoolFlag{ + Name: "json", + Usage: "output JSON instead of human-readable format", + } +) + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/ethkey/message.go b/cmd/ethkey/message.go new file mode 100644 index 0000000..6b8dec0 --- /dev/null +++ b/cmd/ethkey/message.go @@ -0,0 +1,160 @@ +// 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 . + +package main + +import ( + "encoding/hex" + "fmt" + "os" + + "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/crypto" + "github.com/urfave/cli/v2" +) + +type outputSign struct { + Signature string +} + +var msgfileFlag = &cli.StringFlag{ + Name: "msgfile", + Usage: "file containing the message to sign/verify", +} + +var commandSignMessage = &cli.Command{ + Name: "signmessage", + Usage: "sign a message", + ArgsUsage: " ", + Description: ` +Sign the message with a keyfile. + +To sign a message contained in a file, use the --msgfile flag. +`, + Flags: []cli.Flag{ + passphraseFlag, + jsonFlag, + msgfileFlag, + }, + Action: func(ctx *cli.Context) error { + message := getMessage(ctx, 1) + + // Load the keyfile. + keyfilepath := ctx.Args().First() + keyjson, err := os.ReadFile(keyfilepath) + if err != nil { + utils.Fatalf("Failed to read the keyfile at '%s': %v", keyfilepath, err) + } + + // Decrypt key with passphrase. + passphrase := getPassphrase(ctx, false) + key, err := keystore.DecryptKey(keyjson, passphrase) + if err != nil { + utils.Fatalf("Error decrypting key: %v", err) + } + + signature, err := crypto.Sign(accounts.TextHash(message), key.PrivateKey) + if err != nil { + utils.Fatalf("Failed to sign message: %v", err) + } + out := outputSign{Signature: hex.EncodeToString(signature)} + if ctx.Bool(jsonFlag.Name) { + mustPrintJSON(out) + } else { + fmt.Println("Signature:", out.Signature) + } + return nil + }, +} + +type outputVerify struct { + Success bool + RecoveredAddress string + RecoveredPublicKey string +} + +var commandVerifyMessage = &cli.Command{ + Name: "verifymessage", + Usage: "verify the signature of a signed message", + ArgsUsage: "
", + Description: ` +Verify the signature of the message. +It is possible to refer to a file containing the message.`, + Flags: []cli.Flag{ + jsonFlag, + msgfileFlag, + }, + Action: func(ctx *cli.Context) error { + addressStr := ctx.Args().First() + signatureHex := ctx.Args().Get(1) + message := getMessage(ctx, 2) + + if !common.IsHexAddress(addressStr) { + utils.Fatalf("Invalid address: %s", addressStr) + } + address := common.HexToAddress(addressStr) + signature, err := hex.DecodeString(signatureHex) + if err != nil { + utils.Fatalf("Signature encoding is not hexadecimal: %v", err) + } + + recoveredPubkey, err := crypto.SigToPub(accounts.TextHash(message), signature) + if err != nil || recoveredPubkey == nil { + utils.Fatalf("Signature verification failed: %v", err) + } + recoveredPubkeyBytes := crypto.FromECDSAPub(recoveredPubkey) + recoveredAddress := crypto.PubkeyToAddress(*recoveredPubkey) + success := address == recoveredAddress + + out := outputVerify{ + Success: success, + RecoveredPublicKey: hex.EncodeToString(recoveredPubkeyBytes), + RecoveredAddress: recoveredAddress.Hex(), + } + if ctx.Bool(jsonFlag.Name) { + mustPrintJSON(out) + } else { + if out.Success { + fmt.Println("Signature verification successful!") + } else { + fmt.Println("Signature verification failed!") + } + fmt.Println("Recovered public key:", out.RecoveredPublicKey) + fmt.Println("Recovered address:", out.RecoveredAddress) + } + return nil + }, +} + +func getMessage(ctx *cli.Context, msgarg int) []byte { + if file := ctx.String(msgfileFlag.Name); file != "" { + if ctx.NArg() > msgarg { + utils.Fatalf("Can't use --msgfile and message argument at the same time.") + } + msg, err := os.ReadFile(file) + if err != nil { + utils.Fatalf("Can't read message file: %v", err) + } + return msg + } else if ctx.NArg() == msgarg+1 { + return []byte(ctx.Args().Get(msgarg)) + } + utils.Fatalf("Invalid number of arguments: want %d, got %d", msgarg+1, ctx.NArg()) + return nil +} diff --git a/cmd/ethkey/message_test.go b/cmd/ethkey/message_test.go new file mode 100644 index 0000000..389bb8c --- /dev/null +++ b/cmd/ethkey/message_test.go @@ -0,0 +1,65 @@ +// Copyright 2018 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 ( + "path/filepath" + "testing" +) + +func TestMessageSignVerify(t *testing.T) { + t.Parallel() + tmpdir := t.TempDir() + + keyfile := filepath.Join(tmpdir, "the-keyfile") + message := "test message" + + // Create the key. + generate := runEthkey(t, "generate", "--lightkdf", keyfile) + generate.Expect(` +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "foobar"}} +Repeat password: {{.InputLine "foobar"}} +`) + _, matches := generate.ExpectRegexp(`Address: (0x[0-9a-fA-F]{40})\n`) + address := matches[1] + generate.ExpectExit() + + // Sign a message. + sign := runEthkey(t, "signmessage", keyfile, message) + sign.Expect(` +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "foobar"}} +`) + _, matches = sign.ExpectRegexp(`Signature: ([0-9a-f]+)\n`) + signature := matches[1] + sign.ExpectExit() + + // Verify the message. + verify := runEthkey(t, "verifymessage", address, signature, message) + _, matches = verify.ExpectRegexp(` +Signature verification successful! +Recovered public key: [0-9a-f]+ +Recovered address: (0x[0-9a-fA-F]{40}) +`) + recovered := matches[1] + verify.ExpectExit() + + if recovered != address { + t.Error("recovered address doesn't match generated key") + } +} diff --git a/cmd/ethkey/run_test.go b/cmd/ethkey/run_test.go new file mode 100644 index 0000000..73506e5 --- /dev/null +++ b/cmd/ethkey/run_test.go @@ -0,0 +1,54 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + "testing" + + "github.com/ethereum/go-ethereum/internal/cmdtest" + "github.com/ethereum/go-ethereum/internal/reexec" +) + +type testEthkey struct { + *cmdtest.TestCmd +} + +// spawns ethkey with the given command line args. +func runEthkey(t *testing.T, args ...string) *testEthkey { + tt := new(testEthkey) + tt.TestCmd = cmdtest.NewTestCmd(t, tt) + tt.Run("ethkey-test", args...) + return tt +} + +func TestMain(m *testing.M) { + // Run the app if we've been exec'd as "ethkey-test" in runEthkey. + reexec.Register("ethkey-test", func() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + }) + // check if we have been reexec'd + if reexec.Init() { + return + } + os.Exit(m.Run()) +} diff --git a/cmd/ethkey/utils.go b/cmd/ethkey/utils.go new file mode 100644 index 0000000..2821145 --- /dev/null +++ b/cmd/ethkey/utils.go @@ -0,0 +1,56 @@ +// 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 . + +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/urfave/cli/v2" +) + +// getPassphrase obtains a passphrase given by the user. It first checks the +// --passfile command line flag and ultimately prompts the user for a +// passphrase. +func getPassphrase(ctx *cli.Context, confirmation bool) string { + // Look for the --passwordfile flag. + passphraseFile := ctx.String(passphraseFlag.Name) + if passphraseFile != "" { + content, err := os.ReadFile(passphraseFile) + if err != nil { + utils.Fatalf("Failed to read password file '%s': %v", + passphraseFile, err) + } + return strings.TrimRight(string(content), "\r\n") + } + + // Otherwise prompt the user for the passphrase. + return utils.GetPassPhrase("", confirmation) +} + +// mustPrintJSON prints the JSON encoding of the given object and +// exits the program with an error message when the marshaling fails. +func mustPrintJSON(jsonObject interface{}) { + str, err := json.MarshalIndent(jsonObject, "", " ") + if err != nil { + utils.Fatalf("Failed to marshal JSON object: %v", err) + } + fmt.Println(string(str)) +} diff --git a/cmd/evm/README.md b/cmd/evm/README.md new file mode 100644 index 0000000..f95b6b4 --- /dev/null +++ b/cmd/evm/README.md @@ -0,0 +1,626 @@ +# EVM tool + +The EVM tool provides a few useful subcommands to facilitate testing at the EVM +layer. + +* transition tool (`t8n`) : a stateless state transition utility +* transaction tool (`t9n`) : a transaction validation utility +* block builder tool (`b11r`): a block assembler utility + +## State transition tool (`t8n`) + + +The `evm t8n` tool is a stateless state transition utility. It is a utility +which can + +1. Take a prestate, including + - Accounts, + - Block context information, + - Previous blockshashes (*optional) +2. Apply a set of transactions, +3. Apply a mining-reward (*optional), +4. And generate a post-state, including + - State root, transaction root, receipt root, + - Information about rejected transactions, + - Optionally: a full or partial post-state dump + +### Specification + +The idea is to specify the behaviour of this binary very _strict_, so that other +node implementors can build replicas based on their own state-machines, and the +state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based +implementation. + +#### Command line params + +Command line params that need to be supported are + +``` + --input.alloc value (default: "alloc.json") + --input.env value (default: "env.json") + --input.txs value (default: "txs.json") + --output.alloc value (default: "alloc.json") + --output.basedir value + --output.body value + --output.result value (default: "result.json") + --state.chainid value (default: 1) + --state.fork value (default: "GrayGlacier") + --state.reward value (default: 0) + --trace.memory (default: false) + --trace.nomemory (default: true) + --trace.noreturndata (default: true) + --trace.nostack (default: false) + --trace.returndata (default: false) +``` +#### Objects + +The transition tool uses JSON objects to read and write data related to the transition operation. The +following object definitions are required. + +##### `alloc` + +The `alloc` object defines the prestate that transition will begin with. + +```go +// Map of address to account definition. +type Alloc map[common.Address]Account +// Genesis account. Each field is optional. +type Account struct { + Code []byte `json:"code"` + Storage map[common.Hash]common.Hash `json:"storage"` + Balance *big.Int `json:"balance"` + Nonce uint64 `json:"nonce"` + SecretKey []byte `json:"secretKey"` +} +``` + +##### `env` + +The `env` object defines the environmental context in which the transition will +take place. + +```go +type Env struct { + // required + CurrentCoinbase common.Address `json:"currentCoinbase"` + CurrentGasLimit uint64 `json:"currentGasLimit"` + CurrentNumber uint64 `json:"currentNumber"` + CurrentTimestamp uint64 `json:"currentTimestamp"` + Withdrawals []*Withdrawal `json:"withdrawals"` + // optional + CurrentDifficulty *big.Int `json:"currentDifficulty"` + CurrentRandom *big.Int `json:"currentRandom"` + CurrentBaseFee *big.Int `json:"currentBaseFee"` + ParentDifficulty *big.Int `json:"parentDifficulty"` + ParentGasUsed uint64 `json:"parentGasUsed"` + ParentGasLimit uint64 `json:"parentGasLimit"` + ParentTimestamp uint64 `json:"parentTimestamp"` + BlockHashes map[uint64]common.Hash `json:"blockHashes"` + ParentUncleHash common.Hash `json:"parentUncleHash"` + Ommers []Ommer `json:"ommers"` +} +type Ommer struct { + Delta uint64 `json:"delta"` + Address common.Address `json:"address"` +} +type Withdrawal struct { + Index uint64 `json:"index"` + ValidatorIndex uint64 `json:"validatorIndex"` + Recipient common.Address `json:"recipient"` + Amount *big.Int `json:"amount"` +} +``` + +##### `txs` + +The `txs` object is an array of any of the transaction types: `LegacyTx`, +`AccessListTx`, or `DynamicFeeTx`. + +```go +type LegacyTx struct { + Nonce uint64 `json:"nonce"` + GasPrice *big.Int `json:"gasPrice"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +type AccessList []AccessTuple +type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` +} +type AccessListTx struct { + ChainID *big.Int `json:"chainId"` + Nonce uint64 `json:"nonce"` + GasPrice *big.Int `json:"gasPrice"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + AccessList AccessList `json:"accessList"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +type DynamicFeeTx struct { + ChainID *big.Int `json:"chainId"` + Nonce uint64 `json:"nonce"` + GasTipCap *big.Int `json:"maxPriorityFeePerGas"` + GasFeeCap *big.Int `json:"maxFeePerGas"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + AccessList AccessList `json:"accessList"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +``` + +##### `result` + +The `result` object is output after a transition is executed. It includes +information about the post-transition environment. + +```go +type ExecutionResult struct { + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptsRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom"` + Receipts types.Receipts `json:"receipts"` + Rejected []*rejectedTx `json:"rejected,omitempty"` + Difficulty *big.Int `json:"currentDifficulty"` + GasUsed uint64 `json:"gasUsed"` + BaseFee *big.Int `json:"currentBaseFee,omitempty"` +} +``` + +#### Error codes and output + +All logging should happen against the `stderr`. +There are a few (not many) errors that can occur, those are defined below. + +##### EVM-based errors (`2` to `9`) + +- Other EVM error. Exit code `2` +- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. +- Block history is not supplied, but needed for a `BLOCKHASH` operation. If `BLOCKHASH` + is invoked targeting a block which history has not been provided for, the program will + exit with code `4`. + +##### IO errors (`10`-`20`) + +- Invalid input json: the supplied data could not be marshalled. + The program will exit with code `10` +- IO problems: failure to load or save files, the program will exit with code `11` + +``` +# This should exit with 3 +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null +exitcode:3 OK +``` +#### Forks +### Basic usage + +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). + +#### Examples +##### Basic usage + +Invoking it with the provided example files +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin +``` +Two resulting files: + +`alloc.json`: +```json +{ + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeed1a9d", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xa410" + } +} +``` +`result.json`: +```json +{ + "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", + "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "rejected": [ + { + "index": 1, + "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x5208" +} +``` + +We can make them spit out the data to e.g. `stdout` like this: +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout --state.fork=Berlin +``` +Output: +```json +{ + "alloc": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeed1a9d", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xa410" + } + }, + "result": { + "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", + "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "rejected": [ + { + "index": 1, + "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x5208" + } +} +``` + +#### About Ommers + +Mining rewards and ommer rewards might need to be added. This is how those are applied: + +- `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`. +- For each ommer (mined by `0xbb`), with blocknumber `N-delta` + - (where `delta` is the difference between the current block and the ommer) + - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` + - The account `0xaa` (block miner) is awarded `block_reward / 32` + +To make `t8n` apply these, the following inputs are required: + +- `--state.reward` + - For ethash, it is `5000000000000000000` `wei`, + - If this is not defined, mining rewards are not applied, + - A value of `0` is valid, and causes accounts to be 'touched'. +- For each ommer, the tool needs to be given an `address\` and a `delta`. This + is done via the `ommers` field in `env`. + +Note: the tool does not verify that e.g. the normal uncle rules apply, +and allows e.g two uncles at the same height, or the uncle-distance. This means that +the tool allows for negative uncle reward (distance > 8) + +Example: +`./testdata/5/env.json`: +```json +{ + "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000", + "ommers": [ + {"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, + {"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } + ] +} +``` +When applying this, using a reward of `0x08` +Output: +```json +{ + "alloc": { + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { + "balance": "0x88" + }, + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { + "balance": "0x70" + }, + "0xcccccccccccccccccccccccccccccccccccccccc": { + "balance": "0x60" + } + } +} +``` +#### Future EIPS + +It is also possible to experiment with future eips that are not yet defined in a hard fork. +Example, putting EIP-1344 into Frontier: +``` +./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json +``` + +#### Block history + +The `BLOCKHASH` opcode requires blockhashes to be provided by the caller, inside the `env`. +If a required blockhash is not provided, the exit code should be `4`: +Example where blockhashes are provided: +``` +./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace --state.fork=Berlin + +``` + +``` +cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 +``` +``` +{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"BLOCKHASH"} +{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"depth":1,"refund":0,"opName":"STOP"} +{"output":"","gasUsed":"0x17"} +``` + +In this example, the caller has not provided the required blockhash: +``` +./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace --state.fork=Berlin +ERROR(4): getHash(3) invoked, blockhash for that block not provided +``` +Error code: 4 + +#### Chaining + +Another thing that can be done, is to chain invocations: +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json --state.fork=Berlin + +``` +What happened here, is that we first applied two identical transactions, so the second one was rejected. +Then, taking the poststate alloc as the input for the next state, we tried again to include +the same two transactions: this time, both failed due to too low nonce. + +In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the +actual blocknumber (exposed to the EVM) would not increase. + +#### Transactions in RLP form + +It is possible to provide already-signed transactions as input to, using an `input.txs` which ends with the `rlp` suffix. +The input format for RLP-form transactions is _identical_ to the _output_ format for block bodies. Therefore, it's fully possible +to use the evm to go from `json` input to `rlp` input. + +The following command takes **json** the transactions in `./testdata/13/txs.json` and signs them. After execution, they are output to `signed_txs.rlp`.: +``` +./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./testdata/13/txs.json --input.env=./testdata/13/env.json --output.result=alloc_jsontx.json --output.body=signed_txs.rlp +INFO [12-27|09:25:11.102] Trie dumping started root=e4b924..6aef61 +INFO [12-27|09:25:11.102] Trie dumping complete accounts=3 elapsed="275.66µs" +INFO [12-27|09:25:11.102] Wrote file file=alloc.json +INFO [12-27|09:25:11.103] Wrote file file=alloc_jsontx.json +INFO [12-27|09:25:11.103] Wrote file file=signed_txs.rlp +``` + +The `output.body` is the rlp-list of transactions, encoded in hex and placed in a string a'la `json` encoding rules: +``` +cat signed_txs.rlp +"0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" +``` + +We can use `rlpdump` to check what the contents are: +``` +rlpdump -hex $(cat signed_txs.rlp | jq -r ) +[ + 02f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904, + 02f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9, +] +``` +Now, we can now use those (or any other already signed transactions), as input, like so: +``` +./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./signed_txs.rlp --input.env=./testdata/13/env.json --output.result=alloc_rlptx.json +INFO [12-27|09:25:11.187] Trie dumping started root=e4b924..6aef61 +INFO [12-27|09:25:11.187] Trie dumping complete accounts=3 elapsed="123.676µs" +INFO [12-27|09:25:11.187] Wrote file file=alloc.json +INFO [12-27|09:25:11.187] Wrote file file=alloc_rlptx.json +``` +You might have noticed that the results from these two invocations were stored in two separate files. +And we can now finally check that they match. +``` +cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot +"0xe4b924a6adb5959fccf769d5b7bb2f6359e26d1e76a2443c5a91a36d826aef61" +"0xe4b924a6adb5959fccf769d5b7bb2f6359e26d1e76a2443c5a91a36d826aef61" +``` + +## Transaction tool + +The transaction tool is used to perform static validity checks on transactions such as: +* intrinsic gas calculation +* max values on integers +* fee semantics, such as `maxFeePerGas < maxPriorityFeePerGas` +* newer tx types on old forks + +### Examples + +``` +./evm t9n --state.fork Homestead --input.txs testdata/15/signed_txs.rlp +[ + { + "error": "transaction type not supported", + "hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476" + }, + { + "error": "transaction type not supported", + "hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a" + } +] +``` +``` +./evm t9n --state.fork London --input.txs testdata/15/signed_txs.rlp +[ + { + "address": "0xd02d72e067e77158444ef2020ff2d325f929b363", + "hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", + "intrinsicGas": "0x5208" + }, + { + "address": "0xd02d72e067e77158444ef2020ff2d325f929b363", + "hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", + "intrinsicGas": "0x5208" + } +] +``` +## Block builder tool (b11r) + +The `evm b11r` tool is used to assemble and seal full block rlps. + +### Specification + +#### Command line params + +Command line params that need to be supported are: + +``` + --input.header value `stdin` or file name of where to find the block header to use. (default: "header.json") + --input.ommers value `stdin` or file name of where to find the list of ommer header RLPs to use. + --input.txs value `stdin` or file name of where to find the transactions list in RLP form. (default: "txs.rlp") + --output.basedir value Specifies where output files are placed. Will be created if it does not exist. + --output.block value Determines where to put the alloc of the post-state. (default: "block.json") + - into the file + `stdout` - into the stdout output + `stderr` - into the stderr output + --seal.clique value Seal block with Clique. `stdin` or file name of where to find the Clique sealing data. + --seal.ethash Seal block with ethash. (default: false) + --seal.ethash.dir value Path to ethash DAG. If none exists, a new DAG will be generated. + --seal.ethash.mode value Defines the type and amount of PoW verification an ethash engine makes. (default: "normal") + --verbosity value Sets the verbosity level. (default: 3) +``` + +#### Objects + +##### `header` + +The `header` object is a consensus 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"` +} +``` +#### `ommers` + +The `ommers` object is a list of RLP-encoded ommer blocks in hex +representation. + +```go= +type Ommers []string +``` + +#### `txs` + +The `txs` object is a list of RLP-encoded transactions in hex representation. + +```go= +type Txs []string +``` + +#### `clique` + +The `clique` object provides the necessary information to complete a clique +seal of the block. + +```go= +var CliqueInfo struct { + Key *common.Hash `json:"secretKey"` + Voted *common.Address `json:"voted"` + Authorize *bool `json:"authorize"` + Vanity common.Hash `json:"vanity"` +} +``` + +#### `output` + +The `output` object contains two values, the block RLP and the block hash. + +```go= +type BlockInfo struct { + Rlp []byte `json:"rlp"` + Hash common.Hash `json:"hash"` +} +``` + +## A Note on Encoding + +The encoding of values for `evm` utility attempts to be relatively flexible. It +generally supports hex-encoded or decimal-encoded numeric values, and +hex-encoded byte values (like `common.Address`, `common.Hash`, etc). When in +doubt, the [`execution-apis`](https://github.com/ethereum/execution-apis) way +of encoding should always be accepted. + +## Testing + +There are many test cases in the [`cmd/evm/testdata`](./testdata) directory. +These fixtures are used to power the `t8n` tests in +[`t8n_test.go`](./t8n_test.go). The best way to verify correctness of new `evm` +implementations is to execute these and verify the output and error codes match +the expected values. + diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go new file mode 100644 index 0000000..d5cd8d8 --- /dev/null +++ b/cmd/evm/blockrunner.go @@ -0,0 +1,100 @@ +// 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 ( + "encoding/json" + "errors" + "fmt" + "os" + "regexp" + "sort" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "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", + 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") + } + + var tracer *tracing.Hooks + // Configure the EVM logger + if ctx.Bool(MachineFlag.Name) { + tracer = logger.NewJSONLogger(&logger.Config{ + EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), + DisableStack: ctx.Bool(DisableStackFlag.Name), + DisableStorage: ctx.Bool(DisableStorageFlag.Name), + EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), + }, os.Stderr) + } + // Load the test content from the input file + src, err := os.ReadFile(ctx.Args().First()) + if err != nil { + return err + } + var tests map[string]tests.BlockTest + if err = json.Unmarshal(src, &tests); err != nil { + return 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, false, 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 new file mode 100644 index 0000000..c071834 --- /dev/null +++ b/cmd/evm/compiler.go @@ -0,0 +1,55 @@ +// 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 . + +package main + +import ( + "errors" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" + + "github.com/urfave/cli/v2" +) + +var compileCommand = &cli.Command{ + Action: compileCmd, + Name: "compile", + Usage: "Compiles easm source to evm binary", + ArgsUsage: "", +} + +func compileCmd(ctx *cli.Context) error { + debug := ctx.Bool(DebugFlag.Name) + + if len(ctx.Args().First()) == 0 { + return errors.New("filename required") + } + + fn := ctx.Args().First() + src, err := os.ReadFile(fn) + if err != nil { + return err + } + + bin, err := compiler.Compile(fn, src, debug) + if err != nil { + return err + } + fmt.Println(bin) + return nil +} diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go new file mode 100644 index 0000000..b1f35cb --- /dev/null +++ b/cmd/evm/disasm.go @@ -0,0 +1,55 @@ +// 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 . + +package main + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/ethereum/go-ethereum/core/asm" + "github.com/urfave/cli/v2" +) + +var disasmCommand = &cli.Command{ + Action: disasmCmd, + Name: "disasm", + Usage: "Disassembles evm binary", + ArgsUsage: "", +} + +func disasmCmd(ctx *cli.Context) error { + var in string + switch { + case len(ctx.Args().First()) > 0: + fn := ctx.Args().First() + input, err := os.ReadFile(fn) + if err != nil { + return err + } + in = string(input) + case ctx.IsSet(InputFlag.Name): + in = ctx.String(InputFlag.Name) + default: + return errors.New("missing filename or --input value") + } + + code := strings.TrimSpace(in) + fmt.Printf("%v\n", code) + return asm.PrintDisassembled(code) +} diff --git a/cmd/evm/internal/compiler/compiler.go b/cmd/evm/internal/compiler/compiler.go new file mode 100644 index 0000000..54981b6 --- /dev/null +++ b/cmd/evm/internal/compiler/compiler.go @@ -0,0 +1,39 @@ +// 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 . + +package compiler + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/core/asm" +) + +func Compile(fn string, src []byte, debug bool) (string, error) { + compiler := asm.NewCompiler(debug) + compiler.Feed(asm.Lex(src, debug)) + + bin, compileErrors := compiler.Compile() + if len(compileErrors) > 0 { + // report errors + for _, err := range compileErrors { + fmt.Printf("%s:%v\n", fn, err) + } + return "", errors.New("compiling failed") + } + return bin, nil +} diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go new file mode 100644 index 0000000..37a6db9 --- /dev/null +++ b/cmd/evm/internal/t8ntool/block.go @@ -0,0 +1,340 @@ +// 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 t8ntool + +import ( + "crypto/ecdsa" + "encoding/json" + "errors" + "fmt" + "math/big" + "os" + + "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/clique" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "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"` + 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 + BlobGasUsed *math.HexOrDecimal64 + ExcessBlobGas *math.HexOrDecimal64 +} + +type bbInput struct { + Header *header `json:"header,omitempty"` + OmmersRlp []string `json:"ommers,omitempty"` + TxRlp string `json:"txs,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` + Clique *cliqueInput `json:"clique,omitempty"` + + Ethash bool `json:"-"` + Txs []*types.Transaction `json:"-"` + Ommers []*types.Header `json:"-"` +} + +type cliqueInput struct { + Key *ecdsa.PrivateKey + Voted *common.Address + Authorize *bool + Vanity common.Hash +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (c *cliqueInput) UnmarshalJSON(input []byte) error { + var x struct { + Key *common.Hash `json:"secretKey"` + Voted *common.Address `json:"voted"` + Authorize *bool `json:"authorize"` + Vanity common.Hash `json:"vanity"` + } + if err := json.Unmarshal(input, &x); err != nil { + return err + } + if x.Key == nil { + return errors.New("missing required field 'secretKey' for cliqueInput") + } + if ecdsaKey, err := crypto.ToECDSA(x.Key[:]); err != nil { + return err + } else { + c.Key = ecdsaKey + } + c.Voted = x.Voted + c.Authorize = x.Authorize + c.Vanity = x.Vanity + return nil +} + +// 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, + BlobGasUsed: i.Header.BlobGasUsed, + ExcessBlobGas: i.Header.ExcessBlobGas, + ParentBeaconRoot: i.Header.ParentBeaconBlockRoot, + } + + // Fill optional values. + if i.Header.OmmerHash != nil { + header.UncleHash = *i.Header.OmmerHash + } else if len(i.Ommers) != 0 { + // Calculate the ommer hash if none is provided and there are ommers to hash + header.UncleHash = types.CalcUncleHash(i.Ommers) + } + if i.Header.Coinbase != nil { + header.Coinbase = *i.Header.Coinbase + } + if i.Header.TxHash != nil { + header.TxHash = *i.Header.TxHash + } + if i.Header.ReceiptHash != nil { + header.ReceiptHash = *i.Header.ReceiptHash + } + if i.Header.Nonce != nil { + header.Nonce = *i.Header.Nonce + } + if i.Header.Difficulty != nil { + header.Difficulty = i.Header.Difficulty + } + return types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: i.Txs, Uncles: i.Ommers, Withdrawals: i.Withdrawals}) +} + +// SealBlock seals the given block using the configured engine. +func (i *bbInput) SealBlock(block *types.Block) (*types.Block, error) { + switch { + case i.Clique != nil: + return i.sealClique(block) + default: + return block, nil + } +} + +// sealClique seals the given block using clique. +func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) { + // If any clique value overwrites an explicit header value, fail + // to avoid silently building a block with unexpected values. + if i.Header.Extra != nil { + return nil, NewError(ErrorConfig, errors.New("sealing with clique will overwrite provided extra data")) + } + header := block.Header() + if i.Clique.Voted != nil { + if i.Header.Coinbase != nil { + return nil, NewError(ErrorConfig, errors.New("sealing with clique and voting will overwrite provided coinbase")) + } + header.Coinbase = *i.Clique.Voted + } + if i.Clique.Authorize != nil { + if i.Header.Nonce != nil { + return nil, NewError(ErrorConfig, errors.New("sealing with clique and voting will overwrite provided nonce")) + } + if *i.Clique.Authorize { + header.Nonce = [8]byte{} + } else { + header.Nonce = [8]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + } + } + // Extra is fixed 32 byte vanity and 65 byte signature + header.Extra = make([]byte, 32+65) + copy(header.Extra[0:32], i.Clique.Vanity.Bytes()[:]) + + // Sign the seal hash and fill in the rest of the extra data + h := clique.SealHash(header) + sighash, err := crypto.Sign(h[:], i.Clique.Key) + if err != nil { + return nil, err + } + copy(header.Extra[32:], sighash) + block = block.WithSeal(header) + return block, nil +} + +// BuildBlock constructs a block from the given inputs. +func BuildBlock(ctx *cli.Context) error { + baseDir, err := createBasedir(ctx) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) + } + inputData, err := readInput(ctx) + if err != nil { + return err + } + block := inputData.ToBlock() + block, err = inputData.SealBlock(block) + if err != nil { + return err + } + return dispatchBlock(ctx, baseDir, block) +} + +func readInput(ctx *cli.Context) (*bbInput, error) { + var ( + headerStr = ctx.String(InputHeaderFlag.Name) + ommersStr = ctx.String(InputOmmersFlag.Name) + withdrawalsStr = ctx.String(InputWithdrawalsFlag.Name) + txsStr = ctx.String(InputTxsRlpFlag.Name) + cliqueStr = ctx.String(SealCliqueFlag.Name) + inputData = &bbInput{} + ) + if headerStr == stdinSelector || ommersStr == stdinSelector || txsStr == stdinSelector || cliqueStr == stdinSelector { + decoder := json.NewDecoder(os.Stdin) + if err := decoder.Decode(inputData); err != nil { + return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err)) + } + } + if cliqueStr != stdinSelector && cliqueStr != "" { + var clique cliqueInput + if err := readFile(cliqueStr, "clique", &clique); err != nil { + return nil, err + } + inputData.Clique = &clique + } + if headerStr != stdinSelector { + var env header + if err := readFile(headerStr, "header", &env); err != nil { + return nil, err + } + inputData.Header = &env + } + if ommersStr != stdinSelector && ommersStr != "" { + var ommers []string + if err := readFile(ommersStr, "ommers", &ommers); err != nil { + return nil, err + } + inputData.OmmersRlp = ommers + } + if withdrawalsStr != stdinSelector && withdrawalsStr != "" { + var withdrawals []*types.Withdrawal + if err := readFile(withdrawalsStr, "withdrawals", &withdrawals); err != nil { + return nil, err + } + inputData.Withdrawals = withdrawals + } + if txsStr != stdinSelector { + var txs string + if err := readFile(txsStr, "txs", &txs); err != nil { + return nil, err + } + inputData.TxRlp = txs + } + // Deserialize rlp txs and ommers + var ( + ommers = []*types.Header{} + txs = []*types.Transaction{} + ) + if inputData.TxRlp != "" { + if err := rlp.DecodeBytes(common.FromHex(inputData.TxRlp), &txs); err != nil { + return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode transaction from rlp data: %v", err)) + } + inputData.Txs = txs + } + for _, str := range inputData.OmmersRlp { + type extblock struct { + Header *types.Header + Txs []*types.Transaction + Ommers []*types.Header + } + var ommer *extblock + if err := rlp.DecodeBytes(common.FromHex(str), &ommer); err != nil { + return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode ommer from rlp data: %v", err)) + } + ommers = append(ommers, ommer.Header) + } + inputData.Ommers = ommers + + return inputData, nil +} + +// dispatchBlock writes the output data to either stderr or stdout, or to the specified +// files +func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error { + raw, _ := rlp.EncodeToBytes(block) + type blockInfo struct { + Rlp hexutil.Bytes `json:"rlp"` + Hash common.Hash `json:"hash"` + } + enc := blockInfo{ + Rlp: raw, + Hash: block.Hash(), + } + b, err := json.MarshalIndent(enc, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + switch dest := ctx.String(OutputBlockFlag.Name); dest { + case "stdout": + os.Stdout.Write(b) + os.Stdout.WriteString("\n") + case "stderr": + os.Stderr.Write(b) + os.Stderr.WriteString("\n") + default: + if err := saveFile(baseDir, dest, enc); err != nil { + return err + } + } + return nil +} diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go new file mode 100644 index 0000000..a4c5f6e --- /dev/null +++ b/cmd/evm/internal/t8ntool/execution.go @@ -0,0 +1,438 @@ +// 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" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "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/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/triedb" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" +) + +type Prestate struct { + Env stEnv `json:"env"` + Pre types.GenesisAlloc `json:"pre"` +} + +// ExecutionResult contains the execution status after running a state test, any +// error that might have occurred and a dump of the final state if requested. +type ExecutionResult struct { + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptsRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom" gencodec:"required"` + Receipts types.Receipts `json:"receipts"` + Rejected []*rejectedTx `json:"rejected,omitempty"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` + WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` + CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` + CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` +} + +type ommer struct { + Delta uint64 `json:"delta"` + Address common.Address `json:"address"` +} + +//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:"currentExcessBlobGas,omitempty"` + ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"` + ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"` + ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` +} + +type stEnvMarshaling struct { + Coinbase common.UnprefixedAddress + Difficulty *math.HexOrDecimal256 + Random *math.HexOrDecimal256 + ParentDifficulty *math.HexOrDecimal256 + ParentBaseFee *math.HexOrDecimal256 + ParentGasUsed math.HexOrDecimal64 + ParentGasLimit math.HexOrDecimal64 + GasLimit math.HexOrDecimal64 + Number math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 + ParentTimestamp math.HexOrDecimal64 + BaseFee *math.HexOrDecimal256 + ExcessBlobGas *math.HexOrDecimal64 + ParentExcessBlobGas *math.HexOrDecimal64 + ParentBlobGasUsed *math.HexOrDecimal64 +} + +type rejectedTx struct { + Index int `json:"index"` + Err string `json:"error"` +} + +// Apply applies a set of transactions to a pre-state +func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, + txIt txIterator, miningReward int64, + getTracerFn func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error)) (*state.StateDB, *ExecutionResult, []byte, error) { + // Capture errors for BLOCKHASH operation, if we haven't been supplied the + // required blockhashes + var hashError error + getHash := func(num uint64) common.Hash { + if pre.Env.BlockHashes == nil { + hashError = fmt.Errorf("getHash(%d) invoked, no blockhashes provided", num) + return common.Hash{} + } + h, ok := pre.Env.BlockHashes[math.HexOrDecimal64(num)] + if !ok { + hashError = fmt.Errorf("getHash(%d) invoked, blockhash for that block not provided", num) + } + return h + } + var ( + statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) + signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) + gaspool = new(core.GasPool) + blockHash = common.Hash{0x13, 0x37} + rejectedTxs []*rejectedTx + includedTxs types.Transactions + gasUsed = uint64(0) + blobGasUsed = uint64(0) + receipts = make(types.Receipts, 0) + txIndex = 0 + ) + gaspool.AddGas(pre.Env.GasLimit) + vmContext := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: pre.Env.Coinbase, + BlockNumber: new(big.Int).SetUint64(pre.Env.Number), + Time: pre.Env.Timestamp, + Difficulty: pre.Env.Difficulty, + GasLimit: pre.Env.GasLimit, + GetHash: getHash, + } + // If currentBaseFee is defined, add it to the vmContext. + if pre.Env.BaseFee != nil { + vmContext.BaseFee = new(big.Int).Set(pre.Env.BaseFee) + } + // If random is defined, add it to the vmContext. + if pre.Env.Random != nil { + rnd := common.BigToHash(pre.Env.Random) + vmContext.Random = &rnd + } + // Calculate the BlobBaseFee + var excessBlobGas uint64 + if pre.Env.ExcessBlobGas != nil { + excessBlobGas = *pre.Env.ExcessBlobGas + vmContext.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + } else { + // If it is not explicitly defined, but we have the parent values, we try + // to calculate it ourselves. + parentExcessBlobGas := pre.Env.ParentExcessBlobGas + parentBlobGasUsed := pre.Env.ParentBlobGasUsed + if parentExcessBlobGas != nil && parentBlobGasUsed != nil { + excessBlobGas = eip4844.CalcExcessBlobGas(*parentExcessBlobGas, *parentBlobGasUsed) + vmContext.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + } + } + // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's + // done in StateProcessor.Process(block, ...), right before transactions are applied. + if chainConfig.DAOForkSupport && + chainConfig.DAOForkBlock != nil && + chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { + misc.ApplyDAOHardFork(statedb) + } + if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { + evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) + core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) + } + + 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}) + continue + } + msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee) + if err != nil { + log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", err) + 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, traceOutput, err := getTracerFn(txIndex, tx.Hash()) + if err != nil { + return nil, nil, nil, err + } + if tracer != nil { + vmConfig.Tracer = tracer.Hooks + } + statedb.SetTxContext(tx.Hash(), txIndex) + + var ( + txContext = core.NewEVMTxContext(msg) + snapshot = statedb.Snapshot() + prevGas = gaspool.Gas() + ) + evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) + + if tracer != nil && tracer.OnTxStart != nil { + tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) + } + // (ret []byte, usedGas uint64, failed bool, err error) + msgResult, err := core.ApplyMessage(evm, msg, gaspool) + if err != nil { + statedb.RevertToSnapshot(snapshot) + log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) + rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) + gaspool.SetGas(prevGas) + if tracer != nil { + if tracer.OnTxEnd != nil { + tracer.OnTxEnd(nil, err) + } + if err := writeTraceResult(tracer, traceOutput); err != nil { + log.Warn("Error writing tracer output", "err", err) + } + } + continue + } + includedTxs = append(includedTxs, tx) + if hashError != nil { + return nil, nil, nil, NewError(ErrorMissingBlockhash, hashError) + } + blobGasUsed += txBlobGas + gasUsed += msgResult.UsedGas + + // Receipt: + { + var root []byte + if chainConfig.IsByzantium(vmContext.BlockNumber) { + statedb.Finalise(true) + } else { + root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes() + } + + // Create a new receipt for the transaction, storing the intermediate root and + // gas used by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: gasUsed} + if msgResult.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + receipt.TxHash = tx.Hash() + receipt.GasUsed = msgResult.UsedGas + + // 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()) + } + + // Set the receipt logs and create the bloom filter. + receipt.Logs = statedb.GetLogs(tx.Hash(), vmContext.BlockNumber.Uint64(), blockHash) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + // These three are non-consensus fields: + //receipt.BlockHash + //receipt.BlockNumber + receipt.TransactionIndex = uint(txIndex) + receipts = append(receipts, receipt) + if tracer != nil { + if tracer.Hooks.OnTxEnd != nil { + tracer.Hooks.OnTxEnd(receipt, nil) + } + if err = writeTraceResult(tracer, traceOutput); err != nil { + log.Warn("Error writing tracer output", "err", err) + } + } + } + + txIndex++ + } + statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) + // Add mining reward? (-1 means rewards are disabled) + if miningReward >= 0 { + // Add mining reward. The mining reward may be `0`, which only makes a difference in the cases + // where + // - the coinbase self-destructed, or + // - there are only 'bad' transactions, which aren't executed. In those cases, + // the coinbase gets no txfee, so isn't created, and thus needs to be touched + var ( + blockReward = big.NewInt(miningReward) + minerReward = new(big.Int).Set(blockReward) + perOmmer = new(big.Int).Rsh(blockReward, 5) + ) + for _, ommer := range pre.Env.Ommers { + // Add 1/32th for each ommer included + minerReward.Add(minerReward, perOmmer) + // Add (8-delta)/8 + reward := big.NewInt(8) + reward.Sub(reward, new(big.Int).SetUint64(ommer.Delta)) + reward.Mul(reward, blockReward) + reward.Rsh(reward, 3) + statedb.AddBalance(ommer.Address, uint256.MustFromBig(reward), tracing.BalanceIncreaseRewardMineUncle) + } + statedb.AddBalance(pre.Env.Coinbase, uint256.MustFromBig(minerReward), tracing.BalanceIncreaseRewardMineBlock) + } + // Apply withdrawals + for _, w := range pre.Env.Withdrawals { + // Amount is in gwei, turn into wei + amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) + statedb.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal) + } + // Commit block + root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber)) + if err != nil { + return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) + } + execRs := &ExecutionResult{ + StateRoot: root, + TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)), + ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), + Bloom: types.CreateBloom(receipts), + LogsHash: rlpHash(statedb.Logs()), + Receipts: receipts, + Rejected: rejectedTxs, + Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty), + GasUsed: (math.HexOrDecimal64)(gasUsed), + BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee), + } + if pre.Env.Withdrawals != nil { + h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil)) + execRs.WithdrawalsRoot = &h + } + if vmContext.BlobBaseFee != nil { + execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas) + execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed) + } + // Re-create statedb instance with new root upon the updated database + // for accessing latest states. + statedb, err = state.New(root, statedb.Database(), nil) + if err != nil { + return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err)) + } + body, _ := rlp.EncodeToBytes(includedTxs) + return statedb, execRs, body, nil +} + +func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB { + sdb := state.NewDatabaseWithConfig(db, &triedb.Config{Preimages: true}) + statedb, _ := state.New(types.EmptyRootHash, sdb, nil) + for addr, a := range accounts { + statedb.SetCode(addr, a.Code) + statedb.SetNonce(addr, a.Nonce) + statedb.SetBalance(addr, uint256.MustFromBig(a.Balance), tracing.BalanceIncreaseGenesisBalance) + for k, v := range a.Storage { + statedb.SetState(addr, k, v) + } + } + // Commit and re-open to start with a clean state. + root, _ := statedb.Commit(0, false) + statedb, _ = state.New(root, sdb, nil) + return statedb +} + +func rlpHash(x interface{}) (h common.Hash) { + hw := sha3.NewLegacyKeccak256() + rlp.Encode(hw, x) + hw.Sum(h[:0]) + return h +} + +// calcDifficulty is based on ethash.CalcDifficulty. This method is used in case +// the caller does not provide an explicit difficulty, but instead provides only +// parent timestamp + difficulty. +// Note: this method only works for ethash engine. +func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime uint64, + parentDifficulty *big.Int, parentUncleHash common.Hash) *big.Int { + uncleHash := parentUncleHash + if uncleHash == (common.Hash{}) { + uncleHash = types.EmptyUncleHash + } + parent := &types.Header{ + ParentHash: common.Hash{}, + UncleHash: uncleHash, + Difficulty: parentDifficulty, + Number: new(big.Int).SetUint64(number - 1), + Time: parentTime, + } + return ethash.CalcDifficulty(config, currentTime, parent) +} + +func writeTraceResult(tracer *tracers.Tracer, f io.WriteCloser) error { + defer f.Close() + result, err := tracer.GetResult() + if err != nil || result == nil { + return err + } + err = json.NewEncoder(f).Encode(result) + if err != nil { + return err + } + return nil +} diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go new file mode 100644 index 0000000..f2606c8 --- /dev/null +++ b/cmd/evm/internal/t8ntool/flags.go @@ -0,0 +1,157 @@ +// 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 ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/tests" + "github.com/urfave/cli/v2" +) + +var ( + TraceFlag = &cli.BoolFlag{ + Name: "trace", + Usage: "Configures the use of the JSON opcode tracer. This tracer emits traces to files as trace--.jsonl", + } + 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", + Usage: "Enable full memory dump in traces", + } + TraceDisableStackFlag = &cli.BoolFlag{ + Name: "trace.nostack", + Usage: "Disable stack output in traces", + } + TraceEnableReturnDataFlag = &cli.BoolFlag{ + Name: "trace.returndata", + Usage: "Enable return data output in traces", + } + TraceEnableCallFramesFlag = &cli.BoolFlag{ + Name: "trace.callframes", + Usage: "Enable call frames output in traces", + } + OutputBasedir = &cli.StringFlag{ + Name: "output.basedir", + Usage: "Specifies where output files are placed. Will be created if it does not exist.", + Value: "", + } + OutputBodyFlag = &cli.StringFlag{ + Name: "output.body", + Usage: "If set, the RLP of the transactions (block body) will be written to this file.", + Value: "", + } + OutputAllocFlag = &cli.StringFlag{ + Name: "output.alloc", + Usage: "Determines where to put the `alloc` of the post-state.\n" + + "\t`stdout` - into the stdout output\n" + + "\t`stderr` - into the stderr output\n" + + "\t - into the file ", + Value: "alloc.json", + } + OutputResultFlag = &cli.StringFlag{ + Name: "output.result", + Usage: "Determines where to put the `result` (stateroot, txroot etc) of the post-state.\n" + + "\t`stdout` - into the stdout output\n" + + "\t`stderr` - into the stderr output\n" + + "\t - into the file ", + Value: "result.json", + } + OutputBlockFlag = &cli.StringFlag{ + Name: "output.block", + Usage: "Determines where to put the `block` after building.\n" + + "\t`stdout` - into the stdout output\n" + + "\t`stderr` - into the stderr output\n" + + "\t - into the file ", + Value: "block.json", + } + InputAllocFlag = &cli.StringFlag{ + Name: "input.alloc", + Usage: "`stdin` or file name of where to find the prestate alloc to use.", + Value: "alloc.json", + } + InputEnvFlag = &cli.StringFlag{ + Name: "input.env", + Usage: "`stdin` or file name of where to find the prestate env to use.", + Value: "env.json", + } + InputTxsFlag = &cli.StringFlag{ + Name: "input.txs", + Usage: "`stdin` or file name of where to find the transactions to apply. " + + "If the file extension is '.rlp', then the data is interpreted as an RLP list of signed transactions." + + "The '.rlp' format is identical to the output.body format.", + Value: "txs.json", + } + InputHeaderFlag = &cli.StringFlag{ + Name: "input.header", + Usage: "`stdin` or file name of where to find the block header to use.", + Value: "header.json", + } + InputOmmersFlag = &cli.StringFlag{ + Name: "input.ommers", + Usage: "`stdin` or file name of where to find the list of ommer header RLPs to use.", + } + InputWithdrawalsFlag = &cli.StringFlag{ + Name: "input.withdrawals", + Usage: "`stdin` or file name of where to find the list of withdrawals to use.", + } + InputTxsRlpFlag = &cli.StringFlag{ + Name: "input.txs", + Usage: "`stdin` or file name of where to find the transactions list in RLP form.", + Value: "txs.rlp", + } + SealCliqueFlag = &cli.StringFlag{ + Name: "seal.clique", + Usage: "Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.", + } + RewardFlag = &cli.Int64Flag{ + Name: "state.reward", + Usage: "Mining reward. Set to -1 to disable", + Value: 0, + } + ChainIDFlag = &cli.Int64Flag{ + Name: "state.chainid", + Usage: "ChainID to use", + Value: 1, + } + ForknameFlag = &cli.StringFlag{ + Name: "state.fork", + Usage: fmt.Sprintf("Name of ruleset to use."+ + "\n\tAvailable forknames:"+ + "\n\t %v"+ + "\n\tAvailable extra eips:"+ + "\n\t %v"+ + "\n\tSyntax (+ExtraEip)", + strings.Join(tests.AvailableForks(), "\n\t "), + strings.Join(vm.ActivateableEips(), ", ")), + Value: "GrayGlacier", + } + VerbosityFlag = &cli.IntFlag{ + Name: "verbosity", + Usage: "sets the verbosity level", + Value: 3, + } +) diff --git a/cmd/evm/internal/t8ntool/gen_header.go b/cmd/evm/internal/t8ntool/gen_header.go new file mode 100644 index 0000000..a8c8668 --- /dev/null +++ b/cmd/evm/internal/t8ntool/gen_header.go @@ -0,0 +1,159 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package t8ntool + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" +) + +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"` + 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 + enc.OmmerHash = h.OmmerHash + enc.Coinbase = h.Coinbase + enc.Root = h.Root + enc.TxHash = h.TxHash + enc.ReceiptHash = h.ReceiptHash + enc.Bloom = h.Bloom + enc.Difficulty = (*math.HexOrDecimal256)(h.Difficulty) + enc.Number = (*math.HexOrDecimal256)(h.Number) + enc.GasLimit = math.HexOrDecimal64(h.GasLimit) + enc.GasUsed = math.HexOrDecimal64(h.GasUsed) + enc.Time = math.HexOrDecimal64(h.Time) + enc.Extra = h.Extra + enc.MixDigest = h.MixDigest + 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"` + 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 { + return err + } + if dec.ParentHash != nil { + h.ParentHash = *dec.ParentHash + } + if dec.OmmerHash != nil { + h.OmmerHash = dec.OmmerHash + } + if dec.Coinbase != nil { + h.Coinbase = dec.Coinbase + } + if dec.Root == nil { + return errors.New("missing required field 'stateRoot' for header") + } + h.Root = *dec.Root + if dec.TxHash != nil { + h.TxHash = dec.TxHash + } + if dec.ReceiptHash != nil { + h.ReceiptHash = dec.ReceiptHash + } + if dec.Bloom != nil { + h.Bloom = *dec.Bloom + } + if dec.Difficulty != nil { + h.Difficulty = (*big.Int)(dec.Difficulty) + } + if dec.Number == nil { + return errors.New("missing required field 'number' for header") + } + h.Number = (*big.Int)(dec.Number) + if dec.GasLimit == nil { + return errors.New("missing required field 'gasLimit' for header") + } + h.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed != nil { + h.GasUsed = uint64(*dec.GasUsed) + } + if dec.Time == nil { + return errors.New("missing required field 'timestamp' for header") + } + h.Time = uint64(*dec.Time) + if dec.Extra != nil { + h.Extra = *dec.Extra + } + if dec.MixDigest != nil { + h.MixDigest = *dec.MixDigest + } + if dec.Nonce != nil { + h.Nonce = dec.Nonce + } + if dec.BaseFee != nil { + h.BaseFee = (*big.Int)(dec.BaseFee) + } + 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 new file mode 100644 index 0000000..d47db4a --- /dev/null +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -0,0 +1,158 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package t8ntool + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" +) + +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:"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) + enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty) + enc.Random = (*math.HexOrDecimal256)(s.Random) + enc.ParentDifficulty = (*math.HexOrDecimal256)(s.ParentDifficulty) + enc.ParentBaseFee = (*math.HexOrDecimal256)(s.ParentBaseFee) + enc.ParentGasUsed = math.HexOrDecimal64(s.ParentGasUsed) + enc.ParentGasLimit = math.HexOrDecimal64(s.ParentGasLimit) + enc.GasLimit = math.HexOrDecimal64(s.GasLimit) + enc.Number = math.HexOrDecimal64(s.Number) + enc.Timestamp = math.HexOrDecimal64(s.Timestamp) + enc.ParentTimestamp = math.HexOrDecimal64(s.ParentTimestamp) + enc.BlockHashes = s.BlockHashes + enc.Ommers = s.Ommers + enc.Withdrawals = s.Withdrawals + enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) + enc.ParentUncleHash = s.ParentUncleHash + enc.ExcessBlobGas = (*math.HexOrDecimal64)(s.ExcessBlobGas) + enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas) + enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed) + enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *stEnv) UnmarshalJSON(input []byte) error { + type stEnv struct { + Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"` + Random *math.HexOrDecimal256 `json:"currentRandom"` + ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` + ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"` + ParentGasUsed *math.HexOrDecimal64 `json:"parentGasUsed,omitempty"` + ParentGasLimit *math.HexOrDecimal64 `json:"parentGasLimit,omitempty"` + GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` + ParentUncleHash *common.Hash `json:"parentUncleHash"` + 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 { + return err + } + if dec.Coinbase == nil { + return errors.New("missing required field 'currentCoinbase' for stEnv") + } + s.Coinbase = common.Address(*dec.Coinbase) + if dec.Difficulty != nil { + s.Difficulty = (*big.Int)(dec.Difficulty) + } + if dec.Random != nil { + s.Random = (*big.Int)(dec.Random) + } + if dec.ParentDifficulty != nil { + s.ParentDifficulty = (*big.Int)(dec.ParentDifficulty) + } + if dec.ParentBaseFee != nil { + s.ParentBaseFee = (*big.Int)(dec.ParentBaseFee) + } + if dec.ParentGasUsed != nil { + s.ParentGasUsed = uint64(*dec.ParentGasUsed) + } + if dec.ParentGasLimit != nil { + s.ParentGasLimit = uint64(*dec.ParentGasLimit) + } + if dec.GasLimit == nil { + return errors.New("missing required field 'currentGasLimit' for stEnv") + } + s.GasLimit = uint64(*dec.GasLimit) + if dec.Number == nil { + return errors.New("missing required field 'currentNumber' for stEnv") + } + s.Number = uint64(*dec.Number) + if dec.Timestamp == nil { + return errors.New("missing required field 'currentTimestamp' for stEnv") + } + s.Timestamp = uint64(*dec.Timestamp) + if dec.ParentTimestamp != nil { + s.ParentTimestamp = uint64(*dec.ParentTimestamp) + } + if dec.BlockHashes != nil { + s.BlockHashes = dec.BlockHashes + } + if dec.Ommers != nil { + s.Ommers = dec.Ommers + } + if dec.Withdrawals != nil { + s.Withdrawals = dec.Withdrawals + } + if dec.BaseFee != nil { + s.BaseFee = (*big.Int)(dec.BaseFee) + } + if dec.ParentUncleHash != nil { + s.ParentUncleHash = *dec.ParentUncleHash + } + if dec.ExcessBlobGas != nil { + s.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } + if dec.ParentExcessBlobGas != nil { + s.ParentExcessBlobGas = (*uint64)(dec.ParentExcessBlobGas) + } + if dec.ParentBlobGasUsed != nil { + s.ParentBlobGasUsed = (*uint64)(dec.ParentBlobGasUsed) + } + if dec.ParentBeaconBlockRoot != nil { + s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot + } + return nil +} diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go new file mode 100644 index 0000000..7f66ba4 --- /dev/null +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -0,0 +1,177 @@ +// 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 t8ntool + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "os" + "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/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/tests" + "github.com/urfave/cli/v2" +) + +type result struct { + Error error + Address common.Address + Hash common.Hash + IntrinsicGas uint64 +} + +// MarshalJSON marshals as JSON with a hash. +func (r *result) MarshalJSON() ([]byte, error) { + type xx struct { + Error string `json:"error,omitempty"` + Address *common.Address `json:"address,omitempty"` + Hash *common.Hash `json:"hash,omitempty"` + IntrinsicGas hexutil.Uint64 `json:"intrinsicGas,omitempty"` + } + var out xx + if r.Error != nil { + out.Error = r.Error.Error() + } + if r.Address != (common.Address{}) { + out.Address = &r.Address + } + if r.Hash != (common.Hash{}) { + out.Hash = &r.Hash + } + out.IntrinsicGas = hexutil.Uint64(r.IntrinsicGas) + return json.Marshal(out) +} + +func Transaction(ctx *cli.Context) error { + var ( + err error + ) + // We need to load the transactions. May be either in stdin input or in files. + // Check if anything needs to be read from stdin + var ( + txStr = ctx.String(InputTxsFlag.Name) + inputData = &input{} + chainConfig *params.ChainConfig + ) + // Construct the chainconfig + if cConf, _, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { + return NewError(ErrorConfig, fmt.Errorf("failed constructing chain configuration: %v", err)) + } else { + chainConfig = cConf + } + // Set the chain id + chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) + var body hexutil.Bytes + if txStr == stdinSelector { + decoder := json.NewDecoder(os.Stdin) + if err := decoder.Decode(inputData); err != nil { + return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err)) + } + // Decode the body of already signed transactions + body = common.FromHex(inputData.TxRlp) + } else { + // Read input from file + 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") { + if err := decoder.Decode(&body); err != nil { + return err + } + } else { + return NewError(ErrorIO, errors.New("only rlp supported")) + } + } + signer := types.MakeSigner(chainConfig, new(big.Int), 0) + // We now have the transactions in 'body', which is supposed to be an + // rlp list of transactions + it, err := rlp.NewListIterator([]byte(body)) + if err != nil { + return err + } + var results []result + for it.Next() { + if err := it.Err(); err != nil { + return NewError(ErrorIO, err) + } + var tx types.Transaction + err := rlp.DecodeBytes(it.Value(), &tx) + if err != nil { + results = append(results, result{Error: err}) + continue + } + r := result{Hash: tx.Hash()} + if sender, err := types.Sender(signer, &tx); err != nil { + r.Error = err + results = append(results, r) + continue + } else { + r.Address = sender + } + // Check intrinsic gas + if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, + chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0)); err != nil { + r.Error = err + results = append(results, r) + continue + } else { + r.IntrinsicGas = gas + if tx.Gas() < gas { + r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas) + results = append(results, r) + continue + } + } + // Validate <256bit fields + switch { + case tx.Nonce()+1 < tx.Nonce(): + r.Error = errors.New("nonce exceeds 2^64-1") + case tx.Value().BitLen() > 256: + r.Error = errors.New("value exceeds 256 bits") + case tx.GasPrice().BitLen() > 256: + r.Error = errors.New("gasPrice exceeds 256 bits") + case tx.GasTipCap().BitLen() > 256: + r.Error = errors.New("maxPriorityFeePerGas exceeds 256 bits") + case tx.GasFeeCap().BitLen() > 256: + r.Error = errors.New("maxFeePerGas exceeds 256 bits") + case tx.GasFeeCap().Cmp(tx.GasTipCap()) < 0: + r.Error = errors.New("maxFeePerGas < maxPriorityFeePerGas") + case new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())).BitLen() > 256: + r.Error = errors.New("gas * gasPrice exceeds 256 bits") + case new(big.Int).Mul(tx.GasFeeCap(), new(big.Int).SetUint64(tx.Gas())).BitLen() > 256: + r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits") + } + // Check whether the init code size has been exceeded. + if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { + r.Error = errors.New("max initcode size exceeded") + } + results = append(results, r) + } + out, err := json.MarshalIndent(results, "", " ") + fmt.Println(string(out)) + return err +} diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go new file mode 100644 index 0000000..fa052f5 --- /dev/null +++ b/cmd/evm/internal/t8ntool/transition.go @@ -0,0 +1,373 @@ +// 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" + "errors" + "fmt" + "io" + "math/big" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "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/tests" + "github.com/urfave/cli/v2" +) + +const ( + ErrorEVM = 2 + ErrorConfig = 3 + ErrorMissingBlockhash = 4 + + ErrorJson = 10 + ErrorIO = 11 + ErrorRlp = 12 + + stdinSelector = "stdin" +) + +type NumberedError struct { + errorCode int + err error +} + +func NewError(errorCode int, err error) *NumberedError { + return &NumberedError{errorCode, err} +} + +func (n *NumberedError) Error() string { + return fmt.Sprintf("ERROR(%d): %v", n.errorCode, n.err.Error()) +} + +func (n *NumberedError) ExitCode() int { + return n.errorCode +} + +// compile-time conformance test +var ( + _ cli.ExitCoder = (*NumberedError)(nil) +) + +type input struct { + Alloc types.GenesisAlloc `json:"alloc,omitempty"` + Env *stEnv `json:"env,omitempty"` + Txs []*txWithKey `json:"txs,omitempty"` + TxRlp string `json:"txsRlp,omitempty"` +} + +func Transition(ctx *cli.Context) error { + var getTracer = func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) { return nil, 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) { // JSON opcode tracing + // Configure the EVM logger + logConfig := &logger.Config{ + DisableStack: ctx.Bool(TraceDisableStackFlag.Name), + EnableMemory: ctx.Bool(TraceEnableMemoryFlag.Name), + EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name), + Debug: true, + } + getTracer = func(txIndex int, txHash common.Hash) (*tracers.Tracer, io.WriteCloser, error) { + traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) + if err != nil { + return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) + } + var l *tracing.Hooks + if ctx.Bool(TraceEnableCallFramesFlag.Name) { + l = logger.NewJSONLoggerWithCallFrames(logConfig, traceFile) + } else { + l = logger.NewJSONLogger(logConfig, traceFile) + } + tracer := &tracers.Tracer{ + Hooks: l, + // jsonLogger streams out result to file. + GetResult: func() (json.RawMessage, error) { return nil, nil }, + Stop: func(err error) {}, + } + return tracer, traceFile, 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) (*tracers.Tracer, io.WriteCloser, error) { + traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String()))) + if err != nil { + return nil, 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, nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err)) + } + return tracer, traceFile, nil + } + } + // We need to load three things: alloc, env and transactions. May be either in + // stdin input or in files. + // Check if anything needs to be read from stdin + var ( + prestate Prestate + txIt txIterator // txs to apply + allocStr = ctx.String(InputAllocFlag.Name) + + envStr = ctx.String(InputEnvFlag.Name) + txStr = ctx.String(InputTxsFlag.Name) + inputData = &input{} + ) + // Figure out the prestate alloc + if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { + decoder := json.NewDecoder(os.Stdin) + if err := decoder.Decode(inputData); err != nil { + return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err)) + } + } + if allocStr != stdinSelector { + if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil { + return err + } + } + prestate.Pre = inputData.Alloc + + // Set the block environment + if envStr != stdinSelector { + var env stEnv + if err := readFile(envStr, "env", &env); err != nil { + return err + } + inputData.Env = &env + } + prestate.Env = *inputData.Env + + vmConfig := vm.Config{} + // Construct the chainconfig + var chainConfig *params.ChainConfig + if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { + return NewError(ErrorConfig, fmt.Errorf("failed constructing chain configuration: %v", err)) + } else { + chainConfig = cConf + vmConfig.ExtraEips = extraEips + } + // Set the chain id + chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) + + if txIt, err = loadTransactions(txStr, inputData, chainConfig); err != nil { + return err + } + if err := applyLondonChecks(&prestate.Env, chainConfig); err != nil { + return err + } + 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 execution result + collector := make(Alloc) + s.DumpToCollector(collector, nil) + return dispatchOutput(ctx, baseDir, result, collector, body) +} + +func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { + if !chainConfig.IsLondon(big.NewInt(int64(env.Number))) { + return nil + } + // Sanity check, to not `panic` in state_transition + if env.BaseFee != nil { + // Already set, base fee has precedent over parent base fee. + return nil + } + if env.ParentBaseFee == nil || env.Number == 0 { + return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'parentBaseFee' 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 env.Withdrawals == nil { + return NewError(ErrorConfig, errors.New("Shanghai config but missing 'withdrawals' in env section")) + } + return nil +} + +func applyMergeChecks(env *stEnv, chainConfig *params.ChainConfig) error { + isMerged := chainConfig.TerminalTotalDifficulty != nil && chainConfig.TerminalTotalDifficulty.BitLen() == 0 + if !isMerged { + // pre-merge: If difficulty was not provided by caller, we need to calculate it. + if env.Difficulty != nil { + // already set + return nil + } + switch { + case env.ParentDifficulty == nil: + return NewError(ErrorConfig, errors.New("currentDifficulty was not provided, and cannot be calculated due to missing parentDifficulty")) + case env.Number == 0: + return NewError(ErrorConfig, errors.New("currentDifficulty needs to be provided for block number 0")) + case env.Timestamp <= env.ParentTimestamp: + return NewError(ErrorConfig, fmt.Errorf("currentDifficulty cannot be calculated -- currentTime (%d) needs to be after parent time (%d)", + env.Timestamp, env.ParentTimestamp)) + } + env.Difficulty = calcDifficulty(chainConfig, env.Number, env.Timestamp, + env.ParentTimestamp, env.ParentDifficulty, env.ParentUncleHash) + return nil + } + // post-merge: + // - random must be supplied + // - difficulty must be zero + switch { + case env.Random == nil: + return NewError(ErrorConfig, errors.New("post-merge requires currentRandom to be defined in env")) + case env.Difficulty != nil && env.Difficulty.BitLen() != 0: + return NewError(ErrorConfig, errors.New("post-merge difficulty must be zero (or omitted) in env")) + } + env.Difficulty = nil + return nil +} + +func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error { + if !chainConfig.IsCancun(big.NewInt(int64(env.Number)), env.Timestamp) { + env.ParentBeaconBlockRoot = nil // un-set it if it has been set too early + return nil + } + // Post-cancun + // We require EIP-4788 beacon root to be set in the env + if env.ParentBeaconBlockRoot == nil { + return NewError(ErrorConfig, errors.New("post-cancun env requires parentBeaconBlockRoot to be set")) + } + return nil +} + +type Alloc map[common.Address]types.Account + +func (g Alloc) OnRoot(common.Hash) {} + +func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) { + if addr == nil { + return + } + balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0) + var storage map[common.Hash]common.Hash + if dumpAccount.Storage != nil { + storage = make(map[common.Hash]common.Hash, len(dumpAccount.Storage)) + for k, v := range dumpAccount.Storage { + storage[k] = common.HexToHash(v) + } + } + genesisAccount := types.Account{ + Code: dumpAccount.Code, + Storage: storage, + Balance: balance, + Nonce: dumpAccount.Nonce, + } + g[*addr] = genesisAccount +} + +// saveFile marshals the object to the given file +func saveFile(baseDir, filename string, data interface{}) error { + b, err := json.MarshalIndent(data, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + location := filepath.Join(baseDir, filename) + if err = os.WriteFile(location, b, 0644); err != nil { + return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) + } + log.Info("Wrote file", "file", location) + return nil +} + +// dispatchOutput writes the output data to either stderr or stdout, or to the specified +// files +func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error { + stdOutObject := make(map[string]interface{}) + stdErrObject := make(map[string]interface{}) + dispatch := func(baseDir, fName, name string, obj interface{}) error { + switch fName { + case "stdout": + stdOutObject[name] = obj + case "stderr": + stdErrObject[name] = obj + case "": + // don't save + default: // save to file + if err := saveFile(baseDir, fName, obj); err != nil { + return err + } + } + return nil + } + if err := dispatch(baseDir, ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil { + return err + } + if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil { + return err + } + if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil { + return err + } + if len(stdOutObject) > 0 { + b, err := json.MarshalIndent(stdOutObject, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + os.Stdout.Write(b) + os.Stdout.WriteString("\n") + } + if len(stdErrObject) > 0 { + b, err := json.MarshalIndent(stdErrObject, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + os.Stderr.Write(b) + os.Stderr.WriteString("\n") + } + return nil +} diff --git a/cmd/evm/internal/t8ntool/tx_iterator.go b/cmd/evm/internal/t8ntool/tx_iterator.go new file mode 100644 index 0000000..d4ebb4b --- /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, 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 unmarshalling 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/internal/t8ntool/utils.go b/cmd/evm/internal/t8ntool/utils.go new file mode 100644 index 0000000..42f5471 --- /dev/null +++ b/cmd/evm/internal/t8ntool/utils.go @@ -0,0 +1,54 @@ +// 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 t8ntool + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/urfave/cli/v2" +) + +// readFile reads the json-data in the provided path and marshals into dest. +func readFile(path, desc string, dest interface{}) error { + inFile, err := os.Open(path) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading %s file: %v", desc, err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + if err := decoder.Decode(dest); err != nil { + return NewError(ErrorJson, fmt.Errorf("failed unmarshalling %s file: %v", desc, err)) + } + return nil +} + +// createBasedir makes sure the basedir exists, if user specified one. +func createBasedir(ctx *cli.Context) (string, error) { + baseDir := "" + if ctx.IsSet(OutputBasedir.Name) { + if base := ctx.String(OutputBasedir.Name); len(base) > 0 { + err := os.MkdirAll(base, 0755) // //rw-r--r-- + if err != nil { + return "", err + } + baseDir = base + } + } + return baseDir, nil +} diff --git a/cmd/evm/main.go b/cmd/evm/main.go new file mode 100644 index 0000000..f9a2a07 --- /dev/null +++ b/cmd/evm/main.go @@ -0,0 +1,258 @@ +// Copyright 2014 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 . + +// evm executes EVM code snippets. +package main + +import ( + "fmt" + "math/big" + "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", + Category: flags.VMCategory, + } + StatDumpFlag = &cli.BoolFlag{ + Name: "statdump", + Usage: "displays stack and heap memory information", + Category: flags.VMCategory, + } + CodeFlag = &cli.StringFlag{ + 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 ", + Category: flags.VMCategory, + } + GasFlag = &cli.Uint64Flag{ + 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), + Category: flags.VMCategory, + } + ValueFlag = &flags.BigFlag{ + 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", + Category: flags.VMCategory, + } + InputFlag = &cli.StringFlag{ + Name: "input", + Usage: "input for the EVM", + Category: flags.VMCategory, + } + InputFileFlag = &cli.StringFlag{ + Name: "inputfile", + Usage: "file containing input for the EVM", + Category: flags.VMCategory, + } + BenchFlag = &cli.BoolFlag{ + Name: "bench", + Usage: "benchmark the execution", + Category: flags.VMCategory, + } + CreateFlag = &cli.BoolFlag{ + 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", + Category: flags.VMCategory, + } + MachineFlag = &cli.BoolFlag{ + Name: "json", + Usage: "output trace logs in machine readable format (json)", + Category: flags.VMCategory, + } + SenderFlag = &cli.StringFlag{ + Name: "sender", + Usage: "The transaction origin", + Category: flags.VMCategory, + } + ReceiverFlag = &cli.StringFlag{ + Name: "receiver", + Usage: "The transaction receiver (execution context)", + Category: flags.VMCategory, + } + DisableMemoryFlag = &cli.BoolFlag{ + Name: "nomemory", + Value: true, + Usage: "disable memory output", + Category: flags.VMCategory, + } + DisableStackFlag = &cli.BoolFlag{ + Name: "nostack", + Usage: "disable stack output", + Category: flags.VMCategory, + } + DisableStorageFlag = &cli.BoolFlag{ + Name: "nostorage", + Usage: "disable storage output", + Category: flags.VMCategory, + } + DisableReturnDataFlag = &cli.BoolFlag{ + 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", + Action: t8ntool.Transition, + Flags: []cli.Flag{ + t8ntool.TraceFlag, + t8ntool.TraceTracerFlag, + t8ntool.TraceTracerConfigFlag, + t8ntool.TraceEnableMemoryFlag, + t8ntool.TraceDisableStackFlag, + t8ntool.TraceEnableReturnDataFlag, + t8ntool.TraceEnableCallFramesFlag, + t8ntool.OutputBasedir, + t8ntool.OutputAllocFlag, + t8ntool.OutputResultFlag, + t8ntool.OutputBodyFlag, + t8ntool.InputAllocFlag, + t8ntool.InputEnvFlag, + t8ntool.InputTxsFlag, + t8ntool.ForknameFlag, + t8ntool.ChainIDFlag, + t8ntool.RewardFlag, + }, +} + +var transactionCommand = &cli.Command{ + Name: "transaction", + Aliases: []string{"t9n"}, + Usage: "Performs transaction validation", + Action: t8ntool.Transaction, + Flags: []cli.Flag{ + t8ntool.InputTxsFlag, + t8ntool.ChainIDFlag, + t8ntool.ForknameFlag, + }, +} + +var blockBuilderCommand = &cli.Command{ + Name: "block-builder", + Aliases: []string{"b11r"}, + Usage: "Builds a block", + Action: t8ntool.BuildBlock, + Flags: []cli.Flag{ + t8ntool.OutputBasedir, + t8ntool.OutputBlockFlag, + t8ntool.InputHeaderFlag, + t8ntool.InputOmmersFlag, + t8ntool.InputWithdrawalsFlag, + t8ntool.InputTxsRlpFlag, + t8ntool.SealCliqueFlag, + }, +} + +// 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 = flags.Merge(vmFlags, traceFlags, debug.Flags) + app.Commands = []*cli.Command{ + compileCommand, + disasmCommand, + runCommand, + blockTestCommand, + stateTestCommand, + stateTransitionCommand, + 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() { + if err := app.Run(os.Args); err != nil { + code := 1 + if ec, ok := err.(*t8ntool.NumberedError); ok { + code = ec.ExitCode() + } + fmt.Fprintln(os.Stderr, err) + os.Exit(code) + } +} diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go new file mode 100644 index 0000000..f179e73 --- /dev/null +++ b/cmd/evm/runner.go @@ -0,0 +1,312 @@ +// 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 . + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "math/big" + "os" + goruntime "runtime" + "testing" + "time" + + "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" + "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/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "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/params" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/urfave/cli/v2" +) + +var runCommand = &cli.Command{ + Action: runCmd, + Name: "run", + 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 +// the initialized Genesis structure +func readGenesis(genesisPath string) *core.Genesis { + // Make sure we have a valid genesis JSON + //genesisPath := ctx.Args().First() + if len(genesisPath) == 0 { + utils.Fatalf("Must supply path to genesis JSON file") + } + file, err := os.Open(genesisPath) + if err != nil { + utils.Fatalf("Failed to read genesis file: %v", err) + } + defer file.Close() + + genesis := new(core.Genesis) + if err := json.NewDecoder(file).Decode(genesis); err != nil { + utils.Fatalf("invalid genesis file: %v", err) + } + return genesis +} + +type execStats struct { + time time.Duration // The execution time. + allocs int64 // The number of heap allocations during execution. + bytesAllocated int64 // The cumulative number of bytes allocated during execution. +} + +func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) (output []byte, gasLeft uint64, stats execStats, err error) { + if bench { + result := testing.Benchmark(func(b *testing.B) { + for i := 0; i < b.N; i++ { + output, gasLeft, err = execFunc() + } + }) + + // Get the average execution time from the benchmarking result. + // There are other useful stats here that could be reported. + stats.time = time.Duration(result.NsPerOp()) + stats.allocs = result.AllocsPerOp() + stats.bytesAllocated = result.AllocedBytesPerOp() + } else { + var memStatsBefore, memStatsAfter goruntime.MemStats + goruntime.ReadMemStats(&memStatsBefore) + startTime := time.Now() + output, gasLeft, err = execFunc() + stats.time = time.Since(startTime) + goruntime.ReadMemStats(&memStatsAfter) + stats.allocs = int64(memStatsAfter.Mallocs - memStatsBefore.Mallocs) + stats.bytesAllocated = int64(memStatsAfter.TotalAlloc - memStatsBefore.TotalAlloc) + } + + return output, gasLeft, stats, err +} + +func runCmd(ctx *cli.Context) error { + logconfig := &logger.Config{ + EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), + DisableStack: ctx.Bool(DisableStackFlag.Name), + DisableStorage: ctx.Bool(DisableStorageFlag.Name), + EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), + Debug: ctx.Bool(DebugFlag.Name), + } + + var ( + tracer *tracing.Hooks + 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) + } else if ctx.Bool(DebugFlag.Name) { + debugLogger = logger.NewStructLogger(logconfig) + tracer = debugLogger.Hooks() + } else { + debugLogger = logger.NewStructLogger(logconfig) + } + + initialGas := ctx.Uint64(GasFlag.Name) + genesisConfig := new(core.Genesis) + genesisConfig.GasLimit = initialGas + if ctx.String(GenesisFlag.Name) != "" { + genesisConfig = readGenesis(ctx.String(GenesisFlag.Name)) + if genesisConfig.GasLimit != 0 { + initialGas = genesisConfig.GasLimit + } + } else { + genesisConfig.Config = params.AllDevChainProtocolChanges + } + + db := rawdb.NewMemoryDatabase() + triedb := triedb.NewDatabase(db, &triedb.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)) + } + statedb.CreateAccount(sender) + + if ctx.String(ReceiverFlag.Name) != "" { + receiver = common.HexToAddress(ctx.String(ReceiverFlag.Name)) + } + + var code []byte + codeFileFlag := ctx.String(CodeFileFlag.Name) + codeFlag := ctx.String(CodeFlag.Name) + + // The '--code' or '--codefile' flag overrides code in state + if codeFileFlag != "" || codeFlag != "" { + var hexcode []byte + if codeFileFlag != "" { + var err error + // If - is specified, it means that code comes from stdin + if codeFileFlag == "-" { + //Try reading from stdin + if hexcode, err = io.ReadAll(os.Stdin); err != nil { + fmt.Printf("Could not load code from stdin: %v\n", err) + os.Exit(1) + } + } else { + // Codefile with hex assembly + if hexcode, err = os.ReadFile(codeFileFlag); err != nil { + fmt.Printf("Could not load code from file: %v\n", err) + os.Exit(1) + } + } + } else { + hexcode = []byte(codeFlag) + } + hexcode = bytes.TrimSpace(hexcode) + if len(hexcode)%2 != 0 { + fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode)) + os.Exit(1) + } + code = common.FromHex(string(hexcode)) + } else if fn := ctx.Args().First(); len(fn) > 0 { + // EASM-file to compile + src, err := os.ReadFile(fn) + if err != nil { + return err + } + bin, err := compiler.Compile(fn, src, false) + if err != nil { + return err + } + code = common.Hex2Bytes(bin) + } + runtimeConfig := runtime.Config{ + Origin: sender, + State: statedb, + GasLimit: initialGas, + GasPrice: flags.GlobalBig(ctx, PriceFlag.Name), + Value: flags.GlobalBig(ctx, ValueFlag.Name), + Difficulty: genesisConfig.Difficulty, + Time: genesisConfig.Timestamp, + Coinbase: genesisConfig.Coinbase, + BlockNumber: new(big.Int).SetUint64(genesisConfig.Number), + BlobHashes: blobHashes, + BlobBaseFee: blobBaseFee, + EVMConfig: vm.Config{ + Tracer: tracer, + }, + } + + if chainConfig != nil { + runtimeConfig.ChainConfig = chainConfig + } else { + runtimeConfig.ChainConfig = params.AllEthashProtocolChanges + } + + var hexInput []byte + if inputFileFlag := ctx.String(InputFileFlag.Name); inputFileFlag != "" { + var err error + if hexInput, err = os.ReadFile(inputFileFlag); err != nil { + fmt.Printf("could not load input from file: %v\n", err) + os.Exit(1) + } + } else { + hexInput = []byte(ctx.String(InputFlag.Name)) + } + hexInput = bytes.TrimSpace(hexInput) + if len(hexInput)%2 != 0 { + fmt.Println("input length must be even") + os.Exit(1) + } + input := common.FromHex(string(hexInput)) + + var execFunc func() ([]byte, uint64, error) + if ctx.Bool(CreateFlag.Name) { + input = append(code, input...) + execFunc = func() ([]byte, uint64, error) { + output, _, gasLeft, err := runtime.Create(input, &runtimeConfig) + return output, gasLeft, err + } + } else { + if len(code) > 0 { + statedb.SetCode(receiver, code) + } + execFunc = func() ([]byte, uint64, error) { + return runtime.Call(receiver, input, &runtimeConfig) + } + } + + bench := ctx.Bool(BenchFlag.Name) + output, leftOverGas, stats, err := timedExec(bench, execFunc) + + if ctx.Bool(DumpFlag.Name) { + root, err := statedb.Commit(genesisConfig.Number, true) + if err != nil { + fmt.Printf("Failed to commit changes %v\n", err) + return err + } + dumpdb, err := state.New(root, sdb, nil) + if err != nil { + fmt.Printf("Failed to open statedb %v\n", err) + return err + } + fmt.Println(string(dumpdb.Dump(nil))) + } + + if ctx.Bool(DebugFlag.Name) { + if debugLogger != nil { + fmt.Fprintln(os.Stderr, "#### TRACE ####") + logger.WriteTrace(os.Stderr, debugLogger.StructLogs()) + } + fmt.Fprintln(os.Stderr, "#### LOGS ####") + logger.WriteLogs(os.Stderr, statedb.Logs()) + } + + if bench || ctx.Bool(StatDumpFlag.Name) { + fmt.Fprintf(os.Stderr, `EVM gas used: %d +execution time: %v +allocations: %d +allocated bytes: %d +`, initialGas-leftOverGas, stats.time, stats.allocs, stats.bytesAllocated) + } + if tracer == nil { + fmt.Printf("%#x\n", output) + if err != nil { + fmt.Printf(" error: %v\n", err) + } + } + + return nil +} diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go new file mode 100644 index 0000000..fc2bf82 --- /dev/null +++ b/cmd/evm/staterunner.go @@ -0,0 +1,126 @@ +// 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 . + +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "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/vm" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/ethereum/go-ethereum/tests" + "github.com/urfave/cli/v2" +) + +var stateTestCommand = &cli.Command{ + Action: stateTestCmd, + Name: "statetest", + Usage: "Executes the given state tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).", + ArgsUsage: "", +} + +// StatetestResult contains the execution status after running a state test, any +// error that might have occurred and a dump of the final state if requested. +type StatetestResult struct { + Name string `json:"name"` + Pass bool `json:"pass"` + Root *common.Hash `json:"stateRoot,omitempty"` + Fork string `json:"fork"` + Error string `json:"error,omitempty"` + State *state.Dump `json:"state,omitempty"` +} + +func stateTestCmd(ctx *cli.Context) error { + // Configure the EVM logger + config := &logger.Config{ + EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), + DisableStack: ctx.Bool(DisableStackFlag.Name), + DisableStorage: ctx.Bool(DisableStorageFlag.Name), + EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), + } + var cfg vm.Config + switch { + case ctx.Bool(MachineFlag.Name): + cfg.Tracer = logger.NewJSONLogger(config, os.Stderr) + + case ctx.Bool(DebugFlag.Name): + cfg.Tracer = logger.NewStructLogger(config).Hooks() + } + // Load the test content from the input file + if len(ctx.Args().First()) != 0 { + return runStateTest(ctx.Args().First(), cfg, ctx.Bool(DumpFlag.Name)) + } + // Read filenames from stdin and execute back-to-back + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + fname := scanner.Text() + if len(fname) == 0 { + return nil + } + if err := runStateTest(fname, cfg, ctx.Bool(DumpFlag.Name)); err != nil { + return err + } + } + return nil +} + +// runStateTest loads the state-test given by fname, and executes the test. +func runStateTest(fname string, cfg vm.Config, dump bool) error { + src, err := os.ReadFile(fname) + if err != nil { + return err + } + var testsByName map[string]tests.StateTest + if err := json.Unmarshal(src, &testsByName); err != nil { + return err + } + + // Iterate over all the tests, run them and aggregate the results + results := make([]StatetestResult, 0, len(testsByName)) + for key, test := range testsByName { + for _, st := range test.Subtests() { + // Run the test and aggregate the result + result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} + test.Run(st, cfg, false, rawdb.HashScheme, func(err error, tstate *tests.StateTestState) { + var root common.Hash + if tstate.StateDB != nil { + root = tstate.StateDB.IntermediateRoot(false) + result.Root = &root + fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root) + if dump { // Dump any state to aid debugging + cpy, _ := state.New(root, tstate.StateDB.Database(), nil) + dump := cpy.RawDump(nil) + result.State = &dump + } + } + if err != nil { + // Test failed, mark as so + result.Pass, result.Error = false, err.Error() + } + }) + results = append(results, *result) + } + } + out, _ := json.MarshalIndent(results, "", " ") + fmt.Println(string(out)) + return nil +} diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go new file mode 100644 index 0000000..76ebc42 --- /dev/null +++ b/cmd/evm/t8n_test.go @@ -0,0 +1,685 @@ +// 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 ( + "bufio" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "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) { + // Run the app if we've been exec'd as "ethkey-test" in runEthkey. + reexec.Register("evm-test", func() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + }) + // check if we have been reexec'd + if reexec.Init() { + return + } + os.Exit(m.Run()) +} + +type testT8n struct { + *cmdtest.TestCmd +} + +type t8nInput struct { + inAlloc string + inTxs string + inEnv string + stFork string + stReward string +} + +func (args *t8nInput) get(base string) []string { + var out []string + if opt := args.inAlloc; opt != "" { + out = append(out, "--input.alloc") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.inTxs; opt != "" { + out = append(out, "--input.txs") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.inEnv; opt != "" { + out = append(out, "--input.env") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.stFork; opt != "" { + out = append(out, "--state.fork", opt) + } + if opt := args.stReward; opt != "" { + out = append(out, "--state.reward", opt) + } + return out +} + +type t8nOutput struct { + alloc bool + result bool + body bool +} + +func (args *t8nOutput) get() (out []string) { + if args.body { + out = append(out, "--output.body", "stdout") + } else { + out = append(out, "--output.body", "") // empty means ignore + } + if args.result { + out = append(out, "--output.result", "stdout") + } else { + out = append(out, "--output.result", "") + } + if args.alloc { + out = append(out, "--output.alloc", "stdout") + } else { + out = append(out, "--output.alloc", "") + } + return out +} + +func TestT8n(t *testing.T) { + t.Parallel() + tt := new(testT8n) + tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, tc := range []struct { + base string + input t8nInput + output t8nOutput + expExitCode int + expOut string + }{ + { // Test exit (3) on bad config + base: "./testdata/1", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Frontier+1346", "", + }, + output: t8nOutput{alloc: true, result: true}, + expExitCode: 3, + }, + { + base: "./testdata/1", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Byzantium", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // blockhash test + base: "./testdata/3", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Berlin", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // missing blockhash test + base: "./testdata/4", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Berlin", "", + }, + output: t8nOutput{alloc: true, result: true}, + expExitCode: 4, + }, + { // Uncle test + base: "./testdata/5", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Byzantium", "0x80", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // Sign json transactions + base: "./testdata/13", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "London", "", + }, + output: t8nOutput{body: true}, + expOut: "exp.json", + }, + { // Already signed transactions + base: "./testdata/13", + input: t8nInput{ + "alloc.json", "signed_txs.rlp", "env.json", "London", "", + }, + output: t8nOutput{result: true}, + expOut: "exp2.json", + }, + { // Difficulty calculation - no uncles + base: "./testdata/14", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "London", "", + }, + output: t8nOutput{result: true}, + expOut: "exp.json", + }, + { // Difficulty calculation - with uncles + base: "./testdata/14", + input: t8nInput{ + "alloc.json", "txs.json", "env.uncles.json", "London", "", + }, + output: t8nOutput{result: true}, + expOut: "exp2.json", + }, + { // Difficulty calculation - with ommers + Berlin + base: "./testdata/14", + input: t8nInput{ + "alloc.json", "txs.json", "env.uncles.json", "Berlin", "", + }, + output: t8nOutput{result: true}, + expOut: "exp_berlin.json", + }, + { // Difficulty calculation on arrow glacier + base: "./testdata/19", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "London", "", + }, + output: t8nOutput{result: true}, + expOut: "exp_london.json", + }, + { // Difficulty calculation on arrow glacier + base: "./testdata/19", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "ArrowGlacier", "", + }, + output: t8nOutput{result: true}, + expOut: "exp_arrowglacier.json", + }, + { // Difficulty calculation on gray glacier + base: "./testdata/19", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "GrayGlacier", "", + }, + output: t8nOutput{result: true}, + expOut: "exp_grayglacier.json", + }, + { // Sign unprotected (pre-EIP155) transaction + base: "./testdata/23", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Berlin", "", + }, + output: t8nOutput{result: true}, + expOut: "exp.json", + }, + { // Test post-merge transition + base: "./testdata/24", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Paris", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // Test post-merge transition where input is missing random + base: "./testdata/24", + input: t8nInput{ + "alloc.json", "txs.json", "env-missingrandom.json", "Paris", "", + }, + output: t8nOutput{alloc: false, result: false}, + expExitCode: 3, + }, + { // Test base fee calculation + base: "./testdata/25", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Paris", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // Test withdrawals transition + base: "./testdata/26", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Shanghai", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // Cancun tests + base: "./testdata/28", + input: t8nInput{ + "alloc.json", "txs.rlp", "env.json", "Cancun", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // More cancun tests + base: "./testdata/29", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Cancun", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // 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()...) + args = append(args, tc.input.get(tc.base)...) + var qArgs []string // quoted args for debugging purposes + for _, arg := range args { + if len(arg) == 0 { + qArgs = append(qArgs, `""`) + } else { + qArgs = append(qArgs, arg) + } + } + tt.Logf("args: %v\n", strings.Join(qArgs, " ")) + tt.Run("evm-test", args...) + // Compare the expected output, if provided + if tc.expOut != "" { + file := fmt.Sprintf("%v/%v", tc.base, tc.expOut) + want, err := os.ReadFile(file) + if err != nil { + t.Fatalf("test %d: could not read expected output: %v", i, err) + } + have := tt.Output() + ok, err := cmpJson(have, want) + switch { + case err != nil: + t.Fatalf("test %d, file %v: json parsing failed: %v", i, file, err) + case !ok: + t.Fatalf("test %d, file %v: output wrong, have \n%v\nwant\n%v\n", i, file, string(have), string(want)) + } + } + tt.WaitExit() + if have, want := tt.ExitStatus(), tc.expExitCode; have != want { + t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) + } + } +} + +func lineIterator(path string) func() (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return func() (string, error) { return err.Error(), err } + } + scanner := bufio.NewScanner(strings.NewReader(string(data))) + return func() (string, error) { + if scanner.Scan() { + return scanner.Text(), nil + } + if err := scanner.Err(); err != nil { + return "", err + } + return "", io.EOF // scanner gobbles io.EOF, but we want it + } +} + +// TestT8nTracing is a test that checks the tracing-output from t8n. +func TestT8nTracing(t *testing.T) { + t.Parallel() + tt := new(testT8n) + tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, tc := range []struct { + base string + input t8nInput + expExitCode int + extraArgs []string + expectedTraces []string + }{ + { + base: "./testdata/31", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Cancun", "", + }, + extraArgs: []string{"--trace"}, + expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl"}, + }, + { + base: "./testdata/31", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Cancun", "", + }, + extraArgs: []string{"--trace.tracer", ` +{ + result: function(){ + return "hello world" + }, + fault: function(){} +}`}, + expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json"}, + }, + { + base: "./testdata/32", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Paris", "", + }, + extraArgs: []string{"--trace", "--trace.callframes"}, + expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"}, + }, + } { + args := []string{"t8n"} + args = append(args, tc.input.get(tc.base)...) + // Place the output somewhere we can find it + outdir := t.TempDir() + args = append(args, "--output.basedir", outdir) + args = append(args, tc.extraArgs...) + + var qArgs []string // quoted args for debugging purposes + for _, arg := range args { + if len(arg) == 0 { + qArgs = append(qArgs, `""`) + } else { + qArgs = append(qArgs, arg) + } + } + tt.Logf("args: %v\n", strings.Join(qArgs, " ")) + tt.Run("evm-test", args...) + t.Log(string(tt.Output())) + + // Compare the expected traces + for _, traceFile := range tc.expectedTraces { + haveFn := lineIterator(filepath.Join(outdir, traceFile)) + wantFn := lineIterator(filepath.Join(tc.base, traceFile)) + + for line := 0; ; line++ { + want, wErr := wantFn() + have, hErr := haveFn() + if want != have { + t.Fatalf("test %d, trace %v, line %d\nwant: %v\nhave: %v\n", + i, traceFile, line, want, have) + } + if wErr != nil && hErr != nil { + break + } + if wErr != nil { + t.Fatal(wErr) + } + if hErr != nil { + t.Fatal(hErr) + } + t.Logf("%v\n", want) + } + } + if have, want := tt.ExitStatus(), tc.expExitCode; have != want { + t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) + } + } +} + +type t9nInput struct { + inTxs string + stFork string +} + +func (args *t9nInput) get(base string) []string { + var out []string + if opt := args.inTxs; opt != "" { + out = append(out, "--input.txs") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.stFork; opt != "" { + out = append(out, "--state.fork", opt) + } + return out +} + +func TestT9n(t *testing.T) { + t.Parallel() + tt := new(testT8n) + tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, tc := range []struct { + base string + input t9nInput + expExitCode int + expOut string + }{ + { // London txs on homestead + base: "./testdata/15", + input: t9nInput{ + inTxs: "signed_txs.rlp", + stFork: "Homestead", + }, + expOut: "exp.json", + }, + { // London txs on London + base: "./testdata/15", + input: t9nInput{ + inTxs: "signed_txs.rlp", + stFork: "London", + }, + expOut: "exp2.json", + }, + { // An RLP list (a blockheader really) + base: "./testdata/15", + input: t9nInput{ + inTxs: "blockheader.rlp", + stFork: "London", + }, + expOut: "exp3.json", + }, + { // Transactions with too low gas + base: "./testdata/16", + input: t9nInput{ + inTxs: "signed_txs.rlp", + stFork: "London", + }, + expOut: "exp.json", + }, + { // Transactions with value exceeding 256 bits + base: "./testdata/17", + input: t9nInput{ + inTxs: "signed_txs.rlp", + stFork: "London", + }, + expOut: "exp.json", + }, + { // Invalid RLP + base: "./testdata/18", + input: t9nInput{ + inTxs: "invalid.rlp", + stFork: "London", + }, + expExitCode: t8ntool.ErrorIO, + }, + } { + args := []string{"t9n"} + args = append(args, tc.input.get(tc.base)...) + + tt.Run("evm-test", args...) + tt.Logf("args:\n go run . %v\n", strings.Join(args, " ")) + // Compare the expected output, if provided + if tc.expOut != "" { + want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut)) + if err != nil { + t.Fatalf("test %d: could not read expected output: %v", i, err) + } + have := tt.Output() + ok, err := cmpJson(have, want) + switch { + case err != nil: + t.Logf(string(have)) + t.Fatalf("test %d, json parsing failed: %v", i, err) + case !ok: + t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) + } + } + tt.WaitExit() + if have, want := tt.ExitStatus(), tc.expExitCode; have != want { + t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) + } + } +} + +type b11rInput struct { + inEnv string + inOmmersRlp string + inWithdrawals string + inTxsRlp string + inClique string + ethash bool + ethashMode string + ethashDir string +} + +func (args *b11rInput) get(base string) []string { + var out []string + if opt := args.inEnv; opt != "" { + out = append(out, "--input.header") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.inOmmersRlp; opt != "" { + out = append(out, "--input.ommers") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.inWithdrawals; opt != "" { + out = append(out, "--input.withdrawals") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.inTxsRlp; opt != "" { + out = append(out, "--input.txs") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.inClique; opt != "" { + out = append(out, "--seal.clique") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if args.ethash { + out = append(out, "--seal.ethash") + } + if opt := args.ethashMode; opt != "" { + out = append(out, "--seal.ethash.mode") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + if opt := args.ethashDir; opt != "" { + out = append(out, "--seal.ethash.dir") + out = append(out, fmt.Sprintf("%v/%v", base, opt)) + } + out = append(out, "--output.block") + out = append(out, "stdout") + return out +} + +func TestB11r(t *testing.T) { + t.Parallel() + tt := new(testT8n) + tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, tc := range []struct { + base string + input b11rInput + expExitCode int + expOut string + }{ + { // unsealed block + base: "./testdata/20", + input: b11rInput{ + inEnv: "header.json", + inOmmersRlp: "ommers.json", + inTxsRlp: "txs.rlp", + }, + expOut: "exp.json", + }, + { // ethash test seal + base: "./testdata/21", + input: b11rInput{ + inEnv: "header.json", + inOmmersRlp: "ommers.json", + inTxsRlp: "txs.rlp", + }, + expOut: "exp.json", + }, + { // clique test seal + base: "./testdata/21", + input: b11rInput{ + inEnv: "header.json", + inOmmersRlp: "ommers.json", + inTxsRlp: "txs.rlp", + inClique: "clique.json", + }, + expOut: "exp-clique.json", + }, + { // block with ommers + base: "./testdata/22", + input: b11rInput{ + inEnv: "header.json", + inOmmersRlp: "ommers.json", + inTxsRlp: "txs.rlp", + }, + expOut: "exp.json", + }, + { // block with withdrawals + base: "./testdata/27", + input: b11rInput{ + inEnv: "header.json", + inOmmersRlp: "ommers.json", + inWithdrawals: "withdrawals.json", + inTxsRlp: "txs.rlp", + }, + expOut: "exp.json", + }, + } { + args := []string{"b11r"} + args = append(args, tc.input.get(tc.base)...) + + tt.Run("evm-test", args...) + tt.Logf("args:\n go run . %v\n", strings.Join(args, " ")) + // Compare the expected output, if provided + if tc.expOut != "" { + want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut)) + if err != nil { + t.Fatalf("test %d: could not read expected output: %v", i, err) + } + have := tt.Output() + ok, err := cmpJson(have, want) + switch { + case err != nil: + t.Logf(string(have)) + t.Fatalf("test %d, json parsing failed: %v", i, err) + case !ok: + t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) + } + } + tt.WaitExit() + if have, want := tt.ExitStatus(), tc.expExitCode; have != want { + t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) + } + } +} + +// cmpJson compares the JSON in two byte slices. +func cmpJson(a, b []byte) (bool, error) { + var j, j2 interface{} + if err := json.Unmarshal(a, &j); err != nil { + return false, err + } + if err := json.Unmarshal(b, &j2); err != nil { + return false, err + } + return reflect.DeepEqual(j2, j), nil +} diff --git a/cmd/evm/testdata/1/alloc.json b/cmd/evm/testdata/1/alloc.json new file mode 100644 index 0000000..cef1a25 --- /dev/null +++ b/cmd/evm/testdata/1/alloc.json @@ -0,0 +1,12 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/1/env.json b/cmd/evm/testdata/1/env.json new file mode 100644 index 0000000..dd60abd --- /dev/null +++ b/cmd/evm/testdata/1/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000" +} \ No newline at end of file diff --git a/cmd/evm/testdata/1/exp.json b/cmd/evm/testdata/1/exp.json new file mode 100644 index 0000000..d1351e5 --- /dev/null +++ b/cmd/evm/testdata/1/exp.json @@ -0,0 +1,45 @@ +{ + "alloc": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeed1a9d", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xa410" + } + }, + "result": { + "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", + "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "rejected": [ + { + "index": 1, + "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x5208" + } +} diff --git a/cmd/evm/testdata/1/txs.json b/cmd/evm/testdata/1/txs.json new file mode 100644 index 0000000..50b31ff --- /dev/null +++ b/cmd/evm/testdata/1/txs.json @@ -0,0 +1,26 @@ +[ + { + "gas": "0x5208", + "gasPrice": "0x2", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0x0", + "r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", + "s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "v": "0x1b", + "value": "0x1" + }, + { + "gas": "0x5208", + "gasPrice": "0x2", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0x0", + "r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", + "s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "v": "0x1b", + "value": "0x1" + } +] diff --git a/cmd/evm/testdata/10/alloc.json b/cmd/evm/testdata/10/alloc.json new file mode 100644 index 0000000..6e98e75 --- /dev/null +++ b/cmd/evm/testdata/10/alloc.json @@ -0,0 +1,23 @@ +{ + "0x1111111111111111111111111111111111111111" : { + "balance" : "0x010000000000", + "code" : "0xfe", + "nonce" : "0x01", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x010000000000", + "code" : "0x", + "nonce" : "0x01", + "storage" : { + } + }, + "0xd02d72e067e77158444ef2020ff2d325f929b363" : { + "balance" : "0x01000000000000", + "code" : "0x", + "nonce" : "0x01", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/10/env.json b/cmd/evm/testdata/10/env.json new file mode 100644 index 0000000..3a82d46 --- /dev/null +++ b/cmd/evm/testdata/10/env.json @@ -0,0 +1,12 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentNumber" : "0x01", + "currentTimestamp" : "0x079e", + "previousHash" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f", + "currentGasLimit" : "0x40000000", + "currentBaseFee" : "0x036b", + "blockHashes" : { + "0" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/10/readme.md b/cmd/evm/testdata/10/readme.md new file mode 100644 index 0000000..afa3787 --- /dev/null +++ b/cmd/evm/testdata/10/readme.md @@ -0,0 +1,85 @@ +## EIP-1559 testing + +This test contains testcases for EIP-1559, which were reported by Ori as misbehaving. + +``` +[user@work evm]$ dir=./testdata/10 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout --output.result=stdout 2>&1 +INFO [05-09|22:11:59.436] rejected tx index=3 hash=db07bf..ede1e8 from=0xd02d72E067e77158444ef2020Ff2d325f929B363 error="gas limit reached" +``` +Output: +```json +{ + "alloc": { + "0x1111111111111111111111111111111111111111": { + "code": "0xfe", + "balance": "0x10000000000", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x10000000000", + "nonce": "0x1" + }, + "0xd02d72e067e77158444ef2020ff2d325f929b363": { + "balance": "0xff5beffffc95", + "nonce": "0x4" + } + }, + "result": { + "stateRoot": "0xf91a7ec08e4bfea88719aab34deabb000c86902360532b52afa9599d41f2bb8b", + "txRoot": "0xda925f2306a52fa24c15d5cd212d736ee016415fd8dd0c45fd368de7917d64bb", + "receiptsRoot": "0x439a25f7fc424c10fb1f89800e4aa1df74156b137239d9ac3eaa7c911c353cd5", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "type": "0x2", + "root": "0x", + "status": "0x0", + "cumulativeGasUsed": "0x10000001", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x88980f6efcc5358d9c359663e7b9414722d430497637340ea056b076bc206701", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x10000001", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + }, + { + "type": "0x2", + "root": "0x", + "status": "0x0", + "cumulativeGasUsed": "0x20000001", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0xd7bf3886f4e2aef74d525ae072c680f3846f550254401b67cbfda4a233757582", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x10000000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x1" + }, + { + "type": "0x2", + "root": "0x", + "status": "0x0", + "cumulativeGasUsed": "0x30000001", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x50308296760f01f1eeec7500e9e73cad67469249b1f59e9a9f55e6625a4923db", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x10000000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x2" + } + ], + "rejected": [ + { + "index": 3, + "error": "gas limit reached" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x30000001", + "currentBaseFee": "0x36b" + } +} +``` diff --git a/cmd/evm/testdata/10/txs.json b/cmd/evm/testdata/10/txs.json new file mode 100644 index 0000000..f7c9baa --- /dev/null +++ b/cmd/evm/testdata/10/txs.json @@ -0,0 +1,70 @@ +[ + { + "input" : "0x", + "gas" : "0x10000001", + "nonce" : "0x1", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x7a45f00bcde9036b026cdf1628b023cd8a31a95c62b5e4dbbee2fa7debe668fb", + "s" : "0x3cc9d6f2cd00a045b0263f2d6dad7d60938d5d13d061af4969f95928aa934d4a", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x0", + "accessList" : [ + ] + }, + { + "input" : "0x", + "gas" : "0x10000000", + "nonce" : "0x2", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x4c564b94b0281a8210eeec2dd1fe2e16ff1c1903a8c3a1078d735d7f8208b2af", + "s" : "0x56432b2593e6de95db1cb997b7385217aca03f1615327e231734446b39f266d", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x0", + "accessList" : [ + ] + }, + { + "input" : "0x", + "gas" : "0x10000000", + "nonce" : "0x3", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x2ed2ef52f924f59d4a21e1f2a50d3b1109303ce5e32334a7ece9b46f4fbc2a57", + "s" : "0x2980257129cbd3da987226f323d50ba3975a834d165e0681f991b75615605c44", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x0", + "accessList" : [ + ] + }, + { + "input" : "0x", + "gas" : "0x10000000", + "nonce" : "0x4", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x5df7d7f8f8e15b36fc9f189cacb625040fad10398d08fc90812595922a2c49b2", + "s" : "0x565fc1803f77a84d754ffe3c5363ab54a8d93a06ea1bb9d4c73c73a282b35917", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x0", + "accessList" : [ + ] + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/11/alloc.json b/cmd/evm/testdata/11/alloc.json new file mode 100644 index 0000000..8693823 --- /dev/null +++ b/cmd/evm/testdata/11/alloc.json @@ -0,0 +1,25 @@ +{ + "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x61ffff5060046000f3", + "nonce" : "0x01", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + "0x00" : "0x00" + } + }, + "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x00", + "code" : "0x6001600055", + "nonce" : "0x00", + "storage" : { + } + } +} + diff --git a/cmd/evm/testdata/11/env.json b/cmd/evm/testdata/11/env.json new file mode 100644 index 0000000..37dedf0 --- /dev/null +++ b/cmd/evm/testdata/11/env.json @@ -0,0 +1,12 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentNumber" : "0x01", + "currentTimestamp" : "0x03e8", + "previousHash" : "0xfda4419b3660e99f37e536dae1ab081c180136bb38c837a93e93d9aab58553b2", + "currentGasLimit" : "0x0f4240", + "blockHashes" : { + "0" : "0xfda4419b3660e99f37e536dae1ab081c180136bb38c837a93e93d9aab58553b2" + } +} + diff --git a/cmd/evm/testdata/11/readme.md b/cmd/evm/testdata/11/readme.md new file mode 100644 index 0000000..d499f8e --- /dev/null +++ b/cmd/evm/testdata/11/readme.md @@ -0,0 +1,13 @@ +## Test missing basefee + +In this test, the `currentBaseFee` is missing from the env portion. +On a live blockchain, the basefee is present in the header, and verified as part of header validation. + +In `evm t8n`, we don't have blocks, so it needs to be added in the `env`instead. + +When it's missing, an error is expected. + +``` +dir=./testdata/11 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout --output.result=stdout 2>&1>/dev/null +ERROR(3): EIP-1559 config but missing 'currentBaseFee' in env section +``` \ No newline at end of file diff --git a/cmd/evm/testdata/11/txs.json b/cmd/evm/testdata/11/txs.json new file mode 100644 index 0000000..c54b0a1 --- /dev/null +++ b/cmd/evm/testdata/11/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x38600060013960015160005560006000f3", + "gas" : "0x61a80", + "gasPrice" : "0x1", + "nonce" : "0x0", + "value" : "0x186a0", + "v" : "0x1c", + "r" : "0x2e1391fd903387f1cc2b51df083805fb4bbb0d4710a2cdf4a044d191ff7be63e", + "s" : "0x7f10a933c42ab74927db02b1db009e923d9d2ab24ac24d63c399f2fe5d9c9b22", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] + diff --git a/cmd/evm/testdata/12/alloc.json b/cmd/evm/testdata/12/alloc.json new file mode 100644 index 0000000..3ed9689 --- /dev/null +++ b/cmd/evm/testdata/12/alloc.json @@ -0,0 +1,11 @@ +{ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "84000000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + "0x00" : "0x00" + } + } +} + diff --git a/cmd/evm/testdata/12/env.json b/cmd/evm/testdata/12/env.json new file mode 100644 index 0000000..8ae5465 --- /dev/null +++ b/cmd/evm/testdata/12/env.json @@ -0,0 +1,10 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentNumber" : "0x01", + "currentTimestamp" : "0x03e8", + "previousHash" : "0xfda4419b3660e99f37e536dae1ab081c180136bb38c837a93e93d9aab58553b2", + "currentGasLimit" : "0x0f4240", + "currentBaseFee" : "0x20" +} + diff --git a/cmd/evm/testdata/12/readme.md b/cmd/evm/testdata/12/readme.md new file mode 100644 index 0000000..e3195be --- /dev/null +++ b/cmd/evm/testdata/12/readme.md @@ -0,0 +1,43 @@ +## Test 1559 balance + gasCap + +This test contains an EIP-1559 consensus issue which happened on Ropsten, where +`geth` did not properly account for the value transfer while doing the check on `max_fee_per_gas * gas_limit`. + +Before the issue was fixed, this invocation allowed the transaction to pass into a block: +``` +dir=./testdata/12 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout --output.result=stdout +``` + +With the fix applied, the result is: +``` +dir=./testdata/12 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout --output.result=stdout +INFO [03-09|10:43:12.649] rejected tx index=0 hash=ccc996..d83435 from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="insufficient funds for gas * price + value: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B have 84000000 want 84000032" +INFO [03-09|10:43:12.650] Trie dumping started root=e05f81..6597a5 +INFO [03-09|10:43:12.650] Trie dumping complete accounts=1 elapsed="46.393µs" +{ + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x501bd00" + } + }, + "result": { + "stateRoot": "0xe05f81f8244a76503ceec6f88abfcd03047a612a1001217f37d30984536597a5", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "rejected": [ + { + "index": 0, + "error": "insufficient funds for gas * price + value: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B have 84000000 want 84000032" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x0", + "currentBaseFee": "0x20" + } +} +``` + +The transaction is rejected. \ No newline at end of file diff --git a/cmd/evm/testdata/12/txs.json b/cmd/evm/testdata/12/txs.json new file mode 100644 index 0000000..cd683f2 --- /dev/null +++ b/cmd/evm/testdata/12/txs.json @@ -0,0 +1,20 @@ +[ + { + "input" : "0x", + "gas" : "0x5208", + "nonce" : "0x0", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x20", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "chainId" : "0x1", + "type" : "0x2", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x20", + "accessList" : [ + ] + } +] + diff --git a/cmd/evm/testdata/13/alloc.json b/cmd/evm/testdata/13/alloc.json new file mode 100644 index 0000000..6e98e75 --- /dev/null +++ b/cmd/evm/testdata/13/alloc.json @@ -0,0 +1,23 @@ +{ + "0x1111111111111111111111111111111111111111" : { + "balance" : "0x010000000000", + "code" : "0xfe", + "nonce" : "0x01", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x010000000000", + "code" : "0x", + "nonce" : "0x01", + "storage" : { + } + }, + "0xd02d72e067e77158444ef2020ff2d325f929b363" : { + "balance" : "0x01000000000000", + "code" : "0x", + "nonce" : "0x01", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/13/env.json b/cmd/evm/testdata/13/env.json new file mode 100644 index 0000000..3a82d46 --- /dev/null +++ b/cmd/evm/testdata/13/env.json @@ -0,0 +1,12 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentNumber" : "0x01", + "currentTimestamp" : "0x079e", + "previousHash" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f", + "currentGasLimit" : "0x40000000", + "currentBaseFee" : "0x036b", + "blockHashes" : { + "0" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/13/exp.json b/cmd/evm/testdata/13/exp.json new file mode 100644 index 0000000..2b049df --- /dev/null +++ b/cmd/evm/testdata/13/exp.json @@ -0,0 +1,3 @@ +{ + "body": "0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" +} diff --git a/cmd/evm/testdata/13/exp2.json b/cmd/evm/testdata/13/exp2.json new file mode 100644 index 0000000..babce35 --- /dev/null +++ b/cmd/evm/testdata/13/exp2.json @@ -0,0 +1,42 @@ +{ + "result": { + "stateRoot": "0xe4b924a6adb5959fccf769d5b7bb2f6359e26d1e76a2443c5a91a36d826aef61", + "txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", + "receiptsRoot": "0xa532a08aa9f62431d6fe5d924951b8efb86ed3c54d06fee77788c3767dd13420", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "type": "0x2", + "root": "0x", + "status": "0x0", + "cumulativeGasUsed": "0x84d0", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x84d0", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + }, + { + "type": "0x2", + "root": "0x", + "status": "0x0", + "cumulativeGasUsed": "0x109a0", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x84d0", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x1" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x109a0", + "currentBaseFee": "0x36b" + } +} diff --git a/cmd/evm/testdata/13/readme.md b/cmd/evm/testdata/13/readme.md new file mode 100644 index 0000000..889975d --- /dev/null +++ b/cmd/evm/testdata/13/readme.md @@ -0,0 +1,4 @@ +## Input transactions in RLP form + +This testdata folder is used to exemplify how transaction input can be provided in rlp form. +Please see the README in `evm` folder for how this is performed. \ No newline at end of file diff --git a/cmd/evm/testdata/13/signed_txs.rlp b/cmd/evm/testdata/13/signed_txs.rlp new file mode 100644 index 0000000..9d1157e --- /dev/null +++ b/cmd/evm/testdata/13/signed_txs.rlp @@ -0,0 +1 @@ +"0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" \ No newline at end of file diff --git a/cmd/evm/testdata/13/txs.json b/cmd/evm/testdata/13/txs.json new file mode 100644 index 0000000..c45ef1e --- /dev/null +++ b/cmd/evm/testdata/13/txs.json @@ -0,0 +1,34 @@ +[ + { + "input" : "0x", + "gas" : "0x84d0", + "nonce" : "0x1", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x0", + "accessList" : [] + }, + { + "input" : "0x", + "gas" : "0x84d0", + "nonce" : "0x2", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x0", + "accessList" : [] + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/14/alloc.json b/cmd/evm/testdata/14/alloc.json new file mode 100644 index 0000000..cef1a25 --- /dev/null +++ b/cmd/evm/testdata/14/alloc.json @@ -0,0 +1,12 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/14/env.json b/cmd/evm/testdata/14/env.json new file mode 100644 index 0000000..0bf1c5c --- /dev/null +++ b/cmd/evm/testdata/14/env.json @@ -0,0 +1,9 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentGasLimit": "0x750a163df65e8a", + "currentBaseFee": "0x500", + "currentNumber": "12800000", + "currentTimestamp": "100015", + "parentTimestamp" : "99999", + "parentDifficulty" : "0x2000000000000" +} diff --git a/cmd/evm/testdata/14/env.uncles.json b/cmd/evm/testdata/14/env.uncles.json new file mode 100644 index 0000000..83811b9 --- /dev/null +++ b/cmd/evm/testdata/14/env.uncles.json @@ -0,0 +1,10 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentGasLimit": "0x750a163df65e8a", + "currentBaseFee": "0x500", + "currentNumber": "12800000", + "currentTimestamp": "100035", + "parentTimestamp" : "99999", + "parentDifficulty" : "0x2000000000000", + "parentUncleHash" : "0x000000000000000000000000000000000000000000000000000000000000beef" +} diff --git a/cmd/evm/testdata/14/exp.json b/cmd/evm/testdata/14/exp.json new file mode 100644 index 0000000..26d4917 --- /dev/null +++ b/cmd/evm/testdata/14/exp.json @@ -0,0 +1,13 @@ +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "currentDifficulty": "0x2000020000000", + "receipts": [], + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} diff --git a/cmd/evm/testdata/14/exp2.json b/cmd/evm/testdata/14/exp2.json new file mode 100644 index 0000000..cd75b47 --- /dev/null +++ b/cmd/evm/testdata/14/exp2.json @@ -0,0 +1,13 @@ +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": "0x1ff8020000000", + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} diff --git a/cmd/evm/testdata/14/exp_berlin.json b/cmd/evm/testdata/14/exp_berlin.json new file mode 100644 index 0000000..5c00ef1 --- /dev/null +++ b/cmd/evm/testdata/14/exp_berlin.json @@ -0,0 +1,13 @@ +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": "0x1ff9000000000", + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} diff --git a/cmd/evm/testdata/14/readme.md b/cmd/evm/testdata/14/readme.md new file mode 100644 index 0000000..40dd754 --- /dev/null +++ b/cmd/evm/testdata/14/readme.md @@ -0,0 +1,45 @@ +## Difficulty calculation + +This test shows how the `evm t8n` can be used to calculate the (ethash) difficulty, if none is provided by the caller. + +Calculating it (with an empty set of txs) using `London` rules (and no provided unclehash for the parent block): +``` +[user@work evm]$ ./evm t8n --input.alloc=./testdata/14/alloc.json --input.txs=./testdata/14/txs.json --input.env=./testdata/14/env.json --output.result=stdout --state.fork=London +INFO [03-09|10:43:57.070] Trie dumping started root=6f0588..7f4bdc +INFO [03-09|10:43:57.070] Trie dumping complete accounts=2 elapsed="214.663µs" +INFO [03-09|10:43:57.071] Wrote file file=alloc.json +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": "0x2000020000000", + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} +``` +Same thing, but this time providing a non-empty (and non-`emptyKeccak`) unclehash, which leads to a slightly different result: +``` +[user@work evm]$ ./evm t8n --input.alloc=./testdata/14/alloc.json --input.txs=./testdata/14/txs.json --input.env=./testdata/14/env.uncles.json --output.result=stdout --state.fork=London +INFO [03-09|10:44:20.511] Trie dumping started root=6f0588..7f4bdc +INFO [03-09|10:44:20.511] Trie dumping complete accounts=2 elapsed="184.319µs" +INFO [03-09|10:44:20.512] Wrote file file=alloc.json +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": "0x1ff8020000000", + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} +``` + diff --git a/cmd/evm/testdata/14/txs.json b/cmd/evm/testdata/14/txs.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/evm/testdata/14/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/15/blockheader.rlp b/cmd/evm/testdata/15/blockheader.rlp new file mode 100644 index 0000000..1124e8e --- /dev/null +++ b/cmd/evm/testdata/15/blockheader.rlp @@ -0,0 +1 @@ +"0xf901f0a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b0101020383010203a00000000000000000000000000000000000000000000000000000000000000000880000000000000000" \ No newline at end of file diff --git a/cmd/evm/testdata/15/exp.json b/cmd/evm/testdata/15/exp.json new file mode 100644 index 0000000..1893fdf --- /dev/null +++ b/cmd/evm/testdata/15/exp.json @@ -0,0 +1,10 @@ +[ + { + "error": "transaction type not supported", + "hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476" + }, + { + "error": "transaction type not supported", + "hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/15/exp2.json b/cmd/evm/testdata/15/exp2.json new file mode 100644 index 0000000..dd5e8a3 --- /dev/null +++ b/cmd/evm/testdata/15/exp2.json @@ -0,0 +1,12 @@ +[ + { + "address": "0xd02d72e067e77158444ef2020ff2d325f929b363", + "hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", + "intrinsicGas": "0x5208" + }, + { + "address": "0xd02d72e067e77158444ef2020ff2d325f929b363", + "hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", + "intrinsicGas": "0x5208" + } +] diff --git a/cmd/evm/testdata/15/exp3.json b/cmd/evm/testdata/15/exp3.json new file mode 100644 index 0000000..d7606a2 --- /dev/null +++ b/cmd/evm/testdata/15/exp3.json @@ -0,0 +1,47 @@ +[ + { + "error": "transaction type not supported" + }, + { + "error": "transaction type not supported" + }, + { + "error": "transaction type not supported" + }, + { + "error": "transaction type not supported" + }, + { + "error": "transaction type not supported" + }, + { + "error": "transaction type not supported" + }, + { + "error": "transaction type not supported" + }, + { + "error": "typed transaction too short" + }, + { + "error": "typed transaction too short" + }, + { + "error": "typed transaction too short" + }, + { + "error": "typed transaction too short" + }, + { + "error": "typed transaction too short" + }, + { + "error": "rlp: expected input list for types.AccessListTx" + }, + { + "error": "transaction type not supported" + }, + { + "error": "transaction type not supported" + } +] diff --git a/cmd/evm/testdata/15/signed_txs.rlp b/cmd/evm/testdata/15/signed_txs.rlp new file mode 100644 index 0000000..9d1157e --- /dev/null +++ b/cmd/evm/testdata/15/signed_txs.rlp @@ -0,0 +1 @@ +"0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" \ No newline at end of file diff --git a/cmd/evm/testdata/15/signed_txs.rlp.json b/cmd/evm/testdata/15/signed_txs.rlp.json new file mode 100644 index 0000000..187f40f --- /dev/null +++ b/cmd/evm/testdata/15/signed_txs.rlp.json @@ -0,0 +1,4 @@ +{ + "txsRlp" : "0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" +} + diff --git a/cmd/evm/testdata/16/exp.json b/cmd/evm/testdata/16/exp.json new file mode 100644 index 0000000..137ade6 --- /dev/null +++ b/cmd/evm/testdata/16/exp.json @@ -0,0 +1,13 @@ +[ + { + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "hash": "0x7cc3d1a8540a44736750f03bb4d85c0113be4b3472a71bf82241a3b261b479e6", + "intrinsicGas": "0x5208" + }, + { + "error": "intrinsic gas too low: have 82, want 21000", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "hash": "0x3b2d2609e4361562edb9169314f4c05afc6dbf5d706bf9dda5abe242ab76a22b", + "intrinsicGas": "0x5208" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/16/signed_txs.rlp b/cmd/evm/testdata/16/signed_txs.rlp new file mode 100644 index 0000000..952ced2 --- /dev/null +++ b/cmd/evm/testdata/16/signed_txs.rlp @@ -0,0 +1 @@ +"0xf8cab86401f8610180018252089411111111111111111111111111111111111111112080c001a0937f65ef1deece46c473b99962678fb7c38425cf303d1e8fa9717eb4b9d012b5a01940c5a5647c4940217ffde1051a5fd92ec8551e275c1787f81f50a2ad84de43b86201f85f018001529411111111111111111111111111111111111111112080c001a0241c3aec732205542a87fef8c76346741e85480bce5a42d05a9a73dac892f84ca04f52e2dfce57f3a02ed10e085e1a154edf38a726da34127c85fc53b4921759c8" \ No newline at end of file diff --git a/cmd/evm/testdata/16/unsigned_txs.json b/cmd/evm/testdata/16/unsigned_txs.json new file mode 100644 index 0000000..f619589 --- /dev/null +++ b/cmd/evm/testdata/16/unsigned_txs.json @@ -0,0 +1,34 @@ +[ + { + "input" : "0x", + "gas" : "0x5208", + "nonce" : "0x0", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x20", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "chainId" : "0x1", + "type" : "0x1", + "gasPrice": "0x1", + "accessList" : [ + ] + }, + { + "input" : "0x", + "gas" : "0x52", + "nonce" : "0x0", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x20", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "chainId" : "0x1", + "type" : "0x1", + "gasPrice": "0x1", + "accessList" : [ + ] + } +] diff --git a/cmd/evm/testdata/17/exp.json b/cmd/evm/testdata/17/exp.json new file mode 100644 index 0000000..4859060 --- /dev/null +++ b/cmd/evm/testdata/17/exp.json @@ -0,0 +1,22 @@ + [ + { + "error": "value exceeds 256 bits", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "hash": "0xfbd91685dcbf8172f0e8c53e2ddbb4d26707840da6b51a74371f62a33868fd82", + "intrinsicGas": "0x5208" + }, + { + "error": "gasPrice exceeds 256 bits", + "address": "0x1b57ccef1fe5fb73f1e64530fb4ebd9cf1655964", + "hash": "0x45dc05035cada83748e4c1fe617220106b331eca054f44c2304d5654a9fb29d5", + "intrinsicGas": "0x5208" + }, + { + "error": "invalid transaction v, r, s values", + "hash": "0xf06691c2a803ab7f3c81d06a0c0a896f80f311105c599fc59a9fdbc669356d35" + }, + { + "error": "invalid transaction v, r, s values", + "hash": "0x84703b697ad5b0db25e4f1f98fb6b1adce85b9edb2232eeba9cedd8c6601694b" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/17/rlpdata.txt b/cmd/evm/testdata/17/rlpdata.txt new file mode 100644 index 0000000..874461f --- /dev/null +++ b/cmd/evm/testdata/17/rlpdata.txt @@ -0,0 +1,46 @@ +[ + [ + "", + "d", + 5208, + d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0, + 010000000000000000000000000000000000000000000000000000000000000001, + "", + 1b, + c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549d, + 6180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28, + ], + [ + "", + 010000000000000000000000000000000000000000000000000000000000000001, + 5208, + d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0, + 11, + "", + 1b, + c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549d, + 6180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28, + ], + [ + "", + 11, + 5208, + d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0, + 11, + "", + 1b, + c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549daa, + 6180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28, + ], + [ + "", + 11, + 5208, + d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0, + 11, + "", + 1b, + c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549d, + 6180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28bb, + ], +] diff --git a/cmd/evm/testdata/17/signed_txs.rlp b/cmd/evm/testdata/17/signed_txs.rlp new file mode 100644 index 0000000..0e351fb --- /dev/null +++ b/cmd/evm/testdata/17/signed_txs.rlp @@ -0,0 +1 @@ +"0xf901c8f880806482520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a1010000000000000000000000000000000000000000000000000000000000000001801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28f88080a101000000000000000000000000000000000000000000000000000000000000000182520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d011801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28f860801182520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d011801ba1c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549daaa06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28f860801182520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d011801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da16180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28bb" \ No newline at end of file diff --git a/cmd/evm/testdata/18/README.md b/cmd/evm/testdata/18/README.md new file mode 100644 index 0000000..360a9bb --- /dev/null +++ b/cmd/evm/testdata/18/README.md @@ -0,0 +1,9 @@ +# Invalid rlp + +This folder contains a sample of invalid RLP, and it's expected +that the t9n handles this properly: + +``` +$ go run . t9n --input.txs=./testdata/18/invalid.rlp --state.fork=London +ERROR(11): rlp: value size exceeds available input length +``` \ No newline at end of file diff --git a/cmd/evm/testdata/18/invalid.rlp b/cmd/evm/testdata/18/invalid.rlp new file mode 100644 index 0000000..7ff2824 --- /dev/null +++ b/cmd/evm/testdata/18/invalid.rlp @@ -0,0 +1 @@ +"0xf852328001825208870b9331677e6ebf0a801ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa03887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3" \ No newline at end of file diff --git a/cmd/evm/testdata/19/alloc.json b/cmd/evm/testdata/19/alloc.json new file mode 100644 index 0000000..cef1a25 --- /dev/null +++ b/cmd/evm/testdata/19/alloc.json @@ -0,0 +1,12 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/19/env.json b/cmd/evm/testdata/19/env.json new file mode 100644 index 0000000..0c64392 --- /dev/null +++ b/cmd/evm/testdata/19/env.json @@ -0,0 +1,9 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentGasLimit": "0x750a163df65e8a", + "currentBaseFee": "0x500", + "currentNumber": "13000000", + "currentTimestamp": "100015", + "parentTimestamp" : "99999", + "parentDifficulty" : "0x2000000000000" +} diff --git a/cmd/evm/testdata/19/exp_arrowglacier.json b/cmd/evm/testdata/19/exp_arrowglacier.json new file mode 100644 index 0000000..dd49f7d --- /dev/null +++ b/cmd/evm/testdata/19/exp_arrowglacier.json @@ -0,0 +1,13 @@ +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "currentDifficulty": "0x2000000200000", + "receipts": [], + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} diff --git a/cmd/evm/testdata/19/exp_grayglacier.json b/cmd/evm/testdata/19/exp_grayglacier.json new file mode 100644 index 0000000..86fd8e6 --- /dev/null +++ b/cmd/evm/testdata/19/exp_grayglacier.json @@ -0,0 +1,13 @@ +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": "0x2000000004000", + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/19/exp_london.json b/cmd/evm/testdata/19/exp_london.json new file mode 100644 index 0000000..9e9a17d --- /dev/null +++ b/cmd/evm/testdata/19/exp_london.json @@ -0,0 +1,13 @@ +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "currentDifficulty": "0x2000080000000", + "receipts": [], + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} diff --git a/cmd/evm/testdata/19/readme.md b/cmd/evm/testdata/19/readme.md new file mode 100644 index 0000000..9c7c4b3 --- /dev/null +++ b/cmd/evm/testdata/19/readme.md @@ -0,0 +1,25 @@ +## Difficulty calculation + +This test shows how the `evm t8n` can be used to calculate the (ethash) difficulty, if none is provided by the caller, +this time on `GrayGlacier` (Eip 5133). + +Calculating it (with an empty set of txs) using `GrayGlacier` rules (and no provided unclehash for the parent block): +``` +[user@work evm]$ ./evm t8n --input.alloc=./testdata/19/alloc.json --input.txs=./testdata/19/txs.json --input.env=./testdata/19/env.json --output.result=stdout --state.fork=GrayGlacier +INFO [03-09|10:45:26.777] Trie dumping started root=6f0588..7f4bdc +INFO [03-09|10:45:26.777] Trie dumping complete accounts=2 elapsed="176.471µs" +INFO [03-09|10:45:26.777] Wrote file file=alloc.json +{ + "result": { + "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": "0x2000000004000", + "gasUsed": "0x0", + "currentBaseFee": "0x500" + } +} +``` \ No newline at end of file diff --git a/cmd/evm/testdata/19/txs.json b/cmd/evm/testdata/19/txs.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/evm/testdata/19/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/2/alloc.json b/cmd/evm/testdata/2/alloc.json new file mode 100644 index 0000000..a9720af --- /dev/null +++ b/cmd/evm/testdata/2/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x6001600053600160006001f0ff00", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/2/env.json b/cmd/evm/testdata/2/env.json new file mode 100644 index 0000000..ebadd3f --- /dev/null +++ b/cmd/evm/testdata/2/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x01", + "currentTimestamp" : "0x03e8" +} \ No newline at end of file diff --git a/cmd/evm/testdata/2/readme.md b/cmd/evm/testdata/2/readme.md new file mode 100644 index 0000000..4bcf0f0 --- /dev/null +++ b/cmd/evm/testdata/2/readme.md @@ -0,0 +1 @@ +These files exemplify a selfdestruct to the `0`-address. \ No newline at end of file diff --git a/cmd/evm/testdata/2/txs.json b/cmd/evm/testdata/2/txs.json new file mode 100644 index 0000000..3044458 --- /dev/null +++ b/cmd/evm/testdata/2/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/20/exp.json b/cmd/evm/testdata/20/exp.json new file mode 100644 index 0000000..7bec6ce --- /dev/null +++ b/cmd/evm/testdata/20/exp.json @@ -0,0 +1,4 @@ +{ + "rlp": "0xf902d9f90211a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794e997a23b159e2e2a5ce72333262972374b15425ca0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e99476574682f76312e302e312f6c696e75782f676f312e342e32a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf8897435673d874f7c8f8c2f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600c0", + "hash": "0xaba9a3b6a4e96e9ecffcadaa5a2ae0589359455617535cd86589fe1dd26fe899" +} diff --git a/cmd/evm/testdata/20/header.json b/cmd/evm/testdata/20/header.json new file mode 100644 index 0000000..fb9b7fc --- /dev/null +++ b/cmd/evm/testdata/20/header.json @@ -0,0 +1,14 @@ +{ + "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e", + "miner": "0xe997a23b159e2e2a5ce72333262972374b15425c", + "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x1000", + "number": "0xc3be", + "gasLimit": "0x50785", + "gasUsed": "0x0", + "timestamp": "0x55c5277e", + "extraData": "0x476574682f76312e302e312f6c696e75782f676f312e342e32", + "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf", + "nonce": "0x97435673d874f7c8" +} diff --git a/cmd/evm/testdata/20/ommers.json b/cmd/evm/testdata/20/ommers.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/evm/testdata/20/ommers.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/20/readme.md b/cmd/evm/testdata/20/readme.md new file mode 100644 index 0000000..2c448a9 --- /dev/null +++ b/cmd/evm/testdata/20/readme.md @@ -0,0 +1,11 @@ +# Block building + +This test shows how `b11r` can be used to assemble an unsealed block. + +```console +$ go run . b11r --input.header=testdata/20/header.json --input.txs=testdata/20/txs.rlp --input.ommers=testdata/20/ommers.json --output.block=stdout +{ + "rlp": "0xf90216f90211a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794e997a23b159e2e2a5ce72333262972374b15425ca0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e99476574682f76312e302e312f6c696e75782f676f312e342e32a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf8897435673d874f7c8c0c0", + "hash": "0xaba9a3b6a4e96e9ecffcadaa5a2ae0589359455617535cd86589fe1dd26fe899" +} +``` diff --git a/cmd/evm/testdata/20/txs.rlp b/cmd/evm/testdata/20/txs.rlp new file mode 100644 index 0000000..3599ff0 --- /dev/null +++ b/cmd/evm/testdata/20/txs.rlp @@ -0,0 +1 @@ +"0xf8c2f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600" \ No newline at end of file diff --git a/cmd/evm/testdata/21/clique.json b/cmd/evm/testdata/21/clique.json new file mode 100644 index 0000000..84fa259 --- /dev/null +++ b/cmd/evm/testdata/21/clique.json @@ -0,0 +1,6 @@ +{ + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "voted": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "authorize": false, + "vanity": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +} diff --git a/cmd/evm/testdata/21/exp-clique.json b/cmd/evm/testdata/21/exp-clique.json new file mode 100644 index 0000000..c990ba8 --- /dev/null +++ b/cmd/evm/testdata/21/exp-clique.json @@ -0,0 +1,4 @@ +{ + "rlp": "0xf9025ff9025aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277eb861aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac540a67aaee364005841da84f488f6b6d0116dfb5103d091402c81a163d5f66666595e37f56f196d8c5c98da714dbfae68d6b7e1790cc734a20ec6ce52213ad800a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88ffffffffffffffffc0c0", + "hash": "0x71c59102cc805dbe8741e1210ebe229a321eff144ac7276006fefe39e8357dc7" +} diff --git a/cmd/evm/testdata/21/exp.json b/cmd/evm/testdata/21/exp.json new file mode 100644 index 0000000..b3e5e7a --- /dev/null +++ b/cmd/evm/testdata/21/exp.json @@ -0,0 +1,4 @@ +{ + "rlp": "0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0", + "hash": "0x801411e9f6609a659825690d13e4f75a3cfe9143952fa2d9573f3b0a5eb9ebbb" +} diff --git a/cmd/evm/testdata/21/header.json b/cmd/evm/testdata/21/header.json new file mode 100644 index 0000000..62abe3c --- /dev/null +++ b/cmd/evm/testdata/21/header.json @@ -0,0 +1,11 @@ +{ + "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e", + "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x1000", + "number": "0xc3be", + "gasLimit": "0x50785", + "gasUsed": "0x0", + "timestamp": "0x55c5277e", + "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf" +} diff --git a/cmd/evm/testdata/21/ommers.json b/cmd/evm/testdata/21/ommers.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/evm/testdata/21/ommers.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/21/readme.md b/cmd/evm/testdata/21/readme.md new file mode 100644 index 0000000..b70f106 --- /dev/null +++ b/cmd/evm/testdata/21/readme.md @@ -0,0 +1,23 @@ +# Sealed block building + +This test shows how `b11r` can be used to assemble a sealed block. + +## Ethash + +```console +$ go run . b11r --input.header=testdata/21/header.json --input.txs=testdata/21/txs.rlp --input.ommers=testdata/21/ommers.json --seal.ethash --seal.ethash.mode=test --output.block=stdout +{ + "rlp": "0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0", + "hash": "0x801411e9f6609a659825690d13e4f75a3cfe9143952fa2d9573f3b0a5eb9ebbb" +} +``` + +## Clique + +```console +$ go run . b11r --input.header=testdata/21/header.json --input.txs=testdata/21/txs.rlp --input.ommers=testdata/21/ommers.json --seal.clique=testdata/21/clique.json --output.block=stdout +{ + "rlp": "0xf9025ff9025aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277eb861aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac540a67aaee364005841da84f488f6b6d0116dfb5103d091402c81a163d5f66666595e37f56f196d8c5c98da714dbfae68d6b7e1790cc734a20ec6ce52213ad800a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88ffffffffffffffffc0c0", + "hash": "0x71c59102cc805dbe8741e1210ebe229a321eff144ac7276006fefe39e8357dc7" +} +``` diff --git a/cmd/evm/testdata/21/txs.rlp b/cmd/evm/testdata/21/txs.rlp new file mode 100644 index 0000000..e815397 --- /dev/null +++ b/cmd/evm/testdata/21/txs.rlp @@ -0,0 +1 @@ +"c0" diff --git a/cmd/evm/testdata/22/exp-clique.json b/cmd/evm/testdata/22/exp-clique.json new file mode 100644 index 0000000..c990ba8 --- /dev/null +++ b/cmd/evm/testdata/22/exp-clique.json @@ -0,0 +1,4 @@ +{ + "rlp": "0xf9025ff9025aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277eb861aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac540a67aaee364005841da84f488f6b6d0116dfb5103d091402c81a163d5f66666595e37f56f196d8c5c98da714dbfae68d6b7e1790cc734a20ec6ce52213ad800a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88ffffffffffffffffc0c0", + "hash": "0x71c59102cc805dbe8741e1210ebe229a321eff144ac7276006fefe39e8357dc7" +} diff --git a/cmd/evm/testdata/22/exp.json b/cmd/evm/testdata/22/exp.json new file mode 100644 index 0000000..14fd819 --- /dev/null +++ b/cmd/evm/testdata/22/exp.json @@ -0,0 +1,4 @@ +{ + "rlp": "0xf905f5f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea06eb9f0c3cd68c9e97134e6725d12b1f1d8f0644458da6870a37ff84c908fb1e7940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0f903f6f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000", + "hash": "0xd9a81c8fcd57a7f2a0d2c375eff6ad192c30c3729a271303f0a9a7e1b357e755" +} diff --git a/cmd/evm/testdata/22/header.json b/cmd/evm/testdata/22/header.json new file mode 100644 index 0000000..62abe3c --- /dev/null +++ b/cmd/evm/testdata/22/header.json @@ -0,0 +1,11 @@ +{ + "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e", + "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x1000", + "number": "0xc3be", + "gasLimit": "0x50785", + "gasUsed": "0x0", + "timestamp": "0x55c5277e", + "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf" +} diff --git a/cmd/evm/testdata/22/ommers.json b/cmd/evm/testdata/22/ommers.json new file mode 100644 index 0000000..997015b --- /dev/null +++ b/cmd/evm/testdata/22/ommers.json @@ -0,0 +1 @@ +["0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0","0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0"] diff --git a/cmd/evm/testdata/22/readme.md b/cmd/evm/testdata/22/readme.md new file mode 100644 index 0000000..2cac8a2 --- /dev/null +++ b/cmd/evm/testdata/22/readme.md @@ -0,0 +1,11 @@ +# Building blocks with ommers + +This test shows how `b11r` can chain together ommer assembles into a canonical block. + +```console +$ echo "{ \"ommers\": [`go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --output.block=stdout | jq '.[\"rlp\"]'`,`go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --output.block=stdout | jq '.[\"rlp\"]'`]}" | go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --input.ommers=stdin --output.block=stdout +{ + "rlp": "0xf905f5f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea06eb9f0c3cd68c9e97134e6725d12b1f1d8f0644458da6870a37ff84c908fb1e7940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0f903f6f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000", + "hash": "0xd9a81c8fcd57a7f2a0d2c375eff6ad192c30c3729a271303f0a9a7e1b357e755" +} +``` diff --git a/cmd/evm/testdata/22/txs.rlp b/cmd/evm/testdata/22/txs.rlp new file mode 100644 index 0000000..e815397 --- /dev/null +++ b/cmd/evm/testdata/22/txs.rlp @@ -0,0 +1 @@ +"c0" diff --git a/cmd/evm/testdata/23/alloc.json b/cmd/evm/testdata/23/alloc.json new file mode 100644 index 0000000..239b355 --- /dev/null +++ b/cmd/evm/testdata/23/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x6001", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} diff --git a/cmd/evm/testdata/23/env.json b/cmd/evm/testdata/23/env.json new file mode 100644 index 0000000..1b46321 --- /dev/null +++ b/cmd/evm/testdata/23/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x05", + "currentTimestamp" : "0x03e8" +} diff --git a/cmd/evm/testdata/23/exp.json b/cmd/evm/testdata/23/exp.json new file mode 100644 index 0000000..22dde0a --- /dev/null +++ b/cmd/evm/testdata/23/exp.json @@ -0,0 +1,26 @@ +{ + "result": { + "stateRoot": "0x65334305e4accfa18352deb24f007b837b5036425b0712cf0e65a43bfa95154d", + "txRoot": "0x75e61774a2ff58cbe32653420256c7f44bc715715a423b0b746d5c622979af6b", + "receiptsRoot": "0xf951f9396af203499cc7d379715a9110323de73967c5700e2f424725446a3c76", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x520b", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x520b", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x520b" + } +} diff --git a/cmd/evm/testdata/23/readme.md b/cmd/evm/testdata/23/readme.md new file mode 100644 index 0000000..f31b64d --- /dev/null +++ b/cmd/evm/testdata/23/readme.md @@ -0,0 +1 @@ +These files exemplify how to sign a transaction using the pre-EIP155 scheme. diff --git a/cmd/evm/testdata/23/txs.json b/cmd/evm/testdata/23/txs.json new file mode 100644 index 0000000..22f3840 --- /dev/null +++ b/cmd/evm/testdata/23/txs.json @@ -0,0 +1,15 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "protected": false + } +] diff --git a/cmd/evm/testdata/24/alloc.json b/cmd/evm/testdata/24/alloc.json new file mode 100644 index 0000000..73a9a03 --- /dev/null +++ b/cmd/evm/testdata/24/alloc.json @@ -0,0 +1,14 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00", + "code" : "0x44600055", + "_comment": "The code is 'sstore(0, random)'" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/24/env-missingrandom.json b/cmd/evm/testdata/24/env-missingrandom.json new file mode 100644 index 0000000..db49fd3 --- /dev/null +++ b/cmd/evm/testdata/24/env-missingrandom.json @@ -0,0 +1,9 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": null, + "currentRandom": null, + "currentGasLimit": "0x750a163df65e8a", + "currentBaseFee": "0x500", + "currentNumber": "1", + "currentTimestamp": "1000" +} diff --git a/cmd/evm/testdata/24/env.json b/cmd/evm/testdata/24/env.json new file mode 100644 index 0000000..262cc25 --- /dev/null +++ b/cmd/evm/testdata/24/env.json @@ -0,0 +1,9 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": null, + "currentRandom": "0xdeadc0de", + "currentGasLimit": "0x750a163df65e8a", + "currentBaseFee": "0x500", + "currentNumber": "1", + "currentTimestamp": "1000" +} diff --git a/cmd/evm/testdata/24/exp.json b/cmd/evm/testdata/24/exp.json new file mode 100644 index 0000000..ac571d1 --- /dev/null +++ b/cmd/evm/testdata/24/exp.json @@ -0,0 +1,56 @@ +{ + "alloc": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "code": "0x44600055", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000deadc0de" + }, + "balance": "0xfeedbeaf" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878b803f972", + "nonce": "0xae" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x1030600" + } + }, + "result": { + "stateRoot": "0x9e4224c6bba343d5b0fdbe9200cc66a7ef2068240d901ae516e634c45a043c15", + "txRoot": "0x16cd3a7daa6686ceebadf53b7af2bc6919eccb730907f0e74a95a4423c209593", + "receiptsRoot": "0x22b85cda738345a9880260b2a71e144aab1ca9485f5db4fd251008350fc124c8", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xa861", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x92ea4a28224d033afb20e0cc2b290d4c7c2d61f6a4800a680e4e19ac962ee941", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0xa861", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + }, + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x10306", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x16b1d912f1d664f3f60f4e1b5f296f3c82a64a1a253117b4851d18bc03c4f1da", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5aa5", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x1" + } + ], + "currentDifficulty": null, + "gasUsed": "0x10306", + "currentBaseFee": "0x500" + } +} diff --git a/cmd/evm/testdata/24/txs.json b/cmd/evm/testdata/24/txs.json new file mode 100644 index 0000000..99c2068 --- /dev/null +++ b/cmd/evm/testdata/24/txs.json @@ -0,0 +1,28 @@ +[ + { + "gas": "0x186a0", + "gasPrice": "0x600", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0xac", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "value": "0x1", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x186a0", + "gasPrice": "0x600", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0xad", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "value": "0x1", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/evm/testdata/25/alloc.json b/cmd/evm/testdata/25/alloc.json new file mode 100644 index 0000000..d663667 --- /dev/null +++ b/cmd/evm/testdata/25/alloc.json @@ -0,0 +1,8 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + } +} diff --git a/cmd/evm/testdata/25/env.json b/cmd/evm/testdata/25/env.json new file mode 100644 index 0000000..bb2c9e0 --- /dev/null +++ b/cmd/evm/testdata/25/env.json @@ -0,0 +1,11 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": null, + "currentRandom": "0xdeadc0de", + "currentGasLimit": "0x750a163df65e8a", + "parentBaseFee": "0x500", + "parentGasUsed": "0x0", + "parentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000" +} diff --git a/cmd/evm/testdata/25/exp.json b/cmd/evm/testdata/25/exp.json new file mode 100644 index 0000000..1cb5217 --- /dev/null +++ b/cmd/evm/testdata/25/exp.json @@ -0,0 +1,39 @@ +{ + "alloc": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878bc29ed73", + "nonce": "0xad" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x854d00" + } + }, + "result": { + "stateRoot": "0x5139609e39f4d158a7d1ad1800908eb0349cea9b500a8273a6cf0a7e4392639b", + "txRoot": "0x572690baf4898c2972446e56ecf0aa2a027c08a863927d2dce34472f0c5496fe", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x92ea4a28224d033afb20e0cc2b290d4c7c2d61f6a4800a680e4e19ac962ee941", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": null, + "gasUsed": "0x5208", + "currentBaseFee": "0x460" + } +} diff --git a/cmd/evm/testdata/25/txs.json b/cmd/evm/testdata/25/txs.json new file mode 100644 index 0000000..acb4035 --- /dev/null +++ b/cmd/evm/testdata/25/txs.json @@ -0,0 +1,15 @@ +[ + { + "gas": "0x186a0", + "gasPrice": "0x600", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0xac", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "value": "0x1", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/evm/testdata/26/alloc.json b/cmd/evm/testdata/26/alloc.json new file mode 100644 index 0000000..d67655a --- /dev/null +++ b/cmd/evm/testdata/26/alloc.json @@ -0,0 +1,8 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0", + "code": "0x", + "nonce": "0xac", + "storage": {} + } +} diff --git a/cmd/evm/testdata/26/env.json b/cmd/evm/testdata/26/env.json new file mode 100644 index 0000000..03d817b --- /dev/null +++ b/cmd/evm/testdata/26/env.json @@ -0,0 +1,17 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": null, + "currentRandom": "0xdeadc0de", + "currentGasLimit": "0x750a163df65e8a", + "currentBaseFee": "0x500", + "currentNumber": "1", + "currentTimestamp": "1000", + "withdrawals": [ + { + "index": "0x42", + "validatorIndex": "0x42", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "amount": "0x2a" + } + ] +} diff --git a/cmd/evm/testdata/26/exp.json b/cmd/evm/testdata/26/exp.json new file mode 100644 index 0000000..4815e5c --- /dev/null +++ b/cmd/evm/testdata/26/exp.json @@ -0,0 +1,20 @@ +{ + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x9c7652400", + "nonce": "0xac" + } + }, + "result": { + "stateRoot": "0x6e061c2f6513af27d267a0e3b07cb9a10f1ba3a0f65ab648d3a17c36e15021d2", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": null, + "gasUsed": "0x0", + "currentBaseFee": "0x500", + "withdrawalsRoot": "0x4921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5" + } +} diff --git a/cmd/evm/testdata/26/txs.json b/cmd/evm/testdata/26/txs.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/evm/testdata/26/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/27/exp.json b/cmd/evm/testdata/27/exp.json new file mode 100644 index 0000000..5975a9c --- /dev/null +++ b/cmd/evm/testdata/27/exp.json @@ -0,0 +1,4 @@ +{ + "rlp": "0xf90239f9021aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88000000000000000080a04921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5c0c0d9d8424394a94f5374fce5edbc8e2a8697c15331677e6ebf0b2a", + "hash": "0xdc42abd3698499675819e0a85cc1266f16da90277509b867446a6b25fa2b9d87" +} diff --git a/cmd/evm/testdata/27/header.json b/cmd/evm/testdata/27/header.json new file mode 100644 index 0000000..4ed7eac --- /dev/null +++ b/cmd/evm/testdata/27/header.json @@ -0,0 +1,12 @@ +{ + "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e", + "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x1000", + "number": "0xc3be", + "gasLimit": "0x50785", + "gasUsed": "0x0", + "timestamp": "0x55c5277e", + "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf", + "withdrawalsRoot": "0x4921c0162c359755b2ae714a0978a1dad2eb8edce7ff9b38b9b6fc4cbc547eb5" +} diff --git a/cmd/evm/testdata/27/ommers.json b/cmd/evm/testdata/27/ommers.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/evm/testdata/27/ommers.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/27/txs.rlp b/cmd/evm/testdata/27/txs.rlp new file mode 100644 index 0000000..e815397 --- /dev/null +++ b/cmd/evm/testdata/27/txs.rlp @@ -0,0 +1 @@ +"c0" diff --git a/cmd/evm/testdata/27/withdrawals.json b/cmd/evm/testdata/27/withdrawals.json new file mode 100644 index 0000000..6634aff --- /dev/null +++ b/cmd/evm/testdata/27/withdrawals.json @@ -0,0 +1,8 @@ +[ + { + "index": "0x42", + "validatorIndex": "0x43", + "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "amount": "0x2a" + } +] diff --git a/cmd/evm/testdata/28/alloc.json b/cmd/evm/testdata/28/alloc.json new file mode 100644 index 0000000..680a89f --- /dev/null +++ b/cmd/evm/testdata/28/alloc.json @@ -0,0 +1,16 @@ +{ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x016345785d8a0000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + }, + "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x016345785d8a0000", + "code" : "0x60004960015500", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/28/env.json b/cmd/evm/testdata/28/env.json new file mode 100644 index 0000000..82f22ac --- /dev/null +++ b/cmd/evm/testdata/28/env.json @@ -0,0 +1,22 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentNumber" : "0x01", + "currentTimestamp" : "0x079e", + "currentGasLimit" : "0x7fffffffffffffff", + "previousHash" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6", + "currentBlobGasUsed" : "0x00", + "parentTimestamp" : "0x03b6", + "parentDifficulty" : "0x00", + "parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "currentRandom" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "withdrawals" : [], + "parentBaseFee" : "0x0a", + "parentGasUsed" : "0x00", + "parentGasLimit" : "0x7fffffffffffffff", + "parentExcessBlobGas" : "0x00", + "parentBlobGasUsed" : "0x00", + "blockHashes" : { + "0" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6" + }, + "parentBeaconBlockRoot": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" +} diff --git a/cmd/evm/testdata/28/exp.json b/cmd/evm/testdata/28/exp.json new file mode 100644 index 0000000..75c715e --- /dev/null +++ b/cmd/evm/testdata/28/exp.json @@ -0,0 +1,47 @@ +{ + "alloc": { + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { + "balance": "0x150ca" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x16345785d80c3a9", + "nonce": "0x1" + }, + "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x60004960015500", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x01a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "balance": "0x16345785d8a0000" + } + }, + "result": { + "stateRoot": "0xa40cb3fab01848e922a48bd24191815df9f721ad4b60376edac75161517663e8", + "txRoot": "0x4409cc4b699384ba5f8248d92b784713610c5ff9c1de51e9239da0dac76de9ce", + "receiptsRoot": "0xbff643da765981266133094092d98c81d2ac8e9a83a7bbda46c3d736f1f874ac", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "type": "0x3", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xa865", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x7508d7139d002a4b3a26a4f12dec0d87cb46075c78bf77a38b569a133b509262", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0xa865", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": null, + "gasUsed": "0xa865", + "currentBaseFee": "0x9", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "currentExcessBlobGas": "0x0", + "blobGasUsed": "0x20000" + } +} diff --git a/cmd/evm/testdata/28/txs.rlp b/cmd/evm/testdata/28/txs.rlp new file mode 100644 index 0000000..8df20e3 --- /dev/null +++ b/cmd/evm/testdata/28/txs.rlp @@ -0,0 +1 @@ +"0xf88bb88903f8860180026483061a8094b94f5374fce5edbc8e2a8697c15331677e6ebf0b8080c00ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d801a025e16bb498552165016751911c3608d79000ab89dc3100776e729e6ea13091c7a03acacff7fc0cff6eda8a927dec93ca17765e1ee6cbc06c5954ce102e097c01d2" \ No newline at end of file diff --git a/cmd/evm/testdata/29/alloc.json b/cmd/evm/testdata/29/alloc.json new file mode 100644 index 0000000..d2c879a --- /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 0000000..e752a90 --- /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 0000000..c4c001e --- /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 0000000..ab02ce9 --- /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 0000000..d6743cc --- /dev/null +++ b/cmd/evm/testdata/29/txs.json @@ -0,0 +1,19 @@ +[ + { + "input" : "0x", + "gas" : "0x10000000", + "nonce" : "0x0", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "chainId" : "0x1", + "type" : "0x2", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x0", + "accessList" : [ + ] + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/3/alloc.json b/cmd/evm/testdata/3/alloc.json new file mode 100644 index 0000000..dca318e --- /dev/null +++ b/cmd/evm/testdata/3/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x600140", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/3/env.json b/cmd/evm/testdata/3/env.json new file mode 100644 index 0000000..e283eff --- /dev/null +++ b/cmd/evm/testdata/3/env.json @@ -0,0 +1,8 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x05", + "currentTimestamp" : "0x03e8", + "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} +} \ No newline at end of file diff --git a/cmd/evm/testdata/3/exp.json b/cmd/evm/testdata/3/exp.json new file mode 100644 index 0000000..7230dca --- /dev/null +++ b/cmd/evm/testdata/3/exp.json @@ -0,0 +1,39 @@ +{ + "alloc": { + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { + "code": "0x600140", + "balance": "0xde0b6b3a76586a0" + }, + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { + "balance": "0x521f" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xde0b6b3a7622741", + "nonce": "0x1" + } + }, + "result": { + "stateRoot": "0xb7341da3f9f762a6884eaa186c32942734c146b609efee11c4b0214c44857ea1", + "txRoot": "0x75e61774a2ff58cbe32653420256c7f44bc715715a423b0b746d5c622979af6b", + "receiptsRoot": "0xd0d26df80374a327c025d405ebadc752b1bbd089d864801ae78ab704bcad8086", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x521f", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x521f", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": "0x20000", + "gasUsed": "0x521f" + } +} diff --git a/cmd/evm/testdata/3/readme.md b/cmd/evm/testdata/3/readme.md new file mode 100644 index 0000000..246c58e --- /dev/null +++ b/cmd/evm/testdata/3/readme.md @@ -0,0 +1,2 @@ +These files exemplify a transition where a transaction (executed on block 5) requests +the blockhash for block `1`. diff --git a/cmd/evm/testdata/3/txs.json b/cmd/evm/testdata/3/txs.json new file mode 100644 index 0000000..3044458 --- /dev/null +++ b/cmd/evm/testdata/3/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ 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 0000000..84c92de --- /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 0000000..6bc93d2 --- /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 0000000..4acd979 --- /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 0000000..f0b19c6 --- /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 0000000..620c1a1 --- /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 0000000..35af8d1 --- /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/31/README.md b/cmd/evm/testdata/31/README.md new file mode 100644 index 0000000..305e4f5 --- /dev/null +++ b/cmd/evm/testdata/31/README.md @@ -0,0 +1 @@ +This test does some EVM execution, and can be used to test the tracers and trace-outputs. diff --git a/cmd/evm/testdata/31/alloc.json b/cmd/evm/testdata/31/alloc.json new file mode 100644 index 0000000..bad5481 --- /dev/null +++ b/cmd/evm/testdata/31/alloc.json @@ -0,0 +1,16 @@ +{ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x016345785d8a0000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + }, + "0x1111111111111111111111111111111111111111" : { + "balance" : "0x1", + "code" : "0x604060406040604000", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/31/env.json b/cmd/evm/testdata/31/env.json new file mode 100644 index 0000000..09b5f12 --- /dev/null +++ b/cmd/evm/testdata/31/env.json @@ -0,0 +1,20 @@ +{ + "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" +} \ No newline at end of file diff --git a/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json b/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json new file mode 100644 index 0000000..cd4bc1a --- /dev/null +++ b/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json @@ -0,0 +1 @@ +"hello world" diff --git a/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl b/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl new file mode 100644 index 0000000..26e5c7e --- /dev/null +++ b/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl @@ -0,0 +1,6 @@ +{"pc":0,"op":96,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":2,"op":96,"gas":"0x13495","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":4,"op":96,"gas":"0x13492","gasCost":"0x3","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":6,"op":96,"gas":"0x1348f","gasCost":"0x3","memSize":0,"stack":["0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":8,"op":0,"gas":"0x1348c","gasCost":"0x0","memSize":0,"stack":["0x40","0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"STOP"} +{"output":"","gasUsed":"0xc"} diff --git a/cmd/evm/testdata/31/txs.json b/cmd/evm/testdata/31/txs.json new file mode 100644 index 0000000..473c152 --- /dev/null +++ b/cmd/evm/testdata/31/txs.json @@ -0,0 +1,14 @@ +[ + { + "gas": "0x186a0", + "gasPrice": "0x600", + "input": "0x", + "nonce": "0x0", + "to": "0x1111111111111111111111111111111111111111", + "value": "0x1", + "v" : "0x0", + "r" : "0x0", + "s" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/evm/testdata/32/README.md b/cmd/evm/testdata/32/README.md new file mode 100644 index 0000000..508ac97 --- /dev/null +++ b/cmd/evm/testdata/32/README.md @@ -0,0 +1 @@ +This test does some EVM execution, and can be used to test callframes emitted by the tracer when they are enabled. diff --git a/cmd/evm/testdata/32/alloc.json b/cmd/evm/testdata/32/alloc.json new file mode 100644 index 0000000..0cd4493 --- /dev/null +++ b/cmd/evm/testdata/32/alloc.json @@ -0,0 +1,30 @@ +{ + "0x8a0a19589531694250d570040a0c4b74576919b8": { + "nonce": "0x00", + "balance": "0x0de0b6b3a7640000", + "code": "0x600060006000600060007310000000000000000000000000000000000000015af1600155600060006000600060007310000000000000000000000000000000000000025af16002553d600060003e600051600355", + "storage": { + "0x01": "0x0100", + "0x02": "0x0100", + "0x03": "0x0100" + } + }, + "0x1000000000000000000000000000000000000001": { + "nonce": "0x00", + "balance": "0x29a2241af62c0000", + "code": "0x6103e8ff", + "storage": {} + }, + "0x1000000000000000000000000000000000000002": { + "nonce": "0x00", + "balance": "0x4563918244f40000", + "code": "0x600060006000600060647310000000000000000000000000000000000000015af1600f0160005260206000fd", + "storage": {} + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x00", + "balance": "0x6124fee993bc0000", + "code": "0x", + "storage": {} + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/32/env.json b/cmd/evm/testdata/32/env.json new file mode 100644 index 0000000..4f0833e --- /dev/null +++ b/cmd/evm/testdata/32/env.json @@ -0,0 +1,12 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "71794957647893862", + "currentNumber": "1", + "currentTimestamp": "1000", + "currentRandom": "0", + "currentDifficulty": "0", + "blockHashes": {}, + "ommers": [], + "currentBaseFee": "7", + "parentUncleHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/cmd/evm/testdata/32/trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl b/cmd/evm/testdata/32/trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl new file mode 100644 index 0000000..b6c5237 --- /dev/null +++ b/cmd/evm/testdata/32/trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl @@ -0,0 +1,61 @@ +{"from":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","to":"0x8a0a19589531694250d570040a0c4b74576919b8","gas":"0x74f18","value":"0x0","type":"CALL"} +{"pc":0,"op":96,"gas":"0x74f18","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":2,"op":96,"gas":"0x74f15","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":4,"op":96,"gas":"0x74f12","gasCost":"0x3","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":6,"op":96,"gas":"0x74f0f","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":8,"op":96,"gas":"0x74f0c","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":10,"op":115,"gas":"0x74f09","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH20"} +{"pc":31,"op":90,"gas":"0x74f06","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000000000000000000000000000000000000001"],"depth":1,"refund":0,"opName":"GAS"} +{"pc":32,"op":241,"gas":"0x74f04","gasCost":"0x731f1","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000000000000000000000000000000000000001","0x74f04"],"depth":1,"refund":0,"opName":"CALL"} +{"from":"0x8a0a19589531694250d570040a0c4b74576919b8","to":"0x1000000000000000000000000000000000000001","gas":"0x727c9","value":"0x0","type":"CALL"} +{"pc":0,"op":97,"gas":"0x727c9","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH2"} +{"pc":3,"op":255,"gas":"0x727c6","gasCost":"0x7f58","memSize":0,"stack":["0x3e8"],"depth":2,"refund":0,"opName":"SELFDESTRUCT"} +{"from":"0x1000000000000000000000000000000000000001","to":"0x00000000000000000000000000000000000003e8","gas":"0x0","value":"0x29a2241af62c0000","type":"SELFDESTRUCT"} +{"output":"","gasUsed":"0x0"} +{"output":"","gasUsed":"0x7f5b"} +{"pc":33,"op":96,"gas":"0x6c581","gasCost":"0x3","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":35,"op":85,"gas":"0x6c57e","gasCost":"0x1388","memSize":0,"stack":["0x1","0x1"],"depth":1,"refund":0,"opName":"SSTORE"} +{"pc":36,"op":96,"gas":"0x6b1f6","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":38,"op":96,"gas":"0x6b1f3","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":40,"op":96,"gas":"0x6b1f0","gasCost":"0x3","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":42,"op":96,"gas":"0x6b1ed","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":44,"op":96,"gas":"0x6b1ea","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":46,"op":115,"gas":"0x6b1e7","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH20"} +{"pc":67,"op":90,"gas":"0x6b1e4","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000000000000000000000000000000000000002"],"depth":1,"refund":0,"opName":"GAS"} +{"pc":68,"op":241,"gas":"0x6b1e2","gasCost":"0x69744","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000000000000000000000000000000000000002","0x6b1e2"],"depth":1,"refund":0,"opName":"CALL"} +{"from":"0x8a0a19589531694250d570040a0c4b74576919b8","to":"0x1000000000000000000000000000000000000002","gas":"0x68d1c","value":"0x0","type":"CALL"} +{"pc":0,"op":96,"gas":"0x68d1c","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":2,"op":96,"gas":"0x68d19","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":4,"op":96,"gas":"0x68d16","gasCost":"0x3","memSize":0,"stack":["0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":6,"op":96,"gas":"0x68d13","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":8,"op":96,"gas":"0x68d10","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":10,"op":115,"gas":"0x68d0d","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x64"],"depth":2,"refund":0,"opName":"PUSH20"} +{"pc":31,"op":90,"gas":"0x68d0a","gasCost":"0x2","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x64","0x1000000000000000000000000000000000000001"],"depth":2,"refund":0,"opName":"GAS"} +{"pc":32,"op":241,"gas":"0x68d08","gasCost":"0x67363","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x64","0x1000000000000000000000000000000000000001","0x68d08"],"depth":2,"refund":0,"opName":"CALL"} +{"from":"0x1000000000000000000000000000000000000002","to":"0x1000000000000000000000000000000000000001","gas":"0x658d3","value":"0x64","type":"CALL"} +{"pc":0,"op":97,"gas":"0x658d3","gasCost":"0x3","memSize":0,"stack":[],"depth":3,"refund":0,"opName":"PUSH2"} +{"pc":3,"op":255,"gas":"0x658d0","gasCost":"0x1388","memSize":0,"stack":["0x3e8"],"depth":3,"refund":0,"opName":"SELFDESTRUCT"} +{"from":"0x1000000000000000000000000000000000000001","to":"0x00000000000000000000000000000000000003e8","gas":"0x0","value":"0x64","type":"SELFDESTRUCT"} +{"output":"","gasUsed":"0x0"} +{"output":"","gasUsed":"0x138b"} +{"pc":33,"op":96,"gas":"0x65eed","gasCost":"0x3","memSize":0,"stack":["0x1"],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":35,"op":1,"gas":"0x65eea","gasCost":"0x3","memSize":0,"stack":["0x1","0xf"],"depth":2,"refund":0,"opName":"ADD"} +{"pc":36,"op":96,"gas":"0x65ee7","gasCost":"0x3","memSize":0,"stack":["0x10"],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":38,"op":82,"gas":"0x65ee4","gasCost":"0x6","memSize":0,"stack":["0x10","0x0"],"depth":2,"refund":0,"opName":"MSTORE"} +{"pc":39,"op":96,"gas":"0x65ede","gasCost":"0x3","memSize":32,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":41,"op":96,"gas":"0x65edb","gasCost":"0x3","memSize":32,"stack":["0x20"],"depth":2,"refund":0,"opName":"PUSH1"} +{"pc":43,"op":253,"gas":"0x65ed8","gasCost":"0x0","memSize":32,"stack":["0x20","0x0"],"depth":2,"refund":0,"opName":"REVERT"} +{"pc":43,"op":253,"gas":"0x65ed8","gasCost":"0x0","memSize":32,"stack":[],"depth":2,"refund":0,"opName":"REVERT","error":"execution reverted"} +{"output":"0000000000000000000000000000000000000000000000000000000000000010","gasUsed":"0x2e44","error":"execution reverted"} +{"pc":69,"op":96,"gas":"0x67976","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"} +{"pc":71,"op":85,"gas":"0x67973","gasCost":"0x1388","memSize":0,"stack":["0x0","0x2"],"depth":1,"refund":4800,"opName":"SSTORE"} +{"pc":72,"op":61,"gas":"0x665eb","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":4800,"opName":"RETURNDATASIZE"} +{"pc":73,"op":96,"gas":"0x665e9","gasCost":"0x3","memSize":0,"stack":["0x20"],"depth":1,"refund":4800,"opName":"PUSH1"} +{"pc":75,"op":96,"gas":"0x665e6","gasCost":"0x3","memSize":0,"stack":["0x20","0x0"],"depth":1,"refund":4800,"opName":"PUSH1"} +{"pc":77,"op":62,"gas":"0x665e3","gasCost":"0x9","memSize":0,"stack":["0x20","0x0","0x0"],"depth":1,"refund":4800,"opName":"RETURNDATACOPY"} +{"pc":78,"op":96,"gas":"0x665da","gasCost":"0x3","memSize":32,"stack":[],"depth":1,"refund":4800,"opName":"PUSH1"} +{"pc":80,"op":81,"gas":"0x665d7","gasCost":"0x3","memSize":32,"stack":["0x0"],"depth":1,"refund":4800,"opName":"MLOAD"} +{"pc":81,"op":96,"gas":"0x665d4","gasCost":"0x3","memSize":32,"stack":["0x10"],"depth":1,"refund":4800,"opName":"PUSH1"} +{"pc":83,"op":85,"gas":"0x665d1","gasCost":"0x1388","memSize":32,"stack":["0x10","0x3"],"depth":1,"refund":4800,"opName":"SSTORE"} +{"pc":84,"op":0,"gas":"0x65249","gasCost":"0x0","memSize":32,"stack":[],"depth":1,"refund":4800,"opName":"STOP"} +{"output":"","gasUsed":"0xfccf"} diff --git a/cmd/evm/testdata/32/txs.json b/cmd/evm/testdata/32/txs.json new file mode 100644 index 0000000..0530fd6 --- /dev/null +++ b/cmd/evm/testdata/32/txs.json @@ -0,0 +1,17 @@ +[ + { + "type": "0x0", + "chainId": "0x0", + "nonce": "0x0", + "gasPrice": "0xa", + "gas": "0x7a120", + "to": "0x8a0a19589531694250d570040a0c4b74576919b8", + "value": "0x0", + "input": "0x", + "v": "0x1c", + "r": "0x9a207ad45b7fc2aa5f8e72a30487f2b0bc489778e6d022f19036efdf2a922a17", + "s": "0x640d4da05078b5a4aa561f1b4d58176ea828bfa0f88d27d14459c1d789e1a1eb", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/4/alloc.json b/cmd/evm/testdata/4/alloc.json new file mode 100644 index 0000000..fadf2bd --- /dev/null +++ b/cmd/evm/testdata/4/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x600340", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/4/env.json b/cmd/evm/testdata/4/env.json new file mode 100644 index 0000000..e283eff --- /dev/null +++ b/cmd/evm/testdata/4/env.json @@ -0,0 +1,8 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x05", + "currentTimestamp" : "0x03e8", + "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} +} \ No newline at end of file diff --git a/cmd/evm/testdata/4/readme.md b/cmd/evm/testdata/4/readme.md new file mode 100644 index 0000000..eede41a --- /dev/null +++ b/cmd/evm/testdata/4/readme.md @@ -0,0 +1,3 @@ +These files exemplify a transition where a transaction (executed on block 5) requests +the blockhash for block `4`, but where the hash for that block is missing. +It's expected that executing these should cause `exit` with errorcode `4`. diff --git a/cmd/evm/testdata/4/txs.json b/cmd/evm/testdata/4/txs.json new file mode 100644 index 0000000..3044458 --- /dev/null +++ b/cmd/evm/testdata/4/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/5/alloc.json b/cmd/evm/testdata/5/alloc.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/cmd/evm/testdata/5/alloc.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/cmd/evm/testdata/5/env.json b/cmd/evm/testdata/5/env.json new file mode 100644 index 0000000..1085f63 --- /dev/null +++ b/cmd/evm/testdata/5/env.json @@ -0,0 +1,11 @@ +{ + "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000", + "ommers": [ + {"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, + {"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } + ] +} \ No newline at end of file diff --git a/cmd/evm/testdata/5/exp.json b/cmd/evm/testdata/5/exp.json new file mode 100644 index 0000000..7d71567 --- /dev/null +++ b/cmd/evm/testdata/5/exp.json @@ -0,0 +1,23 @@ +{ + "alloc": { + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { + "balance": "0x88" + }, + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { + "balance": "0x70" + }, + "0xcccccccccccccccccccccccccccccccccccccccc": { + "balance": "0x60" + } + }, + "result": { + "stateRoot": "0xa7312add33811645c6aa65d928a1a4f49d65d448801912c069a0aa8fe9c1f393", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [], + "currentDifficulty": "0x20000", + "gasUsed": "0x0" + } +} diff --git a/cmd/evm/testdata/5/readme.md b/cmd/evm/testdata/5/readme.md new file mode 100644 index 0000000..1a84afa --- /dev/null +++ b/cmd/evm/testdata/5/readme.md @@ -0,0 +1 @@ +These files exemplify a transition where there are no transactions, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2). \ No newline at end of file diff --git a/cmd/evm/testdata/5/txs.json b/cmd/evm/testdata/5/txs.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/evm/testdata/5/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/7/alloc.json b/cmd/evm/testdata/7/alloc.json new file mode 100644 index 0000000..cef1a25 --- /dev/null +++ b/cmd/evm/testdata/7/alloc.json @@ -0,0 +1,12 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/7/env.json b/cmd/evm/testdata/7/env.json new file mode 100644 index 0000000..8fd9bc0 --- /dev/null +++ b/cmd/evm/testdata/7/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "5", + "currentTimestamp": "1000" +} \ No newline at end of file diff --git a/cmd/evm/testdata/7/readme.md b/cmd/evm/testdata/7/readme.md new file mode 100644 index 0000000..59e0dbe --- /dev/null +++ b/cmd/evm/testdata/7/readme.md @@ -0,0 +1,375 @@ +This is a test for HomesteadToDao, checking if the +DAO-transition works + +Example: +``` + ./evm t8n --input.alloc=./testdata/7/alloc.json --input.txs=./testdata/7/txs.json --input.env=./testdata/7/env.json --output.alloc=stdout --state.fork=HomesteadToDaoAt5 +INFO [03-09|10:47:37.255] Trie dumping started root=157847..2891b7 +INFO [03-09|10:47:37.256] Trie dumping complete accounts=120 elapsed="715.635µs" +INFO [03-09|10:47:37.256] Wrote file file=result.json +{ + "alloc": { + "0x005f5cee7a43331d5a3d3eec71305925a62f34b6": { + "balance": "0x0" + }, + "0x0101f3be8ebb4bbd39a2e3b9a3639d4259832fd9": { + "balance": "0x0" + }, + "0x057b56736d32b86616a10f619859c6cd6f59092a": { + "balance": "0x0" + }, + "0x06706dd3f2c9abf0a21ddcc6941d9b86f0596936": { + "balance": "0x0" + }, + "0x0737a6b837f97f46ebade41b9bc3e1c509c85c53": { + "balance": "0x0" + }, + "0x07f5c1e1bc2c93e0402f23341973a0e043f7bf8a": { + "balance": "0x0" + }, + "0x0e0da70933f4c7849fc0d203f5d1d43b9ae4532d": { + "balance": "0x0" + }, + "0x0ff30d6de14a8224aa97b78aea5388d1c51c1f00": { + "balance": "0x0" + }, + "0x12e626b0eebfe86a56d633b9864e389b45dcb260": { + "balance": "0x0" + }, + "0x1591fc0f688c81fbeb17f5426a162a7024d430c2": { + "balance": "0x0" + }, + "0x17802f43a0137c506ba92291391a8a8f207f487d": { + "balance": "0x0" + }, + "0x1975bd06d486162d5dc297798dfc41edd5d160a7": { + "balance": "0x0" + }, + "0x1ca6abd14d30affe533b24d7a21bff4c2d5e1f3b": { + "balance": "0x0" + }, + "0x1cba23d343a983e9b5cfd19496b9a9701ada385f": { + "balance": "0x0" + }, + "0x200450f06520bdd6c527622a273333384d870efb": { + "balance": "0x0" + }, + "0x21c7fdb9ed8d291d79ffd82eb2c4356ec0d81241": { + "balance": "0x0" + }, + "0x23b75c2f6791eef49c69684db4c6c1f93bf49a50": { + "balance": "0x0" + }, + "0x24c4d950dfd4dd1902bbed3508144a54542bba94": { + "balance": "0x0" + }, + "0x253488078a4edf4d6f42f113d1e62836a942cf1a": { + "balance": "0x0" + }, + "0x27b137a85656544b1ccb5a0f2e561a5703c6a68f": { + "balance": "0x0" + }, + "0x2a5ed960395e2a49b1c758cef4aa15213cfd874c": { + "balance": "0x0" + }, + "0x2b3455ec7fedf16e646268bf88846bd7a2319bb2": { + "balance": "0x0" + }, + "0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f": { + "balance": "0x0" + }, + "0x304a554a310c7e546dfe434669c62820b7d83490": { + "balance": "0x0" + }, + "0x319f70bab6845585f412ec7724b744fec6095c85": { + "balance": "0x0" + }, + "0x35a051a0010aba705c9008d7a7eff6fb88f6ea7b": { + "balance": "0x0" + }, + "0x3ba4d81db016dc2890c81f3acec2454bff5aada5": { + "balance": "0x0" + }, + "0x3c02a7bc0391e86d91b7d144e61c2c01a25a79c5": { + "balance": "0x0" + }, + "0x40b803a9abce16f50f36a77ba41180eb90023925": { + "balance": "0x0" + }, + "0x440c59b325d2997a134c2c7c60a8c61611212bad": { + "balance": "0x0" + }, + "0x4486a3d68fac6967006d7a517b889fd3f98c102b": { + "balance": "0x0" + }, + "0x4613f3bca5c44ea06337a9e439fbc6d42e501d0a": { + "balance": "0x0" + }, + "0x47e7aa56d6bdf3f36be34619660de61275420af8": { + "balance": "0x0" + }, + "0x4863226780fe7c0356454236d3b1c8792785748d": { + "balance": "0x0" + }, + "0x492ea3bb0f3315521c31f273e565b868fc090f17": { + "balance": "0x0" + }, + "0x4cb31628079fb14e4bc3cd5e30c2f7489b00960c": { + "balance": "0x0" + }, + "0x4deb0033bb26bc534b197e61d19e0733e5679784": { + "balance": "0x0" + }, + "0x4fa802324e929786dbda3b8820dc7834e9134a2a": { + "balance": "0x0" + }, + "0x4fd6ace747f06ece9c49699c7cabc62d02211f75": { + "balance": "0x0" + }, + "0x51e0ddd9998364a2eb38588679f0d2c42653e4a6": { + "balance": "0x0" + }, + "0x52c5317c848ba20c7504cb2c8052abd1fde29d03": { + "balance": "0x0" + }, + "0x542a9515200d14b68e934e9830d91645a980dd7a": { + "balance": "0x0" + }, + "0x5524c55fb03cf21f549444ccbecb664d0acad706": { + "balance": "0x0" + }, + "0x579a80d909f346fbfb1189493f521d7f48d52238": { + "balance": "0x0" + }, + "0x58b95c9a9d5d26825e70a82b6adb139d3fd829eb": { + "balance": "0x0" + }, + "0x5c6e67ccd5849c0d29219c4f95f1a7a93b3f5dc5": { + "balance": "0x0" + }, + "0x5c8536898fbb74fc7445814902fd08422eac56d0": { + "balance": "0x0" + }, + "0x5d2b2e6fcbe3b11d26b525e085ff818dae332479": { + "balance": "0x0" + }, + "0x5dc28b15dffed94048d73806ce4b7a4612a1d48f": { + "balance": "0x0" + }, + "0x5f9f3392e9f62f63b8eac0beb55541fc8627f42c": { + "balance": "0x0" + }, + "0x6131c42fa982e56929107413a9d526fd99405560": { + "balance": "0x0" + }, + "0x6231b6d0d5e77fe001c2a460bd9584fee60d409b": { + "balance": "0x0" + }, + "0x627a0a960c079c21c34f7612d5d230e01b4ad4c7": { + "balance": "0x0" + }, + "0x63ed5a272de2f6d968408b4acb9024f4cc208ebf": { + "balance": "0x0" + }, + "0x6966ab0d485353095148a2155858910e0965b6f9": { + "balance": "0x0" + }, + "0x6b0c4d41ba9ab8d8cfb5d379c69a612f2ced8ecb": { + "balance": "0x0" + }, + "0x6d87578288b6cb5549d5076a207456a1f6a63dc0": { + "balance": "0x0" + }, + "0x6f6704e5a10332af6672e50b3d9754dc460dfa4d": { + "balance": "0x0" + }, + "0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97": { + "balance": "0x0" + }, + "0x779543a0491a837ca36ce8c635d6154e3c4911a6": { + "balance": "0x0" + }, + "0x77ca7b50b6cd7e2f3fa008e24ab793fd56cb15f6": { + "balance": "0x0" + }, + "0x782495b7b3355efb2833d56ecb34dc22ad7dfcc4": { + "balance": "0x0" + }, + "0x807640a13483f8ac783c557fcdf27be11ea4ac7a": { + "balance": "0x0" + }, + "0x8163e7fb499e90f8544ea62bbf80d21cd26d9efd": { + "balance": "0x0" + }, + "0x84ef4b2357079cd7a7c69fd7a37cd0609a679106": { + "balance": "0x0" + }, + "0x86af3e9626fce1957c82e88cbf04ddf3a2ed7915": { + "balance": "0x0" + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeedbead" + }, + "0x8d9edb3054ce5c5774a420ac37ebae0ac02343c6": { + "balance": "0x0" + }, + "0x914d1b8b43e92723e64fd0a06f5bdb8dd9b10c79": { + "balance": "0x0" + }, + "0x97f43a37f595ab5dd318fb46e7a155eae057317a": { + "balance": "0x0" + }, + "0x9aa008f65de0b923a2a4f02012ad034a5e2e2192": { + "balance": "0x0" + }, + "0x9c15b54878ba618f494b38f0ae7443db6af648ba": { + "balance": "0x0" + }, + "0x9c50426be05db97f5d64fc54bf89eff947f0a321": { + "balance": "0x0" + }, + "0x9da397b9e80755301a3b32173283a91c0ef6c87e": { + "balance": "0x0" + }, + "0x9ea779f907f0b315b364b0cfc39a0fde5b02a416": { + "balance": "0x0" + }, + "0x9f27daea7aca0aa0446220b98d028715e3bc803d": { + "balance": "0x0" + }, + "0x9fcd2deaff372a39cc679d5c5e4de7bafb0b1339": { + "balance": "0x0" + }, + "0xa2f1ccba9395d7fcb155bba8bc92db9bafaeade7": { + "balance": "0x0" + }, + "0xa3acf3a1e16b1d7c315e23510fdd7847b48234f6": { + "balance": "0x0" + }, + "0xa5dc5acd6a7968a4554d89d65e59b7fd3bff0f90": { + "balance": "0x0" + }, + "0xa82f360a8d3455c5c41366975bde739c37bfeb8a": { + "balance": "0x0" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xac1ecab32727358dba8962a0f3b261731aad9723": { + "balance": "0x0" + }, + "0xaccc230e8a6e5be9160b8cdf2864dd2a001c28b6": { + "balance": "0x0" + }, + "0xacd87e28b0c9d1254e868b81cba4cc20d9a32225": { + "balance": "0x0" + }, + "0xadf80daec7ba8dcf15392f1ac611fff65d94f880": { + "balance": "0x0" + }, + "0xaeeb8ff27288bdabc0fa5ebb731b6f409507516c": { + "balance": "0x0" + }, + "0xb136707642a4ea12fb4bae820f03d2562ebff487": { + "balance": "0x0" + }, + "0xb2c6f0dfbb716ac562e2d85d6cb2f8d5ee87603e": { + "balance": "0x0" + }, + "0xb3fb0e5aba0e20e5c49d252dfd30e102b171a425": { + "balance": "0x0" + }, + "0xb52042c8ca3f8aa246fa79c3feaa3d959347c0ab": { + "balance": "0x0" + }, + "0xb9637156d330c0d605a791f1c31ba5890582fe1c": { + "balance": "0x0" + }, + "0xbb9bc244d798123fde783fcc1c72d3bb8c189413": { + "balance": "0x0" + }, + "0xbc07118b9ac290e4622f5e77a0853539789effbe": { + "balance": "0x0" + }, + "0xbcf899e6c7d9d5a215ab1e3444c86806fa854c76": { + "balance": "0x0" + }, + "0xbe8539bfe837b67d1282b2b1d61c3f723966f049": { + "balance": "0x0" + }, + "0xbf4ed7b27f1d666546e30d74d50d173d20bca754": { + "balance": "0x0" + }, + "0xc4bbd073882dd2add2424cf47d35213405b01324": { + "balance": "0x0" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0" + }, + "0xca544e5c4687d109611d0f8f928b53a25af72448": { + "balance": "0x0" + }, + "0xcbb9d3703e651b0d496cdefb8b92c25aeb2171f7": { + "balance": "0x0" + }, + "0xcc34673c6c40e791051898567a1222daf90be287": { + "balance": "0x0" + }, + "0xceaeb481747ca6c540a000c1f3641f8cef161fa7": { + "balance": "0x0" + }, + "0xd131637d5275fd1a68a3200f4ad25c71a2a9522e": { + "balance": "0x0" + }, + "0xd164b088bd9108b60d0ca3751da4bceb207b0782": { + "balance": "0x0" + }, + "0xd1ac8b1ef1b69ff51d1d401a476e7e612414f091": { + "balance": "0x0" + }, + "0xd343b217de44030afaa275f54d31a9317c7f441e": { + "balance": "0x0" + }, + "0xd4fe7bc31cedb7bfb8a345f31e668033056b2728": { + "balance": "0x0" + }, + "0xd9aef3a1e38a39c16b31d1ace71bca8ef58d315b": { + "balance": "0x0" + }, + "0xda2fef9e4a3230988ff17df2165440f37e8b1708": { + "balance": "0x0" + }, + "0xdbe9b615a3ae8709af8b93336ce9b477e4ac0940": { + "balance": "0x0" + }, + "0xe308bd1ac5fda103967359b2712dd89deffb7973": { + "balance": "0x0" + }, + "0xe4ae1efdfc53b73893af49113d8694a057b9c0d1": { + "balance": "0x0" + }, + "0xec8e57756626fdc07c63ad2eafbd28d08e7b0ca5": { + "balance": "0x0" + }, + "0xecd135fa4f61a655311e86238c92adcd779555d2": { + "balance": "0x0" + }, + "0xf0b1aa0eb660754448a7937c022e30aa692fe0c5": { + "balance": "0x0" + }, + "0xf1385fb24aad0cd7432824085e42aff90886fef5": { + "balance": "0x0" + }, + "0xf14c14075d6c4ed84b86798af0956deef67365b5": { + "balance": "0x0" + }, + "0xf4c64518ea10f995918a454158c6b61407ea345c": { + "balance": "0x0" + }, + "0xfe24cdd8648121a43a7c86d289be4dd2951ed49f": { + "balance": "0x0" + } + } +} +``` \ No newline at end of file diff --git a/cmd/evm/testdata/7/txs.json b/cmd/evm/testdata/7/txs.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/cmd/evm/testdata/7/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/8/alloc.json b/cmd/evm/testdata/8/alloc.json new file mode 100644 index 0000000..1d1b5f8 --- /dev/null +++ b/cmd/evm/testdata/8/alloc.json @@ -0,0 +1,11 @@ +{ + "0x000000000000000000000000000000000000aaaa": { + "balance": "0x03", + "code": "0x5854505854", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x100000", + "nonce": "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/8/env.json b/cmd/evm/testdata/8/env.json new file mode 100644 index 0000000..8b91934 --- /dev/null +++ b/cmd/evm/testdata/8/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x1000000000", + "currentNumber": "0x1000000", + "currentTimestamp": "0x04" +} \ No newline at end of file diff --git a/cmd/evm/testdata/8/readme.md b/cmd/evm/testdata/8/readme.md new file mode 100644 index 0000000..85aae18 --- /dev/null +++ b/cmd/evm/testdata/8/readme.md @@ -0,0 +1,59 @@ +## EIP-2930 testing + +This test contains testcases for EIP-2930, which uses transactions with access lists. + +### Prestate + +The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the +following code: `0x5854505854`: `PC ;SLOAD; POP; PC; SLOAD`. + +Essentially, this contract does `SLOAD(0)` and `SLOAD(3)`. + +The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. + +## Transactions + +There are three transactions, each invokes the contract above. + +1. ACL-transaction, which contains some non-used slots +2. Regular transaction +3. ACL-transaction, which contains the slots `1` and `3` in `0x000000000000000000000000000000000000aaaa` + +## Execution + +Running it yields: +``` +dir=./testdata/8 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --trace 2>/dev/null && cat trace-* | grep SLOAD +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +``` + +Similarly, we can provide the input transactions via `stdin` instead of as file: + +``` +$ dir=./testdata/8 \ + && cat $dir/txs.json | jq "{txs: .}" \ + | ./evm t8n --state.fork=Berlin \ + --input.alloc=$dir/alloc.json \ + --input.txs=stdin \ + --input.env=$dir/env.json \ + --trace \ + 2>/dev/null \ + && cat trace-* | grep SLOAD +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +``` + +If we try to execute it on older rules: +``` +$ dir=./testdata/8 && ./evm t8n --state.fork=Istanbul --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json +ERROR(10): failed signing transactions: ERROR(10): tx 0: failed to sign tx: transaction type not supported +``` diff --git a/cmd/evm/testdata/8/txs.json b/cmd/evm/testdata/8/txs.json new file mode 100644 index 0000000..35142ba --- /dev/null +++ b/cmd/evm/testdata/8/txs.json @@ -0,0 +1,58 @@ +[ + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "chainId": "0x1", + "input": "0x", + "nonce": "0x0", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x1", + "type" : "0x1", + "accessList": [ + {"address": "0x0000000000000000000000000000000000000000", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "input": "0x", + "nonce": "0x1", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x2", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "chainId": "0x1", + "input": "0x", + "nonce": "0x2", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x1", + "type" : "0x1", + "accessList": [ + {"address": "0x000000000000000000000000000000000000aaaa", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/evm/testdata/9/alloc.json b/cmd/evm/testdata/9/alloc.json new file mode 100644 index 0000000..c14e38e --- /dev/null +++ b/cmd/evm/testdata/9/alloc.json @@ -0,0 +1,11 @@ +{ + "0x000000000000000000000000000000000000aaaa": { + "balance": "0x03", + "code": "0x58585454", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x100000000000000", + "nonce": "0x00" + } +} diff --git a/cmd/evm/testdata/9/env.json b/cmd/evm/testdata/9/env.json new file mode 100644 index 0000000..082bff7 --- /dev/null +++ b/cmd/evm/testdata/9/env.json @@ -0,0 +1,8 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x1000000000", + "currentBaseFee": "0x3B9ACA00", + "currentNumber": "0x1000000", + "currentTimestamp": "0x04" +} diff --git a/cmd/evm/testdata/9/readme.md b/cmd/evm/testdata/9/readme.md new file mode 100644 index 0000000..357e200 --- /dev/null +++ b/cmd/evm/testdata/9/readme.md @@ -0,0 +1,79 @@ +## EIP-1559 testing + +This test contains testcases for EIP-1559, which uses a new transaction type and has a new block parameter. + +### Prestate + +The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the +following code: `0x58585454`: `PC; PC; SLOAD; SLOAD`. + +Essentially, this contract does `SLOAD(0)` and `SLOAD(1)`. + +The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. + +## Transactions + +There are two transactions, each invokes the contract above. + +1. EIP-1559 ACL-transaction, which contains the `0x0` slot for `0xaaaa` +2. Legacy transaction + +## Execution + +Running it yields: +``` +$ dir=./testdata/9 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --trace 2>/dev/null && cat trace-* | grep SLOAD +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":2,"op":84,"gas":"0x48c28","gasCost":"0x834","memSize":0,"stack":["0x0","0x1"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":3,"op":84,"gas":"0x483f4","gasCost":"0x64","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":2,"op":84,"gas":"0x49cf4","gasCost":"0x834","memSize":0,"stack":["0x0","0x1"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":3,"op":84,"gas":"0x494c0","gasCost":"0x834","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SLOAD"} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} +``` + +We can also get the post-alloc: +``` +$ dir=./testdata/9 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout 2>/dev/null +{ + "alloc": { + "0x000000000000000000000000000000000000aaaa": { + "code": "0x58585454", + "balance": "0x3", + "nonce": "0x1" + }, + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { + "balance": "0x5bb10ddef6e0" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xff745ee8832120", + "nonce": "0x2" + } + } +} +``` + +If we try to execute it on older rules: +``` +dir=./testdata/9 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout +ERROR(10): Failed signing transactions: ERROR(10): Tx 0: failed to sign tx: transaction type not supported +``` + +It fails, due to the `evm t8n` cannot sign them in with the given signer. We can bypass that, however, +by feeding it presigned transactions, located in `txs_signed.json`. + +``` +dir=./testdata/9 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs_signed.json --input.env=$dir/env.json +WARN [03-09|11:06:22.065] rejected tx index=0 hash=334e09..f8dce5 error="transaction type not supported" +INFO [03-09|11:06:22.066] rejected tx index=1 hash=a9c6c6..fa4036 from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="nonce too high: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B, tx: 1 state: 0" +INFO [03-09|11:06:22.066] Trie dumping started root=6eebe9..a0fda5 +INFO [03-09|11:06:22.066] Trie dumping complete accounts=2 elapsed="55.844µs" +INFO [03-09|11:06:22.066] Wrote file file=alloc.json +INFO [03-09|11:06:22.066] Wrote file file=result.json +``` + +Number `0` is not applicable, and therefore number `1` has wrong nonce, and both are rejected. + diff --git a/cmd/evm/testdata/9/txs.json b/cmd/evm/testdata/9/txs.json new file mode 100644 index 0000000..740abce --- /dev/null +++ b/cmd/evm/testdata/9/txs.json @@ -0,0 +1,37 @@ +[ + { + "gas": "0x4ef00", + "maxPriorityFeePerGas": "0x2", + "maxFeePerGas": "0x12A05F200", + "chainId": "0x1", + "input": "0x", + "nonce": "0x0", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x0", + "type" : "0x2", + "accessList": [ + {"address": "0x000000000000000000000000000000000000aaaa", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x12A05F200", + "chainId": "0x1", + "input": "0x", + "nonce": "0x1", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x0", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/evm/testdata/9/txs_signed.json b/cmd/evm/testdata/9/txs_signed.json new file mode 100644 index 0000000..dcddf01 --- /dev/null +++ b/cmd/evm/testdata/9/txs_signed.json @@ -0,0 +1,37 @@ +[ + { + "gas": "0x4ef00", + "maxFeePerGas": "0x2", + "maxPriorityFeePerGas": "0x12A05F200", + "chainId": "0x1", + "input": "0x", + "nonce": "0x0", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x0", + "type" : "0x2", + "accessList": [ + {"address": "0x000000000000000000000000000000000000aaaa", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "v": "0x1", + "r": "0xd77c8ff989789b5d9d99254cbae2e2996dc7e6215cba4d55254c14e6d6b9f314", + "s": "0x5cc021481e7e6bb444bbb87ab32071e8fd0a8d1e125c7bb352d2879bd7ff5c0a", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x12A05F200", + "chainId": "0x1", + "input": "0x", + "nonce": "0x1", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x0", + "v": "0x25", + "r": "0xbee5ec9f6650020266bf3455a852eece2b073a2fa918c4d1836a1af69c2aa50c", + "s": "0x556c897a58dbc007a6b09814e1fba7502adb76effd2146da4365816926f387ce", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/evm/transition-test.sh b/cmd/evm/transition-test.sh new file mode 100644 index 0000000..2ddda2d --- /dev/null +++ b/cmd/evm/transition-test.sh @@ -0,0 +1,518 @@ +#!/bin/bash +ticks="\`\`\`" + +function showjson(){ + echo "\`$1\`:" + echo "${ticks}json" + cat $1 + echo "" + echo "$ticks" +} +function demo(){ + echo "$ticks" + echo "$1" + $1 + echo "" + echo "$ticks" + echo "" +} +function tick(){ + echo "$ticks" +} + +function code(){ + echo "$ticks$1" +} + +cat << "EOF" +# EVM tool + +The EVM tool provides a few useful subcommands to facilitate testing at the EVM +layer. + +* transition tool (`t8n`) : a stateless state transition utility +* transaction tool (`t9n`) : a transaction validation utility +* block builder tool (`b11r`): a block assembler utility + +## State transition tool (`t8n`) + + +The `evm t8n` tool is a stateless state transition utility. It is a utility +which can + +1. Take a prestate, including + - Accounts, + - Block context information, + - Previous blockshashes (*optional) +2. Apply a set of transactions, +3. Apply a mining-reward (*optional), +4. And generate a post-state, including + - State root, transaction root, receipt root, + - Information about rejected transactions, + - Optionally: a full or partial post-state dump + +### Specification + +The idea is to specify the behaviour of this binary very _strict_, so that other +node implementors can build replicas based on their own state-machines, and the +state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based +implementation. + +#### Command line params + +Command line params that need to be supported are + +``` +EOF +./evm t8n -h | grep "\-\-trace\.\|\-\-output\.\|\-\-state\.\|\-\-input" +cat << "EOF" +``` +#### Objects + +The transition tool uses JSON objects to read and write data related to the transition operation. The +following object definitions are required. + +##### `alloc` + +The `alloc` object defines the prestate that transition will begin with. + +```go +// Map of address to account definition. +type Alloc map[common.Address]Account +// Genesis account. Each field is optional. +type Account struct { + Code []byte `json:"code"` + Storage map[common.Hash]common.Hash `json:"storage"` + Balance *big.Int `json:"balance"` + Nonce uint64 `json:"nonce"` + SecretKey []byte `json:"secretKey"` +} +``` + +##### `env` + +The `env` object defines the environmental context in which the transition will +take place. + +```go +type Env struct { + // required + CurrentCoinbase common.Address `json:"currentCoinbase"` + CurrentGasLimit uint64 `json:"currentGasLimit"` + CurrentNumber uint64 `json:"currentNumber"` + CurrentTimestamp uint64 `json:"currentTimestamp"` + Withdrawals []*Withdrawal `json:"withdrawals"` + // optional + CurrentDifficulty *big.Int `json:"currentDifficulty"` + CurrentRandom *big.Int `json:"currentRandom"` + CurrentBaseFee *big.Int `json:"currentBaseFee"` + ParentDifficulty *big.Int `json:"parentDifficulty"` + ParentGasUsed uint64 `json:"parentGasUsed"` + ParentGasLimit uint64 `json:"parentGasLimit"` + ParentTimestamp uint64 `json:"parentTimestamp"` + BlockHashes map[uint64]common.Hash `json:"blockHashes"` + ParentUncleHash common.Hash `json:"parentUncleHash"` + Ommers []Ommer `json:"ommers"` +} +type Ommer struct { + Delta uint64 `json:"delta"` + Address common.Address `json:"address"` +} +type Withdrawal struct { + Index uint64 `json:"index"` + ValidatorIndex uint64 `json:"validatorIndex"` + Recipient common.Address `json:"recipient"` + Amount *big.Int `json:"amount"` +} +``` + +##### `txs` + +The `txs` object is an array of any of the transaction types: `LegacyTx`, +`AccessListTx`, or `DynamicFeeTx`. + +```go +type LegacyTx struct { + Nonce uint64 `json:"nonce"` + GasPrice *big.Int `json:"gasPrice"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +type AccessList []AccessTuple +type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` +} +type AccessListTx struct { + ChainID *big.Int `json:"chainId"` + Nonce uint64 `json:"nonce"` + GasPrice *big.Int `json:"gasPrice"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + AccessList AccessList `json:"accessList"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +type DynamicFeeTx struct { + ChainID *big.Int `json:"chainId"` + Nonce uint64 `json:"nonce"` + GasTipCap *big.Int `json:"maxPriorityFeePerGas"` + GasFeeCap *big.Int `json:"maxFeePerGas"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *big.Int `json:"value"` + Data []byte `json:"data"` + AccessList AccessList `json:"accessList"` + V *big.Int `json:"v"` + R *big.Int `json:"r"` + S *big.Int `json:"s"` + SecretKey *common.Hash `json:"secretKey"` +} +``` + +##### `result` + +The `result` object is output after a transition is executed. It includes +information about the post-transition environment. + +```go +type ExecutionResult struct { + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptsRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom"` + Receipts types.Receipts `json:"receipts"` + Rejected []*rejectedTx `json:"rejected,omitempty"` + Difficulty *big.Int `json:"currentDifficulty"` + GasUsed uint64 `json:"gasUsed"` + BaseFee *big.Int `json:"currentBaseFee,omitempty"` +} +``` + +#### Error codes and output + +All logging should happen against the `stderr`. +There are a few (not many) errors that can occur, those are defined below. + +##### EVM-based errors (`2` to `9`) + +- Other EVM error. Exit code `2` +- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. +- Block history is not supplied, but needed for a `BLOCKHASH` operation. If `BLOCKHASH` + is invoked targeting a block which history has not been provided for, the program will + exit with code `4`. + +##### IO errors (`10`-`20`) + +- Invalid input json: the supplied data could not be marshalled. + The program will exit with code `10` +- IO problems: failure to load or save files, the program will exit with code `11` + +``` +# This should exit with 3 +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null +EOF +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null +exitcode=$? +if [ $exitcode != 3 ]; then + echo "Failed, exitcode should be 3,was $exitcode" +else + echo "exitcode:$exitcode OK" +fi +cat << "EOF" +``` +#### Forks +### Basic usage + +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). + +#### Examples +##### Basic usage + +Invoking it with the provided example files +EOF +cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin" +tick;echo "$cmd"; tick +$cmd 2>/dev/null +echo "Two resulting files:" +echo "" +showjson alloc.json +showjson result.json +echo "" + +echo "We can make them spit out the data to e.g. \`stdout\` like this:" +cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout --state.fork=Berlin" +tick;echo "$cmd"; tick +output=`$cmd 2>/dev/null` +echo "Output:" +echo "${ticks}json" +echo "$output" +echo "$ticks" + +cat << "EOF" + +#### About Ommers + +Mining rewards and ommer rewards might need to be added. This is how those are applied: + +- `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`. +- For each ommer (mined by `0xbb`), with blocknumber `N-delta` + - (where `delta` is the difference between the current block and the ommer) + - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` + - The account `0xaa` (block miner) is awarded `block_reward / 32` + +To make `t8n` apply these, the following inputs are required: + +- `--state.reward` + - For ethash, it is `5000000000000000000` `wei`, + - If this is not defined, mining rewards are not applied, + - A value of `0` is valid, and causes accounts to be 'touched'. +- For each ommer, the tool needs to be given an `address\` and a `delta`. This + is done via the `ommers` field in `env`. + +Note: the tool does not verify that e.g. the normal uncle rules apply, +and allows e.g two uncles at the same height, or the uncle-distance. This means that +the tool allows for negative uncle reward (distance > 8) + +Example: +EOF + +showjson ./testdata/5/env.json + +echo "When applying this, using a reward of \`0x08\`" +cmd="./evm t8n --input.alloc=./testdata/5/alloc.json -input.txs=./testdata/5/txs.json --input.env=./testdata/5/env.json --output.alloc=stdout --state.reward=0x80 --state.fork=Berlin" +output=`$cmd 2>/dev/null` +echo "Output:" +echo "${ticks}json" +echo "$output" +echo "$ticks" + +echo "#### Future EIPS" +echo "" +echo "It is also possible to experiment with future eips that are not yet defined in a hard fork." +echo "Example, putting EIP-1344 into Frontier: " +cmd="./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json" +tick;echo "$cmd"; tick +echo "" + +echo "#### Block history" +echo "" +echo "The \`BLOCKHASH\` opcode requires blockhashes to be provided by the caller, inside the \`env\`." +echo "If a required blockhash is not provided, the exit code should be \`4\`:" +echo "Example where blockhashes are provided: " +demo "./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace --state.fork=Berlin" +cmd="cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2" +tick && echo $cmd && tick +echo "$ticks" +cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 +echo "$ticks" +echo "" + +echo "In this example, the caller has not provided the required blockhash:" +cmd="./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace --state.fork=Berlin" +tick && echo $cmd && $cmd 2>&1 +errc=$? +tick +echo "Error code: $errc" +echo "" + +echo "#### Chaining" +echo "" +echo "Another thing that can be done, is to chain invocations:" +cmd1="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin --output.alloc=stdout" +cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json --state.fork=Berlin" +echo "$ticks" +echo "$cmd1 | $cmd2" +output=$($cmd1 | $cmd2 ) +echo $output +echo "$ticks" +echo "What happened here, is that we first applied two identical transactions, so the second one was rejected. " +echo "Then, taking the poststate alloc as the input for the next state, we tried again to include" +echo "the same two transactions: this time, both failed due to too low nonce." +echo "" +echo "In order to meaningfully chain invocations, one would need to provide meaningful new \`env\`, otherwise the" +echo "actual blocknumber (exposed to the EVM) would not increase." +echo "" + +echo "#### Transactions in RLP form" +echo "" +echo "It is possible to provide already-signed transactions as input to, using an \`input.txs\` which ends with the \`rlp\` suffix." +echo "The input format for RLP-form transactions is _identical_ to the _output_ format for block bodies. Therefore, it's fully possible" +echo "to use the evm to go from \`json\` input to \`rlp\` input." +echo "" +echo "The following command takes **json** the transactions in \`./testdata/13/txs.json\` and signs them. After execution, they are output to \`signed_txs.rlp\`.:" +cmd="./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./testdata/13/txs.json --input.env=./testdata/13/env.json --output.result=alloc_jsontx.json --output.body=signed_txs.rlp" +echo "$ticks" +echo $cmd +$cmd 2>&1 +echo "$ticks" +echo "" +echo "The \`output.body\` is the rlp-list of transactions, encoded in hex and placed in a string a'la \`json\` encoding rules:" +demo "cat signed_txs.rlp" +echo "We can use \`rlpdump\` to check what the contents are: " +echo "$ticks" +echo "rlpdump -hex \$(cat signed_txs.rlp | jq -r )" +rlpdump -hex $(cat signed_txs.rlp | jq -r ) +echo "$ticks" +echo "Now, we can now use those (or any other already signed transactions), as input, like so: " +cmd="./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./signed_txs.rlp --input.env=./testdata/13/env.json --output.result=alloc_rlptx.json" +echo "$ticks" +echo $cmd +$cmd 2>&1 +echo "$ticks" +echo "You might have noticed that the results from these two invocations were stored in two separate files. " +echo "And we can now finally check that they match." +echo "$ticks" +echo "cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot" +cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot +echo "$ticks" + +cat << "EOF" + +## Transaction tool + +The transaction tool is used to perform static validity checks on transactions such as: +* intrinsic gas calculation +* max values on integers +* fee semantics, such as `maxFeePerGas < maxPriorityFeePerGas` +* newer tx types on old forks + +### Examples + +EOF + +cmd="./evm t9n --state.fork Homestead --input.txs testdata/15/signed_txs.rlp" +tick;echo "$cmd"; +$cmd 2>/dev/null +tick + +cmd="./evm t9n --state.fork London --input.txs testdata/15/signed_txs.rlp" +tick;echo "$cmd"; +$cmd 2>/dev/null +tick + +cat << "EOF" +## Block builder tool (b11r) + +The `evm b11r` tool is used to assemble and seal full block rlps. + +### Specification + +#### Command line params + +Command line params that need to be supported are: + +``` + --input.header value `stdin` or file name of where to find the block header to use. (default: "header.json") + --input.ommers value `stdin` or file name of where to find the list of ommer header RLPs to use. + --input.txs value `stdin` or file name of where to find the transactions list in RLP form. (default: "txs.rlp") + --output.basedir value Specifies where output files are placed. Will be created if it does not exist. + --output.block value Determines where to put the alloc of the post-state. (default: "block.json") + - into the file + `stdout` - into the stdout output + `stderr` - into the stderr output + --seal.clique value Seal block with Clique. `stdin` or file name of where to find the Clique sealing data. + --seal.ethash Seal block with ethash. (default: false) + --seal.ethash.dir value Path to ethash DAG. If none exists, a new DAG will be generated. + --seal.ethash.mode value Defines the type and amount of PoW verification an ethash engine makes. (default: "normal") + --verbosity value Sets the verbosity level. (default: 3) +``` + +#### Objects + +##### `header` + +The `header` object is a consensus 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"` +} +``` +#### `ommers` + +The `ommers` object is a list of RLP-encoded ommer blocks in hex +representation. + +```go= +type Ommers []string +``` + +#### `txs` + +The `txs` object is a list of RLP-encoded transactions in hex representation. + +```go= +type Txs []string +``` + +#### `clique` + +The `clique` object provides the necessary information to complete a clique +seal of the block. + +```go= +var CliqueInfo struct { + Key *common.Hash `json:"secretKey"` + Voted *common.Address `json:"voted"` + Authorize *bool `json:"authorize"` + Vanity common.Hash `json:"vanity"` +} +``` + +#### `output` + +The `output` object contains two values, the block RLP and the block hash. + +```go= +type BlockInfo struct { + Rlp []byte `json:"rlp"` + Hash common.Hash `json:"hash"` +} +``` + +## A Note on Encoding + +The encoding of values for `evm` utility attempts to be relatively flexible. It +generally supports hex-encoded or decimal-encoded numeric values, and +hex-encoded byte values (like `common.Address`, `common.Hash`, etc). When in +doubt, the [`execution-apis`](https://github.com/ethereum/execution-apis) way +of encoding should always be accepted. + +## Testing + +There are many test cases in the [`cmd/evm/testdata`](./testdata) directory. +These fixtures are used to power the `t8n` tests in +[`t8n_test.go`](./t8n_test.go). The best way to verify correctness of new `evm` +implementations is to execute these and verify the output and error codes match +the expected values. + +EOF diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go new file mode 100644 index 0000000..cc22684 --- /dev/null +++ b/cmd/geth/accountcmd.go @@ -0,0 +1,384 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + + "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/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +var ( + walletCommand = &cli.Command{ + Name: "wallet", + Usage: "Manage Ethereum presale wallets", + ArgsUsage: "", + Description: ` + geth wallet import /path/to/my/presale.wallet + +will prompt for your password and imports your ether presale account. +It can be used non-interactively with the --password option taking a +passwordfile as argument containing the wallet password in plaintext.`, + Subcommands: []*cli.Command{ + { + + Name: "import", + Usage: "Import Ethereum presale wallet", + ArgsUsage: "", + Action: importWallet, + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + utils.PasswordFileFlag, + utils.LightKDFFlag, + }, + Description: ` + geth wallet [options] /path/to/my/presale.wallet + +will prompt for your password and imports your ether presale account. +It can be used non-interactively with the --password option taking a +passwordfile as argument containing the wallet password in plaintext.`, + }, + }, + } + + accountCommand = &cli.Command{ + Name: "account", + Usage: "Manage accounts", + Description: ` + +Manage accounts, list all existing accounts, import a private key into a new +account, create a new account or update an existing account. + +It supports interactive mode, when you are prompted for password as well as +non-interactive mode where passwords are supplied via a given password file. +Non-interactive mode is only meant for scripted use on test networks or known +safe environments. + +Make sure you remember the password you gave when creating a new account (with +either new or import). Without it you are not able to unlock your account. + +Note that exporting your key in unencrypted format is NOT supported. + +Keys are stored under /keystore. +It is safe to transfer the entire directory or the individual keys therein +between ethereum nodes by simply copying. + +Make sure you backup your keys regularly.`, + Subcommands: []*cli.Command{ + { + Name: "list", + Usage: "Print summary of existing accounts", + Action: accountList, + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + }, + Description: ` +Print a short summary of all accounts`, + }, + { + Name: "new", + Usage: "Create a new account", + Action: accountCreate, + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + utils.PasswordFileFlag, + utils.LightKDFFlag, + }, + Description: ` + geth account new + +Creates a new account and prints the address. + +The account is saved in encrypted format, you are prompted for a password. + +You must remember this password to unlock your account in the future. + +For non-interactive use the password can be specified with the --password flag: + +Note, this is meant to be used for testing only, it is a bad idea to save your +password to file or expose in any other way. +`, + }, + { + Name: "update", + Usage: "Update an existing account", + Action: accountUpdate, + ArgsUsage: "
", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + utils.LightKDFFlag, + }, + Description: ` + geth account update
+ +Update an existing account. + +The account is saved in the newest version in encrypted format, you are prompted +for a password to unlock the account and another to save the updated file. + +This same command can therefore be used to migrate an account of a deprecated +format to the newest format or change the password for an account. + +For non-interactive use the password can be specified with the --password flag: + + geth account update [options]
+ +Since only one password can be given, only format update can be performed, +changing your password is only possible interactively. +`, + }, + { + Name: "import", + Usage: "Import a private key into a new account", + Action: accountImport, + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + utils.PasswordFileFlag, + utils.LightKDFFlag, + }, + ArgsUsage: "", + Description: ` + geth account import + +Imports an unencrypted private key from and creates a new account. +Prints the address. + +The keyfile is assumed to contain an unencrypted private key in hexadecimal format. + +The account is saved in encrypted format, you are prompted for a password. + +You must remember this password to unlock your account in the future. + +For non-interactive use the password can be specified with the -password flag: + + geth account import [options] + +Note: +As you can directly copy your encrypted accounts to another ethereum instance, +this import mechanism is not needed when you transfer an account between +nodes. +`, + }, + }, + } +) + +// makeAccountManager creates an account manager with backends +func makeAccountManager(ctx *cli.Context) *accounts.Manager { + cfg := loadBaseConfig(ctx) + am := accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: cfg.Node.InsecureUnlockAllowed}) + keydir, isEphemeral, err := cfg.Node.GetKeyStoreDir() + if err != nil { + utils.Fatalf("Failed to get the keystore directory: %v", err) + } + if isEphemeral { + utils.Fatalf("Can't use ephemeral directory as keystore path") + } + + if err := setAccountManagerBackends(&cfg.Node, am, keydir); err != nil { + utils.Fatalf("Failed to set account manager backends: %v", err) + } + return am +} + +func accountList(ctx *cli.Context) error { + am := makeAccountManager(ctx) + var index int + for _, wallet := range am.Wallets() { + for _, account := range wallet.Accounts() { + fmt.Printf("Account #%d: {%x} %s\n", index, account.Address, &account.URL) + index++ + } + } + + return nil +} + +// tries unlocking the specified account a few times. +func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) { + account, err := utils.MakeAddress(ks, address) + if err != nil { + utils.Fatalf("Could not list accounts: %v", err) + } + for trials := 0; trials < 3; trials++ { + prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) + password := utils.GetPassPhraseWithList(prompt, false, i, passwords) + err = ks.Unlock(account, password) + if err == nil { + log.Info("Unlocked account", "address", account.Address.Hex()) + return account, password + } + if err, ok := err.(*keystore.AmbiguousAddrError); ok { + log.Info("Unlocked account", "address", account.Address.Hex()) + return ambiguousAddrRecovery(ks, err, password), password + } + if err != keystore.ErrDecrypt { + // No need to prompt again if the error is not decryption-related. + break + } + } + // All trials expended to unlock account, bail out + utils.Fatalf("Failed to unlock account %s (%v)", address, err) + + return accounts.Account{}, "" +} + +func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { + fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) + for _, a := range err.Matches { + fmt.Println(" ", a.URL) + } + fmt.Println("Testing your password against all of them...") + var match *accounts.Account + for i, a := range err.Matches { + if e := ks.Unlock(a, auth); e == nil { + match = &err.Matches[i] + break + } + } + if match == nil { + utils.Fatalf("None of the listed files could be unlocked.") + return accounts.Account{} + } + fmt.Printf("Your password unlocked %s\n", match.URL) + fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") + for _, a := range err.Matches { + if a != *match { + fmt.Println(" ", a.URL) + } + } + return *match +} + +// accountCreate creates a new account into the keystore defined by the CLI flags. +func accountCreate(ctx *cli.Context) error { + cfg := loadBaseConfig(ctx) + keydir, isEphemeral, err := cfg.Node.GetKeyStoreDir() + if err != nil { + utils.Fatalf("Failed to get the keystore directory: %v", err) + } + if isEphemeral { + utils.Fatalf("Can't use ephemeral directory as keystore path") + } + scryptN := keystore.StandardScryptN + scryptP := keystore.StandardScryptP + if cfg.Node.UseLightweightKDF { + scryptN = keystore.LightScryptN + scryptP = keystore.LightScryptP + } + + password := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + + account, err := keystore.StoreKey(keydir, password, scryptN, scryptP) + + if err != nil { + utils.Fatalf("Failed to create account: %v", err) + } + fmt.Printf("\nYour new key was generated\n\n") + fmt.Printf("Public address of the key: %s\n", account.Address.Hex()) + fmt.Printf("Path of the secret key file: %s\n\n", account.URL.Path) + fmt.Printf("- You can share your public address with anyone. Others need it to interact with you.\n") + fmt.Printf("- You must NEVER share the secret key with anyone! The key controls access to your funds!\n") + fmt.Printf("- You must BACKUP your key file! Without the key, it's impossible to access account funds!\n") + fmt.Printf("- You must REMEMBER your password! Without the password, it's impossible to decrypt the key!\n\n") + return nil +} + +// accountUpdate transitions an account from a previous format to the current +// one, also providing the possibility to change the pass-phrase. +func accountUpdate(ctx *cli.Context) error { + if ctx.Args().Len() == 0 { + utils.Fatalf("No accounts specified to update") + } + am := makeAccountManager(ctx) + backends := am.Backends(keystore.KeyStoreType) + if len(backends) == 0 { + utils.Fatalf("Keystore is not available") + } + ks := backends[0].(*keystore.KeyStore) + + for _, addr := range ctx.Args().Slice() { + account, oldPassword := unlockAccount(ks, addr, 0, nil) + newPassword := utils.GetPassPhraseWithList("Please give a new password. Do not forget this password.", true, 0, nil) + if err := ks.Update(account, oldPassword, newPassword); err != nil { + utils.Fatalf("Could not update the account: %v", err) + } + } + return nil +} + +func importWallet(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + utils.Fatalf("keyfile must be given as the only argument") + } + keyfile := ctx.Args().First() + keyJSON, err := os.ReadFile(keyfile) + if err != nil { + utils.Fatalf("Could not read wallet file: %v", err) + } + + am := makeAccountManager(ctx) + backends := am.Backends(keystore.KeyStoreType) + if len(backends) == 0 { + utils.Fatalf("Keystore is not available") + } + ks := backends[0].(*keystore.KeyStore) + passphrase := utils.GetPassPhraseWithList("", false, 0, utils.MakePasswordList(ctx)) + + acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) + if err != nil { + utils.Fatalf("%v", err) + } + fmt.Printf("Address: {%x}\n", acct.Address) + return nil +} + +func accountImport(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + utils.Fatalf("keyfile must be given as the only argument") + } + keyfile := ctx.Args().First() + key, err := crypto.LoadECDSA(keyfile) + if err != nil { + utils.Fatalf("Failed to load the private key: %v", err) + } + am := makeAccountManager(ctx) + backends := am.Backends(keystore.KeyStoreType) + if len(backends) == 0 { + utils.Fatalf("Keystore is not available") + } + ks := backends[0].(*keystore.KeyStore) + passphrase := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + + acct, err := ks.ImportECDSA(key, passphrase) + if err != nil { + utils.Fatalf("Could not create the account: %v", err) + } + fmt.Printf("Address: {%x}\n", acct.Address) + return nil +} diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go new file mode 100644 index 0000000..ea3a7c3 --- /dev/null +++ b/cmd/geth/accountcmd_test.go @@ -0,0 +1,378 @@ +// Copyright 2016 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 ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/cespare/cp" +) + +// These tests are 'smoke tests' for the account related +// subcommands and flags. +// +// For most tests, the test files from package accounts +// are copied into a temporary keystore directory. + +func tmpDatadirWithKeystore(t *testing.T) string { + datadir := t.TempDir() + keystore := filepath.Join(datadir, "keystore") + source := filepath.Join("..", "..", "accounts", "keystore", "testdata", "keystore") + if err := cp.CopyAll(keystore, source); err != nil { + t.Fatal(err) + } + return datadir +} + +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 +Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}/keystore/aaa +Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/keystore/zzz +` + if runtime.GOOS == "windows" { + want = ` +Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}\keystore\UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 +Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}\keystore\aaa +Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}\keystore\zzz +` + } + { + geth := runGeth(t, "account", "list", "--datadir", datadir) + geth.Expect(want) + geth.ExpectExit() + } + { + geth := runGeth(t, "--datadir", datadir, "account", "list") + geth.Expect(want) + geth.ExpectExit() + } +} + +func TestAccountNew(t *testing.T) { + t.Parallel() + geth := runGeth(t, "account", "new", "--lightkdf") + defer geth.ExpectExit() + geth.Expect(` +Your new account is locked with a password. Please give a password. Do not forget this password. +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "foobar"}} +Repeat password: {{.InputLine "foobar"}} + +Your new key was generated +`) + geth.ExpectRegexp(` +Public address of the key: 0x[0-9a-fA-F]{40} +Path of the secret key file: .*UTC--.+--[0-9a-f]{40} + +- You can share your public address with anyone. Others need it to interact with you. +- You must NEVER share the secret key with anyone! The key controls access to your funds! +- You must BACKUP your key file! Without the key, it's impossible to access account funds! +- You must REMEMBER your password! Without the password, it's impossible to decrypt the key! +`) +} + +func TestAccountImport(t *testing.T) { + t.Parallel() + tests := []struct{ name, key, output string }{ + { + name: "correct account", + key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + output: "Address: {fcad0b19bb29d4674531d6f115237e16afce377c}\n", + }, + { + name: "invalid character", + key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1", + output: "Fatal: Failed to load the private key: invalid character '1' at end of key file\n", + }, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + importAccountWithExpect(t, test.key, test.output) + }) + } +} + +func TestAccountHelp(t *testing.T) { + t.Parallel() + geth := runGeth(t, "account", "-h") + geth.WaitExit() + if have, want := geth.ExitStatus(), 0; have != want { + t.Errorf("exit error, have %d want %d", have, want) + } + + geth = runGeth(t, "account", "import", "-h") + geth.WaitExit() + if have, want := geth.ExitStatus(), 0; have != want { + t.Errorf("exit error, have %d want %d", have, want) + } +} + +func importAccountWithExpect(t *testing.T, key string, expected string) { + dir := t.TempDir() + keyfile := filepath.Join(dir, "key.prv") + if err := os.WriteFile(keyfile, []byte(key), 0600); err != nil { + t.Error(err) + } + passwordFile := filepath.Join(dir, "password.txt") + if err := os.WriteFile(passwordFile, []byte("foobar"), 0600); err != nil { + t.Error(err) + } + geth := runGeth(t, "--lightkdf", "account", "import", "-password", passwordFile, keyfile) + defer geth.ExpectExit() + geth.Expect(expected) +} + +func TestAccountNewBadRepeat(t *testing.T) { + t.Parallel() + geth := runGeth(t, "account", "new", "--lightkdf") + defer geth.ExpectExit() + geth.Expect(` +Your new account is locked with a password. Please give a password. Do not forget this password. +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "something"}} +Repeat password: {{.InputLine "something else"}} +Fatal: Passwords do not match +`) +} + +func TestAccountUpdate(t *testing.T) { + t.Parallel() + datadir := tmpDatadirWithKeystore(t) + geth := runGeth(t, "account", "update", + "--datadir", datadir, "--lightkdf", + "f466859ead1932d743d622cb74fc058882e8648a") + defer geth.ExpectExit() + geth.Expect(` +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "foobar"}} +Please give a new password. Do not forget this password. +Password: {{.InputLine "foobar2"}} +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(` +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "foo"}} +Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} +`) + + files, err := os.ReadDir(filepath.Join(geth.Datadir, "keystore")) + if len(files) != 1 { + t.Errorf("expected one key file in keystore directory, found %d files (error: %v)", len(files), err) + } +} + +func TestWalletImportBadPassword(t *testing.T) { + t.Parallel() + geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") + defer geth.ExpectExit() + geth.Expect(` +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "wrong"}} +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(` +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "foobar"}} +undefined +`) + geth.ExpectExit() + + wantMessages := []string{ + "Unlocked account", + "=0xf466859eAD1932D743d622CB74FC058882E8648A", + } + for _, m := range wantMessages { + if !strings.Contains(geth.StderrText(), m) { + t.Errorf("stderr text does not contain %q", m) + } + } +} + +func TestUnlockFlagWrongPassword(t *testing.T) { + t.Parallel() + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "console", "--exec", "loadScript('testdata/empty.js')") + + defer geth.ExpectExit() + geth.Expect(` +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "wrong1"}} +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 2/3 +Password: {{.InputLine "wrong2"}} +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 3/3 +Password: {{.InputLine "wrong3"}} +Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could not decrypt key with given password) +`) +} + +// 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')") + + geth.Expect(` +Unlocking account 0 | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "foobar"}} +Unlocking account 2 | Attempt 1/3 +Password: {{.InputLine "foobar"}} +undefined +`) + geth.ExpectExit() + + wantMessages := []string{ + "Unlocked account", + "=0x7EF5A6135f1FD6a02593eEdC869c6D41D934aef8", + "=0x289d485D9771714CCe91D3393D764E1311907ACc", + } + for _, m := range wantMessages { + if !strings.Contains(geth.StderrText(), m) { + t.Errorf("stderr text does not contain %q", m) + } + } +} + +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')") + + geth.Expect(` +undefined +`) + geth.ExpectExit() + + wantMessages := []string{ + "Unlocked account", + "=0x7EF5A6135f1FD6a02593eEdC869c6D41D934aef8", + "=0x289d485D9771714CCe91D3393D764E1311907ACc", + } + for _, m := range wantMessages { + if !strings.Contains(geth.StderrText(), m) { + t.Errorf("stderr text does not contain %q", m) + } + } +} + +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") + defer geth.ExpectExit() + geth.Expect(` +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", + store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + "console", "--exec", "loadScript('testdata/empty.js')") + defer geth.ExpectExit() + + // Helper for the expect template, returns absolute keystore path. + geth.SetTemplateFunc("keypath", func(file string) string { + abs, _ := filepath.Abs(filepath.Join(store, file)) + return abs + }) + geth.Expect(` +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "foobar"}} +Multiple key files exist for address f466859ead1932d743d622cb74fc058882e8648a: + keystore://{{keypath "1"}} + keystore://{{keypath "2"}} +Testing your password against all of them... +Your password unlocked keystore://{{keypath "1"}} +In order to avoid this warning, you need to remove the following duplicate key files: + keystore://{{keypath "2"}} +undefined +`) + geth.ExpectExit() + + wantMessages := []string{ + "Unlocked account", + "=0xf466859eAD1932D743d622CB74FC058882E8648A", + } + for _, m := range wantMessages { + if !strings.Contains(geth.StderrText(), m) { + t.Errorf("stderr text does not contain %q", m) + } + } +} + +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", + store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + + defer geth.ExpectExit() + + // Helper for the expect template, returns absolute keystore path. + geth.SetTemplateFunc("keypath", func(file string) string { + abs, _ := filepath.Abs(filepath.Join(store, file)) + return abs + }) + geth.Expect(` +Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 +!! Unsupported terminal, password will be echoed. +Password: {{.InputLine "wrong"}} +Multiple key files exist for address f466859ead1932d743d622cb74fc058882e8648a: + keystore://{{keypath "1"}} + keystore://{{keypath "2"}} +Testing your password against all of them... +Fatal: None of the listed files could be unlocked. +`) + geth.ExpectExit() +} diff --git a/cmd/geth/attach_test.go b/cmd/geth/attach_test.go new file mode 100644 index 0000000..ceae3a1 --- /dev/null +++ b/cmd/geth/attach_test.go @@ -0,0 +1,83 @@ +// 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 main + +import ( + "fmt" + "net" + "net/http" + "sync/atomic" + "testing" +) + +type testHandler struct { + body func(http.ResponseWriter, *http.Request) +} + +func (t *testHandler) ServeHTTP(out http.ResponseWriter, in *http.Request) { + t.body(out, in) +} + +// TestAttachWithHeaders tests that 'geth attach' with custom headers works, i.e +// that custom headers are forwarded to the target. +func TestAttachWithHeaders(t *testing.T) { + t.Parallel() + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + } + port := ln.Addr().(*net.TCPAddr).Port + testReceiveHeaders(t, ln, "attach", "-H", "first: one", "-H", "second: two", fmt.Sprintf("http://localhost:%d", port)) + // This way to do it fails due to flag ordering: + // + // testReceiveHeaders(t, ln, "-H", "first: one", "-H", "second: two", "attach", fmt.Sprintf("http://localhost:%d", port)) + // This is fixed in a follow-up PR. +} + +// TestRemoteDbWithHeaders tests that 'geth db --remotedb' with custom headers works, i.e +// that custom headers are forwarded to the target. +func TestRemoteDbWithHeaders(t *testing.T) { + t.Parallel() + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + } + port := ln.Addr().(*net.TCPAddr).Port + testReceiveHeaders(t, ln, "db", "metadata", "--remotedb", fmt.Sprintf("http://localhost:%d", port), "-H", "first: one", "-H", "second: two") +} + +func testReceiveHeaders(t *testing.T, ln net.Listener, gethArgs ...string) { + var ok atomic.Uint32 + server := &http.Server{ + Addr: "localhost:0", + Handler: &testHandler{func(w http.ResponseWriter, r *http.Request) { + // We expect two headers + if have, want := r.Header.Get("first"), "one"; have != want { + t.Fatalf("missing header, have %v want %v", have, want) + } + if have, want := r.Header.Get("second"), "two"; have != want { + t.Fatalf("missing header, have %v want %v", have, want) + } + ok.Store(1) + }}} + go server.Serve(ln) + defer server.Close() + runGeth(t, gethArgs...).WaitExit() + if ok.Load() != 1 { + t.Fatal("Test fail, expected invocation to succeed") + } +} diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go new file mode 100644 index 0000000..9450c09 --- /dev/null +++ b/cmd/geth/chaincmd.go @@ -0,0 +1,605 @@ +// Copyright 2015 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 ( + "encoding/json" + "errors" + "fmt" + "os" + "runtime" + "strconv" + "sync/atomic" + "time" + + "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/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/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" + "github.com/urfave/cli/v2" +) + +var ( + initCommand = &cli.Command{ + Action: initGenesis, + Name: "init", + Usage: "Bootstrap and initialize a new genesis block", + ArgsUsage: "", + 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 +participating. + +It expects the genesis file as argument.`, + } + dumpGenesisCommand = &cli.Command{ + Action: dumpGenesis, + Name: "dumpgenesis", + Usage: "Dumps genesis block JSON configuration to stdout", + ArgsUsage: "", + Flags: append([]cli.Flag{utils.DataDirFlag}, utils.NetworkFlags...), + Description: ` +The dumpgenesis command prints the genesis configuration of the network preset +if one is set. Otherwise it prints the genesis from the datadir.`, + } + importCommand = &cli.Command{ + Action: importChain, + Name: "import", + Usage: "Import a blockchain file", + ArgsUsage: " ( ... ) ", + Flags: flags.Merge([]cli.Flag{ + utils.CacheFlag, + utils.SyncModeFlag, + utils.GCModeFlag, + utils.SnapshotFlag, + utils.CacheDatabaseFlag, + utils.CacheGCFlag, + utils.MetricsEnabledFlag, + utils.MetricsEnabledExpensiveFlag, + utils.MetricsHTTPFlag, + utils.MetricsPortFlag, + utils.MetricsEnableInfluxDBFlag, + utils.MetricsEnableInfluxDBV2Flag, + utils.MetricsInfluxDBEndpointFlag, + utils.MetricsInfluxDBDatabaseFlag, + utils.MetricsInfluxDBUsernameFlag, + utils.MetricsInfluxDBPasswordFlag, + utils.MetricsInfluxDBTagsFlag, + utils.MetricsInfluxDBTokenFlag, + utils.MetricsInfluxDBBucketFlag, + utils.MetricsInfluxDBOrganizationFlag, + utils.TxLookupLimitFlag, + utils.VMTraceFlag, + utils.VMTraceJsonConfigFlag, + utils.TransactionHistoryFlag, + utils.StateHistoryFlag, + }, utils.DatabaseFlags), + Description: ` +The import command imports blocks from an RLP-encoded form. The form can be one file +with several RLP-encoded blocks, or several files can be used. + +If only one file is used, import error will result in failure. If several files are used, +processing will proceed even if an individual RLP-file import failure occurs.`, + } + exportCommand = &cli.Command{ + Action: exportChain, + Name: "export", + Usage: "Export blockchain into file", + ArgsUsage: " [ ]", + Flags: flags.Merge([]cli.Flag{ + utils.CacheFlag, + utils.SyncModeFlag, + }, utils.DatabaseFlags), + Description: ` +Requires a first argument of the file to write to. +Optional second and third arguments control the first and +last block to write. In this mode, the file will be appended +if already existing. If the file ends with .gz, the output will +be gzipped.`, + } + importHistoryCommand = &cli.Command{ + Action: importHistory, + Name: "import-history", + Usage: "Import an Era archive", + ArgsUsage: "", + Flags: flags.Merge([]cli.Flag{ + utils.TxLookupLimitFlag, + }, + utils.DatabaseFlags, + utils.NetworkFlags, + ), + Description: ` +The import-history command will import blocks and their corresponding receipts +from Era archives. +`, + } + exportHistoryCommand = &cli.Command{ + Action: exportHistory, + Name: "export-history", + Usage: "Export blockchain history to Era archives", + ArgsUsage: " ", + Flags: flags.Merge(utils.DatabaseFlags), + Description: ` +The export-history command will export blocks and their corresponding receipts +into Era archives. Eras are typically packaged in steps of 8192 blocks. +`, + } + importPreimagesCommand = &cli.Command{ + Action: importPreimages, + Name: "import-preimages", + Usage: "Import the preimage database from an RLP stream", + ArgsUsage: "", + Flags: flags.Merge([]cli.Flag{ + utils.CacheFlag, + utils.SyncModeFlag, + }, utils.DatabaseFlags), + Description: ` +The import-preimages command imports hash preimages from an RLP encoded stream. +It's deprecated, please use "geth db import" instead. +`, + } + + dumpCommand = &cli.Command{ + Action: dump, + Name: "dump", + Usage: "Dump a specific block from storage", + ArgsUsage: "[? | ]", + Flags: flags.Merge([]cli.Flag{ + utils.CacheFlag, + utils.IterativeOutputFlag, + utils.ExcludeCodeFlag, + utils.ExcludeStorageFlag, + utils.IncludeIncompletesFlag, + utils.StartKeyFlag, + utils.DumpLimitFlag, + }, utils.DatabaseFlags), + Description: ` +This command dumps out the state for a given block (or latest, if none provided). +`, + } +) + +// initGenesis will initialise the given JSON format genesis file and writes it as +// the zero'd block (i.e. genesis) or will fail hard if it can't succeed. +func initGenesis(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + utils.Fatalf("need genesis.json file as the only argument") + } + genesisPath := ctx.Args().First() + if len(genesisPath) == 0 { + utils.Fatalf("invalid path to genesis file") + } + file, err := os.Open(genesisPath) + if err != nil { + utils.Fatalf("Failed to read genesis file: %v", err) + } + defer file.Close() + + genesis := new(core.Genesis) + if err := json.NewDecoder(file).Decode(genesis); err != nil { + utils.Fatalf("invalid genesis file: %v", err) + } + // Open and initialise both full and light databases + 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) + } + 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) + } + log.Info("Successfully wrote genesis state", "database", name, "hash", hash) + } + return nil +} + +func dumpGenesis(ctx *cli.Context) error { + // check if there is a testnet preset enabled + var genesis *core.Genesis + if utils.IsNetworkPreset(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"} { + db, err := stack.OpenDatabase(name, 0, 0, "", true) + if err != nil { + if !os.IsNotExist(err) { + return err + } + continue + } + genesis, err := core.ReadGenesis(db) + if err != nil { + utils.Fatalf("failed to read genesis: %s", err) + } + db.Close() + + if err := json.NewEncoder(os.Stdout).Encode(*genesis); err != nil { + utils.Fatalf("could not encode stored genesis: %s", err) + } + return nil + } + if ctx.IsSet(utils.DataDirFlag.Name) { + utils.Fatalf("no existing datadir at %s", stack.Config().DataDir) + } + utils.Fatalf("no network preset provided, and no genesis exists in the default datadir") + return nil +} + +func importChain(ctx *cli.Context) error { + if ctx.Args().Len() < 1 { + utils.Fatalf("This command requires an argument.") + } + // Start metrics export if enabled + utils.SetupMetrics(ctx) + // Start system runtime metrics collection + go metrics.CollectProcessMetrics(3 * time.Second) + + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, db := utils.MakeChain(ctx, stack, false) + defer db.Close() + + // Start periodically gathering memory profiles + var peakMemAlloc, peakMemSys atomic.Uint64 + go func() { + stats := new(runtime.MemStats) + for { + runtime.ReadMemStats(stats) + if peakMemAlloc.Load() < stats.Alloc { + peakMemAlloc.Store(stats.Alloc) + } + if peakMemSys.Load() < stats.Sys { + peakMemSys.Store(stats.Sys) + } + time.Sleep(5 * time.Second) + } + }() + // Import the chain + start := time.Now() + + var importErr error + + if ctx.Args().Len() == 1 { + if err := utils.ImportChain(chain, ctx.Args().First()); err != nil { + importErr = err + log.Error("Import error", "err", err) + } + } else { + for _, arg := range ctx.Args().Slice() { + if err := utils.ImportChain(chain, arg); err != nil { + importErr = err + log.Error("Import error", "file", arg, "err", err) + } + } + } + chain.Stop() + fmt.Printf("Import done in %v.\n\n", time.Since(start)) + + // Output pre-compaction stats mostly to see the import trashing + showDBStats(db) + + // Print the memory statistics used by the importing + mem := new(runtime.MemStats) + runtime.ReadMemStats(mem) + + fmt.Printf("Object memory: %.3f MB current, %.3f MB peak\n", float64(mem.Alloc)/1024/1024, float64(peakMemAlloc.Load())/1024/1024) + fmt.Printf("System memory: %.3f MB current, %.3f MB peak\n", float64(mem.Sys)/1024/1024, float64(peakMemSys.Load())/1024/1024) + fmt.Printf("Allocations: %.3f million\n", float64(mem.Mallocs)/1000000) + fmt.Printf("GC pause: %v\n\n", time.Duration(mem.PauseTotalNs)) + + if ctx.Bool(utils.NoCompactionFlag.Name) { + return nil + } + + // Compact the entire database to more accurately measure disk io and print the stats + start = time.Now() + fmt.Println("Compacting entire database...") + if err := db.Compact(nil, nil); err != nil { + utils.Fatalf("Compaction failed: %v", err) + } + fmt.Printf("Compaction done in %v.\n\n", time.Since(start)) + + showDBStats(db) + return importErr +} + +func exportChain(ctx *cli.Context) error { + if ctx.Args().Len() < 1 { + utils.Fatalf("This command requires an argument.") + } + + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, db := utils.MakeChain(ctx, stack, true) + defer db.Close() + start := time.Now() + + var err error + fp := ctx.Args().First() + if ctx.Args().Len() < 3 { + err = utils.ExportChain(chain, fp) + } else { + // This can be improved to allow for numbers larger than 9223372036854775807 + first, ferr := strconv.ParseInt(ctx.Args().Get(1), 10, 64) + last, lerr := strconv.ParseInt(ctx.Args().Get(2), 10, 64) + if ferr != nil || lerr != nil { + utils.Fatalf("Export error in parsing parameters: block number not an integer\n") + } + if first < 0 || last < 0 { + utils.Fatalf("Export error: block number must be greater than 0\n") + } + if head := chain.CurrentSnapBlock(); uint64(last) > head.Number.Uint64() { + utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.Number.Uint64()) + } + err = utils.ExportAppendChain(chain, fp, uint64(first), uint64(last)) + } + if err != nil { + utils.Fatalf("Export error: %v\n", err) + } + fmt.Printf("Export done in %v\n", time.Since(start)) + return nil +} + +func importHistory(ctx *cli.Context) error { + if ctx.Args().Len() != 1 { + utils.Fatalf("usage: %s", ctx.Command.ArgsUsage) + } + + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, db := utils.MakeChain(ctx, stack, false) + defer db.Close() + + var ( + start = time.Now() + dir = ctx.Args().Get(0) + network string + ) + + // Determine network. + if utils.IsNetworkPreset(ctx) { + switch { + case ctx.Bool(utils.MainnetFlag.Name): + network = "mainnet" + case ctx.Bool(utils.SepoliaFlag.Name): + network = "sepolia" + case ctx.Bool(utils.GoerliFlag.Name): + network = "goerli" + } + } else { + // No network flag set, try to determine network based on files + // present in directory. + var networks []string + for _, n := range params.NetworkNames { + entries, err := era.ReadDir(dir, n) + if err != nil { + return fmt.Errorf("error reading %s: %w", dir, err) + } + if len(entries) > 0 { + networks = append(networks, n) + } + } + if len(networks) == 0 { + return fmt.Errorf("no era1 files found in %s", dir) + } + if len(networks) > 1 { + return errors.New("multiple networks found, use a network flag to specify desired network") + } + network = networks[0] + } + + if err := utils.ImportHistory(chain, db, dir, network); err != nil { + return err + } + fmt.Printf("Import done in %v\n", time.Since(start)) + return nil +} + +// exportHistory exports chain history in Era archives at a specified +// directory. +func exportHistory(ctx *cli.Context) error { + if ctx.Args().Len() != 3 { + utils.Fatalf("usage: %s", ctx.Command.ArgsUsage) + } + + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, _ := utils.MakeChain(ctx, stack, true) + start := time.Now() + + var ( + dir = ctx.Args().Get(0) + first, ferr = strconv.ParseInt(ctx.Args().Get(1), 10, 64) + last, lerr = strconv.ParseInt(ctx.Args().Get(2), 10, 64) + ) + if ferr != nil || lerr != nil { + utils.Fatalf("Export error in parsing parameters: block number not an integer\n") + } + if first < 0 || last < 0 { + utils.Fatalf("Export error: block number must be greater than 0\n") + } + if head := chain.CurrentSnapBlock(); uint64(last) > head.Number.Uint64() { + utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.Number.Uint64()) + } + err := utils.ExportHistory(chain, dir, uint64(first), uint64(last), uint64(era.MaxEra1Size)) + if err != nil { + utils.Fatalf("Export error: %v\n", err) + } + fmt.Printf("Export done in %v\n", time.Since(start)) + return nil +} + +// 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.") + } + + stack, _ := makeConfigNode(ctx) + 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 { + utils.Fatalf("Import error: %v\n", err) + } + fmt.Printf("Import done in %v\n", time.Since(start)) + return nil +} + +func parseDumpConfig(ctx *cli.Context, db ethdb.Database) (*state.DumpConfig, common.Hash, error) { + var header *types.Header + if ctx.NArg() > 1 { + return nil, common.Hash{}, fmt.Errorf("expected 1 argument (number or hash), got %d", ctx.NArg()) + } + if ctx.NArg() == 1 { + arg := ctx.Args().First() + if hashish(arg) { + hash := common.HexToHash(arg) + if number := rawdb.ReadHeaderNumber(db, hash); number != nil { + header = rawdb.ReadHeader(db, hash, *number) + } else { + return nil, common.Hash{}, fmt.Errorf("block %x not found", hash) + } + } else { + number, err := strconv.ParseUint(arg, 10, 64) + if err != nil { + return nil, common.Hash{}, err + } + if hash := rawdb.ReadCanonicalHash(db, number); hash != (common.Hash{}) { + header = rawdb.ReadHeader(db, hash, number) + } else { + return nil, common.Hash{}, fmt.Errorf("header for block %d not found", number) + } + } + } else { + // Use latest + header = rawdb.ReadHeadHeader(db) + } + if header == nil { + return nil, common.Hash{}, errors.New("no head block found") + } + startArg := common.FromHex(ctx.String(utils.StartKeyFlag.Name)) + var start common.Hash + switch len(startArg) { + case 0: // common.Hash + case 32: + start = common.BytesToHash(startArg) + case 20: + start = crypto.Keccak256Hash(startArg) + log.Info("Converting start-address to hash", "address", common.BytesToAddress(startArg), "hash", start.Hex()) + default: + return nil, common.Hash{}, fmt.Errorf("invalid start argument: %x. 20 or 32 hex-encoded bytes required", startArg) + } + var conf = &state.DumpConfig{ + SkipCode: ctx.Bool(utils.ExcludeCodeFlag.Name), + SkipStorage: ctx.Bool(utils.ExcludeStorageFlag.Name), + OnlyWithAddresses: !ctx.Bool(utils.IncludeIncompletesFlag.Name), + Start: start.Bytes(), + Max: ctx.Uint64(utils.DumpLimitFlag.Name), + } + log.Info("State dump configured", "block", header.Number, "hash", header.Hash().Hex(), + "skipcode", conf.SkipCode, "skipstorage", conf.SkipStorage, + "start", hexutil.Encode(conf.Start), "limit", conf.Max) + return conf, header.Root, nil +} + +func dump(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + conf, root, err := parseDumpConfig(ctx, db) + if err != nil { + return err + } + 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 { + fmt.Println(string(state.Dump(conf))) + } + return nil +} + +// hashish returns true for strings that look like hashes. +func hashish(x string) bool { + _, err := strconv.Atoi(x) + return err != nil +} diff --git a/cmd/geth/config.go b/cmd/geth/config.go new file mode 100644 index 0000000..522e5e2 --- /dev/null +++ b/cmd/geth/config.go @@ -0,0 +1,386 @@ +// 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 . + +package main + +import ( + "bufio" + "errors" + "fmt" + "os" + "reflect" + "runtime" + "strings" + "unicode" + + "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/beacon/blsync" + "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/ethconfig" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/internal/version" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/naoina/toml" + "github.com/urfave/cli/v2" +) + +var ( + dumpConfigCommand = &cli.Command{ + Action: dumpConfig, + Name: "dumpconfig", + Usage: "Export configuration values in a TOML format", + ArgsUsage: "", + Flags: flags.Merge(nodeFlags, rpcFlags), + Description: `Export configuration values in TOML format (to stdout by default).`, + } + + configFileFlag = &cli.StringFlag{ + Name: "config", + Usage: "TOML configuration file", + Category: flags.EthCategory, + } +) + +// These settings ensure that TOML keys use the same names as Go struct fields. +var tomlSettings = toml.Config{ + NormFieldName: func(rt reflect.Type, key string) string { + return key + }, + FieldToKey: func(rt reflect.Type, field string) string { + return field + }, + MissingField: func(rt reflect.Type, field string) error { + id := fmt.Sprintf("%s.%s", rt.String(), field) + if deprecated(id) { + log.Warn("Config field is deprecated and won't have an effect", "name", id) + return nil + } + var link string + if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" { + link = fmt.Sprintf(", see https://godoc.org/%s#%s for available fields", rt.PkgPath(), rt.Name()) + } + return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link) + }, +} + +type ethstatsConfig struct { + URL string `toml:",omitempty"` +} + +type gethConfig struct { + Eth ethconfig.Config + Node node.Config + Ethstats ethstatsConfig + Metrics metrics.Config +} + +func loadConfig(file string, cfg *gethConfig) error { + f, err := os.Open(file) + if err != nil { + return err + } + defer f.Close() + + err = tomlSettings.NewDecoder(bufio.NewReader(f)).Decode(cfg) + // Add file name to errors that have a line number. + if _, ok := err.(*toml.LineError); ok { + err = errors.New(file + ", " + err.Error()) + } + return err +} + +func defaultNodeConfig() node.Config { + git, _ := version.VCS() + cfg := node.DefaultConfig + cfg.Name = clientIdentifier + cfg.Version = params.VersionWithCommit(git.Commit, git.Date) + cfg.HTTPModules = append(cfg.HTTPModules, "eth") + cfg.WSModules = append(cfg.WSModules, "eth") + cfg.IPCPath = "geth.ipc" + return cfg +} + +// loadBaseConfig loads the gethConfig based on the given command line +// parameters and config file. +func loadBaseConfig(ctx *cli.Context) gethConfig { + // Load defaults. + cfg := gethConfig{ + Eth: ethconfig.Defaults, + Node: defaultNodeConfig(), + Metrics: metrics.DefaultConfig, + } + + // Load config file. + if file := ctx.String(configFileFlag.Name); file != "" { + if err := loadConfig(file, &cfg); err != nil { + utils.Fatalf("%v", err) + } + } + + // Apply flags. + utils.SetNodeConfig(ctx, &cfg.Node) + return cfg +} + +// makeConfigNode loads geth configuration and creates a blank node instance. +func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { + cfg := loadBaseConfig(ctx) + stack, err := node.New(&cfg.Node) + if err != nil { + utils.Fatalf("Failed to create the protocol stack: %v", err) + } + // Node doesn't by default populate account manager backends + if err := setAccountManagerBackends(stack.Config(), stack.AccountManager(), stack.KeyStoreDir()); err != nil { + utils.Fatalf("Failed to set account manager backends: %v", err) + } + + utils.SetEthConfig(ctx, stack, &cfg.Eth) + if ctx.IsSet(utils.EthStatsURLFlag.Name) { + cfg.Ethstats.URL = ctx.String(utils.EthStatsURLFlag.Name) + } + applyMetricConfig(ctx, &cfg) + + return stack, cfg +} + +// makeFullNode loads geth configuration and creates the Ethereum backend. +func makeFullNode(ctx *cli.Context) *node.Node { + stack, cfg := makeConfigNode(ctx) + if ctx.IsSet(utils.OverrideCancun.Name) { + v := ctx.Uint64(utils.OverrideCancun.Name) + cfg.Eth.OverrideCancun = &v + } + if ctx.IsSet(utils.OverrideVerkle.Name) { + v := ctx.Uint64(utils.OverrideVerkle.Name) + cfg.Eth.OverrideVerkle = &v + } + + backend, eth := utils.RegisterEthService(stack, &cfg.Eth) + + // Create gauge with geth system and build information + if eth != nil { // The 'eth' backend may be nil in light mode + var protos []string + for _, p := range eth.Protocols() { + protos = append(protos, fmt.Sprintf("%v/%d", p.Name, p.Version)) + } + metrics.NewRegisteredGaugeInfo("geth/info", nil).Update(metrics.GaugeInfoValue{ + "arch": runtime.GOARCH, + "os": runtime.GOOS, + "version": cfg.Node.Version, + "protocols": strings.Join(protos, ","), + }) + } + + // Configure log filter RPC API. + filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth) + + // Configure GraphQL if requested. + 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) { + 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)) + } + + if ctx.IsSet(utils.DeveloperFlag.Name) { + // Start dev mode. + simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), eth) + if err != nil { + utils.Fatalf("failed to register dev mode catalyst service: %v", err) + } + catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon) + stack.RegisterLifecycle(simBeacon) + } else if ctx.IsSet(utils.BeaconApiFlag.Name) { + // Start blsync mode. + srv := rpc.NewServer() + srv.RegisterName("engine", catalyst.NewConsensusAPI(eth)) + blsyncer := blsync.NewClient(ctx) + blsyncer.SetEngineRPC(rpc.DialInProc(srv)) + stack.RegisterLifecycle(blsyncer) + } else { + // Launch the engine API for interacting with external consensus client. + err := catalyst.Register(stack, eth) + if err != nil { + utils.Fatalf("failed to register catalyst service: %v", err) + } + } + return stack +} + +// dumpConfig is the dumpconfig command. +func dumpConfig(ctx *cli.Context) error { + _, cfg := makeConfigNode(ctx) + comment := "" + + if cfg.Eth.Genesis != nil { + cfg.Eth.Genesis = nil + comment += "# Note: this config doesn't contain the genesis block.\n\n" + } + + out, err := tomlSettings.Marshal(&cfg) + if err != nil { + return err + } + + dump := os.Stdout + if ctx.NArg() > 0 { + dump, err = os.OpenFile(ctx.Args().Get(0), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer dump.Close() + } + dump.WriteString(comment) + dump.Write(out) + + return nil +} + +func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) { + if ctx.IsSet(utils.MetricsEnabledFlag.Name) { + cfg.Metrics.Enabled = ctx.Bool(utils.MetricsEnabledFlag.Name) + } + if ctx.IsSet(utils.MetricsEnabledExpensiveFlag.Name) { + log.Warn("Expensive metrics are collected by default, please remove this flag", "flag", utils.MetricsEnabledExpensiveFlag.Name) + } + if ctx.IsSet(utils.MetricsHTTPFlag.Name) { + cfg.Metrics.HTTP = ctx.String(utils.MetricsHTTPFlag.Name) + } + if ctx.IsSet(utils.MetricsPortFlag.Name) { + cfg.Metrics.Port = ctx.Int(utils.MetricsPortFlag.Name) + } + if ctx.IsSet(utils.MetricsEnableInfluxDBFlag.Name) { + cfg.Metrics.EnableInfluxDB = ctx.Bool(utils.MetricsEnableInfluxDBFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBEndpointFlag.Name) { + cfg.Metrics.InfluxDBEndpoint = ctx.String(utils.MetricsInfluxDBEndpointFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBDatabaseFlag.Name) { + cfg.Metrics.InfluxDBDatabase = ctx.String(utils.MetricsInfluxDBDatabaseFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBUsernameFlag.Name) { + cfg.Metrics.InfluxDBUsername = ctx.String(utils.MetricsInfluxDBUsernameFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBPasswordFlag.Name) { + cfg.Metrics.InfluxDBPassword = ctx.String(utils.MetricsInfluxDBPasswordFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBTagsFlag.Name) { + cfg.Metrics.InfluxDBTags = ctx.String(utils.MetricsInfluxDBTagsFlag.Name) + } + if ctx.IsSet(utils.MetricsEnableInfluxDBV2Flag.Name) { + cfg.Metrics.EnableInfluxDBV2 = ctx.Bool(utils.MetricsEnableInfluxDBV2Flag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBTokenFlag.Name) { + cfg.Metrics.InfluxDBToken = ctx.String(utils.MetricsInfluxDBTokenFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBBucketFlag.Name) { + cfg.Metrics.InfluxDBBucket = ctx.String(utils.MetricsInfluxDBBucketFlag.Name) + } + if ctx.IsSet(utils.MetricsInfluxDBOrganizationFlag.Name) { + cfg.Metrics.InfluxDBOrganization = ctx.String(utils.MetricsInfluxDBOrganizationFlag.Name) + } +} + +func deprecated(field string) bool { + switch field { + case "ethconfig.Config.EVMInterpreter": + return true + case "ethconfig.Config.EWASMInterpreter": + return true + case "ethconfig.Config.TrieCleanCacheJournal": + return true + case "ethconfig.Config.TrieCleanCacheRejournal": + return true + default: + return false + } +} + +func setAccountManagerBackends(conf *node.Config, am *accounts.Manager, keydir string) error { + scryptN := keystore.StandardScryptN + scryptP := keystore.StandardScryptP + if conf.UseLightweightKDF { + scryptN = keystore.LightScryptN + scryptP = keystore.LightScryptP + } + + // Assemble the supported backends + if len(conf.ExternalSigner) > 0 { + log.Info("Using external signer", "url", conf.ExternalSigner) + if extBackend, err := external.NewExternalBackend(conf.ExternalSigner); err == nil { + am.AddBackend(extBackend) + return nil + } else { + return fmt.Errorf("error connecting to external signer: %v", err) + } + } + + // For now, we're using EITHER external signer OR local signers. + // If/when we implement some form of lockfile for USB and keystore wallets, + // we can have both, but it's very confusing for the user to see the same + // accounts in both externally and locally, plus very racey. + am.AddBackend(keystore.NewKeyStore(keydir, scryptN, scryptP)) + if conf.USB { + // Start a USB hub for Ledger hardware wallets + if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { + log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) + } else { + am.AddBackend(ledgerhub) + } + // Start a USB hub for Trezor hardware wallets (HID version) + if trezorhub, err := usbwallet.NewTrezorHubWithHID(); err != nil { + log.Warn(fmt.Sprintf("Failed to start HID Trezor hub, disabling: %v", err)) + } else { + am.AddBackend(trezorhub) + } + // Start a USB hub for Trezor hardware wallets (WebUSB version) + if trezorhub, err := usbwallet.NewTrezorHubWithWebUSB(); err != nil { + log.Warn(fmt.Sprintf("Failed to start WebUSB Trezor hub, disabling: %v", err)) + } else { + am.AddBackend(trezorhub) + } + } + if len(conf.SmartCardDaemonPath) > 0 { + // Start a smart card hub + if schub, err := scwallet.NewHub(conf.SmartCardDaemonPath, scwallet.Scheme, keydir); err != nil { + log.Warn(fmt.Sprintf("Failed to start smart card hub, disabling: %v", err)) + } else { + am.AddBackend(schub) + } + } + + return nil +} diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go new file mode 100644 index 0000000..e2d3125 --- /dev/null +++ b/cmd/geth/consolecmd.go @@ -0,0 +1,160 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/urfave/cli/v2" +) + +var ( + consoleFlags = []cli.Flag{utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag} + + consoleCommand = &cli.Command{ + Action: localConsole, + Name: "console", + Usage: "Start an interactive JavaScript environment", + Flags: flags.Merge(nodeFlags, rpcFlags, consoleFlags), + Description: ` +The Geth console is an interactive shell for the JavaScript runtime environment +which exposes a node admin interface as well as the Ãapp JavaScript API. +See https://geth.ethereum.org/docs/interacting-with-geth/javascript-console.`, + } + + attachCommand = &cli.Command{ + Action: remoteConsole, + Name: "attach", + Usage: "Start an interactive JavaScript environment (connect to node)", + ArgsUsage: "[endpoint]", + Flags: flags.Merge([]cli.Flag{utils.DataDirFlag, utils.HttpHeaderFlag}, consoleFlags), + Description: ` +The Geth console is an interactive shell for the JavaScript runtime environment +which exposes a node admin interface as well as the Ãapp JavaScript API. +See https://geth.ethereum.org/docs/interacting-with-geth/javascript-console. +This command allows to open a console on a running geth node.`, + } + + javascriptCommand = &cli.Command{ + Action: ephemeralConsole, + Name: "js", + Usage: "(DEPRECATED) Execute the specified JavaScript files", + ArgsUsage: " [jsfile...]", + Flags: flags.Merge(nodeFlags, consoleFlags), + Description: ` +The JavaScript VM exposes a node admin interface as well as the Ãapp +JavaScript API. See https://geth.ethereum.org/docs/interacting-with-geth/javascript-console`, + } +) + +// localConsole starts a new geth node, attaching a JavaScript console to it at the +// same time. +func localConsole(ctx *cli.Context) error { + // Create and start the node based on the CLI flags + prepare(ctx) + stack := makeFullNode(ctx) + startNode(ctx, stack, true) + defer stack.Close() + + // Attach to the newly started node and create the JavaScript console. + client := stack.Attach() + config := console.Config{ + DataDir: utils.MakeDataDir(ctx), + DocRoot: ctx.String(utils.JSpathFlag.Name), + Client: client, + Preload: utils.MakeConsolePreloads(ctx), + } + console, err := console.New(config) + if err != nil { + return fmt.Errorf("failed to start the JavaScript console: %v", err) + } + defer console.Stop(false) + + // If only a short execution was requested, evaluate and return. + if script := ctx.String(utils.ExecFlag.Name); script != "" { + console.Evaluate(script) + return nil + } + + // Track node shutdown and stop the console when it goes down. + // This happens when SIGTERM is sent to the process. + go func() { + stack.Wait() + console.StopInteractive() + }() + + // Print the welcome screen and enter interactive mode. + console.Welcome() + console.Interactive() + return nil +} + +// remoteConsole will connect to a remote geth instance, attaching a JavaScript +// console to it. +func remoteConsole(ctx *cli.Context) error { + if ctx.Args().Len() > 1 { + utils.Fatalf("invalid command-line: too many arguments") + } + endpoint := ctx.Args().First() + if endpoint == "" { + cfg := defaultNodeConfig() + utils.SetDataDir(ctx, &cfg) + endpoint = cfg.IPCEndpoint() + } + client, err := utils.DialRPCWithHeaders(endpoint, ctx.StringSlice(utils.HttpHeaderFlag.Name)) + if err != nil { + utils.Fatalf("Unable to attach to remote geth: %v", err) + } + config := console.Config{ + DataDir: utils.MakeDataDir(ctx), + DocRoot: ctx.String(utils.JSpathFlag.Name), + Client: client, + Preload: utils.MakeConsolePreloads(ctx), + } + console, err := console.New(config) + if err != nil { + utils.Fatalf("Failed to start the JavaScript console: %v", err) + } + defer console.Stop(false) + + if script := ctx.String(utils.ExecFlag.Name); script != "" { + console.Evaluate(script) + return nil + } + + // Otherwise print the welcome screen and enter interactive mode + console.Welcome() + console.Interactive() + return nil +} + +// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript +// console to it, executes each of the files specified as arguments and tears +// everything down. +func ephemeralConsole(ctx *cli.Context) error { + var b strings.Builder + for _, file := range ctx.Args().Slice() { + b.Write([]byte(fmt.Sprintf("loadScript('%s');", file))) + } + utils.Fatalf(`The "js" command is deprecated. Please use the following instead: +geth --exec "%s" console`, b.String()) + return nil +} diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go new file mode 100644 index 0000000..33d6d4b --- /dev/null +++ b/cmd/geth/consolecmd_test.go @@ -0,0 +1,160 @@ +// Copyright 2016 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 ( + "crypto/rand" + "math/big" + "path/filepath" + "runtime" + "strconv" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/params" +) + +const ( + ipcAPIs = "admin:1.0 clique:1.0 debug:1.0 engine:1.0 eth:1.0 miner:1.0 net:1.0 rpc:1.0 txpool:1.0 web3:1.0" + httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" +) + +// spawns geth with the given command line args, using a set of flags to minimise +// memory and disk IO. If the args don't set --datadir, the +// child g gets a temporary data directory. +func runMinimalGeth(t *testing.T, args ...string) *testgeth { + // --goerli to make the 'writing genesis to disk' faster (no accounts) + // --networkid=1337 to avoid cache bump + // --syncmode=full to avoid allocating fast sync bloom + allArgs := []string{"--goerli", "--networkid", "1337", "--authrpc.port", "0", "--syncmode=full", "--port", "0", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64", + "--datadir.minfreedisk", "0"} + return runGeth(t, append(allArgs, args...)...) +} + +// 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 + geth := runMinimalGeth(t, "--miner.etherbase", coinbase, "console") + + // Gather all the infos the welcome message needs to contain + geth.SetTemplateFunc("goos", func() string { return runtime.GOOS }) + geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH }) + geth.SetTemplateFunc("gover", runtime.Version) + geth.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") }) + geth.SetTemplateFunc("niltime", func() string { + return time.Unix(1548854791, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)") + }) + geth.SetTemplateFunc("apis", func() string { return ipcAPIs }) + + // Verify the actual welcome message to the required template + geth.Expect(` +Welcome to the Geth JavaScript console! + +instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}} +at block: 0 ({{niltime}}) + datadir: {{.Datadir}} + modules: {{apis}} + +To exit, press ctrl-d or type exit +> {{.InputLine "exit"}} +`) + geth.ExpectExit() +} + +// Tests that a console can be attached to a running node via various means. +func TestAttachWelcome(t *testing.T) { + var ( + ipc string + httpPort string + wsPort string + ) + // Configure the instance for IPC attachment + if runtime.GOOS == "windows" { + ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999)) + } else { + ipc = filepath.Join(t.TempDir(), "geth.ipc") + } + // And HTTP + WS attachment + p := trulyRandInt(1024, 65533) // Yeah, sometimes this will fail, sorry :P + httpPort = strconv.Itoa(p) + wsPort = strconv.Itoa(p + 1) + geth := runMinimalGeth(t, "--miner.etherbase", "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182", + "--ipcpath", ipc, + "--http", "--http.port", httpPort, + "--ws", "--ws.port", wsPort) + t.Run("ipc", func(t *testing.T) { + waitForEndpoint(t, ipc, 4*time.Second) + testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) + }) + t.Run("http", func(t *testing.T) { + endpoint := "http://127.0.0.1:" + httpPort + waitForEndpoint(t, endpoint, 4*time.Second) + testAttachWelcome(t, geth, endpoint, httpAPIs) + }) + t.Run("ws", func(t *testing.T) { + endpoint := "ws://127.0.0.1:" + wsPort + waitForEndpoint(t, endpoint, 4*time.Second) + testAttachWelcome(t, geth, endpoint, httpAPIs) + }) + geth.Kill() +} + +func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) { + // Attach to a running geth node and terminate immediately + attach := runGeth(t, "attach", endpoint) + defer attach.ExpectExit() + attach.CloseStdin() + + // Gather all the infos the welcome message needs to contain + attach.SetTemplateFunc("goos", func() string { return runtime.GOOS }) + attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH }) + attach.SetTemplateFunc("gover", runtime.Version) + attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") }) + attach.SetTemplateFunc("niltime", func() string { + return time.Unix(1548854791, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)") + }) + attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") }) + attach.SetTemplateFunc("datadir", func() string { return geth.Datadir }) + attach.SetTemplateFunc("apis", func() string { return apis }) + + // Verify the actual welcome message to the required template + attach.Expect(` +Welcome to the Geth JavaScript console! + +instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}} +at block: 0 ({{niltime}}){{if ipc}} + datadir: {{datadir}}{{end}} + modules: {{apis}} + +To exit, press ctrl-d or type exit +> {{.InputLine "exit" }} +`) + attach.ExpectExit() +} + +// trulyRandInt generates a crypto random integer used by the console tests to +// not clash network ports with other tests running concurrently. +func trulyRandInt(lo, hi int) int { + num, _ := rand.Int(rand.Reader, big.NewInt(int64(hi-lo))) + return int(num.Int64()) + lo +} diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go new file mode 100644 index 0000000..4b68356 --- /dev/null +++ b/cmd/geth/dbcmd.go @@ -0,0 +1,931 @@ +// 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 ( + "bytes" + "fmt" + "os" + "os/signal" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "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/console/prompt" + "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/ethdb" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" + "github.com/olekukonko/tablewriter" + "github.com/urfave/cli/v2" +) + +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: flags.Merge(utils.DatabaseFlags, + []cli.Flag{removeStateDataFlag, removeChainDataFlag}), + Description: ` +Remove blockchain and state databases`, + } + dbCommand = &cli.Command{ + Name: "db", + Usage: "Low level database operations", + ArgsUsage: "", + Subcommands: []*cli.Command{ + dbInspectCmd, + dbStatCmd, + dbCompactCmd, + dbGetCmd, + dbDeleteCmd, + dbPutCmd, + dbGetSlotsCmd, + dbDumpFreezerIndex, + dbImportCmd, + dbExportCmd, + dbMetadataCmd, + dbCheckStateContentCmd, + dbInspectHistoryCmd, + }, + } + dbInspectCmd = &cli.Command{ + Action: inspect, + Name: "inspect", + ArgsUsage: " ", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, 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.`, + } + dbCheckStateContentCmd = &cli.Command{ + Action: checkStateContent, + Name: "check-state-content", + ArgsUsage: "", + 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 +a data corruption.`, + } + dbStatCmd = &cli.Command{ + Action: dbStats, + Name: "stats", + Usage: "Print leveldb statistics", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, utils.NetworkFlags, utils.DatabaseFlags), + } + dbCompactCmd = &cli.Command{ + Action: dbCompact, + Name: "compact", + Usage: "Compact leveldb database. WARNING: May take a very long time", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + utils.CacheFlag, + utils.CacheDatabaseFlag, + }, 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'!`, + } + dbGetCmd = &cli.Command{ + Action: dbGet, + Name: "get", + Usage: "Show the value of a database key", + ArgsUsage: "", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, utils.NetworkFlags, utils.DatabaseFlags), + Description: "This command looks up the specified database key from the database.", + } + dbDeleteCmd = &cli.Command{ + Action: dbDelete, + Name: "delete", + Usage: "Delete a database key (WARNING: may corrupt your database)", + ArgsUsage: "", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, 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{ + Action: dbPut, + Name: "put", + Usage: "Set the value of a database key (WARNING: may corrupt your database)", + ArgsUsage: " ", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, 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{ + Action: dbDumpTrie, + Name: "dumptrie", + Usage: "Show the storage key/values of a given storage trie", + ArgsUsage: " ", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, utils.NetworkFlags, utils.DatabaseFlags), + Description: "This command looks up the specified database key from the database.", + } + dbDumpFreezerIndex = &cli.Command{ + Action: freezerInspect, + Name: "freezer-index", + Usage: "Dump out the index of a specific freezer table", + ArgsUsage: " ", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, utils.NetworkFlags, utils.DatabaseFlags), + Description: "This command displays information about the freezer index.", + } + dbImportCmd = &cli.Command{ + Action: importLDBdata, + Name: "import", + Usage: "Imports leveldb-data from an exported RLP dump.", + ArgsUsage: " has .gz suffix, gzip compression will be used.", + ArgsUsage: " ", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, utils.NetworkFlags, utils.DatabaseFlags), + Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.", + } + dbMetadataCmd = &cli.Command{ + Action: showMetaData, + Name: "metadata", + Usage: "Shows metadata about the chain status.", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + }, utils.NetworkFlags, utils.DatabaseFlags), + Description: "Shows metadata about the chain status.", + } + dbInspectHistoryCmd = &cli.Command{ + Action: inspectHistory, + Name: "inspect-history", + Usage: "Inspect the state history within block range", + ArgsUsage: "
[OPTIONAL ]", + Flags: flags.Merge([]cli.Flag{ + utils.SyncModeFlag, + &cli.Uint64Flag{ + Name: "start", + Usage: "block number of the range start, zero means earliest history", + }, + &cli.Uint64Flag{ + Name: "end", + Usage: "block number of the range end(included), zero means latest history", + }, + &cli.BoolFlag{ + Name: "raw", + Usage: "display the decoded raw state value (otherwise shows rlp-encoded value)", + }, + }, utils.NetworkFlags, utils.DatabaseFlags), + Description: "This command queries the history of the account or storage slot within the specified block range", + } +) + +func removeDB(ctx *cli.Context) error { + stack, config := makeConfigNode(ctx) + + // Resolve folder paths. + var ( + rootDir = stack.ResolvePath("chaindata") + ancientDir = config.Eth.DatabaseFreezer + ) + switch { + 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 +// 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", "kind", kind, "paths", paths) + default: + 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) + } + } + log.Info("Database successfully deleted", "kind", kind, "paths", deleted, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +func inspect(ctx *cli.Context) error { + var ( + prefix []byte + start []byte + ) + if ctx.NArg() > 2 { + return fmt.Errorf("max 2 arguments: %v", ctx.Command.ArgsUsage) + } + if ctx.NArg() >= 1 { + if d, err := hexutil.Decode(ctx.Args().Get(0)); err != nil { + return fmt.Errorf("failed to hex-decode 'prefix': %v", err) + } else { + prefix = d + } + } + if ctx.NArg() >= 2 { + if d, err := hexutil.Decode(ctx.Args().Get(1)); err != nil { + return fmt.Errorf("failed to hex-decode 'start': %v", err) + } else { + start = d + } + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + return rawdb.InspectDatabase(db, prefix, start) +} + +func checkStateContent(ctx *cli.Context) error { + var ( + prefix []byte + start []byte + ) + if ctx.NArg() > 1 { + return fmt.Errorf("max 1 argument: %v", ctx.Command.ArgsUsage) + } + if ctx.NArg() > 0 { + if d, err := hexutil.Decode(ctx.Args().First()); err != nil { + return fmt.Errorf("failed to hex-decode 'start': %v", err) + } else { + start = d + } + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + var ( + it = rawdb.NewKeyLengthIterator(db.NewIterator(prefix, start), 32) + hasher = crypto.NewKeccakState() + got = make([]byte, 32) + errs int + count int + startTime = time.Now() + lastLog = time.Now() + ) + for it.Next() { + count++ + k := it.Key() + v := it.Value() + hasher.Reset() + hasher.Write(v) + hasher.Read(got) + if !bytes.Equal(k, got) { + errs++ + fmt.Printf("Error at %#x\n", k) + fmt.Printf(" Hash: %#x\n", got) + fmt.Printf(" Data: %#x\n", v) + } + if time.Since(lastLog) > 8*time.Second { + log.Info("Iterating the database", "at", fmt.Sprintf("%#x", k), "elapsed", common.PrettyDuration(time.Since(startTime))) + lastLog = time.Now() + } + } + if err := it.Error(); err != nil { + return err + } + log.Info("Iterated the state content", "errors", errs, "items", count) + return nil +} + +func showDBStats(db ethdb.KeyValueStater) { + stats, err := db.Stat() + if err != nil { + log.Warn("Failed to read database stats", "error", err) + return + } + fmt.Println(stats) +} + +func dbStats(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + showDBStats(db) + return nil +} + +func dbCompact(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() + + log.Info("Stats before compaction") + showDBStats(db) + + log.Info("Triggering compaction") + if err := db.Compact(nil, nil); err != nil { + log.Info("Compact err", "error", err) + return err + } + log.Info("Stats after compaction") + showDBStats(db) + return nil +} + +// dbGet shows the value of a given database key +func dbGet(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + key, err := common.ParseHexOrString(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + + data, err := db.Get(key) + if err != nil { + log.Info("Get operation failed", "key", fmt.Sprintf("%#x", key), "error", err) + return err + } + fmt.Printf("key %#x: %#x\n", key, data) + return nil +} + +// dbDelete deletes a key from the database +func dbDelete(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() + + key, err := common.ParseHexOrString(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + data, err := db.Get(key) + if err == nil { + fmt.Printf("Previous value: %#x\n", data) + } + if err = db.Delete(key); err != nil { + log.Info("Delete operation returned an error", "key", fmt.Sprintf("%#x", key), "error", err) + return err + } + return nil +} + +// dbPut overwrite a value in the database +func dbPut(ctx *cli.Context) error { + if ctx.NArg() != 2 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() + + var ( + key []byte + value []byte + data []byte + err error + ) + key, err = common.ParseHexOrString(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + value, err = hexutil.Decode(ctx.Args().Get(1)) + if err != nil { + log.Info("Could not decode the value", "error", err) + return err + } + data, err = db.Get(key) + if err == nil { + fmt.Printf("Previous value: %#x\n", data) + } + return db.Put(key, value) +} + +// dbDumpTrie shows the key-value slots of a given storage trie +func dbDumpTrie(ctx *cli.Context) error { + if ctx.NArg() < 3 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + 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 + account []byte + start []byte + max = int64(-1) + err error + ) + if state, err = hexutil.Decode(ctx.Args().Get(0)); err != nil { + log.Info("Could not decode the state root", "error", err) + return err + } + if account, err = hexutil.Decode(ctx.Args().Get(1)); err != nil { + log.Info("Could not decode the account hash", "error", err) + return err + } + if storage, err = hexutil.Decode(ctx.Args().Get(2)); err != nil { + log.Info("Could not decode the storage trie root", "error", err) + return err + } + if ctx.NArg() > 3 { + if start, err = hexutil.Decode(ctx.Args().Get(3)); err != nil { + log.Info("Could not decode the seek position", "error", err) + return err + } + } + if ctx.NArg() > 4 { + if max, err = strconv.ParseInt(ctx.Args().Get(4), 10, 64); err != nil { + log.Info("Could not decode the max count", "error", err) + return err + } + } + id := trie.StorageTrieID(common.BytesToHash(state), common.BytesToHash(account), common.BytesToHash(storage)) + theTrie, err := trie.New(id, triedb) + if err != nil { + return err + } + trieIt, err := theTrie.NodeIterator(start) + if err != nil { + return err + } + var count int64 + it := trie.NewIterator(trieIt) + for it.Next() { + if max > 0 && count == max { + fmt.Printf("Exiting after %d values\n", count) + break + } + fmt.Printf(" %d. key %#x: %#x\n", count, it.Key, it.Value) + count++ + } + return it.Err +} + +func freezerInspect(ctx *cli.Context) error { + if ctx.NArg() < 4 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + var ( + freezer = ctx.Args().Get(0) + table = ctx.Args().Get(1) + ) + start, err := strconv.ParseInt(ctx.Args().Get(2), 10, 64) + if err != nil { + log.Info("Could not read start-param", "err", err) + return err + } + end, err := strconv.ParseInt(ctx.Args().Get(3), 10, 64) + if err != nil { + log.Info("Could not read count param", "err", err) + return err + } + stack, _ := makeConfigNode(ctx) + ancient := stack.ResolveAncient("chaindata", ctx.String(utils.AncientFlag.Name)) + stack.Close() + return rawdb.InspectFreezerTable(ancient, freezer, table, start, end) +} + +func importLDBdata(ctx *cli.Context) error { + start := 0 + switch ctx.NArg() { + case 1: + break + case 2: + s, err := strconv.Atoi(ctx.Args().Get(1)) + if err != nil { + return fmt.Errorf("second arg must be an integer: %v", err) + } + start = s + default: + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + var ( + fName = ctx.Args().Get(0) + stack, _ = makeConfigNode(ctx) + interrupt = make(chan os.Signal, 1) + stop = make(chan struct{}) + ) + defer stack.Close() + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(interrupt) + defer close(interrupt) + go func() { + if _, ok := <-interrupt; ok { + log.Info("Interrupted during ldb import, stopping at next batch") + } + close(stop) + }() + db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() + return utils.ImportLDBData(db, fName, int64(start), stop) +} + +type preimageIterator struct { + iter ethdb.Iterator +} + +func (iter *preimageIterator) Next() (byte, []byte, []byte, bool) { + for iter.iter.Next() { + key := iter.iter.Key() + if bytes.HasPrefix(key, rawdb.PreimagePrefix) && len(key) == (len(rawdb.PreimagePrefix)+common.HashLength) { + return utils.OpBatchAdd, key, iter.iter.Value(), true + } + } + return 0, nil, nil, false +} + +func (iter *preimageIterator) Release() { + iter.iter.Release() +} + +type snapshotIterator struct { + init bool + account ethdb.Iterator + storage ethdb.Iterator +} + +func (iter *snapshotIterator) Next() (byte, []byte, []byte, bool) { + if !iter.init { + iter.init = true + return utils.OpBatchDel, rawdb.SnapshotRootKey, nil, true + } + for iter.account.Next() { + key := iter.account.Key() + if bytes.HasPrefix(key, rawdb.SnapshotAccountPrefix) && len(key) == (len(rawdb.SnapshotAccountPrefix)+common.HashLength) { + return utils.OpBatchAdd, key, iter.account.Value(), true + } + } + for iter.storage.Next() { + key := iter.storage.Key() + if bytes.HasPrefix(key, rawdb.SnapshotStoragePrefix) && len(key) == (len(rawdb.SnapshotStoragePrefix)+2*common.HashLength) { + return utils.OpBatchAdd, key, iter.storage.Value(), true + } + } + return 0, nil, nil, false +} + +func (iter *snapshotIterator) Release() { + iter.account.Release() + iter.storage.Release() +} + +// chainExporters defines the export scheme for all exportable chain data. +var chainExporters = map[string]func(db ethdb.Database) utils.ChainDataIterator{ + "preimage": func(db ethdb.Database) utils.ChainDataIterator { + iter := db.NewIterator(rawdb.PreimagePrefix, nil) + return &preimageIterator{iter: iter} + }, + "snapshot": func(db ethdb.Database) utils.ChainDataIterator { + account := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) + storage := db.NewIterator(rawdb.SnapshotStoragePrefix, nil) + return &snapshotIterator{account: account, storage: storage} + }, +} + +func exportChaindata(ctx *cli.Context) error { + if ctx.NArg() < 2 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + // Parse the required chain data type, make sure it's supported. + kind := ctx.Args().Get(0) + kind = strings.ToLower(strings.Trim(kind, " ")) + exporter, ok := chainExporters[kind] + if !ok { + var kinds []string + for kind := range chainExporters { + kinds = append(kinds, kind) + } + return fmt.Errorf("invalid data type %s, supported types: %s", kind, strings.Join(kinds, ", ")) + } + var ( + stack, _ = makeConfigNode(ctx) + interrupt = make(chan os.Signal, 1) + stop = make(chan struct{}) + ) + defer stack.Close() + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(interrupt) + defer close(interrupt) + go func() { + if _, ok := <-interrupt; ok { + log.Info("Interrupted during db export, stopping at next batch") + } + close(stop) + }() + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + return utils.ExportChaindata(ctx.Args().Get(1), kind, exporter(db), stop) +} + +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) + } + data := rawdb.ReadChainMetadata(db) + data = append(data, []string{"frozen", fmt.Sprintf("%d items", ancients)}) + data = append(data, []string{"snapshotGenerator", snapshot.ParseGeneratorStatus(rawdb.ReadSnapshotGenerator(db))}) + if b := rawdb.ReadHeadBlock(db); b != nil { + data = append(data, []string{"headBlock.Hash", fmt.Sprintf("%v", b.Hash())}) + data = append(data, []string{"headBlock.Root", fmt.Sprintf("%v", b.Root())}) + data = append(data, []string{"headBlock.Number", fmt.Sprintf("%d (%#x)", b.Number(), b.Number())}) + } + if h := rawdb.ReadHeadHeader(db); h != nil { + data = append(data, []string{"headHeader.Hash", fmt.Sprintf("%v", h.Hash())}) + data = append(data, []string{"headHeader.Root", fmt.Sprintf("%v", h.Root)}) + data = append(data, []string{"headHeader.Number", fmt.Sprintf("%d (%#x)", h.Number, h.Number)}) + } + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Field", "Value"}) + table.AppendBulk(data) + table.Render() + return nil +} + +func inspectAccount(db *triedb.Database, start uint64, end uint64, address common.Address, raw bool) error { + stats, err := db.AccountHistory(address, start, end) + if err != nil { + return err + } + fmt.Printf("Account history:\n\taddress: %s\n\tblockrange: [#%d-#%d]\n", address.Hex(), stats.Start, stats.End) + + from := stats.Start + for i := 0; i < len(stats.Blocks); i++ { + var content string + if len(stats.Origins[i]) == 0 { + content = "" + } else { + if !raw { + content = fmt.Sprintf("%#x", stats.Origins[i]) + } else { + account := new(types.SlimAccount) + if err := rlp.DecodeBytes(stats.Origins[i], account); err != nil { + panic(err) + } + code := "" + if len(account.CodeHash) > 0 { + code = fmt.Sprintf("%#x", account.CodeHash) + } + root := "" + if len(account.Root) > 0 { + root = fmt.Sprintf("%#x", account.Root) + } + content = fmt.Sprintf("nonce: %d, balance: %d, codeHash: %s, root: %s", account.Nonce, account.Balance, code, root) + } + } + fmt.Printf("#%d - #%d: %s\n", from, stats.Blocks[i], content) + from = stats.Blocks[i] + } + return nil +} + +func inspectStorage(db *triedb.Database, start uint64, end uint64, address common.Address, slot common.Hash, raw bool) error { + // The hash of storage slot key is utilized in the history + // rather than the raw slot key, make the conversion. + slotHash := crypto.Keccak256Hash(slot.Bytes()) + stats, err := db.StorageHistory(address, slotHash, start, end) + if err != nil { + return err + } + fmt.Printf("Storage history:\n\taddress: %s\n\tslot: %s\n\tblockrange: [#%d-#%d]\n", address.Hex(), slot.Hex(), stats.Start, stats.End) + + from := stats.Start + for i := 0; i < len(stats.Blocks); i++ { + var content string + if len(stats.Origins[i]) == 0 { + content = "" + } else { + if !raw { + content = fmt.Sprintf("%#x", stats.Origins[i]) + } else { + _, data, _, err := rlp.Split(stats.Origins[i]) + if err != nil { + fmt.Printf("Failed to decode storage slot, %v", err) + return err + } + content = fmt.Sprintf("%#x", data) + } + } + fmt.Printf("#%d - #%d: %s\n", from, stats.Blocks[i], content) + from = stats.Blocks[i] + } + return nil +} + +func inspectHistory(ctx *cli.Context) error { + if ctx.NArg() == 0 || ctx.NArg() > 2 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + var ( + address common.Address + slot common.Hash + ) + if err := address.UnmarshalText([]byte(ctx.Args().Get(0))); err != nil { + return err + } + if ctx.NArg() > 1 { + if err := slot.UnmarshalText([]byte(ctx.Args().Get(1))); err != nil { + return err + } + } + // Load the databases. + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + triedb := utils.MakeTrieDatabase(ctx, db, false, false, false) + defer triedb.Close() + + var ( + err error + start uint64 // the id of first history object to query + end uint64 // the id (included) of last history object to query + ) + // State histories are identified by state ID rather than block number. + // To address this, load the corresponding block header and perform the + // conversion by this function. + blockToID := func(blockNumber uint64) (uint64, error) { + header := rawdb.ReadHeader(db, rawdb.ReadCanonicalHash(db, blockNumber), blockNumber) + if header == nil { + return 0, fmt.Errorf("block #%d is not existent", blockNumber) + } + id := rawdb.ReadStateID(db, header.Root) + if id == nil { + first, last, err := triedb.HistoryRange() + if err == nil { + return 0, fmt.Errorf("history of block #%d is not existent, available history range: [#%d-#%d]", blockNumber, first, last) + } + return 0, fmt.Errorf("history of block #%d is not existent", blockNumber) + } + return *id, nil + } + // Parse the starting block number for inspection. + startNumber := ctx.Uint64("start") + if startNumber != 0 { + start, err = blockToID(startNumber) + if err != nil { + return err + } + } + // Parse the ending block number for inspection. + endBlock := ctx.Uint64("end") + if endBlock != 0 { + end, err = blockToID(endBlock) + if err != nil { + return err + } + } + // Inspect the state history. + if slot == (common.Hash{}) { + return inspectAccount(triedb, start, end, address, ctx.Bool("raw")) + } + return inspectStorage(triedb, start, end, address, slot, ctx.Bool("raw")) +} diff --git a/cmd/geth/exportcmd_test.go b/cmd/geth/exportcmd_test.go new file mode 100644 index 0000000..9570b1f --- /dev/null +++ b/cmd/geth/exportcmd_test.go @@ -0,0 +1,46 @@ +// 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 main + +import ( + "bytes" + "fmt" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +// 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) + geth.WaitExit() + if have, want := geth.ExitStatus(), 0; have != want { + t.Errorf("exit error, have %d want %d", have, want) + } + have, err := os.ReadFile(outfile) + if err != nil { + t.Fatal(err) + } + want := common.FromHex("0xf9026bf90266a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a08758259b018f7bce3d2be2ddb62f325eaeea0a0c188cf96623eab468a4413e03a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000180837a12008080b875000000000000000000000000000000000000000000000000000000000000000002f0d131f1f97aef08aec6e3291b957d9efe71050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0") + if !bytes.Equal(have, want) { + t.Fatalf("wrong content exported") + } +} diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go new file mode 100644 index 0000000..ffe8176 --- /dev/null +++ b/cmd/geth/genesis_test.go @@ -0,0 +1,198 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "testing" +) + +var customGenesisTests = []struct { + genesis string + query string + result string +}{ + // Genesis file with an empty chain configuration (ensure missing fields work) + { + genesis: `{ + "alloc" : {}, + "coinbase" : "0x0000000000000000000000000000000000000000", + "difficulty" : "0x20000", + "extraData" : "", + "gasLimit" : "0x2fefd8", + "nonce" : "0x0000000000001338", + "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp" : "0x00", + "config" : { + "terminalTotalDifficultyPassed": true + } + }`, + query: "eth.getBlock(0).nonce", + result: "0x0000000000001338", + }, + // Genesis file with specific chain configurations + { + genesis: `{ + "alloc" : {}, + "coinbase" : "0x0000000000000000000000000000000000000000", + "difficulty" : "0x20000", + "extraData" : "", + "gasLimit" : "0x2fefd8", + "nonce" : "0x0000000000001339", + "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp" : "0x00", + "config" : { + "homesteadBlock" : 42, + "daoForkBlock" : 141, + "daoForkSupport" : true, + "terminalTotalDifficultyPassed" : true + } + }`, + query: "eth.getBlock(0).nonce", + result: "0x0000000000001339", + }, +} + +// Tests that initializing Geth with a custom genesis block and chain definitions +// work properly. +func TestCustomGenesis(t *testing.T) { + t.Parallel() + for i, tt := range customGenesisTests { + // Create a temporary data directory to use and inspect later + datadir := t.TempDir() + + // Initialize the data directory with the custom genesis block + json := filepath.Join(datadir, "genesis.json") + if err := os.WriteFile(json, []byte(tt.genesis), 0600); err != nil { + t.Fatalf("test %d: failed to write genesis file: %v", i, err) + } + runGeth(t, "--datadir", datadir, "init", json).WaitExit() + + // Query the custom genesis block + geth := runGeth(t, "--networkid", "1337", "--syncmode=full", "--cache", "16", + "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--authrpc.port", "0", + "--nodiscover", "--nat", "none", "--ipcdisable", + "--exec", tt.query, "console") + geth.ExpectRegexp(tt.result) + geth.ExpectExit() + } +} + +// TestCustomBackend that the backend selection and detection (leveldb vs pebble) works properly. +func TestCustomBackend(t *testing.T) { + t.Parallel() + // Test pebble, but only on 64-bit platforms + if strconv.IntSize != 64 { + t.Skip("Custom backends are only available on 64-bit platform") + } + genesis := `{ + "alloc" : {}, + "coinbase" : "0x0000000000000000000000000000000000000000", + "difficulty" : "0x20000", + "extraData" : "", + "gasLimit" : "0x2fefd8", + "nonce" : "0x0000000000001338", + "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp" : "0x00", + "config" : { + "terminalTotalDifficultyPassed": true + } + }` + type backendTest struct { + initArgs []string + initExpect string + execArgs []string + execExpect string + } + testfunc := func(t *testing.T, tt backendTest) error { + // Create a temporary data directory to use and inspect later + datadir := t.TempDir() + + // Initialize the data directory with the custom genesis block + json := filepath.Join(datadir, "genesis.json") + if err := os.WriteFile(json, []byte(genesis), 0600); err != nil { + return fmt.Errorf("failed to write genesis file: %v", err) + } + { // Init + args := append(tt.initArgs, "--datadir", datadir, "init", json) + geth := runGeth(t, args...) + geth.ExpectRegexp(tt.initExpect) + geth.ExpectExit() + } + { // Exec + query + args := append(tt.execArgs, "--networkid", "1337", "--syncmode=full", "--cache", "16", + "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--authrpc.port", "0", + "--nodiscover", "--nat", "none", "--ipcdisable", + "--exec", "eth.getBlock(0).nonce", "console") + geth := runGeth(t, args...) + geth.ExpectRegexp(tt.execExpect) + geth.ExpectExit() + } + return nil + } + for i, tt := range []backendTest{ + { // When not specified, it should default to pebble + execArgs: []string{"--db.engine", "pebble"}, + execExpect: "0x0000000000001338", + }, + { // Explicit leveldb + initArgs: []string{"--db.engine", "leveldb"}, + execArgs: []string{"--db.engine", "leveldb"}, + execExpect: "0x0000000000001338", + }, + { // Explicit leveldb first, then autodiscover + initArgs: []string{"--db.engine", "leveldb"}, + execExpect: "0x0000000000001338", + }, + { // Explicit pebble + initArgs: []string{"--db.engine", "pebble"}, + execArgs: []string{"--db.engine", "pebble"}, + execExpect: "0x0000000000001338", + }, + { // Explicit pebble, then auto-discover + initArgs: []string{"--db.engine", "pebble"}, + execExpect: "0x0000000000001338", + }, + { // Can't start pebble on top of leveldb + initArgs: []string{"--db.engine", "leveldb"}, + execArgs: []string{"--db.engine", "pebble"}, + execExpect: `Fatal: Failed to register the Ethereum service: db.engine choice was pebble but found pre-existing leveldb database in specified data directory`, + }, + { // Can't start leveldb on top of pebble + initArgs: []string{"--db.engine", "pebble"}, + execArgs: []string{"--db.engine", "leveldb"}, + execExpect: `Fatal: Failed to register the Ethereum service: db.engine choice was leveldb but found pre-existing pebble database in specified data directory`, + }, + { // Reject invalid backend choice + initArgs: []string{"--db.engine", "mssql"}, + initExpect: `Fatal: Invalid choice for db.engine 'mssql', allowed 'leveldb' or 'pebble'`, + // Since the init fails, this will return the (default) mainnet genesis + // block nonce + execExpect: `0x0000000000000042`, + }, + } { + if err := testfunc(t, tt); err != nil { + t.Fatalf("test %d-leveldb: %v", i, err) + } + } +} diff --git a/cmd/geth/logging_test.go b/cmd/geth/logging_test.go new file mode 100644 index 0000000..f426b13 --- /dev/null +++ b/cmd/geth/logging_test.go @@ -0,0 +1,239 @@ +//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) + } + defer readFile.Close() + 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) + } + defer readFile.Close() + 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 0000000..f2a2c5d --- /dev/null +++ b/cmd/geth/logtestcmd_active.go @@ -0,0 +1,171 @@ +//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/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 { + { // 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 0000000..691ab5b --- /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 new file mode 100644 index 0000000..f6bb09e --- /dev/null +++ b/cmd/geth/main.go @@ -0,0 +1,465 @@ +// Copyright 2014 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 . + +// geth is a command-line client for Ethereum. +package main + +import ( + "fmt" + "os" + "sort" + "strconv" + "strings" + "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/console/prompt" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/node" + "go.uber.org/automaxprocs/maxprocs" + + // Force-load the tracer engines to trigger registration + _ "github.com/ethereum/go-ethereum/eth/tracers/js" + _ "github.com/ethereum/go-ethereum/eth/tracers/live" + _ "github.com/ethereum/go-ethereum/eth/tracers/native" + + "github.com/urfave/cli/v2" +) + +const ( + clientIdentifier = "geth" // Client identifier to advertise over the network +) + +var ( + // flags that configure the node + nodeFlags = flags.Merge([]cli.Flag{ + utils.IdentityFlag, + utils.UnlockedAccountFlag, + utils.PasswordFileFlag, + utils.BootnodesFlag, + utils.MinFreeDiskSpaceFlag, + utils.KeyStoreDirFlag, + utils.ExternalSignerFlag, + utils.NoUSBFlag, // deprecated + utils.USBFlag, + utils.SmartCardDaemonPathFlag, + utils.OverrideCancun, + utils.OverrideVerkle, + utils.EnablePersonal, + utils.TxPoolLocalsFlag, + utils.TxPoolNoLocalsFlag, + utils.TxPoolJournalFlag, + utils.TxPoolRejournalFlag, + utils.TxPoolPriceLimitFlag, + utils.TxPoolPriceBumpFlag, + utils.TxPoolAccountSlotsFlag, + utils.TxPoolGlobalSlotsFlag, + utils.TxPoolAccountQueueFlag, + utils.TxPoolGlobalQueueFlag, + utils.TxPoolLifetimeFlag, + utils.BlobPoolDataDirFlag, + utils.BlobPoolDataCapFlag, + utils.BlobPoolPriceBumpFlag, + utils.SyncModeFlag, + utils.SyncTargetFlag, + utils.ExitWhenSyncedFlag, + utils.GCModeFlag, + utils.SnapshotFlag, + 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, // deprecated + utils.EthRequiredBlocksFlag, + utils.LegacyWhitelistFlag, // deprecated + utils.BloomFilterSizeFlag, + utils.CacheFlag, + utils.CacheDatabaseFlag, + utils.CacheTrieFlag, + utils.CacheTrieJournalFlag, // deprecated + utils.CacheTrieRejournalFlag, // deprecated + utils.CacheGCFlag, + utils.CacheSnapshotFlag, + utils.CacheNoPrefetchFlag, + utils.CachePreimagesFlag, + utils.CacheLogSizeFlag, + utils.FDLimitFlag, + utils.CryptoKZGFlag, + utils.ListenPortFlag, + utils.DiscoveryPortFlag, + utils.MaxPeersFlag, + utils.MaxPendingPeersFlag, + utils.MiningEnabledFlag, // deprecated + utils.MinerGasLimitFlag, + utils.MinerGasPriceFlag, + utils.MinerEtherbaseFlag, // deprecated + utils.MinerExtraDataFlag, + utils.MinerRecommitIntervalFlag, + utils.MinerPendingFeeRecipientFlag, + utils.MinerNewPayloadTimeoutFlag, // deprecated + utils.NATFlag, + utils.NoDiscoverFlag, + utils.DiscoveryV4Flag, + utils.DiscoveryV5Flag, + utils.LegacyDiscoveryV5Flag, // deprecated + utils.NetrestrictFlag, + utils.NodeKeyFileFlag, + utils.NodeKeyHexFlag, + utils.DNSDiscoveryFlag, + utils.DeveloperFlag, + utils.DeveloperGasLimitFlag, + utils.DeveloperPeriodFlag, + utils.VMEnableDebugFlag, + utils.VMTraceFlag, + utils.VMTraceJsonConfigFlag, + utils.NetworkIdFlag, + utils.EthStatsURLFlag, + utils.NoCompactionFlag, + utils.GpoBlocksFlag, + utils.GpoPercentileFlag, + utils.GpoMaxGasPriceFlag, + utils.GpoIgnoreGasPriceFlag, + configFileFlag, + utils.LogDebugFlag, + utils.LogBacktraceAtFlag, + utils.BeaconApiFlag, + utils.BeaconApiHeaderFlag, + utils.BeaconThresholdFlag, + utils.BeaconNoFilterFlag, + utils.BeaconConfigFlag, + utils.BeaconGenesisRootFlag, + utils.BeaconGenesisTimeFlag, + utils.BeaconCheckpointFlag, + utils.CollectWitnessFlag, + }, utils.NetworkFlags, utils.DatabaseFlags) + + rpcFlags = []cli.Flag{ + utils.HTTPEnabledFlag, + utils.HTTPListenAddrFlag, + utils.HTTPPortFlag, + utils.HTTPCORSDomainFlag, + utils.AuthListenFlag, + utils.AuthPortFlag, + utils.AuthVirtualHostsFlag, + utils.JWTSecretFlag, + utils.HTTPVirtualHostsFlag, + utils.GraphQLEnabledFlag, + utils.GraphQLCORSDomainFlag, + utils.GraphQLVirtualHostsFlag, + utils.HTTPApiFlag, + utils.HTTPPathPrefixFlag, + utils.WSEnabledFlag, + utils.WSListenAddrFlag, + utils.WSPortFlag, + utils.WSApiFlag, + utils.WSAllowedOriginsFlag, + utils.WSPathPrefixFlag, + utils.IPCDisabledFlag, + utils.IPCPathFlag, + utils.InsecureUnlockAllowedFlag, + utils.RPCGlobalGasCapFlag, + utils.RPCGlobalEVMTimeoutFlag, + utils.RPCGlobalTxFeeCapFlag, + utils.AllowUnprotectedTxs, + utils.BatchRequestLimit, + utils.BatchResponseMaxSize, + } + + metricsFlags = []cli.Flag{ + utils.MetricsEnabledFlag, + utils.MetricsEnabledExpensiveFlag, + utils.MetricsHTTPFlag, + utils.MetricsPortFlag, + utils.MetricsEnableInfluxDBFlag, + utils.MetricsInfluxDBEndpointFlag, + utils.MetricsInfluxDBDatabaseFlag, + utils.MetricsInfluxDBUsernameFlag, + utils.MetricsInfluxDBPasswordFlag, + utils.MetricsInfluxDBTagsFlag, + utils.MetricsEnableInfluxDBV2Flag, + utils.MetricsInfluxDBTokenFlag, + utils.MetricsInfluxDBBucketFlag, + utils.MetricsInfluxDBOrganizationFlag, + } +) + +var app = flags.NewApp("the go-ethereum command line interface") + +func init() { + // Initialize the CLI app and start Geth + app.Action = geth + app.Commands = []*cli.Command{ + // See chaincmd.go: + initCommand, + importCommand, + exportCommand, + importHistoryCommand, + exportHistoryCommand, + importPreimagesCommand, + removedbCommand, + dumpCommand, + dumpGenesisCommand, + // See accountcmd.go: + accountCommand, + walletCommand, + // See consolecmd.go: + consoleCommand, + attachCommand, + javascriptCommand, + // See misccmd.go: + versionCommand, + versionCheckCommand, + licenseCommand, + // See config.go + dumpConfigCommand, + // see dbcmd.go + dbCommand, + // See cmd/utils/flags_legacy.go + utils.ShowDeprecated, + // See snapshot.go + snapshotCommand, + // See verkle.go + verkleCommand, + } + if logTestCommand != nil { + app.Commands = append(app.Commands, logTestCommand) + } + sort.Sort(cli.CommandsByName(app.Commands)) + + app.Flags = flags.Merge( + nodeFlags, + rpcFlags, + consoleFlags, + 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) + 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() + prompt.Stdin.Close() // Resets terminal mode. + return nil + } +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +// prepare manipulates memory cache allowance and setups metric system. +// This function should be called before launching devp2p stack. +func prepare(ctx *cli.Context) { + // If we're running a known preset, log it for convenience. + switch { + case ctx.IsSet(utils.GoerliFlag.Name): + log.Info("Starting Geth on Görli testnet...") + + case ctx.IsSet(utils.SepoliaFlag.Name): + log.Info("Starting Geth on Sepolia testnet...") + + case ctx.IsSet(utils.HoleskyFlag.Name): + log.Info("Starting Geth on Holesky testnet...") + + case ctx.IsSet(utils.DeveloperFlag.Name): + log.Info("Starting Geth in ephemeral dev mode...") + log.Warn(`You are running Geth in --dev mode. Please note the following: + + 1. This mode is only intended for fast, iterative development without assumptions on + security or persistence. + 2. The database is created in memory unless specified otherwise. Therefore, shutting down + your computer or losing power will wipe your entire block data and chain state for + your dev environment. + 3. A random, pre-allocated developer account will be available and unlocked as + eth.coinbase, which can be used for testing. The random dev account is temporary, + stored on a ramdisk, and will be lost if your machine is restarted. + 4. Mining is enabled by default. However, the client will only seal blocks if transactions + are pending in the mempool. The miner's minimum accepted gas price is 1. + 5. Networking is disabled; there is no listen-address, the maximum number of peers is set + to 0, and discovery is disabled. +`) + + case !ctx.IsSet(utils.NetworkIdFlag.Name): + log.Info("Starting Geth on Ethereum mainnet...") + } + // If we're a full node on mainnet without --cache specified, bump default cache allowance + if !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) { + // Make sure we're not on any supported preconfigured testnet either + 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! + log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096) + ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096)) + } + } + + // Start metrics export if enabled + utils.SetupMetrics(ctx) + + // Start system runtime metrics collection + go metrics.CollectProcessMetrics(3 * time.Second) +} + +// geth is the main entry point into the system if no special subcommand is run. +// It creates a default node based on the command line arguments and runs it in +// blocking mode, waiting for it to be shut down. +func geth(ctx *cli.Context) error { + if args := ctx.Args().Slice(); len(args) > 0 { + return fmt.Errorf("invalid command: %q", args[0]) + } + + prepare(ctx) + stack := makeFullNode(ctx) + defer stack.Close() + + startNode(ctx, stack, false) + stack.Wait() + return nil +} + +// startNode boots up the system node and all registered protocols, after which +// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the +// miner. +func startNode(ctx *cli.Context, stack *node.Node, isConsole bool) { + debug.Memsize.Add("node", stack) + + // Start up the node itself + utils.StartNode(ctx, stack, isConsole) + + // Unlock any account specifically requested + unlockAccounts(ctx, stack) + + // Register wallet event handlers to open and auto-derive wallets + events := make(chan accounts.WalletEvent, 16) + stack.AccountManager().Subscribe(events) + + // Create a client to interact with local geth node. + rpcClient := stack.Attach() + ethClient := ethclient.NewClient(rpcClient) + + go func() { + // Open any wallets already attached + for _, wallet := range stack.AccountManager().Wallets() { + if err := wallet.Open(""); err != nil { + log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) + } + } + // Listen for wallet event till termination + for event := range events { + switch event.Kind { + case accounts.WalletArrived: + if err := event.Wallet.Open(""); err != nil { + log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) + } + case accounts.WalletOpened: + status, _ := event.Wallet.Status() + log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) + + var derivationPaths []accounts.DerivationPath + if event.Wallet.URL().Scheme == "ledger" { + derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath) + } + derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath) + + event.Wallet.SelfDerive(derivationPaths, ethClient) + + case accounts.WalletDropped: + log.Info("Old wallet dropped", "url", event.Wallet.URL()) + event.Wallet.Close() + } + } + }() + + // Spawn a standalone goroutine for status synchronization monitoring, + // close the node when synchronization is complete if user required. + if ctx.Bool(utils.ExitWhenSyncedFlag.Name) { + go func() { + sub := stack.EventMux().Subscribe(downloader.DoneEvent{}) + defer sub.Unsubscribe() + for { + event := <-sub.Chan() + if event == nil { + continue + } + done, ok := event.Data.(downloader.DoneEvent) + if !ok { + continue + } + if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute { + log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(), + "age", common.PrettyAge(timestamp)) + stack.Close() + } + } + }() + } +} + +// unlockAccounts unlocks any account specifically requested. +func unlockAccounts(ctx *cli.Context, stack *node.Node) { + var unlocks []string + inputs := strings.Split(ctx.String(utils.UnlockedAccountFlag.Name), ",") + for _, input := range inputs { + if trimmed := strings.TrimSpace(input); trimmed != "" { + unlocks = append(unlocks, trimmed) + } + } + // Short circuit if there is no account to unlock. + if len(unlocks) == 0 { + return + } + // If insecure account unlocking is not allowed if node's APIs are exposed to external. + // Print warning log to user and skip unlocking. + if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() { + utils.Fatalf("Account unlock with HTTP access is forbidden!") + } + backends := stack.AccountManager().Backends(keystore.KeyStoreType) + if len(backends) == 0 { + log.Warn("Failed to unlock accounts, keystore is not available") + return + } + ks := backends[0].(*keystore.KeyStore) + passwords := utils.MakePasswordList(ctx) + for i, account := range unlocks { + unlockAccount(ks, account, i, passwords) + } +} diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go new file mode 100644 index 0000000..f3530c3 --- /dev/null +++ b/cmd/geth/misccmd.go @@ -0,0 +1,105 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + "runtime" + "strings" + + "github.com/ethereum/go-ethereum/internal/version" + "github.com/ethereum/go-ethereum/params" + "github.com/urfave/cli/v2" +) + +var ( + VersionCheckUrlFlag = &cli.StringFlag{ + Name: "check.url", + Usage: "URL to use when checking vulnerabilities", + Value: "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json", + } + VersionCheckVersionFlag = &cli.StringFlag{ + Name: "check.version", + Usage: "Version to check", + Value: version.ClientName(clientIdentifier), + } + versionCommand = &cli.Command{ + Action: printVersion, + Name: "version", + Usage: "Print version numbers", + ArgsUsage: " ", + Description: ` +The output of this command is supposed to be machine-readable. +`, + } + versionCheckCommand = &cli.Command{ + Action: versionCheck, + Flags: []cli.Flag{ + VersionCheckUrlFlag, + VersionCheckVersionFlag, + }, + Name: "version-check", + Usage: "Checks (online) for known Geth security vulnerabilities", + ArgsUsage: "", + Description: ` +The version-check command fetches vulnerability-information from https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json, +and displays information about any security vulnerabilities that affect the currently executing version. +`, + } + licenseCommand = &cli.Command{ + Action: license, + Name: "license", + Usage: "Display license information", + ArgsUsage: " ", + } +) + +func printVersion(ctx *cli.Context) error { + git, _ := version.VCS() + + fmt.Println(strings.Title(clientIdentifier)) + fmt.Println("Version:", params.VersionWithMeta) + if git.Commit != "" { + fmt.Println("Git Commit:", git.Commit) + } + if git.Date != "" { + fmt.Println("Git Commit Date:", git.Date) + } + fmt.Println("Architecture:", runtime.GOARCH) + fmt.Println("Go Version:", runtime.Version()) + fmt.Println("Operating System:", runtime.GOOS) + fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) + fmt.Printf("GOROOT=%s\n", runtime.GOROOT()) + return nil +} + +func license(_ *cli.Context) error { + fmt.Println(`Geth 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. + +Geth is distributed in the hope that 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 geth. If not, see .`) + return nil +} diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go new file mode 100644 index 0000000..1d32880 --- /dev/null +++ b/cmd/geth/run_test.go @@ -0,0 +1,120 @@ +// Copyright 2016 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" + "testing" + "time" + + "github.com/ethereum/go-ethereum/internal/cmdtest" + "github.com/ethereum/go-ethereum/internal/reexec" + "github.com/ethereum/go-ethereum/rpc" +) + +type testgeth struct { + *cmdtest.TestCmd + + // template variables for expect + Datadir string + Etherbase string +} + +func init() { + // Run the app if we've been exec'd as "geth-test" in runGeth. + reexec.Register("geth-test", func() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) + }) +} + +func TestMain(m *testing.M) { + // check if we have been reexec'd + if reexec.Init() { + return + } + 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 { + tt := &testgeth{} + tt.TestCmd = cmdtest.NewTestCmd(t, tt) + for i, arg := range args { + switch arg { + case "--datadir": + if i < len(args)-1 { + tt.Datadir = args[i+1] + } + case "--miner.etherbase": + if i < len(args)-1 { + tt.Etherbase = args[i+1] + } + } + } + if tt.Datadir == "" { + // The temporary datadir will be removed automatically if something fails below. + tt.Datadir = t.TempDir() + args = append([]string{"--datadir", tt.Datadir}, args...) + } + + // Boot "geth". This actually runs the test binary but the TestMain + // function will prevent any tests from running. + tt.Run("geth-test", args...) + + return tt +} + +// waitForEndpoint attempts to connect to an RPC endpoint until it succeeds. +func waitForEndpoint(t *testing.T, endpoint string, timeout time.Duration) { + probe := func() bool { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + c, err := rpc.DialContext(ctx, endpoint) + if c != nil { + _, err = c.SupportedModules() + c.Close() + } + return err == nil + } + + start := time.Now() + for { + if probe() { + return + } + if time.Since(start) > timeout { + t.Fatal("endpoint", endpoint, "did not open within", timeout) + } + time.Sleep(200 * time.Millisecond) + } +} diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go new file mode 100644 index 0000000..7d713ad --- /dev/null +++ b/cmd/geth/snapshot.go @@ -0,0 +1,694 @@ +// 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 ( + "bytes" + "encoding/json" + "errors" + "fmt" + "os" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "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/pruner" + "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/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/urfave/cli/v2" +) + +var ( + snapshotCommand = &cli.Command{ + Name: "snapshot", + Usage: "A set of commands based on the snapshot", + Description: "", + Subcommands: []*cli.Command{ + { + Name: "prune-state", + Usage: "Prune stale ethereum state data based on the snapshot", + ArgsUsage: "", + Action: pruneState, + Flags: flags.Merge([]cli.Flag{ + utils.BloomFilterSizeFlag, + }, utils.NetworkFlags, utils.DatabaseFlags), + Description: ` +geth snapshot prune-state +will prune historical state data with the help of the state snapshot. +All trie nodes and contract codes that do not belong to the specified +version state will be deleted from the database. After pruning, only +two version states are available: genesis and the specific one. + +The default pruning target is the HEAD-127 state. + +WARNING: it's only supported in hash mode(--state.scheme=hash)". +`, + }, + { + Name: "verify-state", + Usage: "Recalculate state hash based on the snapshot for verification", + ArgsUsage: "", + Action: verifyState, + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), + Description: ` +geth snapshot verify-state +will traverse the whole accounts and storages set based on the specified +snapshot and recalculate the root hash of state for verification. +In other words, this command does the snapshot to trie conversion. +`, + }, + { + Name: "check-dangling-storage", + Usage: "Check that there is no 'dangling' snap storage", + ArgsUsage: "", + Action: checkDanglingStorage, + 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. +`, + }, + { + Name: "inspect-account", + Usage: "Check all snapshot layers for the specific account", + ArgsUsage: "
", + Action: checkAccount, + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), + Description: ` +geth snapshot inspect-account
checks all snapshot layers and prints out +information about the specified address. +`, + }, + { + Name: "traverse-state", + Usage: "Traverse the state with given root hash and perform quick verification", + ArgsUsage: "", + Action: traverseState, + 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 +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 also usable without snapshot enabled. +`, + }, + { + Name: "traverse-rawstate", + Usage: "Traverse the state with given root hash and perform detailed verification", + ArgsUsage: "", + Action: traverseRawState, + 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. + +It's also usable without snapshot enabled. +`, + }, + { + Name: "dump", + Usage: "Dump a specific block from storage (same as 'geth dump' but using snapshots)", + ArgsUsage: "[? | ]", + Action: dumpState, + Flags: flags.Merge([]cli.Flag{ + utils.ExcludeCodeFlag, + utils.ExcludeStorageFlag, + utils.StartKeyFlag, + utils.DumpLimitFlag, + }, 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. + +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. +`, + }, + }, + } +) + +// Deprecation: this command should be deprecated once the hash-based +// scheme is deprecated. +func pruneState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chaindb := utils.MakeChainDatabase(ctx, stack, false) + defer chaindb.Close() + + if rawdb.ReadStateScheme(chaindb) != rawdb.HashScheme { + log.Crit("Offline pruning is not required for path scheme") + } + prunerconfig := pruner.Config{ + Datadir: stack.ResolvePath(""), + BloomSize: ctx.Uint64(utils.BloomFilterSizeFlag.Name), + } + pruner, err := pruner.NewPruner(chaindb, prunerconfig) + if err != nil { + log.Error("Failed to open snapshot tree", "err", err) + return err + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var targetRoot common.Hash + if ctx.NArg() == 1 { + targetRoot, err = parseRoot(ctx.Args().First()) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return err + } + } + if err = pruner.Prune(targetRoot); err != nil { + log.Error("Failed to prune state", "err", err) + return err + } + return nil +} + +func verifyState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + 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") + return errors.New("no head block") + } + 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, triedb, headBlock.Root()) + if err != nil { + log.Error("Failed to open snapshot tree", "err", err) + return err + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var root = headBlock.Root() + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args().First()) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return err + } + } + if err := snaptree.Verify(root); err != nil { + log.Error("Failed to verify state", "root", root, "err", err) + return err + } + log.Info("Verified the state", "root", root) + return snapshot.CheckDanglingStorage(chaindb) +} + +// checkDanglingStorage iterates the snap storage data, and verifies that all +// storage also has corresponding account data. +func checkDanglingStorage(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + return snapshot.CheckDanglingStorage(db) +} + +// traverseState is a helper function used for pruning verification. +// Basically it just iterates the trie, ensure all nodes and associated +// contract codes are present. +func traverseState(ctx *cli.Context) error { + 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() + + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var ( + root common.Hash + err error + ) + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args().First()) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return err + } + log.Info("Start traversing the state", "root", root) + } else { + root = headBlock.Root() + log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) + } + t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) + if err != nil { + log.Error("Failed to open trie", "root", root, "err", err) + return err + } + var ( + accounts int + slots int + codes int + lastReport time.Time + start = time.Now() + ) + acctIt, err := t.NodeIterator(nil) + if err != nil { + log.Error("Failed to open iterator", "root", root, "err", err) + return err + } + accIter := trie.NewIterator(acctIt) + for accIter.Next() { + accounts += 1 + var acc types.StateAccount + if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { + log.Error("Invalid account encountered during traversal", "err", err) + return err + } + if acc.Root != types.EmptyRootHash { + id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root) + storageTrie, err := trie.NewStateTrie(id, triedb) + if err != nil { + log.Error("Failed to open storage trie", "root", acc.Root, "err", err) + return err + } + storageIt, err := storageTrie.NodeIterator(nil) + if err != nil { + log.Error("Failed to open storage iterator", "root", acc.Root, "err", err) + return err + } + storageIter := trie.NewIterator(storageIt) + for storageIter.Next() { + slots += 1 + + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + } + if storageIter.Err != nil { + log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) + return storageIter.Err + } + } + if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) { + if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { + log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) + return errors.New("missing code") + } + codes += 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 accIter.Err != nil { + log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err) + return accIter.Err + } + log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// traverseRawState is a helper function used for pruning verification. +// Basically it just iterates the trie, ensure all nodes and associated +// contract codes are present. It's basically identical to traverseState +// but it will check each trie node. +func traverseRawState(ctx *cli.Context) error { + 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() + + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var ( + root common.Hash + err error + ) + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args().First()) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return err + } + log.Info("Start traversing the state", "root", root) + } else { + root = headBlock.Root() + log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) + } + t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) + if err != nil { + log.Error("Failed to open trie", "root", root, "err", err) + return err + } + var ( + nodes int + accounts int + slots int + codes int + lastReport time.Time + start = time.Now() + hasher = crypto.NewKeccakState() + got = make([]byte, 32) + ) + accIter, err := t.NodeIterator(nil) + if err != nil { + log.Error("Failed to open iterator", "root", root, "err", err) + return err + } + reader, err := triedb.Reader(root) + if err != nil { + log.Error("State is non-existent", "root", root) + return nil + } + for accIter.Next(true) { + nodes += 1 + node := accIter.Hash() + + // Check the present for non-empty hash node(embedded node doesn't + // have their own hash). + if node != (common.Hash{}) { + 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") + } + hasher.Reset() + hasher.Write(blob) + hasher.Read(got) + if !bytes.Equal(got, node.Bytes()) { + log.Error("Invalid trie node(account)", "hash", node.Hex(), "value", blob) + return errors.New("invalid account node") + } + } + // If it's a leaf node, yes we are touching an account, + // dig into the storage trie further. + if accIter.Leaf() { + accounts += 1 + var acc types.StateAccount + if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { + log.Error("Invalid account encountered during traversal", "err", err) + return errors.New("invalid account") + } + if acc.Root != types.EmptyRootHash { + id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root) + storageTrie, err := trie.NewStateTrie(id, triedb) + if err != nil { + log.Error("Failed to open storage trie", "root", acc.Root, "err", err) + return errors.New("missing storage trie") + } + storageIter, err := storageTrie.NodeIterator(nil) + if err != nil { + log.Error("Failed to open storage iterator", "root", acc.Root, "err", err) + return err + } + for storageIter.Next(true) { + nodes += 1 + node := storageIter.Hash() + + // Check the presence for non-empty hash node(embedded node doesn't + // have their own hash). + if node != (common.Hash{}) { + 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") + } + hasher.Reset() + hasher.Write(blob) + hasher.Read(got) + if !bytes.Equal(got, node.Bytes()) { + log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob) + return errors.New("invalid storage node") + } + } + // Bump the counter if it's leaf node. + 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()) + return storageIter.Error() + } + } + if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) { + if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { + log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) + return errors.New("missing code") + } + codes += 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 accIter.Error() != nil { + log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error()) + return accIter.Error() + } + log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +func parseRoot(input string) (common.Hash, error) { + var h common.Hash + if err := h.UnmarshalText([]byte(input)); err != nil { + return h, err + } + return h, nil +} + +func dumpState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + conf, root, err := parseDumpConfig(ctx, db) + 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, triedb, root) + if err != nil { + return err + } + accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start)) + if err != nil { + return err + } + defer accIt.Release() + + log.Info("Snapshot dumping started", "root", root) + var ( + start = time.Now() + logged = time.Now() + accounts uint64 + ) + enc := json.NewEncoder(os.Stdout) + enc.Encode(struct { + Root common.Hash `json:"root"` + }{root}) + for accIt.Next() { + account, err := types.FullAccount(accIt.Account()) + if err != nil { + return err + } + da := &state.DumpAccount{ + 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)) + } + if !conf.SkipStorage { + da.Storage = make(map[common.Hash]string) + + stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{}) + if err != nil { + return err + } + for stIt.Next() { + da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot()) + } + } + enc.Encode(da) + accounts++ + if time.Since(logged) > 8*time.Second { + log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts, + "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + if conf.Max > 0 && accounts >= conf.Max { + break + } + } + log.Info("Snapshot dumping complete", "accounts", accounts, + "elapsed", common.PrettyDuration(time.Since(start))) + 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 { + if ctx.NArg() != 1 { + return errors.New("need arg") + } + var ( + hash common.Hash + addr common.Address + ) + switch arg := ctx.Args().First(); len(arg) { + case 40, 42: + addr = common.HexToAddress(arg) + hash = crypto.Keccak256Hash(addr.Bytes()) + case 64, 66: + hash = common.HexToHash(arg) + default: + return errors.New("malformed address or hash") + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + chaindb := utils.MakeChainDatabase(ctx, stack, true) + defer chaindb.Close() + start := time.Now() + log.Info("Checking difflayer journal", "address", addr, "hash", hash) + if err := snapshot.CheckJournalAccount(chaindb, hash); err != nil { + return err + } + log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start))) + return nil +} diff --git a/cmd/geth/testdata/blockchain.blocks b/cmd/geth/testdata/blockchain.blocks new file mode 100644 index 0000000000000000000000000000000000000000..d29453d3e53b4382cc3ff4254e1931182a6c630d GIT binary patch literal 23287 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":""} +{"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 0000000..5c5316b --- /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 0000000..e3b5621 --- /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/password.txt b/cmd/geth/testdata/password.txt new file mode 100644 index 0000000..f6ea049 --- /dev/null +++ b/cmd/geth/testdata/password.txt @@ -0,0 +1 @@ +foobar \ No newline at end of file diff --git a/cmd/geth/testdata/passwords.txt b/cmd/geth/testdata/passwords.txt new file mode 100644 index 0000000..96f98c7 --- /dev/null +++ b/cmd/geth/testdata/passwords.txt @@ -0,0 +1,3 @@ +foobar +foobar +foobar diff --git a/cmd/geth/testdata/vcheck/data.json b/cmd/geth/testdata/vcheck/data.json new file mode 100644 index 0000000..e7ee2bf --- /dev/null +++ b/cmd/geth/testdata/vcheck/data.json @@ -0,0 +1,61 @@ +[ + { + "name": "CorruptedDAG", + "uid": "GETH-2020-01", + "summary": "Mining nodes will generate erroneous PoW on epochs > `385`.", + "description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.", + "links": [ + "https://github.com/ethereum/go-ethereum/pull/21793", + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49" + ], + "introduced": "v1.6.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Medium", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.2(1|2|3)-.*" + }, + { + "name": "GoCrash", + "uid": "GETH-2020-02", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", + "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", + "https://github.com/golang/go/issues/42552" + ], + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" + }, + { + "name": "ShallowCopy", + "uid": "GETH-2020-03", + "summary": "A consensus flaw in Geth, related to `datacopy` precompile", + "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.7", + "fixed": "v1.9.17", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" + }, + { + "name": "GethCrash", + "uid": "GETH-2020-04", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", + "description": "Full details to be disclosed at a later date", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.16", + "fixed": "v1.9.18", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9.(16|17).*$" + } +] 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 0000000..eaea9f9 --- /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/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 new file mode 100644 index 0000000..f9066d4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: timestamp:1605618622 file:vulnerabilities.json +osAPs4QPdDkmiWQxqeMIzYv/b+ZGxJ+19Sbrk1Cpq4t2gHBT+lqFtwL3OCzKWWyjGRTmHfsVGBYpzEdPRQ0/BQ== diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 new file mode 100644 index 0000000..a89a83d --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 @@ -0,0 +1,4 @@ +untrusted comment: Here's a comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 new file mode 100644 index 0000000..6fd33b1 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 @@ -0,0 +1,4 @@ +untrusted comment: One more (untrusted) comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/minisign.pub b/cmd/geth/testdata/vcheck/minisign.pub new file mode 100644 index 0000000..183dce5 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisign.pub @@ -0,0 +1,2 @@ +untrusted comment: minisign public key 284E00B52C269624 +RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp diff --git a/cmd/geth/testdata/vcheck/minisign.sec b/cmd/geth/testdata/vcheck/minisign.sec new file mode 100644 index 0000000..5c50715 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisign.sec @@ -0,0 +1,2 @@ +untrusted comment: minisign encrypted secret key +RWRTY0Iyz8kmPMKrqk6DCtlO9a33akKiaOQG1aLolqDxs52qvPoAAAACAAAAAAAAAEAAAAAArEiggdvyn6+WzTprirLtgiYQoU+ihz/HyGgjhuF+Pz2ddMduyCO+xjCHeq+vgVVW039fbsI8hW6LRGJZLBKV5/jdxCXAVVQE7qTQ6xpEdO0z8Z731/pV1hlspQXG2PNd16NMtwd9dWw= diff --git a/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig b/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig new file mode 100644 index 0000000..3d5fcac --- /dev/null +++ b/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig @@ -0,0 +1,2 @@ +untrusted comment: verify with ./signifykey.pub +RWSKLNhZb0KdAbhRUhW2LQZXdnwttu2SYhM9EuC4mMgOJB85h7/YIPupf8/ldTs4N8e9Y/fhgdY40q5LQpt5IFC62fq0v8U1/w8= diff --git a/cmd/geth/testdata/vcheck/signifykey.pub b/cmd/geth/testdata/vcheck/signifykey.pub new file mode 100644 index 0000000..328f973 --- /dev/null +++ b/cmd/geth/testdata/vcheck/signifykey.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/ diff --git a/cmd/geth/testdata/vcheck/signifykey.sec b/cmd/geth/testdata/vcheck/signifykey.sec new file mode 100644 index 0000000..3279a2e --- /dev/null +++ b/cmd/geth/testdata/vcheck/signifykey.sec @@ -0,0 +1,2 @@ +untrusted comment: signify secret key +RWRCSwAAACpLQDLawSQCtI7eAVIvaiHzjTsTyJsfV5aKLNhZb0KdAWeICXJGa93/bHAcsY6jUh9I8RdEcDWEoGxmaXZC+IdVBPxDpkix9fBRGEUdKWHi3dOfqME0YRzErWI5AVg3cRw= diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 new file mode 100644 index 0000000..f9066d4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: timestamp:1605618622 file:vulnerabilities.json +osAPs4QPdDkmiWQxqeMIzYv/b+ZGxJ+19Sbrk1Cpq4t2gHBT+lqFtwL3OCzKWWyjGRTmHfsVGBYpzEdPRQ0/BQ== diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 new file mode 100644 index 0000000..a89a83d --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 @@ -0,0 +1,4 @@ +untrusted comment: Here's a comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 new file mode 100644 index 0000000..6fd33b1 --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 @@ -0,0 +1,4 @@ +untrusted comment: One more (untrusted) comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/vulnerabilities.json b/cmd/geth/testdata/vcheck/vulnerabilities.json new file mode 100644 index 0000000..31a34de --- /dev/null +++ b/cmd/geth/testdata/vcheck/vulnerabilities.json @@ -0,0 +1,202 @@ +[ + { + "name": "CorruptedDAG", + "uid": "GETH-2020-01", + "summary": "Mining nodes will generate erroneous PoW on epochs > `385`.", + "description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.", + "links": [ + "https://github.com/ethereum/go-ethereum/pull/21793", + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-v592-xf75-856p" + ], + "introduced": "v1.6.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Medium", + "CVE": "CVE-2020-26240", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.\\d-.*|Geth\\/v1\\.9\\.1.*|Geth\\/v1\\.9\\.2(0|1|2|3)-.*" + }, + { + "name": "Denial of service due to Go CVE-2020-28362", + "uid": "GETH-2020-02", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", + "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", + "https://github.com/golang/go/issues/42552", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-m6gx-rhvj-fh52" + ], + "introduced": "v0.0.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Critical", + "CVE": "CVE-2020-28362", + "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" + }, + { + "name": "ShallowCopy", + "uid": "GETH-2020-03", + "summary": "A consensus flaw in Geth, related to `datacopy` precompile", + "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-69v6-xc2j-r2jf" + ], + "introduced": "v1.9.7", + "fixed": "v1.9.17", + "published": "2020-11-12", + "severity": "Critical", + "CVE": "CVE-2020-26241", + "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" + }, + { + "name": "Geth DoS via MULMOD", + "uid": "GETH-2020-04", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", + "description": "Affected versions suffer from a vulnerability which can be exploited through the `MULMOD` operation, by specifying a modulo of `0`: `mulmod(a,b,0)`, causing a `panic` in the underlying library. \nThe crash was in the `uint256` library, where a buffer [underflowed](https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L442).\n\n\tif `d == 0`, `dLen` remains `0`\n\nand https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L451 will try to access index `[-1]`.\n\nThe `uint256` library was first merged in this [commit](https://github.com/ethereum/go-ethereum/commit/cf6674539c589f80031f3371a71c6a80addbe454), on 2020-06-08. \nExploiting this vulnerabilty would cause all vulnerable nodes to drop off the network. \n\nThe issue was brought to our attention through a [bug report](https://github.com/ethereum/go-ethereum/issues/21367), showing a `panic` occurring on sync from genesis on the Ropsten network.\n \nIt was estimated that the least obvious way to fix this would be to merge the fix into `uint256`, make a new release of that library and then update the geth-dependency.\n", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-jm5c-rv3w-w83m", + "https://github.com/holiman/uint256/releases/tag/v1.1.1", + "https://github.com/holiman/uint256/pull/80", + "https://github.com/ethereum/go-ethereum/pull/21368" + ], + "introduced": "v1.9.16", + "fixed": "v1.9.18", + "published": "2020-11-12", + "severity": "Critical", + "CVE": "CVE-2020-26242", + "check": "Geth\\/v1\\.9.(16|17).*$" + }, + { + "name": "LES Server DoS via GetProofsV2", + "uid": "GETH-2020-05", + "summary": "A DoS vulnerability can make a LES server crash.", + "description": "A DoS vulnerability can make a LES server crash via malicious GetProofsV2 request from a connected LES client.\n\nThe vulnerability was patched in #21896.\n\nThis vulnerability only concern users explicitly running geth as a light server", + "links": [ + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-r33q-22hv-j29q", + "https://github.com/ethereum/go-ethereum/pull/21896" + ], + "introduced": "v1.8.0", + "fixed": "v1.9.25", + "published": "2020-12-10", + "severity": "Medium", + "CVE": "CVE-2020-26264", + "check": "(Geth\\/v1\\.8\\.*)|(Geth\\/v1\\.9\\.\\d-.*)|(Geth\\/v1\\.9\\.1\\d-.*)|(Geth\\/v1\\.9\\.(20|21|22|23|24)-.*)$" + }, + { + "name": "SELFDESTRUCT-recreate consensus flaw", + "uid": "GETH-2020-06", + "introduced": "v1.9.4", + "fixed": "v1.9.20", + "summary": "A consensus-vulnerability in Geth could cause a chain split, where vulnerable versions refuse to accept the canonical chain.", + "description": "A flaw was repoted at 2020-08-11 by John Youngseok Yang (Software Platform Lab), where a particular sequence of transactions could cause a consensus failure.\n\n- Tx 1:\n - `sender` invokes `caller`.\n - `caller` invokes `0xaa`. `0xaa` has 3 wei, does a self-destruct-to-self\n - `caller` does a `1 wei` -call to `0xaa`, who thereby has 1 wei (the code in `0xaa` still executed, since the tx is still ongoing, but doesn't redo the selfdestruct, it takes a different path if callvalue is non-zero)\n\n-Tx 2:\n - `sender` does a 5-wei call to 0xaa. No exec (since no code). \n\nIn geth, the result would be that `0xaa` had `6 wei`, whereas OE reported (correctly) `5` wei. Furthermore, in geth, if the second tx was not executed, the `0xaa` would be destructed, resulting in `0 wei`. Thus obviously wrong. \n\nIt was determined that the root cause was this [commit](https://github.com/ethereum/go-ethereum/commit/223b950944f494a5b4e0957fd9f92c48b09037ad) from [this PR](https://github.com/ethereum/go-ethereum/pull/19953). The semantics of `createObject` was subtly changd, into returning a non-nil object (with `deleted=true`) where it previously did not if the account had been destructed. This return value caused the new object to inherit the old `balance`.\n", + "links": [ + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-xw37-57qp-9mm4" + ], + "published": "2020-12-10", + "severity": "High", + "CVE": "CVE-2020-26265", + "check": "(Geth\\/v1\\.9\\.(4|5|6|7|8|9)-.*)|(Geth\\/v1\\.9\\.1\\d-.*)$" + }, + { + "name": "Not ready for London upgrade", + "uid": "GETH-2021-01", + "summary": "The client is not ready for the 'London' technical upgrade, and will deviate from the canonical chain when the London upgrade occurs (at block '12965000' around August 4, 2021.", + "description": "At (or around) August 4, Ethereum will undergo a technical upgrade called 'London'. Clients not upgraded will fail to progress on the canonical chain.", + "links": [ + "https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/london.md", + "https://notes.ethereum.org/@timbeiko/ropsten-postmortem" + ], + "introduced": "v1.10.1", + "fixed": "v1.10.6", + "published": "2021-07-22", + "severity": "High", + "check": "(Geth\\/v1\\.10\\.(1|2|3|4|5)-.*)$" + }, + { + "name": "RETURNDATA corruption via datacopy", + "uid": "GETH-2021-02", + "summary": "A consensus-flaw in the Geth EVM could cause a node to deviate from the canonical chain.", + "description": "A memory-corruption bug within the EVM can cause a consensus error, where vulnerable nodes obtain a different `stateRoot` when processing a maliciously crafted transaction. This, in turn, would lead to the chain being split: mainnet splitting in two forks.\n\nAll Geth versions supporting the London hard fork are vulnerable (the bug is older than London), so all users should update.\n\nThis bug was exploited on Mainnet at block 13107518.\n\nCredits for the discovery go to @guidovranken (working for Sentnl during an audit of the Telos EVM) and reported via bounty@ethereum.org.", + "links": [ + "https://github.com/ethereum/go-ethereum/blob/master/docs/postmortems/2021-08-22-split-postmortem.md", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-9856-9gg9-qcmq", + "https://github.com/ethereum/go-ethereum/releases/tag/v1.10.8" + ], + "introduced": "v1.10.0", + "fixed": "v1.10.8", + "published": "2021-08-24", + "severity": "High", + "CVE": "CVE-2021-39137", + "check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7)-.*)$" + }, + { + "name": "DoS via malicious `snap/1` request", + "uid": "GETH-2021-03", + "summary": "A vulnerable node is susceptible to crash when processing a maliciously crafted message from a peer, via the snap/1 protocol. The crash can be triggered by sending a malicious snap/1 GetTrieNodes package.", + "description": "The `snap/1` protocol handler contains two vulnerabilities related to the `GetTrieNodes` packet, which can be exploited to crash the node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v)", + "links": [ + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v", + "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities", + "https://github.com/ethereum/go-ethereum/pull/23657" + ], + "introduced": "v1.10.0", + "fixed": "v1.10.9", + "published": "2021-10-24", + "severity": "Medium", + "CVE": "CVE-2021-41173", + "check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8)-.*)$" + }, + { + "name": "DoS via malicious p2p message", + "uid": "GETH-2022-01", + "summary": "A vulnerable node can crash via p2p messages sent from an attacker node, if running with non-default log options.", + "description": "A vulnerable node, if configured to use high verbosity logging, can be made to crash when handling specially crafted p2p messages sent from an attacker node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5)", + "links": [ + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5", + "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities", + "https://github.com/ethereum/go-ethereum/pull/24507" + ], + "introduced": "v1.10.0", + "fixed": "v1.10.17", + "published": "2022-05-11", + "severity": "Low", + "CVE": "CVE-2022-29177", + "check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)-.*)$" + }, + { + "name": "DoS via malicious p2p message", + "uid": "GETH-2023-01", + "summary": "A vulnerable node can be made to consume unbounded amounts of memory when handling specially crafted p2p messages sent from an attacker node.", + "description": "The p2p handler spawned a new goroutine to respond to ping requests. By flooding a node with ping requests, an unbounded number of goroutines can be created, leading to resource exhaustion and potentially crash due to OOM.", + "links": [ + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-ppjg-v974-84cm", + "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities" + ], + "introduced": "v1.10.0", + "fixed": "v1.12.1", + "published": "2023-09-06", + "severity": "High", + "CVE": "CVE-2023-40591", + "check": "(Geth\\/v1\\.(10|11)\\..*)|(Geth\\/v1\\.12\\.0-.*)$" + }, + { + "name": "DoS via malicious p2p message", + "uid": "GETH-2024-01", + "summary": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node.", + "description": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node. Full details will be available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652)", + "links": [ + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652", + "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities" + ], + "introduced": "v1.10.0", + "fixed": "v1.13.15", + "published": "2024-05-06", + "severity": "High", + "CVE": "CVE-2024-32972", + "check": "(Geth\\/v1\\.(10|11|12)\\..*)|(Geth\\/v1\\.13\\.\\d-.*)|(Geth\\/v1\\.13\\.1(0|1|2|3|4)-.*)$" + } +] diff --git a/cmd/geth/testdata/wrong-passwords.txt b/cmd/geth/testdata/wrong-passwords.txt new file mode 100644 index 0000000..7d1e338 --- /dev/null +++ b/cmd/geth/testdata/wrong-passwords.txt @@ -0,0 +1,3 @@ +wrong +wrong +wrong diff --git a/cmd/geth/verkle.go b/cmd/geth/verkle.go new file mode 100644 index 0000000..9eb37fb --- /dev/null +++ b/cmd/geth/verkle.go @@ -0,0 +1,214 @@ +// 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 main + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-verkle" + "github.com/urfave/cli/v2" +) + +var ( + zero [32]byte + + verkleCommand = &cli.Command{ + Name: "verkle", + Usage: "A set of experimental verkle tree management commands", + Description: "", + Subcommands: []*cli.Command{ + { + Name: "verify", + Usage: "verify the conversion of a MPT into a verkle tree", + ArgsUsage: "", + Action: verifyVerkle, + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), + Description: ` +geth verkle verify +This command takes a root commitment and attempts to rebuild the tree. + `, + }, + { + Name: "dump", + Usage: "Dump a verkle tree to a DOT file", + ArgsUsage: " [ ...]", + Action: expandVerkle, + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), + Description: ` +geth verkle dump [ ...] +This command will produce a dot file representing the tree, rooted at . +in which key1, key2, ... are expanded. + `, + }, + }, + } +) + +// recurse into each child to ensure they can be loaded from the db. The tree isn't rebuilt +// (only its nodes are loaded) so there is no need to flush them, the garbage collector should +// take care of that for us. +func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error { + switch node := root.(type) { + case *verkle.InternalNode: + for i, child := range node.Children() { + childC := child.Commit().Bytes() + + childS, err := resolver(childC[:]) + if bytes.Equal(childC[:], zero[:]) { + continue + } + if err != nil { + 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) + if err != nil { + return fmt.Errorf("decode error child %x in db: %w", child.Commitment().Bytes(), err) + } + if err := checkChildren(childN, resolver); err != nil { + return fmt.Errorf("%x%w", i, err) // write the path to the erroring node + } + } + case *verkle.LeafNode: + // sanity check: ensure at least one value is non-zero + + for i := 0; i < verkle.NodeWidth; i++ { + if len(node.Value(i)) != 0 { + return nil + } + } + return errors.New("both balance and nonce are 0") + case verkle.Empty: + // nothing to do + default: + return fmt.Errorf("unsupported type encountered %v", root) + } + + return nil +} + +func verifyVerkle(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + 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") + return errors.New("no head block") + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var ( + rootC common.Hash + err error + ) + if ctx.NArg() == 1 { + rootC, err = parseRoot(ctx.Args().First()) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + log.Info("Rebuilding the tree", "root", rootC) + } else { + rootC = headBlock.Root() + log.Info("Rebuilding the tree", "root", rootC, "number", headBlock.NumberU64()) + } + + serializedRoot, err := chaindb.Get(rootC[:]) + if err != nil { + return err + } + root, err := verkle.ParseNode(serializedRoot, 0) + if err != nil { + return err + } + + if err := checkChildren(root, chaindb.Get); err != nil { + log.Error("Could not rebuild the tree from the database", "err", err) + return err + } + + log.Info("Tree was rebuilt from the database") + return nil +} + +func expandVerkle(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chaindb := utils.MakeChainDatabase(ctx, stack, true) + defer chaindb.Close() + var ( + rootC common.Hash + keylist [][]byte + err error + ) + if ctx.NArg() >= 2 { + rootC, err = parseRoot(ctx.Args().First()) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + keylist = make([][]byte, 0, ctx.Args().Len()-1) + args := ctx.Args().Slice() + for i := range args[1:] { + key, err := hex.DecodeString(args[i+1]) + log.Info("decoded key", "arg", args[i+1], "key", key) + if err != nil { + return fmt.Errorf("error decoding key #%d: %w", i+1, err) + } + keylist = append(keylist, key) + } + log.Info("Rebuilding the tree", "root", rootC) + } else { + return fmt.Errorf("usage: %s root key1 [key 2...]", ctx.App.Name) + } + + serializedRoot, err := chaindb.Get(rootC[:]) + if err != nil { + return err + } + root, err := verkle.ParseNode(serializedRoot, 0) + if err != nil { + return err + } + + for i, key := range keylist { + log.Info("Reading key", "index", i, "key", keylist[0]) + root.Get(key, chaindb.Get) + } + + if err := os.WriteFile("dump.dot", []byte(verkle.ToDot(root)), 0600); err != nil { + log.Error("Failed to dump file", "err", err) + } else { + log.Info("Tree was dumped to file", "file", "dump.dot") + } + return nil +} diff --git a/cmd/geth/version_check.go b/cmd/geth/version_check.go new file mode 100644 index 0000000..2375567 --- /dev/null +++ b/cmd/geth/version_check.go @@ -0,0 +1,170 @@ +// 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 ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "regexp" + "strings" + + "github.com/ethereum/go-ethereum/log" + "github.com/jedisct1/go-minisign" + "github.com/urfave/cli/v2" +) + +var gethPubKeys []string = []string{ + //@holiman, minisign public key FB1D084D39BAEC24 + "RWQk7Lo5TQgd+wxBNZM+Zoy+7UhhMHaWKzqoes9tvSbFLJYZhNTbrIjx", + //minisign public key 138B1CA303E51687 + "RWSHFuUDoxyLEzjszuWZI1xStS66QTyXFFZG18uDfO26CuCsbckX1e9J", + //minisign public key FD9813B2D2098484 + "RWSEhAnSshOY/b+GmaiDkObbCWefsAoavjoLcPjBo1xn71yuOH5I+Lts", +} + +type vulnJson struct { + Name string + Uid string + Summary string + Description string + Links []string + Introduced string + Fixed string + Published string + Severity string + Check string + CVE string +} + +func versionCheck(ctx *cli.Context) error { + url := ctx.String(VersionCheckUrlFlag.Name) + version := ctx.String(VersionCheckVersionFlag.Name) + log.Info("Checking vulnerabilities", "version", version, "url", url) + return checkCurrent(url, version) +} + +func checkCurrent(url, current string) error { + var ( + data []byte + sig []byte + err error + ) + if data, err = fetch(url); err != nil { + return fmt.Errorf("could not retrieve data: %w", err) + } + if sig, err = fetch(fmt.Sprintf("%v.minisig", url)); err != nil { + return fmt.Errorf("could not retrieve signature: %w", err) + } + if err = verifySignature(gethPubKeys, data, sig); err != nil { + return err + } + var vulns []vulnJson + if err = json.Unmarshal(data, &vulns); err != nil { + return err + } + allOk := true + for _, vuln := range vulns { + r, err := regexp.Compile(vuln.Check) + if err != nil { + return err + } + if r.MatchString(current) { + allOk = false + fmt.Printf("## Vulnerable to %v (%v)\n\n", vuln.Uid, vuln.Name) + fmt.Printf("Severity: %v\n", vuln.Severity) + fmt.Printf("Summary : %v\n", vuln.Summary) + fmt.Printf("Fixed in: %v\n", vuln.Fixed) + if len(vuln.CVE) > 0 { + fmt.Printf("CVE: %v\n", vuln.CVE) + } + if len(vuln.Links) > 0 { + fmt.Printf("References:\n") + for _, ref := range vuln.Links { + fmt.Printf("\t- %v\n", ref) + } + } + fmt.Println() + } + } + if allOk { + fmt.Println("No vulnerabilities found") + } + return nil +} + +// fetch makes an HTTP request to the given url and returns the response body +func fetch(url string) ([]byte, error) { + if filep := strings.TrimPrefix(url, "file://"); filep != url { + return os.ReadFile(filep) + } + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + return body, nil +} + +// verifySignature checks that the sigData is a valid signature of the given +// data, for pubkey GethPubkey +func verifySignature(pubkeys []string, data, sigdata []byte) error { + sig, err := minisign.DecodeSignature(string(sigdata)) + if err != nil { + return err + } + // find the used key + var key *minisign.PublicKey + for _, pubkey := range pubkeys { + pub, err := minisign.NewPublicKey(pubkey) + if err != nil { + // our pubkeys should be parseable + return err + } + if pub.KeyId != sig.KeyId { + continue + } + key = &pub + break + } + if key == nil { + log.Info("Signing key not trusted", "keyid", keyID(sig.KeyId), "error", err) + return errors.New("signature could not be verified") + } + if ok, err := key.Verify(data, sig); !ok || err != nil { + log.Info("Verification failed error", "keyid", keyID(key.KeyId), "error", err) + return errors.New("signature could not be verified") + } + return nil +} + +// keyID turns a binary minisign key ID into a hex string. +// Note: key IDs are printed in reverse byte order. +func keyID(id [8]byte) string { + var rev [8]byte + for i := range id { + rev[len(rev)-1-i] = id[i] + } + return fmt.Sprintf("%X", rev) +} diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go new file mode 100644 index 0000000..3676d25 --- /dev/null +++ b/cmd/geth/version_check_test.go @@ -0,0 +1,186 @@ +// 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 ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/jedisct1/go-minisign" +) + +func TestVerification(t *testing.T) { + 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/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/") + }) +} + +func testVerification(t *testing.T, pubkey, sigdir string) { + // Data to verify + data, err := os.ReadFile("./testdata/vcheck/data.json") + if err != nil { + t.Fatal(err) + } + // Signatures, with and without comments, both trusted and untrusted + files, err := os.ReadDir(sigdir) + 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 { + t.Fatal(err) + } + err = verifySignature([]string{pubkey}, data, sig) + if err != nil { + t.Fatal(err) + } + } +} + +func versionUint(v string) int { + mustInt := func(s string) int { + a, err := strconv.Atoi(s) + if err != nil { + panic(v) + } + return a + } + components := strings.Split(strings.TrimPrefix(v, "v"), ".") + a := mustInt(components[0]) + b := mustInt(components[1]) + c := mustInt(components[2]) + return a*100*100 + b*100 + c +} + +// 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 { + t.Fatal(err) + } + check := func(version string) { + vFull := fmt.Sprintf("Geth/%v-unstable-15339cf1-20201204/linux-amd64/go1.15.4", version) + for _, vuln := range vulns { + r, err := regexp.Compile(vuln.Check) + vulnIntro := versionUint(vuln.Introduced) + vulnFixed := versionUint(vuln.Fixed) + current := versionUint(version) + if err != nil { + t.Fatal(err) + } + if vuln.Name == "Denial of service due to Go CVE-2020-28362" { + // this one is not tied to geth-versions + continue + } + if vulnIntro <= current && vulnFixed > current { + // Should be vulnerable + if !r.MatchString(vFull) { + t.Errorf("Should be vulnerable, version %v, intro: %v, fixed: %v %v %v", + version, vuln.Introduced, vuln.Fixed, vuln.Name, vuln.Check) + } + } else { + if r.MatchString(vFull) { + t.Errorf("Should not be flagged vulnerable, version %v, intro: %v, fixed: %v %v %d %d %d", + version, vuln.Introduced, vuln.Fixed, vuln.Name, vulnIntro, current, vulnFixed) + } + } + } + } + for major := 1; major < 2; major++ { + for minor := 0; minor < 30; minor++ { + for patch := 0; patch < 30; patch++ { + vShort := fmt.Sprintf("v%d.%d.%d", major, minor, patch) + check(vShort) + } + } + } +} + +func TestGethPubKeysParseable(t *testing.T) { + t.Parallel() + for _, pubkey := range gethPubKeys { + _, err := minisign.NewPublicKey(pubkey) + if err != nil { + t.Errorf("Should be parseable") + } + } +} + +func TestKeyID(t *testing.T) { + t.Parallel() + type args struct { + id [8]byte + } + tests := []struct { + name string + args args + want string + }{ + {"@holiman key", args{id: extractKeyId(gethPubKeys[0])}, "FB1D084D39BAEC24"}, + {"second key", args{id: extractKeyId(gethPubKeys[1])}, "138B1CA303E51687"}, + {"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) + } + }) + } +} + +func extractKeyId(pubkey string) [8]byte { + p, _ := minisign.NewPublicKey(pubkey) + return p.KeyId +} diff --git a/cmd/p2psim/main.go b/cmd/p2psim/main.go new file mode 100644 index 0000000..a0f5f0d --- /dev/null +++ b/cmd/p2psim/main.go @@ -0,0 +1,443 @@ +// 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 . + +// p2psim provides a command-line client for a simulation HTTP API. +// +// Here is an example of creating a 2 node network with the first node +// connected to the second: +// +// $ p2psim node create +// Created node01 +// +// $ p2psim node start node01 +// Started node01 +// +// $ p2psim node create +// Created node02 +// +// $ p2psim node start node02 +// Started node02 +// +// $ p2psim node connect node01 node02 +// Connected node01 to node02 +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/p2p" + "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/urfave/cli/v2" +) + +var client *simulations.Client + +var ( + // global command flags + apiFlag = &cli.StringFlag{ + Name: "api", + Value: "http://localhost:8888", + Usage: "simulation API URL", + EnvVars: []string{"P2PSIM_API_URL"}, + } + + // events subcommand flags + currentFlag = &cli.BoolFlag{ + Name: "current", + Usage: "get existing nodes and conns first", + } + filterFlag = &cli.StringFlag{ + Name: "filter", + Value: "", + Usage: "message filter", + } + + // node create subcommand flags + nameFlag = &cli.StringFlag{ + Name: "name", + Value: "", + Usage: "node name", + } + servicesFlag = &cli.StringFlag{ + Name: "services", + Value: "", + Usage: "node services (comma separated)", + } + keyFlag = &cli.StringFlag{ + Name: "key", + Value: "", + Usage: "node private key (hex encoded)", + } + + // node rpc subcommand flags + subscribeFlag = &cli.BoolFlag{ + Name: "subscribe", + Usage: "method is a subscription", + } +) + +func main() { + app := flags.NewApp("devp2p simulation command-line client") + app.Flags = []cli.Flag{ + apiFlag, + } + app.Before = func(ctx *cli.Context) error { + client = simulations.NewClient(ctx.String(apiFlag.Name)) + return nil + } + app.Commands = []*cli.Command{ + { + Name: "show", + Usage: "show network information", + Action: showNetwork, + }, + { + Name: "events", + Usage: "stream network events", + Action: streamNetwork, + Flags: []cli.Flag{ + currentFlag, + filterFlag, + }, + }, + { + Name: "snapshot", + Usage: "create a network snapshot to stdout", + Action: createSnapshot, + }, + { + Name: "load", + Usage: "load a network snapshot from stdin", + Action: loadSnapshot, + }, + { + Name: "node", + Usage: "manage simulation nodes", + Action: listNodes, + Subcommands: []*cli.Command{ + { + Name: "list", + Usage: "list nodes", + Action: listNodes, + }, + { + Name: "create", + Usage: "create a node", + Action: createNode, + Flags: []cli.Flag{ + nameFlag, + servicesFlag, + keyFlag, + }, + }, + { + Name: "show", + ArgsUsage: "", + Usage: "show node information", + Action: showNode, + }, + { + Name: "start", + ArgsUsage: "", + Usage: "start a node", + Action: startNode, + }, + { + Name: "stop", + ArgsUsage: "", + Usage: "stop a node", + Action: stopNode, + }, + { + Name: "connect", + ArgsUsage: " ", + Usage: "connect a node to a peer node", + Action: connectNode, + }, + { + Name: "disconnect", + ArgsUsage: " ", + Usage: "disconnect a node from a peer node", + Action: disconnectNode, + }, + { + Name: "rpc", + ArgsUsage: " []", + Usage: "call a node RPC method", + Action: rpcNode, + Flags: []cli.Flag{ + subscribeFlag, + }, + }, + }, + }, + } + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func showNetwork(ctx *cli.Context) error { + if ctx.NArg() != 0 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + network, err := client.GetNetwork() + if err != nil { + return err + } + w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "NODES\t%d\n", len(network.Nodes)) + fmt.Fprintf(w, "CONNS\t%d\n", len(network.Conns)) + return nil +} + +func streamNetwork(ctx *cli.Context) error { + if ctx.NArg() != 0 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + events := make(chan *simulations.Event) + sub, err := client.SubscribeNetwork(events, simulations.SubscribeOpts{ + Current: ctx.Bool(currentFlag.Name), + Filter: ctx.String(filterFlag.Name), + }) + if err != nil { + return err + } + defer sub.Unsubscribe() + enc := json.NewEncoder(ctx.App.Writer) + for { + select { + case event := <-events: + if err := enc.Encode(event); err != nil { + return err + } + case err := <-sub.Err(): + return err + } + } +} + +func createSnapshot(ctx *cli.Context) error { + if ctx.NArg() != 0 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + snap, err := client.CreateSnapshot() + if err != nil { + return err + } + return json.NewEncoder(os.Stdout).Encode(snap) +} + +func loadSnapshot(ctx *cli.Context) error { + if ctx.NArg() != 0 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + snap := &simulations.Snapshot{} + if err := json.NewDecoder(os.Stdin).Decode(snap); err != nil { + return err + } + return client.LoadSnapshot(snap) +} + +func listNodes(ctx *cli.Context) error { + if ctx.NArg() != 0 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + nodes, err := client.GetNodes() + if err != nil { + return err + } + w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "NAME\tPROTOCOLS\tID\n") + for _, node := range nodes { + fmt.Fprintf(w, "%s\t%s\t%s\n", node.Name, strings.Join(protocolList(node), ","), node.ID) + } + return nil +} + +func protocolList(node *p2p.NodeInfo) []string { + protos := make([]string, 0, len(node.Protocols)) + for name := range node.Protocols { + protos = append(protos, name) + } + return protos +} + +func createNode(ctx *cli.Context) error { + if ctx.NArg() != 0 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + config := adapters.RandomNodeConfig() + config.Name = ctx.String(nameFlag.Name) + if key := ctx.String(keyFlag.Name); key != "" { + privKey, err := crypto.HexToECDSA(key) + if err != nil { + return err + } + config.ID = enode.PubkeyToIDV4(&privKey.PublicKey) + config.PrivateKey = privKey + } + if services := ctx.String(servicesFlag.Name); services != "" { + config.Lifecycles = strings.Split(services, ",") + } + node, err := client.CreateNode(config) + if err != nil { + return err + } + fmt.Fprintln(ctx.App.Writer, "Created", node.Name) + return nil +} + +func showNode(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + nodeName := ctx.Args().First() + node, err := client.GetNode(nodeName) + if err != nil { + return err + } + w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "NAME\t%s\n", node.Name) + fmt.Fprintf(w, "PROTOCOLS\t%s\n", strings.Join(protocolList(node), ",")) + fmt.Fprintf(w, "ID\t%s\n", node.ID) + fmt.Fprintf(w, "ENODE\t%s\n", node.Enode) + for name, proto := range node.Protocols { + fmt.Fprintln(w) + fmt.Fprintf(w, "--- PROTOCOL INFO: %s\n", name) + fmt.Fprintf(w, "%v\n", proto) + fmt.Fprintf(w, "---\n") + } + return nil +} + +func startNode(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + nodeName := ctx.Args().First() + if err := client.StartNode(nodeName); err != nil { + return err + } + fmt.Fprintln(ctx.App.Writer, "Started", nodeName) + return nil +} + +func stopNode(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + nodeName := ctx.Args().First() + if err := client.StopNode(nodeName); err != nil { + return err + } + fmt.Fprintln(ctx.App.Writer, "Stopped", nodeName) + return nil +} + +func connectNode(ctx *cli.Context) error { + if ctx.NArg() != 2 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + args := ctx.Args() + nodeName := args.Get(0) + peerName := args.Get(1) + if err := client.ConnectNode(nodeName, peerName); err != nil { + return err + } + fmt.Fprintln(ctx.App.Writer, "Connected", nodeName, "to", peerName) + return nil +} + +func disconnectNode(ctx *cli.Context) error { + args := ctx.Args() + if args.Len() != 2 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + nodeName := args.Get(0) + peerName := args.Get(1) + if err := client.DisconnectNode(nodeName, peerName); err != nil { + return err + } + fmt.Fprintln(ctx.App.Writer, "Disconnected", nodeName, "from", peerName) + return nil +} + +func rpcNode(ctx *cli.Context) error { + args := ctx.Args() + if args.Len() < 2 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + nodeName := args.Get(0) + method := args.Get(1) + rpcClient, err := client.RPCClient(context.Background(), nodeName) + if err != nil { + return err + } + if ctx.Bool(subscribeFlag.Name) { + return rpcSubscribe(rpcClient, ctx.App.Writer, method, args.Slice()[3:]...) + } + var result interface{} + params := make([]interface{}, len(args.Slice()[3:])) + for i, v := range args.Slice()[3:] { + params[i] = v + } + if err := rpcClient.Call(&result, method, params...); err != nil { + return err + } + return json.NewEncoder(ctx.App.Writer).Encode(result) +} + +func rpcSubscribe(client *rpc.Client, out io.Writer, method string, args ...string) error { + namespace, method, _ := strings.Cut(method, "_") + ch := make(chan interface{}) + subArgs := make([]interface{}, len(args)+1) + subArgs[0] = method + for i, v := range args { + subArgs[i+1] = v + } + sub, err := client.Subscribe(context.Background(), namespace, ch, subArgs...) + if err != nil { + return err + } + defer sub.Unsubscribe() + enc := json.NewEncoder(out) + for { + select { + case v := <-ch: + if err := enc.Encode(v); err != nil { + return err + } + case err := <-sub.Err(): + return err + } + } +} diff --git a/cmd/rlpdump/main.go b/cmd/rlpdump/main.go new file mode 100644 index 0000000..7e1d314 --- /dev/null +++ b/cmd/rlpdump/main.go @@ -0,0 +1,255 @@ +// Copyright 2015 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 . + +// rlpdump is a pretty-printer for RLP data. +package main + +import ( + "bufio" + "bytes" + "container/list" + "encoding/hex" + "flag" + "fmt" + "io" + "math" + "os" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + hexMode = flag.String("hex", "", "dump given hex data") + reverseMode = flag.Bool("reverse", false, "convert ASCII to rlp") + noASCII = flag.Bool("noascii", false, "don't print ASCII strings readably") + single = flag.Bool("single", false, "print only the first element, discard the rest") + showpos = flag.Bool("pos", false, "display element byte posititions") +) + +func init() { + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[-noascii] [-hex ][-reverse] [filename]") + flag.PrintDefaults() + fmt.Fprintln(os.Stderr, ` +Dumps RLP data from the given file in readable form. +If the filename is omitted, data is read from stdin.`) + } +} + +func main() { + flag.Parse() + + var r *inStream + switch { + case *hexMode != "": + data, err := hex.DecodeString(strings.TrimPrefix(*hexMode, "0x")) + if err != nil { + die(err) + } + r = newInStream(bytes.NewReader(data), int64(len(data))) + + case flag.NArg() == 0: + r = newInStream(bufio.NewReader(os.Stdin), 0) + + case flag.NArg() == 1: + fd, err := os.Open(flag.Arg(0)) + if err != nil { + die(err) + } + defer fd.Close() + var size int64 + finfo, err := fd.Stat() + if err == nil { + size = finfo.Size() + } + r = newInStream(bufio.NewReader(fd), size) + + default: + fmt.Fprintln(os.Stderr, "Error: too many arguments") + flag.Usage() + os.Exit(2) + } + + out := os.Stdout + if *reverseMode { + data, err := textToRlp(r) + if err != nil { + die(err) + } + fmt.Printf("%#x\n", data) + return + } else { + err := rlpToText(r, out) + if err != nil { + die(err) + } + } +} + +func rlpToText(in *inStream, out io.Writer) error { + stream := rlp.NewStream(in, 0) + for { + if err := dump(in, stream, 0, out); err != nil { + if err != io.EOF { + return err + } + break + } + fmt.Fprintln(out) + if *single { + break + } + } + return nil +} + +func dump(in *inStream, s *rlp.Stream, depth int, out io.Writer) error { + if *showpos { + fmt.Fprintf(out, "%s: ", in.posLabel()) + } + kind, size, err := s.Kind() + if err != nil { + return err + } + switch kind { + case rlp.Byte, rlp.String: + str, err := s.Bytes() + if err != nil { + return err + } + if len(str) == 0 || !*noASCII && isASCII(str) { + fmt.Fprintf(out, "%s%q", ws(depth), str) + } else { + fmt.Fprintf(out, "%s%x", ws(depth), str) + } + case rlp.List: + s.List() + defer s.ListEnd() + if size == 0 { + fmt.Fprintf(out, ws(depth)+"[]") + } else { + fmt.Fprintln(out, ws(depth)+"[") + for i := 0; ; i++ { + if i > 0 { + fmt.Fprint(out, ",\n") + } + if err := dump(in, s, depth+1, out); err == rlp.EOL { + break + } else if err != nil { + return err + } + } + fmt.Fprint(out, ws(depth)+"]") + } + } + return nil +} + +func isASCII(b []byte) bool { + for _, c := range b { + if c < 32 || c > 126 { + return false + } + } + return true +} + +func ws(n int) string { + return strings.Repeat(" ", n) +} + +func die(args ...interface{}) { + fmt.Fprintln(os.Stderr, args...) + os.Exit(1) +} + +// textToRlp converts text into RLP (best effort). +func textToRlp(r io.Reader) ([]byte, error) { + // We're expecting the input to be well-formed, meaning that + // - each element is on a separate line + // - each line is either an (element OR a list start/end) + comma + // - an element is either hex-encoded bytes OR a quoted string + var ( + scanner = bufio.NewScanner(r) + obj []interface{} + stack = list.New() + ) + for scanner.Scan() { + t := strings.TrimSpace(scanner.Text()) + if len(t) == 0 { + continue + } + switch t { + case "[": // list start + stack.PushFront(obj) + obj = make([]interface{}, 0) + case "]", "],": // list end + parent := stack.Remove(stack.Front()).([]interface{}) + obj = append(parent, obj) + case "[],": // empty list + obj = append(obj, make([]interface{}, 0)) + default: // element + data := []byte(t)[:len(t)-1] // cut off comma + if data[0] == '"' { // ascii string + data = []byte(t)[1 : len(data)-1] + } else { // hex data + data = common.FromHex(string(data)) + } + obj = append(obj, data) + } + } + if err := scanner.Err(); err != nil { + return nil, err + } + data, err := rlp.EncodeToBytes(obj[0]) + return data, err +} + +type inStream struct { + br rlp.ByteReader + pos int + columns int +} + +func newInStream(br rlp.ByteReader, totalSize int64) *inStream { + col := int(math.Ceil(math.Log10(float64(totalSize)))) + return &inStream{br: br, columns: col} +} + +func (rc *inStream) Read(b []byte) (n int, err error) { + n, err = rc.br.Read(b) + rc.pos += n + return n, err +} + +func (rc *inStream) ReadByte() (byte, error) { + b, err := rc.br.ReadByte() + if err == nil { + rc.pos++ + } + return b, err +} + +func (rc *inStream) posLabel() string { + l := strconv.FormatInt(int64(rc.pos), 10) + if len(l) < rc.columns { + l = strings.Repeat(" ", rc.columns-len(l)) + l + } + return l +} diff --git a/cmd/rlpdump/rlpdump_test.go b/cmd/rlpdump/rlpdump_test.go new file mode 100644 index 0000000..4b0ae68 --- /dev/null +++ b/cmd/rlpdump/rlpdump_test.go @@ -0,0 +1,84 @@ +// 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 ( + "bytes" + "fmt" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func TestRoundtrip(t *testing.T) { + t.Parallel() + for i, want := range []string{ + "0xf880806482520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a1010000000000000000000000000000000000000000000000000000000000000001801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28", + "0xd5c0d3cb84746573742a2a808213378667617a6f6e6b", + "0xc780c0c1c0825208", + } { + var out strings.Builder + in := newInStream(bytes.NewReader(common.FromHex(want)), 0) + err := rlpToText(in, &out) + if err != nil { + t.Fatal(err) + } + text := out.String() + rlpBytes, err := textToRlp(strings.NewReader(text)) + if err != nil { + t.Errorf("test %d: error %v", i, err) + continue + } + have := fmt.Sprintf("%#x", rlpBytes) + if have != want { + t.Errorf("test %d: have\n%v\nwant:\n%v\n", i, have, want) + } + } +} + +func TestTextToRlp(t *testing.T) { + t.Parallel() + type tc struct { + text string + want string + } + cases := []tc{ + { + text: `[ + "", + [], +[ + [], + ], + 5208, +]`, + want: "0xc780c0c1c0825208", + }, + } + for i, tc := range cases { + have, err := textToRlp(strings.NewReader(tc.text)) + if err != nil { + t.Errorf("test %d: error %v", i, err) + continue + } + if hexutil.Encode(have) != tc.want { + t.Errorf("test %d:\nhave %v\nwant %v", i, hexutil.Encode(have), tc.want) + } + } +} diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go new file mode 100644 index 0000000..fc66e11 --- /dev/null +++ b/cmd/utils/cmd.go @@ -0,0 +1,866 @@ +// Copyright 2014 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 utils contains internal helper functions for go-ethereum commands. +package utils + +import ( + "bufio" + "bytes" + "compress/gzip" + "crypto/sha256" + "errors" + "fmt" + "io" + "os" + "os/signal" + "path/filepath" + "runtime" + "strings" + "syscall" + "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/snapshot" + "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/internal/debug" + "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/urfave/cli/v2" +) + +const ( + importBatchSize = 2500 +) + +// Fatalf formats a message to standard error and exits the program. +// The message is also printed to standard output if standard error +// is redirected to a different file. +func Fatalf(format string, args ...interface{}) { + w := io.MultiWriter(os.Stdout, os.Stderr) + if runtime.GOOS == "windows" { + // The SameFile check below doesn't work on Windows. + // stdout is unlikely to get redirected though, so just print there. + w = os.Stdout + } else { + outf, _ := os.Stdout.Stat() + errf, _ := os.Stderr.Stat() + if outf != nil && errf != nil && os.SameFile(outf, errf) { + w = os.Stderr + } + } + fmt.Fprintf(w, "Fatal: "+format+"\n", args...) + os.Exit(1) +} + +func StartNode(ctx *cli.Context, stack *node.Node, isConsole bool) { + if err := stack.Start(); err != nil { + Fatalf("Error starting protocol stack: %v", err) + } + go func() { + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(sigc) + + minFreeDiskSpace := 2 * ethconfig.Defaults.TrieDirtyCache // Default 2 * 256Mb + if ctx.IsSet(MinFreeDiskSpaceFlag.Name) { + minFreeDiskSpace = ctx.Int(MinFreeDiskSpaceFlag.Name) + } else if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheGCFlag.Name) { + minFreeDiskSpace = 2 * ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100 + } + if minFreeDiskSpace > 0 { + go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024) + } + + shutdown := func() { + log.Info("Got interrupt, shutting down...") + go stack.Close() + for i := 10; i > 0; i-- { + <-sigc + if i > 1 { + log.Warn("Already shutting down, interrupt more to panic.", "times", i-1) + } + } + debug.Exit() // ensure trace and CPU profile data is flushed. + debug.LoudPanic("boom") + } + + if isConsole { + // In JS console mode, SIGINT is ignored because it's handled by the console. + // However, SIGTERM still shuts down the node. + for { + sig := <-sigc + if sig == syscall.SIGTERM { + shutdown() + return + } + } + } else { + <-sigc + shutdown() + } + }() +} + +func monitorFreeDiskSpace(sigc chan os.Signal, path string, freeDiskSpaceCritical uint64) { + if path == "" { + return + } + for { + freeSpace, err := getFreeDiskSpace(path) + if err != nil { + log.Warn("Failed to get free disk space", "path", path, "err", err) + break + } + if freeSpace < freeDiskSpaceCritical { + log.Error("Low disk space. Gracefully shutting down Geth to prevent database corruption.", "available", common.StorageSize(freeSpace), "path", path) + sigc <- syscall.SIGTERM + break + } else if freeSpace < 2*freeDiskSpaceCritical { + log.Warn("Disk space is running low. Geth will shutdown if disk space runs below critical level.", "available", common.StorageSize(freeSpace), "critical_level", common.StorageSize(freeDiskSpaceCritical), "path", path) + } + time.Sleep(30 * time.Second) + } +} + +func ImportChain(chain *core.BlockChain, fn string) error { + // Watch for Ctrl-C while the import is running. + // If a signal is received, the import will stop at the next batch. + interrupt := make(chan os.Signal, 1) + stop := make(chan struct{}) + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(interrupt) + defer close(interrupt) + go func() { + if _, ok := <-interrupt; ok { + log.Info("Interrupted during import, stopping at next batch") + } + close(stop) + }() + checkInterrupt := func() bool { + select { + case <-stop: + return true + default: + return false + } + } + + log.Info("Importing blockchain", "file", fn) + + // Open the file handle and potentially unwrap the gzip stream + fh, err := os.Open(fn) + if err != nil { + return err + } + defer fh.Close() + + var reader io.Reader = fh + if strings.HasSuffix(fn, ".gz") { + if reader, err = gzip.NewReader(reader); err != nil { + return err + } + } + stream := rlp.NewStream(reader, 0) + + // Run actual the import. + blocks := make(types.Blocks, importBatchSize) + n := 0 + for batch := 0; ; batch++ { + // Load a batch of RLP blocks. + if checkInterrupt() { + return errors.New("interrupted") + } + i := 0 + for ; i < importBatchSize; i++ { + var b types.Block + if err := stream.Decode(&b); err == io.EOF { + break + } else if err != nil { + return fmt.Errorf("at block %d: %v", n, err) + } + // don't import first block + if b.NumberU64() == 0 { + i-- + continue + } + blocks[i] = &b + n++ + } + if i == 0 { + break + } + // Import the batch. + if checkInterrupt() { + return errors.New("interrupted") + } + missing := missingBlocks(chain, blocks[:i]) + if len(missing) == 0 { + log.Info("Skipping batch as all blocks present", "batch", batch, "first", blocks[0].Hash(), "last", blocks[i-1].Hash()) + continue + } + if failindex, err := chain.InsertChain(missing); err != nil { + var failnumber uint64 + if failindex > 0 && failindex < len(missing) { + failnumber = missing[failindex].NumberU64() + } else { + failnumber = missing[0].NumberU64() + } + return fmt.Errorf("invalid block %d: %v", failnumber, err) + } + } + return nil +} + +func readList(filename string) ([]string, error) { + b, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + return strings.Split(string(b), "\n"), nil +} + +// ImportHistory imports Era1 files containing historical block information, +// starting from genesis. +func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, network string) error { + if chain.CurrentSnapBlock().Number.BitLen() != 0 { + return errors.New("history import only supported when starting from genesis") + } + entries, err := era.ReadDir(dir, network) + if err != nil { + return fmt.Errorf("error reading %s: %w", dir, err) + } + checksums, err := readList(filepath.Join(dir, "checksums.txt")) + if err != nil { + return fmt.Errorf("unable to read checksums.txt: %w", err) + } + if len(checksums) != len(entries) { + return fmt.Errorf("expected equal number of checksums and entries, have: %d checksums, %d entries", len(checksums), len(entries)) + } + var ( + start = time.Now() + reported = time.Now() + imported = 0 + forker = core.NewForkChoice(chain, nil) + h = sha256.New() + buf = bytes.NewBuffer(nil) + ) + for i, filename := range entries { + err := func() error { + f, err := os.Open(filepath.Join(dir, filename)) + if err != nil { + return fmt.Errorf("unable to open era: %w", err) + } + defer f.Close() + + // Validate checksum. + if _, err := io.Copy(h, f); err != nil { + return fmt.Errorf("unable to recalculate checksum: %w", err) + } + if have, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; have != want { + return fmt.Errorf("checksum mismatch: have %s, want %s", have, want) + } + h.Reset() + buf.Reset() + + // Import all block data from Era1. + e, err := era.From(f) + if err != nil { + return fmt.Errorf("error opening era: %w", err) + } + it, err := era.NewIterator(e) + if err != nil { + return fmt.Errorf("error making era reader: %w", err) + } + for it.Next() { + block, err := it.Block() + if err != nil { + return fmt.Errorf("error reading block %d: %w", it.Number(), err) + } + if block.Number().BitLen() == 0 { + continue // skip genesis + } + receipts, err := it.Receipts() + if err != nil { + return fmt.Errorf("error reading receipts %d: %w", it.Number(), err) + } + if status, err := chain.HeaderChain().InsertHeaderChain([]*types.Header{block.Header()}, start, forker); err != nil { + return fmt.Errorf("error inserting header %d: %w", it.Number(), err) + } else if status != core.CanonStatTy { + return fmt.Errorf("error inserting header %d, not canon: %v", it.Number(), status) + } + if _, err := chain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{receipts}, 2^64-1); err != nil { + return fmt.Errorf("error inserting body %d: %w", it.Number(), err) + } + imported += 1 + + // Give the user some feedback that something is happening. + if time.Since(reported) >= 8*time.Second { + log.Info("Importing Era files", "head", it.Number(), "imported", imported, "elapsed", common.PrettyDuration(time.Since(start))) + imported = 0 + reported = time.Now() + } + } + return nil + }() + if err != nil { + return err + } + } + + return nil +} + +func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block { + head := chain.CurrentBlock() + for i, block := range blocks { + // If we're behind the chain head, only check block, state is available at head + if head.Number.Uint64() > block.NumberU64() { + if !chain.HasBlock(block.Hash(), block.NumberU64()) { + return blocks[i:] + } + continue + } + // If we're above the chain head, state availability is a must + if !chain.HasBlockAndState(block.Hash(), block.NumberU64()) { + return blocks[i:] + } + } + return nil +} + +// ExportChain exports a blockchain into the specified file, truncating any data +// already present in the file. +func ExportChain(blockchain *core.BlockChain, fn string) error { + log.Info("Exporting blockchain", "file", fn) + + // Open the file handle and potentially wrap with a gzip stream + fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer fh.Close() + + var writer io.Writer = fh + if strings.HasSuffix(fn, ".gz") { + writer = gzip.NewWriter(writer) + defer writer.(*gzip.Writer).Close() + } + // Iterate over the blocks and export them + if err := blockchain.Export(writer); err != nil { + return err + } + log.Info("Exported blockchain", "file", fn) + + return nil +} + +// ExportAppendChain exports a blockchain into the specified file, appending to +// the file if data already exists in it. +func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, last uint64) error { + log.Info("Exporting blockchain", "file", fn) + + // Open the file handle and potentially wrap with a gzip stream + fh, err := os.OpenFile(fn, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm) + if err != nil { + return err + } + defer fh.Close() + + var writer io.Writer = fh + if strings.HasSuffix(fn, ".gz") { + writer = gzip.NewWriter(writer) + defer writer.(*gzip.Writer).Close() + } + // Iterate over the blocks and export them + if err := blockchain.ExportN(writer, first, last); err != nil { + return err + } + log.Info("Exported blockchain to", "file", fn) + return nil +} + +// ExportHistory exports blockchain history into the specified directory, +// following the Era format. +func ExportHistory(bc *core.BlockChain, dir string, first, last, step uint64) error { + log.Info("Exporting blockchain history", "dir", dir) + if head := bc.CurrentBlock().Number.Uint64(); head < last { + log.Warn("Last block beyond head, setting last = head", "head", head, "last", last) + last = head + } + network := "unknown" + if name, ok := params.NetworkNames[bc.Config().ChainID.String()]; ok { + network = name + } + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("error creating output directory: %w", err) + } + var ( + start = time.Now() + reported = time.Now() + h = sha256.New() + buf = bytes.NewBuffer(nil) + checksums []string + ) + for i := first; i <= last; i += step { + err := func() error { + filename := filepath.Join(dir, era.Filename(network, int(i/step), common.Hash{})) + f, err := os.Create(filename) + if err != nil { + return fmt.Errorf("could not create era file: %w", err) + } + defer f.Close() + + w := era.NewBuilder(f) + for j := uint64(0); j < step && j <= last-i; j++ { + var ( + n = i + j + block = bc.GetBlockByNumber(n) + ) + if block == nil { + return fmt.Errorf("export failed on #%d: not found", n) + } + receipts := bc.GetReceiptsByHash(block.Hash()) + if receipts == nil { + return fmt.Errorf("export failed on #%d: receipts not found", n) + } + td := bc.GetTd(block.Hash(), block.NumberU64()) + if td == nil { + return fmt.Errorf("export failed on #%d: total difficulty not found", n) + } + if err := w.Add(block, receipts, td); err != nil { + return err + } + } + root, err := w.Finalize() + if err != nil { + return fmt.Errorf("export failed to finalize %d: %w", step/i, err) + } + // Set correct filename with root. + os.Rename(filename, filepath.Join(dir, era.Filename(network, int(i/step), root))) + + // Compute checksum of entire Era1. + if _, err := f.Seek(0, io.SeekStart); err != nil { + return err + } + if _, err := io.Copy(h, f); err != nil { + return fmt.Errorf("unable to calculate checksum: %w", err) + } + checksums = append(checksums, common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex()) + h.Reset() + buf.Reset() + return nil + }() + if err != nil { + return err + } + if time.Since(reported) >= 8*time.Second { + log.Info("Exporting blocks", "exported", i, "elapsed", common.PrettyDuration(time.Since(start))) + reported = time.Now() + } + } + + os.WriteFile(filepath.Join(dir, "checksums.txt"), []byte(strings.Join(checksums, "\n")), os.ModePerm) + + log.Info("Exported blockchain to", "dir", dir) + + return nil +} + +// ImportPreimages imports a batch of exported hash preimages into the database. +// It's a part of the deprecated functionality, should be removed in the future. +func ImportPreimages(db ethdb.Database, fn string) error { + log.Info("Importing preimages", "file", fn) + + // Open the file handle and potentially unwrap the gzip stream + fh, err := os.Open(fn) + if err != nil { + return err + } + defer fh.Close() + + var reader io.Reader = bufio.NewReader(fh) + if strings.HasSuffix(fn, ".gz") { + if reader, err = gzip.NewReader(reader); err != nil { + return err + } + } + stream := rlp.NewStream(reader, 0) + + // Import the preimages in batches to prevent disk thrashing + preimages := make(map[common.Hash][]byte) + + for { + // Read the next entry and ensure it's not junk + var blob []byte + + if err := stream.Decode(&blob); err != nil { + if err == io.EOF { + break + } + return err + } + // Accumulate the preimages and flush when enough ws gathered + preimages[crypto.Keccak256Hash(blob)] = common.CopyBytes(blob) + if len(preimages) > 1024 { + rawdb.WritePreimages(db, preimages) + preimages = make(map[common.Hash][]byte) + } + } + // Flush the last batch preimage data + if len(preimages) > 0 { + rawdb.WritePreimages(db, preimages) + } + return nil +} + +// ExportPreimages exports all known hash preimages into the specified file, +// truncating any data already present in the file. +// It's a part of the deprecated functionality, should be removed in the future. +func ExportPreimages(db ethdb.Database, fn string) error { + log.Info("Exporting preimages", "file", fn) + + // Open the file handle and potentially wrap with a gzip stream + fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer fh.Close() + + var writer io.Writer = fh + if strings.HasSuffix(fn, ".gz") { + writer = gzip.NewWriter(writer) + defer writer.(*gzip.Writer).Close() + } + // Iterate over the preimages and export them + it := db.NewIterator([]byte("secure-key-"), nil) + defer it.Release() + + for it.Next() { + if err := rlp.Encode(writer, it.Value()); err != nil { + return err + } + } + log.Info("Exported preimages", "file", fn) + 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 +// should be bumped. +// If the importer sees a higher version, it should reject the import. +type exportHeader struct { + Magic string // Always set to 'gethdbdump' for disambiguation + Version uint64 + Kind string + UnixTime uint64 +} + +const exportMagic = "gethdbdump" +const ( + OpBatchAdd = 0 + OpBatchDel = 1 +) + +// ImportLDBData imports a batch of snapshot data into the database +func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan struct{}) error { + log.Info("Importing leveldb data", "file", f) + + // Open the file handle and potentially unwrap the gzip stream + fh, err := os.Open(f) + if err != nil { + return err + } + defer fh.Close() + + var reader io.Reader = bufio.NewReader(fh) + if strings.HasSuffix(f, ".gz") { + if reader, err = gzip.NewReader(reader); err != nil { + return err + } + } + stream := rlp.NewStream(reader, 0) + + // Read the header + var header exportHeader + if err := stream.Decode(&header); err != nil { + return fmt.Errorf("could not decode header: %v", err) + } + if header.Magic != exportMagic { + return errors.New("incompatible data, wrong magic") + } + if header.Version != 0 { + return fmt.Errorf("incompatible version %d, (support only 0)", header.Version) + } + log.Info("Importing data", "file", f, "type", header.Kind, "data age", + common.PrettyDuration(time.Since(time.Unix(int64(header.UnixTime), 0)))) + + // Import the snapshot in batches to prevent disk thrashing + var ( + count int64 + start = time.Now() + logged = time.Now() + batch = db.NewBatch() + ) + for { + // Read the next entry + var ( + op byte + key, val []byte + ) + if err := stream.Decode(&op); err != nil { + if err == io.EOF { + break + } + return err + } + if err := stream.Decode(&key); err != nil { + return err + } + if err := stream.Decode(&val); err != nil { + return err + } + if count < startIndex { + count++ + continue + } + switch op { + case OpBatchDel: + batch.Delete(key) + case OpBatchAdd: + batch.Put(key, val) + default: + return fmt.Errorf("unknown op %d", op) + } + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + } + // Check interruption emitted by ctrl+c + if count%1000 == 0 { + select { + case <-interrupt: + if err := batch.Write(); err != nil { + return err + } + log.Info("External data import interrupted", "file", f, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + return nil + default: + } + } + if count%1000 == 0 && time.Since(logged) > 8*time.Second { + log.Info("Importing external data", "file", f, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + count += 1 + } + // Flush the last batch snapshot data + if batch.ValueSize() > 0 { + if err := batch.Write(); err != nil { + return err + } + } + log.Info("Imported chain data", "file", f, "count", count, + "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// ChainDataIterator is an interface wraps all necessary functions to iterate +// the exporting chain data. +type ChainDataIterator interface { + // Next returns the key-value pair for next exporting entry in the iterator. + // When the end is reached, it will return (0, nil, nil, false). + Next() (byte, []byte, []byte, bool) + + // Release releases associated resources. Release should always succeed and can + // be called multiple times without causing error. + Release() +} + +// ExportChaindata exports the given data type (truncating any data already present) +// in the file. If the suffix is 'gz', gzip compression is used. +func ExportChaindata(fn string, kind string, iter ChainDataIterator, interrupt chan struct{}) error { + log.Info("Exporting chain data", "file", fn, "kind", kind) + defer iter.Release() + + // Open the file handle and potentially wrap with a gzip stream + fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer fh.Close() + + var writer io.Writer = fh + if strings.HasSuffix(fn, ".gz") { + writer = gzip.NewWriter(writer) + defer writer.(*gzip.Writer).Close() + } + // Write the header + if err := rlp.Encode(writer, &exportHeader{ + Magic: exportMagic, + Version: 0, + Kind: kind, + UnixTime: uint64(time.Now().Unix()), + }); err != nil { + return err + } + // Extract data from source iterator and dump them out to file + var ( + count int64 + start = time.Now() + logged = time.Now() + ) + for { + op, key, val, ok := iter.Next() + if !ok { + break + } + if err := rlp.Encode(writer, op); err != nil { + return err + } + if err := rlp.Encode(writer, key); err != nil { + return err + } + if err := rlp.Encode(writer, val); err != nil { + return err + } + if count%1000 == 0 { + // Check interruption emitted by ctrl+c + select { + case <-interrupt: + log.Info("Chain data exporting interrupted", "file", fn, + "kind", kind, "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + return nil + default: + } + if time.Since(logged) > 8*time.Second { + log.Info("Exporting chain data", "file", fn, "kind", kind, + "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + count++ + } + log.Info("Exported chain data", "file", fn, "kind", kind, "count", count, + "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} diff --git a/cmd/utils/diskusage.go b/cmd/utils/diskusage.go new file mode 100644 index 0000000..0e88f91 --- /dev/null +++ b/cmd/utils/diskusage.go @@ -0,0 +1,44 @@ +// 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 . + +//go:build !windows && !openbsd +// +build !windows,!openbsd + +package utils + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func getFreeDiskSpace(path string) (uint64, error) { + var stat unix.Statfs_t + if err := unix.Statfs(path, &stat); err != nil { + return 0, fmt.Errorf("failed to call Statfs: %v", err) + } + + // Available blocks * size per block = available space in bytes + var bavail = stat.Bavail + // nolint:staticcheck + if stat.Bavail < 0 { + // FreeBSD can have a negative number of blocks available + // because of the grace limit. + bavail = 0 + } + //nolint:unconvert + return uint64(bavail) * uint64(stat.Bsize), nil +} diff --git a/cmd/utils/diskusage_openbsd.go b/cmd/utils/diskusage_openbsd.go new file mode 100644 index 0000000..0d71d84 --- /dev/null +++ b/cmd/utils/diskusage_openbsd.go @@ -0,0 +1,44 @@ +// 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 . + +//go:build openbsd +// +build openbsd + +package utils + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func getFreeDiskSpace(path string) (uint64, error) { + var stat unix.Statfs_t + if err := unix.Statfs(path, &stat); err != nil { + return 0, fmt.Errorf("failed to call Statfs: %v", err) + } + + // Available blocks * size per block = available space in bytes + var bavail = stat.F_bavail + // Not sure if the following check is necessary for OpenBSD + if stat.F_bavail < 0 { + // FreeBSD can have a negative number of blocks available + // because of the grace limit. + bavail = 0 + } + //nolint:unconvert + return uint64(bavail) * uint64(stat.F_bsize), nil +} diff --git a/cmd/utils/diskusage_windows.go b/cmd/utils/diskusage_windows.go new file mode 100644 index 0000000..db31449 --- /dev/null +++ b/cmd/utils/diskusage_windows.go @@ -0,0 +1,38 @@ +// 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 utils + +import ( + "fmt" + + "golang.org/x/sys/windows" +) + +func getFreeDiskSpace(path string) (uint64, error) { + + cwd, err := windows.UTF16PtrFromString(path) + if err != nil { + return 0, fmt.Errorf("failed to call UTF16PtrFromString: %v", err) + } + + var freeBytesAvailableToCaller, totalNumberOfBytes, totalNumberOfFreeBytes uint64 + if err := windows.GetDiskFreeSpaceEx(cwd, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes); err != nil { + return 0, fmt.Errorf("failed to call GetDiskFreeSpaceEx: %v", err) + } + + return freeBytesAvailableToCaller, nil +} diff --git a/cmd/utils/export_test.go b/cmd/utils/export_test.go new file mode 100644 index 0000000..c22aad6 --- /dev/null +++ b/cmd/utils/export_test.go @@ -0,0 +1,199 @@ +// 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 utils + +import ( + "fmt" + "os" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/rlp" +) + +// TestExport does basic sanity checks on the export/import functionality +func TestExport(t *testing.T) { + f := fmt.Sprintf("%v/tempdump", os.TempDir()) + defer func() { + os.Remove(f) + }() + testExport(t, f) +} + +func TestExportGzip(t *testing.T) { + f := fmt.Sprintf("%v/tempdump.gz", os.TempDir()) + defer func() { + os.Remove(f) + }() + testExport(t, f) +} + +type testIterator struct { + index int +} + +func newTestIterator() *testIterator { + return &testIterator{index: -1} +} + +func (iter *testIterator) Next() (byte, []byte, []byte, bool) { + if iter.index >= 999 { + return 0, nil, nil, false + } + iter.index += 1 + if iter.index == 42 { + iter.index += 1 + } + return OpBatchAdd, []byte(fmt.Sprintf("key-%04d", iter.index)), + []byte(fmt.Sprintf("value %d", iter.index)), true +} + +func (iter *testIterator) Release() {} + +func testExport(t *testing.T, f string) { + err := ExportChaindata(f, "testdata", newTestIterator(), make(chan struct{})) + if err != nil { + t.Fatal(err) + } + db := rawdb.NewMemoryDatabase() + err = ImportLDBData(db, f, 5, make(chan struct{})) + if err != nil { + t.Fatal(err) + } + // verify + for i := 0; i < 1000; i++ { + v, err := db.Get([]byte(fmt.Sprintf("key-%04d", i))) + if (i < 5 || i == 42) && err == nil { + t.Fatalf("expected no element at idx %d, got '%v'", i, string(v)) + } + if !(i < 5 || i == 42) { + if err != nil { + t.Fatalf("expected element idx %d: %v", i, err) + } + if have, want := string(v), fmt.Sprintf("value %d", i); have != want { + t.Fatalf("have %v, want %v", have, want) + } + } + } + v, err := db.Get([]byte(fmt.Sprintf("key-%04d", 1000))) + if err == nil { + t.Fatalf("expected no element at idx %d, got '%v'", 1000, string(v)) + } +} + +// TestDeletionExport tests if the deletion markers can be exported/imported correctly +func TestDeletionExport(t *testing.T) { + f := fmt.Sprintf("%v/tempdump", os.TempDir()) + defer func() { + os.Remove(f) + }() + testDeletion(t, f) +} + +// TestDeletionExportGzip tests if the deletion markers can be exported/imported +// correctly with gz compression. +func TestDeletionExportGzip(t *testing.T) { + f := fmt.Sprintf("%v/tempdump.gz", os.TempDir()) + defer func() { + os.Remove(f) + }() + testDeletion(t, f) +} + +type deletionIterator struct { + index int +} + +func newDeletionIterator() *deletionIterator { + return &deletionIterator{index: -1} +} + +func (iter *deletionIterator) Next() (byte, []byte, []byte, bool) { + if iter.index >= 999 { + return 0, nil, nil, false + } + iter.index += 1 + if iter.index == 42 { + iter.index += 1 + } + return OpBatchDel, []byte(fmt.Sprintf("key-%04d", iter.index)), nil, true +} + +func (iter *deletionIterator) Release() {} + +func testDeletion(t *testing.T, f string) { + err := ExportChaindata(f, "testdata", newDeletionIterator(), make(chan struct{})) + if err != nil { + t.Fatal(err) + } + db := rawdb.NewMemoryDatabase() + for i := 0; i < 1000; i++ { + db.Put([]byte(fmt.Sprintf("key-%04d", i)), []byte(fmt.Sprintf("value %d", i))) + } + err = ImportLDBData(db, f, 5, make(chan struct{})) + if err != nil { + t.Fatal(err) + } + for i := 0; i < 1000; i++ { + v, err := db.Get([]byte(fmt.Sprintf("key-%04d", i))) + if i < 5 || i == 42 { + if err != nil { + t.Fatalf("expected element at idx %d, got '%v'", i, err) + } + if have, want := string(v), fmt.Sprintf("value %d", i); have != want { + t.Fatalf("have %v, want %v", have, want) + } + } + if !(i < 5 || i == 42) { + if err == nil { + t.Fatalf("expected no element idx %d: %v", i, string(v)) + } + } + } +} + +// 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) + }() + fh, err := os.OpenFile(f, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + t.Fatal(err) + } + defer fh.Close() + if err := rlp.Encode(fh, &exportHeader{ + Magic: exportMagic, + Version: 500, + Kind: "testdata", + UnixTime: uint64(time.Now().Unix()), + }); err != nil { + t.Fatal(err) + } + db2 := rawdb.NewMemoryDatabase() + err = ImportLDBData(db2, f, 0, make(chan struct{})) + if err == nil { + t.Fatal("Expected error, got none") + } + if !strings.HasPrefix(err.Error(), "incompatible version") { + t.Fatalf("wrong error: %v", err) + } +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go new file mode 100644 index 0000000..8732eaf --- /dev/null +++ b/cmd/utils/flags.go @@ -0,0 +1,2307 @@ +// Copyright 2015 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 utils contains internal helper functions for go-ethereum commands. +package utils + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + "net" + "net/http" + "os" + "path/filepath" + godebug "runtime/debug" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" + bparams "github.com/ethereum/go-ethereum/beacon/params" + "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/rawdb" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/eth" + "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/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/remotedb" + "github.com/ethereum/go-ethereum/ethstats" + "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/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/metrics/exp" + "github.com/ethereum/go-ethereum/metrics/influxdb" + "github.com/ethereum/go-ethereum/miner" + "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/p2p/netutil" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + pcsclite "github.com/gballet/go-libpcsclite" + gopsutil "github.com/shirou/gopsutil/mem" + "github.com/urfave/cli/v2" +) + +// These are all the command line flags we support. +// If you add to this list, please remember to include the +// flag in the appropriate command definition. +// +// The flags are defined here so their names and help texts +// are the same for all commands. + +var ( + // General settings + DataDirFlag = &flags.DirectoryFlag{ + Name: "datadir", + Usage: "Data directory for the databases and keystore", + Value: flags.DirectoryString(node.DefaultDataDir()), + Category: flags.EthCategory, + } + RemoteDBFlag = &cli.StringFlag{ + Name: "remotedb", + Usage: "URL for remote database", + Category: flags.LoggingCategory, + } + DBEngineFlag = &cli.StringFlag{ + Name: "db.engine", + Usage: "Backing database implementation to use ('pebble' or 'leveldb')", + Value: node.DefaultConfig.DBEngine, + Category: flags.EthCategory, + } + AncientFlag = &flags.DirectoryFlag{ + Name: "datadir.ancient", + Usage: "Root directory for ancient data (default = inside chaindata)", + Category: flags.EthCategory, + } + MinFreeDiskSpaceFlag = &flags.DirectoryFlag{ + Name: "datadir.minfreedisk", + Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)", + Category: flags.EthCategory, + } + KeyStoreDirFlag = &flags.DirectoryFlag{ + Name: "keystore", + Usage: "Directory for the keystore (default = inside the datadir)", + Category: flags.AccountCategory, + } + USBFlag = &cli.BoolFlag{ + Name: "usb", + Usage: "Enable monitoring and management of USB hardware wallets", + Category: flags.AccountCategory, + } + SmartCardDaemonPathFlag = &cli.StringFlag{ + Name: "pcscdpath", + Usage: "Path to the smartcard daemon (pcscd) socket file", + Value: pcsclite.PCSCDSockName, + Category: flags.AccountCategory, + } + NetworkIdFlag = &cli.Uint64Flag{ + Name: "networkid", + Usage: "Explicitly set network id (integer)(For testnets: use --goerli, --sepolia, --holesky instead)", + Value: ethconfig.Defaults.NetworkId, + Category: flags.EthCategory, + } + MainnetFlag = &cli.BoolFlag{ + Name: "mainnet", + Usage: "Ethereum mainnet", + Category: flags.EthCategory, + } + GoerliFlag = &cli.BoolFlag{ + Name: "goerli", + Usage: "Görli network: pre-configured proof-of-authority test network", + Category: flags.EthCategory, + } + SepoliaFlag = &cli.BoolFlag{ + Name: "sepolia", + Usage: "Sepolia network: pre-configured proof-of-work test network", + Category: flags.EthCategory, + } + HoleskyFlag = &cli.BoolFlag{ + Name: "holesky", + Usage: "Holesky network: pre-configured proof-of-stake test network", + Category: flags.EthCategory, + } + // Dev mode + DeveloperFlag = &cli.BoolFlag{ + Name: "dev", + Usage: "Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled", + Category: flags.DevCategory, + } + DeveloperPeriodFlag = &cli.Uint64Flag{ + Name: "dev.period", + Usage: "Block period to use in developer mode (0 = mine only if transaction pending)", + Category: flags.DevCategory, + } + DeveloperGasLimitFlag = &cli.Uint64Flag{ + Name: "dev.gaslimit", + Usage: "Initial block gas limit", + Value: 11500000, + Category: flags.DevCategory, + } + + IdentityFlag = &cli.StringFlag{ + Name: "identity", + Usage: "Custom node name", + Category: flags.NetworkingCategory, + } + DocRootFlag = &flags.DirectoryFlag{ + Name: "docroot", + Usage: "Document Root for HTTPClient file scheme", + Value: flags.DirectoryString(flags.HomeDir()), + Category: flags.APICategory, + } + ExitWhenSyncedFlag = &cli.BoolFlag{ + Name: "exitwhensynced", + Usage: "Exits after block synchronisation completes", + Category: flags.EthCategory, + } + + // Dump command options. + IterativeOutputFlag = &cli.BoolFlag{ + Name: "iterative", + Usage: "Print streaming JSON iteratively, delimited by newlines", + Value: true, + } + ExcludeStorageFlag = &cli.BoolFlag{ + Name: "nostorage", + Usage: "Exclude storage entries (save db lookups)", + } + IncludeIncompletesFlag = &cli.BoolFlag{ + Name: "incompletes", + Usage: "Include accounts for which we don't have the address (missing preimage)", + } + ExcludeCodeFlag = &cli.BoolFlag{ + Name: "nocode", + Usage: "Exclude contract code (save db lookups)", + } + StartKeyFlag = &cli.StringFlag{ + Name: "start", + Usage: "Start position. Either a hash or address", + Value: "0x0000000000000000000000000000000000000000000000000000000000000000", + } + DumpLimitFlag = &cli.Uint64Flag{ + Name: "limit", + Usage: "Max number of elements (0 = no limit)", + Value: 0, + } + + defaultSyncMode = ethconfig.Defaults.SyncMode + SnapshotFlag = &cli.BoolFlag{ + Name: "snapshot", + Usage: `Enables snapshot-database mode (default = enable)`, + Value: true, + Category: flags.EthCategory, + } + LightKDFFlag = &cli.BoolFlag{ + Name: "lightkdf", + Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength", + Category: flags.AccountCategory, + } + EthRequiredBlocksFlag = &cli.StringFlag{ + Name: "eth.requiredblocks", + Usage: "Comma separated block number-to-hash mappings to require for peering (=)", + Category: flags.EthCategory, + } + BloomFilterSizeFlag = &cli.Uint64Flag{ + Name: "bloomfilter.size", + Usage: "Megabytes of memory allocated to bloom-filter for pruning", + Value: 2048, + Category: flags.EthCategory, + } + OverrideCancun = &cli.Uint64Flag{ + Name: "override.cancun", + Usage: "Manually specify the Cancun fork timestamp, overriding the bundled setting", + Category: flags.EthCategory, + } + OverrideVerkle = &cli.Uint64Flag{ + Name: "override.verkle", + Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting", + Category: flags.EthCategory, + } + SyncModeFlag = &flags.TextMarshalerFlag{ + Name: "syncmode", + Usage: `Blockchain sync mode ("snap" 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, + } + // Beacon client light sync settings + BeaconApiFlag = &cli.StringSliceFlag{ + Name: "beacon.api", + Usage: "Beacon node (CL) light client API URL. This flag can be given multiple times.", + Category: flags.BeaconCategory, + } + BeaconApiHeaderFlag = &cli.StringSliceFlag{ + Name: "beacon.api.header", + Usage: "Pass custom HTTP header fields to the emote beacon node API in \"key:value\" format. This flag can be given multiple times.", + Category: flags.BeaconCategory, + } + BeaconThresholdFlag = &cli.IntFlag{ + Name: "beacon.threshold", + Usage: "Beacon sync committee participation threshold", + Value: bparams.SyncCommitteeSupermajority, + Category: flags.BeaconCategory, + } + BeaconNoFilterFlag = &cli.BoolFlag{ + Name: "beacon.nofilter", + Usage: "Disable future slot signature filter", + Category: flags.BeaconCategory, + } + BeaconConfigFlag = &cli.StringFlag{ + Name: "beacon.config", + Usage: "Beacon chain config YAML file", + Category: flags.BeaconCategory, + } + BeaconGenesisRootFlag = &cli.StringFlag{ + Name: "beacon.genesis.gvroot", + Usage: "Beacon chain genesis validators root", + Category: flags.BeaconCategory, + } + BeaconGenesisTimeFlag = &cli.Uint64Flag{ + Name: "beacon.genesis.time", + Usage: "Beacon chain genesis time", + Category: flags.BeaconCategory, + } + BeaconCheckpointFlag = &cli.StringFlag{ + Name: "beacon.checkpoint", + Usage: "Beacon chain weak subjectivity checkpoint block hash", + Category: flags.BeaconCategory, + } + BlsyncApiFlag = &cli.StringFlag{ + Name: "blsync.engine.api", + Usage: "Target EL engine API URL", + Category: flags.BeaconCategory, + } + BlsyncJWTSecretFlag = &cli.StringFlag{ + Name: "blsync.jwtsecret", + Usage: "Path to a JWT secret to use for target engine API endpoint", + Category: flags.BeaconCategory, + } + // Transaction pool settings + TxPoolLocalsFlag = &cli.StringFlag{ + Name: "txpool.locals", + Usage: "Comma separated accounts to treat as locals (no flush, priority inclusion)", + Category: flags.TxPoolCategory, + } + TxPoolNoLocalsFlag = &cli.BoolFlag{ + Name: "txpool.nolocals", + Usage: "Disables price exemptions for locally submitted transactions", + Category: flags.TxPoolCategory, + } + TxPoolJournalFlag = &cli.StringFlag{ + Name: "txpool.journal", + Usage: "Disk journal for local transaction to survive node restarts", + Value: ethconfig.Defaults.TxPool.Journal, + Category: flags.TxPoolCategory, + } + TxPoolRejournalFlag = &cli.DurationFlag{ + Name: "txpool.rejournal", + Usage: "Time interval to regenerate the local transaction journal", + Value: ethconfig.Defaults.TxPool.Rejournal, + Category: flags.TxPoolCategory, + } + TxPoolPriceLimitFlag = &cli.Uint64Flag{ + Name: "txpool.pricelimit", + Usage: "Minimum gas price tip to enforce for acceptance into the pool", + Value: ethconfig.Defaults.TxPool.PriceLimit, + Category: flags.TxPoolCategory, + } + TxPoolPriceBumpFlag = &cli.Uint64Flag{ + Name: "txpool.pricebump", + Usage: "Price bump percentage to replace an already existing transaction", + Value: ethconfig.Defaults.TxPool.PriceBump, + Category: flags.TxPoolCategory, + } + TxPoolAccountSlotsFlag = &cli.Uint64Flag{ + Name: "txpool.accountslots", + Usage: "Minimum number of executable transaction slots guaranteed per account", + Value: ethconfig.Defaults.TxPool.AccountSlots, + Category: flags.TxPoolCategory, + } + TxPoolGlobalSlotsFlag = &cli.Uint64Flag{ + Name: "txpool.globalslots", + Usage: "Maximum number of executable transaction slots for all accounts", + Value: ethconfig.Defaults.TxPool.GlobalSlots, + Category: flags.TxPoolCategory, + } + TxPoolAccountQueueFlag = &cli.Uint64Flag{ + Name: "txpool.accountqueue", + Usage: "Maximum number of non-executable transaction slots permitted per account", + Value: ethconfig.Defaults.TxPool.AccountQueue, + Category: flags.TxPoolCategory, + } + TxPoolGlobalQueueFlag = &cli.Uint64Flag{ + Name: "txpool.globalqueue", + Usage: "Maximum number of non-executable transaction slots for all accounts", + Value: ethconfig.Defaults.TxPool.GlobalQueue, + Category: flags.TxPoolCategory, + } + TxPoolLifetimeFlag = &cli.DurationFlag{ + Name: "txpool.lifetime", + Usage: "Maximum amount of time non-executable transaction are queued", + Value: ethconfig.Defaults.TxPool.Lifetime, + Category: flags.TxPoolCategory, + } + // Blob transaction pool settings + BlobPoolDataDirFlag = &cli.StringFlag{ + Name: "blobpool.datadir", + Usage: "Data directory to store blob transactions in", + Value: ethconfig.Defaults.BlobPool.Datadir, + Category: flags.BlobPoolCategory, + } + BlobPoolDataCapFlag = &cli.Uint64Flag{ + Name: "blobpool.datacap", + Usage: "Disk space to allocate for pending blob transactions (soft limit)", + Value: ethconfig.Defaults.BlobPool.Datacap, + Category: flags.BlobPoolCategory, + } + BlobPoolPriceBumpFlag = &cli.Uint64Flag{ + Name: "blobpool.pricebump", + Usage: "Price bump percentage to replace an already existing blob transaction", + Value: ethconfig.Defaults.BlobPool.PriceBump, + Category: flags.BlobPoolCategory, + } + // Performance tuning settings + CacheFlag = &cli.IntFlag{ + Name: "cache", + Usage: "Megabytes of memory allocated to internal caching (default = 4096 mainnet full node, 128 light mode)", + Value: 1024, + Category: flags.PerfCategory, + } + CacheDatabaseFlag = &cli.IntFlag{ + Name: "cache.database", + Usage: "Percentage of cache memory allowance to use for database io", + Value: 50, + Category: flags.PerfCategory, + } + CacheTrieFlag = &cli.IntFlag{ + Name: "cache.trie", + Usage: "Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode)", + Value: 15, + Category: flags.PerfCategory, + } + CacheGCFlag = &cli.IntFlag{ + Name: "cache.gc", + Usage: "Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)", + Value: 25, + Category: flags.PerfCategory, + } + CacheSnapshotFlag = &cli.IntFlag{ + Name: "cache.snapshot", + Usage: "Percentage of cache memory allowance to use for snapshot caching (default = 10% full mode, 20% archive mode)", + Value: 10, + Category: flags.PerfCategory, + } + CacheNoPrefetchFlag = &cli.BoolFlag{ + Name: "cache.noprefetch", + Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)", + Category: flags.PerfCategory, + } + CachePreimagesFlag = &cli.BoolFlag{ + Name: "cache.preimages", + Usage: "Enable recording the SHA3/keccak preimages of trie keys", + Category: flags.PerfCategory, + } + CacheLogSizeFlag = &cli.IntFlag{ + Name: "cache.blocklogs", + Usage: "Size (in number of blocks) of the log cache for filtering", + Category: flags.PerfCategory, + Value: ethconfig.Defaults.FilterLogCacheSize, + } + FDLimitFlag = &cli.IntFlag{ + Name: "fdlimit", + Usage: "Raise the open file descriptor resource limit (default = system fd limit)", + Category: flags.PerfCategory, + } + CryptoKZGFlag = &cli.StringFlag{ + Name: "crypto.kzg", + Usage: "KZG library implementation to use; gokzg (recommended) or ckzg", + Value: "gokzg", + Category: flags.PerfCategory, + } + + // Miner settings + MinerGasLimitFlag = &cli.Uint64Flag{ + Name: "miner.gaslimit", + Usage: "Target gas ceiling for mined blocks", + Value: ethconfig.Defaults.Miner.GasCeil, + Category: flags.MinerCategory, + } + MinerGasPriceFlag = &flags.BigFlag{ + Name: "miner.gasprice", + Usage: "Minimum gas price for mining a transaction", + Value: ethconfig.Defaults.Miner.GasPrice, + Category: flags.MinerCategory, + } + MinerExtraDataFlag = &cli.StringFlag{ + Name: "miner.extradata", + Usage: "Block extra data set by the miner (default = client version)", + Category: flags.MinerCategory, + } + MinerRecommitIntervalFlag = &cli.DurationFlag{ + Name: "miner.recommit", + Usage: "Time interval to recreate the block being mined", + Value: ethconfig.Defaults.Miner.Recommit, + Category: flags.MinerCategory, + } + MinerPendingFeeRecipientFlag = &cli.StringFlag{ + Name: "miner.pending.feeRecipient", + Usage: "0x prefixed public address for the pending block producer (not used for actual block production)", + Category: flags.MinerCategory, + } + + // Account settings + UnlockedAccountFlag = &cli.StringFlag{ + Name: "unlock", + Usage: "Comma separated list of accounts to unlock", + Value: "", + Category: flags.AccountCategory, + } + PasswordFileFlag = &cli.PathFlag{ + Name: "password", + Usage: "Password file to use for non-interactive password input", + TakesFile: true, + Category: flags.AccountCategory, + } + ExternalSignerFlag = &cli.StringFlag{ + Name: "signer", + Usage: "External signer (url or path to ipc file)", + Value: "", + Category: flags.AccountCategory, + } + InsecureUnlockAllowedFlag = &cli.BoolFlag{ + Name: "allow-insecure-unlock", + Usage: "Allow insecure account unlocking when account-related RPCs are exposed by http", + Category: flags.AccountCategory, + } + + // EVM settings + VMEnableDebugFlag = &cli.BoolFlag{ + Name: "vmdebug", + Usage: "Record information useful for VM and contract debugging", + Category: flags.VMCategory, + } + VMTraceFlag = &cli.StringFlag{ + Name: "vmtrace", + Usage: "Name of tracer which should record internal VM operations (costly)", + Category: flags.VMCategory, + } + VMTraceJsonConfigFlag = &cli.StringFlag{ + Name: "vmtrace.jsonconfig", + Usage: "Tracer configuration (JSON)", + Category: flags.VMCategory, + } + // API options. + RPCGlobalGasCapFlag = &cli.Uint64Flag{ + Name: "rpc.gascap", + Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", + Value: ethconfig.Defaults.RPCGasCap, + Category: flags.APICategory, + } + RPCGlobalEVMTimeoutFlag = &cli.DurationFlag{ + Name: "rpc.evmtimeout", + Usage: "Sets a timeout used for eth_call (0=infinite)", + Value: ethconfig.Defaults.RPCEVMTimeout, + Category: flags.APICategory, + } + RPCGlobalTxFeeCapFlag = &cli.Float64Flag{ + Name: "rpc.txfeecap", + Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)", + Value: ethconfig.Defaults.RPCTxFeeCap, + Category: flags.APICategory, + } + // Authenticated RPC HTTP settings + AuthListenFlag = &cli.StringFlag{ + Name: "authrpc.addr", + Usage: "Listening address for authenticated APIs", + Value: node.DefaultConfig.AuthAddr, + Category: flags.APICategory, + } + AuthPortFlag = &cli.IntFlag{ + Name: "authrpc.port", + Usage: "Listening port for authenticated APIs", + Value: node.DefaultConfig.AuthPort, + Category: flags.APICategory, + } + AuthVirtualHostsFlag = &cli.StringFlag{ + Name: "authrpc.vhosts", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", + Value: strings.Join(node.DefaultConfig.AuthVirtualHosts, ","), + Category: flags.APICategory, + } + JWTSecretFlag = &flags.DirectoryFlag{ + Name: "authrpc.jwtsecret", + Usage: "Path to a JWT secret to use for authenticated RPC endpoints", + Category: flags.APICategory, + } + + // Logging and debug settings + EthStatsURLFlag = &cli.StringFlag{ + Name: "ethstats", + Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)", + Category: flags.MetricsCategory, + } + NoCompactionFlag = &cli.BoolFlag{ + Name: "nocompaction", + Usage: "Disables db compaction after import", + Category: flags.LoggingCategory, + } + CollectWitnessFlag = &cli.BoolFlag{ + Name: "collectwitness", + Usage: "Enable state witness generation during block execution. Work in progress flag, don't use.", + Category: flags.MiscCategory, + } + + // MISC settings + SyncTargetFlag = &cli.StringFlag{ + Name: "synctarget", + Usage: `Hash of the block to full sync to (dev testing feature)`, + TakesFile: true, + Category: flags.MiscCategory, + } + IliadFlag = &cli.BoolFlag{ + Name: "iliad", + Usage: "iliad test network: pre-configured proof-of-stake test network", + Category: flags.MiscCategory, + } + LocalFlag = &cli.BoolFlag{ + Name: "local", + Usage: "local test network: pre-configured local proof-of-stake test network", + Category: flags.MiscCategory, + } + + // RPC settings + IPCDisabledFlag = &cli.BoolFlag{ + Name: "ipcdisable", + Usage: "Disable the IPC-RPC server", + Category: flags.APICategory, + } + IPCPathFlag = &flags.DirectoryFlag{ + Name: "ipcpath", + Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)", + Category: flags.APICategory, + } + HTTPEnabledFlag = &cli.BoolFlag{ + Name: "http", + Usage: "Enable the HTTP-RPC server", + Category: flags.APICategory, + } + HTTPListenAddrFlag = &cli.StringFlag{ + Name: "http.addr", + Usage: "HTTP-RPC server listening interface", + Value: node.DefaultHTTPHost, + Category: flags.APICategory, + } + HTTPPortFlag = &cli.IntFlag{ + Name: "http.port", + Usage: "HTTP-RPC server listening port", + Value: node.DefaultHTTPPort, + Category: flags.APICategory, + } + HTTPCORSDomainFlag = &cli.StringFlag{ + Name: "http.corsdomain", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", + Value: "", + Category: flags.APICategory, + } + HTTPVirtualHostsFlag = &cli.StringFlag{ + Name: "http.vhosts", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", + Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","), + Category: flags.APICategory, + } + HTTPApiFlag = &cli.StringFlag{ + Name: "http.api", + Usage: "API's offered over the HTTP-RPC interface", + Value: "", + Category: flags.APICategory, + } + HTTPPathPrefixFlag = &cli.StringFlag{ + Name: "http.rpcprefix", + Usage: "HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths.", + Value: "", + Category: flags.APICategory, + } + GraphQLEnabledFlag = &cli.BoolFlag{ + Name: "graphql", + Usage: "Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.", + Category: flags.APICategory, + } + GraphQLCORSDomainFlag = &cli.StringFlag{ + Name: "graphql.corsdomain", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)", + Value: "", + Category: flags.APICategory, + } + GraphQLVirtualHostsFlag = &cli.StringFlag{ + Name: "graphql.vhosts", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.", + Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","), + Category: flags.APICategory, + } + WSEnabledFlag = &cli.BoolFlag{ + Name: "ws", + Usage: "Enable the WS-RPC server", + Category: flags.APICategory, + } + WSListenAddrFlag = &cli.StringFlag{ + Name: "ws.addr", + Usage: "WS-RPC server listening interface", + Value: node.DefaultWSHost, + Category: flags.APICategory, + } + WSPortFlag = &cli.IntFlag{ + Name: "ws.port", + Usage: "WS-RPC server listening port", + Value: node.DefaultWSPort, + Category: flags.APICategory, + } + WSApiFlag = &cli.StringFlag{ + Name: "ws.api", + Usage: "API's offered over the WS-RPC interface", + Value: "", + Category: flags.APICategory, + } + WSAllowedOriginsFlag = &cli.StringFlag{ + Name: "ws.origins", + Usage: "Origins from which to accept websockets requests", + Value: "", + Category: flags.APICategory, + } + WSPathPrefixFlag = &cli.StringFlag{ + Name: "ws.rpcprefix", + Usage: "HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths.", + Value: "", + Category: flags.APICategory, + } + ExecFlag = &cli.StringFlag{ + Name: "exec", + Usage: "Execute JavaScript statement", + Category: flags.APICategory, + } + PreloadJSFlag = &cli.StringFlag{ + Name: "preload", + Usage: "Comma separated list of JavaScript files to preload into the console", + Category: flags.APICategory, + } + AllowUnprotectedTxs = &cli.BoolFlag{ + Name: "rpc.allow-unprotected-txs", + Usage: "Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC", + Category: flags.APICategory, + } + BatchRequestLimit = &cli.IntFlag{ + Name: "rpc.batch-request-limit", + Usage: "Maximum number of requests in a batch", + Value: node.DefaultConfig.BatchRequestLimit, + Category: flags.APICategory, + } + BatchResponseMaxSize = &cli.IntFlag{ + Name: "rpc.batch-response-max-size", + Usage: "Maximum number of bytes returned from a batched call", + Value: node.DefaultConfig.BatchResponseMaxSize, + Category: flags.APICategory, + } + EnablePersonal = &cli.BoolFlag{ + Name: "rpc.enabledeprecatedpersonal", + Usage: "Enables the (deprecated) personal namespace", + Category: flags.APICategory, + } + + // Network Settings + MaxPeersFlag = &cli.IntFlag{ + Name: "maxpeers", + Usage: "Maximum number of network peers (network disabled if set to 0)", + Value: node.DefaultConfig.P2P.MaxPeers, + Category: flags.NetworkingCategory, + } + MaxPendingPeersFlag = &cli.IntFlag{ + Name: "maxpendpeers", + Usage: "Maximum number of pending connection attempts (defaults used if set to 0)", + Value: node.DefaultConfig.P2P.MaxPendingPeers, + Category: flags.NetworkingCategory, + } + ListenPortFlag = &cli.IntFlag{ + Name: "port", + Usage: "Network listening port", + Value: 30303, + Category: flags.NetworkingCategory, + } + BootnodesFlag = &cli.StringFlag{ + Name: "bootnodes", + Usage: "Comma separated enode URLs for P2P discovery bootstrap", + Value: "", + Category: flags.NetworkingCategory, + } + NodeKeyFileFlag = &cli.StringFlag{ + Name: "nodekey", + Usage: "P2P node key file", + Category: flags.NetworkingCategory, + } + NodeKeyHexFlag = &cli.StringFlag{ + Name: "nodekeyhex", + Usage: "P2P node key as hex (for testing)", + Category: flags.NetworkingCategory, + } + NATFlag = &cli.StringFlag{ + Name: "nat", + Usage: "NAT port mapping mechanism (any|none|upnp|pmp|pmp:|extip:)", + Value: "any", + Category: flags.NetworkingCategory, + } + NoDiscoverFlag = &cli.BoolFlag{ + Name: "nodiscover", + Usage: "Disables the peer discovery mechanism (manual peer addition)", + Category: flags.NetworkingCategory, + } + DiscoveryV4Flag = &cli.BoolFlag{ + Name: "discovery.v4", + Aliases: []string{"discv4"}, + Usage: "Enables the V4 discovery mechanism", + Category: flags.NetworkingCategory, + Value: true, + } + DiscoveryV5Flag = &cli.BoolFlag{ + Name: "discovery.v5", + Aliases: []string{"discv5"}, + Usage: "Enables the experimental RLPx V5 (Topic Discovery) mechanism", + Category: flags.NetworkingCategory, + } + NetrestrictFlag = &cli.StringFlag{ + Name: "netrestrict", + Usage: "Restricts network communication to the given IP networks (CIDR masks)", + Category: flags.NetworkingCategory, + } + DNSDiscoveryFlag = &cli.StringFlag{ + Name: "discovery.dns", + Usage: "Sets DNS discovery entry points (use \"\" to disable DNS)", + Category: flags.NetworkingCategory, + } + DiscoveryPortFlag = &cli.IntFlag{ + Name: "discovery.port", + Usage: "Use a custom UDP port for P2P discovery", + Value: 30303, + Category: flags.NetworkingCategory, + } + + // Console + JSpathFlag = &flags.DirectoryFlag{ + Name: "jspath", + Usage: "JavaScript root path for `loadScript`", + Value: flags.DirectoryString("."), + Category: flags.APICategory, + } + HttpHeaderFlag = &cli.StringSliceFlag{ + Name: "header", + Aliases: []string{"H"}, + Usage: "Pass custom headers to the RPC server when using --" + RemoteDBFlag.Name + " or the geth attach console. This flag can be given multiple times.", + Category: flags.APICategory, + } + + // Gas price oracle settings + GpoBlocksFlag = &cli.IntFlag{ + Name: "gpo.blocks", + Usage: "Number of recent blocks to check for gas prices", + Value: ethconfig.Defaults.GPO.Blocks, + Category: flags.GasPriceCategory, + } + GpoPercentileFlag = &cli.IntFlag{ + Name: "gpo.percentile", + Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices", + Value: ethconfig.Defaults.GPO.Percentile, + Category: flags.GasPriceCategory, + } + GpoMaxGasPriceFlag = &cli.Int64Flag{ + Name: "gpo.maxprice", + Usage: "Maximum transaction priority fee (or gasprice before London fork) to be recommended by gpo", + Value: ethconfig.Defaults.GPO.MaxPrice.Int64(), + Category: flags.GasPriceCategory, + } + GpoIgnoreGasPriceFlag = &cli.Int64Flag{ + Name: "gpo.ignoreprice", + Usage: "Gas price below which gpo will ignore transactions", + Value: ethconfig.Defaults.GPO.IgnorePrice.Int64(), + Category: flags.GasPriceCategory, + } + + // Metrics flags + MetricsEnabledFlag = &cli.BoolFlag{ + Name: "metrics", + Usage: "Enable metrics collection and reporting", + Category: flags.MetricsCategory, + } + // MetricsHTTPFlag defines the endpoint for a stand-alone metrics HTTP endpoint. + // Since the pprof service enables sensitive/vulnerable behavior, this allows a user + // to enable a public-OK metrics endpoint without having to worry about ALSO exposing + // other profiling behavior or information. + MetricsHTTPFlag = &cli.StringFlag{ + Name: "metrics.addr", + Usage: `Enable stand-alone metrics HTTP server listening interface.`, + Category: flags.MetricsCategory, + } + MetricsPortFlag = &cli.IntFlag{ + Name: "metrics.port", + Usage: `Metrics HTTP server listening port. +Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.`, + Value: metrics.DefaultConfig.Port, + Category: flags.MetricsCategory, + } + MetricsEnableInfluxDBFlag = &cli.BoolFlag{ + Name: "metrics.influxdb", + Usage: "Enable metrics export/push to an external InfluxDB database", + Category: flags.MetricsCategory, + } + MetricsInfluxDBEndpointFlag = &cli.StringFlag{ + Name: "metrics.influxdb.endpoint", + Usage: "InfluxDB API endpoint to report metrics to", + Value: metrics.DefaultConfig.InfluxDBEndpoint, + Category: flags.MetricsCategory, + } + MetricsInfluxDBDatabaseFlag = &cli.StringFlag{ + Name: "metrics.influxdb.database", + Usage: "InfluxDB database name to push reported metrics to", + Value: metrics.DefaultConfig.InfluxDBDatabase, + Category: flags.MetricsCategory, + } + MetricsInfluxDBUsernameFlag = &cli.StringFlag{ + Name: "metrics.influxdb.username", + Usage: "Username to authorize access to the database", + Value: metrics.DefaultConfig.InfluxDBUsername, + Category: flags.MetricsCategory, + } + MetricsInfluxDBPasswordFlag = &cli.StringFlag{ + Name: "metrics.influxdb.password", + Usage: "Password to authorize access to the database", + Value: metrics.DefaultConfig.InfluxDBPassword, + Category: flags.MetricsCategory, + } + // Tags are part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB. + // For example `host` tag could be used so that we can group all nodes and average a measurement + // across all of them, but also so that we can select a specific node and inspect its measurements. + // https://docs.influxdata.com/influxdb/v1.4/concepts/key_concepts/#tag-key + MetricsInfluxDBTagsFlag = &cli.StringFlag{ + Name: "metrics.influxdb.tags", + Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements", + Value: metrics.DefaultConfig.InfluxDBTags, + Category: flags.MetricsCategory, + } + + MetricsEnableInfluxDBV2Flag = &cli.BoolFlag{ + Name: "metrics.influxdbv2", + Usage: "Enable metrics export/push to an external InfluxDB v2 database", + Category: flags.MetricsCategory, + } + + MetricsInfluxDBTokenFlag = &cli.StringFlag{ + Name: "metrics.influxdb.token", + Usage: "Token to authorize access to the database (v2 only)", + Value: metrics.DefaultConfig.InfluxDBToken, + Category: flags.MetricsCategory, + } + + MetricsInfluxDBBucketFlag = &cli.StringFlag{ + Name: "metrics.influxdb.bucket", + Usage: "InfluxDB bucket name to push reported metrics to (v2 only)", + Value: metrics.DefaultConfig.InfluxDBBucket, + Category: flags.MetricsCategory, + } + + MetricsInfluxDBOrganizationFlag = &cli.StringFlag{ + Name: "metrics.influxdb.organization", + Usage: "InfluxDB organization name (v2 only)", + Value: metrics.DefaultConfig.InfluxDBOrganization, + Category: flags.MetricsCategory, + } +) + +var ( + // TestnetFlags is the flag group of all built-in supported testnets. + TestnetFlags = []cli.Flag{ + GoerliFlag, + SepoliaFlag, + HoleskyFlag, + IliadFlag, + LocalFlag, + } + // NetworkFlags is the flag group of all built-in supported networks. + NetworkFlags = append([]cli.Flag{MainnetFlag}, TestnetFlags...) + + // DatabaseFlags is the flag group of all database flags. + DatabaseFlags = []cli.Flag{ + DataDirFlag, + AncientFlag, + RemoteDBFlag, + DBEngineFlag, + StateSchemeFlag, + HttpHeaderFlag, + } +) + +// 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. +func MakeDataDir(ctx *cli.Context) string { + if path := ctx.String(DataDirFlag.Name); path != "" { + if ctx.Bool(GoerliFlag.Name) { + return filepath.Join(path, "goerli") + } + if ctx.Bool(SepoliaFlag.Name) { + return filepath.Join(path, "sepolia") + } + if ctx.Bool(HoleskyFlag.Name) { + return filepath.Join(path, "holesky") + } + if ctx.Bool(IliadFlag.Name) { + return filepath.Join(path, "iliad") + } + if ctx.Bool(LocalFlag.Name) { + return filepath.Join(path, "local") + } + return path + } + Fatalf("Cannot determine default data directory, please set manually (--datadir)") + return "" +} + +// 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 ephemeral key is to be generated. +func setNodeKey(ctx *cli.Context, cfg *p2p.Config) { + var ( + hex = ctx.String(NodeKeyHexFlag.Name) + file = ctx.String(NodeKeyFileFlag.Name) + key *ecdsa.PrivateKey + err error + ) + switch { + case file != "" && hex != "": + Fatalf("Options %q and %q are mutually exclusive", NodeKeyFileFlag.Name, NodeKeyHexFlag.Name) + case file != "": + if key, err = crypto.LoadECDSA(file); err != nil { + Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err) + } + cfg.PrivateKey = key + case hex != "": + if key, err = crypto.HexToECDSA(hex); err != nil { + Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err) + } + cfg.PrivateKey = key + } +} + +// setNodeUserIdent creates the user identifier from CLI flags. +func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) { + if identity := ctx.String(IdentityFlag.Name); len(identity) > 0 { + cfg.UserIdent = identity + } +} + +// 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 + if ctx.IsSet(BootnodesFlag.Name) { + urls = SplitAndTrim(ctx.String(BootnodesFlag.Name)) + } 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 + case ctx.Bool(IliadFlag.Name): + urls = params.IliadBootnodes + case ctx.Bool(LocalFlag.Name): + urls = params.LocalBootnodes + } + } + cfg.BootstrapNodes = mustParseBootnodes(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) + return nil + } + nodes = append(nodes, node) + } + } + return nodes +} + +// setBootstrapNodesV5 creates a list of bootstrap nodes from the command line +// flags, reverting to pre-configured ones if none have been specified. +func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { + urls := params.V5Bootnodes + switch { + case ctx.IsSet(BootnodesFlag.Name): + urls = SplitAndTrim(ctx.String(BootnodesFlag.Name)) + case cfg.BootstrapNodesV5 != nil: + return // already set, don't apply defaults. + } + + cfg.BootstrapNodesV5 = make([]*enode.Node, 0, len(urls)) + for _, url := range urls { + if url != "" { + node, err := enode.Parse(enode.ValidSchemes, url) + if err != nil { + log.Error("Bootstrap URL invalid", "enode", url, "err", err) + continue + } + cfg.BootstrapNodesV5 = append(cfg.BootstrapNodesV5, node) + } + } +} + +// setListenAddress creates TCP/UDP listening address strings from set command +// line flags +func setListenAddress(ctx *cli.Context, cfg *p2p.Config) { + if ctx.IsSet(ListenPortFlag.Name) { + cfg.ListenAddr = fmt.Sprintf(":%d", ctx.Int(ListenPortFlag.Name)) + } + if ctx.IsSet(DiscoveryPortFlag.Name) { + cfg.DiscAddr = fmt.Sprintf(":%d", ctx.Int(DiscoveryPortFlag.Name)) + } +} + +// setNAT creates a port mapper from command line flags. +func setNAT(ctx *cli.Context, cfg *p2p.Config) { + if ctx.IsSet(NATFlag.Name) { + natif, err := nat.Parse(ctx.String(NATFlag.Name)) + if err != nil { + Fatalf("Option %s: %v", NATFlag.Name, err) + } + cfg.NAT = natif + } +} + +// SplitAndTrim splits input separated by a comma +// and trims excessive white space from the substrings. +func SplitAndTrim(input string) (ret []string) { + l := strings.Split(input, ",") + for _, r := range l { + if r = strings.TrimSpace(r); r != "" { + ret = append(ret, r) + } + } + return ret +} + +// 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) || ctx.Bool(LocalFlag.Name) { + if cfg.HTTPHost == "" { + cfg.HTTPHost = "127.0.0.1" + } + if ctx.IsSet(HTTPListenAddrFlag.Name) { + cfg.HTTPHost = ctx.String(HTTPListenAddrFlag.Name) + } + } + + if ctx.IsSet(HTTPPortFlag.Name) { + cfg.HTTPPort = ctx.Int(HTTPPortFlag.Name) + } + + if ctx.IsSet(AuthListenFlag.Name) { + cfg.AuthAddr = ctx.String(AuthListenFlag.Name) + } + + if ctx.IsSet(AuthPortFlag.Name) { + cfg.AuthPort = ctx.Int(AuthPortFlag.Name) + } + + if ctx.IsSet(AuthVirtualHostsFlag.Name) { + cfg.AuthVirtualHosts = SplitAndTrim(ctx.String(AuthVirtualHostsFlag.Name)) + } + + if ctx.IsSet(HTTPCORSDomainFlag.Name) { + cfg.HTTPCors = SplitAndTrim(ctx.String(HTTPCORSDomainFlag.Name)) + } + + if ctx.IsSet(HTTPApiFlag.Name) { + cfg.HTTPModules = SplitAndTrim(ctx.String(HTTPApiFlag.Name)) + } + + if ctx.IsSet(HTTPVirtualHostsFlag.Name) { + cfg.HTTPVirtualHosts = SplitAndTrim(ctx.String(HTTPVirtualHostsFlag.Name)) + } + + if ctx.IsSet(HTTPPathPrefixFlag.Name) { + cfg.HTTPPathPrefix = ctx.String(HTTPPathPrefixFlag.Name) + } + if ctx.IsSet(AllowUnprotectedTxs.Name) { + cfg.AllowUnprotectedTxs = ctx.Bool(AllowUnprotectedTxs.Name) + } + + if ctx.IsSet(BatchRequestLimit.Name) { + cfg.BatchRequestLimit = ctx.Int(BatchRequestLimit.Name) + } + + if ctx.IsSet(BatchResponseMaxSize.Name) { + cfg.BatchResponseMaxSize = ctx.Int(BatchResponseMaxSize.Name) + } +} + +// setGraphQL creates the GraphQL listener interface string from the set +// command line flags, returning empty if the GraphQL endpoint is disabled. +func setGraphQL(ctx *cli.Context, cfg *node.Config) { + if ctx.IsSet(GraphQLCORSDomainFlag.Name) { + cfg.GraphQLCors = SplitAndTrim(ctx.String(GraphQLCORSDomainFlag.Name)) + } + if ctx.IsSet(GraphQLVirtualHostsFlag.Name) { + cfg.GraphQLVirtualHosts = SplitAndTrim(ctx.String(GraphQLVirtualHostsFlag.Name)) + } +} + +// 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) { + if cfg.WSHost == "" { + cfg.WSHost = "127.0.0.1" + } + if ctx.IsSet(WSListenAddrFlag.Name) { + cfg.WSHost = ctx.String(WSListenAddrFlag.Name) + } + } + if ctx.IsSet(WSPortFlag.Name) { + cfg.WSPort = ctx.Int(WSPortFlag.Name) + } + + if ctx.IsSet(WSAllowedOriginsFlag.Name) { + cfg.WSOrigins = SplitAndTrim(ctx.String(WSAllowedOriginsFlag.Name)) + } + + if ctx.IsSet(WSApiFlag.Name) { + cfg.WSModules = SplitAndTrim(ctx.String(WSApiFlag.Name)) + } + + if ctx.IsSet(WSPathPrefixFlag.Name) { + cfg.WSPathPrefix = ctx.String(WSPathPrefixFlag.Name) + } +} + +// setIPC creates an IPC path configuration from the set command line flags, +// returning an empty string if IPC was explicitly disabled, or the set path. +func setIPC(ctx *cli.Context, cfg *node.Config) { + CheckExclusive(ctx, IPCDisabledFlag, IPCPathFlag) + switch { + case ctx.Bool(IPCDisabledFlag.Name): + cfg.IPCPath = "" + case ctx.IsSet(IPCPathFlag.Name): + cfg.IPCPath = ctx.String(IPCPathFlag.Name) + } +} + +// setLes shows the deprecation warnings for LES flags. +func setLes(ctx *cli.Context, cfg *ethconfig.Config) { + if ctx.IsSet(LightServeFlag.Name) { + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightServeFlag.Name) + } + if ctx.IsSet(LightIngressFlag.Name) { + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightIngressFlag.Name) + } + if ctx.IsSet(LightEgressFlag.Name) { + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightEgressFlag.Name) + } + if ctx.IsSet(LightMaxPeersFlag.Name) { + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightMaxPeersFlag.Name) + } + if ctx.IsSet(LightNoPruneFlag.Name) { + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoPruneFlag.Name) + } + if ctx.IsSet(LightNoSyncServeFlag.Name) { + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoSyncServeFlag.Name) + } +} + +// MakeDatabaseHandles raises out the number of allowed file handles per process +// for Geth and returns half of the allowance to assign to the database. +func MakeDatabaseHandles(max int) int { + limit, err := fdlimit.Maximum() + if err != nil { + Fatalf("Failed to retrieve file descriptor allowance: %v", err) + } + switch { + case max == 0: + // User didn't specify a meaningful value, use system limits + case max < 128: + // User specified something unhealthy, just use system defaults + log.Error("File descriptor limit invalid (<128)", "had", max, "updated", limit) + case max > limit: + // User requested more than the OS allows, notify that we can't allocate it + log.Warn("Requested file descriptors denied by OS", "req", max, "limit", limit) + default: + // User limit is meaningful and within allowed range, use that + limit = max + } + raised, err := fdlimit.Raise(uint64(limit)) + if err != nil { + Fatalf("Failed to raise file descriptor allowance: %v", err) + } + return int(raised / 2) // Leave half for networking and other stuff +} + +// MakeAddress converts an account specified directly as a hex encoded string or +// a key index in the key store to an internal account representation. +func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error) { + // If the specified account is a valid address, return it + if common.IsHexAddress(account) { + return accounts.Account{Address: common.HexToAddress(account)}, nil + } + // Otherwise try to interpret the account as a keystore index + index, err := strconv.Atoi(account) + if err != nil || index < 0 { + return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account) + } + log.Warn("-------------------------------------------------------------------") + log.Warn("Referring to accounts by order in the keystore folder is dangerous!") + log.Warn("This functionality is deprecated and will be removed in the future!") + log.Warn("Please use explicit addresses! (can search via `geth account list`)") + log.Warn("-------------------------------------------------------------------") + + accs := ks.Accounts() + if len(accs) <= index { + return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs)) + } + return accs[index], nil +} + +// setEtherbase retrieves the etherbase from the directly specified command line flags. +func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) { + if ctx.IsSet(MinerEtherbaseFlag.Name) { + log.Warn("Option --miner.etherbase is deprecated as the etherbase is set by the consensus client post-merge") + return + } + if !ctx.IsSet(MinerPendingFeeRecipientFlag.Name) { + return + } + addr := ctx.String(MinerPendingFeeRecipientFlag.Name) + if strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X") { + addr = addr[2:] + } + b, err := hex.DecodeString(addr) + if err != nil || len(b) != common.AddressLength { + Fatalf("-%s: invalid pending block producer address %q", MinerPendingFeeRecipientFlag.Name, addr) + return + } + cfg.Miner.PendingFeeRecipient = common.BytesToAddress(b) +} + +// MakePasswordList reads password lines from the file specified by the global --password flag. +func MakePasswordList(ctx *cli.Context) []string { + path := ctx.Path(PasswordFileFlag.Name) + if path == "" { + return nil + } + text, err := os.ReadFile(path) + if err != nil { + Fatalf("Failed to read password file: %v", err) + } + lines := strings.Split(string(text), "\n") + // Sanitise DOS line endings. + for i := range lines { + lines[i] = strings.TrimRight(lines[i], "\r") + } + return lines +} + +func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { + setNodeKey(ctx, cfg) + setNAT(ctx, cfg) + setListenAddress(ctx, cfg) + setBootstrapNodes(ctx, cfg) + setBootstrapNodesV5(ctx, cfg) + + if ctx.IsSet(MaxPeersFlag.Name) { + cfg.MaxPeers = ctx.Int(MaxPeersFlag.Name) + } + 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) { + cfg.NoDiscovery = true + } + + CheckExclusive(ctx, DiscoveryV4Flag, NoDiscoverFlag) + CheckExclusive(ctx, DiscoveryV5Flag, NoDiscoverFlag) + cfg.DiscoveryV4 = ctx.Bool(DiscoveryV4Flag.Name) + cfg.DiscoveryV5 = ctx.Bool(DiscoveryV5Flag.Name) + + if netrestrict := ctx.String(NetrestrictFlag.Name); netrestrict != "" { + list, err := netutil.ParseNetlist(netrestrict) + if err != nil { + Fatalf("Option %q: %v", NetrestrictFlag.Name, err) + } + cfg.NetRestrict = list + } + + if ctx.Bool(DeveloperFlag.Name) { + // --dev mode can't use p2p networking. + cfg.MaxPeers = 0 + cfg.ListenAddr = "" + cfg.NoDial = true + cfg.NoDiscovery = true + cfg.DiscoveryV5 = false + } +} + +// SetNodeConfig applies node-related command line flags to the config. +func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { + SetP2PConfig(ctx, &cfg.P2P) + setIPC(ctx, cfg) + setHTTP(ctx, cfg) + setGraphQL(ctx, cfg) + setWS(ctx, cfg) + setNodeUserIdent(ctx, cfg) + SetDataDir(ctx, cfg) + setSmartCard(ctx, cfg) + + if ctx.IsSet(JWTSecretFlag.Name) { + cfg.JWTSecret = ctx.String(JWTSecretFlag.Name) + } + + if ctx.IsSet(EnablePersonal.Name) { + cfg.EnablePersonal = true + } + + if ctx.IsSet(ExternalSignerFlag.Name) { + cfg.ExternalSigner = ctx.String(ExternalSignerFlag.Name) + } + + if ctx.IsSet(KeyStoreDirFlag.Name) { + cfg.KeyStoreDir = ctx.String(KeyStoreDirFlag.Name) + } + if ctx.IsSet(DeveloperFlag.Name) { + cfg.UseLightweightKDF = true + } + if ctx.IsSet(LightKDFFlag.Name) { + cfg.UseLightweightKDF = ctx.Bool(LightKDFFlag.Name) + } + if ctx.IsSet(NoUSBFlag.Name) || cfg.NoUSB { + log.Warn("Option nousb is deprecated and USB is deactivated by default. Use --usb to enable") + } + if ctx.IsSet(USBFlag.Name) { + cfg.USB = ctx.Bool(USBFlag.Name) + } + if ctx.IsSet(InsecureUnlockAllowedFlag.Name) { + cfg.InsecureUnlockAllowed = ctx.Bool(InsecureUnlockAllowedFlag.Name) + } + if ctx.IsSet(DBEngineFlag.Name) { + dbEngine := ctx.String(DBEngineFlag.Name) + if dbEngine != "leveldb" && dbEngine != "pebble" { + Fatalf("Invalid choice for db.engine '%s', allowed 'leveldb' or 'pebble'", dbEngine) + } + 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) { + // Skip enabling smartcards if no path is set + path := ctx.String(SmartCardDaemonPathFlag.Name) + if path == "" { + return + } + // Sanity check that the smartcard path is valid + fi, err := os.Stat(path) + if err != nil { + log.Info("Smartcard socket not found, disabling", "err", err) + return + } + if fi.Mode()&os.ModeType != os.ModeSocket { + log.Error("Invalid smartcard daemon path", "path", path, "type", fi.Mode().String()) + return + } + // Smartcard daemon path exists and is a socket, enable it + cfg.SmartCardDaemonPath = path +} + +func SetDataDir(ctx *cli.Context, cfg *node.Config) { + switch { + case ctx.IsSet(DataDirFlag.Name): + cfg.DataDir = ctx.String(DataDirFlag.Name) + case ctx.Bool(DeveloperFlag.Name): + cfg.DataDir = "" // unless explicitly requested, use memory databases + case ctx.Bool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") + case ctx.Bool(SepoliaFlag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "sepolia") + case ctx.Bool(HoleskyFlag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "holesky") + case ctx.Bool(IliadFlag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "iliad") + case ctx.Bool(LocalFlag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "local") + } +} + +func setGPO(ctx *cli.Context, cfg *gasprice.Config) { + if ctx.IsSet(GpoBlocksFlag.Name) { + cfg.Blocks = ctx.Int(GpoBlocksFlag.Name) + } + if ctx.IsSet(GpoPercentileFlag.Name) { + cfg.Percentile = ctx.Int(GpoPercentileFlag.Name) + } + if ctx.IsSet(GpoMaxGasPriceFlag.Name) { + cfg.MaxPrice = big.NewInt(ctx.Int64(GpoMaxGasPriceFlag.Name)) + } + if ctx.IsSet(GpoIgnoreGasPriceFlag.Name) { + cfg.IgnorePrice = big.NewInt(ctx.Int64(GpoIgnoreGasPriceFlag.Name)) + } +} + +func setTxPool(ctx *cli.Context, cfg *legacypool.Config) { + if ctx.IsSet(TxPoolLocalsFlag.Name) { + locals := strings.Split(ctx.String(TxPoolLocalsFlag.Name), ",") + for _, account := range locals { + if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) { + Fatalf("Invalid account in --txpool.locals: %s", trimmed) + } else { + cfg.Locals = append(cfg.Locals, common.HexToAddress(account)) + } + } + } + if ctx.IsSet(TxPoolNoLocalsFlag.Name) { + cfg.NoLocals = ctx.Bool(TxPoolNoLocalsFlag.Name) + } + if ctx.IsSet(TxPoolJournalFlag.Name) { + cfg.Journal = ctx.String(TxPoolJournalFlag.Name) + } + if ctx.IsSet(TxPoolRejournalFlag.Name) { + cfg.Rejournal = ctx.Duration(TxPoolRejournalFlag.Name) + } + if ctx.IsSet(TxPoolPriceLimitFlag.Name) { + cfg.PriceLimit = ctx.Uint64(TxPoolPriceLimitFlag.Name) + } + if ctx.IsSet(TxPoolPriceBumpFlag.Name) { + cfg.PriceBump = ctx.Uint64(TxPoolPriceBumpFlag.Name) + } + if ctx.IsSet(TxPoolAccountSlotsFlag.Name) { + cfg.AccountSlots = ctx.Uint64(TxPoolAccountSlotsFlag.Name) + } + if ctx.IsSet(TxPoolGlobalSlotsFlag.Name) { + cfg.GlobalSlots = ctx.Uint64(TxPoolGlobalSlotsFlag.Name) + } + if ctx.IsSet(TxPoolAccountQueueFlag.Name) { + cfg.AccountQueue = ctx.Uint64(TxPoolAccountQueueFlag.Name) + } + if ctx.IsSet(TxPoolGlobalQueueFlag.Name) { + cfg.GlobalQueue = ctx.Uint64(TxPoolGlobalQueueFlag.Name) + } + if ctx.IsSet(TxPoolLifetimeFlag.Name) { + cfg.Lifetime = ctx.Duration(TxPoolLifetimeFlag.Name) + } +} + +func setMiner(ctx *cli.Context, cfg *miner.Config) { + if ctx.Bool(MiningEnabledFlag.Name) { + log.Warn("The flag --mine is deprecated and will be removed") + } + if ctx.IsSet(MinerExtraDataFlag.Name) { + cfg.ExtraData = []byte(ctx.String(MinerExtraDataFlag.Name)) + } + if ctx.IsSet(MinerGasLimitFlag.Name) { + cfg.GasCeil = ctx.Uint64(MinerGasLimitFlag.Name) + } + if ctx.IsSet(MinerGasPriceFlag.Name) { + cfg.GasPrice = flags.GlobalBig(ctx, MinerGasPriceFlag.Name) + } + if ctx.IsSet(MinerRecommitIntervalFlag.Name) { + cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name) + } + if ctx.IsSet(MinerNewPayloadTimeoutFlag.Name) { + log.Warn("The flag --miner.newpayload-timeout is deprecated and will be removed, please use --miner.recommit") + cfg.Recommit = ctx.Duration(MinerNewPayloadTimeoutFlag.Name) + } +} + +func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) { + requiredBlocks := ctx.String(EthRequiredBlocksFlag.Name) + if requiredBlocks == "" { + if ctx.IsSet(LegacyWhitelistFlag.Name) { + log.Warn("The flag --whitelist is deprecated and will be removed, please use --eth.requiredblocks") + requiredBlocks = ctx.String(LegacyWhitelistFlag.Name) + } else { + return + } + } + cfg.RequiredBlocks = make(map[uint64]common.Hash) + for _, entry := range strings.Split(requiredBlocks, ",") { + parts := strings.Split(entry, "=") + if len(parts) != 2 { + Fatalf("Invalid required block entry: %s", entry) + } + number, err := strconv.ParseUint(parts[0], 0, 64) + if err != nil { + Fatalf("Invalid required block number %s: %v", parts[0], err) + } + var hash common.Hash + if err = hash.UnmarshalText([]byte(parts[1])); err != nil { + Fatalf("Invalid required block hash %s: %v", parts[1], err) + } + cfg.RequiredBlocks[number] = hash + } +} + +// CheckExclusive verifies that only a single instance of the provided flags was +// set by the user. Each flag might optionally be followed by a string type to +// specialize it further. +func CheckExclusive(ctx *cli.Context, args ...interface{}) { + set := make([]string, 0, 1) + for i := 0; i < len(args); i++ { + // Make sure the next argument is a flag and skip if not set + flag, ok := args[i].(cli.Flag) + if !ok { + panic(fmt.Sprintf("invalid argument, not cli.Flag type: %T", args[i])) + } + // Check if next arg extends current and expand its name if so + name := flag.Names()[0] + + if i+1 < len(args) { + switch option := args[i+1].(type) { + case string: + // Extended flag check, make sure value set doesn't conflict with passed in option + if ctx.String(flag.Names()[0]) == option { + name += "=" + option + set = append(set, "--"+name) + } + // shift arguments and continue + i++ + continue + + case cli.Flag: + default: + panic(fmt.Sprintf("invalid argument, not cli.Flag or string extension: %T", args[i+1])) + } + } + // Mark the flag if it's set + if ctx.IsSet(flag.Names()[0]) { + set = append(set, "--"+name) + } + } + if len(set) > 1 { + Fatalf("Flags %v can't be used at the same time", strings.Join(set, ", ")) + } +} + +// 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, HoleskyFlag, IliadFlag, LocalFlag) + CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer + + // Set configurations from CLI flags + setEtherbase(ctx, cfg) + setGPO(ctx, &cfg.GPO) + setTxPool(ctx, &cfg.TxPool) + setMiner(ctx, &cfg.Miner) + setRequiredBlocks(ctx, cfg) + setLes(ctx, cfg) + + // Cap the cache allowance and tune the garbage collector + mem, err := gopsutil.VirtualMemory() + if err == nil { + if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 { + log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024) + mem.Total = 2 * 1024 * 1024 * 1024 + } + allowance := int(mem.Total / 1024 / 1024 / 3) + if cache := ctx.Int(CacheFlag.Name); cache > allowance { + log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance) + ctx.Set(CacheFlag.Name, strconv.Itoa(allowance)) + } + } + // Ensure Go's GC ignores the database cache for trigger percentage + cache := ctx.Int(CacheFlag.Name) + gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024))) + + log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) + godebug.SetGCPercent(int(gogc)) + + 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) { + cfg.NetworkId = ctx.Uint64(NetworkIdFlag.Name) + } + if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheDatabaseFlag.Name) { + cfg.DatabaseCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheDatabaseFlag.Name) / 100 + } + cfg.DatabaseHandles = MakeDatabaseHandles(ctx.Int(FDLimitFlag.Name)) + if ctx.IsSet(AncientFlag.Name) { + cfg.DatabaseFreezer = ctx.String(AncientFlag.Name) + } + + if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" { + Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) + } + if ctx.IsSet(GCModeFlag.Name) { + cfg.NoPruning = ctx.String(GCModeFlag.Name) == "archive" + } + if ctx.IsSet(CacheNoPrefetchFlag.Name) { + cfg.NoPrefetch = ctx.Bool(CacheNoPrefetchFlag.Name) + } + // Read the value from the flag no matter if it's set or not. + cfg.Preimages = ctx.Bool(CachePreimagesFlag.Name) + if cfg.NoPruning && !cfg.Preimages { + cfg.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") + } + 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") + + cfg.StateScheme = rawdb.HashScheme + log.Warn("Forcing hash state-scheme for archive mode") + } + if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheTrieFlag.Name) { + cfg.TrieCleanCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheTrieFlag.Name) / 100 + } + if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheGCFlag.Name) { + cfg.TrieDirtyCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100 + } + if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheSnapshotFlag.Name) { + cfg.SnapshotCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheSnapshotFlag.Name) / 100 + } + if ctx.IsSet(CacheLogSizeFlag.Name) { + cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name) + } + if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 { + // If snap-sync is requested, this flag is also required + if cfg.SyncMode == downloader.SnapSync { + 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 + } + } + if ctx.IsSet(DocRootFlag.Name) { + cfg.DocRoot = ctx.String(DocRootFlag.Name) + } + if ctx.IsSet(VMEnableDebugFlag.Name) { + // TODO(fjl): force-enable this in --dev mode + cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) + } + if ctx.IsSet(CollectWitnessFlag.Name) { + cfg.EnableWitnessCollection = ctx.Bool(CollectWitnessFlag.Name) + } + + if ctx.IsSet(RPCGlobalGasCapFlag.Name) { + cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name) + } + if cfg.RPCGasCap != 0 { + log.Info("Set global gas cap", "cap", cfg.RPCGasCap) + } else { + log.Info("Global gas cap disabled") + } + if ctx.IsSet(RPCGlobalEVMTimeoutFlag.Name) { + cfg.RPCEVMTimeout = ctx.Duration(RPCGlobalEVMTimeoutFlag.Name) + } + if ctx.IsSet(RPCGlobalTxFeeCapFlag.Name) { + cfg.RPCTxFeeCap = ctx.Float64(RPCGlobalTxFeeCapFlag.Name) + } + if ctx.IsSet(NoDiscoverFlag.Name) { + cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{} + } else if ctx.IsSet(DNSDiscoveryFlag.Name) { + urls := ctx.String(DNSDiscoveryFlag.Name) + if urls == "" { + cfg.EthDiscoveryURLs = []string{} + } else { + cfg.EthDiscoveryURLs = SplitAndTrim(urls) + } + } + // Override any default configs for hard coded networks. + switch { + case ctx.Bool(MainnetFlag.Name): + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 1 + } + cfg.Genesis = core.DefaultGenesisBlock() + SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) + case ctx.Bool(HoleskyFlag.Name): + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 17000 + } + cfg.Genesis = core.DefaultHoleskyGenesisBlock() + SetDNSDiscoveryDefaults(cfg, params.HoleskyGenesisHash) + case ctx.Bool(SepoliaFlag.Name): + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 11155111 + } + cfg.Genesis = core.DefaultSepoliaGenesisBlock() + SetDNSDiscoveryDefaults(cfg, params.SepoliaGenesisHash) + case ctx.Bool(GoerliFlag.Name): + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 5 + } + cfg.Genesis = core.DefaultGoerliGenesisBlock() + SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) + case ctx.Bool(IliadFlag.Name): + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 1 + } + cfg.Genesis = core.DefaultIliadGenesisBlock() + SetDNSDiscoveryDefaults(cfg, params.IliadGenesisHash) + case ctx.Bool(LocalFlag.Name): + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 1511 + } + cfg.Genesis = core.DefaultLocalGenesisBlock() + case ctx.Bool(DeveloperFlag.Name): + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 1337 + } + cfg.SyncMode = downloader.FullSync + // Create new developer account or reuse existing one + var ( + developer accounts.Account + passphrase string + err error + ) + if list := MakePasswordList(ctx); len(list) > 0 { + // Just take the first value. Although the function returns a possible multiple values and + // some usages iterate through them as attempts, that doesn't make sense in this setting, + // when we're definitely concerned with only one account. + passphrase = list[0] + } + + // Unlock the developer account by local keystore. + var ks *keystore.KeyStore + if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 { + ks = keystores[0].(*keystore.KeyStore) + } + if ks == nil { + Fatalf("Keystore is not available") + } + + // Figure out the dev account address. + // setEtherbase has been called above, configuring the miner address from command line flags. + if cfg.Miner.PendingFeeRecipient != (common.Address{}) { + developer = accounts.Account{Address: cfg.Miner.PendingFeeRecipient} + } else if accs := ks.Accounts(); len(accs) > 0 { + developer = ks.Accounts()[0] + } else { + developer, err = ks.NewAccount(passphrase) + if err != nil { + Fatalf("Failed to create developer account: %v", err) + } + } + // Make sure the address is configured as fee recipient, otherwise + // the miner will fail to start. + cfg.Miner.PendingFeeRecipient = developer.Address + + if err := ks.Unlock(developer, passphrase); err != nil { + Fatalf("Failed to unlock developer account: %v", err) + } + 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) + if ctx.IsSet(DataDirFlag.Name) { + 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") + } + if genesis.Config.TerminalTotalDifficulty == nil { + Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified") + } else if genesis.Config.TerminalTotalDifficulty.Cmp(big.NewInt(0)) != 0 { + Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be 0") + } + if genesis.Difficulty.Cmp(big.NewInt(0)) != 0 { + Fatalf("Bad developer-mode genesis configuration: difficulty must be 0") + } + } + chaindb.Close() + } + if !ctx.IsSet(MinerGasPriceFlag.Name) { + cfg.Miner.GasPrice = big.NewInt(1) + } + default: + if cfg.NetworkId == 1 { + SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) + } + } + // Set any dangling config values + if ctx.String(CryptoKZGFlag.Name) != "gokzg" && ctx.String(CryptoKZGFlag.Name) != "ckzg" { + Fatalf("--%s flag must be 'gokzg' or 'ckzg'", CryptoKZGFlag.Name) + } + log.Info("Initializing the KZG library", "backend", ctx.String(CryptoKZGFlag.Name)) + if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil { + Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err) + } + // VM tracing config. + if ctx.IsSet(VMTraceFlag.Name) { + if name := ctx.String(VMTraceFlag.Name); name != "" { + var config string + if ctx.IsSet(VMTraceJsonConfigFlag.Name) { + config = ctx.String(VMTraceJsonConfigFlag.Name) + } + + cfg.VMTrace = name + cfg.VMTraceJsonConfig = config + } + } +} + +// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if +// no URLs are set. +func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { + if cfg.EthDiscoveryURLs != nil { + return // already set through flags/config + } + protocol := "all" + if url := params.KnownDNSNetwork(genesis, protocol); url != "" { + cfg.EthDiscoveryURLs = []string{url} + cfg.SnapDiscoveryURLs = cfg.EthDiscoveryURLs + } +} + +// RegisterEthService adds an Ethereum client to the stack. +// The second return value is the full node instance. +func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) { + backend, err := eth.New(stack, cfg) + if err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) + return backend.APIBackend, backend +} + +// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to the node. +func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) { + if err := ethstats.New(stack, backend, backend.Engine(), url); err != nil { + Fatalf("Failed to register the Ethereum Stats service: %v", err) + } +} + +// RegisterGraphQLService adds the GraphQL API to the node. +func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSystem *filters.FilterSystem, cfg *node.Config) { + err := graphql.New(stack, backend, filterSystem, cfg.GraphQLCors, cfg.GraphQLVirtualHosts) + if err != nil { + Fatalf("Failed to register the GraphQL service: %v", err) + } +} + +// RegisterFilterAPI adds the eth log filtering RPC API to the node. +func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem { + filterSystem := filters.NewFilterSystem(backend, filters.Config{ + LogCacheSize: ethcfg.FilterLogCacheSize, + }) + stack.RegisterAPIs([]rpc.API{{ + Namespace: "eth", + Service: filters.NewFilterAPI(filterSystem), + }}) + return filterSystem +} + +// RegisterFullSyncTester adds the full-sync tester service into node. +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) { + if metrics.Enabled { + log.Info("Enabling metrics collection") + + var ( + enableExport = ctx.Bool(MetricsEnableInfluxDBFlag.Name) + enableExportV2 = ctx.Bool(MetricsEnableInfluxDBV2Flag.Name) + ) + + if enableExport || enableExportV2 { + CheckExclusive(ctx, MetricsEnableInfluxDBFlag, MetricsEnableInfluxDBV2Flag) + + v1FlagIsSet := ctx.IsSet(MetricsInfluxDBUsernameFlag.Name) || + ctx.IsSet(MetricsInfluxDBPasswordFlag.Name) + + v2FlagIsSet := ctx.IsSet(MetricsInfluxDBTokenFlag.Name) || + ctx.IsSet(MetricsInfluxDBOrganizationFlag.Name) || + ctx.IsSet(MetricsInfluxDBBucketFlag.Name) + + if enableExport && v2FlagIsSet { + Fatalf("Flags --influxdb.metrics.organization, --influxdb.metrics.token, --influxdb.metrics.bucket are only available for influxdb-v2") + } else if enableExportV2 && v1FlagIsSet { + Fatalf("Flags --influxdb.metrics.username, --influxdb.metrics.password are only available for influxdb-v1") + } + } + + var ( + endpoint = ctx.String(MetricsInfluxDBEndpointFlag.Name) + database = ctx.String(MetricsInfluxDBDatabaseFlag.Name) + username = ctx.String(MetricsInfluxDBUsernameFlag.Name) + password = ctx.String(MetricsInfluxDBPasswordFlag.Name) + + token = ctx.String(MetricsInfluxDBTokenFlag.Name) + bucket = ctx.String(MetricsInfluxDBBucketFlag.Name) + organization = ctx.String(MetricsInfluxDBOrganizationFlag.Name) + ) + + if enableExport { + tagsMap := SplitTagsFlag(ctx.String(MetricsInfluxDBTagsFlag.Name)) + + log.Info("Enabling metrics export to InfluxDB") + + go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap) + } else if enableExportV2 { + tagsMap := SplitTagsFlag(ctx.String(MetricsInfluxDBTagsFlag.Name)) + + log.Info("Enabling metrics export to InfluxDB (v2)") + + go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, token, bucket, organization, "geth.", tagsMap) + } + + if ctx.IsSet(MetricsHTTPFlag.Name) { + address := net.JoinHostPort(ctx.String(MetricsHTTPFlag.Name), fmt.Sprintf("%d", ctx.Int(MetricsPortFlag.Name))) + log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address) + exp.Setup(address) + } else if ctx.IsSet(MetricsPortFlag.Name) { + log.Warn(fmt.Sprintf("--%s specified without --%s, metrics server will not start.", MetricsPortFlag.Name, MetricsHTTPFlag.Name)) + } + } +} + +func SplitTagsFlag(tagsFlag string) map[string]string { + tags := strings.Split(tagsFlag, ",") + tagsMap := map[string]string{} + + for _, t := range tags { + if t != "" { + kv := strings.Split(t, "=") + + if len(kv) == 2 { + tagsMap[kv[0]] = kv[1] + } + } + } + + return tagsMap +} + +// 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 + ) + switch { + case ctx.IsSet(RemoteDBFlag.Name): + log.Info("Using remote db", "url", ctx.String(RemoteDBFlag.Name), "headers", len(ctx.StringSlice(HttpHeaderFlag.Name))) + client, err := DialRPCWithHeaders(ctx.String(RemoteDBFlag.Name), ctx.StringSlice(HttpHeaderFlag.Name)) + if err != nil { + break + } + chainDb = remotedb.New(client) + case ctx.String(SyncModeFlag.Name) == "light": + chainDb, err = stack.OpenDatabase("lightchaindata", cache, handles, "", readonly) + default: + chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.String(AncientFlag.Name), "", readonly) + } + if err != nil { + Fatalf("Could not open database: %v", err) + } + 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) + if ctx.IsSet(bFlag.Name) { + return true + } + } + return false +} + +func DialRPCWithHeaders(endpoint string, headers []string) (*rpc.Client, error) { + if endpoint == "" { + return nil, errors.New("endpoint must be specified") + } + if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") { + // Backwards compatibility with geth < 1.5 which required + // these prefixes. + endpoint = endpoint[4:] + } + var opts []rpc.ClientOption + if len(headers) > 0 { + customHeaders := make(http.Header) + for _, h := range headers { + kv := strings.Split(h, ":") + if len(kv) != 2 { + return nil, fmt.Errorf("invalid http header directive: %q", h) + } + customHeaders.Add(kv[0], kv[1]) + } + opts = append(opts, rpc.WithHeaders(customHeaders)) + } + return rpc.DialOptions(context.Background(), endpoint, opts...) +} + +func MakeGenesis(ctx *cli.Context) *core.Genesis { + var genesis *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): + genesis = core.DefaultGoerliGenesisBlock() + case ctx.Bool(IliadFlag.Name): + genesis = core.DefaultIliadGenesisBlock() + case ctx.Bool(LocalFlag.Name): + genesis = core.DefaultLocalGenesisBlock() + case ctx.Bool(DeveloperFlag.Name): + Fatalf("Developer chains are ephemeral") + } + return genesis +} + +// MakeChain creates a chain manager from set command line flags. +func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockChain, ethdb.Database) { + var ( + gspec = MakeGenesis(ctx) + chainDb = MakeChainDatabase(ctx, stack, readonly) + ) + config, err := core.LoadChainConfig(chainDb, gspec) + if err != nil { + Fatalf("%v", err) + } + engine, err := ethconfig.CreateConsensusEngine(config, chainDb) + if err != nil { + Fatalf("%v", err) + } + 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), + TrieDirtyLimit: ethconfig.Defaults.TrieDirtyCache, + TrieDirtyDisabled: ctx.String(GCModeFlag.Name) == "archive", + 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 + log.Info("Enabling recording of key preimages since archive mode is used") + } + if !ctx.Bool(SnapshotFlag.Name) { + cache.SnapshotLimit = 0 // Disabled + } + // If we're in readonly, do not bother generating snapshot data. + if readonly { + cache.SnapshotNoBuild = true + } + + if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheTrieFlag.Name) { + cache.TrieCleanLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheTrieFlag.Name) / 100 + } + if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheGCFlag.Name) { + cache.TrieDirtyLimit = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100 + } + vmcfg := vm.Config{ + EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name), + EnableWitnessCollection: ctx.Bool(CollectWitnessFlag.Name), + } + if ctx.IsSet(VMTraceFlag.Name) { + if name := ctx.String(VMTraceFlag.Name); name != "" { + var config json.RawMessage + if ctx.IsSet(VMTraceJsonConfigFlag.Name) { + config = json.RawMessage(ctx.String(VMTraceJsonConfigFlag.Name)) + } + t, err := tracers.LiveDirectory.New(name, config) + if err != nil { + Fatalf("Failed to create tracer %q: %v", name, err) + } + vmcfg.Tracer = t + } + } + // Disable transaction indexing/unindexing by default. + chain, err := core.NewBlockChain(chainDb, cache, gspec, nil, engine, vmcfg, nil, nil) + if err != nil { + Fatalf("Can't create BlockChain: %v", err) + } + + return chain, chainDb +} + +// MakeConsolePreloads retrieves the absolute paths for the console JavaScript +// scripts to preload before starting. +func MakeConsolePreloads(ctx *cli.Context) []string { + // Skip preloading if there's nothing to preload + if ctx.String(PreloadJSFlag.Name) == "" { + return nil + } + // Otherwise resolve absolute paths and return them + var preloads []string + + for _, file := range strings.Split(ctx.String(PreloadJSFlag.Name), ",") { + preloads = append(preloads, strings.TrimSpace(file)) + } + return preloads +} + +// MakeTrieDatabase constructs a trie database based on the configured scheme. +func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *triedb.Database { + config := &triedb.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 triedb.NewDatabase(disk, config) + } + if readOnly { + config.PathDB = pathdb.ReadOnly + } else { + config.PathDB = pathdb.Defaults + } + return triedb.NewDatabase(disk, config) +} diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go new file mode 100644 index 0000000..1dfd1a5 --- /dev/null +++ b/cmd/utils/flags_legacy.go @@ -0,0 +1,173 @@ +// 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 utils + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/urfave/cli/v2" +) + +var ShowDeprecated = &cli.Command{ + Action: showDeprecated, + Name: "show-deprecated-flags", + Usage: "Show flags that have been deprecated", + ArgsUsage: " ", + Description: "Show flags that have been deprecated and will soon be removed", +} + +var DeprecatedFlags = []cli.Flag{ + NoUSBFlag, + LegacyWhitelistFlag, + CacheTrieJournalFlag, + CacheTrieRejournalFlag, + LegacyDiscoveryV5Flag, + TxLookupLimitFlag, + LightServeFlag, + LightIngressFlag, + LightEgressFlag, + LightMaxPeersFlag, + LightNoPruneFlag, + LightNoSyncServeFlag, + LogBacktraceAtFlag, + LogDebugFlag, + MinerNewPayloadTimeoutFlag, + MinerEtherbaseFlag, + MiningEnabledFlag, +} + +var ( + // Deprecated May 2020, shown in aliased flags section + NoUSBFlag = &cli.BoolFlag{ + Name: "nousb", + Usage: "Disables monitoring for and managing USB hardware wallets (deprecated)", + Category: flags.DeprecatedCategory, + } + // Deprecated March 2022 + LegacyWhitelistFlag = &cli.StringFlag{ + Name: "whitelist", + Usage: "Comma separated block number-to-hash mappings to enforce (=) (deprecated in favor of --eth.requiredblocks)", + Category: flags.DeprecatedCategory, + } + // Deprecated July 2023 + CacheTrieJournalFlag = &cli.StringFlag{ + Name: "cache.trie.journal", + Usage: "Disk journal directory for trie cache to survive node restarts", + Category: flags.DeprecatedCategory, + } + CacheTrieRejournalFlag = &cli.DurationFlag{ + Name: "cache.trie.rejournal", + Usage: "Time interval to regenerate the trie cache journal", + Category: flags.DeprecatedCategory, + } + LegacyDiscoveryV5Flag = &cli.BoolFlag{ + Name: "v5disc", + Usage: "Enables the experimental RLPx V5 (Topic Discovery) mechanism (deprecated, use --discv5 instead)", + Category: flags.DeprecatedCategory, + } + // Deprecated August 2023 + TxLookupLimitFlag = &cli.Uint64Flag{ + Name: "txlookuplimit", + Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain) (deprecated, use history.transactions instead)", + Value: ethconfig.Defaults.TransactionHistory, + Category: flags.DeprecatedCategory, + } + // 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.DeprecatedCategory, + } + LightIngressFlag = &cli.IntFlag{ + Name: "light.ingress", + Usage: "Incoming bandwidth limit for serving light clients (deprecated)", + Value: ethconfig.Defaults.LightIngress, + Category: flags.DeprecatedCategory, + } + LightEgressFlag = &cli.IntFlag{ + Name: "light.egress", + Usage: "Outgoing bandwidth limit for serving light clients (deprecated)", + Value: ethconfig.Defaults.LightEgress, + Category: flags.DeprecatedCategory, + } + 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.DeprecatedCategory, + } + LightNoPruneFlag = &cli.BoolFlag{ + Name: "light.nopruning", + Usage: "Disable ancient light chain data pruning (deprecated)", + Category: flags.DeprecatedCategory, + } + LightNoSyncServeFlag = &cli.BoolFlag{ + Name: "light.nosyncserve", + Usage: "Enables serving light clients before syncing (deprecated)", + Category: flags.DeprecatedCategory, + } + // 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, + } + // Deprecated February 2024 + MinerNewPayloadTimeoutFlag = &cli.DurationFlag{ + Name: "miner.newpayload-timeout", + Usage: "Specify the maximum time allowance for creating a new payload (deprecated)", + Value: ethconfig.Defaults.Miner.Recommit, + Category: flags.DeprecatedCategory, + } + MinerEtherbaseFlag = &cli.StringFlag{ + Name: "miner.etherbase", + Usage: "0x prefixed public address for block mining rewards (deprecated)", + Category: flags.DeprecatedCategory, + } + MiningEnabledFlag = &cli.BoolFlag{ + Name: "mine", + Usage: "Enable mining (deprecated)", + Category: flags.DeprecatedCategory, + } + MetricsEnabledExpensiveFlag = &cli.BoolFlag{ + Name: "metrics.expensive", + Usage: "Enable expensive metrics collection and reporting (deprecated)", + Category: flags.DeprecatedCategory, + } +) + +// showDeprecated displays deprecated flags that will be soon removed from the codebase. +func showDeprecated(*cli.Context) error { + fmt.Println("--------------------------------------------------------------------") + fmt.Println("The following flags are deprecated and will be removed in the future!") + fmt.Println("--------------------------------------------------------------------") + fmt.Println() + for _, flag := range DeprecatedFlags { + fmt.Println(flag.String()) + } + fmt.Println() + return nil +} diff --git a/cmd/utils/flags_test.go b/cmd/utils/flags_test.go new file mode 100644 index 0000000..00c73a5 --- /dev/null +++ b/cmd/utils/flags_test.go @@ -0,0 +1,67 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +// Package utils contains internal helper functions for go-ethereum commands. +package utils + +import ( + "reflect" + "testing" +) + +func Test_SplitTagsFlag(t *testing.T) { + t.Parallel() + tests := []struct { + name string + args string + want map[string]string + }{ + { + "2 tags case", + "host=localhost,bzzkey=123", + map[string]string{ + "host": "localhost", + "bzzkey": "123", + }, + }, + { + "1 tag case", + "host=localhost123", + map[string]string{ + "host": "localhost123", + }, + }, + { + "empty case", + "", + map[string]string{}, + }, + { + "garbage", + "smth=smthelse=123", + map[string]string{}, + }, + } + 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/history_test.go b/cmd/utils/history_test.go new file mode 100644 index 0000000..a631eaf --- /dev/null +++ b/cmd/utils/history_test.go @@ -0,0 +1,184 @@ +// 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 utils + +import ( + "bytes" + "crypto/sha256" + "io" + "math/big" + "os" + "path/filepath" + "strings" + "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/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +var ( + count uint64 = 128 + step uint64 = 16 +) + +func TestHistoryImportAndExport(t *testing.T) { + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + genesis = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{address: {Balance: big.NewInt(1000000000000000000)}}, + } + signer = types.LatestSigner(genesis.Config) + ) + + // Generate chain. + db, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), int(count), func(i int, g *core.BlockGen) { + if i == 0 { + return + } + tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{ + ChainID: genesis.Config.ChainID, + Nonce: uint64(i - 1), + GasTipCap: common.Big0, + GasFeeCap: g.PrevBlock(0).BaseFee(), + Gas: 50000, + To: &common.Address{0xaa}, + Value: big.NewInt(int64(i)), + Data: nil, + AccessList: nil, + }) + if err != nil { + t.Fatalf("error creating tx: %v", err) + } + g.AddTx(tx) + }) + + // Initialize BlockChain. + chain, err := core.NewBlockChain(db, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("unable to initialize chain: %v", err) + } + if _, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("error inserting chain: %v", err) + } + + // Make temp directory for era files. + dir, err := os.MkdirTemp("", "history-export-test") + if err != nil { + t.Fatalf("error creating temp test directory: %v", err) + } + defer os.RemoveAll(dir) + + // Export history to temp directory. + if err := ExportHistory(chain, dir, 0, count, step); err != nil { + t.Fatalf("error exporting history: %v", err) + } + + // Read checksums. + b, err := os.ReadFile(filepath.Join(dir, "checksums.txt")) + if err != nil { + t.Fatalf("failed to read checksums: %v", err) + } + checksums := strings.Split(string(b), "\n") + + // Verify each Era. + entries, _ := era.ReadDir(dir, "mainnet") + for i, filename := range entries { + func() { + f, err := os.Open(filepath.Join(dir, filename)) + if err != nil { + t.Fatalf("error opening era file: %v", err) + } + var ( + h = sha256.New() + buf = bytes.NewBuffer(nil) + ) + if _, err := io.Copy(h, f); err != nil { + t.Fatalf("unable to recalculate checksum: %v", err) + } + if got, want := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex(), checksums[i]; got != want { + t.Fatalf("checksum %d does not match: got %s, want %s", i, got, want) + } + e, err := era.From(f) + if err != nil { + t.Fatalf("error opening era: %v", err) + } + defer e.Close() + it, err := era.NewIterator(e) + if err != nil { + t.Fatalf("error making era reader: %v", err) + } + for j := 0; it.Next(); j++ { + n := i*int(step) + j + if it.Error() != nil { + t.Fatalf("error reading block entry %d: %v", n, it.Error()) + } + block, receipts, err := it.BlockAndReceipts() + if err != nil { + t.Fatalf("error reading block entry %d: %v", n, err) + } + want := chain.GetBlockByNumber(uint64(n)) + if want, got := uint64(n), block.NumberU64(); want != got { + t.Fatalf("blocks out of order: want %d, got %d", want, got) + } + if want.Hash() != block.Hash() { + t.Fatalf("block hash mismatch %d: want %s, got %s", n, want.Hash().Hex(), block.Hash().Hex()) + } + if got := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); got != want.TxHash() { + t.Fatalf("tx hash %d mismatch: want %s, got %s", n, want.TxHash(), got) + } + if got := types.CalcUncleHash(block.Uncles()); got != want.UncleHash() { + t.Fatalf("uncle hash %d mismatch: want %s, got %s", n, want.UncleHash(), got) + } + if got := types.DeriveSha(receipts, trie.NewStackTrie(nil)); got != want.ReceiptHash() { + t.Fatalf("receipt root %d mismatch: want %s, got %s", n, want.ReceiptHash(), got) + } + } + }() + } + + // Now import Era. + db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) + if err != nil { + panic(err) + } + t.Cleanup(func() { + db2.Close() + }) + + genesis.MustCommit(db2, triedb.NewDatabase(db, triedb.HashDefaults)) + imported, err := core.NewBlockChain(db2, nil, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("unable to initialize chain: %v", err) + } + if err := ImportHistory(imported, db2, dir, "mainnet"); err != nil { + t.Fatalf("failed to import chain: %v", err) + } + if have, want := imported.CurrentHeader(), chain.CurrentHeader(); have.Hash() != want.Hash() { + t.Fatalf("imported chain does not match expected, have (%d, %s) want (%d, %s)", have.Number, have.Hash(), want.Number, want.Hash()) + } +} diff --git a/cmd/utils/prompt.go b/cmd/utils/prompt.go new file mode 100644 index 0000000..f513e38 --- /dev/null +++ b/cmd/utils/prompt.go @@ -0,0 +1,62 @@ +// 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 utils contains internal helper functions for go-ethereum commands. +package utils + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/console/prompt" +) + +// GetPassPhrase displays the given text(prompt) to the user and requests some textual +// data to be entered, but one which must not be echoed out into the terminal. +// The method returns the input provided by the user. +func GetPassPhrase(text string, confirmation bool) string { + if text != "" { + fmt.Println(text) + } + password, err := prompt.Stdin.PromptPassword("Password: ") + if err != nil { + Fatalf("Failed to read password: %v", err) + } + if confirmation { + confirm, err := prompt.Stdin.PromptPassword("Repeat password: ") + if err != nil { + Fatalf("Failed to read password confirmation: %v", err) + } + if password != confirm { + Fatalf("Passwords do not match") + } + } + return password +} + +// GetPassPhraseWithList retrieves the password associated with an account, either fetched +// from a list of preloaded passphrases, or requested interactively from the user. +func GetPassPhraseWithList(text string, confirmation bool, index int, passwords []string) string { + // If a list of passwords was supplied, retrieve from them + if len(passwords) > 0 { + if index < len(passwords) { + return passwords[index] + } + return passwords[len(passwords)-1] + } + // Otherwise prompt the user for the password + password := GetPassPhrase(text, confirmation) + return password +} diff --git a/cmd/utils/prompt_test.go b/cmd/utils/prompt_test.go new file mode 100644 index 0000000..889bf71 --- /dev/null +++ b/cmd/utils/prompt_test.go @@ -0,0 +1,77 @@ +// 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 utils contains internal helper functions for go-ethereum commands. +package utils + +import ( + "testing" +) + +func TestGetPassPhraseWithList(t *testing.T) { + t.Parallel() + type args struct { + text string + confirmation bool + index int + passwords []string + } + tests := []struct { + name string + args args + want string + }{ + { + "test1", + args{ + "text1", + false, + 0, + []string{"zero", "one", "two"}, + }, + "zero", + }, + { + "test2", + args{ + "text2", + false, + 5, + []string{"zero", "one", "two"}, + }, + "two", + }, + { + "test3", + args{ + "text3", + true, + 1, + []string{"zero", "one", "two"}, + }, + "one", + }, + } + 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/big.go b/common/big.go new file mode 100644 index 0000000..cbb562a --- /dev/null +++ b/common/big.go @@ -0,0 +1,36 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package common + +import ( + "math/big" + + "github.com/holiman/uint256" +) + +// Common big integers often used +var ( + Big1 = big.NewInt(1) + Big2 = big.NewInt(2) + Big3 = big.NewInt(3) + Big0 = big.NewInt(0) + Big32 = big.NewInt(32) + Big256 = big.NewInt(256) + Big257 = big.NewInt(257) + + U2560 = uint256.NewInt(0) +) diff --git a/common/bitutil/bitutil.go b/common/bitutil/bitutil.go new file mode 100644 index 0000000..a18a6d1 --- /dev/null +++ b/common/bitutil/bitutil.go @@ -0,0 +1,188 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Adapted from: https://go.dev/src/crypto/subtle/xor_generic.go + +// Package bitutil implements fast bitwise operations. +package bitutil + +import ( + "runtime" + "unsafe" +) + +const wordSize = int(unsafe.Sizeof(uintptr(0))) +const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x" + +// XORBytes xors the bytes in a and b. The destination is assumed to have enough +// space. Returns the number of bytes xor'd. +func XORBytes(dst, a, b []byte) int { + if supportsUnaligned { + return fastXORBytes(dst, a, b) + } + return safeXORBytes(dst, a, b) +} + +// fastXORBytes xors in bulk. It only works on architectures that support +// unaligned read/writes. +func fastXORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + w := n / wordSize + if w > 0 { + dw := *(*[]uintptr)(unsafe.Pointer(&dst)) + aw := *(*[]uintptr)(unsafe.Pointer(&a)) + bw := *(*[]uintptr)(unsafe.Pointer(&b)) + for i := 0; i < w; i++ { + dw[i] = aw[i] ^ bw[i] + } + } + for i := n - n%wordSize; i < n; i++ { + dst[i] = a[i] ^ b[i] + } + return n +} + +// safeXORBytes xors one by one. It works on all architectures, independent if +// it supports unaligned read/writes or not. +func safeXORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + for i := 0; i < n; i++ { + dst[i] = a[i] ^ b[i] + } + return n +} + +// ANDBytes ands the bytes in a and b. The destination is assumed to have enough +// space. Returns the number of bytes and'd. +func ANDBytes(dst, a, b []byte) int { + if supportsUnaligned { + return fastANDBytes(dst, a, b) + } + return safeANDBytes(dst, a, b) +} + +// fastANDBytes ands in bulk. It only works on architectures that support +// unaligned read/writes. +func fastANDBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + w := n / wordSize + if w > 0 { + dw := *(*[]uintptr)(unsafe.Pointer(&dst)) + aw := *(*[]uintptr)(unsafe.Pointer(&a)) + bw := *(*[]uintptr)(unsafe.Pointer(&b)) + for i := 0; i < w; i++ { + dw[i] = aw[i] & bw[i] + } + } + for i := n - n%wordSize; i < n; i++ { + dst[i] = a[i] & b[i] + } + return n +} + +// safeANDBytes ands one by one. It works on all architectures, independent if +// it supports unaligned read/writes or not. +func safeANDBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + for i := 0; i < n; i++ { + dst[i] = a[i] & b[i] + } + return n +} + +// ORBytes ors the bytes in a and b. The destination is assumed to have enough +// space. Returns the number of bytes or'd. +func ORBytes(dst, a, b []byte) int { + if supportsUnaligned { + return fastORBytes(dst, a, b) + } + return safeORBytes(dst, a, b) +} + +// fastORBytes ors in bulk. It only works on architectures that support +// unaligned read/writes. +func fastORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + w := n / wordSize + if w > 0 { + dw := *(*[]uintptr)(unsafe.Pointer(&dst)) + aw := *(*[]uintptr)(unsafe.Pointer(&a)) + bw := *(*[]uintptr)(unsafe.Pointer(&b)) + for i := 0; i < w; i++ { + dw[i] = aw[i] | bw[i] + } + } + for i := n - n%wordSize; i < n; i++ { + dst[i] = a[i] | b[i] + } + return n +} + +// safeORBytes ors one by one. It works on all architectures, independent if +// it supports unaligned read/writes or not. +func safeORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + for i := 0; i < n; i++ { + dst[i] = a[i] | b[i] + } + return n +} + +// TestBytes tests whether any bit is set in the input byte slice. +func TestBytes(p []byte) bool { + if supportsUnaligned { + return fastTestBytes(p) + } + return safeTestBytes(p) +} + +// fastTestBytes tests for set bits in bulk. It only works on architectures that +// support unaligned read/writes. +func fastTestBytes(p []byte) bool { + n := len(p) + w := n / wordSize + if w > 0 { + pw := *(*[]uintptr)(unsafe.Pointer(&p)) + for i := 0; i < w; i++ { + if pw[i] != 0 { + return true + } + } + } + for i := n - n%wordSize; i < n; i++ { + if p[i] != 0 { + return true + } + } + return false +} + +// safeTestBytes tests for set bits one byte at a time. It works on all +// architectures, independent if it supports unaligned read/writes or not. +func safeTestBytes(p []byte) bool { + for i := 0; i < len(p); i++ { + if p[i] != 0 { + return true + } + } + return false +} diff --git a/common/bitutil/bitutil_test.go b/common/bitutil/bitutil_test.go new file mode 100644 index 0000000..12f3fe2 --- /dev/null +++ b/common/bitutil/bitutil_test.go @@ -0,0 +1,221 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Adapted from: https://go.dev/src/crypto/subtle/xor_test.go + +package bitutil + +import ( + "bytes" + "testing" +) + +// Tests that bitwise XOR works for various alignments. +func TestXOR(t *testing.T) { + for alignP := 0; alignP < 2; alignP++ { + for alignQ := 0; alignQ < 2; alignQ++ { + for alignD := 0; alignD < 2; alignD++ { + p := make([]byte, 1023)[alignP:] + q := make([]byte, 1023)[alignQ:] + + for i := 0; i < len(p); i++ { + p[i] = byte(i) + } + for i := 0; i < len(q); i++ { + q[i] = byte(len(q) - i) + } + d1 := make([]byte, 1023+alignD)[alignD:] + d2 := make([]byte, 1023+alignD)[alignD:] + + XORBytes(d1, p, q) + safeXORBytes(d2, p, q) + if !bytes.Equal(d1, d2) { + t.Error("not equal", d1, d2) + } + } + } + } +} + +// Tests that bitwise AND works for various alignments. +func TestAND(t *testing.T) { + for alignP := 0; alignP < 2; alignP++ { + for alignQ := 0; alignQ < 2; alignQ++ { + for alignD := 0; alignD < 2; alignD++ { + p := make([]byte, 1023)[alignP:] + q := make([]byte, 1023)[alignQ:] + + for i := 0; i < len(p); i++ { + p[i] = byte(i) + } + for i := 0; i < len(q); i++ { + q[i] = byte(len(q) - i) + } + d1 := make([]byte, 1023+alignD)[alignD:] + d2 := make([]byte, 1023+alignD)[alignD:] + + ANDBytes(d1, p, q) + safeANDBytes(d2, p, q) + if !bytes.Equal(d1, d2) { + t.Error("not equal") + } + } + } + } +} + +// Tests that bitwise OR works for various alignments. +func TestOR(t *testing.T) { + for alignP := 0; alignP < 2; alignP++ { + for alignQ := 0; alignQ < 2; alignQ++ { + for alignD := 0; alignD < 2; alignD++ { + p := make([]byte, 1023)[alignP:] + q := make([]byte, 1023)[alignQ:] + + for i := 0; i < len(p); i++ { + p[i] = byte(i) + } + for i := 0; i < len(q); i++ { + q[i] = byte(len(q) - i) + } + d1 := make([]byte, 1023+alignD)[alignD:] + d2 := make([]byte, 1023+alignD)[alignD:] + + ORBytes(d1, p, q) + safeORBytes(d2, p, q) + if !bytes.Equal(d1, d2) { + t.Error("not equal") + } + } + } + } +} + +// Tests that bit testing works for various alignments. +func TestTest(t *testing.T) { + for align := 0; align < 2; align++ { + // Test for bits set in the bulk part + p := make([]byte, 1023)[align:] + p[100] = 1 + + if TestBytes(p) != safeTestBytes(p) { + t.Error("not equal") + } + // Test for bits set in the tail part + q := make([]byte, 1023)[align:] + q[len(q)-1] = 1 + + if TestBytes(q) != safeTestBytes(q) { + t.Error("not equal") + } + } +} + +// Benchmarks the potentially optimized XOR performance. +func BenchmarkFastXOR1KB(b *testing.B) { benchmarkFastXOR(b, 1024) } +func BenchmarkFastXOR2KB(b *testing.B) { benchmarkFastXOR(b, 2048) } +func BenchmarkFastXOR4KB(b *testing.B) { benchmarkFastXOR(b, 4096) } + +func benchmarkFastXOR(b *testing.B, size int) { + p, q := make([]byte, size), make([]byte, size) + + for i := 0; i < b.N; i++ { + XORBytes(p, p, q) + } +} + +// Benchmarks the baseline XOR performance. +func BenchmarkBaseXOR1KB(b *testing.B) { benchmarkBaseXOR(b, 1024) } +func BenchmarkBaseXOR2KB(b *testing.B) { benchmarkBaseXOR(b, 2048) } +func BenchmarkBaseXOR4KB(b *testing.B) { benchmarkBaseXOR(b, 4096) } + +func benchmarkBaseXOR(b *testing.B, size int) { + p, q := make([]byte, size), make([]byte, size) + + for i := 0; i < b.N; i++ { + safeXORBytes(p, p, q) + } +} + +// Benchmarks the potentially optimized AND performance. +func BenchmarkFastAND1KB(b *testing.B) { benchmarkFastAND(b, 1024) } +func BenchmarkFastAND2KB(b *testing.B) { benchmarkFastAND(b, 2048) } +func BenchmarkFastAND4KB(b *testing.B) { benchmarkFastAND(b, 4096) } + +func benchmarkFastAND(b *testing.B, size int) { + p, q := make([]byte, size), make([]byte, size) + + for i := 0; i < b.N; i++ { + ANDBytes(p, p, q) + } +} + +// Benchmarks the baseline AND performance. +func BenchmarkBaseAND1KB(b *testing.B) { benchmarkBaseAND(b, 1024) } +func BenchmarkBaseAND2KB(b *testing.B) { benchmarkBaseAND(b, 2048) } +func BenchmarkBaseAND4KB(b *testing.B) { benchmarkBaseAND(b, 4096) } + +func benchmarkBaseAND(b *testing.B, size int) { + p, q := make([]byte, size), make([]byte, size) + + for i := 0; i < b.N; i++ { + safeANDBytes(p, p, q) + } +} + +// Benchmarks the potentially optimized OR performance. +func BenchmarkFastOR1KB(b *testing.B) { benchmarkFastOR(b, 1024) } +func BenchmarkFastOR2KB(b *testing.B) { benchmarkFastOR(b, 2048) } +func BenchmarkFastOR4KB(b *testing.B) { benchmarkFastOR(b, 4096) } + +func benchmarkFastOR(b *testing.B, size int) { + p, q := make([]byte, size), make([]byte, size) + + for i := 0; i < b.N; i++ { + ORBytes(p, p, q) + } +} + +// Benchmarks the baseline OR performance. +func BenchmarkBaseOR1KB(b *testing.B) { benchmarkBaseOR(b, 1024) } +func BenchmarkBaseOR2KB(b *testing.B) { benchmarkBaseOR(b, 2048) } +func BenchmarkBaseOR4KB(b *testing.B) { benchmarkBaseOR(b, 4096) } + +func benchmarkBaseOR(b *testing.B, size int) { + p, q := make([]byte, size), make([]byte, size) + + for i := 0; i < b.N; i++ { + safeORBytes(p, p, q) + } +} + +var GloBool bool // Exported global will not be dead-code eliminated, at least not yet. + +// Benchmarks the potentially optimized bit testing performance. +func BenchmarkFastTest1KB(b *testing.B) { benchmarkFastTest(b, 1024) } +func BenchmarkFastTest2KB(b *testing.B) { benchmarkFastTest(b, 2048) } +func BenchmarkFastTest4KB(b *testing.B) { benchmarkFastTest(b, 4096) } + +func benchmarkFastTest(b *testing.B, size int) { + p := make([]byte, size) + a := false + for i := 0; i < b.N; i++ { + a = a != TestBytes(p) + } + GloBool = a // Use of benchmark "result" to prevent total dead code elimination. +} + +// Benchmarks the baseline bit testing performance. +func BenchmarkBaseTest1KB(b *testing.B) { benchmarkBaseTest(b, 1024) } +func BenchmarkBaseTest2KB(b *testing.B) { benchmarkBaseTest(b, 2048) } +func BenchmarkBaseTest4KB(b *testing.B) { benchmarkBaseTest(b, 4096) } + +func benchmarkBaseTest(b *testing.B, size int) { + p := make([]byte, size) + a := false + for i := 0; i < b.N; i++ { + a = a != safeTestBytes(p) + } + GloBool = a // Use of benchmark "result" to prevent total dead code elimination. +} diff --git a/common/bitutil/compress.go b/common/bitutil/compress.go new file mode 100644 index 0000000..c057cee --- /dev/null +++ b/common/bitutil/compress.go @@ -0,0 +1,170 @@ +// 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 "errors" + +var ( + // errMissingData is returned from decompression if the byte referenced by + // the bitset header overflows the input data. + errMissingData = errors.New("missing bytes on input") + + // errUnreferencedData is returned from decompression if not all bytes were used + // up from the input data after decompressing it. + errUnreferencedData = errors.New("extra bytes on input") + + // errExceededTarget is returned from decompression if the bitset header has + // more bits defined than the number of target buffer space available. + errExceededTarget = errors.New("target data size exceeded") + + // errZeroContent is returned from decompression if a data byte referenced in + // the bitset header is actually a zero byte. + errZeroContent = errors.New("zero byte in input content") +) + +// The compression algorithm implemented by CompressBytes and DecompressBytes is +// optimized for sparse input data which contains a lot of zero bytes. Decompression +// requires knowledge of the decompressed data length. +// +// Compression works as follows: +// +// if data only contains zeroes, +// CompressBytes(data) == nil +// otherwise if len(data) <= 1, +// CompressBytes(data) == data +// otherwise: +// CompressBytes(data) == append(CompressBytes(nonZeroBitset(data)), nonZeroBytes(data)...) +// where +// nonZeroBitset(data) is a bit vector with len(data) bits (MSB first): +// nonZeroBitset(data)[i/8] && (1 << (7-i%8)) != 0 if data[i] != 0 +// len(nonZeroBitset(data)) == (len(data)+7)/8 +// nonZeroBytes(data) contains the non-zero bytes of data in the same order + +// CompressBytes compresses the input byte slice according to the sparse bitset +// representation algorithm. If the result is bigger than the original input, no +// compression is done. +func CompressBytes(data []byte) []byte { + if out := bitsetEncodeBytes(data); len(out) < len(data) { + return out + } + cpy := make([]byte, len(data)) + copy(cpy, data) + return cpy +} + +// bitsetEncodeBytes compresses the input byte slice according to the sparse +// bitset representation algorithm. +func bitsetEncodeBytes(data []byte) []byte { + // Empty slices get compressed to nil + if len(data) == 0 { + return nil + } + // One byte slices compress to nil or retain the single byte + if len(data) == 1 { + if data[0] == 0 { + return nil + } + return data + } + // Calculate the bitset of set bytes, and gather the non-zero bytes + nonZeroBitset := make([]byte, (len(data)+7)/8) + nonZeroBytes := make([]byte, 0, len(data)) + + for i, b := range data { + if b != 0 { + nonZeroBytes = append(nonZeroBytes, b) + nonZeroBitset[i/8] |= 1 << byte(7-i%8) + } + } + if len(nonZeroBytes) == 0 { + return nil + } + return append(bitsetEncodeBytes(nonZeroBitset), nonZeroBytes...) +} + +// DecompressBytes decompresses data with a known target size. If the input data +// matches the size of the target, it means no compression was done in the first +// place. +func DecompressBytes(data []byte, target int) ([]byte, error) { + if len(data) > target { + return nil, errExceededTarget + } + if len(data) == target { + cpy := make([]byte, len(data)) + copy(cpy, data) + return cpy, nil + } + return bitsetDecodeBytes(data, target) +} + +// bitsetDecodeBytes decompresses data with a known target size. +func bitsetDecodeBytes(data []byte, target int) ([]byte, error) { + out, size, err := bitsetDecodePartialBytes(data, target) + if err != nil { + return nil, err + } + if size != len(data) { + return nil, errUnreferencedData + } + return out, nil +} + +// bitsetDecodePartialBytes decompresses data with a known target size, but does +// not enforce consuming all the input bytes. In addition to the decompressed +// output, the function returns the length of compressed input data corresponding +// to the output as the input slice may be longer. +func bitsetDecodePartialBytes(data []byte, target int) ([]byte, int, error) { + // Sanity check 0 targets to avoid infinite recursion + if target == 0 { + return nil, 0, nil + } + // Handle the zero and single byte corner cases + decomp := make([]byte, target) + if len(data) == 0 { + return decomp, 0, nil + } + if target == 1 { + decomp[0] = data[0] // copy to avoid referencing the input slice + if data[0] != 0 { + return decomp, 1, nil + } + return decomp, 0, nil + } + // Decompress the bitset of set bytes and distribute the non zero bytes + nonZeroBitset, ptr, err := bitsetDecodePartialBytes(data, (target+7)/8) + if err != nil { + return nil, ptr, err + } + for i := 0; i < 8*len(nonZeroBitset); i++ { + if nonZeroBitset[i/8]&(1<= len(data) { + return nil, 0, errMissingData + } + if i >= len(decomp) { + return nil, 0, errExceededTarget + } + // Make sure the data is valid and push into the slot + if data[ptr] == 0 { + return nil, 0, errZeroContent + } + decomp[i] = data[ptr] + ptr++ + } + } + return decomp, ptr, nil +} diff --git a/common/bitutil/compress_test.go b/common/bitutil/compress_test.go new file mode 100644 index 0000000..c6f6fe8 --- /dev/null +++ b/common/bitutil/compress_test.go @@ -0,0 +1,223 @@ +// 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" + "fmt" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// Tests that data bitset encoding and decoding works and is bijective. +func TestEncodingCycle(t *testing.T) { + tests := []string{ + // Tests generated by go-fuzz to maximize code coverage + "0x000000000000000000", + "0xef0400", + "0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb", + "0x7b64000000", + "0x000034000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0000000000000000000", + "0x4912385c0e7b64000000", + "0x000034000000000000000000000000000000", + "0x00", + "0x000003e834ff7f0000", + "0x0000", + "0x0000000000000000000000000000000000000000000000000000000000ff00", + "0x895f0c6a020f850c6a020f85f88df88d", + "0xdf7070533534333636313639343638373432313536346c1bc3315aac2f65fefb", + "0x0000000000", + "0xdf70706336346c65fefb", + "0x00006d643634000000", + "0xdf7070533534333636313639343638373532313536346c1bc333393438373130707063363430353639343638373532313536346c1bc333393438336336346c65fe", + } + for i, tt := range tests { + 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 { + size int + input string + fail error + }{ + {size: 0, input: "0x"}, + + // Crashers generated by go-fuzz + {size: 0, input: "0x0020", fail: errUnreferencedData}, + {size: 0, input: "0x30", fail: errUnreferencedData}, + {size: 1, input: "0x00", fail: errUnreferencedData}, + {size: 2, input: "0x07", fail: errMissingData}, + {size: 1024, input: "0x8000", fail: errZeroContent}, + + // Tests generated by go-fuzz to maximize code coverage + {size: 29490, input: "0x343137343733323134333839373334323073333930783e3078333930783e70706336346c65303e", fail: errMissingData}, + {size: 59395, input: "0x00", fail: errUnreferencedData}, + {size: 52574, input: "0x70706336346c65c0de", fail: errExceededTarget}, + {size: 42264, input: "0x07", fail: errMissingData}, + {size: 52, input: "0xa5045bad48f4", fail: errExceededTarget}, + {size: 52574, input: "0xc0de", fail: errMissingData}, + {size: 52574, input: "0x"}, + {size: 29490, input: "0x34313734373332313433383937333432307333393078073034333839373334323073333930783e3078333937333432307333393078073061333930783e70706336346c65303e", fail: errMissingData}, + {size: 29491, input: "0x3973333930783e30783e", fail: errMissingData}, + + {size: 1024, input: "0x808080608080"}, + {size: 1024, input: "0x808470705e3632383337363033313434303137393130306c6580ef46806380635a80"}, + {size: 1024, input: "0x8080808070"}, + {size: 1024, input: "0x808070705e36346c6580ef46806380635a80"}, + {size: 1024, input: "0x80808046802680"}, + {size: 1024, input: "0x4040404035"}, + {size: 1024, input: "0x4040bf3ba2b3f684402d353234373438373934409fe5b1e7ada94ebfd7d0505e27be4035"}, + {size: 1024, input: "0x404040bf3ba2b3f6844035"}, + {size: 1024, input: "0x40402d35323437343837393440bfd7d0505e27be4035"}, + } + for i, tt := range tests { + data := hexutil.MustDecode(tt.input) + + orig, err := bitsetDecodeBytes(data, tt.size) + if err != tt.fail { + t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.fail) + } + if err != nil { + continue + } + if comp := bitsetEncodeBytes(orig); !bytes.Equal(comp, data) { + t.Errorf("test %d: decompress/compress mismatch: have %x, want %x", i, comp, data) + } + } +} + +// TestCompression tests that compression works by returning either the bitset +// encoded input, or the actual input if the bitset version is longer. +func TestCompression(t *testing.T) { + // Check the compression returns the bitset encoding is shorter + in := hexutil.MustDecode("0x4912385c0e7b64000000") + out := hexutil.MustDecode("0x80fe4912385c0e7b64") + + if data := CompressBytes(in); !bytes.Equal(data, out) { + t.Errorf("encoding mismatch for sparse data: have %x, want %x", data, out) + } + if data, err := DecompressBytes(out, len(in)); err != nil || !bytes.Equal(data, in) { + t.Errorf("decoding mismatch for sparse data: have %x, want %x, error %v", data, in, err) + } + // Check the compression returns the input if the bitset encoding is longer + in = hexutil.MustDecode("0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb") + out = hexutil.MustDecode("0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb") + + if data := CompressBytes(in); !bytes.Equal(data, out) { + t.Errorf("encoding mismatch for dense data: have %x, want %x", data, out) + } + if data, err := DecompressBytes(out, len(in)); err != nil || !bytes.Equal(data, in) { + t.Errorf("decoding mismatch for dense data: have %x, want %x, error %v", data, in, err) + } + // Check that decompressing a longer input than the target fails + if _, err := DecompressBytes([]byte{0xc0, 0x01, 0x01}, 2); err != errExceededTarget { + t.Errorf("decoding error mismatch for long data: have %v, want %v", err, errExceededTarget) + } +} + +// Crude benchmark for compressing random slices of bytes. +func BenchmarkEncoding1KBVerySparse(b *testing.B) { benchmarkEncoding(b, 1024, 0.0001) } +func BenchmarkEncoding2KBVerySparse(b *testing.B) { benchmarkEncoding(b, 2048, 0.0001) } +func BenchmarkEncoding4KBVerySparse(b *testing.B) { benchmarkEncoding(b, 4096, 0.0001) } + +func BenchmarkEncoding1KBSparse(b *testing.B) { benchmarkEncoding(b, 1024, 0.001) } +func BenchmarkEncoding2KBSparse(b *testing.B) { benchmarkEncoding(b, 2048, 0.001) } +func BenchmarkEncoding4KBSparse(b *testing.B) { benchmarkEncoding(b, 4096, 0.001) } + +func BenchmarkEncoding1KBDense(b *testing.B) { benchmarkEncoding(b, 1024, 0.1) } +func BenchmarkEncoding2KBDense(b *testing.B) { benchmarkEncoding(b, 2048, 0.1) } +func BenchmarkEncoding4KBDense(b *testing.B) { benchmarkEncoding(b, 4096, 0.1) } + +func BenchmarkEncoding1KBSaturated(b *testing.B) { benchmarkEncoding(b, 1024, 0.5) } +func BenchmarkEncoding2KBSaturated(b *testing.B) { benchmarkEncoding(b, 2048, 0.5) } +func BenchmarkEncoding4KBSaturated(b *testing.B) { benchmarkEncoding(b, 4096, 0.5) } + +func benchmarkEncoding(b *testing.B, bytes int, fill float64) { + // Generate a random slice of bytes to compress + random := rand.NewSource(0) // reproducible and comparable + + data := make([]byte, bytes) + bits := int(float64(bytes) * 8 * fill) + + for i := 0; i < bits; i++ { + idx := random.Int63() % int64(len(data)) + bit := uint(random.Int63() % 8) + data[idx] |= 1 << bit + } + // Reset the benchmark and measure encoding/decoding + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + 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/bytes.go b/common/bytes.go new file mode 100644 index 0000000..d1f5c6c --- /dev/null +++ b/common/bytes.go @@ -0,0 +1,151 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package common contains various helper functions. +package common + +import ( + "encoding/hex" + "errors" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// FromHex returns the bytes represented by the hexadecimal string s. +// s may be prefixed with "0x". +func FromHex(s string) []byte { + if has0xPrefix(s) { + s = s[2:] + } + if len(s)%2 == 1 { + s = "0" + s + } + return Hex2Bytes(s) +} + +// CopyBytes returns an exact copy of the provided bytes. +func CopyBytes(b []byte) (copiedBytes []byte) { + if b == nil { + return nil + } + copiedBytes = make([]byte, len(b)) + copy(copiedBytes, b) + + return +} + +// has0xPrefix validates str begins with '0x' or '0X'. +func has0xPrefix(str string) bool { + return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') +} + +// isHexCharacter returns bool of c being a valid hexadecimal. +func isHexCharacter(c byte) bool { + return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F') +} + +// isHex validates whether each byte is valid hexadecimal string. +func isHex(str string) bool { + if len(str)%2 != 0 { + return false + } + for _, c := range []byte(str) { + if !isHexCharacter(c) { + return false + } + } + return true +} + +// Bytes2Hex returns the hexadecimal encoding of d. +func Bytes2Hex(d []byte) string { + return hex.EncodeToString(d) +} + +// Hex2Bytes returns the bytes represented by the hexadecimal string str. +func Hex2Bytes(str string) []byte { + h, _ := hex.DecodeString(str) + return h +} + +// Hex2BytesFixed returns bytes of a specified fixed length flen. +func Hex2BytesFixed(str string, flen int) []byte { + h, _ := hex.DecodeString(str) + if len(h) == flen { + return h + } + if len(h) > flen { + return h[len(h)-flen:] + } + hh := make([]byte, flen) + copy(hh[flen-len(h):flen], h) + return hh +} + +// ParseHexOrString tries to hexdecode b, but if the prefix is missing, it instead just returns the raw bytes +func ParseHexOrString(str string) ([]byte, error) { + b, err := hexutil.Decode(str) + if errors.Is(err, hexutil.ErrMissingPrefix) { + return []byte(str), nil + } + return b, err +} + +// RightPadBytes zero-pads slice to the right up to length l. +func RightPadBytes(slice []byte, l int) []byte { + if l <= len(slice) { + return slice + } + + padded := make([]byte, l) + copy(padded, slice) + + return padded +} + +// LeftPadBytes zero-pads slice to the left up to length l. +func LeftPadBytes(slice []byte, l int) []byte { + if l <= len(slice) { + return slice + } + + padded := make([]byte, l) + copy(padded[l-len(slice):], slice) + + return padded +} + +// TrimLeftZeroes returns a subslice of s without leading zeroes +func TrimLeftZeroes(s []byte) []byte { + idx := 0 + for ; idx < len(s); idx++ { + if s[idx] != 0 { + break + } + } + return s[idx:] +} + +// TrimRightZeroes returns a subslice of s without trailing zeroes +func TrimRightZeroes(s []byte) []byte { + idx := len(s) + for ; idx > 0; idx-- { + if s[idx-1] != 0 { + break + } + } + return s[:idx] +} diff --git a/common/bytes_test.go b/common/bytes_test.go new file mode 100644 index 0000000..0e3ec97 --- /dev/null +++ b/common/bytes_test.go @@ -0,0 +1,126 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package common + +import ( + "bytes" + "testing" +) + +func TestCopyBytes(t *testing.T) { + input := []byte{1, 2, 3, 4} + + v := CopyBytes(input) + if !bytes.Equal(v, []byte{1, 2, 3, 4}) { + t.Fatal("not equal after copy") + } + v[0] = 99 + if bytes.Equal(v, input) { + t.Fatal("result is not a copy") + } +} + +func TestLeftPadBytes(t *testing.T) { + val := []byte{1, 2, 3, 4} + padded := []byte{0, 0, 0, 0, 1, 2, 3, 4} + + if r := LeftPadBytes(val, 8); !bytes.Equal(r, padded) { + t.Fatalf("LeftPadBytes(%v, 8) == %v", val, r) + } + if r := LeftPadBytes(val, 2); !bytes.Equal(r, val) { + t.Fatalf("LeftPadBytes(%v, 2) == %v", val, r) + } +} + +func TestRightPadBytes(t *testing.T) { + val := []byte{1, 2, 3, 4} + padded := []byte{1, 2, 3, 4, 0, 0, 0, 0} + + if r := RightPadBytes(val, 8); !bytes.Equal(r, padded) { + t.Fatalf("RightPadBytes(%v, 8) == %v", val, r) + } + if r := RightPadBytes(val, 2); !bytes.Equal(r, val) { + t.Fatalf("RightPadBytes(%v, 2) == %v", val, r) + } +} + +func TestFromHex(t *testing.T) { + input := "0x01" + expected := []byte{1} + result := FromHex(input) + if !bytes.Equal(expected, result) { + t.Errorf("Expected %x got %x", expected, result) + } +} + +func TestIsHex(t *testing.T) { + tests := []struct { + input string + ok bool + }{ + {"", true}, + {"0", false}, + {"00", true}, + {"a9e67e", true}, + {"A9E67E", true}, + {"0xa9e67e", false}, + {"a9e67e001", false}, + {"0xHELLO_MY_NAME_IS_STEVEN_@#$^&*", false}, + } + for _, test := range tests { + if ok := isHex(test.input); ok != test.ok { + t.Errorf("isHex(%q) = %v, want %v", test.input, ok, test.ok) + } + } +} + +func TestFromHexOddLength(t *testing.T) { + input := "0x1" + expected := []byte{1} + result := FromHex(input) + if !bytes.Equal(expected, result) { + t.Errorf("Expected %x got %x", expected, result) + } +} + +func TestNoPrefixShortHexOddLength(t *testing.T) { + input := "1" + expected := []byte{1} + result := FromHex(input) + if !bytes.Equal(expected, result) { + t.Errorf("Expected %x got %x", expected, result) + } +} + +func TestTrimRightZeroes(t *testing.T) { + tests := []struct { + arr []byte + exp []byte + }{ + {FromHex("0x00ffff00ff0000"), FromHex("0x00ffff00ff")}, + {FromHex("0x00000000000000"), []byte{}}, + {FromHex("0xff"), FromHex("0xff")}, + {[]byte{}, []byte{}}, + {FromHex("0x00ffffffffffff"), FromHex("0x00ffffffffffff")}, + } + for i, test := range tests { + got := TrimRightZeroes(test.arr) + if !bytes.Equal(got, test.exp) { + t.Errorf("test %d, got %x exp %x", i, got, test.exp) + } + } +} diff --git a/common/compiler/helpers.go b/common/compiler/helpers.go new file mode 100644 index 0000000..063fc10 --- /dev/null +++ b/common/compiler/helpers.go @@ -0,0 +1,45 @@ +// 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 compiler wraps the Solidity and Vyper compiler executables (solc; vyper). +package compiler + +// Contract contains information about a compiled contract, alongside its code and runtime code. +type Contract struct { + Code string `json:"code"` + RuntimeCode string `json:"runtime-code"` + Info ContractInfo `json:"info"` + Hashes map[string]string `json:"hashes"` +} + +// ContractInfo contains information about a compiled contract, including access +// to the ABI definition, source mapping, user and developer docs, and metadata. +// +// Depending on the source, language version, compiler version, and compiler +// options will provide information about how the contract was compiled. +type ContractInfo struct { + Source string `json:"source"` + Language string `json:"language"` + LanguageVersion string `json:"languageVersion"` + CompilerVersion string `json:"compilerVersion"` + CompilerOptions string `json:"compilerOptions"` + SrcMap interface{} `json:"srcMap"` + SrcMapRuntime string `json:"srcMapRuntime"` + AbiDefinition interface{} `json:"abiDefinition"` + UserDoc interface{} `json:"userDoc"` + DeveloperDoc interface{} `json:"developerDoc"` + Metadata string `json:"metadata"` +} diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go new file mode 100644 index 0000000..9de9401 --- /dev/null +++ b/common/compiler/solidity.go @@ -0,0 +1,132 @@ +// 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 compiler wraps the ABI compilation outputs. +package compiler + +import ( + "encoding/json" + "fmt" +) + +// --combined-output format +type solcOutput struct { + Contracts map[string]struct { + BinRuntime string `json:"bin-runtime"` + SrcMapRuntime string `json:"srcmap-runtime"` + Bin, SrcMap, Abi, Devdoc, Userdoc, Metadata string + Hashes map[string]string + } + Version string +} + +// solidity v.0.8 changes the way ABI, Devdoc and Userdoc are serialized +type solcOutputV8 struct { + Contracts map[string]struct { + BinRuntime string `json:"bin-runtime"` + SrcMapRuntime string `json:"srcmap-runtime"` + Bin, SrcMap, Metadata string + Abi interface{} + Devdoc interface{} + Userdoc interface{} + Hashes map[string]string + } + Version string +} + +// ParseCombinedJSON takes the direct output of a solc --combined-output run and +// parses it into a map of string contract name to Contract structs. The +// provided source, language and compiler version, and compiler options are all +// passed through into the Contract structs. +// +// The solc output is expected to contain ABI, source mapping, user docs, and dev docs. +// +// Returns an error if the JSON is malformed or missing data, or if the JSON +// embedded within the JSON is malformed. +func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { + var output solcOutput + if err := json.Unmarshal(combinedJSON, &output); err != nil { + // Try to parse the output with the new solidity v.0.8.0 rules + return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions) + } + // Compilation succeeded, assemble and return the contracts. + contracts := make(map[string]*Contract) + for name, info := range output.Contracts { + // Parse the individual compilation results. + var abi, userdoc, devdoc interface{} + if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil { + return nil, fmt.Errorf("solc: error reading abi definition (%v)", err) + } + if err := json.Unmarshal([]byte(info.Userdoc), &userdoc); err != nil { + return nil, fmt.Errorf("solc: error reading userdoc definition (%v)", err) + } + if err := json.Unmarshal([]byte(info.Devdoc), &devdoc); err != nil { + return nil, fmt.Errorf("solc: error reading devdoc definition (%v)", err) + } + + contracts[name] = &Contract{ + Code: "0x" + info.Bin, + RuntimeCode: "0x" + info.BinRuntime, + Hashes: info.Hashes, + Info: ContractInfo{ + Source: source, + Language: "Solidity", + LanguageVersion: languageVersion, + CompilerVersion: compilerVersion, + CompilerOptions: compilerOptions, + SrcMap: info.SrcMap, + SrcMapRuntime: info.SrcMapRuntime, + AbiDefinition: abi, + UserDoc: userdoc, + DeveloperDoc: devdoc, + Metadata: info.Metadata, + }, + } + } + return contracts, nil +} + +// parseCombinedJSONV8 parses the direct output of solc --combined-output +// and parses it using the rules from solidity v.0.8.0 and later. +func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { + var output solcOutputV8 + if err := json.Unmarshal(combinedJSON, &output); err != nil { + return nil, err + } + // Compilation succeeded, assemble and return the contracts. + contracts := make(map[string]*Contract) + for name, info := range output.Contracts { + contracts[name] = &Contract{ + Code: "0x" + info.Bin, + RuntimeCode: "0x" + info.BinRuntime, + Hashes: info.Hashes, + Info: ContractInfo{ + Source: source, + Language: "Solidity", + LanguageVersion: languageVersion, + CompilerVersion: compilerVersion, + CompilerOptions: compilerOptions, + SrcMap: info.SrcMap, + SrcMapRuntime: info.SrcMapRuntime, + AbiDefinition: info.Abi, + UserDoc: info.Userdoc, + DeveloperDoc: info.Devdoc, + Metadata: info.Metadata, + }, + } + } + return contracts, nil +} diff --git a/common/debug.go b/common/debug.go new file mode 100644 index 0000000..28c52b4 --- /dev/null +++ b/common/debug.go @@ -0,0 +1,52 @@ +// 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 common + +import ( + "fmt" + "os" + "runtime" + "runtime/debug" + "strings" +) + +// Report gives off a warning requesting the user to submit an issue to the github tracker. +func Report(extra ...interface{}) { + fmt.Fprintln(os.Stderr, "You've encountered a sought after, hard to reproduce bug. Please report this to the developers <3 https://github.com/ethereum/go-ethereum/issues") + fmt.Fprintln(os.Stderr, extra...) + + _, file, line, _ := runtime.Caller(1) + fmt.Fprintf(os.Stderr, "%v:%v\n", file, line) + + debug.PrintStack() + + fmt.Fprintln(os.Stderr, "#### BUG! PLEASE REPORT ####") +} + +// PrintDeprecationWarning prints the given string in a box using fmt.Println. +func PrintDeprecationWarning(str string) { + line := strings.Repeat("#", len(str)+4) + emptyLine := strings.Repeat(" ", len(str)) + fmt.Printf(` +%s +# %s # +# %s # +# %s # +%s + +`, line, emptyLine, str, emptyLine, line) +} diff --git a/common/fdlimit/fdlimit_bsd.go b/common/fdlimit/fdlimit_bsd.go new file mode 100644 index 0000000..a3a6902 --- /dev/null +++ b/common/fdlimit/fdlimit_bsd.go @@ -0,0 +1,68 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build freebsd || dragonfly +// +build freebsd dragonfly + +package fdlimit + +import "syscall" + +// This file is largely identical to fdlimit_unix.go, +// but Rlimit fields have type int64 on *BSD so it needs +// an extra conversion. + +// Raise tries to maximize the file descriptor allowance of this process +// to the maximum hard-limit allowed by the OS. +func Raise(max uint64) (uint64, error) { + // Get the current limit + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + // Try to update the limit to the max allowance + limit.Cur = limit.Max + if limit.Cur > int64(max) { + limit.Cur = int64(max) + } + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return uint64(limit.Cur), nil +} + +// Current retrieves the number of file descriptors allowed to be opened by this +// process. +func Current() (int, error) { + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return int(limit.Cur), nil +} + +// Maximum retrieves the maximum number of file descriptors this process is +// allowed to request for itself. +func Maximum() (int, error) { + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return int(limit.Max), nil +} diff --git a/common/fdlimit/fdlimit_darwin.go b/common/fdlimit/fdlimit_darwin.go new file mode 100644 index 0000000..6b26fa0 --- /dev/null +++ b/common/fdlimit/fdlimit_darwin.go @@ -0,0 +1,71 @@ +// 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 fdlimit + +import "syscall" + +// hardlimit is the number of file descriptors allowed at max by the kernel. +const hardlimit = 10240 + +// Raise tries to maximize the file descriptor allowance of this process +// to the maximum hard-limit allowed by the OS. +// Returns the size it was set to (may differ from the desired 'max') +func Raise(max uint64) (uint64, error) { + // Get the current limit + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + // Try to update the limit to the max allowance + limit.Cur = limit.Max + if limit.Cur > max { + limit.Cur = max + } + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + // MacOS can silently apply further caps, so retrieve the actually set limit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return limit.Cur, nil +} + +// Current retrieves the number of file descriptors allowed to be opened by this +// process. +func Current() (int, error) { + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return int(limit.Cur), nil +} + +// Maximum retrieves the maximum number of file descriptors this process is +// allowed to request for itself. +func Maximum() (int, error) { + // Retrieve the maximum allowed by dynamic OS limits + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + // Cap it to OPEN_MAX (10240) because macos is a special snowflake + if limit.Max > hardlimit { + limit.Max = hardlimit + } + return int(limit.Max), nil +} diff --git a/common/fdlimit/fdlimit_test.go b/common/fdlimit/fdlimit_test.go new file mode 100644 index 0000000..9fd5e9f --- /dev/null +++ b/common/fdlimit/fdlimit_test.go @@ -0,0 +1,44 @@ +// 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 fdlimit + +import ( + "testing" +) + +// TestFileDescriptorLimits simply tests whether the file descriptor allowance +// per this process can be retrieved. +func TestFileDescriptorLimits(t *testing.T) { + target := 4096 + hardlimit, err := Maximum() + if err != nil { + t.Fatal(err) + } + if hardlimit < target { + t.Skipf("system limit is less than desired test target: %d < %d", hardlimit, target) + } + + if limit, err := Current(); err != nil || limit <= 0 { + t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err) + } + if _, err := Raise(uint64(target)); err != nil { + t.Fatalf("failed to raise file allowance") + } + if limit, err := Current(); err != nil || limit < target { + t.Fatalf("failed to retrieve raised descriptor limit (have %v, want %v): %v", limit, target, err) + } +} diff --git a/common/fdlimit/fdlimit_unix.go b/common/fdlimit/fdlimit_unix.go new file mode 100644 index 0000000..a1f388e --- /dev/null +++ b/common/fdlimit/fdlimit_unix.go @@ -0,0 +1,66 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +//go:build linux || netbsd || openbsd || solaris +// +build linux netbsd openbsd solaris + +package fdlimit + +import "syscall" + +// Raise tries to maximize the file descriptor allowance of this process +// to the maximum hard-limit allowed by the OS. +// Returns the size it was set to (may differ from the desired 'max') +func Raise(max uint64) (uint64, error) { + // Get the current limit + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + // Try to update the limit to the max allowance + limit.Cur = limit.Max + if limit.Cur > max { + limit.Cur = max + } + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + // MacOS can silently apply further caps, so retrieve the actually set limit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return limit.Cur, nil +} + +// Current retrieves the number of file descriptors allowed to be opened by this +// process. +func Current() (int, error) { + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return int(limit.Cur), nil +} + +// Maximum retrieves the maximum number of file descriptors this process is +// allowed to request for itself. +func Maximum() (int, error) { + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { + return 0, err + } + return int(limit.Max), nil +} diff --git a/common/fdlimit/fdlimit_windows.go b/common/fdlimit/fdlimit_windows.go new file mode 100644 index 0000000..f472153 --- /dev/null +++ b/common/fdlimit/fdlimit_windows.go @@ -0,0 +1,50 @@ +// 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 fdlimit + +import "fmt" + +// hardlimit is the number of file descriptors allowed at max by the kernel. +const hardlimit = 16384 + +// Raise tries to maximize the file descriptor allowance of this process +// to the maximum hard-limit allowed by the OS. +func Raise(max uint64) (uint64, error) { + // This method is NOP by design: + // * Linux/Darwin counterparts need to manually increase per process limits + // * On Windows Go uses the CreateFile API, which is limited to 16K files, non + // changeable from within a running process + // This way we can always "request" raising the limits, which will either have + // or not have effect based on the platform we're running on. + if max > hardlimit { + return hardlimit, fmt.Errorf("file descriptor limit (%d) reached", hardlimit) + } + return max, nil +} + +// Current retrieves the number of file descriptors allowed to be opened by this +// process. +func Current() (int, error) { + // Please see Raise for the reason why we use hard coded 16K as the limit + return hardlimit, nil +} + +// Maximum retrieves the maximum number of file descriptors this process is +// allowed to request for itself. +func Maximum() (int, error) { + return Current() +} diff --git a/common/format.go b/common/format.go new file mode 100644 index 0000000..7af41f5 --- /dev/null +++ b/common/format.go @@ -0,0 +1,82 @@ +// 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 common + +import ( + "fmt" + "regexp" + "strings" + "time" +) + +// PrettyDuration is a pretty printed version of a time.Duration value that cuts +// the unnecessary precision off from the formatted textual representation. +type PrettyDuration time.Duration + +var prettyDurationRe = regexp.MustCompile(`\.[0-9]{4,}`) + +// String implements the Stringer interface, allowing pretty printing of duration +// values rounded to three decimals. +func (d PrettyDuration) String() string { + label := time.Duration(d).String() + if match := prettyDurationRe.FindString(label); len(match) > 4 { + label = strings.Replace(label, match, match[:4], 1) + } + return label +} + +// PrettyAge is a pretty printed version of a time.Duration value that rounds +// the values up to a single most significant unit, days/weeks/years included. +type PrettyAge time.Time + +// ageUnits is a list of units the age pretty printing uses. +var ageUnits = []struct { + Size time.Duration + Symbol string +}{ + {12 * 30 * 24 * time.Hour, "y"}, + {30 * 24 * time.Hour, "mo"}, + {7 * 24 * time.Hour, "w"}, + {24 * time.Hour, "d"}, + {time.Hour, "h"}, + {time.Minute, "m"}, + {time.Second, "s"}, +} + +// String implements the Stringer interface, allowing pretty printing of duration +// values rounded to the most significant time unit. +func (t PrettyAge) String() string { + // Calculate the time difference and handle the 0 cornercase + diff := time.Since(time.Time(t)) + if diff < time.Second { + return "0" + } + // Accumulate a precision of 3 components before returning + result, prec := "", 0 + + for _, unit := range ageUnits { + if diff > unit.Size { + result = fmt.Sprintf("%s%d%s", result, diff/unit.Size, unit.Symbol) + diff %= unit.Size + + if prec += 1; prec >= 3 { + break + } + } + } + return result +} diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go new file mode 100644 index 0000000..d320185 --- /dev/null +++ b/common/hexutil/hexutil.go @@ -0,0 +1,241 @@ +// 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 hexutil implements hex encoding with 0x prefix. +This encoding is used by the Ethereum RPC API to transport binary data in JSON payloads. + +# Encoding Rules + +All hex data must have prefix "0x". + +For byte slices, the hex data must be of even length. An empty byte slice +encodes as "0x". + +Integers are encoded using the least amount of digits (no leading zero digits). Their +encoding may be of uneven length. The number zero encodes as "0x0". +*/ +package hexutil + +import ( + "encoding/hex" + "fmt" + "math/big" + "strconv" +) + +const uintBits = 32 << (uint64(^uint(0)) >> 63) + +// Errors +var ( + ErrEmptyString = &decError{"empty hex string"} + ErrSyntax = &decError{"invalid hex string"} + ErrMissingPrefix = &decError{"hex string without 0x prefix"} + ErrOddLength = &decError{"hex string of odd length"} + ErrEmptyNumber = &decError{"hex string \"0x\""} + ErrLeadingZero = &decError{"hex number with leading zero digits"} + ErrUint64Range = &decError{"hex number > 64 bits"} + ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)} + ErrBig256Range = &decError{"hex number > 256 bits"} +) + +type decError struct{ msg string } + +func (err decError) Error() string { return err.msg } + +// Decode decodes a hex string with 0x prefix. +func Decode(input string) ([]byte, error) { + if len(input) == 0 { + return nil, ErrEmptyString + } + if !has0xPrefix(input) { + return nil, ErrMissingPrefix + } + b, err := hex.DecodeString(input[2:]) + if err != nil { + err = mapError(err) + } + return b, err +} + +// MustDecode decodes a hex string with 0x prefix. It panics for invalid input. +func MustDecode(input string) []byte { + dec, err := Decode(input) + if err != nil { + panic(err) + } + return dec +} + +// Encode encodes b as a hex string with 0x prefix. +func Encode(b []byte) string { + enc := make([]byte, len(b)*2+2) + copy(enc, "0x") + hex.Encode(enc[2:], b) + return string(enc) +} + +// DecodeUint64 decodes a hex string with 0x prefix as a quantity. +func DecodeUint64(input string) (uint64, error) { + raw, err := checkNumber(input) + if err != nil { + return 0, err + } + dec, err := strconv.ParseUint(raw, 16, 64) + if err != nil { + err = mapError(err) + } + return dec, err +} + +// MustDecodeUint64 decodes a hex string with 0x prefix as a quantity. +// It panics for invalid input. +func MustDecodeUint64(input string) uint64 { + dec, err := DecodeUint64(input) + if err != nil { + panic(err) + } + return dec +} + +// EncodeUint64 encodes i as a hex string with 0x prefix. +func EncodeUint64(i uint64) string { + enc := make([]byte, 2, 10) + copy(enc, "0x") + return string(strconv.AppendUint(enc, i, 16)) +} + +var bigWordNibbles int + +func init() { + // This is a weird way to compute the number of nibbles required for big.Word. + // The usual way would be to use constant arithmetic but go vet can't handle that. + b, _ := new(big.Int).SetString("FFFFFFFFFF", 16) + switch len(b.Bits()) { + case 1: + bigWordNibbles = 16 + case 2: + bigWordNibbles = 8 + default: + panic("weird big.Word size") + } +} + +// DecodeBig decodes a hex string with 0x prefix as a quantity. +// Numbers larger than 256 bits are not accepted. +func DecodeBig(input string) (*big.Int, error) { + raw, err := checkNumber(input) + if err != nil { + return nil, err + } + if len(raw) > 64 { + return nil, ErrBig256Range + } + words := make([]big.Word, len(raw)/bigWordNibbles+1) + end := len(raw) + for i := range words { + start := end - bigWordNibbles + if start < 0 { + start = 0 + } + for ri := start; ri < end; ri++ { + nib := decodeNibble(raw[ri]) + if nib == badNibble { + return nil, ErrSyntax + } + words[i] *= 16 + words[i] += big.Word(nib) + } + end = start + } + dec := new(big.Int).SetBits(words) + return dec, nil +} + +// MustDecodeBig decodes a hex string with 0x prefix as a quantity. +// It panics for invalid input. +func MustDecodeBig(input string) *big.Int { + dec, err := DecodeBig(input) + if err != nil { + panic(err) + } + return dec +} + +// EncodeBig encodes bigint as a hex string with 0x prefix. +func EncodeBig(bigint *big.Int) string { + if sign := bigint.Sign(); sign == 0 { + return "0x0" + } else if sign > 0 { + return "0x" + bigint.Text(16) + } else { + return "-0x" + bigint.Text(16)[1:] + } +} + +func has0xPrefix(input string) bool { + return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') +} + +func checkNumber(input string) (raw string, err error) { + if len(input) == 0 { + return "", ErrEmptyString + } + if !has0xPrefix(input) { + return "", ErrMissingPrefix + } + input = input[2:] + if len(input) == 0 { + return "", ErrEmptyNumber + } + if len(input) > 1 && input[0] == '0' { + return "", ErrLeadingZero + } + return input, nil +} + +const badNibble = ^uint64(0) + +func decodeNibble(in byte) uint64 { + switch { + case in >= '0' && in <= '9': + return uint64(in - '0') + case in >= 'A' && in <= 'F': + return uint64(in - 'A' + 10) + case in >= 'a' && in <= 'f': + return uint64(in - 'a' + 10) + default: + return badNibble + } +} + +func mapError(err error) error { + if err, ok := err.(*strconv.NumError); ok { + switch err.Err { + case strconv.ErrRange: + return ErrUint64Range + case strconv.ErrSyntax: + return ErrSyntax + } + } + if _, ok := err.(hex.InvalidByteError); ok { + return ErrSyntax + } + if err == hex.ErrLength { + return ErrOddLength + } + return err +} diff --git a/common/hexutil/hexutil_test.go b/common/hexutil/hexutil_test.go new file mode 100644 index 0000000..f2b800d --- /dev/null +++ b/common/hexutil/hexutil_test.go @@ -0,0 +1,215 @@ +// 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 hexutil + +import ( + "bytes" + "math/big" + "testing" +) + +type marshalTest struct { + input interface{} + want string +} + +type unmarshalTest struct { + input string + want interface{} + wantErr error // if set, decoding must fail on any platform + wantErr32bit error // if set, decoding must fail on 32bit platforms (used for Uint tests) +} + +var ( + encodeBytesTests = []marshalTest{ + {[]byte{}, "0x"}, + {[]byte{0}, "0x00"}, + {[]byte{0, 0, 1, 2}, "0x00000102"}, + } + + encodeBigTests = []marshalTest{ + {referenceBig("0"), "0x0"}, + {referenceBig("1"), "0x1"}, + {referenceBig("ff"), "0xff"}, + {referenceBig("112233445566778899aabbccddeeff"), "0x112233445566778899aabbccddeeff"}, + {referenceBig("80a7f2c1bcc396c00"), "0x80a7f2c1bcc396c00"}, + {referenceBig("-80a7f2c1bcc396c00"), "-0x80a7f2c1bcc396c00"}, + } + + encodeUint64Tests = []marshalTest{ + {uint64(0), "0x0"}, + {uint64(1), "0x1"}, + {uint64(0xff), "0xff"}, + {uint64(0x1122334455667788), "0x1122334455667788"}, + } + + encodeUintTests = []marshalTest{ + {uint(0), "0x0"}, + {uint(1), "0x1"}, + {uint(0xff), "0xff"}, + {uint(0x11223344), "0x11223344"}, + } + + decodeBytesTests = []unmarshalTest{ + // invalid + {input: ``, wantErr: ErrEmptyString}, + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x0`, wantErr: ErrOddLength}, + {input: `0x023`, wantErr: ErrOddLength}, + {input: `0xxx`, wantErr: ErrSyntax}, + {input: `0x01zz01`, wantErr: ErrSyntax}, + // valid + {input: `0x`, want: []byte{}}, + {input: `0X`, want: []byte{}}, + {input: `0x02`, want: []byte{0x02}}, + {input: `0X02`, want: []byte{0x02}}, + {input: `0xffffffffff`, want: []byte{0xff, 0xff, 0xff, 0xff, 0xff}}, + { + input: `0xffffffffffffffffffffffffffffffffffff`, + want: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + } + + decodeBigTests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x`, wantErr: ErrEmptyNumber}, + {input: `0x01`, wantErr: ErrLeadingZero}, + {input: `0xx`, wantErr: ErrSyntax}, + {input: `0x1zz01`, wantErr: ErrSyntax}, + { + input: `0x10000000000000000000000000000000000000000000000000000000000000000`, + wantErr: ErrBig256Range, + }, + // valid + {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"), + }, + } + + decodeUint64Tests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `0x`, wantErr: ErrEmptyNumber}, + {input: `0x01`, wantErr: ErrLeadingZero}, + {input: `0xfffffffffffffffff`, wantErr: ErrUint64Range}, + {input: `0xx`, wantErr: ErrSyntax}, + {input: `0x1zz01`, wantErr: ErrSyntax}, + // valid + {input: `0x0`, want: uint64(0)}, + {input: `0x2`, want: uint64(0x2)}, + {input: `0x2F2`, want: uint64(0x2f2)}, + {input: `0X2F2`, want: uint64(0x2f2)}, + {input: `0x1122aaff`, want: uint64(0x1122aaff)}, + {input: `0xbbb`, want: uint64(0xbbb)}, + {input: `0xffffffffffffffff`, want: uint64(0xffffffffffffffff)}, + } +) + +func TestEncode(t *testing.T) { + for _, test := range encodeBytesTests { + enc := Encode(test.input.([]byte)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecode(t *testing.T) { + for _, test := range decodeBytesTests { + dec, err := Decode(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if !bytes.Equal(test.want.([]byte), dec) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func TestEncodeBig(t *testing.T) { + for _, test := range encodeBigTests { + enc := EncodeBig(test.input.(*big.Int)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeBig(t *testing.T) { + for _, test := range decodeBigTests { + dec, err := DecodeBig(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec.Cmp(test.want.(*big.Int)) != 0 { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func TestEncodeUint64(t *testing.T) { + for _, test := range encodeUint64Tests { + enc := EncodeUint64(test.input.(uint64)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeUint64(t *testing.T) { + for _, test := range decodeUint64Tests { + dec, err := DecodeUint64(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func BenchmarkEncodeBig(b *testing.B) { + for _, bench := range encodeBigTests { + b.Run(bench.want, func(b *testing.B) { + b.ReportAllocs() + bigint := bench.input.(*big.Int) + for i := 0; i < b.N; i++ { + EncodeBig(bigint) + } + }) + } +} diff --git a/common/hexutil/json.go b/common/hexutil/json.go new file mode 100644 index 0000000..e0ac98f --- /dev/null +++ b/common/hexutil/json.go @@ -0,0 +1,421 @@ +// 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 hexutil + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "reflect" + "strconv" + + "github.com/holiman/uint256" +) + +var ( + bytesT = reflect.TypeOf(Bytes(nil)) + 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. +// The empty slice marshals as "0x". +type Bytes []byte + +// MarshalText implements encoding.TextMarshaler +func (b Bytes) MarshalText() ([]byte, error) { + result := make([]byte, len(b)*2+2) + copy(result, `0x`) + hex.Encode(result[2:], b) + return result, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Bytes) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(bytesT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bytesT) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *Bytes) UnmarshalText(input []byte) error { + raw, err := checkText(input, true) + if err != nil { + return err + } + dec := make([]byte, len(raw)/2) + if _, err = hex.Decode(dec, raw); err != nil { + err = mapError(err) + } else { + *b = dec + } + return err +} + +// String returns the hex encoding of b. +func (b Bytes) String() string { + return Encode(b) +} + +// ImplementsGraphQLType returns true if Bytes implements the specified GraphQL type. +func (b Bytes) ImplementsGraphQLType(name string) bool { return name == "Bytes" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Bytes) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + data, err := Decode(input) + if err != nil { + return err + } + *b = data + default: + err = fmt.Errorf("unexpected type %T for Bytes", input) + } + return err +} + +// UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out +// determines the required input length. This function is commonly used to implement the +// UnmarshalJSON method for fixed-size types. +func UnmarshalFixedJSON(typ reflect.Type, input, out []byte) error { + if !isString(input) { + return errNonString(typ) + } + return wrapTypeError(UnmarshalFixedText(typ.String(), input[1:len(input)-1], out), typ) +} + +// UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out +// determines the required input length. This function is commonly used to implement the +// UnmarshalText method for fixed-size types. +func UnmarshalFixedText(typname string, input, out []byte) error { + raw, err := checkText(input, true) + if err != nil { + return err + } + if len(raw)/2 != len(out) { + return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) + } + // Pre-verify syntax before modifying out. + for _, b := range raw { + if decodeNibble(b) == badNibble { + return ErrSyntax + } + } + hex.Decode(out, raw) + return nil +} + +// UnmarshalFixedUnprefixedText decodes the input as a string with optional 0x prefix. The +// length of out determines the required input length. This function is commonly used to +// implement the UnmarshalText method for fixed-size types. +func UnmarshalFixedUnprefixedText(typname string, input, out []byte) error { + raw, err := checkText(input, false) + if err != nil { + return err + } + if len(raw)/2 != len(out) { + return fmt.Errorf("hex string has length %d, want %d for %s", len(raw), len(out)*2, typname) + } + // Pre-verify syntax before modifying out. + for _, b := range raw { + if decodeNibble(b) == badNibble { + return ErrSyntax + } + } + hex.Decode(out, raw) + return nil +} + +// Big marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +// +// Negative integers are not supported at this time. Attempting to marshal them will +// return an error. Values larger than 256bits are rejected by Unmarshal but will be +// marshaled without error. +type Big big.Int + +// MarshalText implements encoding.TextMarshaler +func (b Big) MarshalText() ([]byte, error) { + return []byte(EncodeBig((*big.Int)(&b))), nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Big) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(bigT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bigT) +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *Big) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 64 { + return ErrBig256Range + } + words := make([]big.Word, len(raw)/bigWordNibbles+1) + end := len(raw) + for i := range words { + start := end - bigWordNibbles + if start < 0 { + start = 0 + } + for ri := start; ri < end; ri++ { + nib := decodeNibble(raw[ri]) + if nib == badNibble { + return ErrSyntax + } + words[i] *= 16 + words[i] += big.Word(nib) + } + end = start + } + var dec big.Int + dec.SetBits(words) + *b = (Big)(dec) + return nil +} + +// ToInt converts b to a big.Int. +func (b *Big) ToInt() *big.Int { + return (*big.Int)(b) +} + +// String returns the hex encoding of b. +func (b *Big) String() string { + return EncodeBig(b.ToInt()) +} + +// ImplementsGraphQLType returns true if Big implements the provided GraphQL type. +func (b Big) ImplementsGraphQLType(name string) bool { return name == "BigInt" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Big) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + return b.UnmarshalText([]byte(input)) + case int32: + var num big.Int + num.SetInt64(int64(input)) + *b = Big(num) + default: + err = fmt.Errorf("unexpected type %T for BigInt", input) + } + 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 + +// MarshalText implements encoding.TextMarshaler. +func (b Uint64) MarshalText() ([]byte, error) { + buf := make([]byte, 2, 10) + copy(buf, `0x`) + buf = strconv.AppendUint(buf, uint64(b), 16) + return buf, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint64) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(uint64T) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint64T) +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *Uint64) UnmarshalText(input []byte) error { + raw, err := checkNumberText(input) + if err != nil { + return err + } + if len(raw) > 16 { + return ErrUint64Range + } + var dec uint64 + for _, byte := range raw { + nib := decodeNibble(byte) + if nib == badNibble { + return ErrSyntax + } + dec *= 16 + dec += nib + } + *b = Uint64(dec) + return nil +} + +// String returns the hex encoding of b. +func (b Uint64) String() string { + return EncodeUint64(uint64(b)) +} + +// ImplementsGraphQLType returns true if Uint64 implements the provided GraphQL type. +func (b Uint64) ImplementsGraphQLType(name string) bool { return name == "Long" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Uint64) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + return b.UnmarshalText([]byte(input)) + case int32: + *b = Uint64(input) + default: + err = fmt.Errorf("unexpected type %T for Long", input) + } + return err +} + +// Uint marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type Uint uint + +// MarshalText implements encoding.TextMarshaler. +func (b Uint) MarshalText() ([]byte, error) { + return Uint64(b).MarshalText() +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *Uint) UnmarshalJSON(input []byte) error { + if !isString(input) { + return errNonString(uintT) + } + return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uintT) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (b *Uint) UnmarshalText(input []byte) error { + var u64 Uint64 + err := u64.UnmarshalText(input) + if u64 > Uint64(^uint(0)) || err == ErrUint64Range { + return ErrUintRange + } else if err != nil { + return err + } + *b = Uint(u64) + return nil +} + +// String returns the hex encoding of b. +func (b Uint) String() string { + return EncodeUint64(uint64(b)) +} + +func isString(input []byte) bool { + return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' +} + +func bytesHave0xPrefix(input []byte) bool { + return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') +} + +func checkText(input []byte, wantPrefix bool) ([]byte, error) { + if len(input) == 0 { + return nil, nil // empty strings are allowed + } + if bytesHave0xPrefix(input) { + input = input[2:] + } else if wantPrefix { + return nil, ErrMissingPrefix + } + if len(input)%2 != 0 { + return nil, ErrOddLength + } + return input, nil +} + +func checkNumberText(input []byte) (raw []byte, err error) { + if len(input) == 0 { + return nil, nil // empty strings are allowed + } + if !bytesHave0xPrefix(input) { + return nil, ErrMissingPrefix + } + input = input[2:] + if len(input) == 0 { + return nil, ErrEmptyNumber + } + if len(input) > 1 && input[0] == '0' { + return nil, ErrLeadingZero + } + return input, nil +} + +func wrapTypeError(err error, typ reflect.Type) error { + if _, ok := err.(*decError); ok { + return &json.UnmarshalTypeError{Value: err.Error(), Type: typ} + } + return err +} + +func errNonString(typ reflect.Type) error { + return &json.UnmarshalTypeError{Value: "non-string", Type: typ} +} diff --git a/common/hexutil/json_example_test.go b/common/hexutil/json_example_test.go new file mode 100644 index 0000000..80180d9 --- /dev/null +++ b/common/hexutil/json_example_test.go @@ -0,0 +1,45 @@ +// 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 hexutil_test + +import ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +type MyType [5]byte + +func (v *MyType) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("MyType", input, v[:]) +} + +func (v MyType) String() string { + return hexutil.Bytes(v[:]).String() +} + +func ExampleUnmarshalFixedText() { + var v1, v2 MyType + fmt.Println("v1 error:", json.Unmarshal([]byte(`"0x01"`), &v1)) + fmt.Println("v2 error:", json.Unmarshal([]byte(`"0x0101010101"`), &v2)) + fmt.Println("v2:", v2) + // Output: + // v1 error: hex string has length 2, want 10 for MyType + // v2 error: + // v2: 0x0101010101 +} diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go new file mode 100644 index 0000000..7cca300 --- /dev/null +++ b/common/hexutil/json_test.go @@ -0,0 +1,434 @@ +// 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 hexutil + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + "math/big" + "testing" + + "github.com/holiman/uint256" +) + +func checkError(t *testing.T, input string, got, want error) bool { + if got == nil { + if want != nil { + t.Errorf("input %s: got no error, want %q", input, want) + return false + } + return true + } + if want == nil { + t.Errorf("input %s: unexpected error %q", input, got) + } else if got.Error() != want.Error() { + t.Errorf("input %s: got error %q, want %q", input, got, want) + } + return false +} + +func referenceBig(s string) *big.Int { + b, ok := new(big.Int).SetString(s, 16) + if !ok { + panic("invalid") + } + return b +} + +func referenceBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +var errJSONEOF = errors.New("unexpected end of JSON input") + +var unmarshalBytesTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(bytesT)}, + {input: "10", wantErr: errNonString(bytesT)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bytesT)}, + {input: `"0x0"`, wantErr: wrapTypeError(ErrOddLength, bytesT)}, + {input: `"0xxx"`, wantErr: wrapTypeError(ErrSyntax, bytesT)}, + {input: `"0x01zz01"`, wantErr: wrapTypeError(ErrSyntax, bytesT)}, + + // valid encoding + {input: `""`, want: referenceBytes("")}, + {input: `"0x"`, want: referenceBytes("")}, + {input: `"0x02"`, want: referenceBytes("02")}, + {input: `"0X02"`, want: referenceBytes("02")}, + {input: `"0xffffffffff"`, want: referenceBytes("ffffffffff")}, + { + input: `"0xffffffffffffffffffffffffffffffffffff"`, + want: referenceBytes("ffffffffffffffffffffffffffffffffffff"), + }, +} + +func TestUnmarshalBytes(t *testing.T) { + for _, test := range unmarshalBytesTests { + var v Bytes + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if !bytes.Equal(test.want.([]byte), v) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalBytes(b *testing.B) { + input := []byte(`"0x123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { + var v Bytes + if err := v.UnmarshalJSON(input); err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalBytes(t *testing.T) { + for _, test := range encodeBytesTests { + in := test.input.([]byte) + out, err := json.Marshal(Bytes(in)) + if err != nil { + t.Errorf("%x: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%x: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := Bytes(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var unmarshalBigTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(bigT)}, + {input: "10", wantErr: errNonString(bigT)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bigT)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, bigT)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, bigT)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, bigT)}, + {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, bigT)}, + { + input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`, + wantErr: wrapTypeError(ErrBig256Range, bigT), + }, + + // 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 TestUnmarshalBig(t *testing.T) { + for _, test := range unmarshalBigTests { + var v Big + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if test.want != nil && test.want.(*big.Int).Cmp((*big.Int)(&v)) != 0 { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, (*big.Int)(&v), test.want) + continue + } + } +} + +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++ { + var v Big + if err := v.UnmarshalJSON(input); err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalBig(t *testing.T) { + for _, test := range encodeBigTests { + in := test.input.(*big.Int) + out, err := json.Marshal((*Big)(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (*Big)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var unmarshalUint64Tests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(uint64T)}, + {input: "10", wantErr: errNonString(uint64T)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uint64T)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uint64T)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uint64T)}, + {input: `"0xfffffffffffffffff"`, wantErr: wrapTypeError(ErrUint64Range, uint64T)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uint64T)}, + {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uint64T)}, + + // valid encoding + {input: `""`, want: uint64(0)}, + {input: `"0x0"`, want: uint64(0)}, + {input: `"0x2"`, want: uint64(0x2)}, + {input: `"0x2F2"`, want: uint64(0x2f2)}, + {input: `"0X2F2"`, want: uint64(0x2f2)}, + {input: `"0x1122aaff"`, want: uint64(0x1122aaff)}, + {input: `"0xbbb"`, want: uint64(0xbbb)}, + {input: `"0xffffffffffffffff"`, want: uint64(0xffffffffffffffff)}, +} + +func TestUnmarshalUint64(t *testing.T) { + for _, test := range unmarshalUint64Tests { + var v Uint64 + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint64(v) != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalUint64(b *testing.B) { + input := []byte(`"0x123456789abcdf"`) + for i := 0; i < b.N; i++ { + var v Uint64 + v.UnmarshalJSON(input) + } +} + +func TestMarshalUint64(t *testing.T) { + for _, test := range encodeUint64Tests { + in := test.input.(uint64) + out, err := json.Marshal(Uint64(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (Uint64)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +func TestMarshalUint(t *testing.T) { + for _, test := range encodeUintTests { + in := test.input.(uint) + out, err := json.Marshal(Uint(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (Uint)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var ( + // These are variables (not constants) to avoid constant overflow + // checks in the compiler on 32bit platforms. + maxUint33bits = uint64(^uint32(0)) + 1 + maxUint64bits = ^uint64(0) +) + +var unmarshalUintTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(uintT)}, + {input: "10", wantErr: errNonString(uintT)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uintT)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uintT)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uintT)}, + {input: `"0x100000000"`, want: uint(maxUint33bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)}, + {input: `"0xfffffffffffffffff"`, wantErr: wrapTypeError(ErrUintRange, uintT)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uintT)}, + {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uintT)}, + + // valid encoding + {input: `""`, want: uint(0)}, + {input: `"0x0"`, want: uint(0)}, + {input: `"0x2"`, want: uint(0x2)}, + {input: `"0x2F2"`, want: uint(0x2f2)}, + {input: `"0X2F2"`, want: uint(0x2f2)}, + {input: `"0x1122aaff"`, want: uint(0x1122aaff)}, + {input: `"0xbbb"`, want: uint(0xbbb)}, + {input: `"0xffffffff"`, want: uint(0xffffffff)}, + {input: `"0xffffffffffffffff"`, want: uint(maxUint64bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)}, +} + +func TestUnmarshalUint(t *testing.T) { + for _, test := range unmarshalUintTests { + var v Uint + err := json.Unmarshal([]byte(test.input), &v) + if uintBits == 32 && test.wantErr32bit != nil { + checkError(t, test.input, err, test.wantErr32bit) + continue + } + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint(v) != test.want.(uint) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func TestUnmarshalFixedUnprefixedText(t *testing.T) { + tests := []struct { + input string + want []byte + wantErr error + }{ + {input: "0x2", wantErr: ErrOddLength}, + {input: "2", wantErr: ErrOddLength}, + {input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")}, + {input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")}, + // check that output is not modified for partially correct input + {input: "444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}}, + {input: "0x444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}}, + // valid inputs + {input: "44444444", want: []byte{0x44, 0x44, 0x44, 0x44}}, + {input: "0x44444444", want: []byte{0x44, 0x44, 0x44, 0x44}}, + } + + for _, test := range tests { + out := make([]byte, 4) + err := UnmarshalFixedUnprefixedText("x", []byte(test.input), out) + switch { + case err == nil && test.wantErr != nil: + t.Errorf("%q: got no error, expected %q", test.input, test.wantErr) + case err != nil && test.wantErr == nil: + t.Errorf("%q: unexpected error %q", test.input, err) + case err != nil && err.Error() != test.wantErr.Error(): + t.Errorf("%q: error mismatch: got %q, want %q", test.input, err, test.wantErr) + } + if test.want != nil && !bytes.Equal(out, test.want) { + t.Errorf("%q: output mismatch: got %x, want %x", test.input, out, test.want) + } + } +} diff --git a/common/lru/basiclru.go b/common/lru/basiclru.go new file mode 100644 index 0000000..7386c77 --- /dev/null +++ b/common/lru/basiclru.go @@ -0,0 +1,221 @@ +// 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 lru implements generically-typed LRU caches. +package lru + +// BasicLRU is a simple LRU cache. +// +// This type is not safe for concurrent use. +// The zero value is not valid, instances must be created using NewCache. +type BasicLRU[K comparable, V any] struct { + list *list[K] + items map[K]cacheItem[K, V] + cap int +} + +type cacheItem[K any, V any] struct { + elem *listElem[K] + value V +} + +// NewBasicLRU creates a new LRU cache. +func NewBasicLRU[K comparable, V any](capacity int) BasicLRU[K, V] { + if capacity <= 0 { + capacity = 1 + } + c := BasicLRU[K, V]{ + items: make(map[K]cacheItem[K, V]), + list: newList[K](), + cap: capacity, + } + return c +} + +// Add adds a value to the cache. Returns true if an item was evicted to store the new item. +func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) { + item, ok := c.items[key] + if ok { + // Already exists in cache. + item.value = value + c.items[key] = item + c.list.moveToFront(item.elem) + return false + } + + var elem *listElem[K] + if c.Len() >= c.cap { + elem = c.list.removeLast() + delete(c.items, elem.v) + evicted = true + } else { + elem = new(listElem[K]) + } + + // Store the new item. + // Note that, if another item was evicted, we re-use its list element here. + elem.v = key + c.items[key] = cacheItem[K, V]{elem, value} + c.list.pushElem(elem) + return evicted +} + +// Contains reports whether the given key exists in the cache. +func (c *BasicLRU[K, V]) Contains(key K) bool { + _, ok := c.items[key] + return ok +} + +// Get retrieves a value from the cache. This marks the key as recently used. +func (c *BasicLRU[K, V]) Get(key K) (value V, ok bool) { + item, ok := c.items[key] + if !ok { + return value, false + } + c.list.moveToFront(item.elem) + return item.value, true +} + +// GetOldest retrieves the least-recently-used item. +// Note that this does not update the item's recency. +func (c *BasicLRU[K, V]) GetOldest() (key K, value V, ok bool) { + lastElem := c.list.last() + if lastElem == nil { + return key, value, false + } + key = lastElem.v + item := c.items[key] + return key, item.value, true +} + +// Len returns the current number of items in the cache. +func (c *BasicLRU[K, V]) Len() int { + return len(c.items) +} + +// Peek retrieves a value from the cache, but does not mark the key as recently used. +func (c *BasicLRU[K, V]) Peek(key K) (value V, ok bool) { + item, ok := c.items[key] + return item.value, ok +} + +// Purge empties the cache. +func (c *BasicLRU[K, V]) Purge() { + c.list.init() + clear(c.items) +} + +// Remove drops an item from the cache. Returns true if the key was present in cache. +func (c *BasicLRU[K, V]) Remove(key K) bool { + item, ok := c.items[key] + if ok { + delete(c.items, key) + c.list.remove(item.elem) + } + return ok +} + +// RemoveOldest drops the least recently used item. +func (c *BasicLRU[K, V]) RemoveOldest() (key K, value V, ok bool) { + lastElem := c.list.last() + if lastElem == nil { + return key, value, false + } + + key = lastElem.v + item := c.items[key] + delete(c.items, key) + c.list.remove(lastElem) + return key, item.value, true +} + +// Keys returns all keys in the cache. +func (c *BasicLRU[K, V]) Keys() []K { + keys := make([]K, 0, len(c.items)) + return c.list.appendTo(keys) +} + +// list is a doubly-linked list holding items of type he. +// The zero value is not valid, use newList to create lists. +type list[T any] struct { + root listElem[T] +} + +type listElem[T any] struct { + next *listElem[T] + prev *listElem[T] + v T +} + +func newList[T any]() *list[T] { + l := new(list[T]) + l.init() + return l +} + +// init reinitializes the list, making it empty. +func (l *list[T]) init() { + l.root.next = &l.root + l.root.prev = &l.root +} + +// pushElem adds an element to the front of the list. +func (l *list[T]) pushElem(e *listElem[T]) { + e.prev = &l.root + e.next = l.root.next + l.root.next = e + e.next.prev = e +} + +// moveToFront makes 'node' the head of the list. +func (l *list[T]) moveToFront(e *listElem[T]) { + e.prev.next = e.next + e.next.prev = e.prev + l.pushElem(e) +} + +// remove removes an element from the list. +func (l *list[T]) remove(e *listElem[T]) { + e.prev.next = e.next + e.next.prev = e.prev + e.next, e.prev = nil, nil +} + +// removeLast removes the last element of the list. +func (l *list[T]) removeLast() *listElem[T] { + last := l.last() + if last != nil { + l.remove(last) + } + return last +} + +// last returns the last element of the list, or nil if the list is empty. +func (l *list[T]) last() *listElem[T] { + e := l.root.prev + if e == &l.root { + return nil + } + return e +} + +// appendTo appends all list elements to a slice. +func (l *list[T]) appendTo(slice []T) []T { + for e := l.root.prev; e != &l.root; e = e.prev { + slice = append(slice, e.v) + } + return slice +} diff --git a/common/lru/basiclru_test.go b/common/lru/basiclru_test.go new file mode 100644 index 0000000..29812bd --- /dev/null +++ b/common/lru/basiclru_test.go @@ -0,0 +1,255 @@ +// 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 lru + +import ( + crand "crypto/rand" + "fmt" + "io" + "math/rand" + "testing" +) + +// Some of these test cases were adapted +// from https://github.com/hashicorp/golang-lru/blob/master/simplelru/lru_test.go + +func TestBasicLRU(t *testing.T) { + cache := NewBasicLRU[int, int](128) + + for i := 0; i < 256; i++ { + cache.Add(i, i) + } + if cache.Len() != 128 { + t.Fatalf("bad len: %v", cache.Len()) + } + + // Check that Keys returns least-recent key first. + keys := cache.Keys() + if len(keys) != 128 { + t.Fatal("wrong Keys() length", len(keys)) + } + for i, k := range keys { + v, ok := cache.Peek(k) + if !ok { + t.Fatalf("expected key %d be present", i) + } + if v != k { + t.Fatalf("expected %d == %d", k, v) + } + if v != i+128 { + t.Fatalf("wrong value at key %d: %d, want %d", i, v, i+128) + } + } + + for i := 0; i < 128; i++ { + _, ok := cache.Get(i) + if ok { + t.Fatalf("%d should be evicted", i) + } + } + for i := 128; i < 256; i++ { + _, ok := cache.Get(i) + if !ok { + t.Fatalf("%d should not be evicted", i) + } + } + + for i := 128; i < 192; i++ { + ok := cache.Remove(i) + if !ok { + t.Fatalf("%d should be in cache", i) + } + ok = cache.Remove(i) + if ok { + t.Fatalf("%d should not be in cache", i) + } + _, ok = cache.Get(i) + if ok { + t.Fatalf("%d should be deleted", i) + } + } + + // Request item 192. + cache.Get(192) + // It should be the last item returned by Keys(). + for i, k := range cache.Keys() { + if (i < 63 && k != i+193) || (i == 63 && k != 192) { + t.Fatalf("out of order key: %v", k) + } + } + + cache.Purge() + if cache.Len() != 0 { + t.Fatalf("bad len: %v", cache.Len()) + } + if _, ok := cache.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +func TestBasicLRUAddExistingKey(t *testing.T) { + cache := NewBasicLRU[int, int](1) + + cache.Add(1, 1) + cache.Add(1, 2) + + v, _ := cache.Get(1) + if v != 2 { + t.Fatal("wrong value:", v) + } +} + +// This test checks GetOldest and RemoveOldest. +func TestBasicLRUGetOldest(t *testing.T) { + cache := NewBasicLRU[int, int](128) + for i := 0; i < 256; i++ { + cache.Add(i, i) + } + + k, _, ok := cache.GetOldest() + if !ok { + t.Fatalf("missing") + } + if k != 128 { + t.Fatalf("bad: %v", k) + } + + k, _, ok = cache.RemoveOldest() + if !ok { + t.Fatalf("missing") + } + if k != 128 { + t.Fatalf("bad: %v", k) + } + + k, _, ok = cache.RemoveOldest() + if !ok { + t.Fatalf("missing oldest item") + } + if k != 129 { + t.Fatalf("wrong oldest item: %v", k) + } +} + +// Test that Add returns true/false if an eviction occurred +func TestBasicLRUAddReturnValue(t *testing.T) { + cache := NewBasicLRU[int, int](1) + if cache.Add(1, 1) { + t.Errorf("first add shouldn't have evicted") + } + if !cache.Add(2, 2) { + t.Errorf("second add should have evicted") + } +} + +// This test verifies that Contains doesn't change item recency. +func TestBasicLRUContains(t *testing.T) { + cache := NewBasicLRU[int, int](2) + cache.Add(1, 1) + cache.Add(2, 2) + if !cache.Contains(1) { + t.Errorf("1 should be in the cache") + } + cache.Add(3, 3) + if cache.Contains(1) { + t.Errorf("Contains should not have updated recency of 1") + } +} + +// Test that Peek doesn't update recent-ness +func TestBasicLRUPeek(t *testing.T) { + cache := NewBasicLRU[int, int](2) + cache.Add(1, 1) + cache.Add(2, 2) + if v, ok := cache.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1") + } + cache.Add(3, 3) + if cache.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} + +func BenchmarkLRU(b *testing.B) { + var ( + capacity = 1000 + indexes = make([]int, capacity*20) + keys = make([]string, capacity) + values = make([][]byte, capacity) + ) + for i := range indexes { + indexes[i] = rand.Intn(capacity) + } + for i := range keys { + b := make([]byte, 32) + crand.Read(b) + keys[i] = string(b) + crand.Read(b) + values[i] = b + } + + var sink []byte + + b.Run("Add/BasicLRU", func(b *testing.B) { + cache := NewBasicLRU[int, int](capacity) + for i := 0; i < b.N; i++ { + cache.Add(i, i) + } + }) + b.Run("Get/BasicLRU", func(b *testing.B) { + cache := NewBasicLRU[string, []byte](capacity) + for i := 0; i < capacity; i++ { + index := indexes[i] + cache.Add(keys[index], values[index]) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + k := keys[indexes[i%len(indexes)]] + v, ok := cache.Get(k) + if ok { + sink = v + } + } + }) + + // // vs. github.com/hashicorp/golang-lru/simplelru + // b.Run("Add/simplelru.LRU", func(b *testing.B) { + // cache, _ := simplelru.NewLRU(capacity, nil) + // for i := 0; i < b.N; i++ { + // cache.Add(i, i) + // } + // }) + // b.Run("Get/simplelru.LRU", func(b *testing.B) { + // cache, _ := simplelru.NewLRU(capacity, nil) + // for i := 0; i < capacity; i++ { + // index := indexes[i] + // cache.Add(keys[index], values[index]) + // } + // + // b.ResetTimer() + // for i := 0; i < b.N; i++ { + // k := keys[indexes[i%len(indexes)]] + // v, ok := cache.Get(k) + // if ok { + // sink = v.([]byte) + // } + // } + // }) + + fmt.Fprintln(io.Discard, sink) +} diff --git a/common/lru/blob_lru.go b/common/lru/blob_lru.go new file mode 100644 index 0000000..c9b3398 --- /dev/null +++ b/common/lru/blob_lru.go @@ -0,0 +1,84 @@ +// 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 lru + +import ( + "math" + "sync" +) + +// blobType is the type constraint for values stored in SizeConstrainedCache. +type blobType interface { + ~[]byte | ~string +} + +// SizeConstrainedCache is a cache where capacity is in bytes (instead of item count). When the cache +// is at capacity, and a new item is added, older items are evicted until the size +// constraint is met. +// +// OBS: This cache assumes that items are content-addressed: keys are unique per content. +// In other words: two Add(..) with the same key K, will always have the same value V. +type SizeConstrainedCache[K comparable, V blobType] struct { + size uint64 + maxSize uint64 + lru BasicLRU[K, V] + lock sync.Mutex +} + +// NewSizeConstrainedCache creates a new size-constrained LRU cache. +func NewSizeConstrainedCache[K comparable, V blobType](maxSize uint64) *SizeConstrainedCache[K, V] { + return &SizeConstrainedCache[K, V]{ + size: 0, + maxSize: maxSize, + lru: NewBasicLRU[K, V](math.MaxInt), + } +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +// OBS: This cache assumes that items are content-addressed: keys are unique per content. +// In other words: two Add(..) with the same key K, will always have the same value V. +// OBS: The value is _not_ copied on Add, so the caller must not modify it afterwards. +func (c *SizeConstrainedCache[K, V]) Add(key K, value V) (evicted bool) { + c.lock.Lock() + defer c.lock.Unlock() + + // Unless it is already present, might need to evict something. + // OBS: If it is present, we still call Add internally to bump the recentness. + if !c.lru.Contains(key) { + targetSize := c.size + uint64(len(value)) + for targetSize > c.maxSize { + evicted = true + _, v, ok := c.lru.RemoveOldest() + if !ok { + // list is now empty. Break + break + } + targetSize -= uint64(len(v)) + } + c.size = targetSize + } + c.lru.Add(key, value) + return evicted +} + +// Get looks up a key's value from the cache. +func (c *SizeConstrainedCache[K, V]) Get(key K) (V, bool) { + c.lock.Lock() + defer c.lock.Unlock() + + return c.lru.Get(key) +} diff --git a/common/lru/blob_lru_test.go b/common/lru/blob_lru_test.go new file mode 100644 index 0000000..ca1b0dd --- /dev/null +++ b/common/lru/blob_lru_test.go @@ -0,0 +1,155 @@ +// 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 lru + +import ( + "encoding/binary" + "fmt" + "testing" +) + +type testKey [8]byte + +func mkKey(i int) (key testKey) { + binary.LittleEndian.PutUint64(key[:], uint64(i)) + return key +} + +func TestSizeConstrainedCache(t *testing.T) { + lru := NewSizeConstrainedCache[testKey, []byte](100) + var want uint64 + // Add 11 items of 10 byte each. First item should be swapped out + for i := 0; i < 11; i++ { + k := mkKey(i) + v := fmt.Sprintf("value-%04d", i) + lru.Add(k, []byte(v)) + want += uint64(len(v)) + if want > 100 { + want = 100 + } + if have := lru.size; have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } + } + // Zero:th should be evicted + { + k := mkKey(0) + if _, ok := lru.Get(k); ok { + t.Fatalf("should be evicted: %v", k) + } + } + // Elems 1-11 should be present + for i := 1; i < 11; i++ { + k := mkKey(i) + want := fmt.Sprintf("value-%04d", i) + have, ok := lru.Get(k) + if !ok { + t.Fatalf("missing key %v", k) + } + if string(have) != want { + t.Fatalf("wrong value, have %v want %v", have, want) + } + } +} + +// This test adds inserting an element exceeding the max size. +func TestSizeConstrainedCacheOverflow(t *testing.T) { + lru := NewSizeConstrainedCache[testKey, []byte](100) + + // Add 10 items of 10 byte each, filling the cache + for i := 0; i < 10; i++ { + k := mkKey(i) + v := fmt.Sprintf("value-%04d", i) + lru.Add(k, []byte(v)) + } + // Add one single large elem. We expect it to swap out all entries. + { + k := mkKey(1337) + v := make([]byte, 200) + lru.Add(k, v) + } + // Elems 0-9 should be missing + for i := 1; i < 10; i++ { + k := mkKey(i) + if _, ok := lru.Get(k); ok { + t.Fatalf("should be evicted: %v", k) + } + } + // The size should be accurate + if have, want := lru.size, uint64(200); have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } + // Adding one small item should swap out the large one + { + i := 0 + k := mkKey(i) + v := fmt.Sprintf("value-%04d", i) + lru.Add(k, []byte(v)) + if have, want := lru.size, uint64(10); have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } + } +} + +// This checks what happens when inserting the same k/v multiple times. +func TestSizeConstrainedCacheSameItem(t *testing.T) { + lru := NewSizeConstrainedCache[testKey, []byte](100) + + // Add one 10 byte-item 10 times. + k := mkKey(0) + v := fmt.Sprintf("value-%04d", 0) + for i := 0; i < 10; i++ { + lru.Add(k, []byte(v)) + } + + // The size should be accurate. + if have, want := lru.size, uint64(10); have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } +} + +// This tests that empty/nil values are handled correctly. +func TestSizeConstrainedCacheEmpties(t *testing.T) { + lru := NewSizeConstrainedCache[testKey, []byte](100) + + // This test abuses the lru a bit, using different keys for identical value(s). + for i := 0; i < 10; i++ { + lru.Add(testKey{byte(i)}, []byte{}) + lru.Add(testKey{byte(255 - i)}, nil) + } + + // The size should not count, only the values count. So this could be a DoS + // since it basically has no cap, and it is intentionally overloaded with + // different-keyed 0-length values. + if have, want := lru.size, uint64(0); have != want { + t.Fatalf("size wrong, have %d want %d", have, want) + } + + for i := 0; i < 10; i++ { + if v, ok := lru.Get(testKey{byte(i)}); !ok { + t.Fatalf("test %d: expected presence", i) + } else if v == nil { + t.Fatalf("test %d, v is nil", i) + } + + if v, ok := lru.Get(testKey{byte(255 - i)}); !ok { + t.Fatalf("test %d: expected presence", i) + } else if v != nil { + t.Fatalf("test %d, v is not nil", i) + } + } +} diff --git a/common/lru/lru.go b/common/lru/lru.go new file mode 100644 index 0000000..45965ad --- /dev/null +++ b/common/lru/lru.go @@ -0,0 +1,95 @@ +// 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 lru + +import "sync" + +// Cache is a LRU cache. +// This type is safe for concurrent use. +type Cache[K comparable, V any] struct { + cache BasicLRU[K, V] + mu sync.Mutex +} + +// NewCache creates an LRU cache. +func NewCache[K comparable, V any](capacity int) *Cache[K, V] { + return &Cache[K, V]{cache: NewBasicLRU[K, V](capacity)} +} + +// Add adds a value to the cache. Returns true if an item was evicted to store the new item. +func (c *Cache[K, V]) Add(key K, value V) (evicted bool) { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Add(key, value) +} + +// Contains reports whether the given key exists in the cache. +func (c *Cache[K, V]) Contains(key K) bool { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Contains(key) +} + +// Get retrieves a value from the cache. This marks the key as recently used. +func (c *Cache[K, V]) Get(key K) (value V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Get(key) +} + +// Len returns the current number of items in the cache. +func (c *Cache[K, V]) Len() int { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Len() +} + +// Peek retrieves a value from the cache, but does not mark the key as recently used. +func (c *Cache[K, V]) Peek(key K) (value V, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Peek(key) +} + +// Purge empties the cache. +func (c *Cache[K, V]) Purge() { + c.mu.Lock() + defer c.mu.Unlock() + + c.cache.Purge() +} + +// Remove drops an item from the cache. Returns true if the key was present in cache. +func (c *Cache[K, V]) Remove(key K) bool { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Remove(key) +} + +// Keys returns all keys of items currently in the LRU. +func (c *Cache[K, V]) Keys() []K { + c.mu.Lock() + defer c.mu.Unlock() + + return c.cache.Keys() +} diff --git a/common/math/big.go b/common/math/big.go new file mode 100644 index 0000000..d9748d0 --- /dev/null +++ b/common/math/big.go @@ -0,0 +1,271 @@ +// 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 math provides integer math utilities. +package math + +import ( + "fmt" + "math/big" +) + +// Various big integer limit values. +var ( + tt255 = BigPow(2, 255) + tt256 = BigPow(2, 256) + tt256m1 = new(big.Int).Sub(tt256, big.NewInt(1)) + tt63 = BigPow(2, 63) + MaxBig256 = new(big.Int).Set(tt256m1) + MaxBig63 = new(big.Int).Sub(tt63, big.NewInt(1)) +) + +const ( + // number of bits in a big.Word + wordBits = 32 << (uint64(^big.Word(0)) >> 63) + // number of bytes in a big.Word + wordBytes = wordBits / 8 +) + +// HexOrDecimal256 marshals big.Int as hex or decimal. +type HexOrDecimal256 big.Int + +// NewHexOrDecimal256 creates a new HexOrDecimal256 +func NewHexOrDecimal256(x int64) *HexOrDecimal256 { + b := big.NewInt(x) + h := HexOrDecimal256(*b) + return &h +} + +// UnmarshalJSON implements json.Unmarshaler. +// +// It is similar to UnmarshalText, but allows parsing real decimals too, not just +// quoted decimal strings. +func (i *HexOrDecimal256) UnmarshalJSON(input []byte) error { + if len(input) > 1 && input[0] == '"' { + input = input[1 : len(input)-1] + } + return i.UnmarshalText(input) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *HexOrDecimal256) UnmarshalText(input []byte) error { + bigint, ok := ParseBig256(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = HexOrDecimal256(*bigint) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i *HexOrDecimal256) MarshalText() ([]byte, error) { + if i == nil { + return []byte("0x0"), nil + } + return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil +} + +// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling, +// it however accepts either "0x"-prefixed (hex encoded) or non-prefixed (decimal) +type Decimal256 big.Int + +// NewDecimal256 creates a new Decimal256 +func NewDecimal256(x int64) *Decimal256 { + b := big.NewInt(x) + d := Decimal256(*b) + return &d +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *Decimal256) UnmarshalText(input []byte) error { + bigint, ok := ParseBig256(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = Decimal256(*bigint) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i *Decimal256) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// String implements Stringer. +func (i *Decimal256) String() string { + if i == nil { + return "0" + } + return fmt.Sprintf("%#d", (*big.Int)(i)) +} + +// ParseBig256 parses s as a 256 bit integer in decimal or hexadecimal syntax. +// Leading zeros are accepted. The empty string parses as zero. +func ParseBig256(s string) (*big.Int, bool) { + if s == "" { + return new(big.Int), true + } + var bigint *big.Int + var ok bool + if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { + bigint, ok = new(big.Int).SetString(s[2:], 16) + } else { + bigint, ok = new(big.Int).SetString(s, 10) + } + if ok && bigint.BitLen() > 256 { + bigint, ok = nil, false + } + return bigint, ok +} + +// MustParseBig256 parses s as a 256 bit big integer and panics if the string is invalid. +func MustParseBig256(s string) *big.Int { + v, ok := ParseBig256(s) + if !ok { + panic("invalid 256 bit integer: " + s) + } + return v +} + +// BigPow returns a ** b as a big integer. +func BigPow(a, b int64) *big.Int { + r := big.NewInt(a) + return r.Exp(r, big.NewInt(b), nil) +} + +// BigMax returns the larger of x or y. +func BigMax(x, y *big.Int) *big.Int { + if x.Cmp(y) < 0 { + return y + } + return x +} + +// BigMin returns the smaller of x or y. +func BigMin(x, y *big.Int) *big.Int { + if x.Cmp(y) > 0 { + return y + } + return x +} + +// FirstBitSet returns the index of the first 1 bit in v, counting from LSB. +func FirstBitSet(v *big.Int) int { + for i := 0; i < v.BitLen(); i++ { + if v.Bit(i) > 0 { + return i + } + } + return v.BitLen() +} + +// PaddedBigBytes encodes a big integer as a big-endian byte slice. The length +// of the slice is at least n bytes. +func PaddedBigBytes(bigint *big.Int, n int) []byte { + if bigint.BitLen()/8 >= n { + return bigint.Bytes() + } + ret := make([]byte, n) + ReadBits(bigint, ret) + return ret +} + +// bigEndianByteAt returns the byte at position n, +// in Big-Endian encoding +// So n==0 returns the least significant byte +func bigEndianByteAt(bigint *big.Int, n int) byte { + words := bigint.Bits() + // Check word-bucket the byte will reside in + i := n / wordBytes + if i >= len(words) { + return byte(0) + } + word := words[i] + // Offset of the byte + shift := 8 * uint(n%wordBytes) + + return byte(word >> shift) +} + +// Byte returns the byte at position n, +// with the supplied padlength in Little-Endian encoding. +// n==0 returns the MSB +// Example: bigint '5', padlength 32, n=31 => 5 +func Byte(bigint *big.Int, padlength, n int) byte { + if n >= padlength { + return byte(0) + } + return bigEndianByteAt(bigint, padlength-1-n) +} + +// ReadBits encodes the absolute value of bigint as big-endian bytes. Callers must ensure +// that buf has enough space. If buf is too short the result will be incomplete. +func ReadBits(bigint *big.Int, buf []byte) { + i := len(buf) + for _, d := range bigint.Bits() { + for j := 0; j < wordBytes && i > 0; j++ { + i-- + buf[i] = byte(d) + d >>= 8 + } + } +} + +// U256 encodes x as a 256 bit two's complement number. This operation is destructive. +func U256(x *big.Int) *big.Int { + return x.And(x, tt256m1) +} + +// U256Bytes converts a big Int into a 256bit EVM number. +// This operation is destructive. +func U256Bytes(n *big.Int) []byte { + return PaddedBigBytes(U256(n), 32) +} + +// S256 interprets x as a two's complement number. +// x must not exceed 256 bits (the result is undefined if it does) and is not modified. +// +// S256(0) = 0 +// S256(1) = 1 +// S256(2**255) = -2**255 +// S256(2**256-1) = -1 +func S256(x *big.Int) *big.Int { + if x.Cmp(tt255) < 0 { + return x + } + return new(big.Int).Sub(x, tt256) +} + +// Exp implements exponentiation by squaring. +// Exp returns a newly-allocated big integer and does not change +// base or exponent. The result is truncated to 256 bits. +// +// Courtesy @karalabe and @chfast +func Exp(base, exponent *big.Int) *big.Int { + copyBase := new(big.Int).Set(base) + result := big.NewInt(1) + + for _, word := range exponent.Bits() { + for i := 0; i < wordBits; i++ { + if word&1 == 1 { + U256(result.Mul(result, copyBase)) + } + U256(copyBase.Mul(copyBase, copyBase)) + word >>= 1 + } + } + return result +} diff --git a/common/math/big_test.go b/common/math/big_test.go new file mode 100644 index 0000000..ee8f09e --- /dev/null +++ b/common/math/big_test.go @@ -0,0 +1,324 @@ +// 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 math + +import ( + "bytes" + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestHexOrDecimal256(t *testing.T) { + tests := []struct { + input string + num *big.Int + ok bool + }{ + {"", big.NewInt(0), true}, + {"0", big.NewInt(0), true}, + {"0x0", big.NewInt(0), true}, + {"12345678", big.NewInt(12345678), true}, + {"0x12345678", big.NewInt(0x12345678), true}, + {"0X12345678", big.NewInt(0x12345678), true}, + // Tests for leading zero behaviour: + {"0123456789", big.NewInt(123456789), true}, // note: not octal + {"00", big.NewInt(0), true}, + {"0x00", big.NewInt(0), true}, + {"0x012345678abc", big.NewInt(0x12345678abc), true}, + // Invalid syntax: + {"abcdef", nil, false}, + {"0xgg", nil, false}, + // Larger than 256 bits: + {"115792089237316195423570985008687907853269984665640564039457584007913129639936", nil, false}, + } + for _, test := range tests { + var num HexOrDecimal256 + err := num.UnmarshalText([]byte(test.input)) + if (err == nil) != test.ok { + t.Errorf("ParseBig(%q) -> (err == nil) == %t, want %t", test.input, err == nil, test.ok) + continue + } + if test.num != nil && (*big.Int)(&num).Cmp(test.num) != 0 { + t.Errorf("ParseBig(%q) -> %d, want %d", test.input, (*big.Int)(&num), test.num) + } + } +} + +func TestMustParseBig256(t *testing.T) { + defer func() { + if recover() == nil { + t.Error("MustParseBig should've panicked") + } + }() + MustParseBig256("ggg") +} + +func TestBigMax(t *testing.T) { + a := big.NewInt(10) + b := big.NewInt(5) + + max1 := BigMax(a, b) + if max1 != a { + t.Errorf("Expected %d got %d", a, max1) + } + + max2 := BigMax(b, a) + if max2 != a { + t.Errorf("Expected %d got %d", a, max2) + } +} + +func TestBigMin(t *testing.T) { + a := big.NewInt(10) + b := big.NewInt(5) + + min1 := BigMin(a, b) + if min1 != b { + t.Errorf("Expected %d got %d", b, min1) + } + + min2 := BigMin(b, a) + if min2 != b { + t.Errorf("Expected %d got %d", b, min2) + } +} + +func TestFirstBigSet(t *testing.T) { + tests := []struct { + num *big.Int + ix int + }{ + {big.NewInt(0), 0}, + {big.NewInt(1), 0}, + {big.NewInt(2), 1}, + {big.NewInt(0x100), 8}, + } + for _, test := range tests { + if ix := FirstBitSet(test.num); ix != test.ix { + t.Errorf("FirstBitSet(b%b) = %d, want %d", test.num, ix, test.ix) + } + } +} + +func TestPaddedBigBytes(t *testing.T) { + tests := []struct { + num *big.Int + n int + result []byte + }{ + {num: big.NewInt(0), n: 4, result: []byte{0, 0, 0, 0}}, + {num: big.NewInt(1), n: 4, result: []byte{0, 0, 0, 1}}, + {num: big.NewInt(512), n: 4, result: []byte{0, 0, 2, 0}}, + {num: BigPow(2, 32), n: 4, result: []byte{1, 0, 0, 0, 0}}, + } + for _, test := range tests { + if result := PaddedBigBytes(test.num, test.n); !bytes.Equal(result, test.result) { + t.Errorf("PaddedBigBytes(%d, %d) = %v, want %v", test.num, test.n, result, test.result) + } + } +} + +func BenchmarkPaddedBigBytesLargePadding(b *testing.B) { + bigint := MustParseBig256("123456789123456789123456789123456789") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 200) + } +} + +func BenchmarkPaddedBigBytesSmallPadding(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 5) + } +} + +func BenchmarkPaddedBigBytesSmallOnePadding(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 32) + } +} + +func BenchmarkByteAtBrandNew(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + bigEndianByteAt(bigint, 15) + } +} + +func BenchmarkByteAt(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + bigEndianByteAt(bigint, 15) + } +} + +func BenchmarkByteAtOld(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 32) + } +} + +func TestReadBits(t *testing.T) { + check := func(input string) { + want, _ := hex.DecodeString(input) + n, _ := new(big.Int).SetString(input, 16) + buf := make([]byte, len(want)) + ReadBits(n, buf) + if !bytes.Equal(buf, want) { + t.Errorf("have: %x\nwant: %x", buf, want) + } + } + check("000000000000000000000000000000000000000000000000000000FEFCF3F8F0") + check("0000000000012345000000000000000000000000000000000000FEFCF3F8F0") + check("18F8F8F1000111000110011100222004330052300000000000000000FEFCF3F8F0") +} + +func TestU256(t *testing.T) { + tests := []struct{ x, y *big.Int }{ + {x: big.NewInt(0), y: big.NewInt(0)}, + {x: big.NewInt(1), y: big.NewInt(1)}, + {x: BigPow(2, 255), y: BigPow(2, 255)}, + {x: BigPow(2, 256), y: big.NewInt(0)}, + {x: new(big.Int).Add(BigPow(2, 256), big.NewInt(1)), y: big.NewInt(1)}, + // negative values + {x: big.NewInt(-1), y: new(big.Int).Sub(BigPow(2, 256), big.NewInt(1))}, + {x: big.NewInt(-2), y: new(big.Int).Sub(BigPow(2, 256), big.NewInt(2))}, + {x: BigPow(2, -255), y: big.NewInt(1)}, + } + for _, test := range tests { + if y := U256(new(big.Int).Set(test.x)); y.Cmp(test.y) != 0 { + t.Errorf("U256(%x) = %x, want %x", test.x, y, test.y) + } + } +} + +func TestU256Bytes(t *testing.T) { + ubytes := make([]byte, 32) + ubytes[31] = 1 + + unsigned := U256Bytes(big.NewInt(1)) + if !bytes.Equal(unsigned, ubytes) { + t.Errorf("expected %x got %x", ubytes, unsigned) + } +} + +func TestBigEndianByteAt(t *testing.T) { + tests := []struct { + x string + y int + exp byte + }{ + {"00", 0, 0x00}, + {"01", 1, 0x00}, + {"00", 1, 0x00}, + {"01", 0, 0x01}, + {"0000000000000000000000000000000000000000000000000000000000102030", 0, 0x30}, + {"0000000000000000000000000000000000000000000000000000000000102030", 1, 0x20}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 31, 0xAB}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 32, 0x00}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 500, 0x00}, + } + for _, test := range tests { + v := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) + actual := bigEndianByteAt(v, test.y) + if actual != test.exp { + t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.x, test.y, test.exp, actual) + } + } +} +func TestLittleEndianByteAt(t *testing.T) { + tests := []struct { + x string + y int + exp byte + }{ + {"00", 0, 0x00}, + {"01", 1, 0x00}, + {"00", 1, 0x00}, + {"01", 0, 0x00}, + {"0000000000000000000000000000000000000000000000000000000000102030", 0, 0x00}, + {"0000000000000000000000000000000000000000000000000000000000102030", 1, 0x00}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 31, 0x00}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 32, 0x00}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 0, 0xAB}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 1, 0xCD}, + {"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", 0, 0x00}, + {"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", 1, 0xCD}, + {"0000000000000000000000000000000000000000000000000000000000102030", 31, 0x30}, + {"0000000000000000000000000000000000000000000000000000000000102030", 30, 0x20}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 32, 0x0}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 31, 0xFF}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0xFFFF, 0x0}, + } + for _, test := range tests { + v := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) + actual := Byte(v, 32, test.y) + if actual != test.exp { + t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.x, test.y, test.exp, actual) + } + } +} + +func TestS256(t *testing.T) { + tests := []struct{ x, y *big.Int }{ + {x: big.NewInt(0), y: big.NewInt(0)}, + {x: big.NewInt(1), y: big.NewInt(1)}, + {x: big.NewInt(2), y: big.NewInt(2)}, + { + x: new(big.Int).Sub(BigPow(2, 255), big.NewInt(1)), + y: new(big.Int).Sub(BigPow(2, 255), big.NewInt(1)), + }, + { + x: BigPow(2, 255), + y: new(big.Int).Neg(BigPow(2, 255)), + }, + { + x: new(big.Int).Sub(BigPow(2, 256), big.NewInt(1)), + y: big.NewInt(-1), + }, + { + x: new(big.Int).Sub(BigPow(2, 256), big.NewInt(2)), + y: big.NewInt(-2), + }, + } + for _, test := range tests { + if y := S256(test.x); y.Cmp(test.y) != 0 { + t.Errorf("S256(%x) = %x, want %x", test.x, y, test.y) + } + } +} + +func TestExp(t *testing.T) { + tests := []struct{ base, exponent, result *big.Int }{ + {base: big.NewInt(0), exponent: big.NewInt(0), result: big.NewInt(1)}, + {base: big.NewInt(1), exponent: big.NewInt(0), result: big.NewInt(1)}, + {base: big.NewInt(1), exponent: big.NewInt(1), result: big.NewInt(1)}, + {base: big.NewInt(1), exponent: big.NewInt(2), result: big.NewInt(1)}, + {base: big.NewInt(3), exponent: big.NewInt(144), result: MustParseBig256("507528786056415600719754159741696356908742250191663887263627442114881")}, + {base: big.NewInt(2), exponent: big.NewInt(255), result: MustParseBig256("57896044618658097711785492504343953926634992332820282019728792003956564819968")}, + } + for _, test := range tests { + if result := Exp(test.base, test.exponent); result.Cmp(test.result) != 0 { + t.Errorf("Exp(%d, %d) = %d, want %d", test.base, test.exponent, result, test.result) + } + } +} diff --git a/common/math/integer.go b/common/math/integer.go new file mode 100644 index 0000000..82de96f --- /dev/null +++ b/common/math/integer.go @@ -0,0 +1,109 @@ +// 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 math + +import ( + "fmt" + "math/bits" + "strconv" +) + +// Integer limit values. +const ( + MaxInt8 = 1<<7 - 1 + MinInt8 = -1 << 7 + MaxInt16 = 1<<15 - 1 + MinInt16 = -1 << 15 + MaxInt32 = 1<<31 - 1 + MinInt32 = -1 << 31 + MaxInt64 = 1<<63 - 1 + MinInt64 = -1 << 63 + MaxUint8 = 1<<8 - 1 + MaxUint16 = 1<<16 - 1 + MaxUint32 = 1<<32 - 1 + MaxUint64 = 1<<64 - 1 +) + +// HexOrDecimal64 marshals uint64 as hex or decimal. +type HexOrDecimal64 uint64 + +// UnmarshalJSON implements json.Unmarshaler. +// +// It is similar to UnmarshalText, but allows parsing real decimals too, not just +// quoted decimal strings. +func (i *HexOrDecimal64) UnmarshalJSON(input []byte) error { + if len(input) > 1 && input[0] == '"' { + input = input[1 : len(input)-1] + } + return i.UnmarshalText(input) +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *HexOrDecimal64) UnmarshalText(input []byte) error { + n, ok := ParseUint64(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = HexOrDecimal64(n) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i HexOrDecimal64) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("%#x", uint64(i))), nil +} + +// ParseUint64 parses s as an integer in decimal or hexadecimal syntax. +// Leading zeros are accepted. The empty string parses as zero. +func ParseUint64(s string) (uint64, bool) { + if s == "" { + return 0, true + } + if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { + v, err := strconv.ParseUint(s[2:], 16, 64) + return v, err == nil + } + v, err := strconv.ParseUint(s, 10, 64) + return v, err == nil +} + +// MustParseUint64 parses s as an integer and panics if the string is invalid. +func MustParseUint64(s string) uint64 { + v, ok := ParseUint64(s) + if !ok { + panic("invalid unsigned 64 bit integer: " + s) + } + return v +} + +// SafeSub returns x-y and checks for overflow. +func SafeSub(x, y uint64) (uint64, bool) { + diff, borrowOut := bits.Sub64(x, y, 0) + return diff, borrowOut != 0 +} + +// SafeAdd returns x+y and checks for overflow. +func SafeAdd(x, y uint64) (uint64, bool) { + sum, carryOut := bits.Add64(x, y, 0) + return sum, carryOut != 0 +} + +// SafeMul returns x*y and checks for overflow. +func SafeMul(x, y uint64) (uint64, bool) { + hi, lo := bits.Mul64(x, y) + return lo, hi != 0 +} diff --git a/common/math/integer_test.go b/common/math/integer_test.go new file mode 100644 index 0000000..b31c7c2 --- /dev/null +++ b/common/math/integer_test.go @@ -0,0 +1,116 @@ +// 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 math + +import ( + "testing" +) + +type operation byte + +const ( + sub operation = iota + add + mul +) + +func TestOverflow(t *testing.T) { + for i, test := range []struct { + x uint64 + y uint64 + overflow bool + op operation + }{ + // add operations + {MaxUint64, 1, true, add}, + {MaxUint64 - 1, 1, false, add}, + + // sub operations + {0, 1, true, sub}, + {0, 0, false, sub}, + + // mul operations + {0, 0, false, mul}, + {10, 10, false, mul}, + {MaxUint64, 2, true, mul}, + {MaxUint64, 1, false, mul}, + } { + var overflows bool + switch test.op { + case sub: + _, overflows = SafeSub(test.x, test.y) + case add: + _, overflows = SafeAdd(test.x, test.y) + case mul: + _, overflows = SafeMul(test.x, test.y) + } + + if test.overflow != overflows { + t.Errorf("%d failed. Expected test to be %v, got %v", i, test.overflow, overflows) + } + } +} + +func TestHexOrDecimal64(t *testing.T) { + tests := []struct { + input string + num uint64 + ok bool + }{ + {"", 0, true}, + {"0", 0, true}, + {"0x0", 0, true}, + {"12345678", 12345678, true}, + {"0x12345678", 0x12345678, true}, + {"0X12345678", 0x12345678, true}, + // Tests for leading zero behaviour: + {"0123456789", 123456789, true}, // note: not octal + {"0x00", 0, true}, + {"0x012345678abc", 0x12345678abc, true}, + // Invalid syntax: + {"abcdef", 0, false}, + {"0xgg", 0, false}, + // Doesn't fit into 64 bits: + {"18446744073709551617", 0, false}, + } + for _, test := range tests { + var num HexOrDecimal64 + err := num.UnmarshalText([]byte(test.input)) + if (err == nil) != test.ok { + t.Errorf("ParseUint64(%q) -> (err == nil) = %t, want %t", test.input, err == nil, test.ok) + continue + } + if err == nil && uint64(num) != test.num { + t.Errorf("ParseUint64(%q) -> %d, want %d", test.input, num, test.num) + } + } +} + +func TestMustParseUint64(t *testing.T) { + if v := MustParseUint64("12345"); v != 12345 { + t.Errorf(`MustParseUint64("12345") = %d, want 12345`, v) + } +} + +func TestMustParseUint64Panic(t *testing.T) { + defer func() { + if recover() == nil { + t.Error("MustParseBig should've panicked") + } + }() + MustParseUint64("ggg") +} diff --git a/common/mclock/alarm.go b/common/mclock/alarm.go new file mode 100644 index 0000000..e83810a --- /dev/null +++ b/common/mclock/alarm.go @@ -0,0 +1,106 @@ +// 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 mclock + +import ( + "time" +) + +// Alarm sends timed notifications on a channel. This is very similar to a regular timer, +// but is easier to use in code that needs to re-schedule the same timer over and over. +// +// When scheduling an Alarm, the channel returned by C() will receive a value no later +// than the scheduled time. An Alarm can be reused after it has fired and can also be +// canceled by calling Stop. +type Alarm struct { + ch chan struct{} + clock Clock + timer Timer + deadline AbsTime +} + +// NewAlarm creates an Alarm. +func NewAlarm(clock Clock) *Alarm { + if clock == nil { + panic("nil clock") + } + return &Alarm{ + ch: make(chan struct{}, 1), + clock: clock, + } +} + +// C returns the alarm notification channel. This channel remains identical for +// the entire lifetime of the alarm, and is never closed. +func (e *Alarm) C() <-chan struct{} { + return e.ch +} + +// Stop cancels the alarm and drains the channel. +// This method is not safe for concurrent use. +func (e *Alarm) Stop() { + // Clear timer. + if e.timer != nil { + e.timer.Stop() + } + e.deadline = 0 + + // Drain the channel. + select { + case <-e.ch: + default: + } +} + +// Schedule sets the alarm to fire no later than the given time. If the alarm was already +// scheduled but has not fired yet, it may fire earlier than the newly-scheduled time. +func (e *Alarm) Schedule(time AbsTime) { + now := e.clock.Now() + e.schedule(now, time) +} + +func (e *Alarm) schedule(now, newDeadline AbsTime) { + if e.timer != nil { + if e.deadline > now && e.deadline <= newDeadline { + // Here, the current timer can be reused because it is already scheduled to + // occur earlier than the new deadline. + // + // The e.deadline > now part of the condition is important. If the old + // deadline lies in the past, we assume the timer has already fired and needs + // to be rescheduled. + return + } + e.timer.Stop() + } + + // Set the timer. + d := time.Duration(0) + if newDeadline < now { + newDeadline = now + } else { + d = newDeadline.Sub(now) + } + e.timer = e.clock.AfterFunc(d, e.send) + e.deadline = newDeadline +} + +func (e *Alarm) send() { + select { + case e.ch <- struct{}{}: + default: + } +} diff --git a/common/mclock/alarm_test.go b/common/mclock/alarm_test.go new file mode 100644 index 0000000..d2ad991 --- /dev/null +++ b/common/mclock/alarm_test.go @@ -0,0 +1,116 @@ +// 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 mclock + +import "testing" + +// This test checks basic functionality of Alarm. +func TestAlarm(t *testing.T) { + clk := new(Simulated) + clk.Run(20) + a := NewAlarm(clk) + + a.Schedule(clk.Now() + 10) + if recv(a.C()) { + t.Fatal("Alarm fired before scheduled deadline") + } + if ntimers := clk.ActiveTimers(); ntimers != 1 { + t.Fatal("clock has", ntimers, "active timers, want", 1) + } + clk.Run(5) + if recv(a.C()) { + t.Fatal("Alarm fired too early") + } + + clk.Run(5) + if !recv(a.C()) { + t.Fatal("Alarm did not fire") + } + if recv(a.C()) { + t.Fatal("Alarm fired twice") + } + if ntimers := clk.ActiveTimers(); ntimers != 0 { + t.Fatal("clock has", ntimers, "active timers, want", 0) + } + + a.Schedule(clk.Now() + 5) + if recv(a.C()) { + t.Fatal("Alarm fired before scheduled deadline when scheduling the second event") + } + + clk.Run(5) + if !recv(a.C()) { + t.Fatal("Alarm did not fire when scheduling the second event") + } + if recv(a.C()) { + t.Fatal("Alarm fired twice when scheduling the second event") + } +} + +// This test checks that scheduling an Alarm to an earlier time than the +// one already scheduled works properly. +func TestAlarmScheduleEarlier(t *testing.T) { + clk := new(Simulated) + clk.Run(20) + a := NewAlarm(clk) + + a.Schedule(clk.Now() + 50) + clk.Run(5) + a.Schedule(clk.Now() + 1) + clk.Run(3) + if !recv(a.C()) { + t.Fatal("Alarm did not fire") + } +} + +// This test checks that scheduling an Alarm to a later time than the +// one already scheduled works properly. +func TestAlarmScheduleLater(t *testing.T) { + clk := new(Simulated) + clk.Run(20) + a := NewAlarm(clk) + + a.Schedule(clk.Now() + 50) + clk.Run(5) + a.Schedule(clk.Now() + 100) + clk.Run(50) + if !recv(a.C()) { + t.Fatal("Alarm did not fire") + } +} + +// This test checks that scheduling an Alarm in the past makes it fire immediately. +func TestAlarmNegative(t *testing.T) { + clk := new(Simulated) + clk.Run(50) + a := NewAlarm(clk) + + a.Schedule(-1) + clk.Run(1) // needed to process timers + if !recv(a.C()) { + t.Fatal("Alarm did not fire for negative time") + } +} + +func recv(ch <-chan struct{}) bool { + select { + case <-ch: + return true + default: + return false + } +} diff --git a/common/mclock/mclock.go b/common/mclock/mclock.go new file mode 100644 index 0000000..c05738c --- /dev/null +++ b/common/mclock/mclock.go @@ -0,0 +1,127 @@ +// 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 mclock is a wrapper for a monotonic clock source +package mclock + +import ( + "time" + + _ "unsafe" // for go:linkname +) + +//go:noescape +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +// AbsTime represents absolute monotonic time. +type AbsTime int64 + +// Now returns the current absolute monotonic time. +func Now() AbsTime { + return AbsTime(nanotime()) +} + +// Add returns t + d as absolute time. +func (t AbsTime) Add(d time.Duration) AbsTime { + return t + AbsTime(d) +} + +// Sub returns t - t2 as a duration. +func (t AbsTime) Sub(t2 AbsTime) time.Duration { + return time.Duration(t - t2) +} + +// The Clock interface makes it possible to replace the monotonic system clock with +// a simulated clock. +type Clock interface { + Now() AbsTime + Sleep(time.Duration) + NewTimer(time.Duration) ChanTimer + After(time.Duration) <-chan AbsTime + AfterFunc(d time.Duration, f func()) Timer +} + +// Timer is a cancellable event created by AfterFunc. +type Timer interface { + // Stop cancels the timer. It returns false if the timer has already + // expired or been stopped. + Stop() bool +} + +// ChanTimer is a cancellable event created by NewTimer. +type ChanTimer interface { + Timer + + // The channel returned by C receives a value when the timer expires. + C() <-chan AbsTime + // Reset reschedules the timer with a new timeout. + // It should be invoked only on stopped or expired timers with drained channels. + Reset(time.Duration) +} + +// System implements Clock using the system clock. +type System struct{} + +// Now returns the current monotonic time. +func (c System) Now() AbsTime { + return Now() +} + +// Sleep blocks for the given duration. +func (c System) Sleep(d time.Duration) { + time.Sleep(d) +} + +// NewTimer creates a timer which can be rescheduled. +func (c System) NewTimer(d time.Duration) ChanTimer { + ch := make(chan AbsTime, 1) + t := time.AfterFunc(d, func() { + // This send is non-blocking because that's how time.Timer + // behaves. It doesn't matter in the happy case, but does + // when Reset is misused. + select { + case ch <- c.Now(): + default: + } + }) + return &systemTimer{t, ch} +} + +// After returns a channel which receives the current time after d has elapsed. +func (c System) After(d time.Duration) <-chan AbsTime { + ch := make(chan AbsTime, 1) + time.AfterFunc(d, func() { ch <- c.Now() }) + return ch +} + +// AfterFunc runs f on a new goroutine after the duration has elapsed. +func (c System) AfterFunc(d time.Duration, f func()) Timer { + return time.AfterFunc(d, f) +} + +type systemTimer struct { + *time.Timer + ch <-chan AbsTime +} + +func (st *systemTimer) Reset(d time.Duration) { + st.Timer.Reset(d) +} + +func (st *systemTimer) C() <-chan AbsTime { + return st.ch +} diff --git a/common/mclock/mclock.s b/common/mclock/mclock.s new file mode 100644 index 0000000..99a7a87 --- /dev/null +++ b/common/mclock/mclock.s @@ -0,0 +1 @@ +// This file exists in order to be able to use go:linkname. diff --git a/common/mclock/simclock.go b/common/mclock/simclock.go new file mode 100644 index 0000000..f5ad3f8 --- /dev/null +++ b/common/mclock/simclock.go @@ -0,0 +1,209 @@ +// 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 mclock + +import ( + "container/heap" + "sync" + "time" +) + +// Simulated implements a virtual Clock for reproducible time-sensitive tests. It +// simulates a scheduler on a virtual timescale where actual processing takes zero time. +// +// The virtual clock doesn't advance on its own, call Run to advance it and execute timers. +// Since there is no way to influence the Go scheduler, testing timeout behaviour involving +// goroutines needs special care. A good way to test such timeouts is as follows: First +// perform the action that is supposed to time out. Ensure that the timer you want to test +// is created. Then run the clock until after the timeout. Finally observe the effect of +// the timeout using a channel or semaphore. +type Simulated struct { + now AbsTime + scheduled simTimerHeap + mu sync.RWMutex + cond *sync.Cond +} + +// simTimer implements ChanTimer on the virtual clock. +type simTimer struct { + at AbsTime + index int // position in s.scheduled + s *Simulated + do func() + ch <-chan AbsTime +} + +func (s *Simulated) init() { + if s.cond == nil { + s.cond = sync.NewCond(&s.mu) + } +} + +// Run moves the clock by the given duration, executing all timers before that duration. +func (s *Simulated) Run(d time.Duration) { + s.mu.Lock() + s.init() + + end := s.now.Add(d) + var do []func() + for len(s.scheduled) > 0 && s.scheduled[0].at <= end { + ev := heap.Pop(&s.scheduled).(*simTimer) + do = append(do, ev.do) + } + s.now = end + s.mu.Unlock() + + for _, fn := range do { + fn() + } +} + +// ActiveTimers returns the number of timers that haven't fired. +func (s *Simulated) ActiveTimers() int { + s.mu.RLock() + defer s.mu.RUnlock() + + return len(s.scheduled) +} + +// WaitForTimers waits until the clock has at least n scheduled timers. +func (s *Simulated) WaitForTimers(n int) { + s.mu.Lock() + defer s.mu.Unlock() + s.init() + + for len(s.scheduled) < n { + s.cond.Wait() + } +} + +// Now returns the current virtual time. +func (s *Simulated) Now() AbsTime { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.now +} + +// Sleep blocks until the clock has advanced by d. +func (s *Simulated) Sleep(d time.Duration) { + <-s.After(d) +} + +// NewTimer creates a timer which fires when the clock has advanced by d. +func (s *Simulated) NewTimer(d time.Duration) ChanTimer { + s.mu.Lock() + defer s.mu.Unlock() + + ch := make(chan AbsTime, 1) + var timer *simTimer + timer = s.schedule(d, func() { ch <- timer.at }) + timer.ch = ch + return timer +} + +// After returns a channel which receives the current time after the clock +// has advanced by d. +func (s *Simulated) After(d time.Duration) <-chan AbsTime { + return s.NewTimer(d).C() +} + +// AfterFunc runs fn after the clock has advanced by d. Unlike with the system +// clock, fn runs on the goroutine that calls Run. +func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer { + s.mu.Lock() + defer s.mu.Unlock() + + return s.schedule(d, fn) +} + +func (s *Simulated) schedule(d time.Duration, fn func()) *simTimer { + s.init() + + at := s.now.Add(d) + ev := &simTimer{do: fn, at: at, s: s} + heap.Push(&s.scheduled, ev) + s.cond.Broadcast() + return ev +} + +func (ev *simTimer) Stop() bool { + ev.s.mu.Lock() + defer ev.s.mu.Unlock() + + if ev.index < 0 { + return false + } + heap.Remove(&ev.s.scheduled, ev.index) + ev.s.cond.Broadcast() + ev.index = -1 + return true +} + +func (ev *simTimer) Reset(d time.Duration) { + if ev.ch == nil { + panic("mclock: Reset() on timer created by AfterFunc") + } + + ev.s.mu.Lock() + defer ev.s.mu.Unlock() + ev.at = ev.s.now.Add(d) + if ev.index < 0 { + heap.Push(&ev.s.scheduled, ev) // already expired + } else { + heap.Fix(&ev.s.scheduled, ev.index) // hasn't fired yet, reschedule + } + ev.s.cond.Broadcast() +} + +func (ev *simTimer) C() <-chan AbsTime { + if ev.ch == nil { + panic("mclock: C() on timer created by AfterFunc") + } + return ev.ch +} + +type simTimerHeap []*simTimer + +func (h *simTimerHeap) Len() int { + return len(*h) +} + +func (h *simTimerHeap) Less(i, j int) bool { + return (*h)[i].at < (*h)[j].at +} + +func (h *simTimerHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] + (*h)[i].index = i + (*h)[j].index = j +} + +func (h *simTimerHeap) Push(x interface{}) { + t := x.(*simTimer) + t.index = len(*h) + *h = append(*h, t) +} + +func (h *simTimerHeap) Pop() interface{} { + end := len(*h) - 1 + t := (*h)[end] + t.index = -1 + (*h)[end] = nil + *h = (*h)[:end] + return t +} diff --git a/common/mclock/simclock_test.go b/common/mclock/simclock_test.go new file mode 100644 index 0000000..582bc31 --- /dev/null +++ b/common/mclock/simclock_test.go @@ -0,0 +1,162 @@ +// 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 mclock + +import ( + "testing" + "time" +) + +var _ Clock = System{} +var _ Clock = new(Simulated) + +func TestSimulatedAfter(t *testing.T) { + var ( + timeout = 30 * time.Minute + offset = 99 * time.Hour + adv = 11 * time.Minute + c Simulated + ) + c.Run(offset) + + end := c.Now().Add(timeout) + ch := c.After(timeout) + for c.Now() < end.Add(-adv) { + c.Run(adv) + select { + case <-ch: + t.Fatal("Timer fired early") + default: + } + } + + c.Run(adv) + select { + case stamp := <-ch: + want := AbsTime(0).Add(offset).Add(timeout) + if stamp != want { + t.Errorf("Wrong time sent on timer channel: got %v, want %v", stamp, want) + } + default: + t.Fatal("Timer didn't fire") + } +} + +func TestSimulatedAfterFunc(t *testing.T) { + var c Simulated + + called1 := false + timer1 := c.AfterFunc(100*time.Millisecond, func() { called1 = true }) + if c.ActiveTimers() != 1 { + t.Fatalf("%d active timers, want one", c.ActiveTimers()) + } + if fired := timer1.Stop(); !fired { + t.Fatal("Stop returned false even though timer didn't fire") + } + if c.ActiveTimers() != 0 { + t.Fatalf("%d active timers, want zero", c.ActiveTimers()) + } + if called1 { + t.Fatal("timer 1 called") + } + if fired := timer1.Stop(); fired { + t.Fatal("Stop returned true after timer was already stopped") + } + + called2 := false + timer2 := c.AfterFunc(100*time.Millisecond, func() { called2 = true }) + c.Run(50 * time.Millisecond) + if called2 { + t.Fatal("timer 2 called") + } + c.Run(51 * time.Millisecond) + if !called2 { + t.Fatal("timer 2 not called") + } + if fired := timer2.Stop(); fired { + t.Fatal("Stop returned true after timer has fired") + } +} + +func TestSimulatedSleep(t *testing.T) { + var ( + c Simulated + timeout = 1 * time.Hour + done = make(chan AbsTime, 1) + ) + go func() { + c.Sleep(timeout) + done <- c.Now() + }() + + c.WaitForTimers(1) + c.Run(2 * timeout) + select { + case stamp := <-done: + want := AbsTime(2 * timeout) + if stamp != want { + t.Errorf("Wrong time after sleep: got %v, want %v", stamp, want) + } + case <-time.After(5 * time.Second): + t.Fatal("Sleep didn't return in time") + } +} + +func TestSimulatedTimerReset(t *testing.T) { + var ( + c Simulated + timeout = 1 * time.Hour + ) + timer := c.NewTimer(timeout) + c.Run(2 * timeout) + select { + case ftime := <-timer.C(): + if ftime != AbsTime(timeout) { + t.Fatalf("wrong time %v sent on timer channel, want %v", ftime, AbsTime(timeout)) + } + default: + t.Fatal("timer didn't fire") + } + + timer.Reset(timeout) + c.Run(2 * timeout) + select { + case ftime := <-timer.C(): + if ftime != AbsTime(3*timeout) { + t.Fatalf("wrong time %v sent on timer channel, want %v", ftime, AbsTime(3*timeout)) + } + default: + t.Fatal("timer didn't fire again") + } +} + +func TestSimulatedTimerStop(t *testing.T) { + var ( + c Simulated + timeout = 1 * time.Hour + ) + timer := c.NewTimer(timeout) + c.Run(2 * timeout) + if timer.Stop() { + t.Errorf("Stop returned true for fired timer") + } + select { + case <-timer.C(): + default: + t.Fatal("timer didn't fire") + } +} diff --git a/common/path.go b/common/path.go new file mode 100644 index 0000000..c1e382f --- /dev/null +++ b/common/path.go @@ -0,0 +1,40 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package common + +import ( + "os" + "path/filepath" +) + +// FileExist checks if a file exists at filePath. +func FileExist(filePath string) bool { + _, err := os.Stat(filePath) + if err != nil && os.IsNotExist(err) { + return false + } + + return true +} + +// AbsolutePath returns datadir + filename, or filename if it is absolute. +func AbsolutePath(datadir string, filename string) string { + if filepath.IsAbs(filename) { + return filename + } + return filepath.Join(datadir, filename) +} diff --git a/common/prque/lazyqueue.go b/common/prque/lazyqueue.go new file mode 100644 index 0000000..ebe53d5 --- /dev/null +++ b/common/prque/lazyqueue.go @@ -0,0 +1,195 @@ +// 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 prque + +import ( + "cmp" + "container/heap" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" +) + +// LazyQueue is a priority queue data structure where priorities can change over +// time and are only evaluated on demand. +// Two callbacks are required: +// - priority evaluates the actual priority of an item +// - maxPriority gives an upper estimate for the priority in any moment between +// now and the given absolute time +// +// If the upper estimate is exceeded then Update should be called for that item. +// A global Refresh function should also be called periodically. +type LazyQueue[P cmp.Ordered, V any] struct { + clock mclock.Clock + // Items are stored in one of two internal queues ordered by estimated max + // priority until the next and the next-after-next refresh. Update and Refresh + // always places items in queue[1]. + queue [2]*sstack[P, V] + popQueue *sstack[P, V] + period time.Duration + maxUntil mclock.AbsTime + indexOffset int + setIndex SetIndexCallback[V] + priority PriorityCallback[P, V] + maxPriority MaxPriorityCallback[P, V] + lastRefresh1, lastRefresh2 mclock.AbsTime +} + +type ( + PriorityCallback[P cmp.Ordered, V any] func(data V) P // actual priority callback + MaxPriorityCallback[P cmp.Ordered, V any] func(data V, until mclock.AbsTime) P // estimated maximum priority callback +) + +// NewLazyQueue creates a new lazy queue +func NewLazyQueue[P cmp.Ordered, V any](setIndex SetIndexCallback[V], priority PriorityCallback[P, V], maxPriority MaxPriorityCallback[P, V], clock mclock.Clock, refreshPeriod time.Duration) *LazyQueue[P, V] { + q := &LazyQueue[P, V]{ + popQueue: newSstack[P, V](nil), + setIndex: setIndex, + priority: priority, + maxPriority: maxPriority, + clock: clock, + period: refreshPeriod, + lastRefresh1: clock.Now(), + lastRefresh2: clock.Now(), + } + q.Reset() + q.refresh(clock.Now()) + return q +} + +// Reset clears the contents of the queue +func (q *LazyQueue[P, V]) Reset() { + q.queue[0] = newSstack[P, V](q.setIndex0) + q.queue[1] = newSstack[P, V](q.setIndex1) +} + +// Refresh performs queue re-evaluation if necessary +func (q *LazyQueue[P, V]) Refresh() { + now := q.clock.Now() + for time.Duration(now-q.lastRefresh2) >= q.period*2 { + q.refresh(now) + q.lastRefresh2 = q.lastRefresh1 + q.lastRefresh1 = now + } +} + +// refresh re-evaluates items in the older queue and swaps the two queues +func (q *LazyQueue[P, V]) refresh(now mclock.AbsTime) { + q.maxUntil = now.Add(q.period) + for q.queue[0].Len() != 0 { + q.Push(heap.Pop(q.queue[0]).(*item[P, V]).value) + } + q.queue[0], q.queue[1] = q.queue[1], q.queue[0] + q.indexOffset = 1 - q.indexOffset + q.maxUntil = q.maxUntil.Add(q.period) +} + +// Push adds an item to the queue +func (q *LazyQueue[P, V]) Push(data V) { + heap.Push(q.queue[1], &item[P, V]{data, q.maxPriority(data, q.maxUntil)}) +} + +// Update updates the upper priority estimate for the item with the given queue index +func (q *LazyQueue[P, V]) Update(index int) { + q.Push(q.Remove(index)) +} + +// Pop removes and returns the item with the greatest actual priority +func (q *LazyQueue[P, V]) Pop() (V, P) { + var ( + resData V + resPri P + ) + q.MultiPop(func(data V, priority P) bool { + resData = data + resPri = priority + return false + }) + return resData, resPri +} + +// peekIndex returns the index of the internal queue where the item with the +// highest estimated priority is or -1 if both are empty +func (q *LazyQueue[P, V]) peekIndex() int { + if q.queue[0].Len() != 0 { + if q.queue[1].Len() != 0 && q.queue[1].blocks[0][0].priority > q.queue[0].blocks[0][0].priority { + return 1 + } + return 0 + } + if q.queue[1].Len() != 0 { + return 1 + } + return -1 +} + +// MultiPop pops multiple items from the queue and is more efficient than calling +// Pop multiple times. Popped items are passed to the callback. MultiPop returns +// when the callback returns false or there are no more items to pop. +func (q *LazyQueue[P, V]) MultiPop(callback func(data V, priority P) bool) { + nextIndex := q.peekIndex() + for nextIndex != -1 { + data := heap.Pop(q.queue[nextIndex]).(*item[P, V]).value + heap.Push(q.popQueue, &item[P, V]{data, q.priority(data)}) + nextIndex = q.peekIndex() + for q.popQueue.Len() != 0 && (nextIndex == -1 || q.queue[nextIndex].blocks[0][0].priority < q.popQueue.blocks[0][0].priority) { + i := heap.Pop(q.popQueue).(*item[P, V]) + if !callback(i.value, i.priority) { + for q.popQueue.Len() != 0 { + q.Push(heap.Pop(q.popQueue).(*item[P, V]).value) + } + return + } + nextIndex = q.peekIndex() // re-check because callback is allowed to push items back + } + } +} + +// PopItem pops the item from the queue only, dropping the associated priority value. +func (q *LazyQueue[P, V]) PopItem() V { + i, _ := q.Pop() + return i +} + +// Remove removes the item with the given index. +func (q *LazyQueue[P, V]) Remove(index int) V { + return heap.Remove(q.queue[index&1^q.indexOffset], index>>1).(*item[P, V]).value +} + +// Empty checks whether the priority queue is empty. +func (q *LazyQueue[P, V]) Empty() bool { + return q.queue[0].Len() == 0 && q.queue[1].Len() == 0 +} + +// Size returns the number of items in the priority queue. +func (q *LazyQueue[P, V]) Size() int { + return q.queue[0].Len() + q.queue[1].Len() +} + +// setIndex0 translates internal queue item index to the virtual index space of LazyQueue +func (q *LazyQueue[P, V]) setIndex0(data V, index int) { + if index == -1 { + q.setIndex(data, -1) + } else { + q.setIndex(data, index+index) + } +} + +// setIndex1 translates internal queue item index to the virtual index space of LazyQueue +func (q *LazyQueue[P, V]) setIndex1(data V, index int) { + q.setIndex(data, index+index+1) +} diff --git a/common/prque/lazyqueue_test.go b/common/prque/lazyqueue_test.go new file mode 100644 index 0000000..ffb7e5e --- /dev/null +++ b/common/prque/lazyqueue_test.go @@ -0,0 +1,123 @@ +// 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 prque + +import ( + "math/rand" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" +) + +const ( + testItems = 1000 + testPriorityStep = 100 + testSteps = 1000000 + testStepPeriod = time.Millisecond + testQueueRefresh = time.Second + testAvgRate = float64(testPriorityStep) / float64(testItems) / float64(testStepPeriod) +) + +type lazyItem struct { + p, maxp int64 + last mclock.AbsTime + index int +} + +func testPriority(a interface{}) int64 { + return a.(*lazyItem).p +} + +func testMaxPriority(a interface{}, until mclock.AbsTime) int64 { + i := a.(*lazyItem) + dt := until - i.last + i.maxp = i.p + int64(float64(dt)*testAvgRate) + return i.maxp +} + +func testSetIndex(a interface{}, i int) { + a.(*lazyItem).index = i +} + +func TestLazyQueue(t *testing.T) { + clock := &mclock.Simulated{} + q := NewLazyQueue(testSetIndex, testPriority, testMaxPriority, clock, testQueueRefresh) + + var ( + items [testItems]lazyItem + maxPri int64 + ) + + for i := range items[:] { + items[i].p = rand.Int63n(testPriorityStep * 10) + if items[i].p > maxPri { + maxPri = items[i].p + } + items[i].index = -1 + q.Push(&items[i]) + } + + var ( + lock sync.Mutex + wg sync.WaitGroup + stopCh = make(chan chan struct{}) + ) + defer wg.Wait() + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-clock.After(testQueueRefresh): + lock.Lock() + q.Refresh() + lock.Unlock() + case <-stopCh: + return + } + } + }() + + for c := 0; c < testSteps; c++ { + i := rand.Intn(testItems) + lock.Lock() + items[i].p += rand.Int63n(testPriorityStep*2-1) + 1 + if items[i].p > maxPri { + maxPri = items[i].p + } + items[i].last = clock.Now() + if items[i].p > items[i].maxp { + q.Update(items[i].index) + } + if rand.Intn(100) == 0 { + p := q.PopItem().(*lazyItem) + if p.p != maxPri { + lock.Unlock() + close(stopCh) + t.Fatalf("incorrect item (best known priority %d, popped %d)", maxPri, p.p) + } + q.Push(p) + } + lock.Unlock() + clock.Run(testStepPeriod) + clock.WaitForTimers(1) + } + + close(stopCh) +} diff --git a/common/prque/prque.go b/common/prque/prque.go new file mode 100755 index 0000000..cb0d9f3 --- /dev/null +++ b/common/prque/prque.go @@ -0,0 +1,76 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque". + +// Package prque implements a priority queue data structure supporting arbitrary +// value types and int64 priorities. +// +// If you would like to use a min-priority queue, simply negate the priorities. +// +// Internally the queue is based on the standard heap package working on a +// sortable version of the block based stack. +package prque + +import ( + "cmp" + "container/heap" +) + +// Prque is a priority queue data structure. +type Prque[P cmp.Ordered, V any] struct { + cont *sstack[P, V] +} + +// New creates a new priority queue. +func New[P cmp.Ordered, V any](setIndex SetIndexCallback[V]) *Prque[P, V] { + return &Prque[P, V]{newSstack[P, V](setIndex)} +} + +// Push a value with a given priority into the queue, expanding if necessary. +func (p *Prque[P, V]) Push(data V, priority P) { + heap.Push(p.cont, &item[P, V]{data, priority}) +} + +// Peek returns the value with the greatest priority but does not pop it off. +func (p *Prque[P, V]) Peek() (V, P) { + item := p.cont.blocks[0][0] + return item.value, item.priority +} + +// Pop the value with the greatest priority off the stack and returns it. +// Currently no shrinking is done. +func (p *Prque[P, V]) Pop() (V, P) { + item := heap.Pop(p.cont).(*item[P, V]) + return item.value, item.priority +} + +// PopItem pops only the item from the queue, dropping the associated priority value. +func (p *Prque[P, V]) PopItem() V { + return heap.Pop(p.cont).(*item[P, V]).value +} + +// Remove removes the element with the given index. +func (p *Prque[P, V]) Remove(i int) V { + return heap.Remove(p.cont, i).(*item[P, V]).value +} + +// Empty checks whether the priority queue is empty. +func (p *Prque[P, V]) Empty() bool { + return p.cont.Len() == 0 +} + +// Size returns the number of element in the priority queue. +func (p *Prque[P, V]) Size() int { + return p.cont.Len() +} + +// Reset clears the contents of the priority queue. +func (p *Prque[P, V]) Reset() { + *p = *New[P, V](p.cont.setIndex) +} diff --git a/common/prque/prque_test.go b/common/prque/prque_test.go new file mode 100644 index 0000000..c4910f2 --- /dev/null +++ b/common/prque/prque_test.go @@ -0,0 +1,133 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +package prque + +import ( + "math/rand" + "testing" +) + +func TestPrque(t *testing.T) { + // Generate a batch of random data and a specific priority order + size := 16 * blockSize + prio := rand.Perm(size) + data := make([]int, size) + for i := 0; i < size; i++ { + data[i] = rand.Int() + } + queue := New[int, int](nil) + + for rep := 0; rep < 2; rep++ { + // Fill a priority queue with the above data + for i := 0; i < size; i++ { + queue.Push(data[i], prio[i]) + if queue.Size() != i+1 { + t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) + } + } + // Create a map the values to the priorities for easier verification + dict := make(map[int]int) + for i := 0; i < size; i++ { + dict[prio[i]] = data[i] + } + + // Pop out the elements in priority order and verify them + prevPrio := size + 1 + for !queue.Empty() { + val, prio := queue.Pop() + if prio > prevPrio { + t.Errorf("invalid priority order: %v after %v.", prio, prevPrio) + } + prevPrio = prio + if val != dict[prio] { + t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio]) + } + delete(dict, prio) + } + } +} + +func TestReset(t *testing.T) { + // Generate a batch of random data and a specific priority order + size := 16 * blockSize + prio := rand.Perm(size) + data := make([]int, size) + for i := 0; i < size; i++ { + data[i] = rand.Int() + } + queue := New[int, int](nil) + + for rep := 0; rep < 2; rep++ { + // Fill a priority queue with the above data + for i := 0; i < size; i++ { + queue.Push(data[i], prio[i]) + if queue.Size() != i+1 { + t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) + } + } + // Create a map the values to the priorities for easier verification + dict := make(map[int]int) + for i := 0; i < size; i++ { + dict[prio[i]] = data[i] + } + // Pop out half the elements in priority order and verify them + prevPrio := size + 1 + for i := 0; i < size/2; i++ { + val, prio := queue.Pop() + if prio > prevPrio { + t.Errorf("invalid priority order: %v after %v.", prio, prevPrio) + } + prevPrio = prio + if val != dict[prio] { + t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio]) + } + delete(dict, prio) + } + // Reset and ensure it's empty + queue.Reset() + if !queue.Empty() { + t.Errorf("priority queue not empty after reset: %v", queue) + } + } +} + +func BenchmarkPush(b *testing.B) { + // Create some initial data + data := make([]int, b.N) + prio := make([]int64, b.N) + for i := 0; i < len(data); i++ { + data[i] = rand.Int() + prio[i] = rand.Int63() + } + // Execute the benchmark + b.ResetTimer() + queue := New[int64, int](nil) + for i := 0; i < len(data); i++ { + queue.Push(data[i], prio[i]) + } +} + +func BenchmarkPop(b *testing.B) { + // Create some initial data + data := make([]int, b.N) + prio := make([]int64, b.N) + for i := 0; i < len(data); i++ { + data[i] = rand.Int() + prio[i] = rand.Int63() + } + queue := New[int64, int](nil) + for i := 0; i < len(data); i++ { + queue.Push(data[i], prio[i]) + } + // Execute the benchmark + b.ResetTimer() + for !queue.Empty() { + queue.Pop() + } +} diff --git a/common/prque/sstack.go b/common/prque/sstack.go new file mode 100755 index 0000000..6865a51 --- /dev/null +++ b/common/prque/sstack.go @@ -0,0 +1,113 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque". + +package prque + +import "cmp" + +// The size of a block of data +const blockSize = 4096 + +// A prioritized item in the sorted stack. +type item[P cmp.Ordered, V any] struct { + value V + priority P +} + +// SetIndexCallback is called when the element is moved to a new index. +// Providing SetIndexCallback is optional, it is needed only if the application needs +// to delete elements other than the top one. +type SetIndexCallback[V any] func(data V, index int) + +// Internal sortable stack data structure. Implements the Push and Pop ops for +// the stack (heap) functionality and the Len, Less and Swap methods for the +// sortability requirements of the heaps. +type sstack[P cmp.Ordered, V any] struct { + setIndex SetIndexCallback[V] + size int + capacity int + offset int + + blocks [][]*item[P, V] + active []*item[P, V] +} + +// Creates a new, empty stack. +func newSstack[P cmp.Ordered, V any](setIndex SetIndexCallback[V]) *sstack[P, V] { + result := new(sstack[P, V]) + result.setIndex = setIndex + result.active = make([]*item[P, V], blockSize) + result.blocks = [][]*item[P, V]{result.active} + result.capacity = blockSize + return result +} + +// Push a value onto the stack, expanding it if necessary. Required by +// heap.Interface. +func (s *sstack[P, V]) Push(data any) { + if s.size == s.capacity { + s.active = make([]*item[P, V], blockSize) + s.blocks = append(s.blocks, s.active) + s.capacity += blockSize + s.offset = 0 + } else if s.offset == blockSize { + s.active = s.blocks[s.size/blockSize] + s.offset = 0 + } + if s.setIndex != nil { + s.setIndex(data.(*item[P, V]).value, s.size) + } + s.active[s.offset] = data.(*item[P, V]) + s.offset++ + s.size++ +} + +// Pop a value off the stack and returns it. Currently no shrinking is done. +// Required by heap.Interface. +func (s *sstack[P, V]) Pop() (res any) { + s.size-- + s.offset-- + if s.offset < 0 { + s.offset = blockSize - 1 + s.active = s.blocks[s.size/blockSize] + } + res, s.active[s.offset] = s.active[s.offset], nil + if s.setIndex != nil { + s.setIndex(res.(*item[P, V]).value, -1) + } + return +} + +// Len returns the length of the stack. Required by sort.Interface. +func (s *sstack[P, V]) Len() int { + return s.size +} + +// Less compares the priority of two elements of the stack (higher is first). +// Required by sort.Interface. +func (s *sstack[P, V]) Less(i, j int) bool { + return s.blocks[i/blockSize][i%blockSize].priority > s.blocks[j/blockSize][j%blockSize].priority +} + +// Swap two elements in the stack. Required by sort.Interface. +func (s *sstack[P, V]) Swap(i, j int) { + ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize + a, b := s.blocks[jb][jo], s.blocks[ib][io] + if s.setIndex != nil { + s.setIndex(a.value, i) + s.setIndex(b.value, j) + } + s.blocks[ib][io], s.blocks[jb][jo] = a, b +} + +// Reset the stack, effectively clearing its contents. +func (s *sstack[P, V]) Reset() { + *s = *newSstack[P, V](s.setIndex) +} diff --git a/common/prque/sstack_test.go b/common/prque/sstack_test.go new file mode 100644 index 0000000..edc9995 --- /dev/null +++ b/common/prque/sstack_test.go @@ -0,0 +1,100 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +package prque + +import ( + "math/rand" + "sort" + "testing" +) + +func TestSstack(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item[int64, int], size) + for i := 0; i < size; i++ { + data[i] = &item[int64, int]{rand.Int(), rand.Int63()} + } + stack := newSstack[int64, int](nil) + for rep := 0; rep < 2; rep++ { + // Push all the data into the stack, pop out every second + secs := []*item[int64, int]{} + for i := 0; i < size; i++ { + stack.Push(data[i]) + if i%2 == 0 { + secs = append(secs, stack.Pop().(*item[int64, int])) + } + } + rest := []*item[int64, int]{} + for stack.Len() > 0 { + rest = append(rest, stack.Pop().(*item[int64, int])) + } + // Make sure the contents of the resulting slices are ok + for i := 0; i < size; i++ { + if i%2 == 0 && data[i] != secs[i/2] { + t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i]) + } + if i%2 == 1 && data[i] != rest[len(rest)-i/2-1] { + t.Errorf("push/pop mismatch: have %v, want %v.", rest[len(rest)-i/2-1], data[i]) + } + } + } +} + +func TestSstackSort(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item[int64, int], size) + for i := 0; i < size; i++ { + data[i] = &item[int64, int]{rand.Int(), int64(i)} + } + // Push all the data into the stack + stack := newSstack[int64, int](nil) + for _, val := range data { + stack.Push(val) + } + // Sort and pop the stack contents (should reverse the order) + sort.Sort(stack) + for _, val := range data { + out := stack.Pop() + if out != val { + t.Errorf("push/pop mismatch after sort: have %v, want %v.", out, val) + } + } +} + +func TestSstackReset(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item[int64, int], size) + for i := 0; i < size; i++ { + data[i] = &item[int64, int]{rand.Int(), rand.Int63()} + } + stack := newSstack[int64, int](nil) + for rep := 0; rep < 2; rep++ { + // Push all the data into the stack, pop out every second + secs := []*item[int64, int]{} + for i := 0; i < size; i++ { + stack.Push(data[i]) + if i%2 == 0 { + secs = append(secs, stack.Pop().(*item[int64, int])) + } + } + // Reset and verify both pulled and stack contents + stack.Reset() + if stack.Len() != 0 { + t.Errorf("stack not empty after reset: %v", stack) + } + for i := 0; i < size; i++ { + if i%2 == 0 && data[i] != secs[i/2] { + t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i]) + } + } + } +} diff --git a/common/size.go b/common/size.go new file mode 100644 index 0000000..097b630 --- /dev/null +++ b/common/size.go @@ -0,0 +1,56 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package common + +import ( + "fmt" +) + +// StorageSize is a wrapper around a float value that supports user friendly +// formatting. +type StorageSize float64 + +// String implements the stringer interface. +func (s StorageSize) String() string { + if s > 1099511627776 { + return fmt.Sprintf("%.2f TiB", s/1099511627776) + } else if s > 1073741824 { + return fmt.Sprintf("%.2f GiB", s/1073741824) + } else if s > 1048576 { + return fmt.Sprintf("%.2f MiB", s/1048576) + } else if s > 1024 { + return fmt.Sprintf("%.2f KiB", s/1024) + } else { + return fmt.Sprintf("%.2f B", s) + } +} + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (s StorageSize) TerminalString() string { + if s > 1099511627776 { + return fmt.Sprintf("%.2fTiB", s/1099511627776) + } else if s > 1073741824 { + return fmt.Sprintf("%.2fGiB", s/1073741824) + } else if s > 1048576 { + return fmt.Sprintf("%.2fMiB", s/1048576) + } else if s > 1024 { + return fmt.Sprintf("%.2fKiB", s/1024) + } else { + return fmt.Sprintf("%.2fB", s) + } +} diff --git a/common/size_test.go b/common/size_test.go new file mode 100644 index 0000000..28f053d --- /dev/null +++ b/common/size_test.go @@ -0,0 +1,59 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package common + +import ( + "testing" +) + +func TestStorageSizeString(t *testing.T) { + tests := []struct { + size StorageSize + str string + }{ + {2839274474874, "2.58 TiB"}, + {2458492810, "2.29 GiB"}, + {2381273, "2.27 MiB"}, + {2192, "2.14 KiB"}, + {12, "12.00 B"}, + } + + for _, test := range tests { + if test.size.String() != test.str { + t.Errorf("%f: got %q, want %q", float64(test.size), test.size.String(), test.str) + } + } +} + +func TestStorageSizeTerminalString(t *testing.T) { + tests := []struct { + size StorageSize + str string + }{ + {2839274474874, "2.58TiB"}, + {2458492810, "2.29GiB"}, + {2381273, "2.27MiB"}, + {2192, "2.14KiB"}, + {12, "12.00B"}, + } + + for _, test := range tests { + if test.size.TerminalString() != test.str { + t.Errorf("%f: got %q, want %q", float64(test.size), test.size.TerminalString(), test.str) + } + } +} diff --git a/common/test_utils.go b/common/test_utils.go new file mode 100644 index 0000000..7a17541 --- /dev/null +++ b/common/test_utils.go @@ -0,0 +1,53 @@ +// 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 common + +import ( + "encoding/json" + "fmt" + "os" +) + +// LoadJSON reads the given file and unmarshals its content. +func LoadJSON(file string, val interface{}) error { + content, err := os.ReadFile(file) + if err != nil { + return err + } + if err := json.Unmarshal(content, val); err != nil { + if syntaxerr, ok := err.(*json.SyntaxError); ok { + line := findLine(content, syntaxerr.Offset) + return fmt.Errorf("JSON syntax error at %v:%v: %v", file, line, err) + } + return fmt.Errorf("JSON unmarshal error in %v: %v", file, err) + } + return nil +} + +// findLine returns the line number for the given offset into data. +func findLine(data []byte, offset int64) (line int) { + line = 1 + for i, r := range string(data) { + if int64(i) >= offset { + return + } + if r == '\n' { + line++ + } + } + return +} diff --git a/common/types.go b/common/types.go new file mode 100644 index 0000000..fdb25f1 --- /dev/null +++ b/common/types.go @@ -0,0 +1,488 @@ +// 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 common + +import ( + "bytes" + "database/sql/driver" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "math/rand" + "reflect" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + "golang.org/x/crypto/sha3" +) + +// Lengths of hashes and addresses in bytes. +const ( + // HashLength is the expected length of the hash + HashLength = 32 + // AddressLength is the expected length of the address + AddressLength = 20 +) + +var ( + hashT = reflect.TypeOf(Hash{}) + addressT = reflect.TypeOf(Address{}) + + // 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. +type Hash [HashLength]byte + +// BytesToHash sets b to hash. +// If b is larger than len(h), b will be cropped from the left. +func BytesToHash(b []byte) Hash { + var h Hash + h.SetBytes(b) + return h +} + +// BigToHash sets byte representation of b to hash. +// If b is larger than len(h), b will be cropped from the left. +func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } + +// HexToHash sets byte representation of s to hash. +// If b is larger than len(h), b will be cropped from the left. +func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } + +// Cmp compares two hashes. +func (h Hash) Cmp(other Hash) int { + return bytes.Compare(h[:], other[:]) +} + +// Bytes gets the byte representation of the underlying hash. +func (h Hash) Bytes() []byte { return h[:] } + +// Big converts a hash to a big integer. +func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } + +// Hex converts a hash to a hex string. +func (h Hash) Hex() string { return hexutil.Encode(h[:]) } + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (h Hash) TerminalString() string { + return fmt.Sprintf("%x..%x", h[:3], h[29:]) +} + +// String implements the stringer interface and is used also by the logger when +// doing full logging into a file. +func (h Hash) String() string { + return h.Hex() +} + +// Format implements fmt.Formatter. +// Hash supports the %v, %s, %q, %x, %X and %d format verbs. +func (h Hash) Format(s fmt.State, c rune) { + hexb := make([]byte, 2+len(h)*2) + copy(hexb, "0x") + hex.Encode(hexb[2:], h[:]) + + switch c { + case 'x', 'X': + if !s.Flag('#') { + hexb = hexb[2:] + } + if c == 'X' { + hexb = bytes.ToUpper(hexb) + } + fallthrough + case 'v', 's': + s.Write(hexb) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(hexb) + s.Write(q) + case 'd': + fmt.Fprint(s, ([len(h)]byte)(h)) + default: + fmt.Fprintf(s, "%%!%c(hash=%x)", c, h) + } +} + +// UnmarshalText parses a hash in hex syntax. +func (h *Hash) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Hash", input, h[:]) +} + +// UnmarshalJSON parses a hash in hex syntax. +func (h *Hash) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(hashT, input, h[:]) +} + +// MarshalText returns the hex representation of h. +func (h Hash) MarshalText() ([]byte, error) { + return hexutil.Bytes(h[:]).MarshalText() +} + +// SetBytes sets the hash to the value of b. +// If b is larger than len(h), b will be cropped from the left. +func (h *Hash) SetBytes(b []byte) { + if len(b) > len(h) { + b = b[len(b)-HashLength:] + } + + copy(h[HashLength-len(b):], b) +} + +// Generate implements testing/quick.Generator. +func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { + m := rand.Intn(len(h)) + for i := len(h) - 1; i > m; i-- { + h[i] = byte(rand.Uint32()) + } + return reflect.ValueOf(h) +} + +// Scan implements Scanner for database/sql. +func (h *Hash) Scan(src interface{}) error { + srcB, ok := src.([]byte) + if !ok { + return fmt.Errorf("can't scan %T into Hash", src) + } + if len(srcB) != HashLength { + return fmt.Errorf("can't scan []byte of len %d into Hash, want %d", len(srcB), HashLength) + } + copy(h[:], srcB) + return nil +} + +// Value implements valuer for database/sql. +func (h Hash) Value() (driver.Value, error) { + return h[:], nil +} + +// ImplementsGraphQLType returns true if Hash implements the specified GraphQL type. +func (Hash) ImplementsGraphQLType(name string) bool { return name == "Bytes32" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (h *Hash) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + err = h.UnmarshalText([]byte(input)) + default: + err = fmt.Errorf("unexpected type %T for Hash", input) + } + return err +} + +// UnprefixedHash allows marshaling a Hash without 0x prefix. +type UnprefixedHash Hash + +// UnmarshalText decodes the hash from hex. The 0x prefix is optional. +func (h *UnprefixedHash) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedUnprefixedText("UnprefixedHash", input, h[:]) +} + +// MarshalText encodes the hash as hex. +func (h UnprefixedHash) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(h[:])), nil +} + +/////////// Address + +// Address represents the 20 byte address of an Ethereum account. +type Address [AddressLength]byte + +// BytesToAddress returns Address with value b. +// If b is larger than len(h), b will be cropped from the left. +func BytesToAddress(b []byte) Address { + var a Address + a.SetBytes(b) + return a +} + +// BigToAddress returns Address with byte values of b. +// If b is larger than len(h), b will be cropped from the left. +func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } + +// HexToAddress returns Address with byte values of s. +// If s is larger than len(h), s will be cropped from the left. +func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } + +// IsHexAddress verifies whether a string can represent a valid hex-encoded +// Ethereum address or not. +func IsHexAddress(s string) bool { + if has0xPrefix(s) { + s = s[2:] + } + return len(s) == 2*AddressLength && isHex(s) +} + +// Cmp compares two addresses. +func (a Address) Cmp(other Address) int { + return bytes.Compare(a[:], other[:]) +} + +// Bytes gets the string representation of the underlying address. +func (a Address) Bytes() []byte { return a[:] } + +// Big converts an address to a big integer. +func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) } + +// Hex returns an EIP55-compliant hex string representation of the address. +func (a Address) Hex() string { + return string(a.checksumHex()) +} + +// String implements fmt.Stringer. +func (a Address) String() string { + return a.Hex() +} + +func (a *Address) checksumHex() []byte { + buf := a.hex() + + // compute checksum + sha := sha3.NewLegacyKeccak256() + sha.Write(buf[2:]) + hash := sha.Sum(nil) + for i := 2; i < len(buf); i++ { + hashByte := hash[(i-2)/2] + if i%2 == 0 { + hashByte = hashByte >> 4 + } else { + hashByte &= 0xf + } + if buf[i] > '9' && hashByte > 7 { + buf[i] -= 32 + } + } + return buf[:] +} + +func (a Address) hex() []byte { + var buf [len(a)*2 + 2]byte + copy(buf[:2], "0x") + hex.Encode(buf[2:], a[:]) + return buf[:] +} + +// Format implements fmt.Formatter. +// Address supports the %v, %s, %q, %x, %X and %d format verbs. +func (a Address) Format(s fmt.State, c rune) { + switch c { + case 'v', 's': + s.Write(a.checksumHex()) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(a.checksumHex()) + s.Write(q) + case 'x', 'X': + // %x disables the checksum. + hex := a.hex() + if !s.Flag('#') { + hex = hex[2:] + } + if c == 'X' { + hex = bytes.ToUpper(hex) + } + s.Write(hex) + case 'd': + fmt.Fprint(s, ([len(a)]byte)(a)) + default: + fmt.Fprintf(s, "%%!%c(address=%x)", c, a) + } +} + +// SetBytes sets the address to the value of b. +// If b is larger than len(a), b will be cropped from the left. +func (a *Address) SetBytes(b []byte) { + if len(b) > len(a) { + b = b[len(b)-AddressLength:] + } + copy(a[AddressLength-len(b):], b) +} + +// MarshalText returns the hex representation of a. +func (a Address) MarshalText() ([]byte, error) { + return hexutil.Bytes(a[:]).MarshalText() +} + +// UnmarshalText parses a hash in hex syntax. +func (a *Address) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Address", input, a[:]) +} + +// UnmarshalJSON parses a hash in hex syntax. +func (a *Address) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(addressT, input, a[:]) +} + +// Scan implements Scanner for database/sql. +func (a *Address) Scan(src interface{}) error { + srcB, ok := src.([]byte) + if !ok { + return fmt.Errorf("can't scan %T into Address", src) + } + if len(srcB) != AddressLength { + return fmt.Errorf("can't scan []byte of len %d into Address, want %d", len(srcB), AddressLength) + } + copy(a[:], srcB) + return nil +} + +// Value implements valuer for database/sql. +func (a Address) Value() (driver.Value, error) { + return a[:], nil +} + +// ImplementsGraphQLType returns true if Hash implements the specified GraphQL type. +func (a Address) ImplementsGraphQLType(name string) bool { return name == "Address" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (a *Address) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + err = a.UnmarshalText([]byte(input)) + default: + err = fmt.Errorf("unexpected type %T for Address", input) + } + return err +} + +// UnprefixedAddress allows marshaling an Address without 0x prefix. +type UnprefixedAddress Address + +// UnmarshalText decodes the address from hex. The 0x prefix is optional. +func (a *UnprefixedAddress) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedUnprefixedText("UnprefixedAddress", input, a[:]) +} + +// MarshalText encodes the address as hex. +func (a UnprefixedAddress) MarshalText() ([]byte, error) { + return []byte(hex.EncodeToString(a[:])), nil +} + +// MixedcaseAddress retains the original string, which may or may not be +// correctly checksummed +type MixedcaseAddress struct { + addr Address + original string +} + +// NewMixedcaseAddress constructor (mainly for testing) +func NewMixedcaseAddress(addr Address) MixedcaseAddress { + return MixedcaseAddress{addr: addr, original: addr.Hex()} +} + +// NewMixedcaseAddressFromString is mainly meant for unit-testing +func NewMixedcaseAddressFromString(hexaddr string) (*MixedcaseAddress, error) { + if !IsHexAddress(hexaddr) { + return nil, errors.New("invalid address") + } + a := FromHex(hexaddr) + return &MixedcaseAddress{addr: BytesToAddress(a), original: hexaddr}, nil +} + +// UnmarshalJSON parses MixedcaseAddress +func (ma *MixedcaseAddress) UnmarshalJSON(input []byte) error { + if err := hexutil.UnmarshalFixedJSON(addressT, input, ma.addr[:]); err != nil { + return err + } + return json.Unmarshal(input, &ma.original) +} + +// MarshalJSON marshals the original value +func (ma MixedcaseAddress) MarshalJSON() ([]byte, error) { + if strings.HasPrefix(ma.original, "0x") || strings.HasPrefix(ma.original, "0X") { + return json.Marshal(fmt.Sprintf("0x%s", ma.original[2:])) + } + return json.Marshal(fmt.Sprintf("0x%s", ma.original)) +} + +// Address returns the address +func (ma *MixedcaseAddress) Address() Address { + return ma.addr +} + +// String implements fmt.Stringer +func (ma *MixedcaseAddress) String() string { + if ma.ValidChecksum() { + return fmt.Sprintf("%s [chksum ok]", ma.original) + } + return fmt.Sprintf("%s [chksum INVALID]", ma.original) +} + +// ValidChecksum returns true if the address has valid checksum +func (ma *MixedcaseAddress) ValidChecksum() bool { + return ma.original == ma.addr.Hex() +} + +// Original returns the mixed-case input string +func (ma *MixedcaseAddress) Original() string { + return ma.original +} + +// AddressEIP55 is an alias of Address with a customized json marshaller +type AddressEIP55 Address + +// String returns the hex representation of the address in the manner of EIP55. +func (addr AddressEIP55) String() string { + return Address(addr).Hex() +} + +// MarshalJSON marshals the address in the manner of EIP55. +func (addr AddressEIP55) MarshalJSON() ([]byte, error) { + return json.Marshal(addr.String()) +} + +type Decimal uint64 + +func isString(input []byte) bool { + return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' +} + +// UnmarshalJSON parses a hash in hex syntax. +func (d *Decimal) UnmarshalJSON(input []byte) error { + if !isString(input) { + return &json.UnmarshalTypeError{Value: "non-string", Type: reflect.TypeOf(uint64(0))} + } + if i, err := strconv.ParseUint(string(input[1:len(input)-1]), 10, 64); err == nil { + *d = Decimal(i) + return nil + } else { + return err + } +} + +type PrettyBytes []byte + +// TerminalString implements log.TerminalStringer, formatting a string for console +// output during logging. +func (b PrettyBytes) TerminalString() string { + if len(b) < 7 { + return fmt.Sprintf("%x", b) + } + return fmt.Sprintf("%#x...%x (%dB)", b[:3], b[len(b)-3:], len(b)) +} diff --git a/common/types_test.go b/common/types_test.go new file mode 100644 index 0000000..11247b1 --- /dev/null +++ b/common/types_test.go @@ -0,0 +1,624 @@ +// 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 common + +import ( + "bytes" + "database/sql/driver" + "encoding/json" + "fmt" + "math" + "math/big" + "reflect" + "strings" + "testing" + "time" +) + +func TestBytesConversion(t *testing.T) { + bytes := []byte{5} + hash := BytesToHash(bytes) + + var exp Hash + exp[31] = 5 + + if hash != exp { + t.Errorf("expected %x got %x", exp, hash) + } +} + +func TestIsHexAddress(t *testing.T) { + tests := []struct { + str string + exp bool + }{ + {"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", true}, + {"5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", true}, + {"0X5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", true}, + {"0XAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", true}, + {"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", true}, + {"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed1", false}, + {"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beae", false}, + {"5aaeb6053f3e94c9b9a09f33669435e7ef1beaed11", false}, + {"0xxaaeb6053f3e94c9b9a09f33669435e7ef1beaed", false}, + } + + for _, test := range tests { + if result := IsHexAddress(test.str); result != test.exp { + t.Errorf("IsHexAddress(%s) == %v; expected %v", + test.str, result, test.exp) + } + } +} + +func TestHashJsonValidation(t *testing.T) { + var tests = []struct { + Prefix string + Size int + Error string + }{ + {"", 62, "json: cannot unmarshal hex string without 0x prefix into Go value of type common.Hash"}, + {"0x", 66, "hex string has length 66, want 64 for common.Hash"}, + {"0x", 63, "json: cannot unmarshal hex string of odd length into Go value of type common.Hash"}, + {"0x", 0, "hex string has length 0, want 64 for common.Hash"}, + {"0x", 64, ""}, + {"0X", 64, ""}, + } + for _, test := range tests { + input := `"` + test.Prefix + strings.Repeat("0", test.Size) + `"` + var v Hash + err := json.Unmarshal([]byte(input), &v) + if err == nil { + if test.Error != "" { + t.Errorf("%s: error mismatch: have nil, want %q", input, test.Error) + } + } else { + if err.Error() != test.Error { + t.Errorf("%s: error mismatch: have %q, want %q", input, err, test.Error) + } + } + } +} + +func TestAddressUnmarshalJSON(t *testing.T) { + var tests = []struct { + Input string + ShouldErr bool + Output *big.Int + }{ + {"", true, nil}, + {`""`, true, nil}, + {`"0x"`, true, nil}, + {`"0x00"`, true, nil}, + {`"0xG000000000000000000000000000000000000000"`, true, nil}, + {`"0x0000000000000000000000000000000000000000"`, false, big.NewInt(0)}, + {`"0x0000000000000000000000000000000000000010"`, false, big.NewInt(16)}, + } + for i, test := range tests { + var v Address + err := json.Unmarshal([]byte(test.Input), &v) + if err != nil && !test.ShouldErr { + t.Errorf("test #%d: unexpected error: %v", i, err) + } + if err == nil { + if test.ShouldErr { + t.Errorf("test #%d: expected error, got none", i) + } + if got := new(big.Int).SetBytes(v.Bytes()); got.Cmp(test.Output) != 0 { + t.Errorf("test #%d: address mismatch: have %v, want %v", i, got, test.Output) + } + } + } +} + +func TestAddressHexChecksum(t *testing.T) { + var tests = []struct { + Input string + Output string + }{ + // Test cases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md#specification + {"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"}, + {"0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359", "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"}, + {"0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb", "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"}, + {"0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb", "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"}, + // Ensure that non-standard length input values are handled correctly + {"0xa", "0x000000000000000000000000000000000000000A"}, + {"0x0a", "0x000000000000000000000000000000000000000A"}, + {"0x00a", "0x000000000000000000000000000000000000000A"}, + {"0x000000000000000000000000000000000000000a", "0x000000000000000000000000000000000000000A"}, + } + for i, test := range tests { + output := HexToAddress(test.Input).Hex() + if output != test.Output { + t.Errorf("test #%d: failed to match when it should (%s != %s)", i, output, test.Output) + } + } +} + +func BenchmarkAddressHex(b *testing.B) { + testAddr := HexToAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + for n := 0; n < b.N; n++ { + testAddr.Hex() + } +} + +// Test checks if the customized json marshaller of MixedcaseAddress object +// is invoked correctly. In golang the struct pointer will inherit the +// non-pointer receiver methods, the reverse is not true. In the case of +// MixedcaseAddress, it must define the MarshalJSON method in the object +// but not the pointer level, so that this customized marshalled can be used +// for both MixedcaseAddress object and pointer. +func TestMixedcaseAddressMarshal(t *testing.T) { + var ( + output string + input = "0xae967917c465db8578ca9024c205720b1a3651A9" + ) + addr, err := NewMixedcaseAddressFromString(input) + if err != nil { + t.Fatal(err) + } + blob, err := json.Marshal(*addr) + if err != nil { + t.Fatal(err) + } + json.Unmarshal(blob, &output) + if output != input { + t.Fatal("Failed to marshal/unmarshal MixedcaseAddress object") + } +} + +func TestMixedcaseAccount_Address(t *testing.T) { + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md + // Note: 0X{checksum_addr} is not valid according to spec above + + var res []struct { + A MixedcaseAddress + Valid bool + } + if err := json.Unmarshal([]byte(`[ + {"A" : "0xae967917c465db8578ca9024c205720b1a3651A9", "Valid": false}, + {"A" : "0xAe967917c465db8578ca9024c205720b1a3651A9", "Valid": true}, + {"A" : "0XAe967917c465db8578ca9024c205720b1a3651A9", "Valid": false}, + {"A" : "0x1111111111111111111112222222222223333323", "Valid": true} + ]`), &res); err != nil { + t.Fatal(err) + } + + for _, r := range res { + if got := r.A.ValidChecksum(); got != r.Valid { + t.Errorf("Expected checksum %v, got checksum %v, input %v", r.Valid, got, r.A.String()) + } + } + + // These should throw exceptions: + var r2 []MixedcaseAddress + for _, r := range []string{ + `["0x11111111111111111111122222222222233333"]`, // Too short + `["0x111111111111111111111222222222222333332"]`, // Too short + `["0x11111111111111111111122222222222233333234"]`, // Too long + `["0x111111111111111111111222222222222333332344"]`, // Too long + `["1111111111111111111112222222222223333323"]`, // Missing 0x + `["x1111111111111111111112222222222223333323"]`, // Missing 0 + `["0xG111111111111111111112222222222223333323"]`, //Non-hex + } { + if err := json.Unmarshal([]byte(r), &r2); err == nil { + t.Errorf("Expected failure, input %v", r) + } + } +} + +func TestHash_Scan(t *testing.T) { + type args struct { + src interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "working scan", + args: args{src: []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0x10, 0x00, + }}, + wantErr: false, + }, + { + name: "non working scan", + args: args{src: int64(1234567890)}, + wantErr: true, + }, + { + name: "invalid length scan", + args: args{src: []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + }}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := &Hash{} + if err := h.Scan(tt.args.src); (err != nil) != tt.wantErr { + t.Errorf("Hash.Scan() error = %v, wantErr %v", err, tt.wantErr) + } + + if !tt.wantErr { + for i := range h { + if h[i] != tt.args.src.([]byte)[i] { + t.Errorf( + "Hash.Scan() didn't scan the %d src correctly (have %X, want %X)", + i, h[i], tt.args.src.([]byte)[i], + ) + } + } + } + }) + } +} + +func TestHash_Value(t *testing.T) { + b := []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0x10, 0x00, + } + var usedH Hash + usedH.SetBytes(b) + tests := []struct { + name string + h Hash + want driver.Value + wantErr bool + }{ + { + name: "Working value", + h: usedH, + want: b, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.h.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Hash.Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Hash.Value() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAddress_Scan(t *testing.T) { + type args struct { + src interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "working scan", + args: args{src: []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + }}, + wantErr: false, + }, + { + name: "non working scan", + args: args{src: int64(1234567890)}, + wantErr: true, + }, + { + name: "invalid length scan", + args: args{src: []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, + }}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &Address{} + if err := a.Scan(tt.args.src); (err != nil) != tt.wantErr { + t.Errorf("Address.Scan() error = %v, wantErr %v", err, tt.wantErr) + } + + if !tt.wantErr { + for i := range a { + if a[i] != tt.args.src.([]byte)[i] { + t.Errorf( + "Address.Scan() didn't scan the %d src correctly (have %X, want %X)", + i, a[i], tt.args.src.([]byte)[i], + ) + } + } + } + }) + } +} + +func TestAddress_Value(t *testing.T) { + b := []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + } + var usedA Address + usedA.SetBytes(b) + tests := []struct { + name string + a Address + want driver.Value + wantErr bool + }{ + { + name: "Working value", + a: usedA, + want: b, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.a.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Address.Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Address.Value() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAddress_Format(t *testing.T) { + b := []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + } + var addr Address + addr.SetBytes(b) + + tests := []struct { + name string + out string + want string + }{ + { + name: "println", + out: fmt.Sprintln(addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15\n", + }, + { + name: "print", + out: fmt.Sprint(addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + { + name: "printf-s", + out: func() string { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s", addr) + return buf.String() + }(), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + { + name: "printf-q", + out: fmt.Sprintf("%q", addr), + want: `"0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15"`, + }, + { + name: "printf-x", + out: fmt.Sprintf("%x", addr), + want: "b26f2b342aab24bcf63ea218c6a9274d30ab9a15", + }, + { + name: "printf-X", + out: fmt.Sprintf("%X", addr), + want: "B26F2B342AAB24BCF63EA218C6A9274D30AB9A15", + }, + { + name: "printf-#x", + out: fmt.Sprintf("%#x", addr), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15", + }, + { + name: "printf-v", + out: fmt.Sprintf("%v", addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + // The original default formatter for byte slice + { + name: "printf-d", + out: fmt.Sprintf("%d", addr), + want: "[178 111 43 52 42 171 36 188 246 62 162 24 198 169 39 77 48 171 154 21]", + }, + // Invalid format char. + { + name: "printf-t", + out: fmt.Sprintf("%t", addr), + want: "%!t(address=b26f2b342aab24bcf63ea218c6a9274d30ab9a15)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.out != tt.want { + t.Errorf("%s does not render as expected:\n got %s\nwant %s", tt.name, tt.out, tt.want) + } + }) + } +} + +func TestHash_Format(t *testing.T) { + var hash Hash + hash.SetBytes([]byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0x10, 0x00, + }) + + tests := []struct { + name string + out string + want string + }{ + { + name: "println", + out: fmt.Sprintln(hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000\n", + }, + { + name: "print", + out: fmt.Sprint(hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-s", + out: func() string { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s", hash) + return buf.String() + }(), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-q", + out: fmt.Sprintf("%q", hash), + want: `"0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000"`, + }, + { + name: "printf-x", + out: fmt.Sprintf("%x", hash), + want: "b26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-X", + out: fmt.Sprintf("%X", hash), + want: "B26F2B342AAB24BCF63EA218C6A9274D30AB9A15A218C6A9274D30AB9A151000", + }, + { + name: "printf-#x", + out: fmt.Sprintf("%#x", hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-#X", + out: fmt.Sprintf("%#X", hash), + want: "0XB26F2B342AAB24BCF63EA218C6A9274D30AB9A15A218C6A9274D30AB9A151000", + }, + { + name: "printf-v", + out: fmt.Sprintf("%v", hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + // The original default formatter for byte slice + { + name: "printf-d", + out: fmt.Sprintf("%d", hash), + want: "[178 111 43 52 42 171 36 188 246 62 162 24 198 169 39 77 48 171 154 21 162 24 198 169 39 77 48 171 154 21 16 0]", + }, + // Invalid format char. + { + name: "printf-t", + out: fmt.Sprintf("%t", hash), + want: "%!t(hash=b26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.out != tt.want { + t.Errorf("%s does not render as expected:\n got %s\nwant %s", tt.name, tt.out, tt.want) + } + }) + } +} + +func TestAddressEIP55(t *testing.T) { + addr := HexToAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + addrEIP55 := AddressEIP55(addr) + + if addr.Hex() != addrEIP55.String() { + t.Fatal("AddressEIP55 should match original address hex") + } + + blob, err := addrEIP55.MarshalJSON() + if err != nil { + t.Fatal("Failed to marshal AddressEIP55", err) + } + if strings.Trim(string(blob), "\"") != addr.Hex() { + t.Fatal("Address with checksum is expected") + } + var dec Address + if err := json.Unmarshal(blob, &dec); err != nil { + t.Fatal("Failed to unmarshal AddressEIP55", err) + } + if addr != dec { + 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) +} + +func TestDecimalUnmarshalJSON(t *testing.T) { + // These should error + for _, tc := range []string{``, `"`, `""`, `"-1"`} { + if err := new(Decimal).UnmarshalJSON([]byte(tc)); err == nil { + t.Errorf("input %s should cause error", tc) + } + } + // These should succeed + for _, tc := range []struct { + input string + want uint64 + }{ + {`"0"`, 0}, + {`"9223372036854775807"`, math.MaxInt64}, + {`"18446744073709551615"`, math.MaxUint64}, + } { + have := new(Decimal) + if err := have.UnmarshalJSON([]byte(tc.input)); err != nil { + t.Errorf("input %q triggered error: %v", tc.input, err) + } + if uint64(*have) != tc.want { + t.Errorf("input %q, have %d want %d", tc.input, *have, tc.want) + } + } +} diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go new file mode 100644 index 0000000..b8946e0 --- /dev/null +++ b/consensus/beacon/consensus.go @@ -0,0 +1,474 @@ +// 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 beacon + +import ( + "errors" + "fmt" + "math/big" + + "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/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" +) + +// Proof-of-stake protocol constants. +var ( + beaconDifficulty = common.Big0 // The default block difficulty in the beacon consensus + beaconNonce = types.EncodeNonce(0) // The default block nonce in the beacon consensus +) + +// Various error messages to mark blocks invalid. These should be private to +// prevent engine specific errors from being referenced in the remainder of the +// codebase, inherently breaking if the engine is swapped out. Please put common +// error types into the consensus package. +var ( + errTooManyUncles = errors.New("too many uncles") + errInvalidNonce = errors.New("invalid nonce") + errInvalidUncleHash = errors.New("invalid uncle hash") + errInvalidTimestamp = errors.New("invalid timestamp") +) + +// Beacon is a consensus engine that combines the eth1 consensus and proof-of-stake +// algorithm. There is a special flag inside to decide whether to use legacy consensus +// rules or new rules. The transition rule is described in the eth1/2 merge spec. +// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3675.md +// +// The beacon here is a half-functional consensus engine with partial functions which +// is only used for necessary consensus checks. The legacy consensus engine can be any +// engine implements the consensus interface (except the beacon itself). +type Beacon struct { + ethone consensus.Engine // Original consensus engine used in eth1, e.g. ethash or clique +} + +// New creates a consensus engine with the given embedded eth1 engine. +func New(ethone consensus.Engine) *Beacon { + if _, ok := ethone.(*Beacon); ok { + panic("nested consensus engine") + } + return &Beacon{ethone: ethone} +} + +// Author implements consensus.Engine, returning the verified author of the block. +func (beacon *Beacon) Author(header *types.Header) (common.Address, error) { + if !beacon.IsPoSHeader(header) { + return beacon.ethone.Author(header) + } + return header.Coinbase, nil +} + +// VerifyHeader checks whether a header conforms to the consensus rules of the +// stock Ethereum consensus engine. +func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { + reached, err := IsTTDReached(chain, header.ParentHash, header.Number.Uint64()-1) + if err != nil { + return err + } + if !reached { + return beacon.ethone.VerifyHeader(chain, header) + } + // Short circuit if the parent is not known + parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + // Sanity checks passed, do a proper verification + return beacon.verifyHeader(chain, header, parent) +} + +// errOut constructs an error channel with prefilled errors inside. +func errOut(n int, err error) chan error { + errs := make(chan error, n) + for i := 0; i < n; i++ { + errs <- err + } + return errs +} + +// splitHeaders splits the provided header batch into two parts according to +// the configured ttd. It requires the parent of header batch along with its +// td are stored correctly in chain. If ttd is not configured yet, all headers +// will be treated legacy PoW headers. +// Note, this function will not verify the header validity but just split them. +func (beacon *Beacon) splitHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) ([]*types.Header, []*types.Header, error) { + // TTD is not defined yet, all headers should be in legacy format. + ttd := chain.Config().TerminalTotalDifficulty + if ttd == nil { + return headers, nil, nil + } + ptd := chain.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1) + if ptd == nil { + return nil, nil, consensus.ErrUnknownAncestor + } + // The entire header batch already crosses the transition. + if ptd.Cmp(ttd) >= 0 { + return nil, headers, nil + } + var ( + preHeaders = headers + postHeaders []*types.Header + td = new(big.Int).Set(ptd) + tdPassed bool + ) + for i, header := range headers { + if tdPassed { + preHeaders = headers[:i] + postHeaders = headers[i:] + break + } + td = td.Add(td, header.Difficulty) + if td.Cmp(ttd) >= 0 { + // This is the last PoW header, it still belongs to + // the preHeaders, so we cannot split+break yet. + tdPassed = true + } + } + return preHeaders, postHeaders, nil +} + +// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers +// concurrently. The method returns a quit channel to abort the operations and +// a results channel to retrieve the async verifications. +// VerifyHeaders expect the headers to be ordered and continuous. +func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { + preHeaders, postHeaders, err := beacon.splitHeaders(chain, headers) + if err != nil { + return make(chan struct{}), errOut(len(headers), err) + } + if len(postHeaders) == 0 { + return beacon.ethone.VerifyHeaders(chain, headers) + } + if len(preHeaders) == 0 { + return beacon.verifyHeaders(chain, headers, nil) + } + // The transition point exists in the middle, separate the headers + // into two batches and apply different verification rules for them. + var ( + abort = make(chan struct{}) + results = make(chan error, len(headers)) + ) + go func() { + var ( + old, new, out = 0, len(preHeaders), 0 + errors = make([]error, len(headers)) + done = make([]bool, len(headers)) + oldDone, oldResult = beacon.ethone.VerifyHeaders(chain, preHeaders) + newDone, newResult = beacon.verifyHeaders(chain, postHeaders, preHeaders[len(preHeaders)-1]) + ) + // Collect the results + for { + for ; done[out]; out++ { + results <- errors[out] + if out == len(headers)-1 { + return + } + } + select { + case err := <-oldResult: + if !done[old] { // skip TTD-verified failures + errors[old], done[old] = err, true + } + old++ + case err := <-newResult: + errors[new], done[new] = err, true + new++ + case <-abort: + close(oldDone) + close(newDone) + return + } + } + }() + return abort, results +} + +// VerifyUncles verifies that the given block's uncles conform to the consensus +// rules of the Ethereum consensus engine. +func (beacon *Beacon) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + if !beacon.IsPoSHeader(block.Header()) { + return beacon.ethone.VerifyUncles(chain, block) + } + // Verify that there is no uncle block. It's explicitly disabled in the beacon + if len(block.Uncles()) > 0 { + return errTooManyUncles + } + return nil +} + +// verifyHeader checks whether a header conforms to the consensus rules of the +// stock Ethereum consensus engine. The difference between the beacon and classic is +// (a) The following fields are expected to be constants: +// - difficulty is expected to be 0 +// - nonce is expected to be 0 +// - unclehash is expected to be Hash(emptyHeader) +// to be the desired constants +// +// (b) we don't verify if a block is in the future anymore +// (c) the extradata is limited to 32 bytes +func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header) error { + // Ensure that the header's extra-data section is of a reasonable size + if len(header.Extra) > 32 { + return fmt.Errorf("extra-data longer than 32 bytes (%d)", len(header.Extra)) + } + // Verify the seal parts. Ensure the nonce and uncle hash are the expected value. + if header.Nonce != beaconNonce { + return errInvalidNonce + } + if header.UncleHash != types.EmptyUncleHash { + return errInvalidUncleHash + } + // Verify the timestamp + if header.Time <= parent.Time { + return errInvalidTimestamp + } + // Verify the block's difficulty to ensure it's the default constant + if beaconDifficulty.Cmp(header.Difficulty) != 0 { + return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, beaconDifficulty) + } + // Verify that the gas limit is <= 2^63-1 + if header.GasLimit > params.MaxGasLimit { + return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) + } + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) + } + // Verify that the block number is parent's +1 + if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(common.Big1) != 0 { + return consensus.ErrInvalidNumber + } + // Verify the header's EIP-1559 attributes. + if err := eip1559.VerifyEIP1559Header(chain.Config(), parent, header); err != nil { + return err + } + // Verify existence / non-existence of withdrawalsHash. + shanghai := chain.Config().IsShanghai(header.Number, header.Time) + if shanghai && header.WithdrawalsHash == nil { + return errors.New("missing withdrawalsHash") + } + if !shanghai && header.WithdrawalsHash != nil { + return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) + } + // Verify the existence / non-existence of cancun-specific header fields + cancun := chain.Config().IsCancun(header.Number, header.Time) + if !cancun { + switch { + case header.ExcessBlobGas != nil: + return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas) + case header.BlobGasUsed != nil: + return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed) + case header.ParentBeaconRoot != nil: + return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot) + } + } else { + if header.ParentBeaconRoot == nil { + return errors.New("header is missing beaconRoot") + } + if err := eip4844.VerifyEIP4844Header(parent, header); err != nil { + return err + } + } + return nil +} + +// verifyHeaders is similar to verifyHeader, but verifies a batch of headers +// concurrently. The method returns a quit channel to abort the operations and +// a results channel to retrieve the async verifications. An additional parent +// header will be passed if the relevant header is not in the database yet. +func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, ancestor *types.Header) (chan<- struct{}, <-chan error) { + var ( + abort = make(chan struct{}) + results = make(chan error, len(headers)) + ) + go func() { + for i, header := range headers { + var parent *types.Header + if i == 0 { + if ancestor != nil { + parent = ancestor + } else { + parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1) + } + } else if headers[i-1].Hash() == headers[i].ParentHash { + parent = headers[i-1] + } + if parent == nil { + select { + case <-abort: + return + case results <- consensus.ErrUnknownAncestor: + } + continue + } + err := beacon.verifyHeader(chain, header, parent) + select { + case <-abort: + return + case results <- err: + } + } + }() + return abort, results +} + +// Prepare implements consensus.Engine, initializing the difficulty field of a +// header to conform to the beacon protocol. The changes are done inline. +func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { + // Transition isn't triggered yet, use the legacy rules for preparation. + reached, err := IsTTDReached(chain, header.ParentHash, header.Number.Uint64()-1) + if err != nil { + return err + } + if !reached { + return beacon.ethone.Prepare(chain, header) + } + header.Difficulty = beaconDifficulty + return nil +} + +// Finalize implements consensus.Engine and processes withdrawals on top. +func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body) { + if !beacon.IsPoSHeader(header) { + beacon.ethone.Finalize(chain, header, state, body) + return + } + // Withdrawals processing. + for _, w := range body.Withdrawals { + // Convert amount from gwei to wei. + amount := new(uint256.Int).SetUint64(w.Amount) + amount = amount.Mul(amount, uint256.NewInt(params.GWei)) + state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) + } + // No block reward which is issued by consensus layer instead. +} + +// FinalizeAndAssemble implements consensus.Engine, setting the final state and +// assembling the block. +func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { + if !beacon.IsPoSHeader(header) { + return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts) + } + shanghai := chain.Config().IsShanghai(header.Number, header.Time) + if shanghai { + // All blocks after Shanghai must include a withdrawals root. + if body.Withdrawals == nil { + body.Withdrawals = make([]*types.Withdrawal, 0) + } + } else { + if len(body.Withdrawals) > 0 { + return nil, errors.New("withdrawals set before Shanghai activation") + } + } + // Finalize and assemble the block. + beacon.Finalize(chain, header, state, body) + + // Assign the final state root to header. + header.Root = state.IntermediateRoot(true) + + // Assemble and return the final block. + return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil +} + +// Seal generates a new sealing request for the given input block and pushes +// the result into the given channel. +// +// Note, the method returns immediately and will send the result async. More +// than one result may also be returned depending on the consensus algorithm. +func (beacon *Beacon) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { + if !beacon.IsPoSHeader(block.Header()) { + return beacon.ethone.Seal(chain, block, results, stop) + } + // The seal verification is done by the external consensus engine, + // return directly without pushing any block back. In another word + // beacon won't return any result by `results` channel which may + // blocks the receiver logic forever. + return nil +} + +// SealHash returns the hash of a block prior to it being sealed. +func (beacon *Beacon) SealHash(header *types.Header) common.Hash { + return beacon.ethone.SealHash(header) +} + +// CalcDifficulty is the difficulty adjustment algorithm. It returns +// the difficulty that a new block should have when created at time +// given the parent block's time and difficulty. +func (beacon *Beacon) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + // Transition isn't triggered yet, use the legacy rules for calculation + if reached, _ := IsTTDReached(chain, parent.Hash(), parent.Number.Uint64()); !reached { + return beacon.ethone.CalcDifficulty(chain, time, parent) + } + return beaconDifficulty +} + +// APIs implements consensus.Engine, returning the user facing RPC APIs. +func (beacon *Beacon) APIs(chain consensus.ChainHeaderReader) []rpc.API { + return beacon.ethone.APIs(chain) +} + +// Close shutdowns the consensus engine +func (beacon *Beacon) Close() error { + return beacon.ethone.Close() +} + +// IsPoSHeader reports the header belongs to the PoS-stage with some special fields. +// This function is not suitable for a part of APIs like Prepare or CalcDifficulty +// because the header difficulty is not set yet. +func (beacon *Beacon) IsPoSHeader(header *types.Header) bool { + if header.Difficulty == nil { + panic("IsPoSHeader called with invalid difficulty") + } + return header.Difficulty.Cmp(beaconDifficulty) == 0 +} + +// InnerEngine returns the embedded eth1 consensus engine. +func (beacon *Beacon) InnerEngine() consensus.Engine { + return beacon.ethone +} + +// SetThreads updates the mining threads. Delegate the call +// to the eth1 engine if it's threaded. +func (beacon *Beacon) SetThreads(threads int) { + type threaded interface { + SetThreads(threads int) + } + if th, ok := beacon.ethone.(threaded); ok { + th.SetThreads(threads) + } +} + +// IsTTDReached checks if the TotalTerminalDifficulty has been surpassed on the `parentHash` block. +// It depends on the parentHash already being stored in the database. +// If the parentHash is not stored in the database a UnknownAncestor error is returned. +func IsTTDReached(chain consensus.ChainHeaderReader, parentHash common.Hash, parentNumber uint64) (bool, error) { + if chain.Config().TerminalTotalDifficulty == nil { + return false, nil + } + td := chain.GetTd(parentHash, parentNumber) + if td == nil { + return false, consensus.ErrUnknownAncestor + } + return td.Cmp(chain.Config().TerminalTotalDifficulty) >= 0, nil +} diff --git a/consensus/beacon/faker.go b/consensus/beacon/faker.go new file mode 100644 index 0000000..981e345 --- /dev/null +++ b/consensus/beacon/faker.go @@ -0,0 +1,41 @@ +// 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 beacon + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" +) + +// NewFaker creates a fake consensus engine for testing. +// The fake engine simulates a merged network. +// It can not be used to test the merge transition. +// This type is needed since the fakeChainReader can not be used with +// a normal beacon consensus engine. +func NewFaker() consensus.Engine { + return new(faker) +} + +type faker struct { + Beacon +} + +func (f *faker) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + return beaconDifficulty +} diff --git a/consensus/clique/api.go b/consensus/clique/api.go new file mode 100644 index 0000000..374b506 --- /dev/null +++ b/consensus/clique/api.go @@ -0,0 +1,235 @@ +// 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 clique + +import ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" +) + +// API is a user facing RPC API to allow controlling the signer and voting +// mechanisms of the proof-of-authority scheme. +type API struct { + chain consensus.ChainHeaderReader + clique *Clique +} + +// GetSnapshot retrieves the state snapshot at a given block. +func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) { + // Retrieve the requested block number (or current if none requested) + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + // Ensure we have an actually valid block and return its snapshot + if header == nil { + return nil, errUnknownBlock + } + return api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) +} + +// GetSnapshotAtHash retrieves the state snapshot at a given block. +func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) { + header := api.chain.GetHeaderByHash(hash) + if header == nil { + return nil, errUnknownBlock + } + return api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) +} + +// GetSigners retrieves the list of authorized signers at the specified block. +func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) { + // Retrieve the requested block number (or current if none requested) + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + // Ensure we have an actually valid block and return the signers from its snapshot + if header == nil { + return nil, errUnknownBlock + } + snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + return snap.signers(), nil +} + +// GetSignersAtHash retrieves the list of authorized signers at the specified block. +func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) { + header := api.chain.GetHeaderByHash(hash) + if header == nil { + return nil, errUnknownBlock + } + snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + return snap.signers(), nil +} + +// Proposals returns the current proposals the node tries to uphold and vote on. +func (api *API) Proposals() map[common.Address]bool { + api.clique.lock.RLock() + defer api.clique.lock.RUnlock() + + proposals := make(map[common.Address]bool) + for address, auth := range api.clique.proposals { + proposals[address] = auth + } + return proposals +} + +// Propose injects a new authorization proposal that the signer will attempt to +// push through. +func (api *API) Propose(address common.Address, auth bool) { + api.clique.lock.Lock() + defer api.clique.lock.Unlock() + + api.clique.proposals[address] = auth +} + +// Discard drops a currently running proposal, stopping the signer from casting +// further votes (either for or against). +func (api *API) Discard(address common.Address) { + api.clique.lock.Lock() + defer api.clique.lock.Unlock() + + delete(api.clique.proposals, address) +} + +type status struct { + InturnPercent float64 `json:"inturnPercent"` + SigningStatus map[common.Address]int `json:"sealerActivity"` + NumBlocks uint64 `json:"numBlocks"` +} + +// Status returns the status of the last N blocks, +// - the number of active signers, +// - the number of signers, +// - the percentage of in-turn blocks +func (api *API) Status() (*status, error) { + var ( + numBlocks = uint64(64) + header = api.chain.CurrentHeader() + diff = uint64(0) + optimals = 0 + ) + snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil { + return nil, err + } + var ( + signers = snap.signers() + end = header.Number.Uint64() + start = end - numBlocks + ) + if numBlocks > end { + start = 1 + numBlocks = end - start + } + signStatus := make(map[common.Address]int) + for _, s := range signers { + signStatus[s] = 0 + } + for n := start; n < end; n++ { + h := api.chain.GetHeaderByNumber(n) + if h == nil { + return nil, fmt.Errorf("missing block %d", n) + } + if h.Difficulty.Cmp(diffInTurn) == 0 { + optimals++ + } + diff += h.Difficulty.Uint64() + sealer, err := api.clique.Author(h) + if err != nil { + return nil, err + } + signStatus[sealer]++ + } + return &status{ + InturnPercent: float64(100*optimals) / float64(numBlocks), + SigningStatus: signStatus, + NumBlocks: numBlocks, + }, nil +} + +type blockNumberOrHashOrRLP struct { + *rpc.BlockNumberOrHash + RLP hexutil.Bytes `json:"rlp,omitempty"` +} + +func (sb *blockNumberOrHashOrRLP) UnmarshalJSON(data []byte) error { + bnOrHash := new(rpc.BlockNumberOrHash) + // Try to unmarshal bNrOrHash + if err := bnOrHash.UnmarshalJSON(data); err == nil { + sb.BlockNumberOrHash = bnOrHash + return nil + } + // Try to unmarshal RLP + var input string + if err := json.Unmarshal(data, &input); err != nil { + return err + } + blob, err := hexutil.Decode(input) + if err != nil { + return err + } + sb.RLP = blob + return nil +} + +// GetSigner returns the signer for a specific clique block. +// Can be called with a block number, a block hash or a rlp encoded blob. +// The RLP encoded blob can either be a block or a header. +func (api *API) GetSigner(rlpOrBlockNr *blockNumberOrHashOrRLP) (common.Address, error) { + if len(rlpOrBlockNr.RLP) == 0 { + blockNrOrHash := rlpOrBlockNr.BlockNumberOrHash + var header *types.Header + if blockNrOrHash == nil { + header = api.chain.CurrentHeader() + } else if hash, ok := blockNrOrHash.Hash(); ok { + header = api.chain.GetHeaderByHash(hash) + } else if number, ok := blockNrOrHash.Number(); ok { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + if header == nil { + return common.Address{}, fmt.Errorf("missing block %v", blockNrOrHash.String()) + } + return api.clique.Author(header) + } + block := new(types.Block) + if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, block); err == nil { + return api.clique.Author(block.Header()) + } + header := new(types.Header) + if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, header); err != nil { + return common.Address{}, err + } + return api.clique.Author(header) +} diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go new file mode 100644 index 0000000..c9e9484 --- /dev/null +++ b/consensus/clique/clique.go @@ -0,0 +1,781 @@ +// 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 clique implements the proof-of-authority consensus engine. +package clique + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/big" + "math/rand" + "sync" + "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/lru" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "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/rpc" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/crypto/sha3" +) + +const ( + checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database + inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory + inmemorySignatures = 4096 // Number of recent block signatures to keep in memory + + wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers +) + +// Clique proof-of-authority protocol constants. +var ( + epochLength = uint64(30000) // Default number of blocks after which to checkpoint and reset the pending votes + + extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity + extraSeal = crypto.SignatureLength // Fixed number of extra-data suffix bytes reserved for signer seal + + nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer + nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer. + + uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. + + diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures + diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures +) + +// Various error messages to mark blocks invalid. These should be private to +// prevent engine specific errors from being referenced in the remainder of the +// codebase, inherently breaking if the engine is swapped out. Please put common +// error types into the consensus package. +var ( + // errUnknownBlock is returned when the list of signers is requested for a block + // that is not part of the local blockchain. + errUnknownBlock = errors.New("unknown block") + + // errInvalidCheckpointBeneficiary is returned if a checkpoint/epoch transition + // block has a beneficiary set to non-zeroes. + errInvalidCheckpointBeneficiary = errors.New("beneficiary in checkpoint block non-zero") + + // errInvalidVote is returned if a nonce value is something else that the two + // allowed constants of 0x00..0 or 0xff..f. + errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f") + + // errInvalidCheckpointVote is returned if a checkpoint/epoch transition block + // has a vote nonce set to non-zeroes. + errInvalidCheckpointVote = errors.New("vote nonce in checkpoint block non-zero") + + // errMissingVanity is returned if a block's extra-data section is shorter than + // 32 bytes, which is required to store the signer vanity. + errMissingVanity = errors.New("extra-data 32 byte vanity prefix missing") + + // errMissingSignature is returned if a block's extra-data section doesn't seem + // to contain a 65 byte secp256k1 signature. + errMissingSignature = errors.New("extra-data 65 byte signature suffix missing") + + // errExtraSigners is returned if non-checkpoint block contain signer data in + // their extra-data fields. + errExtraSigners = errors.New("non-checkpoint block contains extra signer list") + + // errInvalidCheckpointSigners is returned if a checkpoint block contains an + // invalid list of signers (i.e. non divisible by 20 bytes). + errInvalidCheckpointSigners = errors.New("invalid signer list on checkpoint block") + + // errMismatchingCheckpointSigners is returned if a checkpoint block contains a + // list of signers different than the one the local node calculated. + errMismatchingCheckpointSigners = errors.New("mismatching signer list on checkpoint block") + + // errInvalidMixDigest is returned if a block's mix digest is non-zero. + errInvalidMixDigest = errors.New("non-zero mix digest") + + // errInvalidUncleHash is returned if a block contains an non-empty uncle list. + errInvalidUncleHash = errors.New("non empty uncle hash") + + // errInvalidDifficulty is returned if the difficulty of a block neither 1 or 2. + errInvalidDifficulty = errors.New("invalid difficulty") + + // errWrongDifficulty is returned if the difficulty of a block doesn't match the + // turn of the signer. + errWrongDifficulty = errors.New("wrong difficulty") + + // errInvalidTimestamp is returned if the timestamp of a block is lower than + // the previous block's timestamp + the minimum block period. + errInvalidTimestamp = errors.New("invalid timestamp") + + // errInvalidVotingChain is returned if an authorization list is attempted to + // be modified via out-of-range or non-contiguous headers. + errInvalidVotingChain = errors.New("invalid voting chain") + + // errUnauthorizedSigner is returned if a header is signed by a non-authorized entity. + errUnauthorizedSigner = errors.New("unauthorized signer") + + // errRecentlySigned is returned if a header is signed by an authorized entity + // that already signed a header recently, thus is temporarily not allowed to. + errRecentlySigned = errors.New("recently signed") +) + +// SignerFn hashes and signs the data to be signed by a backing account. +type SignerFn func(signer accounts.Account, mimeType string, message []byte) ([]byte, error) + +// ecrecover extracts the Ethereum account address from a signed header. +func ecrecover(header *types.Header, sigcache *sigLRU) (common.Address, error) { + // If the signature's already cached, return that + hash := header.Hash() + if address, known := sigcache.Get(hash); known { + return address, nil + } + // Retrieve the signature from the header extra-data + if len(header.Extra) < extraSeal { + return common.Address{}, errMissingSignature + } + signature := header.Extra[len(header.Extra)-extraSeal:] + + // Recover the public key and the Ethereum address + pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature) + if err != nil { + return common.Address{}, err + } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + + sigcache.Add(hash, signer) + return signer, nil +} + +// Clique is the proof-of-authority consensus engine proposed to support the +// Ethereum testnet following the Ropsten attacks. +type Clique struct { + config *params.CliqueConfig // Consensus engine configuration parameters + db ethdb.Database // Database to store and retrieve snapshot checkpoints + + recents *lru.Cache[common.Hash, *Snapshot] // Snapshots for recent block to speed up reorgs + signatures *sigLRU // Signatures of recent blocks to speed up mining + + proposals map[common.Address]bool // Current list of proposals we are pushing + + signer common.Address // Ethereum address of the signing key + signFn SignerFn // Signer function to authorize hashes with + lock sync.RWMutex // Protects the signer and proposals fields + + // The fields below are for testing only + fakeDiff bool // Skip difficulty verifications +} + +// New creates a Clique proof-of-authority consensus engine with the initial +// signers set to the ones provided by the user. +func New(config *params.CliqueConfig, db ethdb.Database) *Clique { + // Set any missing consensus parameters to their defaults + conf := *config + if conf.Epoch == 0 { + conf.Epoch = epochLength + } + // Allocate the snapshot caches and create the engine + recents := lru.NewCache[common.Hash, *Snapshot](inmemorySnapshots) + signatures := lru.NewCache[common.Hash, common.Address](inmemorySignatures) + + return &Clique{ + config: &conf, + db: db, + recents: recents, + signatures: signatures, + proposals: make(map[common.Address]bool), + } +} + +// Author implements consensus.Engine, returning the Ethereum address recovered +// from the signature in the header's extra-data section. +func (c *Clique) Author(header *types.Header) (common.Address, error) { + return ecrecover(header, c.signatures) +} + +// VerifyHeader checks whether a header conforms to the consensus rules. +func (c *Clique) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { + return c.verifyHeader(chain, header, nil) +} + +// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The +// method returns a quit channel to abort the operations and a results channel to +// retrieve the async verifications (the order is that of the input slice). +func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { + abort := make(chan struct{}) + results := make(chan error, len(headers)) + + go func() { + for i, header := range headers { + err := c.verifyHeader(chain, header, headers[:i]) + + select { + case <-abort: + return + case results <- err: + } + } + }() + return abort, results +} + +// verifyHeader checks whether a header conforms to the consensus rules.The +// caller may optionally pass in a batch of parents (ascending order) to avoid +// looking those up from the database. This is useful for concurrently verifying +// a batch of new headers. +func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { + if header.Number == nil { + return errUnknownBlock + } + number := header.Number.Uint64() + + // Don't waste time checking blocks from the future + if header.Time > uint64(time.Now().Unix()) { + return consensus.ErrFutureBlock + } + // Checkpoint blocks need to enforce zero beneficiary + checkpoint := (number % c.config.Epoch) == 0 + if checkpoint && header.Coinbase != (common.Address{}) { + return errInvalidCheckpointBeneficiary + } + // Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints + if !bytes.Equal(header.Nonce[:], nonceAuthVote) && !bytes.Equal(header.Nonce[:], nonceDropVote) { + return errInvalidVote + } + if checkpoint && !bytes.Equal(header.Nonce[:], nonceDropVote) { + return errInvalidCheckpointVote + } + // Check that the extra-data contains both the vanity and signature + if len(header.Extra) < extraVanity { + return errMissingVanity + } + if len(header.Extra) < extraVanity+extraSeal { + return errMissingSignature + } + // Ensure that the extra-data contains a signer list on checkpoint, but none otherwise + signersBytes := len(header.Extra) - extraVanity - extraSeal + if !checkpoint && signersBytes != 0 { + return errExtraSigners + } + if checkpoint && signersBytes%common.AddressLength != 0 { + return errInvalidCheckpointSigners + } + // Ensure that the mix digest is zero as we don't have fork protection currently + if header.MixDigest != (common.Hash{}) { + return errInvalidMixDigest + } + // Ensure that the block doesn't contain any uncles which are meaningless in PoA + if header.UncleHash != uncleHash { + return errInvalidUncleHash + } + // Ensure that the block's difficulty is meaningful (may not be correct at this point) + if number > 0 { + if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0) { + return errInvalidDifficulty + } + } + // Verify that the gas limit is <= 2^63-1 + if header.GasLimit > params.MaxGasLimit { + return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) + } + if chain.Config().IsShanghai(header.Number, header.Time) { + 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) +} + +// verifyCascadingFields verifies all the header fields that are not standalone, +// rather depend on a batch of previous headers. The caller may optionally pass +// in a batch of parents (ascending order) to avoid looking those up from the +// database. This is useful for concurrently verifying a batch of new headers. +func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { + // The genesis block is the always valid dead-end + number := header.Number.Uint64() + if number == 0 { + return nil + } + // Ensure that the block's timestamp isn't too close to its parent + var parent *types.Header + if len(parents) > 0 { + parent = parents[len(parents)-1] + } else { + parent = chain.GetHeader(header.ParentHash, number-1) + } + if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { + return consensus.ErrUnknownAncestor + } + if parent.Time+c.config.Period > header.Time { + return errInvalidTimestamp + } + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) + } + if !chain.Config().IsLondon(header.Number) { + // Verify BaseFee not present before EIP-1559 fork. + if header.BaseFee != nil { + return fmt.Errorf("invalid baseFee before fork: have %d, want ", header.BaseFee) + } + if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { + return err + } + } else if err := eip1559.VerifyEIP1559Header(chain.Config(), parent, header); err != nil { + // Verify the header's EIP-1559 attributes. + return err + } + // Retrieve the snapshot needed to verify this header and cache it + snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + // If the block is a checkpoint block, verify the signer list + if number%c.config.Epoch == 0 { + signers := make([]byte, len(snap.Signers)*common.AddressLength) + for i, signer := range snap.signers() { + copy(signers[i*common.AddressLength:], signer[:]) + } + extraSuffix := len(header.Extra) - extraSeal + if !bytes.Equal(header.Extra[extraVanity:extraSuffix], signers) { + return errMismatchingCheckpointSigners + } + } + // All basic checks passed, verify the seal and return + return c.verifySeal(snap, header, parents) +} + +// snapshot retrieves the authorization snapshot at a given point in time. +func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { + // Search for a snapshot in memory or on disk for checkpoints + var ( + headers []*types.Header + snap *Snapshot + ) + for snap == nil { + // If an in-memory snapshot was found, use that + if s, ok := c.recents.Get(hash); ok { + snap = s + break + } + // If an on-disk checkpoint snapshot can be found, use that + if number%checkpointInterval == 0 { + if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { + log.Trace("Loaded voting snapshot from disk", "number", number, "hash", hash) + snap = s + break + } + } + // If we're at the genesis, snapshot the initial state. Alternatively if we're + // at a checkpoint block without a parent (light client CHT), or we have piled + // up more headers than allowed to be reorged (chain reinit from a freezer), + // consider the checkpoint trusted and snapshot it. + if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) { + checkpoint := chain.GetHeaderByNumber(number) + if checkpoint != nil { + hash := checkpoint.Hash() + + signers := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength) + for i := 0; i < len(signers); i++ { + copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:]) + } + snap = newSnapshot(c.config, c.signatures, number, hash, signers) + if err := snap.store(c.db); err != nil { + return nil, err + } + log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash) + break + } + } + // No snapshot for this header, gather the header and move backward + var header *types.Header + if len(parents) > 0 { + // If we have explicit parents, pick from there (enforced) + header = parents[len(parents)-1] + if header.Hash() != hash || header.Number.Uint64() != number { + return nil, consensus.ErrUnknownAncestor + } + parents = parents[:len(parents)-1] + } else { + // No explicit parents (or no more left), reach out to the database + header = chain.GetHeader(hash, number) + if header == nil { + return nil, consensus.ErrUnknownAncestor + } + } + headers = append(headers, header) + number, hash = number-1, header.ParentHash + } + // Previous snapshot found, apply any pending headers on top of it + for i := 0; i < len(headers)/2; i++ { + headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i] + } + snap, err := snap.apply(headers) + if err != nil { + return nil, err + } + c.recents.Add(snap.Hash, snap) + + // If we've generated a new checkpoint snapshot, save to disk + if snap.Number%checkpointInterval == 0 && len(headers) > 0 { + if err = snap.store(c.db); err != nil { + return nil, err + } + log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash) + } + return snap, err +} + +// VerifyUncles implements consensus.Engine, always returning an error for any +// uncles as this consensus mechanism doesn't permit uncles. +func (c *Clique) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + if len(block.Uncles()) > 0 { + return errors.New("uncles not allowed") + } + return nil +} + +// verifySeal checks whether the signature contained in the header satisfies the +// consensus protocol requirements. The method accepts an optional list of parent +// headers that aren't yet part of the local blockchain to generate the snapshots +// from. +func (c *Clique) verifySeal(snap *Snapshot, header *types.Header, parents []*types.Header) error { + // Verifying the genesis block is not supported + number := header.Number.Uint64() + if number == 0 { + return errUnknownBlock + } + // Resolve the authorization key and check against signers + signer, err := ecrecover(header, c.signatures) + if err != nil { + return err + } + if _, ok := snap.Signers[signer]; !ok { + return errUnauthorizedSigner + } + for seen, recent := range snap.Recents { + if recent == signer { + // Signer is among recents, only fail if the current block doesn't shift it out + if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit { + return errRecentlySigned + } + } + } + // Ensure that the difficulty corresponds to the turn-ness of the signer + if !c.fakeDiff { + inturn := snap.inturn(header.Number.Uint64(), signer) + if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { + return errWrongDifficulty + } + if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 { + return errWrongDifficulty + } + } + return nil +} + +// Prepare implements consensus.Engine, preparing all the consensus fields of the +// header for running the transactions on top. +func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { + // If the block isn't a checkpoint, cast a random vote (good enough for now) + header.Coinbase = common.Address{} + header.Nonce = types.BlockNonce{} + + number := header.Number.Uint64() + // Assemble the voting snapshot to check which votes make sense + snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return err + } + c.lock.RLock() + if number%c.config.Epoch != 0 { + // Gather all the proposals that make sense voting on + addresses := make([]common.Address, 0, len(c.proposals)) + for address, authorize := range c.proposals { + if snap.validVote(address, authorize) { + addresses = append(addresses, address) + } + } + // If there's pending proposals, cast a vote on them + if len(addresses) > 0 { + header.Coinbase = addresses[rand.Intn(len(addresses))] + if c.proposals[header.Coinbase] { + copy(header.Nonce[:], nonceAuthVote) + } else { + copy(header.Nonce[:], nonceDropVote) + } + } + } + + // Copy signer protected by mutex to avoid race condition + signer := c.signer + c.lock.RUnlock() + + // Set the correct difficulty + header.Difficulty = calcDifficulty(snap, signer) + + // Ensure the extra data has all its components + if len(header.Extra) < extraVanity { + header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) + } + header.Extra = header.Extra[:extraVanity] + + if number%c.config.Epoch == 0 { + for _, signer := range snap.signers() { + header.Extra = append(header.Extra, signer[:]...) + } + } + header.Extra = append(header.Extra, make([]byte, extraSeal)...) + + // Mix digest is reserved for now, set to empty + header.MixDigest = common.Hash{} + + // Ensure the timestamp has the correct delay + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + header.Time = parent.Time + c.config.Period + if header.Time < uint64(time.Now().Unix()) { + header.Time = uint64(time.Now().Unix()) + } + return nil +} + +// Finalize implements consensus.Engine. There is no post-transaction +// consensus rules in clique, do nothing here. +func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body) { + // No block rewards in PoA, so the state remains as is +} + +// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, +// nor block rewards given, and returns the final block. +func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { + if len(body.Withdrawals) > 0 { + return nil, errors.New("clique does not support withdrawals") + } + // Finalize block + c.Finalize(chain, header, state, body) + + // Assign the final state root to header. + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + + // Assemble and return the final block for sealing. + return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil +} + +// Authorize injects a private key into the consensus engine to mint new blocks +// with. +func (c *Clique) Authorize(signer common.Address, signFn SignerFn) { + c.lock.Lock() + defer c.lock.Unlock() + + c.signer = signer + c.signFn = signFn +} + +// Seal implements consensus.Engine, attempting to create a sealed block using +// the local signing credentials. +func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { + header := block.Header() + + // Sealing the genesis block is not supported + number := header.Number.Uint64() + if number == 0 { + return errUnknownBlock + } + // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) + if c.config.Period == 0 && len(block.Transactions()) == 0 { + return errors.New("sealing paused while waiting for transactions") + } + // Don't hold the signer fields for the entire sealing procedure + c.lock.RLock() + signer, signFn := c.signer, c.signFn + c.lock.RUnlock() + + // Bail out if we're unauthorized to sign a block + snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) + if err != nil { + return err + } + if _, authorized := snap.Signers[signer]; !authorized { + return errUnauthorizedSigner + } + // If we're amongst the recent signers, wait for the next block + for seen, recent := range snap.Recents { + if recent == signer { + // Signer is among recents, only wait if the current block doesn't shift it out + if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit { + return errors.New("signed recently, must wait for others") + } + } + } + // Sweet, the protocol permits us to sign the block, wait for our time + delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple + if header.Difficulty.Cmp(diffNoTurn) == 0 { + // It's not our turn explicitly to sign, delay it a bit + wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime + delay += time.Duration(rand.Int63n(int64(wiggle))) + + log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) + } + // Sign all the things! + sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeClique, CliqueRLP(header)) + if err != nil { + return err + } + copy(header.Extra[len(header.Extra)-extraSeal:], sighash) + // Wait until sealing is terminated or delay timeout. + log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) + go func() { + select { + case <-stop: + return + case <-time.After(delay): + } + + select { + case results <- block.WithSeal(header): + default: + log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header)) + } + }() + + return nil +} + +// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty +// that a new block should have: +// * DIFF_NOTURN(2) if BLOCK_NUMBER % SIGNER_COUNT != SIGNER_INDEX +// * DIFF_INTURN(1) if BLOCK_NUMBER % SIGNER_COUNT == SIGNER_INDEX +func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil) + if err != nil { + return nil + } + c.lock.RLock() + signer := c.signer + c.lock.RUnlock() + return calcDifficulty(snap, signer) +} + +func calcDifficulty(snap *Snapshot, signer common.Address) *big.Int { + if snap.inturn(snap.Number+1, signer) { + return new(big.Int).Set(diffInTurn) + } + return new(big.Int).Set(diffNoTurn) +} + +// SealHash returns the hash of a block prior to it being sealed. +func (c *Clique) SealHash(header *types.Header) common.Hash { + return SealHash(header) +} + +// Close implements consensus.Engine. It's a noop for clique as there are no background threads. +func (c *Clique) Close() error { + return nil +} + +// APIs implements consensus.Engine, returning the user facing RPC API to allow +// controlling the signer voting. +func (c *Clique) APIs(chain consensus.ChainHeaderReader) []rpc.API { + return []rpc.API{{ + Namespace: "clique", + Service: &API{chain: chain, clique: c}, + }} +} + +// SealHash returns the hash of a block prior to it being sealed. +func SealHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewLegacyKeccak256() + encodeSigHeader(hasher, header) + hasher.(crypto.KeccakState).Read(hash[:]) + return hash +} + +// CliqueRLP returns the rlp bytes which needs to be signed for the proof-of-authority +// sealing. The RLP to sign consists of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// Note, the method requires the extra data to be at least 65 bytes, otherwise it +// panics. This is done to avoid accidentally using both forms (signature present +// or not), which could be abused to produce different hashes for the same header. +func CliqueRLP(header *types.Header) []byte { + b := new(bytes.Buffer) + encodeSigHeader(b, header) + return b.Bytes() +} + +func encodeSigHeader(w io.Writer, header *types.Header) { + enc := []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short + header.MixDigest, + header.Nonce, + } + if header.BaseFee != nil { + enc = append(enc, header.BaseFee) + } + 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/clique/clique_test.go b/consensus/clique/clique_test.go new file mode 100644 index 0000000..8ef8dbf --- /dev/null +++ b/consensus/clique/clique_test.go @@ -0,0 +1,125 @@ +// 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 clique + +import ( + "math/big" + "testing" + + "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/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// This test case is a repro of an annoying bug that took us forever to catch. +// In Clique PoA networks (Görli, etc), consecutive blocks might have +// the same state root (no block subsidy, empty block). If a node crashes, the +// chain ends up losing the recent state and needs to regenerate it from blocks +// already in the database. The bug was that processing the block *prior* to an +// empty one **also completes** the empty one, ending up in a known-block error. +func TestReimportMirroredState(t *testing.T) { + // Initialize a Clique chain with a single signer + var ( + db = rawdb.NewMemoryDatabase() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + engine = New(params.AllCliqueProtocolChanges.Clique, db) + signer = new(types.HomesteadSigner) + ) + genspec := &core.Genesis{ + Config: params.AllCliqueProtocolChanges, + ExtraData: make([]byte, extraVanity+common.AddressLength+extraSeal), + Alloc: map[common.Address]types.Account{ + addr: {Balance: big.NewInt(10000000000000000)}, + }, + BaseFee: big.NewInt(params.InitialBaseFee), + } + copy(genspec.ExtraData[extraVanity:], addr[:]) + + // Generate a batch of blocks, each properly signed + chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genspec, nil, engine, vm.Config{}, nil, nil) + defer chain.Stop() + + _, blocks, _ := core.GenerateChainWithGenesis(genspec, engine, 3, func(i int, block *core.BlockGen) { + // The chain maker doesn't have access to a chain, so the difficulty will be + // lets unset (nil). Set it here to the correct value. + block.SetDifficulty(diffInTurn) + + // We want to simulate an empty middle block, having the same state as the + // first one. The last is needs a state change again to force a reorg. + if i != 1 { + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr), common.Address{0x00}, new(big.Int), params.TxGas, block.BaseFee(), nil), signer, key) + if err != nil { + panic(err) + } + block.AddTxWithChain(chain, tx) + } + }) + for i, block := range blocks { + header := block.Header() + if i > 0 { + header.ParentHash = blocks[i-1].Hash() + } + header.Extra = make([]byte, extraVanity+extraSeal) + header.Difficulty = diffInTurn + + sig, _ := crypto.Sign(SealHash(header).Bytes(), key) + copy(header.Extra[len(header.Extra)-extraSeal:], sig) + blocks[i] = block.WithSeal(header) + } + // Insert the first two blocks and make sure the chain is valid + db = rawdb.NewMemoryDatabase() + chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil) + defer chain.Stop() + + if _, err := chain.InsertChain(blocks[:2]); err != nil { + t.Fatalf("failed to insert initial blocks: %v", err) + } + if head := chain.CurrentBlock().Number.Uint64(); head != 2 { + t.Fatalf("chain head mismatch: have %d, want %d", head, 2) + } + + // Simulate a crash by creating a new chain on top of the database, without + // flushing the dirty states out. Insert the last block, triggering a sidechain + // reimport. + chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil) + defer chain.Stop() + + if _, err := chain.InsertChain(blocks[2:]); err != nil { + t.Fatalf("failed to insert final block: %v", err) + } + if head := chain.CurrentBlock().Number.Uint64(); head != 3 { + t.Fatalf("chain head mismatch: have %d, want %d", head, 3) + } +} + +func TestSealHash(t *testing.T) { + have := SealHash(&types.Header{ + Difficulty: new(big.Int), + Number: new(big.Int), + Extra: make([]byte, 32+65), + BaseFee: new(big.Int), + }) + want := common.HexToHash("0xbd3d1fa43fbc4c5bfcc91b179ec92e2861df3654de60468beb908ff805359e8f") + if have != want { + t.Errorf("have %x, want %x", have, want) + } +} diff --git a/consensus/clique/snapshot.go b/consensus/clique/snapshot.go new file mode 100644 index 0000000..d0b15e9 --- /dev/null +++ b/consensus/clique/snapshot.go @@ -0,0 +1,311 @@ +// 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 clique + +import ( + "bytes" + "encoding/json" + "maps" + "slices" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +// Vote represents a single vote that an authorized signer made to modify the +// list of authorizations. +type Vote struct { + Signer common.Address `json:"signer"` // Authorized signer that cast this vote + Block uint64 `json:"block"` // Block number the vote was cast in (expire old votes) + Address common.Address `json:"address"` // Account being voted on to change its authorization + Authorize bool `json:"authorize"` // Whether to authorize or deauthorize the voted account +} + +// Tally is a simple vote tally to keep the current score of votes. Votes that +// go against the proposal aren't counted since it's equivalent to not voting. +type Tally struct { + Authorize bool `json:"authorize"` // Whether the vote is about authorizing or kicking someone + Votes int `json:"votes"` // Number of votes until now wanting to pass the proposal +} + +type sigLRU = lru.Cache[common.Hash, common.Address] + +// Snapshot is the state of the authorization voting at a given point in time. +type Snapshot struct { + config *params.CliqueConfig // Consensus engine parameters to fine tune behavior + sigcache *sigLRU // Cache of recent block signatures to speed up ecrecover + + Number uint64 `json:"number"` // Block number where the snapshot was created + Hash common.Hash `json:"hash"` // Block hash where the snapshot was created + Signers map[common.Address]struct{} `json:"signers"` // Set of authorized signers at this moment + Recents map[uint64]common.Address `json:"recents"` // Set of recent signers for spam protections + Votes []*Vote `json:"votes"` // List of votes cast in chronological order + Tally map[common.Address]Tally `json:"tally"` // Current vote tally to avoid recalculating +} + +// newSnapshot creates a new snapshot with the specified startup parameters. This +// method does not initialize the set of recent signers, so only ever use if for +// the genesis block. +func newSnapshot(config *params.CliqueConfig, sigcache *sigLRU, number uint64, hash common.Hash, signers []common.Address) *Snapshot { + snap := &Snapshot{ + config: config, + sigcache: sigcache, + Number: number, + Hash: hash, + Signers: make(map[common.Address]struct{}), + Recents: make(map[uint64]common.Address), + Tally: make(map[common.Address]Tally), + } + for _, signer := range signers { + snap.Signers[signer] = struct{}{} + } + return snap +} + +// loadSnapshot loads an existing snapshot from the database. +func loadSnapshot(config *params.CliqueConfig, sigcache *sigLRU, db ethdb.Database, hash common.Hash) (*Snapshot, error) { + blob, err := db.Get(append(rawdb.CliqueSnapshotPrefix, hash[:]...)) + if err != nil { + return nil, err + } + snap := new(Snapshot) + if err := json.Unmarshal(blob, snap); err != nil { + return nil, err + } + snap.config = config + snap.sigcache = sigcache + + return snap, nil +} + +// store inserts the snapshot into the database. +func (s *Snapshot) store(db ethdb.Database) error { + blob, err := json.Marshal(s) + if err != nil { + return err + } + return db.Put(append(rawdb.CliqueSnapshotPrefix, s.Hash[:]...), blob) +} + +// copy creates a deep copy of the snapshot, though not the individual votes. +func (s *Snapshot) copy() *Snapshot { + return &Snapshot{ + config: s.config, + sigcache: s.sigcache, + Number: s.Number, + Hash: s.Hash, + Signers: maps.Clone(s.Signers), + Recents: maps.Clone(s.Recents), + Votes: slices.Clone(s.Votes), + Tally: maps.Clone(s.Tally), + } +} + +// validVote returns whether it makes sense to cast the specified vote in the +// given snapshot context (e.g. don't try to add an already authorized signer). +func (s *Snapshot) validVote(address common.Address, authorize bool) bool { + _, signer := s.Signers[address] + return (signer && !authorize) || (!signer && authorize) +} + +// cast adds a new vote into the tally. +func (s *Snapshot) cast(address common.Address, authorize bool) bool { + // Ensure the vote is meaningful + if !s.validVote(address, authorize) { + return false + } + // Cast the vote into an existing or new tally + if old, ok := s.Tally[address]; ok { + old.Votes++ + s.Tally[address] = old + } else { + s.Tally[address] = Tally{Authorize: authorize, Votes: 1} + } + return true +} + +// uncast removes a previously cast vote from the tally. +func (s *Snapshot) uncast(address common.Address, authorize bool) bool { + // If there's no tally, it's a dangling vote, just drop + tally, ok := s.Tally[address] + if !ok { + return false + } + // Ensure we only revert counted votes + if tally.Authorize != authorize { + return false + } + // Otherwise revert the vote + if tally.Votes > 1 { + tally.Votes-- + s.Tally[address] = tally + } else { + delete(s.Tally, address) + } + return true +} + +// apply creates a new authorization snapshot by applying the given headers to +// the original one. +func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { + // Allow passing in no headers for cleaner code + if len(headers) == 0 { + return s, nil + } + // Sanity check that the headers can be applied + for i := 0; i < len(headers)-1; i++ { + if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 { + return nil, errInvalidVotingChain + } + } + if headers[0].Number.Uint64() != s.Number+1 { + return nil, errInvalidVotingChain + } + // Iterate through the headers and create a new snapshot + snap := s.copy() + + var ( + start = time.Now() + logged = time.Now() + ) + for i, header := range headers { + // Remove any votes on checkpoint blocks + number := header.Number.Uint64() + if number%s.config.Epoch == 0 { + snap.Votes = nil + snap.Tally = make(map[common.Address]Tally) + } + // Delete the oldest signer from the recent list to allow it signing again + if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { + delete(snap.Recents, number-limit) + } + // Resolve the authorization key and check against signers + signer, err := ecrecover(header, s.sigcache) + if err != nil { + return nil, err + } + if _, ok := snap.Signers[signer]; !ok { + return nil, errUnauthorizedSigner + } + for _, recent := range snap.Recents { + if recent == signer { + return nil, errRecentlySigned + } + } + snap.Recents[number] = signer + + // Header authorized, discard any previous votes from the signer + for i, vote := range snap.Votes { + if vote.Signer == signer && vote.Address == header.Coinbase { + // Uncast the vote from the cached tally + snap.uncast(vote.Address, vote.Authorize) + + // Uncast the vote from the chronological list + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + break // only one vote allowed + } + } + // Tally up the new vote from the signer + var authorize bool + switch { + case bytes.Equal(header.Nonce[:], nonceAuthVote): + authorize = true + case bytes.Equal(header.Nonce[:], nonceDropVote): + authorize = false + default: + return nil, errInvalidVote + } + if snap.cast(header.Coinbase, authorize) { + snap.Votes = append(snap.Votes, &Vote{ + Signer: signer, + Block: number, + Address: header.Coinbase, + Authorize: authorize, + }) + } + // If the vote passed, update the list of signers + if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 { + if tally.Authorize { + snap.Signers[header.Coinbase] = struct{}{} + } else { + delete(snap.Signers, header.Coinbase) + + // Signer list shrunk, delete any leftover recent caches + if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { + delete(snap.Recents, number-limit) + } + // Discard any previous votes the deauthorized signer cast + for i := 0; i < len(snap.Votes); i++ { + if snap.Votes[i].Signer == header.Coinbase { + // Uncast the vote from the cached tally + snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize) + + // Uncast the vote from the chronological list + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + + i-- + } + } + } + // Discard any previous votes around the just changed account + for i := 0; i < len(snap.Votes); i++ { + if snap.Votes[i].Address == header.Coinbase { + snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) + i-- + } + } + delete(snap.Tally, header.Coinbase) + } + // If we're taking too much time (ecrecover), notify the user once a while + if time.Since(logged) > 8*time.Second { + log.Info("Reconstructing voting history", "processed", i, "total", len(headers), "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + if time.Since(start) > 8*time.Second { + log.Info("Reconstructed voting history", "processed", len(headers), "elapsed", common.PrettyDuration(time.Since(start))) + } + snap.Number += uint64(len(headers)) + snap.Hash = headers[len(headers)-1].Hash() + + return snap, nil +} + +// signers retrieves the list of authorized signers in ascending order. +func (s *Snapshot) signers() []common.Address { + sigs := make([]common.Address, 0, len(s.Signers)) + for sig := range s.Signers { + sigs = append(sigs, sig) + } + slices.SortFunc(sigs, common.Address.Cmp) + return sigs +} + +// inturn returns if a signer at a given block height is in-turn or not. +func (s *Snapshot) inturn(number uint64, signer common.Address) bool { + signers, offset := s.signers(), 0 + for offset < len(signers) && signers[offset] != signer { + offset++ + } + return (number % uint64(len(signers))) == uint64(offset) +} diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go new file mode 100644 index 0000000..4ef7a7b --- /dev/null +++ b/consensus/clique/snapshot_test.go @@ -0,0 +1,508 @@ +// 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 clique + +import ( + "bytes" + "crypto/ecdsa" + "fmt" + "math/big" + "slices" + "testing" + + "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/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// testerAccountPool is a pool to maintain currently active tester accounts, +// mapped from textual names used in the tests below to actual Ethereum private +// keys capable of signing transactions. +type testerAccountPool struct { + accounts map[string]*ecdsa.PrivateKey +} + +func newTesterAccountPool() *testerAccountPool { + return &testerAccountPool{ + accounts: make(map[string]*ecdsa.PrivateKey), + } +} + +// checkpoint creates a Clique checkpoint signer section from the provided list +// of authorized signers and embeds it into the provided header. +func (ap *testerAccountPool) checkpoint(header *types.Header, signers []string) { + auths := make([]common.Address, len(signers)) + for i, signer := range signers { + auths[i] = ap.address(signer) + } + slices.SortFunc(auths, common.Address.Cmp) + for i, auth := range auths { + copy(header.Extra[extraVanity+i*common.AddressLength:], auth.Bytes()) + } +} + +// address retrieves the Ethereum address of a tester account by label, creating +// a new account if no previous one exists yet. +func (ap *testerAccountPool) address(account string) common.Address { + // Return the zero account for non-addresses + if account == "" { + return common.Address{} + } + // Ensure we have a persistent key for the account + if ap.accounts[account] == nil { + ap.accounts[account], _ = crypto.GenerateKey() + } + // Resolve and return the Ethereum address + return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) +} + +// sign calculates a Clique digital signature for the given block and embeds it +// back into the header. +func (ap *testerAccountPool) sign(header *types.Header, signer string) { + // Ensure we have a persistent key for the signer + if ap.accounts[signer] == nil { + ap.accounts[signer], _ = crypto.GenerateKey() + } + // Sign the header and embed the signature in extra data + sig, _ := crypto.Sign(SealHash(header).Bytes(), ap.accounts[signer]) + copy(header.Extra[len(header.Extra)-extraSeal:], sig) +} + +// testerVote represents a single block signed by a particular account, where +// the account may or may not have cast a Clique vote. +type testerVote struct { + signer string + voted string + auth bool + checkpoint []string + newbatch bool +} + +type cliqueTest struct { + epoch uint64 + signers []string + votes []testerVote + results []string + failure error +} + +// Tests that Clique signer voting is evaluated correctly for various simple and +// complex scenarios, as well as that a few special corner cases fail correctly. +func TestClique(t *testing.T) { + // Define the various voting scenarios to test + tests := []cliqueTest{ + { + // Single signer, no votes cast + signers: []string{"A"}, + votes: []testerVote{{signer: "A"}}, + results: []string{"A"}, + }, { + // Single signer, voting to add two others (only accept first, second needs 2 votes) + signers: []string{"A"}, + votes: []testerVote{ + {signer: "A", voted: "B", auth: true}, + {signer: "B"}, + {signer: "A", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Two signers, voting to add three others (only accept first two, third needs 3 votes already) + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: true}, + {signer: "B", voted: "C", auth: true}, + {signer: "A", voted: "D", auth: true}, + {signer: "B", voted: "D", auth: true}, + {signer: "C"}, + {signer: "A", voted: "E", auth: true}, + {signer: "B", voted: "E", auth: true}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Single signer, dropping itself (weird, but one less cornercase by explicitly allowing this) + signers: []string{"A"}, + votes: []testerVote{ + {signer: "A", voted: "A", auth: false}, + }, + results: []string{}, + }, { + // Two signers, actually needing mutual consent to drop either of them (not fulfilled) + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Two signers, actually needing mutual consent to drop either of them (fulfilled) + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "B", auth: false}, + {signer: "B", voted: "B", auth: false}, + }, + results: []string{"A"}, + }, { + // Three signers, two of them deciding to drop the third + signers: []string{"A", "B", "C"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Four signers, consensus of two not being enough to drop anyone + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Four signers, consensus of three already being enough to drop someone + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "D", auth: false}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + }, + results: []string{"A", "B", "C"}, + }, { + // Authorizations are counted once per signer per target + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: true}, + {signer: "B"}, + {signer: "A", voted: "C", auth: true}, + {signer: "B"}, + {signer: "A", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Authorizing multiple accounts concurrently is permitted + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: true}, + {signer: "B"}, + {signer: "A", voted: "D", auth: true}, + {signer: "B"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: true}, + {signer: "A"}, + {signer: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B", "C", "D"}, + }, { + // Deauthorizations are counted once per signer per target + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "B", auth: false}, + {signer: "B"}, + {signer: "A", voted: "B", auth: false}, + {signer: "B"}, + {signer: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Deauthorizing multiple accounts concurrently is permitted + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A", voted: "D", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + {signer: "A"}, + {signer: "B", voted: "C", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Votes from deauthorized signers are discarded immediately (deauth votes) + signers: []string{"A", "B", "C"}, + votes: []testerVote{ + {signer: "C", voted: "B", auth: false}, + {signer: "A", voted: "C", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "A", voted: "B", auth: false}, + }, + results: []string{"A", "B"}, + }, { + // Votes from deauthorized signers are discarded immediately (auth votes) + signers: []string{"A", "B", "C"}, + votes: []testerVote{ + {signer: "C", voted: "D", auth: true}, + {signer: "A", voted: "C", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "A", voted: "D", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Cascading changes are not allowed, only the account being voted on may change + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A", voted: "D", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "C"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + }, + results: []string{"A", "B", "C"}, + }, { + // Changes reaching consensus out of bounds (via a deauth) execute on touch + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A", voted: "D", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "C"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + {signer: "A"}, + {signer: "C", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch + signers: []string{"A", "B", "C", "D"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: false}, + {signer: "B"}, + {signer: "C"}, + {signer: "A", voted: "D", auth: false}, + {signer: "B", voted: "C", auth: false}, + {signer: "C"}, + {signer: "A"}, + {signer: "B", voted: "D", auth: false}, + {signer: "C", voted: "D", auth: false}, + {signer: "A"}, + {signer: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B", "C"}, + }, { + // Ensure that pending votes don't survive authorization status changes. This + // corner case can only appear if a signer is quickly added, removed and then + // re-added (or the inverse), while one of the original voters dropped. If a + // past vote is left cached in the system somewhere, this will interfere with + // the final signer outcome. + signers: []string{"A", "B", "C", "D", "E"}, + votes: []testerVote{ + {signer: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed + {signer: "B", voted: "F", auth: true}, + {signer: "C", voted: "F", auth: true}, + {signer: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged") + {signer: "E", voted: "F", auth: false}, + {signer: "B", voted: "F", auth: false}, + {signer: "C", voted: "F", auth: false}, + {signer: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed + {signer: "E", voted: "F", auth: true}, + {signer: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed + {signer: "C", voted: "A", auth: false}, + {signer: "D", voted: "A", auth: false}, + {signer: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed + }, + results: []string{"B", "C", "D", "E", "F"}, + }, { + // Epoch transitions reset all votes to allow chain checkpointing + epoch: 3, + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A", voted: "C", auth: true}, + {signer: "B"}, + {signer: "A", checkpoint: []string{"A", "B"}}, + {signer: "B", voted: "C", auth: true}, + }, + results: []string{"A", "B"}, + }, { + // An unauthorized signer should not be able to sign blocks + signers: []string{"A"}, + votes: []testerVote{ + {signer: "B"}, + }, + failure: errUnauthorizedSigner, + }, { + // An authorized signer that signed recently should not be able to sign again + signers: []string{"A", "B"}, + votes: []testerVote{ + {signer: "A"}, + {signer: "A"}, + }, + failure: errRecentlySigned, + }, { + // Recent signatures should not reset on checkpoint blocks imported in a batch + epoch: 3, + signers: []string{"A", "B", "C"}, + votes: []testerVote{ + {signer: "A"}, + {signer: "B"}, + {signer: "A", checkpoint: []string{"A", "B", "C"}}, + {signer: "A"}, + }, + failure: errRecentlySigned, + }, { + // Recent signatures should not reset on checkpoint blocks imported in a new + // batch (https://github.com/ethereum/go-ethereum/issues/17593). Whilst this + // seems overly specific and weird, it was a Rinkeby consensus split. + epoch: 3, + signers: []string{"A", "B", "C"}, + votes: []testerVote{ + {signer: "A"}, + {signer: "B"}, + {signer: "A", checkpoint: []string{"A", "B", "C"}}, + {signer: "A", newbatch: true}, + }, + failure: errRecentlySigned, + }, + } + + // Run through the scenarios and test them + for i, tt := range tests { + t.Run(fmt.Sprint(i), tt.run) + } +} + +func (tt *cliqueTest) run(t *testing.T) { + // Create the account pool and generate the initial set of signers + accounts := newTesterAccountPool() + + signers := make([]common.Address, len(tt.signers)) + for j, signer := range tt.signers { + signers[j] = accounts.address(signer) + } + for j := 0; j < len(signers); j++ { + for k := j + 1; k < len(signers); k++ { + if bytes.Compare(signers[j][:], signers[k][:]) > 0 { + signers[j], signers[k] = signers[k], signers[j] + } + } + } + // Create the genesis block with the initial set of signers + genesis := &core.Genesis{ + ExtraData: make([]byte, extraVanity+common.AddressLength*len(signers)+extraSeal), + BaseFee: big.NewInt(params.InitialBaseFee), + } + for j, signer := range signers { + copy(genesis.ExtraData[extraVanity+j*common.AddressLength:], signer[:]) + } + + // Assemble a chain of headers from the cast votes + config := *params.TestChainConfig + config.Clique = ¶ms.CliqueConfig{ + Period: 1, + Epoch: tt.epoch, + } + genesis.Config = &config + + engine := New(config.Clique, rawdb.NewMemoryDatabase()) + engine.fakeDiff = true + + _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, len(tt.votes), func(j int, gen *core.BlockGen) { + // Cast the vote contained in this block + gen.SetCoinbase(accounts.address(tt.votes[j].voted)) + if tt.votes[j].auth { + var nonce types.BlockNonce + copy(nonce[:], nonceAuthVote) + gen.SetNonce(nonce) + } + }) + // Iterate through the blocks and seal them individually + for j, block := range blocks { + // Get the header and prepare it for signing + header := block.Header() + if j > 0 { + header.ParentHash = blocks[j-1].Hash() + } + header.Extra = make([]byte, extraVanity+extraSeal) + if auths := tt.votes[j].checkpoint; auths != nil { + header.Extra = make([]byte, extraVanity+len(auths)*common.AddressLength+extraSeal) + accounts.checkpoint(header, auths) + } + header.Difficulty = diffInTurn // Ignored, we just need a valid number + + // Generate the signature, embed it into the header and the block + accounts.sign(header, tt.votes[j].signer) + blocks[j] = block.WithSeal(header) + } + // Split the blocks up into individual import batches (cornercase testing) + batches := [][]*types.Block{nil} + for j, block := range blocks { + if tt.votes[j].newbatch { + batches = append(batches, nil) + } + batches[len(batches)-1] = append(batches[len(batches)-1], block) + } + // Pass all the headers through clique and ensure tallying succeeds + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create test chain: %v", err) + } + defer chain.Stop() + + for j := 0; j < len(batches)-1; j++ { + if k, err := chain.InsertChain(batches[j]); err != nil { + t.Fatalf("failed to import batch %d, block %d: %v", j, k, err) + break + } + } + if _, err = chain.InsertChain(batches[len(batches)-1]); err != tt.failure { + t.Errorf("failure mismatch: have %v, want %v", err, tt.failure) + } + if tt.failure != nil { + return + } + + // No failure was produced or requested, generate the final voting snapshot + head := blocks[len(blocks)-1] + + snap, err := engine.snapshot(chain, head.NumberU64(), head.Hash(), nil) + if err != nil { + t.Fatalf("failed to retrieve voting snapshot: %v", err) + } + // Verify the final list of signers against the expected ones + signers = make([]common.Address, len(tt.results)) + for j, signer := range tt.results { + signers[j] = accounts.address(signer) + } + for j := 0; j < len(signers); j++ { + for k := j + 1; k < len(signers); k++ { + if bytes.Compare(signers[j][:], signers[k][:]) > 0 { + signers[j], signers[k] = signers[k], signers[j] + } + } + } + result := snap.signers() + if len(result) != len(signers) { + t.Fatalf("signers mismatch: have %x, want %x", result, signers) + } + for j := 0; j < len(result); j++ { + if !bytes.Equal(result[j][:], signers[j][:]) { + t.Fatalf("signer %d: signer mismatch: have %x, want %x", j, result[j], signers[j]) + } + } +} diff --git a/consensus/consensus.go b/consensus/consensus.go new file mode 100644 index 0000000..9232f7a --- /dev/null +++ b/consensus/consensus.go @@ -0,0 +1,119 @@ +// 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 consensus implements different Ethereum consensus engines. +package consensus + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +// ChainHeaderReader defines a small collection of methods needed to access the local +// blockchain during header verification. +type ChainHeaderReader interface { + // Config retrieves the blockchain's chain configuration. + Config() *params.ChainConfig + + // CurrentHeader retrieves the current header from the local chain. + CurrentHeader() *types.Header + + // GetHeader retrieves a block header from the database by hash and number. + GetHeader(hash common.Hash, number uint64) *types.Header + + // GetHeaderByNumber retrieves a block header from the database by number. + GetHeaderByNumber(number uint64) *types.Header + + // GetHeaderByHash retrieves a block header from the database by its hash. + GetHeaderByHash(hash common.Hash) *types.Header + + // GetTd retrieves the total difficulty from the database by hash and number. + GetTd(hash common.Hash, number uint64) *big.Int +} + +// ChainReader defines a small collection of methods needed to access the local +// blockchain during header and/or uncle verification. +type ChainReader interface { + ChainHeaderReader + + // GetBlock retrieves a block from the database by hash and number. + GetBlock(hash common.Hash, number uint64) *types.Block +} + +// Engine is an algorithm agnostic consensus engine. +type Engine interface { + // Author retrieves the Ethereum address of the account that minted the given + // block, which may be different from the header's coinbase if a consensus + // engine is based on signatures. + Author(header *types.Header) (common.Address, error) + + // VerifyHeader checks whether a header conforms to the consensus rules of a + // given engine. + VerifyHeader(chain ChainHeaderReader, header *types.Header) error + + // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers + // concurrently. The method returns a quit channel to abort the operations and + // a results channel to retrieve the async verifications (the order is that of + // the input slice). + VerifyHeaders(chain ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) + + // VerifyUncles verifies that the given block's uncles conform to the consensus + // rules of a given engine. + VerifyUncles(chain ChainReader, block *types.Block) error + + // Prepare initializes the consensus fields of a block header according to the + // rules of a particular engine. The changes are executed inline. + Prepare(chain ChainHeaderReader, header *types.Header) error + + // Finalize runs any post-transaction state modifications (e.g. block rewards + // or process withdrawals) but does not assemble the block. + // + // Note: The state database might be updated to reflect any consensus rules + // that happen at finalization (e.g. block rewards). + Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body) + + // FinalizeAndAssemble runs any post-transaction state modifications (e.g. block + // rewards or process withdrawals) and assembles the final block. + // + // Note: The block header and state database might be updated to reflect any + // consensus rules that happen at finalization (e.g. block rewards). + FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) + + // Seal generates a new sealing request for the given input block and pushes + // the result into the given channel. + // + // Note, the method returns immediately and will send the result async. More + // than one result may also be returned depending on the consensus algorithm. + Seal(chain ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error + + // SealHash returns the hash of a block prior to it being sealed. + SealHash(header *types.Header) common.Hash + + // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty + // that a new block should have. + CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int + + // APIs returns the RPC APIs this consensus engine provides. + APIs(chain ChainHeaderReader) []rpc.API + + // Close terminates any background threads maintained by the consensus engine. + Close() error +} diff --git a/consensus/errors.go b/consensus/errors.go new file mode 100644 index 0000000..d508b65 --- /dev/null +++ b/consensus/errors.go @@ -0,0 +1,41 @@ +// 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 consensus + +import "errors" + +var ( + // ErrUnknownAncestor is returned when validating a block requires an ancestor + // that is unknown. + ErrUnknownAncestor = errors.New("unknown ancestor") + + // ErrPrunedAncestor is returned when validating a block requires an ancestor + // that is known, but the state of which is not available. + ErrPrunedAncestor = errors.New("pruned ancestor") + + // ErrFutureBlock is returned when a block's timestamp is in the future according + // to the current node. + ErrFutureBlock = errors.New("block in the future") + + // ErrInvalidNumber is returned if a block's number doesn't equal its parent's + // plus one. + ErrInvalidNumber = errors.New("invalid block number") + + // ErrInvalidTerminalBlock is returned if a block is invalid wrt. the terminal + // total difficulty. + ErrInvalidTerminalBlock = errors.New("invalid terminal block") +) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go new file mode 100644 index 0000000..0bd1a56 --- /dev/null +++ b/consensus/ethash/consensus.go @@ -0,0 +1,593 @@ +// 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 ethash + +import ( + "errors" + "fmt" + "math/big" + "time" + + mapset "github.com/deckarep/golang-set/v2" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" +) + +// Ethash proof-of-work protocol constants. +var ( + FrontierBlockReward = uint256.NewInt(5e+18) // Block reward in wei for successfully mining a block + ByzantiumBlockReward = uint256.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium + ConstantinopleBlockReward = uint256.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople + maxUncles = 2 // Maximum number of uncles allowed in a single block + allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks + + // calcDifficultyEip5133 is the difficulty adjustment algorithm as specified by EIP 5133. + // It offsets the bomb a total of 11.4M blocks. + // Specification EIP-5133: https://eips.ethereum.org/EIPS/eip-5133 + calcDifficultyEip5133 = makeDifficultyCalculator(big.NewInt(11_400_000)) + + // calcDifficultyEip4345 is the difficulty adjustment algorithm as specified by EIP 4345. + // It offsets the bomb a total of 10.7M blocks. + // Specification EIP-4345: https://eips.ethereum.org/EIPS/eip-4345 + calcDifficultyEip4345 = makeDifficultyCalculator(big.NewInt(10_700_000)) + + // calcDifficultyEip3554 is the difficulty adjustment algorithm as specified by EIP 3554. + // It offsets the bomb a total of 9.7M blocks. + // Specification EIP-3554: https://eips.ethereum.org/EIPS/eip-3554 + calcDifficultyEip3554 = makeDifficultyCalculator(big.NewInt(9700000)) + + // calcDifficultyEip2384 is the difficulty adjustment algorithm as specified by EIP 2384. + // It offsets the bomb 4M blocks from Constantinople, so in total 9M blocks. + // Specification EIP-2384: https://eips.ethereum.org/EIPS/eip-2384 + calcDifficultyEip2384 = makeDifficultyCalculator(big.NewInt(9000000)) + + // calcDifficultyConstantinople is the difficulty adjustment algorithm for Constantinople. + // It returns the difficulty that a new block should have when created at time given the + // parent block's time and difficulty. The calculation uses the Byzantium rules, but with + // bomb offset 5M. + // Specification EIP-1234: https://eips.ethereum.org/EIPS/eip-1234 + calcDifficultyConstantinople = makeDifficultyCalculator(big.NewInt(5000000)) + + // calcDifficultyByzantium is the difficulty adjustment algorithm. It returns + // the difficulty that a new block should have when created at time given the + // parent block's time and difficulty. The calculation uses the Byzantium rules. + // Specification EIP-649: https://eips.ethereum.org/EIPS/eip-649 + calcDifficultyByzantium = makeDifficultyCalculator(big.NewInt(3000000)) +) + +// Various error messages to mark blocks invalid. These should be private to +// prevent engine specific errors from being referenced in the remainder of the +// codebase, inherently breaking if the engine is swapped out. Please put common +// error types into the consensus package. +var ( + errOlderBlockTime = errors.New("timestamp older than parent") + errTooManyUncles = errors.New("too many uncles") + errDuplicateUncle = errors.New("duplicate uncle") + errUncleIsAncestor = errors.New("uncle is ancestor") + errDanglingUncle = errors.New("uncle's parent is not ancestor") +) + +// Author implements consensus.Engine, returning the header's coinbase as the +// proof-of-work verified author of the block. +func (ethash *Ethash) Author(header *types.Header) (common.Address, error) { + return header.Coinbase, nil +} + +// VerifyHeader checks whether a header conforms to the consensus rules of the +// stock Ethereum ethash engine. +func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { + // Short circuit if the header is known, or its parent not + number := header.Number.Uint64() + if chain.GetHeader(header.Hash(), number) != nil { + return nil + } + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + // Sanity checks passed, do a proper verification + return ethash.verifyHeader(chain, header, parent, false, time.Now().Unix()) +} + +// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers +// concurrently. The method returns a quit channel to abort the operations and +// a results channel to retrieve the async verifications. +func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { + // If we're running a full engine faking, accept any input as valid + if ethash.fakeFull || len(headers) == 0 { + abort, results := make(chan struct{}), make(chan error, len(headers)) + for i := 0; i < len(headers); i++ { + results <- nil + } + return abort, results + } + abort := make(chan struct{}) + results := make(chan error, len(headers)) + unixNow := time.Now().Unix() + + go func() { + for i, header := range headers { + var parent *types.Header + if i == 0 { + parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1) + } else if headers[i-1].Hash() == headers[i].ParentHash { + parent = headers[i-1] + } + var err error + if parent == nil { + err = consensus.ErrUnknownAncestor + } else { + err = ethash.verifyHeader(chain, header, parent, false, unixNow) + } + select { + case <-abort: + return + case results <- err: + } + } + }() + return abort, results +} + +// VerifyUncles verifies that the given block's uncles conform to the consensus +// rules of the stock Ethereum ethash engine. +func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + // If we're running a full engine faking, accept any input as valid + if ethash.fakeFull { + return nil + } + // Verify that there are at most 2 uncles included in this block + if len(block.Uncles()) > maxUncles { + return errTooManyUncles + } + if len(block.Uncles()) == 0 { + return nil + } + // Gather the set of past uncles and ancestors + uncles, ancestors := mapset.NewSet[common.Hash](), make(map[common.Hash]*types.Header) + + number, parent := block.NumberU64()-1, block.ParentHash() + for i := 0; i < 7; i++ { + ancestorHeader := chain.GetHeader(parent, number) + if ancestorHeader == nil { + break + } + ancestors[parent] = ancestorHeader + // If the ancestor doesn't have any uncles, we don't have to iterate them + if ancestorHeader.UncleHash != types.EmptyUncleHash { + // Need to add those uncles to the banned list too + ancestor := chain.GetBlock(parent, number) + if ancestor == nil { + break + } + for _, uncle := range ancestor.Uncles() { + uncles.Add(uncle.Hash()) + } + } + parent, number = ancestorHeader.ParentHash, number-1 + } + ancestors[block.Hash()] = block.Header() + uncles.Add(block.Hash()) + + // Verify each of the uncles that it's recent, but not an ancestor + for _, uncle := range block.Uncles() { + // Make sure every uncle is rewarded only once + hash := uncle.Hash() + if uncles.Contains(hash) { + return errDuplicateUncle + } + uncles.Add(hash) + + // Make sure the uncle has a valid ancestry + if ancestors[hash] != nil { + return errUncleIsAncestor + } + if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() { + return errDanglingUncle + } + if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, time.Now().Unix()); err != nil { + return err + } + } + return nil +} + +// verifyHeader checks whether a header conforms to the consensus rules of the +// stock Ethereum ethash engine. +// See YP section 4.3.4. "Block Header Validity" +func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, unixNow int64) error { + // Ensure that the header's extra-data section is of a reasonable size + if uint64(len(header.Extra)) > params.MaximumExtraDataSize { + return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize) + } + // Verify the header's timestamp + if !uncle { + if header.Time > uint64(unixNow+allowedFutureBlockTimeSeconds) { + return consensus.ErrFutureBlock + } + } + if header.Time <= parent.Time { + return errOlderBlockTime + } + // Verify the block's difficulty based on its timestamp and parent's difficulty + expected := ethash.CalcDifficulty(chain, header.Time, parent) + + if expected.Cmp(header.Difficulty) != 0 { + return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected) + } + // Verify that the gas limit is <= 2^63-1 + if header.GasLimit > params.MaxGasLimit { + return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) + } + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) + } + // Verify the block's gas usage and (if applicable) verify the base fee. + if !chain.Config().IsLondon(header.Number) { + // Verify BaseFee not present before EIP-1559 fork. + if header.BaseFee != nil { + return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee) + } + if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { + return err + } + } else if err := eip1559.VerifyEIP1559Header(chain.Config(), parent, header); err != nil { + // Verify the header's EIP-1559 attributes. + return err + } + // Verify that the block number is parent's +1 + if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { + return consensus.ErrInvalidNumber + } + if chain.Config().IsShanghai(header.Number, header.Time) { + 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) + } + if ethash.fakeFail != nil && *ethash.fakeFail == header.Number.Uint64() { + return errors.New("invalid tester pow") + } + // If all checks passed, validate any special fields for hard forks + if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil { + return err + } + return nil +} + +// CalcDifficulty is the difficulty adjustment algorithm. It returns +// the difficulty that a new block should have when created at time +// given the parent block's time and difficulty. +func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + return CalcDifficulty(chain.Config(), time, parent) +} + +// CalcDifficulty is the difficulty adjustment algorithm. It returns +// the difficulty that a new block should have when created at time +// given the parent block's time and difficulty. +func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { + next := new(big.Int).Add(parent.Number, big1) + switch { + case config.IsGrayGlacier(next): + return calcDifficultyEip5133(time, parent) + case config.IsArrowGlacier(next): + return calcDifficultyEip4345(time, parent) + case config.IsLondon(next): + return calcDifficultyEip3554(time, parent) + case config.IsMuirGlacier(next): + return calcDifficultyEip2384(time, parent) + case config.IsConstantinople(next): + return calcDifficultyConstantinople(time, parent) + case config.IsByzantium(next): + return calcDifficultyByzantium(time, parent) + case config.IsHomestead(next): + return calcDifficultyHomestead(time, parent) + default: + return calcDifficultyFrontier(time, parent) + } +} + +// Some weird constants to avoid constant memory allocs for them. +var ( + expDiffPeriod = big.NewInt(100000) + big1 = big.NewInt(1) + big2 = big.NewInt(2) + big9 = big.NewInt(9) + big10 = big.NewInt(10) + bigMinus99 = big.NewInt(-99) +) + +// makeDifficultyCalculator creates a difficultyCalculator with the given bomb-delay. +// the difficulty is calculated with Byzantium rules, which differs from Homestead in +// how uncles affect the calculation +func makeDifficultyCalculator(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int { + // Note, the calculations below looks at the parent number, which is 1 below + // the block number. Thus we remove one from the delay given + bombDelayFromParent := new(big.Int).Sub(bombDelay, big1) + return func(time uint64, parent *types.Header) *big.Int { + // https://github.com/ethereum/EIPs/issues/100. + // algorithm: + // diff = (parent_diff + + // (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) + // ) + 2^(periodCount - 2) + + bigTime := new(big.Int).SetUint64(time) + bigParentTime := new(big.Int).SetUint64(parent.Time) + + // holds intermediate values to make the algo easier to read & audit + x := new(big.Int) + y := new(big.Int) + + // (2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9 + x.Sub(bigTime, bigParentTime) + x.Div(x, big9) + if parent.UncleHash == types.EmptyUncleHash { + x.Sub(big1, x) + } else { + x.Sub(big2, x) + } + // max((2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9, -99) + if x.Cmp(bigMinus99) < 0 { + x.Set(bigMinus99) + } + // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) + y.Div(parent.Difficulty, params.DifficultyBoundDivisor) + x.Mul(y, x) + x.Add(parent.Difficulty, x) + + // minimum difficulty can ever be (before exponential factor) + if x.Cmp(params.MinimumDifficulty) < 0 { + x.Set(params.MinimumDifficulty) + } + // calculate a fake block number for the ice-age delay + // Specification: https://eips.ethereum.org/EIPS/eip-1234 + fakeBlockNumber := new(big.Int) + if parent.Number.Cmp(bombDelayFromParent) >= 0 { + fakeBlockNumber = fakeBlockNumber.Sub(parent.Number, bombDelayFromParent) + } + // for the exponential factor + periodCount := fakeBlockNumber + periodCount.Div(periodCount, expDiffPeriod) + + // the exponential factor, commonly referred to as "the bomb" + // diff = diff + 2^(periodCount - 2) + if periodCount.Cmp(big1) > 0 { + y.Sub(periodCount, big2) + y.Exp(big2, y, nil) + x.Add(x, y) + } + return x + } +} + +// calcDifficultyHomestead is the difficulty adjustment algorithm. It returns +// the difficulty that a new block should have when created at time given the +// parent block's time and difficulty. The calculation uses the Homestead rules. +func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int { + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md + // algorithm: + // diff = (parent_diff + + // (parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99)) + // ) + 2^(periodCount - 2) + + bigTime := new(big.Int).SetUint64(time) + bigParentTime := new(big.Int).SetUint64(parent.Time) + + // holds intermediate values to make the algo easier to read & audit + x := new(big.Int) + y := new(big.Int) + + // 1 - (block_timestamp - parent_timestamp) // 10 + x.Sub(bigTime, bigParentTime) + x.Div(x, big10) + x.Sub(big1, x) + + // max(1 - (block_timestamp - parent_timestamp) // 10, -99) + if x.Cmp(bigMinus99) < 0 { + x.Set(bigMinus99) + } + // (parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99)) + y.Div(parent.Difficulty, params.DifficultyBoundDivisor) + x.Mul(y, x) + x.Add(parent.Difficulty, x) + + // minimum difficulty can ever be (before exponential factor) + if x.Cmp(params.MinimumDifficulty) < 0 { + x.Set(params.MinimumDifficulty) + } + // for the exponential factor + periodCount := new(big.Int).Add(parent.Number, big1) + periodCount.Div(periodCount, expDiffPeriod) + + // the exponential factor, commonly referred to as "the bomb" + // diff = diff + 2^(periodCount - 2) + if periodCount.Cmp(big1) > 0 { + y.Sub(periodCount, big2) + y.Exp(big2, y, nil) + x.Add(x, y) + } + return x +} + +// calcDifficultyFrontier is the difficulty adjustment algorithm. It returns the +// difficulty that a new block should have when created at time given the parent +// block's time and difficulty. The calculation uses the Frontier rules. +func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int { + diff := new(big.Int) + adjust := new(big.Int).Div(parent.Difficulty, params.DifficultyBoundDivisor) + bigTime := new(big.Int) + bigParentTime := new(big.Int) + + bigTime.SetUint64(time) + bigParentTime.SetUint64(parent.Time) + + if bigTime.Sub(bigTime, bigParentTime).Cmp(params.DurationLimit) < 0 { + diff.Add(parent.Difficulty, adjust) + } else { + diff.Sub(parent.Difficulty, adjust) + } + if diff.Cmp(params.MinimumDifficulty) < 0 { + diff.Set(params.MinimumDifficulty) + } + + periodCount := new(big.Int).Add(parent.Number, big1) + periodCount.Div(periodCount, expDiffPeriod) + if periodCount.Cmp(big1) > 0 { + // diff = diff + 2^(periodCount - 2) + expDiff := periodCount.Sub(periodCount, big2) + expDiff.Exp(big2, expDiff, nil) + diff.Add(diff, expDiff) + diff = math.BigMax(diff, params.MinimumDifficulty) + } + return diff +} + +// Exported for fuzzing +var FrontierDifficultyCalculator = calcDifficultyFrontier +var HomesteadDifficultyCalculator = calcDifficultyHomestead +var DynamicDifficultyCalculator = makeDifficultyCalculator + +// Prepare implements consensus.Engine, initializing the difficulty field of a +// header to conform to the ethash protocol. The changes are done inline. +func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { + parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + header.Difficulty = ethash.CalcDifficulty(chain, header.Time, parent) + return nil +} + +// Finalize implements consensus.Engine, accumulating the block and uncle rewards. +func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body) { + // Accumulate any block and uncle rewards + accumulateRewards(chain.Config(), state, header, body.Uncles) +} + +// FinalizeAndAssemble implements consensus.Engine, accumulating the block and +// uncle rewards, setting the final state and assembling the block. +func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { + if len(body.Withdrawals) > 0 { + return nil, errors.New("ethash does not support withdrawals") + } + // Finalize block + ethash.Finalize(chain, header, state, body) + + // Assign the final state root to header. + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + + // Header seems complete, assemble into a block and return + return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil +} + +// SealHash returns the hash of a block prior to it being sealed. +func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewLegacyKeccak256() + + enc := []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra, + } + if header.BaseFee != nil { + enc = append(enc, header.BaseFee) + } + 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 +} + +// accumulateRewards credits the coinbase of the given block with the mining +// reward. The total reward consists of the static block reward and rewards for +// included uncles. The coinbase of each uncle block is also rewarded. +func accumulateRewards(config *params.ChainConfig, stateDB *state.StateDB, header *types.Header, uncles []*types.Header) { + // Select the correct block reward based on chain progression + blockReward := FrontierBlockReward + if config.IsByzantium(header.Number) { + blockReward = ByzantiumBlockReward + } + if config.IsConstantinople(header.Number) { + blockReward = ConstantinopleBlockReward + } + // Accumulate the rewards for the miner and any included uncles + reward := new(uint256.Int).Set(blockReward) + r := new(uint256.Int) + hNum, _ := uint256.FromBig(header.Number) + for _, uncle := range uncles { + uNum, _ := uint256.FromBig(uncle.Number) + r.AddUint64(uNum, 8) + r.Sub(r, hNum) + r.Mul(r, blockReward) + r.Rsh(r, 3) + stateDB.AddBalance(uncle.Coinbase, r, tracing.BalanceIncreaseRewardMineUncle) + + r.Rsh(blockReward, 5) + reward.Add(reward, r) + } + stateDB.AddBalance(header.Coinbase, reward, tracing.BalanceIncreaseRewardMineBlock) +} diff --git a/consensus/ethash/consensus_test.go b/consensus/ethash/consensus_test.go new file mode 100644 index 0000000..e3793cd --- /dev/null +++ b/consensus/ethash/consensus_test.go @@ -0,0 +1,188 @@ +// 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 ethash + +import ( + crand "crypto/rand" + "encoding/binary" + "encoding/json" + "math/big" + "math/rand" + "os" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +type diffTest struct { + ParentTimestamp uint64 + ParentDifficulty *big.Int + CurrentTimestamp uint64 + CurrentBlocknumber *big.Int + CurrentDifficulty *big.Int +} + +func (d *diffTest) UnmarshalJSON(b []byte) (err error) { + var ext struct { + ParentTimestamp string + ParentDifficulty string + CurrentTimestamp string + CurrentBlocknumber string + CurrentDifficulty string + } + if err := json.Unmarshal(b, &ext); err != nil { + return err + } + + d.ParentTimestamp = math.MustParseUint64(ext.ParentTimestamp) + d.ParentDifficulty = math.MustParseBig256(ext.ParentDifficulty) + d.CurrentTimestamp = math.MustParseUint64(ext.CurrentTimestamp) + d.CurrentBlocknumber = math.MustParseBig256(ext.CurrentBlocknumber) + d.CurrentDifficulty = math.MustParseBig256(ext.CurrentDifficulty) + + return nil +} + +func TestCalcDifficulty(t *testing.T) { + file, err := os.Open(filepath.Join("..", "..", "tests", "testdata", "BasicTests", "difficulty.json")) + if err != nil { + t.Skip(err) + } + defer file.Close() + + tests := make(map[string]diffTest) + err = json.NewDecoder(file).Decode(&tests) + if err != nil { + t.Fatal(err) + } + + config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1150000)} + + for name, test := range tests { + number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1)) + diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{ + Number: number, + Time: test.ParentTimestamp, + Difficulty: test.ParentDifficulty, + }) + if diff.Cmp(test.CurrentDifficulty) != 0 { + t.Error(name, "failed. Expected", test.CurrentDifficulty, "and calculated", diff) + } + } +} + +func randSlice(min, max uint32) []byte { + var b = make([]byte, 4) + crand.Read(b) + a := binary.LittleEndian.Uint32(b) + size := min + a%(max-min) + out := make([]byte, size) + crand.Read(out) + return out +} + +func TestDifficultyCalculators(t *testing.T) { + for i := 0; i < 5000; i++ { + // 1 to 300 seconds diff + var timeDelta = uint64(1 + rand.Uint32()%3000) + diffBig := new(big.Int).SetBytes(randSlice(2, 10)) + if diffBig.Cmp(params.MinimumDifficulty) < 0 { + diffBig.Set(params.MinimumDifficulty) + } + //rand.Read(difficulty) + header := &types.Header{ + Difficulty: diffBig, + Number: new(big.Int).SetUint64(rand.Uint64() % 50_000_000), + Time: rand.Uint64() - timeDelta, + } + if rand.Uint32()&1 == 0 { + header.UncleHash = types.EmptyUncleHash + } + bombDelay := new(big.Int).SetUint64(rand.Uint64() % 50_000_000) + for i, pair := range []struct { + bigFn func(time uint64, parent *types.Header) *big.Int + u256Fn func(time uint64, parent *types.Header) *big.Int + }{ + {FrontierDifficultyCalculator, CalcDifficultyFrontierU256}, + {HomesteadDifficultyCalculator, CalcDifficultyHomesteadU256}, + {DynamicDifficultyCalculator(bombDelay), MakeDifficultyCalculatorU256(bombDelay)}, + } { + time := header.Time + timeDelta + want := pair.bigFn(time, header) + have := pair.u256Fn(time, header) + if want.BitLen() > 256 { + continue + } + if want.Cmp(have) != 0 { + t.Fatalf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have, + header.Number, header.Time, time, bombDelay) + } + } + } +} + +func BenchmarkDifficultyCalculator(b *testing.B) { + x1 := makeDifficultyCalculator(big.NewInt(1000000)) + x2 := MakeDifficultyCalculatorU256(big.NewInt(1000000)) + h := &types.Header{ + ParentHash: common.Hash{}, + UncleHash: types.EmptyUncleHash, + Difficulty: big.NewInt(0xffffff), + Number: big.NewInt(500000), + Time: 1000000, + } + b.Run("big-frontier", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + calcDifficultyFrontier(1000014, h) + } + }) + b.Run("u256-frontier", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + CalcDifficultyFrontierU256(1000014, h) + } + }) + b.Run("big-homestead", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + calcDifficultyHomestead(1000014, h) + } + }) + b.Run("u256-homestead", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + CalcDifficultyHomesteadU256(1000014, h) + } + }) + b.Run("big-generic", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + x1(1000014, h) + } + }) + b.Run("u256-generic", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + x2(1000014, h) + } + }) +} diff --git a/consensus/ethash/difficulty.go b/consensus/ethash/difficulty.go new file mode 100644 index 0000000..66a1805 --- /dev/null +++ b/consensus/ethash/difficulty.go @@ -0,0 +1,191 @@ +// 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 ethash + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +const ( + // frontierDurationLimit is for Frontier: + // The decision boundary on the blocktime duration used to determine + // whether difficulty should go up or down. + frontierDurationLimit = 13 + // minimumDifficulty The minimum that the difficulty may ever be. + minimumDifficulty = 131072 + // expDiffPeriod is the exponential difficulty period + expDiffPeriodUint = 100000 + // difficultyBoundDivisorBitShift is the bound divisor of the difficulty (2048), + // This constant is the right-shifts to use for the division. + difficultyBoundDivisor = 11 +) + +// CalcDifficultyFrontierU256 is the difficulty adjustment algorithm. It returns the +// difficulty that a new block should have when created at time given the parent +// block's time and difficulty. The calculation uses the Frontier rules. +func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int { + /* + Algorithm + block_diff = pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1) + int(2^((num // 100000) - 2)) + + Where: + - pdiff = parent.difficulty + - ptime = parent.time + - time = block.timestamp + - num = block.number + */ + + pDiff, _ := uint256.FromBig(parent.Difficulty) // pDiff: pdiff + adjust := pDiff.Clone() + adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 + + if time-parent.Time < frontierDurationLimit { + pDiff.Add(pDiff, adjust) + } else { + pDiff.Sub(pDiff, adjust) + } + if pDiff.LtUint64(minimumDifficulty) { + pDiff.SetUint64(minimumDifficulty) + } + // 'pdiff' now contains: + // pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1) + + if periodCount := (parent.Number.Uint64() + 1) / expDiffPeriodUint; periodCount > 1 { + // diff = diff + 2^(periodCount - 2) + expDiff := adjust.SetOne() + expDiff.Lsh(expDiff, uint(periodCount-2)) // expdiff: 2 ^ (periodCount -2) + pDiff.Add(pDiff, expDiff) + } + return pDiff.ToBig() +} + +// CalcDifficultyHomesteadU256 is the difficulty adjustment algorithm. It returns +// the difficulty that a new block should have when created at time given the +// parent block's time and difficulty. The calculation uses the Homestead rules. +func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int { + /* + https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md + Algorithm: + block_diff = pdiff + pdiff / 2048 * max(1 - (time - ptime) / 10, -99) + 2 ^ int((num / 100000) - 2)) + + Our modification, to use unsigned ints: + block_diff = pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + 2 ^ int((num / 100000) - 2)) + + Where: + - pdiff = parent.difficulty + - ptime = parent.time + - time = block.timestamp + - num = block.number + */ + + pDiff, _ := uint256.FromBig(parent.Difficulty) // pDiff: pdiff + adjust := pDiff.Clone() + adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 + + x := (time - parent.Time) / 10 // (time - ptime) / 10) + var neg = true + if x == 0 { + x = 1 + neg = false + } else if x >= 100 { + x = 99 + } else { + x = x - 1 + } + z := new(uint256.Int).SetUint64(x) + adjust.Mul(adjust, z) // adjust: (pdiff / 2048) * max((time - ptime) / 10 - 1, 99) + if neg { + pDiff.Sub(pDiff, adjust) // pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + } else { + pDiff.Add(pDiff, adjust) // pdiff + pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + } + if pDiff.LtUint64(minimumDifficulty) { + pDiff.SetUint64(minimumDifficulty) + } + // for the exponential factor, a.k.a "the bomb" + // diff = diff + 2^(periodCount - 2) + if periodCount := (1 + parent.Number.Uint64()) / expDiffPeriodUint; periodCount > 1 { + expFactor := adjust.Lsh(adjust.SetOne(), uint(periodCount-2)) + pDiff.Add(pDiff, expFactor) + } + return pDiff.ToBig() +} + +// MakeDifficultyCalculatorU256 creates a difficultyCalculator with the given bomb-delay. +// the difficulty is calculated with Byzantium rules, which differs from Homestead in +// how uncles affect the calculation +func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int { + // Note, the calculations below looks at the parent number, which is 1 below + // the block number. Thus we remove one from the delay given + bombDelayFromParent := bombDelay.Uint64() - 1 + return func(time uint64, parent *types.Header) *big.Int { + /* + https://github.com/ethereum/EIPs/issues/100 + pDiff = parent.difficulty + BLOCK_DIFF_FACTOR = 9 + a = pDiff + (pDiff // BLOCK_DIFF_FACTOR) * adj_factor + b = min(parent.difficulty, MIN_DIFF) + child_diff = max(a,b ) + */ + x := (time - parent.Time) / 9 // (block_timestamp - parent_timestamp) // 9 + c := uint64(1) // if parent.unclehash == emptyUncleHashHash + if parent.UncleHash != types.EmptyUncleHash { + c = 2 + } + xNeg := x >= c + if xNeg { + // x is now _negative_ adjustment factor + x = x - c // - ( (t-p)/p -( 2 or 1) ) + } else { + x = c - x // (2 or 1) - (t-p)/9 + } + if x > 99 { + x = 99 // max(x, 99) + } + // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) + y := new(uint256.Int) + y.SetFromBig(parent.Difficulty) // y: p_diff + pDiff := y.Clone() // pdiff: p_diff + z := new(uint256.Int).SetUint64(x) //z : +-adj_factor (either pos or negative) + y.Rsh(y, difficultyBoundDivisor) // y: p__diff / 2048 + z.Mul(y, z) // z: (p_diff / 2048 ) * (+- adj_factor) + + if xNeg { + y.Sub(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor + } else { + y.Add(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor + } + // minimum difficulty can ever be (before exponential factor) + if y.LtUint64(minimumDifficulty) { + y.SetUint64(minimumDifficulty) + } + // calculate a fake block number for the ice-age delay + // Specification: https://eips.ethereum.org/EIPS/eip-1234 + var pNum = parent.Number.Uint64() + if pNum >= bombDelayFromParent { + if fakeBlockNumber := pNum - bombDelayFromParent; fakeBlockNumber >= 2*expDiffPeriodUint { + z.SetOne() + z.Lsh(z, uint(fakeBlockNumber/expDiffPeriodUint-2)) + y.Add(z, y) + } + } + return y.ToBig() + } +} diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go new file mode 100644 index 0000000..f37ec26 --- /dev/null +++ b/consensus/ethash/ethash.go @@ -0,0 +1,85 @@ +// 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 ethash implements the ethash proof-of-work consensus engine. +package ethash + +import ( + "time" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" +) + +// Ethash is a consensus engine based on proof-of-work implementing the ethash +// algorithm. +type Ethash struct { + fakeFail *uint64 // Block number which fails PoW check even in fake mode + fakeDelay *time.Duration // Time delay to sleep for before returning from verify + fakeFull bool // Accepts everything as valid +} + +// NewFaker creates an ethash consensus engine with a fake PoW scheme that accepts +// all blocks' seal as valid, though they still have to conform to the Ethereum +// consensus rules. +func NewFaker() *Ethash { + return new(Ethash) +} + +// NewFakeFailer creates a ethash consensus engine with a fake PoW scheme that +// accepts all blocks as valid apart from the single one specified, though they +// still have to conform to the Ethereum consensus rules. +func NewFakeFailer(fail uint64) *Ethash { + return &Ethash{ + fakeFail: &fail, + } +} + +// NewFakeDelayer creates a ethash consensus engine with a fake PoW scheme that +// accepts all blocks as valid, but delays verifications by some time, though +// they still have to conform to the Ethereum consensus rules. +func NewFakeDelayer(delay time.Duration) *Ethash { + return &Ethash{ + fakeDelay: &delay, + } +} + +// NewFullFaker creates an ethash consensus engine with a full fake scheme that +// accepts all blocks as valid, without checking any consensus rules whatsoever. +func NewFullFaker() *Ethash { + return &Ethash{ + fakeFull: true, + } +} + +// Close closes the exit channel to notify all backend threads exiting. +func (ethash *Ethash) Close() error { + return nil +} + +// APIs implements consensus.Engine, returning no APIs as ethash is an empty +// shell in the post-merge world. +func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API { + return []rpc.API{} +} + +// Seal generates a new sealing request for the given input block and pushes +// the result into the given channel. For the ethash engine, this method will +// just panic as sealing is not supported anymore. +func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { + panic("ethash (pow) sealing not supported any more") +} diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go new file mode 100644 index 0000000..45669d0 --- /dev/null +++ b/consensus/misc/dao.go @@ -0,0 +1,88 @@ +// 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 misc + +import ( + "bytes" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +var ( + // ErrBadProDAOExtra is returned if a header doesn't support the DAO fork on a + // pro-fork client. + ErrBadProDAOExtra = errors.New("bad DAO pro-fork extra-data") + + // ErrBadNoDAOExtra is returned if a header does support the DAO fork on a no- + // fork client. + ErrBadNoDAOExtra = errors.New("bad DAO no-fork extra-data") +) + +// VerifyDAOHeaderExtraData validates the extra-data field of a block header to +// ensure it conforms to DAO hard-fork rules. +// +// DAO hard-fork extension to the header validity: +// +// - if the node is no-fork, do not accept blocks in the [fork, fork+10) range +// with the fork specific extra-data set. +// - if the node is pro-fork, require blocks in the specific range to have the +// unique extra-data set. +func VerifyDAOHeaderExtraData(config *params.ChainConfig, header *types.Header) error { + // Short circuit validation if the node doesn't care about the DAO fork + if config.DAOForkBlock == nil { + return nil + } + // Make sure the block is within the fork's modified extra-data range + limit := new(big.Int).Add(config.DAOForkBlock, params.DAOForkExtraRange) + if header.Number.Cmp(config.DAOForkBlock) < 0 || header.Number.Cmp(limit) >= 0 { + return nil + } + // Depending on whether we support or oppose the fork, validate the extra-data contents + if config.DAOForkSupport { + if !bytes.Equal(header.Extra, params.DAOForkBlockExtra) { + return ErrBadProDAOExtra + } + } else { + if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { + return ErrBadNoDAOExtra + } + } + // All ok, header has the same extra-data we expect + return nil +} + +// ApplyDAOHardFork modifies the state database according to the DAO hard-fork +// rules, transferring all balances of a set of DAO accounts to a single refund +// contract. +func ApplyDAOHardFork(statedb *state.StateDB) { + // Retrieve the contract to refund balances into + if !statedb.Exist(params.DAORefundContract) { + statedb.CreateAccount(params.DAORefundContract) + } + + // Move every DAO account and extra-balance account funds into the refund contract + for _, addr := range params.DAODrainList() { + statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), tracing.BalanceIncreaseDaoContract) + statedb.SetBalance(addr, new(uint256.Int), tracing.BalanceDecreaseDaoAccount) + } +} diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go new file mode 100644 index 0000000..84b82c4 --- /dev/null +++ b/consensus/misc/eip1559/eip1559.go @@ -0,0 +1,95 @@ +// 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 eip1559 + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +// VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559, +// - gas limit check +// - basefee check +func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Header) error { + // Verify that the gas limit remains within allowed bounds + parentGasLimit := parent.GasLimit + if !config.IsLondon(parent.Number) { + parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() + } + if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { + return err + } + // Verify the header is not malformed + if header.BaseFee == nil { + return errors.New("header is missing baseFee") + } + // Verify the baseFee is correct based on the parent header. + expectedBaseFee := CalcBaseFee(config, parent) + if header.BaseFee.Cmp(expectedBaseFee) != 0 { + return fmt.Errorf("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d", + header.BaseFee, expectedBaseFee, parent.BaseFee, parent.GasUsed) + } + return nil +} + +// CalcBaseFee calculates the basefee of the header. +func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { + // If the current block is the first EIP-1559 block, return the InitialBaseFee. + if !config.IsLondon(parent.Number) { + return new(big.Int).SetUint64(params.InitialBaseFee) + } + + parentGasTarget := parent.GasLimit / config.ElasticityMultiplier() + // If the parent gasUsed is the same as the target, the baseFee remains unchanged. + if parent.GasUsed == parentGasTarget { + return new(big.Int).Set(parent.BaseFee) + } + + var ( + num = new(big.Int) + denom = new(big.Int) + ) + + if parent.GasUsed > parentGasTarget { + // If the parent block used more gas than its target, the baseFee should increase. + // max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + num.SetUint64(parent.GasUsed - parentGasTarget) + num.Mul(num, parent.BaseFee) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + baseFeeDelta := math.BigMax(num, common.Big1) + + return num.Add(parent.BaseFee, baseFeeDelta) + } else { + // Otherwise if the parent block used less gas than its target, the baseFee should decrease. + // max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + num.SetUint64(parentGasTarget - parent.GasUsed) + num.Mul(num, parent.BaseFee) + num.Div(num, denom.SetUint64(parentGasTarget)) + num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) + baseFee := num.Sub(parent.BaseFee, num) + + return math.BigMax(baseFee, common.Big0) + } +} diff --git a/consensus/misc/eip1559/eip1559_test.go b/consensus/misc/eip1559/eip1559_test.go new file mode 100644 index 0000000..b5afdf0 --- /dev/null +++ b/consensus/misc/eip1559/eip1559_test.go @@ -0,0 +1,131 @@ +// 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 eip1559 + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +// copyConfig does a _shallow_ copy of a given config. Safe to set new values, but +// do not use e.g. SetInt() on the numbers. For testing only +func copyConfig(original *params.ChainConfig) *params.ChainConfig { + return ¶ms.ChainConfig{ + ChainID: original.ChainID, + HomesteadBlock: original.HomesteadBlock, + DAOForkBlock: original.DAOForkBlock, + DAOForkSupport: original.DAOForkSupport, + EIP150Block: original.EIP150Block, + EIP155Block: original.EIP155Block, + EIP158Block: original.EIP158Block, + ByzantiumBlock: original.ByzantiumBlock, + ConstantinopleBlock: original.ConstantinopleBlock, + PetersburgBlock: original.PetersburgBlock, + IstanbulBlock: original.IstanbulBlock, + MuirGlacierBlock: original.MuirGlacierBlock, + BerlinBlock: original.BerlinBlock, + LondonBlock: original.LondonBlock, + TerminalTotalDifficulty: original.TerminalTotalDifficulty, + Ethash: original.Ethash, + Clique: original.Clique, + } +} + +func config() *params.ChainConfig { + config := copyConfig(params.TestChainConfig) + config.LondonBlock = big.NewInt(5) + return config +} + +// TestBlockGasLimits tests the gasLimit checks for blocks both across +// the EIP-1559 boundary and post-1559 blocks +func TestBlockGasLimits(t *testing.T) { + initial := new(big.Int).SetUint64(params.InitialBaseFee) + + for i, tc := range []struct { + pGasLimit uint64 + pNum int64 + gasLimit uint64 + ok bool + }{ + // Transitions from non-london to london + {10000000, 4, 20000000, true}, // No change + {10000000, 4, 20019530, true}, // Upper limit + {10000000, 4, 20019531, false}, // Upper +1 + {10000000, 4, 19980470, true}, // Lower limit + {10000000, 4, 19980469, false}, // Lower limit -1 + // London to London + {20000000, 5, 20000000, true}, + {20000000, 5, 20019530, true}, // Upper limit + {20000000, 5, 20019531, false}, // Upper limit +1 + {20000000, 5, 19980470, true}, // Lower limit + {20000000, 5, 19980469, false}, // Lower limit -1 + {40000000, 5, 40039061, true}, // Upper limit + {40000000, 5, 40039062, false}, // Upper limit +1 + {40000000, 5, 39960939, true}, // lower limit + {40000000, 5, 39960938, false}, // Lower limit -1 + } { + parent := &types.Header{ + GasUsed: tc.pGasLimit / 2, + GasLimit: tc.pGasLimit, + BaseFee: initial, + Number: big.NewInt(tc.pNum), + } + header := &types.Header{ + GasUsed: tc.gasLimit / 2, + GasLimit: tc.gasLimit, + BaseFee: initial, + Number: big.NewInt(tc.pNum + 1), + } + err := VerifyEIP1559Header(config(), parent, header) + if tc.ok && err != nil { + t.Errorf("test %d: Expected valid header: %s", i, err) + } + if !tc.ok && err == nil { + t.Errorf("test %d: Expected invalid header", i) + } + } +} + +// TestCalcBaseFee assumes all blocks are 1559-blocks +func TestCalcBaseFee(t *testing.T) { + tests := []struct { + parentBaseFee int64 + parentGasLimit uint64 + parentGasUsed uint64 + expectedBaseFee int64 + }{ + {params.InitialBaseFee, 20000000, 10000000, params.InitialBaseFee}, // usage == target + {params.InitialBaseFee, 20000000, 9000000, 987500000}, // usage below target + {params.InitialBaseFee, 20000000, 11000000, 1012500000}, // usage above target + } + for i, test := range tests { + parent := &types.Header{ + Number: common.Big32, + GasLimit: test.parentGasLimit, + GasUsed: test.parentGasUsed, + BaseFee: big.NewInt(test.parentBaseFee), + } + if have, want := CalcBaseFee(config(), parent), big.NewInt(test.expectedBaseFee); have.Cmp(want) != 0 { + t.Errorf("test %d: have %d want %d, ", i, have, want) + } + } +} diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go new file mode 100644 index 0000000..2dad9a0 --- /dev/null +++ b/consensus/misc/eip4844/eip4844.go @@ -0,0 +1,98 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eip4844 + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +var ( + minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice) + blobGaspriceUpdateFraction = big.NewInt(params.BlobTxBlobGaspriceUpdateFraction) +) + +// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that +// if the current block contains no transactions, the excessBlobGas is updated +// accordingly. +func VerifyEIP4844Header(parent, header *types.Header) error { + // Verify the header is not malformed + if header.ExcessBlobGas == nil { + return errors.New("header is missing excessBlobGas") + } + if header.BlobGasUsed == nil { + return errors.New("header is missing blobGasUsed") + } + // Verify that the blob gas used remains within reasonable limits. + if *header.BlobGasUsed > params.MaxBlobGasPerBlock { + return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, params.MaxBlobGasPerBlock) + } + if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 { + return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob) + } + // Verify the excessBlobGas is correct based on the parent header + var ( + parentExcessBlobGas uint64 + parentBlobGasUsed uint64 + ) + if parent.ExcessBlobGas != nil { + parentExcessBlobGas = *parent.ExcessBlobGas + parentBlobGasUsed = *parent.BlobGasUsed + } + expectedExcessBlobGas := CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed) + if *header.ExcessBlobGas != expectedExcessBlobGas { + return fmt.Errorf("invalid excessBlobGas: have %d, want %d, parent excessBlobGas %d, parent blobDataUsed %d", + *header.ExcessBlobGas, expectedExcessBlobGas, parentExcessBlobGas, parentBlobGasUsed) + } + return nil +} + +// CalcExcessBlobGas calculates the excess blob gas after applying the set of +// blobs on top of the excess blob gas. +func CalcExcessBlobGas(parentExcessBlobGas uint64, parentBlobGasUsed uint64) uint64 { + excessBlobGas := parentExcessBlobGas + parentBlobGasUsed + if excessBlobGas < params.BlobTxTargetBlobGasPerBlock { + return 0 + } + return excessBlobGas - params.BlobTxTargetBlobGasPerBlock +} + +// CalcBlobFee calculates the blobfee from the header's excess blob gas field. +func CalcBlobFee(excessBlobGas uint64) *big.Int { + return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), blobGaspriceUpdateFraction) +} + +// fakeExponential approximates factor * e ** (numerator / denominator) using +// Taylor expansion. +func fakeExponential(factor, numerator, denominator *big.Int) *big.Int { + var ( + output = new(big.Int) + accum = new(big.Int).Mul(factor, denominator) + ) + for i := 1; accum.Sign() > 0; i++ { + output.Add(output, accum) + + accum.Mul(accum, numerator) + accum.Div(accum, denominator) + accum.Div(accum, big.NewInt(int64(i))) + } + return output.Div(output, denominator) +} diff --git a/consensus/misc/eip4844/eip4844_test.go b/consensus/misc/eip4844/eip4844_test.go new file mode 100644 index 0000000..ec41738 --- /dev/null +++ b/consensus/misc/eip4844/eip4844_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 eip4844 + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/params" +) + +func TestCalcExcessBlobGas(t *testing.T) { + var tests = []struct { + excess uint64 + blobs uint64 + want uint64 + }{ + // The excess blob gas should not increase from zero if the used blob + // slots are below - or equal - to the target. + {0, 0, 0}, + {0, 1, 0}, + {0, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, 0}, + + // If the target blob gas is exceeded, the excessBlobGas should increase + // by however much it was overshot + {0, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob}, + {1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob + 1}, + {1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 2, 2*params.BlobTxBlobGasPerBlob + 1}, + + // The excess blob gas should decrease by however much the target was + // under-shot, capped at zero. + {params.BlobTxTargetBlobGasPerBlock, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, params.BlobTxTargetBlobGasPerBlock}, + {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, params.BlobTxTargetBlobGasPerBlock - params.BlobTxBlobGasPerBlob}, + {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 2, params.BlobTxTargetBlobGasPerBlock - (2 * params.BlobTxBlobGasPerBlob)}, + {params.BlobTxBlobGasPerBlob - 1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, 0}, + } + for i, tt := range tests { + result := CalcExcessBlobGas(tt.excess, tt.blobs*params.BlobTxBlobGasPerBlob) + if result != tt.want { + t.Errorf("test %d: excess blob gas mismatch: have %v, want %v", i, result, tt.want) + } + } +} + +func TestCalcBlobFee(t *testing.T) { + tests := []struct { + excessBlobGas uint64 + blobfee int64 + }{ + {0, 1}, + {2314057, 1}, + {2314058, 2}, + {10 * 1024 * 1024, 23}, + } + for i, tt := range tests { + have := CalcBlobFee(tt.excessBlobGas) + if have.Int64() != tt.blobfee { + t.Errorf("test %d: blobfee mismatch: have %v want %v", i, have, tt.blobfee) + } + } +} + +func TestFakeExponential(t *testing.T) { + tests := []struct { + factor int64 + numerator int64 + denominator int64 + want int64 + }{ + // When numerator == 0 the return value should always equal the value of factor + {1, 0, 1, 1}, + {38493, 0, 1000, 38493}, + {0, 1234, 2345, 0}, // should be 0 + {1, 2, 1, 6}, // approximate 7.389 + {1, 4, 2, 6}, + {1, 3, 1, 16}, // approximate 20.09 + {1, 6, 2, 18}, + {1, 4, 1, 49}, // approximate 54.60 + {1, 8, 2, 50}, + {10, 8, 2, 542}, // approximate 540.598 + {11, 8, 2, 596}, // approximate 600.58 + {1, 5, 1, 136}, // approximate 148.4 + {1, 5, 2, 11}, // approximate 12.18 + {2, 5, 2, 23}, // approximate 24.36 + {1, 50000000, 2225652, 5709098764}, + } + for i, tt := range tests { + f, n, d := big.NewInt(tt.factor), big.NewInt(tt.numerator), big.NewInt(tt.denominator) + original := fmt.Sprintf("%d %d %d", f, n, d) + have := fakeExponential(f, n, d) + if have.Int64() != tt.want { + t.Errorf("test %d: fake exponential mismatch: have %v want %v", i, have, tt.want) + } + later := fmt.Sprintf("%d %d %d", f, n, d) + if original != later { + t.Errorf("test %d: fake exponential modified arguments: have\n%v\nwant\n%v", i, later, original) + } + } +} diff --git a/consensus/misc/gaslimit.go b/consensus/misc/gaslimit.go new file mode 100644 index 0000000..dfcabd9 --- /dev/null +++ b/consensus/misc/gaslimit.go @@ -0,0 +1,41 @@ +// 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 misc + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/params" +) + +// VerifyGaslimit verifies the header gas limit according increase/decrease +// in relation to the parent gas limit. +func VerifyGaslimit(parentGasLimit, headerGasLimit uint64) error { + // Verify that the gas limit remains within allowed bounds + diff := int64(parentGasLimit) - int64(headerGasLimit) + if diff < 0 { + diff *= -1 + } + limit := parentGasLimit / params.GasLimitBoundDivisor + if uint64(diff) >= limit { + return fmt.Errorf("invalid gas limit: have %d, want %d +-= %d", headerGasLimit, parentGasLimit, limit-1) + } + if headerGasLimit < params.MinGasLimit { + return fmt.Errorf("invalid gas limit below %d", params.MinGasLimit) + } + return nil +} diff --git a/console/bridge.go b/console/bridge.go new file mode 100644 index 0000000..3757804 --- /dev/null +++ b/console/bridge.go @@ -0,0 +1,484 @@ +// 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 console + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "reflect" + "strings" + "time" + + "github.com/dop251/goja" + "github.com/ethereum/go-ethereum/accounts/scwallet" + "github.com/ethereum/go-ethereum/accounts/usbwallet" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/internal/jsre" + "github.com/ethereum/go-ethereum/rpc" +) + +// bridge is a collection of JavaScript utility methods to bride the .js runtime +// environment and the Go RPC connection backing the remote method calls. +type bridge struct { + client *rpc.Client // RPC client to execute Ethereum requests through + prompter prompt.UserPrompter // Input prompter to allow interactive user feedback + printer io.Writer // Output writer to serialize any display strings to +} + +// newBridge creates a new JavaScript wrapper around an RPC client. +func newBridge(client *rpc.Client, prompter prompt.UserPrompter, printer io.Writer) *bridge { + return &bridge{ + client: client, + prompter: prompter, + printer: printer, + } +} + +func getJeth(vm *goja.Runtime) *goja.Object { + jeth := vm.Get("jeth") + if jeth == nil { + panic(vm.ToValue("jeth object does not exist")) + } + return jeth.ToObject(vm) +} + +// NewAccount is a wrapper around the personal.newAccount RPC method that uses a +// non-echoing password prompt to acquire the passphrase and executes the original +// RPC method (saved in jeth.newAccount) with it to actually execute the RPC call. +func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) { + var ( + password string + confirm string + err error + ) + switch { + // No password was specified, prompt the user for it + case len(call.Arguments) == 0: + if password, err = b.prompter.PromptPassword("Passphrase: "); err != nil { + return nil, err + } + if confirm, err = b.prompter.PromptPassword("Repeat passphrase: "); err != nil { + return nil, err + } + if password != confirm { + return nil, errors.New("passwords don't match") + } + // A single string password was specified, use that + case len(call.Arguments) == 1 && call.Argument(0).ToString() != nil: + password = call.Argument(0).ToString().String() + default: + return nil, errors.New("expected 0 or 1 string argument") + } + // Password acquired, execute the call and return + newAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("newAccount")) + if !callable { + return nil, errors.New("jeth.newAccount is not callable") + } + ret, err := newAccount(goja.Null(), call.VM.ToValue(password)) + if err != nil { + return nil, err + } + return ret, nil +} + +// OpenWallet is a wrapper around personal.openWallet which can interpret and +// react to certain error messages, such as the Trezor PIN matrix request. +func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { + // Make sure we have a wallet specified to open + if call.Argument(0).ToObject(call.VM).ClassName() != "String" { + return nil, errors.New("first argument must be the wallet URL to open") + } + wallet := call.Argument(0) + + var passwd goja.Value + if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) { + passwd = call.VM.ToValue("") + } else { + passwd = call.Argument(1) + } + // Open the wallet and return if successful in itself + openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) + if !callable { + return nil, errors.New("jeth.openWallet is not callable") + } + val, err := openWallet(goja.Null(), wallet, passwd) + if err == nil { + return val, nil + } + + // Wallet open failed, report error unless it's a PIN or PUK entry + switch { + case strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()): + val, err = b.readPinAndReopenWallet(call) + if err == nil { + return val, nil + } + val, err = b.readPassphraseAndReopenWallet(call) + if err != nil { + return nil, err + } + + case strings.HasSuffix(err.Error(), scwallet.ErrPairingPasswordNeeded.Error()): + // PUK input requested, fetch from the user and call open again + input, err := b.prompter.PromptPassword("Please enter the pairing password: ") + if err != nil { + return nil, err + } + passwd = call.VM.ToValue(input) + if val, err = openWallet(goja.Null(), wallet, passwd); err != nil { + if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) { + return nil, err + } + // PIN input requested, fetch from the user and call open again + input, err := b.prompter.PromptPassword("Please enter current PIN: ") + if err != nil { + return nil, err + } + if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { + return nil, err + } + } + + case strings.HasSuffix(err.Error(), scwallet.ErrPINUnblockNeeded.Error()): + // PIN unblock requested, fetch PUK and new PIN from the user + var pukpin string + input, err := b.prompter.PromptPassword("Please enter current PUK: ") + if err != nil { + return nil, err + } + pukpin = input + input, err = b.prompter.PromptPassword("Please enter new PIN: ") + if err != nil { + return nil, err + } + pukpin += input + + if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(pukpin)); err != nil { + return nil, err + } + + case strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()): + // PIN input requested, fetch from the user and call open again + input, err := b.prompter.PromptPassword("Please enter current PIN: ") + if err != nil { + return nil, err + } + if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { + return nil, err + } + + default: + // Unknown error occurred, drop to the user + return nil, err + } + return val, nil +} + +func (b *bridge) readPassphraseAndReopenWallet(call jsre.Call) (goja.Value, error) { + wallet := call.Argument(0) + input, err := b.prompter.PromptPassword("Please enter your passphrase: ") + if err != nil { + return nil, err + } + openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) + if !callable { + return nil, errors.New("jeth.openWallet is not callable") + } + return openWallet(goja.Null(), wallet, call.VM.ToValue(input)) +} + +func (b *bridge) readPinAndReopenWallet(call jsre.Call) (goja.Value, error) { + wallet := call.Argument(0) + // Trezor PIN matrix input requested, display the matrix to the user and fetch the data + fmt.Fprintf(b.printer, "Look at the device for number positions\n\n") + fmt.Fprintf(b.printer, "7 | 8 | 9\n") + fmt.Fprintf(b.printer, "--+---+--\n") + fmt.Fprintf(b.printer, "4 | 5 | 6\n") + fmt.Fprintf(b.printer, "--+---+--\n") + fmt.Fprintf(b.printer, "1 | 2 | 3\n\n") + + input, err := b.prompter.PromptPassword("Please enter current PIN: ") + if err != nil { + return nil, err + } + openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) + if !callable { + return nil, errors.New("jeth.openWallet is not callable") + } + return openWallet(goja.Null(), wallet, call.VM.ToValue(input)) +} + +// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that +// uses a non-echoing password prompt to acquire the passphrase and executes the +// original RPC method (saved in jeth.unlockAccount) with it to actually execute +// the RPC call. +func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { + if len(call.Arguments) < 1 { + return nil, errors.New("usage: unlockAccount(account, [ password, duration ])") + } + + account := call.Argument(0) + // Make sure we have an account specified to unlock. + if goja.IsUndefined(account) || goja.IsNull(account) || account.ExportType().Kind() != reflect.String { + return nil, errors.New("first argument must be the account to unlock") + } + + // If password is not given or is the null value, prompt the user for it. + var passwd goja.Value + if goja.IsUndefined(call.Argument(1)) || goja.IsNull(call.Argument(1)) { + fmt.Fprintf(b.printer, "Unlock account %s\n", account) + input, err := b.prompter.PromptPassword("Passphrase: ") + if err != nil { + return nil, err + } + passwd = call.VM.ToValue(input) + } else { + if call.Argument(1).ExportType().Kind() != reflect.String { + return nil, errors.New("password must be a string") + } + passwd = call.Argument(1) + } + + // Third argument is the duration how long the account should be unlocked. + duration := goja.Null() + if !goja.IsUndefined(call.Argument(2)) && !goja.IsNull(call.Argument(2)) { + if !isNumber(call.Argument(2)) { + return nil, errors.New("unlock duration must be a number") + } + duration = call.Argument(2) + } + + // Send the request to the backend and return. + unlockAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount")) + if !callable { + return nil, errors.New("jeth.unlockAccount is not callable") + } + return unlockAccount(goja.Null(), account, passwd, duration) +} + +// Sign is a wrapper around the personal.sign RPC method that uses a non-echoing password +// prompt to acquire the passphrase and executes the original RPC method (saved in +// jeth.sign) with it to actually execute the RPC call. +func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { + if nArgs := len(call.Arguments); nArgs < 2 { + return nil, errors.New("usage: sign(message, account, [ password ])") + } + var ( + message = call.Argument(0) + account = call.Argument(1) + passwd = call.Argument(2) + ) + + if goja.IsUndefined(message) || message.ExportType().Kind() != reflect.String { + return nil, errors.New("first argument must be the message to sign") + } + if goja.IsUndefined(account) || account.ExportType().Kind() != reflect.String { + return nil, errors.New("second argument must be the account to sign with") + } + + // if the password is not given or null ask the user and ensure password is a string + if goja.IsUndefined(passwd) || goja.IsNull(passwd) { + fmt.Fprintf(b.printer, "Give password for account %s\n", account) + input, err := b.prompter.PromptPassword("Password: ") + if err != nil { + return nil, err + } + passwd = call.VM.ToValue(input) + } else if passwd.ExportType().Kind() != reflect.String { + return nil, errors.New("third argument must be the password to unlock the account") + } + + // Send the request to the backend and return + sign, callable := goja.AssertFunction(getJeth(call.VM).Get("sign")) + if !callable { + return nil, errors.New("jeth.sign is not callable") + } + return sign(goja.Null(), message, account, passwd) +} + +// Sleep will block the console for the specified number of seconds. +func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) { + if nArgs := len(call.Arguments); nArgs < 1 { + return nil, errors.New("usage: sleep()") + } + sleepObj := call.Argument(0) + if goja.IsUndefined(sleepObj) || goja.IsNull(sleepObj) || !isNumber(sleepObj) { + return nil, errors.New("usage: sleep()") + } + sleep := sleepObj.ToFloat() + time.Sleep(time.Duration(sleep * float64(time.Second))) + return call.VM.ToValue(true), nil +} + +// SleepBlocks will block the console for a specified number of new blocks optionally +// until the given timeout is reached. +func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) { + // Parse the input parameters for the sleep. + var ( + blocks = int64(0) + sleep = int64(9999999999999999) // indefinitely + ) + nArgs := len(call.Arguments) + if nArgs == 0 { + return nil, errors.New("usage: sleepBlocks([, max sleep in seconds])") + } + if nArgs >= 1 { + if goja.IsNull(call.Argument(0)) || goja.IsUndefined(call.Argument(0)) || !isNumber(call.Argument(0)) { + return nil, errors.New("expected number as first argument") + } + blocks = call.Argument(0).ToInteger() + } + if nArgs >= 2 { + if goja.IsNull(call.Argument(1)) || goja.IsUndefined(call.Argument(1)) || !isNumber(call.Argument(1)) { + return nil, errors.New("expected number as second argument") + } + sleep = call.Argument(1).ToInteger() + } + + // Poll the current block number until either it or a timeout is reached. + deadline := time.Now().Add(time.Duration(sleep) * time.Second) + var lastNumber hexutil.Uint64 + if err := b.client.Call(&lastNumber, "eth_blockNumber"); err != nil { + return nil, err + } + for time.Now().Before(deadline) { + var number hexutil.Uint64 + if err := b.client.Call(&number, "eth_blockNumber"); err != nil { + return nil, err + } + if number != lastNumber { + lastNumber = number + blocks-- + } + if blocks <= 0 { + break + } + time.Sleep(time.Second) + } + return call.VM.ToValue(true), nil +} + +type jsonrpcCall struct { + ID int64 + Method string + Params []interface{} +} + +// Send implements the web3 provider "send" method. +func (b *bridge) Send(call jsre.Call) (goja.Value, error) { + // Remarshal the request into a Go value. + reqVal, err := call.Argument(0).ToObject(call.VM).MarshalJSON() + if err != nil { + return nil, err + } + + var ( + rawReq = string(reqVal) + dec = json.NewDecoder(strings.NewReader(rawReq)) + reqs []jsonrpcCall + batch bool + ) + dec.UseNumber() // avoid float64s + if rawReq[0] == '[' { + batch = true + dec.Decode(&reqs) + } else { + batch = false + reqs = make([]jsonrpcCall, 1) + dec.Decode(&reqs[0]) + } + + // Execute the requests. + var resps []*goja.Object + for _, req := range reqs { + resp := call.VM.NewObject() + resp.Set("jsonrpc", "2.0") + resp.Set("id", req.ID) + + var result json.RawMessage + if err = b.client.Call(&result, req.Method, req.Params...); err == nil { + if result == nil { + // Special case null because it is decoded as an empty + // raw message for some reason. + resp.Set("result", goja.Null()) + } else { + JSON := call.VM.Get("JSON").ToObject(call.VM) + parse, callable := goja.AssertFunction(JSON.Get("parse")) + if !callable { + return nil, errors.New("JSON.parse is not a function") + } + resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result))) + if err != nil { + setError(resp, -32603, err.Error(), nil) + } else { + resp.Set("result", resultVal) + } + } + } else { + code := -32603 + var data interface{} + if err, ok := err.(rpc.Error); ok { + code = err.ErrorCode() + } + if err, ok := err.(rpc.DataError); ok { + data = err.ErrorData() + } + setError(resp, code, err.Error(), data) + } + resps = append(resps, resp) + } + // Return the responses either to the callback (if supplied) + // or directly as the return value. + var result goja.Value + if batch { + result = call.VM.ToValue(resps) + } else { + result = resps[0] + } + if fn, isFunc := goja.AssertFunction(call.Argument(1)); isFunc { + fn(goja.Null(), goja.Null(), result) + return goja.Undefined(), nil + } + return result, nil +} + +func setError(resp *goja.Object, code int, msg string, data interface{}) { + err := make(map[string]interface{}) + err["code"] = code + err["message"] = msg + if data != nil { + err["data"] = data + } + resp.Set("error", err) +} + +// isNumber returns true if input value is a JS number. +func isNumber(v goja.Value) bool { + k := v.ExportType().Kind() + return k >= reflect.Int && k <= reflect.Float64 +} + +func getObject(vm *goja.Runtime, name string) *goja.Object { + v := vm.Get(name) + if v == nil { + return nil + } + return v.ToObject(vm) +} diff --git a/console/bridge_test.go b/console/bridge_test.go new file mode 100644 index 0000000..e57e294 --- /dev/null +++ b/console/bridge_test.go @@ -0,0 +1,48 @@ +// 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 console + +import ( + "testing" + + "github.com/dop251/goja" + "github.com/ethereum/go-ethereum/internal/jsre" +) + +// TestUndefinedAsParam ensures that personal functions can receive +// `undefined` as a parameter. +func TestUndefinedAsParam(t *testing.T) { + b := bridge{} + call := jsre.Call{} + call.Arguments = []goja.Value{goja.Undefined()} + + b.UnlockAccount(call) + b.Sign(call) + b.Sleep(call) +} + +// TestNullAsParam ensures that personal functions can receive +// `null` as a parameter. +func TestNullAsParam(t *testing.T) { + b := bridge{} + call := jsre.Call{} + call.Arguments = []goja.Value{goja.Null()} + + b.UnlockAccount(call) + b.Sign(call) + b.Sleep(call) +} diff --git a/console/console.go b/console/console.go new file mode 100644 index 0000000..5acb4cd --- /dev/null +++ b/console/console.go @@ -0,0 +1,564 @@ +// 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 console + +import ( + "errors" + "fmt" + "io" + "os" + "os/signal" + "path/filepath" + "regexp" + "sort" + "strings" + "sync" + "syscall" + + "github.com/dop251/goja" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/internal/jsre" + "github.com/ethereum/go-ethereum/internal/jsre/deps" + "github.com/ethereum/go-ethereum/internal/web3ext" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/mattn/go-colorable" + "github.com/peterh/liner" +) + +var ( + // u: unlock, s: signXX, sendXX, n: newAccount, i: importXX + passwordRegexp = regexp.MustCompile(`personal.[nusi]`) + onlyWhitespace = regexp.MustCompile(`^\s*$`) + exit = regexp.MustCompile(`^\s*exit\s*;*\s*$`) +) + +// HistoryFile is the file within the data directory to store input scrollback. +const HistoryFile = "history" + +// DefaultPrompt is the default prompt line prefix to use for user input querying. +const DefaultPrompt = "> " + +// Config is the collection of configurations to fine tune the behavior of the +// JavaScript console. +type Config struct { + DataDir string // Data directory to store the console history at + DocRoot string // Filesystem path from where to load JavaScript files from + Client *rpc.Client // RPC client to execute Ethereum requests through + Prompt string // Input prompt prefix string (defaults to DefaultPrompt) + Prompter prompt.UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter) + Printer io.Writer // Output writer to serialize any display strings to (defaults to os.Stdout) + Preload []string // Absolute paths to JavaScript files to preload +} + +// Console is a JavaScript interpreted runtime environment. It is a fully fledged +// JavaScript console attached to a running node via an external or in-process RPC +// client. +type Console struct { + client *rpc.Client // RPC client to execute Ethereum requests through + jsre *jsre.JSRE // JavaScript runtime environment running the interpreter + prompt string // Input prompt prefix string + prompter prompt.UserPrompter // Input prompter to allow interactive user feedback + histPath string // Absolute path to the console scrollback history + history []string // Scroll history maintained by the console + printer io.Writer // Output writer to serialize any display strings to + + interactiveStopped chan struct{} + stopInteractiveCh chan struct{} + signalReceived chan struct{} + stopped chan struct{} + wg sync.WaitGroup + stopOnce sync.Once +} + +// New initializes a JavaScript interpreted runtime environment and sets defaults +// with the config struct. +func New(config Config) (*Console, error) { + // Handle unset config values gracefully + if config.Prompter == nil { + config.Prompter = prompt.Stdin + } + if config.Prompt == "" { + config.Prompt = DefaultPrompt + } + if config.Printer == nil { + config.Printer = colorable.NewColorableStdout() + } + + // Initialize the console and return + console := &Console{ + client: config.Client, + jsre: jsre.New(config.DocRoot, config.Printer), + prompt: config.Prompt, + prompter: config.Prompter, + printer: config.Printer, + histPath: filepath.Join(config.DataDir, HistoryFile), + interactiveStopped: make(chan struct{}), + stopInteractiveCh: make(chan struct{}), + signalReceived: make(chan struct{}, 1), + stopped: make(chan struct{}), + } + if err := os.MkdirAll(config.DataDir, 0700); err != nil { + return nil, err + } + if err := console.init(config.Preload); err != nil { + return nil, err + } + + console.wg.Add(1) + go console.interruptHandler() + + return console, nil +} + +// init retrieves the available APIs from the remote RPC provider and initializes +// the console's JavaScript namespaces based on the exposed modules. +func (c *Console) init(preload []string) error { + c.initConsoleObject() + + // Initialize the JavaScript <-> Go RPC bridge. + bridge := newBridge(c.client, c.prompter, c.printer) + if err := c.initWeb3(bridge); err != nil { + return err + } + if err := c.initExtensions(); err != nil { + return err + } + + // Add bridge overrides for web3.js functionality. + c.jsre.Do(func(vm *goja.Runtime) { + c.initAdmin(vm, bridge) + c.initPersonal(vm, bridge) + }) + + // Preload JavaScript files. + for _, path := range preload { + if err := c.jsre.Exec(path); err != nil { + failure := err.Error() + if gojaErr, ok := err.(*goja.Exception); ok { + failure = gojaErr.String() + } + return fmt.Errorf("%s: %v", path, failure) + } + } + + // Configure the input prompter for history and tab completion. + if c.prompter != nil { + if content, err := os.ReadFile(c.histPath); err != nil { + c.prompter.SetHistory(nil) + } else { + c.history = strings.Split(string(content), "\n") + c.prompter.SetHistory(c.history) + } + c.prompter.SetWordCompleter(c.AutoCompleteInput) + } + return nil +} + +func (c *Console) initConsoleObject() { + c.jsre.Do(func(vm *goja.Runtime) { + console := vm.NewObject() + console.Set("log", c.consoleOutput) + console.Set("error", c.consoleOutput) + vm.Set("console", console) + }) +} + +func (c *Console) initWeb3(bridge *bridge) error { + if err := c.jsre.Compile("bignumber.js", deps.BigNumberJS); err != nil { + return fmt.Errorf("bignumber.js: %v", err) + } + if err := c.jsre.Compile("web3.js", deps.Web3JS); err != nil { + return fmt.Errorf("web3.js: %v", err) + } + if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil { + return fmt.Errorf("web3 require: %v", err) + } + var err error + c.jsre.Do(func(vm *goja.Runtime) { + transport := vm.NewObject() + transport.Set("send", jsre.MakeCallback(vm, bridge.Send)) + transport.Set("sendAsync", jsre.MakeCallback(vm, bridge.Send)) + vm.Set("_consoleWeb3Transport", transport) + _, err = vm.RunString("var web3 = new Web3(_consoleWeb3Transport)") + }) + return err +} + +var defaultAPIs = map[string]string{"eth": "1.0", "net": "1.0", "debug": "1.0"} + +// initExtensions loads and registers web3.js extensions. +func (c *Console) initExtensions() error { + const methodNotFound = -32601 + apis, err := c.client.SupportedModules() + if err != nil { + if rpcErr, ok := err.(rpc.Error); ok && rpcErr.ErrorCode() == methodNotFound { + log.Warn("Server does not support method rpc_modules, using default API list.") + apis = defaultAPIs + } else { + return err + } + } + + // Compute aliases from server-provided modules. + aliases := map[string]struct{}{"eth": {}} + for api := range apis { + if api == "web3" { + continue + } + aliases[api] = struct{}{} + if file, ok := web3ext.Modules[api]; ok { + if err = c.jsre.Compile(api+".js", file); err != nil { + return fmt.Errorf("%s.js: %v", api, err) + } + } + } + + // Apply aliases. + c.jsre.Do(func(vm *goja.Runtime) { + web3 := getObject(vm, "web3") + for name := range aliases { + if v := web3.Get(name); v != nil { + vm.Set(name, v) + } + } + }) + return nil +} + +// initAdmin creates additional admin APIs implemented by the bridge. +func (c *Console) initAdmin(vm *goja.Runtime, bridge *bridge) { + if admin := getObject(vm, "admin"); admin != nil { + admin.Set("sleepBlocks", jsre.MakeCallback(vm, bridge.SleepBlocks)) + admin.Set("sleep", jsre.MakeCallback(vm, bridge.Sleep)) + admin.Set("clearHistory", c.clearHistory) + } +} + +// initPersonal redirects account-related API methods through the bridge. +// +// If the console is in interactive mode and the 'personal' API is available, override +// the openWallet, unlockAccount, newAccount and sign methods since these require user +// interaction. The original web3 callbacks are stored in 'jeth'. These will be called +// by the bridge after the prompt and send the original web3 request to the backend. +func (c *Console) initPersonal(vm *goja.Runtime, bridge *bridge) { + personal := getObject(vm, "personal") + if personal == nil || c.prompter == nil { + return + } + log.Warn("Enabling deprecated personal namespace") + jeth := vm.NewObject() + vm.Set("jeth", jeth) + jeth.Set("openWallet", personal.Get("openWallet")) + jeth.Set("unlockAccount", personal.Get("unlockAccount")) + jeth.Set("newAccount", personal.Get("newAccount")) + jeth.Set("sign", personal.Get("sign")) + personal.Set("openWallet", jsre.MakeCallback(vm, bridge.OpenWallet)) + personal.Set("unlockAccount", jsre.MakeCallback(vm, bridge.UnlockAccount)) + personal.Set("newAccount", jsre.MakeCallback(vm, bridge.NewAccount)) + personal.Set("sign", jsre.MakeCallback(vm, bridge.Sign)) +} + +func (c *Console) clearHistory() { + c.history = nil + c.prompter.ClearHistory() + if err := os.Remove(c.histPath); err != nil { + fmt.Fprintln(c.printer, "can't delete history file:", err) + } else { + fmt.Fprintln(c.printer, "history file deleted") + } +} + +// consoleOutput is an override for the console.log and console.error methods to +// stream the output into the configured output stream instead of stdout. +func (c *Console) consoleOutput(call goja.FunctionCall) goja.Value { + var output []string + for _, argument := range call.Arguments { + output = append(output, fmt.Sprintf("%v", argument)) + } + fmt.Fprintln(c.printer, strings.Join(output, " ")) + return goja.Null() +} + +// AutoCompleteInput is a pre-assembled word completer to be used by the user +// input prompter to provide hints to the user about the methods available. +func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, string) { + // No completions can be provided for empty inputs + if len(line) == 0 || pos == 0 { + return "", nil, "" + } + // Chunk data to relevant part for autocompletion + // E.g. in case of nested lines eth.getBalance(eth.coinb + start := pos - 1 + for ; start > 0; start-- { + // Skip all methods and namespaces (i.e. including the dot) + c := line[start] + if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '1' && c <= '9') { + continue + } + // We've hit an unexpected character, autocomplete form here + start++ + break + } + return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:] +} + +// Welcome show summary of current Geth instance and some metadata about the +// console's available modules. +func (c *Console) Welcome() { + message := "Welcome to the Geth JavaScript console!\n\n" + + // Print some generic Geth metadata + if res, err := c.jsre.Run(` + var message = "instance: " + web3.version.node + "\n"; + message += "at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")\n"; + try { + message += " datadir: " + admin.datadir + "\n"; + } catch (err) {} + message + `); err == nil { + message += res.String() + } + // List all the supported modules for the user to call + if apis, err := c.client.SupportedModules(); err == nil { + modules := make([]string, 0, len(apis)) + for api, version := range apis { + modules = append(modules, fmt.Sprintf("%s:%s", api, version)) + } + sort.Strings(modules) + message += " modules: " + strings.Join(modules, " ") + "\n" + } + message += "\nTo exit, press ctrl-d or type exit" + fmt.Fprintln(c.printer, message) +} + +// Evaluate executes code and pretty prints the result to the specified output +// stream. +func (c *Console) Evaluate(statement string) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(c.printer, "[native] error: %v\n", r) + } + }() + c.jsre.Evaluate(statement, c.printer) + + // Avoid exiting Interactive when jsre was interrupted by SIGINT. + c.clearSignalReceived() +} + +// interruptHandler runs in its own goroutine and waits for signals. +// When a signal is received, it interrupts the JS interpreter. +func (c *Console) interruptHandler() { + defer c.wg.Done() + + // During Interactive, liner inhibits the signal while it is prompting for + // input. However, the signal will be received while evaluating JS. + // + // On unsupported terminals, SIGINT can also happen while prompting. + // Unfortunately, it is not possible to abort the prompt in this case and + // the c.readLines goroutine leaks. + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT) + defer signal.Stop(sig) + + for { + select { + case <-sig: + c.setSignalReceived() + c.jsre.Interrupt(errors.New("interrupted")) + case <-c.stopInteractiveCh: + close(c.interactiveStopped) + c.jsre.Interrupt(errors.New("interrupted")) + case <-c.stopped: + return + } + } +} + +func (c *Console) setSignalReceived() { + select { + case c.signalReceived <- struct{}{}: + default: + } +} + +func (c *Console) clearSignalReceived() { + select { + case <-c.signalReceived: + default: + } +} + +// StopInteractive causes Interactive to return as soon as possible. +func (c *Console) StopInteractive() { + select { + case c.stopInteractiveCh <- struct{}{}: + case <-c.stopped: + } +} + +// Interactive starts an interactive user session, where input is prompted from +// the configured user prompter. +func (c *Console) Interactive() { + var ( + prompt = c.prompt // the current prompt line (used for multi-line inputs) + indents = 0 // the current number of input indents (used for multi-line inputs) + input = "" // the current user input + inputLine = make(chan string, 1) // receives user input + inputErr = make(chan error, 1) // receives liner errors + requestLine = make(chan string) // requests a line of input + ) + + defer func() { + c.writeHistory() + }() + + // The line reader runs in a separate goroutine. + go c.readLines(inputLine, inputErr, requestLine) + defer close(requestLine) + + for { + // Send the next prompt, triggering an input read. + requestLine <- prompt + + select { + case <-c.interactiveStopped: + fmt.Fprintln(c.printer, "node is down, exiting console") + return + + case <-c.signalReceived: + // SIGINT received while prompting for input -> unsupported terminal. + // I'm not sure if the best choice would be to leave the console running here. + // Bash keeps running in this case. node.js does not. + fmt.Fprintln(c.printer, "caught interrupt, exiting") + return + + case err := <-inputErr: + if err == liner.ErrPromptAborted { + // When prompting for multi-line input, the first Ctrl-C resets + // the multi-line state. + prompt, indents, input = c.prompt, 0, "" + continue + } + return + + case line := <-inputLine: + // User input was returned by the prompter, handle special cases. + if indents <= 0 && exit.MatchString(line) { + return + } + if onlyWhitespace.MatchString(line) { + continue + } + // Append the line to the input and check for multi-line interpretation. + input += line + "\n" + indents = countIndents(input) + if indents <= 0 { + prompt = c.prompt + } else { + prompt = strings.Repeat(".", indents*3) + " " + } + // If all the needed lines are present, save the command and run it. + if indents <= 0 { + if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) { + if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] { + c.history = append(c.history, command) + if c.prompter != nil { + c.prompter.AppendHistory(command) + } + } + } + c.Evaluate(input) + input = "" + } + } + } +} + +// readLines runs in its own goroutine, prompting for input. +func (c *Console) readLines(input chan<- string, errc chan<- error, prompt <-chan string) { + for p := range prompt { + line, err := c.prompter.PromptInput(p) + if err != nil { + errc <- err + } else { + input <- line + } + } +} + +// countIndents returns the number of indentations for the given input. +// In case of invalid input such as var a = } the result can be negative. +func countIndents(input string) int { + var ( + indents = 0 + inString = false + strOpenChar = ' ' // keep track of the string open char to allow var str = "I'm ...."; + charEscaped = false // keep track if the previous char was the '\' char, allow var str = "abc\"def"; + ) + + for _, c := range input { + switch c { + case '\\': + // indicate next char as escaped when in string and previous char isn't escaping this backslash + if !charEscaped && inString { + charEscaped = true + } + case '\'', '"': + if inString && !charEscaped && strOpenChar == c { // end string + inString = false + } else if !inString && !charEscaped { // begin string + inString = true + strOpenChar = c + } + charEscaped = false + case '{', '(': + if !inString { // ignore brackets when in string, allow var str = "a{"; without indenting + indents++ + } + charEscaped = false + case '}', ')': + if !inString { + indents-- + } + charEscaped = false + default: + charEscaped = false + } + } + + return indents +} + +// Stop cleans up the console and terminates the runtime environment. +func (c *Console) Stop(graceful bool) error { + c.stopOnce.Do(func() { + // Stop the interrupt handler. + close(c.stopped) + c.wg.Wait() + }) + + c.jsre.Stop(graceful) + return nil +} + +func (c *Console) writeHistory() error { + if err := os.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil { + return err + } + return os.Chmod(c.histPath, 0600) // Force 0600, even if it was different previously +} diff --git a/console/console_test.go b/console/console_test.go new file mode 100644 index 0000000..d210a99 --- /dev/null +++ b/console/console_test.go @@ -0,0 +1,321 @@ +// 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 console + +import ( + "bytes" + "errors" + "fmt" + "os" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/internal/jsre" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" +) + +const ( + testInstance = "console-tester" + testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" +) + +// hookedPrompter implements UserPrompter to simulate use input via channels. +type hookedPrompter struct { + scheduler chan string +} + +func (p *hookedPrompter) PromptInput(prompt string) (string, error) { + // Send the prompt to the tester + select { + case p.scheduler <- prompt: + case <-time.After(time.Second): + return "", errors.New("prompt timeout") + } + // Retrieve the response and feed to the console + select { + case input := <-p.scheduler: + return input, nil + case <-time.After(time.Second): + return "", errors.New("input timeout") + } +} + +func (p *hookedPrompter) PromptPassword(prompt string) (string, error) { + return "", errors.New("not implemented") +} +func (p *hookedPrompter) PromptConfirm(prompt string) (bool, error) { + return false, errors.New("not implemented") +} +func (p *hookedPrompter) SetHistory(history []string) {} +func (p *hookedPrompter) AppendHistory(command string) {} +func (p *hookedPrompter) ClearHistory() {} +func (p *hookedPrompter) SetWordCompleter(completer prompt.WordCompleter) {} + +// tester is a console test environment for the console tests to operate on. +type tester struct { + workspace string + stack *node.Node + ethereum *eth.Ethereum + console *Console + input *hookedPrompter + output *bytes.Buffer +} + +// newTester creates a test environment based on which the console can operate. +// Please ensure you call Close() on the returned tester to avoid leaks. +func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { + // Create a temporary storage for the node keys and initialize it + workspace := t.TempDir() + + // Create a networkless protocol stack and start an Ethereum service within + stack, err := node.New(&node.Config{DataDir: workspace, UseLightweightKDF: true, Name: testInstance}) + if err != nil { + t.Fatalf("failed to create node: %v", err) + } + ethConf := ðconfig.Config{ + Genesis: core.DeveloperGenesisBlock(11_500_000, nil), + Miner: miner.Config{ + PendingFeeRecipient: common.HexToAddress(testAddress), + }, + } + if confOverride != nil { + confOverride(ethConf) + } + ethBackend, err := eth.New(stack, ethConf) + if err != nil { + t.Fatalf("failed to register Ethereum protocol: %v", err) + } + // Start the node and assemble the JavaScript console around it + if err = stack.Start(); err != nil { + t.Fatalf("failed to start test stack: %v", err) + } + client := stack.Attach() + t.Cleanup(func() { + client.Close() + }) + + prompter := &hookedPrompter{scheduler: make(chan string)} + printer := new(bytes.Buffer) + + console, err := New(Config{ + DataDir: stack.DataDir(), + DocRoot: "testdata", + Client: client, + Prompter: prompter, + Printer: printer, + Preload: []string{"preload.js"}, + }) + if err != nil { + t.Fatalf("failed to create JavaScript console: %v", err) + } + // Create the final tester and return + return &tester{ + workspace: workspace, + stack: stack, + ethereum: ethBackend, + console: console, + input: prompter, + output: printer, + } +} + +// Close cleans up any temporary data folders and held resources. +func (env *tester) Close(t *testing.T) { + if err := env.console.Stop(false); err != nil { + t.Errorf("failed to stop embedded console: %v", err) + } + if err := env.stack.Close(); err != nil { + t.Errorf("failed to tear down embedded node: %v", err) + } + os.RemoveAll(env.workspace) +} + +// Tests that the node lists the correct welcome message, notably that it contains +// the instance name, block number, data directory and supported console modules. +func TestWelcome(t *testing.T) { + tester := newTester(t, nil) + defer tester.Close(t) + + tester.console.Welcome() + + output := tester.output.String() + if want := "Welcome"; !strings.Contains(output, want) { + t.Fatalf("console output missing welcome message: have\n%s\nwant also %s", output, want) + } + if want := fmt.Sprintf("instance: %s", testInstance); !strings.Contains(output, want) { + t.Fatalf("console output missing instance: have\n%s\nwant also %s", output, want) + } + if want := "at block: 0"; !strings.Contains(output, want) { + t.Fatalf("console output missing sync status: have\n%s\nwant also %s", output, want) + } + if want := fmt.Sprintf("datadir: %s", tester.workspace); !strings.Contains(output, want) { + t.Fatalf("console output missing datadir: have\n%s\nwant also %s", output, want) + } + if want := "modules: "; !strings.Contains(output, want) { + t.Fatalf("console output missing modules: have\n%s\nwant also %s", output, want) + } +} + +// Tests that JavaScript statement evaluation works as intended. +func TestEvaluate(t *testing.T) { + tester := newTester(t, nil) + defer tester.Close(t) + + tester.console.Evaluate("2 + 2") + if output := tester.output.String(); !strings.Contains(output, "4") { + t.Fatalf("statement evaluation failed: have %s, want %s", output, "4") + } +} + +// Tests that the console can be used in interactive mode. +func TestInteractive(t *testing.T) { + // Create a tester and run an interactive console in the background + tester := newTester(t, nil) + defer tester.Close(t) + + go tester.console.Interactive() + + // Wait for a prompt and send a statement back + select { + case <-tester.input.scheduler: + case <-time.After(time.Second): + t.Fatalf("initial prompt timeout") + } + select { + case tester.input.scheduler <- "2+2": + case <-time.After(time.Second): + t.Fatalf("input feedback timeout") + } + // Wait for the second prompt and ensure first statement was evaluated + select { + case <-tester.input.scheduler: + case <-time.After(time.Second): + t.Fatalf("secondary prompt timeout") + } + if output := tester.output.String(); !strings.Contains(output, "4") { + t.Fatalf("statement evaluation failed: have %s, want %s", output, "4") + } +} + +// Tests that preloaded JavaScript files have been executed before user is given +// input. +func TestPreload(t *testing.T) { + tester := newTester(t, nil) + defer tester.Close(t) + + tester.console.Evaluate("preloaded") + if output := tester.output.String(); !strings.Contains(output, "some-preloaded-string") { + t.Fatalf("preloaded variable missing: have %s, want %s", output, "some-preloaded-string") + } +} + +// Tests that the JavaScript objects returned by statement executions are properly +// pretty printed instead of just displaying "[object]". +func TestPrettyPrint(t *testing.T) { + tester := newTester(t, nil) + defer tester.Close(t) + + tester.console.Evaluate("obj = {int: 1, string: 'two', list: [3, 3, 3], obj: {null: null, func: function(){}}}") + + // Define some specially formatted fields + var ( + one = jsre.NumberColor("1") + two = jsre.StringColor("\"two\"") + three = jsre.NumberColor("3") + null = jsre.SpecialColor("null") + fun = jsre.FunctionColor("function()") + ) + // Assemble the actual output we're after and verify + want := `{ + int: ` + one + `, + list: [` + three + `, ` + three + `, ` + three + `], + obj: { + null: ` + null + `, + func: ` + fun + ` + }, + string: ` + two + ` +} +` + if output := tester.output.String(); output != want { + t.Fatalf("pretty print mismatch: have %s, want %s", output, want) + } +} + +// Tests that the JavaScript exceptions are properly formatted and colored. +func TestPrettyError(t *testing.T) { + tester := newTester(t, nil) + defer tester.Close(t) + tester.console.Evaluate("throw 'hello'") + + want := jsre.ErrorColor("hello") + "\n\tat :1:1(1)\n\n" + if output := tester.output.String(); output != want { + t.Fatalf("pretty error mismatch: have %s, want %s", output, want) + } +} + +// Tests that tests if the number of indents for JS input is calculated correct. +func TestIndenting(t *testing.T) { + testCases := []struct { + input string + expectedIndentCount int + }{ + {`var a = 1;`, 0}, + {`"some string"`, 0}, + {`"some string with (parenthesis`, 0}, + {`"some string with newline + ("`, 0}, + {`function v(a,b) {}`, 0}, + {`function f(a,b) { var str = "asd("; };`, 0}, + {`function f(a) {`, 1}, + {`function f(a, function(b) {`, 2}, + {`function f(a, function(b) { + var str = "a)}"; + });`, 0}, + {`function f(a,b) { + var str = "a{b(" + a, ", " + b; + }`, 0}, + {`var str = "\"{"`, 0}, + {`var str = "'("`, 0}, + {`var str = "\\{"`, 0}, + {`var str = "\\\\{"`, 0}, + {`var str = 'a"{`, 0}, + {`var obj = {`, 1}, + {`var obj = { {a:1`, 2}, + {`var obj = { {a:1}`, 1}, + {`var obj = { {a:1}, b:2}`, 0}, + {`var obj = {}`, 0}, + {`var obj = { + a: 1, b: 2 + }`, 0}, + {`var test = }`, -1}, + {`var str = "a\""; var obj = {`, 1}, + } + + for i, tt := range testCases { + counted := countIndents(tt.input) + if counted != tt.expectedIndentCount { + t.Errorf("test %d: invalid indenting: have %d, want %d", i, counted, tt.expectedIndentCount) + } + } +} diff --git a/console/prompt/prompter.go b/console/prompt/prompter.go new file mode 100644 index 0000000..2a20b69 --- /dev/null +++ b/console/prompt/prompter.go @@ -0,0 +1,172 @@ +// 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 prompt + +import ( + "fmt" + "strings" + + "github.com/peterh/liner" +) + +// Stdin holds the stdin line reader (also using stdout for printing prompts). +// Only this reader may be used for input because it keeps an internal buffer. +var Stdin = newTerminalPrompter() + +// UserPrompter defines the methods needed by the console to prompt the user for +// various types of inputs. +type UserPrompter interface { + // PromptInput displays the given prompt to the user and requests some textual + // data to be entered, returning the input of the user. + PromptInput(prompt string) (string, error) + + // PromptPassword displays the given prompt to the user and requests some textual + // data to be entered, but one which must not be echoed out into the terminal. + // The method returns the input provided by the user. + PromptPassword(prompt string) (string, error) + + // PromptConfirm displays the given prompt to the user and requests a boolean + // choice to be made, returning that choice. + PromptConfirm(prompt string) (bool, error) + + // SetHistory sets the input scrollback history that the prompter will allow + // the user to scroll back to. + SetHistory(history []string) + + // AppendHistory appends an entry to the scrollback history. It should be called + // if and only if the prompt to append was a valid command. + AppendHistory(command string) + + // ClearHistory clears the entire history + ClearHistory() + + // SetWordCompleter sets the completion function that the prompter will call to + // fetch completion candidates when the user presses tab. + SetWordCompleter(completer WordCompleter) +} + +// WordCompleter takes the currently edited line with the cursor position and +// returns the completion candidates for the partial word to be completed. If +// the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, +// wo!!!", 9) is passed to the completer which may returns ("Hello, ", {"world", +// "Word"}, "!!!") to have "Hello, world!!!". +type WordCompleter func(line string, pos int) (string, []string, string) + +// terminalPrompter is a UserPrompter backed by the liner package. It supports +// prompting the user for various input, among others for non-echoing password +// input. +type terminalPrompter struct { + *liner.State + warned bool + supported bool + normalMode liner.ModeApplier + rawMode liner.ModeApplier +} + +// newTerminalPrompter creates a liner based user input prompter working off the +// standard input and output streams. +func newTerminalPrompter() *terminalPrompter { + p := new(terminalPrompter) + // Get the original mode before calling NewLiner. + // This is usually regular "cooked" mode where characters echo. + normalMode, _ := liner.TerminalMode() + // Turn on liner. It switches to raw mode. + p.State = liner.NewLiner() + rawMode, err := liner.TerminalMode() + if err != nil || !liner.TerminalSupported() { + p.supported = false + } else { + p.supported = true + p.normalMode = normalMode + p.rawMode = rawMode + // Switch back to normal mode while we're not prompting. + normalMode.ApplyMode() + } + p.SetCtrlCAborts(true) + p.SetTabCompletionStyle(liner.TabPrints) + p.SetMultiLineMode(true) + return p +} + +// PromptInput displays the given prompt to the user and requests some textual +// data to be entered, returning the input of the user. +func (p *terminalPrompter) PromptInput(prompt string) (string, error) { + if p.supported { + p.rawMode.ApplyMode() + defer p.normalMode.ApplyMode() + } else { + // liner tries to be smart about printing the prompt + // and doesn't print anything if input is redirected. + // Un-smart it by printing the prompt always. + fmt.Print(prompt) + prompt = "" + defer fmt.Println() + } + return p.State.Prompt(prompt) +} + +// PromptPassword displays the given prompt to the user and requests some textual +// data to be entered, but one which must not be echoed out into the terminal. +// The method returns the input provided by the user. +func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err error) { + if p.supported { + p.rawMode.ApplyMode() + defer p.normalMode.ApplyMode() + return p.State.PasswordPrompt(prompt) + } + if !p.warned { + fmt.Println("!! Unsupported terminal, password will be echoed.") + p.warned = true + } + // Just as in Prompt, handle printing the prompt here instead of relying on liner. + fmt.Print(prompt) + passwd, err = p.State.Prompt("") + fmt.Println() + return passwd, err +} + +// PromptConfirm displays the given prompt to the user and requests a boolean +// choice to be made, returning that choice. +func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) { + input, err := p.Prompt(prompt + " [y/n] ") + if len(input) > 0 && strings.EqualFold(input[:1], "y") { + return true, nil + } + return false, err +} + +// SetHistory sets the input scrollback history that the prompter will allow +// the user to scroll back to. +func (p *terminalPrompter) SetHistory(history []string) { + p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n"))) +} + +// AppendHistory appends an entry to the scrollback history. +func (p *terminalPrompter) AppendHistory(command string) { + p.State.AppendHistory(command) +} + +// ClearHistory clears the entire history +func (p *terminalPrompter) ClearHistory() { + p.State.ClearHistory() +} + +// SetWordCompleter sets the completion function that the prompter will call to +// fetch completion candidates when the user presses tab. +func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) { + p.State.SetWordCompleter(liner.WordCompleter(completer)) +} diff --git a/console/testdata/preload.js b/console/testdata/preload.js new file mode 100644 index 0000000..5567939 --- /dev/null +++ b/console/testdata/preload.js @@ -0,0 +1 @@ +var preloaded = "some-preloaded-string"; diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..f725d58 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,12 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile ~/.gitignore_global + +/tmp +*/**/*un~ +*un~ +.DS_Store +*/**/.DS_Store + diff --git a/core/asm/asm.go b/core/asm/asm.go new file mode 100644 index 0000000..ff41ff5 --- /dev/null +++ b/core/asm/asm.go @@ -0,0 +1,136 @@ +// 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 asm provides support for dealing with EVM assembly instructions (e.g., disassembling them). +package asm + +import ( + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/core/vm" +) + +// Iterator for disassembled EVM instructions +type instructionIterator struct { + code []byte + pc uint64 + arg []byte + op vm.OpCode + error error + started bool +} + +// NewInstructionIterator creates a new instruction iterator. +func NewInstructionIterator(code []byte) *instructionIterator { + it := new(instructionIterator) + it.code = code + return it +} + +// Next returns true if there is a next instruction and moves on. +func (it *instructionIterator) Next() bool { + if it.error != nil || uint64(len(it.code)) <= it.pc { + // We previously reached an error or the end. + return false + } + + if it.started { + // Since the iteration has been already started we move to the next instruction. + if it.arg != nil { + it.pc += uint64(len(it.arg)) + } + it.pc++ + } else { + // We start the iteration from the first instruction. + it.started = true + } + + if uint64(len(it.code)) <= it.pc { + // We reached the end. + return false + } + + it.op = vm.OpCode(it.code[it.pc]) + if it.op.IsPush() { + a := uint64(it.op) - uint64(vm.PUSH0) + u := it.pc + 1 + a + if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u { + it.error = fmt.Errorf("incomplete push instruction at %v", it.pc) + return false + } + it.arg = it.code[it.pc+1 : u] + } else { + it.arg = nil + } + return true +} + +// Error returns any error that may have been encountered. +func (it *instructionIterator) Error() error { + return it.error +} + +// PC returns the PC of the current instruction. +func (it *instructionIterator) PC() uint64 { + return it.pc +} + +// Op returns the opcode of the current instruction. +func (it *instructionIterator) Op() vm.OpCode { + return it.op +} + +// Arg returns the argument of the current instruction. +func (it *instructionIterator) Arg() []byte { + return it.arg +} + +// PrintDisassembled pretty-print all disassembled EVM instructions to stdout. +func PrintDisassembled(code string) error { + script, err := hex.DecodeString(code) + if err != nil { + return err + } + + it := NewInstructionIterator(script) + for it.Next() { + if it.Arg() != nil && 0 < len(it.Arg()) { + fmt.Printf("%05x: %v %#x\n", it.PC(), it.Op(), it.Arg()) + } else { + fmt.Printf("%05x: %v\n", it.PC(), it.Op()) + } + } + return it.Error() +} + +// Disassemble returns all disassembled EVM instructions in human-readable format. +func Disassemble(script []byte) ([]string, error) { + instrs := make([]string, 0) + + it := NewInstructionIterator(script) + for it.Next() { + if it.Arg() != nil && 0 < len(it.Arg()) { + instrs = append(instrs, fmt.Sprintf("%05x: %v %#x\n", it.PC(), it.Op(), it.Arg())) + } else { + instrs = append(instrs, fmt.Sprintf("%05x: %v\n", it.PC(), it.Op())) + } + } + if err := it.Error(); err != nil { + return nil, err + } + return instrs, nil +} diff --git a/core/asm/asm_test.go b/core/asm/asm_test.go new file mode 100644 index 0000000..cd7520e --- /dev/null +++ b/core/asm/asm_test.go @@ -0,0 +1,58 @@ +// 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 asm + +import ( + "testing" + + "encoding/hex" +) + +// 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 new file mode 100644 index 0000000..02c589b --- /dev/null +++ b/core/asm/compiler.go @@ -0,0 +1,292 @@ +// 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 asm + +import ( + "encoding/hex" + "errors" + "fmt" + "math/big" + "os" + "strings" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/vm" +) + +// Compiler contains information about the parsed source +// and holds the tokens for the program. +type Compiler struct { + tokens []token + out []byte + + labels map[string]int + + pc, pos int + + debug bool +} + +// NewCompiler returns a new allocated compiler. +func NewCompiler(debug bool) *Compiler { + return &Compiler{ + labels: make(map[string]int), + debug: debug, + } +} + +// 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. +func (c *Compiler) Feed(ch <-chan token) { + var prev token + for i := range ch { + switch i.typ { + case number: + num := math.MustParseBig256(i.text).Bytes() + if len(num) == 0 { + num = []byte{0} + } + c.pc += len(num) + case stringValue: + c.pc += len(i.text) - 2 + case element: + c.pc++ + case labelDef: + c.labels[i.text] = c.pc + c.pc++ + case label: + c.pc += 4 + if prev.typ == element && isJump(prev.text) { + c.pc++ + } + } + c.tokens = append(c.tokens, i) + prev = i + } + if c.debug { + fmt.Fprintln(os.Stderr, "found", len(c.labels), "labels") + } +} + +// 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. +func (c *Compiler) Compile() (string, []error) { + var errors []error + // continue looping over the tokens until + // the stack has been exhausted. + for c.pos < len(c.tokens) { + if err := c.compileLine(); err != nil { + errors = append(errors, err) + } + } + + // turn the binary to hex + h := hex.EncodeToString(c.out) + return h, errors +} + +// next returns the next token and increments the +// position. +func (c *Compiler) next() token { + token := c.tokens[c.pos] + c.pos++ + return token +} + +// compileLine compiles a single line instruction e.g. +// "push 1", "jump @label". +func (c *Compiler) compileLine() error { + n := c.next() + if n.typ != lineStart { + return compileErr(n, n.typ.String(), lineStart.String()) + } + + lvalue := c.next() + switch lvalue.typ { + case eof: + return nil + case element: + if err := c.compileElement(lvalue); err != nil { + return err + } + case labelDef: + c.compileLabel() + case lineEnd: + return nil + default: + return compileErr(lvalue, lvalue.text, fmt.Sprintf("%v or %v", labelDef, element)) + } + + if n := c.next(); n.typ != lineEnd { + return compileErr(n, n.text, lineEnd.String()) + } + + return nil +} + +// 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") + } + 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 { + 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 + } +} + +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) + + 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-- + + 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.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)) +} + +// output pushes the value v to the binary stack. +func (c *Compiler) outputBytes(b []byte) { + if c.debug { + fmt.Printf("%d: %x\n", len(c.out), b) + } + c.out = append(c.out, b...) +} + +// isPush returns whether the string op is either any of +// push(N). +func isPush(op string) bool { + return strings.EqualFold(op, "PUSH") +} + +// isJump returns whether the string op is jump(i) +func isJump(op string) bool { + return strings.EqualFold(op, "JUMPI") || strings.EqualFold(op, "JUMP") +} + +// toBinary converts text to a vm.OpCode +func toBinary(text string) vm.OpCode { + return vm.StringToOp(strings.ToUpper(text)) +} + +type compileError struct { + got string + want string + + lineno int +} + +func (err compileError) Error() string { + 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 + 1, + } +} diff --git a/core/asm/compiler_test.go b/core/asm/compiler_test.go new file mode 100644 index 0000000..3d64c96 --- /dev/null +++ b/core/asm/compiler_test.go @@ -0,0 +1,79 @@ +// 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 asm + +import ( + "testing" +) + +func TestCompiler(t *testing.T) { + tests := []struct { + input, output string + }{ + { + input: ` + GAS + label: + PUSH @label +`, + output: "5a5b6300000001", + }, + { + input: ` + PUSH @label + label: +`, + output: "63000000055b", + }, + { + input: ` + PUSH @label + JUMP + label: +`, + output: "6300000006565b", + }, + { + input: ` + JUMP @label + label: +`, + output: "6300000006565b", + }, + { + input: ` + JUMP @label +label: ;; comment + ADD ;; comment +`, + output: "6300000006565b01", + }, + } + for _, test := range tests { + ch := Lex([]byte(test.input), false) + c := NewCompiler(false) + c.Feed(ch) + output, err := c.Compile() + if len(err) != 0 { + t.Errorf("compile error: %v\ninput: %s", err, test.input) + continue + } + if output != test.output { + t.Errorf("incorrect output\ninput: %sgot: %s\nwant: %s\n", test.input, output, test.output) + } + } +} diff --git a/core/asm/lex_test.go b/core/asm/lex_test.go new file mode 100644 index 0000000..1e62d77 --- /dev/null +++ b/core/asm/lex_test.go @@ -0,0 +1,93 @@ +// 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 asm + +import ( + "reflect" + "testing" +) + +func lexAll(src string) []token { + ch := Lex([]byte(src), false) + + var tokens []token + for i := range ch { + tokens = append(tokens, i) + } + return tokens +} + +func TestLexer(t *testing.T) { + tests := []struct { + input string + tokens []token + }{ + { + input: ";; this is a comment", + tokens: []token{{typ: lineStart}, {typ: eof}}, + }, + { + input: "0x12345678", + tokens: []token{{typ: lineStart}, {typ: number, text: "0x12345678"}, {typ: eof}}, + }, + { + input: "0x123ggg", + tokens: []token{{typ: lineStart}, {typ: number, text: "0x123"}, {typ: element, text: "ggg"}, {typ: eof}}, + }, + { + input: "12345678", + tokens: []token{{typ: lineStart}, {typ: number, text: "12345678"}, {typ: eof}}, + }, + { + input: "123abc", + tokens: []token{{typ: lineStart}, {typ: number, text: "123"}, {typ: element, text: "abc"}, {typ: eof}}, + }, + { + input: "0123abc", + tokens: []token{{typ: lineStart}, {typ: number, text: "0123"}, {typ: element, text: "abc"}, {typ: eof}}, + }, + { + input: "00123abc", + tokens: []token{{typ: lineStart}, {typ: number, text: "00123"}, {typ: element, text: "abc"}, {typ: eof}}, + }, + { + input: "@foo", + tokens: []token{{typ: lineStart}, {typ: label, text: "foo"}, {typ: eof}}, + }, + { + 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 { + tokens := lexAll(test.input) + if !reflect.DeepEqual(tokens, test.tokens) { + t.Errorf("input %q\ngot: %+v\nwant: %+v", test.input, tokens, test.tokens) + } + } +} diff --git a/core/asm/lexer.go b/core/asm/lexer.go new file mode 100644 index 0000000..630360b --- /dev/null +++ b/core/asm/lexer.go @@ -0,0 +1,275 @@ +// 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 asm + +import ( + "fmt" + "os" + "strings" + "unicode" + "unicode/utf8" +) + +// stateFn is used through the lifetime of the +// lexer to parse the different values at the +// current state. +type stateFn func(*lexer) stateFn + +// token is emitted when the lexer has discovered +// a new parsable token. These are delivered over +// the tokens channels of the lexer +type token struct { + typ tokenType + lineno int + text string +} + +// tokenType are the different types the lexer +// 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 + lineEnd // emitted when a line ends + invalidStatement // any invalid statement + element // any element during element parsing + label // label is emitted when a label is found + 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 +) + +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. +// Tokens are interpreted by the compiler. +type lexer struct { + input string // input contains the source code of the program + + tokens chan token // tokens is used to deliver tokens to the listener + state stateFn // the current state function + + lineno int // current line number in the source file + start, pos, width int // positions for lexing and returning value + + debug bool // flag for triggering debug output +} + +// Lex lexes the program by name with the given source. It returns a +// channel on which the tokens are delivered. +func Lex(source []byte, debug bool) <-chan token { + ch := make(chan token) + l := &lexer{ + input: string(source), + tokens: ch, + state: lexLine, + debug: debug, + } + go func() { + l.emit(lineStart) + for l.state != nil { + l.state = l.state(l) + } + l.emit(eof) + close(l.tokens) + }() + + return ch +} + +// next returns the next rune in the program's source. +func (l *lexer) next() (rune rune) { + if l.pos >= len(l.input) { + l.width = 0 + return 0 + } + rune, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) + l.pos += l.width + return rune +} + +// backup backsup the last parsed element (multi-character) +func (l *lexer) backup() { + l.pos -= l.width +} + +// peek returns the next rune but does not advance the seeker +func (l *lexer) peek() rune { + r := l.next() + l.backup() + return r +} + +// ignore advances the seeker and ignores the value +func (l *lexer) ignore() { + l.start = l.pos +} + +// accept checks whether the given input matches the next rune +func (l *lexer) accept(valid string) bool { + if strings.ContainsRune(valid, l.next()) { + return true + } + + l.backup() + + return false +} + +// acceptRun will continue to advance the seeker until valid +// can no longer be met. +func (l *lexer) acceptRun(valid string) { + for strings.ContainsRune(valid, l.next()) { + } + l.backup() +} + +// acceptRunUntil is the inverse of acceptRun and will continue +// to advance the seeker until the rune has been found. +func (l *lexer) acceptRunUntil(until rune) bool { + // Continues running until a rune is found + for i := l.next(); !strings.ContainsRune(string(until), i); i = l.next() { + if i == 0 { + return false + } + } + + return true +} + +// blob returns the current value +func (l *lexer) blob() string { + return l.input[l.start:l.pos] +} + +// Emits a new token on to token channel for processing +func (l *lexer) emit(t tokenType) { + token := token{t, l.lineno, l.blob()} + + if l.debug { + fmt.Fprintf(os.Stderr, "%04d: (%-20v) %s\n", token.lineno, token.typ, token.text) + } + + l.tokens <- token + l.start = l.pos +} + +// lexLine is state function for lexing lines +func lexLine(l *lexer) stateFn { + for { + switch r := l.next(); { + case r == '\n': + l.emit(lineEnd) + l.ignore() + l.lineno++ + l.emit(lineStart) + case r == ';' && l.peek() == ';': + return lexComment + case isSpace(r): + l.ignore() + case isLetter(r) || r == '_': + return lexElement + case isNumber(r): + return lexNumber + case r == '@': + l.ignore() + return lexLabel + case r == '"': + return lexInsideString + default: + return nil + } + } +} + +// lexComment parses the current position until the end +// of the line and discards the text. +func lexComment(l *lexer) stateFn { + l.acceptRunUntil('\n') + l.backup() + l.ignore() + + return lexLine +} + +// lexLabel parses the current label, emits and returns +// the lex text state function to advance the parsing +// process. +func lexLabel(l *lexer) stateFn { + l.acceptRun(alpha + "_" + decimalNumbers) + + l.emit(label) + + return lexLine +} + +// lexInsideString lexes the inside of a string until +// the state function finds the closing quote. +// It returns the lex text state function. +func lexInsideString(l *lexer) stateFn { + if l.acceptRunUntil('"') { + l.emit(stringValue) + } + + return lexLine +} + +func lexNumber(l *lexer) stateFn { + acceptance := decimalNumbers + if l.accept("xX") { + acceptance = hexNumbers + } + l.acceptRun(acceptance) + + l.emit(number) + + return lexLine +} + +func lexElement(l *lexer) stateFn { + l.acceptRun(alpha + "_" + decimalNumbers) + + if l.peek() == ':' { + l.emit(labelDef) + + l.accept(":") + l.ignore() + } else { + l.emit(element) + } + return lexLine +} + +func isLetter(t rune) bool { + return unicode.IsLetter(t) +} + +func isSpace(t rune) bool { + return unicode.IsSpace(t) +} + +func isNumber(t rune) bool { + return unicode.IsNumber(t) +} diff --git a/core/asm/tokentype_string.go b/core/asm/tokentype_string.go new file mode 100644 index 0000000..ade76aa --- /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 new file mode 100644 index 0000000..9771386 --- /dev/null +++ b/core/bench_test.go @@ -0,0 +1,331 @@ +// 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 core + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "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/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" +) + +func BenchmarkInsertChain_empty_memdb(b *testing.B) { + benchInsertChain(b, false, nil) +} +func BenchmarkInsertChain_empty_diskdb(b *testing.B) { + benchInsertChain(b, true, nil) +} +func BenchmarkInsertChain_valueTx_memdb(b *testing.B) { + benchInsertChain(b, false, genValueTx(0)) +} +func BenchmarkInsertChain_valueTx_diskdb(b *testing.B) { + benchInsertChain(b, true, genValueTx(0)) +} +func BenchmarkInsertChain_valueTx_100kB_memdb(b *testing.B) { + benchInsertChain(b, false, genValueTx(100*1024)) +} +func BenchmarkInsertChain_valueTx_100kB_diskdb(b *testing.B) { + benchInsertChain(b, true, genValueTx(100*1024)) +} +func BenchmarkInsertChain_uncles_memdb(b *testing.B) { + benchInsertChain(b, false, genUncles) +} +func BenchmarkInsertChain_uncles_diskdb(b *testing.B) { + benchInsertChain(b, true, genUncles) +} +func BenchmarkInsertChain_ring200_memdb(b *testing.B) { + benchInsertChain(b, false, genTxRing(200)) +} +func BenchmarkInsertChain_ring200_diskdb(b *testing.B) { + benchInsertChain(b, true, genTxRing(200)) +} +func BenchmarkInsertChain_ring1000_memdb(b *testing.B) { + benchInsertChain(b, false, genTxRing(1000)) +} +func BenchmarkInsertChain_ring1000_diskdb(b *testing.B) { + benchInsertChain(b, true, genTxRing(1000)) +} + +var ( + // This is the content of the genesis block used by the benchmarks. + benchRootKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + benchRootAddr = crypto.PubkeyToAddress(benchRootKey.PublicKey) + benchRootFunds = math.BigPow(2, 200) +) + +// genValueTx returns a block generator that includes a single +// value-transfer transaction with n bytes of extra data in each +// block. +func genValueTx(nbytes int) func(int, *BlockGen) { + return func(i int, gen *BlockGen) { + toaddr := common.Address{} + data := make([]byte, nbytes) + gas, _ := IntrinsicGas(data, nil, false, false, false, false) + signer := gen.Signer() + gasPrice := big.NewInt(0) + if gen.header.BaseFee != nil { + gasPrice = gen.header.BaseFee + } + tx, _ := types.SignNewTx(benchRootKey, signer, &types.LegacyTx{ + Nonce: gen.TxNonce(benchRootAddr), + To: &toaddr, + Value: big.NewInt(1), + Gas: gas, + Data: data, + GasPrice: gasPrice, + }) + gen.AddTx(tx) + } +} + +var ( + ringKeys = make([]*ecdsa.PrivateKey, 1000) + ringAddrs = make([]common.Address, len(ringKeys)) +) + +func init() { + ringKeys[0] = benchRootKey + ringAddrs[0] = benchRootAddr + for i := 1; i < len(ringKeys); i++ { + ringKeys[i], _ = crypto.GenerateKey() + ringAddrs[i] = crypto.PubkeyToAddress(ringKeys[i].PublicKey) + } +} + +// genTxRing returns a block generator that sends ether in a ring +// among n accounts. This is creates n entries in the state database +// and fills the blocks with many small transactions. +func genTxRing(naccounts int) func(int, *BlockGen) { + from := 0 + availableFunds := new(big.Int).Set(benchRootFunds) + return func(i int, gen *BlockGen) { + block := gen.PrevBlock(i - 1) + gas := block.GasLimit() + gasPrice := big.NewInt(0) + if gen.header.BaseFee != nil { + gasPrice = gen.header.BaseFee + } + signer := gen.Signer() + for { + gas -= params.TxGas + if gas < params.TxGas { + break + } + to := (from + 1) % naccounts + burn := new(big.Int).SetUint64(params.TxGas) + burn.Mul(burn, gen.header.BaseFee) + availableFunds.Sub(availableFunds, burn) + if availableFunds.Cmp(big.NewInt(1)) < 0 { + panic("not enough funds") + } + tx, err := types.SignNewTx(ringKeys[from], signer, + &types.LegacyTx{ + Nonce: gen.TxNonce(ringAddrs[from]), + To: &ringAddrs[to], + Value: availableFunds, + Gas: params.TxGas, + GasPrice: gasPrice, + }) + if err != nil { + panic(err) + } + gen.AddTx(tx) + from = to + } + } +} + +// genUncles generates blocks with two uncle headers. +func genUncles(i int, gen *BlockGen) { + if i >= 7 { + b2 := gen.PrevBlock(i - 6).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(i - 6).Header() + b3.Extra = []byte("bar") + gen.AddUncle(b3) + } +} + +func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { + // Create the database in memory or in a temporary directory. + var db ethdb.Database + var err error + if !disk { + db = rawdb.NewMemoryDatabase() + } else { + dir := b.TempDir() + db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false) + if err != nil { + b.Fatalf("cannot create temporary database: %v", err) + } + defer db.Close() + } + + // Generate a chain of b.N blocks using the supplied block + // generator function. + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, + } + _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), b.N, gen) + + // Time the insertion of the new chain. + // State and blocks are stored in the same DB. + chainman, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer chainman.Stop() + b.ReportAllocs() + b.ResetTimer() + if i, err := chainman.InsertChain(chain); err != nil { + b.Fatalf("insert error (block %d): %v\n", i, err) + } +} + +func BenchmarkChainRead_header_10k(b *testing.B) { + benchReadChain(b, false, 10000) +} +func BenchmarkChainRead_full_10k(b *testing.B) { + benchReadChain(b, true, 10000) +} +func BenchmarkChainRead_header_100k(b *testing.B) { + benchReadChain(b, false, 100000) +} +func BenchmarkChainRead_full_100k(b *testing.B) { + benchReadChain(b, true, 100000) +} +func BenchmarkChainRead_header_500k(b *testing.B) { + benchReadChain(b, false, 500000) +} +func BenchmarkChainRead_full_500k(b *testing.B) { + benchReadChain(b, true, 500000) +} +func BenchmarkChainWrite_header_10k(b *testing.B) { + benchWriteChain(b, false, 10000) +} +func BenchmarkChainWrite_full_10k(b *testing.B) { + benchWriteChain(b, true, 10000) +} +func BenchmarkChainWrite_header_100k(b *testing.B) { + benchWriteChain(b, false, 100000) +} +func BenchmarkChainWrite_full_100k(b *testing.B) { + benchWriteChain(b, true, 100000) +} +func BenchmarkChainWrite_header_500k(b *testing.B) { + benchWriteChain(b, false, 500000) +} +func BenchmarkChainWrite_full_500k(b *testing.B) { + benchWriteChain(b, true, 500000) +} + +// makeChainForBench writes a given number of headers or empty blocks/receipts +// into a database. +func makeChainForBench(db ethdb.Database, genesis *Genesis, full bool, count uint64) { + var hash common.Hash + for n := uint64(0); n < count; n++ { + header := &types.Header{ + Coinbase: common.Address{}, + Number: big.NewInt(int64(n)), + ParentHash: hash, + Difficulty: big.NewInt(1), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + } + if n == 0 { + header = genesis.ToBlock().Header() + } + hash = header.Hash() + + rawdb.WriteHeader(db, header) + rawdb.WriteCanonicalHash(db, hash, n) + rawdb.WriteTd(db, hash, n, big.NewInt(int64(n+1))) + + if n == 0 { + rawdb.WriteChainConfig(db, hash, genesis.Config) + } + rawdb.WriteHeadHeaderHash(db, hash) + + if full || n == 0 { + block := types.NewBlockWithHeader(header) + rawdb.WriteBody(db, hash, n, block.Body()) + rawdb.WriteReceipts(db, hash, n, nil) + rawdb.WriteHeadBlockHash(db, hash) + } + } +} + +func benchWriteChain(b *testing.B, full bool, count uint64) { + genesis := &Genesis{Config: params.AllEthashProtocolChanges} + for i := 0; i < b.N; i++ { + dir := b.TempDir() + db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) + if err != nil { + b.Fatalf("error opening database at %v: %v", dir, err) + } + makeChainForBench(db, genesis, full, count) + db.Close() + } +} + +func benchReadChain(b *testing.B, full bool, count uint64) { + dir := b.TempDir() + + db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) + if err != nil { + b.Fatalf("error opening database at %v: %v", dir, err) + } + genesis := &Genesis{Config: params.AllEthashProtocolChanges} + makeChainForBench(db, genesis, full, count) + db.Close() + cacheConfig := *defaultCacheConfig + cacheConfig.TrieDirtyDisabled = true + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) + if err != nil { + b.Fatalf("error opening database at %v: %v", dir, err) + } + chain, err := NewBlockChain(db, &cacheConfig, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + if err != nil { + b.Fatalf("error creating chain: %v", err) + } + + for n := uint64(0); n < count; n++ { + header := chain.GetHeaderByNumber(n) + if full { + hash := header.Hash() + rawdb.ReadBody(db, hash, n) + rawdb.ReadReceipts(db, hash, n, header.Time, chain.Config()) + } + } + chain.Stop() + db.Close() + } +} diff --git a/core/block_validator.go b/core/block_validator.go new file mode 100644 index 0000000..75f7f8a --- /dev/null +++ b/core/block_validator.go @@ -0,0 +1,199 @@ +// 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 core + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/stateless" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +// BlockValidator is responsible for validating block headers, uncles and +// processed state. +// +// BlockValidator implements Validator. +type BlockValidator struct { + config *params.ChainConfig // Chain configuration options + bc *BlockChain // Canonical block chain +} + +// NewBlockValidator returns a new block validator which is safe for re-use +func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *BlockValidator { + validator := &BlockValidator{ + config: config, + bc: blockchain, + } + return validator +} + +// ValidateBody validates the given block's uncles and verifies the block +// header's transaction and uncle roots. The headers are assumed to be already +// validated at this point. +func (v *BlockValidator) ValidateBody(block *types.Block) error { + // Check whether the block is already imported. + if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) { + return ErrKnownBlock + } + + // Header validity is known at this point. Here we verify that uncles, transactions + // and withdrawals given in the block body match the header. + header := block.Header() + if err := v.bc.engine.VerifyUncles(v.bc, block); err != nil { + return err + } + if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash { + return fmt.Errorf("uncle root hash mismatch (header value %x, calculated %x)", header.UncleHash, hash) + } + if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash { + return fmt.Errorf("transaction root hash mismatch (header value %x, calculated %x)", header.TxHash, hash) + } + + // Withdrawals are present after the Shanghai fork. + if header.WithdrawalsHash != nil { + // Withdrawals list must be present in body after Shanghai. + if block.Withdrawals() == nil { + return errors.New("missing withdrawals in block body") + } + if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash { + return fmt.Errorf("withdrawals root hash mismatch (header value %x, calculated %x)", *header.WithdrawalsHash, hash) + } + } else if block.Withdrawals() != nil { + // Withdrawals are not allowed prior to Shanghai fork + return errors.New("withdrawals present in block body") + } + + // Blob transactions may be present after the Cancun fork. + var blobs int + for i, tx := range block.Transactions() { + // Count the number of blobs to validate against the header's blobGasUsed + blobs += len(tx.BlobHashes()) + + // If the tx is a blob tx, it must NOT have a sidecar attached to be valid in a block. + if tx.BlobTxSidecar() != nil { + return fmt.Errorf("unexpected blob sidecar in transaction at index %d", i) + } + + // The individual checks for blob validity (version-check + not empty) + // happens in StateTransition. + } + + // Check blob gas usage. + if header.BlobGasUsed != nil { + if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated + return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob) + } + } else { + if blobs > 0 { + return errors.New("data blobs present in block body") + } + } + + // Ancestor block must be known. + if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { + if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { + return consensus.ErrUnknownAncestor + } + return consensus.ErrPrunedAncestor + } + return nil +} + +// ValidateState validates the various changes that happen after a state transition, +// such as amount of used gas, the receipt roots and the state root itself. +func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error { + header := block.Header() + if block.GasUsed() != usedGas { + return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) + } + // Validate the received block's bloom with the one derived from the generated receipts. + // For valid blocks this should always validate to true. + rbloom := types.CreateBloom(receipts) + if rbloom != header.Bloom { + return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) + } + // In stateless mode, return early because the receipt and state root are not + // provided through the witness, rather the cross validator needs to return it. + if stateless { + return nil + } + // The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) + receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil)) + if receiptSha != header.ReceiptHash { + return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) + } + // Validate the state root against the received state root and throw + // an error if they don't match. + if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { + return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) + } + return nil +} + +// ValidateWitness cross validates a block execution with stateless remote clients. +// +// Normally we'd distribute the block witness to remote cross validators, wait +// for them to respond and then merge the results. For now, however, it's only +// Geth, so do an internal stateless run. +func (v *BlockValidator) ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error { + // Run the cross client stateless execution + // TODO(karalabe): Self-stateless for now, swap with other clients + crossReceiptRoot, crossStateRoot, err := ExecuteStateless(v.config, witness) + if err != nil { + return fmt.Errorf("stateless execution failed: %v", err) + } + // Stateless cross execution suceeeded, validate the withheld computed fields + if crossReceiptRoot != receiptRoot { + return fmt.Errorf("cross validator receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, receiptRoot) + } + if crossStateRoot != stateRoot { + return fmt.Errorf("cross validator state root mismatch (cross: %x local: %x)", crossStateRoot, stateRoot) + } + return nil +} + +// CalcGasLimit computes the gas limit of the next block after parent. It aims +// to keep the baseline gas close to the provided target, and increase it towards +// the target if the baseline gas is lower. +func CalcGasLimit(parentGasLimit, desiredLimit uint64) uint64 { + delta := parentGasLimit/params.GasLimitBoundDivisor - 1 + limit := parentGasLimit + if desiredLimit < params.MinGasLimit { + desiredLimit = params.MinGasLimit + } + // If we're outside our allowed gas range, we try to hone towards them + if limit < desiredLimit { + limit = parentGasLimit + delta + if limit > desiredLimit { + limit = desiredLimit + } + return limit + } + if limit > desiredLimit { + limit = parentGasLimit - delta + if limit < desiredLimit { + limit = desiredLimit + } + } + return limit +} diff --git a/core/block_validator_test.go b/core/block_validator_test.go new file mode 100644 index 0000000..c573ef9 --- /dev/null +++ b/core/block_validator_test.go @@ -0,0 +1,264 @@ +// 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 core + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// 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} + _, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 8, nil) + ) + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces + chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer chain.Stop() + + for i := 0; i < len(blocks); i++ { + for j, valid := range []bool{true, false} { + var results <-chan error + + if valid { + engine := ethash.NewFaker() + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}) + } else { + engine := ethash.NewFakeFailer(headers[i].Number.Uint64()) + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}) + } + // Wait for the verification result + select { + case result := <-results: + if (result == nil) != valid { + t.Errorf("test %d.%d: validity mismatch: have %v, want %v", i, j, result, valid) + } + case <-time.After(time.Second): + t.Fatalf("test %d.%d: verification timeout", i, j) + } + // Make sure no more data is returned + select { + case result := <-results: + t.Fatalf("test %d.%d: unexpected result returned: %v", i, j, result) + case <-time.After(25 * time.Millisecond): + } + } + chain.InsertChain(blocks[i : i+1]) + } +} + +func TestHeaderVerificationForMergingClique(t *testing.T) { testHeaderVerificationForMerging(t, true) } +func TestHeaderVerificationForMergingEthash(t *testing.T) { testHeaderVerificationForMerging(t, false) } + +// Tests the verification for eth1/2 merging, including pre-merge and post-merge +func testHeaderVerificationForMerging(t *testing.T, isClique bool) { + var ( + gspec *Genesis + preBlocks []*types.Block + postBlocks []*types.Block + engine consensus.Engine + ) + if isClique { + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + config = *params.AllCliqueProtocolChanges + ) + engine = beacon.New(clique.New(params.AllCliqueProtocolChanges.Clique, rawdb.NewMemoryDatabase())) + gspec = &Genesis{ + Config: &config, + ExtraData: make([]byte, 32+common.AddressLength+crypto.SignatureLength), + Alloc: map[common.Address]types.Account{ + addr: {Balance: big.NewInt(1)}, + }, + BaseFee: big.NewInt(params.InitialBaseFee), + Difficulty: new(big.Int), + } + copy(gspec.ExtraData[32:], addr[:]) + + td := 0 + genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 8, nil) + for i, block := range blocks { + header := block.Header() + if i > 0 { + header.ParentHash = blocks[i-1].Hash() + } + header.Extra = make([]byte, 32+crypto.SignatureLength) + header.Difficulty = big.NewInt(2) + + sig, _ := crypto.Sign(engine.SealHash(header).Bytes(), key) + copy(header.Extra[len(header.Extra)-crypto.SignatureLength:], sig) + blocks[i] = block.WithSeal(header) + + // calculate td + td += int(block.Difficulty().Uint64()) + } + preBlocks = blocks + gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(td)) + postBlocks, _ = GenerateChain(gspec.Config, preBlocks[len(preBlocks)-1], engine, genDb, 8, nil) + } else { + config := *params.TestChainConfig + gspec = &Genesis{Config: &config} + engine = beacon.New(ethash.NewFaker()) + td := int(params.GenesisDifficulty.Uint64()) + genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 8, nil) + for _, block := range blocks { + // calculate td + td += int(block.Difficulty().Uint64()) + } + preBlocks = blocks + gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(td)) + t.Logf("Set ttd to %v\n", gspec.Config.TerminalTotalDifficulty) + postBlocks, _ = GenerateChain(gspec.Config, preBlocks[len(preBlocks)-1], engine, genDb, 8, func(i int, gen *BlockGen) { + gen.SetPoS() + }) + } + // Assemble header batch + preHeaders := make([]*types.Header, len(preBlocks)) + for i, block := range preBlocks { + preHeaders[i] = block.Header() + } + postHeaders := make([]*types.Header, len(postBlocks)) + for i, block := range postBlocks { + postHeaders[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, engine, vm.Config{}, nil, nil) + defer chain.Stop() + + // Verify the blocks before the merging + for i := 0; i < len(preBlocks); i++ { + _, results := engine.VerifyHeaders(chain, []*types.Header{preHeaders[i]}) + // Wait for the verification result + select { + case result := <-results: + if result != nil { + t.Errorf("pre-block %d: verification failed %v", i, result) + } + case <-time.After(time.Second): + t.Fatalf("pre-block %d: verification timeout", i) + } + // Make sure no more data is returned + select { + case result := <-results: + t.Fatalf("pre-block %d: unexpected result returned: %v", i, result) + case <-time.After(25 * time.Millisecond): + } + chain.InsertChain(preBlocks[i : i+1]) + } + // Verify the blocks after the merging + for i := 0; i < len(postBlocks); i++ { + _, results := engine.VerifyHeaders(chain, []*types.Header{postHeaders[i]}) + // Wait for the verification result + select { + case result := <-results: + if result != nil { + t.Errorf("post-block %d: verification failed %v", i, result) + } + case <-time.After(time.Second): + t.Fatalf("test %d: verification timeout", i) + } + // Make sure no more data is returned + select { + case result := <-results: + t.Fatalf("post-block %d: unexpected result returned: %v", i, result) + case <-time.After(25 * time.Millisecond): + } + chain.InsertBlockWithoutSetHead(postBlocks[i]) + } + + // Verify the blocks with pre-merge blocks and post-merge blocks + var headers []*types.Header + for _, block := range preBlocks { + headers = append(headers, block.Header()) + } + for _, block := range postBlocks { + headers = append(headers, block.Header()) + } + _, results := engine.VerifyHeaders(chain, headers) + for i := 0; i < len(headers); i++ { + select { + case result := <-results: + if result != nil { + t.Errorf("test %d: verification failed %v", i, result) + } + case <-time.After(time.Second): + t.Fatalf("test %d: verification timeout", i) + } + } + // Make sure no more data is returned + select { + case result := <-results: + t.Fatalf("unexpected result returned: %v", result) + case <-time.After(25 * time.Millisecond): + } +} + +func TestCalcGasLimit(t *testing.T) { + for i, tc := range []struct { + pGasLimit uint64 + max uint64 + min uint64 + }{ + {20000000, 20019530, 19980470}, + {40000000, 40039061, 39960939}, + } { + // Increase + if have, want := CalcGasLimit(tc.pGasLimit, 2*tc.pGasLimit), tc.max; have != want { + t.Errorf("test %d: have %d want <%d", i, have, want) + } + // Decrease + if have, want := CalcGasLimit(tc.pGasLimit, 0), tc.min; have != want { + t.Errorf("test %d: have %d want >%d", i, have, want) + } + // Small decrease + if have, want := CalcGasLimit(tc.pGasLimit, tc.pGasLimit-1), tc.pGasLimit-1; have != want { + t.Errorf("test %d: have %d want %d", i, have, want) + } + // Small increase + if have, want := CalcGasLimit(tc.pGasLimit, tc.pGasLimit+1), tc.pGasLimit+1; have != want { + t.Errorf("test %d: have %d want %d", i, have, want) + } + // No change + if have, want := CalcGasLimit(tc.pGasLimit, tc.pGasLimit), tc.pGasLimit; have != want { + t.Errorf("test %d: have %d want %d", i, have, want) + } + } +} diff --git a/core/blockchain.go b/core/blockchain.go new file mode 100644 index 0000000..05ebfd1 --- /dev/null +++ b/core/blockchain.go @@ -0,0 +1,2552 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package core implements the Ethereum consensus protocol. +package core + +import ( + "errors" + "fmt" + "io" + "math/big" + "runtime" + "strings" + "sync" + "sync/atomic" + "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/common/prque" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/stateless" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/syncx" + "github.com/ethereum/go-ethereum/internal/version" + "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/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" +) + +var ( + headBlockGauge = metrics.NewRegisteredGauge("chain/head/block", nil) + headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil) + headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil) + headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil) + headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil) + + chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil) + + accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil) + accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil) + accountUpdateTimer = metrics.NewRegisteredResettingTimer("chain/account/updates", nil) + accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil) + + storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil) + storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil) + storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil) + + snapshotAccountReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/account/reads", nil) + snapshotStorageReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/storage/reads", nil) + snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil) + + triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil) + + blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil) + blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil) + blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil) + blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil) + + blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) + blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) + blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil) + + blockPrefetchExecuteTimer = metrics.NewRegisteredTimer("chain/prefetch/executes", nil) + blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil) + + errInsertionInterrupted = errors.New("insertion is interrupted") + errChainStopped = errors.New("blockchain is stopped") + errInvalidOldChain = errors.New("invalid old chain") + errInvalidNewChain = errors.New("invalid new chain") +) + +const ( + bodyCacheLimit = 256 + blockCacheLimit = 256 + receiptsCacheLimit = 32 + txLookupCacheLimit = 1024 + + // BlockChainVersion ensures that an incompatible database forces a resync from scratch. + // + // Changelog: + // + // - Version 4 + // The following incompatible database changes were added: + // * the `BlockNumber`, `TxHash`, `TxIndex`, `BlockHash` and `Index` fields of log are deleted + // * the `Bloom` field of receipt is deleted + // * the `BlockIndex` and `TxIndex` fields of txlookup are deleted + // - Version 5 + // The following incompatible database changes were added: + // * the `TxHash`, `GasCost`, and `ContractAddress` fields are no longer stored for a receipt + // * the `TxHash`, `GasCost`, and `ContractAddress` fields are computed by looking up the + // receipts' corresponding block + // - Version 6 + // The following incompatible database changes were added: + // * Transaction lookup information stores the corresponding block number instead of block hash + // - Version 7 + // The following incompatible database changes were added: + // * Use freezer as the ancient database to maintain all ancient data + // - Version 8 + // The following incompatible database changes were added: + // * New scheme for contract code in order to separate the codes and trie nodes + BlockChainVersion uint64 = 8 +) + +// CacheConfig contains the configuration values for the trie database +// 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 + TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk + TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node) + TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk + SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory + Preimages bool // Whether to store preimage of trie key to the disk + StateHistory uint64 // Number of blocks from head whose state histories are reserved. + StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top + + 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(isVerkle bool) *triedb.Config { + config := &triedb.Config{ + Preimages: c.Preimages, + IsVerkle: isVerkle, + } + 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{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 256, + SnapshotWait: true, + StateScheme: rawdb.HashScheme, +} + +// DefaultCacheConfigWithScheme returns a deep copied default cache config with +// a provided trie node scheme. +func DefaultCacheConfigWithScheme(scheme string) *CacheConfig { + config := *defaultCacheConfig + config.StateScheme = scheme + return &config +} + +// txLookup is wrapper over transaction lookup along with the corresponding +// transaction object. +type txLookup struct { + lookup *rawdb.LegacyTxLookupEntry + transaction *types.Transaction +} + +// BlockChain represents the canonical chain given a database with a genesis +// block. The Blockchain manages chain imports, reverts, chain reorganisations. +// +// Importing blocks in to the block chain happens according to the set of rules +// defined by the two stage Validator. Processing of blocks is done using the +// Processor which processes the included transaction. The validation of the state +// is done in the second part of the Validator. Failing results in aborting of +// the import. +// +// The BlockChain also helps in returning blocks from **any** chain included +// in the database as well as blocks that represents the canonical chain. It's +// important to note that GetBlock can return any block and does not need to be +// included in the canonical one where as GetBlockByNumber always represents the +// canonical chain. +type BlockChain struct { + chainConfig *params.ChainConfig // Chain & network configuration + cacheConfig *CacheConfig // Cache configuration for pruning + + db ethdb.Database // Low level persistent database to store final content in + snaps *snapshot.Tree // Snapshot tree for fast trie leaf access + triegc *prque.Prque[int64, common.Hash] // Priority queue mapping block numbers to tries to gc + gcproc time.Duration // Accumulates canonical block processing for trie dumping + lastWrite uint64 // Last block when the state was flushed + flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state + triedb *triedb.Database // The database handler for maintaining trie nodes. + stateCache state.Database // State database to reuse between imports (contains state cache) + txIndexer *txIndexer // Transaction indexer, might be nil if not enabled + + hc *HeaderChain + rmLogsFeed event.Feed + chainFeed event.Feed + chainSideFeed event.Feed + chainHeadFeed event.Feed + logsFeed event.Feed + blockProcFeed event.Feed + scope event.SubscriptionScope + genesisBlock *types.Block + + // This mutex synchronizes chain write operations. + // Readers don't need to take it, they can just read the database. + chainmu *syncx.ClosableMutex + + currentBlock atomic.Pointer[types.Header] // Current head of the chain + currentSnapBlock atomic.Pointer[types.Header] // Current head of snap-sync + currentFinalBlock atomic.Pointer[types.Header] // Latest (consensus) finalized block + currentSafeBlock atomic.Pointer[types.Header] // Latest (consensus) safe block + + bodyCache *lru.Cache[common.Hash, *types.Body] + bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] + receiptsCache *lru.Cache[common.Hash, []*types.Receipt] + blockCache *lru.Cache[common.Hash, *types.Block] + + txLookupLock sync.RWMutex + txLookupCache *lru.Cache[common.Hash, txLookup] + + wg sync.WaitGroup + quit chan struct{} // shutdown signal, closed in Stop. + stopping atomic.Bool // false if chain is running, true when stopped + procInterrupt atomic.Bool // interrupt signaler for block processing + + engine consensus.Engine + validator Validator // Block and state validator interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + forker *ForkChoice + vmConfig vm.Config + logger *tracing.Hooks +} + +// NewBlockChain returns a fully initialised block chain using information +// available in the database. It initialises the default Ethereum Validator +// and Processor. +func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, overrides *ChainOverrides, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) { + if cacheConfig == nil { + cacheConfig = defaultCacheConfig + } + // Open trie database with provided config + triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(genesis != nil && genesis.IsVerkle())) + + // 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. + chainConfig, genesisHash, genesisErr := SetupGenesisBlockWithOverride(db, triedb, genesis, overrides) + if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { + return nil, genesisErr + } + 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("") + + bc := &BlockChain{ + chainConfig: chainConfig, + cacheConfig: cacheConfig, + db: db, + triedb: triedb, + triegc: prque.New[int64, common.Hash](nil), + quit: make(chan struct{}), + chainmu: syncx.NewClosableMutex(), + bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), + bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit), + receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), + blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit), + txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit), + engine: engine, + vmConfig: vmConfig, + logger: vmConfig.Tracer, + } + var err error + bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped) + if err != nil { + return nil, err + } + bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) + bc.forker = NewForkChoice(bc, shouldPreserve) + bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) + bc.validator = NewBlockValidator(chainConfig, bc) + bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc) + bc.processor = NewStateProcessor(chainConfig, bc.hc) + + bc.genesisBlock = bc.GetBlockByNumber(0) + if bc.genesisBlock == nil { + return nil, ErrNoGenesis + } + + bc.currentBlock.Store(nil) + bc.currentSnapBlock.Store(nil) + 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. + if bc.empty() { + rawdb.InitDatabaseFromFreezer(bc.db) + } + // Load blockchain states from disk + if err := bc.loadLastState(); err != nil { + return nil, err + } + // 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) { + 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 { + // 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 + } + } + } + } + // Ensure that a previous crash in SetHead doesn't leave extra ancients + if frozen, err := bc.db.Ancients(); err == nil && frozen > 0 { + var ( + needRewind bool + low uint64 + ) + // The head full block may be rolled back to a very low height due to + // blockchain repair. If the head full block is even lower than the ancient + // chain, truncate the ancient store. + fullBlock := bc.CurrentBlock() + if fullBlock != nil && fullBlock.Hash() != bc.genesisBlock.Hash() && fullBlock.Number.Uint64() < frozen-1 { + needRewind = true + low = fullBlock.Number.Uint64() + } + // In snap sync, it may happen that ancient data has been written to the + // ancient store, but the LastFastBlock has not been updated, truncate the + // extra data here. + snapBlock := bc.CurrentSnapBlock() + if snapBlock != nil && snapBlock.Number.Uint64() < frozen-1 { + needRewind = true + if snapBlock.Number.Uint64() < low || low == 0 { + low = snapBlock.Number.Uint64() + } + } + if needRewind { + log.Error("Truncating ancient chain", "from", bc.CurrentHeader().Number.Uint64(), "to", low) + if err := bc.SetHead(low); err != nil { + return nil, err + } + } + } + // The first thing the node will do is reconstruct the verification data for + // the head block (ethash cache or clique voting snapshot). Might as well do + // it in advance. + bc.engine.VerifyHeader(bc, bc.CurrentHeader()) + + if bc.logger != nil && bc.logger.OnBlockchainInit != nil { + bc.logger.OnBlockchainInit(chainConfig) + } + if bc.logger != nil && bc.logger.OnGenesisBlock != nil { + if block := bc.CurrentBlock(); block.Number.Uint64() == 0 { + alloc, err := getGenesisState(bc.db, block.Hash()) + if err != nil { + return nil, fmt.Errorf("failed to get genesis state: %w", err) + } + if alloc == nil { + return nil, errors.New("live blockchain tracer requires genesis alloc to be set") + } + bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) + } + } + + // Load any existing snapshot, regenerating it if loading failed + if bc.cacheConfig.SnapshotLimit > 0 { + // If the chain was rewound past the snapshot persistent layer (causing + // a recovery block number to be persisted to disk), check if we're still + // in recovery mode and in that case, don't invalidate the snapshot on a + // head mismatch. + var recover bool + + head := bc.CurrentBlock() + if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer >= head.Number.Uint64() { + log.Warn("Enabling snapshot recovery", "chainhead", head.Number, "diskbase", *layer) + recover = true + } + snapconfig := snapshot.Config{ + CacheSize: bc.cacheConfig.SnapshotLimit, + Recovery: recover, + NoBuild: bc.cacheConfig.SnapshotNoBuild, + AsyncBuild: !bc.cacheConfig.SnapshotWait, + } + bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) + } + // 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 { + bc.SetHeadWithTimestamp(compat.RewindToTime) + } else { + bc.SetHead(compat.RewindToBlock) + } + rawdb.WriteChainConfig(db, genesisHash, chainConfig) + } + + // Start tx indexer if it's enabled. + if txLookupLimit != nil { + bc.txIndexer = newTxIndexer(*txLookupLimit, bc) + } + return bc, nil +} + +// empty returns an indicator whether the blockchain is empty. +// Note, it's a special case that we connect a non-empty ancient +// database with an empty node, so that we can plugin the ancient +// into node seamlessly. +func (bc *BlockChain) empty() bool { + genesis := bc.genesisBlock.Hash() + for _, hash := range []common.Hash{rawdb.ReadHeadBlockHash(bc.db), rawdb.ReadHeadHeaderHash(bc.db), rawdb.ReadHeadFastBlockHash(bc.db)} { + if hash != genesis { + return false + } + } + return true +} + +// loadLastState loads the last known chain state from the database. This method +// assumes that the chain manager mutex is held. +func (bc *BlockChain) loadLastState() error { + // Restore the last known head block + head := rawdb.ReadHeadBlockHash(bc.db) + if head == (common.Hash{}) { + // Corrupt or empty database, init from scratch + log.Warn("Empty database, resetting chain") + return bc.Reset() + } + // Make sure the entire head block is available + headBlock := bc.GetBlockByHash(head) + if headBlock == nil { + // Corrupt or empty database, init from scratch + log.Warn("Head block missing, resetting chain", "hash", head) + return bc.Reset() + } + // Everything seems to be fine, set as the head block + bc.currentBlock.Store(headBlock.Header()) + headBlockGauge.Update(int64(headBlock.NumberU64())) + + // Restore the last known head header + headHeader := headBlock.Header() + if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) { + if header := bc.GetHeaderByHash(head); header != nil { + headHeader = header + } + } + bc.hc.SetCurrentHeader(headHeader) + + // Restore the last known head snap block + bc.currentSnapBlock.Store(headBlock.Header()) + headFastBlockGauge.Update(int64(headBlock.NumberU64())) + + if head := rawdb.ReadHeadFastBlockHash(bc.db); head != (common.Hash{}) { + if block := bc.GetBlockByHash(head); block != nil { + bc.currentSnapBlock.Store(block.Header()) + headFastBlockGauge.Update(int64(block.NumberU64())) + } + } + + // Restore the last known finalized block and safe block + // Note: the safe block is not stored on disk and it is set to the last + // known finalized block on startup + if head := rawdb.ReadFinalizedBlockHash(bc.db); head != (common.Hash{}) { + if block := bc.GetBlockByHash(head); block != nil { + bc.currentFinalBlock.Store(block.Header()) + headFinalizedBlockGauge.Update(int64(block.NumberU64())) + bc.currentSafeBlock.Store(block.Header()) + headSafeBlockGauge.Update(int64(block.NumberU64())) + } + } + // Issue a status log for the user + var ( + currentSnapBlock = bc.CurrentSnapBlock() + currentFinalBlock = bc.CurrentFinalBlock() + + headerTd = bc.GetTd(headHeader.Hash(), headHeader.Number.Uint64()) + blockTd = bc.GetTd(headBlock.Hash(), headBlock.NumberU64()) + ) + if headHeader.Hash() != headBlock.Hash() { + log.Info("Loaded most recent local header", "number", headHeader.Number, "hash", headHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(headHeader.Time), 0))) + } + log.Info("Loaded most recent local block", "number", headBlock.Number(), "hash", headBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(headBlock.Time()), 0))) + if headBlock.Hash() != currentSnapBlock.Hash() { + snapTd := bc.GetTd(currentSnapBlock.Hash(), currentSnapBlock.Number.Uint64()) + log.Info("Loaded most recent local snap block", "number", currentSnapBlock.Number, "hash", currentSnapBlock.Hash(), "td", snapTd, "age", common.PrettyAge(time.Unix(int64(currentSnapBlock.Time), 0))) + } + if currentFinalBlock != nil { + finalTd := bc.GetTd(currentFinalBlock.Hash(), currentFinalBlock.Number.Uint64()) + log.Info("Loaded most recent local finalized block", "number", currentFinalBlock.Number, "hash", currentFinalBlock.Hash(), "td", finalTd, "age", common.PrettyAge(time.Unix(int64(currentFinalBlock.Time), 0))) + } + if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil { + log.Info("Loaded last snap-sync pivot marker", "number", *pivot) + } + return nil +} + +// SetHead rewinds the local chain to a new head. Depending on whether the node +// was snap synced or full synced and in which state, the method will try to +// delete minimal data from disk whilst retaining chain consistency. +func (bc *BlockChain) SetHead(head uint64) error { + if _, err := bc.setHeadBeyondRoot(head, 0, common.Hash{}, false); err != nil { + return err + } + // Send chain head event to update the transaction pool + header := bc.CurrentBlock() + block := bc.GetBlock(header.Hash(), header.Number.Uint64()) + if block == nil { + // 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()) + return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4]) + } + bc.chainHeadFeed.Send(ChainHeadEvent{Block: block}) + return nil +} + +// SetHeadWithTimestamp rewinds the local chain to a new head that has at max +// the given timestamp. Depending on whether the node was snap synced or full +// synced and in which state, the method will try to delete minimal data from +// disk whilst retaining chain consistency. +func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error { + if _, err := bc.setHeadBeyondRoot(0, timestamp, common.Hash{}, false); err != nil { + return err + } + // Send chain head event to update the transaction pool + header := bc.CurrentBlock() + block := bc.GetBlock(header.Hash(), header.Number.Uint64()) + if block == nil { + // 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()) + return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4]) + } + bc.chainHeadFeed.Send(ChainHeadEvent{Block: block}) + return nil +} + +// SetFinalized sets the finalized block. +func (bc *BlockChain) SetFinalized(header *types.Header) { + bc.currentFinalBlock.Store(header) + if header != nil { + rawdb.WriteFinalizedBlockHash(bc.db, header.Hash()) + headFinalizedBlockGauge.Update(int64(header.Number.Uint64())) + } else { + rawdb.WriteFinalizedBlockHash(bc.db, common.Hash{}) + headFinalizedBlockGauge.Update(0) + } +} + +// SetSafe sets the safe block. +func (bc *BlockChain) SetSafe(header *types.Header) { + bc.currentSafeBlock.Store(header) + if header != nil { + headSafeBlockGauge.Update(int64(header.Number.Uint64())) + } else { + headSafeBlockGauge.Update(0) + } +} + +// rewindHashHead implements the logic of rewindHead in the context of hash scheme. +func (bc *BlockChain) rewindHashHead(head *types.Header, root common.Hash) (*types.Header, uint64) { + var ( + limit uint64 // The oldest block that will be searched for this rewinding + beyondRoot = root == common.Hash{} // Flag whether we're beyond the requested root (no root, always true) + pivot = rawdb.ReadLastPivotNumber(bc.db) // Associated block number of pivot point state + rootNumber uint64 // Associated block number of requested root + + start = time.Now() // Timestamp the rewinding is restarted + logged = time.Now() // Timestamp last progress log was printed + ) + // The oldest block to be searched is determined by the pivot block or a constant + // searching threshold. The rationale behind this is as follows: + // + // - Snap sync is selected if the pivot block is available. The earliest available + // state is the pivot block itself, so there is no sense in going further back. + // + // - Full sync is selected if the pivot block does not exist. The hash database + // periodically flushes the state to disk, and the used searching threshold is + // considered sufficient to find a persistent state, even for the testnet. It + // might be not enough for a chain that is nearly empty. In the worst case, + // the entire chain is reset to genesis, and snap sync is re-enabled on top, + // which is still acceptable. + if pivot != nil { + limit = *pivot + } else if head.Number.Uint64() > params.FullImmutabilityThreshold { + limit = head.Number.Uint64() - params.FullImmutabilityThreshold + } + for { + logger := log.Trace + if time.Since(logged) > time.Second*8 { + logged = time.Now() + logger = log.Info + } + logger("Block state missing, rewinding further", "number", head.Number, "hash", head.Hash(), "elapsed", common.PrettyDuration(time.Since(start))) + + // If a root threshold was requested but not yet crossed, check + if !beyondRoot && head.Root == root { + beyondRoot, rootNumber = true, head.Number.Uint64() + } + // If search limit is reached, return the genesis block as the + // new chain head. + if head.Number.Uint64() < limit { + log.Info("Rewinding limit reached, resetting to genesis", "number", head.Number, "hash", head.Hash(), "limit", limit) + return bc.genesisBlock.Header(), rootNumber + } + // If the associated state is not reachable, continue searching + // backwards until an available state is found. + if !bc.HasState(head.Root) { + // If the chain is gapped in the middle, return the genesis + // block as the new chain head. + parent := bc.GetHeader(head.ParentHash, head.Number.Uint64()-1) + if parent == nil { + log.Error("Missing block in the middle, resetting to genesis", "number", head.Number.Uint64()-1, "hash", head.ParentHash) + return bc.genesisBlock.Header(), rootNumber + } + head = parent + + // If the genesis block is reached, stop searching. + if head.Number.Uint64() == 0 { + log.Info("Genesis block reached", "number", head.Number, "hash", head.Hash()) + return head, rootNumber + } + continue // keep rewinding + } + // Once the available state is found, ensure that the requested root + // has already been crossed. If not, continue rewinding. + if beyondRoot || head.Number.Uint64() == 0 { + log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash()) + return head, rootNumber + } + log.Debug("Skipping block with threshold state", "number", head.Number, "hash", head.Hash(), "root", head.Root) + head = bc.GetHeader(head.ParentHash, head.Number.Uint64()-1) // Keep rewinding + } +} + +// rewindPathHead implements the logic of rewindHead in the context of path scheme. +func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*types.Header, uint64) { + var ( + pivot = rawdb.ReadLastPivotNumber(bc.db) // Associated block number of pivot block + rootNumber uint64 // Associated block number of requested root + + // BeyondRoot represents whether the requested root is already + // crossed. The flag value is set to true if the root is empty. + beyondRoot = root == common.Hash{} + + // noState represents if the target state requested for search + // is unavailable and impossible to be recovered. + noState = !bc.HasState(root) && !bc.stateRecoverable(root) + + start = time.Now() // Timestamp the rewinding is restarted + logged = time.Now() // Timestamp last progress log was printed + ) + // Rewind the head block tag until an available state is found. + for { + logger := log.Trace + if time.Since(logged) > time.Second*8 { + logged = time.Now() + logger = log.Info + } + logger("Block state missing, rewinding further", "number", head.Number, "hash", head.Hash(), "elapsed", common.PrettyDuration(time.Since(start))) + + // If a root threshold was requested but not yet crossed, check + if !beyondRoot && head.Root == root { + beyondRoot, rootNumber = true, head.Number.Uint64() + } + // If the root threshold hasn't been crossed but the available + // state is reached, quickly determine if the target state is + // possible to be reached or not. + if !beyondRoot && noState && bc.HasState(head.Root) { + beyondRoot = true + log.Info("Disable the search for unattainable state", "root", root) + } + // Check if the associated state is available or recoverable if + // the requested root has already been crossed. + if beyondRoot && (bc.HasState(head.Root) || bc.stateRecoverable(head.Root)) { + break + } + // If pivot block is reached, return the genesis block as the + // new chain head. Theoretically there must be a persistent + // state before or at the pivot block, prevent endless rewinding + // towards the genesis just in case. + if pivot != nil && *pivot >= head.Number.Uint64() { + log.Info("Pivot block reached, resetting to genesis", "number", head.Number, "hash", head.Hash()) + return bc.genesisBlock.Header(), rootNumber + } + // If the chain is gapped in the middle, return the genesis + // block as the new chain head + parent := bc.GetHeader(head.ParentHash, head.Number.Uint64()-1) // Keep rewinding + if parent == nil { + log.Error("Missing block in the middle, resetting to genesis", "number", head.Number.Uint64()-1, "hash", head.ParentHash) + return bc.genesisBlock.Header(), rootNumber + } + head = parent + + // If the genesis block is reached, stop searching. + if head.Number.Uint64() == 0 { + log.Info("Genesis block reached", "number", head.Number, "hash", head.Hash()) + return head, rootNumber + } + } + // Recover if the target state if it's not available yet. + if !bc.HasState(head.Root) { + if err := bc.triedb.Recover(head.Root); err != nil { + log.Crit("Failed to rollback state", "err", err) + } + } + log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash()) + return head, rootNumber +} + +// rewindHead searches the available states in the database and returns the associated +// block as the new head block. +// +// If the given root is not empty, then the rewind should attempt to pass the specified +// state root and return the associated block number as well. If the root, typically +// representing the state corresponding to snapshot disk layer, is deemed impassable, +// then block number zero is returned, indicating that snapshot recovery is disabled +// and the whole snapshot should be auto-generated in case of head mismatch. +func (bc *BlockChain) rewindHead(head *types.Header, root common.Hash) (*types.Header, uint64) { + if bc.triedb.Scheme() == rawdb.PathScheme { + return bc.rewindPathHead(head, root) + } + return bc.rewindHashHead(head, root) +} + +// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition +// that the rewind must pass the specified state root. This method is meant to be +// used when rewinding with snapshots enabled to ensure that we go back further than +// persistent disk layer. Depending on whether the node was snap synced or full, and +// in which state, the method will try to delete minimal data from disk whilst +// retaining chain consistency. +// +// The method also works in timestamp mode if `head == 0` but `time != 0`. In that +// case blocks are rolled back until the new head becomes older or equal to the +// requested time. If both `head` and `time` is 0, the chain is rewound to genesis. +// +// The method returns the block number where the requested root cap was found. +func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Hash, repair bool) (uint64, error) { + if !bc.chainmu.TryLock() { + return 0, errChainStopped + } + defer bc.chainmu.Unlock() + + var ( + // Track the block number of the requested root hash + rootNumber uint64 // (no root == always 0) + + // Retrieve the last pivot block to short circuit rollbacks beyond it + // and the current freezer limit to start nuking it's underflown. + pivot = rawdb.ReadLastPivotNumber(bc.db) + ) + updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) { + // Rewind the blockchain, ensuring we don't end up with a stateless head + // block. Note, depth equality is permitted to allow using SetHead as a + // chain reparation mechanism without deleting any data! + if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() <= currentBlock.Number.Uint64() { + var newHeadBlock *types.Header + newHeadBlock, rootNumber = bc.rewindHead(header, root) + rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash()) + + // 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 SetHead is from high + // to low, so it's safe to update in-memory markers directly. + bc.currentBlock.Store(newHeadBlock) + headBlockGauge.Update(int64(newHeadBlock.Number.Uint64())) + + // 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) { + if newHeadBlock.Number.Uint64() != 0 { + log.Crit("Chain is stateless at a non-genesis block") + } + 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() { + newHeadSnapBlock := bc.GetBlock(header.Hash(), header.Number.Uint64()) + // If either blocks reached nil, reset to the genesis state + if newHeadSnapBlock == nil { + newHeadSnapBlock = bc.genesisBlock + } + rawdb.WriteHeadFastBlockHash(db, newHeadSnapBlock.Hash()) + + // 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 SetHead is from high + // to low, so it's safe the update in-memory markers directly. + bc.currentSnapBlock.Store(newHeadSnapBlock.Header()) + headFastBlockGauge.Update(int64(newHeadSnapBlock.NumberU64())) + } + var ( + headHeader = bc.CurrentBlock() + headNumber = headHeader.Number.Uint64() + ) + // If setHead underflown the freezer threshold and the block processing + // intent afterwards is full block importing, delete the chain segment + // between the stateful-block and the sethead target. + var wipe bool + frozen, _ := bc.db.Ancients() + if headNumber+1 < frozen { + wipe = pivot == nil || headNumber >= *pivot + } + return headHeader, wipe // Only force wipe if full synced + } + // Rewind the header chain, deleting all block bodies until then + delFn := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) { + // Ignore the error here since light client won't hit this path + frozen, _ := bc.db.Ancients() + if num+1 <= frozen { + // Truncate all relative data(header, total difficulty, body, receipt + // and canonical hash) from ancient store. + if _, err := bc.db.TruncateHead(num); err != nil { + log.Crit("Failed to truncate ancient data", "number", num, "err", err) + } + // Remove the hash <-> number mapping from the active store. + rawdb.DeleteHeaderNumber(db, hash) + } else { + // Remove relative body and receipts from the active store. + // The header, total difficulty and canonical hash will be + // removed in the hc.SetHead function. + rawdb.DeleteBody(db, hash, num) + rawdb.DeleteReceipts(db, hash, num) + } + // Todo(rjl493456442) txlookup, bloombits, etc + } + // If SetHead was only called as a chain reparation method, try to skip + // touching the header chain altogether, unless the freezer is broken + if repair { + if target, force := updateFn(bc.db, bc.CurrentBlock()); force { + bc.hc.SetHead(target.Number.Uint64(), nil, delFn) + } + } else { + // Rewind the chain to the requested head and keep going backwards until a + // block with a state is found or snap sync pivot is passed + if time > 0 { + log.Warn("Rewinding blockchain to timestamp", "target", time) + bc.hc.SetHeadWithTimestamp(time, updateFn, delFn) + } else { + log.Warn("Rewinding blockchain to block", "target", head) + bc.hc.SetHead(head, updateFn, delFn) + } + } + // Clear out any stale content from the caches + bc.bodyCache.Purge() + bc.bodyRLPCache.Purge() + bc.receiptsCache.Purge() + bc.blockCache.Purge() + bc.txLookupCache.Purge() + + // Clear safe block, finalized block if needed + if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() { + log.Warn("SetHead invalidated safe block") + bc.SetSafe(nil) + } + if finalized := bc.CurrentFinalBlock(); finalized != nil && head < finalized.Number.Uint64() { + log.Error("SetHead invalidated finalized block") + bc.SetFinalized(nil) + } + return rootNumber, bc.loadLastState() +} + +// SnapSyncCommitHead sets the current head block to the one defined by the hash +// irrelevant what the chain contents were prior. +func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error { + // Make sure that both the block as well at its state trie exists + block := bc.GetBlockByHash(hash) + 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]) + } + // If all checks out, manually set the head block. + if !bc.chainmu.TryLock() { + return errChainStopped + } + bc.currentBlock.Store(block.Header()) + headBlockGauge.Update(int64(block.NumberU64())) + bc.chainmu.Unlock() + + // Destroy any existing state snapshot and regenerate it in the background, + // also resuming the normal maintenance of any previously paused snapshot. + if bc.snaps != nil { + bc.snaps.Rebuild(root) + } + log.Info("Committed new head block", "number", block.Number(), "hash", hash) + return nil +} + +// Reset purges the entire blockchain, restoring it to its genesis state. +func (bc *BlockChain) Reset() error { + return bc.ResetWithGenesisBlock(bc.genesisBlock) +} + +// ResetWithGenesisBlock purges the entire blockchain, restoring it to the +// specified genesis state. +func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { + // Dump the entire block chain and purge the caches + if err := bc.SetHead(0); err != nil { + return err + } + if !bc.chainmu.TryLock() { + return errChainStopped + } + defer bc.chainmu.Unlock() + + // Prepare the genesis block and reinitialise the chain + batch := bc.db.NewBatch() + rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) + rawdb.WriteBlock(batch, genesis) + if err := batch.Write(); err != nil { + log.Crit("Failed to write genesis block", "err", err) + } + bc.writeHeadBlock(genesis) + + // Last update all in-memory chain markers + bc.genesisBlock = genesis + bc.currentBlock.Store(bc.genesisBlock.Header()) + headBlockGauge.Update(int64(bc.genesisBlock.NumberU64())) + bc.hc.SetGenesis(bc.genesisBlock.Header()) + bc.hc.SetCurrentHeader(bc.genesisBlock.Header()) + bc.currentSnapBlock.Store(bc.genesisBlock.Header()) + headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64())) + return nil +} + +// Export writes the active chain to the given writer. +func (bc *BlockChain) Export(w io.Writer) error { + return bc.ExportN(w, uint64(0), bc.CurrentBlock().Number.Uint64()) +} + +// ExportN writes a subset of the active chain to the given writer. +func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { + if first > last { + return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last) + } + log.Info("Exporting batch of blocks", "count", last-first+1) + + var ( + parentHash common.Hash + start = time.Now() + reported = time.Now() + ) + for nr := first; nr <= last; nr++ { + block := bc.GetBlockByNumber(nr) + if block == nil { + return fmt.Errorf("export failed on #%d: not found", nr) + } + if nr > first && block.ParentHash() != parentHash { + return errors.New("export failed: chain reorg during export") + } + parentHash = block.Hash() + if err := block.EncodeRLP(w); err != nil { + return err + } + if time.Since(reported) >= statsReportLimit { + log.Info("Exporting blocks", "exported", block.NumberU64()-first, "elapsed", common.PrettyDuration(time.Since(start))) + reported = time.Now() + } + } + return nil +} + +// writeHeadBlock injects a new head block into the current block chain. This method +// assumes that the block is indeed a true head. It will also reset the head +// header and the head snap sync block to this very same block if they are older +// or if they are on a different side chain. +// +// Note, this function assumes that the `mu` mutex is held! +func (bc *BlockChain) writeHeadBlock(block *types.Block) { + // Add the block to the canonical chain number scheme and mark as the head + batch := bc.db.NewBatch() + rawdb.WriteHeadHeaderHash(batch, block.Hash()) + rawdb.WriteHeadFastBlockHash(batch, block.Hash()) + rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) + rawdb.WriteTxLookupEntriesByBlock(batch, block) + rawdb.WriteHeadBlockHash(batch, block.Hash()) + + // Flush the whole batch into the disk, exit the node if failed + if err := batch.Write(); err != nil { + log.Crit("Failed to update chain indexes and markers", "err", err) + } + // Update all in-memory chain markers in the last step + bc.hc.SetCurrentHeader(block.Header()) + + bc.currentSnapBlock.Store(block.Header()) + headFastBlockGauge.Update(int64(block.NumberU64())) + + bc.currentBlock.Store(block.Header()) + headBlockGauge.Update(int64(block.NumberU64())) +} + +// stopWithoutSaving stops the blockchain service. If any imports are currently in progress +// it will abort them using the procInterrupt. This method stops all running +// goroutines, but does not do all the post-stop work of persisting data. +// OBS! It is generally recommended to use the Stop method! +// This method has been exposed to allow tests to stop the blockchain while simulating +// a crash. +func (bc *BlockChain) stopWithoutSaving() { + if !bc.stopping.CompareAndSwap(false, true) { + return + } + // Signal shutdown tx indexer. + if bc.txIndexer != nil { + bc.txIndexer.close() + } + // Unsubscribe all subscriptions registered from blockchain. + bc.scope.Close() + + // Signal shutdown to all goroutines. + close(bc.quit) + bc.StopInsert() + + // Now wait for all chain modifications to end and persistent goroutines to exit. + // + // Note: Close waits for the mutex to become available, i.e. any running chain + // modification will have exited when Close returns. Since we also called StopInsert, + // the mutex should become available quickly. It cannot be taken again after Close has + // returned. + bc.chainmu.Close() + bc.wg.Wait() +} + +// Stop stops the blockchain service. If any imports are currently in progress +// it will abort them using the procInterrupt. +func (bc *BlockChain) Stop() { + bc.stopWithoutSaving() + + // 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() + } + 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, state.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) + } + } + 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") + } + } + } + // Allow tracers to clean-up and release resources. + if bc.logger != nil && bc.logger.OnClose != nil { + bc.logger.OnClose() + } + // 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") +} + +// StopInsert interrupts all insertion methods, causing them to return +// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after +// calling this method. +func (bc *BlockChain) StopInsert() { + bc.procInterrupt.Store(true) +} + +// insertStopped returns true after StopInsert has been called. +func (bc *BlockChain) insertStopped() bool { + return bc.procInterrupt.Load() +} + +// WriteStatus status of write +type WriteStatus byte + +const ( + NonStatTy WriteStatus = iota + CanonStatTy + SideStatTy +) + +// InsertReceiptChain attempts to complete an already existing header chain with +// transaction and receipt data. +func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts, ancientLimit uint64) (int, error) { + // We don't require the chainMu here since we want to maximize the + // concurrency of header insertion and receipt insertion. + bc.wg.Add(1) + defer bc.wg.Done() + + var ( + ancientBlocks, liveBlocks types.Blocks + ancientReceipts, liveReceipts []types.Receipts + ) + // Do a sanity check that the provided chain is actually ordered and linked + for i, block := range blockChain { + if i != 0 { + 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 block.NumberU64() <= ancientLimit { + ancientBlocks, ancientReceipts = append(ancientBlocks, block), append(ancientReceipts, receiptChain[i]) + } else { + 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) + } + } + } + + var ( + stats = struct{ processed, ignored int32 }{} + start = time.Now() + size = int64(0) + ) + + // updateHead updates the head snap sync block if the inserted blocks are better + // and returns an indicator whether the inserted blocks are canonical. + updateHead := func(head *types.Block) bool { + if !bc.chainmu.TryLock() { + return false + } + defer bc.chainmu.Unlock() + + // Rewind may have occurred, skip in that case. + if bc.CurrentHeader().Number.Cmp(head.Number()) >= 0 { + reorg, err := bc.forker.ReorgNeeded(bc.CurrentSnapBlock(), head.Header()) + if err != nil { + log.Warn("Reorg failed", "err", err) + return false + } else if !reorg { + return false + } + rawdb.WriteHeadFastBlockHash(bc.db, head.Hash()) + bc.currentSnapBlock.Store(head.Header()) + headFastBlockGauge.Update(int64(head.NumberU64())) + return true + } + return false + } + // writeAncient writes blockchain and corresponding receipt chain into ancient store. + // + // this function only accepts canonical chain data. All side chain will be reverted + // eventually. + writeAncient := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { + first := blockChain[0] + last := blockChain[len(blockChain)-1] + + // Ensure genesis is in ancients. + if first.NumberU64() == 1 { + if frozen, _ := bc.db.Ancients(); frozen == 0 { + td := bc.genesisBlock.Difficulty() + writeSize, err := rawdb.WriteAncientBlocks(bc.db, []*types.Block{bc.genesisBlock}, []types.Receipts{nil}, td) + if err != nil { + log.Error("Error writing genesis to ancients", "err", err) + return 0, err + } + size += writeSize + log.Info("Wrote genesis to ancients") + } + } + // Before writing the blocks to the ancients, we need to ensure that + // they correspond to the what the headerchain 'expects'. + // We only check the last block/header, since it's a contiguous chain. + if !bc.HasHeader(last.Hash(), last.NumberU64()) { + return 0, fmt.Errorf("containing header #%d [%x..] unknown", last.Number(), last.Hash().Bytes()[:4]) + } + + // Write all chain data to ancients. + td := bc.GetTd(first.Hash(), first.NumberU64()) + writeSize, err := rawdb.WriteAncientBlocks(bc.db, blockChain, receiptChain, td) + if err != nil { + log.Error("Error importing chain data to ancients", "err", err) + return 0, err + } + size += writeSize + + // Sync the ancient store explicitly to ensure all data has been flushed to disk. + if err := bc.db.Sync(); err != nil { + return 0, err + } + // Update the current snap block because all block data is now present in DB. + previousSnapBlock := bc.CurrentSnapBlock().Number.Uint64() + if !updateHead(blockChain[len(blockChain)-1]) { + // We end up here if the header chain has reorg'ed, and the blocks/receipts + // don't match the canonical chain. + if _, err := bc.db.TruncateHead(previousSnapBlock + 1); err != nil { + log.Error("Can't truncate ancient store after failed insert", "err", err) + } + return 0, errSideChainReceipts + } + + // Delete block data from the main database. + var ( + batch = bc.db.NewBatch() + canonHashes = make(map[common.Hash]struct{}, len(blockChain)) + ) + for _, block := range blockChain { + canonHashes[block.Hash()] = struct{}{} + if block.NumberU64() == 0 { + continue + } + rawdb.DeleteCanonicalHash(batch, block.NumberU64()) + rawdb.DeleteBlockWithoutNumber(batch, block.Hash(), block.NumberU64()) + } + // Delete side chain hash-to-number mappings. + for _, nh := range rawdb.ReadAllHashesInRange(bc.db, first.NumberU64(), last.NumberU64()) { + if _, canon := canonHashes[nh.Hash]; !canon { + rawdb.DeleteHeader(batch, nh.Hash, nh.Number) + } + } + if err := batch.Write(); err != nil { + return 0, err + } + stats.processed += int32(len(blockChain)) + return 0, nil + } + + // writeLive writes blockchain and corresponding receipt chain into active store. + writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) { + var ( + skipPresenceCheck = false + batch = bc.db.NewBatch() + ) + for i, block := range blockChain { + // Short circuit insertion if shutting down or processing failed + if bc.insertStopped() { + return 0, errInsertionInterrupted + } + // Short circuit if the owner header is unknown + if !bc.HasHeader(block.Hash(), block.NumberU64()) { + return i, fmt.Errorf("containing header #%d [%x..] unknown", block.Number(), block.Hash().Bytes()[:4]) + } + if !skipPresenceCheck { + // Ignore if the entire data is already known + if bc.HasBlock(block.Hash(), block.NumberU64()) { + stats.ignored++ + continue + } else { + // If block N is not present, neither are the later blocks. + // This should be true, but if we are mistaken, the shortcut + // here will only cause overwriting of some existing data + skipPresenceCheck = true + } + } + // Write all the data out into the database + rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body()) + rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i]) + + // Write everything belongs to the blocks into the database. So that + // we can ensure all components of body is completed(body, receipts) + // except transaction indexes(will be created once sync is finished). + if batch.ValueSize() >= ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return 0, err + } + size += int64(batch.ValueSize()) + batch.Reset() + } + stats.processed++ + } + // Write everything belongs to the blocks into the database. So that + // we can ensure all components of body is completed(body, receipts, + // tx indexes) + if batch.ValueSize() > 0 { + size += int64(batch.ValueSize()) + if err := batch.Write(); err != nil { + return 0, err + } + } + updateHead(blockChain[len(blockChain)-1]) + return 0, nil + } + + // Write downloaded chain data and corresponding receipt chain data + if len(ancientBlocks) > 0 { + if n, err := writeAncient(ancientBlocks, ancientReceipts); err != nil { + if err == errInsertionInterrupted { + return 0, nil + } + return n, err + } + } + if len(liveBlocks) > 0 { + if n, err := writeLive(liveBlocks, liveReceipts); err != nil { + if err == errInsertionInterrupted { + return 0, nil + } + return n, err + } + } + var ( + head = blockChain[len(blockChain)-1] + context = []interface{}{ + "count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)), + "number", head.Number(), "hash", head.Hash(), "age", common.PrettyAge(time.Unix(int64(head.Time()), 0)), + "size", common.StorageSize(size), + } + ) + if stats.ignored > 0 { + context = append(context, []interface{}{"ignored", stats.ignored}...) + } + log.Debug("Imported new block receipts", context...) + + return 0, nil +} + +// writeBlockWithoutState writes only the block and its metadata to the database, +// but does not write any state. This is used to construct competing side forks +// up to the point where they exceed the canonical total difficulty. +func (bc *BlockChain) writeBlockWithoutState(block *types.Block, td *big.Int) (err error) { + if bc.insertStopped() { + return errInsertionInterrupted + } + batch := bc.db.NewBatch() + rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), td) + rawdb.WriteBlock(batch, block) + if err := batch.Write(); err != nil { + log.Crit("Failed to write block into disk", "err", err) + } + return nil +} + +// writeKnownBlock updates the head block flag with a known block +// and introduces chain reorg if necessary. +func (bc *BlockChain) writeKnownBlock(block *types.Block) error { + current := bc.CurrentBlock() + if block.ParentHash() != current.Hash() { + if err := bc.reorg(current, block); err != nil { + return err + } + } + bc.writeHeadBlock(block) + return nil +} + +// writeBlockWithState writes block, metadata and corresponding state data to the +// database. +func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, statedb *state.StateDB) error { + // Calculate the total difficulty of the block + ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1) + if ptd == nil { + return consensus.ErrUnknownAncestor + } + // Make sure no inconsistent state is leaked during insertion + externTd := new(big.Int).Add(block.Difficulty(), ptd) + + // Irrelevant of the canonical status, write the block itself to the database. + // + // Note all the components of block(td, hash->number map, header, body, receipts) + // should be written atomically. BlockBatch is used for containing all components. + blockBatch := bc.db.NewBatch() + rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd) + rawdb.WriteBlock(blockBatch, block) + rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts) + rawdb.WritePreimages(blockBatch, statedb.Preimages()) + if err := blockBatch.Write(); err != nil { + log.Crit("Failed to write block into disk", "err", err) + } + // Commit all cached state changes into underlying memory database. + root, err := statedb.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number())) + 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) + } + // Full but not archive node, do proper garbage collection + bc.triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive + bc.triegc.Push(root, -int64(block.NumberU64())) + + // Flush limits are not considered for the first TriesInMemory blocks. + current := block.NumberU64() + if current <= state.TriesInMemory { + return nil + } + // If we exceeded our memory allowance, flush matured singleton nodes to disk + var ( + _, 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) + } + // Find the next state trie we need to commit + chosen := current - state.TriesInMemory + flushInterval := time.Duration(bc.flushInterval.Load()) + // If we exceeded time allowance, flush an entire trie to disk + if bc.gcproc > flushInterval { + // If the header is missing (canonical chain behind), we're reorging a low + // diff sidechain. Suspend committing until this operation is completed. + header := bc.GetHeaderByNumber(chosen) + if header == nil { + log.Warn("Reorg in progress, trie commit postponed", "number", chosen) + } else { + // If we're exceeding limits but haven't reached a large enough memory gap, + // warn the user that the system is becoming unstable. + if chosen < bc.lastWrite+state.TriesInMemory && bc.gcproc >= 2*flushInterval { + log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", flushInterval, "optimum", float64(chosen-bc.lastWrite)/state.TriesInMemory) + } + // Flush an entire trie and restart the counters + bc.triedb.Commit(header.Root, true) + bc.lastWrite = chosen + bc.gcproc = 0 + } + } + // Garbage collect anything below our required write retention + for !bc.triegc.Empty() { + root, number := bc.triegc.Pop() + if uint64(-number) > chosen { + bc.triegc.Push(root, number) + break + } + bc.triedb.Dereference(root) + } + return nil +} + +// writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead. +// This function expects the chain mutex to be held. +func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { + if err := bc.writeBlockWithState(block, receipts, state); err != nil { + return NonStatTy, err + } + currentBlock := bc.CurrentBlock() + reorg, err := bc.forker.ReorgNeeded(currentBlock, block.Header()) + if err != nil { + return NonStatTy, err + } + if reorg { + // Reorganise the chain if the parent is not the head block + if block.ParentHash() != currentBlock.Hash() { + if err := bc.reorg(currentBlock, block); err != nil { + return NonStatTy, err + } + } + status = CanonStatTy + } else { + status = SideStatTy + } + // Set new head. + if status == CanonStatTy { + bc.writeHeadBlock(block) + } + if status == CanonStatTy { + bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) + if len(logs) > 0 { + bc.logsFeed.Send(logs) + } + // In theory, we should fire a ChainHeadEvent when we inject + // a canonical block, but sometimes we can insert a batch of + // canonical blocks. Avoid firing too many ChainHeadEvents, + // we will fire an accumulated ChainHeadEvent and disable fire + // event here. + if emitHeadEvent { + bc.chainHeadFeed.Send(ChainHeadEvent{Block: block}) + } + } else { + bc.chainSideFeed.Send(ChainSideEvent{Block: block}) + } + return status, nil +} + +// InsertChain attempts to insert the given batch of blocks in to the canonical +// chain or, otherwise, create a fork. If an error is returned it will return +// the index number of the failing block as well an error describing what went +// wrong. After insertion is done, all accumulated events will be fired. +func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { + // Sanity check that we have something meaningful to import + if len(chain) == 0 { + return 0, nil + } + bc.blockProcFeed.Send(true) + defer bc.blockProcFeed.Send(false) + + // Do a sanity check that the provided chain is actually ordered and linked. + for i := 1; i < len(chain); i++ { + block, prev := chain[i], chain[i-1] + if block.NumberU64() != prev.NumberU64()+1 || block.ParentHash() != prev.Hash() { + log.Error("Non contiguous block 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]) + } + } + // Pre-checks passed, start the full block imports + if !bc.chainmu.TryLock() { + return 0, errChainStopped + } + defer bc.chainmu.Unlock() + return bc.insertChain(chain, true) +} + +// insertChain is the internal implementation of InsertChain, which assumes that +// 1) chains are contiguous, and 2) The chain mutex is held. +// +// This method is split out so that import batches that require re-injecting +// historical blocks can do so without releasing the lock, which could lead to +// racey behaviour. If a sidechain import is in progress, and the historic state +// is imported, but then new canon-head is added before the actual sidechain +// completes, then the historic state could be pruned again +func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) { + // If the chain is terminating, don't even bother starting up. + if bc.insertStopped() { + return 0, nil + } + + // Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss) + SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number(), chain[0].Time()), chain) + + var ( + stats = insertStats{startTime: mclock.Now()} + lastCanon *types.Block + ) + // Fire a single chain head event if we've progressed the chain + defer func() { + if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { + bc.chainHeadFeed.Send(ChainHeadEvent{lastCanon}) + } + }() + // Start the parallel header verifier + headers := make([]*types.Header, len(chain)) + for i, block := range chain { + headers[i] = block.Header() + } + abort, results := bc.engine.VerifyHeaders(bc, headers) + defer close(abort) + + // Peek the error for the first block to decide the directing import logic + it := newInsertIterator(chain, results, bc.validator) + block, err := it.next() + + // Left-trim all the known blocks that don't need to build snapshot + if bc.skipBlock(err, it) { + // First block (and state) is known + // 1. We did a roll-back, and should now do a re-import + // 2. The block is stored as a sidechain, and is lying about it's stateroot, and passes a stateroot + // from the canonical chain, which has not been verified. + // Skip all known blocks that are behind us. + var ( + reorg bool + current = bc.CurrentBlock() + ) + for block != nil && bc.skipBlock(err, it) { + reorg, err = bc.forker.ReorgNeeded(current, block.Header()) + if err != nil { + return it.index, err + } + if reorg { + // Switch to import mode if the forker says the reorg is necessary + // and also the block is not on the canonical chain. + // In eth2 the forker always returns true for reorg decision (blindly trusting + // the external consensus engine), but in order to prevent the unnecessary + // reorgs when importing known blocks, the special case is handled here. + if block.NumberU64() > current.Number.Uint64() || bc.GetCanonicalHash(block.NumberU64()) != block.Hash() { + break + } + } + log.Debug("Ignoring already known block", "number", block.Number(), "hash", block.Hash()) + stats.ignored++ + + block, err = it.next() + } + // The remaining blocks are still known blocks, the only scenario here is: + // During the snap sync, the pivot point is already submitted but rollback + // happens. Then node resets the head full block to a lower height via `rollback` + // and leaves a few known blocks in the database. + // + // When node runs a snap sync again, it can re-import a batch of known blocks via + // `insertChain` while a part of them have higher total difficulty than current + // head full block(new pivot point). + for block != nil && bc.skipBlock(err, it) { + log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash()) + if err := bc.writeKnownBlock(block); err != nil { + return it.index, err + } + lastCanon = block + + block, err = it.next() + } + // Falls through to the block import + } + switch { + // First block is pruned + case errors.Is(err, consensus.ErrPrunedAncestor): + if setHead { + // First block is pruned, insert as sidechain and reorg only if TD grows enough + log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash()) + return bc.insertSideChain(block, it) + } else { + // We're post-merge and the parent is pruned, try to recover the parent state + log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash()) + _, err := bc.recoverAncestors(block) + return it.index, err + } + // Some other error(except ErrKnownBlock) occurred, abort. + // ErrKnownBlock is allowed here since some known blocks + // still need re-execution to generate snapshots that are missing + case err != nil && !errors.Is(err, ErrKnownBlock): + stats.ignored += len(it.chain) + bc.reportBlock(block, nil, err) + return it.index, err + } + // No validation errors for the first block (or chain prefix skipped) + var activeState *state.StateDB + defer func() { + // The chain importer is starting and stopping trie prefetchers. If a bad + // block or other error is hit however, an early return may not properly + // terminate the background threads. This defer ensures that we clean up + // and dangling prefetcher, without deferring each and holding on live refs. + if activeState != nil { + activeState.StopPrefetcher() + } + }() + + for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() { + // If the chain is terminating, stop processing blocks + if bc.insertStopped() { + log.Debug("Abort during block processing") + break + } + // If the block is known (in the middle of the chain), it's a special case for + // Clique blocks where they can share state among each other, so importing an + // older block might complete the state of the subsequent one. In this case, + // just skip the block (we already validated it once fully (and crashed), since + // its header and body was already in the database). But if the corresponding + // snapshot layer is missing, forcibly rerun the execution to build it. + if bc.skipBlock(err, it) { + logger := log.Debug + if bc.chainConfig.Clique == nil { + logger = log.Warn + } + logger("Inserted known block", "number", block.Number(), "hash", block.Hash(), + "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), + "root", block.Root()) + + // Special case. Commit the empty receipt slice if we meet the known + // block in the middle. It can only happen in the clique chain. Whenever + // we insert blocks via `insertSideChain`, we only commit `td`, `header` + // and `body` if it's non-existent. Since we don't have receipts without + // reexecution, so nothing to commit. But if the sidechain will be adopted + // as the canonical chain eventually, it needs to be reexecuted for missing + // state, but if it's this special case here(skip reexecution) we will lose + // the empty receipt entry. + if len(block.Transactions()) == 0 { + rawdb.WriteReceipts(bc.db, block.Hash(), block.NumberU64(), nil) + } else { + log.Error("Please file an issue, skip known block execution without receipt", + "hash", block.Hash(), "number", block.NumberU64()) + } + if err := bc.writeKnownBlock(block); err != nil { + return it.index, err + } + stats.processed++ + if bc.logger != nil && bc.logger.OnSkippedBlock != nil { + bc.logger.OnSkippedBlock(tracing.BlockEvent{ + Block: block, + TD: bc.GetTd(block.ParentHash(), block.NumberU64()-1), + Finalized: bc.CurrentFinalBlock(), + Safe: bc.CurrentSafeBlock(), + }) + } + + // We can assume that logs are empty here, since the only way for consecutive + // Clique blocks to have the same state is if there are no transactions. + lastCanon = block + continue + } + + // Retrieve the parent block and it's state to execute on top + start := time.Now() + parent := it.previous() + if parent == nil { + parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) + } + statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) + if err != nil { + return it.index, err + } + statedb.SetLogger(bc.logger) + + // If we are past Byzantium, enable prefetching to pull in trie node paths + // while processing transactions. Before Byzantium the prefetcher is mostly + // useless due to the intermediate root hashing after each transaction. + if bc.chainConfig.IsByzantium(block.Number()) { + var witness *stateless.Witness + if bc.vmConfig.EnableWitnessCollection { + witness, err = stateless.NewWitness(bc, block) + if err != nil { + return it.index, err + } + } + statedb.StartPrefetcher("chain", witness) + } + activeState = statedb + + // If we have a followup block, run that against the current state to pre-cache + // transactions and probabilistically some of the account/storage trie nodes. + var followupInterrupt atomic.Bool + if !bc.cacheConfig.TrieCleanNoPrefetch { + if followup, err := it.peek(); followup != nil && err == nil { + throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) + + go func(start time.Time, followup *types.Block, throwaway *state.StateDB) { + // Disable tracing for prefetcher executions. + vmCfg := bc.vmConfig + vmCfg.Tracer = nil + bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt) + + blockPrefetchExecuteTimer.Update(time.Since(start)) + if followupInterrupt.Load() { + blockPrefetchInterruptMeter.Mark(1) + } + }(time.Now(), followup, throwaway) + } + } + + // The traced section of block import. + res, err := bc.processBlock(block, statedb, start, setHead) + followupInterrupt.Store(true) + if err != nil { + return it.index, err + } + // Report the import stats before returning the various results + stats.processed++ + stats.usedGas += res.usedGas + + var snapDiffItems, snapBufItems common.StorageSize + if bc.snaps != nil { + snapDiffItems, snapBufItems = bc.snaps.Size() + } + trieDiffNodes, trieBufNodes, _ := bc.triedb.Size() + stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead) + + if !setHead { + // After merge we expect few side chains. Simply count + // all blocks the CL gives us for GC processing time + bc.gcproc += res.procTime + return it.index, nil // Direct block insertion of a single block + } + switch res.status { + case CanonStatTy: + log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), + "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), + "elapsed", common.PrettyDuration(time.Since(start)), + "root", block.Root()) + + lastCanon = block + + // Only count canonical blocks for GC processing time + bc.gcproc += res.procTime + + case SideStatTy: + log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), + "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), + "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), + "root", block.Root()) + + default: + // This in theory is impossible, but lets be nice to our future selves and leave + // a log, instead of trying to track down blocks imports that don't emit logs. + log.Warn("Inserted block with unknown status", "number", block.Number(), "hash", block.Hash(), + "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), + "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), + "root", block.Root()) + } + } + stats.ignored += it.remaining() + return it.index, err +} + +// blockProcessingResult is a summary of block processing +// used for updating the stats. +type blockProcessingResult struct { + usedGas uint64 + procTime time.Duration + status WriteStatus +} + +// processBlock executes and validates the given block. If there was no error +// it writes the block and associated state to database. +func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { + if bc.logger != nil && bc.logger.OnBlockStart != nil { + td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) + bc.logger.OnBlockStart(tracing.BlockEvent{ + Block: block, + TD: td, + Finalized: bc.CurrentFinalBlock(), + Safe: bc.CurrentSafeBlock(), + }) + } + if bc.logger != nil && bc.logger.OnBlockEnd != nil { + defer func() { + bc.logger.OnBlockEnd(blockEndErr) + }() + } + + // Process block using the parent state as reference point + pstart := time.Now() + receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) + if err != nil { + bc.reportBlock(block, receipts, err) + return nil, err + } + ptime := time.Since(pstart) + + vstart := time.Now() + if err := bc.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil { + bc.reportBlock(block, receipts, err) + return nil, err + } + vtime := time.Since(vstart) + + if witness := statedb.Witness(); witness != nil { + if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil { + bc.reportBlock(block, receipts, err) + return nil, fmt.Errorf("cross verification failed: %v", err) + } + } + proctime := time.Since(start) // processing + validation + + // Update the metrics touched during block processing and validation + accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) + storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) + snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) + snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) + accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) + storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) + accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) + triehash := statedb.AccountHashes // The time spent on tries hashing + trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update + trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read + trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read + blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing + blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation + + // Write the block to the chain and get the status. + var ( + wstart = time.Now() + status WriteStatus + ) + if !setHead { + // Don't set the head, only insert the block + err = bc.writeBlockWithState(block, receipts, statedb) + } else { + status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) + } + if err != nil { + return nil, err + } + // Update the metrics touched during block commit + accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them + storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them + snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them + triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them + + blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits) + blockInsertTimer.UpdateSince(start) + + return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil +} + +// insertSideChain is called when an import batch hits upon a pruned ancestor +// error, which happens when a sidechain with a sufficiently old fork-block is +// found. +// +// The method writes all (header-and-body-valid) blocks to disk, then tries to +// switch over to the new chain if the TD exceeded the current chain. +// insertSideChain is only used pre-merge. +func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, error) { + var ( + externTd *big.Int + lastBlock = block + current = bc.CurrentBlock() + ) + // The first sidechain block error is already verified to be ErrPrunedAncestor. + // Since we don't import them here, we expect ErrUnknownAncestor for the remaining + // ones. Any other errors means that the block is invalid, and should not be written + // to disk. + err := consensus.ErrPrunedAncestor + for ; block != nil && errors.Is(err, consensus.ErrPrunedAncestor); block, err = it.next() { + // Check the canonical state root for that number + if number := block.NumberU64(); current.Number.Uint64() >= number { + canonical := bc.GetBlockByNumber(number) + if canonical != nil && canonical.Hash() == block.Hash() { + // Not a sidechain block, this is a re-import of a canon block which has it's state pruned + + // Collect the TD of the block. Since we know it's a canon one, + // we can get it directly, and not (like further below) use + // the parent and then add the block on top + externTd = bc.GetTd(block.Hash(), block.NumberU64()) + continue + } + if canonical != nil && canonical.Root() == block.Root() { + // This is most likely a shadow-state attack. When a fork is imported into the + // database, and it eventually reaches a block height which is not pruned, we + // just found that the state already exist! This means that the sidechain block + // refers to a state which already exists in our canon chain. + // + // If left unchecked, we would now proceed importing the blocks, without actually + // having verified the state of the previous blocks. + log.Warn("Sidechain ghost-state attack detected", "number", block.NumberU64(), "sideroot", block.Root(), "canonroot", canonical.Root()) + + // If someone legitimately side-mines blocks, they would still be imported as usual. However, + // we cannot risk writing unverified blocks to disk when they obviously target the pruning + // mechanism. + return it.index, errors.New("sidechain ghost-state attack") + } + } + if externTd == nil { + externTd = bc.GetTd(block.ParentHash(), block.NumberU64()-1) + } + externTd = new(big.Int).Add(externTd, block.Difficulty()) + + if !bc.HasBlock(block.Hash(), block.NumberU64()) { + start := time.Now() + if err := bc.writeBlockWithoutState(block, externTd); err != nil { + return it.index, err + } + log.Debug("Injected sidechain block", "number", block.Number(), "hash", block.Hash(), + "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(start)), + "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()), + "root", block.Root()) + } + lastBlock = block + } + // At this point, we've written all sidechain blocks to database. Loop ended + // either on some other error or all were processed. If there was some other + // error, we can ignore the rest of those blocks. + // + // If the externTd was larger than our local TD, we now need to reimport the previous + // blocks to regenerate the required state + reorg, err := bc.forker.ReorgNeeded(current, lastBlock.Header()) + if err != nil { + return it.index, err + } + if !reorg { + localTd := bc.GetTd(current.Hash(), current.Number.Uint64()) + log.Info("Sidechain written to disk", "start", it.first().NumberU64(), "end", it.previous().Number, "sidetd", externTd, "localtd", localTd) + return it.index, err + } + // Gather all the sidechain hashes (full blocks may be memory heavy) + var ( + hashes []common.Hash + numbers []uint64 + ) + 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()) + + parent = bc.GetHeader(parent.ParentHash, parent.Number.Uint64()-1) + } + if parent == nil { + return it.index, errors.New("missing parent") + } + // Import all the pruned blocks to make the state available + var ( + blocks []*types.Block + memory uint64 + ) + for i := len(hashes) - 1; i >= 0; i-- { + // Append the next block to our batch + block := bc.GetBlock(hashes[i], numbers[i]) + + blocks = append(blocks, block) + memory += block.Size() + + // If memory use grew too large, import and continue. Sadly we need to discard + // all raised events and logs from notifications since we're too heavy on the + // memory here. + if len(blocks) >= 2048 || memory > 64*1024*1024 { + log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64()) + if _, err := bc.insertChain(blocks, true); err != nil { + return 0, err + } + blocks, memory = blocks[:0], 0 + + // If the chain is terminating, stop processing blocks + if bc.insertStopped() { + log.Debug("Abort during blocks processing") + return 0, nil + } + } + } + if len(blocks) > 0 { + log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64()) + return bc.insertChain(blocks, true) + } + return 0, nil +} + +// recoverAncestors finds the closest ancestor with available state and re-execute +// all the ancestor blocks since that. +// recoverAncestors is only used post-merge. +// We return the hash of the latest block that we could correctly validate. +func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) { + // Gather all the sidechain hashes (full blocks may be memory heavy) + var ( + hashes []common.Hash + numbers []uint64 + 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) + + // If the chain is terminating, stop iteration + if bc.insertStopped() { + log.Debug("Abort during blocks iteration") + return common.Hash{}, errInsertionInterrupted + } + } + if parent == nil { + return common.Hash{}, errors.New("missing parent") + } + // Import all the pruned blocks to make the state available + for i := len(hashes) - 1; i >= 0; i-- { + // If the chain is terminating, stop processing blocks + if bc.insertStopped() { + log.Debug("Abort during blocks processing") + return common.Hash{}, errInsertionInterrupted + } + var b *types.Block + if i == 0 { + b = block + } else { + b = bc.GetBlock(hashes[i], numbers[i]) + } + if _, err := bc.insertChain(types.Blocks{b}, false); err != nil { + return b.ParentHash(), err + } + } + return block.Hash(), nil +} + +// collectLogs collects the logs that were generated or removed during +// the processing of a block. These logs are later announced as deleted or reborn. +func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { + var blobGasPrice *big.Int + excessBlobGas := b.ExcessBlobGas() + if excessBlobGas != nil { + blobGasPrice = eip4844.CalcBlobFee(*excessBlobGas) + } + receipts := rawdb.ReadRawReceipts(bc.db, b.Hash(), b.NumberU64()) + if err := receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Time(), b.BaseFee(), blobGasPrice, b.Transactions()); err != nil { + log.Error("Failed to derive block receipts fields", "hash", b.Hash(), "number", b.NumberU64(), "err", err) + } + var logs []*types.Log + for _, receipt := range receipts { + for _, log := range receipt.Logs { + if removed { + log.Removed = true + } + logs = append(logs, log) + } + } + return logs +} + +// reorg takes two blocks, an old chain and a new chain and will reconstruct the +// blocks and inserts them to be part of the new canonical chain and accumulates +// potential missing transactions and post an event about them. +// Note the new head block won't be processed here, callers need to handle it +// externally. +func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { + var ( + newChain types.Blocks + oldChain types.Blocks + commonBlock *types.Block + + deletedTxs []common.Hash + addedTxs []common.Hash + ) + oldBlock := bc.GetBlock(oldHead.Hash(), oldHead.Number.Uint64()) + if oldBlock == nil { + return errors.New("current head block missing") + } + newBlock := newHead + + // Reduce the longer chain to the same number as the shorter one + if oldBlock.NumberU64() > newBlock.NumberU64() { + // Old chain is longer, gather all transactions and logs as deleted ones + for ; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) { + oldChain = append(oldChain, oldBlock) + for _, tx := range oldBlock.Transactions() { + deletedTxs = append(deletedTxs, tx.Hash()) + } + } + } else { + // New chain is longer, stash all blocks away for subsequent insertion + for ; newBlock != nil && newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) { + newChain = append(newChain, newBlock) + } + } + if oldBlock == nil { + return errInvalidOldChain + } + if newBlock == nil { + return errInvalidNewChain + } + // Both sides of the reorg are at the same number, reduce both until the common + // ancestor is found + for { + // If the common ancestor was found, bail out + if oldBlock.Hash() == newBlock.Hash() { + commonBlock = oldBlock + break + } + // Remove an old block as well as stash away a new block + oldChain = append(oldChain, oldBlock) + for _, tx := range oldBlock.Transactions() { + deletedTxs = append(deletedTxs, tx.Hash()) + } + newChain = append(newChain, newBlock) + + // Step back with both chains + oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) + if oldBlock == nil { + return errInvalidOldChain + } + newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) + if newBlock == nil { + return errInvalidNewChain + } + } + + // Ensure the user sees large reorgs + if len(oldChain) > 0 && len(newChain) > 0 { + logFn := log.Info + msg := "Chain reorg detected" + if len(oldChain) > 63 { + msg = "Large chain reorg detected" + logFn = log.Warn + } + logFn(msg, "number", commonBlock.Number(), "hash", commonBlock.Hash(), + "drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash()) + blockReorgAddMeter.Mark(int64(len(newChain))) + blockReorgDropMeter.Mark(int64(len(oldChain))) + blockReorgMeter.Mark(1) + } else if len(newChain) > 0 { + // Special case happens in the post merge stage that current head is + // the ancestor of new head while these two blocks are not consecutive + log.Info("Extend chain", "add", len(newChain), "number", newChain[0].Number(), "hash", newChain[0].Hash()) + blockReorgAddMeter.Mark(int64(len(newChain))) + } else { + // len(newChain) == 0 && len(oldChain) > 0 + // rewind the canonical chain to a lower point. + log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain)) + } + // Acquire the tx-lookup lock before mutation. This step is essential + // as the txlookups should be changed atomically, and all subsequent + // reads should be blocked until the mutation is complete. + bc.txLookupLock.Lock() + + // Insert the new chain segment in incremental order, from the old + // to the new. The new chain head (newChain[0]) is not inserted here, + // as it will be handled separately outside of this function + for i := len(newChain) - 1; i >= 1; i-- { + // Insert the block in the canonical way, re-writing history + bc.writeHeadBlock(newChain[i]) + + // Collect the new added transactions. + for _, tx := range newChain[i].Transactions() { + addedTxs = append(addedTxs, tx.Hash()) + } + } + + // Delete useless indexes right now which includes the non-canonical + // transaction indexes, canonical chain indexes which above the head. + var ( + indexesBatch = bc.db.NewBatch() + diffs = types.HashDifference(deletedTxs, addedTxs) + ) + for _, tx := range diffs { + rawdb.DeleteTxLookupEntry(indexesBatch, tx) + } + // Delete all hash markers that are not part of the new canonical chain. + // Because the reorg function does not handle new chain head, all hash + // markers greater than or equal to new chain head should be deleted. + number := commonBlock.NumberU64() + if len(newChain) > 1 { + number = newChain[1].NumberU64() + } + for i := number + 1; ; i++ { + hash := rawdb.ReadCanonicalHash(bc.db, i) + if hash == (common.Hash{}) { + break + } + rawdb.DeleteCanonicalHash(indexesBatch, i) + } + if err := indexesBatch.Write(); err != nil { + log.Crit("Failed to delete useless indexes", "err", err) + } + // Reset the tx lookup cache to clear stale txlookup cache. + bc.txLookupCache.Purge() + + // Release the tx-lookup lock after mutation. + bc.txLookupLock.Unlock() + + // Send out events for logs from the old canon chain, and 'reborn' + // logs from the new canon chain. The number of logs can be very + // high, so the events are sent in batches of size around 512. + + // Deleted logs + blocks: + var deletedLogs []*types.Log + for i := len(oldChain) - 1; i >= 0; i-- { + // Also send event for blocks removed from the canon chain. + bc.chainSideFeed.Send(ChainSideEvent{Block: oldChain[i]}) + + // Collect deleted logs for notification + if logs := bc.collectLogs(oldChain[i], true); len(logs) > 0 { + deletedLogs = append(deletedLogs, logs...) + } + if len(deletedLogs) > 512 { + bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs}) + deletedLogs = nil + } + } + if len(deletedLogs) > 0 { + bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs}) + } + + // New logs: + var rebirthLogs []*types.Log + for i := len(newChain) - 1; i >= 1; i-- { + if logs := bc.collectLogs(newChain[i], false); len(logs) > 0 { + rebirthLogs = append(rebirthLogs, logs...) + } + if len(rebirthLogs) > 512 { + bc.logsFeed.Send(rebirthLogs) + rebirthLogs = nil + } + } + if len(rebirthLogs) > 0 { + bc.logsFeed.Send(rebirthLogs) + } + return nil +} + +// InsertBlockWithoutSetHead executes the block, runs the necessary verification +// upon it and then persist the block and the associate state into the database. +// The key difference between the InsertChain is it won't do the canonical chain +// updating. It relies on the additional SetCanonical call to finalize the entire +// procedure. +func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error { + if !bc.chainmu.TryLock() { + return errChainStopped + } + defer bc.chainmu.Unlock() + + _, err := bc.insertChain(types.Blocks{block}, false) + return err +} + +// SetCanonical rewinds the chain to set the new head block as the specified +// block. It's possible that the state of the new head is missing, and it will +// be recovered in this function as well. +func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { + if !bc.chainmu.TryLock() { + return common.Hash{}, errChainStopped + } + defer bc.chainmu.Unlock() + + // Re-execute the reorged chain in case the head state is missing. + if !bc.HasState(head.Root()) { + if latestValidHash, err := bc.recoverAncestors(head); err != nil { + return latestValidHash, err + } + log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash()) + } + // Run the reorg if necessary and set the given block as new head. + start := time.Now() + if head.ParentHash() != bc.CurrentBlock().Hash() { + if err := bc.reorg(bc.CurrentBlock(), head); err != nil { + return common.Hash{}, err + } + } + bc.writeHeadBlock(head) + + // Emit events + logs := bc.collectLogs(head, false) + bc.chainFeed.Send(ChainEvent{Block: head, Hash: head.Hash(), Logs: logs}) + if len(logs) > 0 { + bc.logsFeed.Send(logs) + } + bc.chainHeadFeed.Send(ChainHeadEvent{Block: head}) + + context := []interface{}{ + "number", head.Number(), + "hash", head.Hash(), + "root", head.Root(), + "elapsed", time.Since(start), + } + if timestamp := time.Unix(int64(head.Time()), 0); time.Since(timestamp) > time.Minute { + context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) + } + log.Info("Chain head was updated", context...) + return head.Hash(), nil +} + +// skipBlock returns 'true', if the block being imported can be skipped over, meaning +// that the block does not need to be processed but can be considered already fully 'done'. +func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool { + // We can only ever bypass processing if the only error returned by the validator + // is ErrKnownBlock, which means all checks passed, but we already have the block + // and state. + if !errors.Is(err, ErrKnownBlock) { + return false + } + // If we're not using snapshots, we can skip this, since we have both block + // and (trie-) state + if bc.snaps == nil { + return true + } + var ( + header = it.current() // header can't be nil + parentRoot common.Hash + ) + // If we also have the snapshot-state, we can skip the processing. + if bc.snaps.Snapshot(header.Root) != nil { + return true + } + // In this case, we have the trie-state but not snapshot-state. If the parent + // snapshot-state exists, we need to process this in order to not get a gap + // in the snapshot layers. + // Resolve parent block + if parent := it.previous(); parent != nil { + parentRoot = parent.Root + } else if parent = bc.GetHeaderByHash(header.ParentHash); parent != nil { + parentRoot = parent.Root + } + if parentRoot == (common.Hash{}) { + return false // Theoretically impossible case + } + // Parent is also missing snapshot: we can skip this. Otherwise process. + if bc.snaps.Snapshot(parentRoot) == nil { + return true + } + return false +} + +// reportBlock logs a bad block error. +func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) { + rawdb.WriteBadBlock(bc.db, block) + log.Error(summarizeBadBlock(block, receipts, bc.Config(), err)) +} + +// summarizeBadBlock returns a string summarizing the bad block and other +// relevant information. +func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string { + var receiptString string + for i, receipt := range receipts { + receiptString += fmt.Sprintf("\n %d: cumulative: %v gas: %v contract: %v status: %v tx: %v logs: %v bloom: %x state: %x", + i, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.ContractAddress.Hex(), + receipt.Status, receipt.TxHash.Hex(), receipt.Logs, receipt.Bloom, receipt.PostState) + } + version, vcs := version.Info() + platform := fmt.Sprintf("%s %s %s %s", version, runtime.Version(), runtime.GOARCH, runtime.GOOS) + if vcs != "" { + vcs = fmt.Sprintf("\nVCS: %s", vcs) + } + return fmt.Sprintf(` +########## BAD BLOCK ######### +Block: %v (%#x) +Error: %v +Platform: %v%v +Chain config: %#v +Receipts: %v +############################## +`, block.Number(), block.Hash(), err, platform, vcs, config, receiptString) +} + +// 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. +func (bc *BlockChain) InsertHeaderChain(chain []*types.Header) (int, error) { + if len(chain) == 0 { + return 0, nil + } + start := time.Now() + if i, err := bc.hc.ValidateHeaderChain(chain); err != nil { + return i, err + } + + if !bc.chainmu.TryLock() { + return 0, errChainStopped + } + defer bc.chainmu.Unlock() + _, err := bc.hc.InsertHeaderChain(chain, start, bc.forker) + return 0, err +} + +// SetBlockValidatorAndProcessorForTesting sets the current validator and processor. +// This method can be used to force an invalid blockchain to be verified for tests. +// This method is unsafe and should only be used before block import starts. +func (bc *BlockChain) SetBlockValidatorAndProcessorForTesting(v Validator, p Processor) { + bc.validator = v + bc.processor = p +} + +// SetTrieFlushInterval configures how often in-memory tries are persisted to disk. +// The interval is in terms of block processing time, not wall clock. +// It is thread-safe and can be called repeatedly without side effects. +func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration) { + bc.flushInterval.Store(int64(interval)) +} + +// GetTrieFlushInterval gets the in-memory tries flushAlloc 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 new file mode 100644 index 0000000..49e913a --- /dev/null +++ b/core/blockchain_insert.go @@ -0,0 +1,181 @@ +// 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 core + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// insertStats tracks and reports on block insertion. +type insertStats struct { + queued, processed, ignored int + usedGas uint64 + lastIndex int + startTime mclock.AbsTime +} + +// statsReportLimit is the time limit during import and export after which we +// always print out progress. This avoids the user wondering what's going on. +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, snapDiffItems, snapBufItems, trieDiffNodes, triebufNodes common.StorageSize, setHead bool) { + // Fetch the timings for the batch + var ( + now = mclock.Now() + elapsed = now.Sub(st.startTime) + ) + // If we're at the last block of the batch or report period reached, log + if index == len(chain)-1 || elapsed >= statsReportLimit { + // Count the number of transactions in this segment + var txs int + for _, block := range chain[st.lastIndex : index+1] { + txs += len(block.Transactions()) + } + end := chain[index] + + // Assemble the log context and send it to the logger + context := []interface{}{ + "number", end.Number(), "hash", end.Hash(), + "blocks", st.processed, "txs", txs, "mgas", float64(st.usedGas) / 1000000, + "elapsed", common.PrettyDuration(elapsed), "mgasps", float64(st.usedGas) * 1000 / float64(elapsed), + } + if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute { + context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) + } + 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}...) + } + if st.ignored > 0 { + context = append(context, []interface{}{"ignored", st.ignored}...) + } + if setHead { + log.Info("Imported new chain segment", context...) + } else { + log.Info("Imported new potential chain segment", context...) + } + // Bump the stats reported to the next section + *st = insertStats{startTime: now, lastIndex: index + 1} + } +} + +// insertIterator is a helper to assist during chain import. +type insertIterator struct { + chain types.Blocks // Chain of blocks being iterated over + + results <-chan error // Verification result sink from the consensus engine + errors []error // Header verification errors for the blocks + + index int // Current offset of the iterator + validator Validator // Validator to run if verification succeeds +} + +// newInsertIterator creates a new iterator based on the given blocks, which are +// assumed to be a contiguous chain. +func newInsertIterator(chain types.Blocks, results <-chan error, validator Validator) *insertIterator { + return &insertIterator{ + chain: chain, + results: results, + errors: make([]error, 0, len(chain)), + index: -1, + validator: validator, + } +} + +// next returns the next block in the iterator, along with any potential validation +// error for that block. When the end is reached, it will return (nil, nil). +func (it *insertIterator) next() (*types.Block, error) { + // If we reached the end of the chain, abort + if it.index+1 >= len(it.chain) { + it.index = len(it.chain) + return nil, nil + } + // Advance the iterator and wait for verification result if not yet done + it.index++ + if len(it.errors) <= it.index { + it.errors = append(it.errors, <-it.results) + } + if it.errors[it.index] != nil { + return it.chain[it.index], it.errors[it.index] + } + // Block header valid, run body validation and return + return it.chain[it.index], it.validator.ValidateBody(it.chain[it.index]) +} + +// peek returns the next block in the iterator, along with any potential validation +// error for that block, but does **not** advance the iterator. +// +// Both header and body validation errors (nil too) is cached into the iterator +// to avoid duplicating work on the following next() call. +func (it *insertIterator) peek() (*types.Block, error) { + // If we reached the end of the chain, abort + if it.index+1 >= len(it.chain) { + return nil, nil + } + // Wait for verification result if not yet done + if len(it.errors) <= it.index+1 { + it.errors = append(it.errors, <-it.results) + } + if it.errors[it.index+1] != nil { + return it.chain[it.index+1], it.errors[it.index+1] + } + // Block header valid, ignore body validation since we don't have a parent anyway + return it.chain[it.index+1], nil +} + +// previous returns the previous header that was being processed, or nil. +func (it *insertIterator) previous() *types.Header { + if it.index < 1 { + return nil + } + return it.chain[it.index-1].Header() +} + +// current returns the current header that is being processed, or nil. +func (it *insertIterator) current() *types.Header { + if it.index == -1 || it.index >= len(it.chain) { + return nil + } + return it.chain[it.index].Header() +} + +// first returns the first block in it. +func (it *insertIterator) first() *types.Block { + return it.chain[0] +} + +// remaining returns the number of remaining blocks. +func (it *insertIterator) remaining() int { + return len(it.chain) - it.index +} diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go new file mode 100644 index 0000000..8a85800 --- /dev/null +++ b/core/blockchain_reader.go @@ -0,0 +1,450 @@ +// 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 core + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/triedb" +) + +// CurrentHeader retrieves the current head header of the canonical chain. The +// header is retrieved from the HeaderChain's internal cache. +func (bc *BlockChain) CurrentHeader() *types.Header { + return bc.hc.CurrentHeader() +} + +// CurrentBlock retrieves the current head block of the canonical chain. The +// block is retrieved from the blockchain's internal cache. +func (bc *BlockChain) CurrentBlock() *types.Header { + return bc.currentBlock.Load() +} + +// CurrentSnapBlock retrieves the current snap-sync head block of the canonical +// chain. The block is retrieved from the blockchain's internal cache. +func (bc *BlockChain) CurrentSnapBlock() *types.Header { + return bc.currentSnapBlock.Load() +} + +// CurrentFinalBlock retrieves the current finalized block of the canonical +// chain. The block is retrieved from the blockchain's internal cache. +func (bc *BlockChain) CurrentFinalBlock() *types.Header { + return bc.currentFinalBlock.Load() +} + +// CurrentSafeBlock retrieves the current safe block of the canonical +// chain. The block is retrieved from the blockchain's internal cache. +func (bc *BlockChain) CurrentSafeBlock() *types.Header { + return bc.currentSafeBlock.Load() +} + +// HasHeader checks if a block header is present in the database or not, caching +// it if present. +func (bc *BlockChain) HasHeader(hash common.Hash, number uint64) bool { + return bc.hc.HasHeader(hash, number) +} + +// GetHeader retrieves a block header from the database by hash and number, +// caching it if found. +func (bc *BlockChain) GetHeader(hash common.Hash, number uint64) *types.Header { + return bc.hc.GetHeader(hash, number) +} + +// GetHeaderByHash retrieves a block header from the database by hash, caching it if +// found. +func (bc *BlockChain) GetHeaderByHash(hash common.Hash) *types.Header { + return bc.hc.GetHeaderByHash(hash) +} + +// GetHeaderByNumber retrieves a block header from the database by number, +// caching it (associated with its hash) if found. +func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header { + return bc.hc.GetHeaderByNumber(number) +} + +// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going +// backwards from the given number. +func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue { + return bc.hc.GetHeadersFrom(number, count) +} + +// GetBody retrieves a block body (transactions and uncles) from the database by +// hash, caching it if found. +func (bc *BlockChain) GetBody(hash common.Hash) *types.Body { + // Short circuit if the body's already in the cache, retrieve otherwise + if cached, ok := bc.bodyCache.Get(hash); ok { + return cached + } + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return nil + } + body := rawdb.ReadBody(bc.db, hash, *number) + if body == nil { + return nil + } + // Cache the found body for next time and return + bc.bodyCache.Add(hash, body) + return body +} + +// GetBodyRLP retrieves a block body in RLP encoding from the database by hash, +// caching it if found. +func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue { + // Short circuit if the body's already in the cache, retrieve otherwise + if cached, ok := bc.bodyRLPCache.Get(hash); ok { + return cached + } + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return nil + } + body := rawdb.ReadBodyRLP(bc.db, hash, *number) + if len(body) == 0 { + return nil + } + // Cache the found body for next time and return + bc.bodyRLPCache.Add(hash, body) + return body +} + +// HasBlock checks if a block is fully present in the database or not. +func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool { + if bc.blockCache.Contains(hash) { + return true + } + if !bc.HasHeader(hash, number) { + return false + } + return rawdb.HasBody(bc.db, hash, number) +} + +// HasFastBlock checks if a fast block is fully present in the database or not. +func (bc *BlockChain) HasFastBlock(hash common.Hash, number uint64) bool { + if !bc.HasBlock(hash, number) { + return false + } + if bc.receiptsCache.Contains(hash) { + return true + } + return rawdb.HasReceipts(bc.db, hash, number) +} + +// GetBlock retrieves a block from the database by hash and number, +// caching it if found. +func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { + // Short circuit if the block's already in the cache, retrieve otherwise + if block, ok := bc.blockCache.Get(hash); ok { + return block + } + block := rawdb.ReadBlock(bc.db, hash, number) + if block == nil { + return nil + } + // Cache the found block for next time and return + bc.blockCache.Add(block.Hash(), block) + return block +} + +// GetBlockByHash retrieves a block from the database by hash, caching it if found. +func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block { + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return nil + } + return bc.GetBlock(hash, *number) +} + +// GetBlockByNumber retrieves a block from the database by number, caching it +// (associated with its hash) if found. +func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block { + hash := rawdb.ReadCanonicalHash(bc.db, number) + if hash == (common.Hash{}) { + return nil + } + return bc.GetBlock(hash, number) +} + +// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors. +// [deprecated by eth/62] +func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) { + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return nil + } + for i := 0; i < n; i++ { + block := bc.GetBlock(hash, *number) + if block == nil { + break + } + blocks = append(blocks, block) + hash = block.ParentHash() + *number-- + } + return +} + +// GetReceiptsByHash retrieves the receipts for all transactions in a given block. +func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { + if receipts, ok := bc.receiptsCache.Get(hash); ok { + return receipts + } + number := rawdb.ReadHeaderNumber(bc.db, hash) + if number == nil { + return nil + } + header := bc.GetHeader(hash, *number) + if header == nil { + return nil + } + receipts := rawdb.ReadReceipts(bc.db, hash, *number, header.Time, bc.chainConfig) + if receipts == nil { + return nil + } + bc.receiptsCache.Add(hash, receipts) + return receipts +} + +// GetUnclesInChain retrieves all the uncles from a given block backwards until +// a specific distance is reached. +func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header { + uncles := []*types.Header{} + for i := 0; block != nil && i < length; i++ { + uncles = append(uncles, block.Uncles()...) + block = bc.GetBlock(block.ParentHash(), block.NumberU64()-1) + } + return uncles +} + +// GetCanonicalHash returns the canonical hash for a given block number +func (bc *BlockChain) 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 (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) { + return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical) +} + +// GetTransactionLookup retrieves the lookup along with the transaction +// itself associate with the given transaction hash. +// +// An error will be returned if the transaction is not found, and background +// indexing for transactions is still in progress. The transaction might be +// reachable shortly once it's indexed. +// +// A null will be returned in the transaction is not found and background +// transaction indexing is already finished. The transaction is not existent +// from the node's perspective. +func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) { + bc.txLookupLock.RLock() + defer bc.txLookupLock.RUnlock() + + // Short circuit if the txlookup already in the cache, retrieve otherwise + if item, exist := bc.txLookupCache.Get(hash); exist { + return item.lookup, item.transaction, nil + } + tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash) + if tx == nil { + progress, err := bc.TxIndexProgress() + if err != nil { + return nil, nil, nil + } + // The transaction indexing is not finished yet, returning an + // error to explicitly indicate it. + if !progress.Done() { + return nil, nil, errors.New("transaction indexing still in progress") + } + // The transaction is already indexed, the transaction is either + // not existent or not in the range of index, returning null. + return nil, nil, nil + } + lookup := &rawdb.LegacyTxLookupEntry{ + BlockHash: blockHash, + BlockIndex: blockNumber, + Index: txIndex, + } + bc.txLookupCache.Add(hash, txLookup{ + lookup: lookup, + transaction: tx, + }) + return lookup, tx, nil +} + +// GetTd retrieves a block's total difficulty in the canonical chain from the +// database by hash and number, caching it if found. +func (bc *BlockChain) GetTd(hash common.Hash, number uint64) *big.Int { + return bc.hc.GetTd(hash, number) +} + +// HasState checks if state trie is fully present in the database or not. +func (bc *BlockChain) HasState(hash common.Hash) bool { + _, err := bc.stateCache.OpenTrie(hash) + return err == nil +} + +// HasBlockAndState checks if a block and associated state trie is fully present +// in the database or not, caching it if present. +func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64) bool { + // Check first that the block itself is known + block := bc.GetBlock(hash, number) + if block == nil { + return false + } + return bc.HasState(block.Root()) +} + +// 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 +// hash either from ephemeral in-memory cache, or from persistent storage. +// +// If the code doesn't exist in the in-memory cache, check the storage with +// new code scheme. +func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) { + type codeReader interface { + ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) + } + // TODO(rjl493456442) The associated account address is also required + // in Verkle scheme. Fix it once snap-sync is supported for Verkle. + return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Address{}, hash) +} + +// State returns a new mutable state based on the current HEAD block. +func (bc *BlockChain) State() (*state.StateDB, error) { + return bc.StateAt(bc.CurrentBlock().Root) +} + +// StateAt returns a new mutable state based on a particular point in time. +func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) { + return state.New(root, bc.stateCache, bc.snaps) +} + +// Config retrieves the chain's fork configuration. +func (bc *BlockChain) Config() *params.ChainConfig { return bc.chainConfig } + +// Engine retrieves the blockchain's consensus engine. +func (bc *BlockChain) Engine() consensus.Engine { return bc.engine } + +// Snapshots returns the blockchain snapshot tree. +func (bc *BlockChain) Snapshots() *snapshot.Tree { + return bc.snaps +} + +// Validator returns the current validator. +func (bc *BlockChain) Validator() Validator { + return bc.validator +} + +// Processor returns the current processor. +func (bc *BlockChain) Processor() Processor { + return bc.processor +} + +// StateCache returns the caching database underpinning the blockchain instance. +func (bc *BlockChain) StateCache() state.Database { + return bc.stateCache +} + +// GasLimit returns the gas limit of the current HEAD block. +func (bc *BlockChain) GasLimit() uint64 { + return bc.CurrentBlock().GasLimit +} + +// Genesis retrieves the chain's genesis block. +func (bc *BlockChain) Genesis() *types.Block { + return bc.genesisBlock +} + +// GetVMConfig returns the block chain VM config. +func (bc *BlockChain) GetVMConfig() *vm.Config { + return &bc.vmConfig +} + +// TxIndexProgress returns the transaction indexing progress. +func (bc *BlockChain) TxIndexProgress() (TxIndexProgress, error) { + if bc.txIndexer == nil { + return TxIndexProgress{}, errors.New("tx indexer is not enabled") + } + return bc.txIndexer.txIndexProgress() +} + +// TrieDB retrieves the low level trie database used for data storage. +func (bc *BlockChain) TrieDB() *triedb.Database { + return bc.triedb +} + +// HeaderChain returns the underlying header chain. +func (bc *BlockChain) HeaderChain() *HeaderChain { + return bc.hc +} + +// SubscribeRemovedLogsEvent registers a subscription of RemovedLogsEvent. +func (bc *BlockChain) SubscribeRemovedLogsEvent(ch chan<- RemovedLogsEvent) event.Subscription { + return bc.scope.Track(bc.rmLogsFeed.Subscribe(ch)) +} + +// SubscribeChainEvent registers a subscription of ChainEvent. +func (bc *BlockChain) SubscribeChainEvent(ch chan<- ChainEvent) event.Subscription { + return bc.scope.Track(bc.chainFeed.Subscribe(ch)) +} + +// SubscribeChainHeadEvent registers a subscription of ChainHeadEvent. +func (bc *BlockChain) SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription { + return bc.scope.Track(bc.chainHeadFeed.Subscribe(ch)) +} + +// SubscribeChainSideEvent registers a subscription of ChainSideEvent. +func (bc *BlockChain) SubscribeChainSideEvent(ch chan<- ChainSideEvent) event.Subscription { + return bc.scope.Track(bc.chainSideFeed.Subscribe(ch)) +} + +// SubscribeLogsEvent registers a subscription of []*types.Log. +func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return bc.scope.Track(bc.logsFeed.Subscribe(ch)) +} + +// SubscribeBlockProcessingEvent registers a subscription of bool where true means +// block processing has started while false means it has stopped. +func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription { + return bc.scope.Track(bc.blockProcFeed.Subscribe(ch)) +} diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go new file mode 100644 index 0000000..a4761f3 --- /dev/null +++ b/core/blockchain_repair_test.go @@ -0,0 +1,2020 @@ +// 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 . + +// Tests that abnormal program termination (i.e.crash) and restart doesn't leave +// the database in some strange state with gaps in the chain, nor with block data +// dangling in the future. + +package core + +import ( + "math/big" + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" +) + +// Tests a recovery for a short canonical chain where a recent block was already +// committed to disk and then the process crashed. In this case we expect the full +// chain to be rolled back to the committed block, but the chain data itself left +// in the database for replaying. +func TestShortRepair(t *testing.T) { testShortRepair(t, false) } +func TestShortRepairWithSnapshots(t *testing.T) { testShortRepair(t, true) } + +func testShortRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Frozen: none + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 8, + expSidechainBlocks: 0, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a short canonical chain where the fast sync pivot point was +// already committed, after which the process crashed. In this case we expect the full +// chain to be rolled back to the committed block, but the chain data itself left in +// the database for replaying. +func TestShortSnapSyncedRepair(t *testing.T) { testShortSnapSyncedRepair(t, false) } +func TestShortSnapSyncedRepairWithSnapshots(t *testing.T) { testShortSnapSyncedRepair(t, true) } + +func testShortSnapSyncedRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Frozen: none + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 8, + expSidechainBlocks: 0, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a short canonical chain where the fast sync pivot point was +// not yet committed, but the process crashed. In this case we expect the chain to +// detect that it was fast syncing and not delete anything, since we can just pick +// up directly where we left off. +func TestShortSnapSyncingRepair(t *testing.T) { testShortSnapSyncingRepair(t, false) } +func TestShortSnapSyncingRepairWithSnapshots(t *testing.T) { testShortSnapSyncingRepair(t, true) } + +func testShortSnapSyncingRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Frozen: none + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 8, + expSidechainBlocks: 0, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a shorter side chain, where a +// recent block was already committed to disk and then the process crashed. In this +// test scenario the side chain is below the committed block. In this case we expect +// the canonical chain to be rolled back to the committed block, but the chain data +// itself left in the database for replaying. +func TestShortOldForkedRepair(t *testing.T) { testShortOldForkedRepair(t, false) } +func TestShortOldForkedRepairWithSnapshots(t *testing.T) { testShortOldForkedRepair(t, true) } + +func testShortOldForkedRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: none + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 8, + expSidechainBlocks: 3, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a shorter side chain, where +// the fast sync pivot point was already committed to disk and then the process +// crashed. In this test scenario the side chain is below the committed block. In +// this case we expect the canonical chain to be rolled back to the committed block, +// but the chain data itself left in the database for replaying. +func TestShortOldForkedSnapSyncedRepair(t *testing.T) { + testShortOldForkedSnapSyncedRepair(t, false) +} +func TestShortOldForkedSnapSyncedRepairWithSnapshots(t *testing.T) { + testShortOldForkedSnapSyncedRepair(t, true) +} + +func testShortOldForkedSnapSyncedRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: none + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 8, + expSidechainBlocks: 3, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a shorter side chain, where +// the fast sync pivot point was not yet committed, but the process crashed. In this +// test scenario the side chain is below the committed block. In this case we expect +// the chain to detect that it was fast syncing and not delete anything, since we +// can just pick up directly where we left off. +func TestShortOldForkedSnapSyncingRepair(t *testing.T) { + testShortOldForkedSnapSyncingRepair(t, false) +} +func TestShortOldForkedSnapSyncingRepairWithSnapshots(t *testing.T) { + testShortOldForkedSnapSyncingRepair(t, true) +} + +func testShortOldForkedSnapSyncingRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: none + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 8, + expSidechainBlocks: 3, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a shorter side chain, where a +// recent block was already committed to disk and then the process crashed. In this +// test scenario the side chain reaches above the committed block. In this case we +// expect the canonical chain to be rolled back to the committed block, but the +// chain data itself left in the database for replaying. +func TestShortNewlyForkedRepair(t *testing.T) { testShortNewlyForkedRepair(t, false) } +func TestShortNewlyForkedRepairWithSnapshots(t *testing.T) { testShortNewlyForkedRepair(t, true) } + +func testShortNewlyForkedRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6 + // + // Frozen: none + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3->S4->S5->S6 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 6, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 8, + expSidechainBlocks: 6, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a shorter side chain, where +// the fast sync pivot point was already committed to disk and then the process +// crashed. In this test scenario the side chain reaches above the committed block. +// In this case we expect the canonical chain to be rolled back to the committed +// block, but the chain data itself left in the database for replaying. +func TestShortNewlyForkedSnapSyncedRepair(t *testing.T) { + testShortNewlyForkedSnapSyncedRepair(t, false) +} +func TestShortNewlyForkedSnapSyncedRepairWithSnapshots(t *testing.T) { + testShortNewlyForkedSnapSyncedRepair(t, true) +} + +func testShortNewlyForkedSnapSyncedRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6 + // + // Frozen: none + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3->S4->S5->S6 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 6, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 8, + expSidechainBlocks: 6, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a shorter side chain, where +// the fast sync pivot point was not yet committed, but the process crashed. In +// this test scenario the side chain reaches above the committed block. In this +// case we expect the chain to detect that it was fast syncing and not delete +// anything, since we can just pick up directly where we left off. +func TestShortNewlyForkedSnapSyncingRepair(t *testing.T) { + testShortNewlyForkedSnapSyncingRepair(t, false) +} +func TestShortNewlyForkedSnapSyncingRepairWithSnapshots(t *testing.T) { + testShortNewlyForkedSnapSyncingRepair(t, true) +} + +func testShortNewlyForkedSnapSyncingRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6 + // + // Frozen: none + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3->S4->S5->S6 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 6, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 8, + expSidechainBlocks: 6, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a longer side chain, where a +// recent block was already committed to disk and then the process crashed. In this +// case we expect the canonical chain to be rolled back to the committed block, but +// the chain data itself left in the database for replaying. +func TestShortReorgedRepair(t *testing.T) { testShortReorgedRepair(t, false) } +func TestShortReorgedRepairWithSnapshots(t *testing.T) { testShortReorgedRepair(t, true) } + +func testShortReorgedRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Frozen: none + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 10, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 8, + expSidechainBlocks: 10, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a longer side chain, where +// the fast sync pivot point was already committed to disk and then the process +// crashed. In this case we expect the canonical chain to be rolled back to the +// committed block, but the chain data itself left in the database for replaying. +func TestShortReorgedSnapSyncedRepair(t *testing.T) { + testShortReorgedSnapSyncedRepair(t, false) +} +func TestShortReorgedSnapSyncedRepairWithSnapshots(t *testing.T) { + testShortReorgedSnapSyncedRepair(t, true) +} + +func testShortReorgedSnapSyncedRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Frozen: none + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 10, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 8, + expSidechainBlocks: 10, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a short canonical chain and a longer side chain, where +// the fast sync pivot point was not yet committed, but the process crashed. In +// this case we expect the chain to detect that it was fast syncing and not delete +// anything, since we can just pick up directly where we left off. +func TestShortReorgedSnapSyncingRepair(t *testing.T) { + testShortReorgedSnapSyncingRepair(t, false) +} +func TestShortReorgedSnapSyncingRepairWithSnapshots(t *testing.T) { + testShortReorgedSnapSyncingRepair(t, true) +} + +func testShortReorgedSnapSyncingRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Frozen: none + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 10, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 8, + expSidechainBlocks: 10, + expFrozen: 0, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks where a recent +// block - newer than the ancient limit - was already committed to disk and then +// the process crashed. In this case we expect the chain to be rolled back to the +// committed block, with everything afterwards kept as fast sync data. +func TestLongShallowRepair(t *testing.T) { testLongShallowRepair(t, false) } +func TestLongShallowRepairWithSnapshots(t *testing.T) { testLongShallowRepair(t, true) } + +func testLongShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks where a recent +// block - older than the ancient limit - was already committed to disk and then +// the process crashed. In this case we expect the chain to be rolled back to the +// committed block, with everything afterwards deleted. +func TestLongDeepRepair(t *testing.T) { testLongDeepRepair(t, false) } +func TestLongDeepRepairWithSnapshots(t *testing.T) { testLongDeepRepair(t, true) } + +func testLongDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks where the fast +// sync pivot point - newer than the ancient limit - was already committed, after +// which the process crashed. In this case we expect the chain to be rolled back +// to the committed block, with everything afterwards kept as fast sync data. +func TestLongSnapSyncedShallowRepair(t *testing.T) { + testLongSnapSyncedShallowRepair(t, false) +} +func TestLongSnapSyncedShallowRepairWithSnapshots(t *testing.T) { + testLongSnapSyncedShallowRepair(t, true) +} + +func testLongSnapSyncedShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks where the fast +// sync pivot point - older than the ancient limit - was already committed, after +// which the process crashed. In this case we expect the chain to be rolled back +// to the committed block, with everything afterwards deleted. +func TestLongSnapSyncedDeepRepair(t *testing.T) { testLongSnapSyncedDeepRepair(t, false) } +func TestLongSnapSyncedDeepRepairWithSnapshots(t *testing.T) { testLongSnapSyncedDeepRepair(t, true) } + +func testLongSnapSyncedDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks where the fast +// sync pivot point - older than the ancient limit - was not yet committed, but the +// process crashed. In this case we expect the chain to detect that it was fast +// syncing and not delete anything, since we can just pick up directly where we +// left off. +func TestLongSnapSyncingShallowRepair(t *testing.T) { + testLongSnapSyncingShallowRepair(t, false) +} +func TestLongSnapSyncingShallowRepairWithSnapshots(t *testing.T) { + testLongSnapSyncingShallowRepair(t, true) +} + +func testLongSnapSyncingShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // + // Frozen: + // G->C1->C2 + // + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks where the fast +// sync pivot point - newer than the ancient limit - was not yet committed, but the +// process crashed. In this case we expect the chain to detect that it was fast +// syncing and not delete anything, since we can just pick up directly where we +// left off. +func TestLongSnapSyncingDeepRepair(t *testing.T) { testLongSnapSyncingDeepRepair(t, false) } +func TestLongSnapSyncingDeepRepairWithSnapshots(t *testing.T) { testLongSnapSyncingDeepRepair(t, true) } + +func testLongSnapSyncingDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected in leveldb: + // C8)->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 + // + // Expected head header : C24 + // Expected head fast block: C24 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 24, + expSidechainBlocks: 0, + expFrozen: 9, + expHeadHeader: 24, + expHeadFastBlock: 24, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where a recent block - newer than the ancient limit - was already +// committed to disk and then the process crashed. In this test scenario the side +// chain is below the committed block. In this case we expect the chain to be +// rolled back to the committed block, with everything afterwards kept as fast +// sync data; the side chain completely nuked by the freezer. +func TestLongOldForkedShallowRepair(t *testing.T) { + testLongOldForkedShallowRepair(t, false) +} +func TestLongOldForkedShallowRepairWithSnapshots(t *testing.T) { + testLongOldForkedShallowRepair(t, true) +} + +func testLongOldForkedShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where a recent block - older than the ancient limit - was already +// committed to disk and then the process crashed. In this test scenario the side +// chain is below the committed block. In this case we expect the canonical chain +// to be rolled back to the committed block, with everything afterwards deleted; +// the side chain completely nuked by the freezer. +func TestLongOldForkedDeepRepair(t *testing.T) { testLongOldForkedDeepRepair(t, false) } +func TestLongOldForkedDeepRepairWithSnapshots(t *testing.T) { testLongOldForkedDeepRepair(t, true) } + +func testLongOldForkedDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was already committed to disk and then the process crashed. In this test scenario +// the side chain is below the committed block. In this case we expect the chain +// to be rolled back to the committed block, with everything afterwards kept as +// fast sync data; the side chain completely nuked by the freezer. +func TestLongOldForkedSnapSyncedShallowRepair(t *testing.T) { + testLongOldForkedSnapSyncedShallowRepair(t, false) +} +func TestLongOldForkedSnapSyncedShallowRepairWithSnapshots(t *testing.T) { + testLongOldForkedSnapSyncedShallowRepair(t, true) +} + +func testLongOldForkedSnapSyncedShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was already committed to disk and then the process crashed. In this test scenario +// the side chain is below the committed block. In this case we expect the canonical +// chain to be rolled back to the committed block, with everything afterwards deleted; +// the side chain completely nuked by the freezer. +func TestLongOldForkedSnapSyncedDeepRepair(t *testing.T) { + testLongOldForkedSnapSyncedDeepRepair(t, false) +} +func TestLongOldForkedSnapSyncedDeepRepairWithSnapshots(t *testing.T) { + testLongOldForkedSnapSyncedDeepRepair(t, true) +} + +func testLongOldForkedSnapSyncedDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was not yet committed, but the process crashed. In this test scenario the side +// chain is below the committed block. In this case we expect the chain to detect +// that it was fast syncing and not delete anything. The side chain is completely +// nuked by the freezer. +func TestLongOldForkedSnapSyncingShallowRepair(t *testing.T) { + testLongOldForkedSnapSyncingShallowRepair(t, false) +} +func TestLongOldForkedSnapSyncingShallowRepairWithSnapshots(t *testing.T) { + testLongOldForkedSnapSyncingShallowRepair(t, true) +} + +func testLongOldForkedSnapSyncingShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2 + // + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was not yet committed, but the process crashed. In this test scenario the side +// chain is below the committed block. In this case we expect the chain to detect +// that it was fast syncing and not delete anything. The side chain is completely +// nuked by the freezer. +func TestLongOldForkedSnapSyncingDeepRepair(t *testing.T) { + testLongOldForkedSnapSyncingDeepRepair(t, false) +} +func TestLongOldForkedSnapSyncingDeepRepairWithSnapshots(t *testing.T) { + testLongOldForkedSnapSyncingDeepRepair(t, true) +} + +func testLongOldForkedSnapSyncingDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected in leveldb: + // C8)->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 + // + // Expected head header : C24 + // Expected head fast block: C24 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 24, + expSidechainBlocks: 0, + expFrozen: 9, + expHeadHeader: 24, + expHeadFastBlock: 24, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where a recent block - newer than the ancient limit - was already +// committed to disk and then the process crashed. In this test scenario the side +// chain is above the committed block. In this case we expect the chain to be +// rolled back to the committed block, with everything afterwards kept as fast +// sync data; the side chain completely nuked by the freezer. +func TestLongNewerForkedShallowRepair(t *testing.T) { + testLongNewerForkedShallowRepair(t, false) +} +func TestLongNewerForkedShallowRepairWithSnapshots(t *testing.T) { + testLongNewerForkedShallowRepair(t, true) +} + +func testLongNewerForkedShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where a recent block - older than the ancient limit - was already +// committed to disk and then the process crashed. In this test scenario the side +// chain is above the committed block. In this case we expect the canonical chain +// to be rolled back to the committed block, with everything afterwards deleted; +// the side chain completely nuked by the freezer. +func TestLongNewerForkedDeepRepair(t *testing.T) { testLongNewerForkedDeepRepair(t, false) } +func TestLongNewerForkedDeepRepairWithSnapshots(t *testing.T) { testLongNewerForkedDeepRepair(t, true) } + +func testLongNewerForkedDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was already committed to disk and then the process crashed. In this test scenario +// the side chain is above the committed block. In this case we expect the chain +// to be rolled back to the committed block, with everything afterwards kept as fast +// sync data; the side chain completely nuked by the freezer. +func TestLongNewerForkedSnapSyncedShallowRepair(t *testing.T) { + testLongNewerForkedSnapSyncedShallowRepair(t, false) +} +func TestLongNewerForkedSnapSyncedShallowRepairWithSnapshots(t *testing.T) { + testLongNewerForkedSnapSyncedShallowRepair(t, true) +} + +func testLongNewerForkedSnapSyncedShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was already committed to disk and then the process crashed. In this test scenario +// the side chain is above the committed block. In this case we expect the canonical +// chain to be rolled back to the committed block, with everything afterwards deleted; +// the side chain completely nuked by the freezer. +func TestLongNewerForkedSnapSyncedDeepRepair(t *testing.T) { + testLongNewerForkedSnapSyncedDeepRepair(t, false) +} +func TestLongNewerForkedSnapSyncedDeepRepairWithSnapshots(t *testing.T) { + testLongNewerForkedSnapSyncedDeepRepair(t, true) +} + +func testLongNewerForkedSnapSyncedDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was not yet committed, but the process crashed. In this test scenario the side +// chain is above the committed block. In this case we expect the chain to detect +// that it was fast syncing and not delete anything. The side chain is completely +// nuked by the freezer. +func TestLongNewerForkedSnapSyncingShallowRepair(t *testing.T) { + testLongNewerForkedSnapSyncingShallowRepair(t, false) +} +func TestLongNewerForkedSnapSyncingShallowRepairWithSnapshots(t *testing.T) { + testLongNewerForkedSnapSyncingShallowRepair(t, true) +} + +func testLongNewerForkedSnapSyncingShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2 + // + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was not yet committed, but the process crashed. In this test scenario the side +// chain is above the committed block. In this case we expect the chain to detect +// that it was fast syncing and not delete anything. The side chain is completely +// nuked by the freezer. +func TestLongNewerForkedSnapSyncingDeepRepair(t *testing.T) { + testLongNewerForkedSnapSyncingDeepRepair(t, false) +} +func TestLongNewerForkedSnapSyncingDeepRepairWithSnapshots(t *testing.T) { + testLongNewerForkedSnapSyncingDeepRepair(t, true) +} + +func testLongNewerForkedSnapSyncingDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected in leveldb: + // C8)->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 + // + // Expected head header : C24 + // Expected head fast block: C24 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 24, + expSidechainBlocks: 0, + expFrozen: 9, + expHeadHeader: 24, + expHeadFastBlock: 24, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a longer side +// chain, where a recent block - newer than the ancient limit - was already committed +// to disk and then the process crashed. In this case we expect the chain to be +// rolled back to the committed block, with everything afterwards kept as fast sync +// data. The side chain completely nuked by the freezer. +func TestLongReorgedShallowRepair(t *testing.T) { testLongReorgedShallowRepair(t, false) } +func TestLongReorgedShallowRepairWithSnapshots(t *testing.T) { testLongReorgedShallowRepair(t, true) } + +func testLongReorgedShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a longer side +// chain, where a recent block - older than the ancient limit - was already committed +// to disk and then the process crashed. In this case we expect the canonical chains +// to be rolled back to the committed block, with everything afterwards deleted. The +// side chain completely nuked by the freezer. +func TestLongReorgedDeepRepair(t *testing.T) { testLongReorgedDeepRepair(t, false) } +func TestLongReorgedDeepRepairWithSnapshots(t *testing.T) { testLongReorgedDeepRepair(t, true) } + +func testLongReorgedDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : none + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a longer +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was already committed to disk and then the process crashed. In this case we +// expect the chain to be rolled back to the committed block, with everything +// afterwards kept as fast sync data. The side chain completely nuked by the +// freezer. +func TestLongReorgedSnapSyncedShallowRepair(t *testing.T) { + testLongReorgedSnapSyncedShallowRepair(t, false) +} +func TestLongReorgedSnapSyncedShallowRepairWithSnapshots(t *testing.T) { + testLongReorgedSnapSyncedShallowRepair(t, true) +} + +func testLongReorgedSnapSyncedShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a longer +// side chain, where the fast sync pivot point - older than the ancient limit - +// was already committed to disk and then the process crashed. In this case we +// expect the canonical chains to be rolled back to the committed block, with +// everything afterwards deleted. The side chain completely nuked by the freezer. +func TestLongReorgedSnapSyncedDeepRepair(t *testing.T) { + testLongReorgedSnapSyncedDeepRepair(t, false) +} +func TestLongReorgedSnapSyncedDeepRepairWithSnapshots(t *testing.T) { + testLongReorgedSnapSyncedDeepRepair(t, true) +} + +func testLongReorgedSnapSyncedDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a longer +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was not yet committed, but the process crashed. In this case we expect the +// chain to detect that it was fast syncing and not delete anything, since we +// can just pick up directly where we left off. +func TestLongReorgedSnapSyncingShallowRepair(t *testing.T) { + testLongReorgedSnapSyncingShallowRepair(t, false) +} +func TestLongReorgedSnapSyncingShallowRepairWithSnapshots(t *testing.T) { + testLongReorgedSnapSyncingShallowRepair(t, true) +} + +func testLongReorgedSnapSyncingShallowRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2 + // + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 + // + // Expected head header : C18 + // Expected head fast block: C18 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 18, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 18, + expHeadFastBlock: 18, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a recovery for a long canonical chain with frozen blocks and a longer +// side chain, where the fast sync pivot point - older than the ancient limit - +// was not yet committed, but the process crashed. In this case we expect the +// chain to detect that it was fast syncing and not delete anything, since we +// can just pick up directly where we left off. +func TestLongReorgedSnapSyncingDeepRepair(t *testing.T) { + testLongReorgedSnapSyncingDeepRepair(t, false) +} +func TestLongReorgedSnapSyncingDeepRepairWithSnapshots(t *testing.T) { + testLongReorgedSnapSyncingDeepRepair(t, true) +} + +func testLongReorgedSnapSyncingDeepRepair(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G + // Pivot : C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected in leveldb: + // C8)->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 + // + // Expected head header : C24 + // Expected head fast block: C24 + // Expected head block : G + testRepair(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + expCanonicalBlocks: 24, + expSidechainBlocks: 0, + expFrozen: 9, + expHeadHeader: 24, + expHeadFastBlock: 24, + expHeadBlock: 0, + }, snapshots) +} + +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 := filepath.Join(datadir, "ancient") + + db, err := rawdb.Open(rawdb.OpenOptions{ + Directory: datadir, + AncientsDirectory: ancient, + Ephemeral: true, + }) + if err != nil { + t.Fatalf("Failed to create persistent database: %v", err) + } + defer db.Close() // Might double close, should be fine + + // Initialize a fresh chain + var ( + gspec = &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.AllEthashProtocolChanges, + } + engine = ethash.NewFullFaker() + config = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, // Disable snapshot by default + StateScheme: scheme, + } + ) + defer engine.Close() + if snapshots { + config.SnapshotLimit = 256 + config.SnapshotWait = true + } + chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to create chain: %v", err) + } + // If sidechain blocks are needed, make a light chain and import it + var sideblocks types.Blocks + if tt.sidechainBlocks > 0 { + sideblocks, _ = GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{0x01}) + }) + if _, err := chain.InsertChain(sideblocks); err != nil { + t.Fatalf("Failed to import side chain: %v", err) + } + } + canonblocks, _ := GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{0x02}) + b.SetDifficulty(big.NewInt(1000000)) + }) + if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + if tt.commitBlock > 0 { + 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) + } + } + } + if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil { + t.Fatalf("Failed to import canonical chain tail: %v", err) + } + // Force run a freeze cycle + type freezer interface { + Freeze() error + Ancients() (uint64, error) + } + if tt.freezeThreshold < uint64(tt.canonicalBlocks) { + final := uint64(tt.canonicalBlocks) - tt.freezeThreshold + chain.SetFinalized(canonblocks[int(final)-1].Header()) + } + db.(freezer).Freeze() + + // Set the simulated pivot block + if tt.pivotBlock != nil { + 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: ancient, + Ephemeral: true, + }) + if err != nil { + t.Fatalf("Failed to reopen persistent database: %v", err) + } + defer db.Close() + + newChain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newChain.Stop() + + // Iterate over all the remaining blocks and ensure there are no gaps + verifyNoGaps(t, newChain, true, canonblocks) + verifyNoGaps(t, newChain, false, sideblocks) + verifyCutoff(t, newChain, true, canonblocks, tt.expCanonicalBlocks) + verifyCutoff(t, newChain, false, sideblocks, tt.expSidechainBlocks) + + if head := newChain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader { + t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadHeader) + } + if head := newChain.CurrentSnapBlock(); head.Number.Uint64() != tt.expHeadFastBlock { + t.Errorf("Head fast block mismatch: have %d, want %d", head.Number, tt.expHeadFastBlock) + } + if head := newChain.CurrentBlock(); head.Number.Uint64() != tt.expHeadBlock { + t.Errorf("Head block mismatch: have %d, want %d", head.Number, tt.expHeadBlock) + } + if frozen, err := db.(freezer).Ancients(); err != nil { + t.Errorf("Failed to retrieve ancient count: %v\n", err) + } else if int(frozen) != tt.expFrozen { + t.Errorf("Frozen block count mismatch: have %d, want %d", frozen, tt.expFrozen) + } +} + +// TestIssue23496 tests scenario described in https://github.com/ethereum/go-ethereum/pull/23496#issuecomment-926393893 +// Credits to @zzyalbert for finding the issue. +// +// Local chain owns these blocks: +// G B1 B2 B3 B4 +// B1: state committed +// B2: snapshot disk layer +// B3: state committed +// B4: head block +// +// Crash happens without fully persisting snapshot and in-memory states, +// chain rewinds itself to the B1 (skip B3 in order to recover snapshot) +// 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 := filepath.Join(datadir, "ancient") + + db, err := rawdb.Open(rawdb.OpenOptions{ + Directory: datadir, + AncientsDirectory: ancient, + }) + if err != nil { + t.Fatalf("Failed to create persistent database: %v", err) + } + defer db.Close() // Might double close, should be fine + + // Initialize a fresh chain + var ( + gspec = &Genesis{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + engine = ethash.NewFullFaker() + ) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to create chain: %v", err) + } + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 4, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{0x02}) + b.SetDifficulty(big.NewInt(1000000)) + }) + + // Insert block B1 and commit the state into disk + if _, err := chain.InsertChain(blocks[:1]); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + 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 { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + if err := chain.snaps.Cap(blocks[1].Root(), 0); err != nil { + t.Fatalf("Failed to flatten snapshots: %v", err) + } + + // Insert block B3 and commit the state into disk + if _, err := chain.InsertChain(blocks[2:3]); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + chain.triedb.Commit(blocks[2].Root(), false) + + // Insert the remaining blocks + if _, err := chain.InsertChain(blocks[3:]); err != nil { + t.Fatalf("Failed to import canonical chain tail: %v", err) + } + + // 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: ancient, + Ephemeral: true, + }) + if err != nil { + t.Fatalf("Failed to reopen persistent database: %v", err) + } + defer db.Close() + + chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer chain.Stop() + + if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) { + t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4) + } + if head := chain.CurrentSnapBlock(); head.Number.Uint64() != uint64(4) { + t.Errorf("Head fast block mismatch: have %d, want %d", head.Number, uint64(4)) + } + 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 + if _, err := chain.InsertChain(blocks[1:]); err != nil { + t.Fatalf("Failed to import canonical chain tail: %v", err) + } + if head := chain.CurrentHeader(); head.Number.Uint64() != uint64(4) { + t.Errorf("Head header mismatch: have %d, want %d", head.Number, 4) + } + 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(4) { + t.Errorf("Head block mismatch: have %d, want %d", head.Number, uint64(4)) + } + if layer := chain.Snapshots().Snapshot(blocks[2].Root()); layer == nil { + t.Error("Failed to regenerate the snapshot of known state") + } +} diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go new file mode 100644 index 0000000..8b77f9f --- /dev/null +++ b/core/blockchain_sethead_test.go @@ -0,0 +1,2194 @@ +// 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 . + +// Tests that setting the chain head backwards doesn't leave the database in some +// strange state with gaps in the chain, nor with block data dangling in the future. + +package core + +import ( + "fmt" + "math/big" + "path/filepath" + "strings" + "testing" + "time" + + "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/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" +) + +// rewindTest is a test case for chain rollback upon user request. +type rewindTest struct { + canonicalBlocks int // Number of blocks to generate for the canonical chain (heavier) + sidechainBlocks int // Number of blocks to generate for the side chain (lighter) + freezeThreshold uint64 // Block number until which to move things into the freezer + commitBlock uint64 // Block number for which to commit the state to disk + pivotBlock *uint64 // Pivot block number in case of fast sync + + setheadBlock uint64 // Block number to set head back to + expCanonicalBlocks int // Number of canonical blocks expected to remain in the database (excl. genesis) + expSidechainBlocks int // Number of sidechain blocks expected to remain in the database (excl. genesis) + expFrozen int // Number of canonical blocks expected to be in the freezer (incl. genesis) + expHeadHeader uint64 // Block number of the expected head header + expHeadFastBlock uint64 // Block number of the expected head fast sync block + expHeadBlock uint64 // Block number of the expected head full block +} + +//nolint:unused +func (tt *rewindTest) dump(crash bool) string { + buffer := new(strings.Builder) + + fmt.Fprint(buffer, "Chain:\n G") + for i := 0; i < tt.canonicalBlocks; i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprint(buffer, " (HEAD)\n") + if tt.sidechainBlocks > 0 { + fmt.Fprintf(buffer, " â””") + for i := 0; i < tt.sidechainBlocks; i++ { + fmt.Fprintf(buffer, "->S%d", i+1) + } + fmt.Fprintf(buffer, "\n") + } + fmt.Fprintf(buffer, "\n") + + if tt.canonicalBlocks > int(tt.freezeThreshold) { + fmt.Fprint(buffer, "Frozen:\n G") + for i := 0; i < tt.canonicalBlocks-int(tt.freezeThreshold); i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprintf(buffer, "\n\n") + } else { + fmt.Fprintf(buffer, "Frozen: none\n") + } + fmt.Fprintf(buffer, "Commit: G") + if tt.commitBlock > 0 { + fmt.Fprintf(buffer, ", C%d", tt.commitBlock) + } + fmt.Fprint(buffer, "\n") + + if tt.pivotBlock == nil { + fmt.Fprintf(buffer, "Pivot : none\n") + } else { + fmt.Fprintf(buffer, "Pivot : C%d\n", *tt.pivotBlock) + } + if crash { + fmt.Fprintf(buffer, "\nCRASH\n\n") + } else { + fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", tt.setheadBlock) + } + fmt.Fprintf(buffer, "------------------------------\n\n") + + if tt.expFrozen > 0 { + fmt.Fprint(buffer, "Expected in freezer:\n G") + for i := 0; i < tt.expFrozen-1; i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprintf(buffer, "\n\n") + } + if tt.expFrozen > 0 { + if tt.expFrozen >= tt.expCanonicalBlocks { + fmt.Fprintf(buffer, "Expected in leveldb: none\n") + } else { + fmt.Fprintf(buffer, "Expected in leveldb:\n C%d)", tt.expFrozen-1) + for i := tt.expFrozen - 1; i < tt.expCanonicalBlocks; i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprint(buffer, "\n") + if tt.expSidechainBlocks > tt.expFrozen { + fmt.Fprintf(buffer, " â””") + for i := tt.expFrozen - 1; i < tt.expSidechainBlocks; i++ { + fmt.Fprintf(buffer, "->S%d", i+1) + } + fmt.Fprintf(buffer, "\n") + } + } + } else { + fmt.Fprint(buffer, "Expected in leveldb:\n G") + for i := tt.expFrozen; i < tt.expCanonicalBlocks; i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprint(buffer, "\n") + if tt.expSidechainBlocks > tt.expFrozen { + fmt.Fprintf(buffer, " â””") + for i := tt.expFrozen; i < tt.expSidechainBlocks; i++ { + fmt.Fprintf(buffer, "->S%d", i+1) + } + fmt.Fprintf(buffer, "\n") + } + } + fmt.Fprintf(buffer, "\n") + fmt.Fprintf(buffer, "Expected head header : C%d\n", tt.expHeadHeader) + fmt.Fprintf(buffer, "Expected head fast block: C%d\n", tt.expHeadFastBlock) + if tt.expHeadBlock == 0 { + fmt.Fprintf(buffer, "Expected head block : G\n") + } else { + fmt.Fprintf(buffer, "Expected head block : C%d\n", tt.expHeadBlock) + } + return buffer.String() +} + +// Tests a sethead for a short canonical chain where a recent block was already +// committed to disk and then the sethead called. In this case we expect the full +// chain to be rolled back to the committed block. Everything above the sethead +// point should be deleted. In between the committed block and the requested head +// the data can remain as "fast sync" data to avoid redownloading it. +func TestShortSetHead(t *testing.T) { testShortSetHead(t, false) } +func TestShortSetHeadWithSnapshots(t *testing.T) { testShortSetHead(t, true) } + +func testShortSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Frozen: none + // Commit: G, C4 + // Pivot : none + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 0, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a short canonical chain where the fast sync pivot point was +// already committed, after which sethead was called. In this case we expect the +// chain to behave like in full sync mode, rolling back to the committed block +// Everything above the sethead point should be deleted. In between the committed +// block and the requested head the data can remain as "fast sync" data to avoid +// redownloading it. +func TestShortSnapSyncedSetHead(t *testing.T) { testShortSnapSyncedSetHead(t, false) } +func TestShortSnapSyncedSetHeadWithSnapshots(t *testing.T) { testShortSnapSyncedSetHead(t, true) } + +func testShortSnapSyncedSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Frozen: none + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 0, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a short canonical chain where the fast sync pivot point was +// not yet committed, but sethead was called. In this case we expect the chain to +// detect that it was fast syncing and delete everything from the new head, since +// we can just pick up fast syncing from there. The head full block should be set +// to the genesis. +func TestShortSnapSyncingSetHead(t *testing.T) { testShortSnapSyncingSetHead(t, false) } +func TestShortSnapSyncingSetHeadWithSnapshots(t *testing.T) { testShortSnapSyncingSetHead(t, true) } + +func testShortSnapSyncingSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Frozen: none + // Commit: G + // Pivot : C4 + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 0, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a shorter side chain, where a +// recent block was already committed to disk and then sethead was called. In this +// test scenario the side chain is below the committed block. In this case we expect +// the canonical full chain to be rolled back to the committed block. Everything +// above the sethead point should be deleted. In between the committed block and +// the requested head the data can remain as "fast sync" data to avoid redownloading +// it. The side chain should be left alone as it was shorter. +func TestShortOldForkedSetHead(t *testing.T) { testShortOldForkedSetHead(t, false) } +func TestShortOldForkedSetHeadWithSnapshots(t *testing.T) { testShortOldForkedSetHead(t, true) } + +func testShortOldForkedSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: none + // Commit: G, C4 + // Pivot : none + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 3, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a shorter side chain, where +// the fast sync pivot point was already committed to disk and then sethead was +// called. In this test scenario the side chain is below the committed block. In +// this case we expect the canonical full chain to be rolled back to the committed +// block. Everything above the sethead point should be deleted. In between the +// committed block and the requested head the data can remain as "fast sync" data +// to avoid redownloading it. The side chain should be left alone as it was shorter. +func TestShortOldForkedSnapSyncedSetHead(t *testing.T) { + testShortOldForkedSnapSyncedSetHead(t, false) +} +func TestShortOldForkedSnapSyncedSetHeadWithSnapshots(t *testing.T) { + testShortOldForkedSnapSyncedSetHead(t, true) +} + +func testShortOldForkedSnapSyncedSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: none + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 3, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a shorter side chain, where +// the fast sync pivot point was not yet committed, but sethead was called. In this +// test scenario the side chain is below the committed block. In this case we expect +// the chain to detect that it was fast syncing and delete everything from the new +// head, since we can just pick up fast syncing from there. The head full block +// should be set to the genesis. +func TestShortOldForkedSnapSyncingSetHead(t *testing.T) { + testShortOldForkedSnapSyncingSetHead(t, false) +} +func TestShortOldForkedSnapSyncingSetHeadWithSnapshots(t *testing.T) { + testShortOldForkedSnapSyncingSetHead(t, true) +} + +func testShortOldForkedSnapSyncingSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: none + // Commit: G + // Pivot : C4 + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 3, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a shorter side chain, where a +// recent block was already committed to disk and then sethead was called. In this +// test scenario the side chain reaches above the committed block. In this case we +// expect the canonical full chain to be rolled back to the committed block. All +// data above the sethead point should be deleted. In between the committed block +// and the requested head the data can remain as "fast sync" data to avoid having +// to redownload it. The side chain should be truncated to the head set. +// +// The side chain could be left to be if the fork point was before the new head +// we are deleting to, but it would be exceedingly hard to detect that case and +// properly handle it, so we'll trade extra work in exchange for simpler code. +func TestShortNewlyForkedSetHead(t *testing.T) { testShortNewlyForkedSetHead(t, false) } +func TestShortNewlyForkedSetHeadWithSnapshots(t *testing.T) { testShortNewlyForkedSetHead(t, true) } + +func testShortNewlyForkedSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8 + // + // Frozen: none + // Commit: G, C4 + // Pivot : none + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3->S4->S5->S6->S7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 10, + sidechainBlocks: 8, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 7, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a shorter side chain, where +// the fast sync pivot point was already committed to disk and then sethead was +// called. In this case we expect the canonical full chain to be rolled back to +// between the committed block and the requested head the data can remain as +// "fast sync" data to avoid having to redownload it. The side chain should be +// truncated to the head set. +// +// The side chain could be left to be if the fork point was before the new head +// we are deleting to, but it would be exceedingly hard to detect that case and +// properly handle it, so we'll trade extra work in exchange for simpler code. +func TestShortNewlyForkedSnapSyncedSetHead(t *testing.T) { + testShortNewlyForkedSnapSyncedSetHead(t, false) +} +func TestShortNewlyForkedSnapSyncedSetHeadWithSnapshots(t *testing.T) { + testShortNewlyForkedSnapSyncedSetHead(t, true) +} + +func testShortNewlyForkedSnapSyncedSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8 + // + // Frozen: none + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3->S4->S5->S6->S7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 10, + sidechainBlocks: 8, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 7, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a shorter side chain, where +// the fast sync pivot point was not yet committed, but sethead was called. In +// this test scenario the side chain reaches above the committed block. In this +// case we expect the chain to detect that it was fast syncing and delete +// everything from the new head, since we can just pick up fast syncing from +// there. +// +// The side chain could be left to be if the fork point was before the new head +// we are deleting to, but it would be exceedingly hard to detect that case and +// properly handle it, so we'll trade extra work in exchange for simpler code. +func TestShortNewlyForkedSnapSyncingSetHead(t *testing.T) { + testShortNewlyForkedSnapSyncingSetHead(t, false) +} +func TestShortNewlyForkedSnapSyncingSetHeadWithSnapshots(t *testing.T) { + testShortNewlyForkedSnapSyncingSetHead(t, true) +} + +func testShortNewlyForkedSnapSyncingSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8 + // + // Frozen: none + // Commit: G + // Pivot : C4 + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3->S4->S5->S6->S7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 10, + sidechainBlocks: 8, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 7, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a longer side chain, where a +// recent block was already committed to disk and then sethead was called. In this +// case we expect the canonical full chain to be rolled back to the committed block. +// All data above the sethead point should be deleted. In between the committed +// block and the requested head the data can remain as "fast sync" data to avoid +// having to redownload it. The side chain should be truncated to the head set. +// +// The side chain could be left to be if the fork point was before the new head +// we are deleting to, but it would be exceedingly hard to detect that case and +// properly handle it, so we'll trade extra work in exchange for simpler code. +func TestShortReorgedSetHead(t *testing.T) { testShortReorgedSetHead(t, false) } +func TestShortReorgedSetHeadWithSnapshots(t *testing.T) { testShortReorgedSetHead(t, true) } + +func testShortReorgedSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Frozen: none + // Commit: G, C4 + // Pivot : none + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3->S4->S5->S6->S7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 10, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 7, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a longer side chain, where +// the fast sync pivot point was already committed to disk and then sethead was +// called. In this case we expect the canonical full chain to be rolled back to +// the committed block. All data above the sethead point should be deleted. In +// between the committed block and the requested head the data can remain as +// "fast sync" data to avoid having to redownload it. The side chain should be +// truncated to the head set. +// +// The side chain could be left to be if the fork point was before the new head +// we are deleting to, but it would be exceedingly hard to detect that case and +// properly handle it, so we'll trade extra work in exchange for simpler code. +func TestShortReorgedSnapSyncedSetHead(t *testing.T) { + testShortReorgedSnapSyncedSetHead(t, false) +} +func TestShortReorgedSnapSyncedSetHeadWithSnapshots(t *testing.T) { + testShortReorgedSnapSyncedSetHead(t, true) +} + +func testShortReorgedSnapSyncedSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Frozen: none + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3->S4->S5->S6->S7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 10, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 7, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a short canonical chain and a longer side chain, where +// the fast sync pivot point was not yet committed, but sethead was called. In +// this case we expect the chain to detect that it was fast syncing and delete +// everything from the new head, since we can just pick up fast syncing from +// there. +// +// The side chain could be left to be if the fork point was before the new head +// we are deleting to, but it would be exceedingly hard to detect that case and +// properly handle it, so we'll trade extra work in exchange for simpler code. +func TestShortReorgedSnapSyncingSetHead(t *testing.T) { + testShortReorgedSnapSyncingSetHead(t, false) +} +func TestShortReorgedSnapSyncingSetHeadWithSnapshots(t *testing.T) { + testShortReorgedSnapSyncingSetHead(t, true) +} + +func testShortReorgedSnapSyncingSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 + // + // Frozen: none + // Commit: G + // Pivot : C4 + // + // SetHead(7) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7 + // â””->S1->S2->S3->S4->S5->S6->S7 + // + // Expected head header : C7 + // Expected head fast block: C7 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 8, + sidechainBlocks: 10, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 7, + expCanonicalBlocks: 7, + expSidechainBlocks: 7, + expFrozen: 0, + expHeadHeader: 7, + expHeadFastBlock: 7, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks where a recent +// block - newer than the ancient limit - was already committed to disk and then +// sethead was called. In this case we expect the full chain to be rolled back +// to the committed block. Everything above the sethead point should be deleted. +// In between the committed block and the requested head the data can remain as +// "fast sync" data to avoid redownloading it. +func TestLongShallowSetHead(t *testing.T) { testLongShallowSetHead(t, false) } +func TestLongShallowSetHeadWithSnapshots(t *testing.T) { testLongShallowSetHead(t, true) } + +func testLongShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : none + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks where a recent +// block - older than the ancient limit - was already committed to disk and then +// sethead was called. In this case we expect the full chain to be rolled back +// to the committed block. Since the ancient limit was underflown, everything +// needs to be deleted onwards to avoid creating a gap. +func TestLongDeepSetHead(t *testing.T) { testLongDeepSetHead(t, false) } +func TestLongDeepSetHeadWithSnapshots(t *testing.T) { testLongDeepSetHead(t, true) } + +func testLongDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : none + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 6, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks where the fast +// sync pivot point - newer than the ancient limit - was already committed, after +// which sethead was called. In this case we expect the full chain to be rolled +// back to the committed block. Everything above the sethead point should be +// deleted. In between the committed block and the requested head the data can +// remain as "fast sync" data to avoid redownloading it. +func TestLongSnapSyncedShallowSetHead(t *testing.T) { + testLongSnapSyncedShallowSetHead(t, false) +} +func TestLongSnapSyncedShallowSetHeadWithSnapshots(t *testing.T) { + testLongSnapSyncedShallowSetHead(t, true) +} + +func testLongSnapSyncedShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks where the fast +// sync pivot point - older than the ancient limit - was already committed, after +// which sethead was called. In this case we expect the full chain to be rolled +// back to the committed block. Since the ancient limit was underflown, everything +// needs to be deleted onwards to avoid creating a gap. +func TestLongSnapSyncedDeepSetHead(t *testing.T) { testLongSnapSyncedDeepSetHead(t, false) } +func TestLongSnapSyncedDeepSetHeadWithSnapshots(t *testing.T) { testLongSnapSyncedDeepSetHead(t, true) } + +func testLongSnapSyncedDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks where the fast +// sync pivot point - newer than the ancient limit - was not yet committed, but +// sethead was called. In this case we expect the chain to detect that it was fast +// syncing and delete everything from the new head, since we can just pick up fast +// syncing from there. +func TestLongSnapSyncingShallowSetHead(t *testing.T) { + testLongSnapSyncingShallowSetHead(t, false) +} +func TestLongSnapSyncingShallowSetHeadWithSnapshots(t *testing.T) { + testLongSnapSyncingShallowSetHead(t, true) +} + +func testLongSnapSyncingShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // + // Frozen: + // G->C1->C2 + // + // Commit: G + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks where the fast +// sync pivot point - older than the ancient limit - was not yet committed, but +// sethead was called. In this case we expect the chain to detect that it was fast +// syncing and delete everything from the new head, since we can just pick up fast +// syncing from there. +func TestLongSnapSyncingDeepSetHead(t *testing.T) { + testLongSnapSyncingDeepSetHead(t, false) +} +func TestLongSnapSyncingDeepSetHeadWithSnapshots(t *testing.T) { + testLongSnapSyncingDeepSetHead(t, true) +} + +func testLongSnapSyncingDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6 + // + // Expected in leveldb: none + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 0, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 7, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter side +// chain, where a recent block - newer than the ancient limit - was already committed +// to disk and then sethead was called. In this case we expect the canonical full +// chain to be rolled back to the committed block. Everything above the sethead point +// should be deleted. In between the committed block and the requested head the data +// can remain as "fast sync" data to avoid redownloading it. The side chain is nuked +// by the freezer. +func TestLongOldForkedShallowSetHead(t *testing.T) { + testLongOldForkedShallowSetHead(t, false) +} +func TestLongOldForkedShallowSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedShallowSetHead(t, true) +} + +func testLongOldForkedShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : none + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter side +// chain, where a recent block - older than the ancient limit - was already committed +// to disk and then sethead was called. In this case we expect the canonical full +// chain to be rolled back to the committed block. Since the ancient limit was +// underflown, everything needs to be deleted onwards to avoid creating a gap. The +// side chain is nuked by the freezer. +func TestLongOldForkedDeepSetHead(t *testing.T) { testLongOldForkedDeepSetHead(t, false) } +func TestLongOldForkedDeepSetHeadWithSnapshots(t *testing.T) { testLongOldForkedDeepSetHead(t, true) } + +func testLongOldForkedDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : none + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 6, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was already committed to disk and then sethead was called. In this test scenario +// the side chain is below the committed block. In this case we expect the canonical +// full chain to be rolled back to the committed block. Everything above the +// sethead point should be deleted. In between the committed block and the +// requested head the data can remain as "fast sync" data to avoid redownloading +// it. The side chain is nuked by the freezer. +func TestLongOldForkedSnapSyncedShallowSetHead(t *testing.T) { + testLongOldForkedSnapSyncedShallowSetHead(t, false) +} +func TestLongOldForkedSnapSyncedShallowSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedSnapSyncedShallowSetHead(t, true) +} + +func testLongOldForkedSnapSyncedShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was already committed to disk and then sethead was called. In this test scenario +// the side chain is below the committed block. In this case we expect the canonical +// full chain to be rolled back to the committed block. Since the ancient limit was +// underflown, everything needs to be deleted onwards to avoid creating a gap. The +// side chain is nuked by the freezer. +func TestLongOldForkedSnapSyncedDeepSetHead(t *testing.T) { + testLongOldForkedSnapSyncedDeepSetHead(t, false) +} +func TestLongOldForkedSnapSyncedDeepSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedSnapSyncedDeepSetHead(t, true) +} + +func testLongOldForkedSnapSyncedDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6 + // + // Expected in leveldb: none + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was not yet committed, but sethead was called. In this test scenario the side +// chain is below the committed block. In this case we expect the chain to detect +// that it was fast syncing and delete everything from the new head, since we can +// just pick up fast syncing from there. The side chain is completely nuked by the +// freezer. +func TestLongOldForkedSnapSyncingShallowSetHead(t *testing.T) { + testLongOldForkedSnapSyncingShallowSetHead(t, false) +} +func TestLongOldForkedSnapSyncingShallowSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedSnapSyncingShallowSetHead(t, true) +} + +func testLongOldForkedSnapSyncingShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2 + // + // Commit: G + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was not yet committed, but sethead was called. In this test scenario the side +// chain is below the committed block. In this case we expect the chain to detect +// that it was fast syncing and delete everything from the new head, since we can +// just pick up fast syncing from there. The side chain is completely nuked by the +// freezer. +func TestLongOldForkedSnapSyncingDeepSetHead(t *testing.T) { + testLongOldForkedSnapSyncingDeepSetHead(t, false) +} +func TestLongOldForkedSnapSyncingDeepSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedSnapSyncingDeepSetHead(t, true) +} + +func testLongOldForkedSnapSyncingDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6 + // + // Expected in leveldb: none + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 3, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 7, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where a recent block - newer than the ancient limit - was already +// committed to disk and then sethead was called. In this test scenario the side +// chain is above the committed block. In this case the freezer will delete the +// sidechain since it's dangling, reverting to TestLongShallowSetHead. +func TestLongNewerForkedShallowSetHead(t *testing.T) { + testLongNewerForkedShallowSetHead(t, false) +} +func TestLongNewerForkedShallowSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedShallowSetHead(t, true) +} + +func testLongNewerForkedShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : none + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where a recent block - older than the ancient limit - was already +// committed to disk and then sethead was called. In this test scenario the side +// chain is above the committed block. In this case the freezer will delete the +// sidechain since it's dangling, reverting to TestLongDeepSetHead. +func TestLongNewerForkedDeepSetHead(t *testing.T) { + testLongNewerForkedDeepSetHead(t, false) +} +func TestLongNewerForkedDeepSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedDeepSetHead(t, true) +} + +func testLongNewerForkedDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : none + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 6, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was already committed to disk and then sethead was called. In this test scenario +// the side chain is above the committed block. In this case the freezer will delete +// the sidechain since it's dangling, reverting to TestLongSnapSyncedShallowSetHead. +func TestLongNewerForkedSnapSyncedShallowSetHead(t *testing.T) { + testLongNewerForkedSnapSyncedShallowSetHead(t, false) +} +func TestLongNewerForkedSnapSyncedShallowSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedSnapSyncedShallowSetHead(t, true) +} + +func testLongNewerForkedSnapSyncedShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was already committed to disk and then sethead was called. In this test scenario +// the side chain is above the committed block. In this case the freezer will delete +// the sidechain since it's dangling, reverting to TestLongSnapSyncedDeepSetHead. +func TestLongNewerForkedSnapSyncedDeepSetHead(t *testing.T) { + testLongNewerForkedSnapSyncedDeepSetHead(t, false) +} +func TestLongNewerForkedSnapSyncedDeepSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedSnapSyncedDeepSetHead(t, true) +} + +func testLongNewerForkedSnapSyncedDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was not yet committed, but sethead was called. In this test scenario the side +// chain is above the committed block. In this case the freezer will delete the +// sidechain since it's dangling, reverting to TestLongSnapSyncinghallowSetHead. +func TestLongNewerForkedSnapSyncingShallowSetHead(t *testing.T) { + testLongNewerForkedSnapSyncingShallowSetHead(t, false) +} +func TestLongNewerForkedSnapSyncingShallowSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedSnapSyncingShallowSetHead(t, true) +} + +func testLongNewerForkedSnapSyncingShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2 + // + // Commit: G + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a shorter +// side chain, where the fast sync pivot point - older than the ancient limit - +// was not yet committed, but sethead was called. In this test scenario the side +// chain is above the committed block. In this case the freezer will delete the +// sidechain since it's dangling, reverting to TestLongSnapSyncingDeepSetHead. +func TestLongNewerForkedSnapSyncingDeepSetHead(t *testing.T) { + testLongNewerForkedSnapSyncingDeepSetHead(t, false) +} +func TestLongNewerForkedSnapSyncingDeepSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedSnapSyncingDeepSetHead(t, true) +} + +func testLongNewerForkedSnapSyncingDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6 + // + // Expected in leveldb: none + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 12, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 7, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a longer side +// chain, where a recent block - newer than the ancient limit - was already committed +// to disk and then sethead was called. In this case the freezer will delete the +// sidechain since it's dangling, reverting to TestLongShallowSetHead. +func TestLongReorgedShallowSetHead(t *testing.T) { testLongReorgedShallowSetHead(t, false) } +func TestLongReorgedShallowSetHeadWithSnapshots(t *testing.T) { testLongReorgedShallowSetHead(t, true) } + +func testLongReorgedShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : none + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a longer side +// chain, where a recent block - older than the ancient limit - was already committed +// to disk and then sethead was called. In this case the freezer will delete the +// sidechain since it's dangling, reverting to TestLongDeepSetHead. +func TestLongReorgedDeepSetHead(t *testing.T) { testLongReorgedDeepSetHead(t, false) } +func TestLongReorgedDeepSetHeadWithSnapshots(t *testing.T) { testLongReorgedDeepSetHead(t, true) } + +func testLongReorgedDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : none + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: nil, + setheadBlock: 6, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a longer +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was already committed to disk and then sethead was called. In this case the +// freezer will delete the sidechain since it's dangling, reverting to +// TestLongSnapSyncedShallowSetHead. +func TestLongReorgedSnapSyncedShallowSetHead(t *testing.T) { + testLongReorgedSnapSyncedShallowSetHead(t, false) +} +func TestLongReorgedSnapSyncedShallowSetHeadWithSnapshots(t *testing.T) { + testLongReorgedSnapSyncedShallowSetHead(t, true) +} + +func testLongReorgedSnapSyncedShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2 + // + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a longer +// side chain, where the fast sync pivot point - older than the ancient limit - +// was already committed to disk and then sethead was called. In this case the +// freezer will delete the sidechain since it's dangling, reverting to +// TestLongSnapSyncedDeepSetHead. +func TestLongReorgedSnapSyncedDeepSetHead(t *testing.T) { + testLongReorgedSnapSyncedDeepSetHead(t, false) +} +func TestLongReorgedSnapSyncedDeepSetHeadWithSnapshots(t *testing.T) { + testLongReorgedSnapSyncedDeepSetHead(t, true) +} + +func testLongReorgedSnapSyncedDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G, C4 + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4 + // + // Expected in leveldb: none + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 4, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 4, + expSidechainBlocks: 0, + expFrozen: 5, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a longer +// side chain, where the fast sync pivot point - newer than the ancient limit - +// was not yet committed, but sethead was called. In this case we expect the +// chain to detect that it was fast syncing and delete everything from the new +// head, since we can just pick up fast syncing from there. The side chain is +// completely nuked by the freezer. +func TestLongReorgedSnapSyncingShallowSetHead(t *testing.T) { + testLongReorgedSnapSyncingShallowSetHead(t, false) +} +func TestLongReorgedSnapSyncingShallowSetHeadWithSnapshots(t *testing.T) { + testLongReorgedSnapSyncingShallowSetHead(t, true) +} + +func testLongReorgedSnapSyncingShallowSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2 + // + // Commit: G + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2 + // + // Expected in leveldb: + // C2)->C3->C4->C5->C6 + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 18, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 3, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 0, + }, snapshots) +} + +// Tests a sethead for a long canonical chain with frozen blocks and a longer +// side chain, where the fast sync pivot point - older than the ancient limit - +// was not yet committed, but sethead was called. In this case we expect the +// chain to detect that it was fast syncing and delete everything from the new +// head, since we can just pick up fast syncing from there. The side chain is +// completely nuked by the freezer. +func TestLongReorgedSnapSyncingDeepSetHead(t *testing.T) { + testLongReorgedSnapSyncingDeepSetHead(t, false) +} +func TestLongReorgedSnapSyncingDeepSetHeadWithSnapshots(t *testing.T) { + testLongReorgedSnapSyncingDeepSetHead(t, true) +} + +func testLongReorgedSnapSyncingDeepSetHead(t *testing.T, snapshots bool) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) + // â””->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 + // + // Frozen: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Commit: G + // Pivot : C4 + // + // SetHead(6) + // + // ------------------------------ + // + // Expected in freezer: + // G->C1->C2->C3->C4->C5->C6 + // + // Expected in leveldb: none + // + // Expected head header : C6 + // Expected head fast block: C6 + // Expected head block : G + testSetHead(t, &rewindTest{ + canonicalBlocks: 24, + sidechainBlocks: 26, + freezeThreshold: 16, + commitBlock: 0, + pivotBlock: uint64ptr(4), + setheadBlock: 6, + expCanonicalBlocks: 6, + expSidechainBlocks: 0, + expFrozen: 7, + expHeadHeader: 6, + expHeadFastBlock: 6, + expHeadBlock: 0, + }, snapshots) +} + +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 := filepath.Join(datadir, "ancient") + + db, err := rawdb.Open(rawdb.OpenOptions{ + Directory: datadir, + AncientsDirectory: ancient, + Ephemeral: true, + }) + if err != nil { + t.Fatalf("Failed to create persistent database: %v", err) + } + defer db.Close() + + // Initialize a fresh chain + var ( + gspec = &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.AllEthashProtocolChanges, + } + engine = ethash.NewFullFaker() + config = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, // Disable snapshot + StateScheme: scheme, + } + ) + if snapshots { + config.SnapshotLimit = 256 + config.SnapshotWait = true + } + chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to create chain: %v", err) + } + defer chain.Stop() + + // If sidechain blocks are needed, make a light chain and import it + var sideblocks types.Blocks + if tt.sidechainBlocks > 0 { + sideblocks, _ = GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{0x01}) + }) + if _, err := chain.InsertChain(sideblocks); err != nil { + t.Fatalf("Failed to import side chain: %v", err) + } + } + canonblocks, _ := GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{0x02}) + b.SetDifficulty(big.NewInt(1000000)) + }) + if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + if tt.commitBlock > 0 { + 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) + } + } + } + if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil { + t.Fatalf("Failed to import canonical chain tail: %v", err) + } + // Reopen the trie database without persisting in-memory dirty nodes. + chain.triedb.Close() + dbconfig := &triedb.Config{} + if scheme == rawdb.PathScheme { + dbconfig.PathDB = pathdb.Defaults + } else { + dbconfig.HashDB = hashdb.Defaults + } + chain.triedb = triedb.NewDatabase(chain.db, dbconfig) + chain.stateCache = state.NewDatabaseWithNodeDB(chain.db, chain.triedb) + + // Force run a freeze cycle + type freezer interface { + Freeze() error + Ancients() (uint64, error) + } + if tt.freezeThreshold < uint64(tt.canonicalBlocks) { + final := uint64(tt.canonicalBlocks) - tt.freezeThreshold + chain.SetFinalized(canonblocks[int(final)-1].Header()) + } + db.(freezer).Freeze() + + // Set the simulated pivot block + if tt.pivotBlock != nil { + rawdb.WriteLastPivotNumber(db, *tt.pivotBlock) + } + // Set the head of the chain back to the requested number + chain.SetHead(tt.setheadBlock) + + // Iterate over all the remaining blocks and ensure there are no gaps + verifyNoGaps(t, chain, true, canonblocks) + verifyNoGaps(t, chain, false, sideblocks) + verifyCutoff(t, chain, true, canonblocks, tt.expCanonicalBlocks) + verifyCutoff(t, chain, false, sideblocks, tt.expSidechainBlocks) + + if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader { + t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadHeader) + } + if head := chain.CurrentSnapBlock(); head.Number.Uint64() != tt.expHeadFastBlock { + t.Errorf("Head fast block mismatch: have %d, want %d", head.Number, tt.expHeadFastBlock) + } + if head := chain.CurrentBlock(); head.Number.Uint64() != tt.expHeadBlock { + t.Errorf("Head block mismatch: have %d, want %d", head.Number, tt.expHeadBlock) + } + if frozen, err := db.(freezer).Ancients(); err != nil { + t.Errorf("Failed to retrieve ancient count: %v\n", err) + } else if int(frozen) != tt.expFrozen { + t.Errorf("Frozen block count mismatch: have %d, want %d", frozen, tt.expFrozen) + } +} + +// verifyNoGaps checks that there are no gaps after the initial set of blocks in +// the database and errors if found. +func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks) { + t.Helper() + + var end uint64 + for i := uint64(0); i <= uint64(len(inserted)); i++ { + header := chain.GetHeaderByNumber(i) + if header == nil && end == 0 { + end = i + } + if header != nil && end > 0 { + if canonical { + t.Errorf("Canonical header gap between #%d-#%d", end, i-1) + } else { + t.Errorf("Sidechain header gap between #%d-#%d", end, i-1) + } + end = 0 // Reset for further gap detection + } + } + end = 0 + for i := uint64(0); i <= uint64(len(inserted)); i++ { + block := chain.GetBlockByNumber(i) + if block == nil && end == 0 { + end = i + } + if block != nil && end > 0 { + if canonical { + t.Errorf("Canonical block gap between #%d-#%d", end, i-1) + } else { + t.Errorf("Sidechain block gap between #%d-#%d", end, i-1) + } + end = 0 // Reset for further gap detection + } + } + end = 0 + for i := uint64(1); i <= uint64(len(inserted)); i++ { + receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()) + if receipts == nil && end == 0 { + end = i + } + if receipts != nil && end > 0 { + if canonical { + t.Errorf("Canonical receipt gap between #%d-#%d", end, i-1) + } else { + t.Errorf("Sidechain receipt gap between #%d-#%d", end, i-1) + } + end = 0 // Reset for further gap detection + } + } +} + +// verifyCutoff checks that there are no chain data available in the chain after +// the specified limit, but that it is available before. +func verifyCutoff(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks, head int) { + t.Helper() + + for i := 1; i <= len(inserted); i++ { + if i <= head { + if header := chain.GetHeader(inserted[i-1].Hash(), uint64(i)); header == nil { + if canonical { + t.Errorf("Canonical header #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } else { + t.Errorf("Sidechain header #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } + } + if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block == nil { + if canonical { + t.Errorf("Canonical block #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } else { + t.Errorf("Sidechain block #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } + } + if receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()); receipts == nil { + if canonical { + t.Errorf("Canonical receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } else { + t.Errorf("Sidechain receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } + } + } else { + if header := chain.GetHeader(inserted[i-1].Hash(), uint64(i)); header != nil { + if canonical { + t.Errorf("Canonical header #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } else { + t.Errorf("Sidechain header #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } + } + if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block != nil { + if canonical { + t.Errorf("Canonical block #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } else { + t.Errorf("Sidechain block #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } + } + if receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()); receipts != nil { + if canonical { + t.Errorf("Canonical receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } else { + t.Errorf("Sidechain receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head) + } + } + } + } +} + +// uint64ptr is a weird helper to allow 1-line constant pointer creation. +func uint64ptr(n uint64) *uint64 { + return &n +} diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go new file mode 100644 index 0000000..80f8035 --- /dev/null +++ b/core/blockchain_snapshot_test.go @@ -0,0 +1,713 @@ +// 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 . + +// Tests that abnormal program termination (i.e.crash) and restart can recovery +// the snapshot properly if the snapshot is enabled. + +package core + +import ( + "bytes" + "fmt" + "math/big" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" +) + +// 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 + + expCanonicalBlocks int // Number of canonical blocks expected to remain in the database (excl. genesis) + expHeadHeader uint64 // Block number of the expected head header + expHeadFastBlock uint64 // Block number of the expected head fast sync block + expHeadBlock uint64 // Block number of the expected head full block + expSnapshotBottom uint64 // The block height corresponding to the snapshot disk layer + + // share fields, set in runtime + datadir string + ancient string + db ethdb.Database + genDb ethdb.Database + engine consensus.Engine + gspec *Genesis +} + +func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) { + // Create a temporary persistent database + datadir := t.TempDir() + ancient := filepath.Join(datadir, "ancient") + + db, err := rawdb.Open(rawdb.OpenOptions{ + Directory: datadir, + AncientsDirectory: ancient, + Ephemeral: true, + }) + if err != nil { + t.Fatalf("Failed to create persistent database: %v", err) + } + // Initialize a fresh chain + var ( + gspec = &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.AllEthashProtocolChanges, + } + engine = ethash.NewFullFaker() + ) + 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) + } + genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, basic.chainBlocks, func(i int, b *BlockGen) {}) + + // Insert the blocks with configured settings. + var breakpoints []uint64 + if basic.commitBlock > basic.snapshotBlock { + breakpoints = append(breakpoints, basic.snapshotBlock, basic.commitBlock) + } else { + breakpoints = append(breakpoints, basic.commitBlock, basic.snapshotBlock) + } + var startPoint uint64 + for _, point := range breakpoints { + if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + startPoint = point + + if basic.commitBlock > 0 && basic.commitBlock == point { + chain.TrieDB().Commit(blocks[point-1].Root(), false) + } + if basic.snapshotBlock > 0 && basic.snapshotBlock == point { + // Flushing the entire snap tree into the disk, the + // relevant (a) snapshot root and (b) snapshot generator + // will be persisted atomically. + chain.snaps.Cap(blocks[point-1].Root(), 0) + diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() + if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { + t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) + } + } + } + if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { + t.Fatalf("Failed to import canonical chain tail: %v", err) + } + + // Set runtime fields + basic.datadir = datadir + basic.ancient = ancient + basic.db = db + basic.genDb = genDb + basic.engine = engine + basic.gspec = gspec + return chain, blocks +} + +func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks []*types.Block) { + // Iterate over all the remaining blocks and ensure there are no gaps + verifyNoGaps(t, chain, true, blocks) + verifyCutoff(t, chain, true, blocks, basic.expCanonicalBlocks) + + if head := chain.CurrentHeader(); head.Number.Uint64() != basic.expHeadHeader { + t.Errorf("Head header mismatch: have %d, want %d", head.Number, basic.expHeadHeader) + } + if head := chain.CurrentSnapBlock(); head.Number.Uint64() != basic.expHeadFastBlock { + t.Errorf("Head fast block mismatch: have %d, want %d", head.Number, basic.expHeadFastBlock) + } + if head := chain.CurrentBlock(); head.Number.Uint64() != basic.expHeadBlock { + t.Errorf("Head block mismatch: have %d, want %d", head.Number, basic.expHeadBlock) + } + + // Check the disk layer, ensure they are matched + block := chain.GetBlockByNumber(basic.expSnapshotBottom) + if block == nil { + t.Errorf("The corresponding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom) + } else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { + t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) + } + + // Check the snapshot, ensure it's integrated + if err := chain.snaps.Verify(block.Root()); err != nil { + t.Errorf("The disk layer is not integrated %v", err) + } +} + +//nolint:unused +func (basic *snapshotTestBasic) dump() string { + buffer := new(strings.Builder) + + fmt.Fprint(buffer, "Chain:\n G") + for i := 0; i < basic.chainBlocks; i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprint(buffer, " (HEAD)\n\n") + + fmt.Fprintf(buffer, "Commit: G") + if basic.commitBlock > 0 { + fmt.Fprintf(buffer, ", C%d", basic.commitBlock) + } + fmt.Fprint(buffer, "\n") + + fmt.Fprintf(buffer, "Snapshot: G") + if basic.snapshotBlock > 0 { + fmt.Fprintf(buffer, ", C%d", basic.snapshotBlock) + } + fmt.Fprint(buffer, "\n") + + //if crash { + // fmt.Fprintf(buffer, "\nCRASH\n\n") + //} else { + // fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", basic.setHead) + //} + fmt.Fprintf(buffer, "------------------------------\n\n") + + fmt.Fprint(buffer, "Expected in leveldb:\n G") + for i := 0; i < basic.expCanonicalBlocks; i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprintf(buffer, "\n\n") + fmt.Fprintf(buffer, "Expected head header : C%d\n", basic.expHeadHeader) + fmt.Fprintf(buffer, "Expected head fast block: C%d\n", basic.expHeadFastBlock) + if basic.expHeadBlock == 0 { + fmt.Fprintf(buffer, "Expected head block : G\n") + } else { + fmt.Fprintf(buffer, "Expected head block : C%d\n", basic.expHeadBlock) + } + if basic.expSnapshotBottom == 0 { + fmt.Fprintf(buffer, "Expected snapshot disk : G\n") + } else { + fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", basic.expSnapshotBottom) + } + return buffer.String() +} + +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. +// It can be used for testing that restart Geth normally. +type snapshotTest struct { + snapshotTestBasic +} + +func (snaptest *snapshotTest) test(t *testing.T) { + // 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()) + chain, blocks := snaptest.prepare(t) + + // Restart the chain normally + chain.Stop() + 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) +} + +// 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 +} + +func (snaptest *crashSnapshotTest) test(t *testing.T) { + // 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()) + chain, blocks := snaptest.prepare(t) + + // Pull the plug on the database, simulating a hard crash + 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.ancient, + Ephemeral: true, + }) + if err != nil { + t.Fatalf("Failed to reopen persistent database: %v", err) + } + defer newdb.Close() + + // The interesting thing is: instead of starting the blockchain after + // 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, 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, 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) +} + +// gappedSnapshotTest is a test type used to test this scenario: +// - have a complete snapshot +// - restart without enabling the snapshot +// - insert a few blocks +// - restart with enabling the snapshot again +type gappedSnapshotTest struct { + snapshotTestBasic + gapped int // Number of blocks to insert without enabling snapshot +} + +func (snaptest *gappedSnapshotTest) test(t *testing.T) { + // 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()) + chain, blocks := snaptest.prepare(t) + + // Insert blocks without enabling snapshot if gapping is required. + chain.Stop() + 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{ + TrieCleanLimit: 256, + 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 { + t.Fatalf("Failed to recreate chain: %v", err) + } + newchain.InsertChain(gappedBlocks) + newchain.Stop() + + // Restart the chain with enabling the snapshot + 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) +} + +// setHeadSnapshotTest is the test type used to test this scenario: +// - have a complete snapshot +// - set the head to a lower point +// - restart +type setHeadSnapshotTest struct { + snapshotTestBasic + setHead uint64 // Block number to set head back to +} + +func (snaptest *setHeadSnapshotTest) test(t *testing.T) { + // 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()) + chain, blocks := snaptest.prepare(t) + + // Rewind the chain if setHead operation is required. + chain.SetHead(snaptest.setHead) + chain.Stop() + + 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) +} + +// wipeCrashSnapshotTest is the test type used to test this scenario: +// - have a complete snapshot +// - restart, insert more blocks without enabling the snapshot +// - restart again with enabling the snapshot +// - crash +type wipeCrashSnapshotTest struct { + snapshotTestBasic + newBlocks int +} + +func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { + // 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()) + chain, blocks := snaptest.prepare(t) + + // Firstly, stop the chain properly, with all snapshot journal + // and state committed. + chain.Stop() + + config := &CacheConfig{ + TrieCleanLimit: 256, + 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(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 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 { + t.Fatalf("Failed to recreate chain: %v", err) + } + + // Simulate the blockchain crash. + tmp.triedb.Close() + tmp.stopWithoutSaving() + + 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) + } + snaptest.verify(t, newchain, blocks) + newchain.Stop() +} + +// Tests a Geth restart with valid snapshot. Before the shutdown, all snapshot +// journal will be persisted correctly. In this case no snapshot recovery is +// required. +func TestRestartWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C8 + // Expected snapshot disk : G + 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 +// chain head should be rewound to the point with available state. And also the +// new head should must be lower than disk layer. But there is no committed point +// so the chain should be rewound to genesis and the disk layer should be left +// for recovery. +func TestNoCommitCrashWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + // Expected snapshot disk : C4 + 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 +// chain head should be rewound to the point with available state. And also the +// new head should must be lower than disk layer. But there is only a low committed +// point so the chain should be rewound to committed point and the disk layer +// should be left for recovery. +func TestLowCommitCrashWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G, C2 + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C2 + // Expected snapshot disk : C4 + 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 +// the chain head should be rewound to the point with available state. And also +// the new head should must be lower than disk layer. But there is only a high +// committed point so the chain should be rewound to genesis and the disk layer +// should be left for recovery. +func TestHighCommitCrashWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G, C6 + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + // Expected snapshot disk : C4 + 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 +// enabling snapshot and after that re-enable the snapshot again. In this +// case the snapshot should be rebuilt with latest chain head. +func TestGappedNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 + // + // Expected head header : C10 + // Expected head fast block: C10 + // Expected head block : C10 + // Expected snapshot disk : C10 + 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. +// In this case the head is rewound to the target(with state available). After +// that the chain is restarted and the original disk layer is kept. +func TestSetHeadWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(4) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4 + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + // Expected snapshot disk : G + 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 +// more new blocks on top without enabling the snapshot. After the restart, +// crash happens. Check everything is ok after the restart. +func TestRecoverSnapshotFromWipingCrash(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 + // + // Expected head header : C10 + // Expected head fast block: C10 + // Expected head block : C8 + // Expected snapshot disk : C10 + 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 new file mode 100644 index 0000000..4f28c6f --- /dev/null +++ b/core/blockchain_test.go @@ -0,0 +1,4222 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "os" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "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/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/eth/tracers/logger" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" +) + +// So we can deterministically seed different blockchains +var ( + canonicalSeed = 1 + forkSeed = 2 +) + +// newCanonical creates a chain database, and injects a deterministic canonical +// 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, scheme string) (ethdb.Database, *Genesis, *BlockChain, error) { + var ( + genesis = &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.AllEthashProtocolChanges, + } + ) + // Initialize a fresh chain with only a genesis block + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) + + // Create and inject the requested chain + if n == 0 { + return rawdb.NewMemoryDatabase(), genesis, blockchain, nil + } + if full { + // Full block-chain requested + genDb, blocks := makeBlockChainWithGenesis(genesis, n, engine, canonicalSeed) + _, err := blockchain.InsertChain(blocks) + return genDb, genesis, blockchain, err + } + // Header-only chain requested + genDb, headers := makeHeaderChainWithGenesis(genesis, n, engine, canonicalSeed) + _, err := blockchain.InsertHeaderChain(headers) + return genDb, genesis, blockchain, err +} + +func newGwei(n int64) *big.Int { + return new(big.Int).Mul(big.NewInt(n), big.NewInt(params.GWei)) +} + +// 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), scheme string) { + // Copy old chain up to #i into a new db + genDb, _, blockchain2, err := newCanonical(ethash.NewFaker(), i, full, scheme) + if err != nil { + t.Fatal("could not make new canonical in testFork", err) + } + defer blockchain2.Stop() + + // Assert the chains have the same header/block at #i + var hash1, hash2 common.Hash + if full { + hash1 = blockchain.GetBlockByNumber(uint64(i)).Hash() + hash2 = blockchain2.GetBlockByNumber(uint64(i)).Hash() + } else { + hash1 = blockchain.GetHeaderByNumber(uint64(i)).Hash() + hash2 = blockchain2.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 + var ( + blockChainB []*types.Block + headerChainB []*types.Header + ) + if full { + blockChainB = makeBlockChain(blockchain2.chainConfig, blockchain2.GetBlockByHash(blockchain2.CurrentBlock().Hash()), n, ethash.NewFaker(), genDb, forkSeed) + if _, err := blockchain2.InsertChain(blockChainB); err != nil { + t.Fatalf("failed to insert forking chain: %v", err) + } + } else { + headerChainB = makeHeaderChain(blockchain2.chainConfig, blockchain2.CurrentHeader(), n, ethash.NewFaker(), genDb, forkSeed) + if _, err := blockchain2.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 + + if full { + cur := blockchain.CurrentBlock() + tdPre = blockchain.GetTd(cur.Hash(), cur.Number.Uint64()) + if err := testBlockChainImport(blockChainB, blockchain); err != nil { + t.Fatalf("failed to import forked block chain: %v", err) + } + last := blockChainB[len(blockChainB)-1] + tdPost = blockchain.GetTd(last.Hash(), last.NumberU64()) + } else { + cur := blockchain.CurrentHeader() + tdPre = blockchain.GetTd(cur.Hash(), cur.Number.Uint64()) + if err := testHeaderChainImport(headerChainB, blockchain); err != nil { + t.Fatalf("failed to import forked header chain: %v", err) + } + last := headerChainB[len(headerChainB)-1] + tdPost = blockchain.GetTd(last.Hash(), last.Number.Uint64()) + } + // Compare the total difficulties of the chains + comparator(tdPre, tdPost) +} + +// testBlockChainImport tries to process a chain of blocks, writing them into +// the database if successful. +func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { + for _, block := range chain { + // Try and process the block + err := blockchain.engine.VerifyHeader(blockchain, block.Header()) + if err == nil { + err = blockchain.validator.ValidateBody(block) + } + if err != nil { + if err == ErrKnownBlock { + continue + } + return err + } + statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.stateCache, nil) + if err != nil { + return err + } + receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) + if err != nil { + blockchain.reportBlock(block, receipts, err) + return err + } + if err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil { + blockchain.reportBlock(block, receipts, err) + return err + } + + blockchain.chainmu.MustLock() + rawdb.WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTd(block.ParentHash(), block.NumberU64()-1))) + rawdb.WriteBlock(blockchain.db, block) + statedb.Commit(block.NumberU64(), false) + blockchain.chainmu.Unlock() + } + return nil +} + +// testHeaderChainImport tries to process a chain of header, writing them into +// the database if successful. +func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error { + for _, header := range chain { + // Try and validate the header + if err := blockchain.engine.VerifyHeader(blockchain, header); err != nil { + return err + } + // Manually insert the header into the database, but don't reorganise (allows subsequent testing) + blockchain.chainmu.MustLock() + rawdb.WriteTd(blockchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTd(header.ParentHash, header.Number.Uint64()-1))) + rawdb.WriteHeader(blockchain.db, header) + blockchain.chainmu.Unlock() + } + return nil +} +func TestLastBlock(t *testing.T) { + 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) + } + defer blockchain.Stop() + + blocks := makeBlockChain(blockchain.chainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), 1, ethash.NewFullFaker(), genDb, 0) + if _, err := blockchain.InsertChain(blocks); err != nil { + t.Fatalf("Failed to insert block: %v", err) + } + if blocks[len(blocks)-1].Hash() != rawdb.ReadHeadBlockHash(blockchain.db) { + t.Fatalf("Write/Get HeadBlockHash failed") + } +} + +// 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, scheme string) { + // Copy old chain up to #i into a new db + genDb, _, blockchain2, err := newCanonical(ethash.NewFaker(), i, full, scheme) + if err != nil { + t.Fatal("could not make new canonical in testFork", err) + } + defer blockchain2.Stop() + + // Assert the chains have the same header/block at #i + var hash1, hash2 common.Hash + if full { + hash1 = blockchain.GetBlockByNumber(uint64(i)).Hash() + hash2 = blockchain2.GetBlockByNumber(uint64(i)).Hash() + } else { + hash1 = blockchain.GetHeaderByNumber(uint64(i)).Hash() + hash2 = blockchain2.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 + if full { + blockChainB := makeBlockChain(blockchain2.chainConfig, blockchain2.GetBlockByHash(blockchain2.CurrentBlock().Hash()), n, ethash.NewFaker(), genDb, forkSeed) + if _, err := blockchain2.InsertChain(blockChainB); err != nil { + t.Fatalf("failed to insert forking chain: %v", err) + } + if blockchain2.CurrentBlock().Number.Uint64() != blockChainB[len(blockChainB)-1].NumberU64() { + t.Fatalf("failed to reorg to the given chain") + } + if blockchain2.CurrentBlock().Hash() != blockChainB[len(blockChainB)-1].Hash() { + t.Fatalf("failed to reorg to the given chain") + } + } else { + headerChainB := makeHeaderChain(blockchain2.chainConfig, blockchain2.CurrentHeader(), n, ethash.NewFaker(), genDb, forkSeed) + if _, err := blockchain2.InsertHeaderChain(headerChainB); err != nil { + t.Fatalf("failed to insert forking chain: %v", err) + } + if blockchain2.CurrentHeader().Number.Uint64() != headerChainB[len(headerChainB)-1].Number.Uint64() { + t.Fatalf("failed to reorg to the given chain") + } + if blockchain2.CurrentHeader().Hash() != headerChainB[len(headerChainB)-1].Hash() { + t.Fatalf("failed to reorg to the given chain") + } + } +} + +// 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, 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, scheme string) { + length := 5 + + // Make first chain starting from genesis + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) + if err != nil { + t.Fatalf("failed to make new canonical chain: %v", err) + } + defer processor.Stop() + + // 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, 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, 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, scheme string) { + length := 5 + + // Make first chain starting from genesis + _, _, 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, 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, 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, scheme string) { + length := 10 + + // Make first chain starting from genesis + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) + if err != nil { + t.Fatalf("failed to make new canonical chain: %v", err) + } + defer processor.Stop() + + // 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, 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, 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, scheme string) { + length := 10 + + // Make first chain starting from genesis + _, _, 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, 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, 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, scheme string) { + length := 10 + + // Make first chain starting from genesis + _, _, 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, 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, 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, scheme string) { + length := 10 + + // Make first chain starting from genesis + _, _, 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, 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, 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, scheme string) { + length := 10 + + // Make first chain starting from genesis + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) + if err != nil { + t.Fatalf("failed to make new canonical chain: %v", err) + } + defer processor.Stop() + + // 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, 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, 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, scheme string) { + length := 10 + + // Make first chain starting from genesis + _, _, 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, 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, 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, scheme string) { + // Make chain starting from genesis + genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 10, full, scheme) + if err != nil { + t.Fatalf("failed to make new canonical chain: %v", err) + } + defer blockchain.Stop() + + // Create a forked chain, and try to insert with a missing link + if full { + chain := makeBlockChain(blockchain.chainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), 5, ethash.NewFaker(), genDb, forkSeed)[1:] + if err := testBlockChainImport(chain, blockchain); err == nil { + t.Errorf("broken block chain not reported") + } + } else { + chain := makeHeaderChain(blockchain.chainConfig, blockchain.CurrentHeader(), 5, ethash.NewFaker(), genDb, forkSeed)[1:] + if err := testHeaderChainImport(chain, blockchain); err == nil { + t.Errorf("broken header chain not reported") + } + } +} + +// 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, 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, 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, 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, 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. + easy := make([]int64, 96) + for i := 0; i < len(easy); i++ { + easy[i] = 60 + } + diff := make([]int64, len(easy)-1) + for i := 0; i < len(diff); i++ { + diff[i] = -9 + } + testReorg(t, easy, diff, 12615120+params.GenesisDifficulty.Int64(), full, scheme) +} + +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, scheme) + if err != nil { + t.Fatalf("failed to create pristine chain: %v", err) + } + defer blockchain.Stop() + + // Insert an easy and a difficult chain afterwards + easyBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), ethash.NewFaker(), genDb, len(first), func(i int, b *BlockGen) { + b.OffsetTime(first[i]) + }) + diffBlocks, _ := GenerateChain(params.TestChainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), ethash.NewFaker(), genDb, len(second), func(i int, b *BlockGen) { + b.OffsetTime(second[i]) + }) + if full { + if _, err := blockchain.InsertChain(easyBlocks); err != nil { + t.Fatalf("failed to insert easy chain: %v", err) + } + if _, err := blockchain.InsertChain(diffBlocks); err != nil { + t.Fatalf("failed to insert difficult chain: %v", err) + } + } else { + easyHeaders := make([]*types.Header, len(easyBlocks)) + for i, block := range easyBlocks { + easyHeaders[i] = block.Header() + } + diffHeaders := make([]*types.Header, len(diffBlocks)) + for i, block := range diffBlocks { + diffHeaders[i] = block.Header() + } + if _, err := blockchain.InsertHeaderChain(easyHeaders); err != nil { + t.Fatalf("failed to insert easy chain: %v", err) + } + if _, err := blockchain.InsertHeaderChain(diffHeaders); err != nil { + t.Fatalf("failed to insert difficult chain: %v", err) + } + } + // Check that the chain is valid number and link wise + if full { + prev := blockchain.CurrentBlock() + for block := blockchain.GetBlockByNumber(blockchain.CurrentBlock().Number.Uint64() - 1); block.NumberU64() != 0; prev, block = block.Header(), blockchain.GetBlockByNumber(block.NumberU64()-1) { + if prev.ParentHash != block.Hash() { + t.Errorf("parent block hash mismatch: have %x, want %x", prev.ParentHash, block.Hash()) + } + } + } else { + prev := blockchain.CurrentHeader() + for header := blockchain.GetHeaderByNumber(blockchain.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, blockchain.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(blockchain.genesisBlock.Difficulty(), big.NewInt(td)) + if full { + cur := blockchain.CurrentBlock() + if have := blockchain.GetTd(cur.Hash(), cur.Number.Uint64()); have.Cmp(want) != 0 { + t.Errorf("total difficulty mismatch: have %v, want %v", have, want) + } + } else { + cur := blockchain.CurrentHeader() + if have := blockchain.GetTd(cur.Hash(), cur.Number.Uint64()); have.Cmp(want) != 0 { + t.Errorf("total difficulty mismatch: have %v, want %v", have, want) + } + } +} + +// Tests chain insertions in the face of one entity containing an invalid nonce. +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, scheme string) { + doTest := func(i int) { + // Create a pristine chain and database + genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, scheme) + if err != nil { + t.Fatalf("failed to create pristine chain: %v", err) + } + defer blockchain.Stop() + + // Create and insert a chain with a failing nonce + var ( + failAt int + failRes int + failNum uint64 + ) + if full { + blocks := makeBlockChain(blockchain.chainConfig, blockchain.GetBlockByHash(blockchain.CurrentBlock().Hash()), i, ethash.NewFaker(), genDb, 0) + + failAt = rand.Int() % len(blocks) + failNum = blocks[failAt].NumberU64() + + blockchain.engine = ethash.NewFakeFailer(failNum) + failRes, err = blockchain.InsertChain(blocks) + } else { + headers := makeHeaderChain(blockchain.chainConfig, blockchain.CurrentHeader(), i, ethash.NewFaker(), genDb, 0) + + failAt = rand.Int() % len(headers) + failNum = headers[failAt].Number.Uint64() + + blockchain.engine = ethash.NewFakeFailer(failNum) + blockchain.hc.engine = blockchain.engine + failRes, err = blockchain.InsertHeaderChain(headers) + } + // Check that the returned error indicates the failure + if failRes != failAt { + t.Errorf("test %d: failure (%v) index mismatch: have %d, want %d", i, err, failRes, failAt) + } + // Check that all blocks after the failing block have been inserted + for j := 0; j < i-failAt; j++ { + if full { + if block := blockchain.GetBlockByNumber(failNum + uint64(j)); block != nil { + t.Errorf("test %d: invalid block in chain: %v", i, block) + } + } else { + if header := blockchain.GetHeaderByNumber(failNum + uint64(j)); header != nil { + t.Errorf("test %d: invalid header in chain: %v", i, header) + } + } + } + } + for i := 1; i < 25 && !t.Failed(); i++ { + doTest(i) + } +} + +// 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") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{address: {Balance: funds}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + signer = types.LatestSigner(gspec.Config) + ) + _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 1024, func(i int, block *BlockGen) { + block.SetCoinbase(common.Address{0x00}) + + // If the block number is multiple of 3, send a few bonus transactions to the miner + if i%3 == 2 { + for j := 0; j < i%4+1; j++ { + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key) + if err != nil { + panic(err) + } + block.AddTx(tx) + } + } + // If the block number is a multiple of 5, add an uncle to the block + if i%5 == 4 { + block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i))}) + } + }) + // Import the chain as an archive node for the comparison baseline + archiveDb := rawdb.NewMemoryDatabase() + archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer archive.Stop() + + if n, err := archive.InsertChain(blocks); err != nil { + t.Fatalf("failed to process block %d: %v", n, err) + } + // Fast import the chain as a non-archive node to test + fastDb := rawdb.NewMemoryDatabase() + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer fast.Stop() + + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + if n, err := fast.InsertHeaderChain(headers); err != nil { + t.Fatalf("failed to insert header %d: %v", n, err) + } + if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil { + t.Fatalf("failed to insert receipt %d: %v", n, err) + } + // Freezer style fast import the chain. + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) + if err != nil { + t.Fatalf("failed to create temp freezer db: %v", err) + } + defer ancientDb.Close() + + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer ancient.Stop() + + if n, err := ancient.InsertHeaderChain(headers); err != nil { + t.Fatalf("failed to insert header %d: %v", n, err) + } + if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(len(blocks)/2)); err != nil { + t.Fatalf("failed to insert receipt %d: %v", n, err) + } + + // Iterate over all chain data components, and cross reference + for i := 0; i < len(blocks); i++ { + num, hash, time := blocks[i].NumberU64(), blocks[i].Hash(), blocks[i].Time() + + if ftd, atd := fast.GetTd(hash, num), archive.GetTd(hash, num); ftd.Cmp(atd) != 0 { + t.Errorf("block #%d [%x]: td mismatch: fastdb %v, archivedb %v", num, hash, ftd, atd) + } + if antd, artd := ancient.GetTd(hash, num), archive.GetTd(hash, num); antd.Cmp(artd) != 0 { + t.Errorf("block #%d [%x]: td mismatch: ancientdb %v, archivedb %v", num, hash, antd, artd) + } + if fheader, aheader := fast.GetHeaderByHash(hash), archive.GetHeaderByHash(hash); fheader.Hash() != aheader.Hash() { + t.Errorf("block #%d [%x]: header mismatch: fastdb %v, archivedb %v", num, hash, fheader, aheader) + } + if anheader, arheader := ancient.GetHeaderByHash(hash), archive.GetHeaderByHash(hash); anheader.Hash() != arheader.Hash() { + t.Errorf("block #%d [%x]: header mismatch: ancientdb %v, archivedb %v", num, hash, anheader, arheader) + } + if fblock, arblock, anblock := fast.GetBlockByHash(hash), archive.GetBlockByHash(hash), ancient.GetBlockByHash(hash); fblock.Hash() != arblock.Hash() || anblock.Hash() != arblock.Hash() { + t.Errorf("block #%d [%x]: block mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock, anblock, arblock) + } else if types.DeriveSha(fblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) || types.DeriveSha(anblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) { + t.Errorf("block #%d [%x]: transactions mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Transactions(), anblock.Transactions(), arblock.Transactions()) + } else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) || types.CalcUncleHash(anblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) { + t.Errorf("block #%d [%x]: uncles mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Uncles(), anblock, arblock.Uncles()) + } + + // Check receipts. + freceipts := rawdb.ReadReceipts(fastDb, hash, num, time, fast.Config()) + anreceipts := rawdb.ReadReceipts(ancientDb, hash, num, time, fast.Config()) + areceipts := rawdb.ReadReceipts(archiveDb, hash, num, time, fast.Config()) + if types.DeriveSha(freceipts, trie.NewStackTrie(nil)) != types.DeriveSha(areceipts, trie.NewStackTrie(nil)) { + t.Errorf("block #%d [%x]: receipts mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, freceipts, anreceipts, areceipts) + } + + // Check that hash-to-number mappings are present in all databases. + if m := rawdb.ReadHeaderNumber(fastDb, hash); m == nil || *m != num { + t.Errorf("block #%d [%x]: wrong hash-to-number mapping in fastdb: %v", num, hash, m) + } + if m := rawdb.ReadHeaderNumber(ancientDb, hash); m == nil || *m != num { + t.Errorf("block #%d [%x]: wrong hash-to-number mapping in ancientdb: %v", num, hash, m) + } + if m := rawdb.ReadHeaderNumber(archiveDb, hash); m == nil || *m != num { + t.Errorf("block #%d [%x]: wrong hash-to-number mapping in archivedb: %v", num, hash, m) + } + } + + // Check that the canonical chains are the same between the databases + for i := 0; i < len(blocks)+1; i++ { + if fhash, ahash := rawdb.ReadCanonicalHash(fastDb, uint64(i)), rawdb.ReadCanonicalHash(archiveDb, uint64(i)); fhash != ahash { + t.Errorf("block #%d: canonical hash mismatch: fastdb %v, archivedb %v", i, fhash, ahash) + } + if anhash, arhash := rawdb.ReadCanonicalHash(ancientDb, uint64(i)), rawdb.ReadCanonicalHash(archiveDb, uint64(i)); anhash != arhash { + t.Errorf("block #%d: canonical hash mismatch: ancientdb %v, archivedb %v", i, anhash, arhash) + } + } +} + +// 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") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{address: {Balance: funds}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + ) + height := uint64(64) + _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil) + + // makeDb creates a db instance for testing. + makeDb := func() ethdb.Database { + db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) + if err != nil { + t.Fatalf("failed to create temp freezer db: %v", err) + } + return db + } + // Configure a subchain to roll back + remove := blocks[height/2].NumberU64() + + // Create a small assertion method to check the three heads + assert := func(t *testing.T, kind string, chain *BlockChain, header uint64, fast uint64, block uint64) { + t.Helper() + + if num := chain.CurrentBlock().Number.Uint64(); num != block { + t.Errorf("%s head block mismatch: have #%v, want #%v", kind, num, block) + } + if num := chain.CurrentSnapBlock().Number.Uint64(); num != fast { + t.Errorf("%s head snap-block mismatch: have #%v, want #%v", kind, num, fast) + } + if num := chain.CurrentHeader().Number.Uint64(); num != header { + t.Errorf("%s head header mismatch: have #%v, want #%v", kind, num, header) + } + } + // Import the chain as an archive node and ensure all pointers are updated + archiveDb := makeDb() + defer archiveDb.Close() + + 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 { + t.Fatalf("failed to process block %d: %v", n, err) + } + defer archive.Stop() + + assert(t, "archive", archive, height, height, height) + archive.SetHead(remove - 1) + assert(t, "archive", archive, height/2, height/2, height/2) + + // Import the chain as a non-archive node and ensure all pointers are updated + fastDb := makeDb() + defer fastDb.Close() + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer fast.Stop() + + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + if n, err := fast.InsertHeaderChain(headers); err != nil { + t.Fatalf("failed to insert header %d: %v", n, err) + } + if n, err := fast.InsertReceiptChain(blocks, receipts, 0); err != nil { + t.Fatalf("failed to insert receipt %d: %v", n, err) + } + assert(t, "fast", fast, height, height, 0) + fast.SetHead(remove - 1) + assert(t, "fast", fast, height/2, height/2, 0) + + // Import the chain as a ancient-first node and ensure all pointers are updated + ancientDb := makeDb() + defer ancientDb.Close() + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer ancient.Stop() + + if n, err := ancient.InsertHeaderChain(headers); err != nil { + t.Fatalf("failed to insert header %d: %v", n, err) + } + if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil { + t.Fatalf("failed to insert receipt %d: %v", n, err) + } + assert(t, "ancient", ancient, height, height, 0) + ancient.SetHead(remove - 1) + assert(t, "ancient", ancient, 0, 0, 0) + + if frozen, err := ancientDb.Ancients(); err != nil || frozen != 1 { + t.Fatalf("failed to truncate ancient store, want %v, have %v", 1, frozen) + } + // Import the chain as a light node and ensure all pointers are updated + lightDb := makeDb() + defer lightDb.Close() + 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) + } + defer light.Stop() + + assert(t, "light", light, height, 0, 0) + light.SetHead(remove - 1) + assert(t, "light", light, height/2, 0, 0) +} + +// 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") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + gspec = &Genesis{ + Config: params.TestChainConfig, + GasLimit: 3141592, + Alloc: types.GenesisAlloc{ + addr1: {Balance: big.NewInt(1000000000000000)}, + addr2: {Balance: big.NewInt(1000000000000000)}, + addr3: {Balance: big.NewInt(1000000000000000)}, + }, + } + signer = types.LatestSigner(gspec.Config) + ) + + // Create two transactions shared between the chains: + // - postponed: transaction included at a later block in the forked chain + // - swapped: transaction included at the same block number in the forked chain + postponed, _ := types.SignTx(types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, key1) + swapped, _ := types.SignTx(types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, key1) + + // Create two transactions that will be dropped by the forked chain: + // - pastDrop: transaction dropped retroactively from a past block + // - freshDrop: transaction dropped exactly at the block where the reorg is detected + var pastDrop, freshDrop *types.Transaction + + // Create three transactions that will be added in the forked chain: + // - pastAdd: transaction added before the reorganization is detected + // - freshAdd: transaction added at the exact block the reorg is detected + // - futureAdd: transaction added after the reorg has already finished + var pastAdd, freshAdd, futureAdd *types.Transaction + + _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, gen *BlockGen) { + switch i { + case 0: + pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key2) + + gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point + gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork + + case 2: + freshDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key2) + + gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point + gen.AddTx(swapped) // This transaction will be swapped out at the exact height + + gen.OffsetTime(9) // Lower the block difficulty to simulate a weaker chain + } + }) + // Import the chain. This runs all block validation rules. + db := rawdb.NewMemoryDatabase() + 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) + } + defer blockchain.Stop() + + // overwrite the old chain + _, chain, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 5, func(i int, gen *BlockGen) { + switch i { + case 0: + pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key3) + gen.AddTx(pastAdd) // This transaction needs to be injected during reorg + + case 2: + gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain + gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain + + freshAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key3) + gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time + + case 3: + futureAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key3) + gen.AddTx(futureAdd) // This transaction will be added after a full reorg + } + }) + if _, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("failed to insert forked chain: %v", err) + } + + // removed tx + for i, tx := range (types.Transactions{pastDrop, freshDrop}) { + if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil { + t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn) + } + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil { + t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt) + } + } + // added tx + for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) { + if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { + t.Errorf("add %d: expected tx to be found", i) + } + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { + t.Errorf("add %d: expected receipt to be found", i) + } + } + // shared tx + for i, tx := range (types.Transactions{postponed, swapped}) { + if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { + t.Errorf("share %d: expected tx to be found", i) + } + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { + t.Errorf("share %d: expected receipt to be found", i) + } + } +} + +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) + + // this code generates a log + code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") + gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} + signer = types.LatestSigner(gspec.Config) + ) + + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer blockchain.Stop() + + rmLogsCh := make(chan RemovedLogsEvent) + blockchain.SubscribeRemovedLogsEvent(rmLogsCh) + _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, gen *BlockGen) { + if i == 1 { + tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, code), signer, key1) + if err != nil { + t.Fatalf("failed to create tx: %v", err) + } + gen.AddTx(tx) + } + }) + if _, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + _, chain, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, gen *BlockGen) {}) + done := make(chan struct{}) + go func() { + ev := <-rmLogsCh + if len(ev.Logs) == 0 { + t.Error("expected logs") + } + close(done) + }() + if _, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("failed to insert forked chain: %v", err) + } + timeout := time.NewTimer(1 * time.Second) + defer timeout.Stop() + select { + case <-done: + case <-timeout.C: + t.Fatal("Timeout. There is no RemovedLogsEvent has been sent.") + } +} + +// This EVM code generates a log when the contract is created. +var logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") + +// 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: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} + signer = types.LatestSigner(gspec.Config) + engine = ethash.NewFaker() + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + ) + defer blockchain.Stop() + + // The event channels. + newLogCh := make(chan []*types.Log, 10) + rmLogsCh := make(chan RemovedLogsEvent, 10) + blockchain.SubscribeLogsEvent(newLogCh) + blockchain.SubscribeRemovedLogsEvent(rmLogsCh) + + // This chain contains 10 logs. + genDb, chain, _ := GenerateChainWithGenesis(gspec, engine, 3, func(i int, gen *BlockGen) { + if i < 2 { + for ii := 0; ii < 5; ii++ { + tx, err := types.SignNewTx(key1, signer, &types.LegacyTx{ + Nonce: gen.TxNonce(addr1), + GasPrice: gen.header.BaseFee, + Gas: uint64(1000001), + Data: logCode, + }) + if err != nil { + t.Fatalf("failed to create tx: %v", err) + } + gen.AddTx(tx) + } + } + }) + if _, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + checkLogEvents(t, newLogCh, rmLogsCh, 10, 0) + + // Generate long reorg chain containing more logs. Inserting the + // chain removes one log and adds four. + _, forkChain, _ := GenerateChainWithGenesis(gspec, engine, 3, func(i int, gen *BlockGen) { + if i == 2 { + // The last (head) block is not part of the reorg-chain, we can ignore it + return + } + for ii := 0; ii < 5; ii++ { + tx, err := types.SignNewTx(key1, signer, &types.LegacyTx{ + Nonce: gen.TxNonce(addr1), + GasPrice: gen.header.BaseFee, + Gas: uint64(1000000), + Data: logCode, + }) + if err != nil { + t.Fatalf("failed to create tx: %v", err) + } + gen.AddTx(tx) + } + gen.OffsetTime(-9) // higher block difficulty + }) + if _, err := blockchain.InsertChain(forkChain); err != nil { + t.Fatalf("failed to insert forked chain: %v", err) + } + checkLogEvents(t, newLogCh, rmLogsCh, 10, 10) + + // This chain segment is rooted in the original chain, but doesn't contain any logs. + // When inserting it, the canonical chain switches away from forkChain and re-emits + // the log event for the old chain, as well as a RemovedLogsEvent for forkChain. + newBlocks, _ := GenerateChain(gspec.Config, chain[len(chain)-1], engine, genDb, 1, func(i int, gen *BlockGen) {}) + if _, err := blockchain.InsertChain(newBlocks); err != nil { + t.Fatalf("failed to insert forked chain: %v", err) + } + checkLogEvents(t, newLogCh, rmLogsCh, 10, 10) +} + +// 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: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} + signer = types.LatestSigner(gspec.Config) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ) + defer blockchain.Stop() + + newLogCh := make(chan []*types.Log, 10) + rmLogsCh := make(chan RemovedLogsEvent, 10) + blockchain.SubscribeLogsEvent(newLogCh) + blockchain.SubscribeRemovedLogsEvent(rmLogsCh) + + _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, gen *BlockGen) { + if i == 1 { + gen.OffsetTime(-9) // higher block difficulty + } + }) + if _, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("failed to insert forked chain: %v", err) + } + checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) + + // Generate side chain with lower difficulty + genDb, sideChain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, gen *BlockGen) { + if i == 1 { + tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, logCode), signer, key1) + if err != nil { + t.Fatalf("failed to create tx: %v", err) + } + gen.AddTx(tx) + } + }) + if _, err := blockchain.InsertChain(sideChain); err != nil { + t.Fatalf("failed to insert forked chain: %v", err) + } + checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) + + // Generate a new block based on side chain. + newBlocks, _ := GenerateChain(gspec.Config, sideChain[len(sideChain)-1], ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {}) + if _, err := blockchain.InsertChain(newBlocks); err != nil { + t.Fatalf("failed to insert forked chain: %v", err) + } + checkLogEvents(t, newLogCh, rmLogsCh, 1, 0) +} + +func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan RemovedLogsEvent, wantNew, wantRemoved int) { + t.Helper() + var ( + countNew int + countRm int + prev int + ) + // Drain events. + for len(logsCh) > 0 { + x := <-logsCh + countNew += len(x) + for _, log := range x { + // We expect added logs to be in ascending order: 0:0, 0:1, 1:0 ... + have := 100*int(log.BlockNumber) + int(log.TxIndex) + if have < prev { + t.Fatalf("Expected new logs to arrive in ascending order (%d < %d)", have, prev) + } + prev = have + } + } + prev = 0 + for len(rmLogsCh) > 0 { + x := <-rmLogsCh + countRm += len(x.Logs) + for _, log := range x.Logs { + // We expect removed logs to be in ascending order: 0:0, 0:1, 1:0 ... + have := 100*int(log.BlockNumber) + int(log.TxIndex) + if have < prev { + t.Fatalf("Expected removed logs to arrive in ascending order (%d < %d)", have, prev) + } + prev = have + } + } + + if countNew != wantNew { + t.Fatalf("wrong number of log events: got %d, want %d", countNew, wantNew) + } + if countRm != wantRemoved { + t.Fatalf("wrong number of removed log events: got %d, want %d", countRm, wantRemoved) + } +} + +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) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}, + } + signer = types.LatestSigner(gspec.Config) + ) + 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) {}) + if _, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + _, replacementBlocks, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, func(i int, gen *BlockGen) { + tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, nil), signer, key1) + if i == 2 { + gen.OffsetTime(-9) + } + if err != nil { + t.Fatalf("failed to create tx: %v", err) + } + gen.AddTx(tx) + }) + chainSideCh := make(chan ChainSideEvent, 64) + blockchain.SubscribeChainSideEvent(chainSideCh) + if _, err := blockchain.InsertChain(replacementBlocks); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + // first two block of the secondary chain are for a brief moment considered + // side chains because up to that point the first one is considered the + // heavier chain. + expectedSideHashes := map[common.Hash]bool{ + replacementBlocks[0].Hash(): true, + replacementBlocks[1].Hash(): true, + chain[0].Hash(): true, + chain[1].Hash(): true, + chain[2].Hash(): true, + } + + i := 0 + + const timeoutDura = 10 * time.Second + timeout := time.NewTimer(timeoutDura) +done: + for { + select { + case ev := <-chainSideCh: + block := ev.Block + if _, ok := expectedSideHashes[block.Hash()]; !ok { + t.Errorf("%d: didn't expect %x to be in side chain", i, block.Hash()) + } + i++ + + if i == len(expectedSideHashes) { + timeout.Stop() + + break done + } + timeout.Reset(timeoutDura) + + case <-timeout.C: + t.Fatal("Timeout. Possibly not all blocks were triggered for sideevent") + } + } + + // make sure no more events are fired + select { + case e := <-chainSideCh: + t.Errorf("unexpected event fired: %v", e) + case <-time.After(250 * time.Millisecond): + } +} + +// Tests if the canonical block can be fetched from the database during chain insertion. +func TestCanonicalBlockRetrieval(t *testing.T) { + 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) + } + defer blockchain.Stop() + + _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 10, func(i int, gen *BlockGen) {}) + + var pend sync.WaitGroup + pend.Add(len(chain)) + + for i := range chain { + go func(block *types.Block) { + defer pend.Done() + + // try to retrieve a block by its canonical hash and see if the block data can be retrieved. + for { + ch := rawdb.ReadCanonicalHash(blockchain.db, block.NumberU64()) + if ch == (common.Hash{}) { + continue // busy wait for canonical hash to be written + } + if ch != block.Hash() { + t.Errorf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex()) + return + } + fb := rawdb.ReadBlock(blockchain.db, ch, block.NumberU64()) + if fb == nil { + t.Errorf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex()) + return + } + if fb.Hash() != block.Hash() { + t.Errorf("invalid block hash for block %d, want %s, got %s", block.NumberU64(), block.Hash().Hex(), fb.Hash().Hex()) + return + } + return + } + }(chain[i]) + + if _, err := blockchain.InsertChain(types.Blocks{chain[i]}); err != nil { + t.Fatalf("failed to insert block %d: %v", i, err) + } + } + 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") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + deleteAddr = common.Address{1} + gspec = &Genesis{ + Config: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(2), + HomesteadBlock: new(big.Int), + }, + Alloc: types.GenesisAlloc{address: {Balance: funds}, deleteAddr: {Balance: new(big.Int)}}, + } + ) + genDb, blocks, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, func(i int, block *BlockGen) { + var ( + tx *types.Transaction + err error + basicTx = func(signer types.Signer) (*types.Transaction, error) { + return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key) + } + ) + switch i { + case 0: + tx, err = basicTx(types.HomesteadSigner{}) + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + case 2: + tx, err = basicTx(types.HomesteadSigner{}) + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + + tx, err = basicTx(types.LatestSigner(gspec.Config)) + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + case 3: + tx, err = basicTx(types.HomesteadSigner{}) + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + + tx, err = basicTx(types.LatestSigner(gspec.Config)) + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + } + }) + + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer blockchain.Stop() + + if _, err := blockchain.InsertChain(blocks); err != nil { + t.Fatal(err) + } + block := blockchain.GetBlockByNumber(1) + if block.Transactions()[0].Protected() { + t.Error("Expected block[0].txs[0] to not be replay protected") + } + + block = blockchain.GetBlockByNumber(3) + if block.Transactions()[0].Protected() { + t.Error("Expected block[3].txs[0] to not be replay protected") + } + if !block.Transactions()[1].Protected() { + t.Error("Expected block[3].txs[1] to be replay protected") + } + if _, err := blockchain.InsertChain(blocks[4:]); err != nil { + t.Fatal(err) + } + + // generate an invalid chain id transaction + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(2), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(2), + HomesteadBlock: new(big.Int), + } + blocks, _ = GenerateChain(config, blocks[len(blocks)-1], ethash.NewFaker(), genDb, 4, func(i int, block *BlockGen) { + var ( + tx *types.Transaction + err error + basicTx = func(signer types.Signer) (*types.Transaction, error) { + return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key) + } + ) + if i == 0 { + tx, err = basicTx(types.LatestSigner(config)) + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + } + }) + _, err := blockchain.InsertChain(blocks) + if have, want := err, types.ErrInvalidChainId; !errors.Is(have, want) { + 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") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + theAddr = common.Address{1} + gspec = &Genesis{ + Config: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: new(big.Int), + EIP155Block: new(big.Int), + EIP150Block: new(big.Int), + EIP158Block: big.NewInt(2), + }, + Alloc: types.GenesisAlloc{address: {Balance: funds}}, + } + ) + _, blocks, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, block *BlockGen) { + var ( + tx *types.Transaction + err error + signer = types.LatestSigner(gspec.Config) + ) + switch i { + case 0: + tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil), signer, key) + case 1: + tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil), signer, key) + case 2: + tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), 21000, new(big.Int), nil), signer, key) + } + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + }) + // account must exist pre eip 161 + 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 { + t.Fatal(err) + } + if st, _ := blockchain.State(); !st.Exist(theAddr) { + t.Error("expected account to exist") + } + + // account needs to be deleted post eip 161 + if _, err := blockchain.InsertChain(types.Blocks{blocks[1]}); err != nil { + t.Fatal(err) + } + if st, _ := blockchain.State(); st.Exist(theAddr) { + t.Error("account should not exist") + } + + // account mustn't be created post eip 161 + if _, err := blockchain.InsertChain(types.Blocks{blocks[2]}); err != nil { + t.Fatal(err) + } + if st, _ := blockchain.State(); st.Exist(theAddr) { + t.Error("account should not exist") + } +} + +// This is a regression test (i.e. as weird as it is, don't delete it ever), which +// tests that under weird reorg conditions the blockchain and its internal header- +// chain return the same latest block/header. +// +// 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{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + + // Generate a bunch of fork blocks, each side forking from the canonical chain + forks := make([]*types.Block, len(blocks)) + for i := 0; i < len(forks); i++ { + parent := genesis.ToBlock() + if i > 0 { + parent = blocks[i-1] + } + fork, _ := GenerateChain(genesis.Config, parent, engine, genDb, 1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) }) + forks[i] = fork[0] + } + // Import the canonical and fork chain side by side, verifying the current block + // and current header consistency + 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) + } + defer chain.Stop() + + for i := 0; i < len(blocks); i++ { + if _, err := chain.InsertChain(blocks[i : i+1]); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", i, err) + } + if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() { + t.Errorf("block %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number, chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) + } + if _, err := chain.InsertChain(forks[i : i+1]); err != nil { + t.Fatalf(" fork %d: failed to insert into chain: %v", i, err) + } + if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() { + t.Errorf(" fork %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number, chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) + } + } +} + +// Tests that importing small side forks doesn't leave junk in the trie database +// cache (which would eventually cause memory issues). +func TestTrieForkGC(t *testing.T) { + // Generate a canonical chain to act as the main dataset + engine := ethash.NewFaker() + genesis := &Genesis{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*state.TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + + // Generate a bunch of fork blocks, each side forking from the canonical chain + forks := make([]*types.Block, len(blocks)) + for i := 0; i < len(forks); i++ { + parent := genesis.ToBlock() + if i > 0 { + parent = blocks[i-1] + } + fork, _ := GenerateChain(genesis.Config, parent, engine, genDb, 1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) }) + forks[i] = fork[0] + } + // Import the canonical and fork chain side by side, forcing the trie cache to cache both + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + for i := 0; i < len(blocks); i++ { + if _, err := chain.InsertChain(blocks[i : i+1]); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", i, err) + } + if _, err := chain.InsertChain(forks[i : i+1]); err != nil { + t.Fatalf("fork %d: failed to insert into chain: %v", i, err) + } + } + // Dereference all the recent tries and ensure no past trie is left in + for i := 0; i < state.TriesInMemory; i++ { + 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 { // all memory is returned in the nodes return for hashdb + t.Fatalf("stale tries still alive after garbase collection") + } +} + +// 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{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + genDb, shared, _ := GenerateChainWithGenesis(genesis, engine, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + original, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) }) + competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) }) + + // Import the shared chain and the original canonical one + db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", 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) + } + defer chain.Stop() + + if _, err := chain.InsertChain(shared); err != nil { + t.Fatalf("failed to insert shared chain: %v", err) + } + if _, err := chain.InsertChain(original); err != nil { + t.Fatalf("failed to insert original chain: %v", err) + } + // Ensure that the state associated with the forking point is pruned away + 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 + // we have not processed any of the blocks (protection against malicious blocks) + if _, err := chain.InsertChain(competitor[:len(competitor)-2]); err != nil { + t.Fatalf("failed to insert competitor chain: %v", err) + } + for i, block := range competitor[:len(competitor)-2] { + if chain.HasState(block.Root()) { + t.Fatalf("competitor %d: low TD chain became processed", i) + } + } + // Import the head of the competitor chain, triggering the reorg and ensure we + // successfully reprocess all the stashed away blocks. + if _, err := chain.InsertChain(competitor[len(competitor)-2:]); err != nil { + t.Fatalf("failed to finalize competitor chain: %v", err) + } + // 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 := state.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") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{address: {Balance: funds}}} + ) + height := uint64(64) + _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil) + + // Import the chain as a ancient-first node and ensure all pointers are updated + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + if err != nil { + t.Fatalf("failed to create temp freezer db: %v", err) + } + defer ancientDb.Close() + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + if n, err := ancient.InsertHeaderChain(headers); err != nil { + t.Fatalf("failed to insert header %d: %v", n, err) + } + if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil { + t.Fatalf("failed to insert receipt %d: %v", n, err) + } + rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior + ancient.Stop() + + // Destroy head fast block manually + midBlock := blocks[len(blocks)/2] + rawdb.WriteHeadFastBlockHash(ancientDb, midBlock.Hash()) + + // Reopen broken blockchain again + 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) + } + if num := ancient.CurrentSnapBlock().Number.Uint64(); num != midBlock.NumberU64() { + t.Errorf("head snap-block mismatch: have #%v, want #%v", num, midBlock.NumberU64()) + } + if num := ancient.CurrentHeader().Number.Uint64(); num != midBlock.NumberU64() { + t.Errorf("head header mismatch: have #%v, want #%v", num, midBlock.NumberU64()) + } +} + +// 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(scheme) + if err != nil { + t.Fatal(err) + } + defer tmpChain.Stop() + // Get the side chain receipts. + if _, err := tmpChain.InsertChain(sideblocks); err != nil { + t.Fatal("processing side chain failed:", err) + } + t.Log("sidechain head:", tmpChain.CurrentBlock().Number, tmpChain.CurrentBlock().Hash()) + sidechainReceipts := make([]types.Receipts, len(sideblocks)) + for i, block := range sideblocks { + sidechainReceipts[i] = tmpChain.GetReceiptsByHash(block.Hash()) + } + // Get the canon chain receipts. + if _, err := tmpChain.InsertChain(canonblocks); err != nil { + t.Fatal("processing canon chain failed:", err) + } + t.Log("canon head:", tmpChain.CurrentBlock().Number, tmpChain.CurrentBlock().Hash()) + canonReceipts := make([]types.Receipts, len(canonblocks)) + for i, block := range canonblocks { + canonReceipts[i] = tmpChain.GetReceiptsByHash(block.Hash()) + } + + // Set up a BlockChain that uses the ancient store. + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) + if err != nil { + t.Fatalf("failed to create temp freezer db: %v", err) + } + defer ancientDb.Close() + + ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer ancientChain.Stop() + + // Import the canonical header chain. + canonHeaders := make([]*types.Header, len(canonblocks)) + for i, block := range canonblocks { + canonHeaders[i] = block.Header() + } + if _, err = ancientChain.InsertHeaderChain(canonHeaders); err != nil { + t.Fatal("can't import canon headers:", err) + } + + // Try to insert blocks/receipts of the side chain. + _, err = ancientChain.InsertReceiptChain(sideblocks, sidechainReceipts, uint64(len(sideblocks))) + if err == nil { + t.Fatal("expected error from InsertReceiptChain.") + } + if ancientChain.CurrentSnapBlock().Number.Uint64() != 0 { + t.Fatalf("failed to rollback ancient data, want %d, have %d", 0, ancientChain.CurrentSnapBlock().Number) + } + if frozen, err := ancientChain.db.Ancients(); err != nil || frozen != 1 { + t.Fatalf("failed to truncate ancient data, frozen index is %d", frozen) + } + + // Insert blocks/receipts of the canonical chain. + _, err = ancientChain.InsertReceiptChain(canonblocks, canonReceipts, uint64(len(canonblocks))) + if err != nil { + t.Fatalf("can't import canon chain receipts: %v", err) + } + if ancientChain.CurrentSnapBlock().Number.Uint64() != canonblocks[len(canonblocks)-1].NumberU64() { + t.Fatalf("failed to insert ancient recept chain after rollback") + } + if frozen, _ := ancientChain.db.Ancients(); frozen != uint64(len(canonblocks))+1 { + t.Fatalf("wrong ancients count %d", frozen) + } +} + +// Tests that importing a very large side fork, which is larger than the canon chain, +// but where the difficulty per block is kept low: this means that it will not +// overtake the 'canon' chain until after it's passed canon by about 200 blocks. +// +// Details at: +// - 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{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + // We must use a pretty long chain to ensure that the fork doesn't overtake us + // until after at least 128 blocks post tip + genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 6*state.TriesInMemory, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + b.OffsetTime(-9) + }) + + // Import the canonical chain + diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", 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.Stop() + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + // Generate fork chain, starting from an early block + parent := blocks[10] + fork, _ := GenerateChain(genesis.Config, parent, engine, genDb, 8*state.TriesInMemory, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{2}) + }) + + // And now import the fork + if i, err := chain.InsertChain(fork); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", i, err) + } + head := chain.CurrentBlock() + if got := fork[len(fork)-1].Hash(); got != head.Hash() { + t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) + } + // Sanity check that all the canonical numbers are present + header := chain.CurrentHeader() + for number := head.Number.Uint64(); number > 0; number-- { + if hash := chain.GetHeaderByNumber(number).Hash(); hash != header.Hash() { + t.Fatalf("header %d: canonical hash mismatch: have %x, want %x", number, hash, header.Hash()) + } + header = chain.GetHeader(header.ParentHash, number-1) + } +} + +// Tests that importing a sidechain (S), where +// - S is sidechain, containing blocks [Sn...Sm] +// - C is canon chain, containing blocks [G..Cn..Cm] +// - A common ancestor is placed at prune-point + blocksBetweenCommonAncestorAndPruneblock +// - The sidechain S is prepended with numCanonBlocksInSidechain blocks from the canon chain +// +// The mergePoint can be these values: +// -1: the transition won't happen +// 0: the transition happens since genesis +// 1: the transition happens after some chain segments +func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommonAncestorAndPruneblock int, mergePoint int) { + // Generate a canonical chain to act as the main dataset + chainConfig := *params.TestChainConfig + var ( + engine = beacon.New(ethash.NewFaker()) + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + nonce = uint64(0) + + gspec = &Genesis{ + Config: &chainConfig, + Alloc: types.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + signer = types.LatestSigner(gspec.Config) + mergeBlock = math.MaxInt32 + ) + // Generate and 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) + } + defer chain.Stop() + + // Activate the transition since genesis if required + if mergePoint == 0 { + mergeBlock = 0 + + // Set the terminal total difficulty in the config + gspec.Config.TerminalTotalDifficulty = big.NewInt(0) + } + genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 2*state.TriesInMemory, func(i int, gen *BlockGen) { + tx, err := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("deadbeef"), big.NewInt(100), 21000, big.NewInt(int64(i+1)*params.GWei), nil), signer, key) + if err != nil { + t.Fatalf("failed to create tx: %v", err) + } + gen.AddTx(tx) + if int(gen.header.Number.Uint64()) >= mergeBlock { + gen.SetPoS() + } + nonce++ + }) + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + lastPrunedIndex := len(blocks) - state.TriesInMemory - 1 + lastPrunedBlock := blocks[lastPrunedIndex] + firstNonPrunedBlock := blocks[len(blocks)-state.TriesInMemory] + + // Verify pruning of lastPrunedBlock + if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) { + t.Errorf("Block %d not pruned", lastPrunedBlock.NumberU64()) + } + // Verify firstNonPrunedBlock is not pruned + if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) { + t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64()) + } + + // Activate the transition in the middle of the chain + if mergePoint == 1 { + // Set the terminal total difficulty in the config + ttd := big.NewInt(int64(len(blocks))) + ttd.Mul(ttd, params.GenesisDifficulty) + gspec.Config.TerminalTotalDifficulty = ttd + mergeBlock = len(blocks) + } + + // Generate the sidechain + // First block should be a known block, block after should be a pruned block. So + // canon(pruned), side, side... + + // Generate fork chain, make it longer than canon + parentIndex := lastPrunedIndex + blocksBetweenCommonAncestorAndPruneblock + parent := blocks[parentIndex] + fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*state.TriesInMemory, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{2}) + if int(b.header.Number.Uint64()) >= mergeBlock { + b.SetPoS() + } + }) + // Prepend the parent(s) + var sidechain []*types.Block + for i := numCanonBlocksInSidechain; i > 0; i-- { + sidechain = append(sidechain, blocks[parentIndex+1-i]) + } + sidechain = append(sidechain, fork...) + n, err := chain.InsertChain(sidechain) + if err != nil { + t.Errorf("Got error, %v number %d - %d", err, sidechain[n].NumberU64(), n) + } + head := chain.CurrentBlock() + if got := fork[len(fork)-1].Hash(); got != head.Hash() { + t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) + } +} + +// Tests that importing a sidechain (S), where +// - S is sidechain, containing blocks [Sn...Sm] +// - C is canon chain, containing blocks [G..Cn..Cm] +// - The common ancestor Cc is pruned +// - The first block in S: Sn, is == Cn +// +// That is: the sidechain for import contains some blocks already present in canon chain. +// So the blocks are: +// +// [ Cn, Cn+1, Cc, Sn+3 ... Sm] +// ^ ^ ^ pruned +func TestPrunedImportSide(t *testing.T) { + //glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) + //glogger.Verbosity(3) + //log.Root().SetHandler(log.Handler(glogger)) + testSideImport(t, 3, 3, -1) + testSideImport(t, 3, -3, -1) + testSideImport(t, 10, 0, -1) + testSideImport(t, 1, 10, -1) + testSideImport(t, 1, -10, -1) +} + +func TestPrunedImportSideWithMerging(t *testing.T) { + //glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))) + //glogger.Verbosity(3) + //log.Root().SetHandler(log.Handler(glogger)) + testSideImport(t, 3, 3, 0) + testSideImport(t, 3, -3, 0) + testSideImport(t, 10, 0, 0) + testSideImport(t, 1, 10, 0) + testSideImport(t, 1, -10, 0) + + testSideImport(t, 3, 3, 1) + testSideImport(t, 3, -3, 1) + testSideImport(t, 10, 0, 1) + testSideImport(t, 1, 10, 1) + testSideImport(t, 1, -10, 1) +} + +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, scheme string) { + engine := ethash.NewFaker() + genesis := &Genesis{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + genDb, blocks, receipts := GenerateChainWithGenesis(genesis, engine, 32, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + + // A longer chain but total difficulty is lower. + blocks2, receipts2 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 65, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + + // A shorter chain but total difficulty is higher. + blocks3, receipts3 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 64, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + b.OffsetTime(-9) // A higher difficulty + }) + // Import the shared chain and the original canonical one + chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) + if err != nil { + t.Fatalf("failed to create temp freezer db: %v", err) + } + defer chaindb.Close() + + 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) + } + defer chain.Stop() + + var ( + inserter func(blocks []*types.Block, receipts []types.Receipts) error + asserter func(t *testing.T, block *types.Block) + ) + if typ == "headers" { + inserter = func(blocks []*types.Block, receipts []types.Receipts) error { + headers := make([]*types.Header, 0, len(blocks)) + for _, block := range blocks { + headers = append(headers, block.Header()) + } + _, err := chain.InsertHeaderChain(headers) + return err + } + asserter = func(t *testing.T, block *types.Block) { + if chain.CurrentHeader().Hash() != block.Hash() { + t.Fatalf("current head header mismatch, have %v, want %v", chain.CurrentHeader().Hash().Hex(), block.Hash().Hex()) + } + } + } else if typ == "receipts" { + inserter = func(blocks []*types.Block, receipts []types.Receipts) error { + headers := make([]*types.Header, 0, len(blocks)) + for _, block := range blocks { + headers = append(headers, block.Header()) + } + _, err := chain.InsertHeaderChain(headers) + if err != nil { + return err + } + _, err = chain.InsertReceiptChain(blocks, receipts, 0) + return err + } + asserter = func(t *testing.T, block *types.Block) { + if chain.CurrentSnapBlock().Hash() != block.Hash() { + t.Fatalf("current head fast block mismatch, have %v, want %v", chain.CurrentSnapBlock().Hash().Hex(), block.Hash().Hex()) + } + } + } else { + inserter = func(blocks []*types.Block, receipts []types.Receipts) error { + _, err := chain.InsertChain(blocks) + return err + } + asserter = func(t *testing.T, block *types.Block) { + if chain.CurrentBlock().Hash() != block.Hash() { + t.Fatalf("current head block mismatch, have %v, want %v", chain.CurrentBlock().Hash().Hex(), block.Hash().Hex()) + } + } + } + + if err := inserter(blocks, receipts); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + + // Reimport the chain data again. All the imported + // chain data are regarded "known" data. + if err := inserter(blocks, receipts); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + asserter(t, blocks[len(blocks)-1]) + + // Import a long canonical chain with some known data as prefix. + rollback := blocks[len(blocks)/2].NumberU64() + + chain.SetHead(rollback - 1) + if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + asserter(t, blocks2[len(blocks2)-1]) + + // Import a heavier shorter but higher total difficulty chain with some known data as prefix. + if err := inserter(append(blocks, blocks3...), append(receipts, receipts3...)); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + asserter(t, blocks3[len(blocks3)-1]) + + // Import a longer but lower total difficulty chain with some known data as prefix. + if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + // The head shouldn't change. + asserter(t, blocks3[len(blocks3)-1]) + + // Rollback the heavier chain and re-insert the longer chain again + chain.SetHead(rollback - 1) + if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + asserter(t, blocks2[len(blocks2)-1]) +} + +func TestInsertKnownHeadersWithMerging(t *testing.T) { + testInsertKnownChainDataWithMerging(t, "headers", 0) +} +func TestInsertKnownReceiptChainWithMerging(t *testing.T) { + testInsertKnownChainDataWithMerging(t, "receipts", 0) +} +func TestInsertKnownBlocksWithMerging(t *testing.T) { + testInsertKnownChainDataWithMerging(t, "blocks", 0) +} +func TestInsertKnownHeadersAfterMerging(t *testing.T) { + testInsertKnownChainDataWithMerging(t, "headers", 1) +} +func TestInsertKnownReceiptChainAfterMerging(t *testing.T) { + testInsertKnownChainDataWithMerging(t, "receipts", 1) +} +func TestInsertKnownBlocksAfterMerging(t *testing.T) { + testInsertKnownChainDataWithMerging(t, "blocks", 1) +} + +// mergeHeight can be assigned in these values: +// 0: means the merging is applied since genesis +// 1: means the merging is applied after the first segment +func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight int) { + // Copy the TestChainConfig so we can modify it during tests + chainConfig := *params.TestChainConfig + var ( + genesis = &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: &chainConfig, + } + engine = beacon.New(ethash.NewFaker()) + mergeBlock = uint64(math.MaxUint64) + ) + // Apply merging since genesis + if mergeHeight == 0 { + genesis.Config.TerminalTotalDifficulty = big.NewInt(0) + mergeBlock = uint64(0) + } + + genDb, blocks, receipts := GenerateChainWithGenesis(genesis, engine, 32, + func(i int, b *BlockGen) { + if b.header.Number.Uint64() >= mergeBlock { + b.SetPoS() + } + b.SetCoinbase(common.Address{1}) + }) + + // Apply merging after the first segment + if mergeHeight == 1 { + // TTD is genesis diff + blocks + ttd := big.NewInt(1 + int64(len(blocks))) + ttd.Mul(ttd, params.GenesisDifficulty) + genesis.Config.TerminalTotalDifficulty = ttd + mergeBlock = uint64(len(blocks)) + } + // Longer chain and shorter chain + blocks2, receipts2 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 65, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + if b.header.Number.Uint64() >= mergeBlock { + b.SetPoS() + } + }) + blocks3, receipts3 := GenerateChain(genesis.Config, blocks[len(blocks)-1], engine, genDb, 64, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + b.OffsetTime(-9) // Time shifted, difficulty shouldn't be changed + if b.header.Number.Uint64() >= mergeBlock { + b.SetPoS() + } + }) + // Import the shared chain and the original canonical one + chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) + if err != nil { + t.Fatalf("failed to create temp freezer db: %v", err) + } + defer chaindb.Close() + + chain, err := NewBlockChain(chaindb, nil, genesis, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + var ( + inserter func(blocks []*types.Block, receipts []types.Receipts) error + asserter func(t *testing.T, block *types.Block) + ) + if typ == "headers" { + inserter = func(blocks []*types.Block, receipts []types.Receipts) error { + headers := make([]*types.Header, 0, len(blocks)) + for _, block := range blocks { + headers = append(headers, block.Header()) + } + i, err := chain.InsertHeaderChain(headers) + if err != nil { + return fmt.Errorf("index %d, number %d: %w", i, headers[i].Number, err) + } + return err + } + asserter = func(t *testing.T, block *types.Block) { + if chain.CurrentHeader().Hash() != block.Hash() { + t.Fatalf("current head header mismatch, have %v, want %v", chain.CurrentHeader().Hash().Hex(), block.Hash().Hex()) + } + } + } else if typ == "receipts" { + inserter = func(blocks []*types.Block, receipts []types.Receipts) error { + headers := make([]*types.Header, 0, len(blocks)) + for _, block := range blocks { + headers = append(headers, block.Header()) + } + i, err := chain.InsertHeaderChain(headers) + if err != nil { + return fmt.Errorf("index %d: %w", i, err) + } + _, err = chain.InsertReceiptChain(blocks, receipts, 0) + return err + } + asserter = func(t *testing.T, block *types.Block) { + if chain.CurrentSnapBlock().Hash() != block.Hash() { + t.Fatalf("current head fast block mismatch, have %v, want %v", chain.CurrentSnapBlock().Hash().Hex(), block.Hash().Hex()) + } + } + } else { + inserter = func(blocks []*types.Block, receipts []types.Receipts) error { + i, err := chain.InsertChain(blocks) + if err != nil { + return fmt.Errorf("index %d: %w", i, err) + } + return nil + } + asserter = func(t *testing.T, block *types.Block) { + if chain.CurrentBlock().Hash() != block.Hash() { + t.Fatalf("current head block mismatch, have %v, want %v", chain.CurrentBlock().Hash().Hex(), block.Hash().Hex()) + } + } + } + if err := inserter(blocks, receipts); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + + // Reimport the chain data again. All the imported + // chain data are regarded "known" data. + if err := inserter(blocks, receipts); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + asserter(t, blocks[len(blocks)-1]) + + // Import a long canonical chain with some known data as prefix. + rollback := blocks[len(blocks)/2].NumberU64() + chain.SetHead(rollback - 1) + if err := inserter(blocks, receipts); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + asserter(t, blocks[len(blocks)-1]) + + // Import a longer chain with some known data as prefix. + if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + asserter(t, blocks2[len(blocks2)-1]) + + // Import a shorter chain with some known data as prefix. + // The reorg is expected since the fork choice rule is + // already changed. + if err := inserter(append(blocks, blocks3...), append(receipts, receipts3...)); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + // The head shouldn't change. + asserter(t, blocks3[len(blocks3)-1]) + + // Reimport the longer chain again, the reorg is still expected + chain.SetHead(rollback - 1) + if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { + t.Fatalf("failed to insert chain data: %v", err) + } + asserter(t, blocks2[len(blocks2)-1]) +} + +// getLongAndShortChains returns two chains: A is longer, B is heavier. +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{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + // Generate and import the canonical chain, + // Offset the time, to keep the difficulty low + genDb, longChain, _ := GenerateChainWithGenesis(genesis, engine, 80, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + }) + 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) + } + // Generate fork chain, make it shorter than canon, with common ancestor pretty early + parentIndex := 3 + parent := longChain[parentIndex] + heavyChainExt, _ := GenerateChain(genesis.Config, parent, engine, genDb, 75, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{2}) + b.OffsetTime(-9) + }) + var heavyChain []*types.Block + heavyChain = append(heavyChain, longChain[:parentIndex+1]...) + heavyChain = append(heavyChain, heavyChainExt...) + + // Verify that the test is sane + var ( + longerTd = new(big.Int) + shorterTd = new(big.Int) + ) + for index, b := range longChain { + longerTd.Add(longerTd, b.Difficulty()) + if index <= parentIndex { + shorterTd.Add(shorterTd, b.Difficulty()) + } + } + for _, b := range heavyChain { + shorterTd.Add(shorterTd, b.Difficulty()) + } + if shorterTd.Cmp(longerTd) <= 0 { + return nil, nil, nil, nil, fmt.Errorf("test is moot, heavyChain td (%v) must be larger than canon td (%v)", shorterTd, longerTd) + } + longerNum := longChain[len(longChain)-1].NumberU64() + shorterNum := heavyChain[len(heavyChain)-1].NumberU64() + if shorterNum >= longerNum { + return nil, nil, nil, nil, fmt.Errorf("test is moot, heavyChain num (%v) must be lower than canon num (%v)", shorterNum, longerNum) + } + return chain, longChain, heavyChain, genesis, nil +} + +// TestReorgToShorterRemovesCanonMapping tests that if we +// 1. Have a chain [0 ... N .. X] +// 2. Reorg to shorter but heavier chain [0 ... N ... Y] +// 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) { + 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) + } + defer chain.Stop() + + if n, err := chain.InsertChain(canonblocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + canonNum := chain.CurrentBlock().Number.Uint64() + canonHash := chain.CurrentBlock().Hash() + _, err = chain.InsertChain(sideblocks) + if err != nil { + t.Errorf("Got error, %v", err) + } + head := chain.CurrentBlock() + if got := sideblocks[len(sideblocks)-1].Hash(); got != head.Hash() { + t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) + } + // We have now inserted a sidechain. + if blockByNum := chain.GetBlockByNumber(canonNum); blockByNum != nil { + t.Errorf("expected block to be gone: %v", blockByNum.NumberU64()) + } + if headerByNum := chain.GetHeaderByNumber(canonNum); headerByNum != nil { + t.Errorf("expected header to be gone: %v", headerByNum.Number) + } + if blockByHash := chain.GetBlockByHash(canonHash); blockByHash == nil { + t.Errorf("expected block to be present: %x", blockByHash.Hash()) + } + if headerByHash := chain.GetHeaderByHash(canonHash); headerByHash == nil { + t.Errorf("expected header to be present: %x", headerByHash.Hash()) + } +} + +// TestReorgToShorterRemovesCanonMappingHeaderChain is the same scenario +// as TestReorgToShorterRemovesCanonMapping, but applied on headerchain +// imports -- that is, for fast sync +func TestReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T) { + 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) + } + defer chain.Stop() + + // Convert into headers + canonHeaders := make([]*types.Header, len(canonblocks)) + for i, block := range canonblocks { + canonHeaders[i] = block.Header() + } + if n, err := chain.InsertHeaderChain(canonHeaders); err != nil { + t.Fatalf("header %d: failed to insert into chain: %v", n, err) + } + canonNum := chain.CurrentHeader().Number.Uint64() + canonHash := chain.CurrentBlock().Hash() + sideHeaders := make([]*types.Header, len(sideblocks)) + for i, block := range sideblocks { + sideHeaders[i] = block.Header() + } + if n, err := chain.InsertHeaderChain(sideHeaders); err != nil { + t.Fatalf("header %d: failed to insert into chain: %v", n, err) + } + head := chain.CurrentHeader() + if got := sideblocks[len(sideblocks)-1].Hash(); got != head.Hash() { + t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) + } + // We have now inserted a sidechain. + if blockByNum := chain.GetBlockByNumber(canonNum); blockByNum != nil { + t.Errorf("expected block to be gone: %v", blockByNum.NumberU64()) + } + if headerByNum := chain.GetHeaderByNumber(canonNum); headerByNum != nil { + t.Errorf("expected header to be gone: %v", headerByNum.Number.Uint64()) + } + if blockByHash := chain.GetBlockByHash(canonHash); blockByHash == nil { + t.Errorf("expected block to be present: %x", blockByHash.Hash()) + } + if headerByHash := chain.GetHeaderByHash(canonHash); headerByHash == nil { + t.Errorf("expected header to be present: %x", headerByHash.Hash()) + } +} + +// Benchmarks large blocks with value transfers to non-existing accounts +func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) { + var ( + signer = types.HomesteadSigner{} + testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) + bankFunds = big.NewInt(100000000000000000) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + testBankAddress: {Balance: bankFunds}, + common.HexToAddress("0xc0de"): { + Code: []byte{0x60, 0x01, 0x50}, + Balance: big.NewInt(0), + }, // push 1, pop + }, + GasLimit: 100e6, // 100 M + } + ) + // Generate the original common chain segment and the two competing forks + engine := ethash.NewFaker() + + blockGenerator := func(i int, block *BlockGen) { + block.SetCoinbase(common.Address{1}) + for txi := 0; txi < numTxs; txi++ { + uniq := uint64(i*numTxs + txi) + recipient := recipientFn(uniq) + tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, block.header.BaseFee, nil), signer, testBankKey) + if err != nil { + b.Error(err) + } + block.AddTx(tx) + } + } + + _, shared, _ := GenerateChainWithGenesis(gspec, engine, numBlocks, blockGenerator) + b.StopTimer() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Import the shared chain and the original canonical one + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + b.Fatalf("failed to create tester chain: %v", err) + } + b.StartTimer() + if _, err := chain.InsertChain(shared); err != nil { + b.Fatalf("failed to insert shared chain: %v", err) + } + b.StopTimer() + block := chain.GetBlockByHash(chain.CurrentBlock().Hash()) + if got := block.Transactions().Len(); got != numTxs*numBlocks { + b.Fatalf("Transactions were not included, expected %d, got %d", numTxs*numBlocks, got) + } + } +} + +func BenchmarkBlockChain_1x1000ValueTransferToNonexisting(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(new(big.Int).SetUint64(1337 + nonce)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} + +func BenchmarkBlockChain_1x1000ValueTransferToExisting(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + b.StopTimer() + b.ResetTimer() + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(new(big.Int).SetUint64(1337)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} + +func BenchmarkBlockChain_1x1000Executions(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + b.StopTimer() + b.ResetTimer() + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(new(big.Int).SetUint64(0xc0de)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} + +// Tests that importing a some old blocks, where all blocks are before the +// pruning point. +// This internally leads to a sidechain import, since the blocks trigger an +// ErrPrunedAncestor error. +// This may e.g. happen if +// 1. Downloader rollbacks a batch of inserted blocks and exits +// 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{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + // Generate and import the canonical chain + _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*state.TriesInMemory, 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) + } + defer chain.Stop() + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + // 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 := state.TriesInMemory + if scheme == rawdb.PathScheme { + states = state.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)-states] + // Verify firstNonPrunedBlock is not pruned + if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) { + t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64()) + } + // Now re-import some old blocks + blockToReimport := blocks[5:8] + _, err = chain.InsertChain(blockToReimport) + if err != nil { + t.Errorf("Got error, %v", err) + } +} + +// TestDeleteCreateRevert tests a weird state transition corner case that we hit +// while changing the internals of statedb. The workflow is that a contract is +// self destructed, then in a followup transaction (but same block) it's created +// again and the transaction reverted. +// +// The original statedb implementation flushed dirty objects to the tries after +// 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") + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(100000000000000000) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)}, + Nonce: 1, + Balance: big.NewInt(0), + }, + // The address 0xBBBB send 1 wei to 0xAAAA, then reverts + bb: { + Code: []byte{ + byte(vm.PC), // [0] + byte(vm.DUP1), // [0,0] + byte(vm.DUP1), // [0,0,0] + byte(vm.DUP1), // [0,0,0,0] + byte(vm.PUSH1), 0x01, // [0,0,0,0,1] (value) + byte(vm.PUSH2), 0xaa, 0xaa, // [0,0,0,0,1, 0xaaaa] + byte(vm.GAS), + byte(vm.CALL), + byte(vm.REVERT), + }, + Balance: big.NewInt(1), + }, + }, + } + ) + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to AAAA + tx, _ := types.SignTx(types.NewTransaction(0, aa, + big.NewInt(0), 50000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + // One transaction to BBBB + tx, _ = types.SignTx(types.NewTransaction(1, bb, + big.NewInt(0), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + }) + // Import the canonical chain + 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) + } + defer chain.Stop() + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } +} + +// TestDeleteRecreateSlots tests a state-transition that contains both deletion +// and recreation of contract state. +// Contract A exists, has slots 1 and 2 set +// Tx 1: Selfdestruct A +// Tx 2: Re-create A, set slots 3 and 4 +// Expected outcome is that _all_ slots are cleared from A, due to the selfdestruct, +// and then the new slots exist +func TestDeleteRecreateSlots(t *testing.T) { + testDeleteRecreateSlots(t, rawdb.HashScheme) + testDeleteRecreateSlots(t, rawdb.PathScheme) +} + +func testDeleteRecreateSlots(t *testing.T, scheme string) { + var ( + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) + ) + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("01") + aaStorage[common.HexToHash("02")] = common.HexToHash("02") + + // The bb-code needs to CREATE2 the aa contract. It consists of + // both initcode and deployment code + // initcode: + // 1. Set slots 3=3, 4=4, + // 2. Return aaCode + + initCode := []byte{ + byte(vm.PUSH1), 0x3, // value + byte(vm.PUSH1), 0x3, // location + byte(vm.SSTORE), // Set slot[3] = 3 + byte(vm.PUSH1), 0x4, // value + byte(vm.PUSH1), 0x4, // location + byte(vm.SSTORE), // Set slot[4] = 4 + // Slots are set, now return the code + byte(vm.PUSH2), byte(vm.PC), byte(vm.SELFDESTRUCT), // Push code on stack + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + // Code is now in memory. + byte(vm.PUSH1), 0x2, // size + byte(vm.PUSH1), byte(32 - 2), // offset + byte(vm.RETURN), + } + if l := len(initCode); l > 32 { + t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") + } + bbCode := []byte{ + // Push initcode onto stack + byte(vm.PUSH1) + byte(len(initCode)-1)} + bbCode = append(bbCode, initCode...) + bbCode = append(bbCode, []byte{ + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + byte(vm.PUSH1), 0x00, // salt + byte(vm.PUSH1), byte(len(initCode)), // size + byte(vm.PUSH1), byte(32 - len(initCode)), // offset + byte(vm.PUSH1), 0x00, // endowment + byte(vm.CREATE2), + }...) + + initHash := crypto.Keccak256Hash(initCode) + aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) + t.Logf("Destination address: %x\n", aa) + + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + Storage: aaStorage, + }, + // The contract BB recreates AA + bb: { + Code: bbCode, + Balance: big.NewInt(1), + }, + }, + } + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to AA, to kill it + tx, _ := types.SignTx(types.NewTransaction(0, aa, + big.NewInt(0), 50000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + // One transaction to BB, to recreate AA + tx, _ = types.SignTx(types.NewTransaction(1, bb, + big.NewInt(0), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + }) + // Import the canonical chain + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ + Tracer: logger.NewJSONLogger(nil, os.Stdout), + }, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + statedb, _ := chain.State() + + // If all is correct, then slot 1 and 2 are zero + if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } + // Also, 3 and 4 should be set + if got, exp := statedb.GetState(aa, common.HexToHash("03")), common.HexToHash("03"); got != exp { + t.Fatalf("got %x exp %x", got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("04")), common.HexToHash("04"); got != exp { + t.Fatalf("got %x exp %x", got, exp) + } +} + +// TestDeleteRecreateAccount tests a state-transition that contains deletion of a +// contract with storage, and a recreate of the same contract via a +// regular value-transfer +// Expected outcome is that _all_ slots are cleared from A +func TestDeleteRecreateAccount(t *testing.T) { + testDeleteRecreateAccount(t, rawdb.HashScheme) + testDeleteRecreateAccount(t, rawdb.PathScheme) +} + +func testDeleteRecreateAccount(t *testing.T, scheme string) { + var ( + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + + aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) + ) + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("01") + aaStorage[common.HexToHash("02")] = common.HexToHash("02") + + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + Storage: aaStorage, + }, + }, + } + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to AA, to kill it + tx, _ := types.SignTx(types.NewTransaction(0, aa, + big.NewInt(0), 50000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + // One transaction to AA, to recreate it (but without storage + tx, _ = types.SignTx(types.NewTransaction(1, aa, + big.NewInt(1), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + }) + // Import the canonical chain + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ + Tracer: logger.NewJSONLogger(nil, os.Stdout), + }, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + statedb, _ := chain.State() + + // If all is correct, then both slots are zero + if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } +} + +// TestDeleteRecreateSlotsAcrossManyBlocks tests multiple state-transition that contains both deletion +// and recreation of contract state. +// Contract A exists, has slots 1 and 2 set +// Tx 1: Selfdestruct A +// Tx 2: Re-create A, set slots 3 and 4 +// Expected outcome is that _all_ slots are cleared from A, due to the selfdestruct, +// and then the new slots exist +func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { + testDeleteRecreateSlotsAcrossManyBlocks(t, rawdb.HashScheme) + testDeleteRecreateSlotsAcrossManyBlocks(t, rawdb.PathScheme) +} + +func testDeleteRecreateSlotsAcrossManyBlocks(t *testing.T, scheme string) { + var ( + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) + ) + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("01") + aaStorage[common.HexToHash("02")] = common.HexToHash("02") + + // The bb-code needs to CREATE2 the aa contract. It consists of + // both initcode and deployment code + // initcode: + // 1. Set slots 3=blocknum+1, 4=4, + // 2. Return aaCode + + initCode := []byte{ + byte(vm.PUSH1), 0x1, // + byte(vm.NUMBER), // value = number + 1 + byte(vm.ADD), // + byte(vm.PUSH1), 0x3, // location + byte(vm.SSTORE), // Set slot[3] = number + 1 + byte(vm.PUSH1), 0x4, // value + byte(vm.PUSH1), 0x4, // location + byte(vm.SSTORE), // Set slot[4] = 4 + // Slots are set, now return the code + byte(vm.PUSH2), byte(vm.PC), byte(vm.SELFDESTRUCT), // Push code on stack + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + // Code is now in memory. + byte(vm.PUSH1), 0x2, // size + byte(vm.PUSH1), byte(32 - 2), // offset + byte(vm.RETURN), + } + if l := len(initCode); l > 32 { + t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") + } + bbCode := []byte{ + // Push initcode onto stack + byte(vm.PUSH1) + byte(len(initCode)-1)} + bbCode = append(bbCode, initCode...) + bbCode = append(bbCode, []byte{ + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + byte(vm.PUSH1), 0x00, // salt + byte(vm.PUSH1), byte(len(initCode)), // size + byte(vm.PUSH1), byte(32 - len(initCode)), // offset + byte(vm.PUSH1), 0x00, // endowment + byte(vm.CREATE2), + }...) + + initHash := crypto.Keccak256Hash(initCode) + aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) + t.Logf("Destination address: %x\n", aa) + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + Storage: aaStorage, + }, + // The contract BB recreates AA + bb: { + Code: bbCode, + Balance: big.NewInt(1), + }, + }, + } + var nonce uint64 + + type expectation struct { + exist bool + blocknum int + values map[int]int + } + var current = &expectation{ + exist: true, // exists in genesis + blocknum: 0, + values: map[int]int{1: 1, 2: 2}, + } + var expectations []*expectation + var newDestruct = func(e *expectation, b *BlockGen) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, aa, + big.NewInt(0), 50000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + nonce++ + if e.exist { + e.exist = false + e.values = nil + } + //t.Logf("block %d; adding destruct\n", e.blocknum) + return tx + } + var newResurrect = func(e *expectation, b *BlockGen) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, bb, + big.NewInt(0), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + nonce++ + if !e.exist { + e.exist = true + e.values = map[int]int{3: e.blocknum + 1, 4: 4} + } + //t.Logf("block %d; adding resurrect\n", e.blocknum) + return tx + } + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 150, func(i int, b *BlockGen) { + var exp = new(expectation) + exp.blocknum = i + 1 + exp.values = make(map[int]int) + for k, v := range current.values { + exp.values[k] = v + } + exp.exist = current.exist + + b.SetCoinbase(common.Address{1}) + if i%2 == 0 { + b.AddTx(newDestruct(exp, b)) + } + if i%3 == 0 { + b.AddTx(newResurrect(exp, b)) + } + if i%5 == 0 { + b.AddTx(newDestruct(exp, b)) + } + if i%7 == 0 { + b.AddTx(newResurrect(exp, b)) + } + expectations = append(expectations, exp) + current = exp + }) + // Import the canonical chain + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ + //Debug: true, + //Tracer: vm.NewJSONLogger(nil, os.Stdout), + }, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + var asHash = func(num int) common.Hash { + return common.BytesToHash([]byte{byte(num)}) + } + for i, block := range blocks { + blockNum := i + 1 + if n, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + statedb, _ := chain.State() + // If all is correct, then slot 1 and 2 are zero + if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { + t.Errorf("block %d, got %x exp %x", blockNum, got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { + t.Errorf("block %d, got %x exp %x", blockNum, got, exp) + } + exp := expectations[i] + if exp.exist { + if !statedb.Exist(aa) { + t.Fatalf("block %d, expected %v to exist, it did not", blockNum, aa) + } + for slot, val := range exp.values { + if gotValue, expValue := statedb.GetState(aa, asHash(slot)), asHash(val); gotValue != expValue { + t.Fatalf("block %d, slot %d, got %x exp %x", blockNum, slot, gotValue, expValue) + } + } + } else { + if statedb.Exist(aa) { + t.Fatalf("block %d, expected %v to not exist, it did", blockNum, aa) + } + } + } +} + +// TestInitThenFailCreateContract tests a pretty notorious case that happened +// on mainnet over blocks 7338108, 7338110 and 7338115. +// - Block 7338108: address e771789f5cccac282f23bb7add5690e1f6ca467c is initiated +// with 0.001 ether (thus created but no code) +// - Block 7338110: a CREATE2 is attempted. The CREATE2 would deploy code on +// the same address e771789f5cccac282f23bb7add5690e1f6ca467c. However, the +// deployment fails due to OOG during initcode execution +// - Block 7338115: another tx checks the balance of +// e771789f5cccac282f23bb7add5690e1f6ca467c, and the snapshotter returned it as +// zero. +// +// The problem being that the snapshotter maintains a destructset, and adds items +// to the destructset in case something is created "onto" an existing item. +// We need to either roll back the snapDestructs, or not place it into snapDestructs +// in the first place. +// + +func TestInitThenFailCreateContract(t *testing.T) { + testInitThenFailCreateContract(t, rawdb.HashScheme) + testInitThenFailCreateContract(t, rawdb.PathScheme) +} + +func testInitThenFailCreateContract(t *testing.T, scheme string) { + var ( + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + ) + + // The bb-code needs to CREATE2 the aa contract. It consists of + // both initcode and deployment code + // initcode: + // 1. If blocknum < 1, error out (e.g invalid opcode) + // 2. else, return a snippet of code + initCode := []byte{ + byte(vm.PUSH1), 0x1, // y (2) + byte(vm.NUMBER), // x (number) + byte(vm.GT), // x > y? + byte(vm.PUSH1), byte(0x8), + byte(vm.JUMPI), // jump to label if number > 2 + byte(0xFE), // illegal opcode + byte(vm.JUMPDEST), + byte(vm.PUSH1), 0x2, // size + byte(vm.PUSH1), 0x0, // offset + byte(vm.RETURN), // return 2 bytes of zero-code + } + if l := len(initCode); l > 32 { + t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") + } + bbCode := []byte{ + // Push initcode onto stack + byte(vm.PUSH1) + byte(len(initCode)-1)} + bbCode = append(bbCode, initCode...) + bbCode = append(bbCode, []byte{ + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + byte(vm.PUSH1), 0x00, // salt + byte(vm.PUSH1), byte(len(initCode)), // size + byte(vm.PUSH1), byte(32 - len(initCode)), // offset + byte(vm.PUSH1), 0x00, // endowment + byte(vm.CREATE2), + }...) + + initHash := crypto.Keccak256Hash(initCode) + aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) + t.Logf("Destination address: %x\n", aa) + + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + // The address aa has some funds + aa: {Balance: big.NewInt(100000)}, + // The contract BB tries to create code onto AA + bb: { + Code: bbCode, + Balance: big.NewInt(1), + }, + }, + } + nonce := uint64(0) + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 4, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to BB + tx, _ := types.SignTx(types.NewTransaction(nonce, bb, + big.NewInt(0), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + nonce++ + }) + + // Import the canonical chain + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ + //Debug: true, + //Tracer: vm.NewJSONLogger(nil, os.Stdout), + }, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + statedb, _ := chain.State() + if got, exp := statedb.GetBalance(aa), uint256.NewInt(100000); got.Cmp(exp) != 0 { + t.Fatalf("Genesis err, got %v exp %v", got, exp) + } + // First block tries to create, but fails + { + block := blocks[0] + if _, err := chain.InsertChain([]*types.Block{blocks[0]}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) + } + statedb, _ = chain.State() + if got, exp := statedb.GetBalance(aa), uint256.NewInt(100000); got.Cmp(exp) != 0 { + t.Fatalf("block %d: got %v exp %v", block.NumberU64(), got, exp) + } + } + // Import the rest of the blocks + for _, block := range blocks[1:] { + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) + } + } +} + +// TestEIP2718Transition tests that an EIP-2718 transaction will be accepted +// after the fork block has passed. This is verified by sending an EIP-2930 +// access list transaction, which specifies a single slot access, and then +// 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() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + // Generate blocks + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + + // One transaction to 0xAAAA + signer := types.LatestSigner(gspec.Config) + tx, _ := types.SignNewTx(key, signer, &types.AccessListTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 30000, + GasPrice: b.header.BaseFee, + AccessList: types.AccessList{{ + Address: aa, + StorageKeys: []common.Hash{{0}}, + }}, + }) + b.AddTx(tx) + }) + + // Import the canonical chain + 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) + } + defer chain.Stop() + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block := chain.GetBlockByNumber(1) + + // Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list + expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + + vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929 + if block.GasUsed() != expected { + t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed()) + } +} + +// TestEIP1559Transition tests the following: +// +// 1. A transaction whose gasFeeCap is greater than the baseFee is valid. +// 2. Gas accounting for access lists on EIP-1559 transactions is correct. +// 3. Only the transaction's tip will be received by the coinbase. +// 4. The transaction sender pays for both the tip and baseFee. +// 5. The coinbase receives only the partially realized tip when +// 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() + + // A sender who makes transactions, has some funds + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + 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: &config, + Alloc: types.GenesisAlloc{ + addr1: {Balance: funds}, + addr2: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + gspec.Config.BerlinBlock = common.Big0 + gspec.Config.LondonBlock = common.Big0 + signer := types.LatestSigner(gspec.Config) + + genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + + // One transaction to 0xAAAA + accesses := types.AccessList{types.AccessTuple{ + Address: aa, + StorageKeys: []common.Hash{{0}}, + }} + + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 30000, + GasFeeCap: newGwei(5), + GasTipCap: big.NewInt(2), + AccessList: accesses, + Data: []byte{}, + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key1) + + b.AddTx(tx) + }) + 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) + } + defer chain.Stop() + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block := chain.GetBlockByNumber(1) + + // 1+2: Ensure EIP-1559 access lists are accounted for via gas usage. + expectedGas := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + + vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929 + if block.GasUsed() != expectedGas { + t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expectedGas, block.GasUsed()) + } + + state, _ := chain.State() + + // 3: Ensure that miner received only the tx's tip. + actual := state.GetBalance(block.Coinbase()).ToBig() + expected := new(big.Int).Add( + new(big.Int).SetUint64(block.GasUsed()*block.Transactions()[0].GasTipCap().Uint64()), + ethash.ConstantinopleBlockReward.ToBig(), + ) + if actual.Cmp(expected) != 0 { + t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) + } + + // 4: Ensure the tx sender paid for the gasUsed * (tip + block baseFee). + actual = new(big.Int).Sub(funds, state.GetBalance(addr1).ToBig()) + expected = new(big.Int).SetUint64(block.GasUsed() * (block.Transactions()[0].GasTipCap().Uint64() + block.BaseFee().Uint64())) + if actual.Cmp(expected) != 0 { + t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) + } + + blocks, _ = GenerateChain(gspec.Config, block, engine, genDb, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{2}) + + txdata := &types.LegacyTx{ + Nonce: 0, + To: &aa, + Gas: 30000, + GasPrice: newGwei(5), + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key2) + + b.AddTx(tx) + }) + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block = chain.GetBlockByNumber(2) + state, _ = chain.State() + effectiveTip := block.Transactions()[0].GasTipCap().Uint64() - block.BaseFee().Uint64() + + // 6+5: Ensure that miner received only the tx's effective tip. + actual = state.GetBalance(block.Coinbase()).ToBig() + expected = new(big.Int).Add( + new(big.Int).SetUint64(block.GasUsed()*effectiveTip), + ethash.ConstantinopleBlockReward.ToBig(), + ) + if actual.Cmp(expected) != 0 { + t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) + } + + // 4: Ensure the tx sender paid for the gasUsed * (effectiveTip + block baseFee). + actual = new(big.Int).Sub(funds, state.GetBalance(addr2).ToBig()) + expected = new(big.Int).SetUint64(block.GasUsed() * (effectiveTip + block.BaseFee().Uint64())) + if actual.Cmp(expected) != 0 { + t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) + } +} + +// 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 ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(100000000000000000) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{address: {Balance: funds}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + signer = types.LatestSigner(gspec.Config) + engine = ethash.NewFaker() + chainLength = 10 + ) + // Generate and import the canonical chain + _, canon, _ := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, gen *BlockGen) { + tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key) + if err != nil { + panic(err) + } + gen.AddTx(tx) + }) + diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", 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) + } + defer chain.Stop() + + if n, err := chain.InsertChain(canon); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + // Generate the side chain and import them + _, side, _ := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, gen *BlockGen) { + tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1), params.TxGas, gen.header.BaseFee, nil), signer, key) + if err != nil { + panic(err) + } + gen.AddTx(tx) + }) + for _, block := range side { + err := chain.InsertBlockWithoutSetHead(block) + if err != nil { + t.Fatalf("Failed to insert into chain: %v", err) + } + } + for _, block := range side { + got := chain.GetBlockByHash(block.Hash()) + if got == nil { + t.Fatalf("Lost the inserted block") + } + } + + // Set the chain head to the side chain, ensure all the relevant markers are updated. + verify := func(head *types.Block) { + if chain.CurrentBlock().Hash() != head.Hash() { + t.Fatalf("Unexpected block hash, want %x, got %x", head.Hash(), chain.CurrentBlock().Hash()) + } + if chain.CurrentSnapBlock().Hash() != head.Hash() { + t.Fatalf("Unexpected fast block hash, want %x, got %x", head.Hash(), chain.CurrentSnapBlock().Hash()) + } + if chain.CurrentHeader().Hash() != head.Hash() { + t.Fatalf("Unexpected head header, want %x, got %x", head.Hash(), chain.CurrentHeader().Hash()) + } + if !chain.HasState(head.Root()) { + t.Fatalf("Lost block state %v %x", head.Number(), head.Hash()) + } + } + chain.SetCanonical(side[len(side)-1]) + verify(side[len(side)-1]) + + // Reset the chain head to original chain + chain.SetCanonical(canon[chainLength-1]) + verify(canon[chainLength-1]) +} + +// 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 + }{ + // ForkA: 10 blocks + // ForkB: 1 blocks + // + // reorged: + // markers [2, 10] should be deleted + // markers [1] should be updated + {10, 1}, + + // ForkA: 10 blocks + // ForkB: 2 blocks + // + // reorged: + // markers [3, 10] should be deleted + // markers [1, 2] should be updated + {10, 2}, + + // ForkA: 10 blocks + // ForkB: 10 blocks + // + // reorged: + // markers [1, 10] should be updated + {10, 10}, + + // ForkA: 10 blocks + // ForkB: 11 blocks + // + // reorged: + // markers [1, 11] should be updated + {10, 11}, + } + for _, c := range cases { + var ( + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + engine = ethash.NewFaker() + ) + _, forkA, _ := GenerateChainWithGenesis(gspec, engine, c.forkA, func(i int, gen *BlockGen) {}) + _, forkB, _ := GenerateChainWithGenesis(gspec, engine, c.forkB, func(i int, gen *BlockGen) {}) + + // Initialize test chain + 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) + } + // Insert forkA and forkB, the canonical should on forkA still + if n, err := chain.InsertChain(forkA); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + if n, err := chain.InsertChain(forkB); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + verify := func(head *types.Block) { + if chain.CurrentBlock().Hash() != head.Hash() { + t.Fatalf("Unexpected block hash, want %x, got %x", head.Hash(), chain.CurrentBlock().Hash()) + } + if chain.CurrentSnapBlock().Hash() != head.Hash() { + t.Fatalf("Unexpected fast block hash, want %x, got %x", head.Hash(), chain.CurrentSnapBlock().Hash()) + } + if chain.CurrentHeader().Hash() != head.Hash() { + t.Fatalf("Unexpected head header, want %x, got %x", head.Hash(), chain.CurrentHeader().Hash()) + } + if !chain.HasState(head.Root()) { + t.Fatalf("Lost block state %v %x", head.Number(), head.Hash()) + } + } + + // Switch canonical chain to forkB if necessary + if len(forkA) < len(forkB) { + verify(forkB[len(forkB)-1]) + } else { + verify(forkA[len(forkA)-1]) + chain.SetCanonical(forkB[len(forkB)-1]) + verify(forkB[len(forkB)-1]) + } + + // Ensure all hash markers are updated correctly + for i := 0; i < len(forkB); i++ { + block := forkB[i] + hash := chain.GetCanonicalHash(block.NumberU64()) + if hash != block.Hash() { + t.Fatalf("Unexpected canonical hash %d", block.NumberU64()) + } + } + if c.forkA > c.forkB { + for i := uint64(c.forkB) + 1; i <= uint64(c.forkA); i++ { + hash := chain.GetCanonicalHash(i) + if hash != (common.Hash{}) { + t.Fatalf("Unexpected canonical hash %d", i) + } + } + } + chain.Stop() + } +} + +func TestCreateThenDeletePreByzantium(t *testing.T) { + // We use Ropsten chain config instead of Testchain config, this is + // deliberate: we want to use pre-byz rules where we have intermediate state roots + // between transactions. + testCreateThenDelete(t, ¶ms.ChainConfig{ + ChainID: big.NewInt(3), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(10), + EIP158Block: big.NewInt(10), + ByzantiumBlock: big.NewInt(1_700_000), + }) +} +func TestCreateThenDeletePostByzantium(t *testing.T) { + testCreateThenDelete(t, params.TestChainConfig) +} + +// testCreateThenDelete tests a creation and subsequent deletion of a contract, happening +// within the same block. +func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { + var ( + engine = ethash.NewFaker() + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + destAddress = crypto.CreateAddress(address, 0) + funds = big.NewInt(1000000000000000) + ) + + // runtime code is 0x60ffff : PUSH1 0xFF SELFDESTRUCT, a.k.a SELFDESTRUCT(0xFF) + code := append([]byte{0x60, 0xff, 0xff}, make([]byte, 32-3)...) + initCode := []byte{ + // SSTORE 1:1 + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x1, + byte(vm.SSTORE), + // Get the runtime-code on the stack + byte(vm.PUSH32)} + initCode = append(initCode, code...) + initCode = append(initCode, []byte{ + byte(vm.PUSH1), 0x0, // offset + byte(vm.MSTORE), + byte(vm.PUSH1), 0x3, // size + byte(vm.PUSH1), 0x0, // offset + byte(vm.RETURN), // return 3 bytes of zero-code + }...) + gspec := &Genesis{ + Config: config, + Alloc: types.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}) + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 100000, + Data: initCode, + }) + nonce++ + b.AddTx(tx) + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 100000, + To: &destAddress, + }) + b.AddTx(tx) + nonce++ + }) + // Import the canonical chain + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{ + //Debug: true, + //Tracer: logger.NewJSONLogger(nil, os.Stdout), + }, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + // Import the blocks + for _, block := range blocks { + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) + } + } +} + +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: types.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) { + var ( + engine = ethash.NewFaker() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + destAddress = crypto.CreateAddress(address, 0) + funds = big.NewInt(1000000000000000) + vmConfig = vm.Config{ + ExtraEips: []int{1153}, // Enable transient storage EIP + } + ) + code := append([]byte{ + // TLoad value with location 1 + byte(vm.PUSH1), 0x1, + byte(vm.TLOAD), + + // PUSH location + byte(vm.PUSH1), 0x1, + + // SStore location:value + byte(vm.SSTORE), + }, make([]byte, 32-6)...) + initCode := []byte{ + // TSTORE 1:1 + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x1, + byte(vm.TSTORE), + + // Get the runtime-code on the stack + byte(vm.PUSH32)} + initCode = append(initCode, code...) + initCode = append(initCode, []byte{ + byte(vm.PUSH1), 0x0, // offset + byte(vm.MSTORE), + byte(vm.PUSH1), 0x6, // size + byte(vm.PUSH1), 0x0, // offset + byte(vm.RETURN), // return 6 bytes of zero-code + }...) + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + }, + } + nonce := uint64(0) + signer := types.HomesteadSigner{} + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + fee := big.NewInt(1) + if b.header.BaseFee != nil { + fee = b.header.BaseFee + } + b.SetCoinbase(common.Address{1}) + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 100000, + Data: initCode, + }) + nonce++ + b.AddTxWithVMConfig(tx, vmConfig) + + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 100000, + To: &destAddress, + }) + b.AddTxWithVMConfig(tx, vmConfig) + nonce++ + }) + + // Initialize the blockchain with 1153 enabled. + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vmConfig, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + // Import the blocks + if _, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("failed to insert into chain: %v", err) + } + // Check the storage + state, err := chain.StateAt(chain.CurrentHeader().Root) + if err != nil { + t.Fatalf("Failed to load state %v", err) + } + loc := common.BytesToHash([]byte{1}) + slot := state.GetState(destAddress, loc) + if slot != (common.Hash{}) { + t.Fatalf("Unexpected dirty storage slot") + } +} + +func TestEIP3651(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + engine = beacon.NewFaker() + + // A sender who makes transactions, has some funds + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + 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: &config, + Alloc: types.GenesisAlloc{ + addr1: {Balance: funds}, + addr2: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + // The address 0xBBBB calls 0xAAAA + bb: { + Code: []byte{ + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.PUSH2), // address + byte(0xaa), + byte(0xaa), + byte(vm.GAS), // gas + byte(vm.DELEGATECALL), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + gspec.Config.BerlinBlock = common.Big0 + gspec.Config.LondonBlock = common.Big0 + gspec.Config.TerminalTotalDifficulty = common.Big0 + gspec.Config.TerminalTotalDifficultyPassed = true + gspec.Config.ShanghaiTime = u64(0) + signer := types.LatestSigner(gspec.Config) + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(aa) + // One transaction to Coinbase + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &bb, + Gas: 500000, + GasFeeCap: newGwei(5), + GasTipCap: big.NewInt(2), + AccessList: nil, + Data: []byte{}, + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key1) + + b.AddTx(tx) + }) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{Tracer: logger.NewMarkdownLogger(&logger.Config{}, os.Stderr).Hooks()}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block := chain.GetBlockByNumber(1) + + // 1+2: Ensure EIP-1559 access lists are accounted for via gas usage. + innerGas := vm.GasQuickStep*2 + params.ColdSloadCostEIP2929*2 + expectedGas := params.TxGas + 5*vm.GasFastestStep + vm.GasQuickStep + 100 + innerGas // 100 because 0xaaaa is in access list + if block.GasUsed() != expectedGas { + t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expectedGas, block.GasUsed()) + } + + state, _ := chain.State() + + // 3: Ensure that miner received only the tx's tip. + actual := state.GetBalance(block.Coinbase()).ToBig() + expected := new(big.Int).SetUint64(block.GasUsed() * block.Transactions()[0].GasTipCap().Uint64()) + if actual.Cmp(expected) != 0 { + t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) + } + + // 4: Ensure the tx sender paid for the gasUsed * (tip + block baseFee). + actual = new(big.Int).Sub(funds, state.GetBalance(addr1).ToBig()) + expected = new(big.Int).SetUint64(block.GasUsed() * (block.Transactions()[0].GasTipCap().Uint64() + block.BaseFee().Uint64())) + if actual.Cmp(expected) != 0 { + t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) + } +} diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go new file mode 100644 index 0000000..68a35d8 --- /dev/null +++ b/core/bloom_indexer.go @@ -0,0 +1,92 @@ +// 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 core + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/bitutil" + "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/ethdb" +) + +const ( + // bloomThrottling is the time to wait between processing two consecutive index + // sections. It's useful during chain upgrades to prevent disk overload. + bloomThrottling = 100 * time.Millisecond +) + +// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index +// for the Ethereum header bloom filters, permitting blazing fast filtering. +type BloomIndexer struct { + size uint64 // section size to generate bloombits for + db ethdb.Database // database instance to write index data and metadata into + gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index + section uint64 // Section is the section number being processed currently + head common.Hash // Head is the hash of the last header processed +} + +// NewBloomIndexer returns a chain indexer that generates bloom bits data for the +// canonical chain for fast logs filtering. +func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *ChainIndexer { + backend := &BloomIndexer{ + db: db, + size: size, + } + table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix)) + + return NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits") +} + +// Reset implements core.ChainIndexerBackend, starting a new bloombits index +// section. +func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { + gen, err := bloombits.NewGenerator(uint(b.size)) + b.gen, b.section, b.head = gen, section, common.Hash{} + return err +} + +// Process implements core.ChainIndexerBackend, adding a new header's bloom into +// the index. +func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error { + b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom) + b.head = header.Hash() + return nil +} + +// Commit implements core.ChainIndexerBackend, finalizing the bloom section and +// writing it out into the database. +func (b *BloomIndexer) Commit() error { + batch := b.db.NewBatchWithSize((int(b.size) / 8) * types.BloomBitLength) + for i := 0; i < types.BloomBitLength; i++ { + bits, err := b.gen.Bitset(uint(i)) + if err != nil { + return err + } + rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits)) + } + return batch.Write() +} + +// Prune returns an empty error since we don't support pruning here. +func (b *BloomIndexer) Prune(threshold uint64) error { + return nil +} diff --git a/core/bloombits/doc.go b/core/bloombits/doc.go new file mode 100644 index 0000000..3d159e7 --- /dev/null +++ b/core/bloombits/doc.go @@ -0,0 +1,18 @@ +// 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 bloombits implements bloom filtering on batches of data. +package bloombits diff --git a/core/bloombits/generator.go b/core/bloombits/generator.go new file mode 100644 index 0000000..646151d --- /dev/null +++ b/core/bloombits/generator.go @@ -0,0 +1,98 @@ +// 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 bloombits + +import ( + "errors" + + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + // errSectionOutOfBounds is returned if the user tried to add more bloom filters + // to the batch than available space, or if tries to retrieve above the capacity. + errSectionOutOfBounds = errors.New("section out of bounds") + + // errBloomBitOutOfBounds is returned if the user tried to retrieve specified + // bit bloom above the capacity. + errBloomBitOutOfBounds = errors.New("bloom bit out of bounds") +) + +// Generator takes a number of bloom filters and generates the rotated bloom bits +// to be used for batched filtering. +type Generator struct { + blooms [types.BloomBitLength][]byte // Rotated blooms for per-bit matching + sections uint // Number of sections to batch together + nextSec uint // Next section to set when adding a bloom +} + +// NewGenerator creates a rotated bloom generator that can iteratively fill a +// batched bloom filter's bits. +func NewGenerator(sections uint) (*Generator, error) { + if sections%8 != 0 { + return nil, errors.New("section count not multiple of 8") + } + b := &Generator{sections: sections} + for i := 0; i < types.BloomBitLength; i++ { + b.blooms[i] = make([]byte, sections/8) + } + return b, nil +} + +// AddBloom takes a single bloom filter and sets the corresponding bit column +// in memory accordingly. +func (b *Generator) AddBloom(index uint, bloom types.Bloom) error { + // Make sure we're not adding more bloom filters than our capacity + if b.nextSec >= b.sections { + return errSectionOutOfBounds + } + if b.nextSec != index { + return errors.New("bloom filter with unexpected index") + } + // Rotate the bloom and insert into our collection + byteIndex := b.nextSec / 8 + bitIndex := byte(7 - b.nextSec%8) + for byt := 0; byt < types.BloomByteLength; byt++ { + bloomByte := bloom[types.BloomByteLength-1-byt] + if bloomByte == 0 { + continue + } + base := 8 * byt + b.blooms[base+7][byteIndex] |= ((bloomByte >> 7) & 1) << bitIndex + b.blooms[base+6][byteIndex] |= ((bloomByte >> 6) & 1) << bitIndex + b.blooms[base+5][byteIndex] |= ((bloomByte >> 5) & 1) << bitIndex + b.blooms[base+4][byteIndex] |= ((bloomByte >> 4) & 1) << bitIndex + b.blooms[base+3][byteIndex] |= ((bloomByte >> 3) & 1) << bitIndex + b.blooms[base+2][byteIndex] |= ((bloomByte >> 2) & 1) << bitIndex + b.blooms[base+1][byteIndex] |= ((bloomByte >> 1) & 1) << bitIndex + b.blooms[base][byteIndex] |= (bloomByte & 1) << bitIndex + } + b.nextSec++ + return nil +} + +// Bitset returns the bit vector belonging to the given bit index after all +// blooms have been added. +func (b *Generator) Bitset(idx uint) ([]byte, error) { + if b.nextSec != b.sections { + return nil, errors.New("bloom not fully generated yet") + } + if idx >= types.BloomBitLength { + return nil, errBloomBitOutOfBounds + } + return b.blooms[idx], nil +} diff --git a/core/bloombits/generator_test.go b/core/bloombits/generator_test.go new file mode 100644 index 0000000..ac1aee0 --- /dev/null +++ b/core/bloombits/generator_test.go @@ -0,0 +1,100 @@ +// 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 bloombits + +import ( + "bytes" + crand "crypto/rand" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/core/types" +) + +// Tests that batched bloom bits are correctly rotated from the input bloom +// filters. +func TestGenerator(t *testing.T) { + // Generate the input and the rotated output + var input, output [types.BloomBitLength][types.BloomByteLength]byte + + for i := 0; i < types.BloomBitLength; i++ { + for j := 0; j < types.BloomBitLength; j++ { + bit := byte(rand.Int() % 2) + + input[i][j/8] |= bit << byte(7-j%8) + output[types.BloomBitLength-1-j][i/8] |= bit << byte(7-i%8) + } + } + // Crunch the input through the generator and verify the result + gen, err := NewGenerator(types.BloomBitLength) + if err != nil { + t.Fatalf("failed to create bloombit generator: %v", err) + } + for i, bloom := range input { + if err := gen.AddBloom(uint(i), bloom); err != nil { + t.Fatalf("bloom %d: failed to add: %v", i, err) + } + } + for i, want := range output { + have, err := gen.Bitset(uint(i)) + if err != nil { + t.Fatalf("output %d: failed to retrieve bits: %v", i, err) + } + if !bytes.Equal(have, want[:]) { + t.Errorf("output %d: bit vector mismatch have %x, want %x", i, have, want) + } + } +} + +func BenchmarkGenerator(b *testing.B) { + var input [types.BloomBitLength][types.BloomByteLength]byte + b.Run("empty", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Crunch the input through the generator and verify the result + gen, err := NewGenerator(types.BloomBitLength) + if err != nil { + b.Fatalf("failed to create bloombit generator: %v", err) + } + for j, bloom := range &input { + if err := gen.AddBloom(uint(j), bloom); err != nil { + b.Fatalf("bloom %d: failed to add: %v", i, err) + } + } + } + }) + for i := 0; i < types.BloomBitLength; i++ { + crand.Read(input[i][:]) + } + b.Run("random", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Crunch the input through the generator and verify the result + gen, err := NewGenerator(types.BloomBitLength) + if err != nil { + b.Fatalf("failed to create bloombit generator: %v", err) + } + for j, bloom := range &input { + if err := gen.AddBloom(uint(j), bloom); err != nil { + b.Fatalf("bloom %d: failed to add: %v", i, err) + } + } + } + }) +} diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go new file mode 100644 index 0000000..486581f --- /dev/null +++ b/core/bloombits/matcher.go @@ -0,0 +1,649 @@ +// 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 bloombits + +import ( + "bytes" + "context" + "errors" + "math" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common/bitutil" + "github.com/ethereum/go-ethereum/crypto" +) + +// bloomIndexes represents the bit indexes inside the bloom filter that belong +// to some key. +type bloomIndexes [3]uint + +// calcBloomIndexes returns the bloom filter bit indexes belonging to the given key. +func calcBloomIndexes(b []byte) bloomIndexes { + b = crypto.Keccak256(b) + + var idxs bloomIndexes + for i := 0; i < len(idxs); i++ { + idxs[i] = (uint(b[2*i])<<8)&2047 + uint(b[2*i+1]) + } + return idxs +} + +// partialMatches with a non-nil vector represents a section in which some sub- +// matchers have already found potential matches. Subsequent sub-matchers will +// binary AND their matches with this vector. If vector is nil, it represents a +// section to be processed by the first sub-matcher. +type partialMatches struct { + section uint64 + bitset []byte +} + +// Retrieval represents a request for retrieval task assignments for a given +// 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 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 + Sections []uint64 + Bitsets [][]byte + + Context context.Context + Error error +} + +// Matcher is a pipelined system of schedulers and logic matchers which perform +// binary AND/OR operations on the bit-streams, creating a stream of potential +// blocks to inspect for data content. +type Matcher struct { + sectionSize uint64 // Size of the data batches to filter on + + filters [][]bloomIndexes // Filter the system is matching for + schedulers map[uint]*scheduler // Retrieval schedulers for loading bloom bits + + retrievers chan chan uint // Retriever processes waiting for bit allocations + counters chan chan uint // Retriever processes waiting for task count reports + retrievals chan chan *Retrieval // Retriever processes waiting for task allocations + deliveries chan *Retrieval // Retriever processes waiting for task response deliveries + + running atomic.Bool // Atomic flag whether a session is live or not +} + +// NewMatcher creates a new pipeline for retrieving bloom bit streams and doing +// address and topic filtering on them. Setting a filter component to `nil` is +// allowed and will result in that filter rule being skipped (OR 0x11...1). +func NewMatcher(sectionSize uint64, filters [][][]byte) *Matcher { + // Create the matcher instance + m := &Matcher{ + sectionSize: sectionSize, + schedulers: make(map[uint]*scheduler), + retrievers: make(chan chan uint), + counters: make(chan chan uint), + retrievals: make(chan chan *Retrieval), + deliveries: make(chan *Retrieval), + } + // Calculate the bloom bit indexes for the groups we're interested in + m.filters = nil + + for _, filter := range filters { + // Gather the bit indexes of the filter rule, special casing the nil filter + if len(filter) == 0 { + continue + } + bloomBits := make([]bloomIndexes, len(filter)) + for i, clause := range filter { + if clause == nil { + bloomBits = nil + break + } + bloomBits[i] = calcBloomIndexes(clause) + } + // Accumulate the filter rules if no nil rule was within + if bloomBits != nil { + m.filters = append(m.filters, bloomBits) + } + } + // For every bit, create a scheduler to load/download the bit vectors + for _, bloomIndexLists := range m.filters { + for _, bloomIndexList := range bloomIndexLists { + for _, bloomIndex := range bloomIndexList { + m.addScheduler(bloomIndex) + } + } + } + return m +} + +// addScheduler adds a bit stream retrieval scheduler for the given bit index if +// it has not existed before. If the bit is already selected for filtering, the +// existing scheduler can be used. +func (m *Matcher) addScheduler(idx uint) { + if _, ok := m.schedulers[idx]; ok { + return + } + m.schedulers[idx] = newScheduler(idx) +} + +// Start starts the matching process and returns a stream of bloom matches in +// a given range of blocks. If there are no more matches in the range, the result +// channel is closed. +func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uint64) (*MatcherSession, error) { + // Make sure we're not creating concurrent sessions + if m.running.Swap(true) { + return nil, errors.New("matcher already running") + } + defer m.running.Store(false) + + // Initiate a new matching round + session := &MatcherSession{ + matcher: m, + quit: make(chan struct{}), + ctx: ctx, + } + for _, scheduler := range m.schedulers { + scheduler.reset() + } + sink := m.run(begin, end, cap(results), session) + + // Read the output from the result sink and deliver to the user + session.pend.Add(1) + go func() { + defer session.pend.Done() + defer close(results) + + for { + select { + case <-session.quit: + return + + case res, ok := <-sink: + // New match result found + if !ok { + return + } + // Calculate the first and last blocks of the section + sectionStart := res.section * m.sectionSize + + first := sectionStart + if begin > first { + first = begin + } + last := sectionStart + m.sectionSize - 1 + if end < last { + last = end + } + // Iterate over all the blocks in the section and return the matching ones + for i := first; i <= last; i++ { + // Skip the entire byte if no matches are found inside (and we're processing an entire byte!) + next := res.bitset[(i-sectionStart)/8] + if next == 0 { + if i%8 == 0 { + i += 7 + } + continue + } + // Some bit it set, do the actual submatching + if bit := 7 - i%8; next&(1<= req.section }) + requests[req.bit] = append(queue[:index], append([]uint64{req.section}, queue[index:]...)...) + + // If it's a new bit and we have waiting fetchers, allocate to them + if len(queue) == 0 { + assign(req.bit) + } + + case fetcher := <-retrievers: + // New retriever arrived, find the lowest section-ed bit to assign + bit, best := uint(0), uint64(math.MaxUint64) + for idx := range unallocs { + if requests[idx][0] < best { + bit, best = idx, requests[idx][0] + } + } + // Stop tracking this bit (and alloc notifications if no more work is available) + delete(unallocs, bit) + if len(unallocs) == 0 { + retrievers = nil + } + allocs++ + fetcher <- bit + + case fetcher := <-m.counters: + // New task count request arrives, return number of items + fetcher <- uint(len(requests[<-fetcher])) + + case fetcher := <-m.retrievals: + // New fetcher waiting for tasks to retrieve, assign + task := <-fetcher + if want := len(task.Sections); want >= len(requests[task.Bit]) { + task.Sections = requests[task.Bit] + delete(requests, task.Bit) + } else { + task.Sections = append(task.Sections[:0], requests[task.Bit][:want]...) + requests[task.Bit] = append(requests[task.Bit][:0], requests[task.Bit][want:]...) + } + fetcher <- task + + // If anything was left unallocated, try to assign to someone else + if len(requests[task.Bit]) > 0 { + assign(task.Bit) + } + + case result := <-m.deliveries: + // New retrieval task response from fetcher, split out missing sections and + // deliver complete ones + var ( + sections = make([]uint64, 0, len(result.Sections)) + bitsets = make([][]byte, 0, len(result.Bitsets)) + missing = make([]uint64, 0, len(result.Sections)) + ) + for i, bitset := range result.Bitsets { + if len(bitset) == 0 { + missing = append(missing, result.Sections[i]) + continue + } + sections = append(sections, result.Sections[i]) + bitsets = append(bitsets, bitset) + } + m.schedulers[result.Bit].deliver(sections, bitsets) + allocs-- + + // Reschedule missing sections and allocate bit if newly available + if len(missing) > 0 { + queue := requests[result.Bit] + for _, section := range missing { + index := sort.Search(len(queue), func(i int) bool { return queue[i] >= section }) + queue = append(queue[:index], append([]uint64{section}, queue[index:]...)...) + } + requests[result.Bit] = queue + + if len(queue) == len(missing) { + assign(result.Bit) + } + } + + // End the session when all pending deliveries have arrived. + if shutdown == nil && allocs == 0 { + return + } + } + } +} + +// MatcherSession is returned by a started matcher to be used as a terminator +// for the actively running matching operation. +type MatcherSession struct { + matcher *Matcher + + closer sync.Once // Sync object to ensure we only ever close once + quit chan struct{} // Quit channel to request pipeline termination + + ctx context.Context // Context used by the light client to abort filtering + err error // Global error to track retrieval failures deep in the chain + errLock sync.Mutex + + pend sync.WaitGroup +} + +// Close stops the matching process and waits for all subprocesses to terminate +// before returning. The timeout may be used for graceful shutdown, allowing the +// currently running retrievals to complete before this time. +func (s *MatcherSession) Close() { + s.closer.Do(func() { + // Signal termination and wait for all goroutines to tear down + close(s.quit) + s.pend.Wait() + }) +} + +// Error returns any failure encountered during the matching session. +func (s *MatcherSession) Error() error { + s.errLock.Lock() + defer s.errLock.Unlock() + + return s.err +} + +// allocateRetrieval assigns a bloom bit index to a client process that can either +// immediately request and fetch the section contents assigned to this bit or wait +// a little while for more sections to be requested. +func (s *MatcherSession) allocateRetrieval() (uint, bool) { + fetcher := make(chan uint) + + select { + case <-s.quit: + return 0, false + case s.matcher.retrievers <- fetcher: + bit, ok := <-fetcher + return bit, ok + } +} + +// pendingSections returns the number of pending section retrievals belonging to +// the given bloom bit index. +func (s *MatcherSession) pendingSections(bit uint) int { + fetcher := make(chan uint) + + select { + case <-s.quit: + return 0 + case s.matcher.counters <- fetcher: + fetcher <- bit + return int(<-fetcher) + } +} + +// allocateSections assigns all or part of an already allocated bit-task queue +// to the requesting process. +func (s *MatcherSession) allocateSections(bit uint, count int) []uint64 { + fetcher := make(chan *Retrieval) + + select { + case <-s.quit: + return nil + case s.matcher.retrievals <- fetcher: + task := &Retrieval{ + Bit: bit, + Sections: make([]uint64, count), + } + fetcher <- task + return (<-fetcher).Sections + } +} + +// deliverSections delivers a batch of section bit-vectors for a specific bloom +// bit index to be injected into the processing pipeline. +func (s *MatcherSession) deliverSections(bit uint, sections []uint64, bitsets [][]byte) { + s.matcher.deliveries <- &Retrieval{Bit: bit, Sections: sections, Bitsets: bitsets} +} + +// Multiplex polls the matcher session for retrieval tasks and multiplexes it into +// the requested retrieval queue to be serviced together with other sessions. +// +// This method will block for the lifetime of the session. Even after termination +// of the session, any request in-flight need to be responded to! Empty responses +// are fine though in that case. +func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) { + waitTimer := time.NewTimer(wait) + defer waitTimer.Stop() + + for { + // Allocate a new bloom bit index to retrieve data for, stopping when done + bit, ok := s.allocateRetrieval() + if !ok { + return + } + // Bit allocated, throttle a bit if we're below our batch limit + if s.pendingSections(bit) < batch { + waitTimer.Reset(wait) + select { + case <-s.quit: + // Session terminating, we can't meaningfully service, abort + s.allocateSections(bit, 0) + s.deliverSections(bit, []uint64{}, [][]byte{}) + return + + case <-waitTimer.C: + // Throttling up, fetch whatever is available + } + } + // Allocate as much as we can handle and request servicing + sections := s.allocateSections(bit, batch) + request := make(chan *Retrieval) + + select { + case <-s.quit: + // Session terminating, we can't meaningfully service, abort + s.deliverSections(bit, sections, make([][]byte, len(sections))) + return + + case mux <- request: + // Retrieval accepted, something must arrive before we're aborting + 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() + } + } + } +} diff --git a/core/bloombits/matcher_test.go b/core/bloombits/matcher_test.go new file mode 100644 index 0000000..7f3d5f2 --- /dev/null +++ b/core/bloombits/matcher_test.go @@ -0,0 +1,292 @@ +// 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 bloombits + +import ( + "context" + "math/rand" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" +) + +const testSectionSize = 4096 + +// Tests that wildcard filter rules (nil) can be specified and are handled well. +func TestMatcherWildcards(t *testing.T) { + t.Parallel() + matcher := NewMatcher(testSectionSize, [][][]byte{ + {common.Address{}.Bytes(), common.Address{0x01}.Bytes()}, // Default address is not a wildcard + {common.Hash{}.Bytes(), common.Hash{0x01}.Bytes()}, // Default hash is not a wildcard + {common.Hash{0x01}.Bytes()}, // Plain rule, sanity check + {common.Hash{0x01}.Bytes(), nil}, // Wildcard suffix, drop rule + {nil, common.Hash{0x01}.Bytes()}, // Wildcard prefix, drop rule + {nil, nil}, // Wildcard combo, drop rule + {}, // Inited wildcard rule, drop rule + nil, // Proper wildcard rule, drop rule + }) + if len(matcher.filters) != 3 { + t.Fatalf("filter system size mismatch: have %d, want %d", len(matcher.filters), 3) + } + if len(matcher.filters[0]) != 2 { + t.Fatalf("address clause size mismatch: have %d, want %d", len(matcher.filters[0]), 2) + } + if len(matcher.filters[1]) != 2 { + t.Fatalf("combo topic clause size mismatch: have %d, want %d", len(matcher.filters[1]), 2) + } + if len(matcher.filters[2]) != 1 { + t.Fatalf("singletone topic clause size mismatch: have %d, want %d", len(matcher.filters[2]), 1) + } +} + +// Tests the matcher pipeline on a single continuous workflow without interrupts. +func TestMatcherContinuous(t *testing.T) { + t.Parallel() + testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, false, 75) + testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, false, 81) + testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, false, 36) +} + +// Tests the matcher pipeline on a constantly interrupted and resumed work pattern +// with the aim of ensuring data items are requested only once. +func TestMatcherIntermittent(t *testing.T) { + t.Parallel() + testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, true, 75) + testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, true, 81) + testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, true, 36) +} + +// Tests the matcher pipeline on random input to hopefully catch anomalies. +func TestMatcherRandom(t *testing.T) { + t.Parallel() + for i := 0; i < 10; i++ { + testMatcherBothModes(t, makeRandomIndexes([]int{1}, 50), 0, 10000, 0) + testMatcherBothModes(t, makeRandomIndexes([]int{3}, 50), 0, 10000, 0) + testMatcherBothModes(t, makeRandomIndexes([]int{2, 2, 2}, 20), 0, 10000, 0) + testMatcherBothModes(t, makeRandomIndexes([]int{5, 5, 5}, 50), 0, 10000, 0) + testMatcherBothModes(t, makeRandomIndexes([]int{4, 4, 4}, 20), 0, 10000, 0) + } +} + +// Tests that the matcher can properly find matches if the starting block is +// 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() + // Block 0 always matches in the tests, skip ahead of first 8 blocks with the + // start to get a potential zero byte in the matcher bitset. + + // To keep the second bitset byte zero, the filter must only match for the first + // time in block 16, so doing an all-16 bit filter should suffice. + + // To keep the starting block non divisible by 8, block number 9 is the first + // that would introduce a shift and not match block 0. + testMatcherBothModes(t, [][]bloomIndexes{{{16, 16, 16}}}, 9, 64, 0) +} + +// Tests that matching on everything doesn't crash (special case internally). +func TestWildcardMatcher(t *testing.T) { + t.Parallel() + testMatcherBothModes(t, nil, 0, 10000, 0) +} + +// 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 { + res := make([][]bloomIndexes, len(lengths)) + for i, topics := range lengths { + res[i] = make([]bloomIndexes, topics) + for j := 0; j < topics; j++ { + for k := 0; k < len(res[i][j]); k++ { + res[i][j][k] = uint(rand.Intn(max-1) + 2) + } + } + } + return res +} + +// testMatcherDiffBatches runs the given matches test in single-delivery and also +// in batches delivery mode, verifying that all kinds of deliveries are handled +// correctly within. +func testMatcherDiffBatches(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, intermittent bool, retrievals uint32) { + singleton := testMatcher(t, filter, start, blocks, intermittent, retrievals, 1) + batched := testMatcher(t, filter, start, blocks, intermittent, retrievals, 16) + + if singleton != batched { + t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, %v in singleton vs. %v in batched mode", filter, blocks, intermittent, singleton, batched) + } +} + +// testMatcherBothModes runs the given matcher test in both continuous as well as +// in intermittent mode, verifying that the request counts match each other. +func testMatcherBothModes(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, retrievals uint32) { + continuous := testMatcher(t, filter, start, blocks, false, retrievals, 16) + intermittent := testMatcher(t, filter, start, blocks, true, retrievals, 16) + + if continuous != intermittent { + t.Errorf("filter = %v blocks = %v: request count mismatch, %v in continuous vs. %v in intermittent mode", filter, blocks, continuous, intermittent) + } +} + +// testMatcher is a generic tester to run the given matcher test and return the +// number of requests made for cross validation between different modes. +func testMatcher(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, intermittent bool, retrievals uint32, maxReqCount int) uint32 { + // Create a new matcher an simulate our explicit random bitsets + matcher := NewMatcher(testSectionSize, nil) + matcher.filters = filter + + for _, rule := range filter { + for _, topic := range rule { + for _, bit := range topic { + matcher.addScheduler(bit) + } + } + } + // Track the number of retrieval requests made + var requested atomic.Uint32 + + // Start the matching session for the filter and the retriever goroutines + quit := make(chan struct{}) + matches := make(chan uint64, 16) + + session, err := matcher.Start(context.Background(), start, blocks-1, matches) + if err != nil { + t.Fatalf("failed to stat matcher session: %v", err) + } + startRetrievers(session, quit, &requested, maxReqCount) + + // Iterate over all the blocks and verify that the pipeline produces the correct matches + for i := start; i < blocks; i++ { + if expMatch3(filter, i) { + match, ok := <-matches + if !ok { + t.Errorf("filter = %v blocks = %v intermittent = %v: expected #%v, results channel closed", filter, blocks, intermittent, i) + return 0 + } + if match != i { + t.Errorf("filter = %v blocks = %v intermittent = %v: expected #%v, got #%v", filter, blocks, intermittent, i, match) + } + // If we're testing intermittent mode, abort and restart the pipeline + if intermittent { + session.Close() + close(quit) + + quit = make(chan struct{}) + matches = make(chan uint64, 16) + + session, err = matcher.Start(context.Background(), i+1, blocks-1, matches) + if err != nil { + t.Fatalf("failed to stat matcher session: %v", err) + } + startRetrievers(session, quit, &requested, maxReqCount) + } + } + } + // Ensure the result channel is torn down after the last block + match, ok := <-matches + if ok { + t.Errorf("filter = %v blocks = %v intermittent = %v: expected closed channel, got #%v", filter, blocks, intermittent, match) + } + // Clean up the session and ensure we match the expected retrieval count + session.Close() + close(quit) + + if retrievals != 0 && requested.Load() != retrievals { + t.Errorf("filter = %v blocks = %v intermittent = %v: request count mismatch, have #%v, want #%v", filter, blocks, intermittent, requested.Load(), retrievals) + } + return requested.Load() +} + +// startRetrievers starts a batch of goroutines listening for section requests +// and serving them. +func startRetrievers(session *MatcherSession, quit chan struct{}, retrievals *atomic.Uint32, batch int) { + requests := make(chan chan *Retrieval) + + for i := 0; i < 10; i++ { + // Start a multiplexer to test multiple threaded execution + go session.Multiplex(batch, 100*time.Microsecond, requests) + + // Start a services to match the above multiplexer + go func() { + for { + // Wait for a service request or a shutdown + select { + case <-quit: + return + + case request := <-requests: + task := <-request + + task.Bitsets = make([][]byte, len(task.Sections)) + for i, section := range task.Sections { + if rand.Int()%4 != 0 { // Handle occasional missing deliveries + task.Bitsets[i] = generateBitset(task.Bit, section) + retrievals.Add(1) + } + } + request <- task + } + } + }() + } +} + +// generateBitset generates the rotated bitset for the given bloom bit and section +// numbers. +func generateBitset(bit uint, section uint64) []byte { + bitset := make([]byte, testSectionSize/8) + for i := 0; i < len(bitset); i++ { + for b := 0; b < 8; b++ { + blockIdx := section*testSectionSize + uint64(i*8+b) + bitset[i] += bitset[i] + if (blockIdx % uint64(bit)) == 0 { + bitset[i]++ + } + } + } + return bitset +} + +func expMatch1(filter bloomIndexes, i uint64) bool { + for _, ii := range filter { + if (i % uint64(ii)) != 0 { + return false + } + } + return true +} + +func expMatch2(filter []bloomIndexes, i uint64) bool { + for _, ii := range filter { + if expMatch1(ii, i) { + return true + } + } + return false +} + +func expMatch3(filter [][]bloomIndexes, i uint64) bool { + for _, ii := range filter { + if !expMatch2(ii, i) { + return false + } + } + return true +} diff --git a/core/bloombits/scheduler.go b/core/bloombits/scheduler.go new file mode 100644 index 0000000..a523bc5 --- /dev/null +++ b/core/bloombits/scheduler.go @@ -0,0 +1,181 @@ +// 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 bloombits + +import ( + "sync" +) + +// request represents a bloom retrieval task to prioritize and pull from the local +// database or remotely from the network. +type request struct { + section uint64 // Section index to retrieve the bit-vector from + bit uint // Bit index within the section to retrieve the vector of +} + +// response represents the state of a requested bit-vector through a scheduler. +type response struct { + cached []byte // Cached bits to dedup multiple requests + done chan struct{} // Channel to allow waiting for completion +} + +// scheduler handles the scheduling of bloom-filter retrieval operations for +// entire section-batches belonging to a single bloom bit. Beside scheduling the +// retrieval operations, this struct also deduplicates the requests and caches +// the results to minimize network/database overhead even in complex filtering +// scenarios. +type scheduler struct { + bit uint // Index of the bit in the bloom filter this scheduler is responsible for + responses map[uint64]*response // Currently pending retrieval requests or already cached responses + lock sync.Mutex // Lock protecting the responses from concurrent access +} + +// newScheduler creates a new bloom-filter retrieval scheduler for a specific +// bit index. +func newScheduler(idx uint) *scheduler { + return &scheduler{ + bit: idx, + responses: make(map[uint64]*response), + } +} + +// run creates a retrieval pipeline, receiving section indexes from sections and +// returning the results in the same order through the done channel. Concurrent +// runs of the same scheduler are allowed, leading to retrieval task deduplication. +func (s *scheduler) run(sections chan uint64, dist chan *request, done chan []byte, quit chan struct{}, wg *sync.WaitGroup) { + // Create a forwarder channel between requests and responses of the same size as + // the distribution channel (since that will block the pipeline anyway). + pend := make(chan uint64, cap(dist)) + + // Start the pipeline schedulers to forward between user -> distributor -> user + wg.Add(2) + go s.scheduleRequests(sections, dist, pend, quit, wg) + go s.scheduleDeliveries(pend, done, quit, wg) +} + +// reset cleans up any leftovers from previous runs. This is required before a +// restart to ensure the no previously requested but never delivered state will +// cause a lockup. +func (s *scheduler) reset() { + s.lock.Lock() + defer s.lock.Unlock() + + for section, res := range s.responses { + if res.cached == nil { + delete(s.responses, section) + } + } +} + +// scheduleRequests reads section retrieval requests from the input channel, +// deduplicates the stream and pushes unique retrieval tasks into the distribution +// channel for a database or network layer to honour. +func (s *scheduler) scheduleRequests(reqs chan uint64, dist chan *request, pend chan uint64, quit chan struct{}, wg *sync.WaitGroup) { + // Clean up the goroutine and pipeline when done + defer wg.Done() + defer close(pend) + + // Keep reading and scheduling section requests + for { + select { + case <-quit: + return + + case section, ok := <-reqs: + // New section retrieval requested + if !ok { + return + } + // Deduplicate retrieval requests + unique := false + + s.lock.Lock() + if s.responses[section] == nil { + s.responses[section] = &response{ + done: make(chan struct{}), + } + unique = true + } + s.lock.Unlock() + + // Schedule the section for retrieval and notify the deliverer to expect this section + if unique { + select { + case <-quit: + return + case dist <- &request{bit: s.bit, section: section}: + } + } + select { + case <-quit: + return + case pend <- section: + } + } + } +} + +// scheduleDeliveries reads section acceptance notifications and waits for them +// to be delivered, pushing them into the output data buffer. +func (s *scheduler) scheduleDeliveries(pend chan uint64, done chan []byte, quit chan struct{}, wg *sync.WaitGroup) { + // Clean up the goroutine and pipeline when done + defer wg.Done() + defer close(done) + + // Keep reading notifications and scheduling deliveries + for { + select { + case <-quit: + return + + case idx, ok := <-pend: + // New section retrieval pending + if !ok { + return + } + // Wait until the request is honoured + s.lock.Lock() + res := s.responses[idx] + s.lock.Unlock() + + select { + case <-quit: + return + case <-res.done: + } + // Deliver the result + select { + case <-quit: + return + case done <- res.cached: + } + } + } +} + +// deliver is called by the request distributor when a reply to a request arrives. +func (s *scheduler) deliver(sections []uint64, data [][]byte) { + s.lock.Lock() + defer s.lock.Unlock() + + for i, section := range sections { + if res := s.responses[section]; res != nil && res.cached == nil { // Avoid non-requests and double deliveries + res.cached = data[i] + close(res.done) + } + } +} diff --git a/core/bloombits/scheduler_test.go b/core/bloombits/scheduler_test.go new file mode 100644 index 0000000..dcaaa91 --- /dev/null +++ b/core/bloombits/scheduler_test.go @@ -0,0 +1,103 @@ +// 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 bloombits + +import ( + "bytes" + "math/big" + "sync" + "sync/atomic" + "testing" +) + +// Tests that the scheduler can deduplicate and forward retrieval requests to +// underlying fetchers and serve responses back, irrelevant of the concurrency +// of the requesting clients or serving data fetchers. +func TestSchedulerSingleClientSingleFetcher(t *testing.T) { testScheduler(t, 1, 1, 5000) } +func TestSchedulerSingleClientMultiFetcher(t *testing.T) { testScheduler(t, 1, 10, 5000) } +func TestSchedulerMultiClientSingleFetcher(t *testing.T) { testScheduler(t, 10, 1, 5000) } +func TestSchedulerMultiClientMultiFetcher(t *testing.T) { testScheduler(t, 10, 10, 5000) } + +func testScheduler(t *testing.T, clients int, fetchers int, requests int) { + t.Parallel() + f := newScheduler(0) + + // Create a batch of handler goroutines that respond to bloom bit requests and + // deliver them to the scheduler. + var fetchPend sync.WaitGroup + fetchPend.Add(fetchers) + defer fetchPend.Wait() + + fetch := make(chan *request, 16) + defer close(fetch) + + var delivered atomic.Uint32 + for i := 0; i < fetchers; i++ { + go func() { + defer fetchPend.Done() + + for req := range fetch { + delivered.Add(1) + + f.deliver([]uint64{ + req.section + uint64(requests), // Non-requested data (ensure it doesn't go out of bounds) + req.section, // Requested data + req.section, // Duplicated data (ensure it doesn't double close anything) + }, [][]byte{ + {}, + new(big.Int).SetUint64(req.section).Bytes(), + new(big.Int).SetUint64(req.section).Bytes(), + }) + } + }() + } + // Start a batch of goroutines to concurrently run scheduling tasks + quit := make(chan struct{}) + + var pend sync.WaitGroup + pend.Add(clients) + + for i := 0; i < clients; i++ { + go func() { + defer pend.Done() + + in := make(chan uint64, 16) + out := make(chan []byte, 16) + + f.run(in, fetch, out, quit, &pend) + + go func() { + for j := 0; j < requests; j++ { + in <- uint64(j) + } + close(in) + }() + b := new(big.Int) + for j := 0; j < requests; j++ { + bits := <-out + if want := b.SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) { + t.Errorf("vector %d: delivered content mismatch: have %x, want %x", j, bits, want) + } + } + }() + } + pend.Wait() + + if have := delivered.Load(); int(have) != requests { + t.Errorf("request count mismatch: have %v, want %v", have, requests) + } +} diff --git a/core/chain_indexer.go b/core/chain_indexer.go new file mode 100644 index 0000000..f5fce72 --- /dev/null +++ b/core/chain_indexer.go @@ -0,0 +1,523 @@ +// 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 core + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" +) + +// ChainIndexerBackend defines the methods needed to process chain segments in +// the background and write the segment results into the database. These can be +// used to create filter blooms or CHTs. +type ChainIndexerBackend interface { + // Reset initiates the processing of a new chain segment, potentially terminating + // any partially completed operations (in case of a reorg). + Reset(ctx context.Context, section uint64, prevHead common.Hash) error + + // Process crunches through the next header in the chain segment. The caller + // will ensure a sequential order of headers. + Process(ctx context.Context, header *types.Header) error + + // Commit finalizes the section metadata and stores it into the database. + Commit() error + + // Prune deletes the chain index older than the given threshold. + Prune(threshold uint64) error +} + +// ChainIndexerChain interface is used for connecting the indexer to a blockchain +type ChainIndexerChain interface { + // CurrentHeader retrieves the latest locally known header. + CurrentHeader() *types.Header + + // SubscribeChainHeadEvent subscribes to new head header notifications. + SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription +} + +// ChainIndexer does a post-processing job for equally sized sections of the +// canonical chain (like BlooomBits and CHT structures). A ChainIndexer is +// connected to the blockchain through the event system by starting a +// ChainHeadEventLoop in a goroutine. +// +// Further child ChainIndexers can be added which use the output of the parent +// section indexer. These child indexers receive new head notifications only +// after an entire section has been finished or in case of rollbacks that might +// affect already finished sections. +type ChainIndexer struct { + chainDb ethdb.Database // Chain database to index the data from + indexDb ethdb.Database // Prefixed table-view of the db to write index metadata into + backend ChainIndexerBackend // Background processor generating the index data content + children []*ChainIndexer // Child indexers to cascade chain updates to + + active atomic.Bool // Flag whether the event loop was started + update chan struct{} // Notification channel that headers should be processed + quit chan chan error // Quit channel to tear down running goroutines + ctx context.Context + ctxCancel func() + + sectionSize uint64 // Number of blocks in a single chain segment to process + confirmsReq uint64 // Number of confirmations before processing a completed segment + + storedSections uint64 // Number of sections successfully indexed into the database + knownSections uint64 // Number of sections known to be complete (block wise) + cascadedHead uint64 // Block number of the last completed section cascaded to subindexers + + checkpointSections uint64 // Number of sections covered by the checkpoint + checkpointHead common.Hash // Section head belonging to the checkpoint + + throttling time.Duration // Disk throttling to prevent a heavy upgrade from hogging resources + + log log.Logger + lock sync.Mutex +} + +// NewChainIndexer creates a new chain indexer to do background processing on +// chain segments of a given size after certain number of confirmations passed. +// The throttling parameter might be used to prevent database thrashing. +func NewChainIndexer(chainDb ethdb.Database, indexDb ethdb.Database, backend ChainIndexerBackend, section, confirm uint64, throttling time.Duration, kind string) *ChainIndexer { + c := &ChainIndexer{ + chainDb: chainDb, + indexDb: indexDb, + backend: backend, + update: make(chan struct{}, 1), + quit: make(chan chan error), + sectionSize: section, + confirmsReq: confirm, + throttling: throttling, + log: log.New("type", kind), + } + // Initialize database dependent fields and start the updater + c.loadValidSections() + c.ctx, c.ctxCancel = context.WithCancel(context.Background()) + + go c.updateLoop() + + return c +} + +// AddCheckpoint adds a checkpoint. Sections are never processed and the chain +// is not expected to be available before this point. The indexer assumes that +// the backend has sufficient information available to process subsequent sections. +// +// Note: knownSections == 0 and storedSections == checkpointSections until +// syncing reaches the checkpoint +func (c *ChainIndexer) AddCheckpoint(section uint64, shead common.Hash) { + c.lock.Lock() + defer c.lock.Unlock() + + // Short circuit if the given checkpoint is below than local's. + if c.checkpointSections >= section+1 || section < c.storedSections { + return + } + c.checkpointSections = section + 1 + c.checkpointHead = shead + + c.setSectionHead(section, shead) + c.setValidSections(section + 1) +} + +// Start creates a goroutine to feed chain head events into the indexer for +// cascading background processing. Children do not need to be started, they +// are notified about new events by their parents. +func (c *ChainIndexer) Start(chain ChainIndexerChain) { + events := make(chan ChainHeadEvent, 10) + sub := chain.SubscribeChainHeadEvent(events) + + go c.eventLoop(chain.CurrentHeader(), events, sub) +} + +// Close tears down all goroutines belonging to the indexer and returns any error +// that might have occurred internally. +func (c *ChainIndexer) Close() error { + var errs []error + + c.ctxCancel() + + // Tear down the primary update loop + errc := make(chan error) + c.quit <- errc + if err := <-errc; err != nil { + errs = append(errs, err) + } + // If needed, tear down the secondary event loop + if c.active.Load() { + c.quit <- errc + if err := <-errc; err != nil { + errs = append(errs, err) + } + } + // Close all children + for _, child := range c.children { + if err := child.Close(); err != nil { + errs = append(errs, err) + } + } + // Return any failures + switch { + case len(errs) == 0: + return nil + + case len(errs) == 1: + return errs[0] + + default: + return fmt.Errorf("%v", errs) + } +} + +// eventLoop is a secondary - optional - event loop of the indexer which is only +// started for the outermost indexer to push chain head events into a processing +// queue. +func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainHeadEvent, sub event.Subscription) { + // Mark the chain indexer as active, requiring an additional teardown + c.active.Store(true) + + defer sub.Unsubscribe() + + // Fire the initial new head event to start any outstanding processing + c.newHead(currentHeader.Number.Uint64(), false) + + var ( + prevHeader = currentHeader + prevHash = currentHeader.Hash() + ) + for { + select { + case errc := <-c.quit: + // Chain indexer terminating, report no failure and abort + errc <- nil + return + + case ev, ok := <-events: + // Received a new event, ensure it's not nil (closing) and update + if !ok { + errc := <-c.quit + errc <- nil + return + } + header := ev.Block.Header() + if header.ParentHash != prevHash { + // Reorg to the common ancestor if needed (might not exist in light sync mode, skip reorg then) + // TODO(karalabe, zsfelfoldi): This seems a bit brittle, can we detect this case explicitly? + + if rawdb.ReadCanonicalHash(c.chainDb, prevHeader.Number.Uint64()) != prevHash { + if h := rawdb.FindCommonAncestor(c.chainDb, prevHeader, header); h != nil { + c.newHead(h.Number.Uint64(), true) + } + } + } + c.newHead(header.Number.Uint64(), false) + + prevHeader, prevHash = header, header.Hash() + } + } +} + +// newHead notifies the indexer about new chain heads and/or reorgs. +func (c *ChainIndexer) newHead(head uint64, reorg bool) { + c.lock.Lock() + defer c.lock.Unlock() + + // If a reorg happened, invalidate all sections until that point + if reorg { + // Revert the known section number to the reorg point + known := (head + 1) / c.sectionSize + stored := known + if known < c.checkpointSections { + known = 0 + } + if stored < c.checkpointSections { + stored = c.checkpointSections + } + if known < c.knownSections { + c.knownSections = known + } + // Revert the stored sections from the database to the reorg point + if stored < c.storedSections { + c.setValidSections(stored) + } + // Update the new head number to the finalized section end and notify children + head = known * c.sectionSize + + if head < c.cascadedHead { + c.cascadedHead = head + for _, child := range c.children { + child.newHead(c.cascadedHead, true) + } + } + return + } + // No reorg, calculate the number of newly known sections and update if high enough + var sections uint64 + if head >= c.confirmsReq { + sections = (head + 1 - c.confirmsReq) / c.sectionSize + if sections < c.checkpointSections { + sections = 0 + } + if sections > c.knownSections { + if c.knownSections < c.checkpointSections { + // syncing reached the checkpoint, verify section head + syncedHead := rawdb.ReadCanonicalHash(c.chainDb, c.checkpointSections*c.sectionSize-1) + if syncedHead != c.checkpointHead { + c.log.Error("Synced chain does not match checkpoint", "number", c.checkpointSections*c.sectionSize-1, "expected", c.checkpointHead, "synced", syncedHead) + return + } + } + c.knownSections = sections + + select { + case c.update <- struct{}{}: + default: + } + } + } +} + +// updateLoop is the main event loop of the indexer which pushes chain segments +// down into the processing backend. +func (c *ChainIndexer) updateLoop() { + var ( + updating bool + updated time.Time + ) + + for { + select { + case errc := <-c.quit: + // Chain indexer terminating, report no failure and abort + errc <- nil + return + + case <-c.update: + // Section headers completed (or rolled back), update the index + c.lock.Lock() + if c.knownSections > c.storedSections { + // Periodically print an upgrade log message to the user + if time.Since(updated) > 8*time.Second { + if c.knownSections > c.storedSections+1 { + updating = true + c.log.Info("Upgrading chain index", "percentage", c.storedSections*100/c.knownSections) + } + updated = time.Now() + } + // Cache the current section count and head to allow unlocking the mutex + c.verifyLastHead() + section := c.storedSections + var oldHead common.Hash + if section > 0 { + oldHead = c.SectionHead(section - 1) + } + // Process the newly defined section in the background + c.lock.Unlock() + newHead, err := c.processSection(section, oldHead) + if err != nil { + select { + case <-c.ctx.Done(): + <-c.quit <- nil + return + default: + } + c.log.Error("Section processing failed", "error", err) + } + c.lock.Lock() + + // If processing succeeded and no reorgs occurred, mark the section completed + if err == nil && (section == 0 || oldHead == c.SectionHead(section-1)) { + c.setSectionHead(section, newHead) + c.setValidSections(section + 1) + if c.storedSections == c.knownSections && updating { + updating = false + c.log.Info("Finished upgrading chain index") + } + c.cascadedHead = c.storedSections*c.sectionSize - 1 + for _, child := range c.children { + c.log.Trace("Cascading chain index update", "head", c.cascadedHead) + child.newHead(c.cascadedHead, false) + } + } else { + // If processing failed, don't retry until further notification + c.log.Debug("Chain index processing failed", "section", section, "err", err) + c.verifyLastHead() + c.knownSections = c.storedSections + } + } + // If there are still further sections to process, reschedule + if c.knownSections > c.storedSections { + time.AfterFunc(c.throttling, func() { + select { + case c.update <- struct{}{}: + default: + } + }) + } + c.lock.Unlock() + } + } +} + +// processSection processes an entire section by calling backend functions while +// ensuring the continuity of the passed headers. Since the chain mutex is not +// held while processing, the continuity can be broken by a long reorg, in which +// case the function returns with an error. +func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (common.Hash, error) { + c.log.Trace("Processing new chain section", "section", section) + + // Reset and partial processing + if err := c.backend.Reset(c.ctx, section, lastHead); err != nil { + c.setValidSections(0) + return common.Hash{}, err + } + + for number := section * c.sectionSize; number < (section+1)*c.sectionSize; number++ { + hash := rawdb.ReadCanonicalHash(c.chainDb, number) + if hash == (common.Hash{}) { + return common.Hash{}, fmt.Errorf("canonical block #%d unknown", number) + } + header := rawdb.ReadHeader(c.chainDb, hash, number) + if header == nil { + return common.Hash{}, fmt.Errorf("block #%d [%x..] not found", number, hash[:4]) + } else if header.ParentHash != lastHead { + return common.Hash{}, errors.New("chain reorged during section processing") + } + if err := c.backend.Process(c.ctx, header); err != nil { + return common.Hash{}, err + } + lastHead = header.Hash() + } + if err := c.backend.Commit(); err != nil { + return common.Hash{}, err + } + return lastHead, nil +} + +// verifyLastHead compares last stored section head with the corresponding block hash in the +// actual canonical chain and rolls back reorged sections if necessary to ensure that stored +// sections are all valid +func (c *ChainIndexer) verifyLastHead() { + for c.storedSections > 0 && c.storedSections > c.checkpointSections { + if c.SectionHead(c.storedSections-1) == rawdb.ReadCanonicalHash(c.chainDb, c.storedSections*c.sectionSize-1) { + return + } + c.setValidSections(c.storedSections - 1) + } +} + +// Sections returns the number of processed sections maintained by the indexer +// and also the information about the last header indexed for potential canonical +// verifications. +func (c *ChainIndexer) Sections() (uint64, uint64, common.Hash) { + c.lock.Lock() + defer c.lock.Unlock() + + c.verifyLastHead() + return c.storedSections, c.storedSections*c.sectionSize - 1, c.SectionHead(c.storedSections - 1) +} + +// AddChildIndexer adds a child ChainIndexer that can use the output of this one +func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) { + if indexer == c { + panic("can't add indexer as a child of itself") + } + c.lock.Lock() + defer c.lock.Unlock() + + c.children = append(c.children, indexer) + + // Cascade any pending updates to new children too + sections := c.storedSections + if c.knownSections < sections { + // if a section is "stored" but not "known" then it is a checkpoint without + // available chain data so we should not cascade it yet + sections = c.knownSections + } + if sections > 0 { + indexer.newHead(sections*c.sectionSize-1, false) + } +} + +// Prune deletes all chain data older than given threshold. +func (c *ChainIndexer) Prune(threshold uint64) error { + return c.backend.Prune(threshold) +} + +// loadValidSections reads the number of valid sections from the index database +// and caches is into the local state. +func (c *ChainIndexer) loadValidSections() { + data, _ := c.indexDb.Get([]byte("count")) + if len(data) == 8 { + c.storedSections = binary.BigEndian.Uint64(data) + } +} + +// setValidSections writes the number of valid sections to the index database +func (c *ChainIndexer) setValidSections(sections uint64) { + // Set the current number of valid sections in the database + var data [8]byte + binary.BigEndian.PutUint64(data[:], sections) + c.indexDb.Put([]byte("count"), data[:]) + + // Remove any reorged sections, caching the valids in the mean time + for c.storedSections > sections { + c.storedSections-- + c.removeSectionHead(c.storedSections) + } + c.storedSections = sections // needed if new > old +} + +// SectionHead retrieves the last block hash of a processed section from the +// index database. +func (c *ChainIndexer) SectionHead(section uint64) common.Hash { + var data [8]byte + binary.BigEndian.PutUint64(data[:], section) + + hash, _ := c.indexDb.Get(append([]byte("shead"), data[:]...)) + if len(hash) == len(common.Hash{}) { + return common.BytesToHash(hash) + } + return common.Hash{} +} + +// setSectionHead writes the last block hash of a processed section to the index +// database. +func (c *ChainIndexer) setSectionHead(section uint64, hash common.Hash) { + var data [8]byte + binary.BigEndian.PutUint64(data[:], section) + + c.indexDb.Put(append([]byte("shead"), data[:]...), hash.Bytes()) +} + +// removeSectionHead removes the reference to a processed section from the index +// database. +func (c *ChainIndexer) removeSectionHead(section uint64) { + var data [8]byte + binary.BigEndian.PutUint64(data[:], section) + + c.indexDb.Delete(append([]byte("shead"), data[:]...)) +} diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go new file mode 100644 index 0000000..bf3bde7 --- /dev/null +++ b/core/chain_indexer_test.go @@ -0,0 +1,246 @@ +// 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 core + +import ( + "context" + "errors" + "fmt" + "math/big" + "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" +) + +// Runs multiple tests with randomized parameters. +func TestChainIndexerSingle(t *testing.T) { + for i := 0; i < 10; i++ { + testChainIndexer(t, 1) + } +} + +// Runs multiple tests with randomized parameters and different number of +// chain backends. +func TestChainIndexerWithChildren(t *testing.T) { + for i := 2; i < 8; i++ { + testChainIndexer(t, i) + } +} + +// testChainIndexer runs a test with either a single chain indexer or a chain of +// multiple backends. The section size and required confirmation count parameters +// are randomized. +func testChainIndexer(t *testing.T, count int) { + db := rawdb.NewMemoryDatabase() + defer db.Close() + + // Create a chain of indexers and ensure they all report empty + backends := make([]*testChainIndexBackend, count) + for i := 0; i < count; i++ { + var ( + sectionSize = uint64(rand.Intn(100) + 1) + confirmsReq = uint64(rand.Intn(10)) + ) + backends[i] = &testChainIndexBackend{t: t, processCh: make(chan uint64)} + backends[i].indexer = NewChainIndexer(db, rawdb.NewTable(db, string([]byte{byte(i)})), backends[i], sectionSize, confirmsReq, 0, fmt.Sprintf("indexer-%d", i)) + + if sections, _, _ := backends[i].indexer.Sections(); sections != 0 { + t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, 0) + } + if i > 0 { + backends[i-1].indexer.AddChildIndexer(backends[i].indexer) + } + } + defer backends[0].indexer.Close() // parent indexer shuts down children + // notify pings the root indexer about a new head or reorg, then expect + // processed blocks if a section is processable + notify := func(headNum, failNum uint64, reorg bool) { + backends[0].indexer.newHead(headNum, reorg) + if reorg { + for _, backend := range backends { + headNum = backend.reorg(headNum) + backend.assertSections() + } + return + } + var cascade bool + for _, backend := range backends { + headNum, cascade = backend.assertBlocks(headNum, failNum) + if !cascade { + break + } + backend.assertSections() + } + } + // inject inserts a new random canonical header into the database directly + inject := func(number uint64) { + header := &types.Header{Number: big.NewInt(int64(number)), Extra: big.NewInt(rand.Int63()).Bytes()} + if number > 0 { + header.ParentHash = rawdb.ReadCanonicalHash(db, number-1) + } + rawdb.WriteHeader(db, header) + rawdb.WriteCanonicalHash(db, header.Hash(), number) + } + // Start indexer with an already existing chain + for i := uint64(0); i <= 100; i++ { + inject(i) + } + notify(100, 100, false) + + // Add new blocks one by one + for i := uint64(101); i <= 1000; i++ { + inject(i) + notify(i, i, false) + } + // Do a reorg + notify(500, 500, true) + + // Create new fork + for i := uint64(501); i <= 1000; i++ { + inject(i) + notify(i, i, false) + } + for i := uint64(1001); i <= 1500; i++ { + inject(i) + } + // Failed processing scenario where less blocks are available than notified + notify(2000, 1500, false) + + // Notify about a reorg (which could have caused the missing blocks if happened during processing) + notify(1500, 1500, true) + + // Create new fork + for i := uint64(1501); i <= 2000; i++ { + inject(i) + notify(i, i, false) + } +} + +// testChainIndexBackend implements ChainIndexerBackend +type testChainIndexBackend struct { + t *testing.T + indexer *ChainIndexer + section, headerCnt, stored uint64 + processCh chan uint64 +} + +// assertSections verifies if a chain indexer has the correct number of section. +func (b *testChainIndexBackend) assertSections() { + // Keep trying for 3 seconds if it does not match + var sections uint64 + for i := 0; i < 300; i++ { + sections, _, _ = b.indexer.Sections() + if sections == b.stored { + return + } + time.Sleep(10 * time.Millisecond) + } + b.t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, b.stored) +} + +// assertBlocks expects processing calls after new blocks have arrived. If the +// failNum < headNum then we are simulating a scenario where a reorg has happened +// after the processing has started and the processing of a section fails. +func (b *testChainIndexBackend) assertBlocks(headNum, failNum uint64) (uint64, bool) { + var sections uint64 + if headNum >= b.indexer.confirmsReq { + sections = (headNum + 1 - b.indexer.confirmsReq) / b.indexer.sectionSize + if sections > b.stored { + // expect processed blocks + for expectd := b.stored * b.indexer.sectionSize; expectd < sections*b.indexer.sectionSize; expectd++ { + if expectd > failNum { + // rolled back after processing started, no more process calls expected + // wait until updating is done to make sure that processing actually fails + var updating bool + for i := 0; i < 300; i++ { + b.indexer.lock.Lock() + updating = b.indexer.knownSections > b.indexer.storedSections + b.indexer.lock.Unlock() + if !updating { + break + } + time.Sleep(10 * time.Millisecond) + } + if updating { + b.t.Fatalf("update did not finish") + } + sections = expectd / b.indexer.sectionSize + break + } + select { + case <-time.After(10 * time.Second): + b.t.Fatalf("Expected processed block #%d, got nothing", expectd) + case processed := <-b.processCh: + if processed != expectd { + b.t.Errorf("Expected processed block #%d, got #%d", expectd, processed) + } + } + } + b.stored = sections + } + } + if b.stored == 0 { + return 0, false + } + return b.stored*b.indexer.sectionSize - 1, true +} + +func (b *testChainIndexBackend) reorg(headNum uint64) uint64 { + firstChanged := (headNum + 1) / b.indexer.sectionSize + if firstChanged < b.stored { + b.stored = firstChanged + } + return b.stored * b.indexer.sectionSize +} + +func (b *testChainIndexBackend) Reset(ctx context.Context, section uint64, prevHead common.Hash) error { + b.section = section + b.headerCnt = 0 + return nil +} + +func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Header) error { + b.headerCnt++ + if b.headerCnt > b.indexer.sectionSize { + b.t.Error("Processing too many headers") + } + //t.processCh <- header.Number.Uint64() + select { + case <-time.After(10 * time.Second): + b.t.Error("Unexpected call to Process") + // Can't use Fatal since this is not the test's goroutine. + // Returning error stops the chainIndexer's updateLoop + return errors.New("unexpected call to Process") + case b.processCh <- header.Number.Uint64(): + } + return nil +} + +func (b *testChainIndexBackend) Commit() error { + if b.headerCnt != b.indexer.sectionSize { + b.t.Error("Not enough headers processed") + } + return nil +} + +func (b *testChainIndexBackend) Prune(threshold uint64) error { + return nil +} diff --git a/core/chain_makers.go b/core/chain_makers.go new file mode 100644 index 0000000..5898534 --- /dev/null +++ b/core/chain_makers.go @@ -0,0 +1,682 @@ +// 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 core + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-verkle" + "github.com/holiman/uint256" +) + +// BlockGen creates blocks for testing. +// See GenerateChain for a detailed explanation. +type BlockGen struct { + i int + cm *chainMaker + parent *types.Block + header *types.Header + statedb *state.StateDB + + gasPool *GasPool + txs []*types.Transaction + receipts []*types.Receipt + uncles []*types.Header + withdrawals []*types.Withdrawal + + engine consensus.Engine +} + +// SetCoinbase sets the coinbase of the generated block. +// It can be called at most once. +func (b *BlockGen) SetCoinbase(addr common.Address) { + if b.gasPool != nil { + if len(b.txs) > 0 { + panic("coinbase must be set before adding transactions") + } + panic("coinbase can only be set once") + } + b.header.Coinbase = addr + b.gasPool = new(GasPool).AddGas(b.header.GasLimit) +} + +// SetExtra sets the extra data field of the generated block. +func (b *BlockGen) SetExtra(data []byte) { + b.header.Extra = data +} + +// SetNonce sets the nonce field of the generated block. +func (b *BlockGen) SetNonce(nonce types.BlockNonce) { + b.header.Nonce = nonce +} + +// SetDifficulty sets the difficulty field of the generated block. This method is +// useful for Clique tests where the difficulty does not depend on time. For the +// ethash tests, please use OffsetTime, which implicitly recalculates the diff. +func (b *BlockGen) SetDifficulty(diff *big.Int) { + b.header.Difficulty = diff +} + +// SetPoS makes the header a PoS-header (0 difficulty) +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. +// +// There are a few options can be passed as well in order to run some +// customized rules. +// - bc: enables the ability to query historical block hashes for BLOCKHASH +// - vmConfig: extends the flexibility for customizing evm rules, e.g. enable extra EIPs +func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transaction) { + if b.gasPool == nil { + b.SetCoinbase(common.Address{}) + } + b.statedb.SetTxContext(tx.Hash(), len(b.txs)) + 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 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) +} + +// 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. +func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { + b.addTx(bc, vm.Config{}, tx) +} + +// AddTxWithVMConfig adds a transaction to the generated block. If no coinbase has +// been set, the block's coinbase is set to the zero address. +// The evm interpreter can be customized with the provided vm config. +func (b *BlockGen) AddTxWithVMConfig(tx *types.Transaction, config vm.Config) { + b.addTx(nil, config, tx) +} + +// GetBalance returns the balance of the given address at the generated block. +func (b *BlockGen) GetBalance(addr common.Address) *uint256.Int { + return b.statedb.GetBalance(addr) +} + +// 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. +func (b *BlockGen) AddUncheckedTx(tx *types.Transaction) { + b.txs = append(b.txs, tx) +} + +// Number returns the block number of the block being generated. +func (b *BlockGen) Number() *big.Int { + return new(big.Int).Set(b.header.Number) +} + +// Timestamp returns the timestamp of the block being generated. +func (b *BlockGen) Timestamp() uint64 { + return b.header.Time +} + +// BaseFee returns the EIP-1559 base fee of the block being generated. +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. +// +// AddUncheckedReceipt will cause consensus failures when used during real +// chain processing. This is best used in conjunction with raw block insertion. +func (b *BlockGen) AddUncheckedReceipt(receipt *types.Receipt) { + b.receipts = append(b.receipts, receipt) +} + +// TxNonce returns the next valid transaction nonce for the +// account at addr. It panics if the account does not exist. +func (b *BlockGen) TxNonce(addr common.Address) uint64 { + if !b.statedb.Exist(addr) { + panic("account does not exist") + } + return b.statedb.GetNonce(addr) +} + +// AddUncle adds an uncle header to the generated block. +func (b *BlockGen) AddUncle(h *types.Header) { + // The uncle will have the same timestamp and auto-generated difficulty + h.Time = b.header.Time + + var parent *types.Header + for i := b.i - 1; i >= 0; i-- { + if b.cm.chain[i].Hash() == h.ParentHash { + parent = b.cm.chain[i].Header() + break + } + } + 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.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) + } + } + b.uncles = append(b.uncles, h) +} + +// AddWithdrawal adds a withdrawal to the generated block. +// It returns the withdrawal index. +func (b *BlockGen) AddWithdrawal(w *types.Withdrawal) uint64 { + cpy := *w + cpy.Index = b.nextWithdrawalIndex() + b.withdrawals = append(b.withdrawals, &cpy) + return cpy.Index +} + +// nextWithdrawalIndex computes the index of the next withdrawal. +func (b *BlockGen) nextWithdrawalIndex() uint64 { + if len(b.withdrawals) != 0 { + return b.withdrawals[len(b.withdrawals)-1].Index + 1 + } + for i := b.i - 1; i >= 0; i-- { + 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.cm.bottom.Withdrawals(); len(wd) != 0 { + return wd[len(wd)-1].Index + 1 + } + } + } + return 0 +} + +// PrevBlock returns a previously generated block by number. It panics if +// num is greater or equal to the number of the block being generated. +// For index -1, PrevBlock returns the parent block given to GenerateChain. +func (b *BlockGen) PrevBlock(index int) *types.Block { + if index >= b.i { + panic(fmt.Errorf("block index %d out of range (%d,%d)", index, -1, b.i)) + } + if index == -1 { + return b.cm.bottom + } + return b.cm.chain[index] +} + +// OffsetTime modifies the time instance of a block, implicitly changing its +// associated difficulty. It's useful to test scenarios where forking is not +// tied to chain length directly. +func (b *BlockGen) OffsetTime(seconds int64) { + b.header.Time += uint64(seconds) + if b.header.Time <= b.cm.bottom.Header().Time { + panic("block time out of range") + } + 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 +// parent will be the provided parent. db is used to store +// intermediate states and should contain the parent's state trie. +// +// The generator function is called with a new block generator for +// every block. Any transactions and uncles added to the generator +// become part of the block. If gen is nil, the blocks will be empty +// and their coinbase will be the zero address. +// +// Blocks created by GenerateChain do not contain valid proof of work +// values. Inserting them into BlockChain requires use of FakePow or +// a similar non-validating proof of work implementation. +func GenerateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) { + if config == nil { + config = params.TestChainConfig + } + if engine == nil { + panic("nil consensus engine") + } + cm := newChainMaker(parent, config, engine) + + genblock := func(i int, parent *types.Block, triedb *triedb.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 + // correct value. + if b.header.Difficulty == nil { + if config.TerminalTotalDifficulty == nil { + // Clique chain + b.header.Difficulty = big.NewInt(2) + } else { + // Post-merge chain + b.header.Difficulty = big.NewInt(0) + } + } + // Mutate the state and block according to any hard-fork specs + if daoBlock := config.DAOForkBlock; daoBlock != nil { + limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) + if b.header.Number.Cmp(daoBlock) >= 0 && b.header.Number.Cmp(limit) < 0 { + if config.DAOForkSupport { + b.header.Extra = common.CopyBytes(params.DAOForkBlockExtra) + } + } + } + if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 { + misc.ApplyDAOHardFork(statedb) + } + // Execute any user modifications to the block + if gen != nil { + gen(i, b) + } + + body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals} + block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts) + 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 = 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 := triedb.NewDatabase(db, triedb.HashDefaults) + defer triedb.Close() + + for i := 0; i < n; i++ { + statedb, err := state.New(parent.Root(), state.NewDatabaseWithNodeDB(db, triedb), nil) + if err != nil { + panic(err) + } + 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 cm.chain, cm.receipts +} + +// GenerateChainWithGenesis is a wrapper of GenerateChain which will initialize +// genesis block to database first according to the provided genesis specification +// 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() + triedb := triedb.NewDatabase(db, triedb.HashDefaults) + defer triedb.Close() + _, err := genesis.Commit(db, triedb) + if err != nil { + panic(err) + } + blocks, receipts := GenerateChain(genesis.Config, genesis.ToBlock(), engine, db, n, gen) + return db, blocks, receipts +} + +func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, trdb *triedb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) { + if config == nil { + config = params.TestChainConfig + } + proofs := make([]*verkle.VerkleProof, 0, n) + keyvals := make([]verkle.StateDiff, 0, n) + cm := newChainMaker(parent, config, engine) + + genblock := func(i int, parent *types.Block, triedb *triedb.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) + + // TODO uncomment when proof generation is merged + // Save pre state for proof generation + // preState := statedb.Copy() + + // TODO uncomment when the 2935 PR is merged + // if config.IsPrague(b.header.Number, b.header.Time) { + // if !config.IsPrague(b.parent.Number(), b.parent.Time()) { + // Transition case: insert all 256 ancestors + // InsertBlockHashHistoryAtEip2935Fork(statedb, b.header.Number.Uint64()-1, b.header.ParentHash, chainreader) + // } else { + // ProcessParentBlockHash(statedb, b.header.Number.Uint64()-1, b.header.ParentHash) + // } + // } + // Execute any user modifications to the block + if gen != nil { + gen(i, b) + } + body := &types.Body{ + Transactions: b.txs, + Uncles: b.uncles, + Withdrawals: b.withdrawals, + } + block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, body, b.receipts) + 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 = triedb.Commit(root, false); err != nil { + panic(fmt.Sprintf("trie write error: %v", err)) + } + + // TODO uncomment when proof generation is merged + // proofs = append(proofs, block.ExecutionWitness().VerkleProof) + // keyvals = append(keyvals, block.ExecutionWitness().StateDiff) + + return block, b.receipts + } + + for i := 0; i < n; i++ { + statedb, err := state.New(parent.Root(), state.NewDatabaseWithNodeDB(db, trdb), nil) + if err != nil { + panic(err) + } + block, receipts := genblock(i, parent, trdb, 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 cm.chain, cm.receipts, proofs, keyvals +} + +func GenerateVerkleChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) { + db := rawdb.NewMemoryDatabase() + cacheConfig := DefaultCacheConfigWithScheme(rawdb.PathScheme) + cacheConfig.SnapshotLimit = 0 + triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true)) + defer triedb.Close() + genesisBlock, err := genesis.Commit(db, triedb) + if err != nil { + panic(err) + } + blocks, receipts, proofs, keyvals := GenerateVerkleChain(genesis.Config, genesisBlock, engine, db, triedb, n, gen) + return db, blocks, receipts, proofs, keyvals +} + +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(cm.config.IsEIP158(parent.Number())), + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: engine.CalcDifficulty(cm, time, parent.Header()), + GasLimit: parent.GasLimit(), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: time, + } + + 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 +} + +// makeHeaderChain creates a deterministic chain of headers rooted at parent. +func makeHeaderChain(chainConfig *params.ChainConfig, parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header { + blocks := makeBlockChain(chainConfig, types.NewBlockWithHeader(parent), n, engine, db, seed) + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + return headers +} + +// makeHeaderChainWithGenesis creates a deterministic chain of headers from genesis. +func makeHeaderChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine, seed int) (ethdb.Database, []*types.Header) { + db, blocks := makeBlockChainWithGenesis(genesis, n, engine, seed) + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + return db, headers +} + +// makeBlockChain creates a deterministic chain of blocks rooted at parent. +func makeBlockChain(chainConfig *params.ChainConfig, parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block { + blocks, _ := GenerateChain(chainConfig, parent, engine, db, n, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) + }) + return blocks +} + +// makeBlockChainWithGenesis creates a deterministic chain of blocks from genesis +func makeBlockChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine, seed int) (ethdb.Database, []*types.Block) { + db, blocks, _ := GenerateChainWithGenesis(genesis, engine, n, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) + }) + return db, blocks +} + +// 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 +} + +// Engine returns the consensus engine (for ChainContext). +func (cm *chainMaker) Engine() consensus.Engine { + return cm.engine +} + +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 new file mode 100644 index 0000000..6241f3f --- /dev/null +++ b/core/chain_makers_test.go @@ -0,0 +1,259 @@ +// 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 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" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" +) + +func TestGeneratePOSChain(t *testing.T) { + var ( + keyHex = "9c647b8b7c4e7c3490668fb6c11473619db80c93704c70893d3813af4090c39c" + key, _ = crypto.HexToECDSA(keyHex) + address = crypto.PubkeyToAddress(key.PublicKey) // 658bdf435d810c91414ec09147daa6db62406379 + aa = common.Address{0xaa} + bb = common.Address{0xbb} + funds = big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(params.Ether)) + config = *params.AllEthashProtocolChanges + gspec = &Genesis{ + Config: &config, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + params.BeaconRootsAddress: {Code: params.BeaconRootsCode}, + }, + BaseFee: big.NewInt(params.InitialBaseFee), + Difficulty: common.Big1, + GasLimit: 5_000_000, + } + 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) + storage[common.Hash{0x00}] = common.Hash{0x00} + storage[common.Hash{0x01}] = common.Hash{0x01} + storage[common.Hash{0x02}] = common.Hash{0x02} + storage[common.Hash{0x03}] = common.HexToHash("0303") + gspec.Alloc[aa] = types.Account{ + Balance: common.Big1, + Nonce: 1, + Storage: storage, + Code: common.Hex2Bytes("6042"), + } + gspec.Alloc[bb] = types.Account{ + Balance: common.Big2, + Nonce: 1, + Storage: storage, + Code: common.Hex2Bytes("600154600354"), + } + genesis := gspec.MustCommit(gendb, triedb.NewDatabase(gendb, triedb.HashDefaults)) + + genchain, genreceipts := GenerateChain(gspec.Config, genesis, beacon.NewFaker(), gendb, 4, func(i int, gen *BlockGen) { + gen.SetParentBeaconRoot(common.Hash{byte(i + 1)}) + + // 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, + Address: common.Address{0xee}, + Amount: 1337, + }) + gen.AddWithdrawal(&types.Withdrawal{ + Validator: 13, + Address: common.Address{0xee}, + Amount: 1, + }) + } + if i == 3 { + gen.AddWithdrawal(&types.Withdrawal{ + Validator: 42, + Address: common.Address{0xee}, + Amount: 1337, + }) + gen.AddWithdrawal(&types.Withdrawal{ + Validator: 13, + Address: common.Address{0xee}, + Amount: 1, + }) + } + }) + + // Import the chain. This runs all block validation rules. + blockchain, _ := NewBlockChain(db, nil, gspec, nil, beacon.NewFaker(), vm.Config{}, nil, nil) + defer blockchain.Stop() + + 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 + ) + for i := range genchain { + blocknum := genchain[i].NumberU64() + block := blockchain.GetBlockByNumber(blocknum) + if block == nil { + 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 + } + for j := 0; j < len(block.Withdrawals()); j++ { + if block.Withdrawals()[j].Index != withdrawalIndex { + t.Fatalf("withdrawal index %d does not equal expected index %d", block.Withdrawals()[j].Index, withdrawalIndex) + } + 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.BeaconRootsAddress, 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) + } + } +} + +func ExampleGenerateChain() { + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + 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. + gspec := &Genesis{ + Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, + Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + } + genesis := gspec.MustCommit(genDb, triedb.NewDatabase(genDb, triedb.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(), genDb, 5, func(i int, gen *BlockGen) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 passes it on to addr3. + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, nil, nil), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil), signer, key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }) + + // Import the chain. This runs all block validation rules. + 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 { + fmt.Printf("insert error (block %d): %v\n", chain[i].NumberU64(), err) + return + } + + state, _ := blockchain.State() + fmt.Printf("last block: #%d\n", blockchain.CurrentBlock().Number) + fmt.Println("balance of addr1:", state.GetBalance(addr1)) + fmt.Println("balance of addr2:", state.GetBalance(addr2)) + fmt.Println("balance of addr3:", state.GetBalance(addr3)) + // Output: + // last block: #5 + // balance of addr1: 989000 + // balance of addr2: 10000 + // balance of addr3: 19687500000000001000 +} diff --git a/core/dao_test.go b/core/dao_test.go new file mode 100644 index 0000000..b9a899e --- /dev/null +++ b/core/dao_test.go @@ -0,0 +1,159 @@ +// 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 core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" +) + +// Tests that DAO-fork enabled clients can properly filter out fork-commencing +// blocks based on their extradata fields. +func TestDAOForkRangeExtradata(t *testing.T) { + forkBlock := big.NewInt(32) + chainConfig := *params.NonActivatedConfig + chainConfig.HomesteadBlock = big.NewInt(0) + + // Generate a common prefix for both pro-forkers and non-forkers + gspec := &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: &chainConfig, + } + genDb, prefix, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {}) + + // Create the concurrent, conflicting two nodes + proDb := rawdb.NewMemoryDatabase() + proConf := *params.NonActivatedConfig + proConf.HomesteadBlock = big.NewInt(0) + proConf.DAOForkBlock = forkBlock + proConf.DAOForkSupport = true + progspec := &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: &proConf, + } + proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer proBc.Stop() + + conDb := rawdb.NewMemoryDatabase() + conConf := *params.NonActivatedConfig + conConf.HomesteadBlock = big.NewInt(0) + conConf.DAOForkBlock = forkBlock + conConf.DAOForkSupport = false + congspec := &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: &conConf, + } + conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer conBc.Stop() + + if _, err := proBc.InsertChain(prefix); err != nil { + t.Fatalf("pro-fork: failed to import chain prefix: %v", err) + } + if _, err := conBc.InsertChain(prefix); err != nil { + t.Fatalf("con-fork: failed to import chain prefix: %v", err) + } + // Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks + for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ { + // Create a pro-fork block, and try to feed into the no-fork chain + bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + + blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64())) + for j := 0; j < len(blocks)/2; j++ { + blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j] + } + if _, err := bc.InsertChain(blocks); err != nil { + t.Fatalf("failed to import contra-fork chain for expansion: %v", err) + } + 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() + blocks, _ = GenerateChain(&proConf, conBc.GetBlockByHash(conBc.CurrentBlock().Hash()), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {}) + if _, err := conBc.InsertChain(blocks); err == nil { + t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0]) + } + // Create a proper no-fork block for the contra-forker + blocks, _ = GenerateChain(&conConf, conBc.GetBlockByHash(conBc.CurrentBlock().Hash()), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {}) + if _, err := conBc.InsertChain(blocks); err != nil { + t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err) + } + // Create a no-fork block, and try to feed into the pro-fork chain + bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + + blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64())) + for j := 0; j < len(blocks)/2; j++ { + blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j] + } + if _, err := bc.InsertChain(blocks); err != nil { + t.Fatalf("failed to import pro-fork chain for expansion: %v", err) + } + 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() + blocks, _ = GenerateChain(&conConf, proBc.GetBlockByHash(proBc.CurrentBlock().Hash()), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {}) + if _, err := proBc.InsertChain(blocks); err == nil { + t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0]) + } + // Create a proper pro-fork block for the pro-forker + blocks, _ = GenerateChain(&proConf, proBc.GetBlockByHash(proBc.CurrentBlock().Hash()), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {}) + if _, err := proBc.InsertChain(blocks); err != nil { + t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err) + } + } + // Verify that contra-forkers accept pro-fork extra-datas after forking finishes + bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer bc.Stop() + + blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64())) + for j := 0; j < len(blocks)/2; j++ { + blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j] + } + if _, err := bc.InsertChain(blocks); err != nil { + t.Fatalf("failed to import contra-fork chain for expansion: %v", err) + } + 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) {}) + if _, err := conBc.InsertChain(blocks); err != nil { + t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err) + } + // Verify that pro-forkers accept contra-fork extra-datas after forking finishes + bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + defer bc.Stop() + + blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64())) + for j := 0; j < len(blocks)/2; j++ { + blocks[j], blocks[len(blocks)-1-j] = blocks[len(blocks)-1-j], blocks[j] + } + if _, err := bc.InsertChain(blocks); err != nil { + t.Fatalf("failed to import pro-fork chain for expansion: %v", err) + } + 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) {}) + if _, err := proBc.InsertChain(blocks); err != nil { + t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err) + } +} diff --git a/core/error.go b/core/error.go new file mode 100644 index 0000000..161538f --- /dev/null +++ b/core/error.go @@ -0,0 +1,115 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "errors" + + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + // ErrKnownBlock is returned when a block to import is already known locally. + ErrKnownBlock = errors.New("block already known") + + // ErrNoGenesis is returned when there is no Genesis Block. + ErrNoGenesis = errors.New("genesis not found in chain") + + errSideChainReceipts = errors.New("side blocks can't be accepted as ancient chain data") +) + +// List of evm-call-message pre-checking errors. All state transition messages will +// be pre-checked before execution. If any invalidation detected, the corresponding +// error should be returned which is defined here. +// +// - If the pre-checking happens in the miner, then the transaction won't be packed. +// - If the pre-checking happens in the block processing procedure, then a "BAD BLOCk" +// error should be emitted. +var ( + // ErrNonceTooLow is returned if the nonce of a transaction is lower than the + // one present in the local chain. + ErrNonceTooLow = errors.New("nonce too low") + + // ErrNonceTooHigh is returned if the nonce of a transaction is higher than the + // next one expected based on the local chain. + ErrNonceTooHigh = errors.New("nonce too high") + + // ErrNonceMax is returned if the nonce of a transaction sender account has + // maximum allowed value and would become invalid if incremented. + ErrNonceMax = errors.New("nonce has max value") + + // ErrGasLimitReached is returned by the gas pool if the amount of gas required + // by a transaction is higher than what's left in the block. + ErrGasLimitReached = errors.New("gas limit reached") + + // ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't + // have enough funds for transfer(topmost call only). + ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer") + + // ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger + // than init code size limit. + ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded") + + // ErrInsufficientBalanceWitness is returned if the transaction sender has enough + // funds to cover the transfer, but not enough to pay for witness access/modification + // costs for the transaction + ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction") + + // ErrInsufficientFunds is returned if the total cost of executing a transaction + // is higher than the balance of the user's account. + ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") + + // ErrGasUintOverflow is returned when calculating gas usage. + ErrGasUintOverflow = errors.New("gas uint64 overflow") + + // ErrIntrinsicGas is returned if the transaction is specified to use less gas + // than required to start the invocation. + ErrIntrinsicGas = errors.New("intrinsic gas too low") + + // ErrTxTypeNotSupported is returned if a transaction is not supported in the + // current network configuration. + ErrTxTypeNotSupported = types.ErrTxTypeNotSupported + + // ErrTipAboveFeeCap is a sanity error to ensure no one is able to specify a + // transaction with a tip higher than the total fee cap. + ErrTipAboveFeeCap = errors.New("max priority fee per gas higher than max fee per gas") + + // ErrTipVeryHigh is a sanity error to avoid extremely big numbers specified + // in the tip field. + ErrTipVeryHigh = errors.New("max priority fee per gas higher than 2^256-1") + + // ErrFeeCapVeryHigh is a sanity error to avoid extremely big numbers specified + // in the fee cap field. + ErrFeeCapVeryHigh = errors.New("max fee per gas higher than 2^256-1") + + // ErrFeeCapTooLow is returned if the transaction fee cap is less than the + // base fee of the block. + ErrFeeCapTooLow = errors.New("max fee per gas less than block base fee") + + // ErrSenderNoEOA is returned if the sender of a transaction is a contract. + ErrSenderNoEOA = errors.New("sender not an eoa") + + // ErrBlobFeeCapTooLow is returned if the transaction fee cap is less than the + // blob gas fee of the block. + ErrBlobFeeCapTooLow = errors.New("max fee per blob gas less than block blob gas fee") + + // ErrMissingBlobHashes is returned if a blob transaction has no blob hashes. + ErrMissingBlobHashes = errors.New("blob transaction missing blob hashes") + + // ErrBlobTxCreate is returned if a blob transaction has no explicit to field. + ErrBlobTxCreate = errors.New("blob transaction of type create") +) diff --git a/core/events.go b/core/events.go new file mode 100644 index 0000000..ac935a1 --- /dev/null +++ b/core/events.go @@ -0,0 +1,43 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// NewTxsEvent is posted when a batch of transactions enter the transaction pool. +type NewTxsEvent struct{ Txs []*types.Transaction } + +// NewMinedBlockEvent is posted when a block has been imported. +type NewMinedBlockEvent struct{ Block *types.Block } + +// RemovedLogsEvent is posted when a reorg happens +type RemovedLogsEvent struct{ Logs []*types.Log } + +type ChainEvent struct { + Block *types.Block + Hash common.Hash + Logs []*types.Log +} + +type ChainSideEvent struct { + Block *types.Block +} + +type ChainHeadEvent struct{ Block *types.Block } diff --git a/core/evm.go b/core/evm.go new file mode 100644 index 0000000..5d3c454 --- /dev/null +++ b/core/evm.go @@ -0,0 +1,142 @@ +// 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 core + +import ( + "math/big" + + "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/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/holiman/uint256" +) + +// ChainContext supports retrieving headers and consensus parameters from the +// current blockchain to be used during transaction processing. +type ChainContext interface { + // Engine retrieves the chain's consensus engine. + Engine() consensus.Engine + + // GetHeader returns the header corresponding to the hash/number argument pair. + GetHeader(common.Hash, uint64) *types.Header +} + +// NewEVMBlockContext creates a new context for use in the EVM. +func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { + var ( + beneficiary common.Address + baseFee *big.Int + blobBaseFee *big.Int + random *common.Hash + ) + + // If we don't have an explicit author (i.e. not mining), extract from the header + if author == nil { + beneficiary, _ = chain.Engine().Author(header) // Ignore error, we're past header validation + } else { + beneficiary = *author + } + if header.BaseFee != nil { + baseFee = new(big.Int).Set(header.BaseFee) + } + if header.ExcessBlobGas != nil { + blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) + } + if header.Difficulty.Sign() == 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, + BlobBaseFee: blobBaseFee, + GasLimit: header.GasLimit, + Random: random, + } +} + +// NewEVMTxContext creates a new transaction context for a single transaction. +func NewEVMTxContext(msg *Message) 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 +func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash { + // Cache will initially contain [refHash.parent], + // Then fill up with [refHash.p, refHash.pp, refHash.ppp, ...] + var cache []common.Hash + + return func(n uint64) common.Hash { + if ref.Number.Uint64() <= n { + // This situation can happen if we're doing tracing and using + // block overrides. + return common.Hash{} + } + // If there's no hash cache yet, make one + if len(cache) == 0 { + cache = append(cache, ref.ParentHash) + } + if idx := ref.Number.Uint64() - n - 1; idx < uint64(len(cache)) { + return cache[idx] + } + // No luck in the cache, but we can start iterating from the last element we already know + lastKnownHash := cache[len(cache)-1] + lastKnownNumber := ref.Number.Uint64() - uint64(len(cache)) + + for { + header := chain.GetHeader(lastKnownHash, lastKnownNumber) + if header == nil { + break + } + cache = append(cache, header.ParentHash) + lastKnownHash = header.ParentHash + lastKnownNumber = header.Number.Uint64() - 1 + if n == lastKnownNumber { + return lastKnownHash + } + } + return common.Hash{} + } +} + +// CanTransfer checks whether there are enough funds in the address' account to make a transfer. +// This does not take the necessary gas in to account to make the transfer valid. +func CanTransfer(db vm.StateDB, addr common.Address, amount *uint256.Int) bool { + return db.GetBalance(addr).Cmp(amount) >= 0 +} + +// Transfer subtracts amount from sender and adds amount to recipient using the given Db +func Transfer(db vm.StateDB, sender, recipient common.Address, amount *uint256.Int) { + db.SubBalance(sender, amount, tracing.BalanceChangeTransfer) + db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer) +} diff --git a/core/forkchoice.go b/core/forkchoice.go new file mode 100644 index 0000000..b293c85 --- /dev/null +++ b/core/forkchoice.go @@ -0,0 +1,113 @@ +// 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 core + +import ( + crand "crypto/rand" + "errors" + "math/big" + mrand "math/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +// ChainReader defines a small collection of methods needed to access the local +// blockchain during header verification. It's implemented by both blockchain +// and lightchain. +type ChainReader interface { + // Config retrieves the header chain's chain configuration. + Config() *params.ChainConfig + + // GetTd returns the total difficulty of a local block. + GetTd(common.Hash, uint64) *big.Int +} + +// ForkChoice is the fork chooser based on the highest total difficulty of the +// chain(the fork choice used in the eth1) and the external fork choice (the fork +// choice used in the eth2). This main goal of this ForkChoice is not only for +// offering fork choice during the eth1/2 merge phase, but also keep the compatibility +// for all other proof-of-work networks. +type ForkChoice struct { + chain ChainReader + rand *mrand.Rand + + // preserve is a helper function used in td fork choice. + // Miners will prefer to choose the local mined block if the + // local td is equal to the extern one. It can be nil for light + // client + preserve func(header *types.Header) bool +} + +func NewForkChoice(chainReader ChainReader, preserve func(header *types.Header) bool) *ForkChoice { + // Seed a fast but crypto originating random generator + seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) + if err != nil { + log.Crit("Failed to initialize random seed", "err", err) + } + return &ForkChoice{ + chain: chainReader, + rand: mrand.New(mrand.NewSource(seed.Int64())), + preserve: preserve, + } +} + +// ReorgNeeded returns whether the reorg should be applied +// based on the given external header and local canonical chain. +// In the td mode, the new head is chosen if the corresponding +// total difficulty is higher. In the extern mode, the trusted +// header is always selected as the head. +func (f *ForkChoice) ReorgNeeded(current *types.Header, extern *types.Header) (bool, error) { + var ( + localTD = f.chain.GetTd(current.Hash(), current.Number.Uint64()) + externTd = f.chain.GetTd(extern.Hash(), extern.Number.Uint64()) + ) + if localTD == nil || externTd == nil { + return false, errors.New("missing td") + } + // Accept the new header as the chain head if the transition + // is already triggered. We assume all the headers after the + // transition come from the trusted consensus layer. + if ttd := f.chain.Config().TerminalTotalDifficulty; ttd != nil && ttd.Cmp(externTd) <= 0 { + return true, nil + } + + // If the total difficulty is higher than our known, add it to the canonical chain + if diff := externTd.Cmp(localTD); diff > 0 { + return true, nil + } else if diff < 0 { + return false, nil + } + // Local and external difficulty is identical. + // Second clause in the if statement reduces the vulnerability to selfish mining. + // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf + reorg := false + externNum, localNum := extern.Number.Uint64(), current.Number.Uint64() + if externNum < localNum { + reorg = true + } else if externNum == localNum { + var currentPreserve, externPreserve bool + if f.preserve != nil { + currentPreserve, externPreserve = f.preserve(current), f.preserve(extern) + } + reorg = !currentPreserve && (externPreserve || f.rand.Float64() < 0.5) + } + return reorg, nil +} diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go new file mode 100644 index 0000000..4db366d --- /dev/null +++ b/core/forkid/forkid.go @@ -0,0 +1,297 @@ +// 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 forkid implements EIP-2124 (https://eips.ethereum.org/EIPS/eip-2124). +package forkid + +import ( + "encoding/binary" + "errors" + "hash/crc32" + "math" + "math/big" + "reflect" + "slices" + "strings" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +var ( + // ErrRemoteStale is returned by the validator if a remote fork checksum is a + // subset of our already applied forks, but the announced next fork block is + // not on our already passed chain. + ErrRemoteStale = errors.New("remote needs update") + + // ErrLocalIncompatibleOrStale is returned by the validator if a remote fork + // checksum does not match any local checksum variation, signalling that the + // two chains have diverged in the past at some point (possibly at genesis). + ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update") +) + +// timestampThreshold is the Ethereum mainnet genesis timestamp. It is used to +// differentiate if a forkid.next field is a block number or a timestamp. Whilst +// very hacky, something's needed to split the validation during the transition +// period (block forks -> time forks). +const timestampThreshold = 1438269973 + +// Blockchain defines all necessary method to build a forkID. +type Blockchain interface { + // Config retrieves the chain's fork configuration. + Config() *params.ChainConfig + + // Genesis retrieves the chain's genesis block. + Genesis() *types.Block + + // CurrentHeader retrieves the current head header of the canonical chain. + CurrentHeader() *types.Header +} + +// ID is a fork identifier as defined by EIP-2124. +type ID struct { + Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers + Next uint64 // Block number of the next upcoming fork, or 0 if no forks are known +} + +// Filter is a fork id filter to validate a remotely advertised ID. +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 *types.Block, head, time uint64) ID { + // Calculate the starting checksum from the genesis hash + hash := crc32.ChecksumIEEE(genesis.Hash().Bytes()) + + // Calculate the current fork checksum and the next fork block + forksByBlock, forksByTime := gatherForks(config, genesis.Time()) + for _, fork := range forksByBlock { + if fork <= head { + // Fork already passed, checksum the previous hash and the fork number + hash = checksumUpdate(hash, fork) + continue + } + return ID{Hash: checksumToBytes(hash), Next: fork} + } + for _, fork := range forksByTime { + if fork <= time { + // Fork already passed, checksum the previous hash and fork timestamp + hash = checksumUpdate(hash, fork) + continue + } + return ID{Hash: checksumToBytes(hash), Next: fork} + } + return ID{Hash: checksumToBytes(hash), Next: 0} +} + +// NewIDWithChain calculates the Ethereum fork ID from an existing chain instance. +func NewIDWithChain(chain Blockchain) ID { + head := chain.CurrentHeader() + + return NewID( + chain.Config(), + chain.Genesis(), + head.Number.Uint64(), + head.Time, + ) +} + +// NewFilter creates a filter that returns if a fork ID should be rejected or not +// based on the local chain's status. +func NewFilter(chain Blockchain) Filter { + return newFilter( + chain.Config(), + chain.Genesis(), + func() (uint64, uint64) { + head := chain.CurrentHeader() + return head.Number.Uint64(), head.Time + }, + ) +} + +// NewStaticFilter creates a filter at block zero. +func NewStaticFilter(config *params.ChainConfig, genesis *types.Block) Filter { + head := func() (uint64, uint64) { return 0, 0 } + return newFilter(config, genesis, head) +} + +// 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 *types.Block, headfn func() (uint64, uint64)) Filter { + // Calculate the all the valid fork hash and fork next combos + var ( + 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().Bytes()) + sums[0] = checksumToBytes(hash) + for i, fork := range forks { + hash = checksumUpdate(hash, fork) + sums[i+1] = checksumToBytes(hash) + } + // Add two sentries to simplify the fork checks and don't require special + // casing the last one. + forks = append(forks, math.MaxUint64) // Last fork will never be passed + if len(forksByTime) == 0 { + // In purely block based forks, avoid the sentry spilling into timestapt territory + forksByBlock = append(forksByBlock, math.MaxUint64) // Last fork will never be passed + } + // Create a validator that will filter out incompatible chains + return func(id ID) error { + // Run the fork checksum validation ruleset: + // 1. If local and remote FORK_CSUM matches, compare local head to FORK_NEXT. + // The two nodes are in the same fork state currently. They might know + // of differing future forks, but that's not relevant until the fork + // triggers (might be postponed, nodes might be updated to match). + // 1a. A remotely announced but remotely not passed block is already passed + // locally, disconnect, since the chains are incompatible. + // 1b. No remotely announced fork; or not yet passed locally, connect. + // 2. If the remote FORK_CSUM is a subset of the local past forks and the + // remote FORK_NEXT matches with the locally following fork block number, + // connect. + // Remote node is currently syncing. It might eventually diverge from + // us, but at this current point in time we don't have enough information. + // 3. If the remote FORK_CSUM is a superset of the local past forks and can + // be completed with locally known future forks, connect. + // Local node is currently syncing. It might eventually diverge from + // the remote, but at this current point in time we don't have enough + // information. + // 4. Reject in all other cases. + block, time := headfn() + for i, fork := range forks { + // Pick the head comparison based on fork progression + head := block + if i >= len(forksByBlock) { + head = time + } + // If our head is beyond this fork, continue to the next (we have a dummy + // fork of maxuint64 as the last item to always fail this check eventually). + if head >= fork { + continue + } + // Found the first unpassed fork block, check if our current state matches + // the remote checksum (rule #1). + if sums[i] == id.Hash { + // Fork checksum matched, check if a remote future fork block already passed + // locally without the local node being aware of it (rule #1a). + if id.Next > 0 && (head >= id.Next || (id.Next > timestampThreshold && time >= id.Next)) { + return ErrLocalIncompatibleOrStale + } + // Haven't passed locally a remote-only fork, accept the connection (rule #1b). + return nil + } + // The local and remote nodes are in different forks currently, check if the + // remote checksum is a subset of our local forks (rule #2). + for j := 0; j < i; j++ { + if sums[j] == id.Hash { + // Remote checksum is a subset, validate based on the announced next fork + if forks[j] != id.Next { + return ErrRemoteStale + } + return nil + } + } + // Remote chain is not a subset of our local one, check if it's a superset by + // any chance, signalling that we're simply out of sync (rule #3). + for j := i + 1; j < len(sums); j++ { + if sums[j] == id.Hash { + // Yay, remote checksum is a superset, ignore upcoming forks + return nil + } + } + // No exact, subset or superset match. We are on differing chains, reject. + return ErrLocalIncompatibleOrStale + } + log.Error("Impossible fork ID validation", "id", id) + return nil // Something's very wrong, accept rather than reject + } +} + +// checksumUpdate calculates the next IEEE CRC32 checksum based on the previous +// one and a fork block number (equivalent to CRC32(original-blob || fork)). +func checksumUpdate(hash uint32, fork uint64) uint32 { + var blob [8]byte + binary.BigEndian.PutUint64(blob[:], fork) + return crc32.Update(hash, crc32.IEEETable, blob[:]) +} + +// checksumToBytes converts a uint32 checksum into a [4]byte array. +func checksumToBytes(hash uint32) [4]byte { + var blob [4]byte + binary.BigEndian.PutUint32(blob[:], hash) + return blob +} + +// 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, genesis uint64) ([]uint64, []uint64) { + // Gather all the fork block numbers via reflection + kind := reflect.TypeOf(params.ChainConfig{}) + conf := reflect.ValueOf(config).Elem() + x := uint64(0) + var ( + forksByBlock []uint64 + forksByTime []uint64 + ) + for i := 0; i < kind.NumField(); i++ { + // Fetch the next field and skip non-fork rules + field := kind.Field(i) + + time := strings.HasSuffix(field.Name, "Time") + if !time && !strings.HasSuffix(field.Name, "Block") { + continue + } + + // Extract the fork rule block number or timestamp and aggregate it + if field.Type == reflect.TypeOf(&x) { + if rule := conf.Field(i).Interface().(*uint64); rule != nil { + forksByTime = append(forksByTime, *rule) + } + } + if field.Type == reflect.TypeOf(new(big.Int)) { + if rule := conf.Field(i).Interface().(*big.Int); rule != nil { + forksByBlock = append(forksByBlock, rule.Uint64()) + } + } + } + slices.Sort(forksByBlock) + slices.Sort(forksByTime) + + // Deduplicate fork identifiers applying multiple forks + for i := 1; i < len(forksByBlock); i++ { + if forksByBlock[i] == forksByBlock[i-1] { + forksByBlock = append(forksByBlock[:i], forksByBlock[i+1:]...) + i-- + } + } + for i := 1; i < len(forksByTime); i++ { + if forksByTime[i] == forksByTime[i-1] { + forksByTime = append(forksByTime[:i], forksByTime[i+1:]...) + i-- + } + } + // Skip any forks in block 0, that's the genesis ruleset + if len(forksByBlock) > 0 && forksByBlock[0] == 0 { + forksByBlock = forksByBlock[1:] + } + // 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 new file mode 100644 index 0000000..b9d346b --- /dev/null +++ b/core/forkid/forkid_test.go @@ -0,0 +1,441 @@ +// 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 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" +) + +// TestCreation tests that different genesis and fork rule combinations result in +// the correct fork ID. +func TestCreation(t *testing.T) { + type testcase struct { + head uint64 + time uint64 + want ID + } + tests := []struct { + config *params.ChainConfig + genesis *types.Block + cases []testcase + }{ + // Mainnet test cases + { + params.MainnetChainConfig, + core.DefaultGenesisBlock().ToBlock(), + []testcase{ + {0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced + {1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block + {1150000, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block + {1919999, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block + {1920000, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block + {2462999, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block + {2463000, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block + {2674999, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block + {2675000, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block + {4369999, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block + {4370000, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block + {7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block + {7280000, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block + {9068999, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block + {9069000, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block + {9199999, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block + {9200000, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block + {12243999, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block + {12244000, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block + {12964999, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block + {12965000, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block + {13772999, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block + {13773000, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // First Arrow Glacier block + {15049999, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block + {15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // First Gray Glacier block + {20000000, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block + {20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block + {30000000, 1710338134, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // Last Shanghai block + {40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // First Cancun block + {50000000, 2000000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // Future Cancun block + }, + }, + // Goerli test cases + { + params.GoerliChainConfig, + 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 + {1561651, 0, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block + {4460643, 0, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block + {4460644, 0, ID{Hash: checksumToBytes(0x757a1c47), Next: 5062605}}, // First Berlin block + {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: 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, + 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 + {1735371, 0, ID{Hash: checksumToBytes(0xb96cbd13), Next: 1677557088}}, // First MergeNetsplit block + {1735372, 1677557087, ID{Hash: checksumToBytes(0xb96cbd13), Next: 1677557088}}, // Last MergeNetsplit block + {1735372, 1677557088, ID{Hash: checksumToBytes(0xf7f9bc08), Next: 1706655072}}, // First Shanghai block + {1735372, 1706655071, ID{Hash: checksumToBytes(0xf7f9bc08), Next: 1706655072}}, // Last Shanghai block + {1735372, 1706655072, ID{Hash: checksumToBytes(0x88cf81d9), Next: 0}}, // First Cancun block + {1735372, 2706655072, ID{Hash: checksumToBytes(0x88cf81d9), Next: 0}}, // Future Cancun 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: 1707305664}}, // First Shanghai block + {123, 1707305663, ID{Hash: checksumToBytes(0xfd4f016b), Next: 1707305664}}, // Last Shanghai block + {123, 1707305664, ID{Hash: checksumToBytes(0x9b192ad0), Next: 0}}, // First Cancun block + {123, 2707305664, ID{Hash: checksumToBytes(0x9b192ad0), Next: 0}}, // Future Cancun block + }, + }, + } + for i, tt := range tests { + for j, ttt := range tt.cases { + if have := NewID(tt.config, tt.genesis, ttt.head, ttt.time); have != ttt.want { + t.Errorf("test %d, case %d: fork ID mismatch: have %x, want %x", i, j, have, ttt.want) + } + } + } +} + +// TestValidation tests that a local peer correctly validates and accepts a remote +// fork ID. +func TestValidation(t *testing.T) { + // Config that has not timestamp enabled + legacyConfig := *params.MainnetChainConfig + legacyConfig.ShanghaiTime = nil + legacyConfig.CancunTime = nil + + tests := []struct { + config *params.ChainConfig + head uint64 + time uint64 + id ID + err error + }{ + //------------------ + // Block based tests + //------------------ + + // Local is mainnet Gray Glacier, remote announces the same. No future fork is announced. + {&legacyConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}, nil}, + + // Local is mainnet Gray Glacier, remote announces the same. Remote also announces a next fork + // at block 0xffffffff, but that is uncertain. + {&legacyConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: math.MaxUint64}, nil}, + + // Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces + // also Byzantium, but it's not yet aware of Petersburg (e.g. non updated node before the fork). + // In this case we don't know if Petersburg passed yet or not. + {&legacyConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil}, + + // Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces + // also Byzantium, and it's also aware of Petersburg (e.g. updated node before the fork). We + // don't know if Petersburg passed yet (will pass) or not. + {&legacyConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, + + // Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces + // also Byzantium, and it's also aware of some random fork (e.g. misconfigured Petersburg). As + // neither forks passed at neither nodes, they may mismatch, but we still connect for now. + {&legacyConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: math.MaxUint64}, nil}, + + // Local is mainnet exactly on Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote + // is simply out of sync, accept. + {&legacyConfig, 7280000, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, + + // Local is mainnet Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote + // is simply out of sync, accept. + {&legacyConfig, 7987396, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, + + // Local is mainnet Petersburg, remote announces Spurious + knowledge about Byzantium. Remote + // is definitely out of sync. It may or may not need the Petersburg update, we don't know yet. + {&legacyConfig, 7987396, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil}, + + // Local is mainnet Byzantium, remote announces Petersburg. Local is out of sync, accept. + {&legacyConfig, 7279999, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil}, + + // Local is mainnet Spurious, remote announces Byzantium, but is not aware of Petersburg. Local + // out of sync. Local also knows about a future fork, but that is uncertain yet. + {&legacyConfig, 4369999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil}, + + // Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks. + // Remote needs software update. + {&legacyConfig, 7987396, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, ErrRemoteStale}, + + // Local is mainnet Petersburg, and isn't aware of more forks. Remote announces Petersburg + + // 0xffffffff. Local needs software update, reject. + {&legacyConfig, 7987396, 0, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Byzantium, and is aware of Petersburg. Remote announces Petersburg + + // 0xffffffff. Local needs software update, reject. + {&legacyConfig, 7279999, 0, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Petersburg, remote is Rinkeby Petersburg. + {&legacyConfig, 7987396, 0, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Gray Glacier, far in the future. Remote announces Gopherium (non existing fork) + // at some future block 88888888, for itself, but past block for local. Local is incompatible. + // + // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). + {&legacyConfig, 88888888, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 88888888}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing + // fork) at block 7279999, before Petersburg. Local is incompatible. + {&legacyConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrLocalIncompatibleOrStale}, + + //------------------------------------ + // Block to timestamp transition tests + //------------------------------------ + + // Local is mainnet currently in Gray Glacier only (so it's aware of Shanghai), remote announces + // also Gray Glacier, but it's not yet aware of Shanghai (e.g. non updated node before the fork). + // In this case we don't know if Shanghai passed yet or not. + {params.MainnetChainConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}, nil}, + + // Local is mainnet currently in Gray Glacier only (so it's aware of Shanghai), remote announces + // also Gray Glacier, and it's also aware of Shanghai (e.g. updated node before the fork). We + // don't know if Shanghai passed yet (will pass) or not. + {params.MainnetChainConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}, nil}, + + // Local is mainnet currently in Gray Glacier only (so it's aware of Shanghai), remote announces + // also Gray Glacier, and it's also aware of some random fork (e.g. misconfigured Shanghai). As + // neither forks passed at neither nodes, they may mismatch, but we still connect for now. + {params.MainnetChainConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: math.MaxUint64}, nil}, + + // Local is mainnet exactly on Shanghai, remote announces Gray Glacier + knowledge about Shanghai. Remote + // is simply out of sync, accept. + {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}, nil}, + + // Local is mainnet Shanghai, remote announces Gray Glacier + knowledge about Shanghai. Remote + // is simply out of sync, accept. + {params.MainnetChainConfig, 20123456, 1681338456, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}, nil}, + + // Local is mainnet Shanghai, remote announces Arrow Glacier + knowledge about Gray Glacier. Remote + // is definitely out of sync. It may or may not need the Shanghai update, we don't know yet. + {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}, nil}, + + // Local is mainnet Gray Glacier, remote announces Shanghai. Local is out of sync, accept. + {params.MainnetChainConfig, 15050000, 0, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}, nil}, + + // Local is mainnet Arrow Glacier, remote announces Gray Glacier, but is not aware of Shanghai. Local + // out of sync. Local also knows about a future fork, but that is uncertain yet. + {params.MainnetChainConfig, 13773000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}, nil}, + + // Local is mainnet Shanghai. remote announces Gray Glacier but is not aware of further forks. + // Remote needs software update. + {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}, ErrRemoteStale}, + + // Local is mainnet Gray Glacier, and isn't aware of more forks. Remote announces Gray Glacier + + // 0xffffffff. Local needs software update, reject. + {params.MainnetChainConfig, 15050000, 0, ID{Hash: checksumToBytes(checksumUpdate(0xf0afd0e3, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Gray Glacier, and is aware of Shanghai. Remote announces Shanghai + + // 0xffffffff. Local needs software update, reject. + {params.MainnetChainConfig, 15050000, 0, ID{Hash: checksumToBytes(checksumUpdate(0xdce96c2d, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Gray Glacier, far in the future. Remote announces Gopherium (non existing fork) + // at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible. + // + // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). + {params.MainnetChainConfig, 888888888, 1660000000, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1660000000}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Gray Glacier. Remote is also in Gray Glacier, but announces Gopherium (non existing + // fork) at block 7279999, before Shanghai. Local is incompatible. + {params.MainnetChainConfig, 19999999, 1667999999, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1667999999}, ErrLocalIncompatibleOrStale}, + + //---------------------- + // Timestamp based tests + //---------------------- + + // Local is mainnet Shanghai, remote announces the same. No future fork is announced. + {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}, nil}, + + // Local is mainnet Shanghai, remote announces the same. Remote also announces a next fork + // at time 0xffffffff, but that is uncertain. + {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: math.MaxUint64}, nil}, + + // Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces + // also Shanghai, but it's not yet aware of Cancun (e.g. non updated node before the fork). + // In this case we don't know if Cancun passed yet or not. + {params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}, nil}, + + // Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces + // also Shanghai, and it's also aware of Cancun (e.g. updated node before the fork). We + // don't know if Cancun passed yet (will pass) or not. + {params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil}, + + // Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces + // also Shanghai, and it's also aware of some random fork (e.g. misconfigured Cancun). As + // neither forks passed at neither nodes, they may mismatch, but we still connect for now. + {params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: math.MaxUint64}, nil}, + + // Local is mainnet exactly on Cancun, remote announces Shanghai + knowledge about Cancun. Remote + // is simply out of sync, accept. + {params.MainnetChainConfig, 21000000, 1710338135, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil}, + + // Local is mainnet Cancun, remote announces Shanghai + knowledge about Cancun. Remote + // is simply out of sync, accept. + {params.MainnetChainConfig, 21123456, 1710338136, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}, nil}, + + // Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote + // is definitely out of sync. It may or may not need the Prague update, we don't know yet. + // + // TODO(karalabe): Enable this when Cancun **and** Prague is specced, update all the numbers + //{params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil}, + + // Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept. + {params.MainnetChainConfig, 21000000, 1700000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}, nil}, + + // Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local + // out of sync. Local also knows about a future fork, but that is uncertain yet. + // + // TODO(karalabe): Enable this when Cancun **and** Prague is specced, update remote checksum + //{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x00000000), Next: 0}, nil}, + + // Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks. + // Remote needs software update. + {params.MainnetChainConfig, 21000000, 1710338135, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}, ErrRemoteStale}, + + // Local is mainnet Shanghai, and isn't aware of more forks. Remote announces Shanghai + + // 0xffffffff. Local needs software update, reject. + {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(checksumUpdate(0xdce96c2d, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Shanghai, and is aware of Cancun. Remote announces Cancun + + // 0xffffffff. Local needs software update, reject. + {params.MainnetChainConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(checksumUpdate(0x9f3d2254, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Shanghai, remote is random Shanghai. + {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Cancun, far in the future. Remote announces Gopherium (non existing fork) + // at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible. + // + // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). + {params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0x9f3d2254), Next: 8888888888}, ErrLocalIncompatibleOrStale}, + + // Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing + // fork) at timestamp 1668000000, before Cancun. Local is incompatible. + {params.MainnetChainConfig, 20999999, 1699999999, ID{Hash: checksumToBytes(0x71147644), Next: 1700000000}, ErrLocalIncompatibleOrStale}, + } + genesis := core.DefaultGenesisBlock().ToBlock() + for i, tt := range tests { + 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) + } + } +} + +// Tests that IDs are properly RLP encoded (specifically important because we +// use uint32 to store the hash, but we need to encode it as [4]byte). +func TestEncoding(t *testing.T) { + tests := []struct { + id ID + want []byte + }{ + {ID{Hash: checksumToBytes(0), Next: 0}, common.Hex2Bytes("c6840000000080")}, + {ID{Hash: checksumToBytes(0xdeadbeef), Next: 0xBADDCAFE}, common.Hex2Bytes("ca84deadbeef84baddcafe,")}, + {ID{Hash: checksumToBytes(math.MaxUint32), Next: math.MaxUint64}, common.Hex2Bytes("ce84ffffffff88ffffffffffffffff")}, + } + for i, tt := range tests { + have, err := rlp.EncodeToBytes(tt.id) + if err != nil { + t.Errorf("test %d: failed to encode forkid: %v", i, err) + continue + } + if !bytes.Equal(have, tt.want) { + t.Errorf("test %d: RLP mismatch: have %x, want %x", i, have, tt.want) + } + } +} + +// 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/gaspool.go b/core/gaspool.go new file mode 100644 index 0000000..7672226 --- /dev/null +++ b/core/gaspool.go @@ -0,0 +1,59 @@ +// 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 core + +import ( + "fmt" + "math" +) + +// GasPool tracks the amount of gas available during execution of the transactions +// in a block. The zero value is a pool with zero gas available. +type GasPool uint64 + +// AddGas makes gas available for execution. +func (gp *GasPool) AddGas(amount uint64) *GasPool { + if uint64(*gp) > math.MaxUint64-amount { + panic("gas pool pushed above uint64") + } + *(*uint64)(gp) += amount + return gp +} + +// SubGas deducts the given amount from the pool if enough gas is +// available and returns an error otherwise. +func (gp *GasPool) SubGas(amount uint64) error { + if uint64(*gp) < amount { + return ErrGasLimitReached + } + *(*uint64)(gp) -= amount + return nil +} + +// Gas returns the amount of gas remaining in the pool. +func (gp *GasPool) Gas() uint64 { + return uint64(*gp) +} + +// SetGas sets the amount of gas with the provided number. +func (gp *GasPool) SetGas(gas uint64) { + *(*uint64)(gp) = gas +} + +func (gp *GasPool) String() string { + return fmt.Sprintf("%d", *gp) +} diff --git a/core/gen_genesis.go b/core/gen_genesis.go new file mode 100644 index 0000000..2028f98 --- /dev/null +++ b/core/gen_genesis.go @@ -0,0 +1,137 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package core + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +var _ = (*genesisSpecMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (g Genesis) MarshalJSON() ([]byte, error) { + type Genesis struct { + Config *params.ChainConfig `json:"config"` + Nonce math.HexOrDecimal64 `json:"nonce"` + Timestamp math.HexOrDecimal64 `json:"timestamp"` + ExtraData hexutil.Bytes `json:"extraData"` + GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` + Mixhash common.Hash `json:"mixHash"` + Coinbase common.Address `json:"coinbase"` + Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` + Number math.HexOrDecimal64 `json:"number"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + ParentHash common.Hash `json:"parentHash"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` + } + var enc Genesis + enc.Config = g.Config + enc.Nonce = math.HexOrDecimal64(g.Nonce) + enc.Timestamp = math.HexOrDecimal64(g.Timestamp) + enc.ExtraData = g.ExtraData + enc.GasLimit = math.HexOrDecimal64(g.GasLimit) + enc.Difficulty = (*math.HexOrDecimal256)(g.Difficulty) + enc.Mixhash = g.Mixhash + enc.Coinbase = g.Coinbase + if g.Alloc != nil { + enc.Alloc = make(map[common.UnprefixedAddress]types.Account, len(g.Alloc)) + for k, v := range g.Alloc { + enc.Alloc[common.UnprefixedAddress(k)] = v + } + } + enc.Number = math.HexOrDecimal64(g.Number) + enc.GasUsed = math.HexOrDecimal64(g.GasUsed) + enc.ParentHash = g.ParentHash + enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee) + enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas) + enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (g *Genesis) UnmarshalJSON(input []byte) error { + type Genesis struct { + Config *params.ChainConfig `json:"config"` + Nonce *math.HexOrDecimal64 `json:"nonce"` + Timestamp *math.HexOrDecimal64 `json:"timestamp"` + ExtraData *hexutil.Bytes `json:"extraData"` + GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` + Mixhash *common.Hash `json:"mixHash"` + Coinbase *common.Address `json:"coinbase"` + Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"number"` + GasUsed *math.HexOrDecimal64 `json:"gasUsed"` + ParentHash *common.Hash `json:"parentHash"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` + } + var dec Genesis + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Config != nil { + g.Config = dec.Config + } + if dec.Nonce != nil { + g.Nonce = uint64(*dec.Nonce) + } + if dec.Timestamp != nil { + g.Timestamp = uint64(*dec.Timestamp) + } + if dec.ExtraData != nil { + g.ExtraData = *dec.ExtraData + } + if dec.GasLimit == nil { + return errors.New("missing required field 'gasLimit' for Genesis") + } + g.GasLimit = uint64(*dec.GasLimit) + if dec.Difficulty == nil { + return errors.New("missing required field 'difficulty' for Genesis") + } + g.Difficulty = (*big.Int)(dec.Difficulty) + if dec.Mixhash != nil { + g.Mixhash = *dec.Mixhash + } + if dec.Coinbase != nil { + g.Coinbase = *dec.Coinbase + } + if dec.Alloc == nil { + return errors.New("missing required field 'alloc' for Genesis") + } + g.Alloc = make(types.GenesisAlloc, len(dec.Alloc)) + for k, v := range dec.Alloc { + g.Alloc[common.Address(k)] = v + } + if dec.Number != nil { + g.Number = uint64(*dec.Number) + } + if dec.GasUsed != nil { + g.GasUsed = uint64(*dec.GasUsed) + } + if dec.ParentHash != nil { + g.ParentHash = *dec.ParentHash + } + if dec.BaseFee != nil { + g.BaseFee = (*big.Int)(dec.BaseFee) + } + if dec.ExcessBlobGas != nil { + g.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } + if dec.BlobGasUsed != nil { + g.BlobGasUsed = (*uint64)(dec.BlobGasUsed) + } + return nil +} diff --git a/core/genesis.go b/core/genesis.go new file mode 100644 index 0000000..97e3619 --- /dev/null +++ b/core/genesis.go @@ -0,0 +1,669 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "github.com/holiman/uint256" +) + +//go:generate go run github.com/fjl/gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go + +var errGenesisNoConfig = errors.New("genesis has no chain configuration") + +// Deprecated: use types.Account instead. +type GenesisAccount = types.Account + +// Deprecated: use types.GenesisAlloc instead. +type GenesisAlloc = types.GenesisAlloc + +// Genesis specifies the header fields, state of a genesis block. It also defines hard +// fork switch-over blocks through the chain configuration. +type Genesis struct { + Config *params.ChainConfig `json:"config"` + Nonce uint64 `json:"nonce"` + Timestamp uint64 `json:"timestamp"` + ExtraData []byte `json:"extraData"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + Difficulty *big.Int `json:"difficulty" gencodec:"required"` + Mixhash common.Hash `json:"mixHash"` + Coinbase common.Address `json:"coinbase"` + Alloc types.GenesisAlloc `json:"alloc" gencodec:"required"` + + // These fields are used for consensus tests. Please don't use them + // in actual genesis blocks. + Number uint64 `json:"number"` + GasUsed uint64 `json:"gasUsed"` + ParentHash common.Hash `json:"parentHash"` + BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559 + ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844 + BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844 +} + +func ReadGenesis(db ethdb.Database) (*Genesis, error) { + var genesis Genesis + stored := rawdb.ReadCanonicalHash(db, 0) + if (stored == common.Hash{}) { + return nil, fmt.Errorf("invalid genesis hash in database: %x", stored) + } + blob := rawdb.ReadGenesisStateSpec(db, stored) + if blob == nil { + return nil, errors.New("genesis state missing from db") + } + if len(blob) != 0 { + if err := genesis.Alloc.UnmarshalJSON(blob); err != nil { + return nil, fmt.Errorf("could not unmarshal genesis state json: %s", err) + } + } + genesis.Config = rawdb.ReadChainConfig(db, stored) + if genesis.Config == nil { + return nil, errors.New("genesis config missing from db") + } + genesisBlock := rawdb.ReadBlock(db, stored, 0) + if genesisBlock == nil { + return nil, errors.New("genesis block missing from db") + } + genesisHeader := genesisBlock.Header() + genesis.Nonce = genesisHeader.Nonce.Uint64() + genesis.Timestamp = genesisHeader.Time + genesis.ExtraData = genesisHeader.Extra + genesis.GasLimit = genesisHeader.GasLimit + genesis.Difficulty = genesisHeader.Difficulty + genesis.Mixhash = genesisHeader.MixDigest + genesis.Coinbase = genesisHeader.Coinbase + genesis.BaseFee = genesisHeader.BaseFee + genesis.ExcessBlobGas = genesisHeader.ExcessBlobGas + genesis.BlobGasUsed = genesisHeader.BlobGasUsed + + return &genesis, nil +} + +// hashAlloc computes the state root according to the genesis specification. +func hashAlloc(ga *types.GenesisAlloc, 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 *triedb.Config + if isVerkle { + config = &triedb.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.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), config) + statedb, err := state.New(types.EmptyRootHash, db, nil) + if err != nil { + return common.Hash{}, err + } + for addr, account := range *ga { + if account.Balance != nil { + statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance) + } + statedb.SetCode(addr, account.Code) + statedb.SetNonce(addr, account.Nonce) + for key, value := range account.Storage { + statedb.SetState(addr, key, value) + } + } + return statedb.Commit(0, false) +} + +// flushAlloc 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 flushAlloc(ga *types.GenesisAlloc, db ethdb.Database, triedb *triedb.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 { + if account.Balance != nil { + // This is not actually logged via tracer because OnGenesisBlock + // already captures the allocations. + statedb.AddBalance(addr, uint256.MustFromBig(account.Balance), tracing.BalanceIncreaseGenesisBalance) + } + statedb.SetCode(addr, account.Code) + statedb.SetNonce(addr, account.Nonce) + for key, value := range account.Storage { + statedb.SetState(addr, key, value) + } + } + root, err := statedb.Commit(0, false) + if err != nil { + return err + } + // Commit newly generated states into disk if it's not empty. + if root != types.EmptyRootHash { + if err := triedb.Commit(root, true); err != nil { + return err + } + } + // Marshal the genesis state specification and persist. + blob, err := json.Marshal(ga) + if err != nil { + return err + } + rawdb.WriteGenesisStateSpec(db, blockhash, blob) + return nil +} + +func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc types.GenesisAlloc, err error) { + blob := rawdb.ReadGenesisStateSpec(db, blockhash) + if len(blob) != 0 { + if err := alloc.UnmarshalJSON(blob); err != nil { + return nil, err + } + + return alloc, nil + } + + // 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() + case params.HoleskyGenesisHash: + genesis = DefaultHoleskyGenesisBlock() + case params.IliadGenesisHash: + genesis = DefaultIliadGenesisBlock() + } + if genesis != nil { + return genesis.Alloc, nil + } + + return nil, nil +} + +// field type overrides for gencodec +type genesisSpecMarshaling struct { + Nonce math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 + ExtraData hexutil.Bytes + GasLimit math.HexOrDecimal64 + GasUsed math.HexOrDecimal64 + Number math.HexOrDecimal64 + Difficulty *math.HexOrDecimal256 + Alloc map[common.UnprefixedAddress]types.Account + BaseFee *math.HexOrDecimal256 + ExcessBlobGas *math.HexOrDecimal64 + BlobGasUsed *math.HexOrDecimal64 +} + +// GenesisMismatchError is raised when trying to overwrite an existing +// genesis block with an incompatible one. +type GenesisMismatchError struct { + Stored, New common.Hash +} + +func (e *GenesisMismatchError) Error() string { + return fmt.Sprintf("database contains incompatible genesis (have %x, new %x)", e.Stored, e.New) +} + +// ChainOverrides contains the changes to chain config. +type ChainOverrides struct { + OverrideCancun *uint64 + OverrideVerkle *uint64 +} + +// SetupGenesisBlock writes or updates the genesis block in db. +// The block that will be used is: +// +// genesis == nil genesis != nil +// +------------------------------------------ +// db has no genesis | main-net default | genesis +// db has genesis | from DB | genesis (if compatible) +// +// The stored chain configuration will be updated if it is compatible (i.e. does not +// specify a fork block below the local head block). In case of a conflict, the +// error is a *params.ConfigCompatError and the new, unwritten config is returned. +// +// The returned chain configuration is never nil. +func SetupGenesisBlock(db ethdb.Database, triedb *triedb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { + return SetupGenesisBlockWithOverride(db, triedb, genesis, nil) +} + +func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, genesis *Genesis, overrides *ChainOverrides) (*params.ChainConfig, common.Hash, error) { + if genesis != nil && genesis.Config == nil { + return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig + } + applyOverrides := func(config *params.ChainConfig) { + if config != nil { + if overrides != nil && overrides.OverrideCancun != nil { + config.CancunTime = overrides.OverrideCancun + } + if overrides != nil && overrides.OverrideVerkle != nil { + config.VerkleTime = overrides.OverrideVerkle + } + } + } + // Just commit the new block if there is no stored genesis block. + stored := rawdb.ReadCanonicalHash(db, 0) + if (stored == common.Hash{}) { + if genesis == nil { + log.Info("Writing default main-net genesis block") + genesis = DefaultGenesisBlock() + } 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 + } + return genesis.Config, block.Hash(), nil + } + // 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 && !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 { + return genesis.Config, hash, &GenesisMismatchError{stored, hash} + } + block, err := genesis.Commit(db, triedb) + if err != nil { + return genesis.Config, hash, err + } + 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} + } + } + // Get the existing chain configuration. + newcfg := genesis.configOrDefault(stored) + applyOverrides(newcfg) + if err := newcfg.CheckConfigForkOrder(); err != nil { + return newcfg, common.Hash{}, err + } + storedcfg := rawdb.ReadChainConfig(db, stored) + if storedcfg == nil { + log.Warn("Found genesis block without chain config") + rawdb.WriteChainConfig(db, stored, newcfg) + return newcfg, stored, nil + } + storedData, _ := json.Marshal(storedcfg) + // Special case: if a private network is being used (no genesis and also no + // mainnet hash in the database), we must not apply the `configOrDefault` + // chain config as that would be AllProtocolChanges (applying any new fork + // on top of an existing private network genesis block). In that case, only + // apply the overrides. + if genesis == nil && stored != params.MainnetGenesisHash { + newcfg = storedcfg + applyOverrides(newcfg) + } + // Check config compatibility and write the config. Compatibility errors + // are returned to the caller unless we're already at block zero. + head := rawdb.ReadHeadHeader(db) + if head == nil { + return newcfg, stored, errors.New("missing head header") + } + compatErr := storedcfg.CheckCompatible(newcfg, head.Number.Uint64(), head.Time) + if compatErr != nil && ((head.Number.Uint64() != 0 && compatErr.RewindToBlock != 0) || (head.Time != 0 && compatErr.RewindToTime != 0)) { + return newcfg, stored, compatErr + } + // Don't overwrite if the old is identical to the new + if newData, _ := json.Marshal(newcfg); !bytes.Equal(storedData, newData) { + rawdb.WriteChainConfig(db, stored, newcfg) + } + return newcfg, stored, nil +} + +// LoadChainConfig loads the stored chain config if it is already present in +// database, otherwise, return the config in the provided genesis specification. +func LoadChainConfig(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, error) { + // Load the stored chain config from the database. It can be nil + // in case the database is empty. Notably, we only care about the + // chain config corresponds to the canonical chain. + stored := rawdb.ReadCanonicalHash(db, 0) + if stored != (common.Hash{}) { + storedcfg := rawdb.ReadChainConfig(db, stored) + if storedcfg != nil { + return storedcfg, nil + } + } + // Load the config from the provided genesis specification + if genesis != nil { + // Reject invalid genesis spec without valid chain config + if genesis.Config == nil { + return nil, errGenesisNoConfig + } + // If the canonical genesis header is present, but the chain + // config is missing(initialize the empty leveldb with an + // external ancient chain segment), ensure the provided genesis + // is matched. + if stored != (common.Hash{}) && genesis.ToBlock().Hash() != stored { + return nil, &GenesisMismatchError{stored, genesis.ToBlock().Hash()} + } + return genesis.Config, nil + } + // There is no stored chain config and no new config provided, + // In this case the default chain config(mainnet) will be used + return params.MainnetChainConfig, nil +} + +func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { + switch { + case g != nil: + return g.Config + case ghash == params.MainnetGenesisHash: + return params.MainnetChainConfig + case ghash == params.HoleskyGenesisHash: + return params.HoleskyChainConfig + case ghash == params.SepoliaGenesisHash: + return params.SepoliaChainConfig + case ghash == params.GoerliGenesisHash: + return params.GoerliChainConfig + case ghash == params.IliadGenesisHash: + return params.IliadChainConfig + case ghash == params.LocalGenesisHash: + return params.LocalChainConfig + default: + return params.AllEthashProtocolChanges + } +} + +// 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 := hashAlloc(&g.Alloc, g.IsVerkle()) + if err != nil { + panic(err) + } + head := &types.Header{ + Number: new(big.Int).SetUint64(g.Number), + Nonce: types.EncodeNonce(g.Nonce), + Time: g.Timestamp, + ParentHash: g.ParentHash, + Extra: g.ExtraData, + GasLimit: g.GasLimit, + GasUsed: g.GasUsed, + BaseFee: g.BaseFee, + Difficulty: g.Difficulty, + MixDigest: g.Mixhash, + Coinbase: g.Coinbase, + Root: root, + } + if g.GasLimit == 0 { + head.GasLimit = params.GenesisGasLimit + } + if g.Difficulty == nil && g.Mixhash == (common.Hash{}) { + head.Difficulty = params.GenesisDifficulty + } + if g.Config != nil && g.Config.IsLondon(common.Big0) { + if g.BaseFee != nil { + head.BaseFee = g.BaseFee + } else { + head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee) + } + } + var withdrawals []*types.Withdrawal + if conf := g.Config; conf != nil { + num := big.NewInt(int64(g.Number)) + if conf.IsShanghai(num, g.Timestamp) { + head.WithdrawalsHash = &types.EmptyWithdrawalsHash + 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 { + head.ExcessBlobGas = new(uint64) + } + if head.BlobGasUsed == nil { + head.BlobGasUsed = new(uint64) + } + } + } + return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil)) +} + +// Commit writes the block and state of a genesis specification to the database. +// The block is committed as the canonical head block. +func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Block, error) { + block := g.ToBlock() + if block.Number().Sign() != 0 { + return nil, errors.New("can't commit genesis block with number > 0") + } + config := g.Config + if config == nil { + config = params.AllEthashProtocolChanges + } + if err := config.CheckConfigForkOrder(); err != nil { + return nil, err + } + if config.Clique != nil && len(block.Extra()) < 32+crypto.SignatureLength { + return nil, errors.New("can't start clique chain without signers") + } + // All the checks has passed, flushAlloc the states derived from the genesis + // specification as well as the specification itself into the provided + // database. + if err := flushAlloc(&g.Alloc, db, triedb, block.Hash()); err != nil { + return nil, err + } + rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty()) + rawdb.WriteBlock(db, block) + rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil) + rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) + rawdb.WriteHeadBlockHash(db, block.Hash()) + rawdb.WriteHeadFastBlockHash(db, block.Hash()) + rawdb.WriteHeadHeaderHash(db, block.Hash()) + rawdb.WriteChainConfig(db, block.Hash(), config) + return block, nil +} + +// MustCommit writes the genesis block and state to db, panicking on error. +// The block is committed as the canonical head block. +func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types.Block { + block, err := g.Commit(db, triedb) + if err != nil { + panic(err) + } + return block +} + +// DefaultGenesisBlock returns the Ethereum main net genesis block. +func DefaultGenesisBlock() *Genesis { + return &Genesis{ + Config: params.MainnetChainConfig, + Nonce: 66, + ExtraData: hexutil.MustDecode("0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"), + GasLimit: 5000, + Difficulty: big.NewInt(17179869184), + Alloc: decodePrealloc(mainnetAllocData), + } +} + +// DefaultGoerliGenesisBlock returns the Görli network genesis block. +func DefaultGoerliGenesisBlock() *Genesis { + return &Genesis{ + Config: params.GoerliChainConfig, + Timestamp: 1548854791, + ExtraData: hexutil.MustDecode("0x22466c6578692069732061207468696e6722202d204166726900000000000000e0a2bd4258d2768837baa26a28fe71dc079f84c70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + GasLimit: 10485760, + Difficulty: big.NewInt(1), + Alloc: decodePrealloc(goerliAllocData), + } +} + +// DefaultSepoliaGenesisBlock returns the Sepolia network genesis block. +func DefaultSepoliaGenesisBlock() *Genesis { + return &Genesis{ + Config: params.SepoliaChainConfig, + Nonce: 0, + ExtraData: []byte("Sepolia, Athens, Attica, Greece!"), + GasLimit: 0x1c9c380, + Difficulty: big.NewInt(0x20000), + Timestamp: 1633267481, + Alloc: decodePrealloc(sepoliaAllocData), + } +} + +// 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), + } +} + +// DefaultIliadGenesisBlock returns the iliad network genesis block. +func DefaultIliadGenesisBlock() *Genesis { + return &Genesis{ + Config: params.IliadChainConfig, + Difficulty: big.NewInt(0x20000), + GasLimit: 0x7A1200, + Nonce: 0x42, + Timestamp: 0, + Alloc: decodePrealloc(iliadAllocData), + } +} + +// DefaultLocalGenesisBlock returns the network genesis block for local testing. +func DefaultLocalGenesisBlock() *Genesis { + return &Genesis{ + Config: params.LocalChainConfig, + Difficulty: big.NewInt(0x20000), + GasLimit: 0x7A1200, + Nonce: 0x42, + Timestamp: 0, + Alloc: decodePrealloc(localAllocData), + } +} + +// DeveloperGenesisBlock returns the 'geth --dev' genesis block. +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 + genesis := &Genesis{ + Config: &config, + GasLimit: gasLimit, + BaseFee: big.NewInt(params.InitialBaseFee), + Difficulty: big.NewInt(0), + Alloc: map[common.Address]types.Account{ + common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover + common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 + common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD + common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity + common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp + common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd + common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul + common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing + common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b + common.BytesToAddress([]byte{26}): {Balance: big.NewInt(1)}, // ipGraph + + // Pre-deploy EIP-4788 system contract + params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, + }, + } + if faucet != nil { + genesis.Alloc[*faucet] = types.Account{Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))} + } + return genesis +} + +func decodePrealloc(data string) types.GenesisAlloc { + 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(types.GenesisAlloc, len(p)) + for _, account := range p { + acc := types.Account{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 new file mode 100644 index 0000000..d624264 --- /dev/null +++ b/core/genesis_alloc.go @@ -0,0 +1,29 @@ +// 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 core + +// Constants containing the genesis allocation of built-in genesis blocks. +// Their content is an RLP-encoded list of (address, balance) tuples. +// Use mkalloc.go to create/update them. + +// nolint: misspell +const localAllocData = "\xf9\x86\x19\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\x1a\x01\xf9\x02U\x90eQ\xc1\x94\x87\x81F\x12\xe5\x8f\xe0h\x13wWX\x01\xf9\x02@\x80\xb9\x02;`\x80`@R4\x80\x15a\x00\x10W`\x00\x80\xfd[P`\x046\x10a\x006W`\x005`\xe0\x1c\x80c$j\x00!\x14a\x00;W\x80c\x8aT\xc5/\x14a\x00jW[`\x00\x80\xfd[a\x00Na\x00I6`\x04a\x01\xb7V[a\x00}V[`@Q`\x01`\x01`\xa0\x1b\x03\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xf3[a\x00Na\x00x6`\x04a\x01\xb7V[a\x00\xe1V[`\x00`\x80`$`\x8c7nZ\xf4=\x82\x80>\x90=\x91`+W\xfd[\xf3`lR\x85`]Rs=`\xad\x80`\n=9\x81\xf36==7===6=s`IR`\xff`\x00S`\xb7`U `5R0``\x1b`\x01R\x84`\x15R`U`\x00 ``\x1b``\x1c`\x00R` `\x00\xf3[`\x00`\x80`$`\x8c7nZ\xf4=\x82\x80>\x90=\x91`+W\xfd[\xf3`lR\x85`]Rs=`\xad\x80`\n=9\x81\xf36==7===6=s`IR`\xff`\x00S`\xb7`U `5R0``\x1b`\x01R\x84`\x15R`U`\x00 \x80;a\x01\x8bW\x85`\xb7`U`\x00\xf5\x80a\x01WWc \x18\x8aY`\x00R`\x04`\x1c\xfd[\x80`lRP\x82\x84\x88\x7fy\xf1\x9b6U\xee8\xb1\xceReV\xb7s\x1a \xc8\xf2\x18\xfb\xdaJ9\x90\xb6\xccAr\xfd\xf8\x87\"```l\xa4` `l\xf3[\x80``\x1b``\x1c`\x00R` `\x00\xf3[\x805`\x01`\x01`\xa0\x1b\x03\x81\x16\x81\x14a\x01\xb2W`\x00\x80\xfd[\x91\x90PV[`\x00\x80`\x00\x80`\x00`\xa0\x86\x88\x03\x12\x15a\x01\xcfW`\x00\x80\xfd[a\x01\u0606a\x01\x9bV[\x94P` \x86\x015\x93P`@\x86\x015\x92Pa\x01\xf4``\x87\x01a\x01\x9bV[\x94\x97\x93\x96P\x91\x94`\x80\x015\x92\x91PPV\xfe\xa2dipfsX\"\x12 \xea/\xe5:\xf5\aE\x8e\u01adw\x15V\x85\x9b\x00\xc9\x01\x11\x11\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94\r\xb6\x06\x02\u924e\xa6\x06i\u06e7\xa5\u064b/\"\xe8\x14/\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xf9\v\x93\x94\x13\xb0\xd8\\\u02cb\xf8`\xb6\xb7\x9a\xf3\x02\x9f\xca\b\x1a\xe9\xbe\xf2\x01\xf9\vz\x80\xb9\vu`\x80`@R`\x046\x10a\x00\xb5W`\x005`\xe0\x1c\x80cf\u03e0W\x11a\x00iW\x80c\x84V\xcbY\x11a\x00NW\x80c\x84V\xcbY\x14a\x01\xd2W\x80c\x8d\xa5\xcb[\x14a\x01\xe7W\x80c\xf2\xfd\xe3\x8b\x14a\x02\x12W`\x00\x80\xfd[\x80cf\u03e0W\x14a\x01\x9dW\x80cqP\x18\xa6\x14a\x01\xbdW`\x00\x80\xfd[\x80cH\x12\x86\xe6\x11a\x00\x9aW\x80cH\x12\x86\xe6\x14a\x00\xf8W\x80cV)\x94\x81\x14a\x01BW\x80c\\\x97Z\xbb\x14a\x01bW`\x00\x80\xfd[\x80c\al7\xb2\x14a\x00\xc1W\x80c?K\xa8:\x14a\x00\xe3W`\x00\x80\xfd[6a\x00\xbcW\x00[`\x00\x80\xfd[4\x80\x15a\x00\xcdW`\x00\x80\xfd[Pa\x00\xe1a\x00\xdc6`\x04a\bEV[a\x022V[\x00[4\x80\x15a\x00\xefW`\x00\x80\xfd[Pa\x00\xe1a\x02\x8aV[4\x80\x15a\x01\x04W`\x00\x80\xfd[Pa\x01\x18a\x01\x136`\x04a\bEV[a\x02\x9cV[`@Qs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x90\x91\x16\x81R` \x01[`@Q\x80\x91\x03\x90\xf3[4\x80\x15a\x01NW`\x00\x80\xfd[Pa\x01\x18a\x01]6`\x04a\b\x90V[a\x02\xafV[4\x80\x15a\x01nW`\x00\x80\xfd[P`\x00Tt\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x04`\xff\x16`@Q\x90\x15\x15\x81R` \x01a\x019V[4\x80\x15a\x01\xa9W`\x00\x80\xfd[Pa\x00\xe1a\x01\xb86`\x04a\b\xf4V[a\x02\xc4V[4\x80\x15a\x01\xc9W`\x00\x80\xfd[Pa\x00\xe1a\x02\xddV[4\x80\x15a\x01\xdeW`\x00\x80\xfd[Pa\x00\xe1a\x02\xefV[4\x80\x15a\x01\xf3W`\x00\x80\xfd[P`\x00Ts\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16a\x01\x18V[4\x80\x15a\x02\x1eW`\x00\x80\xfd[Pa\x00\xe1a\x02-6`\x04a\t\xd6V[a\x02\xffV[a\x02:a\x03\xbbV[a\x02\x85\x82\x82`@Q\x80` \x01a\x02O\x90a\b8V[\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\x82\x82\x03\x81\x01\x83R`\x1f\x90\x91\x01\x16`@Ra\x04@V[PPPV[a\x02\x92a\x05\x9fV[a\x02\x9aa\x06 V[V[`\x00a\x02\xa8\x83\x83a\x06\x9dV[\x93\x92PPPV[`\x00a\x02\xbc\x84\x84\x84a\x06\xa6V[\x94\x93PPPPV[a\x02\xcca\x03\xbbV[a\x02\u05c3\x83\x83a\x04@V[PPPPV[a\x02\xe5a\x05\x9fV[a\x02\x9a`\x00a\x06\xd0V[a\x02\xf7a\x05\x9fV[a\x02\x9aa\aEV[a\x03\aa\x05\x9fV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16a\x03\xafW`@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\x82\x01R`&`$\x82\x01R\x7fOwnable: new owner is the zero a`D\x82\x01R\x7fddress\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R`\x84\x01[`@Q\x80\x91\x03\x90\xfd[a\x03\xb8\x81a\x06\xd0V[PV[`\x00Tt\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x04`\xff\x16\x15a\x02\x9aW`@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\x82\x01R`\x10`$\x82\x01R\x7fPausable: paused\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`D\x82\x01R`d\x01a\x03\xa6V[`\x00\x83G\x10\x15a\x04\xacW`@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\x82\x01R`\x1d`$\x82\x01R\x7fCreate2: insufficient balance\x00\x00\x00`D\x82\x01R`d\x01a\x03\xa6V[\x81Q`\x00\x03a\x05\x17W`@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\x82\x01\x81\x90R`$\x82\x01R\x7fCreate2: bytecode length is zero`D\x82\x01R`d\x01a\x03\xa6V[\x82\x82Q` \x84\x01\x86\xf5\x90Ps\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16a\x02\xa8W`@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\x82\x01R`\x19`$\x82\x01R\x7fCreate2: Failed on deploy\x00\x00\x00\x00\x00\x00\x00`D\x82\x01R`d\x01a\x03\xa6V[`\x00Ts\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x163\x14a\x02\x9aW`@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\x82\x01\x81\x90R`$\x82\x01R\x7fOwnable: caller is not the owner`D\x82\x01R`d\x01a\x03\xa6V[a\x06(a\a\xb4V[`\x00\x80T\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x90U\x7f]\xb9\xee\nI[\xf2\xe6\xff\x9c\x91\xa7\x83L\x1b\xa4\xfd\xd2D\xa5\xe8\xaaNS{\u04ca\xea\xe4\xb0s\xaa3[`@Qs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xa1V[`\x00a\x02\xa8\x83\x830[`\x00`@Q\x83`@\x82\x01R\x84` \x82\x01R\x82\x81R`\v\x81\x01\x90P`\xff\x81S`U\x90 \x94\x93PPPPV[`\x00\x80Ts\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x83\x81\x16\x7f\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\x00\x00\x00\x00\x83\x16\x81\x17\x84U`@Q\x91\x90\x92\x16\x92\x83\x91\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0\x91\x90\xa3PPV[a\aMa\x03\xbbV[`\x00\x80T\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16t\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x90U\x7fb\xe7\x8c\xea\x01\xbe\xe3 \xcdNB\x02p\xb5\xeat\x00\r\x11\xb0\xc9\xf7GT\xeb\xdb\xfcTK\x05\xa2Xa\x06s3\x90V[`\x00Tt\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x04`\xff\x16a\x02\x9aW`@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\x82\x01R`\x14`$\x82\x01R\x7fPausable: not paused\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`D\x82\x01R`d\x01a\x03\xa6V[a\x01N\x80a\t\xf2\x839\x01\x90V[`\x00\x80`@\x83\x85\x03\x12\x15a\bXW`\x00\x80\xfd[PP\x805\x92` \x90\x91\x015\x91PV[\x805s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16\x81\x14a\b\x8bW`\x00\x80\xfd[\x91\x90PV[`\x00\x80`\x00``\x84\x86\x03\x12\x15a\b\xa5W`\x00\x80\xfd[\x835\x92P` \x84\x015\x91Pa\b\xbc`@\x85\x01a\bgV[\x90P\x92P\x92P\x92V[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`A`\x04R`$`\x00\xfd[`\x00\x80`\x00``\x84\x86\x03\x12\x15a\t\tW`\x00\x80\xfd[\x835\x92P` \x84\x015\x91P`@\x84\x015g\xff\xff\xff\xff\xff\xff\xff\xff\x80\x82\x11\x15a\t/W`\x00\x80\xfd[\x81\x86\x01\x91P\x86`\x1f\x83\x01\x12a\tCW`\x00\x80\xfd[\x815\x81\x81\x11\x15a\tUWa\tUa\b\xc5V[`@Q`\x1f\x82\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\x90\x81\x16`?\x01\x16\x81\x01\x90\x83\x82\x11\x81\x83\x10\x17\x15a\t\x9bWa\t\x9ba\b\xc5V[\x81`@R\x82\x81R\x89` \x84\x87\x01\x01\x11\x15a\t\xb4W`\x00\x80\xfd[\x82` \x86\x01` \x83\x017`\x00` \x84\x83\x01\x01R\x80\x95PPPPPP\x92P\x92P\x92V[`\x00` \x82\x84\x03\x12\x15a\t\xe8W`\x00\x80\xfd[a\x02\xa8\x82a\bgV\xfe`\x80`@R4\x80\x15a\x00\x10W`\x00\x80\xfd[Pa\x01.\x80a\x00 `\x009`\x00\xf3\xfe`\x80`@R4\x80\x15`\x0fW`\x00\x80\xfd[P`\x046\x10`(W`\x005`\xe0\x1c\x80c$\x9c\xb3\xfa\x14`-W[`\x00\x80\xfd[`<`86`\x04`\xb1V[`NV[`@Q\x90\x81R` \x01`@Q\x80\x91\x03\x90\xf3[`\x00\x82\x81R` \x81\x81R`@\x80\x83 s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x85\x16\x84R\x90\x91R\x81 T`\xff\x16`\x88W`\x00`\xaaV[\x7f\xa2\xefF\x00\xd7B\x02-S-GG\xcb5GGFg\xd6\xf18\x04\x90%\x13\xb2\xec\x01\xc8H\xf4\xb4[\x93\x92PPPV[`\x00\x80`@\x83\x85\x03\x12\x15`\xc3W`\x00\x80\xfd[\x825\x91P` \x83\x015s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16\x81\x14`\xedW`\x00\x80\xfd[\x80\x91PP\x92P\x92\x90PV\xfe\xa2dipfsX\"\x12 _\xfdNl\xed\xe7\xd0j]\xaf\x93\u050d\x05A\xfch\x18\x9e\xeb\x16`\x8c\x19\x99\xa8 c\xb6f\xeb\x11dsolcC\x00\b\x13\x003\xa2dipfsX\"\x12 e'\xf3d\xf9\xf5j\xf7\xd0\xc1w\xf7\xf3\xc1z|.\xf2f\xee\xda+\xb3@@\xd6\xc4\bFU\xab#dsolcC\x00\b\x13\x003\xc0\xf9\x02\xad\x948J\x89\x1d\xfd\xe8\x18\v\x05O\x04\xd6cy\xf1kzg\x8a\xd6\x01\xf9\x02\x94\x80\xb9\x02\x8f`\x80`@R`\x046\x10a\x00)W`\x005`\xe0\x1c\x80c\xcd\xcbv\n\x14a\x00.W\x80c\xdf \xe2R\x14a\x00jW[`\x00\x80\xfd[a\x00Aa\x00<6`\x04a\x01\xc4V[a\x00\x8aV[`@Qs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xf3[4\x80\x15a\x00vW`\x00\x80\xfd[Pa\x00Aa\x00\x856`\x04a\x02@V[a\x00\xd8V[`\x00a\x00\u0384\x84\x84\x80\x80`\x1f\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x93\x92\x91\x90\x81\x81R` \x01\x83\x83\x80\x82\x847`\x00\x92\x01\x91\x90\x91RP4\x92Pa\x00\xe9\x91PPV[\x90P[\x93\x92PPPV[`\x00a\x00\xe3\x82a\x01cV[\x92\x91PPV[`\x00og6==76=4\xf0=R`\b`\x18\xf3`\x00R\x83`\x10\x80`\x00\xf5\x80a\x01\x19Wc0\x11d%`\x00R`\x04`\x1c\xfd[\x80`\x14Ra\u0594`\x00R`\x01`4S`\x17`\x1e \x91P`\x00\x80\x85Q` \x87\x01\x86\x85Z\xf1a\x01NWc\x19\xb9\x91\xa8`\x00R`\x04`\x1c\xfd[P\x80;a\x00\xd1Wc\x19\xb9\x91\xa8`\x00R`\x04`\x1c\xfd[`\x00a\x00\xe3\x820`\x00`@Q\x82`\x00R`\xff`\vS\x83` R\x7f!\xc3]\xbe\x1b4J$\x88\xcf3!\xd6\xceT/\x8e\x9f0UD\xff\t\xe4\x99:b1\x9aI|\x1f`@R`U`\v `\x14R\x80`@RPa\u0594`\x00R`\x01`4SPP`\x17`\x1e \x91\x90PV[`\x00\x80`\x00`@\x84\x86\x03\x12\x15a\x01\xd9W`\x00\x80\xfd[\x835\x92P` \x84\x015g\xff\xff\xff\xff\xff\xff\xff\xff\x80\x82\x11\x15a\x01\xf8W`\x00\x80\xfd[\x81\x86\x01\x91P\x86`\x1f\x83\x01\x12a\x02\fW`\x00\x80\xfd[\x815\x81\x81\x11\x15a\x02\x1bW`\x00\x80\xfd[\x87` \x82\x85\x01\x01\x11\x15a\x02-W`\x00\x80\xfd[` \x83\x01\x94P\x80\x93PPPP\x92P\x92P\x92V[`\x00` \x82\x84\x03\x12\x15a\x02RW`\x00\x80\xfd[P5\x91\x90PV\xfe\xa2dipfsX\"\x12 o\xea\xe4#\xd7\xe5\xcdm\xbcmQ\xa3!\x81\xa2\b\x89\xed\x00\x11\x98\ua05c\xf3$\xc3|\xa3\u0df7dsolcC\x00\b\x17\x003\xc0\xe1\x94U\x18\u047d\x05G\x82y-'\x83P\x9f\xbe0\xfa\x9d\x88\x88\x88\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xf9Fv\x94_\xbd\xb21Vx\xaf\xec\xb3g\xf02\xd9?\x00\x00\x00\x00\x00\x00\x01\xf9F]\x80\xb9;\xfa`\x80`@R`\x046\x10\x15a\x00\x12W`\x00\x80\xfd[`\x005`\xe0\x1c\x80c\x05{\x92\x96\x14a\x02wW\x80c\x06\f\xea\xb0\x14a\x02rW\x80c\x17\xe4.\x12\x14a\x02mW\x80c-\x1e\x97>\x14a\x02hW\x80c.\xbc`4\x14a\x02cW\x80c9\xecM\xf9\x14a\x02^W\x80cH\x90>8\x14a\x02YW\x80cO\x1e\xf2\x86\x14a\x02TW\x80cR\u0450-\x14a\x02OW\x80cS\x97,*\x14a\x02JW\x80cW\x06u\x03\x14a\x02EW\x80cZi\x82]\x14a\x02@W\x80c]Z\xb9h\x14a\x02;W\x80cn\xa3\xa2(\x14a\x026W\x80cqP\x18\xa6\x14a\x021W\x80cx\x7f\x82\xc8\x14a\x02,W\x80cy\xbaP\x97\x14a\x02'W\x80c{n\x84,\x14a\x02\"W\x80c\x83\xdf\xfdo\x14a\x02\x1dW\x80c\x86\xee\u0121\x14a\x02\x18W\x80c\x8d>\x1eA\x14a\x02\x13W\x80c\x8d\xa5\xcb[\x14a\x02\x0eW\x80c\x8f7\xec\x19\x14a\x02\tW\x80c\x98U\u0235\x14a\x02\x04W\x80c\xa1\xcb\x18F\x14a\x01\xffW\x80c\xad<\xb1\xcc\x14a\x01\xfaW\x80c\xb8\u06d8>\x14a\x01\xf5W\x80c\xbd\xa1k\x15\x14a\x01\xf0W\x80c\xc2J\xe5\x86\x14a\x01\xebW\x80c\xd2\xe1\xf5\xb8\x14a\x01\xe6W\x80c\xe3\f9x\x14a\x01\xe1W\x80c\xebJ\xf0E\x14a\x01\xdcW\x80c\xee\xe5\u03ad\x14a\x01\xd7W\x80c\xf1\x88v\x84\x14a\x01\xd2W\x80c\xf2\xfd\xe3\x8b\x14a\x01\xcdW\x80c\xf9*\xd2\x19\x14a\x01\xc8W\x80c\xfc.Y2\x14a\x01\xc3Wc\xfcV\u00a2\x14a\x01\xbeW`\x00\x80\xfd[a\x1e\xe3V[a\x1d\xe5V[a\x1b\xa9V[a\x1a\xdcV[a\x1a\xbeV[a\x1a\x9aV[a\x1avV[a\x1a#V[a\x19\xf9V[a\x19\xe1V[a\x19\xa6V[a\x19eV[a\x19\x03V[a\x18DV[a\x18 V[a\x17\xb4V[a\x17aV[a\x16\xd6V[a\x14\x80V[a\x13\x84V[a\x12{V[a\x11\xf3V[a\x10aV[a\x0f\x96V[a\x0frV[a\x0enV[a\x0e\x01V[a\r\xe9V[a\f\xa3V[a\v\xf9V[a\t\xfcV[a\b\xbeV[a\bqV[a\b0V[a\a\xc7V[a\x04\x91V[a\x04sV[a\x03\"V[\x91\x81`\x1f\x84\x01\x12\x15a\x02\xaaW\x825\x91g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11a\x02\xaaW` \x83\x81\x86\x01\x95\x01\x01\x11a\x02\xaaWV[`\x00\x80\xfd[`\x045\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x82\x03a\x02\xaaWV[`@`\x03\x19\x82\x01\x12a\x02\xaaW`\x045\x90g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11a\x02\xaaWa\x02\xfd\x91`\x04\x01a\x02|V[\x90\x91`$5s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16\x81\x03a\x02\xaaW\x90V[4a\x02\xaaWa\x0306a\x02\xd2V[\x90a\x03=`A\x82\x14a\x1f$V[\x80\x15a\x04nWa\x03\xdba\x03\xd6a\x03\u0445a\x03\x9e\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00a\x03\xe3\x995\x16\x14a\x1f\xdeV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x94a\x03\xca3\x87a\x03\u00c4\x86a'\x1aV[\x16\x14a iV[6\x91a\x06kV[a'\xbfV[a\x06\xe3V[\x91\x16\x90a6\xf0V[\x15a\x03\xeaW\x00[`\x84`@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\x82\x01R`'`$\x82\x01R\x7fIPTokenStaking: Operator already`D\x82\x01R\x7f exists\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[a\x1f\xafV[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` `\x03T`@Q\x90\x81R\xf3[4a\x02\xaaWa\x04\x9f6a\x02\xd2V[\x90a\x04\xac`A\x82\x14a\x1f$V[\x80\x15a\x04nWa\x05\ra\x03\xd6a\x03\u0445a\x03\x9e\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00a\x05\x15\x995\x16\x14a\x1f\xdeV[\x91\x16\x90a7\xf8V[\x15a\x05\x1cW\x00[`\x84`@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\x82\x01R`\"`$\x82\x01R\x7fIPTokenStaking: Operator not fou`D\x82\x01R\x7fnd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`A`\x04R`$`\x00\xfd[`@\x81\x01\x90\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\x05\xebW`@RV[a\x05\xa0V[\x90`\x1f\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\x91\x01\x16\x81\x01\x90\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\x05\xebW`@RV[g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x05\xebW`\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` \x01\x90V[\x92\x91\x92a\x06w\x82a\x061V[\x91a\x06\x85`@Q\x93\x84a\x05\xf0V[\x82\x94\x81\x84R\x81\x83\x01\x11a\x02\xaaW\x82\x81` \x93\x84`\x00\x96\x017\x01\x01RV[\x90\x80`\x1f\x83\x01\x12\x15a\x02\xaaW\x81` a\x06\xbd\x935\x91\x01a\x06kV[\x90V[`\x00[\x83\x81\x10a\x06\xd3WPP`\x00\x91\x01RV[\x81\x81\x01Q\x83\x82\x01R` \x01a\x06\xc3V[` a\x06\xfc\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\a\x81R\x03\x01\x90 \x90V[` a\a\"\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\x06\x81R\x03\x01\x90 \x90V[` a\aH\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\x05\x81R\x03\x01\x90 \x90V[` a\an\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\b\x81R\x03\x01\x90 \x90V[` a\a\x94\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\x04\x81R\x03\x01\x90 \x90V[` \x90a\a\xbb\x92\x82`@Q\x94\x83\x86\x80\x95Q\x93\x84\x92\x01a\x06\xc0V[\x82\x01\x90\x81R\x03\x01\x90 \x90V[4a\x02\xaaW`@`\x03\x196\x01\x12a\x02\xaaWg\xff\xff\xff\xff\xff\xff\xff\xff`\x045\x81\x81\x11a\x02\xaaWa\a\xf9\x906\x90`\x04\x01a\x06\xa2V[\x90`$5\x90\x81\x11a\x02\xaaW` \x91a\b!a\b\x1ba\b'\x936\x90`\x04\x01a\x06\xa2V[\x91a\a\tV[\x90a\a\xa1V[T`@Q\x90\x81R\xf3[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` `@Qc\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x88\x16\x81R\xf3[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` `\x01T`@Q\x90\x81R\xf3[` `\x03\x19\x82\x01\x12a\x02\xaaW`\x045\x90g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11a\x02\xaaWa\b\xba\x91`\x04\x01a\x02|V[\x90\x91V[a\b\xc76a\b\x8fV[\x90a\b\xd4`A\x83\x14a\x1f$V[\x81\x15a\x04nWa\t\u0591a\t,\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00\x845\x16\x14a\x1f\xdeV[a\t4a(\xf6V[`@Q\x91a\tA\x83a\x05\xcfV[`\t\x83R\x7fvalidator\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00` \x84\x01R\x7f\x00\x00\x00\x00\x00\x00\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\xf4\x92\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x88\x92\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xe8\x92a, v\xcc75\xa9 \xa3\xcaP]8+\xbc\x83\x03a\v>Wa\v<\x92Pa9\x98V[\x00[`@Q\x7f\xaa\x1dI\xa4\x00\x00\x00\x00\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\x81\x01\x84\x90R`$\x90\xfd[a\v\x93\x91\x94P` =` \x11a\v\x9aW[a\v\x8b\x81\x83a\x05\xf0V[\x81\x01\x90a.}V[\x928a\n\xbeV[P=a\v\x81V[`\x04`@Q\x7f\xe0|\x8d\xba\x00\x00\x00\x00\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\xfd[\x90P\x83\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16\x14\x158a\nuV[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaWs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00_\xbd\xb21Vx\xaf\xec\xb3g\xf02\xd9?d/d\x18\n\xa3\x160\x03a\v\xa1W` `@Q\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81R\xf3[`\x03\x19\x90` \x82\x82\x01\x12a\x02\xaaW`\x045\x91g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11a\x02\xaaW\x82`\x80\x92\x03\x01\x12a\x02\xaaW`\x04\x01\x90V[4a\x02\xaaWa\f\xb16a\fqV[a\f\xbb\x81\x80a \xf4V[a\f\xca`A\x82\x94\x93\x94\x14a\x1f$V[\x15a\x04nWa\r\x1e\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00\x80\x945\x16\x14a\x1f\xdeV[a\r+` \x82\x01\x82a \xf4V[\x90a\r8`!\x83\x14a\x1f$V[\x81\x15a\x04nWa\r\x88a\r\x8f\x92\x82a\r\x83a\v<\x97`\xff\x955\x16\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x14\x90\x81\x15a\r\x94W[Pa\x1f\xdeV[a!EV[T\x16a!\x90V[a\"\x1bV[\x7f\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x91P\x148a\r}V[` `\x03\x19\x82\x01\x12a\x02\xaaW`\x045\x90g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11a\x02\xaaWa\x06\xbd\x91`\x04\x01a\x06\xa2V[4a\x02\xaaW` a\b'a\r\xfc6a\r\xbeV[a\a/V[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` `\x02T`@Q\x90\x81R\xf3[```\x03\x19\x82\x01\x12a\x02\xaaWg\xff\xff\xff\xff\xff\xff\xff\xff\x91`\x045\x83\x81\x11a\x02\xaaW\x82a\x0eL\x91`\x04\x01a\x02|V[\x93\x90\x93\x92`$5\x91\x82\x11a\x02\xaaWa\x0ef\x91`\x04\x01a\x02|V[\x90\x91`D5\x90V[4a\x02\xaaWa\x0e|6a\x0e\x1fV[\x92a\x0e\x8c`A\x82\x94\x93\x94\x14a\x1f$V[\x80\x15a\x04nW\x7f\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\x00\x00\x00\x94a\x0e\xe2\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x87\x835\x16\x14a\x1f\xdeV[a\x0f\x053s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffa\x03\u00c5\x85a'\x1aV[a\x0f\x11`!\x85\x14a\x1f$V[\x83\x15a\x04nWa\x0fQa\v<\x96\x845\x16\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x14\x90\x81\x15a\r\x94WPa\x1f\xdeV[a\x0fma\x0fha\x0fa\x86\x86a!EV[T`\xff\x16\x90V[a!\x90V[a\"\xeaV[4a\x02\xaaW` `\x03\x196\x01\x12a\x02\xaaWa\x0f\x8ba2TV[a\v<`\x045a2\x94V[4a\x02\xaaW`\x00\x80`\x03\x196\x01\x12a\x10^Wa\x0f\xb0a2TV[\x80s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\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\x00\x00\x00\x00\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x81\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x91\x82\x16\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0\x82\x80\xa3\x80\xf3[\x80\xfd[4a\x02\xaaWa\x10o6a\x02\xd2V[\x91a\x10|`A\x83\x14a\x1f$V[\x81\x15a\x04nWa\x03\u0441a\x10\xd7\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00a\x10\xfc\x955\x16\x14a\x1f\xdeV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x93a\x03\xca3\x86a\x03\u00c4\x86a'\x1aV[\x91a\x11\x06\x83a\a/V[T\x15a\x11oWa\x11j\x83a\x11Qa\x11Ja\x11@\x7f\x9f\x7f\x04\xf6\x88)\x8fGN\xd4\u01c6\xab\xb2\x9e\f\xa0\x17=pQmU\xd9\xea\xc5\x15`\x9bE\xfb\u0297a\aUV[T`\x03T\x90a#FV[B\x11a#SV[Ba\x11[\x82a\aUV[U`@Q\x93\x84\x93\x16\x90\x83a#\xdeV[\x03\x90\xa1\x00[`\x84`@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\x82\x01R`)`$\x82\x01R\x7fIPTokenStaking: Delegator must h`D\x82\x01R\x7fave stake\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16\x03a\x12KWa\v<3a3\x88V[`$`@Q\x7f\x11\x8c\u06a7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R3`\x04\x82\x01R\xfd[4a\x02\xaaWa\x12\x896a\fqV[a\x12\x93\x81\x80a \xf4V[\x91\x90a\x12\xa1`A\x84\x14a\x1f$V[\x82\x15a\x04nWa\x13\x1a\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffa\x03\xc3\x7f\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\x00\x00\x00\x95a\x13\x13\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x88\x865\x16\x14a\x1f\xdeV[3\x93a'\x1aV[a\x13'` \x82\x01\x82a \xf4V[\x90a\x134`!\x83\x14a\x1f$V[\x81\x15a\x04nWa\x0faa\x13\x7f\x92\x82a\r\x83a\v<\x97a\x0fh\x955\x16\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x14\x90\x81\x15a\r\x94WPa\x1f\xdeV[a#\xfaV[4a\x02\xaaWa\x13\x9ba\x13\x956a\b\x8fV[\x90a!^V[`@Q\x90\x81\x90\x80T\x80\x84R` \x80\x94\x01\x90\x81\x92`\x00R\x84`\x00 \x90`\x00[\x86\x82\x82\x10a\x14%W\x86\x86a\x13\u03c2\x88\x03\x83a\x05\xf0V[`@Q\x92\x83\x92\x81\x84\x01\x90\x82\x85RQ\x80\x91R`@\x84\x01\x92\x91`\x00[\x82\x81\x10a\x13\xf8WPPPP\x03\x90\xf3[\x83Qs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x85R\x86\x95P\x93\x81\x01\x93\x92\x81\x01\x92`\x01\x01a\x13\xe9V[\x83T\x85R\x90\x93\x01\x92`\x01\x92\x83\x01\x92\x01a\x13\xb9V[`@`\x03\x19\x82\x01\x12a\x02\xaaWg\xff\xff\xff\xff\xff\xff\xff\xff\x91`\x045\x83\x81\x11a\x02\xaaW\x82a\x14f\x91`\x04\x01a\x02|V[\x93\x90\x93\x92`$5\x91\x82\x11a\x02\xaaWa\b\xba\x91`\x04\x01a\x02|V[a\x14\x896a\x149V[\x91a\x14\x96`A\x82\x14a\x1f$V[\x80\x15a\x04nW\x7f\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\x00\x00\x00\x93a\x14\xec\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x86\x835\x16\x14a\x1f\xdeV[a\x15\x0f3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffa\x03\u00c5\x85a'\x1aV[a\x15\x1b`!\x85\x14a\x1f$V[\x83\x15a\x04nWa\x15[a\v<\x95\x845\x16\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x14\x90\x81\x15a\r\x94WPa\x1f\xdeV[a\x15j`\xffa\r\x88\x86\x86a!EV[a$\xb0V[\x90`\x01\x82\x81\x1c\x92\x16\x80\x15a\x15\xb8W[` \x83\x10\x14a\x15\x89WV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`\"`\x04R`$`\x00\xfd[\x91`\x7f\x16\x91a\x15~V[\x80T`\x00\x93\x92a\x15\u0442a\x15oV[\x91\x82\x82R` \x93`\x01\x91`\x01\x81\x16\x90\x81`\x00\x14a\x169WP`\x01\x14a\x15\xf8W[PPPPPV[\x90\x93\x94\x95P`\x00\x92\x91\x92R\x83`\x00 \x92\x84`\x00\x94[\x83\x86\x10a\x16%WPPPP\x01\x01\x908\x80\x80\x80\x80a\x15\xf1V[\x80T\x85\x87\x01\x83\x01R\x94\x01\x93\x85\x90\x82\x01a\x16\rV[\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\x00\x16\x86\x85\x01RPPP\x90\x15\x15`\x05\x1b\x01\x01\x91P8\x80\x80\x80\x80a\x15\xf1V[\x90a\x16\x91a\x16\x8a\x92`@Q\x93\x84\x80\x92a\x15\xc2V[\x03\x83a\x05\xf0V[V[\x90\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`\x1f` \x93a\x16\u03c1Q\x80\x92\x81\x87R\x87\x80\x88\x01\x91\x01a\x06\xc0V[\x01\x16\x01\x01\x90V[4a\x02\xaaWa\x16\xeca\x16\xe76a\r\xbeV[a\a{V[`\xff\x81T\x16`@Q\x91a\x17\r\x83a\x17\x06\x81`\x01\x85\x01a\x15\xc2V[\x03\x84a\x05\xf0V[`\x03`\x02\x82\x01T\x91\x01Tc\xff\xff\xff\xff\x90a\x17;`@Q\x95\x86\x95\x15\x15\x86R`\xc0` \x87\x01R`\xc0\x86\x01\x90a\x16\x93V[\x92`@\x85\x01R\x81\x81\x16``\x85\x01R\x81\x81` \x1c\x16`\x80\x85\x01R`@\x1c\x16`\xa0\x83\x01R\x03\x90\xf3[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16`@Q\x90\x81R\xf3[a\x17\xbd6a\x149V[\x91a\x17\xca`A\x82\x14a\x1f$V[\x80\x15a\x04nW\x7f\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\x00\x00\x00\x93a\x15\x0f\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x86\x835\x16\x14a\x1f\xdeV[4a\x02\xaaW` `\x03\x196\x01\x12a\x02\xaaWa\x189a2TV[a\v<`\x045a4#\xa9f.\xfc\x9c\"\x9cj\x00T\x90g\xff\xff\xff\xff\xff\xff\xff\xff`\xff\x83`@\x1c\x16\x15\x92\x16\x80\x15\x90\x81a\x1d\xa4W[`\x01\x14\x90\x81a\x1d\x9aW[\x15\x90\x81a\x1d\x91W[Pa\x1dgWa\x1c\x86\x90\x82a\x1cl\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00`\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\x00\x00\x00\x00\x00\x00\x00\x00\x82T\x16\x17\x90UV[a\x1d\vW[`\x845\x90`d5\x90`D5\x90`$5\x90a&RV[a\x1c\x8cW\x00[a\x1c\xd8\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\x81T\x16\x90UV[`@Q`\x01\x81R\x7f\xc7\xf5\x05\xb2\xf3q\xae!u\xeeI\x13\xf4I\x9e\x1f&3\xa7\xb5\x93c!\xee\xd1\u036e\xb6\x11Q\x81\u0490\x80` \x81\x01a\x11jV[a\x1db\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00h\x01\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\x82T\x16\x17\x90UV[a\x1cqV[`\x04`@Q\x7f\xf9.\xe8\xa9\x00\x00\x00\x00\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\xfd[\x90P\x158a\x1c\x13V[0;\x15\x91Pa\x1c\vV[\x83\x91Pa\x1c\x01V[`D5\x90c\xff\xff\xff\xff\x82\x16\x82\x03a\x02\xaaWV[`d5\x90c\xff\xff\xff\xff\x82\x16\x82\x03a\x02\xaaWV[`\x845\x90c\xff\xff\xff\xff\x82\x16\x82\x03a\x02\xaaWV[`\xa0`\x03\x196\x01\x12a\x02\xaaWg\xff\xff\xff\xff\xff\xff\xff\xff`\x045\x81\x81\x11a\x02\xaaWa\x1e\x12\x906\x90`\x04\x01a\x02|V[\x90\x91`$5\x90\x81\x11a\x02\xaaWa\x1e,\x906\x90`\x04\x01a\x02|V[\x91\x90\x92a\x1e7a\x1d\xacV[\x90a\x1e@a\x1d\xbfV[\x92a\x1eIa\x1d\xd2V[\x94a\x1eV`A\x83\x14a\x1f$V[\x81\x15a\x04nWa\t\u0596a\x1e\u0751a\x1e\xb2\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00\x875\x16\x14a\x1f\xdeV[a\x1e\xd53s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffa\x03\u00c7\x89a'\x1aV[a\x03\xcaa(\xf6V[\x91a,\u5bb5\x00\x00\x00\x00\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\xfd[\x15a)WWV[`\x84`@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\x82\x01R`$\x80\x82\x01R\x7fIPTokenStaking: Stake amount too`D\x82\x01R\x7f low\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x91a*\x12\x91\x83T\x90\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\x90`\x03\x1b\x92\x83\x1b\x92\x1b\x19\x16\x17\x90V[\x90UV[\x81\x81\x10a*!WPPV[`\x00\x81U`\x01\x01a*\x16V[\x91\x90`\x1f\x81\x11a*`$\x82\x01R\x7fIPTokenStaking: newWithdrawalAdd`D\x82\x01R\x7fressChangeInterval cannot be 0\x00\x00`d\x82\x01R\xfd[\x80T\x82\x10\x15a\x04nW`\x00R` `\x00 \x01\x90`\x00\x90V[`\x00\x82\x81R`\x01\x82\x01` R`@\x90 Ta7zW\x80T\x90h\x01\x00\x00\x00\x00\x00\x00\x00\x00\x82\x10\x15a\x05\xebW\x82a7ca7.\x84`\x01\x80\x96\x01\x85U\x84a6\xd8V[\x81\x93\x91T\x90\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\x90`\x03\x1b\x92\x83\x1b\x92\x1b\x19\x16\x17\x90V[\x90U\x80T\x92`\x00R\x01` R`@`\x00 U`\x01\x90V[PP`\x00\x90V[\x80T\x90\x81\x15a7\xc9W\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\x80\x92\x01\x91a7\xb9\x83\x83a6\xd8V[\x90\x91\x82T\x91`\x03\x1b\x1b\x19\x16\x90UUV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`1`\x04R`$`\x00\xfd[`\x01\x81\x01\x91\x80`\x00R\x82` R`@`\x00 T\x92\x83\x15\x15`\x00\x14a8\xc2W\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\x92\x83\x85\x01\x90\x85\x82\x11a#AW\x80T\x94\x85\x01\x94\x85\x11a#AW`\x00\x95\x85\x83a8y\x97a8j\x95\x03a8\x7fW[PPPa7\x81V[\x90`\x00R` R`@`\x00 \x90V[U`\x01\x90V[a8\xa9a8\xa3\x91a8\x93a8\xb9\x94\x87a6\xd8V[\x90T\x90`\x03\x1b\x1c\x92\x83\x91\x87a6\xd8V[\x90a)\xdaV[\x85\x90`\x00R` R`@`\x00 \x90V[U8\x80\x80a8bV[PPPP`\x00\x90V[=\x15a8\xf6W=\x90a8\u0702a\x061V[\x91a8\xea`@Q\x93\x84a\x05\xf0V[\x82R=`\x00` \x84\x01>V[``\x90V[`\x00\x80\x80\x80\x933Z\xf1a9\fa8\xcbV[P\x15a9\x14WV[`\x84`@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\x82\x01R`*`$\x82\x01R\x7fIPTokenStaking: Failed to refund`D\x82\x01R\x7f remainder\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x90\x81;\x15a:kWs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81\x7f\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\x00\x00\x00\x00\x82T\x16\x17\x90U\x7f\xbc|\xd7Z \xee'\xfd\x9a\u07ba\xb3 A\xf7U!M\xbck\xff\xa9\f\xc0\"[9\xda.\\-;`\x00\x80\xa2\x80Q\x15a:8Wa:5\x91a;\vV[PV[PP4a:AWV[`\x04`@Q\x7f\xb3\x98\x97\x9f\x00\x00\x00\x00\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\xfd[`$\x82s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[`\xff\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00T`@\x1c\x16\x15a:\xe1WV[`\x04`@Q\x7f\xd7\xe6\xbc\xf8\x00\x00\x00\x00\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\xfd[`\x00\x80a\x06\xbd\x93` \x81Q\x91\x01\x84Z\xf4a;#a8\xcbV[\x91\x90a;cWP\x80Q\x15a;9W\x80Q\x90` \x01\xfd[`\x04`@Q\x7f\x14%\xeaB\x00\x00\x00\x00\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\xfd[\x81Q\x15\x80a;\xbbW[a;tWP\x90V[`$\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7f\x99\x96\xb3\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[P\x80;\x15a;lV\xfe\xa2dipfsX\"\x12 \xde\x1c\xe9\xf0W\x90\v+\xfc?\x0f\xc2\xc4\x17|\x81:\x83\xb4a\x95\xb7\xe5\xe4v\xab\xf2\x1a\x98}\xc0\x88dsolcC\x00\b\x17\x003\xf9\n\\\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\x00\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\r\u0db3\xa7d\x00\x00\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\x01\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\r\u0db3\xa7d\x00\x00\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\x02\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\r\u0db3\xa7d\x00\x00\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\x03\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<\xf8B\xa0\x06\xf6\xe1S\xb6Q\xc4G\xb7\xccc\xa6\xfaF\u02cfu\xfd\x0f\xbf\xa7\x8fB\"\x81]\x89\x02\xa6\xe4\xd59\xf6\x87\x1f\x9e\x89FE\x7f\xd8\xe1\x1e\xe5\xdb5\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\x01\xf8B\xa0f>u\xfd\x0f\xbf\xa7\x8fB\"\x81]\x89\x02\xa6\xe4\xd59\xf6\x87\x1f\x9e\x89FE\x7f\xd8\xe1\x1e\xe5\xdb6\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\x00U\xf8B\xa0f>u\xfd\x0f\xbf\xa7\x8fB\"\x81]\x89\x02\xa6\xe4\xd59\xf6\x87\x1f\x9e\x89FE\x7f\xd8\xe1\x1e\xe5\xdb7\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0065\u026d\xc5\u07a0\x00\x00\xf8B\xa0f>u\xfd\x0f\xbf\xa7\x8fB\"\x81]\x89\x02\xa6\xe4\xd59\xf6\x87\x1f\x9e\x89FE\x7f\xd8\xe1\x1e\xe5\xdb8\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\x00\xf8B\xa0j\xee\xf8.\x12;\x11Q\x1e\x06\x14\xfb\x10\xd6\u0513\xa3\xd09\xbe L\x8e:h\x8c]\x8b\xf1G\x99:\xa00x0617A814633E9B6F3847CC97C09AB9\xf8B\xa0j\xee\xf8.\x12;\x11Q\x1e\x06\x14\xfb\x10\xd6\u0513\xa3\xd09\xbe L\x8e:h\x8c]\x8b\xf1G\x99;\xa0EE14509F20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0k\xd2&\x19\a\x16|\xd0\xfd\x84!\x9a\x0eJ3\xa5\x06\xf8\x02\x90\x98\x97\xec\x7f\x8d\u0495\x91\xc7}\x13\x9d\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0065\u026d\xc5\u07a0\x00\x00\xf8B\xa0\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf8B\xa0\x98f\xbde\v\xf4\u01bb\u030b&\xa6#\xcb\xf2 \xe3Uh\x9eC\xc310d\xdf\x05q\x8cR\ufae00x3FCA8694392F5B7BCF4E81BEA70204\xf8B\xa0\x98f\xbde\v\xf4\u01bb\u030b&\xa6#\xcb\xf2 \xe3Uh\x9eC\xc310d\xdf\x05q\x8cR\ufb20C50223C385\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0\x9bw\x9b\x17B-\r\xf9\"#\x01\x8b2\xb4\xd1\xfaF\xe0qr=h\x17\xe2Hm\x00;\xec\xc5_\x00\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\x01\xf8B\xa0\xad\xcd\x03\x19\x05-\x8d\x10\x8a\xfb\x95\x9b%\xa9\x14\xd2\x035+xt\xd5\b\xa3\xe73=w\x9a\x80\xd4\x1d\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\x01\xf8B\xa0\xad\xcd\x03\x19\x05-\x8d\x10\x8a\xfb\x95\x9b%\xa9\x14\xd2\x035+xt\xd5\b\xa3\xe73=w\x9a\x80\xd4\x1e\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\x00U\xf8B\xa0\xad\xcd\x03\x19\x05-\x8d\x10\x8a\xfb\x95\x9b%\xa9\x14\xd2\x035+xt\xd5\b\xa3\xe73=w\x9a\x80\xd4\x1f\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0065\u026d\xc5\u07a0\x00\x00\xf8B\xa0\xad\xcd\x03\x19\x05-\x8d\x10\x8a\xfb\x95\x9b%\xa9\x14\xd2\x035+xt\xd5\b\xa3\xe73=w\x9a\x80\xd4 \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\x00\xf8B\xa0\xcfo<^c`\xd5!&\xdd\x1e\xf39SD\xbb\xf3\xadWjg\x95F\x11D\xaa\xdf\xec%)\xe7\b\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\x01\xf8B\xa0\xcfo<^c`\xd5!&\xdd\x1e\xf39SD\xbb\xf3\xadWjg\x95F\x11D\xaa\xdf\xec%)\xe7\t\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\x00U\xf8B\xa0\xcfo<^c`\xd5!&\xdd\x1e\xf39SD\xbb\xf3\xadWjg\x95F\x11D\xaa\xdf\xec%)\xe7\n\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0065\u026d\xc5\u07a0\x00\x00\xf8B\xa0\xcfo<^c`\xd5!&\xdd\x1e\xf39SD\xbb\xf3\xadWjg\x95F\x11D\xaa\xdf\xec%)\xe7\v\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\x00\xf8B\xa0\xd3\xe0\xb1%\xdeU\b\t\xf8\x0f\xbb\x8d\xb7c8[\xf7\xb9(\xd8\xf9\x93\xeb\ub8e0'\x1c\xa1f\x93!\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0065\u026d\xc5\u07a0\x00\x00\xf8B\xa0\xd4L\xf6\u03ae`4o\xedfPs\r\xa2\x12%\xa5\xca/\x1a\xc4\xf8~\x81\\R\x8dW\xf2\xb8\xf5J\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0065\u026d\xc5\u07a0\x00\x00\xf8B\xa0\xdew\xe6\x10E\xfa\u0130/\xf97\xe7US\xd2\x047($\xd7a\xa6\x8d\x87\xdf\\\xf4\xf8O7\xc9\x02\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0065\u026d\xc5\u07a0\x00\x00\xf8B\xa0\xfb\b\xc2'z\x03\f\xbd\xa6\xc2\x7fi\x99t\x91Ze\xad\xfa\n\x95\xef\r\xd4A\xc2\xfd\xe5\xc0n\xeb\"\xa00xEAEDE127412F3122AB3D905B579659\xf8B\xa0\xfb\b\xc2'z\x03\f\xbd\xa6\xc2\x7fi\x99t\x91Ze\xad\xfa\n\x95\xef\r\xd4A\xc2\xfd\xe5\xc0n\xeb#\xa01B196AB703\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe1\x94h<:\xc1^N\x02N\x15\tP[\x9a\x8f?{\x1a\x1c\xff\x1e\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\u151a:^\xdd\xdf\xee\x1e:\x1b\xbe\xf6\xfd\xf0\x85\v\x10\u0517\x94\x05\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xf9\x10s\x94\xa5\x13\xe6\xe4\xb8\xf2\xa9#\u0643\x04\xec\x87\xf6\x00\x00\x00\x00\x00\x00\x01\xf9\x10Z\x80\xb9\x0f\xcc`@`\x80\x81R`\x04\x90\x816\x10\x15a\x00\x15W`\x00\x80\xfd[`\x00\x91\x825`\xe0\x1c\x91\x82cO\x1e\xf2\x86\x14a\b\xfcW\x82cR\u0450-\x14a\b@W\x82cqP\x18\xa6\x14a\a[W\x82cy\xbaP\x97\x14a\x06\xafW\x82c\x8d\xa5\xcb[\x14a\x06=W\x82c\xad<\xb1\xcc\x14a\x051W\x82c\xc4\xd6m\xe8\x14a\x02\xd8W\x82c\xe3\f9x\x14a\x02bW\x82c\xef\x17n\x0e\x14a\x01\x80WPPc\xf2\xfd\xe3\x8b\x14a\x00\x92W`\x00\x80\xfd[4a\x01}W` \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\xfc6\x01\x12a\x01}Wa\x00\xc9a\f:V[a\x00\xd1a\ryV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x91\x16\x90\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x82\x7f\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\x00\x00\x00\x00\x82T\x16\x17\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16\x7f8\xd1k\x8c\xac\"\u065f\xc7\xc1$\xb9\xcd\r\xe2\xd3\xfa\x1f\xae\xf4 \xbf\xe7\x91\xd8\xc3b\xd7e\xe2'\x00\x83\x80\xa3\x80\xf3[\x80\xfd[\x90\x91P4a\x02^W``\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\xfc6\x01\x12a\x02^Wg\xff\xff\xff\xff\xff\xff\xff\xff\x91\x805\x83\x81\x11a\x02ZWa\x01\u04906\x90\x83\x01a\r\fV[\x91\x90\x92`$5\x90\x81`\a\v\x80\x92\x03a\x02VW`D5\x95\x86\x11a\x02VWa\x02\x1fa\x02P\x93\x7f\x11'I\xe7\x9b \x98\xb5\x8e\xab6\xc2\x1f\x12;(\x83\xc3\ucef4\xf4\x16#\xa7D\xfam\x9b>7\u01976\x91\x01a\r\fV[\x91a\x02(a\ryV[a\x02>\x81Q\x97\x88\x97``\x89R``\x89\x01\x91a\r:V[\x93` \x87\x01R\x85\x84\x03\x90\x86\x01Ra\r:V[\x03\x90\xa1\x80\xf3[\x86\x80\xfd[\x84\x80\xfd[\x82\x80\xfd[\x83\x904a\x02\xd4W\x81\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\xfc6\x01\x12a\x02\xd4W` \x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16\x90Q\x90\x81R\xf3[P\x80\xfd[\x91P4a\x02^W` \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\xfc6\x01\x12a\x02^Wa\x03\x11a\f:V[\x90\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00\x91\x82T\x91`\xff\x83\x86\x1c\x16\x15\x92g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16\x80\x15\x90\x81a\x05)W[`\x01\x14\x90\x81a\x05\x1fW[\x15\x90\x81a\x05\x16W[Pa\x04\xeeW\x83`\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\x00\x00\x00\x00\x00\x00\x00\x00\x83\x16\x17\x86Ua\x04\xb9W[Ps\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x15a\x046WPa\x03\u05d0a\x03\xc2a\x0e\x9dV[a\x03\xcaa\x0e\x9dV[a\x03\xd2a\x0e\x9dV[a\r\xe9V[a\x03\xdfW\x82\x80\xf3[\x7f\xc7\xf5\x05\xb2\xf3q\xae!u\xeeI\x13\xf4I\x9e\x1f&3\xa7\xb5\x93c!\xee\xd1\u036e\xb6\x11Q\x81\u0491\x81\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff` \x93T\x16\x90UQ`\x01\x81R\xa18\x80\x82\x80\xf3[`\x84\x90` \x86Q\x91\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\x83R\x82\x01R`7`$\x82\x01R\x7fUpgradeEntrypoint: accessManager`D\x82\x01R\x7f cannot be zero address\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x7f\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\x00\x16h\x01\x00\x00\x00\x00\x00\x00\x00\x01\x17\x84U8a\x03\x98V[P\x84Q\x7f\xf9.\xe8\xa9\x00\x00\x00\x00\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\xfd[\x90P\x158a\x03eV[0;\x15\x91Pa\x03]V[\x85\x91Pa\x03SV[\x91P4a\x02^W\x82\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\xfc6\x01\x12a\x02^W\x81Q\x90\x82\x82\x01\x90\x82\x82\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11\x17a\x06\x11WP\x82R`\x05\x81R` \x90\x7f5.0.0\x00\x00\x00\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\x01R\x82Q\x93\x84\x92` \x84R\x82Q\x92\x83` \x86\x01R\x82[\x84\x81\x10a\x05\xfbWPPP\x82\x82\x01\x84\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\x81\x01\x03\x01\x90\xf3[\x81\x81\x01\x83\x01Q\x88\x82\x01\x88\x01R\x87\x95P\x82\x01a\x05\xbfV[\x84`A`$\x92\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83RR\xfd[\x83\x904a\x02\xd4W\x81\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\xfc6\x01\x12a\x02\xd4W` \x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16\x90Q\x90\x81R\xf3[\x90\x91P4a\x02^W\x82\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\xfc6\x01\x12a\x02^W3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16\x03a\a+W\x82a\a(3a\r\xe9V[\x80\xf3[`$\x92PQ\x90\x7f\x11\x8c\u06a7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x82R3\x90\x82\x01R\xfd[\x834a\x01}W\x80\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\xfc6\x01\x12a\x01}Wa\a\x92a\ryV[\x80s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\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\x00\x00\x00\x00\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x81\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x91\x82\x16\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0\x82\x80\xa3\x80\xf3[\x834a\x01}W\x80\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\xfc6\x01\x12a\x01}WPs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01e\x87\x8aYL\xa2U3\x8a\u07e4\u0504I\xf6\x92B\xeb\x8f\x160\x03a\b\xd6W` \x90Q\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81R\xf3[Q\x7f\xe0|\x8d\xba\x00\x00\x00\x00\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\xfd[\x80\x91\x92P\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\xfc6\x01\x12a\x02^Wa\t0a\f:V[\x90`$\x93\x845g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x02\xd4W6`#\x82\x01\x12\x15a\x02\xd4W\x80\x85\x015a\t]\x81a\f\xd2V[\x94a\tj\x85Q\x96\x87a\fbV[\x81\x86R` \x91\x82\x87\x01\x936\x8a\x83\x83\x01\x01\x11a\f6W\x81\x86\x92\x8b\x86\x93\x01\x877\x88\x01\x01Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01e\x87\x8aYL\xa2U3\x8a\u07e4\u0504I\xf6\x92B\xeb\x8f\x16\x800\x14\x90\x81\x15a\f\bW[Pa\v\xe0Wa\t\xdca\ryV[\x81\x16\x95\x85Q\x7fR\u0450-\x00\x00\x00\x00\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\x83\x81\x8a\x81\x8bZ\xfa\x86\x91\x81a\v\xb1W[Pa\nHWPPPPPPQ\x91\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x82\x01R\xfd[\x90\x88\x88\x88\x94\x93\x8c\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x91\x82\x81\x03a\v\x84WP\x85;\x15a\vWWP\x80T\x7f\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\x00\x00\x00\x00\x16\x82\x17\x90U\x84Q\x88\x93\x92\x91\x7f\xbc|\xd7Z \xee'\xfd\x9a\u07ba\xb3 A\xf7U!M\xbck\xff\xa9\f\xc0\"[9\xda.\\-;\x85\x80\xa2\x82Q\x15a\v WPPa\v\x12\x95\x82\x91Q\x90\x84Z\xf4\x91=\x15a\v\x16W=a\v\x04a\n\xfb\x82a\f\xd2V[\x92Q\x92\x83a\fbV[\x81R\x85\x81\x94=\x92\x01>a\x0e\xf6V[P\x80\xf3[P``\x92Pa\x0e\xf6V[\x95P\x95PPPPP4a\v2WPP\x80\xf3[\x7f\xb3\x98\x97\x9f\x00\x00\x00\x00\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\xfd[\x83\x83\x88Q\x91\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x82\x01R\xfd[\x84\x90\x88Q\x91\x7f\xaa\x1dI\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x82\x01R\xfd[\x90\x91P\x84\x81\x81=\x83\x11a\v\xd9W[a\v\u0241\x83a\fbV[\x81\x01\x03\x12a\x02VWQ\x908a\n\x13V[P=a\v\xbfV[\x87\x86Q\x7f\xe0|\x8d\xba\x00\x00\x00\x00\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\xfd[\x90P\x81\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16\x14\x158a\t\xcfV[\x85\x80\xfd[`\x045\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x82\x03a\f]WV[`\x00\x80\xfd[\x90`\x1f\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\x91\x01\x16\x81\x01\x90\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\f\xa3W`@RV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`A`\x04R`$`\x00\xfd[g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\f\xa3W`\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` \x01\x90V[\x91\x81`\x1f\x84\x01\x12\x15a\f]W\x825\x91g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11a\f]W` \x83\x81\x86\x01\x95\x01\x01\x11a\f]WV[`\x1f\x82` \x94\x93\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\x93\x81\x86R\x86\x86\x017`\x00\x85\x82\x86\x01\x01R\x01\x16\x01\x01\x90V[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x163\x03a\r\xb9WV[`$`@Q\x7f\x11\x8c\u06a7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R3`\x04\x82\x01R\xfd[\x7f\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\x00\x00\x00\x00\x90\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x82\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x93\x16\x80\x94\x83\x16\x17\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0`\x00\x80\xa3V[`\xff\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00T`@\x1c\x16\x15a\x0e\xccWV[`\x04`@Q\x7f\xd7\xe6\xbc\xf8\x00\x00\x00\x00\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\xfd[\x90a\x0f5WP\x80Q\x15a\x0f\vW\x80Q\x90` \x01\xfd[`\x04`@Q\x7f\x14%\xeaB\x00\x00\x00\x00\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\xfd[\x81Q\x15\x80a\x0f\x8dW[a\x0fFWP\x90V[`$\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7f\x99\x96\xb3\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[P\x80;\x15a\x0f>V\xfe\xa2dipfsX\"\x12 \u07e4\xd0\x04/\xa8\xdf\xf3\xa6\bh\xdaFP\x9b\t\xe5T:\xc3w2\xf4\xc4]]\x82\xc7ye\xc8~dsolcC\x00\b\x17\x003\xf8\x88\xf8B\xa0#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\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\x00\xf8B\xa0\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\u1528\x12\xfb\u90e1\x85ocU\xa3\x03)\x94\xb3W\xa0\u04102\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\u15355\v|\xae\x94\u00bfk+V\xefj\x06\xcc\x11S\x90\x00\x00\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\u1536(\x8eW\xbft\x06\xb3Z\xb4\xf7\x0f\xd1\x13^\x90q\a\u318bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\u153d9\xfa\xe8s\xf3\x01\xb5>\x14\xd3e81\x18\xcdJ\"\"\"\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xf9\x016\x94\xcc\xcc\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\xf9\x01\x1d\x80\xb8\x90`\x80`@Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16`\x00\x80\x80\x926\x82\x807\x816\x91Z\xf4=\x82\x80>\x15`VW=\x90\xf3[=\x90\xfd\xfe\xa2dipfsX\"\x12 \x1c\x13\xa8\xe0u\xfc\uf3f1\xcb\xc5*\xa5\xf7\x98\xf9\x17\x8f\xe4Et]\x01\u02f3\x1dJA dsolcC\x00\b\x17\x003\xf8\x88\xf8B\xa06\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00_\xbd\xb21Vx\xaf\xec\xb3g\xf02\xd9?\x00\x00\x00\x00\x00\x00\xf8B\xa0\xb51'hJV\x8b1s\xae\x13\xb9\xf8\xa6\x01n$>c\xb6\xe8\xee\x11x\u05a7\x17\x85\v]a\x03\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf9\x016\x94\xcc\xcc\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01\xf9\x01\x1d\x80\xb8\x90`\x80`@Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16`\x00\x80\x80\x926\x82\x807\x816\x91Z\xf4=\x82\x80>\x15`VW=\x90\xf3[=\x90\xfd\xfe\xa2dipfsX\"\x12 \x1c\x13\xa8\xe0u\xfc\uf3f1\xcb\xc5*\xa5\xf7\x98\xf9\x17\x8f\xe4Et]\x01\u02f3\x1dJA dsolcC\x00\b\x17\x003\xf8\x88\xf8B\xa06\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdcd\xa1@\xaa>\x98\x11\x00\xa9\xbe\xcaNh\x00\x00\x00\x00\x00\x00\xf8B\xa0\xb51'hJV\x8b1s\xae\x13\xb9\xf8\xa6\x01n$>c\xb6\xe8\xee\x11x\u05a7\x17\x85\v]a\x03\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf9\x016\x94\xcc\xcc\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x01\xf9\x01\x1d\x80\xb8\x90`\x80`@Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16`\x00\x80\x80\x926\x82\x807\x816\x91Z\xf4=\x82\x80>\x15`VW=\x90\xf3[=\x90\xfd\xfe\xa2dipfsX\"\x12 \x1c\x13\xa8\xe0u\xfc\uf3f1\xcb\xc5*\xa5\xf7\x98\xf9\x17\x8f\xe4Et]\x01\u02f3\x1dJA dsolcC\x00\b\x17\x003\xf8\x88\xf8B\xa06\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa5\x13\xe6\xe4\xb8\xf2\xa9#\u0643\x04\xec\x87\xf6\x00\x00\x00\x00\x00\x00\xf8B\xa0\xb51'hJV\x8b1s\xae\x13\xb9\xf8\xa6\x01n$>c\xb6\xe8\xee\x11x\u05a7\x17\x85\v]a\x03\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf9\x19\a\x94\xdcd\xa1@\xaa>\x98\x11\x00\xa9\xbe\xcaNh\x00\x00\x00\x00\x00\x00\x01\xf9\x18\ue039\x18\x1c`\x80`@R`\x046\x10\x15a\x00\x12W`\x00\x80\xfd[`\x005`\xe0\x1c\x80c\x04\xffS\xed\x14a\x00\xf7W\x80c\f\x86?w\x14a\x00\xf2W\x80c(\x01\xf1\xec\x14a\x00\xedW\x80c@\xed\xa1J\x14a\x00\xe8W\x80cO\x1e\xf2\x86\x14a\x00\xe3W\x80cR\u0450-\x14a\x00\xdeW\x80cqP\x18\xa6\x14a\x00\xd9W\x80cy\xbaP\x97\x14a\x00\xd4W\x80c\x8d\xa5\xcb[\x14a\x00\xcfW\x80c\xad<\xb1\xcc\x14a\x00\xcaW\x80c\xcdm\u0187\x14a\x00\xc5W\x80c\xe3\f9x\x14a\x00\xc0W\x80c\xe4\xdf\xcc\xd8\x14a\x00\xbbWc\xf2\xfd\xe3\x8b\x14a\x00\xb6W`\x00\x80\xfd[a\v\xdbV[a\n\x85V[a\n2V[a\b?V[a\a\xcaV[a\a\x11V[a\x06\x89V[a\x05\xbeV[a\x05FV[a\x03:V[a\x02\x01V[a\x01\x99V[a\x01MV[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW` `@Qs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe7\xf1r^w4\xce(\x8f\x83g\xe1\xbb\x14>\x90\xbb?\x05\x12\x16\x81R\xf3[`\x00\x80\xfd[4a\x01HW` `\x03\x196\x01\x12a\x01HW\x7f\xea\xc8\x1d\xe2\xf2\x01b\xb0T\f\xa5\xd3\xf48\x96\xaf\x15\xb4q\xa5W)\xff\f\x00\x0ea\x1d\x8b'#c` `\x045a\x01\x8ca\x0f\xe7V[\x80`\x00U`@Q\x90\x81R\xa1\x00[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW` `\x00T`@Q\x90\x81R\xf3[\x90` `\x03\x19\x83\x01\x12a\x01HW`\x045g\xff\xff\xff\xff\xff\xff\xff\xff\x92\x83\x82\x11a\x01HW\x80`#\x83\x01\x12\x15a\x01HW\x81`\x04\x015\x93\x84\x11a\x01HW`$\x84\x83\x01\x01\x11a\x01HW`$\x01\x91\x90V[a\x02/a\x02*a\x02\x106a\x01\xb7V[a\x02#a\x02\x1e6\x83\x85a\x03\x03V[a\x11\x8dV[6\x91a\x03\x03V[a\x12\xf0V[\x00[`\x045\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x82\x03a\x01HWV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`A`\x04R`$`\x00\xfd[\x90`\x1f\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\x91\x01\x16\x81\x01\x90\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\x02\xc4W`@RV[a\x02TV[g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x02\xc4W`\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` \x01\x90V[\x92\x91\x92a\x03\x0f\x82a\x02\xc9V[\x91a\x03\x1d`@Q\x93\x84a\x02\x83V[\x82\x94\x81\x84R\x81\x83\x01\x11a\x01HW\x82\x81` \x93\x84`\x00\x96\x017\x01\x01RV[`@`\x03\x196\x01\x12a\x01HWa\x03Na\x021V[`$5g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x01HW6`#\x82\x01\x12\x15a\x01HWa\x03\x7f\x906\x90`$\x81`\x04\x015\x91\x01a\x03\x03V[\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x91\x82\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf~\u04ec\xcaZF~\x9epLp>\x8d\x87\xf64\xfb\x0f\xc9\x16\x800\x14\x90\x81\x15a\x05\x18W[Pa\x04\xeeW` `\x04\x93a\x03\xd6a\x0f\xe7V[`@Q\x94\x85\x80\x92\x7fR\u0450-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x82R\x86\x16Z\xfa`\x00\x93\x81a\x04\xbdW[Pa\x04YW`@Q\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x83\x16`\x04\x82\x01R`$\x90\xfd[\x90\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x83\x03a\x04\x8bWa\x02/\x92Pa\x15\x91V[`@Q\x7f\xaa\x1dI\xa4\x00\x00\x00\x00\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\x81\x01\x84\x90R`$\x90\xfd[a\x04\xe0\x91\x94P` =` \x11a\x04\xe7W[a\x04\u0601\x83a\x02\x83V[\x81\x01\x90a\x13\xd9V[\x928a\x04\rV[P=a\x04\xceV[`\x04`@Q\x7f\xe0|\x8d\xba\x00\x00\x00\x00\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\xfd[\x90P\x83\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16\x14\x158a\x03\xc4V[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HWs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf~\u04ec\xcaZF~\x9epLp>\x8d\x87\xf64\xfb\x0f\xc9\x160\x03a\x04\xeeW` `@Q\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81R\xf3[4a\x01HW`\x00\x80`\x03\x196\x01\x12a\x06\x86Wa\x05\xd8a\x0f\xe7V[\x80s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\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\x00\x00\x00\x00\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x81\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x91\x82\x16\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0\x82\x80\xa3\x80\xf3[\x80\xfd[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16\x03a\x06\xe1Wa\x02/3a\x13\xe8V[`$`@Q\x7f\x11\x8c\u06a7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R3`\x04\x82\x01R\xfd[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW` s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16`@Q\x90\x81R\xf3[`\x00[\x83\x81\x10a\awWPP`\x00\x91\x01RV[\x81\x81\x01Q\x83\x82\x01R` \x01a\agV[\x90\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`\x1f` \x93a\a\u00c1Q\x80\x92\x81\x87R\x87\x80\x88\x01\x91\x01a\adV[\x01\x16\x01\x01\x90V[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW`@Q`@\x81\x01\x90\x80\x82\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11\x17a\x02\xc4Wa\b;\x91`@R`\x05\x81R\x7f5.0.0\x00\x00\x00\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\x01R`@Q\x91\x82\x91` \x83R` \x83\x01\x90a\a\x87V[\x03\x90\xf3[4a\x01HW`@`\x03\x196\x01\x12a\x01HWa\bXa\x021V[\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00T\x90g\xff\xff\xff\xff\xff\xff\xff\xff`\xff\x83`@\x1c\x16\x15\x92\x16\x80\x15\x90\x81a\n*W[`\x01\x14\x90\x81a\n W[\x15\x90\x81a\n\x17W[Pa\t\xedWa\t\x10\x90\x82a\t\x02\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00`\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\x00\x00\x00\x00\x00\x00\x00\x00\x82T\x16\x17\x90UV[a\t\x91W[`$5\x90a\f\xa8V[a\t\x16W\x00[a\tb\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\x81T\x16\x90UV[`@Q`\x01\x81R\x7f\xc7\xf5\x05\xb2\xf3q\xae!u\xeeI\x13\xf4I\x9e\x1f&3\xa7\xb5\x93c!\xee\xd1\u036e\xb6\x11Q\x81\u0490` \x90\xa1\x00[a\t\xe8\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00h\x01\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\x82T\x16\x17\x90UV[a\t\aV[`\x04`@Q\x7f\xf9.\xe8\xa9\x00\x00\x00\x00\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\xfd[\x90P\x158a\b\xa9V[0;\x15\x91Pa\b\xa1V[\x83\x91Pa\b\x97V[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW` s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16`@Q\x90\x81R\xf3[a\n\x8e6a\x01\xb7V[a\n\x9a`A\x82\x14a\r\xd3V[\x80\x15a\v\xd6Wa\n\xee\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00\x845\x16\x14a\x0e\x8dV[\x80`\x01\x11a\x01HWa\v&6\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\x83\x01`\x01\x85\x01a\x03\x03V[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81Q` 3\x93\x01 \x16\x03a\vRWa\x02/\x91a\x0f\x18V[`\x84`@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\x82\x01R`/`$\x82\x01R\x7fIPTokenSlashing: Invalid pubkey `D\x82\x01R\x7fderived address\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[a\x0e^V[4a\x01HW` `\x03\x196\x01\x12a\x01HWa\v\xf4a\x021V[a\v\xfca\x0f\xe7V[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x91\x16\x90\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x82\x7f\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\x00\x00\x00\x00\x82T\x16\x17\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16\x7f8\xd1k\x8c\xac\"\u065f\xc7\xc1$\xb9\xcd\r\xe2\xd3\xfa\x1f\xae\xf4 \xbf\xe7\x91\xd8\xc3b\xd7e\xe2'\x00`\x00\x80\xa3\x00[a\f\xb0a\x16\xabV[a\f\xb8a\x16\xabV[a\f\xc0a\x16\xabV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16\x15a\r\xa2Wa\f\xe5\x90a\x13\xe8V[\x80\x15a\r\x1eW`\x00\x81\x90U`@Q\x90\x81R\x7f\xea\xc8\x1d\xe2\xf2\x01b\xb0T\f\xa5\xd3\xf48\x96\xaf\x15\xb4q\xa5W)\xff\f\x00\x0ea\x1d\x8b'#c\x90` \x90\xa1V[`\x84`@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\x82\x01R`#`$\x82\x01R\x7fIPTokenSlashing: Invalid unjail `D\x82\x01R\x7ffee\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[`$`@Q\x7f\x1eO\xbd\xf7\x00\x00\x00\x00\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`\x00`\x04\x82\x01R\xfd[\x15a\r\xdaWV[`\x84`@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\x82\x01R`&`$\x82\x01R\x7fIPTokenSlashing: Invalid pubkey `D\x82\x01R\x7flength\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`2`\x04R`$`\x00\xfd[\x15a\x0e\x94WV[`\x84`@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\x82\x01R`&`$\x82\x01R\x7fIPTokenSlashing: Invalid pubkey `D\x82\x01R\x7fprefix\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[a\x0f#\x916\x91a\x03\x03V[\x90a\x0f1`A\x83Q\x14a\x14\x9cV[`\xffa\x0fH\x81`A`!\x86\x01Q\x95\x01Q\x16`\x01\x16\x90V[\x16a\x0f\xc1W\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[a\x0fwa\x15'V[\x90`\x00\x1aa\x0f\x84\x82a\x10'V[S`\x00[` \x81\x10a\x0f\xa2WPa\x0f\xa0\x91\x92Pa\x02*\x81a\x11\x8dV[V[\x80\x84`\x01\x92\x1aa\x0f\xbaa\x0f\xb4\x83a\x15TV[\x85a\x104V[S\x01a\x0f\x88V[\x7f\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a\x0foV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x163\x03a\x06\xe1WV[\x80Q\x15a\v\xd6W` \x01\x90V[\x90\x81Q\x81\x10\x15a\v\xd6W\x01` \x01\x90V[Q\x90c\xff\xff\xff\xff\x82\x16\x82\x03a\x01HWV[\x91\x90\x91`\xc0\x81\x84\x03\x12a\x01HW\x80Q\x80\x15\x15\x81\x03a\x01HW\x92` \x82\x01Qg\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x01HW\x82\x01\x81`\x1f\x82\x01\x12\x15a\x01HW\x80Qa\x10\x9b\x81a\x02\xc9V[\x92a\x10\xa9`@Q\x94\x85a\x02\x83V[\x81\x84R` \x82\x84\x01\x01\x11a\x01HWa\x10\u01d1` \x80\x85\x01\x91\x01a\adV[\x91`@\x82\x01Q\x91a\x10\xda``\x82\x01a\x10EV[\x91a\x10\xf3`\xa0a\x10\xec`\x80\x85\x01a\x10EV[\x93\x01a\x10EV[\x90V[`@Q=`\x00\x82>=\x90\xfd[\x15a\x11\tWV[`\x84`@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\x82\x01R`)`$\x82\x01R\x7fIPTokenSlashing: Validator does `D\x82\x01R\x7fnot exist\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[`\x00a\x12;\x91a\x11\xa0`!\x82Q\x14a\r\xd3V[a\x12\x00\x7f\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\x00\x00\x00\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81a\x11\xef\x85a\x10'V[Q\x16\x14\x90\x81\x15a\x12\xbbW[Pa\x0e\x8dV[`@Q\x80\x93\x81\x92\x7f\x8d>\x1eA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R` `\x04\x84\x01R`$\x83\x01\x90a\a\x87V[\x03\x81s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe7\xf1r^w4\xce(\x8f\x83g\xe1\xbb\x14>\x90\xbb?\x05\x12\x16Z\xfa\x80\x15a\x12\xb6Wa\x0f\xa0\x91`\x00\x91a\x12\x8eW[Pa\x11\x02V[a\x12\xab\x91P=\x80`\x00\x83>a\x12\xa3\x81\x83a\x02\x83V[\x81\x01\x90a\x10VV[PPPPP8a\x12\x88V[a\x10\xf6V[\x7f\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x91Pa\x12\xe7\x84a\x10'V[Q\x16\x148a\x11\xfaV[`\x00\x80T4\x03a\x13UW\x80\x804\x15a\x13LW[\x81\x80\x91\x814\x91\xf1\x15a\x12\xb6W\x7fJ\x90\xea2R~\xca\xcc\x0fK2\xb3\x1f\x99\xe4\xc63\xa2\xb4\xfe\x81\xeatD\x98\x9e.h\xbc\x9e\xce;`@Q` \x81R\x80a\x13G3\x94` \x83\x01\x90a\a\x87V[\x03\x90\xa2V[Pa\b\xfca\x13\x03V[`\x84`@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\x82\x01R`!`$\x82\x01R\x7fIPTokenSlashing: Insufficient fe`D\x82\x01R\x7fe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x90\x81` \x91\x03\x12a\x01HWQ\x90V[\x7f\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\x00\x00\x00\x00\x90\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x82\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x93\x16\x80\x94\x83\x16\x17\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0`\x00\x80\xa3V[\x15a\x14\xa3WV[`\x84`@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\x82\x01R`&`$\x82\x01R\x7fInvalid uncompressed public key `D\x82\x01R\x7flength\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[`@Q\x90``\x82\x01\x82\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\x02\xc4W`@R`!\x82R`@\x82` 6\x91\x017V[\x90`\x01\x82\x01\x80\x92\x11a\x15bWV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`\x11`\x04R`$`\x00\xfd[\x90\x81;\x15a\x16dWs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81\x7f\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\x00\x00\x00\x00\x82T\x16\x17\x90U\x7f\xbc|\xd7Z \xee'\xfd\x9a\u07ba\xb3 A\xf7U!M\xbck\xff\xa9\f\xc0\"[9\xda.\\-;`\x00\x80\xa2\x80Q\x15a\x161Wa\x16.\x91a\x17\x04V[PV[PP4a\x16:WV[`\x04`@Q\x7f\xb3\x98\x97\x9f\x00\x00\x00\x00\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\xfd[`$\x82s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[`\xff\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00T`@\x1c\x16\x15a\x16\xdaWV[`\x04`@Q\x7f\xd7\xe6\xbc\xf8\x00\x00\x00\x00\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\xfd[`\x00\x80a\x10\xf3\x93` \x81Q\x91\x01\x84Z\xf4=\x15a\x17BW=\x91a\x17%\x83a\x02\xc9V[\x92a\x173`@Q\x94\x85a\x02\x83V[\x83R=`\x00` \x85\x01>a\x17FV[``\x91[\x90a\x17\x85WP\x80Q\x15a\x17[W\x80Q\x90` \x01\xfd[`\x04`@Q\x7f\x14%\xeaB\x00\x00\x00\x00\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\xfd[\x81Q\x15\x80a\x17\xddW[a\x17\x96WP\x90V[`$\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7f\x99\x96\xb3\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[P\x80;\x15a\x17\x8eV\xfe\xa2dipfsX\"\x12 B\xfe]3\u063d\x9dT\x00\xcc\x11+\x98\xf8\x9b\xe4\xc0U$\xeb7\x97'\xc7\xdc=E\x16u\xa5p$\x92\xa4\xab\xe1\x94\xe727%\x95Q\xb0Dw_v\xccB\x8dU|\xa1O\xa5\xbe\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94\xec\xb1\xd0QGZ~3\v\x1d\xd6h<\xdcx#\xbb\u03cd\u03cbR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94\xf3\x9f\xd6\xe5\x1a\xad\x88\xf6\xf4\xcej\xb8\x82ry\xcf\xff\xb9\"f\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00" +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" +const iliadAllocData = "\xf9\x8d\xcd\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\x1a\x01\xf9\x02U\x90eQ\xc1\x94\x87\x81F\x12\xe5\x8f\xe0h\x13wWX\x01\xf9\x02@\x80\xb9\x02;`\x80`@R4\x80\x15a\x00\x10W`\x00\x80\xfd[P`\x046\x10a\x006W`\x005`\xe0\x1c\x80c$j\x00!\x14a\x00;W\x80c\x8aT\xc5/\x14a\x00jW[`\x00\x80\xfd[a\x00Na\x00I6`\x04a\x01\xb7V[a\x00}V[`@Q`\x01`\x01`\xa0\x1b\x03\x90\x91\x16\x81R` \x01`@Q\x80\x91\x03\x90\xf3[a\x00Na\x00x6`\x04a\x01\xb7V[a\x00\xe1V[`\x00`\x80`$`\x8c7nZ\xf4=\x82\x80>\x90=\x91`+W\xfd[\xf3`lR\x85`]Rs=`\xad\x80`\n=9\x81\xf36==7===6=s`IR`\xff`\x00S`\xb7`U `5R0``\x1b`\x01R\x84`\x15R`U`\x00 ``\x1b``\x1c`\x00R` `\x00\xf3[`\x00`\x80`$`\x8c7nZ\xf4=\x82\x80>\x90=\x91`+W\xfd[\xf3`lR\x85`]Rs=`\xad\x80`\n=9\x81\xf36==7===6=s`IR`\xff`\x00S`\xb7`U `5R0``\x1b`\x01R\x84`\x15R`U`\x00 \x80;a\x01\x8bW\x85`\xb7`U`\x00\xf5\x80a\x01WWc \x18\x8aY`\x00R`\x04`\x1c\xfd[\x80`lRP\x82\x84\x88\x7fy\xf1\x9b6U\xee8\xb1\xceReV\xb7s\x1a \xc8\xf2\x18\xfb\xdaJ9\x90\xb6\xccAr\xfd\xf8\x87\"```l\xa4` `l\xf3[\x80``\x1b``\x1c`\x00R` `\x00\xf3[\x805`\x01`\x01`\xa0\x1b\x03\x81\x16\x81\x14a\x01\xb2W`\x00\x80\xfd[\x91\x90PV[`\x00\x80`\x00\x80`\x00`\xa0\x86\x88\x03\x12\x15a\x01\xcfW`\x00\x80\xfd[a\x01\u0606a\x01\x9bV[\x94P` \x86\x015\x93P`@\x86\x015\x92Pa\x01\xf4``\x87\x01a\x01\x9bV[\x94\x97\x93\x96P\x91\x94`\x80\x015\x92\x91PPV\xfe\xa2dipfsX\"\x12 \xea/\xe5:\xf5\aE\x14a\x02hW\x80c.\xbc`4\x14a\x02cW\x80c9\xecM\xf9\x14a\x02^W\x80cH\x90>8\x14a\x02YW\x80cO\x1e\xf2\x86\x14a\x02TW\x80cR\u0450-\x14a\x02OW\x80cS\x97,*\x14a\x02JW\x80cW\x06u\x03\x14a\x02EW\x80cZi\x82]\x14a\x02@W\x80c]Z\xb9h\x14a\x02;W\x80cn\xa3\xa2(\x14a\x026W\x80cqP\x18\xa6\x14a\x021W\x80cx\x7f\x82\xc8\x14a\x02,W\x80cy\xbaP\x97\x14a\x02'W\x80c{n\x84,\x14a\x02\"W\x80c\x83\xdf\xfdo\x14a\x02\x1dW\x80c\x86\xee\u0121\x14a\x02\x18W\x80c\x8d>\x1eA\x14a\x02\x13W\x80c\x8d\xa5\xcb[\x14a\x02\x0eW\x80c\x8f7\xec\x19\x14a\x02\tW\x80c\x98U\u0235\x14a\x02\x04W\x80c\xa1\xcb\x18F\x14a\x01\xffW\x80c\xad<\xb1\xcc\x14a\x01\xfaW\x80c\xb8\u06d8>\x14a\x01\xf5W\x80c\xbd\xa1k\x15\x14a\x01\xf0W\x80c\xc2J\xe5\x86\x14a\x01\xebW\x80c\xd2\xe1\xf5\xb8\x14a\x01\xe6W\x80c\xe3\f9x\x14a\x01\xe1W\x80c\xebJ\xf0E\x14a\x01\xdcW\x80c\xee\xe5\u03ad\x14a\x01\xd7W\x80c\xf1\x88v\x84\x14a\x01\xd2W\x80c\xf2\xfd\xe3\x8b\x14a\x01\xcdW\x80c\xf9*\xd2\x19\x14a\x01\xc8W\x80c\xfc.Y2\x14a\x01\xc3Wc\xfcV\u00a2\x14a\x01\xbeW`\x00\x80\xfd[a\x1e\xe3V[a\x1d\xe5V[a\x1b\xa9V[a\x1a\xdcV[a\x1a\xbeV[a\x1a\x9aV[a\x1avV[a\x1a#V[a\x19\xf9V[a\x19\xe1V[a\x19\xa6V[a\x19eV[a\x19\x03V[a\x18DV[a\x18 V[a\x17\xb4V[a\x17aV[a\x16\xd6V[a\x14\x80V[a\x13\x84V[a\x12{V[a\x11\xf3V[a\x10aV[a\x0f\x96V[a\x0frV[a\x0enV[a\x0e\x01V[a\r\xe9V[a\f\xa3V[a\v\xf9V[a\t\xfcV[a\b\xbeV[a\bqV[a\b0V[a\a\xc7V[a\x04\x91V[a\x04sV[a\x03\"V[\x91\x81`\x1f\x84\x01\x12\x15a\x02\xaaW\x825\x91g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11a\x02\xaaW` \x83\x81\x86\x01\x95\x01\x01\x11a\x02\xaaWV[`\x00\x80\xfd[`\x045\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x82\x03a\x02\xaaWV[`@`\x03\x19\x82\x01\x12a\x02\xaaW`\x045\x90g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11a\x02\xaaWa\x02\xfd\x91`\x04\x01a\x02|V[\x90\x91`$5s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16\x81\x03a\x02\xaaW\x90V[4a\x02\xaaWa\x0306a\x02\xd2V[\x90a\x03=`A\x82\x14a\x1f$V[\x80\x15a\x04nWa\x03\xdba\x03\xd6a\x03\u0445a\x03\x9e\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00a\x03\xe3\x995\x16\x14a\x1f\xdeV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x94a\x03\xca3\x87a\x03\u00c4\x86a'\x1aV[\x16\x14a iV[6\x91a\x06kV[a'\xbfV[a\x06\xe3V[\x91\x16\x90a6\xf0V[\x15a\x03\xeaW\x00[`\x84`@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\x82\x01R`'`$\x82\x01R\x7fIPTokenStaking: Operator already`D\x82\x01R\x7f exists\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[a\x1f\xafV[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` `\x03T`@Q\x90\x81R\xf3[4a\x02\xaaWa\x04\x9f6a\x02\xd2V[\x90a\x04\xac`A\x82\x14a\x1f$V[\x80\x15a\x04nWa\x05\ra\x03\xd6a\x03\u0445a\x03\x9e\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00a\x05\x15\x995\x16\x14a\x1f\xdeV[\x91\x16\x90a7\xf8V[\x15a\x05\x1cW\x00[`\x84`@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\x82\x01R`\"`$\x82\x01R\x7fIPTokenStaking: Operator not fou`D\x82\x01R\x7fnd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`A`\x04R`$`\x00\xfd[`@\x81\x01\x90\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\x05\xebW`@RV[a\x05\xa0V[\x90`\x1f\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\x91\x01\x16\x81\x01\x90\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\x05\xebW`@RV[g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x05\xebW`\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` \x01\x90V[\x92\x91\x92a\x06w\x82a\x061V[\x91a\x06\x85`@Q\x93\x84a\x05\xf0V[\x82\x94\x81\x84R\x81\x83\x01\x11a\x02\xaaW\x82\x81` \x93\x84`\x00\x96\x017\x01\x01RV[\x90\x80`\x1f\x83\x01\x12\x15a\x02\xaaW\x81` a\x06\xbd\x935\x91\x01a\x06kV[\x90V[`\x00[\x83\x81\x10a\x06\xd3WPP`\x00\x91\x01RV[\x81\x81\x01Q\x83\x82\x01R` \x01a\x06\xc3V[` a\x06\xfc\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\a\x81R\x03\x01\x90 \x90V[` a\a\"\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\x06\x81R\x03\x01\x90 \x90V[` a\aH\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\x05\x81R\x03\x01\x90 \x90V[` a\an\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\b\x81R\x03\x01\x90 \x90V[` a\a\x94\x91\x81`@Q\x93\x82\x85\x80\x94Q\x93\x84\x92\x01a\x06\xc0V[\x81\x01`\x04\x81R\x03\x01\x90 \x90V[` \x90a\a\xbb\x92\x82`@Q\x94\x83\x86\x80\x95Q\x93\x84\x92\x01a\x06\xc0V[\x82\x01\x90\x81R\x03\x01\x90 \x90V[4a\x02\xaaW`@`\x03\x196\x01\x12a\x02\xaaWg\xff\xff\xff\xff\xff\xff\xff\xff`\x045\x81\x81\x11a\x02\xaaWa\a\xf9\x906\x90`\x04\x01a\x06\xa2V[\x90`$5\x90\x81\x11a\x02\xaaW` \x91a\b!a\b\x1ba\b'\x936\x90`\x04\x01a\x06\xa2V[\x91a\a\tV[\x90a\a\xa1V[T`@Q\x90\x81R\xf3[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` `@Qc\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x88\x16\x81R\xf3[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` `\x01T`@Q\x90\x81R\xf3[` `\x03\x19\x82\x01\x12a\x02\xaaW`\x045\x90g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11a\x02\xaaWa\b\xba\x91`\x04\x01a\x02|V[\x90\x91V[a\b\xc76a\b\x8fV[\x90a\b\xd4`A\x83\x14a\x1f$V[\x81\x15a\x04nWa\t\u0591a\t,\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00\x845\x16\x14a\x1f\xdeV[a\t4a(\xf6V[`@Q\x91a\tA\x83a\x05\xcfV[`\t\x83R\x7fvalidator\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00` \x84\x01R\x7f\x00\x00\x00\x00\x00\x00\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\xf4\x92\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x88\x92\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xe8\x92a, v\xcc75\xa9 \xa3\xcaP]8+\xbc\x83\x03a\v>Wa\v<\x92Pa9\x98V[\x00[`@Q\x7f\xaa\x1dI\xa4\x00\x00\x00\x00\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\x81\x01\x84\x90R`$\x90\xfd[a\v\x93\x91\x94P` =` \x11a\v\x9aW[a\v\x8b\x81\x83a\x05\xf0V[\x81\x01\x90a.}V[\x928a\n\xbeV[P=a\v\x81V[`\x04`@Q\x7f\xe0|\x8d\xba\x00\x00\x00\x00\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\xfd[\x90P\x83\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16\x14\x158a\nuV[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaWs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00_\xbd\xb21Vx\xaf\xec\xb3g\xf02\xd9?d/d\x18\n\xa3\x160\x03a\v\xa1W` `@Q\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81R\xf3[`\x03\x19\x90` \x82\x82\x01\x12a\x02\xaaW`\x045\x91g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11a\x02\xaaW\x82`\x80\x92\x03\x01\x12a\x02\xaaW`\x04\x01\x90V[4a\x02\xaaWa\f\xb16a\fqV[a\f\xbb\x81\x80a \xf4V[a\f\xca`A\x82\x94\x93\x94\x14a\x1f$V[\x15a\x04nWa\r\x1e\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00\x80\x945\x16\x14a\x1f\xdeV[a\r+` \x82\x01\x82a \xf4V[\x90a\r8`!\x83\x14a\x1f$V[\x81\x15a\x04nWa\r\x88a\r\x8f\x92\x82a\r\x83a\v<\x97`\xff\x955\x16\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x14\x90\x81\x15a\r\x94W[Pa\x1f\xdeV[a!EV[T\x16a!\x90V[a\"\x1bV[\x7f\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x91P\x148a\r}V[` `\x03\x19\x82\x01\x12a\x02\xaaW`\x045\x90g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11a\x02\xaaWa\x06\xbd\x91`\x04\x01a\x06\xa2V[4a\x02\xaaW` a\b'a\r\xfc6a\r\xbeV[a\a/V[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` `\x02T`@Q\x90\x81R\xf3[```\x03\x19\x82\x01\x12a\x02\xaaWg\xff\xff\xff\xff\xff\xff\xff\xff\x91`\x045\x83\x81\x11a\x02\xaaW\x82a\x0eL\x91`\x04\x01a\x02|V[\x93\x90\x93\x92`$5\x91\x82\x11a\x02\xaaWa\x0ef\x91`\x04\x01a\x02|V[\x90\x91`D5\x90V[4a\x02\xaaWa\x0e|6a\x0e\x1fV[\x92a\x0e\x8c`A\x82\x94\x93\x94\x14a\x1f$V[\x80\x15a\x04nW\x7f\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\x00\x00\x00\x94a\x0e\xe2\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x87\x835\x16\x14a\x1f\xdeV[a\x0f\x053s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffa\x03\u00c5\x85a'\x1aV[a\x0f\x11`!\x85\x14a\x1f$V[\x83\x15a\x04nWa\x0fQa\v<\x96\x845\x16\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x14\x90\x81\x15a\r\x94WPa\x1f\xdeV[a\x0fma\x0fha\x0fa\x86\x86a!EV[T`\xff\x16\x90V[a!\x90V[a\"\xeaV[4a\x02\xaaW` `\x03\x196\x01\x12a\x02\xaaWa\x0f\x8ba2TV[a\v<`\x045a2\x94V[4a\x02\xaaW`\x00\x80`\x03\x196\x01\x12a\x10^Wa\x0f\xb0a2TV[\x80s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\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\x00\x00\x00\x00\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x81\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x91\x82\x16\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0\x82\x80\xa3\x80\xf3[\x80\xfd[4a\x02\xaaWa\x10o6a\x02\xd2V[\x91a\x10|`A\x83\x14a\x1f$V[\x81\x15a\x04nWa\x03\u0441a\x10\xd7\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00a\x10\xfc\x955\x16\x14a\x1f\xdeV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x93a\x03\xca3\x86a\x03\u00c4\x86a'\x1aV[\x91a\x11\x06\x83a\a/V[T\x15a\x11oWa\x11j\x83a\x11Qa\x11Ja\x11@\x7f\x9f\x7f\x04\xf6\x88)\x8fGN\xd4\u01c6\xab\xb2\x9e\f\xa0\x17=pQmU\xd9\xea\xc5\x15`\x9bE\xfb\u0297a\aUV[T`\x03T\x90a#FV[B\x11a#SV[Ba\x11[\x82a\aUV[U`@Q\x93\x84\x93\x16\x90\x83a#\xdeV[\x03\x90\xa1\x00[`\x84`@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\x82\x01R`)`$\x82\x01R\x7fIPTokenStaking: Delegator must h`D\x82\x01R\x7fave stake\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16\x03a\x12KWa\v<3a3\x88V[`$`@Q\x7f\x11\x8c\u06a7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R3`\x04\x82\x01R\xfd[4a\x02\xaaWa\x12\x896a\fqV[a\x12\x93\x81\x80a \xf4V[\x91\x90a\x12\xa1`A\x84\x14a\x1f$V[\x82\x15a\x04nWa\x13\x1a\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffa\x03\xc3\x7f\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\x00\x00\x00\x95a\x13\x13\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x88\x865\x16\x14a\x1f\xdeV[3\x93a'\x1aV[a\x13'` \x82\x01\x82a \xf4V[\x90a\x134`!\x83\x14a\x1f$V[\x81\x15a\x04nWa\x0faa\x13\x7f\x92\x82a\r\x83a\v<\x97a\x0fh\x955\x16\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x14\x90\x81\x15a\r\x94WPa\x1f\xdeV[a#\xfaV[4a\x02\xaaWa\x13\x9ba\x13\x956a\b\x8fV[\x90a!^V[`@Q\x90\x81\x90\x80T\x80\x84R` \x80\x94\x01\x90\x81\x92`\x00R\x84`\x00 \x90`\x00[\x86\x82\x82\x10a\x14%W\x86\x86a\x13\u03c2\x88\x03\x83a\x05\xf0V[`@Q\x92\x83\x92\x81\x84\x01\x90\x82\x85RQ\x80\x91R`@\x84\x01\x92\x91`\x00[\x82\x81\x10a\x13\xf8WPPPP\x03\x90\xf3[\x83Qs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x16\x85R\x86\x95P\x93\x81\x01\x93\x92\x81\x01\x92`\x01\x01a\x13\xe9V[\x83T\x85R\x90\x93\x01\x92`\x01\x92\x83\x01\x92\x01a\x13\xb9V[`@`\x03\x19\x82\x01\x12a\x02\xaaWg\xff\xff\xff\xff\xff\xff\xff\xff\x91`\x045\x83\x81\x11a\x02\xaaW\x82a\x14f\x91`\x04\x01a\x02|V[\x93\x90\x93\x92`$5\x91\x82\x11a\x02\xaaWa\b\xba\x91`\x04\x01a\x02|V[a\x14\x896a\x149V[\x91a\x14\x96`A\x82\x14a\x1f$V[\x80\x15a\x04nW\x7f\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\x00\x00\x00\x93a\x14\xec\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x86\x835\x16\x14a\x1f\xdeV[a\x15\x0f3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffa\x03\u00c5\x85a'\x1aV[a\x15\x1b`!\x85\x14a\x1f$V[\x83\x15a\x04nWa\x15[a\v<\x95\x845\x16\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\x14\x90\x81\x15a\r\x94WPa\x1f\xdeV[a\x15j`\xffa\r\x88\x86\x86a!EV[a$\xb0V[\x90`\x01\x82\x81\x1c\x92\x16\x80\x15a\x15\xb8W[` \x83\x10\x14a\x15\x89WV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`\"`\x04R`$`\x00\xfd[\x91`\x7f\x16\x91a\x15~V[\x80T`\x00\x93\x92a\x15\u0442a\x15oV[\x91\x82\x82R` \x93`\x01\x91`\x01\x81\x16\x90\x81`\x00\x14a\x169WP`\x01\x14a\x15\xf8W[PPPPPV[\x90\x93\x94\x95P`\x00\x92\x91\x92R\x83`\x00 \x92\x84`\x00\x94[\x83\x86\x10a\x16%WPPPP\x01\x01\x908\x80\x80\x80\x80a\x15\xf1V[\x80T\x85\x87\x01\x83\x01R\x94\x01\x93\x85\x90\x82\x01a\x16\rV[\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\x00\x16\x86\x85\x01RPPP\x90\x15\x15`\x05\x1b\x01\x01\x91P8\x80\x80\x80\x80a\x15\xf1V[\x90a\x16\x91a\x16\x8a\x92`@Q\x93\x84\x80\x92a\x15\xc2V[\x03\x83a\x05\xf0V[V[\x90\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`\x1f` \x93a\x16\u03c1Q\x80\x92\x81\x87R\x87\x80\x88\x01\x91\x01a\x06\xc0V[\x01\x16\x01\x01\x90V[4a\x02\xaaWa\x16\xeca\x16\xe76a\r\xbeV[a\a{V[`\xff\x81T\x16`@Q\x91a\x17\r\x83a\x17\x06\x81`\x01\x85\x01a\x15\xc2V[\x03\x84a\x05\xf0V[`\x03`\x02\x82\x01T\x91\x01Tc\xff\xff\xff\xff\x90a\x17;`@Q\x95\x86\x95\x15\x15\x86R`\xc0` \x87\x01R`\xc0\x86\x01\x90a\x16\x93V[\x92`@\x85\x01R\x81\x81\x16``\x85\x01R\x81\x81` \x1c\x16`\x80\x85\x01R`@\x1c\x16`\xa0\x83\x01R\x03\x90\xf3[4a\x02\xaaW`\x00`\x03\x196\x01\x12a\x02\xaaW` s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16`@Q\x90\x81R\xf3[a\x17\xbd6a\x149V[\x91a\x17\xca`A\x82\x14a\x1f$V[\x80\x15a\x04nW\x7f\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\x00\x00\x00\x93a\x15\x0f\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x86\x835\x16\x14a\x1f\xdeV[4a\x02\xaaW` `\x03\x196\x01\x12a\x02\xaaWa\x189a2TV[a\v<`\x045a4#\xa9f.\xfc\x9c\"\x9cj\x00T\x90g\xff\xff\xff\xff\xff\xff\xff\xff`\xff\x83`@\x1c\x16\x15\x92\x16\x80\x15\x90\x81a\x1d\xa4W[`\x01\x14\x90\x81a\x1d\x9aW[\x15\x90\x81a\x1d\x91W[Pa\x1dgWa\x1c\x86\x90\x82a\x1cl\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00`\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\x00\x00\x00\x00\x00\x00\x00\x00\x82T\x16\x17\x90UV[a\x1d\vW[`\x845\x90`d5\x90`D5\x90`$5\x90a&RV[a\x1c\x8cW\x00[a\x1c\xd8\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\x81T\x16\x90UV[`@Q`\x01\x81R\x7f\xc7\xf5\x05\xb2\xf3q\xae!u\xeeI\x13\xf4I\x9e\x1f&3\xa7\xb5\x93c!\xee\xd1\u036e\xb6\x11Q\x81\u0490\x80` \x81\x01a\x11jV[a\x1db\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00h\x01\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\x82T\x16\x17\x90UV[a\x1cqV[`\x04`@Q\x7f\xf9.\xe8\xa9\x00\x00\x00\x00\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\xfd[\x90P\x158a\x1c\x13V[0;\x15\x91Pa\x1c\vV[\x83\x91Pa\x1c\x01V[`D5\x90c\xff\xff\xff\xff\x82\x16\x82\x03a\x02\xaaWV[`d5\x90c\xff\xff\xff\xff\x82\x16\x82\x03a\x02\xaaWV[`\x845\x90c\xff\xff\xff\xff\x82\x16\x82\x03a\x02\xaaWV[`\xa0`\x03\x196\x01\x12a\x02\xaaWg\xff\xff\xff\xff\xff\xff\xff\xff`\x045\x81\x81\x11a\x02\xaaWa\x1e\x12\x906\x90`\x04\x01a\x02|V[\x90\x91`$5\x90\x81\x11a\x02\xaaWa\x1e,\x906\x90`\x04\x01a\x02|V[\x91\x90\x92a\x1e7a\x1d\xacV[\x90a\x1e@a\x1d\xbfV[\x92a\x1eIa\x1d\xd2V[\x94a\x1eV`A\x83\x14a\x1f$V[\x81\x15a\x04nWa\t\u0596a\x1e\u0751a\x1e\xb2\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00\x875\x16\x14a\x1f\xdeV[a\x1e\xd53s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffa\x03\u00c7\x89a'\x1aV[a\x03\xcaa(\xf6V[\x91a,\u5bb5\x00\x00\x00\x00\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\xfd[\x15a)WWV[`\x84`@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\x82\x01R`$\x80\x82\x01R\x7fIPTokenStaking: Stake amount too`D\x82\x01R\x7f low\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x91a*\x12\x91\x83T\x90\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\x90`\x03\x1b\x92\x83\x1b\x92\x1b\x19\x16\x17\x90V[\x90UV[\x81\x81\x10a*!WPPV[`\x00\x81U`\x01\x01a*\x16V[\x91\x90`\x1f\x81\x11a*`$\x82\x01R\x7fIPTokenStaking: newWithdrawalAdd`D\x82\x01R\x7fressChangeInterval cannot be 0\x00\x00`d\x82\x01R\xfd[\x80T\x82\x10\x15a\x04nW`\x00R` `\x00 \x01\x90`\x00\x90V[`\x00\x82\x81R`\x01\x82\x01` R`@\x90 Ta7zW\x80T\x90h\x01\x00\x00\x00\x00\x00\x00\x00\x00\x82\x10\x15a\x05\xebW\x82a7ca7.\x84`\x01\x80\x96\x01\x85U\x84a6\xd8V[\x81\x93\x91T\x90\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\x90`\x03\x1b\x92\x83\x1b\x92\x1b\x19\x16\x17\x90V[\x90U\x80T\x92`\x00R\x01` R`@`\x00 U`\x01\x90V[PP`\x00\x90V[\x80T\x90\x81\x15a7\xc9W\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\x80\x92\x01\x91a7\xb9\x83\x83a6\xd8V[\x90\x91\x82T\x91`\x03\x1b\x1b\x19\x16\x90UUV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`1`\x04R`$`\x00\xfd[`\x01\x81\x01\x91\x80`\x00R\x82` R`@`\x00 T\x92\x83\x15\x15`\x00\x14a8\xc2W\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\x92\x83\x85\x01\x90\x85\x82\x11a#AW\x80T\x94\x85\x01\x94\x85\x11a#AW`\x00\x95\x85\x83a8y\x97a8j\x95\x03a8\x7fW[PPPa7\x81V[\x90`\x00R` R`@`\x00 \x90V[U`\x01\x90V[a8\xa9a8\xa3\x91a8\x93a8\xb9\x94\x87a6\xd8V[\x90T\x90`\x03\x1b\x1c\x92\x83\x91\x87a6\xd8V[\x90a)\xdaV[\x85\x90`\x00R` R`@`\x00 \x90V[U8\x80\x80a8bV[PPPP`\x00\x90V[=\x15a8\xf6W=\x90a8\u0702a\x061V[\x91a8\xea`@Q\x93\x84a\x05\xf0V[\x82R=`\x00` \x84\x01>V[``\x90V[`\x00\x80\x80\x80\x933Z\xf1a9\fa8\xcbV[P\x15a9\x14WV[`\x84`@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\x82\x01R`*`$\x82\x01R\x7fIPTokenStaking: Failed to refund`D\x82\x01R\x7f remainder\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x90\x81;\x15a:kWs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81\x7f\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\x00\x00\x00\x00\x82T\x16\x17\x90U\x7f\xbc|\xd7Z \xee'\xfd\x9a\u07ba\xb3 A\xf7U!M\xbck\xff\xa9\f\xc0\"[9\xda.\\-;`\x00\x80\xa2\x80Q\x15a:8Wa:5\x91a;\vV[PV[PP4a:AWV[`\x04`@Q\x7f\xb3\x98\x97\x9f\x00\x00\x00\x00\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\xfd[`$\x82s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[`\xff\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00T`@\x1c\x16\x15a:\xe1WV[`\x04`@Q\x7f\xd7\xe6\xbc\xf8\x00\x00\x00\x00\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\xfd[`\x00\x80a\x06\xbd\x93` \x81Q\x91\x01\x84Z\xf4a;#a8\xcbV[\x91\x90a;cWP\x80Q\x15a;9W\x80Q\x90` \x01\xfd[`\x04`@Q\x7f\x14%\xeaB\x00\x00\x00\x00\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\xfd[\x81Q\x15\x80a;\xbbW[a;tWP\x90V[`$\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7f\x99\x96\xb3\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[P\x80;\x15a;lV\xfe\xa2dipfsX\"\x12 \xde\x1c\xe9\xf0W\x90\v+\xfc?\x0f\xc2\xc4\x17|\x81:\x83\xb4a\x95\xb7\xe5\xe4v\xab\xf2\x1a\x98}\xc0\x88dsolcC\x00\b\x17\x003\xc0\u150f\xfc\x89\xda(\xdd/_u\x82\xb0E\x95\x05\xe9\xa6\x15b7\x91\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xf9\x0f\ua525\x13\xe6\xe4\xb8\xf2\xa9#\u0643\x04\xec\x87\xf6\x00\x00\x00\x00\x00\x00\x01\xf9\x0f\u0440\xb9\x0f\xcc`@`\x80\x81R`\x04\x90\x816\x10\x15a\x00\x15W`\x00\x80\xfd[`\x00\x91\x825`\xe0\x1c\x91\x82cO\x1e\xf2\x86\x14a\b\xfcW\x82cR\u0450-\x14a\b@W\x82cqP\x18\xa6\x14a\a[W\x82cy\xbaP\x97\x14a\x06\xafW\x82c\x8d\xa5\xcb[\x14a\x06=W\x82c\xad<\xb1\xcc\x14a\x051W\x82c\xc4\xd6m\xe8\x14a\x02\xd8W\x82c\xe3\f9x\x14a\x02bW\x82c\xef\x17n\x0e\x14a\x01\x80WPPc\xf2\xfd\xe3\x8b\x14a\x00\x92W`\x00\x80\xfd[4a\x01}W` \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\xfc6\x01\x12a\x01}Wa\x00\xc9a\f:V[a\x00\xd1a\ryV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x91\x16\x90\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x82\x7f\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\x00\x00\x00\x00\x82T\x16\x17\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16\x7f8\xd1k\x8c\xac\"\u065f\xc7\xc1$\xb9\xcd\r\xe2\xd3\xfa\x1f\xae\xf4 \xbf\xe7\x91\xd8\xc3b\xd7e\xe2'\x00\x83\x80\xa3\x80\xf3[\x80\xfd[\x90\x91P4a\x02^W``\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\xfc6\x01\x12a\x02^Wg\xff\xff\xff\xff\xff\xff\xff\xff\x91\x805\x83\x81\x11a\x02ZWa\x01\u04906\x90\x83\x01a\r\fV[\x91\x90\x92`$5\x90\x81`\a\v\x80\x92\x03a\x02VW`D5\x95\x86\x11a\x02VWa\x02\x1fa\x02P\x93\x7f\x11'I\xe7\x9b \x98\xb5\x8e\xab6\xc2\x1f\x12;(\x83\xc3\ucef4\xf4\x16#\xa7D\xfam\x9b>7\u01976\x91\x01a\r\fV[\x91a\x02(a\ryV[a\x02>\x81Q\x97\x88\x97``\x89R``\x89\x01\x91a\r:V[\x93` \x87\x01R\x85\x84\x03\x90\x86\x01Ra\r:V[\x03\x90\xa1\x80\xf3[\x86\x80\xfd[\x84\x80\xfd[\x82\x80\xfd[\x83\x904a\x02\xd4W\x81\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\xfc6\x01\x12a\x02\xd4W` \x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16\x90Q\x90\x81R\xf3[P\x80\xfd[\x91P4a\x02^W` \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\xfc6\x01\x12a\x02^Wa\x03\x11a\f:V[\x90\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00\x91\x82T\x91`\xff\x83\x86\x1c\x16\x15\x92g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16\x80\x15\x90\x81a\x05)W[`\x01\x14\x90\x81a\x05\x1fW[\x15\x90\x81a\x05\x16W[Pa\x04\xeeW\x83`\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\x00\x00\x00\x00\x00\x00\x00\x00\x83\x16\x17\x86Ua\x04\xb9W[Ps\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x15a\x046WPa\x03\u05d0a\x03\xc2a\x0e\x9dV[a\x03\xcaa\x0e\x9dV[a\x03\xd2a\x0e\x9dV[a\r\xe9V[a\x03\xdfW\x82\x80\xf3[\x7f\xc7\xf5\x05\xb2\xf3q\xae!u\xeeI\x13\xf4I\x9e\x1f&3\xa7\xb5\x93c!\xee\xd1\u036e\xb6\x11Q\x81\u0491\x81\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff` \x93T\x16\x90UQ`\x01\x81R\xa18\x80\x82\x80\xf3[`\x84\x90` \x86Q\x91\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\x83R\x82\x01R`7`$\x82\x01R\x7fUpgradeEntrypoint: accessManager`D\x82\x01R\x7f cannot be zero address\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x7f\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\x00\x16h\x01\x00\x00\x00\x00\x00\x00\x00\x01\x17\x84U8a\x03\x98V[P\x84Q\x7f\xf9.\xe8\xa9\x00\x00\x00\x00\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\xfd[\x90P\x158a\x03eV[0;\x15\x91Pa\x03]V[\x85\x91Pa\x03SV[\x91P4a\x02^W\x82\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\xfc6\x01\x12a\x02^W\x81Q\x90\x82\x82\x01\x90\x82\x82\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11\x17a\x06\x11WP\x82R`\x05\x81R` \x90\x7f5.0.0\x00\x00\x00\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\x01R\x82Q\x93\x84\x92` \x84R\x82Q\x92\x83` \x86\x01R\x82[\x84\x81\x10a\x05\xfbWPPP\x82\x82\x01\x84\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\x81\x01\x03\x01\x90\xf3[\x81\x81\x01\x83\x01Q\x88\x82\x01\x88\x01R\x87\x95P\x82\x01a\x05\xbfV[\x84`A`$\x92\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83RR\xfd[\x83\x904a\x02\xd4W\x81\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\xfc6\x01\x12a\x02\xd4W` \x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16\x90Q\x90\x81R\xf3[\x90\x91P4a\x02^W\x82\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\xfc6\x01\x12a\x02^W3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16\x03a\a+W\x82a\a(3a\r\xe9V[\x80\xf3[`$\x92PQ\x90\x7f\x11\x8c\u06a7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x82R3\x90\x82\x01R\xfd[\x834a\x01}W\x80\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\xfc6\x01\x12a\x01}Wa\a\x92a\ryV[\x80s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\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\x00\x00\x00\x00\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x81\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x91\x82\x16\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0\x82\x80\xa3\x80\xf3[\x834a\x01}W\x80\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\xfc6\x01\x12a\x01}WPs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01e\x87\x8aYL\xa2U3\x8a\u07e4\u0504I\xf6\x92B\xeb\x8f\x160\x03a\b\xd6W` \x90Q\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81R\xf3[Q\x7f\xe0|\x8d\xba\x00\x00\x00\x00\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\xfd[\x80\x91\x92P\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\xfc6\x01\x12a\x02^Wa\t0a\f:V[\x90`$\x93\x845g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x02\xd4W6`#\x82\x01\x12\x15a\x02\xd4W\x80\x85\x015a\t]\x81a\f\xd2V[\x94a\tj\x85Q\x96\x87a\fbV[\x81\x86R` \x91\x82\x87\x01\x936\x8a\x83\x83\x01\x01\x11a\f6W\x81\x86\x92\x8b\x86\x93\x01\x877\x88\x01\x01Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01e\x87\x8aYL\xa2U3\x8a\u07e4\u0504I\xf6\x92B\xeb\x8f\x16\x800\x14\x90\x81\x15a\f\bW[Pa\v\xe0Wa\t\xdca\ryV[\x81\x16\x95\x85Q\x7fR\u0450-\x00\x00\x00\x00\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\x83\x81\x8a\x81\x8bZ\xfa\x86\x91\x81a\v\xb1W[Pa\nHWPPPPPPQ\x91\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x82\x01R\xfd[\x90\x88\x88\x88\x94\x93\x8c\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x91\x82\x81\x03a\v\x84WP\x85;\x15a\vWWP\x80T\x7f\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\x00\x00\x00\x00\x16\x82\x17\x90U\x84Q\x88\x93\x92\x91\x7f\xbc|\xd7Z \xee'\xfd\x9a\u07ba\xb3 A\xf7U!M\xbck\xff\xa9\f\xc0\"[9\xda.\\-;\x85\x80\xa2\x82Q\x15a\v WPPa\v\x12\x95\x82\x91Q\x90\x84Z\xf4\x91=\x15a\v\x16W=a\v\x04a\n\xfb\x82a\f\xd2V[\x92Q\x92\x83a\fbV[\x81R\x85\x81\x94=\x92\x01>a\x0e\xf6V[P\x80\xf3[P``\x92Pa\x0e\xf6V[\x95P\x95PPPPP4a\v2WPP\x80\xf3[\x7f\xb3\x98\x97\x9f\x00\x00\x00\x00\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\xfd[\x83\x83\x88Q\x91\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x82\x01R\xfd[\x84\x90\x88Q\x91\x7f\xaa\x1dI\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x82\x01R\xfd[\x90\x91P\x84\x81\x81=\x83\x11a\v\xd9W[a\v\u0241\x83a\fbV[\x81\x01\x03\x12a\x02VWQ\x908a\n\x13V[P=a\v\xbfV[\x87\x86Q\x7f\xe0|\x8d\xba\x00\x00\x00\x00\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\xfd[\x90P\x81\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16\x14\x158a\t\xcfV[\x85\x80\xfd[`\x045\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x82\x03a\f]WV[`\x00\x80\xfd[\x90`\x1f\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\x91\x01\x16\x81\x01\x90\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\f\xa3W`@RV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`A`\x04R`$`\x00\xfd[g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\f\xa3W`\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` \x01\x90V[\x91\x81`\x1f\x84\x01\x12\x15a\f]W\x825\x91g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11a\f]W` \x83\x81\x86\x01\x95\x01\x01\x11a\f]WV[`\x1f\x82` \x94\x93\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\x93\x81\x86R\x86\x86\x017`\x00\x85\x82\x86\x01\x01R\x01\x16\x01\x01\x90V[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x163\x03a\r\xb9WV[`$`@Q\x7f\x11\x8c\u06a7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R3`\x04\x82\x01R\xfd[\x7f\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\x00\x00\x00\x00\x90\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x82\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x93\x16\x80\x94\x83\x16\x17\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0`\x00\x80\xa3V[`\xff\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00T`@\x1c\x16\x15a\x0e\xccWV[`\x04`@Q\x7f\xd7\xe6\xbc\xf8\x00\x00\x00\x00\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\xfd[\x90a\x0f5WP\x80Q\x15a\x0f\vW\x80Q\x90` \x01\xfd[`\x04`@Q\x7f\x14%\xeaB\x00\x00\x00\x00\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\xfd[\x81Q\x15\x80a\x0f\x8dW[a\x0fFWP\x90V[`$\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7f\x99\x96\xb3\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[P\x80;\x15a\x0f>V\xfe\xa2dipfsX\"\x12 \u07e4\xd0\x04/\xa8\xdf\xf3\xa6\bh\xdaFP\x9b\t\xe5T:\xc3w2\xf4\xc4]]\x82\xc7ye\xc8~dsolcC\x00\b\x17\x003\xc0\xe1\x94\u0293\xa8\xf7\xa3\x97\x1d \x86p\x87b\x02\xd85<\xa3\u0586\x9a\x8b\xa5o\xa5\xb9\x90\x19\xa5\xc8\x00\x00\x00\xf9\x14\x13\x94\xcc\xcc\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\xf9\x13\xfa\x80\xb8\x90`\x80`@Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16`\x00\x80\x80\x926\x82\x807\x816\x91Z\xf4=\x82\x80>\x15`VW=\x90\xf3[=\x90\xfd\xfe\xa2dipfsX\"\x12 \x1c\x13\xa8\xe0u\xfc\uf3f1\xcb\xc5*\xa5\xf7\x98\xf9\x17\x8f\xe4Et]\x01\u02f3\x1dJA dsolcC\x00\b\x17\x003\xf9\x13d\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\x00\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\r\u0db3\xa7d\x00\x00\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\x01\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\r\u0db3\xa7d\x00\x00\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\x02\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\r\u0db3\xa7d\x00\x00\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\x03\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<\xf8B\xa0\nW\x83O)\x90\xba\xb5b\xd0?\u0447[\xd1#hu/\x13_is\x83m\xec\xe2\x9a.\x1d4\xe1\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\x01\xf8B\xa0\nW\x83O)\x90\xba\xb5b\xd0?\u0447[\xd1#hu/\x13_is\x83m\xec\xe2\x9a.\x1d4\xe2\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\x00U\xf8B\xa0\nW\x83O)\x90\xba\xb5b\xd0?\u0447[\xd1#hu/\x13_is\x83m\xec\xe2\x9a.\x1d4\xe3\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\nW\x83O)\x90\xba\xb5b\xd0?\u0447[\xd1#hu/\x13_is\x83m\xec\xe2\x9a.\x1d4\xe4\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\x00\xf8B\xa0\n\xf1\xf9\u06ff\b\x9a\x1a\x12\xd00X\xfa\x05@\x99\xbd\xf4\xde\xc0\x01\xac2Ki6\xcf\x7f\x96[\xbb\u06e00xBC4D18D506439A7AADD7B31113C169\xf8B\xa0\n\xf1\xf9\u06ff\b\x9a\x1a\x12\xd00X\xfa\x05@\x99\xbd\xf4\xde\xc0\x01\xac2Ki6\xcf\x7f\x96[\xbb\u0720E30ECCED20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0\x12}(\x1a}o4\xbb\x970s_o\xba\xc6\u042a\xb4z\xb9jI\x03\xa6R\x0e\x82_\xd9\xcba\u0760\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\x1a\x8a.T\x06\x84L7TB\u052c\xb6\x19p\xe9\xe4\x1a\x17\xc5\xf9m\xc3\x0fsO\xb3\xe4(\x1aL\u02e0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\x1a\xc9\x03R-\v\u0681\xe5\xed\b\x1f\x0f6;\xf4\xedn&\xff$\xc8&k \x90u\xeb\x9e~\xd7\xc1\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\x01\xf8B\xa0\x1a\xc9\x03R-\v\u0681\xe5\xed\b\x1f\x0f6;\xf4\xedn&\xff$\xc8&k \x90u\xeb\x9e~\xd7\u00a0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00U\xf8B\xa0\x1a\xc9\x03R-\v\u0681\xe5\xed\b\x1f\x0f6;\xf4\xedn&\xff$\xc8&k \x90u\xeb\x9e~\xd7\u00e0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\x1a\xc9\x03R-\v\u0681\xe5\xed\b\x1f\x0f6;\xf4\xedn&\xff$\xc8&k \x90u\xeb\x9e~\xd7\u0120\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0\x1b\xcf\xc0.%\xd1\xfe\xad\xfev\x1b\x98\x99\u04ed\x18\x1cS`\xed\xe2\xee\xb6#){\x19\x8fS\x03\x1b\xf6\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\x1e\xe4\xae\x00\xaf\x8e\u05f35(\x0f{r\x0f\xc8\x03\x1cq9&\xe1\x1b\x80\x1e\xdf\xe3T\x9b[Q\nf\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\x01\xf8B\xa0\x1e\xe4\xae\x00\xaf\x8e\u05f35(\x0f{r\x0f\xc8\x03\x1cq9&\xe1\x1b\x80\x1e\xdf\xe3T\x9b[Q\ng\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\x00U\xf8B\xa0\x1e\xe4\xae\x00\xaf\x8e\u05f35(\x0f{r\x0f\xc8\x03\x1cq9&\xe1\x1b\x80\x1e\xdf\xe3T\x9b[Q\nh\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\x1e\xe4\xae\x00\xaf\x8e\u05f35(\x0f{r\x0f\xc8\x03\x1cq9&\xe1\x1b\x80\x1e\xdf\xe3T\x9b[Q\ni\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\x00\xf8B\xa0\x1f\xb1\xfc\xf1\xaf\xa4p\xf2\xa9\u07f0\xe8}\xf7\v}\xce\\\xa2gv\xcc\xdd\xc8L\x94\xdc\x18:\t-\x1f\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\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\x00\xf8B\xa0&8\u0201E \\\xb3\xdf\xddc\x87cA\xa1\u0323\xaf\xab\xd4\x0erp\xf9\x81\x8c+[\x9fF\xdf\xed\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa02\xa9.\x91\xaf\xfa\x10\x0f\xc10\xef\x82|\u010b5/\x1dF\xfc\xac\u030a5]?\x16x\xfcMu\u0320\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa04\xfc\xb1XQ]\x97D\x18\x87\x9c\x97W\xa3`\x91X\xd7\xc4G[x\x89\x9e<\\N\xbb\xaa5\xcc\v\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\x01\xf8B\xa04\xfc\xb1XQ]\x97D\x18\x87\x9c\x97W\xa3`\x91X\xd7\xc4G[x\x89\x9e<\\N\xbb\xaa5\xcc\f\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\x00U\xf8B\xa04\xfc\xb1XQ]\x97D\x18\x87\x9c\x97W\xa3`\x91X\xd7\xc4G[x\x89\x9e<\\N\xbb\xaa5\xcc\r\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa04\xfc\xb1XQ]\x97D\x18\x87\x9c\x97W\xa3`\x91X\xd7\xc4G[x\x89\x9e<\\N\xbb\xaa5\xcc\x0e\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\x00\xf8B\xa06\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00_\xbd\xb21Vx\xaf\xec\xb3g\xf02\xd9?\x00\x00\x00\x00\x00\x00\xf8B\xa09\v\x12\x962\u071c\x80}\xd1\xcdA\xf28B\x1d@B\u0163\xa0\u0344\x05\u052f\x8fE\xe7}\x97\x04\xa00x3D199187D3E34F7D66A414C235581D\xf8B\xa09\v\x12\x962\u071c\x80}\xd1\xcdA\xf28B\x1d@B\u0163\xa0\u0344\x05\u052f\x8fE\xe7}\x97\x05\xa013F0EDD278\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0?%6Z)!X{N^h\x9f\xfb\x8b^\x84\xa7\xa2Z\x01\xb0H\\\x94d\xb7\xfa\xd81\x85~Z\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\x01\xf8B\xa0?%6Z)!X{N^h\x9f\xfb\x8b^\x84\xa7\xa2Z\x01\xb0H\\\x94d\xb7\xfa\xd81\x85~[\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\x00U\xf8B\xa0?%6Z)!X{N^h\x9f\xfb\x8b^\x84\xa7\xa2Z\x01\xb0H\\\x94d\xb7\xfa\xd81\x85~\\\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0?%6Z)!X{N^h\x9f\xfb\x8b^\x84\xa7\xa2Z\x01\xb0H\\\x94d\xb7\xfa\xd81\x85~]\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\x00\xf8B\xa0E\t\xb6M\xb8.\u0175\u83b1\xc8n\xb1\x89\x1d\xf1I\x16o\xad\x0e;\xb4\xb3(\xd4\xec.\u0205\x9e\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0[\x8e\xe9\x91\xe4\nn\x96\xe23\u01d1(\u0744T\x82\x10\xdcI7\x8fZ\x91\x96dA\x9a\x95M\x83a\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0q\xa3N\xe9\xa6g\xa9\xa5\u0518\x7f\x16\xcc\xc3z\xb8\x10\xd89>K\xe1\xbdc\xd0|\x9f\xd7&$\x84\xf1\xa00x2B1CD6524E98D77B0AA40725AE5F12\xf8B\xa0q\xa3N\xe9\xa6g\xa9\xa5\u0518\x7f\x16\xcc\xc3z\xb8\x10\xd89>K\xe1\xbdc\xd0|\x9f\xd7&$\x84\xf2\xa0CEB794A17D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0{\x90Iw8\xdfq\xdd\xe5Z\xf6,\xb5\xb2\x01\x80\xc4\uca24\xd6<\xd7\xf1\xc8\x18'\x96\xed\xad\xa1x\xa00x009100F8C70A1DB3A1D55580A71D8A\xf8B\xa0{\x90Iw8\xdfq\xdd\xe5Z\xf6,\xb5\xb2\x01\x80\xc4\uca24\xd6<\xd7\xf1\xc8\x18'\x96\xed\xad\xa1y\xa05927ACA258\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0|\x10\u66417\xc3\xe6\x8fo\xeb\xd3\u0107\xc7t\xb0NV\xe1\u06e1E\xe3\xe5\x92|\xcd\x16'\xa4\n\xa00x17135610513B956D17D167319AFD14\xf8B\xa0|\x10\u66417\xc3\xe6\x8fo\xeb\xd3\u0107\xc7t\xb0NV\xe1\u06e1E\xe3\xe5\x92|\xcd\x16'\xa4\v\xa08A0000F535\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf8B\xa0\x92\x15k\xeb\xc9y?\v\xb0\x98\xcb\a\xce\xfd0\xe5G}\x85A\xae\x83\x99\xe1<\x9fU\nW\xe9\xca\xeb\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\x94\xadJ\x1f\x9c\x0eC\xea]\x88\x16\xebG\x9f\xa7f\xb8\xf9\x91\xbd\x15\x1b5\xcb\x1b\xd5%\xac]v\xd3C\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\x9bw\x9b\x17B-\r\xf9\"#\x01\x8b2\xb4\xd1\xfaF\xe0qr=h\x17\xe2Hm\x00;\xec\xc5_\x00\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\x01\xf8B\xa0\x9dWY\x86k\x9c\x87\xf2\xa8\xb6\u036eq\xcfK\x92#\xdb\xf2\xd9G\x80`\x1bVz\x88_\xe4\x82m\u03600xFA5A1DF8CE218AC38AF68F4E0112BF\xf8B\xa0\x9dWY\x86k\x9c\x87\xf2\xa8\xb6\u036eq\xcfK\x92#\xdb\xf2\xd9G\x80`\x1bVz\x88_\xe4\x82m\u03a09C1045C94B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0\xa6nj\xed\nC$E\xd8:\xf9\x9f\xd4\x1f\x12\n\x9dz\x16\xd3M\xd5\xddX\xefX\x01\x18\xf2\xc07\xa0\xa00x73768FF04D0455770F888D4A339225\xf8B\xa0\xa6nj\xed\nC$E\xd8:\xf9\x9f\xd4\x1f\x12\n\x9dz\x16\xd3M\xd5\xddX\xefX\x01\x18\xf2\xc07\xa1\xa0C0794F624A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0\xa7\xb8\xd1\x19\x97X\xc1\x81=\xa3N\xcbk\x0e\x1e \x00X\x9fv\xec\xba\xec\xc0\xd9\xdb?\u07da!\x9f\u02e0\x00\x00\x00\x00\x00\x00\x00\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\xf8B\xa0\xa7\xb8\xd1\x19\x97X\xc1\x81=\xa3N\xcbk\x0e\x1e \x00X\x9fv\xec\xba\xec\xc0\xd9\xdb?\u07da!\x9f\u0320\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00U\xf8B\xa0\xa7\xb8\xd1\x19\x97X\xc1\x81=\xa3N\xcbk\x0e\x1e \x00X\x9fv\xec\xba\xec\xc0\xd9\xdb?\u07da!\x9f\u0360\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\xa7\xb8\xd1\x19\x97X\xc1\x81=\xa3N\xcbk\x0e\x1e \x00X\x9fv\xec\xba\xec\xc0\xd9\xdb?\u07da!\x9f\u03a0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0\xa8\xbd\xac\xbda\xe3\xe4\xc1\xc9S~\xb6\xd2\xf6\b\x80\x8e\xa8\x89\xeb{dM\x90\xfex\xfc\xed\xba4c\u0420\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\xae\b\x18J\xb7\x92\xc9\xe5\xb9\xea\x0f\xac\xb35-h\x0e\x13-\x8f\xaf\xff\xf2\x10\x04\x1c\xe8\xd2\x0fR\r\xae\xa00xBEE4E8859692E6D0A0EE0042E6CCFC\xf8B\xa0\xae\b\x18J\xb7\x92\xc9\xe5\xb9\xea\x0f\xac\xb35-h\x0e\x13-\x8f\xaf\xff\xf2\x10\x04\x1c\xe8\xd2\x0fR\r\xaf\xa06B4F1A003D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8B\xa0\xaf\fx\xd4atn\xceD1;\x81\xba\xd8\x1f\xb2\xd7\xe3\x0e\x9a\x16\a*,\x04\u02e9\x94O7\x99\x92\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\x01\xf8B\xa0\xaf\fx\xd4atn\xceD1;\x81\xba\xd8\x1f\xb2\xd7\xe3\x0e\x9a\x16\a*,\x04\u02e9\x94O7\x99\x93\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\x00U\xf8B\xa0\xaf\fx\xd4atn\xceD1;\x81\xba\xd8\x1f\xb2\xd7\xe3\x0e\x9a\x16\a*,\x04\u02e9\x94O7\x99\x94\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\xaf\fx\xd4atn\xceD1;\x81\xba\xd8\x1f\xb2\xd7\xe3\x0e\x9a\x16\a*,\x04\u02e9\x94O7\x99\x95\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\x00\xf8B\xa0\xb51'hJV\x8b1s\xae\x13\xb9\xf8\xa6\x01n$>c\xb6\xe8\xee\x11x\u05a7\x17\x85\v]a\x03\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf8B\xa0\xc1\xc3CH\x17N\xcc?\u48ea\xc1\x92\xa7\x8d\xd3\u07da\xd14\xc9\xf4uz\xe6\xd5\u00cc\xc0S\x86P\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\xccmc]r\x8b\xcf0\xd8\x0e]>\xa1huz=\x04J\x85\xf0\u0325\x86Vv#\xfcM\xa0\xd5\t\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\xdf\xf5xx\xf9.\xb8\x9d\u05f7\x89\x06K\xa0m?$\rOA\xd6\x1f\x94S\x10\xa7ZN6\xaa\x03:\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\xec\x1c\xe2\xab~\x9e\xf9\x8c\xf8\x1c\x8c\x9f\x1f9\x0e\x03\xa3FH`z\xe8B.\xbd\xb9E\x97\xbe\x17\xee&\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\xf2\xc0\xbcC S\xd1\xc9\x01\x99\u0575\n\xd0\xf7\xf4\xa3>y\x96\xbfa\xd0(\xe2.)\xf1\x13\xa5\xd9Z\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\x01\xf8B\xa0\xf2\xc0\xbcC S\xd1\xc9\x01\x99\u0575\n\xd0\xf7\xf4\xa3>y\x96\xbfa\xd0(\xe2.)\xf1\x13\xa5\xd9[\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\x00U\xf8B\xa0\xf2\xc0\xbcC S\xd1\xc9\x01\x99\u0575\n\xd0\xf7\xf4\xa3>y\x96\xbfa\xd0(\xe2.)\xf1\x13\xa5\xd9\\\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf8B\xa0\xf2\xc0\xbcC S\xd1\xc9\x01\x99\u0575\n\xd0\xf7\xf4\xa3>y\x96\xbfa\xd0(\xe2.)\xf1\x13\xa5\xd9]\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\x00\xf8B\xa0\xf4\x8c\xbaF1>\x94\xe6._\xe9\xc0\xc2\xc4r\x91\xfb\x18\xa9x\x81F:\x05mY\x12\rjF\x9a\xb7\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf9\x02\x03\x94\xcc\xcc\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01\xf9\x01\ua038\x90`\x80`@Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16`\x00\x80\x80\x926\x82\x807\x816\x91Z\xf4=\x82\x80>\x15`VW=\x90\xf3[=\x90\xfd\xfe\xa2dipfsX\"\x12 \x1c\x13\xa8\xe0u\xfc\uf3f1\xcb\xc5*\xa5\xf7\x98\xf9\x17\x8f\xe4Et]\x01\u02f3\x1dJA dsolcC\x00\b\x17\x003\xf9\x01T\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\x00\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\r\u0db3\xa7d\x00\x00\xf8B\xa0#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\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\x00\xf8B\xa06\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdcd\xa1@\xaa>\x98\x11\x00\xa9\xbe\xcaNh\x00\x00\x00\x00\x00\x00\xf8B\xa0\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf8B\xa0\xb51'hJV\x8b1s\xae\x13\xb9\xf8\xa6\x01n$>c\xb6\xe8\xee\x11x\u05a7\x17\x85\v]a\x03\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf9\x01\xbf\x94\xcc\xcc\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x01\xf9\x01\xa6\x80\xb8\x90`\x80`@Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16`\x00\x80\x80\x926\x82\x807\x816\x91Z\xf4=\x82\x80>\x15`VW=\x90\xf3[=\x90\xfd\xfe\xa2dipfsX\"\x12 \x1c\x13\xa8\xe0u\xfc\uf3f1\xcb\xc5*\xa5\xf7\x98\xf9\x17\x8f\xe4Et]\x01\u02f3\x1dJA dsolcC\x00\b\x17\x003\xf9\x01\x10\xf8B\xa0#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\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\x00\xf8B\xa06\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa5\x13\xe6\xe4\xb8\xf2\xa9#\u0643\x04\xec\x87\xf6\x00\x00\x00\x00\x00\x00\xf8B\xa0\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xf8B\xa0\xb51'hJV\x8b1s\xae\x13\xb9\xf8\xa6\x01n$>c\xb6\xe8\xee\x11x\u05a7\x17\x85\v]a\x03\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf3\x98\xc1*E\xbc@\x9ble.%\xbb\n>p$\x92\xa4\xab\xe1\x94\xd2`x\xba9\xaf\xcc\xecq\xe0\u058a\x15\x1a\x85=!\x95\x0f\xf0\x8b\xa5o\xa5\xb9\x90\x19\xa5\xc8\x00\x00\x00\xf9\x18:\x94\xdcd\xa1@\xaa>\x98\x11\x00\xa9\xbe\xcaNh\x00\x00\x00\x00\x00\x00\x01\xf9\x18!\x80\xb9\x18\x1c`\x80`@R`\x046\x10\x15a\x00\x12W`\x00\x80\xfd[`\x005`\xe0\x1c\x80c\x04\xffS\xed\x14a\x00\xf7W\x80c\f\x86?w\x14a\x00\xf2W\x80c(\x01\xf1\xec\x14a\x00\xedW\x80c@\xed\xa1J\x14a\x00\xe8W\x80cO\x1e\xf2\x86\x14a\x00\xe3W\x80cR\u0450-\x14a\x00\xdeW\x80cqP\x18\xa6\x14a\x00\xd9W\x80cy\xbaP\x97\x14a\x00\xd4W\x80c\x8d\xa5\xcb[\x14a\x00\xcfW\x80c\xad<\xb1\xcc\x14a\x00\xcaW\x80c\xcdm\u0187\x14a\x00\xc5W\x80c\xe3\f9x\x14a\x00\xc0W\x80c\xe4\xdf\xcc\xd8\x14a\x00\xbbWc\xf2\xfd\xe3\x8b\x14a\x00\xb6W`\x00\x80\xfd[a\v\xdbV[a\n\x85V[a\n2V[a\b?V[a\a\xcaV[a\a\x11V[a\x06\x89V[a\x05\xbeV[a\x05FV[a\x03:V[a\x02\x01V[a\x01\x99V[a\x01MV[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW` `@Qs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe7\xf1r^w4\xce(\x8f\x83g\xe1\xbb\x14>\x90\xbb?\x05\x12\x16\x81R\xf3[`\x00\x80\xfd[4a\x01HW` `\x03\x196\x01\x12a\x01HW\x7f\xea\xc8\x1d\xe2\xf2\x01b\xb0T\f\xa5\xd3\xf48\x96\xaf\x15\xb4q\xa5W)\xff\f\x00\x0ea\x1d\x8b'#c` `\x045a\x01\x8ca\x0f\xe7V[\x80`\x00U`@Q\x90\x81R\xa1\x00[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW` `\x00T`@Q\x90\x81R\xf3[\x90` `\x03\x19\x83\x01\x12a\x01HW`\x045g\xff\xff\xff\xff\xff\xff\xff\xff\x92\x83\x82\x11a\x01HW\x80`#\x83\x01\x12\x15a\x01HW\x81`\x04\x015\x93\x84\x11a\x01HW`$\x84\x83\x01\x01\x11a\x01HW`$\x01\x91\x90V[a\x02/a\x02*a\x02\x106a\x01\xb7V[a\x02#a\x02\x1e6\x83\x85a\x03\x03V[a\x11\x8dV[6\x91a\x03\x03V[a\x12\xf0V[\x00[`\x045\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x82\x03a\x01HWV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`A`\x04R`$`\x00\xfd[\x90`\x1f\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\x91\x01\x16\x81\x01\x90\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\x02\xc4W`@RV[a\x02TV[g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x02\xc4W`\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` \x01\x90V[\x92\x91\x92a\x03\x0f\x82a\x02\xc9V[\x91a\x03\x1d`@Q\x93\x84a\x02\x83V[\x82\x94\x81\x84R\x81\x83\x01\x11a\x01HW\x82\x81` \x93\x84`\x00\x96\x017\x01\x01RV[`@`\x03\x196\x01\x12a\x01HWa\x03Na\x021V[`$5g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x01HW6`#\x82\x01\x12\x15a\x01HWa\x03\x7f\x906\x90`$\x81`\x04\x015\x91\x01a\x03\x03V[\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x91\x82\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf~\u04ec\xcaZF~\x9epLp>\x8d\x87\xf64\xfb\x0f\xc9\x16\x800\x14\x90\x81\x15a\x05\x18W[Pa\x04\xeeW` `\x04\x93a\x03\xd6a\x0f\xe7V[`@Q\x94\x85\x80\x92\x7fR\u0450-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x82R\x86\x16Z\xfa`\x00\x93\x81a\x04\xbdW[Pa\x04YW`@Q\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81Rs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x83\x16`\x04\x82\x01R`$\x90\xfd[\x90\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x83\x03a\x04\x8bWa\x02/\x92Pa\x15\x91V[`@Q\x7f\xaa\x1dI\xa4\x00\x00\x00\x00\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\x81\x01\x84\x90R`$\x90\xfd[a\x04\xe0\x91\x94P` =` \x11a\x04\xe7W[a\x04\u0601\x83a\x02\x83V[\x81\x01\x90a\x13\xd9V[\x928a\x04\rV[P=a\x04\xceV[`\x04`@Q\x7f\xe0|\x8d\xba\x00\x00\x00\x00\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\xfd[\x90P\x83\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbcT\x16\x14\x158a\x03\xc4V[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HWs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf~\u04ec\xcaZF~\x9epLp>\x8d\x87\xf64\xfb\x0f\xc9\x160\x03a\x04\xeeW` `@Q\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81R\xf3[4a\x01HW`\x00\x80`\x03\x196\x01\x12a\x06\x86Wa\x05\xd8a\x0f\xe7V[\x80s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\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\x00\x00\x00\x00\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x81\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x91\x82\x16\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0\x82\x80\xa3\x80\xf3[\x80\xfd[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW3s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16\x03a\x06\xe1Wa\x02/3a\x13\xe8V[`$`@Q\x7f\x11\x8c\u06a7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R3`\x04\x82\x01R\xfd[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW` s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16`@Q\x90\x81R\xf3[`\x00[\x83\x81\x10a\awWPP`\x00\x91\x01RV[\x81\x81\x01Q\x83\x82\x01R` \x01a\agV[\x90\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`\x1f` \x93a\a\u00c1Q\x80\x92\x81\x87R\x87\x80\x88\x01\x91\x01a\adV[\x01\x16\x01\x01\x90V[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW`@Q`@\x81\x01\x90\x80\x82\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x83\x11\x17a\x02\xc4Wa\b;\x91`@R`\x05\x81R\x7f5.0.0\x00\x00\x00\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\x01R`@Q\x91\x82\x91` \x83R` \x83\x01\x90a\a\x87V[\x03\x90\xf3[4a\x01HW`@`\x03\x196\x01\x12a\x01HWa\bXa\x021V[\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00T\x90g\xff\xff\xff\xff\xff\xff\xff\xff`\xff\x83`@\x1c\x16\x15\x92\x16\x80\x15\x90\x81a\n*W[`\x01\x14\x90\x81a\n W[\x15\x90\x81a\n\x17W[Pa\t\xedWa\t\x10\x90\x82a\t\x02\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00`\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\x00\x00\x00\x00\x00\x00\x00\x00\x82T\x16\x17\x90UV[a\t\x91W[`$5\x90a\f\xa8V[a\t\x16W\x00[a\tb\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\x81T\x16\x90UV[`@Q`\x01\x81R\x7f\xc7\xf5\x05\xb2\xf3q\xae!u\xeeI\x13\xf4I\x9e\x1f&3\xa7\xb5\x93c!\xee\xd1\u036e\xb6\x11Q\x81\u0490` \x90\xa1\x00[a\t\xe8\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00h\x01\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\x82T\x16\x17\x90UV[a\t\aV[`\x04`@Q\x7f\xf9.\xe8\xa9\x00\x00\x00\x00\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\xfd[\x90P\x158a\b\xa9V[0;\x15\x91Pa\b\xa1V[\x83\x91Pa\b\x97V[4a\x01HW`\x00`\x03\x196\x01\x12a\x01HW` s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00T\x16`@Q\x90\x81R\xf3[a\n\x8e6a\x01\xb7V[a\n\x9a`A\x82\x14a\r\xd3V[\x80\x15a\v\xd6Wa\n\xee\x7f\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\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\x00\x00\x00\x845\x16\x14a\x0e\x8dV[\x80`\x01\x11a\x01HWa\v&6\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\x83\x01`\x01\x85\x01a\x03\x03V[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81Q` 3\x93\x01 \x16\x03a\vRWa\x02/\x91a\x0f\x18V[`\x84`@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\x82\x01R`/`$\x82\x01R\x7fIPTokenSlashing: Invalid pubkey `D\x82\x01R\x7fderived address\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[a\x0e^V[4a\x01HW` `\x03\x196\x01\x12a\x01HWa\v\xf4a\x021V[a\v\xfca\x0f\xe7V[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x91\x16\x90\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x82\x7f\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\x00\x00\x00\x00\x82T\x16\x17\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x16\x7f8\xd1k\x8c\xac\"\u065f\xc7\xc1$\xb9\xcd\r\xe2\xd3\xfa\x1f\xae\xf4 \xbf\xe7\x91\xd8\xc3b\xd7e\xe2'\x00`\x00\x80\xa3\x00[a\f\xb0a\x16\xabV[a\f\xb8a\x16\xabV[a\f\xc0a\x16\xabV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x81\x16\x15a\r\xa2Wa\f\xe5\x90a\x13\xe8V[\x80\x15a\r\x1eW`\x00\x81\x90U`@Q\x90\x81R\x7f\xea\xc8\x1d\xe2\xf2\x01b\xb0T\f\xa5\xd3\xf48\x96\xaf\x15\xb4q\xa5W)\xff\f\x00\x0ea\x1d\x8b'#c\x90` \x90\xa1V[`\x84`@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\x82\x01R`#`$\x82\x01R\x7fIPTokenSlashing: Invalid unjail `D\x82\x01R\x7ffee\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[`$`@Q\x7f\x1eO\xbd\xf7\x00\x00\x00\x00\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`\x00`\x04\x82\x01R\xfd[\x15a\r\xdaWV[`\x84`@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\x82\x01R`&`$\x82\x01R\x7fIPTokenSlashing: Invalid pubkey `D\x82\x01R\x7flength\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`2`\x04R`$`\x00\xfd[\x15a\x0e\x94WV[`\x84`@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\x82\x01R`&`$\x82\x01R\x7fIPTokenSlashing: Invalid pubkey `D\x82\x01R\x7fprefix\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[a\x0f#\x916\x91a\x03\x03V[\x90a\x0f1`A\x83Q\x14a\x14\x9cV[`\xffa\x0fH\x81`A`!\x86\x01Q\x95\x01Q\x16`\x01\x16\x90V[\x16a\x0f\xc1W\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[a\x0fwa\x15'V[\x90`\x00\x1aa\x0f\x84\x82a\x10'V[S`\x00[` \x81\x10a\x0f\xa2WPa\x0f\xa0\x91\x92Pa\x02*\x81a\x11\x8dV[V[\x80\x84`\x01\x92\x1aa\x0f\xbaa\x0f\xb4\x83a\x15TV[\x85a\x104V[S\x01a\x0f\x88V[\x7f\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00a\x0foV[s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00T\x163\x03a\x06\xe1WV[\x80Q\x15a\v\xd6W` \x01\x90V[\x90\x81Q\x81\x10\x15a\v\xd6W\x01` \x01\x90V[Q\x90c\xff\xff\xff\xff\x82\x16\x82\x03a\x01HWV[\x91\x90\x91`\xc0\x81\x84\x03\x12a\x01HW\x80Q\x80\x15\x15\x81\x03a\x01HW\x92` \x82\x01Qg\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11a\x01HW\x82\x01\x81`\x1f\x82\x01\x12\x15a\x01HW\x80Qa\x10\x9b\x81a\x02\xc9V[\x92a\x10\xa9`@Q\x94\x85a\x02\x83V[\x81\x84R` \x82\x84\x01\x01\x11a\x01HWa\x10\u01d1` \x80\x85\x01\x91\x01a\adV[\x91`@\x82\x01Q\x91a\x10\xda``\x82\x01a\x10EV[\x91a\x10\xf3`\xa0a\x10\xec`\x80\x85\x01a\x10EV[\x93\x01a\x10EV[\x90V[`@Q=`\x00\x82>=\x90\xfd[\x15a\x11\tWV[`\x84`@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\x82\x01R`)`$\x82\x01R\x7fIPTokenSlashing: Validator does `D\x82\x01R\x7fnot exist\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[`\x00a\x12;\x91a\x11\xa0`!\x82Q\x14a\r\xd3V[a\x12\x00\x7f\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\x00\x00\x00\x7f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81a\x11\xef\x85a\x10'V[Q\x16\x14\x90\x81\x15a\x12\xbbW[Pa\x0e\x8dV[`@Q\x80\x93\x81\x92\x7f\x8d>\x1eA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R` `\x04\x84\x01R`$\x83\x01\x90a\a\x87V[\x03\x81s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe7\xf1r^w4\xce(\x8f\x83g\xe1\xbb\x14>\x90\xbb?\x05\x12\x16Z\xfa\x80\x15a\x12\xb6Wa\x0f\xa0\x91`\x00\x91a\x12\x8eW[Pa\x11\x02V[a\x12\xab\x91P=\x80`\x00\x83>a\x12\xa3\x81\x83a\x02\x83V[\x81\x01\x90a\x10VV[PPPPP8a\x12\x88V[a\x10\xf6V[\x7f\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x91Pa\x12\xe7\x84a\x10'V[Q\x16\x148a\x11\xfaV[`\x00\x80T4\x03a\x13UW\x80\x804\x15a\x13LW[\x81\x80\x91\x814\x91\xf1\x15a\x12\xb6W\x7fJ\x90\xea2R~\xca\xcc\x0fK2\xb3\x1f\x99\xe4\xc63\xa2\xb4\xfe\x81\xeatD\x98\x9e.h\xbc\x9e\xce;`@Q` \x81R\x80a\x13G3\x94` \x83\x01\x90a\a\x87V[\x03\x90\xa2V[Pa\b\xfca\x13\x03V[`\x84`@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\x82\x01R`!`$\x82\x01R\x7fIPTokenSlashing: Insufficient fe`D\x82\x01R\x7fe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[\x90\x81` \x91\x03\x12a\x01HWQ\x90V[\x7f\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\x00\x00\x00\x00\x90\x7f#~\x15\x82\"\xe3\u658br\xb9\xdb\r\x80C\xaa\xcf\aJ\xd9\xf6P\xf0\xd1`kM\x82\xeeC,\x00\x82\x81T\x16\x90U\x7f\x90\x16\u041dr\xd4\x0f\xda\xe2\xfd\x8c\xea\u01b6#Lw\x06!O\u04dc\x1c\xd1\xe6\t\xa0R\x8c\x19\x93\x00\x80T\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x80\x93\x16\x80\x94\x83\x16\x17\x90U\x16\x7f\x8b\xe0\a\x9cS\x16Y\x14\x13D\xcd\x1f\u0424\xf2\x84\x19I\x7f\x97\"\xa3\u06af\xe3\xb4\x18okdW\xe0`\x00\x80\xa3V[\x15a\x14\xa3WV[`\x84`@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\x82\x01R`&`$\x82\x01R\x7fInvalid uncompressed public key `D\x82\x01R\x7flength\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`d\x82\x01R\xfd[`@Q\x90``\x82\x01\x82\x81\x10g\xff\xff\xff\xff\xff\xff\xff\xff\x82\x11\x17a\x02\xc4W`@R`!\x82R`@\x82` 6\x91\x017V[\x90`\x01\x82\x01\x80\x92\x11a\x15bWV[\x7fNH{q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00R`\x11`\x04R`$`\x00\xfd[\x90\x81;\x15a\x16dWs\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x82\x16\x7f6\b\x94\xa1;\xa1\xa3!\x06g\xc8(I-\xb9\x8d\xca> v\xcc75\xa9 \xa3\xcaP]8+\xbc\x81\x7f\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\x00\x00\x00\x00\x82T\x16\x17\x90U\x7f\xbc|\xd7Z \xee'\xfd\x9a\u07ba\xb3 A\xf7U!M\xbck\xff\xa9\f\xc0\"[9\xda.\\-;`\x00\x80\xa2\x80Q\x15a\x161Wa\x16.\x91a\x17\x04V[PV[PP4a\x16:WV[`\x04`@Q\x7f\xb3\x98\x97\x9f\x00\x00\x00\x00\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\xfd[`$\x82s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7fL\x9c\x8c\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[`\xff\x7f\xf0\xc5~\x16\x84\r\xf0@\xf1P\x88\xdc/\x81\xfe9\x1c9#\xbe\xc7>#\xa9f.\xfc\x9c\"\x9cj\x00T`@\x1c\x16\x15a\x16\xdaWV[`\x04`@Q\x7f\xd7\xe6\xbc\xf8\x00\x00\x00\x00\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\xfd[`\x00\x80a\x10\xf3\x93` \x81Q\x91\x01\x84Z\xf4=\x15a\x17BW=\x91a\x17%\x83a\x02\xc9V[\x92a\x173`@Q\x94\x85a\x02\x83V[\x83R=`\x00` \x85\x01>a\x17FV[``\x91[\x90a\x17\x85WP\x80Q\x15a\x17[W\x80Q\x90` \x01\xfd[`\x04`@Q\x7f\x14%\xeaB\x00\x00\x00\x00\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\xfd[\x81Q\x15\x80a\x17\xddW[a\x17\x96WP\x90V[`$\x90s\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff`@Q\x91\x7f\x99\x96\xb3\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x83R\x16`\x04\x82\x01R\xfd[P\x80;\x15a\x17\x8eV\xfe\xa2dipfsX\"\x12 B\xfe]3\u063d\x9dT\x00\xcc\x11+\x98\xf8\x9b\xe4\xc0U$\xeb7\x97'\xc7\xdc=E\x16u\xa5. + +package core + +import ( + "bytes" + "encoding/json" + "math/big" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/pathdb" +) + +func TestInvalidCliqueConfig(t *testing.T) { + block := DefaultGoerliGenesisBlock() + block.ExtraData = []byte{} + db := rawdb.NewMemoryDatabase() + if _, err := block.Commit(db, triedb.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{ + Config: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3)}, + Alloc: types.GenesisAlloc{ + {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, + }, + } + oldcustomg = customg + ) + oldcustomg.Config = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(2)} + + tests := []struct { + name string + fn func(ethdb.Database) (*params.ChainConfig, common.Hash, error) + wantConfig *params.ChainConfig + wantHash common.Hash + wantErr error + }{ + { + name: "genesis without ChainConfig", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + return SetupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), new(Genesis)) + }, + wantErr: errGenesisNoConfig, + wantConfig: params.AllEthashProtocolChanges, + }, + { + name: "no block in DB, genesis == nil", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + return SetupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), nil) + }, + wantHash: params.MainnetGenesisHash, + wantConfig: params.MainnetChainConfig, + }, + { + name: "mainnet block in DB, genesis == nil", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + DefaultGenesisBlock().MustCommit(db, triedb.NewDatabase(db, newDbConfig(scheme))) + return SetupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), nil) + }, + wantHash: params.MainnetGenesisHash, + wantConfig: params.MainnetChainConfig, + }, + { + name: "custom block in DB, genesis == nil", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + tdb := triedb.NewDatabase(db, newDbConfig(scheme)) + customg.Commit(db, tdb) + return SetupGenesisBlock(db, tdb, nil) + }, + wantHash: customghash, + wantConfig: customg.Config, + }, + { + name: "custom block in DB, genesis == goerli", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + tdb := triedb.NewDatabase(db, newDbConfig(scheme)) + customg.Commit(db, tdb) + return SetupGenesisBlock(db, tdb, DefaultGoerliGenesisBlock()) + }, + wantErr: &GenesisMismatchError{Stored: customghash, New: params.GoerliGenesisHash}, + wantHash: params.GoerliGenesisHash, + wantConfig: params.GoerliChainConfig, + }, + { + name: "custom block in DB, genesis == iliad", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + tdb := triedb.NewDatabase(db, newDbConfig(scheme)) + customg.Commit(db, tdb) + return SetupGenesisBlock(db, tdb, DefaultIliadGenesisBlock()) + }, + wantErr: &GenesisMismatchError{Stored: customghash, New: params.IliadGenesisHash}, + wantHash: params.IliadGenesisHash, + wantConfig: params.IliadChainConfig, + }, + { + name: "custom block in DB, genesis == local", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + tdb := triedb.NewDatabase(db, newDbConfig(scheme)) + customg.Commit(db, tdb) + return SetupGenesisBlock(db, tdb, DefaultLocalGenesisBlock()) + }, + wantErr: &GenesisMismatchError{Stored: customghash, New: params.LocalGenesisHash}, + wantHash: params.LocalGenesisHash, + wantConfig: params.LocalChainConfig, + }, + { + name: "compatible config in DB", + fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { + tdb := triedb.NewDatabase(db, newDbConfig(scheme)) + oldcustomg.Commit(db, tdb) + return SetupGenesisBlock(db, tdb, &customg) + }, + wantHash: customghash, + wantConfig: customg.Config, + }, + { + name: "incompatible config in DB", + 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. + tdb := triedb.NewDatabase(db, newDbConfig(scheme)) + oldcustomg.Commit(db, tdb) + + bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) + defer bc.Stop() + + _, blocks, _ := GenerateChainWithGenesis(&oldcustomg, ethash.NewFaker(), 4, nil) + bc.InsertChain(blocks) + + // This should return a compatibility error. + return SetupGenesisBlock(db, tdb, &customg) + }, + wantHash: customghash, + wantConfig: customg.Config, + wantErr: ¶ms.ConfigCompatError{ + What: "Homestead fork block", + StoredBlock: big.NewInt(2), + NewBlock: big.NewInt(3), + RewindToBlock: 1, + }, + }, + } + + for _, test := range tests { + db := rawdb.NewMemoryDatabase() + config, hash, err := test.fn(db) + // Check the return values. + if !reflect.DeepEqual(err, test.wantErr) { + spew := spew.ConfigState{DisablePointerAddresses: true, DisableCapacities: true} + t.Errorf("%s: returned error %#v, want %#v", test.name, spew.NewFormatter(err), spew.NewFormatter(test.wantErr)) + } + if !reflect.DeepEqual(config, test.wantConfig) { + t.Errorf("%s:\nreturned %v\nwant %v", test.name, config, test.wantConfig) + } + if hash != test.wantHash { + t.Errorf("%s: returned hash %s, want %s", test.name, hash.Hex(), test.wantHash.Hex()) + } else if err == nil { + // Check database content. + stored := rawdb.ReadBlock(db, test.wantHash, 0) + if stored.Hash() != test.wantHash { + t.Errorf("%s: block in DB has hash %s, want %s", test.name, stored.Hash(), test.wantHash) + } + } + } +} + +// TestGenesisHashes checks the congruity of default genesis data to +// corresponding hardcoded genesis hash values. +func TestGenesisHashes(t *testing.T) { + for i, c := range []struct { + genesis *Genesis + want common.Hash + }{ + {DefaultGenesisBlock(), params.MainnetGenesisHash}, + {DefaultGoerliGenesisBlock(), params.GoerliGenesisHash}, + {DefaultSepoliaGenesisBlock(), params.SepoliaGenesisHash}, + {DefaultIliadGenesisBlock(), params.IliadGenesisHash}, + {DefaultLocalGenesisBlock(), params.LocalGenesisHash}, + } { + // Test via MustCommit + db := rawdb.NewMemoryDatabase() + if have := c.genesis.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)).Hash(); have != c.want { + t.Errorf("case: %d a), want: %s, got: %s", i, c.want.Hex(), have.Hex()) + } + // Test via ToBlock + if have := c.genesis.ToBlock().Hash(); have != c.want { + t.Errorf("case: %d a), want: %s, got: %s", i, c.want.Hex(), have.Hex()) + } + } +} + +func TestGenesis_Commit(t *testing.T) { + genesis := &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.TestChainConfig, + // difficulty is nil + } + + db := rawdb.NewMemoryDatabase() + genesisBlock := genesis.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)) + + if genesis.Difficulty != nil { + t.Fatalf("assumption wrong") + } + + // This value should have been set as default in the ToBlock method. + if genesisBlock.Difficulty().Cmp(params.GenesisDifficulty) != 0 { + t.Errorf("assumption wrong: want: %d, got: %v", params.GenesisDifficulty, genesisBlock.Difficulty()) + } + + // Expect the stored total difficulty to be the difficulty of the genesis block. + stored := rawdb.ReadTd(db, genesisBlock.Hash(), genesisBlock.NumberU64()) + + if stored.Cmp(genesisBlock.Difficulty()) != 0 { + t.Errorf("inequal difficulty; stored: %v, genesisBlock: %v", stored, genesisBlock.Difficulty()) + } +} + +func TestReadWriteGenesisAlloc(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + alloc = &types.GenesisAlloc{ + {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, _ = hashAlloc(alloc, false) + ) + blob, _ := json.Marshal(alloc) + rawdb.WriteGenesisStateSpec(db, hash, blob) + + var reload types.GenesisAlloc + err := reload.UnmarshalJSON(rawdb.ReadGenesisStateSpec(db, hash)) + if err != nil { + t.Fatalf("Failed to load genesis state %v", err) + } + if len(reload) != len(*alloc) { + t.Fatal("Unexpected genesis allocation") + } + for addr, account := range reload { + want, ok := (*alloc)[addr] + if !ok { + t.Fatal("Account is not found") + } + if !reflect.DeepEqual(want, account) { + t.Fatal("Unexpected account") + } + } +} + +func newDbConfig(scheme string) *triedb.Config { + if scheme == rawdb.HashScheme { + return triedb.HashDefaults + } + return &triedb.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: types.GenesisAlloc{ + {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, + }, + } + + expected := common.FromHex("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 := triedb.NewDatabase(db, &triedb.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, block.Root()) + } + + // Test that the trie is verkle + if !triedb.IsVerkle() { + t.Fatalf("expected trie to be verkle") + } + + if !rawdb.HasAccountTrieNode(db, nil) { + t.Fatal("could not find node") + } +} diff --git a/core/headerchain.go b/core/headerchain.go new file mode 100644 index 0000000..9ce8d11 --- /dev/null +++ b/core/headerchain.go @@ -0,0 +1,662 @@ +// 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 core + +import ( + "errors" + "fmt" + "math/big" + "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/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" +) + +const ( + headerCacheLimit = 512 + tdCacheLimit = 1024 + numberCacheLimit = 2048 +) + +// HeaderChain implements the basic block header chain logic. It is not usable +// in itself, but rather an internal structure of core.Blockchain. +// +// HeaderChain is responsible for maintaining the header chain including the +// header query and updating. +// +// The data components maintained by HeaderChain include: +// +// - total difficulty +// - header +// - block hash -> number mapping +// - canonical number -> hash mapping +// - head header flag. +// +// It is not thread safe, the encapsulating chain structures should do the +// necessary mutex locking/unlocking. +type HeaderChain struct { + config *params.ChainConfig + chainDb ethdb.Database + genesisHeader *types.Header + + currentHeader atomic.Pointer[types.Header] // Current head of the header chain (maybe above the block chain!) + currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time) + + headerCache *lru.Cache[common.Hash, *types.Header] + tdCache *lru.Cache[common.Hash, *big.Int] // most recent total difficulties + numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers + + procInterrupt func() bool + engine consensus.Engine +} + +// NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points +// to the parent's interrupt semaphore. +func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) { + hc := &HeaderChain{ + config: config, + chainDb: chainDb, + headerCache: lru.NewCache[common.Hash, *types.Header](headerCacheLimit), + tdCache: lru.NewCache[common.Hash, *big.Int](tdCacheLimit), + numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit), + procInterrupt: procInterrupt, + engine: engine, + } + hc.genesisHeader = hc.GetHeaderByNumber(0) + if hc.genesisHeader == nil { + return nil, ErrNoGenesis + } + hc.currentHeader.Store(hc.genesisHeader) + if head := rawdb.ReadHeadBlockHash(chainDb); head != (common.Hash{}) { + if chead := hc.GetHeaderByHash(head); chead != nil { + hc.currentHeader.Store(chead) + } + } + hc.currentHeaderHash = hc.CurrentHeader().Hash() + headHeaderGauge.Update(hc.CurrentHeader().Number.Int64()) + return hc, nil +} + +// GetBlockNumber retrieves the block number belonging to the given hash +// from the cache or database +func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 { + if cached, ok := hc.numberCache.Get(hash); ok { + return &cached + } + number := rawdb.ReadHeaderNumber(hc.chainDb, hash) + if number != nil { + hc.numberCache.Add(hash, *number) + } + return number +} + +type headerWriteResult struct { + status WriteStatus + ignored int + imported int + lastHash common.Hash + lastHeader *types.Header +} + +// Reorg reorgs the local canonical chain into the specified chain. The reorg +// can be classified into two cases: (a) extend the local chain (b) switch the +// head to the given header. +func (hc *HeaderChain) Reorg(headers []*types.Header) error { + // Short circuit if nothing to reorg. + if len(headers) == 0 { + return nil + } + // If the parent of the (first) block is already the canon header, + // we don't have to go backwards to delete canon blocks, but simply + // pile them onto the existing chain. Otherwise, do the necessary + // reorgs. + var ( + first = headers[0] + last = headers[len(headers)-1] + batch = hc.chainDb.NewBatch() + ) + if first.ParentHash != hc.currentHeaderHash { + // Delete any canonical number assignments above the new head + for i := last.Number.Uint64() + 1; ; i++ { + hash := rawdb.ReadCanonicalHash(hc.chainDb, i) + if hash == (common.Hash{}) { + break + } + rawdb.DeleteCanonicalHash(batch, i) + } + // Overwrite any stale canonical number assignments, going + // backwards from the first header in this import until the + // cross link between two chains. + var ( + header = first + headNumber = header.Number.Uint64() + headHash = header.Hash() + ) + for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { + rawdb.WriteCanonicalHash(batch, headHash, headNumber) + if headNumber == 0 { + break // It shouldn't be reached + } + headHash, headNumber = header.ParentHash, header.Number.Uint64()-1 + header = hc.GetHeader(headHash, headNumber) + if header == nil { + return fmt.Errorf("missing parent %d %x", headNumber, headHash) + } + } + } + // Extend the canonical chain with the new headers + for i := 0; i < len(headers)-1; i++ { + hash := headers[i+1].ParentHash // Save some extra hashing + num := headers[i].Number.Uint64() + rawdb.WriteCanonicalHash(batch, hash, num) + rawdb.WriteHeadHeaderHash(batch, hash) + } + // Write the last header + hash := headers[len(headers)-1].Hash() + num := headers[len(headers)-1].Number.Uint64() + rawdb.WriteCanonicalHash(batch, hash, num) + rawdb.WriteHeadHeaderHash(batch, hash) + + if err := batch.Write(); err != nil { + return err + } + // Last step update all in-memory head header markers + hc.currentHeaderHash = last.Hash() + hc.currentHeader.Store(types.CopyHeader(last)) + headHeaderGauge.Update(last.Number.Int64()) + return nil +} + +// WriteHeaders writes a chain of headers into the local chain, given that the +// parents are already known. The chain head header won't be updated in this +// function, the additional SetCanonical is expected in order to finish the entire +// procedure. +func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) { + if len(headers) == 0 { + return 0, nil + } + ptd := hc.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1) + if ptd == nil { + return 0, consensus.ErrUnknownAncestor + } + var ( + newTD = new(big.Int).Set(ptd) // Total difficulty of inserted chain + inserted []rawdb.NumberHash // Ephemeral lookup of number/hash for the chain + parentKnown = true // Set to true to force hc.HasHeader check the first iteration + batch = hc.chainDb.NewBatch() + ) + for i, header := range headers { + var hash common.Hash + // The headers have already been validated at this point, so we already + // know that it's a contiguous chain, where + // headers[i].Hash() == headers[i+1].ParentHash + if i < len(headers)-1 { + hash = headers[i+1].ParentHash + } else { + hash = header.Hash() + } + number := header.Number.Uint64() + newTD.Add(newTD, header.Difficulty) + + // If the parent was not present, store it + // If the header is already known, skip it, otherwise store + alreadyKnown := parentKnown && hc.HasHeader(hash, number) + if !alreadyKnown { + // Irrelevant of the canonical status, write the TD and header to the database. + rawdb.WriteTd(batch, hash, number, newTD) + hc.tdCache.Add(hash, new(big.Int).Set(newTD)) + + rawdb.WriteHeader(batch, header) + inserted = append(inserted, rawdb.NumberHash{Number: number, Hash: hash}) + hc.headerCache.Add(hash, header) + hc.numberCache.Add(hash, number) + } + parentKnown = alreadyKnown + } + // Skip the slow disk write of all headers if interrupted. + if hc.procInterrupt() { + log.Debug("Premature abort during headers import") + return 0, errors.New("aborted") + } + // Commit to disk! + if err := batch.Write(); err != nil { + log.Crit("Failed to write headers", "error", err) + } + return len(inserted), nil +} + +// writeHeadersAndSetHead writes a batch of block headers and applies the last +// header as the chain head if the fork choicer says it's ok to update the chain. +// Note: This method is not concurrent-safe with inserting blocks simultaneously +// into the chain, as side effects caused by reorganisations cannot be emulated +// without the real blocks. Hence, writing headers directly should only be done +// in two scenarios: pure-header mode of operation (light clients), or properly +// separated header/block phases (non-archive clients). +func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *ForkChoice) (*headerWriteResult, error) { + inserted, err := hc.WriteHeaders(headers) + if err != nil { + return nil, err + } + var ( + lastHeader = headers[len(headers)-1] + lastHash = headers[len(headers)-1].Hash() + result = &headerWriteResult{ + status: NonStatTy, + ignored: len(headers) - inserted, + imported: inserted, + lastHash: lastHash, + lastHeader: lastHeader, + } + ) + // Ask the fork choicer if the reorg is necessary + if reorg, err := forker.ReorgNeeded(hc.CurrentHeader(), lastHeader); err != nil { + return nil, err + } else if !reorg { + if inserted != 0 { + result.status = SideStatTy + } + return result, nil + } + // Special case, all the inserted headers are already on the canonical + // header chain, skip the reorg operation. + if hc.GetCanonicalHash(lastHeader.Number.Uint64()) == lastHash && lastHeader.Number.Uint64() <= hc.CurrentHeader().Number.Uint64() { + return result, nil + } + // Apply the reorg operation + if err := hc.Reorg(headers); err != nil { + return nil, err + } + result.status = CanonStatTy + return result, nil +} + +func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header) (int, error) { + // Do a sanity check that the provided chain is actually ordered and linked + for i := 1; i < len(chain); i++ { + if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 { + hash := chain[i].Hash() + parentHash := chain[i-1].Hash() + // Chain broke ancestry, log a message (programming error) and skip insertion + log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", hash, + "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", parentHash) + + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, chain[i-1].Number, + parentHash.Bytes()[:4], i, chain[i].Number, hash.Bytes()[:4], chain[i].ParentHash[:4]) + } + } + // Start the parallel verifier + abort, results := hc.engine.VerifyHeaders(hc, chain) + defer close(abort) + + // Iterate over the headers and ensure they all check out + for i := range chain { + // If the chain is terminating, stop processing blocks + if hc.procInterrupt() { + log.Debug("Premature abort during headers verification") + return 0, errors.New("aborted") + } + // Otherwise wait for headers checks and ensure they pass + if err := <-results; err != nil { + return i, err + } + } + + return 0, nil +} + +// InsertHeaderChain inserts the given headers and does the reorganisations. +// +// The validity of the headers is NOT CHECKED by this method, i.e. they need to be +// validated by ValidateHeaderChain before calling InsertHeaderChain. +// +// This insert is all-or-nothing. If this returns an error, no headers were written, +// otherwise they were all processed successfully. +// +// The returned 'write status' says if the inserted headers are part of the canonical chain +// or a side chain. +func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time, forker *ForkChoice) (WriteStatus, error) { + if hc.procInterrupt() { + return 0, errors.New("aborted") + } + res, err := hc.writeHeadersAndSetHead(chain, forker) + if err != nil { + return 0, err + } + // Report some public statistics so the user has a clue what's going on + context := []interface{}{ + "count", res.imported, + "elapsed", common.PrettyDuration(time.Since(start)), + } + if last := res.lastHeader; last != nil { + context = append(context, "number", last.Number, "hash", res.lastHash) + if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute { + context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) + } + } + if res.ignored > 0 { + context = append(context, []interface{}{"ignored", res.ignored}...) + } + log.Debug("Imported new block headers", context...) + return res.status, err +} + +// 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 (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) { + if ancestor > number { + return common.Hash{}, 0 + } + if ancestor == 1 { + // in this case it is cheaper to just read the header + if header := hc.GetHeader(hash, number); header != nil { + return header.ParentHash, number - 1 + } + return common.Hash{}, 0 + } + for ancestor != 0 { + if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash { + ancestorHash := rawdb.ReadCanonicalHash(hc.chainDb, number-ancestor) + if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash { + number -= ancestor + return ancestorHash, number + } + } + if *maxNonCanonical == 0 { + return common.Hash{}, 0 + } + *maxNonCanonical-- + ancestor-- + header := hc.GetHeader(hash, number) + if header == nil { + return common.Hash{}, 0 + } + hash = header.ParentHash + number-- + } + return hash, number +} + +// GetTd retrieves a block's total difficulty in the canonical chain from the +// database by hash and number, caching it if found. +func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int { + // Short circuit if the td's already in the cache, retrieve otherwise + if cached, ok := hc.tdCache.Get(hash); ok { + return cached + } + td := rawdb.ReadTd(hc.chainDb, hash, number) + if td == nil { + return nil + } + // Cache the found body for next time and return + hc.tdCache.Add(hash, td) + return td +} + +// GetHeader retrieves a block header from the database by hash and number, +// caching it if found. +func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header { + // Short circuit if the header's already in the cache, retrieve otherwise + if header, ok := hc.headerCache.Get(hash); ok { + return header + } + header := rawdb.ReadHeader(hc.chainDb, hash, number) + if header == nil { + return nil + } + // Cache the found header for next time and return + hc.headerCache.Add(hash, header) + return header +} + +// GetHeaderByHash retrieves a block header from the database by hash, caching it if +// found. +func (hc *HeaderChain) GetHeaderByHash(hash common.Hash) *types.Header { + number := hc.GetBlockNumber(hash) + if number == nil { + return nil + } + return hc.GetHeader(hash, *number) +} + +// HasHeader checks if a block header is present in the database or not. +// In theory, if header is present in the database, all relative components +// like td and hash->number should be present too. +func (hc *HeaderChain) HasHeader(hash common.Hash, number uint64) bool { + if hc.numberCache.Contains(hash) || hc.headerCache.Contains(hash) { + return true + } + return rawdb.HasHeader(hc.chainDb, hash, number) +} + +// GetHeaderByNumber retrieves a block header from the database by number, +// caching it (associated with its hash) if found. +func (hc *HeaderChain) GetHeaderByNumber(number uint64) *types.Header { + hash := rawdb.ReadCanonicalHash(hc.chainDb, number) + if hash == (common.Hash{}) { + return nil + } + return hc.GetHeader(hash, number) +} + +// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going +// backwards from the given number. +// If the 'number' is higher than the highest local header, this method will +// return a best-effort response, containing the headers that we do have. +func (hc *HeaderChain) GetHeadersFrom(number, count uint64) []rlp.RawValue { + // If the request is for future headers, we still return the portion of + // headers that we are able to serve + if current := hc.CurrentHeader().Number.Uint64(); current < number { + if count > number-current { + count -= number - current + number = current + } else { + return nil + } + } + var headers []rlp.RawValue + // If we have some of the headers in cache already, use that before going to db. + hash := rawdb.ReadCanonicalHash(hc.chainDb, number) + if hash == (common.Hash{}) { + return nil + } + for count > 0 { + header, ok := hc.headerCache.Get(hash) + if !ok { + break + } + rlpData, _ := rlp.EncodeToBytes(header) + headers = append(headers, rlpData) + hash = header.ParentHash + count-- + number-- + } + // Read remaining from db + if count > 0 { + headers = append(headers, rawdb.ReadHeaderRange(hc.chainDb, number, count)...) + } + return headers +} + +func (hc *HeaderChain) GetCanonicalHash(number uint64) common.Hash { + return rawdb.ReadCanonicalHash(hc.chainDb, number) +} + +// CurrentHeader retrieves the current head header of the canonical chain. The +// header is retrieved from the HeaderChain's internal cache. +func (hc *HeaderChain) CurrentHeader() *types.Header { + return hc.currentHeader.Load() +} + +// SetCurrentHeader sets the in-memory head header marker of the canonical chan +// as the given header. +func (hc *HeaderChain) SetCurrentHeader(head *types.Header) { + hc.currentHeader.Store(head) + hc.currentHeaderHash = head.Hash() + headHeaderGauge.Update(head.Number.Int64()) +} + +type ( + // UpdateHeadBlocksCallback is a callback function that is called by SetHead + // before head header is updated. The method will return the actual block it + // updated the head to (missing state) and a flag if setHead should continue + // rewinding till that forcefully (exceeded ancient limits) + UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (*types.Header, bool) + + // DeleteBlockContentCallback is a callback function that is called by SetHead + // before each header is deleted. + DeleteBlockContentCallback func(ethdb.KeyValueWriter, common.Hash, uint64) +) + +// SetHead rewinds the local chain to a new head. Everything above the new head +// will be deleted and the new one set. +func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) { + hc.setHead(head, 0, updateFn, delFn) +} + +// SetHeadWithTimestamp rewinds the local chain to a new head timestamp. Everything +// above the new head will be deleted and the new one set. +func (hc *HeaderChain) SetHeadWithTimestamp(time uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) { + hc.setHead(0, time, updateFn, delFn) +} + +// setHead rewinds the local chain to a new head block or a head timestamp. +// Everything above the new head will be deleted and the new one set. +func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) { + // Sanity check that there's no attempt to undo the genesis block. This is + // a fairly synthetic case where someone enables a timestamp based fork + // below the genesis timestamp. It's nice to not allow that instead of the + // entire chain getting deleted. + if headTime > 0 && hc.genesisHeader.Time > headTime { + // Note, a critical error is quite brutal, but we should really not reach + // this point. Since pre-timestamp based forks it was impossible to have + // a fork before block 0, the setHead would always work. With timestamp + // forks it becomes possible to specify below the genesis. That said, the + // only time we setHead via timestamp is with chain config changes on the + // startup, so failing hard there is ok. + log.Crit("Rejecting genesis rewind via timestamp", "target", headTime, "genesis", hc.genesisHeader.Time) + } + var ( + parentHash common.Hash + batch = hc.chainDb.NewBatch() + origin = true + ) + done := func(header *types.Header) bool { + if headTime > 0 { + return header.Time <= headTime + } + return header.Number.Uint64() <= headBlock + } + for hdr := hc.CurrentHeader(); hdr != nil && !done(hdr); hdr = hc.CurrentHeader() { + num := hdr.Number.Uint64() + + // Rewind chain to new head + parent := hc.GetHeader(hdr.ParentHash, num-1) + if parent == nil { + parent = hc.genesisHeader + } + parentHash = parent.Hash() + + // Notably, since geth has the possibility for setting the head to a low + // height which is even lower than ancient head. + // In order to ensure that the head is always no higher than the data in + // the database (ancient store or active store), we need to update head + // first then remove the relative data from the database. + // + // Update head first(head fast block, head full block) before deleting the data. + markerBatch := hc.chainDb.NewBatch() + if updateFn != nil { + newHead, force := updateFn(markerBatch, parent) + if force && ((headTime > 0 && newHead.Time < headTime) || (headTime == 0 && newHead.Number.Uint64() < headBlock)) { + log.Warn("Force rewinding till ancient limit", "head", newHead.Number.Uint64()) + headBlock, headTime = newHead.Number.Uint64(), 0 // Target timestamp passed, continue rewind in block mode (cleaner) + } + } + // Update head header then. + rawdb.WriteHeadHeaderHash(markerBatch, parentHash) + if err := markerBatch.Write(); err != nil { + log.Crit("Failed to update chain markers", "error", err) + } + hc.currentHeader.Store(parent) + hc.currentHeaderHash = parentHash + headHeaderGauge.Update(parent.Number.Int64()) + + // If this is the first iteration, wipe any leftover data upwards too so + // we don't end up with dangling daps in the database + var nums []uint64 + if origin { + for n := num + 1; len(rawdb.ReadAllHashes(hc.chainDb, n)) > 0; n++ { + nums = append([]uint64{n}, nums...) // suboptimal, but we don't really expect this path + } + origin = false + } + nums = append(nums, num) + + // Remove the related data from the database on all sidechains + for _, num := range nums { + // Gather all the side fork hashes + hashes := rawdb.ReadAllHashes(hc.chainDb, num) + if len(hashes) == 0 { + // No hashes in the database whatsoever, probably frozen already + hashes = append(hashes, hdr.Hash()) + } + for _, hash := range hashes { + if delFn != nil { + delFn(batch, hash, num) + } + rawdb.DeleteHeader(batch, hash, num) + rawdb.DeleteTd(batch, hash, num) + } + rawdb.DeleteCanonicalHash(batch, num) + } + } + // Flush all accumulated deletions. + if err := batch.Write(); err != nil { + log.Crit("Failed to rewind block", "error", err) + } + // Clear out any stale content from the caches + hc.headerCache.Purge() + hc.tdCache.Purge() + hc.numberCache.Purge() +} + +// SetGenesis sets a new genesis block header for the chain +func (hc *HeaderChain) SetGenesis(head *types.Header) { + hc.genesisHeader = head +} + +// Config retrieves the header chain's chain configuration. +func (hc *HeaderChain) Config() *params.ChainConfig { return hc.config } + +// Engine retrieves the header chain's consensus engine. +func (hc *HeaderChain) Engine() consensus.Engine { return hc.engine } + +// GetBlock implements consensus.ChainReader, and returns nil for every input as +// a header chain does not have blocks available for retrieval. +func (hc *HeaderChain) GetBlock(hash common.Hash, number uint64) *types.Block { + return nil +} diff --git a/core/headerchain_test.go b/core/headerchain_test.go new file mode 100644 index 0000000..25d9bff --- /dev/null +++ b/core/headerchain_test.go @@ -0,0 +1,116 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "errors" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" +) + +func verifyUnbrokenCanonchain(hc *HeaderChain) error { + h := hc.CurrentHeader() + for { + canonHash := rawdb.ReadCanonicalHash(hc.chainDb, h.Number.Uint64()) + if exp := h.Hash(); canonHash != exp { + return fmt.Errorf("Canon hash chain broken, block %d got %x, expected %x", + h.Number, canonHash[:8], exp[:8]) + } + // Verify that we have the TD + if td := rawdb.ReadTd(hc.chainDb, canonHash, h.Number.Uint64()); td == nil { + return fmt.Errorf("Canon TD missing at block %d", h.Number) + } + if h.Number.Uint64() == 0 { + break + } + h = hc.GetHeader(h.ParentHash, h.Number.Uint64()-1) + } + return nil +} + +func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error, forker *ForkChoice) { + t.Helper() + + status, err := hc.InsertHeaderChain(chain, time.Now(), forker) + if status != wantStatus { + t.Errorf("wrong write status from InsertHeaderChain: got %v, want %v", status, wantStatus) + } + // Always verify that the header chain is unbroken + if err := verifyUnbrokenCanonchain(hc); err != nil { + t.Fatal(err) + } + if !errors.Is(err, wantErr) { + t.Fatalf("unexpected error from InsertHeaderChain: %v", err) + } +} + +// This test checks status reporting of InsertHeaderChain. +func TestHeaderInsertion(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{BaseFee: big.NewInt(params.InitialBaseFee), Config: params.AllEthashProtocolChanges} + ) + gspec.Commit(db, triedb.NewDatabase(db, nil)) + hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false }) + if err != nil { + t.Fatal(err) + } + // chain A: G->A1->A2...A128 + genDb, chainA := makeHeaderChainWithGenesis(gspec, 128, ethash.NewFaker(), 10) + // chain B: G->A1->B1...B128 + chainB := makeHeaderChain(gspec.Config, chainA[0], 128, ethash.NewFaker(), genDb, 10) + + forker := NewForkChoice(hc, nil) + // Inserting 64 headers on an empty chain, expecting + // 1 callbacks, 1 canon-status, 0 sidestatus, + testInsert(t, hc, chainA[:64], CanonStatTy, nil, forker) + + // Inserting 64 identical headers, expecting + // 0 callbacks, 0 canon-status, 0 sidestatus, + testInsert(t, hc, chainA[:64], NonStatTy, nil, forker) + + // Inserting the same some old, some new headers + // 1 callbacks, 1 canon, 0 side + testInsert(t, hc, chainA[32:96], CanonStatTy, nil, forker) + + // Inserting side blocks, but not overtaking the canon chain + testInsert(t, hc, chainB[0:32], SideStatTy, nil, forker) + + // Inserting more side blocks, but we don't have the parent + testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor, forker) + + // Inserting more sideblocks, overtaking the canon chain + testInsert(t, hc, chainB[32:97], CanonStatTy, nil, forker) + + // Inserting more A-headers, taking back the canonicality + testInsert(t, hc, chainA[90:100], CanonStatTy, nil, forker) + + // And B becomes canon again + testInsert(t, hc, chainB[97:107], CanonStatTy, nil, forker) + + // And B becomes even longer + testInsert(t, hc, chainB[107:128], CanonStatTy, nil, forker) +} diff --git a/core/mkalloc.go b/core/mkalloc.go new file mode 100644 index 0000000..cc4955f --- /dev/null +++ b/core/mkalloc.go @@ -0,0 +1,109 @@ +// 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 . + +//go:build none +// +build none + +/* +The mkalloc tool creates the genesis allocation constants in genesis_alloc.go +It outputs a const declaration that contains an RLP-encoded list of (address, balance) tuples. + + go run mkalloc.go genesis.json +*/ +package main + +import ( + "encoding/json" + "fmt" + "math/big" + "os" + "slices" + "strconv" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/rlp" +) + +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 { + 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, misc}) + } + slices.SortFunc(items, func(a, b allocItem) int { + return a.Addr.Cmp(b.Addr) + }) + return items +} + +func makealloc(g *core.Genesis) string { + a := makelist(g) + data, err := rlp.EncodeToBytes(a) + if err != nil { + panic(err) + } + return strconv.QuoteToASCII(string(data)) +} + +func main() { + if len(os.Args) != 2 { + fmt.Fprintln(os.Stderr, "Usage: mkalloc genesis.json") + os.Exit(1) + } + + g := new(core.Genesis) + file, err := os.Open(os.Args[1]) + if err != nil { + panic(err) + } + defer file.Close() + if err := json.NewDecoder(file).Decode(g); err != nil { + panic(err) + } + fmt.Println("const allocData =", makealloc(g)) +} diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go new file mode 100644 index 0000000..c4735c8 --- /dev/null +++ b/core/rawdb/accessors_chain.go @@ -0,0 +1,953 @@ +// 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 rawdb + +import ( + "bytes" + "encoding/binary" + "fmt" + "math/big" + "slices" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// ReadCanonicalHash retrieves the hash assigned to a canonical block number. +func ReadCanonicalHash(db ethdb.Reader, number uint64) common.Hash { + var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { + data, _ = reader.Ancient(ChainFreezerHashTable, number) + if len(data) == 0 { + // Get it by hash from leveldb + data, _ = db.Get(headerHashKey(number)) + } + return nil + }) + return common.BytesToHash(data) +} + +// WriteCanonicalHash stores the hash assigned to a canonical block number. +func WriteCanonicalHash(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + if err := db.Put(headerHashKey(number), hash.Bytes()); err != nil { + log.Crit("Failed to store number to hash mapping", "err", err) + } +} + +// DeleteCanonicalHash removes the number to hash canonical mapping. +func DeleteCanonicalHash(db ethdb.KeyValueWriter, number uint64) { + if err := db.Delete(headerHashKey(number)); err != nil { + log.Crit("Failed to delete number to hash mapping", "err", err) + } +} + +// ReadAllHashes retrieves all the hashes assigned to blocks at a certain heights, +// both canonical and reorged forks included. +func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash { + prefix := headerKeyPrefix(number) + + hashes := make([]common.Hash, 0, 1) + it := db.NewIterator(prefix, nil) + defer it.Release() + + for it.Next() { + if key := it.Key(); len(key) == len(prefix)+32 { + hashes = append(hashes, common.BytesToHash(key[len(key)-32:])) + } + } + return hashes +} + +type NumberHash struct { + Number uint64 + Hash common.Hash +} + +// ReadAllHashesInRange retrieves all the hashes assigned to blocks at certain +// heights, both canonical and reorged forks included. +// This method considers both limits to be _inclusive_. +func ReadAllHashesInRange(db ethdb.Iteratee, first, last uint64) []*NumberHash { + var ( + start = encodeBlockNumber(first) + keyLength = len(headerPrefix) + 8 + 32 + hashes = make([]*NumberHash, 0, 1+last-first) + it = db.NewIterator(headerPrefix, start) + ) + defer it.Release() + for it.Next() { + key := it.Key() + if len(key) != keyLength { + continue + } + num := binary.BigEndian.Uint64(key[len(headerPrefix) : len(headerPrefix)+8]) + if num > last { + break + } + hash := common.BytesToHash(key[len(key)-32:]) + hashes = append(hashes, &NumberHash{num, hash}) + } + return hashes +} + +// ReadAllCanonicalHashes retrieves all canonical number and hash mappings at the +// certain chain range. If the accumulated entries reaches the given threshold, +// abort the iteration and return the semi-finish result. +func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int) ([]uint64, []common.Hash) { + // Short circuit if the limit is 0. + if limit == 0 { + return nil, nil + } + var ( + numbers []uint64 + hashes []common.Hash + ) + // Construct the key prefix of start point. + start, end := headerHashKey(from), headerHashKey(to) + it := db.NewIterator(nil, start) + defer it.Release() + + for it.Next() { + if bytes.Compare(it.Key(), end) >= 0 { + break + } + if key := it.Key(); len(key) == len(headerPrefix)+8+1 && bytes.Equal(key[len(key)-1:], headerHashSuffix) { + numbers = append(numbers, binary.BigEndian.Uint64(key[len(headerPrefix):len(headerPrefix)+8])) + hashes = append(hashes, common.BytesToHash(it.Value())) + // If the accumulated entries reaches the limit threshold, return. + if len(numbers) >= limit { + break + } + } + } + return numbers, hashes +} + +// ReadHeaderNumber returns the header number assigned to a hash. +func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 { + data, _ := db.Get(headerNumberKey(hash)) + if len(data) != 8 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// WriteHeaderNumber stores the hash->number mapping. +func WriteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + key := headerNumberKey(hash) + enc := encodeBlockNumber(number) + if err := db.Put(key, enc); err != nil { + log.Crit("Failed to store hash to number mapping", "err", err) + } +} + +// DeleteHeaderNumber removes hash->number mapping. +func DeleteHeaderNumber(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(headerNumberKey(hash)); err != nil { + log.Crit("Failed to delete hash to number mapping", "err", err) + } +} + +// ReadHeadHeaderHash retrieves the hash of the current canonical head header. +func ReadHeadHeaderHash(db ethdb.KeyValueReader) common.Hash { + data, _ := db.Get(headHeaderKey) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteHeadHeaderHash stores the hash of the current canonical head header. +func WriteHeadHeaderHash(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Put(headHeaderKey, hash.Bytes()); err != nil { + log.Crit("Failed to store last header's hash", "err", err) + } +} + +// ReadHeadBlockHash retrieves the hash of the current canonical head block. +func ReadHeadBlockHash(db ethdb.KeyValueReader) common.Hash { + data, _ := db.Get(headBlockKey) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteHeadBlockHash stores the head block's hash. +func WriteHeadBlockHash(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Put(headBlockKey, hash.Bytes()); err != nil { + log.Crit("Failed to store last block's hash", "err", err) + } +} + +// ReadHeadFastBlockHash retrieves the hash of the current fast-sync head block. +func ReadHeadFastBlockHash(db ethdb.KeyValueReader) common.Hash { + data, _ := db.Get(headFastBlockKey) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteHeadFastBlockHash stores the hash of the current fast-sync head block. +func WriteHeadFastBlockHash(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Put(headFastBlockKey, hash.Bytes()); err != nil { + log.Crit("Failed to store last fast block's hash", "err", err) + } +} + +// ReadFinalizedBlockHash retrieves the hash of the finalized block. +func ReadFinalizedBlockHash(db ethdb.KeyValueReader) common.Hash { + data, _ := db.Get(headFinalizedBlockKey) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteFinalizedBlockHash stores the hash of the finalized block. +func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Put(headFinalizedBlockKey, hash.Bytes()); err != nil { + log.Crit("Failed to store last finalized block's hash", "err", err) + } +} + +// ReadLastPivotNumber retrieves the number of the last pivot block. If the node +// full synced, the last pivot will always be nil. +func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 { + data, _ := db.Get(lastPivotKey) + if len(data) == 0 { + return nil + } + var pivot uint64 + if err := rlp.DecodeBytes(data, &pivot); err != nil { + log.Error("Invalid pivot block number in database", "err", err) + return nil + } + return &pivot +} + +// WriteLastPivotNumber stores the number of the last pivot block. +func WriteLastPivotNumber(db ethdb.KeyValueWriter, pivot uint64) { + enc, err := rlp.EncodeToBytes(pivot) + if err != nil { + log.Crit("Failed to encode pivot block number", "err", err) + } + if err := db.Put(lastPivotKey, enc); err != nil { + log.Crit("Failed to store pivot block number", "err", err) + } +} + +// ReadTxIndexTail retrieves the number of oldest indexed block +// whose transaction indices has been indexed. +func ReadTxIndexTail(db ethdb.KeyValueReader) *uint64 { + data, _ := db.Get(txIndexTailKey) + if len(data) != 8 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// WriteTxIndexTail stores the number of oldest indexed block +// into database. +func WriteTxIndexTail(db ethdb.KeyValueWriter, number uint64) { + if err := db.Put(txIndexTailKey, encodeBlockNumber(number)); err != nil { + log.Crit("Failed to store the transaction index tail", "err", err) + } +} + +// ReadHeaderRange returns the rlp-encoded headers, starting at 'number', and going +// backwards towards genesis. This method assumes that the caller already has +// placed a cap on count, to prevent DoS issues. +// Since this method operates in head-towards-genesis mode, it will return an empty +// slice in case the head ('number') is missing. Hence, the caller must ensure that +// the head ('number') argument is actually an existing header. +// +// N.B: Since the input is a number, as opposed to a hash, it's implicit that +// this method only operates on canon headers. +func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValue { + var rlpHeaders []rlp.RawValue + if count == 0 { + return rlpHeaders + } + i := number + if count-1 > number { + // It's ok to request block 0, 1 item + count = number + 1 + } + limit, _ := db.Ancients() + // First read live blocks + if i >= limit { + // If we need to read live blocks, we need to figure out the hash first + hash := ReadCanonicalHash(db, number) + for ; i >= limit && count > 0; i-- { + if data, _ := db.Get(headerKey(i, hash)); len(data) > 0 { + rlpHeaders = append(rlpHeaders, data) + // Get the parent hash for next query + hash = types.HeaderParentHashFromRLP(data) + } else { + break // Maybe got moved to ancients + } + count-- + } + } + if count == 0 { + return rlpHeaders + } + // read remaining from ancients, cap at 2M + data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 2*1024*1024) + 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 +} + +// ReadHeaderRLP retrieves a block header in its raw RLP database encoding. +func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { + var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { + // First try to look up the data in ancient database. Extra hash + // comparison is necessary since ancient database only maintains + // the canonical data. + data, _ = reader.Ancient(ChainFreezerHeaderTable, number) + if len(data) > 0 && crypto.Keccak256Hash(data) == hash { + return nil + } + // If not, try reading from leveldb + data, _ = db.Get(headerKey(number, hash)) + return nil + }) + return data +} + +// HasHeader verifies the existence of a block header corresponding to the hash. +func HasHeader(db ethdb.Reader, hash common.Hash, number uint64) bool { + if isCanon(db, number, hash) { + return true + } + if has, err := db.Has(headerKey(number, hash)); !has || err != nil { + return false + } + return true +} + +// ReadHeader retrieves the block header corresponding to the hash. +func ReadHeader(db ethdb.Reader, hash common.Hash, number uint64) *types.Header { + data := ReadHeaderRLP(db, hash, number) + if len(data) == 0 { + return nil + } + header := new(types.Header) + if err := rlp.DecodeBytes(data, header); err != nil { + log.Error("Invalid block header RLP", "hash", hash, "err", err) + return nil + } + return header +} + +// WriteHeader stores a block header into the database and also stores the hash- +// to-number mapping. +func WriteHeader(db ethdb.KeyValueWriter, header *types.Header) { + var ( + hash = header.Hash() + number = header.Number.Uint64() + ) + // Write the hash -> number mapping + WriteHeaderNumber(db, hash, number) + + // Write the encoded header + data, err := rlp.EncodeToBytes(header) + if err != nil { + log.Crit("Failed to RLP encode header", "err", err) + } + key := headerKey(number, hash) + if err := db.Put(key, data); err != nil { + log.Crit("Failed to store header", "err", err) + } +} + +// DeleteHeader removes all block header data associated with a hash. +func DeleteHeader(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + deleteHeaderWithoutNumber(db, hash, number) + if err := db.Delete(headerNumberKey(hash)); err != nil { + log.Crit("Failed to delete hash to number mapping", "err", err) + } +} + +// deleteHeaderWithoutNumber removes only the block header but does not remove +// the hash to number mapping. +func deleteHeaderWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + if err := db.Delete(headerKey(number, hash)); err != nil { + log.Crit("Failed to delete header", "err", err) + } +} + +// isCanon is an internal utility method, to check whether the given number/hash +// is part of the ancient (canon) set. +func isCanon(reader ethdb.AncientReaderOp, number uint64, hash common.Hash) bool { + h, err := reader.Ancient(ChainFreezerHashTable, number) + if err != nil { + return false + } + return bytes.Equal(h, hash[:]) +} + +// ReadBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. +func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { + // First try to look up the data in ancient database. Extra hash + // comparison is necessary since ancient database only maintains + // the canonical data. + var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { + // Check if the data is in ancients + if isCanon(reader, number, hash) { + data, _ = reader.Ancient(ChainFreezerBodiesTable, number) + return nil + } + // If not, try reading from leveldb + data, _ = db.Get(blockBodyKey(number, hash)) + return nil + }) + return data +} + +// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the canonical +// block at number, in RLP encoding. +func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue { + var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { + data, _ = reader.Ancient(ChainFreezerBodiesTable, number) + if len(data) > 0 { + return nil + } + // Block is not in ancients, read from leveldb by hash and number. + // Note: ReadCanonicalHash cannot be used here because it also + // calls ReadAncients internally. + hash, _ := db.Get(headerHashKey(number)) + data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hash))) + return nil + }) + return data +} + +// WriteBodyRLP stores an RLP encoded block body into the database. +func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp rlp.RawValue) { + if err := db.Put(blockBodyKey(number, hash), rlp); err != nil { + log.Crit("Failed to store block body", "err", err) + } +} + +// HasBody verifies the existence of a block body corresponding to the hash. +func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool { + if isCanon(db, number, hash) { + return true + } + if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil { + return false + } + return true +} + +// ReadBody retrieves the block body corresponding to the hash. +func ReadBody(db ethdb.Reader, hash common.Hash, number uint64) *types.Body { + data := ReadBodyRLP(db, hash, number) + if len(data) == 0 { + return nil + } + body := new(types.Body) + if err := rlp.DecodeBytes(data, body); err != nil { + log.Error("Invalid block body RLP", "hash", hash, "err", err) + return nil + } + return body +} + +// WriteBody stores a block body into the database. +func WriteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64, body *types.Body) { + data, err := rlp.EncodeToBytes(body) + if err != nil { + log.Crit("Failed to RLP encode body", "err", err) + } + WriteBodyRLP(db, hash, number, data) +} + +// DeleteBody removes all block body data associated with a hash. +func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + if err := db.Delete(blockBodyKey(number, hash)); err != nil { + log.Crit("Failed to delete block body", "err", err) + } +} + +// ReadTdRLP retrieves a block's total difficulty corresponding to the hash in RLP encoding. +func ReadTdRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { + var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { + // Check if the data is in ancients + if isCanon(reader, number, hash) { + data, _ = reader.Ancient(ChainFreezerDifficultyTable, number) + return nil + } + // If not, try reading from leveldb + data, _ = db.Get(headerTDKey(number, hash)) + return nil + }) + return data +} + +// ReadTd retrieves a block's total difficulty corresponding to the hash. +func ReadTd(db ethdb.Reader, hash common.Hash, number uint64) *big.Int { + data := ReadTdRLP(db, hash, number) + if len(data) == 0 { + return nil + } + td := new(big.Int) + if err := rlp.DecodeBytes(data, td); err != nil { + log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err) + return nil + } + return td +} + +// WriteTd stores the total difficulty of a block into the database. +func WriteTd(db ethdb.KeyValueWriter, hash common.Hash, number uint64, td *big.Int) { + data, err := rlp.EncodeToBytes(td) + if err != nil { + log.Crit("Failed to RLP encode block total difficulty", "err", err) + } + if err := db.Put(headerTDKey(number, hash), data); err != nil { + log.Crit("Failed to store block total difficulty", "err", err) + } +} + +// DeleteTd removes all block total difficulty data associated with a hash. +func DeleteTd(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + if err := db.Delete(headerTDKey(number, hash)); err != nil { + log.Crit("Failed to delete block total difficulty", "err", err) + } +} + +// HasReceipts verifies the existence of all the transaction receipts belonging +// to a block. +func HasReceipts(db ethdb.Reader, hash common.Hash, number uint64) bool { + if isCanon(db, number, hash) { + return true + } + if has, err := db.Has(blockReceiptsKey(number, hash)); !has || err != nil { + return false + } + return true +} + +// ReadReceiptsRLP retrieves all the transaction receipts belonging to a block in RLP encoding. +func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { + var data []byte + db.ReadAncients(func(reader ethdb.AncientReaderOp) error { + // Check if the data is in ancients + if isCanon(reader, number, hash) { + data, _ = reader.Ancient(ChainFreezerReceiptTable, number) + return nil + } + // If not, try reading from leveldb + data, _ = db.Get(blockReceiptsKey(number, hash)) + return nil + }) + return data +} + +// ReadRawReceipts retrieves all the transaction receipts belonging to a block. +// The receipt metadata fields are not guaranteed to be populated, so they +// should not be used. Use ReadReceipts instead if the metadata is needed. +func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts { + // Retrieve the flattened receipt slice + data := ReadReceiptsRLP(db, hash, number) + if len(data) == 0 { + return nil + } + // Convert the receipts from their storage form to their internal representation + storageReceipts := []*types.ReceiptForStorage{} + if err := rlp.DecodeBytes(data, &storageReceipts); err != nil { + log.Error("Invalid receipt array RLP", "hash", hash, "err", err) + return nil + } + receipts := make(types.Receipts, len(storageReceipts)) + for i, storageReceipt := range storageReceipts { + receipts[i] = (*types.Receipt)(storageReceipt) + } + return receipts +} + +// ReadReceipts retrieves all the transaction receipts belonging to a block, including +// its corresponding metadata fields. If it is unable to populate these metadata +// fields then nil is returned. +// +// The current implementation populates these metadata fields by reading the receipts' +// corresponding block body, so if the block body is not found it will return nil even +// if the receipt itself is stored. +func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, time uint64, config *params.ChainConfig) types.Receipts { + // We're deriving many fields from the block body, retrieve beside the receipt + receipts := ReadRawReceipts(db, hash, number) + if receipts == nil { + return nil + } + body := ReadBody(db, hash, number) + if body == nil { + log.Error("Missing body but have receipt", "hash", hash, "number", number) + return nil + } + header := ReadHeader(db, hash, number) + + var baseFee *big.Int + if header == nil { + baseFee = big.NewInt(0) + } else { + baseFee = header.BaseFee + } + // Compute effective blob gas price. + var blobGasPrice *big.Int + if header != nil && header.ExcessBlobGas != nil { + blobGasPrice = eip4844.CalcBlobFee(*header.ExcessBlobGas) + } + if err := receipts.DeriveFields(config, hash, number, time, baseFee, blobGasPrice, body.Transactions); err != nil { + log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err) + return nil + } + return receipts +} + +// WriteReceipts stores all the transaction receipts belonging to a block. +func WriteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64, receipts types.Receipts) { + // Convert the receipts into their storage form and serialize them + storageReceipts := make([]*types.ReceiptForStorage, len(receipts)) + for i, receipt := range receipts { + storageReceipts[i] = (*types.ReceiptForStorage)(receipt) + } + bytes, err := rlp.EncodeToBytes(storageReceipts) + if err != nil { + log.Crit("Failed to encode block receipts", "err", err) + } + // Store the flattened receipt slice + if err := db.Put(blockReceiptsKey(number, hash), bytes); err != nil { + log.Crit("Failed to store block receipts", "err", err) + } +} + +// DeleteReceipts removes all receipt data associated with a block hash. +func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + if err := db.Delete(blockReceiptsKey(number, hash)); err != nil { + log.Crit("Failed to delete block receipts", "err", err) + } +} + +// storedReceiptRLP is the storage encoding of a receipt. +// Re-definition in core/types/receipt.go. +// TODO: Re-use the existing definition. +type storedReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Logs []*types.Log +} + +// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps +// the list of logs. When decoding a stored receipt into this object we +// avoid creating the bloom filter. +type receiptLogs struct { + Logs []*types.Log +} + +// DecodeRLP implements rlp.Decoder. +func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error { + var stored storedReceiptRLP + if err := s.Decode(&stored); err != nil { + return err + } + r.Logs = stored.Logs + return nil +} + +// 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) [][]*types.Log { + // Retrieve the flattened receipt slice + data := ReadReceiptsRLP(db, hash, number) + if len(data) == 0 { + return nil + } + receipts := []*receiptLogs{} + if err := rlp.DecodeBytes(data, &receipts); err != nil { + log.Error("Invalid receipt array RLP", "hash", hash, "err", err) + return nil + } + + logs := make([][]*types.Log, len(receipts)) + for i, receipt := range receipts { + logs[i] = receipt.Logs + } + return logs +} + +// ReadBlock retrieves an entire block corresponding to the hash, assembling it +// back from the stored header and body. If either the header or body could not +// be retrieved nil is returned. +// +// Note, due to concurrent download of header and block body the header and thus +// canonical hash can be stored in the database but the body data not (yet). +func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block { + header := ReadHeader(db, hash, number) + if header == nil { + return nil + } + body := ReadBody(db, hash, number) + if body == nil { + return nil + } + return types.NewBlockWithHeader(header).WithBody(*body) +} + +// WriteBlock serializes a block into the database, header and body separately. +func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) { + WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) + WriteHeader(db, block.Header()) +} + +// WriteAncientBlocks writes entire block data into ancient store and returns the total written size. +func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts []types.Receipts, td *big.Int) (int64, error) { + var ( + tdSum = new(big.Int).Set(td) + stReceipts []*types.ReceiptForStorage + ) + return db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i, block := range blocks { + // Convert receipts to storage format and sum up total difficulty. + stReceipts = stReceipts[:0] + for _, receipt := range receipts[i] { + stReceipts = append(stReceipts, (*types.ReceiptForStorage)(receipt)) + } + header := block.Header() + if i > 0 { + tdSum.Add(tdSum, header.Difficulty) + } + if err := writeAncientBlock(op, block, header, stReceipts, tdSum); err != nil { + return err + } + } + return nil + }) +} + +func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts []*types.ReceiptForStorage, td *big.Int) error { + num := block.NumberU64() + if err := op.AppendRaw(ChainFreezerHashTable, num, block.Hash().Bytes()); err != nil { + return fmt.Errorf("can't add block %d hash: %v", num, err) + } + if err := op.Append(ChainFreezerHeaderTable, num, header); err != nil { + return fmt.Errorf("can't append block header %d: %v", num, err) + } + if err := op.Append(ChainFreezerBodiesTable, num, block.Body()); err != nil { + return fmt.Errorf("can't append block body %d: %v", num, err) + } + if err := op.Append(ChainFreezerReceiptTable, num, receipts); err != nil { + return fmt.Errorf("can't append block %d receipts: %v", num, err) + } + if err := op.Append(ChainFreezerDifficultyTable, num, td); err != nil { + return fmt.Errorf("can't append block %d total difficulty: %v", num, err) + } + return nil +} + +// DeleteBlock removes all block data associated with a hash. +func DeleteBlock(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + DeleteReceipts(db, hash, number) + DeleteHeader(db, hash, number) + DeleteBody(db, hash, number) + DeleteTd(db, hash, number) +} + +// DeleteBlockWithoutNumber removes all block data associated with a hash, except +// the hash to number mapping. +func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { + DeleteReceipts(db, hash, number) + deleteHeaderWithoutNumber(db, hash, number) + DeleteBody(db, hash, number) + DeleteTd(db, hash, number) +} + +const badBlockToKeep = 10 + +type badBlock struct { + Header *types.Header + Body *types.Body +} + +// ReadBadBlock retrieves the bad block with the corresponding block hash. +func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block { + blob, err := db.Get(badBlockKey) + if err != nil { + return nil + } + var badBlocks []*badBlock + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + return nil + } + for _, bad := range badBlocks { + if bad.Header.Hash() == hash { + block := types.NewBlockWithHeader(bad.Header) + if bad.Body != nil { + block = block.WithBody(*bad.Body) + } + return block + } + } + return nil +} + +// ReadAllBadBlocks retrieves all the bad blocks in the database. +// All returned blocks are sorted in reverse order by number. +func ReadAllBadBlocks(db ethdb.Reader) []*types.Block { + blob, err := db.Get(badBlockKey) + if err != nil { + return nil + } + var badBlocks []*badBlock + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + return nil + } + var blocks []*types.Block + for _, bad := range badBlocks { + block := types.NewBlockWithHeader(bad.Header) + if bad.Body != nil { + block = block.WithBody(*bad.Body) + } + blocks = append(blocks, block) + } + return blocks +} + +// WriteBadBlock serializes the bad block into the database. If the cumulated +// bad blocks exceeds the limitation, the oldest will be dropped. +func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { + blob, err := db.Get(badBlockKey) + if err != nil { + log.Warn("Failed to load old bad blocks", "error", err) + } + var badBlocks []*badBlock + if len(blob) > 0 { + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + log.Crit("Failed to decode old bad blocks", "error", err) + } + } + for _, b := range badBlocks { + if b.Header.Number.Uint64() == block.NumberU64() && b.Header.Hash() == block.Hash() { + log.Info("Skip duplicated bad block", "number", block.NumberU64(), "hash", block.Hash()) + return + } + } + badBlocks = append(badBlocks, &badBlock{ + Header: block.Header(), + Body: block.Body(), + }) + slices.SortFunc(badBlocks, func(a, b *badBlock) int { + // Note: sorting in descending number order. + return -a.Header.Number.Cmp(b.Header.Number) + }) + if len(badBlocks) > badBlockToKeep { + badBlocks = badBlocks[:badBlockToKeep] + } + data, err := rlp.EncodeToBytes(badBlocks) + if err != nil { + log.Crit("Failed to encode bad blocks", "err", err) + } + if err := db.Put(badBlockKey, data); err != nil { + log.Crit("Failed to write bad blocks", "err", err) + } +} + +// DeleteBadBlocks deletes all the bad blocks from the database +func DeleteBadBlocks(db ethdb.KeyValueWriter) { + if err := db.Delete(badBlockKey); err != nil { + log.Crit("Failed to delete bad blocks", "err", err) + } +} + +// FindCommonAncestor returns the last common ancestor of two block headers +func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header { + for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { + a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1) + if a == nil { + return nil + } + } + for an := a.Number.Uint64(); an < b.Number.Uint64(); { + b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1) + if b == nil { + return nil + } + } + for a.Hash() != b.Hash() { + a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1) + if a == nil { + return nil + } + b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1) + if b == nil { + return nil + } + } + return a +} + +// ReadHeadHeader returns the current canonical head header. +func ReadHeadHeader(db ethdb.Reader) *types.Header { + headHeaderHash := ReadHeadHeaderHash(db) + if headHeaderHash == (common.Hash{}) { + return nil + } + headHeaderNumber := ReadHeaderNumber(db, headHeaderHash) + if headHeaderNumber == nil { + return nil + } + return ReadHeader(db, headHeaderHash, *headHeaderNumber) +} + +// ReadHeadBlock returns the current canonical head block. +func ReadHeadBlock(db ethdb.Reader) *types.Block { + headBlockHash := ReadHeadBlockHash(db) + if headBlockHash == (common.Hash{}) { + return nil + } + headBlockNumber := ReadHeaderNumber(db, headBlockHash) + if headBlockNumber == nil { + return nil + } + return ReadBlock(db, headBlockHash, *headBlockNumber) +} diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go new file mode 100644 index 0000000..2d30af4 --- /dev/null +++ b/core/rawdb/accessors_chain_test.go @@ -0,0 +1,931 @@ +// 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 rawdb + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/big" + "math/rand" + "os" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +// Tests block header storage and retrieval operations. +func TestHeaderStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test header to move around the database and make sure it's really new + header := &types.Header{Number: big.NewInt(42), Extra: []byte("test header")} + if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry != nil { + t.Fatalf("Non existent header returned: %v", entry) + } + // Write and verify the header in the database + WriteHeader(db, header) + if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry == nil { + t.Fatalf("Stored header not found") + } else if entry.Hash() != header.Hash() { + t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, header) + } + if entry := ReadHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil { + t.Fatalf("Stored header RLP not found") + } else { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(entry) + + if hash := common.BytesToHash(hasher.Sum(nil)); hash != header.Hash() { + t.Fatalf("Retrieved RLP header mismatch: have %v, want %v", entry, header) + } + } + // Delete the header and verify the execution + DeleteHeader(db, header.Hash(), header.Number.Uint64()) + if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry != nil { + t.Fatalf("Deleted header returned: %v", entry) + } +} + +// Tests block body storage and retrieval operations. +func TestBodyStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test body to move around the database and make sure it's really new + body := &types.Body{Uncles: []*types.Header{{Extra: []byte("test header")}}} + + hasher := sha3.NewLegacyKeccak256() + rlp.Encode(hasher, body) + hash := common.BytesToHash(hasher.Sum(nil)) + + if entry := ReadBody(db, hash, 0); entry != nil { + t.Fatalf("Non existent body returned: %v", entry) + } + // Write and verify the body in the database + WriteBody(db, hash, 0, body) + if entry := ReadBody(db, hash, 0); entry == nil { + t.Fatalf("Stored body not found") + } else if types.DeriveSha(types.Transactions(entry.Transactions), newTestHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newTestHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) { + t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body) + } + if entry := ReadBodyRLP(db, hash, 0); entry == nil { + t.Fatalf("Stored body RLP not found") + } else { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(entry) + + if calc := common.BytesToHash(hasher.Sum(nil)); calc != hash { + t.Fatalf("Retrieved RLP body mismatch: have %v, want %v", entry, body) + } + } + // Delete the body and verify the execution + DeleteBody(db, hash, 0) + if entry := ReadBody(db, hash, 0); entry != nil { + t.Fatalf("Deleted body returned: %v", entry) + } +} + +// Tests block storage and retrieval operations. +func TestBlockStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test block to move around the database and make sure it's really new + block := types.NewBlockWithHeader(&types.Header{ + Extra: []byte("test block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + }) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry != nil { + t.Fatalf("Non existent header returned: %v", entry) + } + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry != nil { + t.Fatalf("Non existent body returned: %v", entry) + } + // Write and verify the block in the database + WriteBlock(db, block) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry == nil { + t.Fatalf("Stored block not found") + } else if entry.Hash() != block.Hash() { + t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) + } + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry == nil { + t.Fatalf("Stored header not found") + } else if entry.Hash() != block.Header().Hash() { + t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, block.Header()) + } + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil { + t.Fatalf("Stored body not found") + } else if types.DeriveSha(types.Transactions(entry.Transactions), newTestHasher()) != types.DeriveSha(block.Transactions(), newTestHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) { + t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body()) + } + // Delete the block and verify the execution + DeleteBlock(db, block.Hash(), block.NumberU64()) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { + t.Fatalf("Deleted block returned: %v", entry) + } + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry != nil { + t.Fatalf("Deleted header returned: %v", entry) + } + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry != nil { + t.Fatalf("Deleted body returned: %v", entry) + } +} + +// Tests that partial block contents don't get reassembled into full blocks. +func TestPartialBlockStorage(t *testing.T) { + db := NewMemoryDatabase() + block := types.NewBlockWithHeader(&types.Header{ + Extra: []byte("test block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + }) + // Store a header and check that it's not recognized as a block + WriteHeader(db, block.Header()) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + DeleteHeader(db, block.Hash(), block.NumberU64()) + + // Store a body and check that it's not recognized as a block + WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + DeleteBody(db, block.Hash(), block.NumberU64()) + + // Store a header and a body separately and check reassembly + WriteHeader(db, block.Header()) + WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) + + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry == nil { + t.Fatalf("Stored block not found") + } else if entry.Hash() != block.Hash() { + t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) + } +} + +// Tests block storage and retrieval operations. +func TestBadBlockStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test block to move around the database and make sure it's really new + block := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(1), + Extra: []byte("bad block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + }) + if entry := ReadBadBlock(db, block.Hash()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + // Write and verify the block in the database + WriteBadBlock(db, block) + if entry := ReadBadBlock(db, block.Hash()); entry == nil { + t.Fatalf("Stored block not found") + } else if entry.Hash() != block.Hash() { + t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) + } + // Write one more bad block + blockTwo := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(2), + Extra: []byte("bad block two"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + }) + WriteBadBlock(db, blockTwo) + + // Write the block one again, should be filtered out. + WriteBadBlock(db, block) + badBlocks := ReadAllBadBlocks(db) + if len(badBlocks) != 2 { + t.Fatalf("Failed to load all bad blocks") + } + + // Write a bunch of bad blocks, all the blocks are should sorted + // in reverse order. The extra blocks should be truncated. + for _, n := range rand.Perm(100) { + block := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(int64(n)), + Extra: []byte("bad block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + }) + WriteBadBlock(db, block) + } + badBlocks = ReadAllBadBlocks(db) + if len(badBlocks) != badBlockToKeep { + t.Fatalf("The number of persised bad blocks in incorrect %d", len(badBlocks)) + } + for i := 0; i < len(badBlocks)-1; i++ { + if badBlocks[i].NumberU64() < badBlocks[i+1].NumberU64() { + t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, i+1, badBlocks[i].NumberU64(), badBlocks[i+1].NumberU64()) + } + } + + // Delete all bad blocks + DeleteBadBlocks(db) + badBlocks = ReadAllBadBlocks(db) + if len(badBlocks) != 0 { + t.Fatalf("Failed to delete bad blocks") + } +} + +// Tests block total difficulty storage and retrieval operations. +func TestTdStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test TD to move around the database and make sure it's really new + hash, td := common.Hash{}, big.NewInt(314) + if entry := ReadTd(db, hash, 0); entry != nil { + t.Fatalf("Non existent TD returned: %v", entry) + } + // Write and verify the TD in the database + WriteTd(db, hash, 0, td) + if entry := ReadTd(db, hash, 0); entry == nil { + t.Fatalf("Stored TD not found") + } else if entry.Cmp(td) != 0 { + t.Fatalf("Retrieved TD mismatch: have %v, want %v", entry, td) + } + // Delete the TD and verify the execution + DeleteTd(db, hash, 0) + if entry := ReadTd(db, hash, 0); entry != nil { + t.Fatalf("Deleted TD returned: %v", entry) + } +} + +// Tests that canonical numbers can be mapped to hashes and retrieved. +func TestCanonicalMappingStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test canonical number and assigned hash to move around + hash, number := common.Hash{0: 0xff}, uint64(314) + if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) { + t.Fatalf("Non existent canonical mapping returned: %v", entry) + } + // Write and verify the TD in the database + WriteCanonicalHash(db, hash, number) + if entry := ReadCanonicalHash(db, number); entry == (common.Hash{}) { + t.Fatalf("Stored canonical mapping not found") + } else if entry != hash { + t.Fatalf("Retrieved canonical mapping mismatch: have %v, want %v", entry, hash) + } + // Delete the TD and verify the execution + DeleteCanonicalHash(db, number) + if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) { + t.Fatalf("Deleted canonical mapping returned: %v", entry) + } +} + +// Tests that head headers and head blocks can be assigned, individually. +func TestHeadStorage(t *testing.T) { + db := NewMemoryDatabase() + + blockHead := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block header")}) + blockFull := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block full")}) + blockFast := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block fast")}) + + // Check that no head entries are in a pristine database + if entry := ReadHeadHeaderHash(db); entry != (common.Hash{}) { + t.Fatalf("Non head header entry returned: %v", entry) + } + if entry := ReadHeadBlockHash(db); entry != (common.Hash{}) { + t.Fatalf("Non head block entry returned: %v", entry) + } + if entry := ReadHeadFastBlockHash(db); entry != (common.Hash{}) { + t.Fatalf("Non fast head block entry returned: %v", entry) + } + // Assign separate entries for the head header and block + WriteHeadHeaderHash(db, blockHead.Hash()) + WriteHeadBlockHash(db, blockFull.Hash()) + WriteHeadFastBlockHash(db, blockFast.Hash()) + + // Check that both heads are present, and different (i.e. two heads maintained) + if entry := ReadHeadHeaderHash(db); entry != blockHead.Hash() { + t.Fatalf("Head header hash mismatch: have %v, want %v", entry, blockHead.Hash()) + } + if entry := ReadHeadBlockHash(db); entry != blockFull.Hash() { + t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash()) + } + if entry := ReadHeadFastBlockHash(db); entry != blockFast.Hash() { + t.Fatalf("Fast head block hash mismatch: have %v, want %v", entry, blockFast.Hash()) + } +} + +// Tests that receipts associated with a single block can be stored and retrieved. +func TestBlockReceiptStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a live block since we need metadata to reconstruct the receipt + tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil) + tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil) + + body := &types.Body{Transactions: types.Transactions{tx1, tx2}} + + // Create the two receipts to manage afterwards + receipt1 := &types.Receipt{ + Status: types.ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*types.Log{ + {Address: common.BytesToAddress([]byte{0x11})}, + {Address: common.BytesToAddress([]byte{0x01, 0x11})}, + }, + TxHash: tx1.Hash(), + ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 111111, + } + receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1}) + + receipt2 := &types.Receipt{ + PostState: common.Hash{2}.Bytes(), + CumulativeGasUsed: 2, + Logs: []*types.Log{ + {Address: common.BytesToAddress([]byte{0x22})}, + {Address: common.BytesToAddress([]byte{0x02, 0x22})}, + }, + TxHash: tx2.Hash(), + ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), + GasUsed: 222222, + } + receipt2.Bloom = types.CreateBloom(types.Receipts{receipt2}) + receipts := []*types.Receipt{receipt1, receipt2} + + // Check that no receipt entries are in a pristine database + hash := common.BytesToHash([]byte{0x03, 0x14}) + if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) != 0 { + t.Fatalf("non existent receipts returned: %v", rs) + } + // Insert the body that corresponds to the receipts + WriteBody(db, hash, 0, body) + + // Insert the receipt slice into the database and check presence + WriteReceipts(db, hash, 0, receipts) + if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) == 0 { + t.Fatalf("no receipts returned") + } else { + if err := checkReceiptsRLP(rs, receipts); err != nil { + t.Fatalf(err.Error()) + } + } + // Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed) + DeleteBody(db, hash, 0) + if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); rs != nil { + t.Fatalf("receipts returned when body was deleted: %v", rs) + } + // Ensure that receipts without metadata can be returned without the block body too + if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil { + t.Fatalf(err.Error()) + } + // Sanity check that body alone without the receipt is a full purge + WriteBody(db, hash, 0, body) + + DeleteReceipts(db, hash, 0) + if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) != 0 { + t.Fatalf("deleted receipts returned: %v", rs) + } +} + +func checkReceiptsRLP(have, want types.Receipts) error { + if len(have) != len(want) { + return fmt.Errorf("receipts sizes mismatch: have %d, want %d", len(have), len(want)) + } + for i := 0; i < len(want); i++ { + rlpHave, err := rlp.EncodeToBytes(have[i]) + if err != nil { + return err + } + rlpWant, err := rlp.EncodeToBytes(want[i]) + if err != nil { + return err + } + if !bytes.Equal(rlpHave, rlpWant) { + return fmt.Errorf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant)) + } + } + return nil +} + +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), + Extra: []byte("test block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + }) + // Ensure nothing non-existent will be read + hash, number := block.Hash(), block.NumberU64() + if blob := ReadHeaderRLP(db, hash, number); len(blob) > 0 { + t.Fatalf("non existent header returned") + } + if blob := ReadBodyRLP(db, hash, number); len(blob) > 0 { + t.Fatalf("non existent body returned") + } + if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 { + t.Fatalf("non existent receipts returned") + } + if blob := ReadTdRLP(db, hash, number); len(blob) > 0 { + t.Fatalf("non existent td returned") + } + + // Write and verify the header in the database + WriteAncientBlocks(db, []*types.Block{block}, []types.Receipts{nil}, big.NewInt(100)) + + if blob := ReadHeaderRLP(db, hash, number); len(blob) == 0 { + t.Fatalf("no header returned") + } + if blob := ReadBodyRLP(db, hash, number); len(blob) == 0 { + t.Fatalf("no body returned") + } + if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 { + t.Fatalf("no receipts returned") + } + if blob := ReadTdRLP(db, hash, number); len(blob) == 0 { + t.Fatalf("no td returned") + } + + // Use a fake hash for data retrieval, nothing should be returned. + fakeHash := common.BytesToHash([]byte{0x01, 0x02, 0x03}) + if blob := ReadHeaderRLP(db, fakeHash, number); len(blob) != 0 { + t.Fatalf("invalid header returned") + } + if blob := ReadBodyRLP(db, fakeHash, number); len(blob) != 0 { + t.Fatalf("invalid body returned") + } + if blob := ReadReceiptsRLP(db, fakeHash, number); len(blob) != 0 { + t.Fatalf("invalid receipts returned") + } + if blob := ReadTdRLP(db, fakeHash, number); len(blob) != 0 { + t.Fatalf("invalid td returned") + } +} + +func TestCanonicalHashIteration(t *testing.T) { + var cases = []struct { + from, to uint64 + limit int + expect []uint64 + }{ + {1, 8, 0, nil}, + {1, 8, 1, []uint64{1}}, + {1, 8, 10, []uint64{1, 2, 3, 4, 5, 6, 7}}, + {1, 9, 10, []uint64{1, 2, 3, 4, 5, 6, 7, 8}}, + {2, 9, 10, []uint64{2, 3, 4, 5, 6, 7, 8}}, + {9, 10, 10, nil}, + } + // Test empty db iteration + db := NewMemoryDatabase() + numbers, _ := ReadAllCanonicalHashes(db, 0, 10, 10) + if len(numbers) != 0 { + t.Fatalf("No entry should be returned to iterate an empty db") + } + // Fill database with testing data. + for i := uint64(1); i <= 8; i++ { + WriteCanonicalHash(db, common.Hash{}, i) + WriteTd(db, common.Hash{}, i, big.NewInt(10)) // Write some interferential data + } + for i, c := range cases { + numbers, _ := ReadAllCanonicalHashes(db, c.from, c.to, c.limit) + if !reflect.DeepEqual(numbers, c.expect) { + t.Fatalf("Case %d failed, want %v, got %v", i, c.expect, numbers) + } + } +} + +func TestHashesInRange(t *testing.T) { + mkHeader := func(number, seq int) *types.Header { + h := types.Header{ + Difficulty: new(big.Int), + Number: big.NewInt(int64(number)), + GasLimit: uint64(seq), + } + return &h + } + db := NewMemoryDatabase() + // For each number, write N versions of that particular number + total := 0 + for i := 0; i < 15; i++ { + for ii := 0; ii < i; ii++ { + WriteHeader(db, mkHeader(i, ii)) + total++ + } + } + if have, want := len(ReadAllHashesInRange(db, 10, 10)), 10; have != want { + t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) + } + if have, want := len(ReadAllHashesInRange(db, 10, 9)), 0; have != want { + t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) + } + if have, want := len(ReadAllHashesInRange(db, 0, 100)), total; have != want { + t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) + } + if have, want := len(ReadAllHashesInRange(db, 9, 10)), 9+10; have != want { + t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) + } + if have, want := len(ReadAllHashes(db, 10)), 10; have != want { + t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) + } + if have, want := len(ReadAllHashes(db, 16)), 0; have != want { + t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) + } + if have, want := len(ReadAllHashes(db, 1)), 1; have != want { + t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) + } +} + +// This measures the write speed of the WriteAncientBlocks operation. +func BenchmarkWriteAncientBlocks(b *testing.B) { + // Open freezer database. + frdir := b.TempDir() + db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false) + if err != nil { + b.Fatalf("failed to create database with ancient backend") + } + defer db.Close() + + // Create the data to insert. The blocks must have consecutive numbers, so we create + // all of them ahead of time. However, there is no need to create receipts + // individually for each block, just make one batch here and reuse it for all writes. + const batchSize = 128 + const blockTxs = 20 + allBlocks := makeTestBlocks(b.N, blockTxs) + batchReceipts := makeTestReceipts(batchSize, blockTxs) + b.ResetTimer() + + // The benchmark loop writes batches of blocks, but note that the total block count is + // b.N. This means the resulting ns/op measurement is the time it takes to write a + // single block and its associated data. + var td = big.NewInt(55) + var totalSize int64 + for i := 0; i < b.N; i += batchSize { + length := batchSize + if i+batchSize > b.N { + length = b.N - i + } + + blocks := allBlocks[i : i+length] + receipts := batchReceipts[:length] + writeSize, err := WriteAncientBlocks(db, blocks, receipts, td) + if err != nil { + b.Fatal(err) + } + totalSize += writeSize + } + + // Enable MB/s reporting. + b.SetBytes(totalSize / int64(b.N)) +} + +// makeTestBlocks creates fake blocks for the ancient write benchmark. +func makeTestBlocks(nblock int, txsPerBlock int) []*types.Block { + key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + signer := types.LatestSignerForChainID(big.NewInt(8)) + + // Create transactions. + txs := make([]*types.Transaction, txsPerBlock) + for i := 0; i < len(txs); i++ { + var err error + to := common.Address{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + txs[i], err = types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: 2, + GasPrice: big.NewInt(30000), + Gas: 0x45454545, + To: &to, + }) + if err != nil { + panic(err) + } + } + + // Create the blocks. + blocks := make([]*types.Block, nblock) + for i := 0; i < nblock; i++ { + header := &types.Header{ + Number: big.NewInt(int64(i)), + Extra: []byte("test block"), + } + blocks[i] = types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs}) + blocks[i].Hash() // pre-cache the block hash + } + return blocks +} + +// makeTestReceipts creates fake receipts for the ancient write benchmark. +func makeTestReceipts(n int, nPerBlock int) []types.Receipts { + receipts := make([]*types.Receipt, nPerBlock) + for i := 0; i < len(receipts); i++ { + receipts[i] = &types.Receipt{ + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 0x888888888, + Logs: make([]*types.Log, 5), + } + } + allReceipts := make([]types.Receipts, n) + for i := 0; i < n; i++ { + allReceipts[i] = receipts + } + return allReceipts +} + +type fullLogRLP struct { + Address common.Address + Topics []common.Hash + Data []byte + BlockNumber uint64 + TxHash common.Hash + TxIndex uint + BlockHash common.Hash + Index uint +} + +func newFullLogRLP(l *types.Log) *fullLogRLP { + return &fullLogRLP{ + Address: l.Address, + Topics: l.Topics, + Data: l.Data, + BlockNumber: l.BlockNumber, + TxHash: l.TxHash, + TxIndex: l.TxIndex, + BlockHash: l.BlockHash, + Index: l.Index, + } +} + +// Tests that logs associated with a single block can be retrieved. +func TestReadLogs(t *testing.T) { + db := NewMemoryDatabase() + + // Create a live block since we need metadata to reconstruct the receipt + tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil) + tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil) + + body := &types.Body{Transactions: types.Transactions{tx1, tx2}} + + // Create the two receipts to manage afterwards + receipt1 := &types.Receipt{ + Status: types.ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*types.Log{ + {Address: common.BytesToAddress([]byte{0x11})}, + {Address: common.BytesToAddress([]byte{0x01, 0x11})}, + }, + TxHash: tx1.Hash(), + ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 111111, + } + receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1}) + + receipt2 := &types.Receipt{ + PostState: common.Hash{2}.Bytes(), + CumulativeGasUsed: 2, + Logs: []*types.Log{ + {Address: common.BytesToAddress([]byte{0x22})}, + {Address: common.BytesToAddress([]byte{0x02, 0x22})}, + }, + TxHash: tx2.Hash(), + ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), + GasUsed: 222222, + } + receipt2.Bloom = types.CreateBloom(types.Receipts{receipt2}) + receipts := []*types.Receipt{receipt1, receipt2} + + hash := common.BytesToHash([]byte{0x03, 0x14}) + // Check that no receipt entries are in a pristine database + if rs := ReadReceipts(db, hash, 0, 0, params.TestChainConfig); len(rs) != 0 { + t.Fatalf("non existent receipts returned: %v", rs) + } + // Insert the body that corresponds to the receipts + WriteBody(db, hash, 0, body) + + // Insert the receipt slice into the database and check presence + WriteReceipts(db, hash, 0, receipts) + + logs := ReadLogs(db, hash, 0) + if len(logs) == 0 { + t.Fatalf("no logs returned") + } + if have, want := len(logs), 2; have != want { + t.Fatalf("unexpected number of logs returned, have %d want %d", have, want) + } + if have, want := len(logs[0]), 2; have != want { + t.Fatalf("unexpected number of logs[0] returned, have %d want %d", have, want) + } + if have, want := len(logs[1]), 2; have != want { + t.Fatalf("unexpected number of logs[1] returned, have %d want %d", have, want) + } + + for i, pr := range receipts { + for j, pl := range pr.Logs { + rlpHave, err := rlp.EncodeToBytes(newFullLogRLP(logs[i][j])) + if err != nil { + t.Fatal(err) + } + rlpWant, err := rlp.EncodeToBytes(newFullLogRLP(pl)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(rlpHave, rlpWant) { + t.Fatalf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant)) + } + } + } +} + +func TestDeriveLogFields(t *testing.T) { + // Create a few transactions to have receipts for + to2 := common.HexToAddress("0x2") + to3 := common.HexToAddress("0x3") + txs := types.Transactions{ + types.NewTx(&types.LegacyTx{ + Nonce: 1, + Value: big.NewInt(1), + Gas: 1, + GasPrice: big.NewInt(1), + }), + types.NewTx(&types.LegacyTx{ + To: &to2, + Nonce: 2, + Value: big.NewInt(2), + Gas: 2, + GasPrice: big.NewInt(2), + }), + types.NewTx(&types.AccessListTx{ + To: &to3, + Nonce: 3, + Value: big.NewInt(3), + Gas: 3, + GasPrice: big.NewInt(3), + }), + } + // Create the corresponding receipts + receipts := []*types.Receipt{ + { + Logs: []*types.Log{ + {Address: common.BytesToAddress([]byte{0x11})}, + {Address: common.BytesToAddress([]byte{0x01, 0x11})}, + }, + }, + { + Logs: []*types.Log{ + {Address: common.BytesToAddress([]byte{0x22})}, + {Address: common.BytesToAddress([]byte{0x02, 0x22})}, + }, + }, + { + Logs: []*types.Log{ + {Address: common.BytesToAddress([]byte{0x33})}, + {Address: common.BytesToAddress([]byte{0x03, 0x33})}, + }, + }, + } + + // Derive log metadata fields + number := big.NewInt(1) + hash := common.BytesToHash([]byte{0x03, 0x14}) + types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, number.Uint64(), 0, big.NewInt(0), big.NewInt(0), txs) + + // Iterate over all the computed fields and check that they're correct + logIndex := uint(0) + for i := range receipts { + for j := range receipts[i].Logs { + if receipts[i].Logs[j].BlockNumber != number.Uint64() { + t.Errorf("receipts[%d].Logs[%d].BlockNumber = %d, want %d", i, j, receipts[i].Logs[j].BlockNumber, number.Uint64()) + } + if receipts[i].Logs[j].BlockHash != hash { + t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String()) + } + if receipts[i].Logs[j].TxHash != txs[i].Hash() { + t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String()) + } + if receipts[i].Logs[j].TxIndex != uint(i) { + t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i) + } + if receipts[i].Logs[j].Index != logIndex { + t.Errorf("receipts[%d].Logs[%d].Index = %d, want %d", i, j, receipts[i].Logs[j].Index, logIndex) + } + logIndex++ + } + } +} + +func BenchmarkDecodeRLPLogs(b *testing.B) { + // Encoded receipts from block 0x14ee094309fbe8f70b65f45ebcc08fb33f126942d97464aad5eb91cfd1e2d269 + buf, err := os.ReadFile("testdata/stored_receipts.bin") + if err != nil { + b.Fatal(err) + } + b.Run("ReceiptForStorage", func(b *testing.B) { + b.ReportAllocs() + var r []*types.ReceiptForStorage + for i := 0; i < b.N; i++ { + if err := rlp.DecodeBytes(buf, &r); err != nil { + b.Fatal(err) + } + } + }) + b.Run("rlpLogs", func(b *testing.B) { + b.ReportAllocs() + var r []*receiptLogs + for i := 0; i < b.N; i++ { + if err := rlp.DecodeBytes(buf, &r); err != nil { + b.Fatal(err) + } + } + }) +} + +func TestHeadersRLPStorage(t *testing.T) { + // Have N headers in the freezer + 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 blocks + var chain []*types.Block + var pHash common.Hash + for i := 0; i < 100; i++ { + block := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(int64(i)), + Extra: []byte("test block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + ParentHash: pHash, + }) + chain = append(chain, block) + pHash = block.Hash() + } + var receipts []types.Receipts = make([]types.Receipts, 100) + // Write first half to ancients + WriteAncientBlocks(db, chain[:50], receipts[:50], big.NewInt(100)) + // Write second half to db + for i := 50; i < 100; i++ { + WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64()) + WriteBlock(db, chain[i]) + } + checkSequence := func(from, amount int) { + headersRlp := ReadHeaderRange(db, uint64(from), uint64(amount)) + if have, want := len(headersRlp), amount; have != want { + t.Fatalf("have %d headers, want %d", have, want) + } + for i, headerRlp := range headersRlp { + var header types.Header + if err := rlp.DecodeBytes(headerRlp, &header); err != nil { + t.Fatal(err) + } + if have, want := header.Number.Uint64(), uint64(from-i); have != want { + t.Fatalf("wrong number, have %d want %d", have, want) + } + } + } + checkSequence(99, 20) // Latest block and 19 parents + checkSequence(99, 50) // Latest block -> all db blocks + checkSequence(99, 51) // Latest block -> one from ancients + checkSequence(99, 52) // Latest blocks -> two from ancients + checkSequence(50, 2) // One from db, one from ancients + checkSequence(49, 1) // One from ancients + checkSequence(49, 50) // All ancient ones + checkSequence(99, 100) // All blocks + checkSequence(0, 1) // Only genesis + checkSequence(1, 1) // Only block 1 + checkSequence(1, 2) // Genesis + block 1 +} diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go new file mode 100644 index 0000000..4f2ef0a --- /dev/null +++ b/core/rawdb/accessors_indexes.go @@ -0,0 +1,181 @@ +// 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 rawdb + +import ( + "bytes" + "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/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// ReadTxLookupEntry retrieves the positional metadata associated with a transaction +// hash to allow retrieving the transaction or receipt by hash. +func ReadTxLookupEntry(db ethdb.Reader, hash common.Hash) *uint64 { + data, _ := db.Get(txLookupKey(hash)) + if len(data) == 0 { + return nil + } + // Database v6 tx lookup just stores the block number + if len(data) < common.HashLength { + number := new(big.Int).SetBytes(data).Uint64() + return &number + } + // Database v4-v5 tx lookup format just stores the hash + if len(data) == common.HashLength { + return ReadHeaderNumber(db, common.BytesToHash(data)) + } + // Finally try database v3 tx lookup format + var entry LegacyTxLookupEntry + if err := rlp.DecodeBytes(data, &entry); err != nil { + log.Error("Invalid transaction lookup entry RLP", "hash", hash, "blob", data, "err", err) + return nil + } + return &entry.BlockIndex +} + +// writeTxLookupEntry stores a positional metadata for a transaction, +// enabling hash based transaction and receipt lookups. +func writeTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash, numberBytes []byte) { + if err := db.Put(txLookupKey(hash), numberBytes); err != nil { + log.Crit("Failed to store transaction lookup entry", "err", err) + } +} + +// WriteTxLookupEntries is identical to WriteTxLookupEntry, but it works on +// a list of hashes +func WriteTxLookupEntries(db ethdb.KeyValueWriter, number uint64, hashes []common.Hash) { + numberBytes := new(big.Int).SetUint64(number).Bytes() + for _, hash := range hashes { + writeTxLookupEntry(db, hash, numberBytes) + } +} + +// WriteTxLookupEntriesByBlock stores a positional metadata for every transaction from +// a block, enabling hash based transaction and receipt lookups. +func WriteTxLookupEntriesByBlock(db ethdb.KeyValueWriter, block *types.Block) { + numberBytes := block.Number().Bytes() + for _, tx := range block.Transactions() { + writeTxLookupEntry(db, tx.Hash(), numberBytes) + } +} + +// DeleteTxLookupEntry removes all transaction data associated with a hash. +func DeleteTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(txLookupKey(hash)); err != nil { + log.Crit("Failed to delete transaction lookup entry", "err", err) + } +} + +// DeleteTxLookupEntries removes all transaction lookups for a given block. +func DeleteTxLookupEntries(db ethdb.KeyValueWriter, hashes []common.Hash) { + for _, hash := range hashes { + DeleteTxLookupEntry(db, hash) + } +} + +// ReadTransaction retrieves a specific transaction from the database, along with +// its added positional metadata. +func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { + blockNumber := ReadTxLookupEntry(db, hash) + if blockNumber == nil { + return nil, common.Hash{}, 0, 0 + } + blockHash := ReadCanonicalHash(db, *blockNumber) + if blockHash == (common.Hash{}) { + return nil, common.Hash{}, 0, 0 + } + body := ReadBody(db, blockHash, *blockNumber) + if body == nil { + log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash) + return nil, common.Hash{}, 0, 0 + } + for txIndex, tx := range body.Transactions { + if tx.Hash() == hash { + return tx, blockHash, *blockNumber, uint64(txIndex) + } + } + log.Error("Transaction not found", "number", *blockNumber, "hash", blockHash, "txhash", hash) + return nil, common.Hash{}, 0, 0 +} + +// ReadReceipt retrieves a specific transaction receipt from the database, along with +// its added positional metadata. +func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) { + // Retrieve the context of the receipt based on the transaction hash + blockNumber := ReadTxLookupEntry(db, hash) + if blockNumber == nil { + return nil, common.Hash{}, 0, 0 + } + blockHash := ReadCanonicalHash(db, *blockNumber) + if blockHash == (common.Hash{}) { + return nil, common.Hash{}, 0, 0 + } + blockHeader := ReadHeader(db, blockHash, *blockNumber) + if blockHeader == nil { + return nil, common.Hash{}, 0, 0 + } + // Read all the receipts from the block and return the one with the matching hash + receipts := ReadReceipts(db, blockHash, *blockNumber, blockHeader.Time, config) + for receiptIndex, receipt := range receipts { + if receipt.TxHash == hash { + return receipt, blockHash, *blockNumber, uint64(receiptIndex) + } + } + log.Error("Receipt not found", "number", *blockNumber, "hash", blockHash, "txhash", hash) + return nil, common.Hash{}, 0, 0 +} + +// ReadBloomBits retrieves the compressed bloom bit vector belonging to the given +// section and bit index from the. +func ReadBloomBits(db ethdb.KeyValueReader, bit uint, section uint64, head common.Hash) ([]byte, error) { + return db.Get(bloomBitsKey(bit, section, head)) +} + +// WriteBloomBits stores the compressed bloom bits vector belonging to the given +// section and bit index. +func WriteBloomBits(db ethdb.KeyValueWriter, bit uint, section uint64, head common.Hash, bits []byte) { + if err := db.Put(bloomBitsKey(bit, section, head), bits); err != nil { + log.Crit("Failed to store bloom bits", "err", err) + } +} + +// DeleteBloombits removes all compressed bloom bits vector belonging to the +// given section range and bit index. +func DeleteBloombits(db ethdb.Database, bit uint, from uint64, to uint64) { + start, end := bloomBitsKey(bit, from, common.Hash{}), bloomBitsKey(bit, to, common.Hash{}) + it := db.NewIterator(nil, start) + defer it.Release() + + for it.Next() { + if bytes.Compare(it.Key(), end) >= 0 { + break + } + if len(it.Key()) != len(bloomBitsPrefix)+2+8+32 { + continue + } + db.Delete(it.Key()) + } + if it.Error() != nil { + log.Crit("Failed to delete bloom bits", "err", it.Error()) + } +} diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go new file mode 100644 index 0000000..78dba00 --- /dev/null +++ b/core/rawdb/accessors_indexes_test.go @@ -0,0 +1,156 @@ +// 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 rawdb + +import ( + "bytes" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/blocktest" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +var newTestHasher = blocktest.NewHasher + +// Tests that positional lookup metadata can be stored and retrieved. +func TestLookupStorage(t *testing.T) { + tests := []struct { + name string + writeTxLookupEntriesByBlock func(ethdb.Writer, *types.Block) + }{ + { + "DatabaseV6", + func(db ethdb.Writer, block *types.Block) { + WriteTxLookupEntriesByBlock(db, block) + }, + }, + { + "DatabaseV4-V5", + func(db ethdb.Writer, block *types.Block) { + for _, tx := range block.Transactions() { + db.Put(txLookupKey(tx.Hash()), block.Hash().Bytes()) + } + }, + }, + { + "DatabaseV3", + func(db ethdb.Writer, block *types.Block) { + for index, tx := range block.Transactions() { + entry := LegacyTxLookupEntry{ + BlockHash: block.Hash(), + BlockIndex: block.NumberU64(), + Index: uint64(index), + } + data, _ := rlp.EncodeToBytes(entry) + db.Put(txLookupKey(tx.Hash()), data) + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + db := NewMemoryDatabase() + + tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) + tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22}) + tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) + txs := []*types.Transaction{tx1, tx2, tx3} + + block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher()) + + // Check that no transactions entries are in a pristine database + for i, tx := range txs { + if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { + t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn) + } + } + // Insert all the transactions into the database, and verify contents + WriteCanonicalHash(db, block.Hash(), block.NumberU64()) + WriteBlock(db, block) + tc.writeTxLookupEntriesByBlock(db, block) + + for i, tx := range txs { + if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil { + t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash()) + } else { + if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) { + t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i) + } + if tx.Hash() != txn.Hash() { + t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx) + } + } + } + // Delete the transactions and check purge + for i, tx := range txs { + DeleteTxLookupEntry(db, tx.Hash()) + if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { + t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) + } + } + }) + } +} + +func TestDeleteBloomBits(t *testing.T) { + // Prepare testing data + db := NewMemoryDatabase() + for i := uint(0); i < 2; i++ { + for s := uint64(0); s < 2; s++ { + WriteBloomBits(db, i, s, params.MainnetGenesisHash, []byte{0x01, 0x02}) + WriteBloomBits(db, i, s, params.SepoliaGenesisHash, []byte{0x01, 0x02}) + } + } + check := func(bit uint, section uint64, head common.Hash, exist bool) { + bits, _ := ReadBloomBits(db, bit, section, head) + if exist && !bytes.Equal(bits, []byte{0x01, 0x02}) { + t.Fatalf("Bloombits mismatch") + } + if !exist && len(bits) > 0 { + t.Fatalf("Bloombits should be removed") + } + } + // Check the existence of written data. + check(0, 0, params.MainnetGenesisHash, true) + check(0, 0, params.SepoliaGenesisHash, true) + + // Check the existence of deleted data. + DeleteBloombits(db, 0, 0, 1) + check(0, 0, params.MainnetGenesisHash, false) + check(0, 0, params.SepoliaGenesisHash, false) + check(0, 1, params.MainnetGenesisHash, true) + check(0, 1, params.SepoliaGenesisHash, true) + + // Check the existence of deleted data. + DeleteBloombits(db, 0, 0, 2) + check(0, 0, params.MainnetGenesisHash, false) + check(0, 0, params.SepoliaGenesisHash, false) + check(0, 1, params.MainnetGenesisHash, false) + check(0, 1, params.SepoliaGenesisHash, false) + + // Bit1 shouldn't be affect. + check(1, 0, params.MainnetGenesisHash, true) + check(1, 0, params.SepoliaGenesisHash, true) + check(1, 1, params.MainnetGenesisHash, true) + check(1, 1, params.SepoliaGenesisHash, true) +} diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go new file mode 100644 index 0000000..859566f --- /dev/null +++ b/core/rawdb/accessors_metadata.go @@ -0,0 +1,189 @@ +// 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 rawdb + +import ( + "encoding/json" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// ReadDatabaseVersion retrieves the version number of the database. +func ReadDatabaseVersion(db ethdb.KeyValueReader) *uint64 { + var version uint64 + + enc, _ := db.Get(databaseVersionKey) + if len(enc) == 0 { + return nil + } + if err := rlp.DecodeBytes(enc, &version); err != nil { + return nil + } + + return &version +} + +// WriteDatabaseVersion stores the version number of the database +func WriteDatabaseVersion(db ethdb.KeyValueWriter, version uint64) { + enc, err := rlp.EncodeToBytes(version) + if err != nil { + log.Crit("Failed to encode database version", "err", err) + } + if err = db.Put(databaseVersionKey, enc); err != nil { + log.Crit("Failed to store the database version", "err", err) + } +} + +// ReadChainConfig retrieves the consensus settings based on the given genesis hash. +func ReadChainConfig(db ethdb.KeyValueReader, hash common.Hash) *params.ChainConfig { + data, _ := db.Get(configKey(hash)) + if len(data) == 0 { + return nil + } + var config params.ChainConfig + if err := json.Unmarshal(data, &config); err != nil { + log.Error("Invalid chain config JSON", "hash", hash, "err", err) + return nil + } + return &config +} + +// WriteChainConfig writes the chain config settings to the database. +func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.ChainConfig) { + if cfg == nil { + return + } + data, err := json.Marshal(cfg) + if err != nil { + log.Crit("Failed to JSON encode chain config", "err", err) + } + if err := db.Put(configKey(hash), data); err != nil { + log.Crit("Failed to store chain config", "err", err) + } +} + +// ReadGenesisStateSpec retrieves the genesis state specification based on the +// given genesis (block-)hash. +func ReadGenesisStateSpec(db ethdb.KeyValueReader, blockhash common.Hash) []byte { + data, _ := db.Get(genesisStateSpecKey(blockhash)) + return data +} + +// WriteGenesisStateSpec writes the genesis state specification into the disk. +func WriteGenesisStateSpec(db ethdb.KeyValueWriter, blockhash common.Hash, data []byte) { + if err := db.Put(genesisStateSpecKey(blockhash), data); err != nil { + log.Crit("Failed to store genesis state", "err", err) + } +} + +// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the +// database +type crashList struct { + Discarded uint64 // how many ucs have we deleted + Recent []uint64 // unix timestamps of 10 latest unclean shutdowns +} + +const crashesToKeep = 10 + +// PushUncleanShutdownMarker appends a new unclean shutdown marker and returns +// the previous data +// - a list of timestamps +// - a count of how many old unclean-shutdowns have been discarded +func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) { + var uncleanShutdowns crashList + // Read old data + 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)) + copy(previous, uncleanShutdowns.Recent) + // Add a new (but cap it) + uncleanShutdowns.Recent = append(uncleanShutdowns.Recent, uint64(time.Now().Unix())) + if count := len(uncleanShutdowns.Recent); count > crashesToKeep+1 { + numDel := count - (crashesToKeep + 1) + uncleanShutdowns.Recent = uncleanShutdowns.Recent[numDel:] + uncleanShutdowns.Discarded += uint64(numDel) + } + // And save it again + data, _ := rlp.EncodeToBytes(uncleanShutdowns) + if err := db.Put(uncleanShutdownKey, data); err != nil { + log.Warn("Failed to write unclean-shutdown marker", "err", err) + return nil, 0, err + } + return previous, discarded, nil +} + +// PopUncleanShutdownMarker removes the last unclean shutdown marker +func PopUncleanShutdownMarker(db ethdb.KeyValueStore) { + 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 { + log.Error("Error decoding unclean shutdown markers", "error", err) // Should mos def _not_ happen + } + if l := len(uncleanShutdowns.Recent); l > 0 { + uncleanShutdowns.Recent = uncleanShutdowns.Recent[:l-1] + } + data, _ := rlp.EncodeToBytes(uncleanShutdowns) + if err := db.Put(uncleanShutdownKey, data); err != nil { + log.Warn("Failed to clear unclean-shutdown marker", "err", err) + } +} + +// UpdateUncleanShutdownMarker updates the last marker's timestamp to now. +func UpdateUncleanShutdownMarker(db ethdb.KeyValueStore) { + 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 { + log.Warn("Error decoding unclean shutdown markers", "error", err) + } + // This shouldn't happen because we push a marker on Backend instantiation + count := len(uncleanShutdowns.Recent) + if count == 0 { + log.Warn("No unclean shutdown marker to update") + return + } + uncleanShutdowns.Recent[count-1] = uint64(time.Now().Unix()) + data, _ := rlp.EncodeToBytes(uncleanShutdowns) + if err := db.Put(uncleanShutdownKey, data); err != nil { + log.Warn("Failed to write unclean-shutdown marker", "err", err) + } +} + +// ReadTransitionStatus retrieves the eth2 transition status from the database +func ReadTransitionStatus(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(transitionStatusKey) + return data +} + +// WriteTransitionStatus stores the eth2 transition status to the database +func WriteTransitionStatus(db ethdb.KeyValueWriter, data []byte) { + if err := db.Put(transitionStatusKey, data); err != nil { + log.Crit("Failed to store the eth2 transition status", "err", err) + } +} diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go new file mode 100644 index 0000000..5cea581 --- /dev/null +++ b/core/rawdb/accessors_snapshot.go @@ -0,0 +1,210 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// ReadSnapshotDisabled retrieves if the snapshot maintenance is disabled. +func ReadSnapshotDisabled(db ethdb.KeyValueReader) bool { + disabled, _ := db.Has(snapshotDisabledKey) + return disabled +} + +// WriteSnapshotDisabled stores the snapshot pause flag. +func WriteSnapshotDisabled(db ethdb.KeyValueWriter) { + if err := db.Put(snapshotDisabledKey, []byte("42")); err != nil { + log.Crit("Failed to store snapshot disabled flag", "err", err) + } +} + +// DeleteSnapshotDisabled deletes the flag keeping the snapshot maintenance disabled. +func DeleteSnapshotDisabled(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotDisabledKey); err != nil { + log.Crit("Failed to remove snapshot disabled flag", "err", err) + } +} + +// ReadSnapshotRoot retrieves the root of the block whose state is contained in +// the persisted snapshot. +func ReadSnapshotRoot(db ethdb.KeyValueReader) common.Hash { + data, _ := db.Get(SnapshotRootKey) + if len(data) != common.HashLength { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteSnapshotRoot stores the root of the block whose state is contained in +// the persisted snapshot. +func WriteSnapshotRoot(db ethdb.KeyValueWriter, root common.Hash) { + if err := db.Put(SnapshotRootKey, root[:]); err != nil { + log.Crit("Failed to store snapshot root", "err", err) + } +} + +// DeleteSnapshotRoot deletes the hash of the block whose state is contained in +// the persisted snapshot. Since snapshots are not immutable, this method can +// be used during updates, so a crash or failure will mark the entire snapshot +// invalid. +func DeleteSnapshotRoot(db ethdb.KeyValueWriter) { + if err := db.Delete(SnapshotRootKey); err != nil { + log.Crit("Failed to remove snapshot root", "err", err) + } +} + +// ReadAccountSnapshot retrieves the snapshot entry of an account trie leaf. +func ReadAccountSnapshot(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(accountSnapshotKey(hash)) + return data +} + +// WriteAccountSnapshot stores the snapshot entry of an account trie leaf. +func WriteAccountSnapshot(db ethdb.KeyValueWriter, hash common.Hash, entry []byte) { + if err := db.Put(accountSnapshotKey(hash), entry); err != nil { + log.Crit("Failed to store account snapshot", "err", err) + } +} + +// DeleteAccountSnapshot removes the snapshot entry of an account trie leaf. +func DeleteAccountSnapshot(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(accountSnapshotKey(hash)); err != nil { + log.Crit("Failed to delete account snapshot", "err", err) + } +} + +// ReadStorageSnapshot retrieves the snapshot entry of a storage trie leaf. +func ReadStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash common.Hash) []byte { + data, _ := db.Get(storageSnapshotKey(accountHash, storageHash)) + return data +} + +// WriteStorageSnapshot stores the snapshot entry of a storage trie leaf. +func WriteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash, entry []byte) { + if err := db.Put(storageSnapshotKey(accountHash, storageHash), entry); err != nil { + log.Crit("Failed to store storage snapshot", "err", err) + } +} + +// DeleteStorageSnapshot removes the snapshot entry of a storage trie leaf. +func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash) { + if err := db.Delete(storageSnapshotKey(accountHash, storageHash)); err != nil { + log.Crit("Failed to delete storage snapshot", "err", err) + } +} + +// IterateStorageSnapshots returns an iterator for walking the entire storage +// space of a specific account. +func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator { + return NewKeyLengthIterator(db.NewIterator(storageSnapshotsKey(accountHash), nil), len(SnapshotStoragePrefix)+2*common.HashLength) +} + +// ReadSnapshotJournal retrieves the serialized in-memory diff layers saved at +// the last shutdown. The blob is expected to be max a few 10s of megabytes. +func ReadSnapshotJournal(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(snapshotJournalKey) + return data +} + +// WriteSnapshotJournal stores the serialized in-memory diff layers to save at +// shutdown. The blob is expected to be max a few 10s of megabytes. +func WriteSnapshotJournal(db ethdb.KeyValueWriter, journal []byte) { + if err := db.Put(snapshotJournalKey, journal); err != nil { + log.Crit("Failed to store snapshot journal", "err", err) + } +} + +// DeleteSnapshotJournal deletes the serialized in-memory diff layers saved at +// the last shutdown +func DeleteSnapshotJournal(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotJournalKey); err != nil { + log.Crit("Failed to remove snapshot journal", "err", err) + } +} + +// ReadSnapshotGenerator retrieves the serialized snapshot generator saved at +// the last shutdown. +func ReadSnapshotGenerator(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(snapshotGeneratorKey) + return data +} + +// WriteSnapshotGenerator stores the serialized snapshot generator to save at +// shutdown. +func WriteSnapshotGenerator(db ethdb.KeyValueWriter, generator []byte) { + if err := db.Put(snapshotGeneratorKey, generator); err != nil { + log.Crit("Failed to store snapshot generator", "err", err) + } +} + +// DeleteSnapshotGenerator deletes the serialized snapshot generator saved at +// the last shutdown +func DeleteSnapshotGenerator(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotGeneratorKey); err != nil { + log.Crit("Failed to remove snapshot generator", "err", err) + } +} + +// ReadSnapshotRecoveryNumber retrieves the block number of the last persisted +// snapshot layer. +func ReadSnapshotRecoveryNumber(db ethdb.KeyValueReader) *uint64 { + data, _ := db.Get(snapshotRecoveryKey) + if len(data) == 0 { + return nil + } + if len(data) != 8 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// WriteSnapshotRecoveryNumber stores the block number of the last persisted +// snapshot layer. +func WriteSnapshotRecoveryNumber(db ethdb.KeyValueWriter, number uint64) { + var buf [8]byte + binary.BigEndian.PutUint64(buf[:], number) + if err := db.Put(snapshotRecoveryKey, buf[:]); err != nil { + log.Crit("Failed to store snapshot recovery number", "err", err) + } +} + +// DeleteSnapshotRecoveryNumber deletes the block number of the last persisted +// snapshot layer. +func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotRecoveryKey); err != nil { + log.Crit("Failed to remove snapshot recovery number", "err", err) + } +} + +// ReadSnapshotSyncStatus retrieves the serialized sync status saved at shutdown. +func ReadSnapshotSyncStatus(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(snapshotSyncStatusKey) + return data +} + +// WriteSnapshotSyncStatus stores the serialized sync status to save at shutdown. +func WriteSnapshotSyncStatus(db ethdb.KeyValueWriter, status []byte) { + if err := db.Put(snapshotSyncStatusKey, status); err != nil { + log.Crit("Failed to store snapshot sync status", "err", err) + } +} diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go new file mode 100644 index 0000000..9ce58e7 --- /dev/null +++ b/core/rawdb/accessors_state.go @@ -0,0 +1,266 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// ReadPreimage retrieves a single preimage of the provided hash. +func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(preimageKey(hash)) + return data +} + +// WritePreimages writes the provided set of preimages to the database. +func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) { + for hash, preimage := range preimages { + if err := db.Put(preimageKey(hash), preimage); err != nil { + log.Crit("Failed to store trie preimage", "err", err) + } + } + preimageCounter.Inc(int64(len(preimages))) + preimageHitCounter.Inc(int64(len(preimages))) +} + +// ReadCode retrieves the contract code of the provided code hash. +func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte { + // Try with the prefixed code scheme first, if not then try with legacy + // scheme. + data := ReadCodeWithPrefix(db, hash) + if len(data) != 0 { + return data + } + data, _ = db.Get(hash.Bytes()) + return data +} + +// ReadCodeWithPrefix retrieves the contract code of the provided code hash. +// The main difference between this function and ReadCode is this function +// will only check the existence with latest scheme(with prefix). +func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(codeKey(hash)) + return data +} + +// HasCode checks if the contract code corresponding to the +// provided code hash is present in the db. +func HasCode(db ethdb.KeyValueReader, hash common.Hash) bool { + // Try with the prefixed code scheme first, if not then try with legacy + // scheme. + if ok := HasCodeWithPrefix(db, hash); ok { + return true + } + ok, _ := db.Has(hash.Bytes()) + return ok +} + +// HasCodeWithPrefix checks if the contract code corresponding to the +// provided code hash is present in the db. This function will only check +// presence using the prefix-scheme. +func HasCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) bool { + ok, _ := db.Has(codeKey(hash)) + return ok +} + +// WriteCode writes the provided contract code database. +func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) { + if err := db.Put(codeKey(hash), code); err != nil { + log.Crit("Failed to store contract code", "err", err) + } +} + +// DeleteCode deletes the specified contract code from the database. +func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(codeKey(hash)); err != nil { + log.Crit("Failed to delete contract code", "err", err) + } +} + +// ReadStateID retrieves the state id with the provided state root. +func ReadStateID(db ethdb.KeyValueReader, root common.Hash) *uint64 { + data, err := db.Get(stateIDKey(root)) + if err != nil || len(data) == 0 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// WriteStateID writes the provided state lookup to database. +func WriteStateID(db ethdb.KeyValueWriter, root common.Hash, id uint64) { + var buff [8]byte + binary.BigEndian.PutUint64(buff[:], id) + if err := db.Put(stateIDKey(root), buff[:]); err != nil { + log.Crit("Failed to store state ID", "err", err) + } +} + +// DeleteStateID deletes the specified state lookup from the database. +func DeleteStateID(db ethdb.KeyValueWriter, root common.Hash) { + if err := db.Delete(stateIDKey(root)); err != nil { + log.Crit("Failed to delete state ID", "err", err) + } +} + +// ReadPersistentStateID retrieves the id of the persistent state from the database. +func ReadPersistentStateID(db ethdb.KeyValueReader) uint64 { + data, _ := db.Get(persistentStateIDKey) + if len(data) != 8 { + return 0 + } + return binary.BigEndian.Uint64(data) +} + +// WritePersistentStateID stores the id of the persistent state into database. +func WritePersistentStateID(db ethdb.KeyValueWriter, number uint64) { + if err := db.Put(persistentStateIDKey, encodeBlockNumber(number)); err != nil { + log.Crit("Failed to store the persistent state ID", "err", err) + } +} + +// ReadTrieJournal retrieves the serialized in-memory trie nodes of layers saved at +// the last shutdown. +func ReadTrieJournal(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(trieJournalKey) + return data +} + +// WriteTrieJournal stores the serialized in-memory trie nodes of layers to save at +// shutdown. +func WriteTrieJournal(db ethdb.KeyValueWriter, journal []byte) { + if err := db.Put(trieJournalKey, journal); err != nil { + log.Crit("Failed to store tries journal", "err", err) + } +} + +// DeleteTrieJournal deletes the serialized in-memory trie nodes of layers saved at +// the last shutdown. +func DeleteTrieJournal(db ethdb.KeyValueWriter) { + if err := db.Delete(trieJournalKey); err != nil { + log.Crit("Failed to remove tries journal", "err", err) + } +} + +// ReadStateHistoryMeta retrieves the metadata corresponding to the specified +// state history. Compute the position of state history in freezer by minus +// one since the id of first state history starts from one(zero for initial +// state). +func ReadStateHistoryMeta(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryMeta, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateHistoryMetaList retrieves a batch of meta objects with the specified +// start position and count. Compute the position of state history in freezer by +// minus one since the id of first state history starts from one(zero for initial +// state). +func ReadStateHistoryMetaList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, error) { + return db.AncientRange(stateHistoryMeta, start-1, count, 0) +} + +// ReadStateAccountIndex retrieves the state root corresponding to the specified +// state history. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryAccountIndex, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateStorageIndex retrieves the state root corresponding to the specified +// state history. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryStorageIndex, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateAccountHistory retrieves the state root corresponding to the specified +// state history. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryAccountData, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateStorageHistory retrieves the state root corresponding to the specified +// state history. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryStorageData, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateHistory retrieves the state history from database with provided id. +// Compute the position of state history in freezer by minus one since the id +// of first state history starts from one(zero for initial state). +func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []byte, []byte, []byte, error) { + meta, err := db.Ancient(stateHistoryMeta, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + accountIndex, err := db.Ancient(stateHistoryAccountIndex, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + storageIndex, err := db.Ancient(stateHistoryStorageIndex, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + accountData, err := db.Ancient(stateHistoryAccountData, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + storageData, err := db.Ancient(stateHistoryStorageData, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + return meta, accountIndex, storageIndex, accountData, storageData, nil +} + +// WriteStateHistory writes the provided state history to database. Compute the +// position of state history in freezer by minus one since the id of first state +// history starts from one(zero for initial state). +func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIndex []byte, storageIndex []byte, accounts []byte, storages []byte) { + db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + op.AppendRaw(stateHistoryMeta, id-1, meta) + op.AppendRaw(stateHistoryAccountIndex, id-1, accountIndex) + op.AppendRaw(stateHistoryStorageIndex, id-1, storageIndex) + op.AppendRaw(stateHistoryAccountData, id-1, accounts) + op.AppendRaw(stateHistoryStorageData, id-1, storages) + return nil + }) +} diff --git a/core/rawdb/accessors_sync.go b/core/rawdb/accessors_sync.go new file mode 100644 index 0000000..2dc08b3 --- /dev/null +++ b/core/rawdb/accessors_sync.go @@ -0,0 +1,100 @@ +// 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 rawdb + +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/rlp" +) + +// ReadSkeletonSyncStatus retrieves the serialized sync status saved at shutdown. +func ReadSkeletonSyncStatus(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(skeletonSyncStatusKey) + return data +} + +// WriteSkeletonSyncStatus stores the serialized sync status to save at shutdown. +func WriteSkeletonSyncStatus(db ethdb.KeyValueWriter, status []byte) { + if err := db.Put(skeletonSyncStatusKey, status); err != nil { + log.Crit("Failed to store skeleton sync status", "err", err) + } +} + +// DeleteSkeletonSyncStatus deletes the serialized sync status saved at the last +// shutdown +func DeleteSkeletonSyncStatus(db ethdb.KeyValueWriter) { + if err := db.Delete(skeletonSyncStatusKey); err != nil { + log.Crit("Failed to remove skeleton sync status", "err", err) + } +} + +// ReadSkeletonHeader retrieves a block header from the skeleton sync store, +func ReadSkeletonHeader(db ethdb.KeyValueReader, number uint64) *types.Header { + data, _ := db.Get(skeletonHeaderKey(number)) + if len(data) == 0 { + return nil + } + header := new(types.Header) + if err := rlp.DecodeBytes(data, header); err != nil { + log.Error("Invalid skeleton header RLP", "number", number, "err", err) + return nil + } + return header +} + +// WriteSkeletonHeader stores a block header into the skeleton sync store. +func WriteSkeletonHeader(db ethdb.KeyValueWriter, header *types.Header) { + data, err := rlp.EncodeToBytes(header) + if err != nil { + log.Crit("Failed to RLP encode header", "err", err) + } + key := skeletonHeaderKey(header.Number.Uint64()) + if err := db.Put(key, data); err != nil { + log.Crit("Failed to store skeleton header", "err", err) + } +} + +// DeleteSkeletonHeader removes all block header data associated with a hash. +func DeleteSkeletonHeader(db ethdb.KeyValueWriter, number uint64) { + if err := db.Delete(skeletonHeaderKey(number)); err != nil { + 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 new file mode 100644 index 0000000..44eb715 --- /dev/null +++ b/core/rawdb/accessors_trie.go @@ -0,0 +1,302 @@ +// 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 rawdb + +import ( + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// HashScheme is the legacy hash-based state scheme with which trie nodes are +// stored in the disk with node hash as the database key. The advantage of this +// scheme is that different versions of trie nodes can be stored in disk, which +// is very beneficial for constructing archive nodes. The drawback is it will +// store different trie nodes on the same path to different locations on the disk +// with no data locality, and it's unfriendly for designing state pruning. +// +// 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 = "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 +// version of state data in the disk, which means that the state pruning operation +// 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 = "path" + +// hasher is used to compute the sha256 hash of the provided data. +type hasher struct{ sha crypto.KeccakState } + +var hasherPool = sync.Pool{ + New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} }, +} + +func newHasher() *hasher { + return hasherPool.Get().(*hasher) +} + +func (h *hasher) hash(data []byte) common.Hash { + return crypto.HashData(h.sha, data) +} + +func (h *hasher) release() { + hasherPool.Put(h) +} + +// ReadAccountTrieNode retrieves the account trie node with the specified node path. +func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte { + data, _ := db.Get(accountTrieNodeKey(path)) + return data +} + +// HasAccountTrieNode checks the presence of the account trie node with the +// specified node path, regardless of the node hash. +func HasAccountTrieNode(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 { + log.Crit("Failed to store account trie node", "err", err) + } +} + +// DeleteAccountTrieNode deletes the specified account trie node from the database. +func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) { + if err := db.Delete(accountTrieNodeKey(path)); err != nil { + log.Crit("Failed to delete account trie node", "err", err) + } +} + +// ReadStorageTrieNode retrieves the storage trie node with the specified node path. +func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte { + data, _ := db.Get(storageTrieNodeKey(accountHash, path)) + return data +} + +// HasStorageTrieNode checks the presence of the storage trie node with the +// specified account hash and node path, regardless of the node hash. +func HasStorageTrieNode(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 { + log.Crit("Failed to store storage trie node", "err", err) + } +} + +// DeleteStorageTrieNode deletes the specified storage trie node from the database. +func DeleteStorageTrieNode(db ethdb.KeyValueWriter, accountHash common.Hash, path []byte) { + if err := db.Delete(storageTrieNodeKey(accountHash, path)); err != nil { + log.Crit("Failed to delete storage trie node", "err", err) + } +} + +// ReadLegacyTrieNode retrieves the legacy trie node with the given +// associated node hash. +func ReadLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, err := db.Get(hash.Bytes()) + if err != nil { + return nil + } + return data +} + +// HasLegacyTrieNode checks if the trie node with the provided hash is present in db. +func HasLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) bool { + ok, _ := db.Has(hash.Bytes()) + return ok +} + +// WriteLegacyTrieNode writes the provided legacy trie node to database. +func WriteLegacyTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) { + if err := db.Put(hash.Bytes(), node); err != nil { + log.Crit("Failed to store legacy trie node", "err", err) + } +} + +// DeleteLegacyTrieNode deletes the specified legacy trie node from database. +func DeleteLegacyTrieNode(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(hash.Bytes()); err != nil { + log.Crit("Failed to delete legacy trie node", "err", err) + } +} + +// HasTrieNode checks the trie node presence with the provided node info and +// the associated node hash. +func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) bool { + switch scheme { + case HashScheme: + return HasLegacyTrieNode(db, hash) + case PathScheme: + var blob []byte + if owner == (common.Hash{}) { + blob = ReadAccountTrieNode(db, path) + } else { + blob = ReadStorageTrieNode(db, owner, path) + } + if len(blob) == 0 { + return false + } + h := newHasher() + defer h.release() + return h.hash(blob) == hash // exists but not match + default: + panic(fmt.Sprintf("Unknown scheme %v", scheme)) + } +} + +// ReadTrieNode retrieves the trie node from database with the provided node info +// and associated node hash. +func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte { + switch scheme { + case HashScheme: + return ReadLegacyTrieNode(db, hash) + case PathScheme: + var blob []byte + if owner == (common.Hash{}) { + blob = ReadAccountTrieNode(db, path) + } else { + blob = ReadStorageTrieNode(db, owner, path) + } + if len(blob) == 0 { + return nil + } + h := newHasher() + defer h.release() + if h.hash(blob) != hash { + return nil // exists but not match + } + return blob + default: + panic(fmt.Sprintf("Unknown scheme %v", scheme)) + } +} + +// WriteTrieNode writes the trie node into database with the provided node info. +// +// hash-scheme requires the node hash as the identifier. +// path-scheme requires the node owner and path as the identifier. +func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) { + switch scheme { + case HashScheme: + WriteLegacyTrieNode(db, hash, node) + case PathScheme: + if owner == (common.Hash{}) { + WriteAccountTrieNode(db, path, node) + } else { + WriteStorageTrieNode(db, owner, path, node) + } + default: + panic(fmt.Sprintf("Unknown scheme %v", scheme)) + } +} + +// DeleteTrieNode deletes the trie node from database with the provided node info. +// +// hash-scheme requires the node hash as the identifier. +// path-scheme requires the node owner and path as the identifier. +func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) { + switch scheme { + case HashScheme: + DeleteLegacyTrieNode(db, hash) + case PathScheme: + if owner == (common.Hash{}) { + DeleteAccountTrieNode(db, path) + } else { + DeleteStorageTrieNode(db, owner, path) + } + default: + 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. + if HasAccountTrieNode(db, nil) { + 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 + } + if !HasLegacyTrieNode(db, header.Root) { + 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 path-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 == "" { + log.Info("State schema set to default", "scheme", "path") + return PathScheme, nil // use default scheme for empty database + } + 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 new file mode 100644 index 0000000..44867de --- /dev/null +++ b/core/rawdb/ancient_scheme.go @@ -0,0 +1,93 @@ +// 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 rawdb + +import ( + "path/filepath" + + "github.com/ethereum/go-ethereum/ethdb" +) + +// The list of table names of chain freezer. +const ( + // ChainFreezerHeaderTable indicates the name of the freezer header table. + ChainFreezerHeaderTable = "headers" + + // ChainFreezerHashTable indicates the name of the freezer canonical hash table. + ChainFreezerHashTable = "hashes" + + // ChainFreezerBodiesTable indicates the name of the freezer block body table. + ChainFreezerBodiesTable = "bodies" + + // ChainFreezerReceiptTable indicates the name of the freezer receipts table. + ChainFreezerReceiptTable = "receipts" + + // ChainFreezerDifficultyTable indicates the name of the freezer total difficulty table. + ChainFreezerDifficultyTable = "diffs" +) + +// chainFreezerNoSnappy configures whether compression is disabled for the ancient-tables. +// Hashes and difficulties don't compress well. +var chainFreezerNoSnappy = map[string]bool{ + ChainFreezerHeaderTable: false, + ChainFreezerHashTable: true, + ChainFreezerBodiesTable: false, + ChainFreezerReceiptTable: false, + ChainFreezerDifficultyTable: true, +} + +const ( + // stateHistoryTableSize defines the maximum size of freezer data files. + stateHistoryTableSize = 2 * 1000 * 1000 * 1000 + + // stateHistoryAccountIndex indicates the name of the freezer state history table. + stateHistoryMeta = "history.meta" + stateHistoryAccountIndex = "account.index" + stateHistoryStorageIndex = "storage.index" + stateHistoryAccountData = "account.data" + stateHistoryStorageData = "storage.data" +) + +var stateFreezerNoSnappy = map[string]bool{ + stateHistoryMeta: true, + stateHistoryAccountIndex: false, + stateHistoryStorageIndex: false, + stateHistoryAccountData: false, + stateHistoryStorageData: false, +} + +// The list of identifiers of ancient stores. +var ( + ChainFreezerName = "chain" // the folder name of chain segment ancient store. + StateFreezerName = "state" // the folder name of reverse diff ancient store. +) + +// freezers the collections of all builtin freezers. +var freezers = []string{ChainFreezerName, StateFreezerName} + +// NewStateFreezer initializes the ancient store for state history. +// +// - if the empty directory is given, initializes the pure in-memory +// state freezer (e.g. dev mode). +// - if non-empty directory is given, initializes the regular file-based +// state freezer. +func NewStateFreezer(ancientDir string, readOnly bool) (ethdb.ResettableAncientStore, error) { + if ancientDir == "" { + return NewMemoryFreezer(readOnly, stateFreezerNoSnappy), nil + } + 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 new file mode 100644 index 0000000..1c69639 --- /dev/null +++ b/core/rawdb/ancient_utils.go @@ -0,0 +1,146 @@ +// 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 rawdb + +import ( + "fmt" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +type tableSize struct { + name string + size common.StorageSize +} + +// freezerInfo contains the basic information of the freezer. +type freezerInfo struct { + name string // The identifier of freezer + head uint64 // The number of last stored item in the freezer + tail uint64 // The number of first stored item in the freezer + sizes []tableSize // The storage size per table +} + +// count returns the number of stored items in the freezer. +func (info *freezerInfo) count() uint64 { + return info.head - info.tail + 1 +} + +// size returns the storage size of the entire freezer. +func (info *freezerInfo) size() common.StorageSize { + var total common.StorageSize + for _, table := range info.sizes { + total += table.size + } + 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: + info, err := inspect(ChainFreezerName, chainFreezerNoSnappy, db) + if err != nil { + return nil, err + } + infos = append(infos, info) + + case StateFreezerName: + datadir, err := db.AncientDatadir() + if err != nil { + return nil, err + } + f, err := NewStateFreezer(datadir, true) + if err != nil { + continue // might be possible the state freezer is not existent + } + defer f.Close() + + info, err := inspect(freezer, stateFreezerNoSnappy, f) + if err != nil { + return nil, err + } + infos = append(infos, info) + + default: + return nil, fmt.Errorf("unknown freezer, supported ones: %v", freezers) + } + } + return infos, nil +} + +// InspectFreezerTable dumps out the index of a specific freezer table. The passed +// ancient indicates the path of root ancient directory where the chain freezer can +// be opened. Start and end specify the range for dumping out indexes. +// Note this function can only be used for debugging purposes. +func InspectFreezerTable(ancient string, freezerName string, tableName string, start, end int64) error { + var ( + path string + tables map[string]bool + ) + switch freezerName { + 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) + } + noSnappy, exist := tables[tableName] + if !exist { + var names []string + for name := range tables { + names = append(names, name) + } + return fmt.Errorf("unknown table, supported ones: %v", names) + } + table, err := newFreezerTable(path, tableName, noSnappy, true) + if err != nil { + return err + } + table.dumpIndexStdout(start, end) + return nil +} diff --git a/core/rawdb/ancienttest/testsuite.go b/core/rawdb/ancienttest/testsuite.go new file mode 100644 index 0000000..70de263 --- /dev/null +++ b/core/rawdb/ancienttest/testsuite.go @@ -0,0 +1,325 @@ +// Copyright 2024 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 ancienttest + +import ( + "bytes" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/testrand" +) + +// TestAncientSuite runs a suite of tests against an ancient database +// implementation. +func TestAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { + // Test basic read methods + t.Run("BasicRead", func(t *testing.T) { basicRead(t, newFn) }) + + // Test batch read method + t.Run("BatchRead", func(t *testing.T) { batchRead(t, newFn) }) + + // Test basic write methods + t.Run("BasicWrite", func(t *testing.T) { basicWrite(t, newFn) }) + + // Test if data mutation is allowed after db write + t.Run("nonMutable", func(t *testing.T) { nonMutable(t, newFn) }) +} + +func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { + var ( + db = newFn([]string{"a"}) + data = makeDataset(100, 32) + ) + defer db.Close() + + db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := 0; i < len(data); i++ { + op.AppendRaw("a", uint64(i), data[i]) + } + return nil + }) + db.TruncateTail(10) + db.TruncateHead(90) + + // Test basic tail and head retrievals + tail, err := db.Tail() + if err != nil || tail != 10 { + t.Fatal("Failed to retrieve tail") + } + ancient, err := db.Ancients() + if err != nil || ancient != 90 { + t.Fatal("Failed to retrieve ancient") + } + + // Test the deleted items shouldn't be reachable + var cases = []struct { + start int + limit int + }{ + {0, 10}, + {90, 100}, + } + for _, c := range cases { + for i := c.start; i < c.limit; i++ { + exist, err := db.HasAncient("a", uint64(i)) + if err != nil { + t.Fatalf("Failed to check presence, %v", err) + } + if exist { + t.Fatalf("Item %d is already truncated", uint64(i)) + } + _, err = db.Ancient("a", uint64(i)) + if err == nil { + t.Fatal("Error is expected for non-existent item") + } + } + } + + // Test the items in range should be reachable + for i := 10; i < 90; i++ { + exist, err := db.HasAncient("a", uint64(i)) + if err != nil { + t.Fatalf("Failed to check presence, %v", err) + } + if !exist { + t.Fatalf("Item %d is missing", uint64(i)) + } + blob, err := db.Ancient("a", uint64(i)) + if err != nil { + t.Fatalf("Failed to retrieve item, %v", err) + } + if !bytes.Equal(blob, data[i]) { + t.Fatalf("Unexpected item content, want: %v, got: %v", data[i], blob) + } + } + + // Test the items in unknown table shouldn't be reachable + exist, err := db.HasAncient("b", uint64(0)) + if err != nil { + t.Fatalf("Failed to check presence, %v", err) + } + if exist { + t.Fatal("Item in unknown table shouldn't be found") + } + _, err = db.Ancient("b", uint64(0)) + if err == nil { + t.Fatal("Error is expected for unknown table") + } +} + +func batchRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { + var ( + db = newFn([]string{"a"}) + data = makeDataset(100, 32) + ) + defer db.Close() + + db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := 0; i < 100; i++ { + op.AppendRaw("a", uint64(i), data[i]) + } + return nil + }) + db.TruncateTail(10) + db.TruncateHead(90) + + // Test the items in range should be reachable + var cases = []struct { + start uint64 + count uint64 + maxSize uint64 + expStart int + expLimit int + }{ + // Items in range [10, 90) with no size limitation + { + 10, 80, 0, 10, 90, + }, + // Items in range [10, 90) with 32 size cap, single item is expected + { + 10, 80, 32, 10, 11, + }, + // Items in range [10, 90) with 31 size cap, single item is expected + { + 10, 80, 31, 10, 11, + }, + // Items in range [10, 90) with 32*80 size cap, all items are expected + { + 10, 80, 32 * 80, 10, 90, + }, + // Extra items above the last item are not returned + { + 10, 90, 0, 10, 90, + }, + } + for i, c := range cases { + batch, err := db.AncientRange("a", c.start, c.count, c.maxSize) + if err != nil { + t.Fatalf("Failed to retrieve item in range, %v", err) + } + if !reflect.DeepEqual(batch, data[c.expStart:c.expLimit]) { + t.Fatalf("Case %d, Batch content is not matched", i) + } + } + + // Test out-of-range / zero-size retrieval should be rejected + _, err := db.AncientRange("a", 0, 1, 0) + if err == nil { + t.Fatal("Out-of-range retrieval should be rejected") + } + _, err = db.AncientRange("a", 90, 1, 0) + if err == nil { + t.Fatal("Out-of-range retrieval should be rejected") + } + _, err = db.AncientRange("a", 10, 0, 0) + if err == nil { + t.Fatal("Zero-size retrieval should be rejected") + } + + // Test item in unknown table shouldn't be reachable + _, err = db.AncientRange("b", 10, 1, 0) + if err == nil { + t.Fatal("Item in unknown table shouldn't be found") + } +} + +func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { + var ( + db = newFn([]string{"a", "b"}) + dataA = makeDataset(100, 32) + dataB = makeDataset(100, 32) + ) + defer db.Close() + + // The ancient write to tables should be aligned + _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := 0; i < 100; i++ { + op.AppendRaw("a", uint64(i), dataA[i]) + } + return nil + }) + if err == nil { + t.Fatal("Unaligned ancient write should be rejected") + } + + // Test normal ancient write + size, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := 0; i < 100; i++ { + op.AppendRaw("a", uint64(i), dataA[i]) + op.AppendRaw("b", uint64(i), dataB[i]) + } + return nil + }) + if err != nil { + t.Fatalf("Failed to write ancient data %v", err) + } + wantSize := int64(6400) + if size != wantSize { + t.Fatalf("Ancient write size is not expected, want: %d, got: %d", wantSize, size) + } + + // Write should work after head truncating + db.TruncateHead(90) + _, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := 90; i < 100; i++ { + op.AppendRaw("a", uint64(i), dataA[i]) + op.AppendRaw("b", uint64(i), dataB[i]) + } + return nil + }) + if err != nil { + t.Fatalf("Failed to write ancient data %v", err) + } + + // Write should work after truncating everything + db.TruncateTail(0) + _, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := 0; i < 100; i++ { + op.AppendRaw("a", uint64(i), dataA[i]) + op.AppendRaw("b", uint64(i), dataB[i]) + } + return nil + }) + if err != nil { + t.Fatalf("Failed to write ancient data %v", err) + } +} + +func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) { + db := newFn([]string{"a"}) + defer db.Close() + + // We write 100 zero-bytes to the freezer and immediately mutate the slice + db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + data := make([]byte, 100) + op.AppendRaw("a", uint64(0), data) + for i := range data { + data[i] = 0xff + } + return nil + }) + // Now read it. + data, err := db.Ancient("a", uint64(0)) + if err != nil { + t.Fatal(err) + } + for k, v := range data { + if v != 0 { + t.Fatalf("byte %d != 0: %x", k, v) + } + } +} + +// TestResettableAncientSuite runs a suite of tests against a resettable ancient +// database implementation. +func TestResettableAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.ResettableAncientStore) { + t.Run("Reset", func(t *testing.T) { + var ( + db = newFn([]string{"a"}) + data = makeDataset(100, 32) + ) + defer db.Close() + + db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := 0; i < 100; i++ { + op.AppendRaw("a", uint64(i), data[i]) + } + return nil + }) + db.TruncateTail(10) + db.TruncateHead(90) + + // Ancient write should work after resetting + db.Reset() + db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := 0; i < 100; i++ { + op.AppendRaw("a", uint64(i), data[i]) + } + return nil + }) + }) +} + +func makeDataset(size, value int) [][]byte { + var vals [][]byte + for i := 0; i < size; i += 1 { + vals = append(vals, testrand.Bytes(value)) + } + return vals +} diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go new file mode 100644 index 0000000..7a0b819 --- /dev/null +++ b/core/rawdb/chain_freezer.go @@ -0,0 +1,344 @@ +// 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 rawdb + +import ( + "errors" + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +const ( + // freezerRecheckInterval is the frequency to check the key-value database for + // chain progression that might permit new blocks to be frozen into immutable + // storage. + freezerRecheckInterval = time.Minute + + // freezerBatchLimit is the maximum number of blocks to freeze in one batch + // before doing an fsync and deleting it from the key-value store. + freezerBatchLimit = 30000 +) + +// chainFreezer is a wrapper of chain ancient store with additional chain freezing +// feature. The background thread will keep moving ancient chain segments from +// key-value database to flat files for saving space on live database. +type chainFreezer struct { + ethdb.AncientStore // Ancient store for storing cold chain segment + + quit chan struct{} + wg sync.WaitGroup + trigger chan chan struct{} // Manual blocking freeze trigger, test determinism +} + +// newChainFreezer initializes the freezer for ancient chain segment. +// +// - if the empty directory is given, initializes the pure in-memory +// state freezer (e.g. dev mode). +// - if non-empty directory is given, initializes the regular file-based +// state freezer. +func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) { + var ( + err error + freezer ethdb.AncientStore + ) + if datadir == "" { + freezer = NewMemoryFreezer(readonly, chainFreezerNoSnappy) + } else { + freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy) + } + if err != nil { + return nil, err + } + return &chainFreezer{ + AncientStore: freezer, + quit: make(chan struct{}), + trigger: make(chan chan struct{}), + }, nil +} + +// Close closes the chain freezer instance and terminates the background thread. +func (f *chainFreezer) Close() error { + select { + case <-f.quit: + default: + close(f.quit) + } + f.wg.Wait() + return f.AncientStore.Close() +} + +// readHeadNumber returns the number of chain head block. 0 is returned if the +// block is unknown or not available yet. +func (f *chainFreezer) readHeadNumber(db ethdb.KeyValueReader) uint64 { + hash := ReadHeadBlockHash(db) + if hash == (common.Hash{}) { + log.Error("Head block is not reachable") + return 0 + } + number := ReadHeaderNumber(db, hash) + if number == nil { + log.Error("Number of head block is missing") + return 0 + } + return *number +} + +// readFinalizedNumber returns the number of finalized block. 0 is returned +// if the block is unknown or not available yet. +func (f *chainFreezer) readFinalizedNumber(db ethdb.KeyValueReader) uint64 { + hash := ReadFinalizedBlockHash(db) + if hash == (common.Hash{}) { + return 0 + } + number := ReadHeaderNumber(db, hash) + if number == nil { + log.Error("Number of finalized block is missing") + return 0 + } + return *number +} + +// freezeThreshold returns the threshold for chain freezing. It's determined +// by formula: max(finality, HEAD-params.FullImmutabilityThreshold). +func (f *chainFreezer) freezeThreshold(db ethdb.KeyValueReader) (uint64, error) { + var ( + head = f.readHeadNumber(db) + final = f.readFinalizedNumber(db) + headLimit uint64 + ) + if head > params.FullImmutabilityThreshold { + headLimit = head - params.FullImmutabilityThreshold + } + if final == 0 && headLimit == 0 { + return 0, errors.New("freezing threshold is not available") + } + if final > headLimit { + return final, nil + } + return headLimit, nil +} + +// freeze is a background thread that periodically checks the blockchain for any +// import progress and moves ancient data from the fast database into the freezer. +// +// This functionality is deliberately broken off from block importing to avoid +// incurring additional data shuffling delays on block propagation. +func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { + var ( + backoff bool + triggered chan struct{} // Used in tests + nfdb = &nofreezedb{KeyValueStore: db} + ) + timer := time.NewTimer(freezerRecheckInterval) + defer timer.Stop() + + for { + select { + case <-f.quit: + log.Info("Freezer shutting down") + return + default: + } + if backoff { + // If we were doing a manual trigger, notify it + if triggered != nil { + triggered <- struct{}{} + triggered = nil + } + select { + case <-timer.C: + backoff = false + timer.Reset(freezerRecheckInterval) + case triggered = <-f.trigger: + backoff = false + case <-f.quit: + return + } + } + threshold, err := f.freezeThreshold(nfdb) + if err != nil { + backoff = true + log.Debug("Current full block not old enough to freeze", "err", err) + continue + } + frozen, _ := f.Ancients() // no error will occur, safe to ignore + + // Short circuit if the blocks below threshold are already frozen. + if frozen != 0 && frozen-1 >= threshold { + backoff = true + log.Debug("Ancient blocks frozen already", "threshold", threshold, "frozen", frozen) + continue + } + // Seems we have data ready to be frozen, process in usable batches + var ( + start = time.Now() + first = frozen // the first block to freeze + last = threshold // the last block to freeze + ) + if last-first+1 > freezerBatchLimit { + last = freezerBatchLimit + first - 1 + } + ancients, err := f.freezeRange(nfdb, first, last) + if err != nil { + log.Error("Error in block freeze operation", "err", err) + backoff = true + continue + } + // Batch of blocks have been frozen, flush them before wiping from key-value store + if err := f.Sync(); err != nil { + log.Crit("Failed to flush frozen tables", "err", err) + } + // Wipe out all data from the active database + batch := db.NewBatch() + for i := 0; i < len(ancients); i++ { + // Always keep the genesis block in active database + if first+uint64(i) != 0 { + DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i)) + DeleteCanonicalHash(batch, first+uint64(i)) + } + } + if err := batch.Write(); err != nil { + log.Crit("Failed to delete frozen canonical blocks", "err", err) + } + batch.Reset() + + // Wipe out side chains also and track dangling side chains + var dangling []common.Hash + frozen, _ = f.Ancients() // Needs reload after during freezeRange + for number := first; number < frozen; number++ { + // Always keep the genesis block in active database + if number != 0 { + dangling = ReadAllHashes(db, number) + for _, hash := range dangling { + log.Trace("Deleting side chain", "number", number, "hash", hash) + DeleteBlock(batch, hash, number) + } + } + } + if err := batch.Write(); err != nil { + log.Crit("Failed to delete frozen side blocks", "err", err) + } + batch.Reset() + + // Step into the future and delete any dangling side chains + if frozen > 0 { + tip := frozen + for len(dangling) > 0 { + drop := make(map[common.Hash]struct{}) + for _, hash := range dangling { + log.Debug("Dangling parent from Freezer", "number", tip-1, "hash", hash) + drop[hash] = struct{}{} + } + children := ReadAllHashes(db, tip) + for i := 0; i < len(children); i++ { + // Dig up the child and ensure it's dangling + child := ReadHeader(nfdb, children[i], tip) + if child == nil { + log.Error("Missing dangling header", "number", tip, "hash", children[i]) + continue + } + if _, ok := drop[child.ParentHash]; !ok { + children = append(children[:i], children[i+1:]...) + i-- + continue + } + // Delete all block data associated with the child + log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash) + DeleteBlock(batch, children[i], tip) + } + dangling = children + tip++ + } + if err := batch.Write(); err != nil { + log.Crit("Failed to delete dangling side blocks", "err", err) + } + } + + // Log something friendly for the user + context := []interface{}{ + "blocks", frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", frozen - 1, + } + if n := len(ancients); n > 0 { + context = append(context, []interface{}{"hash", ancients[n-1]}...) + } + log.Debug("Deep froze chain segment", context...) + + // Avoid database thrashing with tiny writes + if frozen-first < freezerBatchLimit { + backoff = true + } + } +} + +// freezeRange moves a batch of chain segments from the fast database to the freezer. +// The parameters (number, limit) specify the relevant block range, both of which +// are included. +func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hashes []common.Hash, err error) { + hashes = make([]common.Hash, 0, limit-number+1) + + _, err = f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for ; number <= limit; number++ { + // Retrieve all the components of the canonical block. + hash := ReadCanonicalHash(nfdb, number) + if hash == (common.Hash{}) { + return fmt.Errorf("canonical hash missing, can't freeze block %d", number) + } + header := ReadHeaderRLP(nfdb, hash, number) + if len(header) == 0 { + return fmt.Errorf("block header missing, can't freeze block %d", number) + } + body := ReadBodyRLP(nfdb, hash, number) + if len(body) == 0 { + return fmt.Errorf("block body missing, can't freeze block %d", number) + } + receipts := ReadReceiptsRLP(nfdb, hash, number) + if len(receipts) == 0 { + return fmt.Errorf("block receipts missing, can't freeze block %d", number) + } + td := ReadTdRLP(nfdb, hash, number) + if len(td) == 0 { + return fmt.Errorf("total difficulty missing, can't freeze block %d", number) + } + + // Write to the batch. + if err := op.AppendRaw(ChainFreezerHashTable, number, hash[:]); err != nil { + return fmt.Errorf("can't write hash to Freezer: %v", err) + } + if err := op.AppendRaw(ChainFreezerHeaderTable, number, header); err != nil { + return fmt.Errorf("can't write header to Freezer: %v", err) + } + if err := op.AppendRaw(ChainFreezerBodiesTable, number, body); err != nil { + return fmt.Errorf("can't write body to Freezer: %v", err) + } + if err := op.AppendRaw(ChainFreezerReceiptTable, number, receipts); err != nil { + return fmt.Errorf("can't write receipts to Freezer: %v", err) + } + if err := op.AppendRaw(ChainFreezerDifficultyTable, number, td); err != nil { + return fmt.Errorf("can't write td to Freezer: %v", err) + } + hashes = append(hashes, hash) + } + return nil + }) + return hashes, err +} diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go new file mode 100644 index 0000000..759e591 --- /dev/null +++ b/core/rawdb/chain_iterator.go @@ -0,0 +1,363 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "runtime" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// InitDatabaseFromFreezer reinitializes an empty database from a previous batch +// of frozen ancient blocks. The method iterates over all the frozen blocks and +// injects into the database the block hash->number mappings. +func InitDatabaseFromFreezer(db ethdb.Database) { + // If we can't access the freezer or it's empty, abort + frozen, err := db.Ancients() + if err != nil || frozen == 0 { + return + } + var ( + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log + hash common.Hash + ) + for i := uint64(0); i < frozen; { + // We read 100K hashes at a time, for a total of 3.2M + count := uint64(100_000) + if i+count > frozen { + count = frozen - i + } + data, err := db.AncientRange(ChainFreezerHashTable, i, count, 32*count) + if err != nil { + log.Crit("Failed to init database from freezer", "err", err) + } + for j, h := range data { + number := i + uint64(j) + hash = common.BytesToHash(h) + WriteHeaderNumber(batch, hash, number) + // If enough data was accumulated in memory or we're at the last block, dump to disk + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write data to db", "err", err) + } + batch.Reset() + } + } + i += uint64(len(data)) + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Initializing database from freezer", "total", frozen, "number", i, "hash", hash, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + if err := batch.Write(); err != nil { + log.Crit("Failed to write data to db", "err", err) + } + batch.Reset() + + WriteHeadHeaderHash(db, hash) + WriteHeadFastBlockHash(db, hash) + log.Info("Initialized database from freezer", "blocks", frozen, "elapsed", common.PrettyDuration(time.Since(start))) +} + +type blockTxHashes struct { + number uint64 + hashes []common.Hash +} + +// iterateTransactions iterates over all transactions in the (canon) block +// number(s) given, and yields the hashes on a channel. If there is a signal +// received from interrupt channel, the iteration will be aborted and result +// channel will be closed. +func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool, interrupt chan struct{}) chan *blockTxHashes { + // One thread sequentially reads data from db + type numberRlp struct { + number uint64 + rlp rlp.RawValue + } + if to == from { + return nil + } + threads := to - from + if cpus := runtime.NumCPU(); threads > uint64(cpus) { + threads = uint64(cpus) + } + var ( + rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel + hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh + ) + // lookup runs in one instance + lookup := func() { + n, end := from, to + if reverse { + n, end = to-1, from-1 + } + defer close(rlpCh) + for n != end { + data := ReadCanonicalBodyRLP(db, n) + // Feed the block to the aggregator, or abort on interrupt + select { + case rlpCh <- &numberRlp{n, data}: + case <-interrupt: + return + } + if reverse { + n-- + } else { + n++ + } + } + } + // process runs in parallel + var nThreadsAlive atomic.Int32 + nThreadsAlive.Store(int32(threads)) + process := func() { + defer func() { + // Last processor closes the result channel + if nThreadsAlive.Add(-1) == 0 { + close(hashesCh) + } + }() + for data := range rlpCh { + var body types.Body + if err := rlp.DecodeBytes(data.rlp, &body); err != nil { + log.Warn("Failed to decode block body", "block", data.number, "error", err) + return + } + var hashes []common.Hash + for _, tx := range body.Transactions { + hashes = append(hashes, tx.Hash()) + } + result := &blockTxHashes{ + hashes: hashes, + number: data.number, + } + // Feed the block to the aggregator, or abort on interrupt + select { + case hashesCh <- result: + case <-interrupt: + return + } + } + } + go lookup() // start the sequential db accessor + for i := 0; i < int(threads); i++ { + go process() + } + return hashesCh +} + +// indexTransactions creates txlookup indices of the specified block range. +// +// This function iterates canonical chain in reverse order, it has one main advantage: +// We can write tx index tail flag periodically even without the whole indexing +// procedure is finished. So that we can resume indexing procedure next time quickly. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool, report bool) { + // short circuit for invalid range + if from >= to { + return + } + var ( + hashesCh = iterateTransactions(db, from, to, true, interrupt) + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + + // Since we iterate in reverse, we expect the first number to come + // in to be [to-1]. Therefore, setting lastNum to means that the + // queue gap-evaluation will work correctly + lastNum = to + queue = prque.New[int64, *blockTxHashes](nil) + blocks, txs = 0, 0 // for stats reporting + ) + for chanDelivery := range hashesCh { + // Push the delivery into the queue and process contiguous ranges. + // Since we iterate in reverse, so lower numbers have lower prio, and + // we can use the number directly as prio marker + queue.Push(chanDelivery, int64(chanDelivery.number)) + for !queue.Empty() { + // If the next available item is gapped, return + if _, priority := queue.Peek(); priority != int64(lastNum-1) { + break + } + // For testing + if hook != nil && !hook(lastNum-1) { + break + } + // Next block available, pop it off and index it + delivery := queue.PopItem() + lastNum = delivery.number + WriteTxLookupEntries(batch, delivery.number, delivery.hashes) + blocks++ + txs += len(delivery.hashes) + // If enough data was accumulated in memory or we're at the last block, dump to disk + if batch.ValueSize() > ethdb.IdealBatchSize { + WriteTxIndexTail(batch, lastNum) // Also write the tail here + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Indexing transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to index, but the tail has to + // be flushed anyway. + WriteTxIndexTail(batch, lastNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + logger := log.Debug + if report { + logger = log.Info + } + select { + case <-interrupt: + logger("Transaction indexing interrupted", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start))) + default: + logger("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +// IndexTransactions creates txlookup indices of the specified block range. The from +// is included while to is excluded. +// +// This function iterates canonical chain in reverse order, it has one main advantage: +// We can write tx index tail flag periodically even without the whole indexing +// procedure is finished. So that we can resume indexing procedure next time quickly. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func IndexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, report bool) { + indexTransactions(db, from, to, interrupt, nil, report) +} + +// indexTransactionsForTesting is the internal debug version with an additional hook. +func indexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { + indexTransactions(db, from, to, interrupt, hook, false) +} + +// unindexTransactions removes txlookup indices of the specified block range. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool, report bool) { + // short circuit for invalid range + if from >= to { + return + } + var ( + hashesCh = iterateTransactions(db, from, to, false, interrupt) + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + + // we expect the first number to come in to be [from]. Therefore, setting + // nextNum to from means that the queue gap-evaluation will work correctly + nextNum = from + queue = prque.New[int64, *blockTxHashes](nil) + blocks, txs = 0, 0 // for stats reporting + ) + // Otherwise spin up the concurrent iterator and unindexer + for delivery := range hashesCh { + // Push the delivery into the queue and process contiguous ranges. + queue.Push(delivery, -int64(delivery.number)) + for !queue.Empty() { + // If the next available item is gapped, return + if _, priority := queue.Peek(); -priority != int64(nextNum) { + break + } + // For testing + if hook != nil && !hook(nextNum) { + break + } + delivery := queue.PopItem() + nextNum = delivery.number + 1 + DeleteTxLookupEntries(batch, delivery.hashes) + txs += len(delivery.hashes) + blocks++ + + // If enough data was accumulated in memory or we're at the last block, dump to disk + // A batch counts the size of deletion as '1', so we need to flush more + // often than that. + if blocks%1000 == 0 { + WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to unindex, but the tail has to + // be flushed anyway. + WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + logger := log.Debug + if report { + logger = log.Info + } + select { + case <-interrupt: + logger("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) + default: + logger("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +// UnindexTransactions removes txlookup indices of the specified block range. +// The from is included while to is excluded. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func UnindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, report bool) { + unindexTransactions(db, from, to, interrupt, nil, report) +} + +// unindexTransactionsForTesting is the internal debug version with an additional hook. +func unindexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { + unindexTransactions(db, from, to, interrupt, hook, false) +} diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go new file mode 100644 index 0000000..390424f --- /dev/null +++ b/core/rawdb/chain_iterator_test.go @@ -0,0 +1,208 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "math/big" + "reflect" + "sort" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +func TestChainIterator(t *testing.T) { + // Construct test chain db + chainDb := NewMemoryDatabase() + + var block *types.Block + var txs []*types.Transaction + to := common.BytesToAddress([]byte{0x11}) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) // Empty genesis block + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + for i := uint64(1); i <= 10; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = types.NewTx(&types.LegacyTx{ + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + } else { + tx = types.NewTx(&types.AccessListTx{ + ChainID: big.NewInt(1337), + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + } + txs = append(txs, tx) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher()) + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + } + + var cases = []struct { + from, to uint64 + reverse bool + expect []int + }{ + {0, 11, true, []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}}, + {0, 0, true, nil}, + {0, 5, true, []int{4, 3, 2, 1, 0}}, + {10, 11, true, []int{10}}, + {0, 11, false, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}, + {0, 0, false, nil}, + {10, 11, false, []int{10}}, + } + for i, c := range cases { + var numbers []int + hashCh := iterateTransactions(chainDb, c.from, c.to, c.reverse, nil) + if hashCh != nil { + for h := range hashCh { + numbers = append(numbers, int(h.number)) + if len(h.hashes) > 0 { + if got, exp := h.hashes[0], txs[h.number-1].Hash(); got != exp { + t.Fatalf("block %d: hash wrong, got %x exp %x", h.number, got, exp) + } + } + } + } + if !c.reverse { + sort.Ints(numbers) + } else { + sort.Sort(sort.Reverse(sort.IntSlice(numbers))) + } + if !reflect.DeepEqual(numbers, c.expect) { + t.Fatalf("Case %d failed, visit element mismatch, want %v, got %v", i, c.expect, numbers) + } + } +} + +func TestIndexTransactions(t *testing.T) { + // Construct test chain db + chainDb := NewMemoryDatabase() + + var block *types.Block + var txs []*types.Transaction + to := common.BytesToAddress([]byte{0x11}) + + // Write empty genesis block + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + + for i := uint64(1); i <= 10; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = types.NewTx(&types.LegacyTx{ + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + } else { + tx = types.NewTx(&types.AccessListTx{ + ChainID: big.NewInt(1337), + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + } + txs = append(txs, tx) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher()) + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + } + // verify checks whether the tx indices in the range [from, to) + // is expected. + verify := func(from, to int, exist bool, tail uint64) { + for i := from; i < to; i++ { + if i == 0 { + continue + } + number := ReadTxLookupEntry(chainDb, txs[i-1].Hash()) + if exist && number == nil { + t.Fatalf("Transaction index %d missing", i) + } + if !exist && number != nil { + t.Fatalf("Transaction index %d is not deleted", i) + } + } + number := ReadTxIndexTail(chainDb) + if number == nil || *number != tail { + t.Fatalf("Transaction tail mismatch") + } + } + IndexTransactions(chainDb, 5, 11, nil, false) + verify(5, 11, true, 5) + verify(0, 5, false, 5) + + IndexTransactions(chainDb, 0, 5, nil, false) + verify(0, 11, true, 0) + + UnindexTransactions(chainDb, 0, 5, nil, false) + verify(5, 11, true, 5) + verify(0, 5, false, 5) + + UnindexTransactions(chainDb, 5, 11, nil, false) + verify(0, 11, false, 11) + + // Testing corner cases + signal := make(chan struct{}) + var once sync.Once + indexTransactionsForTesting(chainDb, 5, 11, signal, func(n uint64) bool { + if n <= 8 { + once.Do(func() { + close(signal) + }) + return false + } + return true + }) + verify(9, 11, true, 9) + verify(0, 9, false, 9) + IndexTransactions(chainDb, 0, 9, nil, false) + + signal = make(chan struct{}) + var once2 sync.Once + unindexTransactionsForTesting(chainDb, 0, 11, signal, func(n uint64) bool { + if n >= 8 { + once2.Do(func() { + close(signal) + }) + return false + } + return true + }) + verify(8, 11, true, 8) + verify(0, 8, false, 8) +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go new file mode 100644 index 0000000..3436958 --- /dev/null +++ b/core/rawdb/database.go @@ -0,0 +1,666 @@ +// 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 rawdb + +import ( + "bytes" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "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 enables ancient chain segment freezing. +type freezerdb struct { + ethdb.KeyValueStore + *chainFreezer + + readOnly bool + ancientRoot string +} + +// AncientDatadir returns the path of root ancient directory. +func (frdb *freezerdb) AncientDatadir() (string, error) { + return frdb.ancientRoot, nil +} + +// Close implements io.Closer, closing both the fast key-value store as well as +// the slow ancient tables. +func (frdb *freezerdb) Close() error { + var errs []error + if err := frdb.chainFreezer.Close(); err != nil { + errs = append(errs, err) + } + if err := frdb.KeyValueStore.Close(); err != nil { + errs = append(errs, err) + } + if len(errs) != 0 { + return fmt.Errorf("%v", errs) + } + return nil +} + +// Freeze is a helper method used for external testing to trigger and block until +// a freeze cycle completes, without having to sleep for a minute to trigger the +// automatic background run. +func (frdb *freezerdb) Freeze() error { + if frdb.readOnly { + return errReadOnly + } + // Trigger a freeze cycle and block until it's done + trigger := make(chan struct{}, 1) + frdb.chainFreezer.trigger <- trigger + <-trigger + return nil +} + +// nofreezedb is a database wrapper that disables freezer data retrievals. +type nofreezedb struct { + ethdb.KeyValueStore +} + +// HasAncient returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) HasAncient(kind string, number uint64) (bool, error) { + return false, errNotSupported +} + +// Ancient returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) { + return nil, errNotSupported +} + +// AncientRange returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64) ([][]byte, error) { + return nil, errNotSupported +} + +// Ancients returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) Ancients() (uint64, error) { + return 0, errNotSupported +} + +// Tail returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) Tail() (uint64, error) { + return 0, errNotSupported +} + +// AncientSize returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) AncientSize(kind string) (uint64, error) { + return 0, errNotSupported +} + +// ModifyAncients is not supported. +func (db *nofreezedb) ModifyAncients(func(ethdb.AncientWriteOp) error) (int64, error) { + return 0, errNotSupported +} + +// TruncateHead returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) TruncateHead(items uint64) (uint64, error) { + return 0, errNotSupported +} + +// TruncateTail returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) TruncateTail(items uint64) (uint64, error) { + return 0, errNotSupported +} + +// Sync returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) Sync() error { + return errNotSupported +} + +func (db *nofreezedb) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err 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 the freezer, + // 2. If not, check leveldb. + // + // This will work, since the ancient-checks inside 'fn' will return errors, + // and the leveldb work will continue. + // + // If we instead were to return errNotSupported here, then the caller would + // have to explicitly check for that, having an extra clause to do the + // non-ancient operations. + return fn(db) +} + +// MigrateTable processes the entries in a given table in sequence +// converting them to a new format if they're of an old format. +func (db *nofreezedb) MigrateTable(kind string, convert convertLegacyFn) error { + return errNotSupported +} + +// AncientDatadir returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) AncientDatadir() (string, error) { + return "", errNotSupported +} + +// NewDatabase creates a high level database on top of a given key-value data +// store without a freezer moving immutable chain segments into cold storage. +func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { + return &nofreezedb{KeyValueStore: db} +} + +// resolveChainFreezerDir is a helper function which resolves the absolute path +// of chain freezer by considering backward compatibility. +func resolveChainFreezerDir(ancient string) string { + // Check if the chain freezer is already present in the specified + // sub folder, if not then two possibilities: + // - chain freezer is not initialized + // - chain freezer exists in legacy location (root ancient folder) + freezer := filepath.Join(ancient, ChainFreezerName) + if !common.FileExist(freezer) { + if !common.FileExist(ancient) { + // The entire ancient store is not initialized, still use the sub + // folder for initialization. + } else { + // Ancient root is already initialized, then we hold the assumption + // that chain freezer is also initialized and located in root folder. + // In this case fallback to legacy location. + freezer = ancient + log.Info("Found legacy ancient chain path", "location", ancient) + } + } + return freezer +} + +// NewDatabaseWithFreezer creates a high level database on top of a given key- +// value data store with a freezer moving immutable chain segments into cold +// storage. The passed ancient indicates the path of root ancient directory +// where the chain freezer can be opened. +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) { + // Create the idle freezer instance. If the given ancient directory is empty, + // in-memory chain freezer is used (e.g. dev mode); otherwise the regular + // file-based freezer is created. + chainFreezerDir := ancient + if chainFreezerDir != "" { + chainFreezerDir = resolveChainFreezerDir(chainFreezerDir) + } + frdb, err := newChainFreezer(chainFreezerDir, namespace, readonly) + if err != nil { + printChainMetadata(db) + return nil, err + } + // Since the freezer can be stored separately from the user's key-value database, + // there's a fairly high probability that the user requests invalid combinations + // 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 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 + // blockchain, since we don't have the genesis block here (nor should we at + // this point care, the key-value/freezer combo is valid). + // - 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 + // 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. + + // If the genesis hash is empty, we have a new key-value store, so nothing to + // validate in this method. If, however, the genesis hash is not nil, compare + // it to the freezer content. + if kvgenesis, _ := db.Get(headerHashKey(0)); len(kvgenesis) > 0 { + if frozen, _ := frdb.Ancients(); frozen > 0 { + // If the freezer already contains something, ensure that the genesis blocks + // match, otherwise we might mix up freezers across chains and destroy both + // the freezer and the key-value store. + frgenesis, err := frdb.Ancient(ChainFreezerHashTable, 0) + if err != nil { + printChainMetadata(db) + return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err) + } else if !bytes.Equal(kvgenesis, frgenesis) { + printChainMetadata(db) + return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis) + } + // Key-value store and freezer belong to the same network. Ensure that they + // are contiguous, otherwise we might end up with a non-functional freezer. + if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 { + // Subsequent header after the freezer limit is missing from the database. + // Reject startup if the database has a more recent head. + if head := *ReadHeaderNumber(db, ReadHeadHeaderHash(db)); head > frozen-1 { + // Find the smallest block stored in the key-value store + // in range of [frozen, head] + var number uint64 + for number = frozen; number <= head; number++ { + if present, _ := db.Has(headerHashKey(number)); present { + break + } + } + // 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) + } + // Database contains only older data than the freezer, this happens if the + // state was wiped and reinited from an existing freezer. + } + // Otherwise, key-value store continues where the freezer left off, all is fine. + // We might have duplicate blocks (crash after freezer write but before key-value + // store deletion, but that's fine). + } else { + // If the freezer is empty, ensure nothing was moved yet from the key-value + // store, otherwise we'll end up missing data. We check block #1 to decide + // if we froze anything previously or not, but do take care of databases with + // only the genesis block. + if ReadHeadHeaderHash(db) != common.BytesToHash(kvgenesis) { + // Key-value store contains more data than the genesis block, make sure we + // didn't freeze anything yet. + if kvblob, _ := db.Get(headerHashKey(1)); len(kvblob) == 0 { + printChainMetadata(db) + return nil, errors.New("ancient chain segments already extracted, please set --datadir.ancient to the correct path") + } + // Block #1 is still in the database, we're allowed to init a new freezer + } + // Otherwise, the head header is still the genesis, we're allowed to init a new + // freezer. + } + } + // Freezer is consistent with the key-value database, permit combining the two + if !readonly { + frdb.wg.Add(1) + go func() { + frdb.freeze(db) + frdb.wg.Done() + }() + } + return &freezerdb{ + ancientRoot: ancient, + KeyValueStore: db, + chainFreezer: frdb, + }, nil +} + +// NewMemoryDatabase creates an ephemeral in-memory key-value database without a +// freezer moving immutable chain segments into cold storage. +func NewMemoryDatabase() ethdb.Database { + return NewDatabase(memorydb.New()) +} + +// NewMemoryDatabaseWithCap creates an ephemeral in-memory key-value database +// with an initial starting capacity, but without a freezer moving immutable +// chain segments into cold storage. +func NewMemoryDatabaseWithCap(size int) ethdb.Database { + return NewDatabase(memorydb.NewWithCap(size)) +} + +// NewLevelDBDatabase creates a persistent key-value database without a freezer +// moving immutable chain segments into cold storage. +func NewLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) { + db, err := leveldb.New(file, cache, handles, namespace, readonly) + if err != nil { + return nil, err + } + log.Info("Using LevelDB as the backing database") + 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" +) + +// 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 PreexistingDatabase(path string) string { + if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil { + return "" // No pre-existing db + } + if matches, err := filepath.Glob(filepath.Join(path, "OPTIONS*")); len(matches) > 0 || err != nil { + if err != nil { + panic(err) // only possible if the pattern is malformed + } + return dbPebble + } + return dbLeveldb +} + +// OpenOptions contains the options to apply when opening a database. +// OBS: If AncientsDirectory is empty, it indicates that no freezer is to be used. +type OpenOptions struct { + Type string // "leveldb" | "pebble" + Directory string // the datadir + AncientsDirectory string // the ancients-dir + Namespace string // the namespace for database relevant metrics + 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. +// +// type == null type != null +// +---------------------------------------- +// db is non-existent | pebble default | specified type +// db is existent | from db | specified type (if compatible) +func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) { + // Reject any unsupported database type + if len(o.Type) != 0 && o.Type != dbLeveldb && o.Type != dbPebble { + return nil, fmt.Errorf("unknown db.engine %v", o.Type) + } + // 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 := 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 { + 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. + 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 +// integrates it with a freezer database -- if the AncientDir option has been +// set on the provided OpenOptions. +// The passed o.AncientDir indicates the path of root ancient directory where +// the chain freezer can be opened. +func Open(o OpenOptions) (ethdb.Database, error) { + kvdb, err := openKeyValueDatabase(o) + if err != nil { + return nil, err + } + if len(o.AncientsDirectory) == 0 { + return kvdb, nil + } + frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly) + if err != nil { + kvdb.Close() + return nil, err + } + return frdb, nil +} + +type counter uint64 + +func (c counter) String() string { + return fmt.Sprintf("%d", c) +} + +func (c counter) Percentage(current uint64) string { + return fmt.Sprintf("%d", current*100/uint64(c)) +} + +// stat stores sizes and count for a parameter +type stat struct { + size common.StorageSize + count counter +} + +// Add size to the stat and increase the counter by 1 +func (s *stat) Add(size common.StorageSize) { + s.size += size + s.count++ +} + +func (s *stat) Size() string { + return s.size.String() +} + +func (s *stat) Count() string { + return s.count.String() +} + +// InspectDatabase traverses the entire database and checks the size +// of all different categories of data. +func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { + it := db.NewIterator(keyPrefix, keyStart) + defer it.Release() + + var ( + count int64 + start = time.Now() + logged = time.Now() + + // Key-value store statistics + headers stat + bodies stat + receipts stat + tds stat + numHashPairings stat + hashNumPairings stat + legacyTries stat + stateLookups stat + accountTries stat + storageTries stat + codes stat + txLookups stat + accountSnaps stat + storageSnaps stat + preimages stat + bloomBits stat + beaconHeaders stat + cliqueSnaps stat + + // Les statistic + chtTrieNodes stat + bloomTrieNodes stat + + // Meta- and unaccounted data + metadata stat + unaccounted stat + + // Totals + total common.StorageSize + ) + // Inspect key-value database first. + for it.Next() { + var ( + key = it.Key() + size = common.StorageSize(len(key) + len(it.Value())) + ) + total += size + switch { + case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength): + headers.Add(size) + case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength): + bodies.Add(size) + case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): + receipts.Add(size) + case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): + tds.Add(size) + case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): + numHashPairings.Add(size) + case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): + hashNumPairings.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): + txLookups.Add(size) + case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength): + accountSnaps.Add(size) + case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength): + storageSnaps.Add(size) + case bytes.HasPrefix(key, PreimagePrefix) && len(key) == (len(PreimagePrefix)+common.HashLength): + preimages.Add(size) + case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength): + metadata.Add(size) + case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength): + metadata.Add(size) + case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): + bloomBits.Add(size) + case bytes.HasPrefix(key, BloomBitsIndexPrefix): + bloomBits.Add(size) + case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8): + beaconHeaders.Add(size) + case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength: + cliqueSnaps.Add(size) + case bytes.HasPrefix(key, ChtTablePrefix) || + bytes.HasPrefix(key, ChtIndexTablePrefix) || + bytes.HasPrefix(key, ChtPrefix): // Canonical hash trie + chtTrieNodes.Add(size) + case bytes.HasPrefix(key, BloomTrieTablePrefix) || + bytes.HasPrefix(key, BloomTrieIndexPrefix) || + bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub + bloomTrieNodes.Add(size) + default: + var accounted bool + for _, meta := range [][]byte{ + databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, headFinalizedBlockKey, + 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) + accounted = true + break + } + } + if !accounted { + unaccounted.Add(size) + } + } + count++ + if count%1000 == 0 && time.Since(logged) > 8*time.Second { + log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + // Display the database statistic of key-value store. + stats := [][]string{ + {"Key-Value store", "Headers", headers.Size(), headers.Count()}, + {"Key-Value store", "Bodies", bodies.Size(), bodies.Count()}, + {"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()}, + {"Key-Value store", "Difficulties", tds.Size(), tds.Count()}, + {"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, + {"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, + {"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", "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()}, + {"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()}, + {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, + {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, + {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, + {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, + } + // Inspect all registered append-only file store then. + ancients, err := inspectFreezers(db) + if err != nil { + return err + } + for _, ancient := range ancients { + for _, table := range ancient.sizes { + stats = append(stats, []string{ + fmt.Sprintf("Ancient store (%s)", strings.Title(ancient.name)), + strings.Title(table.name), + table.size.String(), + fmt.Sprintf("%d", ancient.count()), + }) + } + total += ancient.size() + } + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Database", "Category", "Size", "Items"}) + table.SetFooter([]string{"", "Total", total.String(), " "}) + table.AppendBulk(stats) + table.Render() + + if unaccounted.size > 0 { + log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) + } + return nil +} + +// printChainMetadata prints out chain metadata to stderr. +func printChainMetadata(db ethdb.KeyValueStore) { + fmt.Fprintf(os.Stderr, "Chain metadata\n") + for _, v := range ReadChainMetadata(db) { + fmt.Fprintf(os.Stderr, " %s\n", strings.Join(v, ": ")) + } + fmt.Fprintf(os.Stderr, "\n\n") +} + +// 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 { + pp := func(val *uint64) string { + if val == nil { + return "" + } + return fmt.Sprintf("%d (%#x)", *val, *val) + } + data := [][]string{ + {"databaseVersion", pp(ReadDatabaseVersion(db))}, + {"headBlockHash", fmt.Sprintf("%v", ReadHeadBlockHash(db))}, + {"headFastBlockHash", fmt.Sprintf("%v", ReadHeadFastBlockHash(db))}, + {"headHeaderHash", fmt.Sprintf("%v", ReadHeadHeaderHash(db))}, + {"lastPivotNumber", pp(ReadLastPivotNumber(db))}, + {"len(snapshotSyncStatus)", fmt.Sprintf("%d bytes", len(ReadSnapshotSyncStatus(db)))}, + {"snapshotDisabled", fmt.Sprintf("%v", ReadSnapshotDisabled(db))}, + {"snapshotJournal", fmt.Sprintf("%d bytes", len(ReadSnapshotJournal(db)))}, + {"snapshotRecoveryNumber", pp(ReadSnapshotRecoveryNumber(db))}, + {"snapshotRoot", fmt.Sprintf("%v", ReadSnapshotRoot(db))}, + {"txIndexTail", pp(ReadTxIndexTail(db))}, + } + if b := ReadSkeletonSyncStatus(db); b != nil { + data = append(data, []string{"SkeletonSyncStatus", string(b)}) + } + return data +} diff --git a/core/rawdb/database_test.go b/core/rawdb/database_test.go new file mode 100644 index 0000000..a0d7b5e --- /dev/null +++ b/core/rawdb/database_test.go @@ -0,0 +1,17 @@ +// 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 rawdb diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go new file mode 100644 index 0000000..0f28782 --- /dev/null +++ b/core/rawdb/freezer.go @@ -0,0 +1,503 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "errors" + "fmt" + "math" + "os" + "path/filepath" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/gofrs/flock" +) + +var ( + // errReadOnly is returned if the freezer is opened in read only mode. All the + // mutations are disallowed. + errReadOnly = errors.New("read only") + + // errUnknownTable is returned if the user attempts to read from a table that is + // not tracked by the freezer. + errUnknownTable = errors.New("unknown table") + + // errOutOrderInsertion is returned if the user attempts to inject out-of-order + // binary blobs into the freezer. + errOutOrderInsertion = errors.New("the append operation is out-order") + + // errSymlinkDatadir is returned if the ancient directory specified by user + // is a symbolic link. + errSymlinkDatadir = errors.New("symbolic link datadir is not supported") +) + +// freezerTableSize defines the maximum size of freezer data files. +const freezerTableSize = 2 * 1000 * 1000 * 1000 + +// Freezer is a memory mapped append-only database to store immutable ordered +// data into flat files: +// +// - The append-only nature ensures that disk writes are minimized. +// - The memory mapping ensures we can max out system memory for caching without +// reserving it for go-ethereum. This would also reduce the memory requirements +// of Geth, and thus also GC overhead. +type Freezer struct { + frozen atomic.Uint64 // Number of items already frozen + tail atomic.Uint64 // Number of the first stored item in the freezer + + // This lock synchronizes writers and the truncate operation, as well as + // the "atomic" (batched) read operations. + writeLock sync.RWMutex + writeBatch *freezerBatch + + readonly bool + tables map[string]*freezerTable // Data tables for storing everything + instanceLock *flock.Flock // File-system lock to prevent double opens + closeOnce sync.Once +} + +// NewFreezer creates a freezer instance for maintaining immutable ordered +// data according to the given parameters. +// +// The 'tables' argument defines the data tables. If the value of a map +// entry is true, snappy compression is disabled for the table. +func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*Freezer, error) { + // Create the initial freezer object + var ( + readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil) + writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil) + sizeGauge = metrics.NewRegisteredGauge(namespace+"ancient/size", nil) + ) + // Ensure the datadir is not a symbolic link if it exists. + if info, err := os.Lstat(datadir); !os.IsNotExist(err) { + if info.Mode()&os.ModeSymlink != 0 { + log.Warn("Symbolic link ancient database is not supported", "path", datadir) + return nil, errSymlinkDatadir + } + } + flockFile := filepath.Join(datadir, "FLOCK") + if err := os.MkdirAll(filepath.Dir(flockFile), 0755); err != nil { + return nil, err + } + // Leveldb uses LOCK as the filelock filename. To prevent the + // name collision, we use FLOCK as the lock name. + lock := flock.New(flockFile) + 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") + } + // Open all the supported data tables + freezer := &Freezer{ + readonly: readonly, + tables: make(map[string]*freezerTable), + instanceLock: lock, + } + + // Create the tables. + for name, disableSnappy := range tables { + table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, disableSnappy, readonly) + if err != nil { + for _, table := range freezer.tables { + table.Close() + } + lock.Unlock() + return nil, err + } + freezer.tables[name] = table + } + var err error + if freezer.readonly { + // In readonly mode only validate, don't truncate. + // validate also sets `freezer.frozen`. + err = freezer.validate() + } else { + // Truncate all tables to common length. + err = freezer.repair() + } + if err != nil { + for _, table := range freezer.tables { + table.Close() + } + lock.Unlock() + return nil, err + } + + // Create the write batch. + freezer.writeBatch = newFreezerBatch(freezer) + + log.Info("Opened ancient database", "database", datadir, "readonly", readonly) + return freezer, nil +} + +// Close terminates the chain freezer, unmapping all the data files. +func (f *Freezer) Close() error { + f.writeLock.Lock() + defer f.writeLock.Unlock() + + var errs []error + f.closeOnce.Do(func() { + for _, table := range f.tables { + if err := table.Close(); err != nil { + errs = append(errs, err) + } + } + if err := f.instanceLock.Unlock(); err != nil { + errs = append(errs, err) + } + }) + if errs != nil { + return fmt.Errorf("%v", errs) + } + return nil +} + +// HasAncient returns an indicator whether the specified ancient data exists +// in the freezer. +func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) { + if table := f.tables[kind]; table != nil { + return table.has(number), nil + } + return false, nil +} + +// Ancient retrieves an ancient binary blob from the append-only immutable files. +func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) { + if table := f.tables[kind]; table != nil { + return table.Retrieve(number) + } + return nil, errUnknownTable +} + +// AncientRange retrieves multiple items in sequence, starting from the index 'start'. +// It will return +// - at most 'count' items, +// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), +// but will otherwise return as many items as fit into maxByteSize. +// - if maxBytes is not specified, 'count' items will be returned if they are present. +func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { + if table := f.tables[kind]; table != nil { + return table.RetrieveItems(start, count, maxBytes) + } + return nil, errUnknownTable +} + +// Ancients returns the length of the frozen items. +func (f *Freezer) Ancients() (uint64, error) { + return f.frozen.Load(), nil +} + +// Tail returns the number of first stored item in the freezer. +func (f *Freezer) Tail() (uint64, error) { + return f.tail.Load(), nil +} + +// AncientSize returns the ancient size of the specified category. +func (f *Freezer) AncientSize(kind string) (uint64, error) { + // This needs the write lock to avoid data races on table fields. + // Speed doesn't matter here, AncientSize is for debugging. + f.writeLock.RLock() + defer f.writeLock.RUnlock() + + if table := f.tables[kind]; table != nil { + return table.size() + } + return 0, errUnknownTable +} + +// ReadAncients runs the given read operation while ensuring that no writes take place +// on the underlying freezer. +func (f *Freezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { + f.writeLock.RLock() + defer f.writeLock.RUnlock() + + return fn(f) +} + +// ModifyAncients runs the given write operation. +func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { + if f.readonly { + return 0, errReadOnly + } + f.writeLock.Lock() + defer f.writeLock.Unlock() + + // Roll back all tables to the starting position in case of error. + prevItem := f.frozen.Load() + defer func() { + if err != nil { + // The write operation has failed. Go back to the previous item position. + for name, table := range f.tables { + err := table.truncateHead(prevItem) + if err != nil { + log.Error("Freezer table roll-back failed", "table", name, "index", prevItem, "err", err) + } + } + } + }() + + f.writeBatch.reset() + if err := fn(f.writeBatch); err != nil { + return 0, err + } + item, writeSize, err := f.writeBatch.commit() + if err != nil { + return 0, err + } + f.frozen.Store(item) + return writeSize, nil +} + +// TruncateHead discards any recent data above the provided threshold number. +// It returns the previous head number. +func (f *Freezer) TruncateHead(items uint64) (uint64, error) { + if f.readonly { + return 0, errReadOnly + } + f.writeLock.Lock() + defer f.writeLock.Unlock() + + oitems := f.frozen.Load() + if oitems <= items { + return oitems, nil + } + for _, table := range f.tables { + if err := table.truncateHead(items); err != nil { + return 0, err + } + } + f.frozen.Store(items) + return oitems, nil +} + +// TruncateTail discards any recent data below the provided threshold number. +func (f *Freezer) TruncateTail(tail uint64) (uint64, error) { + if f.readonly { + return 0, errReadOnly + } + f.writeLock.Lock() + defer f.writeLock.Unlock() + + old := f.tail.Load() + if old >= tail { + return old, nil + } + for _, table := range f.tables { + if err := table.truncateTail(tail); err != nil { + return 0, err + } + } + f.tail.Store(tail) + return old, nil +} + +// Sync flushes all data tables to disk. +func (f *Freezer) Sync() error { + var errs []error + for _, table := range f.tables { + if err := table.Sync(); err != nil { + errs = append(errs, err) + } + } + if errs != nil { + return fmt.Errorf("%v", errs) + } + return nil +} + +// validate checks that every table has the same boundary. +// Used instead of `repair` in readonly mode. +func (f *Freezer) validate() error { + if len(f.tables) == 0 { + return nil + } + var ( + head uint64 + tail uint64 + name string + ) + // Hack to get boundary of any table + for kind, table := range f.tables { + head = table.items.Load() + tail = table.itemHidden.Load() + name = kind + break + } + // Now check every table against those boundaries. + for kind, table := range f.tables { + if head != table.items.Load() { + return fmt.Errorf("freezer tables %s and %s have differing head: %d != %d", kind, name, table.items.Load(), head) + } + if tail != table.itemHidden.Load() { + return fmt.Errorf("freezer tables %s and %s have differing tail: %d != %d", kind, name, table.itemHidden.Load(), tail) + } + } + f.frozen.Store(head) + f.tail.Store(tail) + return nil +} + +// repair truncates all data tables to the same length. +func (f *Freezer) repair() error { + var ( + head = uint64(math.MaxUint64) + tail = uint64(0) + ) + for _, table := range f.tables { + items := table.items.Load() + if head > items { + head = items + } + hidden := table.itemHidden.Load() + if hidden > tail { + tail = hidden + } + } + for _, table := range f.tables { + if err := table.truncateHead(head); err != nil { + return err + } + if err := table.truncateTail(tail); err != nil { + return err + } + } + f.frozen.Store(head) + f.tail.Store(tail) + return nil +} + +// convertLegacyFn takes a raw freezer entry in an older format and +// returns it in the new format. +type convertLegacyFn = func([]byte) ([]byte, error) + +// MigrateTable processes the entries in a given table in sequence +// converting them to a new format if they're of an old format. +func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { + if f.readonly { + return errReadOnly + } + f.writeLock.Lock() + defer f.writeLock.Unlock() + + table, ok := f.tables[kind] + if !ok { + return errUnknownTable + } + // forEach iterates every entry in the table serially and in order, calling `fn` + // with the item as argument. If `fn` returns an error the iteration stops + // and that error will be returned. + forEach := func(t *freezerTable, offset uint64, fn func(uint64, []byte) error) error { + var ( + items = t.items.Load() + batchSize = uint64(1024) + maxBytes = uint64(1024 * 1024) + ) + for i := offset; i < items; { + if i+batchSize > items { + batchSize = items - i + } + data, err := t.RetrieveItems(i, batchSize, maxBytes) + if err != nil { + return err + } + for j, item := range data { + if err := fn(i+uint64(j), item); err != nil { + return err + } + } + i += uint64(len(data)) + } + return nil + } + // TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration + // process assumes no deletion at tail and needs to be modified to account for that. + if table.itemOffset.Load() > 0 || table.itemHidden.Load() > 0 { + return errors.New("migration not supported for tail-deleted freezers") + } + ancientsPath := filepath.Dir(table.index.Name()) + // Set up new dir for the migrated table, the content of which + // we'll at the end move over to the ancients dir. + migrationPath := filepath.Join(ancientsPath, "migration") + newTable, err := newFreezerTable(migrationPath, kind, table.noCompression, false) + if err != nil { + return err + } + var ( + batch = newTable.newBatch() + out []byte + start = time.Now() + logged = time.Now() + offset = newTable.items.Load() + ) + if offset > 0 { + log.Info("found previous migration attempt", "migrated", offset) + } + // Iterate through entries and transform them + if err := forEach(table, offset, func(i uint64, blob []byte) error { + if i%10000 == 0 && time.Since(logged) > 16*time.Second { + log.Info("Processing legacy elements", "count", i, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + out, err = convert(blob) + if err != nil { + return err + } + if err := batch.AppendRaw(i, out); err != nil { + return err + } + return nil + }); err != nil { + return err + } + if err := batch.commit(); err != nil { + return err + } + log.Info("Replacing old table files with migrated ones", "elapsed", common.PrettyDuration(time.Since(start))) + // Release and delete old table files. Note this won't + // delete the index file. + table.releaseFilesAfter(0, true) + + if err := newTable.Close(); err != nil { + return err + } + files, err := os.ReadDir(migrationPath) + if err != nil { + return err + } + // Move migrated files to ancients dir. + for _, f := range files { + // This will replace the old index file as a side-effect. + if err := os.Rename(filepath.Join(migrationPath, f.Name()), filepath.Join(ancientsPath, f.Name())); err != nil { + return err + } + } + // Delete by now empty dir. + if err := os.Remove(migrationPath); err != nil { + return err + } + return nil +} diff --git a/core/rawdb/freezer_batch.go b/core/rawdb/freezer_batch.go new file mode 100644 index 0000000..84a63a4 --- /dev/null +++ b/core/rawdb/freezer_batch.go @@ -0,0 +1,255 @@ +// 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 rawdb + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/rlp" + "github.com/golang/snappy" +) + +// This is the maximum amount of data that will be buffered in memory +// for a single freezer table batch. +const freezerBatchBufferLimit = 2 * 1024 * 1024 + +// freezerBatch is a write operation of multiple items on a freezer. +type freezerBatch struct { + tables map[string]*freezerTableBatch +} + +func newFreezerBatch(f *Freezer) *freezerBatch { + batch := &freezerBatch{tables: make(map[string]*freezerTableBatch, len(f.tables))} + for kind, table := range f.tables { + batch.tables[kind] = table.newBatch() + } + return batch +} + +// Append adds an RLP-encoded item of the given kind. +func (batch *freezerBatch) Append(kind string, num uint64, item interface{}) error { + return batch.tables[kind].Append(num, item) +} + +// AppendRaw adds an item of the given kind. +func (batch *freezerBatch) AppendRaw(kind string, num uint64, item []byte) error { + return batch.tables[kind].AppendRaw(num, item) +} + +// reset initializes the batch. +func (batch *freezerBatch) reset() { + for _, tb := range batch.tables { + tb.reset() + } +} + +// commit is called at the end of a write operation and +// writes all remaining data to tables. +func (batch *freezerBatch) commit() (item uint64, writeSize int64, err error) { + // Check that count agrees on all batches. + item = uint64(math.MaxUint64) + for name, tb := range batch.tables { + if item < math.MaxUint64 && tb.curItem != item { + return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, tb.curItem, item) + } + item = tb.curItem + } + + // Commit all table batches. + for _, tb := range batch.tables { + if err := tb.commit(); err != nil { + return 0, 0, err + } + writeSize += tb.totalBytes + } + return item, writeSize, nil +} + +// freezerTableBatch is a batch for a freezer table. +type freezerTableBatch struct { + t *freezerTable + + sb *snappyBuffer + encBuffer writeBuffer + dataBuffer []byte + indexBuffer []byte + curItem uint64 // expected index of next append + totalBytes int64 // counts written bytes since reset +} + +// newBatch creates a new batch for the freezer table. +func (t *freezerTable) newBatch() *freezerTableBatch { + batch := &freezerTableBatch{t: t} + if !t.noCompression { + batch.sb = new(snappyBuffer) + } + batch.reset() + return batch +} + +// reset clears the batch for reuse. +func (batch *freezerTableBatch) reset() { + batch.dataBuffer = batch.dataBuffer[:0] + batch.indexBuffer = batch.indexBuffer[:0] + batch.curItem = batch.t.items.Load() + batch.totalBytes = 0 +} + +// Append rlp-encodes and adds data at the end of the freezer table. The item number is a +// precautionary parameter to ensure data correctness, but the table will reject already +// existing data. +func (batch *freezerTableBatch) Append(item uint64, data interface{}) error { + if item != batch.curItem { + return fmt.Errorf("%w: have %d want %d", errOutOrderInsertion, item, batch.curItem) + } + + // Encode the item. + batch.encBuffer.Reset() + if err := rlp.Encode(&batch.encBuffer, data); err != nil { + return err + } + encItem := batch.encBuffer.data + if batch.sb != nil { + encItem = batch.sb.compress(encItem) + } + return batch.appendItem(encItem) +} + +// AppendRaw injects a binary blob at the end of the freezer table. The item number is a +// precautionary parameter to ensure data correctness, but the table will reject already +// existing data. +func (batch *freezerTableBatch) AppendRaw(item uint64, blob []byte) error { + if item != batch.curItem { + return fmt.Errorf("%w: have %d want %d", errOutOrderInsertion, item, batch.curItem) + } + + encItem := blob + if batch.sb != nil { + encItem = batch.sb.compress(blob) + } + return batch.appendItem(encItem) +} + +func (batch *freezerTableBatch) appendItem(data []byte) error { + // Check if item fits into current data file. + itemSize := int64(len(data)) + itemOffset := batch.t.headBytes + int64(len(batch.dataBuffer)) + if itemOffset+itemSize > int64(batch.t.maxFileSize) { + // It doesn't fit, go to next file first. + if err := batch.commit(); err != nil { + return err + } + if err := batch.t.advanceHead(); err != nil { + return err + } + itemOffset = 0 + } + + // Put data to buffer. + batch.dataBuffer = append(batch.dataBuffer, data...) + batch.totalBytes += itemSize + + // Put index entry to buffer. + entry := indexEntry{filenum: batch.t.headId, offset: uint32(itemOffset + itemSize)} + batch.indexBuffer = entry.append(batch.indexBuffer) + batch.curItem++ + + return batch.maybeCommit() +} + +// maybeCommit writes the buffered data if the buffer is full enough. +func (batch *freezerTableBatch) maybeCommit() error { + if len(batch.dataBuffer) > freezerBatchBufferLimit { + return batch.commit() + } + return nil +} + +// commit writes the batched items to the backing freezerTable. +func (batch *freezerTableBatch) commit() error { + // 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. 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] + + // Update headBytes of table. + batch.t.headBytes += dataSize + batch.t.items.Store(batch.curItem) + + // Update metrics. + batch.t.sizeGauge.Inc(dataSize + indexSize) + batch.t.writeMeter.Mark(dataSize + indexSize) + return nil +} + +// snappyBuffer writes snappy in block format, and can be reused. It is +// reset when WriteTo is called. +type snappyBuffer struct { + dst []byte +} + +// compress snappy-compresses the data. +func (s *snappyBuffer) compress(data []byte) []byte { + // The snappy library does not care what the capacity of the buffer is, + // but only checks the length. If the length is too small, it will + // allocate a brand new buffer. + // To avoid that, we check the required size here, and grow the size of the + // buffer to utilize the full capacity. + if n := snappy.MaxEncodedLen(len(data)); len(s.dst) < n { + if cap(s.dst) < n { + s.dst = make([]byte, n) + } + s.dst = s.dst[:n] + } + + s.dst = snappy.Encode(s.dst, data) + return s.dst +} + +// writeBuffer implements io.Writer for a byte slice. +type writeBuffer struct { + data []byte +} + +func (wb *writeBuffer) Write(data []byte) (int, error) { + wb.data = append(wb.data, data...) + return len(data), nil +} + +func (wb *writeBuffer) Reset() { + wb.data = wb.data[:0] +} diff --git a/core/rawdb/freezer_memory.go b/core/rawdb/freezer_memory.go new file mode 100644 index 0000000..954b58e --- /dev/null +++ b/core/rawdb/freezer_memory.go @@ -0,0 +1,428 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// memoryTable is used to store a list of sequential items in memory. +type memoryTable struct { + name string // Table name + items uint64 // Number of stored items in the table, including the deleted ones + offset uint64 // Number of deleted items from the table + data [][]byte // List of rlp-encoded items, sort in order + size uint64 // Total memory size occupied by the table + lock sync.RWMutex +} + +// newMemoryTable initializes the memory table. +func newMemoryTable(name string) *memoryTable { + return &memoryTable{name: name} +} + +// has returns an indicator whether the specified data exists. +func (t *memoryTable) has(number uint64) bool { + t.lock.RLock() + defer t.lock.RUnlock() + + return number >= t.offset && number < t.items +} + +// retrieve retrieves multiple items in sequence, starting from the index 'start'. +// It will return: +// - 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 (t *memoryTable) retrieve(start uint64, count, maxBytes uint64) ([][]byte, error) { + t.lock.RLock() + defer t.lock.RUnlock() + + var ( + size uint64 + batch [][]byte + ) + // Ensure the start is written, not deleted from the tail, and that the + // caller actually wants something. + if t.items <= start || t.offset > start || count == 0 { + return nil, errOutOfBounds + } + // Cap the item count if the retrieval is out of bound. + if start+count > t.items { + count = t.items - start + } + for n := start; n < start+count; n++ { + index := n - t.offset + if len(batch) != 0 && maxBytes != 0 && size+uint64(len(t.data[index])) > maxBytes { + return batch, nil + } + batch = append(batch, t.data[index]) + size += uint64(len(t.data[index])) + } + return batch, nil +} + +// truncateHead discards any recent data above the provided threshold number. +func (t *memoryTable) truncateHead(items uint64) error { + t.lock.Lock() + defer t.lock.Unlock() + + // Short circuit if nothing to delete. + if t.items <= items { + return nil + } + if items < t.offset { + return errors.New("truncation below tail") + } + t.data = t.data[:items-t.offset] + t.items = items + return nil +} + +// truncateTail discards any recent data before the provided threshold number. +func (t *memoryTable) truncateTail(items uint64) error { + t.lock.Lock() + defer t.lock.Unlock() + + // Short circuit if nothing to delete. + if t.offset >= items { + return nil + } + if t.items < items { + return errors.New("truncation above head") + } + t.data = t.data[items-t.offset:] + t.offset = items + return nil +} + +// commit merges the given item batch into table. It's presumed that the +// batch is ordered and continuous with table. +func (t *memoryTable) commit(batch [][]byte) error { + t.lock.Lock() + defer t.lock.Unlock() + + for _, item := range batch { + t.size += uint64(len(item)) + } + t.data = append(t.data, batch...) + t.items += uint64(len(batch)) + return nil +} + +// memoryBatch is the singleton batch used for ancient write. +type memoryBatch struct { + data map[string][][]byte + next map[string]uint64 + size map[string]int64 +} + +func newMemoryBatch() *memoryBatch { + return &memoryBatch{ + data: make(map[string][][]byte), + next: make(map[string]uint64), + size: make(map[string]int64), + } +} + +func (b *memoryBatch) reset(freezer *MemoryFreezer) { + b.data = make(map[string][][]byte) + b.next = make(map[string]uint64) + b.size = make(map[string]int64) + + for name, table := range freezer.tables { + b.next[name] = table.items + } +} + +// Append adds an RLP-encoded item. +func (b *memoryBatch) Append(kind string, number uint64, item interface{}) error { + if b.next[kind] != number { + return errOutOrderInsertion + } + blob, err := rlp.EncodeToBytes(item) + if err != nil { + return err + } + b.data[kind] = append(b.data[kind], blob) + b.next[kind]++ + b.size[kind] += int64(len(blob)) + return nil +} + +// AppendRaw adds an item without RLP-encoding it. +func (b *memoryBatch) AppendRaw(kind string, number uint64, blob []byte) error { + if b.next[kind] != number { + return errOutOrderInsertion + } + b.data[kind] = append(b.data[kind], common.CopyBytes(blob)) + b.next[kind]++ + b.size[kind] += int64(len(blob)) + return nil +} + +// commit is called at the end of a write operation and writes all remaining +// data to tables. +func (b *memoryBatch) commit(freezer *MemoryFreezer) (items uint64, writeSize int64, err error) { + // Check that count agrees on all batches. + items = math.MaxUint64 + for name, next := range b.next { + if items < math.MaxUint64 && next != items { + return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, next, items) + } + items = next + } + // Commit all table batches. + for name, batch := range b.data { + table := freezer.tables[name] + if err := table.commit(batch); err != nil { + return 0, 0, err + } + writeSize += b.size[name] + } + return items, writeSize, nil +} + +// MemoryFreezer is an ephemeral ancient store. It implements the ethdb.AncientStore +// interface and can be used along with ephemeral key-value store. +type MemoryFreezer struct { + items uint64 // Number of items stored + tail uint64 // Number of the first stored item in the freezer + readonly bool // Flag if the freezer is only for reading + lock sync.RWMutex // Lock to protect fields + tables map[string]*memoryTable // Tables for storing everything + writeBatch *memoryBatch // Pre-allocated write batch +} + +// NewMemoryFreezer initializes an in-memory freezer instance. +func NewMemoryFreezer(readonly bool, tableName map[string]bool) *MemoryFreezer { + tables := make(map[string]*memoryTable) + for name := range tableName { + tables[name] = newMemoryTable(name) + } + return &MemoryFreezer{ + writeBatch: newMemoryBatch(), + readonly: readonly, + tables: tables, + } +} + +// HasAncient returns an indicator whether the specified data exists. +func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + if table := f.tables[kind]; table != nil { + return table.has(number), nil + } + return false, nil +} + +// Ancient retrieves an ancient binary blob from the in-memory freezer. +func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + t := f.tables[kind] + if t == nil { + return nil, errUnknownTable + } + data, err := t.retrieve(number, 1, 0) + if err != nil { + return nil, err + } + return data[0], nil +} + +// AncientRange retrieves multiple items in sequence, starting from the index 'start'. +// It will return +// - 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 *MemoryFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + t := f.tables[kind] + if t == nil { + return nil, errUnknownTable + } + return t.retrieve(start, count, maxBytes) +} + +// Ancients returns the ancient item numbers in the freezer. +func (f *MemoryFreezer) Ancients() (uint64, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.items, nil +} + +// Tail returns the number of first stored item in the freezer. +// This number can also be interpreted as the total deleted item numbers. +func (f *MemoryFreezer) Tail() (uint64, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.tail, nil +} + +// AncientSize returns the ancient size of the specified category. +func (f *MemoryFreezer) AncientSize(kind string) (uint64, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + if table := f.tables[kind]; table != nil { + return table.size, nil + } + return 0, errUnknownTable +} + +// ReadAncients runs the given read operation while ensuring that no writes take place +// on the underlying freezer. +func (f *MemoryFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return fn(f) +} + +// ModifyAncients runs the given write operation. +func (f *MemoryFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.readonly { + return 0, errReadOnly + } + // Roll back all tables to the starting position in case of error. + defer func(old uint64) { + if err == nil { + return + } + // The write operation has failed. Go back to the previous item position. + for name, table := range f.tables { + err := table.truncateHead(old) + if err != nil { + log.Error("Freezer table roll-back failed", "table", name, "index", old, "err", err) + } + } + }(f.items) + + // Modify the ancients in batch. + f.writeBatch.reset(f) + if err := fn(f.writeBatch); err != nil { + return 0, err + } + item, writeSize, err := f.writeBatch.commit(f) + if err != nil { + return 0, err + } + f.items = item + return writeSize, nil +} + +// TruncateHead discards any recent data above the provided threshold number. +// It returns the previous head number. +func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.readonly { + return 0, errReadOnly + } + old := f.items + if old <= items { + return old, nil + } + for _, table := range f.tables { + if err := table.truncateHead(items); err != nil { + return 0, err + } + } + f.items = items + return old, nil +} + +// TruncateTail discards any recent data below the provided threshold number. +func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.readonly { + return 0, errReadOnly + } + old := f.tail + if old >= tail { + return old, nil + } + for _, table := range f.tables { + if err := table.truncateTail(tail); err != nil { + return 0, err + } + } + f.tail = tail + return old, nil +} + +// Sync flushes all data tables to disk. +func (f *MemoryFreezer) Sync() error { + return nil +} + +// MigrateTable processes and migrates entries of a given table to a new format. +// The second argument is a function that takes a raw entry and returns it +// in the newest format. +func (f *MemoryFreezer) MigrateTable(string, func([]byte) ([]byte, error)) error { + return errors.New("not implemented") +} + +// Close releases all the sources held by the memory freezer. It will panic if +// any following invocation is made to a closed freezer. +func (f *MemoryFreezer) Close() error { + f.lock.Lock() + defer f.lock.Unlock() + + f.tables = nil + f.writeBatch = nil + return nil +} + +// Reset drops all the data cached in the memory freezer and reset itself +// back to default state. +func (f *MemoryFreezer) Reset() error { + f.lock.Lock() + defer f.lock.Unlock() + + tables := make(map[string]*memoryTable) + for name := range f.tables { + tables[name] = newMemoryTable(name) + } + f.tables = tables + f.items, f.tail = 0, 0 + return nil +} diff --git a/core/rawdb/freezer_memory_test.go b/core/rawdb/freezer_memory_test.go new file mode 100644 index 0000000..e71de0f --- /dev/null +++ b/core/rawdb/freezer_memory_test.go @@ -0,0 +1,41 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "testing" + + "github.com/ethereum/go-ethereum/core/rawdb/ancienttest" + "github.com/ethereum/go-ethereum/ethdb" +) + +func TestMemoryFreezer(t *testing.T) { + ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore { + tables := make(map[string]bool) + for _, kind := range kinds { + tables[kind] = true + } + return NewMemoryFreezer(false, tables) + }) + ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore { + tables := make(map[string]bool) + for _, kind := range kinds { + tables[kind] = true + } + return NewMemoryFreezer(false, tables) + }) +} diff --git a/core/rawdb/freezer_meta.go b/core/rawdb/freezer_meta.go new file mode 100644 index 0000000..9eef9df --- /dev/null +++ b/core/rawdb/freezer_meta.go @@ -0,0 +1,109 @@ +// 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 rawdb + +import ( + "io" + "os" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +const freezerVersion = 1 // The initial version tag of freezer table metadata + +// freezerTableMeta wraps all the metadata of the freezer table. +type freezerTableMeta struct { + // Version is the versioning descriptor of the freezer table. + Version uint16 + + // VirtualTail indicates how many items have been marked as deleted. + // Its value is equal to the number of items removed from the table + // plus the number of items hidden in the table, so it should never + // be lower than the "actual tail". + VirtualTail uint64 +} + +// newMetadata initializes the metadata object with the given virtual tail. +func newMetadata(tail uint64) *freezerTableMeta { + return &freezerTableMeta{ + Version: freezerVersion, + VirtualTail: tail, + } +} + +// readMetadata reads the metadata of the freezer table from the +// given metadata file. +func readMetadata(file *os.File) (*freezerTableMeta, error) { + _, err := file.Seek(0, io.SeekStart) + if err != nil { + return nil, err + } + var meta freezerTableMeta + if err := rlp.Decode(file, &meta); err != nil { + return nil, err + } + return &meta, nil +} + +// writeMetadata writes the metadata of the freezer table into the +// given metadata file. +func writeMetadata(file *os.File, meta *freezerTableMeta) error { + _, err := file.Seek(0, io.SeekStart) + if err != nil { + return err + } + return rlp.Encode(file, meta) +} + +// loadMetadata loads the metadata from the given metadata file. +// Initializes the metadata file with the given "actual tail" if +// it's empty. +func loadMetadata(file *os.File, tail uint64) (*freezerTableMeta, error) { + stat, err := file.Stat() + if err != nil { + return nil, err + } + // Write the metadata with the given actual tail into metadata file + // if it's non-existent. There are two possible scenarios here: + // - the freezer table is empty + // - the freezer table is legacy + // In both cases, write the meta into the file with the actual tail + // as the virtual tail. + if stat.Size() == 0 { + m := newMetadata(tail) + if err := writeMetadata(file, m); err != nil { + return nil, err + } + return m, nil + } + m, err := readMetadata(file) + if err != nil { + return nil, err + } + // Update the virtual tail with the given actual tail if it's even + // lower than it. Theoretically it shouldn't happen at all, print + // a warning here. + if m.VirtualTail < tail { + log.Warn("Updated virtual tail", "have", m.VirtualTail, "now", tail) + m.VirtualTail = tail + if err := writeMetadata(file, m); err != nil { + return nil, err + } + } + return m, nil +} diff --git a/core/rawdb/freezer_meta_test.go b/core/rawdb/freezer_meta_test.go new file mode 100644 index 0000000..ba1a95e --- /dev/null +++ b/core/rawdb/freezer_meta_test.go @@ -0,0 +1,60 @@ +// 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 rawdb + +import ( + "os" + "testing" +) + +func TestReadWriteFreezerTableMeta(t *testing.T) { + f, err := os.CreateTemp(os.TempDir(), "*") + if err != nil { + t.Fatalf("Failed to create file %v", err) + } + err = writeMetadata(f, newMetadata(100)) + if err != nil { + t.Fatalf("Failed to write metadata %v", err) + } + meta, err := readMetadata(f) + if err != nil { + t.Fatalf("Failed to read metadata %v", err) + } + if meta.Version != freezerVersion { + t.Fatalf("Unexpected version field") + } + if meta.VirtualTail != uint64(100) { + t.Fatalf("Unexpected virtual tail field") + } +} + +func TestInitializeFreezerTableMeta(t *testing.T) { + f, err := os.CreateTemp(os.TempDir(), "*") + if err != nil { + t.Fatalf("Failed to create file %v", err) + } + meta, err := loadMetadata(f, uint64(100)) + if err != nil { + t.Fatalf("Failed to read metadata %v", err) + } + if meta.Version != freezerVersion { + t.Fatalf("Unexpected version field") + } + if meta.VirtualTail != uint64(100) { + t.Fatalf("Unexpected virtual tail field") + } +} diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go new file mode 100644 index 0000000..6f8541f --- /dev/null +++ b/core/rawdb/freezer_resettable.go @@ -0,0 +1,243 @@ +// 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 rawdb + +import ( + "os" + "path/filepath" + "sync" + + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +const tmpSuffix = ".tmp" + +// freezerOpenFunc is the function used to open/create a freezer. +type freezerOpenFunc = func() (*Freezer, error) + +// resettableFreezer is a wrapper of the freezer which makes the +// freezer resettable. +type resettableFreezer struct { + readOnly bool + freezer *Freezer + opener freezerOpenFunc + datadir string + lock sync.RWMutex +} + +// newResettableFreezer creates a resettable freezer, note freezer is +// only resettable if the passed file directory is exclusively occupied +// by the freezer. And also the user-configurable ancient root directory +// is **not** supported for reset since it might be a mount and rename +// will cause a copy of hundreds of gigabyte into local directory. It +// needs some other file based solutions. +// +// The reset function will delete directory atomically and re-create the +// freezer from scratch. +func newResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*resettableFreezer, error) { + if err := cleanup(datadir); err != nil { + return nil, err + } + opener := func() (*Freezer, error) { + return NewFreezer(datadir, namespace, readonly, maxTableSize, tables) + } + freezer, err := opener() + if err != nil { + return nil, err + } + return &resettableFreezer{ + readOnly: readonly, + freezer: freezer, + opener: opener, + datadir: datadir, + }, nil +} + +// Reset deletes the file directory exclusively occupied by the freezer and +// recreate the freezer from scratch. The atomicity of directory deletion +// is guaranteed by the rename operation, the leftover directory will be +// cleaned up in next startup in case crash happens after rename. +func (f *resettableFreezer) Reset() error { + f.lock.Lock() + defer f.lock.Unlock() + + if f.readOnly { + return errReadOnly + } + if err := f.freezer.Close(); err != nil { + return err + } + tmp := tmpName(f.datadir) + if err := os.Rename(f.datadir, tmp); err != nil { + return err + } + if err := os.RemoveAll(tmp); err != nil { + return err + } + freezer, err := f.opener() + if err != nil { + return err + } + f.freezer = freezer + return nil +} + +// Close terminates the chain freezer, unmapping all the data files. +func (f *resettableFreezer) Close() error { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.Close() +} + +// HasAncient returns an indicator whether the specified ancient data exists +// in the freezer +func (f *resettableFreezer) HasAncient(kind string, number uint64) (bool, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.HasAncient(kind, number) +} + +// Ancient retrieves an ancient binary blob from the append-only immutable files. +func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.Ancient(kind, number) +} + +// AncientRange retrieves multiple items in sequence, starting from the index 'start'. +// It will return +// - 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() + + return f.freezer.AncientRange(kind, start, count, maxBytes) +} + +// Ancients returns the length of the frozen items. +func (f *resettableFreezer) Ancients() (uint64, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.Ancients() +} + +// Tail returns the number of first stored item in the freezer. +func (f *resettableFreezer) Tail() (uint64, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.Tail() +} + +// AncientSize returns the ancient size of the specified category. +func (f *resettableFreezer) AncientSize(kind string) (uint64, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.AncientSize(kind) +} + +// ReadAncients runs the given read operation while ensuring that no writes take place +// on the underlying freezer. +func (f *resettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.ReadAncients(fn) +} + +// ModifyAncients runs the given write operation. +func (f *resettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.ModifyAncients(fn) +} + +// TruncateHead discards any recent data above the provided threshold number. +// It returns the previous head number. +func (f *resettableFreezer) TruncateHead(items uint64) (uint64, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.TruncateHead(items) +} + +// TruncateTail discards any recent data below the provided threshold number. +// It returns the previous value +func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.TruncateTail(tail) +} + +// Sync flushes all data tables to disk. +func (f *resettableFreezer) Sync() error { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.Sync() +} + +// MigrateTable processes the entries in a given table in sequence +// converting them to a new format if they're of an old format. +func (f *resettableFreezer) MigrateTable(kind string, convert convertLegacyFn) error { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.MigrateTable(kind, convert) +} + +// cleanup removes the directory located in the specified path +// has the name with deletion marker suffix. +func cleanup(path string) error { + parent := filepath.Dir(path) + if _, err := os.Lstat(parent); os.IsNotExist(err) { + return nil + } + dir, err := os.Open(parent) + if err != nil { + return err + } + names, err := dir.Readdirnames(0) + if err != nil { + return err + } + if cerr := dir.Close(); cerr != nil { + return cerr + } + 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)) + } + } + return nil +} + +func tmpName(path string) string { + return filepath.Join(filepath.Dir(path), filepath.Base(path)+tmpSuffix) +} diff --git a/core/rawdb/freezer_resettable_test.go b/core/rawdb/freezer_resettable_test.go new file mode 100644 index 0000000..61dc23d --- /dev/null +++ b/core/rawdb/freezer_resettable_test.go @@ -0,0 +1,107 @@ +// 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 rawdb + +import ( + "bytes" + "os" + "testing" + + "github.com/ethereum/go-ethereum/ethdb" +) + +func TestResetFreezer(t *testing.T) { + items := []struct { + id uint64 + blob []byte + }{ + {0, bytes.Repeat([]byte{0}, 2048)}, + {1, bytes.Repeat([]byte{1}, 2048)}, + {2, bytes.Repeat([]byte{2}, 2048)}, + } + f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef) + defer f.Close() + + f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for _, item := range items { + op.AppendRaw("test", item.id, item.blob) + } + return nil + }) + for _, item := range items { + blob, _ := f.Ancient("test", item.id) + if !bytes.Equal(blob, item.blob) { + t.Fatal("Unexpected blob") + } + } + + // Reset freezer + f.Reset() + count, _ := f.Ancients() + if count != 0 { + t.Fatal("Failed to reset freezer") + } + for _, item := range items { + blob, _ := f.Ancient("test", item.id) + if len(blob) != 0 { + t.Fatal("Unexpected blob") + } + } + + // Fill the freezer + f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for _, item := range items { + op.AppendRaw("test", item.id, item.blob) + } + return nil + }) + for _, item := range items { + blob, _ := f.Ancient("test", item.id) + if !bytes.Equal(blob, item.blob) { + t.Fatal("Unexpected blob") + } + } +} + +func TestFreezerCleanup(t *testing.T) { + items := []struct { + id uint64 + blob []byte + }{ + {0, bytes.Repeat([]byte{0}, 2048)}, + {1, bytes.Repeat([]byte{1}, 2048)}, + {2, bytes.Repeat([]byte{2}, 2048)}, + } + datadir := t.TempDir() + f, _ := newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef) + f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for _, item := range items { + op.AppendRaw("test", item.id, item.blob) + } + return nil + }) + f.Close() + os.Rename(datadir, tmpName(datadir)) + + // Open the freezer again, trigger cleanup operation + f, _ = newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef) + f.Close() + + if _, err := os.Lstat(tmpName(datadir)); !os.IsNotExist(err) { + t.Fatal("Failed to cleanup leftover directory") + } +} diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go new file mode 100644 index 0000000..4b9d510 --- /dev/null +++ b/core/rawdb/freezer_table.go @@ -0,0 +1,990 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sync" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/golang/snappy" +) + +var ( + // errClosed is returned if an operation attempts to read from or write to the + // freezer table after it has already been closed. + errClosed = errors.New("closed") + + // errOutOfBounds is returned if the item requested is not contained within the + // freezer table. + errOutOfBounds = errors.New("out of bounds") + + // errNotSupported is returned if the database doesn't support the required operation. + errNotSupported = errors.New("this operation is not supported") +) + +// indexEntry contains the number/id of the file that the data resides in, as well as the +// offset within the file to the end of the data. +// In serialized form, the filenum is stored as uint16. +type indexEntry struct { + filenum uint32 // stored as uint16 ( 2 bytes ) + offset uint32 // stored as uint32 ( 4 bytes ) +} + +const indexEntrySize = 6 + +// unmarshalBinary deserializes binary b into the rawIndex entry. +func (i *indexEntry) unmarshalBinary(b []byte) { + i.filenum = uint32(binary.BigEndian.Uint16(b[:2])) + i.offset = binary.BigEndian.Uint32(b[2:6]) +} + +// append adds the encoded entry to the end of b. +func (i *indexEntry) append(b []byte) []byte { + offset := len(b) + out := append(b, make([]byte, indexEntrySize)...) + binary.BigEndian.PutUint16(out[offset:], uint16(i.filenum)) + binary.BigEndian.PutUint32(out[offset+2:], i.offset) + return out +} + +// bounds returns the start- and end- offsets, and the file number of where to +// read there data item marked by the two index entries. The two entries are +// assumed to be sequential. +func (i *indexEntry) bounds(end *indexEntry) (startOffset, endOffset, fileId uint32) { + if i.filenum != end.filenum { + // If a piece of data 'crosses' a data-file, + // it's actually in one piece on the second data-file. + // We return a zero-indexEntry for the second file as start + return 0, end.offset, end.filenum + } + return i.offset, end.offset, end.filenum +} + +// freezerTable represents a single chained data table within the freezer (e.g. blocks). +// It consists of a data file (snappy encoded arbitrary data blobs) and an indexEntry +// file (uncompressed 64 bit indices into the data file). +type freezerTable struct { + items atomic.Uint64 // Number of items stored in the table (including items removed from tail) + itemOffset atomic.Uint64 // Number of items removed from the table + + // itemHidden is the number of items marked as deleted. Tail deletion is + // only supported at file level which means the actual deletion will be + // delayed until the entire data file is marked as deleted. Before that + // these items will be hidden to prevent being visited again. The value + // should never be lower than itemOffset. + itemHidden atomic.Uint64 + + noCompression bool // if true, disables snappy compression. Note: does not work retroactively + readonly bool + maxFileSize uint32 // Max file size for data-files + name string + path string + + head *os.File // File descriptor for the data head of the table + index *os.File // File descriptor for the indexEntry file of the table + meta *os.File // File descriptor for metadata of the table + files map[uint32]*os.File // open files + headId uint32 // number of the currently active head file + tailId uint32 // number of the earliest file + + headBytes int64 // Number of bytes written to the head file + readMeter metrics.Meter // Meter for measuring the effective amount of data read + writeMeter metrics.Meter // Meter for measuring the effective amount of data written + sizeGauge metrics.Gauge // Gauge for tracking the combined size of all freezer tables + + logger log.Logger // Logger with database path and table name embedded + lock sync.RWMutex // Mutex protecting the data file descriptors +} + +// newFreezerTable opens the given path as a freezer table. +func newFreezerTable(path, name string, disableSnappy, readonly bool) (*freezerTable, error) { + return newTable(path, name, metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, freezerTableSize, disableSnappy, readonly) +} + +// newTable opens a freezer table, creating the data and index files if they are +// non-existent. Both files are truncated to the shortest common length to ensure +// they don't go out of sync. +func newTable(path string, name string, readMeter metrics.Meter, writeMeter metrics.Meter, sizeGauge metrics.Gauge, maxFilesize uint32, noCompression, readonly bool) (*freezerTable, error) { + // Ensure the containing directory exists and open the indexEntry file + if err := os.MkdirAll(path, 0755); err != nil { + return nil, err + } + var idxName string + if noCompression { + idxName = fmt.Sprintf("%s.ridx", name) // raw index file + } else { + idxName = fmt.Sprintf("%s.cidx", name) // compressed index file + } + var ( + err error + index *os.File + meta *os.File + ) + if readonly { + // Will fail if table index file or meta file is not existent + index, err = openFreezerFileForReadOnly(filepath.Join(path, idxName)) + if err != nil { + return nil, err + } + meta, err = openFreezerFileForReadOnly(filepath.Join(path, fmt.Sprintf("%s.meta", name))) + if err != nil { + return nil, err + } + } else { + index, err = openFreezerFileForAppend(filepath.Join(path, idxName)) + if err != nil { + return nil, err + } + meta, err = openFreezerFileForAppend(filepath.Join(path, fmt.Sprintf("%s.meta", name))) + if err != nil { + return nil, err + } + } + // Create the table and repair any past inconsistency + tab := &freezerTable{ + index: index, + meta: meta, + files: make(map[uint32]*os.File), + readMeter: readMeter, + writeMeter: writeMeter, + sizeGauge: sizeGauge, + name: name, + path: path, + logger: log.New("database", path, "table", name), + noCompression: noCompression, + readonly: readonly, + maxFileSize: maxFilesize, + } + if err := tab.repair(); err != nil { + tab.Close() + return nil, err + } + // Initialize the starting size counter + size, err := tab.sizeNolock() + if err != nil { + tab.Close() + return nil, err + } + tab.sizeGauge.Inc(int64(size)) + + return tab, nil +} + +// repair cross-checks the head and the index file and truncates them to +// be in sync with each other after a potential crash / data loss. +func (t *freezerTable) repair() error { + // Create a temporary offset buffer to init files with and read indexEntry into + buffer := make([]byte, indexEntrySize) + + // If we've just created the files, initialize the index with the 0 indexEntry + stat, err := t.index.Stat() + if err != nil { + return err + } + if stat.Size() == 0 { + if _, err := t.index.Write(buffer); err != nil { + return err + } + } + // Ensure the index is a multiple of indexEntrySize bytes + if overflow := stat.Size() % indexEntrySize; overflow != 0 { + if t.readonly { + return fmt.Errorf("index file(path: %s, name: %s) size is not a multiple of %d", t.path, t.name, indexEntrySize) + } + 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 { + return err + } + offsetsSize := stat.Size() + + // Open the head file + var ( + firstIndex indexEntry + lastIndex indexEntry + contentSize int64 + contentExp int64 + verbose bool + ) + // Read index zero, determine what file is the earliest + // and what item offset to use + t.index.ReadAt(buffer, 0) + firstIndex.unmarshalBinary(buffer) + + // Assign the tail fields with the first stored index. + // The total removed items is represented with an uint32, + // which is not enough in theory but enough in practice. + // TODO: use uint64 to represent total removed items. + t.tailId = firstIndex.filenum + t.itemOffset.Store(uint64(firstIndex.offset)) + + // Load metadata from the file + meta, err := loadMetadata(t.meta, t.itemOffset.Load()) + if err != nil { + return err + } + t.itemHidden.Store(meta.VirtualTail) + + // Read the last index, use the default value in case the freezer is empty + if offsetsSize == indexEntrySize { + lastIndex = indexEntry{filenum: t.tailId, offset: 0} + } else { + 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 { + t.head, err = t.openFile(lastIndex.filenum, openFreezerFileForAppend) + } + if err != nil { + return err + } + if stat, err = t.head.Stat(); err != nil { + return err + } + contentSize = stat.Size() + + // 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 { + t.logger.Warn("Truncating dangling head", "indexed", contentExp, "stored", contentSize) + if err := truncateFreezerFile(t.head, contentExp); err != nil { + return err + } + contentSize = contentExp + } + // Truncate the index to point within the head file + if contentExp > contentSize { + t.logger.Warn("Truncating dangling indexes", "indexes", offsetsSize/indexEntrySize, "indexed", contentExp, "stored", contentSize) + if err := truncateFreezerFile(t.index, offsetsSize-indexEntrySize); err != nil { + return err + } + offsetsSize -= indexEntrySize + + // Read the new head index, use the default value in case + // the freezer is already empty. + var newLastIndex indexEntry + if offsetsSize == indexEntrySize { + newLastIndex = indexEntry{filenum: t.tailId, offset: 0} + } else { + t.index.ReadAt(buffer, offsetsSize-indexEntrySize) + newLastIndex.unmarshalBinary(buffer) + } + // We might have slipped back into an earlier head-file here + if newLastIndex.filenum != lastIndex.filenum { + // Release earlier opened file + t.releaseFile(lastIndex.filenum) + if t.head, err = t.openFile(newLastIndex.filenum, openFreezerFileForAppend); err != nil { + return err + } + if stat, err = t.head.Stat(); err != nil { + // TODO, anything more we can do here? + // A data file has gone missing... + return err + } + contentSize = stat.Size() + } + lastIndex = newLastIndex + contentExp = int64(lastIndex.offset) + } + } + // Sync() fails for read-only files on windows. + if !t.readonly { + // Ensure all reparation changes have been written to disk + if err := t.index.Sync(); err != nil { + return err + } + if err := t.head.Sync(); err != nil { + return err + } + if err := t.meta.Sync(); err != nil { + return err + } + } + // Update the item and byte counters and return + t.items.Store(t.itemOffset.Load() + uint64(offsetsSize/indexEntrySize-1)) // last indexEntry points to the end of the data file + t.headBytes = contentSize + t.headId = lastIndex.filenum + + // Delete the leftover files because of head deletion + t.releaseFilesAfter(t.headId, true) + + // Delete the leftover files because of tail deletion + t.releaseFilesBefore(t.tailId, true) + + // Close opened files and preopen all files + if err := t.preopen(); err != nil { + return err + } + if verbose { + 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)) + } + return nil +} + +// preopen opens all files that the freezer will need. This method should be called from an init-context, +// since it assumes that it doesn't have to bother with locking +// The rationale for doing preopen is to not have to do it from within Retrieve, thus not needing to ever +// obtain a write-lock within Retrieve. +func (t *freezerTable) preopen() (err error) { + // The repair might have already opened (some) files + t.releaseFilesAfter(0, false) + + // Open all except head in RDONLY + for i := t.tailId; i < t.headId; i++ { + if _, err = t.openFile(i, openFreezerFileForReadOnly); err != nil { + return err + } + } + if t.readonly { + t.head, err = t.openFile(t.headId, openFreezerFileForReadOnly) + } else { + // Open head in read/write + t.head, err = t.openFile(t.headId, openFreezerFileForAppend) + } + return err +} + +// truncateHead discards any recent data above the provided threshold number. +func (t *freezerTable) truncateHead(items uint64) error { + t.lock.Lock() + defer t.lock.Unlock() + + // Ensure the given truncate target falls in the correct range + existing := t.items.Load() + if existing <= items { + return nil + } + if items < t.itemHidden.Load() { + return errors.New("truncation below tail") + } + // We need to truncate, save the old size for metrics tracking + oldSize, err := t.sizeNolock() + if err != nil { + return err + } + // Something's out of sync, truncate the table's offset index + log := t.logger.Debug + if existing > items+1 { + log = t.logger.Warn // Only loud warn if we delete multiple items + } + log("Truncating freezer table", "items", existing, "limit", items) + + // Truncate the index file first, the tail position is also considered + // when calculating the new freezer table length. + length := items - t.itemOffset.Load() + 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 { + expected = indexEntry{filenum: t.tailId, offset: 0} + } else { + buffer := make([]byte, indexEntrySize) + if _, err := t.index.ReadAt(buffer, int64(length*indexEntrySize)); err != nil { + return err + } + expected.unmarshalBinary(buffer) + } + // We might need to truncate back to older files + if expected.filenum != t.headId { + // If already open for reading, force-reopen for writing + t.releaseFile(expected.filenum) + newHead, err := t.openFile(expected.filenum, openFreezerFileForAppend) + if err != nil { + return err + } + // 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 + } + 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) + + // Retrieve the new size and update the total size counter + newSize, err := t.sizeNolock() + if err != nil { + return err + } + t.sizeGauge.Dec(int64(oldSize - newSize)) + 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() + defer t.lock.Unlock() + + // Ensure the given truncate target falls in the correct range + if t.itemHidden.Load() >= items { + return nil + } + if t.items.Load() < items { + return errors.New("truncation above head") + } + // Load the new tail index by the given new tail position + var ( + newTailId uint32 + buffer = make([]byte, indexEntrySize) + ) + if t.items.Load() == items { + newTailId = t.headId + } else { + offset := items - t.itemOffset.Load() + if _, err := t.index.ReadAt(buffer, int64((offset+1)*indexEntrySize)); err != nil { + return err + } + var newTail indexEntry + 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 { + return err + } + // Hidden items still fall in the current tail file, no data file + // can be dropped. + if t.tailId == newTailId { + return nil + } + // Hidden items fall in the incorrect range, returns the error. + if t.tailId > newTailId { + return fmt.Errorf("invalid index, tail-file %d, item-file %d", t.tailId, newTailId) + } + // 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 + } + var pre indexEntry + pre.unmarshalBinary(buffer) + if pre.filenum != newTailId { + break + } + newDeleted = current + } + // Commit the changes of metadata file first before manipulating + // the indexes file. + 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{ + filenum: newTailId, + offset: uint32(newDeleted), + } + _, err := f.Write(tailIndex.append(nil)) + return err + }) + if err != nil { + return err + } + // Reopen the modified index file to load the changes + 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) + t.releaseFilesBefore(t.tailId, true) + + // Retrieve the new size and update the total size counter + newSize, err := t.sizeNolock() + if err != nil { + return err + } + t.sizeGauge.Dec(int64(oldSize - newSize)) + return nil +} + +// Close closes all opened files. +func (t *freezerTable) Close() error { + t.lock.Lock() + defer t.lock.Unlock() + + var errs []error + doClose := func(f *os.File, sync bool, close bool) { + if sync && !t.readonly { + if err := f.Sync(); err != nil { + errs = append(errs, err) + } + } + if close { + if err := f.Close(); err != nil { + errs = append(errs, err) + } + } + } + // Trying to fsync a file opened in rdonly causes "Access denied" + // 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 + } + t.index = nil + t.meta = nil + t.head = nil + + if errs != nil { + return fmt.Errorf("%v", errs) + } + return nil +} + +// openFile assumes that the write-lock is held by the caller +func (t *freezerTable) openFile(num uint32, opener func(string) (*os.File, error)) (f *os.File, err error) { + var exist bool + if f, exist = t.files[num]; !exist { + var name string + if t.noCompression { + name = fmt.Sprintf("%s.%04d.rdat", t.name, num) + } else { + name = fmt.Sprintf("%s.%04d.cdat", t.name, num) + } + f, err = opener(filepath.Join(t.path, name)) + if err != nil { + return nil, err + } + t.files[num] = f + } + return f, err +} + +// releaseFile closes a file, and removes it from the open file cache. +// Assumes that the caller holds the write lock +func (t *freezerTable) releaseFile(num uint32) { + if f, exist := t.files[num]; exist { + delete(t.files, num) + f.Close() + } +} + +// releaseFilesAfter closes all open files with a higher number, and optionally also deletes the files +func (t *freezerTable) releaseFilesAfter(num uint32, remove bool) { + for fnum, f := range t.files { + if fnum > num { + delete(t.files, fnum) + f.Close() + if remove { + os.Remove(f.Name()) + } + } + } +} + +// releaseFilesBefore closes all open files with a lower number, and optionally also deletes the files +func (t *freezerTable) releaseFilesBefore(num uint32, remove bool) { + for fnum, f := range t.files { + if fnum < num { + delete(t.files, fnum) + f.Close() + if remove { + os.Remove(f.Name()) + } + } + } +} + +// getIndices returns the index entries for the given from-item, covering 'count' items. +// N.B: The actual number of returned indices for N items will always be N+1 (unless an +// error is returned). +// OBS: This method assumes that the caller has already verified (and/or trimmed) the range +// so that the items are within bounds. If this method is used to read out of bounds, +// it will return error. +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 { + return nil, err + } + var ( + indices []*indexEntry + offset int + ) + for i := from; i <= from+count; i++ { + index := new(indexEntry) + index.unmarshalBinary(buffer[offset:]) + offset += indexEntrySize + indices = append(indices, index) + } + if from == 0 { + // Special case if we're reading the first item in the freezer. We assume that + // the first item always start from zero(regarding the deletion, we + // only support deletion by files, so that the assumption is held). + // This means we can use the first item metadata to carry information about + // the 'global' offset, for the deletion-case + indices[0].offset = 0 + indices[0].filenum = indices[1].filenum + } + return indices, nil +} + +// Retrieve looks up the data offset of an item with the given number and retrieves +// the raw binary blob from the data file. +func (t *freezerTable) Retrieve(item uint64) ([]byte, error) { + items, err := t.RetrieveItems(item, 1, 0) + if err != nil { + return nil, err + } + return items[0], nil +} + +// RetrieveItems returns multiple items in sequence, starting from the index 'start'. +// It will return at most 'max' items, but will abort earlier to respect the +// 'maxBytes' argument. However, if the 'maxBytes' is smaller than the size of one +// item, it _will_ return one element and possibly overflow the maxBytes. +func (t *freezerTable) RetrieveItems(start, count, maxBytes uint64) ([][]byte, error) { + // First we read the 'raw' data, which might be compressed. + diskData, sizes, err := t.retrieveItems(start, count, maxBytes) + if err != nil { + return nil, err + } + var ( + output = make([][]byte, 0, count) + offset int // offset for reading + outputSize int // size of uncompressed data + ) + // Now slice up the data and decompress. + for i, diskSize := range sizes { + item := diskData[offset : offset+diskSize] + offset += diskSize + decompressedSize := diskSize + if !t.noCompression { + decompressedSize, _ = snappy.DecodedLen(item) + } + if i > 0 && maxBytes != 0 && uint64(outputSize+decompressedSize) > maxBytes { + break + } + if !t.noCompression { + data, err := snappy.Decode(nil, item) + if err != nil { + return nil, err + } + output = append(output, data) + } else { + output = append(output, item) + } + outputSize += decompressedSize + } + return output, nil +} + +// retrieveItems reads up to 'count' items from the table. It reads at least +// one item, but otherwise avoids reading more than maxBytes bytes. Freezer +// will ignore the size limitation and continuously allocate memory to store +// data if maxBytes is 0. It returns the (potentially compressed) data, and +// the sizes. +func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []int, error) { + t.lock.RLock() + defer t.lock.RUnlock() + + // Ensure the table and the item are accessible + if t.index == nil || t.head == nil || t.meta == nil { + return nil, nil, errClosed + } + var ( + items = t.items.Load() // the total items(head + 1) + hidden = t.itemHidden.Load() // the number of hidden items + ) + // Ensure the start is written, not deleted from the tail, and that the + // caller actually wants something + if items <= start || hidden > start || count == 0 { + return nil, nil, errOutOfBounds + } + if start+count > items { + count = items - start + } + var output []byte // Buffer to read data into + if maxBytes != 0 { + output = make([]byte, 0, maxBytes) + } else { + output = make([]byte, 0, 1024) // initial buffer cap + } + // readData is a helper method to read a single data item from disk. + readData := func(fileId, start uint32, length int) error { + output = grow(output, length) + dataFile, exist := t.files[fileId] + if !exist { + return fmt.Errorf("missing data file %d", fileId) + } + if _, err := dataFile.ReadAt(output[len(output)-length:], int64(start)); err != nil { + return fmt.Errorf("%w, fileid: %d, start: %d, length: %d", err, fileId, start, length) + } + return nil + } + // Read all the indexes in one go + indices, err := t.getIndices(start, count) + if err != nil { + return nil, nil, err + } + var ( + sizes []int // The sizes for each element + totalSize = 0 // The total size of all data read so far + readStart = indices[0].offset // Where, in the file, to start reading + unreadSize = 0 // The size of the as-yet-unread data + ) + + for i, firstIndex := range indices[:len(indices)-1] { + secondIndex := indices[i+1] + // Determine the size of the item. + offset1, offset2, _ := firstIndex.bounds(secondIndex) + size := int(offset2 - offset1) + // Crossing a file boundary? + if secondIndex.filenum != firstIndex.filenum { + // If we have unread data in the first file, we need to do that read now. + if unreadSize > 0 { + if err := readData(firstIndex.filenum, readStart, unreadSize); err != nil { + return nil, nil, err + } + unreadSize = 0 + } + readStart = 0 + } + if i > 0 && uint64(totalSize+size) > maxBytes && maxBytes != 0 { + // About to break out due to byte limit being exceeded. We don't + // read this last item, but we need to do the deferred reads now. + if unreadSize > 0 { + if err := readData(secondIndex.filenum, readStart, unreadSize); err != nil { + return nil, nil, err + } + } + break + } + // Defer the read for later + unreadSize += size + totalSize += size + sizes = append(sizes, size) + if i == len(indices)-2 || (uint64(totalSize) > maxBytes && maxBytes != 0) { + // Last item, need to do the read now + if err := readData(secondIndex.filenum, readStart, unreadSize); err != nil { + return nil, nil, err + } + break + } + } + + // Update metrics. + t.readMeter.Mark(int64(totalSize)) + return output, sizes, nil +} + +// has returns an indicator whether the specified number data is still accessible +// in the freezer table. +func (t *freezerTable) has(number uint64) bool { + return t.items.Load() > number && t.itemHidden.Load() <= number +} + +// size returns the total data size in the freezer table. +func (t *freezerTable) size() (uint64, error) { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.sizeNolock() +} + +// 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 + } + 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 +} + +// advanceHead should be called when the current head file would outgrow the file limits, +// and a new file must be opened. The caller of this method must hold the write-lock +// before calling this method. +func (t *freezerTable) advanceHead() error { + t.lock.Lock() + defer t.lock.Unlock() + + // We open the next file in truncated mode -- if this file already + // exists, we need to start over from scratch on it. + nextID := t.headId + 1 + newHead, err := t.openFile(nextID, openFreezerFileTruncated) + if err != nil { + return err + } + // Commit the contents of the old file to stable storage and + // tear it down. It will be re-opened in read-only mode. + if err := t.head.Sync(); err != nil { + return err + } + t.releaseFile(t.headId) + t.openFile(t.headId, openFreezerFileForReadOnly) + + // Swap out the current head. + t.head = newHead + t.headBytes = 0 + t.headId = nextID + return nil +} + +// Sync pushes any pending data from memory out to disk. This is an expensive +// operation, so use it with care. +func (t *freezerTable) Sync() error { + t.lock.Lock() + defer t.lock.Unlock() + if t.index == nil || t.head == nil || t.meta == nil { + return errClosed + } + var err error + trackError := func(e error) { + if e != nil && err == nil { + err = e + } + } + + trackError(t.index.Sync()) + trackError(t.meta.Sync()) + trackError(t.head.Sync()) + return err +} + +func (t *freezerTable) dumpIndexStdout(start, stop int64) { + t.dumpIndex(os.Stdout, start, stop) +} + +func (t *freezerTable) dumpIndexString(start, stop int64) string { + var out bytes.Buffer + out.WriteString("\n") + t.dumpIndex(&out, start, stop) + return out.String() +} + +func (t *freezerTable) dumpIndex(w io.Writer, start, stop int64) { + meta, err := readMetadata(t.meta) + if err != nil { + fmt.Fprintf(w, "Failed to decode freezer table %v\n", err) + return + } + fmt.Fprintf(w, "Version %d count %d, deleted %d, hidden %d\n", meta.Version, + t.items.Load(), t.itemOffset.Load(), t.itemHidden.Load()) + + buf := make([]byte, indexEntrySize) + + fmt.Fprintf(w, "| number | fileno | offset |\n") + fmt.Fprintf(w, "|--------|--------|--------|\n") + + for i := uint64(start); ; i++ { + if _, err := t.index.ReadAt(buf, int64((i+1)*indexEntrySize)); err != nil { + break + } + var entry indexEntry + entry.unmarshalBinary(buf) + fmt.Fprintf(w, "| %03d | %03d | %03d | \n", i, entry.filenum, entry.offset) + if stop > 0 && i >= uint64(stop) { + break + } + } + fmt.Fprintf(w, "|--------------------------|\n") +} diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go new file mode 100644 index 0000000..91b4943 --- /dev/null +++ b/core/rawdb/freezer_table_test.go @@ -0,0 +1,1369 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "bytes" + "encoding/binary" + "fmt" + "math/rand" + "os" + "path/filepath" + "reflect" + "testing" + "testing/quick" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/metrics" + "github.com/stretchr/testify/require" +) + +// TestFreezerBasics test initializing a freezertable from scratch, writing to the table, +// and reading it back. +func TestFreezerBasics(t *testing.T) { + t.Parallel() + // set cutoff at 50 bytes + f, err := newTable(os.TempDir(), + fmt.Sprintf("unittest-%d", rand.Uint64()), + metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + // Write 15 bytes 255 times, results in 85 files + writeChunks(t, f, 255, 15) + + //print(t, f, 0) + //print(t, f, 1) + //print(t, f, 2) + // + //db[0] = 000000000000000000000000000000 + //db[1] = 010101010101010101010101010101 + //db[2] = 020202020202020202020202020202 + + for y := 0; y < 255; y++ { + exp := getChunk(15, y) + got, err := f.Retrieve(uint64(y)) + if err != nil { + t.Fatalf("reading item %d: %v", y, err) + } + if !bytes.Equal(got, exp) { + t.Fatalf("test %d, got \n%x != \n%x", y, got, exp) + } + } + // Check that we cannot read too far + _, err = f.Retrieve(uint64(255)) + if err != errOutOfBounds { + t.Fatal(err) + } +} + +// TestFreezerBasicsClosing tests same as TestFreezerBasics, but also closes and reopens the freezer between +// every operation +func TestFreezerBasicsClosing(t *testing.T) { + t.Parallel() + // set cutoff at 50 bytes + var ( + fname = fmt.Sprintf("basics-close-%d", rand.Uint64()) + rm, wm, sg = metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + f *freezerTable + err error + ) + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + + // Write 15 bytes 255 times, results in 85 files. + // In-between writes, the table is closed and re-opened. + for x := 0; x < 255; x++ { + data := getChunk(15, x) + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(uint64(x), data)) + require.NoError(t, batch.commit()) + f.Close() + + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + } + defer f.Close() + + for y := 0; y < 255; y++ { + exp := getChunk(15, y) + got, err := f.Retrieve(uint64(y)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, exp) { + t.Fatalf("test %d, got \n%x != \n%x", y, got, exp) + } + f.Close() + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + } +} + +// TestFreezerRepairDanglingHead tests that we can recover if index entries are removed +func TestFreezerRepairDanglingHead(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64()) + + // Fill table + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // Write 15 bytes 255 times + writeChunks(t, f, 255, 15) + + // The last item should be there + if _, err = f.Retrieve(0xfe); err != nil { + t.Fatal(err) + } + f.Close() + } + + // open the index + idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644) + if err != nil { + t.Fatalf("Failed to open index file: %v", err) + } + // Remove 4 bytes + stat, err := idxFile.Stat() + if err != nil { + t.Fatalf("Failed to stat index file: %v", err) + } + idxFile.Truncate(stat.Size() - 4) + idxFile.Close() + + // Now open it again + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // The last item should be missing + if _, err = f.Retrieve(0xff); err == nil { + t.Errorf("Expected error for missing index entry") + } + // The one before should still be there + if _, err = f.Retrieve(0xfd); err != nil { + t.Fatalf("Expected no error, got %v", err) + } + } +} + +// TestFreezerRepairDanglingHeadLarge tests that we can recover if very many index entries are removed +func TestFreezerRepairDanglingHeadLarge(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64()) + + // Fill a table and close it + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // Write 15 bytes 255 times + writeChunks(t, f, 255, 15) + + // The last item should be there + if _, err = f.Retrieve(f.items.Load() - 1); err != nil { + t.Fatal(err) + } + f.Close() + } + + // open the index + idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644) + if err != nil { + t.Fatalf("Failed to open index file: %v", err) + } + // Remove everything but the first item, and leave data unaligned + // 0-indexEntry, 1-indexEntry, corrupt-indexEntry + idxFile.Truncate(2*indexEntrySize + indexEntrySize/2) + idxFile.Close() + + // Now open it again + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // The first item should be there + if _, err = f.Retrieve(0); err != nil { + t.Fatal(err) + } + // The second item should be missing + if _, err = f.Retrieve(1); err == nil { + t.Errorf("Expected error for missing index entry") + } + // We should now be able to store items again, from item = 1 + batch := f.newBatch() + for x := 1; x < 0xff; x++ { + require.NoError(t, batch.AppendRaw(uint64(x), getChunk(15, ^x))) + } + require.NoError(t, batch.commit()) + f.Close() + } + + // And if we open it, we should now be able to read all of them (new values) + { + f, _ := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + for y := 1; y < 255; y++ { + exp := getChunk(15, ^y) + got, err := f.Retrieve(uint64(y)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, exp) { + t.Fatalf("test %d, got \n%x != \n%x", y, got, exp) + } + } + } +} + +// TestSnappyDetection tests that we fail to open a snappy database and vice versa +func TestSnappyDetection(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("snappytest-%d", rand.Uint64()) + + // Open with snappy + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // Write 15 bytes 255 times + writeChunks(t, f, 255, 15) + f.Close() + } + + // Open without snappy + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, false, false) + if err != nil { + t.Fatal(err) + } + if _, err = f.Retrieve(0); err == nil { + f.Close() + t.Fatalf("expected empty table") + } + } + + // Open with snappy + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // There should be 255 items + if _, err = f.Retrieve(0xfe); err != nil { + f.Close() + t.Fatalf("expected no error, got %v", err) + } + } +} + +func assertFileSize(f string, size int64) error { + stat, err := os.Stat(f) + if err != nil { + return err + } + if stat.Size() != size { + return fmt.Errorf("error, expected size %d, got %d", size, stat.Size()) + } + return nil +} + +// TestFreezerRepairDanglingIndex checks that if the index has more entries than there are data, +// the index is repaired +func TestFreezerRepairDanglingIndex(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("dangling_indextest-%d", rand.Uint64()) + + // Fill a table and close it + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // Write 15 bytes 9 times : 150 bytes + writeChunks(t, f, 9, 15) + + // The last item should be there + if _, err = f.Retrieve(f.items.Load() - 1); err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + // File sizes should be 45, 45, 45 : items[3, 3, 3) + } + + // Crop third file + fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.0002.rdat", fname)) + // Truncate third file: 45 ,45, 20 + { + if err := assertFileSize(fileToCrop, 45); err != nil { + t.Fatal(err) + } + file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644) + if err != nil { + t.Fatal(err) + } + file.Truncate(20) + file.Close() + } + + // Open db it again + // It should restore the file(s) to + // 45, 45, 15 + // with 3+3+1 items + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + defer f.Close() + if f.items.Load() != 7 { + t.Fatalf("expected %d items, got %d", 7, f.items.Load()) + } + if err := assertFileSize(fileToCrop, 15); err != nil { + t.Fatal(err) + } + } +} + +func TestFreezerTruncate(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("truncation-%d", rand.Uint64()) + + // Fill table + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // Write 15 bytes 30 times + writeChunks(t, f, 30, 15) + + // The last item should be there + if _, err = f.Retrieve(f.items.Load() - 1); err != nil { + t.Fatal(err) + } + f.Close() + } + + // Reopen, truncate + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + defer f.Close() + f.truncateHead(10) // 150 bytes + if f.items.Load() != 10 { + t.Fatalf("expected %d items, got %d", 10, f.items.Load()) + } + // 45, 45, 45, 15 -- bytes should be 15 + if f.headBytes != 15 { + t.Fatalf("expected %d bytes, got %d", 15, f.headBytes) + } + } +} + +// TestFreezerRepairFirstFile tests a head file with the very first item only half-written. +// That will rewind the index, and _should_ truncate the head file +func TestFreezerRepairFirstFile(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("truncationfirst-%d", rand.Uint64()) + + // Fill table + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // Write 80 bytes, splitting out into two files + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(0, getChunk(40, 0xFF))) + require.NoError(t, batch.AppendRaw(1, getChunk(40, 0xEE))) + require.NoError(t, batch.commit()) + + // The last item should be there + if _, err = f.Retrieve(1); err != nil { + t.Fatal(err) + } + f.Close() + } + + // Truncate the file in half + fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.0001.rdat", fname)) + { + if err := assertFileSize(fileToCrop, 40); err != nil { + t.Fatal(err) + } + file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644) + if err != nil { + t.Fatal(err) + } + file.Truncate(20) + file.Close() + } + + // Reopen + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + if f.items.Load() != 1 { + f.Close() + t.Fatalf("expected %d items, got %d", 0, f.items.Load()) + } + + // Write 40 bytes + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(1, getChunk(40, 0xDD))) + require.NoError(t, batch.commit()) + + f.Close() + + // Should have been truncated down to zero and then 40 written + if err := assertFileSize(fileToCrop, 40); err != nil { + t.Fatal(err) + } + } +} + +// TestFreezerReadAndTruncate tests: +// - we have a table open +// - do some reads, so files are open in readonly +// - truncate so those files are 'removed' +// - check that we did not keep the rdonly file descriptors +func TestFreezerReadAndTruncate(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("read_truncate-%d", rand.Uint64()) + + // Fill table + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // Write 15 bytes 30 times + writeChunks(t, f, 30, 15) + + // The last item should be there + if _, err = f.Retrieve(f.items.Load() - 1); err != nil { + t.Fatal(err) + } + f.Close() + } + + // Reopen and read all files + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + if f.items.Load() != 30 { + f.Close() + t.Fatalf("expected %d items, got %d", 0, f.items.Load()) + } + for y := byte(0); y < 30; y++ { + f.Retrieve(uint64(y)) + } + + // Now, truncate back to zero + f.truncateHead(0) + + // Write the data again + batch := f.newBatch() + for x := 0; x < 30; x++ { + require.NoError(t, batch.AppendRaw(uint64(x), getChunk(15, ^x))) + } + require.NoError(t, batch.commit()) + f.Close() + } +} + +func TestFreezerOffset(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("offset-%d", rand.Uint64()) + + // Fill table + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) + if err != nil { + t.Fatal(err) + } + + // Write 6 x 20 bytes, splitting out into three files + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(0, getChunk(20, 0xFF))) + require.NoError(t, batch.AppendRaw(1, getChunk(20, 0xEE))) + + require.NoError(t, batch.AppendRaw(2, getChunk(20, 0xdd))) + require.NoError(t, batch.AppendRaw(3, getChunk(20, 0xcc))) + + require.NoError(t, batch.AppendRaw(4, getChunk(20, 0xbb))) + require.NoError(t, batch.AppendRaw(5, getChunk(20, 0xaa))) + require.NoError(t, batch.commit()) + + t.Log(f.dumpIndexString(0, 100)) + f.Close() + } + + // Now crop it. + { + // delete files 0 and 1 + for i := 0; i < 2; i++ { + p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.%04d.rdat", fname, i)) + if err := os.Remove(p); err != nil { + t.Fatal(err) + } + } + // Read the index file + p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname)) + indexFile, err := os.OpenFile(p, os.O_RDWR, 0644) + if err != nil { + t.Fatal(err) + } + indexBuf := make([]byte, 7*indexEntrySize) + indexFile.Read(indexBuf) + + // Update the index file, so that we store + // [ file = 2, offset = 4 ] at index zero + + zeroIndex := indexEntry{ + filenum: uint32(2), // First file is 2 + offset: uint32(4), // We have removed four items + } + buf := zeroIndex.append(nil) + + // Overwrite index zero + copy(indexBuf, buf) + + // Remove the four next indices by overwriting + copy(indexBuf[indexEntrySize:], indexBuf[indexEntrySize*5:]) + indexFile.WriteAt(indexBuf, 0) + + // Need to truncate the moved index items + indexFile.Truncate(indexEntrySize * (1 + 2)) + indexFile.Close() + } + + // Now open again + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) + if err != nil { + t.Fatal(err) + } + defer f.Close() + t.Log(f.dumpIndexString(0, 100)) + + // It should allow writing item 6. + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(6, getChunk(20, 0x99))) + require.NoError(t, batch.commit()) + + checkRetrieveError(t, f, map[uint64]error{ + 0: errOutOfBounds, + 1: errOutOfBounds, + 2: errOutOfBounds, + 3: errOutOfBounds, + }) + checkRetrieve(t, f, map[uint64][]byte{ + 4: getChunk(20, 0xbb), + 5: getChunk(20, 0xaa), + 6: getChunk(20, 0x99), + }) + } + + // Edit the index again, with a much larger initial offset of 1M. + { + // Read the index file + p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname)) + indexFile, err := os.OpenFile(p, os.O_RDWR, 0644) + if err != nil { + t.Fatal(err) + } + indexBuf := make([]byte, 3*indexEntrySize) + indexFile.Read(indexBuf) + + // Update the index file, so that we store + // [ file = 2, offset = 1M ] at index zero + + zeroIndex := indexEntry{ + offset: uint32(1000000), // We have removed 1M items + filenum: uint32(2), // First file is 2 + } + buf := zeroIndex.append(nil) + + // Overwrite index zero + copy(indexBuf, buf) + indexFile.WriteAt(indexBuf, 0) + indexFile.Close() + } + + // Check that existing items have been moved to index 1M. + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) + if err != nil { + t.Fatal(err) + } + defer f.Close() + t.Log(f.dumpIndexString(0, 100)) + + checkRetrieveError(t, f, map[uint64]error{ + 0: errOutOfBounds, + 1: errOutOfBounds, + 2: errOutOfBounds, + 3: errOutOfBounds, + 999999: errOutOfBounds, + }) + checkRetrieve(t, f, map[uint64][]byte{ + 1000000: getChunk(20, 0xbb), + 1000001: getChunk(20, 0xaa), + }) + } +} + +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() + fname := fmt.Sprintf("truncate-tail-%d", rand.Uint64()) + + // Fill table + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) + if err != nil { + t.Fatal(err) + } + + // Write 7 x 20 bytes, splitting out into four files + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(0, getChunk(20, 0xFF))) + require.NoError(t, batch.AppendRaw(1, getChunk(20, 0xEE))) + require.NoError(t, batch.AppendRaw(2, getChunk(20, 0xdd))) + require.NoError(t, batch.AppendRaw(3, getChunk(20, 0xcc))) + require.NoError(t, batch.AppendRaw(4, getChunk(20, 0xbb))) + require.NoError(t, batch.AppendRaw(5, getChunk(20, 0xaa))) + require.NoError(t, batch.AppendRaw(6, getChunk(20, 0x11))) + require.NoError(t, batch.commit()) + + // nothing to do, all the items should still be there. + f.truncateTail(0) + fmt.Println(f.dumpIndexString(0, 1000)) + checkRetrieve(t, f, map[uint64][]byte{ + 0: getChunk(20, 0xFF), + 1: getChunk(20, 0xEE), + 2: getChunk(20, 0xdd), + 3: getChunk(20, 0xcc), + 4: getChunk(20, 0xbb), + 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) + fmt.Println(f.dumpIndexString(0, 1000)) + checkRetrieveError(t, f, map[uint64]error{ + 0: errOutOfBounds, + }) + checkRetrieve(t, f, map[uint64][]byte{ + 1: getChunk(20, 0xEE), + 2: getChunk(20, 0xdd), + 3: getChunk(20, 0xcc), + 4: getChunk(20, 0xbb), + 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() + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) + if err != nil { + t.Fatal(err) + } + checkRetrieveError(t, f, map[uint64]error{ + 0: errOutOfBounds, + }) + checkRetrieve(t, f, map[uint64][]byte{ + 1: getChunk(20, 0xEE), + 2: getChunk(20, 0xdd), + 3: getChunk(20, 0xcc), + 4: getChunk(20, 0xbb), + 5: getChunk(20, 0xaa), + 6: getChunk(20, 0x11), + }) + + // truncate two elements( item 0, item 1 ), the file 0 should be deleted + f.truncateTail(2) + checkRetrieveError(t, f, map[uint64]error{ + 0: errOutOfBounds, + 1: errOutOfBounds, + }) + checkRetrieve(t, f, map[uint64][]byte{ + 2: getChunk(20, 0xdd), + 3: getChunk(20, 0xcc), + 4: getChunk(20, 0xbb), + 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() + f, err = newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + checkRetrieveError(t, f, map[uint64]error{ + 0: errOutOfBounds, + 1: errOutOfBounds, + }) + checkRetrieve(t, f, map[uint64][]byte{ + 2: getChunk(20, 0xdd), + 3: getChunk(20, 0xcc), + 4: getChunk(20, 0xbb), + 5: getChunk(20, 0xaa), + 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{ + 0: errOutOfBounds, + 1: errOutOfBounds, + 2: errOutOfBounds, + 3: errOutOfBounds, + 4: errOutOfBounds, + 5: errOutOfBounds, + 6: errOutOfBounds, + }) + expected = 12 + assertTableSize(t, f, expected) +} + +func TestTruncateHead(t *testing.T) { + t.Parallel() + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("truncate-head-blow-tail-%d", rand.Uint64()) + + // Fill table + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) + if err != nil { + t.Fatal(err) + } + + // Write 7 x 20 bytes, splitting out into four files + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(0, getChunk(20, 0xFF))) + require.NoError(t, batch.AppendRaw(1, getChunk(20, 0xEE))) + require.NoError(t, batch.AppendRaw(2, getChunk(20, 0xdd))) + require.NoError(t, batch.AppendRaw(3, getChunk(20, 0xcc))) + require.NoError(t, batch.AppendRaw(4, getChunk(20, 0xbb))) + require.NoError(t, batch.AppendRaw(5, getChunk(20, 0xaa))) + require.NoError(t, batch.AppendRaw(6, getChunk(20, 0x11))) + require.NoError(t, batch.commit()) + + f.truncateTail(4) // Tail = 4 + + // NewHead is required to be 3, the entire table should be truncated + f.truncateHead(4) + checkRetrieveError(t, f, map[uint64]error{ + 0: errOutOfBounds, // Deleted by tail + 1: errOutOfBounds, // Deleted by tail + 2: errOutOfBounds, // Deleted by tail + 3: errOutOfBounds, // Deleted by tail + 4: errOutOfBounds, // Deleted by Head + 5: errOutOfBounds, // Deleted by Head + 6: errOutOfBounds, // Deleted by Head + }) + + // Append new items + batch = f.newBatch() + require.NoError(t, batch.AppendRaw(4, getChunk(20, 0xbb))) + require.NoError(t, batch.AppendRaw(5, getChunk(20, 0xaa))) + require.NoError(t, batch.AppendRaw(6, getChunk(20, 0x11))) + require.NoError(t, batch.commit()) + + checkRetrieve(t, f, map[uint64][]byte{ + 4: getChunk(20, 0xbb), + 5: getChunk(20, 0xaa), + 6: getChunk(20, 0x11), + }) +} + +func checkRetrieve(t *testing.T, f *freezerTable, items map[uint64][]byte) { + t.Helper() + + for item, wantBytes := range items { + value, err := f.Retrieve(item) + if err != nil { + t.Fatalf("can't get expected item %d: %v", item, err) + } + if !bytes.Equal(value, wantBytes) { + t.Fatalf("item %d has wrong value %x (want %x)", item, value, wantBytes) + } + } +} + +func checkRetrieveError(t *testing.T, f *freezerTable, items map[uint64]error) { + t.Helper() + + for item, wantError := range items { + value, err := f.Retrieve(item) + if err == nil { + t.Fatalf("unexpected value %x for item %d, want error %v", item, value, wantError) + } + if err != wantError { + t.Fatalf("wrong error for item %d: %v", item, err) + } + } +} + +// Gets a chunk of data, filled with 'b' +func getChunk(size int, b int) []byte { + data := make([]byte, size) + for i := range data { + data[i] = byte(b) + } + return data +} + +// TODO (?) +// - test that if we remove several head-files, as well as data last data-file, +// the index is truncated accordingly +// Right now, the freezer would fail on these conditions: +// 1. have data files d0, d1, d2, d3 +// 2. remove d2,d3 +// +// However, all 'normal' failure modes arising due to failing to sync() or save a file +// should be handled already, and the case described above can only (?) happen if an +// external process/user deletes files from the filesystem. + +func writeChunks(t *testing.T, ft *freezerTable, n int, length int) { + t.Helper() + + batch := ft.newBatch() + for i := 0; i < n; i++ { + if err := batch.AppendRaw(uint64(i), getChunk(length, i)); err != nil { + t.Fatalf("AppendRaw(%d, ...) returned error: %v", i, err) + } + } + if err := batch.commit(); err != nil { + t.Fatalf("Commit returned error: %v", err) + } +} + +// TestSequentialRead does some basic tests on the RetrieveItems. +func TestSequentialRead(t *testing.T) { + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("batchread-%d", rand.Uint64()) + { // Fill table + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + // Write 15 bytes 30 times + writeChunks(t, f, 30, 15) + f.dumpIndexStdout(0, 30) + f.Close() + } + { // Open it, iterate, verify iteration + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 50, true, false) + if err != nil { + t.Fatal(err) + } + items, err := f.RetrieveItems(0, 10000, 100000) + if err != nil { + t.Fatal(err) + } + if have, want := len(items), 30; have != want { + t.Fatalf("want %d items, have %d ", want, have) + } + for i, have := range items { + want := getChunk(15, i) + if !bytes.Equal(want, have) { + t.Fatalf("data corruption: have\n%x\n, want \n%x\n", have, want) + } + } + f.Close() + } + { // Open it, iterate, verify byte limit. The byte limit is less than item + // size, so each lookup should only return one item + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 40, true, false) + if err != nil { + t.Fatal(err) + } + items, err := f.RetrieveItems(0, 10000, 10) + if err != nil { + t.Fatal(err) + } + if have, want := len(items), 1; have != want { + t.Fatalf("want %d items, have %d ", want, have) + } + for i, have := range items { + want := getChunk(15, i) + if !bytes.Equal(want, have) { + t.Fatalf("data corruption: have\n%x\n, want \n%x\n", have, want) + } + } + f.Close() + } +} + +// TestSequentialReadByteLimit does some more advanced tests on batch reads. +// These tests check that when the byte limit hits, we correctly abort in time, +// but also properly do all the deferred reads for the previous data, regardless +// of whether the data crosses a file boundary or not. +func TestSequentialReadByteLimit(t *testing.T) { + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("batchread-2-%d", rand.Uint64()) + { // Fill table + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 100, true, false) + if err != nil { + t.Fatal(err) + } + // Write 10 bytes 30 times, + // Splitting it at every 100 bytes (10 items) + writeChunks(t, f, 30, 10) + f.Close() + } + for i, tc := range []struct { + items uint64 + limit uint64 + want int + }{ + {9, 89, 8}, + {10, 99, 9}, + {11, 109, 10}, + {100, 89, 8}, + {100, 99, 9}, + {100, 109, 10}, + } { + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 100, true, false) + if err != nil { + t.Fatal(err) + } + items, err := f.RetrieveItems(0, tc.items, tc.limit) + if err != nil { + t.Fatal(err) + } + if have, want := len(items), tc.want; have != want { + t.Fatalf("test %d: want %d items, have %d ", i, want, have) + } + for ii, have := range items { + want := getChunk(10, ii) + if !bytes.Equal(want, have) { + t.Fatalf("test %d: data corruption item %d: have\n%x\n, want \n%x\n", i, ii, have, want) + } + } + f.Close() + } + } +} + +// TestSequentialReadNoByteLimit tests the batch-read if maxBytes is not specified. +// Freezer should return the requested items regardless the size limitation. +func TestSequentialReadNoByteLimit(t *testing.T) { + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("batchread-3-%d", rand.Uint64()) + { // Fill table + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 100, true, false) + if err != nil { + t.Fatal(err) + } + // Write 10 bytes 30 times, + // Splitting it at every 100 bytes (10 items) + writeChunks(t, f, 30, 10) + f.Close() + } + for i, tc := range []struct { + items uint64 + want int + }{ + {1, 1}, + {30, 30}, + {31, 30}, + } { + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 100, true, false) + if err != nil { + t.Fatal(err) + } + items, err := f.RetrieveItems(0, tc.items, 0) + if err != nil { + t.Fatal(err) + } + if have, want := len(items), tc.want; have != want { + t.Fatalf("test %d: want %d items, have %d ", i, want, have) + } + for ii, have := range items { + want := getChunk(10, ii) + if !bytes.Equal(want, have) { + t.Fatalf("test %d: data corruption item %d: have\n%x\n, want \n%x\n", i, ii, have, want) + } + } + f.Close() + } + } +} + +func TestFreezerReadonly(t *testing.T) { + tmpdir := os.TempDir() + // Case 1: Check it fails on non-existent file. + _, err := newTable(tmpdir, + fmt.Sprintf("readonlytest-%d", rand.Uint64()), + metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, true) + if err == nil { + t.Fatal("readonly table instantiation should fail for non-existent table") + } + + // Case 2: Check that it fails on invalid index length. + fname := fmt.Sprintf("readonlytest-%d", rand.Uint64()) + idxFile, err := openFreezerFileForAppend(filepath.Join(tmpdir, fmt.Sprintf("%s.ridx", fname))) + if err != nil { + t.Errorf("Failed to open index file: %v\n", err) + } + // size should not be a multiple of indexEntrySize. + idxFile.Write(make([]byte, 17)) + idxFile.Close() + _, err = newTable(tmpdir, fname, + metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, true) + if err == nil { + t.Errorf("readonly table instantiation should fail for invalid index size") + } + + // Case 3: Open table non-readonly table to write some data. + // Then corrupt the head file and make sure opening the table + // again in readonly triggers an error. + fname = fmt.Sprintf("readonlytest-%d", rand.Uint64()) + f, err := newTable(tmpdir, fname, + metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) + if err != nil { + t.Fatalf("failed to instantiate table: %v", err) + } + writeChunks(t, f, 8, 32) + // Corrupt table file + if _, err := f.head.Write([]byte{1, 1}); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + _, err = newTable(tmpdir, fname, + metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, true) + if err == nil { + t.Errorf("readonly table instantiation should fail for corrupt table file") + } + + // Case 4: Write some data to a table and later re-open it as readonly. + // Should be successful. + fname = fmt.Sprintf("readonlytest-%d", rand.Uint64()) + f, err = newTable(tmpdir, fname, + metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) + if err != nil { + t.Fatalf("failed to instantiate table: %v\n", err) + } + writeChunks(t, f, 32, 128) + if err := f.Close(); err != nil { + t.Fatal(err) + } + f, err = newTable(tmpdir, fname, + metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, true) + if err != nil { + t.Fatal(err) + } + v, err := f.Retrieve(10) + if err != nil { + t.Fatal(err) + } + exp := getChunk(128, 10) + if !bytes.Equal(v, exp) { + t.Errorf("retrieved value is incorrect") + } + + // Case 5: Now write some data via a batch. + // This should fail either during AppendRaw or Commit + batch := f.newBatch() + writeErr := batch.AppendRaw(32, make([]byte, 1)) + if writeErr == nil { + writeErr = batch.commit() + } + if writeErr == nil { + t.Fatalf("Writing to readonly table should fail") + } +} + +// randTest performs random freezer table operations. +// Instances of this test are created by Generate. +type randTest []randTestStep + +type randTestStep struct { + op int + items []uint64 // for append and retrieve + blobs [][]byte // for append + target uint64 // for truncate(head/tail) + err error // for debugging +} + +const ( + opReload = iota + opAppend + opRetrieve + opTruncateHead + opTruncateHeadAll + opTruncateTail + opTruncateTailAll + opCheckAll + opMax // boundary value, not an actual op +) + +func getVals(first uint64, n int) [][]byte { + var ret [][]byte + for i := 0; i < n; i++ { + val := make([]byte, 8) + binary.BigEndian.PutUint64(val, first+uint64(i)) + ret = append(ret, val) + } + return ret +} + +func (randTest) Generate(r *rand.Rand, size int) reflect.Value { + var ( + deleted uint64 // The number of deleted items from tail + items []uint64 // The index of entries in table + + // getItems retrieves the indexes for items in table. + getItems = func(n int) []uint64 { + length := len(items) + if length == 0 { + return nil + } + var ret []uint64 + index := rand.Intn(length) + for i := index; len(ret) < n && i < length; i++ { + ret = append(ret, items[i]) + } + return ret + } + + // addItems appends the given length items into the table. + addItems = func(n int) []uint64 { + var first = deleted + if len(items) != 0 { + first = items[len(items)-1] + 1 + } + var ret []uint64 + for i := 0; i < n; i++ { + ret = append(ret, first+uint64(i)) + } + items = append(items, ret...) + return ret + } + ) + + var steps randTest + for i := 0; i < size; i++ { + step := randTestStep{op: r.Intn(opMax)} + switch step.op { + case opReload, opCheckAll: + case opAppend: + num := r.Intn(3) + step.items = addItems(num) + if len(step.items) == 0 { + step.blobs = nil + } else { + step.blobs = getVals(step.items[0], num) + } + case opRetrieve: + step.items = getItems(r.Intn(3)) + case opTruncateHead: + if len(items) == 0 { + step.target = deleted + } else { + index := r.Intn(len(items)) + items = items[:index] + step.target = deleted + uint64(index) + } + case opTruncateHeadAll: + step.target = deleted + items = items[:0] + case opTruncateTail: + if len(items) == 0 { + step.target = deleted + } else { + index := r.Intn(len(items)) + items = items[index:] + deleted += uint64(index) + step.target = deleted + } + case opTruncateTailAll: + step.target = deleted + uint64(len(items)) + items = items[:0] + deleted = step.target + } + steps = append(steps, step) + } + return reflect.ValueOf(steps) +} + +func runRandTest(rt randTest) bool { + fname := fmt.Sprintf("randtest-%d", rand.Uint64()) + f, err := newTable(os.TempDir(), fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) + if err != nil { + panic("failed to initialize table") + } + var values [][]byte + for i, step := range rt { + switch step.op { + case opReload: + f.Close() + f, err = newTable(os.TempDir(), fname, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true, false) + if err != nil { + rt[i].err = fmt.Errorf("failed to reload table %v", err) + } + case opCheckAll: + tail := f.itemHidden.Load() + head := f.items.Load() + + if tail == head { + continue + } + got, err := f.RetrieveItems(f.itemHidden.Load(), head-tail, 100000) + if err != nil { + rt[i].err = err + } else { + if !reflect.DeepEqual(got, values) { + rt[i].err = fmt.Errorf("mismatch on retrieved values %v %v", got, values) + } + } + + case opAppend: + batch := f.newBatch() + for i := 0; i < len(step.items); i++ { + batch.AppendRaw(step.items[i], step.blobs[i]) + } + batch.commit() + values = append(values, step.blobs...) + + case opRetrieve: + var blobs [][]byte + if len(step.items) == 0 { + continue + } + tail := f.itemHidden.Load() + for i := 0; i < len(step.items); i++ { + blobs = append(blobs, values[step.items[i]-tail]) + } + got, err := f.RetrieveItems(step.items[0], uint64(len(step.items)), 100000) + if err != nil { + rt[i].err = err + } else { + if !reflect.DeepEqual(got, blobs) { + rt[i].err = fmt.Errorf("mismatch on retrieved values %v %v %v", got, blobs, step.items) + } + } + + case opTruncateHead: + f.truncateHead(step.target) + + length := f.items.Load() - f.itemHidden.Load() + values = values[:length] + + case opTruncateHeadAll: + f.truncateHead(step.target) + values = nil + + case opTruncateTail: + prev := f.itemHidden.Load() + f.truncateTail(step.target) + + truncated := f.itemHidden.Load() - prev + values = values[truncated:] + + case opTruncateTailAll: + f.truncateTail(step.target) + values = nil + } + // Abort the test on error. + if rt[i].err != nil { + return false + } + } + f.Close() + return true +} + +func TestRandom(t *testing.T) { + if err := quick.Check(runRandTest, nil); err != nil { + if cerr, ok := err.(*quick.CheckError); ok { + t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In)) + } + t.Fatal(err) + } +} diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go new file mode 100644 index 0000000..72d1417 --- /dev/null +++ b/core/rawdb/freezer_test.go @@ -0,0 +1,502 @@ +// 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 rawdb + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "math/rand" + "os" + "path/filepath" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/core/rawdb/ancienttest" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" +) + +var freezerTestTableDef = map[string]bool{"test": true} + +func TestFreezerModify(t *testing.T) { + t.Parallel() + + // Create test data. + var valuesRaw [][]byte + var valuesRLP []*big.Int + for x := 0; x < 100; x++ { + v := getChunk(256, x) + valuesRaw = append(valuesRaw, v) + iv := big.NewInt(int64(x)) + iv = iv.Exp(iv, iv, nil) + valuesRLP = append(valuesRLP, iv) + } + + tables := map[string]bool{"raw": true, "rlp": false} + f, _ := newFreezerForTesting(t, tables) + defer f.Close() + + // Commit test data. + _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := range valuesRaw { + if err := op.AppendRaw("raw", uint64(i), valuesRaw[i]); err != nil { + return err + } + if err := op.Append("rlp", uint64(i), valuesRLP[i]); err != nil { + return err + } + } + return nil + }) + if err != nil { + t.Fatal("ModifyAncients failed:", err) + } + + // Dump indexes. + for _, table := range f.tables { + t.Log(table.name, "index:", table.dumpIndexString(0, int64(len(valuesRaw)))) + } + + // Read back test data. + checkAncientCount(t, f, "raw", uint64(len(valuesRaw))) + checkAncientCount(t, f, "rlp", uint64(len(valuesRLP))) + for i := range valuesRaw { + v, _ := f.Ancient("raw", uint64(i)) + if !bytes.Equal(v, valuesRaw[i]) { + t.Fatalf("wrong raw value at %d: %x", i, v) + } + ivEnc, _ := f.Ancient("rlp", uint64(i)) + want, _ := rlp.EncodeToBytes(valuesRLP[i]) + if !bytes.Equal(ivEnc, want) { + t.Fatalf("wrong RLP value at %d: %x", i, ivEnc) + } + } +} + +// This checks that ModifyAncients rolls back freezer updates +// when the function passed to it returns an error. +func TestFreezerModifyRollback(t *testing.T) { + t.Parallel() + + f, dir := newFreezerForTesting(t, freezerTestTableDef) + + theError := errors.New("oops") + _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + // Append three items. This creates two files immediately, + // because the table size limit of the test freezer is 2048. + require.NoError(t, op.AppendRaw("test", 0, make([]byte, 2048))) + require.NoError(t, op.AppendRaw("test", 1, make([]byte, 2048))) + require.NoError(t, op.AppendRaw("test", 2, make([]byte, 2048))) + return theError + }) + if err != theError { + t.Errorf("ModifyAncients returned wrong error %q", err) + } + checkAncientCount(t, f, "test", 0) + f.Close() + + // Reopen and check that the rolled-back data doesn't reappear. + tables := map[string]bool{"test": true} + f2, err := NewFreezer(dir, "", false, 2049, tables) + if err != nil { + t.Fatalf("can't reopen freezer after failed ModifyAncients: %v", err) + } + defer f2.Close() + checkAncientCount(t, f2, "test", 0) +} + +// This test runs ModifyAncients and Ancient concurrently with each other. +func TestFreezerConcurrentModifyRetrieve(t *testing.T) { + t.Parallel() + + f, _ := newFreezerForTesting(t, freezerTestTableDef) + defer f.Close() + + var ( + numReaders = 5 + writeBatchSize = uint64(50) + written = make(chan uint64, numReaders*6) + wg sync.WaitGroup + ) + wg.Add(numReaders + 1) + + // Launch the writer. It appends 10000 items in batches. + go func() { + defer wg.Done() + defer close(written) + for item := uint64(0); item < 10000; item += writeBatchSize { + _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := uint64(0); i < writeBatchSize; i++ { + item := item + i + value := getChunk(32, int(item)) + if err := op.AppendRaw("test", item, value); err != nil { + return err + } + } + return nil + }) + if err != nil { + panic(err) + } + for i := 0; i < numReaders; i++ { + written <- item + writeBatchSize + } + } + }() + + // Launch the readers. They read random items from the freezer up to the + // current frozen item count. + for i := 0; i < numReaders; i++ { + go func() { + defer wg.Done() + for frozen := range written { + for rc := 0; rc < 80; rc++ { + num := uint64(rand.Intn(int(frozen))) + value, err := f.Ancient("test", num) + if err != nil { + panic(fmt.Errorf("error reading %d (frozen %d): %v", num, frozen, err)) + } + if !bytes.Equal(value, getChunk(32, int(num))) { + panic(fmt.Errorf("wrong value at %d", num)) + } + } + } + }() + } + + wg.Wait() +} + +// This test runs ModifyAncients and TruncateHead concurrently with each other. +func TestFreezerConcurrentModifyTruncate(t *testing.T) { + f, _ := newFreezerForTesting(t, freezerTestTableDef) + defer f.Close() + + var item = make([]byte, 256) + + for i := 0; i < 10; i++ { + // First reset and write 100 items. + if _, err := f.TruncateHead(0); err != nil { + t.Fatal("truncate failed:", err) + } + _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := uint64(0); i < 100; i++ { + if err := op.AppendRaw("test", i, item); err != nil { + return err + } + } + return nil + }) + if err != nil { + t.Fatal("modify failed:", err) + } + checkAncientCount(t, f, "test", 100) + + // Now append 100 more items and truncate concurrently. + var ( + wg sync.WaitGroup + truncateErr error + modifyErr error + ) + wg.Add(3) + go func() { + _, modifyErr = f.ModifyAncients(func(op ethdb.AncientWriteOp) error { + for i := uint64(100); i < 200; i++ { + if err := op.AppendRaw("test", i, item); err != nil { + return err + } + } + return nil + }) + wg.Done() + }() + go func() { + _, truncateErr = f.TruncateHead(10) + wg.Done() + }() + go func() { + f.AncientSize("test") + wg.Done() + }() + wg.Wait() + + // Now check the outcome. If the truncate operation went through first, the append + // fails, otherwise it succeeds. In either case, the freezer should be positioned + // at 10 after both operations are done. + if truncateErr != nil { + t.Fatal("concurrent truncate failed:", err) + } + if !(errors.Is(modifyErr, nil) || errors.Is(modifyErr, errOutOrderInsertion)) { + t.Fatal("wrong error from concurrent modify:", modifyErr) + } + checkAncientCount(t, f, "test", 10) + } +} + +func TestFreezerReadonlyValidate(t *testing.T) { + tables := map[string]bool{"a": true, "b": true} + dir := t.TempDir() + // Open non-readonly freezer and fill individual tables + // with different amount of data. + f, err := NewFreezer(dir, "", false, 2049, tables) + if err != nil { + t.Fatal("can't open freezer", err) + } + var item = make([]byte, 1024) + aBatch := f.tables["a"].newBatch() + require.NoError(t, aBatch.AppendRaw(0, item)) + require.NoError(t, aBatch.AppendRaw(1, item)) + require.NoError(t, aBatch.AppendRaw(2, item)) + require.NoError(t, aBatch.commit()) + bBatch := f.tables["b"].newBatch() + require.NoError(t, bBatch.AppendRaw(0, item)) + require.NoError(t, bBatch.commit()) + if f.tables["a"].items.Load() != 3 { + t.Fatalf("unexpected number of items in table") + } + if f.tables["b"].items.Load() != 1 { + t.Fatalf("unexpected number of items in table") + } + require.NoError(t, f.Close()) + + // Re-opening as readonly should fail when validating + // table lengths. + _, err = NewFreezer(dir, "", true, 2049, tables) + if err == nil { + t.Fatal("readonly freezer should fail with differing table lengths") + } +} + +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() + + dir := t.TempDir() + // note: using low max table size here to ensure the tests actually + // switch between multiple files. + f, err := NewFreezer(dir, "", false, 2049, tables) + if err != nil { + t.Fatal("can't open freezer", err) + } + return f, dir +} + +// checkAncientCount verifies that the freezer contains n items. +func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) { + t.Helper() + + if frozen, _ := f.Ancients(); frozen != n { + t.Fatalf("Ancients() returned %d, want %d", frozen, n) + } + + // Check at index n-1. + if n > 0 { + index := n - 1 + if ok, _ := f.HasAncient(kind, index); !ok { + t.Errorf("HasAncient(%q, %d) returned false unexpectedly", kind, index) + } + if _, err := f.Ancient(kind, index); err != nil { + t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err) + } + } + + // Check at index n. + index := n + if ok, _ := f.HasAncient(kind, index); ok { + t.Errorf("HasAncient(%q, %d) returned true unexpectedly", kind, index) + } + if _, err := f.Ancient(kind, index); err == nil { + t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index) + } else if err != errOutOfBounds { + t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err) + } +} + +func TestRenameWindows(t *testing.T) { + var ( + fname = "file.bin" + fname2 = "file2.bin" + data = []byte{1, 2, 3, 4} + data2 = []byte{2, 3, 4, 5} + data3 = []byte{3, 5, 6, 7} + dataLen = 4 + ) + + // Create 2 temp dirs + dir1 := t.TempDir() + dir2 := t.TempDir() + + // Create file in dir1 and fill with data + f, err := os.Create(filepath.Join(dir1, fname)) + if err != nil { + t.Fatal(err) + } + f2, err := os.Create(filepath.Join(dir1, fname2)) + if err != nil { + t.Fatal(err) + } + f3, err := os.Create(filepath.Join(dir2, fname2)) + if err != nil { + t.Fatal(err) + } + if _, err := f.Write(data); err != nil { + t.Fatal(err) + } + if _, err := f2.Write(data2); err != nil { + t.Fatal(err) + } + if _, err := f3.Write(data3); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + if err := f2.Close(); err != nil { + t.Fatal(err) + } + if err := f3.Close(); err != nil { + t.Fatal(err) + } + if err := os.Rename(f.Name(), filepath.Join(dir2, fname)); err != nil { + t.Fatal(err) + } + if err := os.Rename(f2.Name(), filepath.Join(dir2, fname2)); err != nil { + t.Fatal(err) + } + + // Check file contents + f, err = os.Open(filepath.Join(dir2, fname)) + if err != nil { + t.Fatal(err) + } + defer f.Close() + defer os.Remove(f.Name()) + buf := make([]byte, dataLen) + if _, err := f.Read(buf); err != nil { + t.Fatal(err) + } + if !bytes.Equal(buf, data) { + t.Errorf("unexpected file contents. Got %v\n", buf) + } + + f, err = os.Open(filepath.Join(dir2, fname2)) + if err != nil { + t.Fatal(err) + } + defer f.Close() + defer os.Remove(f.Name()) + if _, err := f.Read(buf); err != nil { + t.Fatal(err) + } + if !bytes.Equal(buf, data2) { + t.Errorf("unexpected file contents. Got %v\n", buf) + } +} + +func TestFreezerCloseSync(t *testing.T) { + t.Parallel() + f, _ := newFreezerForTesting(t, map[string]bool{"a": true, "b": true}) + defer f.Close() + + // Now, close and sync. This mimics the behaviour if the node is shut down, + // just as the chain freezer is writing. + // 1: thread-1: chain treezer writes, via freezeRange (holds lock) + // 2: thread-2: Close called, waits for write to finish + // 3: thread-1: finishes writing, releases lock + // 4: thread-2: obtains lock, completes Close() + // 5: thread-1: calls f.Sync() + if err := f.Close(); err != nil { + t.Fatal(err) + } + if err := f.Sync(); err == nil { + t.Fatalf("want error, have nil") + } else if have, want := err.Error(), "[closed closed]"; have != want { + t.Fatalf("want %v, have %v", have, want) + } +} + +func TestFreezerSuite(t *testing.T) { + ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore { + tables := make(map[string]bool) + for _, kind := range kinds { + tables[kind] = true + } + f, _ := newFreezerForTesting(t, tables) + return f + }) + ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore { + tables := make(map[string]bool) + for _, kind := range kinds { + tables[kind] = true + } + f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, tables) + return f + }) +} diff --git a/core/rawdb/freezer_utils.go b/core/rawdb/freezer_utils.go new file mode 100644 index 0000000..752e95b --- /dev/null +++ b/core/rawdb/freezer_utils.go @@ -0,0 +1,131 @@ +// 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 rawdb + +import ( + "io" + "os" + "path/filepath" +) + +// copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'. +// The 'destPath' is created if it doesn't exist, otherwise it is overwritten. +// Before the copy is executed, there is a callback can be registered to +// manipulate the dest file. +// It is perfectly valid to have destPath == srcPath. +func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) error) error { + // Create a temp file in the same dir where we want it to wind up + f, err := os.CreateTemp(filepath.Dir(destPath), "*") + if err != nil { + return err + } + fname := f.Name() + + // Clean up the leftover file + defer func() { + if f != nil { + f.Close() + } + os.Remove(fname) + }() + // Apply the given function if it's not nil before we copy + // the content from the src. + if before != nil { + if err := before(f); err != nil { + return err + } + } + // Open the source file + src, err := os.Open(srcPath) + if err != nil { + return err + } + if _, err = src.Seek(int64(offset), 0); err != nil { + src.Close() + return err + } + // io.Copy uses 32K buffer internally. + _, err = io.Copy(f, src) + if err != nil { + src.Close() + return err + } + // Rename the temporary file to the specified dest name. + // src may be same as dest, so needs to be closed before + // we do the final move. + src.Close() + + if err := f.Close(); err != nil { + return err + } + f = nil + return os.Rename(fname, destPath) +} + +// openFreezerFileForAppend opens a freezer table file and seeks to the end +func openFreezerFileForAppend(filename string) (*os.File, error) { + // Open the file without the O_APPEND flag + // because it has differing behaviour during Truncate operations + // on different OS's + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return nil, err + } + // Seek to end for append + if _, err = file.Seek(0, io.SeekEnd); err != nil { + return nil, err + } + return file, nil +} + +// openFreezerFileForReadOnly opens a freezer table file for read only access +func openFreezerFileForReadOnly(filename string) (*os.File, error) { + return os.OpenFile(filename, os.O_RDONLY, 0644) +} + +// openFreezerFileTruncated opens a freezer table making sure it is truncated +func openFreezerFileTruncated(filename string) (*os.File, error) { + return os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) +} + +// truncateFreezerFile resizes a freezer table file and seeks to the end +func truncateFreezerFile(file *os.File, size int64) error { + if err := file.Truncate(size); err != nil { + return err + } + // Seek to end for append + if _, err := file.Seek(0, io.SeekEnd); err != nil { + return err + } + return nil +} + +// grow prepares the slice space for new item, and doubles the slice capacity +// if space is not enough. +func grow(buf []byte, n int) []byte { + if cap(buf)-len(buf) < n { + newcap := 2 * cap(buf) + if newcap-len(buf) < n { + newcap = len(buf) + n + } + nbuf := make([]byte, len(buf), newcap) + copy(nbuf, buf) + buf = nbuf + } + buf = buf[:len(buf)+n] + return buf +} diff --git a/core/rawdb/freezer_utils_test.go b/core/rawdb/freezer_utils_test.go new file mode 100644 index 0000000..829cbfb --- /dev/null +++ b/core/rawdb/freezer_utils_test.go @@ -0,0 +1,75 @@ +// 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 rawdb + +import ( + "bytes" + "os" + "testing" +) + +func TestCopyFrom(t *testing.T) { + var ( + content = []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8} + prefix = []byte{0x9, 0xa, 0xb, 0xc, 0xd, 0xf} + ) + var cases = []struct { + src, dest string + offset uint64 + writePrefix bool + }{ + {"foo", "bar", 0, false}, + {"foo", "bar", 1, false}, + {"foo", "bar", 8, false}, + {"foo", "foo", 0, false}, + {"foo", "foo", 1, false}, + {"foo", "foo", 8, false}, + {"foo", "bar", 0, true}, + {"foo", "bar", 1, true}, + {"foo", "bar", 8, true}, + } + for _, c := range cases { + os.WriteFile(c.src, content, 0600) + + if err := copyFrom(c.src, c.dest, c.offset, func(f *os.File) error { + if !c.writePrefix { + return nil + } + f.Write(prefix) + return nil + }); err != nil { + os.Remove(c.src) + t.Fatalf("Failed to copy %v", err) + } + + blob, err := os.ReadFile(c.dest) + if err != nil { + os.Remove(c.src) + os.Remove(c.dest) + t.Fatalf("Failed to read %v", err) + } + want := content[c.offset:] + if c.writePrefix { + want = append(prefix, want...) + } + if !bytes.Equal(blob, want) { + t.Fatal("Unexpected value") + } + os.Remove(c.src) + os.Remove(c.dest) + } +} diff --git a/core/rawdb/key_length_iterator.go b/core/rawdb/key_length_iterator.go new file mode 100644 index 0000000..d1c5af2 --- /dev/null +++ b/core/rawdb/key_length_iterator.go @@ -0,0 +1,47 @@ +// 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 rawdb + +import "github.com/ethereum/go-ethereum/ethdb" + +// KeyLengthIterator is a wrapper for a database iterator that ensures only key-value pairs +// with a specific key length will be returned. +type KeyLengthIterator struct { + requiredKeyLength int + ethdb.Iterator +} + +// NewKeyLengthIterator returns a wrapped version of the iterator that will only return key-value +// pairs where keys with a specific key length will be returned. +func NewKeyLengthIterator(it ethdb.Iterator, keyLen int) ethdb.Iterator { + return &KeyLengthIterator{ + Iterator: it, + requiredKeyLength: keyLen, + } +} + +func (it *KeyLengthIterator) Next() bool { + // Return true as soon as a key with the required key length is discovered + for it.Iterator.Next() { + if len(it.Iterator.Key()) == it.requiredKeyLength { + return true + } + } + + // Return false when we exhaust the keys in the underlying iterator. + return false +} diff --git a/core/rawdb/key_length_iterator_test.go b/core/rawdb/key_length_iterator_test.go new file mode 100644 index 0000000..654efc5 --- /dev/null +++ b/core/rawdb/key_length_iterator_test.go @@ -0,0 +1,60 @@ +// 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 rawdb + +import ( + "encoding/binary" + "testing" +) + +func TestKeyLengthIterator(t *testing.T) { + db := NewMemoryDatabase() + + keyLen := 8 + expectedKeys := make(map[string]struct{}) + for i := 0; i < 100; i++ { + key := make([]byte, keyLen) + binary.BigEndian.PutUint64(key, uint64(i)) + if err := db.Put(key, []byte{0x1}); err != nil { + t.Fatal(err) + } + expectedKeys[string(key)] = struct{}{} + + longerKey := make([]byte, keyLen*2) + binary.BigEndian.PutUint64(longerKey, uint64(i)) + if err := db.Put(longerKey, []byte{0x1}); err != nil { + t.Fatal(err) + } + } + + it := NewKeyLengthIterator(db.NewIterator(nil, nil), keyLen) + for it.Next() { + key := it.Key() + _, exists := expectedKeys[string(key)] + if !exists { + t.Fatalf("Found unexpected key %d", binary.BigEndian.Uint64(key)) + } + delete(expectedKeys, string(key)) + if len(key) != keyLen { + t.Fatalf("Found unexpected key in key length iterator with length %d", len(key)) + } + } + + if len(expectedKeys) != 0 { + t.Fatalf("Expected all keys of length %d to be removed from expected keys during iteration", keyLen) + } +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go new file mode 100644 index 0000000..dbf010b --- /dev/null +++ b/core/rawdb/schema.go @@ -0,0 +1,341 @@ +// 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 rawdb contains a collection of low level database accessors. +package rawdb + +import ( + "bytes" + "encoding/binary" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/metrics" +) + +// The fields below define the low level database schema prefixing. +var ( + // databaseVersionKey tracks the current database version. + databaseVersionKey = []byte("DatabaseVersion") + + // headHeaderKey tracks the latest known header's hash. + headHeaderKey = []byte("LastHeader") + + // headBlockKey tracks the latest known full block's hash. + headBlockKey = []byte("LastBlock") + + // headFastBlockKey tracks the latest known incomplete block's hash during fast sync. + headFastBlockKey = []byte("LastFast") + + // headFinalizedBlockKey tracks the latest known finalized block hash. + headFinalizedBlockKey = []byte("LastFinalized") + + // persistentStateIDKey tracks the id of latest stored state(for path-based only). + persistentStateIDKey = []byte("LastStateID") + + // lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead). + lastPivotKey = []byte("LastPivot") + + // fastTrieProgressKey tracks the number of trie entries imported during fast sync. + fastTrieProgressKey = []byte("TrieSync") + + // snapshotDisabledKey flags that the snapshot should not be maintained due to initial sync. + snapshotDisabledKey = []byte("SnapshotDisabled") + + // SnapshotRootKey tracks the hash of the last snapshot. + SnapshotRootKey = []byte("SnapshotRoot") + + // snapshotJournalKey tracks the in-memory diff layers across restarts. + snapshotJournalKey = []byte("SnapshotJournal") + + // snapshotGeneratorKey tracks the snapshot generation marker across restarts. + snapshotGeneratorKey = []byte("SnapshotGenerator") + + // snapshotRecoveryKey tracks the snapshot recovery marker across restarts. + snapshotRecoveryKey = []byte("SnapshotRecovery") + + // snapshotSyncStatusKey tracks the snapshot sync status across restarts. + snapshotSyncStatusKey = []byte("SnapshotSyncStatus") + + // skeletonSyncStatusKey tracks the skeleton sync status across restarts. + skeletonSyncStatusKey = []byte("SkeletonSyncStatus") + + // trieJournalKey tracks the in-memory trie node layers across restarts. + trieJournalKey = []byte("TrieJournal") + + // txIndexTailKey tracks the oldest block whose transactions have been indexed. + txIndexTailKey = []byte("TransactionIndexTail") + + // fastTxLookupLimitKey tracks the transaction lookup limit during fast sync. + // This flag is deprecated, it's kept to avoid reporting errors when inspect + // database. + fastTxLookupLimitKey = []byte("FastTransactionLookupLimit") + + // badBlockKey tracks the list of bad blocks seen by local + badBlockKey = []byte("InvalidBlock") + + // uncleanShutdownKey tracks the list of local crashes + uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db + + // 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 + headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash + headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian) + + blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body + blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts + + txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata + bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits + SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value + SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value + CodePrefix = []byte("c") // CodePrefix + code hash -> account code + skeletonHeaderPrefix = []byte("S") // skeletonHeaderPrefix + num (uint64 big endian) -> header + + // Path-based storage scheme of merkle patricia trie. + TrieNodeAccountPrefix = []byte("A") // TrieNodeAccountPrefix + hexPath -> trie node + TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node + stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id + + PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage + configPrefix = []byte("ethereum-config-") // config prefix for the db + genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db + + // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress + BloomBitsIndexPrefix = []byte("iB") + + ChtPrefix = []byte("chtRootV2-") // ChtPrefix + chtNum (uint64 big endian) -> trie root hash + ChtTablePrefix = []byte("cht-") + ChtIndexTablePrefix = []byte("chtIndexV2-") + + BloomTriePrefix = []byte("bltRoot-") // BloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash + BloomTrieTablePrefix = []byte("blt-") + BloomTrieIndexPrefix = []byte("bltIndex-") + + 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) +) + +// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary +// fields. +type LegacyTxLookupEntry struct { + BlockHash common.Hash + BlockIndex uint64 + Index uint64 +} + +// encodeBlockNumber encodes a block number as big endian uint64 +func encodeBlockNumber(number uint64) []byte { + enc := make([]byte, 8) + binary.BigEndian.PutUint64(enc, number) + return enc +} + +// headerKeyPrefix = headerPrefix + num (uint64 big endian) +func headerKeyPrefix(number uint64) []byte { + return append(headerPrefix, encodeBlockNumber(number)...) +} + +// headerKey = headerPrefix + num (uint64 big endian) + hash +func headerKey(number uint64, hash common.Hash) []byte { + return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...) +} + +// headerTDKey = headerPrefix + num (uint64 big endian) + hash + headerTDSuffix +func headerTDKey(number uint64, hash common.Hash) []byte { + return append(headerKey(number, hash), headerTDSuffix...) +} + +// headerHashKey = headerPrefix + num (uint64 big endian) + headerHashSuffix +func headerHashKey(number uint64) []byte { + return append(append(headerPrefix, encodeBlockNumber(number)...), headerHashSuffix...) +} + +// headerNumberKey = headerNumberPrefix + hash +func headerNumberKey(hash common.Hash) []byte { + return append(headerNumberPrefix, hash.Bytes()...) +} + +// blockBodyKey = blockBodyPrefix + num (uint64 big endian) + hash +func blockBodyKey(number uint64, hash common.Hash) []byte { + return append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) +} + +// blockReceiptsKey = blockReceiptsPrefix + num (uint64 big endian) + hash +func blockReceiptsKey(number uint64, hash common.Hash) []byte { + return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...) +} + +// txLookupKey = txLookupPrefix + hash +func txLookupKey(hash common.Hash) []byte { + return append(txLookupPrefix, hash.Bytes()...) +} + +// accountSnapshotKey = SnapshotAccountPrefix + hash +func accountSnapshotKey(hash common.Hash) []byte { + return append(SnapshotAccountPrefix, hash.Bytes()...) +} + +// storageSnapshotKey = SnapshotStoragePrefix + account hash + storage hash +func storageSnapshotKey(accountHash, storageHash common.Hash) []byte { + 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 +func storageSnapshotsKey(accountHash common.Hash) []byte { + return append(SnapshotStoragePrefix, accountHash.Bytes()...) +} + +// bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash +func bloomBitsKey(bit uint, section uint64, hash common.Hash) []byte { + key := append(append(bloomBitsPrefix, make([]byte, 10)...), hash.Bytes()...) + + binary.BigEndian.PutUint16(key[1:], uint16(bit)) + binary.BigEndian.PutUint64(key[3:], section) + + return key +} + +// skeletonHeaderKey = skeletonHeaderPrefix + num (uint64 big endian) +func skeletonHeaderKey(number uint64) []byte { + return append(skeletonHeaderPrefix, encodeBlockNumber(number)...) +} + +// preimageKey = PreimagePrefix + hash +func preimageKey(hash common.Hash) []byte { + return append(PreimagePrefix, hash.Bytes()...) +} + +// codeKey = CodePrefix + hash +func codeKey(hash common.Hash) []byte { + return append(CodePrefix, hash.Bytes()...) +} + +// IsCodeKey reports whether the given byte slice is the key of contract code, +// if so return the raw code hash as well. +func IsCodeKey(key []byte) (bool, []byte) { + if bytes.HasPrefix(key, CodePrefix) && len(key) == common.HashLength+len(CodePrefix) { + return true, key[len(CodePrefix):] + } + return false, nil +} + +// configKey = configPrefix + hash +func configKey(hash common.Hash) []byte { + return append(configPrefix, hash.Bytes()...) +} + +// genesisStateSpecKey = genesisPrefix + hash +func genesisStateSpecKey(hash common.Hash) []byte { + return append(genesisPrefix, hash.Bytes()...) +} + +// stateIDKey = stateIDPrefix + root (32 bytes) +func stateIDKey(root common.Hash) []byte { + return append(stateIDPrefix, root.Bytes()...) +} + +// accountTrieNodeKey = TrieNodeAccountPrefix + nodePath. +func accountTrieNodeKey(path []byte) []byte { + return append(TrieNodeAccountPrefix, path...) +} + +// storageTrieNodeKey = TrieNodeStoragePrefix + accountHash + nodePath. +func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte { + 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 +// node. The characteristics of legacy trie node are: +// - the key length is 32 bytes +// - the key is the hash of val +func IsLegacyTrieNode(key []byte, val []byte) bool { + if len(key) != common.HashLength { + return false + } + return bytes.Equal(key, crypto.Keccak256(val)) +} + +// 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 + } + // The remaining key should only consist a hex node path + // whose length is in the range 0 to 64 (64 is excluded + // since leaves are always wrapped with shortNode). + if len(key) >= len(TrieNodeAccountPrefix)+common.HashLength*2 { + return false, nil + } + return true, key[len(TrieNodeAccountPrefix):] +} + +// IsAccountTrieNode reports whether a provided database entry is an account +// trie node in path-based state scheme. +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 + } + // The remaining key consists of 2 parts: + // - 32 bytes account hash + // - hex node path whose length is in the range 0 to 64 + if len(key) < len(TrieNodeStoragePrefix)+common.HashLength { + return false, common.Hash{}, nil + } + if len(key) >= len(TrieNodeStoragePrefix)+common.HashLength+common.HashLength*2 { + return false, common.Hash{}, nil + } + 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 new file mode 100644 index 0000000..90849fe --- /dev/null +++ b/core/rawdb/table.go @@ -0,0 +1,307 @@ +// 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 rawdb + +import ( + "github.com/ethereum/go-ethereum/ethdb" +) + +// table is a wrapper around a database that prefixes each key access with a pre- +// configured string. +type table struct { + db ethdb.Database + prefix string +} + +// NewTable returns a database object that prefixes all keys with a given string. +func NewTable(db ethdb.Database, prefix string) ethdb.Database { + return &table{ + db: db, + prefix: prefix, + } +} + +// Close is a noop to implement the Database interface. +func (t *table) Close() error { + return nil +} + +// Has retrieves if a prefixed version of a key is present in the database. +func (t *table) Has(key []byte) (bool, error) { + return t.db.Has(append([]byte(t.prefix), key...)) +} + +// Get retrieves the given prefixed key if it's present in the database. +func (t *table) Get(key []byte) ([]byte, error) { + return t.db.Get(append([]byte(t.prefix), key...)) +} + +// HasAncient is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) HasAncient(kind string, number uint64) (bool, error) { + return t.db.HasAncient(kind, number) +} + +// Ancient is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) Ancient(kind string, number uint64) ([]byte, error) { + return t.db.Ancient(kind, number) +} + +// AncientRange is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { + return t.db.AncientRange(kind, start, count, maxBytes) +} + +// Ancients is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) Ancients() (uint64, error) { + return t.db.Ancients() +} + +// Tail is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) Tail() (uint64, error) { + return t.db.Tail() +} + +// AncientSize is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) AncientSize(kind string) (uint64, error) { + return t.db.AncientSize(kind) +} + +// ModifyAncients runs an ancient write operation on the underlying database. +func (t *table) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) { + return t.db.ModifyAncients(fn) +} + +func (t *table) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err error) { + return t.db.ReadAncients(fn) +} + +// TruncateHead is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) TruncateHead(items uint64) (uint64, error) { + return t.db.TruncateHead(items) +} + +// TruncateTail is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) TruncateTail(items uint64) (uint64, error) { + return t.db.TruncateTail(items) +} + +// Sync is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) Sync() error { + return t.db.Sync() +} + +// MigrateTable processes the entries in a given table in sequence +// converting them to a new format if they're of an old format. +func (t *table) MigrateTable(kind string, convert convertLegacyFn) error { + return t.db.MigrateTable(kind, convert) +} + +// AncientDatadir returns the ancient datadir of the underlying database. +func (t *table) AncientDatadir() (string, error) { + return t.db.AncientDatadir() +} + +// Put inserts the given value into the database at a prefixed version of the +// provided key. +func (t *table) Put(key []byte, value []byte) error { + return t.db.Put(append([]byte(t.prefix), key...), value) +} + +// Delete removes the given prefixed key from the database. +func (t *table) Delete(key []byte) error { + return t.db.Delete(append([]byte(t.prefix), key...)) +} + +// NewIterator creates a binary-alphabetical iterator over a subset +// of database content with a particular key prefix, starting at a particular +// initial key (or after, if it does not exist). +func (t *table) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + innerPrefix := append([]byte(t.prefix), prefix...) + iter := t.db.NewIterator(innerPrefix, start) + return &tableIterator{ + iter: iter, + prefix: t.prefix, + } +} + +// Stat returns the statistic data of the database. +func (t *table) Stat() (string, error) { + return t.db.Stat() +} + +// Compact flattens the underlying data store for the given key range. In essence, +// deleted and overwritten versions are discarded, and the data is rearranged to +// reduce the cost of operations needed to access them. +// +// A nil start is treated as a key before all keys in the data store; a nil limit +// is treated as a key after all keys in the data store. If both is nil then it +// will compact entire data store. +func (t *table) Compact(start []byte, limit []byte) error { + // If no start was specified, use the table prefix as the first value + if start == nil { + start = []byte(t.prefix) + } else { + start = append([]byte(t.prefix), start...) + } + // If no limit was specified, use the first element not matching the prefix + // as the limit + if limit == nil { + limit = []byte(t.prefix) + for i := len(limit) - 1; i >= 0; i-- { + // Bump the current character, stopping if it doesn't overflow + limit[i]++ + if limit[i] > 0 { + break + } + // Character overflown, proceed to the next or nil if the last + if i == 0 { + limit = nil + } + } + } else { + limit = append([]byte(t.prefix), limit...) + } + // Range correctly calculated based on table prefix, delegate down + return t.db.Compact(start, limit) +} + +// NewBatch creates a write-only database that buffers changes to its host db +// until a final write is called, each operation prefixing all keys with the +// pre-configured string. +func (t *table) NewBatch() ethdb.Batch { + return &tableBatch{t.db.NewBatch(), t.prefix} +} + +// NewBatchWithSize creates a write-only database batch with pre-allocated buffer. +func (t *table) NewBatchWithSize(size int) ethdb.Batch { + return &tableBatch{t.db.NewBatchWithSize(size), t.prefix} +} + +// NewSnapshot creates a database snapshot based on the current state. +// The created snapshot will not be affected by all following mutations +// happened on the database. +func (t *table) NewSnapshot() (ethdb.Snapshot, error) { + return t.db.NewSnapshot() +} + +// tableBatch is a wrapper around a database batch that prefixes each key access +// with a pre-configured string. +type tableBatch struct { + batch ethdb.Batch + prefix string +} + +// Put inserts the given value into the batch for later committing. +func (b *tableBatch) Put(key, value []byte) error { + return b.batch.Put(append([]byte(b.prefix), key...), value) +} + +// 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...)) +} + +// ValueSize retrieves the amount of data queued up for writing. +func (b *tableBatch) ValueSize() int { + return b.batch.ValueSize() +} + +// Write flushes any accumulated data to disk. +func (b *tableBatch) Write() error { + return b.batch.Write() +} + +// Reset resets the batch for reuse. +func (b *tableBatch) Reset() { + b.batch.Reset() +} + +// tableReplayer is a wrapper around a batch replayer which truncates +// the added prefix. +type tableReplayer struct { + w ethdb.KeyValueWriter + prefix string +} + +// Put implements the interface KeyValueWriter. +func (r *tableReplayer) Put(key []byte, value []byte) error { + trimmed := key[len(r.prefix):] + return r.w.Put(trimmed, value) +} + +// Delete implements the interface KeyValueWriter. +func (r *tableReplayer) Delete(key []byte) error { + trimmed := key[len(r.prefix):] + return r.w.Delete(trimmed) +} + +// Replay replays the batch contents. +func (b *tableBatch) Replay(w ethdb.KeyValueWriter) error { + return b.batch.Replay(&tableReplayer{w: w, prefix: b.prefix}) +} + +// tableIterator is a wrapper around a database iterator that prefixes each key access +// with a pre-configured string. +type tableIterator struct { + iter ethdb.Iterator + prefix string +} + +// Next moves the iterator to the next key/value pair. It returns whether the +// iterator is exhausted. +func (iter *tableIterator) Next() bool { + return iter.iter.Next() +} + +// Error returns any accumulated error. Exhausting all the key/value pairs +// is not considered to be an error. +func (iter *tableIterator) Error() error { + return iter.iter.Error() +} + +// Key returns the key of the current key/value pair, or nil if done. The caller +// should not modify the contents of the returned slice, and its contents may +// change on the next call to Next. +func (iter *tableIterator) Key() []byte { + key := iter.iter.Key() + if key == nil { + return nil + } + return key[len(iter.prefix):] +} + +// Value returns the value of the current key/value pair, or nil if done. The +// caller should not modify the contents of the returned slice, and its contents +// may change on the next call to Next. +func (iter *tableIterator) Value() []byte { + return iter.iter.Value() +} + +// Release releases associated resources. Release should always succeed and can +// be called multiple times without causing error. +func (iter *tableIterator) Release() { + iter.iter.Release() +} diff --git a/core/rawdb/table_test.go b/core/rawdb/table_test.go new file mode 100644 index 0000000..aa6adf3 --- /dev/null +++ b/core/rawdb/table_test.go @@ -0,0 +1,128 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/ethdb" +) + +func TestTableDatabase(t *testing.T) { testTableDatabase(t, "prefix") } +func TestEmptyPrefixTableDatabase(t *testing.T) { testTableDatabase(t, "") } + +type testReplayer struct { + puts [][]byte + dels [][]byte +} + +func (r *testReplayer) Put(key []byte, value []byte) error { + r.puts = append(r.puts, key) + return nil +} + +func (r *testReplayer) Delete(key []byte) error { + r.dels = append(r.dels, key) + return nil +} + +func testTableDatabase(t *testing.T, prefix string) { + db := NewTable(NewMemoryDatabase(), prefix) + + var entries = []struct { + key []byte + value []byte + }{ + {[]byte{0x01, 0x02}, []byte{0x0a, 0x0b}}, + {[]byte{0x03, 0x04}, []byte{0x0c, 0x0d}}, + {[]byte{0x05, 0x06}, []byte{0x0e, 0x0f}}, + + {[]byte{0xff, 0xff, 0x01}, []byte{0x1a, 0x1b}}, + {[]byte{0xff, 0xff, 0x02}, []byte{0x1c, 0x1d}}, + {[]byte{0xff, 0xff, 0x03}, []byte{0x1e, 0x1f}}, + } + + // Test Put/Get operation + for _, entry := range entries { + db.Put(entry.key, entry.value) + } + for _, entry := range entries { + got, err := db.Get(entry.key) + if err != nil { + t.Fatalf("Failed to get value: %v", err) + } + if !bytes.Equal(got, entry.value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entry.value, got) + } + } + + // Test batch operation + db = NewTable(NewMemoryDatabase(), prefix) + batch := db.NewBatch() + for _, entry := range entries { + batch.Put(entry.key, entry.value) + } + batch.Write() + for _, entry := range entries { + got, err := db.Get(entry.key) + if err != nil { + t.Fatalf("Failed to get value: %v", err) + } + if !bytes.Equal(got, entry.value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entry.value, got) + } + } + + // Test batch replayer + r := &testReplayer{} + batch.Replay(r) + for index, entry := range entries { + got := r.puts[index] + if !bytes.Equal(got, entry.key) { + t.Fatalf("Key mismatch: want=%v, got=%v", entry.key, got) + } + } + + check := func(iter ethdb.Iterator, expCount, index int) { + count := 0 + for iter.Next() { + key, value := iter.Key(), iter.Value() + if !bytes.Equal(key, entries[index].key) { + t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) + } + if !bytes.Equal(value, entries[index].value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) + } + index += 1 + count++ + } + if count != expCount { + t.Fatalf("Wrong number of elems, exp %d got %d", expCount, count) + } + iter.Release() + } + // Test iterators + check(db.NewIterator(nil, nil), 6, 0) + // Test iterators with prefix + check(db.NewIterator([]byte{0xff, 0xff}, nil), 3, 3) + // Test iterators with start point + check(db.NewIterator(nil, []byte{0xff, 0xff, 0x02}), 2, 4) + // Test iterators with prefix and start point + check(db.NewIterator([]byte{0xee}, nil), 0, 0) + check(db.NewIterator(nil, []byte{0x00}), 6, 0) +} diff --git a/core/rawdb/testdata/stored_receipts.bin b/core/rawdb/testdata/stored_receipts.bin new file mode 100644 index 0000000000000000000000000000000000000000..8204fae09bdef1db96cc7ababca9b938910f8a75 GIT binary patch literal 99991 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 literal 0 HcmV?d00001 diff --git a/core/rlp_test.go b/core/rlp_test.go new file mode 100644 index 0000000..bc37408 --- /dev/null +++ b/core/rlp_test.go @@ -0,0 +1,197 @@ +// 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 core + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "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" + "golang.org/x/crypto/sha3" +) + +func getBlock(transactions int, uncles int, dataSize int) *types.Block { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1_000_000_000_000_000_000) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{address: {Balance: funds}}, + } + ) + // We need to generate as many blocks +1 as uncles + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, uncles+1, + func(n int, b *BlockGen) { + if n == uncles { + // Add transactions and stuff on the last block + for i := 0; i < transactions; i++ { + tx, _ := types.SignTx(types.NewTransaction(uint64(i), aa, + big.NewInt(0), 50000, b.header.BaseFee, make([]byte, dataSize)), types.HomesteadSigner{}, key) + b.AddTx(tx) + } + for i := 0; i < uncles; i++ { + b.AddUncle(&types.Header{ParentHash: b.PrevBlock(n - 1 - i).Hash(), Number: big.NewInt(int64(n - i))}) + } + } + }) + block := blocks[len(blocks)-1] + return block +} + +// TestRlpIterator tests that individual transactions can be picked out +// from blocks without full unmarshalling/marshalling +func TestRlpIterator(t *testing.T) { + for _, tt := range []struct { + txs int + uncles int + datasize int + }{ + {0, 0, 0}, + {0, 2, 0}, + {10, 0, 0}, + {10, 2, 0}, + {10, 2, 50}, + } { + testRlpIterator(t, tt.txs, tt.uncles, tt.datasize) + } +} + +func testRlpIterator(t *testing.T, txs, uncles, datasize int) { + desc := fmt.Sprintf("%d txs [%d datasize] and %d uncles", txs, datasize, uncles) + bodyRlp, _ := rlp.EncodeToBytes(getBlock(txs, uncles, datasize).Body()) + it, err := rlp.NewListIterator(bodyRlp) + if err != nil { + t.Fatal(err) + } + // Check that txs exist + if !it.Next() { + t.Fatal("expected two elems, got zero") + } + txdata := it.Value() + // Check that uncles exist + if !it.Next() { + t.Fatal("expected two elems, got one") + } + // No more after that + if it.Next() { + t.Fatal("expected only two elems, got more") + } + txIt, err := rlp.NewListIterator(txdata) + if err != nil { + t.Fatal(err) + } + var gotHashes []common.Hash + var expHashes []common.Hash + for txIt.Next() { + gotHashes = append(gotHashes, crypto.Keccak256Hash(txIt.Value())) + } + + var expBody types.Body + err = rlp.DecodeBytes(bodyRlp, &expBody) + if err != nil { + t.Fatal(err) + } + for _, tx := range expBody.Transactions { + expHashes = append(expHashes, tx.Hash()) + } + if gotLen, expLen := len(gotHashes), len(expHashes); gotLen != expLen { + t.Fatalf("testcase %v: length wrong, got %d exp %d", desc, gotLen, expLen) + } + // also sanity check against input + if gotLen := len(gotHashes); gotLen != txs { + t.Fatalf("testcase %v: length wrong, got %d exp %d", desc, gotLen, txs) + } + for i, got := range gotHashes { + if exp := expHashes[i]; got != exp { + t.Errorf("testcase %v: hash wrong, got %x, exp %x", desc, got, exp) + } + } +} + +// BenchmarkHashing compares the speeds of hashing a rlp raw data directly +// without the unmarshalling/marshalling step +func BenchmarkHashing(b *testing.B) { + // Make a pretty fat block + var ( + bodyRlp []byte + blockRlp []byte + ) + { + block := getBlock(200, 2, 50) + bodyRlp, _ = rlp.EncodeToBytes(block.Body()) + blockRlp, _ = rlp.EncodeToBytes(block) + } + var got common.Hash + var hasher = sha3.NewLegacyKeccak256() + b.Run("iteratorhashing", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var hash common.Hash + it, err := rlp.NewListIterator(bodyRlp) + if err != nil { + b.Fatal(err) + } + it.Next() + txs := it.Value() + txIt, err := rlp.NewListIterator(txs) + if err != nil { + b.Fatal(err) + } + for txIt.Next() { + hasher.Reset() + hasher.Write(txIt.Value()) + hasher.Sum(hash[:0]) + got = hash + } + } + }) + var exp common.Hash + b.Run("fullbodyhashing", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var body types.Body + rlp.DecodeBytes(bodyRlp, &body) + for _, tx := range body.Transactions { + exp = tx.Hash() + } + } + }) + b.Run("fullblockhashing", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + var block types.Block + rlp.DecodeBytes(blockRlp, &block) + for _, tx := range block.Transactions() { + tx.Hash() + } + } + }) + if got != exp { + b.Fatalf("hash wrong, got %x exp %x", got, exp) + } +} diff --git a/core/sender_cacher.go b/core/sender_cacher.go new file mode 100644 index 0000000..4be5361 --- /dev/null +++ b/core/sender_cacher.go @@ -0,0 +1,105 @@ +// 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 core + +import ( + "runtime" + + "github.com/ethereum/go-ethereum/core/types" +) + +// SenderCacher is a concurrent transaction sender recoverer and cacher. +var SenderCacher = newTxSenderCacher(runtime.NumCPU()) + +// txSenderCacherRequest is a request for recovering transaction senders with a +// specific signature scheme and caching it into the transactions themselves. +// +// The inc field defines the number of transactions to skip after each recovery, +// which is used to feed the same underlying input array to different threads but +// ensure they process the early transactions fast. +type txSenderCacherRequest struct { + signer types.Signer + txs []*types.Transaction + inc int +} + +// txSenderCacher is a helper structure to concurrently ecrecover transaction +// senders from digital signatures on background threads. +type txSenderCacher struct { + threads int + tasks chan *txSenderCacherRequest +} + +// newTxSenderCacher creates a new transaction sender background cacher and starts +// as many processing goroutines as allowed by the GOMAXPROCS on construction. +func newTxSenderCacher(threads int) *txSenderCacher { + cacher := &txSenderCacher{ + tasks: make(chan *txSenderCacherRequest, threads), + threads: threads, + } + for i := 0; i < threads; i++ { + go cacher.cache() + } + return cacher +} + +// cache is an infinite loop, caching transaction senders from various forms of +// data structures. +func (cacher *txSenderCacher) cache() { + for task := range cacher.tasks { + for i := 0; i < len(task.txs); i += task.inc { + types.Sender(task.signer, task.txs[i]) + } + } +} + +// Recover recovers the senders from a batch of transactions and caches them +// back into the same data structures. There is no validation being done, nor +// any reaction to invalid signatures. That is up to calling code later. +func (cacher *txSenderCacher) Recover(signer types.Signer, txs []*types.Transaction) { + // If there's nothing to recover, abort + if len(txs) == 0 { + return + } + // Ensure we have meaningful task sizes and schedule the recoveries + tasks := cacher.threads + if len(txs) < tasks*4 { + tasks = (len(txs) + 3) / 4 + } + for i := 0; i < tasks; i++ { + cacher.tasks <- &txSenderCacherRequest{ + signer: signer, + txs: txs[i:], + inc: tasks, + } + } +} + +// RecoverFromBlocks recovers the senders from a batch of blocks and caches them +// back into the same data structures. There is no validation being done, nor +// any reaction to invalid signatures. That is up to calling code later. +func (cacher *txSenderCacher) RecoverFromBlocks(signer types.Signer, blocks []*types.Block) { + count := 0 + for _, block := range blocks { + count += len(block.Transactions()) + } + txs := make([]*types.Transaction, 0, count) + for _, block := range blocks { + txs = append(txs, block.Transactions()...) + } + cacher.Recover(signer, txs) +} diff --git a/core/state/access_events.go b/core/state/access_events.go new file mode 100644 index 0000000..4b6c7c7 --- /dev/null +++ b/core/state/access_events.go @@ -0,0 +1,320 @@ +// 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 state + +import ( + "maps" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/holiman/uint256" +) + +// mode specifies how a tree location has been accessed +// for the byte value: +// * the first bit is set if the branch has been edited +// * the second bit is set if the branch has been read +type mode byte + +const ( + AccessWitnessReadFlag = mode(1) + AccessWitnessWriteFlag = mode(2) +) + +var zeroTreeIndex uint256.Int + +// AccessEvents lists the locations of the state that are being accessed +// during the production of a block. +type AccessEvents struct { + branches map[branchAccessKey]mode + chunks map[chunkAccessKey]mode + + pointCache *utils.PointCache +} + +func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents { + return &AccessEvents{ + branches: make(map[branchAccessKey]mode), + chunks: make(map[chunkAccessKey]mode), + pointCache: pointCache, + } +} + +// Merge is used to merge the access events that were generated during the +// execution of a tx, with the accumulation of all access events that were +// generated during the execution of all txs preceding this one in a block. +func (ae *AccessEvents) Merge(other *AccessEvents) { + for k := range other.branches { + ae.branches[k] |= other.branches[k] + } + for k, chunk := range other.chunks { + ae.chunks[k] |= chunk + } +} + +// Keys returns, predictably, the list of keys that were touched during the +// buildup of the access witness. +func (ae *AccessEvents) Keys() [][]byte { + // TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks). + keys := make([][]byte, 0, len(ae.chunks)) + for chunk := range ae.chunks { + basePoint := ae.pointCache.Get(chunk.addr[:]) + key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey) + keys = append(keys, key) + } + return keys +} + +func (ae *AccessEvents) Copy() *AccessEvents { + cpy := &AccessEvents{ + branches: maps.Clone(ae.branches), + chunks: maps.Clone(ae.chunks), + pointCache: ae.pointCache, + } + return cpy +} + +// AddAccount returns the gas to be charged for each of the currently cold +// member fields of an account. +func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 { + var gas uint64 + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) + return gas +} + +// MessageCallGas returns the gas to be charged for each of the currently +// cold member fields of an account, that need to be touched when making a message +// call to that account. +func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 { + var gas uint64 + gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false) + gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false) + return gas +} + +// ValueTransferGas returns the gas to be charged for each of the currently +// cold balance member fields of the caller and the callee accounts. +func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 { + var gas uint64 + gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + return gas +} + +// ContractCreateInitGas returns the access gas costs for the initialization of +// a contract creation. +func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 { + var gas uint64 + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true) + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true) + if createSendsValue { + gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true) + } + return gas +} + +// AddTxOrigin adds the member fields of the sender account to the access event list, +// so that cold accesses are not charged, since they are covered by the 21000 gas. +func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) { + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) + ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false) +} + +// AddTxDestination adds the member fields of the sender account to the access event list, +// so that cold accesses are not charged, since they are covered by the 21000 gas. +func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) { + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false) + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue) + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false) + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false) + ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false) +} + +// SlotGas returns the amount of gas to be charged for a cold storage access. +func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 { + treeIndex, subIndex := utils.StorageIndex(slot.Bytes()) + return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite) +} + +// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold +// access cost to be charged, if need be. +func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { + stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite) + + var gas uint64 + if stemRead { + gas += params.WitnessBranchReadCost + } + if selectorRead { + gas += params.WitnessChunkReadCost + } + if stemWrite { + gas += params.WitnessBranchWriteCost + } + if selectorWrite { + gas += params.WitnessChunkWriteCost + } + if selectorFill { + gas += params.WitnessChunkFillCost + } + return gas +} + +// touchAddress adds any missing access event to the access event list. +func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) { + branchKey := newBranchAccessKey(addr, treeIndex) + chunkKey := newChunkAccessKey(branchKey, subIndex) + + // Read access. + var branchRead, chunkRead bool + if _, hasStem := ae.branches[branchKey]; !hasStem { + branchRead = true + ae.branches[branchKey] = AccessWitnessReadFlag + } + if _, hasSelector := ae.chunks[chunkKey]; !hasSelector { + chunkRead = true + ae.chunks[chunkKey] = AccessWitnessReadFlag + } + + // Write access. + var branchWrite, chunkWrite, chunkFill bool + if isWrite { + if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 { + branchWrite = true + ae.branches[branchKey] |= AccessWitnessWriteFlag + } + + chunkValue := ae.chunks[chunkKey] + if (chunkValue & AccessWitnessWriteFlag) == 0 { + chunkWrite = true + ae.chunks[chunkKey] |= AccessWitnessWriteFlag + } + // TODO: charge chunk filling costs if the leaf was previously empty in the state + } + return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill +} + +type branchAccessKey struct { + addr common.Address + treeIndex uint256.Int +} + +func newBranchAccessKey(addr common.Address, treeIndex uint256.Int) branchAccessKey { + var sk branchAccessKey + sk.addr = addr + sk.treeIndex = treeIndex + return sk +} + +type chunkAccessKey struct { + branchAccessKey + leafKey byte +} + +func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey { + var lk chunkAccessKey + lk.branchAccessKey = branchKey + lk.leafKey = leafKey + return lk +} + +// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs +func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 { + // note that in the case where the copied code is outside the range of the + // contract code but touches the last leaf with contract code in it, + // we don't include the last leaf of code in the AccessWitness. The + // reason that we do not need the last leaf is the account's code size + // is already in the AccessWitness so a stateless verifier can see that + // the code from the last leaf is not needed. + if (codeLen == 0 && size == 0) || startPC > codeLen { + return 0 + } + + endPC := startPC + size + if endPC > codeLen { + endPC = codeLen + } + if endPC > 0 { + endPC -= 1 // endPC is the last bytecode that will be touched. + } + + var statelessGasCharged uint64 + for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ { + treeIndex := *uint256.NewInt((chunkNumber + 128) / 256) + subIndex := byte((chunkNumber + 128) % 256) + gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite) + var overflow bool + statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas) + if overflow { + panic("overflow when adding gas") + } + } + return statelessGasCharged +} + +// VersionGas adds the account's version to the accessed data, and returns the +// amount of gas that it costs. +// Note that an access in write mode implies an access in read mode, whereas an +// access in read mode does not imply an access in write mode. +func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite) +} + +// BalanceGas adds the account's balance to the accessed data, and returns the +// amount of gas that it costs. +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite) +} + +// NonceGas adds the account's nonce to the accessed data, and returns the +// amount of gas that it costs. +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite) +} + +// CodeSizeGas adds the account's code size to the accessed data, and returns the +// amount of gas that it costs. +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite) +} + +// CodeHashGas adds the account's code hash to the accessed data, and returns the +// amount of gas that it costs. +// in write mode. If false, the charged gas corresponds to an access in read mode. +// Note that an access in write mode implies an access in read mode, whereas an access in +// read mode does not imply an access in write mode. +func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 { + return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite) +} diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go new file mode 100644 index 0000000..c8c93ac --- /dev/null +++ b/core/state/access_events_test.go @@ -0,0 +1,153 @@ +// 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 state + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" +) + +var ( + testAddr [20]byte + testAddr2 [20]byte +) + +func init() { + for i := byte(0); i < 20; i++ { + testAddr[i] = i + testAddr[2] = 2 * i + } +} + +func TestAccountHeaderGas(t *testing.T) { + ae := NewAccessEvents(utils.NewPointCache(1024)) + + // Check cold read cost + gas := ae.VersionGas(testAddr, false) + if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost; gas != want { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) + } + + // Check warm read cost + gas = ae.VersionGas(testAddr, false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + + // Check cold read costs in the same group no longer incur the branch read cost + gas = ae.BalanceGas(testAddr, false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } + gas = ae.NonceGas(testAddr, false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } + gas = ae.CodeSizeGas(testAddr, false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } + gas = ae.CodeHashGas(testAddr, false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } + + // Check cold write cost + gas = ae.VersionGas(testAddr, true) + if want := params.WitnessBranchWriteCost + params.WitnessChunkWriteCost; gas != want { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) + } + + // Check warm write cost + gas = ae.VersionGas(testAddr, true) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + + // Check a write without a read charges both read and write costs + gas = ae.BalanceGas(testAddr2, true) + if want := params.WitnessBranchReadCost + params.WitnessBranchWriteCost + params.WitnessChunkWriteCost + params.WitnessChunkReadCost; gas != want { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) + } + + // Check that a write followed by a read charges nothing + gas = ae.BalanceGas(testAddr2, false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + + // Check that reading a slot from the account header only charges the + // chunk read cost. + gas = ae.SlotGas(testAddr, common.Hash{}, false) + if gas != params.WitnessChunkReadCost { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) + } +} + +// TestContractCreateInitGas checks that the gas cost of contract creation is correctly +// calculated. +func TestContractCreateInitGas(t *testing.T) { + ae := NewAccessEvents(utils.NewPointCache(1024)) + + var testAddr [20]byte + for i := byte(0); i < 20; i++ { + testAddr[i] = i + } + + // Check cold read cost, without a value + gas := ae.ContractCreateInitGas(testAddr, false) + if want := params.WitnessBranchWriteCost + params.WitnessBranchReadCost + params.WitnessChunkWriteCost*2 + params.WitnessChunkReadCost*2; gas != want { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) + } + + // Check warm read cost + gas = ae.ContractCreateInitGas(testAddr, false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } +} + +// TestMessageCallGas checks that the gas cost of message calls is correctly +// calculated. +func TestMessageCallGas(t *testing.T) { + ae := NewAccessEvents(utils.NewPointCache(1024)) + + // Check cold read cost, without a value + gas := ae.MessageCallGas(testAddr) + if want := params.WitnessBranchReadCost + params.WitnessChunkReadCost*2; gas != want { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, want) + } + + // Check that reading the version and code size of the same account does not incur the branch read cost + gas = ae.VersionGas(testAddr, false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + gas = ae.CodeSizeGas(testAddr, false) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } + + // Check warm read cost + gas = ae.MessageCallGas(testAddr) + if gas != 0 { + t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + } +} diff --git a/core/state/access_list.go b/core/state/access_list.go new file mode 100644 index 0000000..90e5590 --- /dev/null +++ b/core/state/access_list.go @@ -0,0 +1,167 @@ +// 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 state + +import ( + "fmt" + "maps" + "slices" + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +type accessList struct { + addresses map[common.Address]int + slots []map[common.Hash]struct{} +} + +// ContainsAddress returns true if the address is in the access list. +func (al *accessList) ContainsAddress(address common.Address) bool { + _, ok := al.addresses[address] + return ok +} + +// Contains checks if a slot within an account is present in the access list, returning +// separate flags for the presence of the account and the slot respectively. +func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { + idx, ok := al.addresses[address] + if !ok { + // no such address (and hence zero slots) + return false, false + } + if idx == -1 { + // address yes, but no slots + return true, false + } + _, slotPresent = al.slots[idx][slot] + return true, slotPresent +} + +// newAccessList creates a new accessList. +func newAccessList() *accessList { + return &accessList{ + addresses: make(map[common.Address]int), + } +} + +// Copy creates an independent copy of an accessList. +func (al *accessList) Copy() *accessList { + cp := newAccessList() + cp.addresses = maps.Clone(al.addresses) + cp.slots = make([]map[common.Hash]struct{}, len(al.slots)) + for i, slotMap := range al.slots { + cp.slots[i] = maps.Clone(slotMap) + } + return cp +} + +// AddAddress adds an address to the access list, and returns 'true' if the operation +// caused a change (addr was not previously in the list). +func (al *accessList) AddAddress(address common.Address) bool { + if _, present := al.addresses[address]; present { + return false + } + al.addresses[address] = -1 + return true +} + +// AddSlot adds the specified (addr, slot) combo to the access list. +// Return values are: +// - address added +// - slot added +// For any 'true' value returned, a corresponding journal entry must be made. +func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { + idx, addrPresent := al.addresses[address] + if !addrPresent || idx == -1 { + // Address not present, or addr present but no slots there + al.addresses[address] = len(al.slots) + slotmap := map[common.Hash]struct{}{slot: {}} + al.slots = append(al.slots, slotmap) + return !addrPresent, true + } + // There is already an (address,slot) mapping + slotmap := al.slots[idx] + if _, ok := slotmap[slot]; !ok { + slotmap[slot] = struct{}{} + // Journal add slot change + return false, true + } + // No changes required + return false, false +} + +// DeleteSlot removes an (address, slot)-tuple from the access list. +// This operation needs to be performed in the same order as the addition happened. +// This method is meant to be used by the journal, which maintains ordering of +// operations. +func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { + idx, addrOk := al.addresses[address] + // There are two ways this can fail + if !addrOk { + panic("reverting slot change, address not present in list") + } + slotmap := al.slots[idx] + delete(slotmap, slot) + // If that was the last (first) slot, remove it + // Since additions and rollbacks are always performed in order, + // we can delete the item without worrying about screwing up later indices + if len(slotmap) == 0 { + al.slots = al.slots[:idx] + al.addresses[address] = -1 + } +} + +// DeleteAddress removes an address from the access list. This operation +// needs to be performed in the same order as the addition happened. +// This method is meant to be used by the journal, which maintains ordering of +// operations. +func (al *accessList) DeleteAddress(address common.Address) { + delete(al.addresses, address) +} + +// Equal returns true if the two access lists are identical +func (al *accessList) Equal(other *accessList) bool { + if !maps.Equal(al.addresses, other.addresses) { + return false + } + return slices.EqualFunc(al.slots, other.slots, + func(m map[common.Hash]struct{}, m2 map[common.Hash]struct{}) bool { + return maps.Equal(m, m2) + }) +} + +// PrettyPrint prints the contents of the access list in a human-readable form +func (al *accessList) PrettyPrint() string { + out := new(strings.Builder) + var sortedAddrs []common.Address + for addr := range al.addresses { + sortedAddrs = append(sortedAddrs, addr) + } + slices.SortFunc(sortedAddrs, common.Address.Cmp) + for _, addr := range sortedAddrs { + idx := al.addresses[addr] + fmt.Fprintf(out, "%#x : (idx %d)\n", addr, idx) + if idx >= 0 { + slotmap := al.slots[idx] + for h := range slotmap { + fmt.Fprintf(out, " %#x\n", h) + } + } + } + return out.String() +} diff --git a/core/state/database.go b/core/state/database.go new file mode 100644 index 0000000..d54417d --- /dev/null +++ b/core/state/database.go @@ -0,0 +1,282 @@ +// 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 state + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/ethereum/go-ethereum/triedb" +) + +const ( + // Number of codehash->size associations to keep. + codeSizeCacheSize = 100000 + + // Cache size granted for caching clean code. + codeCacheSize = 64 * 1024 * 1024 + + // Number of address->curve point associations to keep. + pointCacheSize = 4096 +) + +// Database wraps access to tries and contract code. +type Database interface { + // OpenTrie opens the main account trie. + OpenTrie(root common.Hash) (Trie, error) + + // OpenStorageTrie opens the storage trie of an account. + OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error) + + // CopyTrie returns an independent copy of the given trie. + CopyTrie(Trie) Trie + + // ContractCode retrieves a particular contract's code. + ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) + + // ContractCodeSize retrieves a particular contracts code's size. + ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) + + // DiskDB returns the underlying key-value disk database. + DiskDB() ethdb.KeyValueStore + + // PointCache returns the cache holding points used in verkle tree key computation + PointCache() *utils.PointCache + + // TrieDB returns the underlying trie database for managing trie nodes. + TrieDB() *triedb.Database +} + +// Trie is a Ethereum Merkle Patricia trie. +type Trie interface { + // GetKey returns the sha3 preimage of a hashed key that was previously used + // to store a value. + // + // TODO(fjl): remove this when StateTrie is removed + GetKey([]byte) []byte + + // 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 + // the trie, nil will be returned. If the trie is corrupted(e.g. some nodes + // are missing or the account blob is incorrect for decoding), an error will + // be returned. + GetAccount(address common.Address) (*types.StateAccount, 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 + + // 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 + + // 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. + Hash() common.Hash + + // Commit collects all dirty nodes in the trie and replace them with the + // corresponding node hash. All collected nodes(including dirty leaves if + // collectLeaf is true) will be encapsulated into a nodeset for return. + // The returned nodeset can be nil if the trie is clean(nothing to commit). + // Once the trie is committed, it's not usable anymore. A new trie must + // be created with new root and updated trie database for following usage + Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) + + // Witness returns a set containing all trie nodes that have been accessed. + // The returned map could be nil if the witness is empty. + Witness() map[string]struct{} + + // NodeIterator returns an iterator that returns nodes of the trie. Iteration + // starts at the key after the given start key. And error will be returned + // if fails to create node iterator. + NodeIterator(startKey []byte) (trie.NodeIterator, error) + + // Prove constructs a Merkle proof for key. The result contains all encoded nodes + // on the path to the value at key. The value itself is also included in the last + // 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. + Prove(key []byte, proofDb ethdb.KeyValueWriter) error + + // IsVerkle returns true if the trie is verkle-tree based + IsVerkle() bool +} + +// NewDatabase creates a backing store for state. The returned database is safe for +// concurrent use, but does not retain any recent trie nodes in memory. To keep some +// historical state in memory, use the NewDatabaseWithConfig constructor. +func NewDatabase(db ethdb.Database) Database { + return NewDatabaseWithConfig(db, nil) +} + +// NewDatabaseWithConfig creates a backing store for state. The returned database +// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a +// large memory cache. +func NewDatabaseWithConfig(db ethdb.Database, config *triedb.Config) Database { + return &cachingDB{ + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: triedb.NewDatabase(db, config), + pointCache: utils.NewPointCache(pointCacheSize), + } +} + +// NewDatabaseWithNodeDB creates a state database with an already initialized node database. +func NewDatabaseWithNodeDB(db ethdb.Database, triedb *triedb.Database) Database { + return &cachingDB{ + disk: db, + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + triedb: triedb, + pointCache: utils.NewPointCache(pointCacheSize), + } +} + +type cachingDB struct { + disk ethdb.KeyValueStore + codeSizeCache *lru.Cache[common.Hash, int] + codeCache *lru.SizeConstrainedCache[common.Hash, []byte] + triedb *triedb.Database + pointCache *utils.PointCache +} + +// 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, db.pointCache) + } + tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) + if err != nil { + return nil, err + } + return tr, nil +} + +// OpenStorageTrie opens the storage trie of an account. +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 + } + return tr, nil +} + +// CopyTrie returns an independent copy of the given trie. +func (db *cachingDB) CopyTrie(t Trie) Trie { + switch t := t.(type) { + case *trie.StateTrie: + return t.Copy() + case *trie.VerkleTrie: + return t.Copy() + default: + panic(fmt.Errorf("unknown trie type %T", t)) + } +} + +// ContractCode retrieves a particular contract's code. +func (db *cachingDB) ContractCode(address common.Address, codeHash common.Hash) ([]byte, error) { + code, _ := db.codeCache.Get(codeHash) + if len(code) > 0 { + return code, nil + } + code = rawdb.ReadCode(db.disk, codeHash) + if len(code) > 0 { + db.codeCache.Add(codeHash, code) + db.codeSizeCache.Add(codeHash, len(code)) + return code, nil + } + return nil, errors.New("not found") +} + +// ContractCodeWithPrefix retrieves a particular contract's code. If the +// code can't be found in the cache, then check the existence with **new** +// db scheme. +func (db *cachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) { + code, _ := db.codeCache.Get(codeHash) + if len(code) > 0 { + return code, nil + } + code = rawdb.ReadCodeWithPrefix(db.disk, codeHash) + if len(code) > 0 { + db.codeCache.Add(codeHash, code) + db.codeSizeCache.Add(codeHash, len(code)) + return code, nil + } + return nil, errors.New("not found") +} + +// ContractCodeSize retrieves a particular contracts code's size. +func (db *cachingDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) { + if cached, ok := db.codeSizeCache.Get(codeHash); ok { + return cached, nil + } + code, err := db.ContractCode(addr, codeHash) + return len(code), err +} + +// DiskDB returns the underlying key-value disk database. +func (db *cachingDB) DiskDB() ethdb.KeyValueStore { + return db.disk +} + +// TrieDB retrieves any intermediate trie-node caching layer. +func (db *cachingDB) TrieDB() *triedb.Database { + return db.triedb +} + +// PointCache returns the cache of evaluated curve points. +func (db *cachingDB) PointCache() *utils.PointCache { + return db.pointCache +} diff --git a/core/state/dump.go b/core/state/dump.go new file mode 100644 index 0000000..c9aad4f --- /dev/null +++ b/core/state/dump.go @@ -0,0 +1,234 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "encoding/json" + "fmt" + "time" + + "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/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// DumpConfig is a set of options to control what portions of the state will be +// iterated and collected. +type DumpConfig struct { + SkipCode bool + SkipStorage bool + OnlyWithAddresses bool + Start []byte + Max uint64 +} + +// DumpCollector interface which the state trie calls during iteration +type DumpCollector interface { + // OnRoot is called with the state root + OnRoot(common.Hash) + // OnAccount is called once for each account in the trie + OnAccount(*common.Address, DumpAccount) +} + +// 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 + 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[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 +func (d *Dump) OnRoot(root common.Hash) { + d.Root = fmt.Sprintf("%x", root) +} + +// OnAccount implements DumpCollector interface +func (d *Dump) OnAccount(addr *common.Address, account DumpAccount) { + if addr == nil { + d.Accounts[fmt.Sprintf("pre(%s)", account.AddressHash)] = account + } + if addr != nil { + d.Accounts[(*addr).String()] = account + } +} + +// iterativeDump is a DumpCollector-implementation which dumps output line-by-line iteratively. +type iterativeDump struct { + *json.Encoder +} + +// 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, + AddressHash: account.AddressHash, + Address: addr, + } + d.Encode(dumpAccount) +} + +// OnRoot implements DumpCollector interface +func (d iterativeDump) OnRoot(root common.Hash) { + d.Encode(struct { + Root common.Hash `json:"root"` + }{root}) +} + +// DumpToCollector iterates the state according to the given options and inserts +// the items into a collector for aggregation or serialization. +func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) { + // Sanitize the input to allow nil configs + if conf == nil { + conf = new(DumpConfig) + } + var ( + missingPreimages int + accounts uint64 + start = time.Now() + logged = time.Now() + ) + log.Info("Trie dumping started", "root", s.trie.Hash()) + c.OnRoot(s.trie.Hash()) + + trieIt, err := s.trie.NodeIterator(conf.Start) + if err != nil { + log.Error("Trie dumping error", "err", err) + return nil + } + it := trie.NewIterator(trieIt) + for it.Next() { + var data types.StateAccount + if err := rlp.DecodeBytes(it.Value, &data); err != nil { + panic(err) + } + var ( + 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 { + missingPreimages++ + if conf.OnlyWithAddresses { + continue + } + } else { + addr = common.BytesToAddress(addrBytes) + address = &addr + account.Address = address + } + obj := newObject(s, addr, &data) + if !conf.SkipCode { + account.Code = obj.Code() + } + if !conf.SkipStorage { + account.Storage = make(map[common.Hash]string) + tr, err := obj.getTrie() + if err != nil { + log.Error("Failed to load storage trie", "err", err) + continue + } + trieIt, err := tr.NodeIterator(nil) + if err != nil { + log.Error("Failed to create trie iterator", "err", err) + continue + } + storageIt := trie.NewIterator(trieIt) + for storageIt.Next() { + _, content, _, err := rlp.Split(storageIt.Value) + if err != nil { + log.Error("Failed to decode the value returned by iterator", "error", err) + continue + } + account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content) + } + } + c.OnAccount(address, account) + accounts++ + if time.Since(logged) > 8*time.Second { + log.Info("Trie dumping in progress", "at", it.Key, "accounts", accounts, + "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + if conf.Max > 0 && accounts >= conf.Max { + if it.Next() { + nextKey = it.Key + } + break + } + } + if missingPreimages > 0 { + log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages) + } + log.Info("Trie dumping complete", "accounts", accounts, + "elapsed", common.PrettyDuration(time.Since(start))) + + return nextKey +} + +// 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[string]DumpAccount), + } + dump.Next = s.DumpToCollector(dump, opts) + return *dump +} + +// Dump returns a JSON string representing the entire state as a single json-object +func (s *StateDB) Dump(opts *DumpConfig) []byte { + dump := s.RawDump(opts) + json, err := json.MarshalIndent(dump, "", " ") + if err != nil { + log.Error("Error dumping state", "err", err) + } + return json +} + +// IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout +func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) { + s.DumpToCollector(iterativeDump{output}, opts) +} diff --git a/core/state/iterator.go b/core/state/iterator.go new file mode 100644 index 0000000..83c552c --- /dev/null +++ b/core/state/iterator.go @@ -0,0 +1,171 @@ +// 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 state + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// nodeIterator is an iterator to traverse the entire state trie post-order, +// including all of the contract code and contract state tries. Preimage is +// required in order to resolve the contract address. +type nodeIterator struct { + state *StateDB // State being iterated + + stateIt trie.NodeIterator // Primary iterator for the global state trie + dataIt trie.NodeIterator // Secondary iterator for the data trie of a contract + + accountHash common.Hash // Hash of the node containing the account + codeHash common.Hash // Hash of the contract source code + code []byte // Source code associated with a contract + + Hash common.Hash // Hash of the current entry being iterated (nil if not standalone) + Parent common.Hash // Hash of the first full ancestor node (nil if current is the root) + + Error error // Failure set in case of an internal error in the iterator +} + +// newNodeIterator creates a post-order state node iterator. +func newNodeIterator(state *StateDB) *nodeIterator { + return &nodeIterator{ + state: state, + } +} + +// Next moves the iterator to the next node, returning whether there are any +// further nodes. In case of an internal error this method returns false and +// sets the Error field to the encountered failure. +func (it *nodeIterator) Next() bool { + // If the iterator failed previously, don't do anything + if it.Error != nil { + return false + } + // Otherwise step forward with the iterator and report any errors + if err := it.step(); err != nil { + it.Error = err + return false + } + return it.retrieve() +} + +// step moves the iterator to the next entry of the state trie. +func (it *nodeIterator) step() error { + // Abort if we reached the end of the iteration + if it.state == nil { + return nil + } + // Initialize the iterator if we've just started + var err error + if it.stateIt == nil { + it.stateIt, err = it.state.trie.NodeIterator(nil) + if err != nil { + return err + } + } + // If we had data nodes previously, we surely have at least state nodes + if it.dataIt != nil { + if cont := it.dataIt.Next(true); !cont { + if it.dataIt.Error() != nil { + return it.dataIt.Error() + } + it.dataIt = nil + } + return nil + } + // If we had source code previously, discard that + if it.code != nil { + it.code = nil + return nil + } + // Step to the next state trie node, terminating if we're out of nodes + if cont := it.stateIt.Next(true); !cont { + if it.stateIt.Error() != nil { + return it.stateIt.Error() + } + it.state, it.stateIt = nil, nil + return nil + } + // If the state trie node is an internal entry, leave as is + if !it.stateIt.Leaf() { + return nil + } + // Otherwise we've reached an account node, initiate data iteration + var account types.StateAccount + if err := rlp.DecodeBytes(it.stateIt.LeafBlob(), &account); err != nil { + return err + } + // Lookup the preimage of account hash + preimage := it.state.trie.GetKey(it.stateIt.LeafKey()) + if preimage == nil { + return errors.New("account address is not available") + } + address := common.BytesToAddress(preimage) + + // Traverse the storage slots belong to the account + dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.state.trie) + if err != nil { + return err + } + it.dataIt, err = dataTrie.NodeIterator(nil) + if err != nil { + return err + } + if !it.dataIt.Next(true) { + it.dataIt = nil + } + if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { + it.codeHash = common.BytesToHash(account.CodeHash) + it.code, err = it.state.db.ContractCode(address, common.BytesToHash(account.CodeHash)) + if err != nil { + return fmt.Errorf("code %x: %v", account.CodeHash, err) + } + } + it.accountHash = it.stateIt.Parent() + return nil +} + +// retrieve pulls and caches the current state entry the iterator is traversing. +// The method returns whether there are any more data left for inspection. +func (it *nodeIterator) retrieve() bool { + // Clear out any previously set values + it.Hash = common.Hash{} + + // If the iteration's done, return no available data + if it.state == nil { + return false + } + // Otherwise retrieve the current entry + switch { + case it.dataIt != nil: + it.Hash, it.Parent = it.dataIt.Hash(), it.dataIt.Parent() + if it.Parent == (common.Hash{}) { + it.Parent = it.accountHash + } + case it.code != nil: + it.Hash, it.Parent = it.codeHash, it.accountHash + case it.stateIt != nil: + it.Hash, it.Parent = it.stateIt.Hash(), it.stateIt.Parent() + } + return true +} diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go new file mode 100644 index 0000000..73cc224 --- /dev/null +++ b/core/state/iterator_test.go @@ -0,0 +1,108 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" +) + +// 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, ndb, root, _ := makeTestState(scheme) + ndb.Commit(root, false) + + state, err := New(root, sdb, nil) + if err != nil { + t.Fatalf("failed to create state trie at %x: %v", root, err) + } + // Gather all the node hashes found by the iterator + hashes := make(map[common.Hash]struct{}) + for it := newNodeIterator(state); it.Next(); { + if it.Hash != (common.Hash{}) { + hashes[it.Hash] = struct{}{} + } + } + // Check in-disk nodes + var ( + seenNodes = make(map[common.Hash]struct{}) + seenCodes = make(map[common.Hash]struct{}) + ) + it := db.NewIterator(nil, nil) + for it.Next() { + ok, hash := isTrieNode(scheme, it.Key(), it.Value()) + if !ok { + continue + } + seenNodes[hash] = struct{}{} + } + it.Release() + + // Check in-disk codes + it = db.NewIterator(nil, nil) + for it.Next() { + ok, hash := rawdb.IsCodeKey(it.Key()) + if !ok { + continue + } + if _, ok := hashes[common.BytesToHash(hash)]; !ok { + t.Errorf("state entry not reported %x", it.Key()) + } + seenCodes[common.BytesToHash(hash)] = struct{}{} + } + it.Release() + + // Cross check the iterated hashes and the database/nodepool content + for hash := range hashes { + _, ok := seenNodes[hash] + if !ok { + _, ok = seenCodes[hash] + } + if !ok { + t.Errorf("failed to retrieve reported node %x", hash) + } + } +} + +// isTrieNode is a helper function which reports if the provided +// database entry belongs to a trie node or not. +func isTrieNode(scheme string, key, val []byte) (bool, common.Hash) { + if scheme == rawdb.HashScheme { + if rawdb.IsLegacyTrieNode(key, val) { + return true, common.BytesToHash(key) + } + } else { + ok := rawdb.IsAccountTrieNode(key) + if ok { + return true, crypto.Keccak256Hash(val) + } + ok = rawdb.IsStorageTrieNode(key) + if ok { + return true, crypto.Keccak256Hash(val) + } + } + return false, common.Hash{} +} diff --git a/core/state/journal.go b/core/state/journal.go new file mode 100644 index 0000000..ad4a654 --- /dev/null +++ b/core/state/journal.go @@ -0,0 +1,397 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "maps" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +// journalEntry is a modification entry in the state change journal that can be +// reverted on demand. +type journalEntry interface { + // revert undoes the changes introduced by this journal entry. + revert(*StateDB) + + // dirtied returns the Ethereum address modified by this journal entry. + dirtied() *common.Address + + // copy returns a deep-copied journal entry. + copy() journalEntry +} + +// journal contains the list of state modifications applied since the last state +// commit. These are tracked to be able to be reverted in the case of an execution +// exception or request for reversal. +type journal struct { + entries []journalEntry // Current changes tracked by the journal + dirties map[common.Address]int // Dirty accounts and the number of changes +} + +// newJournal creates a new initialized journal. +func newJournal() *journal { + return &journal{ + dirties: make(map[common.Address]int), + } +} + +// append inserts a new modification entry to the end of the change journal. +func (j *journal) append(entry journalEntry) { + j.entries = append(j.entries, entry) + if addr := entry.dirtied(); addr != nil { + j.dirties[*addr]++ + } +} + +// revert undoes a batch of journalled modifications along with any reverted +// dirty handling too. +func (j *journal) revert(statedb *StateDB, snapshot int) { + for i := len(j.entries) - 1; i >= snapshot; i-- { + // Undo the changes made by the operation + j.entries[i].revert(statedb) + + // Drop any dirty tracking induced by the change + if addr := j.entries[i].dirtied(); addr != nil { + if j.dirties[*addr]--; j.dirties[*addr] == 0 { + delete(j.dirties, *addr) + } + } + } + j.entries = j.entries[:snapshot] +} + +// dirty explicitly sets an address to dirty, even if the change entries would +// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD +// precompile consensus exception. +func (j *journal) dirty(addr common.Address) { + j.dirties[addr]++ +} + +// length returns the current number of entries in the journal. +func (j *journal) length() int { + return len(j.entries) +} + +// copy returns a deep-copied journal. +func (j *journal) copy() *journal { + entries := make([]journalEntry, 0, j.length()) + for i := 0; i < j.length(); i++ { + entries = append(entries, j.entries[i].copy()) + } + return &journal{ + entries: entries, + dirties: maps.Clone(j.dirties), + } +} + +type ( + // Changes to the account trie. + createObjectChange struct { + account *common.Address + } + + // createContractChange represents an account becoming a contract-account. + // This event happens prior to executing initcode. The journal-event simply + // manages the created-flag, in order to allow same-tx destruction. + createContractChange struct { + account common.Address + } + + selfDestructChange struct { + account *common.Address + prev bool // whether account had already self-destructed + prevbalance *uint256.Int + } + + // Changes to individual accounts. + balanceChange struct { + account *common.Address + prev *uint256.Int + } + nonceChange struct { + account *common.Address + prev uint64 + } + storageChange struct { + account *common.Address + key common.Hash + prevvalue common.Hash + origvalue common.Hash + } + codeChange struct { + account *common.Address + prevcode, prevhash []byte + } + + // Changes to other state values. + refundChange struct { + prev uint64 + } + addLogChange struct { + txhash common.Hash + } + addPreimageChange struct { + hash common.Hash + } + touchChange struct { + account *common.Address + } + + // Changes to the access list + accessListAddAccountChange struct { + address *common.Address + } + accessListAddSlotChange struct { + address *common.Address + slot *common.Hash + } + + // Changes to transient storage + transientStorageChange struct { + account *common.Address + key, prevalue common.Hash + } +) + +func (ch createObjectChange) revert(s *StateDB) { + delete(s.stateObjects, *ch.account) +} + +func (ch createObjectChange) dirtied() *common.Address { + return ch.account +} + +func (ch createObjectChange) copy() journalEntry { + return createObjectChange{ + account: ch.account, + } +} + +func (ch createContractChange) revert(s *StateDB) { + s.getStateObject(ch.account).newContract = false +} + +func (ch createContractChange) dirtied() *common.Address { + return nil +} + +func (ch createContractChange) copy() journalEntry { + return createContractChange{ + account: ch.account, + } +} + +func (ch selfDestructChange) revert(s *StateDB) { + obj := s.getStateObject(*ch.account) + if obj != nil { + obj.selfDestructed = ch.prev + obj.setBalance(ch.prevbalance) + } +} + +func (ch selfDestructChange) dirtied() *common.Address { + return ch.account +} + +func (ch selfDestructChange) copy() journalEntry { + return selfDestructChange{ + account: ch.account, + prev: ch.prev, + prevbalance: new(uint256.Int).Set(ch.prevbalance), + } +} + +var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") + +func (ch touchChange) revert(s *StateDB) { +} + +func (ch touchChange) dirtied() *common.Address { + return ch.account +} + +func (ch touchChange) copy() journalEntry { + return touchChange{ + account: ch.account, + } +} + +func (ch balanceChange) revert(s *StateDB) { + s.getStateObject(*ch.account).setBalance(ch.prev) +} + +func (ch balanceChange) dirtied() *common.Address { + return ch.account +} + +func (ch balanceChange) copy() journalEntry { + return balanceChange{ + account: ch.account, + prev: new(uint256.Int).Set(ch.prev), + } +} + +func (ch nonceChange) revert(s *StateDB) { + s.getStateObject(*ch.account).setNonce(ch.prev) +} + +func (ch nonceChange) dirtied() *common.Address { + return ch.account +} + +func (ch nonceChange) copy() journalEntry { + return nonceChange{ + account: ch.account, + prev: ch.prev, + } +} + +func (ch codeChange) revert(s *StateDB) { + s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) +} + +func (ch codeChange) dirtied() *common.Address { + return ch.account +} + +func (ch codeChange) copy() journalEntry { + return codeChange{ + account: ch.account, + prevhash: common.CopyBytes(ch.prevhash), + prevcode: common.CopyBytes(ch.prevcode), + } +} + +func (ch storageChange) revert(s *StateDB) { + s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue, ch.origvalue) +} + +func (ch storageChange) dirtied() *common.Address { + return ch.account +} + +func (ch storageChange) copy() journalEntry { + return storageChange{ + account: ch.account, + key: ch.key, + prevvalue: ch.prevvalue, + } +} + +func (ch transientStorageChange) revert(s *StateDB) { + s.setTransientState(*ch.account, ch.key, ch.prevalue) +} + +func (ch transientStorageChange) dirtied() *common.Address { + return nil +} + +func (ch transientStorageChange) copy() journalEntry { + return transientStorageChange{ + account: ch.account, + key: ch.key, + prevalue: ch.prevalue, + } +} + +func (ch refundChange) revert(s *StateDB) { + s.refund = ch.prev +} + +func (ch refundChange) dirtied() *common.Address { + return nil +} + +func (ch refundChange) copy() journalEntry { + return refundChange{ + prev: ch.prev, + } +} + +func (ch addLogChange) revert(s *StateDB) { + logs := s.logs[ch.txhash] + if len(logs) == 1 { + delete(s.logs, ch.txhash) + } else { + s.logs[ch.txhash] = logs[:len(logs)-1] + } + s.logSize-- +} + +func (ch addLogChange) dirtied() *common.Address { + return nil +} + +func (ch addLogChange) copy() journalEntry { + return addLogChange{ + txhash: ch.txhash, + } +} + +func (ch addPreimageChange) revert(s *StateDB) { + delete(s.preimages, ch.hash) +} + +func (ch addPreimageChange) dirtied() *common.Address { + return nil +} + +func (ch addPreimageChange) copy() journalEntry { + return addPreimageChange{ + hash: ch.hash, + } +} + +func (ch accessListAddAccountChange) revert(s *StateDB) { + /* + One important invariant here, is that whenever a (addr, slot) is added, if the + addr is not already present, the add causes two journal entries: + - one for the address, + - one for the (address,slot) + Therefore, when unrolling the change, we can always blindly delete the + (addr) at this point, since no storage adds can remain when come upon + a single (addr) change. + */ + s.accessList.DeleteAddress(*ch.address) +} + +func (ch accessListAddAccountChange) dirtied() *common.Address { + return nil +} + +func (ch accessListAddAccountChange) copy() journalEntry { + return accessListAddAccountChange{ + address: ch.address, + } +} + +func (ch accessListAddSlotChange) revert(s *StateDB) { + s.accessList.DeleteSlot(*ch.address, *ch.slot) +} + +func (ch accessListAddSlotChange) dirtied() *common.Address { + return nil +} + +func (ch accessListAddSlotChange) copy() journalEntry { + return accessListAddSlotChange{ + address: ch.address, + slot: ch.slot, + } +} diff --git a/core/state/metrics.go b/core/state/metrics.go new file mode 100644 index 0000000..7447e44 --- /dev/null +++ b/core/state/metrics.go @@ -0,0 +1,36 @@ +// 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 state + +import "github.com/ethereum/go-ethereum/metrics" + +var ( + accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil) + storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil) + accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil) + storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil) + accountTrieUpdatedMeter = metrics.NewRegisteredMeter("state/update/accountnodes", nil) + storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil) + accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil) + storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil) + + slotDeletionMaxCount = metrics.NewRegisteredGauge("state/delete/storage/max/slot", nil) + slotDeletionMaxSize = metrics.NewRegisteredGauge("state/delete/storage/max/size", nil) + slotDeletionTimer = metrics.NewRegisteredResettingTimer("state/delete/storage/timer", nil) + slotDeletionCount = metrics.NewRegisteredMeter("state/delete/storage/slot", nil) + slotDeletionSize = metrics.NewRegisteredMeter("state/delete/storage/size", nil) +) diff --git a/core/state/pruner/bloom.go b/core/state/pruner/bloom.go new file mode 100644 index 0000000..dad2b5b --- /dev/null +++ b/core/state/pruner/bloom.go @@ -0,0 +1,125 @@ +// 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 pruner + +import ( + "encoding/binary" + "errors" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/log" + bloomfilter "github.com/holiman/bloomfilter/v2" +) + +// stateBloomHash is used to convert a trie hash or contract code hash into a 64 bit mini hash. +func stateBloomHash(f []byte) uint64 { + return binary.BigEndian.Uint64(f) +} + +// stateBloom is a bloom filter used during the state conversion(snapshot->state). +// The keys of all generated entries will be recorded here so that in the pruning +// stage the entries belong to the specific version can be avoided for deletion. +// +// The false-positive is allowed here. The "false-positive" entries means they +// actually don't belong to the specific version but they are not deleted in the +// pruning. The downside of the false-positive allowance is we may leave some "dangling" +// nodes in the disk. But in practice the it's very unlike the dangling node is +// state root. So in theory this pruned state shouldn't be visited anymore. Another +// potential issue is for fast sync. If we do another fast sync upon the pruned +// database, it's problematic which will stop the expansion during the syncing. +// TODO address it @rjl493456442 @holiman @karalabe. +// +// After the entire state is generated, the bloom filter should be persisted into +// the disk. It indicates the whole generation procedure is finished. +type stateBloom struct { + bloom *bloomfilter.Filter +} + +// newStateBloomWithSize creates a brand new state bloom for state generation. +// The bloom filter will be created by the passing bloom filter size. According +// to the https://hur.st/bloomfilter/?n=600000000&p=&m=2048MB&k=4, the parameters +// are picked so that the false-positive rate for mainnet is low enough. +func newStateBloomWithSize(size uint64) (*stateBloom, error) { + bloom, err := bloomfilter.New(size*1024*1024*8, 4) + if err != nil { + return nil, err + } + log.Info("Initialized state bloom", "size", common.StorageSize(float64(bloom.M()/8))) + return &stateBloom{bloom: bloom}, nil +} + +// NewStateBloomFromDisk loads the state bloom from the given file. +// In this case the assumption is held the bloom filter is complete. +func NewStateBloomFromDisk(filename string) (*stateBloom, error) { + bloom, _, err := bloomfilter.ReadFile(filename) + if err != nil { + return nil, err + } + return &stateBloom{bloom: bloom}, nil +} + +// Commit flushes the bloom filter content into the disk and marks the bloom +// as complete. +func (bloom *stateBloom) Commit(filename, tempname string) error { + // Write the bloom out into a temporary file + _, err := bloom.bloom.WriteFile(tempname) + if err != nil { + return err + } + // Ensure the file is synced to disk + f, err := os.OpenFile(tempname, os.O_RDWR, 0666) + if err != nil { + return err + } + if err := f.Sync(); err != nil { + f.Close() + return err + } + f.Close() + + // Move the temporary file into it's final location + return os.Rename(tempname, filename) +} + +// Put implements the KeyValueWriter interface. But here only the key is needed. +func (bloom *stateBloom) Put(key []byte, value []byte) error { + // If the key length is not 32bytes, ensure it's contract code + // entry with new scheme. + if len(key) != common.HashLength { + isCode, codeKey := rawdb.IsCodeKey(key) + if !isCode { + return errors.New("invalid entry") + } + bloom.bloom.AddHash(stateBloomHash(codeKey)) + return nil + } + bloom.bloom.AddHash(stateBloomHash(key)) + return nil +} + +// Delete removes the key from the key-value data store. +func (bloom *stateBloom) Delete(key []byte) error { panic("not supported") } + +// Contain is the wrapper of the underlying contains function which +// reports whether the key is contained. +// - If it says yes, the key may be contained +// - If it says no, the key is definitely not contained. +func (bloom *stateBloom) Contain(key []byte) bool { + return bloom.bloom.ContainsHash(stateBloomHash(key)) +} diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go new file mode 100644 index 0000000..59c580d --- /dev/null +++ b/core/state/pruner/pruner.go @@ -0,0 +1,493 @@ +// 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 pruner + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math" + "os" + "path/filepath" + "strings" + "time" + + "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/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +const ( + // stateBloomFilePrefix is the filename prefix of state bloom filter. + stateBloomFilePrefix = "statebloom" + + // stateBloomFilePrefix is the filename suffix of state bloom filter. + stateBloomFileSuffix = "bf.gz" + + // stateBloomFileTempSuffix is the filename suffix of state bloom filter + // while it is being written out to detect write aborts. + stateBloomFileTempSuffix = ".tmp" + + // rangeCompactionThreshold is the minimal deleted entry number for + // triggering range compaction. It's a quite arbitrary number but just + // to avoid triggering range compaction because of small deletion. + rangeCompactionThreshold = 100000 +) + +// Config includes all the configurations for pruning. +type Config struct { + Datadir string // The directory of the state database + BloomSize uint64 // The Megabytes of memory allocated to bloom-filter +} + +// Pruner is an offline tool to prune the stale state with the +// help of the snapshot. The workflow of pruner is very simple: +// +// - iterate the snapshot, reconstruct the relevant state +// - iterate the database, delete all other state entries which +// don't belong to the target state and the genesis state +// +// It can take several hours(around 2 hours for mainnet) to finish +// the whole pruning work. It's recommended to run this offline tool +// periodically in order to release the disk usage and improve the +// disk read performance to some extent. +type Pruner struct { + config Config + chainHeader *types.Header + db ethdb.Database + stateBloom *stateBloom + snaptree *snapshot.Tree +} + +// NewPruner creates the pruner instance. +func NewPruner(db ethdb.Database, config Config) (*Pruner, error) { + headBlock := rawdb.ReadHeadBlock(db) + if headBlock == nil { + return nil, errors.New("failed to load head block") + } + // Offline pruning is only supported in legacy hash based scheme. + triedb := triedb.NewDatabase(db, triedb.HashDefaults) + + snapconfig := snapshot.Config{ + CacheSize: 256, + Recovery: false, + NoBuild: true, + AsyncBuild: false, + } + snaptree, err := snapshot.New(snapconfig, db, triedb, headBlock.Root()) + if err != nil { + return nil, err // The relevant snapshot(s) might not exist + } + // Sanitize the bloom filter size if it's too small. + if config.BloomSize < 256 { + log.Warn("Sanitizing bloomfilter size", "provided(MB)", config.BloomSize, "updated(MB)", 256) + config.BloomSize = 256 + } + stateBloom, err := newStateBloomWithSize(config.BloomSize) + if err != nil { + return nil, err + } + return &Pruner{ + config: config, + chainHeader: headBlock.Header(), + db: db, + stateBloom: stateBloom, + snaptree: snaptree, + }, nil +} + +func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, stateBloom *stateBloom, bloomPath string, middleStateRoots map[common.Hash]struct{}, start time.Time) error { + // Delete all stale trie nodes in the disk. With the help of state bloom + // the trie nodes(and codes) belong to the active state will be filtered + // out. A very small part of stale tries will also be filtered because of + // the false-positive rate of bloom filter. But the assumption is held here + // that the false-positive is low enough(~0.05%). The probability of the + // dangling node is the state root is super low. So the dangling nodes in + // theory will never ever be visited again. + var ( + 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() + + // All state entries don't belong to specific state and genesis are deleted here + // - trie node + // - legacy contract code + // - new-scheme contract code + isCode, codeKey := rawdb.IsCodeKey(key) + if len(key) == common.HashLength || isCode { + checkKey := key + if isCode { + checkKey = codeKey + } + if _, exist := middleStateRoots[common.BytesToHash(checkKey)]; exist { + log.Debug("Forcibly delete the middle state roots", "hash", common.BytesToHash(checkKey)) + } else { + if stateBloom.Contain(checkKey) { + skipped += 1 + continue + } + } + count += 1 + size += common.StorageSize(len(key) + len(iter.Value())) + batch.Delete(key) + + var eta time.Duration // Realistically will never remain uninited + if done := binary.BigEndian.Uint64(key[:8]); done > 0 { + var ( + left = math.MaxUint64 - binary.BigEndian.Uint64(key[:8]) + speed = done/uint64(time.Since(pstart)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ) + eta = time.Duration(left/speed) * time.Millisecond + } + if time.Since(logged) > 8*time.Second { + log.Info("Pruning state data", "nodes", count, "skipped", skipped, "size", size, + "elapsed", common.PrettyDuration(time.Since(pstart)), "eta", common.PrettyDuration(eta)) + logged = time.Now() + } + // Recreate the iterator after every batch commit in order + // to allow the underlying compactor to delete the entries. + if batch.ValueSize() >= ethdb.IdealBatchSize { + batch.Write() + batch.Reset() + + iter.Release() + iter = maindb.NewIterator(nil, key) + } + } + } + if batch.ValueSize() > 0 { + batch.Write() + batch.Reset() + } + iter.Release() + log.Info("Pruned state data", "nodes", count, "size", size, "elapsed", common.PrettyDuration(time.Since(pstart))) + + // Pruning is done, now drop the "useless" layers from the snapshot. + // Firstly, flushing the target layer into the disk. After that all + // diff layers below the target will all be merged into the disk. + if err := snaptree.Cap(root, 0); err != nil { + return err + } + // Secondly, flushing the snapshot journal into the disk. All diff + // layers upon are dropped silently. Eventually the entire snapshot + // tree is converted into a single disk layer with the pruning target + // as the root. + if _, err := snaptree.Journal(root); err != nil { + return err + } + // Delete the state bloom, it marks the entire pruning procedure is + // finished. If any crashes or manual exit happens before this, + // `RecoverPruning` will pick it up in the next restarts to redo all + // the things. + os.RemoveAll(bloomPath) + + // Start compactions, will remove the deleted data from the disk immediately. + // Note for small pruning, the compaction is skipped. + if count >= rangeCompactionThreshold { + cstart := time.Now() + for b := 0x00; b <= 0xf0; b += 0x10 { + var ( + start = []byte{byte(b)} + end = []byte{byte(b + 0x10)} + ) + if b == 0xf0 { + end = nil + } + log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) + if err := maindb.Compact(start, end); err != nil { + log.Error("Database compaction failed", "error", err) + return err + } + } + log.Info("Database compaction finished", "elapsed", common.PrettyDuration(time.Since(cstart))) + } + log.Info("State pruning successful", "pruned", size, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// Prune deletes all historical state nodes except the nodes belong to the +// specified state version. If user doesn't specify the state version, use +// the bottom-most snapshot diff layer as the target. +func (p *Pruner) Prune(root common.Hash) error { + // If the state bloom filter is already committed previously, + // reuse it for pruning instead of generating a new one. It's + // mandatory because a part of state may already be deleted, + // the recovery procedure is necessary. + _, stateBloomRoot, err := findBloomFilter(p.config.Datadir) + if err != nil { + return err + } + if stateBloomRoot != (common.Hash{}) { + return RecoverPruning(p.config.Datadir, p.db) + } + // If the target state root is not specified, use the HEAD-127 as the + // target. The reason for picking it is: + // - in most of the normal cases, the related state is available + // - the probability of this layer being reorg is very low + var layers []snapshot.Snapshot + if root == (common.Hash{}) { + // Retrieve all snapshot layers from the current HEAD. + // In theory there are 128 difflayers + 1 disk layer present, + // so 128 diff layers are expected to be returned. + layers = p.snaptree.Snapshots(p.chainHeader.Root, 128, true) + if len(layers) != 128 { + // Reject if the accumulated diff layers are less than 128. It + // means in most of normal cases, there is no associated state + // with bottom-most diff layer. + return fmt.Errorf("snapshot not old enough yet: need %d more blocks", 128-len(layers)) + } + // Use the bottom-most diff layer as the target + root = layers[len(layers)-1].Root() + } + // Ensure the root is really present. The weak assumption + // is the presence of root can indicate the presence of the + // entire trie. + if !rawdb.HasLegacyTrieNode(p.db, root) { + // The special case is for clique based networks(goerli + // and some other private networks), it's possible that two + // consecutive blocks will have same root. In this case snapshot + // difflayer won't be created. So HEAD-127 may not paired with + // head-127 layer. Instead the paired layer is higher than the + // bottom-most diff layer. Try to find the bottom-most snapshot + // layer with state available. + // + // Note HEAD and HEAD-1 is ignored. Usually there is the associated + // state available, but we don't want to use the topmost state + // as the pruning target. + var found bool + for i := len(layers) - 2; i >= 2; i-- { + if rawdb.HasLegacyTrieNode(p.db, layers[i].Root()) { + root = layers[i].Root() + found = true + log.Info("Selecting middle-layer as the pruning target", "root", root, "depth", i) + break + } + } + if !found { + if len(layers) > 0 { + return errors.New("no snapshot paired state") + } + return fmt.Errorf("associated state[%x] is not present", root) + } + } else { + if len(layers) > 0 { + log.Info("Selecting bottom-most difflayer as the pruning target", "root", root, "height", p.chainHeader.Number.Uint64()-127) + } else { + log.Info("Selecting user-specified state as the pruning target", "root", root) + } + } + // All the state roots of the middle layer should be forcibly pruned, + // otherwise the dangling state will be left. + middleRoots := make(map[common.Hash]struct{}) + for _, layer := range layers { + if layer.Root() == root { + break + } + middleRoots[layer.Root()] = struct{}{} + } + // Traverse the target state, re-construct the whole state trie and + // commit to the given bloom filter. + start := time.Now() + if err := snapshot.GenerateTrie(p.snaptree, root, p.db, p.stateBloom); err != nil { + return err + } + // Traverse the genesis, put all genesis state entries into the + // bloom filter too. + if err := extractGenesis(p.db, p.stateBloom); err != nil { + return err + } + filterName := bloomFilterName(p.config.Datadir, root) + + log.Info("Writing state bloom to disk", "name", filterName) + if err := p.stateBloom.Commit(filterName, filterName+stateBloomFileTempSuffix); err != nil { + return err + } + log.Info("State bloom filter committed", "name", filterName) + return prune(p.snaptree, root, p.db, p.stateBloom, filterName, middleRoots, start) +} + +// RecoverPruning will resume the pruning procedure during the system restart. +// This function is used in this case: user tries to prune state data, but the +// system was interrupted midway because of crash or manual-kill. In this case +// if the bloom filter for filtering active state is already constructed, the +// pruning can be resumed. What's more if the bloom filter is constructed, the +// pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left +// in the disk. +func RecoverPruning(datadir string, db ethdb.Database) error { + stateBloomPath, stateBloomRoot, err := findBloomFilter(datadir) + if err != nil { + return err + } + if stateBloomPath == "" { + return nil // nothing to recover + } + headBlock := rawdb.ReadHeadBlock(db) + if headBlock == nil { + return errors.New("failed to load head block") + } + // Initialize the snapshot tree in recovery mode to handle this special case: + // - Users run the `prune-state` command multiple times + // - Neither these `prune-state` running is finished(e.g. interrupted manually) + // - The state bloom filter is already generated, a part of state is deleted, + // so that resuming the pruning here is mandatory + // - The state HEAD is rewound already because of multiple incomplete `prune-state` + // In this case, even the state HEAD is not exactly matched with snapshot, it + // still feasible to recover the pruning correctly. + snapconfig := snapshot.Config{ + CacheSize: 256, + Recovery: true, + NoBuild: true, + AsyncBuild: false, + } + // Offline pruning is only supported in legacy hash based scheme. + triedb := triedb.NewDatabase(db, triedb.HashDefaults) + snaptree, err := snapshot.New(snapconfig, db, triedb, headBlock.Root()) + if err != nil { + return err // The relevant snapshot(s) might not exist + } + stateBloom, err := NewStateBloomFromDisk(stateBloomPath) + if err != nil { + return err + } + log.Info("Loaded state bloom filter", "path", stateBloomPath) + + // All the state roots of the middle layers should be forcibly pruned, + // otherwise the dangling state will be left. + var ( + found bool + layers = snaptree.Snapshots(headBlock.Root(), 128, true) + middleRoots = make(map[common.Hash]struct{}) + ) + for _, layer := range layers { + if layer.Root() == stateBloomRoot { + found = true + break + } + middleRoots[layer.Root()] = struct{}{} + } + if !found { + log.Error("Pruning target state is not existent") + return errors.New("non-existent target state") + } + return prune(snaptree, stateBloomRoot, db, stateBloom, stateBloomPath, middleRoots, time.Now()) +} + +// extractGenesis loads the genesis state and commits all the state entries +// into the given bloomfilter. +func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { + genesisHash := rawdb.ReadCanonicalHash(db, 0) + if genesisHash == (common.Hash{}) { + return errors.New("missing genesis hash") + } + genesis := rawdb.ReadBlock(db, genesisHash, 0) + if genesis == nil { + return errors.New("missing genesis block") + } + t, err := trie.NewStateTrie(trie.StateTrieID(genesis.Root()), triedb.NewDatabase(db, triedb.HashDefaults)) + if err != nil { + return err + } + accIter, err := t.NodeIterator(nil) + if err != nil { + return err + } + for accIter.Next(true) { + hash := accIter.Hash() + + // Embedded nodes don't have hash. + if hash != (common.Hash{}) { + stateBloom.Put(hash.Bytes(), nil) + } + // If it's a leaf node, yes we are touching an account, + // dig into the storage trie further. + if accIter.Leaf() { + var acc types.StateAccount + if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { + return err + } + if acc.Root != types.EmptyRootHash { + id := trie.StorageTrieID(genesis.Root(), common.BytesToHash(accIter.LeafKey()), acc.Root) + storageTrie, err := trie.NewStateTrie(id, triedb.NewDatabase(db, triedb.HashDefaults)) + if err != nil { + return err + } + storageIter, err := storageTrie.NodeIterator(nil) + if err != nil { + return err + } + for storageIter.Next(true) { + hash := storageIter.Hash() + if hash != (common.Hash{}) { + stateBloom.Put(hash.Bytes(), nil) + } + } + if storageIter.Error() != nil { + return storageIter.Error() + } + } + if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) { + stateBloom.Put(acc.CodeHash, nil) + } + } + } + return accIter.Error() +} + +func bloomFilterName(datadir string, hash common.Hash) string { + return filepath.Join(datadir, fmt.Sprintf("%s.%s.%s", stateBloomFilePrefix, hash.Hex(), stateBloomFileSuffix)) +} + +func isBloomFilter(filename string) (bool, common.Hash) { + filename = filepath.Base(filename) + if strings.HasPrefix(filename, stateBloomFilePrefix) && strings.HasSuffix(filename, stateBloomFileSuffix) { + return true, common.HexToHash(filename[len(stateBloomFilePrefix)+1 : len(filename)-len(stateBloomFileSuffix)-1]) + } + return false, common.Hash{} +} + +func findBloomFilter(datadir string) (string, common.Hash, error) { + var ( + stateBloomPath string + stateBloomRoot common.Hash + ) + if err := filepath.Walk(datadir, func(path string, info os.FileInfo, err error) error { + if info != nil && !info.IsDir() { + ok, root := isBloomFilter(path) + if ok { + stateBloomPath = path + stateBloomRoot = root + } + } + return nil + }); err != nil { + return "", common.Hash{}, err + } + return stateBloomPath, stateBloomRoot, nil +} diff --git a/core/state/snapshot/context.go b/core/state/snapshot/context.go new file mode 100644 index 0000000..8a19960 --- /dev/null +++ b/core/state/snapshot/context.go @@ -0,0 +1,241 @@ +// 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 snapshot + +import ( + "bytes" + "encoding/binary" + "errors" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/log" +) + +const ( + snapAccount = "account" // Identifier of account snapshot generation + snapStorage = "storage" // Identifier of storage snapshot generation +) + +// generatorStats is a collection of statistics gathered by the snapshot generator +// for logging purposes. +type generatorStats struct { + origin uint64 // Origin prefix where generation started + start time.Time // Timestamp when generation started + accounts uint64 // Number of accounts indexed(generated or recovered) + slots uint64 // Number of storage slots indexed(generated or recovered) + dangling uint64 // Number of dangling storage slots + storage common.StorageSize // Total account and storage slot size(generation or recovery) +} + +// Log creates a contextual log with the given message and the context pulled +// from the internally maintained statistics. +func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) { + var ctx []interface{} + if root != (common.Hash{}) { + ctx = append(ctx, []interface{}{"root", root}...) + } + // Figure out whether we're after or within an account + switch len(marker) { + case common.HashLength: + ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...) + case 2 * common.HashLength: + ctx = append(ctx, []interface{}{ + "in", common.BytesToHash(marker[:common.HashLength]), + "at", common.BytesToHash(marker[common.HashLength:]), + }...) + } + // Add the usual measurements + ctx = append(ctx, []interface{}{ + "accounts", gs.accounts, + "slots", gs.slots, + "storage", gs.storage, + "dangling", gs.dangling, + "elapsed", common.PrettyDuration(time.Since(gs.start)), + }...) + // Calculate the estimated indexing time based on current stats + if len(marker) > 0 { + if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 { + left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8]) + + speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ctx = append(ctx, []interface{}{ + "eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond), + }...) + } + } + log.Info(msg, ctx...) +} + +// generatorContext carries a few global values to be shared by all generation functions. +type generatorContext struct { + stats *generatorStats // Generation statistic collection + db ethdb.KeyValueStore // Key-value store containing the snapshot data + account *holdableIterator // Iterator of account snapshot data + storage *holdableIterator // Iterator of storage snapshot data + batch ethdb.Batch // Database batch for writing batch data atomically + logged time.Time // The timestamp when last generation progress was displayed +} + +// newGeneratorContext initializes the context for generation. +func newGeneratorContext(stats *generatorStats, db ethdb.KeyValueStore, accMarker []byte, storageMarker []byte) *generatorContext { + ctx := &generatorContext{ + stats: stats, + db: db, + batch: db.NewBatch(), + logged: time.Now(), + } + ctx.openIterator(snapAccount, accMarker) + ctx.openIterator(snapStorage, storageMarker) + return ctx +} + +// openIterator constructs global account and storage snapshot iterators +// at the interrupted position. These iterators should be reopened from time +// to time to avoid blocking leveldb compaction for a long time. +func (ctx *generatorContext) openIterator(kind string, start []byte) { + if kind == snapAccount { + iter := ctx.db.NewIterator(rawdb.SnapshotAccountPrefix, start) + ctx.account = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+common.HashLength)) + return + } + iter := ctx.db.NewIterator(rawdb.SnapshotStoragePrefix, start) + ctx.storage = newHoldableIterator(rawdb.NewKeyLengthIterator(iter, 1+2*common.HashLength)) +} + +// reopenIterator releases the specified snapshot iterator and re-open it +// in the next position. It's aimed for not blocking leveldb compaction. +func (ctx *generatorContext) reopenIterator(kind string) { + // Shift iterator one more step, so that we can reopen + // the iterator at the right position. + var iter = ctx.account + if kind == snapStorage { + iter = ctx.storage + } + hasNext := iter.Next() + if !hasNext { + // Iterator exhausted, release forever and create an already exhausted virtual iterator + iter.Release() + if kind == snapAccount { + ctx.account = newHoldableIterator(memorydb.New().NewIterator(nil, nil)) + return + } + ctx.storage = newHoldableIterator(memorydb.New().NewIterator(nil, nil)) + return + } + next := iter.Key() + iter.Release() + ctx.openIterator(kind, next[1:]) +} + +// close releases all the held resources. +func (ctx *generatorContext) close() { + ctx.account.Release() + ctx.storage.Release() +} + +// iterator returns the corresponding iterator specified by the kind. +func (ctx *generatorContext) iterator(kind string) *holdableIterator { + if kind == snapAccount { + return ctx.account + } + return ctx.storage +} + +// removeStorageBefore deletes all storage entries which are located before +// the specified account. When the iterator touches the storage entry which +// is located in or outside the given account, it stops and holds the current +// iterated element locally. +func (ctx *generatorContext) removeStorageBefore(account common.Hash) { + var ( + count uint64 + start = time.Now() + iter = ctx.storage + ) + for iter.Next() { + key := iter.Key() + if bytes.Compare(key[1:1+common.HashLength], account.Bytes()) >= 0 { + iter.Hold() + break + } + count++ + ctx.batch.Delete(key) + if ctx.batch.ValueSize() > ethdb.IdealBatchSize { + ctx.batch.Write() + ctx.batch.Reset() + } + } + ctx.stats.dangling += count + snapStorageCleanCounter.Inc(time.Since(start).Nanoseconds()) +} + +// removeStorageAt deletes all storage entries which are located in the specified +// account. When the iterator touches the storage entry which is outside the given +// account, it stops and holds the current iterated element locally. An error will +// be returned if the initial position of iterator is not in the given account. +func (ctx *generatorContext) removeStorageAt(account common.Hash) error { + var ( + count int64 + start = time.Now() + iter = ctx.storage + ) + for iter.Next() { + key := iter.Key() + cmp := bytes.Compare(key[1:1+common.HashLength], account.Bytes()) + if cmp < 0 { + return errors.New("invalid iterator position") + } + if cmp > 0 { + iter.Hold() + break + } + count++ + ctx.batch.Delete(key) + if ctx.batch.ValueSize() > ethdb.IdealBatchSize { + ctx.batch.Write() + ctx.batch.Reset() + } + } + snapWipedStorageMeter.Mark(count) + snapStorageCleanCounter.Inc(time.Since(start).Nanoseconds()) + return nil +} + +// removeStorageLeft deletes all storage entries which are located after +// the current iterator position. +func (ctx *generatorContext) removeStorageLeft() { + var ( + count uint64 + start = time.Now() + iter = ctx.storage + ) + for iter.Next() { + count++ + ctx.batch.Delete(iter.Key()) + if ctx.batch.ValueSize() > ethdb.IdealBatchSize { + ctx.batch.Write() + ctx.batch.Reset() + } + } + ctx.stats.dangling += count + snapDanglingStorageMeter.Mark(int64(count)) + snapStorageCleanCounter.Inc(time.Since(start).Nanoseconds()) +} diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go new file mode 100644 index 0000000..8a0fd19 --- /dev/null +++ b/core/state/snapshot/conversion.go @@ -0,0 +1,376 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "runtime" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// trieKV represents a trie key-value pair +type trieKV struct { + key common.Hash + value []byte +} + +type ( + // trieGeneratorFn is the interface of trie generation which can + // be implemented by different trie algorithm. + trieGeneratorFn func(db ethdb.KeyValueWriter, scheme string, owner common.Hash, in chan (trieKV), out chan (common.Hash)) + + // leafCallbackFn is the callback invoked at the leaves of the trie, + // returns the subtrie root with the specified subtrie identifier. + leafCallbackFn func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) +) + +// GenerateAccountTrieRoot takes an account iterator and reproduces the root hash. +func GenerateAccountTrieRoot(it AccountIterator) (common.Hash, error) { + return generateTrieRoot(nil, "", it, common.Hash{}, stackTrieGenerate, nil, newGenerateStats(), true) +} + +// GenerateStorageTrieRoot takes a storage iterator and reproduces the root hash. +func GenerateStorageTrieRoot(account common.Hash, it StorageIterator) (common.Hash, error) { + return generateTrieRoot(nil, "", it, account, stackTrieGenerate, nil, newGenerateStats(), true) +} + +// GenerateTrie takes the whole snapshot tree as the input, traverses all the +// accounts as well as the corresponding storages and regenerate the whole state +// (account trie + all storage tries). +func GenerateTrie(snaptree *Tree, root common.Hash, src ethdb.Database, dst ethdb.KeyValueWriter) error { + // Traverse all state by snapshot, re-generate the whole state trie + acctIt, err := snaptree.AccountIterator(root, common.Hash{}) + if err != nil { + return err // The required snapshot might not exist. + } + defer acctIt.Release() + + scheme := snaptree.triedb.Scheme() + got, err := generateTrieRoot(dst, scheme, acctIt, common.Hash{}, stackTrieGenerate, func(dst ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + // Migrate the code first, commit the contract code into the tmp db. + if codeHash != types.EmptyCodeHash { + code := rawdb.ReadCode(src, codeHash) + if len(code) == 0 { + return common.Hash{}, errors.New("failed to read contract code") + } + rawdb.WriteCode(dst, codeHash, code) + } + // Then migrate all storage trie nodes into the tmp db. + storageIt, err := snaptree.StorageIterator(root, accountHash, common.Hash{}) + if err != nil { + return common.Hash{}, err + } + defer storageIt.Release() + + hash, err := generateTrieRoot(dst, scheme, storageIt, accountHash, stackTrieGenerate, nil, stat, false) + if err != nil { + return common.Hash{}, err + } + return hash, nil + }, newGenerateStats(), true) + + if err != nil { + return err + } + if got != root { + return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root) + } + return nil +} + +// generateStats is a collection of statistics gathered by the trie generator +// for logging purposes. +type generateStats struct { + head common.Hash + start time.Time + + accounts uint64 // Number of accounts done (including those being crawled) + slots uint64 // Number of storage slots done (including those being crawled) + + slotsStart map[common.Hash]time.Time // Start time for account slot crawling + slotsHead map[common.Hash]common.Hash // Slot head for accounts being crawled + + lock sync.RWMutex +} + +// newGenerateStats creates a new generator stats. +func newGenerateStats() *generateStats { + return &generateStats{ + slotsStart: make(map[common.Hash]time.Time), + slotsHead: make(map[common.Hash]common.Hash), + start: time.Now(), + } +} + +// progressAccounts updates the generator stats for the account range. +func (stat *generateStats) progressAccounts(account common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.accounts += done + stat.head = account +} + +// finishAccounts updates the generator stats for the finished account range. +func (stat *generateStats) finishAccounts(done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.accounts += done +} + +// progressContract updates the generator stats for a specific in-progress contract. +func (stat *generateStats) progressContract(account common.Hash, slot common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.slots += done + stat.slotsHead[account] = slot + if _, ok := stat.slotsStart[account]; !ok { + stat.slotsStart[account] = time.Now() + } +} + +// finishContract updates the generator stats for a specific just-finished contract. +func (stat *generateStats) finishContract(account common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.slots += done + delete(stat.slotsHead, account) + delete(stat.slotsStart, account) +} + +// report prints the cumulative progress statistic smartly. +func (stat *generateStats) report() { + stat.lock.RLock() + defer stat.lock.RUnlock() + + ctx := []interface{}{ + "accounts", stat.accounts, + "slots", stat.slots, + "elapsed", common.PrettyDuration(time.Since(stat.start)), + } + if stat.accounts > 0 { + // If there's progress on the account trie, estimate the time to finish crawling it + if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 { + var ( + left = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts + speed = done/uint64(time.Since(stat.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + eta = time.Duration(left/speed) * time.Millisecond + ) + // If there are large contract crawls in progress, estimate their finish time + for acc, head := range stat.slotsHead { + start := stat.slotsStart[acc] + if done := binary.BigEndian.Uint64(head[:8]); done > 0 { + var ( + left = math.MaxUint64 - binary.BigEndian.Uint64(head[:8]) + speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ) + // Override the ETA if larger than the largest until now + if slotETA := time.Duration(left/speed) * time.Millisecond; eta < slotETA { + eta = slotETA + } + } + } + ctx = append(ctx, []interface{}{ + "eta", common.PrettyDuration(eta), + }...) + } + } + log.Info("Iterating state snapshot", ctx...) +} + +// reportDone prints the last log when the whole generation is finished. +func (stat *generateStats) reportDone() { + stat.lock.RLock() + defer stat.lock.RUnlock() + + var ctx []interface{} + ctx = append(ctx, []interface{}{"accounts", stat.accounts}...) + if stat.slots != 0 { + ctx = append(ctx, []interface{}{"slots", stat.slots}...) + } + ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) + log.Info("Iterated snapshot", ctx...) +} + +// runReport periodically prints the progress information. +func runReport(stats *generateStats, stop chan bool) { + timer := time.NewTimer(0) + defer timer.Stop() + + for { + select { + case <-timer.C: + stats.report() + timer.Reset(time.Second * 8) + case success := <-stop: + if success { + stats.reportDone() + } + return + } + } +} + +// generateTrieRoot generates the trie hash based on the snapshot iterator. +// It can be used for generating account trie, storage trie or even the +// whole state which connects the accounts and the corresponding storages. +func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { + var ( + in = make(chan trieKV) // chan to pass leaves + out = make(chan common.Hash, 1) // chan to collect result + stoplog = make(chan bool, 1) // 1-size buffer, works when logging is not enabled + wg sync.WaitGroup + ) + // Spin up a go-routine for trie hash re-generation + wg.Add(1) + go func() { + defer wg.Done() + generatorFn(db, scheme, account, in, out) + }() + // Spin up a go-routine for progress logging + if report && stats != nil { + wg.Add(1) + go func() { + defer wg.Done() + runReport(stats, stoplog) + }() + } + // Create a semaphore to assign tasks and collect results through. We'll pre- + // fill it with nils, thus using the same channel for both limiting concurrent + // processing and gathering results. + threads := runtime.NumCPU() + results := make(chan error, threads) + for i := 0; i < threads; i++ { + results <- nil // fill the semaphore + } + // stop is a helper function to shutdown the background threads + // and return the re-generated trie hash. + stop := func(fail error) (common.Hash, error) { + close(in) + result := <-out + for i := 0; i < threads; i++ { + if err := <-results; err != nil && fail == nil { + fail = err + } + } + stoplog <- fail == nil + + wg.Wait() + return result, fail + } + var ( + logged = time.Now() + processed = uint64(0) + leaf trieKV + ) + // Start to feed leaves + for it.Next() { + if account == (common.Hash{}) { + var ( + err error + fullData []byte + ) + if leafCallback == nil { + fullData, err = types.FullAccountRLP(it.(AccountIterator).Account()) + if err != nil { + return stop(err) + } + } else { + // Wait until the semaphore allows us to continue, aborting if + // a sub-task failed + if err := <-results; err != nil { + results <- nil // stop will drain the results, add a noop back for this error we just consumed + return stop(err) + } + // Fetch the next account and process it concurrently + account, err := types.FullAccount(it.(AccountIterator).Account()) + if err != nil { + return stop(err) + } + go func(hash common.Hash) { + subroot, err := leafCallback(db, hash, common.BytesToHash(account.CodeHash), stats) + if err != nil { + results <- err + return + } + if account.Root != subroot { + results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) + return + } + results <- nil + }(it.Hash()) + fullData, err = rlp.EncodeToBytes(account) + if err != nil { + return stop(err) + } + } + leaf = trieKV{it.Hash(), fullData} + } else { + leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())} + } + in <- leaf + + // Accumulate the generation statistic if it's required. + processed++ + if time.Since(logged) > 3*time.Second && stats != nil { + if account == (common.Hash{}) { + stats.progressAccounts(it.Hash(), processed) + } else { + stats.progressContract(account, it.Hash(), processed) + } + logged, processed = time.Now(), 0 + } + } + // Commit the last part statistic. + if processed > 0 && stats != nil { + if account == (common.Hash{}) { + stats.finishAccounts(processed) + } else { + stats.finishContract(account, processed) + } + } + return stop(nil) +} + +func stackTrieGenerate(db ethdb.KeyValueWriter, scheme string, owner common.Hash, in chan trieKV, out chan common.Hash) { + var onTrieNode trie.OnTrieNode + if db != nil { + onTrieNode = func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(db, owner, path, hash, blob, scheme) + } + } + t := trie.NewStackTrie(onTrieNode) + for leaf := range in { + t.Update(leaf.key[:], leaf.value) + } + out <- t.Hash() +} diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go new file mode 100644 index 0000000..779c1ea --- /dev/null +++ b/core/state/snapshot/difflayer.go @@ -0,0 +1,543 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "encoding/binary" + "fmt" + "math" + "math/rand" + "slices" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + bloomfilter "github.com/holiman/bloomfilter/v2" +) + +var ( + // aggregatorMemoryLimit is the maximum size of the bottom-most diff layer + // that aggregates the writes from above until it's flushed into the disk + // layer. + // + // Note, bumping this up might drastically increase the size of the bloom + // filters that's stored in every diff layer. Don't do that without fully + // understanding all the implications. + aggregatorMemoryLimit = uint64(4 * 1024 * 1024) + + // aggregatorItemLimit is an approximate number of items that will end up + // in the aggregator layer before it's flushed out to disk. A plain account + // weighs around 14B (+hash), a storage slot 32B (+hash), a deleted slot + // 0B (+hash). Slots are mostly set/unset in lockstep, so that average at + // 16B (+hash). All in all, the average entry seems to be 15+32=47B. Use a + // smaller number to be on the safe side. + aggregatorItemLimit = aggregatorMemoryLimit / 42 + + // bloomTargetError is the target false positive rate when the aggregator + // layer is at its fullest. The actual value will probably move around up + // and down from this number, it's mostly a ballpark figure. + // + // Note, dropping this down might drastically increase the size of the bloom + // filters that's stored in every diff layer. Don't do that without fully + // understanding all the implications. + bloomTargetError = 0.02 + + // bloomSize is the ideal bloom filter size given the maximum number of items + // it's expected to hold and the target false positive error rate. + bloomSize = math.Ceil(float64(aggregatorItemLimit) * math.Log(bloomTargetError) / math.Log(1/math.Pow(2, math.Log(2)))) + + // bloomFuncs is the ideal number of bits a single entry should set in the + // bloom filter to keep its size to a minimum (given it's size and maximum + // entry count). + bloomFuncs = math.Round((bloomSize / float64(aggregatorItemLimit)) * math.Log(2)) + + // the bloom offsets are runtime constants which determines which part of the + // account/storage hash the hasher functions looks at, to determine the + // bloom key for an account/slot. This is randomized at init(), so that the + // global population of nodes do not all display the exact same behaviour with + // regards to bloom content + bloomDestructHasherOffset = 0 + bloomAccountHasherOffset = 0 + bloomStorageHasherOffset = 0 +) + +func init() { + // Init the bloom offsets in the range [0:24] (requires 8 bytes) + bloomDestructHasherOffset = rand.Intn(25) + bloomAccountHasherOffset = rand.Intn(25) + bloomStorageHasherOffset = rand.Intn(25) + + // The destruct and account blooms must be different, as the storage slots + // will check for destruction too for every bloom miss. It should not collide + // with modified accounts. + for bloomAccountHasherOffset == bloomDestructHasherOffset { + bloomAccountHasherOffset = rand.Intn(25) + } +} + +// diffLayer represents a collection of modifications made to a state snapshot +// after running a block on top. It contains one sorted list for the account trie +// and one-one list for each storage tries. +// +// The goal of a diff layer is to act as a journal, tracking recent modifications +// made to the state, that have not yet graduated into a semi-immutable state. +type diffLayer struct { + origin *diskLayer // Base disk layer to directly use on bloom misses + parent snapshot // Parent snapshot modified by this one, never nil + memory uint64 // Approximate guess as to how much memory we use + + root common.Hash // Root hash to which this snapshot diff belongs to + stale atomic.Bool // Signals that the layer became stale (state progressed) + + // destructSet is a very special helper marker. If an account is marked as + // deleted, then it's recorded in this set. However it's allowed that an account + // is included here but still available in other sets(e.g. storageData). The + // reason is the diff layer includes all the changes in a *block*. It can + // happen that in the tx_1, account A is self-destructed while in the tx_2 + // it's recreated. But we still need this marker to indicate the "old" A is + // deleted, all data in other set belongs to the "new" A. + destructSet map[common.Hash]struct{} // Keyed markers for deleted (and potentially) recreated accounts + accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil + accountData map[common.Hash][]byte // Keyed accounts for direct retrieval (nil means deleted) + storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil + storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrieval. one per account (nil means deleted) + + diffed *bloomfilter.Filter // Bloom filter tracking all the diffed items up to the disk layer + + lock sync.RWMutex +} + +// destructBloomHash is used to convert a destruct event into a 64 bit mini hash. +func destructBloomHash(h common.Hash) uint64 { + return binary.BigEndian.Uint64(h[bloomDestructHasherOffset : bloomDestructHasherOffset+8]) +} + +// accountBloomHash is used to convert an account hash into a 64 bit mini hash. +func accountBloomHash(h common.Hash) uint64 { + return binary.BigEndian.Uint64(h[bloomAccountHasherOffset : bloomAccountHasherOffset+8]) +} + +// storageBloomHash is used to convert an account hash and a storage hash into a 64 bit mini hash. +func storageBloomHash(h0, h1 common.Hash) uint64 { + return binary.BigEndian.Uint64(h0[bloomStorageHasherOffset:bloomStorageHasherOffset+8]) ^ + binary.BigEndian.Uint64(h1[bloomStorageHasherOffset:bloomStorageHasherOffset+8]) +} + +// newDiffLayer creates a new diff on top of an existing snapshot, whether that's a low +// level persistent database or a hierarchical diff already. +func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + // Create the new layer with some pre-allocated data segments + dl := &diffLayer{ + parent: parent, + root: root, + destructSet: destructs, + accountData: accounts, + storageData: storage, + storageList: make(map[common.Hash][]common.Hash), + } + switch parent := parent.(type) { + case *diskLayer: + dl.rebloom(parent) + case *diffLayer: + dl.rebloom(parent.origin) + default: + panic("unknown parent type") + } + // Sanity check that accounts or storage slots are never nil + for accountHash, blob := range accounts { + if blob == nil { + panic(fmt.Sprintf("account %#x nil", accountHash)) + } + // Determine memory size and track the dirty writes + dl.memory += uint64(common.HashLength + len(blob)) + snapshotDirtyAccountWriteMeter.Mark(int64(len(blob))) + } + for accountHash, slots := range storage { + if slots == nil { + panic(fmt.Sprintf("storage %#x nil", accountHash)) + } + // Determine memory size and track the dirty writes + for _, data := range slots { + dl.memory += uint64(common.HashLength + len(data)) + snapshotDirtyStorageWriteMeter.Mark(int64(len(data))) + } + } + dl.memory += uint64(len(destructs) * common.HashLength) + return dl +} + +// rebloom discards the layer's current bloom and rebuilds it from scratch based +// on the parent's and the local diffs. +func (dl *diffLayer) rebloom(origin *diskLayer) { + dl.lock.Lock() + defer dl.lock.Unlock() + + defer func(start time.Time) { + snapshotBloomIndexTimer.Update(time.Since(start)) + }(time.Now()) + + // Inject the new origin that triggered the rebloom + dl.origin = origin + + // Retrieve the parent bloom or create a fresh empty one + if parent, ok := dl.parent.(*diffLayer); ok { + parent.lock.RLock() + dl.diffed, _ = parent.diffed.Copy() + parent.lock.RUnlock() + } else { + dl.diffed, _ = bloomfilter.New(uint64(bloomSize), uint64(bloomFuncs)) + } + // Iterate over all the accounts and storage slots and index them + for hash := range dl.destructSet { + dl.diffed.AddHash(destructBloomHash(hash)) + } + for hash := range dl.accountData { + dl.diffed.AddHash(accountBloomHash(hash)) + } + for accountHash, slots := range dl.storageData { + for storageHash := range slots { + dl.diffed.AddHash(storageBloomHash(accountHash, storageHash)) + } + } + // Calculate the current false positive rate and update the error rate meter. + // This is a bit cheating because subsequent layers will overwrite it, but it + // should be fine, we're only interested in ballpark figures. + k := float64(dl.diffed.K()) + n := float64(dl.diffed.N()) + m := float64(dl.diffed.M()) + snapshotBloomErrorGauge.Update(math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k)) +} + +// Root returns the root hash for which this snapshot was made. +func (dl *diffLayer) Root() common.Hash { + return dl.root +} + +// Parent returns the subsequent layer of a diff layer. +func (dl *diffLayer) Parent() snapshot { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.parent +} + +// Stale return whether this layer has become stale (was flattened across) or if +// it's still live. +func (dl *diffLayer) Stale() bool { + return dl.stale.Load() +} + +// Account directly retrieves the account associated with a particular hash in +// the snapshot slim data format. +func (dl *diffLayer) Account(hash common.Hash) (*types.SlimAccount, error) { + data, err := dl.AccountRLP(hash) + if err != nil { + return nil, err + } + if len(data) == 0 { // can be both nil and []byte{} + return nil, nil + } + account := new(types.SlimAccount) + if err := rlp.DecodeBytes(data, account); err != nil { + panic(err) + } + return account, nil +} + +// AccountRLP directly retrieves the account RLP associated with a particular +// hash in the snapshot slim data format. +// +// Note the returned account is not a copy, please don't modify it. +func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { + // Check staleness before reaching further. + dl.lock.RLock() + if dl.Stale() { + dl.lock.RUnlock() + return nil, ErrSnapshotStale + } + // Check the bloom filter first whether there's even a point in reaching into + // all the maps in all the layers below + hit := dl.diffed.ContainsHash(accountBloomHash(hash)) + if !hit { + hit = dl.diffed.ContainsHash(destructBloomHash(hash)) + } + var origin *diskLayer + if !hit { + origin = dl.origin // extract origin while holding the lock + } + dl.lock.RUnlock() + + // If the bloom filter misses, don't even bother with traversing the memory + // diff layers, reach straight into the bottom persistent disk layer + if origin != nil { + snapshotBloomAccountMissMeter.Mark(1) + return origin.AccountRLP(hash) + } + // The bloom filter hit, start poking in the internal maps + return dl.accountRLP(hash, 0) +} + +// accountRLP is an internal version of AccountRLP that skips the bloom filter +// checks and uses the internal maps to try and retrieve the data. It's meant +// to be used if a higher layer's bloom filter hit already. +func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the layer was flattened into, consider it invalid (any live reference to + // the original should be marked as unusable). + if dl.Stale() { + return nil, ErrSnapshotStale + } + // If the account is known locally, return it + if data, ok := dl.accountData[hash]; ok { + snapshotDirtyAccountHitMeter.Mark(1) + snapshotDirtyAccountHitDepthHist.Update(int64(depth)) + snapshotDirtyAccountReadMeter.Mark(int64(len(data))) + snapshotBloomAccountTrueHitMeter.Mark(1) + return data, nil + } + // If the account is known locally, but deleted, return it + if _, ok := dl.destructSet[hash]; ok { + snapshotDirtyAccountHitMeter.Mark(1) + snapshotDirtyAccountHitDepthHist.Update(int64(depth)) + snapshotDirtyAccountInexMeter.Mark(1) + snapshotBloomAccountTrueHitMeter.Mark(1) + return nil, nil + } + // Account unknown to this diff, resolve from parent + if diff, ok := dl.parent.(*diffLayer); ok { + return diff.accountRLP(hash, depth+1) + } + // Failed to resolve through diff layers, mark a bloom error and use the disk + snapshotBloomAccountFalseHitMeter.Mark(1) + return dl.parent.AccountRLP(hash) +} + +// Storage directly retrieves the storage data associated with a particular hash, +// within a particular account. If the slot is unknown to this diff, it's parent +// is consulted. +// +// Note the returned slot is not a copy, please don't modify it. +func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) { + // Check the bloom filter first whether there's even a point in reaching into + // all the maps in all the layers below + dl.lock.RLock() + // Check staleness before reaching further. + if dl.Stale() { + dl.lock.RUnlock() + return nil, ErrSnapshotStale + } + hit := dl.diffed.ContainsHash(storageBloomHash(accountHash, storageHash)) + if !hit { + hit = dl.diffed.ContainsHash(destructBloomHash(accountHash)) + } + var origin *diskLayer + if !hit { + origin = dl.origin // extract origin while holding the lock + } + dl.lock.RUnlock() + + // If the bloom filter misses, don't even bother with traversing the memory + // diff layers, reach straight into the bottom persistent disk layer + if origin != nil { + snapshotBloomStorageMissMeter.Mark(1) + return origin.Storage(accountHash, storageHash) + } + // The bloom filter hit, start poking in the internal maps + return dl.storage(accountHash, storageHash, 0) +} + +// storage is an internal version of Storage that skips the bloom filter checks +// and uses the internal maps to try and retrieve the data. It's meant to be +// used if a higher layer's bloom filter hit already. +func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([]byte, error) { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the layer was flattened into, consider it invalid (any live reference to + // the original should be marked as unusable). + if dl.Stale() { + return nil, ErrSnapshotStale + } + // If the account is known locally, try to resolve the slot locally + if storage, ok := dl.storageData[accountHash]; ok { + if data, ok := storage[storageHash]; ok { + snapshotDirtyStorageHitMeter.Mark(1) + snapshotDirtyStorageHitDepthHist.Update(int64(depth)) + if n := len(data); n > 0 { + snapshotDirtyStorageReadMeter.Mark(int64(n)) + } else { + snapshotDirtyStorageInexMeter.Mark(1) + } + snapshotBloomStorageTrueHitMeter.Mark(1) + return data, nil + } + } + // If the account is known locally, but deleted, return an empty slot + if _, ok := dl.destructSet[accountHash]; ok { + snapshotDirtyStorageHitMeter.Mark(1) + snapshotDirtyStorageHitDepthHist.Update(int64(depth)) + snapshotDirtyStorageInexMeter.Mark(1) + snapshotBloomStorageTrueHitMeter.Mark(1) + return nil, nil + } + // Storage slot unknown to this diff, resolve from parent + if diff, ok := dl.parent.(*diffLayer); ok { + return diff.storage(accountHash, storageHash, depth+1) + } + // Failed to resolve through diff layers, mark a bloom error and use the disk + snapshotBloomStorageFalseHitMeter.Mark(1) + return dl.parent.Storage(accountHash, storageHash) +} + +// Update creates a new layer on top of the existing snapshot diff tree with +// the specified data items. +func (dl *diffLayer) Update(blockRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + return newDiffLayer(dl, blockRoot, destructs, accounts, storage) +} + +// flatten pushes all data from this point downwards, flattening everything into +// a single diff at the bottom. Since usually the lowermost diff is the largest, +// the flattening builds up from there in reverse. +func (dl *diffLayer) flatten() snapshot { + // If the parent is not diff, we're the first in line, return unmodified + parent, ok := dl.parent.(*diffLayer) + if !ok { + return dl + } + // Parent is a diff, flatten it first (note, apart from weird corned cases, + // flatten will realistically only ever merge 1 layer, so there's no need to + // be smarter about grouping flattens together). + parent = parent.flatten().(*diffLayer) + + parent.lock.Lock() + defer parent.lock.Unlock() + + // Before actually writing all our data to the parent, first ensure that the + // parent hasn't been 'corrupted' by someone else already flattening into it + if parent.stale.Swap(true) { + panic("parent diff layer is stale") // we've flattened into the same parent from two children, boo + } + // Overwrite all the updated accounts blindly, merge the sorted list + for hash := range dl.destructSet { + parent.destructSet[hash] = struct{}{} + delete(parent.accountData, hash) + delete(parent.storageData, hash) + } + for hash, data := range dl.accountData { + parent.accountData[hash] = data + } + // Overwrite all the updated storage slots (individually) + for accountHash, storage := range dl.storageData { + // If storage didn't exist (or was deleted) in the parent, overwrite blindly + if _, ok := parent.storageData[accountHash]; !ok { + parent.storageData[accountHash] = storage + continue + } + // Storage exists in both parent and child, merge the slots + comboData := parent.storageData[accountHash] + for storageHash, data := range storage { + comboData[storageHash] = data + } + } + // Return the combo parent + return &diffLayer{ + parent: parent.parent, + origin: parent.origin, + root: dl.root, + destructSet: parent.destructSet, + accountData: parent.accountData, + storageData: parent.storageData, + storageList: make(map[common.Hash][]common.Hash), + diffed: dl.diffed, + memory: parent.memory + dl.memory, + } +} + +// AccountList returns a sorted list of all accounts in this diffLayer, including +// the deleted ones. +// +// Note, the returned slice is not a copy, so do not modify it. +func (dl *diffLayer) AccountList() []common.Hash { + // If an old list already exists, return it + dl.lock.RLock() + list := dl.accountList + dl.lock.RUnlock() + + if list != nil { + return list + } + // No old sorted account list exists, generate a new one + dl.lock.Lock() + defer dl.lock.Unlock() + + dl.accountList = make([]common.Hash, 0, len(dl.destructSet)+len(dl.accountData)) + for hash := range dl.accountData { + dl.accountList = append(dl.accountList, hash) + } + for hash := range dl.destructSet { + if _, ok := dl.accountData[hash]; !ok { + dl.accountList = append(dl.accountList, hash) + } + } + slices.SortFunc(dl.accountList, common.Hash.Cmp) + dl.memory += uint64(len(dl.accountList) * common.HashLength) + return dl.accountList +} + +// StorageList returns a sorted list of all storage slot hashes in this diffLayer +// for the given account. If the whole storage is destructed in this layer, then +// an additional flag *destructed = true* will be returned, otherwise the flag is +// false. Besides, the returned list will include the hash of deleted storage slot. +// Note a special case is an account is deleted in a prior tx but is recreated in +// the following tx with some storage slots set. In this case the returned list is +// not empty but the flag is true. +// +// Note, the returned slice is not a copy, so do not modify it. +func (dl *diffLayer) StorageList(accountHash common.Hash) ([]common.Hash, bool) { + dl.lock.RLock() + _, destructed := dl.destructSet[accountHash] + if _, ok := dl.storageData[accountHash]; !ok { + // Account not tracked by this layer + dl.lock.RUnlock() + return nil, destructed + } + // If an old list already exists, return it + if list, exist := dl.storageList[accountHash]; exist { + dl.lock.RUnlock() + return list, destructed // the cached list can't be nil + } + dl.lock.RUnlock() + + // No old sorted account list exists, generate a new one + dl.lock.Lock() + defer dl.lock.Unlock() + + storageMap := dl.storageData[accountHash] + storageList := make([]common.Hash, 0, len(storageMap)) + for k := range storageMap { + storageList = append(storageList, k) + } + slices.SortFunc(storageList, common.Hash.Cmp) + dl.storageList[accountHash] = storageList + dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength) + return storageList, destructed +} diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go new file mode 100644 index 0000000..674a031 --- /dev/null +++ b/core/state/snapshot/difflayer_test.go @@ -0,0 +1,399 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + crand "crypto/rand" + "math/rand" + "testing" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +func copyDestructs(destructs map[common.Hash]struct{}) map[common.Hash]struct{} { + copy := make(map[common.Hash]struct{}) + for hash := range destructs { + copy[hash] = struct{}{} + } + return copy +} + +func copyAccounts(accounts map[common.Hash][]byte) map[common.Hash][]byte { + copy := make(map[common.Hash][]byte) + for hash, blob := range accounts { + copy[hash] = blob + } + return copy +} + +func copyStorage(storage map[common.Hash]map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { + copy := make(map[common.Hash]map[common.Hash][]byte) + for accHash, slots := range storage { + copy[accHash] = make(map[common.Hash][]byte) + for slotHash, blob := range slots { + copy[accHash][slotHash] = blob + } + } + return copy +} + +// TestMergeBasics tests some simple merges +func TestMergeBasics(t *testing.T) { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + // Fill up a parent + for i := 0; i < 100; i++ { + h := randomHash() + data := randomAccount() + + accounts[h] = data + if rand.Intn(4) == 0 { + destructs[h] = struct{}{} + } + if rand.Intn(2) == 0 { + accStorage := make(map[common.Hash][]byte) + value := make([]byte, 32) + crand.Read(value) + accStorage[randomHash()] = value + storage[h] = accStorage + } + } + // Add some (identical) layers on top + parent := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + child := newDiffLayer(parent, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + // And flatten + merged := (child.flatten()).(*diffLayer) + + { // Check account lists + if have, want := len(merged.accountList), 0; have != want { + t.Errorf("accountList wrong: have %v, want %v", have, want) + } + if have, want := len(merged.AccountList()), len(accounts); have != want { + t.Errorf("AccountList() wrong: have %v, want %v", have, want) + } + if have, want := len(merged.accountList), len(accounts); have != want { + t.Errorf("accountList [2] wrong: have %v, want %v", have, want) + } + } + { // Check account drops + if have, want := len(merged.destructSet), len(destructs); have != want { + t.Errorf("accountDrop wrong: have %v, want %v", have, want) + } + } + { // Check storage lists + i := 0 + for aHash, sMap := range storage { + if have, want := len(merged.storageList), i; have != want { + t.Errorf("[1] storageList wrong: have %v, want %v", have, want) + } + list, _ := merged.StorageList(aHash) + if have, want := len(list), len(sMap); have != want { + t.Errorf("[2] StorageList() wrong: have %v, want %v", have, want) + } + if have, want := len(merged.storageList[aHash]), len(sMap); have != want { + t.Errorf("storageList wrong: have %v, want %v", have, want) + } + i++ + } + } +} + +// TestMergeDelete tests some deletion +func TestMergeDelete(t *testing.T) { + var ( + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + // Fill up a parent + h1 := common.HexToHash("0x01") + h2 := common.HexToHash("0x02") + + flipDrops := func() map[common.Hash]struct{} { + return map[common.Hash]struct{}{ + h2: {}, + } + } + flipAccs := func() map[common.Hash][]byte { + return map[common.Hash][]byte{ + h1: randomAccount(), + } + } + flopDrops := func() map[common.Hash]struct{} { + return map[common.Hash]struct{}{ + h1: {}, + } + } + flopAccs := func() map[common.Hash][]byte { + return map[common.Hash][]byte{ + h2: randomAccount(), + } + } + // Add some flipAccs-flopping layers on top + parent := newDiffLayer(emptyLayer(), common.Hash{}, flipDrops(), flipAccs(), storage) + child := parent.Update(common.Hash{}, flopDrops(), flopAccs(), storage) + child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) + child = child.Update(common.Hash{}, flopDrops(), flopAccs(), storage) + child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) + child = child.Update(common.Hash{}, flopDrops(), flopAccs(), storage) + child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) + + if data, _ := child.Account(h1); data == nil { + t.Errorf("last diff layer: expected %x account to be non-nil", h1) + } + if data, _ := child.Account(h2); data != nil { + t.Errorf("last diff layer: expected %x account to be nil", h2) + } + if _, ok := child.destructSet[h1]; ok { + t.Errorf("last diff layer: expected %x drop to be missing", h1) + } + if _, ok := child.destructSet[h2]; !ok { + t.Errorf("last diff layer: expected %x drop to be present", h1) + } + // And flatten + merged := (child.flatten()).(*diffLayer) + + if data, _ := merged.Account(h1); data == nil { + t.Errorf("merged layer: expected %x account to be non-nil", h1) + } + if data, _ := merged.Account(h2); data != nil { + t.Errorf("merged layer: expected %x account to be nil", h2) + } + if _, ok := merged.destructSet[h1]; !ok { // Note, drops stay alive until persisted to disk! + t.Errorf("merged diff layer: expected %x drop to be present", h1) + } + if _, ok := merged.destructSet[h2]; !ok { // Note, drops stay alive until persisted to disk! + t.Errorf("merged diff layer: expected %x drop to be present", h1) + } + // If we add more granular metering of memory, we can enable this again, + // but it's not implemented for now + //if have, want := merged.memory, child.memory; have != want { + // t.Errorf("mem wrong: have %d, want %d", have, want) + //} +} + +// This tests that if we create a new account, and set a slot, and then merge +// it, the lists will be correct. +func TestInsertAndMerge(t *testing.T) { + // Fill up a parent + var ( + acc = common.HexToHash("0x01") + slot = common.HexToHash("0x02") + parent *diffLayer + child *diffLayer + ) + { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + parent = newDiffLayer(emptyLayer(), common.Hash{}, destructs, accounts, storage) + } + { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + accounts[acc] = randomAccount() + storage[acc] = make(map[common.Hash][]byte) + storage[acc][slot] = []byte{0x01} + child = newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) + } + // And flatten + merged := (child.flatten()).(*diffLayer) + { // Check that slot value is present + have, _ := merged.Storage(acc, slot) + if want := []byte{0x01}; !bytes.Equal(have, want) { + t.Errorf("merged slot value wrong: have %x, want %x", have, want) + } + } +} + +func emptyLayer() *diskLayer { + return &diskLayer{ + diskdb: memorydb.New(), + cache: fastcache.New(500 * 1024), + } +} + +// BenchmarkSearch checks how long it takes to find a non-existing key +// BenchmarkSearch-6 200000 10481 ns/op (1K per layer) +// BenchmarkSearch-6 200000 10760 ns/op (10K per layer) +// BenchmarkSearch-6 100000 17866 ns/op +// +// BenchmarkSearch-6 500000 3723 ns/op (10k per layer, only top-level RLock() +func BenchmarkSearch(b *testing.B) { + // First, we set up 128 diff layers, with 1K items each + fill := func(parent snapshot) *diffLayer { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + for i := 0; i < 10000; i++ { + accounts[randomHash()] = randomAccount() + } + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) + } + var layer snapshot + layer = emptyLayer() + for i := 0; i < 128; i++ { + layer = fill(layer) + } + key := crypto.Keccak256Hash([]byte{0x13, 0x38}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + layer.AccountRLP(key) + } +} + +// BenchmarkSearchSlot checks how long it takes to find a non-existing key +// - Number of layers: 128 +// - Each layers contains the account, with a couple of storage slots +// BenchmarkSearchSlot-6 100000 14554 ns/op +// BenchmarkSearchSlot-6 100000 22254 ns/op (when checking parent root using mutex) +// BenchmarkSearchSlot-6 100000 14551 ns/op (when checking parent number using atomic) +// With bloom filter: +// BenchmarkSearchSlot-6 3467835 351 ns/op +func BenchmarkSearchSlot(b *testing.B) { + // First, we set up 128 diff layers, with 1K items each + accountKey := crypto.Keccak256Hash([]byte{0x13, 0x37}) + storageKey := crypto.Keccak256Hash([]byte{0x13, 0x37}) + accountRLP := randomAccount() + fill := func(parent snapshot) *diffLayer { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + accounts[accountKey] = accountRLP + + accStorage := make(map[common.Hash][]byte) + for i := 0; i < 5; i++ { + value := make([]byte, 32) + crand.Read(value) + accStorage[randomHash()] = value + storage[accountKey] = accStorage + } + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) + } + var layer snapshot + layer = emptyLayer() + for i := 0; i < 128; i++ { + layer = fill(layer) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + layer.Storage(accountKey, storageKey) + } +} + +// With accountList and sorting +// BenchmarkFlatten-6 50 29890856 ns/op +// +// Without sorting and tracking accountList +// BenchmarkFlatten-6 300 5511511 ns/op +func BenchmarkFlatten(b *testing.B) { + fill := func(parent snapshot) *diffLayer { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + for i := 0; i < 100; i++ { + accountKey := randomHash() + accounts[accountKey] = randomAccount() + + accStorage := make(map[common.Hash][]byte) + for i := 0; i < 20; i++ { + value := make([]byte, 32) + crand.Read(value) + accStorage[randomHash()] = value + } + storage[accountKey] = accStorage + } + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + var layer snapshot + layer = emptyLayer() + for i := 1; i < 128; i++ { + layer = fill(layer) + } + b.StartTimer() + + for i := 1; i < 128; i++ { + dl, ok := layer.(*diffLayer) + if !ok { + break + } + layer = dl.flatten() + } + b.StopTimer() + } +} + +// This test writes ~324M of diff layers to disk, spread over +// - 128 individual layers, +// - each with 200 accounts +// - containing 200 slots +// +// BenchmarkJournal-6 1 1471373923 ns/ops +// BenchmarkJournal-6 1 1208083335 ns/op // bufio writer +func BenchmarkJournal(b *testing.B) { + fill := func(parent snapshot) *diffLayer { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + for i := 0; i < 200; i++ { + accountKey := randomHash() + accounts[accountKey] = randomAccount() + + accStorage := make(map[common.Hash][]byte) + for i := 0; i < 200; i++ { + value := make([]byte, 32) + crand.Read(value) + accStorage[randomHash()] = value + } + storage[accountKey] = accStorage + } + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) + } + layer := snapshot(emptyLayer()) + for i := 1; i < 128; i++ { + layer = fill(layer) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + layer.Journal(new(bytes.Buffer)) + } +} diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go new file mode 100644 index 0000000..f5518a2 --- /dev/null +++ b/core/state/snapshot/disklayer.go @@ -0,0 +1,177 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "sync" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/triedb" +) + +// diskLayer is a low level persistent snapshot built on top of a key-value store. +type diskLayer struct { + diskdb ethdb.KeyValueStore // Key-value store containing the base snapshot + triedb *triedb.Database // Trie node cache for reconstruction purposes + cache *fastcache.Cache // Cache to avoid hitting the disk for direct access + + root common.Hash // Root hash of the base snapshot + stale bool // Signals that the layer became stale (state progressed) + + genMarker []byte // Marker for the state that's indexed during initial layer generation + genPending chan struct{} // Notification channel when generation is done (test synchronicity) + genAbort chan chan *generatorStats // Notification channel to abort generating the snapshot in this layer + + lock sync.RWMutex +} + +// 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 +} + +// Parent always returns nil as there's no layer below the disk. +func (dl *diskLayer) Parent() snapshot { + return nil +} + +// Stale return whether this layer has become stale (was flattened across) or if +// it's still live. +func (dl *diskLayer) Stale() bool { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.stale +} + +// Account directly retrieves the account associated with a particular hash in +// the snapshot slim data format. +func (dl *diskLayer) Account(hash common.Hash) (*types.SlimAccount, error) { + data, err := dl.AccountRLP(hash) + if err != nil { + return nil, err + } + if len(data) == 0 { // can be both nil and []byte{} + return nil, nil + } + account := new(types.SlimAccount) + if err := rlp.DecodeBytes(data, account); err != nil { + panic(err) + } + return account, nil +} + +// AccountRLP directly retrieves the account RLP associated with a particular +// hash in the snapshot slim data format. +func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the layer was flattened into, consider it invalid (any live reference to + // the original should be marked as unusable). + if dl.stale { + return nil, ErrSnapshotStale + } + // If the layer is being generated, ensure the requested hash has already been + // covered by the generator. + if dl.genMarker != nil && bytes.Compare(hash[:], dl.genMarker) > 0 { + return nil, ErrNotCoveredYet + } + // If we're in the disk layer, all diff layers missed + snapshotDirtyAccountMissMeter.Mark(1) + + // Try to retrieve the account from the memory cache + if blob, found := dl.cache.HasGet(nil, hash[:]); found { + snapshotCleanAccountHitMeter.Mark(1) + snapshotCleanAccountReadMeter.Mark(int64(len(blob))) + return blob, nil + } + // Cache doesn't contain account, pull from disk and cache for later + blob := rawdb.ReadAccountSnapshot(dl.diskdb, hash) + dl.cache.Set(hash[:], blob) + + snapshotCleanAccountMissMeter.Mark(1) + if n := len(blob); n > 0 { + snapshotCleanAccountWriteMeter.Mark(int64(n)) + } else { + snapshotCleanAccountInexMeter.Mark(1) + } + return blob, nil +} + +// Storage directly retrieves the storage data associated with a particular hash, +// within a particular account. +func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the layer was flattened into, consider it invalid (any live reference to + // the original should be marked as unusable). + if dl.stale { + return nil, ErrSnapshotStale + } + key := append(accountHash[:], storageHash[:]...) + + // If the layer is being generated, ensure the requested hash has already been + // covered by the generator. + if dl.genMarker != nil && bytes.Compare(key, dl.genMarker) > 0 { + return nil, ErrNotCoveredYet + } + // If we're in the disk layer, all diff layers missed + snapshotDirtyStorageMissMeter.Mark(1) + + // Try to retrieve the storage slot from the memory cache + if blob, found := dl.cache.HasGet(nil, key); found { + snapshotCleanStorageHitMeter.Mark(1) + snapshotCleanStorageReadMeter.Mark(int64(len(blob))) + return blob, nil + } + // Cache doesn't contain storage slot, pull from disk and cache for later + blob := rawdb.ReadStorageSnapshot(dl.diskdb, accountHash, storageHash) + dl.cache.Set(key, blob) + + snapshotCleanStorageMissMeter.Mark(1) + if n := len(blob); n > 0 { + snapshotCleanStorageWriteMeter.Mark(int64(n)) + } else { + snapshotCleanStorageInexMeter.Mark(1) + } + return blob, nil +} + +// Update creates a new layer on top of the existing snapshot diff tree with +// the specified data items. Note, the maps are retained by the method to avoid +// copying everything. +func (dl *diskLayer) Update(blockHash common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + return newDiffLayer(dl, blockHash, destructs, accounts, storage) +} diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go new file mode 100644 index 0000000..168458c --- /dev/null +++ b/core/state/snapshot/disklayer_test.go @@ -0,0 +1,574 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "testing" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/rlp" +) + +// reverse reverses the contents of a byte slice. It's used to update random accs +// with deterministic changes. +func reverse(blob []byte) []byte { + res := make([]byte, len(blob)) + for i, b := range blob { + res[len(blob)-1-i] = b + } + return res +} + +// Tests that merging something into a disk layer persists it into the database +// and invalidates any previously written and cached values. +func TestDiskMerge(t *testing.T) { + // Create some accounts in the disk layer + db := memorydb.New() + + var ( + accNoModNoCache = common.Hash{0x1} + accNoModCache = common.Hash{0x2} + accModNoCache = common.Hash{0x3} + accModCache = common.Hash{0x4} + accDelNoCache = common.Hash{0x5} + accDelCache = common.Hash{0x6} + conNoModNoCache = common.Hash{0x7} + conNoModNoCacheSlot = common.Hash{0x70} + conNoModCache = common.Hash{0x8} + conNoModCacheSlot = common.Hash{0x80} + conModNoCache = common.Hash{0x9} + conModNoCacheSlot = common.Hash{0x90} + conModCache = common.Hash{0xa} + conModCacheSlot = common.Hash{0xa0} + conDelNoCache = common.Hash{0xb} + conDelNoCacheSlot = common.Hash{0xb0} + conDelCache = common.Hash{0xc} + conDelCacheSlot = common.Hash{0xc0} + conNukeNoCache = common.Hash{0xd} + conNukeNoCacheSlot = common.Hash{0xd0} + conNukeCache = common.Hash{0xe} + conNukeCacheSlot = common.Hash{0xe0} + baseRoot = randomHash() + diffRoot = randomHash() + ) + + rawdb.WriteAccountSnapshot(db, accNoModNoCache, accNoModNoCache[:]) + rawdb.WriteAccountSnapshot(db, accNoModCache, accNoModCache[:]) + rawdb.WriteAccountSnapshot(db, accModNoCache, accModNoCache[:]) + rawdb.WriteAccountSnapshot(db, accModCache, accModCache[:]) + rawdb.WriteAccountSnapshot(db, accDelNoCache, accDelNoCache[:]) + rawdb.WriteAccountSnapshot(db, accDelCache, accDelCache[:]) + + rawdb.WriteAccountSnapshot(db, conNoModNoCache, conNoModNoCache[:]) + rawdb.WriteStorageSnapshot(db, conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conNoModCache, conNoModCache[:]) + rawdb.WriteStorageSnapshot(db, conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conModNoCache, conModNoCache[:]) + rawdb.WriteStorageSnapshot(db, conModNoCache, conModNoCacheSlot, conModNoCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conModCache, conModCache[:]) + rawdb.WriteStorageSnapshot(db, conModCache, conModCacheSlot, conModCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conDelNoCache, conDelNoCache[:]) + rawdb.WriteStorageSnapshot(db, conDelNoCache, conDelNoCacheSlot, conDelNoCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conDelCache, conDelCache[:]) + rawdb.WriteStorageSnapshot(db, conDelCache, conDelCacheSlot, conDelCacheSlot[:]) + + rawdb.WriteAccountSnapshot(db, conNukeNoCache, conNukeNoCache[:]) + rawdb.WriteStorageSnapshot(db, conNukeNoCache, conNukeNoCacheSlot, conNukeNoCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conNukeCache, conNukeCache[:]) + rawdb.WriteStorageSnapshot(db, conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) + + rawdb.WriteSnapshotRoot(db, baseRoot) + + // Create a disk layer based on the above and cache in some data + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + baseRoot: &diskLayer{ + diskdb: db, + cache: fastcache.New(500 * 1024), + root: baseRoot, + }, + }, + } + base := snaps.Snapshot(baseRoot) + base.AccountRLP(accNoModCache) + base.AccountRLP(accModCache) + base.AccountRLP(accDelCache) + base.Storage(conNoModCache, conNoModCacheSlot) + base.Storage(conModCache, conModCacheSlot) + base.Storage(conDelCache, conDelCacheSlot) + base.Storage(conNukeCache, conNukeCacheSlot) + + // Modify or delete some accounts, flatten everything onto disk + if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ + accDelNoCache: {}, + accDelCache: {}, + conNukeNoCache: {}, + conNukeCache: {}, + }, map[common.Hash][]byte{ + accModNoCache: reverse(accModNoCache[:]), + accModCache: reverse(accModCache[:]), + }, map[common.Hash]map[common.Hash][]byte{ + conModNoCache: {conModNoCacheSlot: reverse(conModNoCacheSlot[:])}, + conModCache: {conModCacheSlot: reverse(conModCacheSlot[:])}, + conDelNoCache: {conDelNoCacheSlot: nil}, + conDelCache: {conDelCacheSlot: nil}, + }); err != nil { + t.Fatalf("failed to update snapshot tree: %v", err) + } + if err := snaps.Cap(diffRoot, 0); err != nil { + t.Fatalf("failed to flatten snapshot tree: %v", err) + } + // Retrieve all the data through the disk layer and validate it + base = snaps.Snapshot(diffRoot) + if _, ok := base.(*diskLayer); !ok { + t.Fatalf("update not flattened into the disk layer") + } + + // assertAccount ensures that an account matches the given blob. + assertAccount := func(account common.Hash, data []byte) { + t.Helper() + blob, err := base.AccountRLP(account) + if err != nil { + t.Errorf("account access (%x) failed: %v", account, err) + } else if !bytes.Equal(blob, data) { + t.Errorf("account access (%x) mismatch: have %x, want %x", account, blob, data) + } + } + assertAccount(accNoModNoCache, accNoModNoCache[:]) + assertAccount(accNoModCache, accNoModCache[:]) + assertAccount(accModNoCache, reverse(accModNoCache[:])) + assertAccount(accModCache, reverse(accModCache[:])) + assertAccount(accDelNoCache, nil) + assertAccount(accDelCache, nil) + + // assertStorage ensures that a storage slot matches the given blob. + assertStorage := func(account common.Hash, slot common.Hash, data []byte) { + t.Helper() + blob, err := base.Storage(account, slot) + if err != nil { + t.Errorf("storage access (%x:%x) failed: %v", account, slot, err) + } else if !bytes.Equal(blob, data) { + t.Errorf("storage access (%x:%x) mismatch: have %x, want %x", account, slot, blob, data) + } + } + assertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) + assertStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) + assertStorage(conDelNoCache, conDelNoCacheSlot, nil) + assertStorage(conDelCache, conDelCacheSlot, nil) + assertStorage(conNukeNoCache, conNukeNoCacheSlot, nil) + assertStorage(conNukeCache, conNukeCacheSlot, nil) + + // Retrieve all the data directly from the database and validate it + + // assertDatabaseAccount ensures that an account from the database matches the given blob. + assertDatabaseAccount := func(account common.Hash, data []byte) { + t.Helper() + if blob := rawdb.ReadAccountSnapshot(db, account); !bytes.Equal(blob, data) { + t.Errorf("account database access (%x) mismatch: have %x, want %x", account, blob, data) + } + } + assertDatabaseAccount(accNoModNoCache, accNoModNoCache[:]) + assertDatabaseAccount(accNoModCache, accNoModCache[:]) + assertDatabaseAccount(accModNoCache, reverse(accModNoCache[:])) + assertDatabaseAccount(accModCache, reverse(accModCache[:])) + assertDatabaseAccount(accDelNoCache, nil) + assertDatabaseAccount(accDelCache, nil) + + // assertDatabaseStorage ensures that a storage slot from the database matches the given blob. + assertDatabaseStorage := func(account common.Hash, slot common.Hash, data []byte) { + t.Helper() + if blob := rawdb.ReadStorageSnapshot(db, account, slot); !bytes.Equal(blob, data) { + t.Errorf("storage database access (%x:%x) mismatch: have %x, want %x", account, slot, blob, data) + } + } + assertDatabaseStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + assertDatabaseStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertDatabaseStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) + assertDatabaseStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) + assertDatabaseStorage(conDelNoCache, conDelNoCacheSlot, nil) + assertDatabaseStorage(conDelCache, conDelCacheSlot, nil) + assertDatabaseStorage(conNukeNoCache, conNukeNoCacheSlot, nil) + assertDatabaseStorage(conNukeCache, conNukeCacheSlot, nil) +} + +// Tests that merging something into a disk layer persists it into the database +// and invalidates any previously written and cached values, discarding anything +// after the in-progress generation marker. +func TestDiskPartialMerge(t *testing.T) { + // Iterate the test a few times to ensure we pick various internal orderings + // for the data slots as well as the progress marker. + for i := 0; i < 1024; i++ { + // Create some accounts in the disk layer + db := memorydb.New() + + var ( + accNoModNoCache = randomHash() + accNoModCache = randomHash() + accModNoCache = randomHash() + accModCache = randomHash() + accDelNoCache = randomHash() + accDelCache = randomHash() + conNoModNoCache = randomHash() + conNoModNoCacheSlot = randomHash() + conNoModCache = randomHash() + conNoModCacheSlot = randomHash() + conModNoCache = randomHash() + conModNoCacheSlot = randomHash() + conModCache = randomHash() + conModCacheSlot = randomHash() + conDelNoCache = randomHash() + conDelNoCacheSlot = randomHash() + conDelCache = randomHash() + conDelCacheSlot = randomHash() + conNukeNoCache = randomHash() + conNukeNoCacheSlot = randomHash() + conNukeCache = randomHash() + conNukeCacheSlot = randomHash() + baseRoot = randomHash() + diffRoot = randomHash() + genMarker = append(randomHash().Bytes(), randomHash().Bytes()...) + ) + + // insertAccount injects an account into the database if it's after the + // generator marker, drops the op otherwise. This is needed to seed the + // database with a valid starting snapshot. + insertAccount := func(account common.Hash, data []byte) { + if bytes.Compare(account[:], genMarker) <= 0 { + rawdb.WriteAccountSnapshot(db, account, data[:]) + } + } + insertAccount(accNoModNoCache, accNoModNoCache[:]) + insertAccount(accNoModCache, accNoModCache[:]) + insertAccount(accModNoCache, accModNoCache[:]) + insertAccount(accModCache, accModCache[:]) + insertAccount(accDelNoCache, accDelNoCache[:]) + insertAccount(accDelCache, accDelCache[:]) + + // insertStorage injects a storage slot into the database if it's after + // the generator marker, drops the op otherwise. This is needed to seed + // the database with a valid starting snapshot. + insertStorage := func(account common.Hash, slot common.Hash, data []byte) { + if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 { + rawdb.WriteStorageSnapshot(db, account, slot, data[:]) + } + } + insertAccount(conNoModNoCache, conNoModNoCache[:]) + insertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + insertAccount(conNoModCache, conNoModCache[:]) + insertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + insertAccount(conModNoCache, conModNoCache[:]) + insertStorage(conModNoCache, conModNoCacheSlot, conModNoCacheSlot[:]) + insertAccount(conModCache, conModCache[:]) + insertStorage(conModCache, conModCacheSlot, conModCacheSlot[:]) + insertAccount(conDelNoCache, conDelNoCache[:]) + insertStorage(conDelNoCache, conDelNoCacheSlot, conDelNoCacheSlot[:]) + insertAccount(conDelCache, conDelCache[:]) + insertStorage(conDelCache, conDelCacheSlot, conDelCacheSlot[:]) + + insertAccount(conNukeNoCache, conNukeNoCache[:]) + insertStorage(conNukeNoCache, conNukeNoCacheSlot, conNukeNoCacheSlot[:]) + insertAccount(conNukeCache, conNukeCache[:]) + insertStorage(conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) + + rawdb.WriteSnapshotRoot(db, baseRoot) + + // Create a disk layer based on the above using a random progress marker + // and cache in some data. + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + baseRoot: &diskLayer{ + diskdb: db, + cache: fastcache.New(500 * 1024), + root: baseRoot, + }, + }, + } + snaps.layers[baseRoot].(*diskLayer).genMarker = genMarker + base := snaps.Snapshot(baseRoot) + + // assertAccount ensures that an account matches the given blob if it's + // already covered by the disk snapshot, and errors out otherwise. + assertAccount := func(account common.Hash, data []byte) { + t.Helper() + blob, err := base.AccountRLP(account) + if bytes.Compare(account[:], genMarker) > 0 && err != ErrNotCoveredYet { + t.Fatalf("test %d: post-marker (%x) account access (%x) succeeded: %x", i, genMarker, account, blob) + } + if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { + t.Fatalf("test %d: pre-marker (%x) account access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) + } + } + assertAccount(accNoModCache, accNoModCache[:]) + assertAccount(accModCache, accModCache[:]) + assertAccount(accDelCache, accDelCache[:]) + + // assertStorage ensures that a storage slot matches the given blob if + // it's already covered by the disk snapshot, and errors out otherwise. + assertStorage := func(account common.Hash, slot common.Hash, data []byte) { + t.Helper() + blob, err := base.Storage(account, slot) + if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && err != ErrNotCoveredYet { + t.Fatalf("test %d: post-marker (%x) storage access (%x:%x) succeeded: %x", i, genMarker, account, slot, blob) + } + if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { + t.Fatalf("test %d: pre-marker (%x) storage access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) + } + } + assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertStorage(conModCache, conModCacheSlot, conModCacheSlot[:]) + assertStorage(conDelCache, conDelCacheSlot, conDelCacheSlot[:]) + assertStorage(conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) + + // Modify or delete some accounts, flatten everything onto disk + if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ + accDelNoCache: {}, + accDelCache: {}, + conNukeNoCache: {}, + conNukeCache: {}, + }, map[common.Hash][]byte{ + accModNoCache: reverse(accModNoCache[:]), + accModCache: reverse(accModCache[:]), + }, map[common.Hash]map[common.Hash][]byte{ + conModNoCache: {conModNoCacheSlot: reverse(conModNoCacheSlot[:])}, + conModCache: {conModCacheSlot: reverse(conModCacheSlot[:])}, + conDelNoCache: {conDelNoCacheSlot: nil}, + conDelCache: {conDelCacheSlot: nil}, + }); err != nil { + t.Fatalf("test %d: failed to update snapshot tree: %v", i, err) + } + if err := snaps.Cap(diffRoot, 0); err != nil { + t.Fatalf("test %d: failed to flatten snapshot tree: %v", i, err) + } + // Retrieve all the data through the disk layer and validate it + base = snaps.Snapshot(diffRoot) + if _, ok := base.(*diskLayer); !ok { + t.Fatalf("test %d: update not flattened into the disk layer", i) + } + assertAccount(accNoModNoCache, accNoModNoCache[:]) + assertAccount(accNoModCache, accNoModCache[:]) + assertAccount(accModNoCache, reverse(accModNoCache[:])) + assertAccount(accModCache, reverse(accModCache[:])) + assertAccount(accDelNoCache, nil) + assertAccount(accDelCache, nil) + + assertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) + assertStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) + assertStorage(conDelNoCache, conDelNoCacheSlot, nil) + assertStorage(conDelCache, conDelCacheSlot, nil) + assertStorage(conNukeNoCache, conNukeNoCacheSlot, nil) + assertStorage(conNukeCache, conNukeCacheSlot, nil) + + // Retrieve all the data directly from the database and validate it + + // assertDatabaseAccount ensures that an account inside the database matches + // the given blob if it's already covered by the disk snapshot, and does not + // exist otherwise. + assertDatabaseAccount := func(account common.Hash, data []byte) { + t.Helper() + blob := rawdb.ReadAccountSnapshot(db, account) + if bytes.Compare(account[:], genMarker) > 0 && blob != nil { + t.Fatalf("test %d: post-marker (%x) account database access (%x) succeeded: %x", i, genMarker, account, blob) + } + if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { + t.Fatalf("test %d: pre-marker (%x) account database access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) + } + } + assertDatabaseAccount(accNoModNoCache, accNoModNoCache[:]) + assertDatabaseAccount(accNoModCache, accNoModCache[:]) + assertDatabaseAccount(accModNoCache, reverse(accModNoCache[:])) + assertDatabaseAccount(accModCache, reverse(accModCache[:])) + assertDatabaseAccount(accDelNoCache, nil) + assertDatabaseAccount(accDelCache, nil) + + // assertDatabaseStorage ensures that a storage slot inside the database + // matches the given blob if it's already covered by the disk snapshot, + // and does not exist otherwise. + assertDatabaseStorage := func(account common.Hash, slot common.Hash, data []byte) { + t.Helper() + blob := rawdb.ReadStorageSnapshot(db, account, slot) + if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && blob != nil { + t.Fatalf("test %d: post-marker (%x) storage database access (%x:%x) succeeded: %x", i, genMarker, account, slot, blob) + } + if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { + t.Fatalf("test %d: pre-marker (%x) storage database access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) + } + } + assertDatabaseStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + assertDatabaseStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertDatabaseStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) + assertDatabaseStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) + assertDatabaseStorage(conDelNoCache, conDelNoCacheSlot, nil) + assertDatabaseStorage(conDelCache, conDelCacheSlot, nil) + assertDatabaseStorage(conNukeNoCache, conNukeNoCacheSlot, nil) + assertDatabaseStorage(conNukeCache, conNukeCacheSlot, nil) + } +} + +// Tests that when the bottom-most diff layer is merged into the disk +// layer whether the corresponding generator is persisted correctly. +func TestDiskGeneratorPersistence(t *testing.T) { + var ( + accOne = randomHash() + accTwo = randomHash() + accOneSlotOne = randomHash() + accOneSlotTwo = randomHash() + + accThree = randomHash() + accThreeSlot = randomHash() + baseRoot = randomHash() + diffRoot = randomHash() + diffTwoRoot = randomHash() + genMarker = append(randomHash().Bytes(), randomHash().Bytes()...) + ) + // Testing scenario 1, the disk layer is still under the construction. + db := rawdb.NewMemoryDatabase() + + rawdb.WriteAccountSnapshot(db, accOne, accOne[:]) + rawdb.WriteStorageSnapshot(db, accOne, accOneSlotOne, accOneSlotOne[:]) + rawdb.WriteStorageSnapshot(db, accOne, accOneSlotTwo, accOneSlotTwo[:]) + rawdb.WriteSnapshotRoot(db, baseRoot) + + // Create a disk layer based on all above updates + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + baseRoot: &diskLayer{ + diskdb: db, + cache: fastcache.New(500 * 1024), + root: baseRoot, + genMarker: genMarker, + }, + }, + } + // Modify or delete some accounts, flatten everything onto disk + if err := snaps.Update(diffRoot, baseRoot, nil, map[common.Hash][]byte{ + accTwo: accTwo[:], + }, nil); err != nil { + t.Fatalf("failed to update snapshot tree: %v", err) + } + if err := snaps.Cap(diffRoot, 0); err != nil { + t.Fatalf("failed to flatten snapshot tree: %v", err) + } + blob := rawdb.ReadSnapshotGenerator(db) + var generator journalGenerator + if err := rlp.DecodeBytes(blob, &generator); err != nil { + t.Fatalf("Failed to decode snapshot generator %v", err) + } + if !bytes.Equal(generator.Marker, genMarker) { + t.Fatalf("Generator marker is not matched") + } + // Test scenario 2, the disk layer is fully generated + // Modify or delete some accounts, flatten everything onto disk + if err := snaps.Update(diffTwoRoot, diffRoot, nil, map[common.Hash][]byte{ + accThree: accThree.Bytes(), + }, map[common.Hash]map[common.Hash][]byte{ + accThree: {accThreeSlot: accThreeSlot.Bytes()}, + }); err != nil { + t.Fatalf("failed to update snapshot tree: %v", err) + } + diskLayer := snaps.layers[snaps.diskRoot()].(*diskLayer) + diskLayer.genMarker = nil // Construction finished + if err := snaps.Cap(diffTwoRoot, 0); err != nil { + t.Fatalf("failed to flatten snapshot tree: %v", err) + } + blob = rawdb.ReadSnapshotGenerator(db) + if err := rlp.DecodeBytes(blob, &generator); err != nil { + t.Fatalf("Failed to decode snapshot generator %v", err) + } + if len(generator.Marker) != 0 { + t.Fatalf("Failed to update snapshot generator") + } +} + +// Tests that merging something into a disk layer persists it into the database +// and invalidates any previously written and cached values, discarding anything +// after the in-progress generation marker. +// +// This test case is a tiny specialized case of TestDiskPartialMerge, which tests +// some very specific cornercases that random tests won't ever trigger. +func TestDiskMidAccountPartialMerge(t *testing.T) { + // TODO(@karalabe) ? +} + +// TestDiskSeek tests that seek-operations work on the disk layer +func TestDiskSeek(t *testing.T) { + // Create some accounts in the disk layer + db := rawdb.NewMemoryDatabase() + defer db.Close() + + // Fill even keys [0,2,4...] + for i := 0; i < 0xff; i += 2 { + acc := common.Hash{byte(i)} + rawdb.WriteAccountSnapshot(db, acc, acc[:]) + } + // Add an 'higher' key, with incorrect (higher) prefix + highKey := []byte{rawdb.SnapshotAccountPrefix[0] + 1} + db.Put(highKey, []byte{0xff, 0xff}) + + baseRoot := randomHash() + rawdb.WriteSnapshotRoot(db, baseRoot) + + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + baseRoot: &diskLayer{ + diskdb: db, + cache: fastcache.New(500 * 1024), + root: baseRoot, + }, + }, + } + // Test some different seek positions + type testcase struct { + pos byte + expkey byte + } + var cases = []testcase{ + {0xff, 0x55}, // this should exit immediately without checking key + {0x01, 0x02}, + {0xfe, 0xfe}, + {0xfd, 0xfe}, + {0x00, 0x00}, + } + for i, tc := range cases { + it, err := snaps.AccountIterator(baseRoot, common.Hash{tc.pos}) + if err != nil { + t.Fatalf("case %d, error: %v", i, err) + } + count := 0 + for it.Next() { + k, v, err := it.Hash()[0], it.Account()[0], it.Error() + if err != nil { + t.Fatalf("test %d, item %d, error: %v", i, count, err) + } + // First item in iterator should have the expected key + if count == 0 && k != tc.expkey { + t.Fatalf("test %d, item %d, got %v exp %v", i, count, k, tc.expkey) + } + count++ + if v != k { + t.Fatalf("test %d, item %d, value wrong, got %v exp %v", i, count, v, k) + } + } + } +} diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go new file mode 100644 index 0000000..d81a628 --- /dev/null +++ b/core/state/snapshot/generate.go @@ -0,0 +1,747 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "errors" + "fmt" + "time" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb" +) + +var ( + // accountCheckRange is the upper limit of the number of accounts involved in + // each range check. This is a value estimated based on experience. If this + // range is too large, the failure rate of range proof will increase. Otherwise, + // if the range is too small, the efficiency of the state recovery will decrease. + accountCheckRange = 128 + + // storageCheckRange is the upper limit of the number of storage slots involved + // in each range check. This is a value estimated based on experience. If this + // range is too large, the failure rate of range proof will increase. Otherwise, + // if the range is too small, the efficiency of the state recovery will decrease. + storageCheckRange = 1024 + + // errMissingTrie is returned if the target trie is missing while the generation + // is running. In this case the generation is aborted and wait the new signal. + errMissingTrie = errors.New("missing trie") +) + +// generateSnapshot regenerates a brand new snapshot based on an existing state +// database and head block asynchronously. The snapshot is returned immediately +// and generation is continued in the background until done. +func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *triedb.Database, cache int, root common.Hash) *diskLayer { + // Create a new disk layer with an initialized state marker at zero + var ( + stats = &generatorStats{start: time.Now()} + batch = diskdb.NewBatch() + genMarker = []byte{} // Initialized but empty! + ) + rawdb.WriteSnapshotRoot(batch, root) + journalProgress(batch, genMarker, stats) + if err := batch.Write(); err != nil { + log.Crit("Failed to write initialized state marker", "err", err) + } + base := &diskLayer{ + diskdb: diskdb, + triedb: triedb, + root: root, + cache: fastcache.New(cache * 1024 * 1024), + genMarker: genMarker, + genPending: make(chan struct{}), + genAbort: make(chan chan *generatorStats), + } + go base.generate(stats) + log.Debug("Start snapshot generation", "root", root) + return base +} + +// journalProgress persists the generator stats into the database to resume later. +func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorStats) { + // Write out the generator marker. Note it's a standalone disk layer generator + // which is not mixed with journal. It's ok if the generator is persisted while + // journal is not. + entry := journalGenerator{ + Done: marker == nil, + Marker: marker, + } + if stats != nil { + entry.Accounts = stats.accounts + entry.Slots = stats.slots + entry.Storage = uint64(stats.storage) + } + blob, err := rlp.EncodeToBytes(entry) + if err != nil { + panic(err) // Cannot happen, here to catch dev errors + } + var logstr string + switch { + case marker == nil: + logstr = "done" + case bytes.Equal(marker, []byte{}): + logstr = "empty" + case len(marker) == common.HashLength: + logstr = fmt.Sprintf("%#x", marker) + default: + logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:]) + } + log.Debug("Journalled generator progress", "progress", logstr) + rawdb.WriteSnapshotGenerator(db, blob) +} + +// proofResult contains the output of range proving which can be used +// for further processing regardless if it is successful or not. +type proofResult struct { + keys [][]byte // The key set of all elements being iterated, even proving is failed + vals [][]byte // The val set of all elements being iterated, even proving is failed + diskMore bool // Set when the database has extra snapshot states since last iteration + trieMore bool // Set when the trie has extra snapshot states(only meaningful for successful proving) + proofErr error // Indicator whether the given state range is valid or not + tr *trie.Trie // The trie, in case the trie was resolved by the prover (may be nil) +} + +// valid returns the indicator that range proof is successful or not. +func (result *proofResult) valid() bool { + return result.proofErr == nil +} + +// last returns the last verified element key regardless of whether the range proof is +// successful or not. Nil is returned if nothing involved in the proving. +func (result *proofResult) last() []byte { + var last []byte + if len(result.keys) > 0 { + last = result.keys[len(result.keys)-1] + } + return last +} + +// forEach iterates all the visited elements and applies the given callback on them. +// The iteration is aborted if the callback returns non-nil error. +func (result *proofResult) forEach(callback func(key []byte, val []byte) error) error { + for i := 0; i < len(result.keys); i++ { + key, val := result.keys[i], result.vals[i] + if err := callback(key, val); err != nil { + return err + } + } + return nil +} + +// proveRange proves the snapshot segment with particular prefix is "valid". +// The iteration start point will be assigned if the iterator is restored from +// the last interruption. Max will be assigned in order to limit the maximum +// amount of data involved in each iteration. +// +// The proof result will be returned if the range proving is finished, otherwise +// the error will be returned to abort the entire procedure. +func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) { + var ( + keys [][]byte + vals [][]byte + proof = rawdb.NewMemoryDatabase() + diskMore = false + iter = ctx.iterator(kind) + start = time.Now() + min = append(prefix, origin...) + ) + for iter.Next() { + // Ensure the iterated item is always equal or larger than the given origin. + key := iter.Key() + if bytes.Compare(key, min) < 0 { + return nil, errors.New("invalid iteration position") + } + // Ensure the iterated item still fall in the specified prefix. If + // not which means the items in the specified area are all visited. + // Move the iterator a step back since we iterate one extra element + // out. + if !bytes.Equal(key[:len(prefix)], prefix) { + iter.Hold() + break + } + // Break if we've reached the max size, and signal that we're not + // done yet. Move the iterator a step back since we iterate one + // extra element out. + if len(keys) == max { + iter.Hold() + diskMore = true + break + } + keys = append(keys, common.CopyBytes(key[len(prefix):])) + + if valueConvertFn == nil { + vals = append(vals, common.CopyBytes(iter.Value())) + } else { + val, err := valueConvertFn(iter.Value()) + if err != nil { + // Special case, the state data is corrupted (invalid slim-format account), + // don't abort the entire procedure directly. Instead, let the fallback + // generation to heal the invalid data. + // + // Here append the original value to ensure that the number of key and + // value are aligned. + vals = append(vals, common.CopyBytes(iter.Value())) + log.Error("Failed to convert account state data", "err", err) + } else { + vals = append(vals, val) + } + } + } + // Update metrics for database iteration and merkle proving + if kind == snapStorage { + snapStorageSnapReadCounter.Inc(time.Since(start).Nanoseconds()) + } else { + snapAccountSnapReadCounter.Inc(time.Since(start).Nanoseconds()) + } + defer func(start time.Time) { + if kind == snapStorage { + snapStorageProveCounter.Inc(time.Since(start).Nanoseconds()) + } else { + snapAccountProveCounter.Inc(time.Since(start).Nanoseconds()) + } + }(time.Now()) + + // The snap state is exhausted, pass the entire key/val set for verification + root := trieId.Root + if origin == nil && !diskMore { + stackTr := trie.NewStackTrie(nil) + for i, key := range keys { + if err := stackTr.Update(key, vals[i]); err != nil { + return nil, err + } + } + if gotRoot := stackTr.Hash(); gotRoot != root { + return &proofResult{ + keys: keys, + vals: vals, + proofErr: fmt.Errorf("wrong root: have %#x want %#x", gotRoot, root), + }, nil + } + return &proofResult{keys: keys, vals: vals}, nil + } + // Snap state is chunked, generate edge proofs for verification. + tr, err := trie.New(trieId, dl.triedb) + if err != nil { + ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) + return nil, errMissingTrie + } + // Generate the Merkle proofs for the first and last element + if origin == nil { + origin = common.Hash{}.Bytes() + } + if err := tr.Prove(origin, proof); err != nil { + log.Debug("Failed to prove range", "kind", kind, "origin", origin, "err", err) + return &proofResult{ + keys: keys, + vals: vals, + diskMore: diskMore, + proofErr: err, + tr: tr, + }, nil + } + 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, + diskMore: diskMore, + proofErr: err, + tr: tr, + }, nil + } + } + // 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, keys, vals, proof) + return &proofResult{ + keys: keys, + vals: vals, + diskMore: diskMore, + trieMore: cont, + proofErr: err, + tr: tr}, + nil +} + +// onStateCallback is a function that is called by generateRange, when processing a range of +// accounts or storage slots. For each element, the callback is invoked. +// +// - If 'delete' is true, then this element (and potential slots) needs to be deleted from the snapshot. +// - If 'write' is true, then this element needs to be updated with the 'val'. +// - If 'write' is false, then this element is already correct, and needs no update. +// The 'val' is the canonical encoding of the value (not the slim format for accounts) +// +// However, for accounts, the storage trie of the account needs to be checked. Also, +// dangling storages(storage exists but the corresponding account is missing) need to +// be cleaned up. +type onStateCallback func(key []byte, val []byte, write bool, delete bool) error + +// generateRange generates the state segment with particular prefix. Generation can +// either verify the correctness of existing state through range-proof and skip +// generation, or iterate trie to regenerate state on demand. +func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefix []byte, kind string, origin []byte, max int, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) { + // Use range prover to check the validity of the flat state in the range + result, err := dl.proveRange(ctx, trieId, prefix, kind, origin, max, valueConvertFn) + if err != nil { + return false, nil, err + } + last := result.last() + + // Construct contextual logger + logCtx := []interface{}{"kind", kind, "prefix", hexutil.Encode(prefix)} + if len(origin) > 0 { + logCtx = append(logCtx, "origin", hexutil.Encode(origin)) + } + logger := log.New(logCtx...) + + // The range prover says the range is correct, skip trie iteration + if result.valid() { + snapSuccessfulRangeProofMeter.Mark(1) + logger.Trace("Proved state range", "last", hexutil.Encode(last)) + + // The verification is passed, process each state with the given + // callback function. If this state represents a contract, the + // corresponding storage check will be performed in the callback + if err := result.forEach(func(key []byte, val []byte) error { return onState(key, val, false, false) }); err != nil { + return false, nil, err + } + // Only abort the iteration when both database and trie are exhausted + return !result.diskMore && !result.trieMore, last, nil + } + logger.Trace("Detected outdated state range", "last", hexutil.Encode(last), "err", result.proofErr) + snapFailedRangeProofMeter.Mark(1) + + // Special case, the entire trie is missing. In the original trie scheme, + // all the duplicated subtries will be filtered out (only one copy of data + // will be stored). While in the snapshot model, all the storage tries + // belong to different contracts will be kept even they are duplicated. + // Track it to a certain extent remove the noise data used for statistics. + if origin == nil && last == nil { + meter := snapMissallAccountMeter + if kind == snapStorage { + meter = snapMissallStorageMeter + } + meter.Mark(1) + } + // We use the snap data to build up a cache which can be used by the + // main account trie as a primary lookup when resolving hashes + var resolver trie.NodeResolver + if len(result.keys) > 0 { + mdb := rawdb.NewMemoryDatabase() + tdb := triedb.NewDatabase(mdb, triedb.HashDefaults) + defer tdb.Close() + snapTrie := trie.NewEmpty(tdb) + for i, key := range result.keys { + snapTrie.Update(key, result.vals[i]) + } + root, nodes := snapTrie.Commit(false) + if nodes != nil { + tdb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) + tdb.Commit(root, false) + } + resolver = func(owner common.Hash, path []byte, hash common.Hash) []byte { + return rawdb.ReadTrieNode(mdb, owner, path, hash, tdb.Scheme()) + } + } + // Construct the trie for state iteration, reuse the trie + // if it's already opened with some nodes resolved. + tr := result.tr + if tr == nil { + tr, err = trie.New(trieId, dl.triedb) + if err != nil { + ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) + return false, nil, errMissingTrie + } + } + var ( + trieMore bool + kvkeys, kvvals = result.keys, result.vals + + // counters + count = 0 // number of states delivered by iterator + created = 0 // states created from the trie + updated = 0 // states updated from the trie + deleted = 0 // states not in trie, but were in snapshot + untouched = 0 // states already correct + + // timers + start = time.Now() + internal time.Duration + ) + nodeIt, err := tr.NodeIterator(origin) + if err != nil { + return false, nil, err + } + nodeIt.AddResolver(resolver) + iter := trie.NewIterator(nodeIt) + + for iter.Next() { + if last != nil && bytes.Compare(iter.Key, last) > 0 { + trieMore = true + break + } + count++ + write := true + created++ + for len(kvkeys) > 0 { + if cmp := bytes.Compare(kvkeys[0], iter.Key); cmp < 0 { + // delete the key + istart := time.Now() + if err := onState(kvkeys[0], nil, false, true); err != nil { + return false, nil, err + } + kvkeys = kvkeys[1:] + kvvals = kvvals[1:] + deleted++ + internal += time.Since(istart) + continue + } else if cmp == 0 { + // the snapshot key can be overwritten + created-- + if write = !bytes.Equal(kvvals[0], iter.Value); write { + updated++ + } else { + untouched++ + } + kvkeys = kvkeys[1:] + kvvals = kvvals[1:] + } + break + } + istart := time.Now() + if err := onState(iter.Key, iter.Value, write, false); err != nil { + return false, nil, err + } + 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 + istart := time.Now() + for _, key := range kvkeys { + if err := onState(key, nil, false, true); err != nil { + return false, nil, err + } + deleted += 1 + } + internal += time.Since(istart) + + // Update metrics for counting trie iteration + if kind == snapStorage { + snapStorageTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds()) + } else { + snapAccountTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds()) + } + logger.Debug("Regenerated state range", "root", trieId.Root, "last", hexutil.Encode(last), + "count", count, "created", created, "updated", updated, "untouched", untouched, "deleted", deleted) + + // If there are either more trie items, or there are more snap items + // (in the next segment), then we need to keep working + return !trieMore && !result.diskMore, last, nil +} + +// checkAndFlush checks if an interruption signal is received or the +// batch size has exceeded the allowance. +func (dl *diskLayer) checkAndFlush(ctx *generatorContext, current []byte) error { + var abort chan *generatorStats + select { + case abort = <-dl.genAbort: + default: + } + if ctx.batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { + if bytes.Compare(current, dl.genMarker) < 0 { + log.Error("Snapshot generator went backwards", "current", fmt.Sprintf("%x", current), "genMarker", fmt.Sprintf("%x", dl.genMarker)) + } + // Flush out the batch anyway no matter it's empty or not. + // It's possible that all the states are recovered and the + // generation indeed makes progress. + journalProgress(ctx.batch, current, ctx.stats) + + if err := ctx.batch.Write(); err != nil { + return err + } + ctx.batch.Reset() + + dl.lock.Lock() + dl.genMarker = current + dl.lock.Unlock() + + if abort != nil { + ctx.stats.Log("Aborting state snapshot generation", dl.root, current) + return newAbortErr(abort) // bubble up an error for interruption + } + // Don't hold the iterators too long, release them to let compactor works + ctx.reopenIterator(snapAccount) + ctx.reopenIterator(snapStorage) + } + if time.Since(ctx.logged) > 8*time.Second { + ctx.stats.Log("Generating state snapshot", dl.root, current) + ctx.logged = time.Now() + } + return nil +} + +// generateStorages generates the missing storage slots of the specific contract. +// It's supposed to restart the generation from the given origin position. +func generateStorages(ctx *generatorContext, dl *diskLayer, stateRoot common.Hash, account common.Hash, storageRoot common.Hash, storeMarker []byte) error { + onStorage := func(key []byte, val []byte, write bool, delete bool) error { + defer func(start time.Time) { + snapStorageWriteCounter.Inc(time.Since(start).Nanoseconds()) + }(time.Now()) + + if delete { + rawdb.DeleteStorageSnapshot(ctx.batch, account, common.BytesToHash(key)) + snapWipedStorageMeter.Mark(1) + return nil + } + if write { + rawdb.WriteStorageSnapshot(ctx.batch, account, common.BytesToHash(key), val) + snapGeneratedStorageMeter.Mark(1) + } else { + snapRecoveredStorageMeter.Mark(1) + } + ctx.stats.storage += common.StorageSize(1 + 2*common.HashLength + len(val)) + ctx.stats.slots++ + + // If we've exceeded our batch allowance or termination was requested, flush to disk + if err := dl.checkAndFlush(ctx, append(account[:], key...)); err != nil { + return err + } + return nil + } + // Loop for re-generating the missing storage slots. + var origin = common.CopyBytes(storeMarker) + for { + id := trie.StorageTrieID(stateRoot, account, storageRoot) + exhausted, last, err := dl.generateRange(ctx, id, append(rawdb.SnapshotStoragePrefix, account.Bytes()...), snapStorage, origin, storageCheckRange, onStorage, nil) + if err != nil { + return err // The procedure it aborted, either by external signal or internal error. + } + // Abort the procedure if the entire contract storage is generated + if exhausted { + break + } + if origin = increaseKey(last); origin == nil { + break // special case, the last is 0xffffffff...fff + } + } + return nil +} + +// generateAccounts generates the missing snapshot accounts as well as their +// storage slots in the main trie. It's supposed to restart the generation +// from the given origin position. +func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) error { + onAccount := func(key []byte, val []byte, write bool, delete bool) error { + // Make sure to clear all dangling storages before this account + account := common.BytesToHash(key) + ctx.removeStorageBefore(account) + + start := time.Now() + if delete { + rawdb.DeleteAccountSnapshot(ctx.batch, account) + snapWipedAccountMeter.Mark(1) + snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) + + ctx.removeStorageAt(account) + return nil + } + // Retrieve the current account and flatten it into the internal format + var acc types.StateAccount + if err := rlp.DecodeBytes(val, &acc); err != nil { + log.Crit("Invalid account encountered during snapshot creation", "err", err) + } + // If the account is not yet in-progress, write it out + if accMarker == nil || !bytes.Equal(account[:], accMarker) { + dataLen := len(val) // Approximate size, saves us a round of RLP-encoding + if !write { + if bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) { + dataLen -= 32 + } + if acc.Root == types.EmptyRootHash { + dataLen -= 32 + } + snapRecoveredAccountMeter.Mark(1) + } else { + data := types.SlimAccountRLP(acc) + dataLen = len(data) + rawdb.WriteAccountSnapshot(ctx.batch, account, data) + snapGeneratedAccountMeter.Mark(1) + } + ctx.stats.storage += common.StorageSize(1 + common.HashLength + dataLen) + ctx.stats.accounts++ + } + // If the snap generation goes here after interrupted, genMarker may go backward + // when last genMarker is consisted of accountHash and storageHash + marker := account[:] + if accMarker != nil && bytes.Equal(marker, accMarker) && len(dl.genMarker) > common.HashLength { + marker = dl.genMarker[:] + } + // If we've exceeded our batch allowance or termination was requested, flush to disk + if err := dl.checkAndFlush(ctx, marker); err != nil { + return err + } + snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) // let's count flush time as well + + // If the iterated account is the contract, create a further loop to + // verify or regenerate the contract storage. + if acc.Root == types.EmptyRootHash { + ctx.removeStorageAt(account) + } else { + var storeMarker []byte + if accMarker != nil && bytes.Equal(account[:], accMarker) && len(dl.genMarker) > common.HashLength { + storeMarker = dl.genMarker[common.HashLength:] + } + if err := generateStorages(ctx, dl, dl.root, account, acc.Root, storeMarker); err != nil { + return err + } + } + // Some account processed, unmark the marker + accMarker = nil + return nil + } + // Always reset the initial account range as 1 whenever recover from the + // interruption. TODO(rjl493456442) can we remove it? + var accountRange = accountCheckRange + if len(accMarker) > 0 { + accountRange = 1 + } + origin := common.CopyBytes(accMarker) + for { + id := trie.StateTrieID(dl.root) + exhausted, last, err := dl.generateRange(ctx, id, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, types.FullAccountRLP) + if err != nil { + return err // The procedure it aborted, either by external signal or internal error. + } + origin = increaseKey(last) + + // Last step, cleanup the storages after the last account. + // All the left storages should be treated as dangling. + if origin == nil || exhausted { + ctx.removeStorageLeft() + break + } + accountRange = accountCheckRange + } + return nil +} + +// generate is a background thread that iterates over the state and storage tries, +// constructing the state snapshot. All the arguments are purely for statistics +// gathering and logging, since the method surfs the blocks as they arrive, often +// being restarted. +func (dl *diskLayer) generate(stats *generatorStats) { + var ( + accMarker []byte + abort chan *generatorStats + ) + if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that + accMarker = dl.genMarker[:common.HashLength] + } + stats.Log("Resuming state snapshot generation", dl.root, dl.genMarker) + + // Initialize the global generator context. The snapshot iterators are + // opened at the interrupted position because the assumption is held + // that all the snapshot data are generated correctly before the marker. + // Even if the snapshot data is updated during the interruption (before + // or at the marker), the assumption is still held. + // For the account or storage slot at the interruption, they will be + // processed twice by the generator(they are already processed in the + // last run) but it's fine. + ctx := newGeneratorContext(stats, dl.diskdb, accMarker, dl.genMarker) + defer ctx.close() + + if err := generateAccounts(ctx, dl, accMarker); err != nil { + // Extract the received interruption signal if exists + if aerr, ok := err.(*abortErr); ok { + abort = aerr.abort + } + // Aborted by internal error, wait the signal + if abort == nil { + abort = <-dl.genAbort + } + abort <- stats + return + } + // Snapshot fully generated, set the marker to nil. + // Note even there is nothing to commit, persist the + // generator anyway to mark the snapshot is complete. + journalProgress(ctx.batch, nil, stats) + if err := ctx.batch.Write(); err != nil { + log.Error("Failed to flush batch", "err", err) + + abort = <-dl.genAbort + abort <- stats + return + } + ctx.batch.Reset() + + log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, + "storage", stats.storage, "dangling", stats.dangling, "elapsed", common.PrettyDuration(time.Since(stats.start))) + + dl.lock.Lock() + dl.genMarker = nil + close(dl.genPending) + dl.lock.Unlock() + + // Someone will be looking for us, wait it out + abort = <-dl.genAbort + abort <- nil +} + +// increaseKey increase the input key by one bit. Return nil if the entire +// addition operation overflows. +func increaseKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]++ + if key[i] != 0x0 { + return key + } + } + return nil +} + +// abortErr wraps an interruption signal received to represent the +// generation is aborted by external processes. +type abortErr struct { + abort chan *generatorStats +} + +func newAbortErr(abort chan *generatorStats) error { + return &abortErr{abort: abort} +} + +func (err *abortErr) Error() string { + return "aborted" +} diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go new file mode 100644 index 0000000..8911119 --- /dev/null +++ b/core/state/snapshot/generate_test.go @@ -0,0 +1,969 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "fmt" + "os" + "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/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" +) + +func hashData(input []byte) common.Hash { + var hasher = sha3.NewLegacyKeccak256() + var hash common.Hash + hasher.Reset() + hasher.Write(input) + hasher.Sum(hash[:0]) + return 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(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: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + + helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + + root, snap := helper.CommitAndGenerate() + if have, want := root, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"); have != want { + t.Fatalf("have %#x want %#x", have, want) + } + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// 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(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: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + + stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + root, snap := helper.CommitAndGenerate() + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) { + t.Helper() + + accIt := snap.AccountIterator(common.Hash{}) + defer accIt.Release() + + snapRoot, err := generateTrieRoot(nil, "", accIt, common.Hash{}, stackTrieGenerate, + func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + storageIt, _ := snap.StorageIterator(accountHash, common.Hash{}) + defer storageIt.Release() + + hash, err := generateTrieRoot(nil, "", storageIt, accountHash, stackTrieGenerate, nil, stat, false) + if err != nil { + return common.Hash{}, err + } + return hash, nil + }, newGenerateStats(), true) + if err != nil { + t.Fatal(err) + } + if snapRoot != trieRoot { + t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot) + } + if err := CheckDanglingStorage(snap.diskdb); err != nil { + t.Fatalf("Detected dangling storages: %v", err) + } +} + +type testHelper struct { + diskdb ethdb.Database + triedb *triedb.Database + accTrie *trie.StateTrie + nodes *trienode.MergedNodeSet +} + +func newHelper(scheme string) *testHelper { + diskdb := rawdb.NewMemoryDatabase() + config := &triedb.Config{} + if scheme == rawdb.PathScheme { + config.PathDB = &pathdb.Config{} // disable caching + } else { + config.HashDB = &hashdb.Config{} // disable caching + } + triedb := triedb.NewDatabase(diskdb, config) + accTrie, _ := trie.NewStateTrie(trie.StateTrieID(types.EmptyRootHash), triedb) + return &testHelper{ + diskdb: diskdb, + triedb: triedb, + accTrie: accTrie, + nodes: trienode.NewMergedNodeSet(), + } +} + +func (t *testHelper) addTrieAccount(acckey string, acc *types.StateAccount) { + val, _ := rlp.EncodeToBytes(acc) + t.accTrie.MustUpdate([]byte(acckey), val) +} + +func (t *testHelper) addSnapAccount(acckey string, acc *types.StateAccount) { + key := hashData([]byte(acckey)) + rawdb.WriteAccountSnapshot(t.diskdb, key, types.SlimAccountRLP(*acc)) +} + +func (t *testHelper) addAccount(acckey string, acc *types.StateAccount) { + t.addTrieAccount(acckey, acc) + t.addSnapAccount(acckey, acc) +} + +func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string) { + accHash := hashData([]byte(accKey)) + for i, key := range keys { + rawdb.WriteStorageSnapshot(t.diskdb, accHash, hashData([]byte(key)), []byte(vals[i])) + } +} + +func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []string, commit bool) common.Hash { + id := trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash) + stTrie, _ := trie.NewStateTrie(id, t.triedb) + for i, k := range keys { + stTrie.MustUpdate([]byte(k), []byte(vals[i])) + } + if !commit { + return stTrie.Hash() + } + root, nodes := stTrie.Commit(false) + if nodes != nil { + t.nodes.Merge(nodes) + } + return root +} + +func (t *testHelper) Commit() common.Hash { + root, nodes := t.accTrie.Commit(true) + if nodes != nil { + t.nodes.Merge(nodes) + } + t.triedb.Update(root, types.EmptyRootHash, 0, t.nodes, nil) + t.triedb.Commit(root, false) + return root +} + +func (t *testHelper) CommitAndGenerate() (common.Hash, *diskLayer) { + root := t.Commit() + snap := generateSnapshot(t.diskdb, t.triedb, 16, root) + return root, snap +} + +// Tests that snapshot generation with existent flat state, where the flat state +// contains some errors: +// - the contract with empty storage root but has storage entries in the disk +// - the contract with non empty storage root but empty storage slots +// - the contract(non-empty storage) misses some storage slots +// - miss in the beginning +// - miss in the middle +// - miss in the end +// +// - the contract(non-empty storage) has wrong storage slots +// - wrong slots in the beginning +// - wrong slots in the middle +// - wrong slots in the end +// +// - the contract(non-empty storage) has extra storage slots +// - extra slots in the beginning +// - extra slots in the middle +// - extra slots in the end +func TestGenerateExistentStateWithWrongStorage(t *testing.T) { + 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: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Account two, non empty root but empty database + stRoot := helper.makeStorageTrie(hashData([]byte("acc-2")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + + // Miss slots + { + // Account three, non empty root but misses slots in the beginning + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-3", []string{"key-2", "key-3"}, []string{"val-2", "val-3"}) + + // Account four, non empty root but misses slots in the middle + helper.makeStorageTrie(hashData([]byte("acc-4")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-4", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-4", []string{"key-1", "key-3"}, []string{"val-1", "val-3"}) + + // Account five, non empty root but misses slots in the end + helper.makeStorageTrie(hashData([]byte("acc-5")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-5", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-5", []string{"key-1", "key-2"}, []string{"val-1", "val-2"}) + } + + // Wrong storage slots + { + // Account six, non empty root but wrong slots in the beginning + helper.makeStorageTrie(hashData([]byte("acc-6")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-6", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"badval-1", "val-2", "val-3"}) + + // Account seven, non empty root but wrong slots in the middle + helper.makeStorageTrie(hashData([]byte("acc-7")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-7", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "badval-2", "val-3"}) + + // Account eight, non empty root but wrong slots in the end + helper.makeStorageTrie(hashData([]byte("acc-8")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-8", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "badval-3"}) + + // Account 9, non empty root but rotated slots + helper.makeStorageTrie(hashData([]byte("acc-9")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-9", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-3", "val-2"}) + } + + // Extra storage slots + { + // Account 10, non empty root but extra slots in the beginning + helper.makeStorageTrie(hashData([]byte("acc-10")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-10", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-10", []string{"key-0", "key-1", "key-2", "key-3"}, []string{"val-0", "val-1", "val-2", "val-3"}) + + // Account 11, non empty root but extra slots in the middle + helper.makeStorageTrie(hashData([]byte("acc-11")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-11", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-11", []string{"key-1", "key-2", "key-2-1", "key-3"}, []string{"val-1", "val-2", "val-2-1", "val-3"}) + + // Account 12, non empty root but extra slots in the end + helper.makeStorageTrie(hashData([]byte("acc-12")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-12", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapStorage("acc-12", []string{"key-1", "key-2", "key-3", "key-4"}, []string{"val-1", "val-2", "val-3", "val-4"}) + } + + root, snap := helper.CommitAndGenerate() + t.Logf("Root: %#x\n", root) // Root = 0x8746cce9fd9c658b2cfd639878ed6584b7a2b3e73bb40f607fcfa156002429a0 + + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation with existent flat state, where the flat state +// contains some errors: +// - miss accounts +// - wrong accounts +// - extra accounts +func TestGenerateExistentStateWithWrongAccounts(t *testing.T) { + 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) + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.makeStorageTrie(hashData([]byte("acc-4")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + stRoot := helper.makeStorageTrie(hashData([]byte("acc-6")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + + // Trie accounts [acc-1, acc-2, acc-3, acc-4, acc-6] + // Extra accounts [acc-0, acc-5, acc-7] + + // Missing accounts, only in the trie + { + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Beginning + helper.addTrieAccount("acc-4", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Middle + helper.addTrieAccount("acc-6", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // End + } + + // Wrong accounts + { + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: common.Hex2Bytes("0x1234")}) + + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + } + + // Extra accounts, only in the snap + { + helper.addSnapAccount("acc-0", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // before the beginning + helper.addSnapAccount("acc-5", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: common.Hex2Bytes("0x1234")}) // Middle + helper.addSnapAccount("acc-7", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // after the end + } + + root, snap := helper.CommitAndGenerate() + t.Logf("Root: %#x\n", root) // Root = 0x825891472281463511e7ebcc7f109e4f9200c20fa384754e11fd605cd98464e8 + + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// 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(scheme) + + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074 + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4 + + root := helper.Commit() // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978 + + // 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 { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt account trie") + + case <-time.After(time.Second): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation errors out correctly in case of a missing root +// 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. + 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: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + + root := helper.Commit() + + // 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 { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt storage trie") + + case <-time.After(time.Second): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// 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(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: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + + root := helper.Commit() + + // 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 { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt storage trie") + + case <-time.After(time.Second): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation when an extra account with storage exists in the snap state. +func TestGenerateWithExtraAccounts(t *testing.T) { + 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")), + []string{"key-1", "key-2", "key-3", "key-4", "key-5"}, + []string{"val-1", "val-2", "val-3", "val-4", "val-5"}, + true, + ) + acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + helper.accTrie.MustUpdate([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + + // Identical in the snap + key := hashData([]byte("acc-1")) + rawdb.WriteAccountSnapshot(helper.diskdb, key, val) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-1")), []byte("val-1")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-2")), []byte("val-2")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-3")), []byte("val-3")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-4")), []byte("val-4")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-5")), []byte("val-5")) + } + { + // Account two exists only in the snapshot + stRoot := helper.makeStorageTrie(hashData([]byte("acc-2")), + []string{"key-1", "key-2", "key-3", "key-4", "key-5"}, + []string{"val-1", "val-2", "val-3", "val-4", "val-5"}, + true, + ) + acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + key := hashData([]byte("acc-2")) + rawdb.WriteAccountSnapshot(helper.diskdb, key, val) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-1")), []byte("b-val-1")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-2")), []byte("b-val-2")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-3")), []byte("b-val-3")) + } + root := helper.Commit() + + // To verify the test: If we now inspect the snap db, there should exist extraneous storage items + if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil { + t.Fatalf("expected snap storage to exist") + } + snap := generateSnapshot(helper.diskdb, helper.triedb, 16, root) + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop + // If we now inspect the snap db, there should exist no extraneous storage items + if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil { + t.Fatalf("expected slot to be removed, got %v", string(data)) + } +} + +func enableLogging() { + 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(scheme) + { + // Account one in the trie + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), + []string{"key-1", "key-2", "key-3"}, + []string{"val-1", "val-2", "val-3"}, + true, + ) + acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + helper.accTrie.MustUpdate([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + + // Identical in the snap + key := hashData([]byte("acc-1")) + rawdb.WriteAccountSnapshot(helper.diskdb, key, val) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-1")), []byte("val-1")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-2")), []byte("val-2")) + rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-3")), []byte("val-3")) + } + { + // 100 accounts exist only in snapshot + for i := 0; i < 1000; i++ { + acc := &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + key := hashData([]byte(fmt.Sprintf("acc-%d", i))) + rawdb.WriteAccountSnapshot(helper.diskdb, key, val) + } + } + root, snap := helper.CommitAndGenerate() + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests this case +// maxAccountRange 3 +// snapshot-accounts: 01, 02, 03, 04, 05, 06, 07 +// trie-accounts: 03, 07 +// +// We iterate three snapshot storage slots (max = 3) from the database. They are 0x01, 0x02, 0x03. +// The trie has a lot of deletions. +// 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(scheme) + { + acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + helper.accTrie.MustUpdate(common.HexToHash("0x03").Bytes(), val) + helper.accTrie.MustUpdate(common.HexToHash("0x07").Bytes(), val) + + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x01"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x02"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x03"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x04"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x05"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x06"), val) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x07"), val) + } + root, snap := helper.CommitAndGenerate() + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// 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(scheme) + { + acc := &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + helper.accTrie.MustUpdate(common.HexToHash("0x03").Bytes(), val) + + junk := make([]byte, 100) + copy(junk, []byte{0xde, 0xad}) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x02"), junk) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x03"), junk) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x04"), junk) + rawdb.WriteAccountSnapshot(helper.diskdb, common.HexToHash("0x05"), junk) + } + root, snap := helper.CommitAndGenerate() + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop + // If we now inspect the snap db, there should exist no extraneous storage items + if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil { + t.Fatalf("expected slot to be removed, got %v", string(data)) + } +} + +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(scheme) + // Add 1K accounts to the trie + for i := 0; i < 400; i++ { + stRoot := helper.makeStorageTrie(hashData([]byte(fmt.Sprintf("acc-%d", i))), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount(fmt.Sprintf("acc-%d", i), + &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + } + root, snap := helper.CommitAndGenerate() + t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4 + + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation with existent flat state, where the flat state +// storage is correct, but incomplete. +// The incomplete part is on the second range +// snap: [ 0x01, 0x02, 0x03, 0x04] , [ 0x05, 0x06, 0x07, {missing}] (with storageCheck = 4) +// trie: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 +// 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(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 + // we don't have to order the keys and figure out exactly which hash-key winds up + // on the sensitive spots at the boundaries + for i := 0; i < 8; i++ { + accKey := fmt.Sprintf("acc-%d", i) + stRoot := helper.makeStorageTrie(hashData([]byte(accKey)), stKeys, stVals, true) + helper.addAccount(accKey, &types.StateAccount{Balance: uint256.NewInt(uint64(i)), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + var moddedKeys []string + var moddedVals []string + for ii := 0; ii < 8; ii++ { + if ii != i { + moddedKeys = append(moddedKeys, stKeys[ii]) + moddedVals = append(moddedVals, stVals[ii]) + } + } + helper.addSnapStorage(accKey, moddedKeys, moddedVals) + } + root, snap := helper.CommitAndGenerate() + t.Logf("Root: %#x\n", root) // Root: 0xca73f6f05ba4ca3024ef340ef3dfca8fdabc1b677ff13f5a9571fd49c16e67ff + + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +func incKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]++ + if key[i] != 0x0 { + break + } + } + return key +} + +func decKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]-- + if key[i] != 0xff { + break + } + } + return key +} + +func populateDangling(disk ethdb.KeyValueStore) { + populate := func(accountHash common.Hash, keys []string, vals []string) { + for i, key := range keys { + rawdb.WriteStorageSnapshot(disk, accountHash, hashData([]byte(key)), []byte(vals[i])) + } + } + // Dangling storages of the "first" account + populate(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages of the "last" account + populate(common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages around the account 1 + hash := decKey(hashData([]byte("acc-1")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + hash = incKey(hashData([]byte("acc-1")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages around the account 2 + hash = decKey(hashData([]byte("acc-2")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + hash = incKey(hashData([]byte("acc-2")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages around the account 3 + hash = decKey(hashData([]byte("acc-3")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + hash = incKey(hashData([]byte("acc-3")).Bytes()) + populate(common.BytesToHash(hash), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Dangling storages of the random account + populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + populate(randomHash(), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) +} + +// Tests that snapshot generation with dangling storages. Dangling storage means +// the storage data is existent while the corresponding account data is missing. +// +// This test will populate some dangling storages to see if they can be cleaned up. +func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) { + 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: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + + helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + populateDangling(helper.diskdb) + + root, snap := helper.CommitAndGenerate() + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation with dangling storages. Dangling storage means +// the storage data is existent while the corresponding account data is missing. +// +// This test will populate some dangling storages to see if they can be cleaned up. +func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) { + 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: uint256.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: uint256.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + + helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: uint256.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + + populateDangling(helper.diskdb) + + root, snap := helper.CommitAndGenerate() + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(3 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} diff --git a/core/state/snapshot/holdable_iterator.go b/core/state/snapshot/holdable_iterator.go new file mode 100644 index 0000000..1e86ff9 --- /dev/null +++ b/core/state/snapshot/holdable_iterator.go @@ -0,0 +1,97 @@ +// 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 snapshot + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +// holdableIterator is a wrapper of underlying database iterator. It extends +// the basic iterator interface by adding Hold which can hold the element +// locally where the iterator is currently located and serve it up next time. +type holdableIterator struct { + it ethdb.Iterator + key []byte + val []byte + atHeld bool +} + +// newHoldableIterator initializes the holdableIterator with the given iterator. +func newHoldableIterator(it ethdb.Iterator) *holdableIterator { + return &holdableIterator{it: it} +} + +// Hold holds the element locally where the iterator is currently located which +// can be served up next time. +func (it *holdableIterator) Hold() { + if it.it.Key() == nil { + return // nothing to hold + } + it.key = common.CopyBytes(it.it.Key()) + it.val = common.CopyBytes(it.it.Value()) + it.atHeld = false +} + +// Next moves the iterator to the next key/value pair. It returns whether the +// iterator is exhausted. +func (it *holdableIterator) Next() bool { + if !it.atHeld && it.key != nil { + it.atHeld = true + } else if it.atHeld { + it.atHeld = false + it.key = nil + it.val = nil + } + if it.key != nil { + return true // shifted to locally held value + } + return it.it.Next() +} + +// Error returns any accumulated error. Exhausting all the key/value pairs +// is not considered to be an error. +func (it *holdableIterator) Error() error { return it.it.Error() } + +// Release releases associated resources. Release should always succeed and can +// be called multiple times without causing error. +func (it *holdableIterator) Release() { + it.atHeld = false + it.key = nil + it.val = nil + it.it.Release() +} + +// Key returns the key of the current key/value pair, or nil if done. The caller +// should not modify the contents of the returned slice, and its contents may +// change on the next call to Next. +func (it *holdableIterator) Key() []byte { + if it.key != nil { + return it.key + } + return it.it.Key() +} + +// Value returns the value of the current key/value pair, or nil if done. The +// caller should not modify the contents of the returned slice, and its contents +// may change on the next call to Next. +func (it *holdableIterator) Value() []byte { + if it.val != nil { + return it.val + } + return it.it.Value() +} diff --git a/core/state/snapshot/holdable_iterator_test.go b/core/state/snapshot/holdable_iterator_test.go new file mode 100644 index 0000000..ce4cf6b --- /dev/null +++ b/core/state/snapshot/holdable_iterator_test.go @@ -0,0 +1,163 @@ +// 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 snapshot + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" +) + +func TestIteratorHold(t *testing.T) { + // Create the key-value data store + var ( + content = map[string]string{"k1": "v1", "k2": "v2", "k3": "v3"} + order = []string{"k1", "k2", "k3"} + db = rawdb.NewMemoryDatabase() + ) + for key, val := range content { + if err := db.Put([]byte(key), []byte(val)); err != nil { + t.Fatalf("failed to insert item %s:%s into database: %v", key, val, err) + } + } + // Iterate over the database with the given configs and verify the results + it, idx := newHoldableIterator(db.NewIterator(nil, nil)), 0 + + // Nothing should be affected for calling Discard on non-initialized iterator + it.Hold() + + for it.Next() { + if len(content) <= idx { + t.Errorf("more items than expected: checking idx=%d (key %q), expecting len=%d", idx, it.Key(), len(order)) + break + } + if !bytes.Equal(it.Key(), []byte(order[idx])) { + t.Errorf("item %d: key mismatch: have %s, want %s", idx, string(it.Key()), order[idx]) + } + if !bytes.Equal(it.Value(), []byte(content[order[idx]])) { + t.Errorf("item %d: value mismatch: have %s, want %s", idx, string(it.Value()), content[order[idx]]) + } + // Should be safe to call discard multiple times + it.Hold() + it.Hold() + + // Shift iterator to the discarded element + it.Next() + if !bytes.Equal(it.Key(), []byte(order[idx])) { + t.Errorf("item %d: key mismatch: have %s, want %s", idx, string(it.Key()), order[idx]) + } + if !bytes.Equal(it.Value(), []byte(content[order[idx]])) { + t.Errorf("item %d: value mismatch: have %s, want %s", idx, string(it.Value()), content[order[idx]]) + } + + // Discard/Next combo should work always + it.Hold() + it.Next() + if !bytes.Equal(it.Key(), []byte(order[idx])) { + t.Errorf("item %d: key mismatch: have %s, want %s", idx, string(it.Key()), order[idx]) + } + if !bytes.Equal(it.Value(), []byte(content[order[idx]])) { + t.Errorf("item %d: value mismatch: have %s, want %s", idx, string(it.Value()), content[order[idx]]) + } + idx++ + } + if err := it.Error(); err != nil { + t.Errorf("iteration failed: %v", err) + } + if idx != len(order) { + t.Errorf("iteration terminated prematurely: have %d, want %d", idx, len(order)) + } + db.Close() +} + +func TestReopenIterator(t *testing.T) { + var ( + content = map[common.Hash]string{ + common.HexToHash("a1"): "v1", + common.HexToHash("a2"): "v2", + common.HexToHash("a3"): "v3", + common.HexToHash("a4"): "v4", + common.HexToHash("a5"): "v5", + common.HexToHash("a6"): "v6", + } + order = []common.Hash{ + common.HexToHash("a1"), + common.HexToHash("a2"), + common.HexToHash("a3"), + common.HexToHash("a4"), + common.HexToHash("a5"), + common.HexToHash("a6"), + } + db = rawdb.NewMemoryDatabase() + ) + for key, val := range content { + rawdb.WriteAccountSnapshot(db, key, []byte(val)) + } + checkVal := func(it *holdableIterator, index int) { + if !bytes.Equal(it.Key(), append(rawdb.SnapshotAccountPrefix, order[index].Bytes()...)) { + t.Fatalf("Unexpected data entry key, want %v got %v", order[index], it.Key()) + } + if !bytes.Equal(it.Value(), []byte(content[order[index]])) { + t.Fatalf("Unexpected data entry key, want %v got %v", []byte(content[order[index]]), it.Value()) + } + } + // Iterate over the database with the given configs and verify the results + ctx, idx := newGeneratorContext(&generatorStats{}, db, nil, nil), -1 + + idx++ + ctx.account.Next() + checkVal(ctx.account, idx) + + ctx.reopenIterator(snapAccount) + idx++ + ctx.account.Next() + checkVal(ctx.account, idx) + + // reopen twice + ctx.reopenIterator(snapAccount) + ctx.reopenIterator(snapAccount) + idx++ + ctx.account.Next() + checkVal(ctx.account, idx) + + // reopen iterator with held value + ctx.account.Next() + ctx.account.Hold() + ctx.reopenIterator(snapAccount) + idx++ + ctx.account.Next() + checkVal(ctx.account, idx) + + // reopen twice iterator with held value + ctx.account.Next() + ctx.account.Hold() + ctx.reopenIterator(snapAccount) + ctx.reopenIterator(snapAccount) + idx++ + ctx.account.Next() + checkVal(ctx.account, idx) + + // shift to the end and reopen + ctx.account.Next() // the end + ctx.reopenIterator(snapAccount) + ctx.account.Next() + if ctx.account.Key() != nil { + t.Fatal("Unexpected iterated entry") + } +} diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go new file mode 100644 index 0000000..c1a196c --- /dev/null +++ b/core/state/snapshot/iterator.go @@ -0,0 +1,400 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "fmt" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" +) + +// Iterator is an iterator to step over all the accounts or the specific +// storage in a snapshot which may or may not be composed of multiple layers. +type Iterator interface { + // Next steps the iterator forward one element, returning false if exhausted, + // or an error if iteration failed for some reason (e.g. root being iterated + // becomes stale and garbage collected). + Next() bool + + // Error returns any failure that occurred during iteration, which might have + // caused a premature iteration exit (e.g. snapshot stack becoming stale). + Error() error + + // Hash returns the hash of the account or storage slot the iterator is + // currently at. + Hash() common.Hash + + // Release releases associated resources. Release should always succeed and + // can be called multiple times without causing error. + Release() +} + +// AccountIterator is an iterator to step over all the accounts in a snapshot, +// which may or may not be composed of multiple layers. +type AccountIterator interface { + Iterator + + // Account returns the RLP encoded slim account the iterator is currently at. + // An error will be returned if the iterator becomes invalid + Account() []byte +} + +// StorageIterator is an iterator to step over the specific storage in a snapshot, +// which may or may not be composed of multiple layers. +type StorageIterator interface { + Iterator + + // Slot returns the storage slot the iterator is currently at. An error will + // be returned if the iterator becomes invalid + Slot() []byte +} + +// diffAccountIterator is an account iterator that steps over the accounts (both +// live and deleted) contained within a single diff layer. Higher order iterators +// will use the deleted accounts to skip deeper iterators. +type diffAccountIterator struct { + // curHash is the current hash the iterator is positioned on. The field is + // explicitly tracked since the referenced diff layer might go stale after + // the iterator was positioned and we don't want to fail accessing the old + // hash as long as the iterator is not touched any more. + curHash common.Hash + + layer *diffLayer // Live layer to retrieve values from + keys []common.Hash // Keys left in the layer to iterate + fail error // Any failures encountered (stale) +} + +// AccountIterator creates an account iterator over a single diff layer. +func (dl *diffLayer) AccountIterator(seek common.Hash) AccountIterator { + // Seek out the requested starting account + hashes := dl.AccountList() + index := sort.Search(len(hashes), func(i int) bool { + return bytes.Compare(seek[:], hashes[i][:]) <= 0 + }) + // Assemble and returned the already seeked iterator + return &diffAccountIterator{ + layer: dl, + keys: hashes[index:], + } +} + +// Next steps the iterator forward one element, returning false if exhausted. +func (it *diffAccountIterator) Next() bool { + // If the iterator was already stale, consider it a programmer error. Although + // we could just return false here, triggering this path would probably mean + // somebody forgot to check for Error, so lets blow up instead of undefined + // behavior that's hard to debug. + if it.fail != nil { + panic(fmt.Sprintf("called Next of failed iterator: %v", it.fail)) + } + // Stop iterating if all keys were exhausted + if len(it.keys) == 0 { + return false + } + if it.layer.Stale() { + it.fail, it.keys = ErrSnapshotStale, nil + return false + } + // Iterator seems to be still alive, retrieve and cache the live hash + it.curHash = it.keys[0] + // key cached, shift the iterator and notify the user of success + it.keys = it.keys[1:] + return true +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +func (it *diffAccountIterator) Error() error { + return it.fail +} + +// Hash returns the hash of the account the iterator is currently at. +func (it *diffAccountIterator) Hash() common.Hash { + return it.curHash +} + +// Account returns the RLP encoded slim account the iterator is currently at. +// This method may _fail_, if the underlying layer has been flattened between +// the call to Next and Account. That type of error will set it.Err. +// This method assumes that flattening does not delete elements from +// the accountdata mapping (writing nil into it is fine though), and will panic +// if elements have been deleted. +// +// Note the returned account is not a copy, please don't modify it. +func (it *diffAccountIterator) Account() []byte { + it.layer.lock.RLock() + blob, ok := it.layer.accountData[it.curHash] + if !ok { + if _, ok := it.layer.destructSet[it.curHash]; ok { + it.layer.lock.RUnlock() + return nil + } + panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash)) + } + it.layer.lock.RUnlock() + if it.layer.Stale() { + it.fail, it.keys = ErrSnapshotStale, nil + } + return blob +} + +// Release is a noop for diff account iterators as there are no held resources. +func (it *diffAccountIterator) Release() {} + +// diskAccountIterator is an account iterator that steps over the live accounts +// contained within a disk layer. +type diskAccountIterator struct { + layer *diskLayer + it ethdb.Iterator +} + +// AccountIterator creates an account iterator over a disk layer. +func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator { + pos := common.TrimRightZeroes(seek[:]) + return &diskAccountIterator{ + layer: dl, + it: dl.diskdb.NewIterator(rawdb.SnapshotAccountPrefix, pos), + } +} + +// Next steps the iterator forward one element, returning false if exhausted. +func (it *diskAccountIterator) Next() bool { + // If the iterator was already exhausted, don't bother + if it.it == nil { + return false + } + // Try to advance the iterator and release it if we reached the end + for { + if !it.it.Next() { + it.it.Release() + it.it = nil + return false + } + if len(it.it.Key()) == len(rawdb.SnapshotAccountPrefix)+common.HashLength { + break + } + } + return true +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +// +// A diff layer is immutable after creation content wise and can always be fully +// iterated without error, so this method always returns nil. +func (it *diskAccountIterator) Error() error { + if it.it == nil { + return nil // Iterator is exhausted and released + } + return it.it.Error() +} + +// Hash returns the hash of the account the iterator is currently at. +func (it *diskAccountIterator) Hash() common.Hash { + return common.BytesToHash(it.it.Key()) // The prefix will be truncated +} + +// Account returns the RLP encoded slim account the iterator is currently at. +func (it *diskAccountIterator) Account() []byte { + return it.it.Value() +} + +// Release releases the database snapshot held during iteration. +func (it *diskAccountIterator) Release() { + // The iterator is auto-released on exhaustion, so make sure it's still alive + if it.it != nil { + it.it.Release() + it.it = nil + } +} + +// diffStorageIterator is a storage iterator that steps over the specific storage +// (both live and deleted) contained within a single diff layer. Higher order +// iterators will use the deleted slot to skip deeper iterators. +type diffStorageIterator struct { + // curHash is the current hash the iterator is positioned on. The field is + // explicitly tracked since the referenced diff layer might go stale after + // the iterator was positioned and we don't want to fail accessing the old + // hash as long as the iterator is not touched any more. + curHash common.Hash + account common.Hash + + layer *diffLayer // Live layer to retrieve values from + keys []common.Hash // Keys left in the layer to iterate + fail error // Any failures encountered (stale) +} + +// StorageIterator creates a storage iterator over a single diff layer. +// Except the storage iterator is returned, there is an additional flag +// "destructed" returned. If it's true then it means the whole storage is +// destructed in this layer(maybe recreated too), don't bother deeper layer +// for storage retrieval. +func (dl *diffLayer) StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) { + // Create the storage for this account even it's marked + // as destructed. The iterator is for the new one which + // just has the same address as the deleted one. + hashes, destructed := dl.StorageList(account) + index := sort.Search(len(hashes), func(i int) bool { + return bytes.Compare(seek[:], hashes[i][:]) <= 0 + }) + // Assemble and returned the already seeked iterator + return &diffStorageIterator{ + layer: dl, + account: account, + keys: hashes[index:], + }, destructed +} + +// Next steps the iterator forward one element, returning false if exhausted. +func (it *diffStorageIterator) Next() bool { + // If the iterator was already stale, consider it a programmer error. Although + // we could just return false here, triggering this path would probably mean + // somebody forgot to check for Error, so lets blow up instead of undefined + // behavior that's hard to debug. + if it.fail != nil { + panic(fmt.Sprintf("called Next of failed iterator: %v", it.fail)) + } + // Stop iterating if all keys were exhausted + if len(it.keys) == 0 { + return false + } + if it.layer.Stale() { + it.fail, it.keys = ErrSnapshotStale, nil + return false + } + // Iterator seems to be still alive, retrieve and cache the live hash + it.curHash = it.keys[0] + // key cached, shift the iterator and notify the user of success + it.keys = it.keys[1:] + return true +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +func (it *diffStorageIterator) Error() error { + return it.fail +} + +// Hash returns the hash of the storage slot the iterator is currently at. +func (it *diffStorageIterator) Hash() common.Hash { + return it.curHash +} + +// Slot returns the raw storage slot value the iterator is currently at. +// This method may _fail_, if the underlying layer has been flattened between +// the call to Next and Value. That type of error will set it.Err. +// This method assumes that flattening does not delete elements from +// the storage mapping (writing nil into it is fine though), and will panic +// if elements have been deleted. +// +// Note the returned slot is not a copy, please don't modify it. +func (it *diffStorageIterator) Slot() []byte { + it.layer.lock.RLock() + storage, ok := it.layer.storageData[it.account] + if !ok { + panic(fmt.Sprintf("iterator referenced non-existent account storage: %x", it.account)) + } + // Storage slot might be nil(deleted), but it must exist + blob, ok := storage[it.curHash] + if !ok { + panic(fmt.Sprintf("iterator referenced non-existent storage slot: %x", it.curHash)) + } + it.layer.lock.RUnlock() + if it.layer.Stale() { + it.fail, it.keys = ErrSnapshotStale, nil + } + return blob +} + +// Release is a noop for diff account iterators as there are no held resources. +func (it *diffStorageIterator) Release() {} + +// diskStorageIterator is a storage iterator that steps over the live storage +// contained within a disk layer. +type diskStorageIterator struct { + layer *diskLayer + account common.Hash + it ethdb.Iterator +} + +// StorageIterator creates a storage iterator over a disk layer. +// If the whole storage is destructed, then all entries in the disk +// layer are deleted already. So the "destructed" flag returned here +// is always false. +func (dl *diskLayer) StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) { + pos := common.TrimRightZeroes(seek[:]) + return &diskStorageIterator{ + layer: dl, + account: account, + it: dl.diskdb.NewIterator(append(rawdb.SnapshotStoragePrefix, account.Bytes()...), pos), + }, false +} + +// Next steps the iterator forward one element, returning false if exhausted. +func (it *diskStorageIterator) Next() bool { + // If the iterator was already exhausted, don't bother + if it.it == nil { + return false + } + // Try to advance the iterator and release it if we reached the end + for { + if !it.it.Next() { + it.it.Release() + it.it = nil + return false + } + if len(it.it.Key()) == len(rawdb.SnapshotStoragePrefix)+common.HashLength+common.HashLength { + break + } + } + return true +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +// +// A diff layer is immutable after creation content wise and can always be fully +// iterated without error, so this method always returns nil. +func (it *diskStorageIterator) Error() error { + if it.it == nil { + return nil // Iterator is exhausted and released + } + return it.it.Error() +} + +// Hash returns the hash of the storage slot the iterator is currently at. +func (it *diskStorageIterator) Hash() common.Hash { + return common.BytesToHash(it.it.Key()) // The prefix will be truncated +} + +// Slot returns the raw storage slot content the iterator is currently at. +func (it *diskStorageIterator) Slot() []byte { + return it.it.Value() +} + +// Release releases the database snapshot held during iteration. +func (it *diskStorageIterator) Release() { + // The iterator is auto-released on exhaustion, so make sure it's still alive + if it.it != nil { + it.it.Release() + it.it = nil + } +} diff --git a/core/state/snapshot/iterator_binary.go b/core/state/snapshot/iterator_binary.go new file mode 100644 index 0000000..edf4712 --- /dev/null +++ b/core/state/snapshot/iterator_binary.go @@ -0,0 +1,213 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) + +// binaryIterator is a simplistic iterator to step over the accounts or storage +// in a snapshot, which may or may not be composed of multiple layers. Performance +// wise this iterator is slow, it's meant for cross validating the fast one, +type binaryIterator struct { + a Iterator + b Iterator + aDone bool + bDone bool + accountIterator bool + k common.Hash + account common.Hash + fail error +} + +// initBinaryAccountIterator creates a simplistic iterator to step over all the +// accounts in a slow, but easily verifiable way. Note this function is used for +// initialization, use `newBinaryAccountIterator` as the API. +func (dl *diffLayer) initBinaryAccountIterator() Iterator { + parent, ok := dl.parent.(*diffLayer) + if !ok { + l := &binaryIterator{ + a: dl.AccountIterator(common.Hash{}), + b: dl.Parent().AccountIterator(common.Hash{}), + accountIterator: true, + } + l.aDone = !l.a.Next() + l.bDone = !l.b.Next() + return l + } + l := &binaryIterator{ + a: dl.AccountIterator(common.Hash{}), + b: parent.initBinaryAccountIterator(), + accountIterator: true, + } + l.aDone = !l.a.Next() + l.bDone = !l.b.Next() + return l +} + +// initBinaryStorageIterator creates a simplistic iterator to step over all the +// storage slots in a slow, but easily verifiable way. Note this function is used +// for initialization, use `newBinaryStorageIterator` as the API. +func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator { + parent, ok := dl.parent.(*diffLayer) + if !ok { + // If the storage in this layer is already destructed, discard all + // deeper layers but still return a valid single-branch iterator. + a, destructed := dl.StorageIterator(account, common.Hash{}) + if destructed { + l := &binaryIterator{ + a: a, + account: account, + } + l.aDone = !l.a.Next() + l.bDone = true + return l + } + // The parent is disk layer, don't need to take care "destructed" + // anymore. + b, _ := dl.Parent().StorageIterator(account, common.Hash{}) + l := &binaryIterator{ + a: a, + b: b, + account: account, + } + l.aDone = !l.a.Next() + l.bDone = !l.b.Next() + return l + } + // If the storage in this layer is already destructed, discard all + // deeper layers but still return a valid single-branch iterator. + a, destructed := dl.StorageIterator(account, common.Hash{}) + if destructed { + l := &binaryIterator{ + a: a, + account: account, + } + l.aDone = !l.a.Next() + l.bDone = true + return l + } + l := &binaryIterator{ + a: a, + b: parent.initBinaryStorageIterator(account), + account: account, + } + l.aDone = !l.a.Next() + l.bDone = !l.b.Next() + return l +} + +// Next steps the iterator forward one element, returning false if exhausted, +// or an error if iteration failed for some reason (e.g. root being iterated +// becomes stale and garbage collected). +func (it *binaryIterator) Next() bool { + if it.aDone && it.bDone { + return false + } +first: + if it.aDone { + it.k = it.b.Hash() + it.bDone = !it.b.Next() + return true + } + if it.bDone { + it.k = it.a.Hash() + it.aDone = !it.a.Next() + return true + } + nextA, nextB := it.a.Hash(), it.b.Hash() + if diff := bytes.Compare(nextA[:], nextB[:]); diff < 0 { + it.aDone = !it.a.Next() + it.k = nextA + return true + } else if diff == 0 { + // Now we need to advance one of them + it.aDone = !it.a.Next() + goto first + } + it.bDone = !it.b.Next() + it.k = nextB + return true +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +func (it *binaryIterator) Error() error { + return it.fail +} + +// Hash returns the hash of the account the iterator is currently at. +func (it *binaryIterator) Hash() common.Hash { + return it.k +} + +// Account returns the RLP encoded slim account the iterator is currently at, or +// nil if the iterated snapshot stack became stale (you can check Error after +// to see if it failed or not). +// +// Note the returned account is not a copy, please don't modify it. +func (it *binaryIterator) Account() []byte { + if !it.accountIterator { + return nil + } + // The topmost iterator must be `diffAccountIterator` + blob, err := it.a.(*diffAccountIterator).layer.AccountRLP(it.k) + if err != nil { + it.fail = err + return nil + } + return blob +} + +// Slot returns the raw storage slot data the iterator is currently at, or +// nil if the iterated snapshot stack became stale (you can check Error after +// to see if it failed or not). +// +// Note the returned slot is not a copy, please don't modify it. +func (it *binaryIterator) Slot() []byte { + if it.accountIterator { + return nil + } + blob, err := it.a.(*diffStorageIterator).layer.Storage(it.account, it.k) + if err != nil { + it.fail = err + return nil + } + return blob +} + +// Release recursively releases all the iterators in the stack. +func (it *binaryIterator) Release() { + it.a.Release() + it.b.Release() +} + +// newBinaryAccountIterator creates a simplistic account iterator to step over +// all the accounts in a slow, but easily verifiable way. +func (dl *diffLayer) newBinaryAccountIterator() AccountIterator { + iter := dl.initBinaryAccountIterator() + return iter.(AccountIterator) +} + +// newBinaryStorageIterator creates a simplistic account iterator to step over +// all the storage slots in a slow, but easily verifiable way. +func (dl *diffLayer) newBinaryStorageIterator(account common.Hash) StorageIterator { + iter := dl.initBinaryStorageIterator(account) + return iter.(StorageIterator) +} diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go new file mode 100644 index 0000000..fa0daea --- /dev/null +++ b/core/state/snapshot/iterator_fast.go @@ -0,0 +1,344 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "fmt" + "slices" + "sort" + + "github.com/ethereum/go-ethereum/common" +) + +// weightedIterator is an iterator with an assigned weight. It is used to prioritise +// which account or storage slot is the correct one if multiple iterators find the +// same one (modified in multiple consecutive blocks). +type weightedIterator struct { + it Iterator + priority int +} + +func (it *weightedIterator) Cmp(other *weightedIterator) int { + // Order the iterators primarily by the account hashes + hashI := it.it.Hash() + hashJ := other.it.Hash() + + switch bytes.Compare(hashI[:], hashJ[:]) { + case -1: + return -1 + case 1: + return 1 + } + // Same account/storage-slot in multiple layers, split by priority + if it.priority < other.priority { + return -1 + } + if it.priority > other.priority { + return 1 + } + return 0 +} + +// fastIterator is a more optimized multi-layer iterator which maintains a +// direct mapping of all iterators leading down to the bottom layer. +type fastIterator struct { + tree *Tree // Snapshot tree to reinitialize stale sub-iterators with + root common.Hash // Root hash to reinitialize stale sub-iterators through + + curAccount []byte + curSlot []byte + + iterators []*weightedIterator + initiated bool + account bool + fail error +} + +// newFastIterator creates a new hierarchical account or storage iterator with one +// element per diff layer. The returned combo iterator can be used to walk over +// the entire snapshot diff stack simultaneously. +func newFastIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash, accountIterator bool) (*fastIterator, error) { + snap := tree.Snapshot(root) + if snap == nil { + return nil, fmt.Errorf("unknown snapshot: %x", root) + } + fi := &fastIterator{ + tree: tree, + root: root, + account: accountIterator, + } + current := snap.(snapshot) + for depth := 0; current != nil; depth++ { + if accountIterator { + fi.iterators = append(fi.iterators, &weightedIterator{ + it: current.AccountIterator(seek), + priority: depth, + }) + } else { + // If the whole storage is destructed in this layer, don't + // bother deeper layer anymore. But we should still keep + // the iterator for this layer, since the iterator can contain + // some valid slots which belongs to the re-created account. + it, destructed := current.StorageIterator(account, seek) + fi.iterators = append(fi.iterators, &weightedIterator{ + it: it, + priority: depth, + }) + if destructed { + break + } + } + current = current.Parent() + } + fi.init() + return fi, nil +} + +// init walks over all the iterators and resolves any clashes between them, after +// which it prepares the stack for step-by-step iteration. +func (fi *fastIterator) init() { + // Track which account hashes are iterators positioned on + var positioned = make(map[common.Hash]int) + + // Position all iterators and track how many remain live + for i := 0; i < len(fi.iterators); i++ { + // Retrieve the first element and if it clashes with a previous iterator, + // advance either the current one or the old one. Repeat until nothing is + // clashing any more. + it := fi.iterators[i] + for { + // If the iterator is exhausted, drop it off the end + if !it.it.Next() { + it.it.Release() + last := len(fi.iterators) - 1 + + fi.iterators[i] = fi.iterators[last] + fi.iterators[last] = nil + fi.iterators = fi.iterators[:last] + + i-- + break + } + // The iterator is still alive, check for collisions with previous ones + hash := it.it.Hash() + if other, exist := positioned[hash]; !exist { + positioned[hash] = i + break + } else { + // Iterators collide, one needs to be progressed, use priority to + // determine which. + // + // This whole else-block can be avoided, if we instead + // do an initial priority-sort of the iterators. If we do that, + // then we'll only wind up here if a lower-priority (preferred) iterator + // has the same value, and then we will always just continue. + // However, it costs an extra sort, so it's probably not better + if fi.iterators[other].priority < it.priority { + // The 'it' should be progressed + continue + } else { + // The 'other' should be progressed, swap them + it = fi.iterators[other] + fi.iterators[other], fi.iterators[i] = fi.iterators[i], fi.iterators[other] + continue + } + } + } + } + // Re-sort the entire list + slices.SortFunc(fi.iterators, func(a, b *weightedIterator) int { return a.Cmp(b) }) + fi.initiated = false +} + +// Next steps the iterator forward one element, returning false if exhausted. +func (fi *fastIterator) Next() bool { + if len(fi.iterators) == 0 { + return false + } + if !fi.initiated { + // Don't forward first time -- we had to 'Next' once in order to + // do the sorting already + fi.initiated = true + if fi.account { + fi.curAccount = fi.iterators[0].it.(AccountIterator).Account() + } else { + fi.curSlot = fi.iterators[0].it.(StorageIterator).Slot() + } + if innerErr := fi.iterators[0].it.Error(); innerErr != nil { + fi.fail = innerErr + return false + } + if fi.curAccount != nil || fi.curSlot != nil { + return true + } + // Implicit else: we've hit a nil-account or nil-slot, and need to + // fall through to the loop below to land on something non-nil + } + // If an account or a slot is deleted in one of the layers, the key will + // still be there, but the actual value will be nil. However, the iterator + // should not export nil-values (but instead simply omit the key), so we + // need to loop here until we either + // - get a non-nil value, + // - hit an error, + // - or exhaust the iterator + for { + if !fi.next(0) { + return false // exhausted + } + if fi.account { + fi.curAccount = fi.iterators[0].it.(AccountIterator).Account() + } else { + fi.curSlot = fi.iterators[0].it.(StorageIterator).Slot() + } + if innerErr := fi.iterators[0].it.Error(); innerErr != nil { + fi.fail = innerErr + return false // error + } + if fi.curAccount != nil || fi.curSlot != nil { + break // non-nil value found + } + } + return true +} + +// next handles the next operation internally and should be invoked when we know +// that two elements in the list may have the same value. +// +// For example, if the iterated hashes become [2,3,5,5,8,9,10], then we should +// invoke next(3), which will call Next on elem 3 (the second '5') and will +// cascade along the list, applying the same operation if needed. +func (fi *fastIterator) next(idx int) bool { + // If this particular iterator got exhausted, remove it and return true (the + // next one is surely not exhausted yet, otherwise it would have been removed + // already). + if it := fi.iterators[idx].it; !it.Next() { + it.Release() + + fi.iterators = append(fi.iterators[:idx], fi.iterators[idx+1:]...) + return len(fi.iterators) > 0 + } + // If there's no one left to cascade into, return + if idx == len(fi.iterators)-1 { + return true + } + // We next-ed the iterator at 'idx', now we may have to re-sort that element + var ( + cur, next = fi.iterators[idx], fi.iterators[idx+1] + curHash, nextHash = cur.it.Hash(), next.it.Hash() + ) + if diff := bytes.Compare(curHash[:], nextHash[:]); diff < 0 { + // It is still in correct place + return true + } else if diff == 0 && cur.priority < next.priority { + // So still in correct place, but we need to iterate on the next + fi.next(idx + 1) + return true + } + // At this point, the iterator is in the wrong location, but the remaining + // list is sorted. Find out where to move the item. + clash := -1 + index := sort.Search(len(fi.iterators), func(n int) bool { + // The iterator always advances forward, so anything before the old slot + // is known to be behind us, so just skip them altogether. This actually + // is an important clause since the sort order got invalidated. + if n < idx { + return false + } + if n == len(fi.iterators)-1 { + // Can always place an elem last + return true + } + nextHash := fi.iterators[n+1].it.Hash() + if diff := bytes.Compare(curHash[:], nextHash[:]); diff < 0 { + return true + } else if diff > 0 { + return false + } + // The elem we're placing it next to has the same value, + // so whichever winds up on n+1 will need further iteration + clash = n + 1 + + return cur.priority < fi.iterators[n+1].priority + }) + fi.move(idx, index) + if clash != -1 { + fi.next(clash) + } + return true +} + +// move advances an iterator to another position in the list. +func (fi *fastIterator) move(index, newpos int) { + elem := fi.iterators[index] + copy(fi.iterators[index:], fi.iterators[index+1:newpos+1]) + fi.iterators[newpos] = elem +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +func (fi *fastIterator) Error() error { + return fi.fail +} + +// Hash returns the current key +func (fi *fastIterator) Hash() common.Hash { + return fi.iterators[0].it.Hash() +} + +// Account returns the current account blob. +// Note the returned account is not a copy, please don't modify it. +func (fi *fastIterator) Account() []byte { + return fi.curAccount +} + +// Slot returns the current storage slot. +// Note the returned slot is not a copy, please don't modify it. +func (fi *fastIterator) Slot() []byte { + return fi.curSlot +} + +// Release iterates over all the remaining live layer iterators and releases each +// of them individually. +func (fi *fastIterator) Release() { + for _, it := range fi.iterators { + it.it.Release() + } + fi.iterators = nil +} + +// Debug is a convenience helper during testing +func (fi *fastIterator) Debug() { + for _, it := range fi.iterators { + fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Hash()[0]) + } + fmt.Println() +} + +// newFastAccountIterator creates a new hierarchical account iterator with one +// element per diff layer. The returned combo iterator can be used to walk over +// the entire snapshot diff stack simultaneously. +func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (AccountIterator, error) { + return newFastIterator(tree, root, common.Hash{}, seek, true) +} + +// newFastStorageIterator creates a new hierarchical storage iterator with one +// element per diff layer. The returned combo iterator can be used to walk over +// the entire snapshot diff stack simultaneously. +func newFastStorageIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) { + return newFastIterator(tree, root, account, seek, false) +} diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go new file mode 100644 index 0000000..daa8cdc --- /dev/null +++ b/core/state/snapshot/iterator_test.go @@ -0,0 +1,1047 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + crand "crypto/rand" + "encoding/binary" + "fmt" + "math/rand" + "testing" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" +) + +// TestAccountIteratorBasics tests some simple single-layer(diff and disk) iteration +func TestAccountIteratorBasics(t *testing.T) { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + // Fill up a parent + for i := 0; i < 100; i++ { + h := randomHash() + data := randomAccount() + + accounts[h] = data + if rand.Intn(4) == 0 { + destructs[h] = struct{}{} + } + if rand.Intn(2) == 0 { + accStorage := make(map[common.Hash][]byte) + value := make([]byte, 32) + crand.Read(value) + accStorage[randomHash()] = value + storage[h] = accStorage + } + } + // Add some (identical) layers on top + diffLayer := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + it := diffLayer.AccountIterator(common.Hash{}) + verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator + + diskLayer := diffToDisk(diffLayer) + it = diskLayer.AccountIterator(common.Hash{}) + verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator +} + +// TestStorageIteratorBasics tests some simple single-layer(diff and disk) iteration for storage +func TestStorageIteratorBasics(t *testing.T) { + var ( + nilStorage = make(map[common.Hash]int) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + // Fill some random data + for i := 0; i < 10; i++ { + h := randomHash() + accounts[h] = randomAccount() + + accStorage := make(map[common.Hash][]byte) + value := make([]byte, 32) + + var nilstorage int + for i := 0; i < 100; i++ { + crand.Read(value) + if rand.Intn(2) == 0 { + accStorage[randomHash()] = common.CopyBytes(value) + } else { + accStorage[randomHash()] = nil // delete slot + nilstorage += 1 + } + } + storage[h] = accStorage + nilStorage[h] = nilstorage + } + // Add some (identical) layers on top + diffLayer := newDiffLayer(emptyLayer(), common.Hash{}, nil, copyAccounts(accounts), copyStorage(storage)) + for account := range accounts { + it, _ := diffLayer.StorageIterator(account, common.Hash{}) + verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator + } + + diskLayer := diffToDisk(diffLayer) + for account := range accounts { + it, _ := diskLayer.StorageIterator(account, common.Hash{}) + verifyIterator(t, 100-nilStorage[account], it, verifyNothing) // Nil is allowed for single layer iterator + } +} + +type testIterator struct { + values []byte +} + +func newTestIterator(values ...byte) *testIterator { + return &testIterator{values} +} + +func (ti *testIterator) Seek(common.Hash) { + panic("implement me") +} + +func (ti *testIterator) Next() bool { + ti.values = ti.values[1:] + return len(ti.values) > 0 +} + +func (ti *testIterator) Error() error { + return nil +} + +func (ti *testIterator) Hash() common.Hash { + return common.BytesToHash([]byte{ti.values[0]}) +} + +func (ti *testIterator) Account() []byte { + return nil +} + +func (ti *testIterator) Slot() []byte { + return nil +} + +func (ti *testIterator) Release() {} + +func TestFastIteratorBasics(t *testing.T) { + type testCase struct { + lists [][]byte + expKeys []byte + } + for i, tc := range []testCase{ + {lists: [][]byte{{0, 1, 8}, {1, 2, 8}, {2, 9}, {4}, + {7, 14, 15}, {9, 13, 15, 16}}, + expKeys: []byte{0, 1, 2, 4, 7, 8, 9, 13, 14, 15, 16}}, + {lists: [][]byte{{0, 8}, {1, 2, 8}, {7, 14, 15}, {8, 9}, + {9, 10}, {10, 13, 15, 16}}, + expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, + } { + var iterators []*weightedIterator + for i, data := range tc.lists { + it := newTestIterator(data...) + iterators = append(iterators, &weightedIterator{it, i}) + } + fi := &fastIterator{ + iterators: iterators, + initiated: false, + } + count := 0 + for fi.Next() { + if got, exp := fi.Hash()[31], tc.expKeys[count]; exp != got { + t.Errorf("tc %d, [%d]: got %d exp %d", i, count, got, exp) + } + count++ + } + } +} + +type verifyContent int + +const ( + verifyNothing verifyContent = iota + verifyAccount + verifyStorage +) + +func verifyIterator(t *testing.T, expCount int, it Iterator, verify verifyContent) { + t.Helper() + + var ( + count = 0 + last = common.Hash{} + ) + for it.Next() { + hash := it.Hash() + if bytes.Compare(last[:], hash[:]) >= 0 { + t.Errorf("wrong order: %x >= %x", last, hash) + } + count++ + if verify == verifyAccount && len(it.(AccountIterator).Account()) == 0 { + t.Errorf("iterator returned nil-value for hash %x", hash) + } else if verify == verifyStorage && len(it.(StorageIterator).Slot()) == 0 { + t.Errorf("iterator returned nil-value for hash %x", hash) + } + last = hash + } + if count != expCount { + t.Errorf("iterator count mismatch: have %d, want %d", count, expCount) + } + if err := it.Error(); err != nil { + t.Errorf("iterator failed: %v", err) + } +} + +// TestAccountIteratorTraversal tests some simple multi-layer iteration. +func TestAccountIteratorTraversal(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Stack three diff layers on top with various overlaps + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + randomAccountSet("0xbb", "0xdd", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + randomAccountSet("0xcc", "0xf0", "0xff"), nil) + + // Verify the single and multi-layer iterators + head := snaps.Snapshot(common.HexToHash("0x04")) + + verifyIterator(t, 3, head.(snapshot).AccountIterator(common.Hash{}), verifyNothing) + verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) + + it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + verifyIterator(t, 7, it, verifyAccount) + it.Release() + + // Test after persist some bottom-most layers into the disk, + // the functionalities still work. + limit := aggregatorMemoryLimit + defer func() { + aggregatorMemoryLimit = limit + }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x04"), 2) + verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + verifyIterator(t, 7, it, verifyAccount) + it.Release() +} + +func TestStorageIteratorTraversal(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Stack three diff layers on top with various overlaps + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil)) + + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x04", "0x05", "0x06"}}, nil)) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil)) + + // Verify the single and multi-layer iterators + head := snaps.Snapshot(common.HexToHash("0x04")) + + diffIter, _ := head.(snapshot).StorageIterator(common.HexToHash("0xaa"), common.Hash{}) + verifyIterator(t, 3, diffIter, verifyNothing) + verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage) + + it, _ := snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{}) + verifyIterator(t, 6, it, verifyStorage) + it.Release() + + // Test after persist some bottom-most layers into the disk, + // the functionalities still work. + limit := aggregatorMemoryLimit + defer func() { + aggregatorMemoryLimit = limit + }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x04"), 2) + verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage) + + it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{}) + verifyIterator(t, 6, it, verifyStorage) + it.Release() +} + +// TestAccountIteratorTraversalValues tests some multi-layer iteration, where we +// also expect the correct values to show up. +func TestAccountIteratorTraversalValues(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Create a batch of account sets to seed subsequent layers with + var ( + a = make(map[common.Hash][]byte) + b = make(map[common.Hash][]byte) + c = make(map[common.Hash][]byte) + d = make(map[common.Hash][]byte) + e = make(map[common.Hash][]byte) + f = make(map[common.Hash][]byte) + g = make(map[common.Hash][]byte) + h = make(map[common.Hash][]byte) + ) + for i := byte(2); i < 0xff; i++ { + a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) + if i > 20 && i%2 == 0 { + b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) + } + if i%4 == 0 { + c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) + } + if i%7 == 0 { + d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) + } + if i%8 == 0 { + e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) + } + if i > 50 || i < 85 { + f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) + } + if i%64 == 0 { + g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) + } + if i%128 == 0 { + h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) + } + } + // Assemble a stack of snapshots from the account layers + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, a, nil) + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, b, nil) + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, c, nil) + snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, d, nil) + snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, e, nil) + snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, f, nil) + snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, g, nil) + snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, h, nil) + + it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) + head := snaps.Snapshot(common.HexToHash("0x09")) + for it.Next() { + hash := it.Hash() + want, err := head.AccountRLP(hash) + if err != nil { + t.Fatalf("failed to retrieve expected account: %v", err) + } + if have := it.Account(); !bytes.Equal(want, have) { + t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want) + } + } + it.Release() + + // Test after persist some bottom-most layers into the disk, + // the functionalities still work. + limit := aggregatorMemoryLimit + defer func() { + aggregatorMemoryLimit = limit + }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x09"), 2) + + it, _ = snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) + for it.Next() { + hash := it.Hash() + want, err := head.AccountRLP(hash) + if err != nil { + t.Fatalf("failed to retrieve expected account: %v", err) + } + if have := it.Account(); !bytes.Equal(want, have) { + t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want) + } + } + it.Release() +} + +func TestStorageIteratorTraversalValues(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + wrapStorage := func(storage map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { + return map[common.Hash]map[common.Hash][]byte{ + common.HexToHash("0xaa"): storage, + } + } + // Create a batch of storage sets to seed subsequent layers with + var ( + a = make(map[common.Hash][]byte) + b = make(map[common.Hash][]byte) + c = make(map[common.Hash][]byte) + d = make(map[common.Hash][]byte) + e = make(map[common.Hash][]byte) + f = make(map[common.Hash][]byte) + g = make(map[common.Hash][]byte) + h = make(map[common.Hash][]byte) + ) + for i := byte(2); i < 0xff; i++ { + a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) + if i > 20 && i%2 == 0 { + b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) + } + if i%4 == 0 { + c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) + } + if i%7 == 0 { + d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) + } + if i%8 == 0 { + e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) + } + if i > 50 || i < 85 { + f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) + } + if i%64 == 0 { + g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) + } + if i%128 == 0 { + h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) + } + } + // Assemble a stack of snapshots from the account layers + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa"), wrapStorage(a)) + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xaa"), wrapStorage(b)) + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xaa"), wrapStorage(c)) + snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, randomAccountSet("0xaa"), wrapStorage(d)) + snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, randomAccountSet("0xaa"), wrapStorage(e)) + snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, randomAccountSet("0xaa"), wrapStorage(e)) + snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, randomAccountSet("0xaa"), wrapStorage(g)) + snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, randomAccountSet("0xaa"), wrapStorage(h)) + + it, _ := snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{}) + head := snaps.Snapshot(common.HexToHash("0x09")) + for it.Next() { + hash := it.Hash() + want, err := head.Storage(common.HexToHash("0xaa"), hash) + if err != nil { + t.Fatalf("failed to retrieve expected storage slot: %v", err) + } + if have := it.Slot(); !bytes.Equal(want, have) { + t.Fatalf("hash %x: slot mismatch: have %x, want %x", hash, have, want) + } + } + it.Release() + + // Test after persist some bottom-most layers into the disk, + // the functionalities still work. + limit := aggregatorMemoryLimit + defer func() { + aggregatorMemoryLimit = limit + }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x09"), 2) + + it, _ = snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{}) + for it.Next() { + hash := it.Hash() + want, err := head.Storage(common.HexToHash("0xaa"), hash) + if err != nil { + t.Fatalf("failed to retrieve expected slot: %v", err) + } + if have := it.Slot(); !bytes.Equal(want, have) { + t.Fatalf("hash %x: slot mismatch: have %x, want %x", hash, have, want) + } + } + it.Release() +} + +// This testcase is notorious, all layers contain the exact same 200 accounts. +func TestAccountIteratorLargeTraversal(t *testing.T) { + // Create a custom account factory to recreate the same addresses + makeAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + // Build up a large stack of snapshots + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + for i := 1; i < 128; i++ { + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) + } + // Iterate the entire stack and ensure everything is hit only once + head := snaps.Snapshot(common.HexToHash("0x80")) + verifyIterator(t, 200, head.(snapshot).AccountIterator(common.Hash{}), verifyNothing) + verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) + + it, _ := snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{}) + verifyIterator(t, 200, it, verifyAccount) + it.Release() + + // Test after persist some bottom-most layers into the disk, + // the functionalities still work. + limit := aggregatorMemoryLimit + defer func() { + aggregatorMemoryLimit = limit + }() + aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk + snaps.Cap(common.HexToHash("0x80"), 2) + + verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount) + + it, _ = snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{}) + verifyIterator(t, 200, it, verifyAccount) + it.Release() +} + +// TestAccountIteratorFlattening tests what happens when we +// - have a live iterator on child C (parent C1 -> C2 .. CN) +// - flattens C2 all the way into CN +// - continues iterating +func TestAccountIteratorFlattening(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Create a stack of diffs on top + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + randomAccountSet("0xbb", "0xdd", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + randomAccountSet("0xcc", "0xf0", "0xff"), nil) + + // Create an iterator and flatten the data from underneath it + it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + defer it.Release() + + if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil { + t.Fatalf("failed to flatten snapshot stack: %v", err) + } + //verifyIterator(t, 7, it) +} + +func TestAccountIteratorSeek(t *testing.T) { + // Create a snapshot stack with some initial data + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + randomAccountSet("0xbb", "0xdd", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + randomAccountSet("0xcc", "0xf0", "0xff"), nil) + + // Account set is now + // 02: aa, ee, f0, ff + // 03: aa, bb, dd, ee, f0 (, f0), ff + // 04: aa, bb, cc, dd, ee, f0 (, f0), ff (, ff) + // Construct various iterators and ensure their traversal is correct + it, _ := snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xdd")) + defer it.Release() + verifyIterator(t, 3, it, verifyAccount) // expected: ee, f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xaa")) + defer it.Release() + verifyIterator(t, 4, it, verifyAccount) // expected: aa, ee, f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff")) + defer it.Release() + verifyIterator(t, 1, it, verifyAccount) // expected: ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff1")) + defer it.Release() + verifyIterator(t, 0, it, verifyAccount) // expected: nothing + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xbb")) + defer it.Release() + verifyIterator(t, 6, it, verifyAccount) // expected: bb, cc, dd, ee, f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xef")) + defer it.Release() + verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xf0")) + defer it.Release() + verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff")) + defer it.Release() + verifyIterator(t, 1, it, verifyAccount) // expected: ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff1")) + defer it.Release() + verifyIterator(t, 0, it, verifyAccount) // expected: nothing +} + +func TestStorageIteratorSeek(t *testing.T) { + // Create a snapshot stack with some initial data + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Stack three diff layers on top with various overlaps + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x03", "0x05"}}, nil)) + + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x02", "0x05", "0x06"}}, nil)) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x05", "0x08"}}, nil)) + + // Account set is now + // 02: 01, 03, 05 + // 03: 01, 02, 03, 05 (, 05), 06 + // 04: 01(, 01), 02, 03, 05(, 05, 05), 06, 08 + // Construct various iterators and ensure their traversal is correct + it, _ := snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x01")) + defer it.Release() + verifyIterator(t, 3, it, verifyStorage) // expected: 01, 03, 05 + + it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x02")) + defer it.Release() + verifyIterator(t, 2, it, verifyStorage) // expected: 03, 05 + + it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x5")) + defer it.Release() + verifyIterator(t, 1, it, verifyStorage) // expected: 05 + + it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x6")) + defer it.Release() + verifyIterator(t, 0, it, verifyStorage) // expected: nothing + + it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x01")) + defer it.Release() + verifyIterator(t, 6, it, verifyStorage) // expected: 01, 02, 03, 05, 06, 08 + + it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x05")) + defer it.Release() + verifyIterator(t, 3, it, verifyStorage) // expected: 05, 06, 08 + + it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x08")) + defer it.Release() + verifyIterator(t, 1, it, verifyStorage) // expected: 08 + + it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x09")) + defer it.Release() + verifyIterator(t, 0, it, verifyStorage) // expected: nothing +} + +// TestAccountIteratorDeletions tests that the iterator behaves correct when there are +// deleted accounts (where the Account() value is nil). The iterator +// should not output any accounts or nil-values for those cases. +func TestAccountIteratorDeletions(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Stack three diff layers on top with various overlaps + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + nil, randomAccountSet("0x11", "0x22", "0x33"), nil) + + deleted := common.HexToHash("0x22") + destructed := map[common.Hash]struct{}{ + deleted: {}, + } + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + destructed, randomAccountSet("0x11", "0x33"), nil) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + nil, randomAccountSet("0x33", "0x44", "0x55"), nil) + + // The output should be 11,33,44,55 + it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + // Do a quick check + verifyIterator(t, 4, it, verifyAccount) + it.Release() + + // And a more detailed verification that we indeed do not see '0x22' + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + defer it.Release() + for it.Next() { + hash := it.Hash() + if it.Account() == nil { + t.Errorf("iterator returned nil-value for hash %x", hash) + } + if hash == deleted { + t.Errorf("expected deleted elem %x to not be returned by iterator", deleted) + } + } +} + +func TestStorageIteratorDeletions(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Stack three diff layers on top with various overlaps + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x03", "0x05"}}, nil)) + + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x02", "0x04", "0x06"}}, [][]string{{"0x01", "0x03"}})) + + // The output should be 02,04,05,06 + it, _ := snaps.StorageIterator(common.HexToHash("0x03"), common.HexToHash("0xaa"), common.Hash{}) + verifyIterator(t, 4, it, verifyStorage) + it.Release() + + // The output should be 04,05,06 + it, _ = snaps.StorageIterator(common.HexToHash("0x03"), common.HexToHash("0xaa"), common.HexToHash("0x03")) + verifyIterator(t, 3, it, verifyStorage) + it.Release() + + // Destruct the whole storage + destructed := map[common.Hash]struct{}{ + common.HexToHash("0xaa"): {}, + } + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), destructed, nil, nil) + + it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{}) + verifyIterator(t, 0, it, verifyStorage) + it.Release() + + // Re-insert the slots of the same account + snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, + randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x07", "0x08", "0x09"}}, nil)) + + // The output should be 07,08,09 + it, _ = snaps.StorageIterator(common.HexToHash("0x05"), common.HexToHash("0xaa"), common.Hash{}) + verifyIterator(t, 3, it, verifyStorage) + it.Release() + + // Destruct the whole storage but re-create the account in the same layer + snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), destructed, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x11", "0x12"}}, nil)) + it, _ = snaps.StorageIterator(common.HexToHash("0x06"), common.HexToHash("0xaa"), common.Hash{}) + verifyIterator(t, 2, it, verifyStorage) // The output should be 11,12 + it.Release() + + verifyIterator(t, 2, snaps.Snapshot(common.HexToHash("0x06")).(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage) +} + +// BenchmarkAccountIteratorTraversal is a bit notorious -- all layers contain the +// exact same 200 accounts. That means that we need to process 2000 items, but +// only spit out 200 values eventually. +// +// The value-fetching benchmark is easy on the binary iterator, since it never has to reach +// down at any depth for retrieving the values -- all are on the topmost layer +// +// BenchmarkAccountIteratorTraversal/binary_iterator_keys-6 2239 483674 ns/op +// BenchmarkAccountIteratorTraversal/binary_iterator_values-6 2403 501810 ns/op +// BenchmarkAccountIteratorTraversal/fast_iterator_keys-6 1923 677966 ns/op +// BenchmarkAccountIteratorTraversal/fast_iterator_values-6 1741 649967 ns/op +func BenchmarkAccountIteratorTraversal(b *testing.B) { + // Create a custom account factory to recreate the same addresses + makeAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + // Build up a large stack of snapshots + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + for i := 1; i <= 100; i++ { + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) + } + // We call this once before the benchmark, so the creation of + // sorted accountlists are not included in the results. + head := snaps.Snapshot(common.HexToHash("0x65")) + head.(*diffLayer).newBinaryAccountIterator() + + b.Run("binary iterator keys", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := head.(*diffLayer).newBinaryAccountIterator() + for it.Next() { + got++ + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("binary iterator values", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := head.(*diffLayer).newBinaryAccountIterator() + for it.Next() { + got++ + head.(*diffLayer).accountRLP(it.Hash(), 0) + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator keys", func(b *testing.B) { + for i := 0; i < b.N; i++ { + it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) + defer it.Release() + + got := 0 + for it.Next() { + got++ + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator values", func(b *testing.B) { + for i := 0; i < b.N; i++ { + it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) + defer it.Release() + + got := 0 + for it.Next() { + got++ + it.Account() + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) +} + +// BenchmarkAccountIteratorLargeBaselayer is a pretty realistic benchmark, where +// the baselayer is a lot larger than the upper layer. +// +// This is heavy on the binary iterator, which in most cases will have to +// call recursively 100 times for the majority of the values +// +// BenchmarkAccountIteratorLargeBaselayer/binary_iterator_(keys)-6 514 1971999 ns/op +// BenchmarkAccountIteratorLargeBaselayer/binary_iterator_(values)-6 61 18997492 ns/op +// BenchmarkAccountIteratorLargeBaselayer/fast_iterator_(keys)-6 10000 114385 ns/op +// BenchmarkAccountIteratorLargeBaselayer/fast_iterator_(values)-6 4047 296823 ns/op +func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { + // Create a custom account factory to recreate the same addresses + makeAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + // Build up a large stack of snapshots + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, makeAccounts(2000), nil) + for i := 2; i <= 100; i++ { + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(20), nil) + } + // We call this once before the benchmark, so the creation of + // sorted accountlists are not included in the results. + head := snaps.Snapshot(common.HexToHash("0x65")) + head.(*diffLayer).newBinaryAccountIterator() + + b.Run("binary iterator (keys)", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := head.(*diffLayer).newBinaryAccountIterator() + for it.Next() { + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("binary iterator (values)", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := head.(*diffLayer).newBinaryAccountIterator() + for it.Next() { + got++ + v := it.Hash() + head.(*diffLayer).accountRLP(v, 0) + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator (keys)", func(b *testing.B) { + for i := 0; i < b.N; i++ { + it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) + defer it.Release() + + got := 0 + for it.Next() { + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator (values)", func(b *testing.B) { + for i := 0; i < b.N; i++ { + it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) + defer it.Release() + + got := 0 + for it.Next() { + it.Account() + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) +} + +/* +func BenchmarkBinaryAccountIteration(b *testing.B) { + benchmarkAccountIteration(b, func(snap snapshot) AccountIterator { + return snap.(*diffLayer).newBinaryAccountIterator() + }) +} + +func BenchmarkFastAccountIteration(b *testing.B) { + benchmarkAccountIteration(b, newFastAccountIterator) +} + +func benchmarkAccountIteration(b *testing.B, iterator func(snap snapshot) AccountIterator) { + // Create a diff stack and randomize the accounts across them + layers := make([]map[common.Hash][]byte, 128) + for i := 0; i < len(layers); i++ { + layers[i] = make(map[common.Hash][]byte) + } + for i := 0; i < b.N; i++ { + depth := rand.Intn(len(layers)) + layers[depth][randomHash()] = randomAccount() + } + stack := snapshot(emptyLayer()) + for _, layer := range layers { + stack = stack.Update(common.Hash{}, layer, nil, nil) + } + // Reset the timers and report all the stats + it := iterator(stack) + + b.ResetTimer() + b.ReportAllocs() + + for it.Next() { + } +} +*/ diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go new file mode 100644 index 0000000..8513e73 --- /dev/null +++ b/core/state/snapshot/journal.go @@ -0,0 +1,363 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "time" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/triedb" +) + +const journalVersion uint64 = 0 + +// journalGenerator is a disk layer entry containing the generator progress marker. +type journalGenerator struct { + // Indicator that whether the database was in progress of being wiped. + // It's deprecated but keep it here for background compatibility. + Wiping bool + + Done bool // Whether the generator finished creating the snapshot + Marker []byte + Accounts uint64 + Slots uint64 + Storage uint64 +} + +// journalDestruct is an account deletion entry in a diffLayer's disk journal. +type journalDestruct struct { + Hash common.Hash +} + +// journalAccount is an account entry in a diffLayer's disk journal. +type journalAccount struct { + Hash common.Hash + Blob []byte +} + +// journalStorage is an account's storage map in a diffLayer's disk journal. +type journalStorage struct { + Hash common.Hash + Keys []common.Hash + Vals [][]byte +} + +func ParseGeneratorStatus(generatorBlob []byte) string { + if len(generatorBlob) == 0 { + return "" + } + var generator journalGenerator + if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { + log.Warn("failed to decode snapshot generator", "err", err) + return "" + } + // Figure out whether we're after or within an account + var m string + switch marker := generator.Marker; len(marker) { + case common.HashLength: + m = fmt.Sprintf("at %#x", marker) + case 2 * common.HashLength: + m = fmt.Sprintf("in %#x at %#x", marker[:common.HashLength], marker[common.HashLength:]) + default: + m = fmt.Sprintf("%#x", marker) + } + return fmt.Sprintf(`Done: %v, Accounts: %d, Slots: %d, Storage: %d, Marker: %s`, + generator.Done, generator.Accounts, generator.Slots, generator.Storage, m) +} + +// loadAndParseJournal tries to parse the snapshot journal in latest format. +func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { + // Retrieve the disk layer generator. It must exist, no matter the + // snapshot is fully generated or not. Otherwise the entire disk + // layer is invalid. + generatorBlob := rawdb.ReadSnapshotGenerator(db) + if len(generatorBlob) == 0 { + return nil, journalGenerator{}, errors.New("missing snapshot generator") + } + var generator journalGenerator + if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { + return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err) + } + // Retrieve the diff layer journal. It's possible that the journal is + // not existent, e.g. the disk layer is generating while that the Geth + // crashes without persisting the diff journal. + // So if there is no journal, or the journal is invalid(e.g. the journal + // is not matched with disk layer; or the it's the legacy-format journal, + // etc.), we just discard all diffs and try to recover them later. + var current snapshot = base + err := iterateJournal(db, func(parent common.Hash, root common.Hash, destructSet map[common.Hash]struct{}, accountData map[common.Hash][]byte, storageData map[common.Hash]map[common.Hash][]byte) error { + current = newDiffLayer(current, root, destructSet, accountData, storageData) + return nil + }) + if err != nil { + return base, generator, nil + } + return current, generator, nil +} + +// loadSnapshot loads a pre-existing state snapshot backed by a key-value store. +func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *triedb.Database, root common.Hash, cache int, recovery bool, noBuild bool) (snapshot, bool, error) { + // If snapshotting is disabled (initial sync in progress), don't do anything, + // wait for the chain to permit us to do something meaningful + if rawdb.ReadSnapshotDisabled(diskdb) { + return nil, true, nil + } + // Retrieve the block number and hash of the snapshot, failing if no snapshot + // is present in the database (or crashed mid-update). + baseRoot := rawdb.ReadSnapshotRoot(diskdb) + if baseRoot == (common.Hash{}) { + return nil, false, errors.New("missing or corrupted snapshot") + } + base := &diskLayer{ + diskdb: diskdb, + triedb: triedb, + cache: fastcache.New(cache * 1024 * 1024), + root: baseRoot, + } + snapshot, generator, err := loadAndParseJournal(diskdb, base) + if err != nil { + log.Warn("Failed to load journal", "error", err) + return nil, false, err + } + // Entire snapshot journal loaded, sanity check the head. If the loaded + // snapshot is not matched with current state root, print a warning log + // or discard the entire snapshot it's legacy snapshot. + // + // Possible scenario: Geth was crashed without persisting journal and then + // restart, the head is rewound to the point with available state(trie) + // which is below the snapshot. In this case the snapshot can be recovered + // by re-executing blocks but right now it's unavailable. + if head := snapshot.Root(); head != root { + // If it's legacy snapshot, or it's new-format snapshot but + // it's not in recovery mode, returns the error here for + // rebuilding the entire snapshot forcibly. + if !recovery { + return nil, false, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) + } + // It's in snapshot recovery, the assumption is held that + // the disk layer is always higher than chain head. It can + // be eventually recovered when the chain head beyonds the + // disk layer. + log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root) + } + // Load the disk layer status from the generator if it's not complete + if !generator.Done { + base.genMarker = generator.Marker + if base.genMarker == nil { + base.genMarker = []byte{} + } + } + // Everything loaded correctly, resume any suspended operations + // if the background generation is allowed + if !generator.Done && !noBuild { + base.genPending = make(chan struct{}) + base.genAbort = make(chan chan *generatorStats) + + var origin uint64 + if len(generator.Marker) >= 8 { + origin = binary.BigEndian.Uint64(generator.Marker) + } + go base.generate(&generatorStats{ + origin: origin, + start: time.Now(), + accounts: generator.Accounts, + slots: generator.Slots, + storage: common.StorageSize(generator.Storage), + }) + } + return snapshot, false, nil +} + +// Journal terminates any in-progress snapshot generation, also implicitly pushing +// the progress into the database. +func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { + // If the snapshot is currently being generated, abort it + var stats *generatorStats + if dl.genAbort != nil { + abort := make(chan *generatorStats) + dl.genAbort <- abort + + if stats = <-abort; stats != nil { + stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) + } + } + // Ensure the layer didn't get stale + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return common.Hash{}, ErrSnapshotStale + } + // Ensure the generator stats is written even if none was ran this cycle + journalProgress(dl.diskdb, dl.genMarker, stats) + + log.Debug("Journalled disk layer", "root", dl.root) + return dl.root, nil +} + +// Journal writes the memory layer contents into a buffer to be stored in the +// database as the snapshot journal. +func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { + // Journal the parent first + base, err := dl.parent.Journal(buffer) + if err != nil { + return common.Hash{}, err + } + // Ensure the layer didn't get stale + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.Stale() { + return common.Hash{}, ErrSnapshotStale + } + // Everything below was journalled, persist this layer too + if err := rlp.Encode(buffer, dl.root); err != nil { + return common.Hash{}, err + } + destructs := make([]journalDestruct, 0, len(dl.destructSet)) + for hash := range dl.destructSet { + destructs = append(destructs, journalDestruct{Hash: hash}) + } + if err := rlp.Encode(buffer, destructs); err != nil { + return common.Hash{}, err + } + accounts := make([]journalAccount, 0, len(dl.accountData)) + for hash, blob := range dl.accountData { + accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) + } + if err := rlp.Encode(buffer, accounts); err != nil { + return common.Hash{}, err + } + storage := make([]journalStorage, 0, len(dl.storageData)) + for hash, slots := range dl.storageData { + keys := make([]common.Hash, 0, len(slots)) + vals := make([][]byte, 0, len(slots)) + for key, val := range slots { + keys = append(keys, key) + vals = append(vals, val) + } + storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) + } + if err := rlp.Encode(buffer, storage); err != nil { + return common.Hash{}, err + } + log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) + return base, nil +} + +// journalCallback is a function which is invoked by iterateJournal, every +// time a difflayer is loaded from disk. +type journalCallback = func(parent common.Hash, root common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error + +// iterateJournal iterates through the journalled difflayers, loading them from +// the database, and invoking the callback for each loaded layer. +// The order is incremental; starting with the bottom-most difflayer, going towards +// the most recent layer. +// This method returns error either if there was some error reading from disk, +// OR if the callback returns an error when invoked. +func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error { + journal := rawdb.ReadSnapshotJournal(db) + if len(journal) == 0 { + log.Warn("Loaded snapshot journal", "diffs", "missing") + return nil + } + r := rlp.NewStream(bytes.NewReader(journal), 0) + // Firstly, resolve the first element as the journal version + version, err := r.Uint64() + if err != nil { + log.Warn("Failed to resolve the journal version", "error", err) + return errors.New("failed to resolve journal version") + } + if version != journalVersion { + log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version) + return errors.New("wrong journal version") + } + // Secondly, resolve the disk layer root, ensure it's continuous + // with disk layer. Note now we can ensure it's the snapshot journal + // correct version, so we expect everything can be resolved properly. + var parent common.Hash + if err := r.Decode(&parent); err != nil { + return errors.New("missing disk layer root") + } + if baseRoot := rawdb.ReadSnapshotRoot(db); baseRoot != parent { + log.Warn("Loaded snapshot journal", "diskroot", baseRoot, "diffs", "unmatched") + return errors.New("mismatched disk and diff layers") + } + for { + var ( + root common.Hash + destructs []journalDestruct + accounts []journalAccount + storage []journalStorage + destructSet = make(map[common.Hash]struct{}) + accountData = make(map[common.Hash][]byte) + storageData = make(map[common.Hash]map[common.Hash][]byte) + ) + // Read the next diff journal entry + if err := r.Decode(&root); err != nil { + // The first read may fail with EOF, marking the end of the journal + if errors.Is(err, io.EOF) { + return nil + } + return fmt.Errorf("load diff root: %v", err) + } + if err := r.Decode(&destructs); err != nil { + return fmt.Errorf("load diff destructs: %v", err) + } + if err := r.Decode(&accounts); err != nil { + return fmt.Errorf("load diff accounts: %v", err) + } + if err := r.Decode(&storage); err != nil { + return fmt.Errorf("load diff storage: %v", err) + } + for _, entry := range destructs { + destructSet[entry.Hash] = struct{}{} + } + for _, entry := range accounts { + if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that + accountData[entry.Hash] = entry.Blob + } else { + accountData[entry.Hash] = nil + } + } + for _, entry := range storage { + slots := make(map[common.Hash][]byte) + for i, key := range entry.Keys { + if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that + slots[key] = entry.Vals[i] + } else { + slots[key] = nil + } + } + storageData[entry.Hash] = slots + } + if err := callback(parent, root, destructSet, accountData, storageData); err != nil { + return err + } + parent = root + } +} diff --git a/core/state/snapshot/metrics.go b/core/state/snapshot/metrics.go new file mode 100644 index 0000000..b2e8845 --- /dev/null +++ b/core/state/snapshot/metrics.go @@ -0,0 +1,53 @@ +// 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 snapshot + +import "github.com/ethereum/go-ethereum/metrics" + +// Metrics in generation +var ( + snapGeneratedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/generated", nil) + snapRecoveredAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/recovered", nil) + snapWipedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/wiped", nil) + snapMissallAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/missall", nil) + snapGeneratedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/generated", nil) + snapRecoveredStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/recovered", nil) + snapWipedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/wiped", nil) + snapMissallStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/missall", nil) + snapDanglingStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/dangling", nil) + snapSuccessfulRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/success", nil) + snapFailedRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/failure", nil) + + // snapAccountProveCounter measures time spent on the account proving + snapAccountProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/prove", nil) + // snapAccountTrieReadCounter measures time spent on the account trie iteration + snapAccountTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/trieread", nil) + // snapAccountSnapReadCounter measures time spent on the snapshot account iteration + snapAccountSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/snapread", nil) + // snapAccountWriteCounter measures time spent on writing/updating/deleting accounts + snapAccountWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/write", nil) + // snapStorageProveCounter measures time spent on storage proving + snapStorageProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/prove", nil) + // snapStorageTrieReadCounter measures time spent on the storage trie iteration + snapStorageTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/trieread", nil) + // snapStorageSnapReadCounter measures time spent on the snapshot storage iteration + snapStorageSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/snapread", nil) + // snapStorageWriteCounter measures time spent on writing/updating storages + snapStorageWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/write", nil) + // snapStorageCleanCounter measures time spent on deleting storages + snapStorageCleanCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/clean", nil) +) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go new file mode 100644 index 0000000..752f435 --- /dev/null +++ b/core/state/snapshot/snapshot.go @@ -0,0 +1,892 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package snapshot implements a journalled, dynamic state dump. +package snapshot + +import ( + "bytes" + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/triedb" +) + +var ( + snapshotCleanAccountHitMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/hit", nil) + snapshotCleanAccountMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/miss", nil) + snapshotCleanAccountInexMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/inex", nil) + snapshotCleanAccountReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/read", nil) + snapshotCleanAccountWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/write", nil) + + snapshotCleanStorageHitMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/hit", nil) + snapshotCleanStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/miss", nil) + snapshotCleanStorageInexMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/inex", nil) + snapshotCleanStorageReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/read", nil) + snapshotCleanStorageWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/write", nil) + + snapshotDirtyAccountHitMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/hit", nil) + snapshotDirtyAccountMissMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/miss", nil) + snapshotDirtyAccountInexMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/inex", nil) + snapshotDirtyAccountReadMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/read", nil) + snapshotDirtyAccountWriteMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/write", nil) + + snapshotDirtyStorageHitMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/hit", nil) + snapshotDirtyStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/miss", nil) + snapshotDirtyStorageInexMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/inex", nil) + snapshotDirtyStorageReadMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/read", nil) + snapshotDirtyStorageWriteMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/write", nil) + + snapshotDirtyAccountHitDepthHist = metrics.NewRegisteredHistogram("state/snapshot/dirty/account/hit/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) + snapshotDirtyStorageHitDepthHist = metrics.NewRegisteredHistogram("state/snapshot/dirty/storage/hit/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) + + snapshotFlushAccountItemMeter = metrics.NewRegisteredMeter("state/snapshot/flush/account/item", nil) + snapshotFlushAccountSizeMeter = metrics.NewRegisteredMeter("state/snapshot/flush/account/size", nil) + snapshotFlushStorageItemMeter = metrics.NewRegisteredMeter("state/snapshot/flush/storage/item", nil) + snapshotFlushStorageSizeMeter = metrics.NewRegisteredMeter("state/snapshot/flush/storage/size", nil) + + snapshotBloomIndexTimer = metrics.NewRegisteredResettingTimer("state/snapshot/bloom/index", nil) + snapshotBloomErrorGauge = metrics.NewRegisteredGaugeFloat64("state/snapshot/bloom/error", nil) + + snapshotBloomAccountTrueHitMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/account/truehit", nil) + snapshotBloomAccountFalseHitMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/account/falsehit", nil) + snapshotBloomAccountMissMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/account/miss", nil) + + snapshotBloomStorageTrueHitMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/storage/truehit", nil) + snapshotBloomStorageFalseHitMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/storage/falsehit", nil) + snapshotBloomStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/storage/miss", nil) + + // ErrSnapshotStale is returned from data accessors if the underlying snapshot + // layer had been invalidated due to the chain progressing forward far enough + // to not maintain the layer's original state. + ErrSnapshotStale = errors.New("snapshot stale") + + // ErrNotCoveredYet is returned from data accessors if the underlying snapshot + // is being generated currently and the requested data item is not yet in the + // range of accounts covered. + ErrNotCoveredYet = errors.New("not covered yet") + + // ErrNotConstructed is returned if the callers want to iterate the snapshot + // while the generation is not finished yet. + ErrNotConstructed = errors.New("snapshot is not constructed") + + // errSnapshotCycle is returned if a snapshot is attempted to be inserted + // that forms a cycle in the snapshot tree. + errSnapshotCycle = errors.New("snapshot cycle") +) + +// Snapshot represents the functionality supported by a snapshot storage layer. +type Snapshot interface { + // Root returns the root hash for which this snapshot was made. + Root() common.Hash + + // Account directly retrieves the account associated with a particular hash in + // the snapshot slim data format. + Account(hash common.Hash) (*types.SlimAccount, error) + + // AccountRLP directly retrieves the account RLP associated with a particular + // hash in the snapshot slim data format. + AccountRLP(hash common.Hash) ([]byte, error) + + // Storage directly retrieves the storage data associated with a particular hash, + // within a particular account. + Storage(accountHash, storageHash common.Hash) ([]byte, error) +} + +// snapshot is the internal version of the snapshot data layer that supports some +// additional methods compared to the public API. +type snapshot interface { + Snapshot + + // Parent returns the subsequent layer of a snapshot, or nil if the base was + // reached. + // + // Note, the method is an internal helper to avoid type switching between the + // disk and diff layers. There is no locking involved. + Parent() snapshot + + // Update creates a new layer on top of the existing snapshot diff tree with + // the specified data items. + // + // Note, the maps are retained by the method to avoid copying everything. + Update(blockRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer + + // Journal commits an entire diff hierarchy to disk into a single journal entry. + // This is meant to be used during shutdown to persist the snapshot without + // flattening everything down (bad for reorgs). + Journal(buffer *bytes.Buffer) (common.Hash, error) + + // Stale return whether this layer has become stale (was flattened across) or + // if it's still live. + Stale() bool + + // AccountIterator creates an account iterator over an arbitrary layer. + AccountIterator(seek common.Hash) AccountIterator + + // StorageIterator creates a storage iterator over an arbitrary layer. + StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) +} + +// Config includes the configurations for snapshots. +type Config struct { + CacheSize int // Megabytes permitted to use for read caches + Recovery bool // Indicator that the snapshots is in the recovery mode + NoBuild bool // Indicator that the snapshots generation is disallowed + AsyncBuild bool // The snapshot generation is allowed to be constructed asynchronously +} + +// Tree is an Ethereum state snapshot tree. It consists of one persistent base +// layer backed by a key-value store, on top of which arbitrarily many in-memory +// diff layers are topped. The memory diffs can form a tree with branching, but +// the disk layer is singleton and common to all. If a reorg goes deeper than the +// disk layer, everything needs to be deleted. +// +// The goal of a state snapshot is twofold: to allow direct access to account and +// storage data to avoid expensive multi-level trie lookups; and to allow sorted, +// cheap iteration of the account/storage tries for sync aid. +type Tree struct { + config Config // Snapshots configurations + diskdb ethdb.KeyValueStore // Persistent database to store the snapshot + triedb *triedb.Database // In-memory cache to access the trie through + layers map[common.Hash]snapshot // Collection of all known layers + lock sync.RWMutex + + // Test hooks + onFlatten func() // Hook invoked when the bottom most diff layers are flattened +} + +// New attempts to load an already existing snapshot from a persistent key-value +// store (with a number of memory layers from a journal), ensuring that the head +// of the snapshot matches the expected one. +// +// If the snapshot is missing or the disk layer is broken, the snapshot will be +// reconstructed using both the existing data and the state trie. +// The repair happens on a background thread. +// +// If the memory layers in the journal do not match the disk layer (e.g. there is +// a gap) or the journal is missing, there are two repair cases: +// +// - if the 'recovery' parameter is true, memory diff-layers and the disk-layer +// will all be kept. This case happens when the snapshot is 'ahead' of the +// state trie. +// - otherwise, the entire snapshot is considered invalid and will be recreated on +// a background thread. +func New(config Config, diskdb ethdb.KeyValueStore, triedb *triedb.Database, root common.Hash) (*Tree, error) { + // Create a new, empty snapshot tree + snap := &Tree{ + config: config, + diskdb: diskdb, + triedb: triedb, + layers: make(map[common.Hash]snapshot), + } + // Attempt to load a previously persisted snapshot and rebuild one if failed + head, disabled, err := loadSnapshot(diskdb, triedb, root, config.CacheSize, config.Recovery, config.NoBuild) + if disabled { + log.Warn("Snapshot maintenance disabled (syncing)") + return snap, nil + } + // Create the building waiter iff the background generation is allowed + if !config.NoBuild && !config.AsyncBuild { + defer snap.waitBuild() + } + if err != nil { + log.Warn("Failed to load snapshot", "err", err) + if !config.NoBuild { + snap.Rebuild(root) + return snap, nil + } + return nil, err // Bail out the error, don't rebuild automatically. + } + // Existing snapshot loaded, seed all the layers + for head != nil { + snap.layers[head.Root()] = head + head = head.Parent() + } + return snap, nil +} + +// waitBuild blocks until the snapshot finishes rebuilding. This method is meant +// to be used by tests to ensure we're testing what we believe we are. +func (t *Tree) waitBuild() { + // Find the rebuild termination channel + var done chan struct{} + + t.lock.RLock() + for _, layer := range t.layers { + if layer, ok := layer.(*diskLayer); ok { + done = layer.genPending + break + } + } + t.lock.RUnlock() + + // Wait until the snapshot is generated + if done != nil { + <-done + } +} + +// Disable interrupts any pending snapshot generator, deletes all the snapshot +// layers in memory and marks snapshots disabled globally. In order to resume +// the snapshot functionality, the caller must invoke Rebuild. +func (t *Tree) Disable() { + // Interrupt any live snapshot layers + t.lock.Lock() + defer t.lock.Unlock() + + for _, layer := range t.layers { + switch layer := layer.(type) { + case *diskLayer: + + layer.lock.RLock() + generating := layer.genMarker != nil + layer.lock.RUnlock() + if !generating { + // Generator is already aborted or finished + break + } + // If the base layer is generating, abort it + if layer.genAbort != nil { + abort := make(chan *generatorStats) + layer.genAbort <- abort + <-abort + } + // Layer should be inactive now, mark it as stale + layer.lock.Lock() + layer.stale = true + layer.lock.Unlock() + + case *diffLayer: + // If the layer is a simple diff, simply mark as stale + layer.lock.Lock() + layer.stale.Store(true) + layer.lock.Unlock() + + default: + panic(fmt.Sprintf("unknown layer type: %T", layer)) + } + } + t.layers = map[common.Hash]snapshot{} + + // Delete all snapshot liveness information from the database + batch := t.diskdb.NewBatch() + + rawdb.WriteSnapshotDisabled(batch) + rawdb.DeleteSnapshotRoot(batch) + rawdb.DeleteSnapshotJournal(batch) + rawdb.DeleteSnapshotGenerator(batch) + rawdb.DeleteSnapshotRecoveryNumber(batch) + // Note, we don't delete the sync progress + + if err := batch.Write(); err != nil { + log.Crit("Failed to disable snapshots", "err", err) + } +} + +// Snapshot retrieves a snapshot belonging to the given block root, or nil if no +// snapshot is maintained for that block. +func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.layers[blockRoot] +} + +// Snapshots returns all visited layers from the topmost layer with specific +// root and traverses downward. The layer amount is limited by the given number. +// If nodisk is set, then disk layer is excluded. +func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot { + t.lock.RLock() + defer t.lock.RUnlock() + + if limits == 0 { + return nil + } + layer := t.layers[root] + if layer == nil { + return nil + } + var ret []Snapshot + for { + if _, isdisk := layer.(*diskLayer); isdisk && nodisk { + break + } + ret = append(ret, layer) + limits -= 1 + if limits == 0 { + break + } + parent := layer.Parent() + if parent == nil { + break + } + layer = parent + } + return ret +} + +// Update adds a new snapshot into the tree, if that can be linked to an existing +// old parent. It is disallowed to insert a disk layer (the origin of all). +func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { + // Reject noop updates to avoid self-loops in the snapshot tree. This is a + // special case that can only happen for Clique networks where empty blocks + // don't modify the state (0 block subsidy). + // + // Although we could silently ignore this internally, it should be the caller's + // responsibility to avoid even attempting to insert such a snapshot. + if blockRoot == parentRoot { + return errSnapshotCycle + } + // Generate a new snapshot on top of the parent + parent := t.Snapshot(parentRoot) + if parent == nil { + return fmt.Errorf("parent [%#x] snapshot missing", parentRoot) + } + snap := parent.(snapshot).Update(blockRoot, destructs, accounts, storage) + + // Save the new snapshot for later + t.lock.Lock() + defer t.lock.Unlock() + + t.layers[snap.root] = snap + return nil +} + +// Cap traverses downwards the snapshot tree from a head block hash until the +// number of allowed layers are crossed. All layers beyond the permitted number +// are flattened downwards. +// +// Note, the final diff layer count in general will be one more than the amount +// requested. This happens because the bottom-most diff layer is the accumulator +// which may or may not overflow and cascade to disk. Since this last layer's +// survival is only known *after* capping, we need to omit it from the count if +// we want to ensure that *at least* the requested number of diff layers remain. +func (t *Tree) Cap(root common.Hash, layers int) error { + // Retrieve the head snapshot to cap from + snap := t.Snapshot(root) + if snap == nil { + return fmt.Errorf("snapshot [%#x] missing", root) + } + diff, ok := snap.(*diffLayer) + if !ok { + return fmt.Errorf("snapshot [%#x] is disk layer", root) + } + // If the generator is still running, use a more aggressive cap + diff.origin.lock.RLock() + if diff.origin.genMarker != nil && layers > 8 { + layers = 8 + } + diff.origin.lock.RUnlock() + + // Run the internal capping and discard all stale layers + t.lock.Lock() + defer t.lock.Unlock() + + // Flattening the bottom-most diff layer requires special casing since there's + // no child to rewire to the grandparent. In that case we can fake a temporary + // child for the capping and then remove it. + if layers == 0 { + // If full commit was requested, flatten the diffs and merge onto disk + diff.lock.RLock() + base := diffToDisk(diff.flatten().(*diffLayer)) + diff.lock.RUnlock() + + // Replace the entire snapshot tree with the flat base + t.layers = map[common.Hash]snapshot{base.root: base} + return nil + } + persisted := t.cap(diff, layers) + + // Remove any layer that is stale or links into a stale layer + children := make(map[common.Hash][]common.Hash) + for root, snap := range t.layers { + if diff, ok := snap.(*diffLayer); ok { + parent := diff.parent.Root() + children[parent] = append(children[parent], root) + } + } + var remove func(root common.Hash) + remove = func(root common.Hash) { + delete(t.layers, root) + for _, child := range children[root] { + remove(child) + } + delete(children, root) + } + for root, snap := range t.layers { + if snap.Stale() { + remove(root) + } + } + // If the disk layer was modified, regenerate all the cumulative blooms + if persisted != nil { + var rebloom func(root common.Hash) + rebloom = func(root common.Hash) { + if diff, ok := t.layers[root].(*diffLayer); ok { + diff.rebloom(persisted) + } + for _, child := range children[root] { + rebloom(child) + } + } + rebloom(persisted.root) + } + return nil +} + +// cap traverses downwards the diff tree until the number of allowed layers are +// crossed. All diffs beyond the permitted number are flattened downwards. If the +// layer limit is reached, memory cap is also enforced (but not before). +// +// The method returns the new disk layer if diffs were persisted into it. +// +// Note, the final diff layer count in general will be one more than the amount +// requested. This happens because the bottom-most diff layer is the accumulator +// which may or may not overflow and cascade to disk. Since this last layer's +// survival is only known *after* capping, we need to omit it from the count if +// we want to ensure that *at least* the requested number of diff layers remain. +func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { + // Dive until we run out of layers or reach the persistent database + for i := 0; i < layers-1; i++ { + // If we still have diff layers below, continue down + if parent, ok := diff.parent.(*diffLayer); ok { + diff = parent + } else { + // Diff stack too shallow, return without modifications + return nil + } + } + // We're out of layers, flatten anything below, stopping if it's the disk or if + // the memory limit is not yet exceeded. + switch parent := diff.parent.(type) { + case *diskLayer: + return nil + + case *diffLayer: + // Hold the write lock until the flattened parent is linked correctly. + // Otherwise, the stale layer may be accessed by external reads in the + // meantime. + diff.lock.Lock() + defer diff.lock.Unlock() + + // Flatten the parent into the grandparent. The flattening internally obtains a + // write lock on grandparent. + flattened := parent.flatten().(*diffLayer) + t.layers[flattened.root] = flattened + + // Invoke the hook if it's registered. Ugly hack. + if t.onFlatten != nil { + t.onFlatten() + } + diff.parent = flattened + if flattened.memory < aggregatorMemoryLimit { + // Accumulator layer is smaller than the limit, so we can abort, unless + // there's a snapshot being generated currently. In that case, the trie + // will move from underneath the generator so we **must** merge all the + // partial data down into the snapshot and restart the generation. + if flattened.parent.(*diskLayer).genAbort == nil { + return nil + } + } + default: + panic(fmt.Sprintf("unknown data layer: %T", parent)) + } + // If the bottom-most layer is larger than our memory cap, persist to disk + bottom := diff.parent.(*diffLayer) + + bottom.lock.RLock() + base := diffToDisk(bottom) + bottom.lock.RUnlock() + + t.layers[base.root] = base + diff.parent = base + return base +} + +// diffToDisk merges a bottom-most diff into the persistent disk layer underneath +// it. The method will panic if called onto a non-bottom-most diff layer. +// +// The disk layer persistence should be operated in an atomic way. All updates should +// be discarded if the whole transition if not finished. +func diffToDisk(bottom *diffLayer) *diskLayer { + var ( + base = bottom.parent.(*diskLayer) + batch = base.diskdb.NewBatch() + stats *generatorStats + ) + // If the disk layer is running a snapshot generator, abort it + if base.genAbort != nil { + abort := make(chan *generatorStats) + base.genAbort <- abort + stats = <-abort + } + // Put the deletion in the batch writer, flush all updates in the final step. + rawdb.DeleteSnapshotRoot(batch) + + // Mark the original base as stale as we're going to create a new wrapper + base.lock.Lock() + if base.stale { + panic("parent disk layer is stale") // we've committed into the same base from two children, boo + } + base.stale = true + base.lock.Unlock() + + // Destroy all the destructed accounts from the database + for hash := range bottom.destructSet { + // Skip any account not covered yet by the snapshot + if base.genMarker != nil && bytes.Compare(hash[:], base.genMarker) > 0 { + continue + } + // Remove all storage slots + rawdb.DeleteAccountSnapshot(batch, hash) + base.cache.Set(hash[:], nil) + + it := rawdb.IterateStorageSnapshots(base.diskdb, hash) + for it.Next() { + key := it.Key() + batch.Delete(key) + base.cache.Del(key[1:]) + snapshotFlushStorageItemMeter.Mark(1) + + // 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() > 64*1024*1024 { + if err := batch.Write(); err != nil { + log.Crit("Failed to write storage deletions", "err", err) + } + batch.Reset() + } + } + it.Release() + } + // Push all updated accounts into the database + for hash, data := range bottom.accountData { + // Skip any account not covered yet by the snapshot + if base.genMarker != nil && bytes.Compare(hash[:], base.genMarker) > 0 { + continue + } + // Push the account to disk + rawdb.WriteAccountSnapshot(batch, hash, data) + base.cache.Set(hash[:], data) + snapshotCleanAccountWriteMeter.Mark(int64(len(data))) + + snapshotFlushAccountItemMeter.Mark(1) + snapshotFlushAccountSizeMeter.Mark(int64(len(data))) + + // 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() > 64*1024*1024 { + if err := batch.Write(); err != nil { + log.Crit("Failed to write storage deletions", "err", err) + } + batch.Reset() + } + } + // Push all the storage slots into the database + for accountHash, storage := range bottom.storageData { + // Skip any account not covered yet by the snapshot + if base.genMarker != nil && bytes.Compare(accountHash[:], base.genMarker) > 0 { + continue + } + // Generation might be mid-account, track that case too + midAccount := base.genMarker != nil && bytes.Equal(accountHash[:], base.genMarker[:common.HashLength]) + + for storageHash, data := range storage { + // Skip any slot not covered yet by the snapshot + if midAccount && bytes.Compare(storageHash[:], base.genMarker[common.HashLength:]) > 0 { + continue + } + if len(data) > 0 { + rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) + base.cache.Set(append(accountHash[:], storageHash[:]...), data) + snapshotCleanStorageWriteMeter.Mark(int64(len(data))) + } else { + rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) + base.cache.Set(append(accountHash[:], storageHash[:]...), nil) + } + snapshotFlushStorageItemMeter.Mark(1) + snapshotFlushStorageSizeMeter.Mark(int64(len(data))) + } + } + // Update the snapshot block marker and write any remainder data + rawdb.WriteSnapshotRoot(batch, bottom.root) + + // Write out the generator progress marker and report + journalProgress(batch, base.genMarker, stats) + + // Flush all the updates in the single db operation. Ensure the + // disk layer transition is atomic. + if err := batch.Write(); err != nil { + log.Crit("Failed to write leftover snapshot", "err", err) + } + log.Debug("Journalled disk layer", "root", bottom.root, "complete", base.genMarker == nil) + res := &diskLayer{ + root: bottom.root, + cache: base.cache, + diskdb: base.diskdb, + triedb: base.triedb, + genMarker: base.genMarker, + genPending: base.genPending, + } + // If snapshot generation hasn't finished yet, port over all the starts and + // continue where the previous round left off. + // + // Note, the `base.genAbort` comparison is not used normally, it's checked + // to allow the tests to play with the marker without triggering this path. + if base.genMarker != nil && base.genAbort != nil { + res.genMarker = base.genMarker + res.genAbort = make(chan chan *generatorStats) + go res.generate(stats) + } + return res +} + +// Release releases resources +func (t *Tree) Release() { + t.lock.RLock() + defer t.lock.RUnlock() + + 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). +// +// The method returns the root hash of the base layer that needs to be persisted +// to disk as a trie too to allow continuing any pending generation op. +func (t *Tree) Journal(root common.Hash) (common.Hash, error) { + // Retrieve the head snapshot to journal from var snap snapshot + snap := t.Snapshot(root) + if snap == nil { + return common.Hash{}, fmt.Errorf("snapshot [%#x] missing", root) + } + // Run the journaling + t.lock.Lock() + defer t.lock.Unlock() + + // Firstly write out the metadata of journal + journal := new(bytes.Buffer) + if err := rlp.Encode(journal, journalVersion); err != nil { + return common.Hash{}, err + } + diskroot := t.diskRoot() + if diskroot == (common.Hash{}) { + return common.Hash{}, errors.New("invalid disk root") + } + // Secondly write out the disk layer root, ensure the + // diff journal is continuous with disk. + if err := rlp.Encode(journal, diskroot); err != nil { + return common.Hash{}, err + } + // Finally write out the journal of each layer in reverse order. + base, err := snap.(snapshot).Journal(journal) + if err != nil { + return common.Hash{}, err + } + // Store the journal into the database and return + rawdb.WriteSnapshotJournal(t.diskdb, journal.Bytes()) + return base, nil +} + +// Rebuild wipes all available snapshot data from the persistent database and +// discard all caches and diff layers. Afterwards, it starts a new snapshot +// generator with the given root hash. +func (t *Tree) Rebuild(root common.Hash) { + t.lock.Lock() + defer t.lock.Unlock() + + // Firstly delete any recovery flag in the database. Because now we are + // building a brand new snapshot. Also reenable the snapshot feature. + rawdb.DeleteSnapshotRecoveryNumber(t.diskdb) + rawdb.DeleteSnapshotDisabled(t.diskdb) + + // Iterate over and mark all layers stale + for _, layer := range t.layers { + switch layer := layer.(type) { + case *diskLayer: + // If the base layer is generating, abort it and save + if layer.genAbort != nil { + abort := make(chan *generatorStats) + layer.genAbort <- abort + <-abort + } + // Layer should be inactive now, mark it as stale + layer.lock.Lock() + layer.stale = true + layer.lock.Unlock() + + case *diffLayer: + // If the layer is a simple diff, simply mark as stale + layer.lock.Lock() + layer.stale.Store(true) + layer.lock.Unlock() + + default: + panic(fmt.Sprintf("unknown layer type: %T", layer)) + } + } + // Start generating a new snapshot from scratch on a background thread. The + // generator will run a wiper first if there's not one running right now. + log.Info("Rebuilding state snapshot") + t.layers = map[common.Hash]snapshot{ + root: generateSnapshot(t.diskdb, t.triedb, t.config.CacheSize, root), + } +} + +// AccountIterator creates a new account iterator for the specified root hash and +// seeks to a starting account hash. +func (t *Tree) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) { + ok, err := t.generating() + if err != nil { + return nil, err + } + if ok { + return nil, ErrNotConstructed + } + return newFastAccountIterator(t, root, seek) +} + +// StorageIterator creates a new storage iterator for the specified root hash and +// account. The iterator will be move to the specific start position. +func (t *Tree) StorageIterator(root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) { + ok, err := t.generating() + if err != nil { + return nil, err + } + if ok { + return nil, ErrNotConstructed + } + return newFastStorageIterator(t, root, account, seek) +} + +// Verify iterates the whole state(all the accounts as well as the corresponding storages) +// with the specific root and compares the re-computed hash with the original one. +func (t *Tree) Verify(root common.Hash) error { + acctIt, err := t.AccountIterator(root, common.Hash{}) + if err != nil { + return err + } + defer acctIt.Release() + + got, err := generateTrieRoot(nil, "", acctIt, common.Hash{}, stackTrieGenerate, func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + storageIt, err := t.StorageIterator(root, accountHash, common.Hash{}) + if err != nil { + return common.Hash{}, err + } + defer storageIt.Release() + + hash, err := generateTrieRoot(nil, "", storageIt, accountHash, stackTrieGenerate, nil, stat, false) + if err != nil { + return common.Hash{}, err + } + return hash, nil + }, newGenerateStats(), true) + + if err != nil { + return err + } + if got != root { + return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root) + } + return nil +} + +// disklayer is an internal helper function to return the disk layer. +// The lock of snapTree is assumed to be held already. +func (t *Tree) disklayer() *diskLayer { + var snap snapshot + for _, s := range t.layers { + snap = s + break + } + if snap == nil { + return nil + } + switch layer := snap.(type) { + case *diskLayer: + return layer + case *diffLayer: + layer.lock.RLock() + defer layer.lock.RUnlock() + return layer.origin + default: + panic(fmt.Sprintf("%T: undefined layer", snap)) + } +} + +// diskRoot is an internal helper function to return the disk layer root. +// The lock of snapTree is assumed to be held already. +func (t *Tree) diskRoot() common.Hash { + disklayer := t.disklayer() + if disklayer == nil { + return common.Hash{} + } + return disklayer.Root() +} + +// generating is an internal helper function which reports whether the snapshot +// is still under the construction. +func (t *Tree) generating() (bool, error) { + t.lock.RLock() + defer t.lock.RUnlock() + + layer := t.disklayer() + if layer == nil { + return false, errors.New("disk layer is missing") + } + layer.lock.RLock() + defer layer.lock.RUnlock() + return layer.genMarker != nil, nil +} + +// DiskRoot is an external helper function to return the disk layer root. +func (t *Tree) DiskRoot() common.Hash { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.diskRoot() +} + +// Size returns the memory usage of the diff layers above the disk layer and the +// dirty nodes buffered in the disk layer. Currently, the implementation uses a +// special diff layer (the first) as an aggregator simulating a dirty buffer, so +// the second return will always be 0. However, this will be made consistent with +// the pathdb, which will require a second return. +func (t *Tree) Size() (diffs common.StorageSize, buf common.StorageSize) { + t.lock.RLock() + defer t.lock.RUnlock() + + var size common.StorageSize + for _, layer := range t.layers { + if layer, ok := layer.(*diffLayer); ok { + size += common.StorageSize(layer.memory) + } + } + return size, 0 +} diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go new file mode 100644 index 0000000..a9ab3ea --- /dev/null +++ b/core/state/snapshot/snapshot_test.go @@ -0,0 +1,491 @@ +// 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 snapshot + +import ( + crand "crypto/rand" + "encoding/binary" + "fmt" + "math/rand" + "testing" + "time" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" +) + +// randomHash generates a random blob of data and returns it as a hash. +func randomHash() common.Hash { + var hash common.Hash + if n, err := crand.Read(hash[:]); n != common.HashLength || err != nil { + panic(err) + } + return hash +} + +// randomAccount generates a random account and returns it RLP encoded. +func randomAccount() []byte { + a := &types.StateAccount{ + Balance: uint256.NewInt(rand.Uint64()), + Nonce: rand.Uint64(), + Root: randomHash(), + CodeHash: types.EmptyCodeHash[:], + } + data, _ := rlp.EncodeToBytes(a) + return data +} + +// randomAccountSet generates a set of random accounts with the given strings as +// the account address hashes. +func randomAccountSet(hashes ...string) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for _, hash := range hashes { + accounts[common.HexToHash(hash)] = randomAccount() + } + return accounts +} + +// randomStorageSet generates a set of random slots with the given strings as +// the slot addresses. +func randomStorageSet(accounts []string, hashes [][]string, nilStorage [][]string) map[common.Hash]map[common.Hash][]byte { + storages := make(map[common.Hash]map[common.Hash][]byte) + for index, account := range accounts { + storages[common.HexToHash(account)] = make(map[common.Hash][]byte) + + if index < len(hashes) { + hashes := hashes[index] + for _, hash := range hashes { + storages[common.HexToHash(account)][common.HexToHash(hash)] = randomHash().Bytes() + } + } + if index < len(nilStorage) { + nils := nilStorage[index] + for _, hash := range nils { + storages[common.HexToHash(account)][common.HexToHash(hash)] = nil + } + } + } + return storages +} + +// Tests that if a disk layer becomes stale, no active external references will +// be returned with junk data. This version of the test flattens every diff layer +// to check internal corner case around the bottom-most memory accumulator. +func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Retrieve a reference to the base and commit a diff on top + ref := snaps.Snapshot(base.root) + + accounts := map[common.Hash][]byte{ + common.HexToHash("0xa1"): randomAccount(), + } + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if n := len(snaps.layers); n != 2 { + t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2) + } + // Commit the diff layer onto the disk and ensure it's persisted + if err := snaps.Cap(common.HexToHash("0x02"), 0); err != nil { + t.Fatalf("failed to merge diff layer onto disk: %v", err) + } + // Since the base layer was modified, ensure that data retrievals on the external reference fail + if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { + t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) + } + if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { + t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) + } + if n := len(snaps.layers); n != 1 { + t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 1) + fmt.Println(snaps.layers) + } +} + +// Tests that if a disk layer becomes stale, no active external references will +// be returned with junk data. This version of the test retains the bottom diff +// layer to check the usual mode of operation where the accumulator is retained. +func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Retrieve a reference to the base and commit two diffs on top + ref := snaps.Snapshot(base.root) + + accounts := map[common.Hash][]byte{ + common.HexToHash("0xa1"): randomAccount(), + } + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if n := len(snaps.layers); n != 3 { + t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) + } + // Commit the diff layer onto the disk and ensure it's persisted + defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) + aggregatorMemoryLimit = 0 + + if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil { + t.Fatalf("failed to merge accumulator onto disk: %v", err) + } + // Since the base layer was modified, ensure that data retrievals on the external reference fail + if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { + t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) + } + if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { + t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) + } + if n := len(snaps.layers); n != 2 { + t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) + fmt.Println(snaps.layers) + } +} + +// Tests that if a diff layer becomes stale, no active external references will +// be returned with junk data. This version of the test retains the bottom diff +// layer to check the usual mode of operation where the accumulator is retained. +func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { + // Un-commenting this triggers the bloom set to be deterministic. The values below + // were used to trigger the flaw described in https://github.com/ethereum/go-ethereum/issues/27254. + // bloomDestructHasherOffset, bloomAccountHasherOffset, bloomStorageHasherOffset = 14, 24, 5 + + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Commit three diffs on top and retrieve a reference to the bottommost + accounts := map[common.Hash][]byte{ + common.HexToHash("0xa1"): randomAccount(), + } + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if n := len(snaps.layers); n != 4 { + t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 4) + } + ref := snaps.Snapshot(common.HexToHash("0x02")) + + // Doing a Cap operation with many allowed layers should be a no-op + exp := len(snaps.layers) + if err := snaps.Cap(common.HexToHash("0x04"), 2000); err != nil { + t.Fatalf("failed to flatten diff layer into accumulator: %v", err) + } + if got := len(snaps.layers); got != exp { + t.Errorf("layers modified, got %d exp %d", got, exp) + } + // Flatten the diff layer into the bottom accumulator + if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil { + t.Fatalf("failed to flatten diff layer into accumulator: %v", err) + } + // Since the accumulator diff layer was modified, ensure that data retrievals on the external reference fail + if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { + t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) + } + if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { + t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) + } + if n := len(snaps.layers); n != 3 { + t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 3) + fmt.Println(snaps.layers) + } +} + +// TestPostCapBasicDataAccess tests some functionality regarding capping/flattening. +func TestPostCapBasicDataAccess(t *testing.T) { + // setAccount is a helper to construct a random account entry and assign it to + // an account slot in a snapshot + setAccount := func(accKey string) map[common.Hash][]byte { + return map[common.Hash][]byte{ + common.HexToHash(accKey): randomAccount(), + } + } + // Create a starting base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // The lowest difflayer + snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) + snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) + snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), nil, setAccount("0xb2"), nil) + + snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) + snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil) + + // checkExist verifies if an account exists in a snapshot + checkExist := func(layer *diffLayer, key string) error { + if data, _ := layer.Account(common.HexToHash(key)); data == nil { + return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key)) + } + return nil + } + // shouldErr checks that an account access errors as expected + shouldErr := func(layer *diffLayer, key string) error { + if data, err := layer.Account(common.HexToHash(key)); err == nil { + return fmt.Errorf("expected error, got data %x", data) + } + return nil + } + // check basics + snap := snaps.Snapshot(common.HexToHash("0xb3")).(*diffLayer) + + if err := checkExist(snap, "0xa1"); err != nil { + t.Error(err) + } + if err := checkExist(snap, "0xb2"); err != nil { + t.Error(err) + } + if err := checkExist(snap, "0xb3"); err != nil { + t.Error(err) + } + // Cap to a bad root should fail + if err := snaps.Cap(common.HexToHash("0x1337"), 0); err == nil { + t.Errorf("expected error, got none") + } + // Now, merge the a-chain + snaps.Cap(common.HexToHash("0xa3"), 0) + + // At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is + // the parent of b2, b2 should no longer be able to iterate into parent. + + // These should still be accessible + if err := checkExist(snap, "0xb2"); err != nil { + t.Error(err) + } + if err := checkExist(snap, "0xb3"); err != nil { + t.Error(err) + } + // But these would need iteration into the modified parent + if err := shouldErr(snap, "0xa1"); err != nil { + t.Error(err) + } + if err := shouldErr(snap, "0xa2"); err != nil { + t.Error(err) + } + if err := shouldErr(snap, "0xa3"); err != nil { + t.Error(err) + } + // Now, merge it again, just for fun. It should now error, since a3 + // is a disk layer + if err := snaps.Cap(common.HexToHash("0xa3"), 0); err == nil { + t.Error("expected error capping the disk layer, got none") + } +} + +// TestSnaphots tests the functionality for retrieving the snapshot +// with given head root and the desired depth. +func TestSnaphots(t *testing.T) { + // setAccount is a helper to construct a random account entry and assign it to + // an account slot in a snapshot + setAccount := func(accKey string) map[common.Hash][]byte { + return map[common.Hash][]byte{ + common.HexToHash(accKey): randomAccount(), + } + } + makeRoot := func(height uint64) common.Hash { + var buffer [8]byte + binary.BigEndian.PutUint64(buffer[:], height) + return common.BytesToHash(buffer[:]) + } + // Create a starting base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: makeRoot(1), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Construct the snapshots with 129 layers, flattening whatever's above that + var ( + last = common.HexToHash("0x01") + head common.Hash + ) + for i := 0; i < 129; i++ { + head = makeRoot(uint64(i + 2)) + snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil) + last = head + snaps.Cap(head, 128) // 130 layers (128 diffs + 1 accumulator + 1 disk) + } + var cases = []struct { + headRoot common.Hash + limit int + nodisk bool + expected int + expectBottom common.Hash + }{ + {head, 0, false, 0, common.Hash{}}, + {head, 64, false, 64, makeRoot(129 + 2 - 64)}, + {head, 128, false, 128, makeRoot(3)}, // Normal diff layers, no accumulator + {head, 129, true, 129, makeRoot(2)}, // All diff layers, including accumulator + {head, 130, false, 130, makeRoot(1)}, // All diff layers + disk layer + } + for i, c := range cases { + layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) + if len(layers) != c.expected { + t.Errorf("non-overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers)) + } + if len(layers) == 0 { + continue + } + bottommost := layers[len(layers)-1] + if bottommost.Root() != c.expectBottom { + t.Errorf("non-overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) + } + } + // Above we've tested the normal capping, which leaves the accumulator live. + // Test that if the bottommost accumulator diff layer overflows the allowed + // memory limit, the snapshot tree gets capped to one less layer. + // Commit the diff layer onto the disk and ensure it's persisted + defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) + aggregatorMemoryLimit = 0 + + snaps.Cap(head, 128) // 129 (128 diffs + 1 overflown accumulator + 1 disk) + + cases = []struct { + headRoot common.Hash + limit int + nodisk bool + expected int + expectBottom common.Hash + }{ + {head, 0, false, 0, common.Hash{}}, + {head, 64, false, 64, makeRoot(129 + 2 - 64)}, + {head, 128, false, 128, makeRoot(3)}, // All diff layers, accumulator was flattened + {head, 129, true, 128, makeRoot(3)}, // All diff layers, accumulator was flattened + {head, 130, false, 129, makeRoot(2)}, // All diff layers + disk layer + } + for i, c := range cases { + layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) + if len(layers) != c.expected { + t.Errorf("overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers)) + } + if len(layers) == 0 { + continue + } + bottommost := layers[len(layers)-1] + if bottommost.Root() != c.expectBottom { + t.Errorf("overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) + } + } +} + +// TestReadStateDuringFlattening tests the scenario that, during the +// bottom diff layers are merging which tags these as stale, the read +// happens via a pre-created top snapshot layer which tries to access +// the state in these stale layers. Ensure this read can retrieve the +// right state back(block until the flattening is finished) instead of +// an unexpected error(snapshot layer is stale). +func TestReadStateDuringFlattening(t *testing.T) { + // setAccount is a helper to construct a random account entry and assign it to + // an account slot in a snapshot + setAccount := func(accKey string) map[common.Hash][]byte { + return map[common.Hash][]byte{ + common.HexToHash(accKey): randomAccount(), + } + } + // Create a starting base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // 4 layers in total, 3 diff layers and 1 disk layers + snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) + snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) + snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) + + // Obtain the topmost snapshot handler for state accessing + snap := snaps.Snapshot(common.HexToHash("0xa3")) + + // Register the testing hook to access the state after flattening + var result = make(chan *types.SlimAccount) + snaps.onFlatten = func() { + // Spin up a thread to read the account from the pre-created + // snapshot handler. It's expected to be blocked. + go func() { + account, _ := snap.Account(common.HexToHash("0xa1")) + result <- account + }() + select { + case res := <-result: + t.Fatalf("Unexpected return %v", res) + case <-time.NewTimer(time.Millisecond * 300).C: + } + } + // Cap the snap tree, which will mark the bottom-most layer as stale. + snaps.Cap(common.HexToHash("0xa3"), 1) + select { + case account := <-result: + if account == nil { + t.Fatal("Failed to retrieve account") + } + case <-time.NewTimer(time.Millisecond * 300).C: + t.Fatal("Unexpected blocker") + } +} diff --git a/core/state/snapshot/utils.go b/core/state/snapshot/utils.go new file mode 100644 index 0000000..62f073d --- /dev/null +++ b/core/state/snapshot/utils.go @@ -0,0 +1,152 @@ +// 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 snapshot + +import ( + "bytes" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// CheckDanglingStorage iterates the snap storage data, and verifies that all +// storage also has corresponding account data. +func CheckDanglingStorage(chaindb ethdb.KeyValueStore) error { + if err := checkDanglingDiskStorage(chaindb); err != nil { + log.Error("Database check error", "err", err) + } + return checkDanglingMemStorage(chaindb) +} + +// checkDanglingDiskStorage checks if there is any 'dangling' storage data in the +// disk-backed snapshot layer. +func checkDanglingDiskStorage(chaindb ethdb.KeyValueStore) error { + var ( + lastReport = time.Now() + start = time.Now() + lastKey []byte + it = rawdb.NewKeyLengthIterator(chaindb.NewIterator(rawdb.SnapshotStoragePrefix, nil), 1+2*common.HashLength) + ) + log.Info("Checking dangling snapshot disk storage") + + defer it.Release() + for it.Next() { + k := it.Key() + accKey := k[1:33] + if bytes.Equal(accKey, lastKey) { + // No need to look up for every slot + continue + } + lastKey = common.CopyBytes(accKey) + if time.Since(lastReport) > time.Second*8 { + log.Info("Iterating snap storage", "at", fmt.Sprintf("%#x", accKey), "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + if data := rawdb.ReadAccountSnapshot(chaindb, common.BytesToHash(accKey)); len(data) == 0 { + log.Warn("Dangling storage - missing account", "account", fmt.Sprintf("%#x", accKey), "storagekey", fmt.Sprintf("%#x", k)) + return fmt.Errorf("dangling snapshot storage account %#x", accKey) + } + } + log.Info("Verified the snapshot disk storage", "time", common.PrettyDuration(time.Since(start)), "err", it.Error()) + return nil +} + +// checkDanglingMemStorage checks if there is any 'dangling' storage in the journalled +// snapshot difflayers. +func checkDanglingMemStorage(db ethdb.KeyValueStore) error { + start := time.Now() + log.Info("Checking dangling journalled storage") + err := iterateJournal(db, func(pRoot, root common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { + for accHash := range storage { + if _, ok := accounts[accHash]; !ok { + log.Error("Dangling storage - missing account", "account", fmt.Sprintf("%#x", accHash), "root", root) + } + } + return nil + }) + if err != nil { + log.Info("Failed to resolve snapshot journal", "err", err) + return err + } + log.Info("Verified the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start))) + return nil +} + +// CheckJournalAccount shows information about an account, from the disk layer and +// up through the diff layers. +func CheckJournalAccount(db ethdb.KeyValueStore, hash common.Hash) error { + // Look up the disk layer first + baseRoot := rawdb.ReadSnapshotRoot(db) + fmt.Printf("Disklayer: Root: %x\n", baseRoot) + if data := rawdb.ReadAccountSnapshot(db, hash); data != nil { + account, err := types.FullAccount(data) + if err != nil { + panic(err) + } + fmt.Printf("\taccount.nonce: %d\n", account.Nonce) + fmt.Printf("\taccount.balance: %x\n", account.Balance) + fmt.Printf("\taccount.root: %x\n", account.Root) + fmt.Printf("\taccount.codehash: %x\n", account.CodeHash) + } + // Check storage + { + it := rawdb.NewKeyLengthIterator(db.NewIterator(append(rawdb.SnapshotStoragePrefix, hash.Bytes()...), nil), 1+2*common.HashLength) + fmt.Printf("\tStorage:\n") + for it.Next() { + slot := it.Key()[33:] + fmt.Printf("\t\t%x: %x\n", slot, it.Value()) + } + it.Release() + } + var depth = 0 + + return iterateJournal(db, func(pRoot, root common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { + _, a := accounts[hash] + _, b := destructs[hash] + _, c := storage[hash] + depth++ + if !a && !b && !c { + return nil + } + fmt.Printf("Disklayer+%d: Root: %x, parent %x\n", depth, root, pRoot) + if data, ok := accounts[hash]; ok { + account, err := types.FullAccount(data) + if err != nil { + panic(err) + } + fmt.Printf("\taccount.nonce: %d\n", account.Nonce) + fmt.Printf("\taccount.balance: %x\n", account.Balance) + fmt.Printf("\taccount.root: %x\n", account.Root) + fmt.Printf("\taccount.codehash: %x\n", account.CodeHash) + } + if _, ok := destructs[hash]; ok { + fmt.Printf("\t Destructed!") + } + if data, ok := storage[hash]; ok { + fmt.Printf("\tStorage\n") + for k, v := range data { + fmt.Printf("\t\t%x: %x\n", k, v) + } + } + return nil + }) +} diff --git a/core/state/state_object.go b/core/state/state_object.go new file mode 100644 index 0000000..880b715 --- /dev/null +++ b/core/state/state_object.go @@ -0,0 +1,639 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "bytes" + "fmt" + "maps" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/holiman/uint256" +) + +type Storage map[common.Hash]common.Hash + +func (s Storage) Copy() Storage { + return maps.Clone(s) +} + +// stateObject represents an Ethereum account which is being modified. +// +// The usage pattern is as follows: +// - First you need to obtain a state object. +// - Account values as well as storages can be accessed and modified through the object. +// - Finally, call commit to return the changes of storage trie and update account data. +type stateObject struct { + db *StateDB + address common.Address // address of ethereum account + addrHash common.Hash // hash of ethereum address of the account + origin *types.StateAccount // Account original data without any change applied, nil means it was not existent + data types.StateAccount // Account data with all mutations applied in the scope of block + + // Write caches. + trie Trie // storage trie, which becomes non-nil on first access + code []byte // contract bytecode, which gets set when code is loaded + + originStorage Storage // Storage entries that have been accessed within the current block + dirtyStorage Storage // Storage entries that have been modified within the current transaction + pendingStorage Storage // Storage entries that have been modified within the current block + + // uncommittedStorage tracks a set of storage entries that have been modified + // but not yet committed since the "last commit operation", along with their + // original values before mutation. + // + // Specifically, the commit will be performed after each transaction before + // the byzantium fork, therefore the map is already reset at the transaction + // boundary; however post the byzantium fork, the commit will only be performed + // at the end of block, this set essentially tracks all the modifications + // made within the block. + uncommittedStorage Storage + + // Cache flags. + dirtyCode bool // true if the code was updated + + // Flag whether the account was marked as self-destructed. The self-destructed + // account is still accessible in the scope of same transaction. + selfDestructed bool + + // This is an EIP-6780 flag indicating whether the object is eligible for + // self-destruct according to EIP-6780. The flag could be set either when + // the contract is just created within the current transaction, or when the + // object was previously existent and is being deployed as a contract within + // the current transaction. + newContract bool +} + +// empty returns whether the account is considered empty. +func (s *stateObject) empty() bool { + return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes()) +} + +// newObject creates a state object. +func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { + origin := acct + if acct == nil { + acct = types.NewEmptyStateAccount() + } + return &stateObject{ + db: db, + address: address, + addrHash: crypto.Keccak256Hash(address[:]), + origin: origin, + data: *acct, + originStorage: make(Storage), + dirtyStorage: make(Storage), + pendingStorage: make(Storage), + uncommittedStorage: make(Storage), + } +} + +func (s *stateObject) markSelfdestructed() { + s.selfDestructed = true +} + +func (s *stateObject) touch() { + s.db.journal.append(touchChange{ + account: &s.address, + }) + if s.address == ripemd { + // Explicitly put it in the dirty-cache, which is otherwise generated from + // flattened journals. + s.db.journal.dirty(s.address) + } +} + +// getTrie returns the associated storage trie. The trie will be opened if it's +// not loaded previously. An error will be returned if trie can't be loaded. +// +// If a new trie is opened, it will be cached within the state object to allow +// subsequent reads to expand the same trie instead of reloading from disk. +func (s *stateObject) getTrie() (Trie, error) { + if s.trie == nil { + tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie) + if err != nil { + return nil, err + } + s.trie = tr + } + return s.trie, nil +} + +// getPrefetchedTrie returns the associated trie, as populated by the prefetcher +// if it's available. +// +// Note, opposed to getTrie, this method will *NOT* blindly cache the resulting +// trie in the state object. The caller might want to do that, but it's cleaner +// to break the hidden interdependency between retrieving tries from the db or +// from the prefetcher. +func (s *stateObject) getPrefetchedTrie() Trie { + // If there's nothing to meaningfully return, let the user figure it out by + // pulling the trie from disk. + if s.data.Root == types.EmptyRootHash || s.db.prefetcher == nil { + return nil + } + // Attempt to retrieve the trie from the prefetcher + return s.db.prefetcher.trie(s.addrHash, s.data.Root) +} + +// GetState retrieves a value associated with the given storage key. +func (s *stateObject) GetState(key common.Hash) common.Hash { + value, _ := s.getState(key) + return value +} + +// getState retrieves a value associated with the given storage key, along with +// its original value. +func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) { + origin := s.GetCommittedState(key) + value, dirty := s.dirtyStorage[key] + if dirty { + return value, origin + } + return origin, origin +} + +// GetCommittedState retrieves the value associated with the specific key +// without any mutations caused in the current execution. +func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { + // If we have a pending write or clean cached, return that + if value, pending := s.pendingStorage[key]; pending { + return value + } + if value, cached := s.originStorage[key]; cached { + return value + } + // If the object was destructed in *this* block (and potentially resurrected), + // the storage has been cleared out, and we should *not* consult the previous + // database about any storage values. The only possible alternatives are: + // 1) resurrect happened, and new slot values were set -- those should + // have been handles via pendingStorage above. + // 2) we don't have new values, and can deliver empty response back + if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { + s.originStorage[key] = common.Hash{} // track the empty slot as origin value + return common.Hash{} + } + // If no live objects are available, attempt to use snapshots + var ( + enc []byte + err error + value common.Hash + ) + if s.db.snap != nil { + start := time.Now() + enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) + s.db.SnapshotStorageReads += time.Since(start) + + if len(enc) > 0 { + _, content, _, err := rlp.Split(enc) + if err != nil { + s.db.setError(err) + } + value.SetBytes(content) + } + } + // If the snapshot is unavailable or reading from it fails, load from the database. + if s.db.snap == nil || err != nil { + start := time.Now() + tr, err := s.getTrie() + if err != nil { + s.db.setError(err) + return common.Hash{} + } + val, err := tr.GetStorage(s.address, key.Bytes()) + s.db.StorageReads += time.Since(start) + + if err != nil { + s.db.setError(err) + return common.Hash{} + } + value.SetBytes(val) + } + // Independent of where we loaded the data from, add it to the prefetcher. + // Whilst this would be a bit weird if snapshots are disabled, but we still + // want the trie nodes to end up in the prefetcher too, so just push through. + if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash { + if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, [][]byte{key[:]}, true); err != nil { + log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err) + } + } + s.originStorage[key] = value + return value +} + +// SetState updates a value in account storage. +func (s *stateObject) SetState(key, value common.Hash) { + // If the new value is the same as old, don't set. Otherwise, track only the + // dirty changes, supporting reverting all of it back to no change. + prev, origin := s.getState(key) + if prev == value { + return + } + // New value is different, update and journal the change + s.db.journal.append(storageChange{ + account: &s.address, + key: key, + prevvalue: prev, + origvalue: origin, + }) + if s.db.logger != nil && s.db.logger.OnStorageChange != nil { + s.db.logger.OnStorageChange(s.address, key, prev, value) + } + s.setState(key, value, origin) +} + +// setState updates a value in account dirty storage. The dirtiness will be +// removed if the value being set equals to the original value. +func (s *stateObject) setState(key common.Hash, value common.Hash, origin common.Hash) { + // Storage slot is set back to its original value, undo the dirty marker + if value == origin { + delete(s.dirtyStorage, key) + return + } + s.dirtyStorage[key] = value +} + +// finalise moves all dirty storage slots into the pending area to be hashed or +// committed later. It is invoked at the end of every transaction. +func (s *stateObject) finalise() { + slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage)) + for key, value := range s.dirtyStorage { + if origin, exist := s.uncommittedStorage[key]; exist && origin == value { + // The slot is reverted to its original value, delete the entry + // to avoid thrashing the data structures. + delete(s.uncommittedStorage, key) + } else if exist { + // The slot is modified to another value and the slot has been + // tracked for commit, do nothing here. + } else { + // The slot is different from its original value and hasn't been + // tracked for commit yet. + s.uncommittedStorage[key] = s.GetCommittedState(key) + slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure + } + // Aggregate the dirty storage slots into the pending area. It might + // be possible that the value of tracked slot here is same with the + // one in originStorage (e.g. the slot was modified in tx_a and then + // modified back in tx_b). We can't blindly remove it from pending + // map as the dirty slot might have been committed already (before the + // byzantium fork) and entry is necessary to modify the value back. + s.pendingStorage[key] = value + } + if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { + if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch, false); err != nil { + log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err) + } + } + if len(s.dirtyStorage) > 0 { + s.dirtyStorage = make(Storage) + } + // Revoke the flag at the end of the transaction. It finalizes the status + // of the newly-created object as it's no longer eligible for self-destruct + // by EIP-6780. For non-newly-created objects, it's a no-op. + s.newContract = false +} + +// 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. +// +// It assumes all the dirty storage slots have been finalized before. +func (s *stateObject) updateTrie() (Trie, error) { + // Short circuit if nothing was accessed, don't trigger a prefetcher warning + if len(s.uncommittedStorage) == 0 { + // Nothing was written, so we could stop early. Unless we have both reads + // and witness collection enabled, in which case we need to fetch the trie. + if s.db.witness == nil || len(s.originStorage) == 0 { + return s.trie, nil + } + } + // Retrieve a pretecher populated trie, or fall back to the database. This will + // block until all prefetch tasks are done, which are needed for witnesses even + // for unmodified state objects. + tr := s.getPrefetchedTrie() + if tr != nil { + // Prefetcher returned a live trie, swap it out for the current one + s.trie = tr + } else { + // Fetcher not running or empty trie, fallback to the database trie + var err error + tr, err = s.getTrie() + if err != nil { + s.db.setError(err) + return nil, err + } + } + // Short circuit if nothing changed, don't bother with hashing anything + if len(s.uncommittedStorage) == 0 { + return s.trie, nil + } + // Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes + // in circumstances similar to the following: + // + // Consider nodes `A` and `B` who share the same full node parent `P` and have no other siblings. + // During the execution of a block: + // - `A` is deleted, + // - `C` is created, and also shares the parent `P`. + // If the deletion is handled first, then `P` would be left with only one child, thus collapsed + // into a shortnode. This requires `B` to be resolved from disk. + // Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved. + var ( + deletions []common.Hash + used = make([][]byte, 0, len(s.uncommittedStorage)) + ) + for key, origin := range s.uncommittedStorage { + // Skip noop changes, persist actual changes + value, exist := s.pendingStorage[key] + if value == origin { + log.Error("Storage update was noop", "address", s.address, "slot", key) + continue + } + if !exist { + log.Error("Storage slot is not found in pending area", s.address, "slot", key) + continue + } + if (value != common.Hash{}) { + if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil { + s.db.setError(err) + return nil, err + } + s.db.StorageUpdated.Add(1) + } else { + deletions = append(deletions, key) + } + // Cache the items for preloading + used = append(used, common.CopyBytes(key[:])) // Copy needed for closure + } + for _, key := range deletions { + if err := tr.DeleteStorage(s.address, key[:]); err != nil { + s.db.setError(err) + return nil, err + } + s.db.StorageDeleted.Add(1) + } + if s.db.prefetcher != nil { + s.db.prefetcher.used(s.addrHash, s.data.Root, used) + } + s.uncommittedStorage = make(Storage) // empties the commit markers + return tr, nil +} + +// updateRoot flushes all cached storage mutations to trie, recalculating the +// new storage trie root. +func (s *stateObject) updateRoot() { + // Flush cached storage mutations into trie, short circuit if any error + // is occurred or there is no change in the trie. + tr, err := s.updateTrie() + if err != nil || tr == nil { + return + } + s.data.Root = tr.Hash() +} + +// commitStorage overwrites the clean storage with the storage changes and +// fulfills the storage diffs into the given accountUpdate struct. +func (s *stateObject) commitStorage(op *accountUpdate) { + var ( + buf = crypto.NewKeccakState() + encode = func(val common.Hash) []byte { + if val == (common.Hash{}) { + return nil + } + blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:])) + return blob + } + ) + for key, val := range s.pendingStorage { + // Skip the noop storage changes, it might be possible the value + // of tracked slot is same in originStorage and pendingStorage + // map, e.g. the storage slot is modified in tx_a and then reset + // back in tx_b. + if val == s.originStorage[key] { + continue + } + hash := crypto.HashData(buf, key[:]) + if op.storages == nil { + op.storages = make(map[common.Hash][]byte) + } + op.storages[hash] = encode(val) + if op.storagesOrigin == nil { + op.storagesOrigin = make(map[common.Hash][]byte) + } + op.storagesOrigin[hash] = encode(s.originStorage[key]) + + // Overwrite the clean value of storage slots + s.originStorage[key] = val + } + s.pendingStorage = make(Storage) +} + +// commit obtains the account changes (metadata, storage slots, code) caused by +// state execution along with the dirty storage trie nodes. +// +// Note, commit may run concurrently across all the state objects. Do not assume +// thread-safe access to the statedb. +func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) { + // commit the account metadata changes + op := &accountUpdate{ + address: s.address, + data: types.SlimAccountRLP(s.data), + } + if s.origin != nil { + op.origin = types.SlimAccountRLP(*s.origin) + } + // commit the contract code if it's modified + if s.dirtyCode { + op.code = &contractCode{ + hash: common.BytesToHash(s.CodeHash()), + blob: s.code, + } + s.dirtyCode = false // reset the dirty flag + } + // Commit storage changes and the associated storage trie + s.commitStorage(op) + if len(op.storages) == 0 { + // nothing changed, don't bother to commit the trie + s.origin = s.data.Copy() + return op, nil, nil + } + root, nodes := s.trie.Commit(false) + s.data.Root = root + s.origin = s.data.Copy() + return op, nodes, nil +} + +// AddBalance adds amount to s's balance. +// It is used to add funds to the destination account of a transfer. +func (s *stateObject) AddBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) { + // EIP161: We must check emptiness for the objects such that the account + // clearing (0,0,0 objects) can take effect. + if amount.IsZero() { + if s.empty() { + s.touch() + } + return + } + s.SetBalance(new(uint256.Int).Add(s.Balance(), amount), reason) +} + +// SubBalance removes amount from s's balance. +// It is used to remove funds from the origin account of a transfer. +func (s *stateObject) SubBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) { + if amount.IsZero() { + return + } + s.SetBalance(new(uint256.Int).Sub(s.Balance(), amount), reason) +} + +func (s *stateObject) SetBalance(amount *uint256.Int, reason tracing.BalanceChangeReason) { + s.db.journal.append(balanceChange{ + account: &s.address, + prev: new(uint256.Int).Set(s.data.Balance), + }) + if s.db.logger != nil && s.db.logger.OnBalanceChange != nil { + s.db.logger.OnBalanceChange(s.address, s.Balance().ToBig(), amount.ToBig(), reason) + } + s.setBalance(amount) +} + +func (s *stateObject) setBalance(amount *uint256.Int) { + s.data.Balance = amount +} + +func (s *stateObject) deepCopy(db *StateDB) *stateObject { + obj := &stateObject{ + db: db, + address: s.address, + addrHash: s.addrHash, + origin: s.origin, + data: s.data, + code: s.code, + originStorage: s.originStorage.Copy(), + pendingStorage: s.pendingStorage.Copy(), + dirtyStorage: s.dirtyStorage.Copy(), + uncommittedStorage: s.uncommittedStorage.Copy(), + dirtyCode: s.dirtyCode, + selfDestructed: s.selfDestructed, + newContract: s.newContract, + } + if s.trie != nil { + obj.trie = db.db.CopyTrie(s.trie) + } + return obj +} + +// +// Attribute accessors +// + +// Address returns the address of the contract/account +func (s *stateObject) Address() common.Address { + return s.address +} + +// Code returns the contract code associated with this object, if any. +func (s *stateObject) Code() []byte { + if len(s.code) != 0 { + return s.code + } + if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { + return nil + } + code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash())) + if err != nil { + s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err)) + } + s.code = code + return code +} + +// CodeSize returns the size of the contract code associated with this object, +// or zero if none. This method is an almost mirror of Code, but uses a cache +// inside the database to avoid loading codes seen recently. +func (s *stateObject) CodeSize() int { + if len(s.code) != 0 { + return len(s.code) + } + if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { + return 0 + } + size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash())) + if err != nil { + s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err)) + } + return size +} + +func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { + prevcode := s.Code() + s.db.journal.append(codeChange{ + account: &s.address, + prevhash: s.CodeHash(), + prevcode: prevcode, + }) + if s.db.logger != nil && s.db.logger.OnCodeChange != nil { + s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevcode, codeHash, code) + } + s.setCode(codeHash, code) +} + +func (s *stateObject) setCode(codeHash common.Hash, code []byte) { + s.code = code + s.data.CodeHash = codeHash[:] + s.dirtyCode = true +} + +func (s *stateObject) SetNonce(nonce uint64) { + s.db.journal.append(nonceChange{ + account: &s.address, + prev: s.data.Nonce, + }) + if s.db.logger != nil && s.db.logger.OnNonceChange != nil { + s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce) + } + s.setNonce(nonce) +} + +func (s *stateObject) setNonce(nonce uint64) { + s.data.Nonce = nonce +} + +func (s *stateObject) CodeHash() []byte { + return s.data.CodeHash +} + +func (s *stateObject) Balance() *uint256.Int { + return s.data.Balance +} + +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_object_test.go b/core/state/state_object_test.go new file mode 100644 index 0000000..42fd778 --- /dev/null +++ b/core/state/state_object_test.go @@ -0,0 +1,46 @@ +// 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 state + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func BenchmarkCutOriginal(b *testing.B) { + value := common.HexToHash("0x01") + for i := 0; i < b.N; i++ { + bytes.TrimLeft(value[:], "\x00") + } +} + +func BenchmarkCutsetterFn(b *testing.B) { + value := common.HexToHash("0x01") + cutSetFn := func(r rune) bool { return r == 0 } + for i := 0; i < b.N; i++ { + bytes.TrimLeftFunc(value[:], cutSetFn) + } +} + +func BenchmarkCutCustomTrim(b *testing.B) { + value := common.HexToHash("0x01") + for i := 0; i < b.N; i++ { + common.TrimLeftZeroes(value[:]) + } +} diff --git a/core/state/state_test.go b/core/state/state_test.go new file mode 100644 index 0000000..9200e4a --- /dev/null +++ b/core/state/state_test.go @@ -0,0 +1,213 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/triedb" + "github.com/holiman/uint256" +) + +type stateEnv struct { + db ethdb.Database + state *StateDB +} + +func newStateEnv() *stateEnv { + db := rawdb.NewMemoryDatabase() + sdb, _ := New(types.EmptyRootHash, NewDatabase(db), nil) + return &stateEnv{db: db, state: sdb} +} + +func TestDump(t *testing.T) { + db := rawdb.NewMemoryDatabase() + tdb := NewDatabaseWithConfig(db, &triedb.Config{Preimages: true}) + sdb, _ := New(types.EmptyRootHash, tdb, nil) + s := &stateEnv{db: db, state: sdb} + + // generate a few entries + obj1 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01})) + obj1.AddBalance(uint256.NewInt(22), tracing.BalanceChangeUnspecified) + obj2 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) + obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) + obj3 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x02})) + obj3.SetBalance(uint256.NewInt(44), tracing.BalanceChangeUnspecified) + + // write some of them to the trie + s.state.updateStateObject(obj1) + s.state.updateStateObject(obj2) + root, _ := s.state.Commit(0, false) + + // check that DumpToCollector contains the state objects that are in trie + s.state, _ = New(root, tdb, nil) + got := string(s.state.Dump(nil)) + want := `{ + "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2", + "accounts": { + "0x0000000000000000000000000000000000000001": { + "balance": "22", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "address": "0x0000000000000000000000000000000000000001", + "key": "0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d" + }, + "0x0000000000000000000000000000000000000002": { + "balance": "44", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "address": "0x0000000000000000000000000000000000000002", + "key": "0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62" + }, + "0x0000000000000000000000000000000000000102": { + "balance": "0", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3", + "code": "0x03030303030303", + "address": "0x0000000000000000000000000000000000000102", + "key": "0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1" + } + } +}` + if got != want { + t.Errorf("DumpToCollector mismatch:\ngot: %s\nwant: %s\n", got, want) + } +} + +func TestIterativeDump(t *testing.T) { + db := rawdb.NewMemoryDatabase() + tdb := NewDatabaseWithConfig(db, &triedb.Config{Preimages: true}) + sdb, _ := New(types.EmptyRootHash, tdb, nil) + s := &stateEnv{db: db, state: sdb} + + // generate a few entries + obj1 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01})) + obj1.AddBalance(uint256.NewInt(22), tracing.BalanceChangeUnspecified) + obj2 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) + obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) + obj3 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x02})) + obj3.SetBalance(uint256.NewInt(44), tracing.BalanceChangeUnspecified) + obj4 := s.state.getOrNewStateObject(common.BytesToAddress([]byte{0x00})) + obj4.AddBalance(uint256.NewInt(1337), tracing.BalanceChangeUnspecified) + + // write some of them to the trie + s.state.updateStateObject(obj1) + s.state.updateStateObject(obj2) + root, _ := s.state.Commit(0, false) + s.state, _ = New(root, tdb, nil) + + b := &bytes.Buffer{} + s.state.IterativeDump(nil, json.NewEncoder(b)) + // check that DumpToCollector contains the state objects that are in trie + got := b.String() + want := `{"root":"0xd5710ea8166b7b04bc2bfb129d7db12931cee82f75ca8e2d075b4884322bf3de"} +{"balance":"22","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","address":"0x0000000000000000000000000000000000000001","key":"0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d"} +{"balance":"1337","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","address":"0x0000000000000000000000000000000000000000","key":"0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a"} +{"balance":"0","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3","code":"0x03030303030303","address":"0x0000000000000000000000000000000000000102","key":"0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1"} +{"balance":"44","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","address":"0x0000000000000000000000000000000000000002","key":"0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62"} +` + if got != want { + t.Errorf("DumpToCollector mismatch:\ngot: %s\nwant: %s\n", got, want) + } +} + +func TestNull(t *testing.T) { + s := newStateEnv() + address := common.HexToAddress("0x823140710bf13990e4500136726d8b55") + s.state.CreateAccount(address) + //value := common.FromHex("0x823140710bf13990e4500136726d8b55") + var value common.Hash + + s.state.SetState(address, common.Hash{}, value) + s.state.Commit(0, false) + + if value := s.state.GetState(address, common.Hash{}); value != (common.Hash{}) { + t.Errorf("expected empty current value, got %x", value) + } + if value := s.state.GetCommittedState(address, common.Hash{}); value != (common.Hash{}) { + t.Errorf("expected empty committed value, got %x", value) + } +} + +func TestSnapshot(t *testing.T) { + stateobjaddr := common.BytesToAddress([]byte("aa")) + var storageaddr common.Hash + data1 := common.BytesToHash([]byte{42}) + data2 := common.BytesToHash([]byte{43}) + s := newStateEnv() + + // snapshot the genesis state + genesis := s.state.Snapshot() + + // set initial state object value + s.state.SetState(stateobjaddr, storageaddr, data1) + snapshot := s.state.Snapshot() + + // set a new state object value, revert it and ensure correct content + s.state.SetState(stateobjaddr, storageaddr, data2) + s.state.RevertToSnapshot(snapshot) + + if v := s.state.GetState(stateobjaddr, storageaddr); v != data1 { + t.Errorf("wrong storage value %v, want %v", v, data1) + } + if v := s.state.GetCommittedState(stateobjaddr, storageaddr); v != (common.Hash{}) { + t.Errorf("wrong committed storage value %v, want %v", v, common.Hash{}) + } + + // revert up to the genesis state and ensure correct content + s.state.RevertToSnapshot(genesis) + if v := s.state.GetState(stateobjaddr, storageaddr); v != (common.Hash{}) { + t.Errorf("wrong storage value %v, want %v", v, common.Hash{}) + } + if v := s.state.GetCommittedState(stateobjaddr, storageaddr); v != (common.Hash{}) { + t.Errorf("wrong committed storage value %v, want %v", v, common.Hash{}) + } +} + +func TestSnapshotEmpty(t *testing.T) { + s := newStateEnv() + s.state.RevertToSnapshot(s.state.Snapshot()) +} + +func TestCreateObjectRevert(t *testing.T) { + state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) + addr := common.BytesToAddress([]byte("so0")) + snap := state.Snapshot() + + state.CreateAccount(addr) + so0 := state.getStateObject(addr) + so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified) + so0.SetNonce(43) + so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) + state.setStateObject(so0) + + state.RevertToSnapshot(snap) + if state.Exist(addr) { + t.Error("Unexpected account after revert") + } +} diff --git a/core/state/statedb.go b/core/state/statedb.go new file mode 100644 index 0000000..ac82a8e --- /dev/null +++ b/core/state/statedb.go @@ -0,0 +1,1479 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package state provides a caching layer atop the Ethereum state trie. +package state + +import ( + "errors" + "fmt" + "maps" + "math/big" + "slices" + "sort" + "sync" + "sync/atomic" + "time" + + "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/stateless" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/holiman/uint256" + "golang.org/x/sync/errgroup" +) + +// TriesInMemory represents the number of layers that are kept in RAM. +const TriesInMemory = 128 + +type revision struct { + id int + journalIndex int +} + +type mutationType int + +const ( + update mutationType = iota + deletion +) + +type mutation struct { + typ mutationType + applied bool +} + +func (m *mutation) copy() *mutation { + return &mutation{typ: m.typ, applied: m.applied} +} + +func (m *mutation) isDelete() bool { + return m.typ == deletion +} + +// StateDB structs within the ethereum protocol are used to store anything +// within the merkle trie. StateDBs take care of caching and storing +// nested states. It's the general query interface to retrieve: +// +// * Contracts +// * Accounts +// +// Once the state is committed, tries cached in stateDB (including account +// trie, storage tries) will no longer be functional. A new state instance +// must be created with new root and updated database for accessing post- +// commit states. +type StateDB struct { + db Database + prefetcher *triePrefetcher + trie Trie + hasher crypto.KeccakState + logger *tracing.Hooks + snaps *snapshot.Tree // Nil if snapshot is not available + snap snapshot.Snapshot // Nil if snapshot is not available + + // originalRoot is the pre-state root, before any changes were made. + // It will be updated when the Commit is called. + originalRoot common.Hash + + // This map holds 'live' objects, which will get modified while + // processing a state transition. + stateObjects map[common.Address]*stateObject + + // This map holds 'deleted' objects. An object with the same address + // might also occur in the 'stateObjects' map due to account + // resurrection. The account value is tracked as the original value + // before the transition. This map is populated at the transaction + // boundaries. + stateObjectsDestruct map[common.Address]*stateObject + + // This map tracks the account mutations that occurred during the + // transition. Uncommitted mutations belonging to the same account + // can be merged into a single one which is equivalent from database's + // perspective. This map is populated at the transaction boundaries. + mutations map[common.Address]*mutation + + // DB error. + // State objects are used by the consensus core and VM which are + // unable to deal with database-level errors. Any error that occurs + // during a database read is memoized here and will eventually be + // returned by StateDB.Commit. Notably, this error is also shared + // by all cached state objects in case the database failure occurs + // when accessing state of accounts. + dbErr error + + // The refund counter, also used by state transitioning. + refund uint64 + + // The tx context and all occurred logs in the scope of transaction. + thash common.Hash + txIndex int + logs map[common.Hash][]*types.Log + logSize uint + + // Preimages occurred seen by VM in the scope of block. + preimages map[common.Hash][]byte + + // Per-transaction access list + accessList *accessList + + // Transient storage + transientStorage transientStorage + + // Journal of state modifications. This is the backbone of + // Snapshot and RevertToSnapshot. + journal *journal + validRevisions []revision + nextRevisionId int + + // State witness if cross validation is needed + witness *stateless.Witness + + // Measurements gathered during execution for debugging purposes + AccountReads time.Duration + AccountHashes time.Duration + AccountUpdates time.Duration + AccountCommits time.Duration + StorageReads time.Duration + StorageUpdates time.Duration + StorageCommits time.Duration + SnapshotAccountReads time.Duration + SnapshotStorageReads time.Duration + SnapshotCommits time.Duration + TrieDBCommits time.Duration + + AccountUpdated int + StorageUpdated atomic.Int64 + AccountDeleted int + StorageDeleted atomic.Int64 +} + +// New creates a new state from a given trie. +func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) { + tr, err := db.OpenTrie(root) + if err != nil { + return nil, err + } + sdb := &StateDB{ + db: db, + trie: tr, + originalRoot: root, + snaps: snaps, + stateObjects: make(map[common.Address]*stateObject), + stateObjectsDestruct: make(map[common.Address]*stateObject), + mutations: make(map[common.Address]*mutation), + logs: make(map[common.Hash][]*types.Log), + preimages: make(map[common.Hash][]byte), + journal: newJournal(), + accessList: newAccessList(), + transientStorage: newTransientStorage(), + hasher: crypto.NewKeccakState(), + } + if sdb.snaps != nil { + sdb.snap = sdb.snaps.Snapshot(root) + } + return sdb, nil +} + +// SetLogger sets the logger for account update hooks. +func (s *StateDB) SetLogger(l *tracing.Hooks) { + s.logger = l +} + +// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the +// state trie concurrently while the state is mutated so that when we reach the +// commit phase, most of the needed data is already hot. +func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) { + // Terminate any previously running prefetcher + if s.prefetcher != nil { + s.prefetcher.terminate(false) + s.prefetcher.report() + s.prefetcher = nil + } + // Enable witness collection if requested + s.witness = witness + + // If snapshots are enabled, start prefethers explicitly + if s.snap != nil { + s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, witness == nil) + + // With the switch to the Proof-of-Stake consensus algorithm, block production + // rewards are now handled at the consensus layer. Consequently, a block may + // have no state transitions if it contains no transactions and no withdrawals. + // In such cases, the account trie won't be scheduled for prefetching, leading + // to unnecessary error logs. + // + // To prevent this, the account trie is always scheduled for prefetching once + // the prefetcher is constructed. For more details, see: + // https://github.com/ethereum/go-ethereum/issues/29880 + if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, nil, false); err != nil { + log.Error("Failed to prefetch account trie", "root", s.originalRoot, "err", err) + } + } +} + +// StopPrefetcher terminates a running prefetcher and reports any leftover stats +// from the gathered metrics. +func (s *StateDB) StopPrefetcher() { + if s.prefetcher != nil { + s.prefetcher.terminate(false) + s.prefetcher.report() + s.prefetcher = nil + } +} + +// setError remembers the first non-nil error it is called with. +func (s *StateDB) setError(err error) { + if s.dbErr == nil { + s.dbErr = err + } +} + +// Error returns the memorized database failure occurred earlier. +func (s *StateDB) Error() error { + return s.dbErr +} + +func (s *StateDB) AddLog(log *types.Log) { + s.journal.append(addLogChange{txhash: s.thash}) + + log.TxHash = s.thash + log.TxIndex = uint(s.txIndex) + log.Index = s.logSize + if s.logger != nil && s.logger.OnLog != nil { + s.logger.OnLog(log) + } + s.logs[s.thash] = append(s.logs[s.thash], log) + s.logSize++ +} + +// GetLogs returns the logs matching the specified transaction hash, and annotates +// them with the given blockNumber and blockHash. +func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log { + logs := s.logs[hash] + for _, l := range logs { + l.BlockNumber = blockNumber + l.BlockHash = blockHash + } + return logs +} + +func (s *StateDB) Logs() []*types.Log { + var logs []*types.Log + for _, lgs := range s.logs { + logs = append(logs, lgs...) + } + return logs +} + +// AddPreimage records a SHA3 preimage seen by the VM. +func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) { + if _, ok := s.preimages[hash]; !ok { + s.journal.append(addPreimageChange{hash: hash}) + s.preimages[hash] = slices.Clone(preimage) + } +} + +// Preimages returns a list of SHA3 preimages that have been submitted. +func (s *StateDB) Preimages() map[common.Hash][]byte { + return s.preimages +} + +// AddRefund adds gas to the refund counter +func (s *StateDB) AddRefund(gas uint64) { + s.journal.append(refundChange{prev: s.refund}) + s.refund += gas +} + +// SubRefund removes gas from the refund counter. +// This method will panic if the refund counter goes below zero +func (s *StateDB) SubRefund(gas uint64) { + s.journal.append(refundChange{prev: s.refund}) + if gas > s.refund { + panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund)) + } + s.refund -= gas +} + +// Exist reports whether the given account address exists in the state. +// Notably this also returns true for self-destructed accounts. +func (s *StateDB) Exist(addr common.Address) bool { + return s.getStateObject(addr) != nil +} + +// Empty returns whether the state object is either non-existent +// or empty according to the EIP161 specification (balance = nonce = code = 0) +func (s *StateDB) Empty(addr common.Address) bool { + so := s.getStateObject(addr) + return so == nil || so.empty() +} + +// GetBalance retrieves the balance from the given address or 0 if object not found +func (s *StateDB) GetBalance(addr common.Address) *uint256.Int { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.Balance() + } + return common.U2560 +} + +// 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 { + return stateObject.Nonce() + } + + 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 SetTxContext. +func (s *StateDB) TxIndex() int { + return s.txIndex +} + +func (s *StateDB) GetCode(addr common.Address) []byte { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.Code() + } + return nil +} + +func (s *StateDB) GetCodeSize(addr common.Address) int { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.CodeSize() + } + return 0 +} + +func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return common.BytesToHash(stateObject.CodeHash()) + } + return common.Hash{} +} + +// GetState retrieves the value associated with the specific key. +func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.GetState(hash) + } + return common.Hash{} +} + +// GetCommittedState retrieves the value associated with the specific key +// without any mutations caused in the current execution. +func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.GetCommittedState(hash) + } + return common.Hash{} +} + +// Database retrieves the low level database supporting the lower level trie ops. +func (s *StateDB) Database() Database { + return s.db +} + +func (s *StateDB) HasSelfDestructed(addr common.Address) bool { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.selfDestructed + } + return false +} + +/* + * SETTERS + */ + +// AddBalance adds amount to the account associated with addr. +func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) { + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.AddBalance(amount, reason) + } +} + +// SubBalance subtracts amount from the account associated with addr. +func (s *StateDB) SubBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) { + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SubBalance(amount, reason) + } +} + +func (s *StateDB) SetBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) { + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetBalance(amount, reason) + } +} + +func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetNonce(nonce) + } +} + +func (s *StateDB) SetCode(addr common.Address, code []byte) { + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetCode(crypto.Keccak256Hash(code), code) + } +} + +func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetState(key, value) + } +} + +// SetStorage replaces the entire storage for the specified account with given +// storage. This function should only be used for debugging and the mutations +// must be discarded afterwards. +func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) { + // SetStorage needs to wipe existing storage. We achieve this by pretending + // that the account self-destructed earlier in this block, by flagging + // it in stateObjectsDestruct. The effect of doing so is that storage lookups + // will not hit disk, since it is assumed that the disk-data is belonging + // to a previous incarnation of the object. + // + // TODO(rjl493456442) this function should only be supported by 'unwritable' + // state and all mutations made should all be discarded afterwards. + if _, ok := s.stateObjectsDestruct[addr]; !ok { + s.stateObjectsDestruct[addr] = nil + } + stateObject := s.getOrNewStateObject(addr) + for k, v := range storage { + stateObject.SetState(k, v) + } +} + +// SelfDestruct marks the given account as selfdestructed. +// This clears the account balance. +// +// The account's state object is still available until the state is committed, +// getStateObject will return a non-nil account after SelfDestruct. +func (s *StateDB) SelfDestruct(addr common.Address) { + stateObject := s.getStateObject(addr) + if stateObject == nil { + return + } + var ( + prev = new(uint256.Int).Set(stateObject.Balance()) + n = new(uint256.Int) + ) + s.journal.append(selfDestructChange{ + account: &addr, + prev: stateObject.selfDestructed, + prevbalance: prev, + }) + if s.logger != nil && s.logger.OnBalanceChange != nil && prev.Sign() > 0 { + s.logger.OnBalanceChange(addr, prev.ToBig(), n.ToBig(), tracing.BalanceDecreaseSelfdestruct) + } + stateObject.markSelfdestructed() + stateObject.data.Balance = n +} + +func (s *StateDB) Selfdestruct6780(addr common.Address) { + stateObject := s.getStateObject(addr) + if stateObject == nil { + return + } + if stateObject.newContract { + s.SelfDestruct(addr) + } +} + +// SetTransientState sets transient storage for a given account. It +// adds the change to the journal so that it can be rolled back +// to its previous value if there is a revert. +func (s *StateDB) SetTransientState(addr common.Address, key, value common.Hash) { + prev := s.GetTransientState(addr, key) + if prev == value { + return + } + s.journal.append(transientStorageChange{ + account: &addr, + key: key, + prevalue: prev, + }) + s.setTransientState(addr, key, value) +} + +// setTransientState is a lower level setter for transient storage. It +// is called during a revert to prevent modifications to the journal. +func (s *StateDB) setTransientState(addr common.Address, key, value common.Hash) { + s.transientStorage.Set(addr, key, value) +} + +// GetTransientState gets transient storage for a given account. +func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash { + return s.transientStorage.Get(addr, key) +} + +// +// Setting, updating & deleting state object methods. +// + +// updateStateObject writes the given object to the trie. +func (s *StateDB) updateStateObject(obj *stateObject) { + // Encode the account and update the account trie + addr := obj.Address() + if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { + s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) + } + if obj.dirtyCode { + s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) + } +} + +// deleteStateObject removes the given object from the state trie. +func (s *StateDB) deleteStateObject(addr common.Address) { + if err := s.trie.DeleteAccount(addr); err != nil { + s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) + } +} + +// getStateObject retrieves a state object given by the address, returning nil if +// the object is not found or was deleted in this execution context. +func (s *StateDB) getStateObject(addr common.Address) *stateObject { + // Prefer live objects if any is available + if obj := s.stateObjects[addr]; obj != nil { + return obj + } + // Short circuit if the account is already destructed in this block. + if _, ok := s.stateObjectsDestruct[addr]; ok { + return nil + } + // If no live objects are available, attempt to use snapshots + var data *types.StateAccount + if s.snap != nil { + start := time.Now() + acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())) + s.SnapshotAccountReads += time.Since(start) + if err == nil { + if acc == nil { + return nil + } + data = &types.StateAccount{ + Nonce: acc.Nonce, + Balance: acc.Balance, + CodeHash: acc.CodeHash, + Root: common.BytesToHash(acc.Root), + } + if len(data.CodeHash) == 0 { + data.CodeHash = types.EmptyCodeHash.Bytes() + } + if data.Root == (common.Hash{}) { + data.Root = types.EmptyRootHash + } + } + } + // If snapshot unavailable or reading from it failed, load from the database + if data == nil { + start := time.Now() + var err error + data, err = s.trie.GetAccount(addr) + s.AccountReads += time.Since(start) + + if err != nil { + s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) + return nil + } + if data == nil { + return nil + } + } + // Independent of where we loaded the data from, add it to the prefetcher. + // Whilst this would be a bit weird if snapshots are disabled, but we still + // want the trie nodes to end up in the prefetcher too, so just push through. + if s.prefetcher != nil { + if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, [][]byte{addr[:]}, true); err != nil { + log.Error("Failed to prefetch account", "addr", addr, "err", err) + } + } + // Insert into the live set + obj := newObject(s, addr, data) + s.setStateObject(obj) + return obj +} + +func (s *StateDB) setStateObject(object *stateObject) { + s.stateObjects[object.Address()] = object +} + +// getOrNewStateObject retrieves a state object or create a new state object if nil. +func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { + obj := s.getStateObject(addr) + if obj == nil { + obj = s.createObject(addr) + } + return obj +} + +// createObject creates a new state object. The assumption is held there is no +// existing account with the given address, otherwise it will be silently overwritten. +func (s *StateDB) createObject(addr common.Address) *stateObject { + obj := newObject(s, addr, nil) + s.journal.append(createObjectChange{account: &addr}) + s.setStateObject(obj) + return obj +} + +// CreateAccount explicitly creates a new state object, assuming that the +// account did not previously exist in the state. If the account already +// exists, this function will silently overwrite it which might lead to a +// consensus bug eventually. +func (s *StateDB) CreateAccount(addr common.Address) { + s.createObject(addr) +} + +// CreateContract is used whenever a contract is created. This may be preceded +// by CreateAccount, but that is not required if it already existed in the +// state due to funds sent beforehand. +// This operation sets the 'newContract'-flag, which is required in order to +// correctly handle EIP-6780 'delete-in-same-transaction' logic. +func (s *StateDB) CreateContract(addr common.Address) { + obj := s.getStateObject(addr) + if !obj.newContract { + obj.newContract = true + s.journal.append(createContractChange{account: addr}) + } +} + +// 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 { + // Copy all the basic fields, initialize the memory ones + state := &StateDB{ + db: s.db, + trie: s.db.CopyTrie(s.trie), + hasher: crypto.NewKeccakState(), + originalRoot: s.originalRoot, + stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)), + stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)), + mutations: make(map[common.Address]*mutation, len(s.mutations)), + dbErr: s.dbErr, + refund: s.refund, + thash: s.thash, + txIndex: s.txIndex, + logs: make(map[common.Hash][]*types.Log, len(s.logs)), + logSize: s.logSize, + preimages: maps.Clone(s.preimages), + journal: s.journal.copy(), + validRevisions: slices.Clone(s.validRevisions), + nextRevisionId: s.nextRevisionId, + + // In order for the block producer to be able to use and make additions + // to the snapshot tree, we need to copy that as well. Otherwise, any + // block mined by ourselves will cause gaps in the tree, and force the + // miner to operate trie-backed only. + snaps: s.snaps, + snap: s.snap, + } + if s.witness != nil { + state.witness = s.witness.Copy() + } + // Deep copy cached state objects. + for addr, obj := range s.stateObjects { + state.stateObjects[addr] = obj.deepCopy(state) + } + // Deep copy destructed state objects. + for addr, obj := range s.stateObjectsDestruct { + state.stateObjectsDestruct[addr] = obj.deepCopy(state) + } + // Deep copy the object state markers. + for addr, op := range s.mutations { + state.mutations[addr] = op.copy() + } + // Deep copy the logs occurred in the scope of block + for hash, logs := range s.logs { + cpy := make([]*types.Log, len(logs)) + for i, l := range logs { + cpy[i] = new(types.Log) + *cpy[i] = *l + } + state.logs[hash] = cpy + } + // Do we need to copy the access list and transient storage? + // In practice: No. At the start of a transaction, these two lists are empty. + // In practice, we only ever copy state _between_ transactions/blocks, never + // in the middle of a transaction. However, it doesn't cost us much to copy + // empty lists, so we do it anyway to not blow up if we ever decide copy them + // in the middle of a transaction. + state.accessList = s.accessList.Copy() + state.transientStorage = s.transientStorage.Copy() + return state +} + +// Snapshot returns an identifier for the current revision of the state. +func (s *StateDB) Snapshot() int { + id := s.nextRevisionId + s.nextRevisionId++ + s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()}) + return id +} + +// RevertToSnapshot reverts all state changes made since the given revision. +func (s *StateDB) RevertToSnapshot(revid int) { + // Find the snapshot in the stack of valid snapshots. + idx := sort.Search(len(s.validRevisions), func(i int) bool { + return s.validRevisions[i].id >= revid + }) + if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid { + panic(fmt.Errorf("revision id %v cannot be reverted", revid)) + } + snapshot := s.validRevisions[idx].journalIndex + + // Replay the journal to undo changes and remove invalidated snapshots + s.journal.revert(s, snapshot) + s.validRevisions = s.validRevisions[:idx] +} + +// GetRefund returns the current value of the refund counter. +func (s *StateDB) GetRefund() uint64 { + return s.refund +} + +// Finalise finalises the state by removing the destructed objects and clears +// the journal as well as the refunds. Finalise, however, will not push any updates +// into the tries just yet. Only IntermediateRoot or Commit will do that. +func (s *StateDB) Finalise(deleteEmptyObjects bool) { + addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) + for addr := range s.journal.dirties { + obj, exist := s.stateObjects[addr] + if !exist { + // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 + // That tx goes out of gas, and although the notion of 'touched' does not exist there, the + // touch-event will still be recorded in the journal. Since ripeMD is a special snowflake, + // it will persist in the journal even though the journal is reverted. In this special circumstance, + // it may exist in `s.journal.dirties` but not in `s.stateObjects`. + // Thus, we can safely ignore it here + continue + } + if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { + delete(s.stateObjects, obj.address) + s.markDelete(addr) + + // If ether was sent to account post-selfdestruct it is burnt. + if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 { + s.logger.OnBalanceChange(obj.address, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) + } + // We need to maintain account deletions explicitly (will remain + // set indefinitely). Note only the first occurred self-destruct + // event is tracked. + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + s.stateObjectsDestruct[obj.address] = obj + } + } else { + obj.finalise() + s.markUpdate(addr) + } + // At this point, also ship the address off to the precacher. The precacher + // will start loading tries, and when the change is eventually committed, + // the commit-phase will be a lot faster + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure + } + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { + if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch, false); err != nil { + log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err) + } + } + // Invalidate journal because reverting across transactions is not allowed. + s.clearJournalAndRefund() +} + +// IntermediateRoot computes the current root hash of the state trie. +// It is called in between transactions to get the root hash that +// goes into transaction receipts. +func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { + // Finalise all the dirty storage states and write them into the tries + s.Finalise(deleteEmptyObjects) + + // If there was a trie prefetcher operating, terminate it async so that the + // individual storage tries can be updated as soon as the disk load finishes. + if s.prefetcher != nil { + s.prefetcher.terminate(true) + defer func() { + s.prefetcher.report() + s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher + }() + } + // Process all storage updates concurrently. The state object update root + // method will internally call a blocking trie fetch from the prefetcher, + // so there's no need to explicitly wait for the prefetchers to finish. + var ( + start = time.Now() + workers errgroup.Group + ) + if s.db.TrieDB().IsVerkle() { + // Whilst MPT storage tries are independent, Verkle has one single trie + // for all the accounts and all the storage slots merged together. The + // former can thus be simply parallelized, but updating the latter will + // need concurrency support within the trie itself. That's a TODO for a + // later time. + workers.SetLimit(1) + } + for addr, op := range s.mutations { + if op.applied || op.isDelete() { + continue + } + obj := s.stateObjects[addr] // closure for the task runner below + workers.Go(func() error { + obj.updateRoot() + + // If witness building is enabled and the state object has a trie, + // gather the witnesses for its specific storage trie + if s.witness != nil && obj.trie != nil { + s.witness.AddState(obj.trie.Witness()) + } + return nil + }) + } + // If witness building is enabled, gather all the read-only accesses + if s.witness != nil { + // Pull in anything that has been accessed before destruction + for _, obj := range s.stateObjectsDestruct { + // Skip any objects that haven't touched their storage + if len(obj.originStorage) == 0 { + continue + } + if trie := obj.getPrefetchedTrie(); trie != nil { + s.witness.AddState(trie.Witness()) + } else if obj.trie != nil { + s.witness.AddState(obj.trie.Witness()) + } + } + // Pull in only-read and non-destructed trie witnesses + for _, obj := range s.stateObjects { + // Skip any objects that have been updated + if _, ok := s.mutations[obj.address]; ok { + continue + } + // Skip any objects that haven't touched their storage + if len(obj.originStorage) == 0 { + continue + } + if trie := obj.getPrefetchedTrie(); trie != nil { + s.witness.AddState(trie.Witness()) + } else if obj.trie != nil { + s.witness.AddState(obj.trie.Witness()) + } + } + } + workers.Wait() + s.StorageUpdates += time.Since(start) + + // Now we're about to start to write changes to the trie. The trie is so far + // _untouched_. We can check with the prefetcher, if it can give us a trie + // which has the same root, but also has some content loaded into it. + start = time.Now() + + if s.prefetcher != nil { + if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil { + log.Error("Failed to retrieve account pre-fetcher trie") + } else { + s.trie = trie + } + } + // Perform updates before deletions. This prevents resolution of unnecessary trie nodes + // in circumstances similar to the following: + // + // Consider nodes `A` and `B` who share the same full node parent `P` and have no other siblings. + // During the execution of a block: + // - `A` self-destructs, + // - `C` is created, and also shares the parent `P`. + // If the self-destruct is handled first, then `P` would be left with only one child, thus collapsed + // into a shortnode. This requires `B` to be resolved from disk. + // Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved. + var ( + usedAddrs [][]byte + deletedAddrs []common.Address + ) + for addr, op := range s.mutations { + if op.applied { + continue + } + op.applied = true + + if op.isDelete() { + deletedAddrs = append(deletedAddrs, addr) + } else { + s.updateStateObject(s.stateObjects[addr]) + s.AccountUpdated += 1 + } + usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure + } + for _, deletedAddr := range deletedAddrs { + s.deleteStateObject(deletedAddr) + s.AccountDeleted += 1 + } + s.AccountUpdates += time.Since(start) + + if s.prefetcher != nil { + s.prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs) + } + // Track the amount of time wasted on hashing the account trie + defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) + + hash := s.trie.Hash() + + // If witness building is enabled, gather the account trie witness + if s.witness != nil { + s.witness.AddState(s.trie.Witness()) + } + return hash +} + +// SetTxContext sets the current transaction hash and index which are +// used when the EVM emits new state logs. It should be invoked before +// transaction execution. +func (s *StateDB) SetTxContext(thash common.Hash, ti int) { + s.thash = thash + s.txIndex = ti +} + +func (s *StateDB) clearJournalAndRefund() { + if len(s.journal.entries) > 0 { + s.journal = newJournal() + s.refund = 0 + } + s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entries +} + +// fastDeleteStorage is the function that efficiently deletes the storage trie +// of a specific account. It leverages the associated state snapshot for fast +// storage iteration and constructs trie node deletion markers by creating +// stack trie with iterated slots. +func (s *StateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) { + iter, err := s.snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{}) + if err != nil { + return 0, nil, nil, err + } + defer iter.Release() + + var ( + size common.StorageSize + nodes = trienode.NewNodeSet(addrHash) + slots = make(map[common.Hash][]byte) + ) + stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + nodes.AddNode(path, trienode.NewDeleted()) + size += common.StorageSize(len(path)) + }) + for iter.Next() { + slot := common.CopyBytes(iter.Slot()) + if err := iter.Error(); err != nil { // error might occur after Slot function + return 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 0, nil, nil, err + } + } + if err := iter.Error(); err != nil { // error might occur during iteration + return 0, nil, nil, err + } + if stack.Hash() != root { + return 0, nil, nil, fmt.Errorf("snapshot is not matched, exp %x, got %x", root, stack.Hash()) + } + return 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) (common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) { + tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie) + if err != nil { + return 0, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err) + } + it, err := tr.NodeIterator(nil) + if err != nil { + return 0, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err) + } + var ( + size common.StorageSize + nodes = trienode.NewNodeSet(addrHash) + slots = make(map[common.Hash][]byte) + ) + for it.Next(true) { + if it.Leaf() { + slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob()) + size += common.StorageSize(common.HashLength + len(it.LeafBlob())) + continue + } + if it.Hash() == (common.Hash{}) { + continue + } + size += common.StorageSize(len(it.Path())) + nodes.AddNode(it.Path(), trienode.NewDeleted()) + } + if err := it.Error(); err != nil { + return 0, nil, nil, err + } + return size, slots, nodes, nil +} + +// deleteStorage is designed to delete the storage trie of a designated account. +// The function will make an attempt to utilize an efficient strategy if the +// associated state snapshot is reachable; otherwise, it will resort to a less +// efficient approach. +func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, *trienode.NodeSet, error) { + var ( + start = time.Now() + err error + 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 { + size, slots, nodes, err = s.fastDeleteStorage(addrHash, root) + } + if s.snap == nil || err != nil { + size, slots, nodes, err = s.slowDeleteStorage(addr, addrHash, root) + } + if err != nil { + return nil, nil, err + } + // Report the metrics + n := int64(len(slots)) + + slotDeletionMaxCount.UpdateIfGt(int64(len(slots))) + slotDeletionMaxSize.UpdateIfGt(int64(size)) + + slotDeletionTimer.UpdateSince(start) + slotDeletionCount.Mark(n) + slotDeletionSize.Mark(int64(size)) + + return slots, nodes, nil +} + +// handleDestruction processes all destruction markers and deletes the account +// and associated storage slots if necessary. There are four potential scenarios +// as following: +// +// (a) the account was not existent and be marked as destructed +// (b) the account was not existent and be marked as destructed, +// however, it's resurrected later in the same block. +// (c) the account was existent and be marked as destructed +// (d) the account was existent and be marked as destructed, +// however it's resurrected later in the same block. +// +// In case (a), nothing needs be deleted, nil to nil transition can be ignored. +// In case (b), nothing needs be deleted, nil is used as the original value for +// newly created account and storages +// In case (c), **original** account along with its storages should be deleted, +// with their values be tracked as original value. +// In case (d), **original** account along with its storages should be deleted, +// with their values be tracked as original value. +func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) { + var ( + nodes []*trienode.NodeSet + buf = crypto.NewKeccakState() + deletes = make(map[common.Hash]*accountDelete) + ) + for addr, prevObj := range s.stateObjectsDestruct { + prev := prevObj.origin + + // The account was non-existent, and it's marked as destructed in the scope + // of block. It can be either case (a) or (b) and will be interpreted as + // null->null state transition. + // - for (a), skip it without doing anything + // - for (b), the resurrected account with nil as original will be handled afterwards + if prev == nil { + continue + } + // The account was existent, it can be either case (c) or (d). + addrHash := crypto.HashData(buf, addr.Bytes()) + op := &accountDelete{ + address: addr, + origin: types.SlimAccountRLP(*prev), + } + deletes[addrHash] = op + + // Short circuit if the origin storage was empty. + if prev.Root == types.EmptyRootHash { + continue + } + // Remove storage slots belonging to the account. + slots, set, err := s.deleteStorage(addr, addrHash, prev.Root) + if err != nil { + return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err) + } + op.storagesOrigin = slots + + // Aggregate the associated trie node changes. + nodes = append(nodes, set) + } + return deletes, nodes, nil +} + +// GetTrie returns the account trie. +func (s *StateDB) GetTrie() Trie { + return s.trie +} + +// commit gathers the state mutations accumulated along with the associated +// trie changes, resetting all internal flags with the new state as the base. +func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) { + // Short circuit in case any database failure occurred earlier. + if s.dbErr != nil { + return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) + } + // Finalize any pending changes and merge everything into the tries + s.IntermediateRoot(deleteEmptyObjects) + + // Commit objects to the trie, measuring the elapsed time + var ( + accountTrieNodesUpdated int + accountTrieNodesDeleted int + storageTrieNodesUpdated int + storageTrieNodesDeleted int + + lock sync.Mutex // protect two maps below + nodes = trienode.NewMergedNodeSet() // aggregated trie nodes + updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates + + // merge aggregates the dirty trie nodes into the global set. + // + // Given that some accounts may be destroyed and then recreated within + // the same block, it's possible that a node set with the same owner + // may already exists. In such cases, these two sets are combined, with + // the later one overwriting the previous one if any nodes are modified + // or deleted in both sets. + // + // merge run concurrently across all the state objects and account trie. + merge = func(set *trienode.NodeSet) error { + if set == nil { + return nil + } + lock.Lock() + defer lock.Unlock() + + updates, deletes := set.Size() + if set.Owner == (common.Hash{}) { + accountTrieNodesUpdated += updates + accountTrieNodesDeleted += deletes + } else { + storageTrieNodesUpdated += updates + storageTrieNodesDeleted += deletes + } + return nodes.Merge(set) + } + ) + // Given that some accounts could be destroyed and then recreated within + // the same block, account deletions must be processed first. This ensures + // that the storage trie nodes deleted during destruction and recreated + // during subsequent resurrection can be combined correctly. + deletes, delNodes, err := s.handleDestruction() + if err != nil { + return nil, err + } + for _, set := range delNodes { + if err := merge(set); err != nil { + return nil, err + } + } + // Handle all state updates afterwards, concurrently to one another to shave + // off some milliseconds from the commit operation. Also accumulate the code + // writes to run in parallel with the computations. + var ( + start = time.Now() + root common.Hash + workers errgroup.Group + ) + // Schedule the account trie first since that will be the biggest, so give + // it the most time to crunch. + // + // TODO(karalabe): This account trie commit is *very* heavy. 5-6ms at chain + // heads, which seems excessive given that it doesn't do hashing, it just + // shuffles some data. For comparison, the *hashing* at chain head is 2-3ms. + // We need to investigate what's happening as it seems something's wonky. + // Obviously it's not an end of the world issue, just something the original + // code didn't anticipate for. + workers.Go(func() error { + // Write the account trie changes, measuring the amount of wasted time + newroot, set := s.trie.Commit(true) + root = newroot + + if err := merge(set); err != nil { + return err + } + s.AccountCommits = time.Since(start) + return nil + }) + // Schedule each of the storage tries that need to be updated, so they can + // run concurrently to one another. + // + // TODO(karalabe): Experimentally, the account commit takes approximately the + // same time as all the storage commits combined, so we could maybe only have + // 2 threads in total. But that kind of depends on the account commit being + // more expensive than it should be, so let's fix that and revisit this todo. + for addr, op := range s.mutations { + if op.isDelete() { + continue + } + // Write any contract code associated with the state object + obj := s.stateObjects[addr] + if obj == nil { + return nil, errors.New("missing state object") + } + // Run the storage updates concurrently to one another + workers.Go(func() error { + // Write any storage changes in the state object to its storage trie + update, set, err := obj.commit() + if err != nil { + return err + } + if err := merge(set); err != nil { + return err + } + lock.Lock() + updates[obj.addrHash] = update + s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime + lock.Unlock() + return nil + }) + } + // Wait for everything to finish and update the metrics + if err := workers.Wait(); err != nil { + return nil, err + } + accountUpdatedMeter.Mark(int64(s.AccountUpdated)) + storageUpdatedMeter.Mark(s.StorageUpdated.Load()) + accountDeletedMeter.Mark(int64(s.AccountDeleted)) + storageDeletedMeter.Mark(s.StorageDeleted.Load()) + accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated)) + accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted)) + storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated)) + storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted)) + s.AccountUpdated, s.AccountDeleted = 0, 0 + s.StorageUpdated.Store(0) + s.StorageDeleted.Store(0) + + // Clear all internal flags and update state root at the end. + s.mutations = make(map[common.Address]*mutation) + s.stateObjectsDestruct = make(map[common.Address]*stateObject) + + origin := s.originalRoot + s.originalRoot = root + return newStateUpdate(origin, root, deletes, updates, nodes), nil +} + +// commitAndFlush is a wrapper of commit which also commits the state mutations +// to the configured data stores. +func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateUpdate, error) { + ret, err := s.commit(deleteEmptyObjects) + if err != nil { + return nil, err + } + // Commit dirty contract code if any exists + if db := s.db.DiskDB(); db != nil && len(ret.codes) > 0 { + batch := db.NewBatch() + for _, code := range ret.codes { + rawdb.WriteCode(batch, code.hash, code.blob) + } + if err := batch.Write(); err != nil { + return nil, err + } + } + if !ret.empty() { + // If snapshotting is enabled, update the snapshot tree with this new version + if s.snap != nil { + s.snap = nil + + start := time.Now() + if err := s.snaps.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil { + log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err) + } + // Keep 128 diff layers in the memory, persistent layer is 129th. + // - head layer is paired with HEAD state + // - head-1 layer is paired with HEAD-1 state + // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state + if err := s.snaps.Cap(ret.root, TriesInMemory); err != nil { + log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err) + } + s.SnapshotCommits += time.Since(start) + } + // If trie database is enabled, commit the state update as a new layer + if db := s.db.TrieDB(); db != nil { + start := time.Now() + set := triestate.New(ret.accountsOrigin, ret.storagesOrigin) + if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, set); err != nil { + return nil, err + } + s.TrieDBCommits += time.Since(start) + } + } + return ret, err +} + +// Commit writes the state mutations into the configured data stores. +// +// Once the state is committed, tries cached in stateDB (including account +// trie, storage tries) will no longer be functional. A new state instance +// must be created with new root and updated database for accessing post- +// commit states. +// +// The associated block number of the state transition is also provided +// for more chain context. +func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) { + ret, err := s.commitAndFlush(block, deleteEmptyObjects) + if err != nil { + return common.Hash{}, err + } + return ret.root, nil +} + +// Prepare handles the preparatory steps for executing a state transition with. +// This method must be invoked before state transition. +// +// Berlin fork: +// - Add sender to access list (2929) +// - Add destination to access list (2929) +// - Add precompiles to access list (2929) +// - Add the contents of the optional tx access list (2930) +// +// Potential EIPs: +// - Reset access list (Berlin) +// - Add coinbase to access list (EIP-3651) +// - Reset transient storage (EIP-1153) +func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + if rules.IsEIP2929 && rules.IsEIP4762 { + panic("eip2929 and eip4762 are both activated") + } + if rules.IsEIP2929 { + // Clear out any leftover from previous executions + al := newAccessList() + s.accessList = al + + al.AddAddress(sender) + if dst != nil { + al.AddAddress(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range precompiles { + al.AddAddress(addr) + } + for _, el := range list { + al.AddAddress(el.Address) + for _, key := range el.StorageKeys { + al.AddSlot(el.Address, key) + } + } + if rules.IsShanghai { // EIP-3651: warm coinbase + al.AddAddress(coinbase) + } + } + // Reset transient storage at the beginning of transaction execution + s.transientStorage = newTransientStorage() +} + +// AddAddressToAccessList adds the given address to the access list +func (s *StateDB) AddAddressToAccessList(addr common.Address) { + if s.accessList.AddAddress(addr) { + s.journal.append(accessListAddAccountChange{&addr}) + } +} + +// AddSlotToAccessList adds the given (address, slot)-tuple to the access list +func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + addrMod, slotMod := s.accessList.AddSlot(addr, slot) + if addrMod { + // In practice, this should not happen, since there is no way to enter the + // scope of 'address' without having the 'address' become already added + // to the access list (via call-variant, create, etc). + // Better safe than sorry, though + s.journal.append(accessListAddAccountChange{&addr}) + } + if slotMod { + s.journal.append(accessListAddSlotChange{ + address: &addr, + slot: &slot, + }) + } +} + +// AddressInAccessList returns true if the given address is in the access list. +func (s *StateDB) AddressInAccessList(addr common.Address) bool { + return s.accessList.ContainsAddress(addr) +} + +// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list. +func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { + return s.accessList.Contains(addr, slot) +} + +// markDelete is invoked when an account is deleted but the deletion is +// not yet committed. The pending mutation is cached and will be applied +// all together +func (s *StateDB) markDelete(addr common.Address) { + if _, ok := s.mutations[addr]; !ok { + s.mutations[addr] = &mutation{} + } + s.mutations[addr].applied = false + s.mutations[addr].typ = deletion +} + +func (s *StateDB) markUpdate(addr common.Address) { + if _, ok := s.mutations[addr]; !ok { + s.mutations[addr] = &mutation{} + } + s.mutations[addr].applied = false + s.mutations[addr].typ = update +} + +func (s *StateDB) PointCache() *utils.PointCache { + return s.db.PointCache() +} + +// Witness retrieves the current state witness being collected. +func (s *StateDB) Witness() *stateless.Witness { + return s.witness +} diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go new file mode 100644 index 0000000..40b079c --- /dev/null +++ b/core/state/statedb_fuzz_test.go @@ -0,0 +1,406 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package state + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math" + "math/rand" + "reflect" + "strings" + "testing" + "testing/quick" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/tracing" + "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/triedb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "github.com/holiman/uint256" +) + +// A stateTest checks that the state changes are correctly captured. Instances +// of this test with pseudorandom content are created by Generate. +// +// The test works as follows: +// +// A list of states are created by applying actions. The state changes between +// each state instance are tracked and be verified. +type stateTest struct { + addrs []common.Address // all account addresses + actions [][]testAction // modifications to the state, grouped by block + chunk int // The number of actions per chunk + err error // failure details are reported through this field +} + +// newStateTestAction creates a random action that changes state. +func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction { + actions := []testAction{ + { + name: "SetBalance", + fn: func(a testAction, s *StateDB) { + s.SetBalance(addr, uint256.NewInt(uint64(a.args[0])), tracing.BalanceChangeUnspecified) + }, + args: make([]int64, 1), + }, + { + name: "SetNonce", + fn: func(a testAction, s *StateDB) { + s.SetNonce(addr, uint64(a.args[0])) + }, + args: make([]int64, 1), + }, + { + name: "SetState", + fn: func(a testAction, s *StateDB) { + var key, val common.Hash + binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) + binary.BigEndian.PutUint16(val[:], uint16(a.args[1])) + s.SetState(addr, key, val) + }, + args: make([]int64, 2), + }, + { + name: "SetCode", + fn: func(a testAction, s *StateDB) { + code := make([]byte, 16) + binary.BigEndian.PutUint64(code, uint64(a.args[0])) + binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) + s.SetCode(addr, code) + }, + args: make([]int64, 2), + }, + { + name: "CreateAccount", + fn: func(a testAction, s *StateDB) { + if !s.Exist(addr) { + s.CreateAccount(addr) + } + }, + }, + { + name: "Selfdestruct", + fn: func(a testAction, s *StateDB) { + s.SelfDestruct(addr) + }, + }, + } + var nonRandom = index != -1 + if index == -1 { + index = r.Intn(len(actions)) + } + action := actions[index] + var names []string + if !action.noAddr { + names = append(names, addr.Hex()) + } + for i := range action.args { + if nonRandom { + action.args[i] = rand.Int63n(10000) + 1 // set balance to non-zero + } else { + action.args[i] = rand.Int63n(10000) + } + names = append(names, fmt.Sprint(action.args[i])) + } + action.name += " " + strings.Join(names, ", ") + return action +} + +// Generate returns a new snapshot test of the given size. All randomness is +// derived from r. +func (*stateTest) Generate(r *rand.Rand, size int) reflect.Value { + addrs := make([]common.Address, 5) + for i := range addrs { + addrs[i][0] = byte(i) + } + actions := make([][]testAction, rand.Intn(5)+1) + + for i := 0; i < len(actions); i++ { + actions[i] = make([]testAction, size) + for j := range actions[i] { + if j == 0 { + // Always include a set balance action to make sure + // the state changes are not empty. + actions[i][j] = newStateTestAction(common.HexToAddress("0xdeadbeef"), r, 0) + continue + } + actions[i][j] = newStateTestAction(addrs[r.Intn(len(addrs))], r, -1) + } + } + chunk := int(math.Sqrt(float64(size))) + if size > 0 && chunk == 0 { + chunk = 1 + } + return reflect.ValueOf(&stateTest{ + addrs: addrs, + actions: actions, + chunk: chunk, + }) +} + +func (test *stateTest) String() string { + out := new(bytes.Buffer) + for i, actions := range test.actions { + fmt.Fprintf(out, "---- block %d ----\n", i) + for j, action := range actions { + if j%test.chunk == 0 { + fmt.Fprintf(out, "---- transaction %d ----\n", j/test.chunk) + } + fmt.Fprintf(out, "%4d: %s\n", j%test.chunk, action.name) + } + } + return out.String() +} + +func (test *stateTest) run() bool { + var ( + roots []common.Hash + accountList []map[common.Address][]byte + storageList []map[common.Address]map[common.Hash][]byte + copyUpdate = func(update *stateUpdate) { + accounts := make(map[common.Address][]byte, len(update.accountsOrigin)) + for key, val := range update.accountsOrigin { + accounts[key] = common.CopyBytes(val) + } + accountList = append(accountList, accounts) + + storages := make(map[common.Address]map[common.Hash][]byte, len(update.storagesOrigin)) + for addr, subset := range update.storagesOrigin { + storages[addr] = make(map[common.Hash][]byte, len(subset)) + for key, val := range subset { + storages[addr][key] = common.CopyBytes(val) + } + } + storageList = append(storageList, storages) + } + disk = rawdb.NewMemoryDatabase() + tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults}) + sdb = NewDatabaseWithNodeDB(disk, tdb) + byzantium = rand.Intn(2) == 0 + ) + defer disk.Close() + defer tdb.Close() + + var snaps *snapshot.Tree + if rand.Intn(3) == 0 { + snaps, _ = snapshot.New(snapshot.Config{ + CacheSize: 1, + Recovery: false, + NoBuild: false, + AsyncBuild: false, + }, disk, tdb, types.EmptyRootHash) + } + for i, actions := range test.actions { + root := types.EmptyRootHash + if i != 0 { + root = roots[len(roots)-1] + } + state, err := New(root, sdb, snaps) + if err != nil { + panic(err) + } + for i, action := range actions { + if i%test.chunk == 0 && i != 0 { + if byzantium { + state.Finalise(true) // call finalise at the transaction boundary + } else { + state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary + } + } + action.fn(action, state) + } + if byzantium { + state.Finalise(true) // call finalise at the transaction boundary + } else { + state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary + } + ret, err := state.commitAndFlush(0, true) // call commit at the block boundary + if err != nil { + panic(err) + } + if ret.empty() { + return true + } + copyUpdate(ret) + roots = append(roots, ret.root) + } + for i := 0; i < len(test.actions); i++ { + root := types.EmptyRootHash + if i != 0 { + root = roots[i-1] + } + test.err = test.verify(root, roots[i], tdb, accountList[i], storageList[i]) + if test.err != nil { + return false + } + } + return true +} + +// verifyAccountCreation this function is called once the state diff says that +// specific account was not present. A serial of checks will be performed to +// ensure the state diff is correct, includes: +// +// - the account was indeed not present in trie +// - the account is present in new trie, nil->nil is regarded as invalid +// - the slots transition is correct +func (test *stateTest) verifyAccountCreation(next common.Hash, db *triedb.Database, otr, ntr *trie.Trie, addr common.Address, slots map[common.Hash][]byte) error { + // Verify account change + addrHash := crypto.Keccak256Hash(addr.Bytes()) + oBlob, err := otr.Get(addrHash.Bytes()) + if err != nil { + return err + } + nBlob, err := ntr.Get(addrHash.Bytes()) + if err != nil { + return err + } + if len(oBlob) != 0 { + return fmt.Errorf("unexpected account in old trie, %x", addrHash) + } + if len(nBlob) == 0 { + return fmt.Errorf("missing account in new trie, %x", addrHash) + } + + // Verify storage changes + var nAcct types.StateAccount + if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil { + return err + } + // Account has no slot, empty slot set is expected + if nAcct.Root == types.EmptyRootHash { + if len(slots) != 0 { + return fmt.Errorf("unexpected slot changes %x", addrHash) + } + return nil + } + // Account has slots, ensure all new slots are contained + st, err := trie.New(trie.StorageTrieID(next, addrHash, nAcct.Root), db) + if err != nil { + return err + } + for key, val := range slots { + st.Update(key.Bytes(), val) + } + if st.Hash() != types.EmptyRootHash { + return errors.New("invalid slot changes") + } + return nil +} + +// verifyAccountUpdate this function is called once the state diff says that +// specific account was present. A serial of checks will be performed to +// ensure the state diff is correct, includes: +// +// - the account was indeed present in trie +// - the account in old trie matches the provided value +// - the slots transition is correct +func (test *stateTest) verifyAccountUpdate(next common.Hash, db *triedb.Database, otr, ntr *trie.Trie, addr common.Address, origin []byte, slots map[common.Hash][]byte) error { + // Verify account change + addrHash := crypto.Keccak256Hash(addr.Bytes()) + oBlob, err := otr.Get(addrHash.Bytes()) + if err != nil { + return err + } + nBlob, err := ntr.Get(addrHash.Bytes()) + if err != nil { + return err + } + if len(oBlob) == 0 { + return fmt.Errorf("missing account in old trie, %x", addrHash) + } + full, err := types.FullAccountRLP(origin) + if err != nil { + return err + } + if !bytes.Equal(full, oBlob) { + return fmt.Errorf("account value is not matched, %x", addrHash) + } + + // Decode accounts + var ( + oAcct types.StateAccount + nAcct types.StateAccount + nRoot common.Hash + ) + if err := rlp.DecodeBytes(oBlob, &oAcct); err != nil { + return err + } + if len(nBlob) == 0 { + nRoot = types.EmptyRootHash + } else { + if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil { + return err + } + nRoot = nAcct.Root + } + + // Verify storage + st, err := trie.New(trie.StorageTrieID(next, addrHash, nRoot), db) + if err != nil { + return err + } + for key, val := range slots { + st.Update(key.Bytes(), val) + } + if st.Hash() != oAcct.Root { + return errors.New("invalid slot changes") + } + return nil +} + +func (test *stateTest) verify(root common.Hash, next common.Hash, db *triedb.Database, accountsOrigin map[common.Address][]byte, storagesOrigin map[common.Address]map[common.Hash][]byte) error { + otr, err := trie.New(trie.StateTrieID(root), db) + if err != nil { + return err + } + ntr, err := trie.New(trie.StateTrieID(next), db) + if err != nil { + return err + } + for addr, account := range accountsOrigin { + var err error + if len(account) == 0 { + err = test.verifyAccountCreation(next, db, otr, ntr, addr, storagesOrigin[addr]) + } else { + err = test.verifyAccountUpdate(next, db, otr, ntr, addr, accountsOrigin[addr], storagesOrigin[addr]) + } + if err != nil { + return err + } + } + return nil +} + +func TestStateChanges(t *testing.T) { + config := &quick.Config{MaxCount: 1000} + err := quick.Check((*stateTest).run, config) + if cerr, ok := err.(*quick.CheckError); ok { + test := cerr.In[0].(*stateTest) + t.Errorf("%v:\n%s", test.err, test) + } else if err != nil { + t.Error(err) + } +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go new file mode 100644 index 0000000..2ce2b86 --- /dev/null +++ b/core/state/statedb_test.go @@ -0,0 +1,1375 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "maps" + "math" + "math/rand" + "reflect" + "slices" + "strings" + "sync" + "testing" + "testing/quick" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/tracing" + "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/trienode" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "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 + var ( + db = rawdb.NewMemoryDatabase() + tdb = triedb.NewDatabase(db, nil) + ) + state, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(db, tdb), nil) + + // Update it with some accounts + for i := byte(0); i < 255; i++ { + addr := common.BytesToAddress([]byte{i}) + state.AddBalance(addr, uint256.NewInt(uint64(11*i)), tracing.BalanceChangeUnspecified) + state.SetNonce(addr, uint64(42*i)) + if i%2 == 0 { + state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) + } + if i%3 == 0 { + state.SetCode(addr, []byte{i, i, i, i, i}) + } + } + + root := state.IntermediateRoot(false) + if err := tdb.Commit(root, false); err != nil { + t.Errorf("can not commit trie %v to persistent database", root.Hex()) + } + + // Ensure that no data was leaked into the database + it := db.NewIterator(nil, nil) + for it.Next() { + t.Errorf("State leaked into database: %x -> %x", it.Key(), it.Value()) + } + it.Release() +} + +// Tests that no intermediate state of an object is stored into the database, +// only the one right before the commit. +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() + transNdb := triedb.NewDatabase(transDb, nil) + finalNdb := triedb.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, uint256.NewInt(uint64(11*i)+uint64(tweak)), tracing.BalanceChangeUnspecified) + state.SetNonce(addr, uint64(42*i+tweak)) + if i%2 == 0 { + state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{}) + state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak}) + } + if i%3 == 0 { + state.SetCode(addr, []byte{i, i, i, i, i, tweak}) + } + } + + // Modify the transient state. + for i := byte(0); i < 255; i++ { + modify(transState, common.Address{i}, i, 0) + } + // Write modifications to trie. + transState.IntermediateRoot(false) + + // Overwrite all the data with new values in the transient database. + for i := byte(0); i < 255; i++ { + modify(transState, common.Address{i}, i, 99) + modify(finalState, common.Address{i}, i, 99) + } + + // Commit and cross check the databases. + transRoot, err := transState.Commit(0, false) + if err != nil { + t.Fatalf("failed to commit transition state: %v", err) + } + if err = transNdb.Commit(transRoot, false); err != nil { + t.Errorf("can not commit trie %v to persistent database", transRoot.Hex()) + } + + finalRoot, err := finalState.Commit(0, false) + if err != nil { + t.Fatalf("failed to commit final state: %v", err) + } + if err = finalNdb.Commit(finalRoot, false); err != nil { + t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex()) + } + + it := finalDb.NewIterator(nil, nil) + for it.Next() { + key, fvalue := it.Key(), it.Value() + tvalue, err := transDb.Get(key) + if err != nil { + t.Errorf("entry missing from the transition database: %x -> %x", key, fvalue) + } + if !bytes.Equal(fvalue, tvalue) { + t.Errorf("value mismatch at key %x: %x in transition database, %x in final database", key, tvalue, fvalue) + } + } + it.Release() + + it = transDb.NewIterator(nil, nil) + for it.Next() { + key, tvalue := it.Key(), it.Value() + fvalue, err := finalDb.Get(key) + if err != nil { + t.Errorf("extra entry in the transition database: %x -> %x", key, it.Value()) + } + if !bytes.Equal(fvalue, tvalue) { + t.Errorf("value mismatch at key %x: %x in transition database, %x in final database", key, tvalue, fvalue) + } + } +} + +// TestCopy tests that copying a StateDB object indeed makes the original and +// the copy independent of each other. This test is a regression test against +// https://github.com/ethereum/go-ethereum/pull/15549. +func TestCopy(t *testing.T) { + // Create a random state test to copy and modify "independently" + orig, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) + + for i := byte(0); i < 255; i++ { + obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified) + orig.updateStateObject(obj) + } + orig.Finalise(false) + + // Copy the state + copy := orig.Copy() + + // Copy the copy state + ccopy := copy.Copy() + + // modify all in memory + for i := byte(0); i < 255; i++ { + origObj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) + copyObj := copy.getOrNewStateObject(common.BytesToAddress([]byte{i})) + ccopyObj := ccopy.getOrNewStateObject(common.BytesToAddress([]byte{i})) + + origObj.AddBalance(uint256.NewInt(2*uint64(i)), tracing.BalanceChangeUnspecified) + copyObj.AddBalance(uint256.NewInt(3*uint64(i)), tracing.BalanceChangeUnspecified) + ccopyObj.AddBalance(uint256.NewInt(4*uint64(i)), tracing.BalanceChangeUnspecified) + + orig.updateStateObject(origObj) + copy.updateStateObject(copyObj) + ccopy.updateStateObject(copyObj) + } + + // Finalise the changes on all concurrently + finalise := func(wg *sync.WaitGroup, db *StateDB) { + defer wg.Done() + db.Finalise(true) + } + + var wg sync.WaitGroup + wg.Add(3) + go finalise(&wg, orig) + go finalise(&wg, copy) + go finalise(&wg, ccopy) + wg.Wait() + + // Verify that the three states have been updated independently + for i := byte(0); i < 255; i++ { + origObj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) + copyObj := copy.getOrNewStateObject(common.BytesToAddress([]byte{i})) + ccopyObj := ccopy.getOrNewStateObject(common.BytesToAddress([]byte{i})) + + if want := uint256.NewInt(3 * uint64(i)); origObj.Balance().Cmp(want) != 0 { + t.Errorf("orig obj %d: balance mismatch: have %v, want %v", i, origObj.Balance(), want) + } + if want := uint256.NewInt(4 * uint64(i)); copyObj.Balance().Cmp(want) != 0 { + t.Errorf("copy obj %d: balance mismatch: have %v, want %v", i, copyObj.Balance(), want) + } + if want := uint256.NewInt(5 * uint64(i)); ccopyObj.Balance().Cmp(want) != 0 { + t.Errorf("copy obj %d: balance mismatch: have %v, want %v", i, ccopyObj.Balance(), want) + } + } +} + +// TestCopyWithDirtyJournal tests if Copy can correct create a equal copied +// stateDB with dirty journal present. +func TestCopyWithDirtyJournal(t *testing.T) { + db := NewDatabase(rawdb.NewMemoryDatabase()) + orig, _ := New(types.EmptyRootHash, db, nil) + + // Fill up the initial states + for i := byte(0); i < 255; i++ { + obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified) + obj.data.Root = common.HexToHash("0xdeadbeef") + orig.updateStateObject(obj) + } + root, _ := orig.Commit(0, true) + orig, _ = New(root, db, nil) + + // modify all in memory without finalizing + for i := byte(0); i < 255; i++ { + obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.SubBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified) + orig.updateStateObject(obj) + } + cpy := orig.Copy() + + orig.Finalise(true) + for i := byte(0); i < 255; i++ { + root := orig.GetStorageRoot(common.BytesToAddress([]byte{i})) + if root != (common.Hash{}) { + t.Errorf("Unexpected storage root %x", root) + } + } + cpy.Finalise(true) + for i := byte(0); i < 255; i++ { + root := cpy.GetStorageRoot(common.BytesToAddress([]byte{i})) + if root != (common.Hash{}) { + t.Errorf("Unexpected storage root %x", root) + } + } + if cpy.IntermediateRoot(true) != orig.IntermediateRoot(true) { + t.Error("State is not equal after copy") + } +} + +// TestCopyObjectState creates an original state, S1, and makes a copy S2. +// It then proceeds to make changes to S1. Those changes are _not_ supposed +// to affect S2. This test checks that the copy properly deep-copies the objectstate +func TestCopyObjectState(t *testing.T) { + db := NewDatabase(rawdb.NewMemoryDatabase()) + orig, _ := New(types.EmptyRootHash, db, nil) + + // Fill up the initial states + for i := byte(0); i < 5; i++ { + obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified) + obj.data.Root = common.HexToHash("0xdeadbeef") + orig.updateStateObject(obj) + } + orig.Finalise(true) + cpy := orig.Copy() + for _, op := range cpy.mutations { + if have, want := op.applied, false; have != want { + t.Fatalf("Error in test itself, the 'done' flag should not be set before Commit, have %v want %v", have, want) + } + } + orig.Commit(0, true) + for _, op := range cpy.mutations { + if have, want := op.applied, false; have != want { + t.Fatalf("Error: original state affected copy, have %v want %v", have, want) + } + } +} + +func TestSnapshotRandom(t *testing.T) { + config := &quick.Config{MaxCount: 1000} + err := quick.Check((*snapshotTest).run, config) + if cerr, ok := err.(*quick.CheckError); ok { + test := cerr.In[0].(*snapshotTest) + t.Errorf("%v:\n%s", test.err, test) + } else if err != nil { + t.Error(err) + } +} + +// A snapshotTest checks that reverting StateDB snapshots properly undoes all changes +// captured by the snapshot. Instances of this test with pseudorandom content are created +// by Generate. +// +// The test works as follows: +// +// A new state is created and all actions are applied to it. Several snapshots are taken +// in between actions. The test then reverts each snapshot. For each snapshot the actions +// leading up to it are replayed on a fresh, empty state. The behaviour of all public +// accessor methods on the reverted state must match the return value of the equivalent +// methods on the replayed state. +type snapshotTest struct { + addrs []common.Address // all account addresses + actions []testAction // modifications to the state + snapshots []int // actions indexes at which snapshot is taken + err error // failure details are reported through this field +} + +type testAction struct { + name string + fn func(testAction, *StateDB) + args []int64 + noAddr bool +} + +// newTestAction creates a random action that changes state. +func newTestAction(addr common.Address, r *rand.Rand) testAction { + actions := []testAction{ + { + name: "SetBalance", + fn: func(a testAction, s *StateDB) { + s.SetBalance(addr, uint256.NewInt(uint64(a.args[0])), tracing.BalanceChangeUnspecified) + }, + args: make([]int64, 1), + }, + { + name: "AddBalance", + fn: func(a testAction, s *StateDB) { + s.AddBalance(addr, uint256.NewInt(uint64(a.args[0])), tracing.BalanceChangeUnspecified) + }, + args: make([]int64, 1), + }, + { + name: "SetNonce", + fn: func(a testAction, s *StateDB) { + s.SetNonce(addr, uint64(a.args[0])) + }, + args: make([]int64, 1), + }, + { + name: "SetState", + fn: func(a testAction, s *StateDB) { + var key, val common.Hash + binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) + binary.BigEndian.PutUint16(val[:], uint16(a.args[1])) + s.SetState(addr, key, val) + }, + args: make([]int64, 2), + }, + { + name: "SetCode", + fn: func(a testAction, s *StateDB) { + code := make([]byte, 16) + binary.BigEndian.PutUint64(code, uint64(a.args[0])) + binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) + s.SetCode(addr, code) + }, + args: make([]int64, 2), + }, + { + name: "CreateAccount", + fn: func(a testAction, s *StateDB) { + if !s.Exist(addr) { + s.CreateAccount(addr) + } + }, + }, + { + name: "CreateContract", + fn: func(a testAction, s *StateDB) { + if !s.Exist(addr) { + s.CreateAccount(addr) + } + contractHash := s.GetCodeHash(addr) + emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash + storageRoot := s.GetStorageRoot(addr) + emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash + if s.GetNonce(addr) == 0 && emptyCode && emptyStorage { + s.CreateContract(addr) + // We also set some code here, to prevent the + // CreateContract action from being performed twice in a row, + // which would cause a difference in state when unrolling + // the journal. (CreateContact assumes created was false prior to + // invocation, and the journal rollback sets it to false). + s.SetCode(addr, []byte{1}) + } + }, + }, + { + name: "SelfDestruct", + fn: func(a testAction, s *StateDB) { + s.SelfDestruct(addr) + }, + }, + { + name: "AddRefund", + fn: func(a testAction, s *StateDB) { + s.AddRefund(uint64(a.args[0])) + }, + args: make([]int64, 1), + noAddr: true, + }, + { + name: "AddLog", + fn: func(a testAction, s *StateDB) { + data := make([]byte, 2) + binary.BigEndian.PutUint16(data, uint16(a.args[0])) + s.AddLog(&types.Log{Address: addr, Data: data}) + }, + args: make([]int64, 1), + }, + { + name: "AddPreimage", + fn: func(a testAction, s *StateDB) { + preimage := []byte{1} + hash := common.BytesToHash(preimage) + s.AddPreimage(hash, preimage) + }, + args: make([]int64, 1), + }, + { + name: "AddAddressToAccessList", + fn: func(a testAction, s *StateDB) { + s.AddAddressToAccessList(addr) + }, + }, + { + name: "AddSlotToAccessList", + fn: func(a testAction, s *StateDB) { + s.AddSlotToAccessList(addr, + common.Hash{byte(a.args[0])}) + }, + args: make([]int64, 1), + }, + { + name: "SetTransientState", + fn: func(a testAction, s *StateDB) { + var key, val common.Hash + binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) + binary.BigEndian.PutUint16(val[:], uint16(a.args[1])) + s.SetTransientState(addr, key, val) + }, + args: make([]int64, 2), + }, + } + action := actions[r.Intn(len(actions))] + var nameargs []string + if !action.noAddr { + nameargs = append(nameargs, addr.Hex()) + } + for i := range action.args { + action.args[i] = rand.Int63n(100) + nameargs = append(nameargs, fmt.Sprint(action.args[i])) + } + action.name += strings.Join(nameargs, ", ") + return action +} + +// Generate returns a new snapshot test of the given size. All randomness is +// derived from r. +func (*snapshotTest) Generate(r *rand.Rand, size int) reflect.Value { + // Generate random actions. + addrs := make([]common.Address, 50) + for i := range addrs { + addrs[i][0] = byte(i) + } + actions := make([]testAction, size) + for i := range actions { + addr := addrs[r.Intn(len(addrs))] + actions[i] = newTestAction(addr, r) + } + // Generate snapshot indexes. + nsnapshots := int(math.Sqrt(float64(size))) + if size > 0 && nsnapshots == 0 { + nsnapshots = 1 + } + snapshots := make([]int, nsnapshots) + snaplen := len(actions) / nsnapshots + for i := range snapshots { + // Try to place the snapshots some number of actions apart from each other. + snapshots[i] = (i * snaplen) + r.Intn(snaplen) + } + return reflect.ValueOf(&snapshotTest{addrs, actions, snapshots, nil}) +} + +func (test *snapshotTest) String() string { + out := new(bytes.Buffer) + sindex := 0 + for i, action := range test.actions { + if len(test.snapshots) > sindex && i == test.snapshots[sindex] { + fmt.Fprintf(out, "---- snapshot %d ----\n", sindex) + sindex++ + } + fmt.Fprintf(out, "%4d: %s\n", i, action.name) + } + return out.String() +} + +func (test *snapshotTest) run() bool { + // Run all actions and create snapshots. + var ( + 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) + } + // 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-- { + state.RevertToSnapshot(snapshotRevs[sindex]) + 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 + } + } + 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 + } + var ( + it = trie.NewIterator(trieIt) + visited = make(map[common.Hash]bool) + ) + + for it.Next() { + key := common.BytesToHash(s.trie.GetKey(it.Key)) + visited[key] = true + 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 { + var err error + checkeq := func(op string, a, b interface{}) bool { + if err == nil && !reflect.DeepEqual(a, b) { + err = fmt.Errorf("got %s(%s) == %v, want %v", op, addr.Hex(), a, b) + return false + } + return true + } + // Check basic accessor methods. + checkeq("Exist", state.Exist(addr), checkstate.Exist(addr)) + checkeq("HasSelfdestructed", state.HasSelfDestructed(addr), checkstate.HasSelfDestructed(addr)) + checkeq("GetBalance", state.GetBalance(addr), checkstate.GetBalance(addr)) + checkeq("GetNonce", state.GetNonce(addr), checkstate.GetNonce(addr)) + checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr)) + checkeq("GetCodeHash", state.GetCodeHash(addr), checkstate.GetCodeHash(addr)) + checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr)) + // Check newContract-flag + if obj := state.getStateObject(addr); obj != nil { + checkeq("IsNewContract", obj.newContract, checkstate.getStateObject(addr).newContract) + } + // Check storage. + if obj := state.getStateObject(addr); obj != nil { + forEachStorage(state, addr, func(key, value common.Hash) bool { + return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value) + }) + forEachStorage(checkstate, addr, func(key, value common.Hash) bool { + return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value) + }) + other := checkstate.getStateObject(addr) + // Check dirty storage which is not in trie + if !maps.Equal(obj.dirtyStorage, other.dirtyStorage) { + print := func(dirty map[common.Hash]common.Hash) string { + var keys []common.Hash + out := new(strings.Builder) + for key := range dirty { + keys = append(keys, key) + } + slices.SortFunc(keys, common.Hash.Cmp) + for i, key := range keys { + fmt.Fprintf(out, " %d. %v %v\n", i, key, dirty[key]) + } + return out.String() + } + return fmt.Errorf("dirty storage err, have\n%v\nwant\n%v", + print(obj.dirtyStorage), + print(other.dirtyStorage)) + } + } + // Check transient storage. + { + have := state.transientStorage + want := checkstate.transientStorage + eq := maps.EqualFunc(have, want, + func(a Storage, b Storage) bool { + return maps.Equal(a, b) + }) + if !eq { + return fmt.Errorf("transient storage differs ,have\n%v\nwant\n%v", + have.PrettyPrint(), + want.PrettyPrint()) + } + } + if err != nil { + return err + } + } + if !checkstate.accessList.Equal(state.accessList) { // Check access lists + return fmt.Errorf("AccessLists are wrong, have \n%v\nwant\n%v", + checkstate.accessList.PrettyPrint(), + state.accessList.PrettyPrint()) + } + if state.GetRefund() != checkstate.GetRefund() { + return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d", + state.GetRefund(), checkstate.GetRefund()) + } + if !reflect.DeepEqual(state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) { + return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v", + state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) + } + if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) { + getKeys := func(dirty map[common.Address]int) string { + var keys []common.Address + out := new(strings.Builder) + for key := range dirty { + keys = append(keys, key) + } + slices.SortFunc(keys, common.Address.Cmp) + for i, key := range keys { + fmt.Fprintf(out, " %d. %v\n", i, key) + } + return out.String() + } + have := getKeys(state.journal.dirties) + want := getKeys(checkstate.journal.dirties) + return fmt.Errorf("dirty-journal set mismatch.\nhave:\n%v\nwant:\n%v\n", have, want) + } + return nil +} + +func TestTouchDelete(t *testing.T) { + s := newStateEnv() + s.state.getOrNewStateObject(common.Address{}) + root, _ := s.state.Commit(0, false) + s.state, _ = New(root, s.state.db, s.state.snaps) + + snapshot := s.state.Snapshot() + s.state.AddBalance(common.Address{}, new(uint256.Int), tracing.BalanceChangeUnspecified) + + if len(s.state.journal.dirties) != 1 { + t.Fatal("expected one dirty state object") + } + s.state.RevertToSnapshot(snapshot) + if len(s.state.journal.dirties) != 0 { + t.Fatal("expected no dirty state object") + } +} + +// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy. +// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512 +func TestCopyOfCopy(t *testing.T) { + state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) + addr := common.HexToAddress("aaaa") + state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) + + if got := state.Copy().GetBalance(addr).Uint64(); got != 42 { + t.Fatalf("1st copy fail, expected 42, got %v", got) + } + if got := state.Copy().Copy().GetBalance(addr).Uint64(); got != 42 { + t.Fatalf("2nd copy fail, expected 42, got %v", got) + } +} + +// Tests a regression where committing a copy lost some internal meta information, +// leading to corrupted subsequent copies. +// +// See https://github.com/ethereum/go-ethereum/issues/20106. +func TestCopyCommitCopy(t *testing.T) { + tdb := NewDatabase(rawdb.NewMemoryDatabase()) + state, _ := New(types.EmptyRootHash, tdb, nil) + + // Create an account and check if the retrieved balance is correct + addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") + skey := common.HexToHash("aaa") + sval := common.HexToHash("bbb") + + state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie + state.SetCode(addr, []byte("hello")) // Change an external metadata + state.SetState(addr, skey, sval) // Change the storage trie + + if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) + } + if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := state.GetState(addr, skey); val != sval { + t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) + } + // Copy the non-committed state database and check pre/post commit balance + copyOne := state.Copy() + if balance := copyOne.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42) + } + if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("first copy pre-commit code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := copyOne.GetState(addr, skey); val != sval { + t.Fatalf("first copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) + } + // Copy the copy and check the balance once more + copyTwo := copyOne.Copy() + if balance := copyTwo.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42) + } + if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("second copy code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := copyTwo.GetState(addr, skey); val != sval { + t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval) + } + // Commit state, ensure states can be loaded from disk + root, _ := state.Commit(0, false) + state, _ = New(root, tdb, nil) + if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42) + } + if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("state post-commit code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := state.GetState(addr, skey); val != sval { + t.Fatalf("state post-commit non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := state.GetCommittedState(addr, skey); val != sval { + t.Fatalf("state post-commit committed storage slot mismatch: have %x, want %x", val, sval) + } +} + +// Tests a regression where committing a copy lost some internal meta information, +// leading to corrupted subsequent copies. +// +// See https://github.com/ethereum/go-ethereum/issues/20106. +func TestCopyCopyCommitCopy(t *testing.T) { + state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) + + // Create an account and check if the retrieved balance is correct + addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") + skey := common.HexToHash("aaa") + sval := common.HexToHash("bbb") + + state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie + state.SetCode(addr, []byte("hello")) // Change an external metadata + state.SetState(addr, skey, sval) // Change the storage trie + + if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) + } + if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := state.GetState(addr, skey); val != sval { + t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) + } + // Copy the non-committed state database and check pre/post commit balance + copyOne := state.Copy() + if balance := copyOne.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42) + } + if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("first copy code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := copyOne.GetState(addr, skey); val != sval { + t.Fatalf("first copy non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{}) + } + // Copy the copy and check the balance once more + copyTwo := copyOne.Copy() + if balance := copyTwo.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42) + } + if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("second copy pre-commit code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := copyTwo.GetState(addr, skey); val != sval { + t.Fatalf("second copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) + } + // Copy the copy-copy and check the balance once more + copyThree := copyTwo.Copy() + if balance := copyThree.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42) + } + if code := copyThree.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("third copy code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := copyThree.GetState(addr, skey); val != sval { + t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := copyThree.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval) + } +} + +// TestCommitCopy tests the copy from a committed state is not fully functional. +func TestCommitCopy(t *testing.T) { + db := NewDatabase(rawdb.NewMemoryDatabase()) + state, _ := New(types.EmptyRootHash, db, nil) + + // Create an account and check if the retrieved balance is correct + addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") + skey1, skey2 := common.HexToHash("a1"), common.HexToHash("a2") + sval1, sval2 := common.HexToHash("b1"), common.HexToHash("b2") + + state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie + state.SetCode(addr, []byte("hello")) // Change an external metadata + state.SetState(addr, skey1, sval1) // Change the storage trie + + if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) + } + if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := state.GetState(addr, skey1); val != sval1 { + t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval1) + } + if val := state.GetCommittedState(addr, skey1); val != (common.Hash{}) { + t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) + } + root, _ := state.Commit(0, true) + + state, _ = New(root, db, nil) + state.SetState(addr, skey2, sval2) + state.Commit(1, true) + + // Copy the committed state database, the copied one is not fully functional. + copied := state.Copy() + if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { + t.Fatalf("unexpected balance: have %v", balance) + } + if code := copied.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("unexpected code: have %x", code) + } + // Miss slots because of non-functional trie after commit + if val := copied.GetState(addr, skey1); val != (common.Hash{}) { + t.Fatalf("unexpected storage slot: have %x", sval1) + } + if val := copied.GetCommittedState(addr, skey1); val != (common.Hash{}) { + t.Fatalf("unexpected storage slot: have %x", val) + } + // Slots cached in the stateDB, available after commit + if val := copied.GetState(addr, skey2); val != sval2 { + t.Fatalf("unexpected storage slot: have %x", sval1) + } + if val := copied.GetCommittedState(addr, skey2); val != sval2 { + t.Fatalf("unexpected storage slot: have %x", val) + } + if !errors.Is(copied.Error(), trie.ErrCommitted) { + t.Fatalf("unexpected state error, %v", copied.Error()) + } +} + +// TestDeleteCreateRevert tests a weird state transition corner case that we hit +// while changing the internals of StateDB. The workflow is that a contract is +// self-destructed, then in a follow-up transaction (but same block) it's created +// again and the transaction reverted. +// +// The original StateDB implementation flushed dirty objects to the tries after +// 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) { + // Create an initial state with a single contract + state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) + + addr := common.BytesToAddress([]byte("so")) + state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified) + + root, _ := state.Commit(0, false) + state, _ = New(root, state.db, state.snaps) + + // Simulate self-destructing in one transaction, then create-reverting in another + state.SelfDestruct(addr) + state.Finalise(true) + + id := state.Snapshot() + state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified) + state.RevertToSnapshot(id) + + // Commit the entire state and make sure we don't crash and have the correct state + root, _ = state.Commit(0, true) + state, _ = New(root, state.db, state.snaps) + + if state.getStateObject(addr) != nil { + t.Fatalf("self-destructed contract came alive") + } +} + +// TestMissingTrieNodes tests that if the StateDB fails to load parts of the trie, +// 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 + var ( + tdb *triedb.Database + memDb = rawdb.NewMemoryDatabase() + ) + if scheme == rawdb.PathScheme { + tdb = triedb.NewDatabase(memDb, &triedb.Config{PathDB: &pathdb.Config{ + CleanCacheSize: 0, + DirtyCacheSize: 0, + }}) // disable caching + } else { + tdb = triedb.NewDatabase(memDb, &triedb.Config{HashDB: &hashdb.Config{ + CleanCacheSize: 0, + }}) // disable caching + } + db := NewDatabaseWithNodeDB(memDb, tdb) + + var root common.Hash + state, _ := New(types.EmptyRootHash, db, nil) + addr := common.BytesToAddress([]byte("so")) + { + state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified) + state.SetCode(addr, []byte{1, 2, 3}) + a2 := common.BytesToAddress([]byte("another")) + state.SetBalance(a2, uint256.NewInt(100), tracing.BalanceChangeUnspecified) + state.SetCode(a2, []byte{1, 2, 4}) + root, _ = state.Commit(0, false) + t.Logf("root: %x", root) + // force-flush + tdb.Commit(root, false) + } + // Create a new state on the old root + state, _ = New(root, db, nil) + // Now we clear out the memdb + it := memDb.NewIterator(nil, nil) + for it.Next() { + k := it.Key() + // Leave the root intact + if !bytes.Equal(k, root[:]) { + t.Logf("key: %x", k) + memDb.Delete(k) + } + } + balance := state.GetBalance(addr) + // The removed elem should lead to it returning zero balance + if exp, got := uint64(0), balance.Uint64(); got != exp { + t.Errorf("expected %d, got %d", exp, got) + } + // Modify the state + state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified) + root, err := state.Commit(0, false) + if err == nil { + t.Fatalf("expected error, got root :%x", root) + } +} + +func TestStateDBAccessList(t *testing.T) { + // Some helpers + addr := func(a string) common.Address { + return common.HexToAddress(a) + } + slot := func(a string) common.Hash { + return common.HexToHash(a) + } + + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(types.EmptyRootHash, db, nil) + state.accessList = newAccessList() + + verifyAddrs := func(astrings ...string) { + t.Helper() + // convert to common.Address form + var addresses []common.Address + var addressMap = make(map[common.Address]struct{}) + for _, astring := range astrings { + address := addr(astring) + addresses = append(addresses, address) + addressMap[address] = struct{}{} + } + // Check that the given addresses are in the access list + for _, address := range addresses { + if !state.AddressInAccessList(address) { + t.Fatalf("expected %x to be in access list", address) + } + } + // Check that only the expected addresses are present in the access list + for address := range state.accessList.addresses { + if _, exist := addressMap[address]; !exist { + t.Fatalf("extra address %x in access list", address) + } + } + } + verifySlots := func(addrString string, slotStrings ...string) { + if !state.AddressInAccessList(addr(addrString)) { + t.Fatalf("scope missing address/slots %v", addrString) + } + var address = addr(addrString) + // convert to common.Hash form + var slots []common.Hash + var slotMap = make(map[common.Hash]struct{}) + for _, slotString := range slotStrings { + s := slot(slotString) + slots = append(slots, s) + slotMap[s] = struct{}{} + } + // Check that the expected items are in the access list + for i, s := range slots { + if _, slotPresent := state.SlotInAccessList(address, s); !slotPresent { + t.Fatalf("input %d: scope missing slot %v (address %v)", i, s, addrString) + } + } + // Check that no extra elements are in the access list + index := state.accessList.addresses[address] + if index >= 0 { + stateSlots := state.accessList.slots[index] + for s := range stateSlots { + if _, slotPresent := slotMap[s]; !slotPresent { + t.Fatalf("scope has extra slot %v (address %v)", s, addrString) + } + } + } + } + + state.AddAddressToAccessList(addr("aa")) // 1 + state.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3 + state.AddSlotToAccessList(addr("bb"), slot("02")) // 4 + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + + // Make a copy + stateCopy1 := state.Copy() + if exp, got := 4, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + + // same again, should cause no journal entries + state.AddSlotToAccessList(addr("bb"), slot("01")) + state.AddSlotToAccessList(addr("bb"), slot("02")) + state.AddAddressToAccessList(addr("aa")) + if exp, got := 4, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + // some new ones + state.AddSlotToAccessList(addr("bb"), slot("03")) // 5 + state.AddSlotToAccessList(addr("aa"), slot("01")) // 6 + state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8 + state.AddAddressToAccessList(addr("cc")) + if exp, got := 8, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + + verifyAddrs("aa", "bb", "cc") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + verifySlots("cc", "01") + + // now start rolling back changes + state.journal.revert(state, 7) + if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb", "cc") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 6) + if state.AddressInAccessList(addr("cc")) { + t.Fatalf("addr present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 5) + if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 4) + if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + + state.journal.revert(state, 3) + if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01") + + state.journal.revert(state, 2) + if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + + state.journal.revert(state, 1) + if state.AddressInAccessList(addr("bb")) { + t.Fatalf("addr present, expected missing") + } + verifyAddrs("aa") + + state.journal.revert(state, 0) + if state.AddressInAccessList(addr("aa")) { + t.Fatalf("addr present, expected missing") + } + if got, exp := len(state.accessList.addresses), 0; got != exp { + t.Fatalf("expected empty, got %d", got) + } + if got, exp := len(state.accessList.slots), 0; got != exp { + t.Fatalf("expected empty, got %d", got) + } + // Check the copy + // Make a copy + state = stateCopy1 + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + if got, exp := len(state.accessList.addresses), 2; got != exp { + t.Fatalf("expected empty, got %d", got) + } + if got, exp := len(state.accessList.slots), 1; got != exp { + t.Fatalf("expected empty, got %d", got) + } +} + +// Tests that account and storage tries are flushed in the correct order and that +// no data loss occurs. +func TestFlushOrderDataLoss(t *testing.T) { + // Create a state trie with many accounts and slots + var ( + memdb = rawdb.NewMemoryDatabase() + triedb = triedb.NewDatabase(memdb, nil) + statedb = NewDatabaseWithNodeDB(memdb, triedb) + state, _ = New(types.EmptyRootHash, statedb, nil) + ) + for a := byte(0); a < 10; a++ { + state.CreateAccount(common.Address{a}) + for s := byte(0); s < 10; s++ { + state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s}) + } + } + root, err := state.Commit(0, false) + if err != nil { + t.Fatalf("failed to commit state trie: %v", err) + } + triedb.Reference(root, common.Hash{}) + if err := triedb.Cap(1024); err != nil { + t.Fatalf("failed to cap trie dirty cache: %v", err) + } + 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 + state, err = New(root, NewDatabase(memdb), nil) + if err != nil { + t.Fatalf("failed to reopen state trie: %v", err) + } + for a := byte(0); a < 10; a++ { + for s := byte(0); s < 10; s++ { + if have := state.GetState(common.Address{a}, common.Hash{a, s}); have != (common.Hash{a, s}) { + t.Errorf("account %d: slot %d: state mismatch: have %x, want %x", a, s, have, common.Hash{a, s}) + } + } + } +} + +func TestStateDBTransientStorage(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(types.EmptyRootHash, db, nil) + + key := common.Hash{0x01} + value := common.Hash{0x02} + addr := common.Address{} + + state.SetTransientState(addr, key, value) + if exp, got := 1, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + // the retrieved value should equal what was set + if got := state.GetTransientState(addr, key); got != value { + t.Fatalf("transient storage mismatch: have %x, want %x", got, value) + } + + // revert the transient state being set and then check that the + // value is now the empty hash + state.journal.revert(state, 0) + if got, exp := state.GetTransientState(addr, key), (common.Hash{}); exp != got { + t.Fatalf("transient storage mismatch: have %x, want %x", got, exp) + } + + // set transient state and then copy the statedb and ensure that + // the transient state is copied + state.SetTransientState(addr, key, value) + cpy := state.Copy() + if got := cpy.GetTransientState(addr, key); got != value { + t.Fatalf("transient storage mismatch: have %x, want %x", got, value) + } +} + +func TestDeleteStorage(t *testing.T) { + var ( + disk = rawdb.NewMemoryDatabase() + tdb = triedb.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, uint256.NewInt(1), tracing.BalanceChangeUnspecified) + 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 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) + } +} + +func TestStorageDirtiness(t *testing.T) { + var ( + disk = rawdb.NewMemoryDatabase() + tdb = triedb.NewDatabase(disk, nil) + db = NewDatabaseWithNodeDB(disk, tdb) + state, _ = New(types.EmptyRootHash, db, nil) + addr = common.HexToAddress("0x1") + checkDirty = func(key common.Hash, value common.Hash, dirty bool) { + obj := state.getStateObject(addr) + v, exist := obj.dirtyStorage[key] + if exist != dirty { + t.Fatalf("Unexpected dirty marker, want: %t, got: %t", dirty, exist) + } + if v != value { + t.Fatalf("Unexpected storage slot, want: %t, got: %t", value, v) + } + } + ) + state.CreateAccount(addr) + + // the storage change is noop, no dirty marker + state.SetState(addr, common.Hash{0x1}, common.Hash{}) + checkDirty(common.Hash{0x1}, common.Hash{}, false) + + // the storage change is valid, dirty marker is expected + snap := state.Snapshot() + state.SetState(addr, common.Hash{0x1}, common.Hash{0x1}) + checkDirty(common.Hash{0x1}, common.Hash{0x1}, true) + + // the storage change is reverted, dirtiness should be revoked + state.RevertToSnapshot(snap) + checkDirty(common.Hash{0x1}, common.Hash{}, false) + + // the storage is reset back to its original value, dirtiness should be revoked + state.SetState(addr, common.Hash{0x1}, common.Hash{0x1}) + snap = state.Snapshot() + state.SetState(addr, common.Hash{0x1}, common.Hash{}) + checkDirty(common.Hash{0x1}, common.Hash{}, false) + + // the storage change is reverted, dirty value should be set back + state.RevertToSnapshot(snap) + checkDirty(common.Hash{0x1}, common.Hash{0x1}, true) +} diff --git a/core/state/stateupdate.go b/core/state/stateupdate.go new file mode 100644 index 0000000..f3e6af9 --- /dev/null +++ b/core/state/stateupdate.go @@ -0,0 +1,133 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +// contractCode represents a contract code with associated metadata. +type contractCode struct { + hash common.Hash // hash is the cryptographic hash of the contract code. + blob []byte // blob is the binary representation of the contract code. +} + +// accountDelete represents an operation for deleting an Ethereum account. +type accountDelete struct { + address common.Address // address is the unique account identifier + origin []byte // origin is the original value of account data in slim-RLP encoding. + storagesOrigin map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in prefix-zero-trimmed RLP format. +} + +// accountUpdate represents an operation for updating an Ethereum account. +type accountUpdate struct { + address common.Address // address is the unique account identifier + data []byte // data is the slim-RLP encoded account data. + origin []byte // origin is the original value of account data in slim-RLP encoding. + code *contractCode // code represents mutated contract code; nil means it's not modified. + storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format. + storagesOrigin map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in prefix-zero-trimmed RLP format. +} + +// stateUpdate represents the difference between two states resulting from state +// execution. It contains information about mutated contract codes, accounts, +// and storage slots, along with their original values. +type stateUpdate struct { + originRoot common.Hash // hash of the state before applying mutation + root common.Hash // hash of the state after applying mutation + destructs map[common.Hash]struct{} // destructs contains the list of destructed accounts + accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding + accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding + storages map[common.Hash]map[common.Hash][]byte // storages stores mutated slots in 'prefix-zero-trimmed' RLP format + storagesOrigin map[common.Address]map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in 'prefix-zero-trimmed' RLP format + codes map[common.Address]contractCode // codes contains the set of dirty codes + nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes +} + +// empty returns a flag indicating the state transition is empty or not. +func (sc *stateUpdate) empty() bool { + return sc.originRoot == sc.root +} + +// newStateUpdate constructs a state update object, representing the differences +// between two states by performing state execution. It aggregates the given +// account deletions and account updates to form a comprehensive state update. +func newStateUpdate(originRoot common.Hash, root common.Hash, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate { + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + accountsOrigin = make(map[common.Address][]byte) + storages = make(map[common.Hash]map[common.Hash][]byte) + storagesOrigin = make(map[common.Address]map[common.Hash][]byte) + codes = make(map[common.Address]contractCode) + ) + // Due to the fact that some accounts could be destructed and resurrected + // within the same block, the deletions must be aggregated first. + for addrHash, op := range deletes { + addr := op.address + destructs[addrHash] = struct{}{} + accountsOrigin[addr] = op.origin + if len(op.storagesOrigin) > 0 { + storagesOrigin[addr] = op.storagesOrigin + } + } + // Aggregate account updates then. + for addrHash, op := range updates { + // Aggregate dirty contract codes if they are available. + addr := op.address + if op.code != nil { + codes[addr] = *op.code + } + // Aggregate the account changes. The original account value will only + // be tracked if it's not present yet. + accounts[addrHash] = op.data + if _, found := accountsOrigin[addr]; !found { + accountsOrigin[addr] = op.origin + } + // Aggregate the storage changes. The original storage slot value will + // only be tracked if it's not present yet. + if len(op.storages) > 0 { + storages[addrHash] = op.storages + } + if len(op.storagesOrigin) > 0 { + origin := storagesOrigin[addr] + if origin == nil { + storagesOrigin[addr] = op.storagesOrigin + continue + } + for key, slot := range op.storagesOrigin { + if _, found := origin[key]; !found { + origin[key] = slot + } + } + storagesOrigin[addr] = origin + } + } + return &stateUpdate{ + originRoot: types.TrieRootHash(originRoot), + root: types.TrieRootHash(root), + destructs: destructs, + accounts: accounts, + accountsOrigin: accountsOrigin, + storages: storages, + storagesOrigin: storagesOrigin, + codes: codes, + nodes: nodes, + } +} diff --git a/core/state/sync.go b/core/state/sync.go new file mode 100644 index 0000000..411b54e --- /dev/null +++ b/core/state/sync.go @@ -0,0 +1,55 @@ +// 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 state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// NewStateSync creates a new state trie download scheduler. +func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(keys [][]byte, leaf []byte) error, scheme string) *trie.Sync { + // Register the storage slot callback if the external callback is specified. + var onSlot func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error + if onLeaf != nil { + onSlot = func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error { + return onLeaf(keys, leaf) + } + } + // Register the account callback to connect the state trie and the storage + // trie belongs to the contract. + var syncer *trie.Sync + onAccount := func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error { + if onLeaf != nil { + if err := onLeaf(keys, leaf); err != nil { + return err + } + } + var obj types.StateAccount + if err := rlp.DecodeBytes(leaf, &obj); err != nil { + return err + } + syncer.AddSubTrie(obj.Root, path, parent, parentPath, onSlot) + syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), path, parent, parentPath) + return nil + } + syncer = trie.NewSync(root, database, onAccount, scheme) + return syncer +} diff --git a/core/state/sync_test.go b/core/state/sync_test.go new file mode 100644 index 0000000..b7039c9 --- /dev/null +++ b/core/state/sync_test.go @@ -0,0 +1,748 @@ +// 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 state + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/tracing" + "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/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "github.com/holiman/uint256" +) + +// testAccount is the data associated with an account used by the state tests. +type testAccount struct { + address common.Address + balance *uint256.Int + nonce uint64 + code []byte +} + +// makeTestState create a sample test state to test node-wise reconstruction. +func makeTestState(scheme string) (ethdb.Database, Database, *triedb.Database, common.Hash, []*testAccount) { + // Create an empty state + config := &triedb.Config{Preimages: true} + if scheme == rawdb.PathScheme { + config.PathDB = pathdb.Defaults + } else { + config.HashDB = hashdb.Defaults + } + db := rawdb.NewMemoryDatabase() + nodeDb := triedb.NewDatabase(db, config) + sdb := NewDatabaseWithNodeDB(db, nodeDb) + state, _ := New(types.EmptyRootHash, sdb, nil) + + // Fill it with some arbitrary data + var accounts []*testAccount + for i := byte(0); i < 96; i++ { + obj := state.getOrNewStateObject(common.BytesToAddress([]byte{i})) + acc := &testAccount{address: common.BytesToAddress([]byte{i})} + + obj.AddBalance(uint256.NewInt(uint64(11*i)), tracing.BalanceChangeUnspecified) + acc.balance = uint256.NewInt(uint64(11 * i)) + + obj.SetNonce(uint64(42 * i)) + acc.nonce = uint64(42 * i) + + if i%3 == 0 { + obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i}) + acc.code = []byte{i, i, i, i, i} + } + if i%5 == 0 { + for j := byte(0); j < 5; j++ { + hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}) + obj.SetState(hash, hash) + } + } + accounts = append(accounts, acc) + } + root, _ := state.Commit(0, false) + + // Return the generated state + 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, scheme string, root common.Hash, accounts []*testAccount) { + var config triedb.Config + if scheme == rawdb.PathScheme { + config.PathDB = pathdb.Defaults + } + // Check root availability and state contents + 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, scheme, root); err != nil { + t.Fatalf("inconsistent state trie at %x: %v", root, err) + } + for i, acc := range accounts { + if balance := state.GetBalance(acc.address); balance.Cmp(acc.balance) != 0 { + t.Errorf("account %d: balance mismatch: have %v, want %v", i, balance, acc.balance) + } + if nonce := state.GetNonce(acc.address); nonce != acc.nonce { + t.Errorf("account %d: nonce mismatch: have %v, want %v", i, nonce, acc.nonce) + } + if code := state.GetCode(acc.address); !bytes.Equal(code, acc.code) { + t.Errorf("account %d: code mismatch: have %x, want %x", i, code, acc.code) + } + } +} + +// checkStateConsistency checks that all data of a state root is present. +func checkStateConsistency(db ethdb.Database, scheme string, root common.Hash) error { + config := &triedb.Config{Preimages: true} + if scheme == rawdb.PathScheme { + config.PathDB = pathdb.Defaults + } + state, err := New(root, NewDatabaseWithConfig(db, config), nil) + if err != nil { + return err + } + it := newNodeIterator(state) + for it.Next() { + } + return it.Error +} + +// Tests that an empty state is not scheduled for syncing. +func TestEmptyStateSync(t *testing.T) { + dbA := triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil) + dbB := triedb.NewDatabase(rawdb.NewMemoryDatabase(), &triedb.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) + } +} + +// 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, rawdb.HashScheme) + testIterativeStateSync(t, 1, false, false, rawdb.PathScheme) +} +func TestIterativeStateSyncBatched(t *testing.T) { + testIterativeStateSync(t, 100, false, false, rawdb.HashScheme) + testIterativeStateSync(t, 100, false, false, rawdb.PathScheme) +} +func TestIterativeStateSyncIndividualFromDisk(t *testing.T) { + testIterativeStateSync(t, 1, true, false, rawdb.HashScheme) + testIterativeStateSync(t, 1, true, false, rawdb.PathScheme) +} +func TestIterativeStateSyncBatchedFromDisk(t *testing.T) { + testIterativeStateSync(t, 100, true, false, rawdb.HashScheme) + testIterativeStateSync(t, 100, true, false, rawdb.PathScheme) +} +func TestIterativeStateSyncIndividualByPath(t *testing.T) { + testIterativeStateSync(t, 1, false, true, rawdb.HashScheme) + testIterativeStateSync(t, 1, false, true, rawdb.PathScheme) +} +func TestIterativeStateSyncBatchedByPath(t *testing.T) { + 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). +type stateElement struct { + path string + hash common.Hash + code common.Hash + syncPath trie.SyncPath +} + +func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool, scheme string) { + // Create a random state to copy + srcDisk, srcDb, ndb, srcRoot, srcAccounts := makeTestState(scheme) + if commit { + ndb.Commit(srcRoot, false) + } + srcTrie, _ := trie.New(trie.StateTrieID(srcRoot), ndb) + + // Create a destination state and sync with the scheduler + dstDb := rawdb.NewMemoryDatabase() + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) + + var ( + nodeElements []stateElement + codeElements []stateElement + ) + paths, nodes, codes := sched.Missing(count) + for i := 0; i < len(paths); i++ { + nodeElements = append(nodeElements, stateElement{ + path: paths[i], + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(paths[i])), + }) + } + for i := 0; i < len(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 ( + nodeResults = make([]trie.NodeSyncResult, len(nodeElements)) + codeResults = make([]trie.CodeSyncResult, len(codeElements)) + ) + for i, element := range codeElements { + data, err := srcDb.ContractCode(common.Address{}, element.code) + if err != nil { + t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code) + } + codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data} + } + for i, node := range nodeElements { + if bypath { + if len(node.syncPath) == 1 { + data, _, err := srcTrie.GetNode(node.syncPath[0]) + if err != nil { + t.Fatalf("failed to retrieve node data for path %x: %v", node.syncPath[0], err) + } + nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data} + } else { + var acc types.StateAccount + if err := rlp.DecodeBytes(srcTrie.MustGet(node.syncPath[0]), &acc); err != nil { + 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, ndb) + if err != nil { + t.Fatalf("failed to retrieve storage trie for path %x: %v", node.syncPath[1], err) + } + data, _, err := stTrie.GetNode(node.syncPath[1]) + if err != nil { + t.Fatalf("failed to retrieve node data for path %x: %v", node.syncPath[1], err) + } + nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data} + } + } else { + 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)) + } + nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data} + } + } + for _, result := range codeResults { + if err := sched.ProcessCode(result); err != nil { + t.Errorf("failed to process result %v", err) + } + } + for _, result := range nodeResults { + if err := sched.ProcessNode(result); err != nil { + t.Errorf("failed to process result %v", err) + } + } + batch := dstDb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + paths, nodes, codes = sched.Missing(count) + nodeElements = nodeElements[:0] + for i := 0; i < len(paths); i++ { + nodeElements = append(nodeElements, stateElement{ + path: paths[i], + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(paths[i])), + }) + } + codeElements = codeElements[:0] + for i := 0; i < len(codes); i++ { + codeElements = append(codeElements, stateElement{ + code: codes[i], + }) + } + } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(srcDisk, dstDb) + + // Cross check that the two states are in sync + checkStateAccounts(t, dstDb, 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, ndb, srcRoot, srcAccounts := makeTestState(scheme) + + // Create a destination state and sync with the scheduler + dstDb := rawdb.NewMemoryDatabase() + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) + + var ( + nodeElements []stateElement + codeElements []stateElement + ) + paths, nodes, codes := sched.Missing(0) + for i := 0; i < len(paths); i++ { + nodeElements = append(nodeElements, stateElement{ + path: paths[i], + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(paths[i])), + }) + } + for i := 0; i < len(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 + var nodeProcessed int + var codeProcessed int + if len(codeElements) > 0 { + codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1) + for i, element := range codeElements[:len(codeResults)] { + data, err := srcDb.ContractCode(common.Address{}, element.code) + if err != nil { + t.Fatalf("failed to retrieve contract bytecode for %x", element.code) + } + codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data} + } + for _, result := range codeResults { + if err := sched.ProcessCode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + codeProcessed = len(codeResults) + } + if len(nodeElements) > 0 { + nodeResults := make([]trie.NodeSyncResult, len(nodeElements)/2+1) + for i, element := range nodeElements[:len(nodeResults)] { + 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) + } + nodeResults[i] = trie.NodeSyncResult{Path: element.path, Data: data} + } + for _, result := range nodeResults { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + nodeProcessed = len(nodeResults) + } + batch := dstDb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + paths, nodes, codes = sched.Missing(0) + nodeElements = nodeElements[nodeProcessed:] + for i := 0; i < len(paths); i++ { + nodeElements = append(nodeElements, stateElement{ + path: paths[i], + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(paths[i])), + }) + } + codeElements = codeElements[codeProcessed:] + for i := 0; i < len(codes); i++ { + codeElements = append(codeElements, stateElement{ + code: codes[i], + }) + } + } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(srcDisk, dstDb) + + // Cross check that the two states are in sync + checkStateAccounts(t, dstDb, 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, 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, scheme string) { + // Create a random state to copy + srcDisk, srcDb, ndb, srcRoot, srcAccounts := makeTestState(scheme) + + // Create a destination state and sync with the scheduler + dstDb := rawdb.NewMemoryDatabase() + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) + + nodeQueue := make(map[string]stateElement) + codeQueue := make(map[common.Hash]struct{}) + paths, nodes, codes := sched.Missing(count) + for i, path := range paths { + nodeQueue[path] = stateElement{ + path: path, + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(path)), + } + } + for _, hash := range codes { + codeQueue[hash] = struct{}{} + } + reader, err := ndb.Reader(srcRoot) + if err != nil { + t.Fatalf("state is not existent, %#x", srcRoot) + } + for len(nodeQueue)+len(codeQueue) > 0 { + // Fetch all the queued nodes in a random order + if len(codeQueue) > 0 { + results := make([]trie.CodeSyncResult, 0, len(codeQueue)) + for hash := range codeQueue { + data, err := srcDb.ContractCode(common.Address{}, hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x", hash) + } + results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) + } + for _, result := range results { + if err := sched.ProcessCode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + } + if len(nodeQueue) > 0 { + results := make([]trie.NodeSyncResult, 0, len(nodeQueue)) + for path, element := range nodeQueue { + owner, inner := trie.ResolvePath([]byte(element.path)) + data, err := 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) + } + results = append(results, trie.NodeSyncResult{Path: path, Data: data}) + } + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + } + batch := dstDb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + nodeQueue = make(map[string]stateElement) + codeQueue = make(map[common.Hash]struct{}) + paths, nodes, codes := sched.Missing(count) + for i, path := range paths { + nodeQueue[path] = stateElement{ + path: path, + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(path)), + } + } + for _, hash := range codes { + codeQueue[hash] = struct{}{} + } + } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(srcDisk, dstDb) + + // Cross check that the two states are in sync + checkStateAccounts(t, dstDb, 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, ndb, srcRoot, srcAccounts := makeTestState(scheme) + + // Create a destination state and sync with the scheduler + dstDb := rawdb.NewMemoryDatabase() + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) + + nodeQueue := make(map[string]stateElement) + codeQueue := make(map[common.Hash]struct{}) + paths, nodes, codes := sched.Missing(0) + for i, path := range paths { + nodeQueue[path] = stateElement{ + path: path, + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(path)), + } + } + 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 { + results := make([]trie.CodeSyncResult, 0, len(codeQueue)/2+1) + for hash := range codeQueue { + delete(codeQueue, hash) + + data, err := srcDb.ContractCode(common.Address{}, hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x", hash) + } + results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) + + if len(results) >= cap(results) { + break + } + } + for _, result := range results { + if err := sched.ProcessCode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + } + if len(nodeQueue) > 0 { + results := make([]trie.NodeSyncResult, 0, len(nodeQueue)/2+1) + for path, element := range nodeQueue { + delete(nodeQueue, path) + + 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) + } + results = append(results, trie.NodeSyncResult{Path: path, Data: data}) + + if len(results) >= cap(results) { + break + } + } + // Feed the retrieved results back and queue new tasks + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + } + batch := dstDb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + paths, nodes, codes := sched.Missing(0) + for i, path := range paths { + nodeQueue[path] = stateElement{ + path: path, + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(path)), + } + } + for _, hash := range codes { + codeQueue[hash] = struct{}{} + } + } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(srcDisk, dstDb) + + // Cross check that the two states are in sync + checkStateAccounts(t, dstDb, 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, ndb, srcRoot, srcAccounts := makeTestState(scheme) + + // isCodeLookup to save some hashing + var isCode = make(map[common.Hash]struct{}) + for _, acc := range srcAccounts { + if len(acc.code) > 0 { + isCode[crypto.Keccak256Hash(acc.code)] = struct{}{} + } + } + isCode[types.EmptyCodeHash] = struct{}{} + + // Create a destination state and sync with the scheduler + dstDb := rawdb.NewMemoryDatabase() + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) + + var ( + addedCodes []common.Hash + addedPaths []string + addedHashes []common.Hash + ) + reader, err := ndb.Reader(srcRoot) + if err != nil { + t.Fatalf("state is not available %x", srcRoot) + } + nodeQueue := make(map[string]stateElement) + codeQueue := make(map[common.Hash]struct{}) + paths, nodes, codes := sched.Missing(1) + for i, path := range paths { + nodeQueue[path] = stateElement{ + path: path, + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(path)), + } + } + for _, hash := range codes { + codeQueue[hash] = struct{}{} + } + for len(nodeQueue)+len(codeQueue) > 0 { + // Fetch a batch of state nodes + if len(codeQueue) > 0 { + results := make([]trie.CodeSyncResult, 0, len(codeQueue)) + for hash := range codeQueue { + data, err := srcDb.ContractCode(common.Address{}, hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x", hash) + } + results = append(results, trie.CodeSyncResult{Hash: hash, Data: data}) + addedCodes = append(addedCodes, hash) + } + // Process each of the state nodes + for _, result := range results { + if err := sched.ProcessCode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + } + if len(nodeQueue) > 0 { + results := make([]trie.NodeSyncResult, 0, len(nodeQueue)) + for path, element := range nodeQueue { + owner, inner := trie.ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x", element.hash) + } + results = append(results, trie.NodeSyncResult{Path: path, Data: data}) + + if element.hash != srcRoot { + addedPaths = append(addedPaths, element.path) + addedHashes = append(addedHashes, element.hash) + } + } + // Process each of the state nodes + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + } + batch := dstDb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + // Fetch the next batch to retrieve + nodeQueue = make(map[string]stateElement) + codeQueue = make(map[common.Hash]struct{}) + paths, nodes, codes := sched.Missing(1) + for i, path := range paths { + nodeQueue[path] = stateElement{ + path: path, + hash: nodes[i], + syncPath: trie.NewSyncPath([]byte(path)), + } + } + for _, hash := range codes { + codeQueue[hash] = struct{}{} + } + } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(db, dstDb) + + // Sanity check that removing any node from the database is detected + for _, node := range addedCodes { + val := rawdb.ReadCode(dstDb, node) + rawdb.DeleteCode(dstDb, node) + if err := checkStateConsistency(dstDb, ndb.Scheme(), srcRoot); err == nil { + t.Errorf("trie inconsistency not caught, missing: %x", node) + } + rawdb.WriteCode(dstDb, node, val) + } + for i, path := range addedPaths { + owner, inner := trie.ResolvePath([]byte(path)) + hash := addedHashes[i] + val := rawdb.ReadTrieNode(dstDb, owner, inner, hash, scheme) + if val == nil { + t.Error("missing trie node") + } + rawdb.DeleteTrieNode(dstDb, owner, inner, hash, scheme) + if err := checkStateConsistency(dstDb, scheme, srcRoot); err == nil { + t.Errorf("trie inconsistency not caught, missing: %v", path) + } + rawdb.WriteTrieNode(dstDb, owner, inner, hash, val, scheme) + } +} + +func copyPreimages(srcDb, dstDb ethdb.Database) { + it := srcDb.NewIterator(rawdb.PreimagePrefix, nil) + defer it.Release() + + preimages := make(map[common.Hash][]byte) + for it.Next() { + hash := it.Key()[len(rawdb.PreimagePrefix):] + preimages[common.BytesToHash(hash)] = common.CopyBytes(it.Value()) + } + rawdb.WritePreimages(dstDb, preimages) +} diff --git a/core/state/transient_storage.go b/core/state/transient_storage.go new file mode 100644 index 0000000..e63db39 --- /dev/null +++ b/core/state/transient_storage.go @@ -0,0 +1,92 @@ +// 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 state + +import ( + "fmt" + "slices" + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +// transientStorage is a representation of EIP-1153 "Transient Storage". +type transientStorage map[common.Address]Storage + +// newTransientStorage creates a new instance of a transientStorage. +func newTransientStorage() transientStorage { + return make(transientStorage) +} + +// Set sets the transient-storage `value` for `key` at the given `addr`. +func (t transientStorage) Set(addr common.Address, key, value common.Hash) { + if value == (common.Hash{}) { // this is a 'delete' + if _, ok := t[addr]; ok { + delete(t[addr], key) + if len(t[addr]) == 0 { + delete(t, addr) + } + } + } else { + if _, ok := t[addr]; !ok { + t[addr] = make(Storage) + } + t[addr][key] = value + } +} + +// Get gets the transient storage for `key` at the given `addr`. +func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash { + val, ok := t[addr] + if !ok { + return common.Hash{} + } + return val[key] +} + +// Copy does a deep copy of the transientStorage +func (t transientStorage) Copy() transientStorage { + storage := make(transientStorage) + for key, value := range t { + storage[key] = value.Copy() + } + return storage +} + +// PrettyPrint prints the contents of the access list in a human-readable form +func (t transientStorage) PrettyPrint() string { + out := new(strings.Builder) + var sortedAddrs []common.Address + for addr := range t { + sortedAddrs = append(sortedAddrs, addr) + slices.SortFunc(sortedAddrs, common.Address.Cmp) + } + + for _, addr := range sortedAddrs { + fmt.Fprintf(out, "%#x:", addr) + var sortedKeys []common.Hash + storage := t[addr] + for key := range storage { + sortedKeys = append(sortedKeys, key) + } + slices.SortFunc(sortedKeys, common.Hash.Cmp) + for _, key := range sortedKeys { + fmt.Fprintf(out, " %X : %X\n", key, storage[key]) + } + } + return out.String() +} diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go new file mode 100644 index 0000000..491b380 --- /dev/null +++ b/core/state/trie_prefetcher.go @@ -0,0 +1,403 @@ +// 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 state + +import ( + "errors" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + // triePrefetchMetricsPrefix is the prefix under which to publish the metrics. + triePrefetchMetricsPrefix = "trie/prefetch/" + + // errTerminated is returned if a fetcher is attempted to be operated after it + // has already terminated. + errTerminated = errors.New("fetcher is already terminated") +) + +// triePrefetcher is an active prefetcher, which receives accounts or storage +// items and does trie-loading of them. The goal is to get as much useful content +// into the caches as possible. +// +// Note, the prefetcher's API is not thread safe. +type triePrefetcher struct { + db Database // Database to fetch trie nodes through + root common.Hash // Root hash of the account trie for metrics + fetchers map[string]*subfetcher // Subfetchers for each trie + term chan struct{} // Channel to signal interruption + noreads bool // Whether to ignore state-read-only prefetch requests + + deliveryMissMeter metrics.Meter + + accountLoadReadMeter metrics.Meter + accountLoadWriteMeter metrics.Meter + accountDupReadMeter metrics.Meter + accountDupWriteMeter metrics.Meter + accountDupCrossMeter metrics.Meter + accountWasteMeter metrics.Meter + + storageLoadReadMeter metrics.Meter + storageLoadWriteMeter metrics.Meter + storageDupReadMeter metrics.Meter + storageDupWriteMeter metrics.Meter + storageDupCrossMeter metrics.Meter + storageWasteMeter metrics.Meter +} + +func newTriePrefetcher(db Database, root common.Hash, namespace string, noreads bool) *triePrefetcher { + prefix := triePrefetchMetricsPrefix + namespace + return &triePrefetcher{ + db: db, + root: root, + fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map + term: make(chan struct{}), + noreads: noreads, + + deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil), + + accountLoadReadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load/read", nil), + accountLoadWriteMeter: metrics.GetOrRegisterMeter(prefix+"/account/load/write", nil), + accountDupReadMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/read", nil), + accountDupWriteMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/write", nil), + accountDupCrossMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup/cross", nil), + accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil), + + storageLoadReadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load/read", nil), + storageLoadWriteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load/write", nil), + storageDupReadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/read", nil), + storageDupWriteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/write", nil), + storageDupCrossMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup/cross", nil), + storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), + } +} + +// terminate iterates over all the subfetchers and issues a termination request +// to all of them. Depending on the async parameter, the method will either block +// until all subfetchers spin down, or return immediately. +func (p *triePrefetcher) terminate(async bool) { + // Short circuit if the fetcher is already closed + select { + case <-p.term: + return + default: + } + // Terminate all sub-fetchers, sync or async, depending on the request + for _, fetcher := range p.fetchers { + fetcher.terminate(async) + } + close(p.term) +} + +// report aggregates the pre-fetching and usage metrics and reports them. +func (p *triePrefetcher) report() { + if !metrics.Enabled { + return + } + for _, fetcher := range p.fetchers { + fetcher.wait() // ensure the fetcher's idle before poking in its internals + + if fetcher.root == p.root { + p.accountLoadReadMeter.Mark(int64(len(fetcher.seenRead))) + p.accountLoadWriteMeter.Mark(int64(len(fetcher.seenWrite))) + + p.accountDupReadMeter.Mark(int64(fetcher.dupsRead)) + p.accountDupWriteMeter.Mark(int64(fetcher.dupsWrite)) + p.accountDupCrossMeter.Mark(int64(fetcher.dupsCross)) + + for _, key := range fetcher.used { + delete(fetcher.seenRead, string(key)) + delete(fetcher.seenWrite, string(key)) + } + p.accountWasteMeter.Mark(int64(len(fetcher.seenRead) + len(fetcher.seenWrite))) + } else { + p.storageLoadReadMeter.Mark(int64(len(fetcher.seenRead))) + p.storageLoadWriteMeter.Mark(int64(len(fetcher.seenWrite))) + + p.storageDupReadMeter.Mark(int64(fetcher.dupsRead)) + p.storageDupWriteMeter.Mark(int64(fetcher.dupsWrite)) + p.storageDupCrossMeter.Mark(int64(fetcher.dupsCross)) + + for _, key := range fetcher.used { + delete(fetcher.seenRead, string(key)) + delete(fetcher.seenWrite, string(key)) + } + p.storageWasteMeter.Mark(int64(len(fetcher.seenRead) + len(fetcher.seenWrite))) + } + } +} + +// prefetch schedules a batch of trie items to prefetch. After the prefetcher is +// closed, all the following tasks scheduled will not be executed and an error +// will be returned. +// +// prefetch is called from two locations: +// +// 1. Finalize of the state-objects storage roots. This happens at the end +// of every transaction, meaning that if several transactions touches +// upon the same contract, the parameters invoking this method may be +// repeated. +// 2. Finalize of the main account trie. This happens only once per block. +func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte, read bool) error { + // If the state item is only being read, but reads are disabled, return + if read && p.noreads { + return nil + } + // Ensure the subfetcher is still alive + select { + case <-p.term: + return errTerminated + default: + } + id := p.trieID(owner, root) + fetcher := p.fetchers[id] + if fetcher == nil { + fetcher = newSubfetcher(p.db, p.root, owner, root, addr) + p.fetchers[id] = fetcher + } + return fetcher.schedule(keys, read) +} + +// trie returns the trie matching the root hash, blocking until the fetcher of +// the given trie terminates. If no fetcher exists for the request, nil will be +// returned. +func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie { + // Bail if no trie was prefetched for this root + fetcher := p.fetchers[p.trieID(owner, root)] + if fetcher == nil { + log.Error("Prefetcher missed to load trie", "owner", owner, "root", root) + p.deliveryMissMeter.Mark(1) + return nil + } + // Subfetcher exists, retrieve its trie + return fetcher.peek() +} + +// used marks a batch of state items used to allow creating statistics as to +// how useful or wasteful the fetcher is. +func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) { + if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil { + fetcher.wait() // ensure the fetcher's idle before poking in its internals + fetcher.used = used + } +} + +// trieID returns an unique trie identifier consists the trie owner and root hash. +func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string { + 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 +// single trie. It is spawned when a new root is encountered and lives until the +// main prefetcher is paused and either all requested items are processed or if +// the trie being worked on is retrieved from the prefetcher. +type subfetcher struct { + db Database // Database to load trie nodes through + state common.Hash // Root hash of the state to prefetch + owner common.Hash // Owner of the trie, usually account hash + root common.Hash // Root hash of the trie to prefetch + addr common.Address // Address of the account that the trie belongs to + trie Trie // Trie being populated with nodes + + tasks []*subfetcherTask // Items queued up for retrieval + lock sync.Mutex // Lock protecting the task queue + + wake chan struct{} // Wake channel if a new task is scheduled + stop chan struct{} // Channel to interrupt processing + term chan struct{} // Channel to signal interruption + + seenRead map[string]struct{} // Tracks the entries already loaded via read operations + seenWrite map[string]struct{} // Tracks the entries already loaded via write operations + + dupsRead int // Number of duplicate preload tasks via reads only + dupsWrite int // Number of duplicate preload tasks via writes only + dupsCross int // Number of duplicate preload tasks via read-write-crosses + + used [][]byte // Tracks the entries used in the end +} + +// subfetcherTask is a trie path to prefetch, tagged with whether it originates +// from a read or a write request. +type subfetcherTask struct { + read bool + key []byte +} + +// newSubfetcher creates a goroutine to prefetch state items belonging to a +// particular root hash. +func newSubfetcher(db Database, state common.Hash, owner common.Hash, root common.Hash, addr common.Address) *subfetcher { + sf := &subfetcher{ + db: db, + state: state, + owner: owner, + root: root, + addr: addr, + wake: make(chan struct{}, 1), + stop: make(chan struct{}), + term: make(chan struct{}), + seenRead: make(map[string]struct{}), + seenWrite: make(map[string]struct{}), + } + go sf.loop() + return sf +} + +// schedule adds a batch of trie keys to the queue to prefetch. +func (sf *subfetcher) schedule(keys [][]byte, read bool) error { + // Ensure the subfetcher is still alive + select { + case <-sf.term: + return errTerminated + default: + } + // Append the tasks to the current queue + sf.lock.Lock() + for _, key := range keys { + key := key // closure for the append below + sf.tasks = append(sf.tasks, &subfetcherTask{read: read, key: key}) + } + sf.lock.Unlock() + + // Notify the background thread to execute scheduled tasks + select { + case sf.wake <- struct{}{}: + // Wake signal sent + default: + // Wake signal not sent as a previous one is already queued + } + return nil +} + +// wait blocks until the subfetcher terminates. This method is used to block on +// an async termination before accessing internal fields from the fetcher. +func (sf *subfetcher) wait() { + <-sf.term +} + +// peek retrieves the fetcher's trie, populated with any pre-fetched data. The +// returned trie will be a shallow copy, so modifying it will break subsequent +// peeks for the original data. The method will block until all the scheduled +// data has been loaded and the fethcer terminated. +func (sf *subfetcher) peek() Trie { + // Block until the fetcher terminates, then retrieve the trie + sf.wait() + return sf.trie +} + +// terminate requests the subfetcher to stop accepting new tasks and spin down +// as soon as everything is loaded. Depending on the async parameter, the method +// will either block until all disk loads finish or return immediately. +func (sf *subfetcher) terminate(async bool) { + select { + case <-sf.stop: + default: + close(sf.stop) + } + if async { + return + } + <-sf.term +} + +// loop loads newly-scheduled trie tasks as they are received and loads them, stopping +// when requested. +func (sf *subfetcher) loop() { + // No matter how the loop stops, signal anyone waiting that it's terminated + defer close(sf.term) + + // Start by opening the trie and stop processing if it fails + if sf.owner == (common.Hash{}) { + trie, err := sf.db.OpenTrie(sf.root) + if err != nil { + log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) + return + } + sf.trie = trie + } else { + 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 + } + sf.trie = trie + } + // Trie opened successfully, keep prefetching items + for { + select { + case <-sf.wake: + // Execute all remaining tasks in a single run + sf.lock.Lock() + tasks := sf.tasks + sf.tasks = nil + sf.lock.Unlock() + + for _, task := range tasks { + key := string(task.key) + if task.read { + if _, ok := sf.seenRead[key]; ok { + sf.dupsRead++ + continue + } + if _, ok := sf.seenWrite[key]; ok { + sf.dupsCross++ + continue + } + } else { + if _, ok := sf.seenRead[key]; ok { + sf.dupsCross++ + continue + } + if _, ok := sf.seenWrite[key]; ok { + sf.dupsWrite++ + continue + } + } + if len(task.key) == common.AddressLength { + sf.trie.GetAccount(common.BytesToAddress(task.key)) + } else { + sf.trie.GetStorage(sf.addr, task.key) + } + if task.read { + sf.seenRead[key] = struct{}{} + } else { + sf.seenWrite[key] = struct{}{} + } + } + + case <-sf.stop: + // Termination is requested, abort if no more tasks are pending. If + // there are some, exhaust them first. + sf.lock.Lock() + done := sf.tasks == nil + sf.lock.Unlock() + + if done { + return + } + // Some tasks are pending, loop and pick them up (that wake branch + // will be selected eventually, whilst stop remains closed to this + // branch will also run afterwards). + } + } +} diff --git a/core/state/trie_prefetcher_test.go b/core/state/trie_prefetcher_test.go new file mode 100644 index 0000000..8f01acd --- /dev/null +++ b/core/state/trie_prefetcher_test.go @@ -0,0 +1,64 @@ +// 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 state + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +func filledStateDB() *StateDB { + state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) + + // Create an account and check if the retrieved balance is correct + addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") + skey := common.HexToHash("aaa") + sval := common.HexToHash("bbb") + + state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie + state.SetCode(addr, []byte("hello")) // Change an external metadata + state.SetState(addr, skey, sval) // Change the storage trie + for i := 0; i < 100; i++ { + sk := common.BigToHash(big.NewInt(int64(i))) + state.SetState(addr, sk, sk) // Change the storage trie + } + return state +} + +func TestUseAfterTerminate(t *testing.T) { + db := filledStateDB() + prefetcher := newTriePrefetcher(db.db, db.originalRoot, "", true) + skey := common.HexToHash("aaa") + + if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}, false); err != nil { + t.Errorf("Prefetch failed before terminate: %v", err) + } + prefetcher.terminate(false) + + if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}, false); err == nil { + t.Errorf("Prefetch succeeded after terminate: %v", err) + } + if tr := prefetcher.trie(common.Hash{}, db.originalRoot); tr == nil { + t.Errorf("Prefetcher returned nil trie after terminate") + } +} diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go new file mode 100644 index 0000000..31405fa --- /dev/null +++ b/core/state_prefetcher.go @@ -0,0 +1,91 @@ +// 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 core + +import ( + "sync/atomic" + + "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" +) + +// statePrefetcher is a basic Prefetcher, which blindly executes a block on top +// of an arbitrary state with the goal of prefetching potentially useful state +// data from disk before the main block processor start executing. +type statePrefetcher struct { + config *params.ChainConfig // Chain configuration options + chain *HeaderChain // Canonical block chain +} + +// newStatePrefetcher initialises a new statePrefetcher. +func newStatePrefetcher(config *params.ChainConfig, chain *HeaderChain) *statePrefetcher { + return &statePrefetcher{ + config: config, + chain: chain, + } +} + +// Prefetch processes the state changes according to the Ethereum rules by running +// the transaction messages using the statedb, but any changes are discarded. The +// only goal is to pre-cache transaction signatures and state trie nodes. +func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) { + var ( + header = block.Header() + gaspool = new(GasPool).AddGas(block.GasLimit()) + blockContext = NewEVMBlockContext(header, p.chain, nil) + evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + signer = types.MakeSigner(p.config, header.Number, header.Time) + ) + // Iterate over and process the individual transactions + byzantium := p.config.IsByzantium(block.Number()) + for i, tx := range block.Transactions() { + // If block precaching was interrupted, abort + if interrupt != nil && interrupt.Load() { + return + } + // Convert the transaction into an executable message and pre-cache its sender + msg, err := TransactionToMessage(tx, signer, header.BaseFee) + if err != nil { + return // Also invalid block, bail out + } + statedb.SetTxContext(tx.Hash(), i) + if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { + return // Ugh, something went horribly wrong, bail out + } + // If we're pre-byzantium, pre-load trie nodes for the intermediate root + if !byzantium { + statedb.IntermediateRoot(true) + } + } + // If were post-byzantium, pre-load trie nodes for the final root hash + if byzantium { + statedb.IntermediateRoot(true) + } +} + +// precacheTransaction attempts to apply a transaction to the given state database +// and uses the input parameters for its environment. The goal is not to execute +// the transaction successfully, rather to warm up touched data slots. +func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { + // Update the evm with the new transaction context. + evm.Reset(NewEVMTxContext(msg), statedb) + // Add addresses to access list if applicable + _, err := ApplyMessage(evm, msg, gaspool) + return err +} diff --git a/core/state_processor.go b/core/state_processor.go new file mode 100644 index 0000000..c21f644 --- /dev/null +++ b/core/state_processor.go @@ -0,0 +1,209 @@ +// 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 core + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// StateProcessor is a basic Processor, which takes care of transitioning +// state from one point to another. +// +// StateProcessor implements Processor. +type StateProcessor struct { + config *params.ChainConfig // Chain configuration options + chain *HeaderChain // Canonical header chain +} + +// NewStateProcessor initialises a new StateProcessor. +func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StateProcessor { + return &StateProcessor{ + config: config, + chain: chain, + } +} + +// Process processes the state changes according to the Ethereum rules by running +// the transaction messages using the statedb and applying any rewards to both +// the processor (coinbase) and any included uncles. +// +// Process returns the receipts and logs accumulated during the process and +// returns the amount of gas that was used in the process. If any of the +// transactions failed to execute due to insufficient gas it will return an error. +func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { + var ( + receipts types.Receipts + usedGas = new(uint64) + header = block.Header() + blockHash = block.Hash() + blockNumber = block.Number() + allLogs []*types.Log + gp = new(GasPool).AddGas(block.GasLimit()) + ) + + // Mutate the block and state according to any hard-fork specs + if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { + misc.ApplyDAOHardFork(statedb) + } + var ( + context vm.BlockContext + signer = types.MakeSigner(p.config, header.Number, header.Time) + ) + context = NewEVMBlockContext(header, p.chain, nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) + 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) + if err != nil { + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + statedb.SetTxContext(tx.Hash(), i) + + receipt, err := ApplyTransactionWithEVM(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) + if err != nil { + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) + } + // Fail if Shanghai not enabled and len(withdrawals) is non-zero. + withdrawals := block.Withdrawals() + if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number(), block.Time()) { + return nil, nil, 0, errors.New("withdrawals before shanghai") + } + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) + p.chain.engine.Finalize(p.chain, header, statedb, block.Body()) + + return receipts, allLogs, *usedGas, nil +} + +// ApplyTransactionWithEVM attempts to apply a transaction to the given state database +// and uses the input parameters for its environment similar to ApplyTransaction. However, +// this method takes an already created EVM instance as input. +func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnTxStart != nil { + evm.Config.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) + if evm.Config.Tracer.OnTxEnd != nil { + defer func() { + evm.Config.Tracer.OnTxEnd(receipt, err) + }() + } + } + // Create a new context to be used in the EVM environment. + txContext := NewEVMTxContext(msg) + evm.Reset(txContext, statedb) + + // Apply the transaction to the current state (included in the env). + result, err := ApplyMessage(evm, msg, gp) + if err != nil { + return nil, err + } + + // Update the state with pending changes. + var root []byte + if config.IsByzantium(blockNumber) { + statedb.Finalise(true) + } else { + root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes() + } + *usedGas += result.UsedGas + + // Create a new receipt for the transaction, storing the intermediate root and gas used + // by the tx. + receipt = &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas} + if result.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + 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()) + } + + // Set the receipt logs and create the bloom filter. + receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + receipt.BlockHash = blockHash + receipt.BlockNumber = blockNumber + receipt.TransactionIndex = uint(statedb.TxIndex()) + return receipt, err +} + +// ApplyTransaction attempts to apply a transaction to the given state database +// and uses the input parameters for its environment. It returns the receipt +// for the transaction, gas used and an error if the transaction failed, +// indicating the block was invalid. +func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { + msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee) + if err != nil { + return nil, err + } + // Create a new context to be used in the EVM environment + blockContext := NewEVMBlockContext(header, bc, author) + txContext := NewEVMTxContext(msg) + vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) + return ApplyTransactionWithEVM(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 vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallStart != nil { + vmenv.Config.Tracer.OnSystemCallStart() + } + if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallEnd != nil { + defer vmenv.Config.Tracer.OnSystemCallEnd() + } + + // 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.BeaconRootsAddress, + Data: beaconRoot[:], + } + vmenv.Reset(NewEVMTxContext(msg), statedb) + statedb.AddAddressToAccessList(params.BeaconRootsAddress) + _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) + statedb.Finalise(true) +} diff --git a/core/state_processor_test.go b/core/state_processor_test.go new file mode 100644 index 0000000..af4d29b --- /dev/null +++ b/core/state_processor_test.go @@ -0,0 +1,530 @@ +// 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 core + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" +) + +func u64(val uint64) *uint64 { return &val } + +// TestStateProcessorErrors tests the output from the 'core' errors +// as defined in core/error.go. These errors are generated when the +// blockchain imports bad blocks, meaning blocks which have valid headers but +// contain invalid transactions +func TestStateProcessorErrors(t *testing.T) { + var ( + config = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + ShanghaiTime: new(uint64), + CancunTime: new(uint64), + } + signer = types.LatestSigner(config) + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("0202020202020202020202020202020202020202020202020202002020202020") + ) + var makeTx = func(key *ecdsa.PrivateKey, nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, key) + return tx + } + var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction { + tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ + Nonce: nonce, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Gas: gasLimit, + To: &to, + Value: big.NewInt(0), + }), signer, key1) + return tx + } + var mkDynamicCreationTx = func(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction { + tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ + Nonce: nonce, + GasTipCap: gasTipCap, + GasFeeCap: gasFeeCap, + Gas: gasLimit, + Value: big.NewInt(0), + Data: data, + }), signer, key1) + return tx + } + 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), + GasFeeCap: uint256.MustFromBig(gasFeeCap), + Gas: gasLimit, + To: to, + BlobHashes: hashes, + BlobFeeCap: uint256.MustFromBig(blobGasFeeCap), + Value: new(uint256.Int), + }), signer, key1) + if err != nil { + t.Fatal(err) + } + return tx + } + + { // Tests against a 'recent' chain definition + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: config, + Alloc: types.GenesisAlloc{ + common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): types.Account{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + common.HexToAddress("0xfd0810DD14796680f72adf1a371963d0745BCc64"): types.Account{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: math.MaxUint64, + }, + }, + } + blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) + tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} + ) + + defer blockchain.Stop() + bigNumber := new(big.Int).SetBytes(common.MaxHash.Bytes()) + tooBigNumber := new(big.Int).Set(bigNumber) + tooBigNumber.Add(tooBigNumber, common.Big1) + for i, tt := range []struct { + txs []*types.Transaction + want string + }{ + { // ErrNonceTooLow + txs: []*types.Transaction{ + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + }, + want: "could not apply tx 1 [0x0026256b3939ed97e2c4a6f3fce8ecf83bdcfa6d507c47838c308a1fb0436f62]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", + }, + { // ErrNonceTooHigh + txs: []*types.Transaction{ + makeTx(key1, 100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0xdebad714ca7f363bd0d8121c4518ad48fa469ca81b0a081be3d10c17460f751b]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0", + }, + { // ErrNonceMax + txs: []*types.Transaction{ + makeTx(key2, math.MaxUint64, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0x84ea18d60eb2bb3b040e3add0eb72f757727122cc257dd858c67cb6591a85986]: nonce has max value: address 0xfd0810DD14796680f72adf1a371963d0745BCc64, nonce: 18446744073709551615", + }, + { // ErrGasLimitReached + txs: []*types.Transaction{ + makeTx(key1, 0, common.Address{}, big.NewInt(0), 21000000, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0xbd49d8dadfd47fb846986695f7d4da3f7b2c48c8da82dbc211a26eb124883de9]: gas limit reached", + }, + { // ErrInsufficientFundsForTransfer + txs: []*types.Transaction{ + makeTx(key1, 0, common.Address{}, big.NewInt(1000000000000000000), params.TxGas, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0x98c796b470f7fcab40aaef5c965a602b0238e1034cce6fb73823042dd0638d74]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 1000018375000000000", + }, + { // ErrInsufficientFunds + txs: []*types.Transaction{ + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(900000000000000000), nil), + }, + want: "could not apply tx 0 [0x4a69690c4b0cd85e64d0d9ea06302455b01e10a83db964d60281739752003440]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 18900000000000000000000", + }, + // ErrGasUintOverflow + // One missing 'core' error is ErrGasUintOverflow: "gas uint64 overflow", + // In order to trigger that one, we'd have to allocate a _huge_ chunk of data, such that the + // multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment + { // ErrIntrinsicGas + txs: []*types.Transaction{ + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0xcf3b049a0b516cb4f9274b3e2a264359e2ba53b2fb64b7bda2c634d5c9d01fca]: intrinsic gas too low: have 20000, want 21000", + }, + { // ErrGasLimitReached + txs: []*types.Transaction{ + makeTx(key1, 0, common.Address{}, big.NewInt(0), params.TxGas*1000, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0xbd49d8dadfd47fb846986695f7d4da3f7b2c48c8da82dbc211a26eb124883de9]: gas limit reached", + }, + { // ErrFeeCapTooLow + 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", + }, + { // ErrTipVeryHigh + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas, tooBigNumber, big.NewInt(1)), + }, + want: "could not apply tx 0 [0x15b8391b9981f266b32f3ab7da564bbeb3d6c21628364ea9b32a21139f89f712]: max priority fee per gas higher than 2^256-1: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxPriorityFeePerGas bit length: 257", + }, + { // ErrFeeCapVeryHigh + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(1), tooBigNumber), + }, + want: "could not apply tx 0 [0x48bc299b83fdb345c57478f239e89814bb3063eb4e4b49f3b6057a69255c16bd]: max fee per gas higher than 2^256-1: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas bit length: 257", + }, + { // ErrTipAboveFeeCap + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(2), big.NewInt(1)), + }, + want: "could not apply tx 0 [0xf987a31ff0c71895780a7612f965a0c8b056deb54e020bb44fa478092f14c9b4]: max priority fee per gas higher than max fee per gas: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxPriorityFeePerGas: 2, maxFeePerGas: 1", + }, + { // ErrInsufficientFunds + // Available balance: 1000000000000000000 + // Effective cost: 18375000021000 + // FeeCap * gas: 1050000000000000000 + // This test is designed to have the effective cost be covered by the balance, but + // the extended requirement on FeeCap*gas < balance to fail + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(50000000000000)), + }, + want: "could not apply tx 0 [0x413603cd096a87f41b1660d3ed3e27d62e1da78eac138961c0a1314ed43bd129]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 1050000000000000000", + }, + { // Another ErrInsufficientFunds, this one to ensure that feecap/tip of max u256 is allowed + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas, bigNumber, bigNumber), + }, + want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 required balance exceeds 256 bits", + }, + { // ErrMaxInitCodeSizeExceeded + txs: []*types.Transaction{ + mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]), + }, + want: "could not apply tx 0 [0xd491405f06c92d118dd3208376fcee18a57c54bc52063ee4a26b1cf296857c25]: max initcode size exceeded: code size 49153 limit 49152", + }, + { // ErrIntrinsicGas: Not enough gas to cover init code + txs: []*types.Transaction{ + mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.InitialBaseFee), make([]byte, 320)), + }, + want: "could not apply tx 0 [0xfd49536a9b323769d8472fcb3ebb3689b707a349379baee3e2ee3fe7baae06a1]: intrinsic gas too low: have 54299, want 54300", + }, + { // ErrBlobFeeCapTooLow + txs: []*types.Transaction{ + mkBlobTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(1), 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", + }, + } { + block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) + _, err := blockchain.InsertChain(types.Blocks{block}) + if err == nil { + t.Fatal("block imported without errors") + } + if have, want := err.Error(), tt.want; have != want { + t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) + } + } + } + + // ErrTxTypeNotSupported, For this, we need an older chain + { + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + }, + Alloc: types.GenesisAlloc{ + common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): types.Account{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + }, + } + blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ) + defer blockchain.Stop() + for i, tt := range []struct { + txs []*types.Transaction + want string + }{ + { // ErrTxTypeNotSupported + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), + }, + want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: transaction type not supported", + }, + } { + block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) + _, err := blockchain.InsertChain(types.Blocks{block}) + if err == nil { + t.Fatal("block imported without errors") + } + if have, want := err.Error(), tt.want; have != want { + t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) + } + } + } + + // ErrSenderNoEOA, for this we need the sender to have contract code + { + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: config, + Alloc: types.GenesisAlloc{ + common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): types.Account{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + Code: common.FromHex("0xB0B0FACE"), + }, + }, + } + blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) + ) + defer blockchain.Stop() + for i, tt := range []struct { + txs []*types.Transaction + want string + }{ + { // ErrSenderNoEOA + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), + }, + want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1", + }, + } { + block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) + _, err := blockchain.InsertChain(types.Blocks{block}) + if err == nil { + t.Fatal("block imported without errors") + } + if have, want := err.Error(), tt.want; have != want { + t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) + } + } + } +} + +// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be +// valid, and no proper post-state can be made. But from the perspective of the blockchain, the block is sufficiently +// valid to be considered for import: +// - valid pow (fake), ancestry, difficulty, gaslimit etc +func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block { + difficulty := big.NewInt(0) + if !config.TerminalTotalDifficultyPassed { + fakeChainReader := newChainMaker(nil, config, engine) + difficulty = engine.CalcDifficulty(fakeChainReader, parent.Time()+10, &types.Header{ + Number: parent.Number(), + Time: parent.Time(), + Difficulty: parent.Difficulty(), + UncleHash: parent.UncleHash(), + }) + } + + header := &types.Header{ + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: difficulty, + GasLimit: parent.GasLimit(), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: parent.Time() + 10, + UncleHash: types.EmptyUncleHash, + } + if config.IsLondon(header.Number) { + header.BaseFee = eip1559.CalcBaseFee(config, parent.Header()) + } + if config.IsShanghai(header.Number, header.Time) { + header.WithdrawalsHash = &types.EmptyWithdrawalsHash + } + var receipts []*types.Receipt + // The post-state result doesn't need to be correct (this is a bad block), but we do need something there + // Preferably something unique. So let's use a combo of blocknum + txhash + hasher := sha3.NewLegacyKeccak256() + hasher.Write(header.Number.Bytes()) + var cumulativeGas uint64 + var nBlobs int + for _, tx := range txs { + txh := tx.Hash() + hasher.Write(txh[:]) + receipt := types.NewReceipt(nil, false, cumulativeGas+tx.Gas()) + receipt.TxHash = tx.Hash() + receipt.GasUsed = tx.Gas() + receipts = append(receipts, receipt) + cumulativeGas += tx.Gas() + nBlobs += len(tx.BlobHashes()) + } + header.Root = common.BytesToHash(hasher.Sum(nil)) + if config.IsCancun(header.Number, header.Time) { + var pExcess, pUsed = uint64(0), uint64(0) + if parent.ExcessBlobGas() != nil { + pExcess = *parent.ExcessBlobGas() + pUsed = *parent.BlobGasUsed() + } + excess := eip4844.CalcExcessBlobGas(pExcess, pUsed) + used := uint64(nBlobs * params.BlobTxBlobGasPerBlob) + header.ExcessBlobGas = &excess + header.BlobGasUsed = &used + + beaconRoot := common.HexToHash("0xbeac00") + header.ParentBeaconRoot = &beaconRoot + } + // Assemble and return the final block for sealing + body := &types.Body{Transactions: txs} + if config.IsShanghai(header.Number, header.Time) { + body.Withdrawals = []*types.Withdrawal{} + } + return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) +} + +var ( + code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) + intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true) + // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness + // will not contain that copied data. + // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 + codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) + intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true) +) + +func TestProcessVerkle(t *testing.T) { + var ( + config = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + ShanghaiTime: u64(0), + VerkleTime: u64(0), + TerminalTotalDifficulty: common.Big0, + TerminalTotalDifficultyPassed: true, + // TODO uncomment when proof generation is merged + // ProofInBlocks: true, + } + signer = types.LatestSigner(config) + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain + coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") + gspec = &Genesis{ + Config: config, + Alloc: GenesisAlloc{ + coinbase: GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + }, + } + ) + // Verkle trees use the snapshot, which must be enabled before the + // data is saved into the tree+database. + // genesis := gspec.MustCommit(bcdb, triedb) + cacheConfig := DefaultCacheConfigWithScheme("path") + cacheConfig.SnapshotLimit = 0 + blockchain, _ := NewBlockChain(bcdb, cacheConfig, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) + defer blockchain.Stop() + + txCost1 := params.TxGas + txCost2 := params.TxGas + contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */) + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */) + blockGasUsagesExpected := []uint64{ + txCost1*2 + txCost2, + txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, + } + _, chain, _, _, _ := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { + gen.SetPoS() + + // TODO need to check that the tx cost provided is the exact amount used (no remaining left-over) + tx, _ := types.SignTx(types.NewTransaction(uint64(i)*3, common.Address{byte(i), 2, 3}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+1, common.Address{}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+2, common.Address{}, big.NewInt(0), txCost2, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + + // Add two contract creations in block #2 + if i == 1 { + tx, _ = types.SignTx(types.NewContractCreation(6, big.NewInt(16), 3000000, big.NewInt(875000000), code), signer, testKey) + gen.AddTx(tx) + + tx, _ = types.SignTx(types.NewContractCreation(7, big.NewInt(0), 3000000, big.NewInt(875000000), codeWithExtCodeCopy), signer, testKey) + gen.AddTx(tx) + } + }) + + t.Log("inserting blocks into the chain") + + endnum, err := blockchain.InsertChain(chain) + if err != nil { + t.Fatalf("block %d imported with error: %v", endnum, err) + } + + for i := 0; i < 2; i++ { + b := blockchain.GetBlockByNumber(uint64(i) + 1) + if b == nil { + t.Fatalf("expected block %d to be present in chain", i+1) + } + if b.Hash() != chain[i].Hash() { + t.Fatalf("block #%d not found at expected height", b.NumberU64()) + } + if b.GasUsed() != blockGasUsagesExpected[i] { + t.Fatalf("expected block #%d txs to use %d, got %d\n", b.NumberU64(), blockGasUsagesExpected[i], b.GasUsed()) + } + } +} diff --git a/core/state_transition.go b/core/state_transition.go new file mode 100644 index 0000000..1a6a66a --- /dev/null +++ b/core/state_transition.go @@ -0,0 +1,522 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "fmt" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/common" + cmath "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// 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, 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 +// analysis outside. +func (result *ExecutionResult) Unwrap() error { + return result.Err +} + +// Failed returns the indicator whether the execution is successful or not +func (result *ExecutionResult) Failed() bool { return result.Err != nil } + +// Return is a helper function to help caller distinguish between revert reason +// and function return. Return returns the data after execution if no error occurs. +func (result *ExecutionResult) Return() []byte { + if result.Err != nil { + return nil + } + return common.CopyBytes(result.ReturnData) +} + +// Revert returns the concrete revert reason if the execution is aborted by `REVERT` +// opcode. Note the reason can be nil if no data supplied with revert opcode. +func (result *ExecutionResult) Revert() []byte { + if result.Err != vm.ErrExecutionReverted { + return nil + } + return common.CopyBytes(result.ReturnData) +} + +// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. +func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) { + // Set the starting gas for the raw transaction + var gas uint64 + if isContractCreation && isHomestead { + gas = params.TxGasContractCreation + } else { + gas = params.TxGas + } + dataLen := uint64(len(data)) + // Bump the required gas by the amount of transactional data + if dataLen > 0 { + // Zero and non-zero bytes are priced differently + var nz uint64 + for _, byt := range data { + if byt != 0 { + nz++ + } + } + // Make sure we don't exceed uint64 for all data combinations + nonZeroGas := params.TxDataNonZeroGasFrontier + if isEIP2028 { + nonZeroGas = params.TxDataNonZeroGasEIP2028 + } + if (math.MaxUint64-gas)/nonZeroGas < nz { + return 0, ErrGasUintOverflow + } + gas += nz * nonZeroGas + + z := dataLen - nz + if (math.MaxUint64-gas)/params.TxDataZeroGas < z { + return 0, ErrGasUintOverflow + } + gas += z * params.TxDataZeroGas + + if isContractCreation && isEIP3860 { + lenWords := toWordSize(dataLen) + if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords { + return 0, ErrGasUintOverflow + } + gas += lenWords * params.InitCodeWordGas + } + } + if accessList != nil { + gas += uint64(len(accessList)) * params.TxAccessListAddressGas + gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas + } + return gas, nil +} + +// toWordSize returns the ceiled word size required for init code payment calculation. +func toWordSize(size uint64) uint64 { + if size > math.MaxUint64-31 { + return math.MaxUint64/32 + 1 + } + + return (size + 31) / 32 +} + +// A Message contains the data derived from a single transaction that is relevant to state +// processing. +type Message struct { + To *common.Address + From common.Address + Nonce uint64 + Value *big.Int + GasLimit uint64 + GasPrice *big.Int + GasFeeCap *big.Int + GasTipCap *big.Int + Data []byte + AccessList types.AccessList + BlobGasFeeCap *big.Int + BlobHashes []common.Hash + + // When SkipAccountChecks is true, the message nonce is not checked against the + // account nonce in state. It also disables checking that the sender is an EOA. + // This field will be set to true for operations like RPC eth_call. + SkipAccountChecks bool +} + +// TransactionToMessage converts a transaction into a Message. +func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { + msg := &Message{ + Nonce: tx.Nonce(), + GasLimit: tx.Gas(), + GasPrice: new(big.Int).Set(tx.GasPrice()), + GasFeeCap: new(big.Int).Set(tx.GasFeeCap()), + GasTipCap: new(big.Int).Set(tx.GasTipCap()), + To: tx.To(), + Value: tx.Value(), + Data: tx.Data(), + AccessList: tx.AccessList(), + SkipAccountChecks: false, + BlobHashes: tx.BlobHashes(), + BlobGasFeeCap: tx.BlobGasFeeCap(), + } + // If baseFee provided, set gasPrice to effectiveGasPrice. + if baseFee != nil { + msg.GasPrice = cmath.BigMin(msg.GasPrice.Add(msg.GasTipCap, baseFee), msg.GasFeeCap) + } + var err error + msg.From, err = types.Sender(s, tx) + return msg, err +} + +// ApplyMessage computes the new state by applying the given message +// against the old state within the environment. +// +// ApplyMessage returns the bytes returned by any EVM execution (if it took place), +// the gas used (which includes gas refunds) and an error if it failed. An error always +// indicates a core error meaning that the message would always fail for that particular +// state and would never be accepted within a block. +func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { + return NewStateTransition(evm, msg, gp).TransitionDb() +} + +// StateTransition represents a state transition. +// +// == The State Transitioning Model +// +// A state transition is a change made when a transaction is applied to the current world +// state. The state transitioning model does all the necessary work to work out a valid new +// state root. +// +// 1. Nonce handling +// 2. Pre pay gas +// 3. Create a new state object if the recipient is nil +// 4. Value transfer +// +// == If contract creation == +// +// 4a. Attempt to run transaction data +// 4b. If valid, use result as code for the new state object +// +// == end == +// +// 5. Run Script section +// 6. Derive new state root +type StateTransition struct { + gp *GasPool + msg *Message + gasRemaining uint64 + initialGas uint64 + state vm.StateDB + evm *vm.EVM +} + +// NewStateTransition initialises and returns a new state transition object. +func NewStateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *StateTransition { + return &StateTransition{ + gp: gp, + evm: evm, + msg: msg, + state: evm.StateDB, + } +} + +// to returns the recipient of the message. +func (st *StateTransition) to() common.Address { + if st.msg == nil || st.msg.To == nil /* contract creation */ { + return common.Address{} + } + return *st.msg.To +} + +func (st *StateTransition) buyGas() error { + mgval := new(big.Int).SetUint64(st.msg.GasLimit) + mgval.Mul(mgval, st.msg.GasPrice) + balanceCheck := new(big.Int).Set(mgval) + if st.msg.GasFeeCap != nil { + balanceCheck.SetUint64(st.msg.GasLimit) + balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) + } + balanceCheck.Add(balanceCheck, st.msg.Value) + + if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { + if blobGas := st.blobGasUsed(); blobGas > 0 { + // Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap + blobBalanceCheck := new(big.Int).SetUint64(blobGas) + blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap) + balanceCheck.Add(balanceCheck, blobBalanceCheck) + // Pay for blobGasUsed * actual blob fee + blobFee := new(big.Int).SetUint64(blobGas) + blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee) + mgval.Add(mgval, blobFee) + } + } + balanceCheckU256, overflow := uint256.FromBig(balanceCheck) + if overflow { + return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex()) + } + if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) + } + if err := st.gp.SubGas(st.msg.GasLimit); err != nil { + return err + } + + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { + st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance) + } + st.gasRemaining = st.msg.GasLimit + + st.initialGas = st.msg.GasLimit + mgvalU256, _ := uint256.FromBig(mgval) + st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy) + return nil +} + +func (st *StateTransition) preCheck() error { + // Only check transactions that are not fake + msg := st.msg + if !msg.SkipAccountChecks { + // Make sure this transaction's nonce is correct. + stNonce := st.state.GetNonce(msg.From) + if msgNonce := msg.Nonce; stNonce < msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, + msg.From.Hex(), msgNonce, stNonce) + } else if stNonce > msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, + msg.From.Hex(), msgNonce, stNonce) + } else if stNonce+1 < stNonce { + return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, + msg.From.Hex(), stNonce) + } + // Make sure the sender is an EOA + codeHash := st.state.GetCodeHash(msg.From) + if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash { + return fmt.Errorf("%w: address %v, codehash: %s", ErrSenderNoEOA, + 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) + 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) + } + if l := msg.GasTipCap.BitLen(); l > 256 { + return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh, + msg.From.Hex(), l) + } + if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 { + return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap, + msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap) + } + // 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, + msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee) + } + } + } + // Check the blob version validity + if msg.BlobHashes != nil { + // The to field of a blob tx type is mandatory, and a `BlobTx` transaction internally + // has it as a non-nillable value, so any msg derived from blob transaction has it non-nil. + // However, messages created through RPC (eth_call) don't have this restriction. + if msg.To == nil { + return ErrBlobTxCreate + } + if len(msg.BlobHashes) == 0 { + return ErrMissingBlobHashes + } + for i, hash := range msg.BlobHashes { + if !kzg4844.IsValidVersionedHash(hash[:]) { + return fmt.Errorf("blob %d has invalid hash version", i) + } + } + } + // 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 { + // 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() +} + +// TransitionDb will transition the state by applying the current message and +// returning the evm execution result with following fields. +// +// - used gas: total gas used (including gas being refunded) +// - returndata: the returned data from evm +// - concrete execution error: various EVM errors which abort the execution, e.g. +// ErrOutOfGas, ErrExecutionReverted +// +// However if any consensus issue encountered, return the error directly with +// nil evm execution result. +func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { + // First check this message satisfies all consensus rules before + // applying the message. The rules include these clauses + // + // 1. the nonce of the message caller is correct + // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) + // 3. the amount of gas required is available in the block + // 4. the purchased gas is enough to cover intrinsic usage + // 5. there is no overflow when calculating intrinsic gas + // 6. caller has enough balance to cover asset transfer for **topmost** call + + // Check clauses 1-3, buy gas if everything is correct + if err := st.preCheck(); err != nil { + return nil, err + } + + var ( + msg = st.msg + sender = vm.AccountRef(msg.From) + rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time) + contractCreation = msg.To == nil + ) + + // Check clauses 4-5, subtract intrinsic gas if everything is correct + gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai) + if err != nil { + return nil, err + } + if st.gasRemaining < gas { + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) + } + if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { + t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, tracing.GasChangeTxIntrinsicGas) + } + st.gasRemaining -= gas + + if rules.IsEIP4762 { + st.evm.AccessEvents.AddTxOrigin(msg.From) + + if targetAddr := msg.To; targetAddr != nil { + st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0) + } + } + + // Check clause 6 + value, overflow := uint256.FromBig(msg.Value) + if overflow { + return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex()) + } + if !value.IsZero() && !st.evm.Context.CanTransfer(st.state, msg.From, value) { + return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex()) + } + + // Check whether the init code size has been exceeded. + if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize { + return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize) + } + + // Execute the preparatory steps for state transition which includes: + // - prepare accessList(post-berlin) + // - reset transient storage(eip 1153) + st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + + var ( + ret []byte + vmerr error // vm errors do not effect consensus and are therefore not assigned to err + ) + if contractCreation { + ret, _, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, value) + } else { + // Increment the nonce for the next transaction + st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) + ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, value) + } + + var gasRefund uint64 + if !rules.IsLondon { + // Before EIP-3529: refunds were capped to gasUsed / 2 + gasRefund = st.refundGas(params.RefundQuotient) + } else { + // After EIP-3529: refunds are capped to gasUsed / 5 + gasRefund = st.refundGas(params.RefundQuotientEIP3529) + } + effectiveTip := msg.GasPrice + if rules.IsLondon { + effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)) + } + effectiveTipU256, _ := uint256.FromBig(effectiveTip) + + if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 { + // Skip fee payment when NoBaseFee is set and the fee fields + // are 0. This avoids a negative effectiveTip being applied to + // the coinbase when simulating calls. + } else { + fee := new(uint256.Int).SetUint64(st.gasUsed()) + fee.Mul(fee, effectiveTipU256) + st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee) + + // add the coinbase to the witness iff the fee is greater than 0 + if rules.IsEIP4762 && fee.Sign() != 0 { + st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true) + } + } + + return &ExecutionResult{ + UsedGas: st.gasUsed(), + RefundedGas: gasRefund, + Err: vmerr, + ReturnData: ret, + }, nil +} + +func (st *StateTransition) refundGas(refundQuotient uint64) uint64 { + // Apply refund counter, capped to a refund quotient + refund := st.gasUsed() / refundQuotient + if refund > st.state.GetRefund() { + refund = st.state.GetRefund() + } + + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 { + st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, tracing.GasChangeTxRefunds) + } + + st.gasRemaining += refund + + // Return ETH for remaining gas, exchanged at the original rate. + remaining := uint256.NewInt(st.gasRemaining) + remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) + st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) + + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 { + st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned) + } + + // 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. +func (st *StateTransition) gasUsed() uint64 { + return st.initialGas - st.gasRemaining +} + +// blobGasUsed returns the amount of blob gas used by the message. +func (st *StateTransition) blobGasUsed() uint64 { + return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob) +} diff --git a/core/stateless.go b/core/stateless.go new file mode 100644 index 0000000..4c7e6f3 --- /dev/null +++ b/core/stateless.go @@ -0,0 +1,73 @@ +// Copyright 2024 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 core + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/stateless" + "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/triedb" +) + +// ExecuteStateless runs a stateless execution based on a witness, verifies +// everything it can locally and returns the two computed fields that need the +// other side to explicitly check. +// +// This method is a bit of a sore thumb here, but: +// - It cannot be placed in core/stateless, because state.New prodces a circular dep +// - It cannot be placed outside of core, because it needs to construct a dud headerchain +// +// TODO(karalabe): Would be nice to resolve both issues above somehow and move it. +func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (common.Hash, common.Hash, error) { + // Create and populate the state database to serve as the stateless backend + memdb := witness.MakeHashDB() + + db, err := state.New(witness.Root(), state.NewDatabaseWithConfig(memdb, triedb.HashDefaults), nil) + if err != nil { + return common.Hash{}, common.Hash{}, err + } + // Create a blockchain that is idle, but can be used to access headers through + chain := &HeaderChain{ + config: config, + chainDb: memdb, + headerCache: lru.NewCache[common.Hash, *types.Header](256), + engine: beacon.New(ethash.NewFaker()), + } + processor := NewStateProcessor(config, chain) + validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block + + // Run the stateless blocks processing and self-validate certain fields + receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{}) + if err != nil { + return common.Hash{}, common.Hash{}, err + } + if err = validator.ValidateState(witness.Block, db, receipts, usedGas, true); err != nil { + return common.Hash{}, common.Hash{}, err + } + // Almost everything validated, but receipt and state root needs to be returned + receiptRoot := types.DeriveSha(receipts, trie.NewStackTrie(nil)) + stateRoot := db.IntermediateRoot(config.IsEIP158(witness.Block.Number())) + + return receiptRoot, stateRoot, nil +} diff --git a/core/stateless/database.go b/core/stateless/database.go new file mode 100644 index 0000000..135da62 --- /dev/null +++ b/core/stateless/database.go @@ -0,0 +1,60 @@ +// Copyright 2024 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 stateless + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" +) + +// MakeHashDB imports tries, codes and block hashes from a witness into a new +// hash-based memory db. We could eventually rewrite this into a pathdb, but +// simple is better for now. +func (w *Witness) MakeHashDB() ethdb.Database { + var ( + memdb = rawdb.NewMemoryDatabase() + hasher = crypto.NewKeccakState() + hash = make([]byte, 32) + ) + // Inject all the "block hashes" (i.e. headers) into the ephemeral database + for _, header := range w.Headers { + rawdb.WriteHeader(memdb, header) + } + // Inject all the bytecodes into the ephemeral database + for code := range w.Codes { + blob := []byte(code) + + hasher.Reset() + hasher.Write(blob) + hasher.Read(hash) + + rawdb.WriteCode(memdb, common.BytesToHash(hash), blob) + } + // Inject all the MPT trie nodes into the ephemeral database + for node := range w.State { + blob := []byte(node) + + hasher.Reset() + hasher.Write(blob) + hasher.Read(hash) + + rawdb.WriteLegacyTrieNode(memdb, common.BytesToHash(hash), blob) + } + return memdb +} diff --git a/core/stateless/encoding.go b/core/stateless/encoding.go new file mode 100644 index 0000000..2b7245d --- /dev/null +++ b/core/stateless/encoding.go @@ -0,0 +1,129 @@ +// Copyright 2024 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 stateless + +import ( + "bytes" + "errors" + "fmt" + "io" + "slices" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +//go:generate go run github.com/fjl/gencodec -type extWitness -field-override extWitnessMarshalling -out gen_encoding_json.go + +// toExtWitness converts our internal witness representation to the consensus one. +func (w *Witness) toExtWitness() *extWitness { + ext := &extWitness{ + Block: w.Block, + Headers: w.Headers, + } + ext.Codes = make([][]byte, 0, len(w.Codes)) + for code := range w.Codes { + ext.Codes = append(ext.Codes, []byte(code)) + } + slices.SortFunc(ext.Codes, bytes.Compare) + + ext.State = make([][]byte, 0, len(w.State)) + for node := range w.State { + ext.State = append(ext.State, []byte(node)) + } + slices.SortFunc(ext.State, bytes.Compare) + return ext +} + +// fromExtWitness converts the consensus witness format into our internal one. +func (w *Witness) fromExtWitness(ext *extWitness) error { + w.Block, w.Headers = ext.Block, ext.Headers + + w.Codes = make(map[string]struct{}, len(ext.Codes)) + for _, code := range ext.Codes { + w.Codes[string(code)] = struct{}{} + } + w.State = make(map[string]struct{}, len(ext.State)) + for _, node := range ext.State { + w.State[string(node)] = struct{}{} + } + return w.sanitize() +} + +// MarshalJSON marshals a witness as JSON. +func (w *Witness) MarshalJSON() ([]byte, error) { + return w.toExtWitness().MarshalJSON() +} + +// EncodeRLP serializes a witness as RLP. +func (w *Witness) EncodeRLP(wr io.Writer) error { + return rlp.Encode(wr, w.toExtWitness()) +} + +// UnmarshalJSON unmarshals from JSON. +func (w *Witness) UnmarshalJSON(input []byte) error { + var ext extWitness + if err := ext.UnmarshalJSON(input); err != nil { + return err + } + return w.fromExtWitness(&ext) +} + +// DecodeRLP decodes a witness from RLP. +func (w *Witness) DecodeRLP(s *rlp.Stream) error { + var ext extWitness + if err := s.Decode(&ext); err != nil { + return err + } + return w.fromExtWitness(&ext) +} + +// sanitize checks for some mandatory fields in the witness after decoding so +// the rest of the code can assume invariants and doesn't have to deal with +// corrupted data. +func (w *Witness) sanitize() error { + // Verify that the "parent" header (i.e. index 0) is available, and is the + // true parent of the block-to-be executed, since we use that to link the + // current block to the pre-state. + if len(w.Headers) == 0 { + return errors.New("parent header (for pre-root hash) missing") + } + for i, header := range w.Headers { + if header == nil { + return fmt.Errorf("witness header nil at position %d", i) + } + } + if w.Headers[0].Hash() != w.Block.ParentHash() { + return fmt.Errorf("parent hash different: witness %v, block parent %v", w.Headers[0].Hash(), w.Block.ParentHash()) + } + return nil +} + +// extWitness is a witness RLP encoding for transferring across clients. +type extWitness struct { + Block *types.Block `json:"block" gencodec:"required"` + Headers []*types.Header `json:"headers" gencodec:"required"` + Codes [][]byte `json:"codes"` + State [][]byte `json:"state"` +} + +// extWitnessMarshalling defines the hex marshalling types for a witness. +type extWitnessMarshalling struct { + Codes []hexutil.Bytes + State []hexutil.Bytes +} diff --git a/core/stateless/gen_encoding_json.go b/core/stateless/gen_encoding_json.go new file mode 100644 index 0000000..1d04979 --- /dev/null +++ b/core/stateless/gen_encoding_json.go @@ -0,0 +1,74 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package stateless + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*extWitnessMarshalling)(nil) + +// MarshalJSON marshals as JSON. +func (e extWitness) MarshalJSON() ([]byte, error) { + type extWitness struct { + Block *types.Block `json:"block" gencodec:"required"` + Headers []*types.Header `json:"headers" gencodec:"required"` + Codes []hexutil.Bytes `json:"codes"` + State []hexutil.Bytes `json:"state"` + } + var enc extWitness + enc.Block = e.Block + enc.Headers = e.Headers + if e.Codes != nil { + enc.Codes = make([]hexutil.Bytes, len(e.Codes)) + for k, v := range e.Codes { + enc.Codes[k] = v + } + } + if e.State != nil { + enc.State = make([]hexutil.Bytes, len(e.State)) + for k, v := range e.State { + enc.State[k] = v + } + } + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (e *extWitness) UnmarshalJSON(input []byte) error { + type extWitness struct { + Block *types.Block `json:"block" gencodec:"required"` + Headers []*types.Header `json:"headers" gencodec:"required"` + Codes []hexutil.Bytes `json:"codes"` + State []hexutil.Bytes `json:"state"` + } + var dec extWitness + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Block == nil { + return errors.New("missing required field 'block' for extWitness") + } + e.Block = dec.Block + if dec.Headers == nil { + return errors.New("missing required field 'headers' for extWitness") + } + e.Headers = dec.Headers + if dec.Codes != nil { + e.Codes = make([][]byte, len(dec.Codes)) + for k, v := range dec.Codes { + e.Codes[k] = v + } + } + if dec.State != nil { + e.State = make([][]byte, len(dec.State)) + for k, v := range dec.State { + e.State[k] = v + } + } + return nil +} diff --git a/core/stateless/witness.go b/core/stateless/witness.go new file mode 100644 index 0000000..7622c5e --- /dev/null +++ b/core/stateless/witness.go @@ -0,0 +1,159 @@ +// Copyright 2024 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 stateless + +import ( + "bytes" + "errors" + "fmt" + "maps" + "slices" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// HeaderReader is an interface to pull in headers in place of block hashes for +// the witness. +type HeaderReader interface { + // GetHeader retrieves a block header from the database by hash and number, + GetHeader(hash common.Hash, number uint64) *types.Header +} + +// Witness encompasses a block, state and any other chain data required to apply +// a set of transactions and derive a post state/receipt root. +type Witness struct { + Block *types.Block // Current block with rootHash and receiptHash zeroed out + Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set. + Codes map[string]struct{} // Set of bytecodes ran or accessed + State map[string]struct{} // Set of MPT state trie nodes (account and storage together) + + chain HeaderReader // Chain reader to convert block hash ops to header proofs + lock sync.Mutex // Lock to allow concurrent state insertions +} + +// NewWitness creates an empty witness ready for population. +func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) { + // Zero out the result fields to avoid accidentally sending them to the verifier + header := block.Header() + header.Root = common.Hash{} + header.ReceiptHash = common.Hash{} + + // Retrieve the parent header, which will *always* be included to act as a + // trustless pre-root hash container + parent := chain.GetHeader(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, errors.New("failed to retrieve parent header") + } + // Create the wtness with a reconstructed gutted out block + return &Witness{ + Block: types.NewBlockWithHeader(header).WithBody(*block.Body()), + Codes: make(map[string]struct{}), + State: make(map[string]struct{}), + Headers: []*types.Header{parent}, + chain: chain, + }, nil +} + +// AddBlockHash adds a "blockhash" to the witness with the designated offset from +// chain head. Under the hood, this method actually pulls in enough headers from +// the chain to cover the block being added. +func (w *Witness) AddBlockHash(number uint64) { + // Keep pulling in headers until this hash is populated + for int(w.Block.NumberU64()-number) > len(w.Headers) { + tail := w.Block.Header() + if len(w.Headers) > 0 { + tail = w.Headers[len(w.Headers)-1] + } + w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1)) + } +} + +// AddCode adds a bytecode blob to the witness. +func (w *Witness) AddCode(code []byte) { + if len(code) == 0 { + return + } + w.Codes[string(code)] = struct{}{} +} + +// AddState inserts a batch of MPT trie nodes into the witness. +func (w *Witness) AddState(nodes map[string]struct{}) { + if len(nodes) == 0 { + return + } + w.lock.Lock() + defer w.lock.Unlock() + + for node := range nodes { + w.State[node] = struct{}{} + } +} + +// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it +// is never mutated by Witness +func (w *Witness) Copy() *Witness { + return &Witness{ + Block: w.Block, + Headers: slices.Clone(w.Headers), + Codes: maps.Clone(w.Codes), + State: maps.Clone(w.State), + } +} + +// String prints a human-readable summary containing the total size of the +// witness and the sizes of the underlying components +func (w *Witness) String() string { + blob, _ := rlp.EncodeToBytes(w) + bytesTotal := len(blob) + + blob, _ = rlp.EncodeToBytes(w.Block) + bytesBlock := len(blob) + + bytesHeaders := 0 + for _, header := range w.Headers { + blob, _ = rlp.EncodeToBytes(header) + bytesHeaders += len(blob) + } + bytesCodes := 0 + for code := range w.Codes { + bytesCodes += len(code) + } + bytesState := 0 + for node := range w.State { + bytesState += len(node) + } + buf := new(bytes.Buffer) + + fmt.Fprintf(buf, "Witness #%d: %v\n", w.Block.Number(), common.StorageSize(bytesTotal)) + fmt.Fprintf(buf, " block (%4d txs): %10v\n", len(w.Block.Transactions()), common.StorageSize(bytesBlock)) + fmt.Fprintf(buf, "%4d headers: %10v\n", len(w.Headers), common.StorageSize(bytesHeaders)) + fmt.Fprintf(buf, "%4d trie nodes: %10v\n", len(w.State), common.StorageSize(bytesState)) + fmt.Fprintf(buf, "%4d codes: %10v\n", len(w.Codes), common.StorageSize(bytesCodes)) + + return buf.String() +} + +// Root returns the pre-state root from the first header. +// +// Note, this method will panic in case of a bad witness (but RLP decoding will +// sanitize it and fail before that). +func (w *Witness) Root() common.Hash { + return w.Headers[0].Root +} diff --git a/core/tracing/CHANGELOG.md b/core/tracing/CHANGELOG.md new file mode 100644 index 0000000..93b91cf --- /dev/null +++ b/core/tracing/CHANGELOG.md @@ -0,0 +1,79 @@ +# Changelog + +All notable changes to the tracing interface will be documented in this file. + +## [Unreleased] + +There have been minor backwards-compatible changes to the tracing interface to explicitly mark the execution of **system** contracts. As of now the only system call updates the parent beacon block root as per [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788). Other system calls are being considered for the future hardfork. + +### New methods + +- `OnSystemCallStart()`: This hook is called when EVM starts processing a system call. Note system calls happen outside the scope of a transaction. This event will be followed by normal EVM execution events. +- `OnSystemCallEnd()`: This hook is called when EVM finishes processing a system call. + +## [v1.14.0] + +There has been a major breaking change in the tracing interface for custom native tracers. JS and built-in tracers are not affected by this change and tracing API methods may be used as before. This overhaul has been done as part of the new live tracing feature ([#29189](https://github.com/ethereum/go-ethereum/pull/29189)). To learn more about live tracing please refer to the [docs](https://geth.ethereum.org/docs/developers/evm-tracing/live-tracing). + +**The `EVMLogger` interface which the tracers implemented has been removed.** It has been replaced by a new struct `tracing.Hooks`. `Hooks` keeps pointers to event listening functions. Internally the EVM will use these function pointers to emit events and can skip an event if the tracer has opted not to implement it. In fact this is the main reason for this change of approach. Another benefit is the ease of adding new hooks in future, and dynamically assigning event receivers. + +The consequence of this change can be seen in the constructor of a tracer. Let's take the 4byte tracer as an example. Previously the constructor return an instance which satisfied the interface. Now it should return a pointer to `tracers.Tracer` (which is now also a struct as opposed to an interface) and explicitly assign the event listeners. As a side-benefit the tracers will not have to provide empty implementation of methods just to satisfy the interface: + +```go +func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { + t := &fourByteTracer{ + ids: make(map[string]int), + } + return t, nil + +} +``` + +And now: + +```go +func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) { + t := &fourByteTracer{ + ids: make(map[string]int), + } + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.onTxStart, + OnEnter: t.onEnter, + }, + GetResult: t.getResult, + Stop: t.stop, + }, nil +} +``` + +### Event listeners + +If you have sharp eyes you might have noticed the new names for `OnTxStart` and `OnEnter`, previously called `CaptureTxStart` and `CaptureEnter`. Indeed there have been various modifications to the signatures of the event listeners. All method names now follow the `On*` pattern instead of `Capture*`. However the modifications are not limited to the names. + +#### New methods + +The live tracing feature was half about adding more observability into the state of the blockchain. As such there have been a host of method additions. Please consult the [Hooks](./hooks.go) struct for the full list of methods. Custom tracers which are invoked through the API (as opposed to "live" tracers) can benefit from the following new methods: + +- `OnGasChange(old, new uint64, reason GasChangeReason)`: This hook tracks the lifetime of gas within a transaction and its subcalls. It will first track the initial purchase of gas with ether, then the following consumptions and refunds of gas until at the end the rest is returned. +- `OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason)`: This hook tracks the balance changes of accounts. Where possible a reason is provided for the change (e.g. a transfer, gas purchase, withdrawal deposit etc). +- `OnNonceChange(addr common.Address, prev, new uint64)`: This hook tracks the nonce changes of accounts. +- `OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)`: This hook tracks the code changes of accounts. +- `OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash)`: This hook tracks the storage changes of accounts. +- `OnLogChange(log *types.Log)`: This hook tracks the logs emitted by the EVM. + +#### Removed methods + +The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signaled the top-level call frame of a transaction. The relevant info will be now emitted by `OnEnter` and `OnExit` which are emitted for every call frame. They now contain a `depth` parameter which can be used to distinguish the top-level call frame when necessary. The `create bool` parameter to `CaptureStart` can now be inferred from `typ byte` in `OnEnter`, i.e. `vm.OpCode(typ) == vm.CREATE`. + +#### Modified methods + +- `CaptureTxStart` -> `OnTxStart(vm *VMContext, tx *types.Transaction, from common.Address)`. It now emits the full transaction object as well as `from` which should be used to get the sender address. The `*VMContext` is a replacement for the `*vm.EVM` object previously passed to `CaptureStart`. +- `CaptureTxEnd` -> `OnTxEnd(receipt *types.Receipt, err error)`. It now returns the full receipt object. +- `CaptureEnter` -> `OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)`. The new `depth int` parameter indicates the call stack depth. It is 0 for the top-level call. Furthermore, the location where `OnEnter` is called in the EVM is now made a soon as a call is started. This means some specific error cases that were not before calling `OnEnter/OnExit` will now do so, leading some transaction to have an extra call traced. +- `CaptureExit` -> `OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool)`. It has the new `depth` parameter, same as `OnEnter`. The new `reverted` parameter indicates whether the call frame was reverted. +- `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract. +- `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above. + +[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.0...master +[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0 \ No newline at end of file diff --git a/core/tracing/gen_balance_change_reason_stringer.go b/core/tracing/gen_balance_change_reason_stringer.go new file mode 100644 index 0000000..d3a515a --- /dev/null +++ b/core/tracing/gen_balance_change_reason_stringer.go @@ -0,0 +1,37 @@ +// Code generated by "stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go"; DO NOT EDIT. + +package tracing + +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[BalanceChangeUnspecified-0] + _ = x[BalanceIncreaseRewardMineUncle-1] + _ = x[BalanceIncreaseRewardMineBlock-2] + _ = x[BalanceIncreaseWithdrawal-3] + _ = x[BalanceIncreaseGenesisBalance-4] + _ = x[BalanceIncreaseRewardTransactionFee-5] + _ = x[BalanceDecreaseGasBuy-6] + _ = x[BalanceIncreaseGasReturn-7] + _ = x[BalanceIncreaseDaoContract-8] + _ = x[BalanceDecreaseDaoAccount-9] + _ = x[BalanceChangeTransfer-10] + _ = x[BalanceChangeTouchAccount-11] + _ = x[BalanceIncreaseSelfdestruct-12] + _ = x[BalanceDecreaseSelfdestruct-13] + _ = x[BalanceDecreaseSelfdestructBurn-14] +} + +const _BalanceChangeReason_name = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurn" + +var _BalanceChangeReason_index = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400} + +func (i BalanceChangeReason) String() string { + if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) { + return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _BalanceChangeReason_name[_BalanceChangeReason_index[i]:_BalanceChangeReason_index[i+1]] +} diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go new file mode 100644 index 0000000..aa66dc4 --- /dev/null +++ b/core/tracing/hooks.go @@ -0,0 +1,313 @@ +// Copyright 2024 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 tracing + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// OpContext provides the context at which the opcode is being +// executed in, including the memory, stack and various contract-level information. +type OpContext interface { + MemoryData() []byte + StackData() []uint256.Int + Caller() common.Address + Address() common.Address + CallValue() *uint256.Int + CallInput() []byte +} + +// StateDB gives tracers access to the whole state. +type StateDB interface { + GetBalance(common.Address) *uint256.Int + GetNonce(common.Address) uint64 + GetCode(common.Address) []byte + GetState(common.Address, common.Hash) common.Hash + Exist(common.Address) bool + GetRefund() uint64 +} + +// VMContext provides the context for the EVM execution. +type VMContext struct { + Coinbase common.Address + BlockNumber *big.Int + Time uint64 + Random *common.Hash + // Effective tx gas price + GasPrice *big.Int + ChainConfig *params.ChainConfig + StateDB StateDB +} + +// BlockEvent is emitted upon tracing an incoming block. +// It contains the block as well as consensus related information. +type BlockEvent struct { + Block *types.Block + TD *big.Int + Finalized *types.Header + Safe *types.Header +} + +type ( + /* + - VM events - + */ + + // TxStartHook is called before the execution of a transaction starts. + // Call simulations don't come with a valid signature. `from` field + // to be used for address of the caller. + TxStartHook = func(vm *VMContext, tx *types.Transaction, from common.Address) + + // TxEndHook is called after the execution of a transaction ends. + TxEndHook = func(receipt *types.Receipt, err error) + + // EnterHook is invoked when the processing of a message starts. + // + // Take note that EnterHook, when in the context of a live tracer, can be invoked + // outside of the `OnTxStart` and `OnTxEnd` hooks when dealing with system calls, + // see [OnSystemCallStartHook] and [OnSystemCallEndHook] for more information. + EnterHook = func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) + + // ExitHook is invoked when the processing of a message ends. + // `revert` is true when there was an error during the execution. + // Exceptionally, before the homestead hardfork a contract creation that + // ran out of gas when attempting to persist the code to database did not + // count as a call failure and did not cause a revert of the call. This will + // be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`. + // + // Take note that ExitHook, when in the context of a live tracer, can be invoked + // outside of the `OnTxStart` and `OnTxEnd` hooks when dealing with system calls, + // see [OnSystemCallStartHook] and [OnSystemCallEndHook] for more information. + ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool) + + // OpcodeHook is invoked just prior to the execution of an opcode. + OpcodeHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, rData []byte, depth int, err error) + + // FaultHook is invoked when an error occurs during the execution of an opcode. + FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error) + + // GasChangeHook is invoked when the gas changes. + GasChangeHook = func(old, new uint64, reason GasChangeReason) + + /* + - Chain events - + */ + + // BlockchainInitHook is called when the blockchain is initialized. + BlockchainInitHook = func(chainConfig *params.ChainConfig) + + // CloseHook is called when the blockchain closes. + CloseHook = func() + + // BlockStartHook is called before executing `block`. + // `td` is the total difficulty prior to `block`. + BlockStartHook = func(event BlockEvent) + + // BlockEndHook is called after executing a block. + BlockEndHook = func(err error) + + // SkippedBlockHook indicates a block was skipped during processing + // due to it being known previously. This can happen e.g. when recovering + // from a crash. + SkippedBlockHook = func(event BlockEvent) + + // GenesisBlockHook is called when the genesis block is being processed. + GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc) + + // OnSystemCallStartHook is called when a system call is about to be executed. Today, + // this hook is invoked when the EIP-4788 system call is about to be executed to set the + // beacon block root. + // + // After this hook, the EVM call tracing will happened as usual so you will receive a `OnEnter/OnExit` + // as well as state hooks between this hook and the `OnSystemCallEndHook`. + // + // Note that system call happens outside normal transaction execution, so the `OnTxStart/OnTxEnd` hooks + // will not be invoked. + OnSystemCallStartHook = func() + + // OnSystemCallEndHook is called when a system call has finished executing. Today, + // this hook is invoked when the EIP-4788 system call is about to be executed to set the + // beacon block root. + OnSystemCallEndHook = func() + + /* + - State events - + */ + + // BalanceChangeHook is called when the balance of an account changes. + BalanceChangeHook = func(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) + + // NonceChangeHook is called when the nonce of an account changes. + NonceChangeHook = func(addr common.Address, prev, new uint64) + + // CodeChangeHook is called when the code of an account changes. + CodeChangeHook = func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) + + // StorageChangeHook is called when the storage of an account changes. + StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash) + + // LogHook is called when a log is emitted. + LogHook = func(log *types.Log) +) + +type Hooks struct { + // VM events + OnTxStart TxStartHook + OnTxEnd TxEndHook + OnEnter EnterHook + OnExit ExitHook + OnOpcode OpcodeHook + OnFault FaultHook + OnGasChange GasChangeHook + // Chain events + OnBlockchainInit BlockchainInitHook + OnClose CloseHook + OnBlockStart BlockStartHook + OnBlockEnd BlockEndHook + OnSkippedBlock SkippedBlockHook + OnGenesisBlock GenesisBlockHook + OnSystemCallStart OnSystemCallStartHook + OnSystemCallEnd OnSystemCallEndHook + // State events + OnBalanceChange BalanceChangeHook + OnNonceChange NonceChangeHook + OnCodeChange CodeChangeHook + OnStorageChange StorageChangeHook + OnLog LogHook +} + +// BalanceChangeReason is used to indicate the reason for a balance change, useful +// for tracing and reporting. +type BalanceChangeReason byte + +//go:generate go run golang.org/x/tools/cmd/stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go + +const ( + BalanceChangeUnspecified BalanceChangeReason = 0 + + // Issuance + // BalanceIncreaseRewardMineUncle is a reward for mining an uncle block. + BalanceIncreaseRewardMineUncle BalanceChangeReason = 1 + // BalanceIncreaseRewardMineBlock is a reward for mining a block. + BalanceIncreaseRewardMineBlock BalanceChangeReason = 2 + // BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain. + BalanceIncreaseWithdrawal BalanceChangeReason = 3 + // BalanceIncreaseGenesisBalance is ether allocated at the genesis block. + BalanceIncreaseGenesisBalance BalanceChangeReason = 4 + + // Transaction fees + // BalanceIncreaseRewardTransactionFee is the transaction tip increasing block builder's balance. + BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5 + // BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction. + // Part of this gas will be burnt as per EIP-1559 rules. + BalanceDecreaseGasBuy BalanceChangeReason = 6 + // BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution. + BalanceIncreaseGasReturn BalanceChangeReason = 7 + + // DAO fork + // BalanceIncreaseDaoContract is ether sent to the DAO refund contract. + BalanceIncreaseDaoContract BalanceChangeReason = 8 + // BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved to the refund contract. + BalanceDecreaseDaoAccount BalanceChangeReason = 9 + + // BalanceChangeTransfer is ether transferred via a call. + // it is a decrease for the sender and an increase for the recipient. + BalanceChangeTransfer BalanceChangeReason = 10 + // BalanceChangeTouchAccount is a transfer of zero value. It is only there to + // touch-create an account. + BalanceChangeTouchAccount BalanceChangeReason = 11 + + // BalanceIncreaseSelfdestruct is added to the recipient as indicated by a selfdestructing account. + BalanceIncreaseSelfdestruct BalanceChangeReason = 12 + // BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct. + BalanceDecreaseSelfdestruct BalanceChangeReason = 13 + // BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed + // account within the same tx (captured at end of tx). + // Note it doesn't account for a self-destruct which appoints itself as recipient. + BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14 +) + +// GasChangeReason is used to indicate the reason for a gas change, useful +// for tracing and reporting. +// +// There is essentially two types of gas changes, those that can be emitted once per transaction +// and those that can be emitted on a call basis, so possibly multiple times per transaction. +// +// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted +// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis. +type GasChangeReason byte + +const ( + GasChangeUnspecified GasChangeReason = 0 + + // GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only + // one such gas change per transaction. + GasChangeTxInitialBalance GasChangeReason = 1 + // GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is + // always exactly one of those per transaction. + GasChangeTxIntrinsicGas GasChangeReason = 2 + // GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) + // this generates an increase in gas. There is at most one of such gas change per transaction. + GasChangeTxRefunds GasChangeReason = 3 + // GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned + // to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas + // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. + // There is at most one of such gas change per transaction. + GasChangeTxLeftOverReturned GasChangeReason = 4 + + // GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only + // one such gas change per call. + GasChangeCallInitialBalance GasChangeReason = 5 + // GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always + // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even + // will be emitted. + GasChangeCallLeftOverReturned GasChangeReason = 6 + // GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it + // executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child. + // If there was no gas left to be refunded, no such even will be emitted. + GasChangeCallLeftOverRefunded GasChangeReason = 7 + // GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE. + GasChangeCallContractCreation GasChangeReason = 8 + // GasChangeContractCreation is the amount of gas that will be burned for a CREATE2. + GasChangeCallContractCreation2 GasChangeReason = 9 + // GasChangeCallCodeStorage is the amount of gas that will be charged for code storage. + GasChangeCallCodeStorage GasChangeReason = 10 + // GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was + // performed can be check by `OnOpcode` handling. + GasChangeCallOpCode GasChangeReason = 11 + // GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution. + GasChangeCallPrecompiledContract GasChangeReason = 12 + // GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules. + GasChangeCallStorageColdAccess GasChangeReason = 13 + // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. + GasChangeCallFailedExecution GasChangeReason = 14 + // GasChangeWitnessContractInit is the amount charged for adding to the witness during the contract creation initialization step + GasChangeWitnessContractInit GasChangeReason = 15 + // GasChangeWitnessContractCreation is the amount charged for adding to the witness during the contract creation finalization step + GasChangeWitnessContractCreation GasChangeReason = 16 + // GasChangeWitnessCodeChunk is the amount charged for touching one or more contract code chunks + GasChangeWitnessCodeChunk GasChangeReason = 17 + + // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as + // it will be "manually" tracked by a direct emit of the gas change event. + GasChangeIgnored GasChangeReason = 0xFF +) diff --git a/core/txindexer.go b/core/txindexer.go new file mode 100644 index 0000000..70fe5f3 --- /dev/null +++ b/core/txindexer.go @@ -0,0 +1,219 @@ +// Copyright 2024 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 core + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// TxIndexProgress is the struct describing the progress for transaction indexing. +type TxIndexProgress struct { + Indexed uint64 // number of blocks whose transactions are indexed + Remaining uint64 // number of blocks whose transactions are not indexed yet +} + +// Done returns an indicator if the transaction indexing is finished. +func (progress TxIndexProgress) Done() bool { + return progress.Remaining == 0 +} + +// txIndexer is the module responsible for maintaining transaction indexes +// according to the configured indexing range by users. +type txIndexer struct { + // limit is the maximum number of blocks from head whose tx indexes + // are reserved: + // * 0: means the entire chain should be indexed + // * N: means the latest N blocks [HEAD-N+1, HEAD] should be indexed + // and all others shouldn't. + limit uint64 + db ethdb.Database + progress chan chan TxIndexProgress + term chan chan struct{} + closed chan struct{} +} + +// newTxIndexer initializes the transaction indexer. +func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer { + indexer := &txIndexer{ + limit: limit, + db: chain.db, + progress: make(chan chan TxIndexProgress), + term: make(chan chan struct{}), + closed: make(chan struct{}), + } + go indexer.loop(chain) + + var msg string + if limit == 0 { + msg = "entire chain" + } else { + msg = fmt.Sprintf("last %d blocks", limit) + } + log.Info("Initialized transaction indexer", "range", msg) + + return indexer +} + +// run executes the scheduled indexing/unindexing task in a separate thread. +// If the stop channel is closed, the task should be terminated as soon as +// possible, the done channel will be closed once the task is finished. +func (indexer *txIndexer) run(tail *uint64, head uint64, stop chan struct{}, done chan struct{}) { + defer func() { close(done) }() + + // Short circuit if chain is empty and nothing to index. + if head == 0 { + return + } + // The tail flag is not existent, it means the node is just initialized + // and all blocks in the chain (part of them may from ancient store) are + // not indexed yet, index the chain according to the configured limit. + if tail == nil { + from := uint64(0) + if indexer.limit != 0 && head >= indexer.limit { + from = head - indexer.limit + 1 + } + rawdb.IndexTransactions(indexer.db, from, head+1, stop, true) + return + } + // The tail flag is existent (which means indexes in [tail, head] should be + // present), while the whole chain are requested for indexing. + if indexer.limit == 0 || head < indexer.limit { + if *tail > 0 { + // It can happen when chain is rewound to a historical point which + // is even lower than the indexes tail, recap the indexing target + // to new head to avoid reading non-existent block bodies. + end := *tail + if end > head+1 { + end = head + 1 + } + rawdb.IndexTransactions(indexer.db, 0, end, stop, true) + } + return + } + // The tail flag is existent, adjust the index range according to configured + // limit and the latest chain head. + if head-indexer.limit+1 < *tail { + // Reindex a part of missing indices and rewind index tail to HEAD-limit + rawdb.IndexTransactions(indexer.db, head-indexer.limit+1, *tail, stop, true) + } else { + // Unindex a part of stale indices and forward index tail to HEAD-limit + rawdb.UnindexTransactions(indexer.db, *tail, head-indexer.limit+1, stop, false) + } +} + +// loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending +// on the received chain event. +func (indexer *txIndexer) loop(chain *BlockChain) { + defer close(indexer.closed) + + // Listening to chain events and manipulate the transaction indexes. + var ( + stop chan struct{} // Non-nil if background routine is active. + done chan struct{} // Non-nil if background routine is active. + lastHead uint64 // The latest announced chain head (whose tx indexes are assumed created) + lastTail = rawdb.ReadTxIndexTail(indexer.db) // The oldest indexed block, nil means nothing indexed + + headCh = make(chan ChainHeadEvent) + sub = chain.SubscribeChainHeadEvent(headCh) + ) + defer sub.Unsubscribe() + + // Launch the initial processing if chain is not empty (head != genesis). + // This step is useful in these scenarios that chain has no progress. + if head := rawdb.ReadHeadBlock(indexer.db); head != nil && head.Number().Uint64() != 0 { + stop = make(chan struct{}) + done = make(chan struct{}) + lastHead = head.Number().Uint64() + go indexer.run(rawdb.ReadTxIndexTail(indexer.db), head.NumberU64(), stop, done) + } + for { + select { + case head := <-headCh: + if done == nil { + stop = make(chan struct{}) + done = make(chan struct{}) + go indexer.run(rawdb.ReadTxIndexTail(indexer.db), head.Block.NumberU64(), stop, done) + } + lastHead = head.Block.NumberU64() + case <-done: + stop = nil + done = nil + lastTail = rawdb.ReadTxIndexTail(indexer.db) + case ch := <-indexer.progress: + ch <- indexer.report(lastHead, lastTail) + case ch := <-indexer.term: + if stop != nil { + close(stop) + } + if done != nil { + log.Info("Waiting background transaction indexer to exit") + <-done + } + close(ch) + return + } + } +} + +// report returns the tx indexing progress. +func (indexer *txIndexer) report(head uint64, tail *uint64) TxIndexProgress { + total := indexer.limit + if indexer.limit == 0 || total > head { + total = head + 1 // genesis included + } + var indexed uint64 + if tail != nil { + indexed = head - *tail + 1 + } + // The value of indexed might be larger than total if some blocks need + // to be unindexed, avoiding a negative remaining. + var remaining uint64 + if indexed < total { + remaining = total - indexed + } + return TxIndexProgress{ + Indexed: indexed, + Remaining: remaining, + } +} + +// txIndexProgress retrieves the tx indexing progress, or an error if the +// background tx indexer is already stopped. +func (indexer *txIndexer) txIndexProgress() (TxIndexProgress, error) { + ch := make(chan TxIndexProgress, 1) + select { + case indexer.progress <- ch: + return <-ch, nil + case <-indexer.closed: + return TxIndexProgress{}, errors.New("indexer is closed") + } +} + +// close shutdown the indexer. Safe to be called for multiple times. +func (indexer *txIndexer) close() { + ch := make(chan struct{}) + select { + case indexer.term <- ch: + <-ch + case <-indexer.closed: + } +} diff --git a/core/txindexer_test.go b/core/txindexer_test.go new file mode 100644 index 0000000..0a606ed --- /dev/null +++ b/core/txindexer_test.go @@ -0,0 +1,240 @@ +// Copyright 2024 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 core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/params" +) + +// TestTxIndexer tests the functionalities for managing transaction indexes. +func TestTxIndexer(t *testing.T) { + var ( + testBankKey, _ = crypto.GenerateKey() + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) + testBankFunds = big.NewInt(1000000000000000000) + + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + engine = ethash.NewFaker() + nonce = uint64(0) + chainHead = uint64(128) + ) + _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) { + tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) + gen.AddTx(tx) + nonce += 1 + }) + + // verifyIndexes checks if the transaction indexes are present or not + // of the specified block. + verifyIndexes := func(db ethdb.Database, number uint64, exist bool) { + if number == 0 { + return + } + block := blocks[number-1] + for _, tx := range block.Transactions() { + lookup := rawdb.ReadTxLookupEntry(db, tx.Hash()) + if exist && lookup == nil { + t.Fatalf("missing %d %x", number, tx.Hash().Hex()) + } + if !exist && lookup != nil { + t.Fatalf("unexpected %d %x", number, tx.Hash().Hex()) + } + } + } + verify := func(db ethdb.Database, expTail uint64, indexer *txIndexer) { + tail := rawdb.ReadTxIndexTail(db) + if tail == nil { + t.Fatal("Failed to write tx index tail") + } + if *tail != expTail { + t.Fatalf("Unexpected tx index tail, want %v, got %d", expTail, *tail) + } + if *tail != 0 { + for number := uint64(0); number < *tail; number += 1 { + verifyIndexes(db, number, false) + } + } + for number := *tail; number <= chainHead; number += 1 { + verifyIndexes(db, number, true) + } + progress := indexer.report(chainHead, tail) + if !progress.Done() { + t.Fatalf("Expect fully indexed") + } + } + + var cases = []struct { + limitA uint64 + tailA uint64 + limitB uint64 + tailB uint64 + limitC uint64 + tailC uint64 + }{ + { + // LimitA: 0 + // TailA: 0 + // + // all blocks are indexed + limitA: 0, + tailA: 0, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + { + // LimitA: 64 + // TailA: 65 + // + // block [65, 128] are indexed + limitA: 64, + tailA: 65, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + { + // LimitA: 127 + // TailA: 2 + // + // block [2, 128] are indexed + limitA: 127, + tailA: 2, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + { + // LimitA: 128 + // TailA: 1 + // + // block [2, 128] are indexed + limitA: 128, + tailA: 1, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + { + // LimitA: 129 + // TailA: 0 + // + // block [0, 128] are indexed + limitA: 129, + tailA: 0, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + } + for _, c := range cases { + db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) + rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) + + // Index the initial blocks from ancient store + indexer := &txIndexer{ + limit: c.limitA, + db: db, + progress: make(chan chan TxIndexProgress), + } + indexer.run(nil, 128, make(chan struct{}), make(chan struct{})) + verify(db, c.tailA, indexer) + + indexer.limit = c.limitB + indexer.run(rawdb.ReadTxIndexTail(db), 128, make(chan struct{}), make(chan struct{})) + verify(db, c.tailB, indexer) + + indexer.limit = c.limitC + indexer.run(rawdb.ReadTxIndexTail(db), 128, make(chan struct{}), make(chan struct{})) + verify(db, c.tailC, indexer) + + // Recover all indexes + indexer.limit = 0 + indexer.run(rawdb.ReadTxIndexTail(db), 128, make(chan struct{}), make(chan struct{})) + verify(db, 0, indexer) + + db.Close() + } +} diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go new file mode 100644 index 0000000..6ff8847 --- /dev/null +++ b/core/txpool/blobpool/blobpool.go @@ -0,0 +1,1656 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package blobpool implements the EIP-4844 blob transaction pool. +package blobpool + +import ( + "container/heap" + "errors" + "fmt" + "math" + "math/big" + "os" + "path/filepath" + "sort" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/billy" + "github.com/holiman/uint256" +) + +const ( + // blobSize is the protocol constrained byte size of a single blob in a + // transaction. There can be multiple of these embedded into a single tx. + blobSize = params.BlobTxFieldElementsPerBlob * params.BlobTxBytesPerFieldElement + + // maxBlobsPerTransaction is the maximum number of blobs a single transaction + // is allowed to contain. Whilst the spec states it's unlimited, the block + // data slots are protocol bound, which implicitly also limit this. + maxBlobsPerTransaction = params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob + + // txAvgSize is an approximate byte size of a transaction metadata to avoid + // tiny overflows causing all txs to move a shelf higher, wasting disk space. + txAvgSize = 4 * 1024 + + // txMaxSize is the maximum size a single transaction can have, outside + // the included blobs. Since blob transactions are pulled instead of pushed, + // and only a small metadata is kept in ram, the rest is on disk, there is + // no critical limit that should be enforced. Still, capping it to some sane + // limit can never hurt. + txMaxSize = 1024 * 1024 + + // maxTxsPerAccount is the maximum number of blob transactions admitted from + // a single account. The limit is enforced to minimize the DoS potential of + // a private tx cancelling publicly propagated blobs. + // + // Note, transactions resurrected by a reorg are also subject to this limit, + // so pushing it down too aggressively might make resurrections non-functional. + maxTxsPerAccount = 16 + + // pendingTransactionStore is the subfolder containing the currently queued + // blob transactions. + pendingTransactionStore = "queue" + + // limboedTransactionStore is the subfolder containing the currently included + // but not yet finalized transaction blobs. + limboedTransactionStore = "limbo" +) + +// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and +// schedule the blob transactions into the following blocks. Only ever add the +// bare minimum needed fields to keep the size down (and thus number of entries +// larger with the same memory consumption). +type blobTxMeta struct { + hash common.Hash // Transaction hash to maintain the lookup table + id uint64 // Storage ID in the pool's persistent store + size uint32 // Byte size in the pool's persistent store + + nonce uint64 // Needed to prioritize inclusion order within an account + costCap *uint256.Int // Needed to validate cumulative balance sufficiency + execTipCap *uint256.Int // Needed to prioritize inclusion order across accounts and validate replacement price bump + execFeeCap *uint256.Int // Needed to validate replacement price bump + blobFeeCap *uint256.Int // Needed to validate replacement price bump + execGas uint64 // Needed to check inclusion validity before reading the blob + blobGas uint64 // Needed to check inclusion validity before reading the blob + + basefeeJumps float64 // Absolute number of 1559 fee adjustments needed to reach the tx's fee cap + blobfeeJumps float64 // Absolute number of 4844 fee adjustments needed to reach the tx's blob fee cap + + evictionExecTip *uint256.Int // Worst gas tip across all previous nonces + evictionExecFeeJumps float64 // Worst base fee (converted to fee jumps) across all previous nonces + evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces +} + +// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction +// and assembles a helper struct to track in memory. +func newBlobTxMeta(id uint64, size uint32, tx *types.Transaction) *blobTxMeta { + meta := &blobTxMeta{ + hash: tx.Hash(), + id: id, + size: size, + nonce: tx.Nonce(), + costCap: uint256.MustFromBig(tx.Cost()), + execTipCap: uint256.MustFromBig(tx.GasTipCap()), + execFeeCap: uint256.MustFromBig(tx.GasFeeCap()), + blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()), + execGas: tx.Gas(), + blobGas: tx.BlobGas(), + } + meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap) + meta.blobfeeJumps = dynamicFeeJumps(meta.blobFeeCap) + + return meta +} + +// BlobPool is the transaction pool dedicated to EIP-4844 blob transactions. +// +// Blob transactions are special snowflakes that are designed for a very specific +// purpose (rollups) and are expected to adhere to that specific use case. These +// behavioural expectations allow us to design a transaction pool that is more robust +// (i.e. resending issues) and more resilient to DoS attacks (e.g. replace-flush +// attacks) than the generic tx pool. These improvements will also mean, however, +// that we enforce a significantly more aggressive strategy on entering and exiting +// the pool: +// +// - Blob transactions are large. With the initial design aiming for 128KB blobs, +// we must ensure that these only traverse the network the absolute minimum +// number of times. Broadcasting to sqrt(peers) is out of the question, rather +// these should only ever be announced and the remote side should request it if +// it wants to. +// +// - Block blob-space is limited. With blocks being capped to a few blob txs, we +// can make use of the very low expected churn rate within the pool. Notably, +// we should be able to use a persistent disk backend for the pool, solving +// the tx resend issue that plagues the generic tx pool, as long as there's no +// artificial churn (i.e. pool wars). +// +// - Purpose of blobs are layer-2s. Layer-2s are meant to use blob transactions to +// commit to their own current state, which is independent of Ethereum mainnet +// (state, txs). This means that there's no reason for blob tx cancellation or +// replacement, apart from a potential basefee / miner tip adjustment. +// +// - Replacements are expensive. Given their size, propagating a replacement +// blob transaction to an existing one should be aggressively discouraged. +// Whilst generic transactions can start at 1 Wei gas cost and require a 10% +// fee bump to replace, we suggest requiring a higher min cost (e.g. 1 gwei) +// and a more aggressive bump (100%). +// +// - Cancellation is prohibitive. Evicting an already propagated blob tx is a huge +// DoS vector. As such, a) replacement (higher-fee) blob txs mustn't invalidate +// already propagated (future) blob txs (cumulative fee); b) nonce-gapped blob +// txs are disallowed; c) the presence of blob transactions exclude non-blob +// transactions. +// +// - Malicious cancellations are possible. Although the pool might prevent txs +// that cancel blobs, blocks might contain such transaction (malicious miner +// or flashbotter). The pool should cap the total number of blob transactions +// per account as to prevent propagating too much data before cancelling it +// via a normal transaction. It should nonetheless be high enough to support +// resurrecting reorged transactions. Perhaps 4-16. +// +// - Local txs are meaningless. Mining pools historically used local transactions +// for payouts or for backdoor deals. With 1559 in place, the basefee usually +// dominates the final price, so 0 or non-0 tip doesn't change much. Blob txs +// retain the 1559 2D gas pricing (and introduce on top a dynamic blob gas fee), +// so locality is moot. With a disk backed blob pool avoiding the resend issue, +// there's also no need to save own transactions for later. +// +// - No-blob blob-txs are bad. Theoretically there's no strong reason to disallow +// blob txs containing 0 blobs. In practice, admitting such txs into the pool +// breaks the low-churn invariant as blob constraints don't apply anymore. Even +// though we could accept blocks containing such txs, a reorg would require moving +// them back into the blob pool, which can break invariants. +// +// - Dropping blobs needs delay. When normal transactions are included, they +// are immediately evicted from the pool since they are contained in the +// including block. Blobs however are not included in the execution chain, +// so a mini reorg cannot re-pool "lost" blob transactions. To support reorgs, +// blobs are retained on disk until they are finalised. +// +// - Blobs can arrive via flashbots. Blocks might contain blob transactions we +// have never seen on the network. Since we cannot recover them from blocks +// either, the engine_newPayload needs to give them to us, and we cache them +// until finality to support reorgs without tx losses. +// +// Whilst some constraints above might sound overly aggressive, the general idea is +// that the blob pool should work robustly for its intended use case and whilst +// anyone is free to use blob transactions for arbitrary non-rollup use cases, +// they should not be allowed to run amok the network. +// +// Implementation wise there are a few interesting design choices: +// +// - Adding a transaction to the pool blocks until persisted to disk. This is +// viable because TPS is low (2-4 blobs per block initially, maybe 8-16 at +// peak), so natural churn is a couple MB per block. Replacements doing O(n) +// updates are forbidden and transaction propagation is pull based (i.e. no +// pileup of pending data). +// +// - When transactions are chosen for inclusion, the primary criteria is the +// signer tip (and having a basefee/data fee high enough of course). However, +// same-tip transactions will be split by their basefee/datafee, preferring +// those that are closer to the current network limits. The idea being that +// very relaxed ones can be included even if the fees go up, when the closer +// ones could already be invalid. +// +// When the pool eventually reaches saturation, some old transactions - that may +// never execute - will need to be evicted in favor of newer ones. The eviction +// strategy is quite complex: +// +// - Exceeding capacity evicts the highest-nonce of the account with the lowest +// paying blob transaction anywhere in the pooled nonce-sequence, as that tx +// would be executed the furthest in the future and is thus blocking anything +// after it. The smallest is deliberately not evicted to avoid a nonce-gap. +// +// - Analogously, if the pool is full, the consideration price of a new tx for +// evicting an old one is the smallest price in the entire nonce-sequence of +// the account. This avoids malicious users DoSing the pool with seemingly +// high paying transactions hidden behind a low-paying blocked one. +// +// - Since blob transactions have 3 price parameters: execution tip, execution +// fee cap and data fee cap, there's no singular parameter to create a total +// price ordering on. What's more, since the base fee and blob fee can move +// independently of one another, there's no pre-defined way to combine them +// into a stable order either. This leads to a multi-dimensional problem to +// solve after every block. +// +// - The first observation is that comparing 1559 base fees or 4844 blob fees +// needs to happen in the context of their dynamism. Since these fees jump +// up or down in ~1.125 multipliers (at max) across blocks, comparing fees +// in two transactions should be based on log1.125(fee) to eliminate noise. +// +// - The second observation is that the basefee and blobfee move independently, +// so there's no way to split mixed txs on their own (A has higher base fee, +// B has higher blob fee). Rather than look at the absolute fees, the useful +// metric is the max time it can take to exceed the transaction's fee caps. +// Specifically, we're interested in the number of jumps needed to go from +// the current fee to the transaction's cap: +// +// jumps = log1.125(txfee) - log1.125(basefee) +// +// - The third observation is that the base fee tends to hover around rather +// than swing wildly. The number of jumps needed from the current fee starts +// to get less relevant the higher it is. To remove the noise here too, the +// pool will use log(jumps) as the delta for comparing transactions. +// +// delta = sign(jumps) * log(abs(jumps)) +// +// - To establish a total order, we need to reduce the dimensionality of the +// two base fees (log jumps) to a single value. The interesting aspect from +// the pool's perspective is how fast will a tx get executable (fees going +// down, crossing the smaller negative jump counter) or non-executable (fees +// going up, crossing the smaller positive jump counter). As such, the pool +// cares only about the min of the two delta values for eviction priority. +// +// priority = min(deltaBasefee, deltaBlobfee) +// +// - The above very aggressive dimensionality and noise reduction should result +// in transaction being grouped into a small number of buckets, the further +// the fees the larger the buckets. This is good because it allows us to use +// the miner tip meaningfully as a splitter. +// +// - For the scenario where the pool does not contain non-executable blob txs +// anymore, it does not make sense to grant a later eviction priority to txs +// with high fee caps since it could enable pool wars. As such, any positive +// priority will be grouped together. +// +// priority = min(deltaBasefee, deltaBlobfee, 0) +// +// Optimisation tradeoffs: +// +// - Eviction relies on 3 fee minimums per account (exec tip, exec cap and blob +// cap). Maintaining these values across all transactions from the account is +// problematic as each transaction replacement or inclusion would require a +// rescan of all other transactions to recalculate the minimum. Instead, the +// pool maintains a rolling minimum across the nonce range. Updating all the +// minimums will need to be done only starting at the swapped in/out nonce +// and leading up to the first no-change. +type BlobPool struct { + config Config // Pool configuration + reserve txpool.AddressReserver // Address reserver to ensure exclusivity across subpools + + store billy.Database // Persistent data store for the tx metadata and blobs + stored uint64 // Useful data size of all transactions on disk + limbo *limbo // Persistent data store for the non-finalized blobs + + signer types.Signer // Transaction signer to use for sender recovery + chain BlockChain // Chain object to access the state through + + head *types.Header // Current head of the chain + state *state.StateDB // Current state at the head of the chain + gasTip *uint256.Int // Currently accepted minimum gas tip + + lookup map[common.Hash]uint64 // Lookup table mapping hashes to tx billy entries + index map[common.Address][]*blobTxMeta // Blob transactions grouped by accounts, sorted by nonce + spent map[common.Address]*uint256.Int // Expenditure tracking for individual accounts + evict *evictHeap // Heap of cheapest accounts for eviction when full + + discoverFeed event.Feed // Event feed to send out new tx events on pool discovery (reorg excluded) + insertFeed event.Feed // Event feed to send out new tx events on pool inclusion (reorg included) + + lock sync.RWMutex // Mutex protecting the pool during reorg handling +} + +// New creates a new blob transaction pool to gather, sort and filter inbound +// blob transactions from the network. +func New(config Config, chain BlockChain) *BlobPool { + // Sanitize the input to ensure no vulnerable gas prices are set + config = (&config).sanitize() + + // Create the transaction pool with its initial settings + return &BlobPool{ + config: config, + signer: types.LatestSigner(chain.Config()), + chain: chain, + lookup: make(map[common.Hash]uint64), + index: make(map[common.Address][]*blobTxMeta), + spent: make(map[common.Address]*uint256.Int), + } +} + +// Filter returns whether the given transaction can be consumed by the blob pool. +func (p *BlobPool) Filter(tx *types.Transaction) bool { + return tx.Type() == types.BlobTxType +} + +// Init sets the gas price needed to keep a transaction in the pool and the chain +// head to allow balance / nonce checks. The transaction journal will be loaded +// from disk and filtered based on the provided starting settings. +func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserve txpool.AddressReserver) error { + p.reserve = reserve + + var ( + queuedir string + limbodir string + ) + if p.config.Datadir != "" { + queuedir = filepath.Join(p.config.Datadir, pendingTransactionStore) + if err := os.MkdirAll(queuedir, 0700); err != nil { + return err + } + limbodir = filepath.Join(p.config.Datadir, limboedTransactionStore) + if err := os.MkdirAll(limbodir, 0700); err != nil { + return err + } + } + // Initialize the state with head block, or fallback to empty one in + // case the head state is not available (might occur when node is not + // fully synced). + state, err := p.chain.StateAt(head.Root) + if err != nil { + state, err = p.chain.StateAt(types.EmptyRootHash) + } + if err != nil { + return err + } + p.head, p.state = head, state + + // Index all transactions on disk and delete anything unprocessable + var fails []uint64 + index := func(id uint64, size uint32, blob []byte) { + if p.parseTransaction(id, size, blob) != nil { + fails = append(fails, id) + } + } + store, err := billy.Open(billy.Options{Path: queuedir, Repair: true}, newSlotter(), index) + if err != nil { + return err + } + p.store = store + + if len(fails) > 0 { + log.Warn("Dropping invalidated blob transactions", "ids", fails) + dropInvalidMeter.Mark(int64(len(fails))) + + for _, id := range fails { + if err := p.store.Delete(id); err != nil { + p.Close() + return err + } + } + } + // Sort the indexed transactions by nonce and delete anything gapped, create + // the eviction heap of anyone still standing + for addr := range p.index { + p.recheck(addr, nil) + } + var ( + basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head)) + blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice) + ) + if p.head.ExcessBlobGas != nil { + blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(*p.head.ExcessBlobGas)) + } + p.evict = newPriceHeap(basefee, blobfee, p.index) + + // Pool initialized, attach the blob limbo to it to track blobs included + // recently but not yet finalized + p.limbo, err = newLimbo(limbodir) + if err != nil { + p.Close() + return err + } + // Set the configured gas tip, triggering a filtering of anything just loaded + basefeeGauge.Update(int64(basefee.Uint64())) + blobfeeGauge.Update(int64(blobfee.Uint64())) + + p.SetGasTip(new(big.Int).SetUint64(gasTip)) + + // Since the user might have modified their pool's capacity, evict anything + // above the current allowance + for p.stored > p.config.Datacap { + p.drop() + } + // Update the metrics and return the constructed pool + datacapGauge.Update(int64(p.config.Datacap)) + p.updateStorageMetrics() + return nil +} + +// Close closes down the underlying persistent store. +func (p *BlobPool) Close() error { + var errs []error + if p.limbo != nil { // Close might be invoked due to error in constructor, before p,limbo is set + if err := p.limbo.Close(); err != nil { + errs = append(errs, err) + } + } + if err := p.store.Close(); err != nil { + errs = append(errs, err) + } + switch { + case errs == nil: + return nil + case len(errs) == 1: + return errs[0] + default: + return fmt.Errorf("%v", errs) + } +} + +// parseTransaction is a callback method on pool creation that gets called for +// each transaction on disk to create the in-memory metadata index. +func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error { + tx := new(types.Transaction) + if err := rlp.DecodeBytes(blob, tx); err != nil { + // This path is impossible unless the disk data representation changes + // across restarts. For that ever improbable case, recover gracefully + // by ignoring this data entry. + log.Error("Failed to decode blob pool entry", "id", id, "err", err) + return err + } + if tx.BlobTxSidecar() == nil { + log.Error("Missing sidecar in blob pool entry", "id", id, "hash", tx.Hash()) + return errors.New("missing blob sidecar") + } + + meta := newBlobTxMeta(id, size, tx) + if _, exists := p.lookup[meta.hash]; exists { + // This path is only possible after a crash, where deleted items are not + // removed via the normal shutdown-startup procedure and thus may get + // partially resurrected. + log.Error("Rejecting duplicate blob pool entry", "id", id, "hash", tx.Hash()) + return errors.New("duplicate blob entry") + } + sender, err := p.signer.Sender(tx) + if err != nil { + // This path is impossible unless the signature validity changes across + // restarts. For that ever improbable case, recover gracefully by ignoring + // this data entry. + log.Error("Failed to recover blob tx sender", "id", id, "hash", tx.Hash(), "err", err) + return err + } + if _, ok := p.index[sender]; !ok { + if err := p.reserve(sender, true); err != nil { + return err + } + p.index[sender] = []*blobTxMeta{} + p.spent[sender] = new(uint256.Int) + } + p.index[sender] = append(p.index[sender], meta) + p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.costCap) + + p.lookup[meta.hash] = meta.id + p.stored += uint64(meta.size) + + return nil +} + +// recheck verifies the pool's content for a specific account and drops anything +// that does not fit anymore (dangling or filled nonce, overdraft). +func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint64) { + // Sort the transactions belonging to the account so reinjects can be simpler + txs := p.index[addr] + if inclusions != nil && txs == nil { // during reorgs, we might find new accounts + return + } + sort.Slice(txs, func(i, j int) bool { + return txs[i].nonce < txs[j].nonce + }) + // If there is a gap between the chain state and the blob pool, drop + // all the transactions as they are non-executable. Similarly, if the + // entire tx range was included, drop all. + var ( + next = p.state.GetNonce(addr) + gapped = txs[0].nonce > next + filled = txs[len(txs)-1].nonce < next + ) + if gapped || filled { + var ( + ids []uint64 + nonces []uint64 + ) + for i := 0; i < len(txs); i++ { + ids = append(ids, txs[i].id) + nonces = append(nonces, txs[i].nonce) + + p.stored -= uint64(txs[i].size) + delete(p.lookup, txs[i].hash) + + // Included transactions blobs need to be moved to the limbo + if filled && inclusions != nil { + p.offload(addr, txs[i].nonce, txs[i].id, inclusions) + } + } + delete(p.index, addr) + delete(p.spent, addr) + if inclusions != nil { // only during reorgs will the heap be initialized + heap.Remove(p.evict, p.evict.index[addr]) + } + p.reserve(addr, false) + + if gapped { + log.Warn("Dropping dangling blob transactions", "from", addr, "missing", next, "drop", nonces, "ids", ids) + dropDanglingMeter.Mark(int64(len(ids))) + } else { + log.Trace("Dropping filled blob transactions", "from", addr, "filled", nonces, "ids", ids) + dropFilledMeter.Mark(int64(len(ids))) + } + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + return + } + // If there is overlap between the chain state and the blob pool, drop + // anything below the current state + if txs[0].nonce < next { + var ( + ids []uint64 + nonces []uint64 + ) + for txs[0].nonce < next { + ids = append(ids, txs[0].id) + nonces = append(nonces, txs[0].nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].costCap) + p.stored -= uint64(txs[0].size) + delete(p.lookup, txs[0].hash) + + // Included transactions blobs need to be moved to the limbo + if inclusions != nil { + p.offload(addr, txs[0].nonce, txs[0].id, inclusions) + } + txs = txs[1:] + } + log.Trace("Dropping overlapped blob transactions", "from", addr, "overlapped", nonces, "ids", ids, "left", len(txs)) + dropOverlappedMeter.Mark(int64(len(ids))) + + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + p.index[addr] = txs + } + // Iterate over the transactions to initialize their eviction thresholds + // and to detect any nonce gaps + txs[0].evictionExecTip = txs[0].execTipCap + txs[0].evictionExecFeeJumps = txs[0].basefeeJumps + txs[0].evictionBlobFeeJumps = txs[0].blobfeeJumps + + for i := 1; i < len(txs); i++ { + // If there's no nonce gap, initialize the eviction thresholds as the + // minimum between the cumulative thresholds and the current tx fees + if txs[i].nonce == txs[i-1].nonce+1 { + txs[i].evictionExecTip = txs[i-1].evictionExecTip + if txs[i].evictionExecTip.Cmp(txs[i].execTipCap) > 0 { + txs[i].evictionExecTip = txs[i].execTipCap + } + txs[i].evictionExecFeeJumps = txs[i-1].evictionExecFeeJumps + if txs[i].evictionExecFeeJumps > txs[i].basefeeJumps { + txs[i].evictionExecFeeJumps = txs[i].basefeeJumps + } + txs[i].evictionBlobFeeJumps = txs[i-1].evictionBlobFeeJumps + if txs[i].evictionBlobFeeJumps > txs[i].blobfeeJumps { + txs[i].evictionBlobFeeJumps = txs[i].blobfeeJumps + } + continue + } + // Sanity check that there's no double nonce. This case would generally + // be a coding error, so better know about it. + // + // Also, Billy behind the blobpool does not journal deletes. A process + // crash would result in previously deleted entities being resurrected. + // That could potentially cause a duplicate nonce to appear. + if txs[i].nonce == txs[i-1].nonce { + id := p.lookup[txs[i].hash] + + log.Error("Dropping repeat nonce blob transaction", "from", addr, "nonce", txs[i].nonce, "id", id) + dropRepeatedMeter.Mark(1) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap) + p.stored -= uint64(txs[i].size) + delete(p.lookup, txs[i].hash) + + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + txs = append(txs[:i], txs[i+1:]...) + p.index[addr] = txs + + i-- + continue + } + // Otherwise if there's a nonce gap evict all later transactions + var ( + ids []uint64 + nonces []uint64 + ) + for j := i; j < len(txs); j++ { + ids = append(ids, txs[j].id) + nonces = append(nonces, txs[j].nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].costCap) + p.stored -= uint64(txs[j].size) + delete(p.lookup, txs[j].hash) + } + txs = txs[:i] + + log.Error("Dropping gapped blob transactions", "from", addr, "missing", txs[i-1].nonce+1, "drop", nonces, "ids", ids) + dropGappedMeter.Mark(int64(len(ids))) + + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + p.index[addr] = txs + break + } + // Ensure that there's no over-draft, this is expected to happen when some + // transactions get included without publishing on the network + var ( + balance = p.state.GetBalance(addr) + spent = p.spent[addr] + ) + if spent.Cmp(balance) > 0 { + // Evict the highest nonce transactions until the pending set falls under + // the account's available balance + var ( + ids []uint64 + nonces []uint64 + ) + for p.spent[addr].Cmp(balance) > 0 { + last := txs[len(txs)-1] + txs[len(txs)-1] = nil + txs = txs[:len(txs)-1] + + ids = append(ids, last.id) + nonces = append(nonces, last.nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap) + p.stored -= uint64(last.size) + delete(p.lookup, last.hash) + } + if len(txs) == 0 { + delete(p.index, addr) + delete(p.spent, addr) + if inclusions != nil { // only during reorgs will the heap be initialized + heap.Remove(p.evict, p.evict.index[addr]) + } + p.reserve(addr, false) + } else { + p.index[addr] = txs + } + log.Warn("Dropping overdrafted blob transactions", "from", addr, "balance", balance, "spent", spent, "drop", nonces, "ids", ids) + dropOverdraftedMeter.Mark(int64(len(ids))) + + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + } + // Sanity check that no account can have more queued transactions than the + // DoS protection threshold. + if len(txs) > maxTxsPerAccount { + // Evict the highest nonce transactions until the pending set falls under + // the account's transaction cap + var ( + ids []uint64 + nonces []uint64 + ) + for len(txs) > maxTxsPerAccount { + last := txs[len(txs)-1] + txs[len(txs)-1] = nil + txs = txs[:len(txs)-1] + + ids = append(ids, last.id) + nonces = append(nonces, last.nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap) + p.stored -= uint64(last.size) + delete(p.lookup, last.hash) + } + p.index[addr] = txs + + log.Warn("Dropping overcapped blob transactions", "from", addr, "kept", len(txs), "drop", nonces, "ids", ids) + dropOvercappedMeter.Mark(int64(len(ids))) + + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + } + // Included cheap transactions might have left the remaining ones better from + // an eviction point, fix any potential issues in the heap. + if _, ok := p.index[addr]; ok && inclusions != nil { + heap.Fix(p.evict, p.evict.index[addr]) + } +} + +// offload removes a tracked blob transaction from the pool and moves it into the +// limbo for tracking until finality. +// +// The method may log errors for various unexpected scenarios but will not return +// any of it since there's no clear error case. Some errors may be due to coding +// issues, others caused by signers mining MEV stuff or swapping transactions. In +// all cases, the pool needs to continue operating. +func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusions map[common.Hash]uint64) { + data, err := p.store.Get(id) + if err != nil { + log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err) + return + } + var tx types.Transaction + if err = rlp.DecodeBytes(data, &tx); err != nil { + log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err) + return + } + block, ok := inclusions[tx.Hash()] + if !ok { + log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id) + return + } + if err := p.limbo.push(&tx, block); err != nil { + log.Warn("Failed to offload blob tx into limbo", "err", err) + return + } +} + +// Reset implements txpool.SubPool, allowing the blob pool's internal state to be +// kept in sync with the main transaction pool's internal state. +func (p *BlobPool) Reset(oldHead, newHead *types.Header) { + waitStart := time.Now() + p.lock.Lock() + resetwaitHist.Update(time.Since(waitStart).Nanoseconds()) + defer p.lock.Unlock() + + defer func(start time.Time) { + resettimeHist.Update(time.Since(start).Nanoseconds()) + }(time.Now()) + + statedb, err := p.chain.StateAt(newHead.Root) + if err != nil { + log.Error("Failed to reset blobpool state", "err", err) + return + } + p.head = newHead + p.state = statedb + + // Run the reorg between the old and new head and figure out which accounts + // need to be rechecked and which transactions need to be readded + if reinject, inclusions := p.reorg(oldHead, newHead); reinject != nil { + var adds []*types.Transaction + for addr, txs := range reinject { + // Blindly push all the lost transactions back into the pool + for _, tx := range txs { + if err := p.reinject(addr, tx.Hash()); err == nil { + adds = append(adds, tx.WithoutBlobTxSidecar()) + } + } + // Recheck the account's pooled transactions to drop included and + // invalidated ones + 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) { + p.limbo.finalize(p.chain.CurrentFinalBlock()) + } + // Reset the price heap for the new set of basefee/blobfee pairs + var ( + basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), newHead)) + blobfee = uint256.MustFromBig(big.NewInt(params.BlobTxMinBlobGasprice)) + ) + if newHead.ExcessBlobGas != nil { + blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(*newHead.ExcessBlobGas)) + } + p.evict.reinit(basefee, blobfee, false) + + basefeeGauge.Update(int64(basefee.Uint64())) + blobfeeGauge.Update(int64(blobfee.Uint64())) + p.updateStorageMetrics() +} + +// reorg assembles all the transactors and missing transactions between an old +// and new head to figure out which account's tx set needs to be rechecked and +// which transactions need to be requeued. +// +// The transactionblock inclusion infos are also returned to allow tracking any +// just-included blocks by block number in the limbo. +func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*types.Transaction, map[common.Hash]uint64) { + // If the pool was not yet initialized, don't do anything + if oldHead == nil { + return nil, nil + } + // If the reorg is too deep, avoid doing it (will happen during snap sync) + oldNum := oldHead.Number.Uint64() + newNum := newHead.Number.Uint64() + + if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 { + return nil, nil + } + // Reorg seems shallow enough to pull in all transactions into memory + var ( + transactors = make(map[common.Address]struct{}) + discarded = make(map[common.Address][]*types.Transaction) + included = make(map[common.Address][]*types.Transaction) + inclusions = make(map[common.Hash]uint64) + + rem = p.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64()) + add = p.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64()) + ) + if add == nil { + // if the new head is nil, it means that something happened between + // the firing of newhead-event and _now_: most likely a + // reorg caused by sync-reversion or explicit sethead back to an + // earlier block. + log.Warn("Blobpool reset with missing new head", "number", newHead.Number, "hash", newHead.Hash()) + return nil, nil + } + if rem == nil { + // This can happen if a setHead is performed, where we simply discard + // the old head from the chain. If that is the case, we don't have the + // lost transactions anymore, and there's nothing to add. + if newNum >= oldNum { + // If we reorged to a same or higher number, then it's not a case + // of setHead + log.Warn("Blobpool reset with missing old head", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + return nil, nil + } + // If the reorg ended up on a lower number, it's indicative of setHead + // being the cause + log.Debug("Skipping blobpool reset caused by setHead", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + return nil, nil + } + // Both old and new blocks exist, traverse through the progression chain + // and accumulate the transactors and transactions + for rem.NumberU64() > add.NumberU64() { + for _, tx := range rem.Transactions() { + from, _ := p.signer.Sender(tx) + + discarded[from] = append(discarded[from], tx) + transactors[from] = struct{}{} + } + if rem = p.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by blobpool", "block", oldHead.Number, "hash", oldHead.Hash()) + return nil, nil + } + } + for add.NumberU64() > rem.NumberU64() { + for _, tx := range add.Transactions() { + from, _ := p.signer.Sender(tx) + + included[from] = append(included[from], tx) + inclusions[tx.Hash()] = add.NumberU64() + transactors[from] = struct{}{} + } + if add = p.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by blobpool", "block", newHead.Number, "hash", newHead.Hash()) + return nil, nil + } + } + for rem.Hash() != add.Hash() { + for _, tx := range rem.Transactions() { + from, _ := p.signer.Sender(tx) + + discarded[from] = append(discarded[from], tx) + transactors[from] = struct{}{} + } + if rem = p.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by blobpool", "block", oldHead.Number, "hash", oldHead.Hash()) + return nil, nil + } + for _, tx := range add.Transactions() { + from, _ := p.signer.Sender(tx) + + included[from] = append(included[from], tx) + inclusions[tx.Hash()] = add.NumberU64() + transactors[from] = struct{}{} + } + if add = p.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by blobpool", "block", newHead.Number, "hash", newHead.Hash()) + return nil, nil + } + } + // Generate the set of transactions per address to pull back into the pool, + // also updating the rest along the way + reinject := make(map[common.Address][]*types.Transaction) + for addr := range transactors { + // Generate the set that was lost to reinject into the pool + lost := make([]*types.Transaction, 0, len(discarded[addr])) + for _, tx := range types.TxDifference(discarded[addr], included[addr]) { + if p.Filter(tx) { + lost = append(lost, tx) + } + } + reinject[addr] = lost + + // Update the set that was already reincluded to track the blocks in limbo + for _, tx := range types.TxDifference(included[addr], discarded[addr]) { + if p.Filter(tx) { + p.limbo.update(tx.Hash(), inclusions[tx.Hash()]) + } + } + } + return reinject, inclusions +} + +// reinject blindly pushes a transaction previously included in the chain - and +// just reorged out - into the pool. The transaction is assumed valid (having +// been in the chain), thus the only validation needed is nonce sorting and over- +// draft checks after injection. +// +// Note, the method will not initialize the eviction cache values as those will +// be done once for all transactions belonging to an account after all individual +// transactions are injected back into the pool. +func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error { + // Retrieve the associated blob from the limbo. Without the blobs, we cannot + // add the transaction back into the pool as it is not mineable. + tx, err := p.limbo.pull(txhash) + if err != nil { + log.Error("Blobs unavailable, dropping reorged tx", "err", err) + return err + } + // TODO: seems like an easy optimization here would be getting the serialized tx + // from limbo instead of re-serializing it here. + + // Serialize the transaction back into the primary datastore. + blob, err := rlp.EncodeToBytes(tx) + if err != nil { + log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err) + return err + } + id, err := p.store.Put(blob) + if err != nil { + log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err) + return err + } + + // Update the indices and metrics + meta := newBlobTxMeta(id, p.store.Size(id), tx) + if _, ok := p.index[addr]; !ok { + if err := p.reserve(addr, true); err != nil { + log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err) + return err + } + p.index[addr] = []*blobTxMeta{meta} + p.spent[addr] = meta.costCap + p.evict.Push(addr) + } else { + p.index[addr] = append(p.index[addr], meta) + p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.costCap) + } + p.lookup[meta.hash] = meta.id + p.stored += uint64(meta.size) + return nil +} + +// SetGasTip implements txpool.SubPool, allowing the blob pool's gas requirements +// to be kept in sync with the main transaction pool's gas requirements. +func (p *BlobPool) SetGasTip(tip *big.Int) { + p.lock.Lock() + defer p.lock.Unlock() + + // Store the new minimum gas tip + old := p.gasTip + p.gasTip = uint256.MustFromBig(tip) + + // If the min miner fee increased, remove transactions below the new threshold + if old == nil || p.gasTip.Cmp(old) > 0 { + for addr, txs := range p.index { + for i, tx := range txs { + if tx.execTipCap.Cmp(p.gasTip) < 0 { + // Drop the offending transaction + var ( + ids = []uint64{tx.id} + nonces = []uint64{tx.nonce} + ) + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap) + p.stored -= uint64(tx.size) + delete(p.lookup, tx.hash) + txs[i] = nil + + // Drop everything afterwards, no gaps allowed + for j, tx := range txs[i+1:] { + ids = append(ids, tx.id) + nonces = append(nonces, tx.nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.costCap) + p.stored -= uint64(tx.size) + delete(p.lookup, tx.hash) + txs[i+1+j] = nil + } + // Clear out the dropped transactions from the index + if i > 0 { + p.index[addr] = txs[:i] + heap.Fix(p.evict, p.evict.index[addr]) + } else { + delete(p.index, addr) + delete(p.spent, addr) + + heap.Remove(p.evict, p.evict.index[addr]) + p.reserve(addr, false) + } + // Clear out the transactions from the data store + log.Warn("Dropping underpriced blob transaction", "from", addr, "rejected", tx.nonce, "tip", tx.execTipCap, "want", tip, "drop", nonces, "ids", ids) + dropUnderpricedMeter.Mark(int64(len(ids))) + + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete dropped transaction", "id", id, "err", err) + } + } + break + } + } + } + } + log.Debug("Blobpool tip threshold updated", "tip", tip) + pooltipGauge.Update(tip.Int64()) + p.updateStorageMetrics() +} + +// validateTx checks whether a transaction is valid according to the consensus +// rules and adheres to some heuristic limits of the local node (price and size). +func (p *BlobPool) validateTx(tx *types.Transaction) error { + // Ensure the transaction adheres to basic pool filters (type, size, tip) and + // consensus rules + baseOpts := &txpool.ValidationOptions{ + Config: p.chain.Config(), + Accept: 1 << types.BlobTxType, + MaxSize: txMaxSize, + MinTip: p.gasTip.ToBig(), + } + if err := txpool.ValidateTransaction(tx, p.head, p.signer, baseOpts); err != nil { + return err + } + // Ensure the transaction adheres to the stateful pool filters (nonce, balance) + stateOpts := &txpool.ValidationOptionsWithState{ + State: p.state, + + FirstNonceGap: func(addr common.Address) uint64 { + // Nonce gaps are not permitted in the blob pool, the first gap will + // be the next nonce shifted by however many transactions we already + // have pooled. + return p.state.GetNonce(addr) + uint64(len(p.index[addr])) + }, + UsedAndLeftSlots: func(addr common.Address) (int, int) { + have := len(p.index[addr]) + if have >= maxTxsPerAccount { + return have, 0 + } + return have, maxTxsPerAccount - have + }, + ExistingExpenditure: func(addr common.Address) *big.Int { + if spent := p.spent[addr]; spent != nil { + return spent.ToBig() + } + return new(big.Int) + }, + ExistingCost: func(addr common.Address, nonce uint64) *big.Int { + next := p.state.GetNonce(addr) + if uint64(len(p.index[addr])) > nonce-next { + return p.index[addr][int(tx.Nonce()-next)].costCap.ToBig() + } + return nil + }, + } + if err := txpool.ValidateTransactionWithState(tx, p.signer, stateOpts); err != nil { + return err + } + // If the transaction replaces an existing one, ensure that price bumps are + // adhered to. + var ( + from, _ = p.signer.Sender(tx) // already validated above + next = p.state.GetNonce(from) + ) + if uint64(len(p.index[from])) > tx.Nonce()-next { + prev := p.index[from][int(tx.Nonce()-next)] + // Ensure the transaction is different than the one tracked locally + if prev.hash == tx.Hash() { + return txpool.ErrAlreadyKnown + } + // Account can support the replacement, but the price bump must also be met + switch { + case tx.GasFeeCapIntCmp(prev.execFeeCap.ToBig()) <= 0: + return fmt.Errorf("%w: new tx gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.execFeeCap) + case tx.GasTipCapIntCmp(prev.execTipCap.ToBig()) <= 0: + return fmt.Errorf("%w: new tx gas tip cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.execTipCap) + case tx.BlobGasFeeCapIntCmp(prev.blobFeeCap.ToBig()) <= 0: + return fmt.Errorf("%w: new tx blob gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.blobFeeCap) + } + var ( + multiplier = uint256.NewInt(100 + p.config.PriceBump) + onehundred = uint256.NewInt(100) + + minGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.execFeeCap), onehundred) + minGasTipCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.execTipCap), onehundred) + minBlobGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.blobFeeCap), onehundred) + ) + switch { + case tx.GasFeeCapIntCmp(minGasFeeCap.ToBig()) < 0: + return fmt.Errorf("%w: new tx gas fee cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.execFeeCap, p.config.PriceBump) + case tx.GasTipCapIntCmp(minGasTipCap.ToBig()) < 0: + return fmt.Errorf("%w: new tx gas tip cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.execTipCap, p.config.PriceBump) + case tx.BlobGasFeeCapIntCmp(minBlobGasFeeCap.ToBig()) < 0: + return fmt.Errorf("%w: new tx blob gas fee cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.blobFeeCap, p.config.PriceBump) + } + } + return nil +} + +// Has returns an indicator whether subpool has a transaction cached with the +// given hash. +func (p *BlobPool) Has(hash common.Hash) bool { + p.lock.RLock() + defer p.lock.RUnlock() + + _, ok := p.lookup[hash] + return ok +} + +// Get returns a transaction if it is contained in the pool, or nil otherwise. +func (p *BlobPool) Get(hash common.Hash) *types.Transaction { + // Track the amount of time waiting to retrieve a fully resolved blob tx from + // the pool and the amount of time actually spent on pulling the data from disk. + getStart := time.Now() + p.lock.RLock() + getwaitHist.Update(time.Since(getStart).Nanoseconds()) + defer p.lock.RUnlock() + + defer func(start time.Time) { + gettimeHist.Update(time.Since(start).Nanoseconds()) + }(time.Now()) + + // Pull the blob from disk and return an assembled response + id, ok := p.lookup[hash] + if !ok { + return nil + } + data, err := p.store.Get(id) + if err != nil { + log.Error("Tracked blob transaction missing from store", "hash", hash, "id", id, "err", err) + return nil + } + item := new(types.Transaction) + if err = rlp.DecodeBytes(data, item); err != nil { + log.Error("Blobs corrupted for traced transaction", "hash", hash, "id", id, "err", err) + return nil + } + return item +} + +// Add inserts a set of blob transactions into the pool if they pass validation (both +// consensus validity and pool restrictions). +func (p *BlobPool) Add(txs []*types.Transaction, local bool, sync bool) []error { + var ( + adds = make([]*types.Transaction, 0, len(txs)) + errs = make([]error, len(txs)) + ) + for i, tx := range txs { + errs[i] = p.add(tx) + if errs[i] == nil { + adds = append(adds, tx.WithoutBlobTxSidecar()) + } + } + if len(adds) > 0 { + p.discoverFeed.Send(core.NewTxsEvent{Txs: adds}) + p.insertFeed.Send(core.NewTxsEvent{Txs: adds}) + } + return errs +} + +// add inserts a new blob transaction into the pool if it passes validation (both +// consensus validity and pool restrictions). +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 from the network, so this method will act as the overload + // protection for fetches. + waitStart := time.Now() + p.lock.Lock() + addwaitHist.Update(time.Since(waitStart).Nanoseconds()) + defer p.lock.Unlock() + + defer func(start time.Time) { + addtimeHist.Update(time.Since(start).Nanoseconds()) + }(time.Now()) + + // Ensure the transaction is valid from all perspectives + if err := p.validateTx(tx); err != nil { + log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err) + switch { + case errors.Is(err, txpool.ErrUnderpriced): + addUnderpricedMeter.Mark(1) + case errors.Is(err, core.ErrNonceTooLow): + addStaleMeter.Mark(1) + case errors.Is(err, core.ErrNonceTooHigh): + addGappedMeter.Mark(1) + case errors.Is(err, core.ErrInsufficientFunds): + addOverdraftedMeter.Mark(1) + case errors.Is(err, txpool.ErrAccountLimitExceeded): + addOvercappedMeter.Mark(1) + case errors.Is(err, txpool.ErrReplaceUnderpriced): + addNoreplaceMeter.Mark(1) + default: + addInvalidMeter.Mark(1) + } + return err + } + // If the address is not yet known, request exclusivity to track the account + // only by this subpool until all transactions are evicted + from, _ := types.Sender(p.signer, tx) // already validated above + if _, ok := p.index[from]; !ok { + if err := p.reserve(from, true); err != nil { + addNonExclusiveMeter.Mark(1) + return err + } + defer func() { + // If the transaction is rejected by some post-validation check, remove + // the lock on the reservation set. + // + // Note, `err` here is the named error return, which will be initialized + // by a return statement before running deferred methods. Take care with + // removing or subscoping err as it will break this clause. + if err != nil { + p.reserve(from, false) + } + }() + } + // Transaction permitted into the pool from a nonce and cost perspective, + // insert it into the database and update the indices + blob, err := rlp.EncodeToBytes(tx) + if err != nil { + log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err) + return err + } + id, err := p.store.Put(blob) + if err != nil { + return err + } + meta := newBlobTxMeta(id, p.store.Size(id), tx) + + var ( + next = p.state.GetNonce(from) + offset = int(tx.Nonce() - next) + newacc = false + ) + var oldEvictionExecFeeJumps, oldEvictionBlobFeeJumps float64 + if txs, ok := p.index[from]; ok { + oldEvictionExecFeeJumps = txs[len(txs)-1].evictionExecFeeJumps + oldEvictionBlobFeeJumps = txs[len(txs)-1].evictionBlobFeeJumps + } + if len(p.index[from]) > offset { + // Transaction replaces a previously queued one + dropReplacedMeter.Mark(1) + + prev := p.index[from][offset] + if err := p.store.Delete(prev.id); err != nil { + // Shitty situation, but try to recover gracefully instead of going boom + log.Error("Failed to delete replaced transaction", "id", prev.id, "err", err) + } + // Update the transaction index + p.index[from][offset] = meta + p.spent[from] = new(uint256.Int).Sub(p.spent[from], prev.costCap) + p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap) + + delete(p.lookup, prev.hash) + p.lookup[meta.hash] = meta.id + p.stored += uint64(meta.size) - uint64(prev.size) + } else { + // Transaction extends previously scheduled ones + p.index[from] = append(p.index[from], meta) + if _, ok := p.spent[from]; !ok { + p.spent[from] = new(uint256.Int) + newacc = true + } + p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap) + p.lookup[meta.hash] = meta.id + p.stored += uint64(meta.size) + } + // Recompute the rolling eviction fields. In case of a replacement, this will + // recompute all subsequent fields. In case of an append, this will only do + // the fresh calculation. + txs := p.index[from] + + for i := offset; i < len(txs); i++ { + // The first transaction will always use itself + if i == 0 { + txs[0].evictionExecTip = txs[0].execTipCap + txs[0].evictionExecFeeJumps = txs[0].basefeeJumps + txs[0].evictionBlobFeeJumps = txs[0].blobfeeJumps + + continue + } + // Subsequent transactions will use a rolling calculation + txs[i].evictionExecTip = txs[i-1].evictionExecTip + if txs[i].evictionExecTip.Cmp(txs[i].execTipCap) > 0 { + txs[i].evictionExecTip = txs[i].execTipCap + } + txs[i].evictionExecFeeJumps = txs[i-1].evictionExecFeeJumps + if txs[i].evictionExecFeeJumps > txs[i].basefeeJumps { + txs[i].evictionExecFeeJumps = txs[i].basefeeJumps + } + txs[i].evictionBlobFeeJumps = txs[i-1].evictionBlobFeeJumps + if txs[i].evictionBlobFeeJumps > txs[i].blobfeeJumps { + txs[i].evictionBlobFeeJumps = txs[i].blobfeeJumps + } + } + // Update the eviction heap with the new information: + // - If the transaction is from a new account, add it to the heap + // - If the account had a singleton tx replaced, update the heap (new price caps) + // - If the account has a transaction replaced or appended, update the heap if significantly changed + switch { + case newacc: + heap.Push(p.evict, from) + + case len(txs) == 1: // 1 tx and not a new acc, must be replacement + heap.Fix(p.evict, p.evict.index[from]) + + default: // replacement or new append + evictionExecFeeDiff := oldEvictionExecFeeJumps - txs[len(txs)-1].evictionExecFeeJumps + evictionBlobFeeDiff := oldEvictionBlobFeeJumps - txs[len(txs)-1].evictionBlobFeeJumps + + if math.Abs(evictionExecFeeDiff) > 0.001 || math.Abs(evictionBlobFeeDiff) > 0.001 { // need math.Abs, can go up and down + heap.Fix(p.evict, p.evict.index[from]) + } + } + // If the pool went over the allowed data limit, evict transactions until + // we're again below the threshold + for p.stored > p.config.Datacap { + p.drop() + } + p.updateStorageMetrics() + + addValidMeter.Mark(1) + return nil +} + +// drop removes the worst transaction from the pool. It is primarily used when a +// freshly added transaction overflows the pool and needs to evict something. The +// method is also called on startup if the user resizes their storage, might be an +// expensive run but it should be fine-ish. +func (p *BlobPool) drop() { + // Peek at the account with the worse transaction set to evict from (Go's heap + // stores the minimum at index zero of the heap slice) and retrieve it's last + // transaction. + var ( + from = p.evict.addrs[0] // cannot call drop on empty pool + + txs = p.index[from] + drop = txs[len(txs)-1] + last = len(txs) == 1 + ) + // Remove the transaction from the pool's index + if last { + delete(p.index, from) + delete(p.spent, from) + p.reserve(from, false) + } else { + txs[len(txs)-1] = nil + txs = txs[:len(txs)-1] + + p.index[from] = txs + p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.costCap) + } + p.stored -= uint64(drop.size) + delete(p.lookup, drop.hash) + + // Remove the transaction from the pool's eviction heap: + // - If the entire account was dropped, pop off the address + // - Otherwise, if the new tail has better eviction caps, fix the heap + if last { + heap.Pop(p.evict) + } else { + tail := txs[len(txs)-1] // new tail, surely exists + + evictionExecFeeDiff := tail.evictionExecFeeJumps - drop.evictionExecFeeJumps + evictionBlobFeeDiff := tail.evictionBlobFeeJumps - drop.evictionBlobFeeJumps + + if evictionExecFeeDiff > 0.001 || evictionBlobFeeDiff > 0.001 { // no need for math.Abs, monotonic decreasing + heap.Fix(p.evict, 0) + } + } + // Remove the transaction from the data store + log.Debug("Evicting overflown blob transaction", "from", from, "evicted", drop.nonce, "id", drop.id) + dropOverflownMeter.Mark(1) + + if err := p.store.Delete(drop.id); err != nil { + log.Error("Failed to drop evicted transaction", "id", drop.id, "err", err) + } +} + +// Pending retrieves all currently processable transactions, grouped by origin +// account and sorted by nonce. +// +// The transactions can also be pre-filtered by the dynamic fee components to +// reduce allocations and load on downstream subsystems. +func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction { + // If only plain transactions are requested, this pool is unsuitable as it + // contains none, don't even bother. + if filter.OnlyPlainTxs { + return nil + } + // Track the amount of time waiting to retrieve the list of pending blob txs + // from the pool and the amount of time actually spent on assembling the data. + // The latter will be pretty much moot, but we've kept it to have symmetric + // across all user operations. + pendStart := time.Now() + p.lock.RLock() + pendwaitHist.Update(time.Since(pendStart).Nanoseconds()) + defer p.lock.RUnlock() + + execStart := time.Now() + defer func() { + pendtimeHist.Update(time.Since(execStart).Nanoseconds()) + }() + + pending := make(map[common.Address][]*txpool.LazyTransaction, len(p.index)) + for addr, txs := range p.index { + lazies := make([]*txpool.LazyTransaction, 0, len(txs)) + for _, tx := range txs { + // If transaction filtering was requested, discard badly priced ones + if filter.MinTip != nil && filter.BaseFee != nil { + if tx.execFeeCap.Lt(filter.BaseFee) { + break // basefee too low, cannot be included, discard rest of txs from the account + } + tip := new(uint256.Int).Sub(tx.execFeeCap, filter.BaseFee) + if tip.Gt(tx.execTipCap) { + tip = tx.execTipCap + } + if tip.Lt(filter.MinTip) { + break // allowed or remaining tip too low, cannot be included, discard rest of txs from the account + } + } + if filter.BlobFee != nil { + if tx.blobFeeCap.Lt(filter.BlobFee) { + break // blobfee too low, cannot be included, discard rest of txs from the account + } + } + // Transaction was accepted according to the filter, append to the pending list + lazies = append(lazies, &txpool.LazyTransaction{ + Pool: p, + Hash: tx.hash, + Time: execStart, // TODO(karalabe): Maybe save these and use that? + GasFeeCap: tx.execFeeCap, + GasTipCap: tx.execTipCap, + Gas: tx.execGas, + BlobGas: tx.blobGas, + }) + } + if len(lazies) > 0 { + pending[addr] = lazies + } + } + return pending +} + +// updateStorageMetrics retrieves a bunch of stats from the data store and pushes +// them out as metrics. +func (p *BlobPool) updateStorageMetrics() { + stats := p.store.Infos() + + var ( + dataused uint64 + datareal uint64 + slotused uint64 + + oversizedDataused uint64 + oversizedDatagaps uint64 + oversizedSlotused uint64 + oversizedSlotgaps uint64 + ) + for _, shelf := range stats.Shelves { + slotDataused := shelf.FilledSlots * uint64(shelf.SlotSize) + slotDatagaps := shelf.GappedSlots * uint64(shelf.SlotSize) + + dataused += slotDataused + datareal += slotDataused + slotDatagaps + slotused += shelf.FilledSlots + + metrics.GetOrRegisterGauge(fmt.Sprintf(shelfDatausedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDataused)) + metrics.GetOrRegisterGauge(fmt.Sprintf(shelfDatagapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDatagaps)) + metrics.GetOrRegisterGauge(fmt.Sprintf(shelfSlotusedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.FilledSlots)) + metrics.GetOrRegisterGauge(fmt.Sprintf(shelfSlotgapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.GappedSlots)) + + if shelf.SlotSize/blobSize > maxBlobsPerTransaction { + oversizedDataused += slotDataused + oversizedDatagaps += slotDatagaps + oversizedSlotused += shelf.FilledSlots + oversizedSlotgaps += shelf.GappedSlots + } + } + datausedGauge.Update(int64(dataused)) + datarealGauge.Update(int64(datareal)) + slotusedGauge.Update(int64(slotused)) + + oversizedDatausedGauge.Update(int64(oversizedDataused)) + oversizedDatagapsGauge.Update(int64(oversizedDatagaps)) + oversizedSlotusedGauge.Update(int64(oversizedSlotused)) + oversizedSlotgapsGauge.Update(int64(oversizedSlotgaps)) + + p.updateLimboMetrics() +} + +// updateLimboMetrics retrieves a bunch of stats from the limbo store and pushes +// them out as metrics. +func (p *BlobPool) updateLimboMetrics() { + stats := p.limbo.store.Infos() + + var ( + dataused uint64 + datareal uint64 + slotused uint64 + ) + for _, shelf := range stats.Shelves { + slotDataused := shelf.FilledSlots * uint64(shelf.SlotSize) + slotDatagaps := shelf.GappedSlots * uint64(shelf.SlotSize) + + dataused += slotDataused + datareal += slotDataused + slotDatagaps + slotused += shelf.FilledSlots + + metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfDatausedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDataused)) + metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfDatagapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDatagaps)) + metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfSlotusedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.FilledSlots)) + metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfSlotgapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.GappedSlots)) + } + limboDatausedGauge.Update(int64(dataused)) + limboDatarealGauge.Update(int64(datareal)) + limboSlotusedGauge.Update(int64(slotused)) +} + +// SubscribeTransactions registers a subscription for new transaction events, +// supporting feeding only newly seen or also resurrected transactions. +func (p *BlobPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { + if reorgs { + return p.insertFeed.Subscribe(ch) + } else { + return p.discoverFeed.Subscribe(ch) + } +} + +// Nonce returns the next nonce of an account, with all transactions executable +// by the pool already applied on top. +func (p *BlobPool) Nonce(addr common.Address) uint64 { + // We need a write lock here, since state.GetNonce might write the cache. + p.lock.Lock() + defer p.lock.Unlock() + + if txs, ok := p.index[addr]; ok { + return txs[len(txs)-1].nonce + 1 + } + return p.state.GetNonce(addr) +} + +// Stats retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions. +func (p *BlobPool) Stats() (int, int) { + p.lock.RLock() + defer p.lock.RUnlock() + + var pending int + for _, txs := range p.index { + pending += len(txs) + } + return pending, 0 // No non-executable txs in the blob pool +} + +// Content retrieves the data content of the transaction pool, returning all the +// pending as well as queued transactions, grouped by account and sorted by nonce. +// +// For the blob pool, this method will return nothing for now. +// TODO(karalabe): Abstract out the returned metadata. +func (p *BlobPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + return make(map[common.Address][]*types.Transaction), make(map[common.Address][]*types.Transaction) +} + +// ContentFrom retrieves the data content of the transaction pool, returning the +// pending as well as queued transactions of this address, grouped by nonce. +// +// For the blob pool, this method will return nothing for now. +// TODO(karalabe): Abstract out the returned metadata. +func (p *BlobPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + return []*types.Transaction{}, []*types.Transaction{} +} + +// Locals retrieves the accounts currently considered local by the pool. +// +// There is no notion of local accounts in the blob pool. +func (p *BlobPool) Locals() []common.Address { + return []common.Address{} +} + +// Status returns the known status (unknown/pending/queued) of a transaction +// identified by their hashes. +func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus { + if p.Has(hash) { + return txpool.TxStatusPending + } + return txpool.TxStatusUnknown +} diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go new file mode 100644 index 0000000..d658a6d --- /dev/null +++ b/core/txpool/blobpool/blobpool_test.go @@ -0,0 +1,1376 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import ( + "bytes" + "crypto/ecdsa" + "crypto/sha256" + "errors" + "math" + "math/big" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/billy" + "github.com/holiman/uint256" +) + +var ( + emptyBlob = new(kzg4844.Blob) + emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) + emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) + emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) +) + +// Chain configuration with Cancun enabled. +// +// TODO(karalabe): replace with params.MainnetChainConfig after Cancun. +var testChainConfig *params.ChainConfig + +func init() { + testChainConfig = new(params.ChainConfig) + *testChainConfig = *params.MainnetChainConfig + + testChainConfig.CancunTime = new(uint64) + *testChainConfig.CancunTime = uint64(time.Now().Unix()) +} + +// testBlockChain is a mock of the live chain for testing the pool. +type testBlockChain struct { + config *params.ChainConfig + basefee *uint256.Int + blobfee *uint256.Int + statedb *state.StateDB +} + +func (bc *testBlockChain) Config() *params.ChainConfig { + return bc.config +} + +func (bc *testBlockChain) CurrentBlock() *types.Header { + // Yolo, life is too short to invert mist.CalcBaseFee and misc.CalcBlobFee, + // just binary search it them. + + // The base fee at 5714 ETH translates into the 21000 base gas higher than + // mainnet ether existence, use that as a cap for the tests. + var ( + blockNumber = new(big.Int).Add(bc.config.LondonBlock, big.NewInt(1)) + blockTime = *bc.config.CancunTime + 1 + gasLimit = uint64(30_000_000) + ) + lo := new(big.Int) + hi := new(big.Int).Mul(big.NewInt(5714), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) + + for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 { + mid := new(big.Int).Add(lo, hi) + mid.Div(mid, big.NewInt(2)) + + if eip1559.CalcBaseFee(bc.config, &types.Header{ + Number: blockNumber, + GasLimit: gasLimit, + GasUsed: 0, + BaseFee: mid, + }).Cmp(bc.basefee.ToBig()) > 0 { + hi = mid + } else { + lo = mid + } + } + baseFee := lo + + // The excess blob gas at 2^27 translates into a blob fee higher than mainnet + // ether existence, use that as a cap for the tests. + lo = new(big.Int) + hi = new(big.Int).Exp(big.NewInt(2), big.NewInt(27), nil) + + for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 { + mid := new(big.Int).Add(lo, hi) + mid.Div(mid, big.NewInt(2)) + + if eip4844.CalcBlobFee(mid.Uint64()).Cmp(bc.blobfee.ToBig()) > 0 { + hi = mid + } else { + lo = mid + } + } + excessBlobGas := lo.Uint64() + + return &types.Header{ + Number: blockNumber, + Time: blockTime, + GasLimit: gasLimit, + BaseFee: baseFee, + ExcessBlobGas: &excessBlobGas, + } +} + +func (bc *testBlockChain) CurrentFinalBlock() *types.Header { + return &types.Header{ + Number: big.NewInt(0), + } +} + +func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { + return nil +} + +func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { + return bc.statedb, nil +} + +// makeAddressReserver is a utility method to sanity check that accounts are +// properly reserved by the blobpool (no duplicate reserves or unreserves). +func makeAddressReserver() txpool.AddressReserver { + var ( + reserved = make(map[common.Address]struct{}) + lock sync.Mutex + ) + return func(addr common.Address, reserve bool) error { + lock.Lock() + defer lock.Unlock() + + _, exists := reserved[addr] + if reserve { + if exists { + panic("already reserved") + } + reserved[addr] = struct{}{} + return nil + } + if !exists { + panic("not reserved") + } + delete(reserved, addr) + return nil + } +} + +// makeTx is a utility method to construct a random blob transaction and sign it +// with a valid key, only setting the interesting fields from the perspective of +// the blob pool. +func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction { + blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap) + return types.MustSignNewTx(key, types.LatestSigner(testChainConfig), blobtx) +} + +// makeUnsignedTx is a utility method to construct a random blob transaction +// without signing it. +func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx { + return &types.BlobTx{ + ChainID: uint256.MustFromBig(testChainConfig.ChainID), + Nonce: nonce, + GasTipCap: uint256.NewInt(gasTipCap), + GasFeeCap: uint256.NewInt(gasFeeCap), + Gas: 21000, + BlobFeeCap: uint256.NewInt(blobFeeCap), + BlobHashes: []common.Hash{emptyBlobVHash}, + Value: uint256.NewInt(100), + Sidecar: &types.BlobTxSidecar{ + Blobs: []kzg4844.Blob{*emptyBlob}, + Commitments: []kzg4844.Commitment{emptyBlobCommit}, + Proofs: []kzg4844.Proof{emptyBlobProof}, + }, + } +} + +// verifyPoolInternals iterates over all the transactions in the pool and checks +// that sort orders, calculated fields, cumulated fields are correct. +func verifyPoolInternals(t *testing.T, pool *BlobPool) { + // Mark this method as a helper to remove from stack traces + t.Helper() + + // Verify that all items in the index are present in the lookup and nothing more + seen := make(map[common.Hash]struct{}) + for addr, txs := range pool.index { + for _, tx := range txs { + if _, ok := seen[tx.hash]; ok { + t.Errorf("duplicate hash #%x in transaction index: address %s, nonce %d", tx.hash, addr, tx.nonce) + } + seen[tx.hash] = struct{}{} + } + } + for hash, id := range pool.lookup { + if _, ok := seen[hash]; !ok { + t.Errorf("lookup entry missing from transaction index: hash #%x, id %d", hash, id) + } + delete(seen, hash) + } + for hash := range seen { + t.Errorf("indexed transaction hash #%x missing from lookup table", hash) + } + // Verify that transactions are sorted per account and contain no nonce gaps + for addr, txs := range pool.index { + for i := 1; i < len(txs); i++ { + if txs[i].nonce != txs[i-1].nonce+1 { + t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1) + } + } + } + // Verify that calculated evacuation thresholds are correct + for addr, txs := range pool.index { + if !txs[0].evictionExecTip.Eq(txs[0].execTipCap) { + t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, 0, txs[0].evictionExecTip, txs[0].execTipCap) + } + if math.Abs(txs[0].evictionExecFeeJumps-txs[0].basefeeJumps) > 0.001 { + t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionExecFeeJumps, txs[0].basefeeJumps) + } + if math.Abs(txs[0].evictionBlobFeeJumps-txs[0].blobfeeJumps) > 0.001 { + t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionBlobFeeJumps, txs[0].blobfeeJumps) + } + for i := 1; i < len(txs); i++ { + wantExecTip := txs[i-1].evictionExecTip + if wantExecTip.Gt(txs[i].execTipCap) { + wantExecTip = txs[i].execTipCap + } + if !txs[i].evictionExecTip.Eq(wantExecTip) { + t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, i, txs[i].evictionExecTip, wantExecTip) + } + + wantExecFeeJumps := txs[i-1].evictionExecFeeJumps + if wantExecFeeJumps > txs[i].basefeeJumps { + wantExecFeeJumps = txs[i].basefeeJumps + } + if math.Abs(txs[i].evictionExecFeeJumps-wantExecFeeJumps) > 0.001 { + t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionExecFeeJumps, wantExecFeeJumps) + } + + wantBlobFeeJumps := txs[i-1].evictionBlobFeeJumps + if wantBlobFeeJumps > txs[i].blobfeeJumps { + wantBlobFeeJumps = txs[i].blobfeeJumps + } + if math.Abs(txs[i].evictionBlobFeeJumps-wantBlobFeeJumps) > 0.001 { + t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionBlobFeeJumps, wantBlobFeeJumps) + } + } + } + // Verify that account balance accumulations are correct + for addr, txs := range pool.index { + spent := new(uint256.Int) + for _, tx := range txs { + spent.Add(spent, tx.costCap) + } + if !pool.spent[addr].Eq(spent) { + t.Errorf("addr %v expenditure mismatch: have %d, want %d", addr, pool.spent[addr], spent) + } + } + // Verify that pool storage size is correct + var stored uint64 + for _, txs := range pool.index { + for _, tx := range txs { + stored += uint64(tx.size) + } + } + if pool.stored != stored { + t.Errorf("pool storage mismatch: have %d, want %d", pool.stored, stored) + } + // Verify the price heap internals + verifyHeapInternals(t, pool.evict) +} + +// Tests that transactions can be loaded from disk on startup and that they are +// correctly discarded if invalid. +// +// - 1. A transaction that cannot be decoded must be dropped +// - 2. A transaction that cannot be recovered (bad signature) must be dropped +// - 3. All transactions after a nonce gap must be dropped +// - 4. All transactions after an already included nonce must be dropped +// - 5. All transactions after an underpriced one (including it) must be dropped +// - 6. All transactions after an overdrafting sequence must be dropped +// - 7. All transactions exceeding the per-account limit must be dropped +// +// Furthermore, some strange corner-cases can also occur after a crash, as Billy's +// simplicity also allows it to resurrect past deleted entities: +// +// - 8. Fully duplicate transactions (matching hash) must be dropped +// - 9. Duplicate nonces from the same account must be dropped +func TestOpenDrops(t *testing.T) { + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert a malformed transaction to verify that decoding errors (or format + // changes) are handled gracefully (case 1) + malformed, _ := store.Put([]byte("this is a badly encoded transaction")) + + // Insert a transaction with a bad signature to verify that stale junk after + // potential hard-forks can get evicted (case 2) + tx := types.NewTx(&types.BlobTx{ + ChainID: uint256.MustFromBig(testChainConfig.ChainID), + GasTipCap: new(uint256.Int), + GasFeeCap: new(uint256.Int), + Gas: 0, + Value: new(uint256.Int), + Data: nil, + BlobFeeCap: new(uint256.Int), + V: new(uint256.Int), + R: new(uint256.Int), + S: new(uint256.Int), + }) + blob, _ := rlp.EncodeToBytes(tx) + badsig, _ := store.Put(blob) + + // Insert a sequence of transactions with a nonce gap in between to verify + // that anything gapped will get evicted (case 3). + var ( + gapper, _ = crypto.GenerateKey() + + valids = make(map[uint64]struct{}) + gapped = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5 + tx := makeTx(nonce, 1, 1, 1, gapper) + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + if nonce < 2 { + valids[id] = struct{}{} + } else { + gapped[id] = struct{}{} + } + } + // Insert a sequence of transactions with a gapped starting nonce to verify + // that the entire set will get dropped (case 3). + var ( + dangler, _ = crypto.GenerateKey() + dangling = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling + tx := makeTx(nonce, 1, 1, 1, dangler) + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + dangling[id] = struct{}{} + } + // Insert a sequence of transactions with already passed nonces to veirfy + // that the entire set will get dropped (case 4). + var ( + filler, _ = crypto.GenerateKey() + filled = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled + tx := makeTx(nonce, 1, 1, 1, filler) + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + filled[id] = struct{}{} + } + // Insert a sequence of transactions with partially passed nonces to verify + // that the included part of the set will get dropped (case 4). + var ( + overlapper, _ = crypto.GenerateKey() + overlapped = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled + tx := makeTx(nonce, 1, 1, 1, overlapper) + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + if nonce >= 2 { + valids[id] = struct{}{} + } else { + overlapped[id] = struct{}{} + } + } + // Insert a sequence of transactions with an underpriced first to verify that + // the entire set will get dropped (case 5). + var ( + underpayer, _ = crypto.GenerateKey() + underpaid = make(map[uint64]struct{}) + ) + for i := 0; i < 5; i++ { // make #0 underpriced + var tx *types.Transaction + if i == 0 { + tx = makeTx(uint64(i), 0, 0, 0, underpayer) + } else { + tx = makeTx(uint64(i), 1, 1, 1, underpayer) + } + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + underpaid[id] = struct{}{} + } + + // Insert a sequence of transactions with an underpriced in between to verify + // that it and anything newly gapped will get evicted (case 5). + var ( + outpricer, _ = crypto.GenerateKey() + outpriced = make(map[uint64]struct{}) + ) + for i := 0; i < 5; i++ { // make #2 underpriced + var tx *types.Transaction + if i == 2 { + tx = makeTx(uint64(i), 0, 0, 0, outpricer) + } else { + tx = makeTx(uint64(i), 1, 1, 1, outpricer) + } + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + if i < 2 { + valids[id] = struct{}{} + } else { + outpriced[id] = struct{}{} + } + } + // Insert a sequence of transactions fully overdrafted to verify that the + // entire set will get invalidated (case 6). + var ( + exceeder, _ = crypto.GenerateKey() + exceeded = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2} { // nonce 0 overdrafts the account + var tx *types.Transaction + if nonce == 0 { + tx = makeTx(nonce, 1, 100, 1, exceeder) + } else { + tx = makeTx(nonce, 1, 1, 1, exceeder) + } + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + exceeded[id] = struct{}{} + } + // Insert a sequence of transactions partially overdrafted to verify that part + // of the set will get invalidated (case 6). + var ( + overdrafter, _ = crypto.GenerateKey() + overdrafted = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2} { // nonce 1 overdrafts the account + var tx *types.Transaction + if nonce == 1 { + tx = makeTx(nonce, 1, 100, 1, overdrafter) + } else { + tx = makeTx(nonce, 1, 1, 1, overdrafter) + } + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + if nonce < 1 { + valids[id] = struct{}{} + } else { + overdrafted[id] = struct{}{} + } + } + // Insert a sequence of transactions overflowing the account cap to verify + // that part of the set will get invalidated (case 7). + var ( + overcapper, _ = crypto.GenerateKey() + overcapped = make(map[uint64]struct{}) + ) + for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ { + blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper)) + + id, _ := store.Put(blob) + if nonce < maxTxsPerAccount { + valids[id] = struct{}{} + } else { + overcapped[id] = struct{}{} + } + } + // Insert a batch of duplicated transactions to verify that only one of each + // version will remain (case 8). + var ( + duplicater, _ = crypto.GenerateKey() + duplicated = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2} { + blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, duplicater)) + + for i := 0; i < int(nonce)+1; i++ { + id, _ := store.Put(blob) + if i == 0 { + valids[id] = struct{}{} + } else { + duplicated[id] = struct{}{} + } + } + } + // Insert a batch of duplicated nonces to verify that only one of each will + // remain (case 9). + var ( + repeater, _ = crypto.GenerateKey() + repeated = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2} { + for i := 0; i < int(nonce)+1; i++ { + blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater)) + + id, _ := store.Put(blob) + if i == 0 { + valids[id] = struct{}{} + } else { + repeated[id] = struct{}{} + } + } + } + store.Close() + + // Create a blob pool out of the pre-seeded data + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3) + statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2) + statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), uint256.NewInt(10000000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(crypto.PubkeyToAddress(duplicater.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(crypto.PubkeyToAddress(repeater.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true) + + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(params.InitialBaseFee), + blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + defer pool.Close() + + // Verify that the malformed (case 1), badly signed (case 2) and gapped (case + // 3) txs have been deleted from the pool + alive := make(map[uint64]struct{}) + for _, txs := range pool.index { + for _, tx := range txs { + switch tx.id { + case malformed: + t.Errorf("malformed RLP transaction remained in storage") + case badsig: + t.Errorf("invalidly signed transaction remained in storage") + default: + if _, ok := dangling[tx.id]; ok { + t.Errorf("dangling transaction remained in storage: %d", tx.id) + } else if _, ok := filled[tx.id]; ok { + t.Errorf("filled transaction remained in storage: %d", tx.id) + } else if _, ok := overlapped[tx.id]; ok { + t.Errorf("overlapped transaction remained in storage: %d", tx.id) + } else if _, ok := gapped[tx.id]; ok { + t.Errorf("gapped transaction remained in storage: %d", tx.id) + } else if _, ok := underpaid[tx.id]; ok { + t.Errorf("underpaid transaction remained in storage: %d", tx.id) + } else if _, ok := outpriced[tx.id]; ok { + t.Errorf("outpriced transaction remained in storage: %d", tx.id) + } else if _, ok := exceeded[tx.id]; ok { + t.Errorf("fully overdrafted transaction remained in storage: %d", tx.id) + } else if _, ok := overdrafted[tx.id]; ok { + t.Errorf("partially overdrafted transaction remained in storage: %d", tx.id) + } else if _, ok := overcapped[tx.id]; ok { + t.Errorf("overcapped transaction remained in storage: %d", tx.id) + } else if _, ok := duplicated[tx.id]; ok { + t.Errorf("duplicated transaction remained in storage: %d", tx.id) + } else if _, ok := repeated[tx.id]; ok { + t.Errorf("repeated nonce transaction remained in storage: %d", tx.id) + } else { + alive[tx.id] = struct{}{} + } + } + } + } + // Verify that the rest of the transactions remained alive + if len(alive) != len(valids) { + t.Errorf("valid transaction count mismatch: have %d, want %d", len(alive), len(valids)) + } + for id := range alive { + if _, ok := valids[id]; !ok { + t.Errorf("extra transaction %d", id) + } + } + for id := range valids { + if _, ok := alive[id]; !ok { + t.Errorf("missing transaction %d", id) + } + } + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) +} + +// Tests that transactions loaded from disk are indexed correctly. +// +// - 1. Transactions must be grouped 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.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert a sequence of transactions with varying price points to check that + // the cumulative minimum will be maintained. + var ( + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + + txExecTipCaps = []uint64{10, 25, 5, 7, 1, 100} + txExecFeeCaps = []uint64{100, 90, 200, 10, 80, 300} + txBlobFeeCaps = []uint64{55, 66, 77, 33, 22, 11} + + //basefeeJumps = []float64{39.098, 38.204, 44.983, 19.549, 37.204, 48.426} // log 1.125 (exec fee cap) + //blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap) + + evictExecTipCaps = []uint64{10, 10, 5, 5, 1, 1} + evictExecFeeJumps = []float64{39.098, 38.204, 38.204, 19.549, 19.549, 19.549} // min(log 1.125 (exec fee cap)) + evictBlobFeeJumps = []float64{34.023, 34.023, 34.023, 29.686, 26.243, 20.358} // min(log 1.125 (blob fee cap)) + + totalSpent = uint256.NewInt(21000*(100+90+200+10+80+300) + blobSize*(55+66+77+33+22+11) + 100*6) // 21000 gas x price + 128KB x blobprice + value + ) + for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load + tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key) + blob, _ := rlp.EncodeToBytes(tx) + store.Put(blob) + } + store.Close() + + // Create a blob pool out of the pre-seeded data + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true) + + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(params.InitialBaseFee), + blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + defer pool.Close() + + // Verify that the transactions have been sorted by nonce (case 1) + for i := 0; i < len(pool.index[addr]); i++ { + if pool.index[addr][i].nonce != uint64(i) { + t.Errorf("tx %d nonce mismatch: have %d, want %d", i, pool.index[addr][i].nonce, uint64(i)) + } + } + // Verify that the cumulative fee minimums have been correctly calculated (case 2) + for i, cap := range evictExecTipCaps { + if !pool.index[addr][i].evictionExecTip.Eq(uint256.NewInt(cap)) { + t.Errorf("eviction tip cap %d mismatch: have %d, want %d", i, pool.index[addr][i].evictionExecTip, cap) + } + } + for i, jumps := range evictExecFeeJumps { + if math.Abs(pool.index[addr][i].evictionExecFeeJumps-jumps) > 0.001 { + t.Errorf("eviction fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionExecFeeJumps, jumps) + } + } + for i, jumps := range evictBlobFeeJumps { + if math.Abs(pool.index[addr][i].evictionBlobFeeJumps-jumps) > 0.001 { + t.Errorf("eviction blob fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionBlobFeeJumps, jumps) + } + } + // Verify that the balance usage has been correctly calculated (case 3) + if !pool.spent[addr].Eq(totalSpent) { + t.Errorf("expenditure mismatch: have %d, want %d", pool.spent[addr], totalSpent) + } + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) +} + +// Tests that after indexing all the loaded transactions from disk, a price heap +// is correctly constructed based on the head basefee and blobfee. +func TestOpenHeap(t *testing.T) { + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert a few transactions from a few accounts. To remove randomness from + // the heap initialization, use a deterministic account/tx/priority ordering. + var ( + key1, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + key3, _ = crypto.GenerateKey() + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + ) + if bytes.Compare(addr1[:], addr2[:]) > 0 { + key1, addr1, key2, addr2 = key2, addr2, key1, addr1 + } + if bytes.Compare(addr1[:], addr3[:]) > 0 { + key1, addr1, key3, addr3 = key3, addr3, key1, addr1 + } + if bytes.Compare(addr2[:], addr3[:]) > 0 { + key2, addr2, key3, addr3 = key3, addr3, key2, addr2 + } + var ( + tx1 = makeTx(0, 1, 1000, 90, key1) + tx2 = makeTx(0, 1, 800, 70, key2) + tx3 = makeTx(0, 1, 1500, 110, key3) + + blob1, _ = rlp.EncodeToBytes(tx1) + blob2, _ = rlp.EncodeToBytes(tx2) + blob3, _ = rlp.EncodeToBytes(tx3) + + heapOrder = []common.Address{addr2, addr1, addr3} + heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2} + ) + store.Put(blob1) + store.Put(blob2) + store.Put(blob3) + store.Close() + + // Create a blob pool out of the pre-seeded data + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true) + + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + defer pool.Close() + + // Verify that the heap's internal state matches the expectations + for i, addr := range pool.evict.addrs { + if addr != heapOrder[i] { + t.Errorf("slot %d mismatch: have %v, want %v", i, addr, heapOrder[i]) + } + } + for addr, i := range pool.evict.index { + if i != heapIndex[addr] { + t.Errorf("index for %v mismatch: have %d, want %d", addr, i, heapIndex[addr]) + } + } + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) +} + +// Tests that after the pool's previous state is loaded back, any transactions +// over the new storage cap will get dropped. +func TestOpenCap(t *testing.T) { + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert a few transactions from a few accounts + var ( + key1, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + key3, _ = crypto.GenerateKey() + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + + tx1 = makeTx(0, 1, 1000, 100, key1) + tx2 = makeTx(0, 1, 800, 70, key2) + tx3 = makeTx(0, 1, 1500, 110, key3) + + blob1, _ = rlp.EncodeToBytes(tx1) + blob2, _ = rlp.EncodeToBytes(tx2) + blob3, _ = rlp.EncodeToBytes(tx3) + + keep = []common.Address{addr1, addr3} + drop = []common.Address{addr2} + size = uint64(2 * (txAvgSize + blobSize)) + ) + store.Put(blob1) + store.Put(blob2) + store.Put(blob3) + store.Close() + + // Verify pool capping twice: first by reducing the data cap, then restarting + // with a high cap to ensure everything was persisted previously + for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} { + // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true) + + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage, Datacap: datacap}, chain) + if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + // Verify that enough transactions have been dropped to get the pool's size + // under the requested limit + if len(pool.index) != len(keep) { + t.Errorf("tracked account count mismatch: have %d, want %d", len(pool.index), len(keep)) + } + for _, addr := range keep { + if _, ok := pool.index[addr]; !ok { + t.Errorf("expected account %v missing from pool", addr) + } + } + for _, addr := range drop { + if _, ok := pool.index[addr]; ok { + t.Errorf("unexpected account %v present in pool", addr) + } + } + if pool.stored != size { + t.Errorf("pool stored size mismatch: have %v, want %v", pool.stored, size) + } + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) + + pool.Close() + } +} + +// Tests that adding transaction will correctly store it in the persistent store +// and update all the indices. +// +// Note, this tests mostly checks the pool transaction shuffling logic or things +// specific to the blob pool. It does not do an exhaustive transaction validity +// check. +func TestAdd(t *testing.T) { + log.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 { + balance uint64 + nonce uint64 + txs []*types.BlobTx + } + + // addtx is a helper sender/tx tuple to represent a new tx addition + type addtx struct { + from string + tx *types.BlobTx + err error + } + + tests := []struct { + seeds map[string]seed + adds []addtx + }{ + // Transactions from new accounts should be accepted if their initial + // nonce matches the expected one from the statedb. Higher or lower must + // be rejected. + { + seeds: map[string]seed{ + "alice": {balance: 21100 + blobSize}, + "bob": {balance: 21100 + blobSize, nonce: 1}, + "claire": {balance: 21100 + blobSize}, + "dave": {balance: 21100 + blobSize, nonce: 1}, + }, + adds: []addtx{ + { // New account, no previous txs: accept nonce 0 + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 1), + err: nil, + }, + { // Old account, 1 tx in chain, 0 pending: accept nonce 1 + from: "bob", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, no previous txs: reject nonce 1 + from: "claire", + tx: makeUnsignedTx(1, 1, 1, 1), + err: core.ErrNonceTooHigh, + }, + { // Old account, 1 tx in chain, 0 pending: reject nonce 0 + from: "dave", + tx: makeUnsignedTx(0, 1, 1, 1), + err: core.ErrNonceTooLow, + }, + { // Old account, 1 tx in chain, 0 pending: reject nonce 2 + from: "dave", + tx: makeUnsignedTx(2, 1, 1, 1), + err: core.ErrNonceTooHigh, + }, + }, + }, + // Transactions from already pooled accounts should only be accepted if + // the nonces are contiguous (ignore prices for now, will check later) + { + seeds: map[string]seed{ + "alice": { + balance: 1000000, + txs: []*types.BlobTx{ + makeUnsignedTx(0, 1, 1, 1), + }, + }, + "bob": { + balance: 1000000, + nonce: 1, + txs: []*types.BlobTx{ + makeUnsignedTx(1, 1, 1, 1), + }, + }, + }, + adds: []addtx{ + { // New account, 1 tx pending: reject duplicate nonce 0 + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 1), + err: txpool.ErrAlreadyKnown, + }, + { // New account, 1 tx pending: reject replacement nonce 0 (ignore price for now) + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 2), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 1 tx pending: accept nonce 1 + from: "alice", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, 2 txs pending: reject nonce 3 + from: "alice", + tx: makeUnsignedTx(3, 1, 1, 1), + err: core.ErrNonceTooHigh, + }, + { // New account, 2 txs pending: accept nonce 2 + from: "alice", + tx: makeUnsignedTx(2, 1, 1, 1), + err: nil, + }, + { // New account, 3 txs pending: accept nonce 3 now + from: "alice", + tx: makeUnsignedTx(3, 1, 1, 1), + err: nil, + }, + { // Old account, 1 tx in chain, 1 tx pending: reject duplicate nonce 1 + from: "bob", + tx: makeUnsignedTx(1, 1, 1, 1), + err: txpool.ErrAlreadyKnown, + }, + { // Old account, 1 tx in chain, 1 tx pending: accept nonce 2 (ignore price for now) + from: "bob", + tx: makeUnsignedTx(2, 1, 1, 1), + err: nil, + }, + }, + }, + // Transactions should only be accepted into the pool if the cumulative + // expenditure doesn't overflow the account balance + { + seeds: map[string]seed{ + "alice": {balance: 63299 + 3*blobSize}, // 3 tx - 1 wei + }, + adds: []addtx{ + { // New account, no previous txs: accept nonce 0 with 21100 wei spend + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 1), + err: nil, + }, + { // New account, 1 pooled tx with 21100 wei spent: accept nonce 1 with 21100 wei spend + from: "alice", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, 2 pooled tx with 42200 wei spent: reject nonce 2 with 21100 wei spend (1 wei overflow) + from: "alice", + tx: makeUnsignedTx(2, 1, 1, 1), + err: core.ErrInsufficientFunds, + }, + }, + }, + // Transactions should only be accepted into the pool if the total count + // from the same account doesn't overflow the pool limits + { + seeds: map[string]seed{ + "alice": {balance: 10000000}, + }, + adds: []addtx{ + { // New account, no previous txs, 16 slots left: accept nonce 0 + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 1), + err: nil, + }, + { // New account, 1 pooled tx, 15 slots left: accept nonce 1 + from: "alice", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, 2 pooled tx, 14 slots left: accept nonce 2 + from: "alice", + tx: makeUnsignedTx(2, 1, 1, 1), + err: nil, + }, + { // New account, 3 pooled tx, 13 slots left: accept nonce 3 + from: "alice", + tx: makeUnsignedTx(3, 1, 1, 1), + err: nil, + }, + { // New account, 4 pooled tx, 12 slots left: accept nonce 4 + from: "alice", + tx: makeUnsignedTx(4, 1, 1, 1), + err: nil, + }, + { // New account, 5 pooled tx, 11 slots left: accept nonce 5 + from: "alice", + tx: makeUnsignedTx(5, 1, 1, 1), + err: nil, + }, + { // New account, 6 pooled tx, 10 slots left: accept nonce 6 + from: "alice", + tx: makeUnsignedTx(6, 1, 1, 1), + err: nil, + }, + { // New account, 7 pooled tx, 9 slots left: accept nonce 7 + from: "alice", + tx: makeUnsignedTx(7, 1, 1, 1), + err: nil, + }, + { // New account, 8 pooled tx, 8 slots left: accept nonce 8 + from: "alice", + tx: makeUnsignedTx(8, 1, 1, 1), + err: nil, + }, + { // New account, 9 pooled tx, 7 slots left: accept nonce 9 + from: "alice", + tx: makeUnsignedTx(9, 1, 1, 1), + err: nil, + }, + { // New account, 10 pooled tx, 6 slots left: accept nonce 10 + from: "alice", + tx: makeUnsignedTx(10, 1, 1, 1), + err: nil, + }, + { // New account, 11 pooled tx, 5 slots left: accept nonce 11 + from: "alice", + tx: makeUnsignedTx(11, 1, 1, 1), + err: nil, + }, + { // New account, 12 pooled tx, 4 slots left: accept nonce 12 + from: "alice", + tx: makeUnsignedTx(12, 1, 1, 1), + err: nil, + }, + { // New account, 13 pooled tx, 3 slots left: accept nonce 13 + from: "alice", + tx: makeUnsignedTx(13, 1, 1, 1), + err: nil, + }, + { // New account, 14 pooled tx, 2 slots left: accept nonce 14 + from: "alice", + tx: makeUnsignedTx(14, 1, 1, 1), + err: nil, + }, + { // New account, 15 pooled tx, 1 slots left: accept nonce 15 + from: "alice", + tx: makeUnsignedTx(15, 1, 1, 1), + err: nil, + }, + { // New account, 16 pooled tx, 0 slots left: accept nonce 15 replacement + from: "alice", + tx: makeUnsignedTx(15, 10, 10, 10), + err: nil, + }, + { // New account, 16 pooled tx, 0 slots left: reject nonce 16 with overcap + from: "alice", + tx: makeUnsignedTx(16, 1, 1, 1), + err: txpool.ErrAccountLimitExceeded, + }, + }, + }, + // Previously existing transactions should be allowed to be replaced iff + // the new cumulative expenditure can be covered by the account and the + // prices are bumped all around (no percentage check here). + { + seeds: map[string]seed{ + "alice": {balance: 2*100 + 5*21000 + 3*blobSize}, + }, + adds: []addtx{ + { // New account, no previous txs: reject nonce 0 with 341172 wei spend + from: "alice", + tx: makeUnsignedTx(0, 1, 20, 1), + err: core.ErrInsufficientFunds, + }, + { // New account, no previous txs: accept nonce 0 with 173172 wei spend + from: "alice", + tx: makeUnsignedTx(0, 1, 2, 1), + err: nil, + }, + { // New account, 1 pooled tx with 173172 wei spent: accept nonce 1 with 152172 wei spend + from: "alice", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with 599684 wei spend (173072 extra) (would overflow balance at nonce 1) + from: "alice", + tx: makeUnsignedTx(0, 2, 5, 2), + err: core.ErrInsufficientFunds, + }, + { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gastip-bump + from: "alice", + tx: makeUnsignedTx(0, 1, 3, 2), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gascap-bump + from: "alice", + tx: makeUnsignedTx(0, 2, 2, 2), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-blobcap-bump + from: "alice", + tx: makeUnsignedTx(0, 2, 4, 1), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 2 pooled tx with 325344 wei spent: accept nonce 0 with 84100 wei spend (42000 extra) + from: "alice", + tx: makeUnsignedTx(0, 2, 4, 2), + err: nil, + }, + }, + }, + // Previously existing transactions should be allowed to be replaced iff + // the new prices are bumped by a sufficient amount. + { + seeds: map[string]seed{ + "alice": {balance: 100 + 8*21000 + 4*blobSize}, + }, + adds: []addtx{ + { // New account, no previous txs: accept nonce 0 + from: "alice", + tx: makeUnsignedTx(0, 2, 4, 2), + err: nil, + }, + { // New account, 1 pooled tx: reject nonce 0 with low-gastip-bump + from: "alice", + tx: makeUnsignedTx(0, 3, 8, 4), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 1 pooled tx: reject nonce 0 with low-gascap-bump + from: "alice", + tx: makeUnsignedTx(0, 4, 6, 4), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 1 pooled tx: reject nonce 0 with low-blobcap-bump + from: "alice", + tx: makeUnsignedTx(0, 4, 8, 3), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 1 pooled tx: accept nonce 0 with all-bumps + from: "alice", + tx: makeUnsignedTx(0, 4, 8, 4), + err: nil, + }, + }, + }, + // Blob transactions that don't meet the min blob gas price should be rejected + { + seeds: map[string]seed{ + "alice": {balance: 10000000}, + }, + adds: []addtx{ + { // New account, no previous txs, nonce 0, but blob fee cap too low + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 0), + err: txpool.ErrUnderpriced, + }, + { // Same as above but blob fee cap equals minimum, should be accepted + from: "alice", + tx: makeUnsignedTx(0, 1, 1, params.BlobTxMinBlobGasprice), + err: nil, + }, + }, + }, + } + for i, tt := range tests { + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) // late defer, still ok + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert the seed transactions for the pool startup + var ( + keys = make(map[string]*ecdsa.PrivateKey) + addrs = make(map[string]common.Address) + ) + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + for acc, seed := range tt.seeds { + // Generate a new random key/address for the seed account + keys[acc], _ = crypto.GenerateKey() + addrs[acc] = crypto.PubkeyToAddress(keys[acc].PublicKey) + + // Seed the state database with this account + statedb.AddBalance(addrs[acc], new(uint256.Int).SetUint64(seed.balance), tracing.BalanceChangeUnspecified) + statedb.SetNonce(addrs[acc], seed.nonce) + + // Sign the seed transactions and store them in the data store + for _, tx := range seed.txs { + signed := types.MustSignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx) + blob, _ := rlp.EncodeToBytes(signed) + store.Put(blob) + } + } + statedb.Commit(0, true) + store.Close() + + // Create a blob pool out of the pre-seeded dats + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("test %d: failed to create blob pool: %v", i, err) + } + verifyPoolInternals(t, pool) + + // Add each transaction one by one, verifying the pool internals in between + for j, add := range tt.adds { + signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(testChainConfig), add.tx) + if err := pool.add(signed); !errors.Is(err, add.err) { + t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err) + } + verifyPoolInternals(t, pool) + } + // Verify the pool internals and close down the test + verifyPoolInternals(t, pool) + pool.Close() + } +} + +// Benchmarks the time it takes to assemble the lazy pending transaction list +// from the pool contents. +func BenchmarkPoolPending100Mb(b *testing.B) { benchmarkPoolPending(b, 100_000_000) } +func BenchmarkPoolPending1GB(b *testing.B) { benchmarkPoolPending(b, 1_000_000_000) } +func BenchmarkPoolPending10GB(b *testing.B) { benchmarkPoolPending(b, 10_000_000_000) } + +func benchmarkPoolPending(b *testing.B, datacap uint64) { + // Calculate the maximum number of transaction that would fit into the pool + // and generate a set of random accounts to seed them with. + capacity := datacap / params.BlobTxBlobGasPerBlob + + var ( + basefee = uint64(1050) + blobfee = uint64(105) + signer = types.LatestSigner(testChainConfig) + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + chain = &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(basefee), + blobfee: uint256.NewInt(blobfee), + statedb: statedb, + } + pool = New(Config{Datadir: ""}, chain) + ) + + if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { + b.Fatalf("failed to create blob pool: %v", err) + } + // Fill the pool up with one random transaction from each account with the + // same price and everything to maximize the worst case scenario + for i := 0; i < int(capacity); i++ { + blobtx := makeUnsignedTx(0, 10, basefee+10, blobfee) + blobtx.R = uint256.NewInt(1) + blobtx.S = uint256.NewInt(uint64(100 + i)) + blobtx.V = uint256.NewInt(0) + tx := types.NewTx(blobtx) + addr, err := types.Sender(signer, tx) + if err != nil { + b.Fatal(err) + } + statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + pool.add(tx) + } + statedb.Commit(0, true) + defer pool.Close() + + // Benchmark assembling the pending + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + p := pool.Pending(txpool.PendingFilter{ + MinTip: uint256.NewInt(1), + BaseFee: chain.basefee, + BlobFee: chain.blobfee, + }) + if len(p) != int(capacity) { + b.Fatalf("have %d want %d", len(p), capacity) + } + } +} diff --git a/core/txpool/blobpool/config.go b/core/txpool/blobpool/config.go new file mode 100644 index 0000000..1d18073 --- /dev/null +++ b/core/txpool/blobpool/config.go @@ -0,0 +1,50 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import ( + "github.com/ethereum/go-ethereum/log" +) + +// Config are the configuration parameters of the blob transaction pool. +type Config struct { + Datadir string // Data directory containing the currently executable blobs + Datacap uint64 // Soft-cap of database storage (hard cap is larger due to overhead) + PriceBump uint64 // Minimum price bump percentage to replace an already existing nonce +} + +// DefaultConfig contains the default configurations for the transaction pool. +var DefaultConfig = Config{ + Datadir: "blobpool", + Datacap: 10 * 1024 * 1024 * 1024 / 4, // TODO(karalabe): /4 handicap for rollout, gradually bump back up to 10GB + PriceBump: 100, // either have patience or be aggressive, no mushy ground +} + +// sanitize checks the provided user configurations and changes anything that's +// unreasonable or unworkable. +func (config *Config) sanitize() Config { + conf := *config + if conf.Datacap < 1 { + log.Warn("Sanitizing invalid blobpool storage cap", "provided", conf.Datacap, "updated", DefaultConfig.Datacap) + conf.Datacap = DefaultConfig.Datacap + } + if conf.PriceBump < 1 { + log.Warn("Sanitizing invalid blobpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump) + conf.PriceBump = DefaultConfig.PriceBump + } + return conf +} diff --git a/core/txpool/blobpool/evictheap.go b/core/txpool/blobpool/evictheap.go new file mode 100644 index 0000000..5e285e6 --- /dev/null +++ b/core/txpool/blobpool/evictheap.go @@ -0,0 +1,141 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import ( + "container/heap" + "math" + "slices" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" + "golang.org/x/exp/maps" +) + +// evictHeap is a helper data structure to keep track of the cheapest bottleneck +// transaction from each account to determine which account to evict from. +// +// The heap internally tracks a slice of cheapest transactions from each account +// and a mapping from addresses to indices for direct removals/updates. +// +// The goal of the heap is to decide which account has the worst bottleneck to +// evict transactions from. +type evictHeap struct { + metas map[common.Address][]*blobTxMeta // Pointer to the blob pool's index for price retrievals + + basefeeJumps float64 // Pre-calculated absolute dynamic fee jumps for the base fee + blobfeeJumps float64 // Pre-calculated absolute dynamic fee jumps for the blob fee + + addrs []common.Address // Heap of addresses to retrieve the cheapest out of + index map[common.Address]int // Indices into the heap for replacements +} + +// newPriceHeap creates a new heap of cheapest accounts in the blob pool to evict +// from in case of over saturation. +func newPriceHeap(basefee *uint256.Int, blobfee *uint256.Int, index map[common.Address][]*blobTxMeta) *evictHeap { + heap := &evictHeap{ + metas: index, + index: make(map[common.Address]int, len(index)), + } + // Populate the heap in account sort order. Not really needed in practice, + // but it makes the heap initialization deterministic and less annoying to + // test in unit tests. + heap.addrs = maps.Keys(index) + slices.SortFunc(heap.addrs, common.Address.Cmp) + for i, addr := range heap.addrs { + heap.index[addr] = i + } + heap.reinit(basefee, blobfee, true) + return heap +} + +// reinit updates the pre-calculated dynamic fee jumps in the price heap and runs +// the sorting algorithm from scratch on the entire heap. +func (h *evictHeap) reinit(basefee *uint256.Int, blobfee *uint256.Int, force bool) { + // If the update is mostly the same as the old, don't sort pointlessly + basefeeJumps := dynamicFeeJumps(basefee) + blobfeeJumps := dynamicFeeJumps(blobfee) + + if !force && math.Abs(h.basefeeJumps-basefeeJumps) < 0.01 && math.Abs(h.blobfeeJumps-blobfeeJumps) < 0.01 { // TODO(karalabe): 0.01 enough, maybe should be smaller? Maybe this optimization is moot? + return + } + // One or both of the dynamic fees jumped, resort the pool + h.basefeeJumps = basefeeJumps + h.blobfeeJumps = blobfeeJumps + + heap.Init(h) +} + +// Len implements sort.Interface as part of heap.Interface, returning the number +// of accounts in the pool which can be considered for eviction. +func (h *evictHeap) Len() int { + return len(h.addrs) +} + +// Less implements sort.Interface as part of heap.Interface, returning which of +// the two requested accounts has a cheaper bottleneck. +func (h *evictHeap) Less(i, j int) bool { + txsI := h.metas[h.addrs[i]] + txsJ := h.metas[h.addrs[j]] + + lastI := txsI[len(txsI)-1] + lastJ := txsJ[len(txsJ)-1] + + prioI := evictionPriority(h.basefeeJumps, lastI.evictionExecFeeJumps, h.blobfeeJumps, lastI.evictionBlobFeeJumps) + if prioI > 0 { + prioI = 0 + } + prioJ := evictionPriority(h.basefeeJumps, lastJ.evictionExecFeeJumps, h.blobfeeJumps, lastJ.evictionBlobFeeJumps) + if prioJ > 0 { + prioJ = 0 + } + if prioI == prioJ { + return lastI.evictionExecTip.Lt(lastJ.evictionExecTip) + } + return prioI < prioJ +} + +// Swap implements sort.Interface as part of heap.Interface, maintaining both the +// order of the accounts according to the heap, and the account->item slot mapping +// for replacements. +func (h *evictHeap) Swap(i, j int) { + h.index[h.addrs[i]], h.index[h.addrs[j]] = h.index[h.addrs[j]], h.index[h.addrs[i]] + h.addrs[i], h.addrs[j] = h.addrs[j], h.addrs[i] +} + +// Push implements heap.Interface, appending an item to the end of the account +// ordering as well as the address to item slot mapping. +func (h *evictHeap) Push(x any) { + h.index[x.(common.Address)] = len(h.addrs) + h.addrs = append(h.addrs, x.(common.Address)) +} + +// Pop implements heap.Interface, removing and returning the last element of the +// heap. +// +// Note, use `heap.Pop`, not `evictHeap.Pop`. This method is used by Go's heap, +// to provide the functionality, it does not embed it. +func (h *evictHeap) Pop() any { + // Remove the last element from the heap + size := len(h.addrs) + addr := h.addrs[size-1] + h.addrs = h.addrs[:size-1] + + // Unindex the removed element and return + delete(h.index, addr) + return addr +} diff --git a/core/txpool/blobpool/evictheap_test.go b/core/txpool/blobpool/evictheap_test.go new file mode 100644 index 0000000..1cf577c --- /dev/null +++ b/core/txpool/blobpool/evictheap_test.go @@ -0,0 +1,320 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import ( + "container/heap" + mrand "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +var rand = mrand.New(mrand.NewSource(1)) + +// verifyHeapInternals verifies that all accounts present in the index are also +// present in the heap and internals are consistent across various indices. +func verifyHeapInternals(t *testing.T, evict *evictHeap) { + t.Helper() + + // Ensure that all accounts are present in the heap and no extras + seen := make(map[common.Address]struct{}) + for i, addr := range evict.addrs { + seen[addr] = struct{}{} + if _, ok := evict.metas[addr]; !ok { + t.Errorf("heap contains unexpected address at slot %d: %v", i, addr) + } + } + for addr := range evict.metas { + if _, ok := seen[addr]; !ok { + t.Errorf("heap is missing required address %v", addr) + } + } + if len(evict.addrs) != len(evict.metas) { + t.Errorf("heap size %d mismatches metadata size %d", len(evict.addrs), len(evict.metas)) + } + // Ensure that all accounts are present in the heap order index and no extras + have := make([]common.Address, len(evict.index)) + for addr, i := range evict.index { + have[i] = addr + } + if len(have) != len(evict.addrs) { + t.Errorf("heap index size %d mismatches heap size %d", len(have), len(evict.addrs)) + } + for i := 0; i < len(have) && i < len(evict.addrs); i++ { + if have[i] != evict.addrs[i] { + t.Errorf("heap index for slot %d mismatches: have %v, want %v", i, have[i], evict.addrs[i]) + } + } +} + +// Tests that the price heap can correctly sort its set of transactions based on +// an input base- and blob fee. +func TestPriceHeapSorting(t *testing.T) { + tests := []struct { + execTips []uint64 + execFees []uint64 + blobFees []uint64 + + basefee uint64 + blobfee uint64 + + order []int + }{ + // If everything is above the basefee and blobfee, order by miner tip + { + execTips: []uint64{1, 0, 2}, + execFees: []uint64{1, 2, 3}, + blobFees: []uint64{3, 2, 1}, + basefee: 0, + blobfee: 0, + order: []int{1, 0, 2}, + }, + // If only basefees are used (blob fee matches with network), return the + // ones the furthest below the current basefee, splitting same ones with + // the tip. Anything above the basefee should be split by tip. + { + execTips: []uint64{100, 50, 100, 50, 1, 2, 3}, + execFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000}, + blobFees: []uint64{0, 0, 0, 0, 0, 0, 0}, + basefee: 1999, + blobfee: 0, + order: []int{3, 2, 1, 0, 4, 5, 6}, + }, + // If only blobfees are used (base fee matches with network), return the + // ones the furthest below the current blobfee, splitting same ones with + // the tip. Anything above the blobfee should be split by tip. + { + execTips: []uint64{100, 50, 100, 50, 1, 2, 3}, + execFees: []uint64{0, 0, 0, 0, 0, 0, 0}, + blobFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000}, + basefee: 0, + blobfee: 1999, + order: []int{3, 2, 1, 0, 4, 5, 6}, + }, + // If both basefee and blobfee is specified, sort by the larger distance + // of the two from the current network conditions, splitting same (loglog) + // ones via the tip. + // + // Basefee: 1000 + // Blobfee: 100 + // + // Tx #0: (800, 80) - 2 jumps below both => priority -1 + // Tx #1: (630, 63) - 4 jumps below both => priority -2 + // Tx #2: (800, 63) - 2 jumps below basefee, 4 jumps below blobfee => priority -2 (blob penalty dominates) + // Tx #3: (630, 80) - 4 jumps below basefee, 2 jumps below blobfee => priority -2 (base penalty dominates) + // + // Txs 1, 2, 3 share the same priority, split via tip, prefer 0 as the best + { + execTips: []uint64{1, 2, 3, 4}, + execFees: []uint64{800, 630, 800, 630}, + blobFees: []uint64{80, 63, 63, 80}, + basefee: 1000, + blobfee: 100, + order: []int{1, 2, 3, 0}, + }, + } + for i, tt := range tests { + // Create an index of the transactions + index := make(map[common.Address][]*blobTxMeta) + for j := byte(0); j < byte(len(tt.execTips)); j++ { + addr := common.Address{j} + + var ( + execTip = uint256.NewInt(tt.execTips[j]) + execFee = uint256.NewInt(tt.execFees[j]) + blobFee = uint256.NewInt(tt.blobFees[j]) + + basefeeJumps = dynamicFeeJumps(execFee) + blobfeeJumps = dynamicFeeJumps(blobFee) + ) + index[addr] = []*blobTxMeta{{ + id: uint64(j), + size: 128 * 1024, + nonce: 0, + execTipCap: execTip, + execFeeCap: execFee, + blobFeeCap: blobFee, + basefeeJumps: basefeeJumps, + blobfeeJumps: blobfeeJumps, + evictionExecTip: execTip, + evictionExecFeeJumps: basefeeJumps, + evictionBlobFeeJumps: blobfeeJumps, + }} + } + // Create a price heap and check the pop order + priceheap := newPriceHeap(uint256.NewInt(tt.basefee), uint256.NewInt(tt.blobfee), index) + verifyHeapInternals(t, priceheap) + + for j := 0; j < len(tt.order); j++ { + if next := heap.Pop(priceheap); int(next.(common.Address)[0]) != tt.order[j] { + t.Errorf("test %d, item %d: order mismatch: have %d, want %d", i, j, next.(common.Address)[0], tt.order[j]) + } else { + delete(index, next.(common.Address)) // remove to simulate a correct pool for the test + } + verifyHeapInternals(t, priceheap) + } + } +} + +// Benchmarks reheaping the entire set of accounts in the blob pool. +func BenchmarkPriceHeapReinit1MB(b *testing.B) { benchmarkPriceHeapReinit(b, 1024*1024) } +func BenchmarkPriceHeapReinit10MB(b *testing.B) { benchmarkPriceHeapReinit(b, 10*1024*1024) } +func BenchmarkPriceHeapReinit100MB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024) } +func BenchmarkPriceHeapReinit1GB(b *testing.B) { benchmarkPriceHeapReinit(b, 1024*1024*1024) } +func BenchmarkPriceHeapReinit10GB(b *testing.B) { benchmarkPriceHeapReinit(b, 10*1024*1024*1024) } +func BenchmarkPriceHeapReinit25GB(b *testing.B) { benchmarkPriceHeapReinit(b, 25*1024*1024*1024) } +func BenchmarkPriceHeapReinit50GB(b *testing.B) { benchmarkPriceHeapReinit(b, 50*1024*1024*1024) } +func BenchmarkPriceHeapReinit100GB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024*1024) } + +func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) { + // Calculate how many unique transactions we can fit into the provided disk + // data cap + blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob) + + // Create a random set of transactions with random fees. Use a separate account + // for each transaction to make it worse case. + index := make(map[common.Address][]*blobTxMeta) + for i := 0; i < int(blobs); i++ { + var addr common.Address + rand.Read(addr[:]) + + var ( + execTip = uint256.NewInt(rand.Uint64()) + execFee = uint256.NewInt(rand.Uint64()) + blobFee = uint256.NewInt(rand.Uint64()) + + basefeeJumps = dynamicFeeJumps(execFee) + blobfeeJumps = dynamicFeeJumps(blobFee) + ) + index[addr] = []*blobTxMeta{{ + id: uint64(i), + size: 128 * 1024, + nonce: 0, + execTipCap: execTip, + execFeeCap: execFee, + blobFeeCap: blobFee, + basefeeJumps: basefeeJumps, + blobfeeJumps: blobfeeJumps, + evictionExecTip: execTip, + evictionExecFeeJumps: basefeeJumps, + evictionBlobFeeJumps: blobfeeJumps, + }} + } + // Create a price heap and reinit it over and over + heap := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), index) + + basefees := make([]*uint256.Int, b.N) + blobfees := make([]*uint256.Int, b.N) + for i := 0; i < b.N; i++ { + basefees[i] = uint256.NewInt(rand.Uint64()) + blobfees[i] = uint256.NewInt(rand.Uint64()) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + heap.reinit(basefees[i], blobfees[i], true) + } +} + +// Benchmarks overflowing the heap over and over (add and then drop). +func BenchmarkPriceHeapOverflow1MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 1024*1024) } +func BenchmarkPriceHeapOverflow10MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 10*1024*1024) } +func BenchmarkPriceHeapOverflow100MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024) } +func BenchmarkPriceHeapOverflow1GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 1024*1024*1024) } +func BenchmarkPriceHeapOverflow10GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 10*1024*1024*1024) } +func BenchmarkPriceHeapOverflow25GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 25*1024*1024*1024) } +func BenchmarkPriceHeapOverflow50GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 50*1024*1024*1024) } +func BenchmarkPriceHeapOverflow100GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024*1024) } + +func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) { + // Calculate how many unique transactions we can fit into the provided disk + // data cap + blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob) + + // Create a random set of transactions with random fees. Use a separate account + // for each transaction to make it worse case. + index := make(map[common.Address][]*blobTxMeta) + for i := 0; i < int(blobs); i++ { + var addr common.Address + rand.Read(addr[:]) + + var ( + execTip = uint256.NewInt(rand.Uint64()) + execFee = uint256.NewInt(rand.Uint64()) + blobFee = uint256.NewInt(rand.Uint64()) + + basefeeJumps = dynamicFeeJumps(execFee) + blobfeeJumps = dynamicFeeJumps(blobFee) + ) + index[addr] = []*blobTxMeta{{ + id: uint64(i), + size: 128 * 1024, + nonce: 0, + execTipCap: execTip, + execFeeCap: execFee, + blobFeeCap: blobFee, + basefeeJumps: basefeeJumps, + blobfeeJumps: blobfeeJumps, + evictionExecTip: execTip, + evictionExecFeeJumps: basefeeJumps, + evictionBlobFeeJumps: blobfeeJumps, + }} + } + // Create a price heap and overflow it over and over + evict := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), index) + var ( + addrs = make([]common.Address, b.N) + metas = make([]*blobTxMeta, b.N) + ) + for i := 0; i < b.N; i++ { + rand.Read(addrs[i][:]) + + var ( + execTip = uint256.NewInt(rand.Uint64()) + execFee = uint256.NewInt(rand.Uint64()) + blobFee = uint256.NewInt(rand.Uint64()) + + basefeeJumps = dynamicFeeJumps(execFee) + blobfeeJumps = dynamicFeeJumps(blobFee) + ) + metas[i] = &blobTxMeta{ + id: uint64(int(blobs) + i), + size: 128 * 1024, + nonce: 0, + execTipCap: execTip, + execFeeCap: execFee, + blobFeeCap: blobFee, + basefeeJumps: basefeeJumps, + blobfeeJumps: blobfeeJumps, + evictionExecTip: execTip, + evictionExecFeeJumps: basefeeJumps, + evictionBlobFeeJumps: blobfeeJumps, + } + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + index[addrs[i]] = []*blobTxMeta{metas[i]} + heap.Push(evict, addrs[i]) + + drop := heap.Pop(evict) + delete(index, drop.(common.Address)) + } +} diff --git a/core/txpool/blobpool/interface.go b/core/txpool/blobpool/interface.go new file mode 100644 index 0000000..6f296a5 --- /dev/null +++ b/core/txpool/blobpool/interface.go @@ -0,0 +1,44 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +// BlockChain defines the minimal set of methods needed to back a blob pool with +// a chain. Exists to allow mocking the live chain out of tests. +type BlockChain interface { + // Config retrieves the chain's fork configuration. + Config() *params.ChainConfig + + // CurrentBlock returns the current head of the chain. + CurrentBlock() *types.Header + + // CurrentFinalBlock returns the current block below which blobs should not + // be maintained anymore for reorg purposes. + CurrentFinalBlock() *types.Header + + // GetBlock retrieves a specific block, used during pool resets. + GetBlock(hash common.Hash, number uint64) *types.Block + + // StateAt returns a state database for a given root hash (generally the head). + StateAt(root common.Hash) (*state.StateDB, error) +} diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go new file mode 100644 index 0000000..32381a3 --- /dev/null +++ b/core/txpool/blobpool/limbo.go @@ -0,0 +1,253 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/billy" +) + +// limboBlob is a wrapper around an opaque blobset that also contains the tx hash +// to which it belongs as well as the block number in which it was included for +// finality eviction. +type limboBlob struct { + TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs + Block uint64 // Block in which the blob transaction was included + Tx *types.Transaction +} + +// limbo is a light, indexed database to temporarily store recently included +// blobs until they are finalized. The purpose is to support small reorgs, which +// would require pulling back up old blobs (which aren't part of the chain). +// +// TODO(karalabe): Currently updating the inclusion block of a blob needs a full db rewrite. Can we do without? +type limbo struct { + store billy.Database // Persistent data store for limboed blobs + + index map[common.Hash]uint64 // Mappings from tx hashes to datastore ids + groups map[uint64]map[uint64]common.Hash // Set of txs included in past blocks +} + +// newLimbo opens and indexes a set of limboed blob transactions. +func newLimbo(datadir string) (*limbo, error) { + l := &limbo{ + index: make(map[common.Hash]uint64), + groups: make(map[uint64]map[uint64]common.Hash), + } + // Index all limboed blobs on disk and delete anything unprocessable + var fails []uint64 + index := func(id uint64, size uint32, data []byte) { + if l.parseBlob(id, data) != nil { + fails = append(fails, id) + } + } + store, err := billy.Open(billy.Options{Path: datadir, Repair: true}, newSlotter(), index) + if err != nil { + return nil, err + } + l.store = store + + if len(fails) > 0 { + log.Warn("Dropping invalidated limboed blobs", "ids", fails) + for _, id := range fails { + if err := l.store.Delete(id); err != nil { + l.Close() + return nil, err + } + } + } + return l, nil +} + +// Close closes down the underlying persistent store. +func (l *limbo) Close() error { + return l.store.Close() +} + +// parseBlob is a callback method on limbo creation that gets called for each +// limboed blob on disk to create the in-memory metadata index. +func (l *limbo) parseBlob(id uint64, data []byte) error { + item := new(limboBlob) + if err := rlp.DecodeBytes(data, item); err != nil { + // This path is impossible unless the disk data representation changes + // across restarts. For that ever improbable case, recover gracefully + // by ignoring this data entry. + log.Error("Failed to decode blob limbo entry", "id", id, "err", err) + return err + } + if _, ok := l.index[item.TxHash]; ok { + // This path is impossible, unless due to a programming error a blob gets + // inserted into the limbo which was already part of if. Recover gracefully + // by ignoring this data entry. + log.Error("Dropping duplicate blob limbo entry", "owner", item.TxHash, "id", id) + return errors.New("duplicate blob") + } + l.index[item.TxHash] = id + + if _, ok := l.groups[item.Block]; !ok { + l.groups[item.Block] = make(map[uint64]common.Hash) + } + l.groups[item.Block][id] = item.TxHash + + return nil +} + +// finalize evicts all blobs belonging to a recently finalized block or older. +func (l *limbo) finalize(final *types.Header) { + // Just in case there's no final block yet (network not yet merged, weird + // restart, sethead, etc), fail gracefully. + if final == nil { + log.Error("Nil finalized block cannot evict old blobs") + return + } + for block, ids := range l.groups { + if block > final.Number.Uint64() { + continue + } + for id, owner := range ids { + if err := l.store.Delete(id); err != nil { + log.Error("Failed to drop finalized blob", "block", block, "id", id, "err", err) + } + delete(l.index, owner) + } + delete(l.groups, block) + } +} + +// push stores a new blob transaction into the limbo, waiting until finality for +// it to be automatically evicted. +func (l *limbo) push(tx *types.Transaction, block uint64) error { + // If the blobs are already tracked by the limbo, consider it a programming + // error. There's not much to do against it, but be loud. + if _, ok := l.index[tx.Hash()]; ok { + log.Error("Limbo cannot push already tracked blobs", "tx", tx) + return errors.New("already tracked blob transaction") + } + if err := l.setAndIndex(tx, block); err != nil { + log.Error("Failed to set and index limboed blobs", "tx", tx, "err", err) + return err + } + return nil +} + +// pull retrieves a previously pushed set of blobs back from the limbo, removing +// it at the same time. This method should be used when a previously included blob +// transaction gets reorged out. +func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) { + // If the blobs are not tracked by the limbo, there's not much to do. This + // can happen for example if a blob transaction is mined without pushing it + // into the network first. + id, ok := l.index[tx] + if !ok { + log.Trace("Limbo cannot pull non-tracked blobs", "tx", tx) + return nil, errors.New("unseen blob transaction") + } + item, err := l.getAndDrop(id) + if err != nil { + log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err) + return nil, err + } + return item.Tx, nil +} + +// update changes the block number under which a blob transaction is tracked. This +// method should be used when a reorg changes a transaction's inclusion block. +// +// The method may log errors for various unexpected scenarios but will not return +// any of it since there's no clear error case. Some errors may be due to coding +// issues, others caused by signers mining MEV stuff or swapping transactions. In +// all cases, the pool needs to continue operating. +func (l *limbo) update(txhash common.Hash, block uint64) { + // If the blobs are not tracked by the limbo, there's not much to do. This + // can happen for example if a blob transaction is mined without pushing it + // into the network first. + id, ok := l.index[txhash] + if !ok { + log.Trace("Limbo cannot update non-tracked blobs", "tx", txhash) + return + } + // If there was no change in the blob's inclusion block, don't mess around + // with heavy database operations. + if _, ok := l.groups[block][id]; ok { + log.Trace("Blob transaction unchanged in limbo", "tx", txhash, "block", block) + return + } + // Retrieve the old blobs from the data store and write them back with a new + // block number. IF anything fails, there's not much to do, go on. + item, err := l.getAndDrop(id) + if err != nil { + log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err) + return + } + if err := l.setAndIndex(item.Tx, block); err != nil { + log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err) + return + } + log.Trace("Blob transaction updated in limbo", "tx", txhash, "old-block", item.Block, "new-block", block) +} + +// getAndDrop retrieves a blob item from the limbo store and deletes it both from +// the store and indices. +func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) { + data, err := l.store.Get(id) + if err != nil { + return nil, err + } + item := new(limboBlob) + if err = rlp.DecodeBytes(data, item); err != nil { + return nil, err + } + delete(l.index, item.TxHash) + delete(l.groups[item.Block], id) + if len(l.groups[item.Block]) == 0 { + delete(l.groups, item.Block) + } + if err := l.store.Delete(id); err != nil { + return nil, err + } + return item, nil +} + +// setAndIndex assembles a limbo blob database entry and stores it, also updating +// the in-memory indices. +func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error { + txhash := tx.Hash() + item := &limboBlob{ + TxHash: txhash, + Block: block, + Tx: tx, + } + data, err := rlp.EncodeToBytes(item) + if err != nil { + panic(err) // cannot happen runtime, dev error + } + id, err := l.store.Put(data) + if err != nil { + return err + } + l.index[txhash] = id + if _, ok := l.groups[block]; !ok { + l.groups[block] = make(map[uint64]common.Hash) + } + l.groups[block][id] = txhash + return nil +} diff --git a/core/txpool/blobpool/metrics.go b/core/txpool/blobpool/metrics.go new file mode 100644 index 0000000..52419ad --- /dev/null +++ b/core/txpool/blobpool/metrics.go @@ -0,0 +1,105 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import "github.com/ethereum/go-ethereum/metrics" + +var ( + // datacapGauge tracks the user's configured capacity for the blob pool. It + // is mostly a way to expose/debug issues. + datacapGauge = metrics.NewRegisteredGauge("blobpool/datacap", nil) + + // The below metrics track the per-datastore metrics for the primary blob + // store and the temporary limbo store. + datausedGauge = metrics.NewRegisteredGauge("blobpool/dataused", nil) + datarealGauge = metrics.NewRegisteredGauge("blobpool/datareal", nil) + slotusedGauge = metrics.NewRegisteredGauge("blobpool/slotused", nil) + + limboDatausedGauge = metrics.NewRegisteredGauge("blobpool/limbo/dataused", nil) + limboDatarealGauge = metrics.NewRegisteredGauge("blobpool/limbo/datareal", nil) + limboSlotusedGauge = metrics.NewRegisteredGauge("blobpool/limbo/slotused", nil) + + // The below metrics track the per-shelf metrics for the primary blob store + // and the temporary limbo store. + shelfDatausedGaugeName = "blobpool/shelf_%d/dataused" + shelfDatagapsGaugeName = "blobpool/shelf_%d/datagaps" + shelfSlotusedGaugeName = "blobpool/shelf_%d/slotused" + shelfSlotgapsGaugeName = "blobpool/shelf_%d/slotgaps" + + limboShelfDatausedGaugeName = "blobpool/limbo/shelf_%d/dataused" + limboShelfDatagapsGaugeName = "blobpool/limbo/shelf_%d/datagaps" + limboShelfSlotusedGaugeName = "blobpool/limbo/shelf_%d/slotused" + limboShelfSlotgapsGaugeName = "blobpool/limbo/shelf_%d/slotgaps" + + // The oversized metrics aggregate the shelf stats above the max blob count + // limits to track transactions that are just huge, but don't contain blobs. + // + // There are no oversized data in the limbo, it only contains blobs and some + // constant metadata. + oversizedDatausedGauge = metrics.NewRegisteredGauge("blobpool/oversized/dataused", nil) + oversizedDatagapsGauge = metrics.NewRegisteredGauge("blobpool/oversized/datagaps", nil) + oversizedSlotusedGauge = metrics.NewRegisteredGauge("blobpool/oversized/slotused", nil) + oversizedSlotgapsGauge = metrics.NewRegisteredGauge("blobpool/oversized/slotgaps", nil) + + // basefeeGauge and blobfeeGauge track the current network 1559 base fee and + // 4844 blob fee respectively. + basefeeGauge = metrics.NewRegisteredGauge("blobpool/basefee", nil) + blobfeeGauge = metrics.NewRegisteredGauge("blobpool/blobfee", nil) + + // pooltipGauge is the configurable miner tip to permit a transaction into + // the pool. + pooltipGauge = metrics.NewRegisteredGauge("blobpool/pooltip", nil) + + // addwait/time, resetwait/time and getwait/time track the rough health of + // the pool and whether it's capable of keeping up with the load from the + // network. + addwaitHist = metrics.NewRegisteredHistogram("blobpool/addwait", nil, metrics.NewExpDecaySample(1028, 0.015)) + addtimeHist = metrics.NewRegisteredHistogram("blobpool/addtime", nil, metrics.NewExpDecaySample(1028, 0.015)) + getwaitHist = metrics.NewRegisteredHistogram("blobpool/getwait", nil, metrics.NewExpDecaySample(1028, 0.015)) + gettimeHist = metrics.NewRegisteredHistogram("blobpool/gettime", nil, metrics.NewExpDecaySample(1028, 0.015)) + pendwaitHist = metrics.NewRegisteredHistogram("blobpool/pendwait", nil, metrics.NewExpDecaySample(1028, 0.015)) + pendtimeHist = metrics.NewRegisteredHistogram("blobpool/pendtime", nil, metrics.NewExpDecaySample(1028, 0.015)) + resetwaitHist = metrics.NewRegisteredHistogram("blobpool/resetwait", nil, metrics.NewExpDecaySample(1028, 0.015)) + resettimeHist = metrics.NewRegisteredHistogram("blobpool/resettime", nil, metrics.NewExpDecaySample(1028, 0.015)) + + // The below metrics track various cases where transactions are dropped out + // of the pool. Most are exceptional, some are chain progression and some + // threshold cappings. + dropInvalidMeter = metrics.NewRegisteredMeter("blobpool/drop/invalid", nil) // Invalid transaction, consensus change or bugfix, neutral-ish + dropDanglingMeter = metrics.NewRegisteredMeter("blobpool/drop/dangling", nil) // First nonce gapped, bad + dropFilledMeter = metrics.NewRegisteredMeter("blobpool/drop/filled", nil) // State full-overlap, chain progress, ok + dropOverlappedMeter = metrics.NewRegisteredMeter("blobpool/drop/overlapped", nil) // State partial-overlap, chain progress, ok + dropRepeatedMeter = metrics.NewRegisteredMeter("blobpool/drop/repeated", nil) // Repeated nonce, bad + dropGappedMeter = metrics.NewRegisteredMeter("blobpool/drop/gapped", nil) // Non-first nonce gapped, bad + dropOverdraftedMeter = metrics.NewRegisteredMeter("blobpool/drop/overdrafted", nil) // Balance exceeded, bad + dropOvercappedMeter = metrics.NewRegisteredMeter("blobpool/drop/overcapped", nil) // Per-account cap exceeded, bad + dropOverflownMeter = metrics.NewRegisteredMeter("blobpool/drop/overflown", nil) // Global disk cap exceeded, neutral-ish + dropUnderpricedMeter = metrics.NewRegisteredMeter("blobpool/drop/underpriced", nil) // Gas tip changed, neutral + dropReplacedMeter = metrics.NewRegisteredMeter("blobpool/drop/replaced", nil) // Transaction replaced, neutral + + // The below metrics track various outcomes of transactions being added to + // the pool. + addInvalidMeter = metrics.NewRegisteredMeter("blobpool/add/invalid", nil) // Invalid transaction, reject, neutral + addUnderpricedMeter = metrics.NewRegisteredMeter("blobpool/add/underpriced", nil) // Gas tip too low, neutral + addStaleMeter = metrics.NewRegisteredMeter("blobpool/add/stale", nil) // Nonce already filled, reject, bad-ish + addGappedMeter = metrics.NewRegisteredMeter("blobpool/add/gapped", nil) // Nonce gapped, reject, bad-ish + addOverdraftedMeter = metrics.NewRegisteredMeter("blobpool/add/overdrafted", nil) // Balance exceeded, reject, neutral + addOvercappedMeter = metrics.NewRegisteredMeter("blobpool/add/overcapped", nil) // Per-account cap exceeded, reject, neutral + addNoreplaceMeter = metrics.NewRegisteredMeter("blobpool/add/noreplace", nil) // Replacement fees or tips too low, neutral + addNonExclusiveMeter = metrics.NewRegisteredMeter("blobpool/add/nonexclusive", nil) // Plain transaction from same account exists, reject, neutral + addValidMeter = metrics.NewRegisteredMeter("blobpool/add/valid", nil) // Valid transaction, add, neutral +) diff --git a/core/txpool/blobpool/priority.go b/core/txpool/blobpool/priority.go new file mode 100644 index 0000000..7ae7f92 --- /dev/null +++ b/core/txpool/blobpool/priority.go @@ -0,0 +1,90 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import ( + "math" + "math/bits" + + "github.com/holiman/uint256" +) + +// log1_125 is used in the eviction priority calculation. +var log1_125 = math.Log(1.125) + +// evictionPriority calculates the eviction priority based on the algorithm +// described in the BlobPool docs for both fee components. +// +// This method takes about 8ns on a very recent laptop CPU, recalculating about +// 125 million transaction priority values per second. +func evictionPriority(basefeeJumps float64, txBasefeeJumps, blobfeeJumps, txBlobfeeJumps float64) int { + var ( + basefeePriority = evictionPriority1D(basefeeJumps, txBasefeeJumps) + blobfeePriority = evictionPriority1D(blobfeeJumps, txBlobfeeJumps) + ) + if basefeePriority < blobfeePriority { + return basefeePriority + } + return blobfeePriority +} + +// evictionPriority1D calculates the eviction priority based on the algorithm +// described in the BlobPool docs for a single fee component. +func evictionPriority1D(basefeeJumps float64, txfeeJumps float64) int { + jumps := txfeeJumps - basefeeJumps + if int(jumps) == 0 { + return 0 // can't log2 0 + } + if jumps < 0 { + return -intLog2(uint(-math.Floor(jumps))) + } + return intLog2(uint(math.Ceil(jumps))) +} + +// dynamicFeeJumps calculates the log1.125(fee), namely the number of fee jumps +// needed to reach the requested one. We only use it when calculating the jumps +// between 2 fees, so it doesn't matter from what exact number it returns. +// It returns the result from (0, 1, 1.125). +// +// This method is very expensive, taking about 75ns on a very recent laptop CPU, +// but the result does not change with the lifetime of a transaction, so it can +// be cached. +func dynamicFeeJumps(fee *uint256.Int) float64 { + if fee.IsZero() { + return 0 // can't log2 zero, should never happen outside tests, but don't choke + } + return math.Log(fee.Float64()) / log1_125 +} + +// intLog2 is a helper to calculate the integral part of a log2 of an unsigned +// integer. It is a very specific calculation that's not particularly useful in +// general, but it's what we need here (it's fast). +func intLog2(n uint) int { + switch { + case n == 0: + panic("log2(0) is undefined") + + case n < 2048: + return bits.UintSize - bits.LeadingZeros(n) - 1 + + default: + // The input is log1.125(uint256) = log2(uint256) / log2(1.125). At the + // most extreme, log2(uint256) will be a bit below 257, and the constant + // log2(1.125) ~= 0.17. The larges input thus is ~257 / ~0.17 ~= ~1511. + panic("dynamic fee jump diffs cannot reach this") + } +} diff --git a/core/txpool/blobpool/priority_test.go b/core/txpool/blobpool/priority_test.go new file mode 100644 index 0000000..cf0e045 --- /dev/null +++ b/core/txpool/blobpool/priority_test.go @@ -0,0 +1,87 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import ( + "testing" + + "github.com/holiman/uint256" +) + +// Tests that the priority fees are calculated correctly as the log2 of the fee +// jumps needed to go from the base fee to the tx's fee cap. +func TestPriorityCalculation(t *testing.T) { + tests := []struct { + basefee uint64 + txfee uint64 + result int + }{ + {basefee: 7, txfee: 10, result: 2}, // 3.02 jumps, 4 ceil, 2 log2 + {basefee: 17_200_000_000, txfee: 17_200_000_000, result: 0}, // 0 jumps, special case 0 log2 + {basefee: 9_853_941_692, txfee: 11_085_092_510, result: 0}, // 0.99 jumps, 1 ceil, 0 log2 + {basefee: 11_544_106_391, txfee: 10_356_781_100, result: 0}, // -0.92 jumps, -1 floor, 0 log2 + {basefee: 17_200_000_000, txfee: 7, result: -7}, // -183.57 jumps, -184 floor, -7 log2 + {basefee: 7, txfee: 17_200_000_000, result: 7}, // 183.57 jumps, 184 ceil, 7 log2 + } + for i, tt := range tests { + var ( + baseJumps = dynamicFeeJumps(uint256.NewInt(tt.basefee)) + feeJumps = dynamicFeeJumps(uint256.NewInt(tt.txfee)) + ) + if prio := evictionPriority1D(baseJumps, feeJumps); prio != tt.result { + t.Errorf("test %d priority mismatch: have %d, want %d", i, prio, tt.result) + } + } +} + +// Benchmarks how many dynamic fee jump values can be done. +func BenchmarkDynamicFeeJumpCalculation(b *testing.B) { + fees := make([]*uint256.Int, b.N) + for i := 0; i < b.N; i++ { + fees[i] = uint256.NewInt(rand.Uint64()) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + dynamicFeeJumps(fees[i]) + } +} + +// Benchmarks how many priority recalculations can be done. +func BenchmarkPriorityCalculation(b *testing.B) { + // The basefee and blob fee is constant for all transactions across a block, + // so we can assume their absolute jump counts can be pre-computed. + basefee := uint256.NewInt(17_200_000_000) // 17.2 Gwei is the 22.03.2023 zero-emission basefee, random number + blobfee := uint256.NewInt(123_456_789_000) // Completely random, no idea what this will be + + basefeeJumps := dynamicFeeJumps(basefee) + blobfeeJumps := dynamicFeeJumps(blobfee) + + // The transaction's fee cap and blob fee cap are constant across the life + // of the transaction, so we can pre-calculate and cache them. + txBasefeeJumps := make([]float64, b.N) + txBlobfeeJumps := make([]float64, b.N) + for i := 0; i < b.N; i++ { + txBasefeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64())) + txBlobfeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64())) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + evictionPriority(basefeeJumps, txBasefeeJumps[i], blobfeeJumps, txBlobfeeJumps[i]) + } +} diff --git a/core/txpool/blobpool/slotter.go b/core/txpool/blobpool/slotter.go new file mode 100644 index 0000000..35349c3 --- /dev/null +++ b/core/txpool/blobpool/slotter.go @@ -0,0 +1,38 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +// newSlotter creates a helper method for the Billy datastore that returns the +// individual shelf sizes used to store transactions in. +// +// The slotter will create shelves for each possible blob count + some tx metadata +// wiggle room, up to the max permitted limits. +// +// The slotter also creates a shelf for 0-blob transactions. Whilst those are not +// allowed in the current protocol, having an empty shelf is not a relevant use +// of resources, but it makes stress testing with junk transactions simpler. +func newSlotter() func() (uint32, bool) { + slotsize := uint32(txAvgSize) + slotsize -= uint32(blobSize) // underflows, it's ok, will overflow back in the first return + + return func() (size uint32, done bool) { + slotsize += blobSize + finished := slotsize > maxBlobsPerTransaction*blobSize+txMaxSize + + return slotsize, finished + } +} diff --git a/core/txpool/blobpool/slotter_test.go b/core/txpool/blobpool/slotter_test.go new file mode 100644 index 0000000..a7b43b4 --- /dev/null +++ b/core/txpool/blobpool/slotter_test.go @@ -0,0 +1,60 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package blobpool + +import "testing" + +// Tests that the slotter creates the expected database shelves. +func TestNewSlotter(t *testing.T) { + // Generate the database shelve sizes + slotter := newSlotter() + + var shelves []uint32 + for { + shelf, done := slotter() + shelves = append(shelves, shelf) + if done { + break + } + } + // Compare the database shelves to the expected ones + want := []uint32{ + 0*blobSize + txAvgSize, // 0 blob + some expected tx infos + 1*blobSize + txAvgSize, // 1 blob + some expected tx infos + 2*blobSize + txAvgSize, // 2 blob + some expected tx infos (could be fewer blobs and more tx data) + 3*blobSize + txAvgSize, // 3 blob + some expected tx infos (could be fewer blobs and more tx data) + 4*blobSize + txAvgSize, // 4 blob + some expected tx infos (could be fewer blobs and more tx data) + 5*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 6*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 7*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 8*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 9*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 10*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 11*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 12*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 13*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 14*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos >= 4 blobs + max tx metadata size + } + if len(shelves) != len(want) { + t.Errorf("shelves count mismatch: have %d, want %d", len(shelves), len(want)) + } + for i := 0; i < len(shelves) && i < len(want); i++ { + if shelves[i] != want[i] { + t.Errorf("shelf %d mismatch: have %d, want %d", i, shelves[i], want[i]) + } + } +} diff --git a/core/txpool/errors.go b/core/txpool/errors.go new file mode 100644 index 0000000..3a6a913 --- /dev/null +++ b/core/txpool/errors.go @@ -0,0 +1,63 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package txpool + +import "errors" + +var ( + // ErrAlreadyKnown is returned if the transactions is already contained + // within the pool. + ErrAlreadyKnown = errors.New("already known") + + // ErrInvalidSender is returned if the transaction contains an invalid signature. + ErrInvalidSender = errors.New("invalid sender") + + // ErrUnderpriced is returned if a transaction's gas price is below the minimum + // configured for the transaction pool. + ErrUnderpriced = errors.New("transaction underpriced") + + // ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced + // with a different one without the required price bump. + ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") + + // ErrAccountLimitExceeded is returned if a transaction would exceed the number + // allowed by a pool for a single account. + ErrAccountLimitExceeded = errors.New("account limit exceeded") + + // ErrGasLimit is returned if a transaction's requested gas limit exceeds the + // maximum allowance of the current block. + ErrGasLimit = errors.New("exceeds block gas limit") + + // ErrNegativeValue is a sanity error to ensure no one is able to specify a + // transaction with a negative value. + ErrNegativeValue = errors.New("negative value") + + // ErrOversizedData is returned if the input data of a transaction is greater + // than some meaningful limit a user might use. This is not a consensus error + // making the transaction invalid, rather a DOS protection. + ErrOversizedData = errors.New("oversized data") + + // ErrFutureReplacePending is returned if a future transaction replaces a pending + // one. Future transactions should only be able to replace other future transactions. + ErrFutureReplacePending = errors.New("future transaction tries to replace pending") + + // ErrAlreadyReserved is returned if the sender address has a pending transaction + // in a different subpool. For example, this error is returned in response to any + // input transaction of non-blob type when a blob transaction from this sender + // remains pending (and vice-versa). + ErrAlreadyReserved = errors.New("address already reserved") +) diff --git a/core/txpool/legacypool/journal.go b/core/txpool/legacypool/journal.go new file mode 100644 index 0000000..899ed00 --- /dev/null +++ b/core/txpool/legacypool/journal.go @@ -0,0 +1,186 @@ +// 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 legacypool + +import ( + "errors" + "io" + "io/fs" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// errNoActiveJournal is returned if a transaction is attempted to be inserted +// into the journal, but no such file is currently open. +var errNoActiveJournal = errors.New("no active journal") + +// devNull is a WriteCloser that just discards anything written into it. Its +// goal is to allow the transaction journal to write into a fake journal when +// loading transactions on startup without printing warnings due to no file +// being read for write. +type devNull struct{} + +func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil } +func (*devNull) Close() error { return nil } + +// journal is a rotating log of transactions with the aim of storing locally +// created transactions to allow non-executed ones to survive node restarts. +type journal struct { + path string // Filesystem path to store the transactions at + writer io.WriteCloser // Output stream to write new transactions into +} + +// newTxJournal creates a new transaction journal to +func newTxJournal(path string) *journal { + return &journal{ + path: path, + } +} + +// load parses a transaction journal dump from disk, loading its contents into +// the specified pool. +func (journal *journal) load(add func([]*types.Transaction) []error) error { + // Open the journal for loading any past transactions + input, err := os.Open(journal.path) + if errors.Is(err, fs.ErrNotExist) { + // Skip the parsing if the journal file doesn't exist at all + return nil + } + if err != nil { + return err + } + defer input.Close() + + // Temporarily discard any journal additions (don't double add on load) + journal.writer = new(devNull) + defer func() { journal.writer = nil }() + + // Inject all transactions from the journal into the pool + stream := rlp.NewStream(input, 0) + total, dropped := 0, 0 + + // Create a method to load a limited batch of transactions and bump the + // appropriate progress counters. Then use this method to load all the + // journaled transactions in small-ish batches. + loadBatch := func(txs types.Transactions) { + for _, err := range add(txs) { + if err != nil { + log.Debug("Failed to add journaled transaction", "err", err) + dropped++ + } + } + } + var ( + failure error + batch types.Transactions + ) + for { + // Parse the next transaction and terminate on error + tx := new(types.Transaction) + if err = stream.Decode(tx); err != nil { + if err != io.EOF { + failure = err + } + if batch.Len() > 0 { + loadBatch(batch) + } + break + } + // New transaction parsed, queue up for later, import if threshold is reached + total++ + + if batch = append(batch, tx); batch.Len() > 1024 { + loadBatch(batch) + batch = batch[:0] + } + } + log.Info("Loaded local transaction journal", "transactions", total, "dropped", dropped) + + return failure +} + +// insert adds the specified transaction to the local disk journal. +func (journal *journal) insert(tx *types.Transaction) error { + if journal.writer == nil { + return errNoActiveJournal + } + if err := rlp.Encode(journal.writer, tx); err != nil { + return err + } + return nil +} + +// rotate regenerates the transaction journal based on the current contents of +// the transaction pool. +func (journal *journal) rotate(all map[common.Address]types.Transactions) error { + // Close the current journal (if any is open) + if journal.writer != nil { + if err := journal.writer.Close(); err != nil { + return err + } + journal.writer = nil + } + // Generate a new journal with the contents of the current pool + replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + journaled := 0 + for _, txs := range all { + for _, tx := range txs { + if err = rlp.Encode(replacement, tx); err != nil { + replacement.Close() + return err + } + } + journaled += len(txs) + } + replacement.Close() + + // Replace the live journal with the newly generated one + if err = os.Rename(journal.path+".new", journal.path); err != nil { + return err + } + sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return err + } + journal.writer = sink + + logger := log.Info + if len(all) == 0 { + logger = log.Debug + } + logger("Regenerated local transaction journal", "transactions", journaled, "accounts", len(all)) + + return nil +} + +// close flushes the transaction journal contents to disk and closes the file. +func (journal *journal) close() error { + var err error + + if journal.writer != nil { + err = journal.writer.Close() + journal.writer = nil + } + return err +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go new file mode 100644 index 0000000..4e1d26a --- /dev/null +++ b/core/txpool/legacypool/legacypool.go @@ -0,0 +1,1961 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package legacypool implements the normal EVM execution transaction pool. +package legacypool + +import ( + "errors" + "math" + "math/big" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +const ( + // txSlotSize is used to calculate how many data slots a single transaction + // takes up based on its size. The slots are used as DoS protection, ensuring + // that validating a new transaction remains a constant operation (in reality + // O(maxslots), where max slots are 4 currently). + txSlotSize = 32 * 1024 + + // txMaxSize is the maximum size a single transaction can have. This field has + // non-trivial consequences: larger transactions are significantly harder and + // more expensive to propagate; larger transactions also take more resources + // to validate whether they fit into the pool or not. + txMaxSize = 4 * txSlotSize // 128KB +) + +var ( + // ErrTxPoolOverflow is returned if the transaction pool is full and can't accept + // another remote transaction. + ErrTxPoolOverflow = errors.New("txpool is full") +) + +var ( + evictionInterval = time.Minute // Time interval to check for evictable transactions + statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats +) + +var ( + // Metrics for the pending pool + pendingDiscardMeter = metrics.NewRegisteredMeter("txpool/pending/discard", nil) + pendingReplaceMeter = metrics.NewRegisteredMeter("txpool/pending/replace", nil) + pendingRateLimitMeter = metrics.NewRegisteredMeter("txpool/pending/ratelimit", nil) // Dropped due to rate limiting + pendingNofundsMeter = metrics.NewRegisteredMeter("txpool/pending/nofunds", nil) // Dropped due to out-of-funds + + // Metrics for the queued pool + queuedDiscardMeter = metrics.NewRegisteredMeter("txpool/queued/discard", nil) + queuedReplaceMeter = metrics.NewRegisteredMeter("txpool/queued/replace", nil) + queuedRateLimitMeter = metrics.NewRegisteredMeter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting + queuedNofundsMeter = metrics.NewRegisteredMeter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds + queuedEvictionMeter = metrics.NewRegisteredMeter("txpool/queued/eviction", nil) // Dropped due to lifetime + + // General tx metrics + knownTxMeter = metrics.NewRegisteredMeter("txpool/known", nil) + validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil) + invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil) + underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil) + overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil) + + // throttleTxMeter counts how many transactions are rejected due to too-many-changes between + // txpool reorgs. + throttleTxMeter = metrics.NewRegisteredMeter("txpool/throttle", nil) + // reorgDurationTimer measures how long time a txpool reorg takes. + reorgDurationTimer = metrics.NewRegisteredTimer("txpool/reorgtime", nil) + // dropBetweenReorgHistogram counts how many drops we experience between two reorg runs. It is expected + // that this number is pretty low, since txpool reorgs happen very frequently. + dropBetweenReorgHistogram = metrics.NewRegisteredHistogram("txpool/dropbetweenreorg", nil, metrics.NewExpDecaySample(1028, 0.015)) + + pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) + queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) + localGauge = metrics.NewRegisteredGauge("txpool/local", nil) + slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) + + reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil) +) + +// BlockChain defines the minimal set of methods needed to back a tx pool with +// a chain. Exists to allow mocking the live chain out of tests. +type BlockChain interface { + // Config retrieves the chain's fork configuration. + Config() *params.ChainConfig + + // CurrentBlock returns the current head of the chain. + CurrentBlock() *types.Header + + // GetBlock retrieves a specific block, used during pool resets. + GetBlock(hash common.Hash, number uint64) *types.Block + + // StateAt returns a state database for a given root hash (generally the head). + StateAt(root common.Hash) (*state.StateDB, error) +} + +// Config are the configuration parameters of the transaction pool. +type Config struct { + Locals []common.Address // Addresses that should be treated by default as local + NoLocals bool // Whether local transaction handling should be disabled + Journal string // Journal of local transactions to survive node restarts + Rejournal time.Duration // Time interval to regenerate the local transaction journal + + PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool + PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce) + + AccountSlots uint64 // Number of executable transaction slots guaranteed per account + GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts + AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account + GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts + + Lifetime time.Duration // Maximum amount of time non-executable transaction are queued +} + +// DefaultConfig contains the default configurations for the transaction pool. +var DefaultConfig = Config{ + Journal: "transactions.rlp", + Rejournal: time.Hour, + + PriceLimit: 1, + PriceBump: 10, + + AccountSlots: 16, + GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio + AccountQueue: 64, + GlobalQueue: 1024, + + Lifetime: 3 * time.Hour, +} + +// sanitize checks the provided user configurations and changes anything that's +// unreasonable or unworkable. +func (config *Config) sanitize() Config { + conf := *config + if conf.Rejournal < time.Second { + log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second) + conf.Rejournal = time.Second + } + if conf.PriceLimit < 1 { + log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultConfig.PriceLimit) + conf.PriceLimit = DefaultConfig.PriceLimit + } + if conf.PriceBump < 1 { + log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump) + conf.PriceBump = DefaultConfig.PriceBump + } + if conf.AccountSlots < 1 { + log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultConfig.AccountSlots) + conf.AccountSlots = DefaultConfig.AccountSlots + } + if conf.GlobalSlots < 1 { + log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultConfig.GlobalSlots) + conf.GlobalSlots = DefaultConfig.GlobalSlots + } + if conf.AccountQueue < 1 { + log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultConfig.AccountQueue) + conf.AccountQueue = DefaultConfig.AccountQueue + } + if conf.GlobalQueue < 1 { + log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) + conf.GlobalQueue = DefaultConfig.GlobalQueue + } + if conf.Lifetime < 1 { + log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) + conf.Lifetime = DefaultConfig.Lifetime + } + return conf +} + +// LegacyPool contains all currently known transactions. Transactions +// enter the pool when they are received from the network or submitted +// locally. They exit the pool when they are included in the blockchain. +// +// The pool separates processable transactions (which can be applied to the +// current state) and future transactions. Transactions move between those +// two states over time as they are received and processed. +type LegacyPool struct { + config Config + chainconfig *params.ChainConfig + chain BlockChain + gasTip atomic.Pointer[uint256.Int] + txFeed event.Feed + signer types.Signer + mu sync.RWMutex + + currentHead atomic.Pointer[types.Header] // Current head of the blockchain + currentState *state.StateDB // Current state in the blockchain head + pendingNonces *noncer // Pending state tracking virtual nonces + + locals *accountSet // Set of local transaction to exempt from eviction rules + journal *journal // Journal of local transaction to back up to disk + + reserve txpool.AddressReserver // Address reserver to ensure exclusivity across subpools + pending map[common.Address]*list // All currently processable transactions + queue map[common.Address]*list // Queued but non-processable transactions + beats map[common.Address]time.Time // Last heartbeat from each known account + all *lookup // All transactions to allow lookups + priced *pricedList // All transactions sorted by price + + reqResetCh chan *txpoolResetRequest + reqPromoteCh chan *accountSet + queueTxEventCh chan *types.Transaction + reorgDoneCh chan chan struct{} + reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop + wg sync.WaitGroup // tracks loop, scheduleReorgLoop + initDoneCh chan struct{} // is closed once the pool is initialized (for tests) + + changesSinceReorg int // A counter for how many drops we've performed in-between reorg. +} + +type txpoolResetRequest struct { + oldHead, newHead *types.Header +} + +// New creates a new transaction pool to gather, sort and filter inbound +// transactions from the network. +func New(config Config, chain BlockChain) *LegacyPool { + // Sanitize the input to ensure no vulnerable gas prices are set + config = (&config).sanitize() + + // Create the transaction pool with its initial settings + pool := &LegacyPool{ + config: config, + chain: chain, + chainconfig: chain.Config(), + signer: types.LatestSigner(chain.Config()), + pending: make(map[common.Address]*list), + queue: make(map[common.Address]*list), + beats: make(map[common.Address]time.Time), + all: newLookup(), + reqResetCh: make(chan *txpoolResetRequest), + reqPromoteCh: make(chan *accountSet), + queueTxEventCh: make(chan *types.Transaction), + reorgDoneCh: make(chan chan struct{}), + reorgShutdownCh: make(chan struct{}), + initDoneCh: make(chan struct{}), + } + pool.locals = newAccountSet(pool.signer) + for _, addr := range config.Locals { + log.Info("Setting new local account", "address", addr) + pool.locals.add(addr) + } + pool.priced = newPricedList(pool.all) + + if !config.NoLocals && config.Journal != "" { + pool.journal = newTxJournal(config.Journal) + } + return pool +} + +// Filter returns whether the given transaction can be consumed by the legacy +// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction. +func (pool *LegacyPool) Filter(tx *types.Transaction) bool { + switch tx.Type() { + case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType: + return true + default: + return false + } +} + +// Init sets the gas price needed to keep a transaction in the pool and the chain +// head to allow balance / nonce checks. The transaction journal will be loaded +// from disk and filtered based on the provided starting settings. The internal +// goroutines will be spun up and the pool deemed operational afterwards. +func (pool *LegacyPool) Init(gasTip uint64, head *types.Header, reserve txpool.AddressReserver) error { + // Set the address reserver to request exclusive access to pooled accounts + pool.reserve = reserve + + // Set the basic pool parameters + pool.gasTip.Store(uint256.NewInt(gasTip)) + + // Initialize the state with head block, or fallback to empty one in + // case the head state is not available (might occur when node is not + // fully synced). + statedb, err := pool.chain.StateAt(head.Root) + if err != nil { + statedb, err = pool.chain.StateAt(types.EmptyRootHash) + } + if err != nil { + return err + } + pool.currentHead.Store(head) + pool.currentState = statedb + pool.pendingNonces = newNoncer(statedb) + + // Start the reorg loop early, so it can handle requests generated during + // journal loading. + pool.wg.Add(1) + go pool.scheduleReorgLoop() + + // If local transactions and journaling is enabled, load from disk + if pool.journal != nil { + if err := pool.journal.load(pool.addLocals); err != nil { + log.Warn("Failed to load transaction journal", "err", err) + } + if err := pool.journal.rotate(pool.local()); err != nil { + log.Warn("Failed to rotate transaction journal", "err", err) + } + } + pool.wg.Add(1) + go pool.loop() + return nil +} + +// loop is the transaction pool's main event loop, waiting for and reacting to +// outside blockchain events as well as for various reporting and transaction +// eviction events. +func (pool *LegacyPool) loop() { + defer pool.wg.Done() + + var ( + prevPending, prevQueued, prevStales int + + // Start the stats reporting and transaction eviction tickers + report = time.NewTicker(statsReportInterval) + evict = time.NewTicker(evictionInterval) + journal = time.NewTicker(pool.config.Rejournal) + ) + defer report.Stop() + defer evict.Stop() + defer journal.Stop() + + // Notify tests that the init phase is done + close(pool.initDoneCh) + for { + select { + // Handle pool shutdown + case <-pool.reorgShutdownCh: + return + + // Handle stats reporting ticks + case <-report.C: + pool.mu.RLock() + pending, queued := pool.stats() + pool.mu.RUnlock() + stales := int(pool.priced.stales.Load()) + + if pending != prevPending || queued != prevQueued || stales != prevStales { + log.Debug("Transaction pool status report", "executable", pending, "queued", queued, "stales", stales) + prevPending, prevQueued, prevStales = pending, queued, stales + } + + // Handle inactive account transaction eviction + case <-evict.C: + pool.mu.Lock() + for addr := range pool.queue { + // Skip local transactions from the eviction mechanism + if pool.locals.contains(addr) { + continue + } + // Any non-locals old enough should be removed + if time.Since(pool.beats[addr]) > pool.config.Lifetime { + list := pool.queue[addr].Flatten() + for _, tx := range list { + pool.removeTx(tx.Hash(), true, true) + } + queuedEvictionMeter.Mark(int64(len(list))) + } + } + pool.mu.Unlock() + + // Handle local transaction journal rotation + case <-journal.C: + if pool.journal != nil { + pool.mu.Lock() + if err := pool.journal.rotate(pool.local()); err != nil { + log.Warn("Failed to rotate local tx journal", "err", err) + } + pool.mu.Unlock() + } + } + } +} + +// Close terminates the transaction pool. +func (pool *LegacyPool) Close() error { + // Terminate the pool reorger and return + close(pool.reorgShutdownCh) + pool.wg.Wait() + + if pool.journal != nil { + pool.journal.close() + } + log.Info("Transaction pool stopped") + return nil +} + +// Reset implements txpool.SubPool, allowing the legacy pool's internal state to be +// kept in sync with the main transaction pool's internal state. +func (pool *LegacyPool) Reset(oldHead, newHead *types.Header) { + wait := pool.requestReset(oldHead, newHead) + <-wait +} + +// SubscribeTransactions registers a subscription for new transaction events, +// supporting feeding only newly seen or also resurrected transactions. +func (pool *LegacyPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { + // The legacy pool has a very messed up internal shuffling, so it's kind of + // hard to separate newly discovered transaction from resurrected ones. This + // is because the new txs are added to the queue, resurrected ones too and + // reorgs run lazily, so separating the two would need a marker. + return pool.txFeed.Subscribe(ch) +} + +// SetGasTip updates the minimum gas tip required by the transaction pool for a +// new transaction, and drops all transactions below this threshold. +func (pool *LegacyPool) SetGasTip(tip *big.Int) { + pool.mu.Lock() + defer pool.mu.Unlock() + + var ( + newTip = uint256.MustFromBig(tip) + old = pool.gasTip.Load() + ) + pool.gasTip.Store(newTip) + // If the min miner fee increased, remove transactions below the new threshold + if newTip.Cmp(old) > 0 { + // pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead + drop := pool.all.RemotesBelowTip(tip) + for _, tx := range drop { + pool.removeTx(tx.Hash(), false, true) + } + pool.priced.Removed(len(drop)) + } + log.Info("Legacy pool tip threshold updated", "tip", newTip) +} + +// Nonce returns the next nonce of an account, with all transactions executable +// by the pool already applied on top. +func (pool *LegacyPool) Nonce(addr common.Address) uint64 { + pool.mu.RLock() + defer pool.mu.RUnlock() + + return pool.pendingNonces.get(addr) +} + +// Stats retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions. +func (pool *LegacyPool) Stats() (int, int) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + return pool.stats() +} + +// stats retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions. +func (pool *LegacyPool) stats() (int, int) { + pending := 0 + for _, list := range pool.pending { + pending += list.Len() + } + queued := 0 + for _, list := range pool.queue { + queued += list.Len() + } + return pending, queued +} + +// Content retrieves the data content of the transaction pool, returning all the +// pending as well as queued transactions, grouped by account and sorted by nonce. +func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + pool.mu.Lock() + defer pool.mu.Unlock() + + pending := make(map[common.Address][]*types.Transaction, len(pool.pending)) + for addr, list := range pool.pending { + pending[addr] = list.Flatten() + } + queued := make(map[common.Address][]*types.Transaction, len(pool.queue)) + for addr, list := range pool.queue { + queued[addr] = list.Flatten() + } + return pending, queued +} + +// ContentFrom retrieves the data content of the transaction pool, returning the +// pending as well as queued transactions of this address, grouped by nonce. +func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + var pending []*types.Transaction + if list, ok := pool.pending[addr]; ok { + pending = list.Flatten() + } + var queued []*types.Transaction + if list, ok := pool.queue[addr]; ok { + queued = list.Flatten() + } + return pending, queued +} + +// Pending retrieves all currently processable transactions, grouped by origin +// account and sorted by nonce. +// +// The transactions can also be pre-filtered by the dynamic fee components to +// reduce allocations and load on downstream subsystems. +func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction { + // If only blob transactions are requested, this pool is unsuitable as it + // contains none, don't even bother. + if filter.OnlyBlobTxs { + return nil + } + pool.mu.Lock() + defer pool.mu.Unlock() + + // Convert the new uint256.Int types to the old big.Int ones used by the legacy pool + var ( + minTipBig *big.Int + baseFeeBig *big.Int + ) + if filter.MinTip != nil { + minTipBig = filter.MinTip.ToBig() + } + if filter.BaseFee != nil { + baseFeeBig = filter.BaseFee.ToBig() + } + pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) + for addr, list := range pool.pending { + txs := list.Flatten() + + // If the miner requests tip enforcement, cap the lists now + if minTipBig != nil && !pool.locals.contains(addr) { + for i, tx := range txs { + if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 { + txs = txs[:i] + break + } + } + } + if len(txs) > 0 { + lazies := make([]*txpool.LazyTransaction, len(txs)) + for i := 0; i < len(txs); i++ { + lazies[i] = &txpool.LazyTransaction{ + Pool: pool, + Hash: txs[i].Hash(), + Tx: txs[i], + Time: txs[i].Time(), + GasFeeCap: uint256.MustFromBig(txs[i].GasFeeCap()), + GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()), + Gas: txs[i].Gas(), + BlobGas: txs[i].BlobGas(), + } + } + pending[addr] = lazies + } + } + return pending +} + +// Locals retrieves the accounts currently considered local by the pool. +func (pool *LegacyPool) Locals() []common.Address { + pool.mu.Lock() + defer pool.mu.Unlock() + + return pool.locals.flatten() +} + +// local retrieves all currently known local transactions, grouped by origin +// account and sorted by nonce. The returned transaction set is a copy and can be +// freely modified by calling code. +func (pool *LegacyPool) local() map[common.Address]types.Transactions { + txs := make(map[common.Address]types.Transactions) + for addr := range pool.locals.accounts { + if pending := pool.pending[addr]; pending != nil { + txs[addr] = append(txs[addr], pending.Flatten()...) + } + if queued := pool.queue[addr]; queued != nil { + txs[addr] = append(txs[addr], queued.Flatten()...) + } + } + return txs +} + +// validateTxBasics checks whether a transaction is valid according to the consensus +// rules, but does not check state-dependent validation such as sufficient balance. +// This check is meant as an early check which only needs to be performed once, +// and does not require the pool mutex to be held. +func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) error { + opts := &txpool.ValidationOptions{ + Config: pool.chainconfig, + Accept: 0 | + 1< pool.config.GlobalSlots+pool.config.GlobalQueue { + // If the new transaction is underpriced, don't accept it + if !isLocal && pool.priced.Underpriced(tx) { + log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) + underpricedTxMeter.Mark(1) + return false, txpool.ErrUnderpriced + } + + // We're about to replace a transaction. The reorg does a more thorough + // analysis of what to remove and how, but it runs async. We don't want to + // do too many replacements between reorg-runs, so we cap the number of + // replacements to 25% of the slots + if pool.changesSinceReorg > int(pool.config.GlobalSlots/4) { + throttleTxMeter.Mark(1) + return false, ErrTxPoolOverflow + } + + // New transaction is better than our worse ones, make room for it. + // If it's a local transaction, forcibly discard all available transactions. + // Otherwise if we can't make enough room for new one, abort the operation. + drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) + + // Special case, we still can't make the room for the new remote one. + if !isLocal && !success { + log.Trace("Discarding overflown transaction", "hash", hash) + overflowedTxMeter.Mark(1) + return false, ErrTxPoolOverflow + } + + // If the new transaction is a future transaction it should never churn pending transactions + if !isLocal && pool.isGapped(from, tx) { + var replacesPending bool + for _, dropTx := range drop { + dropSender, _ := types.Sender(pool.signer, dropTx) + if list := pool.pending[dropSender]; list != nil && list.Contains(dropTx.Nonce()) { + replacesPending = true + break + } + } + // Add all transactions back to the priced queue + if replacesPending { + for _, dropTx := range drop { + pool.priced.Put(dropTx, false) + } + log.Trace("Discarding future transaction replacing pending tx", "hash", hash) + return false, txpool.ErrFutureReplacePending + } + } + + // Kick out the underpriced remote transactions. + for _, tx := range drop { + log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) + underpricedTxMeter.Mark(1) + + sender, _ := types.Sender(pool.signer, tx) + dropped := pool.removeTx(tx.Hash(), false, sender != from) // Don't unreserve the sender of the tx being added if last from the acc + + pool.changesSinceReorg += dropped + } + } + + // Try to replace an existing transaction in the pending pool + if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { + // Nonce already pending, check if required price bump is met + inserted, old := list.Add(tx, pool.config.PriceBump) + if !inserted { + pendingDiscardMeter.Mark(1) + return false, txpool.ErrReplaceUnderpriced + } + // New transaction is better, replace old one + if old != nil { + pool.all.Remove(old.Hash()) + pool.priced.Removed(1) + pendingReplaceMeter.Mark(1) + } + pool.all.Add(tx, isLocal) + pool.priced.Put(tx, isLocal) + pool.journalTx(from, tx) + pool.queueTxEvent(tx) + log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) + + // Successful promotion, bump the heartbeat + pool.beats[from] = time.Now() + return old != nil, nil + } + // New transaction isn't replacing a pending one, push into queue + replaced, err = pool.enqueueTx(hash, tx, isLocal, true) + if err != nil { + return false, err + } + // Mark local addresses and journal local transactions + if local && !pool.locals.contains(from) { + log.Info("Setting new local account", "address", from) + pool.locals.add(from) + pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time. + } + if isLocal { + localGauge.Inc(1) + } + pool.journalTx(from, tx) + + log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) + return replaced, nil +} + +// isGapped reports whether the given transaction is immediately executable. +func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) bool { + // Short circuit if transaction falls within the scope of the pending list + // or matches the next pending nonce which can be promoted as an executable + // transaction afterwards. Note, the tx staleness is already checked in + // 'validateTx' function previously. + next := pool.pendingNonces.get(from) + if tx.Nonce() <= next { + return false + } + // The transaction has a nonce gap with pending list, it's only considered + // as executable if transactions in queue can fill up the nonce gap. + queue, ok := pool.queue[from] + if !ok { + return true + } + for nonce := next; nonce < tx.Nonce(); nonce++ { + if !queue.Contains(nonce) { + return true // txs in queue can't fill up the nonce gap + } + } + return false +} + +// enqueueTx inserts a new transaction into the non-executable transaction queue. +// +// Note, this method assumes the pool lock is held! +func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { + // Try to insert the transaction into the future queue + from, _ := types.Sender(pool.signer, tx) // already validated + if pool.queue[from] == nil { + pool.queue[from] = newList(false) + } + inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) + if !inserted { + // An older transaction was better, discard this + queuedDiscardMeter.Mark(1) + return false, txpool.ErrReplaceUnderpriced + } + // Discard any previous transaction and mark this + if old != nil { + pool.all.Remove(old.Hash()) + pool.priced.Removed(1) + queuedReplaceMeter.Mark(1) + } else { + // Nothing was replaced, bump the queued counter + queuedGauge.Inc(1) + } + // If the transaction isn't in lookup set but it's expected to be there, + // show the error log. + if pool.all.Get(hash) == nil && !addAll { + log.Error("Missing transaction in lookup set, please report the issue", "hash", hash) + } + if addAll { + pool.all.Add(tx, local) + pool.priced.Put(tx, local) + } + // If we never record the heartbeat, do it right now. + if _, exist := pool.beats[from]; !exist { + pool.beats[from] = time.Now() + } + return old != nil, nil +} + +// journalTx adds the specified transaction to the local disk journal if it is +// deemed to have been sent from a local account. +func (pool *LegacyPool) journalTx(from common.Address, tx *types.Transaction) { + // Only journal if it's enabled and the transaction is local + if pool.journal == nil || !pool.locals.contains(from) { + return + } + if err := pool.journal.insert(tx); err != nil { + log.Warn("Failed to journal local transaction", "err", err) + } +} + +// promoteTx adds a transaction to the pending (processable) list of transactions +// and returns whether it was inserted or an older was better. +// +// Note, this method assumes the pool lock is held! +func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool { + // Try to insert the transaction into the pending queue + if pool.pending[addr] == nil { + pool.pending[addr] = newList(true) + } + list := pool.pending[addr] + + inserted, old := list.Add(tx, pool.config.PriceBump) + if !inserted { + // An older transaction was better, discard this + pool.all.Remove(hash) + pool.priced.Removed(1) + pendingDiscardMeter.Mark(1) + return false + } + // Otherwise discard any previous transaction and mark this + if old != nil { + pool.all.Remove(old.Hash()) + pool.priced.Removed(1) + pendingReplaceMeter.Mark(1) + } else { + // Nothing was replaced, bump the pending counter + pendingGauge.Inc(1) + } + // Set the potentially new pending nonce and notify any subsystems of the new tx + pool.pendingNonces.set(addr, tx.Nonce()+1) + + // Successful promotion, bump the heartbeat + pool.beats[addr] = time.Now() + return true +} + +// addLocals enqueues a batch of transactions into the pool if they are valid, marking the +// senders as local ones, ensuring they go around the local pricing constraints. +// +// This method is used to add transactions from the RPC API and performs synchronous pool +// reorganization and event propagation. +func (pool *LegacyPool) addLocals(txs []*types.Transaction) []error { + return pool.Add(txs, !pool.config.NoLocals, true) +} + +// addLocal enqueues a single local transaction into the pool if it is valid. This is +// a convenience wrapper around addLocals. +func (pool *LegacyPool) addLocal(tx *types.Transaction) error { + return pool.addLocals([]*types.Transaction{tx})[0] +} + +// addRemotes enqueues a batch of transactions into the pool if they are valid. If the +// senders are not among the locally tracked ones, full pricing constraints will apply. +// +// This method is used to add transactions from the p2p network and does not wait for pool +// reorganization and internal event propagation. +func (pool *LegacyPool) addRemotes(txs []*types.Transaction) []error { + return pool.Add(txs, false, false) +} + +// addRemote enqueues a single transaction into the pool if it is valid. This is a convenience +// wrapper around addRemotes. +func (pool *LegacyPool) addRemote(tx *types.Transaction) error { + 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.Add(txs, false, true) +} + +// This is like addRemotes with a single transaction, but waits for pool reorganization. Tests use this method. +func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error { + return pool.Add([]*types.Transaction{tx}, false, true)[0] +} + +// Add enqueues a batch of transactions into the pool if they are valid. Depending +// on the local flag, full pricing constraints will or will not be applied. +// +// If sync is set, the method will block until all internal maintenance related +// to the add is finished. Only use this during tests for determinism! +func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error { + // 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)) + news = make([]*types.Transaction, 0, len(txs)) + ) + for i, tx := range txs { + // If the transaction is known, pre-set the error slot + if pool.all.Get(tx.Hash()) != nil { + errs[i] = txpool.ErrAlreadyKnown + knownTxMeter.Mark(1) + continue + } + // Exclude transactions with basic errors, e.g invalid signatures and + // insufficient intrinsic gas as soon as possible and cache senders + // in transactions before obtaining lock + if err := pool.validateTxBasics(tx, local); err != nil { + errs[i] = err + log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err) + invalidTxMeter.Mark(1) + continue + } + // Accumulate all unknown transactions for deeper processing + news = append(news, tx) + } + if len(news) == 0 { + return errs + } + + // Process all the new transaction and merge any errors into the original slice + pool.mu.Lock() + newErrs, dirtyAddrs := pool.addTxsLocked(news, local) + pool.mu.Unlock() + + var nilSlot = 0 + for _, err := range newErrs { + for errs[nilSlot] != nil { + nilSlot++ + } + errs[nilSlot] = err + nilSlot++ + } + // Reorg the pool internals if needed and return + done := pool.requestPromoteExecutables(dirtyAddrs) + if sync { + <-done + } + return errs +} + +// addTxsLocked attempts to queue a batch of transactions if they are valid. +// The transaction pool lock must be held. +func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) { + dirty := newAccountSet(pool.signer) + errs := make([]error, len(txs)) + for i, tx := range txs { + replaced, err := pool.add(tx, local) + errs[i] = err + if err == nil && !replaced { + dirty.addTx(tx) + } + } + validTxMeter.Mark(int64(len(dirty.accounts))) + return errs, dirty +} + +// Status returns the status (unknown/pending/queued) of a batch of transactions +// identified by their hashes. +func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus { + tx := pool.get(hash) + if tx == nil { + return txpool.TxStatusUnknown + } + from, _ := types.Sender(pool.signer, tx) // already validated + + pool.mu.RLock() + defer pool.mu.RUnlock() + + if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { + return txpool.TxStatusPending + } else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { + return txpool.TxStatusQueued + } + return txpool.TxStatusUnknown +} + +// Get returns a transaction if it is contained in the pool and nil otherwise. +func (pool *LegacyPool) Get(hash common.Hash) *types.Transaction { + tx := pool.get(hash) + if tx == nil { + return nil + } + return tx +} + +// get returns a transaction if it is contained in the pool and nil otherwise. +func (pool *LegacyPool) get(hash common.Hash) *types.Transaction { + return pool.all.Get(hash) +} + +// Has returns an indicator whether txpool has a transaction cached with the +// given hash. +func (pool *LegacyPool) Has(hash common.Hash) bool { + return pool.all.Get(hash) != nil +} + +// removeTx removes a single transaction from the queue, moving all subsequent +// transactions back to the future queue. +// +// In unreserve is false, the account will not be relinquished to the main txpool +// even if there are no more references to it. This is used to handle a race when +// a tx being added, and it evicts a previously scheduled tx from the same account, +// which could lead to a premature release of the lock. +// +// Returns the number of transactions removed from the pending queue. +func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bool) int { + // Fetch the transaction we wish to delete + tx := pool.all.Get(hash) + if tx == nil { + return 0 + } + addr, _ := types.Sender(pool.signer, tx) // already validated during insertion + + // If after deletion there are no more transactions belonging to this account, + // relinquish the address reservation. It's a bit convoluted do this, via a + // defer, but it's safer vs. the many return pathways. + if unreserve { + defer func() { + var ( + _, hasPending = pool.pending[addr] + _, hasQueued = pool.queue[addr] + ) + if !hasPending && !hasQueued { + pool.reserve(addr, false) + } + }() + } + // Remove it from the list of known transactions + pool.all.Remove(hash) + if outofbound { + pool.priced.Removed(1) + } + if pool.locals.contains(addr) { + localGauge.Dec(1) + } + // Remove the transaction from the pending lists and reset the account nonce + if pending := pool.pending[addr]; pending != nil { + if removed, invalids := pending.Remove(tx); removed { + // If no more pending transactions are left, remove the list + if pending.Empty() { + delete(pool.pending, addr) + } + // Postpone any invalidated transactions + for _, tx := range invalids { + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(tx.Hash(), tx, false, false) + } + // Update the account nonce if needed + pool.pendingNonces.setIfLower(addr, tx.Nonce()) + // Reduce the pending counter + pendingGauge.Dec(int64(1 + len(invalids))) + return 1 + len(invalids) + } + } + // Transaction is in the future queue + if future := pool.queue[addr]; future != nil { + if removed, _ := future.Remove(tx); removed { + // Reduce the queued counter + queuedGauge.Dec(1) + } + if future.Empty() { + delete(pool.queue, addr) + delete(pool.beats, addr) + } + } + return 0 +} + +// requestReset requests a pool reset to the new head block. +// The returned channel is closed when the reset has occurred. +func (pool *LegacyPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} { + select { + case pool.reqResetCh <- &txpoolResetRequest{oldHead, newHead}: + return <-pool.reorgDoneCh + case <-pool.reorgShutdownCh: + return pool.reorgShutdownCh + } +} + +// requestPromoteExecutables requests transaction promotion checks for the given addresses. +// The returned channel is closed when the promotion checks have occurred. +func (pool *LegacyPool) requestPromoteExecutables(set *accountSet) chan struct{} { + select { + case pool.reqPromoteCh <- set: + return <-pool.reorgDoneCh + case <-pool.reorgShutdownCh: + return pool.reorgShutdownCh + } +} + +// queueTxEvent enqueues a transaction event to be sent in the next reorg run. +func (pool *LegacyPool) queueTxEvent(tx *types.Transaction) { + select { + case pool.queueTxEventCh <- tx: + case <-pool.reorgShutdownCh: + } +} + +// scheduleReorgLoop schedules runs of reset and promoteExecutables. Code above should not +// call those methods directly, but request them being run using requestReset and +// requestPromoteExecutables instead. +func (pool *LegacyPool) scheduleReorgLoop() { + defer pool.wg.Done() + + var ( + curDone chan struct{} // non-nil while runReorg is active + nextDone = make(chan struct{}) + launchNextRun bool + reset *txpoolResetRequest + dirtyAccounts *accountSet + queuedEvents = make(map[common.Address]*sortedMap) + ) + for { + // Launch next background reorg if needed + if curDone == nil && launchNextRun { + // Run the background reorg and announcements + go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents) + + // Prepare everything for the next round of reorg + curDone, nextDone = nextDone, make(chan struct{}) + launchNextRun = false + + reset, dirtyAccounts = nil, nil + queuedEvents = make(map[common.Address]*sortedMap) + } + + select { + case req := <-pool.reqResetCh: + // Reset request: update head if request is already pending. + if reset == nil { + reset = req + } else { + reset.newHead = req.newHead + } + launchNextRun = true + pool.reorgDoneCh <- nextDone + + case req := <-pool.reqPromoteCh: + // Promote request: update address set if request is already pending. + if dirtyAccounts == nil { + dirtyAccounts = req + } else { + dirtyAccounts.merge(req) + } + launchNextRun = true + pool.reorgDoneCh <- nextDone + + case tx := <-pool.queueTxEventCh: + // Queue up the event, but don't schedule a reorg. It's up to the caller to + // request one later if they want the events sent. + addr, _ := types.Sender(pool.signer, tx) + if _, ok := queuedEvents[addr]; !ok { + queuedEvents[addr] = newSortedMap() + } + queuedEvents[addr].Put(tx) + + case <-curDone: + curDone = nil + + case <-pool.reorgShutdownCh: + // Wait for current run to finish. + if curDone != nil { + <-curDone + } + close(nextDone) + return + } + } +} + +// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop. +func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*sortedMap) { + defer func(t0 time.Time) { + reorgDurationTimer.Update(time.Since(t0)) + }(time.Now()) + defer close(done) + + var promoteAddrs []common.Address + if dirtyAccounts != nil && reset == nil { + // Only dirty accounts need to be promoted, unless we're resetting. + // For resets, all addresses in the tx queue will be promoted and + // the flatten operation can be avoided. + promoteAddrs = dirtyAccounts.flatten() + } + pool.mu.Lock() + if reset != nil { + // Reset from the old head to the new, rescheduling any reorged transactions + pool.reset(reset.oldHead, reset.newHead) + + // Nonces were reset, discard any events that became stale + for addr := range events { + events[addr].Forward(pool.pendingNonces.get(addr)) + if events[addr].Len() == 0 { + delete(events, addr) + } + } + // Reset needs promote for all addresses + promoteAddrs = make([]common.Address, 0, len(pool.queue)) + for addr := range pool.queue { + promoteAddrs = append(promoteAddrs, addr) + } + } + // Check for pending transactions for every account that sent new ones + promoted := pool.promoteExecutables(promoteAddrs) + + // If a new block appeared, validate the pool of pending transactions. This will + // remove any transaction that has been included in the block or was invalidated + // because of another transaction (e.g. higher gas price). + if reset != nil { + pool.demoteUnexecutables() + if reset.newHead != nil { + if pool.chainconfig.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) { + pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead) + pool.priced.SetBaseFee(pendingBaseFee) + } else { + pool.priced.Reheap() + } + } + // Update all accounts to the latest known pending nonce + nonces := make(map[common.Address]uint64, len(pool.pending)) + for addr, list := range pool.pending { + highestPending := list.LastElement() + nonces[addr] = highestPending.Nonce() + 1 + } + pool.pendingNonces.setAll(nonces) + } + // Ensure pool.queue and pool.pending sizes stay within the configured limits. + pool.truncatePending() + pool.truncateQueue() + + dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg)) + pool.changesSinceReorg = 0 // Reset change counter + pool.mu.Unlock() + + // Notify subsystems for newly added transactions + for _, tx := range promoted { + addr, _ := types.Sender(pool.signer, tx) + if _, ok := events[addr]; !ok { + events[addr] = newSortedMap() + } + events[addr].Put(tx) + } + if len(events) > 0 { + var txs []*types.Transaction + for _, set := range events { + txs = append(txs, set.Flatten()...) + } + pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) + } +} + +// reset retrieves the current state of the blockchain and ensures the content +// of the transaction pool is valid with regard to the chain state. +func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { + // If we're reorging an old state, reinject all dropped transactions + var reinject types.Transactions + + if oldHead != nil && oldHead.Hash() != newHead.ParentHash { + // If the reorg is too deep, avoid doing it (will happen during fast sync) + oldNum := oldHead.Number.Uint64() + newNum := newHead.Number.Uint64() + + if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 { + log.Debug("Skipping deep transaction reorg", "depth", depth) + } else { + // Reorg seems shallow enough to pull in all transactions into memory + var ( + rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64()) + add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64()) + ) + if rem == nil { + // This can happen if a setHead is performed, where we simply discard the old + // head from the chain. + // If that is the case, we don't have the lost transactions anymore, and + // there's nothing to add + if newNum >= oldNum { + // If we reorged to a same or higher number, then it's not a case of setHead + log.Warn("Transaction pool reset with missing old head", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + return + } + // If the reorg ended up on a lower number, it's indicative of setHead being the cause + log.Debug("Skipping transaction reset caused by setHead", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + // We still need to update the current state s.th. the lost transactions can be readded by the user + } else { + if add == nil { + // if the new head is nil, it means that something happened between + // the firing of newhead-event and _now_: most likely a + // reorg caused by sync-reversion or explicit sethead back to an + // earlier block. + log.Warn("Transaction pool reset with missing new head", "number", newHead.Number, "hash", newHead.Hash()) + return + } + var discarded, included types.Transactions + for rem.NumberU64() > add.NumberU64() { + discarded = append(discarded, rem.Transactions()...) + if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) + return + } + } + for add.NumberU64() > rem.NumberU64() { + included = append(included, add.Transactions()...) + if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) + return + } + } + for rem.Hash() != add.Hash() { + discarded = append(discarded, rem.Transactions()...) + if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) + return + } + included = append(included, add.Transactions()...) + if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) + return + } + } + lost := make([]*types.Transaction, 0, len(discarded)) + for _, tx := range types.TxDifference(discarded, included) { + if pool.Filter(tx) { + lost = append(lost, tx) + } + } + reinject = lost + } + } + } + // Initialize the internal state to the current head + if newHead == nil { + newHead = pool.chain.CurrentBlock() // Special case during testing + } + statedb, err := pool.chain.StateAt(newHead.Root) + if err != nil { + log.Error("Failed to reset txpool state", "err", err) + return + } + pool.currentHead.Store(newHead) + pool.currentState = statedb + pool.pendingNonces = newNoncer(statedb) + + // Inject any transactions discarded due to reorgs + log.Debug("Reinjecting stale transactions", "count", len(reinject)) + core.SenderCacher.Recover(pool.signer, reinject) + pool.addTxsLocked(reinject, false) +} + +// promoteExecutables moves transactions that have become processable from the +// future queue to the set of pending transactions. During this process, all +// invalidated transactions (low nonce, low balance) are deleted. +func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { + // Track the promoted transactions to broadcast them at once + var promoted []*types.Transaction + + // Iterate over all accounts and promote any executable transactions + gasLimit := pool.currentHead.Load().GasLimit + for _, addr := range accounts { + list := pool.queue[addr] + if list == nil { + continue // Just in case someone calls with a non existing account + } + // Drop all transactions that are deemed too old (low nonce) + forwards := list.Forward(pool.currentState.GetNonce(addr)) + for _, tx := range forwards { + hash := tx.Hash() + pool.all.Remove(hash) + } + log.Trace("Removed old queued transactions", "count", len(forwards)) + // Drop all transactions that are too costly (low balance or out of gas) + drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit) + for _, tx := range drops { + hash := tx.Hash() + pool.all.Remove(hash) + } + log.Trace("Removed unpayable queued transactions", "count", len(drops)) + queuedNofundsMeter.Mark(int64(len(drops))) + + // Gather all executable transactions and promote them + readies := list.Ready(pool.pendingNonces.get(addr)) + for _, tx := range readies { + hash := tx.Hash() + if pool.promoteTx(addr, hash, tx) { + promoted = append(promoted, tx) + } + } + log.Trace("Promoted queued transactions", "count", len(promoted)) + queuedGauge.Dec(int64(len(readies))) + + // Drop all transactions over the allowed limit + var caps types.Transactions + if !pool.locals.contains(addr) { + caps = list.Cap(int(pool.config.AccountQueue)) + for _, tx := range caps { + hash := tx.Hash() + pool.all.Remove(hash) + log.Trace("Removed cap-exceeding queued transaction", "hash", hash) + } + queuedRateLimitMeter.Mark(int64(len(caps))) + } + // Mark all the items dropped as removed + pool.priced.Removed(len(forwards) + len(drops) + len(caps)) + queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) + if pool.locals.contains(addr) { + localGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) + } + // Delete the entire queue entry if it became empty. + if list.Empty() { + delete(pool.queue, addr) + delete(pool.beats, addr) + if _, ok := pool.pending[addr]; !ok { + pool.reserve(addr, false) + } + } + } + return promoted +} + +// truncatePending removes transactions from the pending queue if the pool is above the +// pending limit. The algorithm tries to reduce transaction counts by an approximately +// equal number for all for accounts with many pending transactions. +func (pool *LegacyPool) truncatePending() { + pending := uint64(0) + for _, list := range pool.pending { + pending += uint64(list.Len()) + } + if pending <= pool.config.GlobalSlots { + return + } + + pendingBeforeCap := pending + // Assemble a spam order to penalize large transactors first + spammers := prque.New[int64, common.Address](nil) + for addr, list := range pool.pending { + // Only evict transactions from high rollers + if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots { + spammers.Push(addr, int64(list.Len())) + } + } + // Gradually drop transactions from offenders + offenders := []common.Address{} + for pending > pool.config.GlobalSlots && !spammers.Empty() { + // Retrieve the next offender if not local address + offender, _ := spammers.Pop() + offenders = append(offenders, offender) + + // Equalize balances until all the same or below threshold + if len(offenders) > 1 { + // Calculate the equalization threshold for all current offenders + threshold := pool.pending[offender].Len() + + // Iteratively reduce all offenders until below limit or threshold reached + for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold { + for i := 0; i < len(offenders)-1; i++ { + list := pool.pending[offenders[i]] + + caps := list.Cap(list.Len() - 1) + for _, tx := range caps { + // Drop the transaction from the global pools too + hash := tx.Hash() + pool.all.Remove(hash) + + // Update the account nonce to the dropped transaction + pool.pendingNonces.setIfLower(offenders[i], tx.Nonce()) + log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) + } + pool.priced.Removed(len(caps)) + pendingGauge.Dec(int64(len(caps))) + if pool.locals.contains(offenders[i]) { + localGauge.Dec(int64(len(caps))) + } + pending-- + } + } + } + } + + // If still above threshold, reduce to limit or min allowance + if pending > pool.config.GlobalSlots && len(offenders) > 0 { + for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots { + for _, addr := range offenders { + list := pool.pending[addr] + + caps := list.Cap(list.Len() - 1) + for _, tx := range caps { + // Drop the transaction from the global pools too + hash := tx.Hash() + pool.all.Remove(hash) + + // Update the account nonce to the dropped transaction + pool.pendingNonces.setIfLower(addr, tx.Nonce()) + log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) + } + pool.priced.Removed(len(caps)) + pendingGauge.Dec(int64(len(caps))) + if pool.locals.contains(addr) { + localGauge.Dec(int64(len(caps))) + } + pending-- + } + } + } + pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending)) +} + +// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit. +func (pool *LegacyPool) truncateQueue() { + queued := uint64(0) + for _, list := range pool.queue { + queued += uint64(list.Len()) + } + if queued <= pool.config.GlobalQueue { + return + } + + // Sort all accounts with queued transactions by heartbeat + addresses := make(addressesByHeartbeat, 0, len(pool.queue)) + for addr := range pool.queue { + if !pool.locals.contains(addr) { // don't drop locals + addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) + } + } + sort.Sort(sort.Reverse(addresses)) + + // Drop transactions until the total is below the limit or only locals remain + for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { + addr := addresses[len(addresses)-1] + list := pool.queue[addr.address] + + addresses = addresses[:len(addresses)-1] + + // Drop all transactions if they are less than the overflow + if size := uint64(list.Len()); size <= drop { + for _, tx := range list.Flatten() { + pool.removeTx(tx.Hash(), true, true) + } + drop -= size + queuedRateLimitMeter.Mark(int64(size)) + continue + } + // Otherwise drop only last few transactions + txs := list.Flatten() + for i := len(txs) - 1; i >= 0 && drop > 0; i-- { + pool.removeTx(txs[i].Hash(), true, true) + drop-- + queuedRateLimitMeter.Mark(1) + } + } +} + +// demoteUnexecutables removes invalid and processed transactions from the pools +// executable/pending queue and any subsequent transactions that become unexecutable +// are moved back into the future queue. +// +// Note: transactions are not marked as removed in the priced list because re-heaping +// is always explicitly triggered by SetBaseFee and it would be unnecessary and wasteful +// to trigger a re-heap is this function +func (pool *LegacyPool) demoteUnexecutables() { + // Iterate over all accounts and demote any non-executable transactions + gasLimit := pool.currentHead.Load().GasLimit + for addr, list := range pool.pending { + nonce := pool.currentState.GetNonce(addr) + + // Drop all transactions that are deemed too old (low nonce) + olds := list.Forward(nonce) + for _, tx := range olds { + hash := tx.Hash() + pool.all.Remove(hash) + log.Trace("Removed old pending transaction", "hash", hash) + } + // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later + drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit) + for _, tx := range drops { + hash := tx.Hash() + log.Trace("Removed unpayable pending transaction", "hash", hash) + pool.all.Remove(hash) + } + pendingNofundsMeter.Mark(int64(len(drops))) + + for _, tx := range invalids { + hash := tx.Hash() + log.Trace("Demoting pending transaction", "hash", hash) + + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(hash, tx, false, false) + } + pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) + if pool.locals.contains(addr) { + localGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) + } + // If there's a gap in front, alert (should never happen) and postpone all transactions + if list.Len() > 0 && list.txs.Get(nonce) == nil { + gapped := list.Cap(0) + for _, tx := range gapped { + hash := tx.Hash() + log.Error("Demoting invalidated transaction", "hash", hash) + + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(hash, tx, false, false) + } + pendingGauge.Dec(int64(len(gapped))) + } + // Delete the entire pending entry if it became empty. + if list.Empty() { + delete(pool.pending, addr) + if _, ok := pool.queue[addr]; !ok { + pool.reserve(addr, false) + } + } + } +} + +// addressByHeartbeat is an account address tagged with its last activity timestamp. +type addressByHeartbeat struct { + address common.Address + heartbeat time.Time +} + +type addressesByHeartbeat []addressByHeartbeat + +func (a addressesByHeartbeat) Len() int { return len(a) } +func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) } +func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// accountSet is simply a set of addresses to check for existence, and a signer +// capable of deriving addresses from transactions. +type accountSet struct { + accounts map[common.Address]struct{} + signer types.Signer + cache *[]common.Address +} + +// newAccountSet creates a new address set with an associated signer for sender +// derivations. +func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet { + as := &accountSet{ + accounts: make(map[common.Address]struct{}, len(addrs)), + signer: signer, + } + for _, addr := range addrs { + as.add(addr) + } + return as +} + +// contains checks if a given address is contained within the set. +func (as *accountSet) contains(addr common.Address) bool { + _, exist := as.accounts[addr] + return exist +} + +// containsTx checks if the sender of a given tx is within the set. If the sender +// cannot be derived, this method returns false. +func (as *accountSet) containsTx(tx *types.Transaction) bool { + if addr, err := types.Sender(as.signer, tx); err == nil { + return as.contains(addr) + } + return false +} + +// add inserts a new address into the set to track. +func (as *accountSet) add(addr common.Address) { + as.accounts[addr] = struct{}{} + as.cache = nil +} + +// addTx adds the sender of tx into the set. +func (as *accountSet) addTx(tx *types.Transaction) { + if addr, err := types.Sender(as.signer, tx); err == nil { + as.add(addr) + } +} + +// flatten returns the list of addresses within this set, also caching it for later +// reuse. The returned slice should not be changed! +func (as *accountSet) flatten() []common.Address { + if as.cache == nil { + accounts := make([]common.Address, 0, len(as.accounts)) + for account := range as.accounts { + accounts = append(accounts, account) + } + as.cache = &accounts + } + return *as.cache +} + +// merge adds all addresses from the 'other' set into 'as'. +func (as *accountSet) merge(other *accountSet) { + for addr := range other.accounts { + as.accounts[addr] = struct{}{} + } + as.cache = nil +} + +// lookup is used internally by LegacyPool to track transactions while allowing +// lookup without mutex contention. +// +// Note, although this type is properly protected against concurrent access, it +// is **not** a type that should ever be mutated or even exposed outside of the +// transaction pool, since its internal state is tightly coupled with the pools +// internal mechanisms. The sole purpose of the type is to permit out-of-bound +// peeking into the pool in LegacyPool.Get without having to acquire the widely scoped +// LegacyPool.mu mutex. +// +// This lookup set combines the notion of "local transactions", which is useful +// to build upper-level structure. +type lookup struct { + slots int + lock sync.RWMutex + locals map[common.Hash]*types.Transaction + remotes map[common.Hash]*types.Transaction +} + +// newLookup returns a new lookup structure. +func newLookup() *lookup { + return &lookup{ + locals: make(map[common.Hash]*types.Transaction), + remotes: make(map[common.Hash]*types.Transaction), + } +} + +// Range calls f on each key and value present in the map. The callback passed +// should return the indicator whether the iteration needs to be continued. +// Callers need to specify which set (or both) to be iterated. +func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) { + t.lock.RLock() + defer t.lock.RUnlock() + + if local { + for key, value := range t.locals { + if !f(key, value, true) { + return + } + } + } + if remote { + for key, value := range t.remotes { + if !f(key, value, false) { + return + } + } + } +} + +// Get returns a transaction if it exists in the lookup, or nil if not found. +func (t *lookup) Get(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + if tx := t.locals[hash]; tx != nil { + return tx + } + return t.remotes[hash] +} + +// GetLocal returns a transaction if it exists in the lookup, or nil if not found. +func (t *lookup) GetLocal(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.locals[hash] +} + +// GetRemote returns a transaction if it exists in the lookup, or nil if not found. +func (t *lookup) GetRemote(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.remotes[hash] +} + +// Count returns the current number of transactions in the lookup. +func (t *lookup) Count() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.locals) + len(t.remotes) +} + +// LocalCount returns the current number of local transactions in the lookup. +func (t *lookup) LocalCount() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.locals) +} + +// RemoteCount returns the current number of remote transactions in the lookup. +func (t *lookup) RemoteCount() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.remotes) +} + +// Slots returns the current number of slots used in the lookup. +func (t *lookup) Slots() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.slots +} + +// Add adds a transaction to the lookup. +func (t *lookup) Add(tx *types.Transaction, local bool) { + t.lock.Lock() + defer t.lock.Unlock() + + t.slots += numSlots(tx) + slotsGauge.Update(int64(t.slots)) + + if local { + t.locals[tx.Hash()] = tx + } else { + t.remotes[tx.Hash()] = tx + } +} + +// Remove removes a transaction from the lookup. +func (t *lookup) Remove(hash common.Hash) { + t.lock.Lock() + defer t.lock.Unlock() + + tx, ok := t.locals[hash] + if !ok { + tx, ok = t.remotes[hash] + } + if !ok { + log.Error("No transaction found to be deleted", "hash", hash) + return + } + t.slots -= numSlots(tx) + slotsGauge.Update(int64(t.slots)) + + delete(t.locals, hash) + delete(t.remotes, hash) +} + +// RemoteToLocals migrates the transactions belongs to the given locals to locals +// set. The assumption is held the locals set is thread-safe to be used. +func (t *lookup) RemoteToLocals(locals *accountSet) int { + t.lock.Lock() + defer t.lock.Unlock() + + var migrated int + for hash, tx := range t.remotes { + if locals.containsTx(tx) { + t.locals[hash] = tx + delete(t.remotes, hash) + migrated += 1 + } + } + return migrated +} + +// RemotesBelowTip finds all remote transactions below the given tip threshold. +func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions { + found := make(types.Transactions, 0, 128) + t.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { + if tx.GasTipCapIntCmp(threshold) < 0 { + found = append(found, tx) + } + return true + }, false, true) // Only iterate remotes + return found +} + +// numSlots calculates the number of slots needed for a single transaction. +func numSlots(tx *types.Transaction) int { + return int((tx.Size() + txSlotSize - 1) / txSlotSize) +} diff --git a/core/txpool/legacypool/legacypool2_test.go b/core/txpool/legacypool/legacypool2_test.go new file mode 100644 index 0000000..fd961d1 --- /dev/null +++ b/core/txpool/legacypool/legacypool2_test.go @@ -0,0 +1,242 @@ +// 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 legacypool + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "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/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" + "github.com/holiman/uint256" +) + +func pricedValuedTransaction(nonce uint64, value int64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(value), gaslimit, gasprice, nil), types.HomesteadSigner{}, key) + return tx +} + +func count(t *testing.T, pool *LegacyPool) (pending int, queued int) { + t.Helper() + pending, queued = pool.stats() + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + return pending, queued +} + +func fillPool(t testing.TB, pool *LegacyPool) { + t.Helper() + // Create a number of test accounts, fund them and make transactions + executableTxs := types.Transactions{} + nonExecutableTxs := types.Transactions{} + for i := 0; i < 384; i++ { + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(10000000000), tracing.BalanceChangeUnspecified) + // Add executable ones + for j := 0; j < int(pool.config.AccountSlots); j++ { + executableTxs = append(executableTxs, pricedTransaction(uint64(j), 100000, big.NewInt(300), key)) + } + } + // Import the batch and verify that limits have been enforced + pool.addRemotesSync(executableTxs) + pool.addRemotesSync(nonExecutableTxs) + pending, queued := pool.Stats() + slots := pool.all.Slots() + // sanity-check that the test prerequisites are ok (pending full) + if have, want := pending, slots; have != want { + t.Fatalf("have %d, want %d", have, want) + } + if have, want := queued, 0; have != want { + t.Fatalf("have %d, want %d", have, want) + } + + t.Logf("pool.config: GlobalSlots=%d, GlobalQueue=%d\n", pool.config.GlobalSlots, pool.config.GlobalQueue) + t.Logf("pending: %d queued: %d, all: %d\n", pending, queued, slots) +} + +// Tests that if a batch high-priced of non-executables arrive, they do not kick out +// executable transactions +func TestTransactionFutureAttack(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) + config := testTxPoolConfig + config.GlobalQueue = 100 + config.GlobalSlots = 100 + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + fillPool(t, pool) + pending, _ := pool.Stats() + // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops + { + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) + futureTxs := types.Transactions{} + for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { + futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key)) + } + for i := 0; i < 5; i++ { + pool.addRemotesSync(futureTxs) + newPending, newQueued := count(t, pool) + t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) + } + } + newPending, _ := pool.Stats() + // Pending should not have been touched + if have, want := newPending, pending; have < want { + t.Errorf("wrong pending-count, have %d, want %d (GlobalSlots: %d)", + have, want, pool.config.GlobalSlots) + } +} + +// Tests that if a batch high-priced of non-executables arrive, they do not kick out +// executable transactions +func TestTransactionFuture1559(t *testing.T) { + t.Parallel() + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create a number of test accounts, fund them and make transactions + fillPool(t, pool) + pending, _ := pool.Stats() + + // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops + { + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) + futureTxs := types.Transactions{} + for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { + futureTxs = append(futureTxs, dynamicFeeTx(1000+uint64(j), 100000, big.NewInt(200), big.NewInt(101), key)) + } + pool.addRemotesSync(futureTxs) + } + newPending, _ := pool.Stats() + // Pending should not have been touched + if have, want := newPending, pending; have != want { + t.Errorf("Wrong pending-count, have %d, want %d (GlobalSlots: %d)", + have, want, pool.config.GlobalSlots) + } +} + +// Tests that if a batch of balance-overdraft txs arrive, they do not kick out +// executable transactions +func TestTransactionZAttack(t *testing.T) { + t.Parallel() + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + // Create a number of test accounts, fund them and make transactions + fillPool(t, pool) + + countInvalidPending := func() int { + t.Helper() + var ivpendingNum int + pendingtxs, _ := pool.Content() + for account, txs := range pendingtxs { + cur_balance := new(big.Int).Set(pool.currentState.GetBalance(account).ToBig()) + for _, tx := range txs { + if cur_balance.Cmp(tx.Value()) <= 0 { + ivpendingNum++ + } else { + cur_balance.Sub(cur_balance, tx.Value()) + } + } + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + return ivpendingNum + } + ivPending := countInvalidPending() + t.Logf("invalid pending: %d\n", ivPending) + + // Now, DETER-Z attack starts, let's add a bunch of expensive non-executables (from N accounts) along with balance-overdraft txs (from one account), and see if the pending-count drops + for j := 0; j < int(pool.config.GlobalQueue); j++ { + futureTxs := types.Transactions{} + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) + futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 21000, big.NewInt(500), key)) + pool.addRemotesSync(futureTxs) + } + + overDraftTxs := types.Transactions{} + { + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) + for j := 0; j < int(pool.config.GlobalSlots); j++ { + overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key)) + } + } + pool.addRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) + + newPending, newQueued := count(t, pool) + newIvPending := countInvalidPending() + t.Logf("pool.all.Slots(): %d\n", pool.all.Slots()) + t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) + t.Logf("invalid pending: %d\n", newIvPending) + + // Pending should not have been touched + if newIvPending != ivPending { + t.Errorf("Wrong invalid pending-count, have %d, want %d (GlobalSlots: %d, queued: %d)", + newIvPending, ivPending, pool.config.GlobalSlots, newQueued) + } +} + +func BenchmarkFutureAttack(b *testing.B) { + // Create the pool to test the limit enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) + config := testTxPoolConfig + config.GlobalQueue = 100 + config.GlobalSlots = 100 + pool := New(config, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + fillPool(b, pool) + + key, _ := crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) + futureTxs := types.Transactions{} + + for n := 0; n < b.N; n++ { + futureTxs = append(futureTxs, pricedTransaction(1000+uint64(n), 100000, big.NewInt(500), key)) + } + b.ResetTimer() + for i := 0; i < 5; i++ { + pool.addRemotesSync(futureTxs) + } +} diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go new file mode 100644 index 0000000..c86991c --- /dev/null +++ b/core/txpool/legacypool/legacypool_test.go @@ -0,0 +1,2673 @@ +// 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 legacypool + +import ( + "crypto/ecdsa" + crand "crypto/rand" + "errors" + "fmt" + "math/big" + "math/rand" + "os" + "sync" + "sync/atomic" + "testing" + "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/tracing" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" +) + +var ( + // testTxPoolConfig is a transaction pool configuration without stateful disk + // sideeffects used during testing. + testTxPoolConfig Config + + // eip1559Config is a chain config with EIP-1559 enabled at block 0. + eip1559Config *params.ChainConfig +) + +func init() { + testTxPoolConfig = DefaultConfig + testTxPoolConfig.Journal = "" + + cpy := *params.TestChainConfig + eip1559Config = &cpy + eip1559Config.BerlinBlock = common.Big0 + eip1559Config.LondonBlock = common.Big0 +} + +type testBlockChain struct { + config *params.ChainConfig + gasLimit atomic.Uint64 + statedb *state.StateDB + chainHeadFeed *event.Feed +} + +func newTestBlockChain(config *params.ChainConfig, gasLimit uint64, statedb *state.StateDB, chainHeadFeed *event.Feed) *testBlockChain { + bc := testBlockChain{config: config, statedb: statedb, chainHeadFeed: new(event.Feed)} + bc.gasLimit.Store(gasLimit) + return &bc +} + +func (bc *testBlockChain) Config() *params.ChainConfig { + return bc.config +} + +func (bc *testBlockChain) CurrentBlock() *types.Header { + return &types.Header{ + Number: new(big.Int), + GasLimit: bc.gasLimit.Load(), + } +} + +func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { + return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil)) +} + +func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { + return bc.statedb, nil +} + +func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return bc.chainHeadFeed.Subscribe(ch) +} + +func transaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey) *types.Transaction { + return pricedTransaction(nonce, gaslimit, big.NewInt(1), key) +} + +func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, gasprice, nil), types.HomesteadSigner{}, key) + return tx +} + +func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey, bytes uint64) *types.Transaction { + data := make([]byte, bytes) + crand.Read(data) + + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data), types.HomesteadSigner{}, key) + return tx +} + +func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int, key *ecdsa.PrivateKey) *types.Transaction { + tx, _ := types.SignNewTx(key, types.LatestSignerForChainID(params.TestChainConfig.ChainID), &types.DynamicFeeTx{ + ChainID: params.TestChainConfig.ChainID, + Nonce: nonce, + GasTipCap: tip, + GasFeeCap: gasFee, + Gas: gaslimit, + To: &common.Address{}, + Value: big.NewInt(100), + Data: nil, + AccessList: nil, + }) + return tx +} + +func makeAddressReserver() txpool.AddressReserver { + var ( + reserved = make(map[common.Address]struct{}) + lock sync.Mutex + ) + return func(addr common.Address, reserve bool) error { + lock.Lock() + defer lock.Unlock() + + _, exists := reserved[addr] + if reserve { + if exists { + panic("already reserved") + } + reserved[addr] = struct{}{} + return nil + } + if !exists { + panic("not reserved") + } + delete(reserved, addr) + return nil + } +} + +func setupPool() (*LegacyPool, *ecdsa.PrivateKey) { + return setupPoolWithConfig(params.TestChainConfig) +} + +func setupPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.PrivateKey) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(config, 10000000, statedb, new(event.Feed)) + + key, _ := crypto.GenerateKey() + pool := New(testTxPoolConfig, blockchain) + if err := pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()); err != nil { + panic(err) + } + // wait for the pool to initialize + <-pool.initDoneCh + return pool, key +} + +// validatePoolInternals checks various consistency invariants within the pool. +func validatePoolInternals(pool *LegacyPool) error { + pool.mu.RLock() + defer pool.mu.RUnlock() + + // Ensure the total transaction set is consistent with pending + queued + pending, queued := pool.stats() + if total := pool.all.Count(); total != pending+queued { + return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued) + } + pool.priced.Reheap() + priced, remote := pool.priced.urgent.Len()+pool.priced.floating.Len(), pool.all.RemoteCount() + if priced != remote { + return fmt.Errorf("total priced transaction count %d != %d", priced, remote) + } + // Ensure the next nonce to assign is the correct one + for addr, txs := range pool.pending { + // Find the last transaction + var last uint64 + for nonce := range txs.txs.items { + if last < nonce { + last = nonce + } + } + if nonce := pool.pendingNonces.get(addr); nonce != last+1 { + return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1) + } + } + return nil +} + +// validateEvents checks that the correct number of transaction addition events +// were fired on the pool's event feed. +func validateEvents(events chan core.NewTxsEvent, count int) error { + var received []*types.Transaction + + for len(received) < count { + select { + case ev := <-events: + received = append(received, ev.Txs...) + case <-time.After(time.Second): + return fmt.Errorf("event #%d not fired", len(received)) + } + } + if len(received) > count { + return fmt.Errorf("more than %d events fired: %v", count, received[count:]) + } + select { + case ev := <-events: + return fmt.Errorf("more than %d events fired: %v", count, ev.Txs) + + case <-time.After(50 * time.Millisecond): + // This branch should be "default", but it's a data race between goroutines, + // reading the event channel and pushing into it, so better wait a bit ensuring + // really nothing gets injected. + } + return nil +} + +func deriveSender(tx *types.Transaction) (common.Address, error) { + return types.Sender(types.HomesteadSigner{}, tx) +} + +type testChain struct { + *testBlockChain + address common.Address + trigger *bool +} + +// testChain.State() is used multiple times to reset the pending state. +// when simulate is true it will create a state that indicates +// that tx0 and tx1 are included in the chain. +func (c *testChain) State() (*state.StateDB, error) { + // delay "state change" by one. The tx pool fetches the + // state multiple times and by delaying it a bit we simulate + // a state change between those fetches. + stdb := c.statedb + if *c.trigger { + c.statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + // simulate that the new head block included tx0 and tx1 + c.statedb.SetNonce(c.address, 2) + c.statedb.SetBalance(c.address, new(uint256.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified) + *c.trigger = false + } + return stdb, nil +} + +// This test simulates a scenario where a new block is imported during a +// state reset and tests whether the pending state is in sync with the +// block head event that initiated the resetState(). +func TestStateChangeDuringReset(t *testing.T) { + t.Parallel() + + var ( + key, _ = crypto.GenerateKey() + address = crypto.PubkeyToAddress(key.PublicKey) + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + trigger = false + ) + + // setup pool with 2 transaction in it + statedb.SetBalance(address, new(uint256.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified) + blockchain := &testChain{newTestBlockChain(params.TestChainConfig, 1000000000, statedb, new(event.Feed)), address, &trigger} + + tx0 := transaction(0, 100000, key) + tx1 := transaction(1, 100000, key) + + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + nonce := pool.Nonce(address) + if nonce != 0 { + t.Fatalf("Invalid nonce, want 0, got %d", nonce) + } + + pool.addRemotesSync([]*types.Transaction{tx0, tx1}) + + nonce = pool.Nonce(address) + if nonce != 2 { + t.Fatalf("Invalid nonce, want 2, got %d", nonce) + } + + // trigger state change in the background + trigger = true + <-pool.requestReset(nil, nil) + + nonce = pool.Nonce(address) + if nonce != 2 { + t.Fatalf("Invalid nonce, want 2, got %d", nonce) + } +} + +func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) { + pool.mu.Lock() + pool.currentState.AddBalance(addr, uint256.MustFromBig(amount), tracing.BalanceChangeUnspecified) + pool.mu.Unlock() +} + +func testSetNonce(pool *LegacyPool, addr common.Address, nonce uint64) { + pool.mu.Lock() + pool.currentState.SetNonce(addr, nonce) + pool.mu.Unlock() +} + +func TestInvalidTransactions(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Close() + + tx := transaction(0, 100, key) + from, _ := deriveSender(tx) + + // Intrinsic gas too low + testAddBalance(pool, from, big.NewInt(1)) + if err, want := pool.addRemote(tx), core.ErrIntrinsicGas; !errors.Is(err, want) { + t.Errorf("want %v have %v", want, err) + } + + // Insufficient funds + tx = transaction(0, 100000, key) + if err, want := pool.addRemote(tx), core.ErrInsufficientFunds; !errors.Is(err, want) { + t.Errorf("want %v have %v", want, err) + } + + testSetNonce(pool, from, 1) + testAddBalance(pool, from, big.NewInt(0xffffffffffffff)) + tx = transaction(0, 100000, key) + if err, want := pool.addRemote(tx), core.ErrNonceTooLow; !errors.Is(err, want) { + t.Errorf("want %v have %v", want, err) + } + + tx = transaction(1, 100000, key) + pool.gasTip.Store(uint256.NewInt(1000)) + if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) { + t.Errorf("want %v have %v", want, err) + } + if err := pool.addLocal(tx); err != nil { + t.Error("expected", nil, "got", err) + } +} + +func TestQueue(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Close() + + tx := transaction(0, 100, key) + from, _ := deriveSender(tx) + testAddBalance(pool, from, big.NewInt(1000)) + <-pool.requestReset(nil, nil) + + pool.enqueueTx(tx.Hash(), tx, false, true) + <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) + if len(pool.pending) != 1 { + t.Error("expected valid txs to be 1 is", len(pool.pending)) + } + + tx = transaction(1, 100, key) + from, _ = deriveSender(tx) + testSetNonce(pool, from, 2) + pool.enqueueTx(tx.Hash(), tx, false, true) + + <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) + if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { + t.Error("expected transaction to be in tx pool") + } + if len(pool.queue) > 0 { + t.Error("expected transaction queue to be empty. is", len(pool.queue)) + } +} + +func TestQueue2(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Close() + + tx1 := transaction(0, 100, key) + tx2 := transaction(10, 100, key) + tx3 := transaction(11, 100, key) + from, _ := deriveSender(tx1) + testAddBalance(pool, from, big.NewInt(1000)) + pool.reset(nil, nil) + + pool.enqueueTx(tx1.Hash(), tx1, false, true) + pool.enqueueTx(tx2.Hash(), tx2, false, true) + pool.enqueueTx(tx3.Hash(), tx3, false, true) + + pool.promoteExecutables([]common.Address{from}) + if len(pool.pending) != 1 { + t.Error("expected pending length to be 1, got", len(pool.pending)) + } + if pool.queue[from].Len() != 2 { + t.Error("expected len(queue) == 2, got", pool.queue[from].Len()) + } +} + +func TestNegativeValue(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Close() + + tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key) + from, _ := deriveSender(tx) + testAddBalance(pool, from, big.NewInt(1)) + if err := pool.addRemote(tx); err != txpool.ErrNegativeValue { + t.Error("expected", txpool.ErrNegativeValue, "got", err) + } +} + +func TestTipAboveFeeCap(t *testing.T) { + t.Parallel() + + pool, key := setupPoolWithConfig(eip1559Config) + defer pool.Close() + + tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key) + + if err := pool.addRemote(tx); err != core.ErrTipAboveFeeCap { + t.Error("expected", core.ErrTipAboveFeeCap, "got", err) + } +} + +func TestVeryHighValues(t *testing.T) { + t.Parallel() + + pool, key := setupPoolWithConfig(eip1559Config) + defer pool.Close() + + veryBigNumber := big.NewInt(1) + veryBigNumber.Lsh(veryBigNumber, 300) + + tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key) + if err := pool.addRemote(tx); err != core.ErrTipVeryHigh { + t.Error("expected", core.ErrTipVeryHigh, "got", err) + } + + tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key) + if err := pool.addRemote(tx2); err != core.ErrFeeCapVeryHigh { + t.Error("expected", core.ErrFeeCapVeryHigh, "got", err) + } +} + +func TestChainFork(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Close() + + addr := crypto.PubkeyToAddress(key.PublicKey) + resetState := func() { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.AddBalance(addr, uint256.NewInt(100000000000000), tracing.BalanceChangeUnspecified) + + pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed)) + <-pool.requestReset(nil, nil) + } + resetState() + + tx := transaction(0, 100000, key) + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } + pool.removeTx(tx.Hash(), true, true) + + // reset the pool's internal state + resetState() + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } +} + +func TestDoubleNonce(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Close() + + addr := crypto.PubkeyToAddress(key.PublicKey) + resetState := func() { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.AddBalance(addr, uint256.NewInt(100000000000000), tracing.BalanceChangeUnspecified) + + pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed)) + <-pool.requestReset(nil, nil) + } + resetState() + + signer := types.HomesteadSigner{} + tx1, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100000, big.NewInt(1), nil), signer, key) + tx2, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, big.NewInt(2), nil), signer, key) + tx3, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 1000000, big.NewInt(1), nil), signer, key) + + // Add the first two transaction, ensure higher priced stays only + if replace, err := pool.add(tx1, false); err != nil || replace { + t.Errorf("first transaction insert failed (%v) or reported replacement (%v)", err, replace) + } + if replace, err := pool.add(tx2, false); err != nil || !replace { + t.Errorf("second transaction insert failed (%v) or not reported replacement (%v)", err, replace) + } + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + + // Add the third transaction and ensure it's not saved (smaller price) + pool.add(tx3, false) + <-pool.requestPromoteExecutables(newAccountSet(signer, addr)) + if pool.pending[addr].Len() != 1 { + t.Error("expected 1 pending transactions, got", pool.pending[addr].Len()) + } + if tx := pool.pending[addr].txs.items[0]; tx.Hash() != tx2.Hash() { + t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash()) + } + // Ensure the total transaction count is correct + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } +} + +func TestMissingNonce(t *testing.T) { + t.Parallel() + + pool, key := setupPool() + defer pool.Close() + + addr := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, addr, big.NewInt(100000000000000)) + tx := transaction(1, 100000, key) + if _, err := pool.add(tx, false); err != nil { + t.Error("didn't expect error", err) + } + if len(pool.pending) != 0 { + t.Error("expected 0 pending transactions, got", len(pool.pending)) + } + if pool.queue[addr].Len() != 1 { + t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) + } + if pool.all.Count() != 1 { + t.Error("expected 1 total transactions, got", pool.all.Count()) + } +} + +func TestNonceRecovery(t *testing.T) { + t.Parallel() + + const n = 10 + pool, key := setupPool() + defer pool.Close() + + addr := crypto.PubkeyToAddress(key.PublicKey) + testSetNonce(pool, addr, n) + testAddBalance(pool, addr, big.NewInt(100000000000000)) + <-pool.requestReset(nil, nil) + + tx := transaction(n, 100000, key) + if err := pool.addRemote(tx); err != nil { + t.Error(err) + } + // simulate some weird re-order of transactions and missing nonce(s) + testSetNonce(pool, addr, n-1) + <-pool.requestReset(nil, nil) + if fn := pool.Nonce(addr); fn != n-1 { + t.Errorf("expected nonce to be %d, got %d", n-1, fn) + } +} + +// Tests that if an account runs out of funds, any pending and queued transactions +// are dropped. +func TestDropping(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupPool() + defer pool.Close() + + account := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, account, big.NewInt(1000)) + + // Add some pending and some queued transactions + var ( + tx0 = transaction(0, 100, key) + tx1 = transaction(1, 200, key) + tx2 = transaction(2, 300, key) + tx10 = transaction(10, 100, key) + tx11 = transaction(11, 200, key) + tx12 = transaction(12, 300, key) + ) + pool.all.Add(tx0, false) + pool.priced.Put(tx0, false) + pool.promoteTx(account, tx0.Hash(), tx0) + + pool.all.Add(tx1, false) + pool.priced.Put(tx1, false) + pool.promoteTx(account, tx1.Hash(), tx1) + + pool.all.Add(tx2, false) + pool.priced.Put(tx2, false) + pool.promoteTx(account, tx2.Hash(), tx2) + + pool.enqueueTx(tx10.Hash(), tx10, false, true) + pool.enqueueTx(tx11.Hash(), tx11, false, true) + pool.enqueueTx(tx12.Hash(), tx12, false, true) + + // Check that pre and post validations leave the pool as is + if pool.pending[account].Len() != 3 { + t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) + } + if pool.queue[account].Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + } + if pool.all.Count() != 6 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) + } + <-pool.requestReset(nil, nil) + if pool.pending[account].Len() != 3 { + t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) + } + if pool.queue[account].Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + } + if pool.all.Count() != 6 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) + } + // Reduce the balance of the account, and check that invalidated transactions are dropped + testAddBalance(pool, account, big.NewInt(-650)) + <-pool.requestReset(nil, nil) + + if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx0) + } + if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx0) + } + if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { + t.Errorf("out-of-fund pending transaction present: %v", tx1) + } + if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx10) + } + if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx10) + } + if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { + t.Errorf("out-of-fund queued transaction present: %v", tx11) + } + if pool.all.Count() != 4 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4) + } + // Reduce the block gas limit, check that invalidated transactions are dropped + pool.chain.(*testBlockChain).gasLimit.Store(100) + <-pool.requestReset(nil, nil) + + if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok { + t.Errorf("funded pending transaction missing: %v", tx0) + } + if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { + t.Errorf("over-gased pending transaction present: %v", tx1) + } + if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + t.Errorf("funded queued transaction missing: %v", tx10) + } + if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { + t.Errorf("over-gased queued transaction present: %v", tx11) + } + if pool.all.Count() != 2 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2) + } +} + +// Tests that if a transaction is dropped from the current pending pool (e.g. out +// of fund), all consecutive (still valid, but not executable) transactions are +// postponed back into the future queue to prevent broadcasting them. +func TestPostponing(t *testing.T) { + t.Parallel() + + // Create the pool to test the postponing with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create two test accounts to produce different gap profiles with + keys := make([]*ecdsa.PrivateKey, 2) + accs := make([]common.Address, len(keys)) + + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + accs[i] = crypto.PubkeyToAddress(keys[i].PublicKey) + + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(50100)) + } + // Add a batch consecutive pending transactions for validation + txs := []*types.Transaction{} + for i, key := range keys { + for j := 0; j < 100; j++ { + var tx *types.Transaction + if (i+j)%2 == 0 { + tx = transaction(uint64(j), 25000, key) + } else { + tx = transaction(uint64(j), 50000, key) + } + txs = append(txs, tx) + } + } + for i, err := range pool.addRemotesSync(txs) { + if err != nil { + t.Fatalf("tx %d: failed to add transactions: %v", i, err) + } + } + // Check that pre and post validations leave the pool as is + if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { + t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) + } + if len(pool.queue) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + } + if pool.all.Count() != len(txs) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) + } + <-pool.requestReset(nil, nil) + if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { + t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) + } + if len(pool.queue) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + } + if pool.all.Count() != len(txs) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) + } + // Reduce the balance of the account, and check that transactions are reorganised + for _, addr := range accs { + testAddBalance(pool, addr, big.NewInt(-1)) + } + <-pool.requestReset(nil, nil) + + // The first account's first transaction remains valid, check that subsequent + // ones are either filtered out, or queued up for later. + if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok { + t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0]) + } + if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok { + t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0]) + } + for i, tx := range txs[1:100] { + if i%2 == 1 { + if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) + } + if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok { + t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) + } + } else { + if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) + } + if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) + } + } + } + // The second account's first transaction got invalid, check that all transactions + // are either filtered out, or queued up for later. + if pool.pending[accs[1]] != nil { + t.Errorf("invalidated account still has pending transactions") + } + for i, tx := range txs[100:] { + if i%2 == 1 { + if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok { + t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx) + } + } else { + if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok { + t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx) + } + } + } + if pool.all.Count() != len(txs)/2 { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)/2) + } +} + +// Tests that if the transaction pool has both executable and non-executable +// transactions from an origin account, filling the nonce gap moves all queued +// ones into the pending pool. +func TestGapFilling(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupPool() + defer pool.Close() + + account := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, account, big.NewInt(1000000)) + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, testTxPoolConfig.AccountQueue+5) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a pending and a queued transaction with a nonce-gap in between + pool.addRemotesSync([]*types.Transaction{ + transaction(0, 100000, key), + transaction(2, 100000, key), + }) + pending, queued := pool.Stats() + if pending != 1 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Fill the nonce gap and ensure all transactions become pending + if err := pool.addRemoteSync(transaction(1, 100000, key)); err != nil { + t.Fatalf("failed to add gapped transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("gap-filling event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that if the transaction count belonging to a single account goes above +// some threshold, the higher transactions are dropped to prevent DOS attacks. +func TestQueueAccountLimiting(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupPool() + defer pool.Close() + + account := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, account, big.NewInt(1000000)) + + // Keep queuing up transactions and make sure all above a limit are dropped + for i := uint64(1); i <= testTxPoolConfig.AccountQueue+5; i++ { + if err := pool.addRemoteSync(transaction(i, 100000, key)); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + if len(pool.pending) != 0 { + t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) + } + if i <= testTxPoolConfig.AccountQueue { + if pool.queue[account].Len() != int(i) { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i) + } + } else { + if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) { + t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue) + } + } + } + if pool.all.Count() != int(testTxPoolConfig.AccountQueue) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue) + } +} + +// Tests that if the transaction count belonging to multiple accounts go above +// some threshold, the higher transactions are dropped to prevent DOS attacks. +// +// This logic should not hold for local transactions, unless the local tracking +// mechanism is disabled. +func TestQueueGlobalLimiting(t *testing.T) { + testQueueGlobalLimiting(t, false) +} +func TestQueueGlobalLimitingNoLocals(t *testing.T) { + testQueueGlobalLimiting(t, true) +} + +func testQueueGlobalLimiting(t *testing.T, nolocals bool) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + config := testTxPoolConfig + config.NoLocals = nolocals + config.GlobalQueue = config.AccountQueue*3 - 1 // reduce the queue limits to shorten test time (-1 to make it non divisible) + + pool := New(config, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create a number of test accounts and fund them (last one will be the local) + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + local := keys[len(keys)-1] + + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := make(types.Transactions, 0, 3*config.GlobalQueue) + for len(txs) < cap(txs) { + key := keys[rand.Intn(len(keys)-1)] // skip adding transactions with the local account + addr := crypto.PubkeyToAddress(key.PublicKey) + + txs = append(txs, transaction(nonces[addr]+1, 100000, key)) + nonces[addr]++ + } + // Import the batch and verify that limits have been enforced + pool.addRemotesSync(txs) + + queued := 0 + for addr, list := range pool.queue { + if list.Len() > int(config.AccountQueue) { + t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) + } + queued += list.Len() + } + if queued > int(config.GlobalQueue) { + t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) + } + // Generate a batch of transactions from the local account and import them + txs = txs[:0] + for i := uint64(0); i < 3*config.GlobalQueue; i++ { + txs = append(txs, transaction(i+1, 100000, local)) + } + pool.addLocals(txs) + + // If locals are disabled, the previous eviction algorithm should apply here too + if nolocals { + queued := 0 + for addr, list := range pool.queue { + if list.Len() > int(config.AccountQueue) { + t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) + } + queued += list.Len() + } + if queued > int(config.GlobalQueue) { + t.Fatalf("total transactions overflow allowance: %d > %d", queued, config.GlobalQueue) + } + } else { + // Local exemptions are enabled, make sure the local account owned the queue + if len(pool.queue) != 1 { + t.Errorf("multiple accounts in queue: have %v, want %v", len(pool.queue), 1) + } + // Also ensure no local transactions are ever dropped, even if above global limits + if queued := pool.queue[crypto.PubkeyToAddress(local.PublicKey)].Len(); uint64(queued) != 3*config.GlobalQueue { + t.Fatalf("local account queued transaction count mismatch: have %v, want %v", queued, 3*config.GlobalQueue) + } + } +} + +// Tests that if an account remains idle for a prolonged amount of time, any +// non-executable transactions queued up are dropped to prevent wasting resources +// on shuffling them around. +// +// This logic should not hold for local transactions, unless the local tracking +// mechanism is disabled. +func TestQueueTimeLimiting(t *testing.T) { + testQueueTimeLimiting(t, false) +} +func TestQueueTimeLimitingNoLocals(t *testing.T) { + testQueueTimeLimiting(t, true) +} + +func testQueueTimeLimiting(t *testing.T, nolocals bool) { + // Reduce the eviction interval to a testable amount + defer func(old time.Duration) { evictionInterval = old }(evictionInterval) + evictionInterval = time.Millisecond * 100 + + // Create the pool to test the non-expiration enforcement + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + config := testTxPoolConfig + config.Lifetime = time.Second + config.NoLocals = nolocals + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create two test accounts to ensure remotes expire but locals do not + local, _ := crypto.GenerateKey() + remote, _ := crypto.GenerateKey() + + testAddBalance(pool, crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000)) + testAddBalance(pool, crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000)) + + // Add the two transactions and ensure they both are queued up + if err := pool.addLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), remote)); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + pending, queued := pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // Allow the eviction interval to run + time.Sleep(2 * evictionInterval) + + // Transactions should not be evicted from the queue yet since lifetime duration has not passed + pending, queued = pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // Wait a bit for eviction to run and clean up any leftovers, and ensure only the local remains + time.Sleep(2 * config.Lifetime) + + pending, queued = pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if nolocals { + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + } else { + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // remove current transactions and increase nonce to prepare for a reset and cleanup + statedb.SetNonce(crypto.PubkeyToAddress(remote.PublicKey), 2) + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2) + <-pool.requestReset(nil, nil) + + // make sure queue, pending are cleared + pending, queued = pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // Queue gapped transactions + if err := pool.addLocal(pricedTransaction(4, 100000, big.NewInt(1), local)); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + if err := pool.addRemoteSync(pricedTransaction(4, 100000, big.NewInt(1), remote)); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + time.Sleep(5 * evictionInterval) // A half lifetime pass + + // Queue executable transactions, the life cycle should be restarted. + if err := pool.addLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), remote)); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + time.Sleep(6 * evictionInterval) + + // All gapped transactions shouldn't be kicked out + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // The whole life time pass after last promotion, kick out stale transactions + time.Sleep(2 * config.Lifetime) + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if nolocals { + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + } else { + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that even if the transaction count belonging to a single account goes +// above some threshold, as long as the transactions are executable, they are +// accepted. +func TestPendingLimiting(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupPool() + defer pool.Close() + + account := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, account, big.NewInt(1000000000000)) + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, testTxPoolConfig.AccountQueue+5) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Keep queuing up transactions and make sure all above a limit are dropped + for i := uint64(0); i < testTxPoolConfig.AccountQueue+5; i++ { + if err := pool.addRemoteSync(transaction(i, 100000, key)); err != nil { + t.Fatalf("tx %d: failed to add transaction: %v", i, err) + } + if pool.pending[account].Len() != int(i)+1 { + t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1) + } + if len(pool.queue) != 0 { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) + } + } + if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { + t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5) + } + if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil { + t.Fatalf("event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that if the transaction count belonging to multiple accounts go above +// some hard threshold, the higher transactions are dropped to prevent DOS +// attacks. +func TestPendingGlobalLimiting(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + config := testTxPoolConfig + config.GlobalSlots = config.AccountSlots * 10 + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := types.Transactions{} + for _, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + for j := 0; j < int(config.GlobalSlots)/len(keys)*2; j++ { + txs = append(txs, transaction(nonces[addr], 100000, key)) + nonces[addr]++ + } + } + // Import the batch and verify that limits have been enforced + pool.addRemotesSync(txs) + + pending := 0 + for _, list := range pool.pending { + pending += list.Len() + } + if pending > int(config.GlobalSlots) { + t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, config.GlobalSlots) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Test the limit on transaction size is enforced correctly. +// This test verifies every transaction having allowed size +// is added to the pool, and longer transactions are rejected. +func TestAllowedTxSize(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupPool() + defer pool.Close() + + account := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, account, big.NewInt(1000000000)) + + // Compute maximal data size for transactions (lower bound). + // + // It is assumed the fields in the transaction (except of the data) are: + // - nonce <= 32 bytes + // - gasTip <= 32 bytes + // - gasLimit <= 32 bytes + // - recipient == 20 bytes + // - value <= 32 bytes + // - signature == 65 bytes + // All those fields are summed up to at most 213 bytes. + baseSize := uint64(213) + dataSize := txMaxSize - baseSize + // Try adding a transaction with maximal allowed size + tx := pricedDataTransaction(0, pool.currentHead.Load().GasLimit, big.NewInt(1), key, dataSize) + if err := pool.addRemoteSync(tx); err != nil { + t.Fatalf("failed to add transaction of size %d, close to maximal: %v", int(tx.Size()), err) + } + // Try adding a transaction with random allowed size + if err := pool.addRemoteSync(pricedDataTransaction(1, pool.currentHead.Load().GasLimit, big.NewInt(1), key, uint64(rand.Intn(int(dataSize))))); err != nil { + t.Fatalf("failed to add transaction of random allowed size: %v", err) + } + // Try adding a transaction of minimal not allowed size + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentHead.Load().GasLimit, big.NewInt(1), key, txMaxSize)); err == nil { + t.Fatalf("expected rejection on slightly oversize transaction") + } + // Try adding a transaction of random not allowed size + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentHead.Load().GasLimit, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(10*txMaxSize)))); err == nil { + t.Fatalf("expected rejection on oversize transaction") + } + // Run some sanity checks on the pool internals + pending, queued := pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that if transactions start being capped, transactions are also removed from 'all' +func TestCapClearsFromAll(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + config := testTxPoolConfig + config.AccountSlots = 2 + config.AccountQueue = 2 + config.GlobalSlots = 8 + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create a number of test accounts and fund them + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, addr, big.NewInt(1000000)) + + txs := types.Transactions{} + for j := 0; j < int(config.GlobalSlots)*2; j++ { + txs = append(txs, transaction(uint64(j), 100000, key)) + } + // Import the batch and verify that limits have been enforced + pool.addRemotes(txs) + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that if the transaction count belonging to multiple accounts go above +// some hard threshold, if they are under the minimum guaranteed slot count then +// the transactions are still kept. +func TestPendingMinimumAllowance(t *testing.T) { + t.Parallel() + + // Create the pool to test the limit enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + config := testTxPoolConfig + config.GlobalSlots = 1 + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions + nonces := make(map[common.Address]uint64) + + txs := types.Transactions{} + for _, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + for j := 0; j < int(config.AccountSlots)*2; j++ { + txs = append(txs, transaction(nonces[addr], 100000, key)) + nonces[addr]++ + } + } + // Import the batch and verify that limits have been enforced + pool.addRemotesSync(txs) + + for addr, list := range pool.pending { + if list.Len() != int(config.AccountSlots) { + t.Errorf("addr %x: total pending transactions mismatch: have %d, want %d", addr, list.Len(), config.AccountSlots) + } + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that setting the transaction pool gas price to a higher value correctly +// discards everything cheaper than that and moves any gapped transactions back +// from the pending pool to the queue. +// +// Note, local transactions are never allowed to be dropped. +func TestRepricing(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(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 4) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(2), keys[0])) + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[0])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[0])) + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[1])) + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[1])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[1])) + + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[2])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[2])) + txs = append(txs, pricedTransaction(3, 100000, big.NewInt(2), keys[2])) + + ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[3]) + + // Import the batch and that both pending and queued transactions match up + pool.addRemotesSync(txs) + pool.addLocal(ltx) + + pending, queued := pool.Stats() + if pending != 7 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7) + } + if queued != 3 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) + } + if err := validateEvents(events, 7); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Reprice the pool and check that underpriced transactions get dropped + pool.SetGasTip(big.NewInt(2)) + + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 5 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5) + } + if err := validateEvents(events, 0); err != nil { + t.Fatalf("reprice event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Check that we can't add the old transactions back + if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + } + if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + } + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + } + if err := validateEvents(events, 0); err != nil { + t.Fatalf("post-reprice event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // However we can add local underpriced transactions + tx := pricedTransaction(1, 100000, big.NewInt(1), keys[3]) + if err := pool.addLocal(tx); err != nil { + t.Fatalf("failed to add underpriced local transaction: %v", err) + } + if pending, _ = pool.Stats(); pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("post-reprice local event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // And we can fill gaps with properly priced transactions + if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(2), keys[0])); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), keys[1])); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(2), keys[2])); err != nil { + t.Fatalf("failed to add queued transaction: %v", err) + } + if err := validateEvents(events, 5); err != nil { + t.Fatalf("post-reprice event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +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(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. +// +// Note, local transactions are never allowed to be dropped. +func TestRepricingDynamicFee(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + pool, _ := setupPoolWithConfig(eip1559Config) + defer pool.Close() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 4) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(2), keys[0])) + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[0])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[0])) + + txs = append(txs, dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])) + txs = append(txs, dynamicFeeTx(1, 100000, big.NewInt(3), big.NewInt(2), keys[1])) + txs = append(txs, dynamicFeeTx(2, 100000, big.NewInt(3), big.NewInt(2), keys[1])) + + txs = append(txs, dynamicFeeTx(1, 100000, big.NewInt(2), big.NewInt(2), keys[2])) + txs = append(txs, dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])) + txs = append(txs, dynamicFeeTx(3, 100000, big.NewInt(2), big.NewInt(2), keys[2])) + + ltx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[3]) + + // Import the batch and that both pending and queued transactions match up + pool.addRemotesSync(txs) + pool.addLocal(ltx) + + pending, queued := pool.Stats() + if pending != 7 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7) + } + if queued != 3 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) + } + if err := validateEvents(events, 7); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Reprice the pool and check that underpriced transactions get dropped + pool.SetGasTip(big.NewInt(2)) + + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 5 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5) + } + if err := validateEvents(events, 0); err != nil { + t.Fatalf("reprice event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Check that we can't add the old transactions back + tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0]) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + } + tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + } + tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2]) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + } + if err := validateEvents(events, 0); err != nil { + t.Fatalf("post-reprice event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // However we can add local underpriced transactions + tx = dynamicFeeTx(1, 100000, big.NewInt(1), big.NewInt(1), keys[3]) + if err := pool.addLocal(tx); err != nil { + t.Fatalf("failed to add underpriced local transaction: %v", err) + } + if pending, _ = pool.Stats(); pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("post-reprice local event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // And we can fill gaps with properly priced transactions + tx = pricedTransaction(1, 100000, big.NewInt(2), keys[0]) + if err := pool.addRemote(tx); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) + if err := pool.addRemote(tx); err != nil { + t.Fatalf("failed to add pending transaction: %v", err) + } + tx = dynamicFeeTx(2, 100000, big.NewInt(2), big.NewInt(2), keys[2]) + if err := pool.addRemoteSync(tx); err != nil { + t.Fatalf("failed to add queued transaction: %v", err) + } + if err := validateEvents(events, 5); err != nil { + t.Fatalf("post-reprice event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that setting the transaction pool gas price to a higher value does not +// remove local transactions (legacy & dynamic fee). +func TestRepricingKeepsLocals(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, 1000000, statedb, new(event.Feed)) + + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 3) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(100000*1000000)) + } + // Create transaction (both pending and queued) with a linearly growing gasprice + for i := uint64(0); i < 500; i++ { + // Add pending transaction. + pendingTx := pricedTransaction(i, 100000, big.NewInt(int64(i)), keys[2]) + if err := pool.addLocal(pendingTx); err != nil { + t.Fatal(err) + } + // Add queued transaction. + queuedTx := pricedTransaction(i+501, 100000, big.NewInt(int64(i)), keys[2]) + if err := pool.addLocal(queuedTx); err != nil { + t.Fatal(err) + } + + // Add pending dynamic fee transaction. + pendingTx = dynamicFeeTx(i, 100000, big.NewInt(int64(i)+1), big.NewInt(int64(i)), keys[1]) + if err := pool.addLocal(pendingTx); err != nil { + t.Fatal(err) + } + // Add queued dynamic fee transaction. + queuedTx = dynamicFeeTx(i+501, 100000, big.NewInt(int64(i)+1), big.NewInt(int64(i)), keys[1]) + if err := pool.addLocal(queuedTx); err != nil { + t.Fatal(err) + } + } + pending, queued := pool.Stats() + expPending, expQueued := 1000, 1000 + validate := func() { + pending, queued = pool.Stats() + if pending != expPending { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, expPending) + } + if queued != expQueued { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, expQueued) + } + + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + } + validate() + + // Reprice the pool and check that nothing is dropped + pool.SetGasTip(big.NewInt(2)) + validate() + + pool.SetGasTip(big.NewInt(2)) + pool.SetGasTip(big.NewInt(4)) + pool.SetGasTip(big.NewInt(8)) + pool.SetGasTip(big.NewInt(100)) + validate() +} + +// Tests that when the pool reaches its global transaction limit, underpriced +// transactions are gradually shifted out for more expensive ones and any gapped +// pending transactions are moved into the queue. +// +// Note, local transactions are never allowed to be dropped. +func TestUnderpricing(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(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + config := testTxPoolConfig + config.GlobalSlots = 2 + config.GlobalQueue = 2 + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[0])) + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[0])) + + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[1])) + + ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[2]) + + // Import the batch and that both pending and queued transactions match up + pool.addRemotes(txs) + pool.addLocal(ltx) + + pending, queued := pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 3); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding an underpriced transaction on block limit fails + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + } + // Replace a future transaction with a future transaction + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(2), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1 + t.Fatalf("failed to add well priced transaction: %v", err) + } + // Ensure that adding high priced transactions drops cheap ones, but not own + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - + t.Fatalf("failed to add well priced transaction: %v", err) + } + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2 + t.Fatalf("failed to add well priced transaction: %v", err) + } + if err := pool.addRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3 + t.Fatalf("failed to add well priced transaction: %v", err) + } + // Ensure that replacing a pending transaction with a future transaction fails + if err := pool.addRemote(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != txpool.ErrFutureReplacePending { + t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending) + } + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding local transactions can push out even higher priced ones + ltx = pricedTransaction(1, 100000, big.NewInt(0), keys[2]) + if err := pool.addLocal(ltx); err != nil { + t.Fatalf("failed to append underpriced local transaction: %v", err) + } + ltx = pricedTransaction(0, 100000, big.NewInt(0), keys[3]) + if err := pool.addLocal(ltx); err != nil { + t.Fatalf("failed to add new underpriced local transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("local event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that more expensive transactions push out cheap ones from the pool, but +// without producing instability by creating gaps that start jumping transactions +// back and forth between queued/pending. +func TestStableUnderpricing(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(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + config := testTxPoolConfig + config.GlobalSlots = 128 + config.GlobalQueue = 0 + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 2) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Fill up the entire queue with the same transaction price points + txs := types.Transactions{} + for i := uint64(0); i < config.GlobalSlots; i++ { + txs = append(txs, pricedTransaction(i, 100000, big.NewInt(1), keys[0])) + } + pool.addRemotesSync(txs) + + pending, queued := pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, int(config.GlobalSlots)); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { + t.Fatalf("failed to add well priced transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that when the pool reaches its global transaction limit, underpriced +// transactions (legacy & dynamic fee) are gradually shifted out for more +// expensive ones and any gapped pending transactions are moved into the queue. +// +// Note, local transactions are never allowed to be dropped. +func TestUnderpricingDynamicFee(t *testing.T) { + t.Parallel() + + pool, _ := setupPoolWithConfig(eip1559Config) + defer pool.Close() + + pool.config.GlobalSlots = 2 + pool.config.GlobalQueue = 2 + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 4) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[0])) + txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[0])) + txs = append(txs, dynamicFeeTx(1, 100000, big.NewInt(2), big.NewInt(1), keys[1])) + + ltx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[2]) + + // Import the batch and that both pending and queued transactions match up + pool.addRemotes(txs) // Pend K0:0, K0:1; Que K1:1 + pool.addLocal(ltx) // +K2:0 => Pend K0:0, K0:1, K2:0; Que K1:1 + + pending, queued := pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 3); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + + // Ensure that adding an underpriced transaction fails + tx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { // Pend K0:0, K0:1, K2:0; Que K1:1 + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) + } + + // Ensure that adding high priced transactions drops cheap ones, but not own + tx = pricedTransaction(0, 100000, big.NewInt(2), keys[1]) + if err := pool.addRemote(tx); err != nil { // +K1:0, -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - + t.Fatalf("failed to add well priced transaction: %v", err) + } + + tx = pricedTransaction(1, 100000, big.NewInt(3), keys[1]) + if err := pool.addRemoteSync(tx); err != nil { // +K1:2, -K0:1 => Pend K0:0 K1:0, K2:0; Que K1:2 + t.Fatalf("failed to add well priced transaction: %v", err) + } + tx = dynamicFeeTx(2, 100000, big.NewInt(4), big.NewInt(1), keys[1]) + if err := pool.addRemoteSync(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3 + t.Fatalf("failed to add well priced transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding local transactions can push out even higher priced ones + ltx = dynamicFeeTx(1, 100000, big.NewInt(0), big.NewInt(0), keys[2]) + if err := pool.addLocal(ltx); err != nil { + t.Fatalf("failed to append underpriced local transaction: %v", err) + } + ltx = dynamicFeeTx(0, 100000, big.NewInt(0), big.NewInt(0), keys[3]) + if err := pool.addLocal(ltx); err != nil { + t.Fatalf("failed to add new underpriced local transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("local event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests whether highest fee cap transaction is retained after a batch of high effective +// tip transactions are added and vice versa +func TestDualHeapEviction(t *testing.T) { + t.Parallel() + + pool, _ := setupPoolWithConfig(eip1559Config) + defer pool.Close() + + pool.config.GlobalSlots = 10 + pool.config.GlobalQueue = 10 + + var ( + highTip, highCap *types.Transaction + baseFee int + ) + + check := func(tx *types.Transaction, name string) { + if pool.all.GetRemote(tx.Hash()) == nil { + t.Fatalf("highest %s transaction evicted from the pool", name) + } + } + + add := func(urgent bool) { + for i := 0; i < 20; i++ { + var tx *types.Transaction + // Create a test accounts and fund it + key, _ := crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000000)) + if urgent { + tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+1+i)), big.NewInt(int64(1+i)), key) + highTip = tx + } else { + tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) + highCap = tx + } + pool.addRemotesSync([]*types.Transaction{tx}) + } + pending, queued := pool.Stats() + if pending+queued != 20 { + t.Fatalf("transaction count mismatch: have %d, want %d", pending+queued, 10) + } + } + + add(false) + for baseFee = 0; baseFee <= 1000; baseFee += 100 { + pool.priced.SetBaseFee(big.NewInt(int64(baseFee))) + add(true) + check(highCap, "fee cap") + add(false) + check(highTip, "effective tip") + } + + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that the pool rejects duplicate transactions. +func TestDeduplication(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(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create a test account to add transactions with + key, _ := crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) + + // Create a batch of transactions and add a few of them + txs := make([]*types.Transaction, 16) + for i := 0; i < len(txs); i++ { + txs[i] = pricedTransaction(uint64(i), 100000, big.NewInt(1), key) + } + var firsts []*types.Transaction + for i := 0; i < len(txs); i += 2 { + firsts = append(firsts, txs[i]) + } + errs := pool.addRemotesSync(firsts) + if len(errs) != len(firsts) { + t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts)) + } + for i, err := range errs { + if err != nil { + t.Errorf("add %d failed: %v", i, err) + } + } + pending, queued := pool.Stats() + if pending != 1 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1) + } + if queued != len(txs)/2-1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1) + } + // Try to add all of them now and ensure previous ones error out as knowns + errs = pool.addRemotesSync(txs) + if len(errs) != len(txs) { + t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), len(txs)) + } + for i, err := range errs { + if i%2 == 0 && err == nil { + t.Errorf("add %d succeeded, should have failed as known", i) + } + if i%2 == 1 && err != nil { + t.Errorf("add %d failed: %v", i, err) + } + } + pending, queued = pool.Stats() + if pending != len(txs) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, len(txs)) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that the pool rejects replacement transactions that don't meet the minimum +// price bump required. +func TestReplacement(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(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a test account to add transactions with + key, _ := crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) + + // Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) + price := int64(100) + threshold := (price * (100 + int64(testTxPoolConfig.PriceBump))) / 100 + + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil { + t.Fatalf("failed to add original cheap pending transaction: %v", err) + } + if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced) + } + if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil { + t.Fatalf("failed to replace original cheap pending transaction: %v", err) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("cheap replacement event firing failed: %v", err) + } + + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil { + t.Fatalf("failed to add original proper pending transaction: %v", err) + } + if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced) + } + if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil { + t.Fatalf("failed to replace original proper pending transaction: %v", err) + } + if err := validateEvents(events, 2); err != nil { + t.Fatalf("proper replacement event firing failed: %v", err) + } + + // Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil { + t.Fatalf("failed to add original cheap queued transaction: %v", err) + } + if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced) + } + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil { + t.Fatalf("failed to replace original cheap queued transaction: %v", err) + } + + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil { + t.Fatalf("failed to add original proper queued transaction: %v", err) + } + if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced) + } + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil { + t.Fatalf("failed to replace original proper queued transaction: %v", err) + } + + if err := validateEvents(events, 0); err != nil { + t.Fatalf("queued replacement event firing failed: %v", err) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that the pool rejects replacement dynamic fee transactions that don't +// meet the minimum price bump required. +func TestReplacementDynamicFee(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + pool, key := setupPoolWithConfig(eip1559Config) + defer pool.Close() + testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) + + // Keep track of transaction events to ensure all executables get announced + events := make(chan core.NewTxsEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) + gasFeeCap := int64(100) + feeCapThreshold := (gasFeeCap * (100 + int64(testTxPoolConfig.PriceBump))) / 100 + gasTipCap := int64(60) + tipThreshold := (gasTipCap * (100 + int64(testTxPoolConfig.PriceBump))) / 100 + + // Run the following identical checks for both the pending and queue pools: + // 1. Send initial tx => accept + // 2. Don't bump tip or fee cap => discard + // 3. Bump both more than min => accept + // 4. Check events match expected (2 new executable txs during pending, 0 during queue) + // 5. Send new tx with larger tip and gasFeeCap => accept + // 6. Bump tip max allowed so it's still underpriced => discard + // 7. Bump fee cap max allowed so it's still underpriced => discard + // 8. Bump tip min for acceptance => discard + // 9. Bump feecap min for acceptance => discard + // 10. Bump feecap and tip min for acceptance => accept + // 11. Check events match expected (2 new executable txs during pending, 0 during queue) + stages := []string{"pending", "queued"} + for _, stage := range stages { + // Since state is empty, 0 nonce txs are "executable" and can go + // into pending immediately. 2 nonce txs are "gapped" + nonce := uint64(0) + if stage == "queued" { + nonce = 2 + } + + // 1. Send initial tx => accept + tx := dynamicFeeTx(nonce, 100000, big.NewInt(2), big.NewInt(1), key) + if err := pool.addRemoteSync(tx); err != nil { + t.Fatalf("failed to add original cheap %s transaction: %v", stage, err) + } + // 2. Don't bump tip or feecap => discard + tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) + } + // 3. Bump both more than min => accept + tx = dynamicFeeTx(nonce, 100000, big.NewInt(3), big.NewInt(2), key) + if err := pool.addRemote(tx); err != nil { + t.Fatalf("failed to replace original cheap %s transaction: %v", stage, err) + } + // 4. Check events match expected (2 new executable txs during pending, 0 during queue) + count := 2 + if stage == "queued" { + count = 0 + } + if err := validateEvents(events, count); err != nil { + t.Fatalf("cheap %s replacement event firing failed: %v", stage, err) + } + // 5. Send new tx with larger tip and feeCap => accept + tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(gasTipCap), key) + if err := pool.addRemoteSync(tx); err != nil { + t.Fatalf("failed to add original proper %s transaction: %v", stage, err) + } + // 6. Bump tip max allowed so it's still underpriced => discard + tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold-1), key) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) + } + // 7. Bump fee cap max allowed so it's still underpriced => discard + tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) + } + // 8. Bump tip min for acceptance => accept + tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold), key) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) + } + // 9. Bump fee cap min for acceptance => accept + tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) + } + // 10. Check events match expected (3 new executable txs during pending, 0 during queue) + tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(tipThreshold), key) + if err := pool.addRemote(tx); err != nil { + t.Fatalf("failed to replace original cheap %s transaction: %v", stage, err) + } + // 11. Check events match expected (3 new executable txs during pending, 0 during queue) + count = 2 + if stage == "queued" { + count = 0 + } + if err := validateEvents(events, count); err != nil { + t.Fatalf("replacement %s event firing failed: %v", stage, err) + } + } + + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + +// Tests that local transactions are journaled to disk, but remote transactions +// get discarded between restarts. +func TestJournaling(t *testing.T) { testJournaling(t, false) } +func TestJournalingNoLocals(t *testing.T) { testJournaling(t, true) } + +func testJournaling(t *testing.T, nolocals bool) { + t.Parallel() + + // Create a temporary file for the journal + file, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("failed to create temporary journal: %v", err) + } + journal := file.Name() + defer os.Remove(journal) + + // Clean up the temporary file, we only need the path for now + file.Close() + os.Remove(journal) + + // Create the original pool to inject transaction into the journal + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + config := testTxPoolConfig + config.NoLocals = nolocals + config.Journal = journal + config.Rejournal = time.Second + + pool := New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + + // Create two test accounts to ensure remotes expire but locals do not + local, _ := crypto.GenerateKey() + remote, _ := crypto.GenerateKey() + + testAddBalance(pool, crypto.PubkeyToAddress(local.PublicKey), big.NewInt(1000000000)) + testAddBalance(pool, crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000)) + + // Add three local and a remote transactions and ensure they are queued up + if err := pool.addLocal(pricedTransaction(0, 100000, big.NewInt(1), local)); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.addLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.addLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { + t.Fatalf("failed to add local transaction: %v", err) + } + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), remote)); err != nil { + t.Fatalf("failed to add remote transaction: %v", err) + } + pending, queued := pool.Stats() + if pending != 4 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive + pool.Close() + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) + blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + pool = New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + + pending, queued = pool.Stats() + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if nolocals { + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + } else { + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Bump the nonce temporarily and ensure the newly invalidated transaction is removed + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2) + <-pool.requestReset(nil, nil) + time.Sleep(2 * config.Rejournal) + pool.Close() + + statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) + blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + pool = New(config, blockchain) + pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + + pending, queued = pool.Stats() + if pending != 0 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0) + } + if nolocals { + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + } else { + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + pool.Close() +} + +// TestStatusCheck tests that the pool can correctly retrieve the +// pending status of individual transactions. +func TestStatusCheck(t *testing.T) { + t.Parallel() + + // Create the pool to test the status retrievals with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + + pool := New(testTxPoolConfig, blockchain) + pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + // Create the test accounts to check various transaction statuses with + keys := make([]*ecdsa.PrivateKey, 3) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Generate and queue a batch of transactions, both pending and queued + txs := types.Transactions{} + + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[0])) // Pending only + txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[1])) // Pending and queued + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[1])) + txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[2])) // Queued only + + // Import the transaction and ensure they are correctly added + pool.addRemotesSync(txs) + + pending, queued := pool.Stats() + if pending != 2 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + } + if queued != 2 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + } + if err := validatePoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Retrieve the status of each transaction and validate them + hashes := make([]common.Hash, len(txs)) + for i, tx := range txs { + hashes[i] = tx.Hash() + } + hashes = append(hashes, common.Hash{}) + expect := []txpool.TxStatus{txpool.TxStatusPending, txpool.TxStatusPending, txpool.TxStatusQueued, txpool.TxStatusQueued, txpool.TxStatusUnknown} + + for i := 0; i < len(hashes); i++ { + if status := pool.Status(hashes[i]); status != expect[i] { + t.Errorf("transaction %d: status mismatch: have %v, want %v", i, status, expect[i]) + } + } +} + +// Test the transaction slots consumption is computed correctly +func TestSlotCount(t *testing.T) { + t.Parallel() + + key, _ := crypto.GenerateKey() + + // Check that an empty transaction consumes a single slot + smallTx := pricedDataTransaction(0, 0, big.NewInt(0), key, 0) + if slots := numSlots(smallTx); slots != 1 { + t.Fatalf("small transactions slot count mismatch: have %d want %d", slots, 1) + } + // Check that a large transaction consumes the correct number of slots + bigTx := pricedDataTransaction(0, 0, big.NewInt(0), key, uint64(10*txSlotSize)) + if slots := numSlots(bigTx); slots != 11 { + t.Fatalf("big transactions slot count mismatch: have %d want %d", slots, 11) + } +} + +// Benchmarks the speed of validating the contents of the pending queue of the +// transaction pool. +func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) } +func BenchmarkPendingDemotion1000(b *testing.B) { benchmarkPendingDemotion(b, 1000) } +func BenchmarkPendingDemotion10000(b *testing.B) { benchmarkPendingDemotion(b, 10000) } + +func benchmarkPendingDemotion(b *testing.B, size int) { + // Add a batch of transactions to a pool one by one + pool, key := setupPool() + defer pool.Close() + + account := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, account, big.NewInt(1000000)) + + for i := 0; i < size; i++ { + tx := transaction(uint64(i), 100000, key) + pool.promoteTx(account, tx.Hash(), tx) + } + // Benchmark the speed of pool validation + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.demoteUnexecutables() + } +} + +// Benchmarks the speed of scheduling the contents of the future queue of the +// transaction pool. +func BenchmarkFuturePromotion100(b *testing.B) { benchmarkFuturePromotion(b, 100) } +func BenchmarkFuturePromotion1000(b *testing.B) { benchmarkFuturePromotion(b, 1000) } +func BenchmarkFuturePromotion10000(b *testing.B) { benchmarkFuturePromotion(b, 10000) } + +func benchmarkFuturePromotion(b *testing.B, size int) { + // Add a batch of transactions to a pool one by one + pool, key := setupPool() + defer pool.Close() + + account := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, account, big.NewInt(1000000)) + + for i := 0; i < size; i++ { + tx := transaction(uint64(1+i), 100000, key) + pool.enqueueTx(tx.Hash(), tx, false, true) + } + // Benchmark the speed of pool validation + b.ResetTimer() + for i := 0; i < b.N; i++ { + pool.promoteExecutables(nil) + } +} + +// Benchmarks the speed of batched transaction insertion. +func BenchmarkBatchInsert100(b *testing.B) { benchmarkBatchInsert(b, 100, false) } +func BenchmarkBatchInsert1000(b *testing.B) { benchmarkBatchInsert(b, 1000, false) } +func BenchmarkBatchInsert10000(b *testing.B) { benchmarkBatchInsert(b, 10000, false) } + +func BenchmarkBatchLocalInsert100(b *testing.B) { benchmarkBatchInsert(b, 100, true) } +func BenchmarkBatchLocalInsert1000(b *testing.B) { benchmarkBatchInsert(b, 1000, true) } +func BenchmarkBatchLocalInsert10000(b *testing.B) { benchmarkBatchInsert(b, 10000, true) } + +func benchmarkBatchInsert(b *testing.B, size int, local bool) { + // Generate a batch of transactions to enqueue into the pool + pool, key := setupPool() + defer pool.Close() + + account := crypto.PubkeyToAddress(key.PublicKey) + testAddBalance(pool, account, big.NewInt(1000000000000000000)) + + batches := make([]types.Transactions, b.N) + for i := 0; i < b.N; i++ { + batches[i] = make(types.Transactions, size) + for j := 0; j < size; j++ { + batches[i][j] = transaction(uint64(size*i+j), 100000, key) + } + } + // Benchmark importing the transactions into the queue + b.ResetTimer() + for _, batch := range batches { + if local { + pool.addLocals(batch) + } else { + pool.addRemotes(batch) + } + } +} + +func BenchmarkInsertRemoteWithAllLocals(b *testing.B) { + // Allocate keys for testing + key, _ := crypto.GenerateKey() + account := crypto.PubkeyToAddress(key.PublicKey) + + remoteKey, _ := crypto.GenerateKey() + remoteAddr := crypto.PubkeyToAddress(remoteKey.PublicKey) + + locals := make([]*types.Transaction, 4096+1024) // Occupy all slots + for i := 0; i < len(locals); i++ { + locals[i] = transaction(uint64(i), 100000, key) + } + remotes := make([]*types.Transaction, 1000) + for i := 0; i < len(remotes); i++ { + remotes[i] = pricedTransaction(uint64(i), 100000, big.NewInt(2), remoteKey) // Higher gasprice + } + // Benchmark importing the transactions into the queue + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + pool, _ := setupPool() + testAddBalance(pool, account, big.NewInt(100000000)) + for _, local := range locals { + pool.addLocal(local) + } + b.StartTimer() + // Assign a high enough balance for testing + testAddBalance(pool, remoteAddr, big.NewInt(100000000)) + for i := 0; i < len(remotes); i++ { + pool.addRemotes([]*types.Transaction{remotes[i]}) + } + pool.Close() + } +} + +// Benchmarks the speed of batch transaction insertion in case of multiple accounts. +func BenchmarkMultiAccountBatchInsert(b *testing.B) { + // Generate a batch of transactions to enqueue into the pool + pool, _ := setupPool() + defer pool.Close() + b.ReportAllocs() + batches := make(types.Transactions, b.N) + for i := 0; i < b.N; i++ { + key, _ := crypto.GenerateKey() + account := crypto.PubkeyToAddress(key.PublicKey) + pool.currentState.AddBalance(account, uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) + tx := transaction(uint64(0), 100000, key) + batches[i] = tx + } + // Benchmark importing the transactions into the queue + b.ResetTimer() + for _, tx := range batches { + pool.addRemotesSync([]*types.Transaction{tx}) + } +} diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go new file mode 100644 index 0000000..b749db4 --- /dev/null +++ b/core/txpool/legacypool/list.go @@ -0,0 +1,687 @@ +// 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 legacypool + +import ( + "container/heap" + "math" + "math/big" + "slices" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +// nonceHeap is a heap.Interface implementation over 64bit unsigned integers for +// retrieving sorted transactions from the possibly gapped future queue. +type nonceHeap []uint64 + +func (h nonceHeap) Len() int { return len(h) } +func (h nonceHeap) Less(i, j int) bool { return h[i] < h[j] } +func (h nonceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +func (h *nonceHeap) Push(x interface{}) { + *h = append(*h, x.(uint64)) +} + +func (h *nonceHeap) Pop() interface{} { + old := *h + n := len(old) + x := old[n-1] + old[n-1] = 0 + *h = old[0 : n-1] + return x +} + +// 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 + cacheMu sync.Mutex // Mutex covering the cache +} + +// newSortedMap creates a new nonce-sorted transaction map. +func newSortedMap() *sortedMap { + return &sortedMap{ + items: make(map[uint64]*types.Transaction), + index: new(nonceHeap), + } +} + +// Get retrieves the current transactions associated with the given nonce. +func (m *sortedMap) Get(nonce uint64) *types.Transaction { + return m.items[nonce] +} + +// Put inserts a new transaction into the map, also updating the map's nonce +// index. If a transaction already exists with the same nonce, it's overwritten. +func (m *sortedMap) Put(tx *types.Transaction) { + nonce := tx.Nonce() + 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 +// provided threshold. Every removed transaction is returned for any post-removal +// maintenance. +func (m *sortedMap) Forward(threshold uint64) types.Transactions { + var removed types.Transactions + + // Pop off heap items until the threshold is reached + for m.index.Len() > 0 && (*m.index)[0] < threshold { + nonce := heap.Pop(m.index).(uint64) + removed = append(removed, m.items[nonce]) + 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 +} + +// Filter iterates over the list of transactions and removes all of them for which +// the specified function evaluates to true. +// Filter, as opposed to 'filter', re-initialises the heap after the operation is done. +// If you want to do several consecutive filterings, it's therefore better to first +// do a .filter(func1) followed by .Filter(func2) or reheap() +func (m *sortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions { + removed := m.filter(filter) + // If transactions were removed, the heap and cache are ruined + if len(removed) > 0 { + m.reheap() + } + return removed +} + +func (m *sortedMap) reheap() { + *m.index = make([]uint64, 0, len(m.items)) + for nonce := range m.items { + *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 +// should only be used if followed immediately by a call to Filter or reheap() +func (m *sortedMap) filter(filter func(*types.Transaction) bool) types.Transactions { + var removed types.Transactions + + // Collect all the transactions to filter out + for nonce, tx := range m.items { + if filter(tx) { + removed = append(removed, tx) + delete(m.items, nonce) + } + } + if len(removed) > 0 { + m.cacheMu.Lock() + m.cache = nil + m.cacheMu.Unlock() + } + return removed +} + +// Cap places a hard limit on the number of items, returning all transactions +// exceeding that limit. +func (m *sortedMap) Cap(threshold int) types.Transactions { + // Short circuit if the number of items is under the limit + if len(m.items) <= threshold { + return nil + } + // Otherwise gather and drop the highest nonce'd transactions + var drops types.Transactions + slices.Sort(*m.index) + for size := len(m.items); size > threshold; size-- { + drops = append(drops, m.items[(*m.index)[size-1]]) + delete(m.items, (*m.index)[size-1]) + } + *m.index = (*m.index)[:threshold] + // The sorted m.index slice is still a valid heap, so there is no need to + // reheap after deleting tail items. + + // 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 +} + +// Remove deletes a transaction from the maintained map, returning whether the +// transaction was found. +func (m *sortedMap) Remove(nonce uint64) bool { + // Short circuit if no transaction is present + _, ok := m.items[nonce] + if !ok { + return false + } + // Otherwise delete the transaction and fix the heap index + for i := 0; i < m.index.Len(); i++ { + if (*m.index)[i] == nonce { + heap.Remove(m.index, i) + break + } + } + delete(m.items, nonce) + m.cacheMu.Lock() + m.cache = nil + m.cacheMu.Unlock() + + return true +} + +// Ready retrieves a sequentially increasing list of transactions starting at the +// provided nonce that is ready for processing. The returned transactions will be +// removed from the list. +// +// Note, all transactions with nonces lower than start will also be returned to +// 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 + if m.index.Len() == 0 || (*m.index)[0] > start { + return nil + } + // Otherwise start accumulating incremental transactions + var ready types.Transactions + for next := (*m.index)[0]; m.index.Len() > 0 && (*m.index)[0] == next; next++ { + ready = append(ready, m.items[next]) + delete(m.items, next) + heap.Pop(m.index) + } + m.cacheMu.Lock() + m.cache = nil + m.cacheMu.Unlock() + + return ready +} + +// Len returns the length of the transaction map. +func (m *sortedMap) Len() int { + return len(m.items) +} + +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)) + for _, tx := range m.items { + m.cache = append(m.cache, tx) + } + sort.Sort(types.TxByNonce(m.cache)) + } + return m.cache +} + +// Flatten creates a nonce-sorted slice of transactions based on the loosely +// 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 { + cache := m.flatten() + // Copy the cache to prevent accidental modification + txs := make(types.Transactions, len(cache)) + copy(txs, cache) + return txs +} + +// LastElement returns the last element of a flattened list, thus, the +// transaction with the highest nonce +func (m *sortedMap) LastElement() *types.Transaction { + cache := m.flatten() + return cache[len(cache)-1] +} + +// list is a "list" of transactions belonging to an account, sorted by account +// nonce. The same type can be used both for storing contiguous transactions for +// the executable/pending queue; and for storing gapped transactions for the non- +// executable/future queue, with minor behavioral changes. +type list struct { + strict bool // Whether nonces are strictly continuous or not + txs *sortedMap // Heap indexed sorted hash map of the transactions + + costcap *uint256.Int // Price of the highest costing transaction (reset only if exceeds balance) + gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit) + totalcost *uint256.Int // Total cost of all transactions in the list +} + +// newList creates a new transaction list for maintaining nonce-indexable fast, +// gapped, sortable transaction lists. +func newList(strict bool) *list { + return &list{ + strict: strict, + txs: newSortedMap(), + costcap: new(uint256.Int), + totalcost: new(uint256.Int), + } +} + +// Contains returns whether the list contains a transaction +// with the provided nonce. +func (l *list) Contains(nonce uint64) bool { + return l.txs.Get(nonce) != nil +} + +// Add tries to insert a new transaction into the list, returning whether the +// transaction was accepted, and if yes, any previous transaction it replaced. +// +// If the new transaction is accepted into the list, the lists' cost and gas +// thresholds are also potentially updated. +func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { + // If there's an older better transaction, abort + old := l.txs.Get(tx.Nonce()) + if old != nil { + if old.GasFeeCapCmp(tx) >= 0 || old.GasTipCapCmp(tx) >= 0 { + return false, nil + } + // thresholdFeeCap = oldFC * (100 + priceBump) / 100 + a := big.NewInt(100 + int64(priceBump)) + aFeeCap := new(big.Int).Mul(a, old.GasFeeCap()) + aTip := a.Mul(a, old.GasTipCap()) + + // thresholdTip = oldTip * (100 + priceBump) / 100 + b := big.NewInt(100) + thresholdFeeCap := aFeeCap.Div(aFeeCap, b) + thresholdTip := aTip.Div(aTip, b) + + // We have to ensure that both the new fee cap and tip are higher than the + // old ones as well as checking the percentage threshold to ensure that + // this is accurate for low (Wei-level) gas price replacements. + if tx.GasFeeCapIntCmp(thresholdFeeCap) < 0 || tx.GasTipCapIntCmp(thresholdTip) < 0 { + return false, nil + } + // Old is being replaced, subtract old cost + l.subTotalCost([]*types.Transaction{old}) + } + // Add new tx cost to totalcost + cost, overflow := uint256.FromBig(tx.Cost()) + if overflow { + return false, nil + } + l.totalcost.Add(l.totalcost, cost) + + // Otherwise overwrite the old transaction with the current one + l.txs.Put(tx) + if l.costcap.Cmp(cost) < 0 { + l.costcap = cost + } + if gas := tx.Gas(); l.gascap < gas { + l.gascap = gas + } + return true, old +} + +// Forward removes all transactions from the list with a nonce lower than the +// provided threshold. Every removed transaction is returned for any post-removal +// maintenance. +func (l *list) Forward(threshold uint64) types.Transactions { + txs := l.txs.Forward(threshold) + l.subTotalCost(txs) + return txs +} + +// Filter removes all transactions from the list with a cost or gas limit higher +// than the provided thresholds. Every removed transaction is returned for any +// post-removal maintenance. Strict-mode invalidated transactions are also +// returned. +// +// This method uses the cached costcap and gascap to quickly decide if there's even +// a point in calculating all the costs or if the balance covers all. If the threshold +// is lower than the costgas cap, the caps will be reset to a new high after removing +// the newly invalidated transactions. +func (l *list) Filter(costLimit *uint256.Int, gasLimit uint64) (types.Transactions, types.Transactions) { + // If all transactions are below the threshold, short circuit + if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit { + return nil, nil + } + l.costcap = new(uint256.Int).Set(costLimit) // Lower the caps to the thresholds + l.gascap = gasLimit + + // Filter out all the transactions above the account's funds + removed := l.txs.Filter(func(tx *types.Transaction) bool { + return tx.Gas() > gasLimit || tx.Cost().Cmp(costLimit.ToBig()) > 0 + }) + + if len(removed) == 0 { + return nil, nil + } + var invalids types.Transactions + // If the list was strict, filter anything above the lowest nonce + if l.strict { + lowest := uint64(math.MaxUint64) + for _, tx := range removed { + if nonce := tx.Nonce(); lowest > nonce { + lowest = nonce + } + } + invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest }) + } + // Reset total cost + l.subTotalCost(removed) + l.subTotalCost(invalids) + l.txs.reheap() + return removed, invalids +} + +// Cap places a hard limit on the number of items, returning all transactions +// exceeding that limit. +func (l *list) Cap(threshold int) types.Transactions { + txs := l.txs.Cap(threshold) + l.subTotalCost(txs) + return txs +} + +// Remove deletes a transaction from the maintained list, returning whether the +// transaction was found, and also returning any transaction invalidated due to +// the deletion (strict mode only). +func (l *list) Remove(tx *types.Transaction) (bool, types.Transactions) { + // Remove the transaction from the set + nonce := tx.Nonce() + if removed := l.txs.Remove(nonce); !removed { + return false, nil + } + l.subTotalCost([]*types.Transaction{tx}) + // In strict mode, filter out non-executable transactions + if l.strict { + txs := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > nonce }) + l.subTotalCost(txs) + return true, txs + } + return true, nil +} + +// Ready retrieves a sequentially increasing list of transactions starting at the +// provided nonce that is ready for processing. The returned transactions will be +// removed from the list. +// +// Note, all transactions with nonces lower than start will also be returned to +// 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) + l.subTotalCost(txs) + return txs +} + +// Len returns the length of the transaction list. +func (l *list) Len() int { + return l.txs.Len() +} + +// Empty returns whether the list of transactions is empty or not. +func (l *list) Empty() bool { + return l.Len() == 0 +} + +// Flatten creates a nonce-sorted slice of transactions based on the loosely +// 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 (l *list) Flatten() types.Transactions { + return l.txs.Flatten() +} + +// LastElement returns the last element of a flattened list, thus, the +// transaction with the highest nonce +func (l *list) LastElement() *types.Transaction { + return l.txs.LastElement() +} + +// subTotalCost subtracts the cost of the given transactions from the +// total cost of all transactions. +func (l *list) subTotalCost(txs []*types.Transaction) { + for _, tx := range txs { + _, underflow := l.totalcost.SubOverflow(l.totalcost, uint256.MustFromBig(tx.Cost())) + if underflow { + panic("totalcost underflow") + } + } +} + +// priceHeap is a heap.Interface implementation over transactions for retrieving +// price-sorted transactions to discard when the pool fills up. If baseFee is set +// then the heap is sorted based on the effective tip based on the given base fee. +// If baseFee is nil then the sorting is based on gasFeeCap. +type priceHeap struct { + baseFee *big.Int // heap should always be re-sorted after baseFee is changed + list []*types.Transaction +} + +func (h *priceHeap) Len() int { return len(h.list) } +func (h *priceHeap) Swap(i, j int) { h.list[i], h.list[j] = h.list[j], h.list[i] } + +func (h *priceHeap) Less(i, j int) bool { + switch h.cmp(h.list[i], h.list[j]) { + case -1: + return true + case 1: + return false + default: + return h.list[i].Nonce() > h.list[j].Nonce() + } +} + +func (h *priceHeap) cmp(a, b *types.Transaction) int { + if h.baseFee != nil { + // Compare effective tips if baseFee is specified + if c := a.EffectiveGasTipCmp(b, h.baseFee); c != 0 { + return c + } + } + // Compare fee caps if baseFee is not specified or effective tips are equal + if c := a.GasFeeCapCmp(b); c != 0 { + return c + } + // Compare tips if effective tips and fee caps are equal + return a.GasTipCapCmp(b) +} + +func (h *priceHeap) Push(x interface{}) { + tx := x.(*types.Transaction) + h.list = append(h.list, tx) +} + +func (h *priceHeap) Pop() interface{} { + old := h.list + n := len(old) + x := old[n-1] + old[n-1] = nil + h.list = old[0 : n-1] + return x +} + +// pricedList is a price-sorted heap to allow operating on transactions pool +// contents in a price-incrementing way. It's built upon the all transactions +// in txpool but only interested in the remote part. It means only remote transactions +// will be considered for tracking, sorting, eviction, etc. +// +// Two heaps are used for sorting: the urgent heap (based on effective tip in the next +// block) and the floating heap (based on gasFeeCap). Always the bigger heap is chosen for +// eviction. Transactions evicted from the urgent heap are first demoted into the floating heap. +// In some cases (during a congestion, when blocks are full) the urgent heap can provide +// better candidates for inclusion while in other cases (at the top of the baseFee peak) +// the floating heap is better. When baseFee is decreasing they behave similarly. +type pricedList struct { + // Number of stale price points to (re-heap trigger). + stales atomic.Int64 + + all *lookup // Pointer to the map of all transactions + urgent, floating priceHeap // Heaps of prices of all the stored **remote** transactions + reheapMu sync.Mutex // Mutex asserts that only one routine is reheaping the list +} + +const ( + // urgentRatio : floatingRatio is the capacity ratio of the two queues + urgentRatio = 4 + floatingRatio = 1 +) + +// newPricedList creates a new price-sorted transaction heap. +func newPricedList(all *lookup) *pricedList { + return &pricedList{ + all: all, + } +} + +// Put inserts a new transaction into the heap. +func (l *pricedList) Put(tx *types.Transaction, local bool) { + if local { + return + } + // Insert every new transaction to the urgent heap first; Discard will balance the heaps + heap.Push(&l.urgent, tx) +} + +// Removed notifies the prices transaction list that an old transaction dropped +// from the pool. The list will just keep a counter of stale objects and update +// the heap if a large enough ratio of transactions go stale. +func (l *pricedList) Removed(count int) { + // Bump the stale counter, but exit if still too low (< 25%) + stales := l.stales.Add(int64(count)) + if int(stales) <= (len(l.urgent.list)+len(l.floating.list))/4 { + return + } + // Seems we've reached a critical number of stale transactions, reheap + l.Reheap() +} + +// Underpriced checks whether a transaction is cheaper than (or as cheap as) the +// lowest priced (remote) transaction currently being tracked. +func (l *pricedList) Underpriced(tx *types.Transaction) bool { + // Note: with two queues, being underpriced is defined as being worse than the worst item + // in all non-empty queues if there is any. If both queues are empty then nothing is underpriced. + return (l.underpricedFor(&l.urgent, tx) || len(l.urgent.list) == 0) && + (l.underpricedFor(&l.floating, tx) || len(l.floating.list) == 0) && + (len(l.urgent.list) != 0 || len(l.floating.list) != 0) +} + +// underpricedFor checks whether a transaction is cheaper than (or as cheap as) the +// lowest priced (remote) transaction in the given heap. +func (l *pricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool { + // Discard stale price points if found at the heap start + for len(h.list) > 0 { + head := h.list[0] + if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated + l.stales.Add(-1) + heap.Pop(h) + continue + } + break + } + // Check if the transaction is underpriced or not + if len(h.list) == 0 { + return false // There is no remote transaction at all. + } + // If the remote transaction is even cheaper than the + // cheapest one tracked locally, reject it. + return h.cmp(h.list[0], tx) >= 0 +} + +// Discard finds a number of most underpriced transactions, removes them from the +// priced list and returns them for further removal from the entire pool. +// If noPending is set to true, we will only consider the floating list +// +// Note local transaction won't be considered for eviction. +func (l *pricedList) Discard(slots int, force bool) (types.Transactions, bool) { + drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop + for slots > 0 { + if len(l.urgent.list)*floatingRatio > len(l.floating.list)*urgentRatio { + // Discard stale transactions if found during cleanup + tx := heap.Pop(&l.urgent).(*types.Transaction) + if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated + l.stales.Add(-1) + continue + } + // Non stale transaction found, move to floating heap + heap.Push(&l.floating, tx) + } else { + if len(l.floating.list) == 0 { + // Stop if both heaps are empty + break + } + // Discard stale transactions if found during cleanup + tx := heap.Pop(&l.floating).(*types.Transaction) + if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated + l.stales.Add(-1) + continue + } + // Non stale transaction found, discard it + drop = append(drop, tx) + slots -= numSlots(tx) + } + } + // If we still can't make enough room for the new transaction + if slots > 0 && !force { + for _, tx := range drop { + heap.Push(&l.urgent, tx) + } + return nil, false + } + return drop, true +} + +// Reheap forcibly rebuilds the heap based on the current remote transaction set. +func (l *pricedList) Reheap() { + l.reheapMu.Lock() + defer l.reheapMu.Unlock() + start := time.Now() + l.stales.Store(0) + l.urgent.list = make([]*types.Transaction, 0, l.all.RemoteCount()) + l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { + l.urgent.list = append(l.urgent.list, tx) + return true + }, false, true) // Only iterate remotes + heap.Init(&l.urgent) + + // balance out the two heaps by moving the worse half of transactions into the + // floating heap + // Note: Discard would also do this before the first eviction but Reheap can do + // is more efficiently. Also, Underpriced would work suboptimally the first time + // if the floating queue was empty. + floatingCount := len(l.urgent.list) * floatingRatio / (urgentRatio + floatingRatio) + l.floating.list = make([]*types.Transaction, floatingCount) + for i := 0; i < floatingCount; i++ { + l.floating.list[i] = heap.Pop(&l.urgent).(*types.Transaction) + } + heap.Init(&l.floating) + reheapTimer.Update(time.Since(start)) +} + +// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not +// necessary to call right before SetBaseFee when processing a new block. +func (l *pricedList) SetBaseFee(baseFee *big.Int) { + l.urgent.baseFee = baseFee + l.Reheap() +} diff --git a/core/txpool/legacypool/list_test.go b/core/txpool/legacypool/list_test.go new file mode 100644 index 0000000..8587c66 --- /dev/null +++ b/core/txpool/legacypool/list_test.go @@ -0,0 +1,111 @@ +// 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 legacypool + +import ( + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/holiman/uint256" +) + +// Tests that transactions can be added to strict lists and list contents and +// nonce boundaries are correctly maintained. +func TestStrictListAdd(t *testing.T) { + // Generate a list of transactions to insert + key, _ := crypto.GenerateKey() + + txs := make(types.Transactions, 1024) + for i := 0; i < len(txs); i++ { + txs[i] = transaction(uint64(i), 0, key) + } + // Insert the transactions in a random order + list := newList(true) + for _, v := range rand.Perm(len(txs)) { + list.Add(txs[v], DefaultConfig.PriceBump) + } + // Verify internal state + if len(list.txs.items) != len(txs) { + t.Errorf("transaction count mismatch: have %d, want %d", len(list.txs.items), len(txs)) + } + for i, tx := range txs { + if list.txs.items[tx.Nonce()] != tx { + t.Errorf("item %d: transaction mismatch: have %v, want %v", i, list.txs.items[tx.Nonce()], tx) + } + } +} + +// TestListAddVeryExpensive tests adding txs which exceed 256 bits in cost. It is +// expected that the list does not panic. +func TestListAddVeryExpensive(t *testing.T) { + key, _ := crypto.GenerateKey() + list := newList(true) + for i := 0; i < 3; i++ { + value := big.NewInt(100) + gasprice, _ := new(big.Int).SetString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0) + gaslimit := uint64(i) + tx, _ := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, value, gaslimit, gasprice, nil), types.HomesteadSigner{}, key) + t.Logf("cost: %x bitlen: %d\n", tx.Cost(), tx.Cost().BitLen()) + list.Add(tx, DefaultConfig.PriceBump) + } +} + +func BenchmarkListAdd(b *testing.B) { + // Generate a list of transactions to insert + key, _ := crypto.GenerateKey() + + txs := make(types.Transactions, 100000) + for i := 0; i < len(txs); i++ { + txs[i] = transaction(uint64(i), 0, key) + } + // Insert the transactions in a random order + priceLimit := uint256.NewInt(DefaultConfig.PriceLimit) + b.ResetTimer() + for i := 0; i < b.N; i++ { + list := newList(true) + for _, v := range rand.Perm(len(txs)) { + list.Add(txs[v], DefaultConfig.PriceBump) + list.Filter(priceLimit, DefaultConfig.PriceBump) + } + } +} + +func BenchmarkListCapOneTx(b *testing.B) { + // Generate a list of transactions to insert + key, _ := crypto.GenerateKey() + + txs := make(types.Transactions, 32) + for i := 0; i < len(txs); i++ { + txs[i] = transaction(uint64(i), 0, key) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + list := newList(true) + // Insert the transactions in a random order + for _, v := range rand.Perm(len(txs)) { + list.Add(txs[v], DefaultConfig.PriceBump) + } + b.StartTimer() + list.Cap(list.Len() - 1) + b.StopTimer() + } +} diff --git a/core/txpool/legacypool/noncer.go b/core/txpool/legacypool/noncer.go new file mode 100644 index 0000000..2c65dd2 --- /dev/null +++ b/core/txpool/legacypool/noncer.go @@ -0,0 +1,91 @@ +// 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 legacypool + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" +) + +// noncer is a tiny virtual state database to manage the executable nonces of +// accounts in the pool, falling back to reading from a real state database if +// an account is unknown. +type noncer struct { + fallback *state.StateDB + nonces map[common.Address]uint64 + lock sync.Mutex +} + +// newNoncer creates a new virtual state database to track the pool nonces. +func newNoncer(statedb *state.StateDB) *noncer { + return &noncer{ + fallback: statedb.Copy(), + nonces: make(map[common.Address]uint64), + } +} + +// get returns the current nonce of an account, falling back to a real state +// database if the account is unknown. +func (txn *noncer) get(addr common.Address) uint64 { + // We use mutex for get operation is the underlying + // state will mutate db even for read access. + txn.lock.Lock() + defer txn.lock.Unlock() + + if _, ok := txn.nonces[addr]; !ok { + if nonce := txn.fallback.GetNonce(addr); nonce != 0 { + txn.nonces[addr] = nonce + } + } + return txn.nonces[addr] +} + +// set inserts a new virtual nonce into the virtual state database to be returned +// whenever the pool requests it instead of reaching into the real state database. +func (txn *noncer) set(addr common.Address, nonce uint64) { + txn.lock.Lock() + defer txn.lock.Unlock() + + txn.nonces[addr] = nonce +} + +// setIfLower updates a new virtual nonce into the virtual state database if the +// new one is lower. +func (txn *noncer) setIfLower(addr common.Address, nonce uint64) { + txn.lock.Lock() + defer txn.lock.Unlock() + + if _, ok := txn.nonces[addr]; !ok { + if nonce := txn.fallback.GetNonce(addr); nonce != 0 { + txn.nonces[addr] = nonce + } + } + if txn.nonces[addr] <= nonce { + return + } + txn.nonces[addr] = nonce +} + +// setAll sets the nonces for all accounts to the given map. +func (txn *noncer) setAll(all map[common.Address]uint64) { + txn.lock.Lock() + defer txn.lock.Unlock() + + txn.nonces = all +} diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go new file mode 100644 index 0000000..9881ed1 --- /dev/null +++ b/core/txpool/subpool.go @@ -0,0 +1,165 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package txpool + +import ( + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/holiman/uint256" +) + +// LazyTransaction contains a small subset of the transaction properties that is +// enough for the miner and other APIs to handle large batches of transactions; +// and supports pulling up the entire transaction when really needed. +type LazyTransaction struct { + Pool LazyResolver // Transaction resolver to pull the real transaction up + Hash common.Hash // Transaction hash to pull up if needed + Tx *types.Transaction // Transaction if already resolved + + Time time.Time // Time when the transaction was first seen + GasFeeCap *uint256.Int // Maximum fee per gas the transaction may consume + GasTipCap *uint256.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. +// +// Note, the method will *not* cache the retrieved transaction if the original +// pool has not cached it. The idea being, that if the tx was too big to insert +// originally, silently saving it will cause more trouble down the line (and +// indeed seems to have caused a memory bloat in the original implementation +// which did just that). +func (ltx *LazyTransaction) Resolve() *types.Transaction { + if ltx.Tx != nil { + return ltx.Tx + } + return ltx.Pool.Get(ltx.Hash) +} + +// 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 + +// PendingFilter is a collection of filter rules to allow retrieving a subset +// of transactions for announcement or mining. +// +// Note, the entries here are not arbitrary useful filters, rather each one has +// a very specific call site in mind and each one can be evaluated very cheaply +// by the pool implementations. Only add new ones that satisfy those constraints. +type PendingFilter struct { + MinTip *uint256.Int // Minimum miner tip required to include a transaction + BaseFee *uint256.Int // Minimum 1559 basefee needed to include a transaction + BlobFee *uint256.Int // Minimum 4844 blobfee needed to include a blob transaction + + OnlyPlainTxs bool // Return only plain EVM transactions (peer-join announces, block space filling) + OnlyBlobTxs bool // Return only blob transactions (block blob-space filling) +} + +// SubPool represents a specialized transaction pool that lives on its own (e.g. +// blob pool). Since independent of how many specialized pools we have, they do +// need to be updated in lockstep and assemble into one coherent view for block +// production, this interface defines the common methods that allow the primary +// transaction pool to manage the subpools. +type SubPool interface { + // Filter is a selector used to decide whether a transaction would be added + // to this particular subpool. + Filter(tx *types.Transaction) bool + + // Init sets the base parameters of the subpool, allowing it to load any saved + // transactions from disk and also permitting internal maintenance routines to + // start up. + // + // These should not be passed as a constructor argument - nor should the pools + // start by themselves - in order to keep multiple subpools in lockstep with + // one another. + Init(gasTip uint64, head *types.Header, reserve AddressReserver) error + + // Close terminates any background processing threads and releases any held + // resources. + Close() error + + // Reset retrieves the current state of the blockchain and ensures the content + // of the transaction pool is valid with regard to the chain state. + Reset(oldHead, newHead *types.Header) + + // SetGasTip updates the minimum price required by the subpool for a new + // transaction, and drops all transactions below this threshold. + SetGasTip(tip *big.Int) + + // Has returns an indicator whether subpool has a transaction cached with the + // given hash. + Has(hash common.Hash) bool + + // Get returns a transaction if it is contained in the pool, or nil otherwise. + Get(hash common.Hash) *types.Transaction + + // Add enqueues a batch of transactions into the pool if they are valid. Due + // to the large transaction churn, add may postpone fully integrating the tx + // to a later point to batch multiple ones together. + Add(txs []*types.Transaction, local bool, sync bool) []error + + // Pending retrieves all currently processable transactions, grouped by origin + // account and sorted by nonce. + // + // The transactions can also be pre-filtered by the dynamic fee components to + // reduce allocations and load on downstream subsystems. + Pending(filter PendingFilter) map[common.Address][]*LazyTransaction + + // SubscribeTransactions subscribes to new transaction events. The subscriber + // can decide whether to receive notifications only for newly seen transactions + // or also for reorged out ones. + SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription + + // Nonce returns the next nonce of an account, with all transactions executable + // by the pool already applied on top. + Nonce(addr common.Address) uint64 + + // Stats retrieves the current pool stats, namely the number of pending and the + // number of queued (non-executable) transactions. + Stats() (int, int) + + // Content retrieves the data content of the transaction pool, returning all the + // pending as well as queued transactions, grouped by account and sorted by nonce. + Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) + + // ContentFrom retrieves the data content of the transaction pool, returning the + // pending as well as queued transactions of this address, grouped by nonce. + ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) + + // Locals retrieves the accounts currently considered local by the pool. + Locals() []common.Address + + // Status returns the known status (unknown/pending/queued) of a transaction + // identified by their hashes. + Status(hash common.Hash) TxStatus +} diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go new file mode 100644 index 0000000..be74352 --- /dev/null +++ b/core/txpool/txpool.go @@ -0,0 +1,482 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package txpool + +import ( + "errors" + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +// TxStatus is the current status of a transaction as seen by the pool. +type TxStatus uint + +const ( + TxStatusUnknown TxStatus = iota + TxStatusQueued + TxStatusPending + TxStatusIncluded +) + +var ( + // reservationsGaugeName is the prefix of a per-subpool address reservation + // metric. + // + // This is mostly a sanity metric to ensure there's no bug that would make + // some subpool hog all the reservations due to mis-accounting. + reservationsGaugeName = "txpool/reservations" +) + +// BlockChain defines the minimal set of methods needed to back a tx pool with +// a chain. Exists to allow mocking the live chain out of tests. +type BlockChain interface { + // CurrentBlock returns the current head of the chain. + CurrentBlock() *types.Header + + // SubscribeChainHeadEvent subscribes to new blocks being added to the chain. + SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription +} + +// TxPool is an aggregator for various transaction specific pools, collectively +// tracking all the transactions deemed interesting by the node. Transactions +// enter the pool when they are received from the network or submitted locally. +// They exit the pool when they are included in the blockchain or evicted due to +// resource constraints. +type TxPool struct { + subpools []SubPool // List of subpools for specialized transaction handling + + 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 unsubscribe all on shutdown + quit chan chan error // Quit channel to tear down the head updater + term chan struct{} // Termination channel to detect a closed pool + + sync chan chan error // Testing / simulator channel to block until internal reset is done +} + +// New creates a new transaction pool to gather, sort and filter inbound +// transactions from the network. +func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) { + // Retrieve the current head so that all subpools and this main coordinator + // pool will have the same starting state, even if the chain moves forward + // during initialization. + head := chain.CurrentBlock() + + pool := &TxPool{ + subpools: subpools, + reservations: make(map[common.Address]SubPool), + quit: make(chan chan error), + term: make(chan struct{}), + sync: make(chan chan error), + } + for i, subpool := range subpools { + if err := subpool.Init(gasTip, head, pool.reserver(i, subpool)); err != nil { + for j := i - 1; j >= 0; j-- { + subpools[j].Close() + } + return nil, err + } + } + go pool.loop(head, chain) + return pool, nil +} + +// reserver is a method to create an address reservation callback to exclusively +// assign/deassign addresses to/from subpools. This can ensure that at any point +// in time, only a single subpool is able to manage an account, avoiding cross +// subpool eviction issues and nonce conflicts. +func (p *TxPool) reserver(id int, subpool SubPool) AddressReserver { + return func(addr common.Address, reserve bool) error { + p.reserveLock.Lock() + defer p.reserveLock.Unlock() + + owner, exists := p.reservations[addr] + if reserve { + // Double reservations are forbidden even from the same pool to + // avoid subtle bugs in the long term. + if exists { + if owner == subpool { + log.Error("pool attempted to reserve already-owned address", "address", addr) + return nil // Ignore fault to give the pool a chance to recover while the bug gets fixed + } + return ErrAlreadyReserved + } + p.reservations[addr] = subpool + if metrics.Enabled { + m := fmt.Sprintf("%s/%d", reservationsGaugeName, id) + metrics.GetOrRegisterGauge(m, nil).Inc(1) + } + return nil + } + // Ensure subpools only attempt to unreserve their own owned addresses, + // otherwise flag as a programming error. + if !exists { + log.Error("pool attempted to unreserve non-reserved address", "address", addr) + return errors.New("address not reserved") + } + if subpool != owner { + log.Error("pool attempted to unreserve non-owned address", "address", addr) + return errors.New("address not owned") + } + delete(p.reservations, addr) + if metrics.Enabled { + m := fmt.Sprintf("%s/%d", reservationsGaugeName, id) + metrics.GetOrRegisterGauge(m, nil).Dec(1) + } + return nil + } +} + +// Close terminates the transaction pool and all its subpools. +func (p *TxPool) Close() error { + var errs []error + + // Terminate the reset loop and wait for it to finish + errc := make(chan error) + p.quit <- errc + if err := <-errc; err != nil { + errs = append(errs, err) + } + // 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) + } + return nil +} + +// loop is the transaction pool's main event loop, waiting for and reacting to +// outside blockchain events as well as for various reporting and transaction +// eviction events. +func (p *TxPool) loop(head *types.Header, chain BlockChain) { + // Close the termination marker when the pool stops + defer close(p.term) + + // Subscribe to chain head events to trigger subpool resets + var ( + newHeadCh = make(chan core.ChainHeadEvent) + newHeadSub = chain.SubscribeChainHeadEvent(newHeadCh) + ) + defer newHeadSub.Unsubscribe() + + // Track the previous and current head to feed to an idle reset + var ( + oldHead = head + newHead = oldHead + ) + // Consume chain head events and start resets when none is running + var ( + resetBusy = make(chan struct{}, 1) // Allow 1 reset to run concurrently + resetDone = make(chan *types.Header) + + resetForced bool // Whether a forced reset was requested, only used in simulator mode + resetWaiter chan error // Channel waiting on a forced reset, only used in simulator mode + ) + // Notify the live reset waiter to not block if the txpool is closed. + defer func() { + if resetWaiter != nil { + resetWaiter <- errors.New("pool already terminated") + resetWaiter = nil + } + }() + var errc chan error + for errc == nil { + // Something interesting might have happened, run a reset if there is + // one needed but none is running. The resetter will run on its own + // goroutine to allow chain head events to be consumed contiguously. + if newHead != oldHead || resetForced { + // Try to inject a busy marker and start a reset if successful + select { + case resetBusy <- struct{}{}: + // Busy marker injected, start a new subpool reset + go func(oldHead, newHead *types.Header) { + for _, subpool := range p.subpools { + subpool.Reset(oldHead, newHead) + } + resetDone <- newHead + }(oldHead, newHead) + + // If the reset operation was explicitly requested, consider it + // being fulfilled and drop the request marker. If it was not, + // this is a noop. + resetForced = false + + default: + // Reset already running, wait until it finishes. + // + // Note, this will not drop any forced reset request. If a forced + // reset was requested, but we were busy, then when the currently + // running reset finishes, a new one will be spun up. + } + } + // Wait for the next chain head event or a previous reset finish + select { + case event := <-newHeadCh: + // Chain moved forward, store the head for later consumption + newHead = event.Block.Header() + + case head := <-resetDone: + // Previous reset finished, update the old head and allow a new reset + oldHead = head + <-resetBusy + + // If someone is waiting for a reset to finish, notify them, unless + // the forced op is still pending. In that case, wait another round + // of resets. + if resetWaiter != nil && !resetForced { + resetWaiter <- nil + resetWaiter = nil + } + + case errc = <-p.quit: + // Termination requested, break out on the next loop round + + case syncc := <-p.sync: + // Transaction pool is running inside a simulator, and we are about + // to create a new block. Request a forced sync operation to ensure + // that any running reset operation finishes to make block imports + // deterministic. On top of that, run a new reset operation to make + // transaction insertions deterministic instead of being stuck in a + // queue waiting for a reset. + resetForced = true + resetWaiter = syncc + } + } + // Notify the closer of termination (no error possible for now) + errc <- nil +} + +// SetGasTip updates the minimum gas tip required by the transaction pool for a +// new transaction, and drops all transactions below this threshold. +func (p *TxPool) SetGasTip(tip *big.Int) { + for _, subpool := range p.subpools { + subpool.SetGasTip(tip) + } +} + +// Has returns an indicator whether the pool has a transaction cached with the +// given hash. +func (p *TxPool) Has(hash common.Hash) bool { + for _, subpool := range p.subpools { + if subpool.Has(hash) { + return true + } + } + return false +} + +// Get returns a transaction if it is contained in the pool, or nil otherwise. +func (p *TxPool) Get(hash common.Hash) *types.Transaction { + for _, subpool := range p.subpools { + if tx := subpool.Get(hash); tx != nil { + return tx + } + } + return nil +} + +// Add enqueues a batch of transactions into the pool if they are valid. Due +// to the large transaction churn, add may postpone fully integrating the tx +// to a later point to batch multiple ones together. +func (p *TxPool) Add(txs []*types.Transaction, local bool, sync bool) []error { + // Split the input transactions between the subpools. It shouldn't really + // happen that we receive merged batches, but better graceful than strange + // errors. + // + // We also need to track how the transactions were split across the subpools, + // so we can piece back the returned errors into the original order. + txsets := make([][]*types.Transaction, len(p.subpools)) + splits := make([]int, len(txs)) + + for i, tx := range txs { + // Mark this transaction belonging to no-subpool + splits[i] = -1 + + // Try to find a subpool that accepts the transaction + for j, subpool := range p.subpools { + if subpool.Filter(tx) { + txsets[j] = append(txsets[j], tx) + splits[i] = j + break + } + } + } + // Add the transactions split apart to the individual subpools and piece + // back the errors into the original sort order. + errsets := make([][]error, len(p.subpools)) + for i := 0; i < len(p.subpools); i++ { + errsets[i] = p.subpools[i].Add(txsets[i], local, sync) + } + errs := make([]error, len(txs)) + for i, split := range splits { + // If the transaction was rejected by all subpools, mark it unsupported + if split == -1 { + errs[i] = core.ErrTxTypeNotSupported + continue + } + // Find which subpool handled it and pull in the corresponding error + errs[i] = errsets[split][0] + errsets[split] = errsets[split][1:] + } + return errs +} + +// Pending retrieves all currently processable transactions, grouped by origin +// account and sorted by nonce. +// +// The transactions can also be pre-filtered by the dynamic fee components to +// reduce allocations and load on downstream subsystems. +func (p *TxPool) Pending(filter PendingFilter) map[common.Address][]*LazyTransaction { + txs := make(map[common.Address][]*LazyTransaction) + for _, subpool := range p.subpools { + for addr, set := range subpool.Pending(filter) { + txs[addr] = set + } + } + return txs +} + +// SubscribeTransactions registers a subscription for new transaction events, +// supporting feeding only newly seen or also resurrected transactions. +func (p *TxPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { + subs := make([]event.Subscription, len(p.subpools)) + for i, subpool := range p.subpools { + subs[i] = subpool.SubscribeTransactions(ch, reorgs) + } + return p.subs.Track(event.JoinSubscriptions(subs...)) +} + +// Nonce returns the next nonce of an account, with all transactions executable +// by the pool already applied on top. +func (p *TxPool) Nonce(addr common.Address) uint64 { + // Since (for now) accounts are unique to subpools, only one pool will have + // (at max) a non-state nonce. To avoid stateful lookups, just return the + // highest nonce for now. + var nonce uint64 + for _, subpool := range p.subpools { + if next := subpool.Nonce(addr); nonce < next { + nonce = next + } + } + return nonce +} + +// Stats retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions. +func (p *TxPool) Stats() (int, int) { + var runnable, blocked int + for _, subpool := range p.subpools { + run, block := subpool.Stats() + + runnable += run + blocked += block + } + return runnable, blocked +} + +// Content retrieves the data content of the transaction pool, returning all the +// pending as well as queued transactions, grouped by account and sorted by nonce. +func (p *TxPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + var ( + runnable = make(map[common.Address][]*types.Transaction) + blocked = make(map[common.Address][]*types.Transaction) + ) + for _, subpool := range p.subpools { + run, block := subpool.Content() + + for addr, txs := range run { + runnable[addr] = txs + } + for addr, txs := range block { + blocked[addr] = txs + } + } + return runnable, blocked +} + +// ContentFrom retrieves the data content of the transaction pool, returning the +// pending as well as queued transactions of this address, grouped by nonce. +func (p *TxPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + for _, subpool := range p.subpools { + run, block := subpool.ContentFrom(addr) + if len(run) != 0 || len(block) != 0 { + return run, block + } + } + return []*types.Transaction{}, []*types.Transaction{} +} + +// Locals retrieves the accounts currently considered local by the pool. +func (p *TxPool) Locals() []common.Address { + // Retrieve the locals from each subpool and deduplicate them + locals := make(map[common.Address]struct{}) + for _, subpool := range p.subpools { + for _, local := range subpool.Locals() { + locals[local] = struct{}{} + } + } + // Flatten and return the deduplicated local set + flat := make([]common.Address, 0, len(locals)) + for local := range locals { + flat = append(flat, local) + } + return flat +} + +// Status returns the known status (unknown/pending/queued) of a transaction +// identified by its hash. +func (p *TxPool) Status(hash common.Hash) TxStatus { + for _, subpool := range p.subpools { + if status := subpool.Status(hash); status != TxStatusUnknown { + return status + } + } + return TxStatusUnknown +} + +// Sync is a helper method for unit tests or simulator runs where the chain events +// are arriving in quick succession, without any time in between them to run the +// internal background reset operations. This method will run an explicit reset +// operation to ensure the pool stabilises, thus avoiding flakey behavior. +// +// Note, do not use this in production / live code. In live code, the pool is +// meant to reset on a separate thread to avoid DoS vectors. +func (p *TxPool) Sync() error { + sync := make(chan error) + select { + case p.sync <- sync: + return <-sync + case <-p.term: + return errors.New("pool already terminated") + } +} diff --git a/core/txpool/validation.go b/core/txpool/validation.go new file mode 100644 index 0000000..555b777 --- /dev/null +++ b/core/txpool/validation.go @@ -0,0 +1,250 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package txpool + +import ( + "crypto/sha256" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +var ( + // blobTxMinBlobGasPrice is the big.Int version of the configured protocol + // parameter to avoid constructing a new big integer for every transaction. + blobTxMinBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice) +) + +// ValidationOptions define certain differences between transaction validation +// across the different pools without having to duplicate those checks. +type ValidationOptions struct { + Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules + + Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool + MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle + MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool +} + +// ValidateTransaction is a helper method to check whether a transaction is valid +// according to the consensus rules, but does not check state-dependent validation +// (balance, nonce, etc). +// +// This check is public to allow different transaction pools to check the basic +// rules without duplicating code and running the risk of missed updates. +func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types.Signer, opts *ValidationOptions) error { + // Ensure transactions not implemented by the calling pool are rejected + if opts.Accept&(1< opts.MaxSize { + return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize) + } + // Ensure only transactions that have been enabled are accepted + if !opts.Config.IsBerlin(head.Number) && tx.Type() != types.LegacyTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in Berlin", core.ErrTxTypeNotSupported, tx.Type()) + } + if !opts.Config.IsLondon(head.Number) && tx.Type() == types.DynamicFeeTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in London", core.ErrTxTypeNotSupported, tx.Type()) + } + if !opts.Config.IsCancun(head.Number, head.Time) && tx.Type() == types.BlobTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in Cancun", core.ErrTxTypeNotSupported, tx.Type()) + } + // Check whether the init code size has been exceeded + if opts.Config.IsShanghai(head.Number, head.Time) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { + return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) + } + // Transactions can't be negative. This may never happen using RLP decoded + // transactions but may occur for transactions created using the RPC. + if tx.Value().Sign() < 0 { + return ErrNegativeValue + } + // Ensure the transaction doesn't exceed the current block limit gas + if head.GasLimit < tx.Gas() { + return ErrGasLimit + } + // Sanity check for extremely large numbers (supported by RLP or RPC) + if tx.GasFeeCap().BitLen() > 256 { + return core.ErrFeeCapVeryHigh + } + if tx.GasTipCap().BitLen() > 256 { + return core.ErrTipVeryHigh + } + // Ensure gasFeeCap is greater than or equal to gasTipCap + if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 { + return core.ErrTipAboveFeeCap + } + // Make sure the transaction is signed properly + if _, err := types.Sender(signer, tx); err != nil { + return ErrInvalidSender + } + // Ensure the transaction has more gas than the bare minimum needed to cover + // the transaction metadata + intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time)) + if err != nil { + return err + } + if tx.Gas() < intrGas { + return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas) + } + // Ensure the gasprice is high enough to cover the requirement of the calling pool + if tx.GasTipCapIntCmp(opts.MinTip) < 0 { + return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrUnderpriced, tx.GasTipCap(), opts.MinTip) + } + if tx.Type() == types.BlobTxType { + // Ensure the blob fee cap satisfies the minimum blob gas price + if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 { + return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrUnderpriced, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice) + } + sidecar := tx.BlobTxSidecar() + if sidecar == nil { + return errors.New("missing sidecar in blob transaction") + } + // Ensure the number of items in the blob transaction and various side + // data match up before doing any expensive validations + hashes := tx.BlobHashes() + if len(hashes) == 0 { + return errors.New("blobless blob transaction") + } + if len(hashes) > params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob { + return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) + } + // Ensure commitments, proofs and hashes are valid + if err := validateBlobSidecar(hashes, sidecar); err != nil { + return err + } + } + return nil +} + +func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) error { + if len(sidecar.Blobs) != len(hashes) { + return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes)) + } + if len(sidecar.Commitments) != len(hashes) { + return fmt.Errorf("invalid number of %d blob commitments compared to %d blob hashes", len(sidecar.Commitments), len(hashes)) + } + if len(sidecar.Proofs) != len(hashes) { + return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes)) + } + // Blob quantities match up, validate that the provers match with the + // transaction hash before getting to the cryptography + hasher := sha256.New() + for i, vhash := range hashes { + computed := kzg4844.CalcBlobHashV1(hasher, &sidecar.Commitments[i]) + if vhash != computed { + return fmt.Errorf("blob %d: computed hash %#x mismatches transaction one %#x", i, computed, vhash) + } + } + // Blob commitments match with the hashes in the transaction, verify the + // blobs themselves via KZG + for i := range sidecar.Blobs { + if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil { + return fmt.Errorf("invalid blob %d: %v", i, err) + } + } + return nil +} + +// ValidationOptionsWithState define certain differences between stateful transaction +// validation across the different pools without having to duplicate those checks. +type ValidationOptionsWithState struct { + State *state.StateDB // State database to check nonces and balances against + + // FirstNonceGap is an optional callback to retrieve the first nonce gap in + // the list of pooled transactions of a specific account. If this method is + // set, nonce gaps will be checked and forbidden. If this method is not set, + // nonce gaps will be ignored and permitted. + FirstNonceGap func(addr common.Address) uint64 + + // UsedAndLeftSlots is a mandatory callback to retrieve the number of tx slots + // used and the number still permitted for an account. New transactions will + // be rejected once the number of remaining slots reaches zero. + UsedAndLeftSlots func(addr common.Address) (int, int) + + // ExistingExpenditure is a mandatory callback to retrieve the cumulative + // cost of the already pooled transactions to check for overdrafts. + ExistingExpenditure func(addr common.Address) *big.Int + + // ExistingCost is a mandatory callback to retrieve an already pooled + // transaction's cost with the given nonce to check for overdrafts. + ExistingCost func(addr common.Address, nonce uint64) *big.Int +} + +// ValidateTransactionWithState is a helper method to check whether a transaction +// is valid according to the pool's internal state checks (balance, nonce, gaps). +// +// This check is public to allow different transaction pools to check the stateful +// rules without duplicating code and running the risk of missed updates. +func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, opts *ValidationOptionsWithState) error { + // Ensure the transaction adheres to nonce ordering + from, err := signer.Sender(tx) // already validated (and cached), but cleaner to check + if err != nil { + log.Error("Transaction sender recovery failed", "err", err) + return err + } + next := opts.State.GetNonce(from) + if next > tx.Nonce() { + return fmt.Errorf("%w: next nonce %v, tx nonce %v", core.ErrNonceTooLow, next, tx.Nonce()) + } + // Ensure the transaction doesn't produce a nonce gap in pools that do not + // support arbitrary orderings + if opts.FirstNonceGap != nil { + if gap := opts.FirstNonceGap(from); gap < tx.Nonce() { + return fmt.Errorf("%w: tx nonce %v, gapped nonce %v", core.ErrNonceTooHigh, tx.Nonce(), gap) + } + } + // Ensure the transactor has enough funds to cover the transaction costs + var ( + balance = opts.State.GetBalance(from).ToBig() + cost = tx.Cost() + ) + if balance.Cmp(cost) < 0 { + return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, cost, new(big.Int).Sub(cost, balance)) + } + // Ensure the transactor has enough funds to cover for replacements or nonce + // expansions without overdrafts + spent := opts.ExistingExpenditure(from) + if prev := opts.ExistingCost(from, tx.Nonce()); prev != nil { + bump := new(big.Int).Sub(cost, prev) + need := new(big.Int).Add(spent, bump) + if balance.Cmp(need) < 0 { + return fmt.Errorf("%w: balance %v, queued cost %v, tx bumped %v, overshot %v", core.ErrInsufficientFunds, balance, spent, bump, new(big.Int).Sub(need, balance)) + } + } else { + need := new(big.Int).Add(spent, cost) + if balance.Cmp(need) < 0 { + return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, spent, cost, new(big.Int).Sub(need, balance)) + } + // Transaction takes a new nonce value out of the pool. Ensure it doesn't + // overflow the number of permitted transactions from a single account + // (i.e. max cancellable via out-of-bound transaction). + if used, left := opts.UsedAndLeftSlots(from); left <= 0 { + return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used) + } + } + return nil +} diff --git a/core/types.go b/core/types.go new file mode 100644 index 0000000..dc13de5 --- /dev/null +++ b/core/types.go @@ -0,0 +1,58 @@ +// 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 core + +import ( + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/stateless" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +// Validator is an interface which defines the standard for block validation. It +// is only responsible for validating block contents, as the header validation is +// done by the specific consensus engines. +type Validator interface { + // ValidateBody validates the given block's content. + ValidateBody(block *types.Block) error + + // ValidateState validates the given statedb and optionally the receipts and + // gas used. + ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error + + // ValidateWitness cross validates a block execution with stateless remote clients. + ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error +} + +// Prefetcher is an interface for pre-caching transaction signatures and state. +type Prefetcher interface { + // Prefetch processes the state changes according to the Ethereum rules by running + // the transaction messages using the statedb, but any changes are discarded. The + // only goal is to pre-cache transaction signatures and state trie nodes. + Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) +} + +// Processor is an interface for processing blocks using a given initial state. +type Processor interface { + // Process processes the state changes according to the Ethereum rules by running + // the transaction messages using the statedb and applying any rewards to both + // the processor (coinbase) and any included uncles. + Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) +} diff --git a/core/types/account.go b/core/types/account.go new file mode 100644 index 0000000..52ce184 --- /dev/null +++ b/core/types/account.go @@ -0,0 +1,87 @@ +// Copyright 2024 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" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" +) + +//go:generate go run github.com/fjl/gencodec -type Account -field-override accountMarshaling -out gen_account.go + +// Account represents an Ethereum account and its attached data. +// This type is used to specify accounts in the genesis block state, and +// is also useful for JSON encoding/decoding of accounts. +type Account struct { + Code []byte `json:"code,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + Balance *big.Int `json:"balance" gencodec:"required"` + Nonce uint64 `json:"nonce,omitempty"` + + // used in tests + PrivateKey []byte `json:"secretKey,omitempty"` +} + +type accountMarshaling struct { + Code hexutil.Bytes + Balance *math.HexOrDecimal256 + Nonce math.HexOrDecimal64 + Storage map[storageJSON]storageJSON + PrivateKey hexutil.Bytes +} + +// storageJSON represents a 256 bit byte array, but allows less than 256 bits when +// unmarshalling from hex. +type storageJSON common.Hash + +func (h *storageJSON) UnmarshalText(text []byte) error { + text = bytes.TrimPrefix(text, []byte("0x")) + if len(text) > 64 { + return fmt.Errorf("too many hex characters in storage key/value %q", text) + } + offset := len(h) - len(text)/2 // pad on the left + if _, err := hex.Decode(h[offset:], text); err != nil { + return fmt.Errorf("invalid hex storage key/value %q", text) + } + return nil +} + +func (h storageJSON) MarshalText() ([]byte, error) { + return hexutil.Bytes(h[:]).MarshalText() +} + +// GenesisAlloc specifies the initial state of a genesis block. +type GenesisAlloc map[common.Address]Account + +func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error { + m := make(map[common.UnprefixedAddress]Account) + if err := json.Unmarshal(data, &m); err != nil { + return err + } + *ga = make(GenesisAlloc) + for addr, a := range m { + (*ga)[common.Address(addr)] = a + } + return nil +} diff --git a/core/types/block.go b/core/types/block.go new file mode 100644 index 0000000..4857cd6 --- /dev/null +++ b/core/types/block.go @@ -0,0 +1,498 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package types contains data types related to Ethereum consensus. +package types + +import ( + "encoding/binary" + "fmt" + "io" + "math/big" + "reflect" + "slices" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rlp" +) + +// A BlockNonce is a 64-bit hash which proves (combined with the +// mix-hash) that a sufficient amount of computation has been carried +// out on a block. +type BlockNonce [8]byte + +// EncodeNonce converts the given integer to a block nonce. +func EncodeNonce(i uint64) BlockNonce { + var n BlockNonce + binary.BigEndian.PutUint64(n[:], i) + return n +} + +// Uint64 returns the integer value of a block nonce. +func (n BlockNonce) Uint64() uint64 { + return binary.BigEndian.Uint64(n[:]) +} + +// MarshalText encodes n as a hex string with 0x prefix. +func (n BlockNonce) MarshalText() ([]byte, error) { + return hexutil.Bytes(n[:]).MarshalText() +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (n *BlockNonce) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("BlockNonce", input, n[:]) +} + +//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go +//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go + +// Header represents a block header in the Ethereum blockchain. +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 *big.Int `json:"difficulty" gencodec:"required"` + Number *big.Int `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Time uint64 `json:"timestamp" gencodec:"required"` + Extra []byte `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + + // BaseFee was added by EIP-1559 and is ignored in legacy headers. + BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + + // WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers. + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + + // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. + BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` + + // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. + ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` + + // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. + ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` +} + +// field type overrides for gencodec +type headerMarshaling struct { + Difficulty *hexutil.Big + Number *hexutil.Big + GasLimit hexutil.Uint64 + GasUsed hexutil.Uint64 + Time hexutil.Uint64 + Extra hexutil.Bytes + BaseFee *hexutil.Big + Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON + BlobGasUsed *hexutil.Uint64 + ExcessBlobGas *hexutil.Uint64 +} + +// Hash returns the block hash of the header, which is simply the keccak256 hash of its +// RLP encoding. +func (h *Header) Hash() common.Hash { + return rlpHash(h) +} + +var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size()) + +// Size returns the approximate memory used by all internal contents. It is used +// to approximate and limit the memory consumption of various caches. +func (h *Header) Size() common.StorageSize { + var baseFeeBits int + if h.BaseFee != nil { + baseFeeBits = h.BaseFee.BitLen() + } + return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+baseFeeBits)/8) +} + +// SanityCheck checks a few basic things -- these checks are way beyond what +// any 'sane' production values should hold, and can mainly be used to prevent +// that the unbounded fields are stuffed with junk data to add processing +// overhead +func (h *Header) SanityCheck() error { + if h.Number != nil && !h.Number.IsUint64() { + return fmt.Errorf("too large block number: bitlen %d", h.Number.BitLen()) + } + if h.Difficulty != nil { + if diffLen := h.Difficulty.BitLen(); diffLen > 80 { + return fmt.Errorf("too large block difficulty: bitlen %d", diffLen) + } + } + if eLen := len(h.Extra); eLen > 100*1024 { + return fmt.Errorf("too large block extradata: size %d", eLen) + } + if h.BaseFee != nil { + if bfLen := h.BaseFee.BitLen(); bfLen > 256 { + return fmt.Errorf("too large base fee: bitlen %d", bfLen) + } + } + return nil +} + +// EmptyBody returns true if there is no additional 'body' to complete the header +// that is: no transactions, no uncles and no withdrawals. +func (h *Header) EmptyBody() bool { + if h.WithdrawalsHash != nil { + return h.TxHash == EmptyTxsHash && *h.WithdrawalsHash == EmptyWithdrawalsHash + } + return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash +} + +// EmptyReceipts returns true if there are no receipts for this header/block. +func (h *Header) EmptyReceipts() bool { + return h.ReceiptHash == EmptyReceiptsHash +} + +// Body is a simple (mutable, non-safe) data container for storing and moving +// a block's data contents (transactions and uncles) together. +type Body struct { + Transactions []*Transaction + Uncles []*Header + Withdrawals []*Withdrawal `rlp:"optional"` +} + +// Block represents an Ethereum block. +// +// Note the Block type tries to be 'immutable', and contains certain caches that rely +// on that. The rules around block immutability are as follows: +// +// - We copy all data when the block is constructed. This makes references held inside +// the block independent of whatever value was passed in. +// +// - We copy all header data on access. This is because any change to the header would mess +// up the cached hash and size values in the block. Calling code is expected to take +// advantage of this to avoid over-allocating! +// +// - When new body data is attached to the block, a shallow copy of the block is returned. +// This ensures block modifications are race-free. +// +// - We do not copy body data on access because it does not affect the caches, and also +// because it would be too expensive. +type Block struct { + header *Header + uncles []*Header + transactions Transactions + withdrawals Withdrawals + + // caches + hash atomic.Pointer[common.Hash] + size atomic.Uint64 + + // These fields are used by package eth to track + // inter-peer block relay. + ReceivedAt time.Time + ReceivedFrom interface{} +} + +// "external" block encoding. used for eth protocol, etc. +type extblock struct { + Header *Header + Txs []*Transaction + Uncles []*Header + Withdrawals []*Withdrawal `rlp:"optional"` +} + +// NewBlock creates a new block. The input data is copied, changes to header and to the +// field values will not affect the block. +// +// The body elements and the receipts are used to recompute and overwrite the +// relevant portions of the header. +func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block { + if body == nil { + body = &Body{} + } + var ( + b = NewBlockWithHeader(header) + txs = body.Transactions + uncles = body.Uncles + withdrawals = body.Withdrawals + ) + + if len(txs) == 0 { + b.header.TxHash = EmptyTxsHash + } else { + b.header.TxHash = DeriveSha(Transactions(txs), hasher) + b.transactions = make(Transactions, len(txs)) + copy(b.transactions, txs) + } + + if len(receipts) == 0 { + b.header.ReceiptHash = EmptyReceiptsHash + } else { + b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher) + b.header.Bloom = CreateBloom(receipts) + } + + if len(uncles) == 0 { + b.header.UncleHash = EmptyUncleHash + } else { + b.header.UncleHash = CalcUncleHash(uncles) + b.uncles = make([]*Header, len(uncles)) + for i := range uncles { + b.uncles[i] = CopyHeader(uncles[i]) + } + } + + if withdrawals == nil { + b.header.WithdrawalsHash = nil + } else if len(withdrawals) == 0 { + b.header.WithdrawalsHash = &EmptyWithdrawalsHash + b.withdrawals = Withdrawals{} + } else { + hash := DeriveSha(Withdrawals(withdrawals), hasher) + b.header.WithdrawalsHash = &hash + b.withdrawals = slices.Clone(withdrawals) + } + + return b +} + +// CopyHeader creates a deep copy of a block header. +func CopyHeader(h *Header) *Header { + cpy := *h + if cpy.Difficulty = new(big.Int); h.Difficulty != nil { + cpy.Difficulty.Set(h.Difficulty) + } + if cpy.Number = new(big.Int); h.Number != nil { + cpy.Number.Set(h.Number) + } + if h.BaseFee != nil { + cpy.BaseFee = new(big.Int).Set(h.BaseFee) + } + if len(h.Extra) > 0 { + cpy.Extra = make([]byte, len(h.Extra)) + copy(cpy.Extra, h.Extra) + } + if h.WithdrawalsHash != nil { + cpy.WithdrawalsHash = new(common.Hash) + *cpy.WithdrawalsHash = *h.WithdrawalsHash + } + if h.ExcessBlobGas != nil { + cpy.ExcessBlobGas = new(uint64) + *cpy.ExcessBlobGas = *h.ExcessBlobGas + } + if h.BlobGasUsed != nil { + cpy.BlobGasUsed = new(uint64) + *cpy.BlobGasUsed = *h.BlobGasUsed + } + if h.ParentBeaconRoot != nil { + cpy.ParentBeaconRoot = new(common.Hash) + *cpy.ParentBeaconRoot = *h.ParentBeaconRoot + } + return &cpy +} + +// DecodeRLP decodes a block from RLP. +func (b *Block) DecodeRLP(s *rlp.Stream) error { + var eb extblock + _, size, _ := s.Kind() + if err := s.Decode(&eb); err != nil { + return err + } + b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals + b.size.Store(rlp.ListSize(size)) + return nil +} + +// EncodeRLP serializes a block as RLP. +func (b *Block) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, &extblock{ + Header: b.header, + Txs: b.transactions, + Uncles: b.uncles, + Withdrawals: b.withdrawals, + }) +} + +// Body returns the non-header content of the block. +// Note the returned data is not an independent copy. +func (b *Block) Body() *Body { + return &Body{b.transactions, b.uncles, b.withdrawals} +} + +// Accessors for body data. These do not return a copy because the content +// of the body slices does not affect the cached hash/size in block. + +func (b *Block) Uncles() []*Header { return b.uncles } +func (b *Block) Transactions() Transactions { return b.transactions } +func (b *Block) Withdrawals() Withdrawals { return b.withdrawals } + +func (b *Block) Transaction(hash common.Hash) *Transaction { + for _, transaction := range b.transactions { + if transaction.Hash() == hash { + return transaction + } + } + return nil +} + +// Header returns the block header (as a copy). +func (b *Block) Header() *Header { + return CopyHeader(b.header) +} + +// Header value accessors. These do copy! + +func (b *Block) Number() *big.Int { return new(big.Int).Set(b.header.Number) } +func (b *Block) GasLimit() uint64 { return b.header.GasLimit } +func (b *Block) GasUsed() uint64 { return b.header.GasUsed } +func (b *Block) Difficulty() *big.Int { return new(big.Int).Set(b.header.Difficulty) } +func (b *Block) Time() uint64 { return b.header.Time } + +func (b *Block) NumberU64() uint64 { return b.header.Number.Uint64() } +func (b *Block) MixDigest() common.Hash { return b.header.MixDigest } +func (b *Block) Nonce() uint64 { return binary.BigEndian.Uint64(b.header.Nonce[:]) } +func (b *Block) Bloom() Bloom { return b.header.Bloom } +func (b *Block) Coinbase() common.Address { return b.header.Coinbase } +func (b *Block) Root() common.Hash { return b.header.Root } +func (b *Block) ParentHash() common.Hash { return b.header.ParentHash } +func (b *Block) TxHash() common.Hash { return b.header.TxHash } +func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash } +func (b *Block) UncleHash() common.Hash { return b.header.UncleHash } +func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) } + +func (b *Block) BaseFee() *big.Int { + if b.header.BaseFee == nil { + return nil + } + 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 { + excessBlobGas = new(uint64) + *excessBlobGas = *b.header.ExcessBlobGas + } + return excessBlobGas +} + +func (b *Block) BlobGasUsed() *uint64 { + var blobGasUsed *uint64 + if b.header.BlobGasUsed != nil { + blobGasUsed = new(uint64) + *blobGasUsed = *b.header.BlobGasUsed + } + return blobGasUsed +} + +// Size returns the true RLP encoded storage size of the block, either by encoding +// and returning it, or returning a previously cached value. +func (b *Block) Size() uint64 { + if size := b.size.Load(); size > 0 { + return size + } + c := writeCounter(0) + rlp.Encode(&c, b) + b.size.Store(uint64(c)) + return uint64(c) +} + +// SanityCheck can be used to prevent that unbounded fields are +// stuffed with junk data to add processing overhead +func (b *Block) SanityCheck() error { + return b.header.SanityCheck() +} + +type writeCounter uint64 + +func (c *writeCounter) Write(b []byte) (int, error) { + *c += writeCounter(len(b)) + return len(b), nil +} + +func CalcUncleHash(uncles []*Header) common.Hash { + if len(uncles) == 0 { + return EmptyUncleHash + } + return rlpHash(uncles) +} + +// NewBlockWithHeader creates a block with the given header data. The +// header data is copied, changes to header and to the field values +// will not affect the block. +func NewBlockWithHeader(header *Header) *Block { + return &Block{header: CopyHeader(header)} +} + +// WithSeal returns a new block with the data from b but the header replaced with +// the sealed one. +func (b *Block) WithSeal(header *Header) *Block { + return &Block{ + header: CopyHeader(header), + transactions: b.transactions, + uncles: b.uncles, + withdrawals: b.withdrawals, + } +} + +// WithBody returns a new block with the original header and a deep copy of the +// provided body. +func (b *Block) WithBody(body Body) *Block { + block := &Block{ + header: b.header, + transactions: slices.Clone(body.Transactions), + uncles: make([]*Header, len(body.Uncles)), + withdrawals: slices.Clone(body.Withdrawals), + } + for i := range body.Uncles { + block.uncles[i] = CopyHeader(body.Uncles[i]) + } + return block +} + +// Hash returns the keccak256 hash of b's header. +// The hash is computed on the first call and cached thereafter. +func (b *Block) Hash() common.Hash { + if hash := b.hash.Load(); hash != nil { + return *hash + } + h := b.header.Hash() + b.hash.Store(&h) + return h +} + +type Blocks []*Block + +// HeaderParentHashFromRLP returns the parentHash of an RLP-encoded +// header. If 'header' is invalid, the zero hash is returned. +func HeaderParentHashFromRLP(header []byte) common.Hash { + // parentHash is the first list element. + listContent, _, err := rlp.SplitList(header) + if err != nil { + return common.Hash{} + } + parentHash, _, err := rlp.SplitString(listContent) + if err != nil { + return common.Hash{} + } + if len(parentHash) != 32 { + return common.Hash{} + } + return common.BytesToHash(parentHash) +} diff --git a/core/types/block_test.go b/core/types/block_test.go new file mode 100644 index 0000000..1af5b9d --- /dev/null +++ b/core/types/block_test.go @@ -0,0 +1,319 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "bytes" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/blocktest" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// from bcValidBlockTest.json, "SimpleTx" +func TestBlockEncoding(t *testing.T) { + blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + check("Difficulty", block.Difficulty(), big.NewInt(131072)) + check("GasLimit", block.GasLimit(), uint64(3141592)) + check("GasUsed", block.GasUsed(), uint64(21000)) + check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) + check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) + check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) + check("Hash", block.Hash(), common.HexToHash("0a5843ac1cb04865017cb35a57b50b07084e5fcee39b5acadade33149f4fff9e")) + check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) + check("Time", block.Time(), uint64(1426516743)) + check("Size", block.Size(), uint64(len(blockEnc))) + + tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) + tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) + check("len(Transactions)", len(block.Transactions()), 1) + check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + +func TestEIP1559BlockEncoding(t *testing.T) { + blockEnc := common.FromHex("f9030bf901fea083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + + check("Difficulty", block.Difficulty(), big.NewInt(131072)) + check("GasLimit", block.GasLimit(), uint64(3141592)) + check("GasUsed", block.GasUsed(), uint64(21000)) + check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) + check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) + check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) + check("Hash", block.Hash(), common.HexToHash("c7252048cd273fe0dac09650027d07f0e3da4ee0675ebbb26627cea92729c372")) + check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) + check("Time", block.Time(), uint64(1426516743)) + check("Size", block.Size(), uint64(len(blockEnc))) + check("BaseFee", block.BaseFee(), new(big.Int).SetUint64(params.InitialBaseFee)) + + tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) + tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) + + addr := common.HexToAddress("0x0000000000000000000000000000000000000001") + accesses := AccessList{AccessTuple{ + Address: addr, + StorageKeys: []common.Hash{ + {0}, + }, + }} + to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + txdata := &DynamicFeeTx{ + ChainID: big.NewInt(1), + Nonce: 0, + To: &to, + Gas: 123457, + GasFeeCap: new(big.Int).Set(block.BaseFee()), + GasTipCap: big.NewInt(0), + AccessList: accesses, + Data: []byte{}, + } + tx2 := NewTx(txdata) + tx2, err := tx2.WithSignature(LatestSignerForChainID(big.NewInt(1)), common.Hex2Bytes("fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a800")) + if err != nil { + t.Fatal("invalid signature error: ", err) + } + + check("len(Transactions)", len(block.Transactions()), 2) + check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + check("Transactions[1].Hash", block.Transactions()[1].Hash(), tx2.Hash()) + check("Transactions[1].Type", block.Transactions()[1].Type(), tx2.Type()) + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + +func TestEIP2718BlockEncoding(t *testing.T) { + blockEnc := common.FromHex("f90319f90211a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0e6e49996c7ec59f7a23d22b83239a60151512c65613bf84a0d7da336399ebc4aa0cafe75574d59780665a97fbfd11365c7545aa8f1abf4e5e12e8243334ef7286bb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000820200832fefd882a410845506eb0796636f6f6c65737420626c6f636b206f6e20636861696ea0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f90101f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b89e01f89b01800a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a03dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335a0476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef14c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + check("Difficulty", block.Difficulty(), big.NewInt(131072)) + check("GasLimit", block.GasLimit(), uint64(3141592)) + check("GasUsed", block.GasUsed(), uint64(42000)) + check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) + check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) + check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) + check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) + check("Time", block.Time(), uint64(1426516743)) + check("Size", block.Size(), uint64(len(blockEnc))) + + // Create legacy tx. + to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + tx1 := NewTx(&LegacyTx{ + Nonce: 0, + To: &to, + Value: big.NewInt(10), + Gas: 50000, + GasPrice: big.NewInt(10), + }) + sig := common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100") + tx1, _ = tx1.WithSignature(HomesteadSigner{}, sig) + + // Create ACL tx. + addr := common.HexToAddress("0x0000000000000000000000000000000000000001") + tx2 := NewTx(&AccessListTx{ + ChainID: big.NewInt(1), + Nonce: 0, + To: &to, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}}, + }) + sig2 := common.Hex2Bytes("3dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef1401") + tx2, _ = tx2.WithSignature(NewEIP2930Signer(big.NewInt(1)), sig2) + + check("len(Transactions)", len(block.Transactions()), 2) + check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + check("Transactions[1].Hash", block.Transactions()[1].Hash(), tx2.Hash()) + check("Transactions[1].Type()", block.Transactions()[1].Type(), uint8(AccessListTxType)) + + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + +func TestUncleHash(t *testing.T) { + uncles := make([]*Header, 0) + h := CalcUncleHash(uncles) + exp := EmptyUncleHash + if h != exp { + t.Fatalf("empty uncle hash is wrong, got %x != %x", h, exp) + } +} + +var benchBuffer = bytes.NewBuffer(make([]byte, 0, 32000)) + +func BenchmarkEncodeBlock(b *testing.B) { + block := makeBenchBlock() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchBuffer.Reset() + if err := rlp.Encode(benchBuffer, block); err != nil { + b.Fatal(err) + } + } +} + +func makeBenchBlock() *Block { + var ( + key, _ = crypto.GenerateKey() + txs = make([]*Transaction, 70) + receipts = make([]*Receipt, len(txs)) + signer = LatestSigner(params.TestChainConfig) + uncles = make([]*Header, 3) + ) + header := &Header{ + Difficulty: math.BigPow(11, 11), + Number: math.BigPow(2, 9), + GasLimit: 12345678, + GasUsed: 1476322, + Time: 9876543, + Extra: []byte("coolest block on chain"), + } + for i := range txs { + amount := math.BigPow(2, int64(i)) + price := big.NewInt(300000) + data := make([]byte, 100) + tx := NewTransaction(uint64(i), common.Address{}, amount, 123457, price, data) + signedTx, err := SignTx(tx, signer, key) + if err != nil { + panic(err) + } + txs[i] = signedTx + receipts[i] = NewReceipt(make([]byte, 32), false, tx.Gas()) + } + for i := range uncles { + uncles[i] = &Header{ + Difficulty: math.BigPow(11, 11), + Number: math.BigPow(2, 9), + GasLimit: 12345678, + GasUsed: 1476322, + Time: 9876543, + Extra: []byte("benchmark uncle"), + } + } + return NewBlock(header, &Body{Transactions: txs, Uncles: uncles}, receipts, blocktest.NewHasher()) +} + +func TestRlpDecodeParentHash(t *testing.T) { + // A minimum one + want := common.HexToHash("0x112233445566778899001122334455667788990011223344556677889900aabb") + if rlpData, err := rlp.EncodeToBytes(&Header{ParentHash: want}); err != nil { + t.Fatal(err) + } else { + if have := HeaderParentHashFromRLP(rlpData); have != want { + t.Fatalf("have %x, want %x", have, want) + } + } + // And a maximum one + // | Difficulty | dynamic| *big.Int | 0x5ad3c2c71bbff854908 (current mainnet TD: 76 bits) | + // | Number | dynamic| *big.Int | 64 bits | + // | Extra | dynamic| []byte | 65+32 byte (clique) | + // | BaseFee | dynamic| *big.Int | 64 bits | + mainnetTd := new(big.Int) + mainnetTd.SetString("5ad3c2c71bbff854908", 16) + if rlpData, err := rlp.EncodeToBytes(&Header{ + ParentHash: want, + Difficulty: mainnetTd, + Number: new(big.Int).SetUint64(math.MaxUint64), + Extra: make([]byte, 65+32), + BaseFee: new(big.Int).SetUint64(math.MaxUint64), + }); err != nil { + t.Fatal(err) + } else { + if have := HeaderParentHashFromRLP(rlpData); have != want { + t.Fatalf("have %x, want %x", have, want) + } + } + // Also test a very very large header. + { + // The rlp-encoding of the header belowCauses _total_ length of 65540, + // which is the first to blow the fast-path. + h := &Header{ + ParentHash: want, + Extra: make([]byte, 65041), + } + if rlpData, err := rlp.EncodeToBytes(h); err != nil { + t.Fatal(err) + } else { + if have := HeaderParentHashFromRLP(rlpData); have != want { + t.Fatalf("have %x, want %x", have, want) + } + } + } + { + // Test some invalid erroneous stuff + for i, rlpData := range [][]byte{ + nil, + common.FromHex("0x"), + common.FromHex("0x01"), + common.FromHex("0x3031323334"), + } { + if have, want := HeaderParentHashFromRLP(rlpData), (common.Hash{}); have != want { + t.Fatalf("invalid %d: have %x, want %x", i, have, want) + } + } + } +} diff --git a/core/types/bloom9.go b/core/types/bloom9.go new file mode 100644 index 0000000..a560a20 --- /dev/null +++ b/core/types/bloom9.go @@ -0,0 +1,160 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "encoding/binary" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" +) + +type bytesBacked interface { + Bytes() []byte +} + +const ( + // BloomByteLength represents the number of bytes used in a header log bloom. + BloomByteLength = 256 + + // BloomBitLength represents the number of bits used in a header log bloom. + BloomBitLength = 8 * BloomByteLength +) + +// Bloom represents a 2048 bit bloom filter. +type Bloom [BloomByteLength]byte + +// BytesToBloom converts a byte slice to a bloom filter. +// It panics if b is not of suitable size. +func BytesToBloom(b []byte) Bloom { + var bloom Bloom + bloom.SetBytes(b) + return bloom +} + +// SetBytes sets the content of b to the given bytes. +// It panics if d is not of suitable size. +func (b *Bloom) SetBytes(d []byte) { + if len(b) < len(d) { + panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d))) + } + copy(b[BloomByteLength-len(d):], d) +} + +// Add adds d to the filter. Future calls of Test(d) will return true. +func (b *Bloom) Add(d []byte) { + b.add(d, make([]byte, 6)) +} + +// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) +func (b *Bloom) add(d []byte, buf []byte) { + i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) + b[i1] |= v1 + b[i2] |= v2 + b[i3] |= v3 +} + +// Big converts b to a big integer. +// Note: Converting a bloom filter to a big.Int and then calling GetBytes +// does not return the same bytes, since big.Int will trim leading zeroes +func (b Bloom) Big() *big.Int { + return new(big.Int).SetBytes(b[:]) +} + +// Bytes returns the backing byte slice of the bloom +func (b Bloom) Bytes() []byte { + return b[:] +} + +// Test checks if the given topic is present in the bloom filter +func (b Bloom) Test(topic []byte) bool { + i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) + return v1 == v1&b[i1] && + v2 == v2&b[i2] && + v3 == v3&b[i3] +} + +// MarshalText encodes b as a hex string with 0x prefix. +func (b Bloom) MarshalText() ([]byte, error) { + return hexutil.Bytes(b[:]).MarshalText() +} + +// UnmarshalText b as a hex string with 0x prefix. +func (b *Bloom) UnmarshalText(input []byte) error { + return hexutil.UnmarshalFixedText("Bloom", input, b[:]) +} + +// CreateBloom creates a bloom filter out of the give Receipts (+Logs) +func CreateBloom(receipts Receipts) Bloom { + buf := make([]byte, 6) + var bin Bloom + for _, receipt := range receipts { + for _, log := range receipt.Logs { + bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { + bin.add(b[:], buf) + } + } + } + return bin +} + +// LogsBloom returns the bloom bytes for the given logs +func LogsBloom(logs []*Log) []byte { + buf := make([]byte, 6) + var bin Bloom + for _, log := range logs { + bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { + bin.add(b[:], buf) + } + } + return bin[:] +} + +// Bloom9 returns the bloom filter for the given data +func Bloom9(data []byte) []byte { + var b Bloom + b.SetBytes(data) + return b.Bytes() +} + +// bloomValues returns the bytes (index-value pairs) to set for the given data +func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { + sha := hasherPool.Get().(crypto.KeccakState) + sha.Reset() + sha.Write(data) + sha.Read(hashbuf) + hasherPool.Put(sha) + // The actual bits to flip + v1 := byte(1 << (hashbuf[1] & 0x7)) + v2 := byte(1 << (hashbuf[3] & 0x7)) + v3 := byte(1 << (hashbuf[5] & 0x7)) + // The indices for the bytes to OR in + i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 + i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1 + i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 + + return i1, v1, i2, v2, i3, v3 +} + +// BloomLookup is a convenience-method to check presence in the bloom filter +func BloomLookup(bin Bloom, topic bytesBacked) bool { + return bin.Test(topic.Bytes()) +} diff --git a/core/types/bloom9_test.go b/core/types/bloom9_test.go new file mode 100644 index 0000000..d3178d1 --- /dev/null +++ b/core/types/bloom9_test.go @@ -0,0 +1,155 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestBloom(t *testing.T) { + positive := []string{ + "testtest", + "test", + "hallo", + "other", + } + negative := []string{ + "tes", + "lo", + } + + var bloom Bloom + for _, data := range positive { + bloom.Add([]byte(data)) + } + + for _, data := range positive { + if !bloom.Test([]byte(data)) { + t.Error("expected", data, "to test true") + } + } + for _, data := range negative { + if bloom.Test([]byte(data)) { + t.Error("did not expect", data, "to test true") + } + } +} + +// TestBloomExtensively does some more thorough tests +func TestBloomExtensively(t *testing.T) { + var exp = common.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263") + var b Bloom + // Add 100 "random" things + for i := 0; i < 100; i++ { + data := fmt.Sprintf("xxxxxxxxxx data %d yyyyyyyyyyyyyy", i) + b.Add([]byte(data)) + //b.Add(new(big.Int).SetBytes([]byte(data))) + } + got := crypto.Keccak256Hash(b.Bytes()) + if got != exp { + t.Errorf("Got %x, exp %x", got, exp) + } + var b2 Bloom + b2.SetBytes(b.Bytes()) + got2 := crypto.Keccak256Hash(b2.Bytes()) + if got != got2 { + t.Errorf("Got %x, exp %x", got, got2) + } +} + +func BenchmarkBloom9(b *testing.B) { + test := []byte("testestestest") + for i := 0; i < b.N; i++ { + Bloom9(test) + } +} + +func BenchmarkBloom9Lookup(b *testing.B) { + toTest := []byte("testtest") + bloom := new(Bloom) + for i := 0; i < b.N; i++ { + bloom.Test(toTest) + } +} + +func BenchmarkCreateBloom(b *testing.B) { + var txs = Transactions{ + NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil), + NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil), + } + var rSmall = Receipts{ + &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x11})}, + {Address: common.BytesToAddress([]byte{0x01, 0x11})}, + }, + TxHash: txs[0].Hash(), + ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 1, + }, + &Receipt{ + PostState: common.Hash{2}.Bytes(), + CumulativeGasUsed: 3, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x22})}, + {Address: common.BytesToAddress([]byte{0x02, 0x22})}, + }, + TxHash: txs[1].Hash(), + ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), + GasUsed: 2, + }, + } + + var rLarge = make(Receipts, 200) + // Fill it with 200 receipts x 2 logs + for i := 0; i < 200; i += 2 { + copy(rLarge[i:], rSmall) + } + b.Run("small", func(b *testing.B) { + b.ReportAllocs() + var bl Bloom + for i := 0; i < b.N; i++ { + bl = CreateBloom(rSmall) + } + b.StopTimer() + var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) + if got != exp { + b.Errorf("Got %x, exp %x", got, exp) + } + }) + b.Run("large", func(b *testing.B) { + b.ReportAllocs() + var bl Bloom + for i := 0; i < b.N; i++ { + bl = CreateBloom(rLarge) + } + b.StopTimer() + var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) + if got != exp { + b.Errorf("Got %x, exp %x", got, exp) + } + }) +} diff --git a/core/types/gen_access_tuple.go b/core/types/gen_access_tuple.go new file mode 100644 index 0000000..d740b70 --- /dev/null +++ b/core/types/gen_access_tuple.go @@ -0,0 +1,43 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" +) + +// MarshalJSON marshals as JSON. +func (a AccessTuple) MarshalJSON() ([]byte, error) { + type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` + } + var enc AccessTuple + enc.Address = a.Address + enc.StorageKeys = a.StorageKeys + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *AccessTuple) UnmarshalJSON(input []byte) error { + type AccessTuple struct { + Address *common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` + } + var dec AccessTuple + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Address == nil { + return errors.New("missing required field 'address' for AccessTuple") + } + a.Address = *dec.Address + if dec.StorageKeys == nil { + return errors.New("missing required field 'storageKeys' for AccessTuple") + } + a.StorageKeys = dec.StorageKeys + return nil +} diff --git a/core/types/gen_account.go b/core/types/gen_account.go new file mode 100644 index 0000000..4e47589 --- /dev/null +++ b/core/types/gen_account.go @@ -0,0 +1,73 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ = (*accountMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (a Account) MarshalJSON() ([]byte, error) { + type Account struct { + Code hexutil.Bytes `json:"code,omitempty"` + Storage map[storageJSON]storageJSON `json:"storage,omitempty"` + Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` + Nonce math.HexOrDecimal64 `json:"nonce,omitempty"` + PrivateKey hexutil.Bytes `json:"secretKey,omitempty"` + } + var enc Account + enc.Code = a.Code + if a.Storage != nil { + enc.Storage = make(map[storageJSON]storageJSON, len(a.Storage)) + for k, v := range a.Storage { + enc.Storage[storageJSON(k)] = storageJSON(v) + } + } + enc.Balance = (*math.HexOrDecimal256)(a.Balance) + enc.Nonce = math.HexOrDecimal64(a.Nonce) + enc.PrivateKey = a.PrivateKey + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *Account) UnmarshalJSON(input []byte) error { + type Account struct { + Code *hexutil.Bytes `json:"code,omitempty"` + Storage map[storageJSON]storageJSON `json:"storage,omitempty"` + Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` + Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"` + PrivateKey *hexutil.Bytes `json:"secretKey,omitempty"` + } + var dec Account + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Code != nil { + a.Code = *dec.Code + } + if dec.Storage != nil { + a.Storage = make(map[common.Hash]common.Hash, len(dec.Storage)) + for k, v := range dec.Storage { + a.Storage[common.Hash(k)] = common.Hash(v) + } + } + if dec.Balance == nil { + return errors.New("missing required field 'balance' for Account") + } + a.Balance = (*big.Int)(dec.Balance) + if dec.Nonce != nil { + a.Nonce = uint64(*dec.Nonce) + } + if dec.PrivateKey != nil { + a.PrivateKey = *dec.PrivateKey + } + return nil +} diff --git a/core/types/gen_account_rlp.go b/core/types/gen_account_rlp.go new file mode 100644 index 0000000..8b42449 --- /dev/null +++ b/core/types/gen_account_rlp.go @@ -0,0 +1,21 @@ +// Code generated by rlpgen. DO NOT EDIT. + +package types + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *StateAccount) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteUint64(obj.Nonce) + if obj.Balance == nil { + w.Write(rlp.EmptyString) + } else { + w.WriteUint256(obj.Balance) + } + w.WriteBytes(obj.Root[:]) + w.WriteBytes(obj.CodeHash) + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go new file mode 100644 index 0000000..fb1f915 --- /dev/null +++ b/core/types/gen_header_json.go @@ -0,0 +1,167 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*headerMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (h Header) MarshalJSON() ([]byte, error) { + type Header struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` + ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` + Hash common.Hash `json:"hash"` + } + var enc Header + enc.ParentHash = h.ParentHash + enc.UncleHash = h.UncleHash + enc.Coinbase = h.Coinbase + enc.Root = h.Root + enc.TxHash = h.TxHash + enc.ReceiptHash = h.ReceiptHash + enc.Bloom = h.Bloom + enc.Difficulty = (*hexutil.Big)(h.Difficulty) + enc.Number = (*hexutil.Big)(h.Number) + enc.GasLimit = hexutil.Uint64(h.GasLimit) + enc.GasUsed = hexutil.Uint64(h.GasUsed) + enc.Time = hexutil.Uint64(h.Time) + enc.Extra = h.Extra + enc.MixDigest = h.MixDigest + enc.Nonce = h.Nonce + enc.BaseFee = (*hexutil.Big)(h.BaseFee) + 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) +} + +// UnmarshalJSON unmarshals from JSON. +func (h *Header) UnmarshalJSON(input []byte) error { + type Header struct { + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase *common.Address `json:"miner"` + Root *common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom *Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest *common.Hash `json:"mixHash"` + Nonce *BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` + ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` + } + var dec Header + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ParentHash == nil { + return errors.New("missing required field 'parentHash' for Header") + } + h.ParentHash = *dec.ParentHash + if dec.UncleHash == nil { + return errors.New("missing required field 'sha3Uncles' for Header") + } + h.UncleHash = *dec.UncleHash + if dec.Coinbase != nil { + h.Coinbase = *dec.Coinbase + } + if dec.Root == nil { + return errors.New("missing required field 'stateRoot' for Header") + } + h.Root = *dec.Root + if dec.TxHash == nil { + return errors.New("missing required field 'transactionsRoot' for Header") + } + h.TxHash = *dec.TxHash + if dec.ReceiptHash == nil { + return errors.New("missing required field 'receiptsRoot' for Header") + } + h.ReceiptHash = *dec.ReceiptHash + if dec.Bloom == nil { + return errors.New("missing required field 'logsBloom' for Header") + } + h.Bloom = *dec.Bloom + if dec.Difficulty == nil { + return errors.New("missing required field 'difficulty' for Header") + } + h.Difficulty = (*big.Int)(dec.Difficulty) + if dec.Number == nil { + return errors.New("missing required field 'number' for Header") + } + h.Number = (*big.Int)(dec.Number) + if dec.GasLimit == nil { + return errors.New("missing required field 'gasLimit' for Header") + } + h.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed == nil { + return errors.New("missing required field 'gasUsed' for Header") + } + h.GasUsed = uint64(*dec.GasUsed) + if dec.Time == nil { + return errors.New("missing required field 'timestamp' for Header") + } + h.Time = uint64(*dec.Time) + if dec.Extra == nil { + return errors.New("missing required field 'extraData' for Header") + } + h.Extra = *dec.Extra + if dec.MixDigest != nil { + h.MixDigest = *dec.MixDigest + } + if dec.Nonce != nil { + h.Nonce = *dec.Nonce + } + if dec.BaseFee != nil { + h.BaseFee = (*big.Int)(dec.BaseFee) + } + 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.ParentBeaconRoot != nil { + h.ParentBeaconRoot = dec.ParentBeaconRoot + } + return nil +} diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go new file mode 100644 index 0000000..ed6a1a0 --- /dev/null +++ b/core/types/gen_header_rlp.go @@ -0,0 +1,85 @@ +// Code generated by rlpgen. DO NOT EDIT. + +package types + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Header) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteBytes(obj.ParentHash[:]) + w.WriteBytes(obj.UncleHash[:]) + w.WriteBytes(obj.Coinbase[:]) + w.WriteBytes(obj.Root[:]) + w.WriteBytes(obj.TxHash[:]) + w.WriteBytes(obj.ReceiptHash[:]) + w.WriteBytes(obj.Bloom[:]) + if obj.Difficulty == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Difficulty.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.Difficulty) + } + if obj.Number == nil { + w.Write(rlp.EmptyString) + } else { + if obj.Number.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.Number) + } + w.WriteUint64(obj.GasLimit) + w.WriteUint64(obj.GasUsed) + w.WriteUint64(obj.Time) + w.WriteBytes(obj.Extra) + w.WriteBytes(obj.MixDigest[:]) + w.WriteBytes(obj.Nonce[:]) + _tmp1 := obj.BaseFee != nil + _tmp2 := obj.WithdrawalsHash != nil + _tmp3 := obj.BlobGasUsed != nil + _tmp4 := obj.ExcessBlobGas != nil + _tmp5 := obj.ParentBeaconRoot != nil + if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 { + if obj.BaseFee == nil { + w.Write(rlp.EmptyString) + } else { + if obj.BaseFee.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.BaseFee) + } + } + if _tmp2 || _tmp3 || _tmp4 || _tmp5 { + if obj.WithdrawalsHash == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes(obj.WithdrawalsHash[:]) + } + } + if _tmp3 || _tmp4 || _tmp5 { + if obj.BlobGasUsed == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64((*obj.BlobGasUsed)) + } + } + if _tmp4 || _tmp5 { + if obj.ExcessBlobGas == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64((*obj.ExcessBlobGas)) + } + } + if _tmp5 { + if obj.ParentBeaconRoot == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes(obj.ParentBeaconRoot[:]) + } + } + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/gen_log_json.go b/core/types/gen_log_json.go new file mode 100644 index 0000000..3ffa9c2 --- /dev/null +++ b/core/types/gen_log_json.go @@ -0,0 +1,90 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*logMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (l Log) MarshalJSON() ([]byte, error) { + type Log struct { + 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" 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 + enc.Topics = l.Topics + enc.Data = l.Data + enc.BlockNumber = hexutil.Uint64(l.BlockNumber) + enc.TxHash = l.TxHash + enc.TxIndex = hexutil.Uint(l.TxIndex) + enc.BlockHash = l.BlockHash + enc.Index = hexutil.Uint(l.Index) + enc.Removed = l.Removed + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (l *Log) UnmarshalJSON(input []byte) error { + type Log struct { + 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" 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 { + return err + } + if dec.Address == nil { + return errors.New("missing required field 'address' for Log") + } + l.Address = *dec.Address + if dec.Topics == nil { + return errors.New("missing required field 'topics' for Log") + } + l.Topics = dec.Topics + if dec.Data == nil { + return errors.New("missing required field 'data' for Log") + } + l.Data = *dec.Data + if dec.BlockNumber != nil { + l.BlockNumber = uint64(*dec.BlockNumber) + } + if dec.TxHash == nil { + return errors.New("missing required field 'transactionHash' for Log") + } + l.TxHash = *dec.TxHash + if dec.TxIndex != nil { + l.TxIndex = uint(*dec.TxIndex) + } + if dec.BlockHash != nil { + l.BlockHash = *dec.BlockHash + } + if dec.Index != nil { + l.Index = uint(*dec.Index) + } + if dec.Removed != nil { + l.Removed = *dec.Removed + } + return nil +} diff --git a/core/types/gen_log_rlp.go b/core/types/gen_log_rlp.go new file mode 100644 index 0000000..7e89629 --- /dev/null +++ b/core/types/gen_log_rlp.go @@ -0,0 +1,20 @@ +// Code generated by rlpgen. DO NOT EDIT. + +package types + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Log) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteBytes(obj.Address[:]) + _tmp1 := w.List() + for _, _tmp2 := range obj.Topics { + w.WriteBytes(_tmp2[:]) + } + w.ListEnd(_tmp1) + w.WriteBytes(obj.Data) + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go new file mode 100644 index 0000000..4c641a9 --- /dev/null +++ b/core/types/gen_receipt_json.go @@ -0,0 +1,128 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*receiptMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (r Receipt) MarshalJSON() ([]byte, error) { + type Receipt struct { + Type hexutil.Uint64 `json:"type,omitempty"` + PostState hexutil.Bytes `json:"root"` + Status hexutil.Uint64 `json:"status"` + CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Logs []*Log `json:"logs" gencodec:"required"` + TxHash common.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress common.Address `json:"contractAddress"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"` + BlobGasUsed hexutil.Uint64 `json:"blobGasUsed,omitempty"` + BlobGasPrice *hexutil.Big `json:"blobGasPrice,omitempty"` + BlockHash common.Hash `json:"blockHash,omitempty"` + BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` + TransactionIndex hexutil.Uint `json:"transactionIndex"` + } + var enc Receipt + enc.Type = hexutil.Uint64(r.Type) + enc.PostState = r.PostState + enc.Status = hexutil.Uint64(r.Status) + enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed) + enc.Bloom = r.Bloom + enc.Logs = r.Logs + enc.TxHash = r.TxHash + enc.ContractAddress = r.ContractAddress + enc.GasUsed = hexutil.Uint64(r.GasUsed) + enc.EffectiveGasPrice = (*hexutil.Big)(r.EffectiveGasPrice) + enc.BlobGasUsed = hexutil.Uint64(r.BlobGasUsed) + enc.BlobGasPrice = (*hexutil.Big)(r.BlobGasPrice) + enc.BlockHash = r.BlockHash + enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) + enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (r *Receipt) UnmarshalJSON(input []byte) error { + type Receipt struct { + Type *hexutil.Uint64 `json:"type,omitempty"` + PostState *hexutil.Bytes `json:"root"` + Status *hexutil.Uint64 `json:"status"` + CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` + Bloom *Bloom `json:"logsBloom" gencodec:"required"` + Logs []*Log `json:"logs" gencodec:"required"` + TxHash *common.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress *common.Address `json:"contractAddress"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed,omitempty"` + BlobGasPrice *hexutil.Big `json:"blobGasPrice,omitempty"` + BlockHash *common.Hash `json:"blockHash,omitempty"` + BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` + TransactionIndex *hexutil.Uint `json:"transactionIndex"` + } + var dec Receipt + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Type != nil { + r.Type = uint8(*dec.Type) + } + if dec.PostState != nil { + r.PostState = *dec.PostState + } + if dec.Status != nil { + r.Status = uint64(*dec.Status) + } + if dec.CumulativeGasUsed == nil { + return errors.New("missing required field 'cumulativeGasUsed' for Receipt") + } + r.CumulativeGasUsed = uint64(*dec.CumulativeGasUsed) + if dec.Bloom == nil { + return errors.New("missing required field 'logsBloom' for Receipt") + } + r.Bloom = *dec.Bloom + if dec.Logs == nil { + return errors.New("missing required field 'logs' for Receipt") + } + r.Logs = dec.Logs + if dec.TxHash == nil { + return errors.New("missing required field 'transactionHash' for Receipt") + } + r.TxHash = *dec.TxHash + if dec.ContractAddress != nil { + r.ContractAddress = *dec.ContractAddress + } + if dec.GasUsed == nil { + return errors.New("missing required field 'gasUsed' for Receipt") + } + r.GasUsed = uint64(*dec.GasUsed) + if dec.EffectiveGasPrice != nil { + r.EffectiveGasPrice = (*big.Int)(dec.EffectiveGasPrice) + } + if dec.BlobGasUsed != nil { + r.BlobGasUsed = uint64(*dec.BlobGasUsed) + } + if dec.BlobGasPrice != nil { + r.BlobGasPrice = (*big.Int)(dec.BlobGasPrice) + } + if dec.BlockHash != nil { + r.BlockHash = *dec.BlockHash + } + if dec.BlockNumber != nil { + r.BlockNumber = (*big.Int)(dec.BlockNumber) + } + if dec.TransactionIndex != nil { + r.TransactionIndex = uint(*dec.TransactionIndex) + } + return nil +} diff --git a/core/types/gen_withdrawal_json.go b/core/types/gen_withdrawal_json.go new file mode 100644 index 0000000..983a7f7 --- /dev/null +++ b/core/types/gen_withdrawal_json.go @@ -0,0 +1,55 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*withdrawalMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (w Withdrawal) MarshalJSON() ([]byte, error) { + type Withdrawal struct { + Index hexutil.Uint64 `json:"index"` + Validator hexutil.Uint64 `json:"validatorIndex"` + Address common.Address `json:"address"` + Amount hexutil.Uint64 `json:"amount"` + } + var enc Withdrawal + enc.Index = hexutil.Uint64(w.Index) + enc.Validator = hexutil.Uint64(w.Validator) + enc.Address = w.Address + enc.Amount = hexutil.Uint64(w.Amount) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (w *Withdrawal) UnmarshalJSON(input []byte) error { + type Withdrawal struct { + Index *hexutil.Uint64 `json:"index"` + Validator *hexutil.Uint64 `json:"validatorIndex"` + Address *common.Address `json:"address"` + Amount *hexutil.Uint64 `json:"amount"` + } + var dec Withdrawal + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Index != nil { + w.Index = uint64(*dec.Index) + } + if dec.Validator != nil { + w.Validator = uint64(*dec.Validator) + } + if dec.Address != nil { + w.Address = *dec.Address + } + if dec.Amount != nil { + w.Amount = uint64(*dec.Amount) + } + return nil +} diff --git a/core/types/gen_withdrawal_rlp.go b/core/types/gen_withdrawal_rlp.go new file mode 100644 index 0000000..6a97c04 --- /dev/null +++ b/core/types/gen_withdrawal_rlp.go @@ -0,0 +1,17 @@ +// Code generated by rlpgen. DO NOT EDIT. + +package types + +import "github.com/ethereum/go-ethereum/rlp" +import "io" + +func (obj *Withdrawal) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + _tmp0 := w.List() + w.WriteUint64(obj.Index) + w.WriteUint64(obj.Validator) + w.WriteBytes(obj.Address[:]) + w.WriteUint64(obj.Amount) + w.ListEnd(_tmp0) + return w.Flush() +} diff --git a/core/types/hashes.go b/core/types/hashes.go new file mode 100644 index 0000000..43e9130 --- /dev/null +++ b/core/types/hashes.go @@ -0,0 +1,56 @@ +// 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 types + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" +) + +var ( + // 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. + EmptyUncleHash = rlpHash([]*Header(nil)) // 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 + + // EmptyCodeHash is the known hash of the empty EVM bytecode. + EmptyCodeHash = crypto.Keccak256Hash(nil) // c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 + + // EmptyTxsHash is the known hash of the empty transaction set. + EmptyTxsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // EmptyReceiptsHash is the known hash of the empty receipt set. + EmptyReceiptsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // 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 +// emptyHash one instead. +func TrieRootHash(hash common.Hash) common.Hash { + if hash == (common.Hash{}) { + log.Error("Zero trie root hash!") + return EmptyRootHash + } + return hash +} diff --git a/core/types/hashing.go b/core/types/hashing.go new file mode 100644 index 0000000..224d7a8 --- /dev/null +++ b/core/types/hashing.go @@ -0,0 +1,134 @@ +// 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 types + +import ( + "bytes" + "fmt" + "math" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +// hasherPool holds LegacyKeccak256 hashers for rlpHash. +var hasherPool = sync.Pool{ + New: func() interface{} { return sha3.NewLegacyKeccak256() }, +} + +// encodeBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. +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) + defer hasherPool.Put(sha) + sha.Reset() + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +// prefixedRlpHash writes the prefix into the hasher before rlp-encoding x. +// It's used for typed transactions. +func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + sha.Write([]byte{prefix}) + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +// TrieHasher is the tool used to calculate the hash of derivable list. +// This is internal, do not use. +type TrieHasher interface { + Reset() + Update([]byte, []byte) error + Hash() common.Hash +} + +// DerivableList is the input to DeriveSha. +// It is implemented by the 'Transactions' and 'Receipts' types. +// This is internal, do not use these methods. +type DerivableList interface { + Len() int + EncodeIndex(int, *bytes.Buffer) +} + +func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { + buf.Reset() + list.EncodeIndex(i, buf) + // 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()) +} + +// DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header. +func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { + hasher.Reset() + + valueBuf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(valueBuf) + + // StackTrie requires values to be inserted in increasing hash order, which is not the + // order that `list` provides hashes in. This insertion sequence ensures that the + // order is correct. + // + // The error returned by hasher is omitted because hasher will produce an incorrect + // hash in case any error occurs. + var indexBuf []byte + for i := 1; i < list.Len() && i <= 0x7f; i++ { + indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) + value := encodeForDerive(list, i, valueBuf) + hasher.Update(indexBuf, value) + } + if list.Len() > 0 { + indexBuf = rlp.AppendUint64(indexBuf[:0], 0) + value := encodeForDerive(list, 0, valueBuf) + hasher.Update(indexBuf, value) + } + for i := 0x80; i < list.Len(); i++ { + indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) + value := encodeForDerive(list, i, valueBuf) + hasher.Update(indexBuf, value) + } + return hasher.Hash() +} diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go new file mode 100644 index 0000000..a694941 --- /dev/null +++ b/core/types/hashing_test.go @@ -0,0 +1,231 @@ +// 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 types_test + +import ( + "bytes" + "fmt" + "io" + "math/big" + mrand "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +func TestDeriveSha(t *testing.T) { + txs, err := genTxs(0) + if err != nil { + t.Fatal(err) + } + for len(txs) < 1000 { + exp := types.DeriveSha(txs, trie.NewEmpty(triedb.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) + } + newTxs, err := genTxs(uint64(len(txs) + 1)) + if err != nil { + t.Fatal(err) + } + txs = append(txs, newTxs...) + } +} + +// TestEIP2718DeriveSha tests that the input to the DeriveSha function is correct. +func TestEIP2718DeriveSha(t *testing.T) { + for _, tc := range []struct { + rlpData string + exp string + }{ + { + rlpData: "0xb8a701f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9", + exp: "01 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n80 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n", + }, + } { + d := &hashToHumanReadable{} + var t1, t2 types.Transaction + rlp.DecodeBytes(common.FromHex(tc.rlpData), &t1) + rlp.DecodeBytes(common.FromHex(tc.rlpData), &t2) + txs := types.Transactions{&t1, &t2} + types.DeriveSha(txs, d) + if tc.exp != string(d.data) { + t.Fatalf("Want\n%v\nhave:\n%v", tc.exp, string(d.data)) + } + } +} + +func BenchmarkDeriveSha200(b *testing.B) { + txs, err := genTxs(200) + if err != nil { + b.Fatal(err) + } + var exp common.Hash + var got common.Hash + b.Run("std_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + } + }) + + b.Run("stack_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + got = types.DeriveSha(txs, trie.NewStackTrie(nil)) + } + }) + if got != exp { + b.Errorf("got %x exp %x", got, exp) + } +} + +func TestFuzzDeriveSha(t *testing.T) { + // increase this for longer runs -- it's set to quite low for travis + rndSeed := mrand.Int() + for i := 0; i < 10; i++ { + seed := rndSeed + i + exp := types.DeriveSha(newDummy(i), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + printList(newDummy(seed)) + t.Fatalf("seed %d: got %x exp %x", seed, got, exp) + } + } +} + +// TestDerivableList contains testcases found via fuzzing +func TestDerivableList(t *testing.T) { + type tcase []string + tcs := []tcase{ + { + "0xc041", + }, + { + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + }, + { + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + "0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a", + "0x14abd5c47c0be87b0454596baad2", + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + }, + } + for i, tc := range tcs[1:] { + exp := types.DeriveSha(flatList(tc), trie.NewEmpty(triedb.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) + } + } +} + +func genTxs(num uint64) (types.Transactions, error) { + key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + if err != nil { + return nil, err + } + var addr = crypto.PubkeyToAddress(key.PublicKey) + newTx := func(i uint64) (*types.Transaction, error) { + signer := types.NewEIP155Signer(big.NewInt(18)) + utx := types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil) + tx, err := types.SignTx(utx, signer, key) + return tx, err + } + var txs types.Transactions + for i := uint64(0); i < num; i++ { + tx, err := newTx(i) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + return txs, nil +} + +type dummyDerivableList struct { + len int + seed int +} + +func newDummy(seed int) *dummyDerivableList { + d := &dummyDerivableList{} + src := mrand.NewSource(int64(seed)) + // don't use lists longer than 4K items + d.len = int(src.Int63() & 0x0FFF) + d.seed = seed + return d +} + +func (d *dummyDerivableList) Len() int { + return d.len +} + +func (d *dummyDerivableList) EncodeIndex(i int, w *bytes.Buffer) { + src := mrand.NewSource(int64(d.seed + i)) + // max item size 256, at least 1 byte per item + size := 1 + src.Int63()&0x00FF + io.CopyN(w, mrand.New(src), size) +} + +func printList(l types.DerivableList) { + fmt.Printf("list length: %d\n", l.Len()) + fmt.Printf("{\n") + for i := 0; i < l.Len(); i++ { + var buf bytes.Buffer + l.EncodeIndex(i, &buf) + fmt.Printf("\"%#x\",\n", buf.Bytes()) + } + fmt.Printf("},\n") +} + +type flatList []string + +func (f flatList) Len() int { + return len(f) +} +func (f flatList) EncodeIndex(i int, w *bytes.Buffer) { + w.Write(hexutil.MustDecode(f[i])) +} + +type hashToHumanReadable struct { + data []byte +} + +func (d *hashToHumanReadable) Reset() { + d.data = make([]byte, 0) +} + +func (d *hashToHumanReadable) Update(i []byte, i2 []byte) error { + l := fmt.Sprintf("%x %x\n", i, i2) + d.data = append(d.data, []byte(l)...) + return nil +} + +func (d *hashToHumanReadable) Hash() common.Hash { + return common.Hash{} +} diff --git a/core/types/log.go b/core/types/log.go new file mode 100644 index 0000000..54c7ff6 --- /dev/null +++ b/core/types/log.go @@ -0,0 +1,61 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +//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 +// stored/indexed by the node. +type Log struct { + // Consensus fields: + // address of the contract that generated the event + Address common.Address `json:"address" gencodec:"required"` + // list of topics provided by the contract. + Topics []common.Hash `json:"topics" gencodec:"required"` + // supplied by the contract, usually ABI-encoded + Data []byte `json:"data" gencodec:"required"` + + // 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" rlp:"-"` + // hash of the transaction + TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` + // index of the transaction in the block + TxIndex uint `json:"transactionIndex" rlp:"-"` + // hash of the block in which the transaction was included + BlockHash common.Hash `json:"blockHash" rlp:"-"` + // index of the log in the block + 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" rlp:"-"` +} + +type logMarshaling struct { + Data hexutil.Bytes + BlockNumber hexutil.Uint64 + TxIndex hexutil.Uint + Index hexutil.Uint +} diff --git a/core/types/log_test.go b/core/types/log_test.go new file mode 100644 index 0000000..02eef3e --- /dev/null +++ b/core/types/log_test.go @@ -0,0 +1,132 @@ +// 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 types + +import ( + "encoding/json" + "errors" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var unmarshalLogTests = map[string]struct { + input string + want *Log + wantError error +}{ + "ok": { + input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x000000000000000000000000000000000000000000000001a055690d9db80000","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, + want: &Log{ + Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"), + BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"), + BlockNumber: 2019236, + Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000001a055690d9db80000"), + Index: 2, + TxIndex: 3, + TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"), + Topics: []common.Hash{ + common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"), + }, + }, + }, + "empty data": { + input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, + want: &Log{ + Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"), + BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"), + BlockNumber: 2019236, + Data: []byte{}, + Index: 2, + TxIndex: 3, + TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"), + Topics: []common.Hash{ + common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"), + }, + }, + }, + "missing block fields (pending logs)": { + input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","data":"0x","logIndex":"0x0","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, + want: &Log{ + Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"), + BlockHash: common.Hash{}, + BlockNumber: 0, + Data: []byte{}, + Index: 0, + TxIndex: 3, + TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"), + Topics: []common.Hash{ + common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + }, + }, + }, + "Removed: true": { + input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3","removed":true}`, + want: &Log{ + Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"), + BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"), + BlockNumber: 2019236, + Data: []byte{}, + Index: 2, + TxIndex: 3, + TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"), + Topics: []common.Hash{ + common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + }, + Removed: true, + }, + }, + "missing data": { + input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, + wantError: errors.New("missing required field 'data' for Log"), + }, +} + +func TestUnmarshalLog(t *testing.T) { + dumper := spew.ConfigState{DisableMethods: true, Indent: " "} + for name, test := range unmarshalLogTests { + var log *Log + err := json.Unmarshal([]byte(test.input), &log) + checkError(t, name, err, test.wantError) + if test.wantError == nil && err == nil { + if !reflect.DeepEqual(log, test.want) { + t.Errorf("test %q:\nGOT %sWANT %s", name, dumper.Sdump(log), dumper.Sdump(test.want)) + } + } + } +} + +func checkError(t *testing.T, testname string, got, want error) bool { + if got == nil { + if want != nil { + t.Errorf("test %q: got no error, want %q", testname, want) + return false + } + return true + } + if want == nil { + t.Errorf("test %q: unexpected error %q", testname, got) + } else if got.Error() != want.Error() { + t.Errorf("test %q: got error %q, want %q", testname, got, want) + } + return false +} diff --git a/core/types/receipt.go b/core/types/receipt.go new file mode 100644 index 0000000..4f96fde --- /dev/null +++ b/core/types/receipt.go @@ -0,0 +1,377 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/big" + "unsafe" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +//go:generate go run github.com/fjl/gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go + +var ( + receiptStatusFailedRLP = []byte{} + receiptStatusSuccessfulRLP = []byte{0x01} +) + +var errShortTypedReceipt = errors.New("typed receipt too short") + +const ( + // ReceiptStatusFailed is the status code of a transaction if execution failed. + ReceiptStatusFailed = uint64(0) + + // ReceiptStatusSuccessful is the status code of a transaction if execution succeeded. + ReceiptStatusSuccessful = uint64(1) +) + +// Receipt represents the results of a transaction. +type Receipt struct { + // Consensus fields: These fields are defined by the Yellow Paper + Type uint8 `json:"type,omitempty"` + PostState []byte `json:"root"` + Status uint64 `json:"status"` + CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Logs []*Log `json:"logs" gencodec:"required"` + + // Implementation fields: These fields are added by geth when processing a transaction. + TxHash common.Hash `json:"transactionHash" gencodec:"required"` + ContractAddress common.Address `json:"contractAddress"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + EffectiveGasPrice *big.Int `json:"effectiveGasPrice"` // required, but tag omitted for backwards compatibility + BlobGasUsed uint64 `json:"blobGasUsed,omitempty"` + BlobGasPrice *big.Int `json:"blobGasPrice,omitempty"` + + // Inclusion information: These fields provide information about the inclusion of the + // transaction corresponding to this receipt. + BlockHash common.Hash `json:"blockHash,omitempty"` + BlockNumber *big.Int `json:"blockNumber,omitempty"` + TransactionIndex uint `json:"transactionIndex"` +} + +type receiptMarshaling struct { + Type hexutil.Uint64 + PostState hexutil.Bytes + Status hexutil.Uint64 + CumulativeGasUsed hexutil.Uint64 + GasUsed hexutil.Uint64 + EffectiveGasPrice *hexutil.Big + BlobGasUsed hexutil.Uint64 + BlobGasPrice *hexutil.Big + BlockNumber *hexutil.Big + TransactionIndex hexutil.Uint +} + +// receiptRLP is the consensus encoding of a receipt. +type receiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Bloom Bloom + Logs []*Log +} + +// storedReceiptRLP is the storage encoding of a receipt. +type storedReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Logs []*Log +} + +// NewReceipt creates a barebone transaction receipt, copying the init fields. +// Deprecated: create receipts using a struct literal instead. +func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt { + r := &Receipt{ + Type: LegacyTxType, + PostState: common.CopyBytes(root), + CumulativeGasUsed: cumulativeGasUsed, + } + if failed { + r.Status = ReceiptStatusFailed + } else { + r.Status = ReceiptStatusSuccessful + } + return r +} + +// EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt +// into an RLP stream. If no post state is present, byzantium fork is assumed. +func (r *Receipt) EncodeRLP(w io.Writer) error { + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + if r.Type == LegacyTxType { + return rlp.Encode(w, data) + } + buf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(buf) + buf.Reset() + if err := r.encodeTyped(data, buf); err != nil { + return err + } + return rlp.Encode(w, buf.Bytes()) +} + +// encodeTyped writes the canonical encoding of a typed receipt to w. +func (r *Receipt) encodeTyped(data *receiptRLP, w *bytes.Buffer) error { + w.WriteByte(r.Type) + return rlp.Encode(w, data) +} + +// MarshalBinary returns the consensus encoding of the receipt. +func (r *Receipt) MarshalBinary() ([]byte, error) { + if r.Type == LegacyTxType { + return rlp.EncodeToBytes(r) + } + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + var buf bytes.Buffer + err := r.encodeTyped(data, &buf) + return buf.Bytes(), err +} + +// 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, size, err := s.Kind() + switch { + case err != nil: + return err + case kind == rlp.List: + // It's a legacy receipt. + var dec receiptRLP + if err := s.Decode(&dec); err != nil { + return err + } + r.Type = LegacyTxType + return r.setFromRLP(dec) + case kind == rlp.Byte: + return errShortTypedReceipt + default: + // It's an EIP-2718 typed tx receipt. + 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) + } +} + +// UnmarshalBinary decodes the consensus encoding of receipts. +// It supports legacy RLP receipts and EIP-2718 typed receipts. +func (r *Receipt) UnmarshalBinary(b []byte) error { + if len(b) > 0 && b[0] > 0x7f { + // It's a legacy receipt decode the RLP + var data receiptRLP + err := rlp.DecodeBytes(b, &data) + if err != nil { + return err + } + r.Type = LegacyTxType + return r.setFromRLP(data) + } + // It's an EIP2718 typed transaction envelope. + return r.decodeTyped(b) +} + +// decodeTyped decodes a typed receipt from the canonical format. +func (r *Receipt) decodeTyped(b []byte) error { + if len(b) <= 1 { + return errShortTypedReceipt + } + switch b[0] { + case DynamicFeeTxType, AccessListTxType, BlobTxType: + var data receiptRLP + err := rlp.DecodeBytes(b[1:], &data) + if err != nil { + return err + } + r.Type = b[0] + return r.setFromRLP(data) + default: + return ErrTxTypeNotSupported + } +} + +func (r *Receipt) setFromRLP(data receiptRLP) error { + r.CumulativeGasUsed, r.Bloom, r.Logs = data.CumulativeGasUsed, data.Bloom, data.Logs + return r.setStatus(data.PostStateOrStatus) +} + +func (r *Receipt) setStatus(postStateOrStatus []byte) error { + switch { + case bytes.Equal(postStateOrStatus, receiptStatusSuccessfulRLP): + r.Status = ReceiptStatusSuccessful + case bytes.Equal(postStateOrStatus, receiptStatusFailedRLP): + r.Status = ReceiptStatusFailed + case len(postStateOrStatus) == len(common.Hash{}): + r.PostState = postStateOrStatus + default: + return fmt.Errorf("invalid receipt status %x", postStateOrStatus) + } + return nil +} + +func (r *Receipt) statusEncoding() []byte { + if len(r.PostState) == 0 { + if r.Status == ReceiptStatusFailed { + return receiptStatusFailedRLP + } + return receiptStatusSuccessfulRLP + } + return r.PostState +} + +// Size returns the approximate memory used by all internal contents. It is used +// to approximate and limit the memory consumption of various caches. +func (r *Receipt) Size() common.StorageSize { + size := common.StorageSize(unsafe.Sizeof(*r)) + common.StorageSize(len(r.PostState)) + size += common.StorageSize(len(r.Logs)) * common.StorageSize(unsafe.Sizeof(Log{})) + for _, log := range r.Logs { + size += common.StorageSize(len(log.Topics)*common.HashLength + len(log.Data)) + } + return size +} + +// ReceiptForStorage is a wrapper around a Receipt with RLP serialization +// that omits the Bloom field and deserialization that re-computes it. +type ReceiptForStorage Receipt + +// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt +// into an RLP stream. +func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + outerList := w.List() + w.WriteBytes((*Receipt)(r).statusEncoding()) + w.WriteUint64(r.CumulativeGasUsed) + logList := w.List() + for _, log := range r.Logs { + if err := log.EncodeRLP(w); err != nil { + return err + } + } + w.ListEnd(logList) + w.ListEnd(outerList) + return w.Flush() +} + +// DecodeRLP implements rlp.Decoder, and loads both consensus and implementation +// fields of a receipt from an RLP stream. +func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { + var stored storedReceiptRLP + if err := s.Decode(&stored); err != nil { + return err + } + if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { + return err + } + r.CumulativeGasUsed = stored.CumulativeGasUsed + r.Logs = stored.Logs + r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) + + return nil +} + +// Receipts implements DerivableList for receipts. +type Receipts []*Receipt + +// Len returns the number of receipts in this list. +func (rs Receipts) Len() int { return len(rs) } + +// EncodeIndex encodes the i'th receipt to w. +func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { + r := rs[i] + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + if r.Type == LegacyTxType { + rlp.Encode(w, data) + return + } + w.WriteByte(r.Type) + switch r.Type { + case AccessListTxType, DynamicFeeTxType, BlobTxType: + rlp.Encode(w, data) + default: + // For unsupported types, write nothing. Since this is for + // DeriveSha, the error will be caught matching the derived hash + // to the block. + } +} + +// DeriveFields fills the receipts with their computed fields based on consensus +// data and contextual infos like containing block and transactions. +func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error { + signer := MakeSigner(config, new(big.Int).SetUint64(number), time) + + logIndex := uint(0) + if len(txs) != len(rs) { + return errors.New("transaction and receipt count mismatch") + } + for i := 0; i < len(rs); i++ { + // The transaction type and hash can be retrieved from the transaction itself + rs[i].Type = txs[i].Type() + rs[i].TxHash = txs[i].Hash() + rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee) + + // EIP-4844 blob transaction fields + if txs[i].Type() == BlobTxType { + rs[i].BlobGasUsed = txs[i].BlobGas() + rs[i].BlobGasPrice = blobGasPrice + } + + // block location fields + rs[i].BlockHash = hash + rs[i].BlockNumber = new(big.Int).SetUint64(number) + rs[i].TransactionIndex = uint(i) + + // The contract address can be derived from the transaction itself + if txs[i].To() == nil { + // Deriving the signer is expensive, only do if it's actually needed + from, _ := Sender(signer, txs[i]) + rs[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) + } else { + rs[i].ContractAddress = common.Address{} + } + + // The used gas can be calculated based on previous r + if i == 0 { + rs[i].GasUsed = rs[i].CumulativeGasUsed + } else { + rs[i].GasUsed = rs[i].CumulativeGasUsed - rs[i-1].CumulativeGasUsed + } + + // The derived log fields can simply be set from the block and transaction + for j := 0; j < len(rs[i].Logs); j++ { + rs[i].Logs[j].BlockNumber = number + rs[i].Logs[j].BlockHash = hash + rs[i].Logs[j].TxHash = rs[i].TxHash + rs[i].Logs[j].TxIndex = uint(i) + rs[i].Logs[j].Index = logIndex + logIndex++ + } + } + return nil +} diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go new file mode 100644 index 0000000..fc51eb1 --- /dev/null +++ b/core/types/receipt_test.go @@ -0,0 +1,529 @@ +// 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" + "encoding/json" + "math" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" + "github.com/kylelemons/godebug/diff" +) + +var ( + legacyReceipt = &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + { + Address: common.BytesToAddress([]byte{0x01, 0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + } + accessListReceipt = &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + { + Address: common.BytesToAddress([]byte{0x01, 0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + Type: AccessListTxType, + } + eip1559Receipt = &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + { + Address: common.BytesToAddress([]byte{0x01, 0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + Type: DynamicFeeTxType, + } + + // Create a few transactions to have receipts for + to2 = common.HexToAddress("0x2") + to3 = common.HexToAddress("0x3") + to4 = common.HexToAddress("0x4") + to5 = common.HexToAddress("0x5") + to6 = common.HexToAddress("0x6") + to7 = common.HexToAddress("0x7") + txs = Transactions{ + NewTx(&LegacyTx{ + Nonce: 1, + Value: big.NewInt(1), + Gas: 1, + GasPrice: big.NewInt(11), + }), + NewTx(&LegacyTx{ + To: &to2, + Nonce: 2, + Value: big.NewInt(2), + Gas: 2, + GasPrice: big.NewInt(22), + }), + NewTx(&AccessListTx{ + To: &to3, + Nonce: 3, + Value: big.NewInt(3), + Gas: 3, + GasPrice: big.NewInt(33), + }), + // EIP-1559 transactions. + NewTx(&DynamicFeeTx{ + To: &to4, + Nonce: 4, + Value: big.NewInt(4), + Gas: 4, + GasTipCap: big.NewInt(44), + GasFeeCap: big.NewInt(1044), + }), + NewTx(&DynamicFeeTx{ + To: &to5, + Nonce: 5, + Value: big.NewInt(5), + Gas: 5, + GasTipCap: big.NewInt(55), + GasFeeCap: big.NewInt(1055), + }), + // EIP-4844 transactions. + NewTx(&BlobTx{ + To: to6, + Nonce: 6, + Value: uint256.NewInt(6), + Gas: 6, + GasTipCap: uint256.NewInt(66), + GasFeeCap: uint256.NewInt(1066), + BlobFeeCap: uint256.NewInt(100066), + BlobHashes: []common.Hash{{}}, + }), + NewTx(&BlobTx{ + To: to7, + Nonce: 7, + Value: uint256.NewInt(7), + Gas: 7, + GasTipCap: uint256.NewInt(77), + GasFeeCap: uint256.NewInt(1077), + BlobFeeCap: uint256.NewInt(100077), + BlobHashes: []common.Hash{{}, {}, {}}, + }), + } + + blockNumber = big.NewInt(1) + blockTime = uint64(2) + blockHash = common.BytesToHash([]byte{0x03, 0x14}) + + // Create the corresponding receipts + receipts = Receipts{ + &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 0, + }, + { + Address: common.BytesToAddress([]byte{0x01, 0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 1, + }, + }, + // derived fields: + TxHash: txs[0].Hash(), + ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), + GasUsed: 1, + EffectiveGasPrice: big.NewInt(11), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 0, + }, + &Receipt{ + PostState: common.Hash{2}.Bytes(), + CumulativeGasUsed: 3, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x22}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[1].Hash(), + TxIndex: 1, + BlockHash: blockHash, + Index: 2, + }, + { + Address: common.BytesToAddress([]byte{0x02, 0x22}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[1].Hash(), + TxIndex: 1, + BlockHash: blockHash, + Index: 3, + }, + }, + // derived fields: + TxHash: txs[1].Hash(), + GasUsed: 2, + EffectiveGasPrice: big.NewInt(22), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 1, + }, + &Receipt{ + Type: AccessListTxType, + PostState: common.Hash{3}.Bytes(), + CumulativeGasUsed: 6, + Logs: []*Log{}, + // derived fields: + TxHash: txs[2].Hash(), + GasUsed: 3, + EffectiveGasPrice: big.NewInt(33), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 2, + }, + &Receipt{ + Type: DynamicFeeTxType, + PostState: common.Hash{4}.Bytes(), + CumulativeGasUsed: 10, + Logs: []*Log{}, + // derived fields: + TxHash: txs[3].Hash(), + GasUsed: 4, + EffectiveGasPrice: big.NewInt(1044), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 3, + }, + &Receipt{ + Type: DynamicFeeTxType, + PostState: common.Hash{5}.Bytes(), + CumulativeGasUsed: 15, + Logs: []*Log{}, + // derived fields: + TxHash: txs[4].Hash(), + GasUsed: 5, + EffectiveGasPrice: big.NewInt(1055), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 4, + }, + &Receipt{ + Type: BlobTxType, + PostState: common.Hash{6}.Bytes(), + CumulativeGasUsed: 21, + Logs: []*Log{}, + // derived fields: + TxHash: txs[5].Hash(), + GasUsed: 6, + EffectiveGasPrice: big.NewInt(1066), + BlobGasUsed: params.BlobTxBlobGasPerBlob, + BlobGasPrice: big.NewInt(920), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 5, + }, + &Receipt{ + Type: BlobTxType, + PostState: common.Hash{7}.Bytes(), + CumulativeGasUsed: 28, + Logs: []*Log{}, + // derived fields: + TxHash: txs[6].Hash(), + GasUsed: 7, + EffectiveGasPrice: big.NewInt(1077), + BlobGasUsed: 3 * params.BlobTxBlobGasPerBlob, + BlobGasPrice: big.NewInt(920), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 6, + }, + } +) + +func TestDecodeEmptyTypedReceipt(t *testing.T) { + input := []byte{0x80} + var r Receipt + err := rlp.DecodeBytes(input, &r) + if err != errShortTypedReceipt { + t.Fatal("wrong error:", err) + } +} + +// Tests that receipt data can be correctly derived from the contextual infos +func TestDeriveFields(t *testing.T) { + // Re-derive receipts. + basefee := big.NewInt(1000) + blobGasPrice := big.NewInt(920) + derivedReceipts := clearComputedFieldsOnReceipts(receipts) + err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs) + if err != nil { + t.Fatalf("DeriveFields(...) = %v, want ", err) + } + + // Check diff of receipts against derivedReceipts. + r1, err := json.MarshalIndent(receipts, "", " ") + if err != nil { + t.Fatal("error marshaling input receipts:", err) + } + + r2, err := json.MarshalIndent(derivedReceipts, "", " ") + if err != nil { + t.Fatal("error marshaling derived receipts:", err) + } + d := diff.Diff(string(r1), string(r2)) + if d != "" { + t.Fatal("receipts differ:", d) + } +} + +// Test that we can marshal/unmarshal receipts to/from json without errors. +// This also confirms that our test receipts contain all the required fields. +func TestReceiptJSON(t *testing.T) { + for i := range receipts { + b, err := receipts[i].MarshalJSON() + if err != nil { + t.Fatal("error marshaling receipt to json:", err) + } + r := Receipt{} + err = r.UnmarshalJSON(b) + if err != nil { + t.Fatal("error unmarshalling receipt from json:", err) + } + } +} + +// Test we can still parse receipt without EffectiveGasPrice for backwards compatibility, even +// though it is required per the spec. +func TestEffectiveGasPriceNotRequired(t *testing.T) { + r := *receipts[0] + r.EffectiveGasPrice = nil + b, err := r.MarshalJSON() + if err != nil { + t.Fatal("error marshaling receipt to json:", err) + } + r2 := Receipt{} + err = r2.UnmarshalJSON(b) + if err != nil { + t.Fatal("error unmarshalling receipt from json:", err) + } +} + +// TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt +// rlp decoder, which failed due to a shadowing error. +func TestTypedReceiptEncodingDecoding(t *testing.T) { + var payload = common.FromHex("f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0") + check := func(bundle []*Receipt) { + t.Helper() + for i, receipt := range bundle { + if got, want := receipt.Type, uint8(1); got != want { + t.Fatalf("bundle %d: got %x, want %x", i, got, want) + } + } + } + { + var bundle []*Receipt + rlp.DecodeBytes(payload, &bundle) + check(bundle) + } + { + var bundle []*Receipt + r := bytes.NewReader(payload) + s := rlp.NewStream(r, uint64(len(payload))) + if err := s.Decode(&bundle); err != nil { + t.Fatal(err) + } + check(bundle) + } +} + +func TestReceiptMarshalBinary(t *testing.T) { + // Legacy Receipt + legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt}) + have, err := legacyReceipt.MarshalBinary() + if err != nil { + t.Fatalf("marshal binary error: %v", err) + } + legacyReceipts := Receipts{legacyReceipt} + buf := new(bytes.Buffer) + legacyReceipts.EncodeIndex(0, buf) + haveEncodeIndex := buf.Bytes() + if !bytes.Equal(have, haveEncodeIndex) { + t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) + } + buf.Reset() + if err := legacyReceipt.EncodeRLP(buf); err != nil { + t.Fatalf("encode rlp error: %v", err) + } + haveRLPEncode := buf.Bytes() + if !bytes.Equal(have, haveRLPEncode) { + t.Errorf("BinaryMarshal and EncodeRLP mismatch for legacy tx, got %x want %x", have, haveRLPEncode) + } + legacyWant := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + if !bytes.Equal(have, legacyWant) { + t.Errorf("encoded RLP mismatch, got %x want %x", have, legacyWant) + } + + // 2930 Receipt + buf.Reset() + accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt}) + have, err = accessListReceipt.MarshalBinary() + if err != nil { + t.Fatalf("marshal binary error: %v", err) + } + accessListReceipts := Receipts{accessListReceipt} + accessListReceipts.EncodeIndex(0, buf) + haveEncodeIndex = buf.Bytes() + if !bytes.Equal(have, haveEncodeIndex) { + t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) + } + accessListWant := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + if !bytes.Equal(have, accessListWant) { + t.Errorf("encoded RLP mismatch, got %x want %x", have, accessListWant) + } + + // 1559 Receipt + buf.Reset() + eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt}) + have, err = eip1559Receipt.MarshalBinary() + if err != nil { + t.Fatalf("marshal binary error: %v", err) + } + eip1559Receipts := Receipts{eip1559Receipt} + eip1559Receipts.EncodeIndex(0, buf) + haveEncodeIndex = buf.Bytes() + if !bytes.Equal(have, haveEncodeIndex) { + t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) + } + eip1559Want := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + if !bytes.Equal(have, eip1559Want) { + t.Errorf("encoded RLP mismatch, got %x want %x", have, eip1559Want) + } +} + +func TestReceiptUnmarshalBinary(t *testing.T) { + // Legacy Receipt + legacyBinary := common.FromHex("f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + gotLegacyReceipt := new(Receipt) + if err := gotLegacyReceipt.UnmarshalBinary(legacyBinary); err != nil { + t.Fatalf("unmarshal binary error: %v", err) + } + legacyReceipt.Bloom = CreateBloom(Receipts{legacyReceipt}) + if !reflect.DeepEqual(gotLegacyReceipt, legacyReceipt) { + t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotLegacyReceipt, legacyReceipt) + } + + // 2930 Receipt + accessListBinary := common.FromHex("01f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + gotAccessListReceipt := new(Receipt) + if err := gotAccessListReceipt.UnmarshalBinary(accessListBinary); err != nil { + t.Fatalf("unmarshal binary error: %v", err) + } + accessListReceipt.Bloom = CreateBloom(Receipts{accessListReceipt}) + if !reflect.DeepEqual(gotAccessListReceipt, accessListReceipt) { + t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotAccessListReceipt, accessListReceipt) + } + + // 1559 Receipt + eip1559RctBinary := common.FromHex("02f901c58001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff") + got1559Receipt := new(Receipt) + if err := got1559Receipt.UnmarshalBinary(eip1559RctBinary); err != nil { + t.Fatalf("unmarshal binary error: %v", err) + } + eip1559Receipt.Bloom = CreateBloom(Receipts{eip1559Receipt}) + if !reflect.DeepEqual(got1559Receipt, eip1559Receipt) { + t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", got1559Receipt, eip1559Receipt) + } +} + +func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt { + r := make([]*Receipt, len(receipts)) + for i, receipt := range receipts { + r[i] = clearComputedFieldsOnReceipt(receipt) + } + return r +} + +func clearComputedFieldsOnReceipt(receipt *Receipt) *Receipt { + cpy := *receipt + cpy.TxHash = common.Hash{0xff, 0xff, 0x11} + cpy.BlockHash = common.Hash{0xff, 0xff, 0x22} + cpy.BlockNumber = big.NewInt(math.MaxUint32) + cpy.TransactionIndex = math.MaxUint32 + cpy.ContractAddress = common.Address{0xff, 0xff, 0x33} + cpy.GasUsed = 0xffffffff + cpy.Logs = clearComputedFieldsOnLogs(receipt.Logs) + cpy.EffectiveGasPrice = big.NewInt(0) + cpy.BlobGasUsed = 0 + cpy.BlobGasPrice = nil + return &cpy +} + +func clearComputedFieldsOnLogs(logs []*Log) []*Log { + l := make([]*Log, len(logs)) + for i, log := range logs { + cpy := *log + cpy.BlockNumber = math.MaxUint32 + cpy.BlockHash = common.Hash{} + cpy.TxHash = common.Hash{} + cpy.TxIndex = math.MaxUint32 + cpy.Index = math.MaxUint32 + l[i] = &cpy + } + return l +} diff --git a/core/types/rlp_fuzzer_test.go b/core/types/rlp_fuzzer_test.go new file mode 100644 index 0000000..a3b9f72 --- /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 new file mode 100644 index 0000000..52ef843 --- /dev/null +++ b/core/types/state_account.go @@ -0,0 +1,121 @@ +// 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 types + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" +) + +//go:generate go run ../../rlp/rlpgen -type StateAccount -out gen_account_rlp.go + +// StateAccount is the Ethereum consensus representation of accounts. +// These objects are stored in the main account trie. +type StateAccount struct { + Nonce uint64 + Balance *uint256.Int + Root common.Hash // merkle root of the storage trie + CodeHash []byte +} + +// NewEmptyStateAccount constructs an empty state account. +func NewEmptyStateAccount() *StateAccount { + return &StateAccount{ + Balance: new(uint256.Int), + Root: EmptyRootHash, + CodeHash: EmptyCodeHash.Bytes(), + } +} + +// Copy returns a deep-copied state account object. +func (acct *StateAccount) Copy() *StateAccount { + var balance *uint256.Int + if acct.Balance != nil { + balance = new(uint256.Int).Set(acct.Balance) + } + return &StateAccount{ + Nonce: acct.Nonce, + Balance: balance, + Root: acct.Root, + CodeHash: common.CopyBytes(acct.CodeHash), + } +} + +// SlimAccount is a modified version of an Account, where the root is replaced +// with a byte slice. This format can be used to represent full-consensus format +// or slim format which replaces the empty root and code hash as nil byte slice. +type SlimAccount struct { + Nonce uint64 + Balance *uint256.Int + Root []byte // Nil if root equals to types.EmptyRootHash + CodeHash []byte // Nil if hash equals to types.EmptyCodeHash +} + +// SlimAccountRLP encodes the state account in 'slim RLP' format. +func SlimAccountRLP(account StateAccount) []byte { + slim := SlimAccount{ + Nonce: account.Nonce, + Balance: account.Balance, + } + if account.Root != EmptyRootHash { + slim.Root = account.Root[:] + } + if !bytes.Equal(account.CodeHash, EmptyCodeHash[:]) { + slim.CodeHash = account.CodeHash + } + data, err := rlp.EncodeToBytes(slim) + if err != nil { + panic(err) + } + return data +} + +// FullAccount decodes the data on the 'slim RLP' format and returns +// the consensus format account. +func FullAccount(data []byte) (*StateAccount, error) { + var slim SlimAccount + if err := rlp.DecodeBytes(data, &slim); err != nil { + return nil, err + } + var account StateAccount + account.Nonce, account.Balance = slim.Nonce, slim.Balance + + // Interpret the storage root and code hash in slim format. + if len(slim.Root) == 0 { + account.Root = EmptyRootHash + } else { + account.Root = common.BytesToHash(slim.Root) + } + if len(slim.CodeHash) == 0 { + account.CodeHash = EmptyCodeHash[:] + } else { + account.CodeHash = slim.CodeHash + } + return &account, nil +} + +// FullAccountRLP converts data on the 'slim RLP' format into the full RLP-format. +func FullAccountRLP(data []byte) ([]byte, error) { + account, err := FullAccount(data) + if err != nil { + return nil, err + } + return rlp.EncodeToBytes(account) +} diff --git a/core/types/transaction.go b/core/types/transaction.go new file mode 100644 index 0000000..4ac9187 --- /dev/null +++ b/core/types/transaction.go @@ -0,0 +1,611 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + ErrInvalidSig = errors.New("invalid transaction v, r, s values") + ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures") + ErrInvalidTxType = errors.New("transaction type not valid in this context") + 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. +const ( + LegacyTxType = 0x00 + AccessListTxType = 0x01 + DynamicFeeTxType = 0x02 + BlobTxType = 0x03 +) + +// Transaction is an Ethereum transaction. +type Transaction struct { + inner TxData // Consensus contents of a transaction + time time.Time // Time first seen locally (spam avoidance) + + // caches + hash atomic.Pointer[common.Hash] + size atomic.Uint64 + from atomic.Pointer[sigCache] +} + +// NewTx creates a new transaction. +func NewTx(inner TxData) *Transaction { + tx := new(Transaction) + tx.setDecoded(inner.copy(), 0) + return tx +} + +// TxData is the underlying data of a transaction. +// +// This is implemented by DynamicFeeTx, LegacyTx and AccessListTx. +type TxData interface { + txType() byte // returns the type ID + copy() TxData // creates a deep copy and initializes all fields + + chainID() *big.Int + accessList() AccessList + data() []byte + gas() uint64 + gasPrice() *big.Int + gasTipCap() *big.Int + gasFeeCap() *big.Int + value() *big.Int + nonce() uint64 + to() *common.Address + + rawSignatureValues() (v, r, s *big.Int) + setSignatureValues(chainID, v, r, s *big.Int) + + // effectiveGasPrice computes the gas price paid by the transaction, given + // the inclusion block baseFee. + // + // Unlike other TxData methods, the returned *big.Int should be an independent + // 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 +func (tx *Transaction) EncodeRLP(w io.Writer) error { + if tx.Type() == LegacyTxType { + return rlp.Encode(w, tx.inner) + } + // It's an EIP-2718 typed TX envelope. + buf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(buf) + buf.Reset() + if err := tx.encodeTyped(buf); err != nil { + return err + } + return rlp.Encode(w, buf.Bytes()) +} + +// encodeTyped writes the canonical encoding of a typed transaction to w. +func (tx *Transaction) encodeTyped(w *bytes.Buffer) error { + w.WriteByte(tx.Type()) + return tx.inner.encode(w) +} + +// MarshalBinary returns the canonical encoding of the transaction. +// For legacy transactions, it returns the RLP encoding. For EIP-2718 typed +// transactions, it returns the type and payload. +func (tx *Transaction) MarshalBinary() ([]byte, error) { + if tx.Type() == LegacyTxType { + return rlp.EncodeToBytes(tx.inner) + } + var buf bytes.Buffer + err := tx.encodeTyped(&buf) + return buf.Bytes(), err +} + +// DecodeRLP implements rlp.Decoder +func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { + kind, size, err := s.Kind() + switch { + case err != nil: + return err + case kind == rlp.List: + // It's a legacy transaction. + var inner LegacyTx + err := s.Decode(&inner) + if err == nil { + tx.setDecoded(&inner, rlp.ListSize(size)) + } + return err + case kind == rlp.Byte: + return errShortTypedTx + default: + // It's an EIP-2718 typed TX envelope. + // 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, size) + } + return err + } +} + +// UnmarshalBinary decodes the canonical encoding of 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. + var data LegacyTx + err := rlp.DecodeBytes(b, &data) + if err != nil { + return err + } + tx.setDecoded(&data, uint64(len(b))) + return nil + } + // It's an EIP-2718 typed transaction envelope. + inner, err := tx.decodeTyped(b) + if err != nil { + return err + } + tx.setDecoded(inner, uint64(len(b))) + return nil +} + +// decodeTyped decodes a typed transaction from the canonical format. +func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { + if len(b) <= 1 { + return nil, errShortTypedTx + } + var inner TxData + switch b[0] { + case AccessListTxType: + inner = new(AccessListTx) + case DynamicFeeTxType: + inner = new(DynamicFeeTx) + case BlobTxType: + inner = new(BlobTx) + default: + return nil, ErrTxTypeNotSupported + } + err := inner.decode(b[1:]) + return inner, err +} + +// setDecoded sets the inner transaction and size after decoding. +func (tx *Transaction) setDecoded(inner TxData, size uint64) { + tx.inner = inner + tx.time = time.Now() + if size > 0 { + tx.size.Store(size) + } +} + +func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected bool) error { + if isProtectedV(v) && !maybeProtected { + return ErrUnexpectedProtection + } + + var plainV byte + if isProtectedV(v) { + chainID := deriveChainId(v).Uint64() + plainV = byte(v.Uint64() - 35 - 2*chainID) + } else if maybeProtected { + // Only EIP-155 signatures can be optionally protected. Since + // we determined this v value is not protected, it must be a + // raw 27 or 28. + plainV = byte(v.Uint64() - 27) + } else { + // If the signature is not optionally protected, we assume it + // must already be equal to the recovery id. + plainV = byte(v.Uint64()) + } + if !crypto.ValidateSignatureValues(plainV, r, s, false) { + return ErrInvalidSig + } + + return nil +} + +func isProtectedV(V *big.Int) bool { + if V.BitLen() <= 8 { + v := V.Uint64() + return v != 27 && v != 28 && v != 1 && v != 0 + } + // anything not 27 or 28 is considered protected + return true +} + +// Protected says whether the transaction is replay-protected. +func (tx *Transaction) Protected() bool { + switch tx := tx.inner.(type) { + case *LegacyTx: + return tx.V != nil && isProtectedV(tx.V) + default: + return true + } +} + +// Type returns the transaction type. +func (tx *Transaction) Type() uint8 { + return tx.inner.txType() +} + +// ChainId returns the EIP155 chain ID of the transaction. The return value will always be +// non-nil. For legacy transactions which are not replay-protected, the return value is +// zero. +func (tx *Transaction) ChainId() *big.Int { + return tx.inner.chainID() +} + +// Data returns the input data of the transaction. +func (tx *Transaction) Data() []byte { return tx.inner.data() } + +// AccessList returns the access list of the transaction. +func (tx *Transaction) AccessList() AccessList { return tx.inner.accessList() } + +// Gas returns the gas limit of the transaction. +func (tx *Transaction) Gas() uint64 { return tx.inner.gas() } + +// GasPrice returns the gas price of the transaction. +func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) } + +// GasTipCap returns the gasTipCap per gas of the transaction. +func (tx *Transaction) GasTipCap() *big.Int { return new(big.Int).Set(tx.inner.gasTipCap()) } + +// GasFeeCap returns the fee cap per gas of the transaction. +func (tx *Transaction) GasFeeCap() *big.Int { return new(big.Int).Set(tx.inner.gasFeeCap()) } + +// Value returns the ether amount of the transaction. +func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) } + +// Nonce returns the sender account nonce of the transaction. +func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() } + +// To returns the recipient address of the transaction. +// For contract-creation transactions, To returns nil. +func (tx *Transaction) To() *common.Address { + return copyAddressPtr(tx.inner.to()) +} + +// Cost returns (gas * gasPrice) + (blobGas * blobGasPrice) + value. +func (tx *Transaction) Cost() *big.Int { + total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + if tx.Type() == BlobTxType { + total.Add(total, new(big.Int).Mul(tx.BlobGasFeeCap(), new(big.Int).SetUint64(tx.BlobGas()))) + } + total.Add(total, tx.Value()) + return total +} + +// RawSignatureValues returns the V, R, S signature values of the transaction. +// The return values should not be modified by the caller. +// The return values may be nil or zero, if the transaction is unsigned. +func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { + return tx.inner.rawSignatureValues() +} + +// GasFeeCapCmp compares the fee cap of two transactions. +func (tx *Transaction) GasFeeCapCmp(other *Transaction) int { + return tx.inner.gasFeeCap().Cmp(other.inner.gasFeeCap()) +} + +// GasFeeCapIntCmp compares the fee cap of the transaction against the given fee cap. +func (tx *Transaction) GasFeeCapIntCmp(other *big.Int) int { + return tx.inner.gasFeeCap().Cmp(other) +} + +// GasTipCapCmp compares the gasTipCap of two transactions. +func (tx *Transaction) GasTipCapCmp(other *Transaction) int { + return tx.inner.gasTipCap().Cmp(other.inner.gasTipCap()) +} + +// GasTipCapIntCmp compares the gasTipCap of the transaction against the given gasTipCap. +func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int { + return tx.inner.gasTipCap().Cmp(other) +} + +// EffectiveGasTip returns the effective miner gasTipCap for the given base fee. +// Note: if the effective gasTipCap is negative, this method returns both error +// the actual negative value, _and_ ErrGasFeeCapTooLow +func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) { + if baseFee == nil { + return tx.GasTipCap(), nil + } + var err error + gasFeeCap := tx.GasFeeCap() + if gasFeeCap.Cmp(baseFee) == -1 { + err = ErrGasFeeCapTooLow + } + return math.BigMin(tx.GasTipCap(), gasFeeCap.Sub(gasFeeCap, baseFee)), err +} + +// EffectiveGasTipValue is identical to EffectiveGasTip, but does not return an +// error in case the effective gasTipCap is negative +func (tx *Transaction) EffectiveGasTipValue(baseFee *big.Int) *big.Int { + effectiveTip, _ := tx.EffectiveGasTip(baseFee) + return effectiveTip +} + +// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee. +func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int { + if baseFee == nil { + return tx.GasTipCapCmp(other) + } + return tx.EffectiveGasTipValue(baseFee).Cmp(other.EffectiveGasTipValue(baseFee)) +} + +// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap. +func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) int { + if baseFee == nil { + return tx.GasTipCapIntCmp(other) + } + 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.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.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 +} + +// WithBlobTxSidecar returns a copy of tx with the blob sidecar added. +func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction { + blobtx, ok := tx.inner.(*BlobTx) + if !ok { + return tx + } + cpy := &Transaction{ + inner: blobtx.withSidecar(sideCar), + time: tx.time, + } + // Note: tx.size cache not carried over because the sidecar is included in size! + if h := tx.hash.Load(); h != nil { + cpy.hash.Store(h) + } + if f := tx.from.Load(); f != nil { + cpy.from.Store(f) + } + return cpy +} + +// SetTime sets the decoding time of a transaction. This is used by tests to set +// arbitrary times and by persistent transaction pools when loading old txs from +// disk. +func (tx *Transaction) SetTime(t time.Time) { + tx.time = t +} + +// Time returns the time when the transaction was first seen on the network. It +// is a heuristic to prefer mining older txs vs new all other things equal. +func (tx *Transaction) Time() time.Time { + return tx.time +} + +// Hash returns the transaction hash. +func (tx *Transaction) Hash() common.Hash { + if hash := tx.hash.Load(); hash != nil { + return *hash + } + + var h common.Hash + if tx.Type() == LegacyTxType { + h = rlpHash(tx.inner) + } else { + h = prefixedRlpHash(tx.Type(), tx.inner) + } + tx.hash.Store(&h) + return h +} + +// Size returns the true encoded storage size of the transaction, either by encoding +// and returning it, or returning a previously cached value. +func (tx *Transaction) Size() uint64 { + if size := tx.size.Load(); size > 0 { + return size + } + + // 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 + } + + tx.size.Store(size) + return size +} + +// WithSignature returns a new transaction with the given signature. +// This signature needs to be in the [R || S || V] format where V is 0 or 1. +func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) { + r, s, v, err := signer.SignatureValues(tx, sig) + if err != nil { + return nil, err + } + if r == nil || s == nil || v == nil { + return nil, fmt.Errorf("%w: r: %s, s: %s, v: %s", ErrInvalidSig, r, s, v) + } + cpy := tx.inner.copy() + cpy.setSignatureValues(signer.ChainID(), v, r, s) + return &Transaction{inner: cpy, time: tx.time}, nil +} + +// Transactions implements DerivableList for transactions. +type Transactions []*Transaction + +// Len returns the length of s. +func (s Transactions) Len() int { return len(s) } + +// EncodeIndex encodes the i'th transaction to w. Note that this does not check for errors +// because we assume that *Transaction will only ever contain valid txs that were either +// constructed by decoding or via public API in this package. +func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) { + tx := s[i] + if tx.Type() == LegacyTxType { + rlp.Encode(w, tx.inner) + } else { + tx.encodeTyped(w) + } +} + +// TxDifference returns a new set of transactions that are present in a but not in b. +func TxDifference(a, b Transactions) Transactions { + keep := make(Transactions, 0, len(a)) + + remove := make(map[common.Hash]struct{}) + for _, tx := range b { + remove[tx.Hash()] = struct{}{} + } + + for _, tx := range a { + if _, ok := remove[tx.Hash()]; !ok { + keep = append(keep, tx) + } + } + + return keep +} + +// HashDifference returns a new set of hashes that are present in a but not in b. +func HashDifference(a, b []common.Hash) []common.Hash { + keep := make([]common.Hash, 0, len(a)) + + remove := make(map[common.Hash]struct{}) + for _, hash := range b { + remove[hash] = struct{}{} + } + + for _, hash := range a { + if _, ok := remove[hash]; !ok { + keep = append(keep, hash) + } + } + + return keep +} + +// TxByNonce implements the sort interface to allow sorting a list of transactions +// by their nonces. This is usually only useful for sorting transactions from a +// single account, otherwise a nonce comparison doesn't make much sense. +type TxByNonce Transactions + +func (s TxByNonce) Len() int { return len(s) } +func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce() < s[j].Nonce() } +func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// copyAddressPtr copies an address. +func copyAddressPtr(a *common.Address) *common.Address { + if a == nil { + return nil + } + cpy := *a + return &cpy +} diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go new file mode 100644 index 0000000..4d5b2bc --- /dev/null +++ b/core/types/transaction_marshalling.go @@ -0,0 +1,421 @@ +// 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 types + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/holiman/uint256" +) + +// txJSON is the JSON representation of transactions. +type txJSON struct { + Type hexutil.Uint64 `json:"type"` + + ChainID *hexutil.Big `json:"chainId,omitempty"` + Nonce *hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` + MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` + MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` + Value *hexutil.Big `json:"value"` + Input *hexutil.Bytes `json:"input"` + AccessList *AccessList `json:"accessList,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"` + + // Blob transaction sidecar encoding: + Blobs []kzg4844.Blob `json:"blobs,omitempty"` + Commitments []kzg4844.Commitment `json:"commitments,omitempty"` + Proofs []kzg4844.Proof `json:"proofs,omitempty"` + + // Only used for encoding: + Hash common.Hash `json:"hash"` +} + +// yParityValue returns the YParity value from JSON. For backwards-compatibility reasons, +// this can be given in the 'v' field or the 'yParity' field. If both exist, they must match. +func (tx *txJSON) yParityValue() (*big.Int, error) { + if tx.YParity != nil { + val := uint64(*tx.YParity) + if val != 0 && val != 1 { + return nil, errInvalidYParity + } + bigval := new(big.Int).SetUint64(val) + if tx.V != nil && tx.V.ToInt().Cmp(bigval) != 0 { + return nil, errVYParityMismatch + } + return bigval, nil + } + if tx.V != nil { + return tx.V.ToInt(), nil + } + return nil, errVYParityMissing +} + +// MarshalJSON marshals as JSON with a hash. +func (tx *Transaction) MarshalJSON() ([]byte, error) { + var enc txJSON + // These are set for all tx types. + enc.Hash = tx.Hash() + enc.Type = hexutil.Uint64(tx.Type()) + + // Other fields are set conditionally depending on tx type. + switch itx := tx.inner.(type) { + case *LegacyTx: + enc.Nonce = (*hexutil.Uint64)(&itx.Nonce) + enc.To = tx.To() + enc.Gas = (*hexutil.Uint64)(&itx.Gas) + enc.GasPrice = (*hexutil.Big)(itx.GasPrice) + enc.Value = (*hexutil.Big)(itx.Value) + enc.Input = (*hexutil.Bytes)(&itx.Data) + enc.V = (*hexutil.Big)(itx.V) + enc.R = (*hexutil.Big)(itx.R) + enc.S = (*hexutil.Big)(itx.S) + if tx.Protected() { + enc.ChainID = (*hexutil.Big)(tx.ChainId()) + } + + case *AccessListTx: + enc.ChainID = (*hexutil.Big)(itx.ChainID) + enc.Nonce = (*hexutil.Uint64)(&itx.Nonce) + enc.To = tx.To() + enc.Gas = (*hexutil.Uint64)(&itx.Gas) + enc.GasPrice = (*hexutil.Big)(itx.GasPrice) + enc.Value = (*hexutil.Big)(itx.Value) + enc.Input = (*hexutil.Bytes)(&itx.Data) + enc.AccessList = &itx.AccessList + enc.V = (*hexutil.Big)(itx.V) + enc.R = (*hexutil.Big)(itx.R) + enc.S = (*hexutil.Big)(itx.S) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) + + case *DynamicFeeTx: + enc.ChainID = (*hexutil.Big)(itx.ChainID) + enc.Nonce = (*hexutil.Uint64)(&itx.Nonce) + enc.To = tx.To() + enc.Gas = (*hexutil.Uint64)(&itx.Gas) + enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap) + enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap) + enc.Value = (*hexutil.Big)(itx.Value) + enc.Input = (*hexutil.Bytes)(&itx.Data) + enc.AccessList = &itx.AccessList + enc.V = (*hexutil.Big)(itx.V) + enc.R = (*hexutil.Big)(itx.R) + enc.S = (*hexutil.Big)(itx.S) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) + + case *BlobTx: + enc.ChainID = (*hexutil.Big)(itx.ChainID.ToBig()) + enc.Nonce = (*hexutil.Uint64)(&itx.Nonce) + enc.Gas = (*hexutil.Uint64)(&itx.Gas) + enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap.ToBig()) + enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap.ToBig()) + enc.MaxFeePerBlobGas = (*hexutil.Big)(itx.BlobFeeCap.ToBig()) + enc.Value = (*hexutil.Big)(itx.Value.ToBig()) + enc.Input = (*hexutil.Bytes)(&itx.Data) + enc.AccessList = &itx.AccessList + enc.BlobVersionedHashes = itx.BlobHashes + enc.To = tx.To() + enc.V = (*hexutil.Big)(itx.V.ToBig()) + enc.R = (*hexutil.Big)(itx.R.ToBig()) + enc.S = (*hexutil.Big)(itx.S.ToBig()) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) + if sidecar := itx.Sidecar; sidecar != nil { + enc.Blobs = itx.Sidecar.Blobs + enc.Commitments = itx.Sidecar.Commitments + enc.Proofs = itx.Sidecar.Proofs + } + } + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (tx *Transaction) UnmarshalJSON(input []byte) error { + var dec txJSON + err := json.Unmarshal(input, &dec) + if err != nil { + return err + } + + // Decode / verify fields according to transaction type. + var inner TxData + switch dec.Type { + case LegacyTxType: + var itx LegacyTx + inner = &itx + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.To != nil { + itx.To = dec.To + } + if dec.Gas == nil { + return errors.New("missing required field 'gas' in transaction") + } + itx.Gas = uint64(*dec.Gas) + if dec.GasPrice == nil { + return errors.New("missing required field 'gasPrice' in transaction") + } + itx.GasPrice = (*big.Int)(dec.GasPrice) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Input == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Input + + // signature R + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + // signature S + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + // signature V + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil { + return err + } + } + + case AccessListTxType: + var itx AccessListTx + inner = &itx + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = (*big.Int)(dec.ChainID) + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.To != nil { + itx.To = dec.To + } + if dec.Gas == nil { + return errors.New("missing required field 'gas' in transaction") + } + itx.Gas = uint64(*dec.Gas) + if dec.GasPrice == nil { + return errors.New("missing required field 'gasPrice' in transaction") + } + itx.GasPrice = (*big.Int)(dec.GasPrice) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Input == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Input + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + + // signature R + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + // signature S + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + // signature V + itx.V, err = dec.yParityValue() + if err != nil { + return err + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { + return err + } + } + + case DynamicFeeTxType: + var itx DynamicFeeTx + inner = &itx + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = (*big.Int)(dec.ChainID) + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.To != nil { + itx.To = dec.To + } + if dec.Gas == nil { + return errors.New("missing required field 'gas' for txdata") + } + itx.Gas = uint64(*dec.Gas) + if dec.MaxPriorityFeePerGas == nil { + return errors.New("missing required field 'maxPriorityFeePerGas' for txdata") + } + itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas) + if dec.MaxFeePerGas == nil { + return errors.New("missing required field 'maxFeePerGas' for txdata") + } + itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Input == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Input + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + + // signature R + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + // signature S + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + // signature V + itx.V, err = dec.yParityValue() + if err != nil { + return err + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { + return err + } + } + + case BlobTxType: + var itx BlobTx + inner = &itx + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = uint256.MustFromBig((*big.Int)(dec.ChainID)) + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.To == nil { + return errors.New("missing required field 'to' in transaction") + } + itx.To = *dec.To + if dec.Gas == nil { + return errors.New("missing required field 'gas' for txdata") + } + itx.Gas = uint64(*dec.Gas) + if dec.MaxPriorityFeePerGas == nil { + return errors.New("missing required field 'maxPriorityFeePerGas' for txdata") + } + itx.GasTipCap = uint256.MustFromBig((*big.Int)(dec.MaxPriorityFeePerGas)) + if dec.MaxFeePerGas == nil { + return errors.New("missing required field 'maxFeePerGas' for txdata") + } + itx.GasFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerGas)) + if dec.MaxFeePerBlobGas == nil { + return errors.New("missing required field 'maxFeePerBlobGas' for txdata") + } + itx.BlobFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerBlobGas)) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = uint256.MustFromBig((*big.Int)(dec.Value)) + if dec.Input == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Input + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + if dec.BlobVersionedHashes == nil { + return errors.New("missing required field 'blobVersionedHashes' in transaction") + } + itx.BlobHashes = dec.BlobVersionedHashes + + // signature R + var overflow bool + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + 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, overflow = uint256.FromBig((*big.Int)(dec.S)) + if overflow { + return errors.New("'s' value overflows uint256") + } + // signature V + vbig, err := dec.yParityValue() + if err != nil { + return err + } + itx.V, overflow = uint256.FromBig(vbig) + if overflow { + return errors.New("'v' value overflows uint256") + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { + if err := sanityCheckSignature(vbig, itx.R.ToBig(), itx.S.ToBig(), false); err != nil { + return err + } + } + + default: + return ErrTxTypeNotSupported + } + + // Now set the inner transaction. + tx.setDecoded(inner, 0) + + // TODO: check hash here? + return nil +} diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go new file mode 100644 index 0000000..339fee6 --- /dev/null +++ b/core/types/transaction_signing.go @@ -0,0 +1,577 @@ +// 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 types + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +var ErrInvalidChainId = errors.New("invalid chain id for signer") + +// sigCache is used to cache the derived sender and contains +// the signer used to derive it. +type sigCache struct { + signer Signer + from common.Address +} + +// MakeSigner returns a Signer based on the given chain config and block number. +func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { + var signer Signer + switch { + case config.IsCancun(blockNumber, blockTime): + signer = NewCancunSigner(config.ChainID) + case config.IsLondon(blockNumber): + signer = NewLondonSigner(config.ChainID) + case config.IsBerlin(blockNumber): + signer = NewEIP2930Signer(config.ChainID) + case config.IsEIP155(blockNumber): + signer = NewEIP155Signer(config.ChainID) + case config.IsHomestead(blockNumber): + signer = HomesteadSigner{} + default: + signer = FrontierSigner{} + } + return signer +} + +// LatestSigner returns the 'most permissive' Signer available for the given chain +// configuration. Specifically, this enables support of all types of transactions +// when their respective forks are scheduled to occur at any block number (or time) +// in the chain config. +// +// Use this in transaction-handling code where the current block number is unknown. If you +// have the current block number available, use MakeSigner instead. +func LatestSigner(config *params.ChainConfig) Signer { + if config.ChainID != nil { + if config.CancunTime != nil { + return NewCancunSigner(config.ChainID) + } + if config.LondonBlock != nil { + return NewLondonSigner(config.ChainID) + } + if config.BerlinBlock != nil { + return NewEIP2930Signer(config.ChainID) + } + if config.EIP155Block != nil { + return NewEIP155Signer(config.ChainID) + } + } + return HomesteadSigner{} +} + +// LatestSignerForChainID returns the 'most permissive' Signer available. Specifically, +// this enables support for EIP-155 replay protection and all implemented EIP-2718 +// transaction types if chainID is non-nil. +// +// Use this in transaction-handling code where the current block number and fork +// configuration are unknown. If you have a ChainConfig, use LatestSigner instead. +// If you have a ChainConfig and know the current block number, use MakeSigner instead. +func LatestSignerForChainID(chainID *big.Int) Signer { + if chainID == nil { + return HomesteadSigner{} + } + return NewCancunSigner(chainID) +} + +// SignTx signs the transaction using the given signer and private key. +func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) { + h := s.Hash(tx) + sig, err := crypto.Sign(h[:], prv) + if err != nil { + return nil, err + } + return tx.WithSignature(s, sig) +} + +// SignNewTx creates a transaction and signs it. +func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) { + return SignTx(NewTx(txdata), s, prv) +} + +// MustSignNewTx creates a transaction and signs it. +// This panics if the transaction cannot be signed. +func MustSignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) *Transaction { + tx, err := SignNewTx(prv, s, txdata) + if err != nil { + panic(err) + } + return tx +} + +// Sender returns the address derived from the signature (V, R, S) using secp256k1 +// elliptic curve and an error if it failed deriving or upon an incorrect +// signature. +// +// Sender may cache the address, allowing it to be used regardless of +// signing method. The cache is invalidated if the cached signer does +// not match the signer used in the current call. +func Sender(signer Signer, tx *Transaction) (common.Address, error) { + if sigCache := tx.from.Load(); sigCache != nil { + // If the signer used to derive from in a previous + // call is not the same as used current, invalidate + // the cache. + if sigCache.signer.Equal(signer) { + return sigCache.from, nil + } + } + + addr, err := signer.Sender(tx) + if err != nil { + return common.Address{}, err + } + tx.from.Store(&sigCache{signer: signer, from: addr}) + return addr, nil +} + +// Signer encapsulates transaction signature handling. The name of this type is slightly +// misleading because Signers don't actually sign, they're just for validating and +// processing of signatures. +// +// Note that this interface is not a stable API and may change at any time to accommodate +// new protocol rules. +type Signer interface { + // Sender returns the sender address of the transaction. + Sender(tx *Transaction) (common.Address, error) + + // SignatureValues returns the raw R, S, V values corresponding to the + // given signature. + SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) + ChainID() *big.Int + + // Hash returns 'signature hash', i.e. the transaction hash that is signed by the + // private key. This hash does not uniquely identify the transaction. + Hash(tx *Transaction) common.Hash + + // Equal returns true if the given signer is the same as the receiver. + Equal(Signer) bool +} + +type cancunSigner struct{ londonSigner } + +// NewCancunSigner returns a signer that accepts +// - EIP-4844 blob transactions +// - EIP-1559 dynamic fee transactions +// - EIP-2930 access list transactions, +// - EIP-155 replay protected transactions, and +// - legacy Homestead transactions. +func NewCancunSigner(chainId *big.Int) Signer { + return cancunSigner{londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}} +} + +func (s cancunSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != BlobTxType { + return s.londonSigner.Sender(tx) + } + V, R, S := tx.RawSignatureValues() + // Blob txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) + } + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +func (s cancunSigner) Equal(s2 Signer) bool { + x, ok := s2.(cancunSigner) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s cancunSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + txdata, ok := tx.inner.(*BlobTx) + if !ok { + return s.londonSigner.SignatureValues(tx, sig) + } + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + if txdata.ChainID.Sign() != 0 && txdata.ChainID.ToBig().Cmp(s.chainId) != 0 { + return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId) + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s cancunSigner) Hash(tx *Transaction) common.Hash { + if tx.Type() != BlobTxType { + return s.londonSigner.Hash(tx) + } + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasTipCap(), + tx.GasFeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + tx.BlobGasFeeCap(), + tx.BlobHashes(), + }) +} + +type londonSigner struct{ eip2930Signer } + +// NewLondonSigner returns a signer that accepts +// - EIP-1559 dynamic fee transactions +// - EIP-2930 access list transactions, +// - EIP-155 replay protected transactions, and +// - legacy Homestead transactions. +func NewLondonSigner(chainId *big.Int) Signer { + return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}} +} + +func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != DynamicFeeTxType { + return s.eip2930Signer.Sender(tx) + } + V, R, S := tx.RawSignatureValues() + // DynamicFee txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) + } + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +func (s londonSigner) Equal(s2 Signer) bool { + x, ok := s2.(londonSigner) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + txdata, ok := tx.inner.(*DynamicFeeTx) + if !ok { + return s.eip2930Signer.SignatureValues(tx, sig) + } + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { + return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId) + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s londonSigner) Hash(tx *Transaction) common.Hash { + if tx.Type() != DynamicFeeTxType { + return s.eip2930Signer.Hash(tx) + } + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasTipCap(), + tx.GasFeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + }) +} + +type eip2930Signer struct{ EIP155Signer } + +// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions, +// EIP-155 replay protected transactions, and legacy Homestead transactions. +func NewEIP2930Signer(chainId *big.Int) Signer { + return eip2930Signer{NewEIP155Signer(chainId)} +} + +func (s eip2930Signer) ChainID() *big.Int { + return s.chainId +} + +func (s eip2930Signer) Equal(s2 Signer) bool { + x, ok := s2.(eip2930Signer) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { + V, R, S := tx.RawSignatureValues() + switch tx.Type() { + case LegacyTxType: + 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. + V = new(big.Int).Add(V, big.NewInt(27)) + default: + return common.Address{}, ErrTxTypeNotSupported + } + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) + } + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + switch txdata := tx.inner.(type) { + case *LegacyTx: + return s.EIP155Signer.SignatureValues(tx, sig) + case *AccessListTx: + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { + return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId) + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + default: + return nil, nil, nil, ErrTxTypeNotSupported + } + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s eip2930Signer) Hash(tx *Transaction) common.Hash { + switch tx.Type() { + case LegacyTxType: + return s.EIP155Signer.Hash(tx) + case AccessListTxType: + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + }) + default: + // This _should_ not happen, but in case someone sends in a bad + // json struct via RPC, it's probably more prudent to return an + // empty hash instead of killing the node with a panic + //panic("Unsupported transaction type: %d", tx.typ) + return common.Hash{} + } +} + +// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which +// are replay-protected as well as unprotected homestead transactions. +type EIP155Signer struct { + chainId, chainIdMul *big.Int +} + +func NewEIP155Signer(chainId *big.Int) EIP155Signer { + if chainId == nil { + chainId = new(big.Int) + } + return EIP155Signer{ + chainId: chainId, + chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)), + } +} + +func (s EIP155Signer) ChainID() *big.Int { + return s.chainId +} + +func (s EIP155Signer) Equal(s2 Signer) bool { + eip155, ok := s2.(EIP155Signer) + return ok && eip155.chainId.Cmp(s.chainId) == 0 +} + +var big8 = big.NewInt(8) + +func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } + if !tx.Protected() { + return HomesteadSigner{}.Sender(tx) + } + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId) + } + V, R, S := tx.RawSignatureValues() + V = new(big.Int).Sub(V, s.chainIdMul) + V.Sub(V, big8) + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +// SignatureValues returns signature values. This signature +// needs to be in the [R || S || V] format where V is 0 or 1. +func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + if tx.Type() != LegacyTxType { + return nil, nil, nil, ErrTxTypeNotSupported + } + R, S, V = decodeSignature(sig) + if s.chainId.Sign() != 0 { + V = big.NewInt(int64(sig[64] + 35)) + V.Add(V, s.chainIdMul) + } + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s EIP155Signer) Hash(tx *Transaction) common.Hash { + return rlpHash([]interface{}{ + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + s.chainId, uint(0), uint(0), + }) +} + +// HomesteadSigner implements Signer interface using the +// homestead rules. +type HomesteadSigner struct{ FrontierSigner } + +func (hs HomesteadSigner) ChainID() *big.Int { + return nil +} + +func (hs HomesteadSigner) Equal(s2 Signer) bool { + _, ok := s2.(HomesteadSigner) + return ok +} + +// SignatureValues returns signature values. This signature +// needs to be in the [R || S || V] format where V is 0 or 1. +func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { + return hs.FrontierSigner.SignatureValues(tx, sig) +} + +func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return recoverPlain(hs.Hash(tx), r, s, v, true) +} + +// FrontierSigner implements Signer interface using the +// frontier rules. +type FrontierSigner struct{} + +func (fs FrontierSigner) ChainID() *big.Int { + return nil +} + +func (fs FrontierSigner) Equal(s2 Signer) bool { + _, ok := s2.(FrontierSigner) + return ok +} + +func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return recoverPlain(fs.Hash(tx), r, s, v, false) +} + +// SignatureValues returns signature values. This signature +// needs to be in the [R || S || V] format where V is 0 or 1. +func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { + if tx.Type() != LegacyTxType { + return nil, nil, nil, ErrTxTypeNotSupported + } + r, s, v = decodeSignature(sig) + return r, s, v, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { + return rlpHash([]interface{}{ + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + }) +} + +func decodeSignature(sig []byte) (r, s, v *big.Int) { + if len(sig) != crypto.SignatureLength { + panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)) + } + r = new(big.Int).SetBytes(sig[:32]) + s = new(big.Int).SetBytes(sig[32:64]) + v = new(big.Int).SetBytes([]byte{sig[64] + 27}) + return r, s, v +} + +func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) { + if Vb.BitLen() > 8 { + return common.Address{}, ErrInvalidSig + } + V := byte(Vb.Uint64() - 27) + if !crypto.ValidateSignatureValues(V, R, S, homestead) { + return common.Address{}, ErrInvalidSig + } + // encode the signature in uncompressed format + r, s := R.Bytes(), S.Bytes() + sig := make([]byte, crypto.SignatureLength) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = V + // recover the public key from the signature + pub, err := crypto.Ecrecover(sighash[:], sig) + if err != nil { + return common.Address{}, err + } + if len(pub) == 0 || pub[0] != 4 { + return common.Address{}, errors.New("invalid public key") + } + var addr common.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil +} + +// deriveChainId derives the chain id from the given v parameter +func deriveChainId(v *big.Int) *big.Int { + if v.BitLen() <= 64 { + v := v.Uint64() + if v == 27 || v == 28 { + return new(big.Int) + } + return new(big.Int).SetUint64((v - 35) / 2) + } + v.Sub(v, big.NewInt(35)) + return v.Rsh(v, 1) +} diff --git a/core/types/transaction_signing_test.go b/core/types/transaction_signing_test.go new file mode 100644 index 0000000..b66577f --- /dev/null +++ b/core/types/transaction_signing_test.go @@ -0,0 +1,190 @@ +// 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 types + +import ( + "errors" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +func TestEIP155Signing(t *testing.T) { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + signer := NewEIP155Signer(big.NewInt(18)) + tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil), signer, key) + if err != nil { + t.Fatal(err) + } + + from, err := Sender(signer, tx) + if err != nil { + t.Fatal(err) + } + if from != addr { + t.Errorf("expected from and address to be equal. Got %x want %x", from, addr) + } +} + +func TestEIP155ChainId(t *testing.T) { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + signer := NewEIP155Signer(big.NewInt(18)) + tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil), signer, key) + if err != nil { + t.Fatal(err) + } + if !tx.Protected() { + t.Fatal("expected tx to be protected") + } + + if tx.ChainId().Cmp(signer.chainId) != 0 { + t.Error("expected chainId to be", signer.chainId, "got", tx.ChainId()) + } + + tx = NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil) + tx, err = SignTx(tx, HomesteadSigner{}, key) + if err != nil { + t.Fatal(err) + } + + if tx.Protected() { + t.Error("didn't expect tx to be protected") + } + + if tx.ChainId().Sign() != 0 { + t.Error("expected chain id to be 0 got", tx.ChainId()) + } +} + +func TestEIP155SigningVitalik(t *testing.T) { + // Test vectors come from http://vitalik.ca/files/eip155_testvec.txt + for i, test := range []struct { + txRlp, addr string + }{ + {"f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "0xf0f6f18bca1b28cd68e4357452947e021241e9ce"}, + {"f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", "0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112"}, + {"f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", "0x2e485e0c23b4c3c542628a5f672eeab0ad4888be"}, + {"f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de", "0x82a88539669a3fd524d669e858935de5e5410cf0"}, + {"f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060", "0xf9358f2538fd5ccfeb848b64a96b743fcc930554"}, + {"f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1", "0xa8f7aba377317440bc5b26198a363ad22af1f3a4"}, + {"f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d", "0xf1f571dc362a0e5b2696b8e775f8491d3e50de35"}, + {"f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021", "0xd37922162ab7cea97c97a87551ed02c9a38b7332"}, + {"f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", "0x9bddad43f934d313c2b79ca28a432dd2b7281029"}, + {"f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f"}, + } { + signer := NewEIP155Signer(big.NewInt(1)) + + var tx *Transaction + err := rlp.DecodeBytes(common.Hex2Bytes(test.txRlp), &tx) + if err != nil { + t.Errorf("%d: %v", i, err) + continue + } + + from, err := Sender(signer, tx) + if err != nil { + t.Errorf("%d: %v", i, err) + continue + } + + addr := common.HexToAddress(test.addr) + if from != addr { + t.Errorf("%d: expected %x got %x", i, addr, from) + } + } +} + +func TestChainId(t *testing.T) { + key, _ := defaultTestKey() + + tx := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil) + + var err error + tx, err = SignTx(tx, NewEIP155Signer(big.NewInt(1)), key) + if err != nil { + t.Fatal(err) + } + + _, err = Sender(NewEIP155Signer(big.NewInt(2)), tx) + if !errors.Is(err, ErrInvalidChainId) { + t.Error("expected error:", ErrInvalidChainId, err) + } + + _, err = Sender(NewEIP155Signer(big.NewInt(1)), tx) + if err != nil { + t.Error("expected no error") + } +} + +type nilSigner struct { + v, r, s *big.Int + Signer +} + +func (ns *nilSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { + return ns.v, ns.r, ns.s, nil +} + +// TestNilSigner ensures a faulty Signer implementation does not result in nil signature values or panics. +func TestNilSigner(t *testing.T) { + key, _ := crypto.GenerateKey() + innerSigner := LatestSignerForChainID(big.NewInt(1)) + for i, signer := range []Signer{ + &nilSigner{v: nil, r: nil, s: nil, Signer: innerSigner}, + &nilSigner{v: big.NewInt(1), r: big.NewInt(1), s: nil, Signer: innerSigner}, + &nilSigner{v: big.NewInt(1), r: nil, s: big.NewInt(1), Signer: innerSigner}, + &nilSigner{v: nil, r: big.NewInt(1), s: big.NewInt(1), Signer: innerSigner}, + } { + t.Run(fmt.Sprintf("signer_%d", i), func(t *testing.T) { + t.Run("legacy", func(t *testing.T) { + legacyTx := createTestLegacyTxInner() + _, err := SignNewTx(key, signer, legacyTx) + if !errors.Is(err, ErrInvalidSig) { + t.Fatal("expected signature values error, no nil result or panic") + } + }) + // test Blob tx specifically, since the signature value types changed + t.Run("blobtx", func(t *testing.T) { + blobtx := createEmptyBlobTxInner(false) + _, err := SignNewTx(key, signer, blobtx) + if !errors.Is(err, ErrInvalidSig) { + t.Fatal("expected signature values error, no nil result or panic") + } + }) + }) + } +} + +func createTestLegacyTxInner() *LegacyTx { + return &LegacyTx{ + Nonce: uint64(0), + To: nil, + Value: big.NewInt(0), + Gas: params.TxGas, + GasPrice: big.NewInt(params.GWei), + Data: nil, + } +} diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go new file mode 100644 index 0000000..5dbf367 --- /dev/null +++ b/core/types/transaction_test.go @@ -0,0 +1,545 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "bytes" + "crypto/ecdsa" + "encoding/json" + "errors" + "fmt" + "maps" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +// The values in those tests are from the Transaction Tests +// at github.com/ethereum/tests. +var ( + testAddr = common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b") + + emptyTx = NewTransaction( + 0, + common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), + big.NewInt(0), 0, big.NewInt(0), + nil, + ) + + rightvrsTx, _ = NewTransaction( + 3, + testAddr, + big.NewInt(10), + 2000, + big.NewInt(1), + common.FromHex("5544"), + ).WithSignature( + HomesteadSigner{}, + common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"), + ) + + emptyEip2718Tx = NewTx(&AccessListTx{ + ChainID: big.NewInt(1), + Nonce: 3, + To: &testAddr, + Value: big.NewInt(10), + Gas: 25000, + GasPrice: big.NewInt(1), + Data: common.FromHex("5544"), + }) + + signedEip2718Tx, _ = emptyEip2718Tx.WithSignature( + NewEIP2930Signer(big.NewInt(1)), + common.Hex2Bytes("c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101"), + ) +) + +func TestDecodeEmptyTypedTx(t *testing.T) { + input := []byte{0x80} + var tx Transaction + err := rlp.DecodeBytes(input, &tx) + if err != errShortTypedTx { + t.Fatal("wrong error:", err) + } +} + +func TestTransactionSigHash(t *testing.T) { + var homestead HomesteadSigner + if homestead.Hash(emptyTx) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") { + t.Errorf("empty transaction hash mismatch, got %x", emptyTx.Hash()) + } + if homestead.Hash(rightvrsTx) != common.HexToHash("fe7a79529ed5f7c3375d06b26b186a8644e0e16c373d7a12be41c62d6042b77a") { + t.Errorf("RightVRS transaction hash mismatch, got %x", rightvrsTx.Hash()) + } +} + +func TestTransactionEncode(t *testing.T) { + txb, err := rlp.EncodeToBytes(rightvrsTx) + if err != nil { + t.Fatalf("encode error: %v", err) + } + should := common.FromHex("f86103018207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a8255441ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa08887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3") + if !bytes.Equal(txb, should) { + t.Errorf("encoded RLP mismatch, got %x", txb) + } +} + +func TestEIP2718TransactionSigHash(t *testing.T) { + s := NewEIP2930Signer(big.NewInt(1)) + if s.Hash(emptyEip2718Tx) != common.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") { + t.Errorf("empty EIP-2718 transaction hash mismatch, got %x", s.Hash(emptyEip2718Tx)) + } + if s.Hash(signedEip2718Tx) != common.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") { + t.Errorf("signed EIP-2718 transaction hash mismatch, got %x", s.Hash(signedEip2718Tx)) + } +} + +// This test checks signature operations on access list transactions. +func TestEIP2930Signer(t *testing.T) { + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + keyAddr = crypto.PubkeyToAddress(key.PublicKey) + signer1 = NewEIP2930Signer(big.NewInt(1)) + signer2 = NewEIP2930Signer(big.NewInt(2)) + tx0 = NewTx(&AccessListTx{Nonce: 1}) + tx1 = NewTx(&AccessListTx{ChainID: big.NewInt(1), Nonce: 1}) + tx2, _ = SignNewTx(key, signer2, &AccessListTx{ChainID: big.NewInt(2), Nonce: 1}) + ) + + tests := []struct { + tx *Transaction + signer Signer + wantSignerHash common.Hash + wantSenderErr error + wantSignErr error + wantHash common.Hash // after signing + }{ + { + tx: tx0, + signer: signer1, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantSenderErr: ErrInvalidChainId, + wantHash: common.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"), + }, + { + tx: tx1, + signer: signer1, + wantSenderErr: ErrInvalidSig, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantHash: common.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"), + }, + { + // This checks what happens when trying to sign an unsigned tx for the wrong chain. + tx: tx1, + signer: signer2, + wantSenderErr: ErrInvalidChainId, + wantSignerHash: common.HexToHash("367967247499343401261d718ed5aa4c9486583e4d89251afce47f4a33c33362"), + wantSignErr: ErrInvalidChainId, + }, + { + // This checks what happens when trying to re-sign a signed tx for the wrong chain. + tx: tx2, + signer: signer1, + wantSenderErr: ErrInvalidChainId, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantSignErr: ErrInvalidChainId, + }, + } + + for i, test := range tests { + sigHash := test.signer.Hash(test.tx) + if sigHash != test.wantSignerHash { + t.Errorf("test %d: wrong sig hash: got %x, want %x", i, sigHash, test.wantSignerHash) + } + sender, err := Sender(test.signer, test.tx) + if !errors.Is(err, test.wantSenderErr) { + t.Errorf("test %d: wrong Sender error %q", i, err) + } + if err == nil && sender != keyAddr { + t.Errorf("test %d: wrong sender address %x", i, sender) + } + signedTx, err := SignTx(test.tx, test.signer, key) + if !errors.Is(err, test.wantSignErr) { + t.Fatalf("test %d: wrong SignTx error %q", i, err) + } + if signedTx != nil { + if signedTx.Hash() != test.wantHash { + t.Errorf("test %d: wrong tx hash after signing: got %x, want %x", i, signedTx.Hash(), test.wantHash) + } + } + } +} + +func TestEIP2718TransactionEncode(t *testing.T) { + // RLP representation + { + have, err := rlp.EncodeToBytes(signedEip2718Tx) + if err != nil { + t.Fatalf("encode error: %v", err) + } + want := common.FromHex("b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521") + if !bytes.Equal(have, want) { + t.Errorf("encoded RLP mismatch, got %x", have) + } + } + // Binary representation + { + have, err := signedEip2718Tx.MarshalBinary() + if err != nil { + t.Fatalf("encode error: %v", err) + } + want := common.FromHex("01f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521") + if !bytes.Equal(have, want) { + t.Errorf("encoded RLP mismatch, got %x", have) + } + } +} + +func decodeTx(data []byte) (*Transaction, error) { + var tx Transaction + t, err := &tx, rlp.DecodeBytes(data, &tx) + return t, err +} + +func defaultTestKey() (*ecdsa.PrivateKey, common.Address) { + key, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") + addr := crypto.PubkeyToAddress(key.PublicKey) + return key, addr +} + +func TestRecipientEmpty(t *testing.T) { + _, addr := defaultTestKey() + tx, err := decodeTx(common.Hex2Bytes("f8498080808080011ca09b16de9d5bdee2cf56c28d16275a4da68cd30273e2525f3959f5d62557489921a0372ebd8fb3345f7db7b5a86d42e24d36e983e259b0664ceb8c227ec9af572f3d")) + if err != nil { + t.Fatal(err) + } + + from, err := Sender(HomesteadSigner{}, tx) + if err != nil { + t.Fatal(err) + } + if addr != from { + t.Fatal("derived address doesn't match") + } +} + +func TestRecipientNormal(t *testing.T) { + _, addr := defaultTestKey() + + tx, err := decodeTx(common.Hex2Bytes("f85d80808094000000000000000000000000000000000000000080011ca0527c0d8f5c63f7b9f41324a7c8a563ee1190bcbf0dac8ab446291bdbf32f5c79a0552c4ef0a09a04395074dab9ed34d3fbfb843c2f2546cc30fe89ec143ca94ca6")) + if err != nil { + t.Fatal(err) + } + + from, err := Sender(HomesteadSigner{}, tx) + if err != nil { + t.Fatal(err) + } + if addr != from { + t.Fatal("derived address doesn't match") + } +} + +// TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON. +func TestTransactionCoding(t *testing.T) { + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("could not generate key: %v", err) + } + var ( + signer = NewEIP2930Signer(common.Big1) + addr = common.HexToAddress("0x0000000000000000000000000000000000000001") + recipient = common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + accesses = AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}} + ) + for i := uint64(0); i < 500; i++ { + var txdata TxData + switch i % 5 { + case 0: + // Legacy tx. + txdata = &LegacyTx{ + Nonce: i, + To: &recipient, + Gas: 1, + GasPrice: big.NewInt(2), + Data: []byte("abcdef"), + } + case 1: + // Legacy tx contract creation. + txdata = &LegacyTx{ + Nonce: i, + Gas: 1, + GasPrice: big.NewInt(2), + Data: []byte("abcdef"), + } + case 2: + // Tx with non-zero access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + To: &recipient, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: accesses, + Data: []byte("abcdef"), + } + case 3: + // Tx with empty access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + To: &recipient, + Gas: 123457, + GasPrice: big.NewInt(10), + Data: []byte("abcdef"), + } + case 4: + // Contract creation with access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: accesses, + } + } + tx, err := SignNewTx(key, signer, txdata) + if err != nil { + t.Fatalf("could not sign transaction: %v", err) + } + // RLP + parsedTx, err := encodeDecodeBinary(tx) + if err != nil { + t.Fatal(err) + } + if err := assertEqual(parsedTx, tx); err != nil { + t.Fatal(err) + } + + // JSON + parsedTx, err = encodeDecodeJSON(tx) + if err != nil { + t.Fatal(err) + } + if err := assertEqual(parsedTx, tx); err != nil { + t.Fatal(err) + } + } +} + +func encodeDecodeJSON(tx *Transaction) (*Transaction, error) { + data, err := json.Marshal(tx) + if err != nil { + return nil, fmt.Errorf("json encoding failed: %v", err) + } + var parsedTx = &Transaction{} + if err := json.Unmarshal(data, &parsedTx); err != nil { + return nil, fmt.Errorf("json decoding failed: %v", err) + } + return parsedTx, nil +} + +func encodeDecodeBinary(tx *Transaction) (*Transaction, error) { + data, err := tx.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("rlp encoding failed: %v", err) + } + var parsedTx = &Transaction{} + if err := parsedTx.UnmarshalBinary(data); err != nil { + return nil, fmt.Errorf("rlp decoding failed: %v", err) + } + return parsedTx, nil +} + +func assertEqual(orig *Transaction, cpy *Transaction) error { + // compare nonce, price, gaslimit, recipient, amount, payload, V, R, S + if want, got := orig.Hash(), cpy.Hash(); want != got { + return fmt.Errorf("parsed tx differs from original tx, want %v, got %v", want, got) + } + if want, got := orig.ChainId(), cpy.ChainId(); want.Cmp(got) != 0 { + return fmt.Errorf("invalid chain id, want %d, got %d", want, got) + } + if orig.AccessList() != nil { + if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) { + return errors.New("access list wrong") + } + } + return nil +} + +func TestTransactionSizes(t *testing.T) { + signer := NewLondonSigner(big.NewInt(123)) + key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + to := common.HexToAddress("0x01") + for i, txdata := range []TxData{ + &AccessListTx{ + ChainID: big.NewInt(123), + Nonce: 0, + To: nil, + Value: big.NewInt(1000), + Gas: 21000, + GasPrice: big.NewInt(100000), + }, + &LegacyTx{ + Nonce: 1, + GasPrice: big.NewInt(500), + Gas: 1000000, + To: &to, + Value: big.NewInt(1), + }, + &AccessListTx{ + ChainID: big.NewInt(123), + Nonce: 1, + GasPrice: big.NewInt(500), + Gas: 1000000, + To: &to, + Value: big.NewInt(1), + AccessList: AccessList{ + AccessTuple{ + Address: common.HexToAddress("0x01"), + StorageKeys: []common.Hash{common.HexToHash("0x01")}, + }}, + }, + &DynamicFeeTx{ + ChainID: big.NewInt(123), + Nonce: 1, + Gas: 1000000, + To: &to, + Value: big.NewInt(1), + GasTipCap: big.NewInt(500), + GasFeeCap: big.NewInt(500), + }, + } { + tx, err := SignNewTx(key, signer, txdata) + if err != nil { + t.Fatalf("test %d: %v", i, err) + } + bin, _ := tx.MarshalBinary() + + // Check initial calc + if have, want := int(tx.Size()), len(bin); have != want { + t.Errorf("test %d: size wrong, have %d want %d", i, have, want) + } + // Check cached version too + if have, want := int(tx.Size()), len(bin); have != want { + t.Errorf("test %d: (cached) size wrong, have %d want %d", i, have, want) + } + // Check unmarshalled version too + utx := new(Transaction) + if err := utx.UnmarshalBinary(bin); err != nil { + t.Fatalf("test %d: failed to unmarshal tx: %v", i, err) + } + if have, want := int(utx.Size()), len(bin); have != want { + t.Errorf("test %d: (unmarshalled) size wrong, have %d want %d", i, have, want) + } + } +} + +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 := maps.Clone(baseJson) + + // 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 new file mode 100644 index 0000000..730a77b --- /dev/null +++ b/core/types/tx_access_list.go @@ -0,0 +1,129 @@ +// 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 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 + +// AccessList is an EIP-2930 access list. +type AccessList []AccessTuple + +// AccessTuple is the element type of an access list. +type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` +} + +// StorageKeys returns the total number of storage keys in the access list. +func (al AccessList) StorageKeys() int { + sum := 0 + for _, tuple := range al { + sum += len(tuple.StorageKeys) + } + return sum +} + +// AccessListTx is the data of EIP-2930 access list transactions. +type AccessListTx struct { + ChainID *big.Int // destination chain ID + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int // wei amount + Data []byte // contract invocation input data + AccessList AccessList // EIP-2930 access list + V, R, S *big.Int // signature values +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *AccessListTx) copy() TxData { + cpy := &AccessListTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(big.Int), + ChainID: new(big.Int), + GasPrice: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + copy(cpy.AccessList, tx.AccessList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// 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) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + return dst.Set(tx.GasPrice) +} + +func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +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 new file mode 100644 index 0000000..ce1f287 --- /dev/null +++ b/core/types/tx_blob.go @@ -0,0 +1,244 @@ +// 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 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" +) + +// BlobTx represents an EIP-4844 transaction. +type BlobTx struct { + ChainID *uint256.Int + Nonce uint64 + GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *uint256.Int // a.k.a. maxFeePerGas + Gas uint64 + To common.Address + Value *uint256.Int + Data []byte + AccessList AccessList + 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 signing. + 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 { + hasher := sha256.New() + h := make([]common.Hash, len(sc.Commitments)) + for i := range sc.Blobs { + h[i] = kzg4844.CalcBlobHashV1(hasher, &sc.Commitments[i]) + } + return h +} + +// encodedSize computes the RLP size of the sidecar elements. This does NOT return the +// encoded size of the BlobTxSidecar, it's just a helper for tx.Size(). +func (sc *BlobTxSidecar) encodedSize() uint64 { + var blobs, commitments, proofs uint64 + for i := range sc.Blobs { + blobs += rlp.BytesSize(sc.Blobs[i][:]) + } + for i := range sc.Commitments { + commitments += rlp.BytesSize(sc.Commitments[i][:]) + } + for i := range sc.Proofs { + proofs += rlp.BytesSize(sc.Proofs[i][:]) + } + return rlp.ListSize(blobs) + rlp.ListSize(commitments) + rlp.ListSize(proofs) +} + +// blobTxWithBlobs is used for encoding of transactions when blobs are present. +type blobTxWithBlobs struct { + BlobTx *BlobTx + Blobs []kzg4844.Blob + Commitments []kzg4844.Commitment + Proofs []kzg4844.Proof +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *BlobTx) copy() TxData { + cpy := &BlobTx{ + Nonce: tx.Nonce, + To: tx.To, + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + BlobHashes: make([]common.Hash, len(tx.BlobHashes)), + Value: new(uint256.Int), + ChainID: new(uint256.Int), + GasTipCap: new(uint256.Int), + GasFeeCap: new(uint256.Int), + BlobFeeCap: new(uint256.Int), + V: new(uint256.Int), + R: new(uint256.Int), + S: new(uint256.Int), + } + copy(cpy.AccessList, tx.AccessList) + copy(cpy.BlobHashes, tx.BlobHashes) + + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.BlobFeeCap != nil { + cpy.BlobFeeCap.Set(tx.BlobFeeCap) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + 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) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap.ToBig()) + } + tip := dst.Sub(tx.GasFeeCap.ToBig(), baseFee) + if tip.Cmp(tx.GasTipCap.ToBig()) > 0 { + tip.Set(tx.GasTipCap.ToBig()) + } + return tip.Add(tip, baseFee) +} + +func (tx *BlobTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V.ToBig(), tx.R.ToBig(), tx.S.ToBig() +} + +func (tx *BlobTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID.SetFromBig(chainID) + tx.V.SetFromBig(v) + tx.R.SetFromBig(r) + tx.S.SetFromBig(s) +} + +func (tx *BlobTx) withoutSidecar() *BlobTx { + cpy := *tx + cpy.Sidecar = nil + return &cpy +} + +func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx { + cpy := *tx + cpy.Sidecar = sideCar + 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 +} diff --git a/core/types/tx_blob_test.go b/core/types/tx_blob_test.go new file mode 100644 index 0000000..6bd0f18 --- /dev/null +++ b/core/types/tx_blob_test.go @@ -0,0 +1,95 @@ +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 = new(kzg4844.Blob) + emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) + emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) +) + +func createEmptyBlobTx(key *ecdsa.PrivateKey, withSidecar bool) *Transaction { + blobtx := createEmptyBlobTxInner(withSidecar) + signer := NewCancunSigner(blobtx.ChainID.ToBig()) + return MustSignNewTx(key, signer, blobtx) +} + +func createEmptyBlobTxInner(withSidecar bool) *BlobTx { + 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 + } + return blobtx +} diff --git a/core/types/tx_dynamic_fee.go b/core/types/tx_dynamic_fee.go new file mode 100644 index 0000000..8b5b514 --- /dev/null +++ b/core/types/tx_dynamic_fee.go @@ -0,0 +1,125 @@ +// 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 types + +import ( + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +// DynamicFeeTx represents an EIP-1559 transaction. +type DynamicFeeTx struct { + ChainID *big.Int + Nonce uint64 + GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas + GasFeeCap *big.Int // a.k.a. maxFeePerGas + Gas uint64 + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int + Data []byte + AccessList AccessList + + // Signature values + V *big.Int `json:"v" gencodec:"required"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *DynamicFeeTx) copy() TxData { + cpy := &DynamicFeeTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(big.Int), + ChainID: new(big.Int), + GasTipCap: new(big.Int), + GasFeeCap: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + copy(cpy.AccessList, tx.AccessList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// 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) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap) + } + tip := dst.Sub(tx.GasFeeCap, baseFee) + if tip.Cmp(tx.GasTipCap) > 0 { + tip.Set(tx.GasTipCap) + } + return tip.Add(tip, baseFee) +} + +func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +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 new file mode 100644 index 0000000..71025b7 --- /dev/null +++ b/core/types/tx_legacy.go @@ -0,0 +1,125 @@ +// 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 types + +import ( + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// LegacyTx is the transaction data of the original Ethereum transactions. +type LegacyTx struct { + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int // wei amount + Data []byte // contract invocation input data + V, R, S *big.Int // signature values +} + +// NewTransaction creates an unsigned legacy transaction. +// Deprecated: use NewTx instead. +func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { + return NewTx(&LegacyTx{ + Nonce: nonce, + To: &to, + Value: amount, + Gas: gasLimit, + GasPrice: gasPrice, + Data: data, + }) +} + +// NewContractCreation creates an unsigned legacy transaction. +// Deprecated: use NewTx instead. +func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { + return NewTx(&LegacyTx{ + Nonce: nonce, + Value: amount, + Gas: gasLimit, + GasPrice: gasPrice, + Data: data, + }) +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *LegacyTx) copy() TxData { + cpy := &LegacyTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are initialized below. + Value: new(big.Int), + GasPrice: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// 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) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + return dst.Set(tx.GasPrice) +} + +func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +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/types/types_test.go b/core/types/types_test.go new file mode 100644 index 0000000..1fb386d --- /dev/null +++ b/core/types/types_test.go @@ -0,0 +1,148 @@ +// 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 types + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +type devnull struct{ len int } + +func (d *devnull) Write(p []byte) (int, error) { + d.len += len(p) + return len(p), nil +} + +func BenchmarkEncodeRLP(b *testing.B) { + benchRLP(b, true) +} + +func BenchmarkDecodeRLP(b *testing.B) { + benchRLP(b, false) +} + +func benchRLP(b *testing.B, encode bool) { + key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + to := common.HexToAddress("0x00000000000000000000000000000000deadbeef") + signer := NewLondonSigner(big.NewInt(1337)) + for _, tc := range []struct { + name string + obj interface{} + }{ + { + "legacy-header", + &Header{ + Difficulty: big.NewInt(10000000000), + Number: big.NewInt(1000), + GasLimit: 8_000_000, + GasUsed: 8_000_000, + Time: 555, + Extra: make([]byte, 32), + }, + }, + { + "london-header", + &Header{ + Difficulty: big.NewInt(10000000000), + Number: big.NewInt(1000), + GasLimit: 8_000_000, + GasUsed: 8_000_000, + Time: 555, + Extra: make([]byte, 32), + BaseFee: big.NewInt(10000000000), + }, + }, + { + "receipt-for-storage", + &ReceiptForStorage{ + Status: ReceiptStatusSuccessful, + CumulativeGasUsed: 0x888888888, + Logs: make([]*Log, 0), + }, + }, + { + "receipt-full", + &Receipt{ + Status: ReceiptStatusSuccessful, + CumulativeGasUsed: 0x888888888, + Logs: make([]*Log, 0), + }, + }, + { + "legacy-transaction", + MustSignNewTx(key, signer, + &LegacyTx{ + Nonce: 1, + GasPrice: big.NewInt(500), + Gas: 1000000, + To: &to, + Value: big.NewInt(1), + }), + }, + { + "access-transaction", + MustSignNewTx(key, signer, + &AccessListTx{ + Nonce: 1, + GasPrice: big.NewInt(500), + Gas: 1000000, + To: &to, + Value: big.NewInt(1), + }), + }, + { + "1559-transaction", + MustSignNewTx(key, signer, + &DynamicFeeTx{ + Nonce: 1, + Gas: 1000000, + To: &to, + Value: big.NewInt(1), + GasTipCap: big.NewInt(500), + GasFeeCap: big.NewInt(500), + }), + }, + } { + if encode { + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + var null = &devnull{} + for i := 0; i < b.N; i++ { + rlp.Encode(null, tc.obj) + } + b.SetBytes(int64(null.len / b.N)) + }) + } else { + data, _ := rlp.EncodeToBytes(tc.obj) + // Test decoding + b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + if err := rlp.DecodeBytes(data, tc.obj); err != nil { + b.Fatal(err) + } + } + b.SetBytes(int64(len(data))) + }) + } + } +} diff --git a/core/types/withdrawal.go b/core/types/withdrawal.go new file mode 100644 index 0000000..d1ad918 --- /dev/null +++ b/core/types/withdrawal.go @@ -0,0 +1,56 @@ +// 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 types + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rlp" +) + +//go:generate go run github.com/fjl/gencodec -type Withdrawal -field-override withdrawalMarshaling -out gen_withdrawal_json.go +//go:generate go run ../../rlp/rlpgen -type Withdrawal -out gen_withdrawal_rlp.go + +// Withdrawal represents a validator withdrawal from the consensus layer. +type Withdrawal struct { + Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer + Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal + Address common.Address `json:"address"` // target address for withdrawn ether + Amount uint64 `json:"amount"` // value of withdrawal in Gwei +} + +// field type overrides for gencodec +type withdrawalMarshaling struct { + Index hexutil.Uint64 + Validator hexutil.Uint64 + Amount hexutil.Uint64 +} + +// Withdrawals implements DerivableList for withdrawals. +type Withdrawals []*Withdrawal + +// Len returns the length of s. +func (s Withdrawals) Len() int { return len(s) } + +// EncodeIndex encodes the i'th withdrawal to w. Note that this does not check for errors +// because we assume that *Withdrawal will only ever contain valid withdrawals that were either +// constructed by decoding or via public API in this package. +func (s Withdrawals) EncodeIndex(i int, w *bytes.Buffer) { + rlp.Encode(w, s[i]) +} diff --git a/core/vm/analysis.go b/core/vm/analysis.go new file mode 100644 index 0000000..38af908 --- /dev/null +++ b/core/vm/analysis.go @@ -0,0 +1,118 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +const ( + set2BitsMask = uint16(0b11) + set3BitsMask = uint16(0b111) + set4BitsMask = uint16(0b1111) + set5BitsMask = uint16(0b1_1111) + set6BitsMask = uint16(0b11_1111) + set7BitsMask = uint16(0b111_1111) +) + +// bitvec is a bit vector which maps bytes in a program. +// An unset bit means the byte is an opcode, a set bit means +// it's data (i.e. argument of PUSHxx). +type bitvec []byte + +func (bits bitvec) set1(pos uint64) { + bits[pos/8] |= 1 << (pos % 8) +} + +func (bits bitvec) setN(flag uint16, pos uint64) { + a := flag << (pos % 8) + bits[pos/8] |= byte(a) + if b := byte(a >> 8); b != 0 { + bits[pos/8+1] = b + } +} + +func (bits bitvec) set8(pos uint64) { + a := byte(0xFF << (pos % 8)) + bits[pos/8] |= a + bits[pos/8+1] = ^a +} + +func (bits bitvec) set16(pos uint64) { + a := byte(0xFF << (pos % 8)) + bits[pos/8] |= a + bits[pos/8+1] = 0xFF + bits[pos/8+2] = ^a +} + +// codeSegment checks if the position is in a code segment. +func (bits *bitvec) codeSegment(pos uint64) bool { + return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0 +} + +// codeBitmap collects data locations in code. +func codeBitmap(code []byte) bitvec { + // The bitmap is 4 bytes longer than necessary, in case the code + // ends with a PUSH32, the algorithm will set bits on the + // bitvector outside the bounds of the actual code. + bits := make(bitvec, len(code)/8+1+4) + return codeBitmapInternal(code, bits) +} + +// codeBitmapInternal is the internal implementation of codeBitmap. +// It exists for the purpose of being able to run benchmark tests +// without dynamic allocations affecting the results. +func codeBitmapInternal(code, bits bitvec) bitvec { + for pc := uint64(0); pc < uint64(len(code)); { + op := OpCode(code[pc]) + pc++ + if int8(op) < int8(PUSH1) { // If not PUSH (the int8(op) > int(PUSH32) is always false). + continue + } + numbits := op - PUSH1 + 1 + if numbits >= 8 { + for ; numbits >= 16; numbits -= 16 { + bits.set16(pc) + pc += 16 + } + for ; numbits >= 8; numbits -= 8 { + bits.set8(pc) + pc += 8 + } + } + switch numbits { + case 1: + bits.set1(pc) + pc += 1 + case 2: + bits.setN(set2BitsMask, pc) + pc += 2 + case 3: + bits.setN(set3BitsMask, pc) + pc += 3 + case 4: + bits.setN(set4BitsMask, pc) + pc += 4 + case 5: + bits.setN(set5BitsMask, pc) + pc += 5 + case 6: + bits.setN(set6BitsMask, pc) + pc += 6 + case 7: + bits.setN(set7BitsMask, pc) + pc += 7 + } + } + return bits +} diff --git a/core/vm/analysis_test.go b/core/vm/analysis_test.go new file mode 100644 index 0000000..471d2b4 --- /dev/null +++ b/core/vm/analysis_test.go @@ -0,0 +1,107 @@ +// 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 vm + +import ( + "math/bits" + "testing" + + "github.com/ethereum/go-ethereum/crypto" +) + +func TestJumpDestAnalysis(t *testing.T) { + tests := []struct { + code []byte + exp byte + which int + }{ + {[]byte{byte(PUSH1), 0x01, 0x01, 0x01}, 0b0000_0010, 0}, + {[]byte{byte(PUSH1), byte(PUSH1), byte(PUSH1), byte(PUSH1)}, 0b0000_1010, 0}, + {[]byte{0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1)}, 0b0101_0100, 0}, + {[]byte{byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), 0x01, 0x01, 0x01}, bits.Reverse8(0x7F), 0}, + {[]byte{byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0001, 1}, + {[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), byte(PUSH2), byte(PUSH2), 0x01, 0x01, 0x01}, 0b1100_0000, 0}, + {[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0000, 1}, + {[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0010_1110, 0}, + {[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0000, 1}, + {[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1100, 0}, + {[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0011, 1}, + {[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1110, 0}, + {[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1111, 1}, + {[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0001, 2}, + {[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0b1111_1110, 0}, + {[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0b0000_0101, 1}, + {[]byte{byte(PUSH32)}, 0b1111_1110, 0}, + {[]byte{byte(PUSH32)}, 0b1111_1111, 1}, + {[]byte{byte(PUSH32)}, 0b1111_1111, 2}, + {[]byte{byte(PUSH32)}, 0b1111_1111, 3}, + {[]byte{byte(PUSH32)}, 0b0000_0001, 4}, + } + for i, test := range tests { + ret := codeBitmap(test.code) + if ret[test.which] != test.exp { + t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which]) + } + } +} + +const analysisCodeSize = 1200 * 1024 + +func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) { + // 1.4 ms + code := make([]byte, analysisCodeSize) + bench.SetBytes(analysisCodeSize) + bench.ResetTimer() + for i := 0; i < bench.N; i++ { + codeBitmap(code) + } + bench.StopTimer() +} +func BenchmarkJumpdestHashing_1200k(bench *testing.B) { + // 4 ms + code := make([]byte, analysisCodeSize) + bench.SetBytes(analysisCodeSize) + bench.ResetTimer() + for i := 0; i < bench.N; i++ { + crypto.Keccak256Hash(code) + } + bench.StopTimer() +} + +func BenchmarkJumpdestOpAnalysis(bench *testing.B) { + var op OpCode + bencher := func(b *testing.B) { + code := make([]byte, analysisCodeSize) + b.SetBytes(analysisCodeSize) + for i := range code { + code[i] = byte(op) + } + bits := make(bitvec, len(code)/8+1+4) + b.ResetTimer() + for i := 0; i < b.N; i++ { + clear(bits) + codeBitmapInternal(code, bits) + } + } + for op = PUSH1; op <= PUSH32; op++ { + bench.Run(op.String(), bencher) + } + op = JUMPDEST + bench.Run(op.String(), bencher) + op = STOP + bench.Run(op.String(), bencher) +} diff --git a/core/vm/common.go b/core/vm/common.go new file mode 100644 index 0000000..ba75950 --- /dev/null +++ b/core/vm/common.go @@ -0,0 +1,94 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/holiman/uint256" +) + +// calcMemSize64 calculates the required memory size, and returns +// the size and whether the result overflowed uint64 +func calcMemSize64(off, l *uint256.Int) (uint64, bool) { + if !l.IsUint64() { + return 0, true + } + return calcMemSize64WithUint(off, l.Uint64()) +} + +// calcMemSize64WithUint calculates the required memory size, and returns +// the size and whether the result overflowed uint64 +// Identical to calcMemSize64, but length is a uint64 +func calcMemSize64WithUint(off *uint256.Int, length64 uint64) (uint64, bool) { + // if length is zero, memsize is always zero, regardless of offset + if length64 == 0 { + return 0, false + } + // Check that offset doesn't overflow + offset64, overflow := off.Uint64WithOverflow() + if overflow { + return 0, true + } + val := offset64 + length64 + // if value < either of it's parts, then it overflowed + return val, val < offset64 +} + +// getData returns a slice from the data based on the start and size and pads +// up to size with zero's. This function is overflow safe. +func getData(data []byte, start uint64, size uint64) []byte { + length := uint64(len(data)) + if start > length { + start = length + } + end := start + size + if end > length { + end = length + } + return common.RightPadBytes(data[start:end], int(size)) +} + +func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) { + length := uint64(len(data)) + if start > length { + start = length + } + end := start + size + if end > length { + end = length + } + return common.RightPadBytes(data[start:end], int(size)), start, end - start +} + +// toWordSize returns the ceiled word size required for memory expansion. +func toWordSize(size uint64) uint64 { + if size > math.MaxUint64-31 { + return math.MaxUint64/32 + 1 + } + + return (size + 31) / 32 +} + +func allZero(b []byte) bool { + for _, byte := range b { + if byte != 0 { + return false + } + } + return true +} diff --git a/core/vm/contract.go b/core/vm/contract.go new file mode 100644 index 0000000..cfda75b --- /dev/null +++ b/core/vm/contract.go @@ -0,0 +1,210 @@ +// 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 vm + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/holiman/uint256" +) + +// ContractRef is a reference to the contract's backing object +type ContractRef interface { + Address() common.Address +} + +// AccountRef implements ContractRef. +// +// Account references are used during EVM initialisation and +// 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 an Address +func (ar AccountRef) Address() common.Address { return (common.Address)(ar) } + +// Contract represents an ethereum contract in the state database. It contains +// the contract code, calling arguments. Contract implements ContractRef +type Contract struct { + // CallerAddress is the result of the caller which initialised this + // contract. However when the "call method" is delegated this value + // needs to be initialised to that of the caller's caller. + CallerAddress common.Address + caller ContractRef + self ContractRef + + jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis. + analysis bitvec // Locally cached result of JUMPDEST analysis + + Code []byte + CodeHash common.Hash + CodeAddr *common.Address + Input []byte + + // is the execution frame represented by this object a contract deployment + IsDeployment bool + + Gas uint64 + value *uint256.Int +} + +// NewContract returns a new contract environment for the execution of EVM. +func NewContract(caller ContractRef, object ContractRef, value *uint256.Int, gas uint64) *Contract { + c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object} + + if parent, ok := caller.(*Contract); ok { + // Reuse JUMPDEST analysis from parent context if available. + c.jumpdests = parent.jumpdests + } else { + c.jumpdests = make(map[common.Hash]bitvec) + } + + // Gas should be a pointer so it can safely be reduced through the run + // This pointer will be off the state transition + c.Gas = gas + // ensures a value is set + c.value = value + + return c +} + +func (c *Contract) validJumpdest(dest *uint256.Int) bool { + udest, overflow := dest.Uint64WithOverflow() + // PC cannot go beyond len(code) and certainly can't be bigger than 63bits. + // Don't bother checking for JUMPDEST in that case. + if overflow || udest >= uint64(len(c.Code)) { + return false + } + // Only JUMPDESTs allowed for destinations + if OpCode(c.Code[udest]) != JUMPDEST { + return false + } + return c.isCode(udest) +} + +// isCode returns true if the provided PC location is an actual opcode, as +// opposed to a data-segment following a PUSHN operation. +func (c *Contract) isCode(udest uint64) bool { + // Do we already have an analysis laying around? + if c.analysis != nil { + return c.analysis.codeSegment(udest) + } + // Do we have a contract hash already? + // If we do have a hash, that means it's a 'regular' contract. For regular + // contracts ( not temporary initcode), we store the analysis in a map + if c.CodeHash != (common.Hash{}) { + // Does parent context have the analysis? + analysis, exist := c.jumpdests[c.CodeHash] + if !exist { + // Do the analysis and save in parent context + // We do not need to store it in c.analysis + analysis = codeBitmap(c.Code) + c.jumpdests[c.CodeHash] = analysis + } + // Also stash it in current contract for faster access + c.analysis = analysis + return analysis.codeSegment(udest) + } + // We don't have the code hash, most likely a piece of initcode not already + // in state trie. In that case, we do an analysis, and save it locally, so + // we don't have to recalculate it for every JUMP instruction in the execution + // However, we don't save it within the parent context + if c.analysis == nil { + c.analysis = codeBitmap(c.Code) + } + return c.analysis.codeSegment(udest) +} + +// AsDelegate sets the contract to be a delegate call and returns the current +// contract (for chaining calls) +func (c *Contract) AsDelegate() *Contract { + // NOTE: caller must, at all times be a contract. It should never happen + // that caller is something other than a Contract. + parent := c.caller.(*Contract) + c.CallerAddress = parent.CallerAddress + c.value = parent.value + + return c +} + +// GetOp returns the n'th element in the contract's byte array +func (c *Contract) GetOp(n uint64) OpCode { + if n < uint64(len(c.Code)) { + return OpCode(c.Code[n]) + } + + return STOP +} + +// Caller returns the caller of the contract. +// +// Caller will recursively call caller when the contract is a delegate +// call, including that of caller's caller. +func (c *Contract) Caller() common.Address { + return c.CallerAddress +} + +// UseGas attempts the use gas and subtracts it and returns true on success +func (c *Contract) UseGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) { + if c.Gas < gas { + return false + } + if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { + logger.OnGasChange(c.Gas, c.Gas-gas, reason) + } + c.Gas -= gas + return true +} + +// RefundGas refunds gas to the contract +func (c *Contract) RefundGas(gas uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) { + if gas == 0 { + return + } + if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored { + logger.OnGasChange(c.Gas, c.Gas+gas, reason) + } + c.Gas += gas +} + +// Address returns the contracts address +func (c *Contract) Address() common.Address { + return c.self.Address() +} + +// Value returns the contract's value (sent to it from it's caller) +func (c *Contract) Value() *uint256.Int { + return c.value +} + +// SetCallCode sets the code of the contract and address of the backing data +// object +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { + c.Code = code + c.CodeHash = hash + c.CodeAddr = addr +} + +// SetCodeOptionalHash can be used to provide code, but it's optional to provide hash. +// In case hash is not provided, the jumpdest analysis will not be saved to the parent context +func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash) { + c.Code = codeAndHash.code + c.CodeHash = codeAndHash.hash + c.CodeAddr = addr +} diff --git a/core/vm/contracts.go b/core/vm/contracts.go new file mode 100644 index 0000000..e61f713 --- /dev/null +++ b/core/vm/contracts.go @@ -0,0 +1,1229 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "github.com/ethereum/go-ethereum/crypto" + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/crypto/blake2b" + "github.com/ethereum/go-ethereum/crypto/bn256" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "golang.org/x/crypto/ripemd160" +) + +// PrecompiledContract is the basic interface for native Go contracts. The implementation +// requires a deterministic gas count based on the input size of the Run method of the +// contract. +type PrecompiledContract interface { + RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use + Run(evm *EVM, input []byte) ([]byte, error) // Run runs the precompiled contract +} + +// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum +// contracts used in the Frontier and Homestead releases. +var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x1}): &ecrecover{}, + common.BytesToAddress([]byte{0x2}): &sha256hash{}, + common.BytesToAddress([]byte{0x3}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x4}): &dataCopy{}, +} + +// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum +// contracts used in the Byzantium release. +var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x1}): &ecrecover{}, + common.BytesToAddress([]byte{0x2}): &sha256hash{}, + common.BytesToAddress([]byte{0x3}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x4}): &dataCopy{}, + common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: false}, + common.BytesToAddress([]byte{0x6}): &bn256AddByzantium{}, + common.BytesToAddress([]byte{0x7}): &bn256ScalarMulByzantium{}, + common.BytesToAddress([]byte{0x8}): &bn256PairingByzantium{}, +} + +// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum +// contracts used in the Istanbul release. +var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x1}): &ecrecover{}, + common.BytesToAddress([]byte{0x2}): &sha256hash{}, + common.BytesToAddress([]byte{0x3}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x4}): &dataCopy{}, + common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: false}, + common.BytesToAddress([]byte{0x6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{0x7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{0x8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{0x9}): &blake2F{}, +} + +// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum +// contracts used in the Berlin release. +var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x1}): &ecrecover{}, + common.BytesToAddress([]byte{0x2}): &sha256hash{}, + common.BytesToAddress([]byte{0x3}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x4}): &dataCopy{}, + common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{0x6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{0x7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{0x8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{0x9}): &blake2F{}, +} + +// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum +// contracts used in the Cancun release. +var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x1}): &ecrecover{}, + common.BytesToAddress([]byte{0x2}): &sha256hash{}, + common.BytesToAddress([]byte{0x3}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x4}): &dataCopy{}, + common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{0x6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{0x7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{0x8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{0x9}): &blake2F{}, + common.BytesToAddress([]byte{0xa}): &kzgPointEvaluation{}, + common.BytesToAddress([]byte{0x1a}): &ipGraph{}, +} + +// PrecompiledContractsPrague contains the set of pre-compiled Ethereum +// contracts used in the Prague release. +var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{0x01}): &ecrecover{}, + common.BytesToAddress([]byte{0x02}): &sha256hash{}, + common.BytesToAddress([]byte{0x03}): &ripemd160hash{}, + common.BytesToAddress([]byte{0x04}): &dataCopy{}, + common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{0x09}): &blake2F{}, + common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{}, + common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{}, + common.BytesToAddress([]byte{0x0c}): &bls12381G1Mul{}, + common.BytesToAddress([]byte{0x0d}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{0x0e}): &bls12381G2Add{}, + common.BytesToAddress([]byte{0x0f}): &bls12381G2Mul{}, + common.BytesToAddress([]byte{0x10}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{0x11}): &bls12381Pairing{}, + common.BytesToAddress([]byte{0x12}): &bls12381MapG1{}, + common.BytesToAddress([]byte{0x13}): &bls12381MapG2{}, +} + +var PrecompiledContractsBLS = PrecompiledContractsPrague + +var PrecompiledContractsVerkle = PrecompiledContractsPrague + +var ( + PrecompiledAddressesPrague []common.Address + PrecompiledAddressesCancun []common.Address + PrecompiledAddressesBerlin []common.Address + PrecompiledAddressesIstanbul []common.Address + PrecompiledAddressesByzantium []common.Address + PrecompiledAddressesHomestead []common.Address +) + +func init() { + for k := range PrecompiledContractsHomestead { + PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k) + } + for k := range PrecompiledContractsByzantium { + PrecompiledAddressesByzantium = append(PrecompiledAddressesByzantium, k) + } + for k := range PrecompiledContractsIstanbul { + PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) + } + for k := range PrecompiledContractsBerlin { + PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) + } + for k := range PrecompiledContractsCancun { + PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k) + } + for k := range PrecompiledContractsPrague { + PrecompiledAddressesPrague = append(PrecompiledAddressesPrague, k) + } +} + +// ActivePrecompiles returns the precompiles enabled with the current configuration. +func ActivePrecompiles(rules params.Rules) []common.Address { + switch { + case rules.IsPrague: + return PrecompiledAddressesPrague + case rules.IsCancun: + return PrecompiledAddressesCancun + case rules.IsBerlin: + return PrecompiledAddressesBerlin + case rules.IsIstanbul: + return PrecompiledAddressesIstanbul + case rules.IsByzantium: + return PrecompiledAddressesByzantium + default: + return PrecompiledAddressesHomestead + } +} + +// RunPrecompiledContract runs and evaluates the output of a precompiled contract. +// It returns +// - the returned bytes, +// - the _remaining_ gas, +// - any error that occurred +func RunPrecompiledContract(evm *EVM, p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { + log.Info("RunPrecompiledContract", "input", input, "suppliedGas", suppliedGas) + gasCost := p.RequiredGas(input) + if suppliedGas < gasCost { + return nil, 0, ErrOutOfGas + } + if logger != nil && logger.OnGasChange != nil { + logger.OnGasChange(suppliedGas, suppliedGas-gasCost, tracing.GasChangeCallPrecompiledContract) + } + suppliedGas -= gasCost + output, err := p.Run(evm, input) + log.Info("RunPrecompiledContract", "output", output, "err", err) + return output, suppliedGas, err +} + +// ecrecover implemented as a native contract. +type ecrecover struct{} + +func (c *ecrecover) RequiredGas(input []byte) uint64 { + return params.EcrecoverGas +} + +func (c *ecrecover) Run(evm *EVM, input []byte) ([]byte, error) { + const ecRecoverInputLength = 128 + + input = common.RightPadBytes(input, ecRecoverInputLength) + // "input" is (hash, v, r, s), each 32 bytes + // but for ecrecover we want (r, s, v) + + r := new(big.Int).SetBytes(input[64:96]) + s := new(big.Int).SetBytes(input[96:128]) + v := input[63] - 27 + + // tighter sig s values input homestead only apply to tx sigs + if !allZero(input[32:63]) || !crypto.ValidateSignatureValues(v, r, s, false) { + return nil, nil + } + // We must make sure not to modify the 'input', so placing the 'v' along with + // the signature needs to be done on a new allocation + sig := make([]byte, 65) + copy(sig, input[64:128]) + sig[64] = v + // v needs to be at the end for libsecp256k1 + pubKey, err := crypto.Ecrecover(input[:32], sig) + // make sure the public key is a valid one + if err != nil { + return nil, nil + } + + // the first byte of pubkey is bitcoin heritage + return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32), nil +} + +// SHA256 implemented as a native contract. +type sha256hash struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +// +// This method does not require any overflow checking as the input size gas costs +// required for anything significant is so high it's impossible to pay for. +func (c *sha256hash) RequiredGas(input []byte) uint64 { + return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas +} +func (c *sha256hash) Run(evm *EVM, input []byte) ([]byte, error) { + h := sha256.Sum256(input) + return h[:], nil +} + +// RIPEMD160 implemented as a native contract. +type ripemd160hash struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +// +// This method does not require any overflow checking as the input size gas costs +// required for anything significant is so high it's impossible to pay for. +func (c *ripemd160hash) RequiredGas(input []byte) uint64 { + return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas +} +func (c *ripemd160hash) Run(evm *EVM, input []byte) ([]byte, error) { + ripemd := ripemd160.New() + ripemd.Write(input) + return common.LeftPadBytes(ripemd.Sum(nil), 32), nil +} + +// data copy implemented as a native contract. +type dataCopy struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +// +// This method does not require any overflow checking as the input size gas costs +// required for anything significant is so high it's impossible to pay for. +func (c *dataCopy) RequiredGas(input []byte) uint64 { + return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas +} +func (c *dataCopy) Run(evm *EVM, in []byte) ([]byte, error) { + return common.CopyBytes(in), nil +} + +// bigModExp implements a native big integer exponential modular operation. +type bigModExp struct { + eip2565 bool +} + +var ( + big1 = big.NewInt(1) + big3 = big.NewInt(3) + big7 = big.NewInt(7) + big20 = big.NewInt(20) + big32 = big.NewInt(32) + big64 = big.NewInt(64) + big96 = big.NewInt(96) + big480 = big.NewInt(480) + big1024 = big.NewInt(1024) + big3072 = big.NewInt(3072) + big199680 = big.NewInt(199680) +) + +// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198 +// +// def mult_complexity(x): +// if x <= 64: return x ** 2 +// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072 +// else: return x ** 2 // 16 + 480 * x - 199680 +// +// where is x is max(length_of_MODULUS, length_of_BASE) +func modexpMultComplexity(x *big.Int) *big.Int { + switch { + case x.Cmp(big64) <= 0: + x.Mul(x, x) // x ** 2 + case x.Cmp(big1024) <= 0: + // (x ** 2 // 4 ) + ( 96 * x - 3072) + x = new(big.Int).Add( + new(big.Int).Rsh(new(big.Int).Mul(x, x), 2), + new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072), + ) + default: + // (x ** 2 // 16) + (480 * x - 199680) + x = new(big.Int).Add( + new(big.Int).Rsh(new(big.Int).Mul(x, x), 4), + new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680), + ) + } + return x +} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bigModExp) RequiredGas(input []byte) uint64 { + var ( + baseLen = new(big.Int).SetBytes(getData(input, 0, 32)) + expLen = new(big.Int).SetBytes(getData(input, 32, 32)) + modLen = new(big.Int).SetBytes(getData(input, 64, 32)) + ) + if len(input) > 96 { + input = input[96:] + } else { + input = input[:0] + } + // Retrieve the head 32 bytes of exp for the adjusted exponent length + var expHead *big.Int + if big.NewInt(int64(len(input))).Cmp(baseLen) <= 0 { + expHead = new(big.Int) + } else { + if expLen.Cmp(big32) > 0 { + expHead = new(big.Int).SetBytes(getData(input, baseLen.Uint64(), 32)) + } else { + expHead = new(big.Int).SetBytes(getData(input, baseLen.Uint64(), expLen.Uint64())) + } + } + // Calculate the adjusted exponent length + var msb int + if bitlen := expHead.BitLen(); bitlen > 0 { + msb = bitlen - 1 + } + adjExpLen := new(big.Int) + if expLen.Cmp(big32) > 0 { + adjExpLen.Sub(expLen, big32) + adjExpLen.Lsh(adjExpLen, 3) + } + adjExpLen.Add(adjExpLen, big.NewInt(int64(msb))) + // Calculate the gas cost of the operation + gas := new(big.Int).Set(math.BigMax(modLen, baseLen)) + if c.eip2565 { + // EIP-2565 has three changes + // 1. Different multComplexity (inlined here) + // in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565): + // + // def mult_complexity(x): + // ceiling(x/8)^2 + // + //where is x is max(length_of_MODULUS, length_of_BASE) + gas.Add(gas, big7) + gas.Rsh(gas, 3) + gas.Mul(gas, gas) + + gas.Mul(gas, math.BigMax(adjExpLen, big1)) + // 2. Different divisor (`GQUADDIVISOR`) (3) + gas.Div(gas, big3) + if gas.BitLen() > 64 { + return math.MaxUint64 + } + // 3. Minimum price of 200 gas + if gas.Uint64() < 200 { + return 200 + } + return gas.Uint64() + } + gas = modexpMultComplexity(gas) + gas.Mul(gas, math.BigMax(adjExpLen, big1)) + gas.Div(gas, big20) + + if gas.BitLen() > 64 { + return math.MaxUint64 + } + return gas.Uint64() +} + +func (c *bigModExp) Run(evm *EVM, input []byte) ([]byte, error) { + var ( + baseLen = new(big.Int).SetBytes(getData(input, 0, 32)).Uint64() + expLen = new(big.Int).SetBytes(getData(input, 32, 32)).Uint64() + modLen = new(big.Int).SetBytes(getData(input, 64, 32)).Uint64() + ) + if len(input) > 96 { + input = input[96:] + } else { + input = input[:0] + } + // Handle a special case when both the base and mod length is zero + if baseLen == 0 && modLen == 0 { + return []byte{}, nil + } + // Retrieve the operands and execute the exponentiation + var ( + base = new(big.Int).SetBytes(getData(input, 0, baseLen)) + exp = new(big.Int).SetBytes(getData(input, baseLen, expLen)) + mod = new(big.Int).SetBytes(getData(input, baseLen+expLen, modLen)) + v []byte + ) + switch { + case mod.BitLen() == 0: + // Modulo 0 is undefined, return zero + return common.LeftPadBytes([]byte{}, int(modLen)), nil + case base.BitLen() == 1: // a bit length of 1 means it's 1 (or -1). + //If base == 1, then we can just return base % mod (if mod >= 1, which it is) + v = base.Mod(base, mod).Bytes() + default: + v = base.Exp(base, exp, mod).Bytes() + } + return common.LeftPadBytes(v, int(modLen)), nil +} + +// newCurvePoint unmarshals a binary blob into a bn256 elliptic curve point, +// returning it, or an error if the point is invalid. +func newCurvePoint(blob []byte) (*bn256.G1, error) { + p := new(bn256.G1) + if _, err := p.Unmarshal(blob); err != nil { + return nil, err + } + return p, nil +} + +// newTwistPoint unmarshals a binary blob into a bn256 elliptic curve point, +// returning it, or an error if the point is invalid. +func newTwistPoint(blob []byte) (*bn256.G2, error) { + p := new(bn256.G2) + if _, err := p.Unmarshal(blob); err != nil { + return nil, err + } + return p, nil +} + +// runBn256Add implements the Bn256Add precompile, referenced by both +// Byzantium and Istanbul operations. +func runBn256Add(input []byte) ([]byte, error) { + x, err := newCurvePoint(getData(input, 0, 64)) + if err != nil { + return nil, err + } + y, err := newCurvePoint(getData(input, 64, 64)) + if err != nil { + return nil, err + } + res := new(bn256.G1) + res.Add(x, y) + return res.Marshal(), nil +} + +// bn256AddIstanbul implements a native elliptic curve point addition conforming to +// Istanbul consensus rules. +type bn256AddIstanbul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256AddIstanbul) RequiredGas(input []byte) uint64 { + return params.Bn256AddGasIstanbul +} + +func (c *bn256AddIstanbul) Run(evm *EVM, input []byte) ([]byte, error) { + return runBn256Add(input) +} + +// bn256AddByzantium implements a native elliptic curve point addition +// conforming to Byzantium consensus rules. +type bn256AddByzantium struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256AddByzantium) RequiredGas(input []byte) uint64 { + return params.Bn256AddGasByzantium +} + +func (c *bn256AddByzantium) Run(evm *EVM, input []byte) ([]byte, error) { + return runBn256Add(input) +} + +// runBn256ScalarMul implements the Bn256ScalarMul precompile, referenced by +// both Byzantium and Istanbul operations. +func runBn256ScalarMul(input []byte) ([]byte, error) { + p, err := newCurvePoint(getData(input, 0, 64)) + if err != nil { + return nil, err + } + res := new(bn256.G1) + res.ScalarMult(p, new(big.Int).SetBytes(getData(input, 64, 32))) + return res.Marshal(), nil +} + +// bn256ScalarMulIstanbul implements a native elliptic curve scalar +// multiplication conforming to Istanbul consensus rules. +type bn256ScalarMulIstanbul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256ScalarMulIstanbul) RequiredGas(input []byte) uint64 { + return params.Bn256ScalarMulGasIstanbul +} + +func (c *bn256ScalarMulIstanbul) Run(evm *EVM, input []byte) ([]byte, error) { + return runBn256ScalarMul(input) +} + +// bn256ScalarMulByzantium implements a native elliptic curve scalar +// multiplication conforming to Byzantium consensus rules. +type bn256ScalarMulByzantium struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256ScalarMulByzantium) RequiredGas(input []byte) uint64 { + return params.Bn256ScalarMulGasByzantium +} + +func (c *bn256ScalarMulByzantium) Run(evm *EVM, input []byte) ([]byte, error) { + return runBn256ScalarMul(input) +} + +var ( + // true32Byte is returned if the bn256 pairing check succeeds. + true32Byte = []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, 1} + + // false32Byte is returned if the bn256 pairing check fails. + false32Byte = make([]byte, 32) + + // errBadPairingInput is returned if the bn256 pairing input is invalid. + errBadPairingInput = errors.New("bad elliptic curve pairing size") +) + +// runBn256Pairing implements the Bn256Pairing precompile, referenced by both +// Byzantium and Istanbul operations. +func runBn256Pairing(input []byte) ([]byte, error) { + // Handle some corner cases cheaply + if len(input)%192 > 0 { + return nil, errBadPairingInput + } + // Convert the input into a set of coordinates + var ( + cs []*bn256.G1 + ts []*bn256.G2 + ) + for i := 0; i < len(input); i += 192 { + c, err := newCurvePoint(input[i : i+64]) + if err != nil { + return nil, err + } + t, err := newTwistPoint(input[i+64 : i+192]) + if err != nil { + return nil, err + } + cs = append(cs, c) + ts = append(ts, t) + } + // Execute the pairing checks and return the results + if bn256.PairingCheck(cs, ts) { + return true32Byte, nil + } + return false32Byte, nil +} + +// bn256PairingIstanbul implements a pairing pre-compile for the bn256 curve +// conforming to Istanbul consensus rules. +type bn256PairingIstanbul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256PairingIstanbul) RequiredGas(input []byte) uint64 { + return params.Bn256PairingBaseGasIstanbul + uint64(len(input)/192)*params.Bn256PairingPerPointGasIstanbul +} + +func (c *bn256PairingIstanbul) Run(evm *EVM, input []byte) ([]byte, error) { + return runBn256Pairing(input) +} + +// bn256PairingByzantium implements a pairing pre-compile for the bn256 curve +// conforming to Byzantium consensus rules. +type bn256PairingByzantium struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bn256PairingByzantium) RequiredGas(input []byte) uint64 { + return params.Bn256PairingBaseGasByzantium + uint64(len(input)/192)*params.Bn256PairingPerPointGasByzantium +} + +func (c *bn256PairingByzantium) Run(evm *EVM, input []byte) ([]byte, error) { + return runBn256Pairing(input) +} + +type blake2F struct{} + +func (c *blake2F) RequiredGas(input []byte) uint64 { + // If the input is malformed, we can't calculate the gas, return 0 and let the + // actual call choke and fault. + if len(input) != blake2FInputLength { + return 0 + } + return uint64(binary.BigEndian.Uint32(input[0:4])) +} + +const ( + blake2FInputLength = 213 + blake2FFinalBlockBytes = byte(1) + blake2FNonFinalBlockBytes = byte(0) +) + +var ( + errBlake2FInvalidInputLength = errors.New("invalid input length") + errBlake2FInvalidFinalFlag = errors.New("invalid final flag") +) + +func (c *blake2F) Run(evm *EVM, input []byte) ([]byte, error) { + // Make sure the input is valid (correct length and final flag) + if len(input) != blake2FInputLength { + return nil, errBlake2FInvalidInputLength + } + if input[212] != blake2FNonFinalBlockBytes && input[212] != blake2FFinalBlockBytes { + return nil, errBlake2FInvalidFinalFlag + } + // Parse the input into the Blake2b call parameters + var ( + rounds = binary.BigEndian.Uint32(input[0:4]) + final = input[212] == blake2FFinalBlockBytes + + h [8]uint64 + m [16]uint64 + t [2]uint64 + ) + for i := 0; i < 8; i++ { + offset := 4 + i*8 + h[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) + } + for i := 0; i < 16; i++ { + offset := 68 + i*8 + m[i] = binary.LittleEndian.Uint64(input[offset : offset+8]) + } + t[0] = binary.LittleEndian.Uint64(input[196:204]) + t[1] = binary.LittleEndian.Uint64(input[204:212]) + + // Execute the compression function, extract and return the result + blake2b.F(&h, m, t, final, rounds) + + output := make([]byte, 64) + for i := 0; i < 8; i++ { + offset := i * 8 + binary.LittleEndian.PutUint64(output[offset:offset+8], h[i]) + } + return output, nil +} + +var ( + errBLS12381InvalidInputLength = errors.New("invalid input length") + errBLS12381InvalidFieldElementTopBytes = errors.New("invalid field element top bytes") + errBLS12381G1PointSubgroup = errors.New("g1 point is not on correct subgroup") + errBLS12381G2PointSubgroup = errors.New("g2 point is not on correct subgroup") +) + +// bls12381G1Add implements EIP-2537 G1Add precompile. +type bls12381G1Add struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1Add) RequiredGas(input []byte) uint64 { + return params.Bls12381G1AddGas +} + +func (c *bls12381G1Add) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 G1Add precompile. + // > G1 addition call expects `256` bytes as an input that is interpreted as byte concatenation of two G1 points (`128` bytes each). + // > Output is an encoding of addition operation result - single G1 point (`128` bytes). + if len(input) != 256 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0, p1 *bls12381.G1Affine + + // Decode G1 point p_0 + if p0, err = decodePointG1(input[:128]); err != nil { + return nil, err + } + // Decode G1 point p_1 + if p1, err = decodePointG1(input[128:]); err != nil { + return nil, err + } + + // No need to check the subgroup here, as specified by EIP-2537 + + // Compute r = p_0 + p_1 + p0.Add(p0, p1) + + // Encode the G1 point result into 128 bytes + return encodePointG1(p0), nil +} + +// bls12381G1Mul implements EIP-2537 G1Mul precompile. +type bls12381G1Mul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1Mul) RequiredGas(input []byte) uint64 { + return params.Bls12381G1MulGas +} + +func (c *bls12381G1Mul) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 G1Mul precompile. + // > G1 multiplication call expects `160` bytes as an input that is interpreted as byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiplication operation result - single G1 point (`128` bytes). + if len(input) != 160 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0 *bls12381.G1Affine + + // Decode G1 point + if p0, err = decodePointG1(input[:128]); err != nil { + return nil, err + } + // 'point is on curve' check already done, + // Here we need to apply subgroup checks. + if !p0.IsInSubGroup() { + return nil, errBLS12381G1PointSubgroup + } + // Decode scalar value + e := new(big.Int).SetBytes(input[128:]) + + // Compute r = e * p_0 + r := new(bls12381.G1Affine) + r.ScalarMultiplication(p0, e) + + // Encode the G1 point into 128 bytes + return encodePointG1(r), nil +} + +// bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile. +type bls12381G1MultiExp struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 { + // Calculate G1 point, scalar value pair length + k := len(input) / 160 + if k == 0 { + // Return 0 gas for small input length + return 0 + } + // Lookup discount value for G1 point, scalar value pair length + var discount uint64 + if dLen := len(params.Bls12381MultiExpDiscountTable); k < dLen { + discount = params.Bls12381MultiExpDiscountTable[k-1] + } else { + discount = params.Bls12381MultiExpDiscountTable[dLen-1] + } + // Calculate gas and return the result + return (uint64(k) * params.Bls12381G1MulGas * discount) / 1000 +} + +func (c *bls12381G1MultiExp) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 G1MultiExp precompile. + // G1 multiplication call expects `160*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). + // Output is an encoding of multiexponentiation operation result - single G1 point (`128` bytes). + k := len(input) / 160 + if len(input) == 0 || len(input)%160 != 0 { + return nil, errBLS12381InvalidInputLength + } + points := make([]bls12381.G1Affine, k) + scalars := make([]fr.Element, k) + + // Decode point scalar pairs + for i := 0; i < k; i++ { + off := 160 * i + t0, t1, t2 := off, off+128, off+160 + // Decode G1 point + p, err := decodePointG1(input[t0:t1]) + if err != nil { + return nil, err + } + // 'point is on curve' check already done, + // Here we need to apply subgroup checks. + if !p.IsInSubGroup() { + return nil, errBLS12381G1PointSubgroup + } + points[i] = *p + // Decode scalar value + scalars[i] = *new(fr.Element).SetBytes(input[t1:t2]) + } + + // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1) + r := new(bls12381.G1Affine) + r.MultiExp(points, scalars, ecc.MultiExpConfig{}) + + // Encode the G1 point to 128 bytes + return encodePointG1(r), nil +} + +// bls12381G2Add implements EIP-2537 G2Add precompile. +type bls12381G2Add struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2Add) RequiredGas(input []byte) uint64 { + return params.Bls12381G2AddGas +} + +func (c *bls12381G2Add) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 G2Add precompile. + // > G2 addition call expects `512` bytes as an input that is interpreted as byte concatenation of two G2 points (`256` bytes each). + // > Output is an encoding of addition operation result - single G2 point (`256` bytes). + if len(input) != 512 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0, p1 *bls12381.G2Affine + + // Decode G2 point p_0 + if p0, err = decodePointG2(input[:256]); err != nil { + return nil, err + } + // Decode G2 point p_1 + if p1, err = decodePointG2(input[256:]); err != nil { + return nil, err + } + + // No need to check the subgroup here, as specified by EIP-2537 + + // Compute r = p_0 + p_1 + r := new(bls12381.G2Affine) + r.Add(p0, p1) + + // Encode the G2 point into 256 bytes + return encodePointG2(r), nil +} + +// bls12381G2Mul implements EIP-2537 G2Mul precompile. +type bls12381G2Mul struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2Mul) RequiredGas(input []byte) uint64 { + return params.Bls12381G2MulGas +} + +func (c *bls12381G2Mul) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 G2MUL precompile logic. + // > G2 multiplication call expects `288` bytes as an input that is interpreted as byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiplication operation result - single G2 point (`256` bytes). + if len(input) != 288 { + return nil, errBLS12381InvalidInputLength + } + var err error + var p0 *bls12381.G2Affine + + // Decode G2 point + if p0, err = decodePointG2(input[:256]); err != nil { + return nil, err + } + // 'point is on curve' check already done, + // Here we need to apply subgroup checks. + if !p0.IsInSubGroup() { + return nil, errBLS12381G2PointSubgroup + } + // Decode scalar value + e := new(big.Int).SetBytes(input[256:]) + + // Compute r = e * p_0 + r := new(bls12381.G2Affine) + r.ScalarMultiplication(p0, e) + + // Encode the G2 point into 256 bytes + return encodePointG2(r), nil +} + +// bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile. +type bls12381G2MultiExp struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 { + // Calculate G2 point, scalar value pair length + k := len(input) / 288 + if k == 0 { + // Return 0 gas for small input length + return 0 + } + // Lookup discount value for G2 point, scalar value pair length + var discount uint64 + if dLen := len(params.Bls12381MultiExpDiscountTable); k < dLen { + discount = params.Bls12381MultiExpDiscountTable[k-1] + } else { + discount = params.Bls12381MultiExpDiscountTable[dLen-1] + } + // Calculate gas and return the result + return (uint64(k) * params.Bls12381G2MulGas * discount) / 1000 +} + +func (c *bls12381G2MultiExp) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 G2MultiExp precompile logic + // > G2 multiplication call expects `288*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). + // > Output is an encoding of multiexponentiation operation result - single G2 point (`256` bytes). + k := len(input) / 288 + if len(input) == 0 || len(input)%288 != 0 { + return nil, errBLS12381InvalidInputLength + } + points := make([]bls12381.G2Affine, k) + scalars := make([]fr.Element, k) + + // Decode point scalar pairs + for i := 0; i < k; i++ { + off := 288 * i + t0, t1, t2 := off, off+256, off+288 + // Decode G2 point + p, err := decodePointG2(input[t0:t1]) + if err != nil { + return nil, err + } + // 'point is on curve' check already done, + // Here we need to apply subgroup checks. + if !p.IsInSubGroup() { + return nil, errBLS12381G2PointSubgroup + } + points[i] = *p + // Decode scalar value + scalars[i] = *new(fr.Element).SetBytes(input[t1:t2]) + } + + // Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1) + r := new(bls12381.G2Affine) + r.MultiExp(points, scalars, ecc.MultiExpConfig{}) + + // Encode the G2 point to 256 bytes. + return encodePointG2(r), nil +} + +// bls12381Pairing implements EIP-2537 Pairing precompile. +type bls12381Pairing struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381Pairing) RequiredGas(input []byte) uint64 { + return params.Bls12381PairingBaseGas + uint64(len(input)/384)*params.Bls12381PairingPerPairGas +} + +func (c *bls12381Pairing) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 Pairing precompile logic. + // > Pairing call expects `384*k` bytes as an inputs that is interpreted as byte concatenation of `k` slices. Each slice has the following structure: + // > - `128` bytes of G1 point encoding + // > - `256` bytes of G2 point encoding + // > Output is a `32` bytes where last single byte is `0x01` if pairing result is equal to multiplicative identity in a pairing target field and `0x00` otherwise + // > (which is equivalent of Big Endian encoding of Solidity values `uint256(1)` and `uin256(0)` respectively). + k := len(input) / 384 + if len(input) == 0 || len(input)%384 != 0 { + return nil, errBLS12381InvalidInputLength + } + + var ( + p []bls12381.G1Affine + q []bls12381.G2Affine + ) + + // Decode pairs + for i := 0; i < k; i++ { + off := 384 * i + t0, t1, t2 := off, off+128, off+384 + + // Decode G1 point + p1, err := decodePointG1(input[t0:t1]) + if err != nil { + return nil, err + } + // Decode G2 point + p2, err := decodePointG2(input[t1:t2]) + if err != nil { + return nil, err + } + + // 'point is on curve' check already done, + // Here we need to apply subgroup checks. + if !p1.IsInSubGroup() { + return nil, errBLS12381G1PointSubgroup + } + if !p2.IsInSubGroup() { + return nil, errBLS12381G2PointSubgroup + } + p = append(p, *p1) + q = append(q, *p2) + } + // Prepare 32 byte output + out := make([]byte, 32) + + // Compute pairing and set the result + ok, err := bls12381.PairingCheck(p, q) + if err == nil && ok { + out[31] = 1 + } + return out, nil +} + +func decodePointG1(in []byte) (*bls12381.G1Affine, error) { + if len(in) != 128 { + return nil, errors.New("invalid g1 point length") + } + // decode x + x, err := decodeBLS12381FieldElement(in[:64]) + if err != nil { + return nil, err + } + // decode y + y, err := decodeBLS12381FieldElement(in[64:]) + if err != nil { + return nil, err + } + elem := bls12381.G1Affine{X: x, Y: y} + if !elem.IsOnCurve() { + return nil, errors.New("invalid point: not on curve") + } + + return &elem, nil +} + +// decodePointG2 given encoded (x, y) coordinates in 256 bytes returns a valid G2 Point. +func decodePointG2(in []byte) (*bls12381.G2Affine, error) { + if len(in) != 256 { + return nil, errors.New("invalid g2 point length") + } + x0, err := decodeBLS12381FieldElement(in[:64]) + if err != nil { + return nil, err + } + x1, err := decodeBLS12381FieldElement(in[64:128]) + if err != nil { + return nil, err + } + y0, err := decodeBLS12381FieldElement(in[128:192]) + if err != nil { + return nil, err + } + y1, err := decodeBLS12381FieldElement(in[192:]) + if err != nil { + return nil, err + } + + p := bls12381.G2Affine{X: bls12381.E2{A0: x0, A1: x1}, Y: bls12381.E2{A0: y0, A1: y1}} + if !p.IsOnCurve() { + return nil, errors.New("invalid point: not on curve") + } + return &p, err +} + +// decodeBLS12381FieldElement decodes BLS12-381 elliptic curve field element. +// Removes top 16 bytes of 64 byte input. +func decodeBLS12381FieldElement(in []byte) (fp.Element, error) { + if len(in) != 64 { + return fp.Element{}, errors.New("invalid field element length") + } + // check top bytes + for i := 0; i < 16; i++ { + if in[i] != byte(0x00) { + return fp.Element{}, errBLS12381InvalidFieldElementTopBytes + } + } + var res [48]byte + copy(res[:], in[16:]) + + return fp.BigEndian.Element(&res) +} + +// encodePointG1 encodes a point into 128 bytes. +func encodePointG1(p *bls12381.G1Affine) []byte { + out := make([]byte, 128) + fp.BigEndian.PutElement((*[fp.Bytes]byte)(out[16:]), p.X) + fp.BigEndian.PutElement((*[fp.Bytes]byte)(out[64+16:]), p.Y) + return out +} + +// encodePointG2 encodes a point into 256 bytes. +func encodePointG2(p *bls12381.G2Affine) []byte { + out := make([]byte, 256) + // encode x + fp.BigEndian.PutElement((*[fp.Bytes]byte)(out[16:16+48]), p.X.A0) + fp.BigEndian.PutElement((*[fp.Bytes]byte)(out[80:80+48]), p.X.A1) + // encode y + fp.BigEndian.PutElement((*[fp.Bytes]byte)(out[144:144+48]), p.Y.A0) + fp.BigEndian.PutElement((*[fp.Bytes]byte)(out[208:208+48]), p.Y.A1) + return out +} + +// bls12381MapG1 implements EIP-2537 MapG1 precompile. +type bls12381MapG1 struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381MapG1) RequiredGas(input []byte) uint64 { + return params.Bls12381MapG1Gas +} + +func (c *bls12381MapG1) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 Map_To_G1 precompile. + // > Field-to-curve call expects an `64` bytes input that is interpreted as an element of the base field. + // > Output of this call is `128` bytes and is G1 point following respective encoding rules. + if len(input) != 64 { + return nil, errBLS12381InvalidInputLength + } + + // Decode input field element + fe, err := decodeBLS12381FieldElement(input) + if err != nil { + return nil, err + } + + // Compute mapping + r := bls12381.MapToG1(fe) + + // Encode the G1 point to 128 bytes + return encodePointG1(&r), nil +} + +// bls12381MapG2 implements EIP-2537 MapG2 precompile. +type bls12381MapG2 struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *bls12381MapG2) RequiredGas(input []byte) uint64 { + return params.Bls12381MapG2Gas +} + +func (c *bls12381MapG2) Run(evm *EVM, input []byte) ([]byte, error) { + // Implements EIP-2537 Map_FP2_TO_G2 precompile logic. + // > Field-to-curve call expects an `128` bytes input that is interpreted as an element of the quadratic extension field. + // > Output of this call is `256` bytes and is G2 point following respective encoding rules. + if len(input) != 128 { + return nil, errBLS12381InvalidInputLength + } + + // Decode input field element + c0, err := decodeBLS12381FieldElement(input[:64]) + if err != nil { + return nil, err + } + c1, err := decodeBLS12381FieldElement(input[64:]) + if err != nil { + return nil, err + } + + // Compute mapping + r := bls12381.MapToG2(bls12381.E2{A0: c0, A1: c1}) + + // Encode the G2 point to 256 bytes + return encodePointG2(&r), nil +} + +// kzgPointEvaluation implements the EIP-4844 point evaluation precompile. +type kzgPointEvaluation struct{} + +// RequiredGas estimates the gas required for running the point evaluation precompile. +func (b *kzgPointEvaluation) RequiredGas(input []byte) uint64 { + return params.BlobTxPointEvaluationPrecompileGas +} + +const ( + blobVerifyInputLength = 192 // Max input length for the point evaluation precompile. + blobCommitmentVersionKZG uint8 = 0x01 // Version byte for the point evaluation precompile. + blobPrecompileReturnValue = "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" +) + +var ( + errBlobVerifyInvalidInputLength = errors.New("invalid input length") + errBlobVerifyMismatchedVersion = errors.New("mismatched versioned hash") + errBlobVerifyKZGProof = errors.New("error verifying kzg proof") +) + +// Run executes the point evaluation precompile. +func (b *kzgPointEvaluation) Run(evm *EVM, input []byte) ([]byte, error) { + if len(input) != blobVerifyInputLength { + return nil, errBlobVerifyInvalidInputLength + } + // versioned hash: first 32 bytes + var versionedHash common.Hash + copy(versionedHash[:], input[:]) + + var ( + point kzg4844.Point + claim kzg4844.Claim + ) + // Evaluation point: next 32 bytes + copy(point[:], input[32:]) + // Expected output: next 32 bytes + copy(claim[:], input[64:]) + + // input kzg point: next 48 bytes + var commitment kzg4844.Commitment + copy(commitment[:], input[96:]) + if kZGToVersionedHash(commitment) != versionedHash { + return nil, errBlobVerifyMismatchedVersion + } + + // Proof: next 48 bytes + var proof kzg4844.Proof + copy(proof[:], input[144:]) + + if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil { + return nil, fmt.Errorf("%w: %v", errBlobVerifyKZGProof, err) + } + + return common.Hex2Bytes(blobPrecompileReturnValue), nil +} + +// kZGToVersionedHash implements kzg_to_versioned_hash from EIP-4844 +func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash { + h := sha256.Sum256(kzg[:]) + h[0] = blobCommitmentVersionKZG + + return h +} diff --git a/core/vm/contracts_fuzz_test.go b/core/vm/contracts_fuzz_test.go new file mode 100644 index 0000000..c4bed0b --- /dev/null +++ b/core/vm/contracts_fuzz_test.go @@ -0,0 +1,54 @@ +// 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 vm + +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/params" + "github.com/holiman/uint256" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +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 + } + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + evm := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + inWant := string(input) + RunPrecompiledContract(evm, p, input, gas, nil) + if inHave := string(input); inWant != inHave { + t.Errorf("Precompiled %v modified input data", a) + } + }) +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go new file mode 100644 index 0000000..bfd980e --- /dev/null +++ b/core/vm/contracts_test.go @@ -0,0 +1,427 @@ +// 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 vm + +import ( + "bytes" + "encoding/json" + "fmt" + "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/params" + "github.com/holiman/uint256" + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" +) + +// precompiledTest defines the input/output pairs for precompiled contract tests. +type precompiledTest struct { + Input, Expected string + Gas uint64 + Name string + NoBenchmark bool // Benchmark primarily the worst-cases +} + +// precompiledFailureTest defines the input/error pairs for precompiled +// contract failure tests. +type precompiledFailureTest struct { + Input string + ExpectedError string + Name string +} + +// allPrecompiles does not map to the actual set of precompiles, as it also contains +// repriced versions of precompiles at certain slots +var allPrecompiles = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, + common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{}, + + common.BytesToAddress([]byte{0x0f, 0x0a}): &bls12381G1Add{}, + common.BytesToAddress([]byte{0x0f, 0x0b}): &bls12381G1Mul{}, + common.BytesToAddress([]byte{0x0f, 0x0c}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{0x0f, 0x0d}): &bls12381G2Add{}, + common.BytesToAddress([]byte{0x0f, 0x0e}): &bls12381G2Mul{}, + common.BytesToAddress([]byte{0x0f, 0x0f}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{0x0f, 0x10}): &bls12381Pairing{}, + common.BytesToAddress([]byte{0x0f, 0x11}): &bls12381MapG1{}, + common.BytesToAddress([]byte{0x0f, 0x12}): &bls12381MapG2{}, +} + +// EIP-152 test vectors +var blake2FMalformedInputTests = []precompiledFailureTest{ + { + Input: "", + ExpectedError: errBlake2FInvalidInputLength.Error(), + Name: "vector 0: empty input", + }, + { + Input: "00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + ExpectedError: errBlake2FInvalidInputLength.Error(), + Name: "vector 1: less than 213 bytes input", + }, + { + Input: "000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + ExpectedError: errBlake2FInvalidInputLength.Error(), + Name: "vector 2: more than 213 bytes input", + }, + { + Input: "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002", + ExpectedError: errBlake2FInvalidFinalFlag.Error(), + Name: "vector 3: malformed final block indicator flag", + }, +} + +func testPrecompiled(addr string, test precompiledTest, t *testing.T) { + p := allPrecompiles[common.HexToAddress(addr)] + in := common.Hex2Bytes(test.Input) + gas := p.RequiredGas(in) + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + evm := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { + if res, _, err := RunPrecompiledContract(evm, p, in, gas, nil); err != nil { + t.Error(err) + } else if common.Bytes2Hex(res) != test.Expected { + t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) + } + if expGas := test.Gas; expGas != gas { + t.Errorf("%v: gas wrong, expected %d, got %d", test.Name, expGas, gas) + } + // Verify that the precompile did not touch the input buffer + exp := common.Hex2Bytes(test.Input) + if !bytes.Equal(in, exp) { + t.Errorf("Precompiled %v modified input data", addr) + } + }) +} + +func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { + p := allPrecompiles[common.HexToAddress(addr)] + in := common.Hex2Bytes(test.Input) + gas := p.RequiredGas(in) - 1 + + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + evm := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + + t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { + _, _, err := RunPrecompiledContract(evm, p, in, gas, nil) + if err.Error() != "out of gas" { + t.Errorf("Expected error [out of gas], got [%v]", err) + } + // Verify that the precompile did not touch the input buffer + exp := common.Hex2Bytes(test.Input) + if !bytes.Equal(in, exp) { + t.Errorf("Precompiled %v modified input data", addr) + } + }) +} + +func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) { + p := allPrecompiles[common.HexToAddress(addr)] + in := common.Hex2Bytes(test.Input) + gas := p.RequiredGas(in) + + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + evm := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + + t.Run(test.Name, func(t *testing.T) { + _, _, err := RunPrecompiledContract(evm, p, in, gas, nil) + if err.Error() != test.ExpectedError { + t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) + } + // Verify that the precompile did not touch the input buffer + exp := common.Hex2Bytes(test.Input) + if !bytes.Equal(in, exp) { + t.Errorf("Precompiled %v modified input data", addr) + } + }) +} + +func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { + if test.NoBenchmark { + return + } + p := allPrecompiles[common.HexToAddress(addr)] + in := common.Hex2Bytes(test.Input) + reqGas := p.RequiredGas(in) + + var ( + res []byte + err error + data = make([]byte, len(in)) + ) + + bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) { + bench.ReportAllocs() + start := time.Now() + bench.ResetTimer() + for i := 0; i < bench.N; i++ { + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + evm := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + copy(data, in) + res, _, err = RunPrecompiledContract(evm, p, data, reqGas, nil) + } + bench.StopTimer() + elapsed := uint64(time.Since(start)) + if elapsed < 1 { + elapsed = 1 + } + gasUsed := reqGas * uint64(bench.N) + bench.ReportMetric(float64(reqGas), "gas/op") + // Keep it as uint64, multiply 100 to get two digit float later + mgasps := (100 * 1000 * gasUsed) / elapsed + bench.ReportMetric(float64(mgasps)/100, "mgas/s") + //Check if it is correct + if err != nil { + bench.Error(err) + return + } + if common.Bytes2Hex(res) != test.Expected { + bench.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) + return + } + }) +} + +// Benchmarks the sample inputs from the ECRECOVER precompile. +func BenchmarkPrecompiledEcrecover(bench *testing.B) { + t := precompiledTest{ + Input: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02", + Expected: "000000000000000000000000ceaccac640adf55b2028469bd36ba501f28b699d", + Name: "", + } + benchmarkPrecompiled("01", t, bench) +} + +// Benchmarks the sample inputs from the SHA256 precompile. +func BenchmarkPrecompiledSha256(bench *testing.B) { + t := precompiledTest{ + Input: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02", + Expected: "811c7003375852fabd0d362e40e68607a12bdabae61a7d068fe5fdd1dbbf2a5d", + Name: "128", + } + benchmarkPrecompiled("02", t, bench) +} + +// Benchmarks the sample inputs from the RIPEMD precompile. +func BenchmarkPrecompiledRipeMD(bench *testing.B) { + t := precompiledTest{ + Input: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02", + Expected: "0000000000000000000000009215b8d9882ff46f0dfde6684d78e831467f65e6", + Name: "128", + } + benchmarkPrecompiled("03", t, bench) +} + +// Benchmarks the sample inputs from the identity precompile. +func BenchmarkPrecompiledIdentity(bench *testing.B) { + t := precompiledTest{ + Input: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02", + Expected: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02", + Name: "128", + } + benchmarkPrecompiled("04", t, bench) +} + +// Tests the sample inputs from the ModExp EIP 198. +func TestPrecompiledModExp(t *testing.T) { testJson("modexp", "05", t) } +func BenchmarkPrecompiledModExp(b *testing.B) { benchJson("modexp", "05", b) } + +func TestPrecompiledModExpEip2565(t *testing.T) { testJson("modexp_eip2565", "f5", t) } +func BenchmarkPrecompiledModExpEip2565(b *testing.B) { benchJson("modexp_eip2565", "f5", b) } + +// Tests the sample inputs from the elliptic curve addition EIP 213. +func TestPrecompiledBn256Add(t *testing.T) { testJson("bn256Add", "06", t) } +func BenchmarkPrecompiledBn256Add(b *testing.B) { benchJson("bn256Add", "06", b) } + +// Tests OOG +func TestPrecompiledModExpOOG(t *testing.T) { + modexpTests, err := loadJson("modexp") + if err != nil { + t.Fatal(err) + } + for _, test := range modexpTests { + testPrecompiledOOG("05", test, t) + } +} + +// Tests the sample inputs from the elliptic curve scalar multiplication EIP 213. +func TestPrecompiledBn256ScalarMul(t *testing.T) { testJson("bn256ScalarMul", "07", t) } +func BenchmarkPrecompiledBn256ScalarMul(b *testing.B) { benchJson("bn256ScalarMul", "07", b) } + +// Tests the sample inputs from the elliptic curve pairing check EIP 197. +func TestPrecompiledBn256Pairing(t *testing.T) { testJson("bn256Pairing", "08", t) } +func BenchmarkPrecompiledBn256Pairing(b *testing.B) { benchJson("bn256Pairing", "08", b) } + +func TestPrecompiledBlake2F(t *testing.T) { testJson("blake2F", "09", t) } +func BenchmarkPrecompiledBlake2F(b *testing.B) { benchJson("blake2F", "09", b) } + +func TestPrecompileBlake2FMalformedInput(t *testing.T) { + for _, test := range blake2FMalformedInputTests { + testPrecompiledFailure("09", test, t) + } +} + +func TestPrecompiledEcrecover(t *testing.T) { testJson("ecRecover", "01", t) } + +func testJson(name, addr string, t *testing.T) { + tests, err := loadJson(name) + if err != nil { + t.Fatal(err) + } + for _, test := range tests { + testPrecompiled(addr, test, t) + } +} + +func testJsonFail(name, addr string, t *testing.T) { + tests, err := loadJsonFail(name) + if err != nil { + t.Fatal(err) + } + for _, test := range tests { + testPrecompiledFailure(addr, test, t) + } +} + +func benchJson(name, addr string, b *testing.B) { + tests, err := loadJson(name) + if err != nil { + b.Fatal(err) + } + for _, test := range tests { + benchmarkPrecompiled(addr, test, b) + } +} + +func TestPrecompiledBLS12381G1Add(t *testing.T) { testJson("blsG1Add", "f0a", t) } +func TestPrecompiledBLS12381G1Mul(t *testing.T) { testJson("blsG1Mul", "f0b", t) } +func TestPrecompiledBLS12381G1MultiExp(t *testing.T) { testJson("blsG1MultiExp", "f0c", t) } +func TestPrecompiledBLS12381G2Add(t *testing.T) { testJson("blsG2Add", "f0d", t) } +func TestPrecompiledBLS12381G2Mul(t *testing.T) { testJson("blsG2Mul", "f0e", t) } +func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp", "f0f", t) } +func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "f10", t) } +func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "f11", t) } +func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "f12", t) } + +func TestPrecompiledPointEvaluation(t *testing.T) { testJson("pointEvaluation", "0a", t) } + +func BenchmarkPrecompiledPointEvaluation(b *testing.B) { benchJson("pointEvaluation", "0a", b) } + +func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "f0a", b) } +func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "f0b", b) } +func BenchmarkPrecompiledBLS12381G1MultiExp(b *testing.B) { benchJson("blsG1MultiExp", "f0c", b) } +func BenchmarkPrecompiledBLS12381G2Add(b *testing.B) { benchJson("blsG2Add", "f0d", b) } +func BenchmarkPrecompiledBLS12381G2Mul(b *testing.B) { benchJson("blsG2Mul", "f0e", b) } +func BenchmarkPrecompiledBLS12381G2MultiExp(b *testing.B) { benchJson("blsG2MultiExp", "f0f", b) } +func BenchmarkPrecompiledBLS12381Pairing(b *testing.B) { benchJson("blsPairing", "f10", b) } +func BenchmarkPrecompiledBLS12381MapG1(b *testing.B) { benchJson("blsMapG1", "f11", b) } +func BenchmarkPrecompiledBLS12381MapG2(b *testing.B) { benchJson("blsMapG2", "f12", b) } + +// Failure tests +func TestPrecompiledBLS12381G1AddFail(t *testing.T) { testJsonFail("blsG1Add", "f0a", t) } +func TestPrecompiledBLS12381G1MulFail(t *testing.T) { testJsonFail("blsG1Mul", "f0b", t) } +func TestPrecompiledBLS12381G1MultiExpFail(t *testing.T) { testJsonFail("blsG1MultiExp", "f0c", t) } +func TestPrecompiledBLS12381G2AddFail(t *testing.T) { testJsonFail("blsG2Add", "f0d", t) } +func TestPrecompiledBLS12381G2MulFail(t *testing.T) { testJsonFail("blsG2Mul", "f0e", t) } +func TestPrecompiledBLS12381G2MultiExpFail(t *testing.T) { testJsonFail("blsG2MultiExp", "f0f", t) } +func TestPrecompiledBLS12381PairingFail(t *testing.T) { testJsonFail("blsPairing", "f10", t) } +func TestPrecompiledBLS12381MapG1Fail(t *testing.T) { testJsonFail("blsMapG1", "f11", t) } +func TestPrecompiledBLS12381MapG2Fail(t *testing.T) { testJsonFail("blsMapG2", "f12", t) } + +func loadJson(name string) ([]precompiledTest, error) { + data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name)) + if err != nil { + return nil, err + } + var testcases []precompiledTest + err = json.Unmarshal(data, &testcases) + return testcases, err +} + +func loadJsonFail(name string) ([]precompiledFailureTest, error) { + data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/fail-%v.json", name)) + if err != nil { + return nil, err + } + var testcases []precompiledFailureTest + err = json.Unmarshal(data, &testcases) + return testcases, err +} + +// BenchmarkPrecompiledBLS12381G1MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas. +func BenchmarkPrecompiledBLS12381G1MultiExpWorstCase(b *testing.B) { + task := "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be1" + + "0000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + input := task + for i := 0; i < 4787; i++ { + input = input + task + } + testcase := precompiledTest{ + Input: input, + Expected: "0000000000000000000000000000000005a6310ea6f2a598023ae48819afc292b4dfcb40aabad24a0c2cb6c19769465691859eeb2a764342a810c5038d700f18000000000000000000000000000000001268ac944437d15923dc0aec00daa9250252e43e4b35ec7a19d01f0d6cd27f6e139d80dae16ba1c79cc7f57055a93ff5", + Name: "WorstCaseG1", + NoBenchmark: false, + } + benchmarkPrecompiled("f0c", testcase, b) +} + +// BenchmarkPrecompiledBLS12381G2MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas. +func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) { + task := "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f" + + "000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b99" + + "00000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b" + + "000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + input := task + for i := 0; i < 1040; i++ { + input = input + task + } + + testcase := precompiledTest{ + Input: input, + Expected: "0000000000000000000000000000000018f5ea0c8b086095cfe23f6bb1d90d45de929292006dba8cdedd6d3203af3c6bbfd592e93ecb2b2c81004961fdcbb46c00000000000000000000000000000000076873199175664f1b6493a43c02234f49dc66f077d3007823e0343ad92e30bd7dc209013435ca9f197aca44d88e9dac000000000000000000000000000000000e6f07f4b23b511eac1e2682a0fc224c15d80e122a3e222d00a41fab15eba645a700b9ae84f331ae4ed873678e2e6c9b000000000000000000000000000000000bcb4849e460612aaed79617255fd30c03f51cf03d2ed4163ca810c13e1954b1e8663157b957a601829bb272a4e6c7b8", + Name: "WorstCaseG2", + NoBenchmark: false, + } + benchmarkPrecompiled("f0f", testcase, b) +} diff --git a/core/vm/doc.go b/core/vm/doc.go new file mode 100644 index 0000000..5864d0c --- /dev/null +++ b/core/vm/doc.go @@ -0,0 +1,24 @@ +// 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 vm implements the Ethereum Virtual Machine. + +The vm package implements one EVM, a byte code VM. The BC (Byte Code) VM loops +over a set of bytes and executes them according to the set of rules defined +in the Ethereum yellow paper. +*/ +package vm diff --git a/core/vm/eips.go b/core/vm/eips.go new file mode 100644 index 0000000..edd6ec8 --- /dev/null +++ b/core/vm/eips.go @@ -0,0 +1,535 @@ +// 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 vm + +import ( + "fmt" + "math" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +var activators = map[int]func(*JumpTable){ + 5656: enable5656, + 6780: enable6780, + 3855: enable3855, + 3860: enable3860, + 3529: enable3529, + 3198: enable3198, + 2929: enable2929, + 2200: enable2200, + 1884: enable1884, + 1344: enable1344, + 1153: enable1153, + 4762: enable4762, +} + +// EnableEIP enables the given EIP on the config. +// This operation writes in-place, and callers need to ensure that the globally +// defined jump tables are not polluted. +func EnableEIP(eipNum int, jt *JumpTable) error { + enablerFn, ok := activators[eipNum] + if !ok { + return fmt.Errorf("undefined eip %d", eipNum) + } + enablerFn(jt) + return nil +} + +func ValidEip(eipNum int) bool { + _, ok := activators[eipNum] + return ok +} +func ActivateableEips() []string { + var nums []string + for k := range activators { + nums = append(nums, fmt.Sprintf("%d", k)) + } + sort.Strings(nums) + return nums +} + +// enable1884 applies EIP-1884 to the given jump table: +// - Increase cost of BALANCE to 700 +// - Increase cost of EXTCODEHASH to 700 +// - Increase cost of SLOAD to 800 +// - Define SELFBALANCE, with cost GasFastStep (5) +func enable1884(jt *JumpTable) { + // Gas cost changes + jt[SLOAD].constantGas = params.SloadGasEIP1884 + jt[BALANCE].constantGas = params.BalanceGasEIP1884 + jt[EXTCODEHASH].constantGas = params.ExtcodeHashGasEIP1884 + + // New opcode + jt[SELFBALANCE] = &operation{ + execute: opSelfBalance, + constantGas: GasFastStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) + scope.Stack.push(balance) + return nil, nil +} + +// enable1344 applies EIP-1344 (ChainID Opcode) +// - Adds an opcode that returns the current chain’s EIP-155 unique identifier +func enable1344(jt *JumpTable) { + // New opcode + jt[CHAINID] = &operation{ + execute: opChainID, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// opChainID implements CHAINID opcode +func opChainID(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) + scope.Stack.push(chainId) + return nil, nil +} + +// enable2200 applies EIP-2200 (Rebalance net-metered SSTORE) +func enable2200(jt *JumpTable) { + jt[SLOAD].constantGas = params.SloadGasEIP2200 + jt[SSTORE].dynamicGas = gasSStoreEIP2200 +} + +// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes" +// https://eips.ethereum.org/EIPS/eip-2929 +func enable2929(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP2929 + + jt[SLOAD].constantGas = 0 + jt[SLOAD].dynamicGas = gasSLoadEIP2929 + + jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929 + + jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck + + jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929 + jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck + + jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929 + jt[BALANCE].dynamicGas = gasEip2929AccountCheck + + jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALL].dynamicGas = gasCallEIP2929 + + jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 + jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 + + jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 + + jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929 + + // This was previously part of the dynamic cost, but we're using it as a constantGas + // factor here + jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 +} + +// enable3529 enabled "EIP-3529: Reduction in refunds": +// - Removes refunds for selfdestructs +// - Reduces refunds for SSTORE +// - Reduces max refunds to 20% gas +func enable3529(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP3529 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529 +} + +// enable3198 applies EIP-3198 (BASEFEE Opcode) +// - Adds an opcode that returns the current block's base fee. +func enable3198(jt *JumpTable) { + // New opcode + jt[BASEFEE] = &operation{ + execute: opBaseFee, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// enable1153 applies EIP-1153 "Transient Storage" +// - Adds TLOAD that reads from transient storage +// - Adds TSTORE that writes to transient storage +func enable1153(jt *JumpTable) { + jt[TLOAD] = &operation{ + execute: opTload, + constantGas: params.WarmStorageReadCostEIP2929, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[TSTORE] = &operation{ + execute: opTstore, + constantGas: params.WarmStorageReadCostEIP2929, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + } +} + +// opTload implements TLOAD opcode +func opTload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.peek() + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetTransientState(scope.Contract.Address(), hash) + loc.SetBytes(val.Bytes()) + return nil, nil +} + +// opTstore implements TSTORE opcode +func opTstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + loc := scope.Stack.pop() + val := scope.Stack.pop() + interpreter.evm.StateDB.SetTransientState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) + return nil, nil +} + +// opBaseFee implements BASEFEE opcode +func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + baseFee, _ := uint256.FromBig(interpreter.evm.Context.BaseFee) + scope.Stack.push(baseFee) + return nil, nil +} + +// enable3855 applies EIP-3855 (PUSH0 opcode) +func enable3855(jt *JumpTable) { + // New opcode + jt[PUSH0] = &operation{ + execute: opPush0, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// opPush0 implements the PUSH0 opcode +func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int)) + return nil, nil +} + +// enable3860 enables "EIP-3860: Limit and meter initcode" +// https://eips.ethereum.org/EIPS/eip-3860 +func enable3860(jt *JumpTable) { + jt[CREATE].dynamicGas = gasCreateEip3860 + jt[CREATE2].dynamicGas = gasCreate2Eip3860 +} + +// enable5656 enables EIP-5656 (MCOPY opcode) +// https://eips.ethereum.org/EIPS/eip-5656 +func enable5656(jt *JumpTable) { + jt[MCOPY] = &operation{ + execute: opMcopy, + constantGas: GasFastestStep, + dynamicGas: gasMcopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryMcopy, + } +} + +// opMcopy implements the MCOPY opcode (https://eips.ethereum.org/EIPS/eip-5656) +func opMcopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + dst = scope.Stack.pop() + src = scope.Stack.pop() + length = scope.Stack.pop() + ) + // These values are checked for overflow during memory expansion calculation + // (the memorySize function on the opcode). + scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64()) + return nil, nil +} + +// opBlobHash implements the BLOBHASH opcode +func opBlobHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + index := scope.Stack.peek() + if index.LtUint64(uint64(len(interpreter.evm.TxContext.BlobHashes))) { + blobHash := interpreter.evm.TxContext.BlobHashes[index.Uint64()] + index.SetBytes32(blobHash[:]) + } else { + index.Clear() + } + return nil, nil +} + +// opBlobBaseFee implements BLOBBASEFEE opcode +func opBlobBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + blobBaseFee, _ := uint256.FromBig(interpreter.evm.Context.BlobBaseFee) + scope.Stack.push(blobBaseFee) + return nil, nil +} + +// enable4844 applies EIP-4844 (BLOBHASH opcode) +func enable4844(jt *JumpTable) { + jt[BLOBHASH] = &operation{ + execute: opBlobHash, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } +} + +// enable7516 applies EIP-7516 (BLOBBASEFEE opcode) +func enable7516(jt *JumpTable) { + jt[BLOBBASEFEE] = &operation{ + execute: opBlobBaseFee, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// enable6780 applies EIP-6780 (deactivate SELFDESTRUCT) +func enable6780(jt *JumpTable) { + jt[SELFDESTRUCT] = &operation{ + execute: opSelfdestruct6780, + dynamicGas: gasSelfdestructEIP3529, + constantGas: params.SelfdestructGasEIP150, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + } +} + +func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + stack = scope.Stack + a = stack.pop() + memOffset = stack.pop() + codeOffset = stack.pop() + length = stack.pop() + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = math.MaxUint64 + } + addr := common.Address(a.Bytes20()) + code := interpreter.evm.StateDB.GetCode(addr) + contract := &Contract{ + Code: code, + self: AccountRef(addr), + } + paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy) + + return nil, nil +} + +// opPush1EIP4762 handles the special case of PUSH1 opcode for EIP-4762, which +// need not worry about the adjusted bound logic when adding the PUSHDATA to +// the list of access events. +func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = uint64(len(scope.Contract.Code)) + integer = new(uint256.Int) + ) + *pc += 1 + if *pc < codeLen { + scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + + if !scope.Contract.IsDeployment && *pc%31 == 0 { + // touch next chunk if PUSH1 is at the boundary. if so, *pc has + // advanced past this boundary. + contractAddr := scope.Contract.Address() + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } + } else { + scope.Stack.push(integer.Clear()) + } + return nil, nil +} + +func makePushEIP4762(size uint64, pushByteSize int) executionFunc { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = len(scope.Contract.Code) + start = min(codeLen, int(*pc+1)) + end = min(codeLen, start+pushByteSize) + ) + scope.Stack.push(new(uint256.Int).SetBytes( + common.RightPadBytes( + scope.Contract.Code[start:end], + pushByteSize, + )), + ) + + if !scope.Contract.IsDeployment { + contractAddr := scope.Contract.Address() + statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false) + if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } + + *pc += size + return nil, nil + } +} + +func enable4762(jt *JumpTable) { + jt[SSTORE] = &operation{ + dynamicGas: gasSStore4762, + execute: opSstore, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + } + jt[SLOAD] = &operation{ + dynamicGas: gasSLoad4762, + execute: opSload, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[BALANCE] = &operation{ + execute: opBalance, + dynamicGas: gasBalance4762, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[EXTCODESIZE] = &operation{ + execute: opExtCodeSize, + dynamicGas: gasExtCodeSize4762, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[EXTCODEHASH] = &operation{ + execute: opExtCodeHash, + dynamicGas: gasExtCodeHash4762, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[EXTCODECOPY] = &operation{ + execute: opExtCodeCopyEIP4762, + dynamicGas: gasExtCodeCopyEIP4762, + minStack: minStack(4, 0), + maxStack: maxStack(4, 0), + memorySize: memoryExtCodeCopy, + } + + jt[CODECOPY] = &operation{ + execute: opCodeCopy, + constantGas: GasFastestStep, + dynamicGas: gasCodeCopyEip4762, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryCodeCopy, + } + + jt[SELFDESTRUCT] = &operation{ + execute: opSelfdestruct6780, + dynamicGas: gasSelfdestructEIP4762, + constantGas: params.SelfdestructGasEIP150, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + } + + jt[CREATE] = &operation{ + execute: opCreate, + constantGas: params.CreateNGasEip4762, + dynamicGas: gasCreateEip3860, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + memorySize: memoryCreate, + } + + jt[CREATE2] = &operation{ + execute: opCreate2, + constantGas: params.CreateNGasEip4762, + dynamicGas: gasCreate2Eip3860, + minStack: minStack(4, 1), + maxStack: maxStack(4, 1), + memorySize: memoryCreate2, + } + + jt[CALL] = &operation{ + execute: opCall, + dynamicGas: gasCallEIP4762, + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), + memorySize: memoryCall, + } + + jt[CALLCODE] = &operation{ + execute: opCallCode, + dynamicGas: gasCallCodeEIP4762, + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), + memorySize: memoryCall, + } + + jt[STATICCALL] = &operation{ + execute: opStaticCall, + dynamicGas: gasStaticCallEIP4762, + minStack: minStack(6, 1), + maxStack: maxStack(6, 1), + memorySize: memoryStaticCall, + } + + jt[DELEGATECALL] = &operation{ + execute: opDelegateCall, + dynamicGas: gasDelegateCallEIP4762, + minStack: minStack(6, 1), + maxStack: maxStack(6, 1), + memorySize: memoryDelegateCall, + } + + jt[PUSH1] = &operation{ + execute: opPush1EIP4762, + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } + for i := 1; i < 32; i++ { + jt[PUSH1+OpCode(i)] = &operation{ + execute: makePushEIP4762(uint64(i+1), i+1), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } + } +} diff --git a/core/vm/errors.go b/core/vm/errors.go new file mode 100644 index 0000000..e5efc95 --- /dev/null +++ b/core/vm/errors.go @@ -0,0 +1,193 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "errors" + "fmt" + "math" +) + +// List evm execution errors +var ( + ErrOutOfGas = errors.New("out of gas") + ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") + ErrDepth = errors.New("max call depth exceeded") + ErrInsufficientBalance = errors.New("insufficient balance for transfer") + ErrContractAddressCollision = errors.New("contract address collision") + ErrExecutionReverted = errors.New("execution reverted") + ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") + ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded") + ErrInvalidJump = errors.New("invalid jump destination") + ErrWriteProtection = errors.New("write protection") + ErrReturnDataOutOfBounds = errors.New("return data out of bounds") + ErrGasUintOverflow = errors.New("gas uint64 overflow") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + + // errStopToken is an internal token indicating interpreter loop termination, + // never returned to outside callers. + errStopToken = errors.New("stop token") +) + +// ErrStackUnderflow wraps an evm error when the items on the stack less +// than the minimal requirement. +type ErrStackUnderflow struct { + stackLen int + required int +} + +func (e *ErrStackUnderflow) Error() string { + return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required) +} + +// ErrStackOverflow wraps an evm error when the items on the stack exceeds +// the maximum allowance. +type ErrStackOverflow struct { + stackLen int + limit int +} + +func (e *ErrStackOverflow) Error() string { + return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit) +} + +// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered. +type ErrInvalidOpCode struct { + opcode OpCode +} + +func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } + +// rpcError is the same interface as the one defined in rpc/errors.go +// but we do not want to depend on rpc package here so we redefine it. +// +// It's used to ensure that the VMError implements the RPC error interface. +type rpcError interface { + Error() string // returns the message + ErrorCode() int // returns the code +} + +var _ rpcError = (*VMError)(nil) + +// VMError wraps a VM error with an additional stable error code. The error +// field is the original error that caused the VM error and must be one of the +// VM error defined at the top of this file. +// +// If the error is not one of the known error above, the error code will be +// set to VMErrorCodeUnknown. +type VMError struct { + error + code int +} + +func VMErrorFromErr(err error) error { + if err == nil { + return nil + } + + return &VMError{ + error: err, + code: vmErrorCodeFromErr(err), + } +} + +func (e *VMError) Error() string { + return e.error.Error() +} + +func (e *VMError) Unwrap() error { + return e.error +} + +func (e *VMError) ErrorCode() int { + return e.code +} + +const ( + // We start the error code at 1 so that we can use 0 later for some possible extension. There + // is no unspecified value for the code today because it should always be set to a valid value + // that could be VMErrorCodeUnknown if the error is not mapped to a known error code. + + VMErrorCodeOutOfGas = 1 + iota + VMErrorCodeCodeStoreOutOfGas + VMErrorCodeDepth + VMErrorCodeInsufficientBalance + VMErrorCodeContractAddressCollision + VMErrorCodeExecutionReverted + VMErrorCodeMaxCodeSizeExceeded + VMErrorCodeInvalidJump + VMErrorCodeWriteProtection + VMErrorCodeReturnDataOutOfBounds + VMErrorCodeGasUintOverflow + VMErrorCodeInvalidCode + VMErrorCodeNonceUintOverflow + VMErrorCodeStackUnderflow + VMErrorCodeStackOverflow + VMErrorCodeInvalidOpCode + + // VMErrorCodeUnknown explicitly marks an error as unknown, this is useful when error is converted + // from an actual `error` in which case if the mapping is not known, we can use this value to indicate that. + VMErrorCodeUnknown = math.MaxInt - 1 +) + +func vmErrorCodeFromErr(err error) int { + switch { + case errors.Is(err, ErrOutOfGas): + return VMErrorCodeOutOfGas + case errors.Is(err, ErrCodeStoreOutOfGas): + return VMErrorCodeCodeStoreOutOfGas + case errors.Is(err, ErrDepth): + return VMErrorCodeDepth + case errors.Is(err, ErrInsufficientBalance): + return VMErrorCodeInsufficientBalance + case errors.Is(err, ErrContractAddressCollision): + return VMErrorCodeContractAddressCollision + case errors.Is(err, ErrExecutionReverted): + return VMErrorCodeExecutionReverted + case errors.Is(err, ErrMaxCodeSizeExceeded): + return VMErrorCodeMaxCodeSizeExceeded + case errors.Is(err, ErrInvalidJump): + return VMErrorCodeInvalidJump + case errors.Is(err, ErrWriteProtection): + return VMErrorCodeWriteProtection + case errors.Is(err, ErrReturnDataOutOfBounds): + return VMErrorCodeReturnDataOutOfBounds + case errors.Is(err, ErrGasUintOverflow): + return VMErrorCodeGasUintOverflow + case errors.Is(err, ErrInvalidCode): + return VMErrorCodeInvalidCode + case errors.Is(err, ErrNonceUintOverflow): + return VMErrorCodeNonceUintOverflow + + default: + // Dynamic errors + if v := (*ErrStackUnderflow)(nil); errors.As(err, &v) { + return VMErrorCodeStackUnderflow + } + + if v := (*ErrStackOverflow)(nil); errors.As(err, &v) { + return VMErrorCodeStackOverflow + } + + if v := (*ErrInvalidOpCode)(nil); errors.As(err, &v) { + return VMErrorCodeInvalidOpCode + } + + return VMErrorCodeUnknown + } +} diff --git a/core/vm/evm.go b/core/vm/evm.go new file mode 100644 index 0000000..dbdff98 --- /dev/null +++ b/core/vm/evm.go @@ -0,0 +1,633 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "errors" + "math/big" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +type ( + // CanTransferFunc is the signature of a transfer guard function + CanTransferFunc func(StateDB, common.Address, *uint256.Int) bool + // TransferFunc is the signature of a transfer function + TransferFunc func(StateDB, common.Address, common.Address, *uint256.Int) + // GetHashFunc returns the n'th block hash in the blockchain + // and is used by the BLOCKHASH EVM op code. + GetHashFunc func(uint64) common.Hash +) + +func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { + var precompiles map[common.Address]PrecompiledContract + switch { + case evm.chainRules.IsVerkle: + precompiles = PrecompiledContractsVerkle + case evm.chainRules.IsPrague: + precompiles = PrecompiledContractsPrague + case evm.chainRules.IsCancun: + precompiles = PrecompiledContractsCancun + case evm.chainRules.IsBerlin: + precompiles = PrecompiledContractsBerlin + case evm.chainRules.IsIstanbul: + precompiles = PrecompiledContractsIstanbul + case evm.chainRules.IsByzantium: + precompiles = PrecompiledContractsByzantium + default: + precompiles = PrecompiledContractsHomestead + } + p, ok := precompiles[addr] + return p, ok +} + +// BlockContext provides the EVM with auxiliary information. Once provided +// it shouldn't be modified. +type BlockContext struct { + // CanTransfer returns whether the account contains + // sufficient ether to transfer the value + CanTransfer CanTransferFunc + // Transfer transfers ether from one account to the other + Transfer TransferFunc + // GetHash returns the hash corresponding to n + 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 (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. +// All fields can change between transactions. +type TxContext struct { + // Message information + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE (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 + AccessEvents *state.AccessEvents // Capture all state accesses for this tx +} + +// EVM is the Ethereum Virtual Machine base object and provides +// the necessary tools to run a contract on the given state with +// the provided context. It should be noted that any error +// generated through any of the calls should be considered a +// revert-state-and-consume-all-gas operation, no checks on +// specific errors should ever be performed. The interpreter makes +// sure that any errors generated are to be considered faulty code. +// +// The EVM should never be reused and is not thread safe. +type EVM struct { + // Context provides auxiliary blockchain related information + Context BlockContext + TxContext + // StateDB gives access to the underlying state + StateDB StateDB + // Depth is the current call stack + depth int + + // chainConfig contains information about the current chain + chainConfig *params.ChainConfig + // chain rules contains the chain rules for the current epoch + chainRules params.Rules + // virtual machine configuration options used to initialise the + // evm. + Config Config + // global (to this context) ethereum virtual machine + // used throughout the execution of the tx. + interpreter *EVMInterpreter + // abort is used to abort the EVM calling operations + abort atomic.Bool + // callGasTemp holds the gas available for the current call. This is needed because the + // available gas is calculated in gasCall* according to the 63/64 rule and later + // applied in opCall*. + callGasTemp uint64 +} + +// 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, + StateDB: statedb, + Config: config, + chainConfig: chainConfig, + chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), + } + evm.interpreter = NewEVMInterpreter(evm) + return evm +} + +// Reset resets the EVM with a new transaction context.Reset +// This is not threadsafe and should only be done very cautiously. +func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { + if evm.chainRules.IsEIP4762 { + txCtx.AccessEvents = state.NewAccessEvents(statedb.PointCache()) + } + evm.TxContext = txCtx + evm.StateDB = statedb +} + +// Cancel cancels any running EVM operation. This may be called concurrently and +// it's safe to be called multiple times. +func (evm *EVM) Cancel() { + evm.abort.Store(true) +} + +// Cancelled returns true if Cancel has been called +func (evm *EVM) Cancelled() bool { + return evm.abort.Load() +} + +// Interpreter returns the current interpreter +func (evm *EVM) Interpreter() *EVMInterpreter { + return evm.interpreter +} + +// 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 +// execution error or failed value transfer. +func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { + // Capture the tracer start/end events in debug mode + if evm.Config.Tracer != nil { + evm.captureBegin(evm.depth, CALL, caller.Address(), addr, input, gas, value.ToBig()) + defer func(startGas uint64) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) + } + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + // Fail if we're trying to transfer more than the available balance + if !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, gas, ErrInsufficientBalance + } + snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) + + if !evm.StateDB.Exist(addr) { + if !isPrecompile && evm.chainRules.IsEIP4762 { + // add proof of absence to witness + wgas := evm.AccessEvents.AddAccount(addr, false) + if gas < wgas { + evm.StateDB.RevertToSnapshot(snapshot) + return nil, 0, ErrOutOfGas + } + gas -= wgas + } + + if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { + // Calling a non-existing account, don't do anything. + return nil, gas, nil + } + evm.StateDB.CreateAccount(addr) + } + evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) + + if isPrecompile { + ret, gas, err = RunPrecompiledContract(evm, p, input, gas, evm.Config.Tracer) + log.Info("Call", "precompile", true, "ret", ret, "gas", gas, "err", err) + } else { + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + code := evm.StateDB.GetCode(addr) + if witness := evm.StateDB.Witness(); witness != nil { + witness.AddCode(code) + } + if len(code) == 0 { + ret, err = nil, nil // gas is unchanged + } else { + addrCopy := addr + // If the account has no code, we can abort here + // The depth-check is already done, and precompiles handled above + contract := NewContract(caller, AccountRef(addrCopy), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + } + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally, + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + } + + gas = 0 + } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) + } + return ret, gas, err +} + +// CallCode 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 +// execution error or failed value transfer. +// +// CallCode differs from Call in the sense that it executes the given address' +// code with the caller as context. +func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) { + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Tracer != nil { + evm.captureBegin(evm.depth, CALLCODE, caller.Address(), addr, input, gas, value.ToBig()) + defer func(startGas uint64) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) + } + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + // Fail if we're trying to transfer more than the available balance + // Note although it's noop to transfer X ether to caller itself. But + // if caller doesn't have enough balance, it would be an error to allow + // over-charging itself. So the check here is necessary. + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, gas, ErrInsufficientBalance + } + var snapshot = evm.StateDB.Snapshot() + + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(evm, p, input, gas, evm.Config.Tracer) + } else { + addrCopy := addr + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(caller.Address()), value, gas) + if witness := evm.StateDB.Witness(); witness != nil { + witness.AddCode(evm.StateDB.GetCode(addrCopy)) + } + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + } + + gas = 0 + } + } + return ret, gas, err +} + +// DelegateCall executes the contract associated with the addr with the given input +// as parameters. It reverses the state in case of an execution error. +// +// DelegateCall differs from CallCode in the sense that it executes the given address' +// code with the caller as context and the caller is set to the caller of the caller. +func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Tracer != nil { + // NOTE: caller must, at all times be a contract. It should never happen + // that caller is something other than a Contract. + parent := caller.(*Contract) + // DELEGATECALL inherits value from parent call + evm.captureBegin(evm.depth, DELEGATECALL, caller.Address(), addr, input, gas, parent.value.ToBig()) + defer func(startGas uint64) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) + } + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + var snapshot = evm.StateDB.Snapshot() + + // It is allowed to call precompiles, even via delegatecall + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(evm, p, input, gas, evm.Config.Tracer) + } else { + addrCopy := addr + // Initialise a new contract and make initialise the delegate values + contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() + if witness := evm.StateDB.Witness(); witness != nil { + witness.AddCode(evm.StateDB.GetCode(addrCopy)) + } + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + } + gas = 0 + } + } + return ret, gas, err +} + +// StaticCall executes the contract associated with the addr with the given input +// as parameters while disallowing any modifications to the state during the call. +// Opcodes that attempt to perform such modifications will result in exceptions +// instead of performing the modifications. +func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Tracer != nil { + evm.captureBegin(evm.depth, STATICCALL, caller.Address(), addr, input, gas, nil) + defer func(startGas uint64) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) + } + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. + // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced + // after all empty accounts were deleted, so this is not required. However, if we omit this, + // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. + // We could change this, but for now it's left for legacy reasons + var snapshot = evm.StateDB.Snapshot() + + // We do an AddBalance of zero here, just in order to trigger a touch. + // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, + // but is the correct thing to do and matters on other networks, in tests, and potential + // future scenarios + evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount) + + if p, isPrecompile := evm.precompile(addr); isPrecompile { + ret, gas, err = RunPrecompiledContract(evm, p, input, gas, evm.Config.Tracer) + } else { + // At this point, we use a copy of address. If we don't, the go compiler will + // leak the 'contract' to the outer scope, and make allocation for 'contract' + // even if the actual execution ends on RunPrecompiled above. + addrCopy := addr + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas) + if witness := evm.StateDB.Witness(); witness != nil { + witness.AddCode(evm.StateDB.GetCode(addrCopy)) + } + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in Homestead this also counts for code storage gas errors. + ret, err = evm.interpreter.Run(contract, input, true) + gas = contract.Gas + } + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + } + + gas = 0 + } + } + return ret, gas, err +} + +type codeAndHash struct { + code []byte + hash common.Hash +} + +func (c *codeAndHash) Hash() common.Hash { + if c.hash == (common.Hash{}) { + c.hash = crypto.Keccak256Hash(c.code) + } + return c.hash +} + +// create creates a new contract using code as deployment code. +func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { + if evm.Config.Tracer != nil { + evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig()) + defer func(startGas uint64) { + evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) + }(gas) + } + // Depth check execution. Fail if we're trying to execute above the + // limit. + if evm.depth > int(params.CallCreateDepth) { + return nil, common.Address{}, gas, ErrDepth + } + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, common.Address{}, gas, ErrInsufficientBalance + } + nonce := evm.StateDB.GetNonce(caller.Address()) + if nonce+1 < nonce { + return nil, common.Address{}, gas, ErrNonceUintOverflow + } + evm.StateDB.SetNonce(caller.Address(), nonce+1) + + // We add this to the access list _before_ taking a snapshot. Even if the + // creation fails, the access-list change should not be rolled back. + if evm.chainRules.IsEIP2929 { + evm.StateDB.AddAddressToAccessList(address) + } + // Ensure there's no existing contract already at the designated address. + // Account is regarded as existent if any of these three conditions is met: + // - the nonce is non-zero + // - the code is non-empty + // - the storage is non-empty + contractHash := evm.StateDB.GetCodeHash(address) + storageRoot := evm.StateDB.GetStorageRoot(address) + if evm.StateDB.GetNonce(address) != 0 || + (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code + (storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) + } + return nil, common.Address{}, 0, ErrContractAddressCollision + } + // Create a new account on the state only if the object was not present. + // It might be possible the contract code is deployed to a pre-existent + // account with non-zero balance. + snapshot := evm.StateDB.Snapshot() + if !evm.StateDB.Exist(address) { + evm.StateDB.CreateAccount(address) + } + // CreateContract means that regardless of whether the account previously existed + // in the state trie or not, it _now_ becomes created as a _contract_ account. + // This is performed _prior_ to executing the initcode, since the initcode + // acts inside that account. + evm.StateDB.CreateContract(address) + + if evm.chainRules.IsEIP158 { + evm.StateDB.SetNonce(address, 1) + } + evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) + + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(address), value, gas) + contract.SetCodeOptionalHash(&address, codeAndHash) + contract.IsDeployment = true + + // Charge the contract creation init gas in verkle mode + if evm.chainRules.IsEIP4762 { + if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) { + err = ErrOutOfGas + } + } + + if err == nil { + ret, err = evm.interpreter.Run(contract, nil, false) + } + + // Check whether the max code size has been exceeded, assign err if the case. + if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { + err = ErrMaxCodeSizeExceeded + } + + // Reject code starting with 0xEF if EIP-3541 is enabled. + if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { + err = ErrInvalidCode + } + + // if the contract creation ran successfully and no errors were returned + // calculate the gas required to store the code. If the code could not + // be stored due to not enough gas set an error and let it be handled + // by the error checking condition below. + if err == nil { + if !evm.chainRules.IsEIP4762 { + createDataGas := uint64(len(ret)) * params.CreateDataGas + if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { + err = ErrCodeStoreOutOfGas + } + } else { + // Contract creation completed, touch the missing fields in the contract + if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) { + err = ErrCodeStoreOutOfGas + } + + if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) { + err = ErrCodeStoreOutOfGas + } + } + + if err == nil { + evm.StateDB.SetCode(address, ret) + } + } + + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally, + // when we're in homestead this also counts for code storage gas errors. + if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { + evm.StateDB.RevertToSnapshot(snapshot) + if err != ErrExecutionReverted { + contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) + } + } + + return ret, address, contract.Gas, err +} + +// Create creates a new contract using code as deployment code. +func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) + return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE) +} + +// Create2 creates a new contract using code as deployment code. +// +// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] +// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. +func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { + codeAndHash := &codeAndHash{code: code} + contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2) +} + +// ChainConfig returns the environment's chain configuration +func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } + +func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) { + tracer := evm.Config.Tracer + if tracer.OnEnter != nil { + tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value) + } + if tracer.OnGasChange != nil { + tracer.OnGasChange(0, startGas, tracing.GasChangeCallInitialBalance) + } +} + +func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) { + tracer := evm.Config.Tracer + if leftOverGas != 0 && tracer.OnGasChange != nil { + tracer.OnGasChange(leftOverGas, 0, tracing.GasChangeCallLeftOverReturned) + } + var reverted bool + if err != nil { + reverted = true + } + if !evm.chainRules.IsHomestead && errors.Is(err, ErrCodeStoreOutOfGas) { + reverted = false + } + if tracer.OnExit != nil { + tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) + } +} + +// GetVMContext provides context about the block being executed as well as state +// to the tracers. +func (evm *EVM) GetVMContext() *tracing.VMContext { + return &tracing.VMContext{ + Coinbase: evm.Context.Coinbase, + BlockNumber: evm.Context.BlockNumber, + Time: evm.Context.Time, + Random: evm.Context.Random, + GasPrice: evm.TxContext.GasPrice, + ChainConfig: evm.ChainConfig(), + StateDB: evm.StateDB, + } +} diff --git a/core/vm/gas.go b/core/vm/gas.go new file mode 100644 index 0000000..5cf1d85 --- /dev/null +++ b/core/vm/gas.go @@ -0,0 +1,53 @@ +// 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 vm + +import ( + "github.com/holiman/uint256" +) + +// Gas costs +const ( + GasQuickStep uint64 = 2 + GasFastestStep uint64 = 3 + GasFastStep uint64 = 5 + GasMidStep uint64 = 8 + GasSlowStep uint64 = 10 + GasExtStep uint64 = 20 +) + +// callGas returns the actual gas cost of the call. +// +// The cost of gas was changed during the homestead price change HF. +// As part of EIP 150 (TangerineWhistle), the returned gas is gas - base * 63 / 64. +func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (uint64, error) { + if isEip150 { + availableGas = availableGas - base + gas := availableGas - availableGas/64 + // If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150 + // is smaller than the requested amount. Therefore we return the new gas instead + // of returning an error. + if !callCost.IsUint64() || gas < callCost.Uint64() { + return gas, nil + } + } + if !callCost.IsUint64() { + return 0, ErrGasUintOverflow + } + + return callCost.Uint64(), nil +} diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go new file mode 100644 index 0000000..d294324 --- /dev/null +++ b/core/vm/gas_table.go @@ -0,0 +1,504 @@ +// 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 vm + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +// memoryGasCost calculates the quadratic gas for memory expansion. It does so +// only for the memory region that is expanded, not the total memory. +func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { + if newMemSize == 0 { + return 0, nil + } + // The maximum that will fit in a uint64 is max_word_count - 1. Anything above + // that will result in an overflow. Additionally, a newMemSize which results in + // a newMemSizeWords larger than 0xFFFFFFFF will cause the square operation to + // overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used + // without overflowing the gas calculation. + if newMemSize > 0x1FFFFFFFE0 { + return 0, ErrGasUintOverflow + } + newMemSizeWords := toWordSize(newMemSize) + newMemSize = newMemSizeWords * 32 + + if newMemSize > uint64(mem.Len()) { + square := newMemSizeWords * newMemSizeWords + linCoef := newMemSizeWords * params.MemoryGas + quadCoef := square / params.QuadCoeffDiv + newTotalFee := linCoef + quadCoef + + fee := newTotalFee - mem.lastGasCost + mem.lastGasCost = newTotalFee + + return fee, nil + } + return 0, nil +} + +// memoryCopierGas creates the gas functions for the following opcodes, and takes +// the stack position of the operand which determines the size of the data to copy +// as argument: +// CALLDATACOPY (stack position 2) +// CODECOPY (stack position 2) +// MCOPY (stack position 2) +// EXTCODECOPY (stack position 3) +// RETURNDATACOPY (stack position 2) +func memoryCopierGas(stackpos int) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // Gas for expanding the memory + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + // And gas for copying data, charged per word at param.CopyGas + words, overflow := stack.Back(stackpos).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + + if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { + return 0, ErrGasUintOverflow + } + + if gas, overflow = math.SafeAdd(gas, words); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } +} + +var ( + gasCallDataCopy = memoryCopierGas(2) + gasCodeCopy = memoryCopierGas(2) + gasMcopy = memoryCopierGas(2) + gasExtCodeCopy = memoryCopierGas(3) + gasReturnDataCopy = memoryCopierGas(2) +) + +func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + y, x = stack.Back(1), stack.Back(0) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) + ) + // The legacy gas metering only takes into consideration the current state + // 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 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) + // 3. From a non-zero to a non-zero (CHANGE) + switch { + case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0 + return params.SstoreSetGas, nil + case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0 + evm.StateDB.AddRefund(params.SstoreRefundGas) + return params.SstoreClearGas, nil + default: // non 0 => non 0 (or 0 => 0) + return params.SstoreResetGas, nil + } + } + + // The new gas metering is based on net gas costs (EIP-1283): + // + // (1.) If current value equals new value (this is a no-op), 200 gas is deducted. + // (2.) If current value does not equal new value + // (2.1.) If original value equals current value (this storage slot has not been changed by the current execution context) + // (2.1.1.) If original value is 0, 20000 gas is deducted. + // (2.1.2.) Otherwise, 5000 gas is deducted. If new value is 0, add 15000 gas to refund counter. + // (2.2.) If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses. + // (2.2.1.) If original value is not 0 + // (2.2.1.1.) If current value is 0 (also means that new value is not 0), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0. + // (2.2.1.2.) If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter. + // (2.2.2.) If original value equals new value (this storage slot is reset) + // (2.2.2.1.) If original value is 0, add 19800 gas to refund counter. + // (2.2.2.2.) Otherwise, add 4800 gas to refund counter. + value := common.Hash(y.Bytes32()) + if current == value { // noop (1) + return params.NetSstoreNoopGas, nil + } + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return params.NetSstoreInitGas, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.NetSstoreClearRefund) + } + return params.NetSstoreCleanGas, nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(params.NetSstoreClearRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(params.NetSstoreClearRefund) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + evm.StateDB.AddRefund(params.NetSstoreResetClearRefund) + } else { // reset to original existing slot (2.2.2.2) + evm.StateDB.AddRefund(params.NetSstoreResetRefund) + } + } + return params.NetSstoreDirtyGas, nil +} + +// Here come the EIP2200 rules: +// +// (0.) If *gasleft* is less than or equal to 2300, fail the current call. +// (1.) If current value equals new value (this is a no-op), SLOAD_GAS is deducted. +// (2.) If current value does not equal new value: +// (2.1.) If original value equals current value (this storage slot has not been changed by the current execution context): +// (2.1.1.) If original value is 0, SSTORE_SET_GAS (20K) gas is deducted. +// (2.1.2.) Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter. +// (2.2.) If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses: +// (2.2.1.) If original value is not 0: +// (2.2.1.1.) If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter. +// (2.2.1.2.) If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter. +// (2.2.2.) If original value equals new value (this storage slot is reset): +// (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. +// (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. +func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.Back(0) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) + ) + value := common.Hash(y.Bytes32()) + + if current == value { // noop (1) + return params.SloadGasEIP2200, nil + } + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return params.SstoreSetGasEIP2200, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + } + return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + } else { // reset to original existing slot (2.2.2.2) + evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + } + } + return params.SloadGasEIP2200, nil // dirty update (2.2) +} + +func makeGasLog(n uint64) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + requestedSize, overflow := stack.Back(1).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + + if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { + return 0, ErrGasUintOverflow + } + if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { + return 0, ErrGasUintOverflow + } + + var memorySizeGas uint64 + if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { + return 0, ErrGasUintOverflow + } + if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } +} + +func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + wordGas, overflow := stack.Back(1).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { + return 0, ErrGasUintOverflow + } + if gas, overflow = math.SafeAdd(gas, wordGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +// pureMemoryGascost is used by several operations, which aside from their +// static cost have a dynamic cost which is solely based on the memory +// expansion +func pureMemoryGascost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return memoryGasCost(mem, memorySize) +} + +var ( + gasReturn = pureMemoryGascost + gasRevert = pureMemoryGascost + gasMLoad = pureMemoryGascost + gasMStore8 = pureMemoryGascost + gasMStore = pureMemoryGascost + gasCreate = pureMemoryGascost +) + +func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + wordGas, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow { + return 0, ErrGasUintOverflow + } + if gas, overflow = math.SafeAdd(gas, wordGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if size > params.MaxInitCodeSize { + return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) + } + // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + moreGas := params.InitCodeWordGas * ((size + 31) / 32) + if gas, overflow = math.SafeAdd(gas, moreGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} +func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + size, overflow := stack.Back(2).Uint64WithOverflow() + if overflow { + return 0, ErrGasUintOverflow + } + if size > params.MaxInitCodeSize { + return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size) + } + // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32) + if gas, overflow = math.SafeAdd(gas, moreGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) + + var ( + gas = expByteLen * params.ExpByteFrontier // no overflow check required. Max is 256 * ExpByte gas + overflow bool + ) + if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) + + var ( + gas = expByteLen * params.ExpByteEIP158 // no overflow check required. Max is 256 * ExpByte gas + overflow bool + ) + if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + transfersValue = !stack.Back(2).IsZero() + address = common.Address(stack.Back(1).Bytes20()) + ) + if evm.chainRules.IsEIP158 { + if transfersValue && evm.StateDB.Empty(address) { + gas += params.CallNewAccountGas + } + } else if !evm.StateDB.Exist(address) { + gas += params.CallNewAccountGas + } + if transfersValue && !evm.chainRules.IsEIP4762 { + gas += params.CallValueTransferGas + } + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, ErrGasUintOverflow + } + if evm.chainRules.IsEIP4762 { + if transfersValue { + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) + if overflow { + return 0, ErrGasUintOverflow + } + } + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + + return gas, nil +} + +func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + var ( + gas uint64 + overflow bool + ) + if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { + gas += params.CallValueTransferGas + } + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, ErrGasUintOverflow + } + if evm.chainRules.IsEIP4762 { + address := common.Address(stack.Back(1).Bytes20()) + transfersValue := !stack.Back(2).IsZero() + if transfersValue { + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) + if overflow { + return 0, ErrGasUintOverflow + } + } + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} + +func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var gas uint64 + // EIP150 homestead gas reprice fork: + if evm.chainRules.IsEIP150 { + gas = params.SelfdestructGasEIP150 + var address = common.Address(stack.Back(0).Bytes20()) + + if evm.chainRules.IsEIP158 { + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + } else if !evm.StateDB.Exist(address) { + gas += params.CreateBySelfdestructGas + } + } + + if !evm.StateDB.HasSelfDestructed(contract.Address()) { + evm.StateDB.AddRefund(params.SelfdestructRefundGas) + } + return gas, nil +} diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go new file mode 100644 index 0000000..02fc948 --- /dev/null +++ b/core/vm/gas_table_test.go @@ -0,0 +1,182 @@ +// 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 vm + +import ( + "bytes" + "errors" + "math" + "math/big" + "sort" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +func TestMemoryGasCost(t *testing.T) { + tests := []struct { + size uint64 + cost uint64 + overflow bool + }{ + {0x1fffffffe0, 36028809887088637, false}, + {0x1fffffffe1, 0, true}, + } + for i, tt := range tests { + v, err := memoryGasCost(&Memory{}, tt.size) + if (err == ErrGasUintOverflow) != tt.overflow { + t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == ErrGasUintOverflow, tt.overflow) + } + if v != tt.cost { + t.Errorf("test %d: gas cost mismatch: have %v, want %v", i, v, tt.cost) + } + } +} + +var eip2200Tests = []struct { + original byte + gaspool uint64 + input string + used uint64 + refund uint64 + failure error +}{ + {0, math.MaxUint64, "0x60006000556000600055", 1612, 0, nil}, // 0 -> 0 -> 0 + {0, math.MaxUint64, "0x60006000556001600055", 20812, 0, nil}, // 0 -> 0 -> 1 + {0, math.MaxUint64, "0x60016000556000600055", 20812, 19200, nil}, // 0 -> 1 -> 0 + {0, math.MaxUint64, "0x60016000556002600055", 20812, 0, nil}, // 0 -> 1 -> 2 + {0, math.MaxUint64, "0x60016000556001600055", 20812, 0, nil}, // 0 -> 1 -> 1 + {1, math.MaxUint64, "0x60006000556000600055", 5812, 15000, nil}, // 1 -> 0 -> 0 + {1, math.MaxUint64, "0x60006000556001600055", 5812, 4200, nil}, // 1 -> 0 -> 1 + {1, math.MaxUint64, "0x60006000556002600055", 5812, 0, nil}, // 1 -> 0 -> 2 + {1, math.MaxUint64, "0x60026000556000600055", 5812, 15000, nil}, // 1 -> 2 -> 0 + {1, math.MaxUint64, "0x60026000556003600055", 5812, 0, nil}, // 1 -> 2 -> 3 + {1, math.MaxUint64, "0x60026000556001600055", 5812, 4200, nil}, // 1 -> 2 -> 1 + {1, math.MaxUint64, "0x60026000556002600055", 5812, 0, nil}, // 1 -> 2 -> 2 + {1, math.MaxUint64, "0x60016000556000600055", 5812, 15000, nil}, // 1 -> 1 -> 0 + {1, math.MaxUint64, "0x60016000556002600055", 5812, 0, nil}, // 1 -> 1 -> 2 + {1, math.MaxUint64, "0x60016000556001600055", 1612, 0, nil}, // 1 -> 1 -> 1 + {0, math.MaxUint64, "0x600160005560006000556001600055", 40818, 19200, nil}, // 0 -> 1 -> 0 -> 1 + {1, math.MaxUint64, "0x600060005560016000556000600055", 10818, 19200, nil}, // 1 -> 0 -> 1 -> 0 + {1, 2306, "0x6001600055", 2306, 0, ErrOutOfGas}, // 1 -> 1 (2300 sentry + 2xPUSH) + {1, 2307, "0x6001600055", 806, 0, nil}, // 1 -> 1 (2301 sentry + 2xPUSH) +} + +func TestEIP2200(t *testing.T) { + for i, tt := range eip2200Tests { + address := common.BytesToAddress([]byte("contract")) + + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.CreateAccount(address) + statedb.SetCode(address, hexutil.MustDecode(tt.input)) + statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original})) + statedb.Finalise(true) // Push the state into the "original" slot + + vmctx := BlockContext{ + CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true }, + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) + + _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(uint256.Int)) + if !errors.Is(err, tt.failure) { + t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) + } + if used := tt.gaspool - gas; used != tt.used { + t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used) + } + if refund := vmenv.StateDB.GetRefund(); refund != tt.refund { + t.Errorf("test %d: gas refund mismatch: have %v, want %v", i, refund, tt.refund) + } + } +} + +var createGasTests = []struct { + code string + eip3860 bool + gasUsed uint64 + minimumGas uint64 +}{ + // legacy create(0, 0, 0xc000) without 3860 used + {"0x61C00060006000f0" + "600052" + "60206000F3", false, 41237, 41237}, + // legacy create(0, 0, 0xc000) _with_ 3860 + {"0x61C00060006000f0" + "600052" + "60206000F3", true, 44309, 44309}, + // create2(0, 0, 0xc001, 0) without 3860 + {"0x600061C00160006000f5" + "600052" + "60206000F3", false, 50471, 50471}, + // create2(0, 0, 0xc001, 0) (too large), with 3860 + {"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32012, 100_000}, + // create2(0, 0, 0xc000, 0) + // This case is trying to deploy code at (within) the limit + {"0x600061C00060006000f5" + "600052" + "60206000F3", true, 53528, 53528}, + // create2(0, 0, 0xc001, 0) + // This case is trying to deploy code exceeding the limit + {"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32024, 100000}, +} + +func TestCreateGas(t *testing.T) { + for i, tt := range createGasTests { + var gasUsed = uint64(0) + doCheck := func(testGas int) bool { + address := common.BytesToAddress([]byte("contract")) + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.CreateAccount(address) + statedb.SetCode(address, hexutil.MustDecode(tt.code)) + statedb.Finalise(true) + vmctx := BlockContext{ + CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true }, + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + BlockNumber: big.NewInt(0), + } + config := Config{} + if tt.eip3860 { + config.ExtraEips = []int{3860} + } + + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, config) + var startGas = uint64(testGas) + ret, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(uint256.Int)) + if err != nil { + return false + } + gasUsed = startGas - gas + if len(ret) != 32 { + t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret)) + } + if bytes.Equal(ret, make([]byte, 32)) { + // Failure + return false + } + return true + } + minGas := sort.Search(100_000, doCheck) + if uint64(minGas) != tt.minimumGas { + t.Fatalf("test %d: min gas error, want %d, have %d", i, tt.minimumGas, minGas) + } + // If the deployment succeeded, we also check the gas used + if minGas < 100_000 { + if gasUsed != tt.gasUsed { + t.Errorf("test %d: gas used mismatch: have %v, want %v", i, gasUsed, tt.gasUsed) + } + } + } +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go new file mode 100644 index 0000000..9ec4544 --- /dev/null +++ b/core/vm/instructions.go @@ -0,0 +1,935 @@ +// 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 vm + +import ( + "math" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Add(&x, y) + return nil, nil +} + +func opSub(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Sub(&x, y) + return nil, nil +} + +func opMul(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Mul(&x, y) + return nil, nil +} + +func opDiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Div(&x, y) + return nil, nil +} + +func opSdiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.SDiv(&x, y) + return nil, nil +} + +func opMod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Mod(&x, y) + return nil, nil +} + +func opSmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.SMod(&x, y) + return nil, nil +} + +func opExp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + base, exponent := scope.Stack.pop(), scope.Stack.peek() + exponent.Exp(&base, exponent) + return nil, nil +} + +func opSignExtend(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + back, num := scope.Stack.pop(), scope.Stack.peek() + num.ExtendSign(num, &back) + return nil, nil +} + +func opNot(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() + x.Not(x) + return nil, nil +} + +func opLt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Lt(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opGt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Gt(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opSlt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Slt(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opSgt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Sgt(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opEq(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + if x.Eq(y) { + y.SetOne() + } else { + y.Clear() + } + return nil, nil +} + +func opIszero(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() + if x.IsZero() { + x.SetOne() + } else { + x.Clear() + } + return nil, nil +} + +func opAnd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.And(&x, y) + return nil, nil +} + +func opOr(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Or(&x, y) + return nil, nil +} + +func opXor(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() + y.Xor(&x, y) + return nil, nil +} + +func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + th, val := scope.Stack.pop(), scope.Stack.peek() + val.Byte(&th) + return nil, nil +} + +func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() + z.AddMod(&x, &y, z) + return nil, nil +} + +func opMulmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() + z.MulMod(&x, &y, z) + return nil, nil +} + +// opSHL implements Shift Left +// The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2, +// and pushes on the stack arg2 shifted to the left by arg1 number of bits. +func opSHL(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards + shift, value := scope.Stack.pop(), scope.Stack.peek() + if shift.LtUint64(256) { + value.Lsh(value, uint(shift.Uint64())) + } else { + value.Clear() + } + return nil, nil +} + +// opSHR implements Logical Shift Right +// The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2, +// and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill. +func opSHR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards + shift, value := scope.Stack.pop(), scope.Stack.peek() + if shift.LtUint64(256) { + value.Rsh(value, uint(shift.Uint64())) + } else { + value.Clear() + } + return nil, nil +} + +// opSAR implements Arithmetic Shift Right +// The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2, +// and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension. +func opSAR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + shift, value := scope.Stack.pop(), scope.Stack.peek() + if shift.GtUint64(256) { + if value.Sign() >= 0 { + value.Clear() + } else { + // Max negative shift: all bits set + value.SetAllOne() + } + return nil, nil + } + n := uint(shift.Uint64()) + value.SRsh(value, n) + return nil, nil +} + +func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.peek() + data := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) + + if interpreter.hasher == nil { + interpreter.hasher = crypto.NewKeccakState() + } else { + interpreter.hasher.Reset() + } + interpreter.hasher.Write(data) + interpreter.hasher.Read(interpreter.hasherBuf[:]) + + evm := interpreter.evm + if evm.Config.EnablePreimageRecording { + evm.StateDB.AddPreimage(interpreter.hasherBuf, data) + } + 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 +} + +func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() + address := common.Address(slot.Bytes20()) + slot.Set(interpreter.evm.StateDB.GetBalance(address)) + return nil, nil +} + +func opOrigin(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + 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 +} + +func opCallValue(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(scope.Contract.value) + return nil, nil +} + +func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() + if offset, overflow := x.Uint64WithOverflow(); !overflow { + data := getData(scope.Contract.Input, offset, 32) + x.SetBytes(data) + } else { + x.Clear() + } + return nil, nil +} + +func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Input)))) + return nil, nil +} + +func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + memOffset = scope.Stack.pop() + dataOffset = scope.Stack.pop() + length = scope.Stack.pop() + ) + dataOffset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + dataOffset64 = math.MaxUint64 + } + // These values are checked for overflow during gas cost calculation + memOffset64 := memOffset.Uint64() + length64 := length.Uint64() + scope.Memory.Set(memOffset64, length64, getData(scope.Contract.Input, dataOffset64, length64)) + + return nil, nil +} + +func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData)))) + return nil, nil +} + +func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + memOffset = scope.Stack.pop() + dataOffset = scope.Stack.pop() + length = scope.Stack.pop() + ) + + offset64, overflow := dataOffset.Uint64WithOverflow() + if overflow { + return nil, ErrReturnDataOutOfBounds + } + // we can reuse dataOffset now (aliasing it for clarity) + var end = dataOffset + end.Add(&dataOffset, &length) + end64, overflow := end.Uint64WithOverflow() + if overflow || uint64(len(interpreter.returnData)) < end64 { + return nil, ErrReturnDataOutOfBounds + } + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) + return nil, nil +} + +func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() + address := slot.Bytes20() + if witness := interpreter.evm.StateDB.Witness(); witness != nil { + witness.AddCode(interpreter.evm.StateDB.GetCode(address)) + } + slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) + return nil, nil +} + +func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Code)))) + return nil, nil +} + +func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + memOffset = scope.Stack.pop() + codeOffset = scope.Stack.pop() + length = scope.Stack.pop() + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = math.MaxUint64 + } + + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + return nil, nil +} + +func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + stack = scope.Stack + a = stack.pop() + memOffset = stack.pop() + codeOffset = stack.pop() + length = stack.pop() + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = math.MaxUint64 + } + addr := common.Address(a.Bytes20()) + code := interpreter.evm.StateDB.GetCode(addr) + if witness := interpreter.evm.StateDB.Witness(); witness != nil { + witness.AddCode(code) + } + codeCopy := getData(code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + + return nil, nil +} + +// opExtCodeHash returns the code hash of a specified account. +// There are several cases when the function is called, while we can relay everything +// to `state.GetCodeHash` function to ensure the correctness. +// +// 1. Caller tries to get the code hash of a normal contract account, state +// should return the relative code hash and set it as the result. +// +// 2. Caller tries to get the code hash of a non-existent account, state should +// return common.Hash{} and zero will be set as the result. +// +// 3. Caller tries to get the code hash for an account without contract code, state +// should return emptyCodeHash(0xc5d246...) as the result. +// +// 4. Caller tries to get the code hash of a precompiled account, the result should be +// zero or emptyCodeHash. +// +// It is worth noting that in order to avoid unnecessary create and clean, all precompile +// accounts on mainnet have been transferred 1 wei, so the return here should be +// emptyCodeHash. If the precompile account is not transferred any amount on a private or +// customized chain, the return value will be zero. +// +// 5. Caller tries to get the code hash for an account which is marked as self-destructed +// in the current transaction, the code hash of this account should be returned. +// +// 6. Caller tries to get the code hash for an account which is marked as deleted, this +// account should be regarded as a non-existent account and zero should be returned. +func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() + address := common.Address(slot.Bytes20()) + if interpreter.evm.StateDB.Empty(address) { + slot.Clear() + } else { + slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) + } + return nil, nil +} + +func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.GasPrice) + scope.Stack.push(v) + return nil, nil +} + +func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + num := scope.Stack.peek() + num64, overflow := num.Uint64WithOverflow() + if overflow { + num.Clear() + return nil, nil + } + + var upper, lower uint64 + upper = interpreter.evm.Context.BlockNumber.Uint64() + if upper < 257 { + lower = 0 + } else { + lower = upper - 256 + } + if num64 >= lower && num64 < upper { + res := interpreter.evm.Context.GetHash(num64) + if witness := interpreter.evm.StateDB.Witness(); witness != nil { + witness.AddBlockHash(num64) + } + num.SetBytes(res[:]) + } else { + num.Clear() + } + return nil, nil +} + +func opCoinbase(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes())) + return nil, nil +} + +func opTimestamp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.Time)) + return nil, nil +} + +func opNumber(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber) + scope.Stack.push(v) + return nil, nil +} + +func opDifficulty(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty) + scope.Stack.push(v) + return nil, nil +} + +func opRandom(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v := new(uint256.Int).SetBytes(interpreter.evm.Context.Random.Bytes()) + scope.Stack.push(v) + return nil, nil +} + +func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit)) + return nil, nil +} + +func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.pop() + return nil, nil +} + +func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v := scope.Stack.peek() + offset := int64(v.Uint64()) + v.SetBytes(scope.Memory.GetPtr(offset, 32)) + return nil, nil +} + +func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // pop value of the stack + mStart, val := scope.Stack.pop(), scope.Stack.pop() + scope.Memory.Set32(mStart.Uint64(), &val) + return nil, nil +} + +func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + off, val := scope.Stack.pop(), scope.Stack.pop() + scope.Memory.store[off.Uint64()] = byte(val.Uint64()) + return nil, nil +} + +func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.peek() + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) + loc.SetBytes(val.Bytes()) + return nil, nil +} + +func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + loc := scope.Stack.pop() + val := scope.Stack.pop() + interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) + return nil, nil +} + +func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.evm.abort.Load() { + return nil, errStopToken + } + pos := scope.Stack.pop() + if !scope.Contract.validJumpdest(&pos) { + return nil, ErrInvalidJump + } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + return nil, nil +} + +func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.evm.abort.Load() { + return nil, errStopToken + } + pos, cond := scope.Stack.pop(), scope.Stack.pop() + if !cond.IsZero() { + if !scope.Contract.validJumpdest(&pos) { + return nil, ErrInvalidJump + } + *pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop + } + return nil, nil +} + +func opJumpdest(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, nil +} + +func opPc(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(*pc)) + return nil, nil +} + +func opMsize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(uint64(scope.Memory.Len()))) + return nil, nil +} + +func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(scope.Contract.Gas)) + return nil, nil +} + +func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + value = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas + ) + if interpreter.evm.chainRules.IsEIP150 { + gas -= gas / 64 + } + + // reuse size int for stackvalue + stackvalue := size + + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation) + + res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, &value) + // Push item on the stack based on the returned error. If the ruleset is + // homestead we must check for CodeStoreOutOfGasError (homestead only + // rule) and treat as an error, if the ruleset is frontier we must + // ignore this error and pretend the operation was successful. + if interpreter.evm.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas { + stackvalue.Clear() + } else if suberr != nil && suberr != ErrCodeStoreOutOfGas { + stackvalue.Clear() + } else { + stackvalue.SetBytes(addr.Bytes()) + } + scope.Stack.push(&stackvalue) + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer + return res, nil + } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil +} + +func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + endowment = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + salt = scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas + ) + + // Apply EIP150 + gas -= gas / 64 + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2) + // reuse size int for stackvalue + stackvalue := size + res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas, + &endowment, &salt) + // Push item on the stack based on the returned error. + if suberr != nil { + stackvalue.Clear() + } else { + stackvalue.SetBytes(addr.Bytes()) + } + scope.Stack.push(&stackvalue) + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + if suberr == ErrExecutionReverted { + interpreter.returnData = res // set REVERT data to return data buffer + return res, nil + } + interpreter.returnData = nil // clear dirty return data buffer + return nil, nil +} + +func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack + // Pop gas. The actual gas in interpreter.evm.callGasTemp. + // We can use this as a temporary value + temp := stack.pop() + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + // Get the arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + if interpreter.readOnly && !value.IsZero() { + return nil, ErrWriteProtection + } + if !value.IsZero() { + gas += params.CallStipend + } + ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, &value) + + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil +} + +func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // Pop gas. The actual gas is in interpreter.evm.callGasTemp. + stack := scope.Stack + // We use it as a temporary value + temp := stack.pop() + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + // Get arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + if !value.IsZero() { + gas += params.CallStipend + } + + ret, returnGas, err := interpreter.evm.CallCode(scope.Contract, toAddr, args, gas, &value) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil +} + +func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack + // Pop gas. The actual gas is in interpreter.evm.callGasTemp. + // We use it as a temporary value + temp := stack.pop() + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + // Get arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil +} + +func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + // Pop gas. The actual gas is in interpreter.evm.callGasTemp. + stack := scope.Stack + // We use it as a temporary value + temp := stack.pop() + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + // Get arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + + scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) + + interpreter.returnData = ret + return ret, nil +} + +func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.pop() + ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) + + return ret, errStopToken +} + +func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.pop() + ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) + + interpreter.returnData = ret + return ret, ErrExecutionReverted +} + +func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])} +} + +func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, errStopToken +} + +func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + beneficiary := scope.Stack.pop() + balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct) + interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) + if tracer := interpreter.evm.Config.Tracer; tracer != nil { + if tracer.OnEnter != nil { + tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) + } + if tracer.OnExit != nil { + tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false) + } + } + return nil, errStopToken +} + +func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + beneficiary := scope.Stack.pop() + balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) + interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct) + interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) + if tracer := interpreter.evm.Config.Tracer; tracer != nil { + if tracer.OnEnter != nil { + tracer.OnEnter(interpreter.evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) + } + if tracer.OnExit != nil { + tracer.OnExit(interpreter.evm.depth, []byte{}, 0, nil, false) + } + } + return nil, errStopToken +} + +// following functions are used by the instruction jump table + +// make log instruction function +func makeLog(size int) executionFunc { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + topics := make([]common.Hash, size) + stack := scope.Stack + mStart, mSize := stack.pop(), stack.pop() + for i := 0; i < size; i++ { + addr := stack.pop() + topics[i] = addr.Bytes32() + } + + d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) + interpreter.evm.StateDB.AddLog(&types.Log{ + Address: scope.Contract.Address(), + Topics: topics, + Data: d, + // This is a non-consensus field, but assigned here because + // core/state doesn't know the current block number. + BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(), + }) + + return nil, nil + } +} + +// opPush1 is a specialized version of pushN +func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = uint64(len(scope.Contract.Code)) + integer = new(uint256.Int) + ) + *pc += 1 + if *pc < codeLen { + scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + } else { + scope.Stack.push(integer.Clear()) + } + return nil, nil +} + +// make push instruction function +func makePush(size uint64, pushByteSize int) executionFunc { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + codeLen = len(scope.Contract.Code) + start = min(codeLen, int(*pc+1)) + end = min(codeLen, start+pushByteSize) + ) + scope.Stack.push(new(uint256.Int).SetBytes( + common.RightPadBytes( + scope.Contract.Code[start:end], + pushByteSize, + )), + ) + + *pc += size + return nil, nil + } +} + +// make dup instruction function +func makeDup(size int64) executionFunc { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.dup(int(size)) + return nil, nil + } +} + +// make swap instruction function +func makeSwap(size int64) executionFunc { + // switch n + 1 otherwise n would be swapped with n + size++ + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.swap(int(size)) + return nil, nil + } +} diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go new file mode 100644 index 0000000..e17e913 --- /dev/null +++ b/core/vm/instructions_test.go @@ -0,0 +1,930 @@ +// 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 vm + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "os" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +type TwoOperandTestcase struct { + X string + Y string + Expected string +} + +type twoOperandParams struct { + x string + y string +} + +var alphabetSoup = "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" +var commonParams []*twoOperandParams +var twoOpMethods map[string]executionFunc + +type contractRef struct { + addr common.Address +} + +func (c contractRef) Address() common.Address { + return c.addr +} + +func init() { + // Params is a list of common edgecases that should be used for some common tests + params := []string{ + "0000000000000000000000000000000000000000000000000000000000000000", // 0 + "0000000000000000000000000000000000000000000000000000000000000001", // +1 + "0000000000000000000000000000000000000000000000000000000000000005", // +5 + "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", // + max -1 + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // + max + "8000000000000000000000000000000000000000000000000000000000000000", // - max + "8000000000000000000000000000000000000000000000000000000000000001", // - max+1 + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", // - 5 + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // - 1 + } + // Params are combined so each param is used on each 'side' + commonParams = make([]*twoOperandParams, len(params)*len(params)) + for i, x := range params { + for j, y := range params { + commonParams[i*len(params)+j] = &twoOperandParams{x, y} + } + } + twoOpMethods = map[string]executionFunc{ + "add": opAdd, + "sub": opSub, + "mul": opMul, + "div": opDiv, + "sdiv": opSdiv, + "mod": opMod, + "smod": opSmod, + "exp": opExp, + "signext": opSignExtend, + "lt": opLt, + "gt": opGt, + "slt": opSlt, + "sgt": opSgt, + "eq": opEq, + "and": opAnd, + "or": opOr, + "xor": opXor, + "byte": opByte, + "shl": opSHL, + "shr": opSHR, + "sar": opSAR, + } +} + +func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + + for i, test := range tests { + x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.X)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Y)) + expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) + stack.push(x) + stack.push(y) + opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + if len(stack.data) != 1 { + t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) + } + actual := stack.pop() + + if actual.Cmp(expected) != 0 { + t.Errorf("Testcase %v %d, %v(%x, %x): expected %x, got %x", name, i, name, x, y, expected, actual) + } + } +} + +func TestByteOp(t *testing.T) { + tests := []TwoOperandTestcase{ + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", "00", "AB"}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", "01", "CD"}, + {"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", "00", "00"}, + {"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", "01", "CD"}, + {"0000000000000000000000000000000000000000000000000000000000102030", "1F", "30"}, + {"0000000000000000000000000000000000000000000000000000000000102030", "1E", "20"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "20", "00"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "FFFFFFFFFFFFFFFF", "00"}, + } + testTwoOperandOp(t, tests, opByte, "byte") +} + +func TestSHL(t *testing.T) { + // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shl-shift-left + tests := []TwoOperandTestcase{ + {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000002"}, + {"0000000000000000000000000000000000000000000000000000000000000001", "ff", "8000000000000000000000000000000000000000000000000000000000000000"}, + {"0000000000000000000000000000000000000000000000000000000000000001", "0100", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"0000000000000000000000000000000000000000000000000000000000000001", "0101", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "8000000000000000000000000000000000000000000000000000000000000000"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"}, + } + testTwoOperandOp(t, tests, opSHL, "shl") +} + +func TestSHR(t *testing.T) { + // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shr-logical-shift-right + tests := []TwoOperandTestcase{ + {"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"}, + {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"8000000000000000000000000000000000000000000000000000000000000000", "01", "4000000000000000000000000000000000000000000000000000000000000000"}, + {"8000000000000000000000000000000000000000000000000000000000000000", "ff", "0000000000000000000000000000000000000000000000000000000000000001"}, + {"8000000000000000000000000000000000000000000000000000000000000000", "0100", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"8000000000000000000000000000000000000000000000000000000000000000", "0101", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "0000000000000000000000000000000000000000000000000000000000000001"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, + } + testTwoOperandOp(t, tests, opSHR, "shr") +} + +func TestSAR(t *testing.T) { + // Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#sar-arithmetic-shift-right + tests := []TwoOperandTestcase{ + {"0000000000000000000000000000000000000000000000000000000000000001", "00", "0000000000000000000000000000000000000000000000000000000000000001"}, + {"0000000000000000000000000000000000000000000000000000000000000001", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"8000000000000000000000000000000000000000000000000000000000000000", "01", "c000000000000000000000000000000000000000000000000000000000000000"}, + {"8000000000000000000000000000000000000000000000000000000000000000", "ff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"8000000000000000000000000000000000000000000000000000000000000000", "0100", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"8000000000000000000000000000000000000000000000000000000000000000", "0101", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "00", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "01", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}, + {"0000000000000000000000000000000000000000000000000000000000000000", "01", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"4000000000000000000000000000000000000000000000000000000000000000", "fe", "0000000000000000000000000000000000000000000000000000000000000001"}, + {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "f8", "000000000000000000000000000000000000000000000000000000000000007f"}, + {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "fe", "0000000000000000000000000000000000000000000000000000000000000001"}, + {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ff", "0000000000000000000000000000000000000000000000000000000000000000"}, + {"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0100", "0000000000000000000000000000000000000000000000000000000000000000"}, + } + + testTwoOperandOp(t, tests, opSAR, "sar") +} + +func TestAddMod(t *testing.T) { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + evmInterpreter = NewEVMInterpreter(env) + pc = uint64(0) + ) + tests := []struct { + x string + y string + z string + expected string + }{ + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + }, + } + // x + y = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + // in 256 bit repr, fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd + + for i, test := range tests { + x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.x)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.y)) + z := new(uint256.Int).SetBytes(common.Hex2Bytes(test.z)) + expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.expected)) + stack.push(z) + stack.push(y) + stack.push(x) + opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + actual := stack.pop() + if actual.Cmp(expected) != 0 { + t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) + } + } +} + +// utility function to fill the json-file with testcases +// Enable this test to generate the 'testcases_xx.json' files +func TestWriteExpectedValues(t *testing.T) { + t.Skip("Enable this test to create json test cases.") + + // getResult is a convenience function to generate the expected values + getResult := func(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + interpreter = env.interpreter + ) + result := make([]TwoOperandTestcase, len(args)) + for i, param := range args { + x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x)) + y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) + stack.push(x) + stack.push(y) + opFn(&pc, interpreter, &ScopeContext{nil, stack, nil}) + actual := stack.pop() + result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} + } + return result + } + + for name, method := range twoOpMethods { + data, err := json.Marshal(getResult(commonParams, method)) + if err != nil { + t.Fatal(err) + } + _ = os.WriteFile(fmt.Sprintf("testdata/testcases_%v.json", name), data, 0644) + if err != nil { + t.Fatal(err) + } + } +} + +// TestJsonTestcases runs through all the testcases defined as json-files +func TestJsonTestcases(t *testing.T) { + for name := range twoOpMethods { + data, err := os.ReadFile(fmt.Sprintf("testdata/testcases_%v.json", name)) + if err != nil { + t.Fatal("Failed to read file", err) + } + var testcases []TwoOperandTestcase + json.Unmarshal(data, &testcases) + testTwoOperandOp(t, testcases, twoOpMethods[name], name) + } +} + +func opBenchmark(bench *testing.B, op executionFunc, args ...string) { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + scope = &ScopeContext{nil, stack, nil} + evmInterpreter = NewEVMInterpreter(env) + ) + + env.interpreter = evmInterpreter + // convert args + intArgs := make([]*uint256.Int, len(args)) + for i, arg := range args { + intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg)) + } + pc := uint64(0) + bench.ResetTimer() + for i := 0; i < bench.N; i++ { + for _, arg := range intArgs { + stack.push(arg) + } + op(&pc, evmInterpreter, scope) + stack.pop() + } + bench.StopTimer() + + for i, arg := range args { + want := new(uint256.Int).SetBytes(common.Hex2Bytes(arg)) + if have := intArgs[i]; !want.Eq(have) { + bench.Fatalf("input #%d mutated, have %x want %x", i, have, want) + } + } +} + +func BenchmarkOpAdd64(b *testing.B) { + x := "ffffffff" + y := "fd37f3e2bba2c4f" + + opBenchmark(b, opAdd, x, y) +} + +func BenchmarkOpAdd128(b *testing.B) { + x := "ffffffffffffffff" + y := "f5470b43c6549b016288e9a65629687" + + opBenchmark(b, opAdd, x, y) +} + +func BenchmarkOpAdd256(b *testing.B) { + x := "0802431afcbce1fc194c9eaa417b2fb67dc75a95db0bc7ec6b1c8af11df6a1da9" + y := "a1f5aac137876480252e5dcac62c354ec0d42b76b0642b6181ed099849ea1d57" + + opBenchmark(b, opAdd, x, y) +} + +func BenchmarkOpSub64(b *testing.B) { + x := "51022b6317003a9d" + y := "a20456c62e00753a" + + opBenchmark(b, opSub, x, y) +} + +func BenchmarkOpSub128(b *testing.B) { + x := "4dde30faaacdc14d00327aac314e915d" + y := "9bbc61f5559b829a0064f558629d22ba" + + opBenchmark(b, opSub, x, y) +} + +func BenchmarkOpSub256(b *testing.B) { + x := "4bfcd8bb2ac462735b48a17580690283980aa2d679f091c64364594df113ea37" + y := "97f9b1765588c4e6b69142eb00d20507301545acf3e1238c86c8b29be227d46e" + + opBenchmark(b, opSub, x, y) +} + +func BenchmarkOpMul(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opMul, x, y) +} + +func BenchmarkOpDiv256(b *testing.B) { + x := "ff3f9014f20db29ae04af2c2d265de17" + y := "fe7fb0d1f59dfe9492ffbf73683fd1e870eec79504c60144cc7f5fc2bad1e611" + opBenchmark(b, opDiv, x, y) +} + +func BenchmarkOpDiv128(b *testing.B) { + x := "fdedc7f10142ff97" + y := "fbdfda0e2ce356173d1993d5f70a2b11" + opBenchmark(b, opDiv, x, y) +} + +func BenchmarkOpDiv64(b *testing.B) { + x := "fcb34eb3" + y := "f97180878e839129" + opBenchmark(b, opDiv, x, y) +} + +func BenchmarkOpSdiv(b *testing.B) { + x := "ff3f9014f20db29ae04af2c2d265de17" + y := "fe7fb0d1f59dfe9492ffbf73683fd1e870eec79504c60144cc7f5fc2bad1e611" + + opBenchmark(b, opSdiv, x, y) +} + +func BenchmarkOpMod(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opMod, x, y) +} + +func BenchmarkOpSmod(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opSmod, x, y) +} + +func BenchmarkOpExp(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opExp, x, y) +} + +func BenchmarkOpSignExtend(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opSignExtend, x, y) +} + +func BenchmarkOpLt(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opLt, x, y) +} + +func BenchmarkOpGt(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opGt, x, y) +} + +func BenchmarkOpSlt(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opSlt, x, y) +} + +func BenchmarkOpSgt(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opSgt, x, y) +} + +func BenchmarkOpEq(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opEq, x, y) +} +func BenchmarkOpEq2(b *testing.B) { + x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff" + y := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201fffffffe" + opBenchmark(b, opEq, x, y) +} +func BenchmarkOpAnd(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opAnd, x, y) +} + +func BenchmarkOpOr(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opOr, x, y) +} + +func BenchmarkOpXor(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opXor, x, y) +} + +func BenchmarkOpByte(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + + opBenchmark(b, opByte, x, y) +} + +func BenchmarkOpAddmod(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + z := alphabetSoup + + opBenchmark(b, opAddmod, x, y, z) +} + +func BenchmarkOpMulmod(b *testing.B) { + x := alphabetSoup + y := alphabetSoup + z := alphabetSoup + + opBenchmark(b, opMulmod, x, y, z) +} + +func BenchmarkOpSHL(b *testing.B) { + x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff" + y := "ff" + + opBenchmark(b, opSHL, x, y) +} +func BenchmarkOpSHR(b *testing.B) { + x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff" + y := "ff" + + opBenchmark(b, opSHR, x, y) +} +func BenchmarkOpSAR(b *testing.B) { + x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff" + y := "ff" + + opBenchmark(b, opSAR, x, y) +} +func BenchmarkOpIsZero(b *testing.B) { + x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff" + opBenchmark(b, opIszero, x) +} + +func TestOpMstore(t *testing.T) { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + evmInterpreter = NewEVMInterpreter(env) + ) + + env.interpreter = evmInterpreter + mem.Resize(64) + pc := uint64(0) + v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" + stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) + stack.push(new(uint256.Int)) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { + t.Fatalf("Mstore fail, got %v, expected %v", got, v) + } + stack.push(new(uint256.Int).SetUint64(0x1)) + stack.push(new(uint256.Int)) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { + t.Fatalf("Mstore failed to overwrite previous value") + } +} + +func BenchmarkOpMstore(bench *testing.B) { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + evmInterpreter = NewEVMInterpreter(env) + ) + + env.interpreter = evmInterpreter + mem.Resize(64) + pc := uint64(0) + memStart := new(uint256.Int) + value := new(uint256.Int).SetUint64(0x1337) + + bench.ResetTimer() + for i := 0; i < bench.N; i++ { + stack.push(value) + stack.push(memStart) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + } +} + +func TestOpTstore(t *testing.T) { + var ( + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + evmInterpreter = NewEVMInterpreter(env) + caller = common.Address{} + to = common.Address{1} + contractRef = contractRef{caller} + contract = NewContract(contractRef, AccountRef(to), new(uint256.Int), 0) + scopeContext = ScopeContext{mem, stack, contract} + value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") + ) + + // Add a stateObject for the caller and the contract being called + statedb.CreateAccount(caller) + statedb.CreateAccount(to) + + env.interpreter = evmInterpreter + pc := uint64(0) + // push the value to the stack + stack.push(new(uint256.Int).SetBytes(value)) + // push the location to the stack + stack.push(new(uint256.Int)) + opTstore(&pc, evmInterpreter, &scopeContext) + // there should be no elements on the stack after TSTORE + if stack.len() != 0 { + t.Fatal("stack wrong size") + } + // push the location to the stack + stack.push(new(uint256.Int)) + opTload(&pc, evmInterpreter, &scopeContext) + // there should be one element on the stack after TLOAD + if stack.len() != 1 { + t.Fatal("stack wrong size") + } + val := stack.peek() + if !bytes.Equal(val.Bytes(), value) { + t.Fatal("incorrect element read from transient storage") + } +} + +func BenchmarkOpKeccak256(bench *testing.B) { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + evmInterpreter = NewEVMInterpreter(env) + ) + env.interpreter = evmInterpreter + mem.Resize(32) + pc := uint64(0) + start := new(uint256.Int) + + bench.ResetTimer() + for i := 0; i < bench.N; i++ { + stack.push(uint256.NewInt(32)) + stack.push(start) + opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + } +} + +func TestCreate2Addresses(t *testing.T) { + type testcase struct { + origin string + salt string + code string + expected string + } + + for i, tt := range []testcase{ + { + origin: "0x0000000000000000000000000000000000000000", + salt: "0x0000000000000000000000000000000000000000", + code: "0x00", + expected: "0x4d1a2e2bb4f88f0250f26ffff098b0b30b26bf38", + }, + { + origin: "0xdeadbeef00000000000000000000000000000000", + salt: "0x0000000000000000000000000000000000000000", + code: "0x00", + expected: "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3", + }, + { + origin: "0xdeadbeef00000000000000000000000000000000", + salt: "0xfeed000000000000000000000000000000000000", + code: "0x00", + expected: "0xD04116cDd17beBE565EB2422F2497E06cC1C9833", + }, + { + origin: "0x0000000000000000000000000000000000000000", + salt: "0x0000000000000000000000000000000000000000", + code: "0xdeadbeef", + expected: "0x70f2b2914A2a4b783FaEFb75f459A580616Fcb5e", + }, + { + origin: "0x00000000000000000000000000000000deadbeef", + salt: "0xcafebabe", + code: "0xdeadbeef", + expected: "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7", + }, + { + origin: "0x00000000000000000000000000000000deadbeef", + salt: "0xcafebabe", + code: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + expected: "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C", + }, + { + origin: "0x0000000000000000000000000000000000000000", + salt: "0x0000000000000000000000000000000000000000", + code: "0x", + expected: "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0", + }, + } { + origin := common.BytesToAddress(common.FromHex(tt.origin)) + salt := common.BytesToHash(common.FromHex(tt.salt)) + code := common.FromHex(tt.code) + codeHash := crypto.Keccak256(code) + address := crypto.CreateAddress2(origin, salt, codeHash) + /* + stack := newstack() + // salt, but we don't need that for this test + stack.push(big.NewInt(int64(len(code)))) //size + stack.push(big.NewInt(0)) // memstart + stack.push(big.NewInt(0)) // value + gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0) + fmt.Printf("Example %d\n* address `0x%x`\n* salt `0x%x`\n* init_code `0x%x`\n* gas (assuming no mem expansion): `%v`\n* result: `%s`\n\n", i,origin, salt, code, gas, address.String()) + */ + expected := common.BytesToAddress(common.FromHex(tt.expected)) + if !bytes.Equal(expected.Bytes(), address.Bytes()) { + t.Errorf("test %d: expected %s, got %s", i, expected.String(), address.String()) + } + } +} + +func TestRandom(t *testing.T) { + type testcase struct { + name string + random common.Hash + } + + for _, tt := range []testcase{ + {name: "empty hash", random: common.Hash{}}, + {name: "1", random: common.Hash{0}}, + {name: "emptyCodeHash", random: types.EmptyCodeHash}, + {name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})}, + } { + var ( + env = NewEVM(BlockContext{Random: &tt.random}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + opRandom(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + if len(stack.data) != 1 { + t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) + } + actual := stack.pop() + expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.random.Bytes())) + if overflow { + t.Errorf("Testcase %v: invalid overflow", tt.name) + } + if actual.Cmp(expected) != 0 { + t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual) + } + } +} + +func TestBlobHash(t *testing.T) { + type testcase struct { + name string + idx uint64 + expect common.Hash + hashes []common.Hash + } + var ( + zero = common.Hash{0} + one = common.Hash{1} + two = common.Hash{2} + three = common.Hash{3} + ) + for _, tt := range []testcase{ + {name: "[{1}]", idx: 0, expect: one, hashes: []common.Hash{one}}, + {name: "[1,{2},3]", idx: 2, expect: three, hashes: []common.Hash{one, two, three}}, + {name: "out-of-bounds (empty)", idx: 10, expect: zero, hashes: []common.Hash{}}, + {name: "out-of-bounds", idx: 25, expect: zero, hashes: []common.Hash{one, two, three}}, + {name: "out-of-bounds (nil)", idx: 25, expect: zero, hashes: nil}, + } { + var ( + env = NewEVM(BlockContext{}, TxContext{BlobHashes: tt.hashes}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + stack.push(uint256.NewInt(tt.idx)) + opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + if len(stack.data) != 1 { + t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) + } + actual := stack.pop() + expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.expect.Bytes())) + if overflow { + t.Errorf("Testcase %v: invalid overflow", tt.name) + } + if actual.Cmp(expected) != 0 { + t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual) + } + } +} + +func TestOpMCopy(t *testing.T) { + // Test cases from https://eips.ethereum.org/EIPS/eip-5656#test-cases + for i, tc := range []struct { + dst, src, len string + pre string + want string + wantGas uint64 + }{ + { // MCOPY 0 32 32 - copy 32 bytes from offset 32 to offset 0. + dst: "0x0", src: "0x20", len: "0x20", + pre: "0000000000000000000000000000000000000000000000000000000000000000 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + want: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + wantGas: 6, + }, + + { // MCOPY 0 0 32 - copy 32 bytes from offset 0 to offset 0. + dst: "0x0", src: "0x0", len: "0x20", + pre: "0101010101010101010101010101010101010101010101010101010101010101", + want: "0101010101010101010101010101010101010101010101010101010101010101", + wantGas: 6, + }, + { // MCOPY 0 1 8 - copy 8 bytes from offset 1 to offset 0 (overlapping). + dst: "0x0", src: "0x1", len: "0x8", + pre: "000102030405060708 000000000000000000000000000000000000000000000000", + want: "010203040506070808 000000000000000000000000000000000000000000000000", + wantGas: 6, + }, + { // MCOPY 1 0 8 - copy 8 bytes from offset 0 to offset 1 (overlapping). + dst: "0x1", src: "0x0", len: "0x8", + pre: "000102030405060708 000000000000000000000000000000000000000000000000", + want: "000001020304050607 000000000000000000000000000000000000000000000000", + wantGas: 6, + }, + // Tests below are not in the EIP, but maybe should be added + { // MCOPY 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds index(overlapping). + dst: "0xFFFFFFFFFFFF", src: "0xFFFFFFFFFFFF", len: "0x0", + pre: "11", + want: "11", + wantGas: 3, + }, + { // MCOPY 0xFFFFFFFFFFFF 0 0 - copy zero bytes from start of mem to out-of-bounds. + dst: "0xFFFFFFFFFFFF", src: "0x0", len: "0x0", + pre: "11", + want: "11", + wantGas: 3, + }, + { // MCOPY 0 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds to start of mem + dst: "0x0", src: "0xFFFFFFFFFFFF", len: "0x0", + pre: "11", + want: "11", + wantGas: 3, + }, + { // MCOPY - copy 1 from space outside of uint64 space + dst: "0x0", src: "0x10000000000000000", len: "0x1", + pre: "0", + }, + { // MCOPY - copy 1 from 0 to space outside of uint64 + dst: "0x10000000000000000", src: "0x0", len: "0x1", + pre: "0", + }, + { // MCOPY - copy nothing from 0 to space outside of uint64 + dst: "0x10000000000000000", src: "0x0", len: "0x0", + pre: "", + want: "", + wantGas: 3, + }, + { // MCOPY - copy 1 from 0x20 to 0x10, with no prior allocated mem + dst: "0x10", src: "0x20", len: "0x1", + pre: "", + // 64 bytes + want: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + wantGas: 12, + }, + { // MCOPY - copy 1 from 0x19 to 0x10, with no prior allocated mem + dst: "0x10", src: "0x19", len: "0x1", + pre: "", + // 32 bytes + want: "0x0000000000000000000000000000000000000000000000000000000000000000", + wantGas: 9, + }, + } { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + data := common.FromHex(strings.ReplaceAll(tc.pre, " ", "")) + // Set pre + mem := NewMemory() + mem.Resize(uint64(len(data))) + mem.Set(0, uint64(len(data)), data) + // Push stack args + len, _ := uint256.FromHex(tc.len) + src, _ := uint256.FromHex(tc.src) + dst, _ := uint256.FromHex(tc.dst) + + stack.push(len) + stack.push(src) + stack.push(dst) + wantErr := (tc.wantGas == 0) + // Calc mem expansion + var memorySize uint64 + if memSize, overflow := memoryMcopy(stack); overflow { + if wantErr { + continue + } + t.Errorf("overflow") + } else { + var overflow bool + if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { + t.Error(ErrGasUintOverflow) + } + } + // and the dynamic cost + var haveGas uint64 + if dynamicCost, err := gasMcopy(env, nil, stack, mem, memorySize); err != nil { + t.Error(err) + } else { + haveGas = GasFastestStep + dynamicCost + } + // Expand mem + if memorySize > 0 { + mem.Resize(memorySize) + } + // Do the copy + opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) + if have := mem.store; !bytes.Equal(want, have) { + t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have) + } + wantGas := tc.wantGas + if haveGas != wantGas { + t.Errorf("case %d: gas wrong, want %d have %d\n", i, wantGas, haveGas) + } + } +} diff --git a/core/vm/interface.go b/core/vm/interface.go new file mode 100644 index 0000000..5f42643 --- /dev/null +++ b/core/vm/interface.go @@ -0,0 +1,106 @@ +// 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 vm + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/stateless" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/holiman/uint256" +) + +// StateDB is an EVM database for full state querying. +type StateDB interface { + CreateAccount(common.Address) + CreateContract(common.Address) + + SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) + AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) + GetBalance(common.Address) *uint256.Int + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) + + GetCodeHash(common.Address) common.Hash + GetCode(common.Address) []byte + SetCode(common.Address, []byte) + GetCodeSize(common.Address) int + + AddRefund(uint64) + SubRefund(uint64) + GetRefund() uint64 + + GetCommittedState(common.Address, common.Hash) common.Hash + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + GetStorageRoot(addr common.Address) common.Hash + + GetTransientState(addr common.Address, key common.Hash) common.Hash + SetTransientState(addr common.Address, key, value common.Hash) + + SelfDestruct(common.Address) + HasSelfDestructed(common.Address) bool + + Selfdestruct6780(common.Address) + + // Exist reports whether the given account exists in state. + // Notably this should also return true for self-destructed accounts. + Exist(common.Address) bool + // Empty returns whether the given account is empty. Empty + // is defined according to EIP161 (balance = nonce = code = 0). + Empty(common.Address) bool + + AddressInAccessList(addr common.Address) bool + SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) + // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressToAccessList(addr common.Address) + // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddSlotToAccessList(addr common.Address, slot common.Hash) + + // PointCache returns the point cache used in computations + PointCache() *utils.PointCache + + Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + + RevertToSnapshot(int) + Snapshot() int + + AddLog(*types.Log) + AddPreimage(common.Hash, []byte) + + Witness() *stateless.Witness +} + +// CallContext provides a basic interface for the EVM calling conventions. The EVM +// depends on this context being implemented for doing subcalls and initialising new EVM contracts. +type CallContext interface { + // Call calls another contract. + Call(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) + // CallCode takes another contracts code and execute within our own context + CallCode(env *EVM, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) + // DelegateCall is same as CallCode except sender and value is propagated from parent to child scope + DelegateCall(env *EVM, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) + // Create creates a new contract + Create(env *EVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go new file mode 100644 index 0000000..2b1ea38 --- /dev/null +++ b/core/vm/interpreter.go @@ -0,0 +1,315 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" +) + +// Config are the configuration options for the Interpreter +type Config struct { + Tracer *tracing.Hooks + NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) + EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages + ExtraEips []int // Additional EIPS that are to be enabled + EnableWitnessCollection bool // true if witness collection is enabled +} + +// ScopeContext contains the things that are per-call, such as stack and memory, +// but not transients like pc and gas +type ScopeContext struct { + Memory *Memory + Stack *Stack + Contract *Contract +} + +// MemoryData returns the underlying memory slice. Callers must not modify the contents +// of the returned data. +func (ctx *ScopeContext) MemoryData() []byte { + if ctx.Memory == nil { + return nil + } + return ctx.Memory.Data() +} + +// StackData returns the stack data. Callers must not modify the contents +// of the returned data. +func (ctx *ScopeContext) StackData() []uint256.Int { + if ctx.Stack == nil { + return nil + } + return ctx.Stack.Data() +} + +// Caller returns the current caller. +func (ctx *ScopeContext) Caller() common.Address { + return ctx.Contract.Caller() +} + +// Address returns the address where this scope of execution is taking place. +func (ctx *ScopeContext) Address() common.Address { + return ctx.Contract.Address() +} + +// CallValue returns the value supplied with this call. +func (ctx *ScopeContext) CallValue() *uint256.Int { + return ctx.Contract.Value() +} + +// CallInput returns the input/calldata with this call. Callers must not modify +// the contents of the returned data. +func (ctx *ScopeContext) CallInput() []byte { + return ctx.Contract.Input +} + +// EVMInterpreter represents an EVM interpreter +type EVMInterpreter struct { + evm *EVM + table *JumpTable + + hasher crypto.KeccakState // Keccak256 hasher instance shared across 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 +} + +// NewEVMInterpreter returns a new instance of the Interpreter. +func NewEVMInterpreter(evm *EVM) *EVMInterpreter { + // If jump table was not initialised we set the default one. + var table *JumpTable + switch { + case evm.chainRules.IsVerkle: + // TODO replace with proper instruction set when fork is specified + table = &verkleInstructionSet + case evm.chainRules.IsCancun: + table = &cancunInstructionSet + case evm.chainRules.IsShanghai: + table = &shanghaiInstructionSet + case evm.chainRules.IsMerge: + table = &mergeInstructionSet + case evm.chainRules.IsLondon: + table = &londonInstructionSet + case evm.chainRules.IsBerlin: + table = &berlinInstructionSet + case evm.chainRules.IsIstanbul: + table = &istanbulInstructionSet + case evm.chainRules.IsConstantinople: + table = &constantinopleInstructionSet + case evm.chainRules.IsByzantium: + table = &byzantiumInstructionSet + case evm.chainRules.IsEIP158: + table = &spuriousDragonInstructionSet + case evm.chainRules.IsEIP150: + table = &tangerineWhistleInstructionSet + case evm.chainRules.IsHomestead: + table = &homesteadInstructionSet + default: + table = &frontierInstructionSet + } + var extraEips []int + if len(evm.Config.ExtraEips) > 0 { + // Deep-copy jumptable to prevent modification of opcodes in other tables + table = copyJumpTable(table) + } + for _, eip := range evm.Config.ExtraEips { + if err := EnableEIP(eip, table); err != nil { + // Disable it, so caller can check if it's activated or not + log.Error("EIP activation failed", "eip", eip, "error", err) + } else { + extraEips = append(extraEips, eip) + } + } + evm.Config.ExtraEips = extraEips + return &EVMInterpreter{evm: evm, table: table} +} + +// Run loops and evaluates the contract's code with the given input data and returns +// the return byte-slice and an error if one occurred. +// +// It's important to note that any errors returned by the interpreter should be +// considered a revert-and-consume-all-gas operation except for +// ErrExecutionReverted which means revert-and-keep-gas-left. +func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { + // Increment the call depth which is restricted to 1024 + in.evm.depth++ + defer func() { in.evm.depth-- }() + + // Make sure the readOnly is only set if we aren't in readOnly yet. + // This also makes sure that the readOnly flag isn't removed for child calls. + if readOnly && !in.readOnly { + in.readOnly = true + defer func() { in.readOnly = false }() + } + + // Reset the previous call's return data. It's unimportant to preserve the old buffer + // as every returning call will return new data anyway. + in.returnData = nil + + // Don't bother with the execution if there's no code. + if len(contract.Code) == 0 { + return nil, nil + } + + var ( + op OpCode // current opcode + mem = NewMemory() // bound memory + stack = newstack() // local stack + callContext = &ScopeContext{ + Memory: mem, + Stack: stack, + Contract: contract, + } + // For optimisation reason we're using uint64 as the program counter. + // It's theoretically possible to go above 2^64. The YP defines the PC + // to be uint256. Practically much less so feasible. + pc = uint64(0) // program counter + cost uint64 + // copies used by tracer + pcCopy uint64 // needed for the deferred EVMLogger + gasCopy uint64 // for EVMLogger to log gas remaining before execution + logged bool // deferred EVMLogger should ignore already logged steps + res []byte // result of the opcode execution function + debug = in.evm.Config.Tracer != nil + ) + // Don't move this deferred function, it's placed before the OnOpcode-deferred method, + // so that it gets executed _after_: the OnOpcode needs the stacks before + // they are returned to the pools + defer func() { + returnStack(stack) + }() + contract.Input = input + + if debug { + defer func() { // this deferred method handles exit-with-error + if err == nil { + return + } + if !logged && in.evm.Config.Tracer.OnOpcode != nil { + in.evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) + } + if logged && in.evm.Config.Tracer.OnFault != nil { + in.evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err)) + } + }() + } + // The Interpreter main run loop (contextual). This loop runs until either an + // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during + // the execution of one of the operations or until the done flag is set by the + // parent context. + for { + if debug { + // Capture pre-execution values for tracing. + logged, pcCopy, gasCopy = false, pc, contract.Gas + } + + if in.evm.chainRules.IsEIP4762 && !contract.IsDeployment { + // if the PC ends up in a new "chunk" of verkleized code, charge the + // associated costs. + contractAddr := contract.Address() + contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false) + } + + // Get the operation from the jump table and validate the stack to ensure there are + // enough stack items available to perform the operation. + op = contract.GetOp(pc) + operation := in.table[op] + cost = operation.constantGas // For tracing + // Validate stack + if sLen := stack.len(); sLen < operation.minStack { + return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack} + } else if sLen > operation.maxStack { + return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} + } + if !contract.UseGas(cost, in.evm.Config.Tracer, tracing.GasChangeIgnored) { + return nil, ErrOutOfGas + } + + if operation.dynamicGas != nil { + // All ops with a dynamic memory usage also has a dynamic gas cost. + var memorySize uint64 + // calculate the new memory size and expand the memory to fit + // the operation + // Memory check needs to be done prior to evaluating the dynamic gas portion, + // to detect calculation overflows + if operation.memorySize != nil { + memSize, overflow := operation.memorySize(stack) + if overflow { + return nil, ErrGasUintOverflow + } + // memory is expanded in words of 32 bytes. Gas + // is also calculated in words. + if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { + return nil, ErrGasUintOverflow + } + } + // Consume the gas and return an error if not enough gas is available. + // cost is explicitly set so that the capture state defer method can get the proper cost + var dynamicCost uint64 + dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) + cost += dynamicCost // for tracing + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) + } + if !contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) { + return nil, ErrOutOfGas + } + + // Do tracing before memory expansion + if debug { + if in.evm.Config.Tracer.OnGasChange != nil { + in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) + } + if in.evm.Config.Tracer.OnOpcode != nil { + in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) + logged = true + } + } + if memorySize > 0 { + mem.Resize(memorySize) + } + } else if debug { + if in.evm.Config.Tracer.OnGasChange != nil { + in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) + } + if in.evm.Config.Tracer.OnOpcode != nil { + in.evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) + logged = true + } + } + + // execute the operation + res, err = operation.execute(&pc, in, callContext) + if err != nil { + break + } + pc++ + } + + if err == errStopToken { + err = nil // clear stop token error + } + + return res, err +} diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go new file mode 100644 index 0000000..ff4977d --- /dev/null +++ b/core/vm/interpreter_test.go @@ -0,0 +1,77 @@ +// 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 vm + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +var loopInterruptTests = []string{ + // infinite loop using JUMP: push(2) jumpdest dup1 jump + "60025b8056", + // infinite loop using JUMPI: push(1) push(4) jumpdest dup2 dup2 jumpi + "600160045b818157", +} + +func TestLoopInterrupt(t *testing.T) { + address := common.BytesToAddress([]byte("contract")) + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + } + + for i, tt := range loopInterruptTests { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.CreateAccount(address) + statedb.SetCode(address, common.Hex2Bytes(tt)) + statedb.Finalise(true) + + evm := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + + errChannel := make(chan error) + timeout := make(chan bool) + + go func(evm *EVM) { + _, _, err := evm.Call(AccountRef(common.Address{}), address, nil, math.MaxUint64, new(uint256.Int)) + errChannel <- err + }(evm) + + go func() { + <-time.After(time.Second) + timeout <- true + }() + + evm.Cancel() + + select { + case <-timeout: + t.Errorf("test %d timed out", i) + case err := <-errChannel: + if err != nil { + t.Errorf("test %d failure: %v", i, err) + } + } + } +} diff --git a/core/vm/ipgraph.go b/core/vm/ipgraph.go new file mode 100644 index 0000000..29c6b89 --- /dev/null +++ b/core/vm/ipgraph.go @@ -0,0 +1,376 @@ +package vm + +import ( + "bytes" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" +) + +var ( + aclAddress = common.HexToAddress("0x680E66e4c7Df9133a7AFC1ed091089B32b89C4ae") + aclSlot = "af99b37fdaacca72ee7240cb1435cc9e498aee6ef4edc19c8cc0cd787f4e6800" + ipGraphAddress = common.HexToAddress("0x000000000000000000000000000000000000001A") + addParentIpSelector = crypto.Keccak256Hash([]byte("addParentIp(address,address[])")).Bytes()[:4] + hasParentIpSelector = crypto.Keccak256Hash([]byte("hasParentIp(address,address)")).Bytes()[:4] + getParentIpsSelector = crypto.Keccak256Hash([]byte("getParentIps(address)")).Bytes()[:4] + getParentIpsCountSelector = crypto.Keccak256Hash([]byte("getParentIpsCount(address)")).Bytes()[:4] + getAncestorIpsSelector = crypto.Keccak256Hash([]byte("getAncestorIps(address)")).Bytes()[:4] + getAncestorIpsCountSelector = crypto.Keccak256Hash([]byte("getAncestorIpsCount(address)")).Bytes()[:4] + hasAncestorIpsSelector = crypto.Keccak256Hash([]byte("hasAncestorIp(address,address)")).Bytes()[:4] + setRoyaltySelector = crypto.Keccak256Hash([]byte("setRoyalty(address,address,uint256)")).Bytes()[:4] + getRoyaltySelector = crypto.Keccak256Hash([]byte("getRoyalty(address,address)")).Bytes()[:4] + getRoyaltyStackSelector = crypto.Keccak256Hash([]byte("getRoyaltyStack(address)")).Bytes()[:4] +) + +type ipGraph struct{} + +func (c *ipGraph) RequiredGas(input []byte) uint64 { + return uint64(1) +} + +func (c *ipGraph) Run(evm *EVM, input []byte) ([]byte, error) { + log.Info("ipGraph.Run", "input", input) + + if len(input) < 4 { + return nil, fmt.Errorf("input too short") + } + + selector := input[:4] + args := input[4:] + + switch { + case bytes.Equal(selector, addParentIpSelector): + return c.addParentIp(args, evm) + case bytes.Equal(selector, hasParentIpSelector): + return c.hasParentIp(args, evm) + case bytes.Equal(selector, getParentIpsSelector): + return c.getParentIps(args, evm) + case bytes.Equal(selector, getParentIpsCountSelector): + return c.getParentIpsCount(args, evm) + case bytes.Equal(selector, getAncestorIpsSelector): + return c.getAncestorIps(args, evm) + case bytes.Equal(selector, getAncestorIpsCountSelector): + return c.getAncestorIpsCount(args, evm) + case bytes.Equal(selector, hasAncestorIpsSelector): + return c.hasAncestorIp(args, evm) + case bytes.Equal(selector, setRoyaltySelector): + return c.setRoyalty(args, evm) + case bytes.Equal(selector, getRoyaltySelector): + return c.getRoyalty(args, evm) + case bytes.Equal(selector, getRoyaltyStackSelector): + return c.getRoyaltyStack(args, evm) + default: + return nil, fmt.Errorf("unknown selector") + } +} + +func (c *ipGraph) isAllowed(evm *EVM) (bool, error) { + slot := new(big.Int) + slot.SetString(aclSlot, 16) + isAllowedHash := evm.StateDB.GetState(aclAddress, common.BigToHash(slot)) + isAllowedBig := isAllowedHash.Big() + + log.Info("isAllowed", "aclAddress", aclAddress, "slot", slot, "isAllowedBig", isAllowedBig) + if isAllowedBig.Cmp(big.NewInt(1)) == 0 { + log.Info("isAllowed", "allowed", true) + return true, nil + } + log.Info("isAllowed", "allowed", false) + return false, nil +} + +func (c *ipGraph) addParentIp(input []byte, evm *EVM) ([]byte, error) { + allowed, err := c.isAllowed(evm) + + if err != nil { + return nil, err + } + + if !allowed { + return nil, fmt.Errorf("caller not allowed to add parent IP") + } + + log.Info("addParentIp", "input", input) + if len(input) < 96 { + return nil, fmt.Errorf("input too short for addParentIp") + } + ipId := common.BytesToAddress(input[0:32]) + log.Info("addParentIp", "ipId", ipId) + parentCount := new(big.Int).SetBytes(getData(input, 64, 32)) + log.Info("addParentIp", "parentCount", parentCount) + + if len(input) < int(96+parentCount.Uint64()*32) { + return nil, fmt.Errorf("input too short for parent IPs") + } + + for i := 0; i < int(parentCount.Uint64()); i++ { + parentIpId := common.BytesToAddress(input[96+i*32 : 96+(i+1)*32]) + index := uint64(i) + slot := crypto.Keccak256Hash(ipId.Bytes()).Big() + slot.Add(slot, new(big.Int).SetUint64(index)) + log.Info("addParentIp", "ipId", ipId, "parentIpId", parentIpId, "slot", slot) + evm.StateDB.SetState(ipGraphAddress, common.BigToHash(slot), common.BytesToHash(parentIpId.Bytes())) + } + + log.Info("addParentIp", "ipId", ipId, "parentCount", parentCount) + evm.StateDB.SetState(ipGraphAddress, common.BytesToHash(ipId.Bytes()), common.BigToHash(parentCount)) + + return nil, nil +} + +func (c *ipGraph) hasParentIp(input []byte, evm *EVM) ([]byte, error) { + if len(input) < 64 { + return nil, fmt.Errorf("input too short for hasParentIp") + } + ipId := common.BytesToAddress(input[0:32]) + parentIpId := common.BytesToAddress(input[32:64]) + + currentLengthHash := evm.StateDB.GetState(ipGraphAddress, common.BytesToHash(ipId.Bytes())) + currentLength := currentLengthHash.Big() + log.Info("hasParentIp", "ipId", ipId, "parentIpId", parentIpId, "currentLength", currentLength) + for i := uint64(0); i < currentLength.Uint64(); i++ { + slot := crypto.Keccak256Hash(ipId.Bytes()).Big() + slot.Add(slot, new(big.Int).SetUint64(i)) + storedParent := evm.StateDB.GetState(ipGraphAddress, common.BigToHash(slot)) + log.Info("hasParentIp", "storedParent", storedParent, "parentIpId", parentIpId) + if common.BytesToAddress(storedParent.Bytes()) == parentIpId { + log.Info("hasParentIp", "found", true) + return common.LeftPadBytes([]byte{1}, 32), nil + } + } + log.Info("hasParentIp", "found", false) + return common.LeftPadBytes([]byte{0}, 32), nil +} + +func (c *ipGraph) getParentIps(input []byte, evm *EVM) ([]byte, error) { + log.Info("getParentIps", "input", input) + if len(input) < 32 { + return nil, fmt.Errorf("input too short for getParentIps") + } + ipId := common.BytesToAddress(input[0:32]) + + currentLengthHash := evm.StateDB.GetState(ipGraphAddress, common.BytesToHash(ipId.Bytes())) + currentLength := currentLengthHash.Big() + + output := make([]byte, 64+currentLength.Uint64()*32) + copy(output[0:32], common.BigToHash(new(big.Int).SetUint64(32)).Bytes()) + copy(output[32:64], common.BigToHash(currentLength).Bytes()) + + for i := uint64(0); i < currentLength.Uint64(); i++ { + slot := crypto.Keccak256Hash(ipId.Bytes()).Big() + slot.Add(slot, new(big.Int).SetUint64(i)) + storedParent := evm.StateDB.GetState(ipGraphAddress, common.BigToHash(slot)) + copy(output[64+i*32:], storedParent.Bytes()) + } + log.Info("getParentIps", "output", output) + return output, nil +} + +func (c *ipGraph) getParentIpsCount(input []byte, evm *EVM) ([]byte, error) { + log.Info("getParentIpsCount", "input", input) + if len(input) < 32 { + return nil, fmt.Errorf("input too short for getParentIpsCount") + } + ipId := common.BytesToAddress(input[0:32]) + + currentLengthHash := evm.StateDB.GetState(ipGraphAddress, common.BytesToHash(ipId.Bytes())) + currentLength := currentLengthHash.Big() + + log.Info("getParentIpsCount", "ipId", ipId, "currentLength", currentLength) + return common.BigToHash(currentLength).Bytes(), nil +} + +func (c *ipGraph) getAncestorIps(input []byte, evm *EVM) ([]byte, error) { + log.Info("getAncestorIps", "input", input) + if len(input) < 32 { + return nil, fmt.Errorf("input too short for getAncestorIps") + } + ipId := common.BytesToAddress(input[0:32]) + ancestors := c.findAncestors(ipId, evm) + + output := make([]byte, 64+len(ancestors)*32) + copy(output[0:32], common.BigToHash(new(big.Int).SetUint64(32)).Bytes()) + copy(output[32:64], common.BigToHash(new(big.Int).SetUint64(uint64(len(ancestors)))).Bytes()) + + i := 0 + for ancestor := range ancestors { + copy(output[64+i*32:], common.LeftPadBytes(ancestor.Bytes(), 32)) + i++ + } + + log.Info("getAncestorIps", "output", output) + return output, nil +} + +func (c *ipGraph) getAncestorIpsCount(input []byte, evm *EVM) ([]byte, error) { + log.Info("getAncestorIpsCount", "input", input) + if len(input) < 32 { + return nil, fmt.Errorf("input too short for getAncestorIpsCount") + } + ipId := common.BytesToAddress(input[0:32]) + ancestors := c.findAncestors(ipId, evm) + + count := new(big.Int).SetUint64(uint64(len(ancestors))) + log.Info("getAncestorIpsCount", "ipId", ipId, "count", count) + return common.BigToHash(count).Bytes(), nil +} + +func (c *ipGraph) hasAncestorIp(input []byte, evm *EVM) ([]byte, error) { + if len(input) < 64 { + return nil, fmt.Errorf("input too short for hasAncestorIp") + } + ipId := common.BytesToAddress(input[0:32]) + parentIpId := common.BytesToAddress(input[32:64]) + ancestors := c.findAncestors(ipId, evm) + + if _, found := ancestors[parentIpId]; found { + log.Info("hasAncestorIp", "found", true) + return common.LeftPadBytes([]byte{1}, 32), nil + } + log.Info("hasAncestorIp", "found", false) + return common.LeftPadBytes([]byte{0}, 32), nil +} + +func (c *ipGraph) findAncestors(ipId common.Address, evm *EVM) map[common.Address]struct{} { + ancestors := make(map[common.Address]struct{}) + var stack []common.Address + stack = append(stack, ipId) + for len(stack) > 0 { + node := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + currentLengthHash := evm.StateDB.GetState(ipGraphAddress, common.BytesToHash(node.Bytes())) + currentLength := currentLengthHash.Big() + + for i := uint64(0); i < currentLength.Uint64(); i++ { + slot := crypto.Keccak256Hash(node.Bytes()).Big() + slot.Add(slot, new(big.Int).SetUint64(i)) + storedParent := evm.StateDB.GetState(ipGraphAddress, common.BigToHash(slot)) + parentIpId := common.BytesToAddress(storedParent.Bytes()) + + if _, found := ancestors[parentIpId]; !found { + ancestors[parentIpId] = struct{}{} + stack = append(stack, parentIpId) + } + } + } + return ancestors +} + +func (c *ipGraph) setRoyalty(input []byte, evm *EVM) ([]byte, error) { + allowed, err := c.isAllowed(evm) + + if err != nil { + return nil, err + } + + if !allowed { + return nil, fmt.Errorf("caller not allowed to set Royalty") + } + + log.Info("setRoyalty", "input", input) + if len(input) < 96 { + return nil, fmt.Errorf("input too short for setRoyalty") + } + ipId := common.BytesToAddress(input[0:32]) + parentIpId := common.BytesToAddress(input[32:64]) + royalty := new(big.Int).SetBytes(getData(input, 64, 32)) + slot := crypto.Keccak256Hash(ipId.Bytes(), parentIpId.Bytes()).Big() + log.Info("setRoyalty", "ipId", ipId, "parentIpId", parentIpId, "royalty", royalty, "slot", slot) + evm.StateDB.SetState(ipGraphAddress, common.BigToHash(slot), common.BigToHash(royalty)) + + return nil, nil +} + +func (c *ipGraph) getRoyalty(input []byte, evm *EVM) ([]byte, error) { + log.Info("getRoyalty", "input", input) + if len(input) < 64 { + return nil, fmt.Errorf("input too short for getRoyalty") + } + ipId := common.BytesToAddress(input[0:32]) + ancestorIpId := common.BytesToAddress(input[32:64]) + ancestors := c.findAncestors(ipId, evm) + totalRoyalty := big.NewInt(0) + for ancestor := range ancestors { + if ancestor == ancestorIpId { + // Traverse the graph to accumulate royalties + totalRoyalty.Add(totalRoyalty, c.getRoyaltyForAncestor(ipId, ancestorIpId, evm)) + } + } + + log.Info("getRoyalty", "ipId", ipId, "ancestorIpId", ancestorIpId, "totalRoyalty", totalRoyalty) + return common.BigToHash(totalRoyalty).Bytes(), nil +} + +func (c *ipGraph) getRoyaltyForAncestor(ipId, ancestorIpId common.Address, evm *EVM) *big.Int { + ancestors := make(map[common.Address]struct{}) + totalRoyalty := big.NewInt(0) + var stack []common.Address + stack = append(stack, ipId) + for len(stack) > 0 { + node := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + currentLengthHash := evm.StateDB.GetState(ipGraphAddress, common.BytesToHash(node.Bytes())) + currentLength := currentLengthHash.Big() + + for i := uint64(0); i < currentLength.Uint64(); i++ { + slot := crypto.Keccak256Hash(node.Bytes()).Big() + slot.Add(slot, new(big.Int).SetUint64(i)) + storedParent := evm.StateDB.GetState(ipGraphAddress, common.BigToHash(slot)) + parentIpId := common.BytesToAddress(storedParent.Bytes()) + + if _, found := ancestors[parentIpId]; !found { + ancestors[parentIpId] = struct{}{} + stack = append(stack, parentIpId) + } + + if parentIpId == ancestorIpId { + royaltySlot := crypto.Keccak256Hash(node.Bytes(), ancestorIpId.Bytes()).Big() + royalty := evm.StateDB.GetState(ipGraphAddress, common.BigToHash(royaltySlot)).Big() + totalRoyalty.Add(totalRoyalty, royalty) + } + } + } + return totalRoyalty +} + +func (c *ipGraph) getRoyaltyStack(input []byte, evm *EVM) ([]byte, error) { + log.Info("getRoyaltyStack", "input", input) + if len(input) < 32 { + return nil, fmt.Errorf("input too short for getRoyaltyStack") + } + ipId := common.BytesToAddress(input[0:32]) + ancestors := make(map[common.Address]struct{}) + totalRoyalty := big.NewInt(0) + var stack []common.Address + stack = append(stack, ipId) + for len(stack) > 0 { + node := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + currentLengthHash := evm.StateDB.GetState(ipGraphAddress, common.BytesToHash(node.Bytes())) + currentLength := currentLengthHash.Big() + + for i := uint64(0); i < currentLength.Uint64(); i++ { + slot := crypto.Keccak256Hash(node.Bytes()).Big() + slot.Add(slot, new(big.Int).SetUint64(i)) + storedParent := evm.StateDB.GetState(ipGraphAddress, common.BigToHash(slot)) + parentIpId := common.BytesToAddress(storedParent.Bytes()) + + if _, found := ancestors[parentIpId]; !found { + ancestors[parentIpId] = struct{}{} + stack = append(stack, parentIpId) + } + + royaltySlot := crypto.Keccak256Hash(node.Bytes(), parentIpId.Bytes()).Big() + royalty := evm.StateDB.GetState(ipGraphAddress, common.BigToHash(royaltySlot)).Big() + totalRoyalty.Add(totalRoyalty, royalty) + } + } + return common.BigToHash(totalRoyalty).Bytes(), nil +} diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go new file mode 100644 index 0000000..5624f47 --- /dev/null +++ b/core/vm/jump_table.go @@ -0,0 +1,1083 @@ +// 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 vm + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/params" +) + +type ( + executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) + gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 + // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 + memorySizeFunc func(*Stack) (size uint64, overflow bool) +) + +type operation struct { + // execute is the operation function + execute executionFunc + constantGas uint64 + dynamicGas gasFunc + // minStack tells how many stack items are required + minStack int + // maxStack specifies the max length the stack can have for this operation + // to not overflow the stack. + maxStack int + + // memorySize returns the memory size required for the operation + memorySize memorySizeFunc +} + +var ( + frontierInstructionSet = newFrontierInstructionSet() + homesteadInstructionSet = newHomesteadInstructionSet() + tangerineWhistleInstructionSet = newTangerineWhistleInstructionSet() + spuriousDragonInstructionSet = newSpuriousDragonInstructionSet() + byzantiumInstructionSet = newByzantiumInstructionSet() + constantinopleInstructionSet = newConstantinopleInstructionSet() + istanbulInstructionSet = newIstanbulInstructionSet() + berlinInstructionSet = newBerlinInstructionSet() + londonInstructionSet = newLondonInstructionSet() + mergeInstructionSet = newMergeInstructionSet() + shanghaiInstructionSet = newShanghaiInstructionSet() + cancunInstructionSet = newCancunInstructionSet() + verkleInstructionSet = newVerkleInstructionSet() +) + +// JumpTable contains the EVM opcodes supported at a given fork. +type JumpTable [256]*operation + +func validate(jt JumpTable) JumpTable { + for i, op := range jt { + if op == nil { + panic(fmt.Sprintf("op %#x is not set", i)) + } + // The interpreter has an assumption that if the memorySize function is + // set, then the dynamicGas function is also set. This is a somewhat + // arbitrary assumption, and can be removed if we need to -- but it + // allows us to avoid a condition check. As long as we have that assumption + // in there, this little sanity check prevents us from merging in a + // change which violates it. + if op.memorySize != nil && op.dynamicGas == nil { + panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String())) + } + } + return jt +} + +func newVerkleInstructionSet() JumpTable { + instructionSet := newCancunInstructionSet() + enable4762(&instructionSet) + return validate(instructionSet) +} + +func newCancunInstructionSet() JumpTable { + instructionSet := newShanghaiInstructionSet() + enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) + enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode) + enable1153(&instructionSet) // EIP-1153 "Transient Storage" + enable5656(&instructionSet) // EIP-5656 (MCOPY opcode) + enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction + return validate(instructionSet) +} + +func newShanghaiInstructionSet() JumpTable { + instructionSet := newMergeInstructionSet() + enable3855(&instructionSet) // PUSH0 instruction + enable3860(&instructionSet) // Limit and meter initcode + + return validate(instructionSet) +} + +func newMergeInstructionSet() JumpTable { + instructionSet := newLondonInstructionSet() + instructionSet[PREVRANDAO] = &operation{ + execute: opRandom, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } + return validate(instructionSet) +} + +// newLondonInstructionSet returns the frontier, homestead, byzantium, +// constantinople, istanbul, petersburg, berlin and london instructions. +func newLondonInstructionSet() JumpTable { + instructionSet := newBerlinInstructionSet() + enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529 + enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 + return validate(instructionSet) +} + +// newBerlinInstructionSet returns the frontier, homestead, byzantium, +// constantinople, istanbul, petersburg and berlin instructions. +func newBerlinInstructionSet() JumpTable { + instructionSet := newIstanbulInstructionSet() + enable2929(&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929 + return validate(instructionSet) +} + +// newIstanbulInstructionSet returns the frontier, homestead, byzantium, +// constantinople, istanbul and petersburg instructions. +func newIstanbulInstructionSet() JumpTable { + instructionSet := newConstantinopleInstructionSet() + + enable1344(&instructionSet) // ChainID opcode - https://eips.ethereum.org/EIPS/eip-1344 + enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884 + enable2200(&instructionSet) // Net metered SSTORE - https://eips.ethereum.org/EIPS/eip-2200 + + return validate(instructionSet) +} + +// newConstantinopleInstructionSet returns the frontier, homestead, +// byzantium and constantinople instructions. +func newConstantinopleInstructionSet() JumpTable { + instructionSet := newByzantiumInstructionSet() + instructionSet[SHL] = &operation{ + execute: opSHL, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + } + instructionSet[SHR] = &operation{ + execute: opSHR, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + } + instructionSet[SAR] = &operation{ + execute: opSAR, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + } + instructionSet[EXTCODEHASH] = &operation{ + execute: opExtCodeHash, + constantGas: params.ExtcodeHashGasConstantinople, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + instructionSet[CREATE2] = &operation{ + execute: opCreate2, + constantGas: params.Create2Gas, + dynamicGas: gasCreate2, + minStack: minStack(4, 1), + maxStack: maxStack(4, 1), + memorySize: memoryCreate2, + } + return validate(instructionSet) +} + +// newByzantiumInstructionSet returns the frontier, homestead and +// byzantium instructions. +func newByzantiumInstructionSet() JumpTable { + instructionSet := newSpuriousDragonInstructionSet() + instructionSet[STATICCALL] = &operation{ + execute: opStaticCall, + constantGas: params.CallGasEIP150, + dynamicGas: gasStaticCall, + minStack: minStack(6, 1), + maxStack: maxStack(6, 1), + memorySize: memoryStaticCall, + } + instructionSet[RETURNDATASIZE] = &operation{ + execute: opReturnDataSize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } + instructionSet[RETURNDATACOPY] = &operation{ + execute: opReturnDataCopy, + constantGas: GasFastestStep, + dynamicGas: gasReturnDataCopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryReturnDataCopy, + } + instructionSet[REVERT] = &operation{ + execute: opRevert, + dynamicGas: gasRevert, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryRevert, + } + return validate(instructionSet) +} + +// EIP 158 a.k.a Spurious Dragon +func newSpuriousDragonInstructionSet() JumpTable { + instructionSet := newTangerineWhistleInstructionSet() + instructionSet[EXP].dynamicGas = gasExpEIP158 + return validate(instructionSet) +} + +// EIP 150 a.k.a Tangerine Whistle +func newTangerineWhistleInstructionSet() JumpTable { + instructionSet := newHomesteadInstructionSet() + instructionSet[BALANCE].constantGas = params.BalanceGasEIP150 + instructionSet[EXTCODESIZE].constantGas = params.ExtcodeSizeGasEIP150 + instructionSet[SLOAD].constantGas = params.SloadGasEIP150 + instructionSet[EXTCODECOPY].constantGas = params.ExtcodeCopyBaseEIP150 + instructionSet[CALL].constantGas = params.CallGasEIP150 + instructionSet[CALLCODE].constantGas = params.CallGasEIP150 + instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150 + return validate(instructionSet) +} + +// newHomesteadInstructionSet returns the frontier and homestead +// instructions that can be executed during the homestead phase. +func newHomesteadInstructionSet() JumpTable { + instructionSet := newFrontierInstructionSet() + instructionSet[DELEGATECALL] = &operation{ + execute: opDelegateCall, + dynamicGas: gasDelegateCall, + constantGas: params.CallGasFrontier, + minStack: minStack(6, 1), + maxStack: maxStack(6, 1), + memorySize: memoryDelegateCall, + } + return validate(instructionSet) +} + +// newFrontierInstructionSet returns the frontier instructions +// that can be executed during the frontier phase. +func newFrontierInstructionSet() JumpTable { + tbl := JumpTable{ + STOP: { + execute: opStop, + constantGas: 0, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + }, + ADD: { + execute: opAdd, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + MUL: { + execute: opMul, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SUB: { + execute: opSub, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + DIV: { + execute: opDiv, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SDIV: { + execute: opSdiv, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + MOD: { + execute: opMod, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SMOD: { + execute: opSmod, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + ADDMOD: { + execute: opAddmod, + constantGas: GasMidStep, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + }, + MULMOD: { + execute: opMulmod, + constantGas: GasMidStep, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + }, + EXP: { + execute: opExp, + dynamicGas: gasExpFrontier, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SIGNEXTEND: { + execute: opSignExtend, + constantGas: GasFastStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + LT: { + execute: opLt, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + GT: { + execute: opGt, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SLT: { + execute: opSlt, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + SGT: { + execute: opSgt, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + EQ: { + execute: opEq, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + ISZERO: { + execute: opIszero, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + AND: { + execute: opAnd, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + XOR: { + execute: opXor, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + OR: { + execute: opOr, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + NOT: { + execute: opNot, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + BYTE: { + execute: opByte, + constantGas: GasFastestStep, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, + KECCAK256: { + execute: opKeccak256, + constantGas: params.Keccak256Gas, + dynamicGas: gasKeccak256, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + memorySize: memoryKeccak256, + }, + ADDRESS: { + execute: opAddress, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + BALANCE: { + execute: opBalance, + constantGas: params.BalanceGasFrontier, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + ORIGIN: { + execute: opOrigin, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CALLER: { + execute: opCaller, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CALLVALUE: { + execute: opCallValue, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CALLDATALOAD: { + execute: opCallDataLoad, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + CALLDATASIZE: { + execute: opCallDataSize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CALLDATACOPY: { + execute: opCallDataCopy, + constantGas: GasFastestStep, + dynamicGas: gasCallDataCopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryCallDataCopy, + }, + CODESIZE: { + execute: opCodeSize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + CODECOPY: { + execute: opCodeCopy, + constantGas: GasFastestStep, + dynamicGas: gasCodeCopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryCodeCopy, + }, + GASPRICE: { + execute: opGasprice, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + EXTCODESIZE: { + execute: opExtCodeSize, + constantGas: params.ExtcodeSizeGasFrontier, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + EXTCODECOPY: { + execute: opExtCodeCopy, + constantGas: params.ExtcodeCopyBaseFrontier, + dynamicGas: gasExtCodeCopy, + minStack: minStack(4, 0), + maxStack: maxStack(4, 0), + memorySize: memoryExtCodeCopy, + }, + BLOCKHASH: { + execute: opBlockhash, + constantGas: GasExtStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + COINBASE: { + execute: opCoinbase, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + TIMESTAMP: { + execute: opTimestamp, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + NUMBER: { + execute: opNumber, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + DIFFICULTY: { + execute: opDifficulty, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + GASLIMIT: { + execute: opGasLimit, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + POP: { + execute: opPop, + constantGas: GasQuickStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + }, + MLOAD: { + execute: opMload, + constantGas: GasFastestStep, + dynamicGas: gasMLoad, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + memorySize: memoryMLoad, + }, + MSTORE: { + execute: opMstore, + constantGas: GasFastestStep, + dynamicGas: gasMStore, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryMStore, + }, + MSTORE8: { + execute: opMstore8, + constantGas: GasFastestStep, + dynamicGas: gasMStore8, + memorySize: memoryMStore8, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + }, + SLOAD: { + execute: opSload, + constantGas: params.SloadGasFrontier, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + }, + SSTORE: { + execute: opSstore, + dynamicGas: gasSStore, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + }, + JUMP: { + execute: opJump, + constantGas: GasMidStep, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + }, + JUMPI: { + execute: opJumpi, + constantGas: GasSlowStep, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + }, + PC: { + execute: opPc, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + MSIZE: { + execute: opMsize, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + GAS: { + execute: opGas, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + JUMPDEST: { + execute: opJumpdest, + constantGas: params.JumpdestGas, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + }, + PUSH1: { + execute: opPush1, + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH2: { + execute: makePush(2, 2), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH3: { + execute: makePush(3, 3), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH4: { + execute: makePush(4, 4), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH5: { + execute: makePush(5, 5), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH6: { + execute: makePush(6, 6), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH7: { + execute: makePush(7, 7), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH8: { + execute: makePush(8, 8), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH9: { + execute: makePush(9, 9), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH10: { + execute: makePush(10, 10), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH11: { + execute: makePush(11, 11), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH12: { + execute: makePush(12, 12), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH13: { + execute: makePush(13, 13), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH14: { + execute: makePush(14, 14), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH15: { + execute: makePush(15, 15), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH16: { + execute: makePush(16, 16), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH17: { + execute: makePush(17, 17), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH18: { + execute: makePush(18, 18), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH19: { + execute: makePush(19, 19), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH20: { + execute: makePush(20, 20), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH21: { + execute: makePush(21, 21), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH22: { + execute: makePush(22, 22), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH23: { + execute: makePush(23, 23), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH24: { + execute: makePush(24, 24), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH25: { + execute: makePush(25, 25), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH26: { + execute: makePush(26, 26), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH27: { + execute: makePush(27, 27), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH28: { + execute: makePush(28, 28), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH29: { + execute: makePush(29, 29), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH30: { + execute: makePush(30, 30), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH31: { + execute: makePush(31, 31), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + PUSH32: { + execute: makePush(32, 32), + constantGas: GasFastestStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + }, + DUP1: { + execute: makeDup(1), + constantGas: GasFastestStep, + minStack: minDupStack(1), + maxStack: maxDupStack(1), + }, + DUP2: { + execute: makeDup(2), + constantGas: GasFastestStep, + minStack: minDupStack(2), + maxStack: maxDupStack(2), + }, + DUP3: { + execute: makeDup(3), + constantGas: GasFastestStep, + minStack: minDupStack(3), + maxStack: maxDupStack(3), + }, + DUP4: { + execute: makeDup(4), + constantGas: GasFastestStep, + minStack: minDupStack(4), + maxStack: maxDupStack(4), + }, + DUP5: { + execute: makeDup(5), + constantGas: GasFastestStep, + minStack: minDupStack(5), + maxStack: maxDupStack(5), + }, + DUP6: { + execute: makeDup(6), + constantGas: GasFastestStep, + minStack: minDupStack(6), + maxStack: maxDupStack(6), + }, + DUP7: { + execute: makeDup(7), + constantGas: GasFastestStep, + minStack: minDupStack(7), + maxStack: maxDupStack(7), + }, + DUP8: { + execute: makeDup(8), + constantGas: GasFastestStep, + minStack: minDupStack(8), + maxStack: maxDupStack(8), + }, + DUP9: { + execute: makeDup(9), + constantGas: GasFastestStep, + minStack: minDupStack(9), + maxStack: maxDupStack(9), + }, + DUP10: { + execute: makeDup(10), + constantGas: GasFastestStep, + minStack: minDupStack(10), + maxStack: maxDupStack(10), + }, + DUP11: { + execute: makeDup(11), + constantGas: GasFastestStep, + minStack: minDupStack(11), + maxStack: maxDupStack(11), + }, + DUP12: { + execute: makeDup(12), + constantGas: GasFastestStep, + minStack: minDupStack(12), + maxStack: maxDupStack(12), + }, + DUP13: { + execute: makeDup(13), + constantGas: GasFastestStep, + minStack: minDupStack(13), + maxStack: maxDupStack(13), + }, + DUP14: { + execute: makeDup(14), + constantGas: GasFastestStep, + minStack: minDupStack(14), + maxStack: maxDupStack(14), + }, + DUP15: { + execute: makeDup(15), + constantGas: GasFastestStep, + minStack: minDupStack(15), + maxStack: maxDupStack(15), + }, + DUP16: { + execute: makeDup(16), + constantGas: GasFastestStep, + minStack: minDupStack(16), + maxStack: maxDupStack(16), + }, + SWAP1: { + execute: makeSwap(1), + constantGas: GasFastestStep, + minStack: minSwapStack(2), + maxStack: maxSwapStack(2), + }, + SWAP2: { + execute: makeSwap(2), + constantGas: GasFastestStep, + minStack: minSwapStack(3), + maxStack: maxSwapStack(3), + }, + SWAP3: { + execute: makeSwap(3), + constantGas: GasFastestStep, + minStack: minSwapStack(4), + maxStack: maxSwapStack(4), + }, + SWAP4: { + execute: makeSwap(4), + constantGas: GasFastestStep, + minStack: minSwapStack(5), + maxStack: maxSwapStack(5), + }, + SWAP5: { + execute: makeSwap(5), + constantGas: GasFastestStep, + minStack: minSwapStack(6), + maxStack: maxSwapStack(6), + }, + SWAP6: { + execute: makeSwap(6), + constantGas: GasFastestStep, + minStack: minSwapStack(7), + maxStack: maxSwapStack(7), + }, + SWAP7: { + execute: makeSwap(7), + constantGas: GasFastestStep, + minStack: minSwapStack(8), + maxStack: maxSwapStack(8), + }, + SWAP8: { + execute: makeSwap(8), + constantGas: GasFastestStep, + minStack: minSwapStack(9), + maxStack: maxSwapStack(9), + }, + SWAP9: { + execute: makeSwap(9), + constantGas: GasFastestStep, + minStack: minSwapStack(10), + maxStack: maxSwapStack(10), + }, + SWAP10: { + execute: makeSwap(10), + constantGas: GasFastestStep, + minStack: minSwapStack(11), + maxStack: maxSwapStack(11), + }, + SWAP11: { + execute: makeSwap(11), + constantGas: GasFastestStep, + minStack: minSwapStack(12), + maxStack: maxSwapStack(12), + }, + SWAP12: { + execute: makeSwap(12), + constantGas: GasFastestStep, + minStack: minSwapStack(13), + maxStack: maxSwapStack(13), + }, + SWAP13: { + execute: makeSwap(13), + constantGas: GasFastestStep, + minStack: minSwapStack(14), + maxStack: maxSwapStack(14), + }, + SWAP14: { + execute: makeSwap(14), + constantGas: GasFastestStep, + minStack: minSwapStack(15), + maxStack: maxSwapStack(15), + }, + SWAP15: { + execute: makeSwap(15), + constantGas: GasFastestStep, + minStack: minSwapStack(16), + maxStack: maxSwapStack(16), + }, + SWAP16: { + execute: makeSwap(16), + constantGas: GasFastestStep, + minStack: minSwapStack(17), + maxStack: maxSwapStack(17), + }, + LOG0: { + execute: makeLog(0), + dynamicGas: makeGasLog(0), + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryLog, + }, + LOG1: { + execute: makeLog(1), + dynamicGas: makeGasLog(1), + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryLog, + }, + LOG2: { + execute: makeLog(2), + dynamicGas: makeGasLog(2), + minStack: minStack(4, 0), + maxStack: maxStack(4, 0), + memorySize: memoryLog, + }, + LOG3: { + execute: makeLog(3), + dynamicGas: makeGasLog(3), + minStack: minStack(5, 0), + maxStack: maxStack(5, 0), + memorySize: memoryLog, + }, + LOG4: { + execute: makeLog(4), + dynamicGas: makeGasLog(4), + minStack: minStack(6, 0), + maxStack: maxStack(6, 0), + memorySize: memoryLog, + }, + CREATE: { + execute: opCreate, + constantGas: params.CreateGas, + dynamicGas: gasCreate, + minStack: minStack(3, 1), + maxStack: maxStack(3, 1), + memorySize: memoryCreate, + }, + CALL: { + execute: opCall, + constantGas: params.CallGasFrontier, + dynamicGas: gasCall, + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), + memorySize: memoryCall, + }, + CALLCODE: { + execute: opCallCode, + constantGas: params.CallGasFrontier, + dynamicGas: gasCallCode, + minStack: minStack(7, 1), + maxStack: maxStack(7, 1), + memorySize: memoryCall, + }, + RETURN: { + execute: opReturn, + dynamicGas: gasReturn, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryReturn, + }, + SELFDESTRUCT: { + execute: opSelfdestruct, + dynamicGas: gasSelfdestruct, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + }, + } + + // Fill all unassigned slots with opUndefined. + for i, entry := range tbl { + if entry == nil { + tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)} + } + } + + return validate(tbl) +} + +func copyJumpTable(source *JumpTable) *JumpTable { + dest := *source + for i, op := range source { + if op != nil { + opCopy := *op + dest[i] = &opCopy + } + } + return &dest +} diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go new file mode 100644 index 0000000..b74109d --- /dev/null +++ b/core/vm/jump_table_export.go @@ -0,0 +1,76 @@ +// 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 vm + +import ( + "errors" + + "github.com/ethereum/go-ethereum/params" +) + +// LookupInstructionSet returns the instruction set for the fork configured by +// the rules. +func LookupInstructionSet(rules params.Rules) (JumpTable, error) { + switch { + case rules.IsVerkle: + return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") + case rules.IsPrague: + return newCancunInstructionSet(), errors.New("prague-fork not defined yet") + case rules.IsCancun: + return newCancunInstructionSet(), nil + case rules.IsShanghai: + return newShanghaiInstructionSet(), nil + case rules.IsMerge: + return newMergeInstructionSet(), nil + case rules.IsLondon: + return newLondonInstructionSet(), nil + case rules.IsBerlin: + return newBerlinInstructionSet(), nil + case rules.IsIstanbul: + return newIstanbulInstructionSet(), nil + case rules.IsConstantinople: + return newConstantinopleInstructionSet(), nil + case rules.IsByzantium: + return newByzantiumInstructionSet(), nil + case rules.IsEIP158: + return newSpuriousDragonInstructionSet(), nil + case rules.IsEIP150: + return newTangerineWhistleInstructionSet(), nil + case rules.IsHomestead: + return newHomesteadInstructionSet(), nil + } + return newFrontierInstructionSet(), nil +} + +// Stack returns the minimum and maximum stack requirements. +func (op *operation) Stack() (int, int) { + return op.minStack, op.maxStack +} + +// HasCost returns true if the opcode has a cost. Opcodes which do _not_ have +// a cost assigned are one of two things: +// - undefined, a.k.a invalid opcodes, +// - the STOP opcode. +// This method can thus be used to check if an opcode is "Invalid (or STOP)". +func (op *operation) HasCost() bool { + // Ideally, we'd check this: + // return op.execute == opUndefined + // However, go-lang does now allow that. So we'll just check some other + // 'indicators' that this is an invalid op. Alas, STOP is impossible to + // filter out + return op.dynamicGas != nil || op.constantGas != 0 +} diff --git a/core/vm/jump_table_test.go b/core/vm/jump_table_test.go new file mode 100644 index 0000000..0255803 --- /dev/null +++ b/core/vm/jump_table_test.go @@ -0,0 +1,35 @@ +// 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 vm + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestJumpTableCopy tests that deep copy is necessary to prevent modify shared jump table +func TestJumpTableCopy(t *testing.T) { + tbl := newMergeInstructionSet() + require.Equal(t, uint64(0), tbl[SLOAD].constantGas) + + // a deep copy won't modify the shared jump table + deepCopy := copyJumpTable(&tbl) + deepCopy[SLOAD].constantGas = 100 + require.Equal(t, uint64(100), deepCopy[SLOAD].constantGas) + require.Equal(t, uint64(0), tbl[SLOAD].constantGas) +} diff --git a/core/vm/memory.go b/core/vm/memory.go new file mode 100644 index 0000000..e0202fd --- /dev/null +++ b/core/vm/memory.go @@ -0,0 +1,116 @@ +// 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 vm + +import ( + "github.com/holiman/uint256" +) + +// Memory implements a simple memory model for the ethereum virtual machine. +type Memory struct { + store []byte + lastGasCost uint64 +} + +// NewMemory returns a new memory model. +func NewMemory() *Memory { + return &Memory{} +} + +// Set sets offset + size to value +func (m *Memory) Set(offset, size uint64, value []byte) { + // It's possible the offset is greater than 0 and size equals 0. This is because + // the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP) + if size > 0 { + // length of store may never be less than offset + size. + // The store should be resized PRIOR to setting the memory + if offset+size > uint64(len(m.store)) { + panic("invalid memory: store empty") + } + copy(m.store[offset:offset+size], value) + } +} + +// Set32 sets the 32 bytes starting at offset to the value of val, left-padded with zeroes to +// 32 bytes. +func (m *Memory) Set32(offset uint64, val *uint256.Int) { + // length of store may never be less than offset + size. + // The store should be resized PRIOR to setting the memory + if offset+32 > uint64(len(m.store)) { + panic("invalid memory: store empty") + } + // Fill in relevant bits + b32 := val.Bytes32() + copy(m.store[offset:], b32[:]) +} + +// Resize resizes the memory to size +func (m *Memory) Resize(size uint64) { + if uint64(m.Len()) < size { + m.store = append(m.store, make([]byte, size-uint64(m.Len()))...) + } +} + +// GetCopy returns offset + size as a new slice +func (m *Memory) GetCopy(offset, size int64) (cpy []byte) { + if size == 0 { + return nil + } + + if len(m.store) > int(offset) { + cpy = make([]byte, size) + copy(cpy, m.store[offset:offset+size]) + + return + } + + return +} + +// GetPtr returns the offset + size +func (m *Memory) GetPtr(offset, size int64) []byte { + if size == 0 { + return nil + } + + if len(m.store) > int(offset) { + return m.store[offset : offset+size] + } + + return nil +} + +// Len returns the length of the backing slice +func (m *Memory) Len() int { + return len(m.store) +} + +// Data returns the backing slice +func (m *Memory) Data() []byte { + return m.store +} + +// Copy copies data from the src position slice into the dst position. +// The source and destination may overlap. +// OBS: This operation assumes that any necessary memory expansion has already been performed, +// and this method may panic otherwise. +func (m *Memory) Copy(dst, src, len uint64) { + if len == 0 { + return + } + copy(m.store[dst:], m.store[src:src+len]) +} diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go new file mode 100644 index 0000000..61a910a --- /dev/null +++ b/core/vm/memory_table.go @@ -0,0 +1,121 @@ +// 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 vm + +func memoryKeccak256(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} + +func memoryCallDataCopy(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(2)) +} + +func memoryReturnDataCopy(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(2)) +} + +func memoryCodeCopy(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(2)) +} + +func memoryExtCodeCopy(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(3)) +} + +func memoryMLoad(stack *Stack) (uint64, bool) { + return calcMemSize64WithUint(stack.Back(0), 32) +} + +func memoryMStore8(stack *Stack) (uint64, bool) { + return calcMemSize64WithUint(stack.Back(0), 1) +} + +func memoryMStore(stack *Stack) (uint64, bool) { + return calcMemSize64WithUint(stack.Back(0), 32) +} + +func memoryMcopy(stack *Stack) (uint64, bool) { + mStart := stack.Back(0) // stack[0]: dest + if stack.Back(1).Gt(mStart) { + mStart = stack.Back(1) // stack[1]: source + } + return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length +} + +func memoryCreate(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(2)) +} + +func memoryCreate2(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(1), stack.Back(2)) +} + +func memoryCall(stack *Stack) (uint64, bool) { + x, overflow := calcMemSize64(stack.Back(5), stack.Back(6)) + if overflow { + return 0, true + } + y, overflow := calcMemSize64(stack.Back(3), stack.Back(4)) + if overflow { + return 0, true + } + if x > y { + return x, false + } + return y, false +} +func memoryDelegateCall(stack *Stack) (uint64, bool) { + x, overflow := calcMemSize64(stack.Back(4), stack.Back(5)) + if overflow { + return 0, true + } + y, overflow := calcMemSize64(stack.Back(2), stack.Back(3)) + if overflow { + return 0, true + } + if x > y { + return x, false + } + return y, false +} + +func memoryStaticCall(stack *Stack) (uint64, bool) { + x, overflow := calcMemSize64(stack.Back(4), stack.Back(5)) + if overflow { + return 0, true + } + y, overflow := calcMemSize64(stack.Back(2), stack.Back(3)) + if overflow { + return 0, true + } + if x > y { + return x, false + } + return y, false +} + +func memoryReturn(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} + +func memoryRevert(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} + +func memoryLog(stack *Stack) (uint64, bool) { + return calcMemSize64(stack.Back(0), stack.Back(1)) +} diff --git a/core/vm/memory_test.go b/core/vm/memory_test.go new file mode 100644 index 0000000..ba36f80 --- /dev/null +++ b/core/vm/memory_test.go @@ -0,0 +1,69 @@ +package vm + +import ( + "bytes" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestMemoryCopy(t *testing.T) { + // Test cases from https://eips.ethereum.org/EIPS/eip-5656#test-cases + for i, tc := range []struct { + dst, src, len uint64 + pre string + want string + }{ + { // MCOPY 0 32 32 - copy 32 bytes from offset 32 to offset 0. + 0, 32, 32, + "0000000000000000000000000000000000000000000000000000000000000000 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + }, + + { // MCOPY 0 0 32 - copy 32 bytes from offset 0 to offset 0. + 0, 0, 32, + "0101010101010101010101010101010101010101010101010101010101010101", + "0101010101010101010101010101010101010101010101010101010101010101", + }, + { // MCOPY 0 1 8 - copy 8 bytes from offset 1 to offset 0 (overlapping). + 0, 1, 8, + "000102030405060708 000000000000000000000000000000000000000000000000", + "010203040506070808 000000000000000000000000000000000000000000000000", + }, + { // MCOPY 1 0 8 - copy 8 bytes from offset 0 to offset 1 (overlapping). + 1, 0, 8, + "000102030405060708 000000000000000000000000000000000000000000000000", + "000001020304050607 000000000000000000000000000000000000000000000000", + }, + // Tests below are not in the EIP, but maybe should be added + { // MCOPY 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds index(overlapping). + 0xFFFFFFFFFFFF, 0xFFFFFFFFFFFF, 0, + "11", + "11", + }, + { // MCOPY 0xFFFFFFFFFFFF 0 0 - copy zero bytes from start of mem to out-of-bounds. + 0xFFFFFFFFFFFF, 0, 0, + "11", + "11", + }, + { // MCOPY 0 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds to start of mem + 0, 0xFFFFFFFFFFFF, 0, + "11", + "11", + }, + } { + m := NewMemory() + // Clean spaces + data := common.FromHex(strings.ReplaceAll(tc.pre, " ", "")) + // Set pre + m.Resize(uint64(len(data))) + m.Set(0, uint64(len(data)), data) + // Do the copy + m.Copy(tc.dst, tc.src, tc.len) + want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) + if have := m.store; !bytes.Equal(want, have) { + t.Errorf("case %d: want: %#x\nhave: %#x\n", i, want, have) + } + } +} diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go new file mode 100644 index 0000000..2b9231f --- /dev/null +++ b/core/vm/opcodes.go @@ -0,0 +1,562 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "fmt" +) + +// OpCode is an EVM opcode +type OpCode byte + +// IsPush specifies if an opcode is a PUSH opcode. +func (op OpCode) IsPush() bool { + return PUSH0 <= op && op <= PUSH32 +} + +// 0x0 range - arithmetic ops. +const ( + STOP OpCode = 0x0 + ADD OpCode = 0x1 + MUL OpCode = 0x2 + SUB OpCode = 0x3 + DIV OpCode = 0x4 + SDIV OpCode = 0x5 + MOD OpCode = 0x6 + SMOD OpCode = 0x7 + ADDMOD OpCode = 0x8 + MULMOD OpCode = 0x9 + EXP OpCode = 0xa + SIGNEXTEND OpCode = 0xb +) + +// 0x10 range - comparison ops. +const ( + LT OpCode = 0x10 + GT OpCode = 0x11 + SLT OpCode = 0x12 + SGT OpCode = 0x13 + EQ OpCode = 0x14 + ISZERO OpCode = 0x15 + AND OpCode = 0x16 + OR OpCode = 0x17 + XOR OpCode = 0x18 + NOT OpCode = 0x19 + BYTE OpCode = 0x1a + SHL OpCode = 0x1b + SHR OpCode = 0x1c + SAR OpCode = 0x1d +) + +// 0x20 range - crypto. +const ( + KECCAK256 OpCode = 0x20 +) + +// 0x30 range - closure state. +const ( + ADDRESS OpCode = 0x30 + BALANCE OpCode = 0x31 + ORIGIN OpCode = 0x32 + CALLER OpCode = 0x33 + CALLVALUE OpCode = 0x34 + CALLDATALOAD OpCode = 0x35 + CALLDATASIZE OpCode = 0x36 + CALLDATACOPY OpCode = 0x37 + CODESIZE OpCode = 0x38 + CODECOPY OpCode = 0x39 + GASPRICE OpCode = 0x3a + EXTCODESIZE OpCode = 0x3b + EXTCODECOPY OpCode = 0x3c + RETURNDATASIZE OpCode = 0x3d + RETURNDATACOPY OpCode = 0x3e + EXTCODEHASH OpCode = 0x3f +) + +// 0x40 range - block operations. +const ( + BLOCKHASH OpCode = 0x40 + COINBASE OpCode = 0x41 + TIMESTAMP OpCode = 0x42 + NUMBER OpCode = 0x43 + DIFFICULTY OpCode = 0x44 + RANDOM OpCode = 0x44 // Same as DIFFICULTY + PREVRANDAO OpCode = 0x44 // Same as DIFFICULTY + GASLIMIT OpCode = 0x45 + CHAINID OpCode = 0x46 + SELFBALANCE OpCode = 0x47 + BASEFEE OpCode = 0x48 + BLOBHASH OpCode = 0x49 + BLOBBASEFEE OpCode = 0x4a +) + +// 0x50 range - 'storage' and execution. +const ( + POP OpCode = 0x50 + MLOAD OpCode = 0x51 + MSTORE OpCode = 0x52 + MSTORE8 OpCode = 0x53 + SLOAD OpCode = 0x54 + SSTORE OpCode = 0x55 + JUMP OpCode = 0x56 + JUMPI OpCode = 0x57 + PC OpCode = 0x58 + MSIZE OpCode = 0x59 + GAS OpCode = 0x5a + JUMPDEST OpCode = 0x5b + TLOAD OpCode = 0x5c + TSTORE OpCode = 0x5d + MCOPY OpCode = 0x5e + PUSH0 OpCode = 0x5f +) + +// 0x60 range - pushes. +const ( + PUSH1 OpCode = 0x60 + iota + PUSH2 + PUSH3 + PUSH4 + PUSH5 + PUSH6 + PUSH7 + PUSH8 + PUSH9 + PUSH10 + PUSH11 + PUSH12 + PUSH13 + PUSH14 + PUSH15 + PUSH16 + PUSH17 + PUSH18 + PUSH19 + PUSH20 + PUSH21 + PUSH22 + PUSH23 + PUSH24 + PUSH25 + PUSH26 + PUSH27 + PUSH28 + PUSH29 + PUSH30 + PUSH31 + PUSH32 +) + +// 0x80 range - dups. +const ( + DUP1 = 0x80 + iota + DUP2 + DUP3 + DUP4 + DUP5 + DUP6 + DUP7 + DUP8 + DUP9 + DUP10 + DUP11 + DUP12 + DUP13 + DUP14 + DUP15 + DUP16 +) + +// 0x90 range - swaps. +const ( + SWAP1 = 0x90 + iota + SWAP2 + SWAP3 + SWAP4 + SWAP5 + SWAP6 + SWAP7 + SWAP8 + SWAP9 + SWAP10 + SWAP11 + SWAP12 + SWAP13 + SWAP14 + SWAP15 + SWAP16 +) + +// 0xa0 range - logging ops. +const ( + LOG0 OpCode = 0xa0 + iota + LOG1 + LOG2 + LOG3 + LOG4 +) + +// 0xf0 range - closures. +const ( + CREATE OpCode = 0xf0 + CALL OpCode = 0xf1 + CALLCODE OpCode = 0xf2 + RETURN OpCode = 0xf3 + DELEGATECALL OpCode = 0xf4 + CREATE2 OpCode = 0xf5 + + STATICCALL OpCode = 0xfa + REVERT OpCode = 0xfd + INVALID OpCode = 0xfe + SELFDESTRUCT OpCode = 0xff +) + +var opCodeToString = [256]string{ + // 0x0 range - arithmetic ops. + STOP: "STOP", + ADD: "ADD", + MUL: "MUL", + SUB: "SUB", + DIV: "DIV", + SDIV: "SDIV", + MOD: "MOD", + SMOD: "SMOD", + EXP: "EXP", + NOT: "NOT", + LT: "LT", + GT: "GT", + SLT: "SLT", + SGT: "SGT", + EQ: "EQ", + ISZERO: "ISZERO", + SIGNEXTEND: "SIGNEXTEND", + + // 0x10 range - bit ops. + AND: "AND", + OR: "OR", + XOR: "XOR", + BYTE: "BYTE", + SHL: "SHL", + SHR: "SHR", + SAR: "SAR", + ADDMOD: "ADDMOD", + MULMOD: "MULMOD", + + // 0x20 range - crypto. + KECCAK256: "KECCAK256", + + // 0x30 range - closure state. + ADDRESS: "ADDRESS", + BALANCE: "BALANCE", + ORIGIN: "ORIGIN", + CALLER: "CALLER", + CALLVALUE: "CALLVALUE", + CALLDATALOAD: "CALLDATALOAD", + CALLDATASIZE: "CALLDATASIZE", + CALLDATACOPY: "CALLDATACOPY", + CODESIZE: "CODESIZE", + CODECOPY: "CODECOPY", + GASPRICE: "GASPRICE", + EXTCODESIZE: "EXTCODESIZE", + EXTCODECOPY: "EXTCODECOPY", + RETURNDATASIZE: "RETURNDATASIZE", + RETURNDATACOPY: "RETURNDATACOPY", + EXTCODEHASH: "EXTCODEHASH", + + // 0x40 range - block operations. + BLOCKHASH: "BLOCKHASH", + COINBASE: "COINBASE", + TIMESTAMP: "TIMESTAMP", + NUMBER: "NUMBER", + DIFFICULTY: "DIFFICULTY", // TODO (MariusVanDerWijden) rename to PREVRANDAO post merge + GASLIMIT: "GASLIMIT", + CHAINID: "CHAINID", + SELFBALANCE: "SELFBALANCE", + BASEFEE: "BASEFEE", + BLOBHASH: "BLOBHASH", + BLOBBASEFEE: "BLOBBASEFEE", + + // 0x50 range - 'storage' and execution. + POP: "POP", + MLOAD: "MLOAD", + MSTORE: "MSTORE", + MSTORE8: "MSTORE8", + SLOAD: "SLOAD", + SSTORE: "SSTORE", + JUMP: "JUMP", + JUMPI: "JUMPI", + PC: "PC", + MSIZE: "MSIZE", + GAS: "GAS", + JUMPDEST: "JUMPDEST", + TLOAD: "TLOAD", + TSTORE: "TSTORE", + MCOPY: "MCOPY", + PUSH0: "PUSH0", + + // 0x60 range - pushes. + PUSH1: "PUSH1", + PUSH2: "PUSH2", + PUSH3: "PUSH3", + PUSH4: "PUSH4", + PUSH5: "PUSH5", + PUSH6: "PUSH6", + PUSH7: "PUSH7", + PUSH8: "PUSH8", + PUSH9: "PUSH9", + PUSH10: "PUSH10", + PUSH11: "PUSH11", + PUSH12: "PUSH12", + PUSH13: "PUSH13", + PUSH14: "PUSH14", + PUSH15: "PUSH15", + PUSH16: "PUSH16", + PUSH17: "PUSH17", + PUSH18: "PUSH18", + PUSH19: "PUSH19", + PUSH20: "PUSH20", + PUSH21: "PUSH21", + PUSH22: "PUSH22", + PUSH23: "PUSH23", + PUSH24: "PUSH24", + PUSH25: "PUSH25", + PUSH26: "PUSH26", + PUSH27: "PUSH27", + PUSH28: "PUSH28", + PUSH29: "PUSH29", + PUSH30: "PUSH30", + PUSH31: "PUSH31", + PUSH32: "PUSH32", + + // 0x80 - dups. + DUP1: "DUP1", + DUP2: "DUP2", + DUP3: "DUP3", + DUP4: "DUP4", + DUP5: "DUP5", + DUP6: "DUP6", + DUP7: "DUP7", + DUP8: "DUP8", + DUP9: "DUP9", + DUP10: "DUP10", + DUP11: "DUP11", + DUP12: "DUP12", + DUP13: "DUP13", + DUP14: "DUP14", + DUP15: "DUP15", + DUP16: "DUP16", + + // 0x90 - swaps. + SWAP1: "SWAP1", + SWAP2: "SWAP2", + SWAP3: "SWAP3", + SWAP4: "SWAP4", + SWAP5: "SWAP5", + SWAP6: "SWAP6", + SWAP7: "SWAP7", + SWAP8: "SWAP8", + SWAP9: "SWAP9", + SWAP10: "SWAP10", + SWAP11: "SWAP11", + SWAP12: "SWAP12", + SWAP13: "SWAP13", + SWAP14: "SWAP14", + SWAP15: "SWAP15", + SWAP16: "SWAP16", + + // 0xa0 range - logging ops. + LOG0: "LOG0", + LOG1: "LOG1", + LOG2: "LOG2", + LOG3: "LOG3", + LOG4: "LOG4", + + // 0xf0 range - closures. + CREATE: "CREATE", + CALL: "CALL", + RETURN: "RETURN", + CALLCODE: "CALLCODE", + DELEGATECALL: "DELEGATECALL", + CREATE2: "CREATE2", + STATICCALL: "STATICCALL", + REVERT: "REVERT", + INVALID: "INVALID", + SELFDESTRUCT: "SELFDESTRUCT", +} + +func (op OpCode) String() string { + if s := opCodeToString[op]; s != "" { + return s + } + return fmt.Sprintf("opcode %#x not defined", int(op)) +} + +var stringToOp = map[string]OpCode{ + "STOP": STOP, + "ADD": ADD, + "MUL": MUL, + "SUB": SUB, + "DIV": DIV, + "SDIV": SDIV, + "MOD": MOD, + "SMOD": SMOD, + "EXP": EXP, + "NOT": NOT, + "LT": LT, + "GT": GT, + "SLT": SLT, + "SGT": SGT, + "EQ": EQ, + "ISZERO": ISZERO, + "SIGNEXTEND": SIGNEXTEND, + "AND": AND, + "OR": OR, + "XOR": XOR, + "BYTE": BYTE, + "SHL": SHL, + "SHR": SHR, + "SAR": SAR, + "ADDMOD": ADDMOD, + "MULMOD": MULMOD, + "KECCAK256": KECCAK256, + "ADDRESS": ADDRESS, + "BALANCE": BALANCE, + "ORIGIN": ORIGIN, + "CALLER": CALLER, + "CALLVALUE": CALLVALUE, + "CALLDATALOAD": CALLDATALOAD, + "CALLDATASIZE": CALLDATASIZE, + "CALLDATACOPY": CALLDATACOPY, + "CHAINID": CHAINID, + "BASEFEE": BASEFEE, + "BLOBHASH": BLOBHASH, + "BLOBBASEFEE": BLOBBASEFEE, + "DELEGATECALL": DELEGATECALL, + "STATICCALL": STATICCALL, + "CODESIZE": CODESIZE, + "CODECOPY": CODECOPY, + "GASPRICE": GASPRICE, + "EXTCODESIZE": EXTCODESIZE, + "EXTCODECOPY": EXTCODECOPY, + "RETURNDATASIZE": RETURNDATASIZE, + "RETURNDATACOPY": RETURNDATACOPY, + "EXTCODEHASH": EXTCODEHASH, + "BLOCKHASH": BLOCKHASH, + "COINBASE": COINBASE, + "TIMESTAMP": TIMESTAMP, + "NUMBER": NUMBER, + "DIFFICULTY": DIFFICULTY, + "GASLIMIT": GASLIMIT, + "SELFBALANCE": SELFBALANCE, + "POP": POP, + "MLOAD": MLOAD, + "MSTORE": MSTORE, + "MSTORE8": MSTORE8, + "SLOAD": SLOAD, + "SSTORE": SSTORE, + "JUMP": JUMP, + "JUMPI": JUMPI, + "PC": PC, + "MSIZE": MSIZE, + "GAS": GAS, + "JUMPDEST": JUMPDEST, + "TLOAD": TLOAD, + "TSTORE": TSTORE, + "MCOPY": MCOPY, + "PUSH0": PUSH0, + "PUSH1": PUSH1, + "PUSH2": PUSH2, + "PUSH3": PUSH3, + "PUSH4": PUSH4, + "PUSH5": PUSH5, + "PUSH6": PUSH6, + "PUSH7": PUSH7, + "PUSH8": PUSH8, + "PUSH9": PUSH9, + "PUSH10": PUSH10, + "PUSH11": PUSH11, + "PUSH12": PUSH12, + "PUSH13": PUSH13, + "PUSH14": PUSH14, + "PUSH15": PUSH15, + "PUSH16": PUSH16, + "PUSH17": PUSH17, + "PUSH18": PUSH18, + "PUSH19": PUSH19, + "PUSH20": PUSH20, + "PUSH21": PUSH21, + "PUSH22": PUSH22, + "PUSH23": PUSH23, + "PUSH24": PUSH24, + "PUSH25": PUSH25, + "PUSH26": PUSH26, + "PUSH27": PUSH27, + "PUSH28": PUSH28, + "PUSH29": PUSH29, + "PUSH30": PUSH30, + "PUSH31": PUSH31, + "PUSH32": PUSH32, + "DUP1": DUP1, + "DUP2": DUP2, + "DUP3": DUP3, + "DUP4": DUP4, + "DUP5": DUP5, + "DUP6": DUP6, + "DUP7": DUP7, + "DUP8": DUP8, + "DUP9": DUP9, + "DUP10": DUP10, + "DUP11": DUP11, + "DUP12": DUP12, + "DUP13": DUP13, + "DUP14": DUP14, + "DUP15": DUP15, + "DUP16": DUP16, + "SWAP1": SWAP1, + "SWAP2": SWAP2, + "SWAP3": SWAP3, + "SWAP4": SWAP4, + "SWAP5": SWAP5, + "SWAP6": SWAP6, + "SWAP7": SWAP7, + "SWAP8": SWAP8, + "SWAP9": SWAP9, + "SWAP10": SWAP10, + "SWAP11": SWAP11, + "SWAP12": SWAP12, + "SWAP13": SWAP13, + "SWAP14": SWAP14, + "SWAP15": SWAP15, + "SWAP16": SWAP16, + "LOG0": LOG0, + "LOG1": LOG1, + "LOG2": LOG2, + "LOG3": LOG3, + "LOG4": LOG4, + "CREATE": CREATE, + "CREATE2": CREATE2, + "CALL": CALL, + "RETURN": RETURN, + "CALLCODE": CALLCODE, + "REVERT": REVERT, + "INVALID": INVALID, + "SELFDESTRUCT": SELFDESTRUCT, +} + +// StringToOp finds the opcode whose name is stored in `str`. +func StringToOp(str string) OpCode { + return stringToOp[str] +} diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go new file mode 100644 index 0000000..289da44 --- /dev/null +++ b/core/vm/operations_acl.go @@ -0,0 +1,250 @@ +// 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 vm + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/params" +) + +func makeGasSStoreFunc(clearingRefund uint64) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.peek() + slot = common.Hash(x.Bytes32()) + current = evm.StateDB.GetState(contract.Address(), slot) + cost = uint64(0) + ) + // Check slot presence in the access list + if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + cost = params.ColdSloadCostEIP2929 + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + if !addrPresent { + // Once we're done with YOLOv2 and schedule this for mainnet, might + // be good to remove this panic here, which is just really a + // canary to have during testing + panic("impossible case: address was not present in access list during sstore op") + } + } + value := common.Hash(y.Bytes32()) + + if current == value { // noop (1) + // EIP 2200 original clause: + // return params.SloadGasEIP2200, nil + return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS + } + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return cost + params.SstoreSetGasEIP2200, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(clearingRefund) + } + // EIP-2200 original clause: + // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929), nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(clearingRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(clearingRefund) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + // EIP 2200 Original clause: + //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.WarmStorageReadCostEIP2929) + } else { // reset to original existing slot (2.2.2.2) + // EIP 2200 Original clause: + // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) + // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST + // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST + evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929) + } + } + // EIP-2200 original clause: + //return params.SloadGasEIP2200, nil // dirty update (2.2) + return cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2) + } +} + +// gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929 +// For SLOAD, if the (address, storage_key) pair (where address is the address of the contract +// whose storage is being read) is not yet in accessed_storage_keys, +// charge 2100 gas and add the pair to accessed_storage_keys. +// If the pair is already in accessed_storage_keys, charge 100 gas. +func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + loc := stack.peek() + slot := common.Hash(loc.Bytes32()) + // Check slot presence in the access list + if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + // If the caller cannot afford the cost, this change will be rolled back + // If he does afford it, we can skip checking the same thing later on, during execution + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + return params.ColdSloadCostEIP2929, nil + } + return params.WarmStorageReadCostEIP2929, nil +} + +// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 +// EIP spec: +// > If the target is not in accessed_addresses, +// > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. +// > Otherwise, charge WARM_STORAGE_READ_COST gas. +func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // memory expansion first (dynamic part of pre-2929 implementation) + gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + addr := common.Address(stack.peek().Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } + return gas, nil +} + +// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list. +// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it +// is also using 'warm' as constant factor. +// This method is used by: +// - extcodehash, +// - extcodesize, +// - (ext) balance +func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.peek().Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(addr) + // The warm storage read cost is already charged as constantGas + return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil + } + return 0, nil +} + +func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.Back(1).Bytes20()) + // Check slot presence in the access list + warmAccess := evm.StateDB.AddressInAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 + if !warmAccess { + evm.StateDB.AddAddressToAccessList(addr) + // Charge the remaining difference here already, to correctly calculate available + // gas for call + if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + } + // Now call the old calculator, which takes into account + // - create new account + // - transfer value + // - memory expansion + // - 63/64ths rule + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if warmAccess || err != nil { + return gas, err + } + // In case of a cold access, we temporarily add the cold charge back, and also + // add it to the returned gas. By adding it to the return, it will be charged + // outside of this function, as part of the dynamic gas, and that will make it + // also become correctly reported to tracers. + contract.Gas += coldCost + + var overflow bool + if gas, overflow = math.SafeAdd(gas, coldCost); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } +} + +var ( + gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall) + gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall) + gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall) + gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode) + gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) + // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds) + gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) + + // gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 + // + // When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys. + // If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys. + // Additionally, modify the parameters defined in EIP 2200 as follows: + // + // Parameter Old value New value + // SLOAD_GAS 800 = WARM_STORAGE_READ_COST + // SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST + // + //The other parameters defined in EIP 2200 are unchanged. + // see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified + gasSStoreEIP2929 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP2200) + + // 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-3529 +func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { + gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + address = common.Address(stack.peek().Bytes20()) + ) + if !evm.StateDB.AddressInAccessList(address) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(address) + gas = params.ColdAccountAccessCostEIP2929 + } + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + if refundsEnabled && !evm.StateDB.HasSelfDestructed(contract.Address()) { + evm.StateDB.AddRefund(params.SelfdestructRefundGas) + } + return gas, nil + } + return gasFunc +} diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go new file mode 100644 index 0000000..73eb059 --- /dev/null +++ b/core/vm/operations_verkle.go @@ -0,0 +1,159 @@ +// Copyright 2024 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 vm + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + gas := evm.AccessEvents.BalanceGas(address, false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + if _, isPrecompile := evm.precompile(address); isPrecompile { + return 0, nil + } + gas := evm.AccessEvents.VersionGas(address, false) + gas += evm.AccessEvents.CodeSizeGas(address, false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + address := stack.peek().Bytes20() + if _, isPrecompile := evm.precompile(address); isPrecompile { + return 0, nil + } + gas := evm.AccessEvents.CodeHashGas(address, false) + if gas == 0 { + gas = params.WarmStorageReadCostEIP2929 + } + return gas, nil +} + +func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { + return gas, nil + } + witnessGas := evm.AccessEvents.MessageCallGas(contract.Address()) + if witnessGas == 0 { + witnessGas = params.WarmStorageReadCostEIP2929 + } + return witnessGas + gas, nil + } +} + +var ( + gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall) + gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode) + gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall) + gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall) +) + +func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + beneficiaryAddr := common.Address(stack.peek().Bytes20()) + if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile { + return 0, nil + } + contractAddr := contract.Address() + statelessGas := evm.AccessEvents.VersionGas(contractAddr, false) + statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr, false) + statelessGas += evm.AccessEvents.BalanceGas(contractAddr, false) + if contractAddr != beneficiaryAddr { + statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, false) + } + // Charge write costs if it transfers value + if evm.StateDB.GetBalance(contractAddr).Sign() != 0 { + statelessGas += evm.AccessEvents.BalanceGas(contractAddr, true) + if contractAddr != beneficiaryAddr { + statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, true) + } + } + return statelessGas, nil +} + +func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := gasCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + var ( + codeOffset = stack.Back(1) + length = stack.Back(2) + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = math.MaxUint64 + } + _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) + if !contract.IsDeployment { + gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + } + return gas, nil +} + +func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // memory expansion first (dynamic part of pre-2929 implementation) + gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + addr := common.Address(stack.peek().Bytes20()) + wgas := evm.AccessEvents.VersionGas(addr, false) + wgas += evm.AccessEvents.CodeSizeGas(addr, false) + if wgas == 0 { + wgas = params.WarmStorageReadCostEIP2929 + } + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, wgas); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil +} diff --git a/core/vm/runtime/doc.go b/core/vm/runtime/doc.go new file mode 100644 index 0000000..a3b464a --- /dev/null +++ b/core/vm/runtime/doc.go @@ -0,0 +1,18 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package runtime provides a basic execution model for executing EVM code. +package runtime diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go new file mode 100644 index 0000000..34335b8 --- /dev/null +++ b/core/vm/runtime/env.go @@ -0,0 +1,46 @@ +// 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 runtime + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" +) + +func NewEnv(cfg *Config) *vm.EVM { + txContext := vm.TxContext{ + Origin: cfg.Origin, + GasPrice: cfg.GasPrice, + BlobHashes: cfg.BlobHashes, + BlobFeeCap: cfg.BlobFeeCap, + } + blockContext := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: cfg.GetHashFn, + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber, + Time: cfg.Time, + 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 new file mode 100644 index 0000000..1181e5f --- /dev/null +++ b/core/vm/runtime/runtime.go @@ -0,0 +1,223 @@ +// 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 runtime + +import ( + "math" + "math/big" + + "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/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// Config is a basic type specifying certain configuration flags for running +// the EVM. +type Config struct { + ChainConfig *params.ChainConfig + Difficulty *big.Int + Origin common.Address + Coinbase common.Address + BlockNumber *big.Int + Time uint64 + GasLimit uint64 + GasPrice *big.Int + Value *big.Int + 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 +} + +// sets defaults on the config +func setDefaults(cfg *Config) { + if cfg.ChainConfig == nil { + var ( + shanghaiTime = uint64(0) + cancunTime = uint64(0) + ) + cfg.ChainConfig = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: new(big.Int), + DAOForkBlock: new(big.Int), + DAOForkSupport: false, + EIP150Block: new(big.Int), + EIP155Block: new(big.Int), + EIP158Block: new(big.Int), + ByzantiumBlock: new(big.Int), + ConstantinopleBlock: new(big.Int), + PetersburgBlock: new(big.Int), + IstanbulBlock: new(big.Int), + MuirGlacierBlock: new(big.Int), + BerlinBlock: new(big.Int), + LondonBlock: new(big.Int), + ArrowGlacierBlock: nil, + GrayGlacierBlock: nil, + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + MergeNetsplitBlock: nil, + ShanghaiTime: &shanghaiTime, + CancunTime: &cancunTime} + } + if cfg.Difficulty == nil { + cfg.Difficulty = new(big.Int) + } + if cfg.GasLimit == 0 { + cfg.GasLimit = math.MaxUint64 + } + if cfg.GasPrice == nil { + cfg.GasPrice = new(big.Int) + } + if cfg.Value == nil { + cfg.Value = new(big.Int) + } + if cfg.BlockNumber == nil { + cfg.BlockNumber = new(big.Int) + } + if cfg.GetHashFn == nil { + cfg.GetHashFn = func(n uint64) common.Hash { + return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) + } + } + if cfg.BaseFee == nil { + cfg.BaseFee = big.NewInt(params.InitialBaseFee) + } + if cfg.BlobBaseFee == nil { + cfg.BlobBaseFee = big.NewInt(params.BlobTxMinBlobGasprice) + } + // Merge indicators + if t := cfg.ChainConfig.ShanghaiTime; cfg.ChainConfig.TerminalTotalDifficultyPassed || (t != nil && *t == 0) { + cfg.Random = &(common.Hash{}) + } +} + +// Execute executes the code using the input as call data during the execution. +// It returns the EVM's return value, the new state and an error if it failed. +// +// Execute sets up an in-memory, temporary, environment for the execution of +// the given code. It makes sure that it's restored to its original state afterwards. +func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { + if cfg == nil { + cfg = new(Config) + } + setDefaults(cfg) + + if cfg.State == nil { + cfg.State, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + } + var ( + address = common.BytesToAddress([]byte("contract")) + vmenv = NewEnv(cfg) + sender = vm.AccountRef(cfg.Origin) + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) + ) + if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxStart != nil { + cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{To: &address, Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin) + } + // Execute the preparatory steps for state transition which includes: + // - prepare accessList(post-berlin) + // - reset transient storage(eip 1153) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + cfg.State.CreateAccount(address) + // set the receiver's (the executing contract) code for execution. + cfg.State.SetCode(address, code) + // Call the code with the given configuration. + ret, _, err := vmenv.Call( + sender, + common.BytesToAddress([]byte("contract")), + input, + cfg.GasLimit, + uint256.MustFromBig(cfg.Value), + ) + return ret, cfg.State, err +} + +// Create executes the code using the EVM create method +func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { + if cfg == nil { + cfg = new(Config) + } + setDefaults(cfg) + + if cfg.State == nil { + cfg.State, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + } + var ( + vmenv = NewEnv(cfg) + sender = vm.AccountRef(cfg.Origin) + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) + ) + if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxStart != nil { + cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin) + } + // Execute the preparatory steps for state transition which includes: + // - prepare accessList(post-berlin) + // - reset transient storage(eip 1153) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) + // Call the code with the given configuration. + code, address, leftOverGas, err := vmenv.Create( + sender, + input, + cfg.GasLimit, + uint256.MustFromBig(cfg.Value), + ) + return code, address, leftOverGas, err +} + +// Call executes the code given by the contract's address. It will return the +// EVM's return value or an error if it failed. +// +// Call, unlike Execute, requires a config and also requires the State field to +// be set. +func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, error) { + setDefaults(cfg) + + var ( + vmenv = NewEnv(cfg) + sender = vm.AccountRef(cfg.Origin) + statedb = cfg.State + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Random != nil, vmenv.Context.Time) + ) + if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxStart != nil { + cfg.EVMConfig.Tracer.OnTxStart(vmenv.GetVMContext(), types.NewTx(&types.LegacyTx{To: &address, Data: input, Value: cfg.Value, Gas: cfg.GasLimit}), cfg.Origin) + } + // Execute the preparatory steps for state transition which includes: + // - prepare accessList(post-berlin) + // - reset transient storage(eip 1153) + statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + + // Call the code with the given configuration. + ret, leftOverGas, err := vmenv.Call( + sender, + address, + input, + cfg.GasLimit, + uint256.MustFromBig(cfg.Value), + ) + return ret, leftOverGas, err +} diff --git a/core/vm/runtime/runtime_example_test.go b/core/vm/runtime/runtime_example_test.go new file mode 100644 index 0000000..b7d0ddc --- /dev/null +++ b/core/vm/runtime/runtime_example_test.go @@ -0,0 +1,34 @@ +// 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 runtime_test + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm/runtime" +) + +func ExampleExecute() { + ret, _, err := runtime.Execute(common.Hex2Bytes("6060604052600a8060106000396000f360606040526008565b00"), nil, nil) + if err != nil { + fmt.Println(err) + } + fmt.Println(ret) + // Output: + // [96 96 96 64 82 96 8 86 91 0] +} diff --git a/core/vm/runtime/runtime_fuzz_test.go b/core/vm/runtime/runtime_fuzz_test.go new file mode 100644 index 0000000..8a4d31d --- /dev/null +++ b/core/vm/runtime/runtime_fuzz_test.go @@ -0,0 +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 runtime + +import ( + "testing" +) + +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 new file mode 100644 index 0000000..04abc54 --- /dev/null +++ b/core/vm/runtime/runtime_test.go @@ -0,0 +1,915 @@ +// 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 runtime + +import ( + "fmt" + "math/big" + "os" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/asm" + "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/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/ethereum/go-ethereum/params" + + // force-load js tracers to trigger registration + _ "github.com/ethereum/go-ethereum/eth/tracers/js" + "github.com/holiman/uint256" +) + +func TestDefaults(t *testing.T) { + cfg := new(Config) + setDefaults(cfg) + + if cfg.Difficulty == nil { + t.Error("expected difficulty to be non nil") + } + + if cfg.GasLimit == 0 { + t.Error("didn't expect gaslimit to be zero") + } + if cfg.GasPrice == nil { + t.Error("expected time to be non nil") + } + if cfg.Value == nil { + t.Error("expected time to be non nil") + } + if cfg.GetHashFn == nil { + t.Error("expected time to be non nil") + } + if cfg.BlockNumber == nil { + t.Error("expected block number to be non nil") + } +} + +func TestEVM(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("crashed with: %v", r) + } + }() + + Execute([]byte{ + byte(vm.DIFFICULTY), + byte(vm.TIMESTAMP), + byte(vm.GASLIMIT), + byte(vm.PUSH1), + byte(vm.ORIGIN), + byte(vm.BLOCKHASH), + byte(vm.COINBASE), + }, nil, nil) +} + +func TestExecute(t *testing.T) { + ret, _, err := Execute([]byte{ + byte(vm.PUSH1), 10, + byte(vm.PUSH1), 0, + byte(vm.MSTORE), + byte(vm.PUSH1), 32, + byte(vm.PUSH1), 0, + byte(vm.RETURN), + }, nil, nil) + if err != nil { + t.Fatal("didn't expect error", err) + } + + num := new(big.Int).SetBytes(ret) + if num.Cmp(big.NewInt(10)) != 0 { + t.Error("Expected 10, got", num) + } +} + +func TestCall(t *testing.T) { + state, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + address := common.HexToAddress("0xaa") + state.SetCode(address, []byte{ + byte(vm.PUSH1), 10, + byte(vm.PUSH1), 0, + byte(vm.MSTORE), + byte(vm.PUSH1), 32, + byte(vm.PUSH1), 0, + byte(vm.RETURN), + }) + + ret, _, err := Call(address, nil, &Config{State: state}) + if err != nil { + t.Fatal("didn't expect error", err) + } + + num := new(big.Int).SetBytes(ret) + if num.Cmp(big.NewInt(10)) != 0 { + t.Error("Expected 10, got", num) + } +} + +func BenchmarkCall(b *testing.B) { + var definition = `[{"constant":true,"inputs":[],"name":"seller","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"abort","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"value","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"refund","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"buyer","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"confirmReceived","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"state","outputs":[{"name":"","type":"uint8"}],"type":"function"},{"constant":false,"inputs":[],"name":"confirmPurchase","outputs":[],"type":"function"},{"inputs":[],"type":"constructor"},{"anonymous":false,"inputs":[],"name":"Aborted","type":"event"},{"anonymous":false,"inputs":[],"name":"PurchaseConfirmed","type":"event"},{"anonymous":false,"inputs":[],"name":"ItemReceived","type":"event"},{"anonymous":false,"inputs":[],"name":"Refunded","type":"event"}]` + + var code = common.Hex2Bytes("6060604052361561006c5760e060020a600035046308551a53811461007457806335a063b4146100865780633fa4f245146100a6578063590e1ae3146100af5780637150d8ae146100cf57806373fac6f0146100e1578063c19d93fb146100fe578063d696069714610112575b610131610002565b610133600154600160a060020a031681565b610131600154600160a060020a0390811633919091161461015057610002565b61014660005481565b610131600154600160a060020a039081163391909116146102d557610002565b610133600254600160a060020a031681565b610131600254600160a060020a0333811691161461023757610002565b61014660025460ff60a060020a9091041681565b61013160025460009060ff60a060020a9091041681146101cc57610002565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60025460009060a060020a900460ff16811461016b57610002565b600154600160a060020a03908116908290301631606082818181858883f150506002805460a060020a60ff02191660a160020a179055506040517f72c874aeff0b183a56e2b79c71b46e1aed4dee5e09862134b8821ba2fddbf8bf9250a150565b80546002023414806101dd57610002565b6002805460a060020a60ff021973ffffffffffffffffffffffffffffffffffffffff1990911633171660a060020a1790557fd5d55c8a68912e9a110618df8d5e2e83b8d83211c57a8ddd1203df92885dc881826060a15050565b60025460019060a060020a900460ff16811461025257610002565b60025460008054600160a060020a0390921691606082818181858883f150508354604051600160a060020a0391821694503090911631915082818181858883f150506002805460a060020a60ff02191660a160020a179055506040517fe89152acd703c9d8c7d28829d443260b411454d45394e7995815140c8cbcbcf79250a150565b60025460019060a060020a900460ff1681146102f057610002565b6002805460008054600160a060020a0390921692909102606082818181858883f150508354604051600160a060020a0391821694503090911631915082818181858883f150506002805460a060020a60ff02191660a160020a179055506040517f8616bbbbad963e4e65b1366f1d75dfb63f9e9704bbbf91fb01bec70849906cf79250a15056") + + abi, err := abi.JSON(strings.NewReader(definition)) + if err != nil { + b.Fatal(err) + } + + cpurchase, err := abi.Pack("confirmPurchase") + if err != nil { + b.Fatal(err) + } + creceived, err := abi.Pack("confirmReceived") + if err != nil { + b.Fatal(err) + } + refund, err := abi.Pack("refund") + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 400; j++ { + Execute(code, cpurchase, nil) + Execute(code, creceived, nil) + Execute(code, refund, nil) + } + } +} +func benchmarkEVM_Create(bench *testing.B, code string) { + var ( + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + sender = common.BytesToAddress([]byte("sender")) + receiver = common.BytesToAddress([]byte("receiver")) + ) + + statedb.CreateAccount(sender) + statedb.SetCode(receiver, common.FromHex(code)) + runtimeConfig := Config{ + Origin: sender, + State: statedb, + GasLimit: 10000000, + Difficulty: big.NewInt(0x200000), + Time: 0, + Coinbase: common.Address{}, + BlockNumber: new(big.Int).SetUint64(1), + ChainConfig: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: new(big.Int), + ByzantiumBlock: new(big.Int), + ConstantinopleBlock: new(big.Int), + DAOForkBlock: new(big.Int), + DAOForkSupport: false, + EIP150Block: new(big.Int), + EIP155Block: new(big.Int), + EIP158Block: new(big.Int), + }, + EVMConfig: vm.Config{}, + } + // Warm up the intpools and stuff + bench.ResetTimer() + for i := 0; i < bench.N; i++ { + Call(receiver, []byte{}, &runtimeConfig) + } + bench.StopTimer() +} + +func BenchmarkEVM_CREATE_500(bench *testing.B) { + // initcode size 500K, repeatedly calls CREATE and then modifies the mem contents + benchmarkEVM_Create(bench, "5b6207a120600080f0600152600056") +} +func BenchmarkEVM_CREATE2_500(bench *testing.B) { + // initcode size 500K, repeatedly calls CREATE2 and then modifies the mem contents + benchmarkEVM_Create(bench, "5b586207a120600080f5600152600056") +} +func BenchmarkEVM_CREATE_1200(bench *testing.B) { + // initcode size 1200K, repeatedly calls CREATE and then modifies the mem contents + benchmarkEVM_Create(bench, "5b62124f80600080f0600152600056") +} +func BenchmarkEVM_CREATE2_1200(bench *testing.B) { + // initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents + benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056") +} + +func fakeHeader(n uint64, parentHash common.Hash) *types.Header { + header := types.Header{ + Coinbase: common.HexToAddress("0x00000000000000000000000000000000deadbeef"), + Number: big.NewInt(int64(n)), + ParentHash: parentHash, + Time: 1000, + Nonce: types.BlockNonce{0x1}, + Extra: []byte{}, + Difficulty: big.NewInt(0), + GasLimit: 100000, + } + return &header +} + +type dummyChain struct { + counter int +} + +// Engine retrieves the chain's consensus engine. +func (d *dummyChain) Engine() consensus.Engine { + return nil +} + +// GetHeader returns the hash corresponding to their hash. +func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header { + d.counter++ + parentHash := common.Hash{} + s := common.LeftPadBytes(big.NewInt(int64(n-1)).Bytes(), 32) + copy(parentHash[:], s) + + //parentHash := common.Hash{byte(n - 1)} + //fmt.Printf("GetHeader(%x, %d) => header with parent %x\n", h, n, parentHash) + return fakeHeader(n, parentHash) +} + +// TestBlockhash tests the blockhash operation. It's a bit special, since it internally +// requires access to a chain reader. +func TestBlockhash(t *testing.T) { + // Current head + n := uint64(1000) + parentHash := common.Hash{} + s := common.LeftPadBytes(big.NewInt(int64(n-1)).Bytes(), 32) + copy(parentHash[:], s) + header := fakeHeader(n, parentHash) + + // This is the contract we're using. It requests the blockhash for current num (should be all zeroes), + // then iteratively fetches all blockhashes back to n-260. + // It returns + // 1. the first (should be zero) + // 2. the second (should be the parent hash) + // 3. the last non-zero hash + // By making the chain reader return hashes which correlate to the number, we can + // verify that it obtained the right hashes where it should + + /* + + pragma solidity ^0.5.3; + contract Hasher{ + + function test() public view returns (bytes32, bytes32, bytes32){ + uint256 x = block.number; + bytes32 first; + bytes32 last; + bytes32 zero; + zero = blockhash(x); // Should be zeroes + first = blockhash(x-1); + for(uint256 i = 2 ; i < 260; i++){ + bytes32 hash = blockhash(x - i); + if (uint256(hash) != 0){ + last = hash; + } + } + return (zero, first, last); + } + } + + */ + // The contract above + data := common.Hex2Bytes("6080604052348015600f57600080fd5b50600436106045576000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d14604a575b600080fd5b60506074565b60405180848152602001838152602001828152602001935050505060405180910390f35b600080600080439050600080600083409050600184034092506000600290505b61010481101560c35760008186034090506000816001900414151560b6578093505b5080806001019150506094565b508083839650965096505050505090919256fea165627a7a72305820462d71b510c1725ff35946c20b415b0d50b468ea157c8c77dff9466c9cb85f560029") + // The method call to 'test()' + input := common.Hex2Bytes("f8a8fd6d") + chain := &dummyChain{} + ret, _, err := Execute(data, input, &Config{ + GetHashFn: core.GetHashFn(header, chain), + BlockNumber: new(big.Int).Set(header.Number), + }) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if len(ret) != 96 { + t.Fatalf("expected returndata to be 96 bytes, got %d", len(ret)) + } + + zero := new(big.Int).SetBytes(ret[0:32]) + first := new(big.Int).SetBytes(ret[32:64]) + last := new(big.Int).SetBytes(ret[64:96]) + if zero.BitLen() != 0 { + t.Fatalf("expected zeroes, got %x", ret[0:32]) + } + if first.Uint64() != 999 { + t.Fatalf("second block should be 999, got %d (%x)", first, ret[32:64]) + } + if last.Uint64() != 744 { + t.Fatalf("last block should be 744, got %d (%x)", last, ret[64:96]) + } + if exp, got := 255, chain.counter; exp != got { + t.Errorf("suboptimal; too much chain iteration, expected %d, got %d", exp, got) + } +} + +// benchmarkNonModifyingCode benchmarks code, but if the code modifies the +// state, this should not be used, since it does not reset the state between runs. +func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode string, b *testing.B) { + cfg := new(Config) + setDefaults(cfg) + cfg.State, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + cfg.GasLimit = gas + if len(tracerCode) > 0 { + tracer, err := tracers.DefaultDirectory.New(tracerCode, new(tracers.Context), nil) + if err != nil { + b.Fatal(err) + } + cfg.EVMConfig = vm.Config{ + Tracer: tracer.Hooks, + } + } + var ( + destination = common.BytesToAddress([]byte("contract")) + vmenv = NewEnv(cfg) + sender = vm.AccountRef(cfg.Origin) + ) + cfg.State.CreateAccount(destination) + eoa := common.HexToAddress("E0") + { + cfg.State.CreateAccount(eoa) + cfg.State.SetNonce(eoa, 100) + } + reverting := common.HexToAddress("EE") + { + cfg.State.CreateAccount(reverting) + cfg.State.SetCode(reverting, []byte{ + byte(vm.PUSH1), 0x00, + byte(vm.PUSH1), 0x00, + byte(vm.REVERT), + }) + } + + //cfg.State.CreateAccount(cfg.Origin) + // set the receiver's (the executing contract) code for execution. + cfg.State.SetCode(destination, code) + vmenv.Call(sender, destination, nil, gas, uint256.MustFromBig(cfg.Value)) + + b.Run(name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + vmenv.Call(sender, destination, nil, gas, uint256.MustFromBig(cfg.Value)) + } + }) +} + +// BenchmarkSimpleLoop test a pretty simple loop which loops until OOG +// 55 ms +func BenchmarkSimpleLoop(b *testing.B) { + staticCallIdentity := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.PUSH1), 0x4, // address of identity + byte(vm.GAS), // gas + byte(vm.STATICCALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + callIdentity := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.DUP1), // value + byte(vm.PUSH1), 0x4, // address of identity + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + callInexistant := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.DUP1), // value + byte(vm.PUSH1), 0xff, // address of existing contract + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + callEOA := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.DUP1), // value + byte(vm.PUSH1), 0xE0, // address of EOA + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + loopingCode := []byte{ + byte(vm.JUMPDEST), // [ count ] + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.DUP1), // out insize + byte(vm.DUP1), // in offset + byte(vm.PUSH1), 0x4, // address of identity + byte(vm.GAS), // gas + + byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + callRevertingContractWithInput := []byte{ + byte(vm.JUMPDEST), // + // push args for the call + byte(vm.PUSH1), 0, // out size + byte(vm.DUP1), // out offset + byte(vm.PUSH1), 0x20, // in size + byte(vm.PUSH1), 0x00, // in offset + byte(vm.PUSH1), 0x00, // value + byte(vm.PUSH1), 0xEE, // address of reverting contract + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), // pop return value + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + //tracer := logger.NewJSONLogger(nil, os.Stdout) + //Execute(loopingCode, nil, &Config{ + // EVMConfig: vm.Config{ + // Debug: true, + // Tracer: tracer, + // }}) + // 100M gas + benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", "", b) + benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", "", b) + benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", "", b) + benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", "", b) + benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", "", b) + benchmarkNonModifyingCode(100000000, callRevertingContractWithInput, "call-reverting-100M", "", b) + + //benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b) + //benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b) +} + +// TestEip2929Cases contains various testcases that are used for +// EIP-2929 about gas repricings +func TestEip2929Cases(t *testing.T) { + t.Skip("Test only useful for generating documentation") + id := 1 + prettyPrint := func(comment string, code []byte) { + instrs := make([]string, 0) + it := asm.NewInstructionIterator(code) + for it.Next() { + if it.Arg() != nil && 0 < len(it.Arg()) { + instrs = append(instrs, fmt.Sprintf("%v %#x", it.Op(), it.Arg())) + } else { + instrs = append(instrs, fmt.Sprintf("%v", it.Op())) + } + } + ops := strings.Join(instrs, ", ") + fmt.Printf("### Case %d\n\n", id) + id++ + fmt.Printf("%v\n\nBytecode: \n```\n%#x\n```\nOperations: \n```\n%v\n```\n\n", + comment, + code, ops) + Execute(code, nil, &Config{ + EVMConfig: vm.Config{ + Tracer: logger.NewMarkdownLogger(nil, os.Stdout).Hooks(), + ExtraEips: []int{2929}, + }, + }) + } + + { // First eip testcase + code := []byte{ + // Three checks against a precompile + byte(vm.PUSH1), 1, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 2, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 3, byte(vm.BALANCE), byte(vm.POP), + // Three checks against a non-precompile + byte(vm.PUSH1), 0xf1, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 0xf2, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 0xf3, byte(vm.BALANCE), byte(vm.POP), + // Same three checks (should be cheaper) + byte(vm.PUSH1), 0xf2, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 0xf3, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 0xf1, byte(vm.BALANCE), byte(vm.POP), + // Check the origin, and the 'this' + byte(vm.ORIGIN), byte(vm.BALANCE), byte(vm.POP), + byte(vm.ADDRESS), byte(vm.BALANCE), byte(vm.POP), + + byte(vm.STOP), + } + prettyPrint("This checks `EXT`(codehash,codesize,balance) of precompiles, which should be `100`, "+ + "and later checks the same operations twice against some non-precompiles. "+ + "Those are cheaper second time they are accessed. Lastly, it checks the `BALANCE` of `origin` and `this`.", code) + } + + { // EXTCODECOPY + code := []byte{ + // extcodecopy( 0xff,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY), + // extcodecopy( 0xff,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY), + // extcodecopy( this,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.ADDRESS), byte(vm.EXTCODECOPY), + + byte(vm.STOP), + } + prettyPrint("This checks `extcodecopy( 0xff,0,0,0,0)` twice, (should be expensive first time), "+ + "and then does `extcodecopy( this,0,0,0,0)`.", code) + } + + { // SLOAD + SSTORE + code := []byte{ + + // Add slot `0x1` to access list + byte(vm.PUSH1), 0x01, byte(vm.SLOAD), byte(vm.POP), // SLOAD( 0x1) (add to access list) + // Write to `0x1` which is already in access list + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x01, byte(vm.SSTORE), // SSTORE( loc: 0x01, val: 0x11) + // Write to `0x2` which is not in access list + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11) + // Write again to `0x2` + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11) + // Read slot in access list (0x2) + byte(vm.PUSH1), 0x02, byte(vm.SLOAD), // SLOAD( 0x2) + // Read slot in access list (0x1) + byte(vm.PUSH1), 0x01, byte(vm.SLOAD), // SLOAD( 0x1) + } + prettyPrint("This checks `sload( 0x1)` followed by `sstore(loc: 0x01, val:0x11)`, then 'naked' sstore:"+ + "`sstore(loc: 0x02, val:0x11)` twice, and `sload(0x2)`, `sload(0x1)`. ", code) + } + { // Call variants + code := []byte{ + // identity precompile + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0x04, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP), + + // random account - call 1 + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP), + + // random account - call 2 + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.STATICCALL), byte(vm.POP), + } + prettyPrint("This calls the `identity`-precompile (cheap), then calls an account (expensive) and `staticcall`s the same"+ + "account (cheap)", code) + } +} + +// TestColdAccountAccessCost test that the cold account access cost is reported +// correctly +// see: https://github.com/ethereum/go-ethereum/issues/22649 +func TestColdAccountAccessCost(t *testing.T) { + for i, tc := range []struct { + code []byte + step int + want uint64 + }{ + { // EXTCODEHASH(0xff) + code: []byte{byte(vm.PUSH1), 0xFF, byte(vm.EXTCODEHASH), byte(vm.POP)}, + step: 1, + want: 2600, + }, + { // BALANCE(0xff) + code: []byte{byte(vm.PUSH1), 0xFF, byte(vm.BALANCE), byte(vm.POP)}, + step: 1, + want: 2600, + }, + { // CALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALL), byte(vm.POP), + }, + step: 7, + want: 2855, + }, + { // CALLCODE(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALLCODE), byte(vm.POP), + }, + step: 7, + want: 2855, + }, + { // DELEGATECALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.DELEGATECALL), byte(vm.POP), + }, + step: 6, + want: 2855, + }, + { // STATICCALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.STATICCALL), byte(vm.POP), + }, + step: 6, + want: 2855, + }, + { // SELFDESTRUCT(0xff) + code: []byte{ + byte(vm.PUSH1), 0xff, byte(vm.SELFDESTRUCT), + }, + step: 1, + want: 7600, + }, + } { + tracer := logger.NewStructLogger(nil) + Execute(tc.code, nil, &Config{ + EVMConfig: vm.Config{ + Tracer: tracer.Hooks(), + }, + }) + have := tracer.StructLogs()[tc.step].GasCost + if want := tc.want; have != want { + for ii, op := range tracer.StructLogs() { + t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost) + } + t.Fatalf("testcase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want) + } + } +} + +func TestRuntimeJSTracer(t *testing.T) { + jsTracers := []string{ + `{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0, + step: function() { this.steps++}, + fault: function() {}, + result: function() { + return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",") + }, + enter: function(frame) { + this.enters++; + this.enterGas = frame.getGas(); + }, + exit: function(res) { + this.exits++; + this.gasUsed = res.getGasUsed(); + }}`, + `{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0, + fault: function() {}, + result: function() { + return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",") + }, + enter: function(frame) { + this.enters++; + this.enterGas = frame.getGas(); + }, + exit: function(res) { + this.exits++; + this.gasUsed = res.getGasUsed(); + }}`} + tests := []struct { + code []byte + // One result per tracer + results []string + }{ + { + // CREATE + code: []byte{ + // Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes) + byte(vm.PUSH5), + // Init code: PUSH1 0, PUSH1 0, RETURN (3 steps) + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN), + byte(vm.PUSH1), 0, + byte(vm.MSTORE), + // length, offset, value + byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0, + byte(vm.CREATE), + byte(vm.POP), + }, + results: []string{`"1,1,952853,6,12"`, `"1,1,952853,6,0"`}, + }, + { + // CREATE2 + code: []byte{ + // Store initcode in memory at 0x00 (5 bytes left-padded to 32 bytes) + byte(vm.PUSH5), + // Init code: PUSH1 0, PUSH1 0, RETURN (3 steps) + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN), + byte(vm.PUSH1), 0, + byte(vm.MSTORE), + // salt, length, offset, value + byte(vm.PUSH1), 1, byte(vm.PUSH1), 5, byte(vm.PUSH1), 27, byte(vm.PUSH1), 0, + byte(vm.CREATE2), + byte(vm.POP), + }, + results: []string{`"1,1,952844,6,13"`, `"1,1,952844,6,0"`}, + }, + { + // CALL + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xbb, //address + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), + }, + results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`}, + }, + { + // CALLCODE + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xcc, //address + byte(vm.GAS), // gas + byte(vm.CALLCODE), + byte(vm.POP), + }, + results: []string{`"1,1,981796,6,13"`, `"1,1,981796,6,0"`}, + }, + { + // STATICCALL + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0xdd, //address + byte(vm.GAS), // gas + byte(vm.STATICCALL), + byte(vm.POP), + }, + results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`}, + }, + { + // DELEGATECALL + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0xee, //address + byte(vm.GAS), // gas + byte(vm.DELEGATECALL), + byte(vm.POP), + }, + results: []string{`"1,1,981799,6,12"`, `"1,1,981799,6,0"`}, + }, + { + // CALL self-destructing contract + code: []byte{ + // outsize, outoffset, insize, inoffset + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xff, //address + byte(vm.GAS), // gas + byte(vm.CALL), + byte(vm.POP), + }, + results: []string{`"2,2,0,5003,12"`, `"2,2,0,5003,0"`}, + }, + } + calleeCode := []byte{ + byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, + byte(vm.RETURN), + } + suicideCode := []byte{ + byte(vm.PUSH1), 0xaa, + byte(vm.SELFDESTRUCT), + } + main := common.HexToAddress("0xaa") + for i, jsTracer := range jsTracers { + for j, tc := range tests { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.SetCode(main, tc.code) + statedb.SetCode(common.HexToAddress("0xbb"), calleeCode) + statedb.SetCode(common.HexToAddress("0xcc"), calleeCode) + statedb.SetCode(common.HexToAddress("0xdd"), calleeCode) + statedb.SetCode(common.HexToAddress("0xee"), calleeCode) + statedb.SetCode(common.HexToAddress("0xff"), suicideCode) + + tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil) + if err != nil { + t.Fatal(err) + } + _, _, err = Call(main, nil, &Config{ + GasLimit: 1000000, + State: statedb, + EVMConfig: vm.Config{ + Tracer: tracer.Hooks, + }}) + if err != nil { + t.Fatal("didn't expect error", err) + } + res, err := tracer.GetResult() + if err != nil { + t.Fatal(err) + } + if have, want := string(res), tc.results[i]; have != want { + t.Errorf("wrong result for tracer %d testcase %d, have \n%v\nwant\n%v\n", i, j, have, want) + } + } + } +} + +func TestJSTracerCreateTx(t *testing.T) { + jsTracer := ` + {enters: 0, exits: 0, + step: function() {}, + fault: function() {}, + result: function() { return [this.enters, this.exits].join(",") }, + enter: function(frame) { this.enters++ }, + exit: function(res) { this.exits++ }}` + code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)} + + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil) + if err != nil { + t.Fatal(err) + } + _, _, _, err = Create(code, &Config{ + State: statedb, + EVMConfig: vm.Config{ + Tracer: tracer.Hooks, + }}) + if err != nil { + t.Fatal(err) + } + + res, err := tracer.GetResult() + if err != nil { + t.Fatal(err) + } + if have, want := string(res), `"0,0"`; have != want { + t.Errorf("wrong result for tracer, have \n%v\nwant\n%v\n", have, want) + } +} + +func BenchmarkTracerStepVsCallFrame(b *testing.B) { + // Simply pushes and pops some values in a loop + code := []byte{ + byte(vm.JUMPDEST), + byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, + byte(vm.POP), + byte(vm.POP), + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + + stepTracer := ` + { + step: function() {}, + fault: function() {}, + result: function() {}, + }` + callFrameTracer := ` + { + enter: function() {}, + exit: function() {}, + fault: function() {}, + result: function() {}, + }` + + benchmarkNonModifyingCode(10000000, code, "tracer-step-10M", stepTracer, b) + benchmarkNonModifyingCode(10000000, code, "tracer-call-frame-10M", callFrameTracer, b) +} diff --git a/core/vm/stack.go b/core/vm/stack.go new file mode 100644 index 0000000..e1a957e --- /dev/null +++ b/core/vm/stack.go @@ -0,0 +1,82 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "sync" + + "github.com/holiman/uint256" +) + +var stackPool = sync.Pool{ + New: func() interface{} { + return &Stack{data: make([]uint256.Int, 0, 16)} + }, +} + +// Stack is an object for basic stack operations. Items popped to the stack are +// expected to be changed and modified. stack does not take care of adding newly +// initialised objects. +type Stack struct { + data []uint256.Int +} + +func newstack() *Stack { + return stackPool.Get().(*Stack) +} + +func returnStack(s *Stack) { + s.data = s.data[:0] + stackPool.Put(s) +} + +// Data returns the underlying uint256.Int array. +func (st *Stack) Data() []uint256.Int { + return st.data +} + +func (st *Stack) push(d *uint256.Int) { + // NOTE push limit (1024) is checked in baseCheck + st.data = append(st.data, *d) +} + +func (st *Stack) pop() (ret uint256.Int) { + ret = st.data[len(st.data)-1] + st.data = st.data[:len(st.data)-1] + return +} + +func (st *Stack) len() int { + return len(st.data) +} + +func (st *Stack) swap(n int) { + st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n] +} + +func (st *Stack) dup(n int) { + st.push(&st.data[st.len()-n]) +} + +func (st *Stack) peek() *uint256.Int { + return &st.data[st.len()-1] +} + +// Back returns the n'th item in stack +func (st *Stack) Back(n int) *uint256.Int { + return &st.data[st.len()-n-1] +} diff --git a/core/vm/stack_table.go b/core/vm/stack_table.go new file mode 100644 index 0000000..10c1290 --- /dev/null +++ b/core/vm/stack_table.go @@ -0,0 +1,42 @@ +// 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 vm + +import ( + "github.com/ethereum/go-ethereum/params" +) + +func minSwapStack(n int) int { + return minStack(n, n) +} +func maxSwapStack(n int) int { + return maxStack(n, n) +} + +func minDupStack(n int) int { + return minStack(n, n+1) +} +func maxDupStack(n int) int { + return maxStack(n, n+1) +} + +func maxStack(pop, push int) int { + return int(params.StackLimit) + pop - push +} +func minStack(pops, push int) int { + return pops +} diff --git a/core/vm/testdata/precompiles/blake2F.json b/core/vm/testdata/precompiles/blake2F.json new file mode 100644 index 0000000..a25f9ae --- /dev/null +++ b/core/vm/testdata/precompiles/blake2F.json @@ -0,0 +1,37 @@ +[ + { + "Input": "0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "Expected": "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b", + "Name": "vector 4", + "Gas": 0, + "NoBenchmark": false + }, + { + "Input": "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "Expected": "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", + "Name": "vector 5", + "Gas": 12, + "NoBenchmark": false + }, + { + "Input": "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000", + "Expected": "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", + "Name": "vector 6", + "Gas": 12, + "NoBenchmark": false + }, + { + "Input": "0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "Expected": "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", + "Name": "vector 7", + "Gas": 1, + "NoBenchmark": false + }, + { + "Input": "007A120048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "Expected": "6d2ce9e534d50e18ff866ae92d70cceba79bbcd14c63819fe48752c8aca87a4bb7dcc230d22a4047f0486cfcfb50a17b24b2899eb8fca370f22240adb5170189", + "Name": "vector 8", + "Gas": 8000000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG1Add.json b/core/vm/testdata/precompiles/blsG1Add.json new file mode 100644 index 0000000..14a6b16 --- /dev/null +++ b/core/vm/testdata/precompiles/blsG1Add.json @@ -0,0 +1,730 @@ +[ + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "Expected": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28", + "Name": "bls_g1add_(g1+g1=2*g1)", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d280000000000000000000000000000000009ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e522400000000000000000000000000000000032b80d3a6f5b09f8a84623389c5f80ca69a0cddabc3097f9d9c27310fd43be6e745256c634af45ca3473b0590ae30d1", + "Expected": "0000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e2", + "Name": "bls_g1add_(2*g1+3*g1=5*g1)", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "Name": "bls_g1add_(inf+g1=g1)", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1add_(inf+inf=inf)", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7", + "Expected": "000000000000000000000000000000001466e1373ae4a7e7ba885c5f0c3ccfa48cdb50661646ac6b779952f466ac9fc92730dcaed9be831cd1f8c4fefffd5209000000000000000000000000000000000c1fb750d2285d4ca0378e1e8cdbf6044151867c34a711b73ae818aee6dbe9e886f53d7928cc6ed9c851e0422f609b11", + "Name": "matter_g1_add_0", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2000000000000000000000000000000000441e7f7f96198e4c23bd5eb16f1a7f045dbc8c53219ab2bcea91d3a027e2dfe659feac64905f8b9add7e4bfc91bec2b0000000000000000000000000000000005fc51bb1b40c87cd4292d4b66f8ca5ce4ef9abd2b69d4464b4879064203bda7c9fc3f896a3844ebc713f7bb20951d95", + "Expected": "0000000000000000000000000000000016b8ab56b45a9294466809b8e858c1ad15ad0d52cfcb62f8f5753dc94cee1de6efaaebce10701e3ec2ecaa9551024ea600000000000000000000000000000000124571eec37c0b1361023188d66ec17c1ec230d31b515e0e81e599ec19e40c8a7c8cdea9735bc3d8b4e37ca7e5dd71f6", + "Name": "matter_g1_add_1", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f00000000000000000000000000000000114c3f11ba0b47551fa28f09f148936d6b290dc9f2d0534a83c32b0b849ab921ce6bcaa4ff3c917707798d9c74f2084f00000000000000000000000000000000149dc028207fb04a7795d94ea65e21f9952e445000eb954531ee519efde6901675d3d2446614d243efb77a9cfe0ca3ae", + "Expected": "0000000000000000000000000000000002ce7a08719448494857102da464bc65a47c95c77819af325055a23ac50b626df4732daf63feb9a663d71b7c9b8f2c510000000000000000000000000000000016117e87e9b55bd4bd5763d69d5240d30745e014b9aef87c498f9a9e3286ec4d5927df7cd5a2e54ac4179e78645acf27", + "Name": "matter_g1_add_2", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000c3d564ac1fe12f18f528c3750583ab6af8973bff3eded7bb4778c32805d9b17846cc7c687af0f46bc87de7748ab72980000000000000000000000000000000002f164c131cbd5afc85692c246157d38dc4bbb2959d2edfa6daf0a8b17c7a898aad53b400e8bdc2b29bf6688ee863db7", + "Expected": "0000000000000000000000000000000015510826f50b88fa369caf062ecdf8b03a67e660a35b219b44437a5583b5a9adf76991dce7bff9afc50257f847299504000000000000000000000000000000000a83e879895a1b47dbd6cd25ce8b719e7490cfe021614f7539e841fc2f9c09f071e386676de60b6579aa4bf6d37b13dd", + "Name": "matter_g1_add_3", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d30000000000000000000000000000000019fe3a64361fea14936ff0b3e630471494d0c0b9423e6a004184a2965221c18849b5ed0eb2708a587323d8d6c6735a90000000000000000000000000000000000340823d314703e5efeb0a65c23069199d7dfff8793aaacb98cdcd6177fc8e61ab3294c57bf13b4406266715752ef3e6", + "Expected": "00000000000000000000000000000000010b1c96d3910f56b0bf54da5ae8c7ab674a07f8143b61fed660e7309e626dc73eaa2b11886cdb82e2b6735e7802cc860000000000000000000000000000000002dabbbedd72872c2c012e7e893d2f3df1834c43873315488d814ddd6bfcca6758a18aa6bd02a0f3aed962cb51f0a222", + "Name": "matter_g1_add_4", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90000000000000000000000000000000001461565b03a86df363d1854b4af74879115dffabeddfa879e2c8db9aa414fb291a076c3bdf0beee82d9c094ea8dc381a000000000000000000000000000000000e19d51ab619ee2daf25ea5bfa51eb217eabcfe0b5cb0358fd2fa105fd7cb0f5203816b990df6fda4e0e8d541be9bcf6", + "Expected": "000000000000000000000000000000000cb40d0bf86a627d3973f1e7846484ffd0bc4943b42a54ff9527c285fed3c056b947a9b6115824cabafe13cd1af8181c00000000000000000000000000000000076255fc12f1a9dbd232025815238baaa6a3977fd87594e8d1606caec0d37b916e1e43ee2d2953d75a40a7ba416df237", + "Name": "matter_g1_add_5", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b0000000000000000000000000000000019cabba3e09ad34cc3d125e0eb41b527aa48a4562c2b7637467b2dbc71c373897d50eed1bc75b2bde8904ece5626d6e400000000000000000000000000000000056b0746f820cff527358c86479dc924a10b9f7cae24cd495625a4159c8b71a8c3ad1a15ebf22d3561cd4b74e8a6e48b", + "Expected": "000000000000000000000000000000000e115e0b61c1f1b25cc10a7b3bd21cf696b1433a0c366c2e1bca3c26b09482c6eced8c8ecfa69ce6b9b3b4419779262e00000000000000000000000000000000077b85daf61b9f947e81633e3bc64e697bc6c1d873f2c21e5c4c3a11302d4d5ef4c3ff5519564729aaf2a50a3c9f1196", + "Name": "matter_g1_add_6", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a0000000000000000000000000000000011f649ee35ff8114060fc5e4df9ac828293f6212a9857ca31cb3e9ce49aa1212154a9808f1e763bc989b6d5ba7cf09390000000000000000000000000000000019af81eca7452f58c1a6e99fab50dc0d5eeebc7712153e717a14a31cffdfd0a923dbd585e652704a174905605a2e8b9d", + "Expected": "000000000000000000000000000000000013e37a8950a659265b285c6fb56930fb77759d9d40298acac2714b97b83ec7692a7d1c4ccb83f074384db9eedd809c0000000000000000000000000000000003215d524d6419214568ba42a31502f2a58a97d0139c66908e9d71755f5a7666567aafe30ea84d89308f06768f28a648", + "Name": "matter_g1_add_7", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000d713e148769fac2efd380886f8566c6d4662dd38317bb7e68744c4339efaedbab88435ce3dc289afaa7ecb37df37a5300000000000000000000000000000000129d9cd031b31c77a4e68093dcdbb585feba786207aa115d9cf120fe4f19ca31a0dca9c692bd0f53721d60a55c333129", + "Expected": "00000000000000000000000000000000029405b9615e14bdac8b5666bbc5f3843d4bca17c97bed66d164f1b58d2a148f0f506d645d665a40e60d53fe29375ed400000000000000000000000000000000162761f1712814e474beb2289cc50519253d680699b530c2a6477f727ccc75a19681b82e490f441f91a3c611eeb0e9e2", + "Name": "matter_g1_add_8", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000006d92bcb599edca426ff4ceeb154ebf133c2dea210c7db0441f74bd37c8d239149c8b5056ace0bfefb1db04b42664f530000000000000000000000000000000008522fc155eef6d5746283808091f91b427f2a96ac248850f9e3d7aadd14848101c965663fd4a63aea1153d71918435a", + "Expected": "000000000000000000000000000000000cfaa8df9437c0b6f344a0c8dcbc7529a07aec0d7632ace89af6796b6b960b014f78dd10e987a993fb8a95cc909822ec0000000000000000000000000000000007475f115f6eb35f78ba9a2b71a44ccb6bbc1e980b8cd369c5c469565f3fb798bc907353cf47f524ba715deaedf379cb", + "Name": "matter_g1_add_9", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac9430000000000000000000000000000000016380d03b7c5cc3301ffcb2cf7c28c9bde54fc22ba2b36ec293739d8eb674678c8e6461e34c1704747817c8f8341499a000000000000000000000000000000000ec6667aa5c6a769a64c180d277a341926376c39376480dc69fcad9a8d3b540238eb39d05aaa8e3ca15fc2c3ab696047", + "Expected": "0000000000000000000000000000000011541d798b4b5069e2541fa5410dad03fd02784332e72658c7b0fa96c586142a967addc11a7a82bfcee33bd5d07066b900000000000000000000000000000000195b3fcb94ab7beb908208283b4e5d19c0af90fca4c76268f3c703859dea7d038aca976927f48839ebc7310869c724aa", + "Name": "matter_g1_add_10", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e00000000000000000000000000000000065eb0770ab40199658bf87db6c6b52cd8c6c843a3e40dd60433d4d79971ff31296c9e00a5d553df7c81ade533379f4b0000000000000000000000000000000017a6f6137ddd90c15cf5e415f040260e15287d8d2254c6bfee88938caec9e5a048ff34f10607d1345ba1f09f30441ef4", + "Expected": "0000000000000000000000000000000006b0853b3d41fc2d7b27da0bb2d6eb76be32530b59f8f537d227a6eb78364c7c0760447494a8bba69ef4b256dbef750200000000000000000000000000000000166e55ba2d20d94da474d4a085c14245147705e252e2a76ae696c7e37d75cde6a77fea738cef045182d5e628924dc0bb", + "Name": "matter_g1_add_11", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000006a3f7eb0e42567210cc1ba5e6f8c42d02f1eef325b6483fef49ba186f59ab69ca2284715b736086d2a0a1f0ea224b40000000000000000000000000000000000bc08427fda31a6cfbe657a8c71c73894a33700e93e411d42f1471160c403b939b535070b68d60a4dc50e47493da63dc", + "Expected": "000000000000000000000000000000000c35d4cd5d43e9cf52c15d46fef521666a1e1ab9f0b4a77b8e78882e9fab40f3f988597f202c5bd176c011a56a1887d4000000000000000000000000000000000ae2b5c24928a00c02daddf03fade45344f250dcf4c12eda06c39645b4d56147cb239d95b06fd719d4dc20fe332a6fce", + "Name": "matter_g1_add_12", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a578768100000000000000000000000000000000068e79aea45b7199ec4b6f26e01e88ec76533743639ce76df66937fff9e7de3edf6700d227f10f43e073afcc63e2eddc00000000000000000000000000000000039c0b6d9e9681401aeb57a94cedc0709a0eff423ace9253eb00ae75e21cabeb626b52ef4368e6a4592aed9689c6fca4", + "Expected": "0000000000000000000000000000000013bad27dafa20f03863454c30bd5ae6b202c9c7310875da302d4693fc1c2b78cca502b1ff851b183c4b2564c5d3eb4dc0000000000000000000000000000000000552b322b3d672704382b5d8b214c225b4f7868f9c5ae0766b7cdb181f97ed90a4892235915ffbc0daf3e14ec98a606", + "Name": "matter_g1_add_13", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f7680000000000000000000000000000000000adac9bb98bb6f35a8f941dbff39dfd307b6a4d5756ccae103c814564e3d3993a8866ff91581ccdd7686c1dce0b19f700000000000000000000000000000000083d235e0579032ca47f65b6ae007ce8ffd2f1a890ce3bc45ebd0df6673ad530d2f42125d543cb0c51ba0c28345729d8", + "Expected": "000000000000000000000000000000000b5513e42f5217490f395a8cb3673a4fc35142575f770af75ecf7a4fcd97eee215c4298fc4feab51915137cbdb814839000000000000000000000000000000000e9d4db04b233b0b12a7ff620faefef906aeb2b15481ce1609dad50eb6a7d0c09a850375599c501296219fb7b288e305", + "Name": "matter_g1_add_14", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000d5bb4fa8b494c0adf4b695477d4a05f0ce48f7f971ef53952f685e9fb69dc8db1603e4a58292ddab7129bb5911d6cea0000000000000000000000000000000004a568c556641f0e0a2f44124b77ba70e4e560d7e030f1a21eff41eeec0d3c437b43488c535cdabf19a70acc777bacca", + "Expected": "000000000000000000000000000000000c27ef4ebf37fd629370508f4cd062b74faa355b305d2ee60c7f4d67dd741363f18a7bbd368cdb17e848f372a5e33a6f0000000000000000000000000000000000ed833df28988944115502f554636e0b436cccf845341e21191e82d5b662482f32c24df492da4c605a0f9e0f8b00604", + "Name": "matter_g1_add_15", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000091ee883cb9ea2c933f6645f0f4c535a826d95b6da6847b4fe2349342bd4bd496e0dd546df7a7a17a4b9fb8349e5064f000000000000000000000000000000000902d7e72242a5e6b068ca82d0cb71dc0f51335dbd302941045319f9a06777518b56a6e0b0b0c9fd8f1edf6b114ad331", + "Expected": "00000000000000000000000000000000122cce99f623944dfebffcdf6b0a0a3696162f35053e5952dddc2537421c60da9fe931579d1c4fc2e31082b6c25f96b500000000000000000000000000000000011366ffa91dc0b7da8b7c1839ea84d49299310f5c1ca244012eed0dd363dbcf4ad5813b8e3fb49361ef05ea8cb18ffe", + "Name": "matter_g1_add_16", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000000d3d4f11bc79b8425b77d25698b7e151d360ebb22c3a6afdb227de72fe432dcd6f0276b4fd3f1fcc2da5b59865053930000000000000000000000000000000015ac432071dc23148765f198ed7ea2234662745a96032c215cd9d7cf0ad8dafb8d52f209983fe98aaa2243ecc2073f1b", + "Expected": "000000000000000000000000000000000113ccf11264ff04448f8c58b279a6a49acb386750c2051eab2c90fa8b8e03d7c5b9e87eccf36b4b3f79446b80be7b1d0000000000000000000000000000000004358a1fabfe803f4c787a671196b593981a837ee78587225fb21d5a883b98a15b912862763b94d18b971cb7e37dbcf0", + "Name": "matter_g1_add_17", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb600000000000000000000000000000000034f725766897ed76394145da2f02c92c66794a51fd5ae07bd7cc60c013d7a48ebf1b07faf669dfed74d82d07e48d1150000000000000000000000000000000018f4926a3d0f740988da25379199ecb849250239ad7efcfef7ffaa43bc1373166c0448cc30dcdbd75ceb71f76f883ea7", + "Expected": "00000000000000000000000000000000167336aeeb9e447348156936849d518faee314c291c84d732fa3c1bd3951559230d94230e37a08e28e689e9d1fef05770000000000000000000000000000000005366535f7a68996e066ab80c55bb372a15fb0ed6634585b88fe7cafbf818fbfebbf6f6ddd9ca0ff72137594a1e84b35", + "Name": "matter_g1_add_18", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b00000000000000000000000000000000079e5a154cf84190b6c735bc8cd968559182166568649b813732e4fb4c5c428c8b38e8265d4ef04990c49aa1381f51c8000000000000000000000000000000000ae08e682ef92b4986a5ac5d4f094ad0919c826a97efe8d8120a96877766eae5828803804a0cae67df9822fd18622aae", + "Expected": "000000000000000000000000000000000a3d66cf87b1ce8c5683d71a6de4bf829d094041240f56d9071aa84ff189a06940e8e1935127e23a970c78ca73c28bf6000000000000000000000000000000000b2adda87740873c0c59e3ebde44d33834773f0fe69e2f5e7ede99c4f928978a5caaede7262e45fd22136a394b3f7858", + "Name": "matter_g1_add_19", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000008cefd0fd289d6964a962051c2c2ad98dab178612663548370dd5f007c5264fece368468d3ca8318a381b443c68c4cc7000000000000000000000000000000000708d118d44c1cb5609667fd51df9e58cacce8b65565ef20ad1649a3e1b9453e4fb37af67c95387de008d4c2114e5b95", + "Expected": "0000000000000000000000000000000004b2311897264fe08972d62872d3679225d9880a16f2f3d7dd59412226e5e3f4f2aa8a69d283a2dc5b93e022293f0ee1000000000000000000000000000000000f03e18cef3f9a86e6b842272f2c7ee48d0ad23bfc7f1d5a9a796d88e5d5ac31326db5fe90de8f0690c70ae6e0155039", + "Name": "matter_g1_add_20", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000008e5afc16d909eb9d8bdaaf229ad291f34f7baf5247bbd4cc938278f1349adb4b0f0aacd14799c01d0ca2ed38c937d600000000000000000000000000000000006cf972c64e20403c82fee901c90eaa5547460d57cce2565fd091ff9bc55e24584595c9182298f148882d6949c36c9d5", + "Expected": "000000000000000000000000000000000caf46f480ae2ea8e700f7913c505d5150c4629c9137e917357d2a4ba8a7a1c63b8f6e2978293755952fbed7f0ad8d6d0000000000000000000000000000000002e62e715b72eebbc7c366a2390318f73e69203a9533e72340aab568f65105129ffc9889a8bc00a692494d93688c7ec0", + "Name": "matter_g1_add_21", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0000000000000000000000000000000013a6439e0ec0fabe93f6c772e102b96b1f692971d7181c386f7f8a360daca6e5f99772e1a736f1e72a17148d90b08efe0000000000000000000000000000000010f27477f3171dcf74498e940fc324596ef5ec6792be590028c2963385d84ef8c4bbb12c6eb3f06b1afb6809a2cb0358", + "Expected": "000000000000000000000000000000000dea57d1fc19f994e6bdda9478a400b0ada23aed167bfe7a16ef79b6aa020403a04d554303c0b2a9c5a38f85cf6f3800000000000000000000000000000000000b8d76ccd41ba81a835775185bbf1d6bf94b031d94d5c78b3b97beb24cf246b0c25c4c309e2c06ae9896ed800169eeee", + "Name": "matter_g1_add_22", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff320000000000000000000000000000000005728a219d128bc0a1f851f228e2bf604a72400c393cfb0d3484456b6b28a2c5061198656f0e106bbe257d849be159040000000000000000000000000000000011f6d08baa91fb2c8b36191d5b2318e355f8964cc8112838394ba1ded84b075de58d90452601dcfc9aa8a275cfec695d", + "Expected": "0000000000000000000000000000000012e6d6c518c15cfd3020181ff3f829e29140b3b507b99251cc7f31795128adec817750296bce413bac18b9a80f69ca5000000000000000000000000000000000131ee9b748f6f1eb790adeb9edd0e79d89a9908368f5a6bb82ee0c913061cdfffe75d9ba411a49aa3f9194ee6d4d08a9", + "Name": "matter_g1_add_23", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f200000000000000000000000000000000171696781ba195f330241584e42fb112adf9b8437b54ad17d410892b45c7d334e8734e25862604d1b679097590b8ab0a000000000000000000000000000000001879328fdf0d1fb79afd920e0b0a386828be5b8e0e6024dfeea800ffcb5c65f9044061af26d639d4dcc27bcb5ba1481a", + "Expected": "00000000000000000000000000000000111c416d5bd018a77f3317e3fbf4b03d8e19658f2b810dc9c17863310dfb09e1c4ffdbb7c98951d357f1c3d93c5d0745000000000000000000000000000000000af0a252bff336d5eb3a406778557ef67d91776a9c788be9a76cff7727f519a70fc7809f1a50a58d29185cb9722624fd", + "Name": "matter_g1_add_24", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c2000000000000000000000000000000000231b0d6189a4faad082ce4a69398c1734fcf35d222b7bce22b14571033a1066b049ae3cd3bd6c8cec5bec743955cdd600000000000000000000000000000000037375237fb71536564ea693ab316ae11722aadd7cab12b17b926c8a31bd13c4565619e8c894bffb960e632896856bbe", + "Expected": "000000000000000000000000000000000d2b9c677417f4e9b38af6393718f55a27dbd23c730796c50472bc476ebf52172559b10f6ceb81e644ec2d0a41b3bb01000000000000000000000000000000001697f241ff6eceb05d9ada4be7d7078ecbbffa64dd4fb43ead0692eef270cb7cc31513ee4bf38a1b1154fe008a8b836a", + "Name": "matter_g1_add_25", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000015653d1c5184736cdc78838be953390d12b307d268b394136b917b0462d5e31b8f1b9d96cce8f7a1203c2cae93db6a4000000000000000000000000000000000060efeece033ac711d500c1156e4b6dce3243156170c94bc948fd7beae7b28a31463a44872ca22ca49dc5d4d4dd27d1c", + "Expected": "0000000000000000000000000000000003996050756117eeab27a5e4fa9acdde2a1161d6fbfff2601a1c7329f900e93a29f55a8073f85be8f7c2a4d0323e95cc00000000000000000000000000000000010b195a132c1cba2f1a6a73f2507baa079e9b5cb8894ea78bebc16d4151ee56fe562b16e2741f3ab1e8640cdad83180", + "Name": "matter_g1_add_26", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc0000000000000000000000000000000018adb42928304cbc310a229306a205e7c21cdb31b9e5daf0ff6bb9437acee80cd8cf02b35dab823155d60f8a83fde5cc0000000000000000000000000000000018b57460c81cab43235be79c8c90dcda40fafcaf69e4e767133aee56308a6df07eac71275597dd8ed6607ffb9151ed9a", + "Expected": "0000000000000000000000000000000003c7a7ee3d1b73cf1f0213404363bf3c0de4425ab97d679ed51448e877b7537400f148f14eba588ed241fea34e56d465000000000000000000000000000000000c581b5070e6bb8582b7ee2cd312dfeb5aaf0b0da95cf5a22a505ffba21fc204e26a5e17311d1f47113653ff13349f57", + "Name": "matter_g1_add_27", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da00000000000000000000000000000000001da65df8574a864ab454e5f2fa929405501bb73c3162a600979a1145586079361c89839cc0c5a07f1135c94bf059f9c0000000000000000000000000000000002560df402c0550662a2c4c463ad428ab6e60297fbc42a6484107e397ae016b58494d1c46ac4952027aa8c0896c50be3", + "Expected": "000000000000000000000000000000000d7a539b679e5858271a6f9cf20108410eb5d5d2b1a905e09a8aa20318efbe9175450385d78389f08f836f5634f7a2f0000000000000000000000000000000000fb624e5f6c4c814b7d73eb63b70237c5de7d90d19ac81cac776d86171a8d307d3cc8c56da14f444fe8cf329ab7e63dd", + "Name": "matter_g1_add_28", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e0530000000000000000000000000000000005311c11f4d0bb8542f3b60247c1441656608e5ac5c363f4d62127cecb88800a771767cf23a0e7c45f698ffa5015061f0000000000000000000000000000000018f7f1d23c8b0566a6a1fcb58d3a5c6fd422573840eb04660c3c6ba65762ed1becc756ac6300e9ce4f5bfb962e963419", + "Expected": "0000000000000000000000000000000000849bbc7b0226b18abbcb4c9a9e78dca2f5f75a2cbb983bd95ff3a95b427b1a01fd909ce36384c49eb88ffb8ff77bb000000000000000000000000000000000087d8d28d92305b5313ca533a6b47f454ddce1c2d0fa3574b255128ef0b145fa4158beb07e4f0d50d6b7b90ea8a8ea8a", + "Name": "matter_g1_add_29", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e000000000000000000000000000000000c8e293f730253128399e5c39ab18c3f040b6cd9df10d794a28d2a428a9256ea1a71cf53022bd1be11f501805e0ddda40000000000000000000000000000000003e60c2291be46900930f710969f79f27e76cf710efefc243236428db2fed93719edeeb64ada0edf6346a0411f2a4cb8", + "Expected": "00000000000000000000000000000000191084201608f706ea1f7c51dd5b593dda87b15d2c594b52829db66ce3beab6b30899d1d285bdb9590335949ceda5f050000000000000000000000000000000000d3460622c7f1d849658a20a7ae7b05e5afae1f01e871cad52ef632cc831b0529a3066f7b81248a7728d231e51fc4ad", + "Name": "matter_g1_add_30", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc80000000000000000000000000000000013267db8fdf8f488a2806fead5cffdcbb7b1b4b7681a2b67d322cd7f5985c65d088c70cdc2638e679ed678cae3cc63c80000000000000000000000000000000007757233ad6d38d488c3d9d8252b41e4ab7ee54e4ef4bbf171402df57c14f9977dd3583c6c8f9b5171b368d61f082447", + "Expected": "000000000000000000000000000000000c06fef6639ab7dceb44dc648ca6a7d614739e40e6486ee9fc01ecc55af580d98abc026c630a95878da7b6d5701d755c0000000000000000000000000000000007c9a7f2bc7fa1f65c9e3a1e463eb4e3283e47bb5490938edb12abf6c8f5a9b56d8ce7a81a60df67db8c399a9a1df1d4", + "Name": "matter_g1_add_31", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17000000000000000000000000000000001975bc52669187f27a86096ae6bf2d60178706105d15bce8fe782759f14e449bc97cb1570e87eec5f12214a9ae0e0170000000000000000000000000000000000ca6106d6e6487a3b6f00fc2af769d21cb3b83b5dc03db19e4824fc28fd9b3d9f7a986e79f05c02b3a914ff26c7a78d6", + "Expected": "0000000000000000000000000000000002fbf4fba68ae416b42a99f3b26916dea464d662cebce55f4545481e5ab92d3c40f3e189504b54db4c9cd51ecdd60e8d0000000000000000000000000000000008e81e094c6d4ded718ef63c5edfacb2d258f48ccfa37562950c607299bb2dca18e680a620dff8c72dedc89b4e9d4759", + "Name": "matter_g1_add_32", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f00000000000000000000000000000000109f6168a719add6ea1a14f9dc95345e325d6b0e56da2f4ecff8408536446894069fa61e81bdaebfc96b13b402fad865000000000000000000000000000000001806aa27c576f4c4fa8a6db49d577cd8f257a8450e89b061cbc7773c0b5434f06bacf12b479abf6847f537c4cbefcb46", + "Expected": "0000000000000000000000000000000014e0bd4397b90a3f96240daf835d5fb05da28a64538f4bf42d9e7925a571f831c6e663910aa37dcc265ddd7938d83045000000000000000000000000000000001695d405d4f8ba385ebf4ad25fb3f34c65977217e90d6e5ed5085b3e5b0b143194f82e6c25766d28ad6c63114ca9dcdf", + "Name": "matter_g1_add_33", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af0000000000000000000000000000000019d3623a7866933e2d73214ceb2e56097a1b047db5943c3ecb846890aa02250126e90fc76a729a952cef895bd154cc7d000000000000000000000000000000000e87c376bbd695a356ef72226ac7ef6a550d99e9693d8485770a686e568ae28c038ee201d3f2ea38362046236ade91cd", + "Expected": "000000000000000000000000000000000ffeab47985bd9b3e10ce27c6636bbda336dcf540cd37eccc3faec2adff2d97dd126633bd83a7d3c8c73c3623bdf0ba2000000000000000000000000000000001992eca4b1e924b360d57ca98b543ab496a8b55bd288d23f03bcc1b22f6bc76d95b12f47c3e305812097253c73b876dd", + "Name": "matter_g1_add_34", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c300000000000000000000000000000000163aaecf83d6c77a5d7417e73f5cf9d71a6aedfd194b2f3b53c608d06a228190f4f79ac57b029d77504c72744df4ecc0000000000000000000000000000000000416e6f9ca188d16daa2c28acd6a594f8fcb990eaa26e60ca2a34dfcad7ad76c425b241acedf674d48d298d0df0f824d", + "Expected": "000000000000000000000000000000001812bcb26fa05e0ab5176e703699ab16f5ef8917a33a9626ae6ff20f2a6f4a9d5e2afe3a11f57061cbaa992e1f30477f000000000000000000000000000000000680acf0b632cb48017cb80baa93753d030aa4b49957178d8a10d1d1a27bbdc89ac6811a91868b2c181c5c0b9b6caf86", + "Name": "matter_g1_add_35", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000000aba7362eee717d03ef2d4f0fef2763822115fcc8fb9e2e8243683b6c1cde799ebc78f23812e557de2cc38e2b4a2e56700000000000000000000000000000000170833db69b3f067cf5c4c4690857e6711c9e3fcad91ca7cd045e9d2f38c7b31236960e8718f5dd4c8bfb4de76c6c9b9", + "Expected": "00000000000000000000000000000000196ffe76a4b726fa8dd720cc1cd04c040724cb18ec10915e312eaa90d124100b08f0ce3a7fc888f46914319a3d7581f4000000000000000000000000000000000e2612357059ca6dbb64efb98ef19370560c9e83e2aad7ab2d9015e2444fe4d8c796b5577584aac9f63258beb5ae863c", + "Name": "matter_g1_add_36", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da8257721808000000000000000000000000000000000a98ae36c690f2e3be8100f43678be5a1064390e210328dd23f61f5a496b87398db2798580edeabc6273fb9537fa12880000000000000000000000000000000009aedf77bb969592c6552ae0121a1c74de78ba222b6cd08623c7a34708a12763b5ff7969cf761ccd25adc1b65da0f02d", + "Expected": "00000000000000000000000000000000072334ec8349fc38b99d6dea0b4259c03cd96c1438c90ef0da6321df2495892de031a53c23838ca2b260774fa09b5461000000000000000000000000000000000e4535767c2477c4f87c087540c836eeffcd0c45960841f9c3561a8a5f8e61ab98b183b11192b8e7ea1c9c7717336243", + "Name": "matter_g1_add_37", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e00000000000000000000000000000000015c3c056ec904ce865d073f8f70ef2d4b5adb5b9238deaa5e167d32f45cad4901aa6d87efa2338c633e7853ce4c19185000000000000000000000000000000000a15f1aa6e662f21d7127351a1655821c943c4cf590e3c9e60c9ab968b4a835f87fb8d87eee6331ee4e194e5f1ea91f4", + "Expected": "000000000000000000000000000000000140fb6dcf872d0a3bff3e32a0cb4a7fb7e60ee4fb476bb120c4ce068e169d72e1c167d7fda321280d5855983d5a9af800000000000000000000000000000000108f54a4ec3ba26dd614f4d94c5c82652583906986158ad40ffea54c17703fa4b0bd7806633e1c0318d06e8dc7d41cde", + "Name": "matter_g1_add_38", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb000000000000000000000000000000000307841cb33e0f188103a83334a828fa864cea09c264d5f4343246f64ab244add4610c9ccd64c001816e5074fe84013f000000000000000000000000000000000e15bbeb6fff7f1435097828f5d64c448bbc800f31a5b7428436dcffd68abc92682f2b01744d7c60540e0cd1b57ab5d4", + "Expected": "000000000000000000000000000000000a1b50660ed9120fff1e5c4abb401e4691a09f41780ca188cea4b1c2d77002f08ce28eb1caa41ee3fe73169e3651bb7f00000000000000000000000000000000125439ac3b45c698a98063ab911364bd3c6dd2a69435d00d6edf89fc5566b33038e960a125e5e52141abb605587942fe", + "Name": "matter_g1_add_39", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000013866438b089d39de5a3ca2a624d72c241a54cbdcf5b2a67ebdd2db8373b112a814e74662bd52e37748ffbfc21782a5000000000000000000000000000000000d55454a22d5c2ef82611ef9cb6533e2f08668577764afc5bb9b7dfe32abd5d333147774fb1001dd24889775de57d305", + "Expected": "000000000000000000000000000000000037b4e8846b423335711ac12f91e2419de772216509d6b9deb9c27fd1c1ee5851b3e032bf3bcac3dd8e93f3dce8a91b00000000000000000000000000000000113a1bf4be1103e858c3be282effafd5e2384f4d1073350f7073b0a415ecf9e7a3bfb55c951c0b2c25c6bab35454ecf0", + "Name": "matter_g1_add_40", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f180320000000000000000000000000000000017440fd557df23286da15f9a96bb88cfbc79589b1c157af13baf02c65227dc0a5bdec6f2f300083ff91dae395ed8cb75000000000000000000000000000000000ad09b4290842cc599d346110fdb39ededbb1d651568579564e274465f07b8f77eeaf00fece0c10db69c2125de8ab394", + "Expected": "0000000000000000000000000000000007c158b4e21566742f7e4e39a672bd383e27864505acef4ef8c26f8b0a9db418f9c088b555b8e9eb25acf9859b1207b40000000000000000000000000000000016e06a1ace89f992d582af0de7662ef91c0a98f574306f6f6d0d8d5e80166638d2deef70105cce2e9b20faa9d6315510", + "Name": "matter_g1_add_41", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0000000000000000000000000000000000d7ccc3a4efdfe1a92a88e453933b8216016091f1b9d575faf18a5b3abf90daf077813167a3f4acce7359472dee544bb00000000000000000000000000000000128008c075ab176100e755cbb8de5b9ff0e9a78114f862d26ed030d9c1d1dea1c21ec8ae4d82a84d3ff5ae4c1cd6f339", + "Expected": "000000000000000000000000000000000b84f9de79c748e37797c629cb78b86b4b736b199f161b30147b5dacf6eabe0b54afce40d5dacfe9a8ee8da5ef5b49de0000000000000000000000000000000010277ad094bb9a3b96379b1366dd90125b51a21ebeb4f776a81d9d9c1f37ab58c32a884a26fa32c83783ed0eef42b820", + "Name": "matter_g1_add_42", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e00000000000000000000000000000000008da4a93d5ffcdaa0adc736a59f0c187ae3bf11ecb5e9e6f6aedea976a47757739042200b4c4593c2dd5db555425531000000000000000000000000000000000a6fdb2d4160c6c35223daa6fa10d0b1073de07fe4f2eba28e65ed049ff8d8852ed0538b30759fe7a0d944009ddf9a6f", + "Expected": "000000000000000000000000000000000d740bd1effd8674250618af0358ad0b83bbc787f0264af9c2ada72fa5431be909e82155da1de0211f46fb307e9949f0000000000000000000000000000000000ddf62c91d587a14b64feef07da52c081b40fbbf9a0f2eae8b66022e0850fc94de6a467e7e4f580c7f2c806f6c6ed8cf", + "Name": "matter_g1_add_43", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d790000000000000000000000000000000003258d7931a1d72ab6344c7e96c0dbd435a7909fe68cc679c08ca9b62f7a6a04863082cbcfdbe9a736625d895e4f3bdb0000000000000000000000000000000009ee3e470e2b2cebc955ba3444b7e478f887138e36c13bd68490689122627269ea5e7ce22dd9c69792394a24187103d6", + "Expected": "000000000000000000000000000000000af674691f5d87655f0066188fac5013f31b4169a0181d3feb7ac3beae0d9a3429d4125f099ee344f644a2de8b941f9f00000000000000000000000000000000042a9603b8e4a6c37d59ede3a1398f5f80c5298da66de575a204ee28811d9f7c7c0dd40cef3769bd72a2156b9eb620c8", + "Name": "matter_g1_add_44", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820000000000000000000000000000000001833807f1ced52399305419450355499a63411837ee61ad681559d59561db18511eb1e8ad3161e7fe30016b560d18b8f00000000000000000000000000000000198b11b31586e17964a4a4ccdee85703163d2106481833e71f26327a589bafb43578d08d87f6cb19c7a04b4ca92392bf", + "Expected": "000000000000000000000000000000001081c3359a0fadfe7850ce878182859e3dd77028772da7bcac9f6451ac6455739c22627889673db626bbea70aa3648d50000000000000000000000000000000000f4e8766f976fa49a0b05ef3f06f56d92fe6452ff05c3fac455f9c16efadf1b81a44d2921bed73511dda81d6fc7478e", + "Name": "matter_g1_add_45", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000007dc719ae9e3f1e11d3ed4747a546a7b973ccb1967adb1b3066645a8bde9632bcfa3530e768f088ddbc022b169e67cbf000000000000000000000000000000000bbf9cf884b19c84045da1cead7dcd9fdbf39d764ff1ad60d83ed1e4fd0ce0554f0fb618203952cf02a7c4ba466c66b8", + "Expected": "000000000000000000000000000000000f60d66fd1ed5eb04f9619d6458c522cc49f5ace111aff2b61903b112559972f80ac615591463abf2b944c4f99d4c03e000000000000000000000000000000000001a1abfa869be2cda6bd7e05454a8735e1b638db7e1b3715708539c2d14ade53069c7e68b36d3b08cff80837028b7d", + "Name": "matter_g1_add_46", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd0000000000000000000000000000000014b78c66c4acecdd913ba73cc4ab573c64b404a9494d29d4a2ba02393d9b8fdaba47bb7e76d32586df3a00e03ae2896700000000000000000000000000000000025c371cd8b72592a45dc521336a891202c5f96954812b1095ba2ea6bb11aad7b6941a44d68fe9b44e4e5fd06bd541d4", + "Expected": "0000000000000000000000000000000015b164c854a2277658f5d08e04887d896a082c6c20895c8809ed4b349da8492d6fa0333ace6059a1f0d37e92ae9bad30000000000000000000000000000000001510d176ddba09ab60bb452188c2705ef154f449bed26abf0255897673a625637b5761355b17676748f67844a61d4e9f", + "Name": "matter_g1_add_47", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe900000000000000000000000000000000104ee0990ba4194916f670f44e254200971b67a18ed45b25c17be49df66e4f9b934bac8c1552ecc25bdaa3af55952076000000000000000000000000000000000591094d9d89afe025ca1832d7f3e60444f83e72403a434b42216b6c4213980d29e4ef0c64ae497006de550c1faa9425", + "Expected": "0000000000000000000000000000000006db0cc24ffec8aa11aecc43e9b76a418daac51d51f3de437090c1bcaabace19f7f8b5ceb6277d6b32b7f3b239a90c4700000000000000000000000000000000069e01f60ca7468c6b9a247c79d18cf3d88bf5d1d62c76abf9237408edeba05dea744205ac5b501920f519bb847bb711", + "Name": "matter_g1_add_48", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a0000000000000000000000000000000004840d028d0c0f056aeb37b7a8505325081e9822ef26046f2da72f2155c20987dd51f4b5577c5395e24288b71d2ce5140000000000000000000000000000000015f231a233e997633c1d6492e0df358fb658ae29d0f53928c8a0578484c899a699178ca3223772210063aa08991c3fff", + "Expected": "000000000000000000000000000000000fa72bf2d7d564cc4982b9f2cdca743d2ac14f0f1be4218dbafb8b93a9277e55273487a5d2857fd3f731ac4ee469a6a1000000000000000000000000000000000fce44f886453c6ca5ebde9af41d2be92d1126e9897d72978a179dd7eebeed6242b6e9718604ab0c9369529a0426a575", + "Name": "matter_g1_add_49", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece1738220000000000000000000000000000000004877b97faa1d05d61ab65001110bf190d442cabcd6d4d1b9c1f0e513309aebd278f84a80354dfdef875769d00ec2c7500000000000000000000000000000000187066cccb5008bc2ffd0bcd1b227a5a0fe0cd4984316ba3cfd5113c4632a04c56cbda8d48993bd0dd50e9b7ce2b7ee9", + "Expected": "0000000000000000000000000000000019ecd38afacc6b281b2515270157328e18039d51574bae0f7e0ef16c3f6da89f55ddee9e3bbb450ad51fe11edfd9f18d00000000000000000000000000000000088a5e292761bbf7a914a9f723de099035e91bd3c1fe9cd50728a4ceaa4fd3953683f30aa8e70ba0eb23919092aa9e22", + "Name": "matter_g1_add_50", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000001881f5aba0603b0a256e03e5dc507598dd63682ce80a29e0fa141b2afdadf6168e98221e4ee45d378cee0416baaadc49000000000000000000000000000000000070d255101319dd3a0f8ca3a0856188428c09de15475d6b70d70a405e45ab379a5b1f2e55f84bd7fe5dd12aeedce670", + "Expected": "0000000000000000000000000000000011ccd455d5e3eba94567a17bcd777559b4ff1afa66fd6f05f99c69937404290a2f1c83cfd6c2c25886ebff4934332c0e0000000000000000000000000000000010920aa3d5974df25530610ef466adce3d51fd6a508d4b1111739c586dfd7ba9040836e075fd812fe111d92f25b67f51", + "Name": "matter_g1_add_51", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf000000000000000000000000000000000b53e5339f25bcd31afd091362874b5042c0b762ed7425341331630addbc4dccc299936e1acdf89823c36867d46c6f28000000000000000000000000000000000fc3c6b522268511dd52826dd1aee707413d925ee51aeb0e5d69c0e3eb697fabbc14783b5007e240cc0c53c299a40ada", + "Expected": "00000000000000000000000000000000060773b9b8f3babdba3db27089b7be3e6e287a635dbae19576039d34ae18a0e6413278bfa280570f6329ae05cdb693fd00000000000000000000000000000000075fb9527f99a8c8db41e67baaf1deafffd2c134badb1b3478a26b5501b31dca858fad6f0d52f412d5631ecfa72eece4", + "Name": "matter_g1_add_52", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000001693f4ebab3fed548784264196fb01cf55311399f47cdad74a9543bda5d1ca682a00ee04bb0b3954d5a0f00ceef97a750000000000000000000000000000000017f4019c23bd68e84d889857c417b17aa96c780fec3c1ed6ca75100cc70c97a8bb8272ad4c6de896d76dc2a1b09c7a61", + "Expected": "000000000000000000000000000000000a3ea8afdc83794f18f9a9427bcd60a355196925d38fdf74ab09d4a08279647b2da6f1fbe30948a785497d6c6dddc2a9000000000000000000000000000000001263c88f1ca3e574cafac21641432d45ee01e1b05eba95716565922abe28c7f0fb004c255afcbfa10cf7959bbe6b00d7", + "Name": "matter_g1_add_53", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000005d5602e05499a435effff3812744b582b0cd7c68f1c88faa3c268515c8b14f3c041b8ae322fe526b2406e7c25d84e61000000000000000000000000000000001038eaf49e74e19111e4456ebba01dc4d22c7e23a303d5dec821da832e90a1b07b1a6b8034137f1bfdcddeb58053a170", + "Expected": "0000000000000000000000000000000019258ea5023ce73343dcd201ec9be68ec1ee1cb4e5b9964309d801c2bc523343c8ebc4f8393a403c7881e5928f29db14000000000000000000000000000000001423bf52daefb432162ce2bd9ef78b256ff3b24d0a84766b87119489fd56ecf6156b2884c8a7e1220e493469723cd7f8", + "Name": "matter_g1_add_54", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000002626f28d421d9d1c28f5e1eb5a51ada9610dbdd62cd33c4078d2fdfc18dbd092e2847cf705ba5fcd8c1a60c1cc34a3b0000000000000000000000000000000001f7b8cfdb7e406c920f5fdecae45fb4be736f209480ccb455f972c6b1a1aebdd5ba116903c46ded72ce37cd8836e871", + "Expected": "00000000000000000000000000000000081d674f5b9c7c64673c39fe33f4f3d77271e826dcb4dfd2591062e47c931237e8539ef9c886c9e112eccc50da4f63fd00000000000000000000000000000000141b700695839110ed4ced5f8a3f4fd64a8086805358ab4a5abd2705592e616cd95ff01271212ca9014dcb68d8157ba0", + "Name": "matter_g1_add_55", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df91000000000000000000000000000000000259e307eacb1bc45a13811b02a7aeaaf4dc2bb405dcd88069bb6ec1c08a78905516169bd3440a36921764df0ef3a85b000000000000000000000000000000001263372b675124f6cc19ca16842ba069c5697dbf57730875fe72c864a81189d7d16fe126b5d24953a0524f96dbac5183", + "Expected": "000000000000000000000000000000001908aa3a640817e31a4213156fbd4fd39ab39eb931091670a0e06399def71a689e67286f90d38ce9f97cb85f6488d9c8000000000000000000000000000000000764e46b6b82aa2f8862d28e9d543a751a9de855645377b9633cc098c2110ec6ed4fd30f0044ea5868c93f950f6cfd24", + "Name": "matter_g1_add_56", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000a138203c916cb8425663db3bbff37f239a5745be885784b8e035a4f40c47954c48873f6d5aa06d579e213282fe789fa0000000000000000000000000000000016897b8adbc3a3a0dccd809f7311ba1f84f76e218c58af243c0aa29a1bb150ed719191d1ced802d4372e717c1c97570a", + "Expected": "0000000000000000000000000000000004ad79769fd10081ebaaed9e2131de5d8738d9ef143b6d0fa6e106bd82cfd53bbc9fab08c422aa03d03896a0fb2460d0000000000000000000000000000000000bb79356c2d477dfbcb1b0e417df7cb79affbe151c1f03fa60b1372d7d82fd53b2160afdd88be1bf0e9dc99596366055", + "Name": "matter_g1_add_57", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29460000000000000000000000000000000019f60f2cf585bdbc36947f760a15fa16c54cf46435cc5707def410202a3f4fa61b577ab2481e058b0345982d3e3d1666000000000000000000000000000000000a70b7bbc55e1f3e11e9eb7efd79d4e396742de48d911ddff8dd0a7cf10422423d5e68021948e1448e92c2e07c194776", + "Expected": "000000000000000000000000000000000a87e7e115ccdf3c2c1a2716491d449c3f8329e73d264088f4af444d43cf05f8be0410da273ce7eeb32969830195b7e70000000000000000000000000000000010a973d6e4bd85105bf311eb0dcfdc0a5d38dba1c099206b60f2e2df4791fd58846bf19d83769506e1561212920b4895", + "Name": "matter_g1_add_58", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000109bd6e0636a7f96ffe2ce8e109171efaacfcd60189c7050259ddedd15dd257e11f2585bbd84e4a3f4d8fc5fbc0289cf0000000000000000000000000000000019b420d778da53aed81b48f2c9b9eb399e771edd5e124a41577452b409ca2503e2798cd25d791f489352fc7b7268ae23", + "Expected": "00000000000000000000000000000000162bd29f2de10002c1c446bd9583e89751fb91703ad564e7951d41673e28d214729aa9b4b9875c397989df197c912d5f0000000000000000000000000000000004d393181871c93714afab6c33c16f68ec391fbfcad606ac65cc1d070949c099e21f710e2fe0dd4e4f50f99ea2167a7e", + "Name": "matter_g1_add_59", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000012bb529b45ad7875784b62a7281d025002f15e7f86cc33555e7472df60da2cb15d37c8bf628142818c0711ee9047fb4d000000000000000000000000000000000baa801623312d95e2b51ce86373fea516007e468f265d974c2327c1779830db180bed6dbe8a64f0959aad26eaafb8d9", + "Expected": "0000000000000000000000000000000010c4b328d264893099d89ba81b0765d0642bf36b0ac043be090c7b4f7987d21a906228c3c208c4ec5123d577efb0771f0000000000000000000000000000000016d08ce3bf755da7d4bae5f4b06b37845c17a717329c547e941be93325a04e9a5095d3f6e6c6f9ec3b1a740f59d88919", + "Name": "matter_g1_add_60", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000002c9e50f37ff0db2676637be8a6275fce7948ae700df1e9e6a0861a8af942b6032cca2c3be8b8d95d4b4b36171b4b0d400000000000000000000000000000000050f1a9b2416bbda35bac9c8fdd4a91c12e7ee8e035973f79bd35e418fd88fa603761e2b36736c13f1d7a582984bd15e", + "Expected": "000000000000000000000000000000000f798f8d5c21cbce7e9cfcbb708c3800bf5c22773ec5b44590cdbb6f720ccddf05a9f5d5e6a51f704f7c295c291df29f000000000000000000000000000000001483903fde5a968dba6924dfac3933cd39f757e2f89120f4ca9d03aaaf9e18252bdb5c5d3939471666b8a42aeb31b4ed", + "Name": "matter_g1_add_61", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec000000000000000000000000000000000332cdc97c1611c043dac5fd0014cfeaee4879fee3f1ad36cddf43d76162108e2dc71f181407171da0ceec4165bcd9760000000000000000000000000000000015b96a13732a726bad5860446a8f7e3f40458e865229bd924181aa671d16b2df2171669a3faa3977f0ee27920a2c5270", + "Expected": "0000000000000000000000000000000001c762175f885a8d7cb0be11866bd370c97fb50d4277ab15b5531dacd08da0145e037d82be3a46a4ee4116305b807de6000000000000000000000000000000000bb6c4065723eaf84d432c9fde8ce05f80de7fe3baed26cf9d1662939baac9320da69c7fe956acdd085f725178fe1b97", + "Name": "matter_g1_add_62", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c50000000000000000000000000000000003ebca978ea429eedad3a2c782816929724fc7529fbf78ea5738f2ca049aab56c1773f625df2698433d55db7f5fc8ca2000000000000000000000000000000000d2477f57b21ed471a40566f99b7c2d84ce6b82eaf83a6c87a7c21f3242959c8423d4113b7fd8449277b363303bb17b0", + "Expected": "00000000000000000000000000000000071dc0f985703bd8335093779de651b524c02faca5fc967766abd3f6f59176d2046d7a14d18c0b757b8c9802e44ebcd300000000000000000000000000000000154e5cb66be8979ee276e8e0f240557e3f7dc074c497293af589256652da21d66a6e6b00ca5bfa6f89963fbd5bc6cf48", + "Name": "matter_g1_add_63", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001461afe277bf0e1754c12a8aabbe60262758941281f23496c2eeb714f8c01fd3793faf15139ae173be6c3ff5d534d2bc00000000000000000000000000000000148ad14901be55baa302fa166e5d81cc741d67a98a7052618d77294c12aea56e2d04b7e497662debc714096c433e844e", + "Expected": "0000000000000000000000000000000012c4dd169f55dfb5634bc4866f7cbd110648b5392ace6042b5f64aba3278f24085227521b7834864f00d01ec9998dd6800000000000000000000000000000000102d7a495850195424677853da01d70caeb6c0af5270bcfffbc2d4252c0f3680518cd8d2a0a6dbbbc7b52923a5b26562", + "Name": "matter_g1_add_64", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002218b4498c91e0fe66417fe835e03c2896d858a10338e92a461c9d76bcecd66df209771ae02c7dcace119596018f83c000000000000000000000000000000001990233c0bae1c21ba9b0e18e09b03aeb3680539c2b2ef8c9a95a3e94cf6e7c344730bf7a499d0f9f1b77345926fef2d", + "Expected": "0000000000000000000000000000000010c50bd0f5169ebd65ee1f9cd2341fa18dd5254b33d2f7da0c644327677fe99b5d655dd5bfdb705b50d4df9cfce33d1400000000000000000000000000000000088e47ffbbc80c69ec3c5f2abe644a483f62df3e7c17aa2ff025553d1aaf3c884a44506eff069f4c41d622df84bbafa1", + "Name": "matter_g1_add_65", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7000000000000000000000000000000000160e0f540d64a3cedba9cf1e97b727be716bbfa97fbf980686c86e086833dc7a3028758be237de7be488e1c1c368fe100000000000000000000000000000000108250b265bd78f5e52f14ef11515d80af71e4d201389693a5c3ef202cf9d974628421d73666ead30481547582f7abaf", + "Expected": "00000000000000000000000000000000168af33c85ae6e650375ed29b91218198edd9135683f6a1428211acdcbf16bdf86f0a95575e47ee0969587a10fa9f3c90000000000000000000000000000000012d9f5d692c870b3da951b6d07797c186a8ddc89b9f08a1c0b8f0f119f10ca0b155e8df5424cf48900ad3bf09ce6872a", + "Name": "matter_g1_add_66", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d20000000000000000000000000000000002fa19b32a825608ab46b5c681c16ae23ebefd804bb06079059e3f2c7686fe1a74c9406f8581d29ff78f39221d995bfd000000000000000000000000000000000b41ea8a18c64de43301320eaf52d923a1f1d36812c92c6e8b34420eff031e05a037eed47b9fe701fd6a03eb045f2ca7", + "Expected": "000000000000000000000000000000000b99587f721a490b503a973591b2bb76152919269d80347aeba85d2912b864a3f67b868c34aee834ecc8cd82ac1373db0000000000000000000000000000000007767bb0ca3047eee40b83bf14d444e63d98e9fc6c4121bdf04ea7148bcfaf3819b70dcebd9a941134e5c649da8f8d80", + "Name": "matter_g1_add_67", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000002a540b681a6113a54249c0bbb47faf7c79e8da746260f71fbf83e60f18c17e5d6c8a7474badafee646fe74217a86ca4000000000000000000000000000000000fe2db7736129b35dc4958ffd0de7115359857fb9480b03a751c4fceb9ae1b2b05855398badffc517ae52c67f6394e2a", + "Expected": "000000000000000000000000000000000bc719a8397a035fc3587d32d7ef4b4cfd63d4a5619ab78301d59659208f86df9e247e5d12650acc51a3bca3827063a900000000000000000000000000000000150d5519380a65b1909b0d84da374484675d99b00b254d03e423e634a012b286e3fe074e9b0a7bb24ff52d327249a01b", + "Name": "matter_g1_add_68", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787900000000000000000000000000000000019d917eb431ce0c066f80742fe7b48f5e008cffa55ee5d02a2a585cc7a105a32bbf47bdff44f8a855ade38184a8279e0000000000000000000000000000000012ee762e29d91a4fc70bc7a2fb296a1dcdd05c90368286cca352b3d5fffc76e3b838e14ea005773c461075beddf414d8", + "Expected": "0000000000000000000000000000000008197403ab10f32d873974c937ef4c27fbdb0f505c4df8ac96504705d4851cf951fb0263335e477063884527b21edf160000000000000000000000000000000005396f1affa20ca8530b519a4d5d400969f0c8c8731ecc0944e8086388e89a7ff7c16d9a2a90780972c4762b88a0f0af", + "Name": "matter_g1_add_69", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000d280fe0b8297311751de20adf5e2d9e97f0c1bfe0cd430514cfddbafd5cdcb8c61bd8af4176cc3394f51f2de64b152400000000000000000000000000000000039f511e890187f28c7a0b2bd695ae665e89b0544c325a44b9109da52cc6908d81e1a27163a353ab275d683860c2e007", + "Expected": "0000000000000000000000000000000002baea63055f72646189bdd133153dd83026f95afad5ce2cffbee3f74c8d47d5480094b2b58b0936c78aa33cd9a8f72f0000000000000000000000000000000013e600456a2d76f5a760059e0ba987b881c6bc10d6161f388d7a9d8b2031921054edfec46afbd80b1364d8e8f6a5a7a2", + "Name": "matter_g1_add_70", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000015bad24d12b5d68558e961a17dbc3e1686e1b918e6192ebe6f3f71c925177e61d0162e018ac81126099effa0cadfa185000000000000000000000000000000000de73182569184b3d79dcfa8c27f46ec7a31fe8a3fd73fe26eec37a088461192bdbcf4d4b37b33b6177d6fde015d1631", + "Expected": "000000000000000000000000000000000ced641c930387432d512861eefbf2d6131017154f99a0d3d24da880dfd2aaae91c2d9634053fab8b85fc11a7884d30600000000000000000000000000000000122071c0e87fae5031c850dccc4777c3ec9d8463bbc4ed84364d4261bc9d38f696a4320d53eea926a75ed9fcc9789a07", + "Name": "matter_g1_add_71", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b256730000000000000000000000000000000015cdf7dafedce64aba34e1f18c57b28f297629c07ee96b732029b545cf5ea6afdf926daa6a48d1250c67aa2a8b797d370000000000000000000000000000000004867352f86267dbe8e32806e4ed02f1487e036051068f8e06d02e8dea6d3773b422e065d2db27c89ea69246d0185351", + "Expected": "000000000000000000000000000000000e2c633351d627a075acd1e373bec96ba41b047f0307201f4b7c9978c1a72243d0b18113604cc421b8f66d76ec9b1360000000000000000000000000000000000844e258d602bf9aaa35ce46c4c91c80dd9337053d8ab22c1163a0571fcd1488a2ef57476e2b66dd9c26963b28284d11", + "Name": "matter_g1_add_72", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000077eb801bcde78e9dd73b58d2429a907ea0f5600a8005093d471be373bba23ea70bf828c766ccced6a46db84b440053f00000000000000000000000000000000101af9df2939089d72e42fe2dc3de3e32be8f4526a2263ebd872d0080ed4a152107bb3d2f56176bf72d5ae8bd0c30a3f", + "Expected": "0000000000000000000000000000000010205c6be10a5fc5390b0e5ae47a8a822c8e9a7a96f113d081cde477ec0de7bf0e8385e61780b2335e4297edb35bcc6d000000000000000000000000000000001796af180463ed70cf330791c8201ee3f0fe52993f64819291bda33017285fcc3a515669b3d48a411276c849fa021f6f", + "Name": "matter_g1_add_73", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0000000000000000000000000000000019b09bb7dddd11c5d0e304dac120b920601dd3a3505e478c88850cc701c17eb02aa7bfb20e4017a62fc4fb544d4f9e8f00000000000000000000000000000000048ad536cf89576d4cce83ef065bc16c47f1a28ae27bd71d30d8f2177a9c6f8b2ed0cdf872ead71bc5a1252bccb4a7e0", + "Expected": "000000000000000000000000000000000fb047098a1996a625cd19021f81ea79895e038756878d8772aaee9b6bbb66930e474dcc04579ad58f4877b742a890900000000000000000000000000000000017da74a4caefc55794a36eda7938371f42265cc1f2d87d41883152db82873daeb59642e8e663afddd4f24536a1f52b3f", + "Name": "matter_g1_add_74", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa30000000000000000000000000000000005f84f9afa2a4a80ea1be03770cb26ac94bec65cf9cb3412a07683df41bb267c2b561b744b34779635218527484633e30000000000000000000000000000000013ce1d1764961d1b0dff236c1f64eabec2ce5a8526edf6b0bccb9ea412e5a91880db24510435cf297fcc1b774b318b65", + "Expected": "000000000000000000000000000000000f4ca788dc52b7c8c0cb3419ab62c26db9fb434321fc6830837333c2bb53b9f31138eecccc3c33461297f99a810e24ad0000000000000000000000000000000006785d4f9cdf42264c00fdc4452883b9050eb56e2f6e46c7b8fc8d937dfe4d3ad5072d969a47c4811b36d3887256d0b9", + "Name": "matter_g1_add_75", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000f0dd7a15dfc39dc2df47cf09761498b0b363157d8443356e768567f5a6d5913c2a67f12d93df2dcf50756bb686836b100000000000000000000000000000000055914dbda5b115222e738d94fbd430440c99bcc6d2c6cf7225c77756ffadf765b2d83447d395e876b5f6134563ed914", + "Expected": "000000000000000000000000000000000ac0f0f62202d09cede55ca77b7344b46fd831b41015eb357cac07f0fa49c2564c2e9d5c591630226677446a9100757c000000000000000000000000000000000ca21d0128ef933fc1a48c1b4967f56912513e63a416d86ad40c0a4590b2edf88e4e8a286338b8b176d8b341ea480277", + "Name": "matter_g1_add_76", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb211000000000000000000000000000000000a6ff5f01a97c0f3c89ac0a460861dc9040f00693bfae22d81ea9a46b6c570436f0688ed0deef5cdcc5e2142f195b5c000000000000000000000000000000000193a17880edffe5b2ebedf0dc25e479cac3b136db9b6b24009ea0a9ca526d6dd9714d10d64c999d4334baa081b9f2fbe", + "Expected": "000000000000000000000000000000000b728d4ae4b45fae9a9e242524e95e44f175356726da50f46236f690eec17fdd5edce5df1253383378dc8f9c1fee98ae00000000000000000000000000000000131d28a5eab968c45ddc86b82f220dcdeab7c009c7c61986ee4e55045c024e1bcbe76a4e35000b5699ccec5858ba427e", + "Name": "matter_g1_add_77", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a000000000000000000000000000000000b35fcf625cde78fba1b70904acb97d7eb449d968e8013855d44292e9c3b0df3cfbcace6f292ec3c7717e25490bb4c67000000000000000000000000000000000af57abd87df55034c32dbe68bd1c0b47139fc2c3a8887b7c151e57b57c9002070337c8dcb2ce2687f9f007d48dd68c1", + "Expected": "00000000000000000000000000000000178a19966b5b0fa70c138be7f5ea51d5399c7b8dcc5171cbef82ecb1451aeccbd1ed29170a27f404ebf6daa2ec99bd69000000000000000000000000000000000b1b748494806175030f6b5e2977c58982bd6ec6662d69237f0521351653c772a40035f2504ac8949fb448a901379fd6", + "Name": "matter_g1_add_78", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b00000000000000000000000000000000177a51fcc81580ccb7a8873fa93eaf860ca8fedde13cdf3eb53f11e66a1c1e934b82ee9251f711c5c479f33a22770c47000000000000000000000000000000000a0edc9a58f4bb414aa0aeec7bfa6076fb62bdbaee987192c18855adf4e813e7103b943e1dddc24754acfa90600a5750", + "Expected": "0000000000000000000000000000000019195049a2d457709e284c84c72a211224efc4d7d46d25c9a537eea94149b06506df02a2a4e0a6428263e9605eaaacb500000000000000000000000000000000061139f9a70ce7cd87ed3a701163bde247382295f557b47a3a0a880d2780f015e8ac753eb3243f9ad138f92c3a2257c5", + "Name": "matter_g1_add_79", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d000000000000000000000000000000001552982822e0b64a6204b27da0e192873bb5bd2997784ff0b6ed53801b402501a665c17f0a379fd946ab1adfae43c6af000000000000000000000000000000000938359655fe135dd2a390f83e27273feb68387ba94f2b6f7c15389f8272d64231ebe9c8271de90ff2358d935359ba85", + "Expected": "00000000000000000000000000000000168f958a40e85341d90012e134976d1a5839e807948410cc0c81a50961552c052bb784c50da4c734f6aa583777c22b28000000000000000000000000000000000d26998bac6ec11bc5fcf6fe7262c984d6500cd5b21af979048b940e20054f8d759f8a011f3e09d01d10f9cf8ab150e1", + "Name": "matter_g1_add_80", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d50000000000000000000000000000000000d94885dcc21b0b98821b6861a4d094e9eb5d5adcf7ca4275c5b759abbf9a9910f3b38073183d54a0569ecbbc1e9826400000000000000000000000000000000034a54b4bbb3f128608a866f5f5c554cf6ad7899f6650ca663a5bd5f1a3e4471e35a2440644c0e4e0a56080936b46d12", + "Expected": "000000000000000000000000000000000d4734ab1bbcf9e30cf142a7aa9e8cde1b3c88d92397b8d7d48c7a7402561feee58a810abf67776e1890489efe7f8ec20000000000000000000000000000000005be9e4af0c0c183c43601339f162345f7c013f5941167cd925057e91c4641e19091a20123a36f2e803142833c0bc1ef", + "Name": "matter_g1_add_81", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000014f16cbb17e7f63284d8a75968a4c8fc8ee7f37233ed656d696477c507c23e7c7eaf54001f44c93deb14c298aa6f94c00000000000000000000000000000000169bde83e861889c50b2138c76531a5866235d515a6fee4da7aaf8e8b903f2848a9fe7bbd55eac7f1c58ce3a88e7249d", + "Expected": "000000000000000000000000000000001400f774b2d932c6b990da6e1b3493685e8f51d429e0c53e9af1b4a2d3876781b790bca4a1bc28ce0240ea21be24a2350000000000000000000000000000000004993fcf5723b7e02095d4ba73ff3194bbe36027bc9099b57084c91c7e7d50b76331bfb06d3c678d3e401bc3f7fcc577", + "Name": "matter_g1_add_82", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000009acc4b4678b4b645fde47d1b75a5dda8caf6696ad2bf312dd5c12d7f3ab50b95152f5fe59842650c8a1a785f345c3ab000000000000000000000000000000000b672989004fe54f4d645e40cd29a21418151134fd2b90a68185040ceff141ced7f7ece1fdd9137c32589fa04b105a0e", + "Expected": "000000000000000000000000000000000fcb0ab180a69b0a230d9dba98099fdce4969f82fc7e7ad93352a7c8dd448bb0ba9c7d62f53d5dc80506bc36190d9bc700000000000000000000000000000000047b7306f4a53c21d42993c50f2365486d02dac495f2dee4f8971a4af308396fce6c90f3cfde857bf7a2c6bf5d0d8aa7", + "Name": "matter_g1_add_83", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6000000000000000000000000000000000198e12ade128447a240e03e024183c401d605cab1ed81f0f5bb7bc4c7cc9c889a2a01f59c0e37a0767a927719e5a95d000000000000000000000000000000001946e39fee9b76ce552108b339b9b24d11e43d3275ac19d2d4bc745c409bdc3f7c473a60c4d3a4d2cc3b598ae0d66880", + "Expected": "00000000000000000000000000000000050b45f896fa40099cda8b1f20ab88644915c16f926589cd709e00149b12922347fa7122175424cd44e8875f217b9ad7000000000000000000000000000000001122b7e9b1509efe5616368b14085bdd36fb7adb85cd5a7f23e327548986f5298c045a602b6ee1265d53a4432a4a3c0e", + "Name": "matter_g1_add_84", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac47388620000000000000000000000000000000009c48aa2681b3005b24075bb3a122ac100cbaca872f761f4398edaba9dd9da6d04d4a4925028297dfe5f77c2b0b5c821000000000000000000000000000000000ea95c646fb68aa458e69c267a6ca640a6a24d40bdca0161246e4521d13c46facfc1ac86dfc0a804cfa6665cebeec822", + "Expected": "0000000000000000000000000000000005325a499aec678ada9eb673d366fe0475e885d5188e2fb687a96949e8f782852fba962197976b868ec083c512bfb66b000000000000000000000000000000000c4d6fcacc8d82401882bee355b37930d83e3cea2e4a7bc133e65a3e0af919b25fc3f30c333873da9406845ce42dbb87", + "Name": "matter_g1_add_85", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae80000000000000000000000000000000008e8799a6cc0339e94e861692c81eee53e8a7b326523d5344b416bfbce04290585ef56018834cfd93d234bfa2943369f000000000000000000000000000000000fa1b01aab0878adad693ec769fb68640931c355b3802c51d4a3772300be5b16ceecdc8328a229b3b9f3639170db96f8", + "Expected": "000000000000000000000000000000000685ec14da61c48bcb697966aca9e27601db43f0fb1f32e026fb33738eecfbb7012aa1ca3acf36a21fa846730245add70000000000000000000000000000000003fc52a1c3342b12271bbc178545bb20e96e8f1fde673e51f3d27ab5cb42e60aca49c6077e0f687be59b2d25cda9718e", + "Name": "matter_g1_add_86", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000bb3a76287fb98fe668cb0a5de603c768340ee6b7f9f686a22da3a86926d8734d2c565c41f94f08fa3ef0e665f4ccb520000000000000000000000000000000016c02dbfb307c96d5b9c144672fe62f3e9cd78991844f246945ee484cbdef2a4c1b001a017cafb3acc57b35f7c08dc44", + "Expected": "00000000000000000000000000000000021796fd6ef624eed7049b8a5c50415cc86104b2367f2966eb3a9f5b7c4833b9470ef558457426f87756d526d94d8dfe000000000000000000000000000000000f492dca3f0a89102b503d7a7d5b197946348e195954d23b8ab9ab7704b3bccecaa2123b8386662f95cd4cfdbbb7a64d", + "Name": "matter_g1_add_87", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f00000000000000000000000000000000127420ff97df415e336cf3e24c39c161fad630c45c7ccef80f1831c4f5ed54da12f2c49a161e72bc70285fa0498e46d00000000000000000000000000000000013e605c21014f72364f8bff392ce64a10078ea537237fa282d5dd252ba1677b84b8c15d7925e54a4ab36f1feb13d3064", + "Expected": "000000000000000000000000000000000ae916770455b0a63717e81802f5a7fcfbcc3e260b7adeca02a61a520c338d495eea29c4f070fd6efc1b8d23eb285e4c00000000000000000000000000000000134784e092744df573ba78f7d6f3cf1ed19491a0fc7ddfa02d3ca043bcf102fd40c33ac44b03a947308e3cc7af41c2df", + "Name": "matter_g1_add_88", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab40000000000000000000000000000000016f41e8b098839944adc12481e5f965657a4faedd4f4cdea51a9597a6a0356989e791a686d3d2ee6232ab93683259c6b000000000000000000000000000000000d27b4a56b2cc2216e61eb41061f9a586a704652704906f7fe0eab869ba00d34205ea66f7a02d337d08b916598494e52", + "Expected": "0000000000000000000000000000000012842c9d7f4309f6e40124a071d317f5597de419db0d5a8e5324a517f7b61dfdeea2fb4503ad7cdd8deb8aaa5c412554000000000000000000000000000000000ace4d9f98ee6e8a4416ef14d64f26dc49e102e69eced46ef829a352e58e8c1a7e1f083e3f4fc07f24ccd1685dedf215", + "Name": "matter_g1_add_89", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000019e7c8d182e3b674dfa21539613f7de5d4872d4f4732307a5c6d95ada7e81a01bc25bda34e0b46634e0b0b32cd47e8ec0000000000000000000000000000000008149237de73ab46d5c20dfd85b07f593c0caf2e2e364335450e3ebb478a9f6b9ac0af89174dffd92eda2783a5271f01", + "Expected": "000000000000000000000000000000000875289fdaead079a283aafe4de7035c88662642b6bba389b17583f8e3b5801dada6e46bd897af961997665e6ed4a55700000000000000000000000000000000050a6b9c1db35865df0a042d27a042ff4b8d3bec2fba6a3a28a71c5a574620dc05cda0e70932ce9b8966e4592220c147", + "Name": "matter_g1_add_90", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6000000000000000000000000000000000c0f33f2d76366af661d6fa58a8b5aab207d35ce03899e495f7ddccedf201d9816f270468b207413a2ca70380c798fc60000000000000000000000000000000002a7dc7e2b163e65cadf93b5d682982288c8f36d08b1db8e0b1cb40cd3c7231f3f1672da42b4679f35db2076a8de5b42", + "Expected": "0000000000000000000000000000000019ea92820dcd442358db359146797aa82beff6154946b1ea14dccae05e8252b776b817dc044a20764e3514cd22799c0b000000000000000000000000000000000ed929fef2cb11e8b6b9b5d52bfde82080eda747f0c82f33b9cb87019476f0c128e6b918a4486172dee2884ba538ae5d", + "Name": "matter_g1_add_91", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000118fb45274a6b0ca9fe2654821e3b30caa46444f7c64b1921cf16dfd56a43916947d4fb6968d718a59a30ed38d65ce3000000000000000000000000000000000110e8e73e640bbea6927cd770baaf887c8e0e0c58260bca489c39b6dd7a24ab8c0c0a2495133d8ff8c7afb9790b37faa", + "Expected": "0000000000000000000000000000000009452bd0a167683e30c673ffd4e750c66a81edf309a8d2d6dd915c358b30b0ffc001c4165b1b17bf157a0f966bfd91d00000000000000000000000000000000015df0b1ee359dd3e35a7b2c33edbb8e92b18804ae3359a369c6a529f5561298e6be9a3498c9477f33353124af7e91968", + "Name": "matter_g1_add_92", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000005dcb54cdf9635db275540c16307fc9f07b4ca5cd91e3977e4b95b58e8103e40ed9fa74752b2a43d95b6acb6f5fcbf440000000000000000000000000000000007ef8457752a47864ef2698176a53990e4822421ecf83b2716251e3ce69151ab2767d4a6611a0a6e0e40a57164ffb94e", + "Expected": "0000000000000000000000000000000011f1ac702a06699dd64b63ebdd8b5381578f63b603c63c3a47413fe764af239ab7024712320f3ea3daefa6bd3cd3dfe9000000000000000000000000000000000918bb83a22b4fc66247e007c17155c4c2ec6326131c10fe04a5f9b82ddeca3d21c7c397a70a3949fda4d766540c85ff", + "Name": "matter_g1_add_93", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35850000000000000000000000000000000006d3335e092616363e94436bb68be89667c706564ba687f4a3494fcf7da62fd9ad8ae68cb76524926c261983711a14ad000000000000000000000000000000000f085a3d013592c402a380e2e8d9019864a775e7b8e8b94603c8cc1eb1def1e91075fd5675f76534397e2a7d76c2331e", + "Expected": "000000000000000000000000000000000344951ccb5e60d1838f7793fcf8b765f5f252b69e1cfdb4bd3c20692c8ffa01afbda6950974a65f6ac74afb9da5942e0000000000000000000000000000000014f5f0e6b99a04d1c5c2adf96c53dd41f8c01aab8db4f0e6d7fc5eab27f6c03c429632db4e1c21467c09d8a54066a4d3", + "Name": "matter_g1_add_94", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b0000000000000000000000000000000019e2ed6e9757e2339d013078fac91c966045f7a1416a56135d75e603c2021a8bebf4acbf6c0d5ba911f66510e9a7ad1a0000000000000000000000000000000008b8585444ffb3bd4fb6ee23e8128142aa72fd574a506151a0eea8979cbd694e03897caba63771b0490d46063bc5bb57", + "Expected": "000000000000000000000000000000000a449fb0da911c544887b24860bc5fcaaf054041cc80f16bbb44c796520bee454d0d06f84fd5aa179a44fd4fac9f144a000000000000000000000000000000000fca81401349089caaef9156a86c64271c77235c9efd136dcfad9894450b076cb3dd1a05bfa1e62ef904435eee5d2250", + "Name": "matter_g1_add_95", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65000000000000000000000000000000000f4a256b4288386545957a3ba28278c0ce69a8a412febfed1f952ca13e673822bacb6b7751ea75893b680ea363aab66400000000000000000000000000000000152379d006e74798199f83b0c6c22a98440ef653d7f0a8c5e3026bcdabec8be59a3cc291ba05860bd0639c5c5f5bee26", + "Expected": "000000000000000000000000000000000c427721953e139d4f12ad2a3f8f91a4caa49875a87001b619c8a6e909a7da8ddd9dd026bf56d5f85d49fd17527106a800000000000000000000000000000000018add2816914ef51a289e707ba0224fcf0b7bcfa4001487e90dbdce53f1b596e1f5872de32fcee6f63bce4484ccbef7", + "Name": "matter_g1_add_96", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d220000000000000000000000000000000012dae9aee13ed6ad52fe664bf7d2d0a1f134f0951d0d7ce5184e223bde164f6860967f9aaaa44fa6654d77d026c52d2a000000000000000000000000000000000f71889d64ec2f7da7319994883eb8bd1c753e6cdd3495036b630c35f07118a1bc10568c411ecbdf468a9cdaa9b4811b", + "Expected": "000000000000000000000000000000000275b8efb3a3e43e2a24d0cda238154520f0a2b265f168bfc502b9cd4a07b930756961ae7e4fe3f01a5473d36ce3356200000000000000000000000000000000113403d5a968f01ba127dd8ef6c8d7b783a10d039a6b69c617032eba7122e9297f3ce2360c829ae64fdc9794695bf173", + "Name": "matter_g1_add_97", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe54850000000000000000000000000000000004e9dd69012ab596b5d3f1f8e4593b448685fcec4ab3394008178b137b762ddf9150cbb8dbb74c8af45bd8baab9a6c4f000000000000000000000000000000001132b66a2127885774062732127951f051c9c3c9b5aba02406e3f3cd4ecfe2dbf6614ebaca3bfe9efbe4f6e5b15ba0f5", + "Expected": "000000000000000000000000000000000594c808954bb930bd038806500c9e3fd6460a83554e945baeeec2354a3805f046c76aea62c249080f16ae8e70f8fa6b00000000000000000000000000000000046924a32fb3f2df9a52615e45eeea2fa3ac0e2ccd38458194ada6b4d993ecdc0f441e41d0ea37599254a06aef68b9ae", + "Name": "matter_g1_add_98", + "Gas": 500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b50000000000000000000000000000000017a81b957a12adf474a2913e8636f169ea9cd10be62c16b88f95f5caf661f158a032a9f7d249fdf2765caa1564bed0570000000000000000000000000000000017fbf2abc62dc2678b65d509e19c9c9c5d961c72565649a078da8dff98be6236ef314e9ff8022f639ff565353345c230", + "Expected": "00000000000000000000000000000000002c8bc5f39b2c9fea01372429e92a9c945fad152da67174f4e478fdead734d50f6e2da867c235f1f2f11bdfee67d2a7000000000000000000000000000000000c1dd27aad9f5d48c4824da3071daedf0c7a0e2a0b0ed39c50c9d25e61334a9c96765e049542ccaa00e0eccb316eec08", + "Name": "matter_g1_add_99", + "Gas": 500, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG1Mul.json b/core/vm/testdata/precompiles/blsG1Mul.json new file mode 100644 index 0000000..0e166a2 --- /dev/null +++ b/core/vm/testdata/precompiles/blsG1Mul.json @@ -0,0 +1,730 @@ +[ + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1mul_(0*g1=inf)", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1mul_(x*inf=inf)", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1mul_(1*g1=g1)", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000011", + "Expected": "000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436", + "Name": "bls_g1mul_(17*g1)", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e", + "Expected": "000000000000000000000000000000000f1f230329be03ac700ba718bc43c8ee59a4b2d1e20c7de95b22df14e7867eae4658ed2f2dfed4f775d4dcedb4235cf00000000000000000000000000000000012924104fdb82fb074cfc868bdd22012694b5bae2c0141851a5d6a97d8bc6f22ecb2f6ddec18cba6483f2e73faa5b942", + "Name": "matter_g1_mul_0", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d", + "Expected": "00000000000000000000000000000000195592b927f3f1783a0c7b5117702cb09fa4f95bb2d35aa2a70fe89ba84aa4f385bdb2bfd4e1aaffbb0bfa002ac0e51b000000000000000000000000000000000607f070f4ae567633d019a63d0411a07d767bd7b6fe258c3ba1e720279e94c31f23166b806eabdb830bb632b003ca8b", + "Name": "matter_g1_mul_1", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1", + "Expected": "0000000000000000000000000000000014f9bc24d65e3a2d046dbae935781596fb277359ba785808fd9ff7fd135ba8c1ddc27d97a16cc844427afbf4f8fc75a60000000000000000000000000000000017e3a485f84e2f2bdcf3255fe939945abe60dca5e0ae55eae9675dcc8d73e06d00b440a27ab4dc21c37f0bd492d70cf4", + "Name": "matter_g1_mul_2", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a", + "Expected": "000000000000000000000000000000000827517654873d535010e589eaf22f646cf7626144ca04738286de1f1d345342d5ae0eab9cd37ced9a3db90e569301720000000000000000000000000000000002a474c2443d71b0231d2b2b874a6aeac0452dd75da88e6f27949edafc7d094cb1577a79f4e643db42edcaecc17d66da", + "Name": "matter_g1_mul_3", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89", + "Expected": "000000000000000000000000000000000d7e5794c88c549970383454d98f9b7cebb7fdf8545256f1a5e42a61aa1d61193f02075dc6314b650da14f3776da6ead0000000000000000000000000000000002054faff236d38d2307aa6cbbc696d50f5b3ffead1be2df97a05ebbcbc9e02eaf153f311a1e141eb95d411c0ec6e981", + "Name": "matter_g1_mul_4", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944", + "Expected": "000000000000000000000000000000000ff16ff83b45eae09d858f8fe443c3f0e0b7418a87ac27bb00f7eea343d20a4a7f5c0fcc56da9b792fe12bd38d0d43c600000000000000000000000000000000042a815a4a5dca00bd1791889491c882a21f0fe0a53809d83740407455cf9c980c5547961f9ebe61871a4896dace7fbd", + "Name": "matter_g1_mul_5", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1", + "Expected": "0000000000000000000000000000000009e425f5bdc7df5c2a72303918e5a3c7d2fdeeb071179c533f83cdcf38dbbdb1ec5f4ebc85f3ed80757641ee3f8a8637000000000000000000000000000000000819a3e81e9ac2baacdc778225129e16344107517157ab2a7bc5e3480938585c55fd2dd7185f52251f5ab191f162cf5d", + "Name": "matter_g1_mul_6", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c", + "Expected": "0000000000000000000000000000000015e6bea7ecf15d91bde67231f794397502c087960fab36d905137ce2608172b5a5def065cf7ee567ca7fb08a22adecf80000000000000000000000000000000001eed472d6138fbc56e10edb62563c086fdeb9acf6de957f2367db7f1c80d2c23197c09039ed55e65cb56de9fb9be64d", + "Name": "matter_g1_mul_7", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108", + "Expected": "000000000000000000000000000000000220a71ad70fcf7e47df60381fbd1aba33c03a3f8537ba2029ad8e99b63c8677e0183f0b5bb2a5e1b23bc56693adb45c0000000000000000000000000000000017f26ac6ffc79ded7c08e08673336402f47ab48ef9ee2e46e3265e5cbb790cfc86f41bd1b578c5891eb052d11197c850", + "Name": "matter_g1_mul_8", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672", + "Expected": "0000000000000000000000000000000006b27724c4898b4f71be9727b773709a7905997d06a41ee618b7dcf864d7457bb3241046f0139c1d678b6ba6226f090f000000000000000000000000000000000b20cabf58f9c29897e20e91a9b482f5f867bef45ce0941cb8850aaa2022182298a1a24655a4b905f436520cc42a30cd", + "Name": "matter_g1_mul_9", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea", + "Expected": "0000000000000000000000000000000004745f9877b3a0851df5bb770a54c69d5355cdadddc9d961e2bfdb3d0531d3d0f780f462335289be29ad4c62cb1250a00000000000000000000000000000000011034a094f59212c29e3f91c48df670e7a4021e4586645d250ee74a90f4b7b51510a5048dba3b555511c327ed211f81f", + "Name": "matter_g1_mul_10", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76", + "Expected": "000000000000000000000000000000000841c1538c1a3b54418c1c5557a5815c9ed74f6e1c8ed70e1ad424220dc522c530e2e48affe6cb3190abb25af84b91a300000000000000000000000000000000167490a2aa6c8796736cbd364a4d18007ecfee403bde5dc13c611a214610e85af314ddddbf05ea129e027e0ae8d89b36", + "Name": "matter_g1_mul_11", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c", + "Expected": "000000000000000000000000000000000ea1f952d65dbb9a40209aa89e367d9d75e1b4c3a70a609efda5fbe7f5c5483163671da425545d3f1afb817c6d8c59a0000000000000000000000000000000000cd537dc11cc63dd15c8ff74d15961390eaee59b2d5697b18c1ea6d534d71551f5e195e8a0793140d821dde97dc77623", + "Name": "matter_g1_mul_12", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a", + "Expected": "0000000000000000000000000000000004c92b7cf9199f47008dd561e624c822a067c57fdea9d016f79e6c7956dda9df0e36b4e78715f3da1319af9f4f1fb160000000000000000000000000000000000d2851d68617567ad5308f69dc5dbbf37603c2ba48cb3759b70fc4301fdce3bdc9fca076e2ae09562396c1b8558ccdcc", + "Name": "matter_g1_mul_13", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054", + "Expected": "0000000000000000000000000000000006ed98add25d64f7488ed270e0899ee3633c84b73de26557c552017e7cda4cba1228c15e87efb5a740284dddb8cc80de000000000000000000000000000000000b363e14b0285fbd24eaacfe80b992d8df1abfe83991cc55b0484076385374bc87d9c7860177f06143c600503ac54577", + "Name": "matter_g1_mul_14", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746", + "Expected": "00000000000000000000000000000000164671460621354cd352d93ca7de51828b3e6db0a37d2894a0ac475a5facdbc3ca5909d3bd7553271dadaa68b7474e2c00000000000000000000000000000000188827c6e2f4e9796c71703ba53ba2ded71bd6e8280e047fb6ea440b8dcafa7c4252d26bee1780ac67790e0d603c8ca7", + "Name": "matter_g1_mul_15", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d507f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf", + "Expected": "00000000000000000000000000000000023b2129ac67abc79966102ba223b982d40ca83e9b1ce33dff681c751b3f0c692f8bf19fa0394eae190767899829d1d10000000000000000000000000000000015449c6b5ee2c9f8b28e9732c9ebf6ffee5048263f7b5050a5ac9a76b034931a5c034f91d24b461636f5b116e37a26a5", + "Name": "matter_g1_mul_16", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da", + "Expected": "0000000000000000000000000000000004edac7b03b5861d178bb4aa34e795c776fd95e7c0980f19d111ef208ca4854f73a3ddc219bb6bca173dec67b0e863a00000000000000000000000000000000004dbff672368f86e048c3e33cbe90aba570484b4ca2221f7f6adaa1738c369f4c02c0a10118e84ea8e53cfbaa10fa48b", + "Name": "matter_g1_mul_17", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833", + "Expected": "00000000000000000000000000000000169d637c52c31e4c62c9563a508869f7bb5adc7defedb5f4ba9f3eabe517fa8c0be2e44d656e50903dcab67a6a44984d00000000000000000000000000000000192b39d5cddac36940d896a738e25c25217768e1d0ca712968718b8fd9ad492bae63063b3cb168368c3df196306b6a1e", + "Name": "matter_g1_mul_18", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154bd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f", + "Expected": "000000000000000000000000000000001608c3bfb131eae485545b7d19b8f42de18dcea6a0db3279eac2b7c008fbead54046bf13dd63835abe9c63110e12526c000000000000000000000000000000000abb41b2f17cfcc2292c5bf559b38af3b25db40121c6a5627997f65765eee1743c204f1161abe3f71ac1fe4de6aec1d7", + "Name": "matter_g1_mul_19", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af6bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc", + "Expected": "0000000000000000000000000000000016e3125ae97a2b1184e2c6dfe5d9459ac567c686e65674f3b0513df6de5e80d1efbff3c254e509eec3f951b0835b5829000000000000000000000000000000001889481258d3e898ed4e4a43e74c0eda5ba26c0b7525973ca86b896969240ac5928ba58bc86ec17a47f2469d023682dc", + "Name": "matter_g1_mul_20", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719292a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52", + "Expected": "0000000000000000000000000000000017d8c0aa81ca6a1e4de8d0b8b3a13b1d6350f79ee8439da97a5d564d435f4d40bde99138b67284beffbb176daee92352000000000000000000000000000000000a04e0bee6b9681db56604a6dd5e41c072e84f8ee9cb4054410eb610472b96c09802a1d70e325c40c7ab7e248eb2e3e4", + "Name": "matter_g1_mul_21", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1", + "Expected": "00000000000000000000000000000000089ae9fc5cdba1a24ca87fe4f1207d1a36c494d842eed330069f988d3bc8554af1deee3a5c59b5e74729097acc1185fb00000000000000000000000000000000002fd95001da3011b48067d351ec8667c2b2390b23fa0948896725292311dbae71b51d6d5d57e173970bc992d11fdd11", + "Name": "matter_g1_mul_22", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff327064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9", + "Expected": "000000000000000000000000000000000548e7564e09c2bad9859dd63dd1045878c9b257015558b18cf5911d1763325e411c1fb8af52e8766fa7adae83eea12700000000000000000000000000000000111235351d136905fd19fa726eb6626085875c33c98067a01fde9688a5b2c289cb8e3f5d6a85d0829200a355c82f423e", + "Name": "matter_g1_mul_23", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b", + "Expected": "00000000000000000000000000000000165504769c7ab0d28b39f38f3bd09cd47c63b74c57d39935d1c03e262f9da0e8b0b9264b0d8e2908423fe5c74288c208000000000000000000000000000000001680df1d577bbbb66ffa10258bca54b74cd90a7b3f3d50472e70e18ef54b7a4412e9eb93e39b9b312e3e8e00a52e4067", + "Name": "matter_g1_mul_24", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c23176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6", + "Expected": "00000000000000000000000000000000087a52e8eadd5461e202a640024fa17e201a9f0a2984be3fecfdeef86abed72d059e8879d0be8789f2a6db0d2cf55d3400000000000000000000000000000000196fe307db05207661a5a5f8f7fb24d8fea18ef91941ea7febbc18819f49f73aef9dd1bdf4fd605e031dc04f16fa92e3", + "Name": "matter_g1_mul_25", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd1d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36", + "Expected": "000000000000000000000000000000000301caf675cd5359bcc274b6141bb6ac53ab6a86a38ad4f8c3233cc9c1a77723eb0de4a2014e556185947dc1ef6624e3000000000000000000000000000000000136d286e623637f12c8b86cd9fad2bed8479ace5189e064a4e12e6e641447dfb0399757026126ad2d169c05011f5031", + "Name": "matter_g1_mul_26", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc", + "Expected": "0000000000000000000000000000000004ce73cde58c9af5d1f76e100849b0ba3d3cc6491e76b39cf4d7b681fed0686396440f6a721f73b31fb14b4c7624c176000000000000000000000000000000000e26b15c1051d7b049e82476a30545cfa4bf0a2075681d7028797c528712c7fba7a59145c9dd9ca9f5e9b1ac8a68b126", + "Name": "matter_g1_mul_27", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da05061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792", + "Expected": "00000000000000000000000000000000028a89c904f63eb8e68096bd2001458a4b9b32556c93fab5e52ab26ed73d62f0489d6bf1906a62c8148d50d30222a65f0000000000000000000000000000000007e54f21e2ac6d5287289ed9e2a15d457b5dac22ef36c19cb28a6cf9a0d11c981bf6549ddaf7ddc0a59b3d3a4698d975", + "Name": "matter_g1_mul_28", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c", + "Expected": "0000000000000000000000000000000008c39ee7c8d86a56ad1a9dbe005b4f0d44849d6fea6bbeb0732de725ad561befd49d465a134bd1a63a39eadbb6e0bce1000000000000000000000000000000000d5c892c92817fa24afb0a0fb319ad21e309edfb6300397a215e34eb3aadf91cb41b4ab1c5273bfea6eaf33982c75eba", + "Name": "matter_g1_mul_29", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ef0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa", + "Expected": "000000000000000000000000000000000ba1650840e24c0f99ddd10a6c3341661e5c96b2e95cb6bda3340e7a0167c906e2f0ccbac6f0be2d7dbb3f9370a5ec960000000000000000000000000000000011638a3d9a81c0fe2ebb547808db758c7cfa8648b4835fb8c4931fd622da3a001fbce9a21d61f98f35b1e907913ffd25", + "Name": "matter_g1_mul_30", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c", + "Expected": "0000000000000000000000000000000000eccc25cfd8c5a58b330a74b92af0c2b932772eacfe898ff3d391fad5dfba52a3940e8edfc9bef5c4de670207c8585100000000000000000000000000000000095ae48a94c92c332915b0c07511bb0d54c316ff3a0dd2509a18a21320b506bbefa76a459260efdf4c045404f02e114d", + "Name": "matter_g1_mul_31", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd", + "Expected": "0000000000000000000000000000000017a7f3b439a98885994a6832b6394b0ec9968f665b5810da58e3ece3d8e8694c482a15d3129732b43d4b7008660f19c000000000000000000000000000000000195299086d3b9448b26fe830522d520d132ed59744e677e6eb114ba7d7045019a0d0386cf817701ca3afad2a0487a689", + "Name": "matter_g1_mul_32", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3fc00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45", + "Expected": "00000000000000000000000000000000063c123a3cdb92469e7e57a18eaf3e7cab1d85d64cbcb52499d2e611e6ba71c717b0ebaf4cc9208b18c925a5ec167b78000000000000000000000000000000000fa5e78ae10ed8a4dee9440bfc7637d903404749681f85bcb62444d921c4fd809a646ffe3bb7c70dc906d07c62381415", + "Name": "matter_g1_mul_33", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9aff661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b", + "Expected": "00000000000000000000000000000000192b1497c71eb894a7509bbdaf308428e4d5899edb15f9e6e45a88340f55e1b76ee0901a830b66114deccda63a913a6b0000000000000000000000000000000017d58bd474a61ca0ceb23ec392dc08abe5697b8394fd60440cf787f15cddab36aa99c2ec2341bcc06dc1771b5f0fa139", + "Name": "matter_g1_mul_34", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c3346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca", + "Expected": "0000000000000000000000000000000015f72ad769cbaa2bbce0aecef9559b825ba4ec17ec5be2d9f0dbc7184383eb3e201de5163e71f1e71655acd5ee1fb30000000000000000000000000000000000194d27d9045b9760e66b578af24b282d9aeb28eb51206d2e18dc04bcb6df90553a846736afd92b23aa004f8de90bbf9f", + "Name": "matter_g1_mul_35", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc", + "Expected": "00000000000000000000000000000000146f12001844bb0ec185e773175634f2e56bfa7190caa851ad16443b629b375ce3967b0c936d30dac2f126343722ce5e00000000000000000000000000000000080e8e90ed0d259ad803269711e511577769f7886b425f9b7857dc90ab36438cbd7435f6eecf2328f5fb6eb56f370163", + "Name": "matter_g1_mul_36", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218082c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447", + "Expected": "000000000000000000000000000000001344d2c2bc5ef45dc69597e948ed6021d84f7bf2c36119869a3f84288f3bdd6fc3a0de2b9e2564a930c2207c1ee36a0e000000000000000000000000000000000dc4d15ae09642ffa17d77510fb1ad4bf9e06084e9d352f4e234ea35f33458df4f23a209e29da42c41fb9a3cec3e8242", + "Name": "matter_g1_mul_37", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828", + "Expected": "00000000000000000000000000000000084f2ed8573d5d04e41909d5c8ed3feb88f572726fc86d17d466276342f01503f7c8552498f8a7e96c875c4928b808f2000000000000000000000000000000000b618ca81b6ee891690099459634e011b5f59fb5c96488b0205139a65c77f15af135b3528a5ca3b794e7b2991d2434d6", + "Name": "matter_g1_mul_38", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcbd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a", + "Expected": "0000000000000000000000000000000014733ee8425f42a30010366e4585cbbbdde6ed602a639bd299e63c113db3d797fa01075e24a042a060a043c9e1fa79f40000000000000000000000000000000013b44e1932681d238c52e959e1e3daa7a2e1ac67252ebea0cae90e8249f85b61812b9e09203d38d96f4916837b3693c8", + "Name": "matter_g1_mul_39", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6341776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb", + "Expected": "000000000000000000000000000000000ba15476a1346fbe9be2720721b592ce7c111b95f0b8738495e6c28487e12fcad60006314dfe68789e60f4df2db14eec000000000000000000000000000000000b44b9a9f695c94ad206717daa3128b672924d0db83ae0d47b62b3c79428f6fe151a65a39ae411e18b128d6796b67bbc", + "Name": "matter_g1_mul_40", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425", + "Expected": "00000000000000000000000000000000070dfc697f7068180a7a792604d7b8453dbd393c993be9829a263ad5864c3575d3fb235692ab12a4dfa4221bc6e0c6d600000000000000000000000000000000123a9d9b83e2ca7c95de9602116b1e14d48175073e1fe766458e3fd4b6676f120adfcc5c497febe2f7ff68b1e3508e3c", + "Name": "matter_g1_mul_41", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96", + "Expected": "000000000000000000000000000000000dcad6e29cda2332dff09377460c7a2b9d908ee53ab13f648cd892bf68a44ffcc8cd5d501f8b068f506b506d01d3f4430000000000000000000000000000000003aa625a60932474ca3f914a3e0aa8384533723f824b12c686a64863a734d96ba13670c8b355b52b0c01b49fbffb6149", + "Name": "matter_g1_mul_42", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5ec26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc", + "Expected": "00000000000000000000000000000000023909bac6048bff0373d27a06dbbb8aba8ddbada93f4fea65c983598307f3c3a8cbe163462484ebb88165c6b6da41590000000000000000000000000000000002162d8a498670158c23daebb724168b5379d9124b064de871674a3ecd15e6b546366287563928a1e279fb1eb2ea0ba4", + "Name": "matter_g1_mul_43", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79bba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca", + "Expected": "000000000000000000000000000000000f79050036c4bb6c6b8e91abb300dc49a75b32faaaeb258661c905b4d936f4096d59de89b911de294603a0e3443fada5000000000000000000000000000000000985105497cd87d5ae2698479da55f6be9bc2cf5a2093b651d7305b67e36343debaf19c266ccb55c23f3de55bdae23a6", + "Name": "matter_g1_mul_44", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a", + "Expected": "0000000000000000000000000000000017901e77745a98c09d6740597c40f27df841cca6dd95653a1da6d8eb1c57d5ebffa6a7b894369b6b419c61462697080b0000000000000000000000000000000001732540a1bfa4a1a851106209ce4807d7c0a33816d3742ad5e2729229f3403940e03b93121b79bb94c24f7e60539ece", + "Name": "matter_g1_mul_45", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81158f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4", + "Expected": "000000000000000000000000000000000f990d646495fff77d090f4a69b8af0e1762982b53ef8ae9bb955ad8b894942b85c7726587c9fd956ad58eb9e3ca25630000000000000000000000000000000007b7315e1f93cfba8076cf539aae01fd3bbe1cf92daa168a6fd6a2e7c969d35c51fe7eba04f1e0dd3e2020635f2c4f09", + "Name": "matter_g1_mul_46", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556", + "Expected": "000000000000000000000000000000000de9d7e58919ba6386f32af53ccf36cb0b834855ac8dcc19af3c3c9522c3db2985e51ba36067b61181cb0fe8b47d853a0000000000000000000000000000000010ff0800ed1b4067f8c920462f7abd7361dac2371716f7b8648d64a71cc7d53265db6d80b26b9efddd572a2273ab1b17", + "Name": "matter_g1_mul_47", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7", + "Expected": "0000000000000000000000000000000011a11cc098144fe9bd42ec8845be76b6cae4b3001a79f4bbbf9f20e8ac8bca5b37ef8006c958318c3894aac7d6bf77e8000000000000000000000000000000000d5c1e6b78c40a356a35bfabfd66a81924d2eae6d428b5caacf8f3992ab980640e857e756e649ca83f5aa4bda7cd00b7", + "Name": "matter_g1_mul_48", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193ab473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602", + "Expected": "0000000000000000000000000000000002e72f4568780fb41858edc3f5796f7936a30ee9ddc7b5034d9341614d301c7906238bfde3bcb77f063fe652a43b88270000000000000000000000000000000006f971f4a8ac554df7ae7ecdfab724410f1948af994d760c5f5977961f891ba4f4e76b27c3f0e5a1471ad017e91a9af7", + "Name": "matter_g1_mul_49", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013", + "Expected": "0000000000000000000000000000000014b9ef8878af80f824748389d608bc9d0ffbca96230ed590d8e351586607a614f2658e348ac172f3184c1e5fde50f550000000000000000000000000000000000630f0556407c140d0a05b10ea65de48e4866e040455ebcd54fb6ed6996a6a3ac7a94a6818ba424936fa505c2c364124", + "Name": "matter_g1_mul_50", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10ca9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8", + "Expected": "000000000000000000000000000000000e66c8be115a941ef7adf4490faea39149a3d812c29d4afb36febe3f813c7390a715f838dda90cd73556f89abf3949120000000000000000000000000000000015d85c185cb86af3ca1c526ffa6e9459a9c699c5a4d57278f33b14691e980e0f86b9239e626fc4064890cb610f10e496", + "Name": "matter_g1_mul_51", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bff228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b", + "Expected": "0000000000000000000000000000000009db6ac72cdcf1f69c6593bc183aaa2b3980ff78a4417e23243f81243987ec6f2636641c9e9c738c7af2a1e9f94149d0000000000000000000000000000000000ca7537c04c06607e42403e84e7d9e55b2a06c730ec342f16d03689bb684918e85f637e7a6279d95cb7774f106139d0f", + "Name": "matter_g1_mul_52", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b29431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244", + "Expected": "000000000000000000000000000000000620b092ea8cb718ae9669da4ff2faf639fb5e657b7759fdf292e6d841b51545afbabf95a98601847f64fc7367f872ff000000000000000000000000000000000a14bfc0e328310d62f116652b1de3a18282b122e0e3965619a099466986a546b73696274e12bd395224018a48b3d80d", + "Name": "matter_g1_mul_53", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83", + "Expected": "000000000000000000000000000000000a633928be3f3bb4c94cf4d8d7a8169779f8bd4bad31ede895937e8e8b0ddea956d255776141541ef5791aa3a0bc6d360000000000000000000000000000000003dc3b703753a7b8ccf7676b04cac8021aa311233a99e8d5290655d2f84555dedff62f9f81322307b538c3f3458f6313", + "Name": "matter_g1_mul_54", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751ab96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852", + "Expected": "0000000000000000000000000000000014911a8b41cb65cb7ccb940a472cfa58861f1a506a4f719888eb35d48ed9774ea0a0dc3ba38760253bedb4a1acd0963a00000000000000000000000000000000031388c90440f22cc63a1e9450256e5cfcf2f7448641ac66b43d542c4b77e9c590b957efdb1c6d75846b3faccf033276", + "Name": "matter_g1_mul_55", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9178176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c", + "Expected": "000000000000000000000000000000001968070c01f0aeeb42ab71730f5b78ec122c10ca9dac1764ff5e916fc85a5eb5ed406c03263c57858fb03b15ac0035550000000000000000000000000000000012ecfee330e1cc8006c73e9d41ac1947b67f8704d12faf8c0c05c2519dca68be7bdf88a58eb4825b35a1d270554d6ce9", + "Name": "matter_g1_mul_56", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c049c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0", + "Expected": "000000000000000000000000000000001469e7ab4c3740701927da2b0e34508a73387aea671857b042dabbc65cb849f8c8ed0b7f8c8e37f80aeee98ba953f4e4000000000000000000000000000000000674212f9f8e1419608ccf1a0447533fbd6fda87a35cb9fb39c8a7daf5d12f450c12bfac9e9f872b2643b1f8f201439a", + "Name": "matter_g1_mul_57", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29462ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45", + "Expected": "0000000000000000000000000000000009c756aec59a68832728b1133a69f0794f6a082e2f0f161e488078bec7420a0da19e812def625df9b12aa36d94d8a38600000000000000000000000000000000014aa28b18771ca07b7627446eb60d53bf4837541da661a0e5cadcfeaf58f5a650a39ac304f48e45d9b714cead9ba5d2", + "Name": "matter_g1_mul_58", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3184fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f", + "Expected": "00000000000000000000000000000000153548fb1d7f1721c7fbdfeb167e1c060a90aab8f7b6572f4a2707de91b03a7b5e68f792a18d940167ae83d1380d6653000000000000000000000000000000000113bb747eab3987cd195e9eb755735698993332d517890f4e3285bf7274f8579ffcf84908a4758f0bb932021f2c76d6", + "Name": "matter_g1_mul_59", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae928a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d", + "Expected": "0000000000000000000000000000000014ca98181489c96227f8052a77730ab446615cb7b2b00a600cdd7defe8b3ee1cd53a6d98892ffccda5fd4916e0cf5886000000000000000000000000000000001567c3207cbd42c0445ea96b464dbd9099b85f5df1932d152436c936623d92fdeb009e69919368134501fa9363a0b1c4", + "Name": "matter_g1_mul_60", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b170d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a", + "Expected": "00000000000000000000000000000000139d093364c313d400603dba5a79479d566245a397f88aae748e110e09e7ab6dd271b8c37a90b86f6b48490ec1d0d8f3000000000000000000000000000000001099d4cb400f2d786dd2dd5d162580d2113c8405f51e8a619a6894d86a7f7ceb237289808acffa274069c24ee27c860c", + "Name": "matter_g1_mul_61", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ecdbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997", + "Expected": "000000000000000000000000000000001247d4d3b1625ffccd350a9fc9759295637e91d9167d9bc72bbc1b60b1abb71dc29595b49ee1edc778f5219416bcd0cf000000000000000000000000000000000dfc69cdd0e4e126208b76a4e5fb8d032ae93031dde7da9bb1358507d4480881576c5d7cb7f0b3fa3032c0151650f2da", + "Name": "matter_g1_mul_62", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a", + "Expected": "000000000000000000000000000000000150849c60273de83f9ce2016238c273359ecf486adeacc4450e1d1a6cb79fc0d0fb38974489375d5763da8a5f4e743e00000000000000000000000000000000157ec6c2dd68dc5fb3cef4e935fedb74e1f0e856f1d75890bf995a08ed6b53b52e2e0d412ae190365b139101e7fe040f", + "Name": "matter_g1_mul_63", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d", + "Expected": "00000000000000000000000000000000024b59fbec5240fbdf3fb4e565bbec20f26edbc2a1bf7ecaaeb5278ed9fe13d1e360fa298e2d3f9b2880b00aff827f620000000000000000000000000000000013ca56975d9fd667bab347ed67fb96a433d57836ca4069976e12459152e1369154bd095a15980880e21fd02b1d7e3156", + "Name": "matter_g1_mul_64", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616", + "Expected": "000000000000000000000000000000000aaff66eca5ddce81533afa27e2db1c25a2c6f0dc1dd7c2236d4c89cb9d2539e109cd1362dbfee86397156c3703d44e60000000000000000000000000000000013598d8ef4470998aec290e941576f5e94d696f7f0be40e3131b516a1679c5b0eba74dc9ae00ecb8f115e4613a50f3bb", + "Name": "matter_g1_mul_65", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af", + "Expected": "00000000000000000000000000000000163cf5475fae000c38e59754cd29f1290ab2d6550552e9186555d1ce2960b7dca5834e0347699d2869b8c9bc42f6f717000000000000000000000000000000000b21bd3bfe50e0536135a910359527f80c130a08029c24f990c82f02727def21973a20a2021c95aaa3a7c8a980b44f33", + "Name": "matter_g1_mul_66", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d245111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145", + "Expected": "000000000000000000000000000000000bc3667c38602e7e1c018cc62933c013a9e78c375b50ba06f0c3d34fead5ec8a9658702a0856625a712520ac99afde230000000000000000000000000000000015c6b5487a52b41ae1a4634c8675f7b847aa5d319ee9eec0c92fc06d8e92e1cacc90ee394f8c90ce3e2c00307f53dec6", + "Name": "matter_g1_mul_67", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531fc07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a", + "Expected": "000000000000000000000000000000001358e1724cb3ec4028a63e4252eff164defaa41b21042037ea9a1e06bc1a0a1e838afc1965ee665de3da0163d22682420000000000000000000000000000000019828e11831e3e4216d843ed3446345edb357b2082b7947fe71932dfd894543928ddddd8649d32b4f1349f63f60bf095", + "Name": "matter_g1_mul_68", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787929b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0", + "Expected": "00000000000000000000000000000000130e09c096ce8ba86ae71a817426d929c7f9f8bfe00e76668b0041e935d1531d6f58e5eb743df3cf86fe88bdfda8c8a300000000000000000000000000000000187b25d8216fa3851bb6fbace998bf3f23dea80dd6e1cd94bb6a72d335702694804c6ef3d350519c5e781f941bb72f92", + "Name": "matter_g1_mul_69", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258", + "Expected": "0000000000000000000000000000000011e61e5158d9a7c59a5007732a76e27d14602e15159e8f62bd13be8b44c96736af5a77495c3da55c8244af6e60eb4f2c0000000000000000000000000000000008deda8447009898c89c6766e8add105892992585724d520c38d0d4f8c833f88d8c331e11b291b6def6847bfa9629d2b", + "Name": "matter_g1_mul_70", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f57a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e", + "Expected": "000000000000000000000000000000001182f2e45f06a729f82442ddb372f2eb8dbfccf12edd8df0764072c9f14cbe001893d932e89b948a643981ea8aa4fa41000000000000000000000000000000000910335dbdbef74b844a6f3b879d14c23c711ff2362213636ddab7eb1a44cd4b687659f8dd521c134b56bc4eed0ec5bc", + "Name": "matter_g1_mul_71", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567381b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83", + "Expected": "0000000000000000000000000000000019576d68ce66218d4c9e2e6fa9985451eea46ce60b11a74cf5ea9dbb9d0e8741d11436dfd77b0a8b490f4882cc5b416b00000000000000000000000000000000088ba5153e91738f7524034a2609848652a7e416fc68537ab2c16b6699f69695c62e5724dfda2f3b4f90277f5005bfa7", + "Name": "matter_g1_mul_72", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc3ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07", + "Expected": "0000000000000000000000000000000005720fd4bff4da704edb7e317e3d41f1d1f45e3c1f22c1b98ee0b6875af414f6f58793e8ffd5c89bcec2af711973ca1600000000000000000000000000000000051441e34eed472766186a44b2028d86eebadd597cb7e3fa4f935d30aa043f11fb18670b31f0a3b8aa23bc8f05361064", + "Name": "matter_g1_mul_73", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6", + "Expected": "00000000000000000000000000000000141a0eb238edd1cdb670737d94f658fef728691620f9c6d98e34ed8bd166b38ae6912b5bd90ea21b091766ad27d689480000000000000000000000000000000002d0e7d2584586ab2f08cbd419df3defab53a287ca467b6b081e474711a23608831c1507bac4f328750731b99a06c6da", + "Name": "matter_g1_mul_74", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659", + "Expected": "000000000000000000000000000000001227b7021e9d3dc8bcbf5b346fc503f7f8576965769c5e22bb70056eef03c84b8c80290ae9ce20345770290c55549bce00000000000000000000000000000000188ddbbfb4ad2d34a8d3dc0ec92b70b63caa73ad7dea0cc9740bac2309b4bb11107912bd086379746e9a9bcd26d4db58", + "Name": "matter_g1_mul_75", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c88586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d", + "Expected": "00000000000000000000000000000000187cb196679b6baf78a7908c37d7f31a9fcefa90b7cf165d0748a358e6dd86fc5c2d91ff1c4429a563b5962b821cbb01000000000000000000000000000000000d94711dc6efed34385579532f59964ab18b9debeac96044f3eec14cb36965f380d21d39c246e972aa2d5891ce417e9f", + "Name": "matter_g1_mul_76", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2116e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712", + "Expected": "000000000000000000000000000000001405c27eb28f58e7f66988a300df376f3536723e2ba5934d843ae629669485015c90a8da60ef5c00c63c0b08a00203a70000000000000000000000000000000000a62dc83ce27987849070a6022ab6a06186e2527f39ae94d5a23d2e4d234a465d50e03b0d7d175ed7f53ced0c3bbc8f", + "Name": "matter_g1_mul_77", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72", + "Expected": "00000000000000000000000000000000142fa228919f71f75df073927d03d9204b36a5177b4ab7bc995b59ff312034f7ff916635e27abbe775379aafc24a35c30000000000000000000000000000000014429fb137cf912995ca785902877e6675105b252a64282412798f883063824fc31cd79b356ea4e4822363b948ec27d1", + "Name": "matter_g1_mul_78", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6", + "Expected": "000000000000000000000000000000000cf0aa7969ec44cc21bc8cca97fc8a581aecb63054c4fa3b7b69d28e0e2e901fa51c42a629145d9126e63aefe7978c8b00000000000000000000000000000000199d565f26b9c6496a4115eefc75f1066480f498a50314b396685a3ade8e50ab03c7f56316be2bcc02dff8b11ad5e4d9", + "Name": "matter_g1_mul_79", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b", + "Expected": "0000000000000000000000000000000000bf4256ce2a2a976e35a9eb266d11dc53d043f6fcafb47eee06e120457ea56decab47ef22b251c6cce17df9a7d91e3300000000000000000000000000000000152c438e11fe1d661eea7c631e04e02eb9204ebe52cbceca1ab6a9b4c889a1ebdda01d7505df29fe2204ef5787749a63", + "Name": "matter_g1_mul_80", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d506e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035", + "Expected": "0000000000000000000000000000000007754a49dcdde1354412d3fe2e108675fde8a1df069c86be54c4bec46338a0952aeed50842c2486ac652202c26a1861c00000000000000000000000000000000023fe3f5e6786e339002e14ac5c9fdaac3c012526b33da9ed314cdb145f9279a71e306f5d51243a0f0dcdf59bc5d55ed", + "Name": "matter_g1_mul_81", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad51a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff", + "Expected": "00000000000000000000000000000000141464b4326b0353aa99674bbd98853b926aa580c1e03673297bcbe9094eb1d795331d16d883e0583ed0551f064d7a0f0000000000000000000000000000000002dbbfb86c4d313bdbc8ebd266c190e38645016aca22261665dc850b0d7db8b240aacebec8af097724e5291ff43e6f90", + "Name": "matter_g1_mul_82", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9251bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b", + "Expected": "00000000000000000000000000000000118ab56a65ca63becc8aea3f11b370c705f32418d51fb1b1ab64bdb8f0125de2a760cf21e7ffd4d99e9d7cde1368791c00000000000000000000000000000000047674c8f3627527dbb41f51fa52c0fe3a921d07466cb2b5484e4c8094556cae247347a0a1a98499510d1ce5067480ac", + "Name": "matter_g1_mul_83", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d", + "Expected": "0000000000000000000000000000000000d76cf9fa103355e6f5cd4baa3420e694f252249aa6171569b70cb43c906eae9b60bb79b41af8dc714bd917638bf538000000000000000000000000000000000b9272015e64f292d7b76867714a55d7223bb026f354b20109e81122fa13fd0426bb3aec705b477e7b9560c5a99c9d60", + "Name": "matter_g1_mul_84", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886233e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33", + "Expected": "0000000000000000000000000000000007c87e6d92bd41b7fa6a6ca890bf0b58304875a79af7959d9226a5be2f4ac2b4531fd09712eb6299c23d7c1c5ba3997f00000000000000000000000000000000164fb86eafac39e06c2403e315bff96faecc57474bfc964736b1850696ecfedbaa0795e537b8f541159d479ac5b52560", + "Name": "matter_g1_mul_85", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f", + "Expected": "000000000000000000000000000000000fb01ce0567f09dc44fd473009d2467c8c16da5ea7b39a1f1dba7b3656cadd6bdf2bf68f96a43252d92e428c1d2785490000000000000000000000000000000008b4fa645f3c56459a17c912c82ca36165e730807282cabeadd9c6c4a12c8a592cbac265021ef62c60eb60df3ff61061", + "Name": "matter_g1_mul_86", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f594228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c", + "Expected": "000000000000000000000000000000000b9c328c8a18113e1d1f783432c857015eaefa724fa2c441d5ef76b158ee6fe0cd1775b0c6db7600754cbf25fea528fe0000000000000000000000000000000019d30c3557af1da2ca169e70625732d9a4396b51f3b4988a9aba1be62538fd51c167c83e921f4876224d361afc90eaf8", + "Name": "matter_g1_mul_87", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82fa417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1", + "Expected": "00000000000000000000000000000000041054430741e889d4cd8e7efa41547eb624bd775fd9fb64cf9e3dc2c6df27c95ffb8d76933ac4fa1952a5820ff88512000000000000000000000000000000000e8a28f5c622482b296a43ddb607e0f25635664fa849f3d6840ed7118892106a787bc07806dfd83935754d2057f2eff8", + "Name": "matter_g1_mul_88", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab446561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb", + "Expected": "000000000000000000000000000000000da2286b44e7e90e19d51c3c41bef375c54688b07afffbd7c528589dbf7f012e1fd248b9067a3faae9f1c6b626a5c90b000000000000000000000000000000000bfa0a482b0fc445f7b99c52a48116383bb70d5f2ebec5b7715796fbd0da744d0467584bfc1c8a42ace833d57c167a24", + "Name": "matter_g1_mul_89", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c2cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf", + "Expected": "00000000000000000000000000000000148a7e9b0b4fde322f1177ced0bba34abec4a3e500afb86f9ae0a71bd75004e9c631d4cb26798bf963f7aa367f74630c00000000000000000000000000000000097f4c0893f9beadd66e4cfc6976dd277e527b1e31443e07554dacca52390066a4b37a7f0824cbaf51d3a555d696881b", + "Name": "matter_g1_mul_90", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5", + "Expected": "0000000000000000000000000000000003e1d921b5e0280f7370d55967e716bdacb7521547e22190e89862dbfcce02dfe7fa7927a70e7bc33448b9321de3d8ae000000000000000000000000000000001163f78de4af8494666c64d47d68a0feb0905c42ddfa024398401202d1fe0d6672bd1bd4222a8d106668ba4617683485", + "Name": "matter_g1_mul_91", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e", + "Expected": "000000000000000000000000000000000a94a186b96acbee87f9c1745dc301229ec750c6967262e629924227c6680b1d404e4b23d998611ad0e415610dc8edd900000000000000000000000000000000014da21c0f6930a79c8afbe42f73e048236b6d9f9ef8f270733fa1cb1012377eab37ddf2b9c742fea44020caeb95beb9", + "Name": "matter_g1_mul_92", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefbae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5", + "Expected": "0000000000000000000000000000000015cc6c31dfa9482c6341f816786562481bc3a4db4a4a00807a9c7c676eb32b9dc7e002ed4971f26c1dddea00d78721b5000000000000000000000000000000001303660b6bcac611b2d41a4f7ac9ecf3f0b4292f83f2fdeba300a060131322ee3c2da3ca3539114114ec8a76dee6a5ac", + "Name": "matter_g1_mul_93", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35851268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0", + "Expected": "0000000000000000000000000000000009f1903e9a7d275487a503b9c968cd86823fe6667c09593b60ac2c88f306e20ccde32eebb5942a03fabde9195c5c500200000000000000000000000000000000179b41dbc2ede95ba7dad512329aeca9ca3bfd4da4b9620070d76d8fe8b49ad7fa92358070dd5098a2eaff490641edbb", + "Name": "matter_g1_mul_94", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728bf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169", + "Expected": "000000000000000000000000000000000f9736431073987708757d61927a45cfec471c8366776e140f62d805afd948fd132c4a5f4049de3a1474d0cb52c3c25e000000000000000000000000000000001515b057952696810a90dce1ee8464fd6370e8af5434a99333eacd1fb2884f6e8c568f887030a4957ff6d24ca02f4657", + "Name": "matter_g1_mul_95", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4", + "Expected": "00000000000000000000000000000000195460b2d59df32f9f41eaef1139d45f0cb8f35a7982c38d356a8a8412f25e600580026d2d908b0493edba5dbea85f5c0000000000000000000000000000000004b339d62b3cd4cc966c6b4038adb302f997a16d8a6dfebd153295de08e57d1513cf0f16d82dc450e4d6f52621a42fb4", + "Name": "matter_g1_mul_96", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d22d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125", + "Expected": "0000000000000000000000000000000012cf2bcb79668067b7a265672ca614405868cf189ee9789b9e1e3186d231176dab5fea86cc5865392db8c75fc5d124c900000000000000000000000000000000121bf40feea00e151b718157b8c024f126762d84cff20aac08e7f2a027ab88b33e134a410c2af279a39618f7d21482a0", + "Name": "matter_g1_mul_97", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5485041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b", + "Expected": "0000000000000000000000000000000013a530f94e7600820dbd8aabefde2acb8b3c74e833457102fbd297317eb532c0622636ef9e9376fac1637dc745fe895000000000000000000000000000000000139eb14d3b69be977413c832bfda234348186d46fe177154e34fe204f62ac79f4b0f59bbef39b0676d81ea42a0946fb3", + "Name": "matter_g1_mul_98", + "Gas": 12000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf", + "Expected": "000000000000000000000000000000000bf700422a382546a74376b0292f3a49ceff5597f0d2b726b1ff099bcda7ba92238a21db12eff5c314a29dd2387bec850000000000000000000000000000000005e22e3c772f3634b1ccf4e311241977eb20e7269540ef22d379de26ab80c58461dfa3b67848e0d584fb11de1917949a", + "Name": "matter_g1_mul_99", + "Gas": 12000, + "NoBenchmark": false + } +] diff --git a/core/vm/testdata/precompiles/blsG1MultiExp.json b/core/vm/testdata/precompiles/blsG1MultiExp.json new file mode 100644 index 0000000..62b91f6 --- /dev/null +++ b/core/vm/testdata/precompiles/blsG1MultiExp.json @@ -0,0 +1,723 @@ +[ + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000011", + "Expected": "000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436", + "Name": "bls_g1multiexp_single", + "Gas": 14400, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000e12039459c60491672b6a6282355d8765ba6272387fb91a3e9604fa2a81450cf16b870bb446fc3a3e0a187fff6f89450000000000000000000000000000000018b6c1ed9f45d3cbc0b01b9d038dcecacbd702eb26469a0eb3905bd421461712f67f782b4735849644c1772c93fe3d09000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000147b327c8a15b39634a426af70c062b50632a744eddd41b5a4686414ef4cd9746bb11d0a53c6c2ff21bbcf331e07ac9200000000000000000000000000000000078c2e9782fa5d9ab4e728684382717aa2b8fad61b5f5e7cf3baa0bc9465f57342bb7c6d7b232e70eebcdbf70f903a450000000000000000000000000000000000000000000000000000000000000034", + "Expected": "000000000000000000000000000000001339b4f51923efe38905f590ba2031a2e7154f0adb34a498dfde8fb0f1ccf6862ae5e3070967056385055a666f1b6fc70000000000000000000000000000000009fb423f7e7850ef9c4c11a119bb7161fe1d11ac5527051b29fe8f73ad4262c84c37b0f1b9f0e163a9682c22c7f98c80", + "Name": "bls_g1multiexp_multiple", + "Gas": 27504, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000000000000000000000000000000020590000000000000000000000000000000009ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e522400000000000000000000000000000000032b80d3a6f5b09f8a84623389c5f80ca69a0cddabc3097f9d9c27310fd43be6e745256c634af45ca3473b0590ae30d100000000000000000000000000000000000000000000000000000000000b7fa3000000000000000000000000000000000c9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b6000000000000000000000000000000000143be6d078c2b79a7d4f1d1b21486a030ec93f56aa54e1de880db5a66dd833a652a95bee27c824084006cb5644cbd43f0000000000000000000000000000000000000000000000000000000004165ef10000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e20000000000000000000000000000000000000000000000000000000173f3bfab0000000000000000000000000000000006e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb9090000000000000000000000000000000017d81038f7d60bee9110d9c0d6d1102fe2d998c957f28e31ec284cc04134df8e47e8f82ff3af2e60a6d9688a4563477c0000000000000000000000000000000000000000000000000000008437a521c9000000000000000000000000000000001928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb700000000000000000000000000000000108dadbaa4b636445639d5ae3089b3c43a8a1d47818edd1839d7383959a41c10fdc66849cfa1b08c5a11ec7e28981a1c00000000000000000000000000000000000000000000000000002effc7b3027300000000000000000000000000000000085ae765588126f5e860d019c0e26235f567a9c0c0b2d8ff30f3e8d436b1082596e5e7462d20f5be3764fd473e57f9cf0000000000000000000000000000000019e7dfab8a794b6abb9f84e57739de172a63415273f460d1607fa6a74f0acd97d9671b801dd1fd4f18232dd1259359a10000000000000000000000000000000000000000000000000010b4ebfca1dee10000000000000000000000000000000019cdf3807146e68e041314ca93e1fee0991224ec2a74beb2866816fd0826ce7b6263ee31e953a86d1b72cc2215a577930000000000000000000000000000000007481b1f261aabacf45c6e4fc278055441bfaf99f604d1f835c0752ac9742b4522c9f5c77db40989e7da608505d4861600000000000000000000000000000000000000000000000005f04fe2cd8a39fb000000000000000000000000000000000f81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed0000000000000000000000000000000011af629591ec86916d6ce37877b743fe209a3af61147996c1df7fd1c47b03181cd806fd31c3071b739e4deb234bd9e190000000000000000000000000000000000000000000000021c6c659f10229c390000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a900000000000000000000000000000000000000000000000c01a881f8abc4d8843000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf10000000000000000000000000000000000000000000044496e633650ef8f6fd100000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000000000000018461a3d444ec527fcbf4b0000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb54117000000000000000000000000000000000000000008a0eb53c748001536d7ffa9000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a700000000000000000000000000000000000000031133a6c7d698078a7ec7e11300000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb300000000000000000000000000000000000001171d5c4909480aae3b110d01c1000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc75843600000000000000000000000000000000000063376fcdf64c9bcbeeff0f9f9f9b000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f535800000000000000000000000000000000002344b4be368d3b617df4aa8dbdbc19000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000c896c3f9d64341ba7c5f8a06271dce3000000000000000000000000000000000272e9d1d50a4aea7d8f0583948090d0888be5777f2846800b8281139cd4aa9eee05f89b069857a3e77ccfaae1615f9c0000000000000000000000000000000016ab25d6a997bcac8999d481633caa41606894aae9770cdb54aac65ac0a454dd0346b3428fefd837b1e3f654f8217f4a0000000000000000000000000000000474d97a9cf29e85d4a35f6102fe7984b1000000000000000000000000000000001780e853f8ce7eda772c6691d25e220ca1d2ab0db51a7824b700620f7ac94c06639e91c98bb6abd78128f0ec845df8ef00000000000000000000000000000000095bc13d5a05c686e20d7b904db4931272d84d051a516fbb23acf7981d39bffa3943d08a9be01fc48e5241cd8b775ddd00000000000000000000000000000195894e95ca3e59929612e77c1075322aeb000000000000000000000000000000000b48aa2cc6f4a0bb63b5d67be54ac3aed10326dda304c5aeb9e942b40d6e7610478377680ab90e092ef1895e62786008000000000000000000000000000000000f6fc00c0697119a34363c0294acf608eca3c680d80183a59c89b45a66dc750f818a27e3a6e136d69e7580a8afca001b00000000000000000000000000009027ceef3ee429d71b58b84919d9a8d54189000000000000000000000000000000000c8b694b04d98a749a0763c72fc020ef61b2bb3f63ebb182cb2e568f6a8b9ca3ae013ae78317599e7e7ba2a528ec754a000000000000000000000000000000000951b70c206350e1edc2aefdfaa95318368c151e01e468b9fb1cf7c3c6575e4f06c135715cc5e51e1b492d19adf9bee000000000000000000000000000333e268f0b5b1adf76b88981fc305f03ce4bb3000000000000000000000000000000001717182463fbe215168e6762abcbb55c5c65290f2b5a2af616f8a6f50d625b46164178a11622d21913efdfa4b800648d0000000000000000000000000000000008531aa42aa092a91e0894d84ff0bcec0d37cede43dec85cca80ffad335d6f69da18335869ba1174f73bb37501404d6f000000000000000000000000123717b4d909628d6f3398e134a531c65a54e8a1000000000000000000000000000000000cb58c81ae0cae2e9d4d446b730922239923c345744eee58efaadb36e9a0925545b18a987acf0bad469035b291e37269000000000000000000000000000000001678cefdd942f60480b5f69738a6a4cea5e1a9239d1bd5f701ad96c2dd1fd252f0aeea219bddcda4bc8f83983a282aff00000000000000000000000679956d49265608468757580db6b8b1821c2eb13b", + "Expected": "0000000000000000000000000000000005548dad0613ef8804a347152e8267acdbbcab98a795fc0da2d9df5c8ec37e0eb32e82950fbe5f8ec330b8bffafe13e40000000000000000000000000000000014e94dbbf60d89b3f68a5a076fcbd7cc0b683eae228f5d5036ee61012996ae2d347cec19dbd4eab547fadecdb31c078a", + "Name": "bls_g1multiexp_larger", + "Gas": 89400, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746", + "Expected": "000000000000000000000000000000000b370fc4ca67fb0c3c270b1b4c4816ef953cd9f7cf6ad20e88099c40aace9c4bb3f4cd215e5796f65080c69c9f4d2a0f0000000000000000000000000000000007203220935ddc0190e2d7a99ec3f9231da550768373f9a5933dffd366f48146f8ea5fe5dee6539d925288083bb5a8f1", + "Name": "matter_g1_multiexp_0", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d507f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154bd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af6bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719292a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b10000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff327064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e90000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c23176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a600000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd1d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da05061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b2087920000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ef0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c", + "Expected": "0000000000000000000000000000000017479d99909c144a5a5fdfd71721f4a2ee90b2b9654e069a38b460945b9291fc74e6922a7dbab9bb12b4bff9e2d0175b0000000000000000000000000000000015cfff11afe08d76944c9f810017ecf78b8ed54096078195d65a5418f660cf9b2024646a8532e349eac5d32d59c829db", + "Name": "matter_g1_multiexp_1", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3fc00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9aff661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c3346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218082c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcbd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6341776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5ec26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79bba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81158f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa40000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556", + "Expected": "0000000000000000000000000000000001c143e5d7bba56a959b94955f8eaab82a92a2e2b355baac7da0b57281645c689486059fb590ef2576a7a03a7c57e85d00000000000000000000000000000000182b1e16004c7e6f55923dd0b1dfa7346d1243996070db78f45c4c0a2cef95e93c6373903b5e0dc63f171c8164c2fb5a", + "Name": "matter_g1_multiexp_2", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac700000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193ab473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c0130000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10ca9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bff228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b29431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751ab96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f8520000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9178176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c049c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29462ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3184fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae928a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b170d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ecdbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad9970000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a", + "Expected": "000000000000000000000000000000000b2997ce4cb01abbb0ae6d28099d20e1f08c33351a6f0dce417a279789d6c581d4bc5a4a261e37e6df31a6928040d1f60000000000000000000000000000000003068e73dbbab6fddfd3c1e4fbf58bab58f15e1630c8c236faf3048be840abe316084aad7dd4ca6ee9d353ea8db536d6", + "Name": "matter_g1_multiexp_3", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d245111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d1450000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531fc07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787929b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf542580000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f57a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567381b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc3ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de0700000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c88586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2116e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6", + "Expected": "000000000000000000000000000000000ed96265e66875001ebbe888571ded16799d0bf5a6bad0abaca75b94bebf3023487a29fbe26a68f1cc90485df379845d0000000000000000000000000000000001be40cb29d8b722f91515f7e18372f7a0f77bc3ef2852c59e7533aeb67cc4cc4aab0b8e87f9a4982806124462ae94ec", + "Name": "matter_g1_multiexp_4", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d506e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad51a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9251bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886233e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f594228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82fa417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab446561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c2cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefbae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be50000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35851268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728bf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169", + "Expected": "0000000000000000000000000000000008644b6d6adf9d5b6b50d4759363901ea94218881fac2006ea391c41fed2a94645eeb3359df803d740710f0f7842b985000000000000000000000000000000001168ff1897eb699e475b8ca2930ae9ccff139d534c7cc606c7bafec0ed23a6e55c6ddb1efbb1b5f75044d0a7e122d204", + "Name": "matter_g1_multiexp_5", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d22d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5485041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000107e1fea76b5f2be2d12e11082fe690f01bfe6cefe22ce67de968e410ef51a6192b5b28a89f222db7e5b5fd5b8bc7c4000000000000000000000000000000000014028a700cbde8bce295c564dfbd73294f9bb65db3db9d38312cdc31410ceaf7151ff5d9420de2a5bc8f0d609893c0612adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c6000000000000000000000000000000000a57058bc228914bbb3e3e8f6a73842533432e0cf226cc02990b9b99a74b0acbad498036d8fb72a163590c75b6041d060000000000000000000000000000000016d275fe8c7e37058f287e1646c28ad1b4a675c0eef9671cf95dfa25617e2f2d515b2fbc04cfdffd5d487b255dfca245d1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000016b6ecca57c78d6595e6b55b9360bd946b2f0061b98d931d82b03ed747998285e093c978015f0b775867ad0d8b4a1f82000000000000000000000000000000000b584f6f00bbcb2432b6cfbd4f6c88e228658887b5278e461ede804fc8a65dc6c997de30efc65b4f43e3d96717b938644c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d3000000000000000000000000000000000d1eac060ddc0a327396051c8c4dcccb77d11da05678d0720dec020d8aa29cb8ac959184417267cd7386feb1c81146a70000000000000000000000000000000003f8b5667ee4707958ecb93a1772849d5d8a4d42a2367ca058b160dbafa8ac0b98d5ea216fd18130237a1f17ce905feb3c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd00000000000000000000000000000000180152247144900b015c3db2d8b26d45a57930a5ca988c1fbf74b63b48afa149347a343f3fc6b1f31ddd6de079391efa000000000000000000000000000000000b6f3ae16d2a580ae06634455302db85fa94d71def14c84cbacc5ef98335d6d87faacff7a9bc14dea342a6a80d9bdfd661301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000001918c4f95a0d0931ac3f254cd61c10cadce5cb9e1ef352edc8e5944c8aa8ecd90c403ed764ef42f646c7ec5e3126a140000000000000000000000000000000000ed644cd065411c63c7d054a57344e7a909e1d0a6b414bccbd356f15d16fc1b42c681cb6b36b143e91b31866387fa94395cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c00000000000000000000000000000000070dfa1dda5ba02e94b29a63f8eb571ed7e8b0d037a0203af9a8350dacec092be1bfe33f4134b2afac77b9a36f95208000000000000000000000000000000000019e11a80ce3f9b3321cc6fd1ea2b314bf0c71d0dde80cd5b4de5f0d974597f57036613829dc777a6f6ecd6f9bef2f85fb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e000000000000000000000000000000000584b7ea99ce0398473167289d34314c60ba913338b0bb690cfbb013496d24854863237a4d716437dc6ae33326240bd800000000000000000000000000000000065964a064e4da56471c9aed383e6eb38b58b9110a2cbf991d6dee869d2f1307cf7273d203d941ead90ed67c923dfcd5bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b860000000000000000000000000000000007d6061bdf40745ef7573917e0e19f240b6e917f7cd4c47e01969b9afdc6af4e3c93e0f1dc2d15790bc2e6f182c01f680000000000000000000000000000000014625d3f2825121a907b570e9aeececcf81137f40ca6d0c00d709ba9931e403c0c2ed262a8f4c2b24305dcd3185b81b0e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d2500000000000000000000000000000000087b5d6595554184fee36be472a0ddb9ac7f9beb20817647fa9978b2e0c3549ece4f061b58054e9191ff3f120c12077b00000000000000000000000000000000168d8d995c1fd032ca7b0aef2ad5c37ef7c7cef8b61ab8fcb5ea2d449455bc75b1b85631fd2ff8f5ca4e5880f36905ded4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b048000000000000000000000000000000000f44cda026dc5e30eb06f12307bd582b271ee695fa68fbff48674c0499dcc875d617471830958e31bcd2c883e97a9e590000000000000000000000000000000002977682ca8ca450df2ac3c3880b1235e0ad8436a36364d319903fe2ca2664e05a70840aaf2d62531cd8c4ba4bfae9124c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d300000000000000000000000000000000107dd39f779801f608cceb4784134894d2d9aee37cf328bb764d8afcb6d1e0f1387b36bf5b7b335099849278eac44e8200000000000000000000000000000000045c985714b519061a9c8d8c9665b582abdc4116a48a70e0d3c4a7709568aaf011aa8ecb893ca483878404b3f8b22e41a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae", + "Expected": "0000000000000000000000000000000005605f40a1116742ed53aaf7092715355ba8e41d5d49909f79ec9580955c4a9e696fa30df37e4044de4d23fa1c4121530000000000000000000000000000000015acbfdf914883d16918f980aedc2dfddc428ef8e9897776443ec3cb091f6cbeea31634ef5ed061792710d5d2c87b8b1", + "Name": "matter_g1_multiexp_6", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001696e814de192623543f822a22344258c8ef9b92c178c4bf2404b38f39151828bf518a868cde3db5cffb745f1bf0023900000000000000000000000000000000125bd1df30f4799271a9db03842f98ccb5a5c3acbe813b8324fefd06e3c8ec4e2c0be8c2d24328a2d1169bf6ea0ce46fb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff00000000000000000000000000000000138dc5c034135105c8899eeb61ab54a84cf02919e5650f34518f941aea5bbb6f9df3ee6bb2056d0b9e060158140f990a000000000000000000000000000000000eec8442c8656ebc4696ee13273b12f3e362862acc3b8ec6f2b53f58f74ea23b33c79fbbd2058ad205f4932db2e87ae9064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb334610000000000000000000000000000000008f7f45310b5638221cfc9dece18010034c00b3cf170535008d60dc7ed6ff90bfe87e03d94e9a38201699f0742d8973500000000000000000000000000000000185b62a19864e21e1bef19fbbc21863c75f558bcbfa1b948bf939876188f4fbff5d5f4f756e0ec5348e194bb4f70706ca3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca0000000000000000000000000000000019269dcdf3772ae4969b68a0b4f87c5097aa8bcc9e6155638c3de94fc22b4965386be28e059bcea69f993cc388ea9a79000000000000000000000000000000000b95f44ad9f14cb5e3b9a338d0e4345153c4ad0d42aa00a4c12df117b89d9cf8bb556041d49f94b8f63108f03c56a449bd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000011c86d420b6d8820af8a3ef511d79aed7c82ee08df993a5ba479b29ef2f968919444a7c48a24ec33522e1206bb9ab784000000000000000000000000000000000f4a47f3f14a25108c2c9262466d14e3a8d1f21bd2d3d6f28f03f35bf23a4b5b494a7cafe6ed5f39195e07b1692bb6da562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000091d9fb6493f4441c6e57a5a58210a6b78e86f1a9d204094ba6fecf2c146522cf842219c900d7cb95366cf7e411ff4a00000000000000000000000000000000015254260fb67e88d0067ba7006a49814c74a5369837dc5279c0fd19c8826813c922793c96e0f708092158ac297a368ddaf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f00000000000000000000000000000000027910712cefec94f0fd4de6aa70ccc408e64d5de6b473086009c525fb6d058ea03bc99f7ab49cdbad3a42bc8ec0999d000000000000000000000000000000000c0b0bedbad83ebf6af4f5757035b8292fadae4bfbef9f3bfcadd21dd796d7e3ecdf9685ca6d4d649b2f0702a3280d40e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e88000000000000000000000000000000000b0ae8987464ea0b77201d468db7256b135a5cebea92dddb3aff10e451568e714f1c418b6d53903b89bc71109180b8c20000000000000000000000000000000003050becb4625f8e3ab2cf13dd1eb8f7eefc7e14c16934b87661adbf0139631108d241bcb1fb24c5b989f6d424cac883396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000dba43568347a96f26f2633d9fc0fb4610428a8d4992c2734b20928bf974bf642a5122995884cf11b76126ba66522c8c000000000000000000000000000000000b9bb25b0db32149736b671ceed44df71f36a33c15ed821f591098ecd873355cfb8a39fc7c7378a19d84a5b232227ab92087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000018d46d1a9ec91cc7983b29ac83fe9101c0ca36276d40743d2a6010d574fe1c16ebd9d7f0c83cad5ec2b2f652d3e6cfa500000000000000000000000000000000185f6367fcfa70e7a005c1739c0d0a19b5ec8de766037ec92840e66e2e9db18ba2356676899763183222f9957f48f300f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000016677511c781b2b97456c3059c19b3e12a865cc21ad71cf06979bee1a3128682a4a86f3e07cdbc9ff7b5aa7a9899653600000000000000000000000000000000006307c89ac36a88c6921c020d32530fb69338afbb33929e231fa704f0454d642c47a3b8d320b4266283a8571944d0558624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b600000000000000000000000000000000015a9b215eaed682e4704cd3b1265962ae0e24555a16612ac762040e1fb9b412eacec5130a6f6a31eb670806be7ed775f000000000000000000000000000000000f60035910c438c177a27e6141d0ddae706d3e852d61e37cf8bb2f03550feeefa7213545e3af5ea614c91b51bc2fb378b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb3832000000000000000000000000000000000077b7a4c4644b21ac3ef56db1163f7b2e07a817cfd9d4c6830a97d0ae0b620e0b235376d590162c378899ba12eadb5900000000000000000000000000000000022beafe4b4ab44434c9dabae45a395b5b8da15da2fc2e723c1b30b5efc95e880846844f27eb47dfae8657fa27ab64ef88ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb000000000000000000000000000000000324928100db98f5a1af039a8e1b63099214982f1729ba633b51166da97e861426bb91283b386ed4b465d791e63928ce00000000000000000000000000000000178823756c0facbd4b1cab22f346ea7d1dce0ab687263265350c9939d52abcb5a5000b3395f8268a38027410675e8baf951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f440000000000000000000000000000000008828eea92c3245eea4d60ee385734d3237e4e346e52c5de8b24c82325819be6984da4f0c1ecfc6ded5d0539a6f1f1490000000000000000000000000000000017169bab8970f47a303d2487e3af707eddaf7c4453e9d2d6bbaf953e74947b5fd40663173edd55c0d6aad7884f69a0967056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000787474664b2803e78489de6c5d5f1938e784e552bca4c32196cfe121380aad591c9fe4d9230dbe976b3ed3b3044b8630000000000000000000000000000000000c026547c94cea37793fee439c359cbeb2b985a33559ab25d1b55773c24faaf4fe511fbf7df085bf5c1715c79469cc28aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a", + "Expected": "000000000000000000000000000000000a7153c6173dc27b6a241c046a2a9bc25b85c4621667d0a10550cf7a090a8fb914725201876a5bd2af09d4fefdede3890000000000000000000000000000000007aeec94a64ac629673f3be3cf660f471e3e928de0884300ca8271d42c92b965c86bfe76df61d0645e955f40cbe3751e", + "Name": "matter_g1_multiexp_7", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015fec2d82f5286d2067b07d83cd1c131d3fe18628101c3e45caab07f3c775c97e1533836830959cd7e434fc3fc392203000000000000000000000000000000001050e1396a5053c902441cb33003d9c54e6b631a80e3c132dfd37805bfe87cc2ddc495200268fba0376c5fa071fad230a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c4000000000000000000000000000000000fbcd07180f265688329d72ca68cde644a580cc9d698e40f69380065110ff5a61149e4aa9f67056e0e1603bfb9b5b3ce0000000000000000000000000000000006f363a9addd63a59035cad90cd52213665069f540b6c6cb41cfff5711376885e3242b596d051a59f681941bafeca53eb54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000001164abfa75cb4d711ad811c4df430ecbd6329968ab003fa680d235ab34a9565e5c08add76cf412f132b54812671da7a900000000000000000000000000000000141c9858dd17dbb027dde22dd65f6a7cd38a1999eb7977cde87ad762425e364e1395851b1cdb41094551e530d891b0d15849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b95d221c628a77bb75ee5942c9df4b700268c90c4e6330ab5533d13d59826c81aeef7621ef6145f48bef9607d280ad2000000000000000000000000000000000b2ae1b6f916d77c31e4421f8d0241201bdab5339f95eae0e9491b4da5e226f8eb3f754d40be3b446ad6d18f28158b08aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000128989e92641f3c3a914c13e986aea1bad2c87a8c28cf156bbc68bcbb134b25cd672832f2a988f60d2ecaaa1b83159e50000000000000000000000000000000000106dc95373dfcc85d9de6b5b609554b67e8683f90ea13156c8318aa8de0a2355a721b3bd77a6329264ae671c05af4a52c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b2020000000000000000000000000000000009fd9fc9ecc0d1521696bfe7624360d11111523a4ee0e30432a468dbaf1c101691fa527aac5ab531be822ae914b0afad0000000000000000000000000000000016b317ad68ec471b0ad67be2c489c9f5bb0d8bb6b5ef909ea975cb17f5964564d5f1a61d32d60c457923e4680a218b9bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd9000000000000000000000000000000000b982f33980dea4d89b577c9f849f8b8d9cb0c7efec7e17284d45c855638fe9ab2e5bdc52ba79d06a9133f66bf0ea2b5000000000000000000000000000000000c252a2e2769d3250479091050133808a1b0fd20af2b41cdeebe7cfcf7e3a92b9ab17cdf4d370f9fc391981db76de39c915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000001769e8b5fda96ef205750826f34fdda3587efddc86f69d37001c62938a90efc23a3ae150d223ef4bf3766ab7d86d80df0000000000000000000000000000000009ee24ab483300764bccba33b55b8889b084288ffda23d157f650df34125fd803624d88f2bd0c3c3ca51bcb57b9f4dcb58fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000146001b68cd902fbb4548c3e7cfae9cf3c8916e462f1becb9918c8de42483ef65f418d6e93200e8ec95528928916bdb10000000000000000000000000000000008bef4996b8120613292dc76dcc77b07b24d4498d6bd35f5dfb80ad241ad97bd161cb2c5c96fb250b70f8aec1aee5b56c29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea00000000000000000000000000000000019d02e168efb5769416132b0457ee1ca74bd5737f9364623bb270e8218c96e71dc49403584aa0a7e6c15bf6948ddb956000000000000000000000000000000000510c0917796c7ef2e100c7656591d04c3c5968d688b36b93dd690b0a8ea55694157fead964b85a5eef1815cd5932819dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e9000000000000000000000000000000000dd05e53ee40f051037c88fd28364aba276c793047007a20f893d13222c35b24e14f6c74004c3d8070405621380553af00000000000000000000000000000000191d7f1863ab7bc4ad1ebab359499f4df75b8c7a58fae8fe7cca530c7a56e5ee1617b343765960ca4bdc0245ff997a9221c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea470000000000000000000000000000000013320367c29a4f1527e8c0f3047f776d7c892d08988c402c55e90e84b07ed7f0932c3b5fd19f8d133aa839ebd90f6428000000000000000000000000000000000f8396d819d7aabefda680c8ad51c7f907911dc4da7c5fbb7e599e7f3b758c5e7c9e9ab4de1700f72f109d7206c1be0ee1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c96000000000000000000000000000000001413f6a4ec8b21a459a4aa33ea9d92614857df629ec16990939fbb8ab11fcc919a25a10423ded219ca5b94f71377dc2c0000000000000000000000000000000014a3320275a64ede5e1221c78b421c1e4474bd499263aa21e97af103d7cb62335faf4b85b5983c5865599b709e95efc4a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000057c3c7e4cf799d716483f1e8bd4e6ec91ad9566379683c54204ce46a0e5635fd9852b0a83328386643b2017b9b551f90000000000000000000000000000000010e3d5725beabfa7e4843eeb5bcbf6e7a54b4b82fd1768a3c276bba8fb7dd25dcca7e20e74231e2f7cdf0ff50cb9cf7c2c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b60000000000000000000000000000000009d85ce8e918ddbcc47494c4b194649fdbc8de31f5f3299ea4bec7c68ff56c7f6ae916c85118553b6a6634ef9b8820f50000000000000000000000000000000000c9a680e6389d447a4884b4e134a3e025f8679edcba56bf8ea2061a00e34d38c325319a8a5efb556fc2536886e225912908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f000000000000000000000000000000000f34c8793a9ec6c34c704159d18e385dc9a127e0a9b5f95667f58e68f5ddaa272f68f5fb55e105010fb656954f25927c000000000000000000000000000000000fa1d9379fbd273b05aaa8ef5397eae24cc14f83118b2584085312986c192d2c5e3a0fd8fe5c2d82be2ee5b006413a2be8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399", + "Expected": "000000000000000000000000000000001223d94bca6cb3225511b4e28797ddbf1b1e2d059c6f7c5e852331f381c578016671b5390dff3898f9048e5868b253de00000000000000000000000000000000093eb1c50064251cf043e7c7af7518b883a8f760deac99f6e10c3dc941fed718f2293ec2cecaba6d58060374bce11a8f", + "Name": "matter_g1_multiexp_8", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001429e7011c17bff6df1b3237a06bae78d427720af641d2614f32cfef8c537d5ae9315c0b179af0a114a486e2eff7bc470000000000000000000000000000000003b9caa69b5495dd33139d14146919f9344efe2416b665dc262bd09ab91f3f07d1fb5eaa3c3a94606e74ee747114f347e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a80000000000000000000000000000000012b481abfcf8ecfcaed39a4277492641c420acb65ec809a7d55892091c7f76f82c02e7baf2a648cdd5cdac45113b11e90000000000000000000000000000000015d32649850a5c99a787ceb894a66b58066c9257dafc4a6cfad2887e7a19f8af69f8d1fa69258289e417954d064e63eb5307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af0000000000000000000000000000000006038134150b97e785f33b0accd0d1991c7b97aee1acf9bf671188f61a846a9603f2d3f56d2edc0564d1ea7967e112460000000000000000000000000000000019434ad4fe571da11e2de03c891d19ea2729f4bb7b7863ae0bb8f18b53852ad4dbbbe682da2c8568fbe96c6c9a7236dc7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e2260000000000000000000000000000000013786032ab493b5026cf23fdcc468ecc486cc8179c9510d99a503031d1fe39f9caedb2d42dcdfa17173e693e2344bd05000000000000000000000000000000000f1deaaefeedfac7f708092bbe3005be7c4b56499bdeb8fc742b72be7ffe4d8ca90e605502f1863d89a41ed794e06586eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df000000000000000000000000000000000aff14b235c3569586e67cf5113ac0ab32d442a1c07cd9e249149d719dbd64f8ec1b07c4241af135d3869eae05ddc0a40000000000000000000000000000000013d960e93447cf6df8bb48db45532d567dd2b0756dd674625657e5364f81b4bb94bf436b54bfe9afe8eb5f4bd1be90732c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000f85e9736fd9d3f9a839f701b6d8a6724af55ea74d28f101f97229f4b406016e50f54a0b3d2087117f352bcc28b53d5e000000000000000000000000000000000b2717e98f9fca574ad9202bd76ff6e53c74c342d1b6049fe66310040217563a4e5df460f264769418cfdc443dc31e008dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a02538000000000000000000000000000000000ed8e6113d657b2d3283e50e9d054e612793fcdebfc31c53ef4f417e63c76234900c627b7e8c433addbeb6a79bcc5d380000000000000000000000000000000012f0a3095ae16b5535192a932f188c62c3cf01d2184f8e299794bcba86d4573e423a0eda4e17b4b512c5e06367e470f6146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000002fa5630b261e07326fb51aa2bd897ab49e0b960f769e3207906a530fd759a53db8ae17fa79c8e8c889a923fb38888770000000000000000000000000000000013d49d032b888aeba7e652b200c91042f409a6a824d1aaa04bc402f94233385254a2d1f8605d15d04013ab0de9e40a94de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb9497368000000000000000000000000000000001495234b14a93a24881f3b4425dfd82b49aa1828746b06822097c8338da57db37ddc836a9abc46f7a0cd17ec08d36fef0000000000000000000000000000000013b868cdd5ed7bf90018873ae2ec84e4bc71d002483831ab7a4a19bf18feabaa210a729ebae606ea18ce16458e997497c2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf000000000000000000000000000000000f223490fde3ae0d7b94412b3aa86030e5d9dca805f6ab5b025ce8e9648aa02067fd29ab9a1915c2df7b2186f35a2c74000000000000000000000000000000000aa747ff7e24cf6d1dd2c4fe9db8c031b78830e98cab27cf765fd874fe6b7731c13af69559748c81f3915f9f3a6c63bac1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b00000000000000000000000000000000134f8ec87b5572c062f6f3b43ee896c2e019356214ad397f703a839d39215bec954f02d3f81e3442586ba9762bb9690e000000000000000000000000000000000218735ec0b5bf9b59dee7cfc70ec4c6f21aa129d604fffe824b7ed6b6346dc242757abbe98c19c02d5235da448e331d6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa2000000000000000000000000000000001510f39616d7f576980055d0547c603d882dbe85dd0b634577fae134f210736007345d035d815306db660de4a07fc24300000000000000000000000000000000064d356ad7bd2edcd3622b1fc225fe319f86b5f7da875cd57fe5adc5bdb6443c5b09d676950e2d069bd4303b8f9206928e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca10000000000000000000000000000000014d4184d69d34b8e509f3fc7e7033d76b10ba913d6109bdf842be4c49cc0c29576adae2f75e6fa054bd989e26bda58170000000000000000000000000000000019d0b70eb45a353166bfaabcb661b46eb1b7d8a59a903cbf9e43ceb6ece492e78d7f1765922e981903153072a08bde098efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c00000000000000000000000000000000087bc015b995ff8a840fbbf23db2cdaa8bb2dcbc38e12b588bdc4186a77409fa2a4cd74347f568c5b516879b70552df9000000000000000000000000000000000b15f04955dc27d19ad2a97a99e0890e6d3ad17d29f6b30f866f8cb3ee7789038abcc24c63d4525860e64593af02e39f47f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d530000000000000000000000000000000013eb2ed1d78059beb34c3fce731d42ba28c485dbc74916e373424917d60bc8c402e331e8aa2fdf70360049740e670da7000000000000000000000000000000000eaf5b5e47a2312410035d87aba7196f3f0b65abfaac28ac80accc9d87a1115b7f175e59ea2394198a2876568986fbebfb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b5580000000000000000000000000000000012d7a2e92adfff3d37ad21dd26299188e25b628a9e9d7b54d2eb8a886e80de812a32db9816964f2c0ad25d9f0aa6ae9e000000000000000000000000000000000c7084afff475bdc0a4ec265a3cb3f87d862270b6263a47d869786495abdd4316f6f154b997224d3a895010ce04151c34b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a", + "Expected": "0000000000000000000000000000000019c9d9833332c6dd40c200d53b30e92f1595c22568b31882e24b8fb17be41f4d2f9692ca1e63106c59ba574f8b0764c9000000000000000000000000000000001414165b510abdf0d4a32236cdbe51fe2b5c9a31942710a07bb5664592a3f1b5c821edea44bd7fe185cb53b11d6407df", + "Name": "matter_g1_multiexp_9", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000038e60d2dae22dee4dad0d9e0658741c13d165d3718c763270292602852625ac83c5ebc1a6d86c181686cd01a1891b520000000000000000000000000000000007299913b59e2d245fa489d92873b7d2bc8921191a34a0d7f6c5774757ea4eb3d667ff8f3e9293f0d2354ef03cb6592b68ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000b07454ff91e3f9707880c1713c69f8a44d70040b44d96ac74d196853c62f264ccbe6d9c8945905092d9bef665e45bf9000000000000000000000000000000000405c965e2e8cb5e85ef9e18927c7e86e63e7aeb49f45b3428089010f34eef9ff37eb005e6b86e20236dc870661dd68c61529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f81000000000000000000000000000000000557c7f55246759b901e4e8478aac7b80d37edd5d6be057e5aeafe3d8da008e48c96c17ab1093a6a4fb39cbe9364fdff00000000000000000000000000000000158908f112d7cdcf867f1a5b05062b92972c2947be213ede3a7fed7a477fd57e69e1de82164f7cbd53a3f4f4bad551d744d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c0000000000000000000000000000000001701edcc472ffbbf157b1f239968924bb91825754bd4fda9f13450162e82932b8f5f39e54ec5975dbe7dc744d6d676a0000000000000000000000000000000017d13c1f6d64af2a808c3ba20792af9ee9c626235ceb9ced3c7acb4bc864ba47e55e0945a430da47da1e87f015dc024724872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000011231262d0fcf5a4b92cc1ed62aa66a55be739eab1316219ed2bb8d3e939e25b840b75f914cdd3f07b3f57bbc07c23e0000000000000000000000000000000001eabe4a5782244ceaa57ea0b58ed1334dcb94e449b7fb905805cefb786e83af66ded006cadc651a7b2cb07c3e3fceb401a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc000000000000000000000000000000000045bb823151b691e26b0e706b8abb248ecd87107a88c728e7a627a962aca7f85d4c88df949b3c53e2d32ef18f60675350000000000000000000000000000000003342b2d1a75300ae9ffbae66326936b19c7e59fc6f597ff09f2e5d50c1942f161dcbcbba00e4a46d87ab51074320132c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000043d72d26ee669ae8e47eaa74199feb37d51f5c99151a8f854362469e5acb2c5f6d2c208e7d674efa189fb90275b835e0000000000000000000000000000000019e6f1b3137bdb49c534902abbf42893fd576a211b93c831dee90723c7daeecdceccd3eb981537d4fe729d6e48d70d6ae62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000837b6a981e486865dc4d6d0c123230ced707e2518277cbfd0be747a8c9c76be6aff8b06df76f7c801fa34d11141354900000000000000000000000000000000011d745300b20c5ff1e607ef3a42ac31cc55e8be979b091aac0396748e607f00f30ff579321f2e660e90e8e5f9efd4f77a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000740837b02d2923815914ee9cfad663eb7246ec8c56e632cdc2dce25b6e475dbb6a75ed2ca6790f5f83fd1a274832e8d00000000000000000000000000000000188034daa9801ea182b712da519f7524cbb9f641146bc0fbf77e72ecd066bd577672c1ccf28a2c4d3cb9854cb2b9e7c80f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce888000000000000000000000000000000000014ddfffbffd0317ba7e248f648cbc98fac2be9f0cc31d6476f41527c25fe8d078207965eb2382ee1e0f08a38fbff7c10000000000000000000000000000000003e492f3667da69d44b35899f425af2ba51130aa6341bcc0d4d9646cc96b090061acece81ed16c7e75fa452818748b119b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c0000000000000000000000000000000005107fd2b5b483173992b0f2f51dc24bdba94b5174c063b52c33a8cf84ce3adefe0efe08e6bf4de3e68189e495b39c6d000000000000000000000000000000000605e8540f1c7f5790c306643a68606581a16a60d33607064dad5572947c93f3846f66afae10a66cd33621c6a2dae30c6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000012eb811b231a07e27e997900be274f73720afe3b0626104a9d5aed39a3931595f2ad57cf6e8f12d5110cf38fc8e7f244000000000000000000000000000000000abf1b8abe848b91333b4bb226b81a33aff5b8f7af70108538a3c706da182476a42e0e5c2fcdf694c8a12f62a996c86c030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d4050000000000000000000000000000000009d2b5044a8fe22a957b6d1eb20454db2cff51e7ebb6357b3c6b95387b1fd810b94eab4aef4f0a0aec4e6a693903dab60000000000000000000000000000000012ccb794eb1174735b5f7700ef95ccb67691cd3673d601dbf6b2e2469521f1b2ed283f2f98a9cd601867de4640c9517988e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000003eb6e7ab6dbf66614ff5b55ed36243e1d9baa317f01aacbd7f3a015bddfd818c6764c0802e97a42063a18edd9dd091d0000000000000000000000000000000018571d50a947e56f63b26a4377678c838de7b315e655104eeee48b7d5e6f5ee5d876b3ebdebcbde4080e022cc88c995326989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000906d5a1691dcb7dfd5d78f0688e95de2e2f06cdc70f8760e43a562365939d3fa23ddaaddfd1ddfbd3bc9777783a7ab600000000000000000000000000000000168422a6171f5ae44b645b6b6e73011494dc75e98793db2424bab311990eb7730a9a45234afb78aeff7778503cf4e5a03d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b10000000000000000000000000000000011c20d0c6140e0e11d3ffb8c28c6bd80ec495d057775f6dc331c98b0b0aba17568e1ba773771c703068dcc6747187767000000000000000000000000000000000f88fde780460bd75f46f593cf6fd0aa25ad14cccc061d9ae2cd8c20398f24e76ef614008efc9ffe1d1884df1122111b5639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef", + "Expected": "00000000000000000000000000000000018ed322f140a351069f18d039ebded8630fd019e8c6c401dc760ec5cc6536bc2f52a6cd5400dca0caae95e4b9282967000000000000000000000000000000000b9666fbbe91ec8bd670b2a64571a34d689eac44c5b63a43f60da08df51b003d85c03f0eab3e2042b1175af3501de8b5", + "Name": "matter_g1_multiexp_10", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b42381a83d4472a3a7a18d2ba5266bcca254fade1170c6f55d442aa2a7674008fb35c58d5a638280e0ff7531617a768000000000000000000000000000000000eb5de05b5cdf9f95c5a3ad30ce068d5491006640be4c7f02b7498963b5769d516efb9a117c60c1c5fb71617d42c977142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000163ee62f1ea9219b921ae7ed0f121426fe9fb8fc0056916c81ea9e713f1a16e3f2bec6ed0e3e552a7173f8dffcde82bb000000000000000000000000000000000f5fa0e4980d3d2b92e98e76e5d67815ce55858852f03ec7b8809b02d4b1e9e1a6c8b06bd481d9d153acc68378e779fa253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b7000000000000000000000000000000000cfbefb41304008b0e7341451f13d65681f0726544f14fd1c0d02433d3c34a4769f1456960cfdb11b6bcf016b906228d0000000000000000000000000000000001adf387f4feeb3845b12449fd5294802ed30ae211d0837eff1b22c3fedf538ec7119c1fe69ed7d595f7c0fdcd54f684acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000d736d3b8b586e09d6ecb1ee2d7eb28bd68aec60234e90611da8f1e1aedebd9c74718d41a89186a4a5dcc3f7cc81e99e00000000000000000000000000000000000ec0e89da57affa4686494e8e0f5517f11532f6e294215bd060c370fc64c26e34ee1e2d77cf341226daf84791f5e3cd9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df0000000000000000000000000000000008fef795f8bfb6de5feee020a9363adb1c26fb521439e405570b4e997f55af5783968b24d2e95144bcb6b38e4ef9497c0000000000000000000000000000000004d4e31720644e8828faeaeff38985ffa4fa2f7bdaa476b5c4d7eee81c89491eedd3f4262effe118a4c204eb555abfbd05fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c3400000000000000000000000000000000195f8fc4b1ca0c7041810b02bbc38b8bcd0711dccfd80de2b2f357f4a732e65492d57f455e99fc810d6f86eeab0ac101000000000000000000000000000000000e3010ed298656b91b5aa342f6be7250cf5504fc3aa26a2c7f46f90e852fd7799d96a85b25e6066b7d24794648a81331d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee11909900000000000000000000000000000000192cc18dff89d9a94e6f0498419ceb9f21d70e42a1b9b64bea093d67075d499184d7b2106f74d31ccd1863beeb7be0a9000000000000000000000000000000000b80e940dce71be82106640d99c121dd21e99ba459f0dc8b1f11cdffaa0d8ab295b9711c23de1f4bc35120a89948b91a44c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f6000000000000000000000000000000000b53f55edb182dd08e2c9d0ee43aa3d734143b54686295410f80086d3aebf6fc681d1150e808d684f47b0eb23fcaf629000000000000000000000000000000000d73442636f4d5dd1374cfc7ab29b995420995bee9808aec29ef7d1aac08c0ee51a0390330a863295af6129b7e8171d82583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000002bd8316507e6eded2034cf268b2b4660211e6bea2e82b3e3a0902bcda0f9ae9980b401f36178f681691ee7c10dc4ecf000000000000000000000000000000000e9af98fdbd02ef62ae90f1e87c4e7a8eb2089204b1c58dc6e59fa32d001c97f22740d8a13ccab23b5a8842b693504a8506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000aef5a5d5b46d340fccdfad359b0499a5c62ff4e5d9b9d6f7a5fd6a97e96820b7fd226e7a2aabaea392869a40cd38e1d000000000000000000000000000000000865d32d825149d26b60969ca567ca85af5e280b835cf541b20b0a4db83309dd2b5700f802ed9106af73b912dcf9630b7f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca600000000000000000000000000000000153310de30b7a485753dd8443f8638c12b21083f6133a1c093648bcb566b33f73631c6fc558f32abeb0d6df8430e61a900000000000000000000000000000000005be397e9f77556ad952dba0540f46cbc7db910d5203cb976e168a7be3a3b8557c5f08d51cca9379552694a291d67fb4429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c000000000000000000000000000000000cc75210c78f2e7903b7c33379a6ab412e92f35de51a152cfd2f4a5d122f9e558b617d8a09670990b7f056e95eb058ab0000000000000000000000000000000000aee8eda7c1bedd39f97efc60af110e64662b9990257beff15ef5e7856e5ea388df058ed8aa6dd93cf5a81ba48cb88854a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a000000000000000000000000000000000f396976e55dc0c46fc4543a8dbf690b8da7b6010a03e04c9010f01abe1b3beab8870be0b6a2c6d6afdf85c6fd38d8b70000000000000000000000000000000006c60eeaa2d94b571df8a6291c2b12b2ce9f17f414264e4af2a006d6aef2d70436ef0978139751d4ccafce200f16f06113814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa04000000000000000000000000000000000543f8d9faa2b3cac2518f1462c297595ca10d8415143c8ff3feecfa58b648d0dd0c25156287b2f29f3b6f9a60f02701000000000000000000000000000000000be673141c496cdeab5ba8604e081ed3006828c7c877d8990efd29798c1ceae3093e052f1f928fac0c5cf84174283844aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000001342f79c96ba0a29de9a77cc2e10314bf2e15a7d192a90af9c025e2f23ff30fe49cf239b180cfb6f8c35f95c115777390000000000000000000000000000000011f0bfb11be253b3680817af2b929de9ccf06dc574d17cf6680643b87e5fadd06b54224f155c1393c870c2dd01d6bb07c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e00000000000000000000000000000000183eab3c2a127818862c6cb42bfbc9d59c51043dcc28c68d3fea08331323c9dd50cc34a4ef66a97f98684a5d9a982a1d000000000000000000000000000000000228f8f774bb68f966f3ffab5d0928a59707d6fb4f6ca84fed831a8212f71085cdc27b1d52909bdc005b3250f26cff3b9680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd", + "Expected": "0000000000000000000000000000000017ddd94df17d52e842abacf3467f4461e345cbb680ae624f98c6885e40b17940bc240da17ed0a1a45f13f2ce4ab8dc100000000000000000000000000000000005ea5144aa5d5393b98db4d23477011638dba465a542dc28691ee2807ffc08413938ffb74e16c7afc507f85d76acbcd1", + "Name": "matter_g1_multiexp_11", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000023977b65312306b1a746b94bebbe79ccef0342ce833684a273d8baf74e0ee71104d6c453acf02d0c4f3909144b1a3b700000000000000000000000000000000050494df74705eddbf97da56a21bd673e2b0d3a9cc157168b8b413a89359c9c48f09e756f8e6ecc67811d4bd8043bba91ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e0000000000000000000000000000000015862e2e3cb73ed2ba6b0b69dd9fc4c308c0a79e5cca2d2a42fe94e9b029b22b5b6aefe0503798d78d4599dd5c201cd0000000000000000000000000000000000c49723dfa37fb1592722b14e6c75110cf2252ad5170131bb72fa35bc359470bbda292fc2a459dab89900eb251e848e12943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b1000000000000000000000000000000000af2d03791884033b8293fb636b0c569d9b008b075c6c71ddd7b0c3f5e139a17e1fbb18144d1ecf491d2fc40b7369c0d000000000000000000000000000000000d680b707e32626219fba862cbb18e39e03a8b9ac78f7bde619049748f7f0e49cc0223f1111dfc1f5c851229e62a9cdc1551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b442117cecac25834a442ef457061634d863875c10e1809a3b9464eef6760f074e06c046a74bfb34f4d16255cd4f62a0000000000000000000000000000000000febea79eb8102b2632b6fe3151d9d972d5dded2893a117a6cd7e2bb662f042131cf06d04ca5c88c8535155910f9e008eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f000000000000000000000000000000000d355c97dcf055181b8c523bbdf7eabbf064159c15532bef1e1be56146d72c08eb5d6994a3be7d6f4a4ef204f0e6d8dd000000000000000000000000000000000cd6d4e6df1ef7cd5fcd360e8aac511a3aea1f3e29536c193f4c3a2ff0f3ca16ebec620cecddfa8f27732eacbea75500f72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000072ff95f5cd9416eac2cd83781acf856a0bfa567a079bd3cc909eeaf5a3fb31090e3e2ccc3acd44b6b04b47b5b8609a7000000000000000000000000000000000b7a39ab3ec7de26c86eee5d8737c7ae7e5969b03457b7b7b5720e3492ce254a63e031fc477361606a24821830d27271331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000886babc1acee93b5f96e4a0700805982657d15170c77468c77000f21978f0cc154a265de2f766d6f7f8600f378b219c0000000000000000000000000000000013cc47f0a1e5f7315e6ddb9003dbf901824e419854d234676e4a8593bc5ad4c15e8c59ee6985d0b729e7d095e9b7642416d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000567f08c96b8431a133cb284144f6ec8f7c68722f18ec257b4def0a18a754507eb477f405b8c256adb797f45ed2755050000000000000000000000000000000004945b59bc84df7b793dc759bc2a3352b3eecc5cd59bea7a9560c06ef25828ad2e9ccdc6b3beab7a71a702b829208b8556be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef4000000000000000000000000000000000584ae62e22e0c2fd733cf2093f7a1f3c763453cc34a7a7a4548d8fd43c95f13be06da4e41f257f6d38e6e6921ad0f6e000000000000000000000000000000000dc803ba6a45298075a8cf45939a61760de44d22407da6ac0d63939918daa6f78e8d0b7cd794256f992cc89b8622e737aea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef00000000000000000000000000000000090848e332eec39e026eac0e6416d1ecd5aee8b4d82712b6c113da1e7d38901470743af43bae951d4141592f6057caec00000000000000000000000000000000140f8aa557213d49097ef315a18ae7e62924a97c71139555baf08c70674031934b629a457f75bd801af579f9fe9395579ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea000000000000000000000000000000000e29d6fd73f56b4546358967d7f0080e6cad97531e3d672a91a6dd121f35cdf0f452dfee1ad98b7c832c2878b495f3c100000000000000000000000000000000050fe9818b36baa8ccef166247bc673baa8424e19a19b199ea5e9d0baf56fd68cb339fdf5d041b31545e28bb2b8fe32c773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000c13e5062ec580886d09c87c7cc72f7f19227eca99b0092a7e9759672ed1405d21fbdc8985847fa1b57129ac40bb036b0000000000000000000000000000000007d6407d32f846088759be5369c5ab66d2f512f00c93eefaca86a86bf7b1e3ef39ab85fb6c317c28c4e331a19b927650f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba6800000000000000000000000000000000112839aa4daa7b0d614dc6a555731cd4b595a0495f2a2f0f1a3b3fa1b603c36348e265145583e8bdfa8a2a26c1f822f1000000000000000000000000000000000383bcca42f2513ce42342f4bab5377ec276bf0f1910718c7203d450f15c5b6a3648a82e4cd1222109171030eaf05292d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a0000000000000000000000000000000010aa255df04dde054fc069473dbbcde9c68dbd71048b195df2b23e5471e5cd39eab5658ce689ca09db80c72e099907120000000000000000000000000000000013cfb46746c9bd13aa88a24ef3097b35ee2302e76b19ed001baee8cbe5b19c2620043efeaf81697ce48af0717a1066eec629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b000000000000000000000000000000001888735aecb7125b08f2a840957887fb5be0517788a8931fdb8d280579776c5ad70e6454303ba23908bc6fb864a4ea290000000000000000000000000000000019479631b9c711f700ff2353aac97cd0ddbf14669cc046e686ef19ff0bea0aa74b4bf771882f7226de0d4fe356301912c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d50000000000000000000000000000000003233c1edded239fd465f7f7833251b98ffed6180b56676bcbe2ed361438d26db671c03a6454a4fda34111e358eb2cb10000000000000000000000000000000003cc9768ad0576a34550b913a895e2687481c6adb3371bad5cc8f9792c61aec555a52bcb267c337649fa00293c9b4af3c685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7", + "Expected": "00000000000000000000000000000000117879988edc3cc57fe19ab04eee6f9e94a30811b809afe922d63bc3d785a3a581b69512180beb89402410a4d8abf6620000000000000000000000000000000000beda376a5f1499882c560961f8b0cfc44c712d6691677ea24db18b138af8a21a5a4fcb9cf5814473b0ef7c00719700", + "Name": "matter_g1_multiexp_12", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bc83829ec8e98081abac2fa8e0572e819b570b2499d4cd1e6748f48c350c392f5d52c672dd0bbcdf1469414d7ce929c00000000000000000000000000000000007d1574eb65b391475b49857766c808fa95ac2a78755d8d740d2df90bfa9aab3dd5c850d536c9794f6cfa2f004b4550c067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000022e4ed74f98d69a9bb1037a307eed57210d3ca92648ca9c54546c7b57102558ab12f5d2bb46502ba3c07529f64b72b30000000000000000000000000000000005ea660c44a9d36696a899ed1bbef1d951656f2eae553f4f124ac9cee3d7de13556a7884ffc07e20d5afb7bdb9c6f1638b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d000000000000000000000000000000000f6f9411caaf7bbed9b05368ed8bbc35a0439a5c1ae417215d10adaab203aa0a607642aa8b94f4846add8f5f8db755530000000000000000000000000000000012eba1de04ecff3405596452a4f5830bc6c8af2ab0e84115a8a04a2cf60400eb741e8eda78ef733338494fd4e7b16f812db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b0000000000000000000000000000000009898acf9cacee1f5750d54798a4c31796fc471a17c9d2ddbd00262f5a82e3ca968c3e02334c29aeae9b16d8916def1600000000000000000000000000000000017f5a3907bc14b6cf182af2778c88704fc6b02d2b47bbbd6e40a448a89ad1455f868dba330452112973ab69489534ece2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000e782486684a6c3fd7f5977fa40038e8a9ac0a8611e79c18ea5328248be9ad4d95c63ba9ce41d3b4d85701283369063f000000000000000000000000000000000a98e9f649d2431991dbad1cc7f4ea0c89a58bd7e75e4a5bf7d9a728943363777c1cf84bdb1853a976e4e66a6d3fa8cbaa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a000000000000000000000000000000001171bd468b4d40e77b8264e082cf7a168d88ec3c21adb6c33f215e82f5ff3d0d2314e0fb12d7ec93aca92532debde74500000000000000000000000000000000099bc823a44c54fd379798eed2559d95275b324481c248d452a02755e1b5a48a7b0694b637dce4c21ad7d73a63cef2a3e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa00000000000000000000000000000000190f9d82f079757ad752b17b419c63ca09e3c8a23d0f56b1e738dc8ff4d588a4a2360687679e51bd75615c18c49103c400000000000000000000000000000000191b91de53dc0807b537540e81d9219daee48ad27de9e5ab2980dcc09062b80dad2a0a9024c5b0465e04e6ea2b225d0249510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e4000000000000000000000000000000000c8f48d3dacefba0e1719f74867b539a65d640d2372ad38bcfc43548f7ad3d8a04337878529119b9175068b511efb04c0000000000000000000000000000000003c7b5c11985fd7ff7c75e2cdd8670f75de655aa81f6b99206ed8a344f86ae85d2fb14bce434a25a5ee25c903c238341713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000055e115a8a7edec3a443354b381f584ba13a5802520c54b51ade1bfc7c93c96c7cd66254738929aea2e88edf2895d82f0000000000000000000000000000000001bdf3f4b489cc22c6f57a1eba23d3348c5567d0dd1cc82924873813b92a0d0b2b90727589028b9844d351e13c6e3868c040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae400000000000000000000000000000000041fd1625afa48a446454d6613c17cc6a65b3ec8b8f2125c0eb7b8e5d07968397d43969a6579226f496d9b24dbb71b820000000000000000000000000000000006131c506f243b5ac40354f826ac1838839eee9f61301aabd88e499d40e57df3122edc8b36f0a8b16b72f9ac783efd3e17b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a000000000000000000000000000000000113a08cd0728cb3bab3886681d8cd4e5f14b3a4a7979f9929ed4d8dc77de6a65f7bbbf8a282818ea3f21e6ea59ab1f5100000000000000000000000000000000032e95b26193c9768cc9967c9710c7695f57fce8a4e089f290526842963504cc8c99981bed3cc7d827eedcf686c813c3bd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf25752000000000000000000000000000000000ac19ea5cb7169ffa2741bbef922e0ba307e2bff5eb67fbd2c1545bcfebb79948489605f3c6c072444093e996594c95700000000000000000000000000000000111c277e16440fc3f0cfe16bb81b927cf76553fad040c1825210fa145240abb0bfc8a40a016db15844b8830d4d725da3f221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000906df246466ac720b1db9445902aeba8ff5c747133b037f29b33880b3f511621a0241fcc46adb0532682feb4e8819bb00000000000000000000000000000000145b356e384183788358353a69c49332ca137e9faf30bbcd7a67434a980c27630c3f21781a36fe73e82459318b59331bba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e0000000000000000000000000000000003f2f02b7ab2d2165836349ef8f53e42d223f4f6a892e7b72db93362de3929fcbda5edc4606766fe26ddfda9d09b283b000000000000000000000000000000000feb10a6ba91dddb0829cd6b95a78958fd55cdb120a7237a2842df1a2007530775848c3976804824698a4370fb022bdc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001433eeb265f1d57027a80189806d071edb1f5ccb97da0b5e00dc75eb88304ef2eed287f5d74264245684a1677a23b3f5000000000000000000000000000000000be2d2b5fd307192ef8a0b2b4dc9970c112a236a71ee899a0a5147012a206a0274d34901594f54bdaae26f2552da481b87eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000011b50012e0d92c0f74e3b6e83d60bf77e710dc03baeedc949c1af218bcb87ca1528a745aa819a5b615ac355dec360eed0000000000000000000000000000000013cd46e3cbe008dcec36e64285173b7d545359c23fea32d3a1fa2918c5c5d671a87d90791b70a740564c0f731fbb32013bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded", + "Expected": "000000000000000000000000000000000cd7a2b89d286a421808305db523aca962c3f253f6defcfee9b74bd7f00b3ca8521b520376659d584b03fc5dd736d3f800000000000000000000000000000000117b8b8a8e299cb0fe3eee45651069a21988a2421b1d349529cbaf7362425349148fa043581e5fd275cc94a4fce44732", + "Name": "matter_g1_multiexp_13", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016b98dda34f703f90438f5c2624c1ccc870b18cf8eb964800ec97179f67f82c521b1cccb1b81ebd3484da1349e4c0cc3000000000000000000000000000000000c743850f15041ed9023ce296570036f96db4a510903a0e7971592348651b44afc0091c8f0d6e86bbed8bd3f6b28072af44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000007df1648d65d140c775f729e7739a807a7f430de0711671807a7154a8e5723a2b9137175d47bc319ca659faca10af23d00000000000000000000000000000000199ebb99b555fa438587b9badcf5d7858029e905b97229f1de4ecc1940ccac59503e0e1a99c9571d50ba39ac3619699bde25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea0000000000000000000000000000000000027b64caa979063b420cff77cd259e54bf86498f87e7297651f9bbad6087a8b4b704b27746db53f8869d080a22363c90000000000000000000000000000000003239455ad4ab885727a25b0cae5115d87ac9ccfd93120ffded5130ac683b3b2485fb358e3aca3b6cac4bb3da5b4210d2e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b210000000000000000000000000000000002f14454852a72159581b8a931d863c65170fa9280cb811c697fd067a505910d17fcb71b27963c2a6a02264aa0e1fa04000000000000000000000000000000000303f0857d990e90e19a076d2d331f5eb7fbcf102dbf8d4cb29f159fa2277eb413c0c10c3b501cefd9ca581ca62876c5e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a91300000000000000000000000000000000192b3fcf7dd2534f226ad51f40e7256064eb788e7c91b1155908fb752ed4e854fda44af13f0c681fcb818eb4202eb64100000000000000000000000000000000125b51b4cf8e9427db9baeee0417b02c2d296ec4adfd437667238ffe5137b85b40fca4fa705f81d0b4b6d788a8456f1fe6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c400000000000000000000000000000000130798c851758638c03f90f9181814eba97c5f93de85a71bbcc360bc53e4491e8fea38ff8c94061cd5008b0333ff26af0000000000000000000000000000000014758dbfcbbf0e1c78fb3ad4945bd300a74f2555338a009d807e2cf0e5fe582729556bd3ecb79db131ed9a72c3362c37e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f243500000000000000000000000000000000093318a1c189c8957c9736a56a4b3e8da13bc8a303303bbc106148a0a7f319e30f5dcd11787dcd3424255c7a02cd3e760000000000000000000000000000000015f0767a3a1e3c448ecbd4ac8c4c70db6daec95a1e4b3a69cb5dc10fb43f8ad030e360832f7726cb166e0fe5fad0c860c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000c7143093aea0143c58e2c459472f44b6b759a3f036aefced481eef6fb3a1b2af72ae4cc4de06af2a8a99e27cf9cae140000000000000000000000000000000019f44d1120d82e50f7da3c1e87a47d3433152b7141e9085eb54e04f30f5931d067f9ad559cf5d092dbaece723e6a724138cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d14740000000000000000000000000000000012a171d46d2bbfab83d02e023f5edb18e353ea82174d1a1653952bbba234c7de4fd5ed212c81f795e8c7a0b81e37087a0000000000000000000000000000000015dd85eecde306a845917187c404cee066038a764beaca9a58b859873b06652800291506b4c995581866a3c2bd7f19618a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f800000000000000000000000000000000176e512a4122ef10ca1fe6626cd2c839d4c573bede92092e5ca55b0bb936de9b62297b2a598a033e9a7e49ba9aabb9190000000000000000000000000000000013bf0f4c0dee3c9298192748497803a906e4192333b1ca61deff010a63eb8e4cbd63c7bd5b5546540e71bcac6000eb5380030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f600000000000000000000000000000000066bb65bbc3f8ed9cdd5cfcdb121274427ab7dff904551a60be48f8197c84400d54ec27ed25c2a09687f1067c10edae5000000000000000000000000000000000afe1e97e1dcee30959a6411328f0d69134bb4c3a0d5ac53b87f254593f7cecf3070eaa9e19de76ebc6e1052a41ccca00d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000001439b3031d7272f92c7072c6b44dd3a1c328251d34e1fcafc5f864b7072086168fa6f398d6334fe7fc56d6fc0e776eb600000000000000000000000000000000090885199f56df470628357ad224e19c29dc435ac54b8c17a7df5cdd24c3fdfb136952063dcb446ffe271ab5775bbc51969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000011a0c8f7d76a36e605f193efdb5f7899d7db5b89ab0603dd6184e69a7e51f0d7e12f466fbc917cc5b6dd6d4a0bac16c30000000000000000000000000000000015dfa17cdd22984bec570d2ca24a5ac373f6f174b66aed70a15ec892caaf92c73ad3d7ef11b2f4a0104df8ec5397f5e9957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000004bfe701f6645589925b34c1117cf62752b4e242e38bf056ef36515338a5c3698f561d65b237123677d926c1616618ec0000000000000000000000000000000011892535443daffffce0867dee36b7bc711006bc0963e6a061066b889adcde877a8dd3661250b6bc48064ed9dea304168e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d50000000000000000000000000000000017666cd38f1e7139fd032a79776301e4eef7fc22c144900c711f1568634d9712b2e3566bcfdd152faeef20b47cf6cf7100000000000000000000000000000000150c30df0eb5945ab96603b0f36120a4f697b6958a9929f6dd8d1b8a34a1d1d3f1a34bddf9ff7f1e105ca23ac34b6f7671a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000107f9378f695524614ba000d6fd1b72c5eafc4ee60c5ba36ddb72814936403fded547f8d15083186f7f5f5d94c1ce18300000000000000000000000000000000140bc17d86038d4fed0580582f55d90259b460ddaeb37a70063d09d83f5fb6c803f8b467927758cb7cc52a2a6f8a84ba2d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641", + "Expected": "00000000000000000000000000000000179ba87e1596396fb5cf88f91f56e3fd6f01dda6f95363be3365bd42960ed620b7648863524c22663bad20d128d22f4c0000000000000000000000000000000001ad84c4e62b0494bab4f2ec2931d2331d88502674a1bf58b765eeb022f3d6fbe8f8546818d965a8be79a94be86292c8", + "Name": "matter_g1_multiexp_14", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007eefedc0360b258ca2bc9add8e23b9d535f35332e7a35952fd832d7fe3d448aac08a01073876a21914a501dbca513850000000000000000000000000000000016188049abc44154b244c6af4e115caa14a977efcdd524ad78e5dce010f2f48259708d14454630eabf2318bb271315007740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000010a19a7cae27e432b77c77d26653c6f17507413a5037621bdb096fa4f33e68dd86d5aa3b52fa54655730fd88415c3eea00000000000000000000000000000000031925aae4540280dd6d08fc53478fbf05b0ec784d04abd04c3a8dadb04ad9adebe87101c6401ebb4a808104b3d7e88fd226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000000447e249cb49d64494fb1f1b18c94a44791fd8d4957bac13df1f992480f72a14c3aec517184700d87200092e866d60ee0000000000000000000000000000000018a12284086bf2f64297a65f6c8b55b4ff3b791372b88aed9085152e24b1214655a74a182e131d7023f949c8cd9602dbc133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000780d3f5c10ab7932e3e3b45c942d1ee2a12f28070674d9c666016d084613f3ffbcccfb576fb7779feb2d0e614106c990000000000000000000000000000000000ea320730367c89cf162305c69ad594d8730d71a910f53143770f50024bdbc40b7d2486b1eec63b1ac7dbaeb51ef9640fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f00000000000000000000000000000000049ff517593107482da6805fe4ab49cfe9cf71c9a95eba00091511719eb76db98f71f089a701c6c136b398a40dccfee700000000000000000000000000000000038d1566f1057bb2da7813c39374b79149e598e1651dc3541a445264693495dea35a6515dd2173f7de43964dd5e8257d70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d00000000000000000000000000000000016baae36e71ce87a6dd7136f7572788c256ef88cb73e550641f14a557828e06ad64f001fe78d69465fed92b67e8dec3000000000000000000000000000000000613a6b87249bfdfd01016ce920aaf902de85c066c2d64c866ca0a93950a1a971cc561560a4122d9a766e38f9dca9239ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d721000000000000000000000000000000001921d310700ff4e2868a28dd29ae6e0216bc27ee9463cc8dd2823a1b4670abe973859e86719142525ae5c76e2df0bae0000000000000000000000000000000000b4b4952e96be92ba6c78037e529c197c9404cfb67af04f39d24045c742b34a700057b2cedb3193dad70e64944642c01fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000001dae69033cf21e6e1618efba143426df1501250c82f214ecc9ccbf957e685d9831533cf7f747fc22309227aca1d1a2200000000000000000000000000000000114abe65155656679b89a11c7961435ea9f77fe2f957833dfb61b8538695e2569e509f0ee2c0bfff75f83d9399a3d49b5af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d700000000000000000000000000000000128e019ff92e7171d3c791bd4cf75b0f47c2a9d8722b4a8279f1178db6dddf8a4c00083a935168518a1c26a56b23624f0000000000000000000000000000000008d0c5f3300e73682f4756e6ff1d6722dde576beb587301ded34427d6935e59e76cc8a8cb0ea5f659db9ad5435851e53c7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f1000000000000000000000000000000001110168c2dc1c2f0df0dc645970c0feb03bd644fdbe1576d5e5a8090282bcb81ac9be738d18e72a31ceeb5ba826b40290000000000000000000000000000000013fccd2429da394be698812af6c3288e89a26f0244327cd38bc85d5c3bb934004bfe24449534b7d271add7a279bdc8512d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a000000000000000000000000000000000199b9c9772a8c1bb0c015c467098bd38b5f73e5d0b3f627c8279b8dc853fa2952faad01e7be353a2762b8144cc1614c000000000000000000000000000000000f781597005df947eaccca59939253b936d1ae84805ec27dde0dc707a4583af408672addb2eea607a14faec9dabe61ae3549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab710000000000000000000000000000000014bd5d22e4bd2f7b8df4add90446650fd83d72d531395fb35dfcff72eca0886ded935e7a0e3fc99a7dd07efa1ed60c3f00000000000000000000000000000000122cfac9ae5c98dd162576c92e9acb4582b9eb67117bfbf4074654fc8bc473793a7139995666447a7663f3af1446dc35c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000f67ef1eff6875abb96378e5a7b1602b5dc553554987589b9953c4401fefdcc5cd7b196a1a65cb3daaa13f9fdd703835000000000000000000000000000000000f58ef60be74af52c23662e6b405f1d5c359b2ce9d15b5e139460e10da0e31161fb52f529c7b406e52c6f600d5670f3c9ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000000b7dc680fbfff55bf0cf276a864f448d5a9feef303d2416e7d87d6d669456b951a8769026bbba545685e1f92277b182000000000000000000000000000000000c36a14d5693b0d9d91d831c0581d1f4ee801f86e5c32f10cc400f66b58f247594c30f0059b4ea79995d6f9d90b0009ebd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b0000000000000000000000000000000014dd947a01add8294f97a84850e6dd11ed4a513e7656daac5b725cff501446e95e3b966492e028ec23fe1238b53d99ea0000000000000000000000000000000003d9726342018f802df12fc867998b6016743739a2a4f47e1f6f50992e4fe23a6bacfea0e7ed5be570eb8242ec4101ec10bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000014182228dbd223cb5b601521608bd7f87659f86a7a01233d4158484024730925e3d841e05e07f2a330b9495fb028db6d0000000000000000000000000000000002e0ad163d40a56215a774751434d19ea17341f41701d41e521983ff753ed76c435c6e2b543510e47060edaaa06d29f665c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d", + "Expected": "000000000000000000000000000000001364f0b671bbcf69b3a101dd758ce54434d59fd053591cb744841ba2061bbe2be91cc9f2cbe1ec058119ec8a5e909024000000000000000000000000000000000cf5be1c16fd10ff97a154826067ab7cfd7b78ca5ad0a2e2117155f1ee126286019f8a1049b86095887e79ba67216a33", + "Name": "matter_g1_multiexp_15", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010040f531866c4e6fdc255e2a7ebcea89ffc36d44e265d5129f8be44b07f00646a7810662723546ed158b2cef146c7120000000000000000000000000000000016d6a5e46b2067c29e11d00b6b6ae9f0987afb4e9357c1d223fb2962589c3527f94d4e01f2ce6a7c57f971756163e48108e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b790000000000000000000000000000000002b6e3a234119f0f06a2b049d952230da40590a84d241ff76483169428e787093ae88c4040c64f2f1e3aa5be2c37db3b000000000000000000000000000000000732aea9a2ac5612ac350b474d9d267dd1ffa822cead992d3eb411efcb6992d196d66868a0e1f89dd47da584d075d4f55e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d0000000000000000000000000000000014c3187e04024d719560e36b5a63228a685f085aa080c82244a3a704aa2ed68b219d1c699e49dc1fd648e904ae638e3d000000000000000000000000000000001911df5a9f709b8434856c14fe4111935156a984a5e8cc27081059840167c3daf468a290461bf6cbd2ea4fa21255d7c11362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000216ba7fa8afa06136b054c11bbd978209017dc4d8c8a2b05fa717a97f4d88abd9efc1e9879de709b87d7de65c859b65000000000000000000000000000000001797c34bdde358ba5533d5bb531915545e3ba359ea1fd66d9dc2ce06f7cdb64684bf11e5bc02097f3b957957c986de1074d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de0000000000000000000000000000000013343f0b79485528b8a5ca5e0780e8925ea7277970843ce3699046673a41c977dd0cbbc97273ed47a1a105a0017853340000000000000000000000000000000010f3232b511b8d529f91f1ab613af1e2443947fb2e29c4f98d1dbab1aeb965079f64281d0b10e58e26a4bc0577943873389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000005aa017b9381423c9d00982fffb93a7cf9bceceaaf31895a17ce3a9bc42bc5b6f5c69679ebc91c9e5cdaf7651cf78621000000000000000000000000000000000c77e86d84377ceab757a0da9bcea401b3db29e8e577da793da0d5338eb471315315171ec4bab4e9dab36f4ec6d907a85f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000001971e5758027516443fb373a8ba8cb98b78fd5d16b42a83becd2a9b06e8ca7d255fd687cdf10de7dfc6bee5cfd199b1f0000000000000000000000000000000013465b45ed2469c2dc6ef4b4b8ac90b9b30c793425093898203d3b13d76cf4b8e0836c6fe57e637a6eb08bffa3bb55250dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b6000000000000000000000000000000000c14833dd82daba173eeb40c29912c0edacff741bc3ab03ae4911c334cf91d5832a8847d7e175934f61089f523b77fdc0000000000000000000000000000000013820819e27a27009ee44a5cf02e995bb317ee49b6068d2e9f4c5f072d233a6808d0feb61958e047f70b2bb1a5426319c579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b53600000000000000000000000000000000105a1323577a38bc9495090b4d023a9dfed8b510a9a6d755f7ad6af72eedf1c92e6a5172cf68608d8dac34242d1e0eb200000000000000000000000000000000147d889d919a58de8aad3b4735359201c47d8961a1dbd321061a81c67b1a05c6732782975445d9c1f2aed12b0b7306f469f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f1000000000000000000000000000000001335049a2ed3629ca83f041e4ccedede286445e4b79f3afe225bbee6273e0cc84b32b91c54991dd072c54ecf0d6c538e00000000000000000000000000000000098220fab5661a40cf34782efcd62ede159c82dba8c6e9f032f7216b888ad85fca1031c4622547a03f14185b3eb6d0d576618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000018799254b6fe847f53e2892343dc77efa3717bccb3589b776584fcc9e934deb3b8fa4c1ac0709ce505ca4d1504ed822c0000000000000000000000000000000017b98c35564c9d67b77bfec8ce23310c93167a5f75a4680420e8d71d8851f4061d897fd86b52d4a8cdde391c5b21a63afbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a0787617788000000000000000000000000000000000499468c8da336124bb89285a81eb76fb05e4ac2bde68d2f78f1de8926109631ee3e33eeebf686c7f6b7b4d68d13d2fc0000000000000000000000000000000001ac43e7c6d46e88d88a195180df6a3a91b3aabbe54f88c8b39168ead4b9847a031561828b0076b9b94c8fc7cc0c4636a0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000006690b59efd7c3e7f9477cc35fc5e13a5dc7f485100ecde7771e7bbd9f79f72719cd45cc9e0e791b7b5dee6f0252c53d0000000000000000000000000000000008b6f82c8514f7804a1d75f347f08334064b81ff95765355550c53098e19a4a5fe59c6a9611f4795981047754a6304792c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b0000000000000000000000000000000000ca51cd2fbe8d015a2e80bb4a24f52abfe6b99b1fbf1b656d4398f76e8e73e7a441dcacb43a4bd0a1dd45df2ed03a4e0000000000000000000000000000000006269d0e0f77f3ac5af8f70905ddb323362ec5de91a1eb90bf3773457a2bc2d018942e58c04013b83a7764b6639ea87c141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c59200000000000000000000000000000000122f6c35f7b1456952b56a5f90ef9066a191a4164d4b2f81965bf7318d485c725141576e5a1164c3c17a8bc387c9262800000000000000000000000000000000086bcc20a2f0f0afd4ce845243061e1c12eb238f2d3fd711000f259c31d826c2bb56617479139cd611d35b6548a438101d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c00000000000000000000000000000000008ee124fb457671b65c0f9f550ce1ef196c3bf13a5403a3a21a801cb1a335012b43cbdab33a1ace7f84a998a4322ae20000000000000000000000000000000005b0067f853d9dec4dee3b2834679b9145bba170f22b7e1dbbb6ca3dd98abe4f41673b283f9c43f2cc7ee2305b874a0e1b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4", + "Expected": "0000000000000000000000000000000016dbe06af92533e96177581a7a349811e0a3d3826241c4ca047411d5b24c76dcb6849df7f4ca050286b2d84afd71ec9f0000000000000000000000000000000012dc4fc05526d6dd6da27b117294da4e9473a34699871b7bc9f02937a8308c57b5387a6fde0dd09e8a01702a8b97c4cd", + "Name": "matter_g1_multiexp_16", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001394f8d94cccdaf982b1c6a8080be6bbb65c9352a961cd5daf2f817a17bd8d5e3e086c6f54f6068691f3edc4378215350000000000000000000000000000000013560d0482e6ef2fc19cf274f85bc3d14236273dd8af86107839882dc26dbe897a7de90ab5457ca440498265bb59e59358fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000bfc36885481f9ea9aa275c1b4a774fd01476c6f956fe75b5f6e73199928b1928108658e35dad50b298307598582443a00000000000000000000000000000000161f833b58de4db4de0af0fd17ddf81ba20e4b6ca21dd80852cb992afce9857e6cf99cc580664a970e9c6928d13dffba73b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad00000000000000000000000000000000042489a05aecc0fc139c0ef0c703860ed36f8bdf50e4c772487c0d27b46b395f6417ae34ee98290a40b3b765d5a41d430000000000000000000000000000000018fdc2c8ac7aa01ae6dbf84412de8a47c3c504f2abed060c63190265babf779384dd6e3330e91198f5bce5a103bdcd701ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000005a44b3af7b95c7869d74c7084d0e556a67b39090b7a62fe51fa833cee316044a26d4e383695ecd3bb1715d0693f2f1a00000000000000000000000000000000112fafd6d6f1da250d12817711bc999217d16d7a6a923b5e11cb91a333898fb27f7b89885567d33b39923d7a664960eca691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000197317f509ddb9d536845443d7966314eca15f20cfcbf3ff2f8701d94974e35cc0957855e0085b3f85c7da512ea882910000000000000000000000000000000018b1ddc196607122be575ebc923dee96823fb4f8ed05fd8639b1af06ddff25398e67709809b642d4d9c21dd8ab6e65470d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000000c7ed49a60aa90f074af9f7fb19f6e27ec4a83ce2ed77a44c70c8e0bec02318bbe44a212c505efed3550ab6a1ea2c6d50000000000000000000000000000000013c0a772ce2c97522607b1b05cd9a89e930b6371202b69eddd108237f1495eb1c6ca65549c5ab030cc4f7e3ff4492fe9c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000fd64797f2bdd429e6f5217858cb14d78b7054b178b74696b8bc8ec9f9ede70bd03c36c824a3f775ee2f8cd6be7e2ca2000000000000000000000000000000000f675a8a43da599a09ae2367240870636ed385eb280cc199fb7c4ee575f5e3c5fe0b302566cde70b956f3c2b20fdf09c6fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec500000000000000000000000000000000065856fe1dcbef934cef47b177ecb7df76cc8796624400d5c0518aa9438bcadf397234808d099bed89ab674560ffbb1800000000000000000000000000000000071b2ff64379ed3e20cda000602c3504616dd673aebbe7690e797d6428ecfbdb29f11138169f3462dffd319cad68b96ebffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf00000000000000000000000000000000094d6e0bae02b4e7541a27111092737e7b27fe742fd0400672953d8fd787482195a2cb59a91e8584be002976c3c3e9b8000000000000000000000000000000000c2146b68ef535ed9efbed7fd02ea5cf6ba8cc20ad8bce17c06e5d595282f6e7453e2cd267181e477f511cd4fd56e8b157f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f40000000000000000000000000000000003925e9f1e24531f9f26547108671a6a0fcf58aa6ef2bcf9f4f64b659782b93187bdf2988029de9f51e5d41cbbc4744d000000000000000000000000000000001975210e2c8bbd2431288a42f9cf5d6bd6c6afa2eb05caebe740c0a1f680b9cced0f32f8f84e368563183b97aeb6e7ef2d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba8400000000000000000000000000000000004a2ac3d53c193265889f6c3802d7c68b938ebb6298dbfa14d1a9f515647482c84ebbb3855686b544d4299554473f1d60000000000000000000000000000000003283688bec2b8ff2e34565f8e254d579f57f9c0fe0e8521129088099a5005dfa9d565d52a75a2b26148205dae83aa6a614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000001b82af64f984294882fef7e5ba880ed8b0a36a90a5e9680ddfc5d86e65aafc3899a7d63e2a420113ba29412a025a0970000000000000000000000000000000012b11a5bf0f7895e329c2c6bb3d1737aeb5fe9f32a96262d8268c74687a460c47a89e252e607032576e7b67f5ad655b87c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef72840000000000000000000000000000000003ad0dbf936f79659ccab765a61633ebb648503a774e92b24967aa8f8e45c5e26f03acbc7984a45e089ce68c5566664c0000000000000000000000000000000011686f58262dca9399d95cf2828b50b216e1df251b61c77f952c21374bcdacd99d26891fe5f335afb7ec76ce7d95b43f64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f000000000000000000000000000000000ddfea60c169079c0fb4b9c3ca539e43b7f184f31cfa2eeb942acd2a84b472597c83fb52544479f326bd1207b4e872f000000000000000000000000000000000102108e827cf4473ba1382a2fa8f3b904f20a40657784d54e3a91fcf2703dc6fbcfb7f4b0e04c3a53a24a6e14b5735f435bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf000000000000000000000000000000001286a578ce3829f289cb98aa41cb6bd7274aecbe15b5087d8c16d575fd991878b06c88f17fd4bd905c4576494ca9f8fe0000000000000000000000000000000018e3cffb0746cf70aa79053ac579c1adbb09ed5b6a8b5e7b84951460e551e9bb62f2c1968e37ba34f7633e60a5f1f2a97980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b28000000000000000000000000000000000ad648d5e0a45c8208fb9b6adcb3c47cf0e20ca906c4fdb31e5c2f0678fa3ddb6e27848a39e8035cfd9eb91aeea824200000000000000000000000000000000005ea40be38d82e2b256bd5e26e71dc642e06145d94c1ca4fcfd6e63e2bbbd7b7aa153b498793e94ed1d89691195b4aa3a256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca", + "Expected": "0000000000000000000000000000000013a9e1e306a5cfd461a1df4f2097f0beb079a845a959ca3a6d3e229198c75d29daeb5e7b65603397353cf800e0a10d820000000000000000000000000000000016532afaf2b6d884a5b593cb8dbc38b4e2bbe522ac69b745fe9938834d66e2393653e31b052a803f3f901abdcb352eae", + "Name": "matter_g1_multiexp_17", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a187eff5afd944ea8731afffb4aefde4046b812b47e7cd99687ce40a5af90d6a4a2c7e2c9ce515a229e6c99ce46933a000000000000000000000000000000000121183879453793d954c99cbb007ff428c721d0e0b9cef192dbb177696ab9d575d3ade2cd56964428adfbdfbafba7505805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c00000000000000000000000000000000196b029b6a808602b09dd4597db611f19bb911b3acb5dce08bad8676cae9910865355cca0a109bb8d7b60359da6d0544000000000000000000000000000000000cf045d01c1a6d6ae397b39833243ad3cc310be9220f71a248032e9325c7186ce3ea40fbcdae5258410e1a513b69713e502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000000c6578ed0ccdfea63fe924d0a30c4aa7d65d9f85ea832733013c0ac225f039bd6f94b4acf634a01ac67b7165a810db8000000000000000000000000000000000624981245bedf55b95217691d9dfbc16d0d83476f8c09a46f9541d77c29ff978ded7fb7fed7272701e385e016647463e7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a74000000000000000000000000000000000a406d8da1910d9ae8e52ac70f1fbb85954ff7590863ba9f6e00861160f83defd24e99be31ec63489a483fa77d84ffaf00000000000000000000000000000000170bac083f0f6f4ff5edbacc5cedbdfa314de364e86486cac0e656d27e6a4880ea3f76ebe0f69927299bbe4a734e0482e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba000000000000000000000000000000000b8a583c24eba7a27a05bcc606a10a306ec07401ddb8de8e9bf206250ab7cc720903bd81a2c17a9e075ecf0ef99ad65a0000000000000000000000000000000006d5c7e9faf830ebd0154dc1c366b84445a85f0ebfc13b5339294752f4d1dc352e0e4204d9d64abed83e8297325de2556e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc500000000000000000000000000000000122735cbd1927c40688662c740db5cb78425942985ea69c729061a6ba749c78d4fc3d894d07c285aea9ee104f59581690000000000000000000000000000000007c18425af769864f403c39ce3df4f07d4b7319342457d0dee30ce4bab013b754e2ab7492f2dbcd5bac2ec1ca2e0220f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed00000000000000000000000000000000039c8c0453627d13ca0e174f5a27525f8a0054ced2b9e7d92c0ba7bcf06c78c1e1632db35abe2a81f72b986934ade66300000000000000000000000000000000134876b42096d986e6004364e176e23f81637f8ffd3dd86097f480d25aca9ce3a96c9dc73b651106b4de307c002dad95586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000032e727809658a52f60a973d32bf47bff5fc45404e6652facc1801d52188dc7db79ac1bff415a6c3e49e417f205422c7000000000000000000000000000000000c83d3e5ed78c1304f42afcc0143f959ca24348510e241c3e79ed5eff8742a89b4ce131e63544b9497c2a1712999a18cefaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e4000000000000000000000000000000000c2bb8dd01510ffe473715d7714e47dc8fff0f24af49405e55a9537a617dbf425950ca3df681f1fb2a3721afdc5a4d730000000000000000000000000000000019fcf0bdc8cf0f14c4b8eff44ce2646feecb3ab0555f530f579cb2764badb6445166598824f7b0c46a857865ade1278239d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca80000000000000000000000000000000011a463b5221e4c3abd9652917379564ed2830907464235fb6f9038f1d3a4f0f8cf9f6ccbbf66c86e216975b2d372400d000000000000000000000000000000000f0e9d5050d539f9211ff7d3cf3f0e7108c5580b634b122810c78d8fe047ac709bbb06ab1501e08f0e58093ba8208e0d4c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a0000000000000000000000000000000010b293dd411de6a5cc464e919d290d6bdb51952a7a68cc27aee3ec80808bf05a50763fd4c17f25e94e655997bc948860000000000000000000000000000000000f18c7ab95bd74d9095ea9ea66b2b14987157162b8b8a313a781ce58b05d2307db4e853733a45344923488ae9dce1a459af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000013ca27fdf920f901634156567835601ac0b84efdc79d7d979c2156041bac04f3297c1799d3b0641df33da9647e604b87000000000000000000000000000000001527cf040f6c84496ceb57df9c9ebda89c394eef034e40f5e6b540e931775ab91a4aebbf6078922da479ff397cc5271ac72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b00000000000000000000000000000000197c0e4474e27fcaf203819563b86e725778409c7d6792fe41820c495e412382fefda97b7df560885082c70f9d522024000000000000000000000000000000000b14b9d40bf866d933a15e16f06ec16b502ea8e7084d68c74418414fd281a6da50bc443647fdba348b26b4a3490d0ac4b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000a254b07ca0f2c9219fc0dfb49bdd7901999cc85161f741500a3ae8be566e64f8a5fb3e59985444206a2cd02ed4ee99d000000000000000000000000000000001726739e92da7bf5a6d2dfbf96fee6028fc7022cb1be2f838ec1b9bd08ef141f4b24e093fcbd99080721063f8b7c98dc986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000001133389c12bf1d2e232cfef1a8303a733edb0dc4fa26acedbb288166fd232b79f67cbe76227ab2eb517f1c3126b929a30000000000000000000000000000000001ca6bf5c18255bb3c533ece833964320bee7c3da4af56d876058edd15f89b0ef147fba11e1d539d528e4bc452e17df8979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000000d0caaa05d3a01c89d6efad96f5957f1f9338136119e8530853a58c0698583d834fb0f045e212e6889d8baaa49815c790000000000000000000000000000000009e7fd124160f6ba3afa752b2557f1c4b5f4010a6d4a3c8a8bfe350c6b6e198b9e3d11f2ec7dc6a02dad4c07bcd4bb1d25ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990", + "Expected": "00000000000000000000000000000000138cea47ce2ea638f06c26d24ce87073f98b023b800245b6fc74fc2851d79a402b49c54e5df4e1aa98e33801d3fbb965000000000000000000000000000000001558e37121ec3710ff5e6c2a4e418c803a5b83cdeec98c8216b8dac7890ce17bff08a95ca2aacb40eccc761c8a31e8c0", + "Name": "matter_g1_multiexp_18", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001920ce210ffc78b2c053eb2106acf1e238ac5160b50187fe816010e8a95ec632a7fd29565aefa4bec90d87701c2610dd000000000000000000000000000000000322ce646a20e23a1a68361806cf072ae3d6310f4055f5289ace0036a90b5c7ada537e614780156f6a103ed726e15b4fbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee967000000000000000000000000000000000a1ee4319282f43ab9cecccf2c7f5e08f35a6c7e7bdc8dd2f4d642e8968aff377791a5d1e2b2152c59a8f36d9bbe04ed0000000000000000000000000000000012e60ad9f99f55859f2529ce02b8b41f8565705455cdfeef3cb315903ffbf29fabffc2546359007a36ba579b6dd06c2043cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000000c2aa9516360c840b7f88ce0cfaa0ebec502bc9cb9304c1a4d895089a2344bdb6623638e730cf30c66d977e077423a000000000000000000000000000000001163f60b32213940c9cfdeb2c86d5ccf61c0a714436b3d0923ec338ce7bd35542726a87a1311c8072fd589499c26521d733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b600000000000000000000000000000000116aa02028755dd5195ce0b2d3234d31b07b557a52330fdb50064a18015ae630f427a4512dff06f93ae67c4fd0c1e10f00000000000000000000000000000000117d4a68064b3f11d88ce976ed43ceeb742ba6f473645995a2773121b2b8edb8fa2715f51c8be109f8d91c44e8943e7c5112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea7100000000000000000000000000000000166cbdb131fadd6c4e7a94af82ce4fc4805dc34aacb0d6cd89e69cef0b9071b112ea4a7d9d03e3dd961b5d833b84195c0000000000000000000000000000000010736a73e2283849595569db9a5b0b9cabf2182c3d8c40a39fa32abe52dd6038edfb8176f64ec12671e3411dd69397585683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000ec2405173e541945011d09092cc3a71d9dd1ff54451127181bb2d5b50876a148e59f298ee30ec5473c520be0a53d61f000000000000000000000000000000001239198a5b1f6f57bce914583c3bac476a922e56d2bb30da4912acd31cbf307bc258f22fd9f6a0073ec48dfdaa4799bb5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000000232940188006769a382a4958383aa6702b2cbfb9c2907a989938ac618f23e241767b021e8ae11c23617ab393d4f85a0000000000000000000000000000000016a672061fe76ed943e36b2d6fa4aadf579db96eba5e4c60cda2884ddcbb0f37668638a3167d8858cd296917eaeff8e0f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da00000000000000000000000000000000066fe1f7cb3d67c20a1ba29a52c0c86d6a2aca176630ff20d45632398a39404619e55b8ade69e0cb0b7a6f363c3b2d4d000000000000000000000000000000000aa25dbff2a8c1f1d0982a709fbe88563813e918c8f25f0da9c5d0dcf3abc257f7e32def4efbf74035aee1ee350cd4fa57a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000001e54b0e8f34cbfbc20c9feffc555036857c31f453a1bbcffe67bb71d0d6b2b278b2ec5d6ab6648b397c9255a1139993000000000000000000000000000000000bb6d6c1a41675b3394f5b9cf14ddfe73c188592916f24240edcf0940fdab1d1fc04a11bea4af90d0d9f6734a743b38086ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb00000000000000000000000000000000099f521ecae704ed5a37ac90dd4beb4fa21ac197d467185c8329ad7b87c02943a228285b109178bbc2606e89699403ce000000000000000000000000000000000a95a85f84e76ebace78bbedbd13c6b79a6339dba246596e0695aac18d2b14b370c033e62a01caf8484dced0ebe8a76a03fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac377321360000000000000000000000000000000005b8ba4c7d3c83fbe9bcbcbf60b0b3ce42b52ca19a5a322fb18bc20f81c2fcac23e1f62b9fd6edde5ffa2e37f685e06a0000000000000000000000000000000008c03604012e4dff47923a2a43382edde86c76754a1073ba51fa3a2ec7011268ffcd1452d46786682ab2ee4848210cc635ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc62791000000000000000000000000000000000869a2ec19afbe70ad0a15532f776f56da5d7a7dd5b75194d0c65d0304c69a6d0363c0ff3b549e8d15171fae18ea13f8000000000000000000000000000000000389d0e6c9d73bd98202191b5b213fbe77bcf527faf98f4d25c9dd3ea2cec8f3b1e8f261d9fc8baf7b1c21dfd102f99104a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa0084527000000000000000000000000000000000138a4f53b8fcaea11869a6208e7498238dd80be79cde96885e6e5226315deedc98a17f8d75df733ab6f15dc24efb5c5b000000000000000000000000000000000d25d69d6d5a9c597fbec8aa7fbbe579dd86c5fd3747378e984c20b34e018b83f889bef3069c693a91ff552fff1fb8a403f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c98162100000000000000000000000000000000192e8e186cc9159d2207b0af2dca495e9d0c82fb376041360ea80562e470168b52a3326553902fd6f5a43ead32eb968e000000000000000000000000000000000fcac12d18fdfb661a12d112fc3414839bd34aa244ce0cb40be79718ec37a014b43856e5e4b003f4816e04ce612e63ca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c942342000000000000000000000000000000000b860984aed11a63656e3390f5e94695d8cd9367ad7961c65d714637c68ad88a3602699ed3f627f0fbc5782ff18775af000000000000000000000000000000000ed00636e74e8163645c43b8b31f05228da7c42aa332ca250270e5f14b3660fbadb8e8957f52592d942b1cc1bd2eb0a50033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000b162c0897755fa47053e45ee1b298404818ca282a7b5818364c292a6052703502656e536f2dfb470730e9bef0d7cbf6000000000000000000000000000000001924ea42eddcddda067126534e8b862f0e16dc0cc296ea892115a9ca9734fa03d019e90263be2c909528129a12a68d874c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87", + "Expected": "00000000000000000000000000000000056604e75c1069b4a061ea72cae2cfcba90130f6db6d9c268649089ce4ae1cbd80120a086e3f066f2584f5b2233b468c0000000000000000000000000000000018c5e93590b89119ad7d44f80cce5ecd97d4a4d3010a19fd02a58342752b166a1b82dbbad30d6f3404b330dba6a57454", + "Name": "matter_g1_multiexp_19", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015f9de55b3b45c16d59adda55d9f5059e765ddc06d22d6e68c099358d8df0229c6fe368384a0486af1cc9e532f78817a0000000000000000000000000000000018b992d73dd4c602afd82ad0845ee2c6662c860c5b7be197c62a8a20e91764004b5293ea40602574e91c313e8103e7a1dbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc8000000000000000000000000000000000157a9795cf9a45d2ea5e0312783829cddce176c63eb16195e7994b0688f9f30a4f2b2113e955bc66dcf05b5441521889000000000000000000000000000000000dd9365359ce805327b8f627f02ef5458cdc806bba246dbb21065c89e7ac6093004d214145cf3dec605195f14f1a49d357df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec0000000000000000000000000000000000066d9a54dbb5fe64835523e8ae05bb70b1934e389db0ee7547da60e4af965c7eee14a148f2e3269f01e8a545480db610000000000000000000000000000000017d6a22dffc3eac4366d0d35bfdd053d73d7b3392e7f52fe04e7e481783db3232f85687d2341358d2148fb3af7e9315de2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e0000000000000000000000000000000008be91d2752203afba19d8f3660118f83dbf851a6d2c54af389ef979121c55426d0761812de72a79d46c66dfcd00d5cb000000000000000000000000000000000269b050e36718ef4ebbc89bd88106a4043b267d974439855b6027f7fc3441518c39af6d3fee46e87d399d3ef03c63c82897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a94000000000000000000000000000000000dc976bbec5c5791688499da28c1d120e8a68eb5511ddf54525c047378016f68e8590b95f05cfeffba56c3daeb0729dd000000000000000000000000000000000af6e02afcbb707fd4d8bdcb5e73e1db56d7a2eb02258b91ec4a5c46c4627525220c11e6e379077677e1b733e2df60e02f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000119b86eced2222d203b6428907269b950bcbc1519859c013349b1c7acf486d3da5c4b35319e6b1ba8ae815e4ea14a6900000000000000000000000000000000015c342be097ba679319b83a68164f6820e2ceece3a90d1ec296514f0ccab6e454a0fc444d599a812bb4d78e656e8897fce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a610000000000000000000000000000000002da8de95ee2ee1be2f3ba8afd8f52a4fd0e352c295e92aa8fe9a08a03b6170222f5d6cabc9b9d9bf2835128c6ece3e9000000000000000000000000000000000fddd2b5faaff49cec261eaa8d093b410e024e1620863b6b9bd882088b59afdd4445a4971f31738e2afeafb36900b2d47b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000012060c8cab190beadf40a2e3d927d7cff21c475dad04d64c718d02ead9e351a27be81a3c5a71c6c95aa7d7e287070356000000000000000000000000000000000233ee868716db87f46d546aa1a7e4d3e70b2592efa0104d9f4fab1680c627484a33346406f61499e3971157a6dfbf972b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000cb9c4b59eb8bbbfb8aa2e9ed72eab69735a0154645d68428f0bda762d3b061b0659b31a907f531a55c0906532c539e6000000000000000000000000000000001806c7e8a8d95a34403ec78b43dbfe0bb09014fbe0e019f8c3b6ffd91a75d5e361a6794996e975309fa716b6c6a933784f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000131a492451e5c0ff787a233f72766339d7dae09f2e17c6bec9faeb08e4e48d6407b12adf2dffa3911395d5f25980c9650000000000000000000000000000000001f14d5268c422f94657a20ca02be7d007ea88e1a352753b2fdcceca5275a7ac101c0ecfc075735eec82b8fa6bd61c980ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000d988d419d559b1b487297cec19386f28659fbc5f121750b6bbe941794954e82e67c15a9a00334527d85e9be706bc2960000000000000000000000000000000004c222c037fedce38f42da2b08f06614ec9b166cc6428e3c4cad8ffa440af3d8fca7b9e4aff727eb0890effbc2b88060002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000fc9786ef5291943cfd885238090be47632c10cc46df48f6bb5250a7a85690f1c90f5f5bae03a71d7c52634cd0deff340000000000000000000000000000000019b4ec13ad67e058906a3559cc683511715b25e52f39a591b22177e2dd235e042832f740269544de112d9100c1ae49d9aa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000570b5e7b74c04db066d0aa751c9f763f59c6121e4e2ca4eec222277049143fb2e5fa39ac0fb41cd85310e4504f662ef000000000000000000000000000000000b522af535ca2b9db0cff08bf8ba19862e8f964b6210ee19f0cfccae8972150ae41ae1b8ddce4b1d2733c7dd47bc4c87df9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d860000000000000000000000000000000000892cc60eeaa0ab6584ef2731538a84c6a1e8dcc2efa9591ef1321442684ca9fd953553268ac4ed44bf50004683793550000000000000000000000000000000010234542eb7231f4356c34e11e7b4f08b4cb405a31aa87f961d4eaddbdaf5ba6227b2764e7c7c9ba76bac7da3b19f6014005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000005f3ff7ed08cfc6bfc8f5b55e2b368cd7e9f4a508ab46c7a383b2123b0346b81c39ba1304d628448c65d8c86bec682760000000000000000000000000000000001cbd3457f6925d5b8db7a785587d0dc6e2ad2ff5a6683dd11c8946e953dee72bd52760cc977987cd06a2679c74f9b64893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd15157000000000000000000000000000000000aa17bed794d72f8ac77989ce1b78550da54b4920ef6ac4ee0e83bb3cac5431cc7fb5c300c021045d4d391c67963feab000000000000000000000000000000001300e87daa3c36d87138628ad9aac5ec7d62e979c83c5ee4ce9a375fdabc745fc5874578945395ae128022eb98c6d8e4f685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b", + "Expected": "0000000000000000000000000000000010a773006edb1a84341f2971f9226841142b26bcc4af52bc8b000f165f6c59d094aa2eab1b83b3623c6c09145b5bf6120000000000000000000000000000000000130a0242c95fb2b858256de2fe27df7f5736765f880673300e3506a5e756b3b563e52b481c51a003bac76d04f56c5a", + "Name": "matter_g1_multiexp_20", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000090ef8b0781c66698848215b3aa84f7be47f86a9d95bf5a1ebe9c3dd6615d4fb4c6425f9e0029fa3d7b94052ef8bb252000000000000000000000000000000000cd1927ed1bfac35325d69fc924f4045c5af9fa5b0a18fbf6c658a3a6d405ac1159d1c82934aa116a98cceb382dde2ee94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000dfb10a6b4e5980400bc275ba5cd8211b8a6bb6cce026546b9459805ba48f46a429ba683ad3f96ace4a4ffd6cfdecafd0000000000000000000000000000000001f643a6d83f235edd9dea19f0f2ecb98a82ba295d8ad45f75be5c0d5b1a1522c5d9f5ed812d47da6e5fe8d7924648fc6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed7000000000000000000000000000000001824ddc80e263475b6ae3b73ef5613c7334b2f71c95d64cbb84dd489851580e767be29e7c7b47d53668a0ee3e6bcb03e00000000000000000000000000000000073f6ee13c3b05c466d35ac49c33e5ffebe5e8325f8f06b893042734bcaa4a1bc76da272602664c2aff48e731cea0304e5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000000abe490a12162aa01307e814684261566302501f589c655b3cb840876259112a1924b1ee723e0c81d6cc6b19535d52f20000000000000000000000000000000006a2205d02f58dff40715070cfd635aa5e68553eea8718090e5f6a96dfb0a2f5a23e11ba92d38a7cee16ce67aaf5de194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000180b5eb4201b4f10f605b4a7f5f5e25783bbd7c9e354238dacbd29563cdf119c832b4ca5c908329d5087d5c8c6786d68000000000000000000000000000000000ac5f56013acf364ce736c455a88a4b2615ca40fc67251039eb99df3cf6423fb85695cc035b6a9b47ef15db7406880bcde4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef1000000000000000000000000000000001963e29f92f6f72be2afa4635221b0d2f6afe9ada4582bd7ca4b77eb77fc4503578f38fb49aa1838751db8cf1ca0b0cd0000000000000000000000000000000009856a48f12966554afbcde1971499ee3ae40c9c5c3aef13bc415fddb97545ed84d5f50d2a26b9c16c4403a487dca614bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006cb805ab137fc56763f73867a7ee5635448a8a66bbeaa9ff07554db3d07aa38542884006744f6719f4cfab1392039820000000000000000000000000000000005e6f6f14f7aedc757cc458ba363fb5d97ee0dc092cf6866083722d4535e1b852c1d99d0c7c57e96a644de4b431c7f9bc3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced4329000000000000000000000000000000001045bd19d4fba8380467df25a777b1ed2850b7f5c5ff5501c048339c2f71278b2c97e4815973303e9eef283378cd8f470000000000000000000000000000000003278c7c8aa02c15275cbbdfc49f6286d6e7fb208a71a4da390c0c853684d7b4d8a6ab24953075a6a45f79fe0c9b910b70fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000007af4aafeee0372e88786c6025a710fad46252a8df870b56bc1d8a39497c2422bc01aebfb567b5b68273ac59b5cc8d6f000000000000000000000000000000000dfe4a8471e42dceabb609b983b59dfd9869f29fdde01a168c07247252a9be6555a823a61487778597e0ae305da4205fe538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000005f847129487acc07fffe21e2d0aa6275a586f051c06e2575f3bf8549ad9f6c2678c541d0dc7bdf909b7cff683ecc5bc00000000000000000000000000000000163451ea5122e16ee62d58d6ccaf8cd981a29aa820d77967e69478127a76092e9bd0dc9f24a27ddca5b40b1fe8ce18b130b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000faa1804b1f65a6ca75d032186b5dda63799a5fff3ffcf1f53eeb04bb5ce08be40fac13295937f34666e0f0be3bdfd9c0000000000000000000000000000000016a9086134daa2a1374fd8eb74ea65858ebe8b2990bb92972121ac68bd6bd77916203a1033ac4b163d863d9120bea0a33a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb4605000000000000000000000000000000000a2ad31568d9778b306525e275bc4f525d86c04dbb98f48e72adae813ce9d02dc6d826a813ffa5b9f9d014e92de42c520000000000000000000000000000000014e928d48c4ca7640a5f5c55c8ae756fb6f03bc1a8e4e907ba89865ce610fbd919a024e86969c52a4216d84b37673cb5c7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d5600000000000000000000000000000000041fc07f8759995530350fdb8712304083da882a5e4df8188cdad48a3df91a5f1bcc1b2a25fb3c9b59e2c935d579a9d1000000000000000000000000000000001925153fa12217d98007963237a665e56570cc666651c29729445adab3963d599a4eab996b192be1d49c7429d9f0cfe43563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe00000000000000000000000000000000006c45218eaa27435aff594c2601276950bb99fb3c1756dbec76e609d163b2593933b5ecd5fd8544d4bd2d145821831c000000000000000000000000000000000a43ab2ea73a8e1131e184fbe9004aaea198a3dab575d3516b422c275f20c7a6e5d41bca0aa3dfe7ec761dca0ba6687d833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b5000000000000000000000000000000000d17ec8ed30bbca5766def9fa375219503bf2f7322d2cc36a38fcc8471fd9d11d2a30ef004e39cac4d1ed2d33a66f7d200000000000000000000000000000000108e6c9ef3a5a41662fa16488243af3419e2d8e78c0311446186c96f20d9c15a60b5470eb95e0e58143a3c71a7565b05d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000017f498e192905962fdaf41120027d49267523bee9de8e412161cec69c62d2586752d1da3d15e89446b5941a2f321beb60000000000000000000000000000000015e9e4eb30296ca3355ba9c5eee343fe7edcbf5bd110ca5be12f55191d0f07b563881f52e65588a8f4b3e03dfce6566e3ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0", + "Expected": "0000000000000000000000000000000008b5f4f55def15b4590e978384fa0aa87e088916de20ff0fbd688ab3a13138f32d1e89cddc72acdf69fd86aaed6cbc4200000000000000000000000000000000022a02016f38156fcff83fceed881f96fe14e5d3410b4fc50e607d8a23ca986351ce48d6af970590f68aa2ad7181c9e8", + "Name": "matter_g1_multiexp_21", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001155a7d2cf81ee4f8d65c835ef422075a9453bb85b3566ec0545c1198b93749beffffbad14ededaa5bc6443736f77bb800000000000000000000000000000000073e4df0ea06345dba9fe772710ab71153e57152c74bd05d8cd4229c5ba1301f7e654f3fbb2a45526f1bc3b09c72366f16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f51000000000000000000000000000000001238e5a46f24e0f00d2b45bfad87f96140ce10d774f4a17c3df224b58693afa7cd0655e5ab202998f4f8b4b5e22cb82d0000000000000000000000000000000012628d85d982086640b09f046c5bf07b1cf718b5b4b20bd99d64382bbd8bd0112230609d78ecdc742cf1ebd24f1750ef8c02014d5392d30863a12102d1c9315839b5611dccfdb489207f918662513850000000000000000000000000000000001363b85a95432193800bdf353de1a5764cc2333b0369ca7dd539f230bffe81dce11288a289e0842f2db62a89e6f6af1a0000000000000000000000000000000003dc043b958167a900cbca116b097724e64d49897f8fb6a31df99e100be837e873328f5113a28c9fb510017d28d90d30d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea24900000000000000000000000000000000175aef023d9375ae90e9f562f88e0a4affdd399c3755c1b22494445d4e7d96899aa4d5f77ab9392051de4cb7e400ca830000000000000000000000000000000018e3eab56eae429c09f9eed67492181279704d947cff0f1c9a4919dff5e6fe07fedcaf5dae854dba6719194f9fccde1704753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000018d7001b1d4a67d22399c5f9b3262183a47b6fc81786f8f7b78e80fdafb3c0c175756e602c92855e8ff9d99d4116e3a40000000000000000000000000000000018451928599da4a14442910a5bf125d97f0b67af4194797b3f54ecc9ef0be840a1e0ede13e1415391f57044d71fae2efd1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000013ca19bea2e93c748cd2adf682a123416823a2473148e59d87da33cabba8e0ff2516e5b2bc9a8fcea9dc4240b20133ad000000000000000000000000000000000433fa5475709a7b70044f88a5949064e32014f1d64826abbf60789380db6d5ccfa750a868d9902e4646bae766e241acab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da000000000000000000000000000000001236e6ebf0b704a18f85281b09a9552e8a478c66e59c9f5d53eb6ff1f606fd667a6f0bfe239970892c9c295a378fe389000000000000000000000000000000000cc5c1039850f3333981b1cd6457a466dde93e2355c2052cc325e18604f59cb22588b6d892685fd7843938fc1b5b8d8a1d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a00000000000000000000000000000000003f86a5cabfe7792de25b9d8c58a283c5cef56e23dbf713851b42fc0d66481ce1946d1c632e38b9de1a55ffa0bd7f5a000000000000000000000000000000000f548b05782ebe160d487c622f8378786712cb5b68545ede95b34b08698f600e02e918fa2253a8be2c1b773cc74c41042cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d40000000000000000000000000000000015a4bfb53e57dcf53483fca1b4dad7f788e48fedf8bbd7ac40b1707c35a57011a0c7f77ce6626821221e59d8185b9ca40000000000000000000000000000000005618adc16eb9771bfe731dea180e7e2b3b0c9537806349e653a586dea4633aaff7fa7e7ff165fa16ae0013c9672a783214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e000000000000000000000000000000000039895bd3ef87c094c9cb1ec77229d615e76dbf0f3bbd399948a70714d6835b570e54f46f94197657dc94d36c4a49093000000000000000000000000000000000f1c6f8b06ea4378234e99d16fcc439a64cad45a7f8ec567755febdeeeaea4f4b133af18a4c00b3778090c5857739b66c1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e20000000000000000000000000000000003007275e93f828b96d060e775f2b92d191d6da44b1441bd0aaeccc5abcfc7d2b5e9cfaf7b8497016ec992b13455af2c0000000000000000000000000000000015c1320efcddd0709a12a75049633dd871747e51f099e40908542a3e426d7a29b6633f5e69a4c0b5c32ad0269a969bbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f7400000000000000000000000000000000068dbddbfea897bc2b20b6f967aeafb0ef759082f55a180b3eda87174d0e036761f1be1c682d1a4c33f5113a6ff4e2240000000000000000000000000000000004ad9da407bd80ef365df2eb763ee35ae06074dae0eec7e2a36e57df4b3e5ac333e373cc60c1986543c0c23f3124253561ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a0000000000000000000000000000000007f506a54adb1f763d55278419d4c18ca581b28ee369f33b848be495dbcce72c76533b809d70e26dda71316cfc3a1c73000000000000000000000000000000000a6c574799ba920ac58d6cea6d0f8ae249ef5310609904965bf86fbf88269530badbeededfcaa03892f1ad6b76818ec4681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da000000000000000000000000000000001424ab1e7a30035c4ee7d5bdcd8ef87a0aac284a36259742b68a5997e7dd3f2e5065e2238f2e29a23ac5ae9bce3bedc1000000000000000000000000000000001530257b63872851431a0bf5397dff45d6c201da58d7b779318beb70a5ee2a93142e4c5c43c3d65ddc65fe2df1af18906f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000bdc58489ffec3668363be0a3e45ca2115bd5cd1745f86f1842ab82ae31b08a1f285e88dd4e0c7b94778f42d495b1f9c0000000000000000000000000000000006f4d2a07ebc588a8f9993ec6048092b6dad82c25275c922b2842253a8fe24e191cad4fab51621198147c6d1bfabeb0ba8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe6000000000000000000000000000000000096e94b43a1dae483b49c1a616c010c25b660ec3566fb7d9c295d3b43c60ba4967b3f0abcc0634de5cf3fba14169fea00000000000000000000000000000000026146a58d55ba4cef1cfbc1db6efd46400b78f508ecc0b2eede8834eeb741b68ade43ef2300fdfae18c02b86e3386768823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b", + "Expected": "00000000000000000000000000000000143fd63e2576a606ec59d017e6582711718a542dd0a4c84566fa4312b2d2bbb2d71c1e0ab8e4e214ef2376706a20e3130000000000000000000000000000000001e97699fd2e0badc3a97f6cc55bcf729142aaa13c51f9b155e5904382ed0d94fbe1d2553d602a71ac7ff39189848a52", + "Name": "matter_g1_multiexp_22", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000023a2a3e6e1c1cc57b2295c664ac26abd0f5bbecc0ed8e9850f90b04484c0cf048a76477ddde84e90cc452429e28b78e00000000000000000000000000000000194aa1d8332fd8120ed518f27fd827e3c955c2cbb2cae8d5e677f55963565dfdd232c83a38826621e8e66565f8e200b39f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c540700000000000000000000000000000000124adb0352af8f18a631cb0078ec7daf00c2186e04d3ee47882d557b0e9e7fda0e0d258393ded20288789085583a97dd00000000000000000000000000000000053f94d0889a5122b6dfb1da2d7f13a836b9be039f127a011991c360c941e5dab8cb3c7ff3d7e128e52dfeb776aeedafc8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000009dee1a168c00632903b93fcf330b28ec7dcb8d6fba65f369237ef873ecaddd60a2d1af6e5b087f07a103f096aeb5e600000000000000000000000000000000006f90048b72dc28cf4cb40585925e62275d44df95fcbf1206e2bc762a455dea5fc6b830420d49b2415d259f8d5ed3ab7e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c000000000000000000000000000000001750fc7241cee9d71d95f0023dbc4b1f41ce794e9e7822a29a84c93b9374ccf0f11f931795fb824bb5c9fdb4f9e7bd9c000000000000000000000000000000000a0e6e6c76088200a345531f589ed883203e35c8ad8413575bf961b1e8d6716829f632e72fe90947dfa46745c9ffdefdf7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe2200000000000000000000000000000000141d878adfaa6a3982cd0de93b4d64ba840a07c026ca443d6d4c2b6c36cf882e109d80df63b1626c112f9a89809788080000000000000000000000000000000005a5888d22a2f654a58d9a03c68d59cde9ab5e5356b2288033ba58fe2dbacf533e59344bdf30eed07698261d6269fc70557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000afb5e198ea80997e7cace2d5b271e3907525b6383e9d45d8a7717317655a79bec3a48800149d6bbb11a838b1338079200000000000000000000000000000000060dee81112b7e0bde192c9d382b1eb695f3a1b0b9ef7ae33b1c5ef8ad9134c23b4f473103df15a97bd6de007b828fe63313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb75000000000000000000000000000000000a9327207fa94bdffaac0a8741955968ee2278dc0fd17e99c6f4717e8b0db2ce7915b1b028c81d48380cdef05ecd5a7e0000000000000000000000000000000006c24bd6aa5f9c41bd4551afaa6baf5bab1729b7012951fd0ddaf2c6dd03ddc2030d49dc92073540503718a44260fb028faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000195d0a7f5a351dff02a805fa08b2a793d9e0c74ae95fbf2f42bfefae8aeb0deccadeb9a2dbad7285c015ce14724879ba000000000000000000000000000000000e177a86f6aebee8bad62d77703d1d34a1b708e84216437c02e0694fe722414f2ef2577c1d39a45b4cfe6c73f411b1b413994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000bc7fbda14f76ed98e78eb84033b65f286527ef76ba56dae43a094a23067e10798065674daa14f912ee13dece4f36b17000000000000000000000000000000000f69104995530de05660aa048993c4e08576488deaa177520676c9cd53034ef101fa3911e40933975aa958efbb1b931f81eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000001c3bfedaa15025440c6cd32115555fbbec439a9a2fbf706ef21e06a534af3f43baf46897158e211ea8821a5e32f932e000000000000000000000000000000000fe08cc9ff0fc601e5609ca139ae0ebe58faf8d2e2f4f3d0a1231382a15ebdc8f67271b556cc24fc5408daf3c7f74f875bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d00000000000000000000000000000000032c376b26551a064cace577ef53077cde48c284af5633152c89ee109e880b511c0b90db1b30d6d9700037489f6984af00000000000000000000000000000000059c013cde62f10f39175335b76adc5cf7330ffa75d770d908ac7e0fba6faa7b9453e8d0215f0589af872b2e648ec1d0a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d20000000000000000000000000000000009cb943167f21d9399b184f0bc0c2aca58dcf8e702614ffaf5407644ffa9eda85efa12dd23e756c5ccb5bbb25abe57e9000000000000000000000000000000000d4f59115321181962452c6f3c1e086cbfbc155f2c3019e51e73fd193e9b11ec891b2dfbd95198b318e4513c62cd51bc2c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b2000000000000000000000000000000000637e1dae04d31282c2278e087eac9ba8506d3c1349c6b98485cf32805bcad002e37d55667f1cc8e5e11f35b4d228cba000000000000000000000000000000000778c3a40e79d6288d3a93580c8f8bef7591acfac2c734018d61aea5dac020360ad4c69b4422f7320b87ff22e30d9a6a5e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf04227500000000000000000000000000000000069a54448ac1c9ee754fc28c9b671e84a67e884492f8e84e09e49cbcbcaf07fffed42820b1de61cdd0bf6314a2f4a1e20000000000000000000000000000000008f5512a1a70d3a61ee7fd6750813a29c47410b7ddd62db0426b3caf9cd7c31029638499c2e27e5922810cb9bb130723606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000078356cf80bc64c0e03da2198da5971b01341024a620ef4a455291b7a694ac3d91fe6f19299d725cdf7506e0485485da0000000000000000000000000000000015af5f875422c1e3ec6bfc5e57ed793f368799c2e068669656294be0de25eb772aebbc61358b410fa9ef79c72f309c84c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b8510000000000000000000000000000000019699fb3c6af71eae16b8ee123870888d646ac71dd31d0bb3ca365f728a6687540851c8539dee5c34f16871ca244ac6b000000000000000000000000000000000e68a278bee81ea53d4a52e84c8f534a0fb8c065bbcad9f3727917402746b4d1f611ba5064f0c3cea6f4d7fe84948dfd47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455", + "Expected": "0000000000000000000000000000000009057b093eae9c7ab2455b447a681857d588819c94b1cdffc0e315987b095edba1ca727043667749c56591429f9173b900000000000000000000000000000000157bac2835d2f972fd1269039a7b6159b7a81a1bf4327cfbd3be8b7c779631e8beea634ffefd9771c910c612d6925384", + "Name": "matter_g1_multiexp_23", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015b6687a34084292423eb600bacc585b4e686251892b16a52d0783b1490a82f68f4eba5eefd36d147c4ec442d2eddf8b00000000000000000000000000000000151f59108d7383351b426ba8bebcf2a04976550aa2d10d5f89d5ed7c3bbd3473ebfa29c1706560866c8596f7549085cc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a00000000000000000000000000000000064e3333f828b1e54d201c043bb0f327d8c9af2cb96fbf587dcfbd55547d76784de0981a0ac86b65f4b8e45b19abc66f00000000000000000000000000000000172b76a242fb2bd9070ad26497a5c190d08472d3fbffa83dafc53d2bf612bf805691bc8f850da8c230ca0b8bd4fab818a59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000158e81d92b789696efcdbd6e3e7c16386d6e5259a247991118dfbb3674643fb97a82fe404832cdbcbb58156c9548e59000000000000000000000000000000000fa0d18e57d64db246ee52980218c3eda5fb7b1029e1c76c9894548df52f69725fb7ff090417ae05957a652029d0a37019ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c500000000000000000000000000000000027a07cd6b7cf0219b57110edf07d758ea40b1cca42270b341b2bc33c78fb9cf52acc31676811032d3f618898a0d13330000000000000000000000000000000000e1212938244e425860646cd0258b65556360e832d4f2262984f4e307023896714731a2db10004e5509a1dc25f49ab7b8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc1000000000000000000000000000000000bd589682a8510471ab1be8c348ed0d242548f0a5b85ee9eaab5af164367be21684ce2329a64a6afdc6a30ecc5bbb51b0000000000000000000000000000000008c8af9dd0e06a08f2da0ab7cdfc20100b94c04c7e6773a0351bc0e0ea503a69e5f25f250f0bbc5c7685795b279ae151edf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d0000000000000000000000000000000015865d51ca8131cd5d2b0cb11c2f06e39b7e167ddf504d5772d478d48463668c4f7dabed00cbaca414b6ba96224c95cc00000000000000000000000000000000042fee2fb44ab45d310ab00896170a638940edb2df9a0f06c077bd00d203966d49694c82cd59c378445ae0577471221c0dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd0000000000000000000000000000000017257c7d5c733cb6e9ea1bc93bda4f36b98375147a119c376996beb6f0bd030c997ac52b1556d01152991738dc640788000000000000000000000000000000001155b29f473d9abd15514a0ae1cbd0b6a4ef394aa65f4fadfd3e9551c1d8420fac28acd5337fc5d114c092bd45e9e30d109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d370000000000000000000000000000000009802bef3feb5688df77c86c74214451e4613d0260fdc5ed6e763226d3eea8a583c7dcf29eaf4c0bf16c907ceda76db9000000000000000000000000000000001447b1f7ac05cf8dce7e81de516d7303b310316f49ed5ef3f40f03db17926ff5f6656d859367805c889e07919224a6436326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a0000000000000000000000000000000000057b59f849f0237ad511a75b66a77e79ae062025e5019eb71b7b7ad94a96c2905e25afe4357506b2472f99bc71a8ca000000000000000000000000000000000f10b6ad9fdb4f346c5b4a499722e377c7649a800bb95306dd7e2ab7542e59455ea5541f2d75e7cfb1da5dd03bf037a1e005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d000000000000000000000000000000000e0725ff4149698aa757e794590ce446a1589d9a574587575ef64d6a3c935fbd78fb60c7c840d7ef42eee8d72a5ce341000000000000000000000000000000000f0478a776be354e29bf8bd2710a8529cd01da31853d04ea722225bde560f2d9da302ce4f2634c9385ffeae379324b743917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000003103b0553facf8f3cd18967a758b73111a4a9987b0ceca3a20d6657a7e365be3925f63bd09990e33e1162bbffb63278000000000000000000000000000000000998a34ba445dbefe6023e737f3e35cc6416289185a26611301721db3a24f80dd784b001a2f2a745ffc3d0da5a9e6204f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000041e13fb55bc9ed069c6d625ee08122efb0212f525b319b88197450ed1a60fc7283f61083ff263e4df10499b689498670000000000000000000000000000000010d931f006adaf737afd1ed2d1a631f519e6d1e9e22166c24830e92e3571e9f138ba901f5ac2f03192c9701067e7906b3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb442000000000000000000000000000000000bc0d401197ce816b692c5ac3ea539cc9658de56e48b4c3ac78631f3c529d4fa2a656f66098a702b4307fc56e147f962000000000000000000000000000000000d89fa2bbf3ad409a9ee7b7097662113b94fab95c98bd47a70fc2707a6aff23bf39944aad5509aba34930d7343762f6e5ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e9918800000000000000000000000000000000103cc442deeb800c14c9b3071c13d354d8c36d187e580073d150f4936ff178817dce67ee276d1633e003e66985c038cd00000000000000000000000000000000188b34fb0a4fc2408d8c70eab6df4c6c42d92ac5e43827044db526d4208acad4561c1310115448bc00feb9ee7cfdc40a877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000145220a2f8fc61b2973d219042580a0edfcbd73a6bb6feea3655dd33bde8a25e0fb841a3b038049e554315100e6724c50000000000000000000000000000000018bf41cf4ce164819a8b00e630401f0332f5caa08b03bda27c205e8fcc5ea7a3374b591a4adc581f492cb07445c8995f145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f607000000000000000000000000000000001416a39ffccdb10f65e5f06c8d7af68fbe894a0778e7270ab167ae2a5e917fb0eef1ef1b9fd45c991a45dc92a223ceaa000000000000000000000000000000000755c58a0692f8ff860430c5f75fa35366391f7e5313936e04230a1fcf1142c81b01e68fb3c888effddc0a498f264da9de4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88", + "Expected": "000000000000000000000000000000000d73a7edcbb7163795dbb5a5b4daca733e07f6498d336a5dea1a61c9edee346f74676afe0d6d39c39caa1fa7660ab311000000000000000000000000000000000f3d573970077a17967ecc0fc5e2e7dd4b6ce910f1891f444e36761e2ee3a72fce399993405761de29f9563f74d8b1c7", + "Name": "matter_g1_multiexp_24", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008c2f928feb8b65e521b7218b029a4f54022a28a18845614b3b2de93035228c282c73ce172997e6af93a402e35158ce3000000000000000000000000000000000ca2dd2c06221058a4a7a06438f035ddbd96f6b39fe80c0029f41246a2c8a4410961555e43d9b3d5d87dceb8d0be1ef42576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e00448390000000000000000000000000000000010d919a48f588429918f1b2f05ba6e897c45b12d905615e045c1969ee8a7d9ae262551f546b7de764266d3ab656c3137000000000000000000000000000000000a40d6f247315e0440b0b8195fe5f7a7dfdb2e1be9e593f7933691fd22789ae94bcb6bfebf3b84afaef7cae9fd539b5379f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000005eaa990ca9d57885e6ee3eee10b6e2dde6e1652a743c62ebce4871ebd2d3c8e4915418aea4f4285ba375ad1923b70a200000000000000000000000000000000159919c720eefd062ba8d72fd3befd953e1272695471315ff500830c9b5b60ce5f94bd6e966828d69f7f268bb423dfd7300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f65715000000000000000000000000000000000b7244995b7819857f716288dc59eee9ba5ac7bfe010937ea0b67ee71388a3792e5b7feb6890a436db4f1b26df18b38c0000000000000000000000000000000009a0b73360bc0ca3b632c0116f21ffdaecf37e4d6c904c98d6225a08d7caadf5024ad6b457cf31b924118ea147ff10fb0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000005c2bd45375084cbc4bfebf41709a87c2a8d52256a5e4bc162501bc119394186fd624c5d3d6749708be2811da2c84c15000000000000000000000000000000001626cfa6e87e41c2f0960d6d2b8e303ff8de00c78d1e788f32cdf548a5ca00db1f3a3c082f051b4bca93788243d9b0973685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae8206000000000000000000000000000000000f736c8cab0794e3751a9e13027a8e4ded1308c23be3d75b373780eb69f130654121435c53b62a929cad39c605637ce10000000000000000000000000000000015b1edb73501789811fc09fe0156344a7a4eab1f04d1fabc24f36e2ddef7c2ccf9643699cfc654b7169d8e371c14e8c660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc2540000000000000000000000000000000018dbc414f9e1c66af803b0c228a3fe77c94c29239e529cee652099d80795c460a507538eea6c94e99b78779fc0f3f33400000000000000000000000000000000151bf39a8e3e85b9361a9472e95cafc3ae11f7d0b952714d2836b903910a8c701e0c3832b8c88592bb8507694d9109b5addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001241319f49e1bcc2d3f3eaca51d2e4c395241e2c5d8f32749a168e4af17570793fe086610432db1f93fcbbb95ced8b49000000000000000000000000000000000d90602dfcefc3860a78a8f51432a7608a7c483fcd86c0ee6a70f8ac723537825c14736240cbcf903c94d04e24e8ecc928416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae800000000000000000000000000000000024f26ba0c3295002418f7839b774cd305cecc3c2cfe20974343dafbfa6677c2fa6be5c546a1fe81458678c3548d8d6a000000000000000000000000000000000fc8ac2bf4585e8ac8454e3e424e858e1d67cb6b9a7181e26af803d8895717796f20abdfce0dfb390bbc0c7b16c70ffb78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000005f24bd878cf5832ebcf008835f12f9dfbc78b2f6e46ee384b419928aae0e754d86809d360b0afc01bd8f2f8d79a685d0000000000000000000000000000000004aafc9a20f52d1c78a17e7824062a1e7165362ff265dddd4c3458c7810a8e59104d36035c93284988eb708ba196d6a2871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce000000000000000000000000000000000375313e7ab999d174735b5290bf9ea333a62387996bf4df3dc33d9a5212ac0645789ef4153223d488aa2fbbcfe808f00000000000000000000000000000000014b792fb5bc39dbfe409356bd75b195d7023bf6f715a4102cf36ef05b52fb2284cc0739fe5ad628a760049c3624a3f2876ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000006ae2c85b2b267c86320c4cdc56b1a09e25f0f68dd208e898ac5b1c0645aca3dd8000eb544eb666f4256806123480800000000000000000000000000000000006670390bd47829d3c31cf2da8fdbbb64b92b47c78d3ab638727ea834ea6203e45a9a023060056c69c1fb567c35b671795ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c40000000000000000000000000000000011c78f1b6d0ecc5523dc089852d95dee641222c743dfd09ff2e56d008ce523762bbd9c7bec6c18e9885b7022131ad30b00000000000000000000000000000000066a1aa8af751eac5dbaf2d3ae285e0cc7a975c1787178f550a42e8ba89fa74a1b18f27716eb7ccc4f21b7957cffd8e806d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db000000000000000000000000000000000cbc0fe6b4956c0f7b9fdd36ea14a4d8284468c280605a31536636114759ece1339f06e050260bbf936b560586e7d12c000000000000000000000000000000001213bfe642bf78554d91820c362b73b7059cf20a0aefa5855f9e61a0490d165f6f61416e135473e2de54bf97cc14b8f6257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f0980000000000000000000000000000000007cac206b2d123cbe9375f5c913939b25886a51c857271a59cc2fae2e9d669af0ada833c72366f78be265ff9db049d0e0000000000000000000000000000000002db3f65b6fe7c6688f8d3741e448ac6ff322b8769277572f0198dd6ee8a99397aaeb9addd0892286a9ec6028bf9678863d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc3000000000000000000000000000000000a60331f8e8b26e97366c0e4cfea158e78ac72d63f219e1abbb670675bea008609f7154752438d9c7758b2a2e076da7b000000000000000000000000000000000d40d90f498a2855ba35f1c4bb3c5409b87062d7857bd97dd37d6e5fa53c94c78319c6b16bdcbf2610ba379d50d131e47a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c", + "Expected": "000000000000000000000000000000000ddd3c7964bf51207485b0575afb6430cf801bae388ff78a69b8173c27431e0593584f9e755b99a5b2ed3113b3fc0082000000000000000000000000000000001735fb40978d364be3521ada17c3ae74b2a738b412906fdf425bdf13ec09e5acdf29013b03fbabe889fa261302a7ca42", + "Name": "matter_g1_multiexp_25", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c52993730e412fec923e33f3da42adadb5d87290ac4448d7df9b401e28b3c7fe7f49c7b7e4bad5412c815931416303e000000000000000000000000000000000db71c91975e41b3f12e303bd8ad15f7c9836b146073946129ba3815bc3217b6116a2a03137608cdab8807d5834eb12026a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c0000000000000000000000000000000009657686875d82eaf4f93f3e710c467ced1348b60aa47658992771195660c4b96798cfec584ace3bc64040666de71f8f000000000000000000000000000000001375f7e985d987df508321c3d0aa7e7a06cdb78117248e19c3344dc443da319f49c00ff605c057d1ecd942e8b04a5e4ef1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b6000000000000000000000000000000000da69e098b5e2c8be2ba699f20fa38cd27b9c78025e071ecb2d9fba3bc84b1e673eed79f1887fcad9bfd5b0516236a1f0000000000000000000000000000000016c4ca4d9f15716b7efe6f9e61aaad880423243b2d5ffc96804fc70f29b633dc16474f7194b5e3ca12ab5a1627da580f97fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e0000000000000000000000000000000005370f5c60fb3bc36ee208e8c185613390748452cf6191bfad06c9bcb52501873bff63892066e0afcb01a0204cbc951b0000000000000000000000000000000003c7a2a97cf7be433864541082bd04467bbb42b2ab708866c8520a6582cce5225af13acb887b6b6a8d627c90e43f6e7b640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d67000000000000000000000000000000001820666eb1abd6144df2f21f2d46096410274e346ba862aca0e62d293fc64a6fd213dca4ddc1a4e414796f59db4d6104000000000000000000000000000000000a2521c021f2fb7beb76a2ff4c7ce96cf1d05823ad8edd9b2021eb39c08e0c7caff505ea76bcff8f6afb6e8c2e81d2f68bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af4000000000000000000000000000000000e756ad1ccf0404e110a778f66ade3d10464bf8902f646f7d7ff38d15ef890bbc6d61d48122ba6edb799630a62ae084a0000000000000000000000000000000005b322f44f07d3db292c43f9ddf9ac9e44e8d16c07537bf563c98e02c2705eefc1013e627567ac2a03698268707cd84e8da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000000420b819a63b7ff7ce541661c5fa8cb107cf00ae678981b3fc1b568174ae3864a8241f1e9b656cadeeba232156e66feb00000000000000000000000000000000136fe878b886bc14fed061cd8ff1fa2d85f05bab922bf18a1f09b55c331e7cc9bf0f9860e9112c2f6242b6d1124851dbd6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000b73d3549a6b2f76741aa39ee9bc2bda8cd55759bbedaa9ecc5802310b054b01670dc803938aaea547389d7b0ceda469000000000000000000000000000000000227fc49bdf53bc4f916714ea9789b526aa53efa1eb032c4030519608c62434443847cac82a13e2dd2eb48f73473d8e1e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b100000000000000000000000000000000167cdb86301937bff18287eb0b00f5224e674953d70258065e5e8370016cac8194ec8c2f44330adaea44426aaefac7d70000000000000000000000000000000007e9128bb015f01aa725796d7b7851f9c2819a8a578bc7d3af02f7328c922c26335ae9f87756f52409c446852bc710ada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000018bd46832b101d12f95b21332b7259719c1f94c056118d877324656d285f73a4fe2cf637cc62a45647db92ba9d6c7d18000000000000000000000000000000000fe58fe2c19ee903d82da6da8713863423f10edb954606b6c56326eb8eea6c66cab63b0c816479f8107612391072c634b929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf00000000000000000000000000000000012db7fda36505d19a2c6ba5072044154f444eaaf3e12cce81ea74f28e691e4b7a730095667a71308db5e8322e80fc66a000000000000000000000000000000000fd0f22b05bf82688ac72e9ede526bf806695ff430ff3c750c2946d58ef90c778e4c5693d152e39fb1837bb10cf5f3be36999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f00000000000000000000000000000000116ba7b6faedd465fd4d1e5f42ae80c133a1d158614894ba663f87137f6108ae03b8e80bf32852ccce78b776dc224c760000000000000000000000000000000004c3702ff7fd9c74169ea76c00efb7b475d45efb12e1b5b700d47a970ed9f95f46e4c0ac66cd12fe79d62898b24b54a0fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000016254d89b0e2a8315253434d5444000d9b56b8f43d3c20d17fd26da4c8e7432d6e463b71a5b2a1a7f559a908d73abf6a000000000000000000000000000000000170c490fe3962fbfaaea1707bd28ecdd46ba29b5d8a0a35baf7fea4eaa47694e680e47e8a9f07d25078274074e232dcc36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000007a7fd283d64efef7094fbd6162da2fd56399765b559674c18d1cf6df51036007ad6c9af62bee534388ea093d3cdc3c90000000000000000000000000000000012fcf920eeec2c1728f3e620fdab1f8a0b99c6219f44b0fd19d0f7f4a15d1636fce7b4701f9c3963cff9b030c3759fb20f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e40000000000000000000000000000000009bcf0b2d49ce38914ea877832eaa3f1034cec429cd9fe0d06ef36691ac8ac6b69a712792e31afc700872d08c2e0fa48000000000000000000000000000000000f5fd9d2d4710d1cc6c13c88ae602f584a7b671df91cd544697070eff3342d80d750e15e09358125d15fbf8a1ae8df93c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba00000000000000000000000000000000053fc59a0b84028cbb3a97dc3124927d6a0eab1c58d4c6d143462bf73c0c847712bf22557a1181750146fe63e9c9668b000000000000000000000000000000000c1fa8c1539ae702bc9441085a89790a5dcac9b18925cdb1e21b95c9f7286795e8f36e7a8b4c3f4dcfa12454624911675398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc", + "Expected": "00000000000000000000000000000000085e4232f0daeddb9e1ec8731855cf855d7dbc05d4b82d10b77a53306ee7a38ebf45bdeef1981325a61ecd754944c84d00000000000000000000000000000000061e32056ac411c3917684356a6ab3c7068f55d30ebcf8cfe446c68267923e4fb98596aded9740dc7944847a2e617fea", + "Name": "matter_g1_multiexp_26", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000070bcf49d6d066afa9b008fa22fd52f63b68a648bfbb5cb3eefd6feae666f3fd0b9a8447f427d5a9db52ba49854db7cc000000000000000000000000000000000947d708a02cd0a18342bc04639e8d126fc4c97acb497aa507e1c4c3912b04bdca886b75b9b9e1c5ca745acd090433119f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000001023942a16150e6497289627dbb0205a7c34afc704232ef214a6609125e90260d68b7c60600cd6f4859ddcad46c015580000000000000000000000000000000002da96265b7460ea6a8d51122bbd2442c6784d4f5bcf6d8b0b6eee6ec82e4d03c9265887f88c106792795837c02ed76e4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000d5be6f99bb9a2379d1e542ece048164fa5d14e0c6c459180717b3da46e8446e9def576635ac1124e1390196fe97f39e000000000000000000000000000000001482d8339b402e3bffe61aaa298c8bae4286f1fbfc877a66e21cfe239bbee383d701d95a6c2b8193d67df5a551bb7aba7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000001969dbab76e6a158506b9dd38c647d4a670a21458a9552d903ac686855fe021a7dcabc91e712aa252de369c9234fdb59000000000000000000000000000000000b60179a6fa6146aa6e57b097f20944c123916c6722fd7e606aa34b8da579f6c126dcbb251da7917076a83e2e4b02d32a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000e5fa0feaca8dca2a6b4a42e4a291383ec867f12b85593360f8caec45d31109373dc16d985a4702e3b5684774699e6b5000000000000000000000000000000000ae96f4a4ac0d0a6fe6aabcf902eb0765aee9ac81ad09e7e097d649b0c0165de6ad7e5ffd4ae7d8a272034f28c85ad6f97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d00000000000000000000000000000000006391a93eb14641ff145f690c626ca412af266d50b903f7465d9a9b678025a35a68bf1962bb5ffe76ea07989a7d807920000000000000000000000000000000001a90846cba7c708bf8b4bcdb3415e17e80ffc9b48820d3307362327b29eca0d1bb7fcac9c09d09fa309829679080b36efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000000c9cc9547fd49cb22986f7a1dc1da89b05f5e7c0d3cf2179f22002df9fa2c586bb3f1496c0c60f8ba36b631fe74c8fcf0000000000000000000000000000000014f8e4e8c5a12b61caf4325d1e4a8505409d722e4eb16d51be5f01f863e5dc1ca68df1b83f546d22fc116f1654a3b30e9a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d45200000000000000000000000000000000123292cef01012c3723b4713a345ea7648bdd8b8edaf76f149f1afb993f196f57b3315d86a374fb78a34486ea10e0c26000000000000000000000000000000000ee2389f669431df6697d79ba16d3e4d9bb4264c9ac146a772de6a9a8ac94760cdde7f613a4ae6592509b04b1f8233cce9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000001284787a11e0164bb197f69702d0d746975bd96a3b9221841c7193676861e97e11077b74e69f744c521ddb40689f9685000000000000000000000000000000000eb6c4c25fa1322f7c829691d938f87ba6bcce850404bab57cc3be8c3d0abcf123be8922af9967b83789fe64e2cb35f40a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d400000000000000000000000000000000069f8970964efa22facc786291d6ffe860929121595fa713f4a12f9e99d8508d7d20f7d19c51514538d1ce89d2adb78500000000000000000000000000000000122bc9405ccae4e409c1aa22b36db314a19ef6e67a572f7ea67c247085205302ad12ef7f83d3616279892ccd3c456980cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb0000000000000000000000000000000017b92fbdb00429846fb30633a2c3f383d32d0bd433d5a46e27d3c7bd6880948f89bf70b3f1639a18d308ba80b7209df00000000000000000000000000000000012374e8e7c1fdaa4ad4a2d8607afb62ce939bed23ea42a51fbac995e2c3026c2daaa338be160dbec2602a0fdaa1e9897e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac576800000000000000000000000000000000004850419631e3de2617bb6b51ef19bf14dcc9f4c7b24ae817cb239342081947f1799080cafaf51ed687b9dabb2f3581000000000000000000000000000000001108a0463d38d617d0a778bf9478ab44050ec290e442ac41e23b526089ab5aabd5819a8f08f903343e93177ce4042c82f26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac57946000000000000000000000000000000000c9ef168fadf7a056e6cceef0430f65a57b0f2c3372a5d3c533871c91cf81c40d3459cfdb5f1f66f53b2d8d50124ed15000000000000000000000000000000000483ebcdc219c4c361735aa0ea96c00c4908b9db62ed8cb565d25a7fa664829bcacc37a5608a3c3ea3a42ecf74708ee9ccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f94000000000000000000000000000000000f7232dfd8367af413dd078f9d5f47b8c76c38b3ccc4110fd59764265e6a368fd4609b52c21f8e6db2c73908d4ac0b3d0000000000000000000000000000000018433b00ede4de21cd6a1c78c5d280af98b814f0a60c625f0a8f355be43d8d99346282b6d9911c4d4074fe827b55d726fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb3400000000000000000000000000000000015b0ef81908ae275b2d5c3cbc563b8424ee0be0e1f2fb77f67749a79b7730d33028a136d133825da14448b05bda1409d000000000000000000000000000000000461c575bf65c6c5754a214c2e72d6d24df2cc228ae1c9f99d75eebf9cf48f20945a6483185337aa7c0096543dc0a527d94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000006e7385d061bafef2c731ffedd01758f153e2635c7f2bc42ea2efe29931697a1c50e4a13ac420572afc523b7316190cf0000000000000000000000000000000018ad5dac1577c9cc1e9ed30ab277dd381a6babc17e86538570abac44573a8c2439d97cbc370cd2b5d2c6509a18dbc96f122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2", + "Expected": "00000000000000000000000000000000020da97236c2405d3f1bf4e937d8285014a190bbc59a17b7163a292a2b825f086db5d371776988d1aa2d7529a64d2a4e0000000000000000000000000000000016cf6d7b831a81d0c487bfc3380a1dc8a1bdada61426a457993f7d6c9c8fee9ee4959324bf7a2425b070aeace3cdaff6", + "Name": "matter_g1_multiexp_27", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017df783852d1f1f9c6dcf1975ed2dfacf3dc0cf942cbd7243a0cea7907ddb289f378ae59b30661d06d0702792ea9e9e2000000000000000000000000000000001717bc4192402e587400b4e7243db7e79fead2f878079c3af998b3a683a0539aad5d6c1e5da6e0a00ffbd10a2d891ff2b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb0000000000000000000000000000000018c9cc123fd18d50a7c878b31622a3727864fa61d784285b990fd116567c69dbc7ed872866db2166c7af1812157af9040000000000000000000000000000000000f38e55466a6d1cc2512c1282f74f5c0c19777365819e48606c0a86d2c6aab8938475d15a74f24db868802fe935f6107e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d310000000000000000000000000000000000816580c761a2f54c386cf60b1417d51a310bb7569a50b475f8d45f13ed6c1f11640079b5d6119270d616e77a489069d000000000000000000000000000000000d9af7b25803b611351f00daa88464e49b277de8d8fe22284a9001a13ed63ff931937d27ee19ba4000ebc212fe03a0390a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000032fbde9d988ef200df573dc99b087a8ffbec95349256989774194dabea55d970ba303657837bdcdce3b59eb54669c86000000000000000000000000000000000d65e89d8df2a189761e04d35c9f4d3a5292d1dc0d083bc9a982a131b07df6250cc969a3534808959b583923bf02125cb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a000000000000000000000000000000000ed3f47ea234f8fdc16e97eec7f4521941c37acccdfc422fefc6df9c1127ed293998945fb1bdce89ea18b9ec2b6e5175000000000000000000000000000000000a066fb6f1d69b88495bcb0f0eeaad2a41d5c6764e2dcac2ddb4ac340cda72d7b51b7901c758df15ea16e4e46c7053298902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf20628000000000000000000000000000000000ca7faa768ce5ddb6d668436e2e1692893d07afdf7466c00bc8c963b80cf0d44f6eb9a2070a7bd889ef692a81f9d76d8000000000000000000000000000000000f86fb53e3f061cbe777c7aeb63402616c428216a0c65d5d5a13cce1dc31567a4051420d54b4fc93c6bf263601046712426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000011113946d8ed7e5e545ecd0ef30de293206f3ac50e6010fa7a1cb0371f47aab2d8775c51172c4dbacb05414e65fdae10000000000000000000000000000000000022a7b8af616e4076f625f8151d748f4f49e6dbe439ec695b854544f8a498c7e261c366a4c81be5b9cad85a4eb07c36de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad000000000000000000000000000000000d863702db9f9e43ea311fdd7e0d87495ed0bbbddaedd3333108704417521b3da4b8ff0bf904710b0200453ecb2948620000000000000000000000000000000016a520d1162c7070030fea7702420de2a6e0f255c28a89bbcaf663c0d6761d201f07d86adf5ea6589e27bf844abf85a57431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb6000000000000000000000000000000001357cc987a4ee7c7bc063ec8cbaecbea0ace4b80e3af01f74d23801d5d37326ab5732222f60ad864cdc8c5dfd3edb37f000000000000000000000000000000000094fbbc2936e1730a1abeb42e58818ffe6dd97bed27a1e4fc090388d943763b055301852222503a2d2a9dedf69b3da26745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e000000000000000000000000000000000672e9a4eb4e8be8efab0595bcb7a6fdf269db71dcb585c12f9d7c1a8414b6e11d91373959d47a4c64a8890766f68671000000000000000000000000000000000203f3804abe330bca60b7bf9925a626eeae79d58ce7c71658b2fceb8cc93da9d455b6d59bb58bdc23b58238d4f01948ed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000c4e95c27fd983c31fcacb578a688c2fe055516735b5f1ea1415c5cd29592e7720eb2f548071fa3ac642b70e339757dd00000000000000000000000000000000067ab19ad1c97a773164e812771aac69fd5d199e4f60eb28c7aa5f09dd9b3adea959ab4ad47683d27394714eab4a40d281633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000003da3fcadcafc5eff08a736e4cacb1d6617c3f0850ffe33ff1648f783a4467163d1ddda082ba0b54e678b171b1f79618000000000000000000000000000000000a273fbd5fe99df4f724fb20ae0fee994823d374979ec7ff23dbe148f6977145de9a1f20eda777cbfe0fa4cf8c2a8949c6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000015b74087be4a98f4c5cb442e4e893d4d92602b1ad36d0f038f232ce25b53e19816f44122e8f5c821b40a0cb36897fef0000000000000000000000000000000017e50b1e84c7e767171edbddc397653c35b34141bd69ca7123792d6f20532f6daa5ed18615bb364b72744f96d4a730be6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000131517851372c44894bf433d5162d0da394b87a9554e9d4f6174d5712dbf69f756c5da1534eed80f8596f906f36799a100000000000000000000000000000000130a4583c7529129831ad621cd1e04a8fcfeed67ea96db4932809ac140a089e6252bcd101f17d4653555b1bdd9ea3a9b5906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000002b8d72148ed7076656128040e7dec82ecfc2d5ed05050b27361a85d0fae6d90de6dc32dbeaebac039187e3883ab238d0000000000000000000000000000000004021fbb748bdffca854bfc5de8f69a9bec478181477d3c6e41a7da2fab3100f7e2737ce958c046d6447370d47e373ad94ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000015d1c5bda34c6fafa52dd3801d94a04c53a3acbe43cdd128de3a346739df5afc6dba58d63c7cc09d18589c41d9679cff0000000000000000000000000000000014367ab7f03febf90be2279a87890527935725880ae3d418ec055004f312fa0c42c8f6fbc9c319117f6ce600d86910f16feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438", + "Expected": "00000000000000000000000000000000130db02ba2d24a3d70439503b089e6da4cde7b5c51b1d69774b38ae0f265aeb8996e50ef077ec12199ffa3d000adbf38000000000000000000000000000000000de25ad8eb2142051fb2c97175cb4cb2984ddcab65dcfacb76cfe60f6a47083a22dac4f6e06e357a194249b7363210be", + "Name": "matter_g1_multiexp_28", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016785db77cadde48a4ed0d2f8aa9f91bed9387a4766c3566217afec80b180461c8e1017297888e9c5896e509a26137b000000000000000000000000000000000025b26ffb3fa42b1a9e974eb23ada4b9329d670e38970e7abc937463e522887d777934895be0cfbf13d213b3b737a5f6cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000d90bd38049f2a8de869d8a748c9ff3120542f38fca6e8d5fbbff86baaabf0f19dbf449cf23c043dfea322d99837f7110000000000000000000000000000000000ede89c8bb8299726ec685765f10167c5b844e427d3c15da6ec2c1d97de174819d52caa96d5cc938e93dd09bbd1e0d813a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b0000000000000000000000000000000019e1e2706e878e60bf6fada47a4d4028750cb27749bcf8fff531ec75d1ff9b3a1b5e0bf19e2758899c3d8bc96a18a0540000000000000000000000000000000004b5f00109eb4832ffc9108740f0728ac059c613654a771beaaa028fef06b6cadb9dd182cc573d7ada1dcaf307a8bca4ace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b77400000000000000000000000000000000013844937de287b98db2b9631d8e36bc36ded8bbb3ebb2005ea5ab39a4844fa354b62feb7433b8fd3e72aa89ac8e4ff50000000000000000000000000000000005603183a5fb09ffcf6faabcb5042328496f8b0f83e8fe9031f9dddfefef43ee4525d1afe859177d4b9f966599005bdb8d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f5360000000000000000000000000000000008ad6b2bb88897a2e53d4fb9910b6244faaa045ef32a2fd223adbe6e0b1a5c1683dca69c0e9515dccf7e4589f1e69bff0000000000000000000000000000000013564245d53366d8468b51f88becc288b695879a70c3c753933092904b9fa5e64e39be30edf1f5e9de7eb29c4b3cdfebabeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000003b587bba9173011da620ff930befccb7b43093052636d6632fb6e9b59b8d127ffa0b7829b59873ae347eccf0e6c86c5000000000000000000000000000000000363be6dee6dd9a1271b24ff84c6557adc62738805b31714c9f7208c320aff220c02b222b96c62af96f1eb42b5299a63adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000009b403c5fe094f6ec4e4b9b7d098c3ca6fcd838e46a885506ebe8cb3d8b29849a8f3d8f9550f6d33315e69f6c1a6654a000000000000000000000000000000000714a7aee8bd6d754b9bf0292be50836e13ae886f7952c61afb1b45a02a2c378d6d22eb3eb882206a3141e43658a068c06e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f00000000000000000000000000000000115f7928ee8b8e47af2739dd70bbccbbd8c4c4f9b92868b981e407887b448745514b67164df86126a7aa53af9ea7a0ab000000000000000000000000000000000772b21e2bdc688f0b883a2ec5accd48a13ff3917d1c5ca8896faffca7e4097021ae3c348bfc2e8174db93e079979967b3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c60000000000000000000000000000000011bbf90f59d646617a6d074f5938f64232550e189c6d8105bcb67a3607e13b4668701f64933de602e5daf7b0f4f50c8300000000000000000000000000000000153ff6cb6a6dc6b6ec086e2ea8122d23e2c6abb8d59c7535fcbdfa721ba505d7e9113cfac69e1d81611c72e872071bdd29b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000001897421ca9a740a1f03d67ed31b3922d7f6067287b4addef6689303571b49bae574c343e967dc0f270aa4f91381609520000000000000000000000000000000007ab14771a4e256ec4009aa03af8caedbec4b3ab21d6499041ec58afe17175a656a7600c4bdac42c92efc9d2d21b48bb6b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e0000000000000000000000000000000005e4061b14fa76d4c02d77adc7e07881dbcb023dca9dbfd1301cb3252410d54db87816a6403d18c2ea8c18027674133600000000000000000000000000000000079d3ca06d0878a569a3984858cac6daf967bacb3fd540187e47dc2c0790d6cfffd1ae1f377c75910f0b9a17d2cde2bb3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f7897000000000000000000000000000000000e2814ce8e1011c37f6f7c38ee9543c65d0d40282793dec81b195b2d4f4b55f2d2b68416eedc6aba6e31b2234c3f08b90000000000000000000000000000000006ddeccda49ae15e5574bce201589758d7ab8baaf1348c30111e997154b6ba413c03e939e288fd95d808017387f1882947944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd000000000000000000000000000000000b78dc15a4f413ea9c8b347cd82c278cec530a28d239694d051812c4af08b5be888064f54d2fa2278ca4734549cdd41b000000000000000000000000000000000a8c5ecc1541fd79771037e247357599146fc46b852536529b841bf4b21978a85dd09c01baf8878bc2b6bd8e36bb93c030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac00000000000000000000000000000000172140620e46db480b2a9f1b7f9d0b374c0fa19145e3349906aba351686e0b75305db408fca3465fd263d06157ea471d000000000000000000000000000000000c20ddfb4502ad34e0934812913e222fd9aa201b9e10b4af688031d2202663e9c044cf3374ede037ef0c7aaa82428ccc4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd97860000000000000000000000000000000009d75caf6ffb593ff15d5635502abd9ef88675210aaf98a73bfea25888c90b63de14501459a038f07ca502b2b0eb98ea00000000000000000000000000000000091c4826870da1d2d7da43fabda1311384f24bc6d7693ab92f59cb76a06ea129911abdc22addd72181c3ecaa15dffc884d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e0000000000000000000000000000000011e977de99564d61c5e0d1654ceca0d0d63dc09a6dadf6baac980bbb97f38513459b391e40c09329d22be015fcdafa6700000000000000000000000000000000119164ddb3240c59428f11ef8c7e0469d219a591b926296f394048dd59a62a21ee2dbcca55f79df5cac6b784a2e06bc5e140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b", + "Expected": "00000000000000000000000000000000073edf80ee80c7d1675d05f8bed28da759098f44730bcde3ca1a9a8e286ff1791fbf22bc36de06d88b20f7f1422dbe38000000000000000000000000000000000d52fe400f41b902f8801063c0f3e793bf643c027676e0a1ad3860e5455bdde58d988b929582823e5d7ee0af8987c551", + "Name": "matter_g1_multiexp_29", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004663e332c105837eebfb9ecaf524a8f7f4d651f3eeae6909824eaaa6250c9f7fc212f98c6b3d4c08c5198477f240a8300000000000000000000000000000000057144a8578437c9a10a7801fb179e417e9bbe1b85e9dd8e2208943978cdd77a8345d682ba83950e174c6cd39c9eb936a57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b1530000000000000000000000000000000017c44ab586ecd185de616da02f99ee799487b32baf2470871865baa2b2e3ca20f61e6c82d741853b71c5578199d46afb000000000000000000000000000000000c77154ab5f0ba817b30672367bf1e19f9e53a95d7fcc4565f82f604a07d5eedba2182cf1bcca2371af4d1bd09146cb98fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000002df334ee40a5aa144d3727ec6c19d8dac476c01935e7ddbfc164112e35cca9180ffdae5e56f1fb31741c327b5733d6b0000000000000000000000000000000006c1721530a765ce427eacc4e5679c42591d5d1443f0a1bca8a87dd19d6a33b731db6561c50a35511735324c5f402858b061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d6100000000000000000000000000000000016682e225b46618ff794f2da02a82e40193289c9df4ed6985b4daca3e9ce9ac6e8ce84a3fd6776119ae1a2e84f62e73000000000000000000000000000000000e383f55e44fa8528e80fdf391f2804f7b7f3367e0db07b78647e9ceeba5fb151a5b867bafb2d9c07a6a572ee71c2714355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a00000000000000000000000000000000176de8a3ee21e803ec6fd42f7f297daeaf1541c08c5c359e286ba65b78d7c31a0a630a2c73d2e886cfcb289783f30cf20000000000000000000000000000000010645db8d7d42e004c4f76bb2fe8b99a3177624ce0c1f465e67f3767bb57ca80ebadb12fba65bd021106e17adcd8553430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a77200000000000000000000000000000000006c151767d1066f9567ed86f7759a6f425a9a130a4530a2dec0913e4efe2485dd4b0105f453e90bf27cbeee5d0482af40000000000000000000000000000000019a081fb1fe2893f1919628cb8a3b332ef072971fe6ea7fbaf79d327440274a589045db5d3f06d6dc32d6bc7038c528b89a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000d19d55d1fa04f886078bba50e09ece3a394f3413745785c16d17c5936941345e42e4ac50cba055d79f2d813c69e0b20000000000000000000000000000000000ba513864132f44be3056d3d3d1fe8d10b8be954e785e3d07f816875a3454fb6d44c1a6da8c9644648b46dc7d8a0b67120b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d5620000000000000000000000000000000019cb4ac7844effff88b242db9908bd8773d91cbd8e076127493c548350bb9f8230d57a3e9c4e4b212e5686bee925d80a00000000000000000000000000000000021e94fbe9881b2f5ce2e8d777a33336fa21c24818cc1b6b699f0bf5cf1f22d7b9fe85be05d09509b88391f78eadf14e3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe000000000000000000000000000000000c6429ad7548acf43bd9e7fd9ccbb09b5b9b4474937bcca985a2d00c62cc8b72e07e725a5d447e2a92a6bb9fff0c50c100000000000000000000000000000000135ae562ac2225bdfcbed36817c8deadf892da1f8982f4bf53271320bb4e702022128dfbf9e48fc6623648878020c1a67fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000004a813c60a1988f7983f6ac644a66369153319e3bceda90fcef6fdf3e53ceb04b2c5d240cc65aaeb2530e8931f1a962b00000000000000000000000000000000141411938210cef5576dacba6d521bc46b13ce9c1f2a9aa41a0e9b56639995b69b6198f2a406ca5e471cb0a48233985ff0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000041855bc5957b8649451b7d91ef58fe8e0770b113ea3009815e60cb36c9b7ab797b4448d3747fa9b64b7fb50af906b6d00000000000000000000000000000000048f78b763a88fb7122e117ea4946a631be83b5ae456f0c77a16f3f2b546802bea7117eb27e23a5db65d616966bf2630f8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000003ca70d52cbfe2c097c17bd300f4baba1d03951c6dae613bfbbd53f68598a71d80a285af1a16365b5b82991599ae8fd0000000000000000000000000000000000ff454d717d8518415f23ced167ad7ad1ec76c437e29fef81b5604e8bc628b320fa39c192f32aa6201c2b5b4035cfddc285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d3258200000000000000000000000000000000098363ac967c6800b28c28afe92c1379574ec11e0585a0319273aaa6b92322563ad56144437569f3b9cd70ba9e7f9e030000000000000000000000000000000006e4aa226ef031c07150bb231046f36b8ced6b795b3e3f25f707435abc214f14e0c420c699f9c880e8d647ba85d467ef35bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e440000000000000000000000000000000001ced5366374fd923b3196d8f6e35900b80d01eeaa6ac41bf7d05d1fb7d47810eb8cd2d1ab793126edbe863be4c1224200000000000000000000000000000000010b27a94ae8413494e0560a10ac71554ff502be7e86cd9760b0d4ea7d1df926cf7ff1661b7902fb93ebcfd1542619caa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c800000000000000000000000000000000120ffc413256888669dce253043ace9a8c924f2996d73ef3a64d76d88dab415c870071a22b97da222361dc02d91cb25e000000000000000000000000000000000940f2259f4fadc3bfbed20ed2b80bdd86f30a846d6167661339e15548f6e57030fcd0be99496fa406a2d025077a4a4e1155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a640000000000000000000000000000000003ea26434b5bc703c242cc5e84e17be5c7777758f0b232feccef6d200db9a03f10df46cf0eead48064f8dbbccccc3369000000000000000000000000000000000649df5d665a64565079201123e954e78f07177739d082c2bd0aabddcc13f9fec6ef082a1348a369e446b82181e52aadc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045", + "Expected": "00000000000000000000000000000000110fac33d46271daf3924995a4798b3f62c79562d3b44f736b91add9f2af779a614d4b12a9c0d7c60bcb1f104b35474c000000000000000000000000000000001592121fbb147085613d1b647cb0e4a7b895bfd4e5391b45bcb287975bbf0e5218078d3e88f8383a506550ae07c9d167", + "Name": "matter_g1_multiexp_30", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000033f3c31337bc48622d27a9a3224a2acdb5c538a59b497a4a85840c81cff667ed0a0e4e3f4bb23a9ae53c1e79ea54cbb000000000000000000000000000000000cf0dc22af4530260cde26aa0eedc83a0ec3ae87d024e6907f3d22070e1054b3d4f24d5ace7218ed44763af6ec3f25ee32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000935dce5baf85335575af5a6801b36647727c3e28f224cf25227bfaa52fd646d6fdf0f24466631a93506a58b5f2df9b70000000000000000000000000000000007e032c51e2d9aa53a3120e5777a14963af8a9fc65dadf5da779c5ade6aa043ff496cf4f33e2672dc5e10c4a06dad86a6583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e86300000000000000000000000000000000178e7b4d05c4b7762b474649b38a5ce999c67ea677fee77115ce7e55207d87a82b6d05516ab41c2bac294fc382c0e12400000000000000000000000000000000126e5aef1a9729c73278b805cf102934239d1f706bb3fc3a81f3726feb4b3d2fd8de69fff2f20d5e5217edabb645e8df5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000014760b82d3b4949c67d38c6d9172e12bacd52ed49f442d781aeccb7c0444407629e3b7d5d5e1be996940966785940e46000000000000000000000000000000000aa2d6391e40e50ab9ece25786a42e8dc657e9112683279b143be5665bca43746244c27352d3600dc62c2c1c7776924339c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b0000000000000000000000000000000003f7c65aeca3fe6e67c91e1f284be35149276a9d9c0c1907010d8ce26d5c88f2a68b632530a31e41388cfc97529485f40000000000000000000000000000000012b9322902ed50ae50e3bb3e07eddec3245df27f193fa88a7685795990a5fecfa4be4b5bf8b0702897cfa369d614eb942196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c5650000000000000000000000000000000013995f89bc17b99384e389c9a768fa4bc37526606966a74a370c9f964cd9d3a7dff9d6be2319d2c8c9d5ac1b6f5140b20000000000000000000000000000000017b32d8800e21a4553a1a15ddbee029788f58023164e65b25086e0dbe2ee0c16e519dcc4753c322b50c24edc305cc26d8df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000017601971d5328ca817108dc9899c9c3b88aeca2ac5c03f70662c9bf6bf3e06d25fa4b7150e0838c21c9b089c7102a17700000000000000000000000000000000198db85ed42c61e1137fa50c8b2a3ad2eca4e9dfde3553b8ff7ee3aa6389d73c80d500c883e52be5cb9fe8f828bba84f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef0000000000000000000000000000000000c830262d029435b1b857e7e3cd118e8a6825e3e413f5a5f67b37da686f442577c0beca3e86c13ef6924472305ab54b10000000000000000000000000000000003d35dcd36ea7352d453041e821dea655422ae01a50731698af020234e3ddd38140c24ba2af296a964f4f5896bc0af8c8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000009830bb211c58fdb25fb97a4ba226ab03516911e7b7d98f25b94c827774592b5d5c56edfe3c3040454def1429f81c4fb0000000000000000000000000000000003f34873ad16852f435cec18f977db00f786b7860c580ae0dcff8f03a8a1edbb417f01e0dbeaf035b6f60b733f38a564cd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e50667000000000000000000000000000000001718cef19fe02a179385ba031f23d28e20e7f57ee82db31e632cc3530d17291e54e8a01564963835c724056c53f9853b0000000000000000000000000000000016c44ed6c85628341789e80e1d95a10399b6ac126319bba3c66bdfe6a40f2b06b721a0867c30be1356656cd36e6370aa2a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000011267a6e9adc4b547ea0f42ff6cc9b35a40c3cdfd7ea3c4169fe1efdf533341969cc591f26fe9a48a44e544c515339310000000000000000000000000000000013d878f761efaacf28677577c93d825336698772044266d469b934332412bde9ad5deeee4c1f534a9fd89e799584d3394a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee280000000000000000000000000000000004ca71357762ac2e9bc1f53919ee2c19d071fbd3918f5948f32ecc78be1e65672d12afb4d4a8df41a038bd5448bb0a04000000000000000000000000000000000b80b54ce782afbdad1cfbd57a852f629c0452346d5b898062a8abf12c73bf79296564d3fdb867ddd81156697a00f03ba62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000009cc641fda19b0e33065a35e74a7ac28ca1bd3bb8a7fd350244ad0cd5dc89d91e7b2865e78ba24e112589e298e6c5cb40000000000000000000000000000000009c3ce4324dacb1e2ca82f4ce6a7ed1292f204f4f7b2c5e0086843546c5c00d16be4e7bd9c979ecd3af590b40b0d70a4a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a60000000000000000000000000000000016ed972bad2d24d80332c4aeb1dc012ae4fc30a11597df1ca73114945c20e337d1c424e636d403141c737103a4dc02470000000000000000000000000000000009ab2d22c0161247a3c4eee341027a97009ea95bfd45fd186e15feaaabcfc09fd39dfeddb2d3631b943958620555fed81f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b40000000000000000000000000000000001721e0fe2ebc0be63df10f4b9db3faa5c5fc3ada0bfea176c4fcd1cbb696779c03602cbcc1da3917dfc09af72fa3cee200000000000000000000000000000000192e3e3b5b9b087aba72b852319c200451a4976a4e7cd817eec04c007c8a2f800fe0bf7834d22a21c1989ad8c6ef73973bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000159c5a01e76ee666e8e22aafc77e27705a633bd3d1dbaca92117e4b80f917a3bfe80b36d3fc7721ed2fb8434558c780000000000000000000000000000000000c8c356e19c759e1eaacab45b4fd2e0b42dadf6aa2ee8c051b8ef4de0c4e583fadfd86ff6bbfca1eed42a29afa470c8c1b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d", + "Expected": "0000000000000000000000000000000010f2b9ae629ef12f213e6408c94601482c4c3cd0ee33e3418c86f0b8092d1a1ab7d2600140f6e1231297c3bee4a48a9400000000000000000000000000000000018446e6fc72ffb3c6c25d6aee2d9a8bfafec7b4f63dd3f98fde09c088876c7f4d30cc0ee31985526712228766ad91d9", + "Name": "matter_g1_multiexp_31", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000de77471af6d857548f26f2ccea9c33f50db361c59b097fa481887b5a5deb4fcbaa25ec1008b131fedd3711d4d3ba029000000000000000000000000000000001037ee7b2005032974767d672e14be86177621db0ad5d7df5faa966b0e7db6319ead334358142feb370f60cec698f3d1bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc0000000000000000000000000000000000ae57db1c0d1575c49f8b049667e1c8ba0ca863fa56ee58a34ac1ae780c92418ec50294b666a0f99e0efcb2686a4d27000000000000000000000000000000000aa08900fcc4f9b551229b7a8a59aa9b337100c68703ef60597f6acaaa7c1ce910e643549dd0c328a7fa17e44b68de1cf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda994380000000000000000000000000000000016cf186f3a0ee77c7e990ec0784d99510320114793fd7a672d5f739e9b0f1186faaa9d5914860d66173696c603173b3000000000000000000000000000000000124f5c20e988b460c261d274251841cadf5c99a12c9ae8b4b3baa7fea8b592192dac3506860b15289df704cdba1dfdbfe45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a67000000000000000000000000000000000166434c1551befa708de9201c02cfe18020d18ed881ac4d154f5e560995f302b57b1694740f76232307ec0ff729b2709000000000000000000000000000000000a961fa3c19068590b4c252c0429414ef393ee071b02a4ef15f6a5c722a73d145c8e058ebe1997058b38ce7961860da954868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001618c78e4962162f253729c4cbe326e7ea7dfd6d5cdac1b17353135485d434fe7c4d857df673793e9d12ee65dcad4bb50000000000000000000000000000000016921790d30423d878255c44966b316f9c29dde6695d66a97139fbc6fba9c4df9e291c308effc424e5e2134680846fc37068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb00000000000000000000000000000000128a8a8584726a4aa2cab71853f843f49efa79071a8ed0a6ed2c7913fbb85e254184d457163fe647d0ad719d04e6857100000000000000000000000000000000158d36271e87ac2879fdd3f1fe8ff306126adb340ed93406951e372a7f7f3deb1c347ccf598f2e007d92f502038bd4960042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e00000000000000000000000000000000047e63ded02c49b7126a1023f1ed4a0af20c2d5e95718f474e4171c0fa888d7fb53b6a2bfcd47893aef6657f31071167000000000000000000000000000000001404e16f51ea45098d5bfa00ece3df841a3a6630bff2b02a8063ff9af5c3f149e504f04e1fc9d9bf35324569e8b2e1730a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da000000000000000000000000000000001866eb045ddc4e29fa612a31a34355ecaaa8482cd0885bbfbc5cc0b3870a86a2b4c3f15da23638dc03619cae6b721f1800000000000000000000000000000000086aeb6a413db889a86bb3fe036486b4e26dd614aabf575f8d63614a300df8a528c9f6d47d59daad59d840f591063b22ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000001765c386f85f7282251b6054f03a3941d44f9a8ea2814a49f75519f9fc985133937e2c9e06b59441a6d9a95c806d6b10000000000000000000000000000000011db74b6bd144f9a0d48185a3e9f4adbc79131764b6e82f11823f1bec92245a55d82e6d949f3378ea6605ec84f0613285f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000000bc9a01aee9eb527491f7334959b0f4275492afa38044f0e6dd222a3704f440b5ae2120e8e2798179634c65f3d674413000000000000000000000000000000000ff19f94b6802a4788c4fd84f66b9be03fb1417544d56d0e473caae0f9b9124c622e6298624fa1d53886fb5ba8b470fdcc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed2165000000000000000000000000000000000df5ceaa6ca501d1869b51f035c19c0f3f9db39c739f882a380930cbde7737790b25a2c01e65ed477755c2beb16e97f300000000000000000000000000000000148458f4ff4fcf8559b9f8a2ee4e486febff21d91fe4bc3c77988007cf700186894f1c1fa18ee3c4595a462712750d3097b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba591672700000000000000000000000000000000029b16c9578701febf6662da833091deee23e647a15f16895fc057a37c153fa738efb1742c4bfcf27eda953a07aa01c3000000000000000000000000000000000196d74cfb1e6472b7ab67a664a7c46ad0377c2b465e12d94b035b4b79c7e358475339e09690557e4b280cc84391eb84752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000e005ebdde060ed0233d1b1d6344b8d21f8cc1ceb6d4fcca389303e1c44c5964a4521dac8ce225e2e4909c4b2a47f622000000000000000000000000000000000fb3185aca9683a81d41a17b3a6048e75549d589354d4652756a4663cb25b9fbca1bcb9158e2ed73765d03be4e2b570f2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee300000000000000000000000000000000004df18eeff223e3a255e6652c3d14a6dad17c76e0597b43a6679a85f78d4bbaac1e2fc0ccf6a89149dc18045169345860000000000000000000000000000000019d60ee8b23308fdcfbb26ed30fda1dda5c6841b46fcd902e6c34dd268fdb1426e215d21bf650a340b284d5c7516efd3f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db44580000000000000000000000000000000006b084e91066f299e44a0c37cf65c30009006ddda34d4151b0c18a5545d67f2bc76df0bf9a78fd2b771795c8d041655d000000000000000000000000000000000262ba1d9dbb009f779e2a584ed313d78e4ac69a811e071c10e21027138234a32deceab16a33767fdc4a78062cd23ec71c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc0000000000000000000000000000000009aafc73979c000236c08e089828880f54645b5ff4c1dcfea0ff41ffe8e3fce8ba0dbcebf0d4205bb6616a737b6d3542000000000000000000000000000000001399a2072604d50f92ee186924ce32c4e887803dc258b7495aa2f3d2187571045db7f360d2614b198f83bc8024b06559b0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4", + "Expected": "000000000000000000000000000000000365ffdbc48aabd8f0e786634b9a853cb8312bf295543bd280c1a0a9f7d0f8ba95b3aebe31987ffab1f69a504edeac2400000000000000000000000000000000150af5ab7e9b1bc60cda3ceeada36abf9bb43f1182659d8d72281c1f1cdba73fe7d6e52abaa7506b89ef43f092f25bba", + "Name": "matter_g1_multiexp_32", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000012a651f467e9a1c1cc99c82e16cab2cef53b77268d968dcc73c5008e103d2e4d19aef4cdffa24b9474fcb393a48d6a70000000000000000000000000000000005d202cf9bc8c0124c0f817465eee7d4b1219071cfde50ce2cf8951efcc21fa19c762a1a8630eb7b8dd90cd03b8bbb0484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd00000000000000000000000000000000060650b71c97950ce5cd6b6bfdad46d66df454c5aae1ea313a70e7fc841e06f64a31edaaced17d8de56f1ee75f5263540000000000000000000000000000000018a211f44acc52e92ab5eb1ce304d80532fd4dacce60370dc62d9ffdebbf749689620798429b5ad1d8293c1967a43c12bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd10000000000000000000000000000000000f51ac340d512becf5d7a515111f63123e9bc940242ba42be9f464b89847a8cca9d93360851e3d047de4ee667a6baf0000000000000000000000000000000000dd7e71b516b3752c5be5ee5f3908c17e3e019b46422f24659596a42e569ba9e8711b1e8f8329cfbb990942f258cce103717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000015d542246cc0b46bbf5571c3173abfcf10ba447e5ec962b5f712ea7de3974c2873df1979c9d6432bc88d02588a3730f00000000000000000000000000000000005e1611597c12a4c7aaa25bd9ab1b6d30c58bd1fce3d87d66a03f25d6ed110c84c3e902ff5475795b5159126debf6cb522788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d9400000000000000000000000000000000018f565b38ce775e6b40581f757935efca255311b872fea3bfafa0662620ad5a02a7e8ce48c17daf45668c95ab0487c4e0000000000000000000000000000000010686971b402783c1e7d60126cf484fd01b871944179adc4b28de5d72e5b8823b48d382a8b69f6b4681c74961ca2a3843c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd3840000000000000000000000000000000008d42e31cb4c514e450f56488208444481db0beb5807c6f1c2d82ee09c9413cd6726dccd72e0b8ab6f6ce6492921b14f0000000000000000000000000000000012143ca6dcc3bc9edb5b10c3a47a5130e393986dc5e83d1eb61d9b193ca28193101eadf00916a3cdcf7b6c1369b17038ccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a00000000000000000000000000000000101eb8b48df43c3e01c1508aa9d3dbfe168e7458cef2ff61c15d5b4e8dd11be6b9a76966c01682fb07368f22362f355a0000000000000000000000000000000000babbb820a5a8e0bbbae1e2455d54b97f6771ff914fe33a007734d5072a993df31c6a2726c8b03a8c2dcf48a73959a8ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f0000000000000000000000000000000002fe8c461de25f5e6c5a082fbc4ecab5a37dbba9255ebaa0b5d245735edd27550968c2558ed24f7bee99092228e37c8a0000000000000000000000000000000012513b2fb62725aaf948403c13f11a6d7461c70cce3e4f912c8d2cc9f2a8676d9bb37face3770e7c0121bad6af6302d121cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c91000000000000000000000000000000000175c93838001f4c67a3e0e5dd7eded26a8818b2e492eab2e0e6f8b421e3d3611561c8b933010a3c5ff96128631f4e88700000000000000000000000000000000136292092a366a73a5609cb1e7fa403c59825e99c8c91a37b289ed779c4a3db71370a4bda2cf8509cc9d4b4731b4f52d2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a05000000000000000000000000000000000d03e1d6dc4bf59262fe3bc3e163565110b751c534e57c621b4be59bac28d6e8bb379cd4afa3740797dadf32194fde310000000000000000000000000000000014ee46a0cf13e795c8a46399ae63e1b812f237eea725539265e13d3ad1a663374dd566df450fc1191512ba978736e5b779cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000003cefffd8fa01842c36dd9fe1c57efef3278eebe5d1020582c3d13ced75d24177127da37eb59e9b46b4a0a19421a5aef0000000000000000000000000000000016c258ffb2edb299fcc04ad309ee5d8a8f186db5f3af8011d42b22b23687c2e814e2a8d366f3cc61d7c89bd9619523b31973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e400000000000000000000000000000000097b6535843436f879ce659b6ac9563d81ac0262b9a861bbb367bf8244a35a5de51f3060d05cb2174cb41c8c3dbd8dfb0000000000000000000000000000000012dc9607e0ebf73e3577ba1ab39437b03215e366cf1ecffeae4ad4c7919a63f62e45103db65de4c9e3281d7604b07f24a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000004479ec5d5ba2f1c661df8e4f85320d0e754372e0c463098b0ad7477f7373f309c674dfd31c7f08cccbbf4bbd17c23d7000000000000000000000000000000000470cabd9f5c4bb8b1a370888d8f0f486387a89efb92912072fb0907a1e64f3327e9beaddeaff44c502414632243d6fb99ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b00000000000000000000000000000000038101da3c35dff20a878300bcf69e393b77873a971838581daa9d096b00bd6fec3dceca882a02d397a90c816fb415a4000000000000000000000000000000001184246344c03be6103acd745b3ed37d8f67ebf0caecb00cb2528e0da9aa3f352a4677dd6b832c042d6e1235da7521fbfdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e90000000000000000000000000000000000cf99121ecf9b02cbd006348b16f9d80f64ae3c946c4802ec6bc056bf6e95e01b80cf3fd10ab1d30260a402b7c46f880000000000000000000000000000000015f35fe1ec8c258095394ab2b021d63ce54ed4bfe14cc5666f5ea4d5a0461d535b8bce3263913c1b4e6db6996cdc037d786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e83f4b1d3eb8d45ec0fd9a4ef001e5bfdcfb9c99a6d1dd4b4e8043b4d11f5c6fd65296a33c7fd26a4e30dbbe1869090000000000000000000000000000000001197b11d6747280b37769946549ad9d4a1ff1006ac726d7cd322cdb4e3cf86906c7ed371e770fd95ab4fbaa1b7b514d985d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6", + "Expected": "0000000000000000000000000000000012f496f031f5c1b594256e272520ab98f3733fc9c481e7ec8de8ba70f493065eb25b681a3959994d37aec979c22c6c3b00000000000000000000000000000000015dbaf471eeef9307d8dccceaee179d8c9072b052af66fbf049ad1d346e08bb555238a763e903541fc72d9edc30ec30", + "Name": "matter_g1_multiexp_33", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b632afb8deb955e64fb4ff5aac396152e23b11a3f326df0d77b3ec078934cfd5e486244aebb44cbd1599f594991a26d000000000000000000000000000000000519f9de5a5b1623e4524be68b5ba0f997addb4da78adcc9c3d5910009a261fdf8b0efbb6e2a085e74112ac4e2106ef319582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d370000000000000000000000000000000005456d9312825dcfe5501b2c38aa610a767bd38f46cdc8acd92f0c8206a9c2f9b8f65c8baedffdec5e69f03fd3adc4c40000000000000000000000000000000009b2dab21ba4e4b4c284a623994b92ed5fff0fc198bd154fcfac9abe5f05b830066b44894ac6f92bb2f61bc88a7867a8ac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d10000000000000000000000000000000009f55a987011dcfc796df284c7bd758c3024d4f09edb3884dc087de26fc1df0f71067d44fde07fab9334971b4a0bace000000000000000000000000000000000003a4ee3e9ac2632cc81cbd4ba397d44f738ee390a4af6ecd65079f412bdd8c4a37d5413d0d9a7dbeda8a1267d6d843b069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000173c7db310b54a4a720074dee01dc0e5f84b606c9c3ea0962bd4610b569f478d7a5221feaa944054cf7395e578d730d8000000000000000000000000000000001697f0e16c49b223dec9e0fa429e68dbaf96b004a561aa3e37158064ceb9232c1cd21156c053fb89ddb230deaa7f8336be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c000000000000000000000000000000000ab7f085b711171f999d0c4a46cc7c8cd8a429f6bd90d1b860c01066bd0d193f1c1441ae5aa97d690569807749ed69e1000000000000000000000000000000000824841eab90d56a1810c129b8f27d0068fbb7e3536d6e56cdfdd9eb553e283c5d0ab1c418869e886fafce53697520859b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000bbc8c5b5e4373e76457fa45acfd3f1151735457b0fae06e1d3e6e5dfeb35815aed44bbe6395039481ce02d2aa2c502900000000000000000000000000000000089ac22ebc582bb71a60c88638747e2243096e8d193fa1863089698fbf6805128f9e32636d6f954ff03bfb6c5bcb0060d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f00000000000000000000000000000000001fea43c3029447965718c8e76100875acc8fb4da66f7a4f7fc5260de3844aa9e9a89ae4d9baa11c118b9f851fd63de000000000000000000000000000000000844aecf4a3ebfd8b711dfa9efaf1a57d635f46fb980903e362d4ad55d48c4289a3fb1f439e6b7d8f88cc51867d6b462cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c4000000000000000000000000000000001384e33086ebe795cde3c951de9b48f3f0fa2f627524cf0c4e3691599b62d4611c6a84897298c287d162825c3f153a75000000000000000000000000000000000a04af7cc41c2d3663444c8aaabeaf70dd146dec114458b3d1dbc95cee99ba89a4c5a38f2974622292e3236fe2aede6d17f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b400000000000000000000000000000000158820954aaf8e6387cc0e8e528723e0875f5f719a46ae5cd9d967674815a2d9679aea9b5736f882d37e2dd26b7db17f00000000000000000000000000000000058cb933f8dbac61a22477cdb3f52c9e3de6f060dd51aada35b6f8480a53e8eec8f82800e89ccaa2d2eb1dfb4352f16561ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb50000000000000000000000000000000000a7c9c1bf574503a884ecde5e921da80b299c4efe674a2d5c841e6036adaf7c1156393116c2c0b9827978d43f1e3e440000000000000000000000000000000005cf22e56bf4a46504ecedb072fe5e18096f9da550065612a1d00cf79c65384dea1bf59cb7c52de905a04f1886f36c8a0fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be0000000000000000000000000000000018e344838e2efd9363911898f27882f67454dc3b1bbc71f1d99e787bbd6a1ec9744876156ed8db2ccd826f2b4fa784050000000000000000000000000000000005528854a8568ec6491c79aae1df15d965cde683c9ea400b470105117f2bf3b41d2f958a8dea5f866a55e60fd06c1f07d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc400000000000000000000000000000000047dfb6a6125ff02e12c4a9d88ebcdf8a4375367e1473f5a0d99152bf0a4055138aa9a83d98d7f74d9fb8888f643cac00000000000000000000000000000000019d0bf5162ca55d8113a97cc3255d090c6924362e6e05083fc323dafc3b12e898cd600d2730acf8cf5cdfd4420962881c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e132de353cb09b69ab369c616718b9cf492cdb9d3002593319a6e7b61c7d90f94808b75d8c7e3b9d7a811d01baa47a1000000000000000000000000000000000d636abffa063379e2084cfc09da5ee04d40d8e74ba0247a01be414cce820024766195520f1d2eaa90fe254e12a4d86026cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f0000000000000000000000000000000013f2367ff71430cb541557f79c5ae8a0d9053d82341d83037c1f73a52585255b205706227de4e87d6ea2ca602483d2170000000000000000000000000000000011f3f4e882de30b40bc160e69fc2bf4f7c588cc83bb9dce3467accec7c47714e2b326be001a36c42ba39c7f56b72d6fc699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000014bcf3f26683234584d79b436cc608462f1e2c20b5ecc5019988d8e30137859a4b6d0e1135dd5bbea0781b8ed3f0653700000000000000000000000000000000090ef29bf63ca97ae8388588227e1d1a0653c43b16a35a63f2ab4f0b11fd8005d9a85d30a7406491d983f347e4dfb9f140e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf9523000000000000000000000000000000001817fac61301ea6a43d7968b22616b836ecd1f20e5883e9b475c18353b066f93bd68a8274d0b6ea4480d8e314766dff7000000000000000000000000000000000c52fc676604061338bf0712fc1606dd09783a1f9a5250e3417056e3c39e59a28c7707d5225808414279ab61e49b6081062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d", + "Expected": "0000000000000000000000000000000018410462829b3a72024468ddcbc42d59a99a70296024654f99b591ce016304537c525513defb655417ba3c0f5e614aa8000000000000000000000000000000001416a19f73407c262f5e464021eeae1d1f10c3ae5e45f132a2f402a75cfbe409651d3795e482b15d29037e2f7105255b", + "Name": "matter_g1_multiexp_34", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018e6f25220e4b4011a0291424b4062930f5df45eaf1581d9591560fd77e630411e0abd57f9973d4542741de5cf3132e7000000000000000000000000000000000b31f903e7fc36327e404973b90efe5a5d2249770170ea1e58839e19d8aee99743be012b6e8a3fa73efc6bdc08be372f764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff20000000000000000000000000000000005dc07fa620d476d8f64358c920a401f8b08abf739befe1c266fb307b959f37542140e398c33b082d09f9f53cedf6f810000000000000000000000000000000019d8e51a28c936b5037424a7ffa8cae75496131eeb2b2d5034e4e882c1c91f6bbabc9ce4fb2fe4be3da4eba46326a3603280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000006f5f80dcfe8be87d057e2162788f7599e55b69ee8c6bb6a47c505aa324ddb5ffddacfcff35cef3dee6264ef73d6a353000000000000000000000000000000001056081108195d4d27af7332215c0b444c9f63c7574eefa81046e1d064825492e2dfc5bf2ab5847a37e6b253d9dda9fdd4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb111770000000000000000000000000000000008db431907692896f9e6e254a6eac1a0ba5f9cb84563da69c3601aff1370b7a5a98edf5a5fbab06abfb4496c777bd83f0000000000000000000000000000000018a3bc407fc42236c4429f241fa760c6513614653e8b02835480dbe1152763bc6a1a7fe076e8bb44ddc04322cc906e1ac66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000cf32da94af97001664607c7840631a8df02a008fe262c6dc649a3eff34a42dcb98884212bf3e979629c98cbe5fc457f0000000000000000000000000000000019b3b4d82326ec1aaa3de3b2f8e329ac0243d3f6bf9356886be4033aadd0398a5c58c68510de29f92a7ca910d851da244b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b845000000000000000000000000000000001504d90c52af16b5f88357c87d4be7c329855ccad6f6633af0fcf4341fae54aa4b1ddc1aa22fe1ac12e9d850a05a9ffa0000000000000000000000000000000012ea642b96304316451dcece5a6bb324d197e31f56ef3f1a17c973742322d08f443b7cd156787f8291b52c0a6f78b4b1708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a000000000000000000000000000000000fc61e9589a2dd7f6dfd613225d80a70ceb977bdb518b5a16e415f887eb73fe9fa5c9130d5fc6deb4ad153c5de0907d6000000000000000000000000000000000a0fd7de87139581e9b1ab707e25c186640db92875a7822d61d8c476c40ea07bff000cbfe6975076434d0b703695740685ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000188901f19a776ebd2ddad60209f4545ca9b0a038b0b3c67b6f5e35d61f8cc2a297d51450663c4af182079d3ab6b01d2000000000000000000000000000000000151b9eaaa281acd803abd71ee4098b4ff6535e5081a33cc68ecca54eb9f1a8f94f3b1b21440f33b8648ec456dc1cf7f3fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee314780000000000000000000000000000000008ce8bd24052a8e1472bb64cc215974e20bb16d502b3a8113cd6e3e9a2bb7c3fccd45ff711518e8430221f40859374ba000000000000000000000000000000000aac2e8db9123be3e82905a0fe780daf4a841f6f961428b9b431c3ba2ac31e8c06118402bfc7fd15fbe3ada0ec8bbb2af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf2000000000000000000000000000000000017c580f501a1c4823483ae718371432a8a69e16e42dc0b15bb8e01729b6707ec20b898e3835bba40d7e8802d9438281000000000000000000000000000000000bcc167264fb9d6c27272c2280d8e89f9655ac7e6408694a3a4ca6fd0b46d1d7e3cf608bc2ac343806c5de42ae7a99e80ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000017ae89082d6f531bb7905068a9c00017ba8ac8867c6e467fcd3e88e9229ba5b21ff4d0a5ce937b75b3d5dfbbe35f2e7000000000000000000000000000000000005bda8d641b782ed51c416d0ebb1cc7c8f623d49b741a7cb93b3514e71d5b9102ba2e6c768661686c2af2acedf466e4dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d099539000000000000000000000000000000000150ed2c2b2d1b0b87badd0dda44325000a6fe98d335e03f0d4d147b20d4738e1e0f0ae0ddb2783bef283684e631ff45000000000000000000000000000000000ec1fa174f3f42cdb0fb67a520da161d9a9d1e53a5b0735738580fa3e80550c95cc3a1cf67fed67dc2eee1597e469fe0e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f900000000000000000000000000000000111f184636052719c6df1541c100d5a21d573370fa7afd18f5ddd1d86842169eeb02c494b33f2bb2f54278530729bfbe0000000000000000000000000000000016be03c9764aa34c898dcaacabd1493610f55efd36ca0b35eb48e89c7968e7a720d545b18fdb95954e01596856d42975cc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000114fa84ccbe9552a2ce2368f1778a1fd3c67303d8036fe4ba171ba9f2f6039aec1a59fea1b8efae88c01bb50e53950440000000000000000000000000000000017a51bf70c41571f36d003c0715238b6c8fd64185f616cd9076b730ad16caf364a75fe68de246249a42cfe013606874709a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce000000000000000000000000000000001450fe1500a6fa9d966a0c905167a414d59a3f8a064089f09db047241e9abc31d9e41ef73558eed741541414731f838a0000000000000000000000000000000017e61d4092537ec48683f86b72123637df25a5fd926e5703f993678a798dbe635ea29303f8b4d9ac76231a71cf515a70d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000000e72f0c855fce66335533c05ae30031cbde78ef07571eb1b645fa3ac5f3a7d76a4d60cf078145617c5a7ccb16266bbee0000000000000000000000000000000005b3981900432b193985f28a88a72ca9958b4628e5ff9d2cf8b0b23184e2bd433d495636de3d56711f207719fdd3fd2f9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12", + "Expected": "00000000000000000000000000000000110feb31a1c40d570d7281ed9f0c0ac8418f4a7aeb6be3221b130945becc15bb353ea63623ec7dba2844d3f527c167e6000000000000000000000000000000000d76c7aed58945a7fe52f37eec3be7cbd4438645a649a04859a487e9e2d4c82bfc76f7ba990f825302861d82a748c8f2", + "Name": "matter_g1_multiexp_35", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000030c6580a3dc73be106748d070b24d9231c382df143fb4bb8ad45e4723b40f90724b7e54510da1b2bee523a29aeb58100000000000000000000000000000000010cb3562fa1b0a3778393412994e46028367ed52dd62a1d446fa02b50acd48a784ab49141778bee5036b7d3a95c9ec217b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d000000000000000000000000000000001972db503f6d70a0b247eeac7fef277098604e54465309967b68d24ec1cece802d8c4b699eabb72e03736902d41fd5b60000000000000000000000000000000007f30233f9043927a629b11e7da48f895fce86b31911ff5c511c7b50642c296d37a3078e2e12f1adfe668731d0e6810ec6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b4800000000000000000000000000000000011ab9fd98e42539382c85bf76b563478fae8cca90ba1beb0be56b405da8326e6f1348b94eba61fa29c78645f8eb96f8b000000000000000000000000000000000f30617240632d129ceb69de1d69a23c9bdf950819608deac0600d1d1fd730a3a6d22dcfd635b25154b5ac7e22b20c70410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d0000000000000000000000000000000007556b86cbfa9f186f38fb1a8adce4c08f93f874bcb36ba61df5750c7927cec8896bf831c0150c249067ddada2e914bf0000000000000000000000000000000016ecf045f13c78de8aa18c2ddd1714bfc532ba8ff5b7851b58240cfede20f032067e943486df628995b8f3845289eb02d97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b30000000000000000000000000000000011379452e627dbed2ef1c74eb917b95b3933b8fad8295235cdbd6a4394d9b75cd3598c930d48c2d4abbf1558c65e97490000000000000000000000000000000005e7044829ae3f9b073e4a2237de96b0a1bbec3a30dc39c839573eff77321b1e0a49d555f0e31b8aa096f83f5945026bbdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb360000000000000000000000000000000015f8f0f22c1553ca663ce7e9ac00514eb53443f6c4869f985dceb118ee60a88a4826e9dc7fdbf61e77cbc93768fbfde0000000000000000000000000000000001646ecc89754ac57d7d6fe9b871692d65057f23d397a410bcb07ef3df0a3c3fad9eca515f0d0dcf0610edbdaf4cdb5d743da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000003a18dcef4939e154aa790b0ce8265f27cfff48d5fec149d91307759eaddf601c788da6ed8124764bad940f117751b0e000000000000000000000000000000001813f4650490f3839fdc9f96ef744ea93a9fd86f8a43d767259c2e0abafe308fec2bc6b9d62c1dd7b5ab1aebc19586e93c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b68058930000000000000000000000000000000006490f351e78a40c0cdb827aed3869db293c7d654b43d69ad1c9b3b536b1fbac67d50a835878171974669a30ae9ad1bd00000000000000000000000000000000041816bf846528e23eb129689a87c2325f1b8edf237c530eaf578a908fa0a2604baa19d6e0b4a5801280c27285896d5a51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000064be79c5d382c6dab72bbf28defddf14cc7cdbb23eced6bd93abed078175668d4dd66d0b3abc6384165d26bd86680f9000000000000000000000000000000000fa4c8be5d20d16bee7bd5bacc0b0086875a14a119b4888bc408850c0a099603fe3f79d334e45bdc9130132ea15a180f6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b000000000000000000000000000000000182f23242108b022ecc1d156a97f1a5fea2cc2e059dcc82273212f37c312ab77886c1adc370bdcc6ee05cfec957db970000000000000000000000000000000014ceefb3ca54bfde172e0455d34f1f462208df69328782b7961ade821ab91e7b3ed5426b4065fad10cc8fc88c90d8e87f4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb10000000000000000000000000000000009339b95b043903f2a3b5926a27e57cd0c45e7955946718e7dfebb01f18e9d7a2002c670769c4674773a835311f2e58e000000000000000000000000000000000ba94f6b625c507934f633d5420654056a939c68899c41e3f337f7b927fe82191d39905b349870ba0c41c8bfc97d64a9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000007604b5eb3218140b94732a601da577da3cfebe04dc7dcd94396c1a6704a0ef5a5bbd0c31c196f2876e1a4bb7490629700000000000000000000000000000000193098ff839d38c9bbda43944d7b0a3ec9d0d6732519d4cfbec506d29801780813b2faab46658c4383b2f26c477580af283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e500000000000000000000000000000000022ca1a16df42ba543a118212a75eca13707ee01eb3ce27d3659b1fedd99b9fae859f4eaa51e9be9107704276b578a0c00000000000000000000000000000000012d60cf33701caf11be6c9e3ebbddb9c7066dec3821a2e0f9e5b94e029dfea4063bebd4b2fe18c2442311c2bddc7c08d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b00000000000000000000000000000000011d4918642919c801fff0962062a387a4dffe693ec09cd3d0286a18e3a22c84fc09e8396ca82e6054d8535cd888179230000000000000000000000000000000016a1f0c7fec5647dcce688d3e4e526749bbf23c1fcd9e9168ace47399f9198c9b3a6b8aeca68febde1b7beeea0641aa2310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a990000000000000000000000000000000017203c37b21375a524bcc906843a0045229c5531ca23177dc88026e83723db21d9a8b5e52cc0be1d232818ed9abd496800000000000000000000000000000000097b4d7fdfa442dcdb64e405965439ebe70e4e71cc8e13e299fcc0b5dd88c67d6d0dfd254ab9b545e66295e2f3df14dd91b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab9850000000000000000000000000000000000fc4198a87e789015a1e44935321677e84356aa9e06592f9cdbd149d13ac312980f3048dcb9bd02779a3b10fd24ec98b0000000000000000000000000000000011425345ae1139647f93fc13eea0e920c491a49998430a339cd9d4260479a427515109753e70811be4cfb3b96db5c78b3e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8", + "Expected": "0000000000000000000000000000000011cd4c4507778871fd7b28aaf79274178df83f3e53c256dbe7a52df884d28df6a0d87d20238a15f489546a459195ace0000000000000000000000000000000000439a672492225fc09e46bb178a5d38956ae237d9f7d187d4cee14597facf2c06d7e70be5ce20e1f1389e4da6321e455", + "Name": "matter_g1_multiexp_36", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003790fe37a3aa78cdeafa76bdbebfebb22ab5f1e09e4e488418568fa307a5db18f9d93126b0d3cdd6a28abe3a4648f6e00000000000000000000000000000000043244b9c78fa56c611bf72bd6a17148abe76fd0efbd25085d7b46c90318ed591c5975f79653b98440f5f7c04cae4d7ea2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000008e8fcaf6d2056c6e144295d437f7f1422f6af7a1b62e0b8073141b2992b6ba865822aa2d9fe439aa1d896b2a6d231c4000000000000000000000000000000000bc693fcd2021972914747e48c600c444bf69ca8e1386655bb5d987608d648965c754668ae0a72c2439ba0ed98e5e581fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af0000000000000000000000000000000004e877b9032e168650ec3502ba65118aa0a8013b995a647210c1c36a6e6c702a93caef674d03d82da1f7c5d7ddfa0d0200000000000000000000000000000000063dd22dcd667c8288ca5b172e34b4eb783403105523c0467139b814e048fa21245879a5e9188a1a87d26fba52a9f601da609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000018cb2fffa3181bb665dedf1d60de6096e8c5ce43287cbd86c2df5a5d42d0129c73cd281c085fc562b7afdf52f0a680c80000000000000000000000000000000007f9884780460ea018351b4ccb5a120d44312056b96c5ba77cc38789627d20500d6b7e69dbf6ab49d6bee998a6aded67bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000bccad9f23b4c1231eb07df139548b66714a064dbec4ac6ac43ce18671144f2bf7ed99f16442b9f6600e1122c58f52e50000000000000000000000000000000013646b3c310a4b3f279e17f45fc8104d2c9d00f698b869479a5a0e1c2131e3f3a9dce86115ccd539bbd4346261c5a75f3222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb4000000000000000000000000000000000d02250115596126e858a63a7082a8c8f8ebe055653f5a60c855ddbbe3ed05792d08e5cc348094b8dfa4584037be597c000000000000000000000000000000000f68ec7da947cd0a57177fb91d12a820ef8574f4c524fe54b9420f9ba4944759c92d5919d6dc8030fc663c34519b64c37431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da1000000000000000000000000000000001960580ae965c37c2ec219dd0753749bd70ac2f0c4a3837418023c5142caf7b4dbf592554a6dd95872e018e912e3a20b000000000000000000000000000000001210b4093a07616543ac2034faa9c4a93b5f4cc3daffef2d8450b1a1770948de56c5bdbfdc9f1dc9af5e20778c1e8e6cd474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c73000000000000000000000000000000001955d93fc0f3ce0563ca4f4ffae0257297002001a3eb941cb9d3bf82b8d7f97657ad7168bd386636aaf45398745d5158000000000000000000000000000000000cc7a0babdf499322e060f2c83897fa7b6c3e7b4f56de3a18c823e0ffd87545a3dd68947df8cd8d3de5795ef7cb05391976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd1000000000000000000000000000000000d451eb31b21eff2c18b52b882e1eac68a524e3db43f233a9d08139667cd0173e3c716f29085c599a09f19019fcf447f0000000000000000000000000000000015852c483c8545fbf0932c99b1944ac58b37228d15284c7be5f5259bb8002abd57b26c244846652a862d46016221eab19db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000044e70861dec38d2b5ac7fec042c6b931d4e0a072073333f03ec4382fe40919b29378cac920836b1641e5e2db053c5c2000000000000000000000000000000000c422a91c81a99caa32666511c0ae4decc67cd94e85260b49760ac9e97894b0eb434d39c3884aa4614360b79681403f94ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce1997000000000000000000000000000000000af002ec82c5ac0dc87e1ac27f4cd052eab67bda318557c70fcc2edbdc071ac4a3fcae90f73ee514cdf8a543ef59050d00000000000000000000000000000000109f720464ff2eb2978d66370041206abd9ef0c6ce79d51f7d233c49b72da520612e59c39f3a775e288ba2220fac1563ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000f62e7d0aa954742a2018d42dd9cd76f041d9ac46ce659f4e192053a1d0c9b23fad78a06f61d2c90eb7b4d1bfe6d951a000000000000000000000000000000000ad5a5ce7b66928d8e6e3806a25425bbf2bc63f8ec87002a913c28ab702b83b6ba590b41a0691daa5b921a12375ef47b275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000008b836a23836624b39e3b3388027093125749a5edd5df50ee0cadf1d485c9dac9c2569a82484269fe7af02334369a29b0000000000000000000000000000000015232caa0c064d8d1bb7fdcd23c0eba21685fc4671e9f04cd1dbaa0382aa4e9d87aea42a99cca22205367d7b2261defaec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000875311ab0cde9a925383dc84e4ee8e1610b2f5af0e1f530aed4155cb8ef0b5050d907277f55d8dd542a89e4e0990bc30000000000000000000000000000000002c7a0d315bedb602f8ec558648ffa69831b9fdb6c14fdd44e636ff00777f2f8ae4aa23aca1b261460e6dfd87e7e501131e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f7000000000000000000000000000000000f9531c4998aafabc26e1ab588a97a78c236a854c3fc92424320a37a236d5181d34f8e5533aaaab2a6ea3385acc85f6300000000000000000000000000000000130350be432fd7d68940fd5f54649820ff5b3d015448d48d1f4db3a05ab0405a73ccfc8eea1966abce35833b5d03bf79f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e0000000000000000000000000000000010fefde43b2cbdab52ba664e12c7a6ff29f647942e16ba5a0d41701754ec63bf199ac8e710ae8dc6a033abbcaed3e05c0000000000000000000000000000000002189172e607876a6e1664fddb990009dd5c7a8412d60f7dcb235ed1825c756598bc67f8d5d383c2570a880492d4ee1967b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f", + "Expected": "000000000000000000000000000000000765877938c1a8170e2c2fda55241e3c64f7485bbca218f4a2026d00ef4708d014fe4194048da8e994cae1088694d1b4000000000000000000000000000000000b32833dc9a39e1e73578b21f75136be6c6aa2b4128b0e6ff4fe099f7b7a8ba8f2b769f68d32ab4d1f37793aca8ecfc9", + "Name": "matter_g1_multiexp_37", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ab94114b3ecf9535261a0726a9bc0e0907385d56206b61b7a42f643d46296c4022bedae90d761d3c002dceaa9167fed0000000000000000000000000000000008e67942ab2b9aaf2f6f865b7e957a25dd7ab8d8a0cba02fb1648e4c7f15ce00f4f5d09199a583f38425bc62d32ddde69f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e000000000000000000000000000000000e53abd9ff27231fbb09155f794e5d126c490314016e31c0b12bd1d2af97a705bc267f92e20b64c91d9af1bbf5e45b92000000000000000000000000000000000ce7d0cc6656108aa7005a56d15a497009c90871f01eb38f1bdc82edcbe4945a2f2b67c9b812aee42cc9a9bf9ee84bc08fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f30000000000000000000000000000000018c2688f573d4849b6d19e711ef4d14659c2c580eb938434a3b2afb8c20c522423db4c7fffa42eee9ee907a6492b77ad0000000000000000000000000000000016a7e69d5539263fd6b7eb893d476a00efb8cf09f21a54e9ff0d1c11e9f3651eac8a5db31b40598af6c943f864ff60ba128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000002380f3260c7289ab2005f7b1d7f572565ec938bd894bbb0047ced0b652fa2e74aef19c9fe6bc1fd469b2a4640245777000000000000000000000000000000000f32ca31e6bbb72a02f4b0da0e1627dab9cf1195fd7f48613c89b06c702e662478b24d8b3730321f803ae3a307fd498bd45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c2060000000000000000000000000000000001892b0d219ebabc3be00f45b00be55ae486eb79b1e41aa7dc8457aa0812e7276c21024c79646128fcb2b3c517aa41c3000000000000000000000000000000000793bed9530c814fa0d0ed1684614c1e6968dec931868a64372dc1b648b1f99ccce20fffec7d485a226033601b92a7f228f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c132200000000000000000000000000000000173f49cbbe6304aa41513d3742b89c6b07a91be50264350d71bc03fb9efe4faac4a19e2591795ff4a7e67fef7a85ed430000000000000000000000000000000019bb5dcc59ddf055f099a1c3949bb50972c4cfd035d4d829dde4ae94ff9669983e9b1a7edccfc2436648dc942862676fe6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000e3daf60e4929b4a237caeab203f86e6eed0ac630a8b955a03460a7e609398d076c660401f8d2bd9601e5bb5e315e1e400000000000000000000000000000000058b20160ca2232cb8b6cc63c5a8e11613afb9776e22d93f687e7ba005b099531f9693f65f153db01f20c8e9bdd7839ea83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000c89f1ebd19fb920b6748b15192829d58820ee4995cab9035ad6bfd8dedadbc6352058806a7d45fecefce40133261f360000000000000000000000000000000019151260431a35d124fe44116d86ea99e3f3aa14e2eb09be8193dbaa8f26fb0ae2451ca1c70610233d3f0af9d2e33fca4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd3700000000000000000000000000000000162b48d56f439ff56197fad444dc460cc6432722b9b86c7abbbfa383ae1546e160716d94e442183196816084da90bf77000000000000000000000000000000001278d0796c26110f66930ea9248078c222a0590a031df30c62fe6beeefa70deb0c8287b0d204a911c147cb6344632bf329d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e6000000000000000000000000000000000f4fdfa45aa3b5d1838b4dc8a2dc6250c069806ec3c551ac961da5b44eb58d962d843a1c17ebf89bd653e9e44d16300200000000000000000000000000000000052ad9ce994c837596339dcfb73ee25bf8326657633fb5861039f197249d425e35c238dcebb287b77f41bfe7f4db5c9b971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e400000000000000000000000000000000185c62a080df61ddc97ab56d2286ceec655172b6c863b509a1a92eeb0719060528ad3a3365ad5e7c0858167ac2c6d22100000000000000000000000000000000126b489e107dfdf4a4638069944d1b1297db734e5da1964086114f9f62081527d7d3f6032c2f29e75b4e1ccf5b3776d4659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e000000000000000000000000000000000f884244e098975b837a58ae0218e7e2606821c95f51d114a483ed5d31a59c9b9cb3b1db029a0286eb95686e0457afd8000000000000000000000000000000000caab7f67feea4752d3822979a770a28c879f5e8f916b72dc71a3b14820ce170fd229fdb61596d9e89b4be8f515c470e12ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c600000000000000000000000000000000155d3e886cce6f257513529e40c21b5657ef1ff1f4e71bc32b968db3e05652b1ac780da573fe1a1b94b7fef86e7c260f000000000000000000000000000000001184cf09544ec2826d0101d2b79095da6e5f77d453203c52ea17b6476360ccf166ef092eccf86dbe3a260f7fd25a2794666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000001523e919446b532593b8e70cff1206e8910444c01399c0dbad932b596cd0b9c2e40983ddb38eeff4fbd5e8d2b15bdc780000000000000000000000000000000004be8fdc3a3296e543701ce8c1184a983a2932f33913d6d733f5baa3a783382739b697fab4a3d6f9ac5b85ffbbc78a3540a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e401390000000000000000000000000000000018d20e7846239f472ef42c78454b6c335979ec563ecbbc3a93176a7be9dde603e6f21afbb68058035958ef7392dff3f20000000000000000000000000000000011ae4de8a7e1a958a1186bda4890d282773788f7d5fc5432393ac9deaba8bccb5db952547f6aae49b8a90c813c5a93a4662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000010641c99a359d16dc3e3f68547288c944d44c7c3e6177fe94428ddcf3c86937a3fe1f41a31eeab551e11cffac012e1fc000000000000000000000000000000000f407b01737dca388d0793521b667757d70e626ea0ba3b051f522639e752280b5657b1b97beae3105489161ae95a470059401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6", + "Expected": "0000000000000000000000000000000010084535f50807f287eabff2fdb83d34ca30454e4cd747cc3818a9dfd80c30fb3bf2f9f771d435b79a2d36105266f0c1000000000000000000000000000000001663a611323246a963856a92d52947e72dc123dfbeaeb9a3ede6147246814630e5304b50a6585894843790f5d4c818c3", + "Name": "matter_g1_multiexp_38", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005315310b8412d62f5d63fd996e8c6b14aaad5a6c83eb3505a28fa6bbe469f7a7cfcf10b49382aad4d6764859ef4910e0000000000000000000000000000000012fbfd9ee8bc712354fa3b73e57fcbb07231aeac980e99d5843fdabc081a159bfc6507911212adafc162dfc21a5afb739c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c0000000000000000000000000000000014e14689a5ef5b9ee89369c5c0de07fbb7980f37294a0e7570191b73f4406ec4bd9bf4ca2521f8d90157e9c3c7d4211900000000000000000000000000000000040d06da8127e64a71532afc8846bd7eb6fd5e845ca0f1d96effe0b12a2f8afb121d7fbe89f632262ba0e382e8204701ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000c5ece364affb6af365a4c7506389694b9a10f3ad6798c326852fe85a892014b6901d097aa8910256f47ca1d4667b5a20000000000000000000000000000000003f300682da34e22416f1ca2bc3430e3b153c95773c8c76660603a0ecddc20ba570545d9307a6b0910eb406aa14d196bdfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000ad0508c3b4fcc1cc608d002b66bc703cc16182a6e83794e4f3739238c3e02fbb6387ceb445791d54321ea52f779a35d0000000000000000000000000000000009a442ba572cdd9e658080fdf1753670c27e88fa894c307eaeded6ead17799365d1cefd1fd13f0dc321c0e881a4965d3f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c30000000000000000000000000000000018825670284d3dcaa90a678ff37f23e8ba36307f3c1146a8f6c782f7b43ce16f281dd346962904684c22c1980a772ffc000000000000000000000000000000000d65166eaa6b4ed79b5ddcd7b44f06ca1bf8b960211bcb17d5a26a8595a1ae1aecee9945a674b92384ad05f2f0f64fb6c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000098da5d9289f26b61486e3ea52b0145a47847ff2b9f1d2756e363e5ea0bab27a98fd01d633a46ab48aa1d2f1d2886f9100000000000000000000000000000000191412a43858276e4d7e69542f9e6ba4fa9bc0a8784df590aeb1e0d65ffb56cce0031916af640dc3e57662f5e5203436458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013a279c27bf2234542f4ac0e4c2676b41b3cdfa1b55d5c0eca1c686589c37ac63139a7f532910fefe275a08ce2d37fe50000000000000000000000000000000002f56719390112560fda45943509729fef3eed60215190ca1f90143a4d2ae6b41aeaff7edf027f27857d56bae1900ecc804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000016489ce6e2b8298e2fe0836556875156502d36aaac621e45514ef03db87631cfcd308285fdcf8ca7ae8bf65bf53a37b3000000000000000000000000000000000b6c8fe0db4492a309148c54465ca06c59c7b71e4418d8fc1874cc338df40fc1355a523387187402b04f5d01b5e5b82b6f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000065358f885a974a1f64ffd526e5ced18ae5ebab2ed6c9719c9f879adc940292ad124fe5b6c8278c82a33d1ab2a1916130000000000000000000000000000000010d019536f727f8ae098dd9ccb6344417042855fc6722443218d83127cd2b07a6816698dc1a48776d2cbbc937f83163dfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000004248c5eb514980da698bc5146fd3743f5b1a458dbb17edd38f65c294e48bbd55e0d9afb3b39df2e82085fbc03e5655c000000000000000000000000000000001830c1d21ff8cd1ad8467ae0a8d2a34367e7c44829f7530263ef3d7d5bd9eef76b756f475448c308f4c03453f54b43cc1aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000000dbb72f9afde915110f2483c09291595c369f0b4ce2c91779da9266c9f74764da4976a221c4997cb940302ce0e59ac080000000000000000000000000000000012de4b2ca14004be2c64ada45e9a0ba7989ea0e22d0407088a092cad87b4e26b33d5d8f96fe6831e085c6fd27901af61c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa289000000000000000000000000000000001387a1edcc34afa05541e15e2355d3cdefbfe22ab7481e1f194e461521894b97b2e18c9fbab1eb5d8e508a0bdae08b5a0000000000000000000000000000000016c4ed675f20aaf2c825de5bc4c11ce1e85a0b91b08577080108ab7b52bb674f78943a5f619f557b96a72206cc1bd447e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf000000000000000000000000000000000498481301a55b2d1dc95f8115534b1baade13c2cc4d5bdce1fe8cb1734004600a2359e5dd1c61c7338275e2f4fdf455000000000000000000000000000000000a3d2ee413b7e6c0e32e51dcb7d124be92990b7e4307b9b459da1db20f85f4a35964b7987933634fb62a07f797b00b27fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf000000000000000000000000000000000a885ed8c3ab46b60a7d2e198b6e8d069ca8f7e0692f2b8ce99df2f44979b6045fc17991bfc27867be79e2055cc8aeac000000000000000000000000000000001728864f0fda8476fda4df08fb6aa9e40a01dbf19a4d22c4fa0c319d8496d405f0a5f9c79ffbdd5a4c1b617326f3d774dc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e0000000000000000000000000000000016edd94f91c43f15818752660e4737071d44edcec5d5de426141966a9880bb894f3566e98a05232b9717bf85d66a57c6000000000000000000000000000000000a789ee6ecb80e2ab9c6e7a945ae4839c620f9a7bf430ce09b57a64479d5a10a1ec0a721678b5bece737f0dce97a3a56e4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a0000000000000000000000000000000008affb2247059dd4bd1498c8e229dcba313b156e2f420fa55331e7eac93d44af55a6c02bf2101d90955b95ff6fcb411d0000000000000000000000000000000004759596f12f17d7bad24723ccd6f86c646a39beb2aad35ae5a219ef57e1ce6eb310b2098130489421709bc20b4a53d703f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c", + "Expected": "000000000000000000000000000000000f841cf3d8897108b4a57a7802a3cf8a43ae31e711a6258991b6d5b3851e9e0d759fb90899e462828ff9cf996bbe9ec70000000000000000000000000000000016fa655a67f441e967d3137f6ea8f6cf636fc1a7bb662b1e22f87397e0c77f34e015e6bc124291647727102a12561dd8", + "Name": "matter_g1_multiexp_39", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c98e02c9f7784d0dbcb4a49c97a9365cd069817d57cea3042cb4198180b95d141c5ba4d383de188f06faf8f845f78110000000000000000000000000000000014be6f602cd67fc2d147925cd6c90457dd253db766c4b8f737cfca02ae15b47d5798c621091c4be71fec75e0b8b1c00feb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea00000000000000000000000000000000077bb03ccd915742dcf3c2640ec61f05bbd70987d2dbe9641e0e34ebed3600703e8f9c382e77f99b70c47f54496bb6840000000000000000000000000000000015ad452396c23e820d1e8a8a9cd7557062ca9c627cc7439d43c528e0170e2760e7761c9cd872141543834c89c75537d72b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a0000000000000000000000000000000008eeb6c2c00a9f95c5b238290b06a67c1cbe0e96da246537c29c0efa36b53230c3c5d91e3fa9d129743e5a9d87e81d0e000000000000000000000000000000000ede1011370a956f240419cdb8a0c8ae869c3d583d938ec32e29c5ece68ec8be0e69296ff0c97aacba59991d65a25563babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db00000000000000000000000000000000179776b08cf2da01a94bfe7be4b89b3308330cf797906f85889b63487115b386c68c8518158342747377fbda82a6d2240000000000000000000000000000000003e51d69bfdb73a2abb469b379e2b4825423d2a2cf2cec62e2313a76d260be1b0f2892bf82e5435e88205ecc9424275d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000000af478b121104742d0cd13473d1b7f647437d980999cbe7aa8d2246148d970136f6194df1785027ce944cf9ba00aa4f500000000000000000000000000000000170e9f798184188cc21b0950e0f3a570398a97405dc87a2e077af96799960a938f363d216474422d8f4762fe5893ece61a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000005817e3812f73d3d236e45664af8a4abd2d4a44f741c3c1866588c2bdd88b11741b1c272b68e20800abf3adad7125a400000000000000000000000000000000008dc859c2323f0d2dcab76bd8454209c86685a971d531a32b00985eb822d33691c2524fe25d14ca386047a4976b9e7159b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa453000000000000000000000000000000000ee06092a2ba4c33f5c9dc6062d50e3b133c7fde5c81056f74a2d869e8f92310f07629db9cc2b755f12016cb7894aac10000000000000000000000000000000011714a54e236d1e13f9b649a0aaa80cff9e093342c71a8dc9ff1e2d4e95b0f6b4219ed847ba6620d23feded7d95944183b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f6460000000000000000000000000000000017f6061908e62edbb8fc5498eec23a51c861815bc1b437b7383dabf303e6a45d52e73f8363addac61974043afacb02ef000000000000000000000000000000000f3fc04d17d801741f3583e072110b327a3488135659fab2e8b1d2aecf4694f6d168bdd60624713a7c2c3314f8309079f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b000000000000000000000000000000000db7d958b44ac5ff3bdb4991dbcdcbeab36bc6d21d9e0c8fbb1eb66601df227a6367ccc783a92c534a30b17be462b95d000000000000000000000000000000000424eb0d9da831c658ff048d3e9ee43a900bd1ac98bee97be073ea55be1dfd07d425e0906779f0e3459fc69d316599e56440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af000000000000000000000000000000000c9cf785be01b7f4bfb0140004873d0db4c8b1387dac0fec42c6ae1a72123ea5cdd2b8c98c69b78d617b16c48ebfff2b0000000000000000000000000000000015c4856f183d26d13196739d9b9c971af111b4905b669f3e46bbc8d8c4281cad1be05e9ac28de0a98031923fcd1f5aae43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f0000000000000000000000000000000001802675ef47f9660d5969dbfce973c8bb3e6b2a2717fac9a509fb3c7ddb272db86f283992eb3167145f2e496002fb1f0000000000000000000000000000000014a5b5d966ff72e036c51686dc6a9f39a487ab8adab6fa4a906f28acc67d64576fbb3a00cefb7720f42ffcd62fc8adefa40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e00000000000000000000000000000000076ed600ed860f16ec5dbae3f09471302bf85fde7702b3376b0d670f93560e77699bed969e7001570f44dc5e37aaa830000000000000000000000000000000000c993a8b08d2eb00bcee05e1c09e8a37834fac53643643402f60fbfe2cc7d795f5c68f3d6a32c8604c37211585830426e03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000eaeaec30bd8d8dd9ad4d38ff97e08706ffbe51388a93967cf16155b10d218e5b1213c29c8054cb778a0d3ad22d32eb200000000000000000000000000000000079e5f2bf405cf2dc79984ddb3f813a07225729d4cae8ddf7536e9240fbd0480f6b66321749a6a9286cb07758482e7f865cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c10000000000000000000000000000000009a0933829c2a3f2c3e93f58551e7572ecf6eaa7857aa899a7ff0eeb15ccd601559b9ff844a177568632bc0ddd6e80a5000000000000000000000000000000000b69f23cc1556385897bb7457a706cdd8539a3ed3e7fa504ffbd95abba1e824dc77911efd1ad0a9c37e1a41a76ad38d13ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000d3c4cfdc03ef5fa066be3c26744032e5a2045746cd303b6df542a6133c671f4d25dfbd889840fd624125b63839a1aaf000000000000000000000000000000000102fd619ac946e99c765010a4ac392ab907c37b31f628d6d58c0ade093ef394a7547de36ca0630820f4b5d857dce449e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a3000000000000000000000000000000001589cebd579c2cd31226245f1dd3e428a76c7d0012f8dfac4dd3428a716d05a0a79763f0061d3b5846dc29a8a006a37c000000000000000000000000000000000bdf3425e6cbe628f9223930cb74ace4358e12e5d367a3604edb05cf0f0cbde84346ef45597bd61592500583827524144c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d", + "Expected": "000000000000000000000000000000000555fd5a7818bbaa7e786f11eaf6f8620b9686b76c6293fd91690a4d181c0b470295313294589daaac429607b0020c9d0000000000000000000000000000000009c3a53113a657a5f7e30ec28056455f700cc7c3d40cbe4219dac00980675023bfb7462e634c8a131493f12725a27d5a", + "Name": "matter_g1_multiexp_40", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002d49464783e5ff91aa0dbf6827315dd308e778b3da5833cfca3b6431ae784193d915a566142ef347b6ca024b6f1695e00000000000000000000000000000000029051d39ea4369a837d4cc8cec1eb8f9e7f9c3a247dcf99dc75eeae43378b4b9c4175aaa5eb3f7abdb1afc15bc2076d5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000001363b8347ef6754f61520942fa8cdd07e6dc2b72cd40ae41a23622be239ee25834482533ea7edb9cfd5a4e21e4f33f020000000000000000000000000000000004495e8d41b145ca7f5268e66c03528c8d976cd650d815257906e46c1f9a0827e0e79f5a8c2906ec96718538e1da3b1d2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000007735147af3bbef7cf0c4a7c8f1dea302a5e4edf01d42c1e484f7fb1f4b8fa23b8a7a16fbece9270d8786016836bc979000000000000000000000000000000000053406bb3d2a4cf37924643a186a56844a4e77ea4c9e9e2c707b5f947ef956369f400e448930aef7135449f8cc51ae1530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000133b92eb9f9a3c6cba655d5f26f396dac467b6444657eb0a811dc6a58ba1898f24b336f4fe9b11c1e0795891b00b6c150000000000000000000000000000000018952f3a7f8aa78a8c5e5bd96ecd5d2b2f237916d8e2982c40cb7498423f12c6ddd3cf1afee75a3e2cd773bad7ac3bf6d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec04991300000000000000000000000000000000051453a8b81b0b0a1566540b3026e40676ea48e3c5aff89ec4fe3b36c61aea27ebe01fe8a811fd3ff73eae0a67027cfc00000000000000000000000000000000090b399b1e5af056b428a4c270eb204df4999e53807d34ca750f30b292cd38030491c3d1b0e08600f40a16f707b4903242a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d600000000000000000000000000000000142bcc3458437416506631c4dda54572b5d66093ff23f152957350a3aaa462000ab000cb8e9c9b23a17149b5d012adb0000000000000000000000000000000000734c0fe1df24449ef498fbb60558010093cbc8a14ae068aba2f70bd7718e30450411a81499a895e3d84079a9dbb19557a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad0000000000000000000000000000000010a7a3380a6d8b2bbf212da72eefb57d2fc2305ce222e8d908bb572600bef7ff55b1df6a9af717e1345967cc18e779ac000000000000000000000000000000000c5a3aa84b489c879eddd3c20df6d510edb5e9ac5c1a2e42b770571ceec315d560235b27468299e2e60af3ac1283be12bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b000000000000000000000000000000000035471ee35c187e24cf0d113c0ca1ab6322528153d0687b15953c39290ec295c0dd4197b72448f2a692537064ede8fb0000000000000000000000000000000002717020e3369b288314a42fd8ab6c6ddf7007480ebc4fa094ff7c4c4b750f477917caf071d2f1897a826fe870c2b7dabef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d000000000000000000000000000000000b501cef8ea57ae253de63d81998768e115d58b353ac1ed6e90d24f8c39a31bac1a5be1b535a1dfe05e72d80d1db8b0a000000000000000000000000000000000a3b62c001c4b725f7cc861fa042c31fde4e77b3b0610df63dcbb7e89d3fd746919c2bd8ee4d623838a05d42b6932383392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000011cda0c937d8fb2b21174ff3a5b88aa5e1c9a8ce6eaf26cac9fb3ee7f3ad20e74ebbe2d1bd9f4faa3acc43b6e6d0d70b00000000000000000000000000000000195257a442c8e39ee6b72cedaefab0034f48bb988a3355ad07b3e3e314800b2ce30267dad6ef3fd9dccd7d2318dbce0a20a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000001630ea3c7f910ee8574f29d652e86fe3125c306218a894df0b4688ba582ea7d597d7e62cc2e7c78dc2db289f587f10ce000000000000000000000000000000000d2ecfe74480518ad4f5ded701afa68040246a08df1b8dcfe6fdffe77e33c6bbd37192c6c41c6ab5af506ba58d8b3fe4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c4725880000000000000000000000000000000017a910c111d7a0f7e7a3d48b1cd358e2a1213edc077034b06d1e96beedef80473ec17d1c10bc2d33d4fd2a8c052d926900000000000000000000000000000000040167897293a68c980bc34b3f79802b95186200b40b4763fee9cdce8afc681ee916042d619cd51361e6e02688b4915ac8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab000000000000000000000000000000000dbca3203ee6c7fe8d6504ad2041aad2681b889996bbe28ff1282cd20da563dcd5c9fea5fd03072134019f579e4ef7af0000000000000000000000000000000001317a861403866494eef2bf59519f2d324586e93a0037d07312dd8df4ab844525afdf4b70f9e21a6e0230bcde35db4d8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef200000000000000000000000000000000069ea1da08dce1c1239d49411861d3e8ee7e6082d9bf8ff0aad1cbebdea6dbf82fb0d6332ae436327440b71ce6535ed500000000000000000000000000000000079904ab7b16de5812ea3eae39d790aad32db02c9cbf7b8a3a8d4222d3baf710ba1cc5bcdcf4fc9e2c4567992fa911edd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d00000000000000000000000000000000173ca2615b65e574bd77c8cf55bb116462a7ab9ad4a3879f0eefe03f1a6c0d30feed076e0fb21fc60ee9f270af180cda00000000000000000000000000000000179351092d68e7e0d428811cd4503a57bab9a4072f1bd27b5e8445ec0058eb46af58c4752601b53714b816a4bd386048b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000016c9d1fc1790a15985028a38e57c87cf010c87bdeb2a288a055b4b08497abd1d616fa8b28d6da8cc23047e9f8bbe6bec00000000000000000000000000000000089601933b759bb565d849c3837570feb39d442461d764a22f993a695fe1c55283b8c7db02694aa66032512d44dc88867b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f", + "Expected": "0000000000000000000000000000000018bda18912ce64106fd3d54ec2024a1d3e4a807d7bb8aaff7b515d75c9934d4729c14a4a72ca7654ca811a69f09d170b0000000000000000000000000000000011478fbc5c03470d9cfbf3decf9416e1dbea8a696636b94244c5c637e43f81eaed0210b1cbcdd291094e8581dba3548e", + "Name": "matter_g1_multiexp_41", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000446af4281a01e0a20b7428d06b63b89573912955971be4a5cddca514708419640f8a7f95b50ef8714a04e1fd81bec64000000000000000000000000000000000087b94d8493239047a5cef74dc20d7708d7e3365018df80624cc5511483c3a5d9b14ac3d4aa391da60980397e4fb1e96f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d74100000000000000000000000000000000033e5eb4bae80d55f512a48b44054d0efb8af1f9870fddd99df00f31dd437025381df3f4023ca217ba924a961864223e000000000000000000000000000000000f6d7a7371eddf7283890d59bea3c61fc3bff19eb7fa333ae713fb9a73c4971354474986ef5a9a81ca8c5b38bb67f58d5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000013d4e9518d398fc0add8233fe58c198d65966844fe286fe657891245fba8f37665e2bc40e4e70886667c9e2c0a1c245300000000000000000000000000000000089562c10b287d4d66b2b694d29fbac936f700de78525e9be59a83543593b42c5c577910e7ba1b67d840d88e7a3e53fdbbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce50000000000000000000000000000000005762727639503eb63854e5fd3de33bcdd80227e16de19cd7cfaa10b7863915e490087dbb980b6dae5114df7d56716d300000000000000000000000000000000104306b38970a94b5c8839ff282883b7c88c7ef45a7ed49a02b322a16521faf2b881e2dfe22da3f4472e2bea9fc40d7e607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000056e61b51113719c1829d4ae4361f79c543961de801b1a62ebbc3cff04b0722be241236d4e1b2dcf7c309ab9735334a700000000000000000000000000000000031ddb45e491ba2d719b1f72f54640c63e281dbf6ff84eba2eaa2b781d87e243e7bf84d7151f27556156970dc8a2407f41c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000029eff96206ff45ea9bd0be2b83cdb660d6bb2d236971517b962faa54535f01097327a00154bf35dbe47841eb36417020000000000000000000000000000000013734f1218c3c34d2780920806c5ad211128352d8a41c2a1035594f470ae347e372914827775094164a5db9d0b2a1ef7c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c000000000000000000000000000000000f3aa9f069b07cc935a974ad4eeb47e8b0083397928e8102651ee54f53005625c359d82fc8b5dbe1c76f650cdccc2ee2000000000000000000000000000000000e2bf6a8c4234d118676a29f12daf244ad9aa562faa970d2d63feb074946ca70da039e2de104f1524b1a8f3897f053f4c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e959000000000000000000000000000000000795a2b6b27209b48c00cc8d37864f14c6be66d6a41038122a28186d7bbcc4b02f531aaabd000fc93c685ceeb67bc3c500000000000000000000000000000000143926b42a6654e439fd01883f1ceb524cd8b5b1f2e3eed3e905f6e948736790cc1325d1b04e30247e4971b75939a766e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd200000000000000000000000000000000074096150c9e04c082a1aea20c785b3a7396568e43707c42c512575a97db8127c8c1e0548d640dff8821d7d235f268340000000000000000000000000000000012dde2f1d15c04292bb5da4c467cd674ddb43e401799257524cf3097d0dda1f3c9f2f0637cfee914a4c66d737f9e3278651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f269000000000000000000000000000000000109196dc59d6ec06fc4c774f665612c11bc3e826ca4ba528a15c6290f733f3aa1fc441bd896021471e1e85943fc9ec2000000000000000000000000000000000aa0d17d44bf354e48275ee3e4f06291e242402469be6f4cd4a62ad3871d878c1d27a8d06974c5c1138281802368edb01a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca000000000000000000000000000000000067458ca402c19488e2515037abf9323ab8288e0e11f7cdee18b3da50cfa377435cfde1f63dcdc451ce65a05641cae370000000000000000000000000000000010ed9c895629bdafae66ea176388be4e4ce45cb13ecbe0869ce57f0f48852b6b8c47bcc4a14fc5327f1df372ad9f5d4a7db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf7000000000000000000000000000000000f393c5fc8e5f1cbc7b59742e5b6236c9d1d262d0b736c1bc188ebf58f954bf2835cc70617062a01459c139f328c912d0000000000000000000000000000000015501635aa7565045ef59067e0ae91a5ec4871485ba411425987d540bcd7b5782aa7164dd631e4c7896b3949cb115f9a14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e600000000000000000000000000000000174b0620cb49d8b1a5798c3746046c2888c8e96664dc7bda5b4e90336517448eef534469a40086703d9a835d2a94930500000000000000000000000000000000033db9968fd6322e7bbb9de572e8c92b5e3717a9496803e3f6ef8dd796dc6487909ff318ad6d4d91297ae6f2daf07bcbed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000000449ee22d2c23ec02fdf1751bb59feafef9291d6d56f7120612948875afdea56453e081c5c5086205ea83f0b8cd541ca0000000000000000000000000000000006114d6d8ef1e4c6d79b23a2b91e5577323107d90523001cf7d6d18a0ecf3b414d4fe1a3eb831a6d907fce9d22030bcc30fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000000c2082409ec14f6121de6ebdc06656a28dfc5e439a0278593dc6aa845e8091d8caaef45ea1ad05aa12e3c1533275a663000000000000000000000000000000000a2ad9980247640d44d3b37c7b7b2c1b57592ac12cfe9aabca4f88ba90c8b3221a2b9f5e4ce19ffcdbbaf99ffc584219980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000108ea3fbf78237f0e90d4addb69f25eadb0f21c89d92774b4fdcbc97632f1622ab4ab408fee95e735281ea5da5c2c8130000000000000000000000000000000012338527c7932a737daab3f8de98b9f2aab59aa1b12e84d3674a8ddbc1f86a8a9e7eb0ba854e9564407aedd489b6016c619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1", + "Expected": "000000000000000000000000000000000aaf02063d6b5b395b75faae4039cf2eebb69053f1f161242b47854cf07786788930f3be2598520c178301ae0bd13ab80000000000000000000000000000000019574e1de9161a11e804d8077973c5ca70ff7925c847d870cd2bc985a8724d41331fec6c1cb341f7509a37371db9e4be", + "Name": "matter_g1_multiexp_42", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000048708595ff4f08cfa2b1c101ec7b3538a2e6044157bf39a63255b5540211105f680464be5b03256f9153a90a4e62d44000000000000000000000000000000000f2fad0353cd8fbcf0ba75a403209094d88d8c8d068eb0c7077b8263fd9f7bff8d6234d75ac4da232667b5c566604706119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc000000000000000000000000000000000866fb774b231d82a4508ff9b017ab836936299954b2b404affea65f315b62da34c76019192f5c9a447dba8cc1b9075d0000000000000000000000000000000004e050fb7a17bc738a55f1ceba48920c62648a27cf438b770a66166522fb0929069fa6f2b2b742ed689f554e9023ec14f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001710b342a52b0781d1ea18a9f07d54fb18e9c90e44815cc7509aca3a5c9ca3cca6bc32ff6ff726cfa353faca4f097e9f0000000000000000000000000000000017fd38b122a7ac39533af597b462224b86370f6e6814ca1ea71d961b9c7cf94b952fd75502031cde0851773b2c6b0108e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001fd243a3c69dd5e7ef19cfbd9b7cecd475e88d7be85dd3a8f48eb46d5dca39d05aa4b43c0c700b6632ebc0b4cb3baeb0000000000000000000000000000000008ebf24e9d2de0fd82c69e0ddd1625da0367c2e9f975118dd2ba5606d77de377be10515d9eb921be5136ed25fa6b27abf7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000021f919adb62791296db3a0b81f03b87c01d94ca312f55cd94364eaa654bc47684d7b0336a3afe813ef1aefc7dd0ced2000000000000000000000000000000000b40dd6bc2fbfa2ed277d88f77aded330c54c1c46a781ccd039b270ee9b799a70855ddb1201dae29a1b124dde1e6acaab1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef00000000000000000000000000000000054f284874c53bc914040e6751ddd444604d34a38314d8057fa0f77978150fce0add250a6bd8693ede79c9f6b2e025de00000000000000000000000000000000045f6579793d166198d73ccd03da2e907efdb31b54b0b0fe3e2f1e02edd7d9cc0c08af089330d53aedb60aa7cafb0e0ca41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000189aa0df86ba479009d4bfb8608c31d3d49f52f1bf758e5c05ee9e5a673bfa15e1c6c37a978c4c431ea035cb7948297500000000000000000000000000000000120c90261fe77d6f41a42a170b28df1c9e6e0cc4bae247303f399d3be7c6ce8319a43e7d551fe554783ec5ccaeba3bb363cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de0000000000000000000000000000000005e990869491ce375477b586b63641ec71adf226c631a14ebfae3514718ce546987c17c9ef41f9005c10eb04909a74ee00000000000000000000000000000000141b8edf812a2918dc9a2242301a7e7f6433a83298be9312cb48f0d3f0c819a4368ca961a0b6f09f9e077cca6111657e6a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f0000000000000000000000000000000010f3f93de5573e42ced8278a7a12b58086c04f8b862e11f256f26731560e606ab81d61a1090857eada5f8eb3afc363c400000000000000000000000000000000111915ab2711479677489dad7695cb02626a0525ae9ca51b5271d5fb6ff438d99730369654240b05b5d47fe00847c6327d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec0000000000000000000000000000000000ca6c2f2191cf86c596b439de0e0df79b441de41c7661d4b80723f14337a379bed9b97958d225700f06f8be5401399e10000000000000000000000000000000015904391fc3cb879147c2b5192641c4ddde11ca8129c3a03b82f5f824b2ae60b3a33c925112d2de94ba3eee10761da528bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000fefa6dadbcd8edf2861c6ff4f5eb501a76507b1fdc1b8cc992226a7e5ee17ea343cff89426c409bc36c2aa3a8f5793600000000000000000000000000000000166706cd1ae090a41ea211d1333d360a1e34dde717979295a0d6a870932f31158e43ca041d1978815aadbf761275953163b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000008f02061fbfe82eacd770520b46ab49bc29bf358468adcf904854e39b30ec4e363e80f18eeec8064947bd8612c37493a00000000000000000000000000000000138888a1fd168e9c94959cf026605691b4100a828c3a75ce95f3dbeba2a21d8a44dfaaad834dbafe28c12154f41f652e70de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f531500000000000000000000000000000000002a07974c00de6936c31202e2b0c76c30ad15b6c42393d5c5d2b1e0d5eaba8b5680d3837a8029283f572d43d2944e4b10000000000000000000000000000000013fa3f905a5618b7aa3ee5ed37055f0472fa361fbe07733f9c500657338c62bd4cc3b0b89e8223894f365a58100ee35416732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000000775861019fd75c201b3a23141c8e962948ce38fb0f15cf9d08d56ce0dc574300e0a6ed90a7c50b8c71a1a9c466d16200000000000000000000000000000000066ea30b3a1bd410e3c70b1173b91d3eb9fd0be55b2d583c4be627c3aa9cab1b2a5fe13ccb37d781965b1b121079916c4a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc24000000000000000000000000000000000ec3ae37e6e5b0c623534f5c02d998bad139394daa28aced4b9f781a5ca671a02f1638cddd3bfb5124f9c5c830cdd9e20000000000000000000000000000000002688ab0be331d6f8246a54749c54fc111d2f7414ddcb1f3b42724e5bf14cb8ff3546a3b9be6115d91f62af8c3eed35efa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b890000000000000000000000000000000005d5e2230d538b05b690e878c03d793fc70c391e853b0ae3609f81a7f24aa6d5a67f3138308328783888645d1d84a15c000000000000000000000000000000000d625eed47e245ee74aeb91fbd72981c4f2afd53deff7ab478f32e2a8635431d9ab9848f7912dfa4bdf8ee7201ff418bc4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515", + "Expected": "0000000000000000000000000000000007cff832bedad3caa9c91ac59292828e79811f91c51c4799e2752ac0c12e2c47a2a07ad0521097af7e4a939f3fd230b70000000000000000000000000000000015037ed0ec4981737fa94f276aa9a4325a2267b90f34844f14a92d677060a23e23e3ff0b3df77e7e67e85c23b28cd44e", + "Name": "matter_g1_multiexp_43", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006984b92b5b868004f39ebf04f41084d03704732363e65c823e5ad4a0a3fb4c983ff9375249bdcc2f46650921031bc1d0000000000000000000000000000000019b9d69589cd29a9909af5a303586aed5e33650331b9866a6d959b8580ca8312ad0e96c7214ad50db7502f50ecdcdafb0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e1500000000000000000000000000000000173433f7025400852ffdfec020a44b545b365158ba8f919f434fcd995c0d84509c77d8a05405c79953b8cb667047690e0000000000000000000000000000000017d73ee336ea56efa64038b31d5abb6650c4c6f7efe67add40d09faf93fdd9fae44732bb69dbfb0dc8267c4d01d8aaae6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a2700000000000000000000000000000000184ef5b6e309fee5030e2cd8c6c3ec49b1cfb09cc9cfb349ed47e17409d9c478e8e54f285a3b3a4025464162b172d33f0000000000000000000000000000000009b78ea5d2fd2113a4bbcbbe6d0108bcf27b60ff435b5b587e91155eb0ac6ea35c27f276b7e11fe5fb59508454fd8bd24e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000d0b8aca446806ab51b4a49049cede15587aae742ce7d80c2a05d255429c945d1337b4fa7ecb8f2c3b7c0b0299a41ad8000000000000000000000000000000000bce866df7061aa4319336ba1f876254a8e0faf3faf2f9ffdafd0ebd7d7d0c854c61b476583207818f484ccf7faf90fddc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed70000000000000000000000000000000007018908a64fe5795ad178b8bb1c8540ccc5c78ddccf4e6cbae72bfb84e794d23172998d29e568b186cacfd025962a010000000000000000000000000000000004751f7d225407a8d68b4a196e32cb4c0bc6a9ed9f2093e4242b268d6c5df978b8595d8940f59be860b66310bf8a5460f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba74000000000000000000000000000000000f61d66b0539c7ce56da9308d0ccfa9245158541b2d1b14c381ba53471ae9944ef3ec9f4eaf52c95d5d0bda92d6b9a47000000000000000000000000000000000612e57aaddc6eedd9b8a08b991bebe6f5cdf7805c2cd4de5853856f11eaee94c4c2e0799254f98348cef63236cbae3980eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd5107000000000000000000000000000000000b8a8cf0fa6ea9f3154eb35994cfe2f7af4252adb8f26d718163f2bbee3cf1bfca400f4d3582fd5fd407083e0bb48ccc000000000000000000000000000000000c3251d0d9e8520b3e7b43acdef58c75348786654103fc770c7ffef8593b169bba3eaa2686791f919fc70f40a171bda8067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b10000000000000000000000000000000008138d173e3e8f5e63f6aef89cf2437690dd0c848435f6032f943ef6cbca87bd2a622f9aca825b7caefb497450dee4c200000000000000000000000000000000183379ed3c9a6a6904e169c68d627bb828a05a93e38ea3b7886db2fe6d1015319d3887136180ab7dbddaa26b1fb3335f34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a00000000000000000000000000000000083f8fe152f7edcde2c81107eacee9c58ce22b5aeb10eac15e7df1657a813c98b182433655380c9e8ac18efff2188b5900000000000000000000000000000000100b06f6129bd9063d2841f4c244adf2dfead83e23f3b1586126623ec35674ecd6422efa0e86ed0502a83549551afebd8d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e1900000000000000000000000000000000072c644635936a91dcaee40e3b4794e634c315a39a9cb5cb99ef6784b332fdcfaafdc80e228cd19d0104d5796f584c350000000000000000000000000000000002318bea9077484e9c1937dfa63774b5ecf6fc63ff06e5cb653553d5111a981c09c907069ffe11b5704ea60a9987328329ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c698693000000000000000000000000000000000aa7249112c7897c9b1f95a7d8299790a25d155dc9ef7b1ad6dd7b186bcddfacd4c77ee95e634b5f283c8caebc00b9c30000000000000000000000000000000012e31211b2bc88c568e08157da9c3e3220dcd563cebe44653ff4d62f8c306ee9136832704272317342f634e66e8e66a240bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000003a5576b3663114b410276a8c537a93f790276754913df727ec6c0a684ab3c705ec04b8bac882bb9c5223702860885520000000000000000000000000000000002221eb21003c6512428cccf8a9c775df9b72ed8810dada5c92463e6cfa3d619f22a22e314b9b8882c9e2f609b73353a1574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d700000000000000000000000000000000111829f79d4ec1a80533f76f32503cae2842981e29ddf9a376d16ecd7037d3e0dd1f8cc84d512fbb39d58564c019a559000000000000000000000000000000001808e65ee7f31a1fc15d187eebd76c63a3158469099bd6acddb0cc96354072f636651137d060efd850fb599a6965044e6dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001274417dae37cd33b2a3e086f327df292b6f997e5c93e71add346d6e5f6ded135c8d6047978c10c5c38752006b7f76910000000000000000000000000000000014f867c58d3be7b09891f087f47c1bcdf82c16f899ba960d8a0db4a5eb66efde12dbee75e77816cf9afd4877d9d08f32d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f423082000000000000000000000000000000000d98fa6b2371f65f6f0b62133d1a294a7faa9949c7df16818657a9757fbd8381222cbea98a72a951e4b2b69b216f705b0000000000000000000000000000000016331e8f0661228b1e5f4df59a09de5133d16e06e1628afaf8b2a1160961ed9738400078bd79cb5bada5f99748ba220b19d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb0000000000000000000000000000000018f129d1799d9b46dcea6d239679eb64f144adbe1a9561044355cf66b4b1158513406ef4423468b5ae446c4128dc03d8000000000000000000000000000000001669ead3f97913fe5448bda1bb0be354fff223e51bda5eba9743526e964247211e9cccf75e6f99c6abb5b8912af94f5d3f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6", + "Expected": "0000000000000000000000000000000013426d2d18267fa615975295d2029d2e5730fc845556d501c8c6ff8442cf0f3c7facfc329f6703043bb2d45acc1639130000000000000000000000000000000012fea8316f8eb7cd655aaf9cff8e395592360eb6d62bd42f6e1d1e27b9b54bfb7be5b56791d5ba55a798f073f9b5634d", + "Name": "matter_g1_multiexp_44", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018eef275d602fb152cee956b04313bdbc8f0079da7bd8d6841fbff1f63a9626f17ea3f7a8332023fd9793ed2eff3990f000000000000000000000000000000000c41214c40c5c65e79f561b59d7ae103cf8c60f78c2b0a16d96a2e714eb30eeb0cf748044bdca077c4be5f4ed155ec50cdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c90000000000000000000000000000000001e5db25f5964e3a5030005152fbe9c00252e37dba6febdb7441046f734d4b86d60334d91960b09bd32d258b7ca2662b000000000000000000000000000000000949bfe49b0256a01da76f5c2270cd0b6ae70fdbeb55f932895d0e72d94eb6db236a8ea40d419ec6d9354c611b8010a918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd00000000000000000000000000000000158addae39a79638dbd1a7cc280e0a24d18a4914ce4e290f8f79c147c4e817d95a01bf6b074ef8e819a124cf4e3bf088000000000000000000000000000000000bd2f13538d08742b3bc7b1cca9cb9e059b4bcff76b49a189f506b4bde56d8a58fe0bec2f8425ba5d574dcbc5efe0e93650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000f8615d47e4327d862fa64ff4b9be14df2cad729b816ac7bdcddcb32500b6523af3303fe36c0e93b638992c671958d5c0000000000000000000000000000000011aa78c5d0073fb9b34235555bb2e3f27e55a1d576ad4e3f63974cfcb2646c6ebfd6e595d46613987c0c8e86846845dc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000000fe66db078653da2fcd1490a36db9371039f3630bfa4d574cb700b19697c7194e8e44453e16ae71db6c9001e18392a76000000000000000000000000000000000cc69605c26212c6a088b9a5c2cf6024e46f035e4c64da67383f294d6186bedc18922ac891f742165e3f09fb1720d476283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000a280b29948ccda96a2a05ceb9fca703dd63c65ebe18a0002cf1c63b8f64282cf9d3d4d73ba3a13426f253d09f83ebbe00000000000000000000000000000000146f604d1e90c4a14aa6599ff5c6389e426232a2dff39334f3390006f021f83500300b7b0f1585ad591acb1e0baadcd7145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f4720000000000000000000000000000000000be58255d1f227af95dc9a818204d687064d62166c16f1de052aca69a37ae98c2a73a9a9cc6cf187128e5b86969e2810000000000000000000000000000000003f1155d7e91220bf0b80943a16a9f41e4def1d5f8ce44d95dc2f9099019a1d5e770158338ec248eeda7c5af412890cdd9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af00000000000000000000000000000000077c3ebd0169da81bf07ab1bfb8770732e4182a30504cbdc8fb1abc49f31d698c17f68de1a6d8bada62e98e09bcb22130000000000000000000000000000000000d677a33c1590cb55c9c78afa455fe2b349c465e90537a73906343aef577afbfacc8e157ea6f834ff959f3dea5941bcf262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e000000000000000000000000000000000aa0a3898520c5bc19d7f3a8e0710585dd08419b39d9bdcfe12f7baa6b4cecb50bc0d6e877ccc2518e4d0254934669ec000000000000000000000000000000001376af22bb714adbd16d8d41ab503066fbe78f799aa8c1d8958eda9e4c8c6fbe119e592f655e0c3f93455e8acd8a2bc14f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a80000000000000000000000000000000011b1300312d0ad0352ea153746f051816693008f2d0b980974bc354996ebb664e910350e28770192f59c053f3f2bf00500000000000000000000000000000000125d87c441a1dd88f489514b1d550387aaba857d5a6bef20acfdc0afdbba3e98c2e0aee0528cb78970395a9da853ffba25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d0000000000000000000000000000000006a05ac512adc0dccb74c7b4c2187763a6ba8db9e290cb0efd1325b7a463e0e14a3e7463b5cedd732527dbd131246c6a0000000000000000000000000000000001c1b41b6d5c823c05a5d6db55d7068409f5fec25986db6e2689dc6ec3e0d85749db6deb737445c5feacd69925c5dfc44ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a616300000000000000000000000000000000143d22823412da99f7b87a794662bded7b7ebad9742e4d6fffd471b1bdc748c6f1b5bb395cd0a79c7291b9e8081825ba000000000000000000000000000000000f2b98d54e293befed0a97667791ae35494084229b2a25494fbd7295a04f03173a52efb8ff9033c4615ad1185d4e9032eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a00000000000000000000000000000000172919c33fd97de83b30740356c2bb2a9c97c3616d9f80a8d8266e07a1de21ad974ea796d3cf56660fc4e0df263a27c80000000000000000000000000000000019afdfd10bb736e8a6596db59f4f9a8244e585fa81ae315a768c8d91716de32d42fb75a57da238dc597885f083049a769d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b046000000000000000000000000000000000a944d2667a10dc5892760cd3e13289785f0a5a461068d70960e6546a0543474f92d68ecfa96efd19619d976af2ee491000000000000000000000000000000000a88a16dba3fa6cb5ef21015b18a14956ec9ec29650929fbd0308fa59ac4aa389aa2e306a3a68fc04e062367a72b3f861e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f96220000000000000000000000000000000014118a990f2649838954ab911e795c453ecd0d700077a5fffd1a4f303087074d595caf1b20399578ff1e23a2cada7e5200000000000000000000000000000000145bf8164b82ca5f8f93d89ca65a894c6d15e38da2cda296a94aa1a1efddc4d2663b8f09efc3b2d78510c4dceef8558fe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000012db33b91d99f44cdc785470e67a781b4a72ae2dcfe4555efe5b7527b9a76de8e492c4dc7793ad948cb46070eb1cc5be000000000000000000000000000000000ecf06e454ea574dbb9ba6209577425a58267d01f29f8706d06018a9caac994d2dbc9c6ca9fe3ec01aed9aa2ab886c60dcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c", + "Expected": "00000000000000000000000000000000003747597e0f9bc39306319018fd08bc936c4d37cc6f84ef296df5a36cebf0aa46ed35ed215ba89a247174fd72fc7c1c00000000000000000000000000000000150f973b61f26aca79a3f7d1705999308a96136b08673322b4729f16b471e38f27e66996e2921cfad0cf889878c2ce27", + "Name": "matter_g1_multiexp_45", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000046e955a4631d1a490f92cd40ee0a31c096210ead2b307a7aac60e84efc04898da5d4d9767f1303ad5652a0e377f0738000000000000000000000000000000000afd054be493fb26c7826c9c1f62365ebb28ed853bd3a45d266f4c690a24e179b2eea5261adb0bc50dd184c165231d2eaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b6000000000000000000000000000000000ec3c20a24a5f9fa7c5754007407d1aaddaeccf3f7956914ed3b06dbcff7f15c6d487a3b71fa9aeb61352698a93ed14f00000000000000000000000000000000086f3cdb1e21cf60a7a57e7ea7e00b4698a837916eb1f6ac1c6cf97ef2abd48292ecfa471ba7d9b8688b6f0dcfb6af62c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c000000000000000000000000000000000769b870411b65a1a86dfdbbd7dbb65feb708f9f90ee73153e42f7141cc660c50f41835ee44f58c7ffa136b944e84dcf0000000000000000000000000000000005f0480b4a35dacd304d8feca77f8580f66396a6434af09b98d57fd4f9f781012f3900407a49f4e0aca8d3ebddd2a7bea1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000000208f1b01599c969333ddf9accadb24f1c8239f82f5beca72d0d6d823b59a3b8c450e25a2da32b5a8cf8c0f47137e04000000000000000000000000000000000054051408658f025572a45c731e81f3fb88d741a632f1e2acadc48a1f257a69481c9c11e655c226d8e0623d34fc9fc158810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea0000000000000000000000000000000001738659b582e3667cee963fbea8cf695daa6b811dd808e724ae77db2060f248accf645db52f9838802c5322d993488e000000000000000000000000000000000a36fe571929153dd774fdcbaff2b924cd3f0ab4aced47d22a2662ac6f415b89372406c4ea5a0a466d4a4c5cfb02ad7d93e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a000000000000000000000000000000000c70dbc5f707fb949a2e0cd57e0ba6a5d28a2d85affcb55bdc9fd24a3fe395bd78b7431175a629475c0932b769b55d6e000000000000000000000000000000000a49fcd19bde4473bb98384bd63e96508b539fb80e1e0cd9fc9aedaacba0c36d705ad16a47f345c083401c6640675823eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000011236c10b9622f4e3d468d91ba9c6c072be74aea66f5bd77411193bf2358a03fd47d029dc7b50343ef72fe9bc08c7ea3000000000000000000000000000000000b923cf7f612e800c2c52b51203e12a72d6f106c0d047d1317711954cb33d44678f509da27f03dcfa1d4482a9cc2eceeabe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000883a868a58809bbe3ec9df32f8b963030d71a3ae97250ee9aa8446a8b1a4428324f22fddbe77b338ca58de26b1ad73f000000000000000000000000000000000a49fcca1f052e82fef8913b64268a33ef1d2ee213ce96e60a3a1842aa304c63cce711bba8f523302d9252e3def20e3fc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000d1ed017ef4702bcd3bfbbcff36000af6a1d26ab363e68ea5629027e0b90352bf1d8e03c13a7955da6c15507cc1c9f47000000000000000000000000000000000e09830e54fe9eddd416479a1740f6f1b7693f2d153d322f27779b16bb6451d7657df85a55da75a4aee0a2e33b3a46e637d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b0000000000000000000000000000000001f902e2947de38842c207b9029743da51ad0dffb61615b22c73d88739d80c926c07f97507ca3bb830c66661b397dc1f000000000000000000000000000000000d8a1d29f87b3335287142baf613fceebe9d4765d29e46bbc9e459af5450256295538b49081d849f3253f07357451b6e4904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000001846aeb64ead3a9b6da3b6f5de234fdc98442bbdc402af2d016c9dd25de8f9ca09269a3f01a812187ab7427b2bf31603000000000000000000000000000000001775e3fa3bd35f96faaaf9c3ce1d2391f89340f8d533e41a1d637fde7a2cd7ad997e50a6e9437468a1d5940e4004bc9068911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb490000000000000000000000000000000008aff7ad8d3e83ecbf5c3fa2cc9a5328531b1dd6e30b2aa618aa087281202de8f4d356586d64082fb039db4c9ce6c3e40000000000000000000000000000000014196e8ec67e5f0093da2b1233331bf1e90a8fe1db52b2629c0d25e3c181d595c03bbab3b399c87236d2353f1ea6bfe9481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef00000000000000000000000000000000018ad28e8d8c1d9dfd8f8cf4e60214446a988285005d92e38d46ba32f619e982cf96ab10b605b1e378d7b46b54282ff300000000000000000000000000000000029807f431a2101ac341241af021ee35c47e0ffa1975c982f75c10ebf3ab9081d294578288a5c308abb074b3e3c756c672780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b0000000000000000000000000000000008cf05c3d3bbcc63ee761f7cab1494299a3e2274ebaebedcbae5b35ff33bca129d79f73ea77152f19cc67fc66ff774040000000000000000000000000000000009ab576dbf0e8cead9450eea0a506c83f12d09fd2267715a76eb46602756859146e96920174dde3a361636986a3d38e084ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c6567000000000000000000000000000000000006f62bad30339a1a912280ba5d982bdf0d3c04ad9051555eabc32eef501e80d996f183a990ebd17301ede13db85f6b000000000000000000000000000000000b0c4bb1a10f8a281b83384ee05be2d65d6dfcec36253b9101cec7f1193f8fe3d29333034de96dc62d18a97153ce1d153256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd0000000000000000000000000000000010afb2bdbea9f6eb0c75ddb0a4404116498920557a5d416c6d855978e47aa90da70f29519ab244079762fbf965edcd070000000000000000000000000000000000b8b62a1e52eb3805056576810721cfcdb5b0d94759a11862cd7b0a88e3ddadc0efaeccfb89662860e187f8af2039f8575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b", + "Expected": "000000000000000000000000000000001422eeff2bf06ecd67e0b310e7201773577a899fab8ee7c5b6ef5ce1021c9371e106c49a6b607cb3d58852f0e671000e0000000000000000000000000000000017ff4ceafb7c286f2036e6bf47a15e6695beacc7b988dc35f8521459203f69975d297160dc67fb88c4ed2fd7b61ccc0f", + "Name": "matter_g1_multiexp_46", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008fae47827bf8786df7e9f8cb38a8e01354ed4417a05332e45a94f93a5ec61f11d517f5554d5444001ef2991f2e7eed60000000000000000000000000000000005cd17cc813442f45e7c2fc542a6359b16db4de7749677b1575f12ed694514b3569b722ab257f7678a230ca3ccb6e0ed1129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b070000000000000000000000000000000005aaeaf87735d9e9895e8703177faf8b11bea34aaa045852c57e9b86f6283332ab633f3e6947b84784733f6f73b289580000000000000000000000000000000004957220d5264c0ff61dbeeb0d0d51278386227a9386756a042df89fff5ff9a4d3e3e52293cc94ed729d00ed3e70b1a32dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c900000000000000000000000000000000049bec519df011ae5f19c85afb3301f41f71119ea6cd9eaffa9a00f9cb901681eec5f3f694ef9b4fe768a25a55afec560000000000000000000000000000000011414953ff3fec28aabaf3d62236d6a972da12c42102911a3ee8e88e188970a11487df719a739201b31fcda4e52d7c515a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b010000000000000000000000000000000003e5345484f59b269fa25b659e9a43573d4191c3c02f5f94534bfcd63d9abd57b2f3ab92f9fc746a852b185a6ae2c778000000000000000000000000000000000b7d7648096606b0c3fb93627e484eca017b95b27a8098e5dd332bb45171793570c69fdc16caf5b16e65f68c817de3bb579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000c6afd550c55cc41cea88e670443d97c6419a295918dfde1d5490718f18ccaf8fa0cb68c42fa2cf583284cc70bfb0a11000000000000000000000000000000000f88ec67e9ba0e169ebf93fffed1fb14dd1aa3e1a2fa614a140c1a2147fcf051457cba68043efdb1b851bace84078fd64be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d1600000000000000000000000000000000143a1884ecb4121e2c1c0cf2998b690d7f01aa3deec1a2ae5542647a3721f7be47c21ca071f92d74d9c3d9027b56d9c300000000000000000000000000000000113b01f060d10d95776b35c2b294216f768a323aececb308d3de24299dc12e55fac82c3134519456660a3465abeeb5950983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa000000000000000000000000000000000019a5620f3241d03d63ccaffdfabf7e99e784399929cfc3218d6b828d7ce137c9c6cf3ae830630fcef3cfdff705490e000000000000000000000000000000000114347768e5c8109c1bd47623eb51764d4b3f63f333677bfc28b143fcc1142f4d9094b2355408cd8c412a37a4579e0706615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c00000000000000000000000000000000043c0f4b09396d4b14deb7c5027ef6cd2d426fa4f93d4ba9c3647031d557a759e3426c113fa3949cadb8b98a64bd69880000000000000000000000000000000017efb6ab8b2eaa0768bb740cc8a4e5ecbad81087cec2a307e5f53b5f431d19e3467dee84df6c6453ad4566ffa2380c9ad77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba18122727363900000000000000000000000000000000140f0efabdc88a109da948494a9fca5ff790ccd6c629a088cac62e043e00e38c4281e49173ea0e423152c5b944d80ac10000000000000000000000000000000006d3d01cd44e56a4cd62d88a22c701b42c116082e92abb629e64040f57a240d71718927aedbd8ddef910198e1bb09c6841f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000000f20ace44f4b981adbb3035e450a656ce3d8464fbe4c45b9f7035c00aef11e389cccef660dddc025786d4f9216ef60c1000000000000000000000000000000000d5fb0a9e9ab03893a9ec61675af29e88bb30f3b61e05d7c5a3d823159bf8e641ad894ebedba4bd681df789e0c3d2547c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a16000000000000000000000000000000000dd38f99872751b4571253940ca588424190bae80434a3126a7ab5ad1383c55ad769e09179d148d151506e5cf5007b3f00000000000000000000000000000000032b2b9a8b13acb6589fea9e8b8d2535285bb32ab0e519cf8c63ea3e25d58cea7f9fb27481adcb9475abadd6f1384f4f8db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c88000000000000000000000000000000000c696064b7c9653cce986e119686b2e01216faf8098d494bdf6d302c4d176b24b05bfbd70b9ea3ecc16312f899f887180000000000000000000000000000000001b5b8d333dbf1d84feaab7737d3af13d3995d3ea976d9ea1cf1d005090a809fa6c210a6363495c2b22902442fc5080b70751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc000000000000000000000000000000000082bc6c7ff7924b88b4a6cda58295d050bbe8087670bc6036b5bad53247b803306ea596ee0689d805e7b4de65a634eeb0000000000000000000000000000000010a79825c716dce1572e6e8886f1c698d730327f195871db7a9b6690e9ba1dc38e8d92b34ee32b33705edc021f42349184bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000cbd4ac75eb0928f366d3b99e05799bf3d9dbf187e557f211af5ae514101961ba750e81ede07cb5a14c49884a9b55b980000000000000000000000000000000004fdb80f44f89e6cb44b950735703653152466f30a410109a24b555c4e6907b2c1d4f54c9c0d2b7954002a74f1b65e23d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000019a41f73feae98fd65e365912f5bc6c86142380b2633feaba440a6c635ce2bcf7f871f1f033f93f9f8668360da3898090000000000000000000000000000000005bd1afda6a52adb550fd9bb59826bcf492cdaae8e9600e517d77832a8f3ae8777756421fe7640aae0bf07518ff695a66940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000ce2a96c1ac3e2cd01ee4a20258436b62dfc2efb96a7148cf887c25d635aded48d18d38da7347abeaf72d73d613fafcd000000000000000000000000000000001773ef3bc5044059bdb5100430d4936f328cf876a48bd30784c8d3767a119bdbd5f1f97d78d52afadc42ebc85f912f0f7b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e", + "Expected": "0000000000000000000000000000000013fcc5da42975bad80f3447a1ba05d9c6a51383921164ea3e93e983e24055f6398fe773b0e7a50d98568d49de36e295c00000000000000000000000000000000188455bd9ca4a0d3174cc8f0794d8c35356f697e62265d9e3d8e72bb2d1450caf5bf79dc5ba78a363a23d2ad58891165", + "Name": "matter_g1_multiexp_47", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000466047055d438bbdade1bbb00a7bca3ec0ce30b042e56afb9a25de1407d5937038e01e3c07595f46bd00cc8202d2200000000000000000000000000000000016bcc696716c21293b68d4f29d9cf675d447b726d579628417cc457043186d54f27c28b47d2e430041f9417ba323109dfacfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe2433513000000000000000000000000000000000e8eb8fa4c0c2c86d0e571cd4708361e606c9fe789b60e099278d35d169424721bc789a6048774d739a5ceff56adc668000000000000000000000000000000000ddb7d2e6094f1940dc0f41509bd430163b220aeed1b8c0a2b90e37f791410a35d682b75223b32febc95500c7006f6626546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000001080ce47aa1c38db9c71d1834c0b5d59676b0d938ba55a62daaf50911d23e286b3b813c7261bfc19e95f3bc8ea3b91fc000000000000000000000000000000000bebf539c3c03dd260d579aa853c28ae582b9c904ba2c56bb1239aebbfae10c05d9e33c8e1c2bf90553025d3279572fba9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc000000000000000000000000000000000ba078b44f92e90fca4981c66e89c5490b34f92e4026d826c2076a995269e4d4fcab419a508b530793c465531a631ead0000000000000000000000000000000007c19bb972c27c00b5b1a8731ed7dc9af8270187cd26b1b9d65cbc96688fe2f0ae86ffe753a50b4500a46c01a75a93032c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000009b70de2dbfe9af8ae771ad5bf0ff962c9f906a3637f992b08946c864b3d1dc996a2ff918ecb3c9648ef9188b15b624c00000000000000000000000000000000186a9f4c06ce9d5a969b959e4b17d4428393d02d0e7259fcbfab8898481bc97582ccd0e1d87d1735e28dde10a99b683e9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000005b7dcbe86bb6e6b328325141c1da77f8af531bf1463bf3c8c94812784314fb13e457fa461c1c51aef0721c5d6ceb5e9000000000000000000000000000000000d9d1ac39a5ecd61670c1b0d061d93a198eca1d294d2e64c3f9e0a872e7c93212ce7835ae0a7fc2a42ab5c02192d70715e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee2000000000000000000000000000000000741769993f2dcf5869b8153bbbff2e6e5d429fd2d862bdd590fc50a8f186bfb105f5d57f736b07d919bf0dff0cf4094000000000000000000000000000000001917c91f954f68c6406d6dc716dacf729a8c4a0de73e04cf0ce554eac40d750fd25b289127023af299c6f63372c01b7d4a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a2000000000000000000000000000000000bb29ce10d6e571e62611364143e08a60eee5ccb13dcb77f17fde5829ae5fc025b309c98f892aec1fdedb7d1920e658c000000000000000000000000000000000ab6fe2dd5eb1b90f15a3632749c351ec871038f0550dc54cf1bf2575f80ecb8a3c0d3c1a333bdd803e22fb6bd3e64bc5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000004fe17772d4205d7b1d0cce0db3404119707893e20f6b27138918d2cb0e4de49cd5df1258103c1fac903c1a443cb62530000000000000000000000000000000014d8246911dc40ecea823f02c0e17e690a5f66848223218dd1735cadca1a0ae89d7afbdc727158257d2cb248323c55316e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000784a1b282846404f71227064ff1a97766781900136d4b7ac73bab19cf8e03b449ddd35360fdb6dcdac80e335ac5cd1600000000000000000000000000000000074fc137d93decad1cbd4b753fe9ef3b8b3445c12e358450ff494a1fbd6e192ad7a4812358d85f6e3cefedea3aadaac6428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f000000000000000000000000000000001661e6d386aa6516f08decbbac9c1c3411ae9cae62b05037dd626a2e2273eece64615c54a4d73e09814d497067f9e6e30000000000000000000000000000000007543030f8995237f65cec9b69b0356a29133d8be27b5f79aea580955042242c2bc1c6a01539b6b55ec9af96db60b394ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d9700000000000000000000000000000000148597902b3ffe4ba8a5f9012e699a3cf189f58275557d98d132b72d3c34e5faa0953ec8cb10b0228a23803b70836e200000000000000000000000000000000008741bbe372a1e5a697e7059a9e80de8a012b0cc7b12c14bea098c16cfea154204d4e27753f1a8fae0e618223da14fdd88a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000000cfbbd4632e8998ba59721686310ec115b98ef470c3c4bbe427495d6d95d06ec6180e64b509c4c06e32862e17939a2cf00000000000000000000000000000000060042078794f4539a9b3e3127632c3c8b46322a669605d1774e995c5d82287d3d9be51690b4b5df6de8d55b20941dc630eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000007e67da2f320e1ef0d3afbd50634aff753a2e2104ddc03244a0c79eeb117ed1beb7316f7c5e116bbde47c53d47e725b3000000000000000000000000000000000b5399ef864331db729724870b431d8dcd8d3279cd00a59de2fdc15bbdff2035794025edafa21fce97836e93b41aae067e8dcbf708682225fe3f71b7a687da23de5ed188e40585be05533580121325770000000000000000000000000000000014bd7f0effe81cb626f92422ae7900bafa7f4c2d51d4ee6926eff68b60c7f41e667a57bb0506f7c36d3549cf154f6cf300000000000000000000000000000000050aecd688a63075feacfd29d1ab6430176dbc5ba6d406636a6650427a9e0b0d51df51d8dca27665b0b6c60e08d5b087532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000009252c5f7f7f3b36c5dd32991641c9f8244579960fd2d07a8641b82c5cb1768a36f4e5ad623319ef3f7d0c670fee58430000000000000000000000000000000018e432d33e506ce42bf3d873e36ed6ede0c9de44203cdd453cf91c42fc2ddaadaadd2e3870c5f5c171cfe76862ce44dc3ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5", + "Expected": "0000000000000000000000000000000008b38b298fe2dfaed042b35ce73c76ece7537fe5181ce647de756349a8dc48d3296e243fc7613abb10e254e2b0197d7a0000000000000000000000000000000018d59a69b976b1bacdffbea68d523da3fd0d2910db0a509760bce56bcba36a55fbfe11cdc14cad50e6951ffdabf97a64", + "Name": "matter_g1_multiexp_48", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005d929298c9361736ef5f7c83b6a851c344d72b7bb92a8201d629bf9bc1e66e4db6dc9df64ffb41a11eeeba10be52ec40000000000000000000000000000000007962e1b1b823b770b44eab51b3b84fd7e0e57a2a3f7eb1ad9c3ab02677376cee08b0a2977552a0f9399584b576f17f148da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c0000000000000000000000000000000013283d9b3cb5ca4c3a39517adf466d2b7fc90f4895a24effca7ebaee4df8735c69993c7cf2483c3480cd2df4be04366f000000000000000000000000000000000fc94dee82225161feb78f2a7c951c41f43ff3c1109a824b56c01854688feb86e240c9fa48534809354e74ca8360cda4def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000000fb727cd02c5f69af676f9cfa68cc4363cbfe5343e304ff5180ed1f57e6928fb808539276feeb1e492ae2455f65de0b00000000000000000000000000000000082d09bb2e1f1585933e1b9076711803e71c2236ff78e83f5dae6ad492c1d723120ef64eb25c8e91486d102c2297c9e5b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000000882f34897651c59970934848ba13e815710b4952dc0ee1abd0e04ed82ab399ccfb16ec966d010eab51e5fe514af91ae0000000000000000000000000000000017a32754dbdae7a2541eddba29cb8ca85a0c6d189f9bbbfa24d477e9f1ec2ab8f7dd2a5aa7a596d3a2af916ecfbdb2c2a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000db9ec112ddb4a9c6e371440d0c79bf043c5a3c6c6bd613dc031ce9b81b49a32b006a165ef29a8e05f019b76b3cf520c0000000000000000000000000000000002485dbc3c3e2aeafcf18dfecd842ec48b2e79d3bf7936917df759a9ca2e25fc3f137eb88a701f5fee1ccbb06d5cb08c0b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000100d1fb78f53423c8cd60de5d39a004ee1c99b2fdf6847a62c73c33bb3d317ec06afd6424359481f8ff2d0730cfc9095000000000000000000000000000000000211cda7659f1e848c931ba1f65ca9c6021067ca01cdc8e87f5c742006f6dae39645553b69a4ae00ab6eca16afd0bda7571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf00000000000000000000000000000000004b07c2cb575e2499e333140e48446fdaa00368a74b87e607e285781b42eec39d1578d2e34701ed28488f160e9e50680000000000000000000000000000000001c2d66d28031aa91f6aacfdd80d222b4a0bc699a9b58b7f5d68bb9ed0a297ffbec3a6ba968f225732879f2f9907ca3954c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b000000000000000000000000000000000bfa7f8b7783780a2b0f5b9f1b10da77cb5904618b8c8a1d062fc94aedb0fce090d8c4e65515c0d05a471f2261d0063c000000000000000000000000000000000f45747e4b0bffddaa13c7e03b6930ec474735b6a0e779d3722330828ca26a07bb731a5d4884ed3eecc710356a00a897000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b000000000000000000000000000000001245cf167d097de0753d29ce6018b7777b1befe43b5709e8217b9f380d958e3e9298347673dce432e57338b313e84950000000000000000000000000000000000d697bf8ec405e252588e3ef6d979bfa60ba174da03266c3a2efdac176c1ec1341d737b16d53bda6ddf8be6e1f433ff6909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000000152d7f1e704619bbac7e594be6e105120b76d9bbc711ea40beb1063c2996fad70bc8f77a915411f3601e75af2f2059b000000000000000000000000000000001622a6467c13c534ff1fabaee8b29452d689e7f9e118e050cb91328b8078ef97fc82321b80d28d0c02f2b0a7b66f04a36d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000001c118fddc8df59e2d4ef9865d69cc044fcf870f296b009a2a471b1f74692f99e392b455b8b03d079b1f39b09e5fb720000000000000000000000000000000000032c05dc9eef5b55857956919f7a51b5f5225a45ca12d80208231304e66c77b24707a934cb9814108b44427e658d143dcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb280000000000000000000000000000000012da4a2c89951f85757c59a2630bde25c30af955448c972d256f1a6a259793c7b2bdc3f8734f4e312897cb6a3550800d00000000000000000000000000000000199939ffbde7b14b5f23eef23d4a660bf3f561aed38205e68d091ddef9679df9230a59e8cb03212df2e99788fa2595bc23d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000017a61df7581a341f21da2d1768fb41bb89847c88b2a0d7b61aa3275e376a46672dcb919eebf20b242ce83493c83335680000000000000000000000000000000013edc932b7755115f530d1d044c4afe71807a6b9810f555432910b54b0fef441b4618652fc4bc2ac5b789a75d2d276aa2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000000c73683f328a0aa252c10bc3fae9e786ccf183f1b606a4596094fbe10630d4418a527509c93d23e62dba263d86f88951000000000000000000000000000000000260c9dd70a1ddb422491a20293c18e4749427cbe9841aaa3370533b6e5d6fcf882f8bd68b7161434bcd5060716fdb97e3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e000000000000000000000000000000000bad0e395f46f714ac9d40865d588c06adb54b12439bb408a9d546b0a8ba5b3098c242cf5c17d1e40dcf7b384e81b444000000000000000000000000000000000e595304cd73c8c2a0bd1dff70e89edfab22be69bafa16877ecf669ab1e1160c9719952bb6103f31f2ff028cae0f0ea45d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000011f0037c9bc2bf953a3eb7d8a0a3c8d991e6eaa5f13dc1978a31f0eddb550432c70aad096cc0b904ee540e5d2d1ee4730000000000000000000000000000000004f8616cc7476fd0b95f7bbb7fbcda389aab60a88ffba3c819868f7ed6cf08e7c0c7da0958bcd957e0429b9a7fe120bafa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc", + "Expected": "000000000000000000000000000000000e87aa419d686c55b1ed8ebf6e48d8f218d7198edcbc7db0dc3bb9581bb8dbf891dc383f27717536dc5fb7265ce1ffd8000000000000000000000000000000000a00646bc197307a7416aa9e35db9ce7eb81d762a065cf8d2e07f6470e424d7d59021be049b36eba2e44750a902f3124", + "Name": "matter_g1_multiexp_49", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005d5e69a8876b82b1de0b2d2a0d808c739b361d1cadf3ebc9c6096afbc19169f237774be6882caeffe47e86e3b8a33710000000000000000000000000000000017bf0fa8c247af0078d486e1961577d7977d0b4258ada3e158822d995188ee374d900c4d8b1ef4887fb03d8f6a4bf1776e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000e6ee0b0c7bb7c3f62284efda6bdbaf38bb5a72b4435b76928c5640fedbf9d4144358a20629403359fef5bcb99a795eb000000000000000000000000000000000e72324fb2decb0b0c7fa18061a41bddd6e2c55f901554de9be8ac7b2263631fee8bc77773318f6b13b2db7eb1ad0f3cd51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000102df0d54108666e7aa611fec5c09b72d269c72e6fcee7787ece5f33153a3999ba5f22adfafa461aeda64e113b795dbc000000000000000000000000000000000b77ab3de0a2d91b8c24a47a27fbc5b2281cea40d87872010b94e895d9589880385f82ff53fad55af4f4e462df1c9ef6fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000018e8fa3c5bd83b51b1af197f0dee78e5c912c742df0cae1b59ac44fb2b903ad5ee1fe9750a034d18141f09a2b8298f850000000000000000000000000000000001526a80337eb938420cf2e825e5bcf3152e90e448ae3b40ee61929117d35f694eb5ce9133b2cc664c520fa9da8ed65a7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca0581299490000000000000000000000000000000007f11b03e06ca74a35cf702f19fe29facac855d7f5adac59bbb8c058b1eff7d4748c886eb08600e0484aa976269e5d0c0000000000000000000000000000000010a5b0f723371690f6ccc5fb346874e58071167947d45e54f9d5edd035f2d68b4ef9e301f26ef671634121ae6145e44e41b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d65000000000000000000000000000000000db2e2ef96ea61075e063629eb031235543e8f39f012fd006e143eb137524976c5a81eb26996a4ec3619a7fda051df6d0000000000000000000000000000000015d39e93da2b392dec64c58e73740376552e69caf87ce9162801466e75dd1e25b7d5762099112b21411e8d8bc18806fe5615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc000000000000000000000000000000000e048ef3ee3bf3c41cc10b89b7d0f8b3f27c89fa0ab25542653155dfb7f8a7e8488a737bf2f6dab558910c9ae98aea33000000000000000000000000000000001357eb0945e2c4933b358970184a21b3369dd7a43a88841e94c3a388681f338770fdc3a32862c3a52eb251721b2979e9bcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000c6321bbc74b6b3a9f0c9470461c80b1713a5092871dc54dd022d3ade73845852315b3e85b53b74ce2b31d1780939d13000000000000000000000000000000000cdef7351c2923faedb211e79a44e0e02ebceb8103cec2ed7541a54bfafe3967791edbeb6d4b0da1ee37b9a5d77ac8f194c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c4000000000000000000000000000000000b925015af89d42f155eb1f5104db1128faa23101fb9bc1a9757266a2717d50e908c64c502a8d19bb1e8c01dad554e41000000000000000000000000000000000fc8c5cbacca685c24188e8f936637c7c8010f6126e9b9b49e7d38191af1246c2a3cf7ca45bce6f1e11c404919da61c3dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f20000000000000000000000000000000013fa1374d37396bc60386d07a441a7d21fb808e3b2ea0c39ca78a6dd70c473a8feb972e2981e50cab6288dd80c40c06a000000000000000000000000000000000f35c2a2897b35cd7417aac29ade18f86d56ba24848aed78a31513d5115bd964ac6711c5f71736490195bd97d2d5b507d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c1000000000000000000000000000000000799d71ab5145a8a4726cc5567d99b344971eb8bd6248e41aae02bacc358f967475f64169e1828a66905e4373cf5c9670000000000000000000000000000000017c680c55af98789584e073c3caf32373f58bea6ef7f839f1d5c39e512058360efe80a884ef5822bd5fee34869d028d5aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000000b4be422e3d3e96f6a6821c55bd2a37ba57de1bb59c8f4855b1f4b6906259de6be1c1be40523d5370ccc426b89478a350000000000000000000000000000000019212f598150b576c17c32a8f374db52c19431d7a60b99379f570189b3fa15edc75b807adabbed712268087cd9b89a8a106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a0000000000000000000000000000000009df46395e64ce38bc79acee751484ce1bac53c5e5233d3545df2ec776440e3f5b04239d6de10bdb086aa3c462fc6e820000000000000000000000000000000009a5c816b2abdcca7a916b1eb015b3d1c01f766e01264b5139e5a34a82a874c1efa8ef097d23b9e9441916a2f5bb17b4c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000023537e0238470f4d513d56d4ef8e244e3d853b3b10a893928547675c6b2d409ef6bbfaa299a726eb472067c48f056c000000000000000000000000000000000b48f21e01e72bb6ec384a1e8ab35db6ca032e4476f37a3282214efe483b672c34989e6d5c99f69473eb19e472d984bea5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed490000000000000000000000000000000013f1ad33a2016874de5265565049722929528a1c66b84c1876f4e4396f22fb2583d025c481d4d9aa2877e0062e842d7c0000000000000000000000000000000008a11522b3e6982a4b46ab6f1f6b07d33443780c914d4bcd50ef7ebcbec6ad944ab88b82640971e890a363dd92c71531083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a460000000000000000000000000000000019dfc9ca394e105c6ad51b130aab8a043ee58f26a0d8efa5beee59eb1543c2c3d33abb5cf2b23b0882a409d32f845b1400000000000000000000000000000000143e219edb6fad7dbd64e6aa82fafd05ed92bb46e526468cc3bc0d60c89319d3fa2032b5a617691ca2f136c9f7904225d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d", + "Expected": "00000000000000000000000000000000027cfeeac9c1606a0942a95068faed1405a5cc9b5e631c25a0010ea14cae5b7a51b406fd0b8a9d9ea7be9a8c0f76c86c00000000000000000000000000000000106c07dd0d4e6f44fb1c3451942bf10060d16980f50b63005936e35b5add71e1352c48d1048e5d77cda79a09f86ff213", + "Name": "matter_g1_multiexp_50", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000065d5c6ad252823540ff4a4639cd42443a3cccd808d40d8bd71379ef939b47c3027ba5593167b4dae93b62b2bd498f910000000000000000000000000000000012623162c0f025b16dfc1c7e5fa02f8af7b7fb0f2d42d6fa0fb01af45621f00faa4ed6da6f33c609448bc027cd6a4fd367c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000008b3c274f83f49cada0a1bbf0f56f6fe0f8a0873cf13efa42ff65dd6fda913102c2034a31a1a92cd154210d27b0120450000000000000000000000000000000001521dda1b2c9b42d7dc9822c64bec62e71c629d61e796165d9a18f8ab44056914fe5c8809f21663bfc70e310ddf5d952d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba9400000000000000000000000000000000109f72caac5abd41a228bd82b6649fab639e4d22cb3a9a060ff7577de61f33d32217a73014f5cc2c2a76582a6b751ae200000000000000000000000000000000059d0e9e64b10cefe03daa146c00c5040381ce6ac63886b5fcf19a0555a22a395a0cbe8b49c510c9bb7a308813fb482958c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e70000000000000000000000000000000001c5e132707520c525045a08626e014a84d8da23dc27b6320d5915e328c3bb0df3618cbd7ace26834920d4a8757368050000000000000000000000000000000008f5127405631bed295596639ec6091e97f16ce5a3062831102be951aec98c9ad34721489f65e731026029ae3eb13aaa50a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c3200000000000000000000000000000000146124bfbb9a3d253670be419f80998382895ad6237138044c55764f0d6fc07da5b70cbe17af3ad0c4b0dbe33f869e490000000000000000000000000000000011cadf640e78298347115e6110d3ed63dcbd251c48d3e21cfba4bd6859b0310041e67d212b54e63be6d68d2e7fccd83b3b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000012ab7e51b87512007e1baf2f3c3473cebb553bc2ea3d3146358688ea3167817a449ab9a7e0b090e00f47846da7f46340000000000000000000000000000000000702c1e0df68bee2666abb90bd593a17a6f9dad02a7d66102add9f3a525a1b4f1fefa3abe262852fd5ca357d2e1f02fd1d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e39210000000000000000000000000000000014635c8b9cacbe976733bcb1245eea410008082f240cc8d8246200abc0eeb6b7444f38da3ad93b1e029b06cbb12d42f7000000000000000000000000000000000d9aa00397e1799a82d73040122515b98be82052b784a4b385417f6e260e555c7c0c48a32ca1fb28224f75f887fa4bf86fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000009a0efefb9daaaba4b2beabf6c381c27df7c32d4021a4d722118886405414837cde5c55933de23ff6769a0a42933bdd700000000000000000000000000000000101c9941d98dc8a146a75f2fa48a8650b25ae8f6d943323b1c10360cfdcbebe220494660f4d6f7921fea006942e122ac06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e118862000000000000000000000000000000000e5b403702a229f36c9b83bab9335cbb4e39fe8f5e9a5aa4bace70361dd05c87ae356a40720c4a8214765d028cd161ec0000000000000000000000000000000006e447c61bce31b4843530e504fa1324657eba731a272ddae680c202a7d017ffdf0ad0656dc0984a1fa297f5e32c2740fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab00000000000000000000000000000000135fb22eca115779ad1295f8c7f149a6eb4fe046df664ddaee976a15e11a7a59db5e2c44b4a82c8ca1d17c0043f41ee0000000000000000000000000000000000fd9c1dceb20e85ef80bc9ee44e483cd0e2714882734a561ebbd0982d6d08e9c41484ee99790c20e83d051dad0a1b1e04a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf00000000000000000000000000000000040f65cac81c01f04db3e331659d6bbaac8fa01581b1bbfa62891c1bc95a67182d254650019dfa3171e16ce37deef29a000000000000000000000000000000000afd5e22abd5d5cf78764262a91aadcb8b807b2aafecb2aa3d3ba5a187304208e212e5df46a4dc48d6150a733075bbaacd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf00000000000000000000000000000000051046cbe6862c5e37cd2f3c14dfc2825d5c32de69b40f29140fd31405615edf6c116d384bdf1552a33fb00c6c65cd97000000000000000000000000000000000a61a19fdfc994105f03aa3e1b907f5177409664b2e50243cf7e0e6e7e74c7bfce582929e5670a351b3d7b4034f101ffaba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa0000000000000000000000000000000013b76183fa2e01d10a3ecea5be65ffbcb04724ed30e4655e26a7ac94d5861f0f308b7d4577789d2f4892eb89202d84100000000000000000000000000000000012c3fbed77d9c37c47c838899aaea0fb6585eec54801c3ff2b486086e33040aca6baf6192c33af59f7db1d489ddf7d086ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c00000000000000000000000000000000195c1bf8dc0114a472cb4daa31be44f22a162d22f2968b7909374fbc4d0883614d2911475cc3ba242844ef1c046885e70000000000000000000000000000000000d03e5bc3acdd01d174e1d2308e3f1ff3f103db8e2804210da44c47229bd983ac127295558dc5560c0fb2ea34def196f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000001261cc24d5e69fe8a7747fce45086499ad54f7c138fe76fa665517c58e475683c5a219df303810745dc554fa3c096f300000000000000000000000000000000122fc4c068c079827635d29e944366516c1d7cdb1ff62968d847f4882da8a4919b59e57690f6e0f6aaf083af0a04b2ca32b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a00000000000000000000000000000000050b5493fdadda15e15b2ad6104274da831753b1cd247f1dacffb6f896b9db7190bfae2ca202907d36b979b668540ea400000000000000000000000000000000141245d4556c7f1032d0ccd606e3a2d3338ad753fd7d0a3c1b8ab38e94d8618e85c22a269428537abe003f8de89f2c1171a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6", + "Expected": "0000000000000000000000000000000008bfa9c347d937e2ff2d77ce7127b1af4e7adad5f1aa6d71c6226b64c62c955fb0dd0e36a5e87472d6979b656c8a740e00000000000000000000000000000000032de435000391e441ecb7e777741fc72f4676f64cfaca6fadf030e12ea6374a7fa988c443a38595def0b434b185c75a", + "Name": "matter_g1_multiexp_51", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d0296528c7b2516ea73cf14c5625a4296c311fc2e09722f3b381279da52ba9482e43d3fdc1694b96c3f62b7d98d6951000000000000000000000000000000000da2aaba37d0955c5fcf31152926f2fb345deba744241bf66511da5f4ad9fab8a1cffa270c4e838c39b34bc28fbc08b02e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b200000000000000000000000000000000175c976baf0205ee7326c84c49cbd2d7c3db91d1ec92d87cca896ea09a7cfe4ef8ca45873f86e28afc4b525356a68cba000000000000000000000000000000000c442d3edb8b614407e0d138417f8a6c028b29dc1beb5825c928dff3a08820c5a8ed5de643068bf4d239bbcc2dcd0b7612a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea7778355947000000000000000000000000000000000f45bf893109177d3c336915c5e28c338ea28468cbe215ee6fc6f6e3c9aa9e0b7120586e42c5c087b55fb5789a4a9eb2000000000000000000000000000000000b6ad0cffaf555f081ec7a6fb354d6b20950fb6fee059f2f571430f86a7cb9996b5f655bc7cddd14f3f8ed37c7fd278889a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000001944f2fa08357307df2271f4bb57cd07a998df56425f7b8563902aaa0330070ce260b6d86fc38a5c6a284788d9cc0ed700000000000000000000000000000000165d8134931f7a4cbeb5114a10e44172aa6a0c250989dbd88282f92fc238a8e1e21221b04b239cfc597e2b74700c626d27dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c7a02cbcc758fa7b1ea5fd30b3b88cdda7c8661b3712ba5bf924b441e056fb9bea804bbfa1850c21cad891ee253ff7100000000000000000000000000000000012202a151fcb86875b4dd2dbeafe5ca484b63408ba01440007164fa2a2b7ebbe9d7f738f382a010508408d26a57c566ca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f840000000000000000000000000000000017d02a3ec6d45e9b49ddc8d1bdee168f71c32ba26d4de8c1bdb328cb4c46286328387aac8785eb5a7c71d0ed59810f4e000000000000000000000000000000000d23ee9c9fc914404ff46d0f6ee86984862e97a777ab516c2b84f5b5a7c1807d64e93fe57db53c7b95257fe46a7a15495aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000040ca6ea6cae1be17996106cacbc5d9f1962203fd25917dec2c053816f3200b9853b218a07db690d8261ae3cc85679bc00000000000000000000000000000000097e8f4b5a24b010382888ebd7ab7cb71f471bca00c1499486cfbf1bc5ba6af169ac27e1ed8cf31b5d9600361ad13663fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000016f95e3e24941c2745c009437c1b2f5ebf690c9c76e269f877bbf73ddc6b15c6132d424c26a3c7bdd9c5302dcbab171f000000000000000000000000000000000cfca2fd001c0da52f231a60288b22a134c7e16aac8745129c351dd96fa37b72a9ef3d93d5e8e45cb5fab9e73ff188e128d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b6224000000000000000000000000000000000e2bdbc906c10b04c5fc1e867af43bea7ca43cdbc43cc3574a47b2b0670716a92fd863d4f423f3392ec8849e74850eb9000000000000000000000000000000000ae76847a2524be3a04bf85e096a1ca4cd3674459698fe326db2d71799c8906022e15bcadfbc9ddcd43dbee3443842a81208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a2900000000000000000000000000000000035d66b8b8b64bb0d3d1ba6bc1bd34c326ce6abac3a97188f82be38d1756f14a63bfedd531d5e19813b668012f77763300000000000000000000000000000000060851234e4cfa8c168db199bea8cbc337e685b565a6faf67e07c463632a6a163a2d22acf9fc6bc6a1f7ead5d288fcccd3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000010e398f6c9ded2fef3cd95cbef681c5335a1e9d08c05dc05b6391f65941cb3a79df9e1cc4ebd3fce82d36cc628b7f65c0000000000000000000000000000000016dede30728c57650952e9425b6da1ec8ee5702e783c69936eaf6857f199bd9ffae569db3cbd61483d48188633fef7ed7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000005eb3a914a78b4bb3041a32397bdba3edf6943ed474ac8efbf9c84a6cdae5d65a8f55ce4ad141b846f1bcb5df1206417000000000000000000000000000000000c20828a5d8abc2c8f72809348e770649bdf4bc0991f45979501f31d9f31e028731a8ccf07f0cc51bf8b59632897c540c5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f1300000000000000000000000000000000122cf863d9ddfdc627a0993dc7ca5810e84ab254ff8147a220d436043c0a695b0cceaa374842c335c14b6ebb273472d800000000000000000000000000000000150fc0b14e30ee797e3b9202533c681ca9e6b1b43347cfa11da59ceab439c9e5cbc038a50917cd9167a0fd591d8175e484ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000002ec5aa74588f6a7fd8076b9a846ff3542543dc7a3c798c423326eb06ef92edb8c35583785cfff21f903f08f692d6293000000000000000000000000000000000df140c1539cd3d94b5f9d0aafc38294d1738c5b3c1880d8864e83909b152de0a469742cd31e5e8f5838ad793ea32649a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b5000000000000000000000000000000000a440f227be209dd1bb816a4dd8c1abbdbd03d97c243ac6e48c4efcabef4d7a4b5bf65ea7bea6f4a1da985bbb9fac626000000000000000000000000000000001431a99e1243e57054d2b43217286b35bbf37afff72b163ad40dd4ca92439f4b513284551b0fb137f968f9f59a540cac650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000b99ae325f1fcf4f3c83f251183871d1b6048a43d15da80650e0b5c1b671031cc9af63a478b5939210356c4c2dcc7aa1000000000000000000000000000000001382d6f0550aad61dccb47a66d004ab3801445d55dd320a6ccf03577b1c1c915022a955e7f3fccbbdd20e4175bd0ae38282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57", + "Expected": "000000000000000000000000000000000c62c70aac1893222d967bde4fdffc565cc65fe29387825224b8571367ae8fa857b63c32033faa564f6e12d409b5cc060000000000000000000000000000000015cb57fcbc876f5aeca01d90d55ea777aa1421547e8aff0de69fe5527c9a575f9cecd1235a37648c7509d9bebb4e6800", + "Name": "matter_g1_multiexp_52", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000123fa54665de1ad1eb74d400f93b70f8502bd9386a164ef9ac7549b3693525e3fd077b2b2d8b15ab0c6cd5da30f8317800000000000000000000000000000000185921a0fb38ec1eb6804002b3bcbd4d4bc759885e9c1fafe275d51840434382df783518ce768ae40e736ad2ca8fc8803d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb9000000000000000000000000000000000eb30c8da4c7eb16d797f24b5d8e210dfaa68684939cad598518298c84214ad769f6a2634fc290c2c267c8f3a2872f020000000000000000000000000000000006452f211931b8d7ccd8777b2407e5cb073097ae9b309f1e95633f39d1a5a7f5843a6e87473b4b9c1bbfc17971108e3de71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000162c0325ae75a81c92a8885f14e2f7b9b8bfb249fb9a352d0007cd8bdfce2d8024f1e4674614cd0afbded99472d547000000000000000000000000000000000010d8497a5f31cda80af22bfa6695b4e2c8fb5557ee74581a33fbd0cf8cb2e0b4ca3ecc42487cf957ea81a5388d9871fcbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a0000000000000000000000000000000013270ce7a1b4abe3026d245df9b93061a435ed00d0464d8de14675247c7f2f1cbb6e21c8282e71d2fa28eca1e3f5863c000000000000000000000000000000000b87656d14cfe98c2d3f34b03de0b9f08207b00aaf6c5a4a6b9b4989744581772a2d6d1923c3d07b784853f7b2d789b9ef0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab700000000000000000000000000000000067296630285ec7da7cfdeedd387d52d905ec39e183b87479c8f0fba967e840f8394cb518dba4f4b7d4e2cdc00ca62c3000000000000000000000000000000000ed41fe0f04e0c63f3fd7ec7560d24974fd06a1566e8f129f580251227cb9b7e10ed6e60c2e7449721d5332709f465973ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc00000000000000000000000000000000006f7bbdc3c8fe2f7da9533a3f8a3c48c630d6cf567c75dcf89e13852f7a8691e2625ca24517ad3b59ed3513f7d3b4fb20000000000000000000000000000000000a2e63715ec49b06a78e014b98effbb03f99ce61b464c66108cf18ea49def3e1f035a8b88f37b453b31357d2a2a48f4e5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a9e51eaf24d2d0fcb7f1dc7ad985ecd4da3ecd19fb75591467edb0f7fc7bcef67c1c272f39c31ef36bbc73d7ea6034d000000000000000000000000000000000332dedca239f4d1272db77dc388e07005d90f44311aa889b42e931d08c2669c3f4aeecd9052d3f2585b2a4e41c8abbf97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb5926000000000000000000000000000000000c6b189ddc86e2d6722ebabc445190cf94bb4c54135aae2601c957e062d351d0c9fff19cbeb45cfc5dd05eb3543a660000000000000000000000000000000000133794839bae14fa041004f173506fff511526313da5a8f4e32c895751a22ecf01cfba564006037326187b899aed596ac54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff67352000000000000000000000000000000000a15343698b916965009f1894c8b74a790d59bc39b7f0de01095275ec002c97c66e7a6a970b4b9091cdc54abdff1cdb800000000000000000000000000000000045f084e0a7c0014e58c9988e72e1861bdb4f962ff9869d444d5ba4094178d52f9c2aa511feb6e8717098cc1f09d49eb47ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001656928ad3ee67675951e2d2ddd6a7d9c629a3148face6d1269f79c3d0699f95350e83a6ec20aa3be78a2794c3f250160000000000000000000000000000000001b8c9e4c818774dbd2416193e795a429a22881abc94ebd9a8b42bc4d7069a9778e4bdf7270180784d914bc6be99b41c14ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b0000000000000000000000000000000013d779138ab03fafee1e4bfa2a290c4f20d2b57854a5133cf5ad7817bd32bbf2945a02b4fd5c8489e704e60ee937f962000000000000000000000000000000000aa058528a4f9bb583295ace843feac4dbce24a22ea6bf412be019f590c621bdfc7562e8dd49afcc337cab474d9abd0129b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e2040000000000000000000000000000000014f30fdaf2f81f9d941af33d53e2d9e3162f62f47c60164e9b5ea3a5cf3a681a80b66ebfea391331c231abc4341cb94b000000000000000000000000000000001854addff23c2f53a21a6d39c72f91ef0e8d9a6d6468f319200466f78854c41be3e914bf7f966f00e185b44108af30f092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f00000000000000000000000000000000188a1934a28c7571ee94f1aa5c161be611939e52156bff158170d5e12a6480e3b9d1528082cc2e537ae1734b1847f8f8000000000000000000000000000000001728b57eca86cc8fcd9dfc65a8f5f055d51d300d8781839d744a1b81a0233221cd353f642b3507703880eb0a33afa05c8f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000006c99e6c8b554d748a3526da79e8a867efde15ec50ff62e43f691748996dc087dbc538cf65820ca065f3adb5884e2f0c000000000000000000000000000000000c577c42243b95b4a613c485026306513685cce294333b72388d6968019d04214ed4bbbd5b64bce78fc380115a4b067ca9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000d7b48e69a9807c6fc867f59c894d5bbfeeeacff500a3ad4528ed4848f5ce501baf8959f822c259b712236529dff0b0a000000000000000000000000000000000e7d7932084a0416a4bafe237c923d1390dc6662e7842829ab6747024378f284af07ccde9cf80042bec56e7429ab3acd80b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000a1f74acb627d1814ef90b2d756bf76383075134c1b34dc126094238eadebd780c1ab8a3d1f4d9566dbef1c706d931920000000000000000000000000000000009bf8c2fc78b1f7af25941bf429059e9f86b34a36ff865b33e918c8435a766d897df83005c54871ad0d3e82308e368501b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61", + "Expected": "000000000000000000000000000000000a912d7d352bdd182a8b431672b496ecbf18276da61d6a8eb066c41783b7cf3df287796b34b165db186e815c8847b3ea0000000000000000000000000000000002881de241ed8109f544f3a6262eac1aae69de7a7e9953812eede9e63a970225646f5c441b7de80106d53cb6dbb43f10", + "Name": "matter_g1_multiexp_53", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018896a4d84c1ec1a20e1b0e33f159de4d82b55b6d27d863ec7cbefc2d9c180beed2285aadb34d29ceea681689dab06ce0000000000000000000000000000000013398b5f6f2c0c9095af94796572d603de02d41c599e09d3e254b326fa1575e0c6a2b7263a196c5150440daffb0d60e810e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f6631850000000000000000000000000000000015b31d591dedde69dcfe9c23df11782c090c443e505d2edfa217121a1d51b6883d782917b2a082a41ce698ecd95ba95b00000000000000000000000000000000164b18eaf53165842e50112c4a8490b8246376b58bb6c188fc929160f49cb0b68ad2f13dbeac8466fca75e6f72a398b8cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000012876e247618c76af5221a50780803ab64970fea8bdeefcdc1ef4c9a160718fbaed9dc6691502433295d54d4030ee157000000000000000000000000000000000cfd8dbbeecfd176cce05ca1663930be8cf3b300a287ed053e36f64618a14850a3e813582da1f54ac7e96ff61ae57c86290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e800000000000000000000000000000000085c5db53c4abe188f44f084bf17084d3ae409b753089636d3c528162c2816b9b9ef3c0c8c05e88189407d7ca95d40f9000000000000000000000000000000000015d9ab325a8ae365f173821829aa395db9211015903c08491375f82853d9084d8aa2e35c2634a296ba14b50e34c1feb71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff570000000000000000000000000000000014bc3ba096662dc560e88ea6b7b4363c427d038fe85a49ab8d9d63524940f26106447ad6e3d7495ca562c98b64d445880000000000000000000000000000000018bf745fde497914d81d1e3ab96630f24f6ed27ebf1208f7a46ad9fb893a3f183982c0acfc001984de34f617841524f9250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000019b15da994067a017c3040830b5e5f7eb77ce0cf0674e96b209b80c54f1307cb04799624647fd1fb990c61092682ef730000000000000000000000000000000002248d31211c2a37df59a0a4ddb0cc7880dea316519ab7baf1c614b26e2673f03b00e387fd537aee442cfc94f734aad8ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000003a499813ed2a3878ffc11d27dba4d55837d1114049a72444b6db0c8a7d23a53af765d66b5017695efa39bcf7d1c97ba00000000000000000000000000000000011fb1a989afe2b093fa2ae3c0405483bb1a52c21226acbdf2a52e2e5fd5f7404776551c2deee87f431ff39dfb031d716ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000014540330ba54d2f16a9bdec93a0b7ccd58ecb44361c67f209d36d2a42b5d5a4f9b9dce0701ad0677d6d6ca83a256e8460000000000000000000000000000000001a64d5b128c07848ec579df1d26755e5d2f70cf123013ac249a4d188b0eb56cd74cb12f7de2db69b3a0f9f4ece2c4201271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000017783e019baea183ee5d9e1f671a23108e403a22580f5c203dd6ff72dc0adaf802d031a236e72463e0fa2c5f7c6e68b300000000000000000000000000000000132d32bae3b92b7212dd7db16c87360274a409f46199f66e572bdb21c4af24af62758978e6d01af60f5fb87481d9f4f23ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd00000000000000000000000000000000000a625f252a8185bea7f1b73d1c7c9b1fc7f4ea5cdd017afbe9e56e7c12d58d893ddc387b7c2870f4a975b613bef0129000000000000000000000000000000000aff6dcf60f78bc908fc4c2466270065766792a05d8629fc7f5d2b61ce4882644947fcc3600d63bd5f49fea5574616bcc6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000008ee6e9521f32feaa034b533c0b7c749f60d84adb53d6943d3974fb4b92ce3cb3f67fbf52fff27802c893cb97e587b930000000000000000000000000000000012000b50d1c9628f822c41d56b29e21f3f496f00bcf05edb234ffda56767bb33dfae736aa9fb9a84ccb6a0e21131c5887275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c6150000000000000000000000000000000014d9d3051d073d24701f01631408b7dd1d37f0855baa64a13c493c15f7acf36da116595fb3d69dc386cc611c998f9ea9000000000000000000000000000000000b33438dc1f84da6ae50b1aa76fc52f5ba0e547fb15e8f655db9e0e26d6aed15c5cc4e48412d089d1ca6fb7a550f8eecdbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000008935c4cfe2a1620a0c895feecd91ea7fdcca3bb06fa514bafee38ea5819b7372e75a106904b9c9e8af268c9f5e5a45700000000000000000000000000000000114e9944fbfc05ee1ed75603bb9b79301a1f90d3b5209ea14989fdd16f5deeb01e3474da2b4692a3e0b9625d3bf9b4b2ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf00000000000000000000000000000000105268fff23696890182b5ec307b38ee1cf28336e1c3fa28b9b697998567035323ccc91e974f63c55c928f64fabc2ca0000000000000000000000000000000000ac2f8c91fa31e2d950385509b86d512c80f0d1c73d223f71b26040d58822e4269a85e82ae390441853f8169177943aa461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000d5a5c94375029e5511a6c6ca40108377db43e4e0b03cceaf9fb77fac7906f71019c1a85591719bfa5d9349f1089ba0d00000000000000000000000000000000163bdfc6d40c96bd24a3b83f89037ec9e4191b533e36dc699a32c854291b0823b3f071464654eed00f08a691aa68636bcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d0000000000000000000000000000000011654611997b772db3111d2d4edf92b83689451b1e7594a7a4bd40d85820df6a1ab090f6a1959acb322323eef27fbd86000000000000000000000000000000000b905fec9e379cfba09fd502197305ae39b48facdb01f52afbcdf159c5674234ac9723643830ab8e2639e7a0d6bd979267de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda", + "Expected": "0000000000000000000000000000000004de1528d78645a4055ea348ef2618b85f8214c1dbd21ee08ad164abc02cbb2927202302dcd142c65e12934dec409e18000000000000000000000000000000000de34a6fbb73c7152f1636e5c02c75dbbc5910c763bb790d78bb56e48cbc6380bcc2ca14cc11ae139fe009135c81b243", + "Name": "matter_g1_multiexp_54", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000184094ad33f83f5b229643d9808f5b3b7f3e50306788f8485472405c79e57e489549c0901c3d1694b5f61f74d87afe9600000000000000000000000000000000007ec616b56868e00563d8e8bdd36de3b5d1e314be0d81c4ee97fabab1641c89cc21e70153a4d3d4e788b04ffaf07bad624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000017e7c08cfedb74bf88c1a80762be0e0754a86e5482c27b41240f4ffa9d014e9e8560e172519031eecccd897b869c365f00000000000000000000000000000000115ff96d404829597f16b9b97f8ff71a8eaca1a76bbcb72d53803d35335d8a8c1cea58559136f9b254c28262aa907414edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c235000000000000000000000000000000000b5450038d49c91e4e5a40a31f1f75923c7e1599695b829d9975ba7d845ab20ed5a62a7238d6a6479b2c6f9249068aed0000000000000000000000000000000013066cb8ef171bdfa11e70ddf83eb2447c4169fe38e008be5787c38b1b8a946fc474e07795765ca17fd5bcf64150fb04feb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b000000000000000000000000000000000f19fc0ba8a0ec5a2cdb9844002601f580e0eb9b2265c86f6efc4b633079d43461d6bf241ccbf422eb9d7c00ecca88570000000000000000000000000000000012e744ae937ff9e8e4f611fbd1c9896bd31bb1ca36b948d9be89960fee6c0cbab3264aacb916ae3596f110cc1b26bfedcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000006ca09ce8c07e89e9e51e208b5d32b5ab61f0de60484d9185a26911b56a728a7473b70313fd18c893ed3453719b074450000000000000000000000000000000003d372a5477fe7fd84a58f6f2eda8f5c61aa0c357c7fc1708f7616b8cdff249e7d2910d753c2e531a278f5853fc065970b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000152ea2fd1934c32c3c8e27a6ffb278741b899c5e296549380d019307875629d57ae44580a944babeecef73753e30c92600000000000000000000000000000000161a77844c90a6e83ed2c40c937de21fbd714a5cde60015a71bd4c960e894d3cb54a8d1e4bb4cb0a1985d4469814a991730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000a959b12e3af03cd4629f5f6f412b7084eec6aa55369e2dd2f355c93ea984ea6f2a7a01e6a10146849503d230fb08f7300000000000000000000000000000000161340908a38e4ff5373df643e3cfdc459d872b5cfd41ab34fd3297b10c37dbf3088fc23fb71f2a1751a121bcf51ee36ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000013e31a4f0cc29a5ff7f4df39db999c95eac789656bc9c6b91d0209b8a5ec2dbab698048fefb75a3dfa48066ed5743215000000000000000000000000000000001851e72741707cf96f887d13e01981f1e3db5834185eedaafdea99eeb11dcd3e90a9985f40886b60ee2a779b141bb62082b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000001501f155cf6f053631ebac7d2c57cbb101a750f98b6e11df79dbb24ec8804535b1b24942022aa64713fc60adb2017bff0000000000000000000000000000000012a08f9b1ab90531a26221b70751efa598b4046a5482c01d72f506ffbb3430d35016848755674d01e16bb78a44f8b6882396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc0000000000000000000000000000000008fe1ea18cd8e1d2c620356430ca43782f844a2efa6a285a7c9c086e972b12735faf6237447759bd93d98b6dc7c42344000000000000000000000000000000001731f36e811c640f44adce6bb68fd71065f440eeada278ebcabfb9bf0291e551ed302c592aa4ba7e3a502cf58e3eede69a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000018e3a4176b543f2152bd7f72ca358af6226f77b5e10f3f9006c8bbe4283776ac31e6d10e838e89e8090215a133e2cc510000000000000000000000000000000000f88c3eab9ab32fc165083ba1650736e04b4e8740591f6e3ffbf684fb359fc8d82513c25a9ecf4d46faaa14d9f13a3ff20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb0000000000000000000000000000000016fb47b4497cdcc75c0547f4234ce94f45d160e7bbe199902b2af5a5896e7d46cdc866d0fd730f568449032fc3a2df4b0000000000000000000000000000000016c2da30ef51e6728c09c3b29a7abdbb104f1a4fcc8960248b9773d2ea7f1bd161bf17203a271edfb235e8b0be437957f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000001992edcbf32707e92506e5cd12662e730bc96b5f33bb88c5569fe6b266aecf63548be20b03fefaa078231b17424ac98d000000000000000000000000000000000f6179cb8878214222c2353a60e0ee210c86e306e335e929050543f084ce7c7ef56ca8444eee59856f4107e0d8cf997b52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e000000000000000000000000000000000d3ff341e9b3821ac23ff7a87cc9dec3fba38ab8f2bc0f58e4c0135a9d66c6d6731ad8bb97468ca44538ca7f26fdfeea00000000000000000000000000000000053240b8429fb290453de18000ac58df56b5bf3c279e35d9cae8b350b932b0545b6c19ec7ff186c2123731d971146df1d32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f0000000000000000000000000000000004b424dab429bb3d22d18b52c4f9412a65eb7e8ec40b5e308f65fe6c0da1a1ab55a629ef8ed57adf108d146b46e6261e00000000000000000000000000000000057b7d5285194693a7ec1ed9ee3dfbe8598d9acb670baf03bf77c7799227ea788052de690e229b0d28c0a6cd79d22b0c1f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad2694000000000000000000000000000000000ffc202d826607947dd8f63b227a06d8c6b04848dd102da57723fe20e9b06b7c125f0ab2d2f53e14cbe95f1031624f99000000000000000000000000000000000880400b425ffe1b63214509f9acb0255d089e9de8e4eb643fa3b0383aed760f4c00babadd32f48af724a2c80a8223b383052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692", + "Expected": "0000000000000000000000000000000013c0b89e259f71ae41cc73ffa3c900ccea45a8a655353db6eb17a87582b00bfb971ba91d48526d934b95e9bb6a0fb5a200000000000000000000000000000000042a78ec26bc1ac4165c36d84588ca132b7366a3fb548801810da041213ee84c7e6aaf5ba02ac051cc1c5be5dfce0ea5", + "Name": "matter_g1_multiexp_55", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000083ddce067e21b219535e477f77ba100fb86744b1b82b4ccd0c72aac69025038e719ed173e70805c025b19bf7ba5908a000000000000000000000000000000000a9eb816ed60bbe55d4833c0e91ee73669aad116ff793d941223c17c86fea3ea434172c3214a4620d4090915cfb15d11c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c705200000000000000000000000000000000050bdd7d98b9df55ec0ab87e757de009c804880f06be3ce13c5e051c3080df45bedad4f074812a698f50d6774cd5921b000000000000000000000000000000000a8dde7b81feef753cf16f0818f29256391276847cb832bc2940bddb329b249af4970684e95fc02e702f09a84e7737dfccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c0000000000000000000000000000000000d989e383d1c6e48d14332a72a8efd89260fed65a47c4baabeb0c0cd8322e26ade95b8be9f532b4813153cc39e7a9402000000000000000000000000000000000f6f7ba41c95beccbf59ca1ebb1dd43348c51de617a09ab8a2d67d3f7065d3f4699b1fc31197275e5b895f92dd106d667ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c396310000000000000000000000000000000013f3c3eec6fd2d4c830458cf58d5e18f0367675c47d38fd5ddce1e8be3d6ab04f71d09852b987d2db64652b3255e874d0000000000000000000000000000000009c0000761e1fe517eb32bc3da4f7a933e77db6f960f5405b64d9088776b6ee8e23743cb4a1779e8d0d93787ca029d7c6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf018403978000000000000000000000000000000000c7a5bc0249717c1e39a4eea37de1b423960b409f5e0b3877e90d5278cabee197948383936739ee3f25b4bbf7f32e18900000000000000000000000000000000113d6fdda1f4b2a20d98e1d458920658c762303ee69fd7273a8830728f79be00358b3f3000927bc4d26352e5b9e6652b7fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f00000000000000000000000000000000108fb7a97ce429fc3ba1ca54ae841309e2ccce748dca953cb7dd9dee3ad9d919e3f8ab635b294b94b939cd80d3435b5e0000000000000000000000000000000003af838ba4ec485ec2a17e6f592fd832d05133952f273d1b472800b210c96cc503caadc17b38d3d1e978606786d9ffcddd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b0000000000000000000000000000000004cef28329ccf221ad7ab2b851e869bd433116753e0d8bf38d22ca46fbdc71fd9d96aeb9c0df69c47905a99c96fef0aa0000000000000000000000000000000012ef5c40d8b6469d9f3921eaa99446fe494a55994551fd1996c453a4e5cb4a2cbabe20671ff51639710a5e45a57271aab4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000c81313e8b5689935fc01b5f999de2fbe9852bdccf484edd0771e8427f2a194e29d0af09db1152fcd91c8f7b665f6929000000000000000000000000000000000f37dc7f87b8de48441861ce0c88b1a24f22aef2c321ddbf385cedec7810c20c7fee3d2c5a04b5390a5fc24612e4b3e9457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd10000000000000000000000000000000004ebf9f75e92ec4fb7168bf71215c9ea8ec17dd9ab392c9810316a30a33b4ace8d93ab75356baaeb51a7f47b4370915d000000000000000000000000000000001307c68414b73db43bcd9062580f7c814c3c34545ad5d943685ed8df26acd457823ed628e4b215875a9008a406fadb5619f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000cb5860f081e314d4fa3bf70a5eb18d6fb7f5257a708f1b1726b539115050754724ffd6a34d3b5c95359f40f41f2390e000000000000000000000000000000000c392d8603c2ef93d2765d98c695dbda8e4b64ed90c4771a4e69fa00a77d788981132336f870a3a93765902fd8fe8763f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000011c802516f42e267c0f9db096fdfff77d676eb301ef1ad440b6c2129c5b5722c420f6e479443cbf43d48803f7e32d8470000000000000000000000000000000004a5ef232d3582724c3eda67cf2e69b26ce44bd927555359820efc3fc67912df560edfc4d119c5595e1ab1fd7e2a262f50aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000eef1e6400dbda287910c117ba17eee1137377e262f7f5cf13710b521bd26eca2aa9731b0a1cf182a0d57a329369125400000000000000000000000000000000188e925365fe7cb96875e85f711d8ce233cadbcdd4c892eac52d9c77f98082662410db4cb6b24889b21f162eecd10f42d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad434000000000000000000000000000000001307849ed4d685815c670477ac54826e94465aed0b70df9683d09ddc62597e7a0a7a4b2839fbec735eeba08bbd3e821c0000000000000000000000000000000005dd74ee1018ff2280c3dd8faec3c97bbd00bbb7cfbcb849bb003b590a999b6bb3a973ec96bd9d825206eb353086283485c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae000000000000000000000000000000000458181a1019a65c34835eeca4898b88b0351da7422bb5982616c90740e8773b5a03272646f26c3a5801c6c16be33ec900000000000000000000000000000000101c2091a08179eb0be41e20a545f5b53b8ee39365dc9b57f12d75b2beebdad488d63e857ba5187c8f92af447f72896ebacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000cb8c637a9b8f053d5104b582ca03ecba768425c639fef23c4b624f31523e0ac669183639991728135474ca19e0335160000000000000000000000000000000009e0798589417cff12eef14f00e415c51c30fc26461e92c4e3fb4a5ab1a653ae791f05f4cde0cfe2132c377175cec1c2473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae0000000000000000000000000000000008400ba7dce60413ff085c0904066b8e9e9ae290781132e739a5a8c7bcbda322fe1c8d0fdb0e9b0abe44ff99d4ca22ee0000000000000000000000000000000008b54feb64f59541ba3b7c6f86d24b69fa30ba057db890cc6d958e3a7de8bd379257c90a413050f7789ded9ee7b28bbd2774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a", + "Expected": "0000000000000000000000000000000017377baed9953cc7fe1868baa9e2f6c87edfab1700bd1f60e2644bb97cbe2dd1fe031a986fde6555930d4c464399f1f6000000000000000000000000000000000ff69a282658b49c270e829b326502d83a82dade820de50764a7df957e94e7c0a1f0e3d9084b45d9a875134bedc4a0bf", + "Name": "matter_g1_multiexp_56", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000d1de82c29aaa76b17079b2e1000005bf37df08de2c5ba7a0f9a14870e0ac327f46f59a116c72db57cf5110aeed6c76000000000000000000000000000000000a8ff0afd1cd7f541775567134a889d82727e893e4f57d1b5981fabd4bbff59dd3d3995a181efc9b5fc078eb3d4cd0e7d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f026672140000000000000000000000000000000018120f0d0dc908dce4adbe50b24b66ce12e710fd35e5a8a8c357dd80c078d6854f20b12d40279b9d6a895460d8989cda00000000000000000000000000000000064f4e282ec5cac74e1a12f678391730663c83afcc0b415fd21475875762de2224e389d607cba84788a16d622d2ef5c13e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000dae339b418871e2f31ed380824412acbe44e6c73ede9b4c52c054924297aaee1f7da749374d7ca44b138acb85dc182f00000000000000000000000000000000155cfb670ac94e7d5a095d2797cbbb5b8ad3e037fd246246f8e8c2278f5d4e53a773e6518ebc3ea5aeec6383d6fbb62c145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f000000000000000000000000000000000c1f7aeb05294c1b496de11f743c0c7aa4255211e1e36389bc93dc8d0e73fdb9af7bfbcef2c196a95d1d449b9983b2020000000000000000000000000000000011251668e9edb38ad147f22cbab7d280d436d11039d9fb823a19dffedf2c6a484f112560623cde7e5525c85b4f5d06accf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed0000000000000000000000000000000000ad488f5b9934adcdc834558c8db1d62574e1ffcd03da30eed865042abae4dc03d69010e7e591d9f0a8e421d22cab23a0000000000000000000000000000000002cb0a8e0713dd3c4833af74767ce46aca6c1efdfe75d09a50fce4df2eee3fbc031357691e23ceee810d30004d03f6b9d9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000465d95750a3c688f560ab9ca6fd1f77457592a0d5f54c17904a222010444d048df2be3dd402f046b1375d75de446d2500000000000000000000000000000000166289d948aa518167e72591a011b3f5ce209bd32ce091543bbdee1e8776269347ed711e1e9f1193f818e3045761a75141733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced157586700000000000000000000000000000000012abc4f1c56f9ac3760acec3d79b77e9ac71bbfe4d2a90cf43da3607c99035e550a4d0fda734bcfcb16ad08f773535d400000000000000000000000000000000030953a6099532f7ac352eff43569914c3f8d736b8aca89f778b4a67c754ada78e121dff664feb751532a41c8081380eb21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000138cb559d92b392e94cdd8666605cb5b05e585dccfc023bb6f1abe82fad35c108fca7a41afa49a801700dd8ef89eb3b0000000000000000000000000000000018cf89ad3e05492ac8699ba0723d5ce43e81b0166fc33653c967da921faef37f3ee2e8e3f71f983774966ca183e05f9eeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000129c48a05e3d6bfab6e6f5200fbb90fbb743b045509b129e3622929712939c5d15126a09f1a650489c8afde7ace8baea000000000000000000000000000000000abff3803d605dbd63bb8453e304335a943bebd224d2d8067d76f5591cc6a2b954b9156a243b0c23d08424fb9edb52383d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000194e6f217b863339824d95c77253ddef4ab97d9744d10392d399b1f165170bb8c13ef1b7cbd995c1c1dc2a9d1b87f0da0000000000000000000000000000000019fbdffa8df167a5e891d09aa1e79049d377014e58523c0eb453f5f072a468809dca8ce0aa22b45bad4f8853d985be1df6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda40000000000000000000000000000000006f0b72c2a934e430e4b773a61317007f1ef02c5f978b3565d623b6590b6cfec22f98b49f9d7f7efcc6913c139fb27a60000000000000000000000000000000018ea7df5f807d4c4981a9159d73d83ea84359d6aa00a5ef019b0dc307d096676c0d16c6b167fc55e14329a858c044c5c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e108700000000000000000000000000000000130fae66f6b4e1a9b0b39906fac847f1285a7d37bdb0d3ddc2c2bfcc6320ccbad2ef1f119f2663e3a45dbff005a469a10000000000000000000000000000000019ba2ae0c371256e4c3dd6f9ae2568386d3a8bd90a57ff982294eae9194494add18958dd516ca9dda6a0b334391cc211f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f000000000000000000000000000000001152651000a16809ec599f2fe9f330b0782685f6302254450884f0ee61ee2dc2cc9211f69d5d9dbcd7fe3345542a0159000000000000000000000000000000000b5c017e7ef71eb089188ed85331815b40c37abb6ff73d76f40fd8dcc6d2120c6a52df0da042b2b63dfd0da7db2bbca9ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a3130000000000000000000000000000000003a6f178d8c63765b2c8df834ebf7e96a4f451c6e05692f96b71c8be2a6e9af17a5cfd8b263eaa254592ea9a898488bf00000000000000000000000000000000185537df1a10c4c12fbcff08de45b349a90b0cc8cd17827df87abe160e84b661d58a1fd03c669015b991225ba08e171e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000085475c2fd70cb7caaaa7c5c1fb17e2346903a962fa68536240d041f2f8cd3a7b83aa79a77f713bc31f7becd347d18d7000000000000000000000000000000000c98414bc318b350113186db9e965a238f1f181b00a2265638d914d263e4a71ff643907ed8dca814e5b8d5713baa8dc9d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd7100200000000000000000000000000000000001064b94e868fa82c892dd244c6247063a276cc651e22d09695ac6e73d20bb801a189e8fcef8a711ed471fa3b2c7d19000000000000000000000000000000001561503962d7314fe41f7b2d34eadcc985fa748cc98479a06749692a00a46fb2fe5b5a68f7001a0f89f20f7f42f4463c38f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0", + "Expected": "0000000000000000000000000000000003e62892118f11065ebc66c58c86e2f0d3c79541aca8db33bd0e12f034f0e106a76f0aecd537539cf2d793cf823ebbbe000000000000000000000000000000000067e42ecf23e1b0590c30106b0720c806ca71fca0601b499025b58f137ff22aabdc6cc5eeef232fc9a20fb9c2bdee16", + "Name": "matter_g1_multiexp_57", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000184a661b34e18b637bca53ba60c891da69fe743d5336d92e811649094c15ecf2445736d0c1577bba4eb729aa7204b44f00000000000000000000000000000000129a348f7fa726585badc23f5dabf49ae095d300056b219bce0ce15f1f6a9fc5c8ebaae56362c3501af3f3de19515143cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a000000000000000000000000000000000badd515b1e0959e77e0f00c7420b46bda5fcb6db59cbd431a1b0ca68c291c6dfe89ece299434f83a980613fe73ab7d3000000000000000000000000000000001266343ad330fcb2cc8242e30a8085cf6995ebd810780115ef881516d4227c6051564d7343e4a5d6bfd210e2e40b91069a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000085e7c22d51db0a45d8db7d5365de9541eb87b81c237fc47cd25c297da4435b4c9b8212c76c929b7c8f32e8d9b11374c000000000000000000000000000000000a4b0f905b48145f1831e453d0372b7861f7be6e413182153cf77d737450a58f378652255cb4516a482d166233dc88c574e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e00000000000000000000000000000000086b97f87625356425a79db717f940debc7a7e932370ea315d1f94b1ead853e3ab6edea6302b6b5b0eb4e4bb3c7fd14e000000000000000000000000000000000fde70203ac7a82901250e9798ef1c671f8d5f878fa3bc83556437b9b98e77f7fe7d3a0f31b8cf05ff6332df0424136fc23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000e629b2d9a57bf96cdc6871ee7dd7675257cf62dd10028201448d8e5b1c0abe777190a868fee83ff5d067252312e82dc0000000000000000000000000000000002102d461c9522542acc185349ea93810c3e2412ebb427f8556b947efe198d616fe00818bedc22765f697507d7678dad972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a32640000000000000000000000000000000015727f52d46099c0ba041be660ca312204afb0f927fdcf0f1afa4cd3448cf3e9fb76bce7ce0da8b4c0048f76f0e7b1410000000000000000000000000000000009dc4e213faf0a8216061b59dd35a135b364431e2be37e42d065a42fc8e42eb8669d32a5f5ecdfd9234487479543471bdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000afb691289f877e1de6fbeb38cee0e36fabf3daf904256d5d6db6e96ce555a9304219bad41400ab6278727e5fe2faffa00000000000000000000000000000000165a54d6db7332b12224d59d8b677517190744c039d9bb401c2e3c4437dbf230b67308fa2d5ae2bf5de282c9ae38a3fa4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd3300000000000000000000000000000000032dbd300fa383541e5c40c849addae3def5a4f5392c44b9e96981dbcedd02252f9bfe4100de9954ab34fae9b2ec21ce00000000000000000000000000000000185e62adc2a44462019c86028c617ddf59a6b1c16071624de5ca755f936e73c47cba00f552d2d79baf60a1796dee009edc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b0000000000000000000000000000000008a574ccaa24ef76112a25b990b5d3b462ff9c43589c9efbb617b45a87bc26eca6dfc6c9e58a12650c202a06d3c86fe60000000000000000000000000000000011f41e39dc0f0bdde1b9e1879741824b20d9237dd7b462272115e8ed44a1e6b7bf82e8ae481204dd8662418fadc63bbf8af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b000000000000000000000000000000000efa7f183cdfcb25cc5516bdb45c409581b6f2a5bd8ce8092dbf9050a20b2ff57c6add39e96a6f1c8d2134a5a37778c7000000000000000000000000000000000a8213977e8512648b6aeafff2cefcd17a14a052791d20236a78e0b462dcac81db74f1625e787540d7dc279846983f647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b8000000000000000000000000000000000287e0add9dcf33f37a10a5ee89cef5240313af0bf0dc183d0c3d6b919c88b979c932c7f141ec5faf012a7f33fe56fa4000000000000000000000000000000001313f591d1da8f6baff044857d2c04f01935b493f5b951cd3538054756d33a52f71be92ef908f016c133aafeb9b9ad2470cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f85200000000000000000000000000000000192d02ab0a323e85e9fa6f553eaafe0d8ca2de63f0fec8139e24805f0785cc85b39908756ab4eb39354ecd8d9440d5260000000000000000000000000000000013997cf706bc8d40b019c2dacf6a7d269e0ffdf8bbc1b4b39e75b48ca5e5e6eba0007b8c55b59530b34b7ebb4c657c57244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f000000000000000000000000000000000a61b3cc7913e45c132cfb06a26fdb1882bd700b32361572fc79a3d2c432644392f341cc70905b86cad2ce52c30e2ace0000000000000000000000000000000011bb3d958600993ec04d9f98ea3f29df0dacbfe6557b36bed865c564595a64132e4036b6240c97cdb38a60533d5a08baad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf30000000000000000000000000000000003d2bc11fa699b284b37d1b45c8dd6b41436a7b2fa09cef316821516801afaa4e1282d717d4eb3d46e54c0208548dd9100000000000000000000000000000000123f8cdf2bcd7d6eab31975ddd610afa79c3c95fed2a6348fa6872b74a6e2816509c71f11d1f272dddb59bafc0f48fc454b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c6000000000000000000000000000000000982c1cbcc39867c7c8c4512392af1489a5e6aa01ecf56abf4cd9050a33536feeb1866421958b929096d2c3f6923891700000000000000000000000000000000104ba4defb74b35d15db80df1f4029650f00b306d702b5934c1705d226886d4bd22b6c88e71b862109f8dceacde3c6d2ae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e490000000000000000000000000000000006e5fc17bdc786eef8cf2140bd8002ea859619d319126fcc5053be9c28526e14e0bc8eb924fa242305069226d766f71c0000000000000000000000000000000017ee60b0dc932806dfefdff2cdf00efc4d5c81a1e84ce48a25db1d49ca26232d4e4cc1f37b34c80375597587dc183b4259b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4", + "Expected": "000000000000000000000000000000000c3dbdef90052dd5bdb70f15963091c8fccb5b8720a16140ec96dda18eb6cf003b6f5f7c244d25cf6da34f2a1703a9d800000000000000000000000000000000187456c5af42c3de3d5e08936e8a3f411fd6458d017ec508e48f2b634319155428733634e22883492d7612b2bc38158c", + "Name": "matter_g1_multiexp_58", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005a912b6b2b00c2b2c90ab1aef8c9240831ea9ff2ac3a92753054f159f5ee4eadab8ef57eb5972e3169ff9649b886daa000000000000000000000000000000000981b901734dfb3b5f63bcff802536492664ac13dc695960ad89342ea865ac67d00da7130833126a33573d55a9baf128a53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b46000000000000000000000000000000000b052881b3e27d232ec980dd99bd0ece4e861cecff2496472caffd741f2954718d605de98d9c27dd3ff473ff12b238400000000000000000000000000000000004de4bb9e5a4cef93662cee72259b88f7ccf8a495b733e868d76cc25e04c53a65a83c853c98a25f7a551641d54ecd9534d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000016e4d257db25c08a68943e6e0065b375422fc817539d2874279e2b41428da449627e6e04087fd448f651a23fb01816ba000000000000000000000000000000000e80d041b65789b3289a94848ca4b1109028c9fb314e652486e650221945ef4224ca03a693e062b06036898eb664fc211b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c74000000000000000000000000000000000542fcc8d668a827daf3726bd71d7ddeabc440a6fd0c08a4730803be6e76613cc0265252c41123146a5d7aeae93f485f00000000000000000000000000000000109a61920ccf34a0a71f51f4fe7c882b3d6fe449a8c67711dda64f9eb684b4a28cce6e8bfcd6f3cb599adbd0771a132dee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a50000000000000000000000000000000000a65c746a1206b1250598823b9b6fe5df3dfbee21cd31000e50140893875d1ad9fcd4fe12bce0758544ad8cb4cf5ba700000000000000000000000000000000038c25d3c35fb34151428d2f6bb8a459f834902334d195da214ee9fae4bc6099d225588a001f8fddacadeff0d3d215463e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000158f2288a667f475c736dce9e404ed37b18d677f984b6d9dafb051b87901e6fc60289161bfcfa46a3fdbea7b4cc3f795000000000000000000000000000000000d7faf96c636ee38347b0046d333e72d601e20b018d554d50ed63e30c62db7fa20e29b3ea57b1f31e0d7544ad711c96aab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001933815ab2d8b6cef8790f87dd9750bc2b7a75a9d6c425a3e84cd109f5f5ea866e171dfc212f6f8641252e9421fe3aaa000000000000000000000000000000000f8ba799ca5dd885046a4ffce1d26688d0bc6936f3a5a943dd54f89d991500908c81ec4f9b116e73f74d46b67731421bc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000d4ba404254175cdf5c47c08ec158ad83b6ff5b8dd32b8cb9753fa157726271f343cc0cf5231e7e31583877d2591930000000000000000000000000000000000191f45fc4b8c94519d13ab28e5f84e22dae2f82550b44be737728a695865973ff5060a639e3f03904d74717963dcd764ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba0000000000000000000000000000000014d348b7dbace24bfcb258c853b19fcc1637d7ed9b0ec00d4124cdf6d608c6849e8d2f9858afa83ff356380afa1376fe0000000000000000000000000000000008c509beae3cc22f0da64bccd2e0387c05d7613460942d25182605b3eae6ce052540142d5975733cb6554e6da9f473b6e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ef9aac66681015bdd9bf287caff9aee89225e30a7976e9f503a1712fa863c8d6d46a80952a1d94d96a5e0496f64ce5b0000000000000000000000000000000016c66018f43bf585195b256ca106f47077f977701d97f42564223817ade0a520aa3d7f06d868f1e91705232b1d2440d9f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000042d1d00a946085dc6329e852342573db7dda7385e6a50a2660a924ed6202968e787559fc58a162a775bcb115bf1fcf800000000000000000000000000000000162b52027b08b7d91fe0814c7be69414121cfd452f4d0407a2300bdfe9ba81a4561af74d8067e929b71a92947eac4fce71927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000001598949030cc21d76a9c69305f023bad3cc761d5f857bbccec4de6b0f7557395efb2d126382731aca994a5020039acf5000000000000000000000000000000000dbea8852edc6bef41dd317e7d70eb2a5416d5087ec5207af3f5b3fec39a416dd9ccf4cfb5400cca152f173e66df05f75ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000081e898b02838558c1c9d7ef9f86fefe512e2e7364ad824506c886b4cbe947657c5480353e4f72e237da013d81e5eeb10000000000000000000000000000000005353bf2dafb1b9b4f2cf58e16645aa3fb759eef6eb8f516db068d2768851e7724fda5cb85241aee62b4404de2862dfbd37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000028453aa48ad0302804f9cac568467668b1dc0dce2cbbaf280810ead2c0a94e156420f4fd2566ee7f629e57c3741b8960000000000000000000000000000000001cfc5ed80924f7088ce6a5414372d13fd8f6eb3dd83c66d8b8e4dd1d4db2bbbbbc6ffac00e3a880d8a8fb5dc07fb23f06d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b4000000000000000000000000000000000a236833fafc3da813b95f4562804361aaabcd8166780a4646734e4b65e3a1924c075d402404b52adda4902bac7a2cbe000000000000000000000000000000000def6beaad6a180998c4c70f9a8dd0d948a79524b31fa44874908058e9e58caec2e23d5a0787f1ca05a359ca276c840ff56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af00000000000000000000000000000000188bb3e69bdf0a5f31ad16751a12c767c86df80f53f6688ad74cb2fb32b81bbf9d60be1182ea1b6c0d6fd12ef73e253e00000000000000000000000000000000139ce5ffa569548f1bb877c3d573136a8eb12e7c69cd21a70526f8724bc67e0b37cf7149dac3f78377ae7d5bf4882a6771e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c", + "Expected": "0000000000000000000000000000000006e5af407ada013abf1451bc9d5c08e5ba9cddebff0cb77175b152fc19bbdc48e1498673ae4698dc74d039a671ecdcd9000000000000000000000000000000000c8783b3ce25445209b9f1d8bd3ba539c01d230c07c4fdff38ec902467d5f7e9e8e660d8997939684e119fdfcc963836", + "Name": "matter_g1_multiexp_59", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001731f73d2ef1f87fe1752c9d6428de241ba71506c76f31aa9697d1c436af51de363405f60110e8e69ab268280c20f92d0000000000000000000000000000000001ec6ede05f60685e39acc7e105f60602f0fa3c4a6da7342da755eb34aeaa5adbbac4c13197a2c93314ec79f5da8b90177f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da00000000000000000000000000000000158d295d41540fb1a27d8200ddf51fbb9d31a70fcb639c42b7fafae4a95b90ab1ca777125092aefe20f856e3291e528d0000000000000000000000000000000019670ff04a77cfd367c5f0c14218b5d95ea2eae8577da10f27d96e58039b7dd7e9f7f75c32f99dae0920509733ff9c96630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb00000000000000000000000000000000052f8e8098f9e83eaaec1c2638aad30b043c2359f2551a02b2b95816e1c55d37bbfa6e284f280f15dc174d5f03a7698400000000000000000000000000000000034bc698f07544952274c21f416d8f1281ccdcf6bf53ad352afb15a3412879a10b37e6b8b9fc5f46ad715f9ce7b46e3d0969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a5162000000000000000000000000000000000c2e014d5068adce3049cc326d36ef92f294700ab64bfe170260727117f098727cef2e28dc10fe473a46c98867c618400000000000000000000000000000000005b3ea9c12179a47f7e69690f3303ccae614e06878189b40264f02e9bb26284dde846d704121340723bfd1fe5696410dddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000119e9fe8723883d9ae8c61efdd3ae961795d79409750dd39aa6f0f8727ca2429856f977697c4f81894061da278a0f9a9000000000000000000000000000000001438a4dca0c786062aa9cb21e26b87e92f90dcc0bfa014f654b1734cf7cdb8a2e62fd3836a802a9917539dd068c6b4b1191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000012872f4dca3a9f3fcb07d67c76836c23eba3f7957bb77950a4b43ad9c7ee54f53187a742b13e026f8234df9e91659c400000000000000000000000000000000078b9d597bd9b5ed2f7e0d5f8e4a518012591b855c5352fa1450704a33c3cbd5695a0f8da235411aa99aada88086f643aa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f8480000000000000000000000000000000006c414c6611e00c6e98b370bacf2ffbd7ebeae890278a0e951d6aff7dd3e5fb90f82b4e65dd007a3289f97a9600786a9000000000000000000000000000000000cae4750f99ba13f03d3e0769cccc879a4832210d6a2f25b2696099c0cb184398b7d432e801d23200166a4c53a3e70f3049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf100000000000000000000000000000000122f404ddd6b34938d8e57d9d6ee78c3fdc1b771dd7392944ae88c625f81df63915a87ac63dbb69adf8fdf856a92bffd00000000000000000000000000000000197c20bf1392d4d68efc6ac3bd5d8b53b360e305a501dcfc2e350e3738503ebd44a574e478757240236762db2f23d4310502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f0000000000000000000000000000000019195a36dfc449c19b172ec061b4825e4de85fd5b9c633c953ba7a5617973e61abd0de3d59d441f49264a0dd2e781b20000000000000000000000000000000001430f743ee98a2b2f37d9ecf2a7d4dd4963707fd4cd6ccfdff55c3eb189aba2fb295877bc2d3db9032af26eff6485e459787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef52371375000000000000000000000000000000000be5d90e5fa172a2034667160f635ffc190fa495aa9af51b648125c29bcf9b4b31fea7a7e4b49d91b4a8d081c9aa2d3d000000000000000000000000000000001721ebb02265f698528ae1bdc5bd4500d7612bcab9ea939f552ffd8e9dec1d267dfd25ad4d3531676e2ecde3d2170c4810b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000012b7607bd9f1701ed002b6f72b2e832dad7c9b2bb6eb6368fbe78c48bdfa17b2546574d7876425cca7986fa6839b6da2000000000000000000000000000000001975f41ed7cf252a658e80634872ac495e4b518349487930610906bd396f7fe4af3c97acd0ed3b3f265917560b13e6ef072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000014ddf2cedfda66e12e999d0b280883c546e00dddc0bd17817d6df90b7a614c472cb2840b133eabdc7be39b63e50cd9ae000000000000000000000000000000000b86e0559e27a6061aafc091f93b744a8273032f0e8b1c8b7071baf3ac7008a8173b71f51b27efccba27cb018b25257ff3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d60000000000000000000000000000000010f691744e7094b801c180810b24f6a29c21a13514bcaa6303ae49067bdd001213f13c6f980c51b050a684b525c2dabe000000000000000000000000000000000e4e4cc3769cd3e0e458ded43b5c7c481c17efd3283972919212b877c21aa7abd31cf86ee2bdfd3cf0ef6d730c0907db393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca177000000000000000000000000000000000b5e86c2be33255bf6f2f2aa8b17109467543168c0bb92a9ce19bb64c5f84188b2e9f93ac85d948c76989d9d4dc9eafc000000000000000000000000000000000c5244fe670dcb16d7994b7db8f933ff98744e5c6dc124e057c05d2697881115a99f983be480e30ae3e0ce75081b261edb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000df7f56643536b20f65cae1ce4c67c6bb6def8c9b514d6edc92673ae743a2f4e4906aaf7e3b048f88f08a4f5c9f85c8000000000000000000000000000000000176cd183f547a3f38a86d604f8e76261755f72e7222f3734a456a3bf7029590848970e8836b3570e9a4f3500e54fa3008e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf240000000000000000000000000000000003737e58505d0f4c6890c7e03d5f252aa682c110f5bf5dfe8bcee9393104393f4a6a22c34c773e1dcb78881a31b33a71000000000000000000000000000000001988ab3430de7a463dcc2156db572c43b68e58ac2ee26f1ee1bf8e9889f6cd3250e5d7f9464a8eabb127306af39c13140940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c", + "Expected": "000000000000000000000000000000000d541103aa046ef53761e929d50c88796b90b3284668a2a75ecd461ade103f086fc443a3681b6440e6b8a8b9558870f20000000000000000000000000000000014120e26b160a37f8e18faf541bb863ebb425fbd0eb072268a12657b6e5ff62d4c0e504691b3d09b710741820f747b85", + "Name": "matter_g1_multiexp_60", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015c91ab58aad72af3364a3d05e2893c756a273b2c731ef421c0552dffcb32fdc4296bb79afcae2d3c8aec6e0dcd27c17000000000000000000000000000000001901b4fec7a1324a34fe403dcc51656145fcbeb4eac94f955f4fcc5ad6a016eaa436878e85dcebd8992e1a177c5bdbf80f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000016df7578f74b1ccdfd537a074d71f2dbdd581f1a2f78875a7d4e1c3cb772aad0d02bf4935f7b08aa5163e82e5a747bdb00000000000000000000000000000000053931dd0624377808705d3fc6e12c4894414c8f6a5662ffd71251bc7725e6d23b7781286b8be1e35eb615bb1efeee9c34680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba200000000000000000000000000000000056f0c5d78c5d4e97fcc7d6c3132dc4cd802eaa1bf18921d039274104b56e8a701c25de6ad33e57997b2e8491d7cedee000000000000000000000000000000000c87632eb73c464f53c15ec127cc5c72fe6a413e74313e80395b55e122108e2984eee6f53742ce4445f455108002398fefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad02200000000000000000000000000000000040f1ed7a9f7c70a546088822088c476f8954681f3741cffb7e6614dcefe2963253599acbd263b988af3764331a273030000000000000000000000000000000007f9d150a4b34b9a6f872f9bbec4d2e0795d02c5411d6b3a844ab95ea87f9330662c8b0789e12a8f6dafa2f7cf2f13a12c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000002890e211b1969c72a15c0f24b21dbf672b2cd33ba9ab79790c07f0734709bf13bbef4f54bf17db9629cd7abfcf1fc2f000000000000000000000000000000000010f13eb17ab7ccaa0bf32b8d4d38760b72fa0fbbabe04017d9d8283f6dcc5500a336339400bdfca06749f7c1e08f748b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc93000000000000000000000000000000000f7e328026c07b116dcb8950273579e0c4af027bd3aa442a41d279b1b7d87d672154d2513669428e8f401db490404e6d0000000000000000000000000000000004208901e02756c5a2430200d562c0ddec0224446b3fca62cc98e9efcfb3508f50794301b026d47eb99aee210dd2f898202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000001309e8c1cd6ca596ab2c9605ed0e356cfb97c4079518b0241d40a3e0e4769a8e58c0ec6a7bda173fc427aaedaa275ff7000000000000000000000000000000000143b1d1bb451cd56d800d71a747173e56b75cbd6fd28ff4abacbc1dd87653abcae715882af29c29a1631850694c5aff8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a0000000000000000000000000000000013bf8880a6c95a8791b8ec37c2188e4c0c2cf188e2fba01a9e7e4b81116b10da49415a0588385156e4bbd45b168467e3000000000000000000000000000000000be052be3f3278259b6e01d9d81afb4d4215b0b738378e56719403e2ed31bb6e15e47c9986aac19f79001a76f35e4162ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a730000000000000000000000000000000013b27128d2e8bde36f11503986c226a1613ba0779de9b25686284d12bd995c83e0db9eb0b2ea759ee81bce0ed2c0c2ad00000000000000000000000000000000128d6ea67c8cc9ce6eb93111780989b4b33afff45a5075691026ebcc607e61b7a48e2549ce8286cfe4a72b182073f373b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f600000000000000000000000000000000137033427de6a6d23e0a2fc17d396114f8f4ca3e56e42936c96029c5b829b3b8b7ea46fa47fa39f6e5dbcd804873d3ab000000000000000000000000000000001986563cad41be453d14ea3f166c2ef2d89ada32a345554ea7c7141f6b1306af815579d7399c73039d1696fb62edcf80b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e472000000000000000000000000000000000f0878d6eda3d119eafa0e5cd0260cd5c9bed5fd3251f0eda5a6aab6b475ad8982b55a0c8c07b6921de77c4e23478f2f00000000000000000000000000000000181d4cc9e77cc1e21145457948923cee50db145dde59520e6ddc2da13c3380188856c220cbace98f7ac4bcd7dcbfb1812810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b8936389000000000000000000000000000000001267b7c2a91132c46ec835a5c2ea1f1c1021449d4ab3c14355777f1b7771787ca8b72b61563dc7587db6318c2661551f000000000000000000000000000000000d9f7257977b3f207e889678b72b584b84bf736bc23081d1267145a886e2dd6b669bcfd8b58414def71c27cae868f39a175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b6460000000000000000000000000000000017c223749282ef77696136edd0b30041b7743e40c2cadf8b491c2dee0730554e39ecdce41e45d647340e73bfe77407d900000000000000000000000000000000025924e40885fe566166bd4c5de6e5bdb3ab993c154ce908afeded5614cbb0c00e6ddd648263f17ebb3d81bd6a4f79afe7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000730c41758d12795c7e5540e4204e43c75a01dc6263833f8db435117429ddff6cf4fbffd6cc27f553b8524710aee9ab000000000000000000000000000000000154c3ac230c725594a3c985b7ad71d98c172de8764926e74f6932f5a5d40543b5060c5d604877e3a8df093927b0b171c6aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea50000000000000000000000000000000005c6852bd3eb4db383e9aa8c74f4c158888ada1c9ba07ab8c7b4abe9c05bca51f0065a29a814892303a42a6f2736043800000000000000000000000000000000086d733e758dd4f0f911df6cae3d678dee3500a53d8a364986d88c50576ca6bdcd10fd31f3cebc7a35f43de1d90ee4bc985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000008445e5c464c4e10fb0a10c97023c5a9b169d042971597eff4380821e44430e3790683c7c66afb89921f06199c72c87f0000000000000000000000000000000017e55467ed664833131b82a2875e22fc5b29a3808639e90741b731d4efc0420b4934fc75ebc2048e8196be55a600f9bca3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010", + "Expected": "0000000000000000000000000000000004f03dd7334a0dfbc7cd3b74a8e06ccf23fad0802e44c95712e920a2b227f09ac3d62429f40bef8241abe41575cc541c000000000000000000000000000000001092b20e8618beabaee18b4752a5b320307d22fea2125b3d23c6ad178f0cf2af7a1a1c0af3cfc669929087235e2f8960", + "Name": "matter_g1_multiexp_61", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000121d2cecb2c9892d69e6a15561688edb5020dc39fba96eac835c0577ef017191572f8bba780a608c41d53544d24a306100000000000000000000000000000000080c59704a5ef9251654458bebe25d949bd5c7793c438a50019a9a7cf26036f014fa3f024edb767d233dc09710d53daa709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000012be549d6b4efbe6e8c17393390f3cf190abe4621a16e951203747dc7faf6d6ac831582fefaff20c952502fe43e2020000000000000000000000000000000003112e26ed614405376dc1af80b9f1984439c0b67863f5cee6d3c44f74f320e66574aa1501376cf8f924efd83655a72b9ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e0000000000000000000000000000000013b6249bc071ab2f9f048531e6bd27a1b8a45d34c66623268402bb37f6be2d71bb5127461221089ffead4a9f6c708f0200000000000000000000000000000000016a321e986c6301240b1e9258423bb8f38012ad533b42cb487384d9af63713d4b84c383ebd4512145b3e518e0c935b1391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000069ae7a90e9402d4f9f1b4a8a799fd5bec30002683692a700ac3a25f8f0a8ef9fa9e6f34844a6c320877f4b4883f36e7000000000000000000000000000000001214fee37b448c79b5c3097dfb65f8b181f16f0daf54861d4e5e7297db7981f2ea20622d12acfac04c066fcd23169f0294f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000006e8cf07f48627571ab5fd1a6f988723465ea3f741b71b9aa9156c50e13d5481d66f7fe4006a54cb283c6d43eecb4ff9000000000000000000000000000000000ade4e4a949e6dcb45cfedf2eeb91abe406cccbbc7b4c7804b77d04fd7cbd91fd44f0053196bb344fb8ac1ffa37c83d470f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000006b130d66b74b99a2048127c24899ea6ccb0a53c4404f36371f30fc1ea99d02853d4555385a9fa022a552b85422daa71000000000000000000000000000000001824d4d0eebb0178947adf316258d297698ef4575d8ebc2bd300558df914fea04f0269fe67205db1e3dbbae74c0db22bbd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c0000000000000000000000000000000012ba5b9c8a86cb99337a7c4955b1a1b459c8a1a7eb6ea908bf27d5f7e41d5f3423c1ff44b4615c689df14709c703e9ae0000000000000000000000000000000008627851a30e33fecf67dff807bfc5430a77d0a85f1e4f8b790b2b072fb7b86d5e81b934ce197fdac6aea60414a616541a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001051ddad286eaf9c9ae5b3757c53e324acfcb6a1a7d5b490eb9479e337c9824bf619167bf8f2aa5c7f175da534e91a10000000000000000000000000000000000754b16cc6cc813c5c4d44eb4488b04abb659d89cf0dae5fd5f59f257cb396e139443a99b71079c5aa10f8f48465fc398ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd00000000000000000000000000000000035732a9fc03435f3dc3e31af693b1d1ae79110cd46d07541a35b956b928cb4a2de2a16cb8295aa8e8d0c74556b8189a000000000000000000000000000000000d4e762f40fcf43635151631fd6238ab3e1dcf578dcc84d462dbfadcdb621be918f1f0a7015377b5ff9c182494ae149c26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000018f1eb31d3d4e915cd1e0cec33b4838da1401c6667d8ea25209e4c5683dce96b1d7adb4feae7fdb80144c30145d7f35c00000000000000000000000000000000050693e8b9c90d12af4ded25e05df86a3e233425e2f77c7ca9e99b0868eb8d9337186113b078f8083a4273c9411ac1dfc470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb38000000000000000000000000000000000e1ca58d3eb507f977257ed8bdff474a05dee19a00818754e3a85f1cec882b8e3e0296d5c3788b101da669a716772936000000000000000000000000000000000532526ecf42eb00da76db02ab6236dc51a346f0a1271f1e9d721a40a4569d46fdb63e0211f7986b98475d81998dbf8be53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c92690000000000000000000000000000000017bcd6bf54d51fa12356f3428f02ad8ca31131a77951459d32c554e2dc2487be1bb9f10450e5d1f38af3cc7de1096a9a000000000000000000000000000000000b7b5ffa4d08175916fcc542660c85063e8420987b2e16ed2ef9464adf928a4c0b8e6d5dc870b4f00de8bbec6f0dbae3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f6200000000000000000000000000000000151e2e32203b03a054459fe391ff4a4e962ba5e10ff93a1592043ad968c9f968a6e50b5943e50815268a4abe055a1a4a0000000000000000000000000000000004bd116c6857c2f4efa087272df160b765dfdbb842a342f9cd3e5cff006030f32e5a8b60acd8a376378096743000b2fe95f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000ab6c3210ca0b70b2b3bb916f31e17b8632513b15a99c7cc61cd21181152bfc6ba6ebaf8e96a05d0d2d42a9dd3b61a53000000000000000000000000000000001308a33fccdd6cc8990c21fe7ed03bca42e3ae24bf07aebfe6878c2c8316a7a52477c929fc7c67a3a13ed811a2adda7b740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b00000000000000000000000000000000010db7de8485e5504211088ada8924386b36b7dee37170f73469bc77212d56c3dce9802c7599c83c5cc5b18883cca5845000000000000000000000000000000000ae8d817daba71325b57f81301c17f401a6870a13506de2a443602ed44b6b0824e6cb763ef556908f9b3f30010f86394f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000000fc19f1ad220ef5bd76cdd7d3ca08539a97514bb21429af5b1774d4c58a7e4ae137505fc240dd0ec01d1a9eb06a157c0000000000000000000000000000000017ce712d74d68568a945fbe2e0b21c180c58e9297f1f4dbfb0775a133832d4d8aa0688f031385190324f1e8ed65bd5378390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9", + "Expected": "0000000000000000000000000000000016cd97de498b8809f4de87bcd5a66c41c231653fdec9bc1ebae0ab3b8c56258430bb13bf400f5f971a1ec558ee66ef02000000000000000000000000000000000cf57701a5038ec26e4027c3cc5a8cc298c2d86e9425ae027dacea92d4d475130487b2743c64459990e88768d8b7a4e0", + "Name": "matter_g1_multiexp_62", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016fb67277c28b5665f1b7aaeb1bd70f679b507a6b30f956a1fdc0d522e430cb4a9c089093cfd14714f25cc9498f89b610000000000000000000000000000000018ddf06c643bd77c953a0bde77e80e77334410d76910dfb587922e6dff23e821ebbee2dd546e65591726f9743defaf9a414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d0000000000000000000000000000000011d630f01000c6e1279f330893a18b903b7246031d7d05d80d4172b08e1da182594cd42934de3d1418445a76bc9c8189000000000000000000000000000000000c3e335aba4402bd3c711569e466293c15d89f4893ba91d8690e4eaf4c7962da458471e8c7f22c417abec313c2fd223399eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef942000000000000000000000000000000000c38e3a1c95f0faa10980976f83d85954813faea27c120fc3102de51096f6c3ce89fc4155c6fc878fbd18ffb32092d7800000000000000000000000000000000178d0c64b3b7da5b6f57c69bccbf73e329b18e29e9187a7af31b9b8e480b210dd36589540d77b3041472d9612b05693a49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee00000000000000000000000000000000146c5b46bb4194ec04b5b63f09e8066f24e350cc62fce016b8a25ae57877614162f2733a5df8909eeed2df30374004ba000000000000000000000000000000000cbb312823ea25bdbfc4afd00cb65748401b47ab7dbd5a40905162c1ea676268745af11a2770509eb74aad45663f7f5b6e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d000000000000000000000000000000000ef489c4443175873e33111e9ebb3140ca0796f13ce8d34b30d8fcb7b9130ea0574754e800fa0ad15d71c35a3584e11e0000000000000000000000000000000018bd8ba66d5b67537a03030f5ae56c01e640021ec2524a2cb4b2005ba267e737d27916dde1e94d1f15b6d3e1d480ad82d3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000182acbf5e02a0b1344779f7ced01961f418fa8ce94f939025110823e5d5116d771328362498324e1067a3419062341aa00000000000000000000000000000000174d3a7754b18715722a07ecff5ee3b7f30606c3c573770c88703b6e0abd9ff4aa4bd2879c4c0512f879af95554f47316de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000178c3a28f9333be85ff364329fe897660261092d9bddb36687cdbf5a7a450f27060a3aceaf45fb8acfd123116f195d8c0000000000000000000000000000000015e0a930af79ad263b115dc733560752cbc4453f111550fe3e9448b6818a75babbd0044b9b4f133bcbf16f8fb7586055a28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f0000000000000000000000000000000007fbd9b191af6a797c68ca85df2100b898e3a4d9569c717e3d02c259eb4dff3a1ea948e56001f33a3ee1c74eb966b6260000000000000000000000000000000003b892510d5073bc3597f8f513908077814a7efda2df6051c08f7347433703496e522d70ad4093f76a3e5288044ba5dca3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c0000000000000000000000000000000015d26d3ee6fc5f98584c206466d2c1a4323f597e0ad665b289e76184770e81856482c9f45ff8c891622d8de353b172e80000000000000000000000000000000017fe0582d363a30677bca1feb6d7f16be6b07d6e5d6b2a2080d07ca306d5cf733103f20403ceb486ec703277804e7971b90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d00000000000000000000000000000000108de390a69c6001124820072eb5d9ed9eb5b5a6199c33db1ab0239c447e009df4296f5324660e7ea1133df0c8e6a9de00000000000000000000000000000000040e7b3392a116c7289644f393bfb24d84b76d8378c042d86cb4af861af42374b709cb0ff5341e3ae9d21271c32c0a5914f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb5180000000000000000000000000000000001ed57bfdd0542efe8734b0af448c025eba4d60053b7b45baf682cd310f4c2ea07e708bccaed390c2b061c89c2855c9e000000000000000000000000000000001496190ccfc4bf428706ac344ed691fbcc7b9d6a456f2653f0da421a44653d4b1e9e967954b847a4e6014df15ef48719e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a100000000000000000000000000000000147e5056444c7ea97a319bc71a3ee4188f68b517b92c64f556d22382389c5bab95110728cbb7d525499cc3b2d70541b1000000000000000000000000000000000f05b91c8d05b31ef6497595ecee6a6766f03a006b4c2da408f4d7b7601915cef64be69735c269007fa23e5f91fb07148c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e038930000000000000000000000000000000011b1b7c28754f3dc8b21dc823fe02d617374bdb9b96dbca572eaf8897f98ce9409ce8a63eafcf5308d8236bc3c18b4960000000000000000000000000000000012360ef03ee4dbf0bad68232b8454a26b666d827bebac03da314b2631a45cd365248316f72e991004d0158f89ba5811839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc36520000000000000000000000000000000008b563f6f97fee7e2852b44d8e39ca314963b517116733924d2f57d9c4f202b47fb3fdb85fbca42ffedcee290050ef0f0000000000000000000000000000000016112f264c2b3c838b02b78822d27f6351860d10da3ccb763c1650420bf22755938cb45c7566a2df0e4aea4f0281262ac395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000a8397b009cac789cfd496f4f1237e92ae570f67b4bfe7e8c80171bb9d9cb53201c2ce112473b74646a4948d7c10c338000000000000000000000000000000000092b7425031fc7c328e3be114916a06305b62ffec8e7e93a591fc5f4f9022333cc664057ff6983677cfb998defe249553ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e1000000000000000000000000000000000df6739202d9f1f13145b697d5b78ccb84845710923a0f3bfd5a3f337e200b3ce5390aa185ddbbe8088462926a7f4b40000000000000000000000000000000000d00ec3648b2e5790ca7b05ff32c6bd3249296bd693f520f6d8385f15dbaa9f808d770f9ba28efdc4aa6bcf862c17c4abadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa", + "Expected": "0000000000000000000000000000000002665808cfac4b9befb1f285c590d639132adf9d36a4fd460de0b3347303aa056a14780deaaa02072fbb08e1dea06b940000000000000000000000000000000001ef22acce32662085c53b2080df9354903f151089faffa43c5f6a1a306d2254fad240bb1ba3dba4927ea5640347bac4", + "Name": "matter_g1_multiexp_63", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000194b906ed067bab0e26b9ff4c0ecd909c6aa23b5cab3a90d1761840b784bd2af6e9f9ca570ba6643d4781885553f3e4b000000000000000000000000000000000e8a480cf75e20cceb6e1d9db5594d19849aee6d45bb3ca7c0311bfbff8263420e0278b7e814088abb69e73bab6368a92c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000019ab570a48bf15ce6f007b528d7113cf423e1c04d9af9497dac47a69deaff52dc9fc4d202649fced07378b84fa1b0054000000000000000000000000000000000e3c2971aefe89a629600a243c7967ef001ee17f9ac452a8131ea44815ecb6596f4fca4f47a316f62234851dc485fc50b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c0000000000000000000000000000000001250315bb81e9ef7de73e709f18003018fc1c55f694c0e28152fdb244b07dd2d7812c3ecc4ba362fdda0707d02d697b00000000000000000000000000000000188a852c5850f471d4ed207d5782518f189cd08d63279c4cf19c76122df0e4663217f1cc8374c7a02d99bf6d59a80457ccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000000df12b5c659c17c808d8e875a1b9c125396cdc3d8a2bd6f99def15d9fdd1fc7fcbb309333cce1b778612d6114bba63b500000000000000000000000000000000019f11577152bcb0229e168a8e97804e8e00a58fc236c8ae59c575c07d6a3c1864b7c8132f245aeec55d999d54745cab99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000004b007a33b0ddefa5ca9379614f698dbdbbfc6bf8bedaa485dc360cc759ffa4ace304fc64071e8f228a8882d5bbdce22000000000000000000000000000000000927e9f018b8cbc2f21b72f0f19994705197d4b6ab3f03e231e51b9cb3d899fd8f8b71feadf3c9be61994936535c61e8338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d0000000000000000000000000000000005decc41dadef7dc4ebd8911af09974686531907e41dfa16c857fc3a2451b96069d06ce1159d47e6f1c97cdd932486d4000000000000000000000000000000000151d369a147cae5d78eaf7ed99623675491f20aa2cec9700053f853551208fa21e085962342072c96d79233bacc7adc1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a000000000000000000000000000000000fd41ed8d5b7e5ca6a6feae98592217dbe676accaf6e73062d9de9eced8af59563f7f441a50ffbb591b8a987c47988f80000000000000000000000000000000001199e002504726f2ce429cdb3da304f9b54a933c1937e8dc39a3a416d068cf46f411b207d9c6862a50962516b2867ea5202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000017cc9741662834dcee7af988d3e4de2c30d4f9e90f2b3f7ad07f756acc793c58acb2a04c2726129d0f0c959f1d3154650000000000000000000000000000000008052061afea4c307df56a72530effa73b34beea4d731b1562de1e985ef455d39b0d6c57008ec092241262dd611ec598a2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f97220000000000000000000000000000000019adb959f4807d3bf7e0616a8a3c02e9babc94b8ee9f8898f2ddbc8fed7a5bd88e83c70c5a98afa823a0f46560e32198000000000000000000000000000000001189adca458e0ef67fc686b5a94986be37c414cffcea5b4fd44430c8d5902512d84200007a93104048160ca3f5bbb9a8892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf0000000000000000000000000000000013d55a4b466ddafa04c5690628dc29deb0ae9115a4549767b2aa22b8aa02a13f1db82dc86fa3df85a6a15463fb0e7903000000000000000000000000000000001488a03340fadc9e8f7552273699870ad444ea513cc7bb91259ffa7cdd5e7377d8fb5510adc2502fb8124d7914af85d5fdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000a847c98ccbccdec67192529c3da593f1d6de5d7dc0bf4452e4f09e93c2c406d6eaea30431ba95568c92938150a00a05000000000000000000000000000000001201397edaef2f9b89dba7f67b22088cb954f95b9db3d1c11bd77aa0dc94def6283af2866a64f0028fdd87b587669f31660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000017f76412c8e679676eb464204348d591221ba17a1c90a22b2482991deee6b61edd7520ed10b0105426a15fa3282cbff000000000000000000000000000000000c65a821d170a9726e947868d861717e8cbcd2438e4d4b8ffcee38eaf033f8f3a57af68ab6314a52952a305db54ecb361ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af84090000000000000000000000000000000000b14fc1d34bd7d85fa96a4d12ee99a6d327347dc63608f94bd750e2096dcf11066e384ba3c68610c70dabae795e668c0000000000000000000000000000000004f3ac3e885cadfaa565b1ec15cb81e3fd4d561b2a8d92a9287bd0de893563676118d34a9ef3bb3112aa534605219feb2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000000979231339f20ffaa38ed21cfcef923fc9a4ff77f7d6fb4df212a530ff456a32f50a77d2e7f6d87c4a58270c006e68070000000000000000000000000000000011ff95871a91385ffeafd8a609a0c562bbeba71a110081e5db6c8035d8176067a528f4d1c6d7dad43b3bb8d090077e1357f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d000000000000000000000000000000000e2c7c67fd50bd2cc8ab18808a69d62bc2d3f110ef49a02259163f8fb152da6ca9cc771d1221d7719f9bc349e68594120000000000000000000000000000000008393769453eec7639d66525d6e875bbde7a4a28c434c82571468d496c4313e12414f929139c482569c003a6c0dccadf3017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f0000000000000000000000000000000012cfa8448935a292911ae6fc175f3049eae5e30d714b3439f55be9970ca959f218157097bf9837125bc8f772968b0d52000000000000000000000000000000001747193c5402daffffe4b1ba9034231321d01966befa174f526014d6c27fe3683eedefea8690b95c8f71fef1152929bd08bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd", + "Expected": "000000000000000000000000000000000bedee9e836b3e046bba7fca7632d09a5a25fe1f0fd25cc6ae1d1e07d98ec52742a60bf346285488dc84b2360e0db58900000000000000000000000000000000071ef77988eea20a38fe33564689a39a7113b1715dddc1b212c6edab6bdea8de54089eb7b49b63296792bb2f4aa68733", + "Name": "matter_g1_multiexp_64", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000b7db363585b0061a4707a2ee67d6d7220e9209b4eb9a59c02aa6e177c948057826780f292dbdd824d67ca9f78864cb000000000000000000000000000000000a31f49bfddb5c48730e1cd429f128a540ff44b6a5031e7975ec0c6661f9f3f2b79ccd2d13cc1b50d50ef9c7f658d412cc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d000000000000000000000000000000000d4fb266e9fb18590037394b18971cad5840bf89047dc11e52c90284642be7e27007c62a1e331a2f90ae67313efcbc0000000000000000000000000000000000047b518cd6a7d7c4d262d1f9f5f11480e30c626d45fee2d6caa274aa1353035a3c42ba96b5875de36442aa5d4b92d6d257b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000000cdc9bca5cc807710948d5189dfadca2cdfa6fca5496234f13024efd84a37070a2fd51a609c4ed6aab54f8687ac9700200000000000000000000000000000000011bc450e4222090603ccfaf7c1dee67bbd59aadafc3810d3aaa8362fe43f48952320e25bebef482c5d21a541400df5a03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000000015d20abf274edf0c9d45c2675e4af7987e98005b2a0d128ba7df6b16b88784a7134d37d0da2da02557f88d26de33f00000000000000000000000000000000190adb20cb0f5902f7e92f79dd6e7d214eb892834611ef222e9a80ade4c7cf96e0b5f9382b61715e1701c7e9cc4f4ba5976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f0000000000000000000000000000000017dcf175327086e058e4696d689f2e8a167aca5616f2317b7673850a2272fd5742b70eb362b37874d573cfefa25ce3ce000000000000000000000000000000000e5e1af08f6174641aaf4f1584ac40d53c393314dcb1c405263e8689558445196371e2858a4f44d605550fe0f15962223aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c830000000000000000000000000000000008456d980ecc64b04a203d61bdb78bad67b4568b2dd9a123634cefbd7f7077cd9a4c038c0aa3654915c12242dc594b37000000000000000000000000000000000adbd582b0a8ac28ab21961476e163255089c2d362bfe9daa7007a2c9d8d261593eab22a6bdaa9740da81efaa24cc3d5f9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000b02d326ecb5c04ccea4cc3d29f82117f3d97f788b8e70cbb346d43d27e674540c7a94d242d290e55d992eebac546c9b0000000000000000000000000000000013901f8dd68285d73093c30b37419ef8e4b28371474a040a2ea293f7274ec4d6ced0f32686405205324740884306e3a693be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000181bf2fe4bc67a1d10335a0ca9427f603610646de485a7cf039f0706c0a0858ea694db3b3e5ca85317c98b5cd75865420000000000000000000000000000000014b1b652e2ec7d05956705f692860b83713c5cc98c6532b3df50259f27f92d485e8df846883a4af4e46020ae54038d955d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd3000000000000000000000000000000001084f77ef23ac990b43363db38d652f0e6dc04a4bc395c8018083fae6fa6e42f463af7748d71f65b14f94632ca0eaaae0000000000000000000000000000000004ebfd75ecc9cea5e49082e1adacf6b50e4f14600d9343f6459900605c5f36ee51e95408a3005c0c1093e41794c282a0854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b2400000000000000000000000000000000062a062d2ccf5c131e1278a63e713ebcf8a221e429b52b3a7688f7e68a12558fd0f584e03835daa3988233d6a84010310000000000000000000000000000000013e9330d29635892fbe0742d1a8c96ef527b78ae389385a366b6dcc6a04b8cd1d5b8bbb79ea649179e78fc061d23cafd6f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000b131e0623b7f30bad70145cc4964257053f2ead992d28aa5b24c04bc316d479d077af0ff409cd395a86b808bd3e4f02000000000000000000000000000000000380fe6e79e5e0a399365d73244f2962facda8b7b381c111508819309ec5b1d3d8783067245dca26641a966969dcd0ab191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e20000000000000000000000000000000014971f46efae601309f3d16c15ab5c46ac48d2199431fd959cbf4efb768ebcc4f410fd66de04d3280659004a6b54e64700000000000000000000000000000000113e6438dd8088e73eed84d24ec286a45ca51f0fda88c7ae3f1e6a2195f6b11877e606773bb9a8db19dc92c3b0729754b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000006fceb59d8baea4a10aa9f1e825631e28bdd379189eb464a3c6d2482319a09337a78173f9207a58ce15bb1c518b39328000000000000000000000000000000001609e1ff34ad2e4bea4cfc4a993d8d52a1a8676679c91544ded432adfd7fdb5c016f8d825af1c6b8207170d05c10e04a7d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000069e48b113b822cdfc02f2f0efa02724193a5f032dea902b189290db91c6e4550fb33e2915eaa8e56ef329d6c61a0d95000000000000000000000000000000001426fa2fe7c160e8e32c3252383a7c7967b3515c3f76eeefaa5c76f02b3308d86ab95f9a3a0dfacfa6dc12eed2f3a5e8a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000000c173c6c949a7f21df4431025ce16c18b1008c75b8b1b23d03122c7c6ef714b5741804ec7aa5ac40f6b72a1a74ca5c340000000000000000000000000000000001b32d54f8f9839dc39e08bc6a5f0efc5db9bdf487a60004ee135c30efda577d187d9b9e68bdcdad558f2028d66e935cc0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc00000000000000000000000000000000037d1cbe4534b82ee79b2c957a6eb19d18dd3f3f6faf3313b0ce12a98953190aeb55f9d494bbac4f56ca6986c65f7668000000000000000000000000000000000734f505be94516149bcd6302a2c9f2f9b952c9e614c8e90b5466073a7e734ca203fcca242cb97abe1c532d7f59a783aaf833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94", + "Expected": "0000000000000000000000000000000012de1cacd791ab81eb49d2e259f36abfd90e8c7ed4f9f23a4c95cb48cc7bf9df233951291342d1266d016a7fcc6b9d53000000000000000000000000000000001938d574d02b8802a45ddf8488340f2397e93125678400cfbd8e64cc3805704bd5f85f8fb2f64a96ec1089b03d9d1c24", + "Name": "matter_g1_multiexp_65", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017387ec261c6dea7bbcaf4537182de1620adaa5842cf52c8b5b6cd851ca3c27abafa584547db7366455281d82d3f83ea000000000000000000000000000000000246dc1cc9773db7151e05d131398146b28850e97f6b13694d696be374095fb153b206723afcafddd4b3b56bb15bf778b16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000a909dad5029834df0202c298a577f897a376b205812d79e0bb58b91ca11262a766dc396f69fd2b199dbfa52670515ea0000000000000000000000000000000003737873dec25f011b24543071a61590646e4319a2128eed87d40193a22c47b1a6c0f807ba3115a7e45823e5a4bb433dcf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000002b959df6a1badcd306209c1f3c4c496cbfe4f00995cb4403b04ffa6b9f2c8dd9875a2747354a653a74fbb605eea50b00000000000000000000000000000000004d6b15939c8e282a5995c8c0b67fcba3171b35ecc039fcf32d1e96671698d8a9fd2cbcaa7019cfd01e56d68cac64fe51cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f421290000000000000000000000000000000006be65e97560a40394d9295fac0029a0c889bf803f09926359a1ac40deb7777cea7dc5d2c4a9600328605fb994f87b5600000000000000000000000000000000128249d2137f7ab1c5622a8eb1c59ee8ed792fe6b09e4d868c9d9ba900a8d28bde5b783ca591f79e1d729c99e10d5cf6d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf00000000000000000000000000000000002244c1e55324a4aeaa07c414cd3f9872290e729c1cf1c05a5b1de3443e12b2335cd36f0e84f11f04b62af37005ce0ad00000000000000000000000000000000151684aed084d38aba7127434ea73e63219c4f5b4b92017142d19d0330417fb2806e31440e0bb7c9fca2bc8dec73072f02e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000009f0c959d995af6cd0d45750cb35a28461d0f791e59b2975ba4edbd7db015858b41b3b7c5c2da0a4c6a5d7b4e855329d0000000000000000000000000000000012d495ae3096c2399149afd00f640f8840c3f8e5dda5835b62ef0dd8bb7303f522692efa72c37190bf6808ed3d4fe8e89b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e000000000000000000000000000000000334cef31670360b5ac7550b55cb03b770660ee79816a2742c059b2ed6cd9d5c53c5ca54793a9912ddd7603d975c3f58000000000000000000000000000000000144f221db562b0daefb20238a527a10ff1ccc279eda86723668f8ada40b41a2825f82f5ee5d619fb193b9c2b4180d932f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa0400000000000000000000000000000000037f14fb2d51b25cc04768d50fe26c1e156a3478b80e32da980f7e8d5692a4cf282f75e5d8be325ccb4223c7ec2c04af0000000000000000000000000000000004eaf2c069c96dcf18051a2c1d7ea876af67bf344070415894c07b3dd69330d8ca18e1313ff57d83b70e5cda3c9ea8582d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000104c1f5bdf874c91020d410d8fe74834cf15f341b86e66ac693003766484cffaef2c57fab5888f02f5ebfe1b9ef2fffd0000000000000000000000000000000014a2f6d185c2989ecbb766179c0b0d0713ea9714da2ac555bebf0522ff00766ea7e39c8237f8515224fd096d2b1ede34efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d80000000000000000000000000000000003ebae6a494d46ade2dc7d4630a420b519df7086b57a33da178616d4242fc20e4d02d38b5d00675d2cfdb51adc1921f6000000000000000000000000000000000edc56e6eb4aa8556225d928408702042d49cf3e1410e1c78d8ed5832ecae449d17c9d8f2a89ffbfaf01bfcc85ebc1669848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000000f96864832e7a9602196f0abba78f456300796d5afc18b0ff0c5c23b61865256fe5cfb960bcc8f73231c21b1084cf04000000000000000000000000000000000c59dcca2249b5b01c1b54be0e4114ae8228bc150e5ac7593bdf96136cd7cdd7562eb936ddc5c9e42bd93abe91bac5b0cbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000422c390e56fa27e3d7d5da1b2ef00a29d5340026becefc095d4cfe830208d3b94cbf5ae6f4506ae45d04764acc8044c000000000000000000000000000000000d1cc7c147cbedefa854fb9764352a9689fd157cb2540fe070ad7f6f3eaf761b4670ab9334de4002fa811aa7a01aaad479eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d6300000000000000000000000000000000018000e31f0ca43417865a1cc128f33383106f5bea71015e9e77cc5320cc3e5704e437ae8d84d96f2c4530c41bfad29b0000000000000000000000000000000011a74c3779c8f351d39db6745210972f4f299009afff643e944f30dbc4367e17271c688e1858e6f79b6636787fa56e6b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000002a6c7367526da989ae093350b7c1ee9013f977d6e75563f996e1f15cd4279932a3e4060a26262f27403966a7e0111f200000000000000000000000000000000038a85281b09e5e68d7e31bfc323c9c250b42248cbae47f9c018d72f3e69ec572779d7f8fc6ed3f027499741565274e5eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000b788a0d47da0daa1f0d802d340e68f9bdb5ddf91875732b4ae82f1a89ebb5787ec1c9f539b82e3c94c36a5df4ddb4ad0000000000000000000000000000000016f46ff55e9f1e19a332ba4ba43d66d2a11a2728a484a719ddfc9e223b54224db55af162e73a8f5c3355f0127a6b7cb652b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000379d868d91304b24e19694937402bb685f064ec5a89b49e243e2ab7eff5ca0a2023af9828c4ce9f768a1d6488c10e110000000000000000000000000000000011a9b9432ab253d47e8dff776c8b5810ecf7f7aae2ff36ce06b87436b4e20c22596c7713def3886549a36bb535a96fd1e83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384", + "Expected": "0000000000000000000000000000000001a50ddc4d4542aae264dce69f5f5e93353a9eba30b177b33c2f74ef61149a301599beecc4d0f78078dbb9638eea526200000000000000000000000000000000160a2fd67df885232b06d3dead7ffca93cf0434a3e9a5412ed3b4c342819b44aad5385bf5ab60bc2b7191722d1372183", + "Name": "matter_g1_multiexp_66", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008788a699276abcc2d8e4a35a9d0ddcbd8006a809799374ffd56ee8afa1a89461602d92fae6eba7fdd4045ba34d917e5000000000000000000000000000000000c8e03ca0da00c6829e2d7c49360e67e46ce12e0c99cb3d957119bd9c8bcac8e03cf32ec71db2a18568157f4b44cd4dca4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b0000000000000000000000000000000001373b4a0653f48c205b36bc50541a43abfcf35974a584953bbc40f5cabdc3ac2047bb86267cdad1e8f00766682d2e6f000000000000000000000000000000000faa8c977b4db7a3c9e65d9cd5af4ffd2d7d67fb038d92c1096124312a98d94e6dc3f3b8de73eeb057cdeec4bc0e0482bd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000012dddf5b96d0dfd2fd619b634b086ba5d5f25a53e93938559a7adef7b988749ca27d14f2ddbf5a9e7e6c1914403a45b900000000000000000000000000000000044b5c8041fa805cf2ec5a243814308369e5af534729cc9608fd17583a48132809f507cdb5b76fd6597fcababa865ddaefbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000017c5807458fbb875593ebfe83c49ac2493ddaa15671a59032528e0464360c64bf564f9727959108940ccbdb8d01f329e00000000000000000000000000000000121dcb798111976daed483f4efc95f968f5212cdfaaf0497eab0419a1b55c7ee4e2ea26716d0c1a8aded4804228b8ad860d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000717296a20594f940a05ed3ce4bf2b7779c428b33a297087e08b2283b33228a7d4d5b9c49a71ce036d6f2a078d8344540000000000000000000000000000000000fc78f64a461fb66ca081ff4d67369058e57e5ae0e284562161fc3244bae0b9c70ea6abb2d0da6cea4942530c64ea0e386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee8000000000000000000000000000000000584bbc0c537e7f37ee64604a134d5fc21d838c51a89c608ff9e3684357ed7f931fbd4fa4a5a56d20304d6f6f072316600000000000000000000000000000000191ea3bf1016b6402dca2856845017dc49c74d06bc3c5f10de379e04302c469015f205cfc97fa142727ba7e2439c15575ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d000000000000000000000000000000000f1ff007860ac58bb04d992d639a5f882c3c647e76e2d6d96888a55648f81ad8b7edb3dc2b0e56b6f2dadca73db7cbda000000000000000000000000000000000fbb952eff64505e02e0ab34875d7a79c72ab724cea7cd8f28df2578b50f78601b9a9eb4170e1b7e8d94d9db252e23c592837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c00000000000000000000000000000000011dad65f38b4c24527ce87f8893c8331a32a3d058cddcdec9f8708a3bd1e31871cbdcf944ec14d5f101b8d138b2a46c0000000000000000000000000000000012a6981c5100177e643dc421c5917896455107c5995b1e969bb18b4b2752700a18281f732530af9684db180290dcb138127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000bb4dccab7abf3f5393a338a3a07fc20d337ba2ec3b33227e8c9a832900f347d582d88cab123dab489daf471191538b20000000000000000000000000000000008589985e2952db000968a793cc0fb5bd1764ab1ecdc6f278a11dd4a1de87823016e14e9fdd682e6c489192b154cb997ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac6148900000000000000000000000000000000056fd39f2a5356870a3ebfedf35769710c16b2f2eb4a061c936f6de4f9001990769795b1c756d7c67623ce3931ea1b5a000000000000000000000000000000000b7fcba295d34fc38739c4b36689653731fa46e6029bf8e38ccb6af5ae08ffc09c86abe0de62230844a66cbde876f52663da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f000000000000000000000000000000000039b08e7110b0d17c41709378f75844379c662f7f3dc480bead6bd4996de2d8889f458aabca142d50ba0e34c0c327970000000000000000000000000000000013363b0da7c7dd343ffcf6cc5e9ddb5b51480b04a472c38f90ee08cc97507f5dd665e15a160860c6df4dfec154c1504bef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e00000000000000000000000000000000034edf693e1b201be14c496860d508d12d9180b62cf3bd2407b8ff95b93da67dc0c4c43344614dfed516d7828ffda4b20000000000000000000000000000000015246f388664b1d817fd17831f85d84cdaf31212f093820835f201c3fe6ac99d67cdcfdda3c2d74d75d5114e32c65cd7bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f000000000000000000000000000000000982b8886abbfe18cfaf4c0e16c2e7045973f5efa27e5cdb56443a22f5434e2456cad041bba3e6deafb072e5fc40f10f0000000000000000000000000000000016a45f684caf0eec143cf8f31ed5111750d8c4f1092651a471cb88cf534e81df117e3b0e8238270d3b03aeedf04d7a9f70363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a0000000000000000000000000000000016d13da2900e2b2ef8f6ae295bf16d100d451ac4709455c55323988c71ea6aef694de0fa5a33cdd7fa2512d3548e39a70000000000000000000000000000000005795677001cab950d1a7b802bb14f9203036f15fb335daa5f0b0ece4bcfcd3b31b581b439da46452e4e688f16685e37997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e88000000000000000000000000000000000b7d422ac85798cb5ef5548805bd6d3de20ada4994fc38355e92cbf0d0c9da356a5e9e1674a50a017643f652f71226e8000000000000000000000000000000001715616f53a501acbaaec470121caac29827b6b7bfd7e689d8e48822d2c464ae50158662e69c1c232ecd09f5ec946a7a5ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000010e7530ba600fa531878ad0f798a0ede2d025f149ca980bcdbb0e4316e8d2e7d2b248619369e36d21dfd766aba5918070000000000000000000000000000000000ecfa746f1cadbed34fc1ee3483307de400ded69af4a7dbb598802b7908495519b0cd4c1fa98c9cd8e82daf8b3e836e03c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26", + "Expected": "0000000000000000000000000000000016bbc2dfb25ff6cb9cd03b0cc91f53c6ed9be933746d59e78c7251080856cf47c12bbecda442840675b03a812aaa5d98000000000000000000000000000000000555b0683640ef1795c918f5d10e8b3e07a09030fbf26eb34bebfbeac34e13c93ecf53d54dec5c8654cdedab2fd60570", + "Name": "matter_g1_multiexp_67", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f08b9765910d97ac42bc31d4b4c8bad5f3db3fe5374f11ae1c08af41ee226bbb4b0869b039fa81a935025de11b1d1fe000000000000000000000000000000000ea29999ba91652e2e6dfdf77595b44da8e5cddf2e3ae6c782dbf1f972717833d03478bb8651bc0cc7946d813371aaba2849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd000000000000000000000000000000000997b39892bfe0c67c296135573975801ddb99d06de02d96853f44336fdaa25dcfe253708583f415d882115ec68dbaed000000000000000000000000000000000a88e2f75817ce91c7dbe365d67aca52186b5e94c735e5893bef6aabc61f015f854f9bd110d3201be6f35147f9f9b8fce6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba6990000000000000000000000000000000012a730eaf214a874448e654a06604c4b9218f163b979bd3700b7a7fa3856b814c380532afce59b6253344da5bffd684600000000000000000000000000000000182fb293f9a63c705501aa0ec7ca72698d7d4d50af3a0f68ee849cd3f82ac24aca2e2ee813f68e708991a97e58f2d03d8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b0000000000000000000000000000000015bbe08935721cc6199f9255379589a4512c178bbedf69c82a0d9cba22b285730d4f27a3629d92574b2c24dbe09300de0000000000000000000000000000000007aba01238f2c4ef0192fea78fbccf2e669f802a2822baf067632daadbc1d07e70095c14bae959a0f706092b0be10335c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e40000000000000000000000000000000011727d6d6cff667f5bdec92a3b502f9d9fbcded2ef12ac058ac51ccb4064443b7a2671e9ffa2fefd9b121d89bb4ded1e000000000000000000000000000000000960f8ac1e52246529fcc6f8f7cbaf42677297c00022d312e0deb5fc45d3685bb33fd68c193758258439864ba4a073e5650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd50000000000000000000000000000000014b3a9296c87b54f8f51b935a8d9ec0af44d711e3109e75fe34f07d0705e9ebf0ba5e81dd8b7e3c4b4f862570637a7f80000000000000000000000000000000005b834857b8629cdbf514e5ac2e0e2a45e4374c287bab5e4c163d669e7b1a36c72cec1ab7d857e28f2633a6e5f298f55c067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ad8c626ba39823a33d17a4f06cf17d29e9e0ae3f28db0b369fa0bb4b7343115fb3ded39862381822c3b2d74ab7f70e800000000000000000000000000000000117230d8da035f40c181b50c12370f159748955f63ee1eb61e8242e476575e9aaf16bd43b7e79a35ab4e2da20f43fd92b448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd00000000000000000000000000000000027764a17af5328811b305c21b0fecc54a3f225eaaedbf453ea4c0724fdbd481873d84b1a7ffbdc7f1cb07c2d1efaf5c000000000000000000000000000000001090ec8d750ceecf682de76d4794f9a8bbbf3a3f4ab591fe882613c1b6db0912696974a1f2ce349bd8c79acb4891719d441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee350000000000000000000000000000000015e707430eae84b75946f21e1fb0b6ede203b843671911923efd9674421a92ff13cd900bee1b27d70b8e8cbeccb165930000000000000000000000000000000001263ed28f531d8197606a038d7d7c3e1d732690cd69f52533470f6fbef193be5e63d5af0dee3aa8a73a23253533f8223020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c0000000000000000000000000000000007fe70102db7df6529f732b5cc2b1caef0fe03af9824a5097922dc0b07e5ff32bc195fbdfd7b5e4b2bbcd75b1badc6ef0000000000000000000000000000000011b40afd78bb5e835227e5a08f94f7c70b06dc010f5a710a025f589521543eaff27d789d4de10fd4020879b45bc0a9dc82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000e4df86963d375710c681c5b3910fe79446e73e00613bd554ee20f47fa9e2b0cfb6c14a29ed6dab0a56c49708fc624d80000000000000000000000000000000010029bbd62162cbca140c56354ea070ae3f1028e438c70dce31e7bc8691541e59e9168e9b689c19d177d4fd68f8b1081338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b00000000000000000000000000000000097f3f61b164193da313d88429a4f34b0ef2f864ad8fdf7183c3e1da02dcbf0ddeba9bc04a7594516e6255ed59527e110000000000000000000000000000000008133f297b8da5dac5e1ac3db3073587b92a5d821949968c125e5c9c79a19b5945ab47fb0ce5d6f4269231596b157826d29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee7502100000000000000000000000000000000028cacc78001b805c3e43e92fb8c4477778ce81fca9068240e0088e344cc8201ed5bba52e7ee09d5ea6f982f30d6ea2e0000000000000000000000000000000012c5db0995324657574a27c48313674d2ad3aa931cee78ade96408c5e04e6f5f8eae88018511ff156bcc787970ec40ab675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb25000000000000000000000000000000001768f68b0ec15fdd37c3ad9445e53a582ab5546f9eeec590b84e11f5a72585eada71129d1b93a72b334bec4df57ea4c40000000000000000000000000000000004d6e137e66243b56bbaaac98717061b36545c1c3e24801e6e054bdaaa6d28d641821a51233175f5e5823b7d2b7b42cc544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff70000000000000000000000000000000008caba9658e420fa17950c995efd00447bd5074af9b57122240d4e709229d382e371d7de867005745a35a2a7d68fee8200000000000000000000000000000000072e0c25435616f157284b48fc8da4a3fdaefc4f6d484e071cbe648fedf30b5da4457852d7715741615317e21110d4c2bb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a0000000000000000000000000000000016c418a3b3f054188d6891ddadb19c00ec629a3ae0f49cb1b6801a9db0afb1b5e473c75cc8e9f352adf7ce8ac738ae0100000000000000000000000000000000110b8099a39e40541dab01e10314a0cc10fd2277c8766c7c73d32d7d0c6edd3ed3984c8bce249de4776920dfa28ee86994a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc", + "Expected": "0000000000000000000000000000000011104fff0cde20e65e73185321d26defcbce9c9d3c27923a2366730c1b560da80066734c61e0c42bac1da45787f39e2500000000000000000000000000000000066efa830182669715da4dbafc6434be27a2e95140cb39738fa75cfba2b5786d27fd727df4e9b0195b49be2dcb9c2c1c", + "Name": "matter_g1_multiexp_68", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000188c13fde41e3c7d84ef3b5d1fa869dff4bf02cc8448ae49c6b72cc005bd06916a5d0a74fd770bbdd3d2c58839840095000000000000000000000000000000001637ed432b4ac6b5021aac0c9d5f084e1f6c541c101a3d650861f7d860572795f04e986c4a890ea0ec049da7c6025fa3f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c27700000000000000000000000000000000063a1afe2f64f1d04f7a5aa727cbd0e9dd9b66234120118db1f8fc3b90ae50cf493c3c4a48949441cc1e46488972d39e00000000000000000000000000000000049261c42dea531a6e8fd82f77605ad0cc9addb23e429f03f1aaf2fb8d9dddaa89101bd5b5b169dce793de9bcafc3b5ddcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec0880000000000000000000000000000000012ecb0f3bb6fbb4802479611a25781ab09c81ff7175170805ebadbc5f25d2c40bcaca855ece57f481160d49af008d2b3000000000000000000000000000000000bc4bccd65e010b69676d3c226057528dbe08271d65f83a918b06969c1d5303cb7383645fc19548eadb83649ecc54a551d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c67000000000000000000000000000000000e3dfb86c2eefe0b25f117484a9d693702496124fd0dda80830a4e917bc418a793519dc269fd4932236f73506ecc949300000000000000000000000000000000140faa4b38ace6e80e5d3fdd57079c215792672ce651563eb013a90e66665dccf6bfc9f9df145d34894e3972eb524f86310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a160000000000000000000000000000000016e70554f8580b8e9c5e421c6a6495df7df846ad67d5d4334e9aa89f7e3fae505a2d335d21624e66aa542dccf38081e0000000000000000000000000000000001090383d5f42c056c291a4c4c6127315849c647783a556aba3dc41c52545549d67560bdd697fd1f47dace750483ec9b72fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000e8ba15ec58e5de08935384a3674418942311beff3887d7b5b81da0d03348791e4b17a06397e33e988ac6719f4d6f5c300000000000000000000000000000000159841665c915844ed85abdec0c1e78f178df2511da4d3be989f27063a8e572fe746b20e3aff056a63f4832d82a7cc75aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef00000000000000000000000000000000167848a43b68c8f4c205613e1440f940735d7d44eb1b046e63ce50fe8d7acc5b2c020fa936d6e07347a7858be57870e5000000000000000000000000000000000aed7f9b7108aa4e7445be41bba256667ce7587a867b9b8ca70d3c42155521ea3bebbfe01bab038969721364eb758be10227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa8000000000000000000000000000000000699a81c47bcab8342b11a207af072cededbadd374aa79f6b401e4bd5d429a0443234522a8955b3a62a21ef6697410270000000000000000000000000000000008ec25a0e0dc6a3c8906a1b3413f522440d56f67fb780545fa022026c6faae016108cb6eb23d6d6d519a4aa790327ae6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc0000000000000000000000000000000009c2158ea44c3b590df30e15f97ebda263670c1bba0d97ceda7ea674af0e61f0b5928fe0bdcd8f18efe5340525259b4c0000000000000000000000000000000019a5534906413fdacde78ffb03e6564d8beaa155f86e4f19be2188854a8709e82d2ade21621934c1aef8be723ea91a141a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec000000000000000000000000000000000165b15830a84e786d563cc3c5117a3e7dbe9dda178bafd225503467ea4c9aa894294c4fda58734eba9864796974a016000000000000000000000000000000001285a2be50f38fa6a068b75386d468d8fc1c11405291e794d5aa5157cc81d7d66c1095f2fd9289f1306f74596e9b5c21b206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000019d5819c1c4f10c83ca6f1596e6cf9901611c1407d6d7abab989333b37a8c21cc3deb039722a51e2dec161c38f3ce74200000000000000000000000000000000136d05ff33253260cbbfea0390e78cf66845afb4ddd0b684b928da017fbdf6b0e840431064e6e6d5bc8e417a74c811ab3a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e000000000000000000000000000000000593e66a323cf3efa13fe19cde7a3c254c90b23bc836e1f437f4a4b85790f325f0746147aeb1d0447022bb138178bff50000000000000000000000000000000011a4b1222d0b49a27e66cd34a12f252296ecd1aeda435035f06c059aa3e6ba69acd1ae6d7da394f32ab78538f4e50a351231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa000000000000000000000000000000000613bef17f6b6b39f9f6bde785a82d2e4c368ef231d8cb89940059ac2c16bdd707170b660c0faef9e927ff7a72f6712e000000000000000000000000000000000fc85913ebe30f0af146df556c6984ab442b286fa70ee00d39a802f4c76c3e41cee68802982ea42fb25d4bb04593c0b5393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000003da5997b7b3677f6cb03fe969e328549b1c0b083a6df457a70f1276d10e01d65feaa5a36cfad19dbe41cad9eba2fe73000000000000000000000000000000000345176bf6a03a49ae0b6d89d07548ed47dd67dd620e5e29066d09a00a7e3bd4b7fcb79b114a046dcc0c705068f71b50d412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000105ed7acf8c7c116842dc159553499aac7b8beb36dfd7eb717c571ad4ee1f86b82b736b72c2936925afdc3c739e0ad56000000000000000000000000000000000618b8fbf8a2aa2d1030c6304655b1df3cf8e8260b7b2d97639bd857d58606d0eceff7ff0fc1a811396552719407daee5b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f1000000000000000000000000000000000eabffb8ece92d4b22ee47560984b3efc33913953dcdf5e22771bb8db2cd8eceea21a2b14d70b1d467d692371ff499a300000000000000000000000000000000143282a2cc502f477be295d5fb2ec847cc988e43f72be848464eb4c1dcd0b1ab66a6cc30dd4b465050f6c37e8b8e08a7ab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5", + "Expected": "000000000000000000000000000000000378217eb65cf5ff48e40ebbfbf03c307aabb63955f755a93bfbea0c176e90dc986a8202c6ea4e9ecea176068cfc62440000000000000000000000000000000012ac5e1a0c75ac5b96af3959a65eed134dac6b35559cd923facd2062ee06cb3ae2018db7d27dad2927b3e9c2e3dada33", + "Name": "matter_g1_multiexp_69", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d6ab2022d950cd2ad2f0087a079e435634a1e24008d12a349836cb7297defe857cadf3adf061e8b55ece662dd36ca280000000000000000000000000000000007682f1ced1ac2aca6ee9de682c7a6743fd32264eb0a087eb1df7c520c5748cd598be45213b398b073dccbb6bd67b44c3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e000000000000000000000000000000000927b5590892a4b897ff2d6ef6d5abe32bec8233bc5f35ea9ace2ec516037a8f3d162b0161c91d4e06d80d73528a6ba400000000000000000000000000000000064d3d8340eea43bb2d54dd6f5d9d49fc2275ca1ae7212329a11ed9a94c70c80584cb6ccc1eb653f001a1c1c4306e702b3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000322791d0e53364128288e40b621e6c47324dafcf86e9a8590a79eddc8d3e6c9d74cf9721115550e7e33868ced39cc4700000000000000000000000000000000112a246f82756d88f30e74b3f5df21e18ffc9cccd713e6509572338ccb4f52cbc0c3a6d5b5c112e304f90ffb9179238521953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b8250000000000000000000000000000000010bdece8fbaa604439e942e2c78aa5904cc1a0532d5bbf624794d3f10f4b64df30838799e374982feaa7346c039c08ad000000000000000000000000000000001085372e79e1046c870b1d49a2a8ea83bcddd6bb8718c7cb340dd3032739319c54eb947d518c7e17d6e603dd3539f269505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000001551cb2abe299a01cfba81bb306457b662ad57858a30d55e0ae0c0f5851483123c388ba06ead8ec4fad0b1e4f69ddd6b00000000000000000000000000000000159e5ffc459d38a6b1e49b30647939f37c0d4fc02b83f9dbac123d64535752977005e0cb1232ebaa7cf0bfdc203ccaeac4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f0000000000000000000000000000000004111e81afa9fd09e39df891cbb99d9b62205777bebee33b2914e24570db46f75db5dbe2e9831c50f9717dc317f05ceb000000000000000000000000000000000a999eb350750cd505ea9de43945cfb0c9c4ea412cb0f0e769e62e47d08f8d50392d3a5e821f1e9c947990e6398b5ec699762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb000000000000000000000000000000001496d3b0062e9e7166d777d90553545ee7dfdbdacb355fa7ecfecd65bcb96321aec0fd835b32c8bce462c87a2b52a58f000000000000000000000000000000000ef77e6ddce1e0eae50a1c663374c31a0c5846d6c2d777bb2f4831ecc806ac28591c3ab0222a6cc7821a45ddde1ce23e298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000194bd82f02047bc08871e431ebde41327a60e838d3a1ce6eb5470ba21a9b863025c8663f7d509a73847ed41515fdd3ac0000000000000000000000000000000006c9303814ddedc68b0047b5b2f0333cf226908dcb14ccc0aae4e14456a0c83eb4f498d559a649bb64bc78900a788a4b177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e573000000000000000000000000000000000736fc761eca44cd197ec6fc680de349f96e5294e42648825ce9262fef91766a8d7a084e5b598b5b47d947548e0c61860000000000000000000000000000000018eedf050da521b9af0ce2007cd664e2760320056e14ddb162db5cae78ed7ec859bad03fc60caa06081f0c24bb130ea4cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a990000000000000000000000000000000018a8e4887f0c08dfb7a741858580a1e0ba7e7ee1959284ad0955beb186e84a5d503ffe4000d5a8641575540b6b7a3885000000000000000000000000000000001946ae0b124fb60fb4dd32181783564dfb8ed0616a220d5650fcc1f6968ff70dc74535c71b0cf1019eb038c19cef0caae03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f5030000000000000000000000000000000003cc2093935fcacc3fbe4429868c7b31fe8c8b12c1184e2181dc8da4d56b9b3ace85ad8d6b850deccd047eb002acc8fe0000000000000000000000000000000008cebb95902576d96a3a257ccfe76bc727174e08d70492dbc2132b9d5f534de3b6a7baac2d90338278064565aa67b22c804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f10000000000000000000000000000000003a03e6c183afe6aae9bee030f46086032e9d81fe337e7e1c77ac6c903fb33154bebdc15e81422f057ba1853c1f7cf110000000000000000000000000000000011f5e4fff35ad1d6e2d2d4e30ddeac28432eaf13fc7c35f5a90f7f8a17de0f61bee21529b3db3633c178006f5c5fc403ed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db000000000000000000000000000000001986f950d86f35d45dfeba6c3e484a6da296ccda2314d03adc37bdaaab374aa9011e07e6c8fe056e66b9204c5e16fc990000000000000000000000000000000003220ebcac8189b30f6efe6051a2be1001b85a7f94d9ce289bf6e04edfdf2ff17b17702a1ce116445d763ed1c0dee645e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd50640000000000000000000000000000000001000e0934c04c36c621d9b308565cc75ff58f6c1c778b8e0926b4d22d58025edf8a853139667ab3d3616c33d8a98afd0000000000000000000000000000000008776b843fa3b1449a0879616b3a37bd5eff5c809c077fb0274fccd67d645439a79a410fe2c2db44f52887ea7f20c6062f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000e7b037fccbb3fed299960355ff2c6a51562814ac797ed6b4b770ec565bae5ac998eeba19819cf2b3d4e91591e7f051f000000000000000000000000000000000143dd07288b59a279de228ea59aecfba3275a87fd8307252e6b5d567bde87088a8a8f52da57cba4c0fa0e2aed423241e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d730537000000000000000000000000000000000b41b673bab477cdb21ae5f1c04922f2b8216d7a1423a6f6b86d4c33f0b4def9c553faca2798cba20a31ee7d71422b21000000000000000000000000000000000b64686b90964104f8e79bf9527f452d25c3c8e9d53e715d884e795d26e391dbf510d72fb2850fe66e35d31444814e650cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5", + "Expected": "000000000000000000000000000000001673148063c4f68c59d371410a8ef20cc4516c579523e2d7d2ba04b03fc5a481a30fdc6b9ebaee1138b28a9463011227000000000000000000000000000000000067e9b23610ac41d41e0bfcabc455342360d4ae4c75fa0303631e97f702b9cb660b84b5d422e8e9a74890b803c944ea", + "Name": "matter_g1_multiexp_70", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014cf7c57711c1708096cd33a9efd4f907112a3d4e5bad1767ddc6fb408cb7ac3f866143000154d1270c07b4294480543000000000000000000000000000000000a20191e6786d94721067d6942731110df277047541383ef9847fed9e4b8599723fd7cd7e2ca2186d56986feb8dd24d72063b046a71c2674e35466657a85d8e02253b42517b033619e31a53665917212000000000000000000000000000000000cdb0c20ac2c22a458d2370662d665005cdd8c662e318bb8652a2123f2d65d21c8e150daf51d7874c69bc039bb6163710000000000000000000000000000000008480687d726eefe93d5484ca375557e109fc64f60666e1b8aaf440100aa15e76aab6f821fde170046d2714d8986a1fe92fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000011f20086905f64c21bec021e5726c05158f892658cd69536945a3337a8075994caf4fa16fe66b85e3e0ec71ae5b4c09c0000000000000000000000000000000006d71057aeaf26fc685bfc0ca071126a81224692b3eb90e37a1941782b8f65d45b6a31567c6e3d2935d38e9e02ba08654484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df80000000000000000000000000000000005a186d0ceb2535037b22a6455c49b6227e54c6e6dcdd98f46d996f23301b208a87c4bcd0608972961b67c523f01c99100000000000000000000000000000000142367fb02fc6b2cf52a78e4cb1157d273e9fe13ca721e0fa725f2a6dd0b4897ffe7affa25925da47fe851362700c31bfae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000a758981a1524501c48ffc9990b738d51ebe38a0ba07b2b049110c7aa439253bfb0491a66cc42eb241a47d5e963db75500000000000000000000000000000000082adfa66bb46b97f14dec70b970469478d73d30216201e7467a927ae4ab9d93747b07ea69c406dfef789226afc4240a3168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a2000000000000000000000000000000000de8dddf04e0c2d9ef1887ea598030f2bf3bc7bd98b8b218d19f661ec4c9a47cb087639f72fbe97afe9617acb162bd1a00000000000000000000000000000000127e78f1f41df717e5f76692b9ecf21ec0fbaf9b1d56e51b37cea02143f3b91eb1f16a65046527339dc65d29435a2874f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda0000000000000000000000000000000009c37c64ffe9bbf264c475076ccbe6638653574ea84b30f4eb2601f1990f73fb5708af6007f21e4dd52f23ef5041cb3600000000000000000000000000000000170177e891c421ac91eac0dfff8bb397d7fc531e0fbd275c17cb4d894d18278a40a6c3093b92fc537244798f24eea4e92d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d000000000000000000000000000000000178d45abe2415895e0a550005c76522962c0ef0193cc7475a52f4d9cec9d4789406b7afa2872485722ec034df4446d90000000000000000000000000000000005e4253dde4284944b2083e07b04940cc72cb24d9866c953564bc0e847b72da59888e7a08cde7aa7c0753cda94a6e97c53bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000e48f11dee27507acd407ce1b810cfa8d0ff4414380fe26aba6c608784ef756d605c8c3ba92592ce342baef8aa927bd90000000000000000000000000000000000e604525ab4ed10f3a9a688774c6b27e679fe456190e67689959da296b650dbfb75610dcf54b30ab891c40784a9b90ff944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e43100000000000000000000000000000000199b1367bc3aec710e82f98d3564debe9e01ef2beb878935df4ea98e3725391e873d2661e2a27d778bd29ce6f66a9b24000000000000000000000000000000000e77a3ca6bc4584cc1c3df35b18402b75936f68f0f70193708da21649b6def59f1baec4d6d1a2733c369cb5d9a6b39347de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e10000000000000000000000000000000016ee4a1a3f99134ef55398e96b86a21708388c3ddbd86746745e24bafb062a6283c5bdd771f15eb501df6a19920162d4000000000000000000000000000000001001936f457d8241a4929aec1d3769bd1955433b340481936f9443c63a6c6ddb3be4f4e1ffbf62a5c4b154fa9f8acba3b0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000019aad23ac037d496eeedaeac9248842b0dec15478f62ab61d000a402cbdcc240186248ed931fe3eaae5a1d7153d3e135000000000000000000000000000000000fc1c74c4d8488edd92b42ca7c27e22a4776761829b06efb0d1b2cfa37738efb276cc5121d926665b99497841afcbd394aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f88990000000000000000000000000000000014c557c44a90fa9d958d2e701cb2aac1c0204246fae4ba7b060e74e5d4ff50630fdca918c47323f5d0eff118c7595a040000000000000000000000000000000015821312dfed1e0bc2cfb23536baceb7ceb45c6c5a5f15ce0d4d67ef261a30ab8154b873513e2c44f652b93989cb6f1badde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca00000000000000000000000000000000001ce81da6511eae9d2e155efb4f999a5d75faa99eed8fe784c7a398bf4b0e135bd0e8be8d9dfa2aa8ce9c63e091cb44b000000000000000000000000000000000695ff4e598b9e469bc62dffa214418536a6f49fa5f05680e09783b2f29bbfec5d43d42c969ad3b62c25c6192e328419b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000002f155e83bcd838ee8840996a3d8b0bef77334b0e8e75c8e4278411ae1012bae06959e8394dc4d1fd4ed5f07804b41870000000000000000000000000000000004daf1423e319b18dc57753d39777bb127b651f5294fe03a15dc4974eef8cffe337704c7f867fcb4c2fbac382e444a2b0cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e00000000000000000000000000000000000162f530647fc6290626d74753efe315e64dca2d73571dbd4416dbb41b07e8ddba40b3dbe170922c64fabbd937c961b1400000000000000000000000000000000021ac62abe15b0f1318063428d89f22d2090050b913973de571871125a391affb1cd595f9c596c9dbeb6025fc8392e48e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7", + "Expected": "00000000000000000000000000000000127c2a1365d966520de7aef12e66e55d807b1a3e2305ccd5a78547fad6e6112d60a206412bf56866ca1b0af1b422c7230000000000000000000000000000000003a613a2e2abca1f42d3ed594a3b99a6cc51207b249aee2b5829aafb1327cc7bbf99604473b4048c4b2033e1befbf14a", + "Name": "matter_g1_multiexp_71", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fd31933662cde0814cea424436ddeb6a668e20b69259424a652bab722aac70b3582cb641d53bff963ead87ef5dfe1090000000000000000000000000000000007d17925b0309fd8c92e52c1ad67937efffa7ae3c783177a82f1133c8e3aee2b8fe71095b6b88b01576c5203d7dc8c3f75888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001782f625bc3b25168b1f5b388b7963b9d158c09abbc0bc4c0bf5332e1817fc083d3d55124532fee2665c536b6923fe3b00000000000000000000000000000000118650bcb2d32f4e83257cfebbe8209c2c9062ab0eb805ae2977f79ef48af6fd78e7512b331933edd087054273eab52c18ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c4950430000000000000000000000000000000014a69e56a173ed13a9e2568a8af49d74c74dd67609ca58744f96f9213197b45de6468d69ed084ed8b1b29104322ac517000000000000000000000000000000000739671cdbdf98251ed4bf13d23c675500cb66344731ea6aa66ffe401dd6daa8157676fc46b413378b8325ed4cfe804a24a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005c95d722f8e50603951c21421e8532eae710929e976d76f28c050fb2b093618489c5f892198ca822d3f287fea6eb83200000000000000000000000000000000077a07fe1348e4b6b2a46f444137eb86bf7c58e320afda3d75769a9839fefd9142cfcb75da1d1aa0e7ce84b880ff1b3fe4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a135000000000000000000000000000000000efd66388da0825c846b6437b13ce5014b94b20cd3a713bdbb41a80892820ea7b12b6f6720fc7aa6e6756d496ef5ffdc0000000000000000000000000000000000adeb6281219c324d14ab4dc29841d52f3f21b512ef0a784454a01358747684afe22b34d4ff1ed29ea013d47d9059c838573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000010367597f1deb2ca9338b59ddcd8d02440ce8cc34c71a6ff93205375077c00f3f1c22e00ebc9fb60de7475400976e1860000000000000000000000000000000017d148179e9671959bf03fa1c95ab608fe2fb8b9b1a650f524a070d7857dbb8b14a67a813ba1b22e4b71df52e46c42c002257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc163000000000000000000000000000000000797e0eff7ff579b0c5161c8ee06a2b99ab44e515045e83438952226046bbb4adf3c8d0538a0bcfe27a533444e2bfc9f000000000000000000000000000000000c556867cb0238505da3b55321df66611e6a018be4e181a1ec121dd55c509d501558af880a2bcc71fcc641edcffdb13076b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c8000000000000000000000000000000001357812e6d93272645cacde880754514ee42aea3690d9d5d67e3bb5ee4444b7a3473ea2af0fc563d246b4c3e8ab5525200000000000000000000000000000000176c413594ca45019a174848f765f69e138e70dde1e184515c6f3012df4c5fa39a28a7e202c6c563db7681b0c4f8b3a9c9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000c5f9145b11f6af0895eca18ba6338408ce40ae1b25f8c04b40c0410a6c69b0144541e2ca1d4303c4c55fc407ca11b1a0000000000000000000000000000000010f2a09fd8b6cffae5a06bf50597a9c0d496bf5529c8925c1141cdb25ffd3afc6b51cb5d21d97c99a8d27281c657bd842467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000000c16911df03f532313d162bae1eb57c947059fb5d776ce3bfa661bad92ebacb51154697593e2321bbf85d43ae7ea567000000000000000000000000000000000564ac0f20388ca3bd483033994bf76b1ba135e229487e0c8aa10dfdec1887c62651f4cc0c05622de6356edbfd9abfef2f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae0000000000000000000000000000000001f6de29a7cf8a89e3cb5befc926eeef59270b929edb68e9b0cd96feb5286e130f1f7c0e0d46cf2a411e499be21d47a00000000000000000000000000000000002b4c8ff1040a843a0e1d691adead4fe3d5306f89f83724a891abffec3c742a3416fe54c27c97bd131730ad364373ed0474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000005d07fdc2e2afd92d5f0f1ab6541313b5a58868d1707ff0cc9e4ccdea0c105cf9cf1f6e52d0dfd22c70aee1f7835ee90000000000000000000000000000000001229bfa1d5c5e4aa5ed0f6753dcb40952fc5446b0c5d0d90b22a7b2abc388cc18e8ef74bb2370b6ccf036f09040f62dceaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec0000000000000000000000000000000019f4a0cb264a617986898fbfb53d1bde9cd82c092ad86e608750ffa990d6926644c717f6a63279f8061b066f0c4e86fd00000000000000000000000000000000082f1b79a9ccf56b743e14caf0cf18b94f1978d164d9a95fbf87ce15c3a9b414b098fb09654c23ed2981249233e8baae5158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000011c516cfd059a1b8ff75df3b9b6b135c2a52371f1a0dad631e96d8673f1b26daff9e776e9dfb225e9881635a28dd34c5000000000000000000000000000000000bb0dfd476dab29ccc80781a92f5a998b8ba2464d76df001440240957eb1237d9d210be62c9187d7f17891e837d52635d66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000928c4d78abffa6517742e617ff8efcf59b48efe0b55eaca1d93a434b84c42f29683952dd08546dc1b88bb63a35b49c7000000000000000000000000000000000d63b1f625ca9d33aaf51f8251a088642211a474deac9931c3ff8ad45f80782f62f71f014505606cc4a96f91c79a25709acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df00000000000000000000000000000000131c7e90e794b09da6c4936747e6509f94a467f38ac7f4bfd0c5da88d1733d1b6871a9df498b265c65695ab3ca889f9e00000000000000000000000000000000190e566597ec19df03c473b8ff4ec0cf24168f47c89525b31b1f3592bc7f87540caa8f91e2eb2f415c05502f72673dbd0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8", + "Expected": "0000000000000000000000000000000015610fcdfe0bc92be1f3ea82eb0b0512ed5f448e0000951fdcb0e637ecca602f715674aab1b7555c8f8bdf886d3aa62b000000000000000000000000000000000103c3ceee1b45d9d8912887c40ca16dcaabab3dabf56b8f77cb3186b267a74a5193048ce4165c419cf727d6e9925ac3", + "Name": "matter_g1_multiexp_72", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000cda9f382fd65f5ab92cc560477f1e3b69d0efe355e40ad3bcaf258509b6ca5e179deed8348836b0e723d5f7ca4c43ea00000000000000000000000000000000037011fda0d188f8d17436d21b9bce522cc9f8e4f473965803b242694f403ecee29d2abccbf56ab0a1f2fe5831c14380b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000f591bf508a5076b26dd8ea3b0f7a92889131142b34cba3f35a9b861cc6deeca7378d5728c0af9503441144bfc82038b00000000000000000000000000000000156067cc00e82414150bc05ca2d0c0ce1c19e5276e00434754616c9021120bbf9d1c00df6a42b76c3ffeb6e32f8fc3eefee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b2350000000000000000000000000000000002a8128978ebfb99e20ac99ff5b3088e8eb95a7b6b354d46e775dd1662a27d5adc9513467690f377d4a13766276bf87d0000000000000000000000000000000012ba903800e9641de498d8e286c7ee48b48f7d36255823b88a24cfb67f8d2b7b6411ba3304819f588fff0d730cf130e428728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb96000000000000000000000000000000000b107e62453c7181b26a3accaa624a612b7498ccc50eaf0d47bbf350b3c8c54e940266cde786c608e42f59d793e45eb000000000000000000000000000000000194c2c3717a8284051a29586e540bd9e456c0169eab0412699865c12226521796a55d598f60280cdcf37b54a24c931040fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000013228e1a6346683320d8acad4a5cb1c23cfebdb9d9c451ab81335d27e8b82297b38e1fe2fd02651a8dce3838144cf650000000000000000000000000000000000c6d54add7bdaf9ff8158680f35be7f51dcd5c26a698750c7eab857140b6329157bb7aca8d7c68f107ed9f68b3a076aac14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d050000000000000000000000000000000006e1e7c15fd14ff3bab1e9b8f8b7d6244c707744708db629ef4146b8cefe68c505ea034c180fcff95a452f7e1e5433e1000000000000000000000000000000000735faa57e1c4349be51395bac55a331a04851b41d2ec98072c5ac38eb7bb03e00ed64bcf32c3eac8b34cc6e26769c3ad81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c60000000000000000000000000000000018008132dcbd9455c3932155a0b0c58066bec4803eafb0a2cc30a93b0a335738b52e6cff60b379fb04b5aad342baf11800000000000000000000000000000000149ea542cf34141fface44046aee2f6c436218374d095bdd46638ebc804bb0c9a7e1e3b01c0470bb6efc7749b8f70eb73a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c0000000000000000000000000000000003f12b0eb97856f3ead3d46a8321481351471e558add0ac4e1f285e7ee8a1f2ca88ffedbc8ed21df31d599e80b8f0e94000000000000000000000000000000001315ca27c955f3826da43745809fb1759f0f5d5674e4d94118bf2f2ea0411c7d9cbc65f054c41ffbdf196ef24eb9afc55af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000cca2b061959fb70d383f7e247c131f51920e048dc136036cc301f1ae6ce13809551d0a8074cc05409d124e2df6536d0000000000000000000000000000000000a9692e0263b563cda35f8497d182fc05e78e7bf88267aaebea1f5f41bd1cadb39c61431bfcaef208adcc9118d4dfb546541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c600000000000000000000000000000000005bfb5a43e3643846f92310e9d5439deeb4fdd6b5dfd3de2ab3a40b9b8b3461136b03c5601add616dd87b9a72e81856a000000000000000000000000000000000212c6c42e24a3f11c30b7751f37c0101b8a071a3d56f2d10b6c9f4f84ae12079d8c4f2d216cdc7ee93abf8b9d6973394b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000008adf951da1f0b64c17f84031985bd1f3561ab44c80c339c4c753a7c2080e0f57c41b79b6cccb75662e8642ae0a94451000000000000000000000000000000000d9082079fa53008a03f58b87fe0aceb121c6c004493f3da7ab37f3236942c8ac01fc28db26b87bd2546f93b12577ee57e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000000f352ce042cbbf1adcc42030ba8e0dfc76b4ca313e82a5c5105ec56266977dc83626c9a9b3b5c25ef459a6feb2722140000000000000000000000000000000009443440da963a7e64d90e4642861f3f5399835fc2fdefa7e87708c033848170eb02407a6a9edadad27cb02793055140fde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000001699cd7355b0a0be2946f8f49648bb04a90c6bc8ee7fa258a357455864022db999793771a2e66adf3cea5a54ada82d6e000000000000000000000000000000000a3ebfef4ba72cbccab5e93155429a14fd61c106ed6d2c0db0694c4733b6f1730cc9f34a5e9598c60e189b8e4943efb56f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000be5ac701c69b81cd75fddb8da92066cfc9d0d2aa7f01495afd87e44076f9f022179b7d4b4781d0b5c6c52b498b63dd80000000000000000000000000000000006f2fd1ca9a34fb09d922a76943b43505f2aad16489a138668f08b9f388c67e46a4d5df7387a1c3aa23c76954913abfae2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000019489b41d8b1f2e8ac09cf3f0930e092afd74405e213454c458cfe44e5f393a88713b62715097a1aaf01a188e8ab07c00000000000000000000000000000000018471d616eb66f1dcfaf84b7d49f632e0a5306888e44c70710bb61d4afd440e5f692eefad842b5d37762cab649fbef34762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001088372334c452709f81b57f5e5c148e0f88dc29dc9a118abd6911c46ee83d0c6b58ec9b854c15f519d33d281ac9e21d000000000000000000000000000000000394a7e49f32e4f7d27f276892002ad034dccc8263591b5d941eb2a5e60097e757ea67dcdc5242b755fce30c3b3b64cdffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843", + "Expected": "0000000000000000000000000000000009e68140307d9309c32763e4de857372a61733060ac582922e3822e46c28f67bea87a75bd98248466e8938abdc4ef54b00000000000000000000000000000000187dccf66e6d0593ac8caf6723033db545c43cb90a5f5486418325860b692ffdf8dcf23da9103dc65336d3cec2577a4d", + "Name": "matter_g1_multiexp_73", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000083dad213737f1789595316285a77c859c469b9bd0cf08c61884456e4fc5ef0947847186bd420af252d822419b1de3ef000000000000000000000000000000000795a6ced1d34d91bf5ddbe77fee452699a1b32daac270b4e8661259dcacbb9c8c3776043f2e149773427fe109818c87be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000005db83053f9824116b9d14ce0173c2243a4a8506e161db7f97408dd6fa77b65d0e0a32e95062699f7aa85cc9be448dcb000000000000000000000000000000000f20953295dde557a078c981f0b988cd9da8c7469fb7fa3361f2386c7dd609bf80ccf91cefd797eb3a4f849b2cec4370bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c726000000000000000000000000000000000000bc3e40ec1b6e863f75e4adbcb8b504026d0634d1d3769f7795ed2956bd450e68aebb1a9d11a71fbe5b51bc79d97aa000000000000000000000000000000001703e1fde7f2c740ca3224c1994282e633292f86095be38dde3673b78729db84bca33ee820532aa92bfd32728d9756404f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d72840000000000000000000000000000000009622c13e8924441b0043770faaead6db793ab818532c7323d9ee9a8d118cfd2a578e1c13723c8bbdd049b1d8aaad9ed0000000000000000000000000000000009da68565c05aa28648c0d0a0e185335b4e58903982fd361fb57f544c1f253a55e8a233b341537d78c4f229ec5f935a85d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd0000000000000000000000000000000005818b813993d7c346cd70190e1e6410974e64e08fb0a70721a0ee430dcb0d92d302943836343e274b26c69030226c0d000000000000000000000000000000000ee84b6b251c9d4f7e7abf843c73f0456968e23e79c54d8742cd5967737b9cf9ae8c6030722134c376c7c9433b749563feeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000006870d696789986991a222b988c3623ffb51ce96ee35140e817887ea37068ec77d8131a97579f2ea29a5b45ab55ec5d90000000000000000000000000000000016b203c189343e67e10928c2a45259593cedb1a016491e94435a0823522010469729bd69af9c3bd6f4e71e96c7d8ca72edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a600000000000000000000000000000000191a7f7469739ef4da1fcfed877b875c4b0af45df7aa9055b7d5f0c1360e4c4b7b67958d03125fade281c663923670040000000000000000000000000000000014d5256c242839e0951390f00affb226ee6c906214d8d7dca7e4fba7eaa8b1944fe4f1f93bf6ebb21b4a8585e000a76b7a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000001968eb742dc0e128c94c1f0dab2ff3b0d300966537293ea16856e5f3ce5e12164d9c52fa59e08481bce84f3f87dae8f100000000000000000000000000000000098ec0e7bc53314fc8729f4688b99c3d87e7e2770877a30898c37c68a5e0a4459851b8fa390cab18e7cf0d325d906ce4f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000064ef5e6fe9de3e86ccc7a8b809cbdd945eef98e8e6cfa82dc64ba94070cc107090427c13ddd3bf25d542696d5de44500000000000000000000000000000000116b4babfc4b1a7a36405f597d4afb478c024805495e1a412a3ad5e9ec5f01dc47411ee6e81a9477677b89291e91c2b68031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000014d07ad766b50a6150a50decabc56f04559d1b196b713be88b5543a673ee3f4499e42b58c532e38dca0101f639aaa9fe0000000000000000000000000000000001678e7e66f44cff05163ce249df65063c4ea2d2517a31f42dfe76f67041d7927ad4b0efa4b30c33156b14f5127af190cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000745f042a917dca8e35c8f0301612ce198f75144e145a3c3041f4ecf893360eb0b7fdfaeefe78733bb88010d6a7b9bb3000000000000000000000000000000000e8879142826593a2f1214eee206ba69b7962e9a10ba014af5daccc1e4a2d3c893fa47eb533cd0c0a9fc1c09d389db19d983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000009da4fdf5b86facd674ffe6d91d03674ebfa3aeff5ca2a659777be20109946b1bbd759d4dc2d9e859d587ce50ec3bf01000000000000000000000000000000000924985f655b00fec0bdacfc6914eedab676a962e21ffedd83be646dc17f5cdcdd3f43a9ad7ff9d976e4828b4dd219b7a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a700000000000000000000000000000000187414507425106691a2dac49fea1eaa14783b2a5b79a945fee44957619793be1a68aa110867ea405a076d30568ecf3800000000000000000000000000000000034e932247b81bda0a54568f2887824028d69767b9131c106a4d204c0b2bfb929b9ed7b3fce1e354e405aeca8a28d92e7ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b800000000000000000000000000000000027361b6341bf8985d79b6dde029a9ee54ef441894f34d60a3324edb502bdc78ef60789e5ce342c240db0fa91bbbfd00000000000000000000000000000000000bea3c850bc9d0860241fc6de65c203d5a11e6425faa503c37641522fba6fcd31643209329e6ad75a3dc5e4a4790db4a297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000000fc7095889f943697577c8867b411ac925ea7182e47a7cd19387dcdd48fad5e558de3d80e3036992ba5fb8dd7925774700000000000000000000000000000000160f1fbb346c48a6cab0105d343c55b3714899e931e7b4e0abe68c4fc7067189181afb9c040d41e4c1f7c4e2f1b8a63b1b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b64000000000000000000000000000000000bf1740d01ece251c0f0ee4f798872eda7f5a4ad3152d86db12844ffa88ca52835799f0b2601ed1bae6d4850cc889940000000000000000000000000000000000557f274109f745af6cd965d6e706b9ea1fa3c295cbbdb203ebf049c1070595ab820efad6652b1f1ba4e2d331b5bc6da5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6", + "Expected": "000000000000000000000000000000000c57ca082c662618951201a98d88170a9aa076fd1fc48c8ababdcbb09b63c793725261acd370d03d28ea47c0ef176f1500000000000000000000000000000000110234e4c6a99c6d1ef8b43fa897990d605af9b469045dcd0ead52b03f9f90dc441f7fe5e624349788746802e4f74015", + "Name": "matter_g1_multiexp_74", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ca64fb3ced1d15f94e9b234e6f6fe59d805eb0b50ae29c9b31514ea5c6e79542688e871de6ace893868fa0eafdf46890000000000000000000000000000000019c60ebb5ca4e605e3b0eabdec53f566c9b96a143631be93250260560e47a2ff6b073e432cb1f9104ff913616e7d81c834aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a60000000000000000000000000000000010c867a070e161939458694cd4015b76bc4c76eea884d9dd309d6642436a82bc76ab57b2c0e2d3ca61f34645db65f2460000000000000000000000000000000014d9df8b34369bb23fbeac29aa8c35b346992d847fc2b9e3b96345f4a2245fa8eed505daf17edb4090726052be75662308ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de000000000000000000000000000000000017ffdfa10cc8e1a8b3751312e5bcd09772462618b8bbdca59a60701a96dab651fee0dc755969e1c3a1d2aa4c11e48d6d0000000000000000000000000000000005c2aadea5a4b11077a2a1641eef2d3bc40c2d8001e9853e44bcead87cd968ce41ca50644ea0fe1d0ec4c2d7eda9dcd058c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb4150000000000000000000000000000000012bb9b8a1537c2856d4b2bbcc6fdec6d69eb6196d795bb0f1f49d8a886076e7fb424f63400134622941b2b88ea61b8e30000000000000000000000000000000017206fbf293f1ca1f2a0971b920e702ea39996058111ac2c041c12f58f67037a3840955e1185b413859a6f845b333b58ae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c0000000000000000000000000000000005eadaee4c48dca28f9469e882ca8ccb71f82bf1f2cb5b7f50b2e63a05e78415b3c5d0767a27f19a0b1c88400116e5310000000000000000000000000000000017e95e480a145b5e897c7a1ecc1b21c5a000248f87e74bfecc21a3cf8a06c04fd075612a62145ac089f208e567e4e12072022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c0128710000000000000000000000000000000007b834503ed3e1cb74738db29c91f415beeb3ac5b75bb2cbf11f4a9cd1608ea6080dd1bd50c195dbf5ab6808fe9d6594000000000000000000000000000000000eb32afb90ecf9923ec22a483ffeca3a15d358013e64e521aa42d3db1ed0397e07a85321492e0693f8f041f4f8346c6c800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc0000000000000000000000000000000018f208e26fd7c03313df686e27bb6ea09d9a998764e805fe6182ee221cb9ff1552e4db5feb91b3b2fa595bc32f81898e00000000000000000000000000000000137c06c3f9eb27f1c0546b3c7ce879218a309dc37c0590fc3e151d9f7fd5963f0fda201faab489dce0043c3180abf753a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c0130000000000000000000000000000000016fa49bb488a35ecbfa9e714235790cf6e7c3ea46e6a9a424f59c63d018206740e9467b0575077e86091ad6e0f9f56b6000000000000000000000000000000000197185b7c82ab9e6dc8e2a71c94dde328c923eedc6e305d8f36f4b636e7662e501917b89b33877cb2094b523c969dfeebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec7304050000000000000000000000000000000006b797e2bb8c0c2a5a6ef8d9f08241d42299efc8af049245c254a2e4bfd122a01954bc596750942bf7ee467b22bcc528000000000000000000000000000000000a655491c6381e81473c23565082544d9f223042c82e241b1cb8ba48e847d98a373fc68b762a600489cbbca612defc61fa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a7000000000000000000000000000000000d7b7ba451334d1391a51142c4b7cecf0032fa6d28fa7f36d2d43ba39c6418946244da3cedeb2bdfadd453eb4d54d05b00000000000000000000000000000000127655a7acb4e3271a188cfd287cc1af890756e340eb4648bf3ea3e469644e6d21f63e64f81ccb55b9b1e0a62ddf58b5f0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf211000000000000000000000000000000000129442dedea08bee8661b558bdf8c22dd391900a501f1841c77359b20c1a1ff8838829baafd2a6ab5eff31e3f9ee884c000000000000000000000000000000000ed7c27bfcfbf9b41c833fc0d8573d7b28a6d788ea3cff4d96900559cc63969ac1d5fd366fa705357626eacf402c2ec560d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f90000000000000000000000000000000013a11df012f8a55c263c5c55df0fb682e685a5feef160d77d26db7125ed08e6605f3d67878ec78fd064487f30228f4cf0000000000000000000000000000000019292997c874c72ce7c432f20da1a338e9dc433f9257b7353f99b5b531a9997bc3a3405b0aba89ab5a2f1cda98dd8199006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d600000000000000000000000000000000006a56b816898a1fc9954495b711c493ace881e3989207b2f862dc41c5fe346fc2eee18adfbb9db67e774055561af00600000000000000000000000000000000013971cff1e9a6ce35a7ae40118a007518bbdc5df5939a90fb263a9c345a70f4eef2f94ec671ac6964390d0478cfbf728f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be40000000000000000000000000000000004962ef115a4288177df2f0e4665e5d1976fd027f7f87a24ccdd0584e265e2f5cf0a7490dc7824f5eb26c9569bde9d6e000000000000000000000000000000001544f43d961320d59c65563d5f04341a8ec3e6e64fc2dba7e953652232d615c90eef2c859525fed99ae6ede2c39f510a861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000004b250ff8bea739fd73b3c3463617eaaf3b6bb9db11c2b915f7435996bb4cff3561fc268d2cf0db1705711de522382200000000000000000000000000000000001c428a889955fbb5fcba993f2defa5906ac7b6a3fee6c07f52de8d54b0665cbea84e89a0af3523213fd19f7d37944012dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be5500000000000000000000000000000000106fab277085c88a7d664587f67aac8de95aae908177dc513fa24c8115fa23db44eafa7075b036242306002ee6918da80000000000000000000000000000000009e832e0d01bb5e89460e2cab772c308da07414ff8b880288c7b55d6390360924b806c71c9f9762d84d8d3cb3c2f6a6199893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23", + "Expected": "0000000000000000000000000000000010224cb0e43534025f8ba7a7c426355a2091473ab16a752a9819d8e5f3eb5b1b5c0891b1c0cc0017655dd8aa7102cea80000000000000000000000000000000004313278c1bbc33ae2c3010c50be0120bb3ec794d9ff77fe97154438606e5f6f04c1dbf8dc01b827643a31899342e1ed", + "Name": "matter_g1_multiexp_75", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001812b7bdac748d2c0f05f10edaccd351e35347a4a582671762c0567f55e411839ec0a776c18cd71cc6de0c3a3b8bba820000000000000000000000000000000011afad9a48c42d8c3bf74dde15d7b744c6c141ea57e133c9dde7fd762636115e0296a647fc3fbca8144048721902973fd8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000f4900ffdc92661bb33e7561d08ce7757ae71a2b5ebdf6427922454044c6c6695e249069e83f3053e8a8a0adb5d3d3d2000000000000000000000000000000000be84ebce32bce4d58557422c7a8c4020d1bc643a99b00231a4d4a06d5dcb56bba61ead26fbf07079e9457dd4364ab6d33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f0000000000000000000000000000000017399488c58e24c6e1f5e9a04291930595389536480ee6dc493cafa7f0e85410bccbe5c5841a1a0e495830be7e32c0da000000000000000000000000000000001055ca833e53172ac1d2d3d7c6fd625dcc72556e8d49bb487a83e56deabee4fb672b6cf7787d1231c760c2b7e9d4e55e7894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde48000000000000000000000000000000000c57fc0c785d6b81d4831ba71bf27f9af318a730a9502917a68397678c7ba22f21335ca2fff5bd495676faa418fe21a9000000000000000000000000000000001012cef9cbc88b838492b6a0074e0e5d24635d36d669288acebfe446157a202443fbaa5241b288fe418e1fa50eb3e65cfb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000000a2a4bd7c7a79c2336b05bd5e0558736697c435477d4d0dc790033366ffcdecac3bb9cf48d1341835f7a42e17af833c9000000000000000000000000000000000ba384bfc6aaa8402ff869d78973c68ccc36c20a839da8d570b6890614f692f3a3316f0eb45e4afee0cca078cded752e83c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000143e594b8762b4f821a6cd294251a114e248974494bd16a66f27192d3c2dc56c19d886b6305d420f8b81b22a2ce4faf10000000000000000000000000000000012fff0d7edf98633e1b10ba09b3c70fa0ea8674120160933689115275da6f95a8cae1ec665f89ef3c5454dd91d291ba4d2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b100000000000000000000000000000000159734584d9cceceb9a27808a5bbc1be9acc15c6d2edad81759312898be4efaf85420cbd004102f7b051c83b27bc3fba000000000000000000000000000000000eaaf5b8e35ea5d52bbba19087520a96348b418159e043d3b39c451fb77d5b98aeaa43cacacadf3e6ebb503f49c5ad4c120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f000000000000000000000000000000000c62de2a514ba6a74f66312553218cfcf49828b6f01ed05561b54d5f2a87806694ada45b80429e60fb985d9cc39e9c4600000000000000000000000000000000146b134c46ef783488e0f2d6d9b7039971e8ab7f3c29fbb2635bed84b44013159f483df0e7f0afd038b64f9e5cd105726af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a00000000000000000000000000000000189f02eda06f2d39974098d874325e4711a3f4dddf78c1b9ffb025425c8abe6dbcf5a01de0ebc802816fd67b0a9882fb000000000000000000000000000000000b378df4be4566190679691561aabd7182e68dba4ba05cc67ae19cef483fae99f4cc54540b5a5180c3854f5a82b6fdd0f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b0000000000000000000000000000000011c0ed482c1a1f030fff7395db725633a60875028e2a7763a1ac801f00a8f4aff5e19e556516df899cf5e798197f6880000000000000000000000000000000000fa7faf03f2f636ab340a9d27d9b5a66fb8daa9c083a32904a4407d408cd3a14c17734d7a14abe3655979230e1a93e4d23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f500000000000000000000000000000000101f93857688bc4e4da2c5407d8bc68b9304d27c89a44daf7cebeef81ab96d89c83ac34ccd0dcd87297929551810e47f000000000000000000000000000000000457eef8e4d47638f83aa2165c0f2581e6a0886595f03fc41319d6ba71da0193a4cf9f52c39c79327a69037b11a382f696c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f00000000000000000000000000000000110957948a78ad9c04b7abea4d1caff1de20b5615909c2f5b8ab7a1dbd02b9cf2ebfaaf3b21908aeeae55e47b9a21b7500000000000000000000000000000000168f08d45ec66fd4c9a94d82d9533aeaa251186478851a421f097d00506fe6dc0392114115e3e66d8874e0aa4b15cca281c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c0000000000000000000000000000000015fe358a596150d9eabe6f18e06d562f9e6c42e9df7ad9ef57be8c47c5764e408efbedf136059d0e04f81d4838713a83000000000000000000000000000000000ff7a343274892ba23daff40f5f8c56db9a4788483c16a4a0495a1f696d3304c6276ab5a6d7b3cbdce14e9711b033582e44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d30000000000000000000000000000000010d66f27b2da2ffe49b7540da57c25f0d36de0c43d04da9b123c153ba3eb63f3d26d28d4cc4cfef2c0652010be2f9eb10000000000000000000000000000000004d4cf53935c01bca14c75d1be55e7473d17de6c5a2d69813df90c7612aa4815ca6ea982222793ce66bd1c69f6e456feae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d980000000000000000000000000000000008ece81bc19694eb40ac3ed089d8fb0cbed88371c7e314ece92547151165a017b0a5db4eac06bb2679a8d82b296f522b0000000000000000000000000000000017732041d736996351f132c92fa7249483612bcd79532156694314834c04d3b99579d44628c52eda270ec7c3ca7c3e576a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e00000000000000000000000000000000026b3730efe162d58adc8d4845706f9bfe8ff54116b518d6c3b2bc6418997a44e98071e83566a905973a2d512878cf1d000000000000000000000000000000001449b0e28d1c43ced7cd687a550ff7669df47e80d3f2ee621b791848f1f7d6cf6272e39c66e8a69c81aeb67b06c630b2ed3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5", + "Expected": "000000000000000000000000000000000154edd700b8cda3a0532b2d6e81cded6a9662547b7d638f229ac44975d3b4b2f19816eb664e6208f33bf6f8d070fa58000000000000000000000000000000000a41ce7605d7ec592ec3a030307391ac64be5df9a7f8ff9b7ba15f7f6931353fb824ae8aa76b31466b7e06cb89fbc1e6", + "Name": "matter_g1_multiexp_76", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001227a5d20faf2f8d9242e1a7bea89b5d7c41c3e0d8f2629b4004269f9babd2521a97cc23075e13a53f4c66a82970ee76000000000000000000000000000000001726ad8abed312a369001f53270b5e7ad8f3f2a031804ac055ed4ddb2f40eadf9142416efbc90e84f499e07a307994db8c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd1000000000000000000000000000000000a07b5276098f9b3767908192f91473c554eaed23b810d3b464a3677089c45e2263600cc8d84766c7c67d9b5e6a057cb00000000000000000000000000000000175af857d5b53d195a17ae246208b55f35f4ff193545ea5a725a70f11fdd34ad2fe22431cec7835d4fe3c401c82a93fd8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000039e109e0c2ccb5e6cb4c5451125047bbb854488ddd74fc4360430fd80f16db3498a8be9514099d3ad50ed4376bb5e50000000000000000000000000000000003dec8af7f6805ff9df65c39262959c3c80f271d2f0e53e7e719fbb16080d7d90a1211a6b4d0513c771ddad7d3dc009063016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc10000000000000000000000000000000015a00f549c3a050a5ffa8427bd0c8b90a788c6f9150728b037232ce1148c02bce908f60ee367b70d0c9642114d6e657d0000000000000000000000000000000016831ffba7d7d0bc239563e9e62990af4f740e57ca56d0d8826a9738338e9a1d2e8dc2b8869d62090b06f5a3f68bbcd36c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000165e447cc890b383b46f251531cb6d29cee835fe2a0fbe14c65f0998b2911ba86337ba79decd2701a4db1916e01ff4bb00000000000000000000000000000000007bfb52f3d4a281238eb65565af329b3e043e412588ae00342144d168d903cdc9131775ddcb5217ff692b0f922504ddaaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d71000000000000000000000000000000000ef8ba702c88495b63ac012fd9ce54b4a7ed67b5f7d25bcbedf951455fcfa95a8c7775c5ccc875ca5bafb9bfa1af738e000000000000000000000000000000000e53e18a3e7d294b508ec4084cf57557dd1a96ece8eac9873d35e4f1ee812a1380bf56569e5e797ef54202b1ea69291df6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd4900000000000000000000000000000000021ef16de941ce6394ebd484f6b9de12787aef9e7921292106e6c1b18b8de5c640e448f53abd536953b07dc41db21ec0000000000000000000000000000000000a5d482a1c20571e03501b89d2bb4c6d3251bf0b015f23ecfec87dd7cfde705f946c311483ffc84381609c394c83513a0cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c6470000000000000000000000000000000019c277726fc9c53de1ef3aa2ae6e15b360a98b4a2b27f9057f91eae5b2a308b2f5d618d8e458839d1d60105e4888e7920000000000000000000000000000000012ea8dedac124f05ff58ac72fc967e325e00e83aeedf956adee447720f491ba1bcee564f52e4f0e53faa106ed8088d4cea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000000483da14288400f7b27d712ad849fd7c068db47709f78b297c746ab3e15f17f20130b415c9a1b024bd5b24f74428f0e0000000000000000000000000000000006746bb7d3a38fd833187a16d5500d394303e2edf7d5341d787257a9f811411a5cd586b300b7b4398f9d266bcc27d9cecaf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea3000000000000000000000000000000001700795ca26c2cf7dbdb64034e45362295b7e9c60753d728bf689239b0ad7073b29fb872aff047605509ecd10cbd4fd2000000000000000000000000000000000266a09604de2ccb74c5d97dfe4e9a74cf89d3612de9b2d2d39dfa3362b500be127b83566a61df49e639d548a0ecfea7ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc00000000000000000000000000000000146b2b839ff63d376db418a51890c46b0e3df6848a5a39a26a02673e93ea8dec5079e89a333c85785eb0cd1d67b1e101000000000000000000000000000000000f57e8e4cdf2670dc35a12072923d334523e7ccaca66795e3a762bdda8efe5424f88ef7e4c48b0d6760234ddaad4d7370d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001989144efb1979a42399f93fa80bdf256316f6365bd82b89e0e2371de79ce9de2435a6cfe9704ed710bdfcbc8cc2bcb000000000000000000000000000000000084230cca1eb5defbf2f2ee29fb2c47b417919f220c25bdd2a017b514840466a45b2c00047e9628852d48a057d6335ad7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b8200000000000000000000000000000000128df806a651c43c7e0a3b2c5833bf158ea40953fb0efb02620cc4ecfc4c32a409a8bd9e98e82812b54d027b6346afc70000000000000000000000000000000005e28760f1e574aff9664e373622147c08538ed45cdad72a546e4b5840758f5ed442f8cf24cb0ba35902e64d084406f32a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab719000000000000000000000000000000000289520e710e7ce4a8a671cb00a015dcf40ee2a69309cb89b514f6fb2c6e8fc92a49905893e3e0e9567956fcc86dd89c000000000000000000000000000000000d1329a4174f802680dfe8410fb45e23f96eef4649579ca8e29b3040de33cd6bc485d1339afac9593097c70a0312f5162bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da96000000000000000000000000000000000175f1ed2dcd584f9c59c9c747ea1841792bfd9a64747f84dfe32e256ab5a48eb2dcaa337990089c86b3dd589d276e2ce0000000000000000000000000000000014d8bb6e278ae9bd9df2609690286be593eeb668f5e2adfe880e1d34276ec3bf4ab5514c7898a6504da63e0ecfa49d020f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed0000000000000000000000000000000018adcc61d9162790bd8c19be058afcce08104a952b15efc276af8a8807a4d2edcf8557aa03a297ca01d6a3869160148b0000000000000000000000000000000004338e5f7a12f2ffdc8158a51b14dd36934f01d7fbfe45e18276f2432b1b8210ba6bc5f246a52646bdbf99ed91f2f48f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e", + "Expected": "000000000000000000000000000000000c1f9b78641053cdbdd6545691d1a5238389614524365bcddb07f9b9c6c654e58a40047084532b8473c7d541ebb187ee00000000000000000000000000000000028eb1aeec5e4672c41eccb45b356529e5331bb5fb0ca8f9e10b20a2ef1ea911f03af896ecf7575613bce5eb8a0b0837", + "Name": "matter_g1_multiexp_77", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001242be79cbeb2176ecadb07d205d532bdaaaa26bf9103371f2c4d86ed1df72ba8b6d5c76b7aef25c743ec4f43e5237fa000000000000000000000000000000000d2de7792d0655ebcbdc123ed6093ba68948b8ea156a31b9f23d1abd948f4b2ef2f27a3cbf72b9e5b3e966576e9ffbd5f3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d8000000000000000000000000000000000fa50f78e45b1b7b61f8508bb5842bf59d0f41f2a8192cccec6e56125ff94b402dc47d3bc7762f3196a163fb148105820000000000000000000000000000000002933cca4d82c6f89ff8db5f9239ef8fee2efdfdfa22e0b4d0fbe223910b08060a77eb4328a05ddd31d205861db090ae4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000c15db9d1dcf646bb4c169490256050ad5e408d1f45221a9b4bf02f7651fe93ffb892c98d19d730bdf3971281c9e2e3e00000000000000000000000000000000150a6d1978ec63013ef3dd3b258ea3a716c1e564469d2aba343f3d15c30cf287b706b9eef8363351cccb79ecdf5aa189518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af07000000000000000000000000000000000f7e810001b9e3a11a535f6744a0dd357cffa585baabf065f1e72c9bab5484829a94159c72ff2221406c8b15de465f8c0000000000000000000000000000000009d48808fbf21370420cad4df7a269e1eeac98d2aa5ad5890ff362d91cca5ab1b57fb079caaba3a135c15515e98c6b175561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c38655100000000000000000000000000000000191dcaf13a62fd6de0bdd16151b3c27f54b40ad82da1299164da87d0cb7b4c769f941c39fb4b68a8915fa95a5ddc0e900000000000000000000000000000000008b0ad7fa07edefa61ad026d42df18273b6628b65a4e655a98b705f588494d06c37153ecdadff83d94739bc254d6d8f837c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e260000000000000000000000000000000005e3001f37e840a9edba48b3b436dce520203b0b36c3871933464be1c41178f7a8af9b14000b713ee8fc0faf5cc1a870000000000000000000000000000000001732dba0dbadbe7db31ea6af17520d791feced0a7bca298b932f51f3dbcb355699db533cfc8b61d35d1a346ea5de8032981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de400000000000000000000000000000000072e4d38aa0e168255f1d69ef129642b4b1b57289e630455b147574b03d17e3cf0f32326afb7c45da468e0d8c2276da9000000000000000000000000000000000b60685ad05be8453d5d272c73365d645dab6c50c820c1fb7fb50d82eebf9b03ad3c8f711140ddaafb2bb128b7be2e6c9913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d3000000000000000000000000000000001401e023aac71de3398f89893102efa8760cedf47938a655983d73ca8d394a239f37959e629cd908b4e4f5e55955b153000000000000000000000000000000001458e304efcf48594d7094d30a804742b08ec94ae479cf5d4e0575828ad92cfe8e11847d6078f5eeea4308a8f0644172188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe000000000000000000000000000000000d446202ebd7a7995a4e8aa7fcbaf6c4c4591c4bc40b374720752a150b452b461f59b775e3088733ca967854413a9f0a000000000000000000000000000000000d5fcb5510c0f7ee77c7584631149cd494a5fc496b325ba93ac5f801e34c815fe562be4758212f32ab0978930d142adf5525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a0000000000000000000000000000000000e034e4027e846a8608680995860b2673854d8fdf0e61e2663d7e0d904b6725ff28bb4593e7bf5e2c252d9c9710e39c0000000000000000000000000000000010bbf60b95669468e5dbdfe912dfeae9945f44454df62ec116b097b867b14c402349af692490269797a30639177151945ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc5700000000000000000000000000000000095e1315b3568e8a069dee00c3676d5d6ad94a2164795ca5f1418cff4a25052e741530c0df6d50c5cbcdd55a084227f3000000000000000000000000000000001993b036a3225289827691296b51ea4e42735af0506b317932b6719a381a59c89871a2a394f4a9de0aba3bb9a2b881f86ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb94280000000000000000000000000000000010aad99bc8570d83847a2a2688fa61d5d0ecc978ae842715a084d99392db343f581290478bc1bfeb8bb692e0d6fd58ec0000000000000000000000000000000004f82c0527d3e9329a6b460f1d781f881073b87711771699e9cc8c4229d5112d91d4357380c12c120313d2c9eb7bb427854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000008ec7244587110fd3fa0e1888427fbb3942d0885e002e4f846fb749bfc4a82bd7edd15cf81af454354006a2ea85234f6000000000000000000000000000000000fc7a19df5adfb5a154f32b9022e54b1560237f4319160c9c945b7bf4b55e45fc86616d3ec3cecc177c9f6bc54dd2cdb7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd00000000000000000000000000000000037ab89247516909dceeb59abb90d6968ddc3ef3abffac93c68757f3c9309d145cf9350e4d8f85db810cc5f156f8f126000000000000000000000000000000000289168c6dfdc25ea10e1839e10ddffbb25522be7ff80ef321241c6cc887fc7a42586dd9c1686c6c5c2e4caff0278155923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f5476000000000000000000000000000000000523020b4c34e867e75cdc668e541cfa25f2afc35573b2db083987fc585a487f1eafbac1c4267d2fdfdc5d2f94c51a84000000000000000000000000000000001581bf2744d78d680c9bb38a3f0fee76b6f0231f011b3f7ab3fd59c1ec6c99fac518857dafd410bce2e8610c6e5efbb1e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf9000000000000000000000000000000000615745e737980a923e87c3ef72330f55e38434b3974c1cc997a9d1136527de9bc21dfa73ea0d33d27324a53f12bf6f9000000000000000000000000000000001164b6ac376ef24ce3cba8e2ae74eb58437bbbedf68b4d0b6e8b7e213a789c8c3b7f173bbe52150faed93fa83bce0a9db6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa", + "Expected": "0000000000000000000000000000000016e6cb1f899ee8f3db4b934c1facb3928b08fabdce74f569a06ae6eeab201925f6acb1a47ffef3c608fed32c949786a7000000000000000000000000000000001796fe817d2e4a69b4e8c539f6f2179a802cb02caaeedcdb316b3ec6571c13c349e115a7a05b296d6b182c8a618ed918", + "Name": "matter_g1_multiexp_78", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000cd7cca90c8742e7f541981a13b177a4e639195af5f15cee2ce37b01d50fb8478a3f0d0abe4312a4d92a201b4dbb030e0000000000000000000000000000000018e2a69bd1cd9bb7ea75ceedf28ac9c9514e8d28223af69dc991e46a03d8d272d267842f30552583a1f08a07188058ce13ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff5660000000000000000000000000000000011cbbf1ee7e9cf8deae286ba67ab0eeddabd2471d2ea15e86c77c4f2f23ce38e17ac3f3e3c2a40a1640bb3485be4e59600000000000000000000000000000000108e9f887f86f03dcbd515501f69a5983b4a6d707c26b69cb9ea7a387c5a914612ef645cbe81bf29ba91d209e839c72abcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a0000000000000000000000000000000015c1856c661396f8e3a477932e1eea7e124b2e9ae0dfb1df67c4b3928c462cfbb3220c4c2fbd755fb6435e144a2b937e0000000000000000000000000000000011b114659fa71c3ac2412d5c2cc1e184f05a45871e5ab08fbe5eff68ef9e457c4f3e2bb4f16d10e91f7ee2231bc3266359f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000b693c93d4f06be5bc8a84157c6f407c3db14175c56310e7d041118ec869f3992f75809b209f6dd01085991deaae2a96000000000000000000000000000000000ee21d90cc3825b401e6d452e27814672d849386eccec7be992581b1fb9f4ff4f3892d63e124bd669603e6269f099452995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c00000000000000000000000000000000013b40cfe91492dc53089325be73b5d404288e8056e30cfe4bf3feb6b854eb7d0efa3ac4afa822162ac16608555ccc92000000000000000000000000000000000576146711dfa2ee08bf08121c30fe63ef0ca4448b28076eaba9298ab925c615a56d497044be803f73e9586763aad52497b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a50000000000000000000000000000000009d66d52069b0d23faa33818a8c9bfc812ae6938dd02604e98a422f50c085a5641a46272dc9c8801a9c76cdfc2020a0c0000000000000000000000000000000004dba0f971336c813933bc6386e55044f5e3d3e5cf38ede5811b4e775fb41cd09d7f136d9de6fc36f2f435b8cdfdc26198115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd17810000000000000000000000000000000005ae8fd5c52fff0b80a2c5c4fca4bccad28f580c94edb7e28ca2ce2390cc2fe476a2b11f63c3c8759847e647d5fe5d1f000000000000000000000000000000000edbff5012f6efde3a9bcad65c805b1c4ac0899fbba5fd760513c673ce8ad18d3baf28acb3344f511fd4d9785afea33c27370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf50000000000000000000000000000000009d003d4213a46812ea1565bd9a6f0f3da1e69e289f026e619911354cd7444dfbfff1d842e3d9c61c305b2154851b29500000000000000000000000000000000070a1387dd16f9d8b4306ecfe0e9ba7aaa5959ec917e06da4ddf90c992fc569a56c61f6372bd26e21f5cbe7d720b68c66bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f7100000000000000000000000000000000008fccb033a3e10a0015b11ffe2ed5f4c96ea2262d06ca4b0eabbc15c9b299a5220444345c65e7092501b56599980bd0d00000000000000000000000000000000127583566286e52f2f2c7809cea1170a49993f171c1c217b82c17983e02b7e69cb8c948725c7a613c41f96e80c3f1aa96b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d000000000000000000000000000000000084501b09915fa13908466d6bd50a7e0d8b39893bfcec9c6876b7ed8effd100b8f0a459d754efb6b110af2becd882cfd000000000000000000000000000000000373669b2a03d3da4e907da24c61f5e7928c5fcef4e6c9ad4303fc4cc2cb641212680f7c33605212de8914caa58732f44f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce110700000000000000000000000000000000010608a9f87f46e528d782ef81493625f9a47134832eecca6471d2113060703750b679e64179e7a1c1c81311c38c493400000000000000000000000000000000032a0c82e42be6203415638e6cca4dc1621f87f030a9d742bc77862f4f10ceb44f1ecd377acec6587be0fdc33d8c17c98b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa300000000000000000000000000000000126f62cc3033b7235be5778289fc568a1c474b70cba2d35a0b9fdab5cf239a2d4fb03f0bedfa84425b142c04284da058000000000000000000000000000000000dc1f91754d582f57b413fde9b837cbfe3430582b0964620b02bf854c6f666914157d44a165f16ca1d7204f35caa7b0630e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b20000000000000000000000000000000016d277ee7864b3af3102190cc99db1cff9fd1b1d6e7fc039040149c5944e7837895532ae41b4db50e29a5d6bad7ceb630000000000000000000000000000000016c3f6e29114782c84734cb927d1a89b7755c3a8fbc99076ce3ae17f7f1d088e5fb9757237773fd4e14c2855ec12b93723a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe000000000000000000000000000000000dc8f59e410ef7145d636d2c7d43fc4b1c903d6c8c0efc3ae162293c7c65c48182f9a25c4e5f111635881533cc558cf7000000000000000000000000000000000082dcb0872d815465131953c69e260e3a9ae44d16975f361b5effe13ab1d61c18f050108e73f50871221faf28fd79771ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000002536653a945e03329279f382937d72bddd71ff8f19053e1fb19ef83d9751eaf101676249ac65fc61a0cbacbfca3cfac000000000000000000000000000000000806ebe4d62e62904ead05f814dfa6e8a392b887bab4aee61552c6f93ea5ffec6593e9078a33f4cefc96393a667c934c212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000018fe7f7093e0313737b8e0c6ba2fb0c93afe1e8241bc769f14cebbfdb4c73aa578fe3d37ce1221f21aca8af9ab99201c000000000000000000000000000000000ea0f2ff4c8ed0a51fc8fedaa056a369c5e97e347c6883b215d0f7e019960c0178a7962415c220766c16f4596d4b9d8ce9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37", + "Expected": "00000000000000000000000000000000184197d1ebcdaa25c041e95424bb891fc9eb38160cb0d91e702ac283280c6df697ae85674eccbd0fb130b6f2e1c193b00000000000000000000000000000000015593ed27279ca601616dfcdc493b8c7bd68260f97f8a9f10c03cf871b17cf8f492518d2f8569d60056721723a0172dc", + "Name": "matter_g1_multiexp_79", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018c31653abd67897b3bf8f046712b12d35ada1799d1c18071182fb61273b7bc506779ff2d774576a725f2f1035431c82000000000000000000000000000000000011b2fab972f183c75df3bfb7968dcdfeed0755f71ec118e56c61203e97064355200c5f016b9ed66040fc886062dc58f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000030d7e368c99113318a6657deb3c89424b9acbc5e3568e03dbf629333ed3a5cb45ce6988a3e5ef79e5ee91aa6b990b1d0000000000000000000000000000000002700af33eedebc8a4847d6772cf615413149e6d98ba3b36c96e43c8d97619cb01117570f263bb2f7579c7da67f40a25c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f0000000000000000000000000000000008cbea0d07e870d679cd20b4ad088bf3c5c23e83266b20e816f69bb918824c9bb4d0b3216f8da5a5cdc6f43359e02d06000000000000000000000000000000000d1c9949921e37e73f95b0e4c444e390bb71fef0d893d1b341b9338321bff4a23d1da4ffdd5d7148fa9fe9cc52ebbfa8f2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a958530000000000000000000000000000000003111c080876670db10abfc439b17b32f9e96758b057d3344c7823af1b0320037906b1a9d8fc42cab9e9e0e8449aa997000000000000000000000000000000000e0c7d19a0362a173b70b6fee3d3feb541c7d2ccca71f1f01f8bd105a18024fab05e0a6d448153139f2777b189ba0fe41022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b0000000000000000000000000000000018bcaa4869a5c6ae46e6f5fd5fcf835965d21d48871010245e722bead79d844e96e10558d71e425377f4adacb3f74074000000000000000000000000000000000414d616a4207e7cf79352dbf7f319bf554f043710cbeb48aa502235db7d30f4983b5381269f34ad6ad4fd5ff56d9586b80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000c86dc8b8f38d1e4281269ca252adde9f0fe933d4cc051c7aad55f96252d1e6f9eb6f4f876e153c11b61714d985d318c0000000000000000000000000000000014113f8e2c3ac4919de334eb5c04c909b88df39998e58883a5393a4d760cb6d07c65eae053a7b2100ff3028a786782bff397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000009b4437230d9dae44852d88dba2655070162501702998ea5a035cd88eecb64ad7c9ccaf696545dff98d778cd7400943f000000000000000000000000000000000706b196155640680b257a537c836507d95e6d5cb7f163ca340dc0f8b80859721b7b2a2ba51dd4d72ccc4c3cb91030c928e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c50000000000000000000000000000000017bf848757da8e7ce5e5e69574a9b31d35eb628102897922d4c996443fbc970374ebd601b96b3ca9412c13f50943c7590000000000000000000000000000000014741c0b49e4f02630a6cc1a723cae1a6a9862158bdcf996b46a9614dd34527a859db0b5718788eaf2caa059671f3c683cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000001269325967fc68b78cee64d0386e1fa6ecaca1f85d672f8b63831a1adfcbdbb40461a77ee0e59b1fcccb7c1d543f08a100000000000000000000000000000000053a22e8c4219e4d68a961c2127201a23443d8fddb02e3756cfdf74e616dd4abe73c4ac498ff5f6a68d730c0050b79e18e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c500000000000000000000000000000000018248505148876ab5a5ec3be7e3a6cbac30798d52f437bea7e966921723e6a4a30a0e53518e109d1683f3a4b3432136e000000000000000000000000000000000120602fd461206973e62ec8a3f1cfedddc1e9f9e1769ac06e2a1024a9af19d402f40ffe30f9cf77b8704497d3cba4a3674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000009cf2460e5121b15d177b8ad803c045529933d1abf62205d04726b67d64fee85e2008b5098ceddc42d5c8d95d39147600000000000000000000000000000000012749abe2d8b47bd9c899b6726ccc749bab2786e9568d32299f0e659664ba1efe764944c4087c549e2bb717c87c6b876288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b340000000000000000000000000000000008d764f80994fd37a21f6923d7fef255145ea875c892888d45efb7a37310182b04d2c16d4d91a2e7c41164706afdb617000000000000000000000000000000001156c016a289989510f1c8b39bd6a8c358a1c5611bd2286e9f15983f984e89e061e60717f1b700abaed57076e148a8a956e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000734cd0d73ef7d79fa501b98b7211d551127abf68c473c1c72c591180b605c938ef71f66c422bf2a8bcf16c6c8946c050000000000000000000000000000000008ded96a9fce61040c1acc71d6496cf72590c63c3514c4f1f77d4582635af9eccdfab2e60749ed24fd3b6e30e3576c58ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd000000000000000000000000000000000df9ecaab534bbe9c8531f813a95a7733df6a4c8785575c5ee89647941a6984cdb5a33d2eced340c683339c18f5da32b0000000000000000000000000000000003632b2377ab368bc9f735609452e0ec9fadd6f261cd5352e0a5ed6a37b25ff7a49fe57452e79e7330661b81d7d80a64b96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000006bc4871c0271394c9d6099667ff68e1dbfa9980976075bf81fc18f1875fc91b50a0e3be622882c90b1594419da7dbcd00000000000000000000000000000000168e1dfde47d19280dc213bba9fbb61fdce41f81d4b25b2a7abae0404bbd7a413cdd89611966a7f9bc32617dca51f369d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb000000000000000000000000000000000132a2a6832653eac18e2fcb2c336292dc7990fa1a004404973029a227c9871181ffdd88a74adc3edc7a8308dee803fa000000000000000000000000000000000b230c171d5739fed98d32a3b27584bb0128434401e9e05ae09a4dcd7a017d1cefe7a46dad2db5addfb389feb9c846181ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5", + "Expected": "000000000000000000000000000000000cc6517e655697449988bef516e60c8202fa48c3573967491ea2ff2db9fa0de3f542f656228a5b90d7fc1e5eaa8b06d7000000000000000000000000000000001191ca6ef2791726a31f77b0431ebe170d8fb595cf6e6b9b33b7fb0d4acbecf2d171884c392832d2a91832e982c5c0f4", + "Name": "matter_g1_multiexp_80", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000111de2b65f5f94851aee2861910898b74dacf013591772902239ff7f71a9cf84919bc4a84d6936f9552e97314eb52e7d000000000000000000000000000000000db96af045180bd4d88dc8c40f8cd918d2195c2f3651c176c1ee3ccb583a7363e2c2c900f2a54f26a881938cba98565f7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd0000000000000000000000000000000000e48144181d956ebb37d72c38c062958f73de8944995c7e7568997b04ec19949b348fd80e810632462ce43c7c6571ae000000000000000000000000000000000b4a19556d8c21206c4198059adf5ac2b8a0e08c948a8a4d7465bd31c5ce5887a069df5f80b1df89ab868ca53e16c730c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb10000000000000000000000000000000010547f218e33dd9f9425c8e7be4136e65ee3dc23e0cdfd5f1caa8986162cc13b77d30259b6b9c359ab0faac9ba29bda00000000000000000000000000000000006729e532ba87a77d1e458663690110cf63eea96f8e41a5a338493ff71b68e78e78b9c929006c0410c3739b15ff2810069700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000000d3630086b7e0c068c60192be8724ab4d18409fa6ddcbed02b52fa776e84e2115457c40cac7e903047fc435114150d5c000000000000000000000000000000001066ce26d2e940899e80e9c0e515ce9d5810a4048925a7ddfe0cbb24b3d8d654c6835c6872fff5a988f525c648661cbc19e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b000000000000000000000000000000000ae73f595bc9d22c8c959eedec4d1301a13c9b8c643f4335160bab4a99886694d112ed6fbfbf082629b76d1e2509ed280000000000000000000000000000000013dc07950689ba36736838714eeb28ff3be77ef8ba181718ea7b5229e01d4e036c98eb9ff7a867c017857c029f7f13e3199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e0000000000000000000000000000000016e11b169dc405035037a10180fb368988498b6e209ad62260c7ef45e9bffedbb0587fe282d193bbf88311f3d2880cf500000000000000000000000000000000090a277517ea7a1a7cbd68598aa1e16977cc57c8d095f66a7cd3f67814c2b8f35e17e20d7a26fa67274dc5aecbe778648159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b0000000000000000000000000000000002ea8cba4bcbaeed7feaac63caf21645ddc97daf9250ae29994fd04e798f94dab33bac6e08eef8e6c20f122bc5f88996000000000000000000000000000000000f7a0f6ac02bc9821a883393c8265ba748f9d7c3ea763037bde3bb0178067e93aea4dc70d25e5bcda642d06f41a7f18bef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a000000000000000000000000000000001618ee9c413dcf713699b7910989c20bffc5ba1ca03e973005f49084aba558797e7f9ec20cb86f308d737b97c08f42a6000000000000000000000000000000000db1daa5ed21250c696ca4da3e82f6623c54d643d773286811e21c09e9ef7c9ecb9d84d90b9c76ea9f65e04a29f82750d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c50000000000000000000000000000000012aece7d9e7384ae79e047ca4b4fe72fe541a825530d6c38b9a8fbbf8b801883ccbc3cae7c33e4d811198a7b7876c92d0000000000000000000000000000000013fb42fb1b4e7785c1b66364de150d1e38fd9fe3d8f209b7c168beacf4b26c35fe0fbb4a41f30adabe4314b20b16319561d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000033fce20f9202b89411dbeea59a5b1c632435eaf29e2739163b0837ef9278ee3903ae569931e70f79a9af5a2abd29749000000000000000000000000000000000a50360c73c3f735f97d7d71b21b2831f7d7fb59c594e85b604dbb79ccc884349cba8eab9ce613ed60416994322916db03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd0000000000000000000000000000000014d9a7dbc453effa7a76c774a289957b0ccd72994e568c0de345b482ed2b6db9a3a3e56e0fda159c25acb43b4a6765d5000000000000000000000000000000000b916f28e3fdc62d296e421b1684efd4e9a4b523f79dfaecc00872a1d17724e1e07e2386b4bc6d76b157ae94559d0bcde4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b0000000000000000000000000000000012a4670c5c2847bb188464dafe41360f00621ceb3b5da0a3dcc16732f4baeb0491664ed8c2f95ff9b44e2b77e698eb3800000000000000000000000000000000077b561ed2fe5c91b30a12a2df71e76cc4ac882301d1975c3cb176e22874e28868655db9d0c91003442b0277eff52669c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000570e1a0fe7f82c0d3cf38d90f77634f8dc2bf9b58ac473d9bcbe7242a4bb76d11f36083c90588a680004c077e957a9e00000000000000000000000000000000038ac2b58a16af0a3a0070faabe3969025440d9781e3ebc22ff873dab532d6ca1b0bbf21f32eb9728a322c158f5390fa7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af40000000000000000000000000000000004a3a287fe4401c48d7dc804363941b5836cfad6490b00dcb0ee830e876fa05a42d6e2b036a4e213bbf5b6ae5a4e31ee000000000000000000000000000000001877a91254211b2af54ea910d9efdf4b4e829fda5bf6b0c2dc849903c357bfc6f55b45c7437ba538ab6cc795b71e95796f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000159bf4b0dc89cfc9d1687d8552489b5c3e2ed059164197028bc67c51ad18b341d04e4b8be660880a76a44ef11e785ab5000000000000000000000000000000001643a41fe4104ab0bb96200472ca67064635bb728e6d909fc0026216a90083eb612f11bd5983cf4d7fe664f1c527b96a12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd60000000000000000000000000000000003dc904709f1da618b6a623888015a875b11e5baa5c10eb6d750354c09359b180858bf29d24bae18e7c78c81465659aa000000000000000000000000000000000c61dabb7085a1937782433ec46b0a063a34e102ae9a6b6bae7d82c94e93c3cd05afe19f0673f729761462bcd0d9ca5e5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256", + "Expected": "00000000000000000000000000000000109dbdd05f92274f3edb0232b827a4abbe115bd4d51f8a1e7b8ee609511125ecf35ca60991e75a0b7973c4085b7d4bca000000000000000000000000000000000e14a168decb6893e9c447b4366247d997701471a33bf8f762bde44473d516d857d1825255d8b4cee8d04736cb369758", + "Name": "matter_g1_multiexp_81", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009b85ef81b184c6383ff4e2695a8c261ab252ebd81bdb518001f110b2ba72fbf5014214f816c9453319934d8a010aa0d0000000000000000000000000000000013ce486b15a77cede98a46f66ae51d17713bef6dafbb2ff34c8f441271d52f4fa27fb88c5695f4af6d43e32333e68130acc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd0000000000000000000000000000000006a10f5973fd2aa312ce8f30ba5caad0ae6028bca5c186e4fd55ff4e3f5ce00220b94683e440b09a9fcee238af140699000000000000000000000000000000000ae8e9db6953ce2461bac3be78bebf6c4df8bc57bc7de375aa652d793bdb0899477464097514f0fe2d0badc9027baf3898c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf000000000000000000000000000000000c0c430ee1e9112d901b82e43a25ce4e5b61c81ed7ac7220d88bd10d44d28c1bd20fc8e1ad85f9b6eb43fc232594b4f1000000000000000000000000000000001233dee860032e2f9a67d7b3d61cea99f18b91620b76f8bd178295ac4fc3b8d0db4c4ff602085c7a897435a283e2a4eda0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f000000000000000000000000000000000061e9d3a919bdbdc42500b7daec837506bf0841caf35aaac34a3670517a59bf52343b47b46e8212208cd6fdca6b7140c000000000000000000000000000000000b87f7efb446cdba6e619d5fc04ca8dce8e57f6a76faa4a773c03ddc0666ce2d83682f24d8463d9331ae58e8afcc5641e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe000000000000000000000000000000001606311f79e836a03da5cacc4e1c3930695372f8f679c8f910627f86af15d1612d653c76d88b9d33f848f94bb63fa1ce000000000000000000000000000000000075b5d9626107a486079315a85991f3d77461b45e5c8aca6876287f624694c8ef1a4f5f0a5b65eefa8d6a4746fd2e5fa32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000012bc152cb7df01fd9ca35142806664fdbacb881adcf443051abac7c979d09a1c887fcfb8cad281f376ea3f6693812914000000000000000000000000000000000e32d4d6aa1f5046382c1d5e6e2f97319e8c6887b850b3cee498c482e35319a4f062be80f7f48ff3d1160ea6b18cf67824301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000001599c2c489535375270f0d1f370c6416c83c4043dbdb4999256f187e29c198b1f6c5bd1a52c997f01ebd3622c40feb63000000000000000000000000000000000b60ea3ee221eeac4a8a364eb52ee08579cf5a907aa5642971bd5523dee5dc6d6584ab993d33d9b8ad9de4a1a4f0cbb117a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff82569570674000000000000000000000000000000000b85c776ed6c9c78001ec7bf3412be495f40b0978d0582ad4f86ed54464fe562f9e699f727f36b2fc753f4328f0b2c6b0000000000000000000000000000000006e11a826fb4a8f0ac32f5c52a531508ad1363bf9b09919ccdb61ef25baa7718a4829fdd10fb6b680321cb7ef12d0c01d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a3180000000000000000000000000000000013539f96257faa2ae642c15f9c04e8fa7b2d6d095f7ca285e0dd90f022ec4a8fd74cf48557afdb57bace088b017b8ec20000000000000000000000000000000006cbc3e4291f373ee280eaface275e0334e46e54f65efc4e18b4ebb8ed1e61941d9c859903b56ed0d4aa3f4f3152b5b4cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a000000000000000000000000000000000675b4dab12db428a14afd8e696a64c0bb352bbcbecdcf2b064428b489194112f1cea4a383788e0bb0e97b7f88b817700000000000000000000000000000000013273075195b02abac630211c5870727a42e11bd96a2e2c6057d0c96bb60b73db72dec3135122865cd520c525588664a6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000000cb4629e659d5c2d91c5f909bbeb3381271ebde4f8486f76c1903e86efa78da06af752404ebddb3fc5d1a09ed28b3aa0000000000000000000000000000000019202a57e95d8d2623851973c324d1ed64b48b15388e052761493b1cdd6f3b54c6f47d2b312edec23e9da4c815f02e172e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b000000000000000000000000000000000a80bc4a39d62ca891044795e2b78f4eb82a3bf38c4ccb2e6d24ced4526db7c57ebf8b1951af0707af5ae5929f727c290000000000000000000000000000000001cbe991b082e840d8bd505a2eeeadf034f8f8c2bb530c742d7953089da1447e090d82399bc332127f14f1521c95f0042eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f18400000000000000000000000000000000112e7894d90a5cba2a8bdd0fa750d6e57c0a9938ca30526eb5289b4a59f92bddb33f59ca22a51d1bae03b850999180fa0000000000000000000000000000000016cf6b093a188ccbf1a000aa860fc794546ab0cf261784e7b7bc5750848f685d629ba55f71f2266edcf24d27667d2720caf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000005e2b8ac9124e8ccb6665842d77a2e9398e5b3519fa4fddfc4b10acb5eefceceb1cd6cc733e300ff95ea80d09e3bbeba000000000000000000000000000000001273d1990fa922276859d3921bbd49a452c821a9746c747734692d12c6f7d45533c0a7692d1a2d95e2d2be6dbfb3f6ad106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000dd2c7410b5f5ee63ad2a9ff3a96df2bad103caabe00a9892cc9b2ed2cc3bbbb53724b2ab63cabc44da7097b619f34c3000000000000000000000000000000000f695edd4b67f81f09fa89104c81717577cdd16db30901f4f04ac97e2e0749a80d34422bdfa85b5cdb65c042d90515742b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd300000000000000000000000000000000113c8f77a2409e0c7ad34186119833605f924545821895a283ec83bb6cc38c549a356b205c24f65be66fa627a378eae30000000000000000000000000000000013038ad87e3b3eb6545a0b5f7eec060895deafaf509ff6687024ada75f700d466df86ae5f95463c05f19750c0ce6cf56dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5", + "Expected": "0000000000000000000000000000000015a7b2803cd9b078d457d3b7c62a2418f19c0cfa006739cf3878844c9e1ea115fa982a02fa6fa0cef74404bcf145952f0000000000000000000000000000000018ea40f019b9226cb0428356483f842ad73140a8da065889d81e35a564e99aacc5d5d833d35fd15713ec76c65f9d3307", + "Name": "matter_g1_multiexp_82", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000163b380ea90b97146aa11c64b34de710e41b2ad54036a1a98659046f0e051e5961f30ea5ad78d8052f4a5d2a8388c28d0000000000000000000000000000000012afed5aa2e8c75e437fd796067e0c610a8a4c2f3368752413e6f179bbd4db25b18d5b3f8502186259a6368dd4321148ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000001bd4abe425f0418c86716516075a3ad09812650908cf383ec1396cbb6929bbc791f5cf65dbd95b51690b58ae3cab3f20000000000000000000000000000000008264362c7fa8021dec396c8355197ce4ef70e7b8894fe23d881d34b9a1b883cba1eba0e54d928b4eaa27aabde0df9b3c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a77300000000000000000000000000000000032112872b64559a03629b7ec8b32344b7d5f044670f6099d8e8b1a1d47223f9a42a072975c821d03b30d0994d782d830000000000000000000000000000000016042f6baa48d7c571e1f6c7cf3c7a0887bc4e2b2de51bae133d266dcad23c579e03d3284c09c83a54eff7f2151ce5b3dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b00000000000000000000000000000000028ea1499ad8761d908d863849ab4bbc155edeb03a7ef4bb93e96e25ab11c6dd0c21a6f06537a688189f08a00aa33171000000000000000000000000000000000ca3ee57dbe627ae681b12e0de4ed602bc3c09558444f38b0dee27320708549491a4482f7f101e8a722ef85e3fd742a5da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce4780000000000000000000000000000000018f397d7c84b8125844e874ea31d18b8705a75027d5324390e2eb7c9962d9de07add34a436db21a34fa7fc7898ef04aa000000000000000000000000000000001591f2cbc58c0841e5eeb8d9c75d8dfa0f2dc5e479d136905abb772a6170d131c0f2c9e8e55ffa215a4bd732c2fd85556aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab7700000000000000000000000000000000135452f0f8d4559ba041dbd2ac45f15416070b1674c9d8094556a289716814d2a4efe14857aaccb82c5ada5d6f0d15ca000000000000000000000000000000000f1c47592319db60db724c9d0649d0d713320be7dcc28e7318517ef80a3fda71fd1f4b722633ed7ab7df06218ee593e940273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a00000000000000000000000000000000003c75767678539abf7a62dcad5f90a3b4a54354fa70206e789a1f9b5daeb5fb6d9aa222476c68cf9db8a0789d7ad43d00000000000000000000000000000000139bcede61bcead99ef0d9554ee1c19db1869fe041671c199246824a923f5fd94e1da04fa17ec921bf6e82b14f126702b77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb5400000000000000000000000000000000020aadb198678aab5a71cd6dc33bd64c47be6d080d24f2f1bab7239808c10867ddcec65e27977b9eabef64455cac25e800000000000000000000000000000000141e58a9f8c9bd92d2de58bf3bbe77a48fae9290815915d7980f4835d805486d678ceee9676ab4fdca51d0fff411ab1b0be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000017abf5f132e8e466d2cae445d75978645c3b24284e1b7df7773c256ffc342d1484976ea1046aeb5307f735a69e2fd20a00000000000000000000000000000000087ce2fc44b9ed797f29c352393a8ea109281514490fbc7dc489acb55753fd5c577c4af0ca6c267c83408cd95b355e26c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000000b106b2b94858155849ec36741c7fef4d97ac704baa6752e8230e172da7208b7e9f187ef0a6cf054d00f2cac99235b8000000000000000000000000000000000d94c6e2349941a20884b9c2d702237c5b5ca2ed277bfc79e53452f1cd6f9f49360215d20fa06df238a7ad4ea253c93ec6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b30000000000000000000000000000000019b33665a81d0ceecd43f003eb34e1292945da1361adf118f36aa5acb71bd821a6732758a4aa6988e29d4cb70004df45000000000000000000000000000000000f3a244e578c66a9263f020e2f6ce49dd655c7e40a992c44cee40e1c874588e464f6254ba644e46adf348a26025d6d3ef3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a2100000000000000000000000000000000015618db18e00670281adb20c975f4774aaf169a653d5f583ff6966113fa773075db78507847586fcae82d6a468302706000000000000000000000000000000000301b18d0fe7d0db7793c62b3da072f4cc2fc3425583537110306e31cf63b228cb8c285029044c7b9439c1227d4c7ace2809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c00000000000000000000000000000000053001a82260b26e34e05a203c8233095da1da58c5f804da9cd6cffce07170e39044394f379173e1340da055066d320f000000000000000000000000000000000bfa2bc7fa0476eeffae4df98bd814db751eeac1dc67205c7629c9921928b55c70c2abe242728bc078bc2685690a38503521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac680000000000000000000000000000000019a7f78102671f6d84ece4a5bdc54e59cbeab60a8c6c15a708e0169f42a52e98bbc1f8ff52f34959befc859d308fea250000000000000000000000000000000016b5d76caac944612d1dc687c6dbaf10ba60a12b491b17b6c1c876a5dff933c4bd9c6f923e2ca4cd1dab38fb06dfab6a9c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000007741d8f72a5ddeea2fe82fbce4b3d0aae61e1ab9243ae6a3200711051ac74f30a4dadb597130fd8389353c230b6b7d3000000000000000000000000000000001809f1cc2fc23be0f05b3d12e6891a6aacea121e6db77400638031065d75c7b3fd9a02ded481eb3893b2449aadcf53d6dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd98966400000000000000000000000000000000013d9fdf041ecc7f2c728fefbd6e9da3169d872406b6fa77a52e342fa8852358b02bb2ae7ac77f83e2b25f0120603d0e7000000000000000000000000000000000101ae8e945d31a98c4dc3ba0e01592285c0c92721372bee6b138d9148883970708ad5e585a1b81d82ab0656a3b03a2c00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0", + "Expected": "00000000000000000000000000000000193115466c33711b07826d2a021792b7c238ae67e3bcba00b24026503d818f912c5635f85e85402e3a6b2a8152027afc00000000000000000000000000000000157fcd63d3a0e48e25ca0196b7ade453fcefea33f09123434f418cd1d65bba65a789e4c7d6ddc92d4fe8aaf6bffb1ef8", + "Name": "matter_g1_multiexp_83", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019f625f232faeac09266c2c4881f92c980db551ea236dc1250189c1e71dbeb151cf74e43b4d5f465c6ad92d75457d10500000000000000000000000000000000175ceb7cef0f8144fd4dd82428bade99833023125d34fb296f198673f4848bbbee343f2f2137b55b5f8c5f74032c1ccaa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000001353a419548d05e568f36adf72d40ba8b30be9a78732660331a5196b0f81b52330ed70e5c635acfa9ffbf083e46c8ea40000000000000000000000000000000013ca17c0dba35a747bcd314d87d1c6558e9f569955aba3d958cc5736db78d16132c9dc8f93d5eaea749a0452c13139da92073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000019a1bdc1f5a43fe746df46a7559bfa0bc5292f574fc424b134fb8b2d971e191b3c5d222d39515dd145819d56d5379d12000000000000000000000000000000000a08d0b7c7f5d71222e984bf574cdb7de76a7b3c61ab5a3ec202b295c62366dd958ffd5bb5a5c6c84584342bc76199c62117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f50000000000000000000000000000000000f2c85f34994643712207fc431219b925f4e701732fce95bfb387ac26ff95c9b10408d24aae5005e437bbae924816b2000000000000000000000000000000000d4377368df00dcde448d8399ceb7508a8fa1c17e9d9a5e09c4fd7c09c253529c07068e4484c7e7c6d3ed6fd3ca777fd9087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000d1483ef230a2ce75a59e07f83091291d2524b5d043db8d5583914a6775ce2c80368d9441aa2dd53061a8d9121a025ac0000000000000000000000000000000019100e75a72e07391db9574b3fc4aa1c669436fa802a1a5d71146c5f4b7fe118a5ee71a9df50ff67633f161fd151b947255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000003a88ed50b36d92aa4411afd0a340497962c7740d629edabd505d6023ecb8f9daf0e5bd8ab9dca26ed2ae3ecdfd98b680000000000000000000000000000000013d9d64ab16ce9401988db4855b26b994da09481a339c2a2597401adb72c80718a4df242776f09ed208a8f34ef7f67e6e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c40000000000000000000000000000000013b16751ff7f6af64c06f9ae6f59e1eb6c3ac76355e6192e6eb44bd1a9f866705eadf0d2907e2458462ad731523bd340000000000000000000000000000000000ae691a4fbf3d0fc72c0e14d4b31fc19c52ca07a81db0ba93949c56a9b75433257d784f7bf0611259dba8af77403f536fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000008ea61aba918d691a0d04582e1f48d671df39bc7de29a6ecc17b31a32d485fb1dbf499e01a9aae5ea21be5d6ff9808de000000000000000000000000000000000f7e8863a541be553b36b8424ba6ad057986a9f78454aea770449a23de70fea8eee6bf8aa30e96e90df9a373917452f70e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000010d41968ddccbb34b3faee226750e99301ac068d8e6f13e72962b53fa2d019da108af82bdadb3cfeecfb85f53607400b000000000000000000000000000000000a90e50ac4e0c39f579a19d49e6f64de6bdd5d6a3f9a91ab654f5be01b258af8709ce1c5a994501177d1c70b25e474a9a49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f000000000000000000000000000000000e85db21593e8d3d86df87ceeea7d7853758d69e15edd53fd7da52f0328805db785aa9aa5db25417d76d796200a37d1d0000000000000000000000000000000015d76c5317e1c8cc5a58a0cf0700ff73d92e7f60f4094030716bb8c657d5c75262825fc0683a88278018b4899a1c1ffeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a70000000000000000000000000000000014c9d6aa24bb34080b9a99d31e1bb431e911b2ccda3c8dae9c2c2114abca597b3849c5b3dca756d0f9ff97616c0b724600000000000000000000000000000000050224129c08fbb2f2d16596f83e2d09a09526851c4d52e8d5f0afdae7001af0006edce648efe7d94b6712d012817ff753961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000da4574f20849e04bafbc41bd361e8f4411815b9e7c2fdaa9a3ee70d4f608f89166dbe9e1cf4ff0fc9ae98f27e115c24000000000000000000000000000000001463727b23e6afc17101cca45de7d08b78358605c7b1ca089fc52f6a3c46f590210083103e51a122ed0768be2adeddefa04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001363808474ae9481f54d40fd35ed90c23d4349403d43af0dd603f1db6f5fd5ad8b77d21426977b78f1f5397df17f0bfd000000000000000000000000000000000118560d0cb0eb2fcd3b2d51fb2aa379112b3075e1d4c20757ec241a4877af271700d3412a8fd6f3f5a3dbdf4dc8cdc9b688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e2252700000000000000000000000000000000040c13a6f53ca485a578c6f3f49d917b774f7b2d1b15ed3e748a47b0bc0be8a7809f0ccf509f09121fdebcf8af46023b0000000000000000000000000000000014fc7869df366473b2c4adc2c0b12acfffeffaf22b4856bed6ec6d15f0f080596b81f3aceab9360e99f35ee7c43f1e2fcf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b600000000000000000000000000000000177b54249c613f044b40a11047778c86f09b20ab387ecb8165c83b36a1af046936623fb00764740a90aa232b7f7ae6bc00000000000000000000000000000000040a52fc58007717d6e1dd8486cfccb1f75827c2feb2b7d59b927c4bd23e5ea80d120875f611bed4b7c12b8a5c929475528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f365000000000000000000000000000000001918e41c557305934aa72aaa361d15843ca77c747ac16cb4c251a2f0d7c218b60a5588b0e5fb3573e8186a48d725e50f000000000000000000000000000000000cc4fa5302c177f9ef018445ab722e568347f4f970dd893e3227756dde9dc8cce3eb2bbbb4c3cd98af0ed4a45c022cf1c32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1", + "Expected": "0000000000000000000000000000000016da14ee1ec80ebf06c0622a500a8eb5a560dfa3439a8e53a19b51c6c4576c31a5486c4c49a8050cc1dc30287f33b5b40000000000000000000000000000000003b04355b2d78266675927706874bb7fa67d441886972a8190a45398942622f09ece67ea74d49bd97827fee82d4a7a37", + "Name": "matter_g1_multiexp_84", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e0ae8df03e4d6e36e1c156a35425a3b8189b56e8ce90045d16cfebf7fdd973d207db6391dcd007c311af34f495cfe0c00000000000000000000000000000000198e58d5278b2a82606af16a9af3f023b7182b6b5b2d685fb667714e9fb5c7a3fd5c98dbcc84ee31fcbeaa8f832d7c854f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe0000000000000000000000000000000007a130c85d67f97fd0dc2159d35be8984bfbe94c28d9d96bca8bab844dffd9a6eb3052c619646a4e564c0d47864b31cb000000000000000000000000000000000e2b8362ef5fa5be398a3589413ea69e98b15cdccd203119b79d96405c2c9ae9ca8eecc7533512a25421e1748ec3a1b74b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000acec379756a1fe9fa72f03da4dfa18de1fad19281f262ff39fec77684f0798b6d8aa895db93dab58165b67a875572cf000000000000000000000000000000000a246df19a23260961ea578a68ab4ae8811f9f391f673eab2b6fdd56dae8ff3b059e5b69052c9216529603e7eaf4ff306765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f00000000000000000000000000000000001bf749b61d7081f1e6141380deb6a5517d64e8c290363306fa23d6ba3b4e72ef53933f15ae77060758287a5a5c2bd4000000000000000000000000000000001661c564a5bc4dd852f35660d1e7c8193d76a48d1f0f3dff25adf312e28ebe9ce8972366ab224a95a7c1f6146b9f22412eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa000000000000000000000000000000000462a37cc68530a1c45001cda667e1ec10283b826b52986adec03db59a266cafc18ff76a666c9de9fc2384c5e336404b0000000000000000000000000000000010736bad21840f49466d9db82f01a922f4d6ab71f8d8ae246765300531b2f806663da2a8c16c644cf871a877b210b9e3f8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000013ee85b0c8f999c9d0682bf3f18a553b64aed8addf87e4baba55c6ad88de9c9955b82155caa83b8b6b7961d88c16c7dd0000000000000000000000000000000011bbe00b5ddab0b579375e2014021e3bfb1e11b7ccfd774b8679896c0ee34d1d19890fe5cf10e33e3332283b3a3dceaa28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fc3750c957b3eb656ad552c3997755bf28a54fe4aefafde15619133ae04a47f7c65122c86ef36fedac0c8e0d93c3836000000000000000000000000000000000f7f21014b7a9f07c2212af1b85395ef3072b84ee5e59ae675f6fdb9cac858b6213a264a202e29b45a57c69be5259470dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000d1fdcb6768654b6bc1b4d885039f1649066db8037f212b2d699c02606257388000b0543d25aace7cd1426462ec25c6b000000000000000000000000000000001386eb9bb7d8be5cb9e74a37759458091c44eb814dc3afbdf017a891359831ffcaad85d00d8e100886cb5624562ea0390adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000f4d6551f5587cdb4d92e13e3749f977f5bd35b5b71667edd79b5006d4b0943331a0b417f669c6125edc42099bea22be00000000000000000000000000000000041b8ec8547b710bf2c15ff41ea779f996db7996911a5b4ae9f23073e02b2c252592229af738f684e9cdf48aaba0512a345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000000c4a3756f2affd338f688ee90501f4bf4be43a4549ad8ea6aea69e5a4be015c97ef088da1a39d1103f866f1675f401900000000000000000000000000000000023e5d0bc92794536d59425c4bdf18dc5a208841953e5d45ae91f25d3c61bf66e704a8ca62a574ffefaea854fd23b8d65ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c0000000000000000000000000000000010129a00ea1c30e98c40a6c86090327d0a9b6c25b488cb0e369bc5a0e0658ec9ac9305e5d1469dd43395f72ef8a0e7e80000000000000000000000000000000006d2f5d4f3f8169f722427dbdee62f45f9791e55988910fefe188d6535fa15e2aab8de5130e81183e6ca25a8009be66f853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef3800000000000000000000000000000000005364313c0d2220ed57bf22cee05b77a53c24c97addae502c7b3275a19522b8ae8167194929770191b96b957b19e5550000000000000000000000000000000016ca50cc1aef3890dd338c8a89b906812ce26e0ef9035d1a026f686b0eecab718f6b0ba401556423ddc99d96dd812d566dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d00000000000000000000000000000000007dc52982caf2f5efa3e1a21e22cb8fc53cd0355f2777272806710a96a22f8e896d001bec053acac6241c7637df158a30000000000000000000000000000000017e9f4fb0adb96150095ad5f0d464549d1489d04c4556576865ed3045e0c477beea3115a6ce63910f797fef29f75bad521d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000000695feaefc8fa22f81bd48a41e6c85acf38fa542e96a7562b8d65834c2f64cf5770ab6731ca85b0c5a80a73622acb83a0000000000000000000000000000000003df65226205511218c263af6fe33a09fa3db22e636da54dd967741657e9da6367fefc5e33a370947f2003dc139765083d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000001588a4aaee74856a9d41305023b7eee367648085516c8135fca8c0a6c9cbdecdb2d7b44317286f3a06f92b9eee2470170000000000000000000000000000000005aa06c47bdbcaea82e910b8a2c43c13c23bdfe1897efb2a57d622f5251f0db6293ad21d988c3ee30e33f3a40865fadf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000140d9a251d355cc6a8ff9fdf2223df59747eed11ad140297b6189a8d49a711ec748447ddcc45733a3c36a48da8cd46880000000000000000000000000000000008ce7046871c0b7f781c667958ff22da6ef5447bd319b2df36c9fae9f5597c020c12c7fbc733cb75ca8f9d9dfd942954cc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3", + "Expected": "000000000000000000000000000000000f1abe4dabd68ac4443ff50c0ecc5425ad60973dfbd2b29462254ad618b303dda061d80b28117803159ba503214b9ccd000000000000000000000000000000000d3da8a424576cdfc147e18fab66de0d1f93c05c0dcb08d9d5b2a9f5dcda76600b771991bf2d67061ea5b96d20a45c14", + "Name": "matter_g1_multiexp_85", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004d4ad5e9acfcabc0b93eb9ea59a778a37d7beca03e285382d10d97803ad63e11aa2e3cd1eabf72383d93528e309c28b000000000000000000000000000000000855cbcccda0476699ad3de8d58b4502f9e61bce8d7db37e9fd26ac4649a4cb831cbb74ecf044ae6014c21148382cca3864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000156e86fc66a8b684327a4de77c31abaebbaf2ee5f0c4d5f9238c7d4683f08dc78d59fcdc25928d180a6f292bee23a523000000000000000000000000000000000f64634ec7de1fc93959639991df42e7dc621380f4433fd7efeff72ce75f6ac7738396a912f78ecfe70bfc4d0ac4239093064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f539000000000000000000000000000000000ae2c40a49f6539bb257fd759c2fcc9f7b09d00059c7a7fd41422ce39aa0792413894bc716d66dc79092223b63de6ad80000000000000000000000000000000017a82c6a853fe29f98129998708f0d4d2b09fb22b07474395050d87cfe4d3bbf94967e05861c20680dabf3f4367135a75e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000013b950aa9b7675322d7b39e81b13b14f2480155f74bdc5793425a02f7de41dc1ebefe4f07accd3719feecfe366e93c440000000000000000000000000000000003378e83277e4b02c3b517d3a8cfbf2d2a6585d028c723b2a263e6ba17faf14bb9aea301cbfdfb73f84709e2af99867693f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000004d87c87f8f05a0999c712756bcaa0572b70264166b16eea7fc4785a59cfca18d5b819f0e65e193dd7ec38d0756b84f20000000000000000000000000000000012f64e2dfa3f00ad8f7f68e08b24aae83a049390fbdbaf570a7973d8516dc90e9c5c9211130d5c6c09f5b29183e24201145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000851a636dfc668d1c5d5467774deaa78283a6f56cc514420fb2b6c58ec831add57b5203e31377a57adcfd9097a1cde2e0000000000000000000000000000000008828c34d4e712bdd5133e220167f3424491b9f47dfd95406bc833b3b030037c0ac0d2c84b06b4a2891c8181359af350ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000011021119ccb1cedf88be6f72d3999df899efc4dc28f828831be911582b61894aa37302f84ae9269b97b03a2e30d66c93000000000000000000000000000000000c373df4c0cc1d8a75cf2b9a99b5889811d3ed42850f55480d891b2f44769a371fa4894cb5bf78b7e995b4912cf47dad87bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000005bbd2831bb4eb8ace45ed719056b95dcf5bda8831bc1495f763ff5e82be9708a004a00ecd102d4fd084579d892e5da40000000000000000000000000000000004de171bf5fab4c89783ad1d0cc9fe697b827f023ea1660b0fa2cab108fbcdc80837d46f292b6062761dd865bd1f905f68cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000018b36452aa579eab36db9b0417c999fa334292bc7174bb88e4bb14025a20c86437d5cace5369b90640c81edbf2d60f2b0000000000000000000000000000000014278d1cc3fd07e947419a6a0d7f7bd5f9e13fbd63779ffadc150e3d5efdd1a3f6f6e5ba8516066b75e1925282d0e644a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c5000000000000000000000000000000001365922301de7c81b839e970775854881955f35ef7f718643a97e54746b9d9867ced3fb7525caf5b5bd0d382de02fedd00000000000000000000000000000000000d37c4e106e51c4cb65fef8460846eab04fae7e5ae1d1dbaa1e0bfb2eab7f2e27a9cd5c3cc942e38b021ef71827a0224c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b0000000000000000000000000000000003b9de0464ac24606ae840185d2ca6cc78773b674688a028161341b88907213e275d7dbcb8d8bca15b483922a09297170000000000000000000000000000000012ee2a578c09b7563508d0d94ce6ed75d277ebd89a7f1d6095f8992c0794b4de12e33ee24547c271e17b7a045eb3bf5b17ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a0000000000000000000000000000000005b7aa071b76f93c765f946b96a972c1d11a2c44244355e90cd77ff069b930b2e8171f7cb1ba29f7ca6e62d88cb83c1b0000000000000000000000000000000012cabb25e52f00f89f2758790f9a81d0e336ccd7bdff06a79552a346d1966f54a5157130e5aa8db175aa64a431e19e494615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000dadebc30ac3e033f433d8d012ffc70adc146f4d9574e5431360fb4a8ff0891c8a9f38a8754984a385d704086c320ca90000000000000000000000000000000000238439bc4e8c7dabe260c7b40d317014463c4728d79f521e7e321346747e9aa65bc6b32ee5920969c34421bb99bee9d38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f700000000000000000000000000000000029df69b4ad5cae9fd974da7f58e4c55e83c61eaf011b5f22e1308b56e2c31530c170b304d39eb3e8a3009b67b308c6700000000000000000000000000000000140451659b4d6eaf05db63be5a7b0341612747eea7536b958b0620bdfd7b9918e8bb76c05eb2a528bf4727e38605f99a364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c745900000000000000000000000000000000062493361a1a862e63eb8f20b0610a78d30ac8595e4c6c3487cf3add7cc38613870c2ecd0cb5a869110a99b76fb9055b000000000000000000000000000000000d8918e018ac5490c91cf2574e6a6962b69c17883caf2caa473de172b14961780fb237236b56a236ce8c674dc9001547031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f00000000000000000000000000000000016470ccd107b2afb9ca03a0efb958bbc165304871e683fd606d2e78f65e34885668c6ccb655d4fa98f5776280e63cb3000000000000000000000000000000000982eaaa34f9301fe0ba1915cc5632329715c506528860701f5e52d1d77b8fabc89706af2c4ab3b729251b9472cde96f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f", + "Expected": "00000000000000000000000000000000112fdd661f948495ae2b9200d959ddc873c53c96ee4ec175f6748e62d76d61d6b15970d3a3a52ae9bda30a822ada25b8000000000000000000000000000000000f5b38208d69b5b842bc22ec9d91eb6b653acea5cb16569c61bfe0921f2d8ad613239e87d48c6a9b29ed6a5f43578987", + "Name": "matter_g1_multiexp_86", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a9494f10e6187fa12d88e350ab84ab5bbf999554924e781d6470e700c3da78e411b8627459b3359d7363b088bbeb0da0000000000000000000000000000000017edbf1108591996f28ae17beadfd6b52340236c2741bf8474dd7471c19c1f62a0f28e8d8692cf3e700ddd86a931dcab4aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b900000000000000000000000000000000052f9c6ecc29239c614936bf9ecdfec677afe80de019230180d0fe529a2e82b9e15d6e081b02475e2bf812cea3ba6c640000000000000000000000000000000003dd0afc91516b50d9027c0b132453fab92b165c08428fd5c2cb994646b6b1cd5b82b7c3f7924e4a5cf8b45575e8dfdc22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000da6e13230b2236b2bdf671bd5f3f8bb47bfc637d6e3f1796b555a95e51b86d04fd310f3d3198dee604baf48f69ce0950000000000000000000000000000000018d209b03f61056147d6734003daa776011b70a57e1ab17d3b92e2565b31a846d8fb7c3fc6fa1fff04552800b73affab895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f683200000000000000000000000000000000116c72b5bd9d30182463c592adb8f73c16d22bb4a22832b8d47b683da5f4b8179d4c80d361ce69f92a393027ab29c18900000000000000000000000000000000026dab8d729338903d46a219004fada41eb666a9a90d8ba115f53da9e89a7bc5d824d7f4071c8859df52b3ede7b7dfaf4c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000004e0627475a0d4da458475dbbebd6c36f4ce771bc2b2a8c6adfe9d372ffed05afbea207476af26974476c0cf51a9267900000000000000000000000000000000199ebe83e44a269752d92629810d0c5402f53a1bee03ccafe0b3299a9968ec45abdb5a74a6d90cb026cd9b28cfd2b89f6d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa000000000000000000000000000000000b4918f4bfad81349edcb45439e148af7af6664094412c9a51b887271cc3c46e34147c8a306a19f08922bda9c7146c61000000000000000000000000000000000afc3d1a7c4b6d899149801cb74a7e64a126631b3e758a73feda92a2867c53fd3efd9adf025ca6f6c762029c57706b0b4cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b1384000000000000000000000000000000000a093eeb354ddfc5ea3090b20312788923c5db9d78905dd31d5bf15cd83521f2f186fd284de0858270eea05d21801aae0000000000000000000000000000000011d047410dbf6df20f81971327b38996484e0862a9f71879ff63462e189471c1ba391496753456f0b5379a3b36380e1296111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c0000000000000000000000000000000003d8e8e3a442f911e23b353e9efe396b746360254c14216c752fad17d96d440988d5a25f044afd37f12d74c89c8cb2d700000000000000000000000000000000179ba95a3d3b5ddd3d181e2312385f4ad7232d9af0c28f375e2036157e4603c1a01aa6c9c91496bb28508e5885bc2e599d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d000000000000000000000000000000000f56dfaafea0ce3152458b7252fac14ea64483e1d4a00a44f95bf3932eda2f2c51f0239e6a7a503cfdbbdd88aef2f4880000000000000000000000000000000010e02e9be7c1b795ebaa84f83bd27eba4f12dd49b146db0d788e37835338d352445e82060dd595f616b4f6d2d03cf4c911ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d300000000000000000000000000000000010ccd262b0cda9ad39177d31be0725b83e935c690fa8e07bc7f24e26f8b03122173f4ba43fe8ac933a7fed79f4496c8000000000000000000000000000000000318da543dfb04005a3cf6d93d6bc4058b4b93c4cd84ef978e6a30dd85d60e5e359b4f518842e73d182567ec4fb236b8b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf4400000000000000000000000000000000003dbf6c0676cec0202e328bf408a8fcc38758db1adba3e8184cb3904ed204b7e18db2183f5a1833737ad8eb089afcafc0000000000000000000000000000000014d9add10a0c739dec7fd09c57b3e959f3b7551eab8423ec5bcab4b14e63b7a27f128758d63f8e43a22eeec7bcaddd41a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf2411691190000000000000000000000000000000014d0230f7d5c51e6fff6490c61972e2564bc31fea4a6d1f293424934f75629cb96f189c80ab32a79b2e988582d0283960000000000000000000000000000000011813cbbc0cae4cf6a8d5d58859f1c3b75ac53819129f92abe0ba9123a1a277b55231e1a24745d0d2ba6242ee758113c87fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c5894500000000000000000000000000000000006adb1e972755f04cc57170d19414e6930d0e6d42c09f587e490593a5c01ce6e827a6dd1e21570ba11c7e4277d532e0000000000000000000000000000000000ef599058025f40c9f77ef858aaf314faaf8d72277cd319a84a9d7038d81b76aa260df0516dd38633b22f9d3996e4761c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000008190fa5e3d23c0186ba502a5892b76cf8faf2c15c91ee39d51b269b6bf4bd3e7ea395787d989c1a14ad88f3702cd6d00000000000000000000000000000000118d2d1b28f9180155277b80f1a7937dc7fe6be3b00cbf6a7ddfd08cf653ed11a4ddaa44576e70b27cacb7646a100d03f29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b49899117000000000000000000000000000000000d28e7ef8433f8d5399ce3cb847f2633392bf44ae9fb2d402ed8e7e6a22de35c39e4f09ea0fe673ae3cb652f75ec80bb000000000000000000000000000000000ebf2ed9df06e2d5688d0ea812b7f9de78fe292584476b20bd62066977f5e221dbbd8f552547f06a3e821a53aeab83c1125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000e162f9ba960f452c269bd2f9f06e8bf1ffe737788d6364b1f75ea2788fda7e265dcaa907e45bc6ef7a31c4791b470e0000000000000000000000000000000008a778bcedb58f562c7b69ef3073c81866a395d6408829816be3172e1e825ca6b88f156ed2b2ac5a8784fac62b893896bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6", + "Expected": "0000000000000000000000000000000003f6acb4e1f7856413fe9317bc7cffa45f2053ae62e3b5e2b82ad57b48cbeb4110f53dfcace08bbb1955c21b50fc636f00000000000000000000000000000000172cf1d257572562f9fc390f96f2c39dc5809765b0b94240a554f8bbcc44e3658f22e81d1e6c43742ef24882934cbbed", + "Name": "matter_g1_multiexp_87", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000013d3d80910d9f43a707ba389b03bda49b65081f65096bdef3942f0bde2122ea575abf810f400d47ced92c45dc73837000000000000000000000000000000000755b4f5a055c718f268cf3a74533fe8e8ebf37aff3045b58927ee6ee7a862c8c1cd61f00dfdaf6cccabf981fff16c7908c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000001530565bb621f7cd530c0eeb4cc41c2587ef8123c552aed339f80711c157e1595baa140434385d0977e9aed2629ea76b000000000000000000000000000000001806c5a90120fe65450e84ee0a56e0176e944a3fffdd2c83bf15d7dca875790d2f842eb31f640456a1221e44035ad33ca0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000143723a10965da7b47bed0d0b5bdb6bfef5b748f6e185ff2efb73c5756d41d77b8c217a6d92245ae36e0add4d743e7e9000000000000000000000000000000001274e8842cc812435a576b2ac19edb84f72d08cffa129d7f4e44be5cc88b3449ecaa719b4d76aaecf08ddd30f7b184ddc252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000379e08cc1f47014f7eede433abbe881818c0c3a9cb02bad8cc86242aeb9f9542aedb67313f494fd19971a0a15d4ee1a0000000000000000000000000000000004e83a0e52981faf6a787d0600ccc457ddf3bb81c76117265c1bd011e5b4f3237383e97dad3b019623521b3c94d67df36d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b0000000000000000000000000000000009ec14585b72733f621a58f35ab30580f131c93db491d4d704c8da2a7a0a1146e144575083bd963238434e2af48d3d57000000000000000000000000000000000ebd1a1c160ba7c8e3c20745bbde05f08d7f3189ecaa831d05c6a34562d6d3ccaa92472c67bdebeac8494658abf2c0405c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b197300000000000000000000000000000000084b7f967b141c94df69804a723169f69e05629c97a7a8c60b140787f3361ac87458372c91e04c08da2d01fa96056ef8000000000000000000000000000000000d731a1a900551ca569b8066af85176b934b94332679aa59924eb7d9a5fd776a55b4d7e5ef2413c53c244c848694b06411009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a0000000000000000000000000000000001847861de1064a4226435ca43c1cfbc5d4660fcac177654cf5d497ba9aa5a6322f1156adafba852633e111576698bd00000000000000000000000000000000005ba738972bf139d91f0a426c96fcbb3b77a01af0f2316f2427a20882b5f355772fd6d6016ed77c31c13f88b26c628763e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf000000000000000000000000000000000148907d2335e046c50fe213b717fedac86eb3920099526a62b4466749d435f5ce11a45032b60bd5d7b26799adc63f830000000000000000000000000000000004bdc2bab60cf6df6dfd25c16f04edd96d5021b97ef38cad02cc1fc7f12494098eb793d99d15b327185718f81ec0ea620444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000145ea0ffc3b24a623d74c27b84a390be062542795eb93a2f71f9358b44b76b93dfc0a2ae507f07a8a07edeed2410e5c10000000000000000000000000000000000d407c6c245316b5cc6b62efcd082829354d7e9e69ad739ae0ee55e6096ea08a48c59ded4595032093c32634576aa132035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc736000000000000000000000000000000000123f333f3554eac47c8daa1d4b362e42de1834ba9f55e4fee138eaf1a057036aa6ff9f50cddc78dabd3d5557b05b8bd1000000000000000000000000000000000116d786097bcac320327d7d56aa734d76d48a677e9c02ecc0bce550d75082c319f568d94b41e1c57c6075ee994e33304bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e826000000000000000000000000000000001012b1790e287a6328cbbcf80eaceb2c518a70e80cfe17143a41c4045e8c6c5317aafcb34f4f56494b401a8a9f21b5fa000000000000000000000000000000000613a88e513248538c1b767ba4d3667bca7aeee7974f691b7e4f012ea9b2b32603eddab0943229f53324c51838d18fe3369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c31aa6f315a1102ea973d13e858d079221087edf178d98fb05701ed0a159309fed05942626b29ade066f8cef465535000000000000000000000000000000000177a3468b7de9612a93b9f2bb3f07acf505f56c63f798b4dfc38a25d0fc133c862e90ec8b40dc94004cfdcc9da197ee7ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e12000000000000000000000000000000000092095e7a431ff3a8e51e26c24dd4a5fed6d4a4a169b5ef79e8822611da8aca5d7c27139a911d5473442db9ee1529bd000000000000000000000000000000000c59f5a649682e864a792ad50fad57b7cd14cbb19d1feadc3536515f01053fab26950f56bb78d5a51f4368e73c19062f2cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca3610000000000000000000000000000000003930511780f28217a125f524ddef656581a4ba2d461730f0837d1846d63258a02e659b25b882a3c3d077c880a64e3cd0000000000000000000000000000000019c682245c941c76605502785b1f79d37f65cf9ec61a4558092973bb2514de4e5852fc757c2fc7eac1b01d414248acdd60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff7900000000000000000000000000000000000edf518026cbf2dcca1d46340c24fa947261bcef36e3c8d026a09068a10a5afdb0964b54b70bb3b27e27c4d2e0bf9b0000000000000000000000000000000005cf718694ca47202be8c0afd56c88742e2b467d01e7b2330de778c434a57610fe7b8bd6071836a58f5d6b2876cff05a652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000013db13bf10b6d8b1ce5dccec98745dab635b8bc81d03601785185cccddfe2dfb3f3f9f6ed16d2c1a7a6bd63264b094d60000000000000000000000000000000001080522766b6cb5c90e6e0ae11ab4ded3db3ea3c7e69d00f29155283f7b25f762eb35bfeedf00caa83dcf04f22ee72976a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20", + "Expected": "000000000000000000000000000000001975e01d64d38cb8f852b4177104388aba62c167da2a8530bc59f510a990ed57be0c6ddfc92622234a121ca407853dbb000000000000000000000000000000000de737b9097065462dda93321d5881430f50631f6f3deabca4c41cd491f1e441139bf8ceb598393ab2a3424b0acf290e", + "Name": "matter_g1_multiexp_88", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001624f6ef9638cdc5f0b16b47ac8c5465cb479333a4ee4caaf6d2b656464d8f84387f01bc1811924312e6cc1e29a590c300000000000000000000000000000000012b3bcce18f60c4b2159df93a2d536bdcadd675439371acce011ac5b542fe1bcf89161fc3b3644679a395aed31dab569f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f0000000000000000000000000000000005d49b4ea69c41ffa7727b98db08ab3fd3ca0c0261ef04b426ef29e724bd6158b3f3242cb915cf0992f2a631fd9b4421000000000000000000000000000000000f635c26698cf5dffbe25ff496f80c5de6b181f94a907204b79b548c1fee8c7dd426b49e9eb9eb0b17e34a26628c38e71deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f1080000000000000000000000000000000003a80767130cd3c3fff0610f215337bc1b4a88886778fc0dcb6bd3cf7bee48f4c23c974c8883e2cf32fb01a84f9e148f00000000000000000000000000000000173f518f3349c1f704fd200747158940ecc395b04b4c476f406cc27836df182c3f1b707aa05767ff1bc75de42dba2a824b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000011874da4371ee8bddb34bc92fee6bf51226878e4550aa33313a434b75243c1f2296c1d62d9f31f6ffe2735f4f26a8082000000000000000000000000000000000f82551ba2b803e35c7118f4294626c151c7137eb4b97aa5265ce383f7ebc5ff5fe381776eee724aebb963d2bcb3d9f6124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010501308d1a05e69700111431a0ca99aa41a991555b9a53df9c38413c67fa1b1836853bda93bbd8679e7724b3141a8d0000000000000000000000000000000000b033cfca384e480f73a4f8f79ceea706d7390e5b702305b79e30890e158ede03814d1a0dffcc3608fcb9926c5c65eb65a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000c7bf31a1f30f8e0de1a4a77b8b6c115d1a5d825b51875cba3db857a9cd2c589696ce2abe5a87acf8d6604c1f1f89ab70000000000000000000000000000000019ad7a6190a69fe1df07d55f8c792fc72cf2be11bbdd83c06325682bdfb5c31efef11fcb819d39f25bb1978570a250218c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab870000000000000000000000000000000012befada1cdf63d34ee2334ba2e42d7e69ffed71a39714e7ed89a86fd5cc1c65a01340c986abc37e7e3ac5a22a2bcc860000000000000000000000000000000006e5b16316867dc33a9770aa2283691f379581ff2b0b7986003174d4862d8b73bcc3f325c9a90097328f881b15f877c7f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff0000000000000000000000000000000008e763f110c9415b63baf27236f1c0975e7bebc04bdaf47ea0d3a2709a455ea48ffefb7551a73c9d599bc5c9fbbca78f000000000000000000000000000000001492e70f2831c87222f7d7a9d00842870b77aa68e87b8cdc9d8ba61f86adce6ea514bf5b8f9d66937b1b640c43b02fac22d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000001685898af1ad3bfd350980872e6438048f6cb37398ceab33d7bae1d621b5b2859e6a07b4e4db891af37e29881cf573ad000000000000000000000000000000001084663fadcf81b9818c999c26a84c6f9a3a1f71a0a2982b5c6d01c56c2974656c08e4ba7833d1ef8bcf9af53d2f0732e816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000013b077eb9130821bcecfe9b366c7a14f4487121095d325e74de44ea206078a6b1ac7d29a4e80f75c7714b6053cf2995a000000000000000000000000000000000b825b95b52382195416477f0bce73f06167db02bbcb91944e9e7534f804973bb363adca8b5ad80e77b70f4f1b9654d004f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea00000000000000000000000000000000014c48b3b2fb994920957b046643bfff19533dbe533df980dc60d9c852a3d07b8cf67454820a89ec9c7ea73a209f911ef0000000000000000000000000000000019b19e64d977d40b95050e4af365541b6c815534dc4abba7ea0af4b0a7e6bff0495fbb347250f5b5a48020ac20ea61cb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d20000000000000000000000000000000017fe39b732e6b815bde4078cba9f926e117349e3e49fcfb6308a0a09296fa27da4580d8fd18b0ecfd0ca68312cc0e5c10000000000000000000000000000000018a4eda1862c5c296de2eea0e720ba13f8a60defc65870f0112ab394e8160d6e1a0beff5db8c450d8770792b7efcccba05edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000e65750f3b9690f25b5bf80de0d76da21752a0daa8ce01b2bd8d172577f6c7d46c119ed20e73617ea163575705343c4c0000000000000000000000000000000019d0f934decb53a477b37d894d6e651a8a4f25b9375bac6b6d3483ee8d85f56b8374bacf74bb8550bd26b3d326962666f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b000000000000000000000000000000000907fe95f32e22ed75f94d96c191bcb19f88355bb84f91a8a535441da04dc211376435ccc60ad2089835b51e79f24b5900000000000000000000000000000000071e35d64ffa38024f4ccf7c4a713e22d8fb4b8450ba7b05ec5e759c2f8ea30e7d9e71ec2c90b8c667370131de785116059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b419000000000000000000000000000000000ccc90617f386ee2a76da43a745972066955c8e346d3de214834ea79423e7d95a008a6c119d640491d515b801034452f0000000000000000000000000000000002588711ccd23b65cf2f63b2d602b1d7dbf97cdbdb159e02e3bdf84fa65685e14d4832cde3662950a7fcfd11e68ad40a47b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface0000000000000000000000000000000003b5acf5f4e39fcb32a267034c5e905eb3df32f2f6f7150d94cd17bf16e3a9fff9dfdf75a966040a6af5a623787a40170000000000000000000000000000000018e4b8d163e5176bc9a45da14fabbac696ae6870717bf5f6c00b5c73dadefbe329d86a761935b18e81d65ab6c48e241567d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c", + "Expected": "000000000000000000000000000000000905fd0b04e8db7657431b5f69f1d896e79ecee300cd67ea8fbedcf51b0389506da0669c28ac8158d5357a61fbc3976a0000000000000000000000000000000003235ff6d1acbceb35cd29c5fe524a452064a6e51d1480ce1e57c538b2ab6ec4f98c3bac676817e25e2d92e701ba881b", + "Name": "matter_g1_multiexp_89", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000688d6eaa2964e33cebae16623e228256937ce9a7721c4fbc85233ffb3edad5d6349d9c8a00c16faa0efd9c54827f46a0000000000000000000000000000000019fa249ce7be07208cdac9f9927163bb1b79b40b320623fc1a08a299d5500cacdc55386ce451173f683a9ce3f006c1e4aaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c00000000000000000000000000000000078eef7d7951f257b17c579fec05f3efe332534b2f56a953a701a8b92664b9a0b37959f7c3dbd77ac18a5e72d174b9f20000000000000000000000000000000017cb59169aee6caa1dbc3c47c29f977a44a81d33f1cd298d5df3e9469c8543d919b985e1b588a96a9268cef03876effbdb48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f200000000000000000000000000000000025bae0252e5d83a3b76f2a861ebb1312bd344e3eaaed5e7169de248137a929ab94156be11e9b16ff312180d856d93900000000000000000000000000000000013c207c57a4876f6bd6e8e87eed0021d5e6b2aa3b2a323572fc2ad521e807c366fe31ec285c8412f89328cdf09dcbc99ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f4000000000000000000000000000000000ea93e5fe055ed1ce77de5d298fafdc4201418489b64d10c447de3973c1b98c184c0cae1d95831742f3d50613c5cd8c40000000000000000000000000000000004f2f3d0a5caac826632ee95dd1aafe181976552abdcc7db737f5693f3d08d3c4a85365e05e369365a37ef1b3df5cbca36d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f460000000000000000000000000000000004c8b69c09f67ad17e8fb9fea4b7532c7c5bf3edb7669e26eea4f9c8f0bc10b0b1895acdee731da5999318d83095ef5900000000000000000000000000000000054f950a1ae65dfcd40eca15e5fbae984e7672a23ec030eea0cbc0424cc8073186b8442e0d71d6a4a77cee37c1108f941a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000b69b6b7b6cb1569ccbcea029dc71560d54b8bb88bd33af1c12a09d867fbeada2e58585385f1fe508a0dcdf8d2143f71000000000000000000000000000000000277561e6ac810ddf4c46288a065e5441ae0fe2d7ee79ebd6cea8712281a36f812c0bf49c21beb63a1f5cb670dd37d03ad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e9000000000000000000000000000000000c0f4206d4cd564be1efcbdf57f99ce43b97d3e170017fe352ed3ec60862f87730d4d9d9d56ea0aac4f586d2f1786df900000000000000000000000000000000073202e8c73d14469d15a392589db79f3897b72bdb2b788da9012c7aaa167a157f85f3431161d35f45bdfe0f2255b6378fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000191cae6012ca07ddf511ed586ef19e9f0d913d081cd752f033c9f74c334c6f5d075b4f6ec85467caea7836f51d0159af0000000000000000000000000000000016e65314e34e1c7ad577a36eff992abe6f26fc5349d12db12394bac648cbc1452cc366aff69e8cc4e2e5bc85db237a863023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e0000000000000000000000000000000000bf583ae5e3a7827610d91c0d2433c8d358fbc12c016c59be8454c039197971f90191737993bfd08aa96d7838b7ce6dc00000000000000000000000000000000046fc386c5b456bafe03fc84b4f98939f9c736ac74cac507ea036d2443066090118138547766f637537425f64be9691b8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000000f08b3868ea056ff8e82fb7e22a6522985e92df1df9db77f787bcb3ed701bf8c90badcfd94e9d3e3b3b68ec497b9fcc700000000000000000000000000000000002e6f5e9eb44fcc7aa96a43856a707f5a82cb4c14c99b21df09e666d4802d15fb50d535184b63ae246d4ad77b6c4851415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c000000000000000000000000000000000167e13cc54e9e9866bddff0c37e942ef8393a588ed3c2e90da12d0a8360edd6c3980bde808ff16588a57100d1a8898fd0000000000000000000000000000000014b21a7a106640b55cfeb19d3c23aabcf1c0be78fa554613e68404978b78e5d34b6b6378c2e87d0b8bf1cf3444d0db31ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a9740000000000000000000000000000000002e8053215ae6894e8df09394353fe98b38fe4b17b9f20c7b48c4baad91519587f63b863e4de79be71672e1fb00d337a000000000000000000000000000000000c2ef9251a148f1ba8cd75a60ee18ba6328e1c3a6780c790cba3bc91a2145f44cb8bda5257c03890d5c5674e4d09296d039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff3000000000000000000000000000000000aa7fdd550eabb1b734db00400304be9663c008d322d67fc771a85991bca6413ec07ab3adc3cb40d390fd41021434b97000000000000000000000000000000001994d9be11443f0a95a2ba4f7240a9dbaaffbc70256aebc0f10c322fc5b120feb2cd8492d02c60578f8becd7a8e589c92c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd05550000000000000000000000000000000012b3574c35288c63930be8024afcc91194b30d2b486edae832dcb34778886af5816f7478df166f0a7e4752d8c12423e30000000000000000000000000000000012cd382d17ea10ad3fbfb40fdf4f3814a19384e302542a0f5731920443e4498a1f8f4d89086764beff079583a672b93bab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000117294ca9961249be6570ea760bb1e562cbd587f78be482263e4228171d9ee3d970b234455912299933689096f4afbd000000000000000000000000000000000029f88a99c750a388eca5dc6939082280ddefbf7d23997cca3653aaaa03a3ee4677fa8291641ad1f46fee0f8f1268140297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000001006f64c279f074bf036897ded9deaf9b4ca380a9a7542490be675355c3979b2925be09ac4613fd6b7a4a8bb9e357f70000000000000000000000000000000001537e170e8dd88a92a6bfedcef69bb370f7bc1f32c36d203f5b6859be9b60fcb4d1e3948687ac7791d867e7c200967eea87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99", + "Expected": "000000000000000000000000000000000603f6b2d8b806c5e399c686878eba299131204084e2c31c38640d757e8a6e5b318da785d54ec8335398610e9e3956280000000000000000000000000000000002abafc5839180e8aff2bbac4db043e8839ea25d8fcb7f6faba2a1c0a567863f256f820e846e49b3296a754650ca9b4e", + "Name": "matter_g1_multiexp_90", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a2eba2e26da82458a494738fcc816405760f4991616d729415ee502d13951c319be796cf35d88a8e00e17fa3c58126900000000000000000000000000000000117f6b75a6e25a786e860df05505f8e107b23c6f4338b2f87ac8740554304046f7cbb43f2193da35350e5fb39077ff3f9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000000f6e3064df312fc97c4f30d3cab398f7921453b933d428a4162a37af5ea27c79d5b21d1d305a9609c994e61e56db226a0000000000000000000000000000000011edcb47b9d5339d08f24be87e52eabbdf701ab15f7799a5ae26cfca9d49e0e9107d9d1f09c711039d096a5745b89c9164be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a6769000000000000000000000000000000000d6bf9e905e907ed86f5d3a4cdf61c527ef43ea0befcf6bb7eb1bb790b3dbdb83e0b958836669827251da94db1d07c420000000000000000000000000000000007f85bbbc54af3eb9e1c7e4c4700b4c784b8d2e6b2ff6a981a534317766790942898b4eabbb8d6c893180a436faf88870c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da000000000000000000000000000000000b17bd374136dc1717cff915f7c898e049e892ced4ba57a16752a6dd875cf1cf9a2005dec3e3bc6f87b7a257d5ce7ca6000000000000000000000000000000000874999db06d15bd4b2f60e9b61d195747d12f38b75b74f3089d5b47735e9dcf79ebce22505399e16492c4a6e0f83abba2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a00000000000000000000000000000000179108aa8a7d8443f69b7c906f9a4869ff4c724aaac4fccb5f52cddec86e32180b3ab2f66ba76d57f69416b70334a0f80000000000000000000000000000000007f83a847f4c7e7b35fd091249120bc59719ede5b6db083b33f5ea6249f9e13457511db006f416e0fb9614b8d22d51e1e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c589000000000000000000000000000000000154b40b3bcd0ef04a5e1a550215c238adf07f92757c227e4d32e42893ee8e7e4fa9d7169005220d89b11253cffbdbd10000000000000000000000000000000018daff3cf04f648e59d00df4b86d8ea5dc74adbbc6fe4f080ea7a84dc6443d8923517a11f264f700e209af9bc52f759c76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000163cb54e83a9935be82161939360356f7f0cd0219f446fd243d05f6333c68a1aca8f5d2dfa2b54dbc07f81f756ed6bd7000000000000000000000000000000001667e7a040817e83896d62adfc4a9f3d329e87f7d598217c7d2195c5b0c3eb58047d4b9bb640e3959f7ad1242e10783f7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d000000000000000000000000000000000222ed79e925d64fb58bf0cf105a2087c538c9538070bd742f7acf5e00ab371766d286fbccb3e708bda2d227523a40cc00000000000000000000000000000000126a9569e9ba97e5c41cf11af3a601560d037f1594f2e352ac86c744542618e9d2b6def0c7d3bb6a3707b80cdcb60f15d35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f0000000000000000000000000000000003786245c244c9508ba94e994dd510a7485f4aed711c75a2f509cf01b784eb12ce2f3907156aa15675e36b4b2587e9770000000000000000000000000000000018de0e75256cfcfa2df959f1491d87dd5414a1b51b6ff02ed5034394ea636fd0bc5d3b3a3b84fa7156ca7f97aa65feea3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e86800000000000000000000000000000000026828a6409635184cb929a5b3fbb881ef013e8342cc9b5123ac82e7ce24fe7aa6a507ec3c017bba10126ad9bab5e63800000000000000000000000000000000132cf4a23eac460fb1a3db9aa43b542ae55d19f6bb2f408c399a570c1e479c4dd0462f9573c95c953bee07a51c543c4e10325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c1138200000000000000000000000000000000035220c800af6a330df6b6b6cbde47abef2e5fafedbb7a0feb84a317ca3cdb79eed934847694e85e2873ef97b31b6ba10000000000000000000000000000000011edd4c17352914beccd8c062aa7b95b913f35892c7cc5dd8f736a31a33d33a98d8f9b4be97ffe608531eb7c9643f32109545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb30000000000000000000000000000000012148b58f805c38bb862dd9847f12aad21d1ed760a022d2f619a0a077a0bd79fbbd6c066f0f6c58517ee9e912c60a37d0000000000000000000000000000000018dd847881616f7410f29d4e68854ded4e97b31d5112fd46437739ed62e6d78fab89b078581d052266b7c2ce403d3a79eef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c81000000000000000000000000000000000e36ce625adc496ac94b53552effd651a73ed0c69abedda36e88d408ca7bee73777fd87b4f55e2e8b567c2fddbcff3d50000000000000000000000000000000008a209510caa720f20cecdfc9b0bd71d3fd4015627d0227a027aeb9992ec8030056a5046feadaf149d2392fc98fd60bfc8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e8000000000000000000000000000000001373edf053517ee79eccbf02cce4b4b67d6efc53917b7cd548379c3f78b447ae5dc331285a28bc2aa5863befe2d26f4b000000000000000000000000000000000fce7f982bb8e937802fef7b3fac517054e6c9b288b03ad6497734d78d4b9074e22b1acef45938a08440948dd8b88683a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000001ac8ab3b3918836a5ba14e3d7c44eb8a0d909dbfaa2772cb9d7f8f517963662b5d4209e9a5d44ca0ed897412792792800000000000000000000000000000000169f8127198935f06d26ad8e4ca3ae5b95ad967aac69f7958fe9fb9c5b1f0e98e596fb73a0d8bf90174ca21a02a3e2c2dcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a000000000000000000000000000000000c1cfb4660400ad5d7ba2f394cefa878c6a8fc214823dab539c0aa6d08f36ff1bd706be273f25ec5f1abfb06bb57e8160000000000000000000000000000000012ff9bad1a1d71fc49e96950c74d388229d4e4c68f7fcfafa42329ae06d4dd3091b5b1c95f6498743393b6e3ee794e4ea6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce", + "Expected": "000000000000000000000000000000000630b9d9da596e60b15251aa74de850ee74c7804414e3e4d7113cb3d5ad6b985e436aa67bed66c36f135050d66f91f75000000000000000000000000000000000ab084fa126c573ed007f983e01737453b3dcc99ead0a74cc0e8d7cdad89ce81489384b311b7ec4c34736e9520b37e1e", + "Name": "matter_g1_multiexp_91", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015fec44912af2bcd34f1ad42ed24b6ce430f6d07b311d65ffd8b6d726ca23f5bc4b7437d158a36bd1790e806fd5ab448000000000000000000000000000000000c4a4de9940c7c26999773a396a8f9a6ff4b86f0525189426529d9cca037d504385dcbf1c89eefb5ae2cbbb394be42fc92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b7000000000000000000000000000000000fa1258cb0d8a37009e8c56228bbd11aa854a4695bfe96ce205efc1c9f32bff8afb64df0fb7863512ff8db6b091f146200000000000000000000000000000000188f128e662e8d28be612c8a17cfbf28b965340487df40bd3f0312187d027cd23b50e713e21f8595bc790ab8011919cfc193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000000c7b39bc2477597e37910b1888ba0afe5ed03e809618bca0e543add93519909b6cdd6281e2afa65a9b45627dc1c6334a000000000000000000000000000000001335cbe866b3139dbe22266c4ed5f9fdbc15a1b338a290a590c03811b6448244027c12d118e6f829dcd352a419bdd8283dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae600000000000000000000000000000000178ea2552d03f645fc3060a61b35af6e3e12095ec65b2e9972a5e346ac1019593298925a887e59a94af2adfac7a8361d0000000000000000000000000000000013996dc427ba51c4ec1f67b30c95659f35c8e71a225bf357f636fbfb428140f9b9e5602eda78bb38e87e3ab77495e505e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000013555f26c2e10b79f8f2a4c397dfda0d8839a35a7cc15673ee5da34578f3fc4d38bd0331a5c42665bf40fb2cf693f31e000000000000000000000000000000000bb16b5b1dacac465a751a68b99def392a69a293377a22194fa4d4d6662b912d3ad804cbe51a4ec4792229de57923ea14dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d7200000000000000000000000000000000120183e73d23355da316783eb47ca687ecd34d85e800aa65d2c95aa5f8eb730a33d3273307cc05d81fdafcee5138a080000000000000000000000000000000000171f5e63fd3c71200720cba782ab863ace945cf405a2f961baf39ffab2d3283c26347ba297d16c3f2567814c6f9914e795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a956800000000000000000000000000000000095ae1795306c8a8c48730987a842a05fcb263d1f9ea49d3f3c0ae70c7ff636fa4e7fa33a35637059c0b11b1b1adc6e000000000000000000000000000000000185e08447394763607d6efd8660118429469a1f6e7edd03a7a3e12ef99c2a15670d1f7ca664a8a14f52814db9810ea2b25b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f08380000000000000000000000000000000012b0afa7f55ff9131a9399cdf0fbf2da69dae7cd504a0160665f0cd74a02163b8ad7ab05cebf3195495a1637134cee450000000000000000000000000000000002a130747763c25b9b6c0436390da91f02c9d5b24178318717024390a841baadae6a9f933e7f87f7965fc96bb498ade5df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e0000000000000000000000000000000018911ed6adc5f48db7221656c622c6cb981b1ac1bffd64e30662035c0daf4bc5accbd53cdb1fe8eb60628262584de15a000000000000000000000000000000000b753d21d823d1050f109683c7c153514dd06663ed0ce118e388d18d36686e94588159e5afbeaa492d021a700caf2dfa959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000001107292ce4d57209e9c1e2c396688ccbe005699de4e77b1a221f9004585ae6cf8f901da6811ad85a88cd85cb819d040a0000000000000000000000000000000012cbe9c273a8a9c1404abe51af4a647f6c89e7e177efc04233586d70df6dad3aacc9ce2a9fbdcf2ee5c73396fe4e498d26ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d7000000000000000000000000000000000297e52ddc42a7da1025d43f46df11009ee035a9ac45e09a0902ba86fcfc5a4bb4c35ae8b0e0c9b86a8ed7e5ab751947000000000000000000000000000000000319c082c39ce4e59b952941dd7d14f3fec39a9eaccdf7bb41a2b935f876ebbb6778c90e1919c1e5804df91abd3bd9d5f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c2000000000000000000000000000000000ea5a9d96509cc5675e165e3a7c9f99a8c6b7be9c33fe5fba895a2d96a68e922271c90badf3c41b3ff52f359f5c6dae300000000000000000000000000000000106614bf5ae42409881f4889a82c6a3bc8000bcdec23b093ebf29b24cad128aaa7aa17566c4293f67af010e9b5950028b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb300000000000000000000000000000000145447f37207ac8d58c706af0b900dfc1f2638f840a0b44fa65245b5e671ffc6c008951ee17217e010ea6cd5e8477d4900000000000000000000000000000000187c607539f8d2b6afd15efa353e2fd1580cee48c469992785f02b3ea3396db5359e0d6743ff8d41648fd8680a4a8c2bad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c10000000000000000000000000000000012a022fc2dd9c201e9d86a0983fed4a71abd086068b8ab8c9586cf51230acafb084d559239d86a3713aef4b87a04c09b0000000000000000000000000000000017e02d69776c705bdeb9fe06d412a67601c6763a19c840f15f96de0fecf782e3a44118def54286cd52227361f0db3bf93c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e09d94291ce5e8310871aad89e0744e6b319b4fb1089048b0181cb9e885aec881fb7577fe0e80222793068deed473560000000000000000000000000000000017c8676e4b8216a98d9e9a05891ccb74e64d72a5ae76dba1b5ab2d1c4eb8291cdefe7753abc5fa59efc4a4834f815488ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff70000000000000000000000000000000008902b3f9b3ed6f0dba21e5d6bfc13fac8f003b3e11de4b883024c3eca0d2c4614604d598d31d9e328c7ee4a9d9be6100000000000000000000000000000000017a918bcd38986300bbc7a401e09b9ae20ccd382280b4e79294b6c8ae7bb1dbe2f72a582e0125381ef2b4fe24998e72f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af", + "Expected": "0000000000000000000000000000000015e9fb1d1a586288f07f399b87c37a085df405bcf88505a7d2b0ae6609d4baef7ec358f70edf838d3bb7291c6e5a413c000000000000000000000000000000000cc7d7e2d372183766a842f5c14c1f2a528d502f1bc5dbf5dfc9d812c56503a0b7cf1e6f052e998aaf45cfe24a261551", + "Name": "matter_g1_multiexp_92", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002cdb1466c13290ff0c55c38ca6afe33efdcd09ddbdf7461d6bdb3e36fb5d8be851458620a0bf54932c4ddc1778c97bd0000000000000000000000000000000012755c81c142c5051ec64de7c89719cb59d9003fd8785ed5b36993123418e49cd3afab18b599deb72c969936633a956114d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d9800000000000000000000000000000000067b3a33aaf3fc4b885035e60ba7f3afc7ccfff469cde1a67f48fd8cdf4b15b7beb9e2fbb13daa9283598aaeaff5014d000000000000000000000000000000000bf43cb79d63db544b2db14ec18c11bb9114db93662e8e6e7858d3e4a99cc332890ce90775b6c190d5ed418571fb907d82cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa86260000000000000000000000000000000019329a66132ba7ceaf5c030fb4ae9a599895aab7df2a27fd92b55e3a52b99ac51107e798175f2af83991eb63147901d30000000000000000000000000000000005c71bf6552c314dda4bf9f2b4fd8aa368c9e88c0cbf4b1c2bef9137d608738636f40579a360bcaee1a3f12274687063ea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f9824900000000000000000000000000000000048a258134ed95f91070684d04b83634c2d4c16601ad259d41e7d27292897a4d4ac76eb73425583ab1718b91f151019e0000000000000000000000000000000013a0b600765fb760919bf273a7b88bba568350ef82fc382babafd40a7e006e6808a03160f3747878368d8f6b31c619b1e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d00000000000000000000000000000000069d94fe286a9d39b64756e79669add0f66db69ead7db5b5c2fa1a9e5338aaa9051457a3a744c3b08d3afec8b87d2e9b00000000000000000000000000000000105028835bbeff46cb7d9be4b21f07670dc5589603d0d695355591ef5f7ba28c04c8e6dc40f0bdda031bb54a5710b4c0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc0000000000000000000000000000000015e6aab154e33627f92560e3def26d936a8876c52490732c807749cc28e34cb98fe8f86addb30e129f8149c504d1dcaa0000000000000000000000000000000005f6040a129df2340f3c3fd0935c02cfe162fe1afb58dba7699e7e08851b3a3c3fba36745bbc769aaf01a4f9a401d038eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000db5640083674fc75c0b0d1b2d6eb2b03cafa2e63d7a65c894d9a76b196d92916ce85c708c6c451aad65e0b439033d9b000000000000000000000000000000000ac8d6b508ff6797668ded6ceba4680443516d601a155cff48a51297e321417bbffa6eee042255e9ec054d837bffe628acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a90000000000000000000000000000000011daaf23ab5fc0ad7abbe7d5f1dc26c8ce388491cc049f01f287eb9b133e52f33d40f8693921d330ae57853539ee30c20000000000000000000000000000000017594ae7ac7f6e4f02df862b6d4ff946ac1a47085b554ebaa720ad3291f576ba720dd455829600f930e3964a44e5c7f30cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d200000000000000000000000000000000174b5b9d4ef01fc9d0f05a03612210690d7d57ccb772aa53175f11b9623388de8019ff2ae1d564e7b30ee06bafc37a84000000000000000000000000000000000e4c03b8dc45b0567e9ddaa0a085d169799d2a595c03f2ac679fd858cd59341393e6a0f62dfac0e53598af4758843673b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a054400000000000000000000000000000000197f0ad6576bdddb48c58adb1c9b2115cd9b38368dacbea9220d6a86bb621dba93325b676071e38aed2338273c98c4100000000000000000000000000000000011514f08bb28c37f078a47b6a0d53b311d5975c8a3c8e2c24a25f34bfdcbea53bcfa14b7f23adeb20bf440c87a251a66825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a3000000000000000000000000000000000aa5f3a29c47fed2e4a87bb4c2a46a5a17102535aba9426235d42f00007e35d1c902b43c1068af279cc9a1b689a0dadb00000000000000000000000000000000056d9729f8faa8e12027b993e8dc41a340d61c64e4388c3166482ddecbef8d04085d6ae3764f0d9cfe76288929749235307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd00000000000000000000000000000000070e376dd57cc8e2146d49ff08c6c6ada6302c36c4eefc3003f0cc3d75040d73599c7e0c2fb9f7e24484c37262f0eb330000000000000000000000000000000016a272b79edcb7e7fa92400bd55fc937d6389f1f0d3d2168656815845d92ab1e7b555fd4ea311802a62cb6c94bdc5d58bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d000000000000000000000000000000000bba6375b28ead3d49197ec9d3662e34c70735ed0f987f05f439da164afcbe98f25d2ce7a5e1e32515eaa4cb7f5a1f98000000000000000000000000000000000b1ec74ff999ac5a7a3ff2c91e93e5f0edf5f296b063d80bca22fa64198a798fa6b6385d25cde65b789454bc2674231058aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000012fadbd9c50f2e8518dc15d95a59ccec0c9886488ed4601b3fddb2bddd77a4bc861f2862c9c4666622e42a5dda7138ad000000000000000000000000000000000b2aa31218a13b4ab0b00d1b76a9ac7bb3d7e6473a29f2f0d137ca63bf7f152954e52182d32d3de31df0e6ef0d102c9e6810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff000000000000000000000000000000000076846443079520c5b1600d5faa5a6d500998ae355c84b9393c79f83f1a2485b1809058bc53cf5f8a1a46bde6cf2e300000000000000000000000000000000012027dd1a4fbf6078b70c507fc2cdc0fefc9a0166694c796eb26e9838195e68fc76297e66e2a0e9e069274d110efb095c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d4700000000000000000000000000000000183c019c306c08401b4f2c1d852b29dc47b56bce8cddfdb66d4e3d5385e4bc75bb9806da1eab476ee02e25ca2b4d41c900000000000000000000000000000000066d56711b80dc8725e112e4e2af6c939977aa66c931c6febb21735d78f5afca4bbaddd77387e52dd5bc9c29cf26923613a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc", + "Expected": "000000000000000000000000000000000f75ea9863e14f6151c452f7a4268b099f97d076b249d02d72caf5d52635bca3c686070d4a4bf997b753c283c82cec600000000000000000000000000000000014402b3e738bee3bda4e62c077e9d355ad8a71b0830ec0e67a4fe6dc59b1f3f7809ca7d07184f53c5afed32db327598d", + "Name": "matter_g1_multiexp_93", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000107072809eaa84dfeba5a95f94aecc2c985c9e5dbc49a741811fe4b2393ba7f6597ac99d8e80c0fbf715a164099e9d5100000000000000000000000000000000124d1694bad88200cde42f1e7721f3390df8dbe4745715a2f0b6f11cfc78996c6f342693acefe88b3d83736cac6e3e05dedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d200000000000000000000000000000000188a853e19d512149800bb0aabcec450561e5ad08b5159e0879422cca1f957ee15bad2b881979d7c8551eb19693bddf3000000000000000000000000000000000dc097932535d21656842615f08e7016f55752556da3be69027d0dea621ef46cc65e335873e041a3dee6c7e5b7589dd5db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d3a53b9865082b23226042f69ca71b99978fd6dc3c8553e33ddb12542d05b026345a23c2b24dbd934be2ba3cd585162000000000000000000000000000000000b0832950405431722c23cc78bf0b9f33c6e2dfecf10e6d503c8c96ca9732c7e7a29251fa5b5b161d14b7155a50846886e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca40000000000000000000000000000000014e19a8a203bd2e9e9601cf6feeac5699a3b2d2129b6e756b9b5a7af0cd9228083de8c9a2a0ebacd636ab1b662c8c0c7000000000000000000000000000000000faf049bd6532cdad26403b269d7dbdcab6c147ce0ddd6285ad9ae0e8ddab4b6706bbf038fddd7f63e6dc9a766928ec327a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000007e2e69d6c96b1841340c48e8ab070c67054b574bd5778a8e38a9873241baf8b85deb73695873fdd9e3387fb1fec3b6b000000000000000000000000000000000fd151202c399636a360cc014c90caabaf3b01d5a6114e078eb2473bc2fff94f1c24597e39a3d2298a2e9210726bb48e446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000000ad0e842dd19673bfb8534ee20591a9076268eb203940212f702131fc6a3e7b226a84324954eb4bcfa8a007669d2317a000000000000000000000000000000000693801615c5282a327ae034c3a1480de0e1c471a412f194178a59582509ac6fe8ea22c8ec8e98034348ac465527f4b35f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f000000000000000000000000000000000ac9f4f22670b52e0e85a37bcdd729b40c45fcbd6e8aa78626752d736771ede9c570991e347134f95385bd77e404e4700000000000000000000000000000000005964a351f406083b14726ced542fc6d95dcb8bccbd41aa3ca9cf0395d8d29143b897c66c78e2fe56eedf17d4d6f6c1f94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000018e270b6208be13c23cabf52e31a156209abcd7bab03694fcb7035b453bce8464fa1e090d59a1139fe451d8c699669c800000000000000000000000000000000158dcfe7736f4fc63071a70923d81db9f7d2a03512724dc41ca47a873132da66eb0eda58134312fdaa63ecba7ab529acb677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000000a614d7a53b7a06e71aea4014f9b951bc19747cd8822da50f7993c0821e05100dc5fc8d043b2cbe7cc4dcae9837679d0000000000000000000000000000000004e0495281282aeeea480fa47f53f8b521a7df4c5619d4e58f730fe346a6deb3d501ec8b55b581489f28b4d991ebd90cf5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000000edd664ad8b77d86bda4ba772f677d34c9341ce2b4d2af4b2680383bce0fd4468e936841dd57753d06c50a3357a47eea00000000000000000000000000000000063eacafb540655984104f60569720625e4499f048ec7849577caf240634ffc42612ca7ca92c17e3e50aa627059cddf2fa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000a9d9bea7d8a058cf254d2b7e10f6d2e8244cf131c6f87c4e25b5febcac352d02b1b45ba347e0b891c8b08e7b5dec82d0000000000000000000000000000000001d256cedcde615d01e15cf526c4a8bc8b565055567aa1de1847b524fa49b4b9f654f5b66cda0a78f414848aab42b05c93d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000001519b05b59250c72c9db7f425954694b29b36af21d9293a36d7bcd1ffb53d0ec55a3ceb7980580ce6f9fb6a0faa7bf3f0000000000000000000000000000000009e7d045b69e2dccad22dac427f5938974a6394c9fef84633fb5f90a0d09d437219f1b7ef7e7bb03eed106948eeb560d3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d060000000000000000000000000000000017cac7707469b98c6b4d24fecf6d818dce6c8b9eb44bb08d6e475e385c30fafc81551e74ee98cc854d38d77d15459e750000000000000000000000000000000019d5bea3e48fa7bd273233bd6325bbe38267e4950dca4fd9ad051f487e7933a366469107258d69f0603b2f9a8dea2e4f5143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000b993d9303ecc19122654d5cb10d488af5411c451b39b1e19e7a104477da50324472076c55c4557576a9e5d7755a381900000000000000000000000000000000172b34e576f0539e32c5025b3a8f25b5bf407f3f3dda863b194a9fd97d3a6facc00902c95fe076b91713bec162f61cbf0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000170d799ffc4c0abf6c582b41732308665d790900ef07a74183826e48c9f0fc500b09109b2b13b2b33cc17e6e639d2969000000000000000000000000000000001943fe62329fcb67a45b5155da7f950ee12fcfe0e8e9ee15868409ae44adaa5f03c330206d7d97fa733c9e93957755a0b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b00000000000000000000000000000000078681739039a022499219b298799027a341be64204a34a97a8115e5e10486420c18664825b764fd7bb931343c2558a60000000000000000000000000000000003313d3482f952c6f9cd4ec2f2b61f28ecf7d8cc7e60f17e9aac8e63ab25dd6bf2da2d67805debce0dad8fe37a36625298df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af", + "Expected": "0000000000000000000000000000000010aaf24dce0b05179769031ab81db339cda85cf68963640b888d3aca310a6de690150166c0943d658e51524981a1f391000000000000000000000000000000000d1af37c2bdca5886d73567cb00d5a9859755267800d7239cf39c291ba83b1c67e6a3532a2d8e8590c1bf2d845001038", + "Name": "matter_g1_multiexp_94", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019401d9118a5c2b0c6ae40507cae6180083258eb6c45cb8bf2fd5d2703c95fb07c031c82d0568a395e18015fe0a48a2b000000000000000000000000000000000511b992882f75fe98131fd35276b7a1de527b94718549bb4f5c9980917b6301a86e45fb7c7e3ea99e54158e49c7e60ee49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c89000000000000000000000000000000000d886eedf2a2b33a50dae5ca6f41237c9425b0a4daf08bf4789a3ea8c7f598d53257916d9c03df0d63f12a1a804fe0990000000000000000000000000000000012cb777812e76378f04fdaf2cea12456aa9e11b4c3ab0f12e63fe7ab11c716562b07b3864cb9dabb7970c81bc1da324c79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f39530000000000000000000000000000000009f09107ccfc5c4ac9b7e0058d6a0c4d7dc4309134d5fb972de3156a554211d4a2fbe639bb8a93d86091137671ab8447000000000000000000000000000000000b7f9955092221c8a2f09c6a9ffe6483ec0f8a0f6c555ec1772c260fb62c4ada6dc7beb92e29620afd15466b5f025cbed2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b0000000000000000000000000000000009a22492a1b78342b919f7b5c8fcdddac408cdd3e8af4d6de5a4b1e510fa3b7e0e6887bcbe074fa839f2d0dc892db631000000000000000000000000000000000e5eac3a77c7a3e89e9324abcc0203046948f3d62e40156a5e1b1d9a274d408d6cb49e06b8cfcd21b596923f86c02c6be9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d30000000000000000000000000000000019914b405a24e72896b3d231582f0075fa7e59b0d0bc796d790754902238943ba634dce66131260efbb5dfc3925a1d54000000000000000000000000000000000352a5a986c500e41d2fa4f65e5a917061b3f9449c1e720caac187c6bfd4ce14f1b49ad414864a1510894530cfb4a768c818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000019567f8de70c4cbbb25335e69154ce48d4106c8c9d0027e17c67777dedf758203b0a8fa3863d4e7812311f6cde36a6640000000000000000000000000000000009947f7401d03fa8b0801b130b43f729d6a71c04edfaf7b9d3265f82b039131fa09f20f9b4565d21939ab7dc7dd3477e9ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c496450000000000000000000000000000000003c42ae9653d1d1f00d79f8b1a0c53d0f2d7f3ca52ca1960a621fc1bead7ab31cf6e5bf30c5cf7877c83b33b6b5b54d6000000000000000000000000000000001221117f45dea3fa1f832bb8280512841ad1798b76f1dd16dc320ea7c86473f6f8c98ce007ebc3ebc39e7a860be987fe96acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000004340b7dbe7c27014add4ecbdf310de758ea5dd1100508a96501ae3caf9955c877113971a61f66e3691d09f0a259d4ac0000000000000000000000000000000001d5f83065f6d178b4dbbe0f00f0a88edf0a90021601bddc2cc27fb0ccccce7e48c6283a1e641408a20de15219b5553e60bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf0000000000000000000000000000000017679efa923688425fa9cff1f8e89ae681245371017f574f4a655aa780bd11009579d7daa47249f503592bf0ab79e67b0000000000000000000000000000000018f57a1ee533981c8df24895ad174228330ea361448ed63e522637df44cc1b888e969ee94d7b44bd532b655123f8f5d8537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa9830215000000000000000000000000000000000bf47631b34b2694ff7fc5d1e25de2195e606daafec34fc2c8ec86c0a325214d874002422810a81cff654eda187076eb000000000000000000000000000000000931c54d05eb43195c3ff6b396e324b5878c3fd507637c316c62b3b6e2d3d84cff9f33cd1046f1939187979330d3fc431a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a200000000000000000000000000000000138adb70a3dce09176914deb0be17821cd0212c6ab554f7e200804dcade06c6cb5f7b084a1d6ac0ef8eeabc7cabe7717000000000000000000000000000000000a4422c569aced58938abb7bdbdefdb27cb06677c1066d17f98a59f847928d1bf2343acf8b5d1717aa38cf81959ac1acc7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000fd9ff095adf9e3f666d3141717ac4a96deb5b4f92dcee35be1d305031d06d51ecabf863a41cfd8dcda0fc94ecf79982000000000000000000000000000000000fb55855aab9e557046ed53421cd3627b519859e26338328d7da249fdfa6a07fa533f748eb5dd564f9922ad911121b2784f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d000000000000000000000000000000001054ff028d2e2875330e3d0ffc52e2a83ee2ad2adf024ee294f695113d9d645f0be2a3d3c70f758f43f2deeb542aae810000000000000000000000000000000009a5e96cd08d3ee4e740e2f7b94a4e390ab5f6f572c4a1b2d927a7ef2365557ab9be65b8e2388fb571a3765892a96445c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea000000000000000000000000000000000bb1de70113edd86e5304248fa2f857f1620dc8a6bb28680f537e04029aee158e2ead4e0eaa373b812f6ca988dc40e7f0000000000000000000000000000000012118b670c9df77af087ad01e3b766d4a2b7c2b2a319cd733ed6c02ec36d9002036964fc442db992bf730c57a7d0a407a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000001968aed17e572c0d99e4e9262f239771976dcd9d7df19c20bfa94aefe1d4f3a3117bbfa4a6e329bc6b9552731446dd10000000000000000000000000000000004e64ce59b928e8cac2f744bef119018de8395b712013b0c69855fbf2bdc6a750a947b1a81c9df959c78367ed0e1575d95a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000148cd980512e0aa153adbdef262f098b1ece801ee4024b5561e261d39b495165851781d519d75f83dc5f298d40b4e9e0000000000000000000000000000000001dd43f37950976e50071226b6aa47c229085807ce9634e6583f5a2d47eb8547d4de0669b16a2771791c9ccdb4289cd9319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40", + "Expected": "00000000000000000000000000000000059c7ca50efe2d0a64b55a560a08976b437c350860f9599fd30370d1cbbeacae147144c216cb3e63afb2ddcf34282a5f0000000000000000000000000000000018f42ef2fb8eb6cc8c31600a3be37ece06ee567ce0c8b8c7a54c910041b8c25b2d94a94722904de079737f06e817f380", + "Name": "matter_g1_multiexp_95", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000172bbfff135f33357b0dd0e8545da99c4ee74d6414724c2aa66ffc85f3a9d0e35ac80850436745a12ca6f1c4ae5c0ecb00000000000000000000000000000000152dac882023cce1a3e1fd4d8d5aedcdf6acb2ca9628a94ce92a4a551b1b7268589b52b2c90af6e4be9631eebc2ef8a62a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000000c40d04b3002c21b041ea8b8ce967056435fadb479fe1fe20c373b2e2c5b568b7a38d031424bc835bdbd85af8ed1d0380000000000000000000000000000000005e7357194364947c8dc32fd74757a3f3014e914dc25f42b2dd86230ca4f86981476e6f10b1559694bc17d014cd243d7f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b000000000000000000000000000000001474002c92db026ff5bce69eddf1d8ff8e6d2ab9427bb82377911597fafa4d60256836c094cd513a52a3a09797afbf5300000000000000000000000000000000176a7b311a333c2d4f6eec66e8c889ecd7becca75fb35a38bcccae52f10ff69630393fb7d87c3b6d97cd648be099c56507f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e40000000000000000000000000000000009ecbdc4836c6c0cb4ccd014f9e582112bce0d0ab047115f38ed5dd51c54de5a43321e85c9b3e9af5fae0caaf2493fcf00000000000000000000000000000000034425e05f0adb1577f7b1bf9b9b50a76bc894f5ff0e9a8d190412eeeaf80d0bb96f21478fe8adea327f69c9137f57094e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb6050000000000000000000000000000000000ec3d4fe1b5e1c26de1558d7dc51eba3b6c37ec037de184e8a6f481ae20b830c92221593e1bbe4ee76a85cb10b33e18b000000000000000000000000000000000e51f811e16f00626d934e69024b55dc29fa4ea363916cd8f44f928fda6e3ca4947eb15de24ce1952c39e9ac52d2739d27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f2289000000000000000000000000000000001205dfd803ff3688c2023913aecb10c138be4d03756e2f05a63627973f511c2635571469d4f630758627f7977b418729000000000000000000000000000000001186b9c0d2b2073b495ef9c233c275922bdbf4691e8be085051c09e245242526b13b7051782a80726b381a72b5ef9d5ec2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc000000000000000000000000000000000413f6ee9bb25469af4298dde67f0a4a26d2f528848ac6646764703922c78d65e046204f891ac94b0b4c425110fe986e0000000000000000000000000000000011860881aa871fa3a6693b23fd7b1da0bcaaf044058ea0700b786f12f1074c615577e572e33faf8b3562bc285632696d911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000000010e8fe8fd7863c2807a4bd717fc4646a0e4f99598b9c6c2cf0547d039d58290a367e4ad851c7a67e8dd546d5e328200000000000000000000000000000000063ea10e84e4f5824ad7b9b68398c9154ab25ddc4043a4990d80e09dd94a890dbabf9c3d93b13c4f40bd7b1ff32b14b2fd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b0000000000000000000000000000000011cbc725705b809ad69c5ebb55ade0039989728e7103b684feb35c8142b100175235c2b395e37a20aa40845ebe2dabcf00000000000000000000000000000000057b5b5a5cf5f5bce985295f8a50252967aa54e934e87855097eb083a59863aba19ffcec4354a5a831b747175ba10e878118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c00000000000000000000000000000000041043cea626d6ab553b95c6e09de597454a3a3d1b8a75fc9ecb3afe15bdd8b5e73b8012ead8777df8957701fc9c9022000000000000000000000000000000000185da96dd1d54bb7ca5d7dc2fbe4cbd8ac95f06fe85a7a26e5e0e6353f6a6daf73b74117ee62be4f3fb268fb4c86275c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce773000000000000000000000000000000000a06b91559964aa8e8628946bfb720047915ddf08d24fa34f7b241e16bb163ef67f1e84fd205485d17725a8386a7016a000000000000000000000000000000000ec787cf5134bbd832d2a7dc1ed87b8c824552d92fdb30a790e1c73b22c753540a9747eecaf14dbf867d9667b7b852c7982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b00000000000000000000000000000000053bfa3bd311c1780afa1862de6ae8a475b8eb9c61fcee2b63dbb6556022d703bc7eb204fb038056c654dfb940e7039400000000000000000000000000000000074ab5797d3c39804dfd5359b69a4bdd2b738670d13662eb2c112eefbc0f90da85dd1a4b6e0613785fc66b100d129202000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000cd50ddae4f053bf5b7b3237701bdee2f5167e09d824d260e89ea498fb3b593e5053b781c159302b0433ead35f072c850000000000000000000000000000000001abe8539a4215a3b7b78c79c306dcef7334c83f571f4d6836e1c1839a65c8cfa9a0811395e3c4bea26b22ac2175757e773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb20000000000000000000000000000000001e6e0709869922c36e073fdf1404a973e0467cab3a04a806361e743d67468f0d66de28f6c0c7b8cf92954330485db0500000000000000000000000000000000084e96298cca174344b7b86052426f9316a15b4031b9e42677253fd9355b1c99ed9ca3eb3949005078ba228d4167f8b0759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e0000000000000000000000000000000002c5e399eab947a52660807752ca662212cf3a201c1127dab3586cae88f8ab6dd23deb0312387178e0e9526bc8fc7b8d000000000000000000000000000000000ad86b21dbf58098fc4f758d7ec9204bb16cbbe680b58fa42821456d4fa508e42b53c8988dc0d9a4d6f6a782a5fb90b6cce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f052530293800000000000000000000000000000000181f41dfee6effe70a28e4c53bb6cec52f232caee076f680fd63d73cae24b44709fc63ee3782a36278edcceeb7b32415000000000000000000000000000000000088d9011a9db9294bb4451e9981e84efa595462e26e5dbe14e9c84a8c5ddeca94f49857cf3b8a70e6a4047ad76d234585e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f", + "Expected": "000000000000000000000000000000001170d9938910ce68e56f9e84be6f6ad284a244adbf26daf8d42f6411c9794b707454ec9300442fcb7a0f08eb91edf86800000000000000000000000000000000043679c7a917148d141a785e65b0a067e35a016c24bf7639ba374e21a817819ad67d317ee70852bd13a1fc3a373a38d2", + "Name": "matter_g1_multiexp_96", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008d9cb39df5b28781d33d996039da8c94cd810bb85aa5868008b4267ad2a8670924d4b3ad7898b33689aab2211bb9bdc00000000000000000000000000000000007a8a6f888722e4717acbfc42ef1907206db31603c403e0a8c1ac0af9b37e63124d4645a506265487e5f9eda09c8baf85431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c000000000000000000000000000000000db6af04eccb3ceedc11378406a26613aebbbc2201a9ea2089848c7af3b34e46a3421d5704242c4b333f72180f6baa0200000000000000000000000000000000105f40c8b702f0989a9e20f72ff6a4f7310d81787e87638c33a61985f02116e106218d64976d50bcc61cf5bcbbff7c9eb6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa000000000000000000000000000000000a77e39abdc9d64d72ea4b321e3310a145feaa5d342bc1a5b16c0143dd01caeda4f18909acccb3cb5b43ad999a94f91b0000000000000000000000000000000016fc4a4f6b488fd1f45a158d941d7aeb5d431821589ee845c64eb198ff10931d586f8a0678237be2a394f5976d895bc854dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000000d0ddfc05bc9f89eed488752d64698bf00633c83cc37931d95a599d6be6e4c5d611a4151839133e86f74bb91aed1703b000000000000000000000000000000000be3dbea501c822730ab0176f64903931aa46b0179c59556ee7e1ba54605ed8da2eafed7eb2254a7ddc34e553a9b6d59eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a0000000000000000000000000000000004d6f288744016f15b21da736283af2ed1f45df12067a3a70391f66fff3ce3953a51169eba6288cabd84ffe7f597c9fc000000000000000000000000000000000f6556a63def531a940269b073ea98be79558d832123dd681bb4446d4c11e2fed59a2f97904797abb07ba53e0d48e923b273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab850000000000000000000000000000000013e692a13e79c734f3758780fbdabff86fe5936bf6c60f2f155ec4d1c49cdefb97dc02c1f1e4280c5ebb055914d93f9d00000000000000000000000000000000060898a9365ae49697e5ac23e320261eda04d818c5f1153f647844b1910bb3430d3c06df9a64af8ff9dd25c18cbfa79d1342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f000000000000000000000000000000000a2a4d92ad63dade4d666ea949dd64d5886eaa3c7ce466677356ce9f65520591c1aab590b48e9fe1eaa0f0f3e306cefc0000000000000000000000000000000002a2bfc836409b33bbe078a5f89c5142411bde621e9117ddf9f81f37bd546c3e2ba94975ab4652fa0858d5a2361592715b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee8239812500000000000000000000000000000000075d3ebb18437feb21f94ad5e2ce96cbaea2f6d68885483ed54ee67f2dbcf8cfa39f405afb46e45d08cb804a7aee3b8e000000000000000000000000000000000d42851366ed4694730b7c58450c3f9ebd365f15fa4dfa3fd226d180aaa921a0d897278506ede76b85decddc9580a365249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d17000000000000000000000000000000001722e05d33728260ebf5e4b48104cb2c89b4bc3073767e56fda373bc0e29261c9a5c53e5768b453b116494c1109cba2000000000000000000000000000000000030e4da8620007236b89103b215e54751ba2f2dce19b0304997f450791880ad34f3e43cc4e6852aa599fd65ef72dd9a5c014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e895000000000000000000000000000000000c4e7e44e8e0387bd99311343d2ff3a080ddad557c8639aad64c4f6e47d64f48b91f9de2e33b4b9c182a87efce5d4e0e000000000000000000000000000000000e7cb49fd7aca3daef3c0329c950c832e1d007f21a4f950f367eb37b5d7433f5d6f1ab1c206232b2ee32137b56b53967ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000014026b8dae20a1913ecb45359e9ceb317137244e16a036ac760b47363f2d389ef6cb12cd5f5fb9e8e31ccd39bf114f8b000000000000000000000000000000000f07f9e76789dd937b85e02a9c346f81e87637bd03bd5f98a9b18ad6d109100b540aaadf1fec048530bcfa35dbb5b8ae8b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000a0249c354052094cae5a3d77313360a8956839af614184696b5b7fbd2af6555c6ae14a150220f01d624484b9096eaa700000000000000000000000000000000043098df38ab37f42175cc9f9fa9ecbde75bb344776ed078632b3d8bbfbf04103adde27ef0d361177bb3814cbb8bc54994ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000011e90effb7ae193b47afffe6fcaa0a28c358222cbb087ce479b7fe88d25386c5a9c9527899d7633eaaed9d982d3ed4e100000000000000000000000000000000174877f80e5e9daf2cc219545ce67b904319f75c0284e41552662512727c1e05b364364c4c8835c1c9c6fe028ae45895b1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000170e754e54f64090c4c7520bfed82665b44728904092fe3a4fb2fd2d3667ccd4ecb796e5ed9fc4dafd315c0b6dc22b86000000000000000000000000000000001081e62ee7c502159f7a8e28c5ee45fb7fc5b301f3a081899bce10096c74d1bf7834d12cb7fb1301b986e9c6f7501d53e670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af85000000000000000000000000000000001434584d8d1cb34eb29fd1c95871f218f4dc46f8b2ddabafdc7049e88f54fa4b80c88960a76411e365aa65cbf77f01ce000000000000000000000000000000000e4e2e1318c5907a07a7ff154b07e959d681a69c066585ba046b8889d417d01c503b32a924500944d43e68d7da8da35d54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000945a9d0603a3bd0278fce30f0cf97274319a760291fea5aee143c364cc0bc60e59dcd1093aca1a3ef64696ec47845e1000000000000000000000000000000000a77cc690d55763a94aa48c210610833427ed3176b6dca184598755f539359bc7302f8dc2cc941d447d9b5b68fa716b70563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d", + "Expected": "000000000000000000000000000000000ac708f97c9e9fef4482af7b95c59c2ce6b8826f6b046b5a29ee2224ad66648a8d1f9959ff434c77394199fbe75780db000000000000000000000000000000000983fb55837062305d7fbcfe8c76cce1c431067a97392923720a6c4f963839d7d2661e2a8dacad1f151f505748239798", + "Name": "matter_g1_multiexp_97", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ae003001e3173dbd17f4d6598fcdaba9966f1e22a06ce747f7d2a06b2bd37579d093242a4940bd816ced07ec1917365000000000000000000000000000000000b27db470845f285c792da64e870b818a7598fb820313e075ec72e78f59f3903cb0860b749bfc67540a8bc80e844a8de5b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e9900000000000000000000000000000000018a33b2c2f1ea187672612b51c8dfdd9e86674df58ff4f77ff3f71628e7aafbb80ad22f34ab4203c42bd39a4f73c3d6d0000000000000000000000000000000017c3a68d8782a479ba9aa829e3f261a3e1b832595fe3922d800349bdc2bf58e0c1b523eb0924bf0996e38aa83267f570a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000015347adf6539116167ee71557b78d8fe13373512ca7d8d365179e25ae8ed2c6a65e1f643cb0ed677a2f44eab809d5b640000000000000000000000000000000002360dbbe0b7f8e97f6aec4b20a7e6525d83056975a4228901b4f19259c9ff2d2ee00da9bb9085232fdf843e5d305561677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb6071910000000000000000000000000000000005b62380515d49aa1427800077a11a8f01ff00fe7df53a13a9266910e4038167ab747bbd0705fc25ae2cb0e2451c893c0000000000000000000000000000000008de7bcad1c67d7f1fb5cfb9d20ac2134006618ce0d22f4120f5396bf8164c0effb0e3ebba7959e9dde757973080a9cc53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac00000000000000000000000000000000185bee837e3212323dc40fd471ed9a1a58f2aebfcf7f07ab761d40bc1ed77b385a134c99385d07e75c5f8c51d6496482000000000000000000000000000000000d7d42e4e18040da671799f981d404085fed490182d397685498e80967cb9c080a766d5c8822152d78920fb388b979f534c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d50000000000000000000000000000000012961da3be1ecc774fc9df2dcd87c337ee50a99df7c4821fe08da7327276a24d754be95b6e916d5c63926b6e44b74310000000000000000000000000000000000e44d11949fe33bc3a0ddfcc74c5b0fa79cebfc0d4a00a574ad7659c7a5e72c728ae4ee031af57e9135a3eabd93686edbebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000f03c013d5554584c2030ea02cb451ae508fe6dcba72bf7c49cb47a25d3d65eabb2fe043b9ea90e03571aa7b64be8b11000000000000000000000000000000001479789662864eabf677d2a541e48e5ce70f35a2cd6c0a476d4179d02955a51123e75c650888e514aecc85d67781c8c18b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c959600000000000000000000000000000000139bf8fb623dd156a3fcc46eca51e61155cf58e2dfe8edfe717effdd4418c833db7fde2031ef27edb4a70f9d60d67440000000000000000000000000000000000c352a16159eeca4dc9a86601973c02e39f2a11c8a0955ad52236d7e46dbc405147258ea8558505bef0f09ba92527c76d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca0000000000000000000000000000000005b8c4c2782a2a2a3abe4f99e60db6ff4179399aef4b9e305fe037e1a14a4c03ff59be1e91f55e5bf316356bbaf876af000000000000000000000000000000000eae605cef3beee4a176a0589f2676b3e212edcd7ac5834ece3066bbbb587bdb6bbe46663acfd9d8aba2251a238004106330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b8000000000000000000000000000000001038258f67b0097ec51adee244cc15d63c4d3bf1b3b3e64ef8ae6ac15a7c4195fe97bfe8c5a42981a2463ed1b39032de000000000000000000000000000000000a6f27fc1f2dca48f6e26456de5d9fb840e4ed3fd9ff12372e51130d7c439f4ceb4fa929da2dfa3ca271d34e9aa0985ec2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe374000000000000000000000000000000000373fbfebce5c599172ab017e8f4f9813b0e6aef3031faf61c336aa7d6b64c8986827a27605b476bfc1057a0388f864d00000000000000000000000000000000079ec2c41547d98277c60dc46a61ddda51c9df65a8ad2d0a64d483eb245986de36eea2509cf7949c5fb05a77f9cf3bacc9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000d50821953bbbdb494e48c59c940c5f2ac2b902f4c2ba2b2ad50960a51ed7eb1a9d592bb903a03b0b90d8817d10848ba000000000000000000000000000000000bf0898bd20e08205aa218e529db578d5118ae411159ed372eb8968cd773ebb1619f92107d2948020bb3c721ea63159dc2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a0000000000000000000000000000000006bab55b7648be3eaec947694311289f17258876d74a7d92f22b7807d007fe142a71210684593b1aabf74579eb1b1c17000000000000000000000000000000001016b28dadfe9b65d86a1f843f7ff4b774eab74431b68b079527c2387ee6cac69e95ca564346fc54237edd3d2d31f6ed9fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000c0d5ae44a0863ef3d6d32f1d8f32f2c5b89112652e2e3d6ce620479882fafd73cd3627f9f11315020c8fc9341c7fb4800000000000000000000000000000000197067de9d61733dc0367d91f55a57ae268d5e7babe7882c1fbcf03cc38de7a2dc41acfa16bac0ae63418fc349b9471cd4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000c3964c79741fe8093ccf2f3d118b33897a18d920ca242ae153118bc17bf0102fd19a9e4000698b256930a2f415305180000000000000000000000000000000003ce4a6877879ee56299ed27f634571126d9f8ca8ccb1e67100064e7efb435cacb1ada74d7c7529b495957ce7a5dfe709644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000018e6260c0cd6cf806ee82a047c51a85e0d7023883cfb05993ee81220e0871b122c12e65bb99b20787322d93b82089e98000000000000000000000000000000000d5b66fc46b7fb60fe8efb6659bbe948c6776d7780633f007123c5c49f5fbe7e3defc0f3d896333d0ca01244f2b6effe0df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d", + "Expected": "000000000000000000000000000000000c7aa8aaa848c068104b88d0757551612285327ba5fbe35ccb4eacb1bc96a341786e4548f5f86f6ded789b712a4be74b0000000000000000000000000000000015c9aefc358834f21ec85092cd69df37e0036ea6ddf23a3ca052a669446b183e5340ec7ce894ff6575f5e6531947c84a", + "Name": "matter_g1_multiexp_98", + "Gas": 64128, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004acd4cb6bcfed3219c3aee9368feeb58d77a7ec81d19bea11402015f4bd0ee2d7afd86fa7ae9dd320910ca28eb6d98f0000000000000000000000000000000009fe1b0094c0c2ae80a3c5accfed5d212ce39f867aa2150b781c193a0053aecb04d06e005fbfa0a24595e5968d024be18a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a8330000000000000000000000000000000018cf9bf39549c35e94211b4e2d0a0157d73e1ce8a17cd724eb33c38281dac07e12eec61b27b440b220c4f21915a73a52000000000000000000000000000000000fca6d956989db84dcfe58b0310fc21b5bdc82a32838c8d9cae912d683dd9c67f68e15b3fbf9d7b430ba239c8904fdd2bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000001897956bc232fd5a9b0ed1b533bebef8ddd9e97002513eec71d67ce1086ba8473f2c013af7d8ac548290453d9f71bd5a000000000000000000000000000000000796da5c8ac165d416c8fa36d84e11bcaa80c1bbfe18efde4b4b2c71d6d00fa24f3d51eac312cad9e854f094dcb6ec7458b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe4000000000000000000000000000000000302240769257e92899da03fcc4abe1ad3944b74c3046e790e4e950f2958426b5fdc691401a1c8a531f42185d382fe5b000000000000000000000000000000000053750b58b6d2fbacae94e22b397261e541eb4abf4715b3f528dbfc3388122918b1b4b506f2fef89ea936efdef0105b3b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe000000000000000000000000000000000168a635a14f61734372f4bdd2fd564d77afa8588e1828d88c4c90bb50f57473b2c20585dc0e93726b84e73c61f29ef1000000000000000000000000000000000e6e92355e59304ad35b1dbfbb98db803d5fadabdef4fb1b2a54080ec9a33a7147ebb4d5219acabd949337bebbffa793b049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc000000000000000000000000000000001797bf2ac9b490cd43a346fdc64bfb22301a0a0e371bb4df8ec02342b4fcc99af43b4735665c6b1386fa04a3dc5406e3000000000000000000000000000000000fcc20f4aec04b7896ddfd86f58c2e1e9dc6f863ec3b477572c073c0f4fb07ee8dc0d5a843321446445b6e7846fbc5d556197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d9703300000000000000000000000000000000013ddb8ff149222a5a0a997c0b89aeee36a6ff2540de3cba8bfe6a2a64fb505f13ad956a3882082ab85bfbe72f3a3a6b600000000000000000000000000000000102c1a1085f60cd5326966a2dda0872290e1658002ff3ed95c47cc0345565076bdecdeab7082bcfb439cf7f3e445faaf721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000189515e637d404ce6db58d24774609cf946074aa22066d808dc022824a26b381bf09148005c61156a976154b025d71c90000000000000000000000000000000009102e313c4517cdd3d07a66e0013eeafc996c21fbf5f0f3e7d232ad5adb781cce1657bd5750193cfc0357ff55bd012a461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e0000000000000000000000000000000002e166e475ff083faad64667b683e546b2358f945b8656f9c2f3f6e87a40dc3fc087dd94874bec1c4bd5929b7c96024a00000000000000000000000000000000022bb4ba4be638d8c14a16c94522c41cd3b3ad917daa454f820b8fa35e5a48c676266feece6986e8fe920b2a5e43e4b3569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d0000000000000000000000000000000004947bd8ea8cc3b116fb7320c573fff0f107913c18cfdba2e7e9a4c8715e334a431156f384548508df8950d681163aee0000000000000000000000000000000001e9e7494c295248184503344b8ac7bfcff41a4561de03d78691ac47980f14aa47c1eaa3cca80103f0f2ba14a2842aea2061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000004789b01538cfc54cad0e99538e874d13eaa7f07199af29d460927c3e622c74e0bb4185afa12c53446f56033348c332f00000000000000000000000000000000154291a8bdefbc91445ef1fe123f326b8aad652c8c54502920d4dfa912c2f42d784fbc5a16d08468d2d6ee56e7e8eaa24129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000029048f227fe8d1b7247a82cfd3e1b4b60cdce6b52de42c4b96641bf8fc5ba9b077e33bd4c4fce9a51b63a6a2451b427000000000000000000000000000000000c83518e1b7700d68966d592cb2e3295a2db5226eb6fef972c8a84721d1e49a30e4a8ee3494ed4bbcd2a6877e1ba597d366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e83467290000000000000000000000000000000003e96431aae4330d3d204093b7af21343ace4f1960de951eeaebea51e778b1fee43ecddc46667d096edbc5ff4735586400000000000000000000000000000000183a282f4b0513be661b1b38eb5f02b51aadc591745e0bd5d2d4e5545739e26470a9ec20d78ec284268d9c54c8e4f7b6d997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f5000000000000000000000000000000000904c85edd36dfa18ddb4e1809607708142f3c0861570f2bc8fff14c462675661f2111c10a01557fb21f7f38957bdd840000000000000000000000000000000012a3a37f34ebb23d4c9268ec9e1d53aed4747aaace497695e6ea8fdbdedd58031cb479003e8bec0d14aa1d062fa30f2ce881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a337100000000000000000000000000000000148532bffbbf8bb1688f6448854214b4273b9d5adf132aa9142c1605d1882879678b6cc70638713b9438532d427f447c0000000000000000000000000000000010971ee30d83719e10e91aad3f1f201fe35ba1a057531b1905bca3a8391a3786cd077ee0f104305eafb3c94f4546da9edcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e65000000000000000000000000000000000f73574aa5a06ea569de88e48fcb96e822039af296684933c1b417dde95e08d2ac9c6ad4d525b0734e24807ee99ba88a000000000000000000000000000000000523deae09e75121a6d89b45161f69f0733a9e43d88d8527a03cca8cc126aeb7a680cfaf291554403723e20440b79437334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638", + "Expected": "000000000000000000000000000000000db91871e4cd84b3b58216b6c2e77a9521f3080182e78d3f66fe33f313013f06aec2dc5a6d483f35fadebde873bff9490000000000000000000000000000000003b9685de062b09b9e277ad5cd664d28af59064448af2b1b2b2357df6fc88e3ee7e0ac837100e0b7593944e8db43ab0f", + "Name": "matter_g1_multiexp_99", + "Gas": 64128, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG2Add.json b/core/vm/testdata/precompiles/blsG2Add.json new file mode 100644 index 0000000..a4a6fd9 --- /dev/null +++ b/core/vm/testdata/precompiles/blsG2Add.json @@ -0,0 +1,730 @@ +[ + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3", + "Name": "bls_g2add_(g2+g2=2*g2)", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf300000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e849", + "Expected": "000000000000000000000000000000000411a5de6730ffece671a9f21d65028cc0f1102378de124562cb1ff49db6f004fcd14d683024b0548eff3d1468df26880000000000000000000000000000000000fb837804dba8213329db46608b6c121d973363c1234a86dd183baff112709cf97096c5e9a1a770ee9d7dc641a894d60000000000000000000000000000000019b5e8f5d4a72f2b75811ac084a7f814317360bac52f6aab15eed416b4ef9938e0bdc4865cc2c4d0fd947e7c6925fd1400000000000000000000000000000000093567b4228be17ee62d11a254edd041ee4b953bffb8b8c7f925bd6662b4298bac2822b446f5b5de3b893e1be5aa4986", + "Name": "bls_g2add_(2*g2+3*g2=5*g2)", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Name": "bls_g2add_(inf+g2=g2)", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2add_(inf+inf=inf)", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40", + "Expected": "000000000000000000000000000000000a9b880c2c13da05bdeda62ea8f61e5fc2bf0b7aa5cc31eaf512bef7c5073d9e9927084b512e818dbf05eab697ba0661000000000000000000000000000000000b963b527aa3ec36813b108f2294115f732c878ac28551b5490615b436406773b5bb6a3f002be0e54db0bcebe40cb2e2000000000000000000000000000000000bd6e9060b42e36b57d88bc95b8b993da2d9d5acd95b73bad0509c2324212bcf7a94a46901932c0750535d00008a34f7000000000000000000000000000000000a374afd32bc3bb20c22a8864ce0dafe298bda17260b9d1d598a80830400c3fd4e8a8f677630eae5d4aa0a76a434e0ba", + "Name": "matter_g2_add_0", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d938800000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633", + "Expected": "000000000000000000000000000000001963e94d1501b6038de347037236c18a0a0c8cec677e48fc514e9fc9753a7d8dcf0acc4b3b64572cb571aebbe0b696640000000000000000000000000000000000d9739acc3a60f6dffb26f9b5f1fd114a21f2983deea192663c53e012b9f8e1cabd4942ad039badbd4745ddc0a26a91000000000000000000000000000000000b4206dcdb80d62195febb6773acab25fa2c09a2e4be9416ca019faeb72f1fad1dfdc51e8cea39b371a045b18947d40a00000000000000000000000000000000100758b888fa27e9258ddd5d83409e8aeac576874bc399b33b8bc50d77fce5358cb091d42f9a1b1ed09be3f200959989", + "Name": "matter_g2_add_1", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f", + "Expected": "00000000000000000000000000000000079e4fc2190d3441fa76c2d925d23b81e353e09e9138fdde51234195e564a32c98aa0d240f051298bf966d17adc2d6fb000000000000000000000000000000000aa327776fa7e15000dd548fcdc3a1cc6f9d0ab33046dd4240a3002962131b738ffed579945a348c795cfcb33682cf3b00000000000000000000000000000000179232ec56602d1ff79861cbfa2edece34b296541483aa65fe0cb493f520b7722cfffbe04294dd054770a38bf75d927b000000000000000000000000000000001826b88a6b411330757bb304a380487a02f7cf421115b84b3f468d11a83dbf304ce7a5661f4f01299d3c7865305a0006", + "Name": "matter_g2_add_2", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf49000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c", + "Expected": "000000000000000000000000000000000f69e3616e7122bf78230461bb1f4b194988adc6149372691d8794d0086fba0870a2255a2c79cc3426e7ba4d032fc2ab00000000000000000000000000000000174752301e05dcd62f7a3ae3357344e64d1c94835b2b742ac24449ee2728d693a0df10c3beaeb45d1b4af4ac2bdbb8b200000000000000000000000000000000051a761a3ceb275ec28a2a269b5ded1d9fd11a617c958e73c07de3a92ac480aa82c7d2a1852d291804e734526277f5740000000000000000000000000000000009bec9045ea89d5d16588e3373cc977f6d975d0e2213b171403a9b2ca460b3b2e1106b474185516d4200655b17a179a1", + "Name": "matter_g2_add_3", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca", + "Expected": "000000000000000000000000000000000c079610e6f8770d65352f911863b6cb4fcb25cacc4a42f75e34e29e977c93244a6241cf3d5bd1040ce7d8987996f87e0000000000000000000000000000000010d08d8f6fa8ee7042c0891ea0c3b9b59a79da52cf3a91627c79d456212e3f6f39e1f69aa0053bbdb4076a3f7d05e5dc00000000000000000000000000000000069047218b0ac1e07650ac8f4a1b9235f68408f543517c4ae3c0ec47c79b468713c704ff3680edc8abd1bbed7a5fa75d00000000000000000000000000000000137737706162e02cfa75ce2154d57c9a3520818cc04626654824769ad92ff7977942f3881a28284ea47c14f353772d0b", + "Name": "matter_g2_add_4", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e200000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d", + "Expected": "0000000000000000000000000000000004dd5dfe38fa70625216ecfec60ea8d38602552726f0fdfb8f392362ce845fe0fda76894d0e456796e08462bb941579f00000000000000000000000000000000195a85cd0685f4053ee539de7e04fccd2380819b291f89cbcd63d5a0015b3214500284a7c6568a71f52bbdbc38be410a00000000000000000000000000000000107c211bad49c7dd8555e30f2500c67e7175eb98a8494f3d5309c65a93cce89572b7b5489428eaf3f0a5c1be323c5352000000000000000000000000000000000c11f978150ac35722679cf79443b3706d288c968116ddedc1f1d0fca8cd746e3c92dc006330be14886c53c41feebbf9", + "Name": "matter_g2_add_5", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46e00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4", + "Expected": "000000000000000000000000000000000fb33caed4de22cf341bb3e04d41c0198b064c1d371a24f5cf59595ab4a1edfd379916a40cc405d35f0603b2f8fb987400000000000000000000000000000000131ad6172c20b3a1cc2542db037de1324086fd9cd140ae97987980f260023d91b24504181af6fcbcfa242f48e99559320000000000000000000000000000000004a0404c00789459395f5344544041785d10f2fe74d4bf484966f5e9b6b4c4c8cb113a811a4fa82a1cdf8e3242bb418900000000000000000000000000000000086ba6a914f3f07bdc6750fcf6baf76124a17964bf9eb9a12982e8a28ca04360da3544b69436d5663e4e94bf7189529b", + "Name": "matter_g2_add_6", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091", + "Expected": "0000000000000000000000000000000019c8a1a206c0006a3033377abba4c31c55710a094d8c9dcef7560818e90411861ce7d189e2763f8fe69bf75e719e4efe000000000000000000000000000000000cccc6bba8691c210aa0a67d26584a359fab94041d853160abd9669893c0d398c805cc37fa3c33bc5ee5ff915b985c45000000000000000000000000000000000e353c1993c36763acec2a75495560e743d099b565f3de195e011afcacff3d60502801f47695da7dd589af81e772eb7800000000000000000000000000000000100c6123cf08eab6c59d78b414fa504ed10c204851289b0598b40ac31971fa12cfda4ef7cd2d64f9797d4d2b193e0bd2", + "Name": "matter_g2_add_7", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5b000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2", + "Expected": "0000000000000000000000000000000009fc3870f88288c680b43d63d3bb5305b99fe461e59c07be981b8819fbee0d1fdfae0c037e830fbbabc40cedac7919720000000000000000000000000000000018bdd4903da4d14fa28af4c2cddcb708238cf68673ce77a04a3926c4aaf17d39a831c5401e84dd042d6adf595a1763710000000000000000000000000000000002c398f0e8ad9752f4aded980bc5de2d91118db06818d815c11e818ead47e7065823737db8e304bae32969cab065d1ff00000000000000000000000000000000180642a633c3aa402e5c0b18fcb6fe8c115575b863abda59b5d91997ab01014faefc975d0aee994f98cf37ce79eb95aa", + "Name": "matter_g2_add_8", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb815960000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90", + "Expected": "00000000000000000000000000000000199600699a6108599c638df8f965d73b5de4ca74598df281ec95c539de2c7eff9767569692d8e0ad120fcbb3d9335b95000000000000000000000000000000000c42b11e2585ba93521b3c968e9dee07e4f5168c11087d8d750795555a105df70c969bfa79b1ab4e5fc8d81657235d08000000000000000000000000000000001370daa4699daa99e9940fe04f69150e6f752798cbc0e66c91c3bd46149d935c1815f32d7f14b510e16d475044eda9cc0000000000000000000000000000000016c7a00be10de5732795cc3ee2951e58cb9d42f9b05d02fbff1b83fab5d3ad830cb8178092b76172108d7a53afe8c539", + "Name": "matter_g2_add_9", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e97795505419600000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53", + "Expected": "000000000000000000000000000000000669cc8a3acae17f99f805afb9012a38851a9e8d4fd9895a9946c29fc859849c24d7ab7b6278c449cfbc5f1d7ea1fdbd0000000000000000000000000000000007a9095be808d0ebc99bce94e851d2a7cd3e1977b923064ab5bbed2347cf18f3343e60120fa051d12fe27da3146cb423000000000000000000000000000000000f1e7f75887651f67457f6dc064d7c11934035d15fe4dc40bab970160ed1b1aa230a3fb84dc1da08770d847c0216347a000000000000000000000000000000000efbc62ade1678cd70eb38c644038bf19e52b0859f65747068d9f3124762d951e4a6ff05f34b6d14919774f8409adff5", + "Name": "matter_g2_add_10", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a90000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773", + "Expected": "000000000000000000000000000000000e25a38d0ce2aabd2538c95ed463f226e3f29ce7f10e1be27af2d3db741926d557178c4b125af8789b40480d8beec0890000000000000000000000000000000002a94b7c57fe2783d055a537004a3b67e41f5374da0813094f5944fbabf4d27eb576dc8b21ccc15f8339df14ff8785220000000000000000000000000000000008b9efd8abfa4fd71a8eafdba9df38360ef0b0a117c0052528d1c24df5032635eebc7b201439f5de858514666c68cd270000000000000000000000000000000012a2fde51f6f4a98435c325dc3b1ae846bc33a5ffb3b13fbe3fde2f74dec0aa815fa8e42392b3dbf798cf547fdb4db0d", + "Name": "matter_g2_add_11", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b381731734598360000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e", + "Expected": "00000000000000000000000000000000067265782d58b04a2ef3dd419cee506e076e49d1119e28db1df7f0e22cba9bbdabc560084cda50bc8db3915fa9c489a30000000000000000000000000000000012448a61fb2f6fd8e355111b671f0e888304284b72d5688091f2ed00edf7ccb7e5bd8a733a910d6964dde07d393798470000000000000000000000000000000005f687356ff6c634eb46613be8e98540107e706714434faff54510234d4aff42ef7752e154aed63fa8ff905ec0af628f00000000000000000000000000000000180dca84a37c964b30f5cd11a090e54acea102f1b884319f8d1252a37bda005512ffc39dec8e33af0dde0d37993f846f", + "Name": "matter_g2_add_12", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792", + "Expected": "0000000000000000000000000000000012a29d35c9af52f172787c90c5a3e77ed29d66feabf5d7bdd6bfc14dd9a05d402976b84d44647628c908d1816f4e7100000000000000000000000000000000000caf3c372e36de557ecd7eba02e6a79b1b4cff30343119df7a23662c8512095e051ae2dc27e577635c74a260be2b084c0000000000000000000000000000000002ceca293a58bc9beb4ee9a0679eab037f5cf7b326d65c0efeefdbf384ad8e4bc08a3a75a02e6b9cba8963e65d6e76ef0000000000000000000000000000000004631773a6590bc89b49a75bbbe2e732f9466ba259ef7a04ae69b6aa5d5a2621c1918eb213101f6f7eeee4656a7b1472", + "Name": "matter_g2_add_13", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd3000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5", + "Expected": "0000000000000000000000000000000017f155ed9911ec56d71d63d57556de071ebe89be36e6bc9943ec068a70dd5a6f045dfb9fde5c1e29d52c9fc17579452e000000000000000000000000000000000a60d62ea549edf4b11f62f2321f39d41bf11f3c4f858dc7db85b1dab1b7644e27eeb1d022d6082f59c65155068d2c390000000000000000000000000000000009d309145fad15860e556ec4b4aecb415865954247c2034d5bc96026e4d6f7612af6e2db99f4e462acee2b303134b91b000000000000000000000000000000000114ed157e3d020c5397cba7e10cb864aabb47461f166a6724614e689274ae74c505fb6ebfe3e88da0d6c272a15a0527", + "Name": "matter_g2_add_14", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bd000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db", + "Expected": "00000000000000000000000000000000118e0c81f9157395578f0fb83b179721de2af3326d13189cb8f43911d8c3268a11fd9702f09f14c115bbdc43d5fbc08b0000000000000000000000000000000016a548df8c87f432c31e4e32c3e5b4d48d6f29fbe391d1181174be9dddee450e7e96bffe8c9f23692ccc080116592944000000000000000000000000000000000eef72a5c698c58f1d2ae9415da256b54d7b1ac37a1d1b88727c0afcfd854a41973c6cb10ecbc3a90050fe3d8d3ce8780000000000000000000000000000000019b16ca8f955dfd21830a3f7fafcc97d7de977bafe1983892988aaedd430d22674d97897d24c1643e99bfa6256df4bf7", + "Name": "matter_g2_add_15", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca800000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e", + "Expected": "000000000000000000000000000000000f2bf3f69276d390c9fc2c15e9f5f5d0b3cf9a6eb028c44811b481f376ab60e17d33a04b78348e46eaa94332c5f16ff8000000000000000000000000000000000bedd0437fb3f4baef87e56f33c77fcdff6a5512571cf11fd9605697abd8763315f1fe4bccf04acc6e971d6aeefd9c1500000000000000000000000000000000067c3ff69733baae2fb4ab77cddb7563047c428b40a257a375f8cf8c9d230a6619f7932b86e0836fff0c1c60d2c4dfd900000000000000000000000000000000057526faed8d62aa10e89add5a338320c748ca1f96ba5ceb579efec69d17475571fc4ce6fce3a93398ea88340f0e969d", + "Name": "matter_g2_add_16", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91be0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9", + "Expected": "0000000000000000000000000000000004fc19f8fe47e6acd37567016704b07f906e8741fcb196f697e1fc24b0204292693ff424bf1c5e407f5bcba5a3b1ab85000000000000000000000000000000001816f992c3c461fa6d2014ced382a35b0d70e61927d72b4d661434efff3dafe2f4b6cc91bb1a5dbf809f10f3ed7f36de000000000000000000000000000000000dadf7f7223ccedbeffef31c97df7e01f99299da71b589c8828b65715012aa343d7e041dacc57b34a6b5f84523a7938100000000000000000000000000000000167f7e73e22df81bd2a7a6f14e940a401bf414e5d18b3aa610b2a82ca8f46aecb5721d0092b27f8968b2302c37957268", + "Name": "matter_g2_add_17", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7", + "Expected": "00000000000000000000000000000000041a5783c748247f05457d30d16f93431e9046a236d5025cc07a27b9f2abaaa556e2df65cf0f0015107253fe94d8b4dd000000000000000000000000000000000193638bf69c7508c4b12808a62e89883c34f97ded6e1b5dcc3f28191e5c7fd901a72a85ae386acccc9865f8144b1bd500000000000000000000000000000000180e8184ab583da58b77b8a4d108a366dff3e3b336ebc5c9153fa815188edc95e7067ef25f7d79526c295d634bc98f5100000000000000000000000000000000125b147100f6df0cede8e22151b3423b1dd364899fdee103c71a44388ff002a367627a2342e15833644bcde61f2ef6b6", + "Name": "matter_g2_add_18", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780", + "Expected": "00000000000000000000000000000000095fda8adf3981f4468fb82aa0ccf80e55138c922c6422cd8e67f53ee63e7a390bc345469e9211a1f8d810cf4ba27d0a0000000000000000000000000000000015c19b6af21f75e8e53fcefbae1c8d7f97853a8aae5fa62e606cfc92ae71890702ef9dc5609d3ca8fefd415fbd820c04000000000000000000000000000000000007b7e908766d34c5d99cb7cc76d5d5ea83c29ae1d9b83b163741bc9962e293926b1e251b546ce0c1268def728da78100000000000000000000000000000000084fbd6253211f7d66d52b7f14360729d54b2f94c52f2b76e521dc3961c40b4f19944923f64c6425a44eb158a9727a4f", + "Name": "matter_g2_add_19", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e170000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e", + "Expected": "00000000000000000000000000000000121e7f2eb906d0b31b8ce5cc46638428b6ee57a1ee70e4ec3c2bc044230b9b86875abe0862145b442c0e34308efc690f00000000000000000000000000000000139120d0a10b82737561d0b3fda01b6df69d9beb7dbabf3ddda036f9b4c317f3ac1eaf400013fe5ad664bea44a73b336000000000000000000000000000000000a923184b381027d8cb3f82708802b204566b2b8bb6a72767aa396324d8a26b4e0f0cb92fd1914d77a4e9af2f1ec31e3000000000000000000000000000000000409732f2225cb5e5c002bef17512519eb1a18bf6c3d7f834d0c7ac8a38433c88b550b3f443d259313eb1133620ebf0c", + "Name": "matter_g2_add_20", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b710000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67", + "Expected": "0000000000000000000000000000000006a200642d5cece5eaacacb36000b4b897e8d8c661c8282f90495002aa515c7638183cf1e80a0b35e953adb92b6bb845000000000000000000000000000000000e88d4cda34e98df4d727fda79b67961b5b8efb1b125ef2a8eafc481a2cb2fa1530e59a091f31c25cc49d38f545491ff00000000000000000000000000000000082f38c1a1c35981f537547dc3b59331ab8c5e8dd261df58fe6f0c44ef1e65d0cdc1980e1a62f6248f38d0afe91e5627000000000000000000000000000000000eda1002e202e9ee4df5354cb87760d4df32eba1eafdad27cb0636879370a8f93be0bf2a30f15f2fbcd7e52c1bdf6b05", + "Name": "matter_g2_add_21", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4", + "Expected": "000000000000000000000000000000001341cf3316152ae8d57ea2194224f04756690133d2e02d077dc271aa577278e346e0ff66e8a49ff8c983fd34546e1f6f0000000000000000000000000000000016c9093da650643f4b4061e1c6e55da6ebaf9f234bef8325aeecad3863a0a2f53e1cdb2d54aa8b075ce6e6632fb4cd660000000000000000000000000000000011eaf3dee010bf2a16c5fbb1f7aa559cd4d831f087d9dfad4e157a6d2b6495e370d9791cbaaae19339a65726ebfc3b910000000000000000000000000000000008476d793305204be414819fce2ca70754a532682876277bc0586514f2096ba9998ae848c722ead6722d5af9395ff77f", + "Name": "matter_g2_add_22", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa097000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa", + "Expected": "0000000000000000000000000000000009792d98ab9b90c2467ad0d070ea44f382ec7ad5290a59d889313c5a55d7b8e837333ad7ecfd97221d405cd6c549dc8e0000000000000000000000000000000002b92dd07b61faec23f48b8a7893dae29509fefd688a978bc2e870d4cd6f963d708a0611b4aa65f5644fbc6ba4c5e66b0000000000000000000000000000000011e46a283946a8e033afbf7c14ce3162a05867809d7de94a090c8cc2cdca8bb79add21f6e2fa8d7f39ea6d26cd37ea850000000000000000000000000000000000fddb7cdf1f1126e7a6780e4892601121b289a386ebce0caf96cd392ddc57c47e3f9284889fd8a18fb330d6c40bdf67", + "Name": "matter_g2_add_23", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e", + "Expected": "00000000000000000000000000000000054dedc002c5f2da8c6e0a0146bfe5c83200b276b074e6d6f2c397e1208f152d3ea3e8f0da7da62cfd2a028d4c94fe5b0000000000000000000000000000000012ff307f86e266e7a212484a169d3e81df98217c6f715176913b0d383cbe4e790212da7feca0cea66df09d92544fae010000000000000000000000000000000009c211438dcf8ccb664b535e73eff304b92aa2f568aeaeb8e10ec142f92b211bb8147b250dad77d508cfe353667b6f150000000000000000000000000000000009d1734f4ecc88fd56f412f9243c387b9da659faa3fe7295580a6b7519b1980bd074339fa9b0bef44dcdd0cf0c4a629b", + "Name": "matter_g2_add_24", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f", + "Expected": "000000000000000000000000000000000896a38ce734c550c178786092292e737d44fa5f503d6d3b66c75e6bb70b59d1db9e8baa1ea3e256e2dfd8a942311e75000000000000000000000000000000001231db96a35229a4c7507b0ec193491446a0b43115c27d18b3715fcd4aea14d4e5c99db5934e73bb0b86f1bb91ee96fa0000000000000000000000000000000000d6f95d5637b29ea889c028dacdcb484d8ccdb243da4d5ff49e5ad82f234d414dc1484e9ed6cba1b5940eaabd3066860000000000000000000000000000000007de052fbb76902e06e1783fa8afcbb54a5069b4c5e9cee78d43da2cf76f24843a740a9eec6fe9b8f9bc4ac9baea77a5", + "Name": "matter_g2_add_25", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b8592390800000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f", + "Expected": "00000000000000000000000000000000156914a9137e52abd4579599dea4c0f857eed0457ee1d80635d3a6ccf0c766ba8ab1b6f989711fbdf125c4ff06b597ea000000000000000000000000000000000c60184e8ab32019ce20d2d137130f657c8964406fe4abb26da232c9c5dbfab243837d700c88d6b9ea4b8f0a2f514281000000000000000000000000000000000dc3e6e3acb898552791431859943d0a83fb4ccd62e4ab2a971370a93a99a9dfcdbe4c42535aa063354e0f2cd48308c300000000000000000000000000000000025be02da875d4990d1f0be626ce634c4856ea91f88f636bc27e313e73897c9c13a1e3ae70c1227dfd4fba97f521d6af", + "Name": "matter_g2_add_26", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a300000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352", + "Expected": "0000000000000000000000000000000010124c1c1c10868b570d2969ebc3bf5cd6bfab13ddc93f0fd2b8a1742eb8e04d31063bb81c52b92e253128d4cb4413a60000000000000000000000000000000013f89997cd2ddae00cbf24cb66a92146c553c6fae41cdfaef14d49078729f239ad2661937dd0d4d6ffd7076b03e0aa84000000000000000000000000000000000ba2ecf990cd846c95b35ab60d4f97f5814c8189190df9d521b3dae462f2d44db006a0daecf6b82c1459006bf82ef7c90000000000000000000000000000000016dc129b83cca5b3c699628d081306c5fa61faf9dda5e92894931714037628fb829c595bf64d4a7fa295f136ae244601", + "Name": "matter_g2_add_27", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e055086016000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6", + "Expected": "000000000000000000000000000000000a66f36f2437db57473bd8b7670994f1cfeb8b43c0ceae358e63a5e4e52b737fce6b3d24cc4de593bcd44c63f2c5935900000000000000000000000000000000070b7ad970f03a38c8a31452cf11422159cd3331d746031781a5861e26f54efbaba63dcb1db8bab997eada9c3dac39cc000000000000000000000000000000000ba4a9d7350adca1ae64e722df11baeea77c5fb75c5b52c8c46b9d863a70bfed1ec47888e907213f4ed4dcaedd37f20f0000000000000000000000000000000008a64244f1870a1dbcc4bd4d5c9eb5cd5225713dc73aa22bc46b1cea36c88a66f85251a8a9ba7279c88bd5dd37a06f7b", + "Name": "matter_g2_add_28", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3ff000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703", + "Expected": "00000000000000000000000000000000079f89f2defd1f97efe0ba1db28523abc88cdf66efd39918a600a07c5ed5b72ab9d3354a172735e7749b5f6814a48f4f0000000000000000000000000000000009e361b8609be8057e5b3c99eaa1727fdac17edc59239af17f55d72c8b8daa89726f4ae240c742ec4b02fbd89d45c46400000000000000000000000000000000121b475a2ab50357ce80fe01fc461195029de20f61474b0773d80434253adfc268a775e1a0e3b7df5e85d1ff8c5008960000000000000000000000000000000019a76aef4e04136b1ad0d03586a3d8608ac4573715f18d5fd6907d03e5fec7c5659e15c19fd87f242da972b651dff5fa", + "Name": "matter_g2_add_29", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b200000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb", + "Expected": "000000000000000000000000000000000383ab7a17cc57e239e874af3f1aaabba0e64625b848676712f05f56132dbbd1cadfabeb3fe1f461daba3f1720057ddd00000000000000000000000000000000096967e9b3747f1b8e344535eaa0c51e70bc77412bfaa2a7ce76f11f570c9febb8f4227316866a416a50436d098e6f9a000000000000000000000000000000001079452b7519a7b090d668d54c266335b1cdd1080ed867dd17a2476b11c2617da829bf740e51cb7dfd60d73ed02c0c6700000000000000000000000000000000015fc3a972e05cbd9014882cfe6f2f16d0291c403bf28b05056ac625e4f71dfb1295c85d73145ef554614e6eb2d5bf02", + "Name": "matter_g2_add_30", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233", + "Expected": "0000000000000000000000000000000013f8cdab447ef9be450b87f941c96d4e93d5efd811d80c6a910965728f7dc496dec132f3fbeee5d1e84ed7c24ca9c2a8000000000000000000000000000000001537d5caa13ddfac93f0f86729c743d9a68175a78c730528b581fb54b1f4d020473b3b766e3882a485ce5d02ab381c33000000000000000000000000000000000b370903684ede24f3df80e3834ed414a765cdbad98f20c49bef8663a82a468d3911d6bbcdc021e22c252e83a857e55800000000000000000000000000000000100cc8d05f071904753776c6092a38db84c5de751bf93216131a0f9a50bf78a722344a14b3be2a9207568d1f669d208d", + "Name": "matter_g2_add_31", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f860000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce", + "Expected": "0000000000000000000000000000000003c5498b8c2d4765a270254dc927c6edf02acf0759540ddad951ea8c097bddb949ea0bf19942accd615bef21e8572dff0000000000000000000000000000000004c17bb648909bdddab4dd86560cb6b341e96f58c515ce471281f226181bded16b358b56d72e363f9ec491b8a9dcd92c000000000000000000000000000000001828973958204f8ab8cd13f5af5f3529f368a149bfe931a8002b61a61895457fbcb0cc6874631bb55799c884b998d8b9000000000000000000000000000000000f61460bf61bbf3ce38917850bfd3cece1e3955ce29d200c6f8aa89076c70919c02668678edc0bcf94efc9e9ff6a650e", + "Name": "matter_g2_add_32", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac5", + "Expected": "0000000000000000000000000000000002c6104b3494fdef86d53f87bea68d313188c0908b935fb3b9f636ccd401c6e9cbd33bfcdd437e1a0150d0e4b9c3a881000000000000000000000000000000000bdc88396f807d1ba8d4d6e284d008b5e40445ce32c23a0178824fdbb6db3c5aede7687eaa2f12249125cded57052ad2000000000000000000000000000000000c7004365c1d3027997b55bd258dfc61ae07a762666fba2a14aa2ca116673fc03a6f694c069f53cd915fef6d37513101000000000000000000000000000000000ec17688d8f53e2c92502091c859cef4fe9a57ae984cb1e72686bf1f0656b10246293cae4b96214a38dc76cf2709bd59", + "Name": "matter_g2_add_33", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c99739", + "Expected": "000000000000000000000000000000000a44e6a48ea0a95667f607ee66290cb0094c964baed779bd6656941db28e30a7e9effe49a617be9ab376af4f535cc28f000000000000000000000000000000001933b87310bf5fa60b1abcd13bb7ac3f2ec0a278f6a0a70c953a2905ac1d3bc5a70cf1da885af45d1c7680bb4f7ff74c000000000000000000000000000000000597ce9f1bf7efacdcb0250427d0341e142226aaea060983175ea149912c5c4f3019fe87be6d87d186a8f562fc3059eb00000000000000000000000000000000198b5a891722a237a5e23e3004798c8d3f069af3267152508e283b4549fc5e8388330343f80e606eba30af51c99c7020", + "Name": "matter_g2_add_34", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe20000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0", + "Expected": "00000000000000000000000000000000047c2ccda315b9c013e87bc9168b3b8dd6d463403f1cefd824fa9f93a99f4c4f98fac5f97e4237f76b1ec91042f99bd600000000000000000000000000000000036861fd0a69cbc851741475905441b51af12c5b2aaee6ce9a27a01a43db810be9c7d6fa401406e98e327703404b83a5000000000000000000000000000000000310cbdf53f6cf8d87e2d178869bee4359a8dd666986d869761a79963680a33ea3ecefd40a1e558acae5ded2ca04447300000000000000000000000000000000108bbb28c73ed7e76a51a78e4d15a2c88c25e05c7127ae89d4347cda00be231b5e70e0b0562caddd4a7083efa4516722", + "Name": "matter_g2_add_35", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a84486000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c", + "Expected": "00000000000000000000000000000000137d23ed3fa0d7e5928af8d1f4bdfdef08e0b4c0f3bf6f51ed28960ce9805eb8fb254233bb18cbfecbadba95e112fdb80000000000000000000000000000000018615147d7a8cce1dfed6de25cf2fb52f54a243bed4913e20e66673f47ecddad9c5e4ff9653f522180de4b90ddb3ad17000000000000000000000000000000001521f12116b13f785b5211aaf438aa6668bbfa318cf0ed6d91aae963f6f00d32cc5f25d3a02bd902ccc25f847ee2db830000000000000000000000000000000014263b23396f4facdacf13c79864157823db724350bc640abf8fb6d62663cec1069eef9db56817660510e2417b51c616", + "Name": "matter_g2_add_36", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e370000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d05712", + "Expected": "000000000000000000000000000000000038f9df6c14f84b8ef8045010c8973e5c2f8d2e37268f6a674298de7b15cae82361ebbfaa00ea1cb2653c5d00886b45000000000000000000000000000000001376f7e2d5621aa9d6f7ce45ed11de7e0e1095ebeea976f78eb83189c6852ee199840c14059c233bc3d40efbeeb5eb36000000000000000000000000000000000c7b0e53adf4f0fc5172f903e3fc479539348241edc3e277f30ae6b4fc419aadcfb73a8f8a09a1ae1dd885a6250de0040000000000000000000000000000000007a00b57ecc8b056436ecacd7e0fd346b906b15042e9a700f54f8c3b1d251c566e0c55bd34f7a9e30f1566b7f2ab16dd", + "Name": "matter_g2_add_37", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4", + "Expected": "0000000000000000000000000000000012662e19e41bfacc0c792f5183596bc7f1986f9bea72c626e187d72111b6ef3f36f5afeeb640cfda99b7044c0d0b846900000000000000000000000000000000050ba08e1b9fe95dc67e6ee1ce60664b291c80fdb59729cdea75dfd18f22fb88f837b439fd119c46c996787d3008194b0000000000000000000000000000000004ea0f488fece967675abdd3c42f8fec25b547cfc45d42fba14bbc55ad7e1a75296a679113d0671cef0aec0c2165f4a0000000000000000000000000000000000f617f51800b09150a7560505079c785ab45cea4705992fc0325edaf4ceb30e1f0bec35a31898db5f810685e55634076", + "Name": "matter_g2_add_38", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb300000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc", + "Expected": "0000000000000000000000000000000019c774e968049bde2188e844c3413203bfe2c4355edc8cbc2cf6f977c34c0a42a206194e6eecba3c97b24558048f3aa700000000000000000000000000000000081ccf6f111575a946341759b9faa13f3608998fbf4ea3b547804737e30fc7e33495caaf2aa328b19bd48315c5c7f9e2000000000000000000000000000000000a4098536041cfb808176c7cd8e980eda613a2b390e8d63d607caaac26db02fccad6d87412b90cb4b3e186bf9ccd31be000000000000000000000000000000000d3c784c6587b9f786c06099a62aa639f40535b512ac2440912f04dfcd1cb5851b7378f381fcdf02d4e58312eb7e442f", + "Name": "matter_g2_add_39", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05500000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6", + "Expected": "0000000000000000000000000000000016fc7c743c5ba747640a6494fb3c30caad5a1e9719a1994d0ca73bd1645fec118a2887acc8876d105102241c10274cd300000000000000000000000000000000058a42a0095a7388fba7ce71dbef4ecfd2018c3fcdde14afd2be26588de4689d8de757e1e3ff22645fb8c17aa60265850000000000000000000000000000000010bb622f649e346834b95e82f93ae83c71c0a65df7842c4ba88df7f6eccb0217ca9377167a6d14777e0474c24821f8d70000000000000000000000000000000010c180c685ea3d0146eb82c007fec3efd129880f18f838f1cd2f80181f5a4884d6b5cc8247430fb0c1701a57f9d1d485", + "Name": "matter_g2_add_40", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d03", + "Expected": "0000000000000000000000000000000019419b635c3742cecffee02ee7e2b1f18ee9ff15e647ca0abc4398ddc421ae7e0444e3c1ec377def9e832d8e64fd40e2000000000000000000000000000000000d9b4abfdaf3b4c7bf00fa07579befa10a3418d8fa0f3a9c31e59ae48b0de50fc8e6d583aaa4d0fe6048bdd1a9c60eb60000000000000000000000000000000003c96d57034ec97c4abef1c2c81f4d4b0f4b6eb1e9dc5464bcab28572555b9b874df80325941501c3766fd7e06bfe7360000000000000000000000000000000002dbb3d72385b562ddcb9a80400ab3770f00d22b880cce2fce1641042b9da669b22b2fbc97617648c25ab644e661e2fe", + "Name": "matter_g2_add_41", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbc00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d", + "Expected": "000000000000000000000000000000000d32b00154a5fe75c576c098419744ac36b911ee800f94bd598ff9b6adcaa39c836bc158c5d6af72c9e715a242d0fe710000000000000000000000000000000006e057c13885d6c05f5d92061fdc4d532f10d31d472c371e71367fef7c5fdd3741e665321d1119b895660fba3770431b000000000000000000000000000000000bfe695c3364e15479741e974f838649e789a76d073e552aaa60981fbc6d185eb7b297fd59e51535965214a02f5cd67e0000000000000000000000000000000014f0a27412248e3163e5f82fed02a25d953b336b0201692f08a3e8e9a9d223b736c70c1a39826a0888fb02a314e223fd", + "Name": "matter_g2_add_42", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6ea0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47", + "Expected": "000000000000000000000000000000001566022247ce012b7de92c8495876b4de91c36448f4f7e00f6e154185d38a735e701dda989ae9e37d332a5e60af5d06b00000000000000000000000000000000065aa42560df7990df2098827a55ceaabf3ec592c53d2f20e5dddc1481ee64381accbc8e58601428d33589b3af78a4b70000000000000000000000000000000002d9b0cf8bfd1adf76bca80ca351a4340f02434090518807e07ed76440497042f13a0cd7a9c30086872d6f145808fb290000000000000000000000000000000015daaa131431e3e78a6221091640811fcf88c835ac975a041a7ab50bc1d06b80e6a3c9ae77d2390fd14cc9bb009b47cc", + "Name": "matter_g2_add_43", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710be000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf", + "Expected": "000000000000000000000000000000001290bff629c93d992ad2cc709317c48980b0e56a32fe239258c7aec75e4523e0bc0b81319e100d10568a44847869a8d000000000000000000000000000000000055d9098e08eabdf2b883df35efebec9f6afb16d651ebaca1067e2129146268664ec51c8a4f28f13a250f3e9883053780000000000000000000000000000000002424dab6f0d18ea8bdded2a72bcf87c13307d27d53e8ec35e91eeab97fcf3398135fd436c530c609fd47a3508472bad000000000000000000000000000000000b25d0db1e28b98d4f9d3c77c0b71489c51186105d93be7fc2cf8c72b8abd8959340114635e705e698b0f257855ea4bc", + "Name": "matter_g2_add_44", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be100000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2", + "Expected": "000000000000000000000000000000000cb2998b4e634bc83b5585b0683b7b561f260eefb826719bdc3c95e8ae51f8f7b442d75d69e0f9228dacde2ce80ef4e60000000000000000000000000000000014d30d1c02122143868ea01b454a4f33432d875f8ba66e6bb1e02fc161bb5f9298e673339a9183a15759f8b94b519cad000000000000000000000000000000001068bf3c768e8c9e9058805050394ea820b5f60bea6d271f8e1fb665d3b7931ab0cc03dff4cbd24577b2c254a956e8200000000000000000000000000000000008b7f4148bd1f4926d2a84497b60a48701057ea08855bb9a2f838d2464e66360a59d058d9072f1416023cc72045af558", + "Name": "matter_g2_add_45", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da900000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb", + "Expected": "000000000000000000000000000000000a7843a1d67360b8a6976aeda2e4e98f1ea229a4d84b947dcf5ed8215173d5cf783920a7714f5b048778df30f01a0bed00000000000000000000000000000000035663ceafda9e5bfe934cff725b36b258f12afe749f907a560a06da4abf8380853f8de31adf14d62cdb310d8740e29b000000000000000000000000000000000f210d576aa5d4cdf5aefd8e55be099c422debc217ddf0151b8801f7d16456c97d1e134b40e6d71d296ee2518e50af9d000000000000000000000000000000000219efb35c68540c6bb0ef224e68dae6f7d48425c2908440072f5f63eec3c8e750b559c73e33464d0b5cdabb50fc4d3d", + "Name": "matter_g2_add_46", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee", + "Expected": "000000000000000000000000000000000ce704e650605f747cbc0bc76e82de8569ba7b3d897eac2bf5f79aba17ef4c989731e959c0bc0b7988000a9b0aef39430000000000000000000000000000000003cd3f3d978d6c85d98812ea0e3d21149bf4151ad1bef966ced124ad62dc7cde55f16e8d08bb1ad54d3a23bb73795d8f0000000000000000000000000000000019d37a20fcf6244c2898b271535e3b8f279eaac5d8fb1ba142096da383488eba28a21d038d7a9d3f9e8a008d6d3ee1d20000000000000000000000000000000001ba9c1720a4ef07ec752efa1ddb629505b3586af415c916fb0ed2953cd8943d9343268f438db860f0bced3e690a66b0", + "Name": "matter_g2_add_47", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c70000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb9", + "Expected": "00000000000000000000000000000000160d8b4bef36fc3d09af09dcc8357067c22e421f3811deea66faec42a2f00fa4aceca8725cf99062613126a9fd7bf7210000000000000000000000000000000004e8691a42c8f3ce0e7c0470446689e9d2b3cf57d55fad7387d624857f977cb9c6864c87bb4b6a2c17538478ac5fb5960000000000000000000000000000000015e20f6baef033efbd38081d5a10eeb3c67d89ebe5cd652110b778313c9e86cffb45231616d5b67e9ec8b7be15980aa9000000000000000000000000000000000af75dc221050256015fecc2bd8113b42afc9c624e5d28d7ff8312af499e34a603d66a4304f263729b440b6266538316", + "Name": "matter_g2_add_48", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c6", + "Expected": "0000000000000000000000000000000013edd8f016f6af49e9bc461ca14c438a32eaa3d1270a5acec99a666aba3f0a7e7eccea81720971cf4432bfa94cd18392000000000000000000000000000000000dbea5617e44c82da828844a5a4a1426d43422fd0158204a99f53cf9821f82f0bb0130a2123297a6941f695e172d9c5e0000000000000000000000000000000005f65a445e9f2d57dff2b210209f9faeb1c8b446454de4724d990aab20bd68362dd7ceb5b95de361c129855abba83f7e000000000000000000000000000000001219ecae79d62d3039e642369353993b1ece049331f06be256f06b01a1c3b0c617221c8d8f0bf4b6a0abe1191a3ee8e2", + "Name": "matter_g2_add_49", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307", + "Expected": "00000000000000000000000000000000158da32df45fe3e9102010bfd7faf3fde936bb8e52f68262ef479ee825a0d7169ff753aa042883a5403103a9bdafd2be000000000000000000000000000000001800a5776a47f52d2af08144364a6cd7442a0e2fc214a2d8d285a29bb7bd3a0293e89f0a1856223a527100d0abf12899000000000000000000000000000000000a6079d18ff3367c47fa61a57a967b782f3529bee93f452ecebd4f5c404b3e1769c100da9b8aee4258b5191ae1dad9a90000000000000000000000000000000011d3188a927e8f13aecf7f8637be6ddbbce309393a94fef77923c286244f8531d3e137e031d8c1af829891425afd53a3", + "Name": "matter_g2_add_50", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52d000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc26", + "Expected": "0000000000000000000000000000000019294d87be784f0f8fa29de80d45a697bcb694b32f3f6d7641d4b08d8a7ebdad0ef78ba5ccafd6b7f240e1cbde019c51000000000000000000000000000000000645f7851644e1e7e255d0b3dca769b987ec3ff2c9eda42cab65dc39be2f9858c31f307d59f6a2caf9dd932d873d2b08000000000000000000000000000000000e8e93f39ce05a11d40f3b52262980c79ecc52939dd02b94df3e5034a57061d040b0c8894189f4626f37bee485712dd00000000000000000000000000000000001e0b7c9c3d7456b2c0ad842083e9ce2a00da91cb1aaba371ff4b9370f0f2c08f4b53b8e5a3030c99b2957cbe5f9e967", + "Name": "matter_g2_add_51", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d761800000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb34150", + "Expected": "00000000000000000000000000000000040f355021ba50c9a3b2b4267668ac8d76dd88991be984ab5bab9c96faed6dcc6e8eac78ed29cd6f7d687dd55cc5d5b70000000000000000000000000000000017853cf0a39332e3c7d75b08b2940d693ac7cfdac46719787c22b55a2ab1036d6f95b68075f1c585942843aa486f17bf0000000000000000000000000000000008696feb333417a7262e8976d1546b6d0a9d5970095485b18efcdee8993b16f42e6dbfdd08d30c45fe4af6a5e203de07000000000000000000000000000000000ec26926720243124ca505c0e04923f3cf5eeca2abfdaf4388960b87c6c1713fc54cdd1c825e2ea359cc67b3bebfa2f9", + "Name": "matter_g2_add_52", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e69000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b1", + "Expected": "000000000000000000000000000000000f3dd56c416db1c06fd27e18fb852c9e1662fed42005e253230a7a8f7c3e0b8ce637666e1d20952c219cd2068d6865f1000000000000000000000000000000000aff045afcbefcdcb5255805a86e8af3de881e5482188c487d15ad1b799cf551c1d48c7665028b05ceb2e82e15ea4ae5000000000000000000000000000000000e0e6ed04926aed1f8c6a4e13227bf2a99d9d6d349a9c86214373be693db702a0011b4423defdb7d842bcb6f722c70b100000000000000000000000000000000148b1af285c65b12eef498f1c9e57a673e7a3803088c56e32aaae13dad3977dda8d3e27809094f8d8ed607239610a1a6", + "Name": "matter_g2_add_53", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df10000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e", + "Expected": "000000000000000000000000000000001220b3da7e7d03823458bcdcee82db56957e5aec335e9b543ebb0f3cf4fe3cf6ecacb6198c886b9abbdaa42f528b4963000000000000000000000000000000000138233b166547e9e9ee9d11048e2d2579b2b111af5cab372d36159c4c45e28d836d733a1265e8833da64f461c0a32cd00000000000000000000000000000000005f860a0c72034f1a928501d9f549e5c2a9dc72670272fbf35a0b301025c0fc751d55ef6fc2c5bf7ff42df7693f3dca0000000000000000000000000000000012c73105adf97bc0dfec1f56153c57c6fdb9d68341f4397b72f5b6c667873ff7ed5cc841451b391e33290cec256395c7", + "Name": "matter_g2_add_54", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0f0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e6", + "Expected": "00000000000000000000000000000000014933a0923416428b5fe5be7120bf399ab62ca091b07d03da3fd2ff080b9c411c3cda3bfef40c8450ae31c412dc5feb000000000000000000000000000000000214229a73780d4f260364649e9eb2ed751ad3f687a832a3738ca2cc81a3acf12757651e88c4bcd79239bc0b0c40e5a6000000000000000000000000000000000548f20fa375e578084e085ee71df5f8ddaec1db03a1415938d9521b5d9c914b5295835fc07263cdbf49d7802551156a00000000000000000000000000000000063ecd9efe55229a76fc848728e940183c23bf47363cb34c5a49837e6df8a5f0dc29d7108cd10ea08e82ccf017d246d1", + "Name": "matter_g2_add_55", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a330000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c32", + "Expected": "0000000000000000000000000000000008a71a08d2c4e2ba3d8774dcb42d3e96c7f72d36fb3b880a4049b078d8257a7a9a51b0b34c093568baf4aa6de70e709d000000000000000000000000000000000daf83b5ad4b91b557982fc4b9b7dbed2998aa39fc4658ba671f5f27b3888dfec7602949cf626c9e6ef21171acb185600000000000000000000000000000000013a7ffca291d9ba8790ca0462c54c147aa22e03a2413b756f27583155932aee65060924e46db321b3fd6f22ff7f54041000000000000000000000000000000000289d7de10285285279aee024e52476fa6fca85550f7af183a161e395d72e1339b629c64127f96bc85858d80e73dcbe1", + "Name": "matter_g2_add_56", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a6000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95", + "Expected": "000000000000000000000000000000000a4ed8d613cfe4f5dbda1d0c6812d0edee45ffc2667323c3828f8ce4ab55c119e92a82f2c3d06afe3adaa4aaccc18f8d000000000000000000000000000000000fe10c5e185f3f8ba81c93754132d76e05eb3543d8aaa8a2d0c98833ce5fa9e2b84420d6e3412e005cf89d11f5400a510000000000000000000000000000000004ac5f8cc614e3833b3b6dd9eee9ac29501002ba9054554314a4c516bfc8cec870995e811f7892811346574f3c58b2ec000000000000000000000000000000000a6bed54d8ed4ccb09211ae7773c604edc6ce51a05c9acc94e8167026906d387af681fb33a40e72e85cb076e072db7d9", + "Name": "matter_g2_add_57", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f254000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150", + "Expected": "0000000000000000000000000000000004d145ad2575313a922667b897052063139eef8c61dd375eb055c4a5c52cfbed35391a85df915e1eea50d000b9b6bb5700000000000000000000000000000000071cc73c16a234e99faba9b04fafaca1a943f2bdbb68dcae0a1742acfca1f90c5f69464aba42be6c18be31f79ce30791000000000000000000000000000000000bf725a2f4d7d33c66fefeefce13fb5649a68a93fb7086c943a7bd5663b5788a5ceaad7fd2a219ade832dfb3c0022a5a000000000000000000000000000000000fef4a2610610afef43da2161b86b25a8f6e30ed90053d57f5ee0a10effcdd2af769d32ef6843804b2b6590f95eccb4c", + "Name": "matter_g2_add_58", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c5600000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b", + "Expected": "00000000000000000000000000000000151ec7c35a67b878420e198ee7bf359d0668ab61ba1a0bc2e5e57b1b7b18838a015464f9910b659fb7d1e10af2801d86000000000000000000000000000000000511536f34067fe931c6e829e22443eb838f0c938eeef6f839eb322d72e2011dd1c33c504dd044e3cd721065d7075b520000000000000000000000000000000010c486f846242024f9bf40d805c8e33ecf1b44cfaa04455d5584db7ebc32c0d29e8742c61886d4ebae93f22c518ea87300000000000000000000000000000000072e184c836a853fd1153eabb1b645bd35ef72eefde4a52db169acdf2d8d68499398599cb4002994c6f4936de1da75ef", + "Name": "matter_g2_add_59", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87310000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421", + "Expected": "000000000000000000000000000000000642f215b772d17a3aa45ee3aee607321c02b4f7a7df3884259a25ce78c73e9536d46333fa388e506fdc79c708bfd9de00000000000000000000000000000000145864ce36521fdb641761be541a27bbd3f4797b923a870148bef1d5b4b0d463c0a7c8ef07954dad464510d836105e05000000000000000000000000000000000ca038e667fe68111b583dfaa95f88d3b9e46c0798abccd1476071435067e6c0e2fa81d25db6e1175e60efa1705538b9000000000000000000000000000000000cf1cb1b155e4ea47077c42a1a99c3f11f8b27516a808b5e73498ee12363652bb46eab7e55de93513cc2d6272f26a537", + "Name": "matter_g2_add_60", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab71960000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d400629", + "Expected": "00000000000000000000000000000000128c909854a20ccf9e8e396b617b36f233909a5f6c3524c93cc659d22afe0e7058a438a5ee4345bed914288c64802e29000000000000000000000000000000000239fc43718cd27855ee5450cc9be5be5d9bca8188c22601242a1bb4269ca0fe62ad5e12b2c65558cd3dfc89ea31205f000000000000000000000000000000000a0aec9527febbd35bf041a901b0b35e5e0d48a2d6d733bb557d0767798369a7ccf2f1c278710eb764f721821f9aeea300000000000000000000000000000000194931bad52daa16a648ccf1ba9a4768e5e2900fee4f9bf46ae07d1aa605aabbfe96684f5d2233c0b254cb4ad5517775", + "Name": "matter_g2_add_61", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f20000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d", + "Expected": "00000000000000000000000000000000189ee5ac642bfd0b612058f96e63acb1feb6b4dce125bf0ea1e56e846775af1a8b0864d4ece6bd96c3b5dbb04e2f6c33000000000000000000000000000000000073d57ab79314e38267ee8015de3156f2c1d5dfcb6655a150b9ab4a3bc9eeddf7b37b3681c49611e02abb012770b3f5000000000000000000000000000000000cfa1363275c7bc5bbb9bb7c03e7bb7f6d6d365e39fccbe62cfe0bb93280527c9ea99079fdf9871abed035b62079856b0000000000000000000000000000000010048e4e96f26710d254110650de36460be2a8302badfc2da8b26147da498e4620e79b4329033fc3f3a9c99b1e12aad4", + "Name": "matter_g2_add_62", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e", + "Expected": "0000000000000000000000000000000005889133be5f447013d779f2b9b0033667c5af87e1c8a16d239ca3ed238920004d87e00119ded46658026c26988ee63a000000000000000000000000000000000d4ed8fd88f7e1394f2b5a65588bf1c461a292acafdb77703c2790ef249f2de695524293c826252c94967a3ea4a3a28500000000000000000000000000000000001b5ff0aa278c7e87a89d4748aef13b516c49b7dc9f7cd5e0448dc6fd860a7a8af7183a198eebe6c7dd549fef806db00000000000000000000000000000000003c9e40ed44427cc3cf886ca2db341ae31f015c542b857f6702d25cb5036e3e6abeb8d4bf9a0e203281ab85ad89ce0da", + "Name": "matter_g2_add_63", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f", + "Expected": "00000000000000000000000000000000093b692a68536b16913ef38c3bba7b19ba94a6af1c36a2e54b8ac1754a29c29882107cde142deb95365af00f2d1f537e000000000000000000000000000000001035e70852f38f860a1a04f33081e84f3ed17d83ad894a6800e7b8b9259067b755fe7e08d4c1b297c6d53064ab8209590000000000000000000000000000000013d38db0d8575131865bd7acb6cbe994812bdd8bc7f51b810bc382a6eb379d442c47be20a2c8e751fb08ccce8fea68690000000000000000000000000000000000bd114951193e3bd58cd0025e0b0c807ea073b1c1f7bb04a2a00771b6442e70ea20e1124572ef5b74d2bd87c93c82f5", + "Name": "matter_g2_add_64", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d", + "Expected": "0000000000000000000000000000000006db1eef1f614613ada8383e63d631484015224902ca38f58ee384a70af0a0575b0e7063675d2dd997ed8a140e2598470000000000000000000000000000000010d7b833f050f18ff4e3a8d0df227a9494dad9cbde88f68802b23e87387622a5333dfb7bcdcbfe2d4d137cb532ef4a150000000000000000000000000000000000c9c40ba972ee0be2823625a23345fe352d701cc8bf9a153d5a55c205ef1b7e5544d0a7f65aaa24bde8d77cb4c31ab3000000000000000000000000000000000402f170c4c3ebb9b1e7d64765b66ba9b8d45b2ea9fe9517626f38e00a11d180e1f8872bf80f6322bdf3a8dd90732ae9", + "Name": "matter_g2_add_65", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad300000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0", + "Expected": "0000000000000000000000000000000002dccab673b26be02d2c645c82a2c73290f0eb053e07d4f81d4d315d9483e57c58b65cfabeb0172934b9fbb52ad519210000000000000000000000000000000011c34a27c850fe319fe89399e7680064caf6dcbad171c3a23c45b9883ee06ccc3482b2b81e5777759ff81b16bcc1b0f500000000000000000000000000000000119adca3e2b052c045124f021fceb03c979e6eec0a270c7f4ab13674e461839a4d3a10fd48da4e9ae750a238a2649ace000000000000000000000000000000000fb5210677e1096cb5448bcda16646d6dd29ff8a0765c5aa51d83fc952a5ab8063aa96e97f33abf701cb8688c989c363", + "Name": "matter_g2_add_66", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0800000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508", + "Expected": "00000000000000000000000000000000056489b2248ba672501069ab6742016cc8ab2af50a119239bbd3c0a4b9b56e014402b78bf62b2b37bf4645c3bd3d95b800000000000000000000000000000000046956432001feaba6d230da27a72e8db5c8eb3d52f00616f87b55c951217095f337a302562cda789e5714c4391ac27000000000000000000000000000000000172c2a583c9563fe02d43b2b767c4ee4e3990fbabe4ac536d64cfcf059f0e38672876289bc86915b6344eb398fbc4ddb0000000000000000000000000000000008915b0edade80caee9b386e4a560ff4b9dce33946ee992649466315786e139e3ce241ebbdfa7ee28fad7e6214e65666", + "Name": "matter_g2_add_67", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e1090000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f", + "Expected": "0000000000000000000000000000000005b81843ef3f98c6a6686f1fbd26f77248497ec3d41aff4be5968d13ba86f86309b0ec4792d74220ad8ef147bdee9aa90000000000000000000000000000000019825376b243f3e374b6e9e7e51e0c969bc72b39cde1dfa09187a3c7c5c2c752ee16fa5a4c8fcf94464287419b3a3845000000000000000000000000000000001308cc0c77219034a9fc3018f1d668a41e6959476aaaa5461ec73d7155c6a68fb08e1fdf8140e18270cd338c266a83f4000000000000000000000000000000000fee2a6e245e3bb570c3b605f7ad805bcd68e9a1f2bb2282f92e2a2e83b69e275b21b923f33a65defa8c4224934aa588", + "Name": "matter_g2_add_68", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b", + "Expected": "00000000000000000000000000000000166414455bcd0e8e40397f4cafa9628d1a092beaef62d35211cf49779ba98df5c1d692f650c1fcf0893a9d4ae1926b1c0000000000000000000000000000000003dd898d0725ee899b913042da8566a1379aeb4dd5f0222ac784205b4e74f32858ae490f981801b166a01fb96266dbeb0000000000000000000000000000000019f0fe4f12b113b337361b977aff7cc7dce50bf37c2609b9f311ce340d30225de178999b73345ef49625518e52aa4d7800000000000000000000000000000000090bc07c6270901d706a8d28d512b07fd0e03013d94d4e43eafbee59677998bfb7c2a58aa93571fb49c35518b6331bca", + "Name": "matter_g2_add_69", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b22000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d", + "Expected": "0000000000000000000000000000000019ce0f31d9ebaed0ea1d12d4e232bd3ad48373fa465af44f1c8015102b624d2f8330d1323fb2fec524e83de0f6699ad7000000000000000000000000000000000915d65fef96562ea3b76f3152aa1b8e445ef50fa66dc487ad0c04cfd7a33b5ee48aed919eb81fe83b1f4dca59b4990d000000000000000000000000000000000e4731ec887261f29475523f7dfc5d21cbbc1b883439701a33cd58bd24f5d447267707c2b60ea38b04510be7dd10d72b00000000000000000000000000000000146a679d7a81aac5952645b2635f24b96393529ab9571ecc1078c4c20a77e59acc4591b9f45df00428250c5e31b1a8e9", + "Name": "matter_g2_add_70", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f", + "Expected": "0000000000000000000000000000000016790155e57f7103d9e325a1f3a64c0b8a1875365eaa0c01c515538b64bd8265e8392e755a2f7314c37ec09026f13d290000000000000000000000000000000007bfe690fc4ab166b29de35e341e8faec4bc3c2d4ea2d42c9f4166c0d748b92b743ba646c86ff9e570612c75bcd522a9000000000000000000000000000000000c11b9ccf990162b772099fdb4266716b11dcf46c5abd12d03caf222c571e2a9e28cfb47e11db05162967ad4b430930e0000000000000000000000000000000000bafe02785607bae144d9ef5391fef02b9f2fd5dcd436e2506bd40866d8726eb83c223e09c00f3b8895181c6710912f", + "Name": "matter_g2_add_71", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a00000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd", + "Expected": "000000000000000000000000000000000965966a8a463de1f3bc49d9873668e87f54d95612231458dc8b885681cee8e2835482b4bfc476153c41b206f427cbb400000000000000000000000000000000183639fa14dd74c33e8696496a3ee269160f88e5daca4fdc468724d9b6af8e7d0706867cdb1bcc608029b89b94c531a800000000000000000000000000000000026257fc32efaf241c7712b0a7e9f881763d8fa0711a452d9b71ea25e973bffd88433cba768f1e5b3ea15bdae9cb9428000000000000000000000000000000001527afbb6594dc0f472673606fb8f4797fc855bde4d308ac1acdaa26f19a70f80f2d2bbf3498b53b887b79fd6273231d", + "Name": "matter_g2_add_72", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db100000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3", + "Expected": "000000000000000000000000000000000018123e82a5572e6b6c62d5db07448838df9db7f7d15dac1adba1fd924892c8bb3c417354e838f706564a9ac282c2ac0000000000000000000000000000000016613fc38997d39b2761aed3485de4d7c273e8392e434185605e968ed942b9d4712cd0d538ed5ed1317870d0cafcae27000000000000000000000000000000000354365566b6e43f8b7f4b94a6343146f35ba3abf61a204e9c976b1ad1a90d4d493494c957def69ff270371c1c8d953100000000000000000000000000000000066adbadf1b69dd16cf19349c82e362be4a3768551599b81a4853ca524a24326e6c9dcc38b5a60ed6fdeb3cc4e7973bc", + "Name": "matter_g2_add_73", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d", + "Expected": "0000000000000000000000000000000018ba8af47c5cfa552374cb1b25ada1ac785381f2da0501f86c9e7b11cd4417e64095a5c4bdc2480ee10d215ae2296063000000000000000000000000000000000a2e09eff98280f6a9863d8b8faf8871b44650496eac1aaf90fc2b256f88e937101407d722c95fa76846776d4e6bf0dd0000000000000000000000000000000003824f5bf25fa4aec5a9e044703e5564122bec11da155c01ba8ab8344265516c1063983235863d826f68bac455327c65000000000000000000000000000000000ea72f8c6768736800b141b477610e37477d926acaffaa1951a5bfebb042c94c065e984a8812430153d529dbf07ce2bc", + "Name": "matter_g2_add_74", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296fe00000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6", + "Expected": "0000000000000000000000000000000009f1339cff0b58b00a871add058929ffebdc58cd1bd8a9c2c965c63e1843945b28138008cca8bf7b7cc9afb69a11767100000000000000000000000000000000011f65b337710a4043e1fa58bb41d80d505e2aee434b6978129c80fa1b124db89e61617e89bc0e596507566f4a484e9f0000000000000000000000000000000017560f768496ed583b3522c4a013f8b96073197e5b53e9041db6dc935a266111e21d8c54fa33b7bda944a573f6e1f07d000000000000000000000000000000000168a0742af91f42058e6501e122b6fc50dc966c2f5981372704694544aaa68fba2b6483752fa2464526d5072f84d8dd", + "Name": "matter_g2_add_75", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb519000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be", + "Expected": "0000000000000000000000000000000005daf8338637bddeba63c788d78faa622e014efb84d3ac1d655d15af06317fe31d1782b2990354bd507632844cc87f2700000000000000000000000000000000185550250e2d9eec798e8b8c483dc37e2a917b304a6036e8ee518a0738d6bf946d99f6b7ee352b1a259aa894d53a8e1300000000000000000000000000000000105a4865d66ed4bc4f51dc52ffcf284615593d573b6beac490c3ee8e08ab83a529c8dd062d762d1d70b9b3290b6e8bd50000000000000000000000000000000014f598e5d0e40090f29aec1ecaccbebbf2a2d6889bbb9439798924db41b70c0cacdcf1e8ff6906f61943e9a8a1ae4fb5", + "Name": "matter_g2_add_76", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c", + "Expected": "0000000000000000000000000000000006b63929ce97554659ae731d60d11abe858383e39a67007877f68233cba8179777c0dfe511fc730448da3f1c4347f85c0000000000000000000000000000000016d4df414c287b0871c69f9745a9ae68ea3a1ff41ecd17d87623338bb8750bf12be52caa81537bacee06cebb86f894890000000000000000000000000000000007ad72c98e2428b90bead3616f1b31b26e978cd3f9b6b759ad53056098c18932c48ba78d3da112d7a738d7a9ba21d84e0000000000000000000000000000000010dfcfc53d0458296686fd7e0555593e0378d2cb176d456abebfd8322012bc9b408bb180d4237679985457e689131705", + "Name": "matter_g2_add_77", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94600000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3", + "Expected": "0000000000000000000000000000000009b166f124b5b85875834b5b0c088ab79a2dcf262240b284f57722e78b6eb56a192cd32544c1bb93ef492fe6d7a6216b00000000000000000000000000000000189b9792982b51b13cc3fc1691e0569b6c8d998168d3a3376e63ca60de4b30a84ce8d04fb265bdcf73f158d8e316bdda0000000000000000000000000000000005b99948b635750040b5b59568f0e8bacbfd512db2ae52c5032cd23eac18ad58d83b8f78cd26ae979ce2abeae8e1f3c3000000000000000000000000000000000d0b6561a49c358101b30f714563bfefc72e0febea857b1ce78cfeb9508b0108c2089c9b35cd694bc8c0ea8afc8d047e", + "Name": "matter_g2_add_78", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88", + "Expected": "000000000000000000000000000000000bbb59d3e6b0b4d86ffc89bbfcf543a5b8ff922f1999a1e06c501a734b19dabd54632132c865c53e5287f69f06942a58000000000000000000000000000000000a3bb94431530879a7fb46b317d4f3d65b5a790739b396c78521a20e1cfad9c44248c9576be11c70970a49a1914ceffd00000000000000000000000000000000198df068ac5d3cfb9bd6896ab64495f4b9933a72872679ac3a46764478f043e9fddf17a7ef85fb72a8dc1a722804198400000000000000000000000000000000155c1a9db0c90634a6d214e996b13252bd4db3a4ab84ca7456ac3e7899e6fa096904a90f1150026307a1cac8de00c6df", + "Name": "matter_g2_add_79", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d70000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff148", + "Expected": "0000000000000000000000000000000010684ea0303f0e76b60eb96c470e1f0466f1f2b073bbedc1a0c0df1d2f6c66d77cb90ef9bfa4fef6a6a9eff8f5c66f9b0000000000000000000000000000000010e7ced79bbf01ae9f65d26894c73a905514296f19561ab4d00c0cde31737d01e7b4e8b8e6050054a7a17e8acb74d49d00000000000000000000000000000000174f771a98e262825ff2db7571f5f5475007d2f73a2c265f24e2929671bd173596b8b163abd46b868a644dd464dcc7cc0000000000000000000000000000000001cbffc9bb3195672ea2d998b169f853d3d4b4e147379329b1bbe69ce76d08ad78f87fdd876af227a050c31884fda084", + "Name": "matter_g2_add_80", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d527500000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f", + "Expected": "000000000000000000000000000000000fa306f630d06c801e0203525c75fd6065bd12bcb3c4d45c7e02b597f85a53fae1e65a969feedca75068433547e4632d0000000000000000000000000000000004b1bdbc29f19f6484ea4648c70eaa47cf5bb07bbc255bb72dcf68a7b661de433dafb682d51321369cd3372288b2b9c400000000000000000000000000000000136671654b24e1ff2e8223ba747ded51f5c826b6e2c0f02e2865fc35d15045f41952835800406f60f966d1f241914726000000000000000000000000000000001007b5e8ed7f0d25091dd959d89732e9df02561a829ce013f5ad1adb8d6d828a8ce87b52d39fda1b5dc2b581ca420e22", + "Name": "matter_g2_add_81", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae452300000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf", + "Expected": "000000000000000000000000000000000fb74d9ad4de11df81c48d10b9a14fde8353ac47dc902b4420be4c086332be480552e26fc42b7c0f30e34f740bf9a4e6000000000000000000000000000000000612a7e23bbb525f91084b122dd4cfce4074c9e6eedaa7cddb58a14e0b1eccc2f08296baea3eb3e003e576fab7c557ea0000000000000000000000000000000016dea145df47a2c5262893c273c6158ee14d44c3740981c161624a6e9ebb982a52c1eab6160c3849f2bf3821d953f4c3000000000000000000000000000000000e920661772b8b737f1a663badead0e89aec4cbb86e6dece5d4db8a673e75b844bfe81662dff671658cb8386c16a7f3c", + "Name": "matter_g2_add_82", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627c0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77", + "Expected": "0000000000000000000000000000000015930559743b21acaf390b557fb960d3021f3cde80630d8867a063d445f860c8a01037057de1929be16d879416b12a6c000000000000000000000000000000000c6074c54c83f717700f61c5b6bfc641502121b59b196a1f8c5f2945e5db1bca0d7a94fdae96bfeeb6204c8c3f4d048a000000000000000000000000000000000b3a78454479c0990e4c65e4f831606c7eeeaef0faa86596350c9e43e84ae959a0f32c8d03d1f631d9b2ecd046efcda6000000000000000000000000000000000aff797d7572f20b06bac75bcf8cef879df11599ba7f8b86eaa28692d1239cff22841b66e28662309e81a6a599e79ddb", + "Name": "matter_g2_add_83", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ad0000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee41", + "Expected": "000000000000000000000000000000000351bad2f1fd9adc84280515c2d9e538b69dd63ac93514987ecace75d6bc4585199b742eae0d357d587924333721a1d90000000000000000000000000000000003e495b544aaf19a6415d5558170b8686968dc922367c5c8c212fa1f2785535fe0e71498b98b9a39c8b1f2384956170a000000000000000000000000000000000c7040f34872eea5f98ddc78737dd01fdafe75081cf66ad5c7c900674fa90257105b4f4fc59103dd5b92727a072ae462000000000000000000000000000000001312bdd27ef038d4a89b12c86281975bb34b435d42642fe0732709baf55e9a0ecc0ede8a4775a33e880aa2e1fa7b7ed3", + "Name": "matter_g2_add_84", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa800000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f", + "Expected": "000000000000000000000000000000000d521781f60198341d116fa5cd9e2b5c2fe51f91f6c8318f351df007c96086f6c3baa5cd2b9b4f442305695dd9b01ac70000000000000000000000000000000013454fc15b1d182bc98d75947547b3bbebef6d5e2d38ed7c67d76eee8da89ea2be19280af4760282fa7576412d5f2107000000000000000000000000000000000d866015c84de74c24dde252542d0d3823f435203c71cda140af235d88f3f4b736e9d75ec32c09ab73bf74083e76866e00000000000000000000000000000000147dfb5f53a9cc61b6788c911dd8649c09cfffbbba368c1872a31cfe3bd6d6427d7b00163d39f8e0b81fc4c40dc60b87", + "Name": "matter_g2_add_85", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bf000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c", + "Expected": "00000000000000000000000000000000059fffdf2d79b4a297f6912e3035cf0b07db9372f3485150e00d60bbe2e7d86f45b5c2ef062dd92c7e8b1e2be5e9bd140000000000000000000000000000000016acdc57e7231b020268373ddc8b8a7318ead02a8c7181165ab045208409373eaf57ace9a6db1fdedcaa477c7a0ff6f40000000000000000000000000000000012fe630f7de8ef5a129b99faff2de080849bf3b59aae1af042c29b1cc49c8825a4f28c4ccffedc6d568f306416b5bb90000000000000000000000000000000000d86ab3e49ffdc7c2485ecbd00256af83e7f3f064d212ea91245d86ca75e3c7f28b42fa9496a5ccc0514cffc60c9fb83", + "Name": "matter_g2_add_86", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e84000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb1", + "Expected": "0000000000000000000000000000000012ba9a8fcb69d15eff147f663a5d7927b6f3f79330eb9ee625e0100b146597554debfcf97a3afb51387a73554522ed0e000000000000000000000000000000000a63a990d6454d4db6d58642eb3489f79e517fbbcabc06f2eaa00c4b6f9a07aae97991f169d90af3461b7a62db276e00000000000000000000000000000000000a95203a1628a6ae2551df832f7ab94ffcdbf985e4c9744e244214c8e8b8079af05a9321d1e49b7240c2bdeeb7b783280000000000000000000000000000000001ec747203be73526d3f943e0af814dbede34020144bf247eef9a6ac2cfc83ef63f18a73d3baae18bfd8d5e83d0519de", + "Name": "matter_g2_add_87", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbe0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc", + "Expected": "000000000000000000000000000000000eefda9046a950c232c6244a79c33e7135d0896bc57839a4f971030220e3ca8196cd0ad75269f3cb5586a384dcd17f9f00000000000000000000000000000000195ce623693996f5ce9e45b4e285adb969e6771e6b0701fb5c95715523c8cb93aa641583821a3b360ad6f4ea1aedcc9f000000000000000000000000000000001553a4d0f965d26fbaba56294591935bed63c84abfedbb9d5c61f3d43484ea71600935fe3c8b6b137d7a9074d907e86c000000000000000000000000000000001673c42c88e4acf8ca38680694b80458f988403a4bd667468506452303000d13649c4f610b738a94ff88b65053731c08", + "Name": "matter_g2_add_88", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba800000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed", + "Expected": "0000000000000000000000000000000007145ce58cbe48405392edda6022ba8942df055ab582ac402e7c9a0a951cc6a38cd147903f042273e736f30849996cd10000000000000000000000000000000011b457ba464ce818a34a11afc3c0007908091fb528836691e6eccaa9a23ea90cdc746769c4b7ec73efb1f2878413c3b70000000000000000000000000000000019ca519fa6a91cb7e83704daa9b92da9bb70b003f9e9bfe9f323430bfec9b19b01005aa9fcd19d5b1ac59dbdab0c0d84000000000000000000000000000000000ae356f5e5de0d7662bab8d947662bf87d792a3438ed477cf6ed4b27c935b1dd76a5aac446d4dc36db544d4aea40b505", + "Name": "matter_g2_add_89", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591c0000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c5", + "Expected": "00000000000000000000000000000000135c42c10ef97279e3d152b18cbb8dac11ca8c805dd1d80818851424f592e7522589ec7df6748b5c72d0808399e629cc00000000000000000000000000000000083ddf3843434937e05ba9e101096371fd8fb34f226bcd517716200003ab9855f7aea94980c57a6b933494cc57afc562000000000000000000000000000000000be9215d936a49538442189c9a0bd3be07d4b0b1d14aa45afcdebc1fde17d33b66f7dc36da1ea5411549577f5a1967ff00000000000000000000000000000000176a4a4962c4af75a712e5093ec2cd5cb5c0433aa0657809dffbc0bc02b1ce303ac084f39a5721d482d41412d391317c", + "Name": "matter_g2_add_90", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff200000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de", + "Expected": "000000000000000000000000000000000bcd916c5888735aa593466e6ab908a05af528f34a7901fb60feb1f51737c73612436c192dfdecf927019724ab2a9b7900000000000000000000000000000000187d4ccf6c22381d0c40c9d7820ff8efe6298c6dad0caa25402412661737cb482dba2719c3a50ec08cd022230952dfc600000000000000000000000000000000164510d4f2cf1e14e039561f1baf82bea678d0065e378d5bb7443fa782e6ab2a3bf7e4ea125d6415a8277c60f5346468000000000000000000000000000000000281f2e28b73eca4db9966456b75de9ae3830c74ac928fc4c36b4aeaaffd47ee587d948f68056df2826ca2775415a53a", + "Name": "matter_g2_add_91", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5400000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157", + "Expected": "000000000000000000000000000000000cceccfefe04f94e0b67b29b5df8007930665006cb5a59504c3656b8c0bfb52324cdf50fa2722ce15b0ded0efa7fc85f000000000000000000000000000000000cdf34c330c0125f524f0711197639f8aca3e7c435f8c5ea30b78e9622c4bb72a7e584980cb4c3c6ecdd0689daf36b6a0000000000000000000000000000000004b1505d7fb65f6c06ef23aef85b16f3d991218187c5782fb635ba805da463cec9cfdd670c53d680c603adb827a4460a000000000000000000000000000000001104af6bef6482ae64b3b6b39664ec06c39bc18fa91b7b4e5bfcd444c827bab30ef548b28ef5487582d88fbc6d7983cd", + "Name": "matter_g2_add_92", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a70000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc0", + "Expected": "000000000000000000000000000000000e1ef3003fe3181f690224cbc7008856e1251430ce3cff56a1965c89a892604398f5101d1bec7ff1590b0cc3d23b854600000000000000000000000000000000185b4d4b5fd8313c31542bd1bac034046ddc705b41a034a00570181503a6ea4c2d808bba0478900064270fadf3d655920000000000000000000000000000000005bed63ab9898b89f92027c04ba256569e6285c851753e12760129c98899bcbab34b62172906a1ea4cb056d4d0a5717c000000000000000000000000000000000961129a3e212c7412018d7407d7ad16412feba8c138f4f6ba69daa1a25c6b23f3466bfde6f5f0d09ab67248a2abdc68", + "Name": "matter_g2_add_93", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa2000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc01204", + "Expected": "0000000000000000000000000000000001504c47ab0c410b32d5f1fe3d3996dbf1b21c5ef5aa3a2862a9d561b419f818f0b32b8e931c65fffc393ce7beec70ee000000000000000000000000000000000217e9fddd2551a171a13183ae3aba6bc5ce99e8f3587b92a7cffc738b478d8293b8c71989cabf9a55c5f5077249345d0000000000000000000000000000000003874de865d93650a95af4e153fe557c45bfdc4837bd6e209b8f05ad12b8fdee6432675cd92fd739b7e98e56e7ef16b60000000000000000000000000000000011303c0c7ec1f434cdf07c110da5f0bcd85935c3a0ce9fdf5546ca61edbc2d478562dbd9aa45a5f8d96e033feac2fdd6", + "Name": "matter_g2_add_94", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99d000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6", + "Expected": "00000000000000000000000000000000101ed22b16502de0d83303134a97db17ce956faedf47256a9ac86004bcd3ed112a71328a58f98a85977a7f22eb1352c3000000000000000000000000000000000e841a88d10493f301af54c5fe07a31ef90de106a6c87d5631b6967fd017f561a56176a5f3544dbb34b9f94040ebd2770000000000000000000000000000000001bde3c0076f26973651cedd3da97c7eda24451bda856026d1e22d3b65c66a3fcbfbf506b4b664b5fc06fca2d712d8a8000000000000000000000000000000000ce553ee3b7d5389798cdc5af8569aaf477b5b74ca1138454dc61badcf3ecf5e0ee8457e374b5735d0b8408b04fdbcdd", + "Name": "matter_g2_add_95", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d00000000000000000000000000000000030dfbb89bbe5c14a7a55e68edc4fc38eaee9fb539a6b2f941264c7dc295da5712b0af0f2bbcdb74f785dc9ba038b0aa00000000000000000000000000000000132b4e02fda605a69251a4a6289c47536f9735dd90908ed1fb619b3ab808b3a1f1ca3fcc8f4b35c9864ae311c15747f80000000000000000000000000000000005858ece0bb09e55e012450551025ad2a6d93a15d29619433742851a62d987e7f8bfa6c6faed76493a27060ef5f51805000000000000000000000000000000000dd6b393e6d1b8d546e3f5ce69bc1737399e6ababc628f25734030e10d82b5e9370edfb5da15566d80e23d2fbf8aad5f", + "Expected": "00000000000000000000000000000000182f90f5d3ce3f5ff2d91430376144583247def83b3e83524094d57c0f1be98b1c4946964deccc25fc303d6450edfbac000000000000000000000000000000001844806f711735c5ca18ca48e559a9e327b87b91d22a5ef161da7874668130e21a9499728fbc2c88366bdb59f8ced0cf000000000000000000000000000000000815e7cff14b4ceaf26d1cda5c267f432fad294b6baa239b65d886ffb039321f9e24330ae738a35298c6d1ec1ce1c95f000000000000000000000000000000001188a4a2f0920ddeccde1a47a0636aa7c404fd77fb9c828e4fdb5406df80ee6c258c2d4a89dae5e2a2b05210df9100d7", + "Name": "matter_g2_add_96", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e740000000000000000000000000000000017032b16be8656cf23bfe0abc8c9e6aade223fa9bea6fe25f95a025da79cea6adf38536eae3859b25ad1af1756b639cd0000000000000000000000000000000010975ed27cefbb43bafad0fd14c87ada8e84525e1d199fdf1e77caa0b718214b33e547a42a040ee3bfd51621a20d22fd00000000000000000000000000000000133d29aa41f92de37523d281eebfe91103f017e5fb390f6bad9a2a4419fa4702bfa04847edbca1da96eb1ad563a92c8a00000000000000000000000000000000014af850de7e800ebee4be1a33c7e3b30aa94106db7defa148568ca3c8d82edc97ab5769ac40162d3728687cdac201a5", + "Expected": "000000000000000000000000000000000cf42f2ccff2e0cdda7e5f1d7652680650b4afa523c8f9a554ec18b905c837a189fff73982cbccf903ea492ea902b87f000000000000000000000000000000000d38219770f669557cdb623f2476b5f3f7478422b016123bf86a17bf75848548d1a1ce96a292637b8d52481321d80fbe00000000000000000000000000000000170d8722b824e3291b570ba8e4f9279c1dccdefb95cb5b7a94d27ad8a93513737f12d18ef3153c4e12b530bc457af34100000000000000000000000000000000021aee9e5f578328caee3177a4e08303c3b5533e288dcb75f94992db3520a6da16f4201e60367240b29c48d175942cef", + "Name": "matter_g2_add_97", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d55300000000000000000000000000000000185aefe71f24281e5b03dd41e6d6d45fbc8975beb175118de7568bff0a9ccf917e9df97dc26bca16e8da06b0e9a8e7bb000000000000000000000000000000000015b326d401b827fdf556e4a24a3dd6c8036b1c849751b5ae3c3728cad88f931b06e3a345523a723481193f7afeb67800000000000000000000000000000000054ca16b4c87293002c31e64ad303e8f040e11de8b45c5fb9aca9dbec59b29dfda8532a8ef5ae6a92ac8ea90ee4303e0000000000000000000000000000000000b65a233a7731366cf24c801724265215a8626b1290d86c60bf1e74b021b0b44d7d6552f936fac7b5e60cf1feaa1d82f", + "Expected": "0000000000000000000000000000000010d1b2f595166929347e06c1debefead06334f554dc31f320cb844abdb1810b5f7c4b933ff8072dc03d303f4a6d0d09b0000000000000000000000000000000013ab41dfca0a7cb0c58c2c19e02f675a94d9e73312cfe2999dbac34e6a80bff9472506b48690f24ad3171ad495f445420000000000000000000000000000000015bfd0db53fd4da538caa3aee7a90a669cb84460365696ee79b190d09a6d4c3f08965de7fff4efeae435db52b97d213b000000000000000000000000000000000182ffc4304b911b47b092ab678edd63ed5f5e8a9069daf9247f3bf9c0dd149cc9992728a13b0a236fc9b37714b35882", + "Name": "matter_g2_add_98", + "Gas": 800, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db400000000000000000000000000000000085dd8bfc00ba517dc8d7ddb49d711d35bd36f9fe3843689019e779624a032d2f023533b8184b73042d1a1953d2885e50000000000000000000000000000000009ba8d5d36e6efe02097a3206bbed68529f0cb9875ab81deafd886d9243bfec8b403d2abe713a2ec929b93305dd2da220000000000000000000000000000000007f8f90ebb2771136a92023901ca85e87fb7c8b1a40f88ae564a124bdd0ff0bc27ea98612a817e2c871fb4bcea3bb06600000000000000000000000000000000152de417d02f1d14e5899201db8fd5db8ecb40ea8d415dcdedce8ac70c28d851db68e9aef94506a50ec28145547a2d68", + "Expected": "0000000000000000000000000000000017555399f979745302f08210de5311a6401b6b181100b3bc6b6d450f0f62079d2f02d7badcb164f50dfc46a975cbd6720000000000000000000000000000000014aea86c06e4c1fbf0711a8cfced2544c7624abc7ae7906cd992bdf575a702540c45c2117e221446ba09960cbc9048ac0000000000000000000000000000000002fac56960c4989a84e02ce36e8970c2e847ee45579d31ca77f042bf96505af574af822da084ae64b22ff876610ba9a5000000000000000000000000000000000a481cfea2aef8975c80a297ce5a185dacd25649d41f8466d3c63d786e3c264a8e4ccab5ef6b80ab1260e86ab6d5b3f3", + "Name": "matter_g2_add_99", + "Gas": 800, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG2Mul.json b/core/vm/testdata/precompiles/blsG2Mul.json new file mode 100644 index 0000000..ee9d50f --- /dev/null +++ b/core/vm/testdata/precompiles/blsG2Mul.json @@ -0,0 +1,730 @@ +[ + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2mul_(0*g2=inf)", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2mul_(x*inf=inf)", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2mul_(1*g2=g2)", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000011", + "Expected": "000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d536944", + "Name": "bls_g2mul_(17*g2)", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e", + "Expected": "0000000000000000000000000000000006334ba1e361fd94bbd98f44b75ae9ec00ecb4d3467b5528870b1a1fa9a7d04449f12af90bd4c7a1e3f29e717d6d19d3000000000000000000000000000000000bf4cc1626393956915845ea7ca43d30a59c7196fbe309f2d5ee6de7e40c191d29821dd6aae46abecf634b904de8f7490000000000000000000000000000000014aeb09e252cc74610ab956057d4ac5af95cbea8a6baba9e5062643dc037d6841044cb38b22d7dfb978fe0b58f94cc3a0000000000000000000000000000000000fdcd73452fc1ced1c06e6271410a48dea05afbe889a692905e1baab8d72418c62531aab8b74842b51016f0a9cbb93d", + "Name": "matter_g2_mul_0", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d", + "Expected": "0000000000000000000000000000000010e70bef8eb893377e7ff92168d7acef11c9efab990fbded728b173b94e1d99e471a8357f16625d353287086543551850000000000000000000000000000000014043c1f00221c439e5febd12724a9224bccf0389914461644daf329208e869b1bf149880dccebccd440b1748d15e944000000000000000000000000000000000f7dee1e7d122e410b29a9eb011ee700c2f230cf8f611e196ec66e153c1fc331175532a8f9b060b573bddaa705430c2e000000000000000000000000000000000e1f659470eab7c0741bc8777ac9fc8dcd11a6f1b30ffb4265e96b879e795a4dbf851d1149429dcab95464e89f334627", + "Name": "matter_g2_mul_1", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1", + "Expected": "00000000000000000000000000000000119a5147fe9ddca7123f721b5662c1a44b0964c37a214cdf3a4fd34166e3b25210344e65220c38ec84d0e3b5ccc7e46d000000000000000000000000000000001642dad5dacf4295b871fe9b2787f0861f158807b2b6c01c2dce12ab053c9472bd3cb98de5dc33f40053ff45ce5c9af40000000000000000000000000000000005bb5761602b6639f2ecaf79f2d1f853fbdf75f4b3852b90808b858993a83f8a0da8a2ce7072aa91e3b6b3ffd0b3d1e20000000000000000000000000000000000a75143b9551d4ae41fb8bd71fdba7826b994c65904d9189a5ac5130a59cbb9d8dee0e016735565148fc49823d3969e", + "Name": "matter_g2_mul_2", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a", + "Expected": "0000000000000000000000000000000017ebc9446f8c8e17dfeddab9188d0c808565da29c0bdbbc4138a44ca3196c4564853be28286b66660cda36832d6940010000000000000000000000000000000007f29a9583b4ae83d3913dcd72590a3f20f39eb5a6d36663c1ef433058e76550085b9c01bf797d98d0eef45cc22ff8c50000000000000000000000000000000016eeaeb123b12d1913ff1e50f974228c79f2b995609d2e3835c8e1d68773b0cd484df57b86111cdb75de1e19eaf062e500000000000000000000000000000000002f5688c1286aed42309896bd65d1826dc64dda615238fa9043669806968b8e0e1e3e77ef192b7df540aaf0ed282a9a", + "Name": "matter_g2_mul_3", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89", + "Expected": "00000000000000000000000000000000042d0c1941ae0ed5e8787437ad5e2753bba02185317848e8ec2e425ac954e0efb1bca534725adfe87e8507851ee337af0000000000000000000000000000000002db55ae8126cbe86327aab880381a81205e33a351d172c883b9cc184799866a8db5a6b4321496e05d3ef62d00416d9a0000000000000000000000000000000012c45444403dd62d7be3e7658dd85909204751dd7d085f6edd38c0aa9185d3c32407d8c95bba371b380f788d0dc48e0900000000000000000000000000000000111421c6dd0db595ab731adfb4bc76c84a61197cb023b6f17e7176c443f20a4b6f8cd0a00cfa61e831ed20b3c6a84d98", + "Name": "matter_g2_mul_4", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944", + "Expected": "000000000000000000000000000000000ccdb2a0b670f199a9b61198e6a2ce2117075733e6a1568c53ca493dc3674c6ae85be2491d2ed983f52e2c7040824afc0000000000000000000000000000000004f52450d7e041c561c00200d5b142b32f2df2e2156e4f6c15d6c00e185e135037a1ed6be15e2ed920daa00e2f9bc8da000000000000000000000000000000000f39c38c18f03ce6baf1d016cf32d7387269940280f2e8d21db4da33dbd2d24ebb93ae3dff9f79b015eee25813d677c700000000000000000000000000000000189df61f7f1025fa6fdd0a4708ff1d53db7d414019c4828de2520af3d36776062350061c2261e46e746a6475fdeccb2b", + "Name": "matter_g2_mul_5", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1", + "Expected": "000000000000000000000000000000001388a59c57ec8ca5e68b99631abdafca1b71352ac35003a55bbc415b48b8171857adda31123ec86a6ed9e1060d56aa67000000000000000000000000000000001471913b1ab5bcf9336665d3d44232b4e58da70285b7b8eb1dfd7c54442afb28c339f56e6389f89b84db0879e1ee058300000000000000000000000000000000022101b4de40b7180ea17bb36bad0a668a8def3e7361a96fbfabcfc4cdbe6f607ee4ee80d0eb2418b848ad056520092900000000000000000000000000000000103cda694792af5a51e04b6422600a0ea6f50808ca54423cd4f59dfba633daa5afea49c85b900f52e182610efb62fe7d", + "Name": "matter_g2_mul_6", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c", + "Expected": "000000000000000000000000000000000cf5cb957a752ce9187940f63b13080790348814debf84b91e74fd6e822c2735941d61d50d492439475bb3ea7aa849ec00000000000000000000000000000000012e546ff33dee9875510a68301f46d89e6175f5cd9a6e179fb8599a580e9478fb8d92038982551dd29041d8185c7474000000000000000000000000000000000d52fb57bf2996dbbacdbcb4088df38e77e25598b91bcd5e41eaa27b1398eac150586b142f068d5b498e0ce458d3e8950000000000000000000000000000000012295e1d1039abe7a5fea51a04a34e9e8d44a0f24b8c032680703c119d54274d3bc2e548854021ab027b693e43964314", + "Name": "matter_g2_mul_7", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108", + "Expected": "0000000000000000000000000000000008e4c57309339400ac9b6b5df16972c272d47cf69ba7baf89afa4f4e72703999c5885253cc35686f6c8d277399da2a390000000000000000000000000000000018ad4e1f105f16b0dbb4eb089c51e709c25e407e54b64346224b1abbe15d62fabb231e36a69eb05a9ba7860f772634200000000000000000000000000000000019994d20a7ecc0f234ccb6b1793fa7d1ece64b3e157c579fb05a8c6cfcdd6f5456ac1f4c1beadb69206988ab543bb8bb000000000000000000000000000000000d435e74bed382442ab83ec90dffb91336137932524bfcf9753fa5ddfe038d0b98a045c8ec9deb53172e5662d3fd67e6", + "Name": "matter_g2_mul_8", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672", + "Expected": "000000000000000000000000000000001425890b6c46c5a07a79127de4ddbb751227dca4481ab7c2f601bf22b8f6a149767c73bfbf57ee399c0f2d0b12852a0a0000000000000000000000000000000012cce15f53fdfffb5f71de3567b0c0adea65b9321c85677c574787f7048c1bb5e2dc985b65fbc48115aa129e6000fe4100000000000000000000000000000000041398497f975289fb9fc6ffe671a19fdcd3753c82ffd3b2084574107bf7fadc8de462507f4484c32df39967c3751a480000000000000000000000000000000007514a7f246006e714d4a8cbb4e89d81b951b5c41a05bcf35f61283e888074fb3686fb6ecc1a66e491ea1e1ce0738102", + "Name": "matter_g2_mul_9", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea", + "Expected": "000000000000000000000000000000000b24adeb2ca184c9646cb39f45e0cf8711e10bf308ddae06519562b0af3b43be44c2fcb90622726f7446ed690551d30e00000000000000000000000000000000069467c3edc19416067f572c51740ba8e0e7380121ade98e38ce26d907a2bf3a4e82af2bd195b6c3b7c9b29218880531000000000000000000000000000000000eb8c90d0727511be53ffcb6f3b144c07983ed4b76d31ab003e45b37c7bc1066910f5e29f5adad5757af979dd0d8351d0000000000000000000000000000000004760f8d814189dcd893949797a3c4f56f2b60964bba3a4fc741e7ead05eb886787b2502fc64b20363eeba44e65d0ca0", + "Name": "matter_g2_mul_10", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76", + "Expected": "00000000000000000000000000000000048ea2c854a0df7b10a2147db6eabcb16eba340644f737fc99663d1ef26d8ed688c2baaa7d7699c5f540d7605eb48485000000000000000000000000000000000c959efb835d48d3e7a8ce643764f27c365f6248a88e39092e3a6498f04ed851c55b796dacd62ae73d7edf23aa45fefc00000000000000000000000000000000114337b8caa68cea6f22a25c0ce3b247cadae24c63fb02c6a98a728b54f97b12b1473c8e23f55338326b9575a637bb2e00000000000000000000000000000000033167b0668ec650581815cefab61d13661f4cbc6e01711af0aefb699e1979b551d0031c603ee5f6dd4f716ea7aa4a6e", + "Name": "matter_g2_mul_11", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c", + "Expected": "00000000000000000000000000000000142f6b71471f3665ee6269cf598fc3587a62523f9753eec48a2461a2e313e376828cf6d1a9ffc9e64353c8a668718736000000000000000000000000000000000153647cc4a5aeb8ea52f845c415651e167ace9f331c1d73eccbbe20a4014f9e1158c281495206de4b841839438a595500000000000000000000000000000000151d07c3f83217e63b332a6c47e91ef2418e9c658353f8b644f23266f5fbc727562f0935b4d892db947cfbd0757ed61500000000000000000000000000000000035bce4bd2d8261e21476c325cb68e581f20513eb5e0e6a0ddbfd4ac4674bc323590b6f52d0cd50010c13642e7e03daa", + "Name": "matter_g2_mul_12", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a", + "Expected": "0000000000000000000000000000000014e83f87e7f66d8ed880ca46a76a5d3bbbacf2dafe1ee055f04af568738f4c6ddf2a93e1810b616da6f64f25c35a7b5a0000000000000000000000000000000003d14447254b61168d36f92710f95f7100cc8f278b0bc9528da763a18a5386b3f5b83b96f4dc426e4b0fbe755bc986790000000000000000000000000000000017f1a79ed64abfe5e960fda02cf3330e6ef5612c1b8639386959f86c970adb797bf077a468273d37996a65685f75ac30000000000000000000000000000000000d973499a7bf7132541c0976bf2e9bb26a2b6cfa5bda720352fa7a180a6b8fe95befcc13de5a2efe58be934cf7d8e664", + "Name": "matter_g2_mul_13", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054", + "Expected": "0000000000000000000000000000000018bb69dd6db0beb468242265c382de5ac342d465b5f72d4e5a24c67a48272d9a1f3af28e0bd3712e16a854c5d91c616b00000000000000000000000000000000072fbcc86b7dee9c2dc177dbabdbbbddb630c98ac3bf3737fd22f99e2b2b690175d9c5aa4b577f78c545dc6a5d2d03c900000000000000000000000000000000161c4218143ab1f0387f19bccdcd08f9caeb2d1331ca890741799ff1b40533076b6a96a910714176c770b25d2c17715300000000000000000000000000000000063098cd9d1eeb899724b40a2d10ac951ba0277db09aad639957f58541dd391fffadc5d97833bb9666b054e12debfa92", + "Name": "matter_g2_mul_14", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746", + "Expected": "000000000000000000000000000000000e43672f1bc25e7e0e64a3fd26cb246bdbd6fb5c9084afdc87c888634916e6a6cc9a351cc67a6ac77ab8e132ed6cbee3000000000000000000000000000000000dee9612527c8ee9c574a4c51f5d3504ccf1d5781b59c78ea15294332c6acfdcc7bc68853e70f1f72524c930e4c3d2eb0000000000000000000000000000000017eba629eb14a0636926275f1c2109318ce8818d8171c69fd371751b6de47bda5b00a0b0e3765d05bab7b8dea9add90900000000000000000000000000000000052f0a4cd9b91695e1e58ead1da1480fef08cecef63896aa51ab16da373b99b3b91767a374645ac5932d9c7fd21d4636", + "Name": "matter_g2_mul_15", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf", + "Expected": "0000000000000000000000000000000019b7ea673dad96c8352870136ea262c9ed105550cb403eb1e64ad598b2145fe1b95e5d61f1b5a6ebec47568c67b68086000000000000000000000000000000000f06ff9bcf2ba284e705b12ef2311f1a9b867ed742ee0737567b5c878547b18394b82c2bb97e16586515728245692cef0000000000000000000000000000000019dfd2d8fc4f2c989c7e1016e147f336174c84d380bab992bf1adbffe96d93d4d2d1d1dacdba3adfaf283b184478229800000000000000000000000000000000068d230422006004cd88ab0dd46a84af3905c7a1d329446cc23c1c5adb401a86a9fa76aaf577f77c2678cd8de8685ed4", + "Name": "matter_g2_mul_16", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da", + "Expected": "0000000000000000000000000000000015ffdd83355978ebfc386e13987effac0137ec628fff1667ede29cfcbd05e31cf8323959dd0247c20cf28978dc242c790000000000000000000000000000000016b1f810da2ae3c2ffbb6b83c47ef03eb0f298ff4c304ab0dd7b97207949d62858458d789c86c0cd474c34fa720ad3b70000000000000000000000000000000002a2e1463d5e795e6a25998a848b079363efc7d0337c3803385f4f17f11726b04108adfd87a811d709cbb6750c969526000000000000000000000000000000000289a3f472799c06a84bb1f377a36bad910220e1017884545159fe1b2505e8e7473882fcf324ba0d9125495bcbbc7226", + "Name": "matter_g2_mul_17", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833", + "Expected": "000000000000000000000000000000000b02ddcfbf391a2d6953261c786945093b09377352473a86cfac6456a811233809434b566b9301eea3105eb86922efcc0000000000000000000000000000000015430deba91113b841303120f0738012d77207e9408474998df5e68d0d61f1a64afb947ff93116ae766ca5325046e263000000000000000000000000000000000ab7094055919f6f707b458cda552f25104d95e4ec8d020ea4c17ac1d7efef5c4c3a769120718f1d5171eb8630a3018200000000000000000000000000000000161e7209f8c98e511a698fbf01735798cb632ae1afe00870654ffa0ba93a549edf4b97d60f03974ab0964cd39298401f", + "Name": "matter_g2_mul_18", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f", + "Expected": "0000000000000000000000000000000006cb218607a1f66ce361c89fd20edc3f00421611adc9aa52ec35d45e023174962c863f740ac36c984c2b466cfc4827a900000000000000000000000000000000152b22d46e9660da8b1be4c5b14da613731e750ff7eebaf879f7074bf3c33e1528a2c8479e0178707e3855b49f85f045000000000000000000000000000000000c928cf78cee2c8b9da8215d33d189c5636df1e8e9bdaf143aba7ed40f29490ca2328b4a20cfc56f62e4ce49d9e77f14000000000000000000000000000000001574b7a9c3931933160ad4eb17400b6297210db47bca034bc1b5d17a0cb8c41834636b9123e625e5eb0b01738cd6b9af", + "Name": "matter_g2_mul_19", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc", + "Expected": "0000000000000000000000000000000003e17452a80996203fdc4037db072c452f9eb2dae689c77c88b299d7ba266d111ab2b9c4b24149968d72cd143a34fc4e0000000000000000000000000000000014a057d7a50c9b0f34712ff8008770080bfa671650fef43c82726257da180dfb9672b266d4c54d65fdc677d917e6c5b80000000000000000000000000000000013b452c980bfc4a484637b578be100753aee9dda9487d5ee5c017c689dda838fc673804369328192d780d60a9a3de0f700000000000000000000000000000000103aa86d1807de242a6d4fa4a49be6c91cd757df5808501acfca44940733c6a524b851ac962b99a9be41bfc8d6254478", + "Name": "matter_g2_mul_20", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52", + "Expected": "0000000000000000000000000000000007c616472f9ac60f749979c6f870b587425d514395ed07558ed287fccabc77f0c90872f3885d0780bcdfffedd124eb3d0000000000000000000000000000000019531e9c25e84a2a968a85d9f1ab61a372ebc59ba5bb7a2bbb3c0d6e4c9d04061b28fdc719735e97ccd5f7243a58cdc70000000000000000000000000000000007772d3cff12bbee916a6569edce0c6dbc2bd8a794919a4dd7bc37024c8273245210511b8f6da551fe626b7b840833f300000000000000000000000000000000186a3e858a83a7ea1bfdaac65c2df1076059aaa193961559792373886c68acd2f9fca61b166a0ee55084a6ea122ec3e8", + "Name": "matter_g2_mul_21", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1", + "Expected": "0000000000000000000000000000000008adebaa95d10b9fc0f1a1f0d52dd6741517d2ba23e3f9e7a9221039684ae226ea602dbb50df0efd44b2b5bf7495c0b50000000000000000000000000000000008e276e78ead2473602d37cb9f2f589f9c60514a1fc5c215acf487bf57c935467d29945d3d671b41a8e47c9495dbf5c9000000000000000000000000000000000fab06240cb8cbe9afcc4ebebde50c2881e4bc4d4f2ed09a1065e3620e6344fb3c5f3019250ca4edaeae4902abb7400d0000000000000000000000000000000003fa6c48ead374be1dd45c8417ca8234c15ddefc5039151e6cd7fb27f866e134cef2f59ac9b2ec1b26896eaec9213549", + "Name": "matter_g2_mul_22", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9", + "Expected": "000000000000000000000000000000001412bdb48546014adf3c4eac4dbe79ba700f90c8030b063828fb01be5977bd73107533a4e8030c8d9cbdde9bcf10649a00000000000000000000000000000000126d3e1006abfeddd810cb1e12c898cf5f543e414438e600ce4c94cd8dbd1e17c0f3b9831add397feda74362eeace6fb0000000000000000000000000000000005b3159638afa34f219513cbcbc51567b16fd5598b85e6ae0d232021133cec25a6269250df2ab7b5ace726e9e2fbf0b0000000000000000000000000000000000c35bfdd1c10e903da6d41e9afbe65b0cd66addd7893fde41dfda8e543a93938cdeab52cc9bbdbe61f93d651bd1c923d", + "Name": "matter_g2_mul_23", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b", + "Expected": "000000000000000000000000000000000bcc781f144bc148687875789fd8c54dd820170984b6f8ae75855f7e45619c1d2ff85c330b7743e447b5fc831dce9277000000000000000000000000000000001409aaf3c94c9a6b5123c82a7f311af7c2f60e9b197d49fb5b010f84faff972151b383a83c106de43454f8097005f6c800000000000000000000000000000000064a91226da8b9cb587030f1f4afb0d422a51e4d55212f26c621abc06fc0c57a473a9be75518a5f4f9a7f8d4aaba69830000000000000000000000000000000002cf239343bb77865ceabfcc1fe34cc9be4a1ebc3a70f16f8b7cb84eed5843524f95673b01466d6cbb0d8d9dc00793e6", + "Name": "matter_g2_mul_24", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6", + "Expected": "0000000000000000000000000000000006bbdabfe104b62d22e78bc8f3446a86cd5f10c4c5a54501140768b55a7e6940b9952c9a90a14d8fdc7c04600195cd6500000000000000000000000000000000172e718c926cd393bf303984518432693c304a2758174dabba303ff4c0289b5bf5376b61e8821abab322d53e88f71d480000000000000000000000000000000000a2f84fbdb5b05107a0a340e81b56ddf6d03c23848448f841dc44f07cbf8a575289cf6d53986f581fddb0f2d07e38d70000000000000000000000000000000005cbc10f143a9a1fe23f670a4c47d385f5c7069d8c46580322d6939122b2d39d185d6a8c2e51e88a1d40fd2e82d08b8f", + "Name": "matter_g2_mul_25", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36", + "Expected": "0000000000000000000000000000000011769e191fe258ffd1922295a9fe877ad5a52fde6e343730f8f5ec6cdcd584f8ed1dbe0f55b5dd81f5f78b7437f02abd000000000000000000000000000000001253689089e9192d10a45342214425de36740c120e49f596d24658941ce2b2ecfb50e879be0125e3d159088f88e234f10000000000000000000000000000000017b642d1b5a953f47fff8f0649263f16f41a0ec0397d5a81571174aeb85431c352e2bf6bafa6894d2e6cdb5eafff16d40000000000000000000000000000000017b3438d0ddbd2ace1e63802013b5bac00d31889dcb2d9653a6f6412d157aad2fc45267322a62129087380bec65ec169", + "Name": "matter_g2_mul_26", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc", + "Expected": "00000000000000000000000000000000089a07bf63b8029e0506393828d8593b94b73c750815552f9a3c74ef7470b5810bc27212ba02ca6fdcd97e1e28a52a1e00000000000000000000000000000000051a93291d4b912f0a594d45c0264a9073663a9ec75e6ee81e13e79383d96e9330bab845fd1e5163e5b28c41c4a854c40000000000000000000000000000000016610bf2b2006207046e489294a132937edbdf95caf508f0df3bf8502e641aab9c44903cde75cff3c1f86873e06cc58c0000000000000000000000000000000005d33669fd8a6256dc55f513bb93cce8bae62a593eb8903cb7d7902a7727efb8fb4bb2e5058441c30b99f146ff5394c3", + "Name": "matter_g2_mul_27", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792", + "Expected": "0000000000000000000000000000000005aa23543088a9a833d773a71275e73fc3081e13c907b8a04a330df7d6c06618fe69e644e0ee55869e364d3561e40f300000000000000000000000000000000010eef9238d2c520f32243f07161f3e35b15fc949b9401baa1a9c5df7d50b2cb3bdd237747735b235862bb57322fd9d090000000000000000000000000000000012dcc16496c95e39ecfd8f0514b5ab2569d89826d957478cdecd4e827095034e974039b37e767a0f25bf057ed715aeb00000000000000000000000000000000000d0593865fd2172ebf1b94c7511ab7d433a276bf833515146adb6d79b6e09d7c18f4c7f4d3241c14d01a4ad0f31580f", + "Name": "matter_g2_mul_28", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c", + "Expected": "0000000000000000000000000000000015785bae0c27680cca2097ab52306207a61ba9903723f574091ef5e57c2e871e076d7f46e6e39f65a01e183e7bd822f000000000000000000000000000000000071110a384248664db46f21d87b455a3ad3c43782c68304ce17f52cc8579fb2e3378995d6eb3b8c97665e5fb7de665fd0000000000000000000000000000000019153a01c2b3c5d481474a71e5c67f27fae3232a0c8f1655ddd4da6b4c79870bfb0b6beb4af8c54aaf7e9251ad41d639000000000000000000000000000000000c58375439a93e0763467c6a11dada3e579ec53a968c9b9c1a446cf3224ea0c89c9ec218a8b78de91fc12f087e722f94", + "Name": "matter_g2_mul_29", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa", + "Expected": "0000000000000000000000000000000004c7495c03fc3fb4d0fd4e0e660d6424de9e060eac72eee3608ba95bac294a3a62d246f42dcf3b575ee1cf8e20a9106100000000000000000000000000000000091140aee42a9dc875f87f3ba29beff95138790140f8bb522c6c15281b3545995f9c13b0b73ae691317e674295db6526000000000000000000000000000000000a945a215b2861427e0fbbfc6fea04e79edeaa1eb87df5db8e5e017cf98fde7b8d5a04a1b2129a4aadd2e3924ecc0bb2000000000000000000000000000000000a43f8d3d92a03b7bd4c8a34ce31729ea0b8e6b051c30241dca2db31a02b6e537071a914d8f0876f944dfdb613540c6d", + "Name": "matter_g2_mul_30", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c", + "Expected": "000000000000000000000000000000001821e14e70e12c7caf2a1ab651eb81dd61c4e1eec9a02fe4124abb865a7029e066f03b62e6ecfcf0fbae5151272b524f00000000000000000000000000000000044ac4a7399d6a67e7ee8cde3f5fe20b0a745462c870926f0ce8554061eba5bd62a8a08c798d8bfe30fba5567d47c7ec00000000000000000000000000000000178b8f061ad9282b3b2057f20c115c91df994ac40aacd05b7669e934bc7d650a0cd88f9fe17d7b766e34bed587ead58200000000000000000000000000000000188311eea279ddcf75f8dd82643ca3efd560ddbe6c8f2696cf7da03e65cc90d97b9f9ce99e29269644d8b881e624cca6", + "Name": "matter_g2_mul_31", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd", + "Expected": "0000000000000000000000000000000012496dd3c1278b55bde81f6944c4bdb71869f5e5e21db7b1425ea32fa1dbc8c301e7f5e68cd7629c91650265d1361e690000000000000000000000000000000004a1251591efdbdbeda21eb89165ca61a2e090a73426451b6933d939161364c4064a67a90f859a7713fb6a9c5321d5a200000000000000000000000000000000163bcd07d030fd6ab8a8e0bf39b136dcb34f03925c3fdadf55e94a90bfde0ecde5c51d2f4d06954aa6a96c913f2ab4610000000000000000000000000000000016dc065a852ef9e038d93cc583b4a71db9b96a7e7a819dc530598f1ae256368438f52e4b709f15f56279b9c7f9db8785", + "Name": "matter_g2_mul_32", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45", + "Expected": "000000000000000000000000000000000a2397fb3a3891d1703eb2112357c5fb8acb070ba9f3a39050be6f05b49b8d2488e94adfbf849c8b4a42e287077e9fff000000000000000000000000000000000cf2c02a97addbc1584091e411f9a07135f1fcf989dfc8ae29155ac90b214ce20dc11a1fc75dfb697694891d934abf0f0000000000000000000000000000000018fd4af647bf0456aff9ef80969613829f8eb837205df552aadca46bc3bf9838e0ff2515d3fe869f80d78e2357091d8b0000000000000000000000000000000003c5671ea4723498359f29d49ebe974099da3dd59d21065a721f7a4f14dc7fb1de3a67a707bfa4bad7058312632c6113", + "Name": "matter_g2_mul_33", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b", + "Expected": "0000000000000000000000000000000000676bd7ce63d8b58cc1e5399ced9b495baba4cef9503c44760f92d6d9e092d6d5308fa88144491eda6c571a8c308786000000000000000000000000000000000605cebb4c20bc9dff0258f75a825f55f23a32cd0804dce56bf3cf2f19a3504f0345e0f1b839d4d5920aab19b363ae19000000000000000000000000000000001512f95f60a6dc79dd9261c321328ab8e22ff314e7582d8de83aa3bf280805cba8ba6d359a620fa6f0564396a45ca9760000000000000000000000000000000005837474ba78e0700c77141d70af1d8fb95a97cbadc95996faa93c2e81b7c8877d08d5287f83219a24bc0080e630e39a", + "Name": "matter_g2_mul_34", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca", + "Expected": "0000000000000000000000000000000010b2a9b32e431c11ceb474942bbbd6915a3cff64a74d67570fadeb7447c5abcf1bb35c822d4441565322ebf75e61f64c000000000000000000000000000000000b75a0212232af0a59440482a1f953cc29bcd35272ef407925eccd70c1dc4705dc1e97d2da604996d3c52155d05d77500000000000000000000000000000000018751bc59f5907cbd7f1d503bc5aa266f4109fd3133a1c4c2e58e4a17250a40053b4489da4825b4c368b0f4947baa6240000000000000000000000000000000019b41fa1af9488596b09c587fc33e044d51674eb6087c647d5a762d85e38a587eb5482687d9346a1a701bd3a8bd36a61", + "Name": "matter_g2_mul_35", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc", + "Expected": "00000000000000000000000000000000054836eb7ef9edbe914bc16d1498e0bc3c978bbed2518802c2f8e1c0b59fee482cce0ae8e805c33861d4cd595f6b8bf40000000000000000000000000000000007dda36d55aa7a890aeaecf2528a390c98d9ecfc8a5c78c2a6def30de55b90ae408ab770cf9a9a4663ba601c4f5765a00000000000000000000000000000000007ff7b24c8ed9fca572069e72b1e93978cea87a0fac7ba60f54aa573d881f21b73012b010e9c0fc9324aa7697bae0c4a0000000000000000000000000000000002d9773bf294efe64021e755e4dd2936a5060bbea5688b6369ffa3b94eadcc58cc3986c74ff365301be1e6c785939b69", + "Name": "matter_g2_mul_36", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447", + "Expected": "000000000000000000000000000000000902c1082ff09bf93b91c9ef5e447bd6832fec9297cdb065f11fc5ee626e6e8834cb5d74775c586609a0394e6114e8820000000000000000000000000000000018e414a40c27430b98246fef556e74dd3dd7adc601e3c05b79f8c29169780a173be9a725df3318d71b6e82abf97930bd000000000000000000000000000000000f924fa88f43c86ec98b34379b9a649c7564ef0dc596c95df19522fd50fb3a37cae031e891a7a7aa6a5e6a9062c3726a0000000000000000000000000000000006bd3340412f64d02d0cb3ac44d1f31cdb1906e56dbfb66d86b60a74cd26c1e241963fcd8bba4109c428db0bb083e81f", + "Name": "matter_g2_mul_37", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828", + "Expected": "0000000000000000000000000000000001415fbd8afeeb5796460a9095f14a8f3f6fe0374d4cc4160f030710a6d4d3a92febcf4dad770de3a3ba1a2efbd858210000000000000000000000000000000015792220c7e53262b56224d230a8a4b32019c77548704ec16da5ce167854305e6cdb9924c248f222d6fe95a8383af7890000000000000000000000000000000001694329d8e0f41256b703a8bb6548f1d9e0749a55c124c9b60361b4cb1daee24fcf272327ba598022a92815764fc8570000000000000000000000000000000003350658842c5b6fc5561a14df27d950a00c5bcc13d6d9d014bfd6dc95ec1a030594625f41d439b90b05275a0ffefdb1", + "Name": "matter_g2_mul_38", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a", + "Expected": "00000000000000000000000000000000054c6cb26c8b0a9a4700e0b95348e6fb1190c577eba03a44e84fe7744c543321d02c4d8f55c03f984b44ffbd899ac53a000000000000000000000000000000000e7ab8da5d573cb88a78f6a6ad2b307bf867777f79a643b6ec89d9cb208711c85d7d2cf8f8ac69a8b322000fc7866024000000000000000000000000000000000fbc5926b9dcd9e4d1ca1a2b43dab5c98aa20b37aff0868c54441de44eb014e5283010642717fafaa95000f4313e14840000000000000000000000000000000003671ee05bc20bead72f2306203dad55cf20b13d3bb2cca079bf4391411b85ed4df55e1426645d73b6935889d4450c58", + "Name": "matter_g2_mul_39", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb", + "Expected": "0000000000000000000000000000000013fdd394635f42a926a2324b8cb870b5995772ef4e25ebc1da41dc5bf724f747da8d95a28dd703b5ed65ada5555c8b5b00000000000000000000000000000000118fd550962d1de8f1e60c312643ec7cd306f0bbcc932739270595537c8d290ca7e20b962fcde570bd2ed7ea43009fe70000000000000000000000000000000018b25fef4b75fc7649a489d078311dfb6da9909f472de7bd9bee9c3ee353f345c83119269ab797fabdbede41e0fe6169000000000000000000000000000000000b7c2a73741f6944ef4ce8fa20b2900612645c224818b7faccf6597827fa07f7262295f42be5f34a751a6400495f7eaf", + "Name": "matter_g2_mul_40", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425", + "Expected": "00000000000000000000000000000000177d29de8a81db2e515d4241e5f7e3d35de22bbcf9aaa616b057cbf2dab57ab8d98213cdec82a2034964f3e1def8a4e3000000000000000000000000000000000a0cce8113eecb064a60ee2c470dfae8b3921f8da2c7ad8dc918b355ff44542b007add28a44848fa8d8f8671617431ff0000000000000000000000000000000010470fcc723286327e951e758fd0474de394778d0c1ec5fe6f263dea1957c60f05dc8f9d82b3c6a7d73b3e783f35ade500000000000000000000000000000000098a6ed331f03da7ccc9148f07b19b132152e15d9fdaee5cc092524b33795edf2b458b4e8383c5e29affd3f025094033", + "Name": "matter_g2_mul_41", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96", + "Expected": "0000000000000000000000000000000018a1f1a60172a65abc8f2d855ee7510c1e0af9bada084325027bd493ae86ea2c62c15ace7f63562a82cb80ee7095661b000000000000000000000000000000001736b977fb52eb1b466cec3d42df7e89047784f0e8362eb6425e37adb1e84d0438f5a6e82c7b31d59b0959a5f4aaf9310000000000000000000000000000000013ea0f849830f8e48161e840295637d8596b32eb576560289620b797b14bd395d835e8140b69039c904ef1d07a82127b000000000000000000000000000000000d7f58873701c138cb7e18ffc36cd0e47b07d70448ddd9fdc4b947003fb29cba0775916c752d531e527ab744c277e5da", + "Name": "matter_g2_mul_42", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc", + "Expected": "000000000000000000000000000000000290fb3f38937ce4439ceaa21cf3b31db8a22f9f5ad9db0fd7d38ca978192bc05d41152f8f86ca7b2ee0bb58e125f57f000000000000000000000000000000001775913fc24699bf08f25fb946fc6527178ebb821c654b7bc69f6f86b5168fc42057a5d3bfdc53b3d57fa1ac05f7a0930000000000000000000000000000000017b9043cde8dbf500ad90463250a49f56b35713f2fd9a35d8391fc36c78c083e39674592a98cb857194ef9e73a62a397000000000000000000000000000000000e5e62e39433d443e7d2d32754d2ca2556cf6deea45e5076ac040e3d6de14e9965c53f8c65bd98ae7d17ad3a26f3accb", + "Name": "matter_g2_mul_43", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca", + "Expected": "000000000000000000000000000000000d9927347a9ac9b0290e68143fbc6a5f4476604c3fa5ae87e729a03ca055e4c6543f9245a4592e195180d88781e46ac900000000000000000000000000000000175e0ee8de4002b18f32f70f1bfa9e0be87288cddf1c436428c2969884112bef5db19e041cbaeb23596e25cabea3777300000000000000000000000000000000074ed9e981818102b9ba818d478ba27033eb38e3fa19cdeb9f5820e59a64dc451342a160359c54bc8ec7d866b62080ef000000000000000000000000000000000a853930020bf01e20816d3aed242e00792b0d0e78fb15403fc3cc255f0dbd99ea6ae1d59d5978e562be4862b3317324", + "Name": "matter_g2_mul_44", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a", + "Expected": "000000000000000000000000000000000e9c290ba8a22f7bb3f7dfdcc9f5a221a5ce838d4fa85a00473a4dd830bacf583dd91a6a6f78d2ebb54a4c1bb217f793000000000000000000000000000000000dc51b0ae8bda6d28c51016764fc028258171d7c7646393228692aef7f1dda4a83e53553f63d6ba996d4c0a802bc967f0000000000000000000000000000000014ab155029dd35206811be9ca4efbf762a1673367e6b57528f79eb50008ce7c3b49a2d25da0ae68ac4030ab4bcc0daba0000000000000000000000000000000008cd743bb52e7908aa973c8518eaded75fc2858f4edb25fb7f2e09900f0abd3ac87e93cf1068bbe0c7d99619aa7a6b76", + "Name": "matter_g2_mul_45", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4", + "Expected": "000000000000000000000000000000001746a449993b0684740630f3f0e46eddfa135371e33e8de4dfe553c78845399e63bb3da48798b35df48d27e1f991954400000000000000000000000000000000057e0fb1113968858981c9803166d8b3eacc91bfad320ea0e610fbc5b276da1b46d74fcc54183ba61d1b2fe6743097c90000000000000000000000000000000000b3a178ae3b739cae3e80f3f44db42d8c465a5cfe4943b449d4c3b7f4ad153916c6cf4fdfece14a00b271222c72764300000000000000000000000000000000041c8b293ded0c647f2e4d6f9b35304179b723c3e6e421a5cb103e561d1655b92e74877ce22c99f22a3700c3aba9ebb9", + "Name": "matter_g2_mul_46", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556", + "Expected": "000000000000000000000000000000001103cc395acf81772955bda38f951a81c5a6a476c0b5e1543616a5a7a7be22dd487ab2a8586524891300adec5225b4020000000000000000000000000000000003479a08e2811ae9aab0301d66ada470935984d7466201f3fb28c610c0b5f67e7305f5ad3514cec5f30b51d0aae775d40000000000000000000000000000000005ea37a6d20c1ad0978da68ded3a5bfcc5ad8fe81e39b525fe7d1f2b2b1ab0be7ada80173b1d0b7fe1e06ab6354e64b10000000000000000000000000000000008f2093151a285dac511df1755e99a652a1cad0af3a019650fbdead1421ba8e84afc9eb0a4fea651f365d72f031a0ca6", + "Name": "matter_g2_mul_47", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7", + "Expected": "0000000000000000000000000000000019f79677ea0e011e5c9a892a407646798b05be05337c73135cb771abf101f450bbffd08e125f077f5ea989decc009b9f000000000000000000000000000000000ed15f35966024cf1de2926108151e976dcb0d51b2736b0877d79de81f6fccb9dd299d14855f4e257cae33ab7455b95100000000000000000000000000000000125e2fabb5cc95c0a7890e9ff2b70102a97a03f2d11d915cf4332dd049a467333e12ebb27955c0310ebdfe2afb3173ee0000000000000000000000000000000011718167000f9b749f1615610a30023db4b986364da5bbdc4506c726624a073548a94307b282590cd8a43b4900a1afb2", + "Name": "matter_g2_mul_48", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602", + "Expected": "0000000000000000000000000000000005af8fd9e79568b46fc42b2c1bac62d115365834e509dab032f66425b7a571a4bd3bf702299d3c5f36c372750b3281f30000000000000000000000000000000018499089f306b3c9f7a645ca2f9aabc4e57c046992fff87e832e21e21875c6adaca050ea8bd7043afec3a36ecf8eafae0000000000000000000000000000000000827fa0f46134e2dff80088129841f0469ec7360fd8b9864e9ed99c5fd3458e6360661ab4c671846681d491b8b823d200000000000000000000000000000000120f829e8d0ffc360a14eabaf52bc653b1e90a36c0a8af806ca745fa306a9739e31435039a377e0748caf5e80c2b0b09", + "Name": "matter_g2_mul_49", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013", + "Expected": "000000000000000000000000000000001745500b00e5ebc6f71c779ba0b0f8d6601a065c550ca19de9562455423d2ccb507e659b0dce982faa841267fb1a27d90000000000000000000000000000000009c36b54f12d130868ff9b9b61b714fb1067dc91637c09614c51b5aafa2cbe3ca7dce0f3e366d4200cbf603ad4fd630000000000000000000000000000000000172e543708bb853712d81c000c9f9f2378e628b4d13b074317e95deeae98e11e7f917f91e02a0b18cfe9b25f1b83f16700000000000000000000000000000000189fc572ff6a8c6606ba0cea7da7040898d9ee85a58f12fade8c5a22031ff26c2f9cc612bc6e1b82a0999fa93c6fdfca", + "Name": "matter_g2_mul_50", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8", + "Expected": "00000000000000000000000000000000013c6f777df97ad3ddab9b7486d54d1bacb3b40ad3035b47a25a66c02e8866955e27a8ee52872c8222ff7466c1310bad0000000000000000000000000000000014a5eb510d7c743e824f4daab21c43db4d6de8ab2e825d13ae0e186aaba828d7b4a2343a11011a8ec4ea82f456e394a70000000000000000000000000000000017a55d3827b78a9c5ea792b705eba7777df74951930791b17ff5b861e98a4488f83007c073c3e904ed4ee328b6f6171c0000000000000000000000000000000019bae02f8d6f1e31dfa09f4feedd5217ade66f6e8248aa98b273574f72aef83d5048534ed38acab9e0eb4c64f4389af4", + "Name": "matter_g2_mul_51", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b", + "Expected": "0000000000000000000000000000000006490c327790b4c451f93197d7db24211a3b4b5f573a6df409206b4bbfc36bd10d2d0c989889efffd8f4daa4a68b211c00000000000000000000000000000000168f224738db3f07af77494f52ea5e957812a1acd62615f0eaa95c1d363cfceff29be9cf3be5329bb41175a0231ced4f000000000000000000000000000000000321f06b55f7dbfd4900b329c914f9ab9be2794e51e54498e18f83ece5bfd205131fbc254bfbf624d57ec2954b05f6f00000000000000000000000000000000018ec54f3e09bb2a6b112b575f9481bf1c85666133051e9c0ab53369d14eb90e27d2ed02dcda1250d5d539df0d0cda37c", + "Name": "matter_g2_mul_52", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244", + "Expected": "0000000000000000000000000000000001641b4ad10da5089164809d82ae47f74e27eaebffc2a2ca3c1b924fc69c1ea80ba3da78c78e86957f6a24e7f75dcada0000000000000000000000000000000014e781e4fe79ea1654460f4b0daddaffb29b287efd8168cb20d7ac6c729f684c5f2a7cfa87885accee3a797febc904c200000000000000000000000000000000001c9a44547f0c5b1f4df190285644c5a31df61e3de7da085835ebda917d5e4163f2deea9a83d641a4759fa3108567ad0000000000000000000000000000000014c3d2a79d80687fd6e6aa423257644fa5d0cf641aaf6a7c5675a810767904166fabd9a2ced0727e3badb932e46fd181", + "Name": "matter_g2_mul_53", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83", + "Expected": "00000000000000000000000000000000129554de7de9a2b73340d94d96f0356a2d1c0524cfb007d76a75f462872e831f45553de05f5b6a1f9eeae37af7f6b4c9000000000000000000000000000000000b1ea2a649ca13a3dc7882f2423036670f68aa05792a8fcd72524420e37381a9ca80dfea701fa5e6da57afa534059617000000000000000000000000000000000b7ff27aba408f9759b5109600cff66c03cdb4bfb3dff64a4838d0516fa46bfcf429fcf9d5cbf74a27f70fdccdb1238c0000000000000000000000000000000005a99aec88967fe775c691d443e2dbd45080eec97e686ee6d7b32e801efe6563315bfafd5c7622d0543519cae4417029", + "Name": "matter_g2_mul_54", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852", + "Expected": "0000000000000000000000000000000007997a499b2194cab634750a189cca6783ff17d866d66f5998603f8639d2242e8039222c65b0d14001167a9b09afb58a0000000000000000000000000000000015050fe6b335884a225efcfea4acd025cfc05e8f5fe9a0e22a0c91b55664c118d79887de91f1ae6cbc081f6f55f0067000000000000000000000000000000000195b23c4c2c087082c30600ff00485d169dbd360643d163f1db363f270cd7d4f177c36b4c291d50da4101e67b229d0de000000000000000000000000000000000df596ba2350ff7d3e75b4cbe5f8d6b2cc0e14b3bd6dc021936e3371ba64031f6266fb1d2951801309f22bfb1c4b27e4", + "Name": "matter_g2_mul_55", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c", + "Expected": "0000000000000000000000000000000001fa243b548f8f5c2e5d7736ca6fa95b74dbfd31f95fd532b94f81a255c73e7c0e000e20f9ca6750cb0dfdcd2c1aea8a00000000000000000000000000000000132a893a2326bf61962e1855331a53667e6279ed7358bc84c4a7c218b6cff1d3f449954f56daea72bc2779c60f1113400000000000000000000000000000000000091dd23c75dd8266f556bf27ba54c95c3ccab06168e4e6d0747239722afb20f3db27454c6db3a88daab0ef10659a66000000000000000000000000000000000d3b2e3fd358aa3dae983e87b5d1fce6d5688e66ced6e3a2c96b8d48041557295d5932af6532c13965d4b383fb252518", + "Name": "matter_g2_mul_56", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0", + "Expected": "0000000000000000000000000000000005095d1becff61df906815842112c6508d6cade4ef5f4b7418f5f01e8c5a383addc1c572237613dfbbb88bcff80e4a44000000000000000000000000000000000bd2561e7bfbda8a48ee038855e37b03fee805689452e9afaf0da4185e0c194e407ce7149b713c689d25f953da36dd1f0000000000000000000000000000000015ba3ae4d4238175425ac5dcbd9e6e9e055b8c1b7752931b524fb546f7bee8723ef2e69351450c6d1ba3c366a22355e20000000000000000000000000000000008c17d77dcfda00a1d75ea0087c58e74263ce5ce4066e979c66397de8e236708831c3a9ca6b35ade8038a28930655eb6", + "Name": "matter_g2_mul_57", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45", + "Expected": "0000000000000000000000000000000005cabaf39b93d7fe15ef6a7a3031df58219bce702a5a77162551a3d916c22e8ec9af2aa20659e7c4ce5f6382a5f82726000000000000000000000000000000000dcefe1a48d8c239164b54771118f7520ac11a7a6b72d8e17be1cd788cad2f26d3a0d9113e6536426800a744be9f0d4000000000000000000000000000000000199d95a44a4334c87aed273a0184be9602ba443d5b8d34f3495b04e927f4687fb88487f586395c7babb4f218fdbecf8c0000000000000000000000000000000010972032f9cb3e8f45447bdd06df82656fbd3ce38a9f7564c6e5d62ea3596c9b7e0a94046f1c65bf0452ca25b15a885c", + "Name": "matter_g2_mul_58", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f", + "Expected": "000000000000000000000000000000000f250b5e47ef616be106a3334e2f516061eec8f7ac69f08f6dfaedecd76fb1c9685ecdac2c3ddd534e3947d007ab177000000000000000000000000000000000073819a6de38303725aa3a9e5a7a9122b4d1e60ee8deb3554b5e06ef5e60d71517c2279c5066af003b32cdf83b7fcdf200000000000000000000000000000000070721107ac6dac198f7ed1a7f84697cbbc3199a220d1aaf82e6f015963bad863f99190f18a482f730254cef753ba22d00000000000000000000000000000000169910eb30b8fe1ad8f84c4a132c6c74a6ff06ed6e792af3baa6619e3c8aa6cc3e6f687299467ec9554f9e91bee77aa8", + "Name": "matter_g2_mul_59", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d", + "Expected": "00000000000000000000000000000000106e892e336b2155909946ab73b980ea761cfe8c48b13ae8a5302eacea08b9cef3e60d5b33c6ec4033218ae5761433dd0000000000000000000000000000000015daeaee59f3b4cc26d3da745661e74db8fe1ea115d50ba49ef5e6151a9ac2f3135f0232235cac7a53e1e8a70eaf0476000000000000000000000000000000000ff494d17c735b934c2c7fb8f413103188fdb116fa8f4d4e43262968ab0fa1bdec23b0d4d8b1c2defe624092de36610d0000000000000000000000000000000008f70b7e9f2d7083774fbce3bff58a1c73fbcbcd9cb049cba71c0c3f0c363517c8956240bcacdfb7934d4c67b1bfdd2b", + "Name": "matter_g2_mul_60", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a", + "Expected": "00000000000000000000000000000000098f32b35e3b7dc1862ca1ca3c76d009f016c6b91c227f2cebe8f1fe87567d936bf1c54103bec31b3552c077c0242fb40000000000000000000000000000000005380a66d48d348487624a15b63d2ecf6976b5b599901101ea8b1f57736649b4397f6679ecab0ae29573695a921ac475000000000000000000000000000000001710c368f70a2b9cc92ec65c4c2ca35fd63440eb350f488e7c6646f9c42bf680eb62a887d533a91e47988221b46c868200000000000000000000000000000000033c3327da938dbe4630dbe16838229d7d427f3adf18dee6fa26b1c8067838922c1bce78cce08d590ee1acf2baebc7df", + "Name": "matter_g2_mul_61", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997", + "Expected": "000000000000000000000000000000000404587c60a4bbd8b5b929ca2ec2a9ff2ba4733f4f2877478a669b238d65ca130cba398899f2910d6de04615f8ffc99f000000000000000000000000000000000940659b3e6de7c3d8de9169a28e68dad433bda78de0991fe4a1d404e5f4babcba9d57c7f3d638aef264642f87c61fc8000000000000000000000000000000001676ce240e1ff70ab03f94f3ba3acd31725ec306ce1fd707e29ec22cf91746216dd998d03ba13a79dedf878fae38d68e00000000000000000000000000000000098a81422511f77191ee15d402614c86f9447ab78a89cc348414108f36857a1929f2b92ced78752ab3604f276861803e", + "Name": "matter_g2_mul_62", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a", + "Expected": "0000000000000000000000000000000010a4ba6952d22a51dbb6762a3f9bd09712c2be5a98bf0ef298d7a7e3a9735ab0d3bf39e40b334895c73a36c218ad24b50000000000000000000000000000000002860f38ef61b497bdaf4faeee7b406007981c17246cfa36cee906452ae85e1c1c6385898ebadc3b4ef8887fff25b8240000000000000000000000000000000002dbbca9034fb17c3f37727d44c027cdf47c36f3f628ea9385fc9fc371d23f22d983656caafbf1cd1f8bdeff4ad7669d000000000000000000000000000000000b7e71b65765c4113a7884771952268a9fe10576f745038912e6877c78372cd261220793b888c43accba1646e902fe14", + "Name": "matter_g2_mul_63", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d", + "Expected": "000000000000000000000000000000000e9c1a6d591be4da37fd6dc283b8d899b625ccc96371dd3d7731aca66cd2a978810497171f2aeded64fa2b10e480de2100000000000000000000000000000000006d2ad7287847255002480627782d513eaf1f68a3d583d4762fc156b8eb40deae6969fa8a7d8f8aae923800091386a00000000000000000000000000000000003c7eae0eda08df9b9eee2605a44fbb486e3bf2e409aaa1c8f38c06f969ff1f74338004b01288dce99be26a837e45d3a00000000000000000000000000000000178174d2f569a9392eddd2715ceba8762c5bcc6325217db5e5f970d6fde069d0e48a824e5b6ca017891de175c92f6b29", + "Name": "matter_g2_mul_64", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616", + "Expected": "000000000000000000000000000000000ce12c9010b4c4afbddb459c1b46063a8488277948188b4ec0b739e1cebb5653681d0e43a0d2c6b3f842bfc609bbdee3000000000000000000000000000000001123f60cedddaf4385e63758d64d4facdc443854176ec199ca0df0a9c258517f2512594f2441a4b9a68aa9a2b4a1f4bb0000000000000000000000000000000007cc6f77d181d13bd9736ee23a33b25b0bd969760642ee19004e095ebb8e2b3c0e09321eb15a2f7961803c0fb10b6ffd00000000000000000000000000000000004d8dbf2f0c14b07ebed2b9cb4bc87df78ac8a34ef0b05cbc2c6fb8e8156415399fa52dfb968ef0e6ec697030fb003c", + "Name": "matter_g2_mul_65", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af", + "Expected": "00000000000000000000000000000000172805bc715a8cfb2e25c384214f4005aa6d3b809a0ad95322209851ef92151526a9d01a914c4d7f0c120b9bf3837010000000000000000000000000000000000473ceaa092a5ac12f38b4065477672deacc08e553d8e9e6391bac0d9ca50015934cdbc340deb05aca916cf50c7915b30000000000000000000000000000000012e85461fbd26c2d0235acf5c8665750656819bb939e8fae77a8d526ca23443aee395a985cdd4b1eb700311fb87e91a7000000000000000000000000000000000246d45fdd88448c93bedf4799becfc7c80e67abd483f2a0aa41e8bbb3f38cbc900314436364f1db6e1d88595544517a", + "Name": "matter_g2_mul_66", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145", + "Expected": "00000000000000000000000000000000122e1f2081cbde0055fc34d2fe61307bc333b35a1e0772a0cd6fb25338c89824bcf2f066bc7b571b2fb314ca7f45106c00000000000000000000000000000000027ed81b54372d858a6ba2faa65fdc132efbca6ddcd56c3625bd9267cf0ae04f6d342209b995060f584be8d40020669500000000000000000000000000000000002a03427a093a3000a1bed9eba91a82dc2f2fcea1a16a1fb8af29c4988b589abe6a505ec87a82864b3c683beaa6420f00000000000000000000000000000000134bf64871d69a72e42766c2903fb4589b84d7772a62f7d2f8f8d02a914f4d3a278c680c626ef4d69de8aa88b57589a7", + "Name": "matter_g2_mul_67", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a", + "Expected": "0000000000000000000000000000000018fa44efeabbd1cc47dd9b1a1195ca921c99c77ed43a44502aad27b6c663f5ce2623382c3ddf208f42e3eea741281f4300000000000000000000000000000000138d11e497e3c5656bc8fc0ae4322a0bfb6fc20e249a47a103b164aa3d9fdbf7df4b1e3b0842b4b12568a31992a151f000000000000000000000000000000000182490d6ae35c1208c0d608984df4988d057f3ce5a25073c77cd5b224a5892768badb1ad5cef8f41d1d2022573098c320000000000000000000000000000000002a6e0523781ccdebb75063dc7ad1a9526f9ff8ea1364bae487914f254c0eebcbb2cfc3715fecb9599bfc2f5feaa62d2", + "Name": "matter_g2_mul_68", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0", + "Expected": "000000000000000000000000000000000dc7488491433d5b3924105c01ffed4f30b755d7253d867fda595e7d80197823e56e4d182d5ecc72d8ef1ba9bca15a310000000000000000000000000000000007bfeeadd6fc468ef6340a2b394c155bf50808cb11e89adb0de5499fbdde91760e9531c1deb23050286a15e5910f1d5a000000000000000000000000000000000f096db706b08485fd577f37b7bd232b5a10c3f80c25bcf82f7a3b666c6efaac8e856bfe5f7dafb7457e33eadcb4133d0000000000000000000000000000000004460d1f25159ce6df59efbd7c693355af4634dadeaee2ced68124b2a887698c10e9c4b40c4f4f9c8444acb881ceff65", + "Name": "matter_g2_mul_69", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258", + "Expected": "000000000000000000000000000000000f1aa4a7a22c568c41270d24824138bf9ffc763a5356b7c0bc1d051a0a0db12616700d9214972b63eeb2a398d27dc83f00000000000000000000000000000000020d0c2ff8f93db6b415c2a01712034e46bdeb6e665a5177a3877db9f5401d3dccb99907ef843062e394c1428983725a00000000000000000000000000000000088abeb6fc3ead45d5b261b7d684f168ca8f5f163cf338863e6b102dc40e2cd0ede97c47460ad6f560c27e95c8b71ca8000000000000000000000000000000000ca2e5cec212d581c737928512118e2f51a0d74070f40a998b7b06d22b9fc754bb2fa5499308058be9ab81521d057414", + "Name": "matter_g2_mul_70", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e", + "Expected": "000000000000000000000000000000000cfa23c46881893f6c50d238a83669deb520a7fffab4f912f77df7cca43f6827a1a0ae0b3f36c8f116ecefa33b8bf37a0000000000000000000000000000000014b7e5c18d2f9bfe15b0c1af3bc6e230039a341e135837d123e91cde9cbcda298c66b93f692232c912e5d7d3d6331c430000000000000000000000000000000009c8984999ecd3a4144ccb925d3e5cae5c1662dfbf8871013b1cb2946482fcb075c489c61b8d6261f2574b44da3fc1ce00000000000000000000000000000000196e7feab383211e4825cf98219c63bf9f45a72d66030219cb585d5d25237a01a97f00e122db6a51325022e69e7d8cdb", + "Name": "matter_g2_mul_71", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83", + "Expected": "00000000000000000000000000000000005c0282830934ea09c9f51b52cb6dee75b874b155c63076dbac2cbbf220863d55557ff1b7d681fa185435df1522f49d000000000000000000000000000000000a1680ebbb185c8e7d8a197a523a7a5e618f97c46670622034d312b3eeef140150e03b00ae3dff8d9f1d872f3d3dca380000000000000000000000000000000019bd2eb4bc25f5aa6bce206f0683dbbbbb002098a118fcfb060c1353a310c2baa1063a782bafcf6ff6bb8edaf6f1597a00000000000000000000000000000000082edf49a0435e0b9f3dc7f207711d66004ae688b18f5b62fd1596899ee8edfaac7da38973d81f12200018fbe8151572", + "Name": "matter_g2_mul_72", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07", + "Expected": "000000000000000000000000000000000b8a715c1c2792a30f7ad752a808b621c34af1fb7f1e3392a36ca9481a019108a21e3ef338a1d05f2f23ac3e2cc42ed500000000000000000000000000000000101375c9de592031c55a7a62189fd3fa3c565abf7c64724796dca3b1c7a6e6834a16ef1c4e2afd6ce2e69487260f0028000000000000000000000000000000000cd385ec8245431d3b1aff88453db7f66a5d7888a5c1e0dd0abe9ac7db752933a343b8be53b7bfffb704768ef0a3dc5c0000000000000000000000000000000015d55c8cddb8715e25fa260d1e1fa672ff76eca7c80d19d00678fb9d08759b810cf266ef0a7e9dd749a576ce07240fa7", + "Name": "matter_g2_mul_73", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6", + "Expected": "000000000000000000000000000000001311de31229f1825d0bd2c9d726fd71e05828a20406a4705ea65f441537486338022bac4e552bf3c25e15717bee00ba400000000000000000000000000000000052e082cbe36c854a028a041981fed87d39fb218a88208aa1096e260a3932a1155db7f306c32d133070b0a5bb6d161760000000000000000000000000000000003269d4afd20002873f4305018a4432c1925eea28486d657cb458198ff2df9d304bdfc7455233243b1712d8663591d460000000000000000000000000000000013376fb98929cbe7f7d090d1c9d5c4f6332bbf25470aa03c35a70481931e4bc91c937029a5e11d2a3418eab698361227", + "Name": "matter_g2_mul_74", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659", + "Expected": "00000000000000000000000000000000021166263d1a443d5b2eee9aeca3678ae4c2b44d556a7cb9631d47e4fa3bb05ecb94d6582f4ca0cd787027fb5f2efab60000000000000000000000000000000015335d034d1a0ce78e1246a16e35e0075f73d4a392da1e05c11388084cf80bf31d499e57c48f4be6e72d3abc7b387ec6000000000000000000000000000000000deac4ae1900a4e1814624fb4b8c7a3149fa9cff2ca97f02e7d6765e034a1532a7b8475ef7aef5ebb851063cf4b9e79500000000000000000000000000000000161e3af03f226278a07ff3b08e5788f6c5029b2c8293e7a7e3ae11c4d78676b60dc0208cec6b82e1714d976007fbb389", + "Name": "matter_g2_mul_75", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d", + "Expected": "00000000000000000000000000000000120b4434babedbd8ff295a6e2ed5fc7af0548d7e60663110050be797584c0cb638988201ae7707cbedf0c8b3dc5ced85000000000000000000000000000000000d2de0a260bdd241a145e3f68a6de48da4c65107a500e02bfeae6ae7dc428026c7c3e9bdda9a3069d2744705df2eda9b0000000000000000000000000000000018a237906c0e277541c4f00c4c2feba7cb2c9b87709c18b62b7c36d78fc118cfd65c127765e01dc0ae5875b9552bb45300000000000000000000000000000000197485daf54e98e097b6bca24b0738682969256decbf3ebc05f6982e4608829f37e2877937b3f26b88efc3deeb4bfacb", + "Name": "matter_g2_mul_76", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712", + "Expected": "0000000000000000000000000000000005de82540aa67c69b962d292133b09e6593961da8944ce02557141abd19ac471f766b4083db85c67a44b65dad2202488000000000000000000000000000000000cd999bf3cb004074fe9f355cd8dfaa7b9d3439d902fddd2fd0688419b5b7f8c4300ab26b658936a90c0b8e1488249d1000000000000000000000000000000000f97ae779429a5afaf7a3343586eea84a4e76f00a1852ce42a4940babd565bc8d61bf72fca9b123922f1ccfb1db8c06b000000000000000000000000000000000935960fa941c27e74234a07857ee680f53c31047235c6152d1669724bdef37ba642cf4e0dd355443ea470e6430def8d", + "Name": "matter_g2_mul_77", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72", + "Expected": "0000000000000000000000000000000001b0aba02b0e907c03d2f4003083c824ce60f2f55f70dc6ec7c7f81f3d0ef4bf533b4c94833e36e8aa7aeec18b7255de0000000000000000000000000000000004fc227a6ae303f3006f75193cef7c653e6bddd28fdb843b41c7d39966a701ba8fcf611efa71abf059d7d98833480e69000000000000000000000000000000001077fddd0bf3d5c80eec653916f9095e900cf165315d74a872219285f62b5412536e43c4cdbc120ec5c7753318852dfe000000000000000000000000000000000ccd90e01c1d4a00f0d9e29a88e8134f2cf68162da66bd343645a998730190114a6921c9b048dda58b60b42a133287f2", + "Name": "matter_g2_mul_78", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6", + "Expected": "00000000000000000000000000000000185520023714580a3f235e24316478b8260565ffabd39670811519066844e131e337bd62ed2069bc6d2305e6638e539700000000000000000000000000000000055fc74cc7cd3fc393d5b5ab2419414effb783ff4da2516e5465a4acc195339c7b5238be4e0744b3d7fdbce46ca7f5dd0000000000000000000000000000000005f584a0311c02d611c15163529130a2fb3dc853083e7225b791ce5ff32d5ef7039c80edfff317ce9ddeef84443b5a51000000000000000000000000000000000f9d5acb355f767cc6286cc09f6df232532f9a0e9e4ed1fe28788abecb200e22066c23f3ac6c49c47071cbb023e70183", + "Name": "matter_g2_mul_79", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b", + "Expected": "000000000000000000000000000000000ceb56d75f3aa1548c50d7780ea1e33c3d069b2f37e7f96be6a8ec03266fa8d0868822afb3b2e54750722266f6032a8000000000000000000000000000000000080f15b7f9f2c22f1afacf558267b5b84f3a6d199fd3349eefa2e46c4f332849c0955d19d4513151dc0f3b566c0058440000000000000000000000000000000013305f8ff6080f7da05c28155c0c2bc1c78d855cdcff0bb2c6b82cd5107d7a070d0830e6705f6832ed5baf75a659c8870000000000000000000000000000000018f4e136859b4ceb230450f9abde0325a4d59db98279d7fbab710305ff53250dae1c8789cccc27586c9b9df5c0c4722e", + "Name": "matter_g2_mul_80", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035", + "Expected": "0000000000000000000000000000000002a0214be95f020c70221fb4fb6856af7ce3845a4b607340f85127b52f8a204efcd94a152835860a4ddeef84946671b1000000000000000000000000000000001767777740a9922a91c39a36e2cdfcd544df902b31812ffc88418dab7321f73406ab142055b5bb264c187f2d4f2d6f9d00000000000000000000000000000000026e6941364c74997506df0f9fbe6b2769839e8b7c7293f4e63d13bd7bee90ff779cf82adc2f23c569d1e13826cdb0e4000000000000000000000000000000001618ab2ffd4b823b9c9776baf849641240109b7a4c4e9269f3df69a06f85a777cb4463b456023b7001adac93243c26f5", + "Name": "matter_g2_mul_81", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff", + "Expected": "00000000000000000000000000000000054ce66b9b0b3cff6637d6cfdd788719d4e33516b98402d8fba54725309307711fb576299ba99104d4e7df0deac9ea2500000000000000000000000000000000055e06ff52cda9116a98ad3709f788d39db53844b7db58a57af52848ce1c59ec2a1f083efe79c5994b9291a2d1020fb900000000000000000000000000000000040c9ad63698ec78d06b41bdd6f5eed089b67f106348f9300f822a2d61ea1e5d2ddda0efd1025825c99cb0e243573f7700000000000000000000000000000000195dd00c48186f8d1337ca857aea02c4d199d638133e9cbd2dfc5f633502f656343746ec2a416465c3c0d4e9d53fd097", + "Name": "matter_g2_mul_82", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b", + "Expected": "000000000000000000000000000000001141b59af8fe6cafdf2e247fcb0ee4642a9b4022b6d71163ec9b6ac2f7d10ee3c5c0173ac686b03cd6a7086b039ec786000000000000000000000000000000000f05ba6973c5d865ac5c037583b65eb4eac826b5a04a7ebed1e10bec6ec7dca93b1c2eba70ee0189fd552d5023f2a87c0000000000000000000000000000000002e54475940985ad2115223c5ea3a4c95890f3e9992e3e1a6df2170ab77143bcc5d29b9dcd1ed3bf16e545e9be21a8640000000000000000000000000000000019acc4705955761518cea482b83e3726dea8d1f66a5f19b06cd7ff95828e15d1b139077e0d274b0e6fb86c027844d97f", + "Name": "matter_g2_mul_83", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d", + "Expected": "0000000000000000000000000000000016fb5839fde95111742255b33f040c41dbd0f142d1daa8abc7c63008ba9f63f07381d9d6128240ae9b6cac5befad84e5000000000000000000000000000000000389a11727c356b8f3bdb6a73bc2f6d2d73d33d287365283359521dcac64f17810bd58c0ec5bef4db253bf630bdd9599000000000000000000000000000000000629a8af1bd0c1b1b6d7e447bb779663d7bae8e895e09418bc350e644d7022fa877496f30e2018f5dd1c9683b2715adf000000000000000000000000000000001950185d2574fe0c8277e3f93f59dc5628ec3487911ba9c3194a2f716116ff0bb9a39dde802dcfaa61633ad7657a578f", + "Name": "matter_g2_mul_84", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33", + "Expected": "000000000000000000000000000000000024c03edb9b54034eacca4b321d51397348c57f406b074b16a9d6215e03f842380f5358f5c095fcf5bf3cabcbabdc260000000000000000000000000000000014e62dc442135d729f65090475fb408ebae132cdf2c2932582af887ed54696f3cd15b163f11285b99e8d8f809aa2e65d000000000000000000000000000000000438a2c99df216c67d92b99d9ee8cbd0e9751e538074d146767bde9675ae3a05bdae051efcdc6bbddeb1b7a8288370ed0000000000000000000000000000000007c462a8f5720e442e1917bf75fc3c3dafab6c39c80d0b93d81d1db4080f6e199be092b4b025e7b02efce4f30d00299a", + "Name": "matter_g2_mul_85", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f", + "Expected": "000000000000000000000000000000000e8137c15436264b5960c27d0c22be7fc5d56a12f43b3832ad0d7f5abddbaaccefd140e2f7c476b99e6fd9b3a52743600000000000000000000000000000000019ee3caa56f0329a2e2acb8907b3edb21f4eee73e312352796b51282e097f9b10af61805d5c222332888737c7f8e227d0000000000000000000000000000000012cb9c610391940fed7882a5cba08eba4226c36eca8a2ed22fb5e752e0a1a5ec556673e47013258b499268f1de77bdf100000000000000000000000000000000031b769f606fa25b81a982db86a1cd442ed738019e7e64728ecf485cddcc17d9dc271146196178740b9f05f56627b061", + "Name": "matter_g2_mul_86", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c", + "Expected": "00000000000000000000000000000000080807a0570b628549629d2eeee39de773badaccefb76e01efaecb0ef0356f535d32c3947f0613bc7d847ef8c8778f02000000000000000000000000000000000e8c091ea30465d204ace72015cbef29645206390fd92ba7c4aa0fecae4ecee53c0b06e1fece99511efd8c7e9cff1a8c000000000000000000000000000000000c881c678c94d80164bb3295acf4341fe6c726ca64a1a015c890450e719b85720f41f80369f99ad3e7e3169ede0113e00000000000000000000000000000000008a2fe01a7100afda40091eb0b2b14cd00b7a4d8bb5cf9d9a3847970a94f2035fec7f292c04c38d7e49890e612830aeb", + "Name": "matter_g2_mul_87", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1", + "Expected": "000000000000000000000000000000000d17f6d9460566d0543df2666d6ade685565e668521a87fabc58148343085415fee92c32907311c9d04713c34bf7690d00000000000000000000000000000000185da28f07b86885031ff5cda913a85b0e4d07673f456ecf2a9f0fd1b21d99e22442f9b705039252d57380b6a42912050000000000000000000000000000000014a4bde5973ef43691b61b3c0f6c2fdb4bcd6ea88e53e2787a7d93ad6e05ee2e69f2799712520f72b3c577ee278008ec000000000000000000000000000000000d92a565b3d8d0fded054a75198b31c521e3223650cdf762fbf7b851f7ac0fc66b8c86c20b905117585704c23b27e7db", + "Name": "matter_g2_mul_88", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb", + "Expected": "0000000000000000000000000000000008b1ebd753364a5a0a6296ab48b348f91668525c0d5f7edc4f2d29844592f34a209f9e77f94ebb38ba76bdb3f96063ec000000000000000000000000000000001062e0ff0a67372207052e2520d8b2823764a5075c94011afd6c60288e187ec77e08db01c95dfa195f2409b58c9dc4e5000000000000000000000000000000000cc2b87b613d97a716586f371c457fa869c2b8d1fa1cf4b9e8c34bae23e0544752b997df4711d0712ec11d3a9d96ac2600000000000000000000000000000000140eae891c87c2026f0b1293df2bd8ae2dcb0ab3f8de74676f37c905334ac1f53fe4b75511691dcf108fca51abcd524c", + "Name": "matter_g2_mul_89", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf", + "Expected": "000000000000000000000000000000000276a138edecfc9378be4e241d64cbb48bfa6fd4fb1788f8bda870d5ec8b2132fc9ec888ef84c43a50b7de0527def36800000000000000000000000000000000153e90d52c747859f88223555bc8bc4e8b6fc846fe7028de728a4dfa085c6e350f9f1d12b9dca4ca8e07377648544400000000000000000000000000000000000cef00e7217da6df0a6d85f40be69f154300c423e86e54e513b2491e65002e308445238082da69aa9e5e83b5f4fc17dd0000000000000000000000000000000008da1da2a0d1da9d2158b9408dd9b0eaf414d237b8219fa7661e40c1a88eac2f9735d0dd6ad67b85aab85952369e8287", + "Name": "matter_g2_mul_90", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5", + "Expected": "000000000000000000000000000000001484993096c210c7bebbc4c0bda24b44a70e982b2528215c0e8578ea55f1181472758caf935aa0a3d6820cdad753e2f90000000000000000000000000000000011802324a6e03c3174bbe7261ecf3812c1a97e1be27269214f232274a3bf82775d47c5fdd70fe1c57155068b296d394200000000000000000000000000000000050f43c874c1cfb5fda81059cb7b4808492632fa20369dcfb611e503ded81a49dacff253e31d7e27ee84bab79e3c5d53000000000000000000000000000000000ef945b6f210fb09bf0ad5bbd4b5a6630f43304ddcb396807c967eb5146741f7432bfdcbd7e5f3d29917781efb62e6ff", + "Name": "matter_g2_mul_91", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e", + "Expected": "00000000000000000000000000000000028233bf12e8dbd8510f119be30ea1fc13b755c6ee3ca2a3637a3bf8f73776c9d1fe231b713396ffc579ef9320a05b150000000000000000000000000000000018e7c00b8047d64ca0c5df54486439c5fb3d1414c2f71cf8a3ed591b7c45bf18b37473daeeadcb625eda638885ddb9870000000000000000000000000000000018b89c9b6bf9ece36f1eac08fc35ffc9f7f964a0a9b19d495ae1361fb4bc98aef8770efb47d9961aff694b878d659818000000000000000000000000000000000eb2fda2c29c6761e35ca4c9772bb232ea0d297582af4f50ef76c0b74fefd414b535e356c069f54ef5224225e95be6e7", + "Name": "matter_g2_mul_92", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5", + "Expected": "000000000000000000000000000000001239935827fb2a269ab064a3ae2bff2555f89bb3a71a47ae815ef755fc1363a89d20326855cfdd0e13f6c85f727bbe120000000000000000000000000000000012fbba047478b5f5b07a582200271a0c331d6f76864f9b6c6ef8ae6b0965eda481eddaf72c7a887b21719164c633d39600000000000000000000000000000000017eb4353b413437244983554a639a9253d105395ff9652504df7700d879cd9a32d5f0824b1eaa532bcf2fea34f8f08800000000000000000000000000000000054ea45475c01ea0557fd143b21c7bdcab6d287bf6bf4f88b6fb06e02ac6fc5ba96f323bb1fda3a1c4d8f42d01d267b2", + "Name": "matter_g2_mul_93", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0", + "Expected": "0000000000000000000000000000000015a145e379b7ecf4566a039b753f91e8ad75d9e9c9a20725ce34a900eb9a1bdf66cabee2100208d7792a963d1fb8c02f0000000000000000000000000000000007f0ca14fc4e34bbdf5008d632dd112c7368e037ce019b7c4ec412000ac02302c85ae64f9ab495361fa5b620e46420aa0000000000000000000000000000000017c00a08bba18426dda40e773d79733030b5b3b199a62436ed06b773fd1f10688e8af00e8a223cdf242bd1ebbedbf634000000000000000000000000000000000a17365cd9f7655793682b72e342227048da0cff88f6ace33ddab548ba126017e4b7f7439373a893e3b5803e662814b8", + "Name": "matter_g2_mul_94", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169", + "Expected": "000000000000000000000000000000000081b4dc78b74250a82da9d803876add659411cfb467860b2ac6f0f68929d6377deb71d6acc9ea8fc8c1286b8f92056e0000000000000000000000000000000002c5fde71346a255ee9dc896f654eb2e0c66f4cb4c51541d2bbccf2463ecf0085a22b9d2bdc5bef39d80c4477824f116000000000000000000000000000000000ebda0cd8bf6ac7e86a1bdbe44ed1e15f8ffa1fff92afd67fb564306882f35037b61cf0d93f278f15149c04a2e83041f000000000000000000000000000000000fc38aa811f5ec015f10a99bf175f1479d4983c9d2180a5e3da88b4e9b62ef50560ff0a6c2fb7bda4c46c54551f8390e", + "Name": "matter_g2_mul_95", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4", + "Expected": "0000000000000000000000000000000007b46fcfb2cd8efe32754306ff2f503d7434168c1c3cbd7c80470cc5a5c8bda10a80bfc0129da349724d2d6431c5ac90000000000000000000000000000000000e1078f4f4ca993d90accbfc036219507bd22d00930ffcfe1227780c00914fcff845698b2541510daf59cc83d8b947e7000000000000000000000000000000000b7c6d9951570e685d3a71b19a38f5485f974f85fe8cd4b4c196d33a18750b278b6d374483d81dc3e15c9b8b9b5dfdd6000000000000000000000000000000001003a239ea4a2f213f0f646bdb62cbe4f98cfaf7298d8b2e0eaa07bf3f939e779caab5ffa0033467c5b297166df657d7", + "Name": "matter_g2_mul_96", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125", + "Expected": "0000000000000000000000000000000000ea29b1e059560fec21c3692d4e632a45c88a807c953fa23dbedb271b049d7fc717333b498ed12573a896f872e795dc000000000000000000000000000000000de0d10c47df92010a6635e3403dd6e91a1bf35bfcae82c1008998e86aa2d18a6cfd3f2f1207fde3bb39b723ec4d3ca60000000000000000000000000000000005e2aef9cd37430b15e5e76b2c7870630d255f630c12e865caefe308a39833e00319406746dbb2af3ed32135e91eed49000000000000000000000000000000000c229fad41b0d27ad7b5db33188fa70b97f22e323e429ef65fcf98f5339e908c31df8859b863356e0fc90538c5c49cf2", + "Name": "matter_g2_mul_97", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b", + "Expected": "000000000000000000000000000000000444a00cfd258bd46f659b09eef17be9929008d3d1c65e46cdc762eeaa2f0b52abfd636e6094e21983fad8171194c71a00000000000000000000000000000000090833e68614be5bf298e04e44527480cb35128bbdecae15eb95d6931a718f66869ddb68352130b4dd8a921ab3f26d080000000000000000000000000000000000994015b1b55340c3839d48320d178b2ffaa0bbff038f7aa63d4dff41a217582fae9613bc537fdeac8d0670c0cf479a000000000000000000000000000000000fc486e2a1680c10ca28d4c3bb22dbccc9572036512645bf868e7693ae4591569c973f9ea26342a573e23a06c2fb4b70", + "Name": "matter_g2_mul_98", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf", + "Expected": "000000000000000000000000000000001375bd5ee66c330796bd8381a26cefa3f40f8cc8de42d4d59a7adbcd3852e6d632422e6ad9a06a6e497b23b17b1df87500000000000000000000000000000000165d8e7be17ecae9bf51a773da705aea42536d0fa3a2206267da50451f5104ee241811dd0e6710a80c38df77b126c009000000000000000000000000000000001559572407aff34969f83c394d2b095a7ae9f53a8e6c923910f256bb87b6ec076fa6acb85465102fd24d34031f88f7510000000000000000000000000000000015ff9ba89b55ef75f63732dec1e64106d7a912a6657fcc970dd011a03b5364117cca46d6cbafbc0c5049db10fa83fe6d", + "Name": "matter_g2_mul_99", + "Gas": 45000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG2MultiExp.json b/core/vm/testdata/precompiles/blsG2MultiExp.json new file mode 100644 index 0000000..2138db4 --- /dev/null +++ b/core/vm/testdata/precompiles/blsG2MultiExp.json @@ -0,0 +1,793 @@ +[ + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000011", + "Expected": "000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d536944", + "Name": "bls_g2multiexp_single", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000019d5f05b4f134bb37d89a03e87c8b729e6bdc062f3ae0ddc5265b270e40a6a5691f51ff60b764ea760651caf395101840000000000000000000000000000000015532df6a12b7c160a0831ef8321b18feb6ce7997c0718b205873608085be3afeec5b5d5251a0f85f7f5b7271271e0660000000000000000000000000000000004623ac0df1e019d337dc9488c17ef9e214dc33c63f96a90fea288e836dbd85079cb3cec42ae693e9c16af3c3204d86e0000000000000000000000000000000011ba77f71923c1b6a711a48fa4085c4885290079448a4b597030cc84aa14647136513cec6d11c4453ca74e906bbca1e1000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000176a7158b310c9ff1bfc21b81903de99c90440792ebe6d9637652ee34acf53b43c2f31738bbc96d71dcadbbf0e3190af000000000000000000000000000000000a592641967934a97e012f7d6412c4f6ff0f177a1b466b9b49c9deb7498decc80d0c809448aa9fa6fbbb6f537515703000000000000000000000000000000000031d84356ef619e688a10247f122e1aa0d3def3e35f94043f64c634198421487ca96af5f0160384bba92bd5494506c4d000000000000000000000000000000000db8fefe735779489c957785fa8e45d24e086ef0c2aba2e3adba888f0aeee51385a82898524c443f017ee40be635048c0000000000000000000000000000000000000000000000000000000000000034", + "Expected": "00000000000000000000000000000000158d8ef3d5cdc8a1b5ce170f6eeadec450ca05952ea7457a638b8ff8b687c047799eb3dd89c2e3c6ca6c29290b64f5ab000000000000000000000000000000000807d135b6b007a101e97f5875e233b41f12bd2ffd77fe1195418a73a4c061248118ea1049aeea44750cd5ec83bcc1ae000000000000000000000000000000000f04136354f45a85a53fb68527bc8fbc7e8c1a0056878012b548a97bfdabcbd3fb8eb3ff187fbe65e1ce233afd2825050000000000000000000000000000000007b15428114e2ea094ba1e64df4c244f80aa2f75bbbf21a407bc84e80bf2a5ad787d02ae8a90cc1c137f0d898edb1684", + "Name": "bls_g2multiexp_multiple", + "Gas": 103140, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3000000000000000000000000000000000000000000000000000000000000205900000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e84900000000000000000000000000000000000000000000000000000000000b7fa3000000000000000000000000000000000e7a30979a8853a077454eb63b8dcee75f106221b262886bb8e01b0abb043368da82f60899cc1412e33e4120195fc55700000000000000000000000000000000070227d3f13684fdb7ce31b8065ba3acb35f7bde6fe2ddfefa359f8b35d08a9ab9537b43e24f4ffb720b5a0bda2a82f2000000000000000000000000000000000701377cb7da22789d032737eabcea2b2eee6bb4634c4365864511a43c2caad50422993ccd3e99636eb8a5f189454b18000000000000000000000000000000000782c14e2c4ee61cbe7be6e462a66b2e3509f42d53ff333efc9bfe9a00307cd2f68b007606446d98a75fb808a405d8b90000000000000000000000000000000000000000000000000000000004165ef1000000000000000000000000000000000411a5de6730ffece671a9f21d65028cc0f1102378de124562cb1ff49db6f004fcd14d683024b0548eff3d1468df26880000000000000000000000000000000000fb837804dba8213329db46608b6c121d973363c1234a86dd183baff112709cf97096c5e9a1a770ee9d7dc641a894d60000000000000000000000000000000019b5e8f5d4a72f2b75811ac084a7f814317360bac52f6aab15eed416b4ef9938e0bdc4865cc2c4d0fd947e7c6925fd1400000000000000000000000000000000093567b4228be17ee62d11a254edd041ee4b953bffb8b8c7f925bd6662b4298bac2822b446f5b5de3b893e1be5aa49860000000000000000000000000000000000000000000000000000000173f3bfab0000000000000000000000000000000019e384121b7d70927c49e6d044fd8517c36bc6ed2813a8956dd64f049869e8a77f7e46930240e6984abe26fa6a89658f0000000000000000000000000000000003f4b4e761936d90fd5f55f99087138a07a69755ad4a46e4dd1c2cfe6d11371e1cc033111a0595e3bba98d0f538db4510000000000000000000000000000000017a31a4fccfb5f768a2157517c77a4f8aaf0dee8f260d96e02e1175a8754d09600923beae02a019afc327b65a2fdbbfc00000000000000000000000000000000088bb5832f4a4a452edda646ebaa2853a54205d56329960b44b2450070734724a74daaa401879bad142132316e9b34010000000000000000000000000000000000000000000000000000008437a521c900000000000000000000000000000000049cd1dbb2d2c3581e54c088135fef36505a6823d61b859437bfc79b617030dc8b40e32bad1fa85b9c0f368af6d38d3c000000000000000000000000000000000d0273f6bf31ed37c3b8d68083ec3d8e20b5f2cc170fa24b9b5be35b34ed013f9a921f1cad1644d4bdb14674247234c80000000000000000000000000000000008b7ae4dbf802c17a6648842922c9467e460a71c88d393ee7af356da123a2f3619e80c3bdcc8e2b1da52f8cd9913ccdd0000000000000000000000000000000005ecf93654b7a1885695aaeeb7caf41b0239dc45e1022be55d37111af2aecef87799638bec572de86a7437898efa702000000000000000000000000000000000000000000000000000002effc7b302730000000000000000000000000000000002142a58bae275564a6d63cb6bd6266ca66bef07a6ab8ca37b9d0ba2d4effbccfd89c169649f7d0e8a3eb006846579ad0000000000000000000000000000000012be651a5fa620340d418834526d37a8c932652345400b4cd9d43c8f41c080f41a6d9558118ebeab9d4268bb73e850e10000000000000000000000000000000015f4b235c209d89ce833f8f296e4cfb748e8abce6990ce1a5a914b9416c08e0d3a26db89625915c821a5f152b7fa592e0000000000000000000000000000000006fcacb3ee6650a1044852d61c9c20bedc8ee90aad97de8e24670a9ef57483e678db11dd95428915088d76e30cb01a370000000000000000000000000000000000000000000000000010b4ebfca1dee100000000000000000000000000000000018405e4b67f957b6465ead9f5afc47832d45643dc3aa03af7314c6cf980fa23dd3bb8db3358693ad06011f6a6b1a5ff000000000000000000000000000000000c48e0d4f9404ae0a7f10774c55a9e838bb09d3bae85b5eaa6b16b0f4dc2354368117f3799c37f3f7126d8b54d3f83930000000000000000000000000000000007e61f4ec5bc9e2cc8ca471ce4ed40e729b1790cd2c0d9c1cb50e615ec7f346636e77e1cf632c881c07c5385898607620000000000000000000000000000000011dfaf9281901dd356fc5dfece21898a93d9ad9e4e246dd6e18d3ee46d58ab7e77401a3e8d04057e5638ed74fb95688100000000000000000000000000000000000000000000000005f04fe2cd8a39fb000000000000000000000000000000001796abe0d9e4a703962be528e6a5cb65c60725886f925db0e2a89107ec248bb39fa332bc63bd91d28ae66e0dfce8f754000000000000000000000000000000000fb665f5a7559cb0fa1300048a0e6f1ab5547226e86f8e752dd13c28eda4168492e3d3bf2f8a6b230dd57f79b1afa9910000000000000000000000000000000003422dbbe4a06a4c6c9fdf35e54f74b4ab1528abb7249e99898e6fd7affebc7aef95bf82d328dc01d63c25f6a735c35d0000000000000000000000000000000010aa5504b469427eb3584a286191149f5c3c5a745f338278dd95337cd2336d3c4e7532d98eb189fa543824953e7c1c170000000000000000000000000000000000000000000000021c6c659f10229c390000000000000000000000000000000009303f04d568e289a35102b6df883d5ed620355c0eb5d02236718cdaf99fba6e19ef5cee2996268eb9a53ae1ee09bce3000000000000000000000000000000000190be857d602284393305bfe0a29e29a6982ed3f04ccaabafb7e59cdc7eda85c22bc3e8690355c7a0fb7590ae40f1b00000000000000000000000000000000016efd497a0c5c6b59a1fdf2b590eb67a7da8cbe72f49084e7050783ff12a783cad1859e1a0b0ec8ff784c703617670330000000000000000000000000000000017a957ea4d53f4fc8412cb015ae91b38445cdb3e7078d875c465c941e0d9a852c78d90b31b6b6010efe8bd5117e831630000000000000000000000000000000000000000000000c01a881f8abc4d8843000000000000000000000000000000000173ed58056bec9874464d3f23c3e7d3d429d6c8a167fc7f39368830eca839d0eb8260d64ca823f6c785c71f85893d8400000000000000000000000000000000123372d7d4c91a249df8f3e4f8e669087b252ab5d8cf2529a87e4ed3622e4158cf17dc44b473d5debd273261383e8a0f0000000000000000000000000000000000c500eb55ab86381a1725f339f686c7e38ce9113493736f57e999badc661b5b8494d220ded0711e841228a389abdb820000000000000000000000000000000010a4025d823c4262367c53f50e67cffa046e4a1e7c69ff30373772e49ecb310de3b313d83cc41f40a00205722f233e270000000000000000000000000000000000000000000044496e633650ef8f6fd100000000000000000000000000000000152110e866f1a6e8c5348f6e005dbd93de671b7d0fbfa04d6614bcdd27a3cb2a70f0deacb3608ba95226268481a0be7c000000000000000000000000000000000bf78a97086750eb166986ed8e428ca1d23ae3bbf8b2ee67451d7dd84445311e8bc8ab558b0bc008199f577195fc39b7000000000000000000000000000000000845be51ad0d708657bfb0da8eec64cd7779c50d90b59a3ac6a2045cad0561d654af9a84dd105cea5409d2adf286b561000000000000000000000000000000000a298f69fd652551e12219252baacab101768fc6651309450e49c7d3bb52b7547f218d12de64961aa7f059025b8e0cb500000000000000000000000000000000000000000018461a3d444ec527fcbf4b000000000000000000000000000000000027513925b419f6c581788578379995290ab9478e08ecd1999d5e1a05c58144d2f9f06fb8c7fd1586f3ef6a973a3ed7000000000000000000000000000000001292b2ce751f6f859ec7882e14083eac9841b035f9d5ed938a81579dbce07dec2c0202b7f6b25226831cd9c578e893d00000000000000000000000000000000017f36da49414d7706209d52840250eea6f33970fd7eac448ee122f24c62f6a6e09757aa29761160be0f65ba3ce7a153a00000000000000000000000000000000086d471f958f3ff679805751b183fb6310e871ba72bbdefd59c58e95ea62de0820d5affe601757e318abaa5a0c2715bd000000000000000000000000000000000000000008a0eb53c748001536d7ffa900000000000000000000000000000000090721a089bbbb130c21a529be0ede9271a91a2dde9cb2a8e091a19fd2c0a40c390ac2bda8304085c2d6e38e520eae44000000000000000000000000000000000cc64109c67b342b6dbcf86cb60fca7ad378ed6398d89076ed108685c57a07d26e40ed3d5c4b3560b21e519db5875d49000000000000000000000000000000000b0ddd488f5a6f61f087cdbf011b50209a4460c8aa8c5f48c0b30d9cf6cf24259f4e7badc42e1b7a33352949ae566fc100000000000000000000000000000000038430e8db04d205d81aa1632d23919c06f89260c7ac5850bd8b780f8388e53db3a3ddfe98cc55d1c686e582f85b0c8900000000000000000000000000000000000000031133a6c7d698078a7ec7e113000000000000000000000000000000001800ecc167bb714100f31e7610cd3fd010ca299b394c01b1a89afd11b051e92989f6336db5e6d3212f6b04673526d83900000000000000000000000000000000070401d9bba01c0445e0a682406b099f21d16d9c348cc97156769084055ca328a145c134b8c8b58f019d62882b2965de000000000000000000000000000000000287f071bda99b0318e386b27a492a6823a9664084b12acddeda34cb53f827a362ba97c0e652c37bd9d6023041d8c8d8000000000000000000000000000000000fa708ca7dd917541cd02281e525d3367b5ebf5e9353103e1f83f3b894d03d8be7e4d819c123492788855d1fdb63f2e000000000000000000000000000000000000001171d5c4909480aae3b110d01c1000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d53694400000000000000000000000000000000000063376fcdf64c9bcbeeff0f9f9f9b0000000000000000000000000000000004b6570b4a6affe97649b0dd7a0ad0df160b37c332a8a7348dd3994cc6b1eb65623b4a9f0a3f320e7278844e261546530000000000000000000000000000000005f8fb4cf5e5313f403f15c59c79b9cebaec78291f2053c49d6427f40f2db2aa659d3a8fed7c7b07b7a5680c7b95ab5800000000000000000000000000000000045cba5ec3fa9acd1b11e1f28a01ebc028f89f96f814513453c553f58785baca8abd4150f334b405fabb925b71f4f4dd0000000000000000000000000000000013daf00b8f53af776c2e8c08d55d164aa15027611188e294230477dc1c926102088f0451222fd2eff9802db8b884ab9c00000000000000000000000000000000002344b4be368d3b617df4aa8dbdbc190000000000000000000000000000000002b29192945df0a74eed138e431962f1d39978202d247335ffbf29d8a02e982c69e96b58d7d92528baf5c422ed633f1f000000000000000000000000000000000d52c7a82fece99279de7a49439c0ff8463a637cc6003320275d69549442c95184fd75ee5e7122e5575af7432e5159290000000000000000000000000000000006ddbaad6cc16c9e62b0da9ab0196dffe92253fcfb2df9aa2076d3f16b3284997d6558cc4432d2aa1705452c4e951e6e00000000000000000000000000000000175f906a99c9d65c4647807879e5eb781532db184d28a326ef9691f8738af067b6a80147bd69327d219fad7c850a7545000000000000000000000000000000000c896c3f9d64341ba7c5f8a06271dce3000000000000000000000000000000000c86c92c9598dde7e6fc5e05d70a34c7a14cff5f400f33cf6cc26e6bf6d9a0bbc421c00f3360721f51974d76be43bd38000000000000000000000000000000001137d93502ef32471f47890a181d7823b3a86dbfcadcc930ae53952f528d617e742a52e4f243c615cc28163dc31bd80600000000000000000000000000000000088f7f8bcbc6dfcc8005b8308cd4780d574d8530e95e7831e52eb2c9a88b846852e111a8389e3d3a67accf78b08326d200000000000000000000000000000000149e43fc675dd3bde8b89cfeb29456f130bbf674cea0266bd1b2e7de23f9a7294096327b452728411ca58acc949777fa0000000000000000000000000000000474d97a9cf29e85d4a35f6102fe7984b100000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f00000000000000000000000000000195894e95ca3e59929612e77c1075322aeb00000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000009027ceef3ee429d71b58b84919d9a8d5418900000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab1600000000000000000000000000333e268f0b5b1adf76b88981fc305f03ce4bb30000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000123717b4d909628d6f3398e134a531c65a54e8a10000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000679956d49265608468757580db6b8b1821c2eb13b", + "Expected": "000000000000000000000000000000000728c5e6e69b9103d82358cb6ba3a45a677df1c3eb3cdccf694fd71cee94f1e591b8021b0eef638cd9a1d878937b5b2d000000000000000000000000000000000ba9bcf9ccef956f2af8dc4c3fbf1cc8f3f284b04ae8710af6ef4fb36301254c777d4461858fb38fdeeb72c0d8589af5000000000000000000000000000000000224b80a57d30bce4c752664f3b5b5e3443aefa6d4e95dc334821f754b8b8d8fda4e73d03cbd4070d43b18324a686b500000000000000000000000000000000016909a02214c6c0f6682895aa99cf6cf0a22eab6f0b574437ef9c36e9df32ac3b8c5adb9f6b8827df0ccf51b16f824df", + "Name": "bls_g2multiexp_larger", + "Gas": 335250, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746", + "Expected": "00000000000000000000000000000000083ad744b34f6393bc983222b004657494232c5d9fbc978d76e2377a28a34c4528da5d91cbc0977dc953397a6d21eca20000000000000000000000000000000015aec6526e151cf5b8403353517dfb9a162087a698b71f32b266d3c5c936a83975d5567c25b3a5994042ec1379c8e526000000000000000000000000000000000e3647185d1a20efad19f975729908840dc33909a583600f7915025f906aef9c022fd34e618170b11178aaa824ae36b300000000000000000000000000000000159576d1d53f6cd12c39d651697e11798321f17cd287118d7ebeabf68281bc03109ee103ee8ef2ef93c71dd1dcbaf1e0", + "Name": "matter_g2_multiexp_0", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c520000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e900000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a60000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c", + "Expected": "000000000000000000000000000000000153da66acafe91b6f13cd739ed3342197310e4824e7aef2e3414654c2678b8d09b296c3f928f3cc489893420031ab800000000000000000000000000000000010f501a96b86343a7c8d8c1250577cc9be6ffec81b5175ed07bd14988c5bbf7f2f3e7111df7d941d0cd267ea191d6ac70000000000000000000000000000000015e0d88894f7f83aacb6710f6c03ae60db8844dd3beec160fdb1df746b1f38a5e23def0893a0b39bee47c97af6535fcb000000000000000000000000000000000bcc275115e87f2f88c4afe8bf4faed46e6ad0c0357884356a26120591ba283f06b464c4853217865b1d2301965f2bd4", + "Name": "matter_g2_multiexp_1", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c4250000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556", + "Expected": "0000000000000000000000000000000013b49054c3957d1e77ba2dc3ef75775bab9f0e9f76b33ff22e244e897b8ab80ee0749c81eceea259e99b5d2a72251e5f0000000000000000000000000000000012e017e4354ef86f73ec51921cbfdd01e3113cff044a049bdd34e36401712420790cf718bd28afa280ad12104c1851ed00000000000000000000000000000000097f28bee5d903e3c6de14e834d5beea5c847c3106742978e586ba7e913f8b631a69c473aa10e19df9795ebfa3ea6a98000000000000000000000000000000001953493daf65b974b549bb98e735da44b543d6fcfd97176fdc7f6f03617d90e6bb952a607fa8e5791df5dc1c9bba2286", + "Name": "matter_g2_multiexp_2", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f85200000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da000000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd450000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a", + "Expected": "0000000000000000000000000000000000fada9f43b29abe15693d047adc277814cb94694cab3be56b92312ab7666649b8e9d92aad81f8e487be0f74b9ce8c250000000000000000000000000000000007f6891775811a325cd7f548011ad4c705ca0327ea0484d938ce061c913a7ee6978293c3258c4b865d5c2325816c39990000000000000000000000000000000016761f859beb90ea03aa35e954d112da02daa8e76de80297afde9c29cbfe8ef4d42dad535917685a99b2a91b1f952ae50000000000000000000000000000000012a4f24ab88341dfb8a60c19993b8abea96dbd7033d3686c40903728b4fd4da7d07961f2584b51e9e6c05976d555757e", + "Name": "matter_g2_multiexp_3", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e36160000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd00000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de070000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b60000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6", + "Expected": "000000000000000000000000000000000b219032a2461a5fd1e43361c46beeae92e30247acadcdd241692abe81691c295ba38a1f0a2a45ae76b1b95d7d0fdc460000000000000000000000000000000016905f64e581aafe928520adc27c24703e7adeb36dfbb416a159cdb9b9a26c9cef0821ccf52f5ea5253b7c9d78769e9d0000000000000000000000000000000015cfff195b2123aa140f963628c41deaf19dfff44d26a38de4547c3d15edef10fe9f65b1802dc374d7ba8fb62117c8880000000000000000000000000000000018dc725cc8d8919a7414b7866fdc54c4467b0f87cf99fc9b36cd65c0ec526e32649f9c57495657a93487f1f2f5769168", + "Name": "matter_g2_multiexp_4", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b3300000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f10000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be500000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c00000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169", + "Expected": "0000000000000000000000000000000007638fa4e8823dacb40ece440f8f1e57cc5c3851f94357a5325207db92380dd57a7c8709e4d00b670e8af1b77368285a0000000000000000000000000000000005b66a6e6b13ea0eb367a61ffe7c620d9edf5563cb4cc0cdfa68b99d9691cf9a40efd967c1e880238eec313eaf4c92ad0000000000000000000000000000000004f7156c69ea88a71a0af2922d1caca24055d40df058eef02bbf95d864156f62fb0e17d9fccd193840c36ad8449bb4f7000000000000000000000000000000000b8f46fd695c5d96d939d42c65c3b709d32f134710a67909dc4bb43d752521a8d4f0465d0590f30f06ce42bf5f8cac28", + "Name": "matter_g2_multiexp_5", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e12500000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000196f78b64fcc342ba4f4edf34a3080ec950532a5de21a875dd061f09351def5ba3b85745a561e38117a14c20d33a14610000000000000000000000000000000003929c2bc55f323d57dc3529bcf6644e61c941b72b424d69969c1cde7a804d157045bbf6d5b79a3e6686509e11ecdac0000000000000000000000000000000000f6b659818510cde463c52cf00bd99da045c80af4d5cd0e55f9bdd81f34169fe869c519f37a98ff20c56db554469087600000000000000000000000000000000129709e97757724e765f6600c2b1928286efab55ec8d16876a2a3210bf9d31cc5425265d0576a2d5469cbd9a6c8c27c012adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c60000000000000000000000000000000009f5f167c9b61a0ef76415fcceff04f3fa57071c2d79f443ef8a7e6049cb1352f650ebd8f358904bb432d42772c29afd000000000000000000000000000000001524a875d73e03c53b92465bafca582479110611bac6a98fc7d76966e9781308a10cb202289c0776cf5c36515733ccf900000000000000000000000000000000002b1acace94a6fe196b217a9aff413fe0bcb55122ce9e344942843e5afba0d5f2cd0bba14c9c8cb9dd1c3e9024918fc0000000000000000000000000000000018e4f85c7663e596182603862adb559635fdf16ba35fbce7278680ea289f871bcf6755d85654b2a37ae77a37e77ba06ed1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000018ee4b4855f866781f38a618c2fe4214c63034620ea5b72361079b0a5c2b2d6fb9ea73fa202db3a2678cf07219cde81100000000000000000000000000000000180870513afef93870ca64e2363fa1aa43a599db97f3b807ada1c25ae331c80b8ead5cd69b6f5a65a083606591de90ff0000000000000000000000000000000010afd546703baa35a9eabaeb45d301bd5be115557bbb4ff2a0e493668ee790e947eeafcaa923f62ca00b8e635994e39b000000000000000000000000000000001089996b218aacde4ccfca4d2f66d79fe161d962baaf2d6696e1a76ea40af4ae7195e8cf9f6417ffd054f20b65ddfb104c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d30000000000000000000000000000000011757ad74a3fb341c8eb6862978ab3fb5e8cfc8fdbda7d82756532a890d61919cce931872ff339843805e00d8c62ec4200000000000000000000000000000000060783a06e93e82cb08e5dc1aa31202ba11676511300e186ae8e45248b7fdec3b7d5b6849f8b79b8f78ad84f36218544000000000000000000000000000000000ecfd8ab18066fe3408fd20f2a4478156e9a19a09b58da76486c9f6a013d861960b6b99bf49cbecfa8c9d01d5615c1bc000000000000000000000000000000000b45709845d35d7b560745375df79fb95df15e85b96cc1b98cc832c74621339c609018d153bff93f2f5493a52b7326073c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd0000000000000000000000000000000003f9de90222619216852356052e9819d7c6e8ff91e0c6f1d8cec832770ed9001db4569fbf579ab16964d76ae7d1b89e900000000000000000000000000000000010b7cf8f0d283cc22942ed73c599115763dcfc1ddc98d87979fc3dce2f33ca3531cc2909d94f86736dda2a4e94a4f0c000000000000000000000000000000000b0aa4d947644cbc7df8d1927cdec66a68862e5a806e25554f27cc1a3701f429fc7097497ad0419e21cc403b472c8ea900000000000000000000000000000000146270ecb66e1763437b824f2ae122f72f20eb93fb30474691a0a192ceb932b1dee111fa44954075335ab360d31ee68d61301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000000c977cb8de4b6e2e33d916f74eb4e42f089d22b54b59fac9aab0e4cafc8aa2b0f8c55d7251662b3499ea140e322dbbff00000000000000000000000000000000106944a9c2d2ecd08e109de29095f3460128bb751051a1f079acb58b6a60b0bb5f52e63d47b688f4a382a77c3b039eb5000000000000000000000000000000000d2f8be1c78995d54fbccab61f816b6ec52dd19aee6aeedc0e4bde2898b2d07c2925da0440a38c4c965a823fff10389f00000000000000000000000000000000183b5d15b243cc5d9584842ab1a0a1e01ad87268728d72aa8c0d7ec6e7069063a11fdd1525d2b30b35e4568da7c44c5495cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c000000000000000000000000000000000f65ad4c21fddadcc49a8f7bc281d2b7901707f51a67122179fe97da46ea5e1bc6e70d68eb4eb6776307510a67e972620000000000000000000000000000000009003dc68cb0cdec4a502436718f066348f1957ae65ecca8d32c5fd776215cb9a098c0ffe56c92d79dd68d251f49f13e00000000000000000000000000000000038ecf0bb98ff2e84b388c58059ba0de0cff3d5881ecf01d668495ce81b76b00323c665ba88309af5552b7950cc8c08f000000000000000000000000000000001924aa0f460659f552458fb469467a2925fcb2420d4fa6249310456853be3d08bd5c37a3f0a9d6e94e434391d20cccedfb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e00000000000000000000000000000000189c3ee691387fbbcffdb147c880218c3e5c0bf78c44461ac1bd3ecd5d4b85225e46cdb068049607fedfcca14882e289000000000000000000000000000000000260efc08531083db2839d1413c90968e87d79bc1a2c730f0020e40beb92e84b73ef43e80f7c61e1a30c0cee11b3cb370000000000000000000000000000000005c852ca0aae2c575c65ef18b624f50a32c007d299f24a3ec6cacbcef1d6e3bdba9650fd7d639bdc60a3e107ee9c013c000000000000000000000000000000000321c01a9de69d6b89db4ed88dd48261ee28facc5e26511fb2833fa45edfb58051c8c3ce9501e8b4c3cab9c456705889bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b8600000000000000000000000000000000183d50635b22e4d620130e0d4008e3bfffae5dadd7e34f4496899ca54eb4d9e3e95c54ae1d9664609c58d02ee5eff65500000000000000000000000000000000029e3b4496a379464302b1476a4549db371f5d6721704b1d6bd35e2344d7679f8a61a0c3b12f287fd86fd247f9652cea0000000000000000000000000000000012c6a3793fd23e955708f5aeb4d6efb670d25a38a67813ecc72f899cd5f926ab7ef198bf6d591328383aaf54f756c66b000000000000000000000000000000001914d3e4b6ea96bb91333468fe8f3bb74636e9a4f2ed198e9ff01b49ba02791d5bd63224f6a38538aceb777168bef688e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d250000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0bd4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b0480000000000000000000000000000000003760958eac45397eca1a1d951a80265a728dc3c584f7dae111e7ce04248885321b69b334b00cdb0334a362676c2d32f000000000000000000000000000000001031e4a63129ec40da5fe9dacfe148a67662eaa00e1fd5c30336462371c167348a10e50f4dc18469a1a6b76485f77e12000000000000000000000000000000001412dbf993c557323426b486f18a91d16b4baa2c497b30fb332a710ac901c96d46a577d04ea87afb08258aa6d204a1c9000000000000000000000000000000000da015ca09ac0c3245c090f39852218f46fea62198fba35ebc4a7f14887943c3bd1bbbfbfa300611e45f419b33988e404c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d30000000000000000000000000000000015c9121f72e2425cc8aa4c878907628dfe75a903b7f756b9e13728372cba598859d20a92a8297d95e1fbe25fd1cd968300000000000000000000000000000000025a3faebfa53918efa733949f914be08b791794bd4963f0c3fd78df48b14ad214374b08299327575c0731b54eafed76000000000000000000000000000000000771782ecd9980da521618af2f9eb55d91d67b20ba615c7b3cb1a48d483ca405fe99a1cdd17e4dc7aeffce586987d41900000000000000000000000000000000136000da90a76d538f336608ce877be943025b4c8bf15880ea9c1c001c20c954292d362dac9783b7bf66b8d51ddaf0f2a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae", + "Expected": "0000000000000000000000000000000014cb24001bd933b1d5866cc3de9f4b8479fe23e4fc26dd210f9d06e7a05449b9f5ac4e2f48fb847599f625824336bf1e00000000000000000000000000000000033fdb2e899427f1cb9757022c5b614f08c64b53583486148b7431311a6f15aea3b968913fd5f3e9b624705351074be600000000000000000000000000000000035420be9c7ae3203d0dec61ecea70e22e62f50368be870e74f9a7349453647a7f61d2a42cec6522164cca0c7081d4de000000000000000000000000000000000fea43388e9f6e31d419c7f9fbb9839b4cec04163a7b401d8f7de73a4560fbfef4e272f1db9c9d5b37693378f139452a", + "Name": "matter_g2_multiexp_6", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000039dc2b60389f6c893c44072f4db23e7df4c2f299d6b70b70784d9370d9ff8e5413872c227074d429db999d30dc9499a000000000000000000000000000000001702273db356abe7a3f91a9fe4bf56584f13de4069a91daa6c0b552089bef60da98d32c615aa5610842dd8a507f9477c00000000000000000000000000000000095285e8c508ff12da79e16e0391dadbe9a823c586a049e729596864c3cae117305c05f009f9e8ac032abaec8a63f8de00000000000000000000000000000000078fc70e926decf7aa4c2e4b395e88f367757dc47a4cedcd5e632c456a4c160393837196af474948ce6ad53f830ce8aeb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff000000000000000000000000000000000cc3cb5e7b033cff3e5cb01ba29ce8e9f4a93e836ddea7d417f7b07ba8aa71a0efae2e1d7a8ec70bdff12d84d229245200000000000000000000000000000000019ce3c830505324b9bc7cda1fbb328150d71310f06a8424dba861d67a7bc0428beaaf697646d22cae9e00477cc8066f000000000000000000000000000000000f6ff67efefa5636b104a0351c90fd3e89a32b8a9beb0d123d3d6ae42eb5e8bbc19c7a972e27762daf852259c65fce6f0000000000000000000000000000000018d98c43fe5b13b701749f4a5dc25f0e713d241d573639fcc73429226bb131d448283338a909670066045c65789bf9e7064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb3346100000000000000000000000000000000003cf82958d68429503265dcc7d88a3763cca32baefe3c8d32564cf30e8e6b8255d4a9f6a76bce1da473b50287deda74000000000000000000000000000000000bfa9cde6c06b2a2ff8f877ca90b3827d0aa0408c4ccbed23ad461433dad71017d4dd387f49c5febdeafa17d06ba784e000000000000000000000000000000001770fe70513533d91c83449ea52964cd8b449aa81f71e71995db5b19ceddef18e2919439c80e10086e670be669696e4f00000000000000000000000000000000194c20491c9d5ed827cd9d370b9bbec55e4a7b1c34ddd1d80201e7019d9487a747b4fa57b480dbdd09af73aa4f5fa0e9a3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca000000000000000000000000000000000a79d9e0ff43249ff54526c5e1cd55a9bce93adf272508871326c933d526602dc9dae5b6f129a0f1c38139ed1c39be5c000000000000000000000000000000001458b554e0387c1ddb9dee9f4e9fba9c81c15807f496442f4b7210267912b9439a19f95dc80a1e09a0e5cfe750f43c8800000000000000000000000000000000012c06b19ed4e8d5d1b9fed56bc5bdaa3bf0112db997e33aa14899d53e1bddd6aa91dce7e9d25473b66b8578d398981f0000000000000000000000000000000015369b2228e728894f2fd7c2d8c41ac3550da4f297de445cc0f0ef7134c478f526987643cb5408a0bbb79f5f983c085ebd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000016649a8231407074af5ffa93f9db5a2ddce8785be8ee77149602d6afa24ab30b26d2f74bdb5f7464333924a817e242e50000000000000000000000000000000001b990f5ed0b23e113042ff004236646c6eacacd99d1d73fe0c3d9351ce8d622327e827b2c0556802c5657f8f06062a4000000000000000000000000000000000f002a2a5ca90285f9b2fd429721c2daffcae5fe48c571ebacaf475606f96cc8350ce88a850ed75e5aae59d445249bf00000000000000000000000000000000015157fe1a767dabc185a8dc8fea3cb208fd995ecd9acab762638faa987f8367ff7c1a60b657be6e9461acc9df16381e5562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000073884ffbe6deff99cb4b0ae1c0e91e2f4a8c2c7296339b1d7e117d5d47ab055743d643155680740befb379a1dcab666000000000000000000000000000000001995bdc23991dd4cbd973e915a16691fb860490bb54011384c553dd14afc37fe673d13950c1e7eaa29c324fd9304624c0000000000000000000000000000000012197a19a498cd94ecbb3a409337b04e76e1a52715c40203add20eb80f7eac66f3386242d51bea34ea016d778248836f00000000000000000000000000000000101069ff0af2ac4dc7a5bf7bf7b56d82a310d67cebc41a9abf1e1af489e1acef3e726fe9571b4382777573712663e26caf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f000000000000000000000000000000000849b88e7ff52d8136a120f924b20b45ea9ae654a0fa037b62f3c275f0661091038a4c1d6ce7d50512e628b6b397c9f6000000000000000000000000000000000e50e82e9b368f2e316d41febab6b0f626d6588b7217b4e28eedbdf50a4abc9039be9e66c97790d12cdedc90873993e2000000000000000000000000000000000bc5d2bdf06fda1e1d1f5c5eaa7988dfdd790bf4d952f5d3a532bb59edf619dafcbc29274fd3661a35a3f15933b1849300000000000000000000000000000000162e5ce45499e620d0977fa26a291a8e75943c4b5a2a80be395ac9b89767ea5a06606d6b75ee4c8a286d2ea5a197baa5e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e880000000000000000000000000000000005c11afc970544b96fc1a4cbb27259e19b5fd588d1be1c8f19eb4f111882292a463c951521388cb8cb743e5a4a1b57cb00000000000000000000000000000000013dc433dadc122376b75fedc923386a7ba5a363678fcf9edf165a50e160dadcc151b6f402648193d9ef960f5e401030000000000000000000000000000000001893af155aca343bc29989ec2b5a583d020a7558c7663accf6f3e40d0a8eb98ac548e933eb8e2d5fe3550927acc2ed4900000000000000000000000000000000043a79bcbaf07bffe6c6890d95c7e74d127446bdea51a0ba3adb164ea39684bb3ac552020ca28b86e34692c9b36f4384396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000e6946ddc8a9d73e5b140af80cc91b31b9a226a945a9574f0629566f7ee7650730c5ed758cc30442770ed1602b84175c000000000000000000000000000000000da0abb9f5bfcad73b3f24903e9ef887c660447332e5457e4a5764f6628c04d6fe903679b8dc8bb3aaacde410812286a000000000000000000000000000000000656016c01d3405dce9f7d40e47976bc8a84abc370e7e42849dd0bd93ef1da0bc88e428efea43dfea37dd834cf246d69000000000000000000000000000000001939b2c92c8299d7ec1dbeb9f291c5e1c9481e10df10e6ba18ae695a780aec5a185ed4c7e82dc2bb5af87a74552c2ea32087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000000942901572722e5005a9ef5f948c8cd6f557be8d114d2810d3cca29933a94de3c7658e7e28675c2a49f138d9c98c524000000000000000000000000000000001908e8b815e95ec07a90861ce53f545f0cd44aacc47df40c24d6cbc61e7b28fb91cfb1cb3c67b6c5b38c34fcb2ca35710000000000000000000000000000000017bad3616d8e510e325d9166790239c8c817c68ba7fb937fd5fb70a4219265edf6625b52ff26f0a34c0bf481c482b2c600000000000000000000000000000000023ff8a50a9c0e9ee829ec81972386ea012df5e8476d8c342df6b98fa1faa1382ae921c2f1018a918868672450355c44f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000014675ab3efd44bffae321791e6fb35a24b9c07405d9985c685795df2db183ee9dadf18c76cf4095e1e0695dc2c08c4c4000000000000000000000000000000000835f2cf09647061ced2bdf4211bdaea408148100f864f47ff76c0c63a43e44e8ddd9e01709b6ad129bd574d71a1a63c000000000000000000000000000000001017eaeaa6eba76923ff27e5848e5f3b09e7b2b9d55b2cb7068f39defa8628d1c8cedcbb0e1cb5810febc4ccea712b7100000000000000000000000000000000054c873449c738383e9fc2f0f74a6334904171fdb704f5ac35a483ba19a8f661187d36fb35014af9ecf88225466c86e48624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b60000000000000000000000000000000000439ae88636244d5e09607960fb033e4217343899d044b21e61335425b94a5067c941e83e5a77f4b0690e1de037325090000000000000000000000000000000003a67653818cece3ff0390d097f1bfbea9ba954a85710f5c24d1de1893f25f2863991fb9f330e60cad725708e70384b4000000000000000000000000000000000243394c3459a3af236189ec6155418c1916b854a20b980ca1044b48e23b725dab7c60a48e89f642423c805c117e64870000000000000000000000000000000004c8c9fd9f278dfe9f5e24e0f5b42699bb9751b56520827afc2fae8393c690a63f10e92f77c4a10b0c161408da9bf505b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb38320000000000000000000000000000000011ba67024503301ec72bfad101a48708e3521c8a23c6bf2994078690041cf7eb75675cf5f20c8e82d11145e31751a2300000000000000000000000000000000008ace953ed2eaef19595cc7c9fb1806d26cbf1e888075e3985b28f8d93b9c0b4c820c8e8b50fd4e0b23923d428da3efa00000000000000000000000000000000054ee6f7247296e0748d0b52148a97b930e69991a242767d80bd6434d42b0865a64d3ce60953fd2631aef873d8b2acf3000000000000000000000000000000000077748b724301a8bc48efd1cd66086e727e9872e4efdaf55ba90ad1bed7e229a9cfb79013333b50efb46090ac0bdab488ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb0000000000000000000000000000000005008a1d62dad51132ad38a226e8abd7421392414acda61111c728713a2ece284b04d75c2bc58d355bb1d3061415010200000000000000000000000000000000189725b7fc48b8a648237021e9a2334247f1cf18ca50008b813978db01667ba08f00b23b3aa0e015f549ff2d5e5c535f0000000000000000000000000000000010483cf2310f64cf0baf556cb2f2828a1c15922547bec03cdb182a316aa86b5473f03373cf7e59a9a78f73193c1caf520000000000000000000000000000000007f635394301441bdc57dd1f4f97656f4218ebb139c13a17e12839091e2e81327f3353c56880c608de824a07a17b2bdd951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f44000000000000000000000000000000000daf4090a229a1ce946064cda1c4b19c88100c8785c69f2eeec3aed12065787ab0abd797ceed07617d55a9c70ac3020c0000000000000000000000000000000011d77fc28355f61037cae3a8342bdf8d11e963495ba3b5d67055f790b1fd632b23565cad77a3d9968d364e4e2a553c9d000000000000000000000000000000001038d7e8fedea873c864b79d1cf8045485299a2bd4d26c5ab5c8d4a073e2c3fcb38cb230dc6ab7e8e228cabc6ed97da50000000000000000000000000000000009de9209ed14d62625ffbf770e8c528594aeddcaf1aaeedb4f3ca973e7b9f9f1a40370cc74b154f3bc641665d8e4d96b7056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000a6b0dc04591cbbb1b82a059e08b488fd66edca0f2d264c352f81cb6ec45e50f0af16917fa4727ee9888f84b6c888c60000000000000000000000000000000001369ae16bb0743f65cdfc8082dbe0d588cf8aa5406a095c3deefc27eb3ed462dda9dd4921cde6a1d878a805cd144515800000000000000000000000000000000124e08d4de6e831229005663df4e4bd5bb7af56dfb13244c50410e6d0aea420ba19208bf1a774207e0e0170ad3a9b4f60000000000000000000000000000000011b2973743034a2c362281b11a1ac1c89f59ace09f0a53afb0c2ceb061726c7aaefe274f6dc04e5d0dea2b687a00609a8aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a", + "Expected": "00000000000000000000000000000000136ff52e440da609b6b73aa838f2eb9791221291b7b14d902458aa7aa9e37114c573edbe8cef7a98dd07275a8c3fd650000000000000000000000000000000000ba625eb47be09ac8cd1e2ec9015640f416af0e3e0e79d39ccac600ea08bdae7a2bc9144f13168a8cec03ce66b9daadb00000000000000000000000000000000095c51e81b5881b009b28006286c704ce3b002e4ca50ac8ea8e574d1e9665a5b1efdd60568d4a4a656ca6a2d1750a39900000000000000000000000000000000143c0c4b3b720fcd0b044a6f420961e2b7eb5f9f1b0d200de56ca8b02709d819f47f0a6ea7d6b49c4f30520586a45616", + "Name": "matter_g2_multiexp_7", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000da4cf56fdbaa9004bf8ffa12d5cfb3f296ba5262dab079c91bdbadd6e41ee5f89912bffd5df1643146bce1f0e021b3d00000000000000000000000000000000150227356e48f29443a0ab4536e7a2f86f9e63840e23bbf1b091a59f52c27978bd6a15b29b105132298de45e51134da50000000000000000000000000000000017f5271c97d84f55f8b7ee0d73267bb69cdc7565c470a4b531f9dcd29596eaedf46e61bd79e71e5ade7d000c1c1d81bc000000000000000000000000000000001322812590e6c22bd90511ed72553c1cdb0ba83487b00e3adcb01a9abb438f365ca23fae9ee4a953544253696ddb0bf1a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c40000000000000000000000000000000007c59f94693320b01b56b36f8d1c39fc9e01bad289577738e648771d8940778276cdbfd59f07926e516fcebb70592de0000000000000000000000000000000000aa71d6dcb0b225526eb92b79891ef920634a007b87986fc0f776f85195ad7ec2d84b9bc684add947df8ff42c33b034d000000000000000000000000000000001362cbd6cca3d5c1ec68928be38aca5de1f224e7cd4f5c3ab1c2cd589bbd7c31022d4adc51720bedf2580d2acfa0f06400000000000000000000000000000000162bf0f38e19ddca9aaa370f988be9b35461d2a0f46143e8663f1fa549d0afa1596f029cf2f800b027b90d1eda6ae8a2b54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000000eb12a92fe65f79c646ba508fa615d09d86e582c3337ae16f66cd3bd74a9caa9dc17defb4b4e67ad62f0665c9ad1b6cf00000000000000000000000000000000058b6ce2582c46c0fc108a37e1d2713ff21ec8b1d8c18da0e69f0dfec7f2f327043e174e16d9d64f9ed4d3818a302bea00000000000000000000000000000000068192bd2ebc0a23092bb98c23f5792e179913c4ff1f23eb27296a77e83729803764b8db3b7ba4fe154ca467475eefb2000000000000000000000000000000000482b16e876aa90da6da35e0d7495a04d5b0a1d084c61821f23e1ad63cb1e66ef5975a3cef9ecdf2e696e9d9b50bf9b65849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b712fffce3e63362bcc246da566a14139a3d12807ba83ab3520b0aa3aa20cecd5718e2b7e00f24e6fa705315bc2175800000000000000000000000000000000057a66fb12f27e4a5268e56805fe2b61b5ef019b31fcdd861e2b0beecdffe1a3a69e8d193815f97740324aaa40ce34a8000000000000000000000000000000001080a9e1133f37288dbc3835e45b6611fe84ec4790e23e5ff84a2f72bfa2837f55cae9177e5a3a918adde777b7298a9200000000000000000000000000000000142dcaefd73d7f6342e87fff8c6cd161389b6049fa077f35076eadd2b4aa66f3a1819bf8272cac1c28cc02bb6440dc42aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000034c0f8249d6aefe4cdbf84d151ea9f84add42ade087048bbbf9de4a412cc805dd9b608fdcfa34fa224066b5f06d18630000000000000000000000000000000009e235ce5eb936bae00d3fecead8859e6d909da3d57bbe0a8aefaa5efdc94969a1cb2e12642c0099bca4e7bbf9833469000000000000000000000000000000000b6fbab498c2706f0efdb4effaf79218cf4b652a5205eabeb84f05a060da8cd18c8154a3d37594485ba50a8228f27f6800000000000000000000000000000000130ab70e17dc73f773df99cbe3f978bcd3fcb92a8226a1450239d209cc6969e2cecdc0bf3cbbe9a9c1de072bffbccaa952c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b20200000000000000000000000000000000031a2c10e95b841ecfcbddee4b458385e5650dec9a2d1e50216d9fc261a9829eb5fe894e47f171c8fd2f4d5d89771341000000000000000000000000000000001378471c7f770672ee82b70fc87af5ccacdf8995df9ce48aa9fc2f638105a2fdfa48b615970665ae4869f1e2dc7988e8000000000000000000000000000000001969517c503df5560628555a8780138e4c340d9d49d8fac4a8a11c894d283d49fd06aa81e9f0db8f015d9372762dad75000000000000000000000000000000000f5c2d9b7fc33167a6e9b5a5fb8c5d16ca009282edc05cbc8a048b835b16ba33515c226174d6ce5f9836581611ab403bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd90000000000000000000000000000000000a6a32f2006c4b7804e99011d934ac91b1b3fa6f5d02c574cecd6570bde1e998f135449dfc148aaa8fb8757d0a7299b00000000000000000000000000000000198beb461b59f57b85d858b730fcf853d967a1592e5e5787fd81c6a3d9d9b40c1cd7912cae21a47aaf78df5540604cb4000000000000000000000000000000000955701e84721866683b4eaba82c2df8a89bc906fb0a3cde565d314cd7278b0c56936205cc8ada10b03e69b93c48067b0000000000000000000000000000000004740253653a0d6cb15c76e145dc0b1f811bdc964f7d595b6027bb012b42409deaa8da83e6ddc3f0f7b4b237eb62b537915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000000f88e1e30674934bf1062ac619f1834f35f804a958e82121255f8087ae08f10525e740ee53d7514e0ee7c49e324513c700000000000000000000000000000000019d554645696b7beae881ef62297283c5b68ad3fa9a84a47c29cb53449d33d6ee7a5a3cb83b6acb75cd41ac3f52fec40000000000000000000000000000000004b32776966e52e8a72c88a689d6c56833296d384e2059d8f615ccd3616972074987f839b4689d5610a88addcd836d930000000000000000000000000000000000fd4d21b00d81ec993d2350f1fe360576fa983754a7159c2e81024a00931d84e419e8b5231ba8cf8f05a0ee6ccea7e558fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000177830cf34186191fa295b7f279bc819d8a53452e2114dbfe709971584ec7a2da7453aae3e64f4b14c261e22314027c3000000000000000000000000000000000ebf2aac35fe070403a4b7a5c2f102c67300bfd68af7863b45185b37ade1bc53d46772062189f348647e74c77caca4a600000000000000000000000000000000128dc7846b2dc5c453ba5fe4675d0c22f4d7089624ede05b0910c34ae623d4671979fd73455b35b61a57c51fe2895adf0000000000000000000000000000000008e33a3c3735be035b550613c712b220595a83c1953b24b3efd38c5913fc23df823e00ae5a1c2ea8a8eebbb93c5c721dc29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea0000000000000000000000000000000000a060a957a8da4384e3436110657110653685bb621c32810b6516c690a00c13e37f70185958beb0ed886aae5cdd611a7000000000000000000000000000000000b5afbc85e274049985eac230b2aede7b2df1485c9539a4a4eb6aea406d0f6515ad8bbece7155fb0dfb2123919fb8af9000000000000000000000000000000000afa722987390440a33d5103445dcef42cc4a3c461daa076d56fd38e0b220016ed2bb8e99b9a8da4af96b7da64ba90950000000000000000000000000000000013ea6b8d327191e53bc71fe43fda305a4a0584cad04048afc0480f179955cb27f2ac8791d847036470ffeb47aae36877dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e900000000000000000000000000000000032b490f795ac3242b8c7185c9e19f0440ecee3a65263dd4e4c9a431571deb7339bc6e2d73ec43750f6f027bcfd674c400000000000000000000000000000000076ab4ab3e8ed6ea3b882fde5cacb3bd094567288699e11f368c3f60f4283c5bcee7b4c5debeac541ead983f5936d9f80000000000000000000000000000000012aa2060e421f4f4249e83ca0ae1752dfa2b7ca958821841a18f05071a35fb9c1448619bd96f8a7adb2202d3ffda8eb30000000000000000000000000000000008b24f29ee7571f31ff86574e654a5d849acbe92653ae1a1d2baf4c9ca6e67da4937bfda51a70931a6e60d90162efb4f21c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea47000000000000000000000000000000000c8b41e5c47babd6ea113c0ad9f45a75d1ef6bd313b768ac01e6f581ef6630ada623c1a27d4aadf543af4055de7f6b73000000000000000000000000000000000a0f73af06f8f0115bf17f7c5db0a6bdea77a8e3d8fd0b52b0d4e2c558f1331f655dc272c86d98bf166b532ec8e45285000000000000000000000000000000000499b55964186bcc6986e7744c52babf47e274e47a202abf6f816bc748baf846df2b5ced2a5f61fbb0aa2047bbaf82db000000000000000000000000000000000d6c2a9a3fa5d0524f772cca2c7e72a5f2da1a6a1b9550997e7a6cac5b6b6c37693a01d30bebe4b9c742b63bd31487a1e1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c9600000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000134125416c7908cb4454ce6aadb30df46042ef2a6b4b69b19fafcb9ebafe8b5579046725590266cfd10fa26e1b5ff3dc00000000000000000000000000000000073f4147cce24e13b9eefad7c69b457acf126bf278a58a26a7c7c6b482edea6dca9725d7e5e4138b4ec81bc2505ce2e60000000000000000000000000000000006125caac1061cd6c556f4cfc122df8e949622a46ca707b48ef088ee5623df058bada1bc0cce1399f0be1ee86225f13000000000000000000000000000000000146e398c161e29c90c8a4fc44bfd5b3dba6f9e80ead561fa3d91ca5f416e06318dddcfe5147ab5def858fb025a1562352c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b6000000000000000000000000000000000944942effc77ad02c5ddb052acf86f3a9dc4127dd032181450295464b49ac1dc0047790acb378221fbeebd4c92886820000000000000000000000000000000018e1d201b38d88665696ee6cef11fb19f7daa7f11c5a5ccc73e6b66ac7b89df8437c9f07132ec8b69e13f63424ad694c000000000000000000000000000000001463117fdcf17f28956a42677b3ff431cc17ccbde067b91ecd6fae51e1e24ba8d594ea368d041656022611ad3ed44a6e0000000000000000000000000000000009715cc5add17395b7ddbcb961269fc5d4739d799fe9554b3c9e9f59c895ca5df8ec75bda05cbef3e6a165f7987e78662908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f00000000000000000000000000000000007c07cf1ac9b8b28e3d2f1f4ce22b8ee46e99914ba20c7362c679559a1618a906c6ea65c475ebbeca4947019cb6fbec0000000000000000000000000000000008b29f72cda71e0bc2246ead57b2f758b741b9232d87be75331275a5cd63afc9aa98b0e42c1b82cc258e93c97e596a81000000000000000000000000000000001512548a4bbd537a4d5baf673fb76ea7e35b2977216e7b29a6375e1f92049d7b7d5fd5d8b4ae6191f5592b738e149a5f000000000000000000000000000000000cc9d646428135296919808c6ac10c142e769bf71bc1490196dfdd4e1fc7b84e58155bfdbe77a9e684622ffd83e97ad3e8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399", + "Expected": "000000000000000000000000000000000ae9da7d12d0a03cca3b41ad869f762784cacb988eac7ce904ec9ff47824e058e2e211e2285f9fe2aed0b4385949b4540000000000000000000000000000000005b0c873d20f7be1410d39885ce4f79884eb6ae2b2f27510d6f6874dacf2a66c64e56b7aacac61ec88261624936e695700000000000000000000000000000000076c6076175ad748dd68fee64431e5e4ad013797de4528287e7226c3df90233799ed5c8b36848c1a2e1c02591a013d270000000000000000000000000000000001f7f6972121d38ee2d10c621a38448ed12271f7e0e9e4567fe1b5fcb469c7906196fe92c66c37f8c5abc91160fea8ae", + "Name": "matter_g2_multiexp_8", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000139cbf360b10e59c20dd4155af2023d5dfe0681c62351dd541cbed41b6a328aa44b862d1985b132a4d4ca61c95b61ebf0000000000000000000000000000000004af6b5a0f7a02d5c93304be0d31959bb4b1a7a5838dc3f9cf50180c4eaf3d32e68c006263d75f9735c8f0b5f811d3cb000000000000000000000000000000001644937e5ff3b8d2005a2f3b0984917837d44412362a121be481526173a4c61e20d61076aa10b4072b79743c5f7f4c4f0000000000000000000000000000000009bd399b55a59550dd876f35e54a5803058228bd6ab6c9a66e438cae473c63816c96bdf378ad426a236b58b90e737831e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a8000000000000000000000000000000000f343e5118d7dc3a38e9975a5f40084ee5f2305e45a8aed28ef105f76345d9f5646b4f3924b92978846b4e605b78fdf400000000000000000000000000000000017e61a2ecf9b3403b43f5a10a97cf5088b4f98e5a4513b0912ea7ecef44e6809f10dee60367cf2fe3e903dd68c2a97c00000000000000000000000000000000039f37f414338cab0e12f99b2aa1e3c02cbdee3406d1bd17c359ba899b7cdcff605f894530895aecb469f41628c3da120000000000000000000000000000000001b78bf69f1b7168d735fb4b7b135fe70db79f50e792eedea23f83cee9b48e092536c2ed146c7499cf047c5b4f0a08735307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af000000000000000000000000000000001342346f1b553e29e661c9f6c0a24d8f788db98262d6af86af55d313a37eeabed1183e367ee3d83faa3f284b260e786c000000000000000000000000000000000960c8af3f7e6587cf83baae447491e73cf41e637e1efd730e3acd9793717e57b85530584942e7a030bad3b91a76996300000000000000000000000000000000166daca4ee2cb9516b5178cefef0553115dec8157f6194d24d191cfe6340406071883c89246c0cd5f89bbd5d0f1ee15b00000000000000000000000000000000187f668086b9b6307899d301bdbfec915cf24ac0be10d6897b0677e4f1de6a241f3dfb19225644858be0941530e67d0f7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e22600000000000000000000000000000000032c3783e701bcb651aef40c91682eda03f9d90f252740612c85a5727f8bcc41a886b328d5ce787031c08ace235ff465000000000000000000000000000000000b0eca06f9fb69ebb46d0af56d3d934b333514d7f31208b4ee2fb92009e6041749028a78246a0adc324034a94503e80d0000000000000000000000000000000019eb24ed35f6c7ae53047814cab14d51ae6cf336d140a17e794d5cf18450b7fac3e6f990e12d340291459197bd353861000000000000000000000000000000001983a596485e657deaedf01614dcd5f4ec515c0050e8068ea02c9833d0b165c0f467107a50da0d8cd43bfcb59db6e710eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df0000000000000000000000000000000002f1c29ffdf7bf20fb8a13363393d5f1cca5dd9af82888f0102030fdda641abd5532ffaa2669c0c4159a989cef1c5bdb000000000000000000000000000000000bd548079899d49cd368bf5c246aa168fc8c777bb84a7930258502c8424a4b68e1ab20dc9ef39c307e52bcafadb0c8e100000000000000000000000000000000070c18918f037d5fa1aa005e2c80ce6a80b4b24d33ce72a2bd824f9a061af1db236f04d6041314310b31b805b8a674800000000000000000000000000000000014422b173840da655aac6ea4b7a04313d5d0675bcd565258c73039f879176e51ec0c8a9deba9c78c33179a5ba54492012c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000a1be8e39a47dbe0bd19b8108a5bdac582e1d11ef7fe28df1f12da52924e734e1d591e8e33ec20c6d5af5bc8c1161fca000000000000000000000000000000000eaa7a7cec93b8d5eb933103b52a35b3d58214feb8e2de0bba3a0e57e7993a9df0dcf8089142f57f8e0d1d303588ce9d000000000000000000000000000000000089fbfb389ba448eb77722994178ee3cfd15a27be4ed6f4d4ab6ea1a4c10d6ee8424beb17d08190fb18ab8498d4a4fb000000000000000000000000000000000ab02df2eb474735e28c45b915299230ce159816419fe9c99a7da397b7210590705262ee14c2a244f4922c35bcb119338dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a0253800000000000000000000000000000000006508fbef44d36cdc6fb37b6324810ab2a1d94e39abdf09d530df34714168105e23a7d6f7fd9caf31f263b658f16b76000000000000000000000000000000000b5bb1802813f9f8a16991d41275ae6d18532e3dcd2eae091da7256aaddd501855e775b779959fcef2822685725cd43b00000000000000000000000000000000052146ee63ae277911fe491420651a96994a30c7d1b19bab32eded008a125369baed2ec5a963bfd863a83c29bc1afb23000000000000000000000000000000000a180d79335347a8be350a92491760c6bf1fd56604d4d99a1c49bcbe50b2d04b7cdde55b4aea8ddda4bfeb8e79ab6ce4146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000015dcdc17a9afbf88b54af22ed2168329bc43ba50d374c0507c790f37f9669d0af167328d50d322a827d45f39724d2b2600000000000000000000000000000000169b83f2567e921a4319fc03b2a7eeefd2aed79914bf608d9e0a54aa71b9cb3e09f1cbfbadaa520c0f77f547fd407ea50000000000000000000000000000000009b7a8ff8388c85a0fe3860f26b09b81b5dc51e00a8961fdba96eb462e1334e9e28a2cdc4be49dd8b96c548c64921718000000000000000000000000000000000243782436fe7cb20a3242a3a21402a43a2c4fcbe77cc7182ee3cc04f4795c269d8a64ddd25e89ba4fc796747b608092de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb94973680000000000000000000000000000000013ce6856b6df48e4c9e3fc0be0aca5b139e1b874de6ddc148c1c23a846d61e7a531cc889bab99706668a3b69d32b9160000000000000000000000000000000000a459676071c7f3065a6dd7632edd5842db34aeda8fa0e7d7a8ea29f842ebcf2c5fdfa74ee7685caa51481c4f46952240000000000000000000000000000000010c1d9ebf7bed9195cf0bfefad6ba45f1bd19a9a7d340b7c630b9953923efe4907bd75a3da066fe3d49d656f3ed91d2800000000000000000000000000000000039189de73332d5b5a160c296a195cb9d8a736cca23a92948d513da7e4fc46e1ed9c207e86751b3cf1310d8a7284877ec2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf00000000000000000000000000000000129821e97c65ad3801c011792f4c099e19919d7d03bf9fcba30b3735586bb7ead7d4f9bd10bc5f0e5cf1dae82d5651ef00000000000000000000000000000000038cfbe45bbdc494988a2dc72dea6a7e36652f5e5a2ecad41b4aeceec05dc4a389e54cd3aab349adbe32e65206eb481b000000000000000000000000000000000bbab53f2be2c471d6e9cbad719a73c00b582d0983e25e1969c0be1faa56b1dfa5b7b55797b3340cf8c7eabc560fac71000000000000000000000000000000000b0db19410e552a2f7889c2204a93c5cfc71c360329e3be3171e88fc7aa1e993a5d089c28b1a8f8fc80d93ba194c63ccc1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b0000000000000000000000000000000019320bb8d29b7b5a7130b87a39e87e271b96656b5a2749f13208520634009c26f9829401d3e21cee5a757782c6bbf9ca0000000000000000000000000000000009b37068d72463e72f3a89b9093c1b09f01770e647b5ff7daa50e0679bb76404cf7729d5575a39f5b9b3b371893967df0000000000000000000000000000000019ff29e41db50c736e12f62d76a28f4ca4f6b0f4f61aee00cc0e9dd4e5a75c0ca965b82698f704c604bb309aa5b457f100000000000000000000000000000000062c352a554dc4bb96b459378c21ec6446e15b868221b2fb745d31dece854bc281bc22827d84ea3b0fecfe5d156712ce6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa200000000000000000000000000000000021c52e82b0012537b57fd92fc276e8de842a59355cc15d69a52effcfaa7cc43dbda0c34e1b9af44c2db8e9356b9c71e000000000000000000000000000000000371a6da5dd39092b6108f631a0f4c4401464a109ea1e5d14e262c8a9577e1421d41734d2c3ed73645cc13ef3988e9e90000000000000000000000000000000004054159263ee60f6b1882ad7c376c738c7ed87e6b34dfb4be2fd7aa29ede414c2c6c3ff098c53f22a1c1cd836a6b0600000000000000000000000000000000012d7af6b57c688e1ce90e9f2796b0e525e775fcb6be65f5d2fbe3d1ce1e5d948dcb098c98d495a6e3dd813527b4635258e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca1000000000000000000000000000000000e53128fa5392dbae9e40ab1ff0149d5b577d9d30dcb85eb5e4fcdc17c7daf2ff1d6fafd4a1aba88d2e7aeb45a01afc60000000000000000000000000000000012972781f214511e9b78d276767b1b64bfe5b43215c7680c0063b6974f703b209b2929470dbae16f9767a7cba5311fec000000000000000000000000000000000cf6b37c5a60851d03752f68eaeaf37ac67c661f644cf507c5458cb5404d0ce903c92ef66a657b25ce07e5cf5d956929000000000000000000000000000000001835f202705c8b984a4c7a6cd219c718ab27a96671574cf7cb618235d19e9046a15212e0da6233f15f18bbe192df29c38efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c000000000000000000000000000000000d01c6e8e34e646911391b012680f0dd8f4b8d77c10192ac09ce57b6524f0eb8c7f83ff8f26d856e0945d7a909eb790000000000000000000000000000000000070fca42e34dacce0051f9e26c7c0dc328fe652110976df6df77af04202831dd095715af1714b60a99f2177e86a3443d000000000000000000000000000000000063ba43df0155373df59b009a8083b9f62004327b16ad455037487c5b8325e7eaf57a4d05c533e284004be6de79ad1e000000000000000000000000000000000870c2e5a7d26ba54bf0d45ddf0a4c3011152dd12a5e01a80e42bc4dcc784c7ffdb66f9d6d69ac445c1d9aa29586245147f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d53000000000000000000000000000000000d84ca02ffb6d3cf6eb27a143ece73d5bf006ff61569f0eab00c5a512c5b46e1fc21e8031d1a578010c9582d75e1faa8000000000000000000000000000000000a41249cf01ecd23d06f6a3bb8573186fe47e5165ec0d447df62bfc236f4c203b4feb8e2a4785648af86646cfb0c4e32000000000000000000000000000000000244fa6caa86fd27e044145557697ea89baf718746711c8dde334a2c5ae3c73d7a0e04fed6289ddfaf26e47a9d26b09e0000000000000000000000000000000017db897060c0a8e3e5d8eca9970407b46dc2c2ca0c004d50a171450852f585268bfa8a379acd01b6d4685e04c0b8c106fb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b55800000000000000000000000000000000071e13963e20eb1dfb671aa4a090973e4a4b7ad3578f8630db8a865847be46c796e6f9e095a9ce558b93d702f8f8572a000000000000000000000000000000000dfc4c89ceaad07e3b4c35d96e8534122ae48421cd4443de478ddf9a8867ffdab279ad745e55c87b731afa7700bbdb110000000000000000000000000000000015dd6b0c26f6821177d0cfebb7f1481a971e7601fb24ea365a0c3127a5b1042eab69446de05b61cb6ac0576752f87aa900000000000000000000000000000000156326c52bc78c82f5cb4aec5de35e3c128c5561dc80da2cb24d68a7e912b1f2dac2078508fdd4ec38769102c082f0f74b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a", + "Expected": "000000000000000000000000000000000b537dc10a6f518122665f7d78326a4728a2889325e5be7da7e25e4752c680fd786cdaadfcc426343a9844efbbce8f2300000000000000000000000000000000085ba3a04aa8cea82b95dd994f5b3bdf0dcf63f13909aca2c2d61e4275a7ea22445c953b927ebc6b0987e98b553469d40000000000000000000000000000000019cec2e9fab640cc88073bd39e46cd571324904b1950fa8f626e2725936d80daacce2487f46ad23fa8af9c6ca0367fdb0000000000000000000000000000000007039a0e11cbb8bd940eaf4a192bb94ff8c6d6c79f775fa67821b5ba411641c09dfe9fac4cf45eb5fae52d2fc4beb6bf", + "Name": "matter_g2_multiexp_9", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f73a297cd6444809aa11b0756167e71986ab31b52b57d3c0aac5637129b8702ff21ec649541e79644c27f0017c8ae3f0000000000000000000000000000000016f96d6ba02aab604dd918cc799cee61cda4c0164ed9f07d4932fc4ac3eeb92b1e6b40dd7b18cd8d26056b486e57ed290000000000000000000000000000000012156f3ca3aa1e79014dfd92fbb6c785cf0ee449a8920b89ad04355e0fb7c8ea804bbad082b4edc9abd3b24ab0df2b61000000000000000000000000000000000d51b5f62a6e70816d7671bcfc52f11bdac6221a23287286af78605b99ae8bd0c722e485bd0381b958a85f61e05de68368ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000acc52d0fca02c3228cd2e5202c4eda297b8227bf4e64308226bc487e5b64738efa4c07a3738397f90251ea9a1a9da29000000000000000000000000000000000b85b853826a28777a5767d5b1966ce12fa8999ceff5d6deab5c947fd19d19de9c103bb920bad615186d132ec22187320000000000000000000000000000000006b5a83827dc7b3580579ab7976a70ee160b712580919b6f5d4e180165e50f5a1698fa7cc63846eb1f5e6df955c3eefe0000000000000000000000000000000006c2957d8adc55931900145388583e5c2d5f6bd784e022702801c38534d2c92c6df9f95d022aa6d800e1e458eb7f313061529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f810000000000000000000000000000000014e4c5991f9f2ee262019c1344a0843756157dc85aecb15718217a2fbe23fe0843992dcd3953ebe79acd85517acece0e00000000000000000000000000000000076a18fe710aca2875bc102f21782c9649f107684a4edcb0c4538f1a2890a2ae5b46a182d5470e620375327965b6d37700000000000000000000000000000000142a0fb19b28a034d326121458628356561e50cd3a471ee78bade0733597b8b90f647f5199d4b5b1ee6be4e1870bcd310000000000000000000000000000000018f8b5933848813cc2c1a0f079b095d565e7875ba6693eaa10967d496fb47257c9c674f301349dd8f2d22f8857f9d5ca44d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c000000000000000000000000000000000db331d2b965dbc053b01a61e671d2ee6b04b072b6494e482f48f12221f23e3b1ccebf48046d92b4be2e4283c77f51380000000000000000000000000000000016704f3e1ce14f49df400592ce29627833ed1dbb91ae5f00779eef94fe9ab313c3e7c8da940085034e1a49158043599d000000000000000000000000000000001956d492f5764c6de0b8e9a716766c762620ebd3265a95b47a8ad2c0614c337692108800e22abbe321d77a6cc17f4b880000000000000000000000000000000017149865739d6aed0f2a4c3c71c2d02f8080d9339025b03f89a37a165fe6e5a4cbd489b5fc90bb2cc432e5baab213c8424872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000036804da102cce975f980ed5a69e0464241b5de87238f9892c77fc2b6e5ceb00d7a37a45b5520fce5f094f8b9510f49b00000000000000000000000000000000049da8b6c974f2d680a80d2007333f15702f1517d3dc11395662ca1db945c795bf64167840c4df0fda68a69e127b2d590000000000000000000000000000000000e94cc66f1ffb2112e37cbd5b4feb7d65032c2e57260504a42816aeac85648558f6997ef12028655103a8cb9de1297d000000000000000000000000000000000abf7703ddf6995d5c29124ba9a3f890854fe0622d547a4f24d6a60b036ec9e58f7ec2deca5a71e1fce2210cf810e2f901a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc00000000000000000000000000000000008d22e456c643ce680f5ea14553a9c249a43d4f92d94135dfec85bc58967ec01135507bd8ac3954b5876c5bebcc1179800000000000000000000000000000000022029d4abec7fc9ab3bfddf2f462660bef7449c4093144d9b7d6f9e84f4f1c947855ca6e09bbb3bee4db096978ae0dd0000000000000000000000000000000014beddf6a3fbcd621e2a592e1c87952ed277163ebf390896f7c668944d6e0a026d3df74b0fc877ed560527a80b981d1e000000000000000000000000000000001414af918645ce0d4d1f670333fedf286b01213408019e327d3cb9321f06fae311b598c2f78bb578e85692e6cb787a52c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000129f1e25d96b8c879710a81b727b31d27ce9887c245bf908a3768f3606870ca6bfa70dbf5135819d36582d55f230e94c000000000000000000000000000000000e91eaa33e7cacce4e1d6d0fe905c72221b534a72cd51e1de79a25ef0c06ab454a849a241c023b0f82aa07de28e35869000000000000000000000000000000001379e390f2f0f3636312465469b532d876529d58dda8b024b6b81d242af47b5720af4360d5a3172ad80fd9fd8a14ba2d000000000000000000000000000000000775992d5a8ae0640af845fae03dd0b2197699f413f90f6130d21db0dab042324094b36acda26ed86c65821d2d8a29d9e62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000f5420156358ddbabf31fcc94678866f899e38747e79dba8ae280704c4b199a03eb423ceed18b5cba7e7ce84583c84a0000000000000000000000000000000001127669ef3ba3785a859aa4e942e8fc3181f2703b0ece6ddbee8830d7ffbfe498794f1ca2e67c3ad39ebd33e838dbc5300000000000000000000000000000000138113386846310db8e21fb8bfe40035cd89e51736b491d5f2d3cf5672e6836c25f62eab80f25ab49d16dbb83796aa5d000000000000000000000000000000001711d74ef4995b473239a574fb8ea6edc6eb7a88793a093df4652da240d069c5bf9249b58e9b1e11f7d6619cdc28a5787a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000d32ccc6598af8156f1c5b35e69e7c7f57f9fe18748510605a2a81b4ee09882bf3fb26abf50206cd57c77924ebeda8010000000000000000000000000000000009043d364e0637c60223f9a5db8c50e983746fdf4c9f7986d27f5f4f3a6df487592ea42078f14efcb3eb1b7e81d058eb000000000000000000000000000000000233495c4961e71cffc2abcde4007c0d587687aea905f3ac5758d0f8d9020197adb6f9d7b86a542b8efffb05dce997130000000000000000000000000000000015b084e773e66ab1459825b6e6dba055a96e4dc1d94ac0b640e906e0a9f12d2124a58537c458e6e1b571311b93acc26c0f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce8880000000000000000000000000000000000e4b859c679a90c03ea4d4b0b3d38211f685db053aede0f7f359f712e1ae808185758546877502d57200da2c2137f37100000000000000000000000000000000173b24ca19436b51aae22838674c41c752536eada3197de6efc98303eceb3e6e8e47ee6679e61e3cb5c8c734c96c98720000000000000000000000000000000005232b8c97a4860a23999d6ed6d173d300ed50b77c7b3ceb4e8407d9d6877a6004e2f76c553bf458b7cfd8d1e6fd364e0000000000000000000000000000000018a115201e3f4eb308c16656b3ca0635e6284169cee3f28101903ce1cab0659c3d83a449918df6e58e8af2e001036b8d9b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c000000000000000000000000000000000131232788aa3038a6b8a055a896af4f8129e3dd3397dfd90ce86b3e09a775e5b5e19f4387f4c02200a36bc2a1e09d98000000000000000000000000000000000eb8cc0455cbaae97dfd05c1246d3d5ee58c286d263184ae342f5c0ef432355a574bb9fb8ec67634f999b6d1419f2b6900000000000000000000000000000000188b8a85a6b255408f074b3cab66b95e0e1a1b5b8965034246dcc196f2bb84aca3a78907409826370bd65cd4c4d0bcf30000000000000000000000000000000009603984f6d9876e9c235621fa817efe45727fd8c4f76abb7b0796ae721701161b39ff7cab4c57850014e7f1750954ab6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc67030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d40500000000000000000000000000000000025809fb06c8a31f31ca5b4a5c795bc93355c78d9a2a4c1d707e32ff2a71d94cc1bf7b709cd5d6a183cb05fb6b5f360c00000000000000000000000000000000127bd8c9ee6388905ffe59bb0fec0e42b4aa44be74e5961dc2353e474baabfea86c41c6173db413ee28681a6bfd3ccbc00000000000000000000000000000000181f40dd8581b9adb2981dbcae27c7e906138569ff41a833ed3e6ee4fb0baccf2ccbe5b28ae2ff8e08c4f534116b58c40000000000000000000000000000000005cdd822cb47f35f31e0cbc26f6c957d51c6880369af94fd84daa1f1ca95e41e240b910f031585842fd2dfb170d618aa88e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000004a06984a3916820368076ab8cad6ffffded2cf1e67ac33f539ea8fc7a79580c1969e55b2a2fe3b31de912d6606c20780000000000000000000000000000000008a1152a581b6fad2a23aa8b0b51cbe523e701193207c896d08b99a672dc047498e565a568b79f8f9188767ba95212be0000000000000000000000000000000003539e82e5b88ef660b6593fdfd9591ec23e7109642f4aea0570f1f8f8e00822d2af277632ba74910459535b35ad47120000000000000000000000000000000015d3441f621c7e6922c489e474f80ebeefbef66cc59e4350b6f803e409034b7f498be2dedc97d902590fc1e296fe983c26989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000f775e13276c2e32dfde955009422557f332fb42dd9ccc3246d2b080e3ec44d910aa734478899698a9b04f6fb1a8f922000000000000000000000000000000000460ee4df6dd0184bcdae6d53cb66967c2213fa878a829c3196664f8d594ca6d60bb2a56f93bda3b0d2e6aac0a1a222d000000000000000000000000000000000fc9bf81d4cc80ba4e4df7307f976c2ec1ea2415df3c263cc970583824cd83703aa994daaa6e5c20450da2ba90a242830000000000000000000000000000000011f08ecbda9a192b232e8330ccbccb16a26bcf4791707f2cf52c2e11a8b3993221666563a772d82f4665804275b03b613d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b100000000000000000000000000000000027fe7ca0fdf1cab9a52e304e55350195492abecce4289b0f1c02235412bb012803e7eb59e23c665ea86dd4f74c35c440000000000000000000000000000000011301ecfc78ada92885bcba8af75da6cbcb448e0c49511f3ea306f4ab944f5bc114e72f473cdadee2d0e84021905c5300000000000000000000000000000000010eea529fd3162ad7b49638a70f6f2c26a6844251b2c2f9f8ba54cd334914e84e5a1ba9c7b4e7a8b9cff1a909db78bc8000000000000000000000000000000000b8a6235a7310d52fc8050bcc484e6ecf299099e193f91bea9db31fae71fbd14978984a9e6de10939d0fbba96314b0a55639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef", + "Expected": "000000000000000000000000000000000de312093622aabdc7523cd72f568060f4236c7287d61c3372bf81d9bfebfda2795c3182d508f0268d8f445f6ea0a5f3000000000000000000000000000000000b027f117583406916a8f139d47227bbea28502ed0df91cf0841345435376c944a587c3b4bd60f8ae0be7c7bad1c8199000000000000000000000000000000000e9a7b96136b26b0044b11288d35969c17146241aa529e581a8fcf000c33fcfff2dfe1e55c0fb63f6032d0b6b0cf81180000000000000000000000000000000002a442e740ee390d87ec657fc218b76adad7f6a766cbe8f34f4824ecd1587deb3706af77a95c1d5f8e79eab1dc482c45", + "Name": "matter_g2_multiexp_10", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f54bcf1637d03854cc2b785e52bde25de7e45308048ed8ec0169069c2124871782bd9d26471014d039c9aa022e1a99d00000000000000000000000000000000106698139b096a5a79d43321ea64adb783011f04e5779625c9f77e5c390b46ef0d249387e978e64529bba2db8d7aef2f000000000000000000000000000000001668d5261a4ba37d79c76f44eae9ce2aa3e216c5fbf6cd2e90c6a73cebd8b59600303afce70de3e83a08c20de4609b100000000000000000000000000000000004b1b122cb55e688f8297913b84d466c6f3d99c09f4b039660238c8bcd0b7f6977851a6ea4b1deb01346db06d75180c142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000192a28dbc40d5ceee4d33b5c2778cacf8c3ed7d3227e7ea0d6fbaa7cd4a81134b63415f4f1960656b1fed15023ce3a4400000000000000000000000000000000138f296c45594a930b949756d0ae14dc9a720bb2bd9e93c7895268121a086a9d55c10135962a172c02da1eabfcb8caa20000000000000000000000000000000001605ef8182fa13a09a6b7661472296af2b0fdcfd7b051e7cf1d9e6d7c7f4ad9521d7732733399bfd5d09a088f25d215000000000000000000000000000000001928f2e5d47d7273e035114cbdeabaca724409a56056b4e95a4ca3b2222716b3a5368da3ed406d73f43e9571d1e04902253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b70000000000000000000000000000000009b35f132a903579d82cae6a321c1ec7fb0281c3e82e9af05c3b2830ecb4a941da5b1637c1bf0fe9a39fcc9ceb0d09d8000000000000000000000000000000000eef9c0846064c866ae07b3709091b8bd48bb6b20f995b44fb49e030b5cb6d78b7f8201704b53697190a5e36e9a4541c000000000000000000000000000000000a98a5d0d5640d6399a3580036f0e5cd693a7cfaa26438a00767d5ffc0777b83c516316d9cd4597cf8601544038f4d9a000000000000000000000000000000000e59541068a62f105a0d26a5f79fa5fa8b41b2211f1fe674d84dd853663962d64a7f70e785b51ac3cc07267c73400fe6acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000157f58b1c7152a7f931bccd9a79073967ec28855a6d74fb8727f59c5e3728fbf07a5032dccb28eb8d8b24229f2dc1880000000000000000000000000000000019f41bbbb853edc1fe3ee82f901e613107dd4ba1d880284ee95a2c4cfb2220ec1408f8bff14defe59775136bc75b4a1f0000000000000000000000000000000015538789157505a0798aa36fdd171e0bb14bdac75339b35805807c18bf9175d877360748f97a8570754af0e28e89df660000000000000000000000000000000010500aaa99216aa979acd66c5b0cea2a6a973f1cd10c412e823c61cb897bce54d783a6c0acee22cf9052166a4bb5adb8d9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df000000000000000000000000000000000439729e13e6a9b5baafdaac65783ce79a5972791610a333224e61104d15c746d7cf8350e619f0f72cb73635f6795c5f00000000000000000000000000000000092e3c976a4a5424b09e50e6513a9e1f427356ce161e742be31f0e589e9ff862460d41281f0bb2d27b1837a70a5938fc000000000000000000000000000000000e0e51e92ac3cabfd999cd72b67cfc488e150b11b18f9a31b1c2338fd4f2c58937521b5a107752c342e67666b99fc42500000000000000000000000000000000023d8884aa3f556e98e006960293230ac966ad18f3f715e6ab31a6bf0872c04e6f115fb1608cd87ffb369ff31012a11705fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c34000000000000000000000000000000000be33bc145611afdbadc636e9d7cb7e3a9c92c32f6944a2b7b5f44c248a0754c174e3286ad307fcdb2ea02a3578aa588000000000000000000000000000000000457de1fa8642d302065319b1d32009c64e7d941fb43d1b3cf455248664b1db516379df87aee05a651c132eab8aaccb5000000000000000000000000000000000a711f3bf1bda60ca49271e8a3143330cf924328d3ac6f7a802c15be1d7413e300f398274f338e6bfd0225cd8ba25fff000000000000000000000000000000000a786c5c7b4f1701e292aaad9b2e47bb883409aae0c44ae813ba48f401f4e2146ea0b1d85f2ce862b6ac9ad3015d4b14d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee1190990000000000000000000000000000000007d03697e195a6b714fc9785b49e54e219694250cf5fe77553434eeced15422de3985f8c736996c1763d4b9248a7a7e00000000000000000000000000000000015841a70a168d2f356a8ad929e2d1433b782351f4833c51b50f3a1af48a85468c2ec02699550d21bd919203df73abeeb00000000000000000000000000000000170902520080c46faae2bf35de396d56921bd0279fc889f0187adbabb9ae52b849269d8097d5b3f331dd5a817f9b2ff40000000000000000000000000000000016846a000f037eaf5953b7c4b477e441ca4fa738895aa24dfb0ef01a4c8fc21a318d40a9424e151380084578ca413b3344c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f60000000000000000000000000000000001a50509bfb12040c0271b231c566d13510e6ba84448e59685f5bfbf5b008fdc64cd5e9456beabd23ac011b071e3a5fc0000000000000000000000000000000014a964c9faf1752170ca40cff1b9b4fa17f8d2b56a4c4bd7ffabb65798771cd624ba61ee43160e70731fb9b07af8ecc2000000000000000000000000000000001822ceaae7bd0a734f57b67e4834cfb00a6b415459d81c7d380a2e5b5c795eb1b6d63ddffb1131cdfdf0d76852c75a70000000000000000000000000000000000c5a1575b30e5470151ba055f577a0ea49cff869614c50194829e53a3e1a95847fa387a0f45d537cabef3a5925e61c432583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000018cab86a0d70fa30b4df3e05a91eef57f6505cbe4bb7284de56d420ef3bf315be9249eedfae92561c643bac2c92301ee00000000000000000000000000000000098ca598ccdffa9bc9d464d51b46ed8a8f22a87ef408cfa45fa7f78ae2dcb9f861d9d6a571f6fa702a71e783ee3395cb000000000000000000000000000000000c073c0a323c3051c302c0558463a5c030539d74b440fdcb16b42ad5ec097e10c16bd9a651d149dd719fb1fb865420a9000000000000000000000000000000000164e622bfb8ecd5eaf691abad9db38ccc64ff0fa1784d26db8c8fbebc929bc6d4dd471321e01233d55fb4a9661780b5506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000fa48147388181e8d0033004118848c50c6425f2e5f91945a17abcff4d11928d298c092d60184e75e67c7ddb9eaa8255000000000000000000000000000000000c535bc54df050c1ba8d858a346d3a644e03fe24873b7dc3e23518d44b06fcb3f52b4be6f11d3b66f0180a0a95dddf680000000000000000000000000000000015e279a2893c205dadc8e1cdebd9c85454cd4b5d7537f984c8f9d451f8316620279357e218fef87339f1728fa317fad5000000000000000000000000000000000316e343ba68c8a762f4c8f2a5c20f16abc4a7a8365556c1625df832219670619b6dc70727e9bd9a64ed491dc22cb9d57f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca60000000000000000000000000000000011dc72100cdf676e41f21015fa7c57897da8260609467ffd38c17868a4dcd2bd5d4d72e89cd0db2de83618222ea3b5cd0000000000000000000000000000000007e074f73287faf304f618478566b91c8e191b229ab40743081342e676be09c2523681cf7ca6f7a396f8589a4ae18a6d000000000000000000000000000000000ff753a16c16bf0dd1de9fa9316694214aea6f99b81f66b6bffd58837c00d7f5632ed5f8f4cdf32ec59c29241ed5e28b000000000000000000000000000000000851e26675814612bcfa639fe567633e1960578a0c8d2e6568418f633eebc109e6c8af97e77bb28ddd47c6bba8a7ba724429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c0000000000000000000000000000000009eb2f172db0fe9ac0332381d929fa200a97047f6e732570d23fe27f5ea3013fdc52fd0b5ee74a4387af44647b75f956000000000000000000000000000000001355f8e1cf45443855f2d62dba0fe45b2bfc4e0d06aa7aec7e4f7f9c4e25b33d9c46a01c224517bac9a1390a9806ed4f00000000000000000000000000000000179d47a62a5c847f47341b1ba58f2c3b073c5282f925f57efed1fc43db04185955075255e4e4f6c209757ddae59101dd000000000000000000000000000000000ef5f74d4b13754ceb3b468879f1a8befb8bbbdbb143eceabf2dc8e68fe6cc8e1ea4f3eca1b23a1175c9f5f5c4c20d3454a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a0000000000000000000000000000000005b70a4d5b91b85971aef26b1521e12904b7ad224f25e31ec6ef59856cc702043a3eb975bf21dc8e4fc55171a3865bbd0000000000000000000000000000000007cf7c3e75a837545b53ca3e175a275dc6fe42fb88678aad45910d150ea9c6c94eba615429540348bb2ba8efacbb20e60000000000000000000000000000000002eacb469f5f8ee6c9f557a6ddcc854e955c5b9203b4ca5dd2e097d3e021479e13629863eb5ff17db46a17d3b0227f58000000000000000000000000000000000905e66f3a051b304b110a8682169fa749ba0de7763d3af7edc3e40f2d22ce7b6aa00cd06d2c82d74f3a9709d955f44e13814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa0400000000000000000000000000000000060ac9ce51426d360eff0d911d9f97a86494340bc5c5ba31ef146b55ad3633ec57a700f04b0cb9d4e91e13c2cc5e68a8000000000000000000000000000000000df205ed85e27c25ce27270384d7c3e58c4e0a9f214d74cddfbc7904eb3115e7bf204375df7558c3e65f7a81a942c5160000000000000000000000000000000007a220d42ca8906013479442d7204457b3ff37c9ee70d64f9f6858ba788b7fc13b71d33ad527c6fc673ad8940b0f01cc000000000000000000000000000000000ad481ef549de13b174d82fe88fa57b7e31ecd8999bcdb0c7a8735ab619a13b1e684b9473f0c59c734567cc08c76ecd6aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000000366f604228e2dff2348a462c56e0043037d1b415ffaf155e72c559d185c6b0a0d125585d060f159a8cdad959af631f5000000000000000000000000000000000f69e829a0995914ac122299d4424b4e2e120fa4913939d2f18f9d1496e7255d00ff0829c20521ef47bb0dee06c28dab000000000000000000000000000000000a3efb4a376281a60f5246d8fc10bc23cbb9cb71037f8f57271a9b01f5e0340a562f9acf0e9a95b8c65ab7a5cd95520a0000000000000000000000000000000004a4ec86e2b04bcb35c7840d85cd1dfaa88e17ffb557ac591640ed8e563cac891793b92e349a7903c6c1f88d26a01c88c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e000000000000000000000000000000000f5bcc27c243ef65dfbfc0de6d431706ab20d6cf6408ca989a2bc1c52b78ab63de6f58b70bfcaf6878a2746f249b6b160000000000000000000000000000000016a4c9e8ad0634e8afa8606a1a7bd1d8cc0815dfc6906b6e6446e0ceddba4a4a2df979d27cd07b8982a12550bc700fce00000000000000000000000000000000051f8d972362caf0a8a39045bb468112f2e73afa392079f8a4dc4c3a3cbb8dc224c21b6633a5ffbad08796ba2f8df44b000000000000000000000000000000001825aeffda04705ded9c702ba30d24b9fe8eb7cb106ee5d4e4ba029dcb57bc42c74e74e92ef8360cf130590b838645429680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd", + "Expected": "000000000000000000000000000000000d0ab61b29ddea1aee0ca4e81b5369f37cf45be383f64ba0b1a5a74b790d7264016ee671959444c94b8e6291c5158ea90000000000000000000000000000000000152bf3709c56b3add8e3396d17abcfebbcfeb230529ea8144d6a120a0a6aa83cb284e40ffb9fd9a96f8a2f7244212400000000000000000000000000000000041f516a7cb2a7137746d028b0739c79ffd8f7535f20ba3728ede32504fe058baaf684cc7677967aa46777818b1fb6630000000000000000000000000000000009f1035729c55cf6ee090983a54d8c0574bf96342901f471a2e5380f11f235a075b0e157c38c456b6eeeaa10b87d3afe", + "Name": "matter_g2_multiexp_11", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011ada4731ae7df493e405383603a8d79ef77f3fd14fe7b8bd9d2afe068998cb712e84927d5e6ea6e94d7f10270fd193b0000000000000000000000000000000008a14eddf88826dc3be792a0c1f7395efdf91454cec7e26c89f6beda37b194b706dbdde8745129e821b6f4b4ea6118490000000000000000000000000000000011c29513e8a826e6b3eefaa20ad841605d04b813cca282fe02dca0f588b9a579b2195b0b080cb6d12c1a7881008117f8000000000000000000000000000000000689c67d05ca379367fec99439e3806f827218ffaae995bf38dd8e2919fb2e751f426525cc2c6ead3b9aff2e377fc99e1ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e000000000000000000000000000000000dd683a8e4ad54b1a95826a3000750c6e3cb250ab5d6add63c21b182d736b220d917d4e70044ec7101c3bf8ac620e1dd000000000000000000000000000000000f3e411cc6800b304fda1373ffa60c7718e20bf3e2e5f9784a81b47e398888b366e1f04f48f5aa070a661b5e2148d4fa000000000000000000000000000000000b0f8d0b695e000158ba80881a9256ed9dda5a7f53b550bf3b5c67ab160060fcbf5ce07fe38253ce037abedf4c6f08d1000000000000000000000000000000000bb92d407c457e9ea7b9851770d2743758e162dc9cdff2dd54b8271c046f642729cd2f10576013adac84a46d38623b932943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b100000000000000000000000000000000023e880685aa69b3480bf2b7f2aed1181e094322da9e79c9263d50a49ba4fca713740bdb55886fc81c81a51045b35139000000000000000000000000000000001707049fb8b7ad278be2949b9eae2e28bde9de1d9eb964eae582541c2d7a8afc4c1489624a0919047a167028b8c77e3c00000000000000000000000000000000062dbb2bfce2f67c32b87ec2fa01ebf7deddfcbeda2fcf0ef094b1be77b7411f657e745350b6d2da16fc83a96f6f20e500000000000000000000000000000000062daeba038c7bc379f56ac371745b91fdfd5b4cbbe50d9619bf1d077f3cde966f81f9b851ebd206f2609a780b6dbd681551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b553826dd9e2252c9da74c2bf1bf850df3f9c37439859f93df3fbceb7cca4fd949dcaa7fff31c9e06f41e51ae0b30bc00000000000000000000000000000000187810711ea5911a437a62e2ca483983bf2535ff9301a1cfe1b4d41902ef689f8d86f817a2a7c77128e4ce1ef6b037d60000000000000000000000000000000010170cf5f2ce08211cfc41bf54cfaa16584f833f7b97b2f6bc436eecc56ef44463690ea1f5c8c2a8f69d93a25206282b0000000000000000000000000000000001e627a68dbab6b0d05c85e49b966a769461ec38c38fd94992839bd0d46e06410fa7a48d418d65a8285f7852e8af4b318eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f0000000000000000000000000000000011ebe2edc3de58a57aa9ab4d6626d7b93235ed24efc3d75c1ecae376c00beffc5e89ec509d243f693d327f7a4551921f00000000000000000000000000000000088ca2fe0651e4d8f3958454640a58ea1cdd804bfd2700bb1bb8e26ac50f2d7fc8c292f94b0bccef5735c4548025735400000000000000000000000000000000154936de8932279cd39ae803a5d814864953f647a5334bad958222de765250e4bc847e02979689dc9cfe1993486b5750000000000000000000000000000000000c7ce07c9746c6d72dae11e243acbe12dc23423f870f3130b244eef34524d547fe0b2c4b704ecb6b2e6c32f5675ce67ff72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000031ea855125d75321a2a86a93e72fb3869dede7531dbcc1cb07ea2a352f3c6cd913275d0d43ccc370f4539f668f205f50000000000000000000000000000000006c4cadb11361f164f5899c6b57c0c6d8af365d902f4575c9d2d14dfd880501ce9ce218544b44bf07f0f04ed68e8f315000000000000000000000000000000000131332638026fd25b1a849c984f9dedd71e64fb52a61968666ba80238673077ac00b9e09817426ceac8c308f475303c000000000000000000000000000000000c7634af796e7aea4d4d83c9972fc822dad951d2473210ad82706ae0aa023ea85c1c467bdda68881094ad2a4f54cb33f331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000fcdbf0083065e13deee2020bb6e47cb9e482df3768ce569f1f7c0e1c6083c97d9f08444e67857c2dce40e4a7b8d50cf00000000000000000000000000000000010f246e8ffccc2e752049f638617e122773a6f10220cdcc0603d24f1a94ca7c100f8ee2d9bc7c0a931fa0385eee456f000000000000000000000000000000000f8b68941df75cac3d4b6b3bee43fb357c8f4e56309d8509fdc62620a085d7ee58f52c7dff28525a449cabfd3b7ab3dc00000000000000000000000000000000019f934ef0c7c40786b073d38cb3e4623544cad59cb63440d4a6e76944d491f6b982e3a5e84124996634687d4618418316d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000c0208c1f3653fb3a5e2acbbb42f2598b22db1a714d616ee6bb501c3338e80db34d517c7086d43ddc77e0134dc5a4f290000000000000000000000000000000000a528245342e44e36f8e02e7259749e63ecfb38cb0609075e871701f2b3bb0765277b78d28cc3ecb7aa8c9e3b27eaf10000000000000000000000000000000010446583a905864064400f9ef168a122d179d46a058525c9be8a65a5d2ac5e967d51185d4964f81a5571123717210d050000000000000000000000000000000017da91a1d0358271b11a0aa524341ba1ee8c31bed15efc4c9183d60c6e1842ec4383070a09914fda991a63d55efa8f2156be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef400000000000000000000000000000000176c64efbfc9958b9c8e71b55e9fdf525d4e5a0265ff01ba95bcd5c6093bd063726f8e277d00b138fa4d8c8f80afc4e200000000000000000000000000000000183eaa6c3c605828852ab5e8a9432bcb87411dd18d574cc2491f1a280e7a267ff9ccc80b06c22e95107a72f22ba2fafc0000000000000000000000000000000013319d3a8564ffcd6fc7accdded740127ef205e8299b390d21e96b2609cbb463569c878f36191d43927868b06dcb912b0000000000000000000000000000000000fbde0ad8e89f5458007ef6ba0f01d0aba04217e06745a5571eedaf544443150f59117b56937f533b4974e5d57c41cbaea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef000000000000000000000000000000000c044a5116e175ca1d1ae59d400de24e4f47132251b4b3dccdf458623c36b4d3d83abc644a2247ac4d0e3f195d12e7b000000000000000000000000000000000048dff6bf65f158b19b992167ff8adb5c858a154bd68bf0c84e41351bf47a8f870cc735d1be5d9afc62bbcda2fcdb1c20000000000000000000000000000000008c5539746d2610eea22e79b3fe5b33a47fd3bf9991d34c6f9d824a46458480b735c0051d7b4e4909fdb1f2a1a4e4b3a000000000000000000000000000000001936558ac97acd903a29d07c4aea399227ea13fd6dea820813c5519412c157e1a477fcfbab60a787c6b3834eac4522889ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea0000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000908ebe27a1bdf0b9e56325c00ea3814527005793ea97eafec541c01cf2d7c909d2521a5fd475589a31e297cecfd5e7000000000000000000000000000000000017e3c40c60cd369ce5a90f6c4aff14896cf73fe06432e71940bd8086e36c2353d6bf9dd414bcf92889887e2d49fbbf5000000000000000000000000000000000ded856e5b2b139487b3816351584f06582a933af2bd4573a89aab0a41af01ec1cb928a7d8035228302032d399bc7caa000000000000000000000000000000000833b77c5d5c98ad95a144c0f167fd3bd62b03f4ad721561ed1d84c7137dcb19521f781bdd3ddc22afdd52c75146e101f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba680000000000000000000000000000000005c6f28c5ebd981fff3aacd70eb18f134bffdc8507d1a3aa153e5787b68fba7f4a94c43045d2676aaa992754783ae87800000000000000000000000000000000148ff39e8062bd488accfead42a684f781c4ee579af6204b5b8dabad9022b029139b1f3670fc270710ced9a53253850c000000000000000000000000000000000ff50eca1a92f123e2534b3289f37ffd5d4e05f7678017ac20e35c2deca054dbe376c5529cddb5e58973f5c60914f251000000000000000000000000000000000b58298ba9496fe32891f4c1cff25395ac5a447205cedaadda4dcb929260ee55781916ef5e4e39793fa2831142111226d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a00000000000000000000000000000000184d156f881f7d10d2f196b7599db85ee826c9c95383978ed68918756f642a2ed1c951503251b0778dcc39598d79fc8a000000000000000000000000000000000952168761380e8fc90a4966e94b8d2b88a784f6e607c99d9af1aa902506f59d6879153339fdb7b8acda178b9bce4ef90000000000000000000000000000000009997621d4e17c76b7798ef2f99d3c0a7519cce278cf718789cd8227b2b1459af7fbbc93078aa0aa361167b1d1c9363600000000000000000000000000000000005369eb3a77d2e26f9907a2d930f39dbb87634346cf10525733aac8ea10eb918d4043d2a05ff8e80b9c69a670e17f15c629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b0000000000000000000000000000000019bdb390c66f7d28cfaa91bcb34c5c55bf93a9f2345ea396f18ed33ff2221a39cf68c5514fe091f7882e82470efb1fee0000000000000000000000000000000002d0b48d2c0377b0dffca247b7625f9901f86e2161626b4154bc25d6c643a48e9addd260298bedaa80e42caa5b9fc5b10000000000000000000000000000000018a2b0a760652e546eeb42e857ca48f59741eed91822c17692e9c41358b213c82537c9c6898713a13a241cca627a7dc400000000000000000000000000000000079c02f41fca45a56d9d8e305141b4fe8f98d102197e7864065d342e6b07f65b62632e0c12660f37de4d698c0df3d0f3c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d5000000000000000000000000000000000c0448fd4ebe9b5615653336fe0a618fa281b0fd7d72a8f956a5fde84f7d356b6be853bf823436bc0b61a603636db9ef000000000000000000000000000000000dc4f2b4d810c4290e263098576cac393fce137cc901b3be23507cecbda7d86d18022cf8e1a7df4b1298520ae5c9314c000000000000000000000000000000000a39413967b558dd8a6b2bed972687d984fb9abd0662a266680f8c90f1897e2aca1ba37b41d7d3fd47406bc5fa3c5b7f0000000000000000000000000000000000550fcbe5bb75afdd8d5f387798a8e83a8dbb6da4918c24eb2e5d2d8acd3512f6649a4ac9c8d3e6794e6f4f8a87687bc685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7", + "Expected": "000000000000000000000000000000001654e242002aafa89c6fdb9e8fe2c197ad2f8aad11868568dd39d68ca35919f94308a80303655bc83fd130de6f9723a900000000000000000000000000000000062b5a064840a5a28b4991ae949f9508586447ad5e8c463593503c0e5857c5233b7ce7ac03e555c2675f2e320e8cee6a0000000000000000000000000000000017d65fbd7caa69629f66be8b201f53baee5ef2957a3c04fe384ae82959105342b52483eba6bcc1442763c677f515f6cf0000000000000000000000000000000002ef8f8ed1114cc9d299e59003c61d62edf8971d65b1b621779bd7b270c4123eb629f56dfa2e2723501588a0caf1847c", + "Name": "matter_g2_multiexp_12", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001392409b92282bccbdaa0268e1173e60911754eb3cdc28a52e93f4d82ec99026f314dfdc59b39a4f988100f9c30cbd1e0000000000000000000000000000000016b3c555d5c196551ba715c6c334a668bcae80f5a17f038038d35dce34843f79968a90e2102f0faa22a93d3240b58d490000000000000000000000000000000002daf83727fdf45dcc1a15adf47de3f8a1724cf4d34116f52106a9e6b22dc24a288e89b940cc57e5a6bb87ee70f680a5000000000000000000000000000000000446009fa3555e4a056a820efa7da52117c15eb105af57985d8e9b33b0b22fde6aef9bad30480c2b8c1246519795f61fc067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000000372ead514d53007690843484c966361661816e0d3949b868176d7a9bea42064f49113a74f2572a6dca7afa0642fa5000000000000000000000000000000001199d3ea66fad87074e62a0b77d3fb962db17dd948f30c38f5beb0e44e1cd11d9172b878128e9a64a08394f13cd786f60000000000000000000000000000000018b7db157bb326ee2f72d4df2b1e0ddf0a90401ccfca1d4ffd6379c62acf5d6e4176a23ded2f81653038d56d848b4fbb000000000000000000000000000000000a932cc9740812c8bde33b68d94220690e0f55618b7e51d3e3fc29d0cb9a8d42b8f8e1efbba5984c3c1007c9a80fae408b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d0000000000000000000000000000000015798d10386f6d24caed3859875be5fb1a43ac753f725f28da6b3583bd9c0e404d36265c2305d7d194e2ad84bfd2bafe000000000000000000000000000000000ef2ea5f3b6e03e3c9693d6db60019f2efa4ea586bdb7623f03bd035c603e8996ef2ea7cf745aa31f60679ca04f93875000000000000000000000000000000001792a66785a3087a80c4b8652c1e4db8f602cf75c1a6955f480a977f92ea262965dad84061f6045177c831dc4a3bf8400000000000000000000000000000000006ea3862318974d6347639ec0d70afe748f4edf32b9e437fd98f38eaf72168a153cac180c2d67bac8a358e3a4d57a2b32db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b00000000000000000000000000000000000974da7500df70d888d5876e7c61bfffcdf830b49bdd40edf65a2ff476e9add35eaf9451a2166e9781805192ffd7ac000000000000000000000000000000000cb2e7152b5b40758b18caea356dd8e095f400282881207c4b79d10d741756e526be261b98b726d5cefb668dcf73a0a00000000000000000000000000000000014aeebb995d464f4d77bbb72f15d9078936b5ab68eb8022bdd97d050576dbe46e6010eb72250c8ccf2a59138efb38f9d000000000000000000000000000000000cf7162768e8eb50e21d3c0a076c7bac4920c70f334336037fb40e57e0efa91eb025356ac3f0988a6b127408a02eb53fe2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000b1d6214796b4775c2b50e634a549ed104e6ebc0e032967b17eece6cf88c93aac23059f263faf3c3f38463270320135c0000000000000000000000000000000013ffa3894a36226664ff53ba9256d39c6312303f5cbda6847b4f68c56134b7d731e74bd711014fe374f909a081a7d02a000000000000000000000000000000000ae4590cdcb1367392635d0f8dc6b9557abd16290fd1abca6da354646d8585a7c9432978dc616e5fc38cd71d55f139c200000000000000000000000000000000124a7b5574ef52359b4beabcc56d3286db8c8fe4ca4718f75da28d89a8a95efb878c18b48360dbcb6fb50a9f18f0d559aa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a00000000000000000000000000000000148ab0e847ecac963f0156da025dbc52e765cd8827fd55ba2969da6775649529226ab13ab8537ad0b89e8f1ebc8648ea000000000000000000000000000000001395b1adb6a56b91c3621a4ac5886a7b13ec00f1c74d5317eb74a766eae655e09e269ec48cdf740abc38f4d6fe52dd0f000000000000000000000000000000000f70f77f07ef2909033665bc05cfeea7df6ed55f2f0b1b87d9f247b6c07c7e22f516840efe68005c3953a2702573a9b400000000000000000000000000000000166a334a711416cab180cc498308487b281711f2d1b832c410ebb4c591af54b154fc8c8d7ac9a49a241f7a3840acbc75e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa0000000000000000000000000000000008c19bcbdc2ef26a30dd88f3e35dc7fbb3c81c0224cbcd6b12c90883f3973bd7089636f997e5f213fbdcb79514c551c600000000000000000000000000000000058620cba8ed5b738167e809cf71392aadfe8f384a4cf397d10f674cfa914e9e02bb1518e42f16806214fec52d880f6100000000000000000000000000000000048ac1120d26e4173bb33a58c0ce86329cdbe9df6a6f268c8d5ee4f1d6110f9d81cd50c46256198a2462d50be3e781270000000000000000000000000000000010af13ba791d554720f5075d46d03b55c0c1dccd679cef5a7d439ae868d3ff2780cc3ab151feb72b8b92905a205e630449510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e40000000000000000000000000000000005c30a126c94b87c54270d0f23a486c3b36a8b491bbd805ae0d5f2bea818a87ff5aaed2d5e6317b786ab5a23f1cb48da000000000000000000000000000000000eb2d4663eca7f8433f10e84984781a57fffcb8f9535518721521ddfc7a4958778915ea3c57bef399a453b8ebc10befb00000000000000000000000000000000161947f57d97a858e5b3e918dbb22dbf28629e51e81335a9bf105d0fd660ef80087c8d69d8db9841cc69fbb5e7f81487000000000000000000000000000000000c52b6a559928fe4ad984a0569c081f3f71eed3d5b0d3c14d1a23afa45594e0fbd94143348390bee178720fc603145ab713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000071aa47d392e1a7787b37c52acedbb4632d5549fc11b79919bab7d22f1bbf1c3a239df622b8824b07f6e35e627283b8500000000000000000000000000000000198e72e05388021919dfc1b2a58ca72bf7655cc6c9b62abe3b45cc782ccfd4a2334780e451b8a6b7c311887036813fe4000000000000000000000000000000000e20cbedbafd96c42612e146debae48c7fab4846b20ad0848c4c42c6aa0603e72f94dfc938ed9e3a9886d221ccbdef70000000000000000000000000000000000c861d1878e63e313e672bebdadd3fdbb691cff5fecbc24da895febce2eef0a3c774a8a9d751498e4fc8e2b71daeb40dc040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae40000000000000000000000000000000005f7cd2205fa2e17fb9896efe3fbe110e1fa59db1ae5f8d6b5f4510abb4da867933d4fe3caaadc4457dcbb35f1b9c62b00000000000000000000000000000000126f2ef6022a7211fa865c1dbdd5b84d96cddff424b06647acc462408f2d31f34ce898d76e1e124db7c39e08dab0bff6000000000000000000000000000000000987f916ad6f718695f3c40703c59ca93eba38931b45d7c33c64c9f75556f075b744dfff8a5f21489b3db6c3846ba09e0000000000000000000000000000000013011b8c72f3853738e22957f742b05ec428ab0da28901800f787b7c3678449acd0359fee93c40c69623aa4acfc0a81017b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a00000000000000000000000000000000014b88c0586fa18333ab11a79acab8e12c6257f82a4ed16d929768a60a3a5d780a22101c32ea9b0099aa2816f18a0351a000000000000000000000000000000000de0fde69efd2cea7ae08d6d2443883002e0b4e11da253222429f6ecc67ba8d282eee84d7f46e0ad00b039a2c2ad226f000000000000000000000000000000000aedfa0a5a8b7577dcc1094469233f8b07e6fc32af26841894d498d70c6a9a046ad636086def948d21e39833c5b6c5a70000000000000000000000000000000010ec6aa0efba4995582585bb67f997f60741648156324696312d17656baf6aeb3e2db0d1a272912fab2fe81d139e971cbd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf2575200000000000000000000000000000000004a847c06abc8ae7ce6e6ff0ab856889dd3e9697a75e3cd4d2af9e06d4c2fc48c0562289348ff52f4d9855ad03d83aa00000000000000000000000000000000075673bc79bafa9a64de6bb0e9dd9fa29cdc9c82e90a7348593eec673cbbf22b1eca436ecf767d45852ed888a3f23949000000000000000000000000000000000f3f8543d1e667404b4564dddba4d7c11d13881fcd8ad774c8eab8fc599f55147c353cd6e163cd7b9d5da55ebc13c2e800000000000000000000000000000000069edec7e7d26962d88a89dfad213daa36046bb2851e5d67adbaa227220f29f83ea67cd3747e6724f148dac28308604cf221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000c945e83822896974116663d3e2769f3df5a70d55b8392c1f6966e330951f3cc5688742d4588648a6988b928b9fe00100000000000000000000000000000000003e94b7ff7c71d633ce69bb44d0ba1bfc7c27a5ee618e703aef81a45ad61771a2fa8e3dadddf7c8038f1f65ad7513801000000000000000000000000000000001727d768c1b51066d2af87a9da3e24ea2a75b0f75b8ece70727f9f54ab77d841e7ae01c9c0760f4186d02a28d6f8ddfb0000000000000000000000000000000000a273f9395cd49b646e90fd2526d5c93fd46c7366b715546529c9edf5cb3d274c9947c21a03add3e7b20612636a6745ba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e000000000000000000000000000000000c996c16a16879bd3194ac366bbd11b5863123ce6fdabeafe56407600e5d49c92ba68ac1256e1515dc9256de14ac26de0000000000000000000000000000000018c584d8a4f14900b2fee70b50b700199ec2372b731dd1380f42ec7fd3d01f0c9a007554059b85946c1c4f4e2fc504ad00000000000000000000000000000000073d6c7d671762e5398e4c9d57f6b68c3d97dfe0d01783f124256fac236f03b774db58b79cb4d5558e1ebf18bb9e19680000000000000000000000000000000008eb2b95e17fdda916b08ff2819cecd2eb031f41c8299b308339b7d9836382ced75e8eb1514a70356882d3a43227a9bc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001029fc28cd502caf3ea3619f6fd04bf457e6a452b5cad680ec2d4f8222a5ac2daa92b880bda76016973494e605ab28c60000000000000000000000000000000002c672c7571b5d8e99de6e47e0a2eb71c6d9bd12baf2b083e6f88598b32c4644d1486aef582c5936e622058bb141db1700000000000000000000000000000000033cda383a77d5b3adbb0809e834993c56717f81f8c66ad2d97f2b298d5a46f7b29a74d35da09271b7053a05af096393000000000000000000000000000000000132da041c6e3e1d68bbd2223f8531eabde8e180b36b2cd0ed4fca248f255cf3eeccdc5f61e1c581ce54edcfb2b73e0787eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000009721f22bc49f68d703a4dfccc3bae791caaf0d73892bafa6e9da465ddaf0fb1a069ffdd55306acff2407da64c1c5a0200000000000000000000000000000000056c0a4804a19aeaf1b4fe52064e43de8e5d41a8d77de054e2cfdff078eaf468d123d7317818d1bad1bf3469c0070b680000000000000000000000000000000007f1f318aed043d9ad7bdd53eb6a8c3167240fca75925b04795210700463c93a66ed64851195df1bafbbe4227d7db5ff0000000000000000000000000000000007b8945e258311e7672e842b91b540fec9ef4a79296956a5cba3749c0ad95ed83d7b0b48384ffb3188459e997b86695d3bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded", + "Expected": "00000000000000000000000000000000086a1ab4c19c27f70aa422e8292752c50b365d6fe3eba21e8f2ed51f283df0446020834ad27c18b5c7285d1156049bef0000000000000000000000000000000007288f40fde69bd350ce1f4d0f68e645f42de319cc032250b76fe4fa305341e244e5b2366751d5311105e3ccd30e701c0000000000000000000000000000000011d0c487c4eceaeac009b694931f8eafaf8eecd6028f14a4de33d2940bbb747025eecd509564721b50b7186910f81949000000000000000000000000000000000366f0c901fb859b4bae006fbcc9ec7e456eedc7366c899f68090fbd457c37b03ab99ae982872c7888b65c1a056c134c", + "Name": "matter_g2_multiexp_13", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000072f3f03bb09ca30239dd8302b05e0d9dc4e43ea33e865864a82578c35eafcf6868bf0cd9431b92b76f00990b780ffa400000000000000000000000000000000170b76cfb7944ea5ea055aeedaface3e8f0fa4d0ff657fb9d5311f3af6e736da84a5e2bef5188e20f76fb42591267fd9000000000000000000000000000000000d85300009165a8da9cb8e590f7f8d372e4264df150b1551185c80e49dbedaaf872ef69c5763fc3713d0c087c89f21050000000000000000000000000000000003ba59b682174ee61630df95c8e2b1c48ffc8f7f8508c21f3bbe8f7bb3266521fcc06c8f90fe5126d872707872db6d59f44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000004e8f16480c2f080a13b9f2b66e6480132d76c4ef76e8bac995a8e33280073ed5610865260e154b32f75f527d89620b3000000000000000000000000000000000f9ca48d732a8055d22fbebf3d2bc1e1c9c815c184f594ad2337731709317ea6a205478ba05ee9271d35a19dcad4db5b00000000000000000000000000000000078013b9290284e7ad528a1bb9a2a64b3ef43964c7226ddff8ca16ab17b4a2e8a2a7d921ba924a718587954f586a954800000000000000000000000000000000004aa76bb1122116cc0c04d65265d8652f08b411632a732a9e66d7932801b77c4ad398d582e446968f7f4966e9167894de25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea000000000000000000000000000000000145de5d101498bfc7c57830eea2931663ca1165ec85b77654c866b04ba6a28bfe710c1aac9876a68cc6ca119708eaf0500000000000000000000000000000000096f9df9d5723e8379f2d09c76a3fd059be47d2c2ed8905d333b2464f72153c5f50b6345980626358839ac691c26c967000000000000000000000000000000001788ffa765c19758da6eb6c38e793190c64d4a7b116576f6827fc090b0f65304988f6a95cf4397f82b7691fc43960ee8000000000000000000000000000000000746e040d7aafdb06a31ba3d7b590dd28f0678badc261a93dc7bd9a605047ec67ba86b2b6dd72637a449872674d6b5982e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b21000000000000000000000000000000000d8f3cfe1cbd2629f3899313cff16ca3d8f964ec1cc0508341936a7b3b49240db1116b2c3de28f9bc45cdfacdb5fd98c000000000000000000000000000000000fa642ed31293e44211b34bb28bd5b389ae6d0510cdab46c89756f31795506fccbdacafdff21b0127e80557e5ba9afdd000000000000000000000000000000000715a8951cb358b0d8cc63377799a9a61ecc85dac795d726fe60e429d492c9ca843be2a2633c17f830f199335e5d7741000000000000000000000000000000000b88a23fdac7d35fc135b45d7565854bf010a75f072b32c57ca4d0979c111aadd84c71df6792dbdc8e975ecd46a15df2e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a913000000000000000000000000000000001171be5820b5a19c045abea399f2b8ab9905d2aa367c6c8c0f84eac132d26150b759a9c029414f1c8f7e4880214446c200000000000000000000000000000000147f0877321f2709183f0b617a7c5ce898db508a3ced4148cc9f7af011fe8040e90885ce817aa956d9f5d19dd968f6220000000000000000000000000000000000acb005c11481b214a17e3cca02c2af266e4c8cd928e3c4e221d866e9f296a2e913bf34c4e051c7503a5e4e7cd7449900000000000000000000000000000000125f45d0af1c010cdf8438bff0f406007853e566fa646df40a581f65496197755eeebaf4f0f77e1e936f399dc4c6c020e6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c40000000000000000000000000000000019cce3f872af5cc515ac4cd7825a5318ead5b464d50349909a70b415a8950206974ee0d4203f208d8e6d14690158f5720000000000000000000000000000000002e08e8accede11afe3e2d085f35c08d7d414c26a9caa992d5a090a43c9b0c0cc1471f3693f9d342a973da65189c888b0000000000000000000000000000000008a984ad2ca60c492cff2e95d541d71e33b269b10d3df107c0513dad5af511c51806068da6cc7226df1cf5e5a2fbe707000000000000000000000000000000000fcd3ad75bb0a5c046cf83be3d973bb3685bc717d7b8262fb8205935db6e632472496907f7c965fc6b52042ce69999f9e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f24350000000000000000000000000000000014ccaf7594d8ff6157f9439ba63480d3d07f44e62a86caaea510d0ec456cd8c6c4b42cf9e38713213eb4942ed45df2ca0000000000000000000000000000000015c2061c532cda006addd2fd6ebbae458197d55fb336f75ca7decc05dc6d421a65495b71ed11874aaf24a0ec13a7c65000000000000000000000000000000000101f953aed7f23b5b6208032f05b818e0147079b7764aa3134dd9e4a316bbef0309ac378ca3cff3bdeab9ca56cb78e60000000000000000000000000000000000c76a2bc721a4d3ead95af79ec24be9b7624bc80d7debc07e388e52ec621082b9a69f48d157b168af4aa73629697f784c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000761832bb5b530b80c668234ab5996bdc225c0c696ea07dcc61c330320404827ada9d58d658e230fcb39a96b339b830e0000000000000000000000000000000001198b85418421d96ebfbf436193b411a3a89c206d006291bd23254ed5fe12ccdad15725a34d962005c0ae60e202bb86000000000000000000000000000000000c1d7ab83b1d2ad57a407e248492773a357c06b83c16c6ce1490e84bc4a3cbae395f160181d2bcca3edc34b764754ab0000000000000000000000000000000000f1e9f0cf96d7671763739b6c37fd442f0e816c49d9c8e001d322397e9d6741dbf8769ef9eb83d08ab024294e279a02838cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d147400000000000000000000000000000000063049bc3282934e29f3bb3dee432bdad6193a5d2247270e88887cac565f4b986e1b3b2af5387cfca64f0d50bc0ee1640000000000000000000000000000000019f0f05fc7f8bf2f0b8ed375690b53b6dafd0a07c49fa55d36e040798334700a3aafc4995bb90de9c4dc0e077ee18b58000000000000000000000000000000000fbe702d148609dc8feb3ac11c5eac8e32a2f7221aa135cc33a585e9f4c97afa1658d8962fd96e26e0c4c1d5108229ef00000000000000000000000000000000061fe418d3b440e84728091a4996119b515118900f54a6f2da2ad5592f48ebc17bba50b59ecf435de3cb892a123ae9d18a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f80000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a4553780030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f6000000000000000000000000000000000549f6464b657eac28f838c6a8bcfcb7a189d6b3b9712e19c1a23503ac209da5f2ad4df83acd505b0231f00eb88515c70000000000000000000000000000000001bf4a46dfdd70542e9d8cd6d6215174cba28f9adbff31c02482ca38205cb4afa2f7fd65ecf57b39e4ee5cee320e33800000000000000000000000000000000012d04a693d565f96566b7c313c47d272fef0ecc828493b0841d58f6bf690a77cb72824a656442e288460ecca7cf05504000000000000000000000000000000000b33eefd5df8b098e6505cbe655a483ab5c6e417a4ed55420beab95e8614c8538dca9296a7848d6aa0495a173df6d0b80d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000000fdd9702ed88aa857254c3ba50b484bfc324e583659c57055e4b09eb1662af2f70b547a1eec139193a0d3c75b565d3b200000000000000000000000000000000193df0fbc5f24065008b5e98c4c4bf9f1e743a6ee60c3700ae4a9108639e540384eaf1f9d7a60b8b6a5d79e1f34949f50000000000000000000000000000000001022f8a254d17e448cadfad35b7a54dd2fb319c8f9ba219874bd8280a5077301ff4332d731a75646cd93bbf31331154000000000000000000000000000000000ca1eb350844ddd0a65a4ad56e1a96821de2c6633a4a45be976577c223e367853e2b1ecf2cc40b8595ba5591ae8e40f3969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000001f9cda056a0f8803be581634562e975223b5311f4752b189cb6bd6df1ca5e3824bbd2889b9b93da59e4f08d482734240000000000000000000000000000000009f43c25de25c5d76ee1a03691aa434de6a063bb3a1133b045797a279346fc938dd2636abf0c4bbcb528c9c28d3105c40000000000000000000000000000000012afc29245da8bcd3c0d96c4ee61617cd9ecf42a47c2ee822003af26aeb4e4de8e432ffb6b2d8241090b814401a8676100000000000000000000000000000000053edfd98742dc70d510f1836fcffa6a3ba9ffd4904c7f5559b48e49dd21071401362d0b39bc0d786b7ee2e84a76af0d957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000001dc3f016ea1a74ae50c21c1955ca1eb4a911026a1e72b316c7bbdc708caef63f0c1efecbecce8901d65bbfcaae429da0000000000000000000000000000000016ce9301888808323c9baf6402d7073fb85ebcd389334cc69d7947e345748ee44b2d6aab3ef818beb21b54a19ae4f5b5000000000000000000000000000000000c49817753eb6459cdb4bc737d3710b5f044bc544c8d92c8ef138ec9d83889664267e1a5691f4bc3fa235ecca2a973a500000000000000000000000000000000074a8450e35f1da18e6de05960e21b7059ece8972c36f000bba9e24488730a44ce3ce200c437e06703addb3b442a790a8e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d5000000000000000000000000000000000bc1f2e9af093ae8235c93af098e692e697ea0ab4c8f53019a6e950f7072b56d5eef6b3237710f1dd1cd1970668d06d0000000000000000000000000000000000d9a63f7a13ff9755c6a3832e3c4c852919514523092367fab7886cac317e564d57fb4042ef40e696edce868e697c45700000000000000000000000000000000129a30657466460db13575dca367105c27d631eead330319b084adfac591f5b3b94988925d778e6d4645d1d2816baad00000000000000000000000000000000005ad64d6e761a9a301589547929f4952ccbfead278cbf6658255a075966340f185d5f356679fb02ff2197468ed7de19a71a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000077d1e5b35c224e2cdc849c02e800c0b80d1c19f3d74d9eec34c40f56bbdb9e2b5d2ef274991dca843755f91a50826fd0000000000000000000000000000000014f3b653e0df0c608b75dee3496a7af04a828e6fc5604f16ed49c39686ec757e96adb0a667853006a8331c3d63ae4ec2000000000000000000000000000000000aae011375b337940f2a53d9091d3581e8197e79251b19c7fba01de987721a9d6fa694b7978f0abf877f46ec26147c98000000000000000000000000000000000aaffbd468a2eb86a3cff59e2e9b7ab88286d2bdd19c2e789b1a68810f0cdc76171a2661ab54e81b17643ff0275eafd72d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641", + "Expected": "0000000000000000000000000000000010a2434fd3150f6b9b491d3a51226bdd457504077ef2ed5a11ceaa8284900d1b84039a34d5239a863809369bf20a704c0000000000000000000000000000000007934f34fd50a98225fe6578d3f34ae5e5ef5e104bb9cb398b2ca4f09048ec39cf52e7fdbac48d45212e9e4c1dcc6e120000000000000000000000000000000013ee70f1b52cb8b07ad957a7565b3e3c56306392cf7b5aa29047b23e5b41fb3239ac3611bcb16ba7c7ffc4213e3d9cc800000000000000000000000000000000035840f8ecf56359dc3239945720ad08702b4ea8d0fa9bea3bfb234431df4618e960a1eea87da72ba4d9443f14bb87a3", + "Name": "matter_g2_multiexp_14", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d04bb9b75bc8078ccfa85e27d32e137ff5f05f9241b19ea835bba2fffc9255a4a3028c0caf9c32d3d27666e1394fe820000000000000000000000000000000013f59c3d8aaee34230cd7715a32e4a45487b9b16ce68d178f95461229a4d0fbe7d31edc7208a7338eed08e65847f8f29000000000000000000000000000000000d63ca2bafaa54e93ea54846b26f88b4c6749953f9cd00c670914cca279b794c1fb5e2664fce44b8c04f01c68698a8b9000000000000000000000000000000000b5188b4b7ef78d3662baa01b1813b4a0b0f855e11397584a460d56f594f11ff2e5d708a23a8e64d0ab337c7076872527740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000019bae57568c879cd743f7def43b6b994f29782c6a0c74734f35b97042a916da00daaea34f321481e6cc4749e23297c1c000000000000000000000000000000001853fd11d4688b027146a07edea647502e80750de4e5e2d105faad3f71ccc90badcc750f76f1b02db3bc0a1a635b2bbb000000000000000000000000000000000b1e45b90e6a7032179236f13f01ab664c32ee5728414ac0d6b9d79510e8c5bd0f5b62e6c59c1a3c88998bf45636cbab000000000000000000000000000000000ed16c2f88b5b8d29d7e01633e2876322caeb740251b034e5e898919f836ae73f0296c62253a0329ee8f71fdb5cac3a1d226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000001455764f99e5eb0e0371e89f88bfee1c43224b9b5202746bd151f72336285556acc5ff36bd8ff87378249e82214cc5e500000000000000000000000000000000007fcee74e5335d96714e4d1a7c6f5c211b1a460efa283e0d0578c6c1f56dbd252198eebf0625362973c40d95fd890d3000000000000000000000000000000000ede26cf87e604507230ad996788e85799cc07245cf7191a6c3cecf0bfd5747b3a277cfbe41252808df6da19f005de9a000000000000000000000000000000001855991a4dd78dfc6088e6a43a64b56c8d86a0278b899bc8a1979a40a287979dee567217b006ca71374156a96b79c176c133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000cd032a7dfed029af020bfa249e6adccaaf5bcd2ccf33736281c4fce9c6e2b2e87fa828cc20301269d8e0579ffb866a1000000000000000000000000000000000765c4d6c4062cfbf7e24f9772dcd812f7e707f2b0ccf9043faf10018326834934df121924abb74d736b0da47554794a000000000000000000000000000000001540fa51e4580ff73e58def90a6f19557dec3c8306e2317ba0c25ece3eb4f8c39beb57741b3c4b9b8554fd2597743ce6000000000000000000000000000000000d875c822d0ce50dd638254cd4aad5dea1443813689a940d72cfa5db9309b171299ca3d69b137dfd37f0b7538a0852750fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f0000000000000000000000000000000011c230689175cc672c25f3c56ef4eaf2bc5766ce424f6c596b40ab24fdbfa56a955205419c149058dffa4d86a48ad35d00000000000000000000000000000000078d493ce3a8038134541ae5f2a82b5e0590218a499dfd78c7a9c06b92307003fb62d6414d6c04b22f2877c3de0b65ca0000000000000000000000000000000001d53c22a622c5d91df934783f8c0cb7e370043ecaf99a0554987e6c5120a0e5f4ede023a9ad988d30d945a2132ba5770000000000000000000000000000000015b1f36a00fee95e13443c9f6e67935a840cedc7c3fb7833ece8e180991909922f59d4f4ecbbf23f16bf5ee7f0b5851b70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d0000000000000000000000000000000014adff1607236910597a951ae169a7f56d6a3b4e0f44ac63a247716bbbf61feff7865d075f79e4108cda6c0731fdcfef000000000000000000000000000000000d740b13885c268da876898b77914bf4a002beef5bd2a3edefbf366e45ebcdf593ec6d9ab21e983fcae9a0832986182d0000000000000000000000000000000002a0827e812e983898351d9f03f660317d41669b0fa378e5c7667b73df299ddc4a32a529ca887a53245d7e1f946623b3000000000000000000000000000000000bf09a2de1a8ccf24a8a65dda72adcb96535ea7235de87f05d27341738b0b4ab16afbc5b37c97e255118dea9bf180ec2ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d7210000000000000000000000000000000015fbbf7e8c26e2f41be32daee2c81390b9bc4413aabb053e3a88bc6117377bc16011e81ed167370b72f84f0e77c2b8680000000000000000000000000000000013d48a27d06ff00048b19879493a5f8ca52b7154be2fcb468b9de9edd1395750434b0e95ae6dd941e84fd6d8918455bc0000000000000000000000000000000012fd2bc91286dd46d68d87a3f8793db997ee684dec6b2de1c4202e5e7eb0e4a8a21222e3dcf80e1ae4a3a92474107d330000000000000000000000000000000004d8b71978c9025dabb3d1b1b3c7f4f13f166514b8b356fd064842269a36c6f1c07f150c03510af7d0913103afda4a68fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000007d679ac21bd4634b415ef8e0e3670a8a1d673f6a4f7f3786b92d55458af980b035e4dab165a3b773ff3469fdd9d5135000000000000000000000000000000000fdb82db6e1096e73322050f828ba41b3012496a4fc4cb481f11fee338243aae20b205ee06887e28f6ba6dad00445f9d0000000000000000000000000000000017e6894b48f60b3d9b4184d58ab9554851e285a1d445b4d97cb1a7ed5a984ade8b0f62ab11ca75fdb280cc0e526108ca000000000000000000000000000000000c03b61690cdd9a4c6c83d03749db72c8946c21a944fb292866cf3a2dd1bf3dcd95743227709740ce8124319d0a540555af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d7000000000000000000000000000000000289f850c4834153f36bfc4855f89e9437a172c35a856117f8b841e5ad4ef973d3aa33fa73d8dbba4b9b2101708006bd000000000000000000000000000000000700025f22c0460613c05f8941f8a79a4319325c37c2b8f099cd910df5c0c27121a9de0e40adc7ba0fda61ea637b47d600000000000000000000000000000000069e17e00d4d726e8eaca8235c88967a7c093c70e5a46b1863ad097acbe233554048838a0a486a72cbed7001c83a27db00000000000000000000000000000000016ce4afb84c1a9e0216f23bcd2dda0bbada6a4acca78e1e0d765a5290f6f4929f6d0eeaf1306fed3c9766ca7c7268acc7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f10000000000000000000000000000000018ca46a89dadcd3b54f60fdf9a7b97c95b9e0668ed9329bbe4121e588a1ba773c9d086dc35b699d65487f428c00ad8c30000000000000000000000000000000003ada6835a93310d0ada01bd7fd6778bd07e718d1ce05aee2b4990bf32322fa94ca898a531ec6e3b8cd7ae3bdc77e0b70000000000000000000000000000000004a8abd2b9f7449213e63ecdb435e5e13fe2aaa31a2c38673a6adb5e96f4dd383dacab391787f6c17579c78a1cefa5450000000000000000000000000000000002a8768d98ccda80149a767e9b5a3b0bbbc0ab4b5f696522c8f1c664f1d27f2f0a6690531672ba2070355c0e77095dc02d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a00000000000000000000000000000000048fb58924bd5952d3bd7b1cd57a1dae6c1034df3a420c1151737f88760e4b0e78fa3f891a0dc32fcb50f89e67b0f08300000000000000000000000000000000073e9723c80eae7685db774d3e2bced53a52f24504fc3aff98e2becf8d59c6e83373ed024ec1ca50101d2d613abd286e0000000000000000000000000000000003b64c8e9a1341bc6a444a871843b3add7dbf04bd1810e1d6da7d31c7c2b7a264c362ac9a366dc8d93bcd9392c6056f000000000000000000000000000000000064462d424e54f50e9849a2bba1b0caae966a8618fda0f8965b1a841dd2173872a44a18ace1e2aecc8e3546a9558d7013549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab71000000000000000000000000000000000c40c8da9281a8b43478c28b2fe59a3cbad0a818e2077d40cfe44624dc2e46f72d4489cccf63eb8460d02f895e78edf5000000000000000000000000000000000735d768f6ac999a47c88bc2f3375f01052259dc69011480e468d8963ea8eda74726c4ef32c8feba52878eaf5c0147730000000000000000000000000000000010adb3ad214b17b963586a10701934727edf05fcbdc94d98255632647d73536decd0c91363840e1b55f29f7d32f650410000000000000000000000000000000019349045e6fd25960c03336888679cb53409027f35a1f211b40d24ebf724866c085a978ffa3a91d989da1a7902bca018c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000a5d2dcc05e218b0633e0a965b6d69a3c6c1c7837e1fff7ff75cc9ee93a112f8e34cbc95bd9dd8fe6ed22f2e9221aa110000000000000000000000000000000017d2e5d2c0578b1ec26b57c3305b209c979bba6925756892f031a7462ec44e8a4a2527e6aa2fc13bae91dcacb8c7a30f000000000000000000000000000000000d437edb45ace50700db548db68b9e8376b3039fa00cb98dd00cd197c14d0f92c8a3945127c43b10b34bef7894fa43410000000000000000000000000000000010d5a2e442a2eb35aa85fdaecf094c1e1f307dc9bcc540693d7206cc4e0d050ab900f17fbdd0754b59bd2aae705c60149ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000016c36464b426c3066aead1aaaf65ca637e93279e8ccc9d838b9b3ff1aa7b896f36de506efc2b0864763cb6ecca4926f30000000000000000000000000000000006d88d5764fc854ed7d7cf1c0e210496ce347bd887da2a149a09679469e98c453d85115afdd2fc4987b64a88c4a6f0a200000000000000000000000000000000053edcc0ca4c205423ee6a7031939379e552bd2d2657f8f25370c9f0ea0a947e77f18b5f218f98d12d720667844f3795000000000000000000000000000000001292909190854cee4499faa602af99dc49d1354a71278b439e983bd89e6c504fa5fcaaafb6ea26dbeba9850bcdfc1f69bd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b000000000000000000000000000000000997e79a7549ada9ee0233b3bf9289df3ff797595f4b5eb2e7dda6977ca981c1c4a2b91b924812b95418f1b1d9d0cb830000000000000000000000000000000000256b830e80f238e8494387429d727a91cf5d323ea87f7dc143058c05e11858796adcdc677429d1db4dc2415cf23808000000000000000000000000000000000cab529c6b86beacc57c874f07108d1df7d98fbd59fce44c48afe9eb2dff823f4869b620bbafc121b4ead2cf244974de0000000000000000000000000000000002774906c1a0acd87de224a9450617db37f8f36a0a192f5daa2774eff0b73aa79b4804342999df761f8572974c697c6010bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000012abd02540073017011e186586023adfca36fae454350b2015a796b7991eece65b63964fcdf581b4b51dbd7ddd506ec3000000000000000000000000000000000ccd3f2d9280908d4b30e924e4a862810a92e1a880cb56e842a94a2a5120956e8713f548ca279d66d06ab23e4976e54e0000000000000000000000000000000000c052ed00fde2cab515694d8c004de910e62d07c462345ffcfbd3904a0171b970bc58d99c5833059315283004f3390e00000000000000000000000000000000008fc4860366074ec0c7aed2c6ffae7c93ae0a81067edd8911b4c53393ebc0f23243823aa7aa2b2e987cb510f6e0a55a65c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d", + "Expected": "0000000000000000000000000000000006ced307065868b6d082bd205bfbaea3b0a8cfdccf831bf154563b5a942154622b0d7689819b337479480d19aedd85e4000000000000000000000000000000000c0f04fbb26cf85c2c22763f3e78fe255d8d1f45ea47232ab58f5b785ad9f2458b0b28f3cdc25c4dfcb47d59957ae10700000000000000000000000000000000120e38740eebbc3eeea9beea483e70d6a9c30a5abd61b86e5f94bf65ffb40fb92c8d246edbeca425ace175f79c9c8afd000000000000000000000000000000000d5a503a26e50f9be34c2e64e4a80402ca6e17f09db1b334a9c1f6318f3e7e63b3847a7ca38ae6aa7c96ff94bf5de842", + "Name": "matter_g2_multiexp_15", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000036480931a5a585ea54b6dbb01759eb1d86804e3f03326188c71f859613722e662c453096431171a49eecf8653f14d470000000000000000000000000000000015fcd6a30b9d59a90d8595ca1758eed7d6810d2916638dc2cb637aa09b16b5ba4920df7d21fc0b923453a6c7d32f056b0000000000000000000000000000000019aa4d8e98808c2fc1273d383e836876b087ad5a7d01743bded01314bc62ced94052d75d312a18839c1b33faa9e2e5160000000000000000000000000000000015747ce0f1171c0d0ff1fee9dbb2e5673b9db0b0c3618cc8bda474f378db58ea42184f907593f3d6fc2fa215cabb7b2308e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b79000000000000000000000000000000000222640c1d64948daac3ff93e86ecc96bcf9c93559266529a37ef1372a81952431673d69f1220e07b8aa0a4f3164c83b000000000000000000000000000000000db593156078821cd0ce0270e8a444d0d204dce0583774496620bd4752839f3451e505aeb3db568048739c7e71d279b40000000000000000000000000000000019932ad2c7e857c2dd51f7846534050b9243e388260cd47a91444fa050a9154eca88ab4d29a37def16d4a11d35683f2f0000000000000000000000000000000004d15ec653a72256ac6b616e9870b0acc7d46286893c0eec523dc27bbcf5fe596204cbf83ce71c2690af67b3616794225e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d00000000000000000000000000000000180622bfa9a1c452f343ed21a3e9c6fdf76589cebfb9a3f0a53782a3e7c9d066294e10699c386b5d0525003289f0ec580000000000000000000000000000000006615ff63c856302dba6d4e25d1070fe873e0c4950ee5ba8bbbd4b94ceeb181f1ee450acfd22f21010b88f0b88375777000000000000000000000000000000000cfd3940b5eeefa92d775792affa34371d13f3098ede3007e06510344ac8483debadd5a2baebafb5ddcb45a9449768b200000000000000000000000000000000145be0107a1e3acecc89a116668f9887579ed7a72abed3f4236930edd3f18974465c99ada86c4980c88768824216170f1362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000820e74e6d0333b6b36590ebae78960d019065f1681ce68a2a01a2522496c840c668575a57f9fd0f50b87f928a41b0de000000000000000000000000000000000dee60d90e96019cf2bb552d016419e92dd358ff97039a61838b0a89ccbbd537f2b435cd11f7b6e75a4ec6675964e7fd0000000000000000000000000000000002ca767de9fbf8af7c73d41a07e1c0e38e3fc971472e11928b65393a27354b2d732012dc57f498f94c0b933565a7493200000000000000000000000000000000134fe97b24e153f0e9a27d3fe7b89999c6a19e353325e0746ead013198b8e00ca6472fcbd2a112aecb9ddf671aaedd9174d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de00000000000000000000000000000000031a9c29323196ef31030ba73827d228e56fd5209eeef0803a189e0c0e5b186ca1f342483eeac99e1e1b12cf490856460000000000000000000000000000000010deea45a01370602bf57a1f81413e8d3b337d7a1a33f9525e4ff7003454d1da2cfb1a9b42c4a654320f91fe7d04b6200000000000000000000000000000000002bafb7b7452a173a3971c2ba1768061a043307d2c32767056f18c1bf8b066176937876a87055e54675876bc1b2d2fc3000000000000000000000000000000000b5c77dba3b4136a7efaa8c2e28f39e88afbf26a7313b52ad6e390da4d948209d96e39aa08eb52200dfb890d7e88b46a389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000018bd1ea5ee8e39c43d442e9c6fd22706e582cd80051f18334c4db2ea91ab019f54bc0074c8f0e52e50367197a797e7520000000000000000000000000000000005c0bcd1b047fdbdff25b138248bf4da4c013beff7dd3030c348d6b2b8724a147cbc44d570db5c4b273c94d0b99bc2290000000000000000000000000000000018e033935c20be5940863f7e9e39fcbdc29ba031e58c10beea90cc48e9da9988fdbf108bcbd87948058f386928f81fa800000000000000000000000000000000107d179204db7b288315e8aed7b92ebfe53b7ad2366d5d7944b3df68d9d9faad023e477213f85214047645bc05fd4cde5f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000000a7b364fbd3bac7e2f2e7ee501db2d248bd73a76c2a12a3e51718b56ca9a8ded14b83b8cf0b5bd46f0c26896a65fdb15000000000000000000000000000000000eafea7128fe20ddf740a6396bf18ff5f2652a0317ea9b6e934927c3ee95b59c7dcd51f7c895b3989d40ae5f78ca508f000000000000000000000000000000000bdce57be904236a8df532c2c0072165b5cbd4103e9061fcfc0a45a67e4b25d11b9f816f63fc0eac4d6d3e10d2764c4a0000000000000000000000000000000012419f94ddbd8275054f8f89fdc27a74afca2eef314393236fca65705354e5cc0a470818999c96b5087997813823e9be0dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b60000000000000000000000000000000011e4010d0cd7855a92cd5d4954ad735363c0c2ab00053db5e078f34e772969d8c492892329cb95ea8893b4b7ff7aaa5e0000000000000000000000000000000013badc54d90a19b84d76b30fef8e3ad2cb268204fdaa50ae951b63e48aec9cc6d585751dd48e4a8d4659b835f38f8da8000000000000000000000000000000000460728f686b9b15cc19ef135af71312e174860284c3f0e7a84cf85a5c934e2bb6cadee8e482d88afe788a796605f79d0000000000000000000000000000000019a50c06ba307d83452a30fbd862270652cf5c7a09b150fcea858a8102ce3b1e9ec13b6abfb323d63d2c4edf209c7cafc579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b5360000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d69f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f10000000000000000000000000000000011982264c8c078518cd0adb05034761224e9063654904e06fb5e5a6eeb1f45e4ff3da661f1232693b79336215dcc0cc40000000000000000000000000000000010c96c872160d2de03a16e85f2828d0cf2dd16a3389effacce46b5b5eecfea1042a77de653da5a1c0380a84c435723fd000000000000000000000000000000000a4ad2d9956bd407c555b26c192c6bf59bf89e40d9c6f9c90780bba313a39db71a73e7633397d47a3f58f61c81edee77000000000000000000000000000000000a7f912530d27a7bf74e01d8e48890cc66f72d14950554991ed1edfc504062ff6bd3cb6941bb398df9fde3cefd33fc0676618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000015bc12aa9ecf417fa5bace8d9e5dc4a418555eeddde1da8b624bf7d6e1873ec4a257d5f6dfc058a8d9b02528e699abb70000000000000000000000000000000015b41567f8c780f83342449f27094bc20a839602ae482de14b92e40017e7acac8857db48a2d27f1f1a625883b6e5255e000000000000000000000000000000000cbe79ac0718555fd8fdc38b68eec8be83b32499d2654be44888e45a2d610b0e81ae12fd56550524ad85b5a632db32ce00000000000000000000000000000000069f46b5baf4357d8010869685b3828c0dbf6e2338598c9b42dfecf0b22d803f95fca716115f74c77778d414cbcbd881fbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a07876177880000000000000000000000000000000017ada4038189c544902167be958e43ee133730e5cd329e572dae2d853b694f5ff8032bd9ab41cddd11c51e8284970f810000000000000000000000000000000013eef75e6d28deec945ddff33128c199fa52565288d63677c824b8d56a6c29eb98d34c5834e84865be35d40c1c59a40c000000000000000000000000000000000e2fb4f9c7ba6bdac1d4ff5055be609abef7fecd7923a753a704da537c0ff41951552420bd78d14cf972dc84fa3f5dd9000000000000000000000000000000000805376b814b8a59435310d49a43081dd7ea36dc7dcb40d38068ae9085b3ea9a3b2249234234cacc76724d8ef84a2eaca0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000002288860f2d671c84c5239313b7f6b82e31c3976e6d310e15d3bfe1c566e2ab5d86ae6ed0df02530f9f7893ba419f1870000000000000000000000000000000017365bc096e260f8dd7b189fabe10eb66923783b41fff70a149251576b3b465c13230dd0af13cde562751dacd8298335000000000000000000000000000000000fa8eb9c818df27181b45a74b333ab481dc7212e417c4e12634816f9e177064f9e1101deff26156d26bc6574db9617080000000000000000000000000000000009379598bf02222e1ec37a721b9ea31a3adc33524c6a41bc58da06caa3da3bd730659f0a80f793a0fcb9c07b43ca929c2c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b00000000000000000000000000000000136870e08ff5fabf36410629ce5c23470eafbe73a7dceb633df5c1492e39445b86ce15c22bf4c421cfd0adc6518e78c30000000000000000000000000000000010aefa3cdf1225da09b796430d096807a83eb2fd5a58db3a4bfc5e500dcfcd472fea3077f0c059620f4ff708f37c95a90000000000000000000000000000000019ee2c62ff860338af623c535979ed31c42c0d0b2f82cd56c153e80e6d92bec9ce39bc8e8f285d1efd1c1e969521dbb50000000000000000000000000000000008ed69eb0a16c8a35d507bc3a50bfc97e18143fef611263715aacf5400cb1aa285b6d2ebf2ec219d2fec477360875a03141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c5920000000000000000000000000000000014abc4eec64f2611197d0c1322c3248eadb725049379e64682f2b3d7f83f7bcea11358d88f52711b3020924b6ddd84790000000000000000000000000000000009fd78c5d1d2043d83be30a88f046f5b633c6dbb11bab25fa3037bd250b6b9d9394327aae25d1939f777fea9f3df46960000000000000000000000000000000010f413640aaa16a95afba98660f9e1b03a8f3e0a7a3d7f2b971f71b5e3d09016ac2b410f97d20471f48621d5a363e9e6000000000000000000000000000000000154b5df93298a5a14a6157819e38db33ae7f2d11dfd13f7f2a92b2fd9b053fbd25f10a8c45db3026f6f583bd56eee0f1d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c000000000000000000000000000000000f1893df99adeff5e4042c4c5e8557e53f7c34efcb2a7953d5347f81d2f4a75ca0273a3845f54e795ac1c1f8ae7240dc0000000000000000000000000000000004856b05d58898be6aba07fcffe487dd895144c7ac8fa8bb1a37c61e73bcd062ff541d510e24c5bf005c8351d3ddf61c00000000000000000000000000000000178b22c2c698dbc4929b119474a741ef44d6275fff5ba058d9debe9475e71398e464aa14a6712c5deeb5010d1c7758ba0000000000000000000000000000000005ad09389c35c45f349e6dcaf1cdb3b63648b3df427ea0c2a371f45634635f9253957ba6987df4aca6cba4cd472308a31b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4", + "Expected": "000000000000000000000000000000001090d83d501373cf07c75effb1c85852019b39eb0d77226823aa3c1054d4e408e82fbf0f4420a30144c611fbb856748c00000000000000000000000000000000120a1e3795f6d5c4ed5b886256c611bdd209677f8324b7091cdd7cab11788b1c0f780e8b4c38b84d7c2ea528123d4783000000000000000000000000000000000d250df34d906ed421eec2a78c2ff4ed4eedb717358d7ca879d58ff5b4d2d72521082dba6ac5d10859125e32c2c8b490000000000000000000000000000000000476adaed9d80cb1545be505496222dba1f0ea85d38d5bece0663461e0e9d47abbefe95303c926db008d08b8aa162e27", + "Name": "matter_g2_multiexp_16", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e1a9066289392b0b0b8f0986366c2975463c9cbe7a74f2eafcb3b8b6d4ee3226ea5886aaae374341bc76b53b165e22a000000000000000000000000000000001557cd01b61b5f2361f6b558a87c67f2778e11c21734b7ed25f72a88cc62cbed396d583de4c2190ae6bbfd096c33bf73000000000000000000000000000000000eab68305118d7e7076043719ac1e13ecda4497df2cf392d6aae4b7753f114d30aae3e8535742947636901feac4b620a0000000000000000000000000000000002cfe5014446556b82d60adf874cef25e58eabd035deb4717c93bf0361f37a4a67aab70b95627326bd97f111efeed57f58fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000c8fa30f6055357f6b697f2115203428b8005ad03286d2b3c805bf3d4dbb461c30e6ee8b0973ef41f884b91e857c53500000000000000000000000000000000005e1c785feb4c4fb7e960233d431d51a4fe471f10321251d018a950374d2a686d52ee8cdd855a29e770bdc1bc565f471000000000000000000000000000000001158d31faab483832d39f5431a5d8aeb952d6a63b82ec019f235b5b2e5580df8cd91b46cd53d4a90b9db354b38c5a1710000000000000000000000000000000004a389b09be6fb7ffd14d7f3359b17991e93d92a1c0b9a89faceaf71f5ce77a1875aaeb7a0ec3b2dfb363c47dfc9875273b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad0000000000000000000000000000000010587118c5f90b545ee707466ea2c5f378e6795c260235cdf9876aed8bd753aac592ee05e23882ee77f4a13bff97f5940000000000000000000000000000000000a0344aed244b90c4fb9ac337edb01429e09f951062b06025a5212300f5471a95f28e09bbc715417a6d98423b518c3a00000000000000000000000000000000128457cf374e5b8864b8241f476da093f48553d609a5f30c0f0f235ecf7127231237b6c8802f2904a8304c7c237842620000000000000000000000000000000004d55ff04eb09b33ebfe90f2a0966a1b59cc224215c0359a4ff0c09e60f9fe7ad8342868184d8cfcaa1d8c28328864241ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000019e6992c3da47715bf379a668a15668508e7ad27bac647490be8e82759b9b79c996735aa1bfdc3cef217750e4ed36fce000000000000000000000000000000000828f782c5bd4f2de3570a4930db2c020f75f93adc98aa0e48449d29c7a3b0d5c349963d956bab7f985ba6ffe59c90ec00000000000000000000000000000000062c7a730d286e895c57b75907713ebf1d20650b5e621f270f1d22a2ca480d022346def4102a62eebe867210e4b6122e000000000000000000000000000000000d6c29462ad449ee6cd122e3dc00d56dd5caf17a2510e5305aecfe85626cf73adb401ec2192eb693158650893fa67412a691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000098de9ab41c289a05ba5a774eafe27d91aa8272fe9f81fadefba9a0cc0e31de20f808ff454a8647c44f5aa632742af9e000000000000000000000000000000000c96019bd5cdd62df1642656f0832ac8ff6aab86f671e18c1c7023dc16b8ff54a8e3e446b19682a23b73ccb90da2fdf0000000000000000000000000000000000178e3b4366b2517d4c19fb40551be6979d46319d7040682241b046f10ab88d269dfc097ae02952d46e69cb1cf159da50000000000000000000000000000000008341bfe1e2fb999f0c3f4e79523c720edd332401f9dfdb8dddba8d1342c2c1fb20ae2fd9dda92c7bde5a0c95ad971f80d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000001004d60af8c21f7c62fcba1c5c41b94fc77f64b89abcd23a218f0da8f47d2ae6879ddcde52f3e6feeae2dc7b2720577d000000000000000000000000000000000b8e8a7da87aa62ca852e2984b0f12b85052fdd03883f01f4496df0835d1cafa48818b5ff1e3cb0e9ecd66054540a0d40000000000000000000000000000000009c16854580ad8191e3e80a0afa8da759a8b2bfa7e0d556418b5c96d97e88a12fb75a91cd68c2f4336c3ed7ac99199fe00000000000000000000000000000000195ce9c562c460c7e715908991ea8b017b81561b45133427f63cdfbe8f65202bdc8e8958ab0977b3a244cfa32fb35f37c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000107292f77666064b7d80d73ea8f3b623170ef79ccc7c228b8366675a422a0cb8491586a2e4ab1a067c31396cd670a8900000000000000000000000000000000126f8136dd61d61b2a9c0f4af3ed44a3cec3ccdedc74821f341d200601a7bf0a17079c824de6cfe28467e843d0c74d2a000000000000000000000000000000000bcec8afcc7ee56b36d6d08b51f61454c8fb15ec5baee1117ed55af8fc85f68674250334f79b0fce632e75623dd173210000000000000000000000000000000016624d64660b63b70ed197f6a675911b02b0bc6f880348faa6ce4727af74127c509ce8535d8dc8db5ae2d71aa497e0756fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec5000000000000000000000000000000000f57747c20e1b3923c7e1d8bd7d877736cccc0e0829837a086d62d48cb54f323d90b57ca3339fe4b256df529bff11363000000000000000000000000000000001940327a1b319dc4212e7a553d3f49904660722c89636f6a38604d96771fa0fc71f57674b7aa710db4275822c2b89903000000000000000000000000000000001956b81bcf961d16e50c053ca07ae67cb8597138f34a9dad4d82e0e8d23a7e08b751682d588f229311bc63f9598ef448000000000000000000000000000000000208981064443e8c72987945e399b45b74e529a0bb75e99b7d6744728e5c182a6b0a10e449147bcb0b0cbe70edcdd845bffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf0000000000000000000000000000000018c35ca3a63053fec853e8fda5920b560f1be28431f2f4b08789c7a202336c8905a5ffffbf69ae4427f267b1e13288d60000000000000000000000000000000019de96be76bd93886cc486c2671b5b0d731b568638b1b830a52dd4c481b9a1fbe2b3cef14b46e25f1188ddb3c158da6e000000000000000000000000000000001813ab16a11c79eb3d3d47ae7d9a7c05401ee91eb1183266d23077ec4c0c8f3ac7188eece06876025dc3fe271d65d4ba0000000000000000000000000000000004d2a416dc874e956fd6d29a3fb96195019f4136561b4c127541ac171b5a6b229746af6d6e535a8017e64ce06709e52e57f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f4000000000000000000000000000000000623b7a8a1c24dcc603f01589e6679c74c4ed3452894e536a4cea69e99047092acc877dd0bb395b0cb693cb1702a64a00000000000000000000000000000000013de9dc75e42f12e905d729a52f25bb1a4125f5edb435734649281bdfd41083716d0797b0a80d842c2503d09cc61162a0000000000000000000000000000000006453c06f56dbaabd4530160bcd5312b8a148dbe19fdf9f1e44b7b047a73ee9ef9d981116d00269942ef73537885eb7a00000000000000000000000000000000075376135ff3acaecc0eeea32f8dc15add57e8f0297d053ffaa0fb0a8fc4418c5b142f96b6b9ce9eee2f949c960aed682d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba84000000000000000000000000000000000120d088fc12210c1f5f6cc3d1091563f9a37d4d0e0d2c305b479f4d7e893c4d5c8170eb164e34e4843a21c9eb193d11d00000000000000000000000000000000159de80db3b1f0ffc5fa8c93e1bd54cf8ae19cbc9018a5dfed86179cdbc976c1c312212080ab221806bbe142d496e7a7000000000000000000000000000000001103abb75a78220218cde4bc4c59ddb5fb647ff808754dda200bdf586ee9c47a09e03762bb726b085928ddcc998af3ee000000000000000000000000000000000bff4bea17eae0f2ff3e7f99bfa91e6ae8aea28f6f3fb6080eb644861defdefc26befbb7874f612edac0cecf70dfb275614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000012dde607a2d4452c6c060054c8adb6307743edea3ccb6ac34c275717f177f0e454d9e33d4391208198cae39d7eb6f6c00000000000000000000000000000000014cb4d8bc98060ee68a8ddbc44b83db5cb6d09f09b0d608357629251c35e44383e97058d0d68fe2df3bc47424a5dda03000000000000000000000000000000000c14fbb6c844fbf896fbd3cb3464a83aa4c6e9a7f0450ad96a07527df6f1eeeaf587f60a990bd6abe7aeaf5eb46f362d0000000000000000000000000000000001d9468774318ea711b79f16303ce86288cee312af296f1c9f607ef5f97c7d1cb48a7218775c8aef00c227ccb586286e7c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef728400000000000000000000000000000000181e7f8d0ec7a4a7858bc96b61484c24dbb9dfeb3746fd3a231a8e442369e3e83516ee6043b1c06e7e2043dc86f6c75e00000000000000000000000000000000184c1d667c0ece59f18fd2eeafc66f1ed530b7d5f4560a6c886429caa13255c63dea01c3e357e3408af58a39420a8b28000000000000000000000000000000000a8475ea694cf607246a1c50064cf90cbe50ad5cf8006934a1fdf1621ba38d20e70860a2b5aecc05acc60943224cadb60000000000000000000000000000000008afa03c2df8e83fb64523c57d0daa7cfbb7af6a4bf2960ebc64515a61a659b2c37ee661050cd538fa00cb34746a371b64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f0000000000000000000000000000000008b33a297c8f86f1e9d7166f9e905283c8e1581e582b879caf48585d0bca3608fe46d8d9f6e7c90855aee9d92283d7a40000000000000000000000000000000016962410d6b4b6f91437617e84bfaaba49de0369b8748d2e2dacb63b421e0d7de4514e7fd3e0dcbcfba8baa4915610d0000000000000000000000000000000000efdab72953b870d0e113efa7c183d99aefc100ce59791aabc72423aff70a5b74c577c06ca94bfd6a7722199b4bc22660000000000000000000000000000000013b18e31700987dfa4344384f9b41e72afe92c39bc961333cad3e7d0a5efd3842a5e849cff5655c4673f720fd0127dca35bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf0000000000000000000000000000000008b86f70c8d8b03b0e9a8975776d7fb0d08f95eded0a0124551d363c2df57124e0e89bd45ddd1cc75c258a4ae2f87916000000000000000000000000000000001120eef9eaff7c308b629deafb060d2c12b20b57562007fa810a2191d99fabe9c7d3c364caec1724665ef556de66b57e0000000000000000000000000000000007698bbef6dcea67a2c643342ab2a0f830c329fb6244d4a98512daa8a3c9d808cd2acc0cebbe3da920053ad73eb7cdc7000000000000000000000000000000001155b6beb28fd88d252c6b407bb9f55d22103257287ce77353bea580c90173b5c3d49080b319ea28817d67c52bead96f7980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b2800000000000000000000000000000000142b28509d72f9e3be9ee916827fc1a8dfc4ef7ae2b72eebad5db605fdb2dfa4492b50cc3e472df1b52baa6e2b0eff5500000000000000000000000000000000134d6821088ce4a8b42383d5a43a32bb0cdc96c85f304a2601292670633d5e231b9dc479d199829a9ba9f39c162318d5000000000000000000000000000000000636da344fcb0fe50ff3e22f8591418f64cfc722b2860b4a5047f973f42e4cefb93c2f8eb8a14b4d150758ecbf3cf712000000000000000000000000000000000e6fd06d5dca702cc9f199f7583add86c82f7b530d4dfb9faec36dbb669cf7c1cd1260c7e4f3026824eeb5b979e9fdaea256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca", + "Expected": "000000000000000000000000000000001684f447f8929ec0187811f66e985f0014eba46eaa87de2d4ac2347d10c0550e4044ec7792d9f315c50081dc2097ebdb000000000000000000000000000000000ee0c46efe930bc98f39dee8cc6a792744e84de4fadec035d25ee8ba82e1c53264d0885a1fb05b2b8dc9c6a1846c28320000000000000000000000000000000003a5ef98843099235a2ad9522c9cfce1908bef77b45794e7df9eb38a4854460031829e947a118e8160365fbec3725b85000000000000000000000000000000000dd205e195abef6a4cfa7da66f022a418235e1a1b2fefa6bd3ddf8a3851d8ca8c27652bf87ac644cd189ae55e3cc7808", + "Name": "matter_g2_multiexp_17", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000064698182f90c20ed6f6da7248cea32a291f901876a900d344ce4dc1b07822b480519cb8d891b5ee4f33b4efd90742cb000000000000000000000000000000000e7e9d2e79ec4b07015baf69a283f6a4abc8d7c1699f3356fdad6ea9b1c70e41e73bc14e500634d73749f9900eeb65f5000000000000000000000000000000000002ddbf40619ea5123c100e2d6553213e37883fb34f0f0f2124795dd971892d5c9051cd4aa78b9d20f196301ca9bb4d0000000000000000000000000000000017a07b32fbffdbf7a80f0437eac1ec5fff5a68f3b053482f034064992158b604bc34489dfd41a24ffba806ccb871fff15805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c0000000000000000000000000000000018b4de9c04fde8b708408efb3aa7f24b5f7bcec14e7d06fd5a5b36bab528e5adc0bbb1e378a5ff6fcbc95aea530ffc6a0000000000000000000000000000000010da98267770a47e5ed14ffb3dbcf537dd14ae5eb79522c772a7a2833be214690db0b4e86621de1842d88018fc0f348400000000000000000000000000000000135548e2eec9ae7c3d23618d8286db13a5a628fee04fb6ec9da980f3a46838899cf965c1cc6f562e71d5b5c7428cabc8000000000000000000000000000000001669fcee7804df9b7bef32e2ffeaf285e8501842efe87c9e827fce872dffbf92255d3c3a2fb5c382ab7aec0bba1ae0e5502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000010ed20c069bb300a27571adabd239e70b767af90b91c4d0e93d88278a6da47b7c12fcfaf62ac0a7b9966968cc9f3770b0000000000000000000000000000000017273eddc25cf41f2d7734a3866711e83d4f2823ee6a036942799f837d5ceff10dd6022ea25e3c1e28c7b14ed8f4e7c5000000000000000000000000000000000f201f314f66f6b2c6e1365c0fac7b187d31bc45b5edaef5243b5488e26581dee24de4a5fe493bee44165cc31d8d72ef0000000000000000000000000000000009dfbdd86633edfacad6b78d292141a1e653a1bfd8c48a96b2f6bf8271ed6033c0511628caf2ef258eb64cc8b63d8e5be7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a740000000000000000000000000000000005c4a4a5ffcb4a39c8809821ff275360ff937070cb97a791cc9ec45f429256a6d2d6127248b6ab0b6c71c30c4fe84ff20000000000000000000000000000000019fa60f481c5be953c9c7dc86903a89af0ca2b4205be3a00d793d6de7103852e147ebc7d983c6d6e8cd99e681241ad440000000000000000000000000000000015b3b2eeb0f81ff8a2624e2ff2396bc69feffeef62b1b6a1e73ca4b9e60506c2950fdd23a37cf56387b8794449d3237f0000000000000000000000000000000017021a69ceba3446dad9fcfd8cbe5b89b61372f57d43a8d2e2c8f4534bef6b91408409dfda9438f24526f7e6bf1f4240e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba0000000000000000000000000000000007c8f07f22a3412fb4638cb704751959cda4e42e4612edaf5b1f22c8f9ea314508353445114bab6c07ccbb4b0d0bfa6b00000000000000000000000000000000062d087155c8722d0102c8e5084f95f5f58ed626d48197297d21d2108ee05f70f16d595ef73e8e1207a3c0b013fe16710000000000000000000000000000000003b6652934f3acd4c91c6c521c2476bcd2594a939ff2e7ebcbb0f451fcf0a656a518dbd4f36f165f9b2f58054e9f778f000000000000000000000000000000000bbf21158227e0ad5461de9ad8bd580f9e65327dd4e23f1ad55618f6b0aec45aa6076fa88557953ad15d385a074bc7d96e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc50000000000000000000000000000000019e336d4d342f110eeeba9773b8e351f26bb56361c77fbf12fd9fc218fd075ae38b95f4a8a5ef830fc2cd92558b1711e000000000000000000000000000000000a112725046ca3b6cc43207e6b36f38d96ff98dfe3444d67ee3f4b0208f3b8543768dc9989f936637d7819e7dc5740fd000000000000000000000000000000000527682076572d8cca15e47a2faf62b129baad29afed22d32ea47983a8d0b138653c1353bfc6fbf9fdbec2efe36700f90000000000000000000000000000000007e3c5aff373b5154ae66f978fcd66d09cbebc7e0c96b4a4cf23c4fa5f2fa655410c7f1ce597a3f5f155017720f7c50f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed000000000000000000000000000000001865c265ed4606ed16056c0b28f953119751d7272bb33b9865eed312ba23b32d01733ad5446cea5873c2bbe37fdfce7e0000000000000000000000000000000007018aca1e7ac211921cab1cc6bb18874d2f39f00d916b8f3d46a088a378f3c9b49ab8a296d0aa21608f11b144a0c687000000000000000000000000000000000210561c0bbe5a9f4b2237e5bdf88bcd73326d395277deb2a883526978df90792993e6ee520c9d5ec0a6f7ef5c6b3542000000000000000000000000000000000cdd344124b7b5da556f64ac5d651a6f9b74427fd712007310d720f3236724e2284aab812d739a87f3a1bfe8737dcee7586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c53efaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e40000000000000000000000000000000002dd11f4dacf3d9c46579182df1c1c45a364a8dc1eb7aa7d54d0141306f1c23bed85235783a22b8e6dc4adc35f9193ab0000000000000000000000000000000010d1c642fce533039e98712bdfcda86eaa62d2d69b861ec4fd835488732fcea414cfb6f3f8414152f9d5398c73a74fd2000000000000000000000000000000000c6759b75b1e3fe86c00fa124d09c5b7438ad61fd1bb71695743ed7793f39b7a0fc99b055201ac1e3aa07ccec61b24a80000000000000000000000000000000017580c9341789484fb31386eccc9c344539a09f1c4421dd124b1a0ce61f2d0528942f7fe8df67c6b2bbf782996def47b39d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca8000000000000000000000000000000000d9c48a111c8c74bce8cd78d127999531e46a411b2f0be3507226766bc8abd088638a237674ac62e0fb7dd4a86d09b79000000000000000000000000000000000073675bb81e2bfe6adb5cd929e0b7280f5d60b3dee7f797d65ffbefc2c2944a9c7207648bb096f13292ff4440c3f03f00000000000000000000000000000000024d2e0d5ba1a804520c72331fa23a2a326d461177fa527473240dda130f4ef893870e893e1dbf7c5dbb0178dcd29b3b0000000000000000000000000000000002a4c9487485ec33f8fb347d246ab0d41b883bec30d2a5e88cccafa676569f25ffd8341cdf6c09f68afae442a574f3334c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a000000000000000000000000000000000c1965a745e42853b4d54739b2dc507d68d80b330360a4020e4412ba5422daaae313fb9597c98575c66ccf351e62a527000000000000000000000000000000000844439e6f08a411e61d37b5b2b07921049432e1833e839b00d6cc11227dfc8770ad9ca06037043668fe7ce3bf3ce84200000000000000000000000000000000152ad6fabde2e0310c978404a5244209a9363cab1f3ac9f71339cdad6d40c84f8e5a8a196283b581d0209ce90e1e3c6c0000000000000000000000000000000010eb6af62c7dba122b0e24e8326dc906370bcb4ba791c47630f05f657a228c20e010c065b93537ec84fa14a756b199789af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000001febb2cf2d664e4a277cbf08fc1fbacd05db415a12329f7be551ed56d67f0b5dcc917d1b02951657bff3a26bd8c178d000000000000000000000000000000000018af160555292b2f7ce27112c1d60038b564f5427d62604387de97dcf48e4473107f91936b5e8008065a1537f7ca340000000000000000000000000000000016bbad2a7f5451098294a7cab2fe10d206741a99b128dde5eade581d02ca849bab3662fc3400fbe055dd93a418aecf0b000000000000000000000000000000000b1e9586cc1b357da6e58621ce09288e62a79517144f6c6b867359251baad6d40217578d49c1501f23206b125282bdf4c72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b000000000000000000000000000000000b88892250c848e7bc7bb7e42cfe1048a1f61dc546929211846f49501ad8c7c8817f5b5b99ed092d5a2236d59d9c8eaf0000000000000000000000000000000011680c6549f6b7d9d187a6409d40cc26554df654083f1e8a47dde826149d68da756adfb1b65bbd219f79a10d8454e881000000000000000000000000000000000f9596121dad98bf7acb3fd65fe7e0bdc8924e2390341c11d9cc9cbb0517f988ff79a5e1d60bd89449b5f042f0d0b0c30000000000000000000000000000000008982832ef53bafc23ea817be378532b95b5872217093e7c7c2f4512d03a9c9a6dbb7950563a520781c7ae213fc82897b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000ea5bc2f8bc2b4088d1fed7090ba389577b11a3ee0775cb3f0657ab5b07a6709d3a18fa5fc33554dea235c60baae4bb100000000000000000000000000000000196b6259b06a4c91a0bb0adecea134c8609cf983c2c87158a69c9de3b6768510fc56543a84d1266dda78d90c3b0516ac000000000000000000000000000000000d0222d8ef278cd0d85dc8765fa7c4256394a5ef61f91301af6c7422b4cb17889224c75ccecd2df3ddc9bac98b493863000000000000000000000000000000000548809ce26cd498816ef1222d062b1ebb7313a07e99e3aad1431f984e9b8ecfd43357ea57da7e0c6c011c5d5400f7ba986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000000b95455351fbce6f73de0345a195f91bf96abee361908cea6c4dcde72048a13a9a23991a75b9c988ba0afd9491d15696000000000000000000000000000000000305f29b05fed06ffab484cb065d4852eb323fda8c9b7c0a78843bd7143effa95cbe5e50c1a0c3a9675bb5381709b6550000000000000000000000000000000016ebcb25f1b8e8d7a8f7131455ed2be084bdcce40034e7ef24a47fc29e447f912c20c7c9910e025aab975cd2c8cf1a96000000000000000000000000000000000d84a5de7a5fd8592f6cc2bc7c3d93c06e26185787856c922d95eeee345ddfb7cbbb60b6d992c5ea4dfb33101f2ef1dc979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000001377d654f80e933c4598aba1f637d1e37d66a96680c3a89a762f412e187817ec08f0ae897b08206a73f1a423b742261900000000000000000000000000000000014b71954b9bc22ac22cb2d7d7f373c3238c923205b223cce6c219175df2bb6d7258ae46d6cdb019311bd386275499fb000000000000000000000000000000000a08ef83b67bc972a67b9174d0e5b1536af882d505d03464c9a97f68061aa319d612de9db84e1e7b12fc3015fc2973b20000000000000000000000000000000005f716d0ffc30005e4a744092704a9e29f58fb06bf7d8d6fdbb95a4c0eeb5c39452cf662721ea3e0bcc67f25931a109425ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990", + "Expected": "000000000000000000000000000000000c53f0ca8901f4751be4a478088b30dce70b9ecc382455049df9ce108eb0a8d2696bb325fe9ebfd7d967ab5b9b2c2bd800000000000000000000000000000000033460babd2984a5d8b7002409349972f518364e92648927e223d7a3b648e952482c06cc713bdc29ab83f2646e9398510000000000000000000000000000000007cb9dfe603dc070151cc477ec5bb5a2a949062e8442399597c5eff8f1decff538cd0aef1384256dec73746e63a6c66c0000000000000000000000000000000016b56ee9b21c533b9c464178d14ba5c92a90e6a54c3ed319f487c2082b1ce1d0ff81131a5fb3dd7d13e0fc1d9ad9e4a1", + "Name": "matter_g2_multiexp_18", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000104e0b91821c59290be48b97936458af89078b176b5585ca9a79070c7050309b01df4b0bcd84f137f58304d90599212f0000000000000000000000000000000013b00ece925fd17a8effc43e21d982553ab2764b13defaae5e5419cb9a23ca7436cfc44088c2aded63785e4f07b6e186000000000000000000000000000000000267cdd42febf0706675b60af8c0953582ced84dd5ae870815654cffa46eb14b747fb8fbb3b014e59c929da49c6908050000000000000000000000000000000011c5384d7c3e0f4fd66ba4b4c2ab60f6f78f9930e1fed233263dad25294814d9e2aaba6388ee9f924e2a323693b6e43bbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee9670000000000000000000000000000000018158ad70994584e6f2443b8b96c1e4772a00fa0bf74865c76000eae470eb02cff627579126cc465046d4e088782557b000000000000000000000000000000000d72979d455733756a0849baa8afd79e18960f3f6dc9676c33d1663961617831f3266015cb998fff28b78300c87c2a73000000000000000000000000000000000056192c20cbcbde6099256a8f40c78a32d3fd212fe9c511951c7523a3559f60662e070f5b5e5f87b1686be0bf6cc890000000000000000000000000000000000c7b7e8ab7486012d95af5b2474ce15db612bfe1508852b8d99f4402d0e4f075ba056c19df3caa3a93bb4db89443096143cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000005a2741902dab47e8d38992180a9670faf56d1849dbeaa75b2b4ded93ee5494184c8658232e9131a8b08ac9b5460bd400000000000000000000000000000000189077d5130b3a4d7d4c3074633fb12739f95b8b6ccb082dfa61d845a389e6ca7aff835fa0f194dc349e1584b3141507000000000000000000000000000000000f226324f242cbc5f616c4a897f82bc5503ab1963ca38f30070c7c9916ef6bef5caa7e2e26b3f9fe68a1d59f19a9831d000000000000000000000000000000000a999bdfa10e4838ca69694272b0187f7d0198d6db0fd85eae688424fb09baa165c623dc6da567fe034d7cf9f9a0087e733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b6000000000000000000000000000000000fe85d976befdae8fd0ad33a4404415304afad1c5698b91bdc15abb4f268807c906410a6ca827320f5271c8fd4c8d6fe000000000000000000000000000000000cbff7963daa20c1d20717bcd47b872b3ecd5f38de1a467ef50936f13d6aebd978116a736cb6c5d676c6a9525bb0b7fc000000000000000000000000000000000c3d20ba17a21bbfe873d88e9221571f1bae7f02f35b8e677c9c42907673d765150c737f0011fdbaf4faa883b0dbf0280000000000000000000000000000000013482c68a5e1084faf12e8aec92cd9f0692b173556ac8ac3c7519beb4bd75f847f41ab9432421c631b14c885c001dce25112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea71000000000000000000000000000000000f9091a0df2c989e12a844c447287b704803d1532a3ecbcc890e6f6a885a54b969c53323c105b3d14d12f2cf766b8ac8000000000000000000000000000000000e54f3a9def8b3a9f972726e606195849584b7197ab70a28cf5644cde15e70bb6e3044042b649825adaf5e37c2d5e614000000000000000000000000000000000cae412d8a3ee3c5af38d7a65bdf2440d9cc2d6348dce0791f4a7e71ac483d7487b6c789be0a401777de3f57ec65de820000000000000000000000000000000014df09fd2ff406707004f6afa366d06bcf8bf18f5fc4b444b07c98b3f358247c6056a6337f5b53c35db45904797fb4455683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000a8aa422e1d58fccc84615f9ca4a4743cf5efe3a1066c9819f05042100bb8784fcceffc8b3a739f549b42f34d62629e7000000000000000000000000000000000c737cf78b10e82fc0cc9823891f1a5f1e9229d61e8f369c589512d01e5180246db46e4f09e811464c6e1ad930226d390000000000000000000000000000000016017354434899e2285da6ff4b27fbaab633d962197d2ff4fa5f688c4a85e1817434cbef13a6b018df4e359d7b9ab7cf0000000000000000000000000000000001433c364428ac69ce4f5678aadfed4e6d076241519310686de01572da5cf78af4a98b3502519beb0dcf04b748d08cac5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000001f8b803f3f76aee9825a9a960cd2f9e8aa931568b32be6169036683b4e6d8c4abba6bb73b137c7c6d6b6ea92f2023ab000000000000000000000000000000000fe9edeab60bb55990ad2c85c8fc9341e81de54324652c08c615a745813f08153bab3849dbeffcf4073f087f7c0cf0f6000000000000000000000000000000001955289b1210fa31542bd89f95188d60751b32e8d54f1d4d280975850e57db7b151b872bd431c528c22fb89c9b8784af00000000000000000000000000000000079c8a56c72adb9fc9baa503db394635abb10264dd43c60f2c82d041d43240321ac1028688d92c4696395d8840d52f15f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da0000000000000000000000000000000000b0fd79e62c6129fa115d821b8f2a58a4564f5ccbb14088f59d5e6a17a64e803f32bf8e5a415aac4d6491612d95ee8f00000000000000000000000000000000008d837b6c70468e1e10f6b979b7c0694d65942aac48b5baa829c191579186314ea35fe440e6d843fded02b95f9816890000000000000000000000000000000015a05bbc4607b113b37dc0b4b8add23736e0f1bb1e48aabc15500fa6941b17153918d256b6442687a432dd9ca9a198c70000000000000000000000000000000003546953d97306266bdd359d4daa939e05c0466691de59d2dbe3584e2ebfd9a9e1516cdc9cb643c5d31731835dfb07c657a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000009a4366299290c3c6651b22865fb22cc972a05ca5981f5682574851e41096d531e375e981c4e1b1cbfebbc70a41bb6ad00000000000000000000000000000000001e6fe2097fca2afb8385a3100dbd5ee1b7ae972e06ef9f5e34eb9fbdc65455e1c822299e06a9dd5a3f71a0c1efd44a0000000000000000000000000000000005ad2ffa8861848c46722a7924ece68580fe44e03157c982b7133361e974b59dab7b75358fe498fcde9f68b5b99f23e0000000000000000000000000000000000adac33e0b7e6740c980a4f297917fc4fc13f53a71909f2eecd0067656c6f82c3b371cc638509151bf937f8257aa415d86ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb000000000000000000000000000000001462d509503d2c33829c3fb5380199b79b970c2ae7f944e54a6d0f0deab3571976916cfc311ea6ce6128c467665fbbd10000000000000000000000000000000017f6fe356cb0dd5bddd489c26669f0f365260bb48a5f862e9bfb778a7ff5392938b905759718d050f7d93f107236cc75000000000000000000000000000000000d9b3ca93c5133cabf3d3daa565bc6b51e63b7e37f68f3bcc43b9b3ee7db15f8bb33052eb7e332ae3e9ffafb17cb77d60000000000000000000000000000000017d6b898d9799385990c9dcc3f72ed93333486b98349ef106a230a71d768b75cf56cd946f5952075bc41f26dca9c83c003fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac3773213600000000000000000000000000000000158e5e008796c10f6050826c29523864d06e68977cdc95d281a8606924aeed0b475ab152bec5bfca8e0ec53691b307f50000000000000000000000000000000006fe8e75328c067546eaba93f4be2b15513bae4a3458112c3ffa457d15c23636816fb469f071889380f31870d713e949000000000000000000000000000000000b9b21cd58f8742ed094e9b770182f6f3f855204d869e53c02d0c242a133e957c53c9fabc827d6379b39541170be313000000000000000000000000000000000014eaae1f0789f0b1e8ad3b452b4ed3ff87bed49ffedd13c8c35c35668c33537b63050c06a5bf3d88d516cddac13b4c935ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc627910000000000000000000000000000000019f976b3584ffc188424614fd287eb79f060c55e9b3dd2f3eb99760a7cb5b70e2b62a0895b05e7cce2e390853fed61b3000000000000000000000000000000001117181241fead3865eba4804ec2c14f571aef5351d5bce29399113d007cd4e9c262af1c77daf9183346153e562864b2000000000000000000000000000000000f823f71035a4870be2ef20bc94e97d74d18c0a1be9895fb27c54df1f663df6f9e6e45ea5fe4502143a84c05e517b02b00000000000000000000000000000000141250f392fabd4566e0cd3a472a4b2971a432a3a5e1d9c924866c7a9516322bfa691e9dccdd5ef14c561bca6dd70ba204a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa008452700000000000000000000000000000000014d6827b9bc782863491bc7c544263f58dc04c18e08a87ca2fbb5799c4aa70bc039416a85dbba67dd83bcc27b70748670000000000000000000000000000000016c2816e93ea9d4bd6e42a9720cb89d637d88e00074da3300c6409be98a03403e9ac15f83167cdeb13800ad174ac47f10000000000000000000000000000000002aebc0116a62f93a6e86c7fce86745618e08f4aa9cebca7b520e9176bcdf1521cb2bf7eca7f7af9487fdc82dce76bb50000000000000000000000000000000010684e3254207c4ccdd49e4775198df981afcf7d9f89b894e204c5dd84ef42b89fe3e2f6b9278470e6cde4d3f4abb3b003f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c9816210000000000000000000000000000000010ab1d5494509060c9784b4744a0572a9466d6c374524a6d338ea12ac5ad89519217c462c3487e398325439311bea86400000000000000000000000000000000197568cb53ce03f00aeb04278f355da862be757366dad14ca6d30b3a537df9855a1196010773768a91cb4bb664a34f0f0000000000000000000000000000000001fee249315794d30eaf929f44b99e07927194c6015ff34a4530698d7d68239240c9cc48530d52ea06218a826a655cce000000000000000000000000000000000645b5d701bf3422228576467120935f014c754dd68bb3555b50aff5ca04001a26298982c97a64469aeac3432784efca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c94234200000000000000000000000000000000113e17730f8dd7258157085c30cd9d1950a26c848b55e3a8a55865eb567edecfb09f32ba27fb3e2096ea00c30f31ced8000000000000000000000000000000000076db9ccf8df9530b64cd43ef7b496d1f432885062406028901bbfc5882fd12533f84eb12aa2ce8b7adf9dd980db0870000000000000000000000000000000015e487de49f1e494ce9907cf0ed31fb0a159c5290538ad969b2c8a504986dc9cccf7c74a61f622154e928aa2dd689c0800000000000000000000000000000000195e887083a98fe3f50a9ff4b342e004398cdfee55c4b02a4db0f65a77d3c0b142a45201674726c96d5f79f8604d61860033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000d80c7e50973205585b20a068c64957cf4572eea40e32ffa8b759c38c6ad6f4468421f2fd6a6f5da1b0d008f625b3e6600000000000000000000000000000000009242dc1de055aea82b3b917f88b6232c550c3aff41241a7e54caab4c234d29b5d8138968846f7c754d73ab3b4e7913000000000000000000000000000000001188c31a9d8359d737576f4ce7a7900314aca0eb3b51baeccfdc9245bffec49143a11b3331f9126b01de0c307aa4e44400000000000000000000000000000000104ef4835124fa6b30dd551653aca25db5a544af6782cd0b1e7d26178253e0e33cda77428fc1dbcfe6114a758cab5c814c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87", + "Expected": "000000000000000000000000000000000e79d18633c18ac818786bba87d09c9bb1571e179d8769f8fb82e2e2b7a6a8695c1f4f06deebcb84524e8facdcb49d0500000000000000000000000000000000149d0231fb030a1bec170decd307c10e72cf1cca55c8a1b67aa94ce61e4c7d2ddfd0b8e71598e1abb054355dbcac1528000000000000000000000000000000000090f5be784dbafb0a8aab1516c773720341de6176017e0fb43a275d60de54c1189144956d4876d989232b362b90851c0000000000000000000000000000000019dba28eaa6706361f285b3abebef68f764204c74ee93ea011db01c19591ddc6f98799fb3026c3c223effe4489a7c676", + "Name": "matter_g2_multiexp_19", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018a6a982acce5693e632901f3136eded40071e8c7caa7887f302c32621c5bcf9478991ca519978b52f8f69415c0d070b0000000000000000000000000000000013420ab920c8ecad5b2f9aaf9b0074c2386b0b08c81923558770d4c4a6b206a865af8322e9755706cd5e595bf0ffe564000000000000000000000000000000000c0e5bf5465d564e3ce86d6b742ca687448e6952439b1ff44b86ee6461464e07f8039e8ae7a301c6caee7eb99e38fab10000000000000000000000000000000015eb8751b750af62f57971e88b436658758bd5712f98861fa07328d2b11e8725fb55a2a00252e0be06b0c73aac0f7b8cdbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc80000000000000000000000000000000001328927910ab502e573188271108706152f562b1d5f6ec074f8f9ec5eaecc6cd5e8284a060b65d26463d22c8290ea4ca0000000000000000000000000000000005a1fcc348122350981dd5090c865a2aeb851ba8b6e0443c32f48b157ba673ae5652a70390888b3458afe6fe975321700000000000000000000000000000000019edc749a9799c8d3df75d4024791943a8fa02ba0cac90b6819f0bc42687b044457bc7cc6073506e8fc19af37f224624000000000000000000000000000000000fff20fb2b554b63758963c1583b996ad450cfbd5ca9952e38f38a8994809096086ed86311f7d73a0a5898ac261ce09e57df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec00000000000000000000000000000000002a7005dd32bddf1031f27c2ab999604c048a37c39734db48a30baa86c61ef626cf82084651ae9ba8a265333060a408d000000000000000000000000000000000421bf913a25108b8f520b2becc6f8064029dc046d0d5effbef31f0af59eee71cfce83fec8dda7983d50c6d5cbc8329a0000000000000000000000000000000016c75708f1dbfbeae3b06e5e9a7fb676c27100b99deece14d979b32a9c3cde6e9e96c8560a00aafbe6e7decc84e7e2780000000000000000000000000000000000ce23c27b5128bcffa424fd1d181d21b06b77bd6549ca5eba9a28cf18bb9a979270f6a5807c640dde57a0cd4f3af8cbe2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e000000000000000000000000000000000690c7ab321c0c93b5ae4ed77843ff4030e4ffb504c685d28573e98836e8e56dc19d662ae9f496a346bf2a8be5396741000000000000000000000000000000000fbbe3861a8d202b10801cdd606b50db0ad6ec7b923b90ae81ff5443676c3399e249e9efeb47b72d2b0a54cb0594686500000000000000000000000000000000148a27016968f0258e5eafe0a8182c22091873a5a58b27aa2160674584e06d5b2f46fc57a00617af18d0688df75294cb000000000000000000000000000000000148449d00b3d1b5b43b08a0c6e909a2d9c66920b60224a2c6a2521f0bad35b99e3bff8be0effb2f7f34438662d7a4882897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a9400000000000000000000000000000000026b55509b81befaf6baa682a3e92a0ab423fdaa84d2897613fd31acd9e1590f81581ba0ba87d68af76b01c36093e183000000000000000000000000000000000c675e190570bc5173b8f508d5bd2768c83e7f56a08cddbc636792dd75386939942827617c4aff8628a74b74195adea20000000000000000000000000000000014f59f38ae9e77f3a76478ecd47f32200567bad11f191d303cf15d7801ae7b5a3286095fc8726acc9818914b27a776bb000000000000000000000000000000000da89fe9493b2d9d46596d80162f5831d4fd8cbb83b46e84e95d5d684eb927022ee62ebc3519442007fdc543701f97bd2f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000170e52a240a7ccf2d57ae92ea8dabe62ca4b458a5da42319ae89cad22ebf13541b0daccafa1b1d3cfcffe81b500c4cf400000000000000000000000000000000174879425f3bfd40fb74a88e3dc578e45b0e0eaad94da009e4076dc42d234d78248ec3a035666dd6de235f87e1a47bcb0000000000000000000000000000000005aee47acc3260d11fe0ca16050a29f92763b3cf8ac78da52b3b2b3e26d8ce7b6ccc187fcd81695aa456e9b94a84269b0000000000000000000000000000000005eb297abf35b51d57474b4989dd8f793005bf8e82e49859c41b786ae39217b2321299829198bda4aaa261a2723d43d6ce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a61000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e277b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000002d947e728a3b376de520bf78e56452930de42544241180906719a24d72df65f8250402ccaf14d69935b1ecbb0b4d34c000000000000000000000000000000000d5614ec77a9f31915dddb3e4bb533db001702891a45f0bbac49e73d9c19a235a00442b52d452d77018f883706a616f1000000000000000000000000000000000dfc6a73a8e36b7b2d0614b1c6f7bf1ae284ed740c768f08416c0c09a601fadf3e4d7b17a93601b1803d19a04ccd570b0000000000000000000000000000000010d6a8e4eca2e818d6dff13faf0fae44a7fb90be436a9ef3aab05515a35cebfbd53e9af866cde1745f0e2c3b045486dd2b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000d6c5a6fe9b4d4580f8e1d89f0510bf5dd04e113d6ae5db04af2553bc0eb3a32fb881300f638fb33f7c4bfaa10b063660000000000000000000000000000000013e001b08191707ad98e21b3e0830286c6f3bf587b971dd4ce39e55f06db427676626a5c31c4a67a996a5725ec8f402c0000000000000000000000000000000012f86ed85113ed1abe9dd3826423911e63df0dfb51ad3d1e0e0318ae95991a6a11150176cec77f9c83268a322cb7e934000000000000000000000000000000000dda719cd2cf1aa769f94c21af20ab076b8f024e0a4903e38ddaca21b6bcd6f00baf7e1ed23259f135eb8bcf9c3f97c44f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000195ccfb9038bf9e637b88c83c552ffbd562357792513b15f703bffbd373ebaed715a6772fa7e6e5678c2e6422811dae1000000000000000000000000000000000c5a110f31d71b12cc42974003ba39d99dfd91769c2e93393449083a9b84d31473e3a7dff7ca40164e6e7215b03f44ef0000000000000000000000000000000006233b2dcfed96559b565928a494f2a50c2c375b3d7c60ee6b286c538f4fd5ca6f8b2a61654fd04d679bb3e05b9bcb03000000000000000000000000000000000d42233b7b5ad809c735c89c455ba1e8fbd623e1602bc729c01d362368666e4f90e7b076e32468041f3f5665c6fddb0d0ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000f6d53de4f8b20de19b2fcbe8a6b8b8ec4bb801bce7363f89b133532ca7ce4925312e23c618a0182d158037c0d0bf07e0000000000000000000000000000000006ce094e24eb14b9bb1b4a1838d8b6da5f53b5c5799ab8dc8934b488cbabf698b99abeb016259a4e1b0f626d27f2c950000000000000000000000000000000000874aec7c8ac360e3980a6e2cbf3f7468f1df7a8d9158f8bdbb0f387d19f3b05326a081129576251ec41a926f670e58f000000000000000000000000000000001711c9b2ed7e2f789b29073f180e46d0c373d6e75c587ece67b8aaca1e9d9b43a96d04dfdcd42f943eca48e240b72ba8002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000e8ddf88269aebf190bf9bd7a8276de92ff6039e479e42a490fe4ef00f646b049eb8ec4b8e073caa000bfcd86ee8724a000000000000000000000000000000000a9623655c0121ea0575de714e53c9e304fa3309f00828ba0e786112781a38bd458cd67864ab17929448171b5937c1d900000000000000000000000000000000198fccc4a333322599697e904e9096240b9c54f89ee6db97475beead62ebf730da1a179409133698ef13abe1310689270000000000000000000000000000000017b059ac08a3fcebde5888bec4d7cc2c70b147b3b1483fd001330637ff1c036faebf292801204bf2ba49350795708dedaa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000a36274f33b4dc09e03a5ad648af0913e5ee95af83df8b4f2a158456aedf0a0528f9b4832b11162dd67e4d22b26e9f940000000000000000000000000000000008ce96d8bc0aaf2dea732dea188870d398b1f3c266b9bf019e1046cca05002416c910e02e998a1604a17c333c65c99a0000000000000000000000000000000000c1a0e4a80bb0331a94ed14570053f941a0438794e6f19d976cc62b3806a565697720ea03c2531004f13453991bd99bf00000000000000000000000000000000184bdae93abbe4d931a6a51ec85bc330d6181da2d34f2cc530e56b6803515ba87f5719fd6fce6a1a8bf1ee5a968bbfbedf9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d8600000000000000000000000000000000016c3782daa55312a7cfa02c3be73ed75f4b726df5592351fffae19121b5cba73f427d35d5a2df7c63e2a5c68bf57f3800000000000000000000000000000000018b608343616eff759d512c97257f2103cb0909afb4c24a1cc9d8204274b7c9ed51bc762a6280e223a6116a9b23d1f1e000000000000000000000000000000000c687c11a879ec285180cbae3d2e4219df4614e238d4cbdff148ce5a8d21647c489ade3bf6f738052f149fdbc76c8bf6000000000000000000000000000000000936b34fea3a2633b9aa32244329891e332745876d05f95e4efdef859b23ceab4869db562555e5c8edce87a6fd075ae54005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000000d4636a5e13bb59878319af6bb7c98e5d247c2c9cc970b9cec98027de2d4a8ad12d50906fe302c3d055c499a3742ee30000000000000000000000000000000002b0214bb1ee887a7ff10d458fe35208573456f685ee2fb93bb470762c9e27595cb00f2eae7574c8467e417c63c2a960000000000000000000000000000000001710d130f91861230562cd7ab87984ef45916af8e1168fb17b9765183d9d3f9b2c81c649687842de495a757471e28067000000000000000000000000000000000dd15fe505b1364f134ee77e5e3c1a497a20849b6ec7e201813677a1569a9f5a9edbe3df4c36bdcf9ada139b20e048ec893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd151570000000000000000000000000000000009a48d7c55d24ba49f890791d0f6a8a5ae08a19177575dc0d734fa37b52c3adc45b31b5e485a5d4a5533470c3549f5f900000000000000000000000000000000090f680c6fc1f0588add04ee03bf821868b1ce588e3ebe384dae657ba7885ef74da0bdc98d9d9594a9b979d5b50b93df000000000000000000000000000000000314f6aae1e99dbe3ea9ef85db7e1693a30869f48e05cdb073bf8e14865a671e75abb875d1b41f13d4eb74fc802299c70000000000000000000000000000000013c698b76dd68d1b9ab41672c2b07cb9a63168497d1144b51509b602c5acd71ca6cd049616d949214d95ab7a906a8f8bf685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b", + "Expected": "000000000000000000000000000000001747f6d3154e0717435fa023754f115ce2a2b3241b62525cb2833473d84a8ccf4c95e3ea030f2b8b0ccc61124095ac86000000000000000000000000000000001827ed7d84a61c21268857036e91c732b304f609f285cdc4736c951fd8954b10267a8505f25d8be666792358632058b400000000000000000000000000000000121ac61f59051e6e89a7c1e2fb4df4b3a5b7773f46495a99e55348454e1d9d42254e5e11b841a1654ff9c80b157389c70000000000000000000000000000000001bc60cd06879980bc6ef2ca109d31f12cac28ebe4d2a934076d720b12f430e1bc4d4260f40045cc7a862726521a69dc", + "Name": "matter_g2_multiexp_20", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000012a6984f0f8967c5ae6b13569a62095b5fe61ec607daff1845961bdd827c00fd56ef864802673dd21d90560fef6cbea00000000000000000000000000000000085ececa080d0f4c996d46c80a1fbad2ac9cff8b3e324aabb67182d79f941927050f025b633fd5119f30bb29b8e4b6f2000000000000000000000000000000000987518a5edfd5ae2616fc60000e117a4f1dd1db68195c3fb68d8cc639e4200945b2864d41ad86fb3e11c504fc1f9766000000000000000000000000000000000310939c7e11b93e5773cfd36fa70020c85396e525840742f994110e20019769abcd339db6881291639c193b987b68ae94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000ec6922dfc74009c3750ce2540558c7c1e05cb45a5d651b96427c615d8fc563219215a0ee431c0a4827e40b26c4f8d3900000000000000000000000000000000040a4189d002a0e1ec600e71303575e82414e6400f06b9abf57151a28835d454f56421a6dc4049902bfb94dc0e9967ee000000000000000000000000000000000dfefc7c163c34cc004e9d97d812b2717d4736d0d1c722b6bf1a29676a32c8b46878d05a2d137cb7fff5fed8c0f02474000000000000000000000000000000000e3f0c9cbc778693c8ba88af8306d45477493ed6be1bdd9c81c65341239eb510fc948142cc30b73f570819b38f13e20f6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed70000000000000000000000000000000013705ca4ecca16559713df65b376c7c5825b4f63d001ebbfce9cd1b592af5f2ddb38ac7c5ce3c5f7af4f39f909887e8b00000000000000000000000000000000179efff38ea1044e91ccad467cd2b49438079ccb4d0fc692e79e0bc374abe064fb9979c4a1f4b92c15cd1b042b501d5f000000000000000000000000000000000b6fda2dbf6339af225515681184843f1a9bcd72f7b1389f186f8d0e048ac16e20967c28e087cf09d7bcac597a85398d000000000000000000000000000000001946fca8c816e1e11187aabc40dd2436533d537ce4639eb2d08630ed2ce402c1806b6c2b3e04a960408fd4d2049849bae5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000001802064095d029d3897725eeb93ed6e3b090390769026120aab6977d0de264a262aa312c5777ba322c9eac29e5396fc6000000000000000000000000000000001410f17820941e6a67b1b4993496cdcf0d4fa2d4fda3d43ee985f2606b1408aa9c9ce412c80c90a0c876cb5ecb76878c000000000000000000000000000000001514e9b2c65ca86713447f2d5bb8395fe8552e059829afc68bc43ba9267ef41ec6d69d06e7407a731bcca77ed5d9716f00000000000000000000000000000000025b5bb18cad46179fab15b2ccef17858f9259a90ea4548852b8c6fca69f0ecdf0b175669bacff1625a7143e762514194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000146eaf5da57b6ac788f8caeb4b2ebf7c8999e03dd839977046ca834fffa7e57cd949e3fd44999a007b5dcf3c8621ba2f000000000000000000000000000000000d859632d3424ffe4227ae14856e05c4e750545cf276c97aa9ec03ebde334144eea670dc68e92b61fc775e477a2154040000000000000000000000000000000010b44279c0c80886e52fde5e71726422da2f9457ff86b21426d80356fad95d5ff3a7491002364d9de5ca99c2500f344d000000000000000000000000000000000851b769a691f0ebb53ee3693833881fed8dc6d9e5f1dfeaf4ab1aa7ad54e2fcac246b70d81110451ed78044a98d1547de4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef100000000000000000000000000000000131c9a76109929fc977a0a6eda0a7c71cfc744f5f3654e2221ce84c70787598e24c5d8049f92a7c4d78fdb869cbdd1ed00000000000000000000000000000000049872d2c7d472e090d2975daa64fd96f33e7f934e739633b1d7fcd5e771673ed8820752a0d5c8b0c6933318293a4f27000000000000000000000000000000000dd68fbb592a3957ef893180dd758f75978042add36c91b7bf87c4493b0baa875e1854fbc09e6856688cc241b76ab5a20000000000000000000000000000000006143699816cad8ab7583a72b6064fadb6caeb51c8625ddbf7b2911426cf438534da1bdd13e22cd545495c486c9733f7bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006c14301984607d569ad1bd774135e7c9e328be1fe54c3b543276bd06bc0bfff11f299a5eb43b5218c3605011d0ea6d80000000000000000000000000000000012f0a848022f95f4884380a5b8e3637a41e3c399a8d2765aada85dcf4b7c2b559122f792850430681a58ca153be2768a0000000000000000000000000000000016b4cb233e1bd59b7b362c64620eaaa5029c173a05e2278774ad6ed746c70a2f6e76c237182f5d9d790966ae69da5d44000000000000000000000000000000000c277d54a7a72c8528188f6cf29d934cc66471607e5e30d493cd11be6b203bdf734aaf37b686cd7101e8599b69446991c3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced432900000000000000000000000000000000169a3928266375dd5793b7504727f939ef0ed52d69e569b1b75a0e094698b37bc70472578beaeebfd0c3df4bce6177810000000000000000000000000000000008936d470dbb86db1567bb2fe7c09971c6d12b07208d9b1b403c20fbdc05ef8984dd576457fc6989470e40ebfe4ceed30000000000000000000000000000000009cdec9d80f2bf3ebfa9a3316e4250741d0d089245df2fd3c9bba4bac1c2dadfe212682166a0962f78c4bf25b618da900000000000000000000000000000000016521411286cabf3fa2c8f72ca62ca311738fbe63717fd12916a4c9e6af9b05d1f5d65cf60e84d9fc5f7b7645fe9bad570fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000006a827f6149a320a74d9d8c1ae8861c1cb963b3eff899710eda642dae6ed4dbc247a22131758d9f843c62710ce083208000000000000000000000000000000000c83a9fd96bcfd4adcfc6d5a47e84108bd763366e91bf06a7431c6c3a107cbe5647da99ee6c1e57c376d366b21a923df000000000000000000000000000000001604d5c0364afb5503b0e1d52226988d7f7f043ce95e7c0a09d7f96e24a58f089156f0e6d19022138170c1b4b7dd33560000000000000000000000000000000019a11c86f78ce462f46e0462052cc3d342596b329fb62a282a59bbd64c345bd266922b1540e40aac147681754643c2e3e538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000015635de295c16841bf44c73639f047f735175e8906301746837838d124bf0d2a1ebaee142393ce9a0d58107c7cb036e90000000000000000000000000000000004fbbd4252fb901d0737d1bf4da62010c06d690a9584c7631ef5d36f1d8c37486a83f2a1e2db21f05c993fd117c662e8000000000000000000000000000000000f4cfcec1545a08e0e0298753ebcef5f61bfdb7c1b9af71cb4c2f783e4fa3948945d357e8302d99aca96df0cb0fc01a3000000000000000000000000000000000f543dad6d4b797f6fe0b00215a5f70f6340ac6bf7cb0bdfc5bc7698dbf0647e4098413dd19ca7af01685edaaa190c6e30b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000ed552e94021d0912a0e7563462570cb572b189569eb847bd12ebf976d22343b9ad04d400ae98fa184b10ff36720f12700000000000000000000000000000000178727c3e6ff33be9894ef26347b104023ea0bcf79c1a33afc26ac0ee9879344964fada757118829214cfcdbbc0c5a30000000000000000000000000000000000b0a6a575afe5b0c1e287815612fdd3838ab39e8ee7795855837588614715f6687910c42217ad52c1b8721a9e1c908dd0000000000000000000000000000000018cdbf244c78cae1993400ae164b42c09dab4d8e3707a69e25ffa8d0b96b8270c022c0375f933f16f45c9274132a0a633a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb460500000000000000000000000000000000070636611f903f55cc9499481bc3415a6de62d5e6bf8bfa82a8ce665f85bcf01690118441961ff46ff701e361db208500000000000000000000000000000000013d22dff8f6f86f659ad17ef91d90a70c180538f03e10de20c445d22e637015d51a311a3daaed90712d04c9a3d992d12000000000000000000000000000000000db3535057db95fc262f8adfd7f08f3237fde5f0e2aab589d4ddcd9c23aadc437e13644dd3b3534dcb17936a7c610cf200000000000000000000000000000000044c177d4484c07fb04d1dd477b188a2c157973cf26075001d14d2b07ebb9dbf8e495dc23b32a2419621e1c129b08c5ac7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d56000000000000000000000000000000000e10fd069f2f5fddaa0112e70ae89d1ecf034defb24e2923731a7c0068780177c186fde92a3c254a1cbdd255111a4b7c0000000000000000000000000000000018363e01e86e2e922ba435651ad892bf9288be14b54dda821c397ae6167f9478c8132e92b1c2cb0c4037a4e020f08291000000000000000000000000000000000301b5ad2d5c35ebdcc7e7cd1ebf0405cf204d6f5e30ae6f46d20534eb6d7013682c5ae1bba76d2811124ebded0d2a590000000000000000000000000000000015fb3a8afad778031d04e094cbde5f02dcc89ad7b7d452c6c8f41be336a4c8b26e75cfc685b8776cbe5a487f09c304083563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe000000000000000000000000000000000a7880b00f6a3e959ff1bd207fa503eff6e7279e701e37b40735e2bc8bf49e355e92edcaf23aa3654bb26fbfb07b5fb100000000000000000000000000000000113d9b792f4e3dcd958664a8778dc4b177c430d8db9da7805595e40293ef2c0a40f7a843bfa70ec134ed89a453f9da50000000000000000000000000000000000d7f92148dca4a9c96c47a0eb284f1834cf3d141be7c0d9a7a060af6e28e45620d8255e465e9a0d8f78b2ffe17d6b04e0000000000000000000000000000000004e7917a8f3070c656d324c9a816236842fbd6147d326652667e7bca0666d214233ed136dd9464c4ac619d46c28e2393833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b500000000000000000000000000000000160cb05390b54151f6b154b396bb400a91fa83d77fabdf31fba349d1bf3b5dfb6476ad4d714af2a2963e41b077bffcb90000000000000000000000000000000012885f7ec8e780cbaa90a465b5706cf07d45bda7755ae3477c79adbd7956b926e0ef5303fc13f0b97349ff8b754dab500000000000000000000000000000000009ad7509e9e7f5018ae3d1280e881ec12129cbf825cb6606459211ed7b358a97cbe430e94dd9f5e4f6b74fb7287f862e0000000000000000000000000000000014d5d2ac2dbc3d5a061f4e52dbfa68e1eb1d3c818ba26686a3171e310c63cfeb188030b83407070019dc5c42dd079413d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000008ee93fc610712411634079be0bd96c3969b48955fe5478b7a31c3ba7639c18291034167eb62e6b15c16b0dd5145edf500000000000000000000000000000000158cb1731b71905d7b958c5407f090a2c8a9319017719da143a3f4f3fb3982abb83b8dfe14facb014321b4f5edb5e41d000000000000000000000000000000000a9f98f775f06055ac1f137cbc1f95f4afa0d1c4935f536ba2e0569d874d9d76b7b86f71afcea07e2e785c7a6ee1c84400000000000000000000000000000000072f8988dd1ab0fa8037d3620068b34848c65e20dfc90612d123b6f9dbcf9d9d699d5ea73739d31ad54c22116365ab983ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0", + "Expected": "00000000000000000000000000000000161203d8db1381722644f87b04f47e4be2ea2bb105ea0e67678bc8d29f8a8a3247f8c05e057af8f98032faa93d896aaa000000000000000000000000000000000d3af4842627a095a2dca99b52d802b2ef8c8f3d09873ffe39d224333fceae84bf74780956904df6c1dcf5ba31be218d0000000000000000000000000000000001c79fae014e55e5d0239645d618593bfd5aef665b3e636dac5d191a8b88949d207cf0ae9822ce8e1997de302b386b8800000000000000000000000000000000136314cc68b372b06e7771d82b7ce7bfd0e9fd306787e07629f831c7aee853bed80172121949a940bc59c4a0b76f0819", + "Name": "matter_g2_multiexp_21", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000099434adf799099f2e6e2fda4c905e1543893462133ba216aace25836db37b3dd5bd80af1a8c31c7fee56b5ecf9a0acb0000000000000000000000000000000008a6890e5bcacc13e116e3fe2d772ff49839803e4f81d6b088ecb7835b1ed44f2bfa04de1d46dd352346cdee71774e37000000000000000000000000000000000e94fe40225e863b7bdfab4cdc0c1c8d1399554ebbfa3f2c95ddeda74b3dff03d5cc78e295accdc9f02f3f89b4953de3000000000000000000000000000000000b23f2912fdc7a5fd1de69c1f479228f8ffc9f97c40845808cf17a6fd8131ea60285640d32bcd64c9be71d419aae82fb16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f510000000000000000000000000000000004c6f5aaac90132b2d0c6a4e70354ed2e724df7c3e6298eb9ae4ea92e3c7981944c89140c52e893ef2edb2773ab36bcc00000000000000000000000000000000021e813378be9ec30395b917ded5a0424fc7eab0abfdcd2328f725bbd6a1dace0a5aadebe40e10470df0c09b3f4b68440000000000000000000000000000000014e3fee16a833f8c543860ca438d763f764f488463601741a2331fa90efce9f6d54ee0fb7978460a1ab838039d398918000000000000000000000000000000000dec8bb882fe6028a4155e6e2bf48ffd314b5519dc4560f8f7410209214c4a8e37b2b36facc53f4db11ee63ff11f9f228c02014d5392d30863a12102d1c9315839b5611dccfdb489207f9186625138500000000000000000000000000000000002d107029bea087a2d53b6b371aae06c695fa85631450f4ad92c8948b09ed568b28948f80f1455cd22e2ad44697290b00000000000000000000000000000000002fab10cdd8bf17a633c8b3ee8ed2ce783f64bf978c384fb7dbd7e4f0da50b65eb9530365d982bcc17ab91a29eabc065000000000000000000000000000000001369237fb3241ac291a868e6f4610a5103d93aa915e954f18bcf348ece1560a12451723b96ad5fe162a6107dabe1c986000000000000000000000000000000000cb70b7064a2f94efc86060431ba4dea38bc64822efa73c76f3a4500ad23c452c8f2e72713b066a45bfa49559d14a719d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea2490000000000000000000000000000000005ebb9c8202cba234851cf5e060a4114c6fee0632f37e0c52aeb852637f362ce64403347d336c32617cc59f23cc7c93e000000000000000000000000000000001126827b6a0a8adb698854c0089276861e3cccfee420512f0966df78ea0d9c55e85a0536f14ad40e649b8fe4384c836c000000000000000000000000000000000998549680649b294d506c529ade746aeb087f75d62a246b7abfb69397ed67f0f2ccb4811219b35aa894b2f87e3fcddb000000000000000000000000000000001027b604f877ade32df8de6162251acf2751a9bd770c21f22dc819a4f5515bb276a246ad667fe7881965f0b083d1f76304753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000005d1484bad44069b16d1ef4e9ca1db70ec6cd82eca645c2fbd4029ab4ca33d79780ebc144d8774d82518c1fefaab38530000000000000000000000000000000019abc7063361ed64a5750b70bd59283e6a61d55d49d8c2ea2f1be8ea425f040d3865c399a66c253bf38355360f06cdd40000000000000000000000000000000010a97b13b3b579ab5f7fd9801d9e4fc40f3b2b2acb9f21bfcdc6b6a3168720fd0abc2f77ccad01be6a6e268fddf3759c0000000000000000000000000000000004126b5454050d761047e5da23c0b2f9370996589c04f255a1ce8ef37a3a7c8078788a0125e4aa86aafce8df33f322d3d1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9aab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da0000000000000000000000000000000016ca071d741363e7c3297355e49cfbdcf03d419813ed7b329cb2b2a26fc6a46cc52149ca3e9ca3ccd7284cfed97b985d0000000000000000000000000000000018da360fdee88e806ea1a61c01e86687f8e5359730c36c876ad2acb0297bbc1ae13d790d1edaafdaed65db9dac02a74d0000000000000000000000000000000005a46e4572f667b46aee36b8d377c249de25e797b31b822474aa647ee68cc7d40b083fd0a1d938e2b8d85508004c73f40000000000000000000000000000000011701bf88d4287c98996ea561c1ab2f29a5da9138338c7c7539a5fc8355efab6f58e240df4b0e0cb7f01df74bc8010501d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a000000000000000000000000000000000576e79e507d250eb4040197064b8898b0142b3a2551875935f91f22705bfec6da156c7858fbf77028d4a00957553bea0000000000000000000000000000000015d39a325181d6d1a809b1236f4a1ba66a9bfa6c448470425aa5c8ef9fd00b5481c51e8752088dad62e928b3180408df000000000000000000000000000000000aafabc2f68a4933c7d734660e422ba154e37dd90114272e948f79db4ca51d5ca75d504cf74f2dd0479871d69a08386f000000000000000000000000000000000b017c731f63bbaa8fd0b0d9c17140060429f515d2e85a938d10f6529deeae4818c29b9a628802d0ffbbff720339b7bf2cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d400000000000000000000000000000000028c4dacba5f33ba66368c19491f4baa6aea4f309afafcc8f464f2886b1d05b6397142d02f0295fd50825819621673a1000000000000000000000000000000000849e1b630e8db8ef039f280f8d401957f807ca90479745b68c3db1b5ce3a02fe2c099ddf9c387d7ed76ba75d6a9be9700000000000000000000000000000000013b43fabc3d4df82058db215a69776ed5dfd4c773d7a013dba3b4ef5cf65e25f79d7f76a06ca99132d6fd1fdadb59d400000000000000000000000000000000072cde8eb3d3e1a7f7e4a9eedb8e56f5e103db6de6ccf833f818f02a0706b2043d4ba0d5473bbb6472e8aeb28364e1d8214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e00000000000000000000000000000000007a33b95f42cb1d1ddeff3a199ccfd9a5d47c9fcb89dc09b5b3f59dde2b47d24ff29931920b76ecf6deacd70e83576970000000000000000000000000000000014c0a63e0152f06cfc32e6034b7829f9d9d09aca0a6ef821dc61ae8d99b77d76c1b2fafb2a14938a82ec72c4041ebd9f000000000000000000000000000000001433135cd913b05b3f58b2e9c1a3bbb951d2cf6c92fddb21bd5e1d9c44e464d5fe98f0791044d56e50b81a83ef6cb271000000000000000000000000000000000be12ce3bc47bf69a13762343b5e39c2a2f285896e5d1b73c55203cae2f32cccbb4f7b8230b2026a0c8b2f63db5e5bedc1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e2000000000000000000000000000000001494984d478784b2ab3ba27464109f99172033fcd5780a48fbd5a2144354157f6fca2d70b15b0081dfd306ab4239cecc00000000000000000000000000000000078aebc22025af53c6542abe56cf72ce5eb11d3f19212a0f7442d0a0df907c8aabe0ec01d1245ca237a691e685011bb8000000000000000000000000000000000415a1804a46f4595014ef29b12d99b89600aab1d98352437ab8342abf479bb2215bc687532e75f140918b3d030ad4520000000000000000000000000000000015e7b0dae7e3e80eee3c7a9ed4c739288ac2192f7d80b2c8cf9934cea5719081803b207623c771051d7694e705744dbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f74000000000000000000000000000000001470f82372e197a21aaf46cb2bd3c0b77c3428bf2ba073311e75eb65471a8164753ff1d989560f1ce477952bb6555200000000000000000000000000000000001645b5e5b4bcb5f6d34ac841e3a80f09a86a5edcb7f2a7e7bf549b022c0073e01be82e4c9e5c8e8de76ba367595639af000000000000000000000000000000000b43f6572553154e2530fb448d5bf20c3a182cc190149d3b1d75b60e45baa048f44884500fd02c434f9f7eac01dbe4170000000000000000000000000000000014adef5a52d76a267f87d9a8b5e9f570e7775ca4f6a55a5afbf80baea311b1866fa0689271799a654eddcfe36a6bb64c61ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a000000000000000000000000000000000345a2ffa21eb06fa1d76fd81b1239147688093c6a44a40cae37f2af26add812884bed3e8b4643675b1a45320c64f7a8000000000000000000000000000000000c58eeb5ffdf886d6319ead9e6e190300ceb91d58abfb79c0a322de3987eee73ab82092eea8e1249e83ab67e33b303e1000000000000000000000000000000000763a3fba513b6731fb501aab39a4697f3e4de89125c6884f9782bfb73e6e062f17d34555a04a8e2959ee4e1a2ee284100000000000000000000000000000000024180dde2d23cd88cd29c8142d32435d0db57b8ce8e309701fdb963533c1cdc2595e3bfc01d8c0d08d594e096afb34a681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da00000000000000000000000000000000022d2e7502c4d9587df7ecdbafcbb813b1812d76655cb7f9f57418d5ac83d4f60b84a0ab5b53a5eee3c3954aa9fc70cb00000000000000000000000000000000083212aa1316561a079cb8d027bc8f89161fc828d050c8837a24fca6f7f94b6dbf10d6032fed895a427f07827deaf3cb00000000000000000000000000000000021552b99dc02a051ea3af1b1bbd0a7ef64088c3aef4a58b18a29ca05e1f442f8ea2c8fdb3642ee94c5df501ff6898f40000000000000000000000000000000001015a7987d329cd1eb5f991c270643a05b8e1bc35467130e9f53c5d96fc3c8336a00c060dfa2d3165358b51b6a521e56f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000c19c3b9d7c7f520968d8531966cccbe6f0c3fa0938480ca3591b7489febdabd56a70ae55cc309e04d7acb3de6f41a3d0000000000000000000000000000000002ddc64023f0de2730d3affb695927eaba50ecb91cdf1f369a511a8cc8dae8913ada2d8f27a65e75deb9b8b648e4e2e00000000000000000000000000000000000311ef260debf2310fc31fb8ecc802200e11400909eba24b14d9500ff47c1c36ec540eb970c9262dac947b0c2053d6200000000000000000000000000000000199c19645375dea7602b74301adcfd9af259e1c7c20f377fd10d56b719f7a6e0e57d780c976124e0675c2a54aae3e0f5a8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe600000000000000000000000000000000123fce6b793de0ce2d31f2c7c4218fb20f9db68946a7d57914174ea773d6e6fe1fbb1de141c742e0a8154fa1d81a91f70000000000000000000000000000000019f75536e004a61c6d7f466bfa06ad0c9375a1028eb7746406e7c71e551dba249b5c6284f635fe26989aeea69075b3fa0000000000000000000000000000000013088eab16ec77c7ce7e84236337e395690169a4ed7e44e23d233d36d5d25e6afde794cca2bee88fe749851a71aabe24000000000000000000000000000000000e627130da43a6ede3bd6f2fcdf008c8f5c7b7b1fa56cd3b367d3096317948bda115d732346e73b731d1921a1da6aaa18823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b", + "Expected": "000000000000000000000000000000000e3b85a3d6628e097a3d962f3c9aa37e3c5be43faf2a12cd9830ab33c4a904eda23027735bba563d38ae5ae8b302864b000000000000000000000000000000000c92be62cb091056d981ab8872292017cc22ae4eeb0cee03a60cb5d57d37b264fbed2d752ae9dfd76c0bdde1f1dd10500000000000000000000000000000000019e172b23249a17924b890cda6a65287039d0c32b2c0833409816cb21ceb73ac95928234ccf566287400a2ed7d9de771000000000000000000000000000000001276e206235392fdf65e4ea6210d22eb7afd7783caa0777ff0af719cc1822579d5b82fb4c30f07dffe7d68c649b9e2fd", + "Name": "matter_g2_multiexp_22", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000a3d974770eda8c742e5d380482d36fabe18901b0217485141c3841003aeac4459ee28b280550e4643d0f50862bf2e2000000000000000000000000000000000369c2bf3beae4e8761f6c06d9bf5261bbedb177e609c81c9bd81ed0a17573b6e10e7f0512e06109cacc3d483918ed9400000000000000000000000000000000030253d0a050986f49c77ee20ea8e3e07de3ba72c39ffda877bcfe569eeb29598588f5a7cedd9e2e34491a059ac4e707000000000000000000000000000000000ce201f07353bf82ec894ec66c7012d17f3c7968b28b45e88f091510e1646380f902c1c5b036084f9497e9a91476dc2c9f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c54070000000000000000000000000000000018f493dadbcd93df2c614af310e5aec4fac9e502843b8ca8c3de739315d9e9a380f826e2470c96bffa8789133f458d0a000000000000000000000000000000001768f8c3da107b9ac30a12b99f2f3a0f21483c0be334377733cee6024d85af91b03c7ea1c548b42e7a7869141816917a00000000000000000000000000000000076cfc99c16c270d2f6e34aff84832f9ee6493ab757b6361cc921823fe9c30f1c9b1664b650548dba767616bec0fd5d80000000000000000000000000000000006c5f580c9556ed31847b1a3527ac0b5b5f15b9c9197d3cff061c1cf72dc5c96cb5fe98535a4dca8c4e20c8c02158466c8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000010e2d7eb4e874a9c72a98e4c36701a9fa11051b683ac8ab9ac20d14929d72ff7b92a9048a11bde92dc2696467fcb48e6000000000000000000000000000000000eb29e621e9d0af8f661eb1ba90b307eb542dd84a486568f85e19055bf7b8f0a76d34acf276897a01349eff2c36e4b43000000000000000000000000000000000b5f890f22658b207dea2d721d90a8f5991ea2c5ca06b8d1b293f60959ed424dbd7052e010e594a5ee0feda1e93bcef4000000000000000000000000000000000082cdd4d8452078e8b853f196dd76505ece5e98df3e6a8bbb21f422755af23c5ab261accea48d8e4193d6c884773cf6e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c00000000000000000000000000000000077501a457d5f0946d25a4c5eede1b7fd80d90d502bca47d8cc40ab2f9a6d827d7323e7d4035f3d32b02401141f0a89d000000000000000000000000000000000985410246c1db01b42728ea46758906883586cba5618b66c21a3cf58cb98e7c6e0dfbabc5505d1d11ca9d047fb6d25f000000000000000000000000000000001775f4008f688882d02355b6eaa1ab108f239890f71238b36c76228cf2f575cd15f15293a62a72f6ad0ff17d7e8ae79f0000000000000000000000000000000004b6967a5ee180d8b92e95c5ef26baa56d8509e1acc710237083d19269c1c5a1f2d1680e85f0bf040747be1d301300b0f7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe22000000000000000000000000000000000fee170589e8a3d3fdd93b536347af5002e59e8ef2ac8327a7e9f382560ee9bc36b3f636a3f99fba8be7b5ea3dfbcfc600000000000000000000000000000000032380cb6c043e3f9ef7169da12df1c6529d776b049c7061df660df841840933e514eb7ea3152ddac38daa2c52d66191000000000000000000000000000000000620ebccfd931eb70ec688110975ea24b7ee0f9937841aa1b7bf4f45af88b732b76a26299f0fe48259fdf08abefb4314000000000000000000000000000000000dee6bb8c198363fa4107996331aac07216b82208242c73736be31e14e4e04d97a56a1c22479dd94997acb0d32abd3b0557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000eb60e98d6cb4e4b3e58271d47261d05be892eebb9a37f6831ff19d0bf2fc235e655f0eb9b01494868bc082c58ed82d40000000000000000000000000000000007254a64a0d94340bcc2b0142faab2d73e8189dbaf52ad0c3a9206e802193168b8eb03cb18b0e4f1cc95b98b943910db0000000000000000000000000000000001e0051fafaf454072051d2aa9512ba2367778aa1617cecf6a7f989d69c7627c9070c349d363f56711f172d43f5730cf000000000000000000000000000000000f4141c8a45448fecce09908ddb42f7b5f6b5bb53b9e1ede0417bee327644af5c98470e8b5364642fc7435f84be1ab443313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb7500000000000000000000000000000000049c28e0bc677ccf54f4cb46e953a057ffad624752332fb9ee5295438fd5bd61abd2199a0bb729bb7678cf3077e32ec10000000000000000000000000000000007138a996356ca3f5d63bb5a36dfe901254459ed515e18ec8d91fa747a691b40a19878d9a6f1dc74e4f18374a399d38f000000000000000000000000000000000a621b36a3cf04e6a5cb699fe4ff7fb8b3361207186848e81972fdaecf667ceb35f413bd68772f7c1f77c1d3f43a3d610000000000000000000000000000000010becda5a06f3f077218d4387158e4a1ca5e0ef24d4ed304723ed5dd96da7cc9325f7e4ae16d9d6c348577697aa6017b8faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000163ee307e0d0c3b61ade05a022ce2bf315d820ee8ece60f93d63a150e02be843a2eb2240a4882c29be2c7403932c348e0000000000000000000000000000000001fc8e9ca23e8dc8457df8f255db3b434f52cddaf05819dba7df1c5bfed438f756c8b57442197af18bf83fe9ee2b765200000000000000000000000000000000109cbe5279ccb592bd0b33b1a482df53459c48cd0913549739b784ba7ad32872377c2e3924c4d45064b0cc4764220513000000000000000000000000000000000d789795d556a37a375d83120a022f57e26da6e6f9aa3e40e1f32ed04b50fafc4d40d0b9b20a26e4d230dd789e20823013994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000960654bd6e6a6b2f7d87c3c4d6e3fe6c684a50b62f7acf82a67075139a614c056a41cd49769960e229cf07468fc2dcb000000000000000000000000000000001727f2dbcc8d889127060de0079207eed1e094259b59a20fa42ab2783bfd176da00e61a65709dcd60402398fadf30710000000000000000000000000000000000c17805a01e64c320601e0ef521b6573e9c2eb354157cf0412e5c2b13f826759310907c4b77164f5899958cd30f78c030000000000000000000000000000000010fb286ce797c0429ad3385c709259b55cc962ae02c814e537e5261e897b7ee1b7c660273ec908110f997b166c14f5c181eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000015d96a0f988f4951206aeda63af85910db49ab817c83e218ec74cbbf5f34f81279d8a3f2fd1f3000f73b8c5550af3fd600000000000000000000000000000000186d2eca1cac226227d8981324859126864b84e8dac563b4d92357591c2416c93989cfd9e1ab6ad257dfeb168d829a09000000000000000000000000000000000a8a7247a3b09583cd2d4949721160573f1f88221e6eae833128914555a594f21a3fb2bfe3b1f01f3dee90f7772dc97d00000000000000000000000000000000132361ac1950756549c957c174cab9ef586eb2057a4eb22f49252cae032975f56eb0cb7ea70810afaf5716afde5b88015bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d000000000000000000000000000000001328e22bb83331adb09dbed0a8c58040a3564fcae0ec85794f26c077de69cc0a7555f011e028879cb3aafac4dbecab33000000000000000000000000000000000a93db348adb3886802bab1e993f5d7275360a5b0466845055d5274e44716f3e1d03a6e1796ed4de4c157dc8a2d92c39000000000000000000000000000000000dc0879a8e9556b7d9b6d5dffce5e648f835f10acad3afca7a73b0fdd5d5babaa74a1ca80aa4f6880d9b015501e218a20000000000000000000000000000000003f7ae8207de4a179ae48cffc8c6e926455e46ef9e109c08be3ae7401bd36e0876642ae9ac4fd75a74c67ffb7790e265a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d2000000000000000000000000000000001078f43093602a2dacf9b5dd7ec41d47bff02e0dd27a996b58c73febca06e3d977c2fbd73f63508243696ab5d8b97b980000000000000000000000000000000001841869086e850ad97b3122fa51c437113d2bca14deaef5715c354d3845f6829f6aebe668844352d5af3509c0d8da7800000000000000000000000000000000047c42e83194143b9e977fa1babf80d455fc86cf6cb491ef8306a1c32bbf8c868e11bb3308dd5f65fc2942b3e49ff5c50000000000000000000000000000000000872ce87ecd22b39b14c9036e971a562d51c5122bb10939cdfd1945dd1445ac9f5de06b70931aa5c86cd0fda51b89952c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b200000000000000000000000000000000157820de2a134081eb47b1800ec72630348583d77d512b4c6a8c8e581810471a2f57a8eb6b0af87a91960424009ff124000000000000000000000000000000000378cf11b0a2848b06412aa754ddbee5660730001db073724caf902d4b4894959f035a8838e28554b0efc2388f2b4f27000000000000000000000000000000001301d15f290dd11c3f8e53407195e02dbf8f13e4fe25fe38e84740753b5a0032f8dd07df3ce46ba424f6772b3aa66f4f000000000000000000000000000000000d166040d457187232f8f38f2beb1e0e0864105595764022c282867346166e46eb789786a7ec7c00b0446207e9ac1ec05e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf042275000000000000000000000000000000000793797c5bce4b1cc3bcd751c5ae1d293477af96a0e7c6bd392ab4410f806a53088cafeed51754ee7e60e61dc200ccb00000000000000000000000000000000019d595730af1f3039e37494b86a638a528d8bd24c429e3f8bc97076c7463e7f2618e23bd3f300bc7e7a4674f14f8295d0000000000000000000000000000000008e245c7590888fd8dd58f93332b81f48b6e3acd3cfcf5f3b28df654eae1172f52ef5a121707aa9cb111b0b402d1bfa6000000000000000000000000000000000a7c6403659e1a0c2dc7cc2e9b57a452bf553e96388676f4bf4a6e26b3ca2d3cb82006850d8340dacd65aaa0d20e6fba606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000054c37e8acadcec8a795619647d4cf1081a0592de02bef916f847936a1736e74cc3b7ee018717495def8b4ef1d098fc9000000000000000000000000000000000291d89d152b414fb5e7139d6d0bdc7b5b9de1fc44b49f895ae08718b631f7652bb4a895fa11149b9a9db30c344108ed00000000000000000000000000000000107b30992ced35e4ba874e436bed5d88aadf0a0c944ca3eb8319539017bdd652feb7483ab6c705aa17e845723b2cb46a000000000000000000000000000000000895dd8e04114fde4a4cf19925004a72f617f2ff146dd650a2cdbeb12977dd2b34ea7d655dee16ad9560b144b81212f5c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b85100000000000000000000000000000000005cea2e036a8ce057e4dbe2d9d786eb759c2a75934580480f78d2e228c3150a0a1d8c95ac5013aae3ab6e35f524d37b0000000000000000000000000000000000e18c18884209f9e4fb17431248a5f8d29c616a58af16e949f4317c2e117b80d531a39800dc70f6b161b98ba040a8af0000000000000000000000000000000007c42ce885d1bae906128589b72f2e6c18e4eeacb78c853e923e6eb785c073b6490b2f6b3dff2276916d96770ad5019800000000000000000000000000000000132d809c37c341eb0304ec933a6b11bf9ac0d2a13ead818ab6ee03ccc94160b405066381dcdb13b6ee3f5dca48ee10ef47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455", + "Expected": "0000000000000000000000000000000009406918e2dd6f06f4782ed110e29516a911f47133ad8adc58f5780de916a8973ad60e05ba931d66de7545a92f388c20000000000000000000000000000000000041cbd52cad2a5f4c8353c7153b5711ec23fa8bfa2f34f5e1a16d8a14cfd47c237766880debb992a05ba9ed0353beea0000000000000000000000000000000017d4211c827379b310956371129011a92d62d11f0ee5b0cbad9eea2d3f2a95d364717713fd0c544747338725adf27248000000000000000000000000000000000a61903fb81064614c9c6894c7f3954aace7611cedf6bab8e751f0c203bcab827d296016947c071d7b6ccc742e28ee9f", + "Name": "matter_g2_multiexp_23", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007f90813f8c3eabcef04dc1bc9bbafe1dafe220e2db24e4b714aab2b164d7ec9df3e6a3f903e8b7b96df2ad8297381d2000000000000000000000000000000000e34371e51c4c952a0f38c4aaa5fc2324971ade310af2f36ed511fc5fd7a602a551ef77775fcd0f1fccc718710239561000000000000000000000000000000000787edf7a6ed6b50afcd7c0d3876d8919273428bc49833e3503f650e48e788b15cd82eab2672f612025d796bb62d72bb0000000000000000000000000000000006b49e631ace4f72c959919df5d64c537537ccaa3d1890ea9a1d70f9eacbaaa2ec361edf2d4880c9810976c6073028bc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a000000000000000000000000000000000cb63d7eef2d6614d1f629756b3a619a221033207d1621e4ce4791db4248500649b91ff07cd2f1f06eae3a9be5b6af080000000000000000000000000000000019aafbe56da1569959019033e8cc785c9b98bba6b069603969e7ff1150f023706b461913ea7949306a44c3b7d199e86e0000000000000000000000000000000005cdc3a7004f7a7f79ffbf4c4ba7c5dc30ecc62f270a5c231406fa63d82fc64f45e94779cac851ff8443040fd3b2ea6200000000000000000000000000000000040f30dc98e8668194c9278b189e0c0f7b76a4c686ce26a4a96b93190938f07c5b813670e206eb6b5da29624a1b6314ba59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000a5a1bc231f803ae272e497f812ebb663c2ce8b43a366717fc6349264823ca93e29e30762c1a366d8680f81838907f59000000000000000000000000000000000a88fd59ee380449d632d7e1b926210d984d5298fa807570a63a63828cfa55c6e2f01b7745848281795dae36e562181b00000000000000000000000000000000025ad34537909e07beaaff09f22e91e76d93c668d1b45cf6845ab8ba0129e417b758e85a7100a31a9037e307f454bd370000000000000000000000000000000013590106126231b1c616a5dd7aa7ed6946aacdacec963b507907950d6ea11cf1f5b59f819a43eeebaf51a1faa7daa8e719ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c5000000000000000000000000000000000938d43b9747c926c3e2dfaca2d6f1e6d61d5a621ae08c66a5baf33d9241771509689f9ea7d75af607d76b66faa8fbc2000000000000000000000000000000001889a48a74966b9748f4a6128dc3d75a69499db1ba1bc9aa3a9428f0efa898b5f78a9e2dae942d3794ab3d1157a1d305000000000000000000000000000000001129c9bf343f476541980b85229c5c25289ca62173e29b75de431b572c8f01f64ec1aa4625dff9e7df535194c7f4e6e7000000000000000000000000000000000fe95c71f703dcc71cf409b332f66fd69c330758d41832236a510ec4bd9a28c4732434d4c3f97445e6301e3070153dbbb8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc10000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8dedf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d000000000000000000000000000000000b419b675ccee2509daf66e5da4031b08792e1181140b30489ae21f7925305d8cdd8a104580ae5938586d6b8e74f750f0000000000000000000000000000000012e070ab7118991a20b27f1a87fba1f5815665d76269f0d3d460a6b701e57ffdb4fed2c53fa63a3121c74f67e770f31100000000000000000000000000000000124218ca85f235eac3471e0acdabf73f79afdd4bbc159c1e34c641b97f03735e4c3430264f2d94f640486488dd1067380000000000000000000000000000000011c24f4fa1862779f22a628edf9d3cebe0f7593964b642f889201ae85e8fa01e00e48355053f5a7c6d920dcf6a7ee1d60dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd000000000000000000000000000000000266bf0d9d5a4fc713dc0fcc6ea6edae0b326e22cd97bc49c48a7ba398fc87d7a0c7141ba24d80df454de66c2b5a55fb000000000000000000000000000000000aa8f95c7cd61733b0a260149d6608a73d6c1f989afa8cb2aa4098e1fb5a66b4ad5a5c1c4d901aa79812385fd507f02e000000000000000000000000000000000a6b4929df13e1fe7f0a0cf699a7fbfaa97d7527cc3ea1f728ba59def2e75fcf3490199bd42e93b7d47985a307add07c000000000000000000000000000000001719321981d2085ba31c9fb131d6b79c7df5d10d6ad0b5015454329697860121e781093fdde1f19e897dd6f2c272f87a109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d37000000000000000000000000000000000431002c9926aa7d2b06412f544a868a7d48fb5f077dfd098febeeafc28b876c434daec809e5cbf50ff2395ae7e456560000000000000000000000000000000005a15f713b6eafb09495cfb1c89e9421515a07a99ca0f208883f11c430ffe6f2592dbc41bcee5db36385a26f67cd26bb0000000000000000000000000000000008dd30fdd7767486844967c5da0803b52282178287b8ef28e14f07b487132fea3a82d86d414b4d0a25b3dc538be11b500000000000000000000000000000000002dcee67e2d17b3106dcb9f4117456a037ae1996e8f7a09b179baab1ee8345c6d01eae554d3f40da86bd79a04702fbf76326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a000000000000000000000000000000001629fcc374e99fa8303a715fb5077f266b13367bbc0098b5463d3298c0892f83127d6b7f751446575b88858bc742586c000000000000000000000000000000001100783c10618752d25c235e1e76dc64db94adce05651fb8df0a5ee7c299d35b1319f7009b857892ddf9e90c91f7d23b0000000000000000000000000000000000ab6996e4935131becd5df288dacfad1e69b41e200ca7dc841ecc180a81b9d2ca14fc8a76a4e7bd6f924bb9f473de62000000000000000000000000000000000ae9b22f8dff29e5e0a2ec5b5641f53fb5e1ca03130b49d0c26696ca4b439a9d998d9a364ac9cc5ec52df699318cffeae005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d0000000000000000000000000000000019c557ae1c12ff8a7c00b7c9e4bc3d65c92753549c193311a38a84bccfc090052a2219461a9691affe2d67ea4357cdeb000000000000000000000000000000000cd35c5dd126bd4b90dd671f29953c5a49a14b6b3fe946991416edf235c3eb3d574613d27b05cd879518fa7dda3ed39a000000000000000000000000000000000224392063b0825fd332bbede23588c1912e7670a013a99da5507f650dc4284431698a5b4e8c180269af8bb30e4fc8450000000000000000000000000000000002ab8d3250d4bb8ceecc8ca2003f91420d0ef8a7dbc2361e5e7fbfcb59471a4c525856bf796a2c2608d219d215cf83fe3917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000000911417908c2bfe4f63a388f699b31b47df1ea0ec289ee3f96ffd0c71f3deade00d1841aa56b4bebc2adcd3068adf920000000000000000000000000000000005467c7e58e82089fa285c28ea22c759c7806d86fbdcdcc8e09e847d6330922a61bc331ae3b5acce777b7809ca98213f0000000000000000000000000000000010f376fb47933b1f701dd81cebaebb2d8d8f5510a26fb3e9e156ac5ecf2b943c5fa2812d52da542e6c335abad8ecce3c000000000000000000000000000000000dcbf467432acfa4eb9ba11a7cdf02f9110f44ac371128ff8f1f98fc70e4554f057a4608180bfa54d99fd2da010594f6f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000179aaa7119f6fb986714c03b6db16f25eca7172d24cbdd318bebb633bf08920f9e2a8136c94e3ec7c19e57ab51531b3f0000000000000000000000000000000005937c484213ab5b2ca8ed1c5c90e8d2a2f1bac044b88c04b301ff2fdbe67dc4ea42779d919ad510cabfa2ccd178cd9f00000000000000000000000000000000183cc23fd64514ead63f55d375a07af7cf2a56aca64a887dcc542f8a396468a6abc776170a5d4b4bbcd4dbac285e7ffe000000000000000000000000000000000ce12228dec2f84219904d9ac7923f122a99803a9b34749ca68ba385c178811685c19a492aca2e1123ee82a8a9cb90fc3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb4420000000000000000000000000000000009612bf9130e17110f8b15aa6f3317071daf3433bf6d008c383bd5c2fdc7ca03f25ff4cdb483de3c84c0ef9e579f38c6000000000000000000000000000000000c40172540a7e20eeedfe02c37aabac07165cbf04830f20fa76fe8b05c826e7762c9f7567a0fb972212bf736e627948a000000000000000000000000000000000f49e5b1929ad3ed5c07670c471710baa24e8478a50f72a5b7bbc23a66cff91d30a3d68961fbc2e6e8003d08196f325c0000000000000000000000000000000004ba098f915ba9e934384682648ed8d4e1cbaae60d596655fcd9c05f4b049ba0d278730dba5ce3fd4892531a3153bb955ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e99188000000000000000000000000000000000449b15ecec6d6fe5cd32437b54218f62527157aa6344c635fcec8f8305c8b6e44c93105984e0832536237606f07792e0000000000000000000000000000000011e40e8aaf75f5ff8e4040f725ac27693d7b24805a2539ff54b3a6e90c048875ea9609fb8fb3d8de63ca1118876c172400000000000000000000000000000000006ef2a24445f728b53cbf01e5b076acfa7761a84d8261cf1a1b99cc32f330f32fa5ded83d5cd51cc284207adb2451ee000000000000000000000000000000000977966380e772670447b15ad9917035273eb71a21c37607a761aaec808909fcfed50679769aee1573d73cd241de6624877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000074e475c0ff1a51a24be3c964c45c41f767f890dec82712d92a965be504fee43fcc6c0684b2b17c5b294a3eb7ceff1cb000000000000000000000000000000000597b7dd287f3fb27e35a9e4e1718b6b1a4addf9e95e93aeaa25aa34023669368b794a08fdb178d9bcda2738534d1962000000000000000000000000000000000a492d648393bfa317165ccb552e045fefce5b3444d5ff770f43a08a68efefe7fce1216114ed1495cd00f832538198180000000000000000000000000000000003d85cea8063828ff025ba599bdf1efe0412ed5ce06ad5faa841c6400e4eeb6aea1470d48f4e66fc768d7e7bfebedb37145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f6070000000000000000000000000000000008ecc3dd40da2a7a348b4817d9c84242f2f07c5d0ef810dc08311e9d4090d6d96d68b6c725ee6c24de076c71754bc4b50000000000000000000000000000000018fb3a1dc4e0dd9227fba310236a6db7953f0b716fa995b928a2a8de38edb97eca09fe2ab385037dfdcda2ee577e677900000000000000000000000000000000062fce7fe7810273a80760d9f4b3be9e7c821f38ed3e075210d3aac6aa7a763e3cda56465f88b34540b408ac850742080000000000000000000000000000000006fa94466cc47990a80ae6a310ea765590a0e646b5988925f03cc7e30f04fc0a8044b403212290b2fc46c77e84a9028dde4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88", + "Expected": "000000000000000000000000000000000f41bad0a932e28096e51482c646dbdf294aa7b91e0ec258670e7674864765c76989a936fb440bfbf4268a49f450d3230000000000000000000000000000000018282b76521db98f589b1c14e603b6f5d27af357553bca761189a38a944a11c66480f7ddd89d17e4aeddc8d78a2b3a0d00000000000000000000000000000000007efc4a90dd97f1312047ac78a3163dc014c42a44c7054daeefd5b72cd0488832cb6396e02ccff09e4171d790954fcd000000000000000000000000000000000e790fe8323fffc96705a42ca071532d5359641ff7cf8714789c9c578717a054c811cdb581df8b6a43729c6c3e3255ab", + "Name": "matter_g2_multiexp_24", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001304e0ce6a4baa6e0545fdb314523fc91f73eee157249b94f284ba7390b12b23b1c849c45a563ac82b62a2c48aec24e1000000000000000000000000000000000a2d0e9e222db70d49d1e85f587d35bdf5e8328aad14343d296f95b152a79c83a4858cafc350a5df1ad0194c49bb929400000000000000000000000000000000199efb09b34d0699eb4bc1c57fef9cc5d98453bf522c504fe7897e22bd0596a3a6c310eb351e15e3f6609b074b240f7d0000000000000000000000000000000016b69f12ce30ad1a65150094e29d4cd82fbce5dc343517ba9e5d89245ec083c44af9a3dad2169f713d3b01fdf70d20642576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e0044839000000000000000000000000000000000b3ce4ac12861052c602e71906a7c9f3e2186bd2b6eaaf222d8e80b48baee537065ce78372ed936e6728b9642ba1fdb9000000000000000000000000000000000e8186561d23515bc58c77769c93db76dc9c62bb715b283cbfb71462451120a6ded736cd8a292a6799fbad7617d9aa84000000000000000000000000000000000368a6dbc7daaab0a786257c813b1a25c97468732c27cc759fb921cbc3c9a37a46d7dd0298771c447d36ef0a10579ff5000000000000000000000000000000001348d5e34cbe54e3a6b357c4e651acb82d2dc40ef9ed8bb89f0cdf0882ec6a737998f4e4dd61e296d101cbaabccdc3e779f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000004ebb53c462239a78bf13f29856ddc4d78645c457a656f3cccec9d3c032ec19c26488f39e0f5bf0d38424f9e3a9bcc870000000000000000000000000000000002fe1949365831f7c38b1cd6cf2e22345c4ce40cd73def77889c214d1077d70e39578e8be4fe5998f59d47cca7917280000000000000000000000000000000001152f2df1512013a42ac056b75802bc35c1883efb345cefda8276c594b061a0b0f4a49d8bafa6fe251658ee76b2493cc00000000000000000000000000000000094f90cb386f7933b2ffcdba5e46e09cbd7d537c12bc223e76d3a88ce9063a7b3574d3306365d65dd4c6505f1dceea53300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f6571500000000000000000000000000000000169d9469c53e55768c9312680ee82ee581727e28cdb1d6fcdca25d0c03f3da2ad6572039f12c90b09cdb843bc506e07200000000000000000000000000000000174528257f6d3542f754ecbe97eeeea7d196ee4dd01852f6cbad87fbeb4dd7d3799588f17aad129a15549bb787468772000000000000000000000000000000000c9ab635bdaca1c488538c0830453ec6ab3b2b62447c03ff6ffd2712bf62e02a63c76c79d41644ea412e733128685c45000000000000000000000000000000000172ef0fda359bab149c8c04f583f4ace4d1b148426e993996d278f79ed2c6d3933d6cc5fb62ec4869aadc773d3084ca0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000017060fa73b58957d12b3996d67b7baa8b7f0943ad52e80e5c4f8830d33dc74c0a39e08594b647945b402299ca861f7b10000000000000000000000000000000001efdc7f783f9977392e2797a3e0bed222d5b661d056aa0c7e04a493bb9b18048bf72aded134941ece78d63df0a0868d0000000000000000000000000000000011355198320af05f2121939e6489f31e9e13b3cbb2cb30c9e675854cb8ec038f80aa2f4b6f995774b36f5f1b6a84298f00000000000000000000000000000000172e18c490d0cd5ba2449362c0ab296212dbe69ac25515d0f91941d300051320f067f946dcaf999554f55f1f616adc0f3685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae82060000000000000000000000000000000005854f4dba62d1dbbf3ae16f70792f1bb39f111309b454a6400d2916e619d4f70764ecfda7eae5c28cf1d178ad53fe6d000000000000000000000000000000000ed0bad1f5d69a0e621d137746a9ecc764931ab89f24ca827e0340ddc03571ed697f63e79cc58b946e8462099ce4b1d70000000000000000000000000000000011de76edd1cc2f9ba06b98593a24a7a011f2701b451ea3ccd04361ddb678e06d91a676e3f11b62c68cfc05242cb8a859000000000000000000000000000000000599726b5f5b93d414f9310383ed9414e4675d644f83ebaa63dceb2bddc7dcfcbc17c7aaaccd0ee32b0875952554b4e660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc25400000000000000000000000000000000031110347cbea2756b5fdd549d6c0b8f4036f5718d41dcd6c854a91c9df29bd464774be479d0efcb8a3f82cc7441a6c8000000000000000000000000000000000e24a52dccfdda3689c87395e45dbd46156676d9eb2cc09dab22ef7ff0acf5ea243ff117c82b147994d65aee8605b2fb000000000000000000000000000000000e0cd6ea0bffc591c13c48bca0782fecf8e128b0b842aecb06f803a223d32cc350db869b7a77f8e31b05f36bddd587ea00000000000000000000000000000000042ff4ab4596d610638ad23eea904a82701cdf61f9e2dc5832a70e11e717711a2d0e72f32f74706d385a9567426b4713addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001517efd853800946aa37868b525e58fb488bb69755ccec806afca2d21bd3a30ba46c39cdf694ad0ca92841760437c3c1000000000000000000000000000000000e5591c339e88544660380d6362f4119c5596f910d4ceb96ccd4c4d9672efc50805b6fedffa0a48d126aae69b241d3640000000000000000000000000000000010ea5babb0de734641f63eed2eba6124377b5c55e429987917c0bd109d7904766a10b0d2dd123413816d0fbabe25050b0000000000000000000000000000000000efc89ee2ffa56193129062ca55a3350bf50e8fc7d586fae3636a70e3577987fb0f8674d383def4b41225e490d3d81528416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae8000000000000000000000000000000000dae4277d62e3f3dfffb80818a5ba5c371a48d73b92d69a168ebab897ae8be206fdf776e9f955136d7f7f7b2903040270000000000000000000000000000000010ca635ee2e49cd6c951d75ffddd11557432726d26564239c611b139329a28812afe21f094c0585675f4f233233743050000000000000000000000000000000012378b2ec31119e508fd9ae0ccc4c2603b6820283284a278fe16864e5a18cf7992d850c1d6ebd1253103c219bd95ca4c0000000000000000000000000000000018cac4f0660240045214034cfd8a7e40bf0aa12f97a23c4e27db0e05bb25f4d755276a91a4e882a0be63437a522943ab78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000002fd5571c818322d207d58fe0a898a045a26c95c2490765dc9ac663a0de78ef5fbd05b20ea96dc5388d5b2ccf13a5e320000000000000000000000000000000006ff29ccb768da45061ba4e01c90459ededa5e79513917401e7e37151095ccd4656aeb9cb7c083cf27b69377295934cc000000000000000000000000000000000414d34eac47430495be735eb5c4b1a68372abeb43658f27613a9c8b78f17d9074174a8deeeebb1f9cda5d6198bdf89d0000000000000000000000000000000010b11bf63b8c39c1370e8fdbfdcd149fea88eaf1c0a94a51bdd061e4c41abc626a448030bf9ba880032e9f1642caabae871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce00000000000000000000000000000000023eaa08a44eebae674434b013ae9992c75690a3d0de53e4b05d1c0dff249feb24a12432bcb5defe25ee4e44a56b27eb000000000000000000000000000000000f146ac27e685cca04afe8fc58fe853825f5b0009e8831eb0d0121decec23b25bf8521da2fab1508a3ad8254865fbee70000000000000000000000000000000004af1a525d3c33e0b1629cbdb90c56a88d70a28037c87db81c59bcbc811c8f0b98aa9dd574436c9f600c0e8e2d194c0400000000000000000000000000000000170efb5e0e69e46a21ec3b972265bc04b9d5ee926254f61c0e18fed013922e00f1897cf69889576bb5d54810486e7f2776ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000111c832a96329d6db203fc8b6bb5b7db01521529c91c74d9cd71dc78d067b36cb7eabf1af80129a7a3f44b719235927400000000000000000000000000000000097339c17816795238629d4ca6c243a14e9e227e9bfc30370dbb9e1475f6d03020dc35559675121792436bacdf9eac4a000000000000000000000000000000000805870a1efd1fc34c9b576b77418ee8c0d36aa9caf9994a051e1d55b49275f34cdb55edc74ffc267c5776c8d0e113ed0000000000000000000000000000000001513afdfc2f000e3b725fcd0428fe72ab2413ff2aa91b44458a5249c9a160ee27bca01d2fc2e230f4a80454769961af95ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c4000000000000000000000000000000000485b3b1f812b4a28ac87d16f86d8d634e85d49d6dc460646e1224de662e906002c44a1a269c3bc011fd22afeb2d58df0000000000000000000000000000000013ba0752444a794cd00c99eceae51e61c382d0abb26e5e0e595d59321447400e8a8f7d97390bd217fb50bc22cef34b2300000000000000000000000000000000184515a36024d0bf71d9fa4cc5165363ff94ee9f8579bca653ebc0620a9d3146fba70a2f4a9f6bd3777101de0d32e327000000000000000000000000000000000e041422088c0343f7704e726d65ccc4216c4a1bde3668108983643663cf0249e992f9acde2dd8ff478dd26cd8d9434d06d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db0000000000000000000000000000000005bbb0c55fdbc59992c83fc0ff03f677e58b6de6f8649141d88963ebfead9383d692015a7b765b727eacb6de250351ad00000000000000000000000000000000183057eca610b8e07fffb60d21bf2eb87981e6e881bba04ceff420ca38228fce2f94d40a993e2aef09e209f3990dd14a000000000000000000000000000000001231bc55242bea6b589cedd1d82621fb71c606ca9306b268379dbf83ddb1420dea228ffc05cd8b67c38206f3f006ef18000000000000000000000000000000000f2c943e7a8b0ee00fc4e4ba912b94f68f504d2783babb90a3781b666b31bd161af2f97a77813eab9ebba76040b04155257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f09800000000000000000000000000000000054bd97b9cc979006f734ec433e215a4e8afe468e69173384bc895e10ead3749d991ff8ff203abff30bf5cc0d2fc8c6c00000000000000000000000000000000066b73a98d5f5ae140a5784c5594892c849aa7f2db3b5798643f755743d401ca745d810fad5f4a33e5b3cf0fd7d96f7b00000000000000000000000000000000007caea93ff5cc6ffc033717220a215ac4ed7283945ae77e62320a0bde13f2153dc8dd401297cd124b4c67a4f3839dfc00000000000000000000000000000000094568035ffff439e3d3201466f3a1d43414e3f6455627c5479c8b7c55130ccaa5007ace7ef6a2b3e2e5a4c9543dad9163d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc30000000000000000000000000000000015eeef8bcbfac04112931e186f6fd48b7a8ea891ab364ce8266c5fd15f072f08fb3655e324795df182a5ed1c917a5db000000000000000000000000000000000028916fcb3b30a7f95321a0998e544f9f4f578be7a9f866cf72d6b8baccd93f8935f105ed26aceebb3f9c96073a8be180000000000000000000000000000000012b11f356a7e32f3d9281a8999363aca0ae5c1a058724cefb51583e5f217257d47ca76d21e54ab62260796b95f9d3ad0000000000000000000000000000000000d83c75c36cc8dea4aab47823edd26b4492da39b93a15fa454aed4175f28a025ad2c576ef2d76a66e666bedae95cef1a7a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c", + "Expected": "00000000000000000000000000000000059443f363ef0c65973d36469ac651eec6e52485a07a6d28112f4d0711802d182b7e6fc56d4f1aae51fe1c549247d885000000000000000000000000000000000d22118a6f1cd06ee14c63f0e005076bfb061bb85ed184b5444c08ed9dc35f77217b6daafeac89a973f2c73f00e0d3c800000000000000000000000000000000180430caa9917cbb40e3ada2de8d685b4daa99639669a643b8f5cf9a4a55d6162e9fd7f5d4989a1a6588feb0273669b90000000000000000000000000000000015d01fba1192f0f1acf8fb32fe790f0448c6563cf8ef5505d9378fa2fdd38bd99ba938077f71bb8eaa91a99e787e840b", + "Name": "matter_g2_multiexp_25", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012d948b5268524659e29cd407dbbe8f529e608193ab9452f936b2f6fc0b81d3a63a0e929329e2d89b5475dc2d73ebd8a000000000000000000000000000000001219e20a081837f4d4e33bdffda08a946bb9cd876e42e2f561ebfd18ec439e0104b43de61f47b8b7a0c346c33e632be60000000000000000000000000000000000a135c72c45f254cc1c260af803e14cd0f89c2ac3029629a86b05acd3440465aafa4cf84e69551ae772bb55802a90ef00000000000000000000000000000000052750c3a99974f9044531dee9129110b99572cf283b61e6606f1137a87de7344bf01d2ac2f8a1db8d815b6d9e7511fa26a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c00000000000000000000000000000000136b2f21aba94bbc8e5235951b1b186fd4ad221e6ecbea5c7279cc8ee8b01edecedddf48cca47624ee9b155a4c167f140000000000000000000000000000000019852d2bc9c8abc92503f3e7eec9fb20df108c23643ba8a2fe16c2cf085bb4ac079d3f065a1241067daaf401b662288b00000000000000000000000000000000018bf1a4e74ac9507b97a990f3a41cbae3f32e263e9937a8a62679bee93296ee5cd25110833eb5d136425bae0e9dcb8100000000000000000000000000000000096ae4bfaaf4f18d3e987d9f287fdd3dc9b497cc84867e757da52bd5f58688403e1c9cb432a2eb87e239879d52990ab5f1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b60000000000000000000000000000000016ebc2ee18515354b7af5d924c895ffd5556ad088560f89c59a4ceec229279d4075f732b884a6ef2bb2eddc11d27572500000000000000000000000000000000110282084ab6f3e76eeb9e5e8c56749992913c2404b003df9c2d01d72751f879538d23f612c8faabbccff45185f4c6a40000000000000000000000000000000017476677ebf052d13f60ac0ec5e572c398f1a478d60ce92a3de88a74a28688d786d30b1ea8008409e45697db0adc628c000000000000000000000000000000000a5e4239d938bfc7c05f3b3a850ebd5f7784eee7aca48c861eb4bdb1ce6321fc9c6bba997e143aba13a42f69ea14937397fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d670000000000000000000000000000000010c703e31f2d488812a387596c797d8d414e406bd82f238cea50a459d842502e11220ad82fce5dd36635792ff5770bc50000000000000000000000000000000010c11caa640708850e1dddd48bae22961a45029971d823b53030979b7d8ef2eaf2ed055436105697c5b0b31b1a9d0a7a0000000000000000000000000000000006b98568b2b7f0aada97310f7e14084a14bffe580ec65bc8fe5d19c6213c45dc1b8e1da5c6c1b8555729f6c781575278000000000000000000000000000000000f2c506f3e41c28a748656d1dfd87e812b3ba21815637e497a30eca4fc5de18257846f12b67919dd2d739477cf5ed0ae8bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af400000000000000000000000000000000102b6d561172adc9316b3ec11f05e66e7affb1bdc70a364faffa57aa5938c2ac08863be8fe79ce3f627558fcb2ab1230000000000000000000000000000000000c5e72c271a1ee186d443a96d53f0ba0ce226c76aff2a7c3215c2110f96cb3301bd586f509edc45cd20e662756897b78000000000000000000000000000000000d546fbf485bb283a04fa05aa962ae8d77ec4d26f749d83b208f77247778e32a9a2f1483bd84488806e27b13eabf41d30000000000000000000000000000000005a42c6ce8d43d122bbf984e9777f5d1c15057f27e70fef44b97c2c6e7e2e303fcbad643027b7ff3167916f21a723ea98da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000001788323aafb95f8761f87f771fa05a8e49be71e397849daef5877a7f486af13fa651be7a93bdd9465df7be4ff65825fd0000000000000000000000000000000014b7a56f3f7c12e39be76b3872c1ee648f62f9cb6a1842d869e00a5dc2ac8cb4ecd96ec2483d5eade5b0f9113133bb050000000000000000000000000000000009a30623632b757ae8d03ced0c1fdd1877718f8d84f34ebb42426284f73bb7e8abc31a5e5ded57a02d08adaa90abfb2600000000000000000000000000000000020b47acafefce7f617081e22b2bfc566acec6d2cad5063a79cf33e02cc8931bb698b72184a11fab73e0bb0aaec76c61d6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000d1190466f0e8f03d2cac4a5e63a13d7c6d0cac9f2065295e2de818773199d731f8cb7b2be5f6ef0a246401b345a2d560000000000000000000000000000000007d9c5d187494df79c25b6292527b0d6d8c50b6467bf76a1a1316556e48159a3b5dbdbd9fb0bb901d857f61f423d15db0000000000000000000000000000000013e4401fe76e3f1ef73bd244189cdc81fcc152f71449c11aab24c4fa1d123c5aa8c68a2d10fe88c1c6631778dc0bcd420000000000000000000000000000000004ccffb4296883b8690b2f3fe17e4e9ab24390084ac917ed28fa1e04b9758373abd348290d24c915dfcaf0649ddf5a87e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b10000000000000000000000000000000000f4baa5e531ae462b95362292d5366daa89f2fb2707c58568c094c58578e84a8d253fe1de26b917b84635c0aac3a63300000000000000000000000000000000109057e5c5451eb9f85b95aa5ed2615d2faccd0539b1e4481923e04cbdbd2ea9290969022cfa508d3fd050549c74940d0000000000000000000000000000000001c3e147ad9c31927207f2344fedd541316f4010e3de194f924c4a1450a221285b76ff1894f8b1670731007f44965100000000000000000000000000000000000909cdf5c56dc177daa1f3fd7cc31d79a4f6dfcd462c07812cdf629426b75bdaa297b9d7e67aefdbb58175a21e29edada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000009b4c6bd1c460d2e93febfe523c1d54d6bf6af50838e7a10b732c1be8748a0752a517e7103d0ffa4507b086626fbfa8a0000000000000000000000000000000015bf2c13891dfa8dba35b5da1235563d4ee1dac33e89006f5c9fcf06f2fef7b31ca845bcaa8ac608046e8b01c8a61fd2000000000000000000000000000000001898dfd6a0618df821474b90542f261c1febbf2e566978b0fafca44f6dadc57202f88366b19d2c955e4291ac21beab520000000000000000000000000000000019287e1ac6b3eaf412e58511b40d87558e7cbf90dc8af2f5d33825b40fd2f2425d0be3a05d0a49076f4114350dcc601eb929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf0000000000000000000000000000000000c0993401c024d32cecc0d86d4cc52c200e59acb34fee2ae052837f467905e736a1118260ee12a963ca2df6e1a6c9d0a000000000000000000000000000000000103f78f0e7c9a5628a66efa91f150a87e67623ded2560aef278a8caab017fdcf181981952b450c67e3b4d3f362822a80000000000000000000000000000000000df01ff335f23652f1c34480d23c62d705572321c0e7fe92556e033dd3cf5b78a3d554585403a7f3c71744c20d17579000000000000000000000000000000000a0e2c9e2e34e5cb36e96b29231f702abb127a011c7ea3e21d59e5c55f745a02039a68d59ce8e29afac0752d1939106936999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f000000000000000000000000000000000eabff0e6ed9dc358881796441c48e722ea171f26011ab898c5a06758f61a629ae21d5a2595a22dc9855fd2e516b30fe0000000000000000000000000000000002732155a7a2791078dedfedfd3381281554c389bf9b5baa47593153a2acfd22a08557d7a1d49be298e416051b9137dd00000000000000000000000000000000116faa2e2a261e6a3e4de6ad80d75ee05aebae47872e2eed9cd91aafb94a706de673a05f1b86c0b0131cf148a90b2b7900000000000000000000000000000000009a04c09c2a4fce22d237bbe930392dfbbe5c82d480abefbb3be876015e2f5889a0922df6d00d4e94be0e9fb8d2f4a1fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000002a6402848507062e5c5d63b1207a1a41d3b941d21792391f2feff95035f1b4625541770fa5e0f87585cfca670976533000000000000000000000000000000000904095ce640605c957715e378ed733ddf1f94d3beb63543a50c8922ab9f8092755fcc65e2a1ed9232c8cddcb5816371000000000000000000000000000000000ec62b911b08d3e8618880c3784685b2c6cbb07a4aa4e348ab72e4f918152622ddd7748bfcd79f35675cb956d11fcd650000000000000000000000000000000013f651e9104d48a081cef2ae0648816b2b4b5f644a791514e94a8e3dd3001099c27d1f9860337ced1b177b4ad7cd5866c36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000016334abef2a21b9c1926b2086075471bc2d2d2f66b963a41623af91fd2fd50f254c008fa3bad6b53658c2486edcc94aa000000000000000000000000000000001063002a5d17aab2bbb5da49e8bde63a1f3c4dcbc8800f9487f47c6d707109c86d3cf7f9171643418b195e50d7483af4000000000000000000000000000000001213004f31fdd0b0df5d8e3677c4f48624691e2534c02881c6cc6875b9abaee56ed5739c2acd66cb1b10553ba066ef1a000000000000000000000000000000000fb7659081cfcf8beaed9c1daf9e92702977c37a54376597d897082a25f9882f1ae14e7724c0aeb9e002dee708c6b4eb0f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e400000000000000000000000000000000078f06bdfcbc7c0cc491fdc8069314c8a395983f9a2e5c2d1bec360f36e365da377885f897d8d711e33270e3ef9dc4d80000000000000000000000000000000007d43394d5175e020b3a5d768b60ec763d60cb1bb37c0343930fa82e92fb1becde0a178c4565df320824bdadd54ecabb0000000000000000000000000000000012f9fc96355721c35a6f5439065d89cfca5345622b3f38041b41c036b9bc6bcc980498ddc7bcf807e1b97831c099505300000000000000000000000000000000105307b482467b881a59eda1434e31dffdea531603fd3c460aa8d4f58d32668228bfa585bbba2dae7346141af59190e2c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba000000000000000000000000000000001576521fb3be8c3178549969e54bb17b0a3546ac4aacb470e935359e36bea4f43dacc06c151a527f441ab9616e07f7b90000000000000000000000000000000018dff940a21768ee9b9450fee7259663bb29af645bda2acb4d43f4e9d631e0127073f2db04293266e6fd6fd3d005e3f0000000000000000000000000000000000ca6a977016c1ebf52827a5ad52e5efcf7517ccc3ff40df8141f6335fb6c77c3fb8f6b0dcdba2596ded7c3838577e28000000000000000000000000000000000150cc33b55586fac30d316cad6580cee0a070900fe7d540167560b79f4cf9690a5e02cfce9946cf67a95dedc9a7d9aa35398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc", + "Expected": "000000000000000000000000000000000adf84ea7681c442731be8db9508a050d4689c42af8f7472491655356a86fd9a0afd91435bdbaee98db3b1d8f26203fe00000000000000000000000000000000090a7dadc0a14df481e44e2005c9ddc6e026ce2afaba7badd18492bd3a338dffc349b4a339f56221eb795314252d53640000000000000000000000000000000007390fbc06077cd167f53a16f347eaf50ce8c9d111afeabf3a672687441b80e66a66ba3fdb28e2ca8282f3ae3dc81be80000000000000000000000000000000001998f98e85285a428a2860c22a00d0b317854da4190dcb4dcd182ac249e13c51f5d5df5db6a0fd61d01232cbcacd5a1", + "Name": "matter_g2_multiexp_26", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c868a2cce65692f83eedbfeef6f9823ae9382fa5ed23395ff2444807e72403d4f6ac861ecd3a51db841889fe22a033700000000000000000000000000000000111c9aa53da85a63ce1870b963415f0d5f812e061aa6bff57425038d1b65fff57a78bdb963bf2450001525a93011a28e0000000000000000000000000000000011770810c16367d075c695981dfa69b072b82b034f8ac371f26bb157f9f9d667aa555a5c6baca69d08f421cd569faec2000000000000000000000000000000000df6146b29bc8226dccfc95a325d791b30cba8ff2495434d75622b170a634ec7995c5b4c689c73582ca861dd21d8e1e49f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000000fb30aac6502ecdd3544f1879bf1b3f4c19fb897de6c3a7cbf08f36244aa8e9dea8aaf781f7509d3ece16ca144a601e40000000000000000000000000000000012304be931a1d7440d67740f50b1a281468b412e8b6c54c62b993ec609012c7056fc7e62405c7530e8f5136cacb5926f00000000000000000000000000000000182320f5d9211c08f3ba5d40ccca45cb0060a6d362b4422084617b9d8212e94a9b878294ac176b8f0e959bc124a753310000000000000000000000000000000010be6678910072ed9f932ab01a2d72f7374a2cc82bbd86a6006a495272aa89fd655e6719ab8b3a0643d002021f7b7ebb4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000f1ffed9514ee81e9b3fef4162c8f4980fe0429e57bbc224a9c9976cef7d26ab61ea7b0cd42eda30da97e3f8f5ab5f0600000000000000000000000000000000035b9b349b531d85361a4618a172b510dbc924df671b3fa707b474d0d8b17d30dc8ed208d66be91dcb7632d2f05ce31d00000000000000000000000000000000010030dcf6695d44ad3236032e47f7aa25b9f55869f5207e7ac8641db8c01f5b59627dd3442a1834b8b1fc595e47cdcb000000000000000000000000000000000f91ad5c923572a75d32962567e7b1b0eb84a91d485c968b5aebf8b3a772c2f94e47bc1d5b333fe43574308a78e768ac7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000000ae11c60537bbcfa46a08cbc219122ed66fd0d42f90e68243c32010eb99942554c349c021f0e3635bb50f7ca3d106a3f0000000000000000000000000000000019a61254aaa5b51b4d354f444706ebb0bc3edb87ec2d83e830ffe0282bcaa3278e947d053d6678549a098129bace43da000000000000000000000000000000001100f48a07456f01e16bcc833ae0a2835c964e9b0aa850574dfd8b4a7f06d03059e9b4df8931740ce0621ec7eb31218400000000000000000000000000000000003072392a824c386859735e2d203c9d52c19796ccf8538bda3b1436b2f6815bc86d05287f29fd0bb0569a81a57f0c22a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000392760f98883f9cf6c0f0a324b9a645cbae12b780896f6a3eee918c44a815daed156248d6afb25901521b323f6baa240000000000000000000000000000000006375c6629f30b7a36785269d691772afe1b95d6e1bfaaba9459c31086c2697e4ce77d148fe2ea166cc330373583f4730000000000000000000000000000000000aa8e338df7eac5a7b070a69d3ed1553a0c52fcd894c2bc8d1b8cf6ed38983c6c392a9a045ffe8ff40b39d18e7c87c9000000000000000000000000000000000cbc73b589cba1bd47161282642fe6f51f2b3edcdcad6020bdaef369d3f2c11ea9cafb9a7fdccfb89bbbe13560d42d1d97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d0000000000000000000000000000000000e71692cc2342d1e93e0ce72be69013023d012dd2294249dfd69e1d610e2236ee2cdef22446f1996bd3309825989930700000000000000000000000000000000013a1bbd3237dcbe44e05234f7e41982f4fd951d3741a3e90345418af1c922d35edf776a27bfbeaf7a15658db67164bc000000000000000000000000000000001197a2ee5c2541e19b5368c97abf51fde3dd0b922c3d701d7d84552c9f47b38ca09a8aef8240abfdcb03292ade1ff04c0000000000000000000000000000000010ca3c22ff8a47b1c683a58086ed9d831a5c25b6ce5a1971989974b4760cc9e83a1bc8d819825989751405b242eba379efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000001306f8047ba1a3417e7993bba0dfee9077eabfc275af91d0b882a53199874e0777d8dfd29767186d922d49087fff38b20000000000000000000000000000000005371b760380a6d287e129b329e735413447969eb9048def44f5c5987a64323d2a5c81484c40b20206832b86a4af9c4d000000000000000000000000000000001552eeae620c42d0bc4593d7c8e2c8fb4d6dbfcdde68d57158a7dfe837a1870a73b45a97b02abdea174a475a7061331400000000000000000000000000000000033a6dec61540a5cd5773b76847dc5016b309c5a027639598f51ae5b1067b3f7a02f5ea11b0e1be77a3ac236cba15c929a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d4520000000000000000000000000000000012ad5ff49459fd3a7940a69e2a78919876e9b3a4f0c142499e7b5dbcadb5c2b5d79c5dea972f0f0acdfd10ac53bcdd92000000000000000000000000000000000ec1be9cb379bf1e24bd5429a4a91857bc3ad45095d15bc5537c2ba39407e9f2edc5fbf711ef4287a73ea466d4f53c3800000000000000000000000000000000173605df66aaf51810793db1cf2021de6a7645ae84a5d439ee035b917d037d9f9ff072b5dfe8b9ac69feab60fe2d70bb000000000000000000000000000000000d0bd336825381ae1e18ca37bf6160ae32b653ec9f9dad159006e92c24b661f22b5629ba323e9e06ccc5887a962ec23fe9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000000f5e593c6588add92cac2c9467247fc6d900f20b4d3216c258f88f3334eecaccbf3eacda227e2da46cf520e5102a9cdd000000000000000000000000000000000458177ad6c190222e53e054546413c13216286d414e3509b7dc794dc0704afd26bae93ff630c6157d05d46d805a04470000000000000000000000000000000015df8a7720d389e6112707e37694afac2f97282676a89964deabefddbb3a0f1cbc885d4c875b945b8303c1ed2c0f46b8000000000000000000000000000000000e3c7f1af7cf5923dccfc1d25bd86088706a3a44f5fa7f97171228e8f2a2b18e9631b2a63bd5a75ee0bb83fcc91a45c30a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d40000000000000000000000000000000017823fc8a56e6e5cb9924037ad6ad1b43237894a877572dfe3d3cdc1120fe83e01de112b55f7f334dcb5c6247c210613000000000000000000000000000000000daa01f90cd14d82d4fc40b60b463089fc6c0e567fa46bae69184d0e3cc5acdb1d759e3291e2781fe0b65c734ddde28700000000000000000000000000000000164e742b123c19e52e2d7a6727689181f323990a3f3238072f7cfd7fc0f55b7be4274c0df194d85060a81f3744d3978b0000000000000000000000000000000007c03a1678b6e91c1bfc66ce8fd419cea13c7cda3213856ad21823b06db94538153a15d43a9d4270edf77b9a5ed490e6cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb000000000000000000000000000000001865bc91e645e2e24c3efa3afab8b0e278dcf16b29831f75b3eef0b342479e997b9c5f8ccf67c789c830609b3cc425400000000000000000000000000000000018dda7857f919a6a49f6bb465c27342c8fab6afe6350c43b98e91a3105276f3ac27268454e9a9c6dafeb2218ddc7d3cc000000000000000000000000000000000b098258ff8b185a5c59b46150954d52db5a5f68bc7975234491406131e4f1286ce79156dd1290aafe688f936ad34e31000000000000000000000000000000000b294e9ce904fb9e243d0790147b6070b10ff611a06e3f639aacb744154d02016ac08f6769732d4f6944ce9257680d49e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac5768000000000000000000000000000000000e541a22a7a36adc06e445f42497596e1017a1d99de85bb945a195cb3cf0c14d39eb7a2aa994cf234eed77f6307cf6410000000000000000000000000000000002de753e41a16565e5ab1b61debdad54950e9930e04badc6e356f10711d7688befc6827040356c0f0a8ce4f8d7121b3a000000000000000000000000000000000f2202e34ca164f1a6c0afbe179b714b303d87ef14534fe3f4230180f709dc63af17f04487264b3dee6b24ec4d0a423f00000000000000000000000000000000004044d9e3b3a77d6a309780c870a65e05e1ac531c5420f6ed0056f5e728e2b83a968ca90d579db50c2dd395f7e40beaf26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac5794600000000000000000000000000000000166d7692fd30dcd06b9f01ba2101870ed347840509b3242f7cecf91fbed91abc24b08b08cc39c508e6499a2f8bc3637700000000000000000000000000000000076ce6dcbc77812b4d5b44a50edba5a082cc36dc24a5cc348283a4ce1518198b56134c9807ef850edc9e36e9a282b9ff000000000000000000000000000000001261d9412245abd7ba3fc1597f34179e54766c49306725d42588545e14f4e450ee1c7af913ad7225275c57680c23aa6300000000000000000000000000000000096602b4eee053998555ce522c060d5e04c7961eeaab0145d38c9b13362624f54fcc8d0b77f2bbaf8c312a3279f06e4eccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f940000000000000000000000000000000012be34c18145aac51a1494f4052edbeff14c2812ff494cb78198cd7d9db9e951aea80490c55c4ed926f6a96a2c337c880000000000000000000000000000000000536e46a63ec5ac0f2f4eaaad6df98322c6a981cf2fc8ef253269cef20a76ba1ad089c24cba4ad4680dc4192d66595d0000000000000000000000000000000005363b9acb66ee95713b63dad076529805c0dd8921c738e205e7b1d0410a3ecca0870aeb2e64cf45270d49b473371ddd0000000000000000000000000000000016749b2b09d889b883b6fdaf518345d4cf097a728b833e92c4d21b5c41c8d5cfc0758e895b60ad101a74bbb6be6ca0c5fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb34000000000000000000000000000000000067a684b55fdeea39a29252b355700a4810f083909cf2c07a80b362ac1b4d58f5900c68d266f7ad81ea278c0931bc1ec0000000000000000000000000000000001b1f78d194d77cfb4a2116ce9e29438dbf38c52733b0295198159d7cadb2584d86a75c24aedeb36234a0becf9d38a870000000000000000000000000000000011fced2244cd959872a25c0c7bb4af6151d99e1aac079c606db4987b9ba111261d4a16e7d82362b865324824445a946f0000000000000000000000000000000002659e7016ad615ed80ea1ae020903431b470bc0341f8e0918de9b8d2e933dd9f2d9123e9e9d20bfb05d49f71c3c454cd94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000008f3c5de8c94a98dc5ad7846c53980384f997d1657f7349ad9b51376d41f4b21861d212fb6428bcf2347d8774f44156d00000000000000000000000000000000110b245b1e788da41dcbf60a3ac4987c1925696dfca85d450107f654fa1230adb9436d60c9e742dfb4e453ec4944c56c0000000000000000000000000000000011043b975e01df36a36307ba9234a18b97aadb9da509513b13e4f3c80432b0cc5e69a3bbb3cbab8df41bbcc92cdbf60200000000000000000000000000000000120aebda10c52a67d23842e2bd9a897cf38c58fcd11e4e8c5614db5e409a7c03111feebfe2f1212ae753497dc59d6ae9122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2", + "Expected": "00000000000000000000000000000000021067690e6e001e3d0d01449a7257348c4ef68e66dd47b9014d7687d44749be1f33e6be95df6a204169ab7103dc2d3c00000000000000000000000000000000062efa0c36462ab0734128dab5da8635705bd1e1b540817c5805ed9417f176723eea92425db539e8763b1c79b9923e9700000000000000000000000000000000176c9af1970f026bcfa87e6f85a20ed498c28c6982e21bc050cdc29c0f0af832ed4424082e4862d985b78519cfa75b820000000000000000000000000000000018718b0d0fbdf4783cd0b01524ab153b891fbf08cad60241a3f3163d2c3496c36afdc6de62ab3c9a037f88ee408ce5f6", + "Name": "matter_g2_multiexp_27", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018adf92d8da050c76118a3a3b2ee43955ae8b14ddc8ed64f5672f40de475f7e0ba6ff60c4b6ca3e863d7914e6de2cc330000000000000000000000000000000013d1e19011a1ea90389480d14fa608985d895e05edd9c28fb34646f70fd7bdb7857fa785b1e3c8a2997da6c3b5337ccf0000000000000000000000000000000015764827d9838c2b011660230ef9805af388fd997cc229c939bc5f4213d517dd837328c45b0b8ee1d6508cb70629b7bb000000000000000000000000000000000d58fa30a2d095ee8d946e50a027ac4cfdd557b3fd9c82dbf1536ddc0f42491a176ecbdb026306e6ebf1bb182a4e8199b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb000000000000000000000000000000000cacfc8d0bc6f9db737c8a316043a6b52fd5946937467afc09ddd14e509a89f2445065ac8a8c56454d529d67793edb0400000000000000000000000000000000148b1b941f159d93170fed949d5f53bdd2603d78a49443ac0e2353130ff914376e018c3db3d12b807d105f2d50eded8c000000000000000000000000000000001382a3e98cfd072807214479900a8602bd666cac7f19be0443ba1354bfc05666f40384e9ccac314b5d0a2bec1c90ef0c000000000000000000000000000000000c12c2222f67a5adba78f2c0be5be95ed743e835857f4204cf47b67fa2eac45cd5985fd82c7a3904944e7b84737374b17e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d31000000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e0a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000137b12f731ec925dc51e20a9c90323d14e1497e16b3a4b5651135054ef0e58e9b18167da15220b9a4f7d81e9a7648fc20000000000000000000000000000000000b2d3ac534e1e5b2c9ff4092c2d8dc5efd99121de7df953e5426eb33934ef07e41b196eca50f5a04a936881a05f2b2a0000000000000000000000000000000004feae2377d950717695606844a4873ed7b5f6703d7a63dc8b960b99b68efbba710c2db0f1371acbee314875b97ca054000000000000000000000000000000000f49ce3061e7254dc1bc8af3636a05e098cb96d81fb31e25da97c6266adf3c41a74d46ff32f4fbdb4cb7e4a3f69e827bb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a00000000000000000000000000000000064ae3fd67250f2c6332e1e149ec09946147e12e0d871403e559b49aab68190a1454b3ae924727b6dcf6e1ab327c9d7c000000000000000000000000000000001131f91c7a0e1854bba3958b36083c27904cfbdb8b8cb3fe68cf578bd1cb6f7c6eff91d98e4b99086926c5d4272cc1f200000000000000000000000000000000071c6a92a8d460ff72d172c204c8a69d6b6752b8c1f731ec63f7f394c0c3a2a1bc15e865172f693f523c11cd4ab1f88e000000000000000000000000000000001193876df7f4a1cc9b337a41c9faebac2f209b9070bd75294c2a88d3091a1e55b51fad482fd2aee8f90458eeb7e981fb8902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf2062800000000000000000000000000000000025c20ed5572dd1c9a098f241d2965d8739878ddc57c017632afcf6e54964894adbd6d30f62f316c9c3ec7a08268afc70000000000000000000000000000000013bc3e930f4fd5766db8f04e1ebfaab2b67f620119c39d687c68619b3564f3e8b74666c9f8bed6c1f080a9e23e9c0f22000000000000000000000000000000000973a3cf19312f90843f1f013b05484064032557807ca67b2ded4a27fdac12d6cd0e1416c8998cc8635ce10046adfbb900000000000000000000000000000000108903617c78fc608eaf007aa13861c970557f2693b24e8a278920897be9694570ae6e6c7749c3eab84d5fa3af5164b1426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000005695975c140fa14998e5916268bde2135cda80a45414fa85193fd6e13c6b5a6486898f590d76175d8ec2629c923e33600000000000000000000000000000000033f58b1cf67e51e9ad817b31919530cfdb5db5ca4a537d9b006b63399da49b2a5077bf5c3b3b4fb10b2478f466542540000000000000000000000000000000015c532e40ec04d9143e308895b2e7e3d3daee093a5840e1e76ab528fcfa5be57d9796ffd58ad5ab7df6f88aaf34706f2000000000000000000000000000000000b55747d1e8b66e2b2fea67229f2b7b17d58ef547ca841bea8db5b53fafaa18390f11b8170c41a5dd29331917fa2e348de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad00000000000000000000000000000000001ee5ebf73bb40a5c0822350853bb5aeead3262380dc274faba6b04e58e7fb9d5a4ace109ffa5011e73e3d89ee6fd77000000000000000000000000000000001427659e5ab1f8b47edddd27c613b578890d4c66c835c0cf8e8daf19d0ae842f0bba5bc83ed7248adcd75cea5d222a270000000000000000000000000000000001d4560185690ac05e56c2d629d599bceee3ed2919c29e3d1ac54e80ae99b5eb2f93bab865e8c1eef7206f96b2bf4eb20000000000000000000000000000000016ecd3589e3703e5b0ef53790130d5074d2bc0fd5839d9c6ff905746a77e393f73edf53b98b99d9c87a1fee1086aa8657431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb600000000000000000000000000000000163850016261f34de2b831a0a8dd3f224adaa3cc279cdb40e0ae976bbf736dec26c55a6c79cb1c623870b62ea216274b000000000000000000000000000000000a79af5c054cd08608d4be1705058ef7b4ec38a8727560d960f0325d0ef915c049a89e76956d0296bcb6c96333c3470c000000000000000000000000000000000ca89379e558c7308edd25bf06dc05db857204e9351299ab66bf050c8f051341a6c15a02864c679f07373038de3fe87c000000000000000000000000000000001929f42ee5d9dbfd1f6656f61e6243ebf0eb491762b7f3608db3f3e9abf565ab1524f770cd2ade334885d7479342c92c6745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e00000000000000000000000000000000193b6cf7300e47ecd21a05a71b13a8de45418d3f67931789ce6111b8633b9f44063ca13ba8c8a598ee0725caaa3f277a0000000000000000000000000000000016884d982e2ec0fa7e59fb34ae8708d0bf4abfc260837ef4432e8e04474e504b85450db8af8e6809413c90268801fb3b000000000000000000000000000000000fb48a8331f278845979beb8cd21060355566af215ba44029455a03d0c016daf0f6b7c5773d1a99e893e76b4411a53c70000000000000000000000000000000007056e30143058eaea89a3065e1de768d49860b170d4c364a28d38475f90711fba62c1787adda90dd2d347da72680f4eed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000501fa9af88e28d4f0c0590a2624239bf1724ac7174b0f1d5fd7527cff1de9971d6aaf28ba4005e88e181daffee6b20f0000000000000000000000000000000007af5e30b5aa9ad206645ace12cb2b36cc1c6068e604184ca8bfaac5a4ca327f7c43a74d43417918da7df84e3bffd282000000000000000000000000000000000bfc0538d52f277d54749ed0b69697b4c60ef0c5483d21dda76533e15efedc9e2b2ef07618457d64bae8ef922c0b41f600000000000000000000000000000000048935cd352e999bffa613e3be0a9f9a063d5b5eb46cb5056e41ba214e87f871f216ff41ee297aaaf2994a7b6433f58d81633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000016ab1e8b6f41891e0b65f14397c0887b27ff27e7463333e0938a7a1a181dec603056afbefdb23b41bbfb2c05807289b8000000000000000000000000000000000980d0ea9ad5c87bbe1aefb708061f85faae1e1e3b01c55bd577631e5bea2b5ffaf5e2478f5a8df89447fb8a73559729000000000000000000000000000000000784d0c5fa243bf0125cb2c83a4040715197e99d507d71a3bd9ca396074cfda652c1ad0dd95c3cfae369e68d3431ee7c000000000000000000000000000000000e533bb33e6d269dfdeedf7d17c3e0c19f694d151e8eef801c326cbcbc463a42558f58cbc330bdff0d8d91e2974eb4cfc6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000050f005b00f371a7308b5d7d7f67f7c00bf15acc518942607f32686feab5eb503391f964eb7ca711aa6c7b4e494d7eba000000000000000000000000000000000e2ee5092170ea3da0b1397023b2386c65ec8b090484353f2e5d64694aaeb8d5410ae22c92662fcfa21566d70173ef36000000000000000000000000000000001549723160fc7b8f5ef9a84bd1803f18b76698aa7a663d9c107c9ff6c6d02894edc80fd00d436f3a942c05593c5464ad000000000000000000000000000000001032f49e3527cc1f1355c65edb21220c6afc88919ff67ba99c65645cd3b8ca6662dd0146f6a90d92558b3f54815a361d6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000170b317e49f1304570a3a3e6bef78fcf8537a451ebcfef5afe3eac4aa1aa87dbf95d0f870fd3372d37efc9e663621cf7000000000000000000000000000000000269ae0677d71b2537078e96d2593482e4d41b6d1d2cbec755f307735faaf79c01fa27f1103cdfae1a9bdcb665f592c9000000000000000000000000000000000b115d5a9fb9fd9361d0573a8d68c5193f02edc1cf3fecf004c6603f118f28ff394220f6a9e1051a5d9d4b417290b7f800000000000000000000000000000000107b45614b18c2513f8c42a0032cf0f3f300157b39d2969ef7b126f17a9b5e8e9ecc5a61a2ed4db92134b0797f6a0ea35906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000003de39b056f8f0248b138437db1536b7bfee29af00c37fcd14c25c88f0f051eaa07c763d94c8ce497696311736c0b7140000000000000000000000000000000002b52981e828f8dc1cd371e6821d001e1f96d57a865a3c0a255298c43d52741b18fc60903d1a5ef6227061dcb243096c0000000000000000000000000000000016b5335f0f9516f52f2ed45fe723ded427206ba96af0879958f1f22795485b2867e953de3d9b3a9eed2c37f26838e1540000000000000000000000000000000004c860058c7ea2e6e4eb2a65c1dfc20b3070f89ff58ab99bb51a4eb9e7f0642f7b32d1d9f27c668a36a9e053a8d585f394ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000019cbbc125ca1b89330c21ef5b42fe0dc1e795271ce4a9ecabff04eec9029f756f180520f0e7b84be2e9fa4af395536ab000000000000000000000000000000001630cf0c4f3282689a3e01b5c8f9be3803f60238bbe9fecbb0d9e8e49f4ec9f6123c44840acb8cf55f8f6bd15579e6830000000000000000000000000000000012afb848bc0ade8f0c25c6c342bb651a7481be065a48944bbedbc14c095af8a4a048fd1e776126e2128f904afbcb17ff000000000000000000000000000000000dbc984f9ff907ce5553bb11a458deaaee0efea49d6816ed7abf1dee7b70cb18cc669d4808e75678bb898359c7ebedbe6feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438", + "Expected": "000000000000000000000000000000000be6dee62b8c85e36a216d16c5477a7c58f03b992277af83d9b53b3b2169414b72bcb4a97e3667482e888738ff17c94900000000000000000000000000000000067337c69c37ef6f0ae59fddb84c46a2afe7fe047ddb57b3b80437609f1a21fa5a73420fa5b44704ca1cac6c7a99d9320000000000000000000000000000000017fe6f37d2410159e533374ff3812714dcd07610d75a53a5d502cf2f51e750c48858db1e109f6aaf724292c1402382f1000000000000000000000000000000000b8ecfe1f5f5d95777b0fe5d94fe81b82656e6e5a62b7591788baccd251d93e4bbc6857cc87cfe6b4ed470c33631ae22", + "Name": "matter_g2_multiexp_28", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000126d4a9ae3550e31185aac9011e3f086517cf79a279326c264f51bee6615dbcc730d78055489b5602e91b08f96d23882000000000000000000000000000000000aeff5fc04fd06c26af8b048fb2d0d493525ba5c2bde30664e7371812d529ec7dbd584c056b05fe02179b7eefbbc45fe0000000000000000000000000000000017c6538d2801947cbb646d4ec8b70b1e24453f7a984db7ba73e3a5dcf595bdbad9703f2d846ab02491e5e3a5bcee0762000000000000000000000000000000000badf551dbedcefbe7c303a5c8a52151b5460caa22004028893af4d8a3fac30cb1da1e986f9124acd5db7a634657dbd0cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000bed71c7d878e7ecccd8233e3e604e564cba0b1ce75f726f846f3a6e2f3b4f5b12a28b8638be647f5c33226edc2bc7fe000000000000000000000000000000001914c20aabaf1f6f82063223053809622ad82a3a54668bd600db1aafba22aeee5c8a07584e263c91cb0fc5fb809da63d00000000000000000000000000000000056d9cd8f79a90d16b36bde77e546f8b3064ba7dd0fde78d6bc538bd6ce12a4f32860205d5d396bab3d70deaaaccf9450000000000000000000000000000000012f7e420708b66132157a80753678de292998cb6c4f00244d3c47a6077b3401132b73c7f52369aa2a6a90892f7be4ed913a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b000000000000000000000000000000000339aa1471eddee8cc0a4e4db5a29c3e4e92cfbabe023995a79624614aca522cd459dfacc0cab346b1cedac347e1df100000000000000000000000000000000016cc4ee8cb72fe09e65616fbe9bea1a0077114ca841ae335f1f9eb5a0b129a4bdc77cc6dae8727d74fe21f0d870a43f2000000000000000000000000000000000098a21da6e983228ebbed0ec3704c9d2521e935506c0567e3bbf9b9c379ce6d33c3d0dd8f5e013b431f740964db634b000000000000000000000000000000000a7a38abe8e282544ec6c8740dce8559fd264393d0a5c9af9813b2430bdb92b3150eacb6732b9cc278d0d0e622b263ecace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b7740000000000000000000000000000000019ed305bfe8d8bfcc20794832b3c117715b6a658c0bfeb629e5989f265cbb456e857e53d168932589e4ed2806db7c4b4000000000000000000000000000000000e2ffda25fc316a38f556b35a7a3acb1a2bfbc1f9469a1b6427ed1f216e113a379932b0547f5370be1017a1fa0266cfa000000000000000000000000000000000ebc493c9a79b8ba58f48b90b9d287c74f505dcb484eabda79ada987d63a4df04d671d4c4ae4b32f8ad5db6a1b80f37f0000000000000000000000000000000019fc715d26c0c7a0c291ad8319e2e8f2920c63b4d4ed3f0e2f376aeddd4f7bd9269175ac8d0f421b001e2e48634f3f238d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f536000000000000000000000000000000001285ff533da833a3daae7d815b1b86feb6f20b7592af8b0eb76240f390ea48b69a75547b040e7282b71779f450d3510c000000000000000000000000000000000813d38fa21c1f3c87b9c97ac03e6aeb8fa23e0340a0dff4e3892c774595648743d0b8980a7bd21648ce9b16a245ac3400000000000000000000000000000000020a69dbfb736c64e4cbc800aa415729b24ec05e901f2c7ba38e49a21c3851dc03bd4f7ec829d4326fe6c13867069a07000000000000000000000000000000000d518f3944053c8f74c0aea1d054d89106312880de4479b3dfb45b00945ff8bb58b12f9a489fa9fcd87194a71475d0a1abeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000018c82052cd483eee7aaa421c2b998ab0b4b32326dadba03c1d923726697d3940b40d5109ba34de09439e833ebc19daca000000000000000000000000000000000e4feddc3eeb3fd1eff8316d5b0cba554714713e8a605a55909889970ea2c8c58bb6c568024709def73b29a5a76563c100000000000000000000000000000000098da4cd0281a16e2e3e542ebb92269c8208a3d373394b0af92dc8a2676f9f0b6e85fda9161e32558e0569cfc7b1f3df000000000000000000000000000000000b7b54b51821fc037f02167d2e640f8dbfd1472407278b4bdf47b958da39f28c64569c3199846c293bf60e86aa45f205adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000003058abd4e3d49c86ffac9c95b1f07b66a22c42654dc4a2e3b07b87c22024a8bb0ee084a558ac22cc9fa286861fd77ff000000000000000000000000000000000fc9a89ee26c323df22add487a6bb278ca3f4c9a91eba4e067d5abc9dd3afededb4f98263e10083cc7ea224f28d3bbe100000000000000000000000000000000058eb015f1e14da860215d59165e12feb8d1317f652eeb76b3f08b38ed943c94e632dbf8145233dc93755e44e027553e0000000000000000000000000000000010897d5c2b481f9937d830b333e7649931e801a6bbffb7d9a3ee28ab1e27889691a9f0b9616a8437c3cda942bf07282206e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f0000000000000000000000000000000013b88963296d8c8197cafe160846ee11365b7a991b35cf5613dc57714aa48307f4dd9c6ff9704b29905c18a41a48010e0000000000000000000000000000000016a97fff65fca5ff282a818deb8100104308b8d9dfacddcae32fc2b6082331b44fa70580018930fe1ab9d9c1b13a59a20000000000000000000000000000000019cd2038acd84c2db1f0fa1b7eccc5f7ae3da803cb72c4a1e8390d49e0adff1d88a85696d9daaebce9c6b8a2f861fb36000000000000000000000000000000001271338587f06847770c72dfb3d9a657d05f8c7a012bec77a7d40a98cb1637ae99281c82668486119608b01feb25e6dab3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c600000000000000000000000000000000173ed7c70f4683102cc6a276d192a8f3b189197d5ea5dc813c7d0162a1649e906f76a1c9a1cb1ace6e4d937934b72338000000000000000000000000000000000936d260b789b1a2a9d04388caab364049395be61d320aef66ce50f052eb462faaa2017731518675bb0e4a2f050e4f7900000000000000000000000000000000070bd1254cf4b209ecb40afe248f2e53c390636625460439952ca2977be021d93fbec264c31ced2a810e8a5e54d750230000000000000000000000000000000016ddc3312f8ed359792bd213d086a0ff1540e3e5a2dedf6c450fb96a9b6d1edff9bde31fbc04de382cf44694a631178229b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000000aa4bc6e1a3e6c3c45a29db74b27af27b61856e2cf385ce0e5094ad53db4d31c4af45b5b234c66a21bf15018c13ece8000000000000000000000000000000000188affc993bf6c99103029c1e406bb1a693e4f1dc650907809ba3de1471d41095dc1866578962c72538ca85d09fcd22d000000000000000000000000000000000e487a7151916694b980e62b64ba49ffc54aaccfa0b0fbc5c14fa4a50d1bfda55698df5cd8570c07030f145c49a4ba9000000000000000000000000000000000084a05dced107d29a0fd4cf817ab67017ca33018d5c7302167d08c64c45c5c455fb5c907f21c39b8a86d037a126df4e76b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e000000000000000000000000000000000ea7060a07dacd84287007a05b494bf19a03e5a759b0ba67624c54cac3562c0ca3fa6e444206614d00d6d6684b86bcb5000000000000000000000000000000000eb2f332f4481276f931d2192c1a9f6d7585e85f248a8ac95aed398cb61bda05230bf8b9c041c6f78be3b34668a9c1a0000000000000000000000000000000000faa038219f844e379d8cce55cb8f0fe2b55548a0a0e1e37e25ba4f432e6b1a6451b8f081c171490bf055f81cbfe5f8600000000000000000000000000000000037c70d4e8befff257c4bc98a4726a961f3e2e68e7e02f9f2c94aa8f5fc67a1da44d41394dfe376a6c04240e4cd5825f3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f789700000000000000000000000000000000100a24d21c0ddb20d76b6d9fe642da5ac1de28afd642ab5c08574206b8b64d1fd822d295476bbdf2ca7e9267138034dd0000000000000000000000000000000000aa7e4f2f77acfe8b4c8f3fabd56b17415ee9bb182bca1db15c399479ec60382f980067b9d4c4ef7556d621259ae9110000000000000000000000000000000012f7a7f91a988fa661c661013736f0ec92b40f571ac15a47067bb847b09ba128d1dcaf8049b941a51cacece5db4e1eb40000000000000000000000000000000007528b0ea66b6ab8d5d318f5e4d1c0e9a4f504057dbb0397b614a1adb160032127f2ac35a1a98da70f023cd343a35ffd47944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd0000000000000000000000000000000015d72b8d4e71cc092c2875de80f3d12e003804d980a4b1dd13cff34e9336397c4533b6ae3a03beb2f09312a605947a270000000000000000000000000000000005976027a98f7b0caf4cc7d0d71440d3e4fffb1ff65fbf32dc890b275b646f2a32600a6215d6b2f999eaec8e58cb6d5c00000000000000000000000000000000111583b7734be53a7d4d090486070cd3d9622156c52871ec79c83ca024880684eada56a36b58cfc3490e65de41e10579000000000000000000000000000000000fb670b553c2ed4c81962b149efd4b0c77edf6ee70eba88300cf264dda98190e550540fb9fb95748599bca3abadd752030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac0000000000000000000000000000000003900e7cc0a8e891dc4dfc45f08d97e73ccbe2021a560a92c493aacd9c0614ad100294b5d7ebd634ffe4e5ea301a26170000000000000000000000000000000011ccc136127189728a7036e85d233fd150d5483963c48074f9d8ff83a0791c950da380e717f2bd0bff8fc115e9e886290000000000000000000000000000000007d3e76bd1f22679d228b4ee50a60cf1bd1fdaa171372cfa34bf4136a091abf7e5ef3c6b3446fd41d5de68b563fc7ff3000000000000000000000000000000001107f636d9187155357bea75c943dafcfba2394a9300054026b46d6f9db31eacc06d1f64c2b139af297dc4783026d98f4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd9786000000000000000000000000000000000e3a4101f6af3cf0d5d5aa5a0ebc26852dc69f91c06e96c5f1c7f8e4528c3dd92cb6f629620136ec356f0657fd9ebc6a0000000000000000000000000000000008d34dc3e1fa8bc22258e23b504d442a11938370325c101f1cfa52f313724e0894be722646195fd078c1a49720cde8c900000000000000000000000000000000163730996c79787e7ab89030de2c26e26188187762fa128ba4378a398ebd906dc56d99cf228591f394396248665c196600000000000000000000000000000000008f0a8b3d003b6727834228798950fb7a3cb6b931bced4540693445a007b474f7459ede17f87158e932e4c9c094ab904d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e000000000000000000000000000000000cc2d30f7d3869abfc34719f40b0ddaf00f52bcee7ec09a16de51785d55531fa7fe3ca1544d7103b9caf7105d60d9e930000000000000000000000000000000002ebd8af0bd3f82dc9dca585feaa83071534b2bc2b3d2aadbe0d01d759ade77ecec3b3f7b72f82087365a14dc205add80000000000000000000000000000000011aa3734a4b9168d3c46944cd726bcb203b94b25a97437a6aaace9c84da708bb073ee10585f28bc41e0601567863c193000000000000000000000000000000000ceb4ae5a8b506d31e77e2a43f3af8ba9459b887a927ca5287edbc2ba7c7cbba85a6e4d35c099b7ec7bf7eb2814cc38ae140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b", + "Expected": "00000000000000000000000000000000192eb406b52075513584ae3c6093fb534270d716c79961d0a3c4bbc44096a2e8d28228363e2c8da54857945f1b983569000000000000000000000000000000000ee0d95748b13b531821ddd71a15fc529a2ce2c99a66f14e28f97478c3c2d524cb7c4cd7e71a1027030765554b8f50f7000000000000000000000000000000000610ab3e064532ce261aa2ba4f78721ac4f78661cc13fa09ccc279267e6f703f1bda17265a5eccb0061ce24d31e000ec000000000000000000000000000000001966a334b16e64e4dbd66119af97bd2b8d6afec0eb1b8207f437c00ab134ff369b3b3c1bf51b871a7fe8ad1ce93dca4e", + "Name": "matter_g2_multiexp_29", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004c22bd94b82ed3b106532a58a0253daf51f579b9d746c624bbc6b58603942eb139c1b576241ca8fab5bf1c457112bd80000000000000000000000000000000010c6f7551d758d1128add57b110227296e060074e4cb934132368f079a794770ff406fc7717867df0f461f5c9fe56960000000000000000000000000000000000048f88afaf6eee5039b76c0c5b4b49671f6fd04f38bdee1b1c8f347a9dd4e6aef387b742c8f9a8aa387ab4d01fe4267000000000000000000000000000000000e7be987d0411dd7138e47ac00f9f07c4737d93aac501edd16362ea5a633c9071a6bf542d4db540d75edecdedc3a8f0ca57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b15300000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae8fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000003e33b840426a6bbe15b23fceba829bda9a5ab89d37e60133874f61bf1b10e05d460bb5d228cb178cfae2a5f41035d32000000000000000000000000000000000a9c5460c6443364d9f9440d101d92a0037343789ca0aab6dffcc2bf81e1aed312299a21556d16e55b1398334d9061f00000000000000000000000000000000015db251708253f7de13a5eeae5aa76fec415ecee1ffd88d882580da5da8d9f96c6ff90d920b329096a103dd71e7cfa580000000000000000000000000000000014c3a004cb6ab8465e05d965dc720b37084d98de424b160062f225dd0b67a8e62ae11a3c7bacaa129a568f3a243357ebb061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d61000000000000000000000000000000000c8fecac8bee21d916cc47b96a66b7a522ef4fea76fcc86ec490ff44b46fc01ac0446e3885e36ae7ab62a409ccffcca60000000000000000000000000000000011676ccef54bb27ab7db0b5ec025a9d1f29217030f3686e71564fa011d9fb598f44a8bed3da8fa7fcd10d01e3f66d86500000000000000000000000000000000093aecb91956215980854c6f19120777983a160e16026560c8076bdc4372f53065f9fee0f5830ea192aa5637590a745100000000000000000000000000000000035d773ef15d8d99b600a6a575eefd661aacb49d6540639223a454594570d0f00ba37340b63a2c8a0d4e53ee7dc2dd91355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a0000000000000000000000000000000007b2891e9cea2a464742c7f962deb1566c9d4f9e4e7cbee1912a72c5b064211c39801bf42bd888bc239e6b4ba71d700300000000000000000000000000000000169cf5e706dff2945145d5ac14bd5fc8f7e7c3e5f7ce733c865e1882d236926c71853efbea26e13efe4eb0d0e7ed5db6000000000000000000000000000000000de9ee19c4bc2fac36debd4c91317e54f57e761866b134ba9a0e84a8d268b11674110ee8f91aa8a6b80eabee2e5e75ae0000000000000000000000000000000016d91408a670e4ee43ab8e21cc341596709113950d22bdf5073cd90f520667699e94f64f76290f1bebfecfd80a9e051430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a7720000000000000000000000000000000001982744a15e8163a6f2ee681bf27a68996682216037d67d91993fbbe040e16ea21a9cb600fc6a40e7289185393544c3f000000000000000000000000000000001131d7dd5a5b96ac1f4c4aa210afe7af8d371cc16d32289aad38c93afcc1d3be53716f82e9d14ce6b1c833f7f5871ad00000000000000000000000000000000009adedaf19fb8823ec55b803c9509ad98217730bfc6424c8b69a071e99d026492e7c8c4a06509491a3bbe5893988c357000000000000000000000000000000000cc60733a783c7df76541daddef2245e6d2b694b94649b13c21aaffdce124c1cec3fd8ed5a5d4d4eff3115ac933e5df989a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000b366a80247a8e3797f1c711aebd60c99ec7caffda34514a3716154e900f2387c46f87f81af036a383e3f9234bd1b50e0000000000000000000000000000000004608b7cea13d08724a2cac691e61255ea7472537f7ff59894d511af7fd99ad72f0a7406271576300a7d1d56aea17bdb00000000000000000000000000000000141abedc914d3d1ed587162acbfddde60f7dbc1ee5e07fdb5f3515b87d1a29024c9e19f24e4c0e3979bd938aa4e798270000000000000000000000000000000010e72c6c0510495dd2c4ecaf13c1c6404654e1be369d1ca485c76d8c2304d60d69b90c2e171f18bf55668232e747825820b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d56200000000000000000000000000000000081d5a229481fd297363e8e217bf1f94a00f54eb6e8a3f95f4de30081bb2b9edd82d53cf287e37b459afabcb73fea1d1000000000000000000000000000000000ab55f52ff7dc578ae8267fe3fa09bdb8174dc30bb835cab9851dbee7a1aeba82e83e07d5e79aafb34643d9fc9a0d1c100000000000000000000000000000000195245c7a762776bc1e81d7111e3b814088f1e0e7d686c3ee3e500cd0a7ad4015851563a1b8b592e491e00078187c66e000000000000000000000000000000001850c1e8edb0d6dab973a9975833cffee8b5243654bc4ebe64972e423799283707f9ad343bfa86548cd2acbe04ede5da3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe00000000000000000000000000000000000007e9191fa9057cd7df8fb83d497ad774735c242bce9bd34cfd21d3f8f2a8e37d1f38b592a61ac8a8d22a4287fc5b0000000000000000000000000000000010e36db1460fa65ea229402f558397c6fc57e9c8a4b0b9e85d9ba938196bfeffc951587353cb7c7d84479f60c087e3660000000000000000000000000000000004d86938bebb850fea82acd336c3900b241757dd937f831dd909ce548325955f103dd57611c0b75bf71412a6ac3d6ed30000000000000000000000000000000013990c82583007b693c1d6271c1e5820d7274c4a729da21a76eccbf7abab1f2bdd6c5d26e78d51476ecf154e4fecd1b87fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000009104610d5887fb7cf6a866584cae30cfeb00e1241083b017ccb82ddc9d72fdc0d2b1d227c22ff6d8497495f44828efc0000000000000000000000000000000002235f959b071f21fd63282fdbb46b1dec27cc193f3e9988def691c73dddd789b6a1adb977a68e2661fb41d62280f229000000000000000000000000000000000ccd46984208f183f0b70c9152c01fdb8ac078ad1d85f41e3a24819da321d9dd9321a8d70103282abe6d8b981447f202000000000000000000000000000000001711057042a54ca76b0c3e7f36f2fd49e339b76cbd2e053d93ec2838848d359865fdbbeb9e75e408b4b316d60ce2741ef0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000001481684941fea0f66c78faa40aeb4b5254bf78c44df7e37b191c095ff12fc94248acf01d2aac5637e9536e73a82c9f0000000000000000000000000000000016b72eff2830f49b24b1e1317c95143cda8bc11b9dc4a91ff22a24e0bc1a244c7215ab1040fcfbc292ab236ac73cbd3d0000000000000000000000000000000013535421771fdad616171f7348cdf32bea7486bf4d836b8b95c69b71ea9915c099e256287aa119af53cf6320ad86664f0000000000000000000000000000000019ba0f36dc556fcf09f0a4a6cee53de485d03d846af7afb792d16220551fb5a42a4261f936b008babc096e6f8f68b63af8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000014b2da0add872d6e61253d6022559f668bf192b4aafe0acfbbf341ada55b404d42b2b31182c1ad50c73673494ea5b7d40000000000000000000000000000000018b76b74e9e6cda8466a354ff66baeb935b5645cf9eca81f4b7342f7914c9bf35c57be402458c09781e66a89cba6e67e0000000000000000000000000000000019bc8c1f32ce934b7ccae6d8ca39a263939585d8f94414c3880fc7bb5a0a27d728708e7ebc42c5a935f769adcfc083f6000000000000000000000000000000001636b62bbbe34bec06253887b78ad5b3ccda1bc5d8baafe450f2d1a8e07334ca79a40c5c4a50b58aaed96408749e6f68285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d325820000000000000000000000000000000013c0fd7a8441b6eb2dabfe8c152aa480015f81139c46440741f3da1c50d18c17526c47e8b8c2fbcfaefabbad5f8a0b000000000000000000000000000000000009da839802e7c6759a87eeae5a05146e1d226dd828d4ef6d908b4a0431008f352539f3abcd3e4c532a3d8204e350a8510000000000000000000000000000000014709634973e4554d2379e439d099e9be8bc7ef031b6ea36a7a85d2ff5090b0e0de7cc1c6b6a004465edcf868ef5fd5b00000000000000000000000000000000146779393d82bde1eaa6205e69907a0536c782fa7fc6e11e5e62ad5468f4422b3688f2ff4da2af396741ca5e0f97de3835bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e44000000000000000000000000000000000ddb7d0380370830803a7eda2e9b694af71381990f182b5d1223992abb5afe9531bbef8b9dba239f411fc422210fdc930000000000000000000000000000000018b685009d012d72193043d09f8968f9a41ce2fed598a20536fe54cb26db1733214add38f73148e754e632f6d78f524d000000000000000000000000000000000b967a7b4ed1bcd9f3da16584b08e0c28d967cebe7a07069abfb3bbce94d26b6d95d8a807879b24fb1f5ea00091d6dc300000000000000000000000000000000039349785fdb7d38707d8136e9a8f650c4491c50d7425388b75fe30da56147992c3d662f22131ba7173b2550e613477fa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c8000000000000000000000000000000000455d7799cc1c2af1e219b23e8683113fec126bad1dd7a441c5d113b064b552ccb1e7314dfed1b11f42a18acace706e50000000000000000000000000000000014d2400aa3e2270714b656bd755c4bba55866d6e313f619e10f94de6d82b5343ae9a9483dc10c1a72a5a21e619a20a8b000000000000000000000000000000000a6caa6cf8609d23b7873c908e5321d064a9c107b5492d296d04f92c308ee705229dfecb1f908bca0024ca56bc125126000000000000000000000000000000000b31c384423c84316f65e03ba9e01a8f626236f76e4df4b8ce2fa053c1c1e6a9b8f0afbc253db8c9c5e2ce9f9dcf05c71155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a6400000000000000000000000000000000097938bb53db8d0aeca3f2bc180039a5dc5269748e9cf065cd88e59b30733d527e54cdfa224e9690581e8c7f0881241b0000000000000000000000000000000002d52d97d4dd415fb18348f4de78c65e2933fc45d5e5e1d8f0f0ca1cd52885704ab12609b91d6d2d1ce13eecc7fa0c2d0000000000000000000000000000000018b926a37a8e0ad836846d06c03a9b84db795fdfe5f15d1fd3e0f8fef1b2825b29ee3a503ffb2f75765cca49c2b3d4cd00000000000000000000000000000000073bac093e958a3a09543e060c81b35b6598521a8685629f77200cdc73b372588e66c247097e7c03492c0943bfac4d6bc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045", + "Expected": "000000000000000000000000000000000f79110c74f0e983f3d3618869af1d9b96dadba61f1d596294ef8a9412f946fa26cf63483528a57299dae48f85ada81e000000000000000000000000000000000e1a9cea3af1debcf7d6ef6f7b8566b5bb52d5548d4caf85925109228d7c9b50d65a1b24f089631e75a694f8e8dcaf040000000000000000000000000000000010efc1081f079e841eaa5a65cd7c945d4f37acc92c4ace9ae6c69a9a95d8cf569d604376b1c7e63558d022da90d269fd0000000000000000000000000000000010b7f55ffac8d57c89b664c36c20b2988a493de32f5a956c91b16ff67cb806298a59adcde12ead42d598b6ca3e1b94da", + "Name": "matter_g2_multiexp_30", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017b139e5dddd53433362c49403838b3e2ecdd850a8df12d4dfacc0bb98f79d40966d62dfd0da1e721e7c0f298457d590000000000000000000000000000000000fa35e9c2e37bee1020ed99516174408ba2cf443fed115fe3a964ed86b5e5369e40291dbfbab477e339003ac85eb7405000000000000000000000000000000000e8fb87794860237066ed1b7ae7c2a783c48c52c2267f3e7295d1f17598b96232954e1eb6d6e80e716628f1db8afe48600000000000000000000000000000000083521e3a6d6e3f99570b747498520db5c89092b0077519c8421f9f41772c7a6e177c9cdca52f89a26c6036cadfafa8b32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000b1415e1dc2d4c1f5619b40e616d258867493d8624857e41d007f82ba8dc53f7ebb36d06f8348b94eedb794899e97df80000000000000000000000000000000001c01656fa47d62b4372361b80ea61501cfda47da5534e3e2aaa27b1e3c4de0bee0aa322e60c476fd4345340e5c00e130000000000000000000000000000000010caa407d9d265721d55f01dcfca52bde851ebd918e8fc4c752a41875940709c64599f36fae5e3ac7f211e1f67890d1c000000000000000000000000000000000b54a86474dd5f410290e4b4ac738fbba5e88c6debec17e38a52090b17ef371dc8feb0573e76c4b61d7688547a89f6a36583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e8630000000000000000000000000000000001649c78147fefa91100738e50034424244d22d8e1bb6a2bf471e4c9b29694a5c9476f4b129912bb09fece53aa87deeb00000000000000000000000000000000117a3e040c1f54b96c2435891a45fb9dd95774b5a55cfb306c22517e4ea72172332d893047f7eaa665fcc58dd21781f400000000000000000000000000000000105e8d80d46e6bab2bb9ce0525cbfc82e8b3320ee4a8b9c0086e21cf2b5895cb35abffedd1b5a9eef21f62a0a1dc48e8000000000000000000000000000000001437ee33abadc8ef6bfeca16c3edcf05480c3dd97db06e396e10d5180472f50074f43f9a031a04dcd11d803462fefadc5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000003b51b10efb54dbc2973e001f0bb634e36f689264484eb128de2882d6600a43ad548bc7d1def4541f0ed88a1fb37f3270000000000000000000000000000000009dd80dbfe6663ab04656856f192002593df9ef7f792dfa81f6a51c658c4c9ce5586a5edaffefd507f51ccb7e8c8101500000000000000000000000000000000144160d5ca6b2ad626e6a3424ff5139adadd3319940afa9bff7dc409ac1fc3775d5413ef4612b27fd22c02c1fe57bb86000000000000000000000000000000000e375ff490a626dd1d933a5c751c88cbd61803986fa8dc089ccbdeaa0a922758afbcdc30d29268fe0a34b7b79d0f76c139c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b000000000000000000000000000000000997ade20fe9c0d3eb79e61a66a5c272d02af668b0f3c8201a1ea071737f3d2ee3b0764f859480e95be75ab8845b407f0000000000000000000000000000000003215194b6a363d31ece09b18700479e6093fa3472a23ef0133e3dce60a3d56b6fa984b900162c4ad56a6899aacf35c3000000000000000000000000000000001647647bbc399f40124c43510469cf613732d0919e22b478b2603d7553927584cd4b3a407e3ec6387c4a93e9e5373178000000000000000000000000000000000bacc8afdd70e927e21521b3f62264ad4f22adbc872439ff851d3d169a1c79a0d02bca2aabaa0b9941ab1c71d092fac12196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c565000000000000000000000000000000000b0c5981bf6ef5b85bbc504fb0196ba442fe87302346688165aa7df8cf2642548760e11daf5b3fe2e37b43379afbfd4a000000000000000000000000000000001086828b9560aaee5e28bcb50db8153c40e632b18c61ed4105bb7f472b6a69ddd8a2836f6605102931ee66b2f07e441f000000000000000000000000000000000f4d7aa3d1a281af6f8afb3d886774f4e4a64490232f63dbe16e3b8c4f626e9d07f7c668d09cadab3c92d6fe852427af000000000000000000000000000000000d92ea3318779b532cd81c9be44b1abb179a8411319a6f8fbd7e3f158bc970917d3e0b25f3f3f6c8e0764011f9bab0398df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000000dbd83910f304d0fb2b6d8619c3a308c719f6454a357d9ced03b2882a50692c06cda7f4331f54eb293ed5aa079121fb00000000000000000000000000000000019c33ec829367dfd2610ccef9842ffaa5e4f35809657c22134fb09b024e07949d8370ba8ba1e9149060e9bd3babc19c000000000000000000000000000000000ac468b42925d2daacb8574d40064d393caa643f08767d20e72ac0fad1447a64d8743523312f3a91a118d3e51e1f52d7000000000000000000000000000000000202d1971fef2938cfd10bef5900b91cc4811939f66f1f5578a8ae0eacb2538d2a51c1e025449e1637b5173ab7fa3b6f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef00000000000000000000000000000000008b6709123b9bd501360fa463dd08076c59177dc0e8035c49fa2f541eef3831e4c584c5a9410c68999dddda6c86fd9d5000000000000000000000000000000000fb94eb34355c636dca909cfa71f52471217b9bc241cd3e98907d4a5c7eb67d5bc9cdb0c73c1369d7950a014fe6069fc0000000000000000000000000000000002e2ee515a5dc96a664bb1f862f21a8d3b7f903fb87f6dac41c3541f3d83633f351ba8dc4661607d24b912dd1ab097da0000000000000000000000000000000008bee545e00e3fc283185a85511e09fd0253e191f52d5c0b440b10228041800c013db3c9322a835e4927c0ae0b21bc1e8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000006ba635e74538748c29aa7c5690a0530f2b1970554598a432d4ea6d2713a4d26786b6e80f67b2f39e218b19323654ea200000000000000000000000000000000133ca9e5e0d4a8200d3522d8e87dec3c72edc1cf16b7305af4abd466aa7a0e30159388d34c36ea030450ef45b7940ec20000000000000000000000000000000004724239afc773688ea92296bae8845f20793c05807a18d6f35f03bef295da06f8ac9dff438b720dbea7ea93f3ea9c4500000000000000000000000000000000149c12922fd69e1960274a8b91384e929fb354936c020911495e6e3c49faf16899ec0c6e87713ee2f0149bf808ac8abfcd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e5066700000000000000000000000000000000148a4fe6ca67b6c785d5d8a784d5e68fcd2bd08294ca37f296b6426433b805507b554eb9f0fadfa9d293e8cdb8547d4c0000000000000000000000000000000003700600c2b7bfea54801ac95ff7a2c069bace31ceadab2947a0641462089fb43f0b9697acc005a23007a923ffe97360000000000000000000000000000000001705a769ce3c9a7a91283e4068c602d85808980d6fb457345a5f9b2499ff8fb3ec8383049b9b7cae96bd2ac6106a07fd00000000000000000000000000000000052b1f4e8a48a5eb2b2580614c656393819b4f0ffea874be899e4964c7e32d54757f2d48ca7b50e47e8bf6d6ab8ee7572a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000012ff8ba50d587765e68f95d276e364c8c40c00b55abc929f9ec240985269eb096dd3cef5826cf6269ecf54bc67773510000000000000000000000000000000000959492d74cb34c8c9ca4a21ddee97df99c8a6e627db3ef72200f39e0402d56f0a9709596189c80aa3aa50793e0f1a68000000000000000000000000000000000f7e5dbe884597054d6dc5e80bf4d0d333025bddebc1fdb1d61482cf15bcb4c8a95ea29cdd0925b5b816cc0bb307387200000000000000000000000000000000194e940c041d71f43ffaa51fbb31eb63c23559069b42dbf8777f35eddf14edbc3f7762c7b354174a584507ad714948234a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee2800000000000000000000000000000000101a8e54d1fc2357df60b0ef8872b729295218f29ff63f7a7b6a70b3ecdbfc6809eaa8dc1f62a664b9987e8e86154c6c0000000000000000000000000000000015b5ddd012b42e1a600d738e05b551d91e7fcf3cb36018ceda9b689b92022224990c11a6fa0b421d5610b7e59b7463c30000000000000000000000000000000016130be17fceab55387d43179cd943c85ce1ff1881c07c937b2cc0645ec9ebaf0e10718ec7fe0d720f49bed2b8caf15b0000000000000000000000000000000017d73650680856bc11619e6acc139e137f0a06476f5f8979b5ba7fb8123d85916915da60d1f2e8c84197eef518b350c2a62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000007d26bf37a97d532ec93a3eac00d9d39b064ecd172ebd5e18228b1601eb7a2c272aff9d88d63781b4a587c2c8582eec4000000000000000000000000000000000108000e850bfbfb02d7acef97592e15ca721334eb51197511b0eb2bd3bb647fc8f07713487b0a0bedbafb106992de4b000000000000000000000000000000001868c0b2ba732731f7536851f8005e8bae7b16545b39190251eb2bf93dedbf0803a42ec24cebd151998b690c38c0346c0000000000000000000000000000000016faafe909a1f926333b12f5463231a71058aec31d73893687d3169c4c3588436f6178447eed307b642490199c507d63a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a6000000000000000000000000000000000331338cbaeb8e304fbb9257bb80aff5d3e043d07dbc476dec2795347e4c25248caad06ad14f56183d2b6276c49ff98700000000000000000000000000000000167e9578304a1162de73914b02791468e14faa2e0f161aa57818b8a169b5933dfcab787ec0f4b23737011163dcaa02750000000000000000000000000000000010aadfd5cc781e73c31f2fb64e7981b2e28614aa18dc7b2d96d2bb4ed8c2ee9089d6ebe0cf85479b272cb049e934739900000000000000000000000000000000128d7ea54f338064cd2f041f42a1a1e77d8b9be4ee55f568786a36f87f965d8142207e518798061eb3e32fe3b0f1541d1f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b400000000000000000000000000000000004539f22654b3182d4fda5ab8d4bce6f1268d4e402b6c29a4cdff3b5abe0618d33db55ccd1ff12b27b2cb0196ac53e0600000000000000000000000000000000177e80ab6aa8512cc9e4d65b06b2bd76e33bef9038cdc1ab97fbb9d896ae2ad884ea16407490653dbe972b14e9c30c0b000000000000000000000000000000000c280a4431e41df6515979a694ce292f220278178f7f36e23c8a4cb2b8a7ebc520901ebe34c72a26b2c8a60aa1a155100000000000000000000000000000000006a0b80538a6c8093f3655905af1c59c235567d22192758c28dad1b715045189a412e4c1edc26e1d8ac95a584277709b3bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000eb091007672a212dc4937b314576963d7561657cf1103820ce9bc34e4d46c24f4891a4a4ada648f8cdd2c30f670b86200000000000000000000000000000000166389a37e6e3c02317d68d54f29cc98d1d1df5853940555161d71df791cd92c483eaad87dc0e765b12408d6ac344f31000000000000000000000000000000000affd0d5734cbc27b192c0c0e464db48d3d76799d2c6a493b172127ef2df6ea18a33898828effeeaceb7a203e35ca41800000000000000000000000000000000155708b9756752c9b44048c91d71970fd2cf2a4cae6b0baec00629c81387c8261150e78f856093d81e816be6403f1ee91b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d", + "Expected": "0000000000000000000000000000000007ceeb14945414d96088a7900c1120ff182b2a93b09943c2fd1dc2b0b223f684b0d4c0b1b5803502582f2daf16d81d2d0000000000000000000000000000000008df450fb25534fdc456a8f41cc143a84729ccb082aaa2243c8f37e34a6670f5195750f8547444c49f7a898aa8567d980000000000000000000000000000000008c12d360078d5645b0e095c90d4fd37eb20f0ebbc6fa93fa5beda7e7c78eecc06e0d839268e2c303422ab1769402e0b0000000000000000000000000000000002bd594a21153d7c458b9f804050d05caf2d90bbf9d18def79eb8148b7f89e3a3ac21f84b87fd13c39df5b91cf73460d", + "Name": "matter_g2_multiexp_31", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc00000000000000000000000000000000090b834a587521729426d5b134c6058bf7999f4d4bcc0812e8d8b3ebb050961321b5e93356e87171a6f12160749394ee000000000000000000000000000000000cd5148c7eeac4aaea4288b38a02b5a901a6e2805e2b1695ed98ed86cfa0d259d87b65bf3cc9d00b8548100a60a371d200000000000000000000000000000000026db1079b85411dea0b9fca383956af50b938a465f35347605c01f3b72b297630ee2fb5252da20ee0d8ba5071974ed70000000000000000000000000000000012ae26c193e02d7ae4a7a01181551085dec9fbcac811c45d5cef19abf736ca2514e1259811970af5913891abe22a75ecf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda99438000000000000000000000000000000000ed5d8a609aa4f3c65a89b8dbc9334bd3cec6c7763bff298acd6c260e4d3bec0088e15c5d82618571d13b74a2031eff1000000000000000000000000000000000c28f92f018e6f822912b6eccfab37432ab0ab9acab751f848401791bd2f16e32ac6d97948bd8a0bed2ddc1917f0db3b0000000000000000000000000000000014083be2539d914883172cdc70950512dfe7be8893b1ecf085d837c2e9ba7f03656c5a0e15373e04d300869620eb66d00000000000000000000000000000000002561b77cc2658c54d29f8d1988dd7448f59c80c02ee9256404d8ef5536ee50104cbc11b6ee1ab9ccbf0ca55e53c52aae45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a6700000000000000000000000000000000000fff05aea33a9d1e8f7b227c80ae87c9e7589ba2804904b7d8386b24b0e5324e718f29531251969a972870a30c310630000000000000000000000000000000016ecb8f27a369df13e122c981e7ae37882b36d5492fccbc86d606aa1198f3e4ee7bb7ad0555e11949e6c1783d8f4cda100000000000000000000000000000000187f425b675cb12719a01ee3b78ea73d88f70805f72d6cabef6372ccb9d99008bdd7da54f155454c4c59f041deec86f800000000000000000000000000000000151c272d5cb67b3f801e103ee901deb4b3d3bef76ee4e1b2ce1b5e663ed292845ba012c732d38f9209f82e77f1f73cf354868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001791bd59815309f2aeb7b07df8afd89a288eb6f19c7e613f394353ac5398267e1388c97b17d83104446e57f94581a79c00000000000000000000000000000000154cc72ada5a9c99dea06ebec143a14271cf332b57c631725ab30e2d308d6b688ca08a79efb6fce632cb1216ac3d077e0000000000000000000000000000000012b4c6fe8c17274ef57539563a736c2f83c4cb473e9d075a976e18e193255057340f45de373c7d6e3fe5e08ad0dd97d20000000000000000000000000000000005aef16e11bd4e7787bd5ab4427276ecdf9c6c134b9fdb2ec39e87ae4a5b3b674b5ceee29bcdf804ebd7e83960d8d7ef7068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb0000000000000000000000000000000008e57f905fa202c7640500746b590791cf9d0f160a77e5eaa5a30280e513e8e801c4b6b04cc3f80d9403388571d180ba000000000000000000000000000000000da3c128ae234bc27824062832ac10aa9cd4978f37855a8b4cde3822f5b485fddb9a475a9805e795519d7f138a8199cf0000000000000000000000000000000000ec11b7e07710161fc557a56e04337f71aaa1a0f070cd84525965e53a1fe445c91ac07c618ec349997890ae893c165d000000000000000000000000000000000406b0eafbb8782d11f5dae2f6214282252af9ae9ebc5c17a81d4ddded40f05d0b534d14019bcb6cf4e49c4c182b90f00042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e000000000000000000000000000000000b1d456e66671dfa72ef3a56523eb939146226111fdbbeb697983928aebd5f50b0518db841a3d48912a7a780785c1f180000000000000000000000000000000007a15b2253496b78d270dd55b80bff90583a95283a89d40f6df71fadce56d103f0d365fe79256fa4f93b2d2bf4c06a2e0000000000000000000000000000000010829223166d38fd2c3041dd5643c9784da366a2ea8cbb3abdffb5fe43e975318c86de0ac9ec77c0126ee75bd209f7300000000000000000000000000000000004b124018e83e1e5e77bad42eb831798d450f8ff4a79c9b14f67f080047c491fbba45db79b2cf6015188f9fa6329e8be0a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da0000000000000000000000000000000011119be42b90c7857079a51695dd5be08e59374b0d1c7e12d0ffe870202e1f0c62bff84c9691679a82e610e788b7b5e1000000000000000000000000000000000c7a64524c5dd1bf10d16da7f15b39d05c9ee1620d4dcae79c60316a1f522b238e7934d1be897a441d0c8e621b67d44c0000000000000000000000000000000013045613a090d05d07310865d977c8e0bb1caa713b2249d6676e7cfd6f4e3ba8e667deabf9fdf7fd527685f7d251b178000000000000000000000000000000000dfee7f8259701b5726b6439a7ce77b92245499906502c7dfb384e29cafea61f3b1f21fcd7888231569ebf29d3035a61ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000006d77669207bb2d064824cb56fc786c631936d30db630be3c08e18d7e95b1c26e2d4e7b2eddc2f946fba6e99acb2198a00000000000000000000000000000000168bd8f291f8bcdf8b5e9fa915f7f24856a62803bbbeb9bc38384149008d4e3129338035061631f1fbaceeccfaeef4a700000000000000000000000000000000146bf2dedc262557dec2b4545c94a37434e20e4900b1693e8fa9bda9a94dbd07e0a3bee5f3bedfa42148791f4951db7500000000000000000000000000000000138467700fd5088c76af2f77fea4b746f98701fc0578571997b0ac2fc343354ddc8b2dc57d5298dd4daf767573d8bd3d5f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000001536da0df7c91687339fc93608eb404c5f46adf4b9122b99b1e5cee0012e27ddf30934d8f669bd39091f8673aa3b3c490000000000000000000000000000000002deaa8f9349e7c551e39751b1454a00f8f7896d63110e8e42607e8023ae3070c4abc9885ed54ee37a82f6e5c68451e900000000000000000000000000000000079a62eb17f7b07d4117956d3dab5d16a7f90e98948d5c3caa124fcf755c73f060a90d002cf880f5246a87342717b4dd0000000000000000000000000000000001246f0f3ec2af7c0250ae14cc67b5a1d42309f06c6f47b89178ff7534c47e8413a26a43f27454c0f946c66634563d41cc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed216500000000000000000000000000000000046e4a08785de985c66c7417f9262d363b9acee07e250999a4a7124f101ec4d82e3e4b2b0d9736471329fd61d0cff13b0000000000000000000000000000000017bf1e20ac181780ced62a18c78b378fc0dad157cf30d6026680560b681f5755183bd30b4e454764c08edb93297590b5000000000000000000000000000000000a57cbe93254bb0796eafc0a57330e38bfca37f8b94c4d21ba656e5616239e1e18ba6d632c0129d30291736fe37a4ac90000000000000000000000000000000007f31df7dbe9abe15f4024d8f6bed93c92ff5bfbd7835e08e870eb1bc4a6f62b3809b922c6d5a7350e2e5a978c80a67397b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba5916727000000000000000000000000000000001577abdf6e915c9c3b3fa50a4601709cd629397f2f91784528e4cdbb140065fc2a6ee3830983dcfd49a928e78cf530aa000000000000000000000000000000000d6f98df9e41009837cbb05bc3e3340d38e56a448fe396bd48acf03f061e7489d1402b36a84b3c56eb859437e9c406f1000000000000000000000000000000001912afae5361c3d8c6141755deeef26d1fadf6b0036b9d05b2e0c4d50f42328741f0423ac772fc66dbc922bd4a837ac40000000000000000000000000000000000616661f049b5c784ba05334b2931509e1e033bd203fe17f04cfe12e80e73eb7075beac9d379fc1c457bea1b6adf365752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000f98fed7e4d67a513c746d2fb188597a605165d5d299072aad6d621e077845f93804d575a5796bfa726f529dbd90e014000000000000000000000000000000000adb2d0b6c02e4e8fcab11c7c8819e87f73aab673ff9dbc5c50fee751bc7a6a8d386c8f9fa830b5545f94a73ce6e1f1f000000000000000000000000000000000f08e05ac40655cf59ee3ea9f10fc900315c6f06ffd3b80853560559f580ecdd65aba5ba660c729e0bb9576eee3703710000000000000000000000000000000009da46469f4b8fcd8d2b016e96f6e6582fb01c75407c36c7f87b4a1cd8f08ad06e962a0ec2138ed6fabaa1cb0115f97e2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee3000000000000000000000000000000000047b45ad2ad4f7b5b72194f98b98b2150b5d73a9df2aeb2377beed9a1275a882fa2d849037ddb56af632489f892a48a7000000000000000000000000000000000e1b0d9b52c0c5324067857ba4701f5f20eec165be418871fc0f0adbc3a0bbdce5a33277a33b79013109b81e006c621400000000000000000000000000000000179c471e01e340d8e6fc0f737ec09f0180bd2dd2a86d0817f753d1e9a9f8cb18178e9de68c596dc6a824e6c3c151d8b80000000000000000000000000000000019405c1e571a9b200ff2949aa74647dae59d92a8669d4876ba23f1b4a12a1f9412412503c68acbd619cae3ff056bd346f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db4458000000000000000000000000000000000ca8566b9bd088c471fd33fb7b1bf760ee12cc8b0cfa9ad92b45012cafef5c0772d9bd3bd9b266d6c3e3890c8f00057300000000000000000000000000000000055789839e786ecee7fb7d10f3876359fcc1bd6f2c5cf25c8337aff7fdeec9b43ffbe932cc4936bb708571a59e4339990000000000000000000000000000000013cf827bd57d8179d105f34c147665a072714ccbc114aa4e878d04ce66ca78bdabdc4867b3968c75dead147257197c6a0000000000000000000000000000000014a8dc5ac1858442ca627eaa194e1ba64091b5f9ace551338d770c92fb49ee12449dc200c8c35d70f9e0652b4d9b90da1c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc000000000000000000000000000000001124ea2b97a6d73c81387a51e814b9bdc951a773db2a32d50691be60f1d397cd4aadd9b06e4f49c32b12254e9f824fe80000000000000000000000000000000014cb365e9780feeeff3548f34a56548302ae0dc73402c40317fc819969ee9c4ea2a181381b94f82dd97a236671b456a000000000000000000000000000000000064b769c4b785d45472038aeeebd3ba9b28b3132d72023640ab2d7512cc6e31296c5330be5653ad6902e4e15e57e2c3e0000000000000000000000000000000014c7bfb1f142d69c17f73e23011aee0063a97a99d982d25ff72791a65c7a68941a80fc216cea8a49f3df2d0748b1f95db0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4", + "Expected": "000000000000000000000000000000000fb1227806c750e0eec0b865daaaf51afb74a88589d1c035c60dc1913f05c8ab18de24903ea876fda27b97a5eaa2fd7c0000000000000000000000000000000019903e1341f0285658164f9273b5c958060bf836264502b9dc298f75d4104d7a43b8d5dc0bb934a506ce1273ba839d830000000000000000000000000000000006e791347b54057195189e8b9f10fd42d170f37d455c0af5e92cc6a12e2c23990253be6855f4be6c84a708852c19a6f90000000000000000000000000000000005b72c361dca430fb2414b9d5a326cef8b77cfe5310153d6994dc1f8b9e74e8fbb43079e21956f428ed8aa48d6897e32", + "Name": "matter_g2_multiexp_32", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000052acff88605f33a0cf1201e8101c95ca0befd443c087265821a7d954917a026d41ab24d29bdfd972bb52ff4ad6de14c000000000000000000000000000000000e134b2aac3f6270e43afd994302426796b1e362031638fe0263c0ec212b828a30d8321af34ef7bf260652150cf2293b000000000000000000000000000000000d6628f675008099e9a75e1294803e86417ab22670d36316798680289ae61a26821693f2f9efc8468721a1097c3bceb20000000000000000000000000000000006d66ffad1a2e0f39488fd3f6e0214c9407528c8bfb8d1ebe6d75596a3e3cc844d00fdf46ce7ff6cd6d67732874a24a484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd000000000000000000000000000000001160bf0f7f2915cfc64e12a5a91b7e2aac78d4c2ce362e7677dd0e9c0172b37fd1b52222a13c65819b87593ee32a9ba6000000000000000000000000000000000c8be2cbbd302b31b1ab6dcbeb57b4ad428447bca9159fdfd007f5375218d121673a010f2c7fdf83fb45883458fb068e000000000000000000000000000000000363d3d492e6e6901756bac13b5c32d55aabbedde878115aa41b57d27b49a0f017a61fc90b13a20e009989374b82f5dd0000000000000000000000000000000009302fc26e6d750ff9441d7471903cc296b320128de71f86c4eacc80ce0725e8eea6acd2af056abde2f61e0a349f9bb5bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd1000000000000000000000000000000000b12aca17efc103cad500b3d773e43cb24df6be651963c0f30bca489f1dd7911ffc7204fcaa4511e161c6f75da4a5ff600000000000000000000000000000000179a36e9292d3f78a5fecbec1175f001bd4ac0ff3610f596aacdba29a12ea4844885a7c465e66d3883c7fc99d4a7e06a0000000000000000000000000000000016bfd0758b31f54f90eb8562bb693c45a92a297a3d302279c9e3cb8263efc0f31579a3af8e8f5a091d9a6a36776f445d00000000000000000000000000000000020f6c66fa554a5cb610ab05d194e7583e6798a113b8fff336c986f7358bb9fa6a7aab0b04be9b5c44a6fcfdd21999e83717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000004bf40c1d2d3574ad7fe128ee376364591b6f647f939b0b556ac3fdb5a92873f17c007e926b8a39a97c814728f355bfa000000000000000000000000000000000b8669e10e0a538a421b717287455620b82574b96ed09f64db444ec73a67a3227503e1b4fd6869314214071399eeae0b0000000000000000000000000000000006ddea4adb703d7205b6d2af436b41b4bde3a8c5dbed9dd161c9b3b466ebf06beced64fca25c3bbb97f232315daa5565000000000000000000000000000000000d97248a25ddf0ebd0200c6abbcac9ecd9775cfc5ec8da91634e77488bb592e5ff277a9607fe721990f403dd73f746e622788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d94000000000000000000000000000000000135bc4f28663a6d7d995f6b876ffb8e6ef1d2d0f232388aa5f390c57e8c48cb84d370ebc4bc267eae4466a019c9ed56e0000000000000000000000000000000008b6a9d13dd9d7014df6acb59f80b335a751fa2ba4dce63467aed18f68358f5cb743718112b3cb2d0b5add34bb6989000000000000000000000000000000000013a5389dba4da195f34fbe798b254403f0bc5632ed98bd6017ef24fff33640ae493c1bb7a77a0d3c97649230e455eb51000000000000000000000000000000000a69803a4cc237ddfebc51df2d90fa1ad03359f9635ac1646bc942546575d1558f5f2c3010f6e2207849ee697be41d093c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd384000000000000000000000000000000001939c2431f8ac4ab19d2735f122c0424af2ef18c0028e155611237e86648bf1d74fcba3008f5c6aa30feb5d4a14a3f3f00000000000000000000000000000000174473eedb54aafc522973244ec2feb3f7e95e50a1e996d1100c8da4fa59428c280f76e9e7364906662c4d2802235aa5000000000000000000000000000000001021d15f8ae2f62dfd3862944bf3be88d86d8113f4be22544ae5e925d450044279c5bfa1bfca44cd5934b42a27096b510000000000000000000000000000000015e0f20efae92e1fe8dea2222ce808a7de9e9e861c333db139f8ac11d7c4fa9ae6e49f51095f6e16bc738dc6d094b4cfccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a0000000000000000000000000000000012d70691721f5787ea2e2a652f9c65edaf763637f95c285a62d32dded18579b7257493e01eda19631d00ecdd4e27a9ff0000000000000000000000000000000014da9ef6076e646e7d5b52d1803d3a6d897664851c6de2a9b330e48695737e05f0369224c3eb357bf557625bb94e6ea2000000000000000000000000000000001554f68124a91be5b9f325394db23ed5db8f6c46eb46cb50e57947bae00819b151afbf4ab4949290ad41625499f42dc00000000000000000000000000000000009fc0d459e28cd1239d227e1d2f7d530b9d14ce5638cd308569300a791c997a51dd5a98aad703239a23cfe7cef7f47f6ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f000000000000000000000000000000000452580d6a37a07038ce3564a12c1c7391fdb002cf27a6df7e194b38f3c12a3026f2a8acfe5e634cf89140da256d0a420000000000000000000000000000000004b73c9a4f9d41b8b84e53de538e4b15198f50247e75c274c14f136d7d91dce4a62c5346bf11a105f035e29ccac3dbb70000000000000000000000000000000008a8a3b2705a82b551f8913853f682253e7f1f68c8e42f349337f4f1eaa5103f59430af0c4a124b6a739bf88298c5f6f0000000000000000000000000000000012f4220609899e8610809bb3a4da46e0688c285ba2e8750b4bf44a849cf15fbf5c016e8e8f9372239bb562e7f38916e921cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c910000000000000000000000000000000001884558e709635c046bd6ea8872bda936ba4d5ebcf7a0208cd0a4ee08b69f36dd2e136ce655ddfd89a5b1cf8e48f5ef7000000000000000000000000000000001357e2dd9fb603e5190d7b7ee105668bca2ed23ec6a248aa71aa430c2b2755747b8dfa3b147eb51ea644bf0354a61ba000000000000000000000000000000000009b0b0a76c6980e62e4893157b85f59345e1ac81e1aad1e48acec44c4803e2a9080f0d193fb799e0277ae6f1058839e0000000000000000000000000000000014c984ae4ef5d9d319fc89895f34a7db02747f57b206b0b30e8c9757d4b47419e6c0c8378fdec5aba364936a3b1922ca2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a050000000000000000000000000000000003e1bbb872db172a1fa615155f81aa75ee9760f8602e4135ef9f1640b7f9d54bda911a220d211dc4bb767bc2b5e6e23e0000000000000000000000000000000008464f23cf693b1d4545b6ce4aecdc8fd182cfb288c5ddb1f78ca578e9b16342c8178d886cbb6b8086c0fd1318c4ae09000000000000000000000000000000000af574c4d0fd86087e23daf6d9ce98765e1e445ef9152dbd68152fa4833ada0be440de4abfe7c07dbd4ee67f1a4aec9a000000000000000000000000000000000a8227b982f9286b03c4d49766687622206213d88cde007360df9b4ca5916c44ce44dbe6443577998b4b0d527d22593379cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000012e31070a501a7df7be43dc23e23dafa32ebfbc10ffb4c53f5d36bab2af69db5a05ad64b9ed116560e40b71f9217189b0000000000000000000000000000000011cbcd38ec3c6a6d49df6a8d6e1029a0412b42bd3fe8b42ed625adeb5a2f631e97bfad302de82ae34f715962b5ba0289000000000000000000000000000000001019b1b619fde9fb885d3c5f03a4373358107af7509754ce1ab2deb67df536d05e07ca7d60d927c15b549502750054f90000000000000000000000000000000018f1768b7140484105cf3ad2daa7c565e18eaba834db3f6bdfc9ee37445f2d6f7dc2b4c986b7efd5373224d2c92aa5a81973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e4000000000000000000000000000000001847b14146cfa2e1700f368f414b6a66ccaa02ca2a90b40a8e2be2ee4eb66af77ba563d7507de63362fb18426b6149610000000000000000000000000000000005c028d2b344ccb6400b53134bd179028b8774000ace89369bc655bb9dcd1643aaeec830407ee941df5432ba27987e8f000000000000000000000000000000000c4a680e2157dbdb53ae761209d505b4cf6b18fef5aff1c5009ab41295e0ce2ca23bd7a4f983fb9d085e1d0dbc75ffe40000000000000000000000000000000013c0cc77a5d771f1df99d1530e65ba782604c1ecf67d08572609de9f18405b9b817c2643226cdc7c9ad35beebf87dab0a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000009f32f2f83c21875963818872d243cc8c70b75234f53490eccffbf060cb3b9c53545c1c32025b271514f500b20b00ec10000000000000000000000000000000002491b571087a9e89dbdd039ccd2c37d5d8d25587495b2d7b0066e9dcca02d44b2c134b0128a9a1527396729f069df83000000000000000000000000000000000264e9c47f72b639597de8f26a42ca7d77324f8c0db705986fc3b40dfb46f47764b69c70037a68d76a5de49a278779a100000000000000000000000000000000090614b3bb302ed9fb78b8756524fb78d54a4390b27136087181342571f994b1a93faee28256d765a8ff4f448cc357c199ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b0000000000000000000000000000000012c716ddf17fca0d974e8d6003d99aa90f06b201fd141c74d8fdf1167030d14dc732917d3c6f736c68fbde9df50c098a0000000000000000000000000000000000261ef2b47de8e1576aecc6e19ececf80ddc1f4e28b2ff27953a65199f65a6211db7326632cfe04d543895c727ef8b600000000000000000000000000000000044fd6b9b4a1bacb8b7d4c53c106b025ae78f17c3baebbccca4e18cfbdbcbf8b3ef88ed5bd9bb36d9aea9e24f4117e760000000000000000000000000000000007721612515fd075811ee804314acec9d389900c7ef883e866f71fba00c49d5c4dcc7a2b8e2366f5a93f4577926ed171fdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e900000000000000000000000000000000122581659ab1712afc23c23c2986394de8e155bcf722e944ec05e7e42e05acc366d9a7abf2136b5dc68a8dcfd4a640bf000000000000000000000000000000000188842cf4ef54cf77c145acb685d3187cd9c842ba6705bfed846ace83dc4400c45120fc1d6a633ea879840d3d0c902f0000000000000000000000000000000005c8966862ed4458a753155ffe2c64655779860149641ee5511a46ec576798fdb5cd9521528df77bfebcdaae2f94b865000000000000000000000000000000000cc10d888d2b7a97666de99ac14a501b7e2171f074d30d947efd67d85226c312a7977cf923ddbc88c533f08a99f2045f786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f285d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6", + "Expected": "000000000000000000000000000000000c9be91da9bd8774f18efa3ae9248e4b03d11c49b377c372613b7e745882b2b23c49d518672e58eabd4d9b510a25d8fa0000000000000000000000000000000019687b9eaf5d68b0e795cd57055a74e44efb3e997cb038b7f1cbf08ca70e80a1655cdb04402c542a92ae4e435c22d0b90000000000000000000000000000000010aa1514402ce348d1d61b8d38b53017cd3977a84dc14445db64799cfe822b56a0adbfc5332093ce7ea1f0f438bf15590000000000000000000000000000000019ade30ba0faffcaede95aa272be042aef090f89d9ca25cb825846c4bf9e4c1dc575f8968c88ada51fac71f26fb01517", + "Name": "matter_g2_multiexp_33", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a1346771f8ba25fc44323d5290068e46b3f756de6d97aa934d511979a1486bc32173575639a7e54aea8eeb60f32a8c3000000000000000000000000000000001958ae7fce87db47a65a03402313b99f659ae02e8b62db3525d48dc9075aacc5e5abb50156e704701f3ceb18747e0431000000000000000000000000000000000f98778311e28b4081aa76a3f9546b94c29d86fe8e66b905265d74ee21928dc3ac463049f70d355d8caee5b59d65e07300000000000000000000000000000000185cc233ce72770ae26406476c1779858523e7c940d69adf2750695cb12440440686b6b918f4adb3b14aee9aceb6422119582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d37000000000000000000000000000000000ae2f565a44c8e07f2a136368798a44926cffd3c3a6d4c2fbf91763c20d2bd959271343b80eccec4d59a84394c7a3ce70000000000000000000000000000000009481a5fb276c938801133adf10dde3e7da2087d0bcecd3c9435b7de544271eb3b07a69efe7e168869e727868f24b0d90000000000000000000000000000000011774e0197866b1c8b3428d353d2c9f6326a77ab30d5595e2402a0486f03ca6ebb1e8dd335a60a772dfdd9a3dbdd3eeb0000000000000000000000000000000011ed2480d79f73a67a2adaa6da3ae4f1e1c28feaf0e4cb9aafac658901960129e40f6415ec80a31d72004899326f714bac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d1000000000000000000000000000000000a73b0d8c31af2deed481faec54095875639233bb09f31b1c7c745cb54778d1c8bd0a230e963ddd2ec8d178d31fc14740000000000000000000000000000000015a889b16be93d0b6dced01f5e2278ffde1cef0576d0b04b49996cc5252854f879e04b1ffeb90e222f4b9d5fa350767c000000000000000000000000000000000b53dc4d72e90330ffad17012bc7dd2e497cf8aa6ec73bf25c10427e23fd28137631249eabe9d0308c956dc7a9e92047000000000000000000000000000000000930cdc5d04ed2d1eb62937d9f72fdd733c07a5a0e392fd5216100216b1a2e3cde7053bf766f046cc470d92bebaf6290069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000131c4e590400b69b3657f7c67272b1e3491983997993ee87c94043001d78e605965abf3c1a8c8c39cc08d5a5ef05520000000000000000000000000000000000124f71c136dbb032504da910958e8a7949f1dc5c061f21d50e439e01e67919891633b3bb84fa8a54c69b632f78560ca70000000000000000000000000000000014a4b1a05f1060853f4294e669a20b91f939793a6eada6dbc84fda8ab11509b256d8b785b252a3795f1d2b99a51df05d000000000000000000000000000000000be2489f1f91d7adff356236859679c46b6bf8c1b375e8bc8bd1e97830b5ac223ffbbda60ecda168bacc2c0b90ed25d3be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c00000000000000000000000000000000111ebb796e8770d5a69e724a8d3ca62ef1f13778baf4ba12bf462211d35e325ff8e455c85237a73a3046e531f2e2255e0000000000000000000000000000000004308b76b06067e0a07bda143341220809b481b40b78edb2e24e83aa0f003d209198825b5fa9bfd92597e27a4054d3ee000000000000000000000000000000000de74485713f5c95653e98b96aeefb79b59911a610c2a848a807653c19d50394fdb52178947c779134d24b6d396ca36800000000000000000000000000000000069f47a71ad765591f6335b962e7c2d87b556801e1e6c25b449edc83432612fefd405c952397a704e9aa5a924769ad4e9b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000a3f89408ee43c0ba6a7c9c479327ebab426d430e3ff212c65da6364b16195619d27eee83d701a2ec50bd4b7acfaa06300000000000000000000000000000000092715831af983f740ca2c673e7c9c47727d64165c59fce19dc3fbbdd0b6a7be66288ea1f033ebb5ae2b38b3762edaee00000000000000000000000000000000071ca6fa9e546d4bce965b2bd0f0fb97e6833f05cedcf66d43ec88aba411dc4d6db9f1591de22f493f49a1dab1a2701e0000000000000000000000000000000018f89932ec032fc28775d34d588169a1435bf4ad7e2ee11c9d6934dae31324ddb96b3ef88f95d1bb2e52c3c8d9c01516d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f0000000000000000000000000000000011b5cc382164fc21c9a72cd85acf61c2a78d00a16a2dff938f0b36bfb3bb7075845a1616001ab53271a9a257a38312cc00000000000000000000000000000000139ba2f27e545d45027a0b11253532e28fa691170e08608472ce3b3f9a3e9398c5ee76953b1a1d01a5e79f194c32d1f5000000000000000000000000000000000d875f44829555cec695f3f4a28078b0a6f168bb0985793d003443b75a141936f3c7c633518890e0f7238416d46573cf000000000000000000000000000000000675420ed817ecd24bc5172d3e7df60ac4281b24ba91e8b5ca8bd6a8321f5c7312a6ba043fbcdc467c8a5c957590a692cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c400000000000000000000000000000000107bde844286cd3958cc7a1314127322251699b51d8af8e6b57495497f21a84e05612b1569b54fc5639a75e9f9deef750000000000000000000000000000000002355b1a60e24e4879448437d2c1b12e58f02d7eba88583e96e9634f7e2c8c6886132ef0488918f665ae3f7b6977c7c4000000000000000000000000000000000fed531e437b70bc4a19ad63c61ccaab49afc50fad1f156b1c8ecba0e1b703f8aea61882c6327d4d8fdd072df9c4e73500000000000000000000000000000000182177409579ad53786539514753c696c8757b8c4d9b8360392f24b591e43ec20e84c0abe468061a9e5e879c5c81314217f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b40000000000000000000000000000000001fb25395089228772d6000025cb0356eb510c964bf7d0c12d47a6608fc18cc448e44880eb5ba8475cbe6418fc9d8fee000000000000000000000000000000000f3b9de9980e5afaebc59c56e02fd75fdad13013842ac035f8d5569a46cc67f0cee461a939aa5a3d8fec2966294207930000000000000000000000000000000009a223ac0edb164845eb8397e0cae4363fb2c8c996c3c5d722cb50be56cc3789c732763cfd4b61470886dc991be39f57000000000000000000000000000000001909f17b229eb351dfe8317a8273d846edf14ad5ee0ebe8cc2b595ebfed19b73983035e19ebaee3d05b1dea35968586961ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb500000000000000000000000000000000081fa9eb8ca7d9db52380e4c408e6d5d668471bafbafd62ba9023fa08f6d300a45295b583677824c29ddc3254439cadc000000000000000000000000000000000e2e613043b1566674f791dca9d860a49a75dfa24dce3fe18f544a9b24ec5266a64e77386b672c93fc4d079eb8e76a01000000000000000000000000000000000f471b86ac5783d720e7d73e8871474c8665e8a109aba27c1172ca24217eefb0f66c53232df1672dc0af6ddf9640e10d0000000000000000000000000000000010667cb22a6a818fa7c729e40a7e70e1f31b0ecd568b54a4d352d5c9df8cf1072ebf2ef1e612efd96bddcbeedd8566430fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be000000000000000000000000000000000a0ed87b01f27f26380c6285e82bf2f12ef3016c7e7f3a13041d465825664573db47be6cf099cea615e21f6a5d759b6a0000000000000000000000000000000007afb2a1bd50fa0fd3174d70f1c8d5c229627a496bc9bb89d4f52d47b1862e14d704dddd80045e58d00336e898a996eb000000000000000000000000000000001698f30f824ee5cb71b3f2451953c371987433d2eda570f2a13262ff9e5e529e316b06ef6aadffc152803b076f22db9f0000000000000000000000000000000009eb1d5f3da7cfe9b40a70e1b3c3dae36436e8d068a79dcaa283905614676645c99a5a165630ad46b70bd6be8b1f21a8d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc40000000000000000000000000000000005c4390b8f37cc3fb9f248470b505a5d9502d44e4a4459d1f56452cd9aec89d114f1402fa45935930fa00888a4860a9900000000000000000000000000000000163b0ca84b5cca4f124bfb5a13a4a3efa677a84dc89b6a61e69d0aad34fade528614e549a7b2326d1f6016bd0d35465a000000000000000000000000000000000bf450dc8af483a9f993a29cb47d5362c9f5ef38afc2fba8040e14514eb834fec6520a413fce5868aa9a2c7c3ff6617a000000000000000000000000000000001063619f384102949fa1f8353f0aaa5031234d736c54103df6ef6fcd0df02a19c3aef471f0413a1e19febed6395459a0c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e35db3017963d3a9d62b7e7fbfa13ce4f5fb46a90c1285ddc0fa481d9379b95a77e8cdd4aab5c33059bfcdcd82473fb0000000000000000000000000000000004fa27c663c8d21f041d15cb199d31cfcb96a56cd673b730dd111bf03cd954cc33799456674ed4d58e8e0dfa826a6b26000000000000000000000000000000000e0df4e7f943db5b5c27bafc7e1ce099b2caa64642bcd6336ef926352682fbe81a1945b266cba7eab52b16f4aa63eb8500000000000000000000000000000000020167756b8c68f535c4691b1249ca1ccf0a539f7274623ada824d0ba789ef44ebb20ec1ba51d46c0a42da78653d287e26cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f00000000000000000000000000000000178037c6b5fd1c6c396d8aaadb712863557feb744d2cb9165ae5c36376d2c066f7b1648e083f81c2c96da6562e0b3c20000000000000000000000000000000000b805b4e1cd5d45d8b6ed9d4f604ac0b40f336b8123f7281df43a6e803f8688bd8087fc4d5fbae695d06efb0fa35e18400000000000000000000000000000000000a947562dde45f613ee1d15614940a2edfc770d733a60374f8e9188675d4cf973a5c1081c11fe5a1d93bbe85e6f47800000000000000000000000000000000059473d80c82c6ca06b4aa71d072f4751b3b053b53ffcfb4a84906ddfc36ec5918668a62f07054af1b241bdd4485edba699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000008b9d0916a9f5689b8fdac84bec3a49d0224dbadca6329ecc156da633e1332bcc6735ca3ecb228c22032dcb7b2f372d3000000000000000000000000000000000cac0c264add10bdc1217384a7379f65b93cf822418f7e4e2b48eeac45f068a61f805cedfb1665dda06e04cb726d245c000000000000000000000000000000001578e98a40a64da59154b1c3d757d8f1f8cdc500482c7b7d65b9997576f745442fbac654c19331977bd210df440372970000000000000000000000000000000015ef69f82e85c81d28893d94927068f14c6516eb7d09898d5d055cbb7a9b55c6d7f686f067ab164160e6d6a8f91ea19d40e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf95230000000000000000000000000000000000a6a27b498285085139b8dd0c37b700997134337e696c84b5e0cf70ea3991cfb40ca3a3098a3b3a2fa31e91aac78eb2000000000000000000000000000000000bbd7ebf4301c5eabd4f448b89f1b227415cede3247a1c8dc56a02247efaa99dc78cf370f644ffc06cd2158fa25197dc0000000000000000000000000000000004535a402540474d53c084d4fb6d9e12dba6716ee13286ed758aedc1ef911b55c572640180a54cbc084ff57ceae8a4b4000000000000000000000000000000000759de2a9e0f3c04b4f629a682dbcadb2140e5b935845cb55bd267e230e08c6e8cc5426057473aa03ea2196203bbf6dc062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d", + "Expected": "00000000000000000000000000000000134c29cc5c33c10f04b6c09b5db71b10304028d06ad6acd4f4b39b16823288085a84a0380a1549f04b3dc692cb8216d3000000000000000000000000000000000a0a9379d63527ab9b5f9c00be4acd54e5fd683a0a2f37c85ba570171c705eaadfb0f4e4be1a8836c9de86dff46138300000000000000000000000000000000006ce78f135dda5af34a0e069d7ef13fd589cec5a6128512bdae7f45f28b09c6e4b3cf638628c9f4783097cc00082aeea00000000000000000000000000000000141e710ce7a979dd1772150d0cb2d5b269d5cda50d1bf7bd0cd827b24f9cd8c1e2775f495cfec0428519627b7fede464", + "Name": "matter_g2_multiexp_34", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018aba8353cc470b287a163fdb9b8b4cc46071543ee8261f928a7b856287946637d9b36b728a54e1df5f185a47f1556060000000000000000000000000000000001129541b2e3b2e1a553995b603dc3eee44a5ea440e687739ee9e1339dd79bd96c67231ac753d483e0ca96b27054997b000000000000000000000000000000000e1cdf3591aadeb56dbd80890ff7d5639a64847cec771a19c769df7da732a6d3179d3a89ce0052bd7c982af0304408120000000000000000000000000000000000f5f5f0ebfd2b632e15381ccbabfa88eb774f2c61801381ca73e6970965ecd54f5f3a9af7c152186af8fb75ffb5bc25764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff2000000000000000000000000000000000e527e40c311edc5dcdbb4d0b70497eaee14533aa8ec57dc7cbd7d839fe6c6ae62b1fd0be2346a038de687d5cf5394d60000000000000000000000000000000005f9fc63027dbee5e0d55cd6c57daf5df7af0d138393a2dcdc71ef9aeeb204ea347f7d574e71f2ffdd37d8f05dc7979f000000000000000000000000000000000f8788034c9f1c9c2018a52326c046cdba8997199732152963819b663c6e58e9d6a0065289e2e25a97ce5627505900f20000000000000000000000000000000002a747bb3bcccdc6ea0af1bf1d0ce55de3f41b93060361b30c76063346b606322a76ed7eb260219c83aea0806ac7d8583280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000002148c043e065132e978e89f018a5b728d278c95c9cd1a6f276bd13f0cb708422a62fa22f7b377adf33055fcb09a6a8100000000000000000000000000000000024c4c721a0574e53118bdcc3fd41f73176bc8264d2ff39673210525166bb3513016b5c9af67a47a7032b74a62effcef000000000000000000000000000000000797dfa8cad94896916b7d55fbbb3eb0eeb74f70231205388d0dda69dd8abb436c22addd22c1e3689093739af957b65200000000000000000000000000000000010dd2ea2d45528de8bf1b5c5dc3267fe8951e48ff5987e67ec52d58635521cf1905f1688894e3e23a659764880b2301d4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb11177000000000000000000000000000000000eecc0a4edd3cc3f70d3e0e43ba56b04cfb3f1ac23c657048a94318e622217572b0f929c73f545d6f5f5613920c0580200000000000000000000000000000000137a098ea8d3aed32c197a2d244a2e18753045b55cfe16874f79c728c664b7f23b10476f20dfffb2f80417c26dff4f860000000000000000000000000000000004a7789b02d7d95a2ce0c7bac39d5b057509200393450a47fd9d087a353f866921aa11185550537b98f3073650d9a1370000000000000000000000000000000006ed63730bae06403baf705da0e30c6c00739799eea4a312d06b8d7dc35cb43a4f1e941a69e55ddd7ab8ce42d8629fdcc66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000ee7ddbf43f17f722dae84d34d26add8c1d732918b8c75c6b295f2f584075cea0c655911410b32c06868c1acf36aeaaf0000000000000000000000000000000018775682555d9f5a576cf9462170910bcdd083671ae2e4c8c6fa99a702548f1ce9afe90e681b00d194322b1a2a3be7ef000000000000000000000000000000000f3935bbbf58b91fe8176f3e25ad3fdeffdd6b369ae70b704d4e54d4fe32fe5987e73aa5aa975e958497340274577cf3000000000000000000000000000000000c088bc439d638d86aba6bb1e6e9f7540ac2da3b96080aed455edd1fbabfc141e26f125cc3a9cf72070a24f298dcc3ef4b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b8450000000000000000000000000000000018d8a47d1a13b9b8fb5a1625f9616ba120d5c677bcc996f694b7e15d251fc4bc938b0a7cb5b70f22b8e9f5b416c513210000000000000000000000000000000003d0646458bbee7ccab27f858b8ab0af0cf583da12a40ca5a954d7eaf97c897d379129a63d8131036f29c30c6e644149000000000000000000000000000000000d5466b50975c5a2dad96e4e24339eafc8c85c2497a6f19e12d96603596498654cabea6995a92c91b8319ce06f18d56d00000000000000000000000000000000191a96d62139f8219b9e4369a783400d045d72ab2dd83fd229e08a4ca73de59a11a5add86c739cb3bab4adc5e9f79685708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a00000000000000000000000000000000032eb1f7846b563e98fca0cd44ede4909b6e16a893f5ef01eaccbd7d8aa11710606bbbd0ee6480f7cdbdc9ffe66c3a9c000000000000000000000000000000000c31bb6fb537cfcbffe815d86ebfae1f5053ceb756818ede8a58cd84cb34d0eacc70ab9095f9db1691e4fb4bb816d570000000000000000000000000000000000a8fa1dc2f28277a4bf8fd9665d4b5c3baf1352d89890d4af94a3657cdac7fd72558da1e65cbc5bfac142f0e817be74f0000000000000000000000000000000005ff65c22ff0abfb33518791823c5f2202ea5f7258c0a507ab84460335ffc2cc8d7c7f670752a7647d6a6487ca0c9adb85ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000190379b7629f74bfb88096dc9ffcdecebae0d653410f032a35a811a09022679c9be19f3790af95c3205a396819e068e1000000000000000000000000000000000b6f114fc277ae8f0b5374dd349985bf955dff7fcb0095e0e1e137fb539814be78c924074bbab54f29dfb42f3e7df24a0000000000000000000000000000000002d86b0507c147142d03d3461bfea4c3af7e57a6edbb372387de24a27cfe27c44ee4b9571325a1b3f5e83eef450f2fc6000000000000000000000000000000000ac3b226d5e13c36c3a8ef0c8896d9af55bcc0cb67ac1cee57a5c6519617ec77af9af60ed44e0a8284a2d59816ebf848fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee3147800000000000000000000000000000000079cd4511e953e4d1b3f4f3bbbc66a62772018e809779fa39aaeeffac737cda9a6116293848f967577f03017f33231d2000000000000000000000000000000000ce3cf48be423a2fc0188b94f2a22579872e9ba140798e560ad107f63ab2b8c601831f89d06a4bb8e7a758cf836ddfb3000000000000000000000000000000000a6a90f735f215a79216fc4e7daffbf74775f38824952af72ac38c38a77a277483e34bc95031679494d76f109c0aecc4000000000000000000000000000000000d55fbce780d885cf817cd2126e7acf115ae9c72843af23c376f3a5d4307d1eefaa0f4691e7c09b5da1707aeaa5b675af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf200000000000000000000000000000000008924efbdb46b9324bfb79b922ba8b7d83f5e5e4b3b736105e5339805838171801bcf17208f3dfe5c7475d4e45b6ad970000000000000000000000000000000007bc0096fd23f0c93f0dde8a3974ea3105574e031202f6316d5940c85164c6d6bb5b86078a0c68dc822c0fd1b3dc8cc10000000000000000000000000000000017276b3208b347388a5657b10e3c8e4a187b376e42352f76ee3ee88873217b6b8185022c93097cc116abdecf3cc64467000000000000000000000000000000001915ff932acbdeb52f07b664bcc47c3a5b096c6cec32da4d7044326dfe84358e49539fe50782538a901b99428446b0f50ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000009e295d229b543a17db1cc85c846111b7097bd169d19b410de78f8da9684e664922eae77c64b0db430aeb422016cfe7d000000000000000000000000000000000e29aab30a1da56b8590e9df67171cc1b9c847696b51147cc57ed6c3b55819cfa0992c67e15e4ca6de2573c9e16231c10000000000000000000000000000000007cc9990c6722645e320dd16a4be8adaab41f958f769ba0d22e235549a7457778cb9b14aa6ea5caa9e0bd43f8d04cacc000000000000000000000000000000000b2dab5cf37ae8e76b71dd8748c86e8823142792445fa0b140de31957d35bb7267e3d94e0dc92f4342d9f8560c5d9d86dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d09953900000000000000000000000000000000128e2aa795f8479da3ea2a4efd12aa90a6fb019d4da89fd372e6848ff7ee17da689d766c9e49c88c962eb4f682c56fff0000000000000000000000000000000000fd68bb80d6b2200297aacae1174275f864669e962d85c9105032d7a352fea548e9fa0629a6749c789fa0827a40190700000000000000000000000000000000175bc3918dcc972fb728f1d8cc30ce9887efc6e0b254d8d22af87f95cd4182129d494c43d11b028c4b9849f5520a4fc00000000000000000000000000000000007c5363f507a01c0b6935fee0413345bceaf1336cdd20f69060bdba2e411521a61a549e6159b2e006ffa16e3bd77e998e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f90000000000000000000000000000000000efcd782b89fee74ebc037160c6653ccc104260b5f8989545b40d51ead6ad6ce6252e1232281c813e3c883af86e68ca000000000000000000000000000000000b68ed21f76ce131c089dc454dc48ef948cc7c6d5fd87d647db954c9eeab2f7f76ccc51a1cff8612e89bceed16ca03ba000000000000000000000000000000000cd776670d5171610046fa294fecefb42f9bb4d71baed4af65a09018b09ad9341789abc23c9feb85adf96b4203b0c0a0000000000000000000000000000000000ec4ca0091a28b73c9adbe7120f2bf1a84a62ebba1e86b1948389b1a1966c1de4c632a5e245ba634b53cb932f5847f6ecc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000022f319bb5167c2b945a69a438f712df8975a0e262438ea687e2b0d824e2d1d14bff1065f50fd6ae92494f6f3aa9472b00000000000000000000000000000000198ce9e4ddb6b423788dbea82d75513f43cb43ecf1b27c8788f041248f01808644f60fd823e5862cd7afb4f7e8b6b6a100000000000000000000000000000000119dc1be1bbb7e678319db73055ccb88ef7efcf6119f8a9c43c69247ff264879a627f653a10a950e0dbe69155ebca4f1000000000000000000000000000000000692a0ef5a75d42524e3fd52ae073b0f2ddf6378f18a5dcef05af4868a899b93c7f1d2691883e5c85f97052ef1f4177d09a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce00000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000001423d1707e49d2215f639df75ee0e13bc724efc7d099259179260ba0f17157c4efc4276844bfdc46c61ac2185f64beca0000000000000000000000000000000019ad06d215d3c819311938f89609ea7cc63fadaa11bcc86cf5f26370a966eaed1aca312c18176674b5aaca3ed8ca876e0000000000000000000000000000000013bf3f13e87f3ce29f0524094e2ab8e39679566add32e779256006dc92ce09f60d5bb9cf0452b90ece71a5f6981d77f300000000000000000000000000000000112e4901efca14686c30a883ecdafdc389303f4cf46345e229885c76d900b0aa084a957076009ce22ee36d4e285d410c9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12", + "Expected": "0000000000000000000000000000000016d1fce53fc4cf40acb0347c0983dda46383e4828c16985459ac89a2ce8d3c2a26cd9acfaa2ec899cc63b4c6bc351f560000000000000000000000000000000019c9626363b511a79f297dc79c5a3b7a2e5127fe49a2fac5bc43a4376f170404f044f9f84b82cd09a306012fc81e3bdb00000000000000000000000000000000062e324f3d7c5bd39808b762a5b009cb30bec14a9591477959339bf2de9ef27eb42a0eddb95aa5fdca9bb9d89b278cc20000000000000000000000000000000000f05225a4d3bf910b0ac0103594a90684ffc0c09e2c21744032e30470d5727be3c27621dc2377e9845ad78be67b856a", + "Name": "matter_g2_multiexp_35", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c64577b78ff306186dc44131cf8bd946a68e65502c4af9613154a7e3ffea4fe4e59cac4a500894b470a74e66db1834e000000000000000000000000000000000b4311c295bd30174f17b9ab3544f1496b9655302a4b6026a113b1aca31b555ce7b2d812bf8fafb6b075f67cdc59b77f0000000000000000000000000000000012d7dc3db10ae6b4e3e99c655aadb71124a0bdcfa6e30ec13c7c977d39f83aba4979a1f45881813a3a68e149a40120a90000000000000000000000000000000001b958c47cfecd619c05a2c54315585d15fe377beff889602ecba6ea3b29d51f6480f09a0a8490e4c754045f0bfdc3eb7b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d00000000000000000000000000000000010db66653f2ca73e55d85254f62e8b558584d546913e4b8f530968088d90cd5c4bc164fdb77325fe0bb2fb1a5683896000000000000000000000000000000000a1af9bf84f0b351c8e8c082a79c7ccae768bd2ed40eede38666aab1901a6eab0cff258f440c8c8eb4854a824b9aab4b000000000000000000000000000000000444fa654afb57f1b01d10be08a330f23667af58e3a40c630a2e01c1809125b3ff8f76e8a39418688a023293ff1a45e90000000000000000000000000000000002ebb117ea107a3598b888dcbd5918dd0ca307b21d65843c6c28b3efab89d0e276e5d92283bbb345b674d4a300595425c6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b48000000000000000000000000000000000105818a11efaeab4801d4fa7558b09bd56294be7f58e3e54fab1f4f7c243ceaf08c99a14c3128ccfd866b6f93caf989800000000000000000000000000000000091ca092e5f83a04e872e621e71d54dd6886d088718f60ed5b62d3e4c165d3ff2cea1e50fcb94fff251df0f5ee453cfc000000000000000000000000000000000b42051a1ef52f87536b9bca459fa7070ca17bf825950b13b3bbe9db183ef722f62af6c05c174c65d59b722e1d2f5b0e0000000000000000000000000000000002fdb4a5480418e43aea28e5041af6ad55a6c226e1eea1a0449a99b5a937375285feecabea95c2652da5113dc17d8ef4410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d000000000000000000000000000000000d8f93b589678d4e93bdf8da7834bc8edab648ead731b7f5f0cc299488f07262877ee9bb1174ccc106204dcd3f1f416800000000000000000000000000000000160f740ffca48d3a10c43e591cf46c129507f10e65d76a031fded2930d6c2dca4c79d7813f63e4ff71aee09d672173680000000000000000000000000000000013c768a4889315faa3976c8e43b4d466ea594bd94773f270a198f2571ba9662d10435d1e541a492055c333eece9bb29a0000000000000000000000000000000003dcbcc9e6a0cd5741d77da88fbbc269202e8f944a5df5dc4f9145758654934d5e1eedd596325080382458961ed3d21ed97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b3000000000000000000000000000000001344fb37c1d7dcab01a4bf6fa50c6bb7606f7db22b85a3888ffcc2e9f229f196881cd7c82160730727e49b9e6fea04320000000000000000000000000000000010c7b15a6355d3152eaada7a606031f28809f278a1d0e04d264b563185ac7d9e351295191a6a90ffc9c6dd33995265db0000000000000000000000000000000014438086226a061a1bd557dac24d9333e14cdfa3a7bb00ded4a450e8889a3028b174bf38ae1347e6aad19ebc1cf5ff7800000000000000000000000000000000105165703c4592cc4f1f489d78426a56434dc77327c13221b582dc25306f4c5bfe596f3e47abcb741ab553fa14cad374bdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb36000000000000000000000000000000000ad8b55a198a5e788bb54c32112761ccba9863cba16d95ec9e30181376e7eccaa2741660f2c5f708300be058e566ae27000000000000000000000000000000000b9bbca7db413964d2ec113cdee2d7a7bcdb712d285655f6b2897dcac61456ba4d08e25e8c28627231824829bd7d13800000000000000000000000000000000001ae49c10675256651e3e038a2150d85993fa6f2a97b9bc02c693ed97ad52af34015176258b3b2546b81010a4381d85c000000000000000000000000000000000c8f9668a0a497420acff5207a07cf780e0b2ba78083eb0ed8eef76beea996210541bae2e64d825000f307d54cbe3b2b43da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000012990a66c132a31d60d182f729b52d9b57d9d1eb1988b6f0b4d9fa2847f26983078ef9bbfd0e888e14bf7d1f92d09e54000000000000000000000000000000000585215ffc2673a197bf9cc6c6424547886abc6ef5c6edfeab2ef0c42034a4a458fc7155c35c84a8e9e9d89fbd4aa25c00000000000000000000000000000000118fb4fe0d3498dd2b55e62272e95a1203f9fd22314921d3e044f1b162376aaa7e8154a4e2184b66451aba98729330c0000000000000000000000000000000000364b9032ab9cd9f599979c8a93acbdb831707f1f84fdc92844b58bc7e4d72472ca5b09c51b1b04271ed9f0e391552463c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b6805893000000000000000000000000000000000ddf9e4e302169e195f4f88fed06e0c93fd1b542abbfeea5da5d47c662ad9a16b8f4aed7874354fb9008d073920e1e7e000000000000000000000000000000000043fd1a4b781f25e8747ecb3eec45ce90760e0d5dd701e8193a7e726684ccb8ff21f0219ba15e9e777d339a3d87a1ee000000000000000000000000000000001117d2ca429048056084e4847c7169b4c8ddaefe1d48079949f9f14e4d74f0e0b38a95d0f17389f61da7b2a6d8cabd1c0000000000000000000000000000000007adfc7d87b1c533b4439f6b30d38c694b65b3b321f3eec13cd7d923f1d5f117005be6c3ea901a9479d79fc553e34e6c51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000115a81aebee0329b174c01458f8714b13ea3fb2dbfb051b27b29b940652f27e01a84e522626d12be80da7e1039e2baf6000000000000000000000000000000000d9e37d2e5e7160db30acf5593d1c282541a0d4ac0482f0759fef8704b9ec3ab1e3ed248e37c6be285e890ef1a520d0b000000000000000000000000000000000c198a22c2f590df2902c8dc2bb1ee427b33e9687767666140f9d3b51d73fef18a259d43d86fb3559b1ab0abacf547a70000000000000000000000000000000017e705af54ab76145a79e747167a4fec6ec3a16f3ceef86b1ddd1be144e616ea7d686bbccbd1c5c258e4546405be023d6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b0000000000000000000000000000000007c4c31287ae0b3bb90475f84abdda36610f887aae311d8e97bf97bbdbdfb11d38c7de331cc9dd022926678e5180c0770000000000000000000000000000000017f4afe28adc4b24d16b9cd97aacd171c2104b13b079c643d664a7c924151a401c352832c4967c0e5cecec5f1d1dae290000000000000000000000000000000005a8aa8a3a91461e0ba256e44af56875f8d07e24628e738ffc057249d8380417884f40c84e76dc6ce5816ffc05c0d686000000000000000000000000000000000f84bb7385a6936b519e881a708541570a31a9d7897ab8b348a350adb0d30522567fb917c9b6db661b6f53f98b5e68aaf4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb1000000000000000000000000000000000a8726ea352582ed52ab4e440102963891f059cf5a3f4901615733ad560a99930efd8692f3c30256d58e5cfc4f0803bf0000000000000000000000000000000016a623dfeae872639d99e3b8209748642f20af796325630596b6ab6146783bd4f2247c7ae38d53ba9a3fc0cdd6f29087000000000000000000000000000000000e40709656e569e4fe90eb85d8761c6ce44a4272793ccb7635ce75d67d97464e8dcd8c32bd4ac9a36fcce706006915b20000000000000000000000000000000019e64802756896206a9600f2d888f3d307ebf958492b1b5153c06a11231e2d7d2f1786368447f380093a29117cc25da9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000019c7409cda6084edc6e559da9b361c96cf207f1e2cd63cabc9b86c5bcb07a59b43e9c6ae3e90a61c872f168ab89cb2c9000000000000000000000000000000001101bb63a452b766a085fb788937f6b751417dd8d905ee50ab5bf96cdbb9d7b68c1735460a71eaf9e9bf662734f495c20000000000000000000000000000000014a103871fe523cd01053a992eb9884ce83c6023bd6a8c2cd9ca60b8780118c88502c6980904f2d2bf9ccc9fb597d535000000000000000000000000000000001929f25d52ee6b9a44333237c732a63ce2abc80c5510bd67faad1d7adac96eac5449823f3a52ed18bb90b93d9640d0d1283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e50000000000000000000000000000000015b0a46692f57ccd2b7f53040dd75f30af0196aa3c5499049eb172b4d927f96a59c42a129117d6162a1bb31d2e8734a4000000000000000000000000000000001366dde2d9070a2c057744fffe78effdc328b122e356a6aadb10c3fd2e8badc0ff70bc6d18293b3c52428e2ba78766600000000000000000000000000000000016fd48b067b949ed75bae3e4db29b5785bf672bd01032a925d653f8a605998e1eff6c77ec39dcfccd417f1e0a9defa820000000000000000000000000000000004cf22bd706dbb1cf8b97187ed97636380871402b3ba9de58f174bf50a7a0b528749762c3f55f5f835a276e43b46e669d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b0000000000000000000000000000000000ff424ae9372af46de34210bb0bd670eb173bd49076df5caca4bc4293e742121267a20506f931a4ae77cc36fcbc8df4d0000000000000000000000000000000015a6815b47966fb84aad5de62e6d4280f9135e129f33fd01e667f4d6e1bf7204317fa7741f3cff3682e251437927131c000000000000000000000000000000000639dca43483b79ba8043130e508e91fe3f43bc362fd1dbb135a2eb8f3b94d5cc4af70f1101c790545a0eaf2408706e1000000000000000000000000000000000045f0a04a642bb6e4db34fbffc8adb19a24648554f36ca371fb1a851384a4516a57f1850f7d6be59ff67029ec4002de310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a99000000000000000000000000000000000e796fd500cb1a25b834baf7335641f34ccf04ccf60f82367f0e5c8c7fce8e3030e7b916752bac8e3adc01cbf4b319ac00000000000000000000000000000000142e8bbac9cae69ba3dca48aec045e0c4d7028f73c254433f921b7240761c661cf8e774a21da249f7758234cf7607fbe00000000000000000000000000000000045a3d80767d116e89bab0e9de812ffe7ffdbc41b61f5f17ad16be5bdc9968e34f46b937c5f94f8197e21b358f44b5240000000000000000000000000000000006978b93018bfdbaef0d40f1278e831a1fc50b44fff39b7c93820a284d90b699981b1f422f751a33094ae7b5cedbbb2691b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab98500000000000000000000000000000000003c3561f5d255cf1f83cb5f4df8e3b8d5655d965826d56867ae66da631f8e7d489f733f5824c36652ab00586d9c593be0000000000000000000000000000000010b3adb0017e2cea1b71680ca33aee368429880759660dce2d3cdf57b6cd7339bd8853e5efafb9a5aec3f7e22da676c2000000000000000000000000000000000cdf976e4c65edb79ff15178f6ec5bf0a77a30d97b799e433f216a2fe3eedb10bc6ecbee2974167128773cff43f1922c000000000000000000000000000000001599b60ee70d927849764880830b2e7355daf95eefef39ef61569a2b83b2bcced4dfb28047a1e5350cc87ef3cd5cf1d93e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8", + "Expected": "00000000000000000000000000000000123af49ac2981e14a490a4a6792f21343497b562266a47487cf6c650769c810852e357445bc68f3460e7822e8cd1e3f000000000000000000000000000000000143e79853e4bf6d031e1116dac2c6eca4991b8a1f822fac1d352d2cf1b88df239d82df886f0b24b3e7f305286cc1257e000000000000000000000000000000000b621056a9de2d83c946b1e2c7a07f9beb8e053202407323e412c0d8f60123cfd5818067f57034fe1b1b5a3d1bb285a50000000000000000000000000000000001642fdff2c52d58d38201cf44c31e00df47ea1439e9896b5ac5e9372482f4ffcc698be46c2d375d57a72fc677a9fc8f", + "Name": "matter_g2_multiexp_36", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011f78204afa05c3717d3908dc7e4356ef96c426ef380b6abba3a5616d9ee01addeec3369967ed42e030c4b8ff9939c4e00000000000000000000000000000000175a19c86e7eff0f4e809a5105503ed223fe327ee4617f7f51257426fe408373899f39014821292a75e4cc4eb9f7f31e00000000000000000000000000000000052130dd3cd17840385db424802d869d7eac781365be25ba401b7b0e4025353c8dbf59e5997b5aac74c252192061299d000000000000000000000000000000000457f4fc7ac5d7d4fa07e8ed125df4c4e950e6ea926be9c04b6df3c3699a10e99af7ea8546b8ac70c3003468a75c821ca2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000005182a3af2b52102e09214d048a1a29d1e10b7ace6afbc4e6b1ebf16790be372dfb6d65cb8fe08c3dbbed8c5435a59a3000000000000000000000000000000000f2a1261463c09a88edac443ec3cea8aaa19659e8b7ec2e8a403dcffb1e50ebb3d07217a9ab057d8d097c075609c13900000000000000000000000000000000007d6525fea8fbb685fcf89bd772d48c406aff7377429fb199f27c3c3337f11f8e24c4d81c9026b469600d11e8cce51be0000000000000000000000000000000004b6d6102debaec16c34fecfeb444e7ba573b13b83ec375f14d2c541a0d1fa528ed6599a0eff4f8ca527c5baa579f762fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af000000000000000000000000000000000ba1f2b2c3f1c57afa0ab647d32af5d036ef18069a4abc9795dd9927ea274a718b67373230e337cb9374ef73b5e2303900000000000000000000000000000000016458ff2f5d600af9d2983f535c965a2a8aee48c0d95095bee642bb7bbab8bc87e3e7c3b52a787c53f0d1e00cb4ff32000000000000000000000000000000000d11324e812cd4fa65d20cf58f88a9bc9407657834d7a92f80bfd32c7085ffa2f9f78d7e18c1405a03de939bd0dbb06b00000000000000000000000000000000144a0f4d50bcb16942d22a12d28bf34d2b4c51512a3f11c130f1566aacbdb63ec3984df5569f41ef621f50d138883d0dda609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000000d7b138fe445d7b7e130134db653022ebb389075ffb62ff9faa544cd0fdb9e78e313d0b1cb19bd812421d38d1e9996d00000000000000000000000000000000084411aa2719b729a1e299fe8a710f767009060f1b2becf2aaa92efdeea8c91239aa5d2504c6e7ad2e3f39d89ef00c1e00000000000000000000000000000000017e86dc0146c9bbfa5ea1e48f49918167dda13b31ba73311fd5cfdc12845b95b9e90972a9a4d36203be8c5920f8de5600000000000000000000000000000000150e4b6fa9cd9a609241d1de8a637c6ab25207bccf8e5eff4a97ed633b67826135172880b118037649407a3e1b1a0661bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000ad7430b7778248d63a06e26119e5600ae97071fe8827b24440587e8bf6887b646f342741af69d20e243c9b45d7dcf24000000000000000000000000000000001230cba1a5a66197875240fe00c59b796ba1db5ea5653cc76bf43d6adce0db3a109168593beb39bb45688c1d124b9eb300000000000000000000000000000000144652474c58413cadf9b31715152052b7618e7093e931367a7ed0340e66d84c0471b6ec178e1730cf10e749e01815780000000000000000000000000000000009abdd0210f25d12146f2911a60035867f59cc341b35c73bbdf8f7a5a90d0bb6566c6ba0e868a3d62d3557436190f3f63222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb40000000000000000000000000000000018ced3cd0c169693368fcf9c3dfc49fe248f0b9b5511e9407b8634d8ed7b54ae2dc4ac6ffde8b3dea70ca86ddc89449c0000000000000000000000000000000002f6b227e699dccf7ab1e0b1cba4cf9f05c4dcaf9fee6cd94bbb79f42bc9598fa23eb2c653a7654db73feb511b24829f0000000000000000000000000000000019785959766eb8b00ac2600d87240f2876e049725680f4504f59db6642ff8f82d4e1b856929643906c3be7807a2443180000000000000000000000000000000018285acdf25a475b37ee4da872debba4297fc8731eede6b22be3b0dff12117634de44b84a18042852ef419c3ae18a46b7431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da10000000000000000000000000000000019c7950b01e15669cc1f96fd94957535f32132ff6a5ae788f6f660024c332593942bd3e9603f862756edd4f3ab17b20b000000000000000000000000000000000bf3a6bbe10ad91d687a135f4863ba0332e9b04271d437a6a4770056e6b1ca34319dc895f9186482bbbc815aead03392000000000000000000000000000000000a3ef4d4f7a15da04a91ff079cc40040993a90e9ea21f53e31f7dede52dd513a97ece780374c5f3aa8c8b2e525ee31d10000000000000000000000000000000017749fc7761b06432632ac686d93484f08407504e58b04b3890cc2101f15d21f46ec0dc1e9028c8ef8df10f9ae929887d474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c730000000000000000000000000000000005a36af876edfdf26175c185c3ef005530e02474232ea659f5cf251c5de5721f1b44a25714967d283525632789331d2900000000000000000000000000000000130a6f5edf94736477143b1efc316f131b36d9658c484821be08e7f5b9c93f60cf34042858664db0ff0240addad8782f0000000000000000000000000000000004fedf49e6d49c074dcca96c01607da2105d8053861b4c677a69cff0f82e66a2a63f32f3d9fac8e6c844a1f77055bf31000000000000000000000000000000001528541de3a9d4a216c0e60c31d2b7c7cb91b839fc31307cc70f18e9b87b92bf5b9a9dc4eaacdec6e6bf7791e547d8a2976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd10000000000000000000000000000000013faf7b2b8514f77021d8927a3b63bb7c57785e581f40ca82882341c13a9daf062a26b668844e58291366ea6ae2f179f0000000000000000000000000000000009060f9e1047f15f175fe95cb0914f4941bcaf071f24e856eae6f36263c812689a9217da277613c10c8e254a0933c80800000000000000000000000000000000154619e4ae3901789ed3ecdfc76069d8026a3e2cf142a144e8b58482233380690e378de6b81af0ed9b6536da1cc2a30b00000000000000000000000000000000040c1bce922503699e1fd5ac67725f11d7f9bb6903ff9204412f65355be69d73cd7330a3f7bfcacaa9b078ba6b9a9f839db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000103d91916d537379d6d8717b17ac5b7e9fedd98c24890b51c027cc086458259767d989b3ff9d6adad72bf977e4d378f400000000000000000000000000000000159c01ee371622378339518217dfc0570178aecc938b4a008dee1a6661ffa605c0f1472c107558ea791e0959d7dc1c70000000000000000000000000000000000ea3e10cbc3a55ef2dc7bde7a2e80666557e9e8fd9ce77e2e92c2c70777afe43c23072e263e1def56cae4b6d3772db96000000000000000000000000000000000cf1db638331c47f9080c04117ddab4ba79950563810d50e04af819f14ae0981f6e1e94a635fc90226c8d7beef0844354ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce19970000000000000000000000000000000000095c82c58182ae9a1ba14421c2966d687f7225ccd192b24097f997b471d13b46a048202712cb2d8b1be0ff40755dcc0000000000000000000000000000000017410aca05935a06942f673d1937a593423cbbd226f6707c5922306d28a60396baa08a941122dd4c583331c9481a734f0000000000000000000000000000000002c1d3a1262ce8aae42a6ed10d8020c31a468127e1a59d57d2d409ca9d14143d9fd21353b260edd8b387840580698846000000000000000000000000000000001512e29256b6b9f5a7ba4f79dde2c915b162e4881856258ac2050f02868842381518da4ed824243692b131710d7201f8ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000ff83bf1d50fe35bb3d1bcb07b02d78a1b44d2e0c6bf82c600feec3897fae8b93c0ef05006c1322af0a732392dad86e8000000000000000000000000000000000d70c4957cb3615027cb950e4224d41849b9ff1b435ce936384fe17c4d7bc2883fdbba5123ca0c0c010651500557e1be0000000000000000000000000000000008b16fe9af45fa913aa7e5d01b5b58f946004eaaeeeb493759a5bc2b192d2dd71af24ecc5c6838b5e267ec2dfcf5c17d00000000000000000000000000000000038ca027c985af3cf60cda13e770fbe4919d3a5b413763c8ad155cb4903312822366eb986f2ec9e0804594ad4894e468275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000003a313d6d41f1ebd6b98b2061a2d85943e52d89e4b8680611d41ea182385e154da24248faa1563e6ad79172f91a8763f00000000000000000000000000000000038d9388fe9169710e1a205ecfd03f674b47ba2275794469dbd5f193a55e00765c8eed026363b41afda417bdd8910ea60000000000000000000000000000000007a75f53d9b8e5eef19ef6f5fe8ce5d5308a1a7d02e0bf46f91a1e0cb22555752d82d8471c123269050fd8f35a272f600000000000000000000000000000000011f313127a036403652fb2f83c5122fd12c362ecba2251bd6c357a964dd758eb2a2c3053dc668b9a4bc071898d45cd46ec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e450657331e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f70000000000000000000000000000000007860c3403607d4e13f738357e18bbcc4df10fad4aa25776f84d3c2758624a83aee0996146ba17a812384e1d67a7c54f0000000000000000000000000000000002169148d86b1f7a0ef75d9bd19b6d7cd66da4293fcf33fed9241544dc2564d980161a6bd959f3b43569312bff7a23cb0000000000000000000000000000000001897c121cbf5e82424cc50078ca7143a0c670f1217a9180cd2a4700e06aa895cf84c0af94b7c04bfce047a7d1f8443100000000000000000000000000000000040c1a0c4257f90bd83fece3c9372842a148132d2dffa956729e741ce996d229aacb04387d51a72630329230020b2235f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e00000000000000000000000000000000058c001ee1343c6cde55bbdc4c538f5d14b0e8c199fb822f080ad96ee764bd1908f92260ce60cd521919f223301ba1220000000000000000000000000000000000f8943c35e7fb8b58963719f1b9820153e0831cf81dd208176af7527781ceabcf6ed2e2276cbf374e0525952bace0c80000000000000000000000000000000003b43ea8c32a13c014b05326f7b4ad5b5fc1bb2367866a69373ba91402f4b45409c6d034898e8b0ec3b93c2878d59b72000000000000000000000000000000000101c371ab4d57ed2cf17dcb731117b1986bffc586529fc1edc630de1c6f4fdff1e10b0895907bb81d2ccd3eaa96c04a67b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f", + "Expected": "0000000000000000000000000000000007152d538d0f750901466c1ea34a16e7b0e1296a2a3740568812587affa5c0c76ca2055804e24f3415a403f06a717c0e00000000000000000000000000000000119c0c282d22a01524d87eb850789c4816e7dafdb2782b57c32409b1016615beeee2067443835466283543773cc8b427000000000000000000000000000000000d68137c3df081a519747c044950c3231ef82295eea5b7040843668195d4549c8ece4a91447e0ec89530bc51277535fb0000000000000000000000000000000000d81a4fa2d32ada3e08a7bd4471d45a6afd2cfad5bbfa3d378b1df2e0749f9b05b465be61cc9d1a0f4abd56dce03dbc", + "Name": "matter_g2_multiexp_37", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004b153d6554c9879359052717457179d8318f8127eb73edc1d6ac2efb1ea9643c4357cc813d1df49730b77f995d6d449000000000000000000000000000000001533a450737b4bf8edada15446cf21ebf82aa7bec7943025dccc4784e1070fbce430699cf3a37a36a3ece692ce87639c0000000000000000000000000000000017b8afc300bd70a3221120f6fbc37a8e6158c48b476f00992c6f41808036765071bd0a76f7c641443b97ba523153947e0000000000000000000000000000000012f686b4759a3d5db2325508f148bfd6217e027fe261d3ed7b8fd0526036044dfb563e1c4399ec266e140ca372120e289f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e00000000000000000000000000000000061c8288b7bf2856c075a176d1086fc49f0359ca3e7c1aaf5f151e6462916a4e1b69b6decb18823759b620f7593079c0000000000000000000000000000000000877af18cfa0d029e7c9a5833b346c7fdec06e54d9641d3953d3cae0e8912bac7c990f8864c2adb6e576442c634865b6000000000000000000000000000000000331490f42993de3ce7cdd53afb4b310f25881710a23f601ed95961bad4e9cbf57d3077803908a91b65fc32d1a84130c0000000000000000000000000000000005aec079da804fc572bb8eb867acb93a24ffb6611eba920d2ce799c4c80cd8e73b3cefe989885167ab685365529b4f2a8fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f3000000000000000000000000000000000ecc7abf4f6f9692cf3a118cd01abaab4754c90d1a59468d402bd699992800c2994f47b2094878604bf7825f125133e4000000000000000000000000000000000662396427cc596458e880bb8a43dbe046deb85601e3c64556990de36e8637e8ea3b142a8195762910a83609ca311c3a00000000000000000000000000000000198b9c52be68d073910f5b26bdeada9e9b308e4541561a8ffb21fd8e69ec9d93b01ec966fba65be27ee53d4857a43e120000000000000000000000000000000005201975985cac810248e333ca714cbcac0ede46bb915d8c857837c80c805898d0f9ca0940819878a26d269faa02cb86128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000004e61ad4818ea3c98ed3c0d247798a1a6ca6bcb35a1cb60bda9394272ec092c385661ab93a42af439f1b55ee8b9c0cd4000000000000000000000000000000000ee0b71ebf39e4009bdb310fe3b555cdc860abd47a67bf481ab36b5ed0c00bdca8082abfb75691d45e10c2f2d777be19000000000000000000000000000000000e9582e3b5bd580f3ca7ca1f58e39379918f2d04b82b418a91e133117a9703f7df4aad30d48a47e29aeaccf5b8e33559000000000000000000000000000000000113b4c068fd040cd3300a2d1ef658955b014e571e7c77594edd31968037c1fff241da88e7a88669a569462564e28cd7d45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c206000000000000000000000000000000000d8b0bf633072f19db61ea263a1dbeddb326738396caf1196e31e2cbe99a68e8c70f8db13cfdfa4fc4494e54c1ef28210000000000000000000000000000000009ceaf2a0c63604afb8a903195933fd1ada0e5314255be3d74a95679c7a7845785e22d2c0c206f3afd62110ac9810c2e0000000000000000000000000000000003a135b405f46ae3f5cbdd63f4964cdc5014c9f3405c2062ba17423dcd22b8f2011638d520ce0ec7bb0cb5b03e8ec01700000000000000000000000000000000066eafafe1cea67aa6de267c767f49d4a3fd44c28d45a920fe9b3cebdeded883d8960f5e9fa4cc179246918942b1428d28f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c13220000000000000000000000000000000017e37a2f1c892fdc58ac3f72cc5a5e2b7c0c87333afb06de89f7a84b1267695bcd452925fb2f15f8b7b20aaa85a6b5650000000000000000000000000000000015b2919343962337a41b54076d6735a093190e1965faa33eee800f5eaa43c35f349aaf93f19977b6fcd19360b27edd6d00000000000000000000000000000000161afb1494482f953007038557c847e2cbf84c57c5f5b806e3b0178e71e3238305f733943bea7ad6f2bc290778638e6a000000000000000000000000000000000c27a2170fa584863697292e626e2539aa15f3c8eee65cbf1f1b7ced6297248d059fdaeb9c955437a51cb016d1ee97c3e6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000313a30edffaf864d0f1c6bdafd7d1e563cef434d45e71489e9f9e4cc6700e44991a99220f53f0cf5e7de5f6e4098bf20000000000000000000000000000000010429081ebd2ed6fa07de6ab0b7bd559a26a43df99fca0a2252411b4554dc69821ccf3df1b05114da84a616ccee0a9c800000000000000000000000000000000131a31442f80da4621f7691664e9f8b467988fa039bd086a2d64f9810878b557614c27745b2e821016f648ec36ee797d000000000000000000000000000000001160cef9f5e4d022baeacdf10b3bf9d7ed5e50627a99e29df1be3667cb872b2af333f803bf426314369b490c2eca642aa83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000b20ec53bc643bdfda1e3947b3773d748cce1992e2ae27c6b7460d90d48e08eb9240879a5a7d3dc3189f486706438dbe000000000000000000000000000000001024bae4a7f71d3d2fb8246e82d95664c4ee8bca4a380c293ea084f749911f984aa4c6f266ecfff69c4f57e20c0660ca000000000000000000000000000000000b58472d81a9f16d2fe7af87170ca0c8c51dada62a4b2a713cae053a0066fd268283a785ccf269e05d8873cd686d2f4a0000000000000000000000000000000016b68177bce92fedfbd90cdb752bc332f46fde6673911c016fb9cff4912d79d3267bf629c33097cf8979f2b913c0936d4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd37000000000000000000000000000000001030d5791bd2a27469d242c62403883ca167303d907839e608acd827b4118b752840a4eca0acbe5df0b447d6651e517800000000000000000000000000000000106d65f922581237f779ba3e66608729dfddb2c487bc927f34e5e39707f2c8a82e8c96af68e3257c7a9876a05a1b01d800000000000000000000000000000000115bec40b8fa914305b1d5a85b65f0811517d36839494ba69c929fc2422f7e8b85d78df4e1687ab0087287eff29c65430000000000000000000000000000000018d78a75ec057cfcf179fa2ffa7dba79cabf6525dabd69ab95b23dc8f293aa077e46e562caf447dd0913ac9dd60ec76b29d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e600000000000000000000000000000000023f7736d6de94b08d9e9efb6f32f8c17cafb1e1b9b1f3db6e58df72a451c3225d11f4304eb0d702b07a7966f95a11fb0000000000000000000000000000000007b3204f258c873a6fcb48d0b36c98ed5f99b424cf4f92a028174e0e93db2af549648ea95fa8c7bbb42b2a10eeceaf8500000000000000000000000000000000165d6e769b7df91374dfb44e18d43e03ae12ee10d8a618a20f67332cf96492ff514eb7de06ea53096e823770c686c32700000000000000000000000000000000012e69ca1e106411165c06ca15988362de583c4a05425e2f4aba4c14cef6d8d04c52c87b4fb26b1557801f55b02ee8ba971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e4000000000000000000000000000000000e8e6bf1c8837c31446959242285e9b85978a5349e1f0b3447e380a7bcd6bce758bc6192cb880f9c09d6ad4a0ee36eea00000000000000000000000000000000199b361ad0b435d7a66b46a43d06e5898376a6c260b68c965f7b186fc75d2f321bf883646e7551eaba03181907d3aad1000000000000000000000000000000000a76e3f399f31cddc4dd4bc22187a68fba31fe2371291ab515d22730d320ae4240911c755750f687c7d26aed09da4210000000000000000000000000000000000cbc8dbc004b9253ba91b2238c92bbf7883360c7ce39f6e15592a8668654950a3fc5a94cfa97f5ddc60add40c32a3630659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e0000000000000000000000000000000005448b623604262a9cf1a9a292c36738960e132bcf0ec8e61a510008c2ae0b51b31da25f2bcf0d7c0d4ce15b1d7179fc000000000000000000000000000000000b61df56ef891bac07a873571f68fe43f79438a31038cf8ff97393ba44cc47408e5a6d64e9ebddf0195bf914f141e668000000000000000000000000000000000d196ced22ddf11132bbebf6c85bb3006a194cebca975d74992ecfcbac546f0f25a39ed5d6100768c1f1a791f3604d12000000000000000000000000000000000f727cb947849d2d7b046218f084283e5513e8582229085f9f98fca522879543429cb8ab435aa3dbf01b68ed258d82c112ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c6000000000000000000000000000000001044bcb16b3384a1f350cbd62bae568c96932a364c16b34d91ab9b1035ddee93a02920ab4dbde2c6f254031909dc3a450000000000000000000000000000000004a29aae48210289e5f588aede0756ddf60724b8ac54de5d9159ea834d5da98b7a9d09a6f37bcaeaabc559dbdde58b6800000000000000000000000000000000112ca953b5ba652c715fd20e3b85c5bdfeaa7d577aa49aa4656d142c9c2afa3d8aee151338f59a199f3c0c3f6a430d6a0000000000000000000000000000000001ebc7a17da7809f9e744cf7f13fc437de34d3472f022493f58bb979e2282368f989ca0982098a7c377498f1d8d32583666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000000e7c3d6bef4b1723479ab6724cf7858c221993357b194e5055db96b8168f8d78f72aaa4a2046be17ae9a7eb00695ec510000000000000000000000000000000015e85e85cec08133b86738e1f7a738de455930ffd5073997a1f1692c28044ac00b634b90eb24938cab56e286ca0dfaa400000000000000000000000000000000164646a4767ed69f9280f96be9a7f988d17c187162554239797436a0bf4c4ffa7e4f8387c3d2406a7528c021f56081df00000000000000000000000000000000197b1080bf3ac3ef7bd6123a55f20f1002f366d4efea9e14ed92fd2ef147e2b5d9251a302a85172235438bb2d35943a740a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e40139000000000000000000000000000000000db7cfdfd58a6ce9dbbcd5d65cbf22b5e1a81acc70f1c85651ba962d61fbd7ad83e5524fb9aa019c6bd75dade96f7d4e0000000000000000000000000000000011e269a390fd15ab1d52d38de78ec97eb6202604fab02c4598ecebc7635ac91ee564e751275a485fb43b933678f11fd8000000000000000000000000000000000b8597a00d2401664405dd1fc7d69786353c86cd4699af981fe869f266f9087b00df22a46ac34883173bead870798f650000000000000000000000000000000009117a49b3f2a8a850a0179b558319bdd19a5f1f4a45af0ccad0890e63b222d028536e9bb612093cb3f1068d262af90d662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000002e1311abb9df5e4d76959276b6f725f13728844f8c7dfe5e25469cb95c6937a822282b3baa38817e24a6219601132bb0000000000000000000000000000000012820e6ddc50e19a8f98c15013ecc38901a4ef8ec2b79b85c7f7913da24404afa1c79045f1318cdf271028126f9420a5000000000000000000000000000000001794e653c5673e51a3ace631c1a1265dba07fb74235506b2149d42b90eb16afc26ec0ddc54d03f7ba2dd6a2503971fee00000000000000000000000000000000112479bdabb9dd057b325563c666910c01ef66adf47aa32f5a41bc9cb8234750985c266fcc329ea3704e2b8d9b15bfeb59401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6", + "Expected": "00000000000000000000000000000000168c90045dcccef35cfe8eb642924ec2629db886366fd9ebc082019690d103627865f0dc39ffdd2167111f68d8d03c89000000000000000000000000000000000b6f0928a32672983664ad15252b3f145afaa04f11d5f43a6169a2fbdc0b0a04902a183b25e38987c45579ac6d11011f00000000000000000000000000000000195c4d796989630f85df4594eb8353d44bcee76d82b73ff7a57069466337b49b875b3c1418d22d79716ffded7e704a6c00000000000000000000000000000000032db644ff8ca6a3b1ac7bc51ff783ce0cdb7bee8b2c21dcfd3adb56a3e210390756211f22feb3dd4f706e13e5cc163a", + "Name": "matter_g2_multiexp_38", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c675cb5e90e45300619be91c752a5831ec47b4143c28330422cb57139882e776c1c5f000d6032cd87c16ab3b1c08ee0000000000000000000000000000000000aeb4e78724d46a55e5f101564bc05e0a1be1d80a781ce8a19607322e82c7ee59db9f53ec34c70bef0766a5b965f54b1000000000000000000000000000000000933e8d7c2420cc553afb1c88b5f64c7a39f78272b34b5611972dd5ced3f639ae2ed2aaba470abe829be6ca6d666ddaf000000000000000000000000000000000ac0a9b46323ccabf4b2024e3a5b4717cd8de9ed7de8a78e33a38037f802651a4b43380a746890d93289d547d94b61bb9c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c000000000000000000000000000000000196597114fbaefb8108c185a85d0fff0f6bffecf056902b22d61cc70b49a747bb35638f5b663830a8d2ee15df9fd5a1000000000000000000000000000000000616ed44a5fe69da994e2ada03a1e09065964223333229f5f30ba7a452830848f599ee21810a95e3659cca495897bb710000000000000000000000000000000012d0631e524ee9d3c776c79137499f8c9fb752ca93e92497d89973033d60971da23f672f140c1a753b4d00d08a00babc00000000000000000000000000000000111159e95d131c8cbe8df75853fe9b3f24013daa083e57c5b716e77f6fd3872dcfe0156382c9d2778fe886621be19973ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000a7d253487591fbed97381b3a430404b87aac04073e5931ee0bfd9ea6e0d38a41090c6dc7f6a591336fc58a97a3bea8d000000000000000000000000000000000647c67f1816ae6fec39033c3169eb1ea89e5e20e755cfdea33572d6397e7e87635c7439eda4912361a32de313893206000000000000000000000000000000000e0cbd54634d070aa3c7a503df1171a5cc435d050def17395707bdf7a61cfd539348ee5a4c29c7845cbf0e5df0531f530000000000000000000000000000000007d006601dc1e092a616eb470be35b5d32742dc6a2a4d71cda8865f071dbba9d8a3a8cb10b486253b1633e4590e716dddfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000c502a19770a892b2fa1ba59900a36c0ed054a8bfa0c4e32bf471b90d0da9edca6c06b133c8f12e233b104262a81dbe00000000000000000000000000000000011801f159086d07833a691182595a42645513d316c084b2841445c4a63c6bbb402664a9a9a100e8d6436337ecbf398bb000000000000000000000000000000000f2b9bfd8ef6286bc41e9f47ffdd3efe437aad553c9da02b3c22ca04b5578d634c0543a07bea966bedf345562218c2190000000000000000000000000000000010be5ffe0cc9f580c74e027aad09c213189fa4b7aa92160ce813b8d398b2e2803294e1a730cf5c891cf1546c6bc91414f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c300000000000000000000000000000000132cd5220c125759a18c31313592eda774247f97b5134111b01ef28dad5c3ba4d3f13d1af9076d663f7e217258a6fdaf000000000000000000000000000000000f06a5b03daaf8f92f9a302f06413044ca0dcf2be81d9cf016120312fbd41b273650fbb542d419595fd2815a809c4b960000000000000000000000000000000001b11acf12cf46e40554a1d6a833566cec1b2750f3f72ef77496477831d5933f477d59463ba19c03dfbbbd02fcbb680b000000000000000000000000000000000b2aaf91827ba923c8a1c2fa1d6fb92384c9f48f8f77273056b94245114d1f3cf66fdcab330673ceb2e9dad6c1aed0d4c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000051858339be99d1271152bb390e9a2ec0c0760b7686804ba072c46db3cfc4472404a9f87d868a28f2aef16c9e989d6e90000000000000000000000000000000019a33f21d0bb8303f540bf26816f145360bd1e9a8229dbbe7981f1cb5b099e814f2691fadbeeed8e4c4b772bbd27e60600000000000000000000000000000000073eeb49aa7e601732dc0888ae6b0f5e8dde3d97b818155221f5ab8c599eda75b25c86f15ceecafdfb9ee4abe3419e10000000000000000000000000000000001507073b97d494de26e70f18bd1723d931cd2a88903ab6da2aac3b80fea78ce75caaa9b99375780d759fc4a1683950bd458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013b5422deb0e80bec71309d03fdba007eed33c3ce0fc6d4f9a0d063136b3b85a6fce90ee59956a9b91e1caa519f813e8000000000000000000000000000000000829a11eb50f3bb1a47b72cfeec9d1f63e02b9f7b2592174c481ea7b72a121645ecb36b3d1964b082bc6c7efb4483a180000000000000000000000000000000003d3aab53814f55fa97285af2dc6d32cfcf5a08032d2c15ac83ea036603e08a53e0d2b8d93a25dd969937c113e78064a000000000000000000000000000000000c938a68688138149cda64f168ac1466c401196eaaa44a464d9e345c422948767ad1e25d1ce4cc5996ac5d5dab61516b804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000001ab96f0b60213855fe221fdbe2fb22da6bd6cad8bab8ecb747c9528d3511976236ffefb34afc462abfde13a99503cb900000000000000000000000000000000182fb121778cb002be3f90e2d6837a406edbb609bfae8fe59837aea6f5f6131a10791f92188958b57059b7b9a9d3a24500000000000000000000000000000000159cac269098d223ee6d145a4489f05875b6a546767c023dbea62b3cfba9f8518c9f4d2594d00ceac325f3d8ef551369000000000000000000000000000000000c0d2e4e7aaedec7e53bfebe8f7fe5115720e58768469b6673cee3473b08fb8cd1ebd0514689ef65d78d008889e3ed296f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000079c40bd7fd2ce0f48806dd2e88850ba988e5adb0cc5120977da8110b07da264318fa034c0c213590a2616f0ebe40f21000000000000000000000000000000000905f41389be39361fbfe7641394d30870a079f230dacef89149fdcf81a4d1e0e10b9fa1c0c3ecadced9aaa19fa9dcc800000000000000000000000000000000192f50e08e497f902403df40a504a1b4b82f1957572a9ab7ef97f5ab93c6fb876d8b08f318244cba95ad5200fc2a6e34000000000000000000000000000000000be7ef45a14871dbb344a69c4036af4f994a22ef14540377d1144a92978a23c2d678cca47cbc18e8c036714112d11f7cfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000018d7410f0105ff03cc4ddd87a6e0b65ede4abd4609db5ae53720851c90255757e63c6482de4651eb1d3669b1e1a2f8d9000000000000000000000000000000000d4223be106693a672da890b64d2653135119983639f7052eb32051c34113022080ce2355a93a2f64a75d8e0578b2f95000000000000000000000000000000000764780391249d0c987270bd181a44f6260ef82eb00c06585db7ef09e8b069e46c4e0e659a081ab0fda491534b71b0ba000000000000000000000000000000000a8546031e6466ae43643462b7617703a63841d6d4cb0c09ce63b2fbe2c2ba7cc35367191d0313717b1daa665bcb54551aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000001922491dee4e0f29a1dc090c9b48fe8e6d70c3441e532021985932005b22cedaeea7d9ce1796808d756b740ec63f8ca80000000000000000000000000000000005b34dae0e630be6a59ccae17b44eab4e7f10be2ee700bea15f9771a724f0979798617e129540901a8aa023630a446f800000000000000000000000000000000095bdf612289258b31cc79188566ceeef6fd66858b4dc060864d378cbbb69f951e9c6bfb3d1384014507ff29f9446f410000000000000000000000000000000019f06f11a833c06c1c9227255e3a1d74172e73b06675c547844065dbb909ad66bbc150ba396fa1ba22b7183c0fe80e96c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa2890000000000000000000000000000000003de8082f828ec51e23c864a16147546ff60b5fa71897ff4c120556af5c6616bde96b6e53fa673cd1f8af503070bfacd00000000000000000000000000000000093013f75b6a19b5433b3b5ff044384ddfa258420c80fe81e0424e3102cbf9e550a946e56ba9746423ef745e33da51e7000000000000000000000000000000001227cfc3e9a8d6a71738c514c05766ed4f1f4605198f5a3ad8309c0a49499e4ecd34ba1ba7677d6d90203e54d7611807000000000000000000000000000000000a635221d514e58170ef299eb7f5b679050ee24c589cc7e348b2905a3cd1b7bcf2010cfe168f5aa60f4bfe15e59b4436e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf0000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad048fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf0000000000000000000000000000000009e8450887137cf45b04184b3c6fedac6676cad416a7646e9980dc99a6d6b62164dbdfae7cc20edaacb84432627e6e550000000000000000000000000000000002acbd87ddca9dc775da01ae026f1c60f1cb5974ce40caef80cb0d2eb7839777c1f61eae0472c7568ec9d0ebb2ec7dd20000000000000000000000000000000017c295c458a9dd995d848e3ba585f8dcdec4185a953e4b8e3ca760eb3e815e39a8ff60416e1e6f974cf7e7b086ee4baf0000000000000000000000000000000003cd8725e1cadfbd80585bf5a19e086abd631d6787403edb4bbc785d1a81f6108f451ff642f4df17dfcf94dd6107352bdc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e000000000000000000000000000000000b3cc99db523b3647937b694fc23281a74010079351b2c7d1ae4cc9167917f06c06e627c4ec44af6b09f2886ddf309b800000000000000000000000000000000001e2681dd123994627adc92e6ddd3ffb006521d8bb03040fe1989e4f709e4797d143cd0bb749de33c8109933c709e970000000000000000000000000000000017df13f532bc9894be932e72c609c0386d32390dee95dda45821bedbc1067043d46007b39b6ade871bd36d39a17dd04d00000000000000000000000000000000162db4d1e956fa5b5f9ef244dbc0c6d27718eca7dcc512d1d7b97bbfd2bd00cce7941d1b9a170da6341891773a729e9ae4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a000000000000000000000000000000000f0e4b606ba0a175bf57d4478aa286640ce4b5507f9f9e354fd96c45443333f6889a93012d663d78956bbfa7c645bb9d000000000000000000000000000000000d85dc4d733f0498fcb10e1e814eb61245203d6c1a46181e5a388fda2680640a1271a68d645f8fb179c0dc3107fb788500000000000000000000000000000000185b02140f6314cb62bd7977042ffaaec41ba8788d356047488004d609ae680c2f0cdc94e59a3cf90b6651298b6a81d000000000000000000000000000000000038ce717d08d367a9f882f2241ae4cc0e8a31418498bf68d05805db2e162d053a10dcff85403dc473598089a78dec27e03f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c", + "Expected": "0000000000000000000000000000000004cb919a72e67c31b3409c28dca1d57833a5066c240d2889f5bbdd5540ab2a49484c2462b25da197ec8d93dc8f26ea83000000000000000000000000000000000e1ac1dfcfe22ed7ac52c701a7221b542ce72bf59d62cc49f95f8ba64c05060671098d40c83947dd1952494833a19b55000000000000000000000000000000001331f6ed8ea5ec9b9e1a14997c2c9bc9af2ca305b313e2bc5c5bd35308b7b451a362f8ad61d636dbf77d1b2388702d8f00000000000000000000000000000000186b85e656e45cb5ac9a2a2009353e45749b53dcdcdad4f378431a0e4a317652301f834617e14dfac9836c3c11512aca", + "Name": "matter_g2_multiexp_39", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e6292b4d3031fcdeabe62921f0c562606b1ec6139b9c43938971d7851da4945cf69f39652425396ed1b2e70e65b9f55000000000000000000000000000000000e94bc63f3b8944ea6bd7bab811c013fd61303aa7713619faab85a271308bb220e2a94b26f5c7e4136a3d2761dffea610000000000000000000000000000000012313ef65ba41f8e0a57e9b810c13d23241e8430c6ab967a1a9bf5bd6308e89c135e00e789a5610694d146840fbd877300000000000000000000000000000000165ce83af7edc9e701eb57b332597305fedf4b939f3a13a95a0bb3d119c2a9204a4991388f7fb344ec8f15d32cab0eb5eb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea0000000000000000000000000000000005a88477765bbc8290b7eb137e6de78e62bbd929ca511cf0aa701f926440f21d33bcc6ac8f2ca5de57ee8116c685ba38000000000000000000000000000000000738074a9365c707190f882780b27dbe96179224103392b86c628b601e33b092a03e24a89bb6d1d1024862a9df6fce8d00000000000000000000000000000000188c713945046771bf852155ba412b4222173b6dec8320ffd1c59e9b36943c2c18b0dd3bd551b7b1367dde3e8031201a0000000000000000000000000000000017222294bacd664ec37e9b214407e5325eebe9753b430589de2eea13360783be52a479e2b0e9c5dc4907dd5f06a7fa822b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a000000000000000000000000000000000c97299d7e18f41e538b91b75e962c3ce4e068202271b40469c58cfc477d7820e90a0e91d647e8ef5fc0cb822daefd29000000000000000000000000000000000bd1e11a3646c499a240bad708f97a49acaeb653aa5bafdcaba41c1c9d32d32c516c94a3db8816e0a43d1b1eceac7243000000000000000000000000000000001223ecf82c4622653ce84460c39afe8a967cbd87a2d75cbee1609161837c15b522480c4731c9e6de9c5c392ef1db18e10000000000000000000000000000000016c5e98d3d17c723548427868e3e6d7ef4bca339e41acef19e0710459bd4732de4a556b22cbb49b823c4ee656fa354f1babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db000000000000000000000000000000001125735092842142cb5387f1ef8fb69a3535e1f0ccce59661183d3104ec1ef79dd87a7fb36159bc67bd73ad403b46c1500000000000000000000000000000000162caf579539574199d56f4e756f1532c66278a55b4f67f4f4090368260f46023543a8a18d49e8c5783cb65f93d750480000000000000000000000000000000003accbc87996a220a625e36d5cdf05d8c16fb353068ad819f94ba8223cdf6436f8d822719153bdba620a07c5dd955fe5000000000000000000000000000000000b53c8a4b62466c998327e0c5ad65818ea383650bf0977d98a8a94fa9653fba276f7781af9f5a4e99052ee3ae65c283d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000001264608a59c0ee9a26568cdcea8801cc8cf6616773bcb0971234b2d987563270c7b2291fa035c8f2069ac99e16c68fc0000000000000000000000000000000000e839d8d982d6663ca4552527f4fcab6ad5e0a444e7b5921055c774871601d342a151133ae15bc76c023b7ea643182ba0000000000000000000000000000000012ffd0696b7e29b305412fb840c596b66b77ac2eed936fdbe0562541e4de6b3166a9991dbdfa0f79b78b4b86f11291de000000000000000000000000000000001777ece357f82d7303aa816237a0dbd3a1398574f4061dd2fbf6b32af38a65abf5ec9bc53bb8ede932db9cfa0842d53a1a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000002b41743325db9550c3a84af80bc20c54b8b0b685d7f84d05d14dcabed2f450b91675aa8c5c650eb81151bcfbf1603b4000000000000000000000000000000000f3d3e69d475fe1d4259f18f193cd84a90b91589a6502588106f0a577d1c1dc4b2feeec20a4fc30b3e403d6ca9e03894000000000000000000000000000000000c10e2bd1335363fe958eb50981b99bfbadfd1c414830857b5257bc8fa6e26b50989d9adb5b3a2fa610b3151f8754309000000000000000000000000000000000008c825371319f4ebd684f76b567c4e9a389dce96068c101568dc8cafcc10896e3c20202b591a344d9a1c1be02310be9b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa45300000000000000000000000000000000101d3f92fe64af93468229608007f50e3406719572acf265fb8b2a7051525a9cb67cf2e46fc8e098cf081e73f3b20c770000000000000000000000000000000017b1422f8208c2521e3896820b22a65bb2a9b47d7fdcd2ce57196123c1ce43c1db6d00f236d7582795d00ef33ad6d585000000000000000000000000000000000e261500a9c64f5ae107d6ccb57fa9151f5321ef4e80f0e271515f1eaaa5e3714c59bf97b39acca41b15d90c0505ba9a000000000000000000000000000000000c08c955b6df18444ce3726711d29c2088721fa0aa6e317c52a05f73ec7171ef8bd61047174c74afa1dea804c68a28e33b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f646000000000000000000000000000000000985cdfb3934e0484805a1965984028d6c459654a3eea6ef66e867dfc737e1bbcd92e31020d5a4ddb7f8091cae2371f8000000000000000000000000000000001998c5682209153a261bf981e16bf1f7a6f8e5e566c1b0f975253ea62439e5b36c5e5060751f21941edf0d348bafd18a000000000000000000000000000000000c8822c1d6412bc45fea05faef33c65d5a6dd13aacf1279b9cfda2a2ee34df3146d45e3434ce8e5f242e9cf7d3ac27180000000000000000000000000000000019191b51d6664a3047aeb5590df2939b2cbb115ded70fafc2de4c2e8c2a955a957375314081a8838bf89d5a140b7b915f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b00000000000000000000000000000000156fcfffbf01ff3c8a97e7bd99e59327d38c6f7f1083d068ae158d1901808b3c9ac96f95c2bcbdf5f74b36dd8ce58d7d0000000000000000000000000000000014c64256d1cce124c01fa727482caf8ccf007e4ae00e5277d984f31a11ce584e7633565c61d47bc8accdf7c28bb266b200000000000000000000000000000000052dc9f7fce4859c852d3d9e1e77bb7887ffd35d4d550726632acab3d4303ecf8b3ec7f4114dbd590ac20d748570899f0000000000000000000000000000000017abd1e5dad7ee06116a8131c05c9b48defaa92efc636ee34a2970d701c02b6be0345a58cd8749e582ebd105c02f10a06440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af0000000000000000000000000000000018ce0fb077dfefd57f7943d432e12dc9bf92dfaa30f8341397ff8906b1abdf0c02b599edf85ba1e5bb6287aadc72d7a50000000000000000000000000000000019e5e9e3b0632ec10a26b7c1ec40248a9a8b230806c38aa24e47489a8aee5abb5450f6e5679e3f13c6ec7a79560689050000000000000000000000000000000006e257a74f45142817ea8044f403e98c99db8355d626c59e1d11c6859eb0dd1dc8af389f07562259c1f85411be6cbfe2000000000000000000000000000000000f463e345b004b1364894c6e8ab5d35bfbdf6b7544a576ed6b5c5398ca2074f67e2d80af1ff5b721fc126d3afadff1ef43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f00000000000000000000000000000000174231581338fc8c461c981d4949d18f5b753d27184ffb41568f11e178a271bfc69f8c73f2daed0fdbe5bdc7fdf8ef56000000000000000000000000000000001532474399d6a73501801e5f3fbfc6f13bdaff7a3ea7634568fe82745752ee15af23b16809be18788d295e044e29c05a000000000000000000000000000000000912eaef94ab1f3b3257b26c5e8bbe3f99eaceb8c7ae8da577ef98e24f3308abe6e6005ff674a2af01b4242f8ff87108000000000000000000000000000000001925cd635d0ce770f4925a3117721e96c316dd96708b096901ee04ce02e7b357428e4364cd488eeedf76352a26cc1d10a40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e0000000000000000000000000000000005c4a7f26ef0416f34750badcbbb3bce075606435ee7f69b3589e21e37491f0b4a7a98c825ec222848f5e29618828258000000000000000000000000000000000381c5f6511c9f06ea1a76ca84adab4a26a3cde13e0825b3d81899d6ad3191628894d0f57787f854aeb9e4c57fd15d32000000000000000000000000000000000bd706a5b5ef0d4ee1b679a0af90c217ddf9242b7c39523c39657962952dc14e5e07d02154e05693bad08bfb24a2b19a0000000000000000000000000000000009f28a84aa5bd39eeb09f13fc8770fa7e2e053b6f5d7e6021da77f48b9c3807ad917ac671de88b28dd343c2847c5e8eee03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000a8ff1b15ddcc3684b4d4ecfb53473497feb8a04660350ab84e5719fdb0618d61acbb555174b0900b32341154eb7bec9000000000000000000000000000000001464d21df798c0242ac6aaaf3c579eb66eb8cd53eb1e5ab2727298ca61ea8ca4c7cf815bf5c9f94c2b76bf659a4e2da50000000000000000000000000000000003a25752a4360c84e9353b7f1ce74d5106cbd637ec5ecb03dd0752660fe5c7622fe2d0475a4db98f785307c6961f14b000000000000000000000000000000000163601a86f02900d214ff8fbd041934189503438c557138b6ebaca8ce3c109af50ac28074223fc81d6476a3a99559ac565cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c100000000000000000000000000000000189597e6d618a20ecf9a87cc70b3e0eee69ffa4dba75056ebae93cfc3c2ebb368532b17d9f6c06f09e44d9f101397b2d00000000000000000000000000000000086ba610e490588e9385c8b6944c2bad1eb03058e927fb2f9740dbefb779bdf669a51af88b45985e8345b8cb168c13ec000000000000000000000000000000000db8b9cdd4a9bcfc9f7de144da0b33981e4dd53744cd260c4bf045d643a4ef5f25aa19edab7be0c7f8f5ab74a4b7f1820000000000000000000000000000000010198384a646807b16e2ed9186aed99ca3197b05964dd0348086f446d3ebb847907624f4e02f71a1e866d17a125e07e93ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000a293f07dc3f0da0da4bee671951175a4480a719d44cad3d627878ad2f17596f0dfbd6f43acc7a1f9857c5d1f453e5d5000000000000000000000000000000000be6382cc7a00d590f2aada3b4b75f01f8538caad2ade90227ec71e5661ae353e68789807a13f28b23b17dc0dafc19b70000000000000000000000000000000015a9ad5a6f1a511ffe1891ce260ce442996fe4d8515ca593e3e869cab9b18af57956b1daa43aec98a0281143b0c319fb000000000000000000000000000000001807a4ddb73a9aee58b54bab2b878bea8429cdc91384c8fa533a8c9d15c966350e892bdfce16d37a4048a763cbf25d71e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a30000000000000000000000000000000015aa6b865796f88ffe770bf25612ad27942213131c566a446dc149fcc70a018230f1cc8b20461ba2c55300fd27930bb0000000000000000000000000000000000c39c4f229b23c0f65ed720d655121eab50f695864959a2aa49771b848730494d14597eb85ba35743f64eda897f95917000000000000000000000000000000000ad44cafa754f06e45dfab801998c40e5a9f56e4add5c8add1d7ed9e05d12459f2efe3f3367cbcd161f524c714f7782b000000000000000000000000000000001437b1f1a1399ce2a860f7c6517b14a2db264b2602c1c57b8eb04e165205842b483497e98e6b6f8a62e25ab8b0e722f04c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d", + "Expected": "00000000000000000000000000000000077b81fa5997de07738e1b34d8e17ef4a9bde516577a4290253cc759ceaae745e10a457204b9ed0069942e0b487d106e0000000000000000000000000000000015e79be67a752a46dd0605e9d07d738c5732b2b605457ce056deaa1f7093b0bdc91b4c81c4c5462a51bc713a7fbb86c3000000000000000000000000000000000cfd2e6043263bda2b97798e1a7dcb24c63aa7197f2749f92524329e97f93dcb56429d82c1d63c067d7ceb38e6c65b5a00000000000000000000000000000000026f352d2f93e6407c59d58742dbd91ced464a3746dc1ad9059e6bb9c146dc1e74235bd54b1d90bb3399865cd3102f3a", + "Name": "matter_g2_multiexp_40", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001387fb972f997ed0cb97a5ccdaa759dcc3c2c7f4f15e5cc4fe74685e42cba75e778772d795847b45f274d32cd4960de600000000000000000000000000000000150b1ad31a3d434c1cbef877fde2e105d4a047dc34e3889d21544c2143e7b41b8e0024443a774bd1e09438293860a43f00000000000000000000000000000000065033cee91f5c4d429a074be3d2a8b001892455a11dc708ea73c0082bedb1cb8e8b567a6ea68a1296ad2b80e4b5b08f0000000000000000000000000000000001991ff6fb57e8cbf9d228f1a99697f785261ebce9d3c1f592389fc860b8d7a069896dd48debb8cbe0c43175cd2ecfff5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000000a0fa2d39d868737b9a0526296335256ab4894cc58ffd80bc6334e80d1314bdf017c8226b41ea135f6adefd07650ca1c0000000000000000000000000000000004334f7985211061dedc794ee8931ded12acd39d7e6a6ef44a749118d19ce8204d07935fe62fb2a8ea4f68f99d7c5f5d0000000000000000000000000000000018850a3fc8c851a06781511faaded1ce0752e7ef66da82c2464eccdf78c32fae306da3cfedaf76dad371cfbe012f2bee000000000000000000000000000000001296ca0b0e368429b122537b096fac77d6367988956a7f6cf70c7193b7033ce42fa0cccb8b84b9c78b16a68fd5f4c14c2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000019deb7de7fa5254fdf5ef34fa616651ec70548187fb0bfae9f512e0bfe1f662783f06a9a99e434ced84229deddab9d240000000000000000000000000000000009c199ef916e6f6fe0677ab07beeff221a5687fa8da3ed3ad99a950b7f27159f857d1b561006bfffab551d240b764fb300000000000000000000000000000000148a211fb58b38072cf7c417c70d3ef92e9cbe22b31b2b626198add01dbe1ccfec32d333abf42140b9316312ac48aaa2000000000000000000000000000000000b551b57045365d842133e46814d5d0084248904960f8d2fb28e9623660bcee658582928703f86261cd70e95cd20cf3a530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000185959a297a8f434cb9529a1f7bf9009fc1af3d09efb0a9dce1b9e7d30699da64e4b1d32cdb05b068621db092c1eb59c00000000000000000000000000000000106ef21e9031d108364e93ae4b5d21b0d6d78c2e86e0f8a7af27ed3d38dba0192954e8c716665333e5dcf21387d3f2b1000000000000000000000000000000000185d21efd7d613c409b6ddaa66eed70c235440974b2a9154f3711e3969061461f8824b4547c65e9db09ce875512ca2b0000000000000000000000000000000001aa46b22451afb12962bec5c6309feeb4acefdf3c98c1ea14275409b7111aacf7c92a8e024d01d4dcbfb1c91fc445a1d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec049913000000000000000000000000000000000557370d81bc3da4c50980106b8e65ca2edc757a475194cef201c9edc0f50363cbebcf2750acab0b67e1020daf5660e7000000000000000000000000000000000462f1c1379be9bfed97a1a83a00428de63eadb6360393ba162af3762a99d7eae8549d0cee218e469e4997ada7b35cc00000000000000000000000000000000008aa5ead309fc703f6de980dd43c294530cc2b38b94d5281e9cd9b0d09f82f747a7107b700f1437f3abe36c01bcfed1b0000000000000000000000000000000014110a19d574f26e11e2163a981c3388c04854c5693e9033a474f1020d5f980666d84c60370950734c46663e194bf0ec42a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d60000000000000000000000000000000015beff8cb3c79098bc73dc1ea4b240a4e0d094b3dbbd51592df6adc9c9847beb436ec83df6c55666e296fa843298446a000000000000000000000000000000000943aca2a6e57e9897ec764ee2911d9ff0a59d9e903c70a8494340cef2143895e79d3e6c03af2d6461ca199dfbd0ca0d000000000000000000000000000000000b812ba87c4989af07af44f3dfa87de119fea28ad598cb8e52247cf41bb8bd384c0d8913fc82e4cc2878065e797cc581000000000000000000000000000000000410ed148d1e354653f9d9d17c50026957fb03fec64964f2bee5eeea966b430e77f7b3538d9f4700a673fa07d0daac6b7a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad000000000000000000000000000000000de0a9bbd63c59767938b555c7f9284d0885ca23019818c213a7d4f1594b028965da871cc5818240d155c05c69e4e25400000000000000000000000000000000079dee5649cc67700e9338799a9810d352a5c68098d0676e42e00bac31f37513944dcf47408288cb7f1cba121506a10500000000000000000000000000000000101a650e84352aaf3817b400da0aff40907aae3d2fcf16739f8ee8d5bfc62c2a0dd518201701932728a41134ea3f6278000000000000000000000000000000000f1f9dcc0b55d0ed327f667cebc052c4b6116fde5e3076dd6e447c3214d4c8847885be9547f95f341c42e7c7fa7e2c71bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b00000000000000000000000000000000148f706b4c93e739324e5db40d42025535cd33a32bb3f211add618c0e2022068384a5612da67150746896a2813a664e80000000000000000000000000000000007204ebcef495ca8232078fbf1539a4b46e89506a09dc008da457dee2792acafb6baac4f6cef2de15cbeb48bfd12bfd6000000000000000000000000000000000bf8900e48a4a56b653b1e02c3b9a7d81c2045dbf6297f1ac2acd69d1bf9e06480ea917e3a616243c3a30235abbc426f0000000000000000000000000000000005ebe0ddf4cd1aee76d0b3d03eab754664c8b36fb20ab1060900909e0e0a4abdb45bf74a0b1d40fece9bf73360f580bcbef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d0000000000000000000000000000000007824d1c48bb2cc0f406e356f6e52b66392f6203f49dca7ce03ae6302ce3e8055d071cd812f97481acc654b318d6cae2000000000000000000000000000000000ae89f9eb1abe452efb7ca48f8f939d835f9a79e05211ed9f4abee06b93e34b17d920ddbca3d8bf18b96c3705c1a064500000000000000000000000000000000119ac787a7f3e9b7ee34070aac1a769430eaa8cc838f1752b573ac7f3c02a9f490de9600c856a55448598b149f5392e300000000000000000000000000000000193a3655a80e6e0b1278730600fd4f645d54947d193484131176b890ac197702333ea847317568230ad8af1280864096392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000012f66629836f0f57bdfd9bdeb2c9b7d6d5dc55c586e15d76aaa04aef06722bc8ca156fd1295b3063d738a85b3e8746d900000000000000000000000000000000097825c5db7289b1b9e640d19ecaaa81ee59e5b9884713f6d312604d8ac367634a264c316d73a9cf63358c8fb15f8c5700000000000000000000000000000000181133d027b97d8e2bef308a93b7ea2a35824dc7d01a3ed2f404fbe12ba3b3e51d94ec86cadf3da7dc9ecbaa23b411cc000000000000000000000000000000000a28a609d0bb015e375e74c087ce426dd3c20fbd8b374d3817c626faa81469cfd11a2a4e418a44f4d7ca621d0564bc4920a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000000feca6f7e3cb286090fa3df9c5ebd10c06192fe14af58d46b827acf48fbd462f3f76d9d20670803946028437410ea52800000000000000000000000000000000183dc7085483bd05c27691c25588e33296fb610bddaac253af5b2262db38091650c1c3185d71a69d1a63770f95f381d7000000000000000000000000000000000189f9b9ea528bc2377ca3354fccf440fee059f5732dfdac320fb58541e74e444dbdcdc008c7b47681c05502f0b302f5000000000000000000000000000000000906162085e0e299a07e41b9d62668d4810b97d4be317bf376da537de7adb06de011f5f40af834593761b774771a80e4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c47258800000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb0c8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab0000000000000000000000000000000001d04065373ce5d1ce47e00476f07708bb028040edb9ae7e8e00e2c6c460e1ab8b730ff510a25a3c8114c1753b7bf1ca00000000000000000000000000000000001c87217f150694a84a4e5aba8d188ebf7224e76b078dcaba4a91de6b4ab317966ce1a9267a5a27ce556c3386b086620000000000000000000000000000000003c8422590826e0999e7ae3ecba84edaed20fd7f1eba02b9daf1c46c2aec74d5fe63319047d37f5115f243ae0ddd4ffb00000000000000000000000000000000136ae093c3bd55ddaffc2494f3ba8176947cdf2f1ae408e7e786b23b6a65ba8c4131c83cd890386ba531b8637b3b042c8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef2000000000000000000000000000000000d09b92a559b8efe5224184fb4f43779d0b8c8f23587f4f74e2fc6fb1f94e8d2e0d591eb0702cf51a9eb402e79b46a0a0000000000000000000000000000000014ec2e4702f1ee1074cd1ad29791cf4903357e62570d16ac80c5e8ff73b255ee03a5ba070091cb2f984b2139de06a97d000000000000000000000000000000000d22fceaa48193756ce7331952a2d9a8057b67bede729e07cf8422bfc79f9ed2aeb99a9227af256deee9f8a6f227faba0000000000000000000000000000000015d9322c3a5a7ca404259c4cc7cb93dc3d46dd8dd9475756d2ce6fea527642f9230c7e94a804ecb0b4adec7963fa9cdcd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d000000000000000000000000000000000e16fee11affc6714c7fc8fc5e7cce44d8afe645861dd2f0b8e58aa93d4f0de9b7e73020a1537bfbb0e2c8327c4aae03000000000000000000000000000000000b7745a4aaa8ab4593daa61e375d55f9043fbc7385ff229889fca514562168a4e769c5eeef4d564b41cff28b4efdb7bf0000000000000000000000000000000017f6c5b1fb00746b50ee4c7c743ae57fae2742617e5565241d012a0ef6067d9ce59be749a99886ce9836b648525d2e92000000000000000000000000000000000a3be81720e80f6aa0570c89613c78efe95d87ccb374e7f77065800590bc71d23ae097516ae1e97b498cd233221cf717b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000008080a7d91caaf2470f9632575b43990a9523219d75994f1944979ed5b650be1e3c93eceeafb0875f66a40651f4c6dfc0000000000000000000000000000000007a19c4a6340e39230a33b12fe63e47bb0d1378420ec9e439f216699e512e4d70571a1670eaa6b60a5c899ac63360a250000000000000000000000000000000016898d22b2c123003480e3a01965a72de94cdfa39b20898c49e451dcf6a4727a1ebd629172aa1a1aa6897916cea192b4000000000000000000000000000000001217a373c78de9d3005690023b9e56bbed3073f13ca2408a27a3480578d8013fb9d3ee5cda95c3cdd091a5cc68d928da7b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f", + "Expected": "0000000000000000000000000000000005829c932c80baa420602bf841ad9bb24fa25c61f33f5d88693207b81271c94eef54bb524aa830fdad8caf8c082bd4990000000000000000000000000000000000b8d184316c2471ec6875641ea83de4f9b7227041922415b38b07a0704d01f2585ec2701bb4ae0bf6a0c0522efc0c630000000000000000000000000000000001dd81e075620914254b38ca5a7287eb56f2f31f6f8fe02fa51488d45c7f4609bcf49972d0ae5ded76eed5a4c096939d0000000000000000000000000000000008067feba36999b58342ac54e48b0fe28245f8ac2498b60093082822d19854df5c3168dcd55ccb6b2cb397b77e712333", + "Name": "matter_g2_multiexp_41", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016ed84856b9f41be9bc6c025a9b79e2968e2ee6bbc27608093256c541096e2c9eda1159e6dcdaefe783aa59d52f28ee90000000000000000000000000000000014aabafdfe8c7369f93d5472a9c6c4d426e4b02c943488be993d04ed24aef5477f6d455f82b4af78381b8bd16f42b56f000000000000000000000000000000000af34789c6c923103633e5b1b9fb447b671ab05265c16488ca7224e49db21973487a5d3de4de40b9d8a97ac9b1966619000000000000000000000000000000001123a6601c5351a586f27f8264d4227f5e1df868a03e0c3df5c148cb523cdd178f96fbe52464fdab210564dfc22b29536f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d7410000000000000000000000000000000011b82d78cd9b53b8e7e5c14a7371f34f08546896bd59d1e7d8be15d21742180aacdd01b0d08da2cb24873ce75e166bd500000000000000000000000000000000161ae0d724085a6e801edf73443cca87995c2d6b37e962db5719f4c480cb830e379fa778fd2f29e75173e1c31daccaee000000000000000000000000000000000a2c2b89d00b7d19f2b0530889905c30cecbd4ed0b56ca82208d666e7576c32a6e90cf867ad87f19e4fd367a10c449a2000000000000000000000000000000000b65c0226743b573dad7ff25bf1885e3dec686cfd5da2862ab300fde4fc8fa9b587d0f2d11ebe1f6a6770bcaf2588f8f5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000012d4f299998aa897db9e3194244fdd1dfb95225e3271383b5cc296bbc51c4e1af52e849d8244f82421cd198158918d8900000000000000000000000000000000110638a2f7cdb7104de8fffe29be32610063bc656e13168921501e1614f282bdc9fccff4eb3c479a42b240a2c8014864000000000000000000000000000000000b0adbcbaedbedd376efd20a417bcce562b87b7449cac1e90d44eb05930e6f558b35ef755457305da012a231b5675bc2000000000000000000000000000000000db6fa926c7e02f633730569732fd9239bbacf2042599e79a4bee76619872901c6f4ec4d4fbf3f84143a0d17b167130ebbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce500000000000000000000000000000000166578f3087772545c0f47fe0b3efe32874d26463e4f262be65a3bb6b0fad7d0f779808f69362f3fe63c72f24ed03d70000000000000000000000000000000000a8e61e8193228fa1825cf14e94f68a5eecece9afb48b44871c5ad62510ee1fc4e9c60d5f2529b8685e6aa13ec91979b0000000000000000000000000000000008d25d81bc4bc92508c8cade33c305c11d71a06bd46f184b05dc406f0939f0e0967b02f15b4f7f6984c9fba0644ca8e800000000000000000000000000000000113660a7d2152346500a1578641aad4dac2919ce63d01d8ffa6dad72f524c888fc2e9d2876859859e47d8e884f170f86607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000160529ff217934c85cbaa8b347151539e252dbb502c015e8e45c128df2b8a737866737d5cf0eca6f76e4a16790cd02a200000000000000000000000000000000127f7b0e4f9351836db9c204386a199293955471dbcd7b4ee9186f0434b46dcacd1edc02fb46b4c377c4e62cec10cd6700000000000000000000000000000000094abac17b11600d7447f7ad0f21d98c14e439c4a4a6572b00c90e14d9fc54e85045d0576f74b054d384179afc0a70c80000000000000000000000000000000017165c32410a498add8e1dd55ae43f94be234ba3859fc6b4816d7436746add313f42b1fb49e0cb6c4b7341f0acd09db841c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000060e503ee1c5d3eae4bc0eb30fd86303a5c48c10cc7b4736d17b8774c78a8c97ee05b40d366b2cc9bc7781b1e4a192f200000000000000000000000000000000034e7012414edfc6a8f7b2c6049236b6fb77eb94b05d55b218851fc1e553514e6ad388fac08a24c33bea63ddabdfd8720000000000000000000000000000000004c832477a90683d417a00a698b69c643d6dbf82f5afbb83eb3946f8098d80de6f2d457c0a06d0051315f06e93b5e13b00000000000000000000000000000000048c3339996948974f2bac14d8a6b8430897644ec8e9cff9eb369557003aa2827a4f3fc3444c4df73663ebc9325ff317c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c00000000000000000000000000000000082466944ee7c62788b6fa77816094ea623d03c7aa2af249cfbfbf78eed26a76cff8c23c2295aac7ee1ef8dc84630003000000000000000000000000000000000a8f88adecc3f50d8eb329492f2c031e722f36627cb3b21415781156ef44954c5b8529ceed5978a37ae1248909d38b5d000000000000000000000000000000000e08f628aa014152b50a85bb6eb947d53c596d82c0d03594ed3b64c486b8630c880adf43fb1575b02e4eb8174a04034c000000000000000000000000000000000776844f28958d3e12a5c163dbd039e50df44b1c6215429381790175a609a339621475a5b9a06c3276c9177d2dd2b576c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e9590000000000000000000000000000000004477f153c0510d8e50bfdc2db69182c05d5ae9b94bb1880de239733e380e03d50001378432312b24b5bf0952c38396c0000000000000000000000000000000016663990dbe529a5658f2b3044bbd390ad430adaeffbd5306f758d86bd5422391bfa1d21e88c63300faad55e6a2d1d3200000000000000000000000000000000188f701658558033ce2c41101a611f74ad6d3cd075c195476bd2cd59a1a9dcfe937020737250fe418b4de435f8b3a0380000000000000000000000000000000013f8d3625309767841603329f56686a99e196d697802cfcf31f8b48f9c76f77a321276a0158a22b94e91d6907f6ff451e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd2000000000000000000000000000000000f184d22f3c0431b031ee0ee7ae9598ffb511a2a56f5c9f15c9a4b0c53af2a10d22a311805786e303e234239326dd74b000000000000000000000000000000001062725b8c576e79e314f6a56ef9c41f05a65d7d0d57d8414e2ae9cb1a520b16ede7e418d3a9413c9c1660dd7508d5860000000000000000000000000000000012ef02fbe96f9a191804b6c4a0b65b6024e3e2b1f8cff986f5a950cde9a32ad50d4f7a72804b2d18b93250a63a7ae97800000000000000000000000000000000000b3b0333d61fc46653a7172f5a813d13ff5a48056f9689c78c4b18b8aa3afaeb7cec305d98dd600786351338a2185a651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f2690000000000000000000000000000000010c91e1dda48dc528f618f01abbe01db1a7b6dcb0d47b83c7b7db3331f7156f7b2d0f081458241467b0078935a7b4a4c0000000000000000000000000000000006f87f782979d2adc02e65b56a4906e50430cb4e0913636e9aa0364535c9d7ecd3b9433358e00caa8e90e84b7705bdfe0000000000000000000000000000000004635089c7706cfdb5a22ef643d1a9a5021847646ef01ea559d1b655299b65cd76a73b04149adbac612e7aa756cf30060000000000000000000000000000000002d83d82bc9fd66c558e00547a8c25633899584c9b855195c00eb3c8742d22c601982f244a03f8e0c5c21caee24405481a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca0000000000000000000000000000000000848f10eeba8ef9c7fd0e679767f6b6a2392922092916da8f13573661f84ec97c65717e55c65526cedd59dc1e096f0840000000000000000000000000000000013781974518487de12661bedfca5fe72205c51cab461b5757ff14f319d081e7845cf8e099892ea85470039713e8e48cd0000000000000000000000000000000004cc1a27d1aa88484fed40ceef72e6bd201e5ee276b5ec27624286dee112ece767b37c6f1f7846d71cc0f4042f04dc170000000000000000000000000000000004f7335d6a1463976d9fd86e2baa45d08ec65059b14449ebe4aae99971c5666cdc6e40cf0510ae99dbce97ae8b4598067db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf70000000000000000000000000000000003c1b1e0848bbe37e62f1ebacef1a574400d5048f1e09d935af2052da29140dc4074175e4d6ceb7c2c071331b2f3d1d3000000000000000000000000000000000e1c84d6b20553ddc5ab09049ec488ea2839c5818e31455a7b231cd0455e2945aefcbdc6c1979821a80bb4f77d46e91e00000000000000000000000000000000199ebb31e8800395a9c2e103c9340444c97004186929b52de33cb8d9396e7ab8d5af3fe6035d4463701ea41e341f577300000000000000000000000000000000081b3882bfdf83e67d2dc42b211069a4e93c0f173263f9f20579128391e7f2de70335df949b9c0e9b834b6e574f2f8cc14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e60000000000000000000000000000000018aa45c6b3898a5fa618f87f9a08a7234c1b94fbe38e2297a1f9c7a2e9de0ed83023deebd56560b1928c012c14dd7a860000000000000000000000000000000009ab80da6c519aee8aa1fa68c35bd0fac78b55f88d861e8fcd445f629054325d63cc4241f61e5596dad0d54c94511e4c00000000000000000000000000000000105f8253f37f5538a2c25587fd33ea61fdc744a7cdf4ff23a55e2c66a39040d4de5eeacb7e11c0d2a483d59e7c3186aa000000000000000000000000000000000f6b10cd6522a1e34c87c702f58a07858cb753d67da9625155bd433020775351a9ec4ff879f91a43f63be1c969afe675ed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000001618dd5de43a6bcde91a6a03fcd88fe59d1c8c51d3d85cd44a1920dabd2608a0b17a987b76eb8f5b20c7f1dc0abb383e00000000000000000000000000000000198034b7ab8fb8ff267a52a9423da95bc587eef8684f18639df5db44e50bae7fdea5c5e5ef37ff14937f86cc948a34e500000000000000000000000000000000106d1f017da463176bdf55e3ada78ce70da4486be42dd0095e3a8a0f6e59ed503324565b717b45ee38d90dd3ad13c10600000000000000000000000000000000112d425765fa2fc28486b95e49db63346188fc5a6bd0b7dffa4430dc82703eb44d98d726edfa4a275aa5db5028d01ef530fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000001326581ac1a1a960db1ff2e8b89b1debaae46d1e2d0aa6ffc6c7398f207abb699ac59186ae7222b5cae3abe64cb61c93000000000000000000000000000000000218753594c63ebe5fe503aab4dbe1e944b24138948542c7c43d92ccfeba5854b7bf1bbcf8078d85fb0b8701b8b092fa000000000000000000000000000000000c3ce8c17f75e78a8c9980e9fe125290d377a32ac46411876ef011e169e86e1458ac5e71cb4a446f6c640cceb8d5617300000000000000000000000000000000176966eac1e20586ad2a03b4a1598b4db1d7c66be70b1b22833e4afe0e0b3783572f791ddcd4eb70a88f4acc28b6fc7a980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000099ac8430fa411e74082cf3282f9a456d3826a7df4f91ecf621e645a1abc057e1bcfaf9ee73f149bc447cf4230f2f6c90000000000000000000000000000000004e93d7fedc9e2d7423c9e111b4674a2bd83de28dcbbcc54ce4b324c96318a11603fc9ea385f1c02364ab1f6b5458481000000000000000000000000000000000bbb29d70fba5b12fadb02a24bfe3f6a5362c71fe5f964dcd0e01442781d0462a873501029192858027d612a8572e9d30000000000000000000000000000000010daa9960005562ca2d18eaf4b4bf081f194fa824cc77515c81b2c836627f21b732448f367e2cc1830ad0fa4ceb928e1619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1", + "Expected": "0000000000000000000000000000000002a61fead6801f41f2f27603cf34cfb4b917f2f85cba1f9c684995227653c9dde559e1e8497234fba9b2e4c119cbd9ec000000000000000000000000000000000085f73b8e835a10bcb9312931eb08d916d2b93a1da843fa2f4530cdb26e93b5dc95a555dbe8e50ca465b162661ce1d3000000000000000000000000000000001442fff9019b5476c217ff725ad95c92c17b42471ed7bcc121e8a0506755ec279d4e56d770a322d56f56bc6a6b0a41160000000000000000000000000000000017e7710c4639d51c4a79c5a2791101b52742df202b1827192872f168bd21020bd068160a587fc501782c1301c231a0d3", + "Name": "matter_g2_multiexp_42", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001213b5d5704c454845824994769c8b300676e75bafdeb95202001161aede276ab7967ea7362d38e97ca1484cf9c342fd0000000000000000000000000000000008c7c1fa04bebe5a1fb8678370563db63e7a10b30747c2ddeb4aabd4fc0ec93220d578b8110c6bfe8a3a6ea2820f0db8000000000000000000000000000000000c4061a295120a00de52300ee625ac46566464e6702489467316e8c182ca2168052c50b5962ff47285866c17d213fc8400000000000000000000000000000000086c153169a9ed1aba10a6cbebff4911b37907d6398c441ad47da17988d512d822ee36f5217355b93c9d6dd8dcbc8e0b119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc0000000000000000000000000000000017929edde8f9940826ed739bc9f59099ce76e85950698ab0140784647023f96afa064aa4a49b9728f496515a0a807e5900000000000000000000000000000000198d98f430384c1e7fa9e2403d9c3d2f81873fb7b204378cec95b97e674e10a1a43af97db0488209904469989ce80a0a000000000000000000000000000000000afc9b5138999bcef35613e38bff4f81cf532e00346f5205405470b2424622826c746ddf0369c7bdf77467dcea5cff290000000000000000000000000000000019ccc05724b3e9966bf918f01312c80e8422b697be89365b6ca00eb31b0bd08fae942e90a75bf9da1b3d264e416060f1f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001568e52c2760d895874527d1ac8597730578176bdcfc67aaf69ccda253f6616230811dac59bc27cc1e57b94b5743cb3f000000000000000000000000000000000a4ddeb8b56f105ed5f47a538052f3d38a23c0ceaa2dea241554e6508f82f47d32415ffeeafe5ae5664c936b78e07648000000000000000000000000000000000b3b335a390aa0090bfd6467d6cd02eea1ced347cdce3c9ed85dd46e38e9f2ae9642392c2875a27618ba8f2c555d5b190000000000000000000000000000000012baa4b29d116eec749353b7658af70d4d216189133db707e56068c8483af43ba86583862e6b39df13b88058536861b9e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001888b83ca28c244a6178377b4ee6844dc916e28c3f56312ecc0e29d08e6254dcda39a36ccdc317d1908303db3c028dd000000000000000000000000000000000f4b73d9316fee42d60f8de402a7d07765508b84d8f2c1be1f3f9e802ed7b0c6c5fece3db95d5287225026e73de98ea4000000000000000000000000000000000f1b48122191e1bc421881de831293a80566b9a7f2c9836f7718afb69592d59d2a714cfddf88945b94fac7a50b743eee000000000000000000000000000000000f1c6b052dbd03795433d7ad122473f109484d50245021c8727d252145e7db7dadc015265d1547f9c748409d74f5aa33f7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000011e8613c3a771a177b4b85f0c6f97a53fd7900cc23566aecbf115058d2863189c21be36dd5dd736f6d0ffbe88182b400000000000000000000000000000000017b2c4e8d8aad0a12fd7130789188bb63a08f2b243c8f7700599dd33d7e176f70f2b1818e56540ab3fa507878d96a46e000000000000000000000000000000000e2b5ad5ed3578dfdffa414a4a2142846b1232cd2de468725283e3f92b536d8ede74bacc236993f6f68a16fc6a7828d3000000000000000000000000000000000fdbf06ae4cfedc462f5913bba9bba2b5c86ecd0e298bf27a21317fe74af6ab15014c62cbfb617356548cf808599caf4b1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef000000000000000000000000000000000b48aebc6525620b99cd83979658a35afa233d17849bd0dcabffcf3b550f875a386b6c0b4ddacf18a23843629072c0150000000000000000000000000000000010432e5abf862d3be10ac5677b9f296ccdcedf1480e45de631b6bfec42f20edf62034f7205f659f11fe5a6aa9d882c7a00000000000000000000000000000000011702a3590e7aedd6948bb94bcc874e0b8d77a18126ed4ba3753dc98953ff941495486c14c6d801c71fca3564ded9910000000000000000000000000000000009faa427c0a7da26c92b451c61f5b5e8804fe032a4cfa014397e430882cbfcff81bb22f9c15a8747ef455773c1ef65b0a41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000061a1ee841251bad461f89c52196bebb1cb4463298e88abd62cccd21bbd325ddb33d1306ffedb2734be76c18d80c8dfe000000000000000000000000000000000d05a5ce6372ce34b0bf4b19d8e05aab74abc1cedcc35a2d1d4db38813d1e5c1375d63ca0e8bbf29c510a4319d2aec27000000000000000000000000000000000dfc57aa8de28745b8d28db3769ab5ea26b5115d3e59e51ff19af8ba37efacdccf763ce682cfdc77685705781c3924870000000000000000000000000000000018c17d87411c4f8e0ca51b3eb4c3765d3846e0d1b75574f8e511b2f3e8c5ff53bf7618959ce18dfd9e4c6285e88f094f63cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de000000000000000000000000000000000a0642094b89dc9c6c7c11c1e57ed542982bd246112884969d424a3e091ec4fd73dd40a5ac116f6c68216fd1e733cdc7000000000000000000000000000000000788c7a63eecd1cbc26ee6b14b09d0a3b7a17a848fc0551d50cb7497bc98287da2d9b898260eb678a8a0f446eef5c6670000000000000000000000000000000017a1298f90022ddff3fbbcca180e3f4da8760218dba595a067287a2473a6e10b93dcd54154cb64b6c078b083b42cd09000000000000000000000000000000000116e999b808dcaea0566c0fbef1807e160612dce91756b2cbcf4883b04a90320a0759bba21b41e6f4d8449b52e52f9a96a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f00000000000000000000000000000000064c95bc9c0e2be48849a349f16713791c37310f71b5d0613cf0706febeea3a56a0f0f1ac6b504524eba801e8b759f2900000000000000000000000000000000007088d2f41fc7e1147b92a2ee7062b9bec194d3a47eb9985ac1ceeef57e1a006571e7247a13dc95afcf9905be57e2a7000000000000000000000000000000000e6a0770f4315acd9e410fe58395ab8b20a08240a6948b762dfbbad3414bfca0ced4ec9da982bc9b8798b60dde78a96c000000000000000000000000000000000a70b53a6d71c83971167afe329ffacdd417bd7b228766851c3b43701a439f253a8659312db7e83a398142fe19332b527d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec000000000000000000000000000000000002486eaf9b743d3aa6a1f3e1174c5f213bbf3e3cc0558d63ce40e3c03e1c2f6e8508248bb649aae1bc92f3eb8118a2000000000000000000000000000000000042b03959b40eb0641d39117f7af50dc7ff048697a57b80723aaca164e2dbc647ffe78fea0a6a4c07671f7db6d5b2dfa000000000000000000000000000000000e141eab29f52b9bd0ee44861f154ec1bd30abd715935a7958a19007e789a41cdb0f4b9cf7b3fac0b0d4d77637b510d00000000000000000000000000000000002cc2eaf89cb7a04d425d878a30b5e2e9858ae0b2a2ab28fb28a6db0c7283ad861bb6a92067e969e5721b43466e857db8bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f63b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000007ecfb753be501d9f9b7ae7ceaabaa4fcb7b690ee04fa1a711a15dcf67e4422adef64a0f8118f93e67f24a2d1a2bcb36000000000000000000000000000000000a459e403d85972f7132641c05bb842416a7135009ff46b617bf0918e65cbbf33f76b98c10d901936e589bdf5de31ea4000000000000000000000000000000000bc6ec31a3ff92b4fae07cb73ad7bfa8423044048337b0ab9add09bf10fdf190a5f7996d157483d29fb29a681ed585520000000000000000000000000000000004c622e2bc606fefc8bd83c4a32f7353123205a6d3716b581c2c71360e5200ab069f60c256dfcb04b466c53cd61fc94470de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f5315000000000000000000000000000000000125688e44f593c5f585765f30e9fee5e4f15247cf33ac78ed1744453385f49ac61128e23b1569ea33d74b207a5e72e930000000000000000000000000000000009d77360ea37298fe971569230159967012c4991255fb5337ca6d58cecc3cd44a024a9a044ac98a894cc97dea161844e00000000000000000000000000000000056b2dd9569f0698c732367cfb217af90a3d6dc15e2555ce0aa845616e4067a7fefb304f6525b539555a0a685f0ec5f20000000000000000000000000000000009acb138abacac351e03f7589d4bf29cbd331e93bf538578ca9466b759ea070931c786d35f74fad42261e2df431fd00316732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000013515b0022ea946a8e679b9c0eac6cd67dbc4efc820f0b3d8984f12b7d154c0632a8d7207747284d49c498c79b6bb5c60000000000000000000000000000000004d6765ec6aa8744225c1e652ccddccc91fff7fa8182931c8648b3d8bd33b2177a9af03b2906da02bf117bea59aed3040000000000000000000000000000000006f1d858c4b223552f0aee466cff35d14b3ac6da35b8f482417e8f597514b065be315aec6662ea5c7784d3a9e2184090000000000000000000000000000000000345eaf0d72b9c11fe72261a2fddea318a8dab92a67ccb9438c11e61fd298a333cc42084d4ca127e09792e346cfe0f004a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc2400000000000000000000000000000000029ad10ed6d6d5bb591771cbd597a3a0b841c2347c89027126bfd1efee2ac403933beb99d08721232ab9b7354fcf9aa800000000000000000000000000000000198400d4e026c2463a07ba5a3974c869ed8ceb1f029bfc7f41b23dd7076cf4a83b17c27ad6506c852cd2cf7c4987f93100000000000000000000000000000000152bbf74cefb77fae8e825443e4ce09b4e223242187f563a236695294d0a5f540f0b29d6f93a54cf0a77900e936e61e000000000000000000000000000000000079f4759eaf044a80417345a1b4029f8d4cfc7e00fc625e815cb7daba2243a97d21e42b42ec968dc8647158fbe467088fa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b89000000000000000000000000000000000cf3148d0c30774104a097562cc83456d5d18643d5f7ad58aedd9327bf8e9450feee50ee893442b1cde87acb02b62045000000000000000000000000000000000011d4037dcc15d0c50337d71816a2b77428b8ddd530bc3b3c8550606229f88286ae94ba03578cbb5bbaf118916dddc90000000000000000000000000000000016160c8ec4e2fb780748aac279bc248b2e2f1092262f86d368d2f06a78ebcd27e929930c8f2be124e9d92dff5c6c6f42000000000000000000000000000000001980375281735390f48ddac9d00d4c6ee7312ed0797333a26a1684e09c9575e57bcecfc4a31b8d9597a8ecc703835e22c4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515", + "Expected": "0000000000000000000000000000000019ff32d2901b7732df1a924eb3c099a9d36bf36cb32ab322f46a72d99d81c7942d0f2193a4aeb55cf079a2cc1707c7aa000000000000000000000000000000000193561d0433e1031fc51829504ca70e92e19bead2e0bad655aaffb6b41f5f75d18f04a162db7714f3f23da891ea91af000000000000000000000000000000000d010c36acbfb38d9dc2df6e6e21bd75deba5708fb1012eab23d06d78b1244d4daae38aa4f803d12441d91adfbaece7a000000000000000000000000000000001459ebfe65c3b2c9b2684042bd71201869db1a0248c740a54fbdafcf18fcdbcc7b677af43abe803362b462369237690c", + "Name": "matter_g2_multiexp_43", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005b9860b565fc64146020647d1902e2a2d2fb2002b54bf5e21b6601124edf14d6e8836f938843fbd8c02ed8530953dac00000000000000000000000000000000104938181f16f16318d223febec3be3877bc57067fc23729d1f5552099125558cb21ee0eddc32ae0b0cc3555219eedba000000000000000000000000000000000211f809b624c4992a43e78a978ea32accf9e61fccef6bcd05155e52adbf4853340dfacebec9fa87e5417c045da25f9a0000000000000000000000000000000019bfe94a18da9ab4ea744389c17870ac96218d02178bf2ad502f166a3a1da8c14e3fc52038021503cd24042cde8f306d0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e150000000000000000000000000000000004fbb396eac2a1de9953febed9fb6e158a3b5a366f783d2105b562e8143031d7a1ef039e3fdcdb675b3d3aa4f4dcbe4f00000000000000000000000000000000155e23b5b70f1ce34fc229ad5c8bdfde7fb5dc0eca19596c658c1f8c38716a0a7b5ff59ff19a7a67e12760fa90eeafcd0000000000000000000000000000000002cc82cf87e7ac05be236104c1e668b5573674d9bd741f2d91d05c8a11af1f72aaa1dc20c73953fea38e6e069d2a43de000000000000000000000000000000000a7d1dcab00db0e7c0a239511d630526fb120defcd9453fbb57ee328f974a98721274144e48d22558edf25595b8ff4cc6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a270000000000000000000000000000000010c9293b3c58d646a95c620a0e0a7a0a55cf43b4abaa0de1d5570fabca8d97c91afd67bd45aa234273715457da5a2894000000000000000000000000000000001454f8682f3736847cdb3f784a098f7c9e488629efc3820d49b36a2e928bbf736dcb3e1b30187c2c0090fff290dbf97f000000000000000000000000000000000a0fe3c635a81f20258db4f1031589afc8c7fd07f2fe1e5cfc8f3c40d08a958a3dbec537c51be2de99b849e006870b6c000000000000000000000000000000000728876e3fdc42273e8d71953de61dd5c03e7c31ab6ec56fc03cdf55c8f0aa4b4e5c8ed88c23c28568be0d864df026af4e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000c9ba542189ec1828c397ace9639cf2ebbd1613356d8fb26d3c40dd00af1f43f5bbb25032561aeebba7b874bf39cb0d500000000000000000000000000000000175aa6e94a9e42cf809f48f51c48d60e74d61388dd217b55f3f63612c4565357581e5c39751a65afc3b7488caf5151720000000000000000000000000000000014c880d35d1d31793145803182584a8da003b0ee3c29c978b64bbfe4e1da82910a4539587ba350d393e1bf3169c5e4c70000000000000000000000000000000002a063b3fcd77180de632deca1ba89ec4ceeefefe9883ed9e7e06301a268bdf377c3a6e30859e5a39419e449dd27ebf5dc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed700000000000000000000000000000000069edfb8a83760e09726f6d1c117d4bb3e499084b65e1e830ab30daf1625c37f851ac122f9f5c795912b5b6f7907ffe1000000000000000000000000000000000eb6e9b55869f65ecdf3ac46d0ee596d07c573f88724bcd802b4429392b9a56730a217a03286deb5103f70aba7a9bc46000000000000000000000000000000000e2803e1a646bd70c51806b676591b328cb20359aadc8e79d59e7c31e1ce2f1473b0b19f7a34f23aae09678b11b37432000000000000000000000000000000000b3c9fb5a39a6c40343259e12ff4fe5058f25619d145922e1d80c3f5d105a7495dd9a4da329a2e78afc31a87b2c5d5e2f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba740000000000000000000000000000000019da372143e30307a71c7b96ae0703301ed723814a35e270ef6a6b0c57144f494df1d3fa0ac369f59f3daf534070c9120000000000000000000000000000000006521d89d810c7542108de26bbc888482a3bcec8cb9b542db42d5d4af30d6c339a5b4e959da4f98dd6ef8075554f4017000000000000000000000000000000001387d9c684a0fbf615e7023c0f3ff47f4d2c5a9f748f0261656a09b23066c745420df0eb180c9716d6d0743aae7689a10000000000000000000000000000000014271b9d0b21cc69072333a6c03493321b9d9028149d24964a3773bcbe5045875c457aee11ab0682c2bdd44f098f363d80eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd51070000000000000000000000000000000001dd881f3d2063adcc5638b4b3813a30e75fd308de3c9f42e5382fdbf097d5796ee9e03cb44752515b2459f131f58bb90000000000000000000000000000000010f491f4594dab938115343edb47b0087d1cd1bc12ef908e150ecbdb3a54d8dd51ab24a0e10c585f235ed99fbd3172270000000000000000000000000000000019d1665d452ce7fb6bb6da9782a55dcf12a1d9abdfd50435b8f2a1bc5b323c004fad35ff7e9aedfd414a9b68fb1eb1860000000000000000000000000000000013828087beeeb85e43e8540fbdf97af189878f5ddc1eb35c95aa06a26923330f3b8a2b43f835186865d6f5f6afdb2b9b067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b100000000000000000000000000000000014964f3576b97c00a8c5f4372e2501944a1e4374a3c30e11376ea62e09d52d40d428887833bc2f06279b859c00c98a60000000000000000000000000000000019ed533a3bf469ce5b3e4e9035af177efe9e4f8b0a0e5dc9721dde49a7fc66fa31c8b1c8d5bcda1bf75a532bd2be356d00000000000000000000000000000000064ec4ed48d63ab62373adb7898bc904d246bf2b3790c3cd850524e50ec38e7fb4a364344a6a1dbd26f2ad2d0fccaa1600000000000000000000000000000000134aa3c6b72d39bedd8f9c619d206a295cbc05c611147d38aa7304e995089ce34ab1fa13c2d6c6807a88797dea20214b34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a000000000000000000000000000000000a2a4c8b457d0d2554a2d439fd3b74b18843386aaa00d1b89a1c2d8ff7192cdd1d3a888994376bb7ddda4d16bfcaae3200000000000000000000000000000000155a7dc763caf6f0fe1ba9537c0f75d3e455c2d1c749dbb4aa7242b40a9740fe9e8e88af6017e8f743a9e4c5dc6ebc23000000000000000000000000000000000a693da3aee178e2f0489af77f671c734423032f30c0b7b48debd3b71e65dab7db12ab1e0e72d3ef686d6c1922aebbf700000000000000000000000000000000109c3476016884386d6206c94073c628375a02c8fcd3041e06b8b413508188a1d26ec5ddf84a77d059e9a039dc5470d08d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e19000000000000000000000000000000000a07f457f5dee69e9ee746dd67f982914a2182b5cb2609d273d4122d57a32c195270c956361d78eb65449cf5e13907ba0000000000000000000000000000000011f149ce84c2a11ff818be3ff0f86c1b38a9555e169a8cf791c79828207b7aa89c84e8012a0c5d8cce4e89d758b90e22000000000000000000000000000000000d636e5b027e41809d7ec8bbbfd4bf641a56599a63a7678569404ec8d45c3b88c1d2969e6101528d4edf1ee9d8e793320000000000000000000000000000000011878eefa5ee49be83ea1f7a9cdcd4997ccc59a9669778b3f006429e1a22d3b2a051924f371a228856523e3a09bab59b29ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c6986930000000000000000000000000000000019e9a1950f663b258474b24c334bd256d3aedcd26dc971a745857bf1fe007da0aa00777db5c3e5d21294e99862bf8ea1000000000000000000000000000000000329a12fa0add36f259e401442bbe6e5f9139e4a46d5d091a2110d2561b5629211a1c1996f20d19327d1782340e7ce4200000000000000000000000000000000032782c94c6e45a88425438324f3a24ebf37f0be213424b1be52c878985633950a022f57f8d64af1470486aa3744f3f7000000000000000000000000000000000631556d52fdcee3529023cf20d46ea09ab3c642a7f4eca2878e4af88801d21b80b829c9aec9e73317252639c148676c40bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000009eb122c61ec44afb56b64929040058a804311e0e97d3fa513a162748091304233480bbc883f6fb66080b563b308a24a0000000000000000000000000000000007d1d810fb8788b9f0cd04235771d7adbbdf8c6e67e8538b2c6f0f278755cc5e57ad720515ca558412ae1fe2cd40b74d000000000000000000000000000000000955496bdcbca8716245a130fe6eef44d13280b2d56f15bfc772f8ca66a52ca0a742e6bc273c28cfc858a3269f59beab0000000000000000000000000000000000b27aaa0d94633912c96f00ecc021773e5cf5e164e20c7a7222a58b0465e7baec4e67fb56ffe564c7a2904f36c265e61574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d70000000000000000000000000000000012e0572f5c84f6082dd05705a3fae738920ffff840c21e444f0ed002df16394afdc21c249b6f1837389c48719539f4c5000000000000000000000000000000000c26bb3ab52e3bddc219dc223daf472247547544e3a9ccc31123b82000b17ef325148935621edd36ced4e702ade1ee3e000000000000000000000000000000000c13a8f02dc3f209e9abf3d316fa843be9c4dd98ce1ca2edecf757bf2bb498750f6d96c28abd45d9c6cf5b8b6334b63600000000000000000000000000000000157a50d9034024dfc7b0f0db4ea0f45323d76c81bc844844ff9bdd0c13f2059066ec3060210aaba61bc074afd7ccaa286dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001054edb092a7053eebc542f690e03139f2e25a0098c665741e8711c8a6b9582af47e467f74fff9aeea098b7732be72d400000000000000000000000000000000084f919e219de15e7f9ee122383c772415741e5b86be6ee7d2193a4f6be5c9cc9b2fe5e8beba26cd768bf2ea1b6ebffb0000000000000000000000000000000001822b4e8fae5bddbb36f5c226216471862af238be770d33c4fc1ec2777350db2f42e33a7ba468c317a128e8446ceff300000000000000000000000000000000130f704596ddb28ec6e335d9527707a75c97298407ff3fe17d3cba0cde4c21bfcfd1ae46272018c1db768c036f215182d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f42308200000000000000000000000000000000194dde06f8c54de9ab0ad72ba0de2241fef32fba30fd6f5e83fb7750bc120d51c461d75e495cee0d1e85f0f39aa9d3620000000000000000000000000000000010646496be02c658c82dc68eab86a4f784cf64494bd8441f884e8ff384cbb6ff3a4bf5126bbacaa556aafd652397a8a800000000000000000000000000000000109807bd4b6613acb3eb7d386e84166219e52e841c41185a269cc7cfc5f34e9ef5cd1fea29877749e0cef93a3b44eb1600000000000000000000000000000000020a388c668c9339e7aab15d03108317dea97720dd27a94cd3bb59b372b268d1a7d7d7409780bc4912c3f95acd42a57619d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb00000000000000000000000000000000161506c4a2d57c852fe8c3dce63ec6673f05f99c1e032c8e591239616ef4469c4240482ce5985fdfd4a80f54dfa7024f000000000000000000000000000000000486c5b106393e544852c143c5ac4a882c79870363858b2c910ef4041d8803876cc55ef59cd6a41869bf5247f0db2c0a0000000000000000000000000000000000fe765ebf3c4edd3035c7bedd4aec918426898339d7aa004fd74bbf0e3236deeb7d2bbba56c31fd447816e301100a66000000000000000000000000000000001917c9cf16032e22cdd3f87f098a532a33c9fec560a88f9d4232f96cbe0fe945fbae6bcfdd2095cafe6e0b21071d6ec53f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6", + "Expected": "0000000000000000000000000000000015a88bcfa39f444cd66d0d7e15c4040561154c59b832c5ca895f8f8077659487581681cc8f13be136a35b4a573551ad00000000000000000000000000000000009fb6b87eba1edb3d1d23e566977eac68e8f1a28386fdca9d484c7e341c1b210390787418e2f2dff7a228e1cf10962d6000000000000000000000000000000000978de870dcd8d094072897707313b9f1a18d525e60a7cba2b2a395ffcc9d0f97f84e0784df36247d6c98824aaf3ec82000000000000000000000000000000000fbc6832c324d40f104bf82c8cda941212105131c26f630af1d3f7040ef43c6eb4486766b75a81433e46966f79953647", + "Name": "matter_g2_multiexp_44", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007517a941bec38d0e84d21508d8bdd6778a853d9fb4c5e953bcfe3c8fba3732ca0b7f6cb5c363f33d7718b1b1a68f8e800000000000000000000000000000000150c0d975481422ddec2a58a55b3d917b6b7c0510e75442c81ee856e267d7efa09641c6b79fb9e699c6b473cccde7f4d0000000000000000000000000000000009d37bf938ac30fae1cb3ffaa971ff3746ee4090d4bf8b11dff7710b3f2e4cc686813890e03643fd56fc99e847ae5e940000000000000000000000000000000010fabc4048e0fadad73d0481e290c81884f4578cfb66e0a83324739652ccf273b62204f126696a2fc6469ede39e00a9fcdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c900000000000000000000000000000000089b167ce7fa997ca0ad3f8bbbb358824cb41f525bf60352d5df99402af62cc6d768113d2c1ebc7fe8190c5f732fbfff0000000000000000000000000000000013dcd35865e27bf98f1f6508b32c7e9a989d528df0626228087bda0d8b456af3ea2f4be6732edf1bd8cfd0ab9576197a000000000000000000000000000000000333b0612d7068986d21e1cb67a1c7af423e98cb14aace2ce02f84d32a38f97bcdac465f2b22e5fafa6aaf0d40380e4a0000000000000000000000000000000010de7ba4f50b6654fdecc4fc6c41289bec50cff1be18be9d5c9d1f906ae843189bb43f144aad4d2a2cdebbc2697c974918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd000000000000000000000000000000000fdddfceb29fd79c31b138ae8e41507f324abd5e3750da14f4f86176126a06380d53dd5f7efd00e7f94bc1370ac9816a000000000000000000000000000000000d8371c602e393a4be250583c299d069270a344953f7f07a5fe27f8617cbd3ebc91f423dc176b272339bb3bd8a9a348200000000000000000000000000000000193a260a417c9c46da0aaf139e3bbfbaa9f248943048396d95716b3be0b8a148a3f0ebcf7d6f9a318b16d2d850ec2f5c000000000000000000000000000000000be4d0f2bf6d746b930034eea8a19d73377617645c29153b6ae6d3ca6fb35a704b6a0bb658282cf93555c998f6fd054a650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000731c0a5d076d6addb15c1e5d3143d41371f4835d77756418bee452d2f03b1e603230c59f87905fb67d5eccce65a45d20000000000000000000000000000000013bd198c023009190c65686468523eccb57c5fe7b159a1c5ba30c662a275fb24d69338ec9c023ee6a10a8ec9dc7968210000000000000000000000000000000018fb369923ee655839c7d996e264133c49f102978f18261faf2f8eef376eb0bdcb5af375ada2bc783e50df16737f78dc0000000000000000000000000000000009ab8e16e1d0b406adbb37e950bc3820ec13c882ec4483528ebac836726ba202bcf796e84abb3c16dbf6d1131b3854cc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000001401029d7cf8e7d2690a27c01b32008e273b5a33842dcf52d84f77dfa4b2a1fb290f56eb4ccddbb420b27e06a7a3a3b4000000000000000000000000000000000c464c6fdba702f2fbf4232b34d615e66dbb5bdf80233f695e9103272111a06a79f8972d1034176859d0e29400f5a9c10000000000000000000000000000000006cc97a29f4e694f0cdbb099278fa94140b40147f4f911de96a762f2bd28233598a892899a6329cc3cb854b56076787a000000000000000000000000000000000625811cad7c740758388f330c4a56ef30429ea4cdb9a00e2cd1b7f310184a2e6ba36ebdca57c87cebd5232f52c34d92283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000d0a16b5d48eb062c71b91d74a0d25eca0d4bd7082de25199f33a9d3d598d137fbee2ac36e8f877c157be7438ebabd74000000000000000000000000000000000bf93533bf677050d9a77a5dbdbf7cf084b5d934d55318256712ec361693738d48ef27536476fdc93dd8e81f13d67a8e000000000000000000000000000000000696fbd8841e60300602aa5528391aa8b196d8c186d6124c842a0124a8d8dcbba637502f330c980b2f5a900be8e04d020000000000000000000000000000000017b0c51e699d2252f35619520af71775f9dd8c57c2ef146adeb72640bec2ca02a59680153e5c9f66bd513bd8559b9d66145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f472000000000000000000000000000000000eee94b5148ccbb3642e582cf0a517b72e6ea019676a13b1484982de7f4be0346b7ed22979ba7303f6367294a3eb2716000000000000000000000000000000001502bb3964f6b3e862279e15fb105073e876c4e48c55c42f3737dc9efed82b10fe8e39438ccd39c933f5ac3c6768497e0000000000000000000000000000000016cd8c4b3be55474aef7081cb969b75ef5e7cca9bd0f9627928fe9931c6f869a9a49d0ae2cfb8346116eb3ced25d4a8e0000000000000000000000000000000012456eceaf32cbb6514e6211136475a750889caea18ff4f9d5ed7b378e6d1d265721a646715aee6b9f2098e954a88289d9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af0000000000000000000000000000000007b5633f4a7dfe62f11065d44581f5060210f8e572d960eb85ffd0a903d8b989ce10449fc90b7e5646784a9f6df28699000000000000000000000000000000001710f252cb35d88f6bd151ed596f2d6455f050c5e25add394dbaf60fc036016ae07a5a8ed494b95875c02df3c523186a000000000000000000000000000000000bee19779dc6430ebee993f82a054fbd42e5b7265090017e5b2d2f1469bc96a5a188adf471d576a416f6a841081043df00000000000000000000000000000000038f9fb4159e4e6f596a17ddf45a00a9e4aede63b062af5eda045efacd3977e8dfd61c307834c08bb4c284638696e92ef262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e00000000000000000000000000000000197687895f22c4a639bcf2f494dd9e5a034610b0297528235f1d806cf032f5a86c5248a83ed6b12f0de27f5c6e6f49420000000000000000000000000000000011ccd5dd6d6ce553ade9b31503a9e6a6119ae329178706f051581e3cf0ee9d6fb527b340bab8c79fad1cd451c7edb4330000000000000000000000000000000011e9f051aacd69c8bfd2f0ecb566e6d38eabc43f276ba7a1b8e8ab093917dd1c672c61d6dac4651026823b9938d3601f000000000000000000000000000000001362c3b2e6fd9b3618df26ed28f96530c1915f0a4ecb647658d1ae4ccf4c000f3bd1797696c9ac5c5000dbe58dba8de44f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a8000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d000000000000000000000000000000000db02fcda340fb27a3fd7da468c5cbed9c8dce8471843a8ddadae43dbec9957a0479aa52855d7a6dca99e7922432365c00000000000000000000000000000000163503d24f9af34058cb5afd8e9d5aaf29e141c8521eaac282f138466e834f0daa9ce14e0590b501680d5b47f866aa8c000000000000000000000000000000000fc9175e6d20afd9d194907f2eb311bf8134aeb96da72f6423610612f2ed20a074c113fe8bb632d9ad74b2f6e7e2417d000000000000000000000000000000000b4621f5e4465629648b62b7f2b77afe6470f9706f9bee5b3ccfa66c596842cbae26badc689f7f623360cb7fc1d416b84ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a61630000000000000000000000000000000003c77c7efdab9a9e71283b034ef581a31faee417febfa99be3c18e8ab724c140be684ce719bc5a9ac5d3855ddbf3651a0000000000000000000000000000000011889b02b4a1150fc2b7191a95c5ee767f3c9b82a3a53591018242fa8685ee3b3542526dfdc00695a6cf046033b8eb760000000000000000000000000000000016d7463159c4e3cb635f24bfb944bc518369e894218bc49d7b7f0ea99240259f7ee2b4c26c6083dbc4559ffcfbd392bd0000000000000000000000000000000010a85df6294fd6406ca651f15494153e9802f0068bfa149e87fe4b1cc3071ba74940a21dfd55a8a77e7e2a193468a3d2eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a0000000000000000000000000000000016beec57d3049c382fc039ac96b890412c5e8075afcab599fb877f8639747a587e82241d9a8059a0bb45ad49959777d0000000000000000000000000000000000a70fba1b061dcf587f133035a3aaafcdace3b1e771d71887ae914919e5f52a99d9933307ec15b5f0a1623b9592824500000000000000000000000000000000005064161136c04f9f50e42a5cee5dce3fa0ce1dc0655b3785a852cb9741927f6c9b357ac1010d7212533d1593c83dba70000000000000000000000000000000000d50b992bc0eee37a15cfd32eda2c591fc4c4203ef84232d1a1e7a9888005bc00755d76b9d0345bb01ffa7525f2aa1e9d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b0460000000000000000000000000000000007617e60d8f67344ce6d2fb65cfd5b423a1fd091626da837dc8a51d6ffdeda9712864e8f30e45ae8df917e0e4625e59a00000000000000000000000000000000077c4aad14f870ba24703397ff0b33af2e50b026f3e0f13f3ec1aebc9ea3af98cc65ab56cce4045538ae6e5f410196f10000000000000000000000000000000004a31d0eff18afa87f9a53098cfd5d21e913c7519cb171f83d0b73abbf3e893a3ccd5aebb9f2bbdd3b0eb0326d37fd1b000000000000000000000000000000000393052e6dff65e01e79254af757f12eb1931e0b386f8cf0fa0782269f962ebc5d9bde46f5a4ad3806e88330aca59ff01e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f962200000000000000000000000000000000033aa108d252e9107f29cc7da79585d4525ff2a35d31479a099c7c011a9c4414d7bc5f8498f8a204134b2d14c5fcde5100000000000000000000000000000000121214465992bdefb970d420face6db75d531e67314a021d2877643ddf738fbe57625d286bde7f40efc1d329a2e85b6e0000000000000000000000000000000017e14f6cdd916b1fc949be8ba3ef9ae6cc16d64da4dd498b5458ea0c14eb7aab8f970f030aab26397110331da11a232d000000000000000000000000000000000c56ccda2a5cca61025253407e72967c767f0e7f2aa0b97d4e4a09420dcb882ff35039ae504a9c62b3f9e7bb0c2e7bbbe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000010fb3396b0674b9285cc5d5a4e7a41ac002f2b43332c20a56f428d1e19e1d1bb6f886d3bf03f7b0fc509e52d75965e15000000000000000000000000000000001196b7c253c50da10815bdfd7930a69608187fc3ac5fbcfeb35b95754d3017a094afcdaea867c2f08346717dfce7bce8000000000000000000000000000000001021f178c53b7d7d2041a6419203d12ee162f27999dd8f79baa15c37a7401e7a6df6aa4192a310cc1a23bdb0b427d63c000000000000000000000000000000000953c75910165f11112583476574f3987495d33e5b1a5c650a2b30692592a442d9de36da49255b0c01a7bacaecc9b81adcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c", + "Expected": "0000000000000000000000000000000014da1d424c936453600a4acbd3666c6188493d4da8b34d6bc508aab07e59e3680a9e3488e69d42a724c9486d70ed4fd000000000000000000000000000000000048c637348fb9a4c631a82ded1fa08d693cfa2cdd6cdffb8bffee63d1bb2ee8676512a1a8d375e7ab942b6d6bdda45c80000000000000000000000000000000000443264e7dfca91f17251c33cf72c56b045902b4db2eb10d1fd856f79b4130afa6f29f3283af7d3b8b2a9d8dd63718a000000000000000000000000000000000fb386f875190ac7a49d4742edb387f72c1ae0366ca5c71d5b7e385c11442941ce0fb9fe2014fc624fe93ab86ebc7aff", + "Name": "matter_g2_multiexp_45", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016a539a21320574fc25ffbc0ff10c821d6ad20674413eaeda6f4a31f9a028e21cbb3b224c225a2e3bc3dc221cec084cf00000000000000000000000000000000104e44989e2fba9ddce8e309f5d3fa3129f679d6456ed11137149b50adf8b22c1a148d47154450853e6797aba2b006850000000000000000000000000000000008b33b8cfc992efdf7d733803a6d08a4102e27fc4960ebe6ebdb7949c4ff5af76e55002d93a4f7204eff5f2dc4e37ef10000000000000000000000000000000017c35411c571c302c746a9b79cae892e988d50b4660564660de960ee09b3937b6f5b61fe37d09f1c02528f554210744aaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b60000000000000000000000000000000009cd32594094d4744f59690cf8d7fd260b5ffb2a22945d938c035151861507ecaac9ea553e7b44fc4b3beb03b33783540000000000000000000000000000000006f4de33731b9b13b9cb395798769e54a0679d272c2d5175455e10c790debabae4ee02b6df08975efe806da9c4a208b20000000000000000000000000000000011859798a8383b7f994a1535bc0a96a114b90644d19921f0eec774ed58dbaa899dd3736cd1f4a4ff9bfacbc7370091d7000000000000000000000000000000000376c25b0f70427d4974c4fd1539d40996b6847fbb67822fa01cfd541cd3a3f8a9f3fe9f7ddcc3ce920a6ecb27dafca0c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c0000000000000000000000000000000003a178f91a135d59dbd65eacebab293a3817d30e734c247f56a08812aa540a5c80e3f9908d86ad787bab27fbddd21517000000000000000000000000000000000672b3544dd2b91a626f37dbb389aff073777164e3e20dc572b18a2e5223bd323094e41bdbe2dec9bada227efb37dd22000000000000000000000000000000000f40f2d279c66f22bf0fedd129e02c96d8906f9f1ec19f5a5c1cbd5beb10942a066dd391b69920a0a697138f627a1b180000000000000000000000000000000016ef3caad858d323b752e5c437ee2043c8f691ca0f1862e80857f7cc478a689df97bde5b1d1350892c1adb03c5d2373ba1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000001276e133fc5e708a3265646ef0a0122048ef95d7fb46f78b8dca57dabae0164ca986bdc74e581604ff31165f9f28dca50000000000000000000000000000000008a77611be0502d2ea7fbcf73774fbaec68eba36038e2f34f79caf07f2e4b7444efc49a4e85f88af585fd28a041f26c800000000000000000000000000000000181ab176e391190b1cae2e9b4105ca14cc82d15890b0ec127d8cdb46f30b704a089ac69e76f5b50575ed66176950e1120000000000000000000000000000000004031ce77fe9ee319b8db8f220ef4480c81568b3f6e4043c8710b559d25ad69dd38dda48b2e11d5aead18db0d1cc09b98810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea000000000000000000000000000000000f26e6d71e206c88dc81b8b8a5c05ee84a9f185e7b7f155253aa39104b5de5be7bb6cb6662df4f8e63b37fd1682721f20000000000000000000000000000000010058d13637c8da2e91c8cda7dc2cf1734a2f14b12b798e5c563ef9ef3624255a6e1c7550c37b547c35c55dc736a17ce0000000000000000000000000000000019ed470bd514f8bda8fdcd9c64f7626efdde0102907bd31551b1d1972aa14e1d361e1d58b17948909a669fa4d99cf3200000000000000000000000000000000013277afe1891807e269c22c9aa1598c12081809d888e0eb2513ca3f81308700893f74f176858ceed9c7955dcc0d8fc6893e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a00000000000000000000000000000000083664daa965c4173d6028e047794703a16e52ae459d3db0534d13c72d749d603edd668b9ce500677715e45216367c63000000000000000000000000000000000f4e87a65f4720cbfde7868eaadb34ec1916925ffd84e5407defbda0c39e1c7afcbc90855b275d528e7b63fd3707bd4a0000000000000000000000000000000004c9f689abe0d2dd3d927bad4b39ab44f6704014ef9a1dcd1966777129e1c72515b43c1b92ee60e9611245454683588b000000000000000000000000000000000ecc57b08b45037e62498135643cf077f01d216b5106551daab391446ce7bb37d40f41378c830081bb6a326f0105c2c4eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000012b84341bbad1eaf7fc8ebe56f67598821017365b6f3b4cc1f2355f868e8d55f9c0bed2943ada202a7d85cc884d8e6a20000000000000000000000000000000017693721988f73d77f7a41db108e428b0ba781ea88eab463693ec352cc13d394101b9a2792e0f30c77bebaa395a4776700000000000000000000000000000000093245e2919523cd57a0abd2e8a9c5cbe774bee957f26d3cb502b9c8c06483b850b031461dc2cb033d399651724f4fe4000000000000000000000000000000001530f7dbf6a0fbdc8b4f7a4d298b7824c15035428cb8df834907e25c64b8985186bb13f397b7b99ea7014ae65c428b12abe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000f323f01f2a63bc6eb1b565594ded14043c4ea5d1f0fbf20f39299052617c334e6126afd4273738aeb153c3561348b8a000000000000000000000000000000001525d1e1fa65f1b674feef74f6c81c82c3eeb709e597aedabbfc2b3262271b31d93818613ecdeb49c5d3a6a64f17a5d90000000000000000000000000000000010458c15bf46947a237dd1c61882b1561121f64890681bae5db6fbd24ef6c34b7fcb826eeee1fa328d9ef4d859faf238000000000000000000000000000000000e1f29275fe1805d02e069082d5e9a7acf69be17013e6c4c351277408d49383fe06f00137e777ba4aa49c29c25c6c0ddc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000efb7373e11694b966d0182a9b01d1e52ec1e89cb18275921294e2d36333460b1e49fd420f1ab781b000d1491ccb0b11000000000000000000000000000000000cafcdc2c58fb3fad713ce1a38deadba8636c384243f9971e3930b961efaf303cac4eef1e8e4662636ff91eff1bf52a80000000000000000000000000000000007ea7441e1b2b0f1e42bd511c060b646c2d00bb3e6507beb5d17ab93ff68515b02f82c2dd43ce035ff660ddb0c104a77000000000000000000000000000000000bd04b88caf9dbd0ef5f89d12e72aa47d64212332b0ed871b7eb96b16295cf4810f6f20cc85fd4d1ce72119f80697c1b37d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b000000000000000000000000000000000c3d2d978e23a690e8422fd54f36fbee1f642611b6c3b2c2413844066159bdcd3703d1a392b030446af04b654f8f73b7000000000000000000000000000000000ae652fcdbd8e467ee9b447e61fcb811f8b6aa48840476c92daec3285785a06a81c1705fc2896c0843ab48eb92555b9300000000000000000000000000000000007088e6441cb85aeffcb4a9a0c81ebfc54a61f35c542be3870c2bb94d7081353322d4745747b0dfc3e5db07f9e48c560000000000000000000000000000000006c11f3e0941ea3bde0dd3a562dbbdad433f0b1e99ba34879e86f7951ddfb29b9e04ca62d54d7552a74e8cf1c3da3e704904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000000c665f4417d0163820ac96c83cc2f09b1b3c000023d827e2690aad7357ff59e278832b992703f5f0016051ce0a4510cb0000000000000000000000000000000012f4b6688300b253fe868b3790f6d2f4fc16d81a49ff7a2edf821de16dc992d79482d66e443e0abb5da43df69f8d648d0000000000000000000000000000000009e033750a118d998b136cd671d0e760e3a617f1d6a994db8f6dfc391619f408720cc57fe550785306184b0c824705620000000000000000000000000000000018cbacd471e528535e22f714a841f110fb0484826e30f97842d65072b2790dadf0bd7b28df96bec531fbed1f3f93486b68911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb49000000000000000000000000000000000c42b6fd52cc52034b04078a6565af2b43948695851393596e05f37f297dfaaea931a33f5b4c25980c093f8a742c0020000000000000000000000000000000000fdc7aa20e63743dd6ab32c82d2d6992b29779ec06eebd452c17d844159e90a7f3221f3e0e6b5805dc0f42dc3836d90f0000000000000000000000000000000003a2342a1bd528d701c2a6c72708a16df632f4e4b6cdb3ccc224b58b57af30b44556cc968ba3c0396a5e3f11568a73710000000000000000000000000000000019ccf76462668905c5687b7612a0bdfd4aac70f291d8b772e84fd5d4bcb591556317426471242fb5f44fd695c7d49279481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef000000000000000000000000000000000c8fd4a171c5fbf584f567a1c10b20628e7e0d5d796eac4a9dd2376f8d488da25b9219c7c70709999b5553f8bba915ae0000000000000000000000000000000005d791c907984f2aaebf903a0ace52147745295f0c5e85964999a8fc74b64c8871dce358f26ed1b4af6c6f7f18e8f4c500000000000000000000000000000000110a453bbba72ac171876e0f6b4acd5b178816301e02586a143c2bcbfffcdbf593655408b9aaa4141b2a210599f452ed000000000000000000000000000000001025d5065f9801fcc1c1ebebdf67923b967ce985b5ca27ab5db8af7057fda23561a46b84fac5e793dd9af692c4d56cde72780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b00000000000000000000000000000000023e5ea1909032676cdb79111a33da7ed788d2affbf4029b932eed843268f355dc92905db283d6617fbb530da3d704dd000000000000000000000000000000000b46f07de520aa17d597586cb0a6894a356757941ff9bdc2976f620e1bf1eec1dd9801d6baa2d7efbb3cc7073412ce8e0000000000000000000000000000000010022940611f418de9f9210b1be919d7506aca468fe5853675fe159d3e58685bcff6cbc2c1cb9e7d45a7bf305fca0eaf000000000000000000000000000000001888b5b0dd1648d9a27345f570a1278238957de1bd30c195d554750ea4b119e98b3989b912c4fad531de416c1533467f84ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c65670000000000000000000000000000000011dcc334a5037719256e514b2c3b0f36396d8cedcd77f33545842c686fa0f35558c397562a7e245f8cc412c776a2b3930000000000000000000000000000000006efd32c6afc56a07c813fe19e71f0248666c87e1df7e79b7afbd70178929e5660e85cea35d1c6f42b4c627a94ae0d150000000000000000000000000000000005a5fc2010798c793c1b407a577da0bf0e04b0478f19b7d0cfeff8e4e4fe2d581461831db165cfd17146c49a732c41460000000000000000000000000000000011dfe3b62eb87b039113152af74ae74137cba1762d4ae62d3cb0746272d1c42d3cb4a8fccd845a519fd0650a23a897a13256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd00000000000000000000000000000000110e73e44734b7ab63f021727b75e735702f1acfa6669e0dc27111794ebee371734764bb165132af3a7e02f3605456480000000000000000000000000000000005fbcac7c7334cd0e6468feedebe077b80390833eaa4c28af80d29e75d692a10cf13058526fa5e5ab0fb635335ac8f220000000000000000000000000000000013f537ecc28685aba2cd60d0e3e787bc8104a3373177cb93107b63d39919c583ad3ad7a42e322249d7605ef035fe1af40000000000000000000000000000000014791f94aff42bfca13ab328a3e47b06f7da52e13436ad477cf55e53b54108d3aa531f0a5d73ae5ed7108d5cca1ecf7a575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b", + "Expected": "000000000000000000000000000000000bab02defb32b7938372d656feaebfb5431de1484361542c02519d20c6a500f0b0b112c331fe6f4eac3ec7f6ae4167e50000000000000000000000000000000000796b38c67df1361115bbf3a4afad2651664ef55b1ed02d3172f024f90a003fc3631753d7142aafffc64c6f6f57bf7800000000000000000000000000000000080d91637a93a9025e8691a400254af37cfde67eff7d3037d428596a808a01d9bda8025b7246fb00785cd1068b2752d400000000000000000000000000000000182a97624249f0c6d24672f04e2c93eff63fbe76cc11ace0f7193facd0655cc1e1ccb2d89d9547bc352a395efeb95afb", + "Name": "matter_g2_multiexp_46", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000115b14c4eb9cc78eafedd2072be4555a3db9e61b5fe0139bf3e40a92cc37b4936c68576fee5692a80e4a9aef05a9b7a80000000000000000000000000000000019c828ea555a3c8d28cf0981e98609361b5bafa8b62e860d121c0f6d0f0dcef544784e8a5fa6a9f1d1a68b30e8e8a6f8000000000000000000000000000000000a2ef5146d2658609fd4eb98fcb5d42f6c6aac4fe53597128bbba3ba3539042ee5824f381c41dc76e2c6e4dbe0665657000000000000000000000000000000001807a12ae5f289ecde8ca0a913647d44209b13fae9dd6aa8fb4365a3beeb81652ec17cf92f6784c9ca5a077824ff6dc31129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b0700000000000000000000000000000000186bfd109ea2369818ae2f466953eddfa763960caeee9d6f1ecbf6a3f854163342c26b56d3844bfedd8f227070f75546000000000000000000000000000000001877077daa2ea074b2868e86faf14efc6ed35a64161a77aec54624d9cb916c45de81b40acc3797c6e3338fcd7a42bb0d00000000000000000000000000000000054be1650d9bb6cae6a1ed08879668e4aa4cd139c8b07ce21d40fb1cf37f11de730ff13814a02d2d6d6df5eec4afe8470000000000000000000000000000000001612b5b7c613cb66d4134aa867d985682f6a544147e7865732887d4fbb191a9f5bdc27bfbefd397f38cb101a2d68b192dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c90000000000000000000000000000000015e2b23aa42f1e6a07b0a31dd4acc27e35ce1fb3333c3f330f2d88f112375cf24e6dd5afc4d245531e4e84f1f82230ea00000000000000000000000000000000193649f3b7efb346e0c1f7bc05b0910311270cd44b5803fa16e06655d6239f609363344bb7c16c2105e20709fb5ff0400000000000000000000000000000000002a6ee30841f471dd2ef13888ba03c9cb93c85cfd0f1d0a3927205e3f57fe291bba7eccbd2352f25cc4047097fbb63860000000000000000000000000000000005482d4a47d6e381f755c4756a761f0310d0d981523afcf288e47a1a643d6be62ac6410521e0f25828f469af6150f41e5a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b01000000000000000000000000000000000178ca460993d503e496633fe5230d895bfcdd0696d817a23ae94e529bb145a0861e4448d3bd48c55907be762192a8c4000000000000000000000000000000000015d2db77105a8ed6eadc05d3d1f26a54b3d1b812a58ab49a889f5b7fcf5ec08c2eea6ad09484fda29cc007037a1f6c000000000000000000000000000000000fd5628d61cd0835fe49fcbd2f17058f23d0ffaca4474923a2c0706d9333d9881125efa2fda56234a82158da3eddb5b70000000000000000000000000000000010ce4a0bcada5f92cc8898dbf5c108c0897322eb6a467662be569d9ed0f6e2c808e214b83969ebb86c84d38f67d20754579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000fc4ed0ca43d5cb172deef02704579187a480cc977737070b8ed2dce48d3f3141619f37e985c220a2840ac01dc5667f900000000000000000000000000000000047a15e96760affa4e537a45aefaaab1e0e18052f63514a9f6544136c87b7cb4a5c8dfc0d9544518adc7932ce9cff5f1000000000000000000000000000000000c9be55c06f81e87de58a5c1df8d16174cf4115f81091937d98dad6c4780a9b8dae1081f8961fadc4f994ef62927664700000000000000000000000000000000165f9b1a8f23831a91be8077b18563c7648e54caf30895983cc26580241ca7c86b9b30408a9b27776286ed9f07bd8ecd4be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d160000000000000000000000000000000006edc0d62ec31b14e87b2ccff3a21a7c8d38c3ba0ec48bbb8df27fb1acf58e1a87c4458dc2b770172460adfb9a9bd50b000000000000000000000000000000000ae80063df8d41d45fc43f3aa0881364ab5fcb9ac526ee22d3870f2edb0aa379e9d81780b0ab08a4cf308d819338deee000000000000000000000000000000000e0898453feebb51d9a1cd2bda36a307ff2eebf44dc8f4c694831218c42b51f723ffe521b356ad4e5f0dbbca9af9ab47000000000000000000000000000000000fd186dbc046dd02217cec3c7894972f71e5f00e00a40fb1521659a33e079b7a1f60b026d9055a50ae18aae5757ab8490983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa0000000000000000000000000000000012134b433877e0a7858e6c3b95b2a1dcfb0548b290b68c209642dadf550db1c636598ac43d101b13c2d8d5ed9602a73800000000000000000000000000000000102b5de123c449a078f6f06935c9537efc791ed8e5475ffc2d9e1d098c814abe56d4b7fc6501c315edf7e64a431c5183000000000000000000000000000000000bce703ba78f45a1c59c69429d3dda18243ba2413c5eab46d469f504da975c434eda451c85357738d6c7054755d5cec1000000000000000000000000000000000387724937bfd817a65c0e0411678cdc78df26ebd4a814d92b023710558701163349b56b80e6bb68a4401f2662a0525506615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c0000000000000000000000000000000005abed9305bb79a0ef1cc70e7fc2eae35a8580cd3d1ffad73d3bdae541ad546b8f74b4ca76f8f374e31dbdaf1bd14be9000000000000000000000000000000000199b29da8d161ab3061a18debc8b7400415caf029ced47131e27d81a0f7f79b6ae5e570f34a4c74fb85fea1411bd6ea000000000000000000000000000000000b8a7c42f5289d20b1a55a42d53d49510d3871b6efbe560bb4d87029b85b930f787c3a42e225006ad62c68d5f96c2f8a000000000000000000000000000000000e74aad2b29a210cc316181863e71a1dce8866a088a072ad5972af57b813a2e968a5b16b294273acd6e81e9a5be2961dd77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba1812272736390000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c41f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000001611787ba658b64467b4a28e55ea24a3b230836af6c2a7072231045ee4ee38e02302a62688d6f988f76cb5e50eba40080000000000000000000000000000000008badcd59d6d30f26ca674753ae9257a853dbcf49a5641999634a9a35a97096b6096b7b058360bec2f9476a51eb0d781000000000000000000000000000000000d30154440d8bb5fa6538953a96ba404658817be8047fa7a3a86493f02399543220758e649948b804f2daf84fb86f7580000000000000000000000000000000014fbdd62f761fa675e4cbcb61083a910bcfbd1f8e37f1fb1915f60929b047c970b87be0730ddc20f9716ed8c9bea7f19c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a1600000000000000000000000000000000143d1f811644e3a51c735b708cb2f8a2a90311f9971c90b9ec8e45bdd6488638b6851dbc882205263887b4dd5dfb4e120000000000000000000000000000000015692a6b06e3bd3100e149c6be3cbf1566fb24531eb29036fc48f85d5da83316a38af4e714a17552024c1ca4a5e39d9d00000000000000000000000000000000172b9c88ed9a1fc2d5a7f147d034fa243d420b129343ff92b79bc4d836e380e5a7e388069e9af9026485e9d3f41a7aa300000000000000000000000000000000012e8453dc64f72653c4e9b3f6f43fdd01b896c642d21604f992dc5591f2cadf71de4099e1075a4ca4b7539f84dd5a908db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c880000000000000000000000000000000003e9b6d23809781f50c0033e53d245dfebbba9e0c4d9f676ae61b80fb6e774509f62fad854fd9ea841d9905d48d943a30000000000000000000000000000000016a1ba62bc684bb1848b0ccba59597b19973b56fd9b1d9d06352de44aa79c6bf65409dafb54f859d4a7c32e188bbe19d000000000000000000000000000000000e782741a4b16c5838a8f6e542135221ab3c6ad180c85c08742992ddf0239388e273735eae76c656e61614da386ce2640000000000000000000000000000000001cf6752e88990c221af94e18744790c30aa6a158b10a1f6a56c2ee3c3f0fdb2fa7213f16764ac9e9f4f65e99e715ca170751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc0000000000000000000000000000000000810b0175d781256053c3c9188cee4f55620a6624bfbd2f4d2e70ee68a105bc7b60bafdb76794a048e9f25da976390d4000000000000000000000000000000000716095f8fd72d9350ca62ca3ec34d2228cb563d4e89b19b152787d42fbb750435aa6233d0a97196a9324319837be14f00000000000000000000000000000000178e939d87c37d4a2f49e1e5596945879f2f0f64419e3dfe2afa06bd58098e1ba57a9b60c32cd6527481ab3b325ca827000000000000000000000000000000000aed480a1da482e40ae610a9522f0a18399b0130202f9ca79e3573987f5f7ae30724feddb52fdd05817a96f7937aaf7984bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000133adb236d9eec3544fc91852278abe37a1da0f32a84477c0d93927d64af613b7452a5f64ddec7447779f42873cb157000000000000000000000000000000000f6bd940b51b7ec5a0d92ac77a55c296215e970e9a499793864dd69c3a8d583403e95c08b719b5d8eb0c37a8476d3b960000000000000000000000000000000007d4444062ae06e65b45c6105af53c487f6b275ecdb36f87ec7b71d5861a1bdd6d735e9a1fc5dfb476ab8c13a98b570a000000000000000000000000000000000e043cdc87c67157b5ea3e5ab1b243aef479b23861f8cd823bced140ee03dd1f8bc6cebb4bde4683ac3340823f4d55b8d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000018c35112c27caa6bfe9cf8ae55f51755ed349ee7e7141c99069dea07c21a6d8634778a91f4dc3d17da04966a9eaccab5000000000000000000000000000000001800c8a9b146dba27050ce63e78895bee2016255c59acc34fd5e6cb926c16a8fcd2e8a579fa02559b3c571cb08011bea0000000000000000000000000000000014afab23fd4ea54b1ef576a12a2a62d42b493612ef466483ee8c4e62908486c038598e72dbd9256166960db73259def8000000000000000000000000000000000899a99ae8b10da4bbffb6590d79aa33bb2adb2444a11627f05622c732b70f90cbd2779362349aede5b591e84b53a8a06940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000567d6458d1a3e012c63adc8b9dcf32254c98c0b7021ec6a8d579dad47d501715d2e42a0837def225515d663e663c4f000000000000000000000000000000000178daae121366ce025c1dc2d3e72068fd40ba9d54b2b3724f7a2071a59d4f17d4766a82364540bc31a46398c66d0e7da00000000000000000000000000000000147b2851311913ea53662082acfba785d21915cf00cd154b1b495246e109ac37c3fb6c63aabc4fe71a0d37c81a40148d0000000000000000000000000000000000122b7b1a81888aee37fdd6c23d31c38e79f28945cd1798cee3f4d674e923fc68311eda8ce45a561abf9c5f0bfeb4297b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e", + "Expected": "000000000000000000000000000000000f5b941cda417cce69a30c1ba4a82cca71cb4b953d06d8e545c1b792ae22738dc006627da02b4344bb8be93a5a0dcf07000000000000000000000000000000000eebf4ac30fe0ffb905f81577466889666f801d4d6efe0fb8a663fbf1cbe76b2167243edfc6cde3f49d97d3040a9507400000000000000000000000000000000007ae6a99b86dc7ea95801776589472547ffc7a623009a592403a9710ca365510d85bbf20fa4519ca0e0ca208bf86a670000000000000000000000000000000004b5abf778c72bcc5b887855c582c042a4cfff489b0548785e4c1b735b19159be8a3f4cecf34c769a34cdefa722ba783", + "Name": "matter_g2_multiexp_47", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017b47384e6302b140118d0a9247aeae2091607ebb413ffa232223bb42d16753b2ae48e5ad0265e33616b25f0c4234be600000000000000000000000000000000167be566292b835a42ac7c099d80e8a0b5d4ff91d842d4ff6026876aa1570ef9641e9c0cbd44d8578f6a758717bad6f10000000000000000000000000000000014f692d195979abd9c55ac132d0def925d4e158fe946fa7b0a010c475d60171a0951d4b68ae3c463bf1136600a1ddaed000000000000000000000000000000000ddba1f4236c5200aa52f8cb7e15fac1f20cc66dc65ed180745a3eb8308f2c851ed6c1e27e1507d3f902ce672d6f8d24facfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe243351300000000000000000000000000000000023d08e255b244cffe911e43b9b48408f9fb3562edc2c27f405bb657731c885a58392ebbde9fc80cccee2404cc8547ec00000000000000000000000000000000088ef289adaf206afd2b72c93049fca2cf9292bf6471133c64ac4f42015b97bb9a23f6c34653e0218fd0abdefa56bcc60000000000000000000000000000000015cb78c1440f74b17125c547fe7a37611f01b83b91a351664c696e0f647bd2db3ffead880b96a327780026d74c9abca30000000000000000000000000000000004d1a63607b9a5c9ec31168d85fbbee77cea0ae93e98c8c1dde14d0baa72f91042b2b7ca489958344916ce79bcf286456546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000000ea6cb7ff6a7f4ec38ba11e9945eb406dbb8517585fef6cdd64edc970efba244b071fa162f7c8e184acbf71c5d1e12160000000000000000000000000000000001ab80c0dced33cac8a6a085efce71dcd7021f6255684bb631cf5c1716021bece57b900b819e6eb6f5b755b74c677b6c0000000000000000000000000000000005465fdd51352cbcd8b804cd509526c3b6232976b8278cec3b7db7da14b77f78898c6240c30943d1418462cb7a5abf8f0000000000000000000000000000000006b6caa6a0d5f2d671b10217c0ce5b3962b0c3edb4f2918497c316ebbdbe1a15c803d7fc3413907346f0e7d03920005aa9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc00000000000000000000000000000000170db23154805a04013052a388e14b5da00e65b35b8ce2dd967213a74735dcbfd28782cef1ffe9d384be3ecadd101e9300000000000000000000000000000000082dea309092976408a379f1dbed9d8cf91f768e2921e49ece458859c80a1d9efd4d5e588470bd669c777d16f9d2e7de000000000000000000000000000000000adba8ef34e197689228e6c4e13be75b3d4732872c99b865ce7733b7a42034d6d4d7520ef7ab712f60f1ff87bc4d9d8d0000000000000000000000000000000005df0788ec39430fcd0625f8e030d917d8e7c251ee6e3b0e79fc6fa5f6fac2ad736c818bd833e58ec61cfdff52c9c6ee2c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000000922d8b5db6e415aa3acbd0d6065db1b492c92313260019ef1bda0fa091c4bf091de95846af1edb34516b1abf7d278e0000000000000000000000000000000019af4ecc4f278315ed90d67cf4d22ed6fc9af5c0d0ca654f6a74a3c4bc98588bf5347b4536f36ca8b4750c18464f9b7b00000000000000000000000000000000021eaceb11638bda8b4293991983f11cc60c1daa2287f4b4a6066374bac82d117ac3ea4ec73afc4372d254bfc433b8c3000000000000000000000000000000001037fe26a10305cc5dc11a65edc705be5a0082656cad53e63038ee57a79e16075df54331233229a129483c34d6dd92ec9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000011e63dc251a5a1e2ec83741682d90588b6b185365b33dba45458b1f56324a4900b04d61af155a0edb0bdc2971b7aaa210000000000000000000000000000000002dc1bd5448a2ebb9a02509af8777616ba9657bd3be65519233f0187df77c49fc931bbd3ec0ad5856b2ec0dfde476a870000000000000000000000000000000019f0cf8baf100451313711bbb0a0fa318c14224933897e74fb727b585cc8620b7d741c9ca2f0d3cbe14a8749aa48ef3e0000000000000000000000000000000018448fa9e05f87d4991ae1c248413edc9a8c3ee789c9c005e691bfc9003191ff469e26db9e42e5758fac79309a62942c5e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee200000000000000000000000000000000109b71c19cd36ef3078bbae25ce6d0e8f7b58e129407fe68ab09aa747bfb3e90c04ab804fa6b7a223c172146fdb14683000000000000000000000000000000000d297750ba112da88beb84b8bbf74ed134b59fc9496da3045aa6dbcd97c68425fd68b75508de113733602a5565f4c8a600000000000000000000000000000000149b8ba6e05b66d07b353f46ace4e583bb61ed18fdbcea0e941b8d9805d3168040186d1c961add494f98e4e7fe68824d0000000000000000000000000000000013a6877bd46557d23b9aaf371ee5a101227d7938c64503b04b39cc6cb4e8ddedcf5cb6865439c9f8b1bfebb807ce52e24a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a200000000000000000000000000000000073f440c2704fae6c86aca3cee34591ec03c362c2c5153a5e82c7bcdece2af0c58a3484b448c8bf4da851800ead959df00000000000000000000000000000000075a2c26372b482a2420bd3c9952fdbf9e5fea906dc8a4deb9691f8745372805bacd68a4838a3fefc381a2ce946ed1780000000000000000000000000000000017575b016435782cd09901afd2ea6773b11f5a983bdd19d14668d75362f95d055b76e5bf6966b1bd7bfdfbe9a939e4b60000000000000000000000000000000001569d74258298fac89d0d91a9945780f4c08d7af7b942d06255ae590db6e8509c908c16bd2c2bb634279debb72f489b5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000017180e36b925e2ce23c46813d96b919ca181481efb5d1666c4a4e9c8031abdd9521eb8228c4e3f16de0b33da4c73588e00000000000000000000000000000000138965bff7c573546d80ef7efb3d45e87ed20f59adb0cd7ae148d09a97da7feaf1b0ef2455ca19381762768a7d82f486000000000000000000000000000000000360bd29c3f07c5b560e2ac226112a628839da9db18b052991eb2d9c54541c1b5ade9b3c2d7f446ad50050531228120f0000000000000000000000000000000007105978bcf13bbe2bf5c8f7d165998c3ad99b6a2794c90f5b61fb7bf2472d307df8fc9f4afe7ae1e40e7f0eee8ef9466e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000f4434c5180ad10cd45dca62b8da790cdb912c255c0f33950f7039e3885b38fa9e9297c7b0a875380545839d8c4d4ada000000000000000000000000000000000d0dd1429e512884ac209f788b5832d31649a78a8966d3348a93f841be23c8e4e42d6ff0d6c27e8f43daf495c9582935000000000000000000000000000000001307377f55dfed30ac1a406671af1895218a01d063b025d25bdbc53f5f9d535e4cd8053c09b2cebb25d3a08365ab8ccb0000000000000000000000000000000004f5c06f505ed15aa7661249b7edd71855bbf47237e049aa951e1ea3ff88f98591518bac975ac628e417892f8e9e5523428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f00000000000000000000000000000000083336fa0b79691b4875ed27b2bbd2d2586992940356f6ae5ddd2021c5ddd87f07f0a5c1e8d8a2654b99182cc2233e84000000000000000000000000000000001880f3824f7cef95ae5743de2e17191848d8d30f0469f455461c6559ebc75a7afbc86dfa3ee17f5470f74018ec335edd0000000000000000000000000000000007c2b26353e86223e5dbd4ed6d59f1170b9cc9dc600fdfbc6c73b96f2c667a82128b1ae5af0542b11a7d1efae87c75610000000000000000000000000000000002427b7eeb497a20cf15c10513cadc9ea612f3ae94e2ae833d281734e7b5d1d50e240659ac01da7864a95b4cdcf88744ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d97000000000000000000000000000000000e72845430ebfb84f8e3cd3dd418f6dc528bf521aca4f9dbd798ed903ef0ea3cf21dd1409aa3759351be32b21d8e8cbd000000000000000000000000000000001457ad87f0957006192dff7d99815c35adb3635815e5d157542b9f52f1e9f8c0143a21a3be4dc1aea3a895689f4a316f0000000000000000000000000000000007e8544b1037ece2e5a9ea387e0f43b72e895e9c2ca4d205f12bf6df0b35ae62a4d62756221d6fff65b928b7358f48b00000000000000000000000000000000012c5c3167f6ef118c4044c0aafc85a337d305437d694a7bd6fb406dabb7364d9e90d74a8b327aba971421a5b3dd5d06988a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000001995d7cb79da7b6c5a0c8ccc5ba075d8d6d8ed3cfca85e8ecdd2b589986fa58c4cd4f045983e9184d79173678d618f310000000000000000000000000000000000f9f7f6bcff0f6fd621f3f8fcfebac132b3f0d52a34af33bb9830bd714d2982f3cc6674ad6ca668131a5062e5589df90000000000000000000000000000000017699b298a46829020e0299ab89ab6411af0a602dffb0e149053ff40ccaec71a908da02c8e611723cd06c16a8e5c0f2d000000000000000000000000000000000523b287383c1e47a6f31d397359941fe0bb8167aa11604ff8569969eb5ccddf4c4f432d2b6fe6f39204020e850d4f2b30eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000004760120239593cae5bdec813735ccc99a88129c707686cf43efbd48fb08d8da3086879a6042bf118879fcccce0736bc00000000000000000000000000000000105b8191431f701b365c66680cb4eb267681ee4da17ba55d47cf26d21ba1c0c3eeeabcafcc79dd87b6457bcc91e9fec600000000000000000000000000000000126ab502f66e732aabe02fdb2f7a665a9a43f6b4ff21c22fa976e7e434b08b606e9cf0f02459fd85f5a80a332fb3a62e000000000000000000000000000000000b2ef01adea6c00250f2f14c98ec6d6083c45019f3d166419e3a137667324f80c34b6b72e991daf72e2eaf9985d0f9287e8dcbf708682225fe3f71b7a687da23de5ed188e40585be0553358012132577000000000000000000000000000000000ff22a0db4f1b1679bde5853a7c2932501f191f4a9f25eed968a796219cef028e26070851a9036a05a04abd73bd6bd4e00000000000000000000000000000000097e9310749f52a4b645190069f4d52315f0eb2ff9cbbcd31f1781a68b2664bbbf27166e6e74fc2be2e5b1eb3f3d77a00000000000000000000000000000000015ca218d7d128095bd4f4b4f7bcf7666e92b905e551dd22745bc743ad0783b6ac44b841f87d3deac44617a7c9a341c55000000000000000000000000000000000a1cb723a4c378e5db2775f4dde9a6887ee3313401a64130a78b90d65dda3a5d9c8bcbc1a0d78c310c869a7fc4889954532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000018b30cc461a4e1fbefe209a709a21ae201bc6094b2d15f0d6dee5a55dd84ef56b62ab1b6bd513b27c84c638291f4205a0000000000000000000000000000000008a6f2082d6d510b280a270c09044ad31fb18b851ad2b38859138c9c2e4870fba6b607f682a798bf21a13bff116014d200000000000000000000000000000000150ef352d494a97d0a7ffe44903aba1611c8d81fa2788c0f42a6db48a71101e12f07318da5ceb1f0af3aa10cd4c26341000000000000000000000000000000000ffdf3b133cc926684e4624531569bfa09b1658e29ad9c3efbd5e9d18353ffbbfbf23a2ad80ccee88f8fa597416d47173ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5", + "Expected": "00000000000000000000000000000000134f45e5409998e657923ca76ce92b7d2acc932308e0694bb22f121f8324d16bfce86f96c67111c8080289eada4b4fb40000000000000000000000000000000008d9063b7845ffc8400c0b7585e819043884f92e28f7e3ffa47a39e808cdbb034ef4230b6e19bebf083e939b6b686b0b000000000000000000000000000000000e95f8fcd6b5bcc9e00a580a99627d92fa7486ff5ea587df5dded24d1b0bb76d339f6765a5a2058a8e227f633ce36e91000000000000000000000000000000000393041eb33f2c63df3f40d8ea1e1a9eaa9eb0a46151294845e542054d503ef69b40b0b676b0e4f3e08f4d26c36a5d4b", + "Name": "matter_g2_multiexp_48", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000042a220276f12fb4e81c40ecef3a0d86634b4d1c0acfdc463df4e7501289f0be385e03808d44be053e6ac98f403a8a930000000000000000000000000000000004e3dc92e155aeebdfccaaa1d24f49efc8b02e4d9ba8500a5b953a96e0fdd58519bbf1c279750eb8b98616e6bb9a3f6d00000000000000000000000000000000086bc212a83b09c7361540349767896058d5567d4342c607ae9c07fe5f123d9aaac95ded6cdff0825edb5acbce3e2b6d00000000000000000000000000000000062598cd6d5680a155b6349cd51d636c1350746d17fe0fff5195f164ba2fa51cf52f8662f43d555f7be6bb8bcacca39448da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c00000000000000000000000000000000193e2c749e5bbe87dd5c306d822740969cd69ad6e156c323d217c08b18bb3f97c85aad63aed1e3a455ffa1b6d2a670340000000000000000000000000000000012a3dce63a88ae32a746e3812833569959021e1dd9518621793308f8f11d04829b2c3d0e0ec39fc48dfb8285b6852746000000000000000000000000000000001235488d01c380e91872fc57cbf618c3531a6bbf6cba9dddb9f07168cd459c9e866e44e9e5336369cffd8cc3a36cddc00000000000000000000000000000000010d85f85d6242b63f8421e92f1c37f64d33fed67e0cd3dc4b2b2cb4a7fc7637f9e049fc959720eae6d1f452159d48b78def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000010889825c752d0ae8445a1d0f3510135b9672c30a781788698f637c2f535e35788d76492edce8ea091223d016b5cc141000000000000000000000000000000000577175035c86c022e634ccc9a5beb96a36aa068cdc36e5a4fc2028d5dd099c5296d30a916d3b720f2e051e7d72e4d490000000000000000000000000000000017b46e49ba08a0abb9394479d693d8097a140094d0ef1d1ba7761fb601a686b0b2b4d49abc2e393a99c5cb760299992b000000000000000000000000000000000820f8e52c1b09986a70bff04909b044f671c3933de43a6bdfcbe3712310274ed880d7adc4947490c7de095ea651e578b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000001776b92f683069fdc006904fef8e91f716d9f6bc46306b042228088545f0e11a41b40b60722d4f0483250391febc0ed90000000000000000000000000000000010d5052ef2504115e9d9d4ca81c7080c0868cbed605dc7673f7f94f5959c793c96aa5334175e58499102ff76f974ced80000000000000000000000000000000011d1e719bb69d842df4fc23e8dc4393067d00f6fa8ee42d89b462a546414b91f68dda5378fa093b3ffa764b5fc63b1aa00000000000000000000000000000000099d0784a200e5d2d38773912cf1a49e813c871ede8c50da03abff58ec1943d2adefe66bd2feed1c57f5a80253e091d0a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000d7287adbe0bc3cbb35ab8bfe69064faa83e3e64d73a0c64d960949e10070081a99c35d1dacea5a3b9bf312745acd6e600000000000000000000000000000000034f1995eb8631e080378b22a51ace902ebc9da4589c89ab557b6aaf685fcc74ec1fcf95f6b9a31b7a45cfc5a1610c640000000000000000000000000000000009f56712a46c0fbc199c12d5eb7abd60e660e2c6d437007c34954c6234a0496ad0adf68cf759f8ea30980c9a78175e1a00000000000000000000000000000000073fae1cb78c776188190a4d7223f7cdce9a36488193dc06898919ef4d5136099c3185d352028760c753e9eebb52ac240b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000019944fc459fb601bddf10a3a7eb55f34713d396c3208a10089b8f21f4bf0a5e87e95ccf73e0bd90474d3e043f37a72300000000000000000000000000000000158445bd2b6d396a390a0bd5e26256587f980eae84d7a592b2b4d788c452d312b854427185a770084e1b4c7898962e50000000000000000000000000000000000ba44a1b912645354da7d8d9c694b1d5a9ff2d642fad31975171deef3adb0f8d92b2d3a8bef6ecbe0b8e90470b3938d00000000000000000000000000000000012a040a72ab035684bfdf57bf473ff59cd1ee01ec949dcc6066e5c8afad775ed55123859cdd74c7016a092bade7f991b571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf000000000000000000000000000000000317ca8fdec8c7c56fa3812157f9ca8e9bbf91013dfc7052c0795a04a1b4649b2147d9cb1a61f2c114a705e5133729920000000000000000000000000000000012b893d50fe5ea2eb528d1a04bc8596b10d4714a0dc38bdd5f0a275c07c846970106c3f7b5686685f5c809e93c57e2ff0000000000000000000000000000000014f018a0d13c4c494f4a6b7e836f0f2f46c4b7975d91adb93616a0555910f53574add03b905000f8492465c9b5488c1300000000000000000000000000000000146eb4ef1103b525dfb5c31bcb98e550245732fa252a814824258093a2397d1489df8ca0228d4f5df0a00d473d1566c454c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b0000000000000000000000000000000010e9962dc19aae8e92abf32fb9c8eac44d77f587159af4e3b3a080748322715a958d953d3c057999839a47dcc840076a000000000000000000000000000000000ccafa9761e654ba54a46afff51384f1c6331264082e23f94fffd6c31a1b1b568a391eac79417657f40ce2fc9a154a670000000000000000000000000000000007276b111c94130b2608827156021815faf2be29ae42c454f3e2f95de98d2f5b98cea3eb18335a8fa00e5464f8089cf300000000000000000000000000000000053550896e867e237086098f4493caa2520e8c97a05e14d0ab7012d37b7fbbd42a90accbf0fe2ac99e78ccf0be5c9c58000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b0000000000000000000000000000000016b5bca8537059362147911da9e69ad3ecd3b4a7c43ee7d6d809f46c74c16bc7d69bfb5d7c727b4d5d8a356a0458b59e0000000000000000000000000000000010f3a7eefeb3033a733af7d20c3c5caff4c409305de8d71e08cb9cefbdfacda41bb975c92c5e5f2952c3c1e2bc6ca8cc00000000000000000000000000000000148f5b2bd65b71184ba6974678f709c5f9e3f1a020e3d4bedfa5f5f66478adac47f06ca2626c4a759b5eae09756cfe49000000000000000000000000000000001301306d1259059b5567154ef6e4779fedf98c29ea967ce34b78147c5730f202e1c12d5b5094219bf85fa62834329b45909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000001741f739459f5d462fc9ab55c68101a5a3f2741c05b4c3eec6959b2aa5e12493a19d1b33a9aa89337add642458089eb6000000000000000000000000000000000300d8b7988522706c0690da52d0a67ae41344e43cfa05d22feb91eb8635bdb970810e993e0ebf8fd63ab8fe3e048d660000000000000000000000000000000007c003cfba125692b88feba85e7288bf61bb25e04b1462f7a39b4198737010224ce4b73a320c81b1f70119af34d381d1000000000000000000000000000000000a4870c9de67517f4353de23af21fcfadcfce55365ced33a61a19e5de52f98721b17c6eb382970e7c4acd81b80a7bd2f6d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000002eef52fc72d5aa0456c13808ba548cb765e11cd0bfd0599544793f57c8a27ee90880e6577af1b76b3fe32c4e71f4104000000000000000000000000000000000ea99a4f6772f8114cfb3ae9dc20f11a34880a86088511e5b7fe521d50470148b43f866eb5bf4f67c523266bb55117050000000000000000000000000000000004bd802b889e6d18df7dbd65f39a908cf5889e14be51b5ebd423ccb63e4e5b35e429eb0d4f384b811b47975143ea2ef60000000000000000000000000000000018dded357c546d709beffff2da0c08e8059c720023234c7b53d0ae85750b3e166cde7faf340697b546b8dd7c13b1ce7bdcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb28000000000000000000000000000000001405ef521bcc60c55f8551fb2e2aa7b10117b2f96c03e8535e5bff48ae197b7e5fe69a40eecd25a67f430ca02edcc9d2000000000000000000000000000000000477d85a7dfffcc5a2a1048205362ec42b268e5fbd27ee7c8d4ca77b5c9db84dba482bc4b164f92db2c15cc518b3d32800000000000000000000000000000000060988548ede00aad3682fe827d1e993ed1cf118bec7cbe6f69bc160f030bf87c299d40047a4fb5ee27dc2814649a4580000000000000000000000000000000006b9e0579f82fcb8bc149e40b1199f5897ea48ae5eb58abd2002c923efd0f5275d24a579bd904e49b7447c4a03e3fbe423d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000003cde2bfc5a865cac624d9018c37c1b5746b5394597d79c171b25f84d5fdbc76bb90ba5cf9db14b3b8e62ff91cfd79520000000000000000000000000000000017596885262075e45db62ca68ee5b99d12223bd476e36ed4ddbf5cd56a0c6e9db5d79e7f95b96b1bc323d7c9fc5447d800000000000000000000000000000000018333858871dd41cddb7ad2f179f1f341b2ef20bfc7a1d3cb235e3a1a181e0da7251911886f0788e0f868e16520c5a200000000000000000000000000000000098ce44092980cb14e89faa7efc2906051c9a51cf7b2757dbffa49fafa3a9ba145f809f1212c27aa620bf062e839f83c2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000001180d554fd523a51e0decb92e0134c6064a17dd3aa7b11d590b9b6022f76763b1e20562da21e836e65374efafd78b77e000000000000000000000000000000000488686f793dde899a3f4936f07f9eda7918450966ca85b4715d6fee978d9d091bae1b5d2d04943365c076a849b3359c0000000000000000000000000000000014661fb2d305ec9e63d63e9951d0f081aeba99972b094c922d2797a1100759cfe150812821411205f563e22f01ef29c50000000000000000000000000000000013dd681200608466853cd3bfd20f146a6383151931079654962684d6c6fc3bd6900bb049483c1ca6d2819da456f67e3be3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e0000000000000000000000000000000016c8c7a2a1a76ec05770f2d6c8df35003104c034c76323fedd49663daa759caf2f4fefbe8d44b3abf1dadfec2a06cb45000000000000000000000000000000000837305004aba2e322ae29e8f0109f1c756a44b21c72733019e63ff9886a639464090770d12d35553f0002ad028332370000000000000000000000000000000005c8f82ca2d4f6785e2d76ca3a3d1ac67aedf78e9ac833c52cfda6289e6f5d7a83befbeaf753abce12376889caec312f0000000000000000000000000000000013595cdc9181ca70845c613663367ff774f073774688dc58edfd0c58de5ae12df5acd04a673b645371940d7f7e1601045d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000010f1b8b39ea8ffcb6a96bacd1c00b413c93d3f8da64dcf9257a7cf0264831be23ca63ab8d3d1cea21ed8d83ecaa3a0c70000000000000000000000000000000017a9030fbee573cb71330007900723f85e9e82530283f713f72e68c1d9a5ff9552d0da469a4f38b66e30df1514f922a40000000000000000000000000000000018b9020986a49213d4f3b4b052cf2fb65f82b9bc2051f20b399f2784b984ccfa2752ca576d352c7d65ab218bb8d5df870000000000000000000000000000000015a375a3711f5e9f85ad7266b2d307cac09ace9ea36e149dde5e0d5acdbac3f62e1cecba8be51d88f2143c3070eddaf0fa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc", + "Expected": "000000000000000000000000000000000b668f602b9f56182b74be413d36b03d2266d7165386a7f1f0d25d336d06d2bc5659e80e54dc71f153883292df1cd8940000000000000000000000000000000013151d305bba39734538fe9a2717392bcd134ef1f8c1879740c8cce981a2d70c94b23f1a71a0596e7ead55a55eb186c80000000000000000000000000000000000e5e7c268f93d8a9d3ce45db2a95be906483aefa3831ed2ab2aa357eca0ca231927c0e5031daa200784cba33b96e51d0000000000000000000000000000000011d57d9a9123123f9fb56e990626346e5c76bbd1a4b3349c3f7bc44f05a899f9c4dddd67ce5a788f85b4fb172385faef", + "Name": "matter_g2_multiexp_49", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006a7946b50749147573991c91f13cdef4e9558be37736e3d990c5750d31ba9711721b88eec529ea4b1dec1c935fafa9a00000000000000000000000000000000078d8a565b7f58b915c220882a73b6aaf100f2d54cce2524cc3a80d9b526c3058903668d17427a617ea045c3322ec502000000000000000000000000000000000733c6562cdcb28d740c64f50ee9d204af4ecc8de2c1719fb73c20f2580fcf01e1e494faf4386764e03920a4162049fb000000000000000000000000000000000421365fa828affe963d145d318065933d4d865f2a3d24469ab0db66dd09a574945f8a8b622d079a7ce1c6fb6c795a8f6e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000a4fdc68bda287bd819ebb0a296212ddd19cc76b042e134f1637c894ad64bcd8431392c9791f2eaaf94f6c8d189846760000000000000000000000000000000009d974fcb46fe81d81d62b24b805ab5108c9450e162454c3260ecd0d5356b7c263be5f78f6214cc7254e461166910d23000000000000000000000000000000001081fe3579cb4d8a7e7d43ca8cdda28e1f9ea8df83c6069f4162a2a0e68e0d5876b283193649018e754c5c8fef101f53000000000000000000000000000000000ca4faaddc4d14a6648e3515a8b9028190c17f771c7de086fe4624a3008d7e6e374c967f303d9511b9da1a95409e3cb3d51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000117318e376f2c130e5bca89b3d700fe76e9603adb22a5ef353bb3b5a8f641c85deb4640fbaccf94e025a59fbf2a41370000000000000000000000000000000000433428497ed89a43ba07d816df224809a827194ca899924c3844650a3800952cda8db82f2f8e513994ed9893fac747400000000000000000000000000000000064889f1cb7d6ab216fcceef7c4abf89be14ef93be2d39bbba2b74d06999dec5ae1941b507709d093b28e030700cf866000000000000000000000000000000000957fcb8658497802e78b8250373f77acc4ec47fa2c87e78adbb2daef70240da640a7945895940f76bbb80bd36b4ba24fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000013f9771c105462fb6b975b0b2fd20d0accdd2d95d879c8019b08db394cd785ed9f151d0eb1adeaa63bbc2686d1172b0f0000000000000000000000000000000002062a5f2db0a01114a1c6e8c739f80f598f4e905952101a244853078298eb443be6839b59d4f0c7745b739cc89ad8220000000000000000000000000000000015b5485439f1b94fbb3a8f5ac6197f0dc0577863f39c44b34d4c5437b6a82a704dec17529654b3037a9ee1ebf14c8d8300000000000000000000000000000000154d750e2a660205812d428cfe79aef4e1059f4e231024a665889d112af37e6e17e04cd7c926b6240bf2f616a1f572dd7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca058129949000000000000000000000000000000000231223930956bd2d36a89a0a0a47aa46b4763919455ad3a3581439d25a82c176569698fd5ca2b9429793ebb16c98e50000000000000000000000000000000000b5dd675af51c18d2dc76e3103da4409f6e8c1cc719a622d4a33aaae3f23e529c78b63c55b67fa999bdcc7095a4ece300000000000000000000000000000000010c971be55cd02e4c97031d3b25acfbf722e47e5179beb26eafaa72d4bd5f47cf280a99e0c3c4cdac05bf1572d01fcfc0000000000000000000000000000000002c1370919e6445994df1e25ff4a79c8cd8805f12e5d8c781e58f04dff68a97424b35d162d875ca2b3f805b4cd6d1fa641b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d6500000000000000000000000000000000169938b4d3c859f97a0627bd1a83fde725eafb7ab77b22cae06d2a776569236d834702081e78d61386999c938c0259b900000000000000000000000000000000091e922f00828488e324f9fea652ce1edff83d9f479e843ed4bffee27805a5025e7a150719b354b5e61f381ebd24d4ea000000000000000000000000000000000334ba8044d7d47795b59eb089502808a7ab8f18e3d5e1cc49acdb5020b3973fd21d6d82557afd748dad88e45a7623730000000000000000000000000000000002299bf949ef249b5057c103ecd149444635b4f636a2fd0d073484404c1ff4ef71820260ea6529bee6f5b07f2ba94de35615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc00000000000000000000000000000000076ab7838db87727fd653a3b561a2a5594518f296284bc24a7d215b1fbc0a6492d425078fd98f92a414dfcb3c92cc1d000000000000000000000000000000000022b71fb467dbd6d9b130763350bd06f52d20ff2cbd46cdea5e8b1525fd73bfd08f5ff171f9fa28050e9a3b296d3e9e00000000000000000000000000000000007e917cef0195fc589317d4a71c14022867dbc0db26c653052e2e382d0dbebe67a0f582bc0a27dd1dfb4703c545d0da30000000000000000000000000000000005b1d8651b86a403ad993c5cea4b6b82a0f8a9f8a59d4b94f10e68e9538a559efdde2007736aa9d04f585851a89af88fbcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000653c5f5b2d97239821d173036929dc716e78d835a80af55868dcc3e218bcebdc2a052d31f6a573572d13f3bbb14f241000000000000000000000000000000000cdbdc3cf52239a6d4bdadc273b00924de8730c03ea82bd20ec1f04375daa4497fff3a1726269a736706355e72be83870000000000000000000000000000000008e0285b177fcd768d3519062177fa1314c4370f872eaf10f3e0dc94e716dc6a67894d887f40104552336cbb5ed614f20000000000000000000000000000000000638db8269ea4c2fecd5b45955609ef6a1c2c6faf6ee5a8d777e0b38f16d1acab2da7fe7b6f6ebb315ccb345835a21d94c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c40000000000000000000000000000000005b4f74cd099eafa6ae59e7105873d4a46e8e5985faf2d26ca564125dca93b1c48187ed7afa02cde8b52df878e1aa618000000000000000000000000000000000cda7f9eeadda16ef757ee8a98be147d374d3a1d40790d20a1ae42c9ed38e4fe22be76ec4f807cf93fff5c6efdb50d1c00000000000000000000000000000000121219b0b0d236a89a857c02249cc04c22299d041d95296dd235b3639416337f5be4a2ebe92a50d192fb748d5d4dca0300000000000000000000000000000000112545a4677ca7d60645cb8bd98689c4aa85a68bb62dc68c0affca5a17ecf0a08fb9b91589d08712b5af4aadf31caad2dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f2000000000000000000000000000000001342460712b73ca0ef07d953c32d280a3441e108abdc2d133265160608986481df3563c5dca20f209ce078b13b49707e0000000000000000000000000000000003580a5b4a7f6d6e066ad9073f7105f6cc1ff35ef5e79a0aba7f48ff2b732c7aec72cc9c5f9233fc9c267d8aa37ac17c000000000000000000000000000000000bb7f32db8a4e341cd9f8dd3b5677dc650cef675f0923bf2e5c8b84c33d447daacbf68631c2388eac5698495e1ad5a3a0000000000000000000000000000000015bf9cd1aa585eda2910128f2b452569abc1c94bc8bd308ee92b6c7315a56fc92d6cba03334bc36c137c14eb1f198b07d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c100000000000000000000000000000000091bca266255d692cdfd10929802d79b474706d160033495decd11cb0758136ec3ae7fd4bb99081e44dd7f25224e009c0000000000000000000000000000000001fbba1ba796416ac22c92f3741e3b268d89fbf0307edf0f25c7c12b5cd230c41582ba69465686ffead9f8363dc0c297000000000000000000000000000000001139590315fc4d81e3e747a53e63ad856635050367ffc143c1422e324d5fe9e4fb90631ff8bba764a87b8077b571aa0a000000000000000000000000000000000dcbba28afd445a57db762d08338a26980b4efbd11668e4050d18234ce35a909d6b563a5d3e8e72892514431fabf0147aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000001322b1f1388a9dd2853829bda1a5120250ed08f07c84fa398e59fa2577454f38f0a76a1e8db897bf15b4b50ff52a847c0000000000000000000000000000000017020d7de1dd424de53992c168d924c42f26231d184ea3cd9cfe64ad9c82ad067540b2d9ab18b0fd28477ac792a80c4a0000000000000000000000000000000000fabc0769b95e6feedc2165bd6d324b7d16247b79eebc1f09d849792255136538e628bd6ad9b86af7bcdfdd991fc31000000000000000000000000000000000144f39f792bf5585f4b49dcd3fcdbb61cc7ef471e08af4c15cfebb855f0ac8d5fd057c9486e53e8e1ee4f66bd5e943ad106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a000000000000000000000000000000000c27ac29db98fe3038fe5f537d5ca6faa240602abe11c6f530d9b18d763d6dda3fb25f9538d316e6527c114405ae54f00000000000000000000000000000000017ecc872183413d8065a99a2d1a73b70150e2c1fca2c13a731a39b52aebc6db79772e91f115a63f7b23e5fa231df697a0000000000000000000000000000000016b9715ce820b619274202b52d7e7bee9a17aaeb06c2ecab8bc77c670bd4c714789e4478178d94d2aad57e7bb0b7a4040000000000000000000000000000000002d0723a3386248d8597d2b63289300de6a16011a38985170a1652ff81ea70a78459b3ef252cc5ed26ff1ef1ecaf6a42c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000eb1ddb7306d8d2858fb57dac71f67473b813f37f02d73b17f375be86028176cc1dd84347f183cb7d427b861be34c3d70000000000000000000000000000000009a8811ec77eb21f2b33a591f2fe6d7b74b40c5045ceeee275912aeec664838f332bb49bedcd958ede0af0d0232e76ed00000000000000000000000000000000156e28ee3c40c6f18c6059e06ac8f7b39fa23e5962f640ef3afce13c169346a4c8e5c2bcdac8fa15921a4740cc5a0f2300000000000000000000000000000000084371522a6ebb1925c8fad3f20277c34e657aa71abf8ed7d323a10c14cbbb1a9e0e54bace32eb845e6709c1c58afc34a5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed49000000000000000000000000000000000be2ef9ef38a5dcb42ad31b1415c8eceae625850db4306a26a0598d4a567936d75b701c81793fa7b42d158df2dcb0d5d000000000000000000000000000000000851b82b59fc15b89e33fb618c56d11a07116ea35850583a07066ed97b8a864f3766c0cf921d007a6cb43931ad4fcf8e000000000000000000000000000000000ea8bdfa3c5f000d7cb1b5cd69537e4104daa15ffcec06f40a91b972d8011e5fccfa911c55a07383cce6760c145c39e4000000000000000000000000000000000652a4165602978004ef702103ef18e8fe7decab1522a76486c742d29103e3bdf6dda2d3cd64ff1b5d5a76f4823bd363083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a4600000000000000000000000000000000015ce5b43e1fd950b77e2baccae8c99b82f38bce09989fdc5d402420e7931a38b7fdac5a254b0cb9bd8fbb488d02493b00000000000000000000000000000000018c5b3ff46a04ed114bbf56399738e5d594ef8dd1d5e2e8dc23a0097893be3da4fa4662686a6dac04418fd2d344e36c000000000000000000000000000000000efa3e970a5cd0c7bdef6a2df3be9be18cce63c10c331a18d628bbeef30488ef73d866f3c8804acb3bd375542e99eae6000000000000000000000000000000000e966d9e2f2d47df5d661a89fafb6d4518fa1544ab7a56716df511cbcca99098f944a981c9da569cf95debb455842006d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d", + "Expected": "000000000000000000000000000000000ef06b515addb951b24e5d61f6e6eededf5f93f9f17455e1b563f187f73394457b3b7c1b90ed454218f8782d2bc848be00000000000000000000000000000000167398608a87490fd17506166bf54636aa4dd6d3e8c4d42995bcb0262268eaf2a6d828b295434f45e3e53703aa67cdcf000000000000000000000000000000001602ec6519e4987a052f97eb222f505e241d99602c08ea9c41bc95796675ebf6a819aa0bf87319f29dfe47f45f3c8c7a0000000000000000000000000000000002ad4291ece7ea0fcc9f4440e88eef693b8dd53060ec847bd27d74cf71218eb6210a71895ff1f1f4537a901090f14de5", + "Name": "matter_g2_multiexp_50", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000129cc9cf82aa30671e969148f058a0b8d275bcbb1c8da66e52998d9555dfaa075e2fcf39fc18f305940fbc972cc5c0c0000000000000000000000000000000001252482c1419ad72229a00d90f1c09d464e896f47db91e9680efe36822e99e1ca7a2b5ba8b17b5929fdbeff8993bb18e000000000000000000000000000000001287d5054bf5db038ec2f7b7a3b79848fcc8ca42f9e19d5e21d36d2256f97e0edc2608d19c17d3714024e1e9e86ae264000000000000000000000000000000000f6262669e30a5db67550cdce8a4611b9501d02cd4e950aeedd1c87c4f0f63c74f35c802dafcf91c988a154dd690103c67c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000019cccd010df3e668b1c3ec053e76c46e45d01a4fe02eb074e296df2a48e0e4eb647b06c40cf64a0048a8fcc2b0cfa6100000000000000000000000000000000018e07bc50657f3bbf736c38518933e91af29e3bafd776243296cca3a1d975116e8b428b050045a61069adb23baa22d3800000000000000000000000000000000154f51badda1b828346986264b01fb1be4c7e9b570ab63a5eb15cabe9412a2f9bbc6d5111c638ff5118a4f6d08ed055200000000000000000000000000000000064d4e607a8994c0bb65770a14a14ed1b68c766ac2aed45a44c0f7c7cd4c3ecdfa077206812cf9b24e35021060a3668d2d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba94000000000000000000000000000000000b9bae5f720d9bd3271a71d751e4c35c39ad30fa8a67846107ff769c455e42465b2a39dfa32861634d5e323878f56f4f000000000000000000000000000000000a1077046cf5c7a66452cec2193ee21c1ace50dfd79f707c9297737f13806dc05e9e1cc5d1fb4c87b250ebea5a4ca6c40000000000000000000000000000000013e1fbe1a9b5f2ccff51120590ac0cb00cab502726b43a6fc12459e27bad4aa41537d6f3cc94a81a170998768f6a0fc6000000000000000000000000000000000a02c551ecec1c7a415256caaec1b5485a42f9ca8d897cf26546ace1f2bc8c2d10a353b8b84495b8dab5e3c60881185a58c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e700000000000000000000000000000000021b9ab3ad614816d7006efe688e1ae8cd99b0c4437d4363e557642a7cfda2000db6503b32db36b6d1ffe40d967c5efd000000000000000000000000000000000b7fb0ddd9eb2be9cdffaf8f8c593a9296c4c7deeb1c714c11863d71dab1e6fd309b75c41e25de3cb6089726b43427f0000000000000000000000000000000001277065ea9d208777d0fb7a6726e11c8330f0b3ed3c6716acd559aab19b2fdafceca8126c9facc43b9d80534c07035a20000000000000000000000000000000015e8c12065d601dd5ede75bcfceb7300bf6f9fbcdf68d2f093b7654d80d3e565135d64137dc401d691a45fe052f58a6850a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c32000000000000000000000000000000000484c4f9652427d0649c33e93d666dcb15bc56669c00980c53357ecee874bcdbaa016236df65a4339dfbd44e4eb0823c0000000000000000000000000000000013836a7275c29c989891c94e756cee9d6c54a8f634fa570655ee44b7c1e34137edc33323dc0d1f3a0218039fd6f7013d0000000000000000000000000000000002e88c7d5fd87e97a0de1be95021821942a8004115fb4fdd9ad26b7e0fd171f9c7e6f962eb179bdb95ef960cf9396372000000000000000000000000000000001636e351a0ed1a260ffe0d1355e6da288792fc97a7310b040cb9fcd5c550d85d90572154d58a9847dd5a8a06456bb2e43b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000007579785c14fa012cb5d6c116d34dcc15dfc908a29e90de3bbfb8c9b44e0b4258644440d7c78d751a007c10f98053bd10000000000000000000000000000000009f023538822ceba0883a0e3454121dafe8e5e61d4754b54e6417c989efa998334641d458591b3076b615937de065cfd00000000000000000000000000000000130fe7f2d5e0ffefa67ad3378690c53a6e68de5504f3691de0df3a24c309619bd3a345bc2bec4dcfb4b77255cbfe09980000000000000000000000000000000015bf85ed997eef4d97a81f1d75825bb4409cf86b8c8e5f4368cf1e4c803f9e1e23a2a96f7b0a08e5cff55a78761ebce21d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e3921000000000000000000000000000000000a95597e4402bbd17c20dda088f0134d42f14443bd519b3511b28fd8d395a0e50758386498388ea6ad0e7634587336c1000000000000000000000000000000000079f348d3de505875c5192f795cd77e2f7385ed447b06f2dbee18e85c832386b201cb3eeb21aac3f258d2c4b0434d48000000000000000000000000000000001895b1891a08ea42eb1f68698abc20394ffd66bf0c32979668950bfec5cfc8425314eec2ac17ba25f29133a8becc9f5e00000000000000000000000000000000146160336d881b24c6258a3a86c08d346900680324632b6d5d4582ee0865a7e5f2d01677e5e49c5a4179f8382e49d1566fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000013cadc6c394efb2f93e00f3976dde34efe75adff34bfb6f5e1a150b79bb5baf6bf29fa149581fda48faa68653cb61e300000000000000000000000000000000005fd25362d87f9581a202b186d2786d2859faa9966a1ceae747dd7a48749abd424eb9813e44caada0e456ee8bd12e99c0000000000000000000000000000000018e6b279e2b545acf1da29dc0504caa5982522546f83d4d3389e1fd6cb5328d4a167926a00ccaca402b3a3cdc67d757400000000000000000000000000000000089a9992a36b476fee21abd50977dfee01d7c91b24b3e26d7c15b2301352069dad920f0ea93a3e477a48029eef35605f06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e1188620000000000000000000000000000000005200b78dba7e423bc23e87c5937b464e97405f6461d05bd9d1d0fbf8f3c8e64a39081f9e43b4ec416198dc44db897e7000000000000000000000000000000000bdba1ed07c4a570359863a1098a73830818b3fa5b222316a3e0692a4ec65e59ca6b4bf5f72f8c1384e73e807d272d6e00000000000000000000000000000000073fa3eef473707b6aff37fb6f829f0fdb7ae808e85ebba4d4924a185c3656eb2856896307b671080347cebc32e958bf00000000000000000000000000000000076b56330f07cfc0ab34e98e2fa0ce4702b296a00f6ffee07c3ab523fadc048a047ccea7a9003c090951e4ef698d14e5fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab0000000000000000000000000000000007252747c8275f87b21bbac4071c1826d166d14e6205095e5299315d6b6a85aed995f9ba59a2163ce2c51a8e60eaaeeb000000000000000000000000000000000460a000fe29cca24dec469ba5fb729edf3e443bb032d488cc99102a614a5251915267db003dbf395132d815ba78f262000000000000000000000000000000000161c01cb4d0942faf2303c108604babbd4cabf5d3d30c13d7db9428a445c7f72d96a7405e22e4e451058a94e20068720000000000000000000000000000000010ccf8a8ec4e6515b20e07057fb8cbecc5defb87480f3e32a1bcd0cfe239e00daf6a390c4815ef6b85be1f07a4c4bfbc4a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf000000000000000000000000000000000e6cf781162502d2a758d0f96946bab887591b7c9ec9f67a1b0b962e74ee514e84c14bf67ae3c0a9ea2a3e472b7ef59c0000000000000000000000000000000001542b4e97f1e8a64ffd51ca43137b0660f897f6b3d5c6fee598fc4dd03932c3658ea55e1e9e73376e51df278ddd3a3f0000000000000000000000000000000016dae882ba240343e752eac68122424320d1acb1fbc4bd26c3983dd91325f25e1b1f06213e0e06c142997a13fbeca597000000000000000000000000000000001138b71c95d4de320f02e68dae9bb0de3e5b317cb596532c5cc18ca588cc8566c21551d7d55d685591126b9d9e466455cd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf000000000000000000000000000000000904f6a09f3a5f5baac902c702b059835737c06f62c2ffe9101bac32f854e4d72f74031f5410a5941612b1aaacbb50920000000000000000000000000000000012f39e7022150b2be12cdd621ae23525581405021b21cb9e55972724a22b1aeb2e15b135ceade132d3310e050e607f65000000000000000000000000000000000a92b1daaf23524904d74c3f149fcd2c98e3a4c257113533e7cc59c4656b785aacbf0ba6b9df0dc17cf7c25f1ca698c5000000000000000000000000000000000a20a5d7c0aeb16ff498f46bf05e512784d120b9c3c8b2877411852d7da3abde9e83a6d00213bf69ed88bcbb051a486daba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa00000000000000000000000000000000153918807d7da07ec7014154f00a558ebe0d5fb48fba4c16488d61a826a1eec28e3828d6744300c04207e8ff1cb61211000000000000000000000000000000000a755480457896c5a3fde35658e73fae821151c43fb92e9ffedcb05fabad37cb68aa24e029fc33a2518398d723c4859100000000000000000000000000000000148798bdc5b14b90aefc38946db93be1754f15d78762f38971b1e64a53fda92b96b0a70ca2548baec882887ae7f636910000000000000000000000000000000012299fc413dbaa77cf8867e331bc0602c4fb32fe44a150217de9e6391374a9ed83781034e5775c4933e13cdfffd25a9e6ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c000000000000000000000000000000000fdfcbcce1603198fa344487d2d4838b3ff23fc0a73a76222707d9f8623f0b87dfc816be8717b0b12667bee460ef40f70000000000000000000000000000000015036dff68139419db619912e2d19b7d2a2d637fbb8bffcd941aefe2eb4d24c1f7dc32f4f53d4cfce67785e7c328d6c4000000000000000000000000000000000fd575be9bf54128a9a1cbd366339c993ced315a840d60f8b77e035352bf705c01a9def713e8cae3001dc1062cc0723b0000000000000000000000000000000004015ed456125cf0f46fe0093b81ff9315d955d470ad756a9303f548819f339e137305c58e6f4d8db3c8bbfab90718d4f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000016d31e68cfdc5823970c8c2ebc53c3d4517792c44e90c10f920a819e72e4a6966c59a691b905c8b0b612065c56d86ca40000000000000000000000000000000005096d516e416fdc0df552c2688c74f1c067a3e5e7fd782479bfa468096e6ee3e601bc23d2e38ae7500325765483250600000000000000000000000000000000092c994e9dae287bb6450607a4263bcf6267f0f66ba3e63436292af7f6bc8e4ba794a12792b6af49ef59b5fe50ff6d3400000000000000000000000000000000175a645988f33612e969e1d91b2c30e47ec655ad655d89cd8dac151c3bd194cd5a8c28b498b1cc2f2966b7fc37cfc8c532b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a0000000000000000000000000000000008d09ee15c80facf7e32b15418fefbf7e80400acf37f2a1bc6ced88b1591bcb8f86b45b544646c5fafa71b5b103b927400000000000000000000000000000000060865ba68ed8fb3d0a05779c278352b22d4244edb7add23d985a2836d2772dbffc3c82c3134916e9b0900c9db6ead8f000000000000000000000000000000000dce53bf8aca1ed44bee47096dd988689c1e32e1e65a5f8dbabab7c4edba866132ee2c036aba5648d0dafa9a26405fe30000000000000000000000000000000003319995785be720860bbf48692d1507185d898187993865648ded74d3aaca45df939c6dd986db42a51bd13579a55b8f71a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6", + "Expected": "0000000000000000000000000000000010643af30c3cdefc30144c5d7cab17c9c54adccb3294ae79fe5c69376011c159be1e43940640bf5d9012ccdbc997e2090000000000000000000000000000000002a22b08904ea9ca99103a01caad745dc2afb7b6d23e666770e81a97031de921f9d4d1c04fa941c433b8cd9cafced3a10000000000000000000000000000000010808e5518eb6cd61eec8820b9f279dba2423b1a3677e21fe3a0ca2ad49fbab2995de1c5adc9ac867de79e3b40ffddf30000000000000000000000000000000003ce1270644d71e0055345c7463d72dc119495bfa04a818dd398d944ca46deb0aee8c7936557754fa18225522fb79564", + "Name": "matter_g2_multiexp_51", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000059234bb6d1b66985b79e6a2c6cd158d37fc50ccd6e50ad2fe3b6a02ae2ad35b8b2be92139265957ea698441e780532100000000000000000000000000000000066adc7083a7f3dd75a8e431a36632dfcecc2527f261e961335bbe8fc8329d992880736bb41fffe41484f68c38fda61a0000000000000000000000000000000006aa0794c27d3f60debbee292c28b430c159cb2874b9467312cb857a8777058580f8a2d3b9bf4b8b732edb185cca6ac10000000000000000000000000000000000d81f222ed6acdf29437adf26d2b785535cd6d61b329df98be04114c3ae68bc6854e275792fd48de3eedf6ba7f3849a2e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b20000000000000000000000000000000013dab8066757b8bcce2c9965e600c31b792463623cd5561f7f6d55c5a52c22efcbe48b8684fc2dca87e2945765bf565600000000000000000000000000000000198a0594b5e606b18201fe2706bddffe7bee6c583147513333230715d18295714055b984cd3ff8d1127f9420863e3a67000000000000000000000000000000000ef77ae1e991daea1fe8338cf236ba959b22df4b24f00c6c01483b6956c609b805ab89712f80892bc0160fa3775907890000000000000000000000000000000004d30f5a866a100dfe469d4d0c47872245c4cdcfb18d3ffa0de422691044b52d2b9335682dcbf67aafa9275712ae3f5512a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea77783559470000000000000000000000000000000008010bd5fb5e222618bf4f919c203dadf9a7b7597bf90e16772020614481a0963a8e8b1bd244661bd33e0d147be7663c0000000000000000000000000000000007e21f548efb869a28d6fe39b999ab7fedae9cd6cfa531fe608476ef30c8703951839476811838608dac1aaf9cd87eed0000000000000000000000000000000006cc674c464f80cacb2156fc1eb680938cb38cc166a99f72daa50f9d2c40f10ff07e447d7bd5e59b6b22f0dc407dd8df0000000000000000000000000000000010b160c58ea82bc3904302b1b4fe83d1883efe3c8f52c4e05a3d8681e604eabb1b7f533e61c51e9a172987383506e7b189a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000000d4f8372d1138489913654a7567735be1eacf8c7fd497c2216bf77a94f483bd4a7daa2c8232581d6815af9898a7569b80000000000000000000000000000000013676a1f72cc2ed80fa24f70fe1c52aad9ac13ad6cad1f519649fda6ea3787d86ea164d9ac86de96436e9db4fb4aa44f0000000000000000000000000000000003f7644f7ddc9276ac36aea5c36451f3d5d6e4c508932b16d5677977108f04deace5e8cf0b3b3dee88c328b6030f3567000000000000000000000000000000001953cf03effb7de9e62937572850e9fdeecb74fb4aa655de1abdc6e065920e6d2e51ff28880f33341443b5e6652eff4827dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c76a5bcbd2a61172fdd53b351d143bd30d460e398c9d4b7094a604ab2c0d46d6112bd8a5483c9935f0bf6d84df04b9500000000000000000000000000000000116b15825b780c49fb24617dca620e939e2528e10c49f34971736c82cd35fd3965088595deb86eaca3d3239c6c78a84f000000000000000000000000000000000a0cdaa541dd96fefd46b303b88f1dd4d24257b910a596817f1d946873cfd60ae58a88aa687ba573832331e8fc158db50000000000000000000000000000000016259f7285de159a2c6d6d8687ed348ab97e8cf329ea5de49b6d708b6da5b806bd012ca3641c50f479d85921e20fbaefca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f84000000000000000000000000000000000797e815a98d362e1d7e2ac1fdcb477ccdec8ecdf340d7bded36856ce30e92b661669b38ecbcfb0896b2fd75df9b734a0000000000000000000000000000000017916c559db6b4b28b798c2027e2c70ba1b940212df8a1649b9f6087120660d698bea81258e2007edd4aa7d0d535bccb00000000000000000000000000000000167170a76db0783a8c3228f8246502b15d342b019fb44a46b514f4ba2de3ac66e435941adc3d91874371561870ba87150000000000000000000000000000000010097a585eb9264ea96904d8534820be185d8d9e4b1616439a926c0ff8ceb6f2bc082e5712454690c9c05b8018a996235aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000025eff57e1f37903056835d1b4133ce064c86947f35859817b2cccf1a5c3923ccca766b3e0affd20a4a6df62a45c31000000000000000000000000000000000011158fce4ade070629162b2b6cf1924696f1f7776f3d623cfa3d54c66fc17fa0299c6650b709a1472262fc0abe8d9557000000000000000000000000000000001828a65fb90dcebe25413566deacf0677a3993b39d68854b264fe7807097fbd3106ac618545d3a6a42e197c65f0d2a7100000000000000000000000000000000045eb8164b6ec874467286dc3626fad3c01be61f6a8a88e5f88797978463db648a9b8a1e1a2589364ef2879cb5f75423fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000011847bdf2f67b40aac3426716391da488a8f0462b68bd35a8c1c762591e2f426f48f979a646a094bce16bc99cef7fcfc00000000000000000000000000000000092d61e408120b1549fc8d2174572eb7ed3f679327cb89754f326fa72fbff79e98cf5ad9c94c14dd86135e9aacc98b98000000000000000000000000000000001440e2f4ee2ba254a780a31b02babca093a38e5a1ac09ff388080b6c60918ae5b26e1c0888ea0976527ba103b257d02d0000000000000000000000000000000019797e49808b756128866fae0d6aa7e755a1d6f07f7e6a877bee150fe9adf0cfe612350c5a0e31d68cbeed226fa56f2a28d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b62240000000000000000000000000000000016d723a64ee06a7a631509c6e64b1c8bbe199952da532dd92194147bce9942cb4a76f2358e6c7d263916fa36e2c0c09d0000000000000000000000000000000003d04ce655cad1d63748f6eaa9912d6474a34820986835f60c812aee9980d3ebc18d6fd856a6de9546be024b2e95126a000000000000000000000000000000000ea840bd7f76f8e944f95146cdc9692d97e6a2d7d16d4a7f054f81888472da4d60ae5faccb72d3a05781b399536ccb1e00000000000000000000000000000000155a1c43c39e9dfc6d96e01c981662900fadf1a46aa1c2fdb70bb34e94dcbe86c4f255e259c771ea8ede50b388ceb2f61208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a290000000000000000000000000000000008f189d97f7d82aad87fb71d090a5c99d94079c0b74beb3dc860d440c0f46727fc49104d671bdcbe5b9551552e18afc60000000000000000000000000000000010c4edfb64b8932a617c316820cd27d3f6ffa89b471949362762af8e10d25265b84ca2aafd3b14f33c39a4b533da60d60000000000000000000000000000000017ef3bb919b087fb6745bbf115e2929394fbc9c89f65e7d591f15da93ab785aa6828ebb6ced99d3506810647d28ed814000000000000000000000000000000001591d8213ab349017cc93f1fbe6aca6765dd33ac1f468621e2c79e30aa73bd7606a0e5ba1d97ff03e0029dbc8ab1c5f4d3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000019b3a2df3a9571b066eb451e34d8a38c0d90b6e365862bcd92ae76195956c21c59441f0cd03cc69abdf4ba069759b87f00000000000000000000000000000000082537ef7f4bba5f32db4443abc8eabceef643b0878ef83860d75ba508369a3b459cad96f1cfd872df99548f656b0f9b000000000000000000000000000000000b2fda5ba0c405c9481edd598181ed8a59a8a18462508af8c5d66988a7a58a5c9635d93b5e0ee310bd35e0091fcd4986000000000000000000000000000000000af7e15e0052576f82e36e7e2b614dd835a290e05f2ed9dad7f508b4c04e8d437e7e937a7f4c88b5e66b06e0beffc4df7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000017a1d5add5601010d138263b4793149a02e8f4f7cfaafb69fde7b843a51cf5f0634e26b6e5e3315420d44b0fd205230d0000000000000000000000000000000013ea863ebe1b1cfcf4164d78dbe8fc809d2b82ef3e5a2589ca1357e48dddf2696e910a90301ed910fae77a1e462a5b1000000000000000000000000000000000012b40d9f25dc5a61454ddf1fa9c38e87eee60e55938b411bff9cf2161ebd7d3fc930131a198e7e97dc90cc245164e7000000000000000000000000000000000054f19ed8e2682caeda10c252f11706e7f3b65c81e7ae0a617469babc5f3268fe5c0ce2e85d44fb6731e8ac132b97c3ac5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f13000000000000000000000000000000000ef0378945ae4683666099be36de3e60b5bae9c3137b702e5e4e35afd5c1e81d033c3d6b1debf5bf36bdfc4e3af37759000000000000000000000000000000000c37074af84ff596ff2c7ff963d96968464d6c8d88b69af64ae883457d02ee9ec80720661f39019230a6531a0f2952bb000000000000000000000000000000000454e8aaa2830f07d86eac7aca1d7589fb06aed646146a1b90f4959b5caed73131ab231313b50c15213f89566ed87a3600000000000000000000000000000000143516cd7a1b8da41226cb828887a0b3314cf4f87c207d1d84e9c49f0f7e548ab99e635bd126d49fd2e4dcea98f3adf784ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000019384e15a8754c6d85bd298ed550a26b51b714745bf2980b4920d6e73f59e657d85d3e86baa9bcf7e971233daff99d02000000000000000000000000000000000229d233d605a1a9f060605ae366a263594d8fa2b7797358ffe4c62431b9718d155d24d80bf5af1c806f447b92fcfbab000000000000000000000000000000000bbfb66cc0c7bcf251141c540f712fe9a359d1ed36d228379a1f3791991cccb7dfe1a10d40667ca062cccd55c9e6b08d00000000000000000000000000000000150a4d7a003cb81423604c13d0c5175183ab5f459b96842939f5c4cfbb9196db4667bb4382d2d5c92b70800adf384569a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b50000000000000000000000000000000016352fb8e2751f126fd0f889f2a62a85b95c50d6bda7704112e4487dc94417218b0daa1dd6b998662af2582c44b011c90000000000000000000000000000000016bf4c60eeaca103c90643fe0969c2c261e9697ddbc02279f0d5afb5c905a984ab2396db93555cc2dd5682a1525446d00000000000000000000000000000000014be742feb1215cbdcde21e974c74e23c7bbc2cbfaaace28cf1d4f2b5a77dde2f3910aea74bc200277e6fe0475208057000000000000000000000000000000000bf98dd3e3a8b13e487d8b1a35615b0c6b0f514f9b8da7d6402586f113974c8dc9561db797a96f4f8040c1765518d175650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000362935e552dd01b5fc5a15a76faae937d7ad086b0a67e9cd3558287274106623deb85b6410bb4e64c424d44335f3b1e00000000000000000000000000000000096f23a54cf57aa3306df0a0a4f45aecb9b09bfe83878d551a59c53e18efc5a9f177cb7fdaec1648f66cdfaebb15c61d00000000000000000000000000000000135271fbe0cc0987e82f3430eefa8e3cdcc1be4a441393bb3fac0b8e8f78dc47ba2b833d9dca4277bd60befdf33275cf000000000000000000000000000000000dc1b7512fa5f9d4ea3f4229d947f43d7dc46b7770aadbd7351b6d48d525d0144183f2c84293c63c68d5262851401ae0282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57", + "Expected": "0000000000000000000000000000000001c11610b63eeaf9e00552a230bfee290ea49bf9c93cfea1b6f684c9b5a07f341b718a0070534e0da9e6ab1239d800830000000000000000000000000000000017e8107113714ebb1743c34d83be3acde096bfb6cf140e943ecd0831ecfcd097f58d25a45005db61551a01d9da46de10000000000000000000000000000000000c2eff6c7c25885c514aadecb8f0465a0fb4385eadffa082e8d4f497b10df2395be5e7760a87bc26772dd78701146b730000000000000000000000000000000011ad4e20f5c1518c72f75d67a897f30100dbb83365ef7729c3501c6f266d6002edcab8c8bc1f449c30ec3624cda13809", + "Name": "matter_g2_multiexp_52", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010a26eda028649789d9e01232884e4e5a6b8e0b17169b9d64393e2568b09ac4d3e61a5996108655c24e76abe9e3fb7380000000000000000000000000000000015e4e36bcad524f8aac7f909fbb884e879caa735f80fe9890d7874be919ee727beb2a074984c047dac1d02b8712afa3a0000000000000000000000000000000012458946f1e853a7a45861a92d4ce707e5aebbf69edfe69190c0bb130141a10869e2a73e06785b568248d5b1f647e63e0000000000000000000000000000000004d8061f25edb5a510a2db9e1df850518156138c78ace50f4c9ce47734a0b14352f5283083a232602a070c3ce94c7bd93d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb90000000000000000000000000000000000baa40ea518227b007b9714ae6eb5a4e92883dc75e6328caa780bb2ffee7573dcd7e9ac47821ac449187569986bd2980000000000000000000000000000000009d43d61f070ae308c5c285915600dd9c17b7de63cfeee6fe33c9ba857b3c72e057bcb4d4ac2b492797e7d785997c18800000000000000000000000000000000185215a7fefb96b3ff9229cc3239c3ce5202a97e275ea9b1541d7bc0a2931d7e3b01942febb45c6e96e66e3605744afa00000000000000000000000000000000103ae58b8066dd62c46c14c593c768fda91b90e4840b5560c974ce69b86bd6d2c13f689b72cf9619e57c9dc8e3d3fb15e71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000075585f862c0e0e031efe12f31e159f2a8b89825ce80fdf65906474f0155f397fdc666292f6a7384cab790f071335c49000000000000000000000000000000000eba3d37a5cae738ab99ed9475c2c7fbb88ff54edb8490017162dbb16c8225102158a266fd4ba7570ee6d5ff6cf3f5d400000000000000000000000000000000135a0b0a38c036919f8389eb7bdc505a375fd75d513eecf0cac134645d60fb6030a437ac6a0fbbd167b7a77a927b3b0e000000000000000000000000000000001688fabd4ad751598ca036ec5ef6d7b314980dce7d8652163e89fad01b233af64defbcf352743ec478af42587f58177dbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a00000000000000000000000000000000062a74a9ba0e2e8d95fca478be8d18fc716243b1faf7365a55387fd7188021f53bbe780e973e7d16c9db236faff176cc000000000000000000000000000000000f949be3fcf9b38995624570fcc9e7df9964d038eca189336ec39d9e0bd05148ff7df0b48436a2cf6e249e52248ee8a40000000000000000000000000000000007472e7c366419a0cab844522c46356acdf6a12cffae941fae3d3b78e7a83f0446c945bdf7b247abccaeeaafec49026c0000000000000000000000000000000006a564e6860b97feff368fc9a349282112e591a7a6987fd10a2d4de8ae4384ce229b9db9a93445f727eeec55a6fe5a9def0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab7000000000000000000000000000000000c073d2885eb125d3e7db48127178bea2c5bb0f09eec7081f15bc6fe6cba156914fe1b1fea6cf14a21a328d831523ec300000000000000000000000000000000010d93564b2facde13d29dac198c5f5fa314a0398f30c6fb7fc9575bc83d4e97edcc1c1d34f78728729442777718f54600000000000000000000000000000000136a4ffaacf0b4a607c677ed343c1ad41a1eca49c7c48fe73ab2f74084a07cff18f07f54a7f8ea1bfb7fa3667863bdc8000000000000000000000000000000000fb0c007a907ecdff7bfe2242097caf0c5001124d112689a74544fe4fd85be9771632e7267a1cc7e9f66d7e4bb4c954c3ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc000000000000000000000000000000000032bb9f20fdb19f578fac3008396f5dd0a70860f77f8ae7771fc6253569d47b72751cd56bd373dbc5eadf55b99578861000000000000000000000000000000000c4a4bfb5ca6f9c1bd69d7377c6da405afc3128338dfddd9aea19aec5e1e0f547e3febd28445af5e27469c87c4ac15280000000000000000000000000000000003b551547af253d07625028db4b9a8da2a857bc925620c5d561bbcd3e063eb460d9407cd4d4813800551e5d0d23a2ea40000000000000000000000000000000006d5c69a251e9a042c66bd4ee92d4f3cd4e79704b1b215c15b319e09cae0d798eb201be24f407340dbcefcf2cb87da5ae5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a5802e388f7605bbacd0bb65ba96689e223379214fd7a92de9a313f55d66cc71ffc9ab3f9979b75edf55647ad3b6c94000000000000000000000000000000000f86f968b5c20a81f18074803e1ec55ebd73bc87451c48d5bb61604ebae46538dcc9d21cce062abc07b4b9e89c85bf60000000000000000000000000000000000f9fceddfa8fb5bd76fb7c8986372c32ab9fae3c26e9fedae892bb55178fa2f3432e6eab5043496dcebef46b20bf5824000000000000000000000000000000000dcf7a118881aea4e6a0e4e305910d4e4a5f3d0a8800f52659ac26f122bd63c8aa2c5583f1121275adc9af1800a007fc97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb59260000000000000000000000000000000003ee0ba2b1de438abe66769124b97a951ce18aedc8d9ed005628aeebd90efd316e7a3c60cb5a103d6f72e7a40ed8f44000000000000000000000000000000000119597c99a7a16d8d35937ea15539089741363153ef898d6bb177d9a9b6c5bb4b79728155eacc5d82571f398ac6c32a200000000000000000000000000000000116184ac845a28c4f96641ec19a07e1f8326bd45e2106148f40277ae6fcf200d64e326915cf5c927222def8deccd4ff8000000000000000000000000000000000f890258e70b973c0d69492b2e7d10ccb3997798503c0943af4255c13b3856ca4007b18cb9d638d5d9cca71c368cdfccc54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff6735200000000000000000000000000000000171035755bd519af04efdd477d407267c5a8108bd32dd6d3f1b9555f15f37ce7598c096fb5301873809f0c000457a4a2000000000000000000000000000000000bd35595246a8337a426c50c02299f297036f710b0979c7f981c6909e835c0d9556cf64e2676baf952a787e10d604f210000000000000000000000000000000006600ff240aaa026941290f49ae8968e72293ae7c2af0df1b4ebb9373199b95fc91feedd2782ce819440286aeb2388c50000000000000000000000000000000015b2bbffac097c27944143cfb22e38ff8e50e79f2336e64c8496b0b25892834efb18a765e26f1408df1d64f4b9b78fb947ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001064bd04edf96a3c76d2ace669ff72ee5edd87d32592213cb5a6a4a482154c1723bc19c7c530d164c31626dbf758d43f00000000000000000000000000000000176ac06390e3629bdfa282bf825c0bca9bc4e0b8fd90fcf2d4ee456d5bcb3ac2882d8406d2fd59faf10c8327b1962124000000000000000000000000000000000b58fbe4e14ee0af03d9aac4131abfaaba43c7cd92d530802516cb67343b382a6d2af9399d93b43d6e05f7ec827d5ae20000000000000000000000000000000000bfd241e3180cd5ce9de831b24ca50db23685bea7e008be0c6ead11abee338618728968c25a8e5a916cef8aa516667214ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b00000000000000000000000000000000183ccf0ddeb8573923694decc02b8f02162037156a8f6523ed178c13113d094521c3d9257febcfbd8f15acfe3d5d5c27000000000000000000000000000000000cf716097aabb07979ee435cf57ae36a3034283eeec0771bea24c9a1a15ea106201af8606d3fc28ad8ffbea2cf274458000000000000000000000000000000000b962565763c4cc155b2d9ea104e754e5fb4745303240688fee7e2256fbda82dfb515a51096be5ba0b111637b1a25438000000000000000000000000000000000df04aea745b9df2df0e34153269958d3640c1596fdff3fba696801c96371420a3619c5ace9210af7e0de4f408b09a7729b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e204000000000000000000000000000000000e6be3275371e533a676f8d075bb2ab8b0216642ecde13425bce4ffa8ac51cb1b4c5c789d82387f5355c27f18da556400000000000000000000000000000000009fa3a3df5195203f967322cee54a15d1e0096922b6b881bb3bce54587fdb82931c0b87de7a9dd1a21b4389a34d161ba0000000000000000000000000000000014dd5455deaa5ea4f9b5a6241c2e8b2230fabff9e1ac08b359f029f4c7838201cb88a92a5b696ed47819e4866512fff300000000000000000000000000000000181085d630d1e24ebf79bfafa134c08c0e75626dd400ce500392adf4462028bc714ca07b28b8b8f15c9cf2934a299c3092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f0000000000000000000000000000000013b60e3be9d7d43eb42f7cc2c0a7efc81c175b696e82b034c87d1238db2798d9ad6534b86992653d86755b4f00cf989d0000000000000000000000000000000009dbb325624e698c76b9d697e4f7f03e502ae1cd43b49a0957fc067858e20e8c7ede3577f336eeccee58cad53eb727560000000000000000000000000000000007f2f50be2c6fbc500ea347cd14ca195af08b835814ca515d14dd2f6078eb6def2b9475c2ce370780acf394065032d0400000000000000000000000000000000109803d612b9e27be5725f162d061b9428f363493c17eb39c097032039387d96d0939a06466470ab62ff507ff762fba78f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000008a9fcb462412c1065dc7c3623ba5a980e6f86cc813b5d8eca6b1b8a302ee4176cebc233411f2c9ff171332c66a0d46e00000000000000000000000000000000058d2e7ee02bbd4896b5bcaac0f2b09c16d1664209710945c1f7f1a53e24496d7eace99488debb32afe10d7fea442cb800000000000000000000000000000000084d7600bcb68d5e375457078672fa07ba2c87c8ec5f9eb7b61a0232988b197aff052e7125b33c6657729ce8a1c668e2000000000000000000000000000000000a07c42468c7c65fcc984bbfc2f05bf452daf17d57e669ee5992ce67517e1c93b5f7f4c9434d40f3b9bbdb3446ddb982a9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000ec380d15e0efd71958978b1f9298ced4cc3322e472d03830ebbaf2a4601c8371e6bc1cad047b0e1e429ecf6fc628208000000000000000000000000000000000b278fcc53b7527545ae1340c24158ff662683919717c220e7d2838a853fcc84ce3915f105a932872ca7f64b7cf096ba000000000000000000000000000000001520798dcd146c0b39ee727e8276fd998de0157a68587c2fde56cd82a9779b6ffbf745ec151210d1e9143856f24f01d600000000000000000000000000000000175d53b992d750b34f9daa39aec918a0ebb2f539db8057eff1409492c90f79a00f14a4c53445c028bef5d6372c9f80c680b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000fb3863bc7b468f1a0ab0e4701ea392bd820ec5cc2d7d86b58949002f24c972f51f0f82400fadebef13b750884b35f9e0000000000000000000000000000000008fca1b30d4e01991811679f261d11723086753e816239c8c7ebb60ce9ac0ea207011a69cdc29e3336e8f589b71bdfde0000000000000000000000000000000010696ff9d78b48743abdc6c1f4b44b4c960aa516623a24da515206d95e65286e453a8f275d98aaa09fefea29e71b5643000000000000000000000000000000000fb4b5eb18b6f6f8ee7dc734e8bdb625a403dcac6d0cae363e5a7f3a834c8eed5f01fbc4dc752e228c41f3f9d992bbe01b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61", + "Expected": "00000000000000000000000000000000165baa8b143e3734169986e68a848739ca05330786012de260148cfd0810ffd5659210855f19ca92566ea0d6c48086ec000000000000000000000000000000001225672112e0476418288f381165292a9aabd009b0d9e44d9f8f00469b2c56698f5f985ab6292c9dbcf73bcf610080a20000000000000000000000000000000005418cba24a43fc7edaf2fe77422a0b2e8b38a45415e13654c6176c8f7cf6bb2b80401534154cd3b23e977af589eda9e00000000000000000000000000000000067126ad59105621cb0931ab8f386570b54977563ffd69c2231c56e7961f6df2c5d7b114e0b1ea176cbfc1d657127286", + "Name": "matter_g2_multiexp_53", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000139cfd67c3365c5b4422063d7901108c9f33e233bf6413ba2e5b2ad62d188cb50dbd3dac0f298aef7c1d621249d4b0c50000000000000000000000000000000012fcc0d5d09cb3d86895f76ac3d3e9fa9b2495110b0276e7a039d7d2fc2e48fee646fe331c1d8e6f019898ddb43dd09b00000000000000000000000000000000159356eb3ed0d4f146dc929aa6c77057be5ffbb064432d3fc35d346f19f6c1f8552c7079e27f3188bcf29941375e62c9000000000000000000000000000000000fbd4e9a57aaaec40ef9bce8b76b529bd2261d373f05fd69af58d1f23c089497473e44e937b2617a92942af1a99d031f10e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f66318500000000000000000000000000000000022ce2d2bee57f7567e9b52ae8e913c79e3b2dad381802ccad317b525be0b503bdfa92722eb0c21fdaa31fce2421ae300000000000000000000000000000000001177074350288dff9dd85dbee758fee1400cebc173793198a96c0be3bb810d352720e94b9bdcc6f5a8951b3a86b2a0e000000000000000000000000000000000179e21de58ff76427f5ed7c8ca3058d0e5e81e436280aecc75a3d989d1cf11d41734de22bda74cf0dff175ac789532b0000000000000000000000000000000016abe94a49f071fcd5e24b5f3a837fe3fe7c7dc53416f59d0469d71f144f71ade4569bac3aaa202a8479c794bd251645cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000018dee638031a3c9b1198cd4a4f267cdd66849e2b80e3d670897d9e058bbe772936d827eaa4e78283d42ecd25eb4b22e200000000000000000000000000000000009c04ef31cfda7c086a31341434a1698c1132fb5916d359a523b98d05d57bb38a1e2e2bb779d4762f9d4ec24fbf2564000000000000000000000000000000000a788450652e0bfae66889c66b0dab8d1972a626facb690f8e4ebfefc7e1a7b2b58f6eed02c1f10a74b140a49b6c5de50000000000000000000000000000000009e48b52f2b0548dab1c0d260144ad2e66a22e0f1781f94071b5a3a08311d11dcad6963b4339fa63bd82b4ff0dabe685290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e8000000000000000000000000000000001319607058637d4b796020cca79d62af5862b1c186f32d99c0ff53a830888f297ac4389582f9fd010534d824522e6fe3000000000000000000000000000000000608ca0b4806f17b59a805a3f9f75e7a33ac0791e05050d4eb19f2d4c845fa4e4c738c3309e24a4524b6bfe716949ab5000000000000000000000000000000000a6a6201ec077e113995acb81d4d07d0c4a085d367ed740d26c4a0c04ddf28697c1cf5e648b25148888617ba77ced5e50000000000000000000000000000000003eaff54800dfc8eb3ce647ec4ae8c1aab6a87d4853a1ea061a5e6367d8ebc94243837d4752a1933f7eee0ec1ffe68c8b71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff5700000000000000000000000000000000122822c91bfc4f761b65f4066a94c0eb1f53133a1355c019f04003e84edc5095523b2ce87ff24bb42425ce979743ce31000000000000000000000000000000001928bc315800ae9936e5b763bf29b19a9aeb71268cb47706494598e0ea057f9dbdda6733d9ea165acade87bd89b3ec12000000000000000000000000000000000a87c1ee17bcd7d348ed1a5022bbc7438bfad06172584dd8e3b51db4b3b09645290382ba991df37db0ce562c950c0e6600000000000000000000000000000000127c80da591c3ff8d300bbdbe27e0aa21b5edc1c1fd8a5da27f58a4dec3971b3c4f9631bde244a7072d9c19f1c0a46be250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000004bcd0a0321c3c7e6161cd53254353905c27d965f57c9783c3fa7cd5c55a5820116415ce45491d5d1ccef6017ea4608c0000000000000000000000000000000013a30e19c43a1f466c0c3ebb5cf1b57c44434892b18a7fde18a2a29b09a5b4d13d26cef871d689d9855a73a43d22119a00000000000000000000000000000000066d6b3c9a949049413300ec0398d605277911d7be327b1d816cf25543d1b2d7c31d912f426021e612b56ca288b462450000000000000000000000000000000008549f4dfdf018073cc4e32ac930397659ae7a59ef42ca4f864b26e4635c2b7669186a107e9e91c35f04674d2be46051ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000015900fb486bd2c066cea98e51d30424681fc3347a1cfaeeab65989d1adba104a362837bee51b8b953ebb520feb49aa6c00000000000000000000000000000000198ccab1f94fa910f755936e357a92d358e00cf406894b46adcfc301918c4fd7cf7200a1ea515343d577d920680c83640000000000000000000000000000000018d9380a8568adb92f8f9f67c315f2a837d542b32aa82d9bbf5db6dfea27260738bd0a03683a9988c6c3370563e7bb8f000000000000000000000000000000000528ad42f23c4e21a687f2303f495e962b0a90713d6ef3abbdce38ed166ffea9c132e50c5b002b2ddbbd4933e9a1aedf6ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000017eb2587aef34b03943a170d91d99aa16ceb2a36df3068663382ff4c135083c998743f9145a2fd5dd4ce3bb8b64cf3fe000000000000000000000000000000001256fb29c7482e5469d64183e3e848e5bf32f9c495cc495c3f8cd8e46f71c3f9880f875cfe429677615a6803f849952500000000000000000000000000000000146e2f329f86ddf5b0b17c37aa2905122f457c2c812782bdc15e132468af48c49b715e3080da504d59414ceb367596f100000000000000000000000000000000022a8e385972592430e76bd952a700df8d35b32deaf06c60173d0048d6ea22dad95cc62300bc1a60c6452c41b32b504a1271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000009c80b3191783d235814fc86653bf2f9a32cb7938111408087b6ab5bafc480583e7a2a32c6bee0ee4aa867ad5dbbf77a000000000000000000000000000000000a09af60eed6c47a6c2615cbfe62025530b35727b42fd812032671ca1eece6694aaae259b05906faf7fbb54362ea890900000000000000000000000000000000055c5f0818f41e5d73e8cd5f70fa77cf477cad8dca2a88b8970a3a25c8f38382268e439642518f1974c5b470cbf29699000000000000000000000000000000000834e44669043aed8ad47cccaaa7476ad830e38fc1def66aa7e8207e889ac0fa1a931eb1e90aa6e1cd694bb95056c3e63ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd000000000000000000000000000000000190f8da34caaf472ea9b0f41851f808bba402b9be4baa5d02d1bcb2f66acc3172abe78a49a653cd24dea402dfb972f670000000000000000000000000000000019931343d0e59f0f0a060bcbbeea92fc4670db510c017fd94e0650ace68c2925c627f373d8e755813c199b79c70369f20000000000000000000000000000000013ee811cbc036d2786d8ec0339627d6134b10517c8858f6c6db19a9319636459ebaa217649825ffba32a224175267de90000000000000000000000000000000011039d587f3323ea9d3c50027c427fbcbbf7e097533d8a5f7a61520f3eb548c399e401df0f51884395ad6a338c0a3500c6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000011d5791e9bc632eb63bff86aa433e6df463a84570b779c913f67e77fcfefb6af48f3df2174096a511ac35eff64e0e5f3000000000000000000000000000000000282716505907931bc93748ba1729777b959d65aec5a78c9f829ae6f2a94a022116715a8c2a653a832a62625473a0cd1000000000000000000000000000000000f694a16ce7a69f0261a0ae19478003dcb61bf93a2ff39f940fc4718a38b9f4b6ab13527c5b438d22499ba29c0b5461700000000000000000000000000000000031eab53440757e4065804896e9e811d459665598546796d67472054fa60e5da8685d8e847eae342e44730056757c6287275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c61500000000000000000000000000000000138862ee422bc0f38ce3e27ed3c1b71f71a03d61cc474d989b0cc824efc512ef173ef17bbfb2090997eb9435f4d23e0d000000000000000000000000000000000fabf1fac2ffa25d9c8cbd49b3db5dfdbee52adb947ebc1a3423c9fa2f9d3d29329b60ce0c1c739c7fc6d5a5d3b9e96400000000000000000000000000000000090d92e8763d4df49b8121a50affcecfcd632923b5fede480a3ee79128781f3f49b592d8f65d30adfc75d8a1922c41b0000000000000000000000000000000000074456b341565b13ee3862bd87b72f9d01754c7715751738c5b33ee85e3d8a6f731d7292bb485b5fb59bbf3ddf9b0d0dbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000007b85110889fed72b3654a8632625835cc041ff0a827f3e1b86c090d816d98cb3b4be66b6e573b3dc05b1998f2772f0e00000000000000000000000000000000160524507679ee021f4307e5a9fdaf01459cbb9a3fb9dc8be5599431e2a8bef38bf8a05d601580085da503dfcf57aab7000000000000000000000000000000000f98e2e7ae9cef2b1d954b7f26fa1755258112c496605c3c77408786d4b210e51c76f10870f558296993e0ddcec3d76e00000000000000000000000000000000068841825f5f5d8f622c1d43bfe090d11c6996688589c3d644ff5da47b94c0638128878d51dcf6d43637781f0ab21a68ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf0000000000000000000000000000000013ec97016dc3d6a3cf41edcc18f88f58b1b88cb2616bc2a8f96af3e7774ec1aaefe86a86135a20ab7592c874a33a8e1b000000000000000000000000000000000021dc7e4be6462d64ba6c09c2d326ca0164305dbf5ca1981f265a1e50f1a646748ce66ae07297230325937faf60709e00000000000000000000000000000000121bda2855503ef11b043301cf331a0fda6e5914e5ca657890ffba2542d908f8fb02c2c93cb4ac4fe5bb92eea757ca7b000000000000000000000000000000000386fdda56c778a7552dce451a6ade55cd24bf9eaeb837ebef898e2e868d05eb5edfe97bfa8eff8ab7cbfaca3c918910461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000763746ba87e8bb547180b0bf18699ff74f11154a06cd77a76cc9c264db7c48286fc52e3ef2d30ca914cdcc5c4ed46ad0000000000000000000000000000000018037afcabd273413eb4a712f5d1888249dc987a6fdb8befb92c02660604bd11deb33f283b37f88880cf1be2b2e71f1c0000000000000000000000000000000008ecca3d1652be4764720ef13a6ed6164a3ae89d160cc8c2c8c37bcbaa52db0fc0de84fbe2a19b93b8100556fce0fc80000000000000000000000000000000000c5727babfbc5c36c1d57b9f69c5b41823882e0196e9e0a89d5f4380c4257818d90b1fa6d782e774f2424209bf2e6b5fcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d00000000000000000000000000000000142d41630fb9db2f9630e4d5f9c13069242fbcaf1dd02f93224174567c3f944fa02b9791a409d9236d89df6ad785e8ed0000000000000000000000000000000002fb5fa0b3a7cef16e5638f217bb946085fba870836c618a7db9b4394da9144850572daccbff8208f14c8082aaf1ef6f000000000000000000000000000000000a6be9b4a6a9b96d2096eb3a95780f11be1e13bcb6e625517191822403935c52cd40481bce2e782c42b11321cff2cb7f0000000000000000000000000000000019e2d94e35d608a50b5c8b371044f6410dd6c1988ec7a677016d4b52cc3f21b82fbaa7db897f7107d81a177c31f8e52467de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda", + "Expected": "000000000000000000000000000000000a6f3fcd812e3878cccc6967d49b104599fdaa80cb5dee7298c3fdc80477d277f2c68f1c941f6e03441eb176c222a448000000000000000000000000000000000a4007cc5586d677e7945dc8a5872b4839d5b256999166e7fe8efe4d56895f93be4659f43aaf68c6070babb6d3328168000000000000000000000000000000000cef5304a1077c8f31d72e6f1f91ef5a021d8ba64719b4527225b34e615af388d9b1391f65511eac209ff5e86244039f000000000000000000000000000000000c856e7847ea0b4a8334d124417b45a8689d5d9f113b99ebbe3af3f9aae1cefb236d751c40488a861a8f0e0326b42c4c", + "Name": "matter_g2_multiexp_54", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001805009a7fc3d1705936696191c163a07ea992cbb4bec66884a2d58ac3fc0e16b6e0d2292caccb3541f39b7fd6098100000000000000000000000000000000000f3bcfcb0c400d3d06184563204bdee465de167c7d17bea2e2150fe12eb9bc3285f5693b222fcd224181f8d193b7d95f00000000000000000000000000000000028d60b7fc3790aac7f6b3ec32c4be626a2c64c6348fb8a1f39e58ee56b81469e04886ed9be1388958550c02ca9a75b9000000000000000000000000000000000b60ed8052e43e99d3c10a4b97ac3197ee3cc04ad857c5cf4d8ea1df2671084d02fb683f28f5d499910351354d5e6288624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000002ddb1a9a88e3a0697540cb008bceb075e87e2331f6e9b68f8ffec48752d93cfda5fee121155ad2a142c0ec42808fbc200000000000000000000000000000000144b694018840835fa9c50fdf62c2e32261a8350d2ef074dcf7d016af982316a0c6f9e5d15d29d3a54d8d25aac5534940000000000000000000000000000000010a3765089ada75e9eb61328756ab9ca7b8362cf86cc82af3cf43f390a0745954f28da72a6ea4eb904a040596795639100000000000000000000000000000000056b51dbefab453012b35fb6e06af06ee92e4e84e92a9967b379af760fdca4a3f10f938684a646fd70a2188721c92e98edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c23500000000000000000000000000000000169effb324d60b71dc7ba975e3d5f18700b34cb9017f482f64be37c4df01fb66ee9eb5870e43649225c9a88a0d499b890000000000000000000000000000000016c7ad9c5f7b65a9423f642d87621a5192d7548e1099d774a99a34dd4ec9623aa1168b9adab092b3cf450f369bcb627600000000000000000000000000000000123b35bbcd791ce0d00148cdb3d35ba39054a7126ca5ad3351fef1437461379ef639896b271276a9561b46e270f7501400000000000000000000000000000000161fca2deb729fc55f1102fb75ff466319f18510fc66d6cf95a8256118fca618682f00318b0a5297be873a2f7af1915afeb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b00000000000000000000000000000000073ad8c2f713288313185c3b2455ade93d58e70d5df6b8dfaac8eccd990fca6843778fe42cc8aa6f34ee44aefb49397100000000000000000000000000000000012eb9cf288a366adc58d40c9ea5f2cb5dcc5b04108e3822266ff20eed71f56bd74f1a2727f20d55917adf20b6c4d6a1000000000000000000000000000000001463db177fe5c0dcb899797f89da963731dd4e9e8b2eb77b465b98415dc95f6d5569df51bd2b08a13838f4cca4b62fcc0000000000000000000000000000000009c0bbadad98361209f36eb23a9eeff98f6eafc7d5327fddb6bf43898a2be704520a005b84c5b45c6a68bb7c98d65d6dcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000013c63973ce6549ca3dfe8ea8e3bcd6b0bd88f7c73730834d9ffe2076cd4345090d0364d161ae8998af1048d102f22e5d00000000000000000000000000000000060cd24eea4177c9a5c37038d4cb62aeb709218fa8e64b9084e002f53a0c4c411825812c20df282345bc4a6aabfff6a100000000000000000000000000000000106ea864dd52933be02c1a79cbaf6dc81ae9a2d619bb368c4abc36226104f3b74fadfab906e36d4852a6412315223bdd00000000000000000000000000000000192e45153e4942c88bcce76098fa51782a81b53abddb4c07bd79a2391be68858e2d278969b9fe75bc652d02fe4db1a130b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000101ca1625e9d4a51e08f5eb81387b361f6445eb307d9bc92acd29d62735d4e5078b1a9b36b94e4ea0a314703a85ac4cd000000000000000000000000000000000f134c460c6d931396a0aa397558975ee973e642f1c4a32a3d397051fe250daf4215ff5ac4b2863d570c87f0e32c8cb800000000000000000000000000000000008eeb127a38104351298ad77481c32bf51bc5d3910b03da0cc34062dd2a8766adba6891cb9fc579672276666e1242730000000000000000000000000000000010c896ecd4bdc1ce010da81a51dac96409079853635e57e5c3a5733956a5f5a9c3ea6838849e286ce0405dd54d7e32d6730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000be489a1c71246adaa1c1dd6d2ddfae9523fd1d58d00d4f189f56d08632dccc694e63b371db6922a7f3faa05afbf487500000000000000000000000000000000174212b6840a797f0fe9e209b41f55aa5dbf169a2e2ecf05de48c44e608f6cd6d98ff5269e5412defb431caadc8a09c3000000000000000000000000000000000f4501715c0c511703f6236caa82479b3368de430f2c2d95b39193537be0b990fec1ed8e4d94634ee6233cfa359b043d000000000000000000000000000000000f3b4712f95005004d99fd739affc532d2c4c45970316c1a43f76fa9b57f6676c709e8791c276237b92750f5bdc94492ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000003abd36736fec3e8b89863670666365b169d8510090a89007c7ff3a82fc62ed371544013a1444fedc4358e92ceec62470000000000000000000000000000000008229855468fc63f4024938cd6f41c6e6a5653319cb83f38ab7efb9e9d281166261e7c854bfc08f55a0a9ca47e54dd42000000000000000000000000000000000463ccacb341fc5874f6ba2d44efb5cd24e9409b2ce7f43e9d39466288dc833a45988261f45d34332f416a68c5d10ce80000000000000000000000000000000002baa086177394203a04ce1b46415983399e60986531967b690b1a13cf8ae039b56f0a00bf9aff357d51ac57f8fac8b282b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000000b25a203268100df0510e4155c594a144dbdefbb0ac95e02bb4b3799aee4e738ef4c52f03c6937cdfa7275c28f130778000000000000000000000000000000000c432347a2534e86e90ca346a7b8b40f45075727847fa3ae2f2e297baa14aca88ac6e08342f0d248a92e2c272841fddf00000000000000000000000000000000057ec8099e1e30329762ccf0641b45e1a226f7b66b80644fd551d6fb1f2136afb8e8ab5c6905ffc7c24e67d7f21863e4000000000000000000000000000000000a9e472aa993bea05961affd6782efe8f50d746928efb8fbd328fb50a254db861c90db8df7faa7da8266ceb47fa1a13a2396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc00000000000000000000000000000000025484652f18e2b32e2bbe79916c8bad42902db5528fc45993e04daeca008f3c2ff38fe4b48c292f70a7dc57654233400000000000000000000000000000000008e403f472b60a6046fd190544a1d6b249dc97cbd8641c62613f4de0e0fa9f5456d843ece4ac2b9f4ffa2c0278e61829000000000000000000000000000000000824e0b9b03198597fa54252b3df9690df678e9c6d82301848939dc55ab25a7751bcc2b99786cd31960ee7030bf68ac80000000000000000000000000000000018d1d8c7f2b20f0ba66db616322e48ac8f1d6f4205f228ee8ee6cd13d1f64be9af338c11f511859baabea3e15d165fc09a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000002845c4255819ec6e97abddf4c9db7d91658dd1d55328ab0565144b377e20ca0743d93fddf68acc985ceb7f7431e30b0000000000000000000000000000000001577a5691f2425e65ffd59071c2bb167ad05a8fe23c11c7f7464764442ebb2f7a75a8d02594d4426c1ff022f7a6e19360000000000000000000000000000000012c6ffefcd3964362f1373348404d04d1849e98ffbef7b5ed5704d74b9550869e30a4df26e74b5304b85c7503f7487f1000000000000000000000000000000000faf3dc42113f27ac27aae36725221d04fb1ab46b59e16277be0758b8fad706fa237c0c7627771d8e8d3ad610f63619bf20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb00000000000000000000000000000000044de166200ec06bcb88720e57b84cd8f9534d1fe303a26aca08cc35104ffd7e81a6473c08b28037118dd8a61d090e910000000000000000000000000000000000f4325ebaafc67945de2418c81f5da92da4e67866ab5965eff0f392cc527fc34ba4e7e16b91c26aa370b27eb6a07f6b000000000000000000000000000000000e1d77ccc1c196cf1cdf0dabbee4829d56e937372e9f5613e261ca07e19b3fcf10f7a45c490b98b5a64b955eab5c4f2a0000000000000000000000000000000004ba2e81f901b0da1ead004c76d43278d372456c0c0a8c6752597823d44994177734ed3f355aaa22f325ea36b7c9eba1f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000000c115e270ffd6f2cb9bbb2a62e04c3bf7be9d7db783d292bed272c297773b39e9e51c75e5c79a6606ff7d0bb9ddd040a000000000000000000000000000000000a57b637126b16b23bdaa6a7cf2346f33778cebdc0c9943eb2985ba5c4114674cd596ecdb6959791139c36c22148ab8300000000000000000000000000000000177c7ed16c29d99d3d98c6facca9cb5ffe72e6aa63959dbb51d9382f0fa49b02a1652a398eb223e093516ebf134448c4000000000000000000000000000000000d6bd518678828f582fbb3b1bef725e66f442c4d3e6325fa571e13db492300d03c0188399a2ef9d5687a76e647873c0f52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e00000000000000000000000000000000085d4f90336987f99d250067c2331e7de8f09a80d71fef0570ecfd99e409c1f405058bd3461c9f8ac5ccda406db89bca0000000000000000000000000000000015f310660ca6a0c06b458d0b840a5c1c476d5175d9ff6dce6334466d363d319939572a2b00662247be1ed0f4e6676f8b0000000000000000000000000000000011e9352c0f81bd3857806db678bceb2150848f2224ddfc43fb0c733f0689ab4fffde50d5ce04d54055d27d7702e5d2d40000000000000000000000000000000005d835d04dcf4199130d6a16e86cb97f4ccff58c496594b83524dcd88f5570212f06b744379288f2a737c7a82e897cedd32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f000000000000000000000000000000000f7c4e621c37bd3068a972b9d4211abf9026e438ac7f8cb341516f7e6aa4d8bfb3536389e9155029ce9e8d5d376eec1c0000000000000000000000000000000012a46cab2624797513f2acaefa26fb22c4bf29188881690c350593fd1949cbc243c9d1d7d27d9d76aaccd347359a45660000000000000000000000000000000002dc383d4f9b75907f74bace1769bb5bb1b27a597c9548310f2b5f90098596fcce6b5fe0c72bc8be9037fbf31050d74e000000000000000000000000000000001900deff7ddc62ac302c941e1d2a28a4bd2351edd7700042ea4c4a48145ef91688666d8d7de503913ea259f0b58809f21f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad26940000000000000000000000000000000018ac8048d58f7b1a9407d3101824e3640eb20633f8ffdcc97d43d1b25329a2a1e91added42801c03635ec904e627eb690000000000000000000000000000000000b499fbdbe2ed41dfd6c454796e1ba57021f355a4de8f60964c78dc685e2ffe9c90f5a1f6c9677514ae4a9c95c8d6450000000000000000000000000000000009d10e5e2bb69ea6fd820778f75a2a60627802a49128c3f999d8c1cc2ba56ed18acef354a2e06fbbdfa7e7a4ade7529a00000000000000000000000000000000082839d66a18763656c2ef7196a1d83bd162e1f109b54c5a6095cc7c436e8a4888c4001696958270f54f61b81b00b32d83052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692", + "Expected": "000000000000000000000000000000001463ac5e269d286961036db48ae33fb868a28b0dd828c3a66592ff9dc115303bdf3ab78a8e1f5df68ed1f3b4c6c3f2440000000000000000000000000000000012c64ca0ac10ab616fc733f75fe6181814e9c204f9e4eb79487ba49e3a9746b9b7916a1d768f2ec573a4c4e226365f48000000000000000000000000000000000a06b5b745dd92adbe1f4cf30c79ce0c48428b3e3b05af1585c4ca12eb2e763ffff46b55a060913e1f77fc9b0b085c9f0000000000000000000000000000000006271931ce9c8b9cabdc932297f3c87128a5af25a9f77e71ea4e588f1e88686638e89a8e212c92f6472692be2e05fa5e", + "Name": "matter_g2_multiexp_55", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000001bf5b74c1ac89d4bab4663943e19128619731e315d2d7b39675f7c43493b338020190a72cc7a6edf0b8838886a7fe6000000000000000000000000000000000713f413bab7919cd57c2de3349394121d6bace3c10df0e41a0ab895433d225b05cdb1587deb93ae6e56ec26a29c39f4000000000000000000000000000000000dfb11c9c0bab7e4d1ee39941d5f6b932ab473567be2329c94bb0b146c46fc1c2cda25dbef8ff9b0066bd4ca3b6da67a0000000000000000000000000000000014e399169243bd619be7f2120b2cae5d19b2f04185aebc7d948007c4d3345a9f45249273b6290c2e86448648868ac552c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c7052000000000000000000000000000000000a67bca1f8a0b386b2a67158e80262f025b225535a294394584118f9a701e31e91b2c7eb8fc7e28538966b967c139dc400000000000000000000000000000000185e8aaeec9b9abb9f0d6f34e2480e9abc30208eb1c6e023d4986d544b356a387c323c9edb5c52f5a2f0bd59cca7df98000000000000000000000000000000001877ce1ca6e8b30df86de688d950755f2708fd6f933c07ae45fad1b3e43337f1a8454ca5d2a80940e8fee98fffe953a700000000000000000000000000000000117a0ac9d27292f967ff5bff2ebed5d2ddd9f453d6aeadd9106eb52b53447974561b621fdc1d973c055f1cdf824c367bccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c00000000000000000000000000000000009fc4e9e816ff495dbfd4f745106fc90c023d95bc64b809801d02dc7cead905177ede5016f537243660e4b7f54a02ea200000000000000000000000000000000180aceb6e9851a11a1e34502897299e7db3e09f4970337612634fd9848d1de2bb3de8ede690ca051a75add5810ff777600000000000000000000000000000000199f3c43d429fe8f73e20f81ea00c4e78294eaaa29fd67563664381db3cee2186b387b880089cf96fb99c2e22c95449d00000000000000000000000000000000040b20ec4e685f104be188d0f15a79f27cf34dd01f813275f6019a9ffac56e234b6c967c80745294d9fa46e0083cdd907ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c3963100000000000000000000000000000000189fba635109ca215bf3a09c3e44ad65f7eaf653e0929aed39042e3b9c8b1132c5fe7cfafddfdd0646514aa1f9e7e1c0000000000000000000000000000000000c28f598c80ac262ec7a0e0d1c867e01ef26f182c5df9ea7f88fdf8bcf3a5d2f06128526b1ce72cead8ab4286a0b8d030000000000000000000000000000000008051be3328df43b79dc9040ef0a0263d474acc0edc023f300cdf7c13088d1bb21b5f37ed81b38dcf8718bf6441605f8000000000000000000000000000000000d2d474723c6c246dc59e683be147b1a6bd6e7d3cf12aff7b636802a99954e7a13c9ea429b19833a985ca5649b1a998f6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf01840397800000000000000000000000000000000058bf424fd68aac77c42a046f78a55729e6b5b3fcaf436d0d98354b426a95904b55cdffdd9a8892c9f56f170ca8811a600000000000000000000000000000000142c1ded08928fd155b89bcfaf9c8194f4569b4cdeb3bc7286f4dd79e822f5db497768220533b71be8c71d121e557020000000000000000000000000000000000a9c753686534bfcc295eba0a617f86d7f9e78d3fe6d52f26cede97a5b1f107210a757a2d89361645856b7b20e89185a000000000000000000000000000000000f745541841cc4b5352f659c2b7cfa8d51b07f91b0cb8c787b4492bb4b94ea27117695416e2806e57c38d7e565b9eac67fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f0000000000000000000000000000000004445d4464b51d6b12f164a49ee3b610f11738d60cfa6e02f8c33b168d9d5db90e6cc558cd12c56069571567d91183a30000000000000000000000000000000009e4b96c2b533a16803a36f8d1f179313b7adbe6c4b90716855474ffb2fbe087df3fc0b4ef14cda7d958efc5c92574ac00000000000000000000000000000000104dff7c859eec61a0ff8e0d831bf9667226d5bdbe298400b4f9e3159a64b1bbc7cb9f4ff9604e3ced40bb0de0455ce300000000000000000000000000000000134bc2461459ed6f0d96aca02b62e3110c2009e1ba7d3258656e9cf97c2a1685faf1f61733ce6ac3af7ef4d73d0b43b1dd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b00000000000000000000000000000000104e5709f8edd71f50eac1770ff1c2b21f5ee8cf5a310fd1201109d1b73cab69913bcfa2d27a8ba16d974e9841586ebd0000000000000000000000000000000003a4bedc6277c61825f6ea1f438c058a1afd494c384689a8479195646888eecc7953b8b8aec849fb5f19a20071261336000000000000000000000000000000000856ee8eafb9b3d25fde7e38da4acec624d1444337b87b0b1a660bf497ff37929b1ef9aed8e1fb0ffc6cacd8f0d1a1a00000000000000000000000000000000011b52192c88264df56de3d7b14372443e25183bb816ea1c0346f15a1f324527ef8531e27aac3112e2a497a0eff0d5485b4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000a696c83010719161b6624aa7756e6e84980518416554ac045a93b63c2561a68ca2ff2fd5b6d2d667822ae4e3b3a2ba2000000000000000000000000000000000fb8fdab4f177b0dee52bb5ba615b1d548130deb87b14d05d427984ec148a7a94efc4674804b3660d0f7aae2b49f7b1e0000000000000000000000000000000004914c0359c8e23a7e431e517cb83e5735cb2876e8b53ad45abf1e9eda06e736378ce03ff75002374d47f1bd45b08e8900000000000000000000000000000000139abe340c2d773cc45cfc75c47ff31b2dcdce27ada3e6d6c0823f37e4e693ca30342fe41eb96dde464d14668eb72c5e457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd100000000000000000000000000000000180345fc01e3fa349c45b1a7fdccde5f9ee70d7d65510e8b4bce654f2541fae7641ad86f9bbc1f02e93e94422433f8b40000000000000000000000000000000006cfe7026cd423be189c5ade8de197aecbc9aefd4cdbbd2aeacda816247ad59ae06a5c49b0e29bf1140f400d46845191000000000000000000000000000000000cc4f240a317ae9ce75b44fae87c92fe9b6de10e1191cdebdcc37ac200957683849d8a957216676db1af51fa0a2a1136000000000000000000000000000000000ba84d595661e5d9bdf9d268a3cc575fbb6b0d469b58b3e43f80694c78f4e9e501c4a4f9c42ee4518ed7189a1c36ca0c19f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000fdf7e2372b01b5d926a18ddd06b4573248c02d7debf944312dc06f76ba08a7be460c451d296b71e9e81cf0956b974b80000000000000000000000000000000018326d0e1bfb4a62ab6f772b47ed7188035a62141e6b2eccf53a299028902a172771e8e46c0b1ac4833ab12045922b3600000000000000000000000000000000072107574145c6afdfc7d618f2dba2b8bb01d92007dafd476e4ca62e6053e5e9f2e34243ec2dd16ffdbe3488b925a0f000000000000000000000000000000000070e8491a835ae96087013b0f8da267a7ca5b0a600d71b8c76fee35f41d8b5c1ad82c5170b0e8d1cacfc7b7b13938e96f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000018f4da37ff63f66d68c875def8c758d9a5adcdc408f0c12b3a60ee4a285e6702b1d5b9326c61f443dc71ae83c7bd21e80000000000000000000000000000000013a665e430141cff62c25577798473a645d20321490bae7689de6ea223a434c7d3b16ad004b24a82e2c62879b2408cf90000000000000000000000000000000011b0108562f53bd47d9f8ada54166854bf758ef3769ca1c3b7b006fec8707107fef0b6c7e59feb727646b74c27ec699600000000000000000000000000000000028799b52107d8965066e2f629b30c0edb490a0f4d0b6cdfff89a9f7763afbe6217bd42c2059042397b6c0443465fdc050aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000c42cb42e389f32926ef09584516249ae332641b573ed29bc0884feda08d35c1bdc6c3d4a69fa15105de95010c6cc24600000000000000000000000000000000006c57fbf93c7959c562e0f3ef59966c1640c706fd18a6b539dfd711b0ad79643642038954bc866d42d1c04be375b95a00000000000000000000000000000000039ca3ad23b71693e02af36a4abe6ccd0dd4f4aa709f74d900b9fd015a2eaed55bdc2bc0749c995783a7615971e8a1f50000000000000000000000000000000009a08596b29da34466c8a7f46b805f1b6f2e48bbba614d728562981d3d4884de9a3c1980d398eadcf69e90c851d48526d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad43400000000000000000000000000000000123dde5bb9b7ca11da9e08a9489cf07d147492be8041a5ad0b70715147e21d6017a58af23c47d77885a7830cfbbe5e0d0000000000000000000000000000000001527cec3c393d03e74ee8a7b1d6a8b6398945cd284b59a93fade9839863f0af591c287e89b3b45e6048f2f9b518208e0000000000000000000000000000000017ac3a2d9458bbd5f38d584b0fe4b35f3a452e22161564a7582465d2068b3ba4dc5e1e24a996596b1fb553d641996a4e000000000000000000000000000000000ee5ed5610a78dee181750e35a8ab91c001446f04124930c2ed85de74c6167009af45a6cbc3c59c4915334d7853ee12f85c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae0000000000000000000000000000000014d60c1d436e4486f35ec85bf2655ba6b752a36c86fd9088c0ce46363e75abd636052f876986fa0f4a59152998c0e4a800000000000000000000000000000000083328e38373f1de1049deaba78f568db818b1dc38d981ae92b968134d369ccc399bc3bd55c841755beb484cbbd60f4b000000000000000000000000000000001788850a5508d81df9af1f087356bf8e63b3c8a4e209403c4de7b3adda07684a08f9de6f1f8fd8dd4b2bb9b75be329cf000000000000000000000000000000001506a37d222173f0098f56b7c443e04ffe08b376e1563344e7bf22b1c9df0a1292f70ba51cbe554843fb93a7f535a4aabacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000d5f153952defdea9309269bc996a7714deab12e7644f8f8344140fe53034de538aae6c3af7b06687684edcd2c5dd19e0000000000000000000000000000000002da67345153c87ca65012b8703acbe777900953abaedca4770fd893275948d150ca3d6694d58bbbc9e62904448a8d2c0000000000000000000000000000000006e8c95d22f01fd9d56178d754f0892f46166282a27e6b02826478cd39119636e811c03fd835c714a59bd2f7da5ce5e1000000000000000000000000000000000b5ab6233d8dff50648d89cd65793640c06ea784d00aff329e882ae04fb466506cce3fb6c381b4eacef8b5305953f7b6473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae000000000000000000000000000000000595edc440a5c94506a79f3b3fee818256d7c4185be40c1953b46765b2f925ed16a476b07a267570c727592dfc4a0d8d00000000000000000000000000000000079ad05473fca57f26fd068ed659e4aa4919847dd96e683e7d4b3a731cc9ae0562a693abeea4fd550e644b43b553118500000000000000000000000000000000176a9751dbfe727a442797551254cf904862c4d590892e019a54b72f6a5a124d268777b82e19d557690ccfb81cbe949d00000000000000000000000000000000164ab74c150cd151b70fdd7d63d0404214fc9cdafba3bc642aa798b1c301c287ff6d05ee7b3a3ce997072b8189d54aa62774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a", + "Expected": "0000000000000000000000000000000017d73e29f1d555a10272043ac0900e80883c185ff7d087ee7f5a3b762213e658a42d1b4fdd435d1acb9d5587fa7e8243000000000000000000000000000000000ddc440795d0e4308577fe8439d43418641538711972c9744dfc8a4c206c193aa17958404bc387c7c2fa30bc678937f7000000000000000000000000000000000d7e43c0f99adcb02db99974e7615b4ca0de72117792ea515bb04c4bc8680a3fdb0afcf6a3bdfe16bf54c1d7336aa185000000000000000000000000000000000bcec1d7fc9f2210be80e90631810987801fdf60890ce197db041b6a62682fd7e181c6110956c5f5e9c196049e39100f", + "Name": "matter_g2_multiexp_56", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015425cbb7075a97dfa9409d7b014127396056dee6d4bb63ea285309fd91280fb691f9cb9572b544b332324f6cb3b1276000000000000000000000000000000000c5b9634e6748d5819396051322d9b7e0377554613a7fd8dc0c71cfb7886dc0ac29add7265af84087a9df5ae3799ae30000000000000000000000000000000000534226ad7324ed5600b5438b659c7b1e96f27ee1d77163f2d3073418f7ded5c613ca4b1a686764ecc43ce3388e0c32600000000000000000000000000000000198267e2bd474dc0415f47f5c87a11fe0945a91cc0bfc37d504ac53f9b9b0d087cd5dbc9b03972be03d4b3f9d2123945d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f02667214000000000000000000000000000000000389a084d95445af6e0afaef21d3676794e45986b9520035111ccdbac4ddc1b23974a686a900616f878f3a06eec90db500000000000000000000000000000000064c75d1129753b5f399c1a5166a0f6a8f427d65ec2fd84d0c7339218e0a396681797bab68b33653ffb9820a6005fa7500000000000000000000000000000000147e199e8c08b9af38cb457b623d0fff32242b11e695f2adc0f136c5596db313b03c2466fb58e37c94704152e5c8f9dd000000000000000000000000000000000e8fe5436baf3470a19891b85d15486d1269e1b13098d837b0a510e71b0e6260700ea85f0bc6476217cc73615370cf003e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000a39a2032858a57ccbfb940741f4ae21b318a56d5567cc0088ed52dddf1e0d5de60bd2da9b675212a9a28ec17fca7c0600000000000000000000000000000000039e2a4bb1b417f8a94b02cad60a3e1c4c4bc5a86a23def7cfaecbfd97d89a5104e0cd13870c9fbd010dfec3ad9b1df9000000000000000000000000000000000bc29c5623f9f18ec2af5bc651a65d89554705a349923ee15a9bfb82c114246b404a1dc1c24d65c8749e7c9cf62d963a0000000000000000000000000000000001496d76f7b8583a64c1627151589af876a2f5e7677611ea15f14606538f6052c56e9fc3ed145c313acea69a51547fb6145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f0000000000000000000000000000000015f83b2f998691e504aa740b4db38f5b0236ece3bc1ca933b79999d55b737bfec51e590c2127d57625a9b7c2960c06280000000000000000000000000000000001b7b117f5d722e320b7e90307ac1423aec5e30c29602d314bac9e5272ad3990d31999bf3f516ac78b2be0e16c0375d8000000000000000000000000000000000fa7992cd7fb679eb5f9f9a9febe9c3cf41a717c8f6fffbab5748572098407174f09457e13468165f1c7275d52f6c84b000000000000000000000000000000000737e95f62aacd12f8aebc288c5cfe052f34c4d16e7b44df4497d9a713b77485fb0efc09aef11c7b86eec4d0cfd9b03ecf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed000000000000000000000000000000000004d48d72ad4e77954ec6a5a62299f0472bc52b556cf3857019f8efdd694758f13029f9d6832ed672cc210f32033da8d0000000000000000000000000000000009b2394755d0319741d131b012ba0ece7e2044def20ae73fe73bcc276af9d807ad75be79202963f9a5c512a6ca53197800000000000000000000000000000000128f856fc4790d9fa68cd2a3c152d675453dd81dd64f0ab084c6dabce456f78c2bab0e7f315439b34f86e8fa61a33ffd00000000000000000000000000000000173dbb908ed617ffffb6aeb212cfe6c03f7ee51c84134fde67de2ad9561a897e28a0efa66257ae0c21ebcee3fe4fa68cd9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000bb7b84476d4b17f4ada0b6f50d34dfaecd611356862895c8d2fee6707c4aedbf565560d4207e43c179c5cd33cbb739000000000000000000000000000000000112d8b10c775218d318090dfcef55a903953f7466c50417125ec0b2c20a24fb50bd172331c0377d4f47aec99bd87a3fc000000000000000000000000000000000cf4e4b3c600053f45f350c8860e47621f50f3849872a91ab115f71a2b04657991217e2f0844b296d3a6bc33ee66e6a80000000000000000000000000000000008f625da164bc9d96be3e78df63bd1633a2951dbea0b98e359c6317abe6ac5799c4bb00bbc2c5d02048539e753019a6241733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced1575867000000000000000000000000000000000028db057ab9421eefd1fd481c91153b5c1ceb0f2dacb0097298cac986f036572c6ab0c8709325b3bc25bd494bb46c55400000000000000000000000000000000024be09301c9be4f726fbf7796e8336c50897e8534614c25f65c37bcfc6e724d530c2782bf483668fd08e91ad09484af00000000000000000000000000000000037bfdaa11660111ce0a9c3e18b5da74c004cb44882b1aea4173e18d3a17f04fefa3b319afaf4af9dbf3d4b9ddb2c3a00000000000000000000000000000000008f2138bf621237a286229fe762968a224358b030f6c20db58043c13727b516097b42d47781bd0f0df2b155197ca3946b21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000b4e2b058d6e77cf95be093375233e5c9c8ee0cb2a3aa93172c08faea111df81b9721a506180b7b45bdde4b58b0b7368000000000000000000000000000000000f7025cc33424a7c11eef47baef888535d938d50c0f40eb83ae86791834770e5dd95b30aebdd2c13eda3447d5730ce3b00000000000000000000000000000000088270ef05480ef8aac5c284358d8e06c3482c26279734b8513000019924cefeb396ae79f5d9bd863bdd9b22e3ac3c54000000000000000000000000000000000df75afafb138fb06bfd905c87035bc5d18c45a29267c3965131083d7e0112e10556d7693d424172a53e8d3120f0cf2aeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000122fdd3c83c01c7cbe71f54d783181860e7dcf8406e3966e910f4d0ccddae3a245d6b1f94b1182d1917fd63960cd75d400000000000000000000000000000000043592e5797cc1409d6d42dacad628448799b24320acbda83f6ea9d232968efd021058f540e3bd73a7f95761efbb5fc400000000000000000000000000000000025b5a8577ec1064b5c557415a50e84c2302df97eb65860f979e5b1e261f47c0f305461681beb07e521cf03f0e21fd030000000000000000000000000000000017e86f3ffe72bcb71d46661a1537918d52e886e362d78ed756140a6b5083a4eebb5280b9eeb8a25251dec43a5cf509b13d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000122f26b4561d1f79a70bd0e401f25d50891c0fa0320579ef21aeed7c191fe1c75403a09260c3872cf74b798eb1587ebe00000000000000000000000000000000039a261d9f48b9eab6e89046f333ac328cea287993166057e9b99fa8a7d7eb3e7c34ecbb353b7427b235084f47f45d1100000000000000000000000000000000015d5e297317684bd0169c795d9dcd209452d024ef9a450c41beb0f6c7e6dc5fa0f3ae24c7cf2d7eef97bdc51788188d000000000000000000000000000000001487564f0e9d3e0d2d30ec9930a00f10093e29f2f195344f567960be323ca21231efd8528108dbee4d5ae4de3930ddedf6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda4000000000000000000000000000000001965ac3a520c1ac39b86832ecbe226ae0474b76659076ccbb550a0daf41c40d424ceda084dd991f22cc53779085828430000000000000000000000000000000002e970a4248823049bb4339d21583fdce9540ec103d6e9530b89e39ea875b1c333f7f5f859be39baad34b374055baa770000000000000000000000000000000003460eafb3e54ec03fd5cc1d460e1359b97f5543e6231d61614c1225ab7545fae079ac8e65668b83d022031a7a54746b000000000000000000000000000000000321394863e7c70df3934d874613b7c9d6c331e59a599be593c82edb7a26eff9bee8e4befbf122240d2deb2d527bd38c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e10870000000000000000000000000000000001a5eebe200ec041476457f8585cb4ccdda936cca4977d7701c44e0d4fc5d9c206682a23348013a055117028c16914400000000000000000000000000000000003519bd1dea70245e521988336eb41870599a877380c0a9eb19301f9b2caf963eb559070e23eaeefa4de0173bb1fbd8a00000000000000000000000000000000125707f5a8e26b28968dab97ef4654c315b0a118c20935e38a5a526d9ac0a0e18355d8c9f3f58c082de98691957e2d5e0000000000000000000000000000000010b58dd683f73a16d8bd5557b35b7003a761bdf7d90ef576de8acd420bc74f5219fe7f9d35667feeb3ddf1d568b56bf1f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f00000000000000000000000000000000018267d8b83ca59d4efce7ee3d73f7b984f09556ea4fa5cff5997a1eeeaeb8bdc9185176d77ad0f4d86f2e429f4015350000000000000000000000000000000014114344d6b7c976cdaf2418d7f72c120c2fddcc65c3ead067482e7073e2a3a239af19f862ad247e3181b13f5236d1040000000000000000000000000000000015db961a093b248e83deea0ceeebfc3dd57c7cf8b48cd627c5c566a4f9bea30ff0ef9cab9287a0f520a72b02d9092a0c0000000000000000000000000000000015159439fbfb91d1e24af611563aee3eb498fde666a1014a9f645037995d72dca0ed5569da7ecd084208b7c228e8a2b2ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a313000000000000000000000000000000000e1780b32a7b17464cf514efc4bdb02283af396ffcf6d1ae023e07fae02becdcc3c467f89f8edc9173a71aad27b200da000000000000000000000000000000000c3e7fd95dd823338bdf3d82fd46c265a3f794d4065d83873b1aca66da5f80c5962c9dcf537fc315d024d8cab7bed89d000000000000000000000000000000000e4eb722080e24f54fac7eed4b94e7b1eedb081c3edd7aaf5433d00829929d8bdef940aedbdd7dfb0376b3ad5544d9cf00000000000000000000000000000000158c1ff057f7ffe6492097e339cc4ce56bbefd39658ad55e08d5407619d1cbea7c83b977a1583ee48897a5e9c0d9ce3e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000138e8bc8cfaecba9fd1322a3c1682c9fc1286d78e5b6718da00acc69f811fe9f94c9f0dc9d80e9002c0022c6dfcf156a00000000000000000000000000000000021da679a068b2f5f473ceed588f07adc7f485003f7d2286a18c07b09b835881f4ab94c7d4ec742c33a7cf01801116fe0000000000000000000000000000000018a62c2f4a02b73f5a91f503b53332304afc9cd8769f236259789277599a203b8b304b38993835a87d7cc970ad514d2400000000000000000000000000000000179396865f859386df7c1b8fa84c4ee71c14daf695fc0841c293618e6f8c87fb56b924f3f91a273b969e8635d7f90985d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd710020000000000000000000000000000000004a8cb437297722c0c1a9471ff083ce60ec40c908af4ebb570c87133df705e725e3209152bcff26a0d6e4602030610d3000000000000000000000000000000001832e55a9e703d727156e4677ef4f82b86c6764123c3ed1dd94ae3b46d7eed459114993968eaf8e21cf24c59d042f41d000000000000000000000000000000000f606d5ee57b188636334ad60057cec4008ace88f14ea06324edaecb26da627670b44b6ac57b9fa2717d03096010785300000000000000000000000000000000145bf70f90a9d98f56ed38b3506556a48a1340ca6161806d055d7a1382eed54e294564de7fdbf525b0012de3d25ab5c838f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0", + "Expected": "0000000000000000000000000000000018ca453b9d832f029ac8c7c70df846be97b530e6e42de3ba6943a7d0dc00296942f88eba6a9cc3352900ff124efaf7d90000000000000000000000000000000002e4514102aa3f772f2659ae9f1e2a91c7fb749ea590a3cea2c1a2e0f7236f71e182374cf7ebd2fa086dd921c29013910000000000000000000000000000000007c025696cdbf403494c5fc7f9a10ad0c549f84d1e06c5c4bb22f7a039486909c540776224bcdaaeb3880ae9d745dbe5000000000000000000000000000000000b5b5b70fae8b3953ee6661a0f4a1be25596839482d78710e584d3bcd93dff2b0bf4c8b20974744667e25fd8353cec0a", + "Name": "matter_g2_multiexp_57", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001265e90c564693db716f17d1a8815a8449e43b5a2d5446ca65160d864718cdfd413d5aa024e7581421c7222c29eb452b00000000000000000000000000000000133a6558baa53a2b8d239198e1dcd81af1ee46d55137177be467a99edf282edcd47b7861a3c822f9bd0df2e86aeb5dc2000000000000000000000000000000000d8287564bcedb1e57c3d74b0d484a9b475ce3f5b0322bda0e980de8891e2e8663abda99744b58032b8d7d3adddbac9500000000000000000000000000000000013cc35410d7fe07eac96abd2b35ff656e17b6b1eba2bd1d75ce5c87c5e76755ef9c2cce70f05cdec15d1bc44bf902d4cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a00000000000000000000000000000000193f0cd6b4051cfd89f358cf6643528f0f042ae30ba3627d297b4fa2c2936426a9c1b65145b8192f65dfaad1f2fbc358000000000000000000000000000000000a92ca8943e64a391aa39126f093f2b530f556c1e3ea1b55bef1c264909dc93d260eec6420fb7a4e4a45f932d57951500000000000000000000000000000000005c7dc5832f744089d5fe034bc93e0bcca042ddd1b221cdd5958be86214831906ddbf82508dd91dccee467fd1625dd740000000000000000000000000000000011b11b3d24f44bcafbcb9baf62cef3f18b56ded696b73577375dae8108dcfb663d437e4cd9e44b7e6bf49741e058f8cb9a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000073872ce0d74ea368df132897617aa8f941b67cf3fb395ca6c2f5bb2c551f17d68b0c6ef11e742206d6559796f06426c00000000000000000000000000000000156cc28eece7bed943c8410a44af112edd8576807e25701093eac0c9726f93da68a19c1d7b294f3ae6c84e32e7c2d5ba00000000000000000000000000000000050fe5987d5fa678be3d34c50fa6c5296f883e65ac3201c333b97ec0de00dee6187d2790c357a3f8822a174a534539a900000000000000000000000000000000177fee6e2d3909c0536acdbbdfc716f6ca19b6bfee7920a78ac9725c85114c69cd13152467e72270e35006b3c6caee8c74e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e000000000000000000000000000000000dd147bec9e0d1727c9d7597dea4a5b6b15c0a603dd1b586835580468148a502289fcc38194b2fccdcd8fdf0d8ec1904000000000000000000000000000000000186501fa4f3a20e80bf297e8ef1885b7d157617701839a3b524d61f35b2eb843ff0af13e253bbdef653a83e07a5871e000000000000000000000000000000000023eda2ed9d34aa253c8bf2f3b66b3c0c2551cc0e74f43dde2e429d9dea113a62572d245b44708bed79d662d9cba487000000000000000000000000000000001041cdaeb244803556e9b20db95f2a66830cbe47a68aea262865da50ab15ba658116657625318fe46fef393eeb6f3e2ec23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000c27b1feeeb38068ee52b0fa440af2e3bcfd16601c8af983d259f2d15316b513ac3e89069bc141f02b934f2e474253ba00000000000000000000000000000000183f966cdb28f344ccae4cfda63ba6a6f29d00ab942ae7db7572cc09305e4f80c11305527b8ba38c40aae5f23165cf9400000000000000000000000000000000049cf59bbd6c26ab3e25b3cb94878271c73c0b4436573d612311feceed0f1668f4d79aad92360c1c97d60b540239ae630000000000000000000000000000000015f35eb8e4c40cb1297f7128d99b109ca75944c1943abe9158813432145a4a2a5663b55dbabfa48bfd9dd01907e1e8d3972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a3264000000000000000000000000000000000e5d9a671862733804f517dc9cae2190ef0005f26394e3161fbe771b9a486368871f4b1f10f405e45048362f437238260000000000000000000000000000000008100c6f96ae7af5fc86d9d91fbbefcc1bf5873dacaba9c3adf1b2833dd529d87f303a55e5d4098153377effd0f8114500000000000000000000000000000000010e4863a9b037d4ae6dff827a34be04c7f1627670b40e5cafb1fbca2fbf56af9ea6b24548db58e3119db64553d18cf200000000000000000000000000000000036a298ad5e8b32041a18e3f6c5847eaef20a5b63ddece41bd7dc4c4a54deb9c6d7002e6621aa01d78d64ec9991f68fbdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000f62c0bad83c41887bf1ebd2644cef0577d793c2f3d67cbe43974f460a4afaf2e412fbf9ec97404e5e882ca0b23bd1a400000000000000000000000000000000191562ec9ace63ad2aae1f7fa977b9e0606e1da9775a978b2caafada4f6b3d9104562f2055fe037cd06df6093123a08e00000000000000000000000000000000156702c3feef1baf5ba202a25b9dfd5c1fc620e837501b0c5bcb85ec8b6e3e92bad1fc842bd1a0dac363e4bdf0fac87c0000000000000000000000000000000013a4b7e869ed9bdbf9671a5d8ca9145a2e97b6885d2a93b33f378e649e0e576be65bfe849119381057337315363bab2f4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd330000000000000000000000000000000000a4b273438168494f0db235f535bf31893bb70f4119dc4741aa3c5e63e93b9a8bc001faaca10e37f36e130ef53853900000000000000000000000000000000010936551b148e16249dd934fcc83dee55279495c2a70d46dfc45945a69549657c3dd7cce00d8136e28d64b0c800344cd00000000000000000000000000000000115c053ac0b68573c3abd5f047b8fcd897e3d514945c5fe6efebf1921563d0079eadf32f7428ecb703d9163bc7811ebf00000000000000000000000000000000162e86af01daf552589b62be849e6176d74fa5da9b214a5cf2285802dbc44f346eaee5cc3d93a085740f74cf7e1b17e1dc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b000000000000000000000000000000000d77cb5045f7d4578621c76bf5b3db076661c72174508279280de3e92f0aa57057ab50180f0f908561a87d412636d964000000000000000000000000000000001853f9cdccf5e6e4b87231b153ea5257f52ff10dcb24cbaaaa95426d0231dbb355f9c47475d125ec1079b9bf26b23b560000000000000000000000000000000000fab825e06c2329a19de853a05c4bc65f16fa047eadba8e79607bb31b84ed6541b00f7f14b15687d67cb4cae0ef9c600000000000000000000000000000000005deaebb5f31a62fc0bc1af13da63d0af3c716df8c9bf00f1e831af5882b88974c49e8d35db2545747c85ac35156bb668af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b0000000000000000000000000000000016faa5e91048badedcb33e83684d2670051c82b7a1d0ead0e28f4dddccb141a8ed1fa7606e4b6a3a893c55344263eb4400000000000000000000000000000000019b2c8758abe5d339afade4ad0c1d44d651f185f8a0030b81b136d5972510b353d43cef616ce04827d56255419831a400000000000000000000000000000000124b1e87f343a890fd690e384cd156da57f4f0fc5b1ca99c73bb0571332ec4c12d3ebe955e3ae792efadc1d5c0c67a410000000000000000000000000000000014cef10e4a9a41bf117aacd2fca5f1364a46b0c4aa0723a369fc6ede09dc76dcd8cb67fdf87ac49bd4bd9981a2e589647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b80000000000000000000000000000000002862fd5f38154dd452f65de0d3c1d54403cdd2a397ef416fb92e570913c543d3368a95fa114fcf48c3bb4b68895ba33000000000000000000000000000000000e7185443e5dbb656fcb9ed100949f8f7052ee2cdcba4f5c687a65a1b45bf66ede5c60b0c04845b9a870e004f8af8450000000000000000000000000000000001817be6d13cf2a67225b2eaf073e9f1614f3bd32cf5572766ace4a91f6b6be56f498b989f1c3dd3dbc9a819c029431dc0000000000000000000000000000000001cf41fe428b088a17b8ea93a653677705d5c024db530b8300752c6b100f2abe4c46dfc24afdaa2b3d53cd8ce0df1b6a70cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f8520000000000000000000000000000000002eed272430ca3176988272e6157a18df7151bbfed5b90979752a02619ef467af8083208dcc9c7d926490b1283baa21f000000000000000000000000000000000a644f6137bde232c3a909b742d30bba096ef88b711ef100144276d0944487f9ebe8331483978a47c07d3a42c441310900000000000000000000000000000000042c67cdc10efa8301ae95d6d4f21cf152f04b235bad2dc5a61724cba64083f690b3158676ee6ef10f52dcc7061f7c7d0000000000000000000000000000000007018d0aed5abb744cb998f84140331fb2cef8d9e09c76176def48a85370c6247c2ac6fc726eea891b2041ad5edca7f0244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f00000000000000000000000000000000141c0edc966b7c845d4e68272c6a71f8ffb7fd8d56b7cabcd556a98422f830d7a81d123d701ce1479e84047328ac1f3100000000000000000000000000000000105c1164d721b6dfb05b6b69955b2f25db0e9fdb58600a3229dd516076087aaec05b837ade68bd2a19917eee7b9a22bb000000000000000000000000000000000da3dd97e693948fd6955ae52d493b3a2d2896dd4ad00a0b549d4d392e81593472e4f9435a8b7977f3d58e324c5b9af800000000000000000000000000000000068c531ddb26a2299cc584b5bbfb0235fd774a2447134c06e7de8b94993804958bbf1ee80728cc6db647e8a244462372ad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf3000000000000000000000000000000001881432f4742dbe41bf774930413c98d49a781a48d6c64ee1a18f3076bc6c0e1214f92d5bc84ac65ee1c586c437d697300000000000000000000000000000000067e0a95f3eb826f3efeedc1882ecfa30b8b96c92f626aa324f4044ee74531fbfd50a221b1b0e0182d759d149d51427d00000000000000000000000000000000173f5be7098b756ea84f030e374973feb4f8811118ea6673db1db75ec6909303e571ec5a1d55a6bddf32fc80480cf103000000000000000000000000000000000f28540976a6ddb277df5951fe58e7310861af837cf31fe31c24f7b979f72ef1549372e7ea1ced15b655d24293dade7854b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c600000000000000000000000000000000188f296e218719bb9cabefd4f33d5728a1d280bc59c3d826a0f3b5338f92e6544a4cf36f1a493458e0adb246c01a415a0000000000000000000000000000000007dc8e4222c7ba78190a8e72ec7e6980e2581f51a8d6c41669b6fc9e16d50a2bf4d422af73398e76b2f39705eaf8a6da000000000000000000000000000000000b25a44523323301cc01b50d58726768c2cf61e691203dd34a0ce8d58fe4f72c1c33abfb2a56e0425fa9b7e2fe48e870000000000000000000000000000000000c6f11ea269d9061d2f462ac37401def1b2b28c47b84344d04d1f026add3237d99a586e3fcbae347a4ecb5646c8c569fae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e49000000000000000000000000000000000c41a02e937f8cacc0be5d9f2d9fff0d6d4302fd252f32145974206463854b3a7d09b3b147cdf2d7536e970dc13613ab0000000000000000000000000000000005f9367f4e31f7e4d6e21664ac13d55f501f5368c1ca77fc439db60e1846861e6c4c3c44909469f88e02cd973499992300000000000000000000000000000000131fe6df7fff97f132bfcba1d2599a862c1feb514a05b4b7b0bccf49e00aaad043edae9346bf726e2eee498dbadf2067000000000000000000000000000000000e59044f0950a741da3881282697f4a1a522b026e493f6009227da4c0a963de622d5e421c30e0023f4118c9a036274f859b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4", + "Expected": "00000000000000000000000000000000144433ad3afca0a9581e7e87220a4944e26ef2eef6b887ce77d2a2559ced058e7349b36efa66c492cc75b014b3448ef9000000000000000000000000000000000267b90e45d7001edae01fb198d16dd37c43cadcd2ca87bd7cd1f0f65a95148144f5ddfe75d344eb4573c1376aa2728600000000000000000000000000000000050ade28b09b0394b08d128c089808021e4c65dac49d9fb45efb93792a4faf210230b650fc3ce810fb8d11947e9af5060000000000000000000000000000000003b1d7dd7c6d944d16724fd1bbfe0f53b6b50a70e133dc5998c82b51f817f489bfe1e0c361be36fa41f5af7c1577f2ea", + "Name": "matter_g2_multiexp_58", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a081f037738b0d812da43a907e7c624e331108ffb72104d82725b9c14dec8449f5ba0e8c1a3f1379cad2c3e7aa99f70000000000000000000000000000000000937fb5d8b3c258b7b28555fb59620f114816f0fad46818a5f100bf7dc3332a03d285eda18e31e4047cb2606bc53b20c000000000000000000000000000000001574e355b7570043bf36ecd52f9c4d9ff556146d81a1e9d088444805db9b3b678fb55774865ad34d21022afea2c154590000000000000000000000000000000009f70a5cc658cdab280ed65e13aaa319049b9534a222217a08168047ee2491f25a9d2620c7343a6426bc54a0700bdb4fa53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b460000000000000000000000000000000015a76e89c8938b8a27e4857aaae8c942371b6979605adf774827e9438ef739428fc53b65d32e4e152cbc6a4de42b8bf30000000000000000000000000000000019494030ae0507eeff20b69b4913596c1b9ea6927157945c8295e273707013ef1f2cd08c058f6b469a6c99ad73acc28700000000000000000000000000000000122ea7ac21a27ca7c4b00207538bf561f688429999332c45de7545046acbd6d9e96d31f5f6a00595eeb212918a28d2920000000000000000000000000000000018b023e7da67cb8d9159746bf700f9e151fa60ba8f5a28b3739de005822929cd28c49b9dbb4ca8a10729dd24771730ff4d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000007811c759634904765029e955c3deca648fba6a9da6433b50a6d2086a59e65811d52d41ed8ff2e9bd63a4c0828bc702c00000000000000000000000000000000182c86cddf5e20697462c829f41c7b49e7976880311b01ed4d12d7174340799f19db0f295263a2617182bfd1b49e0d1b0000000000000000000000000000000011824bc20bd1b27876b4f48aa8fe3063f826b6b2c3dd777fb8999a25d9139f218f6f288955274884ce96ef2dc6d34d120000000000000000000000000000000000dd310d5e141e4eb13380db828caf74f62878959b6b2df998bebf9306965f723fcd4dae7c25bf2f79ece3e8e9b92de61b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c740000000000000000000000000000000005667d8c4f8dc3f4aa0021d1026a1d0dd0bc3576c49339262e84d20198fffe33a389d28ab1d782e9d19af761a2f097b40000000000000000000000000000000002803d5ad6393d7072e149f1f2ebf70cd8961ba3bbefd648916a8ac5a5eb893b71bb6015e201dc241537ad5890024239000000000000000000000000000000000122e1d0e0859b04143f23c4d2d2ffec09ca2ce5eaa9429dd0c047032d180bcdb10c106071d9f9701c006e5eb8ef88130000000000000000000000000000000008347a7bdb3b4f381b58ed3a128134c09563b345380ec948943e738347de5b5737540b57c28d00b9d060c60942446617ee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a5000000000000000000000000000000000b26aaa46a279c482fb395ddb84d5b4c9c70102c336cd565ca9eecf62cb96f59f634adf46af748826590fe65beea752b0000000000000000000000000000000012cc63256a9f73f450e86ee38c54ea78baa5bf87d3bc01320f7fbd85bf11e19f75d787b9b12b8f2c7634368a9023de880000000000000000000000000000000006392fe611835f6fd50229725d71d435f704f78cabd1b5569e1c5a89d4b11f911f0e34ec034369f972a80eb407938b97000000000000000000000000000000000f4ff2d6a991fde9093000d7bd9cecb289383d259346d83bc9bf5389d4c39c82a0e1d7deb84b90ef370e0a19fce28d2b3e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000193752c40fa0f466f7c8bd26658f133d0283d2ac3b02eadd27b3e9681329307f91a1512fbc53e537f9e1025a3d68a7ca000000000000000000000000000000001106d751c9e1637f00e51e0be856405e6b69421d81bb30b9b8718cbc9cfdc36c80d2848bab0d5246da84f10b478fe48e000000000000000000000000000000000827a83f28678c4e39c4963e95c2404a70691885788e5457e149c0c45d4e8c74eef55223ed15cd75fad9f7209a6ecaee00000000000000000000000000000000072667f02b781c8e0a75d0ed8f3d55e668ddcc8c61937c80653e240c3a744c961055c782ca41b15211c0f1e1ba800bf5ab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001419629aaf0baf779feca264d0d9846b987506125b0049ebc8b307c4e3ffe00da1284a94a012bfd60456a4a937b2e0e000000000000000000000000000000000119a801bd0a5a1c1b25cebbbcccc7d2bed9baa4995483f4ae94121a8c6cd0c3f90a26234f51590d66cc38b8bef9020d3000000000000000000000000000000001125bd15fd9814ddd15be0997a6961b6f1c05ce7944514371f10c8e5bde271c4b936d6537d91ebed740fbefe6b281a0d000000000000000000000000000000000982a2904a524b1fafc50d540506b8fb07c3b4978310bf3cf53ce570b1b05e746981bcfc06d59a78d170573b09347f3fc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000b1da333e508ec6b0329747fef35cb926d922091d4a45eab7cb5358f20496c66e17e46874ed9600cf4252432c29aeb07000000000000000000000000000000000c757daad8f3ed7dfd64782548eedfe904f7ef3bcc11eefc4781fb37159d07825a4c9f3fdf9cb3d8f3944277bf25f88c0000000000000000000000000000000011160e21503d6fd61a2ca0212a7d48317186f259a987a17cc3eb04a6d9251736e4a66b739a8f3095684b7d91ce6f79730000000000000000000000000000000007440ec0f9197352a3148f9bb3d3dba9b1d5add903e48b50ef3f6879859b22ea0e31b46ea4ce566930d8853520abdd14ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba00000000000000000000000000000000070ffa4d522df8b9f62aaf36132bb1b857e177280a7b6d3af6bfc79b73ad3848241df18ca7f8993ae3d67005ead9264d000000000000000000000000000000000e32b65bf035bcb11f86c60a334622d2367797d0226761b58a7db8c7324fc4bb498a558eec509c2326fbd0e7bb8d3d19000000000000000000000000000000000dd291a760393c6e962818986727e5ca5d46544dc47eb49dd828c6f74caf0599e88c4293881714c425b0697944faa861000000000000000000000000000000000f7ead0be081467f3371ab92c249cea73dedfefcb6aa16a162c06e30605e104844c3dd194b4a89ad5230f596bef64f19e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ca545b53836899e507880329799e4c1a1acc17275f5d71d87b9e41ccd7a090da854f9936254448c988ec772a813bb6e0000000000000000000000000000000016c9b03fd01394560497d6a03add63c034f96744d96a13a4ec92d28719018d1eba1465e4332e53f37f2aec4d93d4ab7f0000000000000000000000000000000007019f5201dce326d5a6a1ebecf3fe50e22335593bc9d3e62256351c591f0a1a577d916055d79c0b4abe191b6b8011fe0000000000000000000000000000000017acbe72fe30c386e463f3e9b35a474b902f6712b30af88ef340e6fc6ec0fe2e606c7e26432c2a4de33a12e35ce41868f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000057f856ae648279f2b6dd17584e1388e4dfdc9e870db48ee6ef5f58389ccd4ba17e074b79ae12b728c59e2f91bac5709000000000000000000000000000000000e0f39f4beddbf05fd700458448067b52c11e963b22603f10d697d6b6286b1449b1663e032bf7bea48f2051d8ded923f000000000000000000000000000000000022cfadc1dc399ef5f12afe1349d9274cd595a9ab6ef7ffdd68f8bd2d170a4a783ce0a7303878d809a16bb8073d79860000000000000000000000000000000007e301565124eb66d59a70897f2ac356e7b0c1bfd4e3b57e508ba0cb5c9c881f9de86b91fd5133aa2977c8e81138d66971927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000000edf3fdbfb03bc07871079aa4aade538a97e1619b54d0692a7f5f73d7fbc8abbf680ea3a99325e03c0501ef174deedd1000000000000000000000000000000000b8c1b5d3c926d7da6e0583f67d981af5286a04429e857b0aa4b1120604f9c8c93f04e763da169137416dc9ec4839a910000000000000000000000000000000006ca2aa4c7109f043da9cd90bc801404685db802eb8bc925d9d098e7af3d9f95ca490790b2b1c77995c050aaebb935db0000000000000000000000000000000001f40a2090b63f94f93e8b61b5ba1ac62a37548342ad81a9bd99ce8339435a7d7477c3b9cee9b531a1ecdc85a72041555ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000021464dded318cfa86db1e4329f302bbeca7095d910c4260799cd2a60ebb20e60152868e67a48b86f44000f267d11c33000000000000000000000000000000000ae45fa46fc8e043c3df99bc0d87ffc5867208fde0eaeda782230341a8624b101346f35fa24e1dd67ab200f5d6fbc8a7000000000000000000000000000000000795b9afedbb128a46c1eb25c52a71375903adf7d3520535372d9af5023dadb1dfefdcc0cb546e9d218890123252946d000000000000000000000000000000001852511855bb368cec51c54d95b430259f05dba6bae53b5c42d69f31371c30cb611037fbd81393a896cbdb6240114549d37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000113b883c6bc41b0673145bfeccda414af45efe5710f436977712e7227f38911cbae851dbe03928f38e310033458eed72000000000000000000000000000000000853e32773ef1f95a3936aacbca50cdd5eed3d08dc467d7ee834487e445fbdaeddb0df394bd0c91fdb06d2883c4dadd60000000000000000000000000000000013a7f9cdebb2ec37fad172d31a717f4b538a8ee74432c5a5e6410460eaaa3b5f24d223b76bde4277097e93087b7136330000000000000000000000000000000003d6f141b56e1e2e400fe821524017cd972678a7d64f660c313e6a8910b72b5ac04328d45945077aa2946931c8dbd11706d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b40000000000000000000000000000000016adbeb3530f6b451d870b2d8292a01143986cd9890c79a64764383575771b8608ea61beb2de87bc034d3b8a085958be000000000000000000000000000000001125d7cf83239e4341c286fe0c8739e7013b234814b26a079ffbffa329ee4705da81fd12f34f49d821690a11b8f83c5e0000000000000000000000000000000005873dc5c0baf0f3297d884ac7b652c749abd0405b96ba60fe396efa179a79fa55be76924b0690c9a528c605ad4f9e120000000000000000000000000000000000fceec23f479c72e0fea0d10d3394d7121bf1673250cf1ebe72eca60af82f232fbee342e2c8705434394d4e519fbb40f56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af0000000000000000000000000000000005ec966cfa28e105f3496f977a2f046fb206a190fce1a6062df0fa1946f274cde9f6fa8a71089af8cc2fbc2b60746cf40000000000000000000000000000000013c77ab66fa92a2411391d366a331a40accd120db1c6a656bdd92858826fcbded296293c13ee189ea3f34635de56732c00000000000000000000000000000000162795b6feaf6a63e6ea2d34f2bff2a4985ad26463b8fac69f8525eb0a005bd377fe7ff4aae820d361592d2d88f98f5c00000000000000000000000000000000044c9d5d3bc0d99693f5a0605ed467cca8b5dc7c7093294d14015b59bfd8ac6bd479b73ed52fd30d8bd891ed971912c571e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c", + "Expected": "000000000000000000000000000000000c3bed2f51a60f9afa6655853ec2f0e9d46bdc1277bfedffc468d9f36cfc7ad9e70365fecc84a5a40d863dcaadabf22a0000000000000000000000000000000008c5894a4f93b02fa1deda8b556798fb7d71f53046ccc305588bfc00b68bdfc34b3f0bf154ce7cb50c9536ad45e65f300000000000000000000000000000000003699501ebb9698e98dc998fcdac54dff895457d2e4e0a0e2d65d275b5798dc016e921bf1f65fec0f284a563aee66ca70000000000000000000000000000000010389c73de7f6d860c972c1f09dd24137c898e92935c45c10565ef3da3406cf521647ef80688f6e799eef4879ca9a6e8", + "Name": "matter_g2_multiexp_59", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000114b9c33bd09899c684e81a5a4e620eefa4e620c01c391a4df5caa75be462ec7ab027a9ae2c31d6643c48e3d75b6ced6000000000000000000000000000000001925084d2a1f537329e23c77b8a820c385ec5e12e4a145888882ec611e99b05b789d79bcab48326db4424309c24d1688000000000000000000000000000000000a1dc78c25cd16211a38bd0c70d24c84da1b83adb219e1b9c06fe6a6669d6e0281a155b4cec32d32751fff653aeef1990000000000000000000000000000000001daa74f19cce1086a87232464ba903938465da5e3e1f9ddc05a4b4dc13f1026e1b07af7254d515d2ad6960ea62dca1f77f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da0000000000000000000000000000000018810adf0cc793c21726e9a27b7c558aa16b81af73f22629c478d293208a107fbfed4511d9cbcc25fbc2826bf004e7dc000000000000000000000000000000000356b25cbc7cf65107438125c930dff24b7786cbd7eb744d7f27967619d5cc02799451ac8814782eaf9aa331e6f8dbe7000000000000000000000000000000001164ab32ddbeb11c2c8baf7f311ffb01bcc367395bc7ecbe5642d344a8e879c74a554b3f9e4b6ed7db4ea0f872cf96740000000000000000000000000000000017704b1dfb111807d1f5d90c370a5b2968008a5ee9fd72262b6543c93fa168285c04931198f5195f1abca648722ebdc5630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb0000000000000000000000000000000000a336a04a8fd8e18dd9a582da897016983d9beb0fdbcea6c88b7c0640620be52bff32afbe700599e3c08669c457b760000000000000000000000000000000001765fe4faeeb13fc2c007682c031ea7ff2899090e16a9a11959c5c3ae7881a1dd2c6d2b7f5f708a92349a2b0de4b92d5000000000000000000000000000000000e7c57db660133ebeadc2cb2054ab4ed16355466932685d4d11038e1e1f47b0349b68bc4e918dd48ef8e1c5d7cc53f7800000000000000000000000000000000169b629ddd7add588b91d9866a750570dec58662e43409031a5e25f1b2913c5c5a7a7cf666953c99835431f091ab1b140969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a51620000000000000000000000000000000017d03e9855f3bbee719a15208ae24324ebf1879972ac134b027c9e03444a5736863bc55604158e81b38c7fd78ba4bee7000000000000000000000000000000000468f7c5478cc0faab7098dbcc455bf18525b56272c2d02cc1febc1825579a613edc6b455764ffc71c903a0704224a4c00000000000000000000000000000000067104ba5366e7e11bd4d516565d9cdd93d4390f2af3c1ef2ea3b1e84ee8e5c0e0fd8ac11ec9d2553e4cc13b277d473e0000000000000000000000000000000012e10495ba15b29c669cb9683b2fc7a45fe7ddba743b4a39677fbf85aa738480eb9da967eee69b02ef14137e102e240eddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000078f8c17427847ddaa1665d206866231a5f36d3a7b4e8fa13910161566163006b5aa5d9696f423d0c44195de65326f21000000000000000000000000000000001613c465b65940f43c61b5e3c93313ae49d92728518d9cdfc57b49d6924479b70e281e724e04fa5f165b5999f1c1ed3100000000000000000000000000000000031741b6830c16d730619457d42767a51037fb4118e00bfd6cfcd8baea35ae76a5159bf1f4639fc2951f0b57446110e70000000000000000000000000000000011a618ffbafe4bad0a435d04084233495e5f7fbeaeb66d0d49a8177f562329b52a5ed4fdc680b791f273a7b0d3d4b349191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000043c09eea638e524661c60ae3704fd1c18c46443ae134a0ab7b9a98cd398377febd9026c28b3e1e50de98766aaf0083600000000000000000000000000000000105918aa1476cf52f91b9ddb7c23ac18af3bd5269dbafc369713687010720affed6b12af9414cecd521cf0c7f5416c350000000000000000000000000000000019ab4a3eca904a15782f560bbbc8819dc09275f1f6d7c3b8e98aa0a96ec33dcb528284636b0f42ad0d503489d17161ff000000000000000000000000000000000a2abada18e79c548d5829991a65491ebcfe0e1a2c89a1e05f06a0ecd197797c5ffea0ae90b61f54c6b3fc844e0eb3ddaa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f848000000000000000000000000000000000d457cb2c77acc8ba4b19ade0c724a2b6b0966ecfbbec8cbea745439b9bb7f3dde2febf9fcd6c5e6139fd7175e57b1720000000000000000000000000000000003154466283addb0d0b5d86a9633f8300960cbe8bf6a1405a3a040472542e9da63fd4f79a43d641a47c2b69a31298d3c0000000000000000000000000000000006599794823797f8ccea9daf0459b9d26e0d207f5fb95383c6b61eba38516b272e8ae6ddff2a9fa791e69c0eb25f3e470000000000000000000000000000000018be316bbe0416ad7deced1486d4e31490f5dc7e379c17542b7d3e9dc77bbae9c992e657c884db320cd51c2141a4abd2049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf10000000000000000000000000000000007ba1ec5293d169b88ca4d2d92eacd51f0b8cffdb403632ea8ffdebc37f3997baf736771231335d12717cb45b51be31a0000000000000000000000000000000013505cc24222fb2ba9e25f5f3497653462f5b10bdd0dc88f9b16d5643a99ddd4a7749dfa6b566f41cd2da7c2b1ae93d2000000000000000000000000000000001465fdced698ca76d5faaa7e4faf1260cd5c4fd2939b16d3593e3588c92de3d003540ec989be9632fdba4ecae889ef180000000000000000000000000000000013a20cecd5e8f161ac70e40b8e9ca4c23e2b267690a3abea941c293b03acbbe4fc68a1e7b6d35b79ac46f65edde73a3e0502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f000000000000000000000000000000001936436783f02f3a5307bfc0bd8c0a00ed8013508a440d040ed4f45b37a4e89986102964a328e93fabde6d9dc7ca424900000000000000000000000000000000000f16408b869303181b4b4877b554353b26a7b4750b711f3c41cc4b6682b2113cc772cf9bfcd0cf60e59ef29a5d0814000000000000000000000000000000000d5880e2ef94663ead736687ee725f7ce98fdc594230c1ac9e8345d39754bd616e261076aa5362776a6026129bff105c0000000000000000000000000000000006865ce3cdb5081e86535beb990d95ec3d75f67c7e881306607e4876c42714d627f8d548849aece4382d1c8f2b693bdc9787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef523713750000000000000000000000000000000016d941b6a0dc023fa2699c836b74e16c31b4cd51538f73fbb271d163519d4de1cb0f6ec2f8efde22c74ffb532c576b16000000000000000000000000000000000d10a7bfe9541a7b22d455f1b68cfe2422a83a070d93476aa0844670f02aecb36e9f41b9d66e8e9d0d67c0ba85c99f44000000000000000000000000000000000d7873f96d45fa8c9ba9cb4913a7b01c8e38876b6bb2a05506d23df0491bcffb42983ef663db85bc3cf755f476291a79000000000000000000000000000000000c22fdb83f9991c85b3577d1ed5a171f28460d79dbc6167b0c30b200235c512f999066eb1fa449115aab55128f8f2dde10b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000010faf9cb9d0fcb487c9e86a2d2123105baa8691d82ebae8f5bb7d5ae7b7d8154837120eea86dfcd35ea5482a7ebf7f8a0000000000000000000000000000000014e40640eb6e8e38651a2eac05165f6cf5e0178b3711f34828766ff9db951e1348f0cdc652a78840dc24ada8b1c835c600000000000000000000000000000000129db7482ec62873591018a8399a8c5e4bf00e8bd9dd78dfa3d0b4cd1d93ce5ec7531e56d58b7a1cb3e58f062f6895ee000000000000000000000000000000000d8db3b54b6e71497faed107b31f5e44f328780cf01c62cb5ca00f99f10385ebb22a367cc89505640d1106a9ceec98c4072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000011ae9bc3ce04df2add17e57f260a72f88f19a1e44b0b074cccb7fd547035038d19e5f2228db46843343a69823decda370000000000000000000000000000000015ea64b6147ef76212bb5223d6d5ab9ca866799365683720866d8ce1117f60bd552a8e9981c095894258ca3c1bb5150500000000000000000000000000000000173bd5cb455b80b78951b15180fa7f8fb4725c1a12e5c53df1b9b31b45a29083e66c7116741d9aa93448c81b5e6014610000000000000000000000000000000007eba059855ab058c2066c643ef5268c864d09ec9962537d65a1686322c374eb5ab8eba4c4260ad0919dc18b4289a694f3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d6000000000000000000000000000000001598471460ae082c2e2568602c99923193c913b9e803cbb7a4503ceff369e8c4bb3a19ad245c08192e12a2e9b3e75c4e0000000000000000000000000000000013b289bec9d97c529382388f7037749c10a64f915746d23d8f37e15db9dcb173b3a6d00bf45e67b8c70959472148321d00000000000000000000000000000000094a99f9b031a51b7d54f7b8865621b204c85d23fd66fe8ce007f0b852f8b5b895010745b2fc469abb670e38fbc41e50000000000000000000000000000000000e36daddab2134f65696ede36c50f90f9a1c56165e09243cd56fd3d9902d3c78cd85e7028f6dd466f6a8655da62ecefd393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca17700000000000000000000000000000000050a818ce247367e8b57673d205d6bff8c650bcab7bf794dd32494669eff865fd4e05d7b4d35eb579eb475a3a0320ff80000000000000000000000000000000017ae5d612bdd46e1351dd1367c08c16ceb002a29832eba75e48d4c82e364f17c58525ee653a0940955b874da6a5bcfcf000000000000000000000000000000000eb2075367b42a0b3dfa30799ce1ab327eb583316d15b8cae21b716e6c7fd8cab96c67bc39e353f5e842e74995356c070000000000000000000000000000000018ca4b533da1baab37f05afc3ae0afe976e4f4530401d2f97176f5c73de3eaa75b8a34e8c6c0543ca0a08aeed28e478bdb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000b3f4bfec920018663bb39c5520491da5c538f82138f03390c768e088bbb2880287196af937f1f70e215edd49d1872ea000000000000000000000000000000000037e7607a60cf235d8e4ecbe69d378dc02f0a8e40b7f23745e15a73fdcfc971cc8707d55a8c5b91d9a5f42c2f49c455000000000000000000000000000000000467df75c2703ccff1a01fa5bdebde210b61b5f3fa33e76e55be5dc953f4758c3a2c499cbd42b256ff5a2005949d9bbf00000000000000000000000000000000010d574c69050ce9e909dc23a76e9a2106870e8d8ce2a0e30d42cbfeea56ce3167535a9af1d453d4d8e6a450eff870638e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf24000000000000000000000000000000000907fb825f247c85d93fca36dcede9c22a409fa82fcf540593e8247c17875a1385fe009f0ff43853c404f6c96e2809ce0000000000000000000000000000000012bff10bd4162207870f6363342f2541804adc6a4e3f7b8be51d361be34def7a85fb39357c85a4e8df670fe39233bed00000000000000000000000000000000014f7e61ccd52bbf6d050c9d506751e03c8771b320872179a9f0161ac5736edc13bc133bda6239abba1ae09bd6c16f0c3000000000000000000000000000000000ca78624563584f8929d72668da70218a2da12b42c4b894108e6b103201372554fdd6b3bbbf2d94a9d0cf4053eb07d460940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c", + "Expected": "000000000000000000000000000000000de8e87899b294575392d523ff6e153a7c2038302ac74574bfae7fb222558f2b4c9556be1bc2757b83ebc180ae710187000000000000000000000000000000001881c7688debe3ff795788c90397c3fe3d6d9f56da9221778d7b12f5a54d8c0a00e1a8d4bb9c0b1d578dff862516b5dc0000000000000000000000000000000014cdfdffbb956a20d8521ccdb214adab14975d22ffbac107b2c15f42e97bb823c6a3945a5b299d3226e2044e64f8d1ed000000000000000000000000000000000eb769b301cb7c0c976623badda4db8ccb18dc7322472b5fdb969393d5d82b3ce94bfa59dae06ece424bfcb88e24207a", + "Name": "matter_g2_multiexp_60", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000164227fbb787b2d47ceea93faf1cf7890f48107ffae3628192235aa57658d9a2861db13fec0e58c347571c2ab0cd11ea0000000000000000000000000000000015478417b6758826b1d6fe0c562d43451e289dd50de31ef365ec70faf961ebb65b510c4788b6c7da2dda9cf56d3c8a74000000000000000000000000000000000f9e50d802ca8cbf80caec6489fbb24a2761db1245d9f7e820e6747bdd0855902ff211c427c00157ed9b1bffdf39eea900000000000000000000000000000000128f69ef5dbea5f80dbb9558a25f133b9ad77492250e0654f8fa5b55266f2fd26826a5c373afcd74990ebf768d6d8fd20f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000005f7cfb31492dacae51caf4036d99d917fa13b0d2353bbce4e6547ea744b3a49b162deac2f107149ebc2f79e74828f720000000000000000000000000000000015ed4627efa9b318cbb52f518b734327f5d1cfbb097adc6184c5034620504181a298ac7e52759586dae2e107f121a9b600000000000000000000000000000000023e832638849599d9d7854d3ae18648e67e8938ebf606a7c86c3a7ea21cab8d4dd5d9cda5c482e05d351ea3ccd854710000000000000000000000000000000001849665396bc36d0301f4c9adbce81fd2f2d0c7f89925487d91a25c6bd0730ce31678694a319666cf42162608ef15a834680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba20000000000000000000000000000000012c9b607e29e35f260f3c4617b4217d5dbc6953eaeffaaa903710195e080d593972e7794897eb176aae3539401a483b10000000000000000000000000000000019cdae8d1d9035d1fc4b4db09e7da3c20d3b8777523155d407cc6565a71a6c951eca609d328ddbb165c2b5a3e6b081da0000000000000000000000000000000009c4629b67c1c50e5fcf316136bc645e9e62ffadac8495c084f97e32b0a3990b3b1019261f78de576ff7ffc89e36e2af00000000000000000000000000000000070a49e8892c5b523f5914e2341dde63127b694eef556de6dcff603da109a53b342363d9a854dda3d2833e25afd5b57eefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad0220000000000000000000000000000000018d3c9259f70312c803dd6bac6488541f92482f7eb61ead71fa42bd5e2cca9338218d62835051bd308799beeed3b422b0000000000000000000000000000000005e0da6859601b6ada82b1826a455a846f8b4e54d9f22c3c639835a8a89e17ea2d76e2f49fb151f519de3e9adb78f0590000000000000000000000000000000010113d2fdc1e8ce0027b651cee6f9f6832b531d843db3ef7bf209aa00018715c1c42c68a82c53247a267929ea3c9363f000000000000000000000000000000000e7d1152af6448aca78aa7983013395f0dfc298848d86def6f017780e9cb144bbb21540a14a4d47b61d7a9b8c62376fc2c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000014125c81d4d7a8ea18004d798311f0d80c41c8e3a08366f686145e867192bbb13244f9f77217559cae72a150faba12a6000000000000000000000000000000000fdcaaf79c0607ebe9c8ca309d29d32284f3567a18dbbd23da9d96bad7269395ec2445d153711df4c883e8e7f7b02ab2000000000000000000000000000000000d34dd6636ef18b14f011fbeb62d33ec4358166f96f38a54c36b8797b51c1bedafa43d9f51fa4afcc2acc0cdd991997f00000000000000000000000000000000017337fab49d545caba55b763c23ce9bb3d3cc475f5ca37a15322e94c37825fc800cc7ee67bdcac66f9b5c22b03bf6558b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc930000000000000000000000000000000013434d32deb96edafc9a0e855281970b7c748c92b3472b34cc758dc3c17c4e6fdcf3190c910fa54a0259ef8bec75a3b300000000000000000000000000000000137df92ec14dd2fc02c0ec15a4e63547492154b4d4809e25f3ebbf24fe84255babfd6949770ba61637cc67e8ff299a2b0000000000000000000000000000000012fb20ef106e8cf3c79173e15dcdddb216c25a4de6797e411fd11d5632aef1304b36f8135c915c8c38caa2d778788f060000000000000000000000000000000014ef5cbe5711a815b9ff845e9201745f4117149b54ea3c6d1606060a192d513aa8ffe73425e37a42537773796b6fac8f202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000000b1581a5def94e95e565bfd402cb84f2f21c181639c047d8f91044da84bb7854f5cb4eb3a6cdeb66569d99410ca3ec6c000000000000000000000000000000000d8029828f4ca245cafa7f396c25592ef08f6768e1a5b806450be6ca5b548cfb212d8c4787c3f15fe922f466dbe518c0000000000000000000000000000000000f51e01a044b6da437e3850349476437e4ff8b94fa190387099b17e6462040918cb2eba3b10d6044ff2123242005bd6f000000000000000000000000000000000991201229a856f88348381e1f2e282f0487e7daf1e5a4ac3854e66fa3d1303e3c20eb9eca605859e7d46dcfdd7615cc8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a00000000000000000000000000000000011f1bff5df413ade311b0bc3b46c4ecb11e386b886b71226987f14bc1a3a4b986412c2bfe8a4618ad5d70afacf4a3b4000000000000000000000000000000001972f49fa8b36d11d9c9d4ed6197261506b892ce6dfa932b87e686cb197560dfb8718aa413c38ee1bb771a5618c17224000000000000000000000000000000000e563bd240f5e18b518a792750c00aa5dfbea1f79b80a71369238ef15df9885d341d6901fb9168a2e74249f036e9a688000000000000000000000000000000000670e59ebf6e30b458ea505075840ed5348563efd536c31003d8d0bafdacfec7ba1ed401c616a3bab431a0fa71bb6188ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a7300000000000000000000000000000000071319574a93739586eda876ffd3be5d982e6fa04f5667873dfabfab83ddf603513394e0dbb9f418e725b02d2dc7b876000000000000000000000000000000000c6a8e0261da2ab499bf9a639a6e261e8c479f3f2b2d12992b41a3267e034c25373d4da4645626e6343e867466bf3626000000000000000000000000000000000045a0312dd5fccdd19edb65e24d5ba50e44689a9748ed9ec208320bd9eddf8d606b9340cd34ebf983e69a65c242fed900000000000000000000000000000000090b3dbebc7dd49e9f764e99c43b5915b67bdebd00d22c80e36e08873e5c5186bcd082dbce94f4f230b237d60cab7107b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f60000000000000000000000000000000007b04063dc315025b8545cef11be6b601fb4ae02597d75979b4946f3872764ffdbfd309f5ab3b36fe47b810f8320c1b40000000000000000000000000000000009361927d02192433a8d3c3d7871d76c6d88361774913067d16b68625aaa60f5a4ca19b6fd4140a5a11f92dec57d783e0000000000000000000000000000000012501f19b73fc6ddb4d194895e5cc2b89ca84defb7ae94f3170f25417965102fc195f38dfb7a2d88aa4b24e4a2fcaa4300000000000000000000000000000000141d0a0be60c32247f6cb0e0114251ac68c90fd43651d58c3108c728601ad6efc27c27a331a2f086d55aed54b3585fd1b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e47200000000000000000000000000000000043f74a82ebfbbcf4abf3fd02eaa4483108a3446c9cf041bc67f5078d1774308ddcb3f918d7999d1e2c0876177cab6790000000000000000000000000000000000da7d4fa72dabb314ad8f68b61fcfa38627d1d7719bc07767f596671c58cca16e005d36e42413d03da3c643eb46b1eb0000000000000000000000000000000019f3f8f1a4008f9db1b604373d3566ae7c14a9147f80597a31839b83f0f8dcdfd829f7fa933fef3499b671867c3121fc0000000000000000000000000000000018bba4bfcf7629fcfa47935e36462cef4fa3751c7affa2ee2cb2fe3e3532d46ca1d247393ea190fb3f48077270d6a8b22810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b89363890000000000000000000000000000000005a1e0e3a023f67aa7ab0109814f130a05c8c739036b98c70c8a8ddc1828d2cc4e2fcd16de4ef038a7373d15c78e81f10000000000000000000000000000000019e2bb467409b3dfae0b06244b4140de7f75cb105ab897d1ffb999c6b53bf3b60a3d11354815621c5d9f07962a237ffe0000000000000000000000000000000012e745499d5ed626b4762b57923bbfae7f1209408e7ecb8813a545c4ece0ec7c48a4015e0e264b47fa08fa82c39d3a110000000000000000000000000000000008acfd3c2a2e17be41a70ebbd1ca2cff2eda8a359e0969a389ab0a6fa51db5601b386dd035b26232be08d704a02033a7175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b646000000000000000000000000000000000fb464af51161f9c2758acc09d16754d4d8ac52a37baf2fb6ccd3bca3058bd3cd204de6c8a0bfcce8822f16ecfcd0601000000000000000000000000000000001819075eaa6d9e3f0568ecc2e507370f938a65169cea1ecc40c9cb4d02c83d7964254602e3d041ba0f93c24369fdf3940000000000000000000000000000000016c179832739a8129d2ef184f4d1231d24bc8d4093670a63d73771983152ec322b6a8c954565d61c2af76c4f6ef5e8a2000000000000000000000000000000000f6623578a4fa45614f4b74768adf65a753a35dacc84af005fa4d7328d733a09f12f709a7bb7f89060f60d4fac85780ae7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000189724a2a0723e7727d224ced126e4288f4743f6855b035722f2aa36cf2f0a6fc23f6835c25222b670c15248884451b0000000000000000000000000000000009a57d85140f31ca58e38b4a99c4ef103f0a4af0d5546d416134fa8adce6ecca6588c3c56ba06b2f59015acc1a081099000000000000000000000000000000000dfc67b7644851c3e928ea33aaa0f745a18983edb7488b148736e81ec0c62345c11e3f0dfce729d893dce27ea249860e000000000000000000000000000000001712009a81e06a85a225a46fac056b139c8da05e6b72074ee4079316e490a06f51c62241e380909b86239d867d631be16aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea5000000000000000000000000000000000976746ae4d9325d5e8300b57ce99650f28055b5e020700ee5f124fa76ef3bdb9923101c3a1f46b6985b8203b4e8c60600000000000000000000000000000000057310c3b6cff6c849938f533b401b0cbe10b6ff3736c79a968009b2c0b90708b6b9a98b8e594cce09c579a64ead846d000000000000000000000000000000000d39511e47f33e310332178b8a0210e76e4d4c7408ff5c2374f5e7bde8335525e03897cb3e2bdfe59bb76b21cc6411df0000000000000000000000000000000010c46a621b7fb2e7ceab8943b3371475d3d6f132fb658b8c6bf299888711f1b344ebd4a5793ffe6a7a7eec8c66c80303985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000011ffff38891ee56cb1fc062d02f6c9993100f991a556445b5ee1b1b0d56d8e64bc6eea4d7f69a6b6dc55ce7d8b4ba300000000000000000000000000000000000d6cdd95d1ab2a11ab424d7aa596cc7e5de025c57217da0da143887d7dccd6fda0addae7c2fd9e0996bdd0d23128e807000000000000000000000000000000000499b3e69214fdb4db7dbecd619ef9c6b5c8343c808e4953f593cc89adba02b5cbc56a5e7a3046c6023c5cf305e54e85000000000000000000000000000000000d267e21606c16479065e47da8e3c058cb59f55a1316a87117a73dbb067ec26f406eba6a40b30ecb00f506bfd3c32f4da3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010", + "Expected": "000000000000000000000000000000000650fe9f3cb3620e0bf1654a7f1dee503b79fe2218739bad608dba9f1e5330f325b4fb7c340f118eb10dd0776fbfe63c000000000000000000000000000000000bcbf1c6a684dea5ad6c1a540b3525cbc64c7c431f37213bc8b08c8d8915a331c07bc899d3a2ea72a9a4bb2c539cf56b0000000000000000000000000000000008fca1c364333f558c7284afa1be486e84bb035b049a2108b0df99395149de83549de153a784e4df2b0134317c85292b0000000000000000000000000000000002784cc1d11667bbd0759bca35a16a1baf49a21765c6c2c3bcdd4fc9697ef20f1274be5caa0f820d37e843bc38c68957", + "Name": "matter_g2_multiexp_61", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000051646993c3aba532988d7baa07eaabeb8366853436b8b19c0fe3e14ed45fdc65448d749adf745291ab5ee62d4e824880000000000000000000000000000000002cec01290d8e51ccf751183dcad20bac20b8231804a2b6f87f886aacb61d31b14f2335629e97af0ae0546a17a4cca49000000000000000000000000000000000762afa7b94ed580fd07d5141a8e1299c6ec439bbfc6c1a4d695d9aba4ab5d6dec93dc4de47096d72e5ad87d879eea190000000000000000000000000000000014769208ce8a9682c8e0340f68a0290a7782c2b04e3c13027f0b23966eada2ffb2156f6e20539738535fa0ef097f78d6709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000acc4e3ccc3574285c19d2545839d1da9db6770b078aa399262b7c91a7c41fb4c83fe7dd0aad19f4e3eb2b56273f664f0000000000000000000000000000000017851c99881677b89956fcdf1b8c5ca5dd0997d810f3fb89f7378dbf7964926cfde315f8722531d6d715b4932179eeb40000000000000000000000000000000005e374a4c7118a76e59cdaadebb1c4e635b4dd18665010249f3bc78d559455d27d547856573e264c98ba39f6f3abea69000000000000000000000000000000000a532979cfd5263c774f629027f7624799dd0f9d6a77f675d790a85fccccad6e93c00ff2e5536b8e9a92443af14611e69ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e00000000000000000000000000000000056f109801b7a4a36fcadbee7219c06ac74e4a3f7b81616076c33ba2a71d7ca0776b596fb25d29992fa26d416272a4b4000000000000000000000000000000000c02d7e6ec50b778a7ff36fbe5751ba32beb1c2024b17bd99b46239e6dd5a708d2fc689e8e8924902e0d80287cdbd6e90000000000000000000000000000000016f18df97f48aba4d1b64e71eb894904d02ee7f6ba425e58f38a08542319e2498cb0dada8dbbb81bb398c9c924ae44270000000000000000000000000000000017dce98b335f536909ce01647aaabb918942ba2468d9a07c5516cfd347e1baa02029d39de1b2602932630e4819f2f00f391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000005b23d6f76b8bd4f334e91771383856794d1dc65b365fbc0c94f21fff049761d7379f0d512c42ce13f878e0661712d100000000000000000000000000000000009dcf70c16f524ff540f132b35074cec6ed7dcc1f319432a0dd09b3ded0778ec9ad0f05d67ecf3ebb7947951fc4b25d000000000000000000000000000000001075fb15240d532a9543dc59cb0098cbd03da77c3bf85a0ef8be1560958f8ab57d3777fab5836ba98d67c721a4a8cd460000000000000000000000000000000003511525fcf6fe224eb87b13999d2548b6b8bb8069fd354f298a025b04a33f48be72d8e82a99b9aa34ce5ccdc1f1a59c94f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000019039c69d52a66330d2d8572a1308bd88159f0383c041ee7605d0aa86f1d0fe3e884d0a2ad9c72405149b5fd204ec3db000000000000000000000000000000000942163eca08672af3827dbd876b9c1adeefcd5ae74a2768fb55f1e8b342aefbf76bc6546853a2b33e26fa866e60a4e9000000000000000000000000000000000c60c6bd103ba5bb5323b5107373cd8d706038bf5ec2b367a43bab72411523bea35985b974c756184c346626ab2622d30000000000000000000000000000000016c4a2fc8a9b3c54f65cd150c80a3bf70ae8dbacdcd37128514b4a881239023e427f0b0c8984ce219207c458bb380da970f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000003e3df9a8ce220be05f15904a3321a6805ab68bbd539479be56b2a870c3d61234e9cda8190bdc89f48e7f0dd9374e1d800000000000000000000000000000000040446db3ec43e3e67dce62efd741a4157e8ea2597a143f7d6273b66c7045daf31f72397b4b9d374328520893157c1f1000000000000000000000000000000000c3a7dde5b02df5f7c1e750a9ee5314a580cc6ed53d326a9157b507ebd6c2da314c37a7f1837f7fcff7e8754ab603b7b0000000000000000000000000000000005e617ca4eced853f8f2e9fdefef810c97eb27d5c8bd06c5b4ea50c03761c01e8adddfe27d2d72eed8cb25ea7514a4aabd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c00000000000000000000000000000000104ccaee210aa8196010a6478702a54cb7ba49c80a98ecbf5c0920408ff8b4a7568212bfbf3561b6a7790520bb73bd42000000000000000000000000000000000870ddd51dcc76c8a97ac4b4f23819df48dc8a8798df0450d7a45d273f830c908541dcaab7b066bcd668b289c846ea000000000000000000000000000000000012fdae32b020a346ad5edc3bab360fb5ba55004ef3dfe5f437e841b5dd7284ddb3880051956c8068e49a3fd165143ac50000000000000000000000000000000019081bf768dae314fbecec408d687df5b6ecb32ec24b41f9febd583c05693f80345e6b9d81322ddc72616c1cc39a86811a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001316b5ce5bcc168d76d2c862230ce604d02cd3d242c51c250bc6b6fe5c380c9e83fe7041049f2272481ab38f44648f4700000000000000000000000000000000079acfc2b9629da9c9f3394874e64aa00527de21e726f02db180f86cc0b9a97138c2c567832e287635721ca40469e00c000000000000000000000000000000000e11807dcd4ac69fdcea71e3e6a93dafc27afedf12c2998dbbb2e4f33e37ea736df73af791eae69bff84f3bb212bab47000000000000000000000000000000000e834a34fb63d9df68d683a26d79ecf8ff67066586e5f760d4468ad196c66d4ebf8605ebfbb7bde201f47b35cfde3a5d8ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd000000000000000000000000000000000990f115519d2125d47b925b613edc3303110e9040fa705211e0d772edb2e0f7f88ce521d1738a5f65c9d158e9d360c2000000000000000000000000000000000bb951a16decf9be8381d0c88726b53d90bb32cd8aeff962d48e43863e4eab1839bd80d7434c7eb808bbc0e32e92a4290000000000000000000000000000000013dbd5bdb7caaecc42ffd81f14be0ff3d8fa228ff121ed4f2f3ad5961fbce617d7cbc8133fd49e03caa62f7d1567541b00000000000000000000000000000000195fd9b85e19d0e3e1c93bab0380cad6f6f3bdbdcbf5c6ec32b7de7972421d0065cf0b265f6250c02eada67e95284bce26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000009ca977266277bdeb985750df47353a6b81c5f0c473eb3369d25a01df67610bebf66a6de5727a465131404025e90441a00000000000000000000000000000000054410a13287ecf4aa18f543916fcd65b15cd5d54617433217b0a2b91a79fea764b511b3b270de3e8985e8f6a2fd8c380000000000000000000000000000000009a9802a03a7c9fb63c1eb13972cd42ea2df614a0972b914c4015c2e8630af319d12fc8108b4c88db9508a9a77d9e57d00000000000000000000000000000000094d83483bca296b20b7bee124f538ae9c659a84541f5c9d9fd22e98251d2b48051ac55ebe07bcc9d2e9109f526d60a6c470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb380000000000000000000000000000000003f2d93ddb6d5983fd5521c1d1726addf662af0945aee54788855037f47a013d2fe595231792a05e1259c5e5a8c553a900000000000000000000000000000000004f4f4e7df5dee975fb440b5a217c27d9d1eb83a5ae280a2b147896f6bb864abe04459c17ef56d784d3c4a0b7ad3f3900000000000000000000000000000000069da36057aaa89cda458af4ee27fd9ec969c8f7612cbb153da0e010d67bfdddadb2941cfbdba8c43019a9f1aaf9c296000000000000000000000000000000001545b8325a80176ea148a3d9301debd7046f33a1b419b4ed01916a3d0a072037fd617d96e0bad32b208983ac3be7dda4e53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c9269000000000000000000000000000000001536a81b203df2640bbe7e695b5fde186021d21685f24c25966cf11dde554d49bcefca64f16697509a9ca86e58b75eff0000000000000000000000000000000014348a2bd4907cf081f2f7bc944a98d3fac671abde029995377df190f7f60319b8de1698b99be39c821328e32a449c760000000000000000000000000000000000e18d4da3823addb2a6cef8336c83f99f390e23d7129365d57035d4363aac7e9c4da9f8000f086f7d2206666f990dac000000000000000000000000000000000d6ba54e2af9afa57ff4536a35e9b61c8d8fb3d431b653a0c66a2a4b8f11d9b5c45389f894d64485233d4183895921f3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f62000000000000000000000000000000000d88d5719e07e2332c54ba41f330c7763d2b2b7c4140d19b8b0972fae6ef902415de5f2abcc2342fce24d3ed8ffe156300000000000000000000000000000000163aa2c768eca58194fb76822deffc37cefe04ceb70aba38a51f507be7cd64c0755abdc2e49e7db234cd5d68575c2d7a000000000000000000000000000000000e443d9953468b8cea4eca4f5968e214888e2b95bc20ece39483ac551d4e180c0b0a41c4668c8ddaf761a0ac03fbcad3000000000000000000000000000000000691930530ce86a1354d73cb21ee32d968e6d89b12e5a09a7991c7d27dec302348af7f49c3e0de91e1a1838aa11651e795f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000d7c6f9d4aa794f34596bb9af4d62363462d9804898ebd7c7db7544be1f46b4bde488ec59004adaa0cbe40aef525ce3f000000000000000000000000000000001094629b1428c4c284b7a64d0623e10ca0c4d395bccbfaad89d1a737a3887c10b714541f2681c33e674c3b99a36b7a450000000000000000000000000000000000d6812fad9c5ea365a64ebd3150238349d88b76d041ccaa7e637fdfa6c715d9d6dc3d3315cb95fd6919fe419d028783000000000000000000000000000000000eee5cb772ce02fe2a4883008f17570aebb902ad7c40b4024a5b24ff75b3aaa2b54ace6fb4601b1c62837a20204194dd740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b0000000000000000000000000000000001026d21e075fb8921dd849c98252a565d39ca9f5a62a825e7e3e77ab5be6620e76e45047e51350c48d9a4cf98a1222a9000000000000000000000000000000000f6459a8287bb2da77404a515dd7a35f46a4aa49ef72cd2cdefbc5e5242872df5f7b7aeae6848d59afa1dd142ae7caca0000000000000000000000000000000011e3545151d4e0b034b950cd2f1a3fc2d29e9d53250ade2482b7ea6075dacf7e8e777afa1e8e612b45028205235265970000000000000000000000000000000017a869d75144ece603c04d39cb56a487895cc882fec613f40f6a66601bdbbbb7748ec755553257d654d1558b1104a981f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000011602a23c9b5cc091a700114e5d3557bd4857c4fc44cb8628ef327ddeeb728927347438f123e2011f9cfda9b6dfc42e4000000000000000000000000000000000c4fad264ca95827e9cbb9783e36cb0b683fcc33038d47bc7ab6b65998770325588e5b910e811cf7d61fce13c3378d6700000000000000000000000000000000009b4711aa67e84434cabc289a78fae48ea86641a162d48b79bbcbfd56237705dd2d1e9ba3a18d737eec29eb8e940e58000000000000000000000000000000001160fc9e2a488ad9385140bb62ab48ee613c2284208cf2f92912e1b973ff81a5d3de338d9aa6881cbe437907890258fc8390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9", + "Expected": "000000000000000000000000000000000cd0d8c746ecc8d92fcf2232793282d7e0e17e0ec27ee851487eb7788f590db3487296061075f36c24f67cd4c4bbf36f0000000000000000000000000000000010c5e1d05070c27f19c228813051c6a830254542eb71469664c842695b219670dba8ddff858e2d147019847866f01084000000000000000000000000000000001799ca7d8f2637da761622b793a3ed3317d50b902a1cabefdfc776b0d0ef88b707b8a5c36786d5ede3d8a381de4e069d00000000000000000000000000000000129881a3b56e0014bf1dac4775f509f309c33406f2cf22df9a0ccd15c87ea48a868d4437303923127bf580b8d6ed0a8f", + "Name": "matter_g2_multiexp_62", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000d087c1b98f8c67c1bbc4389f21d9dab02faf46ee4223c609e7b9eb399132ae168bc12847c580f58edbb9255dca3b000000000000000000000000000000000065ded24bda39d2b830639fa511bce8dc770eb95e349d6874ce63b3355d23c1da3ee9771ad44e57c6c661b7453076fe7000000000000000000000000000000000fa3b2ef40a7c3d41f0c3a5f86afec252c6ce89bd1bf1f2192026e22fa256365360589c788753033658b1ba151797feb00000000000000000000000000000000105040ff4dc2bc435c2a82e1174e2ee0b94043d69074f01e8ed013da8c431f33c94a438a93b06774411780cdb72abbc8414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d00000000000000000000000000000000129c8c1db08ccd0dadd59b04df67a91fb6547d97ce23e59aa57cd3d38458e6baaa67285800809856e7e264d812e584390000000000000000000000000000000004a0be934248b4e142fc51745233b6d0ab2c46f53a8f9d4c84981e5eacff146ee6227de289c713e4ce24a4341572c9d70000000000000000000000000000000005916d14a8592af57a40418b10376e8e20f70929d2ba568c1fb70e343a1dfcf3e63c791cb639bec49c50aebd2f816fdf0000000000000000000000000000000018682c66a461a69b11d7c32f7aca07749e05a23fc46547bac121752aef64e9bb98a274d15a14faa93af8f284790acb9b99eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef9420000000000000000000000000000000001552053742eb89ae3d0b95be919c84e53919c898ada92d3eaf05605a19ac910091fc08a65e9764f3108877c837d478c00000000000000000000000000000000118e5d22f6df0e6bc7447177ce06659f94315478385372046b649fa6d39fefeeb492e6623e0160bc47233f4d3143e326000000000000000000000000000000000dd02c30cfdea5abd3550a9f28b546d82d5b3043f012de622d892062945847748ba820555fb811fb3382791ec43ce1f700000000000000000000000000000000050373898b396d9a641e2f2ed832c7619515fd9070852b891b4ce0b5bb5ea8b5e24248297d53e9db7cb946e76c4433fa49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee0000000000000000000000000000000006b465f4b9d60a3a14e119c54a7c35172bd648c86a7cf331e80ba849fc87b9dcd48410e3c9a07b634e83fc7dd71e5b9f000000000000000000000000000000000283ad9c77f549042f79c47b8a69e72164f0ee77aee50c20519d2b89029c63ea86dde2744cd21eb5d37e896c3abbdf56000000000000000000000000000000001668b08a87787928afe92d941240e503da07b646a34cf82ed09d4c2f4d479aa24358c8475eebd9bcfaa6bae17c430cfd00000000000000000000000000000000150e5b28bd901f7a2a9af44bfd6b78cc84900dc05e334de306f9a45f1e67708adddf4dcede8150a39670054f97a643436e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d00000000000000000000000000000000060d75764a92e30e80e7c1a6df1482585f4de901bbc36dd9d8978a76c12c739f85a9ba16741d0b19ed480fe2dc331e5b000000000000000000000000000000000024fd15c9e5b8872d2e9dae9ae96102bfb0e31d15e92a24316818862dd8ca7a6fef271d499fed5e0db6dfebc4c72e0200000000000000000000000000000000058cda551e1fcd701c6a3880b276a2f7536a26aa366a6425a1c42cf31eec678551f489a27f23ed5dbc76f19b0fbfae43000000000000000000000000000000001152e2cfdb584295563af8120c523a9f4c01cf72da64fcbe0a90a284d693a3089f299bc760166be062cf9f8efb6a951ad3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000116fc24e980b2e7ad6bf17bcd7c4f06e654bbf766ea0238a66d738bf3c2d41c8c63bd52f81553cca5fea91f5f9b74a2c0000000000000000000000000000000001078f19ecf785a5e0d3e764b7d6ea47b2d077b5eb222f4e6a9451f134ff0d77a0b9a3b53caf599705d131e3b17b6ca9000000000000000000000000000000000e44c07f00a1f198583a8ffca43da45d8e54e1f2a85bee7afff6c1c733b5d0b5712961c4b6d344869a8e4de3b34218e000000000000000000000000000000000083c78b3568cdf808b75d9ee2b03b98cd516bb16ca8cc35757f53f12119747bf6b5b0605bdffb2f079cbc69e99ee0bad6de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000087ded6945bd6fae7a0aebb1ea68d3cd34588035531a6cb00fcf1b83e06f7ec21cd3486580165c1364027b43e238e34d00000000000000000000000000000000005a2fe8a9871273bb60cc7ebef44a361300a1033f3f0230a731f5723fca124ec9d305cfde45802482a45942154398cd00000000000000000000000000000000121eb94a41f9e133adf082ef651272c178d780a1c31ba8797f60a208ad36b4c703c9b6c08be845f8844dd14d6406734d000000000000000000000000000000000e5e3da7c91ab4cca1c9286020aab9795e64e667d55a5a700241f9589aa3519639f168d040a0027ac057f334a9f740aba28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f00000000000000000000000000000000041f9117b426938acb40c905bbcba443c043bb55cf9b876edfa2ca051b6354124f0fa54d6a88ea172c3f5c10c6d921b3000000000000000000000000000000001828dc0b9533274db6afc802b2fadaacf57f28126094b6b9038ed5f6bbae0112c873fe5eed15bc49b970461abc2f5c3200000000000000000000000000000000107df6da02f106ae47718959aeba7b4fb4a8f0e2651560e2f2266a62566e13a5af86430b8800543f5eb6b1e96be79c69000000000000000000000000000000001628fd4a598813133de75cd7c96ff3711b6bc826806b96d07e5a89cd549592f0f51c84aa9ee0642cffae5630ca1ebae1a3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c000000000000000000000000000000000e8fff44163cd9c2a4e148eef3cbbee19ab8f648da1a8d438be27d2b0bcab393fb7d49e096d9a7abed3d8f82c11c4e03000000000000000000000000000000001274335d8bde3d14924f8d7ba18fea82bbc85427892f18fb741c8ecc5f2d6d7bee74c68058164c55db3cb8da8597bfe40000000000000000000000000000000010c7fc728c094e47569f0e75446c399d20a1239b511e34d8d6193dd32df607dfaa4377a1825b3892a9f74ff4efa0d9df00000000000000000000000000000000067d904122a6581b5d5a60acfe8156dcb6c10ed083840e506487b5dd9117927663e0ad883fb91b4914778ae082de0a7eb90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d000000000000000000000000000000000a909706e3ce45c86f2c30de5e820c8c9eefef207e530fd504511827f5e6422714d3f4224afa6bbba22ffca533d647390000000000000000000000000000000013ff61472ddc0d70207692648087c283763ede668ae380b0b9d6ae6593498b0adc9d4e4fcc73b5cce250e7563f7577de000000000000000000000000000000000a81db69eca785373c4dcbafd8635b23a9f41265e91152f309fb2945622937e65b5c17656abf8aff042a1fd1e5e50341000000000000000000000000000000000c66269c3ccd9e91766d1a640789bde6de752d08ffe3b2955df8dad3d2a0b6cea9013af235cbfbccee8271a7242e310614f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb51800000000000000000000000000000000181f3f4a16696980bd0eb9bd10ff1084ffe90bcb65f12f505b25f0a26dc1d4e16987d486b2c0b117fd6f2e356b83a5250000000000000000000000000000000010d7be6788da3ec56c87acee68ea8a03e7d467f816060207bb163dfcf8a4e7721651bf2bb23d5bc390d50fb1ee6625a900000000000000000000000000000000196c1ac817493f51d9ca891b55fa65ad5192df83cdb63eb1a634ad54e2d627f7feaa68780418f5354e6cc09cdf2f6c5800000000000000000000000000000000190f36690b8d36f2e295b9625f23afef9d9babe87c1ba0303f60c6d44ec952ba6bf8356469cff9d952f8e26bdb86ca06e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a1000000000000000000000000000000000f77df606f0611856c449c58393f4ee7a6225a5bee667382a48f59dfc747736a895d598f90ab26002dd0ed3a5a8f5a200000000000000000000000000000000012aa50d0ec440884fc6c2f7a0e8db8a5e79160f0c482209ae1a1aca2b9dfedfec6d6ea09252a373ea57905130220a4820000000000000000000000000000000004773f46165cdb19cae49cc42663316df39586c62be5b827535f138e1fca8dcf62ba42ab60ac6dcec85e8496f32b9eda0000000000000000000000000000000010c91923c2c7b3eb2cd9aaf0455c0eb035e38e5352d218b07ea23f50040ea58fd548b373c1bee9113d3d44fcb25f6ba08c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e0389300000000000000000000000000000000044b95fd5f0e049abfdc2adc699646afa5b0f64464779efacce85a5279477697090615933069992bf30036c6ac70dfe50000000000000000000000000000000002778e7dacc5566354c24ea1144613a5ce8a38eb56d53d230ca145ce83d5ed88596afe243df22cba10f423e64a7c103a0000000000000000000000000000000017e87cd2752d8674c373c557ab2b922e02620a070aacf6f5b3d3d07ca35d89ed2666da7246b800717c0e4763dc35f5f6000000000000000000000000000000000a3ed312e5f309eafaed486629d953970cb73f839bf30f506c2f393df4c283f299d6c643ae6c229430d919e8aeae8bd839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc3652000000000000000000000000000000001082a0edac6267151c8ef11fac7614b74cf58b39b72fb71e4d66467ed4fb3264b177c691e569230f2a13a64b4a48c6fc000000000000000000000000000000000073a8d5f96ee580741bee1f82cacb6139d962fec34c44c648c8fcd0322796429bbaef083a11b4c8fa376d4c00cd79c00000000000000000000000000000000008d41e51dc2822e0f14b992511de799fe4db3783a05ddc1026a53faa89af000075ba5aa830ceb7551e51f0fff144c1360000000000000000000000000000000006bc4bf0bdf350af417160d06e8aebf2dde02c9b50be39b0c4dcb3a045f9e04f1f041f6de10328e287df6121247dd4e9c395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000802e7b71127a15a279a629e89f194b51d19c4f329efd8ecf9fe69d340dd06068c8467da6ab39be25c194077d3ce2428000000000000000000000000000000000250172c787afe866b428748be8359d8e0bad161832abc108c850362c5839237483fb38678d77c94696260508907726a000000000000000000000000000000000d46223c1666f314f9a1e32a94f83d8150755d71252e19af91a3b460ab0ade2db2364d8c6217cb422095f0d9a1ed648a0000000000000000000000000000000002fc2849014717d1c07935efe601325e1842ed333897222f6de322dac8b50bf4d9859eed8880a34676af0d0e3277639053ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e10000000000000000000000000000000004151d78d65b0c9eb26822e20d90ace8fac209a1f08f62ce722ae3effd7fcc476f4c0179e71b09fc181db96fb2ea4eec0000000000000000000000000000000013d17ef429483be98411947ca0771ce671fc38e27bd0aa4abcfd5ddf1af9e138404d86f4c2ed74702f80a573638d92f500000000000000000000000000000000178f2a7eb43b9f88acfa892b5868d7f7c5787a399c1c566de39ecedbfe88357fd5256ec57e1ba12e9784382c14331756000000000000000000000000000000000253a391373974beef746c4397654a30a68992fe9163f9518ff0ed9b7be37b858ac60c95259ab894bb6acfd123333b7fbadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa", + "Expected": "000000000000000000000000000000000710bfc39e92b0b9d15ee9bdb4959daa3a78f66aeae29eaeb50a0aa0460f3ff703c86eec8903011b4b61a0dea725ab08000000000000000000000000000000000856fe7a074d37786237cc14ff1bc53c735ee8133b231dd3fc63dfa0dbd1979304bcc7b55cd1bb66fd7529e15d15db5800000000000000000000000000000000014757f1fbfd4fa7935ebfe65e150519d6eb4f4831890df4b236dda98804b79862fb6699b587c3e568fd6de1e582409900000000000000000000000000000000000f7b54e4961dab9e94b1c4b897177dfa74be9937694a38207ddc9d6290dae1d5e122cfe4c8c31d853db3783999a7f0", + "Name": "matter_g2_multiexp_63", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003bfd2535c6d8ffb44670bd02b5aa6f050f5cfae7266fc3225865bc3f34320820eaeaa952f80da51671f6d97b3df9d4f00000000000000000000000000000000026c1adc0ffc3fef9ccf018ff9a647ef5c69c5133fb4a6566cdcbd3180d9ee784f34d667edb1dd54ae292253b45576b4000000000000000000000000000000000ee90fb541becf96b4728f1859aee5ae74e30ba9193b90569b66b0e1d087eb81f30c21774298cb06e7dbee8f8aafb1930000000000000000000000000000000000a4361867bca952446f64c273735764e141eef43d156d6cbb6e68dbf3bc940e935f7bf3a77b57fca4bbc74bda2f26532c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000004bdef85b0da28e0531734016e5953256c75c3620937736cf65de5f05b8beff294677668047a3b74f0f135b846a95bd6000000000000000000000000000000000b754df2aef855b4a0eb6f6aa03115ee8f38a31fc852381deef2b59bf23e2c885ae166030ccadd5673bacc35482f81e9000000000000000000000000000000000f1d760ac6dfb65b39c999211d4e4c3623c3fb8ea59cdcf926249a07285a8e4da1890327fed20ff07f12359f6d9035980000000000000000000000000000000009f2698239c8b452748126ffd83abec768edffb83dfa3dc7943fd499c8980e2d9aad76dc38b336a4a63eccf5c4150ce0b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c000000000000000000000000000000000cd724c51fd56528dfa688df46f71bbfc9144ff98958b559fca8fd05eda01c38c28630ee19579012b9913a393264cd90000000000000000000000000000000000aa1e55f2b6d9385ec6a9cbafcdbad157f7ebc06b2e30e2380ac54e71db5259cb919e17042d6ba6e045f1358aef276ea0000000000000000000000000000000010181ce9ffe235b6b271d570b3c2d6e1be60c53b4a98ef5e8d7d00b463e5bbc9d8d96dda881e58746090983d6f8edd35000000000000000000000000000000000333deb8b14f499319ad675f482fecd80f9a69ba369425decd441cd2ff5c3c77f11075f61bb1d90d0be850ff657d6b7cccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000001581a5440fe892ee6eece5fc2227fe072dfbc440e0620a1e5fb505ff0b16d9e6033d83c83576b4b6ff87a807dc81b88400000000000000000000000000000000099b070a0d7497f33c1c478ac424d5564fa645d836a3d572d98782f08713d8e425b571433fee928475688db2b3a9a04c0000000000000000000000000000000011e1cbaa09a6361aff9e199e21bc52e98dfacc49ed83e732d4b4f2503b3bfdf85d029dead4412b6f3d7ea447e20d669b0000000000000000000000000000000005503e151d620e9a5a142e4f7940ed88375e7efc1109214141c191e9f38a32a40d3a92d6094584e763e0cf13cbb54bcc99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000007c780f119bbccfd658f3f1b69ce9c56b1f5269bded713b6827d97d32b2a6deadcc02c410138d984d977527f3609cc2c00000000000000000000000000000000095aebacfa33928a916ca7b0ceac699c71620781b35cb2f3b254bdbd1544b728a2ec1fb35416ed7a8a3a630bc07ff8720000000000000000000000000000000012194abf7e411f4961b6f8a1e2ad052c27624ded863d7a9132d9c7ecd3b4074ef0060cd86adb73056323f4227ba5fa9e0000000000000000000000000000000002fde2be9ac1e8265f258a09eec85a70112ef1eadc3a91429c9206555933e2b89aaf7493fb833e33e5d61be28a12a1c2338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d000000000000000000000000000000001586c65405e810e1d5b59304bb4555ca43c04a593671ec64d5ed2d2e626b1f8a89f48a4b21d38fb49909b8c614209a460000000000000000000000000000000014528cdf994e774b8fd54090cb45b68098c1ad9a351bc1f36a9393f3b4364f5beaf58fff6e5f8b21a85b67bc427c0e920000000000000000000000000000000000b48d8713aee51d80c79109fb8b4e0c6e32e25a7ca24dd3e7700f8f3195730375208b241b2c722af3c2295a1704cbb3000000000000000000000000000000001913cf6328429cf2966a48117dc74db0d45be7800f93cfbebf597fb48a8bdcae4fae2df7835f9536481f67261755da2a1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a0000000000000000000000000000000017e45a481449f167fd579accc896ac65aff6f1f7392df47d006b404de3cb7ebf6cb59d0913438f3a51e55a0ae3d446c9000000000000000000000000000000000cf4b7db343bea29af6e244a71880538b41b826bfd1d06a21512d00ce58f5d7500ab1ed77b446b1e3782df736bf3dbb6000000000000000000000000000000000525d08e134779ca7614784818876514e14b65e799b7832f61a63601fc491c8b9cb25430547f961cc1c22100170a2065000000000000000000000000000000000450cc2156c4716d0343f32aca82fd2d0712389b1aa984b31d51edc2aa0545c88ff52e470b15eb6b2c22e30f79864dc85202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000004147c105ee8b4db68482b9d7f6a716ea1474b6c62efc41b9444ed1ef9e92e2b7010a1c1ecc59038ac37b385074a6bce0000000000000000000000000000000018a600a85c5c38be835d2e91a35cce4b59e5f5ac3b735fc007bf5498062beca9befc9c8ead58f9f21f6e08266b149d800000000000000000000000000000000012a476fcb81ab66e3101de2364cb609b17e06eabdff5246bf736eb9d5c87fddd404e8867578262f07a05731b04069164000000000000000000000000000000000c54a888678c28766ad17a18507e4bf5dc57dd394eb6e9b69abaf15e645cf4779bf6ccf4314d2756584647cf27af089ba2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f9722000000000000000000000000000000000e809152c44cebdd8b40f0d22d57c3b31f29700e0cbc3e69f660bf7270e59093d84bf7ac358be7e45e799a75cf9c13df000000000000000000000000000000000c6c61f98bd4e3b7095fc7f1196baa98139087df00fae2a795e76544ca47e453f75929cab07c11cd3595de6ecbbbaff000000000000000000000000000000000171c70446c19fec3c152741925c8db28ab0d140720cb6a6c45e9bc66c012a421d12271889ea43fe1524944ff572fe6850000000000000000000000000000000006e4baa09b4660c69cace151e60320b771e56e7460b01442bfcf26823c17779034ac241b9365dbbfade770d2056eeecd892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf000000000000000000000000000000001917a23350e94963e3a7488ac1dafefe9ab11856d405eff39d655e31ba808f02954b63e822613d3c6e5f358be04be4a4000000000000000000000000000000001620211b06288c16aa02f4404192e9f57a048e900f0ec5db9b478475f13b142f924c6de720031b3fc12cf869b422af470000000000000000000000000000000011e8ded9ad57e46713e7ac0044ee4edec12689cdfb98838a74adf1a35244e3d9a4a34c81323b089c10422abf26b044e70000000000000000000000000000000006f85c7478cec590fe3355a8d6e9557c5be084c161e090c72f1281be4ee56f36aa1e3c9c844eb45d9e295c15c4cd903efdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000d555d9f23de97318dafb257cf444952bdd3e844e9ed5ce193c10b76f5179f0c6851f93af1553b128f34d3a7e75339f3000000000000000000000000000000000132704571a12a58f629dab48f1a3956392b40f801c2b3757c15f7be46ef1d9115d89920c460c0e2bb062b3cc1aaed7400000000000000000000000000000000152829eaef900fd2f19d6fdbb8f7eb3b02df35d218b494d075219b69016256e572eb7f555f6fbdbe17c59a666d190055000000000000000000000000000000000fe5c67c949b7c89a867301528f0ab24b04d31d6f18f575c475ab5a6098f7187eef20a9ed6e810684da9afd8de96ded6660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000128bf3cbb5208d84dff719ced229921a889c9a4d02f5a508187662f03852531fb8be1f4c2aa9ef01de7720c352dbd19d00000000000000000000000000000000158d89a44b8fcf9ca8c96a8e516e130ae8af19ed71c2b8487ae300c3cdb546e248728bc58fd9cfef21107e0dabf44fc20000000000000000000000000000000012b70b42c8af4551267a94a795fe18e8d054291225438adaa33fe2edafa87742fc3709abcc7bada5d26e3a14649cb47f0000000000000000000000000000000015a853160b7666ea7d64aacd931314497ac7068a4b8bfe3a7deed85df2bb8dba277716a9d1ee50c56b2970016ada509d1ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af840900000000000000000000000000000000087c7bf08e085e19f0cb301d2e36478357e835620b1cde6e132c237ff6fc63e6fc16a8753550d50fb93a0a1741302cf9000000000000000000000000000000000615299ccefe4da879e5f4b01d6b6ef8358bb59ed8a2b365ec72003c16486d3266243db81f48855d81b6a25440bb861a0000000000000000000000000000000001498fd20640f39dbc03a474f4514e5e283256ac19468077af1c9ddaa40759dcf93afe256de1e49be6469fa106394193000000000000000000000000000000000cba50fc4919a29be2f4e74c261487dbf855db1856e8d5d008cc3f4ee5eb3babfdfaff878adae49b96db99d424bc4dab2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000001369dd82ed013474581ca1ab2d2133341d7c1d52065060d72b8317e899e79e9077bcefe6c76c3c7f67e54f76dd3c246c000000000000000000000000000000000405aa84d3ceb02bf8eae989a9cd65afa15451443af6f3cf5e70f5cd7bb8d413c57ac3893a7e8b888ae93a92dcfa2b20000000000000000000000000000000000378d003988f3c6c16d3b12ef47a4a49e2d3d2c7c67e384bcd510939581770aed92e06291ed3b7c742769f0d1ef740c100000000000000000000000000000000048bfa6550711a17d52f48377821baae6f3de6ad99ccfeb8302466047dfddee8005240cdc65b3ab11ed85b11f128624957f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d0000000000000000000000000000000013370ab697da0ff0a0efa8ebc7589b465374c983c13daee7b5451e8b299933eb5a4d255ffe4aa46782ae0916fd3990230000000000000000000000000000000002ee77be6e0b6fd260ad660a96100bf3259329faf2ff9796102928e70cd52c2bda8d0d1da1d484d7b023d3d59725d12b0000000000000000000000000000000014482fee88e02e61b847c08e61d7ae6fca2d993bbb69bf1653138150d5d7fed09cd5cd4097cb4b6368ea8023383477cf0000000000000000000000000000000009d0380d0d6fa39c9e242b9a67336d86445551658bc29fbd594239a76d7741ba388450caa244fb186afc36d35c8740e93017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f000000000000000000000000000000001537d4a47247af8f60f77d309666056c412ce089f3f011457e894f74fa4ad5168baafd36ed3294f5f61cc9cd8f87554500000000000000000000000000000000119e43382a846c8945e58dc7723a0f24b24d9cd487d436a156156a6da97795cf3f4ce382d21435695949b5137a2bf1d3000000000000000000000000000000000be5fd015998bd6043f124048c82e4d848e1b8c87442d0021390cba41c294de17648a47dacc06268606ba73cc95ae6e70000000000000000000000000000000000e05a3dbbf3da8320c40d51ac44c6380d56ecb460b0e7094819aa6af4d7c70d1541d4bc1fc5afd453b165f3d48d09a708bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd", + "Expected": "000000000000000000000000000000000b00b5c14685ddd17ee99c74598e6bfae5bb1c103f8ebfaec3a620ba57312f3093f9ad5eac820d81096dfece90e72ef8000000000000000000000000000000000dd81552160d449cd787ac27c76685ea0dc993a9fcf8ab182f1ff5d8a484a47c14c1c1a785285b44336c7f6fc0732a0c0000000000000000000000000000000003008b6d97a12868554d294faa26e2ebe2920add650f841adfbf0ee89af72fc4da5dc23b45b7ff191a58c17971b50ae50000000000000000000000000000000013f438d927f35b04bee8fc55693d5c97229c8548ff9de39fae6e26c26f89623d3b0c810b9be8dcf0445910e8eac5c58b", + "Name": "matter_g2_multiexp_64", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e1f825b71cd9edcb231c178e160e37bea70108b369afb248edc7c6a59114c22e843fb5541e0f26c77a2b589ea88fb3d000000000000000000000000000000000d65d777e91920b17400955a4afecf82f67cd13f3e7c5d9c2076c4a4d8f7f26383d22d9977dfd0987f219a625c8a621200000000000000000000000000000000045716092850318c343f0dc5337df1a72f8c74dd729831d12103b46127c9180fb50cece34986a94fee6119e72d16a55e00000000000000000000000000000000083fac698ce800786719d1f6063c87d9f728da03cea2545b4ad8831f6c24bfff73e80f2c2fff1532f6d1fea60e7d438ccc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d00000000000000000000000000000000171d60b76698d4d3f14b4eacbdce9fa66b8c3cc7ecfb989439330fbf0d051d95f3007c389113346e614f5ec8cd170a2100000000000000000000000000000000151a96beb250bdeca3cdad1b07322040bf1cf2105dfa854bb24fa76c8abc25ef4fb924ff995da641244f9daccff2ff970000000000000000000000000000000007e5818778a8331cdcd1432b46abf1efcdf7e4aa8907fd42d5e7d14b57dbfc48125246b57587755ee1571be8b52d2c57000000000000000000000000000000000693eb562e22fa8ca4a655b76e43b50fe487ca1d65cc3867eaf793e50496f0b4658bd92199104c2ab92e4ac53c44db6f57b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000001059d23ec6e472937d80829256db506d2d2deb37d4b750a980568cd5b0db085358a4d610d59009b64db1a9225f9f6f5300000000000000000000000000000000053d9ffc47672f1058856aa08e51aebd469111dcd129ed542454d6401e7893323f8a9c63641f499cd8617c7389518f8e0000000000000000000000000000000002b9b30a5e37b18af4bb02ae8cdd56f6a87820716ea1522a174a0d99c3716295ad0ff2daf663697cb56bc6053c9dba610000000000000000000000000000000019d3230c0bdc228fa0cfd5e0d8bb88be959e70e59d931d9f9e3683d5e65d8ba0d121fcea329b23c5905b80dac34de33b03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000015a619addc75f425596f9a51c6cf2259087cf32afe9b1f07e346a2f4e1f8caa001dc10098d1287b89837f426d073982d000000000000000000000000000000001660598fcd3ab6a55423138ee72a4ca7b57277f6ce140f9f992dd9934bcda78513516df0d309a0e8ea151b2742dceddd0000000000000000000000000000000004cce7d84e0763fbbb54376833ddd7408afe3f741bc2b7e42fef3789a005134cc5540981a15a9f256e0e541ac58ff3b10000000000000000000000000000000019c20a0064f89d37548e06d63d8ff4fbf3584d5bcc2fc2757339b7c89db6d5da76d43b31da7364259187ed602e79bf4f976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f000000000000000000000000000000000d7ec5a27ac44daeebab7658011624c441e45924cce97d5bda354f1daf9362f5bce2ddf57151fa07f78740a7db170e8300000000000000000000000000000000121ee325f4252ae5cdd3e3495f36492d68d9dbe13249039d1185760e6e48a789744b2a9946a3d6478a64b378f76b0de300000000000000000000000000000000014c6c5b98c1e214f78b82f1b3be4c32c5013934b1231fec942b5591d3f0440bf63b1505cfbb7a8fa78a85ba58fd4aa90000000000000000000000000000000016aaea3bd0ae91b9d18ff89a40ae27b68d74f3a227383138ed737d59c19ac578da03df83f04c8d962cb9d6f84a15302f3aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c8300000000000000000000000000000000188e5aed425a768f89f5ce09b2cc909b28c6a0165787c8e3750fca8e8162128ecf62ef0ff853d206d23bc076335008e70000000000000000000000000000000001cfd330da0d1b5b92b6533cf5a8b6b70bd93daec4373f28d669f5e970a947fd813ab1d1272b61afbd2748922b87c8c300000000000000000000000000000000002aec750fd085c99c3b9c3af62b6deddd85e49eba0293e6e8160b26a3945af546a760b8f8f85120d6a51d22313cd33800000000000000000000000000000000162a109abce2edef753ca6351aaa9cecdeac20919681c672dbb183b5b26649e885ff081b9d3687f802dbe20fda43462af9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000f7ad6a1dd9f8cf52bef02ae1e82b0d20dcacfaa5c169a485bf8becec8b51373fae851ca29e64385f0b7024eb0bcf9270000000000000000000000000000000010412a7a710f842fe836414e2729d0ff2e145709d8f7b5e3964af3e0ae267ac53dac3db1e6d2b7f7671ec34b18c844a10000000000000000000000000000000002d3b96fab0e3b8fe44e316fcc5e35f06dab83f2c531a777e162f7521cdd5767ad0b6f877f876f73d2ff663d9b71f462000000000000000000000000000000000c09a98bf623e82a4d2d4b63fb867fab5d3bb1f85a0669c4c11cebaeb357c0717a0f246a9ce4064b7351dcf1e77cdbd393be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000114270d35ebff55c0341776086d893513595aca3b200ab98c8b586029b19a360a04f2e77e90d382174296443ab8531d10000000000000000000000000000000008b88849c3cda9a23d37ec9f4700904edb24be95fbbe6d9e20ced0d52208b597d44bb9269830a1ac5cda35d0c0a03c9e000000000000000000000000000000001144466b13427c10ad7679567067dc47c671107064fbb9bad287924c9bdee653c395dc2654caa5b3013ade932fddd5e50000000000000000000000000000000008e14e3cff3bb57f0d87680a0c09d745c7272bd3c216ff9fde7c03df2caffc27e0bfd9f99912855c156a787200752c125d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd30000000000000000000000000000000015cda76d42de9fa86f900a5180ff016155f31b9276c617ef664202848d2efd2876d412402516c0c3d26d49f71d894acf000000000000000000000000000000001307fa2b963fc19583b7e4ef2e9dddbe93e2505e8f4f00ec52db26ab411002136c1f646b1cda71e19480c767906a6d03000000000000000000000000000000000ba87b08173c841a2bfbe424584d4685c39bdd0f83f278f9fbafa8111102aa3acfad5aabbe032c7123631fb8b454255b0000000000000000000000000000000016c525c1dc247fdf34344168b7cc245579585fdbdd6fd783cbe60b727cd11ee97b87a86647f78dda207c98e65c2ee7e6854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b24000000000000000000000000000000000403887fd4429f44f8da7f17ca072f867e88ac046922ebe3e1e6c4f9d8e174399e7648aca924a557dbf7b29c540db33f000000000000000000000000000000000522324700fb6b2c43eb5b39e0da94cb60e234369543f530ea47f4aa510ec0fd79cdf4dd3ae046e21d78b9c0e35107900000000000000000000000000000000015e946b90984257ffe3814dcc3ef065fed1504f0790f3564c8bfad4e97cffdb61c0d73bb0b1dbe78c4266c773abd56b500000000000000000000000000000000078f604630074ebedbd836c463f3879cd5d4a2c947da0e47740ec369112f4fedd787ae59bea69aab61b91f05d92061036f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000f722bfebd55f75f3bbd0a55492499c3a3f637ead0e54270042fcc88853df5bc5f11a3677efa26d31c28368e00c8713700000000000000000000000000000000182618bc8a4b3f6556d79848f90efd6883df90806a8358cb6852bde465a27a70644ac5d5040d4f64ec355763f1a384990000000000000000000000000000000015f717739a1cbb2eab30e7b1bd9b25f57ad56f36016b59128ea1f2089f2d1dd0128b455b1b0e9e3b320f68a38a1bdfac000000000000000000000000000000000b855788d6b6a7748aa923dea3163fe525a7b43f4619c1eff3f9219ec3d98ceaf34b97bfd19aa6f91f7fcff728728978191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e200000000000000000000000000000000120048ace47bc1ab3fdc07713b91a9223fe0fffdcbeaabc8a61351d756f936e18177f672c5a4db7b9dc29bad16bb7c4c00000000000000000000000000000000101275492a6e843306f2927b6ab540d7a5ee925bdab40103b4ddd885e444e6a6ec2d6e99c061284a1967797d8a2e9e700000000000000000000000000000000002c12f17a5dd2c56aed0d308367f37510f83c94a4482e5f632161dd0517dc2d4f46a90bbc13034c63dbd04fe4c616e320000000000000000000000000000000000e4b9089155ce2178f26b058f4bfef57b73aafb83b0b78138a01890a167709f79100a1e4d797c5849473eb3486cffa4b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000018180eee7e72b6a4bc2e60555236da335fe05fcbe2b3cca4937e73a550aeae6274122ba84ace78eff84d323b4196f58400000000000000000000000000000000147659347e0fac7a16c92950ea5fd115416072f339d7de3cc0f00ef369f5122ff050d8515effacc825c807f7e19650e10000000000000000000000000000000017bdbcae7f63052af9a7d8bd71dc98b6eca7ecf5eee7632959fe56ed51278099690c534ec33be4ace4612b0f516794aa000000000000000000000000000000000d6fa233be4d6d783bf973cca3740cbaf0f719827d7f9310f38d1dd9d1c1f125cdfca6d12fbf6a8e8104f79bf30b00647d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000082f3beaafa575e86be53b4fe7b93835b00759f921933402282e5bb0e643a812e0e4b676ad51ff2c6f5332d777641cc3000000000000000000000000000000001760b87bc4d2c13122fd7acc6d629c9f9db9bc9a2c49634aaf33e258ceb3106bc2755b227c6660a1df1d92c60067cf5a0000000000000000000000000000000016a819d7109c9a12199eb98537a730908a693767cdb35a69b4c7329761939afea766f0b91ae405e273227330761a53dc0000000000000000000000000000000009d14d7138440349e83f5ded46d18b886ef3cd63e0e5bfa0a8b50985142b21a4733813ec347e40cabe28e6ec1e068c24a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000001676d7f489219b56c198f8494e156fc0672ae28dab20021b7a6018436c7c0f107efd2493ddc2a1cfb3ad490ef146348300000000000000000000000000000000001106e89fc098ce7bd8bead5d7f6432bc54501370ae6544f34cfd996b3b610f9cfc7ad366751ae1211b848aad7d93d30000000000000000000000000000000011f8f0bd037365b5427e76d57b018c1c644034b28d06c8f68c59bff45eb4a2c4d761d066d96c13f7e73dfd80c81704a0000000000000000000000000000000000fc826b5957613f35bfa36d3ce088dfbbd06c8f2e88056a22a9f35db561e06fa0378ccff29ba8b81cc12c7a504f8c704c0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc0000000000000000000000000000000014f0f64acb0d9638a68278099abf5b5da3aa087792bef15192cfe3689b69b7ec1aefbfa14e659358b5410d98d2eedac50000000000000000000000000000000015ca79c92e98cf8314a2f6319520e1eb7d4656ca6e51278710cefd9c768a25691fc58e983aaf858d3c8d0ed73e2beec300000000000000000000000000000000007a5192f1dc906693568291f163e9632c53e1f418a87cd25656064adffbf31863680468f3ea451fbd22ac990dc870b3000000000000000000000000000000000131d2e3f6956da8941e8340259b8a15aee9fc6f23573f9a348ee9a51bbca1308dc54e7b4675357e3a9c5971be3a5c16af833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94", + "Expected": "00000000000000000000000000000000163da4bf7e159e05eec90318a8ddad4a59fb02d7ae2fe18795d38c3ccaf797188fa16577e6a421ccfb12ba1ed573c4e6000000000000000000000000000000001256654eef3352b09e0027277aec042519d99eb2567fce2cfa50a0c251c12d3593604739128171bfc13b3bfd1ce8f9e8000000000000000000000000000000000b8a46123bc863bed525f97166bcb77504eeeb66d2db207eb8342a3d18f7f5a99910fae3e6423c6e84e437a2c4b24363000000000000000000000000000000000b73cf08023c8572f48c132add67dda7a15def638a01b198361b9d21a4634ba76ceed9819b37c12e24f148d255483856", + "Name": "matter_g2_multiexp_65", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003113ba8b216d933fe0c81f23f75942c0065d21d8f009d73b1f698281408874e33dd2750fd4298367b81827cf6fdad34000000000000000000000000000000000b8a921e840fc665a786d826f83ca5a9c8f00e7c802bce5473a7d1ebf63e8bb6cf5c4b426153508d874064d1f1dade09000000000000000000000000000000001492ab584088d23d3b0d1283904f9a8f29f9efe47950c6e9ffb9db2123f3f9820b906d672fc7f97f0bd38b8fc0ef44520000000000000000000000000000000010d321c2538f92aad4631af44ae39e63dc06becd2460f0cee0e526328d167fd6cfbcf4edfaafe32d13b5fe66c009533bb16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000ae75d01481a51294003041afc4802326ab878a3a75eafcda43cf873cc65e300d28aa986fb82a2d1d649e5be00f956820000000000000000000000000000000017640eeef8982250f88a4d187dcabfcc9adc3ee9194dbc3c04c741690fce5bc7cb07cd0b7c3497191d9ed8558fd0d24c0000000000000000000000000000000007527fd8dacb81b8d1abc746688db6a47211fa71556155d38361921c4bdb2a9e9921a3a540bcf55c6dd751b84c04a1040000000000000000000000000000000008de9109ba354d7426a5313d66cd747a54df347f0f86a3c0f99e9e4b68fc79641fcf98ab39fa23ef6f1a781c48f53f76cf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000017331b8367f07756e789f7edce4d22f6886656fed78ddacef6987a2751dd3d5d49011a050e7b2a3e11fc8d90266c9d710000000000000000000000000000000016959c303e11f23392f95c1402d1d1ad7f38343c711e96f18d03f832f76e3e81de789a6eaff797ae51079b13571334d40000000000000000000000000000000004266fd13db1ca80196a91263c79d1583b717fb61fd9ce5113e4cd94c59e605152b244e10e364b468c5a561c6fa9715800000000000000000000000000000000026f67cb263be83f3163856f091e9346651c29d4634e242da53b22eb6e66018d235b0f30f8833310dff9f3020e5bd3811cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f42129000000000000000000000000000000000114d8babd11c81ca2b8a7e193afbe0a8fce426b83996bae6f77201870e51c9355c319dd86b985272f73e0804c0f53700000000000000000000000000000000016f5ea7610891d0e72975816c08e6e25a75c7c42500655f26efdfb384241bbc825358a21caff347d00c8b2391501d15400000000000000000000000000000000199c8c74a79ee90c3606906bbb8cc163c214259e4d0127cee3283bcd9c1ebe4090ca7d7b180201910d3f6f51566d3bdc00000000000000000000000000000000032c785165ad4c1a2846e15318bd7cf5b42ce8b675cb18fcc4232e28701f225f1ea384b276e7a38b2c9e2e8b112f1911d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf0000000000000000000000000000000000842c65006caed9b53add048a2eea89e1b4584e1deb4365e3dcf8b9ecb02f337bccbe5d6929ef8c20461847f171fd4d600000000000000000000000000000000100dc23e6c1c6f6756419a9bad3133bba052f408a424c5239b8528ad4429a2bce64b72f1463625f7599ce43865581e9600000000000000000000000000000000125b4d71333274a16e52829ad5eaaecdda5c206063473dedec5a8ff4424def70e6f650926948dd2158b403f985a3421b0000000000000000000000000000000006a031e3c002702837e4ad28250b85cd94d42cf7b0d765b980fab95edded7636d13bdef1be63e66682c4e297d0cb2b0302e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000003f7091a25da7d5afe6fa6b254604a1abe7a0c6ea11cc1a4167f5f648aa973d888383bc7e987b620d23e688868d318360000000000000000000000000000000016637f888efc3e057227cbefecb3037aebf8e330c3a794e51d691e3bc064237b98351beb746868aef977a83d1fe163ac00000000000000000000000000000000126d2884487984f851d1bd7d61bdb803321f263918e88e0677831563bacc9f5207358d1e9c76a5a25a66f0294f459e3900000000000000000000000000000000125c61b387a4462fa3bb2f06a4cfbd7df082d20cb23ee974aece2ec9a3b0c084d13a7ea83725a05d9f31b8033d2888ef9b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e0000000000000000000000000000000006cf2bc7c691c4f8a64d0aa1ca3760d715b3188a2dd299ab09c723315acba8b0b4bbee819ba06cc564f0c875a63a415b000000000000000000000000000000000bded3d695e471f30f9d723f55826eda112eb0e3fbfb9a377cfa07d6233ed84108b92a79bb491a2971e9afdf83db8e9a0000000000000000000000000000000009b0e9928cb267508d4f9444c6ac3dc6f64f49a70c82c0bcaf4022e97854e5d9ec2612a2cd4d67642dc0451583bcb24d00000000000000000000000000000000009347dcfebe93a2f7674ad02ac48794e7cbffb04dd85b0c8c192fc85cfb9cef40fd11def6f63ae9a923960424eac6a02f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa040000000000000000000000000000000013e8215c7bbdca445555c9fa0ae44e1905703334bade3294fc047ec262b9e4903880d52851967339eeadd666200b25ae0000000000000000000000000000000003b0bf4498103ac03601a8594b154b59a2a93d663f98ff8dbd2c85a1902e572a9456c629a12651aa87a1262102e1c770000000000000000000000000000000000e8bfd7d3fa0f773e6bcfd0d43a5c436862d1cb6a4ed715093c6782cd94699090c4bde597f65768e963fd0f8644e09b300000000000000000000000000000000064dab4d0d0c6b94c58b067337f2fac7d0d922cc822562b6bc941a794d96aac5ddb83d1d5844440d21d0a72a69303b8b2d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000098dd9a20f84fc26e78993a9de4d519aa2f8d343fbee501af945e5943e88425d29beb7ae54481b04175a07bf69b260a30000000000000000000000000000000007ef43e7a56e4e7d532420e152ce566d9055eadb4ef13d5698c49da905a4977fa8a7d3f51c8f5275582e1647984be61e0000000000000000000000000000000003755ee4432ea90f2197c7cc2e191dbbf7950c52a2c1b723f26d2aaf7a38c1b97efa29a312fed599f1199cf186400adc00000000000000000000000000000000150edc463f0a55fc70c2ffdd1f73a3abbdae459eb16adf79e96d18849ca638e6f41c6805b73755968be5cb110d81faa4efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d8000000000000000000000000000000000f7149602cbb3e5f2c5f8edfe59fc0fb8e1f03f89ea192bfe3990d87ccd28d4a80d7cd3003a8cfd669e1b6ff7e3cc5890000000000000000000000000000000006ffbc965bd06de07d8c0a9db8db5ab82d5f11afa1ad8eb92ed4453489f5899cc8c46ff02743956bed81229f64cf6efc00000000000000000000000000000000164cd3271ace4809eadeb1c0f769094272f3b66968690339bdb5da92e920cdc80c9d577ae4fa5b6426a5a6f46fba80bd00000000000000000000000000000000098f0a14a511ff424847d2b4d1b80a049b1f05ecd40af96b7a81def54486e4969011c122ca7dca3444029daeae2ecfc79848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000018409c0d0f37f4932cca87e24eb4d55e75dc98f938420ce036d43689fbdbbd839dc608b21d12a8af1d0a780aeac6617400000000000000000000000000000000109f2294669422a4946f926b1f106c2887893a042e3bf900559429c7fa484da4909216c8dcf826871534981021256741000000000000000000000000000000000a1ded19846e603b958d0bdcc9b554beca784b017d2a35ba117890fd0dbf729428bcd9823c7a378706220377c82a215c0000000000000000000000000000000000eafc89e30e4fc0544497e27674ec5b37ec0849fb382e608e09d0c1c94cb78bcb96ef4ea48e374aad1038881706fbcfcbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000e02cb3e099792ae7508321ce7afa323fd499de90c4006621ef5ce1054d0c934ae058a97ff8aeae0c88709c4d8ed0adf000000000000000000000000000000000e19318f5890320f17d5243adb4683a97e3e9763102c4fc93e3c3e3d24f4f61e0500be916c249dab00094b4ab048fe99000000000000000000000000000000000989faafcf6156472368b282313e076613cfe7ff135eb131b49e58932cbfafecf6585009d1f17ff8941d7f871be23e9e000000000000000000000000000000001167419d097ae8b96993b2e67da79b658adde1e12e43c71f27835845c7077f385612158d3e59fe2cb32b9418463e672679eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d63000000000000000000000000000000001579b7d03d3d2c8a280e8ca113bcc98afa6a2705a5d228d92807a85cd5a1ee97510f632293a478c3fe0bd383f4b69cdd00000000000000000000000000000000107cc2e6bd02251bfd565b4b848adaa84babe9d4f083e827ceae6bacd9c9c221f0dbbef53278175bf27ebfe5949fcf8c00000000000000000000000000000000018d187c566690e4edd8d8abe5e0a448e352f622c96680378051228b6d081a4914aa51383326aedf45e351612ad6c5d000000000000000000000000000000000197427117a52f82aa6e931ecb0c5ffeec7f73ee8f44c5816935d26c06cc8285200ff9240d98cc244708e00669460f98b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000019bd947df5a437a7f1ca2340bec628f2783cc1760dbc4a97ae10093aedd9f64e25ba79d9f4ce678f4fec91a3b1eef2d7000000000000000000000000000000000770e0c39988c9d8eca076464a3e10e274b06b1d2f6230e6dbd8dd59dd9c062f8958c6870c44ff196341bb9f65b8db38000000000000000000000000000000000a1833ef19e2b8e31577e5cd26e0a7fa46a5d25355d8b3dc0605f53714a60423556f3bcf17649745695f68f26570de0b000000000000000000000000000000000f449aed4120f3bef05506f2463f4546c7ea67b9e9110d3942dc256400d063dcc571305b1d4cd2bc3f18cf25319286e8eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000c1f9688ea64165f894e85b21761a9b2bfce891070103119ae71ff7acd164a57b0e054319631180c22f19eab8607f5b40000000000000000000000000000000005ba18dafcd3552af464acd469b133896e90c9ccd7e3bfc6e05db883f3c6aa1cc4610ec47f6354f6a7cff4385c56d2b3000000000000000000000000000000000fefbd9d78f48683b378d2d6311bf7ffaccaf7aa73a0bb4ce019a0c1d2e1673e52c724bf3a782729ec23d258043efff5000000000000000000000000000000000ea47ebbe3e858c5fcbf5b0cc9017d6ea23bda36e235d2aecbec827fdd2e4b042d1108d5f645b6dcdd786304e6bbf81b52b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000a313e1bf72d9a176bbad609631192c779e94c293463507edcd1c38bee8f33cfe6104d7169457ad5ffd9f045fce1cadf000000000000000000000000000000000af8db18938c51742b351fffddd74bf1137092ecb50a7e749391bacc9c1a19c7b9cf235b52ed577e7855d4ec1fadd940000000000000000000000000000000000febaa128de79274ef11d3e6378809d5b319796c653604723693c335eda175014b645604271429e3d449e756c85bcf6f0000000000000000000000000000000006adb29cc4ba053fea56d07225d2f7735651c0046f5cbe4a350dcc20431ed9457651d46a5d23d946959cadfc5500b7eae83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384", + "Expected": "0000000000000000000000000000000019a5b588aff8853adcfa959afc5135807d00196a75acb3536ad4fc9a9df3803d919a2de7cbe9ff762913225577ebdbf6000000000000000000000000000000000ac8bde939ba2f164795804d96dfa8d3a1c4d9e4eafb000cfccd956c24f4d594b30bbf961917f625c86270cbe164cc5b0000000000000000000000000000000002de09fdf52aec0b91bbe99fe2eb9043b19975c6fd503815264ce030dd5e5444f0f4275ac9a07a49de775335d52ea3c40000000000000000000000000000000012457bb55876c482e5b907c765b476dfe6ebfe8e588cb7f630e58f78942bfca57e6c0d5d7b0ce80e48960e297863d212", + "Name": "matter_g2_multiexp_66", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008f3f1f04fb80a23d348e3e25dac1d732265fd4a71ab8dad3718d268e49c79578e8e1ad1720e70357439e57df0791d64000000000000000000000000000000000fa4c15c76e395fa706a55d1909ede2163274a68b3e7afb8d2e0bb176f60c06f5a921c9ace35bd311bd79ae86340ba5000000000000000000000000000000000173633369e00c8c5528bd5ccf95c6af8b012e5a31941c134ad4541099c7c33c5ffd29a5a31e18be720f7ae85132cd6cf000000000000000000000000000000000800f5eaf7c8b1dd2787305ecc637a0bba8eac807a7b449410e48aed3dae2b4645b8459fcdd477fd92fa5ac6291b800ea4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b000000000000000000000000000000000ea8057b2d609ac2130b21e0b4a41f0aca20ee7751f55d816ea42cfa4612b67c3c556b01b0bb1c5912a74c50a420407f0000000000000000000000000000000007fbccf8ce8d1a92756fe80b15c7d9342af4e166d3c1c7e35ea2fac34851cfd983633270c877224749365720fbcea54a0000000000000000000000000000000000885e173b73118721d28fd26f3a9c562bfbb878ce71091d7ae4b37c1f2625777d67955a2b7458af71077db7557171f2000000000000000000000000000000001754edbfc3f2af94c92e6754d6bb096bbc4b39bb1128dc6bba8b4d4d9fac6649598be90b06b9d5db44c4e77c0cd1537cbd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000008a1a751b5f9a08e2bf5b2a58f62f0af6b8773f88e50f658ed220c0134e83c7031a288eb50a8a35016d2362b431d809d000000000000000000000000000000000d7f04d4a6c36cb3d105dc3915cd5d57f56692132681b3abca4b00e078c700931848e34ea1b7ec663f3886ff766fef41000000000000000000000000000000000a06c3ac81d6d0466e1ef21115150d04c8bd6dc3e4078e46eab213203c3226bb0c6500ae4fda591d6b8a791de598edb90000000000000000000000000000000014d849ddba2fa79b6a7107efeb46e9b6231d65384c69ed183acfb922d55b790d4fc7546afadc190b76f7da00103ef565efbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000014dfcb5fdb38cf09c1ecb284dd4f2de0c3d70f90d7c167a442d84e9a29bf43be62cd319b2dafdb6ead2c6596443a00090000000000000000000000000000000006220fc05c53f48e7e4104422b0660ab67fd88a695a201366de570f0ac0ad30421d5e37a1575e6b5ba35f45b441b297200000000000000000000000000000000077cb8ec1cb83c4974f6452ce0de630afc82e283eeb55d3b7e9969bb44bcf0404deae617393f82ac228b836c3cb6f95a000000000000000000000000000000000e2bdf539eb45a125112836008effd104e881aca397457004fbda4a40d152817801bd259434481f0509ab1838cdd1fd060d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000a09843630131cc6feeeee8aa8214408235655e4733badd6fe20c5cf1e45f6a61a5216e0cde937799437962706d3bfe2000000000000000000000000000000000ff518501614ed4a199ca9e9aad4e8efb8e9cffa9b4fa683093a49cef4669198a7893db998d5777f2cc8f4bb130c84360000000000000000000000000000000010ea66fb5224f4508ec100cdb611be133c4895a8de1b4c475b097494ff0f1ecdc1bf8fe467c630233cac2ddc07935fcf0000000000000000000000000000000009d22c0a45c82b0a19beb94eda0b93cbbe1f2e5f2d61279e1e1c93ba073cb766f5637195e6964a4814e588e44bb03f03386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee80000000000000000000000000000000015ca795fc7f0d169ba8abdafb1dee80b67e7dc616e824959f84c61284d6b2e0e8b9f99b414f5bd96d0e59b66ee706fd800000000000000000000000000000000042f473d1fa228961aad526efd003461935954abaae347dd6c9bc7fcd68b5f5138e57ab2a160cb19d1983089b58b51ab00000000000000000000000000000000188eb160cb968b4b048ce14bb72be27c228df1a6c014fa7dbec09a30aed8c71e8da59d3d5f8073b6a7d70d94c0e59dda000000000000000000000000000000000d467e6b05f033f3923667a82d5b489a5c90c99c5f68078aec700fc67a83d9bb4c09f3f00b9fc2cfd62bb098f885fe295ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d0000000000000000000000000000000003bec45d94f3073b2ca54d6332d36fdb8f5c801d9f70ccf6e3666b66ee06c0fdfd741f74cde1997aa205fb0318c9c4760000000000000000000000000000000014009b777b660264eedb35ec2e13ea586aa9438c47b3fbfd095ea3d8688a89c85bb4052bbd3edd450c19acea6372d0070000000000000000000000000000000017f26d3cfcb40fd6b4f3f1acb6d47a9b54c232aee484c7a8992a3d1accea794dc384fccefb0418d43e1fa7b399bdacaa00000000000000000000000000000000153c6cafbff3c53114c96d8caeee2880dc063d7db5edf5f14157117387f368c76b739553542bf6a9bc4ace3694de885a92837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c0000000000000000000000000000000005d2481438c03493efc9f1e8e9ae6ab05b7430f7fb82e108aada0e886b14d769969d54b17b31e5bbb63d40836748f541000000000000000000000000000000000971deac599b2161a4baf1178feb81fd4798ad5cb063b1a0cbee7cc33b8fcec6c3f43d1d46d9ed45555187db636af99e000000000000000000000000000000000222acaf8df647744859e04104a5fcd546949feff6244e192a9031fc838f368aa465a3799779c637ef0087183f30731d000000000000000000000000000000000b8e8f1889816f89401b070db687aae47f7264c9be192a8d6e485ee71a5a688070d57ad8928d09d9a4925f1050e2c69e127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000b8a5b0dd422469a8d6d7603e9f3179f443ef3fab0016afd94e93e2ea9e84b332da4b59f23a5257b99460efdf7d2aca7000000000000000000000000000000000c28e7068769c3a79bb8d92c3b89eca5d6eb42e3e18c2a7154f43a671f8670f878c4b110990c2e2b163ba4d1155319fe0000000000000000000000000000000001804302246fd07d86f4bb23f610af38deba8e324cdedbe5e61cf0941281cda8fb5dc211fbc0ce6fddf30aefa9563a0500000000000000000000000000000000015813fe0d6bbcfdc8e7e40b6141db21e1b490d846ffe82eeb3edcd9a024315193259612155b0179a4971e205738af74ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac614890000000000000000000000000000000011ac1ea4dad0f650fe0844ac3ab9434ebac6eb70a5f77c8f9c892cb4cb06639a15c63a9b820ef8f7a720040ae5b9e49500000000000000000000000000000000117da7999552e7886a25a939ada0944cdb15b5c468e9d1c3bf5b6af920e470bd648d24f3cb7f91e670f57a52cd65f7b3000000000000000000000000000000000a24147ef5f2b8ad888899c1db8df0a601eca9d76f1b934b1627e7eef3efe542f51205b96b5c00916688579ece81336900000000000000000000000000000000151863d964b12287ae4278c905341124985410f1ad6a72bd5c62230b7d8b0cddbea0c62cb2a7147afb5bfb03348be53363da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f0000000000000000000000000000000001d32ea5faa6303c530790146df7cd5cdee93c0933b4cbc1c2b8030bf0a8d2600dba1907df1756152625cfccf8cc7fa90000000000000000000000000000000017b05f549751d090f42ce8a3ac5d959cf988ecdc485f51734d52c40a3e22a097917345978209fa74a0a05be0a66e5c6d000000000000000000000000000000001481fab7750380626b174602d9fcbc97555c516f4410193d2849443cf25ec22840e4fd00b225f98d81b38619e8844ce90000000000000000000000000000000001d56434066551c5bfbaf8c9007874abe57a6f78de9355a297bc117f2bc5e6e3f44b248037f400f7caf83fece0c00ba0ef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e000000000000000000000000000000001667f1400973598ad3f56c2e49dcb5b556cc38ee3e5801ac4943f3c4554205d8fa69831e582a084aae1ef584feb0a1880000000000000000000000000000000003f0bb26ea548e498f05a5bbda8b8e536613f10e7165607ab77565b193f794664c8ab0a5ae2368d7483b77bc1173d14500000000000000000000000000000000176d8d294b4d975629c6a89bd6d45f9c3924a621259ab43d33a3d5aa1f423b68e3cef96dc103494bbb9036436c170f5600000000000000000000000000000000002f8ed87c584e69de59cdde02b6de9816c31a6efbebafb6ad9cecaf266f5bb9c8880f062dbc9235c91c668bae5051f4bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f00000000000000000000000000000000085fee95b859c52e44fcb2900a9aa590b1a5c2f981a388d6ad7b81ffbfe033f648c4a84e2119cb0484e178ebd3e220d100000000000000000000000000000000171e6ca074aa97981d2c2ab000a8bd12cbd5f5d574cb83158a6ed734e8f9b7aa4b74aaa43b7aae31b3f4fd3d82fd30ea00000000000000000000000000000000004fe6099a52fb491a0624a8d787d95617f6c64d16d20d1b3769f60d4721f7af66d7e3e905b3e08b2946ef7bff4806ed0000000000000000000000000000000004d3d1a56af91377ae6b00e192ad64fce6dd43a37592fa8706c9344b3d96b1f930e03be85a5ead3007f9016255d2df7570363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a00000000000000000000000000000000155830eff04ec2f4dfca4f73403e408a68830bc031555433fd38ab3ce1035b5f882bcd6032aba69ecc43625546b4a3a8000000000000000000000000000000000ed5b698b1ae23769cf5b6dc2e39f8500fd8a881eb43452d67c6b84ef9f0b3c7d81db1909b646e92412acc7365923a940000000000000000000000000000000009f28ec2f949cddee9bbe2fac12c2c029f4e472afa1ea56d0edfeacdeb9f43a4a43b79ccdfbe8957b4cc16bbcac1857d000000000000000000000000000000001474b435131301db9e232ddf54569ba99bc476200ceefc15e4aaaf1a574c1de8bd2d63c8280e23127a7a036acae223b1997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e8800000000000000000000000000000000168475854829d47356d9a8dc13a94e8d169771ea0070d9ef45e666d5378dd676d603c2eb57a3cda072c11e0926b02d650000000000000000000000000000000008b493a9f4c19831341782fe6285db2f7e8250d72952351ddcfcae6f22a2ec0935e29d396ba32f72dfa4067d0e7ce7cb000000000000000000000000000000000d9e72e22f2a1522babc5f2e8dc7857ee690f60f7843ffe15a080d56bf63db86f124cac039cbfa16fc8ace4d6268a1180000000000000000000000000000000008f3db1f6c0e5e7b3bb27abd34bd877cc3c373c681a3abc88eaa91636924ee477ba5032801dda091dbc51936a90c84685ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000004e34bff7e9e3ede02df950aa0e8c5f4c5f85cd3be89d211e957a7de95b8e321cc11400c3dd5b2ba0d1a3008462cebe7000000000000000000000000000000000fc1047097f01fd2079e6357ed379ba39107ec41ed6c6dc17fa6248d52be2b1cc2593c9735a6cb48e6d6e0434028f755000000000000000000000000000000001896fc5e990aeb416cf21ccc73f02c41d019d0a2679bd533d0811b7c16ad3ad3a6988170fb2db030b5fa7c3e4df5acf4000000000000000000000000000000000b70e14ce1b54d7913b9f3782b2b8ff249967a6b871dfac7f54f959954febb2783cf20e20d1710e5526ef8aeafecb3d603c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26", + "Expected": "0000000000000000000000000000000008056d4dfcb593c10a877cc8a4accbf58f360256b76876ed2b33a07be3110f8e295ef459dd6fb10d12bd02a8276351f50000000000000000000000000000000005686da1a0da89074c6b13fe9913f5cd49e0ecfea46e06493510625f1393ba4cc2e13f023fbc7ec2e130bf9a4f7483ef0000000000000000000000000000000010cd660001f65876db5b2cb1a56d85171d4cbf037f3bfb0e01bf4430c479237cde5b6cce5839a4fb22b406846e757868000000000000000000000000000000000809d7711211d37df76cd1cf71001cbf02c76df67c83e4eccea3e05b11d196b5d52ad7c3d0a00d9f0ef5b018717fc3eb", + "Name": "matter_g2_multiexp_67", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005d6cf50e3add0e5ac3016b394ec363d6145ed66ef56b07bcd33c90600e83b4277558695222062e02d1e2b0693858e73000000000000000000000000000000000de8caaa810d4ac39258e3d1656bf7f2fb7853a5963ecb989346abe90d5d35d3662f6e283cec7bc386a6a8638ac395ed000000000000000000000000000000001849ef86eec16b0612f214c5ed52c0d50a90bd65b623402879f2654fc578ab680d49af9afdeff546702304597a20f1fc00000000000000000000000000000000168707730c4e74eaa4e85e48e7239b9ba3e8cb74c24b7126a685da0fcc963b9f9180e252adf7d8c521deb1a2ce0099582849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd0000000000000000000000000000000007d4fed2fbd9e9dd19e0af5c52637b2cd337e0bfbcef0384f182a56189a7e7304b9d2144266ffa79044be90cb7ede1b6000000000000000000000000000000000baabe8c23a10cfe85494c693d1b09fc8e43ef5f233052d5b6294dae14b4ff9e5ec240a1c00a16a9ddc27cf7b53bcc7c0000000000000000000000000000000001c595f193229da9acff04ef67ca444b0cec75db5b2c1921502e37eebdd2bb43ef47290fc6f1980abc75ef4c50034df00000000000000000000000000000000010fe7f3110ed3a240366ad7ba31d56ab993468dae2dc1b667a46c7759baa37b865d02834e14280a2ccc062af5bb2b7d6e6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba69900000000000000000000000000000000150350d8a771c79268606d6a5e1c147dc9d92e63fdc60b20be688bd52eac697aa5d90fe1b7b91321b2af87c47ac0d5060000000000000000000000000000000000fba8f4da448b8f2bbd99014bee2f9c581f2a974bb0b54f41a84a7fb359e9dbf88ba59a705504140284d486241e94e80000000000000000000000000000000003bb92d6a603bd93f8e987071a7385de68d10cfbde389eaf01ba6480caf1ad8aea03c84d1889b7d5b5c5f72e62a2d75a00000000000000000000000000000000193342a9f15109367030724946342e564507b26971caf75190e0b209e429a948d8b21ca16041a01010b68222db66a16b8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b000000000000000000000000000000000bc4a46eea57231cc64758560e3032a8ad8f1907b3cebd7a8faeb98c4216cb8a0c8fee09929ecefc4bee7955f4e799ba0000000000000000000000000000000009f9486257ae3f94a2ca48eb203e2ef44ecf866ddec7824e1a4bb3b89b320c38b3c46de8202480628c53c415433383a3000000000000000000000000000000000d8e2b5d0825b11344d16dbb2cc614c6b84eb1cb43f70d70e272123867b731775b429aacde611318b2700aa567a84c7a0000000000000000000000000000000007f720929287a70873e9f2f2031b66693eaa6e604668219aa5aff3f50e720b34c5fa3f5c66eced5c3e86e8b34a81b984c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e4000000000000000000000000000000001537ed68e203e56f31498efa314322694ebd74cd1dcc3145d534299fbdadd4256f20b9f74b895931a60753bde6ff9030000000000000000000000000000000000935c6ae847aa7f47bf427988665e5e18a32aa869e196cf9d5bac1349c650219a8d20e01bd8d49bc7e4bb8d464aee84300000000000000000000000000000000013e0661d7254428861cc3ed47c3fc9daae8b86db35d1c64f8ced3bc18a89202825f13163ff94ac0ebf046a0a99727e200000000000000000000000000000000039a6b0b2cb91e460d50eaf9600c29fd4f82a81c283ba4fbd9a7d103efdaeb1e82947f5cc1a7a1112ae6344c51119201650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd500000000000000000000000000000000071a6b0267806f2b9e0ba493960fe0e43f135c739a54c8daf5ef9ee348a281f19876f80c0dcea59dfe9457b49809c12a0000000000000000000000000000000009ac83690c30a4afd78f94b2493674668da4efc84007d2a08fc78bba271ed1f43e2a9e5909149bf0811c44dbe07c52f9000000000000000000000000000000000f5d523612fdb2e7dcf5da56720057dff6b0b80707cf5924d146c0c072edc0635c73fb04256e06c7c9355cfe77a7af0700000000000000000000000000000000168431fc569869ebba9b4a72371e3df232545b5fb95778baf3d9105930d9a89b4cb7eba430e9162a5589c7465e54ca3ac067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ce836522b983fe3ef6a502a0de4c599fad8a36a60d914218d5d2cc4d56d69eed8d27b2d50899639d1a0ea9dc7597f900000000000000000000000000000000014110ac048ac4c20e53f2214df8c06d77f0b3150077d027691cacd3715d4630a387d5819ef58eb1bce2e8669be330a3100000000000000000000000000000000178e5cb42f56df2f1b255a028a00df96c02eab0a79aa0ff3e9772fbe3eb62174728259b3a15e356e6d9666eb65fd6b7e00000000000000000000000000000000045197f136649b61d6e0e7b9a56674e769e2d26716ee7a63fd2b83b767a9ae96694e9cf81375d0377a1b27ea6dffaebbb448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd0000000000000000000000000000000016a6a58301c243b0c59d6934bd926d6440b87b49f004f411ab0fdd924480175052f63f594c18007359055dc776e7f2d300000000000000000000000000000000176db4845cad46a13d9dd0f4077cd22b3458f64084c7325e9885f8ca341ce3ccd4f634f41efd6a70f16e1f0c9ae103a900000000000000000000000000000000068ba68f652c4f072a64d56618f93a1e148274b1b835433be878c06e11f65ff45b7cba0f67fbe80327abace68396da7e00000000000000000000000000000000047a699487964c98453207c98cc91c980c1ed37dc26e17748e6ee88e5f4c0ce424d87c82ca6db2264dc8aa9e437a5f25441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee35000000000000000000000000000000000bf7e4a9751d4e3baa7ce9906f4378764e5384136944f6d3f3074dce66ed017759783c64fc381f0dd7512d6f6e55b4aa00000000000000000000000000000000006ae2a4fda156818cb5ea6120edf7ea39370eeecc3f306890f47a6dcfaffccbb69fd21f33fe491b7065838b277ad2b2000000000000000000000000000000000d3ce00c2f5febfeb232dbbb74fb0405bab86474d1d9c545c93b65c7892bdd58aa56225641074ec9b428efd9063085d00000000000000000000000000000000002552a8c1848fbefd6b039d6c4bd47c34dc34ab307163c4f6d337946f1d1b41aff2f7e37f5fd94012f0ebd21f97d18a83020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c00000000000000000000000000000000105320cccd67b6ea78e96e66425a10a6911d2d348fac3231af583146273609fcd7fd27a19d4614fbdf05bcca0f92b927000000000000000000000000000000001204229ee1f66fb5a5dcc4ee978327e35d703ea310901be9c100af824e39d24a028ef8fce42370e5d734df02a26c145e000000000000000000000000000000000dd21f31f116681c1810bc36141cc18096cc113faee7db2c189abb7a746e398e272fa0cc61286aea0a5ec4008c8d03b60000000000000000000000000000000007911297718e98588844b9022c825bc4b37f2af30e1fc2d9cfb58b4500dffc8e9949afddd051e971fe78d4e1e7ad1b4a82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000760787190048e6ec8bc3bfc368f010e2f8aadd53164693a62b0d7207575bb2597bcec4bb382c57fe9053e90fe2f7159000000000000000000000000000000000ec525abbf13da64a8093c5d3fb800440f4c1fe798bcc71eb97bf2e0aa9e8be4b08afd2313f9143260058132d2607141000000000000000000000000000000000aa12c902084eb843daf7b351989bbab7a86acb62eb54eff0c7599bacaf44653c9fbf53f47f6ca72d22ea1671842eca800000000000000000000000000000000082f330d9a693f2bb9386fe5274aa79ac73a17688821f3c705120fb2aa76903627786a8614053f21a93e0aeb555de64e338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b0000000000000000000000000000000002ce7f08b8d5052d8bd07090744ca067700eaa1db61dad3e5086661850337bcab485c15fdd36c309a9e5169fd2a2b55e00000000000000000000000000000000073fa834cb4dc4ae120e738059749bfbd86b9e64fd71b1d372dcec8474f3341137ce8cb97a38955e9081f9bd5e07ab830000000000000000000000000000000001568df6806d8c3cfc9231802ebe5edc5d505198747a0adc24d0ac59f28d32b7b379d1f2c6b8352389057c7465692ded0000000000000000000000000000000004fb4b08a4fbfe197e924be3f7213a769a2bcd24109ae69a32a197b6212c5f50dbe8f46f5ab6044a4c779cd3e09d13bdd29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee75021000000000000000000000000000000000c07ff656904a47b0c7bf77540abc47cc6eee3e76b6ff0983151de9468ce3a860c427f3d5d489d096264159ab0567cd20000000000000000000000000000000008cce094ae1d9fff246a0e76cd67dbf9808c94554372fc4aed4879487ef240e45047dc201dd8bbccb613feb9c4623a0b0000000000000000000000000000000008a25297940a1bca1267fdce450b0cf43105eb4a21ab14562116039bc8379b1a3f58a7c117e9ba735bdec40f772465300000000000000000000000000000000000ae17a9b1fc3b0b7803ef48cb26643e8e78ef133f94bff5f87739182e662e2641e72383efab1f3ec58fa20fc816d56c675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb250000000000000000000000000000000002357e5a04b0dbd7f9a1709bce9b7afa12b10c7274b440b4dc3bf51a801d483804b1b4b9a096c3205a0e2aa7c0100c6e0000000000000000000000000000000002ff20af67f126c80293e44bb3c9ac74a94586a2de4146588c7ee8503530398eabc30f7e89322727739618087fa55de50000000000000000000000000000000013c6d06ce509fd557946479f2768f62474e6db04b2c92c5cfa86c023f79d05a387bd4c9aa618888476d4ecc93ba0995e00000000000000000000000000000000000fa477870c952f7506b879b17fb0a1c31771ee832ce0ab21a513fdd91b7a2a78a03d297c55558b834e255462b15520544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff700000000000000000000000000000000181336b8fdc03c02e23cd06ac975855caa2bbc1fe78a2fc7a9d0963c90a1f1f9330d50b88bf2526db6132d336ea5b8e6000000000000000000000000000000000f2d94d3fde2c0f67dae5a6ac12f713ccce2621303762e01961843eb9924d1d3c732b4c977d8cd0e5668adcd7dbf7dcc0000000000000000000000000000000005ac9ecab11c3368c75b0d396889dc34bd43ccf550d817c1dcdc7143c15d5c0e241add37328a7bd8556fde87d75d67fb000000000000000000000000000000000184704eeebead43f85b32d7f3efb9b9469f3ae10b73a2f034bd33e6e66da0bc36597d8e29ef5585443a655e24ffb68fbb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a00000000000000000000000000000000046751743f8f747e378738c265c1df3a368cd9570a2bd7636991045974c34039161fb0eddc6b813003e0908915b402170000000000000000000000000000000003341bea6cb81fc5e7baefd386a518d17a6f752c0e1ace5a9580a1b1649f5501c7b4639ba0cdbc33808d78b025a31f190000000000000000000000000000000016e3b9e8e189df73574a00a721440379589a7a6df09eca9a790e04c729400323b2110f63d547d83664c35227bd15b5760000000000000000000000000000000005ebd94e4640344e99e7e0f1619c6288665c985b90d99921ee61bbfce921265c4881a7e1034bcd840a665bae44467f5a94a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc", + "Expected": "000000000000000000000000000000000d993522760839abc960e99d62dca1021b52ddc8147929c4a064ec72570ffb3793205598cefab8490446453fb6da231600000000000000000000000000000000105db1e83fdff735d06d34574f962e70d84e2c1ceef4d8a8f14c2673633d7dbc7b97ba6dce9013f06fcfb134ffa2ef98000000000000000000000000000000000363be663cb0d36b8eb076df283b075ab9e568e460be804f197c51cf7ef611d8783ced304407d4c2540f1a4a04c18467000000000000000000000000000000000ab2c00473a2267682ecb356422aeafc893fab96a3bd27ae58d9b0786624c8fde446cf68bf8a003d9449702e345b1ace", + "Name": "matter_g2_multiexp_68", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000a575d896b06c5ebd7459a70b9321cd0de082dce7dc0ce7e39581751d01b7db810bca80f39f521df0bf70ef642bd66a000000000000000000000000000000000ab497a9590deef40f6fdc0d4db2ae7b6ad9ab59f112a5a0671b48581f1f2b6a71602c73784ca6c0effce66a0a9c6500000000000000000000000000000000000af3812439e44981c91633f73d1a92298ca1ed426c98cfbdb50643cee36affd5fd02886349aa608f4b8a27452a51a96500000000000000000000000000000000013126db8b642d33dd988b745b07084ef86a228767f7e8bd45aac830dbce4136ca5febca5fda9644d3292203e27439d9f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c277000000000000000000000000000000000cc12f75fe5e6d6f082f9977dcce64c7858f3b6378112e7e083caf0c4b33b5811d62a1130c595937983905fbde8db1fe000000000000000000000000000000000308b803bcaf4f63affaea0206aa9f4770c21b4d191890602bb4151b80fdb42af0cd9f8dd2b1a3adfe28d0e49712d2290000000000000000000000000000000019f83af5cbee858fcbc9bca0f499222849b9e80dde7ac79b7c46785a484fecf274e0d4326469eca647cb223068a183d8000000000000000000000000000000000d0a8334171571bc63054c032299824523bd2476b1150a67eb17b84bba01d8a65295624202c3874e0302159951734702dcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec088000000000000000000000000000000000141cc01094391887f46391bd49fdedbaaf524cfc94d741cc7c8cf081dd7c425d81ea3e407be48127550012e39d2b0580000000000000000000000000000000014db31972eb242d6c2912b418ddf416fd7911f13aede9194559b05d1c9e12056deaa1e56c155cdbc231b39f4f9aa91ea0000000000000000000000000000000007b361beb6c156b5c8b92b489e6d6c05e32a4376d20ac3e1a54c94e678c88480779bb789c3e1ff7a021aa6d872c98551000000000000000000000000000000000215d270f2d3c5c5b9fa99a873fdc337f4edad6889f7a55556d8ccb5ee86b592453b74a720ef6a907bc342710cfd9cf91d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c670000000000000000000000000000000017f5d31987655f8eaf046d6ea4025444924befa51c319b2bcb02dcdfde4d80a1c48049514e0b580e4bb59dd2fe40bb22000000000000000000000000000000000141ab771c08ad7c592725630aca0b2564de1ed8759eb3afb10a4bf451eb21d25e8d917f49bd5f7a06894baafdebbe790000000000000000000000000000000012dd82703c939cc5e7dd5bc3b924d744f0ef1a95fd0b9e57617e822e3fdda05b2e5a9959ec48cba0da40079da2253cc7000000000000000000000000000000000c53ff34d875fec4c7095af324d15921cd775873a3ba67740b2c123d6d482263b1cf93585dc810d19c68965cdbd9e102310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a16000000000000000000000000000000000a6514331035d42f58abf98b805f159921d8c4c935f88bb5493c580a6ce14a65e243424b41b3a9188e26a7f0c912a378000000000000000000000000000000001351e48b2d3f619887f4e83823dcd9dc15afb2800169ab78a2cd5ebdf25dcb6310f1051894bd2b549e509c55f5286f600000000000000000000000000000000007900972b84b6a76b2e686fa5757e98b8395bfc99da86eca122ce209afb39e8f3b07603cad92623774ed54d637e350d30000000000000000000000000000000002c68c42b3924b89a67764990478e48fc17aad4b5543bd38bcfee34fa1cae7535671f3b885852aecac53a30f28b0d4aa2fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000152d2c0e798d85e4dbf35dab808dd29d724e9b6c7ca7f53ffddfe1aef5976f2d3079eb1d3099e91b37d9fad7f1af5750000000000000000000000000000000015059423ee4e7201aa65e39116a2a49ba715b15e4b9547d18a0efd355de6f5a0159bc9047508bd3649407758d62887f0000000000000000000000000000000000e5a823fdc69f3928b22c542388f982f8131a978b08dde80d44e51d9eaed2ac4a1d5fa7392be6c7edfa33e833da4832c00000000000000000000000000000000044285f4e4ce526f96f9f512c5be754e0b0953744dcc04807ec6f041ba5c6fb9d5d395e93317064d50e61aae26810df0aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef0000000000000000000000000000000019c457e369dbfaa130ee79bd33ca70d00a3797b6cf62126baec0c5d7c3fdcf5ba7f41195276dc412b6862b71560aeb77000000000000000000000000000000001206f67dee6521ede85573bbd5784d675fea42da16010544857d4e2d81b720b6f85f646fa23540880b44a6cde9a39f5d00000000000000000000000000000000142018ecd7c7acd4f4ae288e1c6a66594f1c7f31bdb9bade2b4dc4c6455cdc685b716382c54d67373831a19100185e850000000000000000000000000000000013b0b57463a3e4cbe063c0d4f4e998cbeb132a41c2877106ee60e83d4ef7d339a5432d30a3c149a42dfb1da9d61f34030227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa80000000000000000000000000000000008a463003900194e45fc2610fb461fde538b17c4fd516919000d423f5a1b582342ab9ec20d8eb6fda8fffc6a898e46420000000000000000000000000000000010eef0f7bf73e35dd75fb924bd9759c09aded9cce46b05e5d3c5eb3e93e5d5032ecc459e2220aa529d2f773c4b8b8c180000000000000000000000000000000002a0247f82a25468ee74da555218cdbb6405871f7097c24e89db3f3eab59b91ce48ac06e8eab2c049346436c846226a3000000000000000000000000000000001895b58a50c025e46a2cd0c59d5437f6eee75fac949adb7ee12d455c96206a33ec9ac17d5088fb773618fec131981ab6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc000000000000000000000000000000000cb0f13b0680c2f7de522a59f4e46fe1d4af3a64cd3ab97a2523ad3c3dc42f5e6760e06cf48e4db22ee64c5ed8273dd90000000000000000000000000000000016517038ecd2799d787c5b6ee93079c93f78de4a96449bc82699ddd6eebcedaa1d02981ab47c529652cc21663f1a665100000000000000000000000000000000067ae1dc093d4aa2ddd8b7127dc60745ce9c462a066106b099a7a07525597c72e4920bf64c2ea8a3fef3de51c703de8b0000000000000000000000000000000016374f51023e2448eee7c64115d85794996fadf4f76fd4266c45093c266f35be09e861d07ff194f3d15e310385705f0e1a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec0000000000000000000000000000000017479396aeac06bd624a47e75b066d6daf5a37dbe515650cdf3e16be21e7d3a1f52a695c1c06382589eb7fc869c7d9250000000000000000000000000000000015c31ff36ed4eaec4d3927e62c111d062236e19fe6514236e6e3f7ff05ee96e3e4c084fcafcd21049a81faa1f84b7e7c000000000000000000000000000000000341b440e6c6273515fa7940d2f77018169bf6362b70a7b0cd6d66cd332ccc30e3ac48f7581edf47ebd137253a9c1369000000000000000000000000000000000cf424de046252efea9320b32b79bdab58e0e04f2916b4e8ef475da7b8ab85d8d5fc793a45ec6e6c035b6331a895d3efb206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000007c9111a85a6acb851e9cbdadf182096b720913ba3fb357dc2cbf2b8e796e9a8044b6df3ccadb740c73a16c3780c640b00000000000000000000000000000000059543a955c84a197d23cac22e15d82363c881026e41c57ee924da2a8c044f3021b29918d1db7926ddc2fc7a662ee7ab000000000000000000000000000000001355d8bcbea65a50c9b6ab59881e48e8e5f5592cee6aa69d5d01b033a84057cb6e74d911769bd2ab5f9722328aa204640000000000000000000000000000000011232571c95d0cbadf8e70454c851974efa4b326370249238db159a1224cc6d34eaad690e1840ad887a875b667ac1f193a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e00000000000000000000000000000000088b7cbecf91721e01e5e4a08ea3b261febb58cdae3056d9316c3840b3e5720a289739568bec7b899f4b1f4f5372013b00000000000000000000000000000000001f8835d4b0e3b957e46b718b6bcd81acdb50ab85f10bb70c6343a23970efbe72bef89dbcb24d66e6a6be3eb55665a200000000000000000000000000000000046500afd292a31bb5a4a9bd7b5bd0fe608bb1265351edea69162e61f1623cf58e34e8e1a8ec58ca166e8203c86f84c00000000000000000000000000000000005d6cc367ff9c88fc8b6c35383f147b4f9e3eb21268a5a7405794441d449b3e1b44c8f66e30783e5f6c3567adf0d80171231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa0000000000000000000000000000000015bab57412cc5c7ee0147b0d2511b7836a14a82df06b4eb2b1baab102840ed04cad81da6e920ee000751e0727091c1460000000000000000000000000000000002f725e61e82980e6164cae7a2e30a36dd7245402f4933697607640d53fab2d5db57698be33a0c9b5dda14aa846db7c90000000000000000000000000000000007fdc589448887f6986efd817c63954d350511401333cb0df89214317dec0a82b06259ae9263f260fc7f21f98ad2630f000000000000000000000000000000001324e3bb46a1c69fc550fa8f2ae2d0ea74bc2d7159bed03c13a9d232233449e271ad1c3922dac5d84aae52606f77dcc0393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000010adc26d73007e3b1cc58684fbdd7d197550658b4c66c702e9cd0f4e481f23a26c94c6798cdd9763110eefdca3d802050000000000000000000000000000000009138258ad1bdf6f9cdfb943fa32b42c4f1d834be536ed365d00126227c78b0df2776610fe5cf66a937cca3e0b088861000000000000000000000000000000001991db3a35bd2cd72377cd459502a84315422bed92890af906fefcc0acc4515fe7cacee1e4f360ba24efb23292482b8f000000000000000000000000000000000d10dfb682ae7a78b23b37b081efba32ff2011fcdae7b0f8a794a6ec33d71f5d6055f93e3b68a37086ab190d7d9bd7aed412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000182795b905320ee69281de833f37e040a3295e23be05ea7ae4563bd49d8b1fb02e95782c5c19645244633951cc29c5c900000000000000000000000000000000053368ee1412723b5c6465ee5ebddcfc00812e0e12e940f8485f44bce475c8897b324eaf7e66c0351ce9a6c92758c337000000000000000000000000000000000279f26c1e76e5f5d0fe1240c0956cd6025f6520ec303feb383b69525ebb6b2f199808a578a91368c3881a4044f37be50000000000000000000000000000000000ba4012c24dfe1038ec4b4565e1b321bbfc174cb197f0b0914bf1c126bdac9f423845f6742129670b7f3dfeaaa62df45b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f10000000000000000000000000000000016c27a3a950fc4857fc775441947f7ac02af9b3df6422874507b11f7b005c61d7d6a4a115d3759fcbd64633a8ad95611000000000000000000000000000000000e92954034df4f15450c32be31d4e146c4b0014a2b81e2afe755df79aa962afb05ca4d03577f15980fc6d8a34f2cc50200000000000000000000000000000000032db3e3c3617c16ceb1c8fae83e806744ca40cffb56bf9b79997cf48c55e5fea89db43b368cd922cd7ce30dd3984d82000000000000000000000000000000000d153fadc3854be49b2376ffcf4e5a46b9dfb4f54e580986767db13127e2d4d10e465f1ca932d79ca90f1971ddc0993dab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5", + "Expected": "000000000000000000000000000000000e1968e131e25d3f911284c369eb63aaf264ee82f4d9bd978b7d02470feab68ae82aed8509ffba875798a292f012c9180000000000000000000000000000000011488365570d9bff018ce6aa15e3d7e078368f09873ed5e0b827d1c25ef369d6571899c8df38a3df3135d0db814c87a700000000000000000000000000000000161973f4949bd72b9f010f017398778e0d7f0c8f77e12a87db1851af516b4540e3f4df314381f295c4d167fd9ac091a6000000000000000000000000000000000ae16f0a4a597159195aa47862585839485615095e538b745c1081ca73f202115a438d279dfa45bd3aef8d4043ec67c6", + "Name": "matter_g2_multiexp_69", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000046eea8e5af344dc8600ba7e506e923f6c356f7ecb3b78bb3805c4561e808c1f570e122c4fc5a1fbe433b48ce0c15d510000000000000000000000000000000006f1ab405a46c825e104bc963d2b2f573f0d345bd2b08a952d8793c0297dce267a754b802ded4db478399cfa88e7e255000000000000000000000000000000000a5fc4a09019ac9649c07b623d2cbcd9f0cbb89d28c01b170b62544d8da8ba3f236ca3172ac754175a3db85d9b846cfd000000000000000000000000000000000f7580110db2549742f69bbc2850e4ab35a6e415bcd1b06220b9b009c1f4c99152289eedbcba2aa653f38f6b8460386b3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e00000000000000000000000000000000026907ccf4d501265cfe67bc1c0b06840e9dd94a614c873d676b5416457d98a1dd744322887f1f1f86176b11a27d2830000000000000000000000000000000000cb08e541a5b32fdf51acb28ec64d3ea542c7bd75179fa3f74e9588156815bda9d027dcf5597d714aa001b2dd8a9553c00000000000000000000000000000000103ac1c03c16706d5936f216a6445577c96acd3a00a3d8a9c2c66e6ce568dd84a4c4db187a5fbde24e6ce60e037f53a90000000000000000000000000000000001da5cedccc02d0f8d1dd7e4d81c3ec47d432e81e941ea1452b112eaf40748a6634957c90f32fb0385dc5d642bf65acdb3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000a0b8dd68918b58ca6b113e938f8a00b2595351777aaf32dfbf703ef3884f02c798f1b5bb78cfac32f196c1fe88aecaa00000000000000000000000000000000121a4104e374566f8d582f75a3c9b70f09628f116b7ab22679ee13a1691b0b0bdb0d737833fb606c746fafee5859f1ee0000000000000000000000000000000000b8bc89d718572ebdd6e3100769f2571cabdd79ef5ca9a4b9bbaa432b1a4dd752f9af9d2a9b1f1f32d76d4ec2d1636500000000000000000000000000000000129f1d760a12eb1a75fec1d2ca438189c933e87095b9fbf9a0371d64eb205d8f0932fde9ee2ab9f36f8b6e5d4b5dd31021953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b825000000000000000000000000000000001499e5481ceeefcd2ff672df24e8987fb60872ed106c496178d71c68e9078409a80016e1f9727ed0d5922c93e821dcc80000000000000000000000000000000007bfb606c005c7da6b4ce2d974f9fdb2e3710c8f51f18257ced7663cc341ff81fe2e46308a2b62b13408965949a6f08800000000000000000000000000000000003fbd951e860e3a4724b667427fd9916ca4ba511a0dcac7b1125b14d8a4f4da82ddc0b0edab8ea50e911b0fcb5c200a000000000000000000000000000000000b43195a5f0263307e85408ae4eb046e06ddb1295a490ac4e0e654324de53d0dd023b8cc159d86b861dfcfdf7ebeee4a505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000000743bed2c17bea1ebddf750da504fe120f457cd3b1754c9413757cc48f7aef07eb4fa0572cb853cb72d68427e875456000000000000000000000000000000000102ddfe3dee27186a9484f74b3cb3aa366a79f0d2e36063af6e484f6a459e9168d7a4a6969bb720ec694a52db7ab34b40000000000000000000000000000000009bdf5b86aba4845adf9187ccf9c74b1fcabaa05764e41fcce4b38356b4a0ace8e7b16abfc7f7b96b785ad47fbf8e90f000000000000000000000000000000001934fa903b71d234c4341b2f49f8177334142e7c401553dad38e66a2c157fcdf7637165058955b7798a59051846dfb8cc4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f000000000000000000000000000000000ddde953f59b8591a83b0cdfce780ec23d052037c26d60cef36522d0f984f907315d7b41c8be9a9632f2b88e0ce950ce000000000000000000000000000000000b8d7bdd94a994901a434e6ea5d03ea45dcdb859e560833d8ea0bd9d20c7db9c16b2427eac27d8f1eb640b7d28a530fe0000000000000000000000000000000017b5b3a3097a74d9c1f1b23783723235b6148023b6b060234dd9e2f6fd05e38668167136c999d91249963e224f9bbcbd00000000000000000000000000000000133da0c217c31ca052800315aa8a3b934fc1f179e6247801904bcea1e28dec0b65632ab2690bcca3606bb1461aeb147b99762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb0000000000000000000000000000000012778a6fe79b1f2b768432df036543cade95504bb7735ff547969faaa8db84e3588046a074838c9a551a4fb48f4a66140000000000000000000000000000000013288a3413d7e7edebd118463d5eea9f9ae2e10f51965480f9b5c244b05775d04079a1dc75ba0885aaa9e2e4bae1ac750000000000000000000000000000000005b766ad112b8d69f1a28079688942ea146f8f31616611909f539a57c58ec5e857da9fce415d683c1c6dcb5e74da9d17000000000000000000000000000000000907e5c3c83d3f12a68d6bf812e310f5a04f1417094301fab7d4f41007b9d01fc1bfbf739dceddef756417367ed5b1d0298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000090be6ce5ed09e45a6fd9ea3a9223fe43a835141c1c29d6b386e085846869f9c5798b80c3bddec8bc15171906dd417dc0000000000000000000000000000000019bdf67eb16f2708ca55fd20af8deca66e2ae270b2f2f9736fcf49dbdf7cee034cc956f6fb799f0e87c12f283a11448e00000000000000000000000000000000124a69c723cbd366d52919a72dfceb7e4cd9ca5b5cef1784bfad3f125b11d810328ea1c849602536af500261aa684f5b000000000000000000000000000000000bbf05318ffd81495efa4f4c271c8b1c669041a6446501788f49b8739a934f09de9d976fe7300b0ae861be567d35c992177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e5730000000000000000000000000000000010833a3e7329ad40c1a8cef296b015f6ac6542c612038ce00f13a99f673783cb7eeb14796485c168d21cc169065d051c000000000000000000000000000000000d3b1416b23453b893c92a6c7850cdc0e4a395459140391b1dce11055da10fb68f318c5561e1c12d991a28f3f544a5230000000000000000000000000000000014721dc58eada80f2d0574fb4e2c1c94c45fbd90c2d2fd666fd618a96f4736a5ecf34cab34fcbdcb19b6cf7b44098922000000000000000000000000000000001905d34029bf84617a956d1edae090853dc1b622f560c5289251447ab6bcea5700bdd80d6ffb2dc12fdf3b0267e74543cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a99000000000000000000000000000000000b4d380f4f4eb976e6121b933be8418c536f85994491b0b93695d50473615e41547ead326bab795d4d59524a61d607cb00000000000000000000000000000000104b7f4058c9b355d38908d715c311a53169b42d2434de0876f1c4ffce1c39603c4876b33fe3076528be15fe42849d3e0000000000000000000000000000000017e2fd647e7739366ebb606e8a326daa5c03cd2b726cc4cec7747cb3468419f1907126d7cba98bbbc659478ce3afee7700000000000000000000000000000000183be0a976dbb3b5385b544c194e111729c7a8d5aa98eba3fe1c0a5b69b5fe6e5d0164e96398cbc61eba5b86d91b3c94e03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f50300000000000000000000000000000000023bc7eab817fcb9982cdac242cb6cc0ee1779bcefaecf144dbe57d5ae2b2ebfe9088f39f416a56de4b4dc04d4bbce7a000000000000000000000000000000001318e728c271746905788dd8f5ab22a3a10edce3fa063438e54ebadba22c29e461b2ed78a95a8f26a65b47022291b8df0000000000000000000000000000000010aab000b9c5de56623f18861b343ffa80da5ed4ae0d7767b7ed791bf3dd507fe7286447b6a07ea0fa12c19f2e4d8e8d000000000000000000000000000000000770e2909b5795a08d98dc66389655b1718e70b93c5bc6d805c3945cb5fc0092a5b390e6497b550988c28c58b6e016a3804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f1000000000000000000000000000000001259a4e36f5bce7d5f97184948d57fccd458cf7f2ae0c9e174f537bece01d744fef544447959cb73a678fe2c378ce3c900000000000000000000000000000000131aa575b2b94232e06879fa1f6f145a0bf5dd12456b698f731a72bc587e6def5054b3b2afb6dbbfc34fa5249dc673860000000000000000000000000000000011d64b923596c316b097a0752043efad8b61fbe068c58bec7a6766d9bc90ed965b3419dde3b96679426f72184adb8931000000000000000000000000000000001653af784cbad5a804e3f72716bb51e0c733014d587952c47395f953828566cbd7da811a3da1d48681998d569db00a7bed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db00000000000000000000000000000000077a9ab830f7683b7fb46676df09f72d773b65286c5f5ea86623306e5de51e63851c18d192c4c3b20af582bb7f017ff70000000000000000000000000000000016dc185f4158e249939541d35ae8230fd749988b9174c40c40b8c932aec625a7e94beaef9a07f492445d4675a01b7453000000000000000000000000000000000c107a895bfb45d33136db6251c76dc0461a235fa5d1ba7a5d216bfebe15691261b46c9816315c146becc328acb6b8c7000000000000000000000000000000001151cba240678efe61e3a36e169e314b3610e9d4df6650507f53ccf635d8f1277a80d86baa85a2d4c7e2af73934a7299e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd506400000000000000000000000000000000090a1ee6c611980e0421b72a122cb39257dc38d1e74ee41b809ad76e440fe307cf45e79afddd8d40b94382d48cdd4c450000000000000000000000000000000010f2e6e610eec7b7c2b95c1510af1af342ac19fd3b01dddf81b8961ead2cc57a8eca36c2f5747238eded5914e484c52e000000000000000000000000000000000acce0789cfff975b09d687ef79535c536f3b799157d3ff731915ea5b323ddd9f6f4750dc8e00a879d4e516bce8cb3e40000000000000000000000000000000008d8203dd13aee7363f6b10a9e1ae9b713bbc8b8fb2c56f05fa71e8d69ea571384d150e8fd01e855b1b0054fe7967a052f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000b332430c518d7dcd120b346440e5b6b48900b5c3656d84840823a96e5bf002816d583a989898cad9e09ba978ebc58a40000000000000000000000000000000004197b43877b833de7f69cc1a43ad8d6d3544cd10d42336d4b19a187f31337a37b10cbf48e72b77e4d8e1a1da68e5e4c0000000000000000000000000000000008887d5dd08f45034584f40a2a68254baf2104f9d6a4c2637ef79c5ff2503c246f7adc36559758a0c07533b66c3637d40000000000000000000000000000000009343819dec1d4569683de4596621c19785d5ed14ba13e57d94b1b1a108aa62cc8c55c58dfa18c06883ce50cc1364b95e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d7305370000000000000000000000000000000003fc7f9a0804e7f1664f8cd3ca67b70ba128529a611c24214fd09674072a6b8d652ccd37bf5d4611424688213a41cb3100000000000000000000000000000000137a869cd7bde696035bd9353662e0d37d2aa0731ae55357df3bc43536b9210f360324cbb3670362cf9ef607b1919bca00000000000000000000000000000000045d9d39c04e257fcd912c54e57c86d2d4304e6a7cb95a83d2bff07964d0a5dd8b4e42bdb91a8b245e512395e6749f1f00000000000000000000000000000000120e5e4b04b8a744757812fc331e7c98b35624faa1cbabfc1470e4c0804248bfb0c53a484107a677a7d3f0d2b533e7530cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5", + "Expected": "0000000000000000000000000000000002bed414afe9c7a630441e7b163280be10e502cf877e94b6521d14baca0087c5dcdfa39ff4a51c8376d99855e1e6f36a000000000000000000000000000000000dcd54727a7729408e682c6e213005687ed51fa7935c522312793fc58cdb273eec9c61cd8b056a26619fc8dc006b066800000000000000000000000000000000137286f4086763e6ccd5ee82d3bda712b26814a17c6a71006a3e6dbdd919e469bd0e744bcdb2074679e78a1e7d56ee7d0000000000000000000000000000000012d75de1310199c0e556d61d6c0395b406afba0f13bfb37486c05d66b446809e8b1a024e8fd2c55f1b82cf2aed99a5e1", + "Name": "matter_g2_multiexp_70", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008b83142b22f6d6496cad0dea23c71355e7c5d98659580b5ee6e97eaccb9fbe523f7e3b925abbca3a38f67426f3fb35f00000000000000000000000000000000035f655a1b2d22ea21cf0081e78d7140bad08c4e66dd45230a113ff3b7a77e39f0f1a72991f85e2b00ff58b27d5cb54900000000000000000000000000000000105d04e38243ef1ad2f734a3c97e91506c5a7c5d95e9b8771b7fded8908f1be933a81a5769044b633d501c0df7b5d7fd000000000000000000000000000000000e670ae4af94d0df34a7f2d7cfbfcefa6eebcf2a6b2dc5b82068b023fe02ce8a279e1bb96d905ad4f2ffbd8214e47d702063b046a71c2674e35466657a85d8e02253b42517b033619e31a536659172120000000000000000000000000000000009051f1e636309016c5433cc7eb019c7dbb75b3a4a5b27f6927de08fdd9577e8eb9e12919157ed35bfd6607be7fc4de5000000000000000000000000000000001953b7a33695ede6d0792eba85567aa5052b8a58c1bdc94ee82b5001893c6b996d3e8f7af8b8effd6cf50656d8b85554000000000000000000000000000000000a2f769f00679b610bbe212c2f8045e7579a96dc6bff80899eb7715aabb1afe79421ad5000f2c7b85d4e0904e335ddfa000000000000000000000000000000000ec962a3d00fac14d05774adc49bbabaf46ae78325083c0020587fb85eb234387aaf6506f503fa988df8e9ecafb4a59992fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000006823056a4da801cae430fb9e3a8663fc8f46bb6c180b743b7f9c7c7e3287f3feb1aad4be0e98409c74ff58004f8732e0000000000000000000000000000000015f7a3f692d55252fa5af5ec952f581b796d54089f13971fce2ef9062173664816dd9f37174294ed78681d8c8c5a9cd800000000000000000000000000000000154743c76f7de590a31cb96d46a0ec0fa88008b7d6684bd8f6fdaec70722afff7b6e88c1f0fb048714fb1072d30780e60000000000000000000000000000000006f3191946d0e7c1307a1a0d1ea9a26db195ec98ad88f9b8f08a03a3d48bbff1fa53ffc920f7db5ebd4c65911392bb834484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df8000000000000000000000000000000000355018079cd02dfcca15fbd2934a8e47c5ee89e679663488499ddd4abdaba7679fb1c9d2102317cf2798c47aff1ceec000000000000000000000000000000000c417d489a224fbba9999300eb65a23749194bf5302fdfaa33ff7daeb8d896e387e56600233038d5c5eb59f644a99b6a000000000000000000000000000000000f5a62e9d711293d4373bec1bc2637802938eb789c828939e6c42f10062ec171ac6110261165bd179206d649713f6fe3000000000000000000000000000000000b11f9fd0ef8dcac2e21ef09846ffe9f5a624ec246e31393b39082a47354fc9523dbd247f0059b6cc740d7a387b137f0fae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000fbb5521cdb9c3a69d58e5c9cd7e4a50bf5469bda2603f5119f3209669eb3e374d700f851b0c7ac5ee3cc9de79e6a7ec00000000000000000000000000000000131ccc37581e64f6f9fdf675b9b63ceb67d9d5844bf512166f39b5bb09d8e031437c06b0ca01caae7ad6d8c9bbb9fd67000000000000000000000000000000000531cb0557fa18ef054dbff2e7e994f1af08aaea7557602a26fd6ff539ab3c0a73f1fe841177012dabed4a1223ffb5a7000000000000000000000000000000000a180e7a345d2b635be92888934608e8b6c17384c48c560f4cb9809ff995f8e70d83cd4cf0e96c458fc414e1275d2a993168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a200000000000000000000000000000000125c83184f63dee35ffd2c0c7dad9010cd6a9735675099f24b465554ab3db727ee76b5b7ea603ead78795d33e37689a400000000000000000000000000000000141bdf7e270dcd356993327cdb5dabe38a5c5a9b53470d9a4aafc041c46fe8bc841089e337469bddab5d4f7fd3d6ccbd000000000000000000000000000000000f9613f6d05f38e3073f14d0c2557101a4864a7d6d0b5a2b931d0613f020adb99a1ab2037a39fea6e99fcfb47929827a00000000000000000000000000000000192d812e05a17d22c60b78c53fabcc55a0eef3656f8e84132faf16686ee18ab4d35767db9a384d42f392c40c7b0fe1c0f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda000000000000000000000000000000000b6e1960e82586de19ffcf29a8c5f16cf2fcf5286bf42febef832767919abddc655a0d1bfa240cac8fdfaed5a1e8f389000000000000000000000000000000000fc1598454caf04414f1930f711d762f0d72f5cdc7a4053c92b916c742b00dd0f107aee111976c1b1218c4577deeb006000000000000000000000000000000000455d6e9e9bb848e0868c9d725edca1f50b279d0acef8c597927eda72763e3702f46b216919ac36b080b4865249fd961000000000000000000000000000000000174463cc7804796b4a6d8ff28d2e8cfd8361b2e38f368de30166cf3c20c474ea0a1e8d94749fc3e6468924a7d1369e62d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d00000000000000000000000000000000144e4b615ddb871bae85484c308423adceb5de387d0c7ffffdd2211b4ea28788eba9bfae96ffc46781e6d6343e2f501b000000000000000000000000000000000046e39cf43fd707ddc4b7ce9a8a22a2aa1e55aa63cae1eb23082f7b4b5dce49f32d2ff887b5108b40f98062c02d5613000000000000000000000000000000000b75b5460db2baca86528569b47209b5ac24930e2545cc6aa08c401a87ef2c4e233de537e5a857e533d0ba0981b24d7c000000000000000000000000000000000018f53b83072fe7daab226c831a89da63a0930ea86e301c97e639d0ee1609e298e2789d1a347bdb4afcd355fffd16d053bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000185b296e9c7209a9abcc3194b46be9a545666527ec9b0634a3e3be579447cb52330174c19e40e1667124552392a7a0c00000000000000000000000000000000158a053c788e5b914fcdcf1aebb4e21cc8bbfbcc20c4d692256b2ae48149f6644e1578f98d58b3e73d9768d0e7df643b000000000000000000000000000000001318ff4150bebd8fa612f4e84f89151d5c56c272969bc1f31a3c1fcbd8ded0e298914e98e1ca48248e9023cd12db0fd300000000000000000000000000000000076555254f382707fdb7419772a4978808a7409f59d1dbb8c9e648372e19c44573f5ce1888a2b570a83afc20e698ee44f944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e431000000000000000000000000000000000f4395e3f2e301ee3e18df3c23cdd142716c7fcfc23caed924f0561795948b0bfbed948a6f7c415ca615ee0ba4d5145c00000000000000000000000000000000176ad308c7fe8c3a1aa350fa82b8f8ec638f77bc703afe1042a6da22e5385cd8473ad789247f205214c9980532b12c7100000000000000000000000000000000092b0ec86c511992c66f320ad46c9d6d7c82df118a9ab2ce1f2c5611ff4e5cdc9193a39c3fc95f18ddf96e139688b00f000000000000000000000000000000000b4f671e334b7f22bd8d89d8c4eb8a52b04bbd4dd1259cc9caa1872093736680618930f3a469b3af4a00cb6e44b573f27de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e100000000000000000000000000000000123b64561ebfe085238220eb1428b3a203acb01846d1e4428f3759db6cff4ed3c1b9d436706f28b77e3b92e2e39ecb41000000000000000000000000000000000ccdf1973693e4b43b6133563986f6c96e2b924895c813f8acdd0f39585e4ee95ef26c0d9d51d6ef88bb62305e51594d000000000000000000000000000000000f51693bd44b12188131ca84801bfee0ca853640c0a8d5b20123c97b369c98299ac04beeb27d75946cc6f45f8a07b5fd000000000000000000000000000000000804c6597810d2c75de94484873a67eae258fcc9577bafa778e13d4814ce099a5684b1cc94e0df5a59acc7b19328fb8bb0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000009b02eea05c78a24adfb0187defb6810116e21894d8782605c1d590f8bdc10723bf71a1e5e5004b181504ac2deb142cb0000000000000000000000000000000015882389195128e20e50ec4f8d278e8b8791e362341be93c475064d640e1f8bb1c92a6c777d666f8644d471409bb9aa90000000000000000000000000000000000d89295f845f989e0fbc6e86e97400b08e39b2968fe6c9a141d1e92ec9c838a3d8e1ada5e44bb08189a5d514ebfc2f5000000000000000000000000000000000dea05d8e6ab50b8f8dd9632337948a60568724d5a03c7914e4a03e2af572dd8153effef1a7d5c2cb27765ef2c17bc5b4aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f8899000000000000000000000000000000000bacd14447ede6af0e92e19b54c4f5b6ebfb94207efec3e9f385a4c84a7d670514ecbc28ab686b383e239ae7f9bd673d000000000000000000000000000000001698bc92d146049174b843dac8c5dadcee12d1d503b2d0e46ee68139dd43d3aa797fd5bd06e2b214cc9ae3647c98394a0000000000000000000000000000000018d20cf6c84446cadfa1a26192a04e16d2b2a053705a89abc51bfbfa35c2b03cd58021ad95a35364ae1e2da5d233208300000000000000000000000000000000113268e360006294fa0203ce58cbfd05d05fb625e1f9474c96c89c0ec1ea80fe834030592c2f1c182ef8a3d5c32caf71adde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca0000000000000000000000000000000001727b6bfa9c601fe84a65c54f556887c4538cb5383a288156fec87420ae7f15da395886e1ac0e10b8fbbae8bf040f4ba0000000000000000000000000000000012127cdf02ada71f28ed036a417971b87fe443b8c65b7739795dc7067082cbc9f06f7bf10c709969281cd072490c06fb00000000000000000000000000000000134f1fa1d277d01e2811c118cf10e2de6324e2ba14efcf717a03c1a10dca0862ebde0f6328839da63d7d85f573e8501f000000000000000000000000000000000d20a036b715d18ac9e2dbe009dd0063a4b13b3ec6fd060a64c4ad2b98e05e069060179530410d154caa575d504c63b7b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000000bd5375e7f98d3972b93420a39fd6c31da86d0d9349ac3774bbef15c2240437cc0761b2f1245e805d2538cbca6f778600000000000000000000000000000000100232139641c8cd5bdaa75b77e1e1c8e33b3f9554e2ae00ec6315b82cc00a6a70d576d744e68938a299ee2b451558250000000000000000000000000000000004224691faacb007bde3e37db6c7486aa5d3b4259a24c8b7653238e7522604ef4ffc1eb3cecf719a1b7f52ff00c34399000000000000000000000000000000001156ceaccfe0396374c6dec5adb39f14b6f08a32b88ef7499756f5cc324a9f1553bf5dc106a97469f2c49be5d563e1100cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e0000000000000000000000000000000000010a549108e77f0ddeacdc795517ccdcb357f909264457cab22fac2b982d10064756d66d0e48af02a59f58eeb1e8ba14b000000000000000000000000000000000c68703ef1c1e93c78faebc5f7ccc69e39046fe8af92e12469e9fd6baee62a2e8cc06fbbb3def81ae5cc57f488fd9c9100000000000000000000000000000000064ffb6aeeed432629242c3843f8cbea5bf7fe78585763926c5c45dc3cb4d1c79b3715506d7cda18c531ef890b22a1f7000000000000000000000000000000000e0eeb69f28a552cc6563f5fdc9919423c4358a2b70ccd56b048c22111454f67107513cda2a5aa0efd2af25dc74a1c47e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7", + "Expected": "000000000000000000000000000000000b1913c672760f98fc6d4e96ad1ef200f99add6f233b17291036e187ac6692ab0a29a4083dcf86a532dd06efb3d9b8c6000000000000000000000000000000000323b703abed59a9824f34d97851546a5e78441accea4e3a933b9270f92a9dd1aa056858ebd1739659018a0ca13b96e0000000000000000000000000000000001603cb3ed75c09ae5da6b94eea6017dac0c40b17d9aa8b65b78f2ba17de051bf3f21109d9afb214d260a09391f5526c10000000000000000000000000000000019f3bcdb8f16d9a2bd11e3f9491266780aa9110f447e19f12f7a2d62dc4f2c7b5fa18e13792007f5f361e20656c8ffdb", + "Name": "matter_g2_multiexp_71", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b7d06c16c77a57b5ed74d829ad6acd665e73d20f1d9285ebba67b60c0d1571e1b02cabe5dea44499ce6d40a83288aac0000000000000000000000000000000007e6ae768ee3d149c7130022f9883ed51e4fcf68c91327ac1fe85aec8083aa61a37e9afc25d3352e144aaf888f264ab20000000000000000000000000000000016f2423478e0388e8495a898c23d63a0929a2ee7cf848034e4c1adad3460c7070caf47631eb930678d3c85aaba044dae000000000000000000000000000000001587e63cdf50d6e0b6b3d7652ad0a0a2497e70259d73432831781641d3a76db4ac7cff1bef165fd8ba29200d7320e43475888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001181bebe3256dd6ed754b8a1d77ac07e9a341789b3f4a4988599c7c60a36f1e95d3e3cec52c54c0f0abe312ac358c28700000000000000000000000000000000189d224b2904bd45cd1e8fa72570a1e35c94680d03d30292892462664f9d7aca3cc45ecc0773e66a10248df28ba9a9a1000000000000000000000000000000000f654f4c8b02a891e14fccbd5a96228afaaf79ed8306c7c1267715bc934e5f2568ea06de2bcdc2a55ef708689d90108c000000000000000000000000000000000c0a413f16e1aab8b91a87e7027f067ffe7de65097da37d67f604a184c7e7a7af6fe59ced8c03fa32ab036862868b35018ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c495043000000000000000000000000000000000dc972d55b7e68f97191d988ae7be5f5301bce5c654b323d4c17bf6e070f7227c0789ee38af3ccc07b04f0793090c6130000000000000000000000000000000016288c405bb42b4e71d12fd0a798cfccc7d33aba0500f939f5fedbd0e071166169d3072befcc5549cc6963b6dacbef4100000000000000000000000000000000171ea4f6607d6efc875cd9cff203bc62eb83bdc05c07f702143c23ab2770f50f42738f748e6bb3bb5d6f51f40fea1d910000000000000000000000000000000000fb729cc9716bf2e9e30a598ee7c4281163b287422ab66b414da85b0b960102991c24cd023791e4241bda5b0f6ddd3424a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005720bcbc598c4eda697406dbb390c2aaf4bc22c794b4b664e9b85b7c2079b90f7678e69496a4a5cd3b46580b90a7a30000000000000000000000000000000001159788c3edf619cc5e6f77c4aeb4860764d46afac4cdce54cade63155040c631eed65c2fa11b9cdff14847950cddc2e000000000000000000000000000000000d61bf02587e2c61544ae8a98b4c742c26a3d6ca49c6ae1b19a9d69c7f8eca43cefd555c973145566f8332902217cec3000000000000000000000000000000000cc0da96623432a2c170f07a3aad2844c1c2aab9d1bb5d2183928c818e681c66cb3767be372be4ae65fa40bf5483258ce4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a1350000000000000000000000000000000016efffb5d4ecbd01567c1e6099c0f06644d4312c599579b4cb780fccc8a425f3d1263a5f0c957dda4508202a354162f600000000000000000000000000000000115686a37624ffa8272ec7dedb7a632ac568245918ed91be6c9116e0fde290c26b5291e5f57ba6a779750685b0f126ba000000000000000000000000000000001852662b92fb49b2f0359255db8a7a2d20bd37705b7994cef1eb8e318aed70fc37bb7af9fc0c32ab3efa8c0afad640570000000000000000000000000000000017a691c08724ccf0e668f2f4eeda692e9ac21385fea243dc62c37ca73421eaf51c3a60771da3fb3e3cb578de37d2d45d38573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000006fe4276e8f2e23127853eb929ee4e0c6ec4a190d46ac222dceb995c2e1d8fc9f822806a877e6cf87cf579cb2862c25c00000000000000000000000000000000044dc671bcd516cf03ad98ccc55266688856a4e4e5a59d6a6bb93e9ca77c596b5ecd2db6f3cc6377a0086c53ceed5487000000000000000000000000000000000c3ca83688d20519423b2b5547afcccbfaaa088a90523272c7cdc9a9b0397340350f2a5ced2a8153d69c81cd79732bce00000000000000000000000000000000069916c468f22bad174522d7fb94b4b7d2a664743b4689daa5423f470014152387a29425642b50f9e50fb679ddafdafa02257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc1630000000000000000000000000000000001fd452b8685b0806545e09783947551bc5f6446c9d15d079a8968a448a6fd6f8b7e91974b71a4b2c50954be299c4885000000000000000000000000000000000f28bdab0b0fd3e05d08ee2c51f1bc0d30380e3a7aa18d6e04b47018d6a8d2b35a8f06df3866ccb95ffbd9c5333ca94c00000000000000000000000000000000035f3aa1cff72df0bb10f1a6f8414aa2ad0289cd15f56d84061a7cc70562f1f12304c402c388e48dd3f34082aaf79eef00000000000000000000000000000000034730e3ad7a3373b97279a00dc7a053aadd088557e0da61b9aa132c5b402fd9aef73cc45dc1cb7f2076cb2ff27ae2fc76b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c80000000000000000000000000000000009c91d800cb1d52501520b3625dd4c20173684bad8742c7ac4b35c0ce028556b6529de5cb7541e3c146b13f58ccae57800000000000000000000000000000000124259d345bf2f8c16215be4b6b7922f4e2d6b32f91c4b1c4f1d4974918fa9e6fcf10e46f0c0b55e2a7210d1a5336eed00000000000000000000000000000000072e6231244ed14aa0f5de06e2f953371995a567684b00e459113380c1434a8faaab8b28a0280336ae35bf1f90f1d4d10000000000000000000000000000000010289a63e0e5f1f35b7af22137e117a85df27874ba15df39b7c26801c169667a3afe9a14663d7ac0c2956f4eb70cf11fc9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000d0f22a9bcda47ffcd034618c15daebad8160f9ab6b3148f1cacb32e713df2ef19f706f14131f8ab1181b7ef7598e3e4000000000000000000000000000000001680314cd79fec583c8bc0842e1750b1318f94aa7700c6662aabd4c592ca61ad51a6876b686ac3fe3f508cb40192c31c000000000000000000000000000000000a172bd8e49637fd9eb611b590c68bda707931e403db35cde1c10bb74c389ed725aab54dcd7048285352c56c8bc5fd920000000000000000000000000000000012589683ff3f85ecb006c5c435ca7bfd9d5a6fd06eb625bcbcb18577cdef610d912e783f3986c965710269b1ff79ba972467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000009720c2b3a0658a4aba8e76e196a558bd155ff550b3e41bb5b43e7c5946bad803b1de64e342956a11627e7f24f69fef7000000000000000000000000000000000decf2262e8369d6a2b1ce07fdd257abe1c7610084ae2f347640c0cdb98c7cfa732dc609c18b7b6a51b47ebe4b07a586000000000000000000000000000000000e8a0158702ff6d6c3a7ed9fbc774bc329681130840d86ca3f26cf6642cb49e5f14ad95fff1c94151457b1d5a142bb5900000000000000000000000000000000035ae66137629e95539e09ee99b001d5b9a6ede79727d7deedcbeb5acf081cd05ad469ab06c265a5224fd5236db160b62f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae000000000000000000000000000000000413d82d0b02ca706f0266051445c04f3ac594ad82e2f1fb4e8e0cf23a6c1087c29383238ad3677f170e99259e2fe93e00000000000000000000000000000000070af21f84895c0193f0b8174cb20b11f45c845a8d782b1f58182b149362e1368ba076ba702185fc54b5da94c3172f5500000000000000000000000000000000182e124ca29d66f9f6c370f6065f60928b6a8f445a74800d59209219add6cab0d1b79702c31d60e61cf56874a4eb6717000000000000000000000000000000000b94b733f76067a102cce9659292f31f3df2cf2770e3a83c1524536e29d0a84ea5c4883cb4e849830384dc7e157d8715474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000004b2feedd5badbbdff6fd0f33a4bee17b38cc8967fc72206246c349e1017ed0407fe08e0cd9208fa9e4e21eca4cfbc2a000000000000000000000000000000000df0d74d5cc17ea94457c0ee26ef24071700a0fd6bfc762e3ec69b8f1c096887f679e312f07cce8340686eb2716c9a96000000000000000000000000000000001878edbfff2efc5af64aa9a23589a52d63749b7ab2970f256874fe0cc15091c4511050b0a243d421dc6536f19b5977cb0000000000000000000000000000000015951da3b20494a266e4d014d0ec70fef4586c8656baf536a0ea9a48dfa041624e8154989a2fb106189217ca979ddbe8eaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec000000000000000000000000000000000657fdf40c829719db134acd6c2a9ff904681b1869f28512cbe2a64d93e5b62114a73bdc5260ad9a1f24a3ff191b7a3e0000000000000000000000000000000004e77bf63eb9c4741028dffd0591b4f525d533b455d35e51cd86c7884d63419a162b145752bde188d2a622251c087f870000000000000000000000000000000016cf02af01fa6750b4d862f0cdd5a87a79da7c3fbedb0fa356ef2e7419e25b3a2bc8cbfa97463d463d0ab349efaa3f2b000000000000000000000000000000000ea4468fe6a85d36ae990d0ba959ae050756805c4c769c829de475a2990ef1c46de20d5b466543978faae0f6045023e85158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000018d42a2df8ca475be6bdc468a82c313599239b974ec3d27e8b8c534aa4d6b85d4ee9aceb15c38b3bade2bb1706a2c2cc000000000000000000000000000000000124d5dc60527faf48f5e9574308f8a328b410de1cb49f2cc6f76b8a1f2707f2d1a94bcbca0a97bc38f24545a8013b250000000000000000000000000000000018b690b3d1e3b22946a91ace004e1d8f92eb5beb284eb05b52ac5ba003d7bc387540d33d088a02711522e3aef7f74f4300000000000000000000000000000000103080d8bb379d961da06bc4c148cb5b056ae115b3a0e33f0a8c99a7fb7b7ceda35d3902e0733156d354dd0240e4bcabd66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000f4d068354cb5b51e5a86163978386533f8f9b6e388c5e75f7d9ff5e1ab6d1637717d251f2b723b7d683e26a274d610c00000000000000000000000000000000001ec5a0d408c55f247d62ffef172ef26e45c41029f1d04e36f0dbb4fe8af414b0f7fe7ec0cfda66a2855b58592486fc0000000000000000000000000000000000cb1b68045076f457746621cd415d743701bf3ecae8d52dd5582c3e0bfb38e6cf2651a5ebdf521afb1ec5b8066444210000000000000000000000000000000010f5672f813470378fa806abdff90edeb0239b00d85ff23a3fc6798779f46d6b43071d66f7742897a4e53ebf6c7dae719acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df000000000000000000000000000000001422fbaf1bc2908be5900968af61ffa7b3af46e7250e4663ff321f42e2db057bcfb2106c433a9eef8fe20f7138b71d280000000000000000000000000000000002176e68cdb0ada2d7baea437bec8754ea293d14afb85a811f7a5d740d645a53e511b5605445b110174ceb5e6720e736000000000000000000000000000000000a69e992b6f4f7eaad2682cf9ac2e58faee9b3341e852543c2aafbff390ae067a641b2b5693319618fde413fdc64d6c10000000000000000000000000000000009440317af8f5c753b5de4648b06212256a39b7fb03678f1913b0a3d402a50e74e2da5d29c211cdf0b292c132759c36d0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8", + "Expected": "000000000000000000000000000000000fa31d16d9625200c13a415fd61b7552646c62fb8db307e92c1ac3d2acc92336765a1db42407ab0f774ccf01291b9ee800000000000000000000000000000000156a77678873dcbe4832b9fc7f516eabc1a10f4a6576cfb15765cdf999a771a6a6d41021897dd783e9beb2db722a6fa2000000000000000000000000000000000ee4599a6ca9642cb4cf38f5f2af23271cc8c5bc6e2cf6bad572b618bff9f8677837104b93ca8942843fd5db5c30dcdf00000000000000000000000000000000138986714a4053618e66a85835d105f4aa2ef38ad18e34b2ee7ae30a4282f7e543c80c94bd12c244506e7fcba25f4c1b", + "Name": "matter_g2_multiexp_72", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000083c515ef8509b12ab85ad7d0a816d986bcdefc14778efcb3bf7c2ab61991849f279ae6a9f5342880837c0d0f4a4eba700000000000000000000000000000000020cf5196b5d567fc429cb9ced7b55e4925e18c914caae216a736886a8d886c4bdf6d704bbd0ceebdc1975ef530c665a000000000000000000000000000000000f3d0a217c224434604d63cef559eed3864d2da62ac00d49fab8c2c6e22c688496adc30c8d591e21bc0be404b62083c20000000000000000000000000000000003d0bf7f25bab0bf2c768b44e10a6022650f7d5b7d568d502b9d0b28209ee69b1d952ed848572d3e966e8771c20becc4b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000cc4c4b7eb7e358d4133b65e635fc13b8a92229706a6dc5867171a60a99a8e343045a794c368f1133ae6cd2788c3a7db0000000000000000000000000000000019508aa39fda9c3efced287d2571db97045f8b7b0c7a9c9d51796aa8017fc0e5abb8fc994700dd5c9f755edb518e096600000000000000000000000000000000049f68b0ac142715cfb385161ee70e453f0e24e2e93f3f96c3d69447f3a28b180fe76989427b2e392c7ff939011e04ab0000000000000000000000000000000004903c0f8e0757dfd3f5edb4f54a0e292df15ff70757df7b0b04c99f590a3dd13c6ce7bbabf3e14daf9f3ec60e2379aafee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b23500000000000000000000000000000000041128064ac768664f076116247e0f8a00adaaa824cd6fff33bf524d0c76e61203408ac13b294aa41f5c462cd42d3cec0000000000000000000000000000000005e150c27979ff1cbe307511816be900648957624caed1f08d88347061cd783179c615258fcf3619bc4bfa53d2513c610000000000000000000000000000000009d2b3d97d29386b93d7af014ea8f1cfe2c1db5a9aa0c17e8430b0fcde974a4e7b8b42ef041e9a7b1a8aecb97cefb52e000000000000000000000000000000000d86096ebd88b2cdaf5cda1e9ca6b7f12ed5def629354b0570eb084bc7139cf20bb8ebe4438f87937b8b554e2201344c28728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb960000000000000000000000000000000018d677cd67e96b10b671d2ed9234d7708042ddfe6fb804d2e9371a80ad167004f9d6b92d26b3d3af34ab7caa0e03964e000000000000000000000000000000000e34a6c85187d328eb33c2d5b2ca96b5210d47a779ab810dcc380dcb7e6b3c334ac8fccd7354aa9108136e4f6dd4ea0a0000000000000000000000000000000000ab8f7274ee3fce1511c58661625c766ffb0ac68bdb835a948b09b7510bb573d49000000e3d3cea772bd71d79681e1800000000000000000000000000000000135ca42f2103905748a1c416d82170f7d24b49ff3f859d6cb7493cf89bbae0217529a9edc835be1f9890ce105877af630fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000007884edaacca499491580c8c7194c0d60ac6eba95f7a81f63742451c8ed21a223ca545d5cc1e648b9d2dd05016b4fea20000000000000000000000000000000014c78d5d1a93760096bf6da73bb41631e94d6a1b251ed0be7bda93e4c50568420bd4d49e4a46e5be4bb204cdb6b0ad5000000000000000000000000000000000128a860c23a183c5bdd18b4a1853cb53475f1a893420bdf3271cc4a65a827eba6b92e1f9e8ac0d10c73edec5160c640b000000000000000000000000000000000ac14b2170042ee6561c34f77fca40e1bd2d40d01798417dd954905135ed9b7772e5689e6d4e543d44a4563da8c3ca40c14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d0500000000000000000000000000000000144df2e76821c19167f60630f50c939b66867a82c2a5f807e943676c876aeaa2aef2126bef7fc431f0c7b39e648542fe0000000000000000000000000000000005e463627bb2d22c25520c27c05cdc75e1f2ee3b91e8088399ee42ad13ca217284596e5404b4370995f71fdbf1c1c7860000000000000000000000000000000012323010d6aba1bc6b1d6e7f7e8c7bbc0838564b279d5ae6279f7f7d3cb5d96273e27e7096e9a8540463ad16deb3780e0000000000000000000000000000000019102ac6bb33bd1c5a158a584ce32308b6ee5679dd6d2acdcfa4b9c54674fecad7489d1e39c05b1ded88e4ea93620724d81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c6000000000000000000000000000000000faf210330693663c8a1d1fef78e211ed2542f7ffeddca3e19be3ba77ef211da1b8bb5abcfc96b692d74f8c7df40b0ce00000000000000000000000000000000134153a252fd8ec5d9aec08ba09a94c4416f95ff6f4ccce59bd400474c836af5bfd941f03384ca4bd5c56fbe81d96ea2000000000000000000000000000000000b4532ff1ceab2a3a177cb83a75c16a833a2ff28df447def351134ec4fcd608b2b75b1f8035ba7d40a737087f3e8c1c100000000000000000000000000000000127e3ed13384b69819b34ef8705fe9a66dd01b275f1f74c2c724420546b39c70cb7a8295a6c1ec4075ead4e3312b8b603a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c000000000000000000000000000000001351d0d5d531a63a5f56aaf1d7906b7ad2bfb4e9d823e2659bed4e05e7edc9179a7bbf13405ab5cf410b25c7d476c342000000000000000000000000000000000f0ec96128e058e8bfb6e0df1331887245dee87c4f9721fc7f1d20c20a2feea7a7078a4946803ac093477707598d59b70000000000000000000000000000000009399034e4aed13cbf197d8c4753285effa72fc53493ca316db11b39d5527b009aec6350d579f9dee22cd6d4cabd88ad000000000000000000000000000000000002f41ed0dcfa2437cad7b12a94501266d670ed6956196c438241aeb90474d17214eec5d5217090d28892d95f4e40055af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000ae208978a751f8921c6067ebab4190ac8d3608dbdf50222eec59460095b8ab2abadd97616c240edd0a9c53dd006e38c000000000000000000000000000000000905224b317a1e64d8af075b6db9de46ca4481458ad6bceaf726ba0f63e81e2a0322e79e70a5a82034abf00d47fccc300000000000000000000000000000000007173c3359f0c2e315d11d646a76e6f500c0922401e4bf9f4ccf2f0801a567fa653f287fdbfb878ba0d9ee12e25396ef000000000000000000000000000000000161d4cc71621e5df13d121c77105af195c2adff5fc6b656b0fc1dd6eb2518f474444d8bc526ae16387f23a4ab3f342f6541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c60000000000000000000000000000000000266af9991c393d3b55f9e0f22b0967d47dbc5b0c97947125e220c4bf9f4bc58d32ebc7bfb02b2e329c933ce41d0d8c00000000000000000000000000000000004cf5748aae8dbc1e4778dc85da575de2b6d9d346f5dc5ccbfd82513166384111f5e5f2f1c2f7ae367a22146d1fac027000000000000000000000000000000000095dbe68521b2cf51283a8cfea1f20eb7ae37e6e945c5f879ba4834d20918b74981f9e0eff4543a79ff4eb36d84a9c60000000000000000000000000000000007953cad14379ffd4309cef1ed6a2dbb73a93db0bd3a256753402e525bb62b10aaf22b662bb2c704865690af995e7d284b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000010e99f318111baeb1b4611847fdaea7cbd5e3ae532af667ad2498fb2e97b1eee0297e2811c7ae854b882f616da7733fd000000000000000000000000000000000e56cea75b4c4e4c669a492a6723fd60e351a66dc5c34c46469dc36cb04d2c23cfd4aeaa23d0e9e83d5b78a1b77696ed0000000000000000000000000000000018f838d6a582a52a508cbd6bbbb9cf515e091deb7a640e141dea4018af6593c001dc43a8fe4819a7877d9ecf53d5752000000000000000000000000000000000119aaa2ebcdb6379f7ae972cb709990a3e8254f1025cef308281bf7057295e3099d1f3127f76bd2f9ce0a03ae0de8e8d7e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000008f837c478e874b857f1c939a26a02e13061d50728c10939ffcf5e862cb177993e204590699a28cabc7593056617d433000000000000000000000000000000000432d9e66dc78bb58ab98771e7e8b5fe51835f286b488e2df6c1991fd36c3c537f2ce30abf24f9d4fb13941189972e39000000000000000000000000000000000b202de3708984f44f7d05ccd9e574a2a93a285d5ca262017346580be273c58f13165437dc90d1d4103d3b9eaac536ce000000000000000000000000000000001873e1251d9ae9448de8e7ccb7ca59a21bcc0d07a2819d140c06ec33cbba559ba90647494a7ecdec8b609b58cf7995cbfde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000000084e07b6576c73aaf43c0ef9c5666dc988ed93d1a106b71e4882fc0cfb5e710b91e5d5eff57327f5678f662f4a451d50000000000000000000000000000000008a29751f1653236a48adb5fbc59059c7137d36139574c6af97314bfbcc22f77a4c5162092762a26b5da7887b94f2da6000000000000000000000000000000000a4fd84c4d58cb9e18aeee180fb05f07c3e1d7ed8d09940182e9b4738744fa6faf600b6f720441e0ad6391a4d502ac040000000000000000000000000000000018b356be2aebca82c54988ab2a2ec58751ce7a815f3dd58a2218a638753d4734d38b74ca0e00bbc8681768f5d1a02b646f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000de0f22cf05620a5d4bdcf50ae179f23a9c089fd6eaeb14eca937d9e2480f1782a1c67df76e06191a9b87514daa8bbce000000000000000000000000000000001981cd1f260e7d96e55533b8e29867f37af507b4a58abd69e0ad6af2a55228ab1c82fc2de52deb7b7b7deae2fe621e10000000000000000000000000000000000d22a7a567ec8826391ee711768e612c403e3c16e20947ca5861185c24728b6c7e7756debb333e7acb53d86032d5748900000000000000000000000000000000016fad52e1e86b9e092955cefdf93a10f30db896fb519fd2ca12571d8dc8aa352cf4f8092e0e973d0b0c66df78433251e2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000000484bb2452158bca93dfeeedb40745bc5d9a9ad49afa20e6c29fc9ed1a8fde33ce508cc252ddd05fc486f8ef78738ac0000000000000000000000000000000003c2d6ff6f292b0f0e505fdfdd2940e72bf8c2837da4ec9c74fb593fe3318a9b9a8592524bb5d40f6c38ad871ab7b6150000000000000000000000000000000015f888ae2722713e1b5b02803a5b48d53116c1a4bb1191c9da77ded8c6ab49f1620b0f7c7867957d84503cfd3dca1be7000000000000000000000000000000000fd96baa382cceadc252eaf000d47d8c1e2085e9f274dd9dbb571bf85bba612836e1da2453fd914135842e2750796b54762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001820de289f62058920ac3d4bc60da023ac29c431ee429a10066f305d2b1a333ffaa906404af977cfd3212b53e66726b500000000000000000000000000000000094e448db84421e25cd03be3867125cedc7f77f286f404524757f3c1a9cfa28ab6771293da490a4d75852f515dfe1a6700000000000000000000000000000000097dec124970bc63d8f62f9133157d412f5ad3fd5eebb444568cf0fe2825d6ef6577ad302842f35570c9977638c6a827000000000000000000000000000000000490bdaabf4db27dce906cfacf3160c0fe25959df4af89301cbe6eeb29f72e4c55bb467841ba7d0750a59a32fc8b03d0ffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843", + "Expected": "00000000000000000000000000000000193358b283147ed5848559d4d1533734822b0248dd17b2effa80920a853b70e7fb683b08aad6ad4dbb91f964ad1b3bb6000000000000000000000000000000000649be60ba72734db4cc307a2fd8be57857f60660d0c496c0dad73794296552e17cb2eabb3537ce677edaac1c6997341000000000000000000000000000000000f91ce27345e86003c99d133eca50710c0722cb35af2ce442ebd74b46d659e0118be9bebf32111c258e4cb4ab795a2cf000000000000000000000000000000000d76ad65233522b1e079fcfef4dfa80f163682d7984d5062680a5dd4cbccd5044de4705013c6bce2140f7950032f90ec", + "Name": "matter_g2_multiexp_73", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013fe4afb94d08ae311b7442de7291a11e733d8e555f2da6f72bf99da780a8f8d357cbf3d8959f6aeaca7bf3f5b5bd10500000000000000000000000000000000025af713b18cbdb5a960371c2dd0317f4bfd0182f4bfd6b88d588b56fadc1a0398412e7e0a786c326aca8779ae384243000000000000000000000000000000000581c277053c15df8eec05c34267f62e63faeefa2d124c2b4b84d2a739ce5484641ce955fbecb901d1e8ca816690189b0000000000000000000000000000000005355dd304b9b60498a3fb1f08e1ba0c98db327365ca9a0365a7f1e5cb56aec43b7fd2b4aa104eac7b1c30b6f53cd422be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000016b5b3a6fdeffe5b9a0244a333ada4444a2e03771f94433832a4617be696e467b4e88ed80b174809dde4242bbb51248b0000000000000000000000000000000003dee846c5b84f89734016e547c63c02e4be07dbbecc86f811e2d8d3245f91205bfc055882565371db532240da1a845900000000000000000000000000000000194d53bbfa962def4da2a9bc7129fb6242a3922fe26cc4e603528ff31393a31d03dfc3463704250ea2ffa973ad175153000000000000000000000000000000000333768faee332d7468119b9e0469bbc7bc98a482562ff2fd9aeb6d9c67daac9c3da1db41c9e12224a2eff2feee51778bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c7260000000000000000000000000000000001910ded86d79f9b043bb79cc4049e0652c13d0fb8db2f070d695124d7a42cc3a2238282fc8a424fcd8d9ecdab4bb6fad000000000000000000000000000000000dc8d6caf97416928d2d58466219f054c6f28f49b2bc04d8a80cd46a308bc95aaca3a8df1914ab0c7da341862fdf47400000000000000000000000000000000004380ca7b1f7ef96295589f78a1683a51bce4b2afe50bd6076ccf5d07d35e6cb2ec7f74fa35097b2c0b9fff3f4797c1100000000000000000000000000000000054f492d7442b1c0d1293277d95efe822faa7d8881b9afde20db58d6267e049b90d0c8828a6c12540f4ba1e7c9ace6d84f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d7284000000000000000000000000000000000bba4761eda87a304a80180c2447a1d5a52f743015ea7c728e70d6a5defe3139c80696f842da3f06586be8d506ca4bc90000000000000000000000000000000019ea930d5733f4a1ace9fa0139d412d65b2886b659770e388894592de0694d38876fcd86d14580f9b92518d5496fd44c0000000000000000000000000000000002bf5d9a36d641d1259c1b30397aeb071b88844c4cf17e3de0984129d7b4d67865157ee2f682e7cf9d968fc07ce43618000000000000000000000000000000000f9a4f29868654abafc7ba935aa22d3d010023ef5112683a037a6c69b9e89374b256b8e1329eb5ad306d9f2063c22c335d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd00000000000000000000000000000000004f194f21373f09f8cb4984169890ad3855e814a4768c84e9fc97dfc181c60114aae534a27d3eb225b2125131c754ee000000000000000000000000000000000e6f88880e9645e35806d193f5d16799d63e2f9edd8ae28df54d19875c61857b0a34819a70ba3e9c31f00b5826b0cdc200000000000000000000000000000000193293c6cfae9ae4b24519fb23469e2f8dc4eda8524ee0b00c7141587b07c8a26a29841d41cafbd24bfbea2034a9c18e0000000000000000000000000000000017433efadfe9873dea9a68177af3d5dec4a13dcf4a710422d52020d4d145e2523ec0b48acc533a1ac7068c08ae6aa28bfeeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000011dc003f7542f6822cb872117fa658638dee2a15429aaa9dd576a7e895bc0a2160bc120558a32aab9e646354233a1afd000000000000000000000000000000000fe9ed8ba572ef7d1176176a31fa92a5ff3dc38b0183ea1e22618e3b3214ee78c53074d4c60b5056901c6f046f8210070000000000000000000000000000000006ef1c20c3bd88bd6787598dcfca52da4e5e0e7c7643af983c709b916e71fd15475da30d763ddba0899b182cbc070ca20000000000000000000000000000000001a38a2e54a44ade572ecde076038f5244f266cd99532024a377829a64c20fb2cfe1633367c74b5990febb08e776bc34edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a60000000000000000000000000000000011ce7b2cba037e5f3ff19b36371d34e287eec807178dad4118c6d43aba68623e182aedbf911a2ae5cf3d0e690ec3ba790000000000000000000000000000000017a617453f391e6e2437d56ee831ba895084f60d1a5f342e19a242b9661c703219d90a157e1b55f005f5059c15c179dd000000000000000000000000000000000746ab134c7f4bc19583a4ea4991c7cec3f651a60582b40c17b2d18cf6e252d93d2f3c2a1a3399be70512ec9eab251de000000000000000000000000000000000698daf214f2de44ebfaa36379862bd9ffb40987dfc8e632f14738c93c8e5c3fc7be9fa9100fb5f7440311cab34fe1897a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000000e4ac65ce62180ac602ad68098ee31cb747886e95a183e4f819d54af99850d70496e6952076084dc7bc2d3f7a273383100000000000000000000000000000000182c718fc9e5cc961426258e82594a5cafc36270af0eb50646d161fcc192c30d40d06647e14a282421638b31f378de940000000000000000000000000000000002bf448ebd27cb6270e1b87087796ca6534ff51ba0962f3290ee1d06dc18ed39fb736ec95632b483f44d3a9d0e45d1d50000000000000000000000000000000018b956acc1300e60b22bb936b2b52e2ae82e256f15f1415263157965179855137715c321d3765c5227dacb63ba2d6225f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000f5372651ffb40bf853f6f8396a7c7483c401b89b67e098ea888fde8d19e7552a006a127af1f3311203434126ffad85800000000000000000000000000000000050d7e89b21c7484cc5831885422fe7aa8e898df85cf7a3a275370623eb9660611610cdb829d3935f0d0955e0ac97506000000000000000000000000000000000f83a3f79f1dd110bdb8521e18a64490d567210801d77fa3c0c6e5cbc7285840da325cab7ab08494c8d516511eb189dd000000000000000000000000000000000f72904131be66380c5a18af4857ada7c15e88572197e100de1cfcc9fdb4306e446f2f330fefcccb41b676f24e3e0bf88031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000009ba6bbf102d390638ceb9259205a1856def2b3a4b5209eb3e4e54074347f71b6c06b70764fe85c8dfc9074067b8d00d000000000000000000000000000000000339c30631229eabc1230240942bdbcfa6e18f23bfbf88b7b8a8fa92f18e35d2f7336f0b819e875ac643b43e6d931e68000000000000000000000000000000000600cfeda6033ff51c3bf9182d22abbfbeb6db46c0fbe15ba82e72fee483744ba5a57ab2eab6f35927b4ba6d2b150063000000000000000000000000000000001530bba4db8a60bb6b7a05f72dbcd23044011d75221d114b839aaa9535400874472f94c849597174322291b5cfec4974cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000c636ac98557e22897fd101dc6c54d87060f460b4cf2c5a88ea14641e2a8a9395492fc5a946eebbba36dbe38f6f5c0c60000000000000000000000000000000007fe3a557aa93f2e9aef4ffc55d39a9172475e6595fd57409df3a7fe3d11558c4d3dea3396ee62f61190add83b85813d0000000000000000000000000000000015b04e0daf4a10541623e7523ac5fbe57dfff9ac17afaf4293c493c1982f3395980ec63046cb1d424c6dec91899202c10000000000000000000000000000000019617b191e9e493751b0a02511a18757330bde56722a72a29a399ace983db7114f84795e2b70bc9d670cc0095220454ed983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000017472b8c1cb3ec528400649fe7c39e3908b16ed69b42d967e4d225b694544e8bc7ce5bec87019db5539f1de39dc6807a0000000000000000000000000000000012b1c4884c37037a94f84c15061df5ca6c05c5a35ad9b37e3ab8e8297c9000e715fd2bdc3f2b485e86c415bf656392a10000000000000000000000000000000002c21af2933029f04b344be76e18ce499def4a0671a97dd9b6a108d0fb23852fcdc56f882be0319978952ef04a207a6a0000000000000000000000000000000015eb31e80fb162d5fa392fada8d43648ef54d4f9ebcb0e9652dd501f55a8875a16a148d42e283ea8bb2c5a38bfcc8843a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a70000000000000000000000000000000015e68e011ed063a9fd9cc8a806d8e3561e4f449526ccb6e5ce983ebc4fc49d61d26dad7db64f56ad5ab0b54fbdb76e61000000000000000000000000000000001617d7387fedcdd772a34b267a44315212d21b798c0fe1e7a9ed3caafb678910d9c9c3bd1fff4a3c8e339d0c90a865b8000000000000000000000000000000000e2b3c9b9cc10f41c4c0129d34c62d526aea47c77ded91a5ca3afa0da1801bba81def3ca66a978ebb2d1f3227ea82a9700000000000000000000000000000000096b6caf7b6f29e91bea370f91c2576c188b08b95f9df6c7df995fc9879c11cdbe2af86809468d472fcac8a89716d1d87ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b80000000000000000000000000000000016daa86ec04f57c72395d96b6ea5d6ba7cf2d9d4a50eb90f7121545f17c1ee16216f4086481d91e59fc5ed8542baeb7e0000000000000000000000000000000017a783d60be67206241e0bcad20e371d86d47d88ba1293b73f32999b0a1646967e5d031a5b28517f035168d7c7d7927800000000000000000000000000000000058f24fbe4e9befd8abe364c961f0ca4d9083260234a939bf6103a3e8f10a8381a9e3d74af7c13f159e5c7dcf456df00000000000000000000000000000000000485c9448fe3a069eb024ec43aaf563a98da09c02c294da2a94a98a95430e25b062e8ff886fb5fca240fba1abf7cee60297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000001230577527a0fde2e8e66b8c4d17594bdab8be1339866819c8890c600b35889d1e3a749fe15fd8182001e30e6420ca6d000000000000000000000000000000000ce03cccfa87229fa8d560884d8c7963276d79ae9873a23d550b4555cc4bda35a242dd2e70cc730b70cdf898609b3d8400000000000000000000000000000000174aab1f142fbb7a45bcdffd64c2d38b99c8919baf9651aa430bcd39613d7565196c18f0f4ee6fe05f5c40ddbcd4a67a0000000000000000000000000000000011dd23f59ca2a033ee5dfa50afb0c7ddeaec6d4f50e1866cca3f061fa03594216f005bc65b2c97ed1109c305e16222671b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b640000000000000000000000000000000014e1cdf4f10b11f47c15d0b6b7dfccb6081d05d116c8149989cce4f1c53dfcd2d0b7443677b03d037710eba813f6f597000000000000000000000000000000000c8415c7d5508010e0db1878ca663d359525b290b2f02c61436e945145a7a4e1b3ff4e27ea1b2c8d3adbe737d8291b14000000000000000000000000000000000e424ece68003cbfaf65a54dba51e7b0942cc53b2fa9794b4deb6aef1dc1ba1719cba285f9a1a59e71a881eebffe2eb9000000000000000000000000000000001404f9a3146b7201b09c5fd678fdbf2111c48130e82cc95012e5aec1df7e64a3b3c727afee4f603e620925686e126c0f5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6", + "Expected": "000000000000000000000000000000000e9f6bedba1f6e2a3ff33e0e4b18fbf8e77558bf42e89023df6338b03a648c591486c63c2ecc8ecbbce23b3ff9a7ae6e0000000000000000000000000000000013d2526d83b4495b5af645d5a1af7bd40bd0ebff125e0fa14f10d1c08511dc29643dcfbd25ca0bee5705a56b26c558730000000000000000000000000000000003fa442ab532094d47f1a9111c87deacb15d80ca6e76bfb5f9b9a209bfe196643351d778b0c6d6b274b4799f733abacf000000000000000000000000000000001278d51523d5d9aefc0d3783e745da54f74a88620f2161090a398defdebf82d13d5b5a21a5cd466352ab8685b034fa89", + "Name": "matter_g2_multiexp_74", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a497e74635fde8caaa5c9dfe666b1b40732e58b93a72d39c8a60c1f4b262e1f18f62229a30fb8257bf895352ac4d249000000000000000000000000000000000c1b2fcbd7f78d85c73ae55f67110b575750bec353e55761de0ff09a9f8a2d916c336655d8f6a78dfbae13fded5a9c36000000000000000000000000000000000173893333d998dd32cc3e82fd7ad8ce77003192ad2bfa1b1d2b43f9466898313276b922f9fbd8e83e86b67acfd9ad780000000000000000000000000000000004ed01b702bbafc73dc1e6846bc944be297ff08d1dfef397603294c7fe11668cd0670d386a8fa0f0f02c52d47f54a11b34aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a6000000000000000000000000000000000b5ec74a2150dcf5ebe09f39234c4dfec623318889d92b0bc1f197a69650bc48d28a1112306be763176b691c6915dc7c00000000000000000000000000000000028db19af73ffdd0111dabf9c7d6879cc7389320a249f108b41be8b1d4c259d5889dbcbb48b30a288e26cd9926682d1900000000000000000000000000000000172fe526c62f9cae49e6d3284170e6339d5af256441590cae9507c61f987eb495d340500cb761896163cb8ec631434690000000000000000000000000000000013bbfcf9cd3167b47b48af5f5ed7e6d45a5fa38192756c9e140eb89a85c75602814f767c57108cfa2f726e71f31548f808ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de0000000000000000000000000000000000052d7a0f93142b36489cfa21d76c0eb96904a3ddd946a53b8a6730036d88d30336fd8aae3ab29ebf62a48c6e849ca66200000000000000000000000000000000198350abe8cc91bd675f26516d771422c128d5dc0af844c6c1af07bf04a1d3ad9654cbddf2de5b7828d1446c45e7828b00000000000000000000000000000000198f35692d5face8dda4b464ff48d650145242852fe189748783b1a2e48806294368ae0a99481bfe739fb4962f3b86a4000000000000000000000000000000000e3cf2e018a7e0acfee25bc3a82cb282cb377bbd72ce3044dd20e109d948f68720c27aea3d4663ee45b2de6f178a00ac58c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb41500000000000000000000000000000000150e6b03a3052d043da6514bf4ee09baf1a35b2a909473db33ea0bd4c6af7d7aee9a8366c1d08d2adc5998635eb0dfb0000000000000000000000000000000001370c2976b0d36fcb955e797087e6ccffc851d2450cd63833d6cbf52e1fccbbbbf9dc695ee45c7df01c2828051bcd79700000000000000000000000000000000048b5fad2fe0af7ccdf675328d8ff5e63b564d8436d04c55b23b6ab7d2aedbd25d614d1780963fcd03d569bed2085bae00000000000000000000000000000000141f94b4e7ba542707d0c3cb69f8dd79e499602952be2374cead840dc669c5ac57089c5fd60c44291703b872098fa2daae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c000000000000000000000000000000000eec51e0ddb8cf9914304e7766a7418e2854ca71367c1d2b3875c12b7dc5c7cc2fbc136037bb7ff72458027104ed3f270000000000000000000000000000000009fe5e8d1918f9b5865a8b97c2c2cfc8bd750a0ccbe2942070827a09d8e41ca795a86b2262b10462795f833c73e788ec000000000000000000000000000000000b95c9146f3f560ad880ca905b5f297e48905680b4613e91f393f72ddb042f6a6201628fb5f75fc23f2298cde66a6df5000000000000000000000000000000000a29a8fba7644ac96d77ee73a93dae23b03d81a57f6cd8cb4594b23571cc1f658f163081ae50d72e09c6513d1cd2c8bf72022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c012871000000000000000000000000000000001807dd8d2bb40a642fef693739b1df12fc787db0f031306f31970d0f59f0c97c0894afc34b9a9913726a20dcb7d5191200000000000000000000000000000000096fe8bb5e911c1ed9985ac08d864c7020367f4259a0d074973a26cc421a44e8034a7007f6d1639285cf8acb8b2d64a60000000000000000000000000000000014026d43eceb26b9ab5bdd4139d4f94349b273e43f27737f9ad26d23454cdb1d35ea793d21f057359d28328a82d5290b0000000000000000000000000000000003dda2a84bd1f92524a8ede9f5e81f0f64b41b24510f4e0b8146496a776d5b509968f188c12c2d66cf755e5000cddb3b800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc00000000000000000000000000000000011246ad07713d1916c662679ab757c053e33def437d7a976533f0ce80ff6ffc259489c26524ea96898c3747c4127539000000000000000000000000000000000acf66265811a57e47a4c98b40b12a37c6f439550b18215fcf856c167b7218397d7d559f852fb45077945a5074f460be0000000000000000000000000000000009badf2799f1c43a2e3859123aca91e894f86d6298a06a9127249100ba270f2bdc79cf511691bf2d7faa45ffa17490eb00000000000000000000000000000000069438b1d53efcc4277ea7b41cbd28a19f80b5380136f62121e766bd2845e13d5cb40b2f15d508414876ddde491a3830a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c01300000000000000000000000000000000112c4cc34da9e83207b5ea8a9251ac5f004546596f2294b3fd51b77ad8d8e98239d53ec4f527c7280801233175500b1b0000000000000000000000000000000011dd8627748c9a2b08524f88e560cd3944bfd1fa17e1d6e2e9cd025b04f2e3ed35125197136afa2848d24fb5fd19508900000000000000000000000000000000093219f9ffbfdaa60c5965b45a5d5bd923eb5d3971542ac147de3f591a5fbe31b30704a0061a524e2ddd05a45dfcb6a10000000000000000000000000000000006407dffb5580790e250a72dfe68a488431f61f45ec9df279217b8800f0ac1ab585d84e486487d5688735fe5aae75bacebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec73040500000000000000000000000000000000107ede23f8e4f273ac2647fc251008905966dde32339c023f1da3c4d35d483a55b54f4157a303e68e1dd7fa3f3b14c8d000000000000000000000000000000001739327f282812fbcbeccb12e40df049284562d8986b8d4559787e1d5247eb6c83d6b838d099f36d8d0e32da2a7999a10000000000000000000000000000000005e5b6b2baede3ceae776da5adf075c1d774e83d6129ccfe7e835862686bb4064b187cc0be0cbfed37e5cc039f3a3fb6000000000000000000000000000000000249554dcfa53f73ef8f08daabf20c55301f75c8ce095cd794061c55e195221602a54ba54260980bcdb35685e41d0f4ffa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a700000000000000000000000000000000048b7fc5a71787231f1c7ed2134be528fc8d8f77102bda806ccbadf4f9bed79ee94b43c0fd3e5b1d776fe73d786872d1000000000000000000000000000000000152a1f005a64e16949d7249c3b391d5c1e0ded4893d0ce926cc666f0f88b64e8dd6ec4f92ddda18127ec24cad7e40b40000000000000000000000000000000013a2e1e7958a53307adf3beb32a88b7c493df0e37e074c9105da3c09bbaa01fed092fce2b1800790c6e8af3d30ec5a81000000000000000000000000000000000e2d405806764c75122c1b5e410673b28759f26af7489cfa6f35c6c0dd16c508af045009853f3329cda4a67948232bcef0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf2110000000000000000000000000000000000a71d61dbb3ae37230a2dceb54061d5f8c1ce645e20ec39785c229cf79aefe238959b2745e3b50e4b3c20c7a8e2ae27f0000000000000000000000000000000010e82b8dd5faed6bbd5755c4e5a88edbb3511d3f4442d1e44b82cf72a6414bf6558d29e8907b07f71c00f537637605bb000000000000000000000000000000000d8c93f1984b742b5a02777b706970215c7d8eeeb7377cc26c3af9005648c2eaea7f7a3177b6e049b132ef6bb4b188da0000000000000000000000000000000000ff082a252082499d70eaeba6d5514fc8d641404b48b2ecb256eeb40d9c6b68ad5af58556c9dcfc5667621c549b8ee760d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f9000000000000000000000000000000000f4cf354b8de6dd2231448bb235af3c84daac2db49abed345da6ded50eae93982a4f2c27b07ce725a062b07fdd9058fe00000000000000000000000000000000076cf19408f0f0379c7e65a6675b9856782990986f5c6d7002e9c9c74b95ab875924bd7ad5e4812844f6d1f530e58deb0000000000000000000000000000000007acffe32f96f5e56557965e3db8dce87eb7140d93608cc003bf4a43fb261bb7360c576da0b7c4dccdbdd9cc53b5c5f8000000000000000000000000000000000eba1c668fd9323d42d6a82d9f075cec2d278cc57122e25ccd72cf8b5a569552cc6b0e9f88d23b9b7af18f3bfa0cc820006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d60000000000000000000000000000000006157cb6e2dfa2733d4c489ec0334f0303ff1ad410f329cb59f99a5fa3ed2cf84eb7d2f231078ba5db0954badb58425f0000000000000000000000000000000003dfee394f4c140e2cad61e8675b26f91244880d9a0b6798d6111090dc9d080563db5c89b7293dcaadc74ea5849a08aa0000000000000000000000000000000001aa1e0683014d5b6f99f469a0b7beefaf05a7ac0298bd1a3e2da409f6cf856f70bc067610fd705a851cd70054df9562000000000000000000000000000000001571b129f69f3a6717272ff75351fa053f46294f68ba3f859208d6c91ba5eb9a0f2133a5e139d04e38c7f7aa303451768f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be4000000000000000000000000000000000c4e84b7c8e46daea67c8090b27dc28b7867b89b92f56232bfd8ecd9968b865a057957292e79c6dc08162f9e91e6a4b2000000000000000000000000000000000b8d1eadcf3f1de6ee608a4a0ebb7defeeaf4e251bf07717a6a8e50c07223ca32a2ef290f26d0de14b1942e02acba39a000000000000000000000000000000000e901b546a4d3c68e4432f376c97f42ecf0724777956c4ffb1e6ca4fda562e57be788ecfa45ba3afadb439c2ea546ff30000000000000000000000000000000007ffe01da4fbda9fe5d47c3bedb4b92fdd71ad73fa272b071a7a7d1cdce7743a535da7dfe05a43d03368eb97fff54b2d861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000008da0fe413e31ca68f84032f23bdd5399e01eb3b5ae47033c6834a39645d7b5cc2ec937067b91ac6d83035a86fa841f9000000000000000000000000000000000b950b982323f747782d9065dddca5332940058a604829e31560a6bf9b03ec72b09cfb87a1cd244ec694c7cf192c37ac000000000000000000000000000000000f4afddd25eac15d2248c71d76c9aa27323f75141820efeef1ab4f5003141053f138d9a7d1a901961d0f2c210ade27ed000000000000000000000000000000000217b1800c53d53459b00b8e463df1882b2cbafe85043f08093a5414e58ea7fd4dd933c601acfd7c154d0e4ce187468a2dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be55000000000000000000000000000000000046aed1acd19201553bb6a88fd6a6c0525ed44822d2a4ed3bca48a0a2b75e76cfcdced8f342b81ce03ffa72e667b3bf0000000000000000000000000000000009a5adbac43cca3402db016a2138342fae89285ab1fa16d7acaa9c3ee2b4e3df2641f7392355996bef7b1578ce1ef119000000000000000000000000000000000c8ebbcbdf2ac3fbb553a2e589f4b7c259a1621b83b14fd1927f92d9f6cb27e82507d7943ff5930f0c14b9fc38c9857900000000000000000000000000000000105b729f678db31d04ceae0aa37f9cb0b0319c4da9a1a4702a11bfe3a5f2f1f2af09b9cbd5ded5a930e2e65f4279a31699893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23", + "Expected": "000000000000000000000000000000000708e9b926f2536731b02b6b75305c549da58e312d9c53701a993624697af2f3469af34dd4634467f8c98a0f721cd9c00000000000000000000000000000000019185b84fc0511a048e3c39bc10334c91dc1052d323a31c8bf325479a2fa3e4228f8260c0e725c2b89d5a0319e6fbed70000000000000000000000000000000013c7c441d5cca81b48d43e908d6a3bf8b5057cf19e4884227cefa9b235103b46edbe01bada06bb9b620ebbd016d537630000000000000000000000000000000000431182c8a1eed66073956fe5798a894be396403c072e766cdc262b719d1779f960f4aebf61c1bcd4d005d3c7413e52", + "Name": "matter_g2_multiexp_75", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000199f555aa5c651183f52470e36cde438422f41c9b2d1947510665254b74ba0bb9cdc6e6a1283b0c8f58d8f009eec46900000000000000000000000000000000018f1d8f22f43b4649300aa23ac92a2e8f17e7e3853b912bbc8e90588125c371084cb224c2d54dcecb4946ff6db53cd02000000000000000000000000000000000efed0bcc83a52f0faf9e260815da8d4e5286396081268485aab052a96af8eea0112be6cce1486b10b60551ad6c810780000000000000000000000000000000013a3b1ca3b9b7d50083c10d36997f5f521d4426af8d2905aa5d074ff37e218a0c96c74387485c2dae24c0842b7a74cf0d8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000efc5a5c506e94ad2754e235e2da866d9c46342f14d518f12510c93f13a619f6bfefec50c146d6d6170f190497eff229000000000000000000000000000000000fb91f34356005f38c9804250549554cfe67ce195d5e218e4e1b1a4fb904257bdb68d6dfb013e8e85fb5a4cbdbf0f21a000000000000000000000000000000000f09903db4c41fe3f11c6f0cdb7c31a131033e30f52cb66ba10c2e7da1ed8a225ef280d313630121701f9a490e8a0f5c0000000000000000000000000000000003484f7e8f7d67ce40b4cccef110bc255d91f61a4e1968a9ad37e25058eeaf39e9f1ff89c9b2e515388a7c1b49a84a2c33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f000000000000000000000000000000000444215c3d4a7d62201ea1b69890e2ab90b5f5c6ff56fdc9908634c7489e785521b8dcd7ed409cf09c585cae8414a3250000000000000000000000000000000002d70674251a0c9ba76b8bf3b70547da77cde5592da9204954abd6d8aec82799cc0fa4fcd42139357043fc867b3d0e0d0000000000000000000000000000000018c57fafbad2351a3da695f8b523443e8c763dd7ab875caaa6a494a498cc40b1c0d44488e2dc80d1f0bce00a2c90c67000000000000000000000000000000000125d5a87ee3f558b5e1e7664b0cb95c195bcebd5e43b930fb47d15eee4fd50b3fdd0a401c9bb011c326acc77645440137894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde480000000000000000000000000000000018790123ce8b3b72d626493a16936c47770a9b06ca45b17c6fa5c7759f088cf98de8ce7b3b5d6082e9e42b39acf76f79000000000000000000000000000000000fea86cad8b40f315d8378550f6d3d831149339a8e8dafa77295859ddd2417e8f5c0ae2baad25fcfe00de14f45a537170000000000000000000000000000000014ad78bb2bce966d52b1fe1a273bc07f2f24b354465edef6dbb1e0123c7c3d7550983b3793ff1c7db846e88eddbf33c4000000000000000000000000000000000c0daa6fba40ec59f6b34d413130df5d9137297d1b7b71b83114a6570fef8e7f83d6f5689527164782f92da4b1ea12e8fb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000001186b7c78952e5c32a9393eab07ad4532471595bc2c5d8137c61dd7fe6b6ca3aaba82dc205a559bdc15421a001b7270d0000000000000000000000000000000012d56b6fcec3d6511d2d723601cb8c9faabdcdd12efdd0e2bfd7c9292f2c3bd7f39c6e9aa53e6955727f88ad69c5b4f10000000000000000000000000000000006a5e56e4a42b04c03619c78232104f1f1f39e755058a19354eb230f2f09bf486b2586817aa6b88f27b884957ea0226600000000000000000000000000000000118c8521dd4866df907ecb252d9ce7a489f17d0f240d054a5dbff6c35895ef20b205236aa6e5be6f0825f9df87878ab783c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000092809d18926c20456857826491f55cec17803e9e7d43f22faf4da18ede3bda15e3319539017ab20ed1de2bff490a33f0000000000000000000000000000000018d736b967eca64234f4e0018e5d6c902608e265037d9b8ba42dcc923b84ac62599e153e1c7d00e552ecc5aac57d1a5d000000000000000000000000000000001804aee99219354d4a5c46328f0658a417c85c6bc89af6db29a4911c4b0cad5638fac5ca61cc997fef3450cfb4a6c666000000000000000000000000000000000bf99dc4a400adda5bc89762e9011dae8ada23b284e52e2d49f75f1c75247f6282c95a36f7a72f896ea308131215404bd2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b1000000000000000000000000000000000604f8bde85c0b26894e0de155cf896c911bca47533362a0b59ccdad0dd64108d33af8262d3ca2ca399306723f2482a8000000000000000000000000000000000ec10d3777aa54cd0cfd84b4062092ca3ac840a24e8e8aaad5f4c275e4d45091f838ae522efb1b2a0fa42229157297d300000000000000000000000000000000132cc70638d02186116773b31ec0e571a55c1cd78ec055fc647ab09cf4d3c543e0552d559b3daa4e99cef031e583e61500000000000000000000000000000000194a6a32a269692906b64feef9e4e8cd204e560b98db8c66380758d2123babae871273b4c571a1570a317c13a51d0fe9120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f0000000000000000000000000000000016d05912dfff44912bf34f242ac85eb55bbb8a21625d45496c76d057f518352528c6632d6e8adbbccdd5983d13c26953000000000000000000000000000000000b10aa1402c15fd601ce605ade8f25531ea8f95cf592bf4ed86c4a3aa847dc8aa2369655ce5348da30a897fa8d71ffd800000000000000000000000000000000183f5a2f40da0a0f4598c6b9ea7b99f8cda1d85cec0e6da5365d7eaad1e9a3167bd647e5e654985f395ea72257f61e5d0000000000000000000000000000000014e615e2d5072c1b536ffa607f3a826ce297800b0da329fff397b6327800ecdc879e91f1e3ebc26c18e188e1ca66bfd66af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a000000000000000000000000000000000f3cd275d72a637bcce855e2e20727c6e5a1f15bc8d799231d3a7f61311d4cd2f58cf38448675aee9910c1a3d0b576210000000000000000000000000000000019efca445312f568727948c803d06b8d4e2c5289015740f2626fedbc0047d344aead06ef521ff7e139312fa41d1c107200000000000000000000000000000000141384e1c9f79e38bbb0bc1025c079741b93f56e150df58cf9a61ec27c2877c4188866fa197242965e3feb47a78c68380000000000000000000000000000000010638286faa6c45cf028e8e3d200edcb348560e2e35902927391401b3155240b62a40784db88e02b874e128e3a2132b5f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b000000000000000000000000000000000c7fc4216767ed298206bc142862c138d78726e2d39afa18fe5732616c73a965d95cd2032d4b2f5a4d562be48ba6885a000000000000000000000000000000000928bbbd76b87f58ecc850e1aa4a2be11b15a81786aa7ca8cf0f6cc342db87b66c435f009f88ad97b747400fbcc651e10000000000000000000000000000000019f5ae9f06f2bc27a39bafacc7f3745fcdf8c78c9ae8a3c066ffd704aa4117eba773691ae43387b93e86d2e2de3688700000000000000000000000000000000014360a7ed73c05ef5fe651321f7e839c920bbc1896636143b88357cbf76e15da839bc7e1f1e629768d447c9d313cec8e23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f50000000000000000000000000000000000b9d955f9d28f9485d0bc4a961f0acbf09ee5fef38ccd81a2c73cf87a461ff1bf28d4dd1e0db3ea522299af67bff93b000000000000000000000000000000000889061e71866001b0760f68e20c7c0c033d782e6e6752f11502a0e8b6b70277a985dd13dd83424d1e5cdb9eb96a01c0000000000000000000000000000000000e05a26686667f44de2bef53c36c82f1fdda13dd3f7f8fe1fb026273dc4dfad18241d732ccb757e2b46ed8317dc69fad00000000000000000000000000000000038b55685b02231905dd9a62a709c0f015cf5650b3fa469462b3e9d06e3af8092d998c8e08ee61db1fd5583b0809a38996c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f0000000000000000000000000000000006649a8eabb25fb7793344a0b29325a88294343f6c69612ee9d9002154a49791f6cd7b37b2bec69fa8ce11722e9f8a03000000000000000000000000000000000e10f2f3de16fce9b9817085f0130e1839d9aae949170ec16834732a9b12f589a2b00f17d2fd3416ddd020b7421ca20500000000000000000000000000000000016b51112b3c7c42a8c2a0fa7f286ec05cd07b6cea5675bf1132de99cf42b450b3c2a8f02ec821529a14a2a0fac3a751000000000000000000000000000000000f471ec8b65bde22e003500d1d422dd0d163abb424dd261fac588333755cc5124acde328085d8df852c61e024155564781c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c000000000000000000000000000000000ec162d22b6516c309efb6a4577c5631a5807bebddc5fd1be5446e4a64785d49eed80eba2e89cfefe484ecb8d50440a600000000000000000000000000000000070c252caf6c56018af6b281b829a4fb8dbab850ba0446d233dcd4d87bebac00e3e5070bd41898dd561526498b153199000000000000000000000000000000000a0d76d1205c1f520d82c85bac4473ea7cf5f68022d95b1f04d06062197973001234d86921e70a94e478eea85264f14a0000000000000000000000000000000014c6a07f0d568f2103ccf8f61278e916458820bcb61fd91479b0dee874fe36c063a34bcb14ee434b68681d297637b5bfe44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d3000000000000000000000000000000000aabac129385d145243c3a1f357ccc963ff14867ad039827488128ac639dc62fba82ace66f889b47d8eac39802bc1af900000000000000000000000000000000062bbbe8c72cd6f8626484bac159b7e28c6c8c3261edc6a05a30c308cc9e56db17eb58f62ab755f04a5c87e58c04c7550000000000000000000000000000000011a4a439d18501142350229778f67bbe0c9b948229dcecf70a8b09d1df6c54801a111c603301da2377d4198d09dd51e70000000000000000000000000000000017de3d9bc6fc5f415d04ecec013a635fa200699c496f4d0bdb5cea7d446274dddd0a7f6b06058fde43fc4f1457361558ae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d98000000000000000000000000000000000c86590a02fb5c9568af4e69611f09980cb5a7e040c94ecdbe64e40005783fd3305a5657a5c6bebca7d20ee123a872b4000000000000000000000000000000000bc873a9bc694171d2606f4efa409897e03198a61b1bb16ae90f0d12345d2650d93c46e0c22b717e2f0504b8983515990000000000000000000000000000000001df9160ac3bc54c0121a9c69e9065f4266202f755c961bcb8641d13720b82ebd73eb3804ba44769fb2d75144442f1c400000000000000000000000000000000045e9c8ed2fe1e5c9a2a5bda75dd60f6bb5dcd0a805f68c1f662a5960b025ff29c8e21857d2a61bcd65c747d2a2da8ef6a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e000000000000000000000000000000000f94df8d267339bb4f51b21014ca6d685f7657d0f0bca189e53cf19e0e5e05bfad773c0553daafd80c86f302b1907ba5000000000000000000000000000000000d92905addc028a1dfdad50e909c77662e10e4689e7c8a4a0174a3e1c746b361665b65e17fce02b6c067a5b8d7a6a6f500000000000000000000000000000000183444f0665790c48bd3c07545115a11f82463a092774234e7b33aac1094761f213235895e5e61ac1b0a15603bffe2140000000000000000000000000000000003cc2cbbf181fb023a5f6088d8a9793b17984b3dddc8c3ef1a9f82f8f436002610df60b2d35be212da9945bc8108c0bced3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5", + "Expected": "0000000000000000000000000000000011f85691799cb76213068ef4f997af66c349bf707295b969d85fe637d4eabf54f3f29e739152aba5027c1b55317a27210000000000000000000000000000000019627f9570f07f44f326b5b3ee19bc477e92d813be2865e00da93135645e02e6fe5507ac4d50085b02149667794609fd0000000000000000000000000000000018fdc97bf0f88b2348b436d70ac4e28b5ee5ba21e21e94808b8b9e401c0c7d688974fe203ebda0b23abe38018876f4930000000000000000000000000000000019e28c9c936ea5a0b3b41871c3afaaabd53a93902e44a96dcb7651bce7e6143d81cb695fea8b94aa32c09ec030dd9ac4", + "Name": "matter_g2_multiexp_76", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000703481cf48efe78fe8dad34184edd1765a1d01846de74a45b43d4721bf1af116c229f969868b0e6e851f22bdfb0451300000000000000000000000000000000063d316d495b1e82380c5b73bd61ce7f2159e7714c50e374e8a91dd56731dbe03a3378bf8afeccaba5fda73b4c2dd166000000000000000000000000000000001012cb2f6578065c93aeb673f447ce95fb42927ef9d12e07968ec04b6a604d785944620043dee5de4de33d59e67d64f20000000000000000000000000000000018cc7cfc360801ecc420d77ee171fb3eac3be0cf26b3f36a6cfb7c6adae7bb74c18071daed8fc56b8fa639ea138267928c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd10000000000000000000000000000000011e0dfc437a65c6fe37bb9e554b5138f68a3c52816807bdf7d98f13cfaf86b37e9669f4e0db1b7865d910a309f16cc200000000000000000000000000000000006f2323e01591a7db1d3c7fa1a2ce4540cbe0396cc55baa3a3e13650a6f6b926a7cde0eebb45d359edd52137152fe360000000000000000000000000000000000066bfec8df4ab5f5f5eb369b34e8e22fe32abfc00ac58b68f2d3841248fe5843d6d29ad012249fb9ee851e40b940dc2000000000000000000000000000000000f4ea977d9249bc05dafb682a863ed17f7fba0a06c4a13cdf5a836748664183272eed96bc4109bc5beff61c5469e221f8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000fa3e3e77112774fd6d6b560ff88cc92ef8d009675d0ed65705398ce727cfe786684da50bcdfaffae97d19bdaddd81c00000000000000000000000000000000019e98284b8b9f53faf3b73902cc322dd80fc330dcaff2a7fceb55db6a4b0f7f667297f5e4650c797ee337985dc6b54310000000000000000000000000000000004e30acf2ba66d842575c8679caec607fd090f0aa2350464f3b6eef22e2b9a1d9d5fabb0f3909f1c19f6b8f27c53b040000000000000000000000000000000000ad76b86e32f84ad74bac68909da0c271571606e071b13bd92e387a8a16a1c4002c5a5e94ecaa1e8d2d6e051e19a45c763016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc100000000000000000000000000000000019bd07479b234bba974ca2f39b317d5f4be33afef66c1d69e53c44cb5e44c679775ba141f82486424110d186561777f00000000000000000000000000000000130002de0d453abe9052a5f70a9d55de74939d1c8e6ad5871a669a867861b1359322eb98539f4a21597d806aeca62d18000000000000000000000000000000000b2f0c649fdb37216c10762f510c3bb4c789dbd29c4f9a8ff39f74ed1a96609c60473a50f5ce3f6535e4af0f2f0a150c000000000000000000000000000000000893b9af710787361a32fbd19c380161c9a214a1bcf3761563424b8546f6068ba650d9caff3e42be63ebf4b6afa2de516c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000019805c0de5e232632228e2772dc79712e3d863bd6fe56932b29ee99870d2ce5eaf90c73632d1dcddc093e9b6b5b0f1d000000000000000000000000000000000405d77f4b3c44f99a956ef375879e62df033aa408127e0fee013b74675a8c7d999c6abd30f459693086bfdb326d67af00000000000000000000000000000000110f2c231998aca3d76e40055a05feb37eba76cdd10106719f2300f57906424d7eb6d9f85115b78b7371ee60e26d02b5000000000000000000000000000000000593a4721a67caa7cbbe1566611a1d48532c68adcdbb67f362c9ec21e08aaddf6b5e09a9a96df9a89bc25f11665f3a36aaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d7100000000000000000000000000000000151758f1921743d116f1c4adfc09cb68b3ff911329e2f6d6bcd04beb9c109568c796f328e1f04381a995fe89aebbc49c000000000000000000000000000000001388c73b1db46bdbe70540c99db46b730e157a23afea97648d73f9d5f7e8b073ed665eed9e9e2500152c87715f1c4d4c000000000000000000000000000000000284ad228867ed14ade5a327ed951ca50c87f0a669e59b7a75d17feb54bc5d685245448a912590179db1e84f1eed1e5b0000000000000000000000000000000017d3da7c167733dd88f1c39315e47cc80c3310cc431989d4cc50ddb22e9fa481c5dc02d94dbf806c4c8da16ba5b24905f6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd490000000000000000000000000000000016064871cb68f748939a839800afbb018fd5836914a2b76c51818e764628a76817c7ea329e6b2f9de653c8162a2a2e0c00000000000000000000000000000000082fa03cda4c617a780caaecd7c859c5251b56b61f70fb3ea8c05b4c11c030adb8a96d715c1325ef3dce9b20e8065b6700000000000000000000000000000000174a245baedb7e1bf1368212620b850151be41ebb00c977d85da499223c207ab6f1a1d94a51aa9e90d07764ec3615b3a000000000000000000000000000000000df5b81cf4b008480775ff3d7644f546a60382e92a98b03deaa4a20f831e69e14a893ffa731c4ae9ee237d747149a9080cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c64700000000000000000000000000000000019d36b8dae5e1083e687743f7494b7f9dd0923024df81e2f83c78743e227ffce588a16630201b9909daa6c9207b5f430000000000000000000000000000000015659059cfee7850e1cf0e49abeef2fe5837cd128742e62de20dc734f1bba343aee1c9f1a59d920a0519995561891fdc00000000000000000000000000000000102b7221257c40d9adabd0db3ec9f6348487187ea1110773fcb2ac5ce210dfed167a4d15e605e9d9e666fd092147a1c7000000000000000000000000000000001402ff9770d27d2d82efa6abe4a181e3c1d944e97a06f670d9e46b24f9900fb4a838b32e17482f25be9b6f3240870c02ea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000001745ea52686f87a39fa42ddb5b0f69368db3757394fa7a1a93eb20c398c26415c8a7edeec7334df5b15345d6174126b0000000000000000000000000000000012b580e6fd228f087c7584cd95826e56d1c074cf16c35286c45d2067a362529d241c1e24fd22cc9727d423551de1a1f700000000000000000000000000000000104b46c42a706c61610f8c0434894c7cb9ef878cd0234f8aec0825cbb8297bed3de349e7f6037dd19a159103ca7753390000000000000000000000000000000010b781b3cbe6f415af15e37be7c60dc6703e6e79618cb3d8d9a5ea3b17c00822aef1eddacad66a646c009dac887bb070caf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea30000000000000000000000000000000004b172c360fca555e65860c7a294960f506b562e012ddebad5803bc3f4b93159c16cedb73f339def9cd1beaa0912c93c000000000000000000000000000000000242e37775a042ccf59e99da667c67fc49e80e54a1b438a74fe306d668059ab4dc7d9e457adb45e1f91b3e6bef0a130f00000000000000000000000000000000186eb83ce3abe66b8760dcc0d375eb783d175b0b2f36cc08793d8a86cf76b7618b826f50c6b02ed586394abe4efec2f1000000000000000000000000000000000bf780324df1cc5de325a796f1fde367eb52dac76c0632915dfcaf01f5acd6ae890dbfc2e505bafeba7fed8fd63018c2ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc000000000000000000000000000000001231b52c8a081add6e5c250caeb9467335933c2ed66826e4ab44561eda9259acf926f22ad0df8e8756aa51279d12bc9600000000000000000000000000000000051c46bb04d3e035d324de681c772e4561cecc6a5bc4ef0a0cea56618e09b3f39f5085e208229e50164bcdcd4abdefd2000000000000000000000000000000000ad7ee610398935a02c3a7139185409d7fd4681ebb74a239e15d1c092ea913016d3f585d8224cb1d109ac111660a94aa000000000000000000000000000000000903bb16efb052b99e9c46f3478b4acf800a173b35b0079d7728fc25c9415c8b05ad520f31e6a3c867245f64355cbc080d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001788efb21597aaac29b7bcb9ad6cecb89267c757cfcd8893c32fb13c0f3e1af7fcccb9573dcffe8d9220292b7861cac90000000000000000000000000000000015f85d3686148ad62d7fecb71920981117cb8759ab249d0ceb45f9e4687914536a1eb16ccd0e185d1352a8d2b4a8ee7a0000000000000000000000000000000015d8ed94c0415ee0f7c9854841bac5821253bb2ed4d86a61f494cbfbd61614983e4279fb17802ca68aba4a0302ec1d8a000000000000000000000000000000000f950a4c8aa18f4605e1252c367dba1e170ad00376a8560c2fccfa7d5487b0d1d5885cec16a0a17d81b5a584d473853f7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b82000000000000000000000000000000000da25ed9154121205ab6843f603a38a6892887d2725f16ff87a5218586c6139188f46da5a42b5e05982468e8115713ce0000000000000000000000000000000013c13ffbed4a60bcb8659013b022012ef3a4400f506d65aff7ffb1bd5a9a5e030a298e417cc1ec8ee7ebc06455dbe61b00000000000000000000000000000000132d83bd141c434326d4772de7f8772c30a6456de7adee7de66a04bece4c0d20bae5526c8eca5af5ef2eebd72c90d54d00000000000000000000000000000000131355c5e359081dc86e0b15c8aedb4f2016b41e8428051f5132258eaf4392fdb63a91452dc56aca20b7ad3263ebc8c92a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab7190000000000000000000000000000000012dbe1327162e4176b4988cec23df0c1b0075d0dc51ea8afbbf98f00891511d9023cf7538c5705d59b6d6ddcc90b101d00000000000000000000000000000000036c12c7f7627b6d6fcba9a303248c38d784a3d1d0ff02e550565efbab68c5116e9a88faaaf09bc72bcc3358e9dad0ee000000000000000000000000000000001578ffb68cf12dc9a5ae6fb5d822324cec9e3f576ce08d45e24fec9203d36a6461c5b8ea6ac50233e8893b07ea6e71e00000000000000000000000000000000015cdb43c82b20b8ab270b942b9e625ada9283962a7ce95eae156aa4355e1123ff87ddb1cc85b2a94bf36102ccbec33fb2bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da9600000000000000000000000000000000017041e16975850e6445c7b4896955eb5eab383ad3c3031aef04e8fdfb65a6d52c9e647330bfbb0f0eab630c9f9ef7a12000000000000000000000000000000000b62757ccfb913ac4264692053f766e142697f598a3fe26e998119b63a3abc7fee03db32a8af36aa21181fe9ea89d12c0000000000000000000000000000000006bbb842a889d7ff3c1eb5e0b16e3a921a11d28a251c488a8a17a29edd93672fd15974a7e972a34c47283c583cf2d29b000000000000000000000000000000000e94e685fb1751f8720b8af79aec7b245ae8daa195f11f485f2c0c5dd68cf39eef848a402ce2342a6b3398cc7879c6010f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed00000000000000000000000000000000100d3fee47ae6c8c7981c8cc615870924fbcb34c2ed817d6862e2e6d0b4612222a4c8332c7d51b58ec59df6832139e1d0000000000000000000000000000000017270fa71c34ec84043ef64c5dfd61614b5b3bd99204f9f70994d71498219818a5f16843c67c668b06aa5ad3a6ba8a0a00000000000000000000000000000000057948c0ebd14664bf33fb282e200fa0e641764a353e8347586465dab0c79ca2caffbdc2c6d60b2d7c8cb6b088bd16fc0000000000000000000000000000000012747eb070f2de18f517648395109bc08b4af3f04d98e23eb6b516199b4eefc5df7d57baec736987139c7b03b573941f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e", + "Expected": "00000000000000000000000000000000128c6c0283ea35c10330502d6aa849a909df6b8dd927a24f08072135b8e21e40c495c42e742160363772569189d73ef40000000000000000000000000000000016d78dba1e0feeab46f8cd38681a9c2f6490ecc3a6e79b31caead256611d133090a4eaed9691a87b66dd1c2ee50d5f470000000000000000000000000000000016de93e176c950975abcbc692384996315a98065db6d6a6214472e5a338e291b36abbcdea1b8be6162fe578acd669abf000000000000000000000000000000000d7155e239e9b15ab64a538b0a0bd53936df4ebdc3ec9b0b1d494e4df780bd014425759e9743c9b262cf48cda01e945a", + "Name": "matter_g2_multiexp_77", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a653e0c24eee1cdf8e3652809de0cd159f2c541981a4f43936e7d41c0f97ffe2f1e1e0d1032f0970023f1d27241a16a0000000000000000000000000000000012d1d8d2f96db0e5f97be096c961e3b90ef3d88492fb756894979d2e8104791a5b9a43888043ce9e543691f15d2fdb650000000000000000000000000000000006ffb94dc3c2d07830498260ebe4641b2cb64df61cebfffaf2d4ab5b6ba92cd75de209e8d7915ee744c4db5352ff239d0000000000000000000000000000000011f25722cf9db77ef8adb9caa250175e12412e6350b494395a86c31e1f5dee6c89cc6603f1dfd08a70344cdc44aa0c2df3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d80000000000000000000000000000000006177a74e3551770e7d906222590108bae7b97a5dd3bdd2344fc12e7005f2c1a188ab9dffe68f5ffb0cc36294106f15800000000000000000000000000000000041b140c46868767119a6ebb58562570732198854c92bcc070f2a8d9be91282a70c5ab99e75cc9e5064ed628aa5c59de000000000000000000000000000000000f318ee33fccf455e46add44922bb6e99afd4354bbc79d7550f8d12d3de4f75e5ddf4e62624b116f91aaa80a148adaf9000000000000000000000000000000000fe012bf88e152eb62c0c906dccba469abe591687573a59d3debe747b7d895e4b0755f16e67fa9193a2fd338c04d243a4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000b26c6e0106d4efbacf2dd0d15df17209b1306f388f493c096429c031bc4a6a535b64cb02b400433f948fd6004df2fa200000000000000000000000000000000061853cf1a32fdf4c370cd413754ea584d3722a08d58575075a7371e57a7bdef95386ed72f91c4893377f6b551dd6b1d000000000000000000000000000000000ebf17e60718c8563a1029ba035dbbba75e7191b4339d5d33f64bb35f34866081f26f4815e01b02e8330e7b7e9c428cd0000000000000000000000000000000008ce40f92efb5c5be48c814018fbbe45f1be45f5b607a6600cecd50d8f791de7d91939ab61204c2a1337c3f21b2c9d26518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af0700000000000000000000000000000000123ef52cc44f36326b33234ab3348893bc722bac3674e43385b201f372fe4ea3569d69d4d561e26f8ea903e017d7376a0000000000000000000000000000000005b1707ef61ff9acb9e8b4dd6922daaaa2d8a7558cb55b1b9b96eb6d57c23f50a7955763c9b5ef04f52b09be8d55f4b50000000000000000000000000000000015b6e35d14da61e7a7fcbcb0dddaf0071d8d2d89f7179f44851947a2b9b0535d6fa86b5cae9713a73bbed909a4c6deaa0000000000000000000000000000000013463e135b1fd460cf042dcd0226e229d60cc2beccd8a1832df241e65a644159722a14297c0033eb499e5890f0caff1e5561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c386551000000000000000000000000000000000fbdf4a533d355e232723fbc97352fc5d7d3d199934883a61a9ea116830bdf9e40d423256225d9a3458134332ef6e817000000000000000000000000000000001195f0ad227941c5e383c48f546be34762d158e6cee585650b6ee987f7b98e802f678abac6646832b30b6e12e90948cb000000000000000000000000000000001820d5fbb5a62140c6e8cd105a70fc2f1ed84e254c839deadae5eadbb75e1c33a07ad12ee92900f55478e91958a3147a0000000000000000000000000000000013849bdcae33fad27f16e91c6d46b9678a00491e3d70a8db905db4b1d2c6f02a29392b5b77c1472052d6f4d49f14a16737c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e2600000000000000000000000000000000188fe1e394b567d71099fa13b5c8a5891636d83b6b8a08f410b080658a0663deaae4dca1afe8b9023b5e8e573c752c92000000000000000000000000000000000f66c65dab8e1b2912fd5285a4c87821888532f5107075cdfedacc4d7f75c6a74b4828d0b4c3a2c0ed94576654a7047d0000000000000000000000000000000016af44a6df79c8c9b6f1d8aeca24e024c454d7b94c9ed386858dd35c4158cddcad1207f9fc3ac9e3b748c2314f875dac000000000000000000000000000000000315e5e4f78e9fcb93aac78025e95b8bf82ce4c840cf565e0a868b0aac22950d62f7becbf8039a16ca3ea66a7498327d981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de4000000000000000000000000000000000f9a61dd1b3034b8cd7408b0a44c8d02f4fe0e87778d5d34f5e884ccc9e2d51eca6b6060b46b66843e8247b3c794e19d0000000000000000000000000000000005c47fa7799a0fffcafbbe4694dfe8d0f47b60f712d6319e9a56ac459a636460e700e2af80f9c688208978aec7c413af000000000000000000000000000000000ab1c55fe2207865ecf12e372a341c776d24c08dba10702fce1cd2c01eda314852d81d0ccf1c3423c2a12e8960677f060000000000000000000000000000000014f8a1964aa3240d788ea40bb51abc50fae2736a34120ca9585fb2d5bba4e5cfa201c83be1e00ecd1c46fcb2ebb4eb809913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d30000000000000000000000000000000006441fcaf5e68b10e7e511a95e56b9613453ec6468bb126c5eb12f204c9681c69b5c296320f92a6fbb0b848f8ab5fcd1000000000000000000000000000000000141de16aeca0a2f991e9fca4b6ce8fbab3d66ee3ee4dffb0124384a7d4ba51864a53e005fd34516c92ecab33165944a0000000000000000000000000000000008543656b5495bdb726109cd98fa18e405648fa88cbe2e5fea5380b7d0ecb207f0343dc7888b9945e55156977336226b000000000000000000000000000000000b53d4e392f304225b1ef363a3528daca1d3a6ad64ee99d58491863ea432a29cde5edd4f390de45a567cf32112ca5929188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe0000000000000000000000000000000002e0c55a43078df575efb2c99b27c5632dd1c08bf28b6c0558081a78de58e4258d1b57d94ec6fa157add04aee06e7b6e0000000000000000000000000000000006d3f4f0791431a56fb386f4bb8e6744cd19b10bd0f2e65e927371ab488d3735e3b83400ddb25ef9d740a8620821b0ab0000000000000000000000000000000011e9cdfec8a8f8eba0de6809485911711149ca0ebd0cecc033e2e5ddfc195fa7de671a686edd2f56e5f7da7328dfbec000000000000000000000000000000000171f188afd5d9568cc5648aefb65cd715c0293344b9aceac1031f10b4a1e4b9fa2ab11114bd58f28aaa58c10ee0eeac65525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a000000000000000000000000000000001651d9bddf61e5e54f86609c2479513ae84b000ad7defd840d9619a8361922dde81c999d0e95d8a3044c46fe0360c2030000000000000000000000000000000014a68c248808e826a3bb50f3c1c1438483cbb9da8dd67a0c9633a47f733e6aa7deb4a13aaebcd50de6e8e8f00000424a0000000000000000000000000000000010c8a94b9e0ec9965f6c8bd0c4279102ab682a14fc3c22e9640d68f240ccecfead9a2c6e69f7c8ed369cce7e2da50d5000000000000000000000000000000000181493e8137fcfae203e1b45189fb828dc9eb56887c89aaf9aad0380fffada423f0ab48ed068ba4e67a2b01a16abbfe55ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc57000000000000000000000000000000000e3e33fa4d85a35e8707419ca6d4fb6a61ee6b07ce152adfbaf6b5f1d7ccc253b59f91e4545848b3570bfaa804ad9767000000000000000000000000000000000c923a4de074dce3ccc94698bf6445af5847c0e6f22f225c589f744ec83ed0810913af2a6d04bd55200ffc738b31b01200000000000000000000000000000000186961ed1c6039476eb6f13bf1b5f6627b3b017ece57a4a5f33db8ef12347fd507398a421932d3d2a1d009f65d06e42c0000000000000000000000000000000011e10ae0139f95a2f1144810894fb98f6e5e86ce67877b949a2a7134c446dfe53c23dfbfd12919b24975f26eafa249216ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb942800000000000000000000000000000000029265ecf3c81aab289c98d9cdb917749ceef56e2e4d59de2d6c83907f394ddd1cce9d093a20206c2c1c215493c41c49000000000000000000000000000000000986ad139381e4dbabd6beba179600e1c782f436f84a7bd58cdd96a22269f1d937f88f25059214fe2a781ac519aa621d0000000000000000000000000000000019e296d5b17f78b3ffbdaa2ef5228fa9dd65abdf6b2c5b0f99a708c4721797b3b156b8df98a5a879f17f095548555da7000000000000000000000000000000000349677d4719445d5525cd65e2338463d232eb75721ca51c48fe52d0fbd299ddbd6cbc12546f056bf212d5700c3c4100854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000016f5d5eb3fc3ff178843a7d21d3dd628bda120321ae44206d88f07ac001651428e0da95d3f0676e1bbb969a300406ce000000000000000000000000000000000029121c539ef1d7b9888497a362fda2f8402adf10a1bee11b53cf3dfcc6f99d5026bc386f86a2eecd0c276494878104f000000000000000000000000000000001320a402922f2a0bb287464854be6782046dd9dae4c0cd94efcb8ad8e0f37b7889bc97a3c8b4d3b3670a6924c8ee23ec00000000000000000000000000000000101fa8bb2c90b755bfba9cd7a98790b7bea2ede4c806fbd9f2006d10cf87c44172d4ba46ea40fb75afbbaa2abc3b6e9d7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd0000000000000000000000000000000003285cb099b04b6acd333c7ac76c839b6c09388792d5fa1f2af0821e49dfbf40a06803c4cca92512bb76d073129a48a00000000000000000000000000000000005b2fdbb25381b3b67814bf6cc0a4cc17271416d16ee369b723b1711d968c355b755183f0bce519709723250515ba32a0000000000000000000000000000000002c7062ba4f642b95e028a364b0698b801f48af3c336fa09d13d83ec6cff10d210b55b23cad1d999889c83df7d1ab7e10000000000000000000000000000000012cdfdc10bf46097083294259754453e084010f7ee928cf540d44c80aa4f601247223a318700bc24114e7603922d15ae923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f54760000000000000000000000000000000000669d760352e34a407aef8e141fcaa9468257b12ec08ec218f49f0769f3acd5068c6dc9d251a1b2af02a2d091f8ad0000000000000000000000000000000000064a7b4026ee3115cb730e56c4b9bf3e1527dd0f0ac6015f43d30a2f3d8d8c2659cf50247e70ca3c93d7e0a404d9faaa000000000000000000000000000000000979ca2e81663ed61486c1f841c19d83549388d798da72feda82283406d4964bc9991f876a6032382c35b605441ee7da0000000000000000000000000000000008d92cf77b44c516c243f3e6a8a8d3f9d3d7405820ab972338f700de1dd9a66d33b4a70540a30f630aa81fe1cb5bf057e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf900000000000000000000000000000000198831a40fec54a210a63f5e00b132bb1eca6408335b85a75e28be6a111beea3b99d9f2fe5091ab0eba0f082c201c14d000000000000000000000000000000000fe457f8d215f390000efbb7fe7193ba02a2ef78e9bff6539995f01604fdca9fa3c010276afb90215890f5a5df3ae21500000000000000000000000000000000076771823180422495d89c301443a9d1fa141716e5e27205b8cb6b461a3ded7e6f196c3976cd6ad56b2e6ebb6b3a70860000000000000000000000000000000007f666efc677f6f767828e1291bde0ba0ca445ddb2d69d5d2fa090ca49e697ce4e00f55d2b706454be6d68f012d76efbb6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa", + "Expected": "000000000000000000000000000000000e1268a5e2f654c4038647a910e6cb4bab1d6ca3562ad4a9ac18444c8b8a3fdfbd35acf37f9203041fd587a5175ce86d0000000000000000000000000000000005e701a8ddd15ecb0231b7625f7868c81467c140b2617e60440a9b348a192e5248b1b3c087545cfb6d173fafe48d32f600000000000000000000000000000000071327f52b1325bb664d32c513fb12afb96dd8da23dd91bc4c3e8ae5f97d6bf673a5d03bb8bdeb6da3144dedac200dbd000000000000000000000000000000001852b86d3ef45aaeb691f454a345ed61104cecf0f444e4d841f2bc0402ea1291ef056eddb3fc3929ef4623f31016c3b5", + "Name": "matter_g2_multiexp_78", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fcd3f253d018ef09a7c6e8f36662ab4190867a197e0c42a0b425dfb5fe61d57596ada28dde0b093676ce15d03406d20000000000000000000000000000000000df00598337060d603607f3b8dd16f277ce1882a2e9ced48e1944662323efc29b33c807653f31583a5d2198426019ba70000000000000000000000000000000009876c81a76986435d34c6d44d51cf1016c19ceed2432ef1e68decd64da2e31e42372c1a41a514b0eac0ac103ab6f43800000000000000000000000000000000121cf298ff8f610c64ca4a887c52cbe940333506ef2afecffe266b5b585ff737395e2c64adc23b5bd232250e67c7a62613ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff566000000000000000000000000000000001885d5cdc3e0e0c8cffa7519e6914e5d85962d07633970c4174ae4587853f13970a1f5d7ccba97458b9b5046847ad29800000000000000000000000000000000105b7c0ba96d5ce32d7447351ded3e3f491a0e741e921447b91f22a23b64c2d749055a0593e5b47f0ff7815e1a4c9943000000000000000000000000000000000cb88fc10c94642ae7e1d7275bbfd51a2d40e9b29f3d51a1ceda577beeb131eae4b17418f9f358d47b4b9c9ca4960a3b00000000000000000000000000000000131a3e080b1d4e936d97d255b07b09a6210b5fe6900da87b5cc595a72de2b6ddb01809e2dc63ad460a2926dd8d3b3b2ebcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a00000000000000000000000000000000066b31c0bc4b3b9fe420dc095d551903a2859556d86e210c96480f1d31d449d85ea292e2432babdb71c151c7b215cd6b0000000000000000000000000000000019d79a60793957745077f9233aee7a4f096515eefa7c49473f09bbc73fa0ee13a2a30a08bd7f3bc1d5c412d671fc37ff00000000000000000000000000000000006882160e4fa8ae2c2d48ae389d8f023e2775adb7a815edeba13728b8f6b343c45788c8e9116445e9989e01eb43e1500000000000000000000000000000000000ce53ab2d81ebaf4a85b3e12a6175ad7fb6cfbae207a69a0fe2195ab916fcb582b097f09d9fc565b837925f68855c4b59f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000a30d335c035afe459dc262fb1bd24dc0bafbc08fae0bed47e4e204280eb96595fada9c4332df1218748921bfb1274c7000000000000000000000000000000000e37eb189560211d6fe56faa3b6e710878a21907fdc1a9f8becabca290c24b8831e28ebb48d06bd822300fd09b4d103100000000000000000000000000000000104842b88b9df6a7b8243494eb11eb62c89d1ccbde9f55fe221c2366d6bc9149178f177628c6fe7c7661318640295e570000000000000000000000000000000011df8599d72b85ade11261076e02c036be5dfa3b6fab4ff72ed7413a879c0a0742be6c36a32d0829a4e3171b0341c6a3995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c0000000000000000000000000000000019f6634435be45b099cc739fe5c2dfa01f61fd2d466d5ea464053e2d5acf2e0e9448b1bb7770b5ad426f8a872c5764400000000000000000000000000000000002bbd52efecb10b3bb6f8bd04a5751042d8598cc34e2837184cea2b5953ec125dee871d1f2f57ebc84849e3a7ee5abe2000000000000000000000000000000001962b716342df9c13c21d89ab5b8c4c0ca191440fa709627e0f240a7ba518f4c95adfc5973b6ed0af591bb54bd00937f00000000000000000000000000000000089eec676276c52bfbb2593ef0362c12a5f3c1a0566d5aa862f5f5ba1580f4dadb36c15fdcf0c3910ee14487ff146c8997b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a5000000000000000000000000000000001556d081a489eba4fbb0c20e22b8cab432a9f6ff459ab9b0e7ceacbbd46c8e24a2ee70151b019a1b4bfe47d934afede30000000000000000000000000000000008fdd7391113e8d9865ef48b60acf921b17c50744e6ad62fa24abaae54836b3d59a7441371bdfdcdb251d252a43aed7b000000000000000000000000000000000cc66cdb1fe32beb91b05922f3920060e7a95467381d62f2f036e6268af4128c9516780ea53e873993744ce932b901f100000000000000000000000000000000151f94dec958859ecaeb810c4b1cc7a707d0e1671cd4a1e3c811910bc8b95c6c944167dd280c7fed22f92ce7650beef998115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd178100000000000000000000000000000000004f88568c7ede48d7476175f1d2e7ded4312c24934f0d47794705621f8aa8a5072b86cc41e187f4aeeb49bff17a4c9d000000000000000000000000000000000ca6c579e86a68b4041150fbbc36da744d359028993681c34e66c537eb8a0a0d55aeb9b8da7fecb844104dabeb507805000000000000000000000000000000000fec63c57d3d3ca98cd1735b2f59217e163ca53b07b4fabc4415b98377d87e75f0fcc9b51c99a57ff61ca8d0016a206d000000000000000000000000000000000940e9f93f3ccbe74c7be93236a2c440b213a014ed51cb57fa053495c3d6f6c8edc08ba8e10be26e5faa898162d67fe327370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf500000000000000000000000000000000055e4dd9da22201b5eb64e3b9eff2eab614c48450424491a85c18e05f50659b88e862490edd11ff980b06696b60c35b00000000000000000000000000000000018fab38f58d3d541666bc29b9e94cb3940f1794b2aa851d079b9aaa1cf742b07cd6dc7c985c7e4d7d3fe683bb15d618e000000000000000000000000000000000534de5e1c1181e951b437fd17993e995fd4aa2f6b28fc3612cd4db615de742e12d66c03b9ced538c1c7cde27752c190000000000000000000000000000000000aa8580f1da71f2ae9ec26f3b6466813a40ba5bd3f89ed0d42695d420032540194617fcc2f13e36219fc0cc3886a69c36bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f710000000000000000000000000000000001804ed7677fa3842bdc3eba708bf4fb7f7d4eaf2f1a46193c861595f64196398622df4358b9526f33663138b24fef1310000000000000000000000000000000011fdd7e1d0c5adfbbbaa69ce63c7c54525091289e4dfdfb3de772a8d5a958581cc23933deadcb8856540e2d0dc564dbc0000000000000000000000000000000013fcf17235506fb194e3adaab881c7aba4b87e5aef739e0547b858410e3cdbff0dab1980b1b30a7d03d617179ae545c900000000000000000000000000000000004eed0ca479cc458231ff969ebdd4e33732953e9f5610d78d4753b99c5f8cf73c742387b8e71b9be074fcc67acd71cf6b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d0000000000000000000000000000000001082b7796d35e387df689bcdda6e0316d343dc907822d1a873adea050374962b164ed27cea0e1b834997f8274e4c5438000000000000000000000000000000000b1905979a90c7a61f4ee2cf3a9f4d6ed4c724c9e216981b8ec34fb9b528018d237771ad620020efc2c3cb104df667cd000000000000000000000000000000000752663e72390108288ef4de3c3ea409c74e7051505b12083c41a2e8937eaadbd8cd61f96f7991722226fdd02dd8d252000000000000000000000000000000000f8e4eb7a3c78b8040a115c42b5d2fc69405f8334e948b8553f444dfef29bf3920892da431cd8394cf61f24e356e95694f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce11070000000000000000000000000000000010884516bb9916084709351ed8768c6105fa451e08d5acb233511254ddbf4e72baf9c43b56b4d7dd129a38f5b34ee5f0000000000000000000000000000000000228fc5fffef746419cc69abb17cdc63ded44892b8c5d02f0c72bc8506a61d15a74ec4ea0e1d78f555ddec07f418539500000000000000000000000000000000048a4192c204b7441e871076d91d4f610c347c2d71cf495ffcb2e2ab808a8c1a549eae96e657d756d9a3b94db2892a2f0000000000000000000000000000000017a94d2472df89104ed96e24d166f922bb852b5ad80f80188fce65b08d39cc3ecf94991c6bec5dc12f9337e7c087db2f8b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa3000000000000000000000000000000000f6293fb0e19ec85f43a1a02df9f59ad4fb0e49b16a216ce097b8ec59e781fdf176360d8492e8b77674ae2c0ddb1da70000000000000000000000000000000000e354d09aad68fce6cde40c787ba1e4488999d5b9f3fec25c9994b56bcccaaa746c958bd16ba271485f461b0d4e983200000000000000000000000000000000014fca0851b0bfdf2c69fb346f23b46135d2b7914bb49e297a0c1304d8c2851ff6bd0a0bb364938dd44680fe86cfe12e300000000000000000000000000000000164e23a53103dfa332e5ae09c7c898b95773c20f019d8b794a6b49594040e2e090db6a8047c943885dca95188e89a63b30e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b200000000000000000000000000000000146d37241ce4f71017e4423dd0bf907a12c1364ae9fc6dfe535c25e5e99e03ce157cbba2675829b396a69f92668107280000000000000000000000000000000000d5a992f5357615f436d95fa516212812f6811dd1f1921ba4129e84e3d487b6c97520995d8a65f6771dbba9d150c7ab0000000000000000000000000000000007b01f86574a9cb7eb3b9a19b6040055a5c11b13e7071078d16b9ad71f714ed28ad25db9511964b156ee34db22385cdf00000000000000000000000000000000154c29c6e2b21a75b14159b183e625c98a04be1850b22d314225e94b313619f641ead73130c1d6feb85abd8c9e172f6323a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe00000000000000000000000000000000075be2703b8416fa07a7cb6ae8841dcab1e36b0ea24231dba617a2fed3bebf8d952d31f68c149dd17eed136fe37b01880000000000000000000000000000000001156563f1401b731cc23c4be59e69b0e6a0827df4889cd9ef9e11310f679c1603a0d9c9679c29b8dab75ae51f49bfe3000000000000000000000000000000000663faacfaa92fbc095a5dd6b1f2dd141e248f84eff1716ee71bdffd4d28ef1f4c88828e3457e8ebf0daba1416d2d6070000000000000000000000000000000018f2871f5897aad9ff6ac45a9c0e78be8f312f07af5f1dab2bc4705558070abf367f1782af896288a7754da82bf1a5141ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000001618a284286899f501f46c4761c93b68bc8ab3157144e4013e242e1678cba20a2d978ab53b4b43145dd6062748df541000000000000000000000000000000000c25da737368775e41ddcd9c64cf99a824afacb1d404f1ef46ec7fe4ffd89673648c5207551914e6e0d12c57e7d7682c00000000000000000000000000000000097ff49c4872e2da1f6c24fd6dd4667f0bef4eb30fc197d13e8b66adc425e39841dea011d79e4d775106a19ea1978f4c00000000000000000000000000000000147426b7d9b0bdc2be051d8f6cc4249014e1bbc2369bc32eca94684483f50ced2c07be6a320effddcc1ed5cae455fc92212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000000cf68f7ab056c4689af95b361ee3e3b1c1c48f18b5aa655cce1a2be217010814b3f07dedf6f9a7b835cb13e2afd7136000000000000000000000000000000000dd6d0fb94048dab34410dba4e682f020ed54a655099fbb6f6e94a31511960f0447d7e94143eea88195291b225d11246000000000000000000000000000000001864c6ad3f2f794239a179647d68734e23b3520b79952bda20acf2f5afe1b76bc18e35b852d35a5cf3b02a3ce86f640700000000000000000000000000000000015ea24562d7bc59d813b77b2a4943f9e98842b5a41c0c7026077a02ddfd3d5fecf352d4399f507fb12ada4ac495ddece9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37", + "Expected": "00000000000000000000000000000000080f0e50f90e001a442965ba7900997fcc89246742590b99add6d90428d3b61516664654bc8fb423f701e85a342a668100000000000000000000000000000000003fa9e84ddd754047649b7cfcf5bd78852abb298b3bbe6575c4c7dbc2e7595499a9f42f379a2463aa74f29e5c73a9040000000000000000000000000000000009e72d3c418726f6400b8cd8b9b649005f3b25ade40cd6f77a0c3cbdbef461e917d4453c9e07ded45301d21df4ec44db0000000000000000000000000000000015a06cac223217602ccfba4f4586cb184994bf08b324bf977dbb3884c394aed0622da7dcf5712970554d73b18e2733c5", + "Name": "matter_g2_multiexp_79", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007340f432a5cd5aff1a1d98c6ea1c94be24de2d15a4e112925586c30979e23a5db93643308d3299e370b1f26bdd09eac00000000000000000000000000000000155027caae88381a60af71b2fa770e58efccfbb7642f5ef6b1591bf77e415eb117ab564aff8d9ebcd576f813b793ad2c000000000000000000000000000000000f604238d1b28f010ce8e45f2fe61d3ea20b902a4debbabcd54ce0ecd44a9540fe2bfe847178656fef0a5fd7e6d012b3000000000000000000000000000000000d7f503ede395dfa5682aadedc98bfe28d3fbfb52f42ecabc9eebc0e0a6616d3671604709f28255f50b62bee641d2711f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000176e1f9eac4dab0d253c0ff41b7600437b53a5ac5278d544a9620648e0bc4dc56aff0bda973fd1338f77fa174d8b13b90000000000000000000000000000000012919a18343cc166e2dfb92ff07bbf838779ef0479985bb85b3b82f9d0632b3f7a19d387f725a21729a77c58dd4d1d1d0000000000000000000000000000000017eb269ed75fe0403021ce70505bb60a711c91c551931605bb2a0773fafa07aeb47cdda382c0aa64f40f5e6e0e6bc77d000000000000000000000000000000000bed8ca999a4691646124a140fcc17dec02b74bd28b599c807abcaaff13bff65aff3892897652cd33b4ba5e4cc0198a9c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f000000000000000000000000000000001344d6902f5fdbb59a4c975847db0191beac284eb17cd92360e59f42cd7796cf2aa282bbd4cb074c4ee10b489ee3f2f60000000000000000000000000000000002158eb3429d0532792532fcceecc404e95a879be68b3685ae94016ca3762438b3320553ab6d5fbda3a0b615a04d996f00000000000000000000000000000000118f6fd8f60edf7088a0b4b49338bfcfc9c38be230460d7516f317b27c07600f504c8cc87acb0c95515c3acdc1b125ce0000000000000000000000000000000014eb422d44ec6931ac9860a6a017a907e8ed76de91bb7557e818dbacb19fb51457a1f45cca91f1d1d75a3567a3375b5cf2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a95853000000000000000000000000000000000f2c72c53fdb1b0cd13a1f20407c64c46e4a0e461778b0e2d48c4f20be7c655c639b38f758fa9199b8395f706df10e7a0000000000000000000000000000000016e6c75cdfbc20c5dbc2dbd1caa66be92911264d407ce3c689ef3ae1dca44dffacb4c0d8a78ac959e47ac5c454f607bc0000000000000000000000000000000011c5d80d52e864b0a46fb48488f497fb85f51ac040c77b1d01336860b972858c0a6e59914112f6cd6c1612c604d26f56000000000000000000000000000000001136aa7eb63d6f85d665d0539975a9a51a9a3f5bd8731910c32130b1ec8b07c39eb42e4f61e7d22bed933d9fce1810581022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b000000000000000000000000000000001306612f5119d33f177b8804443d14d04c8e059e28f63aa10ac6a1b25975327f378d5d24f0236e05849f07e99af93ae20000000000000000000000000000000017340f8887292264d498f84fce4af83573aa6cf1d57d99d364f2b84e1734fa4f9a1e07ddc81a2135ad5f5e0ed2989585000000000000000000000000000000000f65073250019ea69339379aacbeae7520c1ae10c8912ff827b702bdab2e15404cfc939389587364d811054b7d9f2b350000000000000000000000000000000019742f83ba0c9d36aa1d595fcedc3cdfa6c6f08579e66b8956fb32ac03530114ed4266738c57175e7a10313c8dd42deab80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000b2513f906db531d052e8e6f1cb8d7d3c41c7ec3158b370268d1de204ed8fe7618b64ae35029d1718153b5bdb8439dd90000000000000000000000000000000001664c367a2d4170f463c90351cb321608e2a49fca6f3258bf10d32c39747084cf9d2c38d5241888aaad97985cb09a450000000000000000000000000000000014de15b86461cda9f1be69f43a9ceadfe7b7d1548a206f3237d93c7c01ee554c4245fb73827ed0ab72b99a62215faeae000000000000000000000000000000000b25e458522be9fbdde4554b1a0d9af157aeb7d3ec1f89185b193c0429125dafa554d7a531ef9502d443a26112b940b8f397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000006862c0b0e3d7bc4507bea1df82080745aff21b7549b372085776be2f88aedd4cff00ab8258aa21e63340963bd0d937b0000000000000000000000000000000017199c5ec3a2dbc1f1e8d74648cf8da247e35cb07df22629b3845274d29e473819a31bc344f2a2bd6c790530cfcc0126000000000000000000000000000000000e7fd1ff41d86a02014229c5085c886988dfaddcb60f5c7c81063e8289aba846337d61bdde57e276fe6c65bdfb48751f0000000000000000000000000000000010efa6aaf7650edb0c74d30125e36cb67cffd1c7f57932d92ab4aaf36f8d9245d7c75dc2b3bc8f3f328589b16e26230e28e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c5000000000000000000000000000000000935de4b16f5f9c0accee77b5820cf36c24aad9953d40a2409b7e6040f09f85da7d2252843f9f8005316146caae539800000000000000000000000000000000008a8c542111951b32bb0b50f7631f8938d22e298193edffefa3e0f5c861ac8205ea9b865f9420ad74cd22b37c5cb56200000000000000000000000000000000012ddd660879a1f52ae6284e14f2ae6ea381ff3f321458cb76bfa566b04ae19f3793468d0aab652a82671be74332a3b7a0000000000000000000000000000000005eb148c35732f7ababc73861b71fe4ea5e25bcdd675e975fadd0a9e0fc54e175b2e39dcf0323f4a9802a68baecd25df3cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000000bc351eebfd3f3c332268055af1655c8729cea44eaae803607198cf747280adc0d3dedba137828834af3e7179ccff4c3000000000000000000000000000000000d8a6cca17e1c6ceace7c0ab1333ba76ed6c3b114bf99ff80127c6a17eb0585bf6fcce871deb7385e9a8896a21c065ba0000000000000000000000000000000013222db97e31e28946adecda10c9ccc9aa9fce33e0aca51d6483d2f0c5bc3f33994ad516215f8333e22167164ef5459500000000000000000000000000000000144d3707b1898d35c65ae2c89b1570971a9494e8bd23df835f565059554eb7b5cb66a6eec890058316aef43d6c6ff55c8e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c5000000000000000000000000000000000138da93a9a4948d41a6fc6d057a217faf5efad863b45ae8eab311360c033362213edb0ff90bad6c95f60b8e1131336e6000000000000000000000000000000000f41766d9b57b3210d315a2b8f90aabe591c1de6037ec79c0d72a283f0ac3094436bb97b82b7ad12ff4f471a41227bb50000000000000000000000000000000009aa4f5b674782b7adce6bf75ad676480f96a58d68dd7ef8d1fa488cfab794f06e7754e9315430189eed265913db8b300000000000000000000000000000000004e2a4a48f02079c0ed50c1daa91b1216af481a982c7aa64d8ba90449ed886cdeddd0cc08f1f8764f7f8c5988fe677f5674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000019c927bbffd96aeb9342666e1974d30f9dc215e8eca41c24244c63c106331ddad20d64c79faf8c5baa45cd30b561e167000000000000000000000000000000000523f063de96c9b77bfe5c5045a007e155b45dbe68c5f1162884f1d942bb385bd34c2a37e5e67e6dae4a23d600d75d1f000000000000000000000000000000000c221006f5bfc8baf43826258d0588d7c0fc345d68de1add1693bb897959c2cfdbb9c165e82c0c787529cd7be85afbc50000000000000000000000000000000004218e3d52b42a4504611929f94024326f38e78bba2aba105db3ffb4a51f8906b060ce2302e22ded60714d652a234c1f288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b3400000000000000000000000000000000189e5063a36b0edd736bcd9f997f4b08c62d33b27560e2e2b7b40039e7c63b75757f23746e70a330110d975ca683941300000000000000000000000000000000013393485ae494b1f1467cac9a8840c695d619aa1a78c40674038c053f264c1e20481f2005abc7f0545346f5a982d05e0000000000000000000000000000000003f2be501504f4d37e12acdc54b3280671ca0762a063fd3bc04473ed5a051cae3767044c002b7ed1abe88b2143af08750000000000000000000000000000000009d5952af88514996336e1ff19409e3e4eb3079f6dea22f9738f4a331ce842b151e0b842b68cddc10a711afa6d3242b256e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000a79444c673e630f46bbc5a9e06e8c023978a78e3c58d72910a04c3733ad873c0d0de61448076b2fd3764cc17d86d94f00000000000000000000000000000000110cfd215d67d4a091578203855fa0e85feb4dfd0076fbfad20bd092fb91b528a4117850955f5fb6568fc5844e17bbfc0000000000000000000000000000000012ece0577512182c50dbb4a485256e705410108d9ba9c8d57780d49e2e25a0f89ed1fe917797b902aafcb8f7d98fe931000000000000000000000000000000000217cf1dffac7ae162181d43ef12e3e88da4840f1573d7ffa271f64d8d54861099be37b644e96e650dc613975d8a00a4ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd0000000000000000000000000000000010bec428b2865aa7c077c168dc28dc549481c6f8367a5b84cbbad661b0225cf0fda3e840d96c4e4efc36c20d48f23d5d000000000000000000000000000000000ded3a1e9e2eded0a11211a217f9355070361f0a5887a7e19c74edc8768000311cb9dd8513977ecfb45416cda0908cca000000000000000000000000000000000b99ffddc79e825f0b73f2d0229d66e51624d854d00bdee5aa7a884dcafa1888963e2a2149db0f6e40ce3c67941a391000000000000000000000000000000000147618970c71965684bdf0d6cbe1de189bd23bddb2b861c9636efdcb7a96dff27bb1ac70485b562e78485a1e8e56531cb96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000001a402aba8fb28dd37f1be11fca037baa99a6b57188ccab66208a50bb6967dcacd1943cca73e34f6b2e2f72407103a73000000000000000000000000000000000c0bd64d043fa4e3ea566cb84f9139091891231ff500b67e5fd451805f79003f6303352a4f0c236063d60d9088fae88c0000000000000000000000000000000002861fa7d0222711ffcadac86e7b9e7b494f5561c22544bd0876fb6e1b2e680d0f7074c2800312cb233de2412ccbbc8600000000000000000000000000000000015945f0c83e738a17cb1283d08d63ecf12a7272bc62812006ed78254bfc45ca7c42306cb79bb16ed17bea600a4d62b5d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb0000000000000000000000000000000002268793f6872f7715d802c0d96f3b3d850249d8e70aaa97f19793d2c92e7cef384aaac603eb51525c7ceccdd0211fc40000000000000000000000000000000002507d680a2db16746810e966d1ba5547ac98d08c8402aed0859203e6dae0cbd87a9ddcc05119c1ca08fca2fd733882200000000000000000000000000000000192426b6438b2abc7386599afbe09081ed4908fbeb807a65bcb7c6676aa76e5e0c2c87612cd109cb124c73b9c8e0591a0000000000000000000000000000000017f125a2ef5246e7a19e1b2741b31b9224511ffefe63ccfffaef1b7949e88af573e267d6c7617ea97bbaee6d50eef67e1ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5", + "Expected": "0000000000000000000000000000000018c2f533f464f9768308a56209711cf9b6653e8d38591d782ae2374905f99f75c0d42c63af4b534056c28599a9da874400000000000000000000000000000000071d4d708f00875545f381e164f77183e14faab599e472b2857c15091254ddaf5c2e9df809875336f92ebcf5b7628da500000000000000000000000000000000099b207cf6ed022289c27393c32d0b83aed6c7b57323e746374c1a8e2ade071a5293168e72f7aab82f6c2e39b97b03830000000000000000000000000000000005dada01b4dfb6a52d998210a67ccedc11d6aca2405e0836280e2f7c8fd7c8dd271c815a2e9ea1dba6f1ab0d6e89d756", + "Name": "matter_g2_multiexp_80", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000195d3f440857011bf9b764ff270b8ba1d9d13daf48933e49c12ea20d335b58bcbef1353d9698a7e795b4370ee385f12b000000000000000000000000000000000716c151efc6e611b5b15c749eaf02816a86e267428750741b167404a21116f2025d0d07c447b9c7bee8edcc2c7b76d30000000000000000000000000000000012ba0bf62b35327111d09b402db2b75b2e835cbe581638af2fdd6d06034774533e6501be3de84e7075e4184e11fd81a8000000000000000000000000000000000329b14859d004c146047b03870371f53936e078ddc69294ff1fd6f42cf2a354a921e5f2e5c125c454e20af97dcf769e7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd00000000000000000000000000000000042286dd205ac86fdec3fee779059e2ad59adb62505f7b78606c128244b031c53dc40ebc2f5afdba348892d5ef4c10e7000000000000000000000000000000000f960010d4818846b3a0291c6fe1aa53bf0eafbc0e0968e3ee82324452a7c1a8041c06b4db9cd36a07c119c9fd2f9038000000000000000000000000000000001876da0dca72869708b8ff9ea0b74ad6be25ba82ccc76660246413a04344f2b72e5a7f6fddb58e9dc0bfaa6b33a5fadf000000000000000000000000000000001538ad1673f117493d998941d9356fb9907f70c279bde8ae8813b9c7b371344456f8e67cf02bf3401ee06d55604cadf9c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb1000000000000000000000000000000000d91319b4a5e047ffc8a68e10c34b2b90e7f3f08f9e3ec53ed12bba5f66c168c20c6583ba2016f0137caed834845f7470000000000000000000000000000000018d5542919674d2fc32430175405d806ae4abe3e1236df2188bf4c9ddf66c0974036e28414890212ff8dad244d11e3c700000000000000000000000000000000160b128f1ffeb97edf0e62dff85e3f90fa48567ab777a7937a2c0e4659df180fae4565107c2236a5f2808e42a03a4ab40000000000000000000000000000000003ee74d214ec491331fb9db8243e75570daba9feb587671496cea4b480d80ee162c6294c082203534bee450c384f645e69700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000001951afa33800a366944c43bb42b8c5c8beb9ea2e1cead8b84e0f94af51e4a156d9454c0f08d1b13c692c41cc480fdefb00000000000000000000000000000000077f4543fadad6f2f8ae8d5d98f64965bf9626971e7efef5221cb4d95d51b8764324cf4a11d0ff5330d58df70cb79d92000000000000000000000000000000000417251cd0c1b32505377e51bb30ac8a8a3c059644b9ddb5a058b3c6e1110e1c71ee19f549b15090144dcf4668d0d50100000000000000000000000000000000052133be345adc562238c4ecbaf76ca4159fc11ff563ab393317b03065ab668e7df401831baf7027f0577f5791b1ca3019e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b00000000000000000000000000000000067bd52b7a3193d31a4f1ffb76432c8d4108442616f17056d310fbfee2ffaade9437e2bdb8425cf83233f0c632efc1170000000000000000000000000000000011b045d6eebe1bc8218b696b5e81e78db78eadf1b5d987060c1bdd73aa65666f77e1d6bb6f3d939d64cb3e6bda08994c0000000000000000000000000000000016eb5ea5067413b72632f5300efbe0d01a284b2a59b68d0333c269da9302bf0f0cdc923acb27e51bbbbc1d4086e6b06a000000000000000000000000000000000ff37b8812963d9efaa1e6deb5cfd34eec70620fdb65808739295a819e03ebcc8f501b8194d0b3c72717fc922b785194199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e00000000000000000000000000000000149b5e0df255281c1b518427094cc0903fe89eac9a6dcdc379b8ca30f3696d89824c201601fc4b0795a3c859a82893170000000000000000000000000000000016ee9e7d957f439d078f3c5da98d114a1b5bc4da9c17e117e1f540dcbf83a349bba94def4b87b63247f190e3b5813cb00000000000000000000000000000000005d4f56bea105be4bf1fcaf4f25df30f85968d59e60b1c438c28ea0f480851f5ab9c05a7ca6677e6f12c7dd3ed67c2e0000000000000000000000000000000000dc0e87ca5a8b339b485ff3da2b9854a07e9663c43344dfb5ecf3ea055eadf67405c43013e15367fbaa55f1bd8e222f98159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b000000000000000000000000000000000b0575fe2adc9ad66209cb2191efc2946672e4e81b96d50493d2125d9c83165f0c4d3f714539eecef9de0706cc20da9b000000000000000000000000000000001511649f0cb6b86111d2830812231ad37df5500d7ce1086241591dc3cf40b30f1c53dda3133b2f7fff253c94d5eb98720000000000000000000000000000000005b15e4e32f4f4e46c1560792a9973f6ad63f5176694734f379375f16a08c162a4a820385d3ea6c191bd87fea4f5c8cb00000000000000000000000000000000089218403fef08dcc6e679b49a74557dafed3278d41ff36a9801db091b91de0d46d779a40574fa4a3f2baaa1a14be098ef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a0000000000000000000000000000000001c35a3fdea92b28c9ab4bd9ea592b998853a73be844b9dcb500ed6704bbf3ca4ed4216dc24b50254b6ca75c4ca3e7fc000000000000000000000000000000001815292d2a365dd7f41ecf3f9a89e040bab717241cefb3155a097eb9885d64fa55f5de7023f2ecfd33f483ff304666520000000000000000000000000000000013df522c72805b890aef97864ec6769f569504fca2d6a6beae97f80dc92643f8014daf3dafc0040dd7b985c0d9b2c462000000000000000000000000000000001155ad4373a8304fa6301cf48b4ace135d6a0c08cb06d624f42f88073e43612ced3cc37235422171b43af2b4ebbd5662d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c5000000000000000000000000000000000bb9afedf7417ca31beb96486b024af13c06007585d785efd1e78444daa9bc3c03e1d64b560e8d6a18ccf77a8c3c8d05000000000000000000000000000000001652d3adcf1612e487a9ca198801afb9ec30267148502684c2b91c05ebf6c48e2ce33f9c0a986daab81d5359ec1b503c000000000000000000000000000000000baf3d34bf4a78e3b9dfa637c6392c7f4d7ad0ec315d10748784b5b60221bd9da0f4b75c57c139ac2db329e270d559de0000000000000000000000000000000000c30e553fa2324d552bdbc7d2dc86531340c4894495ee9a38b64f5bb6f92314021a2a00c4bcd8837e55a0ae2676a9b761d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000159a1e4e87c35aaaeacdf21efbf8ed99fd6a2ddd7e990c12407b1417edaf185b8f1df9bafbddfaf3d581b5d97d7718300000000000000000000000000000000012239ef7b1e1009c81098aa4aaad8ee9e003530db5afd49867aec47f46d5e29d44b5e62d80d9e832937a299633e863c80000000000000000000000000000000016af6f74392461a9294d9f848508651ca5c0cb50494ee7c6a334bd770580b924a17beb7824b489e7e101ccd50aa0d5cf000000000000000000000000000000001912a0f54ba4fbecaa55c150ae93455e1db6b238c032fa7992bc8456f183c09b6005dd6398a77ab91cf547919ce7485b03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd000000000000000000000000000000000cf1cf7a09a12f51d10059425042ef8e140718ff11d2f17897a0156034f73ed29496d93b8695cdf609280d319c9bb742000000000000000000000000000000000b2c4d26fa1eb72eed1a24f27229d2675e0c6f91e3a4eba7d34b0fc1bf5a9b4eb49c3492d9586669abaf25a656e1f95d0000000000000000000000000000000012c5c83a03087b2449b71e9037591fa265d710ff6d869bfa18ac37cbdcc93024f673128db3dbad9e3517501af12f2540000000000000000000000000000000000ffe5824245e43953e3d0adcd5fdc1a97ffc87f8c5473fdb0fed57000fd126a9925ba7415c698248c51c1f3e12b270d5e4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b00000000000000000000000000000000036aa888e40882b2d6ac71d66c88543e32b4a0a7c959eec560e3d26114d8aeca63fd87dcbb3171622c989a6c7a204ac60000000000000000000000000000000006a5e552e6d2dc95ab8636a8be16bc79572b47860bb88934bf04c195ec01fd71eb91e45f24c58bc2812ed5fa10c8dd7d0000000000000000000000000000000015fa3ffcbd4e562a4bc29975cf8c1eedf442e37374fc87128e6f68bcdf6e996f6f054e0b8c608e651753de96655b2c100000000000000000000000000000000019bba7c0b170dfc1f8fdbf7a2e09ca0c4027a6aa6930d15dc2772a0f20e5e56f0d11644094dc866595f801ba5552e6c4c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000341f46ec06a8def4f044328bcdaa308798469c767d10e5db34b0ffb6f550421c67c6fab7b63cbc7504e55847cee419e0000000000000000000000000000000006952e5f791c37dfebcfe69cdef196dff66563b29e94927e3ab34365773b93e72251a63af4ff294af88d45fe0899a2c3000000000000000000000000000000000874dfe75b31450e99dea063c090e32d24fbff9b681b64a9dca5f967f82003005b003d17eb869bd3b37d4a412bcb28fb0000000000000000000000000000000014203b69e8af4e25232777f503d5e82d6121256fafdff1b037f65d5aaad0f09ce882151d6bb4705328400f00089dcc7a7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af4000000000000000000000000000000001403fa3f418107e0bf7f3f4bfcf621812d32b1b744ab5a4c37b5cf946a5e5dabd675c2b70bd355590a9883436c5e32dd00000000000000000000000000000000069e006f168bed4439fb46db9ba4f279f72ed608c12a05eed172608693f42cb1f04aaa54191f4b0b35f967bf03d0e63b0000000000000000000000000000000003f9ce029f6fe605802de64701ccdf52bf4aa299400a6e1c36f5a1f9173bc11a38e7628f123fdcae01d2b260f77c577c0000000000000000000000000000000009c9732809f60635115cb479c80457c6cd8dad092111d663c0cda0da1fa71c9bd6795ad013d2efaa4599c8ac5c88e5f26f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000115e08d8e4dff7adcfe46a416625be0ac26ea2d7900f5fed497809a6d46e7faa5b47c52ab3bbeb9fb16d82b549707ed6000000000000000000000000000000000dd1b31446e44f64ea5046dca5174ae854f6bb5d95886fb95aa136d432f1a8c03ef1a5f9320f89c82f764049a7f678a40000000000000000000000000000000014879783c07e6986cd393fa1e0ca8a7e23b2c9efa595229fc0b6a11b9c232ba33e92962a1087fe2ba0532d7b541827900000000000000000000000000000000013dc6e2bdb2801333e7f914b99f30b40125fa1ebd49b141d88a8c090b15ec3250a13812a19c3c0751a4e5ed100a6f0ba12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd6000000000000000000000000000000000a2cceef36ec78dc702b6731dbaf8cea1dc2b41fee1b235673c6941729bc5631e69ff37900479391a4d10b300fbf3eb40000000000000000000000000000000002f4881fd626f4ac434bc1e59716e5e5ee14dcb9adca4d639ebc9d86e323d274ad8ec0a4b1e6ff92e1fe7928d48924b000000000000000000000000000000000174cac80e7bc63989f58759e123513b611e9849b44d43a362f2eb84421ad008f3ae9e9f0f233e49fc8e10c1824ba948200000000000000000000000000000000143641099c8a6c8153dc8ce74debe795dd6c4487e8234f164f9f8dcdea6a53619c04a8fac215421f985557b5b956c20a5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256", + "Expected": "0000000000000000000000000000000009807ffe8fa881b235b1181d2d3f147dbe21042524fb0c0b4c90fb122d160c7b895034ab32e20324dfca564ca6e3183c0000000000000000000000000000000010f6da88525da3e86ee56cd5514a436e3ce4128e437a876be130a70c42444a05ac269326c84dca532ca2e546860027c00000000000000000000000000000000011396a7317918841ba171ea46bbddc9bb5a08db7c82b90008c6982b4b79a4dafc151081bbdb7b9fb79784e603e15eb9e00000000000000000000000000000000070b8580f303b83c643a484dd031b780ff4ca2ec805d8c538a0b0c791cc7f8163654f5e5a41776a8681500a6690e24a4", + "Name": "matter_g2_multiexp_81", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f38906bd058e4d32403fc3d39fa57bf49c0da65ef42fb129332b91c184185de4f9f0bfe8908a44833ff4ac4d65b88180000000000000000000000000000000014ea6fffa6dc462463c15feace841697698bc521f608ed0d16be5097bf42aefcd1f73182f37b6279f989e9668a8076d1000000000000000000000000000000000f56d296323177ce53c6977fb60e445278e59ed1cf92e3f68c570eb7a9e5f8afbec5e2ef64674bbb54d7016c829f72750000000000000000000000000000000001b29012ff3460cbe4a07bdc65885718f217cf177866823a7cbeae18bda67f65913ea20bb69e0ffb31bd82f19862113dacc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd000000000000000000000000000000000a4126bff91ada057ceb9a75d577120c7ac8c9ba62151602414364cf88a3e12dfac90b5590db3e40c16163177ad4e7520000000000000000000000000000000004a3768d326c4ebcd5ffed89341e8d04f89e674f3f2aded3205a7193e11c20115b3c4d595b959d6e39a03d76f6b5925b0000000000000000000000000000000006e0ae4a9c45bb69c3a1c65e26e4869f2eb18fefe584e4598ba99c0044e8d911145a5db3f57194ceb6201e7eab9a81b20000000000000000000000000000000005be2ba6b147f3f2052c4877c90ca364427c6721ab64dd35e89f14f3179564d8812b9013e3e3db22f69afd739229682b98c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf0000000000000000000000000000000004c36cf955fc81bdba4ea8d2ecf934adaa57fa4073199f77bd0428d3ef80a7d7102179d4a44ef0de887bcb3ae915408900000000000000000000000000000000138bd3ec7a1b6fb65d1df6bc1d2ada35aa52b06729c10b5d45b9bb7cbbdf41677942b99eb9c2d32e3e73da7d5f9cfed40000000000000000000000000000000000b0291ca10245e2f7a963fa07ec62b15f6bf9e7a5a7839840ebcbe538dfecaf2114c7864a16564a5b3c85c15d97fb7a000000000000000000000000000000000b436e912b8a71cf8050d10d59017eca6e494e5440f02d2816924ac9cc2034bedb1cce6eff5c42f3dc57a74cf1b51cc0a0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f0000000000000000000000000000000000c105ac7475ed9517a0b07f25a030a5616952d817f3893181e352907c7cf4ec9f5f3006e37b1da97e9cae4a1213584e20000000000000000000000000000000002c112c18268934823d5946d2322d0faec497d8e18736da91d2af744d90f74136c49370a4b43952152c62820d25e52ee000000000000000000000000000000000fe2818a397d70543e752e7022f12bab10f1b1289cee61a0230d545296ec872e34d8df6edf7ce9980f3c153e6e51d96d000000000000000000000000000000000f479e6a52bfaab3a31aa9a461adbec8a390daa8eb6273f9e425eeed764a6dbad44d12778bb888aa5808df272edde401e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe00000000000000000000000000000000016106cb42ffc41d5b23bc5b06001473bdfe556d375fac6a0cb0a12494e9c02ca2dd6133356846e1759a2c485faf5e890000000000000000000000000000000003cec25b0f5d1db0ead5319d6dd15517657d1fec442facda4335ae0bbeff606fa9caa6a4c00445001180aaeef895d7fd0000000000000000000000000000000016ce3573fbe27a8d23b3ebd22aec989d61fbd0e41a519c5e2f1d650f2ad73adcfc8c840fb12bce83b722a0cc69164e21000000000000000000000000000000001434d13d44fd8dcf776c2a045734dff7c09ded31c9e3a4b5e765cf26fbfea4cbb4ac15c06599012a7f2cd572bfafd78ba32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000019923ffba0d08ebf1bd43393142d61022430356081c18e37804172082c7ace987ece2594f4852e84604a77235c7795e000000000000000000000000000000000123acf9e1a86846ae27d5fc0358afa34fe9d6b68232c9ebf2d47cc169779c4bd24f225ad30886fdf68166adfd9898abf000000000000000000000000000000000a6061d4cef29d1e3535d54a2e36373e2c16f91543f53e1aca94c4abdabc663049673f2327ea8bb574244d7f5c99e981000000000000000000000000000000000b1f3e1d43575a74584ec7a3280f8b7196f9b99b5e911ed33ba6bde1188c82d906f0f8e6fc2b285fefa0ce59116e449524301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000000373d36dd0fac76a0fc46ba5da279ca3be5a1f8d799570004e429256787110d4fb746f65a8527d0ba681a81b9980bd5c00000000000000000000000000000000057933c2b3e482ae026159211c4742264f7e890efbaeb6e14f3bf66c80923289af095dc97b751a117e181ef917d049b000000000000000000000000000000000068816ad2369bb57b3430c657284858d3736c327284e7410b61ed444786bcb34a66db9c16aca583aa9722aa8d7975b440000000000000000000000000000000007fcd7dbc062d28f6ef906f6a455337e517e1d6e6c02c7c0b2b2685b79f56ca3436c1bfa0ab96e4a5eb0c2e2c321c0dc17a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff825695706740000000000000000000000000000000007bb0ab060cc12002e043724c0fd0c8bad30e08b65ba9f2fe5d09d18cac4bb2d50e29ee14590ca7bfc505f3ee3d4f93d000000000000000000000000000000000e680653d29eb5d90f21802f543eac3102a1de6d2a5bc943a53dd9b80bdcaa6951ced2eae5e2a25448b40468f1923ebc000000000000000000000000000000000b7494b494019e3ef36d5c620ac56483fc6b1c8fe5c6f67537b19f56ef01db327812095fdf805d3dfe678a3ed8bb6226000000000000000000000000000000000291e5b98ecaf7aef0374647d28fb9f8785a64d9165de407d062403047da14d4ecd19fad8575070b278608e16b71d387d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a31800000000000000000000000000000000186536e3ae3edd9cc6bc24fda6589ed26e72e06121e97e1ead65b200fa0578c6e53d1154dc7b14e7eccc3a53237685060000000000000000000000000000000012fefaf6c76ae7197b99571e41a19b14846fc4499e8e964ff750e7c3ffef6ab3dc19eeb42c5f6ba44a573bca7a15166b000000000000000000000000000000000a135db813a44a21174cea3a0b34fb49f273877203ccb66bce44b2b58794818d8bc1df27544ecbf780823467e2e4ee6b0000000000000000000000000000000009b08f70cdf4e349e1a73935de9fb2ad9f4feb8cf5f835be78383fda2af94d81af253ebce08cef825764151d5713ae60cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a0000000000000000000000000000000007976df2d47c14374e554401c4d3330bbf6f1e6b8fafcea1e1974af61e8ebf493dc0473d34b30b0b1cbee082550d85c200000000000000000000000000000000177cd64db8334dccb17fb207e467e5b09e891b05df7658d9b439e3cb72bf3e0a70e84f96fb5e448f33c003c279cb38d800000000000000000000000000000000094d739a02b8ea6ff8113019597f41df4728b270770edc5e68b1f5c32775f0c706e3f31c0a82059c1ee150b89097376a0000000000000000000000000000000006ed888aa4bdbee94ec67500e30d654071774fe22464dd5b900fdc17b445754293504b10d044aac8fa0c289f0b2d9dce6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000014a16b856b04ac4b687c79f2b4e1dd6d45db25b382e0ba6687afac648c9b6384cdcfa89812f1a726bb4d1c22ebaa6668000000000000000000000000000000000764088e337df6db30ce8aa23aefd91d9e35be911c9e89ac62a1e06c3d06e28efac256490400fac4490f595cd03c127e000000000000000000000000000000000894856fa1c8488fce182a9c7749f7953e6a73879b6e743fdb8c780275447122f512806fa83d5ad528f8f61598ed01d20000000000000000000000000000000002b33bfd09e0ff452c3336bde08df0102162488bc83c27052447a1e5d16c9c68bc529f96ee3787a26d2009f22a1246342e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b00000000000000000000000000000000133e2d092352d3ecef5b67a09c2be268fcd4fe1f7360a8ce3ef5f33bf689242961a140d9c8afcc1e2fab3ad4e3dba49d00000000000000000000000000000000101eb285f0c462a22406846d82ca6a278520b65132d2008b124f6647a642c221b0c3bbd4a0abe8af7417e7aefb81b5b20000000000000000000000000000000010958cbc317f1186aab69ac24be87647b8013b678b0eabc6270167bdc9c0cefbaf4d9a34dc41524b709f1b881e6bfa34000000000000000000000000000000000d92c47257fd0c4d6baa4c81efe65852840479b9bfda5cc06b253f167069ca7367924c0c67d6497a1e9abcce7d0ce9502eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f1840000000000000000000000000000000014ad0f935ba129b47ecaad63b9dda44e7ef7933f182a0f5226141c8f0ede026ca2f11db7f4924b5c582461688dad6359000000000000000000000000000000001453716381f13bf6ebf8fff2ed7bcb90f7beb44269008af5880a355dd03de5c84c14f5aaf69fda043b422aab0c694784000000000000000000000000000000000e983c9e9b799eccfdb56444d31948067d46adf275d7f39a70aaa8bfd0fe1b83632c23d87f4e993c8191901e9a607217000000000000000000000000000000000267c8b8c5e09b59277736caad12ec6986f206d1c1f48023356d8bc877a594c8bbd98981cec6382bf9bdb9a5fa38275ecaf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000002e28c245e71a7f6206427ee512f3250612785ce29b369682fbf767d06ac08f91de8ac9f82951574cce46cee1aa757720000000000000000000000000000000019b0dc35eacd961e0ca7d54a0e37c4ace37eb0200d5489316f3371412717c57c8f17c1379721f4dd67b3fde24f50d4cd0000000000000000000000000000000013b9741f7a32e5e5b1ae5400e32dd6fcc1fd43b68df54ade57c934720b1289a51deae77b1726e1955b6430f37928e2bf000000000000000000000000000000000693980b347ed7ee6cd93f565c87efb36fb304d7e9ae24e2b9f902bfc962b6c7fbab93287147f5ac892db2a709c9ab42106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000ad6b7cfc6cefa5783093b7d700360b354d0698d27ecefb7d5928ac5bd6c299e4001474d205cf3b85a32c600ddaf1a360000000000000000000000000000000017172c3d5acf59b70b340fc703e9b7801aeb4857ffbe7a9d5daa0f32ad80d1c0ef2f0b3b7d1fd83a757c076872425fc7000000000000000000000000000000001291f55fa7d14b14c578d57178cc707cabcdc4bfb444cecabda271cbfba2ab361947d045ed46d9edbd215fa4c8164e56000000000000000000000000000000000f64ed6c989eec5222239d888d08dfd638a0e35eff2266410dab0498941fcd1683654064107fb7e53b8c02fbe98a25622b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd30000000000000000000000000000000001d25b5bfcedc6d7ff7e9fcf729f858759936235d23ad45b14dfd0229bf3e50fc68799d19ef019b36728285bf7ecd0b4000000000000000000000000000000000326e300ba07935e0233a03ac891f18dc7b5a9ad9a28264136228e9e23e8f2aa31b7f5e5f3cb3354984f57a868a5d00c000000000000000000000000000000000dc92060e3403df3a92b15ba3e437ef0c403fcfc9c3545e544a78874e5d9b5e63b9ba6060c29022fe2594c2e6fbb6a840000000000000000000000000000000006a01e85f59dc45b1501309a350137d71147c30fb70da6b7637a9b1dd884aeb7e554215474784ecd3bef18d15d2c0524dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5", + "Expected": "000000000000000000000000000000000b7fc0b44723ff0d1cb7c43e470d4d432fc4bbc7f9d98ddb3d91434a5574956fdf15f898e579236426ea44677998665d00000000000000000000000000000000176586b6f157e408138391e3767d0c1c8457857f4cfae571267ed64ac86ff8a4b61a28b406e1caecffaae6a399f4ec9c000000000000000000000000000000000a420992f850db20d4f7d2ddff33e4dc79bc0c39caee533920c5d03d1c2619d8ced769ac09f664c0921829bd7edb446b0000000000000000000000000000000017e4000f4d03a6707174c3adb74966896bcc0eaabf4ff83cce92a666fbd13b59efa2c767442062b6c8b1a3abd604f0ac", + "Name": "matter_g2_multiexp_82", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016ac5146ffc26d1f0c33645931bddfb84756e1c7b03f4838467d1b6701ee9478ae03c910b6b4f5c14135487bd2c14bf8000000000000000000000000000000000e1d082f16e4d5c5f0b6fbe5178aef6f781a6fb165f775cf0cd4dd55f2b498a79edf373007113dcdf6b977a87e1fded2000000000000000000000000000000000bb94be280df1aed761651c0292f88037d172b675bae1933ec12323b947023b437d080aac9e196fe5e06e5c4137284d200000000000000000000000000000000064190f9725bfd5d56c31aef7fd1590c975e2ce549fa6d5c71b254851149e0a0dab0b9de3acfc2d64231c79bc0ebaa37ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000012199d02d3f1bd8c4005878c3302e6a731ea69d69accdd690b4667e847b079563d32e18eb7a440b8005ca936da5e73cf00000000000000000000000000000000125b0dbdb0058639513b007a84d3a3e6302f5d846f22f99a55181f097e200981d9013c00d688a11eb976120f1a5da64200000000000000000000000000000000081e723506635433528fe4a40fe4ecb8a9c3d8cd701c043c0418d149951651e21632cd85f03db33b89efadd69e009ebe000000000000000000000000000000000956af2e67f8ae676abc783c4ec9f85c50ea130410cee8216fe036cd0521b8ea38966288afe7d35c28b30f7ca5c6edc0c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a7730000000000000000000000000000000003e11a4e9dfe82cb495e9e698b16c257ea3f4ecb24749751e7334e0f31fbd6677545e4bf9ff78a82853560f7e7ba2ae2000000000000000000000000000000000caea2c527cb3aeae427e92fc364365b1f55e7128a544009be2ff7a5236d1cf8ffd5a5cefc87820bff5bdf1c6bfa165d00000000000000000000000000000000064a3186774da8bb5d013debf46ccb0d894592c414f32de6f77da47f4d42b0c8a13a2ba4f14b9883d564fd8ff6a4c90200000000000000000000000000000000072f6c48b6a05039e3a4dfc6b73501d6d4ca7e840b119da9c074bd4cd2adf4f2c6e9e6325ebf6f97c3f0b00e6b9bfac6dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b0000000000000000000000000000000019ddf708ab31f6f6f725f0e4f65d11248d3a79af30927a6f2673901fef9819b189502cb952bd4742d2b8e84acebb5196000000000000000000000000000000000d928535c47eafa5da4ce4f91467fc31aff8b86b850e4582a597b334491b14da71763f9aedb15ed32856382069c094ce0000000000000000000000000000000004d6b3545d067aa0768cda9dc3cca0f58eb546345b96f7d6b9355d47770e00286d962a6b3a64ca2ce22fdb4834a4bb6e000000000000000000000000000000000f4ef9366d342b309076299816c1ed9b424b68886a5c69e21e785f97cb0f99ae3a99ff6b5244dab817094449048a7552da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce478000000000000000000000000000000000236c5b4c57ee4facec5d4ff37a478c505217af66e029c3382613442c58875c75cb423789f6703ff3c1c0d80991c9e3a0000000000000000000000000000000016c052de3336002f362d9b0cb386b800860527e0fe81a1a6df0ccde31f3265e6246191b3febd1ea48e9391c44593ab0700000000000000000000000000000000078dcb04ca93c676a9a924e59f924d9d3af872849bc30ca633d4025aecd981ba12e626337635ea77886a45f4da84104f00000000000000000000000000000000027df6394b195222bb8357bd684088e3e2a398f0fb0cb812ca5dcdcd1fa1279cfd03db62e0f8b2800d4b8b48238931656aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab77000000000000000000000000000000000485ce58b387083172102145fdb3e26c6ffca8b35af0e1d84ce9cbd89055be083bddd3da56443924049a056fdb2ef092000000000000000000000000000000000d998b234a69d584c78ed054b1322ceb33f73cafb5b23c1703a9fd609edcabd44f1a642802b9c0b6fde6a6828b50c1200000000000000000000000000000000019235ff13567bd007d77e4dfab139cd57dbb309a3cf6a6198a548c4e6915778094ddf2b05a91f5478169757bf5a56cb300000000000000000000000000000000110f6ea19a7f62bc3e78f4c5c1c6d3efdf1a7f563576e758218b2c363fff8ad8fab0e72431619e4ebc93d2d739fc786c40273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a0000000000000000000000000000000001d45da76e3016c00fe65bb50f7067e4f06364ad8348184831c4932ea0e0f3a170ab5147e4670ee1b16924105b6fdb6f000000000000000000000000000000000b3468206db0613369b2b0750c98da65b660fc07c30cab4e459c311dab683b6b313b99ec0fbe92ba07f8aab43a12a2c9000000000000000000000000000000000f58a57c449a41105837d5e2419a34201cc921ec77408d6c0c7a2eb227be98ec1f6f6eb9fc088daa0d4c78928a1eacda000000000000000000000000000000000ba53b872dcb9fcabf35e673b467523ea77accfc1b38a5f92d7b9d269c28aa00d00b08d70eae6ed4d2e82bdb06008f9ab77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb540000000000000000000000000000000002b82e2b582b247271543117b939fd17ba8bdd617a223873296f7bd75de4790f0d5d8fe523792bc7fb4764d3739669d80000000000000000000000000000000006eb554347efc5f2ee79949bafc012e6d9964ce19459b3867865709d903fe3d11bc617f30f6279a9e62ea104565953600000000000000000000000000000000006a543fe5cfbae629fd3256575e3eb4e0b65864aad6c7f359e169038bf090ed9bd92fef32fe1ac20b2a8c90fbb6081690000000000000000000000000000000013ee42b0693b2f3b9b977fbae5c856e9e4c5e70120b5c29e0a9f898f6d04b7fe351e17b02716a44febcf0a00a9cdd9220be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000001ae7368f84e354e5758554aa9c72ab4b00a644cfb9a4ecba38dc72227d297749bbc98c8f5d6149143b31442359d8013000000000000000000000000000000000abf087f77c79cb8c69e4289fae87b2ed483442daec3851a5ba32c43e342be29433b2deac6dbfa7a787547a7361ed0a00000000000000000000000000000000000fa01cff7aea64b649951a8d85fef0bd475f31e47c706b96ee2753df9987508b5e5456cc49e88ec3aad720a2535f6940000000000000000000000000000000018874d020e2eec0e286dce324b91f15b2a4f293d32956b27524f478983f0e0c5b43df802b60f4f001753f12d449cd821c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000018f7d19362e2cba91023455e115cd90f02aeafcb026349393ca4105e270ab1cf589621b40965fdc9795f66ea0f6a053100000000000000000000000000000000170ce0eb304e0e1047617b709c834b67a8989212e5bf1cbd5a33242be94bb141d5366e636c01a229943bead9a7baf43900000000000000000000000000000000077a17356b3b31faf90f709042938b9e901817f7379b7bd486d18e47d22b0430ba70fb3006e9afa67d7dac71ffaf152400000000000000000000000000000000064aca92c41561e195fa8239800c97d5242ff0f8ce76b0d119063e2ffa09c26e01d23d5728765a59bb9587e885450ad1c6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b3000000000000000000000000000000000876a57dc24ad58416f910ee3ce220630a1297e6bc691c908e6cc16f975b146872d71661bbb869361623c61670627eb0000000000000000000000000000000000760fc65097d215ab9aeb3d5a5153977e1e399e2cc0b0cb9befb0266d98ac13512a0eadaba4e051bf56794621c551ec60000000000000000000000000000000003c8e205e53075a96c14ec26345c75881a0d67c7ce0d62d73c83dc353cd7b555cde52ffc5659ab0db2179a899f0fd694000000000000000000000000000000000d7e8a7fe6b751f7f478698f4f0d30cd0a435a2295a958cabedf4668769819b4cbd4e8b7721eeb5ced3f913156abcaaff3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a21000000000000000000000000000000000180546f697349adb2918129f4d0a979bb114d1b58e5baa6cc221a09d7083469bfaa61f80f1e3a6ccde0da54b24d59db70000000000000000000000000000000004074338380e3d7c0facbbc71d83e78b53191af9ba13ba0cba6015bf4f28e4b0b52ffb34c7867a335848f57b5ce5ef5200000000000000000000000000000000148a800ec38cfc2386497d9aacb4327d5953a6612cd4067ac13fb977046688e80032125d4b0e7cb49913e489796a50ea00000000000000000000000000000000132438d18d942e6dd3f69d117abf83c2fa18418e5145cc43b3cb8d18c873935e41279a9e13596f2863be7aeae9b73d172809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c0000000000000000000000000000000018b3102ce91af86cd10162d3a43e488a0d7b7807dfb9624c3cae76f342e86f8ef1200444a57e2ed7f819828357a6dfe80000000000000000000000000000000017137b470f3c8d1a03e7252e18f4466c9ff809408cbb2043d6b226ae2746d890b267ce3255114b2e073eb66e93c55eb200000000000000000000000000000000054dc1c981c9166d0bd3a54064c33f15ab856b240770ed44adaf9f32d4429babcd0baf2c5b8a1ff80728e9c63e806cd3000000000000000000000000000000001897595f836342ab54bc2e1b72f433bfe3b5bc989727de48575abe89386aaad9b1549af3ca55f39feec14355b29dc9e33521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac68000000000000000000000000000000000467f1a3093c72aba4c2d9e8171057cf88146eb32f38db0761a5ab2027f2213c89e12c67a338b4b342a73384109988d2000000000000000000000000000000000ab26c871d140c9c4e0512afe9fb576409ffdcb95417f8c6cdc0d964011dfb1e745045766bbbc08ff7dbd6935934bba300000000000000000000000000000000183488902b886200e63465098be87a905810b2e8ebe0364316da798e423dbb267743a0d2e3d93303623fb17df0e74ce30000000000000000000000000000000012c7e79f9ba36cc47762139d191e6625c850a03d5b6e0648032d1669575704c91e48a9ae432bb3553ec66e86e082de689c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000012ef4988956e026a79e5e904ad3d7ca56793321d62cad46de3cbde8570be5f0ac86d386216152b37053741fe342de7c60000000000000000000000000000000014ff7804312754d23b251a42aea65207695d4df65cac4f87fc96cb920843c022f24cd27731224db751cfb621886249540000000000000000000000000000000006ea693105a1b2afc79dbf75504c256c519f927ea0d79ddd1997a49638a67151dc81b84473208e8078cf71d456f2de0c00000000000000000000000000000000122d367c147c91517679432d3c7b56f2d529d70040109f803b89a04fd8540a6c565354ae420e1bd4ad4ff61427332629dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd989664000000000000000000000000000000000156e7f8f1412cec315eb76f10c92143157313b8eda0677a6c0236de5fd27e5660ec3eb7369f1604082c59e1aa5f94dd900000000000000000000000000000000018ca9f505a88ed2bf595fa9b55d2356748770af16b35bd5db448990b7d41c3aac53aa490791f7ac09d2f5a087f938f70000000000000000000000000000000017c76ca9ddfcc26b028928364ee35829c6e57fda40773a6bc0c259a1b3cdea715c664d7bd0340192aaf7dec7ad20a2ed00000000000000000000000000000000082a255966c4f9d0ad6bd3d88b136cb2cfca09ed6ae378c914c28ff3338a2cd466cafd839f3fff4a30b33ee56e684f4e00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0", + "Expected": "00000000000000000000000000000000075c71e21ce327a97024c8ab5fcbef4fff76260a4f8c8489167166c4a85d25096c617cceef73097a4bb956be3eae8b780000000000000000000000000000000016270f3ac86c0ec43b9472499c4d845eab488a34ad9e2148c72cbb1db13623c5dbbc8327c47ce596521bd1f54f119a660000000000000000000000000000000007ad4914ceda9fbc161121c818bd05953836a581dcdc78bebcd82ef548671c899581681c908a337618a445f77c6b7cf400000000000000000000000000000000173f401cb78024e844adcc88fcf0e52d32de134f6300216ea0da7747752ae3ddf4d181b8d266b53d1b699921f9871425", + "Name": "matter_g2_multiexp_83", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f40eea638a3d4fec417701c936d61c5b233c9d6b3e94ba9addd0fa0b20adf9f8e07c6b629977445c7750acfd18001c0000000000000000000000000000000005f1ca1ca9cf67c33e3ff174a65adfde2db62e74edf30b5b0156d6b9dc86dd619ad8863c055096685d611ac015ba884f0000000000000000000000000000000001683dc67710880b8af76b464291c17fb0ee4eff3f648ac0772f4a777025c8cda0342d8f5aae3123da7fac57b965685900000000000000000000000000000000143d919ce2cf00838b10fc65374e770bec3db8ecb17d2db08b6a10ac38657bb109f54c1b3040b661c3914ded6f7eb80fa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000000616299341f2921adf083d1190c212a7941bc0d9fee50b05b265f2e8c339fc3dd9f94607631f485e394f5a7d71ae73d00000000000000000000000000000000006b2f12e22369e8aff45b6c05a2bb72a706dc46a5d1393aaa9e5a7931ccff33a5df2967189114c3fc5dbf69d080e39dc000000000000000000000000000000000981e1b119d04343e075a80dfc189000b4cfb4e321575817aece6009e6b3a6233d1409e8e584f0ac9caaed1f43e40d7d0000000000000000000000000000000001ce4693e8c14032c35497e0f9a586a4541d8a1a68ad014b0850753a04215be2bb60cd7c2fa9be4f4f09a562d7b29f3892073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000012d634764207dd7a0201703f855365f7750291c810ff292b3e8dee682d7d8eebd6d6f3b3dc8b0c9e25bd2860e031311b0000000000000000000000000000000000eb0859d79fcdef546026fcd380f5c936e64a5665d73f56d92c03dfb50c534a00c857c86ec43275ce69cccc0b53137f000000000000000000000000000000000131bf000fd117ef722b33a1cebd28899fb012e1113f767d0ed46fdad82a32e4327b883fbe29abba1bb7ba3ecc1cbab2000000000000000000000000000000000e24ef1e44029366ae1daf06524d8beacb2b99f60f419cc2ec1a49013b79fb7a4781dbd37785f32ec67c0a28d61a3cea2117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f500000000000000000000000000000000067b6eda41cb8da47a424a02a142e2b98b9c69e7023cf616040993f41507798882194229cb6572806e82e9e5eb837b37000000000000000000000000000000000e38693cddf130d3645fd60ade780db84fa700e5bfb74ebea49cc95ab001bde442f363b4e4c61f683b3e67f1ec8c2af80000000000000000000000000000000006d593005cbccc55c5e336e19aded70da65a7fe42b6a85070e491a4ae54e18ac213556a91d5d62786b6d4d1305525a76000000000000000000000000000000000ff86216f5388114dc06deffa7b52a273b22fe0bc8d50804b491fac83e13915c2dd1b8c2779a46b5c313c4e1c05eb2979087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000262cf4727703fb227bd7fce6cd3f25c1897011ab892e79fa47446711d6867ca82b9b95f129f7ca24dcb60ac75173d4700000000000000000000000000000000136b5a304807e029d0a77b2ed505ee5c920248242f0f95aa07e9bc2e13d35f6f67451d028dc19d26095b55cdc2fae4fc000000000000000000000000000000000b511b2e19da7bfeb183f0aec91bc7db3e7c913f1c282e12d5d2f422a49e7fa78a5f35656dc9c980324717a5ad386dc30000000000000000000000000000000012eae443aae59fdf907bcfe3ee4366e252bb57e268fd569d742456f348429f009f67bf92f9dadd401104ccd2549cecc8255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000016a827938d8b98e3446445ce23f48a7ba0d634de826dd1ee3c71467eb57bd4c24e0d1b4190f47bd841183baceaa2593e0000000000000000000000000000000011d360e0c18b45ace82eaee902475127d8f18aa4a2ec2081a453f1c85ffe3c59c0f7016f966574a7c51bc14f1935568400000000000000000000000000000000186b5d452c6dcc1ddb4f47b07e01b6d64644f6d01cba8498c3059cc494a68bd25eef35cae05885b9f2689683e65161410000000000000000000000000000000000ff826e5a62affbfd6d2062bd329fcb561f287046870b8be461767759cb0d5f1ac904ecd1f136c5ccd784bc11088233e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c4000000000000000000000000000000001010e75c52ed0acebe30fc588961c849b7b6298bb8d859f9a9401737c467921c5e3cda101cd4e38e4318233d12b6c7b9000000000000000000000000000000001884db518fbe4d621403ce00521878c0d419d8cf476a1dfda59b7d3c7af2bd91058bbbf54ac0c5cf9a217beb78e3f98e0000000000000000000000000000000001272cf0ad917738bba052e88baf88347d60f63f5b875d604cf0531c1ba7d43e868bc70a682b7274067106f611f08ae60000000000000000000000000000000006e3236f6a66bd37af4be230d4edda6eaaed661f206ca4852d3004b5f358f184d80be6af81c62e5bc8c88e7a1072fe21fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000004fd1e2fd0d28db08224fa7e880abb8c48dfd0e488df4d2ae5f6649f448193acbe943baf22af4b12fd763e3e4ddaa08d0000000000000000000000000000000008df68f276f356ade28500eeae3b755c9af9b5acac5f5f60827b5b2044b2405129b00e5271baf9a80847d3b720026b3a0000000000000000000000000000000005e683d1556f513e6d093704405f312687c3b9e2de3b2840fff32e88186c89b18d1ac558d960b1196594730a9bc107480000000000000000000000000000000018161f8d23c394d10ba576fb0ceee530ebb95a670f2589d84c0646f693086ecb7ed80e556f3ed9434d7fa488430ccf430e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000017860708943449c2227c0f50cf1274652dd32e999d5f9b1a8d672feedde15e9f1af484a7b9462a62dd745bb6d3c7295a00000000000000000000000000000000064f8cb707494f82ffb6374641817a466af65f5c7d83cc2964e6cb8efd021e0c40934a3ffbb0d91bf8a7a616dbe8d220000000000000000000000000000000000eb37cc9d56fa0dbf050b557aaeec76f9f6d0a6c448ea298af78004e41ecd8a1df8fe8640e77cb76b593ee17658326ff00000000000000000000000000000000092ab597967544fda640b145edcb3ae6c3f027c2111dbc282ebdd48eb93287ae4729cb30e45c1c8999b3a45b099dbf0ca49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f00000000000000000000000000000000124fa4d48ffc5732fb21d465b559e995891fef98370a1eb73c9264988f75caa93fc134fde7f93c794582ba5cbf6bc685000000000000000000000000000000000b71d012abc1558e49831f053757518643ae04f79234fa92023db9c5483bbd872d24eb87a78960f12930094c4f8fb70e000000000000000000000000000000000651cf0016efea086d98e5bda8e1959e20e4947e302eeb021d196897cffde3e2c28f783521b2a28b8de1ad1a131f5e67000000000000000000000000000000000555ff8a930cc11d320afc3e0635a6f93da1487a5764d56636be4e5803d740a73d84666f6141ed5ee6b778a463823fbeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a7000000000000000000000000000000000974e769869719f0ee30895df837cff50d47382461c557abc4b8806b04776f401b76a5e630a6ccbd3484980d03ff58d300000000000000000000000000000000098157f0190e6bacbf34c20310f6471166750ea1b235e46a5fae313f90dddc799f21548088322910bb0fd7e41beb23450000000000000000000000000000000007f00d7d18719db9d91e2c32f51083b42c4fcb43c38087f86879ad6bc99600d4c395586187d26d041ff49dbbe517fca2000000000000000000000000000000000510cea4a7463bc5882d0cc25fa967a0b02072627bd57f9a5863fe5255953732846d4907fa301789bf02af9c1b25211c53961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000bf264c0b7bf68c595b89453ebbd7fe2e64f4ae2c7268ad51f4578c35d48040277f3dac9021997af02e492039348efaa00000000000000000000000000000000083a4fea41cb1e02e5002259f5f7b335c81e15cca93cbc884dc1b08ee981c55f2dd3c0db1a35ac9907435edd7f0ba625000000000000000000000000000000001468e508a02ed7b61f752ac38313345338d2b2d018f719f391c0f3fa1dd1602d9476f3d8829720d17021a459a2732e96000000000000000000000000000000000629edb2530c38ead8717b289c08036c12630cd8c9ae875111749ed893b8cbce40bcaeaf13df4044147bb665ecc2319ea04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001635830ebf227be126e13c634a84f3649d498e0999ad2dc73b9c7360db120dc2216addfe18c00676ed185efa1e789d8c000000000000000000000000000000000471e3cfca449bde0ba2b1e2a5b63d53badcb34da3251313190a35daf694d70ba385976d1f875242386fc74ae0173d18000000000000000000000000000000000986cf3f1eef587bcc70f66f25c60f353e6b15bd105fde9254487e9b522159658d0fc6b6a8a3ea38c27865f1ea4d76490000000000000000000000000000000015a2eccb9c10bc273cb712ee04bef01a11e486bc6a4d220a0f653582af6ba1bac0b5108250626ddf126f16f4015c9d2cb688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e22527000000000000000000000000000000001213cbd035615f09189171b3e22630d72df2df93fa8c14427bb00c34f5b55bc8d1b1a59404bed6549b582537a397eaab00000000000000000000000000000000161072d8ebec2841f0f34cb38a3e1b2094a597640a34178ee951e5c993646ecfc3a4c0dd753e7e76f3a6da5a091f9f7100000000000000000000000000000000077e9c95b6c6f726902392c3a16b5cc71cd9d4cec58c00eadca6091e45bc095e53006ce8ac8827565e867531013821950000000000000000000000000000000018cdf909bd9f38e57ee24c0f51a5f9f703eb3d190dfbf75be00969e9e8f8fee331cf32d93c3a956d12f374f8752c2c79cf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b6000000000000000000000000000000000cbc27995eaeef2bef14919d48a008a0b0467856f8a6659d6e68e47a2d9d41d217c5913aa1d67911325dbd4fc38e36eb0000000000000000000000000000000010639740654bad5c4ec93f2496f4dc54a7642bc92ed03372ad4edc5fedcdfcf37158d3f02279d4e15078e9d5a7f8b5df000000000000000000000000000000000155ff4d6dfa031b0cc2f57df41c1e1b1c81bf5a5cc1e3aa93920e93c2e2e7a71b56ac410a87855400025badf6dae8e60000000000000000000000000000000018e637da048e7e84b9d1654113978fb148a54d86e1d011d7f5a86cd4f1e5bc15abc5b67d00129f53c0c021cf933f399c528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f3650000000000000000000000000000000015d8f6e47b8f07b3e07ae0952a7c8f79519ce2828e3e26b284b8f2fae7432b337de17089b5c32f0081ec6c6916f2f53f0000000000000000000000000000000010ecfcdb02cff772db667266cb3f99f1dc28004ffcadca7a9c64b3b5853c09b7793ca0aadb155257bd64fa7bccb390450000000000000000000000000000000011096a52f3272955947304ba037e8b3fce6b2f07f2352c08d5932f4d2306ca511a74dc044d0f0e1e260ff40b0fac5e0e00000000000000000000000000000000130facbe0c1c6d077e9dcab647a44b049a1aba3df500bf27d1c268f71a59635e702c9ee1bdd68fbfcff7ae5b3e6bd83bc32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1", + "Expected": "000000000000000000000000000000000b47d58802579e662f34908a4060becd40434e4934ff58790df2a69a759223ca29f42e658ab475cb92bd9c46566811c7000000000000000000000000000000000091d3a4c58a669d3bf0377abfe28d1817168b2a86375928d95df3459c83334669a59aba95ab2b9957d5ded0bd8925910000000000000000000000000000000005aa9c3fe0067338675099ee32f93bc8a5e9ead94b120dfa391651da40cf1ef5ff79d193b0b14b5926f10660aca6c11500000000000000000000000000000000058200992b111461f4d737533301734a5c3731c9f2e7b55e18887ebff4d5b74dbbfd23773606f54cd6a930b85b89aabd", + "Name": "matter_g2_multiexp_84", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bc3eba5594666cec699a47fa13ee74722a80b4e5a1b28553de37c3163f2fdb8b801e35a19f6d99cd207d1642c0d4ad400000000000000000000000000000000104b334afeab36961824611f0fb58bcac55c9d36604ba73e9c8809085c019bd967cae7147c766df6565ddfcc0cd5fc9700000000000000000000000000000000026c3a6a4ace8638bf8ba7434b59c3aebd4bb274cbdcb898ec6a547b215f32d10395f3bb85a9eaecff5ef6d5ef2b50e000000000000000000000000000000000065bc419f9496b5e81ce72a13fc1bdce4738c2e3faacd80676be31db65ba3e7941ea75e370b6d6c0e7b2cdcce80a2fa14f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe000000000000000000000000000000000e7f1f5ead0f212439b4c47599581982712d2e6ba056f36cb04033ff5eebd81b5b41b874a78aeaa98562899418ab04c900000000000000000000000000000000095e45da9a4b2578cedd13af71e289d0067ecca1f09c014a294e0b250d1e8243ff98a9761030ac855a9d897cfe9fafdd00000000000000000000000000000000030b44b150d1337a3ed6a77f7b6332d7c8103da1aef0d445ff7467b4863e4c830fb782a81d01a6bf97e8d52bf333e78d0000000000000000000000000000000013bb76800375a45b847a96ef6edff3fc3c30e3d45bb4afe04230107f6a1802794e1dc23431797bc5e79e0d5ac6357eee4b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000602e0bd3d34415ddd517a73acaed5750dcfd68b633d51003edf79a169ad7a3ca2541d7a131c317c957a9597a753b5080000000000000000000000000000000007a964539081fff51e0ec24bb71257f6a1c513fb0047aad84b80180b22133246a1f62958ead75e4b2a68f973d17f1230000000000000000000000000000000000f48fe0f5b5a95e48bde4d8be1b2f352d748c1201b064bc88309a709b285f81260d4535d3e1dc7f1d6ee23ead35abd9f00000000000000000000000000000000135b480fc8a72248f7a4898fffc6c18b7f2f5b1af5cc3846610c299c8da252fb71190d9f761e037c6b47595bab6a03e56765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f0000000000000000000000000000000004773cd2884e52c2551c1ea0abb78fa17caacfffe13f18b75588484b8bfe781e623075bdf827fc82c6ed7e1d4d62081d0000000000000000000000000000000007e6023fc0e409bfc7d0b7ca65fa0e8d88bf1b4c202a8d1f0e1c3479b0963a646d16795fc5a128a54e624357050fed4000000000000000000000000000000000039f6eaaf99bcc9f4d8fb994a040af0d29c37960e9015d4e48466a9e554da30975c5534e76a1f08a55ed8ce7375b70100000000000000000000000000000000003d2b097d4afdde83a01cf2b4f9d12f77c8e92a8cadc225d40f974ccf385ae65bed1972a365d55e24231d58abed4395a2eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa00000000000000000000000000000000047b8c550310ae246e43b513d39e507f1dace7bcb543a49ae0854a397f62c408ae3632c94d172669ef3e013781796ecf000000000000000000000000000000001592914e260afaca80c0a240426c2828239ad5e256a707530f49cd65e9da2e4bb14a7d6d5978f52c04130a0d434cf4ca0000000000000000000000000000000006c0b8448ad87350db130373778d414deb738d3be97fba25c816826f59e3e926f44956c2e2056b7d769278cf56cf6fe0000000000000000000000000000000000a42d716fd83071bfa014a9b7af6c164d494f0347aed953bd2c1c97ade087a8bbea9f53c507fc0b22d520f28cc5d480cf8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000018026c9f6c86219d0be88955ce0afc3cb637b1c3a531aa2722c56816d368688181ef2fedf1525daec6d9b1651b71f27c000000000000000000000000000000000b40b15bb0621209bf9e33ebc27a7502d90fd3af62a1bb8f54a874a14c105df34ae34a43fc3805c1e4817ba30c048ac7000000000000000000000000000000000465262367e30ccc24632d39bf3af9cb160e97049d855176f665a185c138d5c529d11e53e56c65506e3e30be7b48c6730000000000000000000000000000000009485991319a311052d883b45911be12cf7648b5ca104ffe77594472f7047c803b8e9fb753b98645e630b9913bbc947e28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fcc0aca0d873cb8733ff7e2ea02b3736b737821af2db06ee6508e161f6159f9d944372c513a03cc4c9e30a707dca0930000000000000000000000000000000015c3774f4e0b30c9532beaa2f7f9b777f8d46bfd3888d6835f4a5a046153a98062efb17f78807fa17b3a995ce720c0b900000000000000000000000000000000083d48e01d2fb58244861a74a1261063f7d20b412c8a44f9945fbe373cb4b9a7ffd4c4ba4054ece0abddb6c14c013ceb00000000000000000000000000000000133c4976454b7be427c4c2ed437bc2e882854d2ddce42d2f97cd3fab1fcf60c3272aaa123a0cbecce1a774946bb7a8a0dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000f6ae7de1dba3b3030b208f61d182013231c4666f134b007b52d36bceb6f3cd77577be7b11abc097cf9618d351d61e270000000000000000000000000000000005803904e3e640e51900805f930638ddd8b86cc1bd50cbd32a142e10d85044cc52ff769bf1b40dcfb7269c913d00b01e000000000000000000000000000000000e6997b1f8bb649c56de5c4bf9968d19712abd22fb7dabee19e0aebd1b13adcd3e8b202975b4edc917d93adf087fb539000000000000000000000000000000000a32384fe03280962c5f575b47192e5ef3111fbbb0a01bda2db1e9733471f11eac0a37df8ae1a891de311770c482c06b0adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000531fad86551ac6dee15fbd62cb13f38d8d5c89d23a031b9977f110efcf16501534757bc5b93f0250ff02d6cfdf2009a000000000000000000000000000000000e6d78343049a68514271fc785de053ed7f50a7774b87f264c42e03e6f8f86285477f8cc57ae066ef0fde237c8d1ddb30000000000000000000000000000000013e313484da4d6b85634c5306444bdbe45d7db823616d72821eb64a2bb5f352a4f7e4273fb6557039fa563ce1b091bea0000000000000000000000000000000009a40a984be66c3442fc8946cc42eca722187dd819be9ab34a9c3b4b0de7de3d5f126c175fe84c51a6f09e18623214f9345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000008a76db551280cd43d4608e9fc629a021675bfdf9bc5a021546b92f3734acff1e97928850716b94d15b7dbcc4a1e0aee0000000000000000000000000000000000b2262872c268782e8f27ee8fefe0827d45131555e755c0a65a7c8b4185269bd621412b653348d7c1111d681f38d946000000000000000000000000000000000dabcf0f847045e01ef70ceaa32455f4c962e4657b840f97a1cff7cf5073cbf4ca8ea75a4887076f155e27e8d7406c95000000000000000000000000000000000a9c0ed94170eddfc485d9f1a770a8b493d4a59bd7156d6cd4b95b55bffa1b597ae9d6fbe529dc0833634d75906a4aba5ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c00000000000000000000000000000000170ac69c2bf9b48715f445524cab902b18ce6dea7b258481cc59986ae61c8fcb6708b1457be299a6e2f6f34dfd936fdb00000000000000000000000000000000107e855593b6f3bd2982a65167ecead47039065c9ae6e1bf963f81d441f0ebb411eec4b3ed1cff73044f68a4c114806a00000000000000000000000000000000063b470d158ebb4828e875c3dd0ca29a4fd2cd2af356233885a871cb5b77402090f29709c6d6a78f612c8ca4df2f4119000000000000000000000000000000000db75a60fa0b425b8cd2c955e21846ce3c407cb3f96c472cb412498143cc60212de0dfd0bf4de53ae3b345232180b4ad853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef38000000000000000000000000000000000389e79154f627463a7966252deab10b5e809b0c2a9e90989c56d4076b834e2081ddae1c02a9e01b71d96b772766fc680000000000000000000000000000000009109473c7aa614334fde410951a69ac45967f7550890e01b05279b6dff394775dac51d583ae0aa82edda18ecc5e66240000000000000000000000000000000019dd51ec6783c1618a7f12298e38cc75d4fa32fc31438f67eb15419a2f0e9d4b5f70ea59b69e531c868475cada519569000000000000000000000000000000001121c7a6cbbb54d5e30a11a73c158237dedac46385aa15d93592a30fb64fcd94a674cc77afd21a611f704734337905596dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d000000000000000000000000000000000178670fb06f5eb8a4f182913f46f66147deb3f9f634d620ed55da2ccc88895e75f76f55b979e1ba3c3db29710050c7bd0000000000000000000000000000000011adec68ef139716ee081db7122e911ec5a6e1fd7f681a96a713dddc2b742b6e7cf7485b8f45e7ebdec8b1174c02eaf100000000000000000000000000000000089dac9a47cbdfead8536d6cfe8b94d316123bd92ddf30091e16711ff4651c4e2d8dcaf6c72bc159d7de9fd832c6f5be000000000000000000000000000000000c40b871930f0c6826a943a229112f8bf9a3b7d7e07139e1a7d99f97601b6ca8cf3638e0265743dd732cee17fadf996721d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000001040c4cd3c28a752295b115fd80c8ef0e538e1a3906e0d326e46585d633140bd6b8231f50d50c8e7a9018a625c4bdc530000000000000000000000000000000008b966d9433bfc3bede4ddb005cd0c256a168437c31b8ecc83e6fefa6f4b1f2bfd057c78f82bb76279b74a2f7de493b5000000000000000000000000000000000c0f75db7a17e4b712666b16c31b10bb935e7127eb9a0e59e35ec54814a9de9012210ff1862aef5f765d4f7f673c4962000000000000000000000000000000001015e63589a8b56aa643a79c5a433dcd8f4933a10edc9921bcaa7098af435f7879a40868e25d1ca6f7852800df29c2eb3d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000000fab22ab380043b01d312004057488ffc958168f8fe4d9c86af622030121e14a46c4308d711d5fa9a414b9ef75d51ba300000000000000000000000000000000047c738fe5272e695f421ed463ce0d6308e05c23b6bd0973df9b55ca96d89c0771a45d53b4d17f30d8cf08edbf94490c0000000000000000000000000000000017bcb3ed735e5a302f76002ae82f4ac74889fa0e966f0fb611fa6a6a09440bc923f447eb6aebe47eef917753b7427efe000000000000000000000000000000000b189d5b64578eb53ad850c826082265e506ab620a9ab9684cc2a53718f26befc35e9431af012306a6190f144a9632bf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000049fff545ac239696c995eacc560580a0328af07376f5ec819902e30d5e7e40d5fe07295c4ccf54d5c06134370373c1b000000000000000000000000000000000bff448d5ab544a8cae0cacd216a6b6d48f0abe1b4bc946d95c1a8c4ae44bf049c3b572675a5e20c1b4188fa27a867a70000000000000000000000000000000011dbc52baa00712f66def2fa8fc77bcb07431d3285774e2517dcca65e611f07aac265856cdef0c1637def44c382230fe00000000000000000000000000000000090af0898dd578123c65d1f818c3f33866e4acea19aeafbb31bd8da029ed1daa2d7ab3b22147eb32a09021f7a78fdf2acc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3", + "Expected": "000000000000000000000000000000000d52fcbe9f1776477a9d2149ca55e0651fe9d098a67209ce2e7d772d4901ff2c70be432b53dc94886651865a81ba8c620000000000000000000000000000000006b54871379e2be969f86c72cda9acab9bc99f73de987f17ab8b25c63c55ffa2cff61b87e8c30d9f712afb62a2b9cfcb0000000000000000000000000000000005652612b19c38650d1babd4772722ae2c560e2914f2e246725cea86dbe1275a981a592eb55077ee4b7c6090e84d2ed3000000000000000000000000000000000ee37a6d42ce69aa67cdcacb19efc230c6c34969a2e081ac77e8f9d45128a6e8fff923c7647a0f168fee18342bc6d845", + "Name": "matter_g2_multiexp_85", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f114e56d10dba7945d125fe1ab7871d9510771548d8388a2aec8a481de92572645b73631f9a60285c3eebcacb3bc0f5000000000000000000000000000000000667d3f31955df11e4e7896a1856fbd4e573f1cfc906b3953b5806a5d01dcdb96009d9f148156a3828e822435f722c5e000000000000000000000000000000000d7740ae776eb4766999f5671315c8965ccc84ff71757e361fbbb55babeefb96265c97df8892acdd6a9166641f656e62000000000000000000000000000000000166529d1a76ad784557384cb971728dba298baacc2f2a39ee36516bc7a761e9a7c29e385cf5784efb9f6e60e998b01e864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000133e0b08430d9318d98bcf58b3d8f51c7b717fab56fe25f434bf521f830c7d4247d87d3df910490be2ad38adaa8eec26000000000000000000000000000000000e15afaee4f1ce6290ddfbc13cb887e540efc3fd8150dfbf3a5e7c759ccb8f334ba26953c7bbc43b5234b857159f6722000000000000000000000000000000000e4cc685524d42ea5e435afec7b3d7d025e93ea06407a28c246a39dee8ae77514a0bb2d5031f7367d658027299762bea0000000000000000000000000000000001b231237f7b0538d51adfa4ff92bf313507996cf5255f191875970ed4d946cffa8620b44045f4bcfd8f89baadd331fd93064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f53900000000000000000000000000000000084128f1848b2b244e4812eccba01287b9d07e85450459c8c42b01180bdd843058d9926f39e2fb5f610651a00233e31f000000000000000000000000000000000055ee70765f2cccac966dc08abd4bba0d004b379a2c6bf188f300f5d413f84e77ca1d462219bfb820d7f585b914a52f0000000000000000000000000000000002dd8f1d1cd85a5e6ac793f7e1e3cff887204aa4a5fed92f2088c06eae95842ab2c04d30d56f4b0fcfe61379e8e7c6940000000000000000000000000000000013e318f8b6f4165a8096c76ada440154901de42d69c38e66d9df4ffe5476666ecf7068e7163f29f04972682c43f3b0fd5e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000003415c8bab713aa18d3f0d54e0101ba36793e6e9dd3471f8eed9a15e00d8312732a9ce88b5f0c30207aed92eb173ac680000000000000000000000000000000008a7e145e9576be8ba2fd980fb1735a2b73d1bf5f3e108878b721b6ed8378b5e0f03ecac179a6d148541096ba483b40200000000000000000000000000000000029e5554752db8bb87d58275268f24ccfcf3e0923744d57473d54a72e2cccb847eaa8f3bf638833a934c43930fbf30990000000000000000000000000000000000e0f2ead2697110a132c4ce1643b97fc652dd0660deadaa4e0c45e7ebfa64cb6a6fbbaac7c4e2b725beeadf6881ae5893f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000014325fcc087aa108f152b42759cbc02cfa24e7e7cb995c78ccaa9a283ec2029c08cd747d599e0685d365ee99eeafca880000000000000000000000000000000011da603d3a1128329af19e596ebeaa4bad034c59581e9fa2e42a0260032f84654bf5ce22ee32c34eed7515d7fb0fade0000000000000000000000000000000000189cdb5b934cc1ec7ea0cf4b8158a1416712bb59c1650e6d244de33bebfffd3691b499b3ff8255b1b513deba709f7d3000000000000000000000000000000000e7ab2b279d0d5933df25d8fc4faeb8ca907e7bb8588e618b92737fcb6959380abc205118d2e3fc128b89a2ead5ca906145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000895b6777e677732c74cfa82d5348c4c8ddd63ce10347836f5140b9a64dfe631804ea3be8e20bd4438f5e7fa14a121d80000000000000000000000000000000002422cc4781f007f732239ff9eedc126777d6ca0f0365dd90bab6b68c9e3d02ce726726a6d30d7d51a1f0b45aec1854100000000000000000000000000000000048af8a79663aefaff77a934f0af3a09ba02077c13a794ddb88e5c679ce348b3ab0fa217954ce1422f4e212d1383ebdc000000000000000000000000000000001190fec6c510b0b16e1505f737b25dc2401e9fc2c95bca92aa5d6e93b284b766bfed93a80b137e5fcb339983a86acd41ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000014fbf4d005f43563fb7408d1f20f672c8983120c66462ba9156b64a287e66960fecb41ca129b6b14466a5a0de91b81c50000000000000000000000000000000004fb283724950174d60f64af7bc8a7d059431332c8f17769df33f6607d72633aae3a8d595cb8d5af3f8909297844b3a0000000000000000000000000000000000e187476a19280ad9f33a55c50f37f765e343f92938e247ec9fe099c7f3df65e24af14885539bfcf3efe3bde9f2700ce000000000000000000000000000000000f086e6b9e845fe3b0c5100f82bc8aeaed166bed9fa4d34bc03ed86342a997101c508a4c096c4f67cb5791cc1a1fdb8187bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000018dbe48c54347635d4b6bc17ff5ba390a73925f1b180d2c516eafc0936aa9bddaf7317cc0c211fb2a7f7bb096369a45d0000000000000000000000000000000015544c177a4b8018ed60c2639b43236957c2d995fb0f32523654584b0bf052e0930366a93406e1ec5c6d2edb955e811d000000000000000000000000000000000802d2cdbc5e15b25c77ded4bdba087f1d5760e6ebf9549a37f3314b1e88d3d6f58da9d8c6e9ef85028a271b83dd6242000000000000000000000000000000001577bfeaf213ca8b0983cb178e9634dd18f74baf02f6ca31b2e3b287d80a32d4cf11afc71df09ca5bb0bc8e60fc7ffa968cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000007c059044ce0c15bc527b19ce85cade8b1d5a9cc6dd304ce9a3c461e631e17c4feec52a0ab5cfab6a2270c75f73df86e00000000000000000000000000000000076344286cedc8c180e3bd762f12ac08f0ecc51293b9f9b8e7c0056ceba1bbb6fab4ee39cf559fdbd601db6c3d201199000000000000000000000000000000000bf6e708d0a4fd85c7566804e19f21f7a00bcc3bd7135f6639ad30aafef2ed1e72c84c8995b0e59738c2bf1e4040621b0000000000000000000000000000000018ff3d0ade15b690b6e306adaa5c10796b78ed7f8a984f637271cccfd39fd17c1e8288a11b051ca94de2a9bd04fa96d7a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c500000000000000000000000000000000025cb808922f6deb0bed979b80a675d9324cf25c53de373534d771afd919a182af9aa1dc26a2d0284887121bf4d6b6470000000000000000000000000000000018970aa4f456c1b203817322df2e222516bce67ff9ace069599061c6229596e506c0286171f3551302e45b7d3b69a39f000000000000000000000000000000000a57d0da60f03fd4a5664546f9809c771ab6188aca5102c31f26b09950cadc26b0275417ddd9c4f4cf29794b739733cf0000000000000000000000000000000004ebf2bd93d7921d8bd97ee71cadf91145e064a33651da2604ed6fc8e08b1b8305005f12fd4e6b68b7b6a3b5cf123b1324c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b000000000000000000000000000000000c8ff29d0333e3f38fd8af91ecdca49e54ea5dced71b60d693b1bbade99ae668e4f994f7a5417a08a8ddafa410d437f300000000000000000000000000000000078ac1d0898a9e6cae29fe6b50e435e5f543d0ee233346728c46d659c4338295f27b42fc4b2851ad5035feab2bea8871000000000000000000000000000000000b3a566d2ef4467f21c27e4a3dec99a26c304b32ba1fcce8276a8518383a7de44de5b4011ba738dbb8761e67e36115560000000000000000000000000000000015a0aab8c3d51fc3fc8aa35dcd07f8a08188976883f9d3ccc87ee148525f2115ca46726a2e3c550167c169977b216d6217ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a00000000000000000000000000000000115589e8e1440edcfe72c008f6e9cdf13fb7baaf70aee16166e7f32f4651db784f4c5cac15d91ee13001169fa777f0d00000000000000000000000000000000000f86710678b01c8f648bab2289e8f90648d9470cb13d5145ade526696d22508a4a59164290586c2c000dfc55b4a20350000000000000000000000000000000019b300961b40b0d9fe6e292e9357d04f0483ab3a8cc6f8f522153c51d22de8e96a812adf720d13ff7d05d1e68264638a000000000000000000000000000000000a80b61ab051ce413ec838167fce393f88c8a25f403bdf07cb60391fb15306a5271a7042d36f7c46b5978106a7b5293c4615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000567c33d22805319418cb1ea7eca6205a6c44f1f881c03e37bf3c66a1baa5153473cc73b8c25d497b0b0057ceb0395960000000000000000000000000000000014d7a2bfeea6a746e709f6108eb32581ba38a617e4450b3567c77a992988d91f4da31b209286f8e9fd0d7b8628aa6c4e000000000000000000000000000000000ae6c9fbf0e06f2e38e91699cd21596ba90f92f6022a4f3c7c8a6557b7e1331283bd4d7a7d31d77d9d7cf70a2945ea1600000000000000000000000000000000066b8132c73e1da8ae7fec9169770a188b686f223fd0306441356040bc9070f34a47fe1bb8c94de9fd7606c18b1d2b1dd38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f7000000000000000000000000000000001460040d0a19c37fb0736ebdac0324d8a38c94a73fc5f602b7ea5b7255be9d4b6ffc22fea5043d948420e9ae3476f56a000000000000000000000000000000000b37c0078ab8babcefa8874c6cd1c5184d713b976852d087ed84337073fab3054899859d0fac2f4351bb75ee0e534fa70000000000000000000000000000000004150f3b98e6166d9d6b0388342042dd8eff9b8e1239f479330b64c5b316f98fc7bb401b737efb87e1f6663ca4efa26700000000000000000000000000000000043e6131c1ff621fd6f8caf0939487a927550343e24425ada33cf622de757e6e75c9affff9f04373a954557181641617364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c74590000000000000000000000000000000009fa1754bbc957d2a8317a2eed859457073571379cc7c6d65bc6a0b5829f8142db77654eb98a2bb0cfa5223a27d756cd000000000000000000000000000000000cfe8b8fbbff7507d3d74f4f550b4c85e19b8929d3728a462e12b4008c79014103153c69ed8dc6b743e1b6fb4720bad00000000000000000000000000000000017ca0c08c320c12502a1dbc841425694bde68b7806eddbb40702e58ed26c7e112f9a821a6c67afed174f51896ec2287300000000000000000000000000000000014d08df9cf825b07a387642ac9959e8cd15ea8e752231a3047fa30816acb1ecb79f1755484af9a98b993f50128c2bf5031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f000000000000000000000000000000000273b64e867a9111e257c9b32484655e4d7e676ec50f174d9ebc9fc4262c037b176ada941dd8c1abf645e275dde04f4a0000000000000000000000000000000008a63b9604e96a5034d92e3790411f3112c2c7cdaa056f9f1bdfc0b164c37fc9f58dbb566337132cd1626f9ca2618f800000000000000000000000000000000006a661167c9fb6c26bfe0a3902f309fa683fd22729bfcb433756182e7e1a406bf44ae1d13ef0228534881daa339394e400000000000000000000000000000000193c6c5ec200d225c43c6e37cfd15e16e49b7d87e5515bb7b4c918903966f4f6ae0d42af6b98f6efdedc9b0301fa1c0f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f", + "Expected": "000000000000000000000000000000001403c7e3059135ebcf5e752011fdfaf66e348135314f3f4239b066e1c6192ffcaf89bad4228fcc2be19a64f4f5386f5e000000000000000000000000000000000aadbd8d0e53d5b409f7fa508089337bcf36212a3f613b37a95757793dd6b0ca99d1b3578ad8020d46e29c9c4197ea070000000000000000000000000000000019e43bb32f92ed187fc32d9dbe24a486e38316a3cec0fd7f7c19b313af43a10fd63738b78e609e04a083de6761d53a90000000000000000000000000000000001490da7d36ff16304b27f6e57412975497e9f3a6d35cb162464bcf69fe141d34ae27a33afc75a2802eb120e90d4897bb", + "Name": "matter_g2_multiexp_86", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000038ee0c2c409d8832437ea450ed705589c82791b8319fd0ba6fb4d302d3c5b73ea0521a0253716e5810f03fca2e9dc720000000000000000000000000000000018c9d748aa685bf6e11e6e4b6ad2290ceff59c8837a088b41a08983fb2c5ef077adb0730b298c5df9aa02a820a19a4bd00000000000000000000000000000000015d248426e362ad2489c0c6a567d80b22d54d6a79e198198a771fae4c4e97eb317da9feba8eaafc9460ef45b1a5e5690000000000000000000000000000000005a2342412801cb37911a04d7ee3b1e5d3dce2a06e0658d59f2ddcaa9ba32804a1ddbe8f4d00f4436aad1346ed1ea5344aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b90000000000000000000000000000000019d49748f05458cb9b316e433b0d341e23bb5aaa724b824bd147596761c11efe8f4940eae09e302e563e14e96b814f4a0000000000000000000000000000000018011e7ee4988da168adbcf81cd14a9232edacc06bbfef0fc78dc0f96b5ac86ea67be8661442b5ef60e3889f3137182200000000000000000000000000000000175a2ae3bdade6551b23656c16884ba0fd4247df4ba7471cf81022d7e224b23490db153c8289f95467ddf9671f8b6cf90000000000000000000000000000000013c58c0f55c46bced98faf3865e3b6a836252f252e97b6d2a799b574dc569f09ce33082880a4d0c3b8a2c7c0d4c30eae22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000ee45d5689a8ea6132d5ace000699a157c1cea3c0c98b38d504153d64fcaf1702ac7a1cb0889539d6b15489fef415aef000000000000000000000000000000000b320e0cdedbdc1fc5733488e6d2aece6386a030adc36b0a69dc3809827319947049f3861c2edc859797d30a3689322b00000000000000000000000000000000194096079b3a1d6ab1080dc71bf6d5734bc7b5e7f30bbb0f9b95c9495a6bc4adf76e198fc66accbbbaac215a8932d8c5000000000000000000000000000000000ec07be0cfa9b3d3a64c016471d9e6d25228b46dcaca6e197be00b9ca5087162c35f1d6326a3cf83f568cb06da8c5220895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f6832000000000000000000000000000000000a7f3a3fcf2e7b0ada6d4fce179bdf229454002f1271a39d5e99daae72da549c6ccfc7c574f35bb9784100675c30b1120000000000000000000000000000000000fad14ab095fa09bea919ada313727e7aa5aa06a1cc7746d006e3eaf70f79c5e4001a8a8de03540b45e0598b22710e00000000000000000000000000000000015345ade62c5691690c181da09d8f39c1ead42046987b8c7c975d40690a286a816f8cca519731d0ca23349c54b30d8570000000000000000000000000000000019f0a32361bb6ecd8b1d87c2e15d31c0e0cf995eac9facd5eca123c0799c465f156b0142d98e0f315e9b3595974a7b824c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000017af46e78904915e348734d2450fc6e1938bcf002989f855082e3b4ff3366d81ee8d28293609c3c3b11568668b1305f80000000000000000000000000000000018b0b3859763c2654fc00792a5193b7317fa5051bcfd15ea42be2fda0f43adf322219f34e54b2446ef73a4562151f9a70000000000000000000000000000000015c23509a1b324c649ff878d004ab5f253d041670ef172ec4dabec7a525d5ddb8f9f62f383e3f71b0e9c98532e247d560000000000000000000000000000000003a38564a55fdbe05b047281fa153f736edbf48c901749005473255333590f967171a6fc88751eaf57a5335bbfb6ebe86d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa00000000000000000000000000000000100322c4a92c136437714a6586c82a6842027ee218bf1fdfffaf95ce47c9c8b6c8f61115b092dff81ff2e645d0a7a4340000000000000000000000000000000013a91ed8629acb5e770683015c3c248255d673d4b2e6c96334d1c80326d1a8b4b655c81175e4a914a45fb37c1f178bd10000000000000000000000000000000019075c2eea3f64f42be82fdb8f83f2c68c08e858702a0225d869143c0b017b76a7a40d809116ffbdff6700b288f5ca3b000000000000000000000000000000000598ee9ba9d56400b59c7f5977aef1e179855a37179fbfe97b95f19137b6034568e5c7f616943b4aca804272955d42334cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b138400000000000000000000000000000000025a526b137aaab5ac1b5f8179a18b06feb7c905b4a843cd55e31b7464c2b6d432b569e9bfc3222511c18255102aba5b00000000000000000000000000000000090c20c9f78a242e52daa339d5cc1c3f35aff7ab802a3e4366597db8b6ca43d30fa0fe8d9484e49fa4fd0bf5509f19e6000000000000000000000000000000000e928b2173e32e5fc9c373a2a6f126e1a3a472c01a5e87677be0d29907022b9a7dbec3340cfc89e67377ce472c2d5d4c00000000000000000000000000000000147b4eaa2dcee39b918b7cdf24483b29466120677e5d42b51353a9b2fa207bd911d9b391142a13a212d0ab38adcbe10796111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c00000000000000000000000000000000007790cde9ff8af2d7597d33909f00963eafa228817de1ebf4233ef0831202700b99641318186aec80ac913a1b1143eb0000000000000000000000000000000009d42ea1386d8b019dcd26068ab156f399c35b7d492722a20da0c915f7abe44ba688d9486f4bbb44268542c5a49168930000000000000000000000000000000010611f233bc1c4af0a14e1d1b945c91c077ec3dda592e2f852e2de41e09331664e1a92f9a0b7416c50327bc943a17b9e00000000000000000000000000000000048614243262dd070a754f40652b96a03326fc51273dddabed85df0654890ff38e0da7abb8190e4ebefdd6f78a5fec509d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d0000000000000000000000000000000008451d24fdc873c61db44e57372d43c35a2a8098255f9aad3a6b244913b86bff6444042e391685b1244f009c5ccde935000000000000000000000000000000001177c2da9972a2b96afaf866f97dc149482fbaaa93e194803c09c8334c2c7025e08cad4f7898959a57b07a545ecf76ad0000000000000000000000000000000016f40426cbd1f0f4ca5ae1dfa4c3960a6fbd51a1b5b24ff5d03fb9911e908406a0ecf4f20a78a280d24dc9bdd1c0799b00000000000000000000000000000000194a8c55f549da1842cc3173f3eb7bfd70df26b43a3059a3590992e34fb19b2caac4149f64d442965e166225b9013e2b11ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d3000000000000000000000000000000000a97664c1d7624cae0e969c728a84130fe260581305435ff8ec701cdc51a73977f58c891ecee637eb6b7c972069ebbb80000000000000000000000000000000003f4ed6a9e9f4229f0fb35394bbc10da9adbf4985d4453da64eb312ec88cb15bdc189a3b5df1af3107a36fc001ec92ad000000000000000000000000000000000ac552c5f6170a70563fcdca8e0c6a7c6135af2f9d5ae6f60a2c459d1be4cf76ebcdf9bcd891db8a1e2fc905a23a97b4000000000000000000000000000000001734a46c99e776d1ed4b807f5b313562e0989ad5c67dbcb961c134f8b7b7601c23308839569dc224bdf7c370c4498303b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf440000000000000000000000000000000000a523182c886671435ccc75cbc78293274802c6142465acb31a1809e43b1d656ed9c808068de167b1ab126ed0f73a4490000000000000000000000000000000007c4616080b5a002fea3589d54c7510884a3ece705d27dee315851746b1ee748e8a08d3516d8c6afe1c0482b960a9c62000000000000000000000000000000000dd1bd9b4b9c140aeb97887a0266bfb5696813fea034b78bb7d0cf1cca15b5bb0ed92a97841c8d8cc614f7721b8b7e040000000000000000000000000000000012a41a8941b6f0e4c87f8188718f9bc75305d41d6f4441eb9682473340fce0bbb463e1b922d3af8daea32b8a8ac9c3b4a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf241169119000000000000000000000000000000001763ab2b361681955735ae00b69f26e06469391af993c8dc6f2e1dffb52ca01e49d58d6e2249e7433ccfb5ddaf8fead40000000000000000000000000000000003858f3bb01b2393aa4d4d7889bdeb0bb9bcde0dcb9b39c4ffe0fcd0b865baaff75b676c715be275929ff4303c416e0800000000000000000000000000000000086d64bd1302b0b3a620b87ac29cac3d9e606513ec8b47898cd852bf552c1364291aaa842616b92c8936e076e59451bd000000000000000000000000000000000967c9f59c15ed02c9b2da6e76fb0bf3d445ba849010afb7f9c994b1ef6a05ad577570d4adad043796eb90e51537ce5187fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c58945000000000000000000000000000000000e6b0da7b406bcac2dbb90fbf430fda6442cc2860ce633ab84404dfbb426949d55ecd72992da1a2e8e1ce229b599232c000000000000000000000000000000000fbe3a345ffc8fb85cedc4b8dedf9d952c41b4ff6f1c7ff4cf91b2276621969d905aa9aae5fc89bc516f96b9bd1bb3c10000000000000000000000000000000018c2a7fcc35099c41bb851ff66abb047e2af9cf4fa9fc45f030124ea2c7efd26e594abbfc7a7f258c8081a3a80d15105000000000000000000000000000000000a27cd33c2121c9c542e27b52a13275ef7e81dc0c6ece883b65e71d2bc3e7246f95aef7c6b41eace382a1400568cf298c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000158bd8e6198d22b52efb7f3b945668666e1190a4a8e70307ba5c1b737316a8f8568092f219f683c0f53f56f25745d4e600000000000000000000000000000000097e64e4553371c81a9bf553ddd9719f59b329284eca0d76f023d603c29a034d123ab777cf173c5f2bbc66412d69d4ce000000000000000000000000000000001298cd5501e136a06ad4fcf87a75c0c7b96c73e844863b74bf6aa581a0ea98c2b1f608c668743a3e37ad5ca2074af9340000000000000000000000000000000017ff9f1336d7f2152f17daddde9d3e1679cab8120ed2c0288b0908d4e2099a08c9bc6f79425f004ea3ac4d684abff6dcf29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b4989911700000000000000000000000000000000115baaab8f0331894da531ab557bb454e2003010ba1dc1d96e3d983d49b1312585c6d4c43d85dc074b23b2fb28c8a1d6000000000000000000000000000000000db1621b721c8a54ece26a355b190af5f3e1dc1b43e0827a1912ace651cbad4b980e77a4c3566aa809157229b234c808000000000000000000000000000000000c594e0ed3f7ee55886e251deef9732aea3de11f094ec53907a843b755add8fa5d00779a66621e615ba7772ee821c4030000000000000000000000000000000004e80aeff6c4b85188903b4d2dcac4f94f7cb4285a38f94b0becb556d83dce8735d1db5810b409d45a8dd1b9a6dde29c125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000371a74468ce2ad90e19b7fe3f57159dffb1b0422b32ad693b2fe6c45c5d371b97a90054095da887019d25c1ee8197800000000000000000000000000000000010575e1ec9a3e609ca086ef8bca679c4548482d9e0da2e51878158ac8e5b29d824c31ad7ff642041e748efc50c2514e000000000000000000000000000000000ef36130380f1e84b2f462b5f970abb8535431b79813015261015c6d7e74f038b47504de01794840d93fbbb4b386e17500000000000000000000000000000000018419e85fc2d75f007d1e0e02c1975332e03d42c3b41c50c3538c3625e702161cdcf8913babd2995aea7566ff15abf2bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6", + "Expected": "00000000000000000000000000000000125406a942ae0119575453beb4c093d2696d3bea7bc031d7a586439197f848e1d5a82b925b4e96138a3460eecf198ffa000000000000000000000000000000000befcee6bd1412c54674a3d519dd2813b87b18f2ab3375a731197e9f539f8f8fff634f15647e7fea3c65b93594343c2000000000000000000000000000000000011e4d432ee6babd502a9cbbb5cf4839dc6da6176b6bb0ba51d99a3587465f5f3f83f4d4cf2c7e6187de93b859ca61d800000000000000000000000000000000168509010b867aa198fc294a5879ce14a51503c1d0e8fbc02ec08cf62afbd357ceac24b633bd0fa99f83dda92e10724b", + "Name": "matter_g2_multiexp_87", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000070a0d060c6e9bad0d1bb15417578daaa8b7a5c984c4947dba10fd874d93fd1e3994337c87799d78a674087678d9168f00000000000000000000000000000000128985b69d5d6ea0ad0b19eba7c2b430f5242a7e89626c66fb83b58ca7cb65a479de4b2fca6886cf55b8cfb52394102a000000000000000000000000000000000bb0bced708571662af042d18956b5b7d797b61aba70823618682287deebe69bf1f9a94ca4059e0570e25a39e60b9a8b00000000000000000000000000000000193f0793324dc78c40f356dde030b632feeb1609a1bd75ce88f0d313a0864dbf1f5e92826870866ab9b3c98cd1c12aa508c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000000a61a310f90a5ffde617b78f784b2e699cd77e7c3e7c483a2ccb768f94d68e59a2a4521410c22ef6f21ba589ec3abdcc000000000000000000000000000000000e6568c83e0f7e459b27a28e5bf954983c5dee478a009c244da16041e710ddc67479cdb3da6f47e7203fedb8f765b2490000000000000000000000000000000001c5cf6b948b85a1c426fe932cd87605f1fbf6c932756eb1bfb43beaf012bec4612d8dd0840efd4cba3f5394beb65112000000000000000000000000000000000e02d5bc20c40d7cc2165a21ab37c6e4eb71322c01a43f2085f93b5b02bcabcd668dab90323db0f9288737d757997631a0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000165287d72eca1ecda5fe16a555245b0a34a04beaf9177466bfd88bbc675442d206e70f7a2063b6ed0e15e9406232f5ea0000000000000000000000000000000004c0608bd7e01e65a15716b0c505111a3abb0abac3efb846e05e8db59c063950dcee052f04d1c4e9e492bc6740fafe6d000000000000000000000000000000000de897f7ebaf9089f7e198ee41e1efd7d84fbec7327799b9293a489965cd36159442eb0dc1f79f6b1f122f592b013bb30000000000000000000000000000000009774586dc359e5d20486f00dcea6ff93948c5a8b74058645d1048fe46ae3330dd56d85204d328f43f15e674020f353ec252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000438ee51a560aa419ad6ae45e1014c38b7c43f1f6a512bccc2d4f10a35838369b71799fab4b6a754fd938c1a1b874fc0000000000000000000000000000000000c1491c85965c0b74d08f5866ca727fd30bf641a6ada0ab8363ff01916c37d10b1b7eccff79b396c587d9beca2c826c0000000000000000000000000000000001452f254ceae9626443265ba31a1a750a425f2a7789e69cde16b70eb319c744a6221e74a9e2881c6bafea161d29638df0000000000000000000000000000000011bd6a1bbded174e9cb95d74492f7b07a755339a6c40f2a1a76debccc0f3a32c7017ca4e6679fb2c038c751f19986f526d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b00000000000000000000000000000000139157c34aaf70cbfaa82be655281b085e37d6406df4cf8e291b221394e91d9e3cf04d431f15436064d0bfc8cbe13701000000000000000000000000000000000353fcf6e587e71e59d8f05d4085961d37b1f62694dd5c7f40efb5875b90459dd66c4d2d6c01a40834307ae9e82c2e08000000000000000000000000000000000a4975c9872fd167d0ff4cc80a6ce179b1e6e1eb21c8de80321451b1deffe68d8a13db26218f14935b64af25d63644c10000000000000000000000000000000001e8a2824f21cda745a24844ac0336994fb18e30608ac61201a932c0a5a58f1acd56cbd9353bfab4944efcf2859ad5915c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b1973000000000000000000000000000000000537d7a9d7d9dc451cba4d50630caed32e182cbbd95212577b8c2855c327530e447a4f3d73c7d63fa3ad5111254c9ed90000000000000000000000000000000006984b32955fac4ad3c0d181c81b98534ebaddc316d51a40baa1028bacd6a93a20d4bd6cad6a0f8cf7ade96bcd4d68dd000000000000000000000000000000000720c392a663884ad4d8daeb7279ac41717ea602108c76519da13a45a77d2acafee842828f5ccfcd786bf7ea88afd01600000000000000000000000000000000081f1d3e37ebaacc11671bfe1670ed65ece2aee0e3b5d746a8d618b44bd4b7dea905eb8e958bc026a092b2bd5a7b87cb11009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a00000000000000000000000000000000005af33731879a574f39dca99c5c1b9517eda13121221be77a0c1bac82fbf29b37889c15a9d32531a3f6bf9137ce82dc000000000000000000000000000000000c62939f00d70a07a85804cd97fd34b9764565bdba225cdd7549729ceb9735bf4d09a80ec3055c483e1e24b66c41e403000000000000000000000000000000000e415677988c9d4656e59f77c608926c83028f91bf4c0634120b5f774ba07180b98141ffdf727cf9d0fc7a4cb52f4393000000000000000000000000000000000c9c37eaca857151a0c4a49b079f2f061e6a8ebb77e11eb32b29227529562f8dc8e2646e25469491eec5a07b11943f203e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf00000000000000000000000000000000015113f8f9100cd18427ff48038e1070fd835fce6c0812b7bafa679ac733c80bef56492ec3ca08c1117bd0edf19cb26f000000000000000000000000000000000789cd90c0be1de5d0b359c030d4b9d8aef93951e26870e37c375b9e7879cf277971a05babd319a3a6ac53f00f3254e40000000000000000000000000000000019b1cb91c9a1b1ee49c3837339778806bf0c093f171c92c9931ad43e35fc61cc08dafaf55b7b9e0f49dac28a12bcf92d00000000000000000000000000000000066c7864631333226f191e313436453e59f48f91d42e68874fa4da45eeda1f6f7f6342204e64e124d5ecd861f02ef4f00444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000129d887d694be0ef2f84c343a9aebd0a2aaf19a4e78586470351ffaf0b1309593363bd9c6e7fe39a6e59445d935414ef000000000000000000000000000000000596d7061c2399b6a9be7d4d495e58c0377b18db1e45cf3eb431d10cb8b15ae42548a86a26086d57b1a71cb5857d7917000000000000000000000000000000000cce7181fc87dfe1bb493043279a5d93cb2d980eed38dab2ace8c9fb335c2890447434d80df6e7c95729933ada7b9d8f000000000000000000000000000000000f0e1274ff70bc6d3f1d0d5b251ae528ed94aa3a1b9bbdb260892bfaa6213892071b8a6407abe26105b2f81df90569492035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc7360000000000000000000000000000000001192050735b114c19eb2bb9aa01f04d1fd9bed4df877113a14f7fbc9c31acc10db3ed0e0d15d8433e7408bc237c985b9000000000000000000000000000000000a8a66cda780790311b56836fe69479c7b94dbc6c82ed5886887dbb539a40390ebb2683c04078ed105e639a2ed8732a1000000000000000000000000000000001678ddff677b99011c73e0c9875b5b2ba063170f4d565d261b4c6d3263ccce0334b5bbb7ee08692568037fa96782e48b000000000000000000000000000000000ae15f79ad7f790f8ceaf7709f4b5da71642da0c1f7c442eeaeb165c7dacd8a4892fdfc8447a03a7c56e12513499e43c4bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e8260000000000000000000000000000000007fcb5ea5358074d06b64c5f46454e682dd9ac2127374c83f3ac5ad46bc5fd2fff7c5a80ffc669a1c159ee8c9a01bd37000000000000000000000000000000001010ada1bd493d6282ac2d3582480f50074a02fdf412c63e93c5857974626ff464150c20bdf23a87692bfe69a075eeb300000000000000000000000000000000086bb5664a8738f02af5517aec4c6db47653a6d76bd4b5e37ba4d8b27a7819e82e6a4c7ba4f8377e06a5878e7c0bffbc000000000000000000000000000000000be1463ab76e468e47e1711c158dc9bb10d1278f5cc676cff937f60ba457061bacdad7b8d3286f40219963b147cce4bd369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c3329d1e1c76b0bcc7ca3766b2cc5ec8169690f45e0ea3e37b7173bfd6c884921c7523ff25391a85b47d5de395ca63b00000000000000000000000000000000081ff066c008d5a4c893a636d24e9752c6a06666dcbf80082167610e73a32d70aae3e58c88ffaa27f05260b86b11f72a000000000000000000000000000000001178e88c652d257888cda1c0b65ee2c0636184194fef9e6ae3791a85417c43a31fe75893773ff3e7b4d4cda9eafa8de40000000000000000000000000000000019657ec4604ab5e8812237a28e5ff320a0d728c60c541142ffd87fec2c703665638e5eebc33e308d5582cd043d08d788ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e120000000000000000000000000000000010789200f69d8acc70f108145804b62b521a30a04176c449f52bedff5975ad7b273aaf4a32f8461ced8e92b2229e2cef000000000000000000000000000000001178c36174cdb783b5b09d419ae4a154512bf9ce07368521d1576b2f1bf39f98be29bf533bad16ba9d96aae621612aa70000000000000000000000000000000002580f2115d1814667b6178b6bffca6a4d992eb66e9601c0d21e32a5f3b69e3f85e1205c877b2dc2696a0e872c5bbc6c0000000000000000000000000000000002c94d7ff016d57bd5f589971344c6499577bc2234e18e6c8dfd7d27a205442a4236ac54fe279d1bbca76467530140b42cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca36100000000000000000000000000000000108b6fef7396ef71b46339d421726f83b08320599d66da18234011720d2b524d24075a255d2771f1ae904958c50a9046000000000000000000000000000000000723d5045b65c0887da1bb01d874714ac86d21441119a93a1d5758957215f399f5ef1cbc00558db01b295bf0cc988cab000000000000000000000000000000000994914a3df9d3094dab0c0c41a45315dce5968a99e6171fc609ac9e50bee5ccac771efaa04067467e95709bd924973f000000000000000000000000000000000ac746602f804f52e9a485c30412adf92eb9af3f6daa8f23b974339a0ffa6f5aa1b70a80a9f19cde2a69a4b7251ecf5d60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff790000000000000000000000000000000000a2ffeaff148dc5f70fcf53e7e8d7b6100cd6e7df5b3fa4aa33bced243f15b4f77f48d25f74366a693404b6ed7d3075000000000000000000000000000000000f3e1b34ac8fde4caedf3d8c3e24db02de3f91487db300f09c779e7e4e96ae55229288abd946abcc3a8adaf18a0c89e000000000000000000000000000000000166a68c5191dd7f9d44eade2ef1a9b522dc062bba9c55e2ff03aef400e5d2765a12816b4ba51e10bc21e06113c8ddc5100000000000000000000000000000000109c00de20f7e827375c1841348e684fdb248fad116e9643dbda8be2bd06b71db264e9f2c40dec2092e7d518540a6d82652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000014ddb61173359514226c150a3343576b04fb1b06fabd8fe2f921fb3b90baf5513447c107f6d2f96c8b03274bfe451dca0000000000000000000000000000000001d1064860f6c4d62a282147308e80ceb0c5dd62f39b3232a231b1b287e497df31cbc5a3905a7687eb2f24447e50a395000000000000000000000000000000000859611bb3962955f92bff861e03d07bab7fe1f69e90c6bc7928be8d1758c9194ff7a52b16472d04564607b742543eaf0000000000000000000000000000000008a3e8396901a205a071aad06ba9812207171f33775eb358de4232826a5f0ff50ec3e137b1344b583849e8a5b424b46676a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20", + "Expected": "0000000000000000000000000000000008c9db83241e7f3ae6c2eac8fdcff5f2d35318e24c3b4130e9bb7048a3b84a52fa3f222a8190121d2a5b8835bf911bb200000000000000000000000000000000002db79cbcbabf41bd8c715e024f4687bc0d058d76b8dbe58ffdb80918212ab6e9b35256fde583c0fe903c34a4c41ba70000000000000000000000000000000019f37d05f5c9e65c6f004e1aef03ff0e1899f0739c9cc4e9038e18f9d45678388454d144495b2cd993eb3691bf3e96f5000000000000000000000000000000000d8e0d7715ed71291729bf480f5fee7ae04264015732677488472bedc0dbacf8b35eef7adcce196e3bba9cac0991be81", + "Name": "matter_g2_multiexp_88", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000064a134260b753af73df3764ab662e3b1bd624c8f3248e9bcf7676d8fb0825ab85ea33387d4641c81fb8ba3757e0870a000000000000000000000000000000000d67eff1936a395cd3f808ed7fc89f8b6a227c4849a6941d4bf762af6e41ae41c8114aeccc2565ba01fd902df530df1e000000000000000000000000000000000110ca2339832e7a9468844b94b3ced0c9216654bef1c8a5cf66385a99d5d452f978bbb7fe15fb477f56753488fc909b00000000000000000000000000000000173210b548d1b98b926539049996713f53108cd2911105235c1d5258360d5620d330951db67219ffaa304a67fd6219f39f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f000000000000000000000000000000000f7c3795ac3d511f93a3d85e65261e4c09cd316787f74ced6e472a3993b7b5b0ce5a7c91d99559a8e0791f712cb4e1700000000000000000000000000000000018eacb2c5fa9221881c6311256a69c7616748deb3235c61cc11412860450151a25e3d6a220bb23e0b3e3325044fba68300000000000000000000000000000000121827286873ad31f58cb3889fd01cb7d0f91ff1c241295f6ef2dd0e8aa8638b63a7e6061efc2e7ca1d3579b4868f0460000000000000000000000000000000003a57315175d70880b2b53c67d61831ab066b08d7ac68637364ab1c1f3efad96d42a3cf5189c45012c1f73a1b97bdb4c1deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f10800000000000000000000000000000000180648e5d0bf727101417f515cb9578bdde3e9f6c4176d516454ea7c32c1712610cc8bbed303bd1afd48f580ec11b77c000000000000000000000000000000000d6ffa9b85d69b67abb77f5c8bd776eae82d1cb055d2dcdea31ac66b1825014ec7f7a2aea320ef9f6897c9aac8c0706900000000000000000000000000000000073214fedbade28cc60ecfa4e1fe2fbc05f3d71528aca315312d50214f680956bb9e0fc12783843b00b3f4f0f52efe2700000000000000000000000000000000128f87e7da7b53f28944aeb26ef0f6c99d84038af51a1d242501ec84b5a6a8593ef1a0f6b523478d9fa12e36c2fdbe694b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000001443980d7450af1e19949fb328776cb7238a9b26240cddc565aa9d52c5592083b1533e8103dc07eac80e4bd830f209f000000000000000000000000000000000afdbea7f1cec534c03d3269d50017372f7ccbcba9f096fdb2754af4d6b4956decbab2b0afb69f97a03beeb20b4ccc31000000000000000000000000000000000a83dfa3197dc65097601457a97d0df7710e001e90657b150e289515609f13997b454167a7589ef218893309460139f300000000000000000000000000000000029c362244510c342358130f877de947acad5a379295f3149d5c713274316e06a169501f889e4b9cbf86f10b9521c1bb124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010a83f13a185c70ca3f724dd84efcfa3ec463d7c05360056f8b5304864b20025b0a82c9d542ba08b645e2334f176472d000000000000000000000000000000000848a6a18bcf64d083e118190805d68f7ffea8b5a66e0807b9cd3733d31ffa5cc25dbfa6ada604646dcd8dfa622e08a30000000000000000000000000000000009962205c0ba43e5101fc3d5353f429a57a97bcb84baa0942a7e7facdfb0d032b9307aed8bd2ac9094a2e5b1460db7140000000000000000000000000000000019b1012661a10d31a4a73d0cb31f7eec0e7be729a42baf560c1e90a9124fe8d5fe31ecbb6d4954dba7d943a7af773eaa5a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000e7f02c1d2ceae60f314f51374b338c329f2eaa82553c3fc1643c7f1910ca24e277f3d658f552a47f780d4d9e0ac5e030000000000000000000000000000000014b6b56afc4afed5199191ec13dbeedd797f14ed493c25658a9658f031ac8d43de12e6a8c4b1671c9e5ef78da1a55e2600000000000000000000000000000000194d8a50618ff55ba3fa5602d41cbbeadc01a348ad1484c5e9aee5fb7241fcd9018f436e3c6c6dc64beaa241513a6c8300000000000000000000000000000000052681eac4bd59e160b67ebb27582a6d3ad5286d652787a0e160026607acfbfc5b9f38b9b171375079d052cb242b87fe8c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab87000000000000000000000000000000000d96ce83d917204e674ad9f5e5728651f5f23df25236b0fe769be48adf482ed8c36ad9c9abb6efa3719bd35324bd700800000000000000000000000000000000107f55ab0e5b60dbcc0632c345a9e93818014d7657b264031709275744e1c6722ec63aa209e655878a57704ca6cb3bc10000000000000000000000000000000018d97fba324431fa28b8845d94f62fc9eacc0253134b923908f06889d375405b51610ac21a75bdfb27e3533dd4debc22000000000000000000000000000000001667856804a5471238ffd64bf3bf266ce3a2351ebc68265674bc86ce6faa8dd50a3dfa00c647fb4265951b3a9607ab99f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff000000000000000000000000000000000fde2fd0349e7a47a9b6858014d551aea569ef9802629bd9520e303ef0487c9d2d399682ac16ce6fa03adb6f4b478fa5000000000000000000000000000000001858ae58920dd0abd8ad94d2f9f946c53e050fe89c61f62fccad37e17f8723a4fbecb6b1be1e3cb853f045d0dca8e53e00000000000000000000000000000000093615a7f9d12e92c90706a47abe9620c4db41e95e42e478949745d6b73e021422e40b969e9e34263778c8a4d4907445000000000000000000000000000000001006ae7963b1e1c4d8c2c85175aca958758fb380019825b09ca3f728b5356254ae4fc670aa29812320b921b48a069df622d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000000ac407b75ea77789748e7607b5d6edb1d891875aeef2802715ddc393818fc8cbe82cde9f96377e3ac60107ddcda7e6610000000000000000000000000000000006e63e49356c38b816736d1d7c360ceaaba875c53c98ec68cb825962531855dc6410a125b914b0ad99f6f4327f5450890000000000000000000000000000000018ffb4ac95b8ffde112c8bdbf07a1c97b1d30a42dd4a97c82617698617ceb169e8702437ff6082a2ae387b462cd86256000000000000000000000000000000000497c4b3788c4d6c9b4cd8b3d3569ac4b4332b2f76c5f03f112e089bb79d33152b2469f7ad3eadb8b954775aab73f47de816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000003e16f2f5a2fe15fa02b6217aed7dc688dd2670c09c02791cafeccfceb7d99ce826bccf213f6a7c6064687519f9283de00000000000000000000000000000000095e6638ac74815dc451b3ec85a6a8cc18643b541e8be99052ff6dad39c971f2e8bee976ab2ed5e1cdacf92816249ded000000000000000000000000000000000f2703c08b1d707fb6de215de80b53ffbf2ac48f3dd059d2a952b1031189248fad27beec5c8591ac93625a08e3420f0200000000000000000000000000000000024ae36412ba6f2fdeb0777b892f1ed7bab0527879d93f7b71b62f437f5c1ad1f04a5a7380ae5990a455f11870c7208304f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea0000000000000000000000000000000000f7ab1908c6d4b152835f950b604b55fdda7eb55c6b90c05e98626ba7cd014683bd3e219fd0d5983e9dcfaaa5d389e560000000000000000000000000000000010b285c2884dbdd540d6dfeca704e00839337f12d2267f6a3fc731fa0f724cde19e268782b4b9c2e11ec3aef9a72a6ed0000000000000000000000000000000014a40cc55570e8f45369bd9dc622e05f03989bce6a98a0d87f4fa7add67eee3e2ad9a297615dde05e64203e86153ec230000000000000000000000000000000007f2b6a092adc595e4857e821579801301396321d4a20bccb3296a031d74a62bd79ea4ea094d2e545943138d2fc930fb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d200000000000000000000000000000000108834a685455dc0be10aaf54607a06100673140b012ef23a16d3df204a81dd8505d62ca3e0278a2581abc59e0fbc421000000000000000000000000000000000bca7130de9896e8d6858022f24308af7ca66fb4c91f38b30f717c5491996ef4cdb01f4d38a730f9ba9ca5af5ad1de7700000000000000000000000000000000007d60ded107a06114afaf741dc8826f9e14bac6014eba26089c4e31a73b0f30c4b6e22533ac0db7e73621cecf753590000000000000000000000000000000000b538213a703f7a0bbcffb4aa8ce25ba2a538bf599d3c0251f5e8acddfd596c9912d4cf9a1bd8d3ec070713328ca992205edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000cdf0b9bc829cd8537918d665e5bf344d309678d01ee80c71a6d6efb45ee8a7beca35bb5ee046e0a3fac76e1771520ff00000000000000000000000000000000014e5be9dca2f8ee4da18e5ec9c4caa891dd78acc47f553af584308c72988435b85ad21b14abf8421bdb9e25164d568f000000000000000000000000000000000accdde22a1c479e47a17b8da6f1d2b7f780ac278c68a68090e5402977d897bd734f5af8164118d613f480c1f65e5d8e00000000000000000000000000000000029614458afdf6b572bea02a0af987d178c43650ca1c80a297b1d31e259aabd3e2a2c8e4b2c044466924dd6e5e3483e6f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b0000000000000000000000000000000004e45cc43d4d10ed878e18df156062c799a687b8e6beedad9fa6f66ad855cd053af6918e234ff9a43561da7e67f3dee10000000000000000000000000000000009c9ae47a76c199c93c38e7213c8d6c030cfca709714c703839b9ae9b65207e83486f9c8c16373e2b37756f3fd4355fd0000000000000000000000000000000001594ce9c2e229491b22317452938115747515ce62a0d49f4dd12667f5b3e7b541b3775c9b1363cc185a539b9f7596330000000000000000000000000000000016bf68e05e32168c69ad67331d7bc88a6d130fe8aed3e42eddfeb1d92add266eb69487b246a3ca961ea6ac0a35f8da78059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b41900000000000000000000000000000000080d165c57354f87008eb97610d4a596f180e48ed3190779591a0f7e07278f8d2fa6cd21d1b10e6347f11bd9731fdfed0000000000000000000000000000000008d5a1e66ec76743ca366be80fd1cbd5efc9112dbcfa84ce6c44e8df03140ca5f07d4bafc6c6ce5f2f190ede55fe8718000000000000000000000000000000000d0e1d2e5ef384a4fb314fdce54ab7895f895b3bc669acffd48e92c6320024d4f371f42071fceea550c8cf68615b00960000000000000000000000000000000010beae4ffbb68cf6e5d0683dc0629411ee14563f84788d50b1c8755b0b06092cc0f0ef7b55a39d51945b5178e374f8e047b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface00000000000000000000000000000000161486d422462460923bd98834f0cc270982087697747fe40eb9153a7923d48eda191e4e7a75964f18f1df9365901a360000000000000000000000000000000017ab168a4ec81c8db4a74d529670fe6332b3870004f696f3a143cd1a62abd747d94afac9485e5dc19b0f4262dd379c990000000000000000000000000000000001e9cc85f03039ea53253f0fa2420012171fe39ed8696ddfbed57b80b73476171e59631388d75fe43aafde52aa14a64100000000000000000000000000000000109a5d5449002f4bdca44c0bd141175d5ca1cee449302f0314fcb5f282f022a7a3cef77f4e9fb515107e797726ff51d767d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c", + "Expected": "000000000000000000000000000000000aaa5de171664fcb45439b17a024806ff7e07d02294e0592ca74752a5b66f3365d1b49d6893b3bac3b8b0d10d026e48d000000000000000000000000000000000418354ce1820ecf848321a07ce22117303e5a15169a9cbfd141fb4797de8871d84d577e86270a9cbfe31c088ceed0250000000000000000000000000000000016884caa03ea641e0660a790975d77c5bb03568f873800d0559b69e3e0afcc10ddf031bb5c25c46f136f0791bbd3cc8f0000000000000000000000000000000002bdf659df76cbaaec030448e8f4bbd6b424037a8dfd7c4b8ccaa2224b0852c168f49c6f45c04f23abc85b8df21953ce", + "Name": "matter_g2_multiexp_89", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000062bad6816308f1c8c6941980caf71929a4006083dd29827902ffc92ebd9b14f1ef662f3a0125b1e74dabd039f9106400000000000000000000000000000000118e4ae76e2c321a5b89eb19b58f58f44e80dcbc7bd6d619579da40e1156aab32fe81df8eeb1bd047f96d65aed8b3b6a000000000000000000000000000000000c8c93e1beeb4efe52a96e5d5612338721e3e487c13c18b02475f9ccd8fafc2c95101aed291951f2031bee5216dba26f0000000000000000000000000000000016fba44e9aa39a12ae27e3c36de1f14e3f37ffb0ceaf5fed2a0d9815eab02c5aae91b254812a8f3a2e3654cec01a341caaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c0000000000000000000000000000000013716488daf8586719c52fcec80d35f17d4c595b66c7f2138244f3c8cea69b819778bfb50e49ca1d092e57c51674fca00000000000000000000000000000000019cee25c4731bf48602ceab23b5fc4f764993443e3622107b4c33b29c23d1b5916380431b7ecd94a0ce99811fe6dadba000000000000000000000000000000000562b28b245b7c1ee531a320fa0f4e12d7c171c7e3932ffda6cfebb123fa7f5993e5ed5e7b7d295405e5031b339994bf00000000000000000000000000000000180c4a8158a26d34123c870bc694382352a8e4de712b650d3e45e6baa16d6950ec15d3a4e032c1d1ae8fea18faa6f3d8db48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f20000000000000000000000000000000008472d40e0505d6b8b92500e8e9711112048611fcdcca2377481ae86a7f6da1571f179183301e2194a42dac3873a3ba5000000000000000000000000000000000e2c5b61c050a8a12298f76b5f15383e72b90b001fa26889b67a24bb374b63c1e00979b05450e44ed63e72042af6d46e000000000000000000000000000000000e8723eace9c7a72b3e6097afc9bcadde61462e2ee03fcd5ad1b1c0dcf39f437f80530c2a1c5e6ecdaac14e8715f02e30000000000000000000000000000000002e21e0f451d035a5257fb09e9ed17b27f0994e6d85ddaf8d33153628adb194c97db17656351c029be4d3125bd29dc22ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f40000000000000000000000000000000000dce49634595869d7858e95a301bcff8112eb73dca8a22042137456d6d4887998a541489ff09f8e006176e6beee4e300000000000000000000000000000000010835f7336dc49e62706da4ef21d8e3173629b16742c317c1b397d4f17ced40a56520ea63557d7ac7f251568f4eb3a220000000000000000000000000000000017446ebe659a4510a362ee3b406b636bea8f381503e51ac21031c7cc92acd23046d62c2f32cda01b680c0f107142ae7d0000000000000000000000000000000006ef82deabd8983ebe4255d8e06f4a1b3585c057b2a1ca3c3e1cf04b582b65792e9980e3a1735a8ad58b053b16ca03d036d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f46000000000000000000000000000000000cd8e7422ee179a0499178c3848cc4fbc87fc25c8c882f036a03cd9d3f273f7f2bf71bd3c9cf5e30c42b1ee6e90b36fb0000000000000000000000000000000005005a471d77a35e922b6d6a45b13a90947c2b31d8e7a2e4b6388265b039ce23ed958495dbf904186bef60fd547b941c0000000000000000000000000000000006c337380065eb8a5f63cb20fc61a9eec4ccf0e23c4e0f231a5bc4d765271b9c5697bbde692b4828ae22ea12423ad932000000000000000000000000000000000f7a0080cbe72a6e6473f66ed729f58683a80815a1748e52f7b67a6bf2846b7df8e7dd8599f87fe63706e9823bfe00d21a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000c94834474ac91547546d7d179b2091e33c8812c1b582ff186e69b63011177283a74b549aa342a7f3882ee82ad8ecc03000000000000000000000000000000000d72c4308e9ae695acedb9413445bf6a40d59ca78bd4f74ddbc1bcd8508cfb521bfcca99c98dad8022d3d1ccdd98bca9000000000000000000000000000000001487d006830d00d84a567c5d031019035443fae4791a05253f91249b32a4b3e7b3ce7eae885b8caeaea411a90b3445e0000000000000000000000000000000000d94f17aa100503f605732a48e4f55c394a8df1421a3d7c78bc85f4cb7a53744eadcf76e1620fc54204b123d6071cd3bad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e900000000000000000000000000000000101cfa8d9c7522277f2bb4bae6c09e8b93a876c749c91c61784feeb105be61c2479375abdaa81deafc2fe754ed6cd9da00000000000000000000000000000000089ebbdd489ff670a70218f5aaca78d4e7ade483c7f20de4a84d39217be8f560fbf7bbe36f3f8b8361ba16d17ce609d200000000000000000000000000000000094f094372b2315fabc219099200e7b9e2f3a2f6fef2ede6f83c82f44792da03aaad06b8cd06dc3f140746bee2a45706000000000000000000000000000000000cde6cf9a3a7018b2b1c0c26b5850820080c7e4b56e615d577a78565431c93de78348d2851d5ad9f120ddaa9ff3da31b8fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000059bb8e5dc5f0cd31cf674ea78b80b67b8a8a753e51284a2ab37d3f29459250d904e70ed00481b73556970a7f5424e5900000000000000000000000000000000043c6a53c413bfa2f4bb14ef296afd97ce801a37fe63d11a842f8d66160794c1a651d70f4c836af2c73cb1bc58c706460000000000000000000000000000000003e7b67da1513656f7b08fc5a77682477349ac57e53687c82b6d98772b5f929a2b06b0c7e14481d522aa94fa3a6e1cde00000000000000000000000000000000109e07928216eaea36fbb20a38711e73fdc26e18a6967b54f308b10116a5c8af0c8411406ef6ab1050b61c23bb746b0a3023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e000000000000000000000000000000000166f26d3d26cd48e498578900a8c830ce9b80f162c4b430749651b945d9f60ae6a26306ad7711a1f9d3428946074912d00000000000000000000000000000000165f1bc59c9c36d12754097ea83e9a63fb4ae5d1b93a1b9239a6f338cddf4a9b30415d58076852288c6a467ce9b6b9eb00000000000000000000000000000000198e73619cb93fa6a2bc700cd400519d11a7d3d6d945ffac9754a6faf37da8596b49b7a3a4f2cd899ec9c84f1e79b7ed000000000000000000000000000000000a4740820d60034d37bb85e3e622783852779d36d6e61f81a7eabcd094993dd7d81900277550bb4299d550d2805466aa8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000001714857b0ee07b94ea928ff57aae9fe003c0c85d8564456955d14fc8d4ae14a7c9bc303983af3e2999c6db2d000ea51d0000000000000000000000000000000016512cb60aa372cf5098ad514291d8168ed31bd755861dbd9ef020252c01379d343a9c058839cdec8d14f2fb9da0db80000000000000000000000000000000000af74d8ac711b6590e7041e80ca40dd4db659e42b950bdd68c56d676de654c1a47867bfe6483dfe1971eb7c1d1a70bd10000000000000000000000000000000019e56ca1ef3fffa9e131fc5bc93100577b062cf9b2acd234c79e5e54aa799a389f30002b4bd683edec5fb100f1800d66415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c00000000000000000000000000000000019d1d1e1e2dd4ab86df81a8246c902a573d1fd1598050663342e411a1d1b3c8849473c689afcc8e0ce5e51a9dc9c3b6200000000000000000000000000000000190d7c923bdd6336fe3e0509563b2eb6067354d8807f66e6052e97d5997464b9f07f29f3022f78779a5c4ac155a703ce00000000000000000000000000000000128591bb699c18a7b9e6e4e894654853f6a68233dfe8c744b42e057711b8d0efb3a98bab6aaa40ae7675d9200a8427d600000000000000000000000000000000045e0560e0936b16d1e055d3d3f4e0fb42d129546abddebeb78e871d1442f4796d939929d354b0326b95e50fd5208fa9ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a97400000000000000000000000000000000092373dfd7d4375d6bcffa415e5b36a31499e881a80be32400105a6d56b34d64f4fed09f12640a43289a710f034b71e6000000000000000000000000000000000fa75d6510b3b58a32635a7a6cb4b9255aa7af46905cafc893f29b7866e12565765bcde498dbe87df3d1dd53ab5628320000000000000000000000000000000010dfd3456cb6a8bc853b390380a13f045ab43abd289fd05e7f98839477dea1fb1fbe38ca4f5bdd6691446ac0219e453000000000000000000000000000000000112567397f3fda84db6042817a99aeccd0c46a11fd3ba44e2600deafaaab7014dba98cdcadf81b97272fb7f275ee8a4e039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff30000000000000000000000000000000007b397f093e69874d2bd3592489d93c80d0191b157e71d08a6ebe73063f77e7c5e084a24b34da2aa6354b1815a694185000000000000000000000000000000000fcede3a39dd5f905d072dafdb6f56d85726f6f362f91f079fcd47a8c1d3bdcf199d64edf17e3db1dfc96a3e59f69bfe0000000000000000000000000000000010cfa13c84e750d8af8bbb88bd6d16adf3bc7b532447c2e6accb359a5576be08c1b25f336047fb8e01a4d7f9080d0392000000000000000000000000000000000ca0e88b5c2035bcd3a65e8bf1aa219cf428b6f80617040ae02a0ed41559804844df373ac61a85899bec83e5a6243ed42c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd055500000000000000000000000000000000021f3b793680e0e3127fa53034e9fcf286f5279cd167ac1e8ba051c440aa265ec6d28fcc2f6d3bad126180efd4503fe900000000000000000000000000000000182b429f27996ee070ed27e7015bd70191b814bd02ca6558a9be81d6898161aa525197c1672ae75da92729f2fae9fa3c000000000000000000000000000000000a20b3922e07da4ef6696de85754eabf1f58f7f5d37accb6cde4f62066e789bc64bc8ad6ac827b8c955acc858b03d053000000000000000000000000000000000814faebd3b60fa1a8fb86b3cb57d36b9c85d4b28e97a2251e6bc1fed1ccb18f17664321f38f3723cf8b09a2161c6aeaab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000159f9d329f929a65e41c7a0d4c05e11db61ca7d6d82f8b92a780bac66568694656f4c845a730861fde9a313fa49bdf0e000000000000000000000000000000000d556bdc8dc959b00f74209dff27023c5521d387a40bf20ae2a98f3f55318eddd347bf1e9d856f43a4b5fcd26c3567ad0000000000000000000000000000000009b4b0cedf477ef1e0f99627bdd7a7afeb9e29afbac553a516fab479913b23a9be5e0b38994215a9e23849bb664201ee0000000000000000000000000000000010899f4dc55ac5d1f56a7b8d55ce7f6a5e0a8647bf1ef6e9050f00c5fcac9f679f138018b9aa611be73d3bdc0af2056e297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000000226c8a6b27437972ce29c2ed7e5cca4b6691e3a5dbbe713b5d309ff2f4cbb95e8f1571314444d65ff5fbc3281f9354f000000000000000000000000000000000282a49d0c560d873676967700c1062013a2d4beee96a09af7e14436fda4e3d2a32ab8ee4e591decec39a811ddff130400000000000000000000000000000000167bfe499f1f4609e67134e12ad91aadc37bdabd0055ecf7f96162c39a02a86e62a7b3d39f514f63edd82d04beb1958a00000000000000000000000000000000191673ea5470e4704e361f5ead1c56371d6aee3035d92d9e1b96fd119c4f877cde6451411e441fb45aa9fcb90fe4c66ba87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99", + "Expected": "000000000000000000000000000000001488532d83fddf0bfd69b32f965790b3fe4cd9f64e8d17e78189c346518c91e69db2f0b742cdd5804b3db3777dd931230000000000000000000000000000000016205c470c6371d73b012a14d519bf214ff10de458605097da1b798977bd938727c5be19a10f4f492f301d2ab6c38ed000000000000000000000000000000000142cc08f61d3c9bd4c7bfd0b7a0b8693af6120898fcaff49a7fb5abdaf1d15bf70eb033d6ff09a75995547e6856c595f00000000000000000000000000000000164b2807e19135ca3b66bac9aceb371165c930ae063f3cb5a06efb8985a1e0c39023d8f01df517713796083e8c2cceb7", + "Name": "matter_g2_multiexp_90", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000023bec14deefcc20a90e439bc16912e90191dc7142234b1870e4e8d70c56f695d5cd30a68930ff9b007bdcae8ca90d870000000000000000000000000000000000053a6e226f3bd82150e08ec3690f36616d5ab745b36a9990baac7ad3429a41bc60c7f7000ceda4cc9298b10043639e000000000000000000000000000000000b81b331589ac332093928faa60d6819d3b5559d32d37d2cc13c78aafa1cc34e32d317695c1c4b4979baa1865ced90150000000000000000000000000000000010dbac5e52f9a046ab88aa36b3c5f6952720e174bf8f1732e886e66e5803aab63642185aa24ea08c991edaf8375bcadd9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000001875ef3f90df03d49ce6cede2c791b4d8503b75acff2dcb1c7c88026394dfe11481da72de4ff58ee9a98e75577b6398c000000000000000000000000000000000c8ee603d1404e64ea3ff08c70b3dbffd318736ae95f9a96ca07ddaa449818e6c5a17b2970f572f53c90be893e5c323b000000000000000000000000000000000f31af63c68481f527092b261d29d5c2daa95873b68899c28ac7753d95a64f455ebabedfe6e72246e494cc5fa2a9bd040000000000000000000000000000000009fd06bc51d4dc51de9fad6d1eb763809cdb5ccdba8e0427859d878904bdf295983b318f311856728078e7cbbecb0c5b64be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a67690000000000000000000000000000000003ce75ecf6b605ce73f4e215b1aad4799f91e624daf0deae3a273968490bdbdbd0250686ee91a1c24c2e2f2b6024fa49000000000000000000000000000000000e4d9b65d71b7593310fb5145677d170663c0ca29636f7b7c50ec1988bd2d2f1c41d542d4cd8fa23fad94bd6a84aef5b000000000000000000000000000000000fa4accea53a6362651f6c6ad2a68d20b5f549f8eb961718e0c14cd05249a121e442a6a588eafc83d6a43d8baa66882400000000000000000000000000000000121e325406767852620ddc45677495fe3e0851fd3c70922896a3e92033347d2fe8d07f4db8f26b8127ec39d619d596030c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da0000000000000000000000000000000003a0ac3ac37932b71672b9c48bdbd368d64c11f57ccb952f633bcd10ec19134c65fb2cbad655d773a90cbec2d9232b3b0000000000000000000000000000000007553c470bd8f38a48490dadea29df81ad901ecaaf1eab35b1f497bb58acce77b883e03e78702930dda72e2277139a2b00000000000000000000000000000000044973913824b3326b72e62ccbabd8c9f1b5dc81b423d0dca37b6f33972d993a681c326730717036bc6f0286da9177430000000000000000000000000000000017b0407d2864cfb39dbb0a5fa8deb4ed4a690a4042153e829f51c56bd0f2953a440d8305a318e6d6f67970d473753021a2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a000000000000000000000000000000000b7478dda7053590ed013b7c23431a21626e748c3843e2332bde0bd3890ecea95b6104bac420a8be5f3dd9b075203616000000000000000000000000000000000e6dea641181cf796f62b196652f952ee2a26ba998cce1cfe9d65ae49198d10badffa561e2bd818eb2a7f350c122fa820000000000000000000000000000000003c79917ad5a9c7f046b34e5491ed015695aecb00760f3009dde4cfbf88ad1c03e44117fcb6cdbd5ecaa8df8760a3da100000000000000000000000000000000034e22ddbdeb9dea46c71ca2144ffcc8356c1a525c5ada69a6d5e5c1786aaaf0cf532e31a2f78371e04a72e8222ed4c7e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c5890000000000000000000000000000000017964112272360a38d3bddf89da922ab50be076bf71a094fc8afde109d3817cc2db633e6408f5716b76d70e30ae00c0d0000000000000000000000000000000009bed28bbf43846ab97b92aab9ce094b077bbc59db648dbb469f21842058ef20318a1a8c18045b3de555bd8c76132ff0000000000000000000000000000000001297110789c7aecb0fec577f6f4a4de14608d9aa26a8de68289adea7f6b53b766b840d315152ea346f8c10b2d2729e730000000000000000000000000000000002b551c6a7846b96c6895e55ec435397af70eb435dc1c562ac71a44c36936c2c6d3e6a1e3545513516513391aedaf9ca76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000003d463ee4d177d78849fdecba52b7e83ca90d54177ed39e82b4e80c17994a6a2bfd9c46edc0ddb256f8955428f30eca0000000000000000000000000000000011dd976dfeb8ecb7d7f5cd10c235131709fb16d8a827e83d7084266c2504cd1f5276ae3333bc7fbb4ebab48c0d97a9930000000000000000000000000000000005fd19477fffc246f5991603b48085d95256b273631bcfc16f19c6980a3ba01ac098061faa149b475bfce37d586464b800000000000000000000000000000000103ac3dd682aee109dd7fbf60b50c28cf7e37642f05b424773a06f6cfaf7e9fb01d5074ade97ef6cb0ace2e1fe07d54c7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d0000000000000000000000000000000003208ce7f51a96dee053cbaa66fbdb921c2c3b42ead78b39b4f1df7ab49f05cb88d0f4ac18de5839749416eba5535d4b0000000000000000000000000000000001ff7f9db52aaa0fddc8e96a67b99353b92d7032f59d200bf69da3b446d08435d2ddaeb93584d3b68a1934566187922b0000000000000000000000000000000005f05ccfa5704652cecfb42979c538823fb9d11a00222a963d00f1a4b9a040a0222dcf45baad40c6574d85e5617dbbea0000000000000000000000000000000018637b8c3ef111f6ad4538464c250d780e7f081802bdf720f4c925154f4667c5d50cdbc4dbb7d0b2747b97d2ba2280bfd35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f000000000000000000000000000000000eb400becfa5521b824a4288885fe46642c31576238e94f95e9b4bcbf62845ee9d9ee122f87d36fbe668f0e605fa2ce00000000000000000000000000000000003c8cbdeea0d09590e1719ddffa0a116723f0fe85585583f3f271ead66fbc2107873181915cc41eed3ec6e2c5669e9d3000000000000000000000000000000000e61c0768561517405952c6462f1c5df95be272251d8a7060624b62f9be310cef64436eb2c4c04e8352d7b75fea1756200000000000000000000000000000000036cd74a8efa8a1fce7587f07d5c2a6c4b7ef161b0faae037c9bbe63bd0c92b83e514c8c1bae4a5d9866c0889b1b914f3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e8680000000000000000000000000000000013019d0fc8b93da2c79e473d713d94af33eaffda65a7a49d0cbae9f5259b8323e6f29b83da9608ba7d6ec004fb0710eb000000000000000000000000000000001505d30bf8f7c51994d896d91e8e2259782e2b49bda834015477f18c29e64da4d31f8b96edd080267b77a9539afca06a000000000000000000000000000000000eba929531615d9c0f59c4b33c1fc34b81e9c77cd8c6887099d850b3e39326d7caee1feeb101222f22bea1e9853d06ea0000000000000000000000000000000019d88f62cae047ddf2cefe497495f890d9ab8499e56f72488af65095e992427bf821f63555a67b0afb00d6fb441080a010325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c11382000000000000000000000000000000000b7c8f3d0c56b3b7d96c0a24fea3394551a186f87acbbbbce41d1313b23762945bae2e911725da4211614b456b508c0500000000000000000000000000000000125316f64bdd0c5bcd26a0e5bcfc3139045b3a44c8a8dd1cebbfaeb83b963c5a5abd4a5961465cff261c0e49189278d800000000000000000000000000000000095a327f488b901fe7dcc9f9ce6f4f25876bb09b053b64e9f4de9506a0fb95fc0cd443473c2cc5436750581d39b8e51f0000000000000000000000000000000015d406b31c791ae2d25ce462304c0bcf341686d7967c9dbb6734bc28b02123b1730d0a673fa8071dd90950d9411a2b3909545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb3000000000000000000000000000000000c7029af9422246d0a30784431d6bf9eca09481589438fe9a6d2fe1d5e526ec3d176a3d550204aadb85353d99bfe3ce50000000000000000000000000000000014a0dcb26c40693ad19a1edccda05055a27ca24544e933d01dfb964571071f94c94233f81e1ead0925d24e6d3df2c21500000000000000000000000000000000147a55ebd83c746128ba9c7ac57be125ca5c95f80f891e2c5893caa779484bdc1f9c3b3ccc4223b2343ba939251f7fdc00000000000000000000000000000000125622a040d8b157432ad81b8a83a9b1f0920b92680bbb65050b4862b89017b3bfaf81a3402ccb383265ba7200ce677feef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c810000000000000000000000000000000013b394fd7a0f3d94e5fe4cf5cce3627d425ec848912395565b3e61ffe89e56be799c4779d3b9a0222ecc6538ca3346e40000000000000000000000000000000014ac1a87b333caed0f557fa5692d1138a8c1e92d1f9acdc9f357e2a46f27513dea42f367b046d389dc831610be4fbcf40000000000000000000000000000000011fa243a0aa8b0c01c7636387d60021afe6efc223b7deb69d030651c369643188b9dd5e08d6d031d71dd11eca1e825ac0000000000000000000000000000000015bf8fd7fe438407db7f1b0b586b2c285777c5b6dbef9e45b46cc0a50dc831f32a70e7d4316d4869bc769ff6de58ac30c8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e80000000000000000000000000000000005ea57c269c9d43d3f17a83df04c95ea7e7bd85aad1dc2dd285ccdbd52bfe707a1d2476417e848ab119e62fea30520af000000000000000000000000000000000b99768ffbe95e315b244bf996cf34f8ac356664adda5aa7f4ff8d513b2eb5934b8ffe0fd9af94bc9b934e0a8bbd51ba0000000000000000000000000000000003b02c259df189370dd2700c5cccfc8b212a4b332a083adf9771503f5bd0c9ef040590320fe4a86c555a4ea87531268100000000000000000000000000000000003ebb1e610bd055d037a410cce3ae06aa654950aee0210ed0ee79f7a332be7342e308347d7b17a146a8b4c623029e08a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000015c4da691b5e6242af870e06b29bcde467b4644f01080eca60a28c7f941590192be30e6a4270a36dc8959b80235600aa00000000000000000000000000000000080f3d3d5c35ee24179f51ad854a37ac4ff867a2736a0e3e8f3312ac98c7016beea6ffe2bad1dd4842d6ec77995ff97600000000000000000000000000000000130c29dc633aaefc831b0bccb13fde1212fdce8cdd17beaaf1d06e74ef5b1b69bcc219c8d63f054690af1b6dc7c0d647000000000000000000000000000000000767290aaa1ed4c1dfa5603d976df0715b417599445ca577ded7d99e685118bbec71443fe1d9a65e0f23436353df152cdcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a00000000000000000000000000000000133e997857f47f8d6278b8ad86f4692ba0dec9da336f2726704db593af368dda7aefc0b218ce1674f415e0d9e2dee5c60000000000000000000000000000000018db87da1272bd386f7d8b5245dc2de30e82739723b680dedd36f4ac4cf5042bcbada1e1bb307ba444431d73a4248f9c0000000000000000000000000000000006580be3e67c7a615408aaf9c95c0956678af0e2b1f536f1e69588193387f8a05b03d5e1060ca60c4fec9eaf3e72d39900000000000000000000000000000000050bd9879ef9eea147678f552cedacaee84562e6561b3b7338fa8f9d514099291c3f2a3723fdb22c88f1c9243d411ccba6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce", + "Expected": "000000000000000000000000000000000d8f69d90c871c08ae09e7b3e62e36514fd056c41fb596fec2fc9ce8509ab4f6675d7e85aa6b4b3197f5ab781f6f2e490000000000000000000000000000000011c4bd3cd156c34065e408efcaa5e13ad23d114458b71c2a6345f4aaf82af76cd4362db7ba9ee7e1e92ce72e242f570a000000000000000000000000000000000712dbbf20e9b24d20511d01717a3783608386408a258c2261fcdad5fbcab36c6bd21473c3d93ef8518975256c65a945000000000000000000000000000000000d13747be82153aea8076fd7813ecd7f60a214c31e88e25b14dee5cdb9336599e40b136d9ae6deb85606d35406b2675d", + "Name": "matter_g2_multiexp_91", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017da08f2faa32570d95b9efd2d2fe358faec1ffe304750dca1dc3a273be3427c70904d58864f76afa19b0fe33ab1535f0000000000000000000000000000000017de677b713202f23baecef2b0618da140af624e56b876f2d7a20cd437c3868ea00ff6cd9c8908c1ef323ad294edd9670000000000000000000000000000000011d50aad957c54868aed6d848b2e67094b129282cc2df56c41d6ffe976d02ee83a592c33370d3715588a074db503b3e8000000000000000000000000000000000b8aeb019d120959b21627c1dcfdfb67ade22a948fe433172994d4a34084ac9e1c11333a9c663c87acf50962e21c728e92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b70000000000000000000000000000000001312ebeee36fff8152324a3ed24c37eee50b3099619a33c7a6316470ae722548b4b9e0f0640453caf53f374dba504830000000000000000000000000000000005ea81d2e5d9edeb3ed6c200b75beb731c31ad666e6e37db72ffd0265378bffc2724047c7c0c6e3f1598345fd390e9270000000000000000000000000000000017617a836beb12e637c5bbadd4fbf1ca2f5cc3280814ff5cbb5890b31cf2d2faee9e3ea8134af97ad4feace50aa194140000000000000000000000000000000002606deb5d57dce5b3d2e5f7ccec3ad036992beae238673641ad6042479ec3cf83bcc0fd03b7dacb9b4bb6c181ea9cc8c193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000001203ef36896bfad2a2841689a964328fe4ce3d83798671630d0c8876e67ceda03d99555aac46d984f1d3bc38ffc134c50000000000000000000000000000000013e7461c256c8ff9144b17f8cc2e270aa94b64be62588280baca2ae6b6efc4d32b3800eb84da62561e0e96d5f0387a3f0000000000000000000000000000000009454b6a810647350cf0b364eb1c2b719670af45bdba9d7d1a534e23d4e810c3ef4d9318532e46fd104a83bb10159a30000000000000000000000000000000001034546c4288f642daeccf5b56beed2ca2d946bb4391d056df9c6fd6771048903fa330ec16d59d05540cd715333c4bc73dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae6000000000000000000000000000000000d7782404dc6721f52648fc6969db33a9aa209f8baf5faa9678437c76c9e1635fa6d22d94aedefc90112223bb81ce33f0000000000000000000000000000000001e442e548d3045d1589817d0b57dfcd66fc64ff978186f784bd576faf57607170d49364a72189328c9837c9a2d8b0a0000000000000000000000000000000000da2b207bb7720aeca2e6ea02b65076770b960d4b7a96ed941a7f409757b952031a472384298acc3948bdc485088501c00000000000000000000000000000000048f85bc05ed78c692138f27c3541ced11b6b0ec158b43d133c3450a905416682fbb8c83dea06a06d294c48289ddb829e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000017134f787c920bc15cf2228a186dfa1d10194087f28b6dd8f03e1c86226928f0eb1c27020a5cc74d94b50c4b4e36b8020000000000000000000000000000000012fa1fdcbaa81c4cc1e37447cae51beb29e55bb19b91e2b575afa3754589ee0151cd9e83573edaaefd341f381d34f4f8000000000000000000000000000000000ecafd00cc87a773a13909512466ed11288c842716e1ca5c37a4d9a4cd7585136c86f32140fdf02e2997a6e19e3d76a200000000000000000000000000000000104cf007ea863dbd473d7dbab6f55e74062b18986e9bc09bcfdc9c23e4bff8683f73aa998a5cce59ded10499d18a0ecc4dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d72000000000000000000000000000000000ffffc98e55f4ba9a642c40678d625690464bea39d085dbc9c99b4c36ea8bff5154eae3c315e1dec29aa669840accf290000000000000000000000000000000000a3df9595167048c52b8170596d4127968194aef7fbaea4594a27c6af05c54bb772928a7749d74311038d1c115e91b2000000000000000000000000000000000b317a3abd808e94a7197e0d3b2515a147774f78d0cd7d36e1156da28a26e33bfa76d75c6e3ae346f9ace050c9911cc6000000000000000000000000000000000fb5fbcc2f74fc30ae7e32143f219db7dfe5db6ecb09cedad8f087b6df56bf9693c8b7d78aace064e7c31785f6869541795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a95680000000000000000000000000000000011e4b907a72f34af899a6c4de211af5fbe0265e5bf24d406798de53ecea273d5df4f4953d13fd7c9dc3bb0f0c143e3e4000000000000000000000000000000001623de5e87b6e1ee920e1b7d979fb9c431c12abb47b93876f9ddfaf28a7b673c18be634f96b813f7e0574c55b628a8790000000000000000000000000000000018ba994b02dad759ee79301b42ea20d7545844c0ea4bff2f95dc9420194cc4196fff12cc09bc0cef03cb7ba868c273700000000000000000000000000000000004b3527c8d148bd9e6006bd298ff8d7fe320748dd3f6d23449e874fc0c2f58d933c1e038a74f60fb6032cce41a3dbf5725b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f083800000000000000000000000000000000129641af11fa92056236ef135843b2189d46d870381261d5781a5fd6f2c5cc1861ebb2e801f19f3adf2216609a9e196f0000000000000000000000000000000007b4007c55e47f6bf3aa420ad75fd191ffe0fe824fd30c3f1961a8168922476fdb3869822704999b044feead470e3b8f00000000000000000000000000000000174209113e2d8c363b04f49487176dc6d9eb4ecc0b22daa7ecaa5548d038b3b7c23ebda4f1b6845425cee13493385302000000000000000000000000000000000a58c80a02b7f93db01d2f8e0005839625e6c4f121f3d69115f435526a7f7cb53177caab4db86273bc2d2f0474235f31df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e000000000000000000000000000000001738a03b46a8ca3f3d1f4f4447497c59f114005400f06813b24ff462ebc6f27c1c3c788b5f83f65958cadb34fddd08f40000000000000000000000000000000004dcfff2bc9ca0282016f38df484655cce7b872b1ff047351ae6b903e05f457d7fefae93104f9dfb549980394dfad2760000000000000000000000000000000017cd89434225dba07be137a73892faf0258b3fb19e6c8cec412fcda912c0613f2a925ad50ae485187020a371ff2dbc59000000000000000000000000000000000f1f9f87d3401e7b3b59331a89d9535adc973f869b81bfd8892a37117d8597ebab2800c966e623469792f4ae2a8eb232959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000000bdcb1d2a782541ff7884dde4167ba060fbd4b117944ae69aa2ff685b9bd7d475f45adce0c9f92695b4f4ecdd48cb9b50000000000000000000000000000000012a55432678043888bb9e7e47efb17700b3e702e389d0f58dd454224a02da3f190b2fef4c9d3e2074c7bef813fb56fb0000000000000000000000000000000000efa51ba64f1e7a1a269dc083179a222afac916778a967098582f55a41394bff3747f8d024261959f6d399f44a40d0fe000000000000000000000000000000000845dd0974c5789a85c3cb09ea441f2c433f0606928ee1b177eb851530d6e6b620b4fdcaffb8f75623435dff99b3ad9526ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d700000000000000000000000000000000180beba92bdb95c7803fca0407e29929ee64e03d61cad96ea0e6c469c5a888cc5ca5eb20983b3418a8da6596a5f1b2ba000000000000000000000000000000001322f7356eb3069fe20063f4be22c44426162dc8fc117e4e382bc4e33bdf3d971ef662fffc1d58ce187c33a43a4c853e000000000000000000000000000000001601a0aadaba846f11ba5c9f48e13bda1007ffdc1b8bbc9e85e83e569e9ee17a1e9e780a50ce617e6c780b8155675f2100000000000000000000000000000000105b2c213aa43ead42d9cfdf1d6c0559c25b4b86af43d4493bd75b76986d0d4f1d9b3bf9e3922b5c08a37a1629cab7d8f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c20000000000000000000000000000000017bc70346765b7160a0a5e556805c7944304acbecde06cadba474c51f05f22445c3d943674cc8215f973cdf11b9ea2e9000000000000000000000000000000000bfdbe202619a1d95359941c249b25462d3ecf09fabb878943a8a37cb9eb94abd7e6399f8d82f90ffcf904f4466cc5b1000000000000000000000000000000000f048db8530a288fef10a5ef9bb3cdd9f3d3b0ef4824609efad96bdf52d7c3b10ef628fa04f8b6513485e55f653f4b990000000000000000000000000000000004ec35f59287eadb1738bb50b0e2ad9d280bedfdb0a201e72594bfc4322ade0b7ffd6b532ebc7796cfc71f88a194bef4b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb30000000000000000000000000000000014424c77af7ace8ebf66f556cf219919712d96d24438466ad620221ce1ae9b2cd75b9c526e25df7fbf3c9250583757f500000000000000000000000000000000198aa00723781714152b3494b76ea3ee043b363b3fa81806cdf7e440b4cea907f226a3c038fb95c932710dc9aad4c9dd000000000000000000000000000000001360e4c775f6fa5e987231dce25ec67f61429ca9fd8160c3074383c30a8c0d7ff068b1d1215b2c0cc87129d9c9aecbc9000000000000000000000000000000001280ee6160800c4b0f82d5c2775238b4b223d8a0ac9a8f8013f138d554ba31c9fedb30e0eb5c330da17f5785b2717422ad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c1000000000000000000000000000000000f1f84251204d9f9328f79a45d15b311984df0715579633a82b5a9f680f6645cbe748b0fa64b9ce1e696e20a5645d6d300000000000000000000000000000000156901506e502a09917f76d825614824dfbc34d019ed53c2ec5395b51512da512b27541bc53331444eac2f618ffd5357000000000000000000000000000000000ea8736a97a33112bea9d07b729e973e3a942422f1d2b24c30e96637b535ccfc10cb5930bb59ed90bef604453df8772100000000000000000000000000000000187378477f60e3eaa225e89d8532bd95babd4a5c51729cca800d364b61575704992639dc5035138664e8e074ed0820033c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e5281c1c9210269a7f5ccd02cd5a7d3648b56d9ca6a4ee50beadf151c2601e0291fe7f1b89b694500e6c636d4e445c4000000000000000000000000000000000d5d5399f49697e46013558dfff544383b25f3b60681ba5fa2c5e6edfd3924267d0992abe65cbd5109ba8a1c6eadc7e30000000000000000000000000000000012a2104aa92871dd8e41ae1ae6dc18ceb7d0f361a5a4fc67936454b8866b8aec1602dd596459cccf6d9e1319ec3299d4000000000000000000000000000000000268795f6f9892f5b476c3a534673538647300203a51a8ff60b530094608b5fdf16297f02ab7ba41d6fe556885f064a4ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff7000000000000000000000000000000000a5585961328c52e0fefff16e66e3367e34339dac1a20cbc5e89b78804b8bc265e6e3fec1da6a62cd8a46be2f08a6d960000000000000000000000000000000016fbbd698784beec5a636332c0b20fdcb68fd3015cc6d18b541346a5e6af76613e6fcb14c888a2b8133c0f4132fc079300000000000000000000000000000000041805e0adf2a32153b89d1131226cf0ebd77cde3116a168e792ae8b88ba2edcb1fe7275658a384251b805d282ee039c00000000000000000000000000000000024213e4a8504cbae4875617b9b78473e7842ff72415ceacfaaf2e8b415f9f7e411989bada8101be72f9295dfbddfa3f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af", + "Expected": "0000000000000000000000000000000003c4f051d528166f256d9356aa9cb885db5680c51990d9474a948848888fb82a9b86daa7a2273725ac8ec564ebbf15db00000000000000000000000000000000010a6c4c7067f511ca8f1b66bf9ffcbb275c7575540909262f7c4332c3d75b2f6d2f3ad2848c0d455410afb1cd60c835000000000000000000000000000000000ee5e582554b3930c5670d4e3542bf32e8b871849d7859eafc077bb2b533e936d462f614057f9fc09c4010afab501c1f0000000000000000000000000000000017fdbcaa065d301adb94a60dd20dbae71512d369fc82c556ea0dff66843be768be942e060752591c6eb0718985d8e313", + "Name": "matter_g2_multiexp_92", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001353960aff58d45691c5378a0676a8e837260f5819cbbac9cd75c8cc4c6f1e17b9dc9843eabc0b1dfb27ff7631e4e52d000000000000000000000000000000000d6279a43d3526c035e88b0b640b04d42ea573ed07323aaac1d9d5570a8be64782682892415ba2be5cbb13f56e3a44db000000000000000000000000000000001250fd14fd003f88eb6e0e80e9f2ebe204475fc6c06cd10fa45608a17b7039afe0326474ff80c357f86c2825cdf7a16d00000000000000000000000000000000186cd91cfc8ae625e302946f2b393ea67e1107c0bfe938f5f36d28879fa0c0780c847aac77d0310d43211152c1d5f5d314d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d98000000000000000000000000000000000e55a98e8b1e59600e86cabb5e8db8ee622009b1618ff0df3e93fb55b80985bf2a8ed060aeaba53773274d4186934f75000000000000000000000000000000000bb7215fc43f465f51fc8265477fc8c79493966f040e02f0eacc4ebcb3414b84fd94ded822bd24dd5ad5720f12bb8313000000000000000000000000000000000b23328e15cda8a576ea352b5dd7ce382ec781deca6c23f646e42f0cf63e28669539579ea51e3c0afebbb58e1e8e3243000000000000000000000000000000001716019236169bdb4af7bf7d7ce0aeee7900b74023acbb16f6965c2abcf28917bf88d0f9d5bc26a81710496f7821fd4682cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa8626000000000000000000000000000000001718a4d6f5e78524d8df23d2c589abf04e3567d2176539a30b9f73c6251de573caa60c2881f3da99c48d48e9aacd7402000000000000000000000000000000000ce0e35721379077e6eb3b572f7f7718bbf775b116521c14acbd3ff19549c75d50bf70ce84326cbc3f9e5e53605d8ecd0000000000000000000000000000000007cb3305ef0d2cd7de4dceaf25d2eff44d4f437e065f6b244bf1b0611c891626eafc4b759d55b45d76e94b85852df1de0000000000000000000000000000000011cb56d2ed32a46bd951836f8e0f92d3824a4cddf011eecf1e2d92d81bff407a04abdfcffd60ccecda6e9443b328d51eea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f982490000000000000000000000000000000011ba705da23100f853882dd166d81ee1d7621550d156b14f7c2123e2681887ec3724626061db68b2c63987325b27d6230000000000000000000000000000000014271414fe078a80587269398afd127ce34c8dc2a4851f76613b81dc99d766d75c703949c1093b04d66a301a79d89bc30000000000000000000000000000000011b7935ff284b0f812b5da5b28ed338dc4c21ebbc7fee04db834732b11fd76092db0e8d80368255b0f1205129081e9af00000000000000000000000000000000104ff0ad2e3db08d3b4890b2e54f29e456e627cefc3a4f07c1109b764dae4142480e3e5312ada43fec9ba96ce587e8a4e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d000000000000000000000000000000000c60749ef36d63960022f3127d0ab4e12acf05ba1e1a136dec89be388b9d7144c1d78c04df658727763dbaa9725bd8b90000000000000000000000000000000019932b1c205a765bc9de0cc136999deb153222a9dd9e9ec3660fb6daef56242d08791d440888e69ca0da2bbe0fcb7d79000000000000000000000000000000001764790d12f5ff79ee4f2c9fadd5dfb1cf47db70b9e86018bbdbffd1be18df193c7dfa71533afa381053a77e02719c6400000000000000000000000000000000044b2b0211cbb407281ab2abc4725c2cd791b313bab8779954a2461ce445cdae60d4a9efad9f90f80e66b1438514e0f0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc00000000000000000000000000000000175cf9e7eead650e7ae4fd657bc288b6b6392773bf1bbea48e17172a5019637fbb2bc0a3d0d1e3b8054564935c908db200000000000000000000000000000000136da2a625cf72403d0861b9cd947cdad12b1f1e6cdefc4aab6756536425285a7953a1b892df40ec12ac3430fec889cd000000000000000000000000000000000c2d10c6d71cff4e1deba1984bfd17166571e64659ac91b64c343cdf587c29d52a2266c00a57c01feddb1df6439d21d1000000000000000000000000000000000384a782fb31278f49c840bb8f0552ac2734ef36bb3d115be7df20333aa747c92db990f7e879399235d122fdba0eed76eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000a9a821cc63e7c9857b0f39f7444a1e00a422f7cd5d0575c26bc5c6b98313abfde51e3f6d5f4c817193bdf391344e5ba0000000000000000000000000000000010daa8c7194a75cea757b6ae4eee85006eda459ff2cf155b1b5f19c3ad341972f72e28b781c4878e8919c7e5abe9a1d5000000000000000000000000000000001154d5d5764aa2b8818a9dc5dce30ba2197a86d0bdc7dee3e600462e295cc3a69dfbf8db34acf138e7a1f16b62a45717000000000000000000000000000000000b4243a09b05a958d78ba8ae25fd3fa85d520b95e56f1dff44e556b221a075f8dd3370313886d9dbfc56a75697454d72acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a9000000000000000000000000000000001820f953fd22b71ce00bbe9e9b78fcf5fb28bcb925f6b5dbf5711e00470ed7fd2f38d7291d40514ab4258807f29150270000000000000000000000000000000007b737b56a2ba33f76bcf66c0b26fb44d5f79879273f6ab21ecbfe6a5744da289464ca2b46c55edaadfe3210b907f3f7000000000000000000000000000000001735d1b39c5369bbf886c5063a96dd12b85e56fd9d8ff9d84520918e1dfeccb62bbbe1c2ab440ccecd0fe66f6ec55853000000000000000000000000000000000e591b7709bf00bb2a87e9edb95720de19adc41a42378cf9ebb930c6d3f5993a1d7b6320040d5c69908685d978be8f980cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d2000000000000000000000000000000001732ac410b2a7d10110bbf7709dc6fdc91ce742f8cb9b2c3ba37ba5f0934f8622c675753a26d04a176e24a630d090d81000000000000000000000000000000001111a52da6aca10cf40127fa8ab7683505305e0d474eed28a5e1735ee6877aa00c1bd598420876f2154b814660f3fe7600000000000000000000000000000000098c6d19c2ff42c2c57a4924693325de1a91135e3474ec699b70439d034469e72e844a5511e23dff3948a66cc2a2165300000000000000000000000000000000175fb79e5e54963cdbb133f38dccea2d1abc3cdf005c17e8f2de6dba9b9dbdeff7719983aa9ddb602f0cf966fdd430e0b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a05440000000000000000000000000000000004e88468d35d72dba6b3e4b9ca216b75b5d20c447064a48bee6a6ddf994b1e22fd6ee8abd60c627622daffcda219645a0000000000000000000000000000000015eb2ae16e3310b4c4ff557f0615519c13f29109d9863418fdfbe6309b5bac4463456df8ebb0b6d9022e294cc16265ea000000000000000000000000000000001288ffe0ffdb96708558d914bc412758770d048c4d50523e2b134f8468d11a57da97e42bea303ab7137e2d26c0b3b8f30000000000000000000000000000000003ce563b63c50b09a80b71a1a82995238a9de31aaf189c6d29307924b6f0990854507b7dc1644f689c5abcf931dd5a3c825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a30000000000000000000000000000000009e39ce653485caf699ae1d1d9cf2b8c5ea85b80ea042279e57f0beb81056159e49f73d67e7b1f9ece9f9ece7dcd2cf50000000000000000000000000000000008d6492cc335660c54e4a34b29b337b5800f1ef992d124524c799c04c852ccd3cfc01bf39515cb8b96151753147e8c49000000000000000000000000000000000ca779d87aaa3a6552f9f1a10b0d2e635be90022326db04e6072f326b919ee55d4124b9268f55751dc0f18172bd327ae00000000000000000000000000000000112eea543d6609d0acfaeb7be98be609f03304f50c3814ee8a010283146e6b5dbf170c7314598cac06efb9ced1ac2930307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd0000000000000000000000000000000006624dd7f6eb043da41a36a15752f370eeb3cb2e6bd88b337b370fe0660c5ba8fe64f62e112f91d2524e9324f3a049fb000000000000000000000000000000000415b964484c9246385cf95461ab955ed0390e20209ed405d84fa8c8af9fa7ab39ce89049691a63c61b12bbf6aa2a4e80000000000000000000000000000000014411d7b2db7c9ee78ea14c6a315df3d90827b511db2e2423d660176384d8f8afd284879b22f5aeed73afb2eca4be52200000000000000000000000000000000105bfb471340e76f28901edbdbfe2ba246a8824b501ae2d4a73cffd2690181347c1e6530804614e88e2bb13a8edef8f4bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d0000000000000000000000000000000002b17f4b0b0231be229d87f075998435560ce9046a8b0e8f15e3a9f07cd52f3316f6d8c00d6a872362e7066715cf990e0000000000000000000000000000000003110eb232154f8a06834e2ddd33c0207ea552f439a6127b652bc261158209a00654e50341d333cd1b206a915fe0691d0000000000000000000000000000000007940e209c8934c185e4392f12fc0afe3d234dd1ef3f92df18d76be8fc42bdcdd6d1ea8d5bb6f07b3f3caecbeb5ef27f00000000000000000000000000000000012ec903a8442f68c03300ab02ddd08ec935d97bec9050d26a5e276584592df3ab87d596f90768d2c0918099b28963be58aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000010fdc16bff0fea02b325c672fe06297e0669094e2710d0baf3838f3e234c3f776bb3fd41b967c9ebbc72a6bc6eca70850000000000000000000000000000000009d64ce322e39d5b2d0872760a61a831877c450b1cfac6cacec52d4070b0f179dce90afbdefdaa8466f6a6e2e83ee8da000000000000000000000000000000000cddca46f3b24e05b76e61b4584bc716ca7036afdd914731a61347e453a26d07549e9808e553ee056bd47e53c75eac8f000000000000000000000000000000000451cccaebe1a188d3eaadd40090ca594f071c8b6d0e0d82f5b2d43fa784f8437e4226104c4cfdb24ece1ed75375aa616810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff0000000000000000000000000000000005832ad778dca8dfcfbe741dcf311024d76341d5920b6830cb75893a112c9d86719583d1dfa7287281fb73fe21650c3500000000000000000000000000000000044feb86b4816e45ffb98e9a670fcb039fd9d8844a2c7ff9b7752f20e619195fe6ab1148f30afa393936d3605fa4c8da0000000000000000000000000000000018db9365370a8c703364ba6d9c48b3512da46cc603a43c3fb91c0a8ee59777d7cf9ac646c3e4274bd950d7de92ebce840000000000000000000000000000000017bd82310e251701cafbf8c4dc5b9e6c88085b0df287b6dde7887e1f64f2d9487a25b31abe07aec7d99a75baa5983195c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d470000000000000000000000000000000002c1df194f01dcb503dcc8a283f059b82d141274c8f37cdb6441aa33f84f16dd288d566752a93ca23d26ef5834c0658c000000000000000000000000000000001700fa4459dd4e609453284f4f7dab479342675a87c1cb42b601908296557f39256f1597ed3b9ec38ad0a40a2c728f0d00000000000000000000000000000000135ed4f475eb99397cf204f971215a0303316a3ed8b62b303b4bf756ff753410b7fe263c4e97fd4c4b399c319ff3ad98000000000000000000000000000000000a487e179bf1b73627af9d7d2b43bc0e43127a8fbfeaea7ce958ddd53ecb27741eda187745e3917f1cbb60adf0286f5413a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc", + "Expected": "000000000000000000000000000000001327c57e16f03fbf652bbacd16cf574113860eb87b8f2f6e498dc5dcc4f2fa63859d922d88ccd6683d503d0962db5336000000000000000000000000000000000cb06948c539cbf686f6936b6a1ebef2e148d98c531da36272e0334afca5c2b16a52da542a0fdbc3bf764eb877f5778a0000000000000000000000000000000003acddfb5bc4fd5579d3f592977365840be4d3cff96434e5ff4f01ea798e4401930a1f5d91f8de3ff98504dce398c2ef000000000000000000000000000000000a5a332805f704613eb085d6639f99667d0d9247cae34eabcfa399eed551f24c5d5cb05d6458530ae270b1be682e71f4", + "Name": "matter_g2_multiexp_93", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b1b06a76e5bdcb6c1c2f1952b49e1820a9d8557229fbff8740269a0b819b91cfd0de20db0afd76a2cb0fbc5fac12ec5000000000000000000000000000000000347654df2084082efd32cba2b270f66b0ed30fa8713b27949fc9d270ee8eaa9b9a7896d7a52dfd8faa3e0cd112a24e0000000000000000000000000000000000bf5b7a2c0c8bc223ab334bd1df5d9fd4bc0c635379ed2b32da13f6178e07217bb88a4bc2eae0b975f2e566f657d23aa0000000000000000000000000000000017042f8585a07304995853270b1b03bb08484104f7498a12bc865f2a0e37e662fc4b0331b94ee5690efe74056567000bdedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d2000000000000000000000000000000000fcbc73d0628537eae417f8efc67af0a4c9c375d82406086bdff669911fe1307576333c389f189f49677cbbfe2ee98730000000000000000000000000000000019d552b85b1445660ca49518d202afdc67b0eb5be02c8d3482dc1b12e5d40a4ff95a49ce47809e4d6644d04aeb67b3c2000000000000000000000000000000000ed536c0f19f592180291bbce59a72ce5e516199dcbd4fbba736cae2edbe3cfb860ead0325dcc8f8d9be1ac126dc6cda000000000000000000000000000000000f5d4f0c0ae3e76b1c41edbbebcf1ff17c7cefd41e7ef8f75dfc10170834d05820149d5f721a8c6460cd0181571fca97db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d6207f6684f8d2f083c963551bbf0a674ba40e691a34ebe6164ff80ba9bab2cc23024a896d7b906fb74c95016a9adfb00000000000000000000000000000000145855e7d610b50cde39db8995b127145d68fc9bea3f075f65b7793acbb14bbb313a1a39bd96fbea6641baae02612b000000000000000000000000000000000005b533ee83cf72f0e4d9c9ddcc6b91f4364e50a106becf766987c490d559d0f733839ecc706bbc9c2c75b243814068a3000000000000000000000000000000000cd8fba13b9ba7557c7577da183bf50810fb14eec7380e3b3d4f2fed62bb36f2b5ff288736bed0578fb6f47fb6d22ac86e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca4000000000000000000000000000000000fd6a466f2eb12f6337ae9f9b847ac1481820013142af1a474229c5f5f5e1c0bb2d9678c19c7a3a1aa22cfc7b5052e0e0000000000000000000000000000000002a0340f5a0caf5c66719f7d546972bb4b89147989280542787d281901ff036b7c69d41418c21c43127c0158593aa5cb000000000000000000000000000000000deeee37ef96f26a4907e1a8a8f3f030dc09102799bd0c6dbeb1d208a0c86a423d0da6313e0be03c026da5614a6a576b0000000000000000000000000000000007220475449add59b3cc6570701528dcbdedacb9a3d39674ad4aef4d94114f24d2bff32f40b25af97ba883905ea6838a27a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000009d7023ebb73df81455f74cb2708c14ccecacd49521a0cf67ecb6edc8756e286ede59eed54d89eee5f77f178ea8fdee900000000000000000000000000000000002ad48fc3192634e7b01604678473e286afb0efe67a4377bb885d38b59ea00202241fb28c93232ce7c9a3dabb136a53000000000000000000000000000000001934664f2bfffb254f0415d6769f4e2ac710ee88cd822bf5da5df3a2541f887e4155dbb7e8056efb2a0370d6f9173e3b0000000000000000000000000000000019df518e1ebafe95adf683279729a3298fc8d7eb39c9a3dfe4b6665153f970e243e50dfb16fb87b3be54192f69766659446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000001870048d360f397877321904563d35bfd0817ce464e0078e9605a4744e2723f49f9cb21dd3d6f37f1f9aff5a6a99bc530000000000000000000000000000000000e29dd0da13ac451d013d4a38408827cb0e739772e1f250d31e4192ddc13d651ab576ed6b8f4ee44e928fa663244999000000000000000000000000000000001646183099579322e0115ab0b3bd6c814e216ae6b2b80206354925565b7bcd97bc12668b7f3530a95409456ac99bf01200000000000000000000000000000000092f6f594ad0d92c9c64f78c819c44320e6bb5dc1dc8fbe58acc7ce3c101e49a74ae6d50b1a668a3b7436dc445e3da345f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f0000000000000000000000000000000000428ff447de18dcc11b2c5c679bc2efd125464f589013c6964ea6cab33d9b7cbcce3a5d6177bf43114ee256f23fefa10000000000000000000000000000000000d1ded695e88dae6dfa702375959831f4bda688fc0faa289dcfb90a07f3a7963f2c9070958561909a2051a852cc15e1000000000000000000000000000000000c39bf1d11fc5693167890246c81133faee93a8639f459429757965e0b62e372153ce53c61f2c539247dbe7747b27d1c000000000000000000000000000000000e84ecb6dd9cbd4133c22350f07a976ae13dcbe4c6ae09ccb023f2118fa2dec68c20ba2266f9b571bbe30dde97480e0a94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000015e040fc8753f06ed1112cc06e2cb7142a4fc984834f01faae718c17cde782d5953547857ca9aeee1c4a7d91df060d330000000000000000000000000000000006789ac15d719a7159b650b757f7d3cf58fca02d3b8f3685478ad5e5b1dca0508dea7a8203ece97c7c6d32b2f194458d000000000000000000000000000000001824d75634043cac3fd17ff0bb141daf7010f70b5941d8f75f1ae076713afaa7e0a0a25fc71038baf1b1255d64c914c6000000000000000000000000000000000a2f71bf85af6392a8a070596e30225bec9e3dc12c70e8df7c545bd6bbcee56799db2c9a8d2504c4f90ecf6a5e18abc9b677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000008bd859ff1f22d682f86e1a0e3bdf3a332ae78d64814720687a3de44c9bdd7506d2696b4daf81a94d33f64983967fdc2000000000000000000000000000000000d7b4b958e0087f8edf18a4370ff98700764c126808d5c52afd3e71ee326c766c1e5712dfa351cf5b3c518e52133ce780000000000000000000000000000000013a145331bdd9c93e63edbabb9f6c541a7c4dccb1705f07eb353a0407074a76022a8e5f5f2535b41ecf6474649e257bf000000000000000000000000000000000a12e461b7439bff0dddb560dba21ec53ce88f71fd3dc10723f3d8742ed63a1ab725f7e9619ca1ccb729564dfbdb1be7f5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000001430a8184c5055008a06ea22ca9c997d1a24ddce7e374937c32ed1e487c80537b238a589b5e50b86fa194666bd3410e80000000000000000000000000000000005c78c94f457bdda242deab79524bd2beac82bb1cb427dcb2872b56d1f46d11fc9d69ba132004958fabc5da7d6d103fc000000000000000000000000000000000e985e8ca038b5dadc9fcaf22699e75cad9d2effa47fe7d4c579ee056b1e34ccc540372111a665041062fc6c39e05d170000000000000000000000000000000018c865243534fbde740de0ffbdeab0d38ee878c20f5d84c0226d1f2b14ed3359f5b5b909808b6b3789bfcab3be75c4cdfa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000c5b10541ec34dc0a8b8e42d9d6fd6f4f71e1fe56b5afa323f4ade35c0170b5e224a66771326d9edbddf2bd38c6c68ce0000000000000000000000000000000019cf33c19936f7489a1bbc095d0f5c6ddc1f43bccf7e8d1b30fb8e8cd1ef747b483b9a8e9faf21cba7cb17fbee887ad70000000000000000000000000000000010e83916faa7bc9de9feb8a7f34ac6f2aced06a771b662cbce846107245edb9c07632782300e838957788a8d88c8253c00000000000000000000000000000000066127bed5ac9f2871500fdd68a03ade57c35449d4b4186b9fac7c89e91b4ebf2f2a02e94d0b578aaf60b32017f147a493d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000000cb5a734a28b44f04d39ffae049fe8b63b138411661ca6dba00c72cadd47b50ad4b71e858e817561682d6ca378ebbe870000000000000000000000000000000000baf4d689baa09aaf763ae7e142b801223c8ff58f2b541ee4c44ab2460fb8f6dfc1e9f61a8d73aeb92d7d08c281cf410000000000000000000000000000000008a0c736f19bd0005c9d25f88565b1355e53fa3403021577de536712ec986567184f4dd626127ee80dd03cdf9044b2ba00000000000000000000000000000000063ffb7a3b4e057a9ffe233296c11fb462136fc4b187be6f9e36f9e6d335a3d673ef8b9ae6f60c146a075a1789f389cf3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d06000000000000000000000000000000000c33d89595d039722222b9b9ee7ff1a0dae896a8de97f202d3aca00bd81d0169f14676efc4b051bbd339dce862d8b60b000000000000000000000000000000001109a24dc6f70bea47e040b24df395bf561cf5f1ee79e90c9b0480fff0795677483a85e6f2e9ded4f36ca849ff39d6f60000000000000000000000000000000009c7878f3a4e4e3149b72149a7da91bf527c4d7c94b15ba80b02e0e50b02a2c482ecae9f458a881c87e669986514f6d70000000000000000000000000000000004284448e42187c128578b801f76d421fc508cfee9360a7203a91d6f9cc7ccb6ed3211fc5df9e15f14aea98bc298b2f95143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000271ec1a3f8e3364ba8e101b49c0bb17e2b7c7f27a4aa4d4db5c07203195050f30c1a05d33c524a84b1a2f0ce31a587200000000000000000000000000000000082ce9d1da5d7f192c537b2bd617b36b65f88b308fe1ff85e47c64b62dc62324458493d1cd1da9f5fe308d27545fb6510000000000000000000000000000000000b30356b59eb04258096d0c3f357fb04471583cfe6a060de5279bf2cff4413678c1716ba87d0b6de6b6e79a96ec26030000000000000000000000000000000003c02470a14211fef14d754f6f71efb33a06a76e099093a5b9512f907ff819e1e0e15f14995febe48852007bb5c380bd0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000172df3290c3c5044d590eea59980d02e02d4fc6fe7948168492362de8f0a85df0c3d09d8cd8b206cc4d1608311ef4c130000000000000000000000000000000010e4d14065315a0d9e48204e47955ee9652b08318251a7836f32e6fc015d4856444172de44b3b88efa1b54dad346e9b1000000000000000000000000000000001549b9c85cb2fc2c7495d7ef6aa1452e58937baf58717037069e6bc6d72ced3a163f800991cd26510e71aa64c44f66170000000000000000000000000000000007814c2f1734fcc8cbf9fcba06b936c86d0452a2370f8c9480b97105e42f9babfe0869cecda7e15500e9d8d868290201b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b0000000000000000000000000000000014f849e9749a5ff6b7b10daac7f5934be5f783d49c8593367c4243664e01b1d3552e878802d7dfee823e0122e9fd46f90000000000000000000000000000000000d0b32d7904dbf08269ca3c6ae3fe582501f55e32337ae361fe4a58dada560db54205e56a399aed33bce8758a05ebcb000000000000000000000000000000000cb21440baba44c3cc6943c8cfa2fe544a652f06423d3de06c2ff734ebbb544da07ba8982b3009b6c4857b73ceca570100000000000000000000000000000000174ef591975fdaa0e3cb05bbb4140abcb38f685ce4de77c95e2cec1911985557b77d9229940b8c9157ccf9fb553e8e0d98df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af", + "Expected": "0000000000000000000000000000000006da1222c7ae02843ff289931fcfcb315f621972f45e4fb4160b8bf48cd8102d50fb53d2c699afd36892d91f5e608784000000000000000000000000000000000523048c5de2d0139965c976d8c3328666e99c249104712719e992334442245e955cd6b14a1e3d666220617d78edcc630000000000000000000000000000000009f669d4e7d89fa8d999d8d5a6323da9445583344276bd6a29494a91174aeeb29132926a893d5a0eeee9c3048ebc0dd200000000000000000000000000000000099ee1c33d6f09a8d063393d2a8debeaba93027e31f7b23c5170b6747f56bd6e6494de966dc280dd67a38d39ae35a336", + "Name": "matter_g2_multiexp_94", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b46cd281a09b85d977e88cb2251cc61cf13181522cf1c344b114783c4fa22d08f2d008faea3dfee8ea7c56aa35ee39a0000000000000000000000000000000012b530bd34f5b31161355436c7dc444d41d634b0ac3292c0096558618005fe82d189e4b208a48240dfdb4d50ad44dc620000000000000000000000000000000014d334e7835d4bcee2698ca4f643f9692f28d8090ebb8ed8f67c60639c64eb116067e5e8a6d04a0c88c765c608d57ef1000000000000000000000000000000000578cb4e243b4b20c99afefcdc0e0a9e308ab4bec6d655a8c4981a711515c807067472a6fca30060d776213b8ed98d74e49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c8900000000000000000000000000000000046ad1212696acdbb686543d284d7cf2e1e8e8c10af76c6ba51d55f060c006dbab25d3a789c71c428f5bdde9aafbf6d5000000000000000000000000000000000a6a880d52fed6a45bdc61d9ee78d8fe472e76ccbe155bddd0e2a967f4d116bb9f2dd4c62cc6f7224b835c8060213ecd000000000000000000000000000000000786544589eda15edc433edcbaa46d4953da72473f04169ea64dc114b99f0a58181d41dce1fcaf7f3109f66aef02e53900000000000000000000000000000000030759c3bdeafc94fc8fc0b03ddcd96869459bf54ace74582aa06c179323ef076aef89c09ce8e7bf9109ab2e8c8fb0be79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f3953000000000000000000000000000000000e6addfe0db96a7377fcab1fb92183fd7d7f13ec003fdfe0740bcc8cf03d8cc602d5d808b4bc874f34944a65b249997a0000000000000000000000000000000014a4337107e716113d8ba0fc7f75e85edd1c132e2b3dadb3f9cdec1440f261513646525314b5c0de6fd372472aafe877000000000000000000000000000000000d472ee0484ed831f8ddf7ad86faef5443df8b943c6fd4c3f94c8d52d9eed6fbb53107170a60f25be52219ca4816788f00000000000000000000000000000000035d06ffc452c65a31f80c3f8a0c1e2d15e32d993ec06c50499bc0fb8f669acd3d2182ba23d942489ea922baf61dd49cd2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b000000000000000000000000000000000ef1f5f6b3041939557368d613279043d1aceaf5fee3ed90b3b756ad409d700fb41e62b3758c8c2d325db7a37f339c610000000000000000000000000000000004d66040a8e055399bacb6a1e762b698afbfabf789caeb957fb7a3dccb01d7dff5414e90f5a14961c4e980b298f834ec0000000000000000000000000000000006efe9e66078000c26d375e87ffaca643aae9cd3f8337f5718e0e268b74f4b7838f7661dc0ce60f557e162a21ff467160000000000000000000000000000000014ab782a3b2c06af7e9c2f28f1604cbfa8a676a874853bf38195780751d306936cefd1cc38c2192cb756e28793d2abb3e9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d300000000000000000000000000000000057270788a199a894b37a526a26bc4d293780d365a6b66247e7417884d543dd752ef7c89f2f4b38f4b51e6f9d86b45ad0000000000000000000000000000000000b59fedd6798487ec09d226a7406b27f04f7983075b4659ca6a78c6bb8aa83828fafdc6488518e2cba6fa4193de938c000000000000000000000000000000001105c18d92b4192833302814ee9b176831e57fb64b703ab3c2d3f440ab302c8fdf7ddc81933d3b1adaad16038dd6dc1f00000000000000000000000000000000020509b08e6ed980df29da649051c7095edcd4eed4ce95cd797da430cd09062a110bae21b6f73daff2053fc0289041fac818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000007be348ccf6a76827d3b9b33e7a89378c133c9b226e47dcb205ee061423ee6e1b838bc262a7befae7c15aa385ced00bf000000000000000000000000000000000689787c19192ad55b9c6c260a5ec3aa203ef71f0b746eebf10f82526c4fadaa8570936d7049c1a46e7f3cdc455a63a6000000000000000000000000000000000306965b09678d481aa4c754d56a0bb4565f16f7523cd0b404fbd39dfc3b6ed483f5239fa30f13aa3e87918ca039d5ee0000000000000000000000000000000000a2586143f9610a96eb0ef86593988770db5ed49663eab72f8c368b9388bdfbcd02fc6bee09f4fe055813d140ca0fa89ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c49645000000000000000000000000000000000018f46dfdde786a88e582ff6addbecb4f58e12c2625e3d6440f2e5b5781beaa95cad6f63b7d132e84700e7bd344fe3200000000000000000000000000000000185a4fc339a95a50551d53c18bb0dc3b74e9c164729c2b0d919392f7aad2be3ebae3b8f676ab81ea05233b3039918ab50000000000000000000000000000000015395b020a9d0bb336066c1347dd91c557b6ae7b8817cd8a2cba9e5bb149ca3401d661227c26d52a9be234faea894c8a00000000000000000000000000000000103d9d7e33a0767554e13b57dc756981488a3c7dfcc026ea84b35b0af21193e301226cb5a4760962707d19a95841be9296acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000018359aad8af59cdda484232b885d1b14956ec04b5584684b13a64d97b8310c283e5d66637dd75de405f5f4bc65a6879a000000000000000000000000000000000849fd55e4f3d4dfc643dfede6356826eef21290b84f7e8e226deabbc84273d95f7be5479e9656dc907ec367a7ebf8f60000000000000000000000000000000006ee01b54eb7834b4de53f821ad46f467cadffce6df09751b728d0952bfe615253d7ad173892a52c6181810a815bd90600000000000000000000000000000000161472d45b56dd9fd276fc607f2eef84c5c843ea05799e732d7eb6dce96c632335949e1b3a06815e410e919f4cdc3fb360bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf00000000000000000000000000000000107ef91cf3a3068c4e5644676f7bc7c5f9ecc361524bf3fe2ebfc606f22f8f83b38c0d4bae89f3cdff6119cc27fedf820000000000000000000000000000000006a7f7cad2fa9db8824e4e30da7158f7737d2536554b904ed835c37add0341c07c5220db0f9801da2587a456300c7b75000000000000000000000000000000000f6dc3adda42dbccb1d1e3fea8918f5572e8b26ba3011429e754edd28559b731853761d33777f4e767094f80e63d417700000000000000000000000000000000107d93537a79173ba9367732fa3a28113ec37e053cdf31ce6970dedfa8a9b4cd55238289be9a6f40319e3dfedd132f95537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa98302150000000000000000000000000000000019dc19a1663bb05ebfc0b7cc23ea9e07376de413f77e15a685a3f11fc19bf0ddf38d5671e2a5e6e31624cbcd47a19cf60000000000000000000000000000000019e78aae57f327fbe8ce794afc22bddde08ff9bc9ad3527601cb1fd5dc0b8ed8fdf3b210f86760954b48bf61d74162220000000000000000000000000000000013954a533bf871e99f4a7d81a8b9931c480ce7fc47260c3708c590ade42e6b7bb887d4d24aa18642d010a8170cf85d34000000000000000000000000000000000a561d3f64ba31a6d45ffcf1bcac95f8f665133a1e962e31351ec78e369042bd3afb0c43d12b3087168c1142107241f31a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a20000000000000000000000000000000007149094366e29537b0ad7239ce04bf49f253e4b746b9fe440dbf9b425bfff21064fce66e286e08c87dd83e22a3b499b00000000000000000000000000000000045ead132e0d03c842656cfc82a45c8b4a3b0cee7a5d071c5f235791ff7b5ead071b2c529b446a15aa8837aafc11222d00000000000000000000000000000000013159458f2123698ad4e7d41da47ad7d5083b928839e346a32f2307ee69f643ca11335d50e47d328b0079f1873cc7e800000000000000000000000000000000167edcf807ee723ba70e352367705448047c6b5223fe703381af6bb103cbb24da739ed005b14fab5699fbae6574505a7c7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000feb6f6f85903b3c8e4d6ec2ff234775f12727fdf7c35eade09c9773b004270f659b00248338f0b749d6715778f1f4d90000000000000000000000000000000003300794df19b9e472e8b869a2762c07a9251cdb96b508dfecdbd62fc3c3843b37118d216a64519bc3bdb71e40f9bd700000000000000000000000000000000005fa144135a5d6cf1c73055750ab6582b4c6d368566172b75902b1fc7a6f5de2a251ca7efc7ac6cc6c0bded14df02b700000000000000000000000000000000004239a7bfdefbe78116a588810328024b1bcebaf8f28f09387dcab66dcf2b02c94002df09d12db369fef9dd960783c0b84f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d00000000000000000000000000000000007c8a24005575a3098c12ffa65095bfe227ee59e5e978a7ccab7a9a72391fea61690648c102ce24af723945bbcafece0000000000000000000000000000000000323d57bec7dfbb4614c8c3b286860fbadbf71901fa006149053ea614dafd56b1f3d6a86fa55bf1cbdfe8af4ff08dec000000000000000000000000000000001180b2b0b9c4c12f6d06eec07bbf6f5a220722015fe5365d1c4ca9e58ac9c8f67964d8230152d7a2220575c756bdf8b0000000000000000000000000000000001969a364c447f07d0820586bade587ccc816e50696aa0c5ea4f1daf6cd577769a890b44caa013d93e7f21f5ea269aa85c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea0000000000000000000000000000000008a51c01c3bbed13d42a4da626a8b89e2811db1d83d7de3332b36881ad14a5c8668ece4f5ed2b71204810457aa3d75cb000000000000000000000000000000000658a56aaf627e3f776d3f03caa2c00425bf197c6fa20c92f563f48260109a8f935d0d1638f5039486ce0c0100834fcd00000000000000000000000000000000126d1964f2d964c290cd7364e175ca4a855149e5c4ba488829a436b09ee5e21f6c964e439739f15317873088726bd51f000000000000000000000000000000001803186f88833393bd853970ca4fe414a43b7a619ded1f9c830444b4d43a94e9146146e2284d690436b395bf1e3fad15a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000002dc3756c7f4bb47559cd720a3acf4159290d7413e0498877d1fe321cbcb7cdda90b6c8b4ef8e27b2642b82ab9b3174d000000000000000000000000000000000c7490f1ccfdd91aa37a3044d265cb0612bfd9c065c370adb813b2d96f02d44041e79921d1b8935dcdb8c83ea4460ef30000000000000000000000000000000007beb34bfb9ba9b6fb590c7e830400888095d1958b252d187c184de91f165e12599d66345341292fdcb662deadcded030000000000000000000000000000000001ce203d58bebe1eb5b7cbc6038f75b2f7534bce9f50e7e4c91d6cc5ac1bb68d9fd8ce99206c5ec92bcabb71672c6ac195a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000765acace3e238e51bdaa08c0f6d737c9de55b5ce9ac3523335f0d35bfab6f4e7e2944b8aa4ee031ae9d39d4db96e9ac000000000000000000000000000000000b0fd488a6f9e92c4bdb5e82b52a0035f9a0aed7f69ec65303632017669f34d11552f849326e4dd204d58f50f3ad124800000000000000000000000000000000033991f66588b5e39eb78c7cbf62a74bbde2fa1b7c96164cb58040f0887c485b372e0ef4def9d38da9c6f5c4df2d59a700000000000000000000000000000000187d41fa7905739078d2c2f8775394f830d20352a9d91e97568c6929412f356009239bc9e1da3a8c766e89d09893b5b5319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40", + "Expected": "000000000000000000000000000000000dedf92894c567ee656051a7f02384edc7206152af6d3c5f662ca02559a3cc349c6b034c6fadceeccf652a396dbec6c900000000000000000000000000000000089deb173bda620678247a7218408594efff7ab0cebbf627b93ed37e553cf944e09232b92afe2f5f31d29bb9ae442c26000000000000000000000000000000000178bc39b2ca8b032d3cde53d2da3f8797026d78c76c51381b377c79992f027cf55ba4e182773c99c99ea6293a948e5c00000000000000000000000000000000195d9cb91537e77e7a4be4370b982b6d36190342ef6ebc2250a9cc8ef6ef45211736ce1f41da899178c1adcc4927a9ba", + "Name": "matter_g2_multiexp_95", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004638ececd7f626a069b1bc3ad9d0f7cc71e5f0c1b11711fbfee1f81466b574f7a11de8161e55eb574ab367f56b9d6480000000000000000000000000000000013ef4f403f139771afe7e97815d3b3777818a3054d02125d3a25138e504c8c2c6696061572322aa19ace9ffd8e3ff308000000000000000000000000000000001910f776582f5acbaea626d2378e9da133b63afa087f25c2cfbcd1e7b34f6a237f2e9adccc303f9d5efe22496ee2ab75000000000000000000000000000000001963bd62098614c4ca2fc2a9e2d679c2b74bf9d322d34377cc63c3b8e7f8a4eb7d6d440d081e044606402fb3f51b0cee2a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000001935ecdf4f21cc6b510321b4ed2663e339954cd7399b9d67f1d9e2ea7fb9bfff8531f83ab59d3f0546393dc289ab2dd7000000000000000000000000000000000f390b86fb4cd4c1a072a83e1d1198a57a650fa6e94b69d983b693c592bc0c8fcd9a46c6883adacc4c7e2546dcd079fe00000000000000000000000000000000136beae11ea54ae26a8d69015ea7793675625b2013dbeb081a5ed877832849d67ac709b81fcc4fb322b262ec3776c0c00000000000000000000000000000000011f1df574f63f679b6464df463b58948fd337a4b3f159229ba0313cc040303345e75a3a2b0ed0dedfbefa89d8331d074f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b0000000000000000000000000000000001510378fdcde4027999edc99d49bfb46423ceb0d740829e310f8a381a7632fd0d6b6aa3533c2702a5ca76d386ac0145000000000000000000000000000000000bae237bfcc061552ca07eb14300cf557c974611885aa6894f7933f7dc7a0a1cc3b5587bbe1ea5fa604e3cee1db5f7c9000000000000000000000000000000001743207a1814990c798dc3de272a02b1b194a485bf09faea382dedd957861dc15cbf981f9906cda50cce2899785b9a6300000000000000000000000000000000106f902004c66c80437392e92cad73bed6b73010bdc7b6a75de4c01f0b6fbe5b8d1f47378279bfa42b3af05120854ced07f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e400000000000000000000000000000000123362e41268f7821fcd2294b032c6a51c6d80506eb052ab6132267fa248a1a60c3e4eb2e75a1674bee1c9d46d82b9180000000000000000000000000000000006296670461ca67081cd76528446867e1a4905f88742d0ed8d1f7baf86e0a5e5ee86c8b0eeef07c14dd821dee0143bea00000000000000000000000000000000058bff9544e4e02c063158a52a68c93c7544e8157d37159dbb99b51b09e3d8f5b307bfe63a10fa409e20a35219ac244a000000000000000000000000000000000b135edfbf53187004d0977db94eeabf426ad7bea84ad76c6ac771fa186a073d430af76d717070e3c4057a7a2da095984e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb605000000000000000000000000000000000133d3a223112dc5665e78fba8fc8d040d133858d984e66d2382d5e629f9369dd127e93c7a4da77fad98a0520ebedfeba000000000000000000000000000000000e88515db391bcdeeed2a9f64d27387af0391bf832164fba79100b560d8150debbd703c140dae3ba9b1ec35c1f45670600000000000000000000000000000000042583722c69a19f413392c6a2b75c8ca969be85eb951056d7e1d94e046dba49c346d5774009b8463a40b0576ccc1a6e000000000000000000000000000000000ee61a9eb6ad497c57405a44d798868e22b4fd5b8c480e9938cfdd3f1817eaaf331a9988368680158c59c2801add0a7a27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f228900000000000000000000000000000000020352de9b4e8ea1acc8589bb22e23dfd0ae3a80de9e21bdb3f6391dd05a012e635de9e1f5f450bf4aab05728c054f8a000000000000000000000000000000001733593b94ec800bb59ed97dacbefda5ad882a8023346bdff8f471c5613c67247e27d72cb4ed8cdaa0f236018dd2128a0000000000000000000000000000000011f272a3b25bc519fc3e229211b846042031e22fbed22ecd0d1a4ef1d05feacf105772d71157e3d7293575aca257cd5f00000000000000000000000000000000153b4b4d7d65f7bd13d20fee4812f04706c96cd1a0d27b7e139c47299805e0ac86e8941aa38d90331c78a61c2dd56aa3c2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc00000000000000000000000000000000143e1f6dd9397f0e89a46c6ec995bf6c87ec8a72b309f050dc5b3134e00e2a16327767cb0573ca5ea9776215a5815df500000000000000000000000000000000186cb3af2cdb4562bf2d0c180079547cfb345cc3943fd7f9203fabbdc1547079cb9ed854f9b1a47f513e318cd409df83000000000000000000000000000000000c8c9197fa5a1e66b371a653c5d18c01fed8d17a8aa92d89b2cbd954b9fd2931fa61abb6676e4851dc9481732c6195610000000000000000000000000000000009026b259e840cb5264f6aad6ebbb09661f5b6d980389817309aef99e4e0cb228d3a7a06e6c25bdb1aeafe5acdc44441911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000007bceb74ba86c07d0fca20e4febd3b12b1fe9f786c9a5da0531550244f40261d7ce728498fbfcfe16cc235db6ed42e11000000000000000000000000000000000883104ffcc0d040d70bda04dcf67c1197c39e200d4d9daf5f3c185638a13dffe3dcac94fce4175187dad867e8d2b78c000000000000000000000000000000001404e48e86f199486db7d40076cc8dc4e2aa2c1b6d4bed8f027512e2c71817905b26ff4f0551f9c08a2a7a27b2075b6c000000000000000000000000000000000b789a6addb98ea43c0f9e85831a75b8ee1977936c17929fb45d4c06b4f1ec33b9b41e32b52cde542c9e4b64d27c686cfd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b000000000000000000000000000000000654e7f3985bf90dd1e3169382690fdc0f804eb6384ce407a060f539804fe6e0451094abaa0dad611c15d3ee52f31a92000000000000000000000000000000000deeec957d58a2246ff8f7b7448f5198647576c16c1717369ad155ae36d5a6bdb42c8d6a1f0a095891fb0890b6203f950000000000000000000000000000000013a01a6ca4c296f59cfa4a5f5399d28af76ffcb8b218c861d5e6dc603e140f730f632028c8da46c823d87bff5ca703280000000000000000000000000000000003698f659e86b96613ca74a480c81e749bce4b74324976c1d241a0911d078926fb2adfcc3f901a7a015a02f525ddbb808118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c0000000000000000000000000000000016ce72e1798ffd84b52ac664a184c6cf5ce1ca2aa263c9d056355cf610517e9c7bf7f057c342f6e3ae801b84c2082c0f0000000000000000000000000000000010992af1438eec10881b5e2e3fa3b1e91b6b5313ea58dcc0cd2159f8ba6ce5912d81b38956929620e04b3596f6835a6f0000000000000000000000000000000014315dbebd532d0c835e8e85a02c0814574cf040a20c18d06573718223c8ea15b7ea69f0cf342dd09037258398ba4bef00000000000000000000000000000000136d13a83e72525b2d4af54d14d5e21d8bd9bed18543836b02ae0a7e51d433c93aa1943e85f978a8a9ff4454d8c5d120c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce7730000000000000000000000000000000002c1bf7dc75006c2941b89a2de52fdcdd1b4fbda5b14fe3fc165915b90fd9d93cdb8105898ef59d5b374707f0afaccd600000000000000000000000000000000049a16efcf81de84e443666bf990f6aad2145f9c9c2c61a752e256e8f447dfb27b462e4553544971807f909a666af12f0000000000000000000000000000000000aa4702fb69d791ef958826753d3f74f61c7a591ab94bb6c1bd5d82d94c5877121ecfc1e769d0d16ebe491b775ad96e0000000000000000000000000000000008cd7f2562eca6c53a37382fcdb04be53998f45c2241bfebac3d1fb08d8e1d4df3182f2bd63861d0de72d58072356ccc982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b000000000000000000000000000000000a6b30c4ddb692ae33c903693cdba00ba48efa48e90b9cec9dc747004e57a8d5a05b5522634fc0de306d38c28390dbf4000000000000000000000000000000000601341b3c4057767a910bf30dd16324ad7abcb55b7e98e73584f26d7f87d8a8d24ff2113c12ceb3077bf65e0912b2360000000000000000000000000000000019dc9c50f613470abdb5c763c0272e88e34ed38e617d6757f4e70d05b8ec9f67a023b4ec1363e7e60e38cff64e18f0510000000000000000000000000000000013fb1858f7efeec5fd03d9f7f4513e3e9103c340eee7bfe48ed3cd3dd073b96add9450a17f12e161f1d44669b1b2f813000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000d6c135bfe0fc7af93571a69b7c37ba691f051d69582cf159cbeb0bd59b48342172a82a3eec2e3d440805934e1574f2a0000000000000000000000000000000001b04e56cb3bb221caedd3582943f89a33b955f624f9e473941f1dd987f2898339142a654d11d87bf8bd2fd0fe0d4c1a000000000000000000000000000000000f185fd420b761a1e38d542558b0beffba916f369b37296fdd8878a7c3d2ac9d3ab1d8e45ad799f0d81bb439b5e5058100000000000000000000000000000000002d10ce460c414fa1094ef2b7de8f1ce024b6d086d10067be0fed4e45dc25c8e50bef361d39a2743be1e1ea4fb7e2ef773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb2000000000000000000000000000000001972ea36bae504d7047639ce6e0e6c3b16afe89fa3d6c6b33c910c8d4b70782d8165912e5bfbe8bd84f78f9f23f7f956000000000000000000000000000000000ae55c4fc1c01f1bbdb060191e8551a7ba5ebd3dbadd138202090d7dc6765fd1ef5fe8204ae76a8bcfa03ee5985a35280000000000000000000000000000000018ebed295805e0fc14f1c7b0e6ee12ca48cb7177c1d367a613e0d6cfaaac5128fefce0e8f38d4e2f11ae0d327be466a400000000000000000000000000000000157068d89fe48e77e0f62e3b5b0293423f35c5f4ffb9e0577f5aa49e91cea6bd312a0e65ec08af9c1f53de6499409c8d759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e00000000000000000000000000000000120e4a8935c08032dbfc19a43e2a770b12b05cf1dc229e12f683f0d7f604bc13666bf318fcc38038b618ef83c9448b870000000000000000000000000000000004e3f851be46bd85f37c8b1d84507f4ed63ea76bc305cc26a6f4cfa2135d5affcd3b319d9f57619e21c964c6246fc3f600000000000000000000000000000000138733f352029373b19e1c40d5958a04257e2b344770e1bbb8f377bcfb1c7225ae7a8b0b0e57795ec06a08e13c90d7de00000000000000000000000000000000093e85783c556a017829e28bc42b607b1035890fb9743bf0e279df4dd8a695c1dd07a76a213087c3a8a7e614b29b7a1ecce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f0525302938000000000000000000000000000000000be1ae00f9ba0a2e57f94728508e0029b1bafd52c91ce718ba41790a3541117d1a9f846d68440978cdef016c3b9ae422000000000000000000000000000000001947683154204c9fc93e3aeac17b417453a24d01804e8acbf6f67947f5962dce875f49d05e6ae65384602828784f852c000000000000000000000000000000000859dc1c00b49cd1292cdc65c6aa4b11e27637b949c7db508930c557ee3ce00f98f9cd3dd0f6d73a646d176a91d75c070000000000000000000000000000000015a7a6984b5f42aadebba1e1f4682aaf1a2d01c9ce2afab7fed2269373467787bb1361b493dcdd862180e9159ec2ad5785e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f", + "Expected": "00000000000000000000000000000000047cc33d9decfd059724bbb014fb9cd95de187e2dd29cf4a9bf9ad06d415e2cacb5a4e49266c13e0c38f2563d1a21d6a0000000000000000000000000000000011c79d93fa870d541e79ad4037c20b85d3cec053587f9df60dc11e7dc2727d79059f75bef76474c09fe61ed10b317cad0000000000000000000000000000000003df3f0db20c5ffea4dc9f4d9333d76141447bba50da18e521e89aae1e63750c71b392570d79e597967dfc9952e903c60000000000000000000000000000000014e83ea645b1019ac2dfafe370f616af0c5baeabe217ac1f9ecf78877717475b25911b339557422526a8163434439923", + "Name": "matter_g2_multiexp_96", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000cd1d25f285c2073175ecad5bba4987cc52012eadc7b19dbaa20fa82d7a6cfb8a52f33469b6308d921eb4b3b23f7022d000000000000000000000000000000001707b67a23d9212d30c06f26f0040c38389b185057e80236d2c828a8d9ade4f72eee1d6eccd78e4f4d71e2c28ee9539e0000000000000000000000000000000008e5c04effd14d915b9afc2083afa2b6d4008cfa0e47144a41982d8b5a8e77922a2609384e2c5d18c871ae24a7d505b7000000000000000000000000000000000f414acb056fff2cd6d9b408adb6eb7f34c8f66a66ee93945a3381d46c2d181613047ca6d4067614c190da444223cab685431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c0000000000000000000000000000000008ea841aced2d0b8dd688947648a8ff18d0f6f03f63ee1c331f126dd4fc0da3d386535156b80902bdc1f65add7769cd70000000000000000000000000000000017a32ad2763d99c38c954f62466e78c0332e48875e15afbbe9c78376f1bab12346c73a573738353e2162d3928091dede0000000000000000000000000000000010ea738884dbfe5bc35d031bde9aa4109b1fca529502e236aebb5ad0bf71dd2f3db250d924415b0bfca56519f8ca5d290000000000000000000000000000000013699e29cc1871f51a469898be8b3c732b5cf7860286e655e65bd8176832804d17b48d0ff85eb023360db78162581297b6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa00000000000000000000000000000000004d504e62b2825651381ae862fd33407309851d5291591cd0f541fd092800d709ede00a9134e65ee752eaceb0a344b50000000000000000000000000000000016481efba290c37aa4ecbf940c76ee5df199b0c1f90fddebd2db28120bb5a14dd9f4a067b6d4889aeb683cca0f6ab337000000000000000000000000000000001400c89942cc63417ca4cb05c9d81dda3251e5611c27fc7727c3e803170672f103bff26f7216a0b646533aac3171488d0000000000000000000000000000000019889641be9db08880543ff476b9d4c72167092548ba49a3f3ace4518d3874f4f7993ae7b8cec90f092f144ec9d66c1a54dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000001927fe80adc6dbb581349c603103ad8831e645d9275af8669939b83829182cc6e2a30df2fdeda6d3aa2e2a6126e00ba3000000000000000000000000000000000b6d7934d5ca1098a85a0c60acca075220105e221b802b1be97c2967820bffc2937fc3278ed0f26905c60d44d5fd8dc000000000000000000000000000000000057acc1379f23c0d1d37427d400eb1b0a89f3736c83d3ffd797ae279e01e2acddd84082f13f3c8b8f1bf7c275702a9c700000000000000000000000000000000038dbcd7e08d34c553850a52336991a7d48968e98057e930790d78b5c6368eb2fe01571b60c4aefb653ec04122953d56eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a00000000000000000000000000000000014052031b88af979b7edb06c99c2e46bd9df2c862c7e1b71321754841fad67fc3242b51141e49ad86b61344aec913f40000000000000000000000000000000014806a86d078ee9bdde99257b67f50dc2ddf9bbf01dde931742ee6f739aa986cfdca06cd32d23d86f2c14c3b09033d29000000000000000000000000000000000e0561e795d35ceb8bd9e3b276406ec1f697a38ada25d1dbe08715a28bdd3d6ce6e0aac01f7dfc7c2b403850ab213b4700000000000000000000000000000000146a65209b09487e00e356e3b29952280ebc6a06343c4ce14efa0c6281bc2482698bd02295bc35125275ff5f5bb867dfb273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab8500000000000000000000000000000000021592cd7f4cf9cab3be53561c889c9ee865961aad51339f6393dd6a0b7dcc8a7c48b753c947b15cd3add01abd3d76d8000000000000000000000000000000000f9e1a80bad58055a8577700c177889c4d702de04343c1202eaed9485a76493158547b20bcb552b66c42a0c86df809ed0000000000000000000000000000000013908dcff1945cf06f038e3caac9a7fbb3a6466ca18627e93468a875759a2b5599a96834ff21fcd6bfbba82b79536b9a0000000000000000000000000000000001b6354665109c5a64613c3bd7d805b3a34098708f3d41c7b77db031ac6fa0b2d4e2f2f70c84ac78687b0c0f9bf334771342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f0000000000000000000000000000000019394063202186e141dcebce7b8f0f267ba6057a0f993bb1cbe22a5bc528323823bfd1597a87017d478186a18f09a47800000000000000000000000000000000148437bcc43d432d70b47dadac8e738616c97d38d0f84dc132599626612f7bba74988bb23ae47ac15e6f70c059d607ed00000000000000000000000000000000180851594710f4bb5be7ae0104a383081c50f59e4e048614660ab5a4e2661e171510f5b112d8cf97a6af27d56d137c860000000000000000000000000000000000599f3f82f29b493ffe9ee3a8363b9a599a5ef3c9c5c680d4e003f4ac5a7de0562cba8e2a4c6da7d07cbe86c3f7bfb85b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee82398125000000000000000000000000000000000cfdce7997601afbe484901893a1b5fc0b83e8d238d41d2f889a58fd4d884df1c667a000b53b587df2c42ad46aa2c3e0000000000000000000000000000000000c50bf3e06400cb10494cd09bd89f3c96ff49c9f74dd5325f9489ed6be13b59bd7b0b2351411ac854d430405b8a2a3de0000000000000000000000000000000001db313a34ca4073e4fa2287e234ac32bc579742de22e5218f7873b922f5804894826e6054925a394f125fce850f33ef000000000000000000000000000000000e0627a66d286e8d4d3654b32fc5f552a7ca12f0bd47eb6dee0dde22ee48165247c067a0f4c3d422bf3562d38a3c0cf1249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d170000000000000000000000000000000005285ba39f5bd981fce2fdf853706d70992acab2dc6d4c4198144fab397392a60d631056b580d0d98f3f350414ce554e0000000000000000000000000000000013bddbc1180f155872376fcdfaff2fb12d3d9645b81bd1475a5323ea855cea820ed7eb693791caa9bd3fa5c66036439700000000000000000000000000000000125644d32df397def58dff875d7e3f14166e765ed49a3991f45b38d74db3985fc7f5052058d85594c8b97afcf850e11b000000000000000000000000000000000fca4662eb1b39f576ee820385fba88ddd2fc01fcfb9d9f874453ad725cd5defb357be028fae97ce71bc5ac26d11c1bac014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e8950000000000000000000000000000000015b97d7c74c8ec102083b41d7ce5490466e1c0e261b5ea5c756d3f9ae79dd2d8ec6eb5075cfb76dfcf7bfdd80442f7d10000000000000000000000000000000016812f845faf96b8b69ac7a6af3c8947aa25877199e3c12552527706a17b768bbea259ea61ea82c4624a96cbcdf4040d00000000000000000000000000000000123ad55e5cb5ac5bdd3ca0a5afa7c3f8e4b98ad91a205f073fb546fe799ffc57b3c1c3a6209547ffc6ef05fd24be6f5d00000000000000000000000000000000017719f31946aedabe0e9d88ef3f90eb6ceda884f5e3d2ece368373785b2d8bf0f9677731803b25accfcb6cb716e0aa4ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000000497e3480d58027c780f47cc35a121ee0cd76c4e84d9a2f9002c04a1c286be990167a0138049ad70467132818f48ec9000000000000000000000000000000000ec0ddf938553105400f70989140ca322d996f48ffb1b35641ca36a6ba9ac1daac1603c100822f80cf62ec3bfb442158000000000000000000000000000000000a0b6ebee28a792df46d2f727af812c15fc91a471e0d8e34b25b26048f3b9606d8375b5b268c40fb04ef8f098e1d03340000000000000000000000000000000017843dd19bfabbd0cfa41fb58e70a8900397d17ccea783087ece90962560f5cf090e8d9eaa873a6a6ebac45219ea97a68b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000b27c82d71f7e4aab9a68596669596df3f62071e921e131ba4d9e59d8d81d370e077e93a4a6a43e059661227f40b38c800000000000000000000000000000000093004917ceb2fb4a1b33960ff74943d520f86e83aa02b9a6c85e4b9a489e9331863cd30cb6ad6f099d03289b4ada5520000000000000000000000000000000016f04e35186c7deaf730708e1678089bf3e73c1164bca24bf8f70c4f6cccd5bbb34bbb5dc313ee428aec4ac9c638a01a00000000000000000000000000000000011052348cec9dc3e85e01abcca5a652461f08a9f5d72b3fc27140a6a571137f0065ed7ccf9ec8cebe314ad9a214d5ed94ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000007083dfb0738d58ba8933a1f60283e5da8bf90af5aae4053ca573ee7223d3b80e4bdc30b4a831ce6af9f52f393e9742600000000000000000000000000000000130c627b7d3a527c94cfeba9f514e75eac047e1b6088c082229a8c95d0765a0898ce1e45694ca2c7935bb8e41e44e8b10000000000000000000000000000000009610645b074e652a08f2b89dbe594afa3009d795ef211f7c036a56274b1e1bd69a035c4f356b6b21f69b9cec2bf7c32000000000000000000000000000000001020f3cdef468af700269aa1e9d928e71b8c521f23586c9b0155314f0073da7de04ca41ececd5edcc052af72c05f0e4bb1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000123540047f0768b0af841aa4aeceaf3dac31ea832daed86c8cbd1d33ed0282c6f697d5881f9022af032e90ff82efb6bc000000000000000000000000000000000113daffbe413075f5f4f6fb42f37b6e9d5e5822aa24d6f865792f63e6078584246bcf8b17117385db1d6233974f6ed9000000000000000000000000000000001067b46fd221b6995d25d4bf0adb088e0554d858d4e5d9d6b59e1ae2a7d57188d559b0208918a8944aedd62b1ebd4f4700000000000000000000000000000000087dae77e483d5c0baa37b9b96dad5ca92b5869fa253bffad24dd8747446f7ce60858b52438e58233210d86f470f765fe670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af8500000000000000000000000000000000039d09a094d655c139cb9714aa258d9548473162548048b0f07c9317a41a7e5dbaa5aca156992c8a509d4071d9ae4394000000000000000000000000000000000f0273a38b1b9d006efa43c15a53f026587a676912d0275968608519e97994ea9c6a147e377f68b1738ebeaa178f9c1000000000000000000000000000000000132cd92417578d2e46884f1c1a1080b1916c8c8404d2533a4de02bf8575c80ce7e8097c2ddd1f95737355521c0ec21ce0000000000000000000000000000000019adbf09a268a3ed8eff936d25fbe8af2874e44d2580c7941dc14fb89c5da963b468a7088c4a763eac89f4d15deaaf5e54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000756dced467ea32c3c425590b7690a45e250e464ac6927ad3f5d2d8d2826961b8dd7572db609375c8d06cc3b9bc3a157000000000000000000000000000000000b79b4eecbaf1d0f8a89f9ef8fc144b3aff38148ae260da7c20e9dd3866d946585df7ed12c8b7005e7b0e1387c9db41d000000000000000000000000000000000afc403b008b70e19f17b1ef37c9c396577a585b6c34b23d09621b891efc00ef9460c3f4b5f3e851ef63620dc06c824300000000000000000000000000000000024e06f3f3b18c026a166c41f75d7bcf699480f5b6811463c27606c9ec1232fd249a46235b7f5b5a2ed3b53231b333150563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d", + "Expected": "0000000000000000000000000000000004f2480d0b7e84a996965b76055f6879aab5bab26656e4e5ca7220adc17f14b5946047484327bbc5952d9f2ffa5f92af0000000000000000000000000000000002f7260c55c500b54df4391a55eb4adefa7d19bcbec82a107fc0d222c898b97360a679a02ab3023ce2ebdcffd125ddf30000000000000000000000000000000002cddfa94c8f6b3f29c2fe018b1f8679d0e98c8c2656f86f327b9cbcba574cc52643ab423b458994a03347460deef6570000000000000000000000000000000014eb4c84f71ef5935e707a11a92ba34780677ac7eb198e160585ad92aa5c1ea21e3a77be940fe344e7e170680e2a8d53", + "Name": "matter_g2_multiexp_97", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c2c4c039047d297049afd0e8f0375bd4294d628d3a22078d93b684b737e8c4b6ad3ee544ecbeaad6b3c75d8d217f3a20000000000000000000000000000000004c77a2c0943c6f997ce2e785461f8ec253c47273ded4e1af43ae882766ef8c168e66d831abc2b3b3a0849bbc210cbd40000000000000000000000000000000004456a6c267a5cc6b7d9a9f573270855186a1b621cfdc465fe71ddb4d614565d9d36b13985b31396587919226843c6230000000000000000000000000000000009487cdd8a0cf7f40e9087fd3121cb480730f4302339d25fa12128033239662ed65579a59b837bf1bc5fa87db15b15335b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e990000000000000000000000000000000000ba43205e8392168824f77bac344d60c1a9a0b14ab55538c3bfea4a64984cf381a2f61c64f1ba1bcfd8a7973e43f6e80000000000000000000000000000000000e95e5ac415c3e3e7c9feb6e7a2af3e8189afca06ae1fe54bbeb31783810860921ab3c76a475fb227b3c8299e3f1caf00000000000000000000000000000000001e3cb2106a23e77a126013087751c4d2a419a51beedc3a33faa6c933bedb3d34ee9c6450c583642426edb352e04da98000000000000000000000000000000000ab5af4c98aca1fc3fa55355351d12f3bc639662bf8b5b772152988d676b00ef39f767237a2fa3be936e83d1dd77da86a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000012f3d927316ba638bd6294f7dd2f3f166d20285ee1662ae4dc145835704a17127078343d26042a5c397bfef31754186200000000000000000000000000000000162893d6252361c340057bcac31986458b8b55a8a4283f5a06ce1730098f9838dad1bca264374e7261bb9d08c177c1820000000000000000000000000000000017264aead0ec41a079827296f3d32c12adfca7cb6c674070d54087438d57b6ccca4822b2337263e60075d469b4ce0ccc000000000000000000000000000000000480cae035bd3bf1b4a4a766bcd5f188833e9587e1aff0e1f10e36ebbf2f3ae76bc0946e7c336efc3ee00bf42e7efbb9677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb607191000000000000000000000000000000000d13375356b1518e37a13b43b7d192eb74bd69636f91c570c41a741a8763c03caf8d13c7364f57c867a4a3983e88060f000000000000000000000000000000000f6f78dffb404faab88ac7373e0c765209c0af80514d438e18393bfcfeb60d9a5e13158d399f43162033571ee4a75dbc0000000000000000000000000000000010c379860638ccf3b6cb8479aa38881b0004197e3e367a1d5ef7c7fcf075689d283b87022e2825b5c789ac6a448467320000000000000000000000000000000002dc392872cf2fcd8e196f10c1ded175300070e4e38aa58c89c81e1aa5faa08d770a5ad90a8295a890551f9329a13cee53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac0000000000000000000000000000000010383a21acda7c8f3f3be980bff2d57fa0a5b2dc424164dd2ce53c0b20ca399d6227913b7b550462180b01c231e4813200000000000000000000000000000000078aec90354721f0a31e1377b3652bcb1f388ab36f1866c955f3ea8dfe6ac2c25bc4cea14f54aee71595c2c1bd2dc4910000000000000000000000000000000007dfeec77213d952c183452b98ad936e8854608d950c0c1451262cdc7d6de5aed0db07a8d74b3e8f674967cb4839c4d00000000000000000000000000000000015c09e4ed2ea76d10d196f7a733ccc272b94dc436d6bb5fafad2fae4a96372c2c6f1325d1554746814ae292d8e6b1e3634c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d500000000000000000000000000000000138656fa091cc6613b1fcff04a3efb4f9c393985b2c78fa838eecbbbb8b6dafd88d9c72441f9bc735649480b5187acac000000000000000000000000000000000a35cec4819ca3321917cea5aa589db8cf61882fd1135031dc41a8207a8e71d326312799291b160a646148c382ed086b0000000000000000000000000000000005b6e4c02c9c54630c96271073513cac3a42d47a7272f62a21c7ad4c85c19b60b70d04719626cf4273f6c5691719931700000000000000000000000000000000166a20da734a47d7e28cce8f0c2d679fa6c738a7a1ca9089dc67ba2b1c92a83b024b8991f131e7e8802a617153de4554bebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000a4432a544deda931b1f62759320ada2963062e722bc1b748c9bd0d026ffae10f228be36ea0ab076358924f4c06b6feb0000000000000000000000000000000000e955b1b1b28d2044b6be371c58bc85097c77145b239e913bb0729757518c465d9e69338066f7496aa6a2038ea604f900000000000000000000000000000000017ca2a7d52c3a82ab8abf9fc1bc187389b6e4904e161541008e5b3ba0981870e01060d1272a6d59bfcfb294c942403f000000000000000000000000000000001870649a50e0978185551f213eefd9305d33e92b3f8c39752b6ebe18ae86ad97f92acef05971dceee3b3729becea18168b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c95960000000000000000000000000000000013713effa20d5039ced751ebafe1516f062f11ee05ffad37281cfee9d7a49ab14c065709832f6674bfbf2c9f379bc9c9000000000000000000000000000000000295f7ef148430209b48c292b024474f05036edfdee082c56aea05a62f1fba3ee7a540955423f78614c8385da8ef60040000000000000000000000000000000007408c97321b6d7c27e5e442a9e35b054e743c34d845874aeb1ccf4e903ca7803ed7fb1288327865f9e0ff0a388e92b400000000000000000000000000000000081808d03722a2d48846a693059c2662dee614f181dc406825544d30a6adc0f9d84a712eff80bddd4a27a036e4bf7359d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca000000000000000000000000000000000880b646a674723c15b240ff56d2031e5db724251b1402a68df8b26261ffc9fb60a81abf165c6832137dc7a7293142d200000000000000000000000000000000172354b62bfb8d388b5a984411414738302725a508e8abeacdcb46454371d5e9cf762028fb65921d5c3dd8c67d42a981000000000000000000000000000000000a1af459bc3122dcef78359e468f4094d609ae3da09ca5aa6efb71a7494dafa2373a3906bac1f324d98b3eaa982a27d500000000000000000000000000000000092ac3b47253c7f090df076914cdc08a715faf153e8e365392b4859fca1db14d3f7fb998c97de9ad99b7d0b357252f086330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b80000000000000000000000000000000003b23eff722c078a781771d8b75d519e7a062ca3e4252ecca877845920158fb20d79a9ce449d9087426b113da0091826000000000000000000000000000000000c9026e8d3fee6282492393db504a2c41db19d8fbb83260624b05ba4107d6cb2c90d645a3c16862b27cc3fcce9bf89840000000000000000000000000000000018b8648d0a42285d474f809519696df9e1ad5c35d8e848ad74fbee37425aee8844a8be8cb4d3331670ee294ddb9a290200000000000000000000000000000000068cad37ee8578f4b502ac2ef4371a10e5432e57fe20d0cb074dc427831872113d3514a0b199d813b796b8357fa2a3dbc2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe3740000000000000000000000000000000013200f0aea4c60937be47213b6149b0ae76767f3559e0519f774af4a5d9431e2dd7ea74b42cc3ceb28ccf0d2f01116f30000000000000000000000000000000001c5bff08fd16ecb68f21289a3e7b9a2ec5da1357d604710a18e78ab780f8ef0343d5d9ee7f7988a009329b17e498beb00000000000000000000000000000000125453772eb9d1335ce4dbcc8f2ab8426fe89a0e49fec51d4e96718a38570aa82dbef452368141be2df260fb131c50b2000000000000000000000000000000000432cdd445519775b9914a986a0941cc829b4a15cd361df9ae7129547b24f7a6a15cd8fe9393fa1551db2d761a208b8ec9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000396cb6d7b44f92b716ed02985d351b4e8cd1bbb95f239e4f29d7379428470be395e2faeb8e3a910007aaa490d3c336d0000000000000000000000000000000000ad0c0623fdf50c2b504777554dbab3cde1b9705e976561873d7c22b81f49c7654a7c76e558fad1518ed73a0d3c3570000000000000000000000000000000001241d5bed68e02a2ddeb3ccbe109a161abe81edd7affb72182c5163851211c4763e6aecf766053b61ce575de893985f800000000000000000000000000000000183696d2a48feef6088f4e9f75a5055e8c54b3813658b593958490ddd4245ac495a8ff966861b20f26047f07fa8609a0c2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a000000000000000000000000000000000fe0ce41aa9e7cb2bcb4e01721b7b1d99fca4e9b7c4df09bec00bd346fc57c25118ba70d5333b7f3eef2659c64520a470000000000000000000000000000000005c932e09c62b7ddaf3f5c420c60740befa7cdff5bb812e0f089c45098d71b57004b7a207f0cdd34daaa3282cf6e9f7e000000000000000000000000000000001874200ead9776c1ecd6a54a57e5d0f9577910a4b3afb9b051622f658fe3ef6cc5070af60e7ef910562720e9716158d6000000000000000000000000000000000c2c657e58e400a67e59deee8c28234ff4688e781a2f6f2f0d0b186a5e4012695a522dfa0770cfd543f55939a05e20b09fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000f0b561e5860321249b9ff434c604d26c3275824fc4ab9c1ce5c5858605ddaafae83ae27e523bf6006932f6c7f33d0a7000000000000000000000000000000000b47aab85bbd909599aa85c5eda363b67790ac6729fd8b1f4f53f66dd796cf2fa3496407b1bfaea4dc8eae53519054e70000000000000000000000000000000000cab1ebd23bc05c53bc9e8481c469eac3ee1b140af545bebed10a8fe50698d2ed883219881929207c0addf2f687198d0000000000000000000000000000000007742de55b799950e6f786f4eef45d0fb67e0475272ad68a183135b70047abab6c2ed51ede16c39be7b986df334e9e75d4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000923861332988bc843a65ec5dd4637f9dca8a15e71b82c780fe60d768213d118d8948ab554e30bb9253e900a9b7d87f200000000000000000000000000000000132b1faef49e7966a05783ba526e71134bfb577b13116548352da37e91e617d7c72ed2645e672ebbc517e079247dfb0e00000000000000000000000000000000000a46a8893a194ebfe077afd05fb25d4680f1e4991a3ec29475fa5651d086d20b38136155a65a4c70af31de5a78af59000000000000000000000000000000001344eb957594028b4228cbdb8efb03cc7cf49ec43b2ca5481eae1df6f2df3d5be9a7c4e4e78f8c39be546e29a83c92f49644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000012d7111303563a6358e5ce9155d7a153b5781062c2f6b919efc67ddfb4c61ef03be8828ca6339397b84763a5f8a7e8330000000000000000000000000000000010a2a0ea9973728d3fb1b5906ee84b2635c687c11398ebf605cad30216df3b7b4e3ee1653d4b323a690e6ba614ebec30000000000000000000000000000000000b93d5de37b892d4de9407a820c73ecfd6cd9fa565db82e7e8c14c8406823f705ff0adf6bd6add5ddc5f72c91e52e840000000000000000000000000000000000dcb320ceba5436df8f099c5a77f34376c96d830f5e8ab80667d156d89f6bf8998c148ef9a53847ed395871ab86f6d280df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d", + "Expected": "000000000000000000000000000000000ff3e299e6b9fc488d6db991cf9d226d330846e87c4a5cd3e5d4ac955bc2c3c2d1ee5ff230098b48f594d256495f489800000000000000000000000000000000097fdb8fc95d64c7070d8c71e0fd2a933d1e1ad3fefa230f079bc5743828317cd1b0c6d4253ba9c3c6ec6b12d53afa700000000000000000000000000000000002448bbb179d82eaa453cd5452752b9a083b52694cc65c5d9b47f72eff118d7aa06b0fba18eafe93d2937d7399c001af0000000000000000000000000000000009dec4c0aff2a46b07855c87e08832811633709ddfbbc30dbb7e78b3de43bd95e383baa6ff4c58188f0c7643e0ea5a40", + "Name": "matter_g2_multiexp_98", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008afabec8a9985cbbc6246825785654c1d2eb7da5a01f76c4af4d0096b9baed3c33dbe492d14a6f9e762f06eb3d198f800000000000000000000000000000000027c592315dee4bcc892acc6f41a6eff5219c308253f7cd715d0e4a32c03c6d0d0e8568e146e9e799ac3025486c77fc30000000000000000000000000000000015b4ee27a3aa518a1ec1b447bb8f9128301c85b7176296d68dad3339b1dee78715b2f031a7fb6ba376145c97ceafeef60000000000000000000000000000000004b7e30ec7cc024ced863ce511cef3cabe954a4e5843dd636d776645a44225a36ed7e153ab5bf5d18f23c6444751875c8a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a83300000000000000000000000000000000119949d36d8d8e2bc1c26ded5f5fb01225a980a28b934ed3862480dc9297a3758e0f08ccaab3a09b5e5c0e4215e3246c0000000000000000000000000000000004a82dc22316ee6af39d937b662d1f1f2dc855c2ca8f33ec3274d833e87d594633fc7fab247911e0f46564397910d6ce00000000000000000000000000000000196900a09d8504ed960d41f4a8a2cde2e5dac61b008d3f6eb47e86d7b2ce6fcdc0f85157e3ab1571094d9fdaa75d0d500000000000000000000000000000000010c52ef9407eb4ec57844aebbcc3ea5000b1940d035dcc2a873327affaaabdd79e3560cbd29c63ce04f6279056d6eed1bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000000e43655ae05dc6cfa93113dc26cea895d1c5bc73f20454c7b441dbc5ac80035b290514b13b31b41931ea5336d8d9a6a7000000000000000000000000000000001199a873958c63147e6b82625dfea15ce90dd41ceb4e315f67221eb874ef32c6a2953412e7e981659c72239a7a72bfe6000000000000000000000000000000001845af5936b4d7487ffe59137ba2f86daea3770cf37fd560969ee48243389941a1072205e049ddaa06c0ac56b7edc8930000000000000000000000000000000003cc831177f24614f93a118b896434105f05a277051a852fb9973a775fc54f779c2a1f3d64c457e5231dc22d6aef606b58b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe400000000000000000000000000000000005e63584bc85ba58615985f6a466afe05268e545e0062cd7214e0b6fc8b87537c745b754cd9a1144948bc88b3c43acd000000000000000000000000000000000635b6a49090ccede3ed2ef203f0ed164783e3df4d9a7d93319515cb9230bd841b61a097f39e30175793b3e934d8e426000000000000000000000000000000001861e65f47a9da1584c45bc79a66045d86bc1709c2d1cf6cd2930a9fcc8c4efaa6536b5015be8d54789e8f574f93f9f70000000000000000000000000000000009290ce63d55eb436794acf11be9d896f03e7608a1bc8528f61ec9473f054bc9fbbda1072440e58e2f6ba080a01180173b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe0000000000000000000000000000000018feed9500bff884d2bb58554da2180c68267b6d3a45c2c7cee4c3f8524252d3faaa5eff971bf40123587e669fe66bbb000000000000000000000000000000001441bd3b58b4a4a87c2459f873c0692f5977b775af984bab46dd76cb9f775d2faebcb77b2854c9f1faa33f6c5de61c6a00000000000000000000000000000000123a890c3362c77e5b5cf9846d9c9e43fb3242d5a831e640ad080993fa0547854c8d11cc22f7f7b426528bf1154d2300000000000000000000000000000000000ff4a59ea98d13cfd353ae61e18d3c7018688f755561e6a1da5f09acc4277e8d49645087115acc64f992ea778a11f39bb049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc0000000000000000000000000000000006b019d005141e82393a2ca04469d1f6fd7b9456001ffef4c34eff6b2e91df58e99fd07944f52b108bd41ab6c4d6bbf200000000000000000000000000000000109ae87042029856befff0c916db5437e1e058a96f2970d8816b3becc93a1a50d6d336d5451303715f3e272147a36caa0000000000000000000000000000000000fc381b8dc9dc02d34db13e34732a10d0dfcf676c224a05a3bffd888b0af7c415b38af0b6afe6b464ffca42947c6ee5000000000000000000000000000000000087040d09c39ccd06c9ecc360fa02147a32e8036ad6e4b6bdf5b3883722a4e5a887dd022d53706d2585fe558696be6656197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d970330000000000000000000000000000000000e35c27b29df0fa9298bb9ab6a38b3450782223e2115d79152f9baa924d762d583b3ebe88e42f33028814ec78e5b319d00000000000000000000000000000000190c65667627a16f0af0ac7f23af0803bca810f3986b906b7b4f126d98473d52badf45e90e2e45bb390242fa8c40135100000000000000000000000000000000103f0283a5673c16bcc0f74f259c2eb077061947da04e467dfebf62aa005491e32b85cb73418b624a30dbaa01672921e000000000000000000000000000000000465466955c908607191faf15f0768dce42488c488eb4a065977f21ac7484766bc0abf23961ea2ba46dcc04956abf6c7721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000082346e352e845a54cd4267f93b85b2c8623d4650e00c1c56082b73ee31f63588d2c117d3cdecc0378fbbf8956b082040000000000000000000000000000000001a7f43c2bb19cb32345c43c950536f8e85815b86364f278f6ec8169eca80917c2b8fc08d59b20cf55f25dc468e7bd7f00000000000000000000000000000000085a5cb020df10f9b4c7afc01b1d11700579dec1e85e766507def2e6cf5b714174f7be9cce3b18533a5ebfeec2b4e481000000000000000000000000000000001836d7506d1cc984fb777b8ee935d6f5b110644f59e96ff44d8329336d59a3e1d2b53a05d35e97f634baa4fdc11a6cd8461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e000000000000000000000000000000000c1c59828ec6257a02679cff0bee0d665d449d2a158bc6d877e84cc0fe2161c297dde09b778d5e1249c515833e483004000000000000000000000000000000000f5e82589bfb7781e4110f1486752b00cbdf96cdf4191d75053c6d6d646e1c989add011361031a11559e156d64139fbf0000000000000000000000000000000015053afa7fb2b4e4b70f3c8a570fef8288fdc22dd951b6ba8a40b6087b9ab04ede21f0ddfa84d6d18914041bcf244c110000000000000000000000000000000003f399800cba51ab35624d866831ab6506392cb3acf549787153ffaf08cc451acea46c7a612821dd96c45f8b75133d88569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d000000000000000000000000000000000e26242c8f73116079369ef4265f624abd4377e4e3485c28197663de9de9f5618c3b6ee602ff6bebd1c242aef7295b2200000000000000000000000000000000066ceb3ea6067220bd28fa1164237782859d27c1d3087a42b4d09bcc343611e4ed2be014a27f5b394c67643dc00f57cf00000000000000000000000000000000157f9d30de52110ea7a2a35ddfe67d9fad7223c5e3307e797dd0df3621520a421958a2835205e3c4777923f47d47e5310000000000000000000000000000000016ebb41beb85b9489a6d5482f8a3330a5c5c5e5718e8efb8b67362f9d8e9c313e9e563275ba38c207c5bf3d89c406ea62061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000005d1c9109b5b7409f94ae3f7dd9e8ae4908a9b378fea4ea284cbd33d1e59b605577b63892aaa8ec14d415f34e22fec520000000000000000000000000000000005afed05e62599f20f7eca019f41d770c630cf6359cb5601464be821691fba5205c16e7b580e6881047214f938e5104b00000000000000000000000000000000105637a2aa4725d8e080dec3b731a111ea4c94b79f898dfd51f645501ef0c8d68ea8e80fde28ff96e927e44306ebbb1d00000000000000000000000000000000080cfeea754474ceb37973234d5dc3269f8ca99bd862d4d2d1a602321fc709945a3209e5ff2cc962cfa6d03017c9a1354129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000137762ea5c80033aaf17570451b15a062feedde810f11ebdbe9a79a3275dc12613e0505835c122bd5f9afea7dba84203000000000000000000000000000000000d89c04e45e60769a63fcd73df2a138c457bb549195f2c4eebb3be1ea46149f286756795be8328b5b886f497d8167b34000000000000000000000000000000000be43d515083c8c10f467618685a43d4d5f6457204bacd278445943a9f44f7189b561a0e1bc59d2757fcfab2e3f93a4a0000000000000000000000000000000011a52583227c6dcdc1784d3633fd584612a9f3bbc1922477396dcd5af84413e5e9382a34a71b3a72491ea09fab2fc6bf366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e8346729000000000000000000000000000000000073acefe33525dd2d5204cce72371ed82c7e4b58d1b4e7f4b4994f9c58b02d9d6206fefb3552446b6b355e860ace43c0000000000000000000000000000000007344eaeaae71e17930e769e02bcb4f44ddf3d040ffa0b081f25901cc125a37a58a6a5d13e7b0ba493802ccdaa054e29000000000000000000000000000000000a65fec6ad29ec3eee9ddc7ded2297f49d03ff18a255f1e6d29d2a67c20713f319d79d513af0c58ae3cddfd1f6240ff50000000000000000000000000000000019d5f00d9e2b271f4e9ac779a096386f08ae124f77fb8183405d48ea7f16e685805442dc67a392aefc643ea95b4f1fcfd997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f50000000000000000000000000000000018230bf1a873aa04855af1426da30f1b3ef4b64eec613b9f660222e3827b325c318baea031b463c7e9f775165d22ec8f00000000000000000000000000000000017faafa1294fac53e1de8cae9601acc62d76a5f01a39ce49d65f3f5d2cd5cca33eb90bb4116b3ea36f912ae2b81b6cf000000000000000000000000000000000fc3ef5ea59849a87fcd45500989f1744cb5570ee88e34a952cec32cea2eb5900b64d8d0d04ef5c51e8fdcccd46412490000000000000000000000000000000001c53aa8aaae8422fa4fddc86cacdefa89c37592c8e67e472a23627514623a90901a619af79e93561a0dc65215837274e881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a3371000000000000000000000000000000000e3db6885c2db9244548e11b8c49b73f85e4104b413f54308497262fdff1957495859830114528a22c45d39a554ba82700000000000000000000000000000000181b1bfe2d9a1c563e73356d73f4ed3e7061a79c610bc97c911ab1a0213d123c9f83ed6706e862087a796ce14c5cf53d0000000000000000000000000000000013f5fdceddce771588869b945bd6025e5ce485fe78a362356720b474b83998f27e535cfd8d33ee51cfc68e5d514f915c0000000000000000000000000000000007e8fd7ba457a3cefd50c641847425cf2262deb1d6945a0bd740eadf38dcaa616edc48c3912508d663349f089b8b56fadcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e650000000000000000000000000000000004b20b0408da7b704694b47607928a655077015f2174fe01bac9a0b3a61dae087b0b593f58d2947d8d84f75bbfb327c900000000000000000000000000000000106d623b2007c5d7128e03e540325ba763e992a651e2e5c78936f82ee2ff72d89a1a914345486cd0a04440c75beb190b000000000000000000000000000000001847348e5ef429cfdf1ba4d265d8c5ebcbec3d5dd4611ba36e2754fbd3d327273bf2eb7b7ba4b3888d059dc87f034739000000000000000000000000000000000bcb0a9dfe5189bc965e9721407b4cb3ed4171510aa4d4e5d5f0823a1c2827643e1278f9c0ee960c54ef8f6c208eee7b334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638", + "Expected": "000000000000000000000000000000001205b70b29ee04212589f8a70a71e004f517d3354e714c1b4fe42cf93faf1a8ed40dbc1b5089ddb53bb052c9cb74c0e8000000000000000000000000000000000f619082734dd9de653b61cf2fb927199f228637db70797bd2a21fdd48b6ecd4c4f712097037534024880a436fdd63680000000000000000000000000000000000592eca560be6ae256abe1796f7ec394a8085c127437f6590c8d41501a482c61456392cb320b9e801044dcba7802df9000000000000000000000000000000000a6d20b8009708ca01a274aed6dece732c5eed5aae5e4c2f3793b5fa1f8cb8c95037ce387bda2e7476e9c493507c7fbc", + "Name": "matter_g2_multiexp_99", + "Gas": 240480, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(g2+g2=2*g2)", + "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(p2+p2=2*p2)", + "Expected": "000000000000000000000000000000000b76fcbb604082a4f2d19858a7befd6053fa181c5119a612dfec83832537f644e02454f2b70d40985ebb08042d1620d40000000000000000000000000000000019a4a02c0ae51365d964c73be7babb719db1c69e0ddbf9a8a335b5bed3b0a4b070d2d5df01d2da4a3f1e56aae2ec106d000000000000000000000000000000000d18322f821ac72d3ca92f92b000483cf5b7d9e5d06873a44071c4e7e81efd904f210208fe0b9b4824f01c65bc7e62080000000000000000000000000000000004e563d53609a2d1e216aaaee5fbc14ef460160db8d1fdc5e1bd4e8b54cd2f39abf6f925969fa405efb9e700b01c7085", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g2multiexp_(1*g2=g2)", + "Expected": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_g2multiexp_(1*p2=p2)", + "Expected": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2multiexp_(0*g2=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g2multiexp_(0*p2=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", + "Name": "bls_g2multiexp_(x*inf=inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 54000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(2g2+inf)", + "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3", + "Gas": 79920, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "Name": "bls_g2multiexp_(2p2+inf)", + "Expected": "000000000000000000000000000000000b76fcbb604082a4f2d19858a7befd6053fa181c5119a612dfec83832537f644e02454f2b70d40985ebb08042d1620d40000000000000000000000000000000019a4a02c0ae51365d964c73be7babb719db1c69e0ddbf9a8a335b5bed3b0a4b070d2d5df01d2da4a3f1e56aae2ec106d000000000000000000000000000000000d18322f821ac72d3ca92f92b000483cf5b7d9e5d06873a44071c4e7e81efd904f210208fe0b9b4824f01c65bc7e62080000000000000000000000000000000004e563d53609a2d1e216aaaee5fbc14ef460160db8d1fdc5e1bd4e8b54cd2f39abf6f925969fa405efb9e700b01c7085", + "Gas": 79920, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_g1multiexp_(inf+inf)", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Gas": 79920, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsMapG1.json b/core/vm/testdata/precompiles/blsMapG1.json new file mode 100644 index 0000000..07903a3 --- /dev/null +++ b/core/vm/testdata/precompiles/blsMapG1.json @@ -0,0 +1,702 @@ +[ + { + "Input": "0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350", + "Expected": "000000000000000000000000000000000d7721bcdb7ce1047557776eb2659a444166dc6dd55c7ca6e240e21ae9aa18f529f04ac31d861b54faf3307692545db700000000000000000000000000000000108286acbdf4384f67659a8abe89e712a504cb3ce1cba07a716869025d60d499a00d1da8cdc92958918c222ea93d87f0", + "Name": "matter_fp_to_g1_0", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641", + "Expected": "00000000000000000000000000000000191ba6e4c4dafa22c03d41b050fe8782629337641be21e0397dc2553eb8588318a21d30647182782dee7f62a22fd020c000000000000000000000000000000000a721510a67277eabed3f153bd91df0074e1cbd37ef65b85226b1ce4fb5346d943cf21c388f0c5edbc753888254c760a", + "Name": "matter_fp_to_g1_1", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed", + "Expected": "000000000000000000000000000000001658c31c0db44b5f029dba56786776358f184341458577b94d3a53c877af84ffbb1a13cc47d228a76abb4b67912991850000000000000000000000000000000018cf1f27eab0a1a66f28a227bd624b7d1286af8f85562c3f03950879dd3b8b4b72e74b034223c6fd93de7cd1ade367cb", + "Name": "matter_fp_to_g1_2", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2", + "Expected": "000000000000000000000000000000001672a8831d3e8bf9441972969e56b338594c5c0ede7bdba5b4113ac31ccb848dc2a2c4e23c0b9ec88bfe7165f472b427000000000000000000000000000000000a86e65037cccb5281389512673068d6f91606923629905e895f630059cf87fb37e716494db288958316c6a50de65ca1", + "Name": "matter_fp_to_g1_3", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de2", + "Expected": "0000000000000000000000000000000019adfbc918cb74abc6fa0664dfe60697b233f0663665d2cc133478db4d6c9a41309ff09f9af9240037a7332bc42ffe3a000000000000000000000000000000000d31ffd63837cdf1cf2a7b3fe23a9d86c08f3a7c44ac4fa52d21b8c235f0d45f85c036d80bab332034635845deb31467", + "Name": "matter_fp_to_g1_4", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799", + "Expected": "00000000000000000000000000000000192a005eb944f391251402ac3d31c30f0b2d77987ed9928d244f492f96c1a0a06a7cd0be4bb3dfe3c484ab8ac5279a09000000000000000000000000000000000b99b9e7f0b51a2e0d12272fd0d9ae65294dfd34d45f30fe446a25b225316ef467b02acc3b6a578e054e612434096d7c", + "Name": "matter_fp_to_g1_5", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c", + "Expected": "0000000000000000000000000000000019dbf865a67157efe65fa7171279049864bf6c280d3c3462e93425bbf25f9cbad6c27885d7927b5cdca642df48ceccd2000000000000000000000000000000001606be1ef7aaf56349e5179b01b89e172e463bb3446792d5210452905fcde42522f9719b9e7ddeb8cc3f227eacd55947", + "Name": "matter_fp_to_g1_6", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60", + "Expected": "0000000000000000000000000000000017d476fdf0be6b09206dc83cce64c603a6b911f051e9191a2473a1bc6b1dd2c6e9bc4d262edc936f62911460f0b648a70000000000000000000000000000000016f824bb325ff7f485a8e9d116f4a56ea71ecd2c11b2a4d119c208cf323bc62bf1e9fc609230c571e7830a956e140e47", + "Name": "matter_fp_to_g1_7", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f157", + "Expected": "00000000000000000000000000000000145726f8479d7390e7a21cd31dfee0e6203115e72d04c5a735feb2cb688ff74944bff2b1af1b6368b4d095143662a1300000000000000000000000000000000002fd68d51753faa242bee10148c0e473f4110fc7b67848dfbab7d7105090648534854ba75890e099cb738d1dce604ea4", + "Name": "matter_fp_to_g1_8", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac", + "Expected": "000000000000000000000000000000000671b0f33b0f1ea3386e6876452989416c7171e283c4b0c375e840ea05e7fda22aa03899b50e59e9ca5a87039b2e732800000000000000000000000000000000031bf8caad5ce6a0d94f14693da0d551dd4bfd2c2163c8e8d5a448956153f63ce2ab72f03b97b560d67933887e83be1b", + "Name": "matter_fp_to_g1_9", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c8", + "Expected": "0000000000000000000000000000000007d67999ac2fe6ab93591646905f23aead0d37ca43573ab02dc16c2b199086f788a8a1de6b10aef4f4d772b2e12e72ad0000000000000000000000000000000003700b150ebf60cacbb2b7bcf969b70edb57b34b5c772cdf68d42dc9f1513631799b9b9041c5e94595ea848e195aa730", + "Name": "matter_fp_to_g1_10", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3", + "Expected": "0000000000000000000000000000000006cf4af50766ec08696c9bc0d9617c1f0fcb0ea1bcb576179cd4537d9d31b373bf8e3c5f5fde2c21e44917cf1f51ff0a00000000000000000000000000000000050a9f7d8490ba2b6e49762cf2bfce557e39edb51ef03128b64267fd3c6b996e95d73b26cf1965d427e3445b1ee4d133", + "Name": "matter_fp_to_g1_11", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e", + "Expected": "0000000000000000000000000000000009fbbc6ba7ec2315dc18aadda7b2e53180b904c5f1cbdca1b2f42ed9c6675da7beb4007ab6639520c4736bbf2ee3f04500000000000000000000000000000000113f0bc737b2f3a141121ef236cbaff2f34502aa13e121b857baf327f7be66be97867fc6f752555835fdd01610e30c77", + "Name": "matter_fp_to_g1_12", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be", + "Expected": "00000000000000000000000000000000047dd479fe99840150e73e4a8fa6be74a9b7d743e21cf33e9d7a9fd8700feeccd5111fb037eb3b15b79d5737ec4c7f0c00000000000000000000000000000000000ba7f57ce062eb9c67d84eee64d64d250a18454bd63dc5a136f5341214079eb9269eea7c4e0d836dd8be63a8a45c04", + "Name": "matter_fp_to_g1_13", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a", + "Expected": "000000000000000000000000000000000e8cf94e68b03d1f6a3d4eac7898f143324d08f7544aa9f952947e9008d2c14e46236667173266d82f5e41887c6f614200000000000000000000000000000000089a1ada37f30b1f6e3a6613705992e9708d0437611f1de72a9f696ea5efea6793f285bd5badbdc20af64df8ba95c79e", + "Name": "matter_fp_to_g1_14", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3", + "Expected": "0000000000000000000000000000000011e4919deb9eefd13dd0ba5184003ce34ff6c2bd8920dc49b936917a7b6aaf1c7541780b5d0e380e6c808f093a877eaa000000000000000000000000000000000152dbb758aa5f60b8d0703eb30680857abee717114b8cc5f6466e70856f19c76a88ec6c536e7a679c177986bf636e6a", + "Name": "matter_fp_to_g1_15", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e", + "Expected": "000000000000000000000000000000000750f69c43c56df2c8524b4ead9f6cb3ec16d3a6ec913254e585b0d8518e53c18e0e93dd4594adb926c51820de6001c10000000000000000000000000000000011f5c985ed12f72b6ec7e222dc8d93da520ac65476c716e231e7142cd3aca49b25dbd716a8f587006e4a2af31c37956e", + "Name": "matter_fp_to_g1_16", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad", + "Expected": "0000000000000000000000000000000012652effba341826ee7bc3108404f5fcac84776c6f5fef5d440454b59f04afc2cc87f243265248445c7c2bfc14493ece000000000000000000000000000000000c0fd215b7c012da4532c882d7d7f83ebf133d58acaf8b5123c1211aae5929c6726410631c7f9347456448df643c9ed8", + "Name": "matter_fp_to_g1_17", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6", + "Expected": "000000000000000000000000000000000c444b07e9ee5dc366c63ba30f1b17087bc4c548963caafacf223f4bf5b5bad1f9b51433bd1942978f3f5e5696d5056f000000000000000000000000000000000453941626954845d89821df34efc6f81660684b08f03fc42da54119d10f1f95357ba75a0962961f1487df45b0c534ac", + "Name": "matter_fp_to_g1_18", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20", + "Expected": "0000000000000000000000000000000013d5fcd7e4a0b1d7d8c7b242b46968519521ff8bc4b990a56ece26053d4bf884afd24a00670911f943522e06fe4f87d1000000000000000000000000000000000aab46534de37b5c6d206959a1023ad4f20ed5966bc3fd1750c1758ed806f077444ac70e9943b4e8debaecf208817a5d", + "Name": "matter_fp_to_g1_19", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab1", + "Expected": "000000000000000000000000000000001440f44e3964de59be03a6c69affbb3b44ffcf4ec4976361ac49c31a23f9f154f91750533ff2425d5e8fcde0974a91d50000000000000000000000000000000002031eb89620736dea022880e5188145f080537b1aec183db70bf307029be21a167fb6456bd1a47a75626280f78442a2", + "Name": "matter_fp_to_g1_20", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175", + "Expected": "000000000000000000000000000000000a2d7297376216582c3938c2aab0a26494da7d9df45e1af7b4f826f064467a939ad99134be4c9b804b5bf273e082c4c2000000000000000000000000000000000b0a4da7cc585be1be6c091006fe831edb6f6eadbe3ef611041efa3d14f442c9768feb2958efa161e0adf5c321d7d522", + "Name": "matter_fp_to_g1_21", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d23502", + "Expected": "000000000000000000000000000000001314d7faac7b4d5003baa10cc432108d6bb7f80bb13991f6ac45fd7a772f31cd43345ea100b05f2ad73e3bf583e7e7b2000000000000000000000000000000000eefa97eaf2143a991343a8823d8b362f77d8370421bd13a9a6cc4988544feb0cafd3a797a28d27f4f8d361cb7f49ed4", + "Name": "matter_fp_to_g1_22", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3", + "Expected": "0000000000000000000000000000000003ee4f3d29cd9f29a2e559a86d8204a1d65598e7788d585b145022de2c19022b122c1f10423d3bec769545d656726f5e000000000000000000000000000000001803f26af468740849a2737a42e53098b48c0709415247023aedb111c96043e3b13de300213e5196cc3b678f8be0696f", + "Name": "matter_fp_to_g1_23", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d9900", + "Expected": "000000000000000000000000000000001800b9766f3e621ad7a8d1870ce16c8cd054c87d7fb100120a38c3368cf1879859645874b23297957fef6cd8f9112bf800000000000000000000000000000000091a8b69a1f4eb883a25af2a3a0d1e384ef7a9ba4e8ff8811ad356781c79f631ea20fcd0590e94b9c1841e6add2b848b", + "Name": "matter_fp_to_g1_24", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844", + "Expected": "0000000000000000000000000000000005fe170feabac3805c3eaace41fdaab2c9ae7fe609ba609f4ebce2d24c0d704d847efd510acd8abe5aeff2eb24e781b80000000000000000000000000000000003262879ff5c9831ebdd0de9df478923fee72a8829378f40cfec310a41110ad22faa759276e3b9e015c86c94c3594e0a", + "Name": "matter_fp_to_g1_25", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08", + "Expected": "000000000000000000000000000000000e4110b2efc984c4d7affcbcf5cbbf919c55f948ac7412dc120d30774924d6020a2292f27b8e716c2b5045a561f2b14300000000000000000000000000000000194649f6906daa0394fbc1d45355e17d62f6c22a9e772bd7fa5149e29ab2ac6060d83dc5d70fad75bf3f2c7917b641e1", + "Name": "matter_fp_to_g1_26", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2", + "Expected": "0000000000000000000000000000000013af2a5f26d1f51da0d80fe7c62369ebbec235faf4565e62ba475e6f58418183efc8b9906196ffda72539506243e0482000000000000000000000000000000000774f3096c99bb826792cfd9243d8cbb1bab54fccc3a6347daea74ff1c8aebafdd971b7bfbea5b9a0bce243372caad6a", + "Name": "matter_fp_to_g1_27", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb8750", + "Expected": "00000000000000000000000000000000107c66e91d518789be416606058cfa8e9df478fa097241fc109d065005ae927d83563b72410e5b207d1556c2ee4dd67b00000000000000000000000000000000148c208e55e834c4e4fe20c02f517c21030f60c74b1a3bcf70bb2311cfb9b7548837b9187910bb7e8d1faa40ca8d6d92", + "Name": "matter_fp_to_g1_28", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75", + "Expected": "000000000000000000000000000000000fa72de55fc2229c0176120fac3e0a64c4498bcc7b67ca40b92d47a76a9db87ba498b72f06345c61d59a3d37c51153a300000000000000000000000000000000001f0e176d0987b8ceb7ca0e5ebb491bab0be17282cace8e03d52c986483026180082f86196fe512ac6bac58ec4cd024", + "Name": "matter_fp_to_g1_29", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be1222", + "Expected": "000000000000000000000000000000001655eedb905670d10d2f979962e864d68e9491aea41d073a6119e5bc0ae74216383501a48343d7099b93601f8b67c00c000000000000000000000000000000000842846147959f0f81efc6e8f515a9c59456637740bc15b2d335e0de45890cdd814ca7057c5d3e49e48e5a250c5dad25", + "Name": "matter_fp_to_g1_30", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760", + "Expected": "000000000000000000000000000000001098de70e8748daba7bbad52ce344619d3b5374821c1f932a18666ea0a591b24ece05004546cd519ba4d78c9747c57cb0000000000000000000000000000000005f537b6a394458ad51c2e677b2d52974a714bcf6a7474e748ad7f1b28738b6b874b6f49bdf19479bce4ff6c6a47de1a", + "Name": "matter_fp_to_g1_31", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2", + "Expected": "000000000000000000000000000000000a27f7fde0c79210f4b6cf59c97ac773c9766fdab289225c97f6cf42179385cf18f47f14b7e481df7c19418c79dfaaba000000000000000000000000000000000874f21294205152df3a4fab2ced482d325274886d8105b61668074dc8fc90240a715c62b2a2864901ca7a30f12e76a3", + "Name": "matter_fp_to_g1_32", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091", + "Expected": "000000000000000000000000000000000a6fd7355965c9514dc7237efd262fb9dfd8025ca2c56165e22675e615095887760ecfed4a2080cd5a2b8041ff26578e0000000000000000000000000000000019b1e02c9258fe62160d92eba8640ffd79b3bffb8ca4d602ca6c059239047c5563049758911d0e6034a25ec5094b1f33", + "Name": "matter_fp_to_g1_33", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18", + "Expected": "00000000000000000000000000000000013e4cd06b8ba7b5efb70feaa03550bfa45c7c2c79033c92b819257b2ddce28d501cc836a5ec81bf210bed671bfa66f100000000000000000000000000000000165d806d235d41f21e961795ec3da4f1b0334ba6e71ce384445bfda9e5d89e448d00253ec9f8d49825a230b25ffb2848", + "Name": "matter_fp_to_g1_34", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1", + "Expected": "00000000000000000000000000000000067c19b7c3dcf8b43d6e83dbda7406f5f88b06cfa0d7d145201164a1f06cb5549545ab28fd1ea8c1d5a662cced00822a00000000000000000000000000000000013aab7ac4ebce4686ad8a05e4eb2f60ebdf03c4f4ca0111bb1cd3dd5fa7558f1cf0dec394d0b616cf557f3811bc2104", + "Name": "matter_fp_to_g1_35", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f", + "Expected": "000000000000000000000000000000001673e66a7e558d533be5b855df7c3bdc58f1fb0a3b268b84b4fc25a3a8a211c4c9c8d884fc62f00eccbadbc96dadd7230000000000000000000000000000000016265b691fd43045567ab4fc7e7efa63c8430c8130761b128f0ba7bf381a7cb81bf05aea2526b50ff6e48a87c8ee9cf6", + "Name": "matter_fp_to_g1_36", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192", + "Expected": "000000000000000000000000000000000b36ad42aeacfa47d77f045de527d5bd4fa5fcf25ca3caca99e3e7980e283278e013611d1bc7694bb0b1b86d8589730700000000000000000000000000000000136290ed913b8669f522e16103ff42733a57c1026f966facf4a2d385b0bd52668925d748760975ca5a132d00deddf675", + "Name": "matter_fp_to_g1_37", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a77", + "Expected": "00000000000000000000000000000000076fef7b61f4c687246991d6f735d6f89c953476ffc193bacc1f3cf9573ed47bfbf6dcfbb3da1ec1bb764a9cc9b1c26b0000000000000000000000000000000012b6bb88e8acd6cd0ef1929a79bf4d8b10ec3fd575fe460686921fe94aa3a472cbc7aea543ee6284c368f5ef2c33ebc0", + "Name": "matter_fp_to_g1_38", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510", + "Expected": "000000000000000000000000000000000bbc25f7788b0031f1487ef154e877c5ae277a80d56b3a24a39c3ee94eb7df81a47bbff233c1baaf700829919e5254690000000000000000000000000000000019fd9d1237b508d06d7b2ff807c15c3ab36e6eab7e5b9f145bb2c0f2ce8ec96ca3a24932076abfb74eca85744eee4044", + "Name": "matter_fp_to_g1_39", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b80", + "Expected": "0000000000000000000000000000000015eca2e3d36d619601b0f40b01add7a708bbe59d04d5dfbf12d6e473e252505cec0cf7ea1c420000d49221d5e1ba6b91000000000000000000000000000000000cc6045184317aaf2bb8a904755bf48df9e6754e3a864037ebe0218eb3cd1c0a54e50b95f9e6d318799a72fac8d4e262", + "Name": "matter_fp_to_g1_40", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5", + "Expected": "000000000000000000000000000000000ef0a6307d4a3e92570cad673ca5212780902de416e81d15638ba654951f442e852b53255d7bc4d4e71098924d69f5a600000000000000000000000000000000156abf6f096326c75710300578f0cd946536e16bbf80034c6dbfe454565a501c268135118745989e5274ca2431ca5155", + "Name": "matter_fp_to_g1_41", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072", + "Expected": "000000000000000000000000000000000dc94ea6018ffc5838cb7cb00df9625c0c09701bbf19edddb735a3659b385bdd09e9a7d6e869720b727ec59ff3956d9b0000000000000000000000000000000000a20ea6360179bb6608bcbe4879df186916ee71b3ff7a1dd0fd137a0e9dfb135bfda2c66d1cf8d358d69934012a1a1e", + "Name": "matter_fp_to_g1_42", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03", + "Expected": "000000000000000000000000000000000b4e11f70679333c064d06180df6b54dd1df20ea216415ecb9b704bf4b206141fd841770ab77de4ab2400a076cf9dd04000000000000000000000000000000000ad8c02345e141396401221bb36a2ca21096e89aa76fca4121066da74f2f54b3e2c4049483d9855b7f3159ef448c120c", + "Name": "matter_fp_to_g1_43", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb", + "Expected": "00000000000000000000000000000000136cd8012cebf1639a396f331f73f0da6c114927559cc595f01bad1a18046ae8364858fa262ae04ae3f3b7d13db55a86000000000000000000000000000000000393a915629ccaa9ea06be749f3053dfd07061cfa24bc0aead12622c7d14c085e2994178bfec98b3f8867ac5b4b7a05e", + "Name": "matter_fp_to_g1_44", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e", + "Expected": "0000000000000000000000000000000019008e485a0a9c2f73a79bfe31782a17952edebca308bbc9f90e2ae15525bd501268a1c38c669de0b4e4fcaf1194591b0000000000000000000000000000000009c35254702eb7e3213fcbab62946ba79b7375cc320ee1733d8bf5729d378d1a98fb27d870e27c13626c35cb00a6bcbc", + "Name": "matter_fp_to_g1_45", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e9", + "Expected": "000000000000000000000000000000000059a3315f8b6e75c45e32843b4ff2401c41e1f6716a5909894cfdc71a49253d2cb04ec416d204bf0bdda051ace606260000000000000000000000000000000019cee852aa9fe28e1da49dfbfa7901220616f464ba447480c2421fd6d3a5a818c778510a04cb6557d27f7ef9a78f2fb8", + "Name": "matter_fp_to_g1_46", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe", + "Expected": "0000000000000000000000000000000001d157c963811725ad533539f17acd16ac3aa22917ecb2198d83a3ba396955f2c9654c02fd42e3d4ee6156cd148e9c270000000000000000000000000000000008fd299ddabfe525075f548a31ffc990a3626aba0369bd0accd0e1968204c8e1085c6b287b370808609178ec8ace2d0a", + "Name": "matter_fp_to_g1_47", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf48", + "Expected": "000000000000000000000000000000000ee6b51c5eb4dd9c27a61bc2f3480d799cc4fb88414630adb3961508c7067bb186682194af406f811296228c068e6415000000000000000000000000000000000b878c207bc4b61e827ee09a7825fb216a63ddbc4ef0522b8a944bcb673ca368996c31e6513504c5deb5325ef4df0459", + "Name": "matter_fp_to_g1_48", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909", + "Expected": "000000000000000000000000000000000658d61bbb2273e8969269dc16e16be93ef82be0668c3a164097a1c0816bb4aa94e5f70ed8d96bd15d9acb602d70f8ee0000000000000000000000000000000008f696d49a5c6f3dc971699a5837f7b3a20e222d9559d899eade367ce684b60153dfb75a9a8b81d7359a93069e2d7d7d", + "Name": "matter_fp_to_g1_49", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d5", + "Expected": "000000000000000000000000000000001313f4cc65865c367cb5c1c96cf30c7e993207e9ef4b2fce9db01181b1192520f01a0428668bb9d33eb857d9435939df0000000000000000000000000000000006b5e883fc24585de3b0a0b83cc1742050e578cb57e89b385e245da0dd2832852c3fa5f31ccf55e6744e9cae6c2f705f", + "Name": "matter_fp_to_g1_50", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5", + "Expected": "000000000000000000000000000000000f3fdb57966f9ffd0e20b9ad3bfb4fcade56468aa598cacfe388cd3b647d5966350586daa4493de23703a1debc82e48900000000000000000000000000000000044ff5ce3b9bed637709f9105bec0d86b4f0ea2dd86c9c3b1324637cd4c0fe5a4a965021c51279fc03592414e7968d23", + "Name": "matter_fp_to_g1_51", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0", + "Expected": "000000000000000000000000000000001288807e8f49323b39c5d592b97f19cf76f2f642dc4fa704004789d28452ce7a02a45f3f83a8d9875480d380e76df09400000000000000000000000000000000123b15dc7f166cb7c2c106cfd2f7c321a9bea9e3bdd118058c4745b6666a0df2a7c7fea16887a4c85faf860fe48a3787", + "Name": "matter_fp_to_g1_52", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a", + "Expected": "000000000000000000000000000000000adadcf2f074679ef3523c10674260b0e40106cca8d94b05f83e2b27d8da8c00dea4215a30275ea5e1a8fd0beb45dfb30000000000000000000000000000000003c2d436e545163abbb18ff7c8e6db1e55c733c75f9594c695c66656690e88995f9f266c2620e99075d3b78805e3ad41", + "Name": "matter_fp_to_g1_53", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f8", + "Expected": "0000000000000000000000000000000016cf855c1ea449c47236065ffe53a4c6afdadc08f1eaa26a8f79ea92a7a119b26dea1dfdab4db9b02b3dcad2c077338600000000000000000000000000000000071924c7d4e6aa5234dc921d288dcad3e49b44d2f455d207f3641f4b5b5c809b84c04945df08e785b3d99eda1807611c", + "Name": "matter_fp_to_g1_54", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa", + "Expected": "0000000000000000000000000000000016d80d4689e959233f05a3266628e233b747705bf6d6236771d5e697da03a0daa2dfa88aa5a3a5b97bc4517c467e94510000000000000000000000000000000003bc451286fec0e7a01d29ffae4986a2a3371d4aab875547cac05f759f5a52b8cbf84798b5b3d664a8692b212d4e974d", + "Name": "matter_fp_to_g1_55", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe", + "Expected": "0000000000000000000000000000000017297cdec2f6a54cb11c1fdac799f252c72dad52ead6c29de61d64e56ea0e0a1d3a60284029323e35d38a4a25f82fcd60000000000000000000000000000000009beaeaf3ce2c9bfbfe5e04ceaee87460d760c4c16caa7b37767e16b8e97cf08bdb6d30472b3027f66803dec1ce40eee", + "Name": "matter_fp_to_g1_56", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13", + "Expected": "0000000000000000000000000000000015a163067e8039be1c365804887dfbb78a7a699f0308c8e26519bf1c86fbe6acffaa26f0e5a2a380d1c704fe84d3bba60000000000000000000000000000000013f94e107625aca9c4346102dd5f09d51e445fd44ea67f171048e8f9965ce3496e759610c078404d41add90a358af482", + "Name": "matter_fp_to_g1_57", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce73", + "Expected": "000000000000000000000000000000000b0667e2b7c0fa318c5c0e66425f8cbb8217bec845bfe56997cdb9d0d915131b81e82419a4533eb573ffe103077f35c90000000000000000000000000000000018074b6e0cf144fff9da02a4b5785d21762952d4ed23b1430d6165974f49521b73eaf98973f7967ffb35cee92a2b5269", + "Name": "matter_fp_to_g1_58", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b", + "Expected": "000000000000000000000000000000000b5e953227f4f5e2070482cde7fded231bb0d4649a626d356cab2bfcba6c1588ef38c62cb2c550719091206727715dec00000000000000000000000000000000095f29eab98321d334f22b4db0c30a0604c5c385fd222a71399763f5c815e04226d9d06b460b9e3b44d1ec127d20315d", + "Name": "matter_fp_to_g1_59", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc", + "Expected": "0000000000000000000000000000000017946ce626cd11556f85d15b85044fdab0456e24b5e331886be860bf55411a03886738aed9b19d52e91a94ea5cc5f040000000000000000000000000000000000cbe613ecf3c8ca8a5f0617c64647a609ce6e8fd40ae42f69a928f4ba78f7038254689bac2dcde7a464a03d5e26e34ce", + "Name": "matter_fp_to_g1_60", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82", + "Expected": "0000000000000000000000000000000003b425300fc1885f2e932a469a8137bbf9df9560279a5ba87a13e7d4a461489bd8005054f14fad881e06aa46e4333d920000000000000000000000000000000011dcec636ef785d348fcbf9c59a82080b8f2c02d7ab954bc17af1c163a5383a36dd3948ac9110c6afb363ccfde2b6682", + "Name": "matter_fp_to_g1_61", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df", + "Expected": "000000000000000000000000000000000e545a87fb19f7943e18c75f7a173d18ef8129b200222bf6a2ba5a93a92c47ba7accecc4f089c42d6c6bb2425bd1786e0000000000000000000000000000000008c005ef6e5b25e84a8251add6112db49637c2b955af8cd65d029f8e17abfc660794b474689a00b5d2784163a9a0c241", + "Name": "matter_fp_to_g1_62", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb", + "Expected": "0000000000000000000000000000000004cedd2deb72d9168ab5704e21d9a5d85b65ae1510a628515753e85425286d9825dac99922be4a19870700956a65ece9000000000000000000000000000000000f5b0efbb2b327e294246fe862ac01dcedc7e728b938edb9c4a6128740b7d192cf8ad877b869207fb6d1453d85db895a", + "Name": "matter_fp_to_g1_63", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de74", + "Expected": "00000000000000000000000000000000031841f58b82f7e44aa03f474f18360128aa5699e748e4e2fda1c29d3cf165dc3542b90f09e415e92d73a162af38ad52000000000000000000000000000000000028cbb3ff58cf28f6dc876c2c1cb147bd6af85f3baabe253e9a1dd69687b3a46d4604d2d92d08310ecd7c90723bc7c2", + "Name": "matter_fp_to_g1_64", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5", + "Expected": "000000000000000000000000000000000e378bf9d1d65cf3a39dc2b3cd2dca8954270006abe048cc29183c5e7c1cf464b21a548679fdf5af8a31e198b69ded53000000000000000000000000000000000865c90b45eba1979e433f71c93c7b3b8e90d3d12a3c2153ab7c420f507bbf91edb593d3beb3899e76d41674b5ca33d6", + "Name": "matter_fp_to_g1_65", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276", + "Expected": "000000000000000000000000000000000844679db6a74e2a1f7c342771616c446c5e240e40e1f994fcba49f8ab22a7fe06b6909f50ea3c49a8fbebaf2b22b0a000000000000000000000000000000000090afa19255f7b71630c466d6b180b2100f8ea6b7ee2085973e409af8027859b61e0c46b639120ef6f3ee1555aed2f94", + "Name": "matter_fp_to_g1_66", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7", + "Expected": "0000000000000000000000000000000009ffb2b0054536d714944c6c96f8c1ea902e7109d4917a54ec551d811ab15042f843e158a9e4decab9761cb10e7c3e24000000000000000000000000000000000a6c7a862b951aa9f8c2d1e8ba30af8b7909e9721a06479d186e46ffae3ba09f5f52561c7c4c34d121be1304650cfc6a", + "Name": "matter_fp_to_g1_67", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031", + "Expected": "0000000000000000000000000000000001f9035574fac4ddc3f114a79938105d95ad4947588028b60e2926a8e0fd78710434edb2ab6b761fec43e458e19f0e200000000000000000000000000000000001e86d391172978aadc652b1c5d28dbb26a5357d1deb522bc280a270cc63cc18284e5b05033cd7ce1a6eb962a5b7e268", + "Name": "matter_fp_to_g1_68", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb", + "Expected": "0000000000000000000000000000000002583631492e3e0bf080a5f67334f7a2907c707a678bf63d53badb3ed90305a6eae895f7842a5d44a2110585d412ed860000000000000000000000000000000018719d22fc604567689870d5a5b043ee7234927b1e878dce88be212a8b0981e64f3cf9e03dea94439f504c846c6e42f9", + "Name": "matter_fp_to_g1_69", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5", + "Expected": "000000000000000000000000000000000fc1acd8490dee632c51e67356601295291b107087efc2483c1e1a41fedcff244114608c49f6911a4249a59a891264140000000000000000000000000000000019c402eaa9ddd6ff3c72a7d3bbc736cc867b437dbf56c9941ffdb2e0cd60bdb7ccbecef3d62aad22e97c1d96a328e8db", + "Name": "matter_fp_to_g1_70", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2", + "Expected": "000000000000000000000000000000001785abb82ace5d8024c97b3480fa69a65f5ed48fd3f5416f068690f8f79295d13929d01922c562277f65293abf5d739a000000000000000000000000000000001076dbc521375a1431b24f7d03902491b80b1856cbfd3e759b520927fc559e705801460afaba6991b032d59739c25059", + "Name": "matter_fp_to_g1_71", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b299", + "Expected": "000000000000000000000000000000000bf99b7aa1dd96f57974fd79d5823d1f379bc0e32ce416e6f89a499b82727081aa78529dcc76257d1d699b9979ee23f900000000000000000000000000000000067044e8b0cf455974850859bf76bca780f1908beb06a64a7ee8db2ed54703431c354cc3d7576fde0b45611a2f49f862", + "Name": "matter_fp_to_g1_72", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf", + "Expected": "000000000000000000000000000000000d102c354adf7380053c8b0c11a5c15b046516a87b3e98d1f909bdaff06eebfd9b0c457ec3741833da262f77d411cc500000000000000000000000000000000012cfcd6910ac046ab8c0b448edca5847d0f8cc2a4633fe42edd223ea1b73ec451de8d75cc3d37dfb741ee35259b34449", + "Name": "matter_fp_to_g1_73", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec433292", + "Expected": "0000000000000000000000000000000013c11f82052df6294da64b16551e689c439d2d27922bef2a067bc49eb4718a392693570f3b3e58158dc0f5bc3a5b8f73000000000000000000000000000000001517ee24f199913c184181561823d7c3506caa09d93d506c7773f9f615169df444c9f09b518e840735c259ec02488670", + "Name": "matter_fp_to_g1_74", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036", + "Expected": "000000000000000000000000000000000a546a1f4d65a37d7d60468c18f72152473feeed100119b4518f4c778a7a37a23e8c60ee04cc0b39d5a1eb8c908856870000000000000000000000000000000009c5766d9c88dca87768c0aff4160ff0fdc3aa67dde3eafcca030eb295a6736e95e415f3f5a443f2545c7fbd01f97964", + "Name": "matter_fp_to_g1_75", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d7", + "Expected": "000000000000000000000000000000000a197b81c0950b1b802128a01e3b620fb2134115a0d1aa2946a82fd22e91f172785d19017fca385863ee1643bcd332b80000000000000000000000000000000011fba5b82b0b2726bbe7a6157ec9103d0b5a480066ce5ab7120294930b81c04cf6d0fb8b979d17c3e262bd1268bdf1aa", + "Name": "matter_fp_to_g1_76", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e", + "Expected": "000000000000000000000000000000001644dd543ee92960effec90347ffe5f06d6b087f13c6bd73dca93c9e16818d25ffafe3610260cd43ce9909e2ac2e2884000000000000000000000000000000001893436c9dc44500be831076b375d0feccfad2a126110fbcfb77acfb95d6dd6c6615b4b795c007ece6ea0c31915b8e32", + "Name": "matter_fp_to_g1_77", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b6", + "Expected": "0000000000000000000000000000000001aded655b8ba2739b820b894eefd7e60d11889d7321fdae5ddff5dce11551af24acea3f501044562237fe5df53305df0000000000000000000000000000000010f4f3f415891ba4dfb21307798329aac5baea98cdb44354d4263e1ee6436f613a3accf06802ce2c2782e8a15738bc63", + "Name": "matter_fp_to_g1_78", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee", + "Expected": "000000000000000000000000000000000be0b580d0f12faa809d589ba59c5810c18f74b025e6dd4dc49c83b6a39423c5cf82b0dbb1d750e1801e37a5291692fa0000000000000000000000000000000010891c5bfece55dabcd223518167c5b0663f65c001ed051735635b417cbcf2484a057522e1c3417e43c82095b0cbb855", + "Name": "matter_fp_to_g1_79", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770", + "Expected": "00000000000000000000000000000000152454ae7fed9c971cfd72ed054f44124d71542f9ada5a90f1601114289c93fb490a1c5d99b3e8c70fc44fd10322173f0000000000000000000000000000000017bf9499bdc15ae5091daf41812c74535ca31b56520e420edf9e5aa90795ce5db5fa42a06dfcbc7438e954db83f09b75", + "Name": "matter_fp_to_g1_80", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38", + "Expected": "000000000000000000000000000000001633516081b91621b786a09389e89b274c2d9ec616db5028b009ed5c0a1ab47695a0b95c53a45112144613a4af08e6ea0000000000000000000000000000000014b09586f75c939fd62c3d667ab6263367f8961ad4597f1b92d792e8ef79a469137dfba5ec0a6354d5bfe3a84130bc65", + "Name": "matter_fp_to_g1_81", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e6", + "Expected": "0000000000000000000000000000000000f0474d596ed86a0d664885f9c981228fdc352755d52dd7e979a85fdb1b6dad106d8bc0a1eac04b510829b7da496686000000000000000000000000000000000a72f532897f912eeea707bfd6d183a73786c7b2e2c80a01f3abe7b959467d6ea63093c16d6465382a7808d5f0edd92f", + "Name": "matter_fp_to_g1_82", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d", + "Expected": "00000000000000000000000000000000139ae959f9b0cc2d900e748220c4bfa7dbe22926d8ecb9a10e7d713fa0a6e147fa3463e06b791a5e604c66110b77f7530000000000000000000000000000000013f8d09915f77f4a18854dc2451cf39d7ff502a8184d3b4c59ad3317d62940e903d68836751172ec0b4a796db003b373", + "Name": "matter_fp_to_g1_83", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b", + "Expected": "0000000000000000000000000000000015d065191ab63df2175f821cf62a4b948a6b2389512c7e94e1fa3c99506af624810ee17de2c183ebd69b4dc485ae264b000000000000000000000000000000000fa8cfd94bbfa6d504497866c1e0d9e84717fbf0468a164e3b8ca46348789e2b7f08ac5e8aa2e7205062f3d5083dc5fa", + "Name": "matter_fp_to_g1_84", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7", + "Expected": "0000000000000000000000000000000012978a0da7162aa1e8b32cb6ec0eebf2c2e62350cab4534358c6bf80299dda9281e16ee40313e7c52c804b2f4de7f1870000000000000000000000000000000009dfbafc8e40d71a789a52d5f8b80e7c8510c58bc0774cfa84211a9c1417d75d5c7b06d7aa9fe052ad9c1f30c922705e", + "Name": "matter_fp_to_g1_85", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d", + "Expected": "000000000000000000000000000000000de98869442b759a382d0f6ca45eb60424eb9aee2efdac83086cb6dd374120941343eb314756113e084f943cb60d91470000000000000000000000000000000019dacc8180e6dd09ac4bb97114d2ecadb04bd2aef6e5f0993742c5270267e42d052d436c99ba61f6c0fd1fd2cd51d172", + "Name": "matter_fp_to_g1_86", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1", + "Expected": "00000000000000000000000000000000123313e3cc006c4b95938f5eca903604ac9272c7a0c79cd932407b70635d7ca5de9297496c27406f180d5edebbb54c7e0000000000000000000000000000000002164460e59cc8788c96e235a6faa7fadb7e6ee9f6b0b95292992973ff54a92147dc7ae8e8f217515b6185875bd0bd7d", + "Name": "matter_fp_to_g1_87", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a", + "Expected": "000000000000000000000000000000000393a1b2395447b2e2838c2f49493c185424c4848f888616f16a95552671ff28b5ef223bf34299005f22a8df6efd68290000000000000000000000000000000012b1fe46279922e92d356355752ae0c2f28fc55de39ebfbd317a6c1c507d973f88c6282468571a1efc20c10314ac72f3", + "Name": "matter_fp_to_g1_88", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122", + "Expected": "000000000000000000000000000000000739f563b42648cde5befaf44317468982eb9d2fceee7d2efff1755be973cfc2beda829268246d09cd29fc3aa91f0b8a0000000000000000000000000000000014fe0b03ac5e0e03acd7811270d65742a3345bed7a4790d5f40097dd34050d0043104b65fd4691c251f03e67525d41b5", + "Name": "matter_fp_to_g1_89", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be", + "Expected": "00000000000000000000000000000000128e92c9c10fb9b065fe2c2dcfe365e98aa54eaeb3fae987306c7f0a227171ae0b3464d01a54a8d6b144ff60c45088a00000000000000000000000000000000001beaace4e23c9a31e1e9eb8596b3b05b9d72553f44c61627654757080171b05c900fe1b638193a69058e8d66cff1aa6", + "Name": "matter_fp_to_g1_90", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f", + "Expected": "000000000000000000000000000000000a6099ebb3a1101206bbd21149cf22af2371106bd34671c1cbd4f2e19311fd100bcb56a6d9d77bd834f972e55e0fb75e0000000000000000000000000000000001db77a2045e54b0ac4b3d61190684b4eec9c4ea415e5c820992b70d6ee2e086c02892228c4465c8494f939cc0b7b5ee", + "Name": "matter_fp_to_g1_91", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a", + "Expected": "000000000000000000000000000000001101cd37b61247a9859bb09ccf9eb416643f86b7109bb45d6827fbf424956c9a16b2a19c5e198551c43aa1934ad8ed0e000000000000000000000000000000000da562fcb2e3cba853de6d245a1ea0cfc3ac120b316a5f4f7072cc35a6634027409ad08c5d591a6688b24cdc4562cddb", + "Name": "matter_fp_to_g1_92", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb", + "Expected": "000000000000000000000000000000000323c3aa4b20691af32696c449668fb6da6a0c2e8eb176fb8fcd8aeebc9b5a3bffc57b28dd35e374811d420419fb0fd30000000000000000000000000000000019516a092385d8c917b46a742f086c51e2648c7e9a709ebeb5a0f8bc29c9aabf99972aa3a218582f37d91f9758a5ddb2", + "Name": "matter_fp_to_g1_93", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c9436", + "Expected": "000000000000000000000000000000000d0351d8557d21c2dd3b1be77bb01df804ebb9e2d7e80910264ff94861cdc0a4deedc1231c61b7503c5d653e31fe10850000000000000000000000000000000005858ee487860d1ba04cfdcedebda235616c2d271ed50f89d6cf2852ea7e10ac825dacd8b00071684858a12459d1705c", + "Name": "matter_fp_to_g1_94", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba", + "Expected": "00000000000000000000000000000000176c50c2fcf1bcbe03a1a1ed2eb120f94ad4fcea34a59607ea595bc2b37cb92f87641191b65d4b5d57f5491ce6576a670000000000000000000000000000000000e177361e09975c98849faf8e24086f75a48df0f257ea47b659cc2a142a57ad1f64416f6dee5cbc4e57f780dadd1cf2", + "Name": "matter_fp_to_g1_95", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c29", + "Expected": "000000000000000000000000000000000dcce000aae744f8b3b6754af57a36786d887d7f9857654f93edbcb6c4416ccfea5e859acc82860b5f706087e87cdc07000000000000000000000000000000001847c32c839668a38669fdbabb512df15cde2b28ca336b0e158d1fd57f74638d86ba40ff68f0a50cead7021e86c5271d", + "Name": "matter_fp_to_g1_96", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4", + "Expected": "00000000000000000000000000000000062a58846d39dd1fdbd34a7117797f2200d814b2a8eac9479885762565a979e93b5313575bff5ada3211eeed0a3f4ddc000000000000000000000000000000000548a24e7af2b38c4d16d8dfc8fb2d7e7669051e2643c44aee113f20d31f4853cef84e2dec20095c273680cca278331c", + "Name": "matter_fp_to_g1_97", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa1", + "Expected": "00000000000000000000000000000000107f01e4fb6430e34128e3335872cf40df2b498a63e048d46158190cb627e37833d2238dd72681037ce376384736b43e0000000000000000000000000000000000e1812299403efe0f8d111d97a4b7e7b8aa1f4ec58f9935b1367d81a847fb42cf756154448f9172118123679a41a280", + "Name": "matter_fp_to_g1_98", + "Gas": 5500, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79", + "Expected": "00000000000000000000000000000000188dc9e5ddf48977f33aeb6e505518269bf67fb624fa86b79741d842e75a6fa1be0911c2caa9e55571b6e55a3c0c0b9e00000000000000000000000000000000193e8b7c7e78daf104a59d7b39401a65355fa874bd34e91688580941e99a863367efc68fe871e38e07423090e93919c9", + "Name": "matter_fp_to_g1_99", + "Gas": 5500, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsMapG2.json b/core/vm/testdata/precompiles/blsMapG2.json new file mode 100644 index 0000000..0b2ad89 --- /dev/null +++ b/core/vm/testdata/precompiles/blsMapG2.json @@ -0,0 +1,702 @@ +[ + { + "Input": "0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641", + "Expected": "000000000000000000000000000000000d029393d3a13ff5b26fe52bd8953768946c5510f9441f1136f1e938957882db6adbd7504177ee49281ecccba596f2bf000000000000000000000000000000001993f668fb1ae603aefbb1323000033fcb3b65d8ed3bf09c84c61e27704b745f540299a1872cd697ae45a5afd780f1d600000000000000000000000000000000079cb41060ef7a128d286c9ef8638689a49ca19da8672ea5c47b6ba6dbde193ee835d3b87a76a689966037c07159c10d0000000000000000000000000000000017c688ae9a8b59a7069c27f2d58dd2196cb414f4fb89da8510518a1142ab19d158badd1c3bad03408fafb1669903cd6c", + "Name": "matter_fp2_to_g2_0", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2", + "Expected": "000000000000000000000000000000000a2bca68ca23f3f03c678140d87465b5b336dbd50926d1219fcc0def162280765fe1093c117d52483d3d8cdc7ab76529000000000000000000000000000000000fe83e3a958d6038569da6132bfa19f0e3dae3bee0d8a60e7cc33e4d7084a9e8c32fe31ec6e617277e2e450699eba1f80000000000000000000000000000000005602683f0ef231cc0b7c8c695765d7933f4efa7503ed9f2aa3c774284eabcdd32fd287b6a3539c9749f2e15b58f5cd50000000000000000000000000000000000b4f17de0db6e9d081723b613b23864c1eeae91b7cbda40ecd24823022aee7fc4068adc41947b97e17009fad9d0d4de", + "Name": "matter_fp2_to_g2_1", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de200000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799", + "Expected": "00000000000000000000000000000000184af3f8a359dd35dddd3dfcc6f5b55ed327907ed573378289209569244e3c9c02bdf278eb567186f8b64de380c115360000000000000000000000000000000012f5ba8e520c4730ac1fb75dabbfdc0181855e5ba2968a8c0ba36a47ab86ac45d19aa3d55f15a601e120be1f75eefe240000000000000000000000000000000004e313db704b103c2c1e3a58f8e95a470e7199081eb086e9524583131714c4a3db551fd51a3f2314a19a658e7b1765380000000000000000000000000000000004040eab7416a1703b0d103120506f1de2b26b0f48c7a0ea63dca4d9ad1c478ae03b5d7bfd51f4cd6f8cea26212c4edf", + "Name": "matter_fp2_to_g2_2", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60", + "Expected": "0000000000000000000000000000000017fc341e495bf4ef5da4c159a28320aca97ca28fe3a0441242cf506b0f89bb52f5b5d8c6e038d229ffe67d00151912f00000000000000000000000000000000007666300b7be3d904ae3d19019f7be5cf5ba6161b969c1a78aff639a24387d8fdcc4d0e3cd81ba6f063ebf2d859370f20000000000000000000000000000000007cc705dbfb5c0418beb1cfbd864fa0631bd60eccfdb16b5d55b6ef3558e2ec87dac3b45294dcf04a064d6d1eba5a6eb00000000000000000000000000000000052cb9c982e6b05c1d2ab4eed1d8082f96426b55615ebc6a53bdc320ccad0aad044395ed641b3176b554f19e62d46b73", + "Name": "matter_fp2_to_g2_3", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f1570000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac", + "Expected": "0000000000000000000000000000000015ad9155037e03898cb3b706f7105e39d413ff3a5abb65812b8d21d003cab8fbb607d3938ccd6a774bc8debfa30f42760000000000000000000000000000000019d6382bb2d78180a8998a0536d67412d00ec0ef65f4cbce01340b8d6e781c0ff790296f8cada28966b147c69e02f366000000000000000000000000000000001290c2c205b748069d0875a89ca74a3b05ad8218ed46a1570696932302983c090d96e17e0b828a666fdfc3b72cd348bc000000000000000000000000000000000114f2f7ffaa9f90b547e86c863a5d3585819a78b095848dfa39576a10874a905488687b73e613f3d426510f5d1d1ce1", + "Name": "matter_fp2_to_g2_4", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c80000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3", + "Expected": "0000000000000000000000000000000012084a53cde353a46af17cd2fb02c477e47b874d8ff58025b5015837759032ff98013dc5bf01253bb964f035183c9071000000000000000000000000000000001659272ab7e3a070a5c7b25a5d3402f7371ed67e58cac8438df41c39c1acd95ac5886b030384bf537d7c4bb8ddb2c538000000000000000000000000000000000852ddcc37a09a0a8f62dfbd1ba5064c1f6afacc9a279a4d998bed643eec5a0d96d6bad95701a04f52c83e8f87f48d5d00000000000000000000000000000000097a399370875398028d42bde8cf4e9641730af7a2971e2f59c95938120603a239c65030ded4323c955f7fd24bebf31b", + "Name": "matter_fp2_to_g2_5", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be", + "Expected": "0000000000000000000000000000000006f8191123f1e8f6a05e4e663fa763c8a0ade5de3c7cd38ec1c82e1c85f123ab51fffcebd677afec8e9adecd8d11263d0000000000000000000000000000000004fcd825bc55d044eb70e0bdd5ea2ac58ec1487e903b431c57a640c756265a382581b8450fb15dc649cf22a8539088220000000000000000000000000000000015259f83d76490bb868bb88c2a2c3e07a326bd3e97fc2f552adf85722a360a443d720c328076e35224328e09494746e0000000000000000000000000000000000f76b0b960a1343b4267f5aff44901fd6796a778b1a87666b95b773edd0e7ffb6656d4f0cc3b9b38bc6c0ed20cfce153", + "Name": "matter_fp2_to_g2_6", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3", + "Expected": "0000000000000000000000000000000018bf5f93dbc2c37479b819f8edccd687c4d3c4dd04f8c73762fd89d0c003674e3b2ed749d23e775f925279b3112689f80000000000000000000000000000000008a033b197aa8ea2213dbd7ed478d98c25dc6e9f91b9924f3c14124da26a67bb196926e02da89b746f2a67b14ad226070000000000000000000000000000000006f7824bdc9c53212609512858278f79d9b094165ff178e3da8776e24311bebbd9deb29f366d4c7693a15c34df118403000000000000000000000000000000000edde25fc24b9ec58b3c317aa3ae48dd5fecdf6397ed9636ea042722d264db0b1a89a15a1e16e892755730ef52796527", + "Name": "matter_fp2_to_g2_7", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad", + "Expected": "0000000000000000000000000000000002d28025f4b798083aec3ca9a91a051ce27a374b115c944932026b4fe0dcf68b335d5e47212f800c241c2d42fd219635000000000000000000000000000000001742fb6ef8e9a5a7572b0d3fa4ae8ae56c9c6f4daa20d0b88212c40511c6f6b5ee98314a2d1cbe4bbbec907495a1ade8000000000000000000000000000000000d700a511a58c1b8f11153669cb21d88512dfdacbabe38e402431b4f7ba374b5f9a88614da2d56799d39324e9d19e27a000000000000000000000000000000000c6068bc7a43d614b8f1132b13e04f66d2fb5ac0c5bc8501b754a0bcf4f382db92b0994c4999e104c9d1111ef91d5edc", + "Name": "matter_fp2_to_g2_8", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20", + "Expected": "000000000000000000000000000000001350c68434a9b02392e60540a3985bae8daf9a170b30336ac73afae6f892c7ae8f5f1cadfb2780d6e5961ebf91cd69ee0000000000000000000000000000000000c20bd286fc1886b9b28dfa40d1a27395cf76a8b73946849ea0a7b5e12530de13c16acef8fe2a2c247ea65ca023eed70000000000000000000000000000000002d8ffd0235fb60fa573662034d46260e0c96396537b2a9d486dd03bdd13c5a1efd2d3cb9849ed11c4376b665f378226000000000000000000000000000000000d90ca1b73a6a9566832f9f19d8530a3b12f22bef853fc44088559b923ca108cebf4291e0d7de8f25c7429d455f5ae46", + "Name": "matter_fp2_to_g2_9", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab10000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175", + "Expected": "0000000000000000000000000000000013f223602e8d12c3bb51cd393f6f59beb5c55fe80c3fc8fb0bc90eca533d9b7981563a30ebd727ab6cf0111fa2d3099d000000000000000000000000000000000962b0585c681894cb701f17ec06c0c240899db574c02d82d85ed4dabd4b8654c29b84c71d2921986fc2abc542a3ed9f0000000000000000000000000000000000f0e79245e645a6e3fb88b9103ede3e6ecdd7e45d61b5755d7a8d100d80719746af58bb23d3068cee7389b2acf17f8b0000000000000000000000000000000017fa0aac84c58283f34b9bf713cde98c175b38e92503c08205350822d778f3dd5bed8051e185c495831a628aa89335c7", + "Name": "matter_fp2_to_g2_10", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d235020000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3", + "Expected": "000000000000000000000000000000001062c97c214b86518660c5e1c33a4e48923ae89ab7d8bc5c798e631de16fc1f104aa957d3e7915aee8551e24aaafc8e6000000000000000000000000000000000e42b785f17f25b87a0dc558a8d57b19d8f41767c3b4fd70c147e95443aff2d9a743003da41d578a2b56d7dc748cf59500000000000000000000000000000000111fd38cd2f5f681bb37f6239a5eea820ce3f01023c685f8e7e244fe9aa9dcbd18f0e50705faa5d8d66b28af9f371c630000000000000000000000000000000004726d3e452f6fcb180ce1d50bbee3a23f7949b635a058f12de1cf5abda19c042168feea53211dbed0bfca489a020930", + "Name": "matter_fp2_to_g2_11", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d99000000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844", + "Expected": "000000000000000000000000000000001211bb8d3bf65b60efc7237ffecddb4e7e2f0dd36e2a704dfc9f4972897addff1a57182f8e0a0ac08c9af2c98eaa4c560000000000000000000000000000000007e9877280aad45a3b1453b6771ab509e4f53937cc6da73d3add50aff94869b27f49218fb479fe19a6176b9aadd36e35000000000000000000000000000000000ff915801695a281f6642751be77155a813847ae0237d77d2edf836aebac02b659b98d49842d4d10e82d9d146e63a3da000000000000000000000000000000000fae1c8c01a2dd94f17c660353d158ff6f3eed4e6375f1e414ade9d6fd040a48e3ff0d558c882e92e74bd6ef4ab06168", + "Name": "matter_fp2_to_g2_12", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2", + "Expected": "000000000000000000000000000000000dd00d9f31cb5148048125668286c1790cb7294e740df978ac0bdaa6e1c4ba139a04f5770b194c9bcfb123d9b40b6acb00000000000000000000000000000000085d5f4cb831720fa13cef25464a1ba7af33abcc4079d2c5736a219ad9649ebb5dbb8687a2d3952390866587d7088f72000000000000000000000000000000000de377d773e40e1c76e218b969297d15f7819c525ce39aee5114e8405bd7361116682cf9d673574d415a7016b23b567d0000000000000000000000000000000018db26c2097f72b8788ef5aad2d7aa400627e224924afea1ac7c7a6b5cff4a55255e218572614519a536eaaf0f65533c", + "Name": "matter_fp2_to_g2_13", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb87500000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75", + "Expected": "000000000000000000000000000000001853b4c4e6fcdbed29c5d3aa4a9f6d447adc512f66a32fdef06c6ad316c42eb3ca47ffe6f21318ad610d0a68673d7bc300000000000000000000000000000000123d15c37fa8b1a95229e28500c9a767e6286b780138dcff2714bf1f8242f39bebb7d86e2811551914719ca90fb5615f000000000000000000000000000000000537498c2ec64b2ba58aa0a858b69990cac544d5cac29abdf6a42ae9c04061f83580b79c2a6104ebc55939d9a2bc5ae2000000000000000000000000000000000b348c19aad3b67c690512f372d995555ee38bffcdaf33bb827160d6929d2ce598523880f6136f11e1d6482a654cb016", + "Name": "matter_fp2_to_g2_14", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be122200000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760", + "Expected": "000000000000000000000000000000000994e7b6ccafc996f672c42ab491105ffe1482e65aeb456de2213b531889773ad4d5e6ea1687d6a1f13e74878766f11e000000000000000000000000000000000b89030486a1d622c97970ee7da6189ac341b9cafbb4081463f579ab8b4b049c6e6c8b63157455770a79108424a14f24000000000000000000000000000000000ded43800a991f8c37282d803a39941d3bfbfbdc56dbf7500ef3d16750b27dcb1ad93f89714395fd3dffe318c1771375000000000000000000000000000000001994144b032e1f8c4d688754eef82cdba0018ac47030fcb77e8fd920e0b0336255d2cc8376c03e1074f91269cd2519d1", + "Name": "matter_fp2_to_g2_15", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091", + "Expected": "00000000000000000000000000000000195d99406baadc7d8740962cbbf4bc1f22b08eafb52f3cb3c588b6cb3cd89d16cb7b8d388563289f5b5ea466128525c80000000000000000000000000000000004809f70463633595dd763d658354df4f9b409911e1a0328fdaf486d76ffb410d7c6cfcc2d48fd6757d5c2a4834f81fd000000000000000000000000000000000654f8475562098a2cb27ce224674a383283cde35173e1c16b141998b641ac9ee663d766f045451a7f6d600973f0ec520000000000000000000000000000000013bac451a44982c7b1aaac7522dab598cb79b9a3dab77f4d5a4c1c97c154451499979af1f86ced8ce2099bccd400420d", + "Name": "matter_fp2_to_g2_16", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1", + "Expected": "0000000000000000000000000000000016ea88d0bce32981f489438df1bc14e7ade7a45d449ee1ac1a041c1204460cf53ae5c0e111914d8af9e6b3b7fa394484000000000000000000000000000000000db571ca6a55bc8285421553a373048f7877ecb9683d52acf07d48e1026795993e4e7177490921bc6fe1e63d69c2de3c0000000000000000000000000000000011602919de1df6cc0dd36a59c84ebb8e209056534e336f5074c9ae5323f8a03b123dc6354cf85301d838b16518ab64390000000000000000000000000000000004407d30fbd632fd493055bd4d8cbed337767a2ac534411a3eabec570ba41d2ad28ef37512a7da3611ad60b6536b3f07", + "Name": "matter_fp2_to_g2_17", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192", + "Expected": "000000000000000000000000000000000c1e7b188697aa9a053f14e2d907f2c61a59e0b0c72f9cce30faf81dc714a50113500ca9bc3af6657a5d214f52c90616000000000000000000000000000000001544c35d712eaf79d8dd5a22fbab72f8a6843728898412a7f305b205f8a50e03c6c462b87b3ac165e9e6428e0a44a74a00000000000000000000000000000000029ebafd90a1a887669fd0ace762a66bca2bf0a216333b0ac97dedb6bff3dda2bca1e3d0ed5fa9081c2887fe6a8e24cf000000000000000000000000000000000e1a01ca93ed268e0291a937483f7f8e252c91f9bd8bde55271b0c97fcbbb9219009514217dd8bd7e0267f44e9927a93", + "Name": "matter_fp2_to_g2_18", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a7700000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510", + "Expected": "0000000000000000000000000000000004e8ad9838e7e269cddf0ae5c8f0f57e7467e0b6f2b9e37e7c4bcae965e9582dc46c9c50aa01f5dc761bf2f1ad311eec0000000000000000000000000000000011b1438ccc668900914578c3ec6e1334d0823861c892608817498fe2e538deec73e0034a6e8ba9790f63fdd95af3714a0000000000000000000000000000000005b4c88196425d3ecd22bfc0cb1a95488493f85bb74f50315f0ffcdd57ad2de23c137cd6d2f6f6dca8af2e3f7bb0539c0000000000000000000000000000000017066344a0f345ecf6a2ba66c37ccbce26a3f551524f74636d4c4812bf5adfabffb0645b898b10c332e94e5f2ae2d1c2", + "Name": "matter_fp2_to_g2_19", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b8000000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5", + "Expected": "0000000000000000000000000000000010d393bf893d589c578df58f4d0098ad3cd10d3a1d0f112f51b132a369e68c0284a6b70a5673383ae24a27a9043b16cf0000000000000000000000000000000003402afb77b187b45906d9cce348976ed88c758d75b9962a53352a6c3ee37751a9928097c0d68c6f8a315def4ca875200000000000000000000000000000000019b98631e53a3ffda3fb9165ef7236dad5c0c8d57c3315617cbd3ce77430bd89b9e1d88a019042cae0075594514a5e67000000000000000000000000000000001783bf1c9b0ec44c9191dab01ef5bda0cb2f533dbcd3aeac2b7c6720dbc8e3f770a215ec8ea2035129711ce4b448ba87", + "Name": "matter_fp2_to_g2_20", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03", + "Expected": "00000000000000000000000000000000086ac901098212acd091d9c4d42a1318c3b343480f1130d6e52128d61df9e19fb61ef1ff35de0ef60062cd99202910ff0000000000000000000000000000000019109b7292f1a420f09a56dce9694cb4944808a2ce9f1964cbb6ffd14a710c35abe81300090ffcd9e95f33e0de9f879a0000000000000000000000000000000012660c4e114a215390c6f6eabc4bd6e3d062ee28d0c87e24351c7d43195253cb7b5bcfed2b4abb2fdeb3ac04ee228997000000000000000000000000000000000e56d35a7e40a86ffd2088c81488265ecc4468d6cf02d563c91611cdf8b4333cf66ef50b993fe651b1792d2b242cff94", + "Name": "matter_fp2_to_g2_21", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e", + "Expected": "00000000000000000000000000000000071d3e796fb15d63c2d5cf68f59f11792b0b580b85c8839a02fad96664f14735ede2edfd5ba5b64045b366904f54ab600000000000000000000000000000000013fd1ea38d32772458622731b9e2d9d749f2b747443f7e47ef5e041531b56f86d1775d42a548b2bb201228f49ec9f46800000000000000000000000000000000099c2bd996c8c5ee37de971e8b75a0bdd4f69299778ee3d216973c9dbba97c7a93e40b209d390024bc4b5e82560a1a83000000000000000000000000000000000c4922ed9af845467440b78efa3a53ba904f29adf66e8ac437c8bb6624b5e5ba0772a5639b45fe167b1fb9283747c50f", + "Name": "matter_fp2_to_g2_22", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e900000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe", + "Expected": "000000000000000000000000000000000e413d72fdc3db6fc79ef26ae8b37fe5c4356a80b3598513b5173b3406ffb54708b8794dae158060a1accbe956a39ff30000000000000000000000000000000019ba9dfa74fd241a55a3b47c9f37c6ebd1e8b51f46197881abb64b7f57c0e2d8f18edee35bb9da03702c0dc5cc8749f700000000000000000000000000000000183525156fbc80cc67d6cd15fd2ddf7fb0528656ec1d31b4c275ef101dbb635424abbff1154a3ee04346ac53148fb1f70000000000000000000000000000000011da0dcd666d01180902d8a7fd7d2fbb39f9c7587540451045956108a8579d7c116385a81627dad9d4cb8cfe68927b6d", + "Name": "matter_fp2_to_g2_23", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf480000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909", + "Expected": "000000000000000000000000000000001932acb1fd0708edf13c293007a035991bdfbfe0089b61c261258e8c5c10d82a5318b2af221b372f0f3f43c391421582000000000000000000000000000000000973650743f0ec8e2acca33f2ef230ee7a05635d14099cdce913ad8678458ec0dde5c5a941097af2ee0c8ffb937d09fd000000000000000000000000000000000bdaf319044101ee9aa27b3accd36a5ecaf8b80deda4548377ddeb97283537be3f7199ad3c190ed23cdb44abb8786a080000000000000000000000000000000006c448827e3fe4f274bfa55a66bc76c5b01e29ac6a8dbebd801855ba4e93bcbd03292ccf804f07f21481260c135b827b", + "Name": "matter_fp2_to_g2_24", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d50000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5", + "Expected": "0000000000000000000000000000000004aee050b0ea07118d76f835218b77b39854f5ababc4e2a29d7c8cc7c18a69c30bb22437049a051d049c8a84f7868ad40000000000000000000000000000000003b1b809d5046054924c3814d26fd5fbdc59e03e5505813bab73bc212b0f5bc0d3fc34478311c5e1ac70fd16a01c52800000000000000000000000000000000002249a026af0b49f4659eca2c23dc790fb36a7b2996188828a17d5852003f1420f11699062932835cfe6543d454521e30000000000000000000000000000000008217aea2221f8748cd81cd37777605a95a63aba36a6ddad72c1e1ac57b24d79ff9d9c4ed71a6e3ac8a378129d5475ad", + "Name": "matter_fp2_to_g2_25", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a", + "Expected": "000000000000000000000000000000001274f676bcc05e54fa4b0cce234870ba97a0b1626543d6a9f09afebd5a752769000df404e4d434ebfd561f8335f36d0d0000000000000000000000000000000002877c9438fa319dd1a00f381834e8f3d3cdebf4e1f7690cb82559a2e978bedfd2455be020d0353aa56d435c0174b5b10000000000000000000000000000000009487cc9c7a09be901673cb1bd9a51f45e5d2ed30c90cbdd3e2b294c8f866f68da55533b78152e9ef6de30c345fde5b7000000000000000000000000000000000a3a8d4aabdb260203898655745cb695e6dc90c6e7bf0248784f8aa2340390fd5d8f1c6a98eb1990eb97c2a7f103e3fe", + "Name": "matter_fp2_to_g2_26", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f80000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa", + "Expected": "000000000000000000000000000000000ef415dfc1e47f39e9632ed21c9c2bfcc1959299710dcd7935a757e3756a42c8f6c627c720fd62f9c486a8e88a64c76d00000000000000000000000000000000088079108fe7d9ac93590c045be0d41396f3204d83793c4e862c5360ddb3268a63f704a9d14323943fc85874cdadaff1000000000000000000000000000000000cce908e8dbb7ec35820f2db5ae1174e0f675b21ae416fc89a7f242df3ee98764022744842999f65132229156d2627370000000000000000000000000000000011e0e2f8513d0a71b48599139a9a29c8eca090c5b02292baba58e07b1d3898fe158cdeb3bbe8edb4a805e695e896984a", + "Name": "matter_fp2_to_g2_27", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13", + "Expected": "0000000000000000000000000000000007c6f133647745c312695439f1d8c251e941bad6e988cfe324ec7c959a9e0fb50618984429ff1841d4286922a26873170000000000000000000000000000000008edb220f77ed17fa1f4757a42ec66ad808c1acc25c4b9311be4c09703d547f648d9dd7c8109ffa89d01a35c69ec2685000000000000000000000000000000001595cc05b04f557ed569b19d64c09f4d82e6617437571fddd72a672d07ad94bfbaaed906b3a7e3db519159ec8d0a8c4400000000000000000000000000000000041157d4f40bfcef680af0143ccdd0c4bdd25e598a470dae844d887c398bc498edad715fd7383421fc78758cc9b00326", + "Name": "matter_fp2_to_g2_28", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce7300000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b", + "Expected": "000000000000000000000000000000000f983607a6d8a5c3b8a577cbd5d81ad2ae936e714199e3f4095cf280b8fd6d3699acf4d2ef251a571dd1ef4ba6d838bc00000000000000000000000000000000048c12f8b95f9537e56479b1bc43a121e4edfb6477fcb090a5ea60c5f4d01071776dd0264b0250902448f62800f4d2ea000000000000000000000000000000001644ba272d7003d0077991ccb4569638de0dcc48fd2e8e9a41cee1d2200aee1a849f2d620f60beeb06b08c31cd4eeacc0000000000000000000000000000000018892d773f7e48247215484ca0c8d996833c43a5291b0380c97607c86f4ab2784e692673a1da012ac4fec2713d156a49", + "Name": "matter_fp2_to_g2_29", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82", + "Expected": "000000000000000000000000000000000a06ea8e644d2d762520ad956d41ac2086a588450bc34f6d070b86fdfd73cd0734341a751d823935a009b7517770f86e00000000000000000000000000000000140ef0d6a0482537da7db8d775ac3c4a93b16c15fbe4602b5b1843ce757aada5f7776a74151d0bcf760f7284d4ffe56c000000000000000000000000000000000873c90f56a2b99da2f0a1528b8e376a5912f9cd81a159379ad70b7c10e6ebb7fea0a90d65543d968a34ebd539372e89000000000000000000000000000000000b05ff57079386e4e18e73cbff5f7b0efa329ef7355f083e8be258922203240dbb8926f7d11c22ab4c16d1df4bcbb600", + "Name": "matter_fp2_to_g2_30", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb", + "Expected": "000000000000000000000000000000000e9d6f9e83a2584f2cdacc4711085bd251e060f8c87ff7538ce474d663c6f23361c88971c9da589586e754ed69699c820000000000000000000000000000000003fa90cc1dd81b815704e15c0448bd0e8e8d0cd7ad51237a25d4b8a0f78f532b18ec30a108930b7407b7486aad9824de0000000000000000000000000000000000cb97bce1f75b1df5a4b52745014eb632d2d2230e52a9767e3dfd76754e98252ca81ce274b92a2947f6a65fedbaa3e400000000000000000000000000000000090edabb37f411fae1764792083c8c7412fb470833a9f7399fb312c58687d4afbdc622ecf9d74cdfa3ea87382adcdd5f", + "Name": "matter_fp2_to_g2_31", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de740000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5", + "Expected": "000000000000000000000000000000000cd234fcc729a4206233e46875a557027cb52c96322386b56d6e50d95dd9d23b6f8936ddc6f8475b1076a855c1ae23510000000000000000000000000000000010a774120f607bf9ad2d7bc498536cc9d35cefe384f88a2439a75f1a4f6a9e4b4253daff0d2c91b5915ee0e9a99b4582000000000000000000000000000000001496e7181495114abc0314f580c16038a04a8dab43b5564d518dba5f5e48112ce9daca4b16b6ad51c3af54ec9ce915d20000000000000000000000000000000002c61691a96a2120663c726d7fba3ed37524b58c92a024c15fccc659d1d2cdce077ba233a0d4419a6f237ee4e09abf52", + "Name": "matter_fp2_to_g2_32", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7", + "Expected": "00000000000000000000000000000000011cd454f16209b0b7040c744291f2df465ebc786946ce3cde77fe4d4bcc4b60a51573c45b8bb2d209da69107613764b0000000000000000000000000000000018a026f29fc2f81e82015ef8610b4396f2e3514ab1a213356953804d585c5cd6a3c5cffbf70d63d9dfca50129021f0e60000000000000000000000000000000015bdcc8c139e636b05ba7376c1ced4a183eb465df53b1996f4ddc8cbf42cdff4ae2bbc2d24831a8ec8b1134cff4444ee0000000000000000000000000000000017671fc3995babcd2c0a1d2a71c417fea84e29df67fa1096fe6d3ec77c45b64fb8da6ed08a57726ab314fb860899961d", + "Name": "matter_fp2_to_g2_33", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb", + "Expected": "00000000000000000000000000000000156d8823c37c81d8f03c0b2e61a2342aab6e6c9db36cadc9eb741e085de711e9fda08ca78f21753c4fdd8cec059b6c2800000000000000000000000000000000064d4fc2584c78f1e92f808d4457070b0470eb8de9d558885bba8b03efd8d8e195e4923d8e3382481a0ecee905371ae10000000000000000000000000000000008f1dc4d2ba12e7e3e1b0ef3855df4dbf29468bc99d5cb29fa3058a535af2ba038396bccaa238bba6d538498565c2809000000000000000000000000000000000fc9839b6ee876f7846b5086d487360b8faf133b6f5bd2dbc92a7fe2261b91b15aef8d90c227cd5f8ec05e32d807e022", + "Name": "matter_fp2_to_g2_34", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2", + "Expected": "000000000000000000000000000000000d40f1c25dd57e36ed305276d4505cb250d2d9da0d5b954fe5e396b2c17a5399613243216586cedb19340e80f898873800000000000000000000000000000000063367c4a622fc925319fc6d119d8592f40f126ae05eed86ee5e4f6707b1d234c747e698c40f292dcb82ac5fe74ea80c00000000000000000000000000000000199ddbb5d4b6cd0fb9225a72c53f4596cf2597de63da56f4a9a18be8321a982de17367b0f3d794fa799657dd8ca10c5f000000000000000000000000000000000f1ed84e4fd958547d40cd2dbf16e2da4cb6d0d02763441067221890ae27ea1f689c26c900b695464ededf083667146d", + "Name": "matter_fp2_to_g2_35", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b2990000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf", + "Expected": "000000000000000000000000000000000ee446310185ce76e31c13e4ca6c43166d971d9b9c539c7d0e8dd8ebbbdd9249922cb674bf6ad6840c203a5e208911fc00000000000000000000000000000000037344752896cff03bc39a9d09757a83c15fbd90f8bc1d8d58dca9b23bc00fa2b0f3f0bd7c9ed857d285825d40afde450000000000000000000000000000000003ef77f0220d1caa7538ecaef1ae2924ac1a180f11004034fc118aeac464fe1ce684b5fc90dae3370e3f79619889f3d7000000000000000000000000000000000fdfa434e7bedec071a1a333088d06299f55735f085a1e907a1c71c312bbb8d27ffa7de7ac69d421ebd675c4afd37594", + "Name": "matter_fp2_to_g2_36", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec43329200000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036", + "Expected": "0000000000000000000000000000000000ac465de3832452edcead434729be73be90785158617b5ec3ad53b12653e43721eda7de6742dc51d4d4bb58a291999f00000000000000000000000000000000147c39a5c162afa1f8eef400cfa1bdbe5436bc59d93973f50384022962f828ac934a4f88ab7c3d505b0bc3bb002f5efe00000000000000000000000000000000141bcdad53845a7eb2ec08189a55445059dad24ae5d39fedce869791aa28459f05a6cdf9575676cc6f3dd7d6faf077240000000000000000000000000000000010e9f539a9ced860661472f53147d0347927f065ec09bc32e00c5bc157b07f8b41b05aa4e0eedd1f73c7a287b2d0e5ab", + "Name": "matter_fp2_to_g2_37", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d700000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e", + "Expected": "0000000000000000000000000000000016479eca30f48bfdaba4c8afca63ddbf59fe3367b2d3c17d15a5869dd2956fc67ebde964530926598cdcb62cfc993d32000000000000000000000000000000000650b4fd24ffbb953ccdb1b112799149d29e2377ee233b9ac97f4db432da63c98b8aad751f6060d04fe1f9262b75fca50000000000000000000000000000000004568dc0b9b430596f2fa59291ea6f923d552683ab9ab93000788145cd7c468c5576efd981c9ecee2ee0c16eca1ecdbe00000000000000000000000000000000154af1490463930d6b8261aa1d066eeda6d65b742cb53c65348e5cd766d86982a1489ad191d1b126233f193d24823b9c", + "Name": "matter_fp2_to_g2_38", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b60000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee", + "Expected": "0000000000000000000000000000000000df692ca763a74877352af3609c8cdbc184eb71bd35fd86334cb88543637b40b3adbb5802dcd7b88f4d722b566aba7700000000000000000000000000000000181495e709d1617f2d912f43487ad3920ac5f8e47395ec4b58bcf0b2d986c674a0c7838830a039bfb5bb59cd2fee2f5c000000000000000000000000000000000d20b482dd8aad583bd5d08ba9c61b3e954f022d48f9f4f62ddc9f5015ac71dab7d206b1d8b885d5e605519bd33d93a20000000000000000000000000000000010d3deccb9364ee386eb35c7117bab373a76d024627b8a031f96465d5f75b029fa992e29ad4a170c4473cd1df585429b", + "Name": "matter_fp2_to_g2_39", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38", + "Expected": "000000000000000000000000000000000a007c914ed40c7f2719fc70def0d4752cbaa775cedae9365c5afb61a5e1a2854f9e1ce19af9fc85bfbfd2c33f5bf095000000000000000000000000000000000d85b0d173c25c2915fee429d2468a9eae01ba43c0f1a661f2ef83c1acd726865c00c40ccbc3aae306f93074e5e7858e000000000000000000000000000000000b3df302ec532c8100c121c9a3455392c713ec60de1f9572b040b0966f8ffb888e8cd768dcf6d63d4835a52d13a730c0000000000000000000000000000000001123c43dda8717d03fbc02fa53c4b1c9a931db6b274162cfb02ef5eec602bd8161dedc37c7f6217c8e82236f06e49e2e", + "Name": "matter_fp2_to_g2_40", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e60000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d", + "Expected": "0000000000000000000000000000000016d8d9b1b59a22fd830f88b9850576488f75672a87ccb766e52da77f187a8e66071130c7e71f86675f8379b2a8802c4b000000000000000000000000000000000aa4ca84aa23f01ec536ffa25c4b7a6c822f588bc75a4a72ed9237c0588ab892c8474a0f23afc7ff0dbc3b08f8e35b60000000000000000000000000000000001425e759e2537d9e5f0f356ff1d38128eff3a771fa661a839f7a8d0f548347438574ef7d592cd4273ef9b7269c9c5d7f0000000000000000000000000000000012cf1c67d1ce244ae22eec0bf4a400a0f356b9dd075d87a6e61941933872d7c0e42c1d238b2c1704d2cdb2df75169f39", + "Name": "matter_fp2_to_g2_41", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7", + "Expected": "0000000000000000000000000000000017f6e2743cb30fb93816d0dc802c24509315363c3652b0244e1395cb9200efb4d7b9fa7642e8d165d28a00740f1a83be000000000000000000000000000000001483644fffd3989ac98cea71843e87b8e446a3d497630419afe99b3f1729a831fa6a49bf763b0c410cfc5390ac4ac1db0000000000000000000000000000000018ad20ae5012266d771b2c86f891f498c2e90a7df19561be240319edc1fbfb316948fb3f8a6b0e3720676b076eb372e10000000000000000000000000000000012f404211899d8fc1221ab5b82db9042ad37e63348871e5ac6cdbddacda0a564888f89d22712069b6096b58c5935edd2", + "Name": "matter_fp2_to_g2_42", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1", + "Expected": "000000000000000000000000000000000d4d78f992f12aefb0e3a6b18fbe2411108327a9befe4a822618fecca4def3169972b4f1fb254cc4656a676529d554ad00000000000000000000000000000000145ef33250240a5c9434d4b2cf2404d9e7cc51b55e482ebc6a8aed85caa21ed00623b3cb2d76ce2d96b2f346d395dfc40000000000000000000000000000000011af2ee2514c58078da335c0273cd18b98d1ac6f0e67890677403f71b0e06863fc72611c0cfba39ac894ae500edbdbae00000000000000000000000000000000186863e7c24cbeb45f7a66b5dddc9b57c7e22c5139aa6bdb82e77cd8182bb8d2fb7bddd7d3516b5422f92e08d02606b5", + "Name": "matter_fp2_to_g2_43", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122", + "Expected": "0000000000000000000000000000000002af4a301e90c71eb375110e7fe23f8f05e2ede86b1a9b240e8d1d4d70e96f1dc3640fca7ebbcde9918deb91f3592de600000000000000000000000000000000058b5f36cfb6b0adb14b397dee4c3769c7446426eb5719aef4965cde2dcb70e6f2fa60101a5f03517c0040093453d092000000000000000000000000000000000f77b560469cd42c5cf3458ae13020c6678af3cddf9bc559372d12bc5d6b930795e1eb09f27cfdb8215f39fb2a11b30c0000000000000000000000000000000003308985946c742af7bd7d29abc2517ff1d225607b5f11fc66695cefabd8f25e294ebdb7339949d6bc4d98db19533966", + "Name": "matter_fp2_to_g2_44", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f", + "Expected": "00000000000000000000000000000000093c936d57135b25900bd5dd55cd579aa8b85b9c1b5e8dac6196c4450b624734d9bfc3fda499cedf2e877d79f2da650b000000000000000000000000000000001832306d3ac1c1c61bdaa73c9b6e9c2ccb484c3baa1de6a217a2884c72b72618e864f75fcc2dfaca358181ecbd3347980000000000000000000000000000000002b2e5ff1ee02657fa88c7d6f23cd4c0465152a9daad8479b4b68c97930acb22e4e2eb0011ec4062b8ec46991a7cc630000000000000000000000000000000000712543547e9d24cc78d1c2e3fbe0b51222185f4c6e513256d1ee066ba50beee20321bfd60462e2587c375a0e9395715", + "Name": "matter_fp2_to_g2_45", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb", + "Expected": "000000000000000000000000000000000f52e2f8dff9a93b2985d5c2b8b980e4869af53ce55aa48bc1c9295e557e3b5ff78896e5e6342c2d535d18b11950bf390000000000000000000000000000000013d36cf2805d350c5b748e639d20e592deb4c5bcde99a94fb539dc56d48a862151b925314f21dce4c9130b32e44f54060000000000000000000000000000000017728f485d881b861f626c9de8b3df7d807b266de6cf8dfcba262f40a6248fb5e6506d11e88f460f0b5f1a1907ae5f3e000000000000000000000000000000000c0ab998f63f861c82106dc3ed5ea11a16e98139e8686f8442047a1cf9ac48c3d34b5129263767830144e9a13d4a1f44", + "Name": "matter_fp2_to_g2_46", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c94360000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba", + "Expected": "0000000000000000000000000000000003463d887c4d0aaa21acaa308d77f2c7e13d10157efa9ec3fb1586a8db5ff1a9e807c91c86afc4df34c9fcf06e8561d700000000000000000000000000000000128a81efb9f30ed811ea3163c71b6a46ba2cbdbd3a9f93cb8d0f518747cc860431c6e93bdcdf36d00f83838965da4b50000000000000000000000000000000001777802b7c41111b38da3fd8092c280b4925827b2c1592f779a4ddca71f8268858855c413fd5c0057a652155261d75ba000000000000000000000000000000000c88b522d6dc2000cfbb7052e141ddfe15c6cd7fddc970edc4afc36fc59e7f8e31415706a8121e8e84348be0b50d0d88", + "Name": "matter_fp2_to_g2_47", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c2900000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4", + "Expected": "0000000000000000000000000000000014be96cfc0dbe09155ac8d8233b71ed584153e279b2b2be88471eb653aa4913fd2c33947547c61f7fd8bedbb552a8b1b00000000000000000000000000000000146b9a0011260e2646920894cf405bdebb101db12da7849b30868655fb5f972113cdf2fc322cc246d3dbd9f20b98fe2f00000000000000000000000000000000104bc20e104da5173dcff3e195f80960819a0d64e922bb484c2739c4b7c22535f7faeb1c85188aa853277740b389eac90000000000000000000000000000000019f5aec599f9ec286aefe48eedca3f929ac6c758c231182b92dc965d6ac1f3db53d93f57d733ca8425a5dde070b0dfa8", + "Name": "matter_fp2_to_g2_48", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa10000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79", + "Expected": "0000000000000000000000000000000004ca0149527817b4df0f08acabd4e8c6329c0d1bd9f2e8211cbea25d69b84009ef158c770f948fd67e4609ccadc938680000000000000000000000000000000004101b351e2a9d34042291f38a289d8575872104bcf76f60bf888c60cca5101c34c247da30f7a8db4f0cf2f32abd302c00000000000000000000000000000000167e668de3207ddc60b8a5d5d246bf2f63ceae3bcbc4309e73eebf4d4234c2785bb13e4d5d8fff9c5f205e4fb942a2f6000000000000000000000000000000000491b965ed005065abdac53e3065781f2fd23f6159debc64f01c9f62073c651da33c05ed84617efcb5ffe08ce05e3b2c", + "Name": "matter_fp2_to_g2_49", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003f2dd27e3f0ab503a8752c0802ee14c655271e8cfbc734905b4331fb4e70cdfe291ff71053fbaf91680b1dd108f458f000000000000000000000000000000000c62014b7694a3e81370761e0adcc32430547a1bbe33746637e7762dc24f8d04b4bb955f17ca901659482c622d777642", + "Expected": "000000000000000000000000000000001541320fb6f8a8c3c67278a7ad05ae7927d3555ad562bc8addb54c6693c51fb1c7355d2e74ff10f6bc3eb182d8f5b88b00000000000000000000000000000000172b65b110935b116ee683c8680ef0a660afdee43b9b8fce08ef3a70b352f8710c06b820348c338fb903a165cc5376da000000000000000000000000000000000df529b0e274e2e8993dd89ffef487aff23d31f502a19dd7d383de08fc77f1308a59ac5bf7cc899e81d377b2422187850000000000000000000000000000000010b40c9063d174b358637ab710d15c80d9230a1b3a056cfac4d583ad8c5b79c3d9bf22a1b0a4e0f629cd09ff7586f886", + "Name": "matter_fp2_to_g2_50", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014d1491a45b4b0914a6cb2e4dc7de9d0962f5c175cd571057cae1e17d2c943954d119690ea14f5815f858d277a9ad828000000000000000000000000000000001650771e0f7b33d235f229b7d49a7a5a0f00f78e5f4abaa70f39ec452370198a8532b5873e41f17c449f9c565e6adea5", + "Expected": "000000000000000000000000000000000978ff68d94d33703488298658cf2c1b6034d3d8d21c175d71a0545bc2f99eaaf131f061f3e4f55622668e686e691f53000000000000000000000000000000001124804b252f8187178435761897d00c43cf67b588ca69f97c20b0ffad3ed94acc2c0f85f900713dd6ee9f38e5ca94490000000000000000000000000000000010ca2a8ce71b9a096c132c4a060a17365475b6556d4fc6284266ae787e217b3ceaa3a32bdf751375eaf6ab49800132fd000000000000000000000000000000000a43b435b116d9480497f6b2e1bb377550cb1a7ad59e4214bffacd517afc6b7bf91112fe57b17a02a86876ea07361bca", + "Name": "matter_fp2_to_g2_51", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000aeb244909654b3e1df7cbeccf297223be57c2f514474edf0740dff48dcd5898b6e49eb65c787aa56ef79778249f4e07000000000000000000000000000000001007c89a66dab07f54313db8682f9e829baea229b030b4514d9c93686747207939c50a198e83ac2cf50315e02642a24f", + "Expected": "000000000000000000000000000000000c3d87b1b78fab65cfc853304c682b39b6ec2b4ed005e9108f69daee5aecbd586c9818c37cdee865ba53eab9302320ce00000000000000000000000000000000062a7203cd2fd04a957cac8b6b6bb51e635ed7165c547ace10f93a32b7f37747a2e63d5767d966684409a6c748d4ee6c000000000000000000000000000000000526b44af8157dd68725aa8743684e020c1e385af7413c9dcebb320568663d18b6f29edea26f2628358852b794ffcc8e00000000000000000000000000000000098126f486ff55c21f64421e85b09a1b54f42d3499dc0e198db6f3bf7dd8476cad97c02b5b366e5ea20d8f83cc223f7c", + "Name": "matter_fp2_to_g2_52", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000398d86b5206bae4ceef0bcc6335b1f6bf5d17863ef3a5e8463aaa69d9f73f8227263964659d4b770d6d9813f9399b9d00000000000000000000000000000000096bd18be1176e16a0d80e60f7d7ec9d3b6162f683440e3cde70082a73605da3783c8a058bf76d7e25056f5cd95c31ed", + "Expected": "000000000000000000000000000000000f3e76e7d1cadfaad08d16457b02d89c40c157225eec7916d306faca8dbda008f41792888c647dff1acb4d4ba3b43c4900000000000000000000000000000000132bf730456e2afe745a58cdee689e37223292bf682d5b7dafa7df99e40d385559d0b3161bdda0bf5173c43ee46412dd00000000000000000000000000000000141b36ff6890e35db0054358bc0731b3aa0efac1a247a51daeff3515746456216975f44769174a4be41c109d35e4be33000000000000000000000000000000000ca401ee1addff8fe87b600e057ae34ba297886f92c5be8a8c00b360ada71831e31bc4ea1c309c7da31cb28d1011ecad", + "Name": "matter_fp2_to_g2_53", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004ca5cb60c32edfa385baa911ccb7fd1f383824c22b945944b0f3f7011db8c123efd8fa70e4fe699d40c6716021f0151000000000000000000000000000000001339adb0dd8d83574c2008f0a7ed001b0808d2fb639b5e57e1d293884247d5c66c948ecc60caeea7bf440a3a44ed296d", + "Expected": "0000000000000000000000000000000009d0af77517b654ad97de3ee1dbf69ec1eee901facd0f8c39b4af393d0e63957292a7529b461f7fa58909acad32ba3a2000000000000000000000000000000000fda17cd878ec0f8c294daec1bd1d56c63e875b002a81c9c41146dbb564bab6e4eae2717c9fd718af1ba816a1526e8fa0000000000000000000000000000000017563b7ff22b50b6d9e24b1e0d89ca5c72e68d4d3cc24cce36856191111d087c3dfb392070462dc7850ef5a1422931c600000000000000000000000000000000020001fcff638504055ba35230b360e6d3cb5777b959c194d6f9b038b58d3ead0b82b28bb215378abd85d357b85ea260", + "Name": "matter_fp2_to_g2_54", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000089211892a61202b1ad3a85aab9f08f8d028f3e3deb16c1de4d62c1a403fa63c6dbbdf8cec37f0a9d6f346b1c7ee179d0000000000000000000000000000000012a9fc2070b326f4d7e64804b3a2e977f4bb36b6a4afcf27252af757d8535e8172a99dc909fad5a3ff8df23d6d6c5948", + "Expected": "0000000000000000000000000000000000d51c77c2443f00d965c0d7ec9b5a8a8003c2a77b0ffce3e47bcb55420e8690a9c2ba9235b62a4b351d79d216a3aad40000000000000000000000000000000013cd46e3ee6cbb3bfb771ee30b5f5faf0a64a9efa1f8fc57024c83ad07a9b25e513f211ea604cfdf319dc42bf4c067d300000000000000000000000000000000009fbe1fffc67220067c948e0c80de23795e045fbe8031c9010eaa69356ffd8e5741cfe12731ec13aa236630f1b1dab4000000000000000000000000000000000e5ecdf808d10d47f041e4b078e79b32520ce9623b50059a3bd8b59daebf9103c31425659ecbaebfb2384d1c2f1b400d", + "Name": "matter_fp2_to_g2_55", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b37365748fdb21fcb46f94edf86c586f17d0e042c4683e68c6cb83e7c0ed2c30ed260c15af2c9dce77bb705debfa7590000000000000000000000000000000010d7c02c6c1ba3cf6ac09a05dfe043905e1a7eb32b67f2e8a5dfe82eaca66ef46cce43aaadeff58ca85345dd0d3bf3cb", + "Expected": "000000000000000000000000000000000f3e4d2559261829c0f4816f8b571170de1f74d75d74997cba56fdad42932db73504691f9e001f5b4604705a8c1a38e40000000000000000000000000000000018c72136bc7d3050ee693270668e706ebf70f990e447ecc6153a10625cccc9deaf5ae82d2a656b1376bf33b1c1fdc2c9000000000000000000000000000000001754f2725bfa76e92a74ad5b520ec2aa82a1f86e8623a054ebba489adfc9e71d1f14d4692ff9fdd8acc3d768b67e1b7000000000000000000000000000000000096f1373434a8822569cba0679dbd2abf619bd9a8c73e54e078688d4e2615d45431ac8cf3da5e15a83fe77d14b339e49", + "Name": "matter_fp2_to_g2_56", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000aeee59421c8ee65f8070b9d036e6bacb39dd2537d02960a3a57da4f0985cc7b27784d60fc1613f5a83c34d2250395c1000000000000000000000000000000001715ddcbaed0a05b38b1c820724405a713cc0215a4c497892f00746c0f9af28b440a3686178d9bfcd41944a224311306", + "Expected": "0000000000000000000000000000000018d515b8c99f541c7dd448c3564c1909b84517b662d6a2d1176d3bf5e70abc0a2995c73ae3f1614bfed2f64229e173e80000000000000000000000000000000012126ab671420933cc4fa9206311200cc5241ca3eec54f5d97a426a72642bdde32a65c79735446779cd1744d112d544100000000000000000000000000000000190d836312ffb0d6bf493f4c942263922659abec46ac4de639efc311753148b445509f808c2fd813729b1bd96e0e663f0000000000000000000000000000000006494f9a451460ac658ec17710bef79d59b6e0fca049804c0954c5fc472bbef520f75d34408ccc62cf2da3deeb79acc2", + "Name": "matter_fp2_to_g2_57", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ca4b3e1a8351057ba4a2ffaf0cdf1c3c0717ccfe26433f6c40e2cc29e32ed884f63d25979580fb555a5a86c9147bcb00000000000000000000000000000000010c1db593af38aa14ca9dd588f54b219ff1fc9edd25b3d16c595662ffa7939879244326b14d978e0dfdd25e37776964c", + "Expected": "00000000000000000000000000000000173fa567aa952bfaa9a60b8232a185475cbb36761ebef49ea5fce900a06043d0e2c1b6024e40eadc9f4bf04b077201450000000000000000000000000000000010fdc32ff84f79fe39351cee1ed6b67dbcf2956020e2518d5bb5b367b61f86f1bce36f75516d9551d74cc3a567e6c2be0000000000000000000000000000000007abdff8a8967eccc4de6b4ce142173841c0e8399f5a67dcf0f7b5e5b4133391b44bf4d41d3ae3426839b19aa4c5d40c000000000000000000000000000000000c99f160062566418c09f10eb80f005f2c8c12825435f354f1d65bec0322e9b8ee968c009a84ba792a7ee7334b32bb3d", + "Name": "matter_fp2_to_g2_58", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017cd94e7e672f0dba9a3c1db742d87cb18b9401e8311c4badc24f811a8f40c27942da6485807701c1a12da58076c756b0000000000000000000000000000000012f6de4ac9883e78f9d658cede4c70b44bac6b4c9734cbf24298ddf0df0cf54164aca245d8e313be4aca66ba3cab5d70", + "Expected": "0000000000000000000000000000000019dc92f1da66d0855ebc8e7a2ddec623a2f843a97c7385364a631671be7ee3387a0f98940b5a51c8d9e23eb27e3133b00000000000000000000000000000000008493903c5c68b2847869b8c3b0fa9b8ba15bf1f11a40a29e6e82942e2910901044254cc8e8c3c3bf56e1f1b6dab7e86000000000000000000000000000000000bd3c1e302a191094059a6493e59a11ab05a49faf333f36f7680ec9b1043e59dfd7f0fabe9f334b97cd638dbb8bb664b00000000000000000000000000000000141c9b07ff33b6ab55b320dda6be54320082f0057c446236cf3d3a51e674c26a5241f2c702d9989adbae9045942eeab6", + "Name": "matter_fp2_to_g2_59", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001b2843d9852feae3145b242cd0999877c07785bc72cc2626f388dca32edfb112bb90f9aefd6953eb15a0babe748573d000000000000000000000000000000000a69bfe809a67ee853cb96b5a7a72798748cda56936e5664d509001544539730f57a7541ecd2396c9225818b9dbfa3c6", + "Expected": "000000000000000000000000000000000d0922466c358cfd756727e134b5e64d211244587e4eea036f0959e78570dce3ee264c703cc356cde20637c7560369340000000000000000000000000000000011a66d618f79fb662ac2b2d3b50750a5567e36d7092dfcc72d8f340c04df75ecc0ce4a01b410ea775dc548b8dc66c3d8000000000000000000000000000000000cc49cf4be5e2df6b43054092afa2d6acd66f5a43ef0667f6a2d660beb7fec70558ce02d7acbcd090df91fe833326718000000000000000000000000000000001270b0519db083f903a3dbe0b1b1bd5ce0b0059ea2c2c50335dd80b4bf154fc23a3de1ea753b0e279145254d8e5bd045", + "Name": "matter_fp2_to_g2_60", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002479a989dbf27141bd9f467447218dfa6ef60781a7231f089d5f1f1d8dca2ce9606a75c09f63f37f9cc1ee61dceb32500000000000000000000000000000000037c2f1b96170f6847138232bac663e4940bca602717c877f58ff7f5259778246085d499ec6bbeaade18f738df333cc7", + "Expected": "0000000000000000000000000000000007826398b4ec35ab58ba9fda5c15ada2a41d3854677172ef6a4a54087b64d0f73fc875ad62236eb7fdcbd94f14c8895b0000000000000000000000000000000016b14fa92de5f6e43988829ea2f851746efd6680b0ea1283264f803c8ffbe85a343bdd42225caefd1b94b8b311d2f4950000000000000000000000000000000018797093ff82bc10e6db60b1da50b9a60da01d67673e9bee8c7af2bfa2d57f409f7b06f53944938e5c73b049c2d3c6500000000000000000000000000000000000c66dcc3d30f35c21b8a9369c8f6de28af404e8b30d3c9a7f09c461b0272ba6d5a29e716012536dbeac1d9672af8427", + "Name": "matter_fp2_to_g2_61", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e6fcc48312831b910e52aebbf19869b3b32f05db8310b68905bb244ab784df5732db2e72183de5d231d803d92aacff9000000000000000000000000000000000f61f9e52fe3afc2a6bf12e420cebf83bc55a449d6a167779e5b6ba3281c51d790a045643aa75f2516eaf6ae2a816ac4", + "Expected": "00000000000000000000000000000000191aacce60a1a83f2c453fe196bbe5839a3a1178b147580435f7de8a2b0b4f65b3e280ac7a67570aba0fdbce6c11ad9700000000000000000000000000000000075ddd6b256f53a6ae6758a5158508540aa99b78ca069378f0ae3f5621ec24b9acff1f9b61d378334a63682a33fb0561000000000000000000000000000000000b06e11c9f858446fcc90c69d05cc26c33bafed0feda19adbd838c9c24bbf567b673110a1b248d0ee97fc682e561298e0000000000000000000000000000000018c75dc203493e12e1523af50f85ed648130ce5d3e9757f713850c867cc95c7acbb66c9733dc4f53d6a0e64bfaad5832", + "Name": "matter_fp2_to_g2_62", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018efc6d366d79a09b7d56c81c17c2eec2ef7395fdb5991f41273329cdcf4537d342bddd83c3994a40d5c18f6afa054c600000000000000000000000000000000127021ce28627a9d6a492720f728acef3b38c12b951f70a932c7fc0ce3f5b6c80783351cec55d7d1bc4ab964bb4913b2", + "Expected": "0000000000000000000000000000000012931f51430bea6e96f8ec456ce6b3c9e058b0bd3bbfbfe8b6e84fd6110c3bbbe0001018064e8981797f9c93713a0e4400000000000000000000000000000000196b6093dd2276098853ef2bfac84f0cad06b67a12484e98915dcc756310b818d8136954de1b602eb825ab29a143cf4b0000000000000000000000000000000008284beaa877b25374571dccb218c401cd905b351dd96700853f01920e409d11c4e440e90dc175cdf0fa807cb9d1e93a00000000000000000000000000000000063c6c238485c291fbb60bd2824154a9e23dea374292966d271ae94875391b7ceeee813e3fb9504223bb86f0ea3b6cb4", + "Name": "matter_fp2_to_g2_63", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a0277228ab4e880c12f957a6fcdfe49e2155083f3f93d3f00c68622415cd1f5bae183b7df9e08328a8139392772cdc6000000000000000000000000000000000de0ab426e56029790a5ff72f34da97e11c028dc5d31e448c49ede004102804d2bcc36d509640247a4c8bfdf5104a781", + "Expected": "0000000000000000000000000000000000f7bd0705cc4ea96ca38314cb85963044164b83a506ffeaea6e5eb8f7c4967cab1f1658f33b5435191427aaf9605bbb0000000000000000000000000000000007a93e2a5c118aff6ceaf2370ddad52a82854946ae595d384ee0b2b4935a574ba758736d84b0ae792f998ec6a707dfbe00000000000000000000000000000000090936add00fe5c7556610b28ecb4466ffc37b95b5cab43e072a585920b3cbe70faad01ef75d1dcb4f7d00d900bd99600000000000000000000000000000000006ae82539c68b7af3143e23229fe320924472c2b3e15a2e27e94cba674d30f083dce94706da094435c53285a43f89e56", + "Name": "matter_fp2_to_g2_64", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000170b243c5aa49a0134bf3d6494cc1e55a1c6ebefc6117eca3b343a48ef0a2b077c443ec5b9201b198df015a38e66b7910000000000000000000000000000000019a8ac8a3be1d45318801bb0a654963b312540d24aafec46bb7661cebeec27b0b345275fd53c041d02b1ebfa27fc3854", + "Expected": "00000000000000000000000000000000024c1b869fc13191b71d7159a07e869f1b13c11c73231b82e9bd0a7b4c32d7b376fb73d54f7231dd4974713179f235140000000000000000000000000000000012b9f95af661e8452aa5026302a7c28695307f75e9e4e32365caf378ed394fcecc831a3c47b443172188f4d18338fa75000000000000000000000000000000000f52675fb4d112d1d39ff953a253b22dfa0b73d972e756ea7fb673bf87aa992883c5baf32be6f50f880b03dcb740f06c0000000000000000000000000000000008b57726e17c873e12834dc291cff6bd95307f50e7b1d0caebd8c1eeb6eff4acc0520b135bc8e35a257133b7dc640db2", + "Name": "matter_fp2_to_g2_65", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000fbbd5a10eeb2f358f2b167f1985d4084c4b12accb1520d780ef1c52f6fa80e97aaf190e7a7b241ef96fe8289fc0a9600000000000000000000000000000000155687114e7aa786ba27aeada830fc705aed069c4e3a07e88d7f33923319f416ff3caf6533cbb36e5bbb1b93a191bfd0", + "Expected": "00000000000000000000000000000000061938df3365bf910884ccbd74d3cea7c30416bddc1a9b65e7723c15d89aa657da36a45fe10ed50bfa0c2769bb98aa2b0000000000000000000000000000000007b3981054255715826cf8f247210521ac681305aad3928b69804117fc143c5101383eab7017127c8452a79003a857d60000000000000000000000000000000004c745113480fd87212ed3ff30ba43c8716b32e62c1f0091bde53bd4a8fa8fe6bbcf0904144f4791ed1bf12dffa1f17a000000000000000000000000000000001237ba297c7f69e5e240846a12d86c8276a9a6ceb4af977edadc7ebfba3ad3f4ecc0b875da0ea578c83fc3b91f9f31a5", + "Name": "matter_fp2_to_g2_66", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000115edef357ccc3432226d9bad796a42b1a278d9c8adfdddc5a0f8a36d32ea1787183877f8b7dfab71424cdd10b63441a0000000000000000000000000000000014b369ce61abe60d942346e049644b95a0fda96316446b2fe7ee427641af52fdd2a654bf125ff6c8c7f3dec98f6cbfb9", + "Expected": "000000000000000000000000000000000a0cc3e328b4cfd01afe53dbf971ad78fc74d951050d76210e4c84438109622f0531747e762e185e3d7ecb9faa7c3255000000000000000000000000000000000622ad6092caa727d069b8921f4124d5996f3019705a908ef95d23092c5bb148873a22c227aa25ebee361d4184cc38a10000000000000000000000000000000002938d2ff50cffaab8c056c2844c50013f5bcdbb4f91b3f823836edabb39ba17ed1b8b5862301efad04bd2f5d5bf599b00000000000000000000000000000000072e96136afebbf8c06a37cf9b20c85ef8cb3f7f99d5c71b05a187c193711e5b76f52863c7ef080a1b64b2120ab2ed84", + "Name": "matter_fp2_to_g2_67", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d22b7b36ac66b10adb4570f8e7521ed76de2df2a7b94b2d0b9ee4514cdff6fa7c74854d16e7e70f054a91df93c7ebaf0000000000000000000000000000000016867c9cba66dd9f1d0332d31c4e46f8e393eeeeb19af7e6e01effb29ad999b3086b599ee4b371de557d4fafd5da3794", + "Expected": "00000000000000000000000000000000142ceeefa9fceb903b25d4dc68f6755833d7529752db0f125f7f65f2b7aeea8c90e599ac409576e82f7b9d6f83c43aa0000000000000000000000000000000001664acd89b482aed04ef40bd4d1ff9f39c80d7738771e2b3ca731af01aa230d865869cb05d83992e94ad99549fd0b8550000000000000000000000000000000013d6ace9b492c014d9a7504b5abe442e3bba13b1ada454aa53177990ec44f616e091f1382d36db87b7e794c11570a9bf00000000000000000000000000000000081b7a8a2906435f8a9242f573225ea62c5429e903bebda9fe9973a18ed2682185d72aaa6584b9848d1cc45ac907dd27", + "Name": "matter_fp2_to_g2_68", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000db9258e1257e659e60bf8569ea90c8247a53a1d1eb958481623447a38d0f1f1686c3e40c8f15bd06cf5be9c02485452000000000000000000000000000000000517c87f3df032ff08d960f524939e66f7fa69b4168b0f2507baf7d7231a70dc5690a02d317b26f219365ac3255bee78", + "Expected": "000000000000000000000000000000001182e4230f0c360c07913349f89f8436c01841c9615348a0d7057336c7483342024b0369ae52f39d4582f9885f552b5d000000000000000000000000000000000d15433ed130163a85f8ba87468c906aba88ef8610fcc1a8d6b3308cda29907acca351fd7fb19799184f1ad91c751b5e00000000000000000000000000000000111089005c4c5370863b0ea6b629197a865f978f71becb741f50f9b4e49b13162ca63c29aa26287faa9c923f57f4ad4c000000000000000000000000000000000dce405ed2a79ad433123105ad01a26ee85d1ba4e5f3b4e0339fea787058c06e9a6b10f5ec8f6eeb85b211e18b6ea076", + "Name": "matter_fp2_to_g2_69", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000b6573c743989fc8613d4ea09c2e500ce965b50cf0c8975ff703116464082efff4b42828c8337809f6938d7cdd3f66e000000000000000000000000000000000896d316629c80ce6e5f240535863b9e064728665c0815f39b21675c236f6995e7dfff1e4aec9ad05861e2122469ea56", + "Expected": "000000000000000000000000000000001694cb615d2994a903a13645ad44a63395320f286503902b6009e7c795dc8f024260e0c45bedd864edc9fcb9d1ca6bc1000000000000000000000000000000000f20538af015bd6d213f90fb1a1ebde4d9e2ab2defaf80d791a1f70af2ca7ea1598d43e9eef1cc982f468cf15d223c9d00000000000000000000000000000000046c62bec4c6876a67f5fe68107d677db8fa4d59ac0cb7afe6e706864c6e94744bedac6b34a68e8ebf89c231307b86d3000000000000000000000000000000001839f3b8a6dd8fe8028247670fe5b491bb43ea8fda53116dca87f97da96573a5e701a703fb5fa7bca457ef88a827e061", + "Name": "matter_fp2_to_g2_70", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011fd2ccf6883b78fe19cfe7beded503cdbe1cd5dc9ee452aa6b329d2237c2529df6774334b132cfeaa616f20335f88680000000000000000000000000000000009eacceef036ec500e7676f54af703016fac93d69ed19c8943b64ffed2db498b19cd255a0a3437b568eade0f497c7b17", + "Expected": "0000000000000000000000000000000009d8725eb8757828a94969ebf40545a62835686897d4504a66484a3078b2f15e39fe918d8dc01bc7560dcb005a7a0dbb000000000000000000000000000000000954a6cc9b2dedca1cf280f72fd0625184b8f83b78ee1ffcaf0f9178ce97900d759e4a74b914c3ddc32f84c3f4c3a8d60000000000000000000000000000000014121b83d2a06390ce7359e570e1593d5ff097cb0e44c38bc74171fbd8a8da0dfffcc2bcb95fb2d80a55933f696a86cb0000000000000000000000000000000016f71d24256de70618a02b0f016c6f31a21d2cc42855886ba30176584a028c2e12367be19b834bf41356cdab21223314", + "Name": "matter_fp2_to_g2_71", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004a851380536054f6b69ef7581b57dfd753d1e6201069bd1218ae5766aada087b4b08f65d43b3ce0215640e8d34633310000000000000000000000000000000013579671b64f2d9a2c3ac2737cf95c2148acce3dcecb3db6d019730010c50d1c0504ba4ed42d93771ba296b0b07487d7", + "Expected": "000000000000000000000000000000000cd47f0982904ccaf4f3cdaa37091a08e67a5f04af09033b864631300bb6c2aacbad105eca6ddf68a643976fb555d3d80000000000000000000000000000000012332ddb0e91f0ef9e085f21634c6d69576e60d3d24732a0c91a560906791f60f79d09ac0ebf448bd39f047b1dd428450000000000000000000000000000000000a756a869b3cbc5624f0e08019170beda35fd2642a79108b284a503942f8267b75868636302e5a12b4f1505331b15f9000000000000000000000000000000000f60724f6c8200edff41f3299ca003e9ea03b97b01a3e8c63763bdf67b9f7677331a7144915312458c40d041be97b3c8", + "Name": "matter_fp2_to_g2_72", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000021dc1dedded9b0dd90afa9ab7fa8f9c33930fe4ae68185ea4cce9ed97ce4cc9ff93f96377b11f8d42b02e759a10b06200000000000000000000000000000000034c963fda3bb80043d6d7887661ad59b3c31c88c958b451a8e11684770c132205db6655ad7cbd604ecc3225b0c128b0", + "Expected": "00000000000000000000000000000000095cd509e53f10b1ee18b2120e2d18f0905a202a992a9c62480beb6588275fc8b5b151e6abf17a12b6d9cd03a8b37a59000000000000000000000000000000001723bf1a3d79935eb4b39f7feaa1e05cd8f3e7a32e2c406625053d8d8fde33eefec231ee00adb00b0acac16a83dc77fb0000000000000000000000000000000004af528e886dad3f9fa7232605936bc22a6a22622828367791920ec9d31cdb2f290e37f5fc79efaeaf96c86b3f6e39220000000000000000000000000000000015bada14a84fdb09b77397cd2e27836f9f88854924af0cafc6f9125d32be848c8325a3eee1a26de8be8eb80b601f1ad5", + "Name": "matter_fp2_to_g2_73", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003e8d1be04f8dbe5c7e1c7553cde8355ae16d26c819dea92fb543cbd9fe9e726359e1e4be0483a7720343f34c6a3fb9200000000000000000000000000000000062bc5fdae812802bdea09e4130c3d9bf80c7518138b116a4b6a302c155b97226a6ccc8a3ace18744e7adece08781f72", + "Expected": "000000000000000000000000000000000d8f14042f36bb377655b63dbc37c50e0eb5775d4e4399972a6758cdfa9751cb4b733745ed1a47fe5f2cc434efc5af81000000000000000000000000000000001384016829d028f823e6d062898c042a461bca13ae4627c983d9b5c9e8b4ffff7eb25daa1c52b39e309b9c1e7e4f2e920000000000000000000000000000000004f7904d491a0c2018b1361a9cfec4fc829e607402859fd9b9ded60adcee51e9b522d302f9064130a4eed1327f49bb4f000000000000000000000000000000000ef4fe949fca569b31fc57ae7d0166ea53318c5712311076e052c2967144116f5490fdf56f26adf64aa01beb4f6cd214", + "Name": "matter_fp2_to_g2_74", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000014b922157b19ed9debd9ae95cd9435f7980b8d4ea33fd29f99d5e7fb1a96f6d99ae13f7f86239c4bc286c3927d3522a000000000000000000000000000000000f6d4badf78d9115d93783a59ec9576fcfd87a2c29e1c506b6f7e313e71723811a36d64b37650fb6f7b460105a7e13f1", + "Expected": "000000000000000000000000000000000f20b3a6505784681331208b573d3a241706692db71b5daf4e9c80adb1fa9bb87023d7ba7f9c65158653c735dee9dfdd000000000000000000000000000000000f7f357407ca6cc5c5fae4b84509d71b2f4de9af226cb4038b4820c0541d4999b7396608efd2f322a00a768129f9800400000000000000000000000000000000138dcc1b9d978adb5eee6356980cec5d18cfbfbf18cf6fd14f4119a563f473f5027af06342e84ea858223ed63d1a16af00000000000000000000000000000000012b63f0d2e8ea361d55aa617a99e066b5feef3af1930b83d2a48b527e0ef304ceadf7cba1415db80c54fdcbbcf66d14", + "Name": "matter_fp2_to_g2_75", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000005a54ee5e3dc05c38ade7a42c71baf5a1467938f97c0cdf0742441cd339f22542b1ca6cd215d385b3fd6ba74ec996a4d00000000000000000000000000000000051c6f0ce621e8e27e5017628690fb68f0fea27d67726a0a77b0caf9f524936e123ff096168ff2079b9990c07fa80354", + "Expected": "0000000000000000000000000000000015ff2aa94f802d8f9c60ddcb43aee598239cf3ab7f90f8289a487b673f6065f8d9bc92bd4cd28df4a7b0d3bb78fad243000000000000000000000000000000000884b5d4ca3c8abea737cfca05878528890b6cee9bbac0bf027df5d4e0add431829caddf4c1e001818581ce08686eeed0000000000000000000000000000000019b91a7738fde9760240b335457955e963030848e85717858f22dc33ba5a4721156cfdd7341aa86d10d268e2fc9a1d26000000000000000000000000000000000af85e60161795906f3cf705f5e8cb8c15083a90836eac78445c6bc27ffbfc8c2df3009b436989b46b271dd8d1dbc282", + "Name": "matter_fp2_to_g2_76", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000094e958d9b7dac39fa4f0143a333b2ccee09046cd23e6a1c0712470a3c2e61d2f8b85aeca37350f71d7ec75aea2b3b6b00000000000000000000000000000000080743cdb5e359e8b8ad3485d53ea286669ad66d841945296edf80dde77b20a158e2c1529dfc33a1fbecf177d75a0c69", + "Expected": "0000000000000000000000000000000001bd1fe6a6c373cfdc2bfd488b0c942492b77d55b2560824edef3a91c711ee336bc1366690be40949d04edd39ad48a7500000000000000000000000000000000161476946a5687113c74a34284f49b0658e323fae57aba88b039eae584d6ef28adca669fb083a2fe8f0ef664eb5b957d0000000000000000000000000000000007aead870ae09a04cf9c9fa49d0888f7010782cdc5a0ade4c1340ff15d99cb39b7412d66d4147b95601fcf5a39c39bca00000000000000000000000000000000095cce83dbfec12973e27627bfb2d93fa9a027a2c2af4259a0879d6bda055d74559fc93fb3b4f6b0088f702af29a7643", + "Name": "matter_fp2_to_g2_77", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000dec04526dbf7666d2c29db5de1ef0b3da85380a171d871a57ae3df364d2754fceabf9d4d2a3da1ecd94e377abc78430000000000000000000000000000000000d19875fe988ffbd0cf1e9bfefc7019579962ffa3a585ee232615e4a5fce0a07bce0537b203ea00010a90ec05d5b8de7", + "Expected": "00000000000000000000000000000000133cdf684c3ff1cdaf07ff787b57a66c215eef06acc2aec4d726a086480e7b2a5dead2cb357d99e298df32d4c6f5029b0000000000000000000000000000000019cd65b830fb17880f40e104ed63a7d49b0fbad8eead7502f18f1b9f85f3f6ba6c275b8a242effc61a7a5d770a4fdaa700000000000000000000000000000000039aeacd163862e476b17a22c76042d7896a04f158489ae71afdd35d27106a3ec276baf5c08e3eed4b3f0a79c3c458d200000000000000000000000000000000125a9bd770c1fea2155a581211bd71d55eb1966645cc892a05d32cf1e4e5b23278ea2fb1336bba7f2c887debe4a93b52", + "Name": "matter_fp2_to_g2_78", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000016dd03f0df71b183e42cc29c665f18d57637b65f05df85aed9a0f7b8aa37f7913f6606c10f24a7a8c570f485905583a00000000000000000000000000000000161e62d8be678a114fd4a99a6caeb9481f5eaef145571152fe8a6ed77a34c06a1b6ff66044102d19a94abcaaeb254e73", + "Expected": "0000000000000000000000000000000007843268081f61ad2b3f6653336a99086381bb4da4c23b7d59b9c7827f2d4c196d136508c8a1f3d2f939e8c9799b95e10000000000000000000000000000000000e2c57ad95f762115d8230320810a4ea9978e26ca17decd6af4c112789608967a92fafe3fb3e79539d75d1c0bae97740000000000000000000000000000000010951c9839db9dd6ca5ef95bd1b1b9cf60bfd97cf88129fca23b24f19c9d5c71486dffb762e92f23d2a9e9d462556f620000000000000000000000000000000013d35c17b3763fc5db46ac8c44aef996f3f876c49f5278b7c97e844f23ac49f2d50b3af080322d30ead873af7b4257e1", + "Name": "matter_fp2_to_g2_79", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000036efffcb0c6f42109bf9b8b7421e32fa3f855373345341e6000eccaca135ef3b2e1c0151bddbd46ae92185acb847d74000000000000000000000000000000000edbd7a40f3e688eaff5e29800159b8d799df07e81f65d59011e84329b4689a28a15ce11537fb560df705be26bf14b1e", + "Expected": "0000000000000000000000000000000001aa1919a50b5bad62b839d672d5a11ad345fcc61f75eccc42990e113deb8a486423d1b27e7c81536d8a5799986b9408000000000000000000000000000000001879295d2f7bb3923ec61c063ee4f96d7d7cf7786259e2f4cbc3ccffe7e114af264b3527a5e06dcfad50ec1e2a9c1ae0000000000000000000000000000000001042632662e406c95f3fd44a6d956e526907147e7e6d4219c1c4b28a31e479974d00d4ad6e683f6a834d3d4a20830f4b000000000000000000000000000000000a29ea98ec25e7827bcb349ccdb2a57926809f3cce44d5ff6cd636460278c8103b0db78fa580e9edd4ecd0bdb21018ff", + "Name": "matter_fp2_to_g2_80", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000974c7d17cbf91947ad435b30ad2b639671a43d67da6a4edc7f8bdc11fe817d4d42f687dd642a2be89c81bc36e8df592000000000000000000000000000000000efeeb85860877abdabae35672a77ca9d2cf0ed18ed209fb905b591a841c900ed06d2c32c56bed5f7efd469d369b05b8", + "Expected": "000000000000000000000000000000000c67498c6751cc27d871b8711c4739398c501a5bfb688d7e1a73dc7db5c47c3e28b633078cb83745bf5b0d5d2dde3ce2000000000000000000000000000000000c205c03305422bd44082715b90e0a0ec178003d6f5e14a0d13bb0f2c38f2270816b884b4870b75db44ab080f88a35e2000000000000000000000000000000000257f378935772d326710ec6efeb22f8c9b6b549c8a4c0205b75740047d750d73da4e71aaa8ff33b9bd8ab7621b08e62000000000000000000000000000000000c386a15f09c849be9f449a59e1332a1e7f16a9394c8de198c01399a05b0f963921c4c57d49916407ae0d202af8da32a", + "Name": "matter_fp2_to_g2_81", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015333364f4d0d173ef35e447fc189b9d65ef71b9fc4ecba25fb6c8c1bfe8467f26bb9c55ef10bb34125d714b94aa1df1000000000000000000000000000000000cbba9d8ac191032f03c0746f13108962130c9e2c01d47f01174a4c4d3daa7631268f7dcc08dfda317bd249fb6e73e8a", + "Expected": "000000000000000000000000000000000864da537fd94a9ff1bdae733f01e145dc97a894733d0811cd67c2648ba61d0b187241f9ec69d8c011f514894a05a608000000000000000000000000000000000a53ea4ff9c0ff71541ee21127a33daff2b39e74301946a86e51dc7834717e7d8784cf92fa5845bc0613b6b869003f58000000000000000000000000000000000582f5a1fcef3067dfcdfabc6af33871114538abcb02fcad761cb496020c7b423fc52f0075916f160fbe03574df97ea4000000000000000000000000000000001244ede8ba0dc09aacdc5d9f886e59bf963a25885dbbe2c3d1f611bfae82debc556ec4c94f0606492c7b8c7bf976ec34", + "Name": "matter_fp2_to_g2_82", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000781e980c167c982c2fc8d0baa3907bc5499eafca675ae20a10b25063c9088fd06f6769df505e5900bcaf99e266c052c00000000000000000000000000000000183c12798438ea92db75d5bf76cf29d320fab3653e4131989205f2817aebcb1b13f161864c084fd13a11459d7d5ccd92", + "Expected": "0000000000000000000000000000000016c334aec0e19934665596f0ae37eb398f1d6f0d0c9f08189f1ccc219230395124a9da03858bdba13ec5366da54228af000000000000000000000000000000000b156ea34ae7b5c252dd90997f1c693773a463c26935a69bcc0599b95bde9e6aa31649c48b6ee4ec1f0a56b19273a5170000000000000000000000000000000014b2d69e02418844effcbc0d564b2721deae2872cd1f27f61d544fc0ebd5cadc77c6777ec944ef0500db181a5443618e0000000000000000000000000000000004f0d48a25c1eb81233f385af17ab6abf554e1285b669eeb5e884c64d5815fd5fa1350bb361997cf2e317f7c5e9cd19a", + "Name": "matter_fp2_to_g2_83", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000879133a3c0e50c90abf1a6ac75bbeca1121c865ef39e9224ddb160eb725e0850a34aaf9014e42174966773e40e4c99a0000000000000000000000000000000004c66f8f5bd462cb27e9f5e1d93e322bd97582b9e81d07b2877103420262e4cfe6d0e3bc07f9f160701fd754793eae33", + "Expected": "0000000000000000000000000000000003c0d6b721cee4e5fdc6a02095674a58075f81b1d28163f81d5b258c82634297009e6bfc8193969e23e196cf7a99ad6c0000000000000000000000000000000013229818411c8e55e50a63df6983150c1d5ead828711131d9c81841850ed76e4712954d3225eb6d7fffd3cb9924f7497000000000000000000000000000000000f42d6e4d5a28dbfda87c806cb0b1bbabb745e63e655c3c6be50411da4dcdc745ae50f71d56e88db8454d40375e325810000000000000000000000000000000000f663ab791b48f76d358e66e8cd8fa40848dff2bbec758ce1d7b3fe02d1f6b3f123cef644d4fd86d6a77b8155feae58", + "Name": "matter_fp2_to_g2_84", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a7e855324ef471b8fefb31967cec84d57953407ba555b672fa59364b303030cb02b6c77933cc63fcd1b8c107b263554000000000000000000000000000000000b50c3f7cebdcf538820113acdb017fcd5d41a4fd679af8dfde7b0c330e3576ca79d09eedc724a93a3f5c90d141e7524", + "Expected": "00000000000000000000000000000000197865f685e78a8842fa79ddc728d507e9f31b31666d1952a46f6422c97c83fba3087be70e3bb588260556014523f74000000000000000000000000000000000131f5d85ad3beaabd129d5a5675d90ea911ebd02cddb5ddc7a8be28c33061430d684d123d5c516785d21ebf756c99195000000000000000000000000000000000c7a14948f3aa29f845e5ca9877db9f0477af376eaeb45324c21e6f99e738aeec96b89af4df942bffbabbf50172d8e5b000000000000000000000000000000000ed4aea3cb585b0d36972f9ad6943172ca7375b44d1d6e80e0bf97a0b25d74deca4d35ce865c8747f1c7a2771a37c667", + "Name": "matter_fp2_to_g2_85", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001706830efca18d3e75ea0f1ca8af23a816017ceeb045694cdbad6d3d9aa5a9ddb123f5097a226a217166de3a82038393000000000000000000000000000000000402132ac383a2fcb17fe73398273ef0c2f2d0d6edabc78f31080d3ecbf7c249ffeef28bb8b37a6ef2a7d726c070dc41", + "Expected": "000000000000000000000000000000000a795c2affaaecab6cd2cfd6c8fab6e35cdd646e9cfa7b5e02400ef4abf839a69924ea80152eca7810a5041d1bf58ee800000000000000000000000000000000121426bb945d6f6b385c98a5247b7dadaebd3375dd8b2bff7aa77fddfbe603de89e77baf0e8f36a924c707c53d29a1450000000000000000000000000000000007a6fcb486634186f001c8b99874f0a07a37f1ff4b30599d2f570f1bb4ff290b816547f6ce8b3c1ed33e57630a1d57ab000000000000000000000000000000000fa65924a8f17414eb7dcc54f2a4134568484e91533dd21fd33cbcc37a920f2804516a64f1986e9d887ca189179d07c8", + "Name": "matter_fp2_to_g2_86", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024beda2b950efcee233435f0c748e33aa928f54ff29d3db217d7e32b1aac5f4ed11705da4fb8fd38481382486e4aef7000000000000000000000000000000000c85283ad6e35a72d07b74775df1a4660113d50b51426451f454a575adf9cbf9d7be3f521649f6c367c4f4c74b67ff6b", + "Expected": "00000000000000000000000000000000049d9ac43e31faa3d02f8255d207b82e4b27e8a9a61ba45fc4f9ad8048e5f89b58d25d98253aabe29334e0dc09d1cd6b000000000000000000000000000000001544f90a0baea38b48d89bcb337cf5a80faaa79334733b7e6126f55358a7e498aeb61419065b9434cab9d10fe8e7fd9f00000000000000000000000000000000139bdd668462a1b5d3ef1299d47aa91ed141ccbeba5b08a8ee31b023aa78c16514a97ba08abf5c8bb1abbd85b3fe87350000000000000000000000000000000005c7dbb8a22403a96aee634cfc67ee6f1069cd61a1e1831e8faa9d7e1aa5e4f7623f51f2e5b739f7fcf3b4ba77c82ff1", + "Name": "matter_fp2_to_g2_87", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000cb18f477abe58af92116101c3f52ad4f6074ed92a08c3adcc6660b555df9cff09dd8b34e032ed81e868a62bda50378d0000000000000000000000000000000013c4ab1558dc250c3b5d0f0fae3db62b8df969bb41e9ecc24c10e1e51cb399f1368bed7375a9b9ad9c7653c868eecfe3", + "Expected": "000000000000000000000000000000000b8b8bf2b25c2386e5f3be4bdb387d8005cf055e68ab9a5606f17dbedc4fbd7a11314fd646d08bbd6e394485d4f56f5f00000000000000000000000000000000173a45d766682f82ec2d69aed1d80ede2477c276ddaa8fb97f5f4d0515b2c2e370c615cd81c1e361f95db855c9b1b6e200000000000000000000000000000000115868a9187a0465a9309054e865ef224ec3c88a5eafbcc25f9a912ee3b19084757a90b72a4038ba71b10f59fe2f93100000000000000000000000000000000006c5476eb8aa1a471d289af52c7d1df55f6bb1ad53d7eaba6bdc2a97fcb24ec480f9d8e12079d366f2213194c861f016", + "Name": "matter_fp2_to_g2_88", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000188f650fdc51b970d16c0637ad5e97aade93c7f1398751439484ec6cc56613814908e51cfa7f68da9d980bb9dac47a400000000000000000000000000000000081834f86f1310135a2cb03265f37d9b7c9459bb149bc54b5a61319a7cde36c6a2a0fb49f8f1fb9d80f07b84f799119f", + "Expected": "0000000000000000000000000000000016e8fea4d09831146fc35bcad28e441f2c02e4d17838e04dc7cf909b2133297a13f07ee927722f3d78e36721d6848e3400000000000000000000000000000000114dee8b3a47269e9ada05ee015a874d1cbdfff4acdf5310642f829efd08f78dd6110e1c7a514e7d76aff52046f4ed140000000000000000000000000000000017b9d23f7a865a3ca61197d841fd9195805a9e883d79dc7d36e82f504e6689ade0e84c70a5c5c516fac3e3c643942e160000000000000000000000000000000001ab82b2a0986dec3211507b8adca351829b0a13f25e281f98f54d9e0e32280ea4c638dcb74280eb747a0d9af43b6b74", + "Name": "matter_fp2_to_g2_89", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006f66eb49f95f51ec90df86254580f0ae57862bdd8b5f2759ace63c5f14f8c5a762207f744bb82a8962f8c4fa410dfdb0000000000000000000000000000000004e02a80628c30ce336eab890fa37a227f77357a60be72cb87cc2b7442d2163d395fdc59350624ca1322bfe8619a2efd", + "Expected": "0000000000000000000000000000000006bc2ae646a603a1f4524b445cdeb99914e4ed19cd0676d511764b828bfe126e81cad2cb566655f04de1a302c14d70bc00000000000000000000000000000000023bd509aabfa41385e90cd4b1cbbfa45d066c4defab56993aaa386dc5b7707b1a3a7d444b8bd295a30d0b8f4bdc572e0000000000000000000000000000000006f82e60e18cc958375cce6f465db461ff46ed9d15cfcc01a3aff455d54c77ebba5a654c2ec788b6ed8ac53c39defdd3000000000000000000000000000000000896fbe6492c4c297f8b6d60295a7f2565734d69eea67b2675211a203fec043f0d181b1348bea425a068b7bc12676ed0", + "Name": "matter_fp2_to_g2_90", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001451bcd19495cea3a19393b77760f688fbf17b107dc131c88cbb503eee2a804e2978d6e8a4720d144083d28be73371d70000000000000000000000000000000017db715e8680a0e82e18b513f2c9c7ea136cefe8add71aac6baba146e3e33a498d025c7e0808ced306c915eb02900c61", + "Expected": "0000000000000000000000000000000008604a06a198c3e11458de920176842221667d024f9c155892485a37ff56252be1dc629a6fd580fa41f5e598a23f3651000000000000000000000000000000000e008eed25eafeaa67f27e89e1f81b469724a4b00f08dc4ae672aa1587b19dc615330e3fce0fbd98d7526bc2c4afe69e0000000000000000000000000000000015bc1e4ea5ae2a7fde6d5e5c3e58f6ff5df5bcb125ab402f10edd09087bde39fa27dfcdce7d04fd18ce399729e155fae0000000000000000000000000000000006684e9be8bf9fa4badda842a1d8840f0820d9a797e482c64f4004a18cd63986f19abfc93f6bf068d38eb1e491cabbe6", + "Name": "matter_fp2_to_g2_91", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013a6e129d4dd4aa93cff5489ee879763e2a2231939e609d2d72f07e630b37d09f3057a36fd5cdfc9c81675c996f8ba0f000000000000000000000000000000000e8d7ad082e8f9a718fc2ea712853ed9ab4e8b1a8ca9988f77c70fc759f1fe2d4bd73696e539f130be13b2862efbdf77", + "Expected": "000000000000000000000000000000000f15c3d0b40735babb2e38a2471773faa16b2fa307c3a573ef4cfa5a5559574b2d26cf88b19dee204b77f6e11a1b927c000000000000000000000000000000000d224445f3d31d381bb29c4fdc8130174f5bcb957f451c92f4a652cc3d2b5df985017133a944849b5228a88f99bec771000000000000000000000000000000001338b48bc1fa229f251bcd4828654baec9d149f090b19596ad3b444eacc7bc583f97d9cfc40d5611fdcf89cc9a88e33b000000000000000000000000000000000c30dd2aa51f6577d57175edb3ccc1b324717bc195eb0073c1dff4e5b0d77cf5e41ec233527b3936994e86303f91b172", + "Name": "matter_fp2_to_g2_92", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003379bc10acda5ed1014e2bba1e30cf83b72fe69259eb35476a031b8a898e0183bc32ee853a85fb3d738424208fc880900000000000000000000000000000000175a2e5a44ed62744fbbab9581ea7283470bff12436dfc414ad80b4070f895de3786022cbaed55bdbbc4f68db7460548", + "Expected": "000000000000000000000000000000001735e1f2fe905839fd6534c95b95322f8cc86a4c482f1ad7691b9b9bb8f55015b4faaa1f243786aa33b5874817cd09c80000000000000000000000000000000013f1a27931ac513145f2601e009cf637ba4bdb18a7604f89534fa3ec8488f5b6eab9963c5d753fdd34cbe7d2f8eb8a5900000000000000000000000000000000092d8f800e7a4bf6f9a25ddd7f64fc403db53b1695ae59c15f229458f347a8e7c2ebc415af2d3849282b670c5cf6f8600000000000000000000000000000000019d22d694e559c55db63521e7b60a1a2342c3cce868d70951e5ed32ec0f5efaeab0e78b21359110f6e769776b745938a", + "Name": "matter_fp2_to_g2_93", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b384a9db472c38a5d246da56059b7630f002b5f4663abce7c5f6e896222a1ca1ac02883a1ec95a4ef09bcfab7d0652a000000000000000000000000000000000de09ef45aafa56936e446e18ef9ff97ca8b81c295d35cf7b72416ebd80586d0fc479d86c23295ac54a23489af045ebc", + "Expected": "000000000000000000000000000000000d7dc499e5213120b3ccc173c83d3c15dde9e13ef57238cad84889243b35c8e69eea2ac7ef7560051dcd7402b46b733e00000000000000000000000000000000063ad31c17eb17d39cb4b33e45a0b0e951becc11b685b10cb45cff268b6dca40b780f7e1532be91903372c413a11b5be00000000000000000000000000000000140da959456cbd34e041409350d6106ff65ce6dd2ac3149f04959b16eb83dd0456ca11e5990daf4a1e5c23d3f30a6c4b00000000000000000000000000000000195d07ab127d49baf89fcf5eea1f5e4cffea1a577a5c864c0e637fbdfa10182adc1d5d4ebb871949300193e45ae0fbdd", + "Name": "matter_fp2_to_g2_94", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014df33e7d3ef2c339b958fee667097ccf146556261f7db4b0b0a3c29897b73a0ca249866cff1461488012bc16df43b0d00000000000000000000000000000000099dda253a43b8cfac580306267d9dfeb2c129ac1818fee43c6df5e582f5fa726ba73e1a2ef6a9e011a387c393529678", + "Expected": "0000000000000000000000000000000013ec1ef25b303fe2f10a0bbe9bd77a4b2a055e176c2870c99e63b4baf2b313a835459263351dfbc25c22ea32946d8956000000000000000000000000000000000cb1c3292a2e0c9b1c1ff43cbf7595f39c00fd413b54782681fe75a6f5f231d13912f8d598dd8aaae8159de083dccd8e0000000000000000000000000000000005385f2d4bb6d94d67b2a3bacd3aae31da282707672252c0ab1a12fc85d8e9b9eb75454eb145937542099b860f9d6dce000000000000000000000000000000000e59506f7733a38a7e1da4ea5958de4755b52a9307ba2e5813131b33b86f0e401f97594d9674ff1667068a1ec3c9b145", + "Name": "matter_fp2_to_g2_95", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000011c89c8d7e83155a308b2e517a23f05a4a55353332292b44b0a891b6f730fd126bd0b97eb87f0fbdb6c82779791d022f000000000000000000000000000000000da6f02450955bf26e236ec63aaf80a018ac34fd8784bb24a22a1fc5e8bd686244a923009a10cb38b1422534d0997afd", + "Expected": "000000000000000000000000000000000f4392a41fb3e58dea97b97fd22e2fe6436c3f9bbcd944585a76a5f1a8f98ea4ee21639208d765b6c3a7d08f8cd3f3f00000000000000000000000000000000002c3d62794996dbb881b665eece98926f41a42c21539125fda6070d9f69e29e0557c886b42e4bcd97b14134d6e9d1d710000000000000000000000000000000004b93f315822aa1be8250c2e736727d390ae3a862c4c7dda452817f70f01c73e6f344df1b0f05f03bd574edecc70902e000000000000000000000000000000000731403981fd6243d00c23d0a42a759016f7907548847743f18421f51b1e72cea92f0c5580328babd4ae3e15bc9c56de", + "Name": "matter_fp2_to_g2_96", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015bb227b5c9ccfb8390edcd158b04a69a88a3b99a10ae90e548182751a16448df25493061afde2c9790a0e75e6f409a20000000000000000000000000000000001d7b609155bf3939192eee9642032e6fb10f57d53916674c60211a37b4a7662759899a9569e2dc730febd23f747a7a3", + "Expected": "000000000000000000000000000000000b35c6294b70336217eb9334ff1f1bde9d892d109e947de7f4f5681b3830ed00ad1b89ccd7cbad88ce1586449140697d00000000000000000000000000000000032691e5f4597c06496e9e37907041ddcadd18ca8ce64a8b400b1e2e8d63acce5533231edb66b69807fa2dc026c1d2be000000000000000000000000000000000773ccd132cb215cd98aa17d7fc432e0577b08d8faaa35199000d46fdeeb954e8652566384fa0cc5bcd1724942f7075b00000000000000000000000000000000112e951db3694944fc82fb980547cd8b7f2e5ec6fd2051b6aff2573797bd6a28437848ea0627054af1960ad1de0981e5", + "Name": "matter_fp2_to_g2_97", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000017599d71686e817cf58b78dd7586d5b359999b32b0dec2d67e33fb6388411418ecfaa2670a2cc9dce3dadaed0fb3364000000000000000000000000000000001773995b540be9ffbfd276a92c0494e4eae296d094f9f7eca975cf4f73ae05e92bd64ea71ac47bba534044f4072a6591", + "Expected": "0000000000000000000000000000000018f2eace212eacabd44ff01d886543410ef72b4d27f8d25cb080dbe4b1d4b2b4e57e4dd40723d15789d9b5104b088d9b00000000000000000000000000000000098e9e9b302876ce85ba486609fd028f357314149ce8b530778e6de586ab057fe59648d8c8ae80fe619c4c605b90784a0000000000000000000000000000000016d20a8ca43d37518c8a0f47566ba61a7aade9ea2cdd4a0907ff0ed862c6b7c64815d50397eebec262a05c6010cfaa790000000000000000000000000000000005a70c2fce25acdc4a95fc2bdedb007d71f24b0b5714fa14910ef590215d25442e91a66b6bfea5f7777f0c6d202eff32", + "Name": "matter_fp2_to_g2_98", + "Gas": 75000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f470603a402bc134db1b389fd187460f9eb2dd001a2e99f730af386508c62f0e911d831a2562da84bce11d39f2ff13f000000000000000000000000000000000d8c45f4ab20642d0cba9764126e0818b7d731a6ba29ed234d9d6309a5e8ddfbd85193f1fa8b7cfeed3d31b23b904ee9", + "Expected": "0000000000000000000000000000000012e74d5a0c005a86ca148e9eff8e34a00bfa8b6e6aadf633d65cd09bb29917e0ceb0d5c9d9650c162d7fe4aa274526850000000000000000000000000000000005f09101a2088712619f9c096403b66855a12f9016c55aef6047372fba933f02d9d59db1a86df7be57978021e245782100000000000000000000000000000000136975b37fe400d1d217a2b496c1552b39be4e9e71dd7ad482f5f0836d271d02959fdb698dda3d0530587fb86e0db1dd0000000000000000000000000000000000bad0aabd9309e92e2dd752f4dd73be07c0de2c5ddd57916b9ffa065d7440d03d44e7c042075cda694414a9fb639bb7", + "Name": "matter_fp2_to_g2_99", + "Gas": 75000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsPairing.json b/core/vm/testdata/precompiles/blsPairing.json new file mode 100644 index 0000000..f41d375 --- /dev/null +++ b/core/vm/testdata/precompiles/blsPairing.json @@ -0,0 +1,702 @@ +[ + { + "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e8490000000000000000000000000000000006e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb9090000000000000000000000000000000017d81038f7d60bee9110d9c0d6d1102fe2d998c957f28e31ec284cc04134df8e47e8f82ff3af2e60a6d9688a4563477c00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_pairing_e(2*G1,3*G2)=e(6*G1,G2)", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e8490000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e200000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_pairing_e(2*G1,3*G2)=e(5*G1,G2)", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a9000000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf100000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab160000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb541170000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a70000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb3000000000000000000000000000000000029ea93c2f1eb48b195815571ea0148198ff1b19462618cab08d037646b592ecab5a66b4bc660ffd02d1b996ca377da000000000000000000000000000000000bb319a4550c981ee89e3c7e6dcc434283454847792807940f72fd2dbf3625b092e0a0c03e581fd9bd9cf74f95ccef15000000000000000000000000000000000abb072b8d9011e81c9f5b23ba86fdb6399c878aa4eadee45fb2486afe594dffc53be643598a23e5428894a36f5ac3ce0000000000000000000000000000000005d04aa0b644faae17d4c76a14aa680c69fdfc6b59fee3ef45641f566165fced60cbbda4ca096e132bb6f58ab4516686000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436000000000000000000000000000000001065f2a2d29a997343765f239c99a018490eced40ac42fc93217dfe20d8b43ee2215f65166aff483b3dc042c5a43b196000000000000000000000000000000000766e4c66f4a442ff1f61a7a4d197d2b47dd226d0e7822a9b065108cfc643cd3f3d5ae59ed2ce4cde13fd9260bb5b7cc0000000000000000000000000000000012251cc6abbabeb7bbe1fdd63eaee10832a748fff24f7e3fdccaea87facb6e99f2e0407a38f27f90450a471b873104620000000000000000000000000000000011181e08c8fba91271adfee9d31681f8412ab7a3f754f7ba4709024c0ad2287e32dd455d71a296b4838072a8ab9d96f2000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f53580000000000000000000000000000000005a7445f55add1ed5c143424ceef3d594280e316c9441a8e68c3ad97377141d015bf878bdfcf0df9fbcd0529f4e8100800000000000000000000000000000000192b52ba08ed509fc84d5775a7182498fd1ff80941d673c53470c9c9f1192f9c0057d68a1dfee0c68fe5df3625cc43bf000000000000000000000000000000000d3fcaf2f727e0eb32c65da9b910dc681b948dda874d0db6f6ed3f063430fbf073385a9a14c2dd78568726124e2b3ea8000000000000000000000000000000001943ce22cdb2387bd5796950dc95d1ace4012ab9bb4afb46223760230c1709e075f1ae76d6b3f2e947ba6b16d458ccd1000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000a29e38da2d42fd4712052800c7c8dd6e94fd9f506e946068aaac799d60b94c2d7515769ffdd32ea95d3910330ec47de000000000000000000000000000000000c60dae92451206390e30b5daa7151d63624dee496753c87dd54eadc92dc9602081fae02a1a53bac97e984a571923a5d00000000000000000000000000000000085f4fda4c72328895f20c683cb49603a37ff2c43d62f66602506dad5b8d1daebfbac7a7db3f50ccf4dfff277deb105c0000000000000000000000000000000005674d005457e0fe1f0fd978d63996c5f3d29f9149ee4eb04c464742dd329ccaef5e5f6b896d986ddfc9f1b2a3aec13100000000000000000000000000000000071bc66d6e2d244afc4a5ce4da1dce3d0c22c303ba61310fdf57843bbd97763ef496833dfa99d14be084bb1a039bb2da0000000000000000000000000000000012c22e047b0af8e2f4bf3bd3633ef0f8264004ca8ea5677a468857a1762f815235a479e53f4ad4741ffda3fb855021c900000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "bls_pairing_10paircheckstrue", + "Gas": 495000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a9000000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf100000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab160000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb541170000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a70000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb3000000000000000000000000000000000029ea93c2f1eb48b195815571ea0148198ff1b19462618cab08d037646b592ecab5a66b4bc660ffd02d1b996ca377da000000000000000000000000000000000bb319a4550c981ee89e3c7e6dcc434283454847792807940f72fd2dbf3625b092e0a0c03e581fd9bd9cf74f95ccef15000000000000000000000000000000000abb072b8d9011e81c9f5b23ba86fdb6399c878aa4eadee45fb2486afe594dffc53be643598a23e5428894a36f5ac3ce0000000000000000000000000000000005d04aa0b644faae17d4c76a14aa680c69fdfc6b59fee3ef45641f566165fced60cbbda4ca096e132bb6f58ab4516686000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436000000000000000000000000000000001065f2a2d29a997343765f239c99a018490eced40ac42fc93217dfe20d8b43ee2215f65166aff483b3dc042c5a43b196000000000000000000000000000000000766e4c66f4a442ff1f61a7a4d197d2b47dd226d0e7822a9b065108cfc643cd3f3d5ae59ed2ce4cde13fd9260bb5b7cc0000000000000000000000000000000012251cc6abbabeb7bbe1fdd63eaee10832a748fff24f7e3fdccaea87facb6e99f2e0407a38f27f90450a471b873104620000000000000000000000000000000011181e08c8fba91271adfee9d31681f8412ab7a3f754f7ba4709024c0ad2287e32dd455d71a296b4838072a8ab9d96f2000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f53580000000000000000000000000000000005a7445f55add1ed5c143424ceef3d594280e316c9441a8e68c3ad97377141d015bf878bdfcf0df9fbcd0529f4e8100800000000000000000000000000000000192b52ba08ed509fc84d5775a7182498fd1ff80941d673c53470c9c9f1192f9c0057d68a1dfee0c68fe5df3625cc43bf000000000000000000000000000000000d3fcaf2f727e0eb32c65da9b910dc681b948dda874d0db6f6ed3f063430fbf073385a9a14c2dd78568726124e2b3ea8000000000000000000000000000000001943ce22cdb2387bd5796950dc95d1ace4012ab9bb4afb46223760230c1709e075f1ae76d6b3f2e947ba6b16d458ccd1000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000a29e38da2d42fd4712052800c7c8dd6e94fd9f506e946068aaac799d60b94c2d7515769ffdd32ea95d3910330ec47de000000000000000000000000000000000c60dae92451206390e30b5daa7151d63624dee496753c87dd54eadc92dc9602081fae02a1a53bac97e984a571923a5d00000000000000000000000000000000085f4fda4c72328895f20c683cb49603a37ff2c43d62f66602506dad5b8d1daebfbac7a7db3f50ccf4dfff277deb105c0000000000000000000000000000000005674d005457e0fe1f0fd978d63996c5f3d29f9149ee4eb04c464742dd329ccaef5e5f6b896d986ddfc9f1b2a3aec13100000000000000000000000000000000071bc66d6e2d244afc4a5ce4da1dce3d0c22c303ba61310fdf57843bbd97763ef496833dfa99d14be084bb1a039bb2da0000000000000000000000000000000012c22e047b0af8e2f4bf3bd3633ef0f8264004ca8ea5677a468857a1762f815235a479e53f4ad4741ffda3fb855021c900000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "bls_pairing_10pairchecksfalse", + "Gas": 495000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_0", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed200000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_1", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_2", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_3", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d3000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_4", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f9000000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_5", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_6", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_7", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_8", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_9", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac94300000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_10", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e0000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_11", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_12", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a5787681000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_13", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f768000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_14", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_15", + "Gas": 108000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd8000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_16", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f900000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a6700000000000000000000000000000000018c690fc5571f69793ec3c82915ac9513726ec891312f4f11dd3ba0ee95cc98b50d925099b0642e58a106e8456bbcbed0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_17", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae70000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e88000000000000000000000000000000001129c94e0c06f51f1f808a62e42a5449dd159289c14a09c4cc382c91bcfe878b6f3555767c310de1b1c275eb3d17bcf5000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_18", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a79157800000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000f46a7dee0cb471ddef3a50670a5bfc443b8455877f1ff602b21faff1eff07fc6bf27930ca2159f01479359548339560000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_19", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b090000000000000000000000000000000011dbce34af6cb14d3e4d49f2482e1b058609f25dca79e2ca48e289ace9d1c8db177b7bc35daad79aa5fdac77728bd5fc0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_20", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd6700000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000108248cdc6f503c7bad30eac875d92a75feccbdbe7b5394f8557a92bb12a796430a2c60ca23c6ecd259e5b01e53891820000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_21", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed30250000000000000000000000000000000006f1fe4d98675ffc62d4d5dd0af9968faca4d0ef425d56fa31b80aea892820e270f6da17aec9eb83c6cc9f84289ecbff000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_22", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff32000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e705800000000000000000000000000000000091ce9e6c4bab3ad3d275cf5888387646c3d9bb53ace7bd0c118fce3e44fcd96241b58e5af53978272f56d04b7d7ab79000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_23", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000b4322c2fb5d5dd2c65b45f74ca75002c97444d8d4e5680d0369d32d180c93b294e30ef42f21ac274f77ad89633ab1b9000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_24", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c200000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b100000000000000000000000000000000081162fe2e65a642993ba283df9bc8d60bfe495b2dc5623f0467c87bc7570980be2654c8cc8838fb362c677f3a3cb1e900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_25", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e272839000000000000000000000000000000001324e51f295ea95adedc9730dac2e1ab6d85838840e3955103d76677ce9a7359215f8d954286950bed1be3bbfebabeda0000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_26", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b00000000000000000000000000000000132d85236d655190323279a01acc8cbc6fb736d951e3999589f0559cb61ea93e2e1c526ed08ef08046c3d7a40d7cfdaf00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_27", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da0000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a60000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000a4ec4acf91be994fe45f08dbeb2bbd053275861d11a486693fd6e5bfa3736134396f6b1c3ad146642f2e972ba609d0b000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_28", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b592947030000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe9760000000000000000000000000000000011dc30871a7a9b33e2882f6b24ccd192aad215edfc6c6bd9acd064dff4a43f41b4c212068875e896daf127547bbeca58000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_29", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000dd1137e592119c134103b9e29e4f14b48387a767d4effae44f807e5b579e7f9c2f60105495f070d0a6ab2410f62844d00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_30", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c0533455013242330000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad62420000000000000000000000000000000016ad3b981f689f51f46e3e5e166986e4e71655dcc1e928327751ffdcfff8934caec5cc37327b3320202c4efbcfda6ae3000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_31", + "Gas": 151000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000cbcd06a1c576af16d0d77ff8bcc3669a486d044cc7b85db03661a92f4c5c44a28d028521dfcfc292d8ecd05aed6ab940000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f00000000000000000000000000000000153ba4ab4fecc724c843b8f78db2db1943e91051b8cb9be2eb7e610a570f1f5925b7981334951b505cce1a3992ff05c9000000000000000000000000000000000c1e79925e9ebfd99e5d11489c56a994e0f855a759f0652cc9bb5151877cfea5c37896f56b949167b9cd2226f14333dd", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_32", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c81292925400000000000000000000000000000000065d3d4be1589a6f00c85c208c44916a35349a096b09219704b4c31861ef58e6b4ea5be57a175b429e482b1038718d6c000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000b8bf51e6509e81b70ff44d6e0df8f9f7bc8e33126e9f1b5a8419414878a2209c05df56cf590c8e33f484587f4a1f6950000000000000000000000000000000001c07a85ece4a1c5072fe722fb264bd1d3aaabf9db9809d5a6cb3fe17157d8fd1bfa730a26e34c87279ea81abe141fe6", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_33", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda00000000000000000000000000000000055caff20f9f3f2ea27c64caeb6bb1880f326c64eb6ed6ee7d15da7bfeb16518f76a75f061cd347f7322e0cec634f0fc000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000000197b17c5b695db49c7c8b6fd6f314da7cfdfc4dbe61c76475839c89064870fad5104234c09aee7bafc1660263b6959e0000000000000000000000000000000019e514b65a358640ea90cd3cf547d591f5ff1a13ad99c568ef70c558cbec26dd1e582cc92d849fcfce9749068d361372", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_34", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000003c0c619be1382199bee84862a0ac6bf4c891d22f722b6af5bfef0edd1ed8c7e9af5efb5d3fc546801f3e019329ae4e80000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa5200000000000000000000000000000000173bdb0d0a79f1f2607867ddbf2581b4c1cc20fc0bced60ed8756aa4e79cb87c47fa722b1c8fdbe99a8208fc532327b600000000000000000000000000000000172f37eac49dd7f09adf602ebe562e5d0bd52ee2419fc08dc7fda241c0319b3f43b3ec79ab5aac246a783f13e268d4bb", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_35", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a030000000000000000000000000000000014c22dcacf2e21ff447c94d81067c626b1217e58b7dc98aacab2ea3fc00b1c5e66f660d19f1c69b16571e49d13c8e1d0000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec35000000000000000000000000000000000125ffac343f97afc413ae80d40a309dfe461fe6b20eb84f8eec3a2718ec2c9f1273bcba2fa1ca59fdc924e471173d68000000000000000000000000000000000ebfaf78e267fd93f0e35e0d19feae9db125660a3dde99b028be77c6fac0116a4808aab02a29063c3c0bc4476924d04f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_36", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000010338047b7c67c122ffb13466935623ef2338b32bbf5452f78f7abe9a13a16824c11f5520c9dac256b9d257da88d92a30000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000a96371995c5333949b5e9869599fcb67222c2e44447d133e9b3e18a9e9e14a92ee03dcba86832cde7d35b35a88bcd240000000000000000000000000000000003a912710b425cc477c43ff93684249649732b1e99270bba725e990559169b505e8411fba174b6a15f90d5b3962f5399", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_37", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000008418a39124b40643dddcd6afe1dbdf930303bca65226c2fee1b95de6e080e25451f8b4f2b2b7c4633e1de6a5a7d47cb000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c000000000000000000000000000000001535bce9acb48b6ebc4d3dbb7c236d7cc57d656210e42e9379d4f528fc0ba59bf868503d3bb8e5cd3dafdd881ec56d5c00000000000000000000000000000000037033062d13644c88c317f1c58c18e0d9b4facbbe0701ac8bbdf3c7f0c37b14034c7882d5112a94bea39dfdbfc518e7", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_38", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000ada13f88a645bc6082c6321e0cf2b7ac45c633fe2f0cb36aeb187fe2e50e7510df2a86b98979e8551636e94488c8ce000000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000002f98cb2305518737e92ee72e2c48d0dbdbbb1f2dc63b9d02d1a8c27dd1ba5a071dc72a8dcc7813b5757bc244357f6630000000000000000000000000000000017775ae72405e39e2db32d5b9db6637b7bbb9799e614bd783f0b785c3f2ee815367e67b15cc037fec8252735f36c28cf", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_39", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a3700000000000000000000000000000000162e49ebd1b50c7837336509e48ace0e7856f00ec45a76b96d1dd88eea300a8118357cafabf32ee2d06b601def523e4800000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b6000000000000000000000000000000001575d39e959d14b96f261265417ca949a248c3d46e2abb093541c103dadf58cd5b21e28c79f17b376070799492457358000000000000000000000000000000001240585d5f4c28467bccb5193e4aad78ea3b1d8dfb4716a3310fb5215a478aac3f05a8ed478486c9e703a59b9c32bfe5", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_40", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000efe47bf2b11dd10608a309c8aaefdbcbc2bb5e6adceef375371cface8f79668e2b7c2ce9990063a3b53e419e80e2a79000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba00000000000000000000000000000000088ea99f17bb06ba20c5c67aeb8fbbd19b2270986ee7c6517209679e6f84f5d43fa22daf6264c993f1d048d016e3b39e0000000000000000000000000000000008af6330f5638c0a9f339f4e8d841b955e322766457112039b2a852b37d3bc45efb67aeb216a09a8940f5e4e1a771da8", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_41", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c1872290000000000000000000000000000000001099fe889d8f5ddcad09328997c7c3098ef4b4d74ab1d9f6fcbc33a03cafb59c7b28931da67950d1389fbcedca3fb5bb00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e00000000000000000000000000000000006b5813a1c1f934e8e564a7cad93dc7f57de7a9592aedeb116fa4ed6bc6452bbc0da23be10adfea4ad4fa82969e7a150000000000000000000000000000000008e760ad89fd250a9d5041ec81e51b8b66f5265037e7237f7c4a08bb83e7799f352c54c37cf70a6c61bb95bfbf8a616e", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_42", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a9000000000000000000000000000000001750d2e78525453f113b76f18abf2334de9755c03786fbc9233cda2364d57ed493f4fe6c2b565f4d82ff8113e9b63c4d0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000018446c6ba126e030ed071f87189cbd618627419c82065d26044759f6e4c7257f45021dfad1dcb34dd06b4e391329a61f00000000000000000000000000000000136b60cd7658a5d135d4bc38edff042570c7824245ed9f7a6414e9e7ab3840a99700fb620e809891b66003340db29e64", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_43", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb0000000000000000000000000000000001a28b7856a22db6e79ac4165e60addbb7dfe1f19d815032bc68fea905bd0d7709c2dafc65fe51493c964de678a30d32000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb877660000000000000000000000000000000006a5d436046e0ac1975e4d24bb3e3f35c1ba3801f37705505cdeb6a86c58bf8068b43462a55155799fe2d2cc60c398b900000000000000000000000000000000191fde77c7c2b397a950f0542d2edd183a5e9404e516146697a755561ab2a9705f970b491e4c0003657d864258f391ec", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_44", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e270000000000000000000000000000000007c17aaf82c2aa6bf01695157bcd0c2b34734dfbd572b0abe79c8dd3eef7ce6eb9c5e7de55b36ddf87f05e55ba9ac28b00000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df700000000000000000000000000000000073b61e6c2de0969138ce3671582c9b58305c5abefb54cfe13eb3284c2be04d26c906c717fd29aaf60b485e18de8e4fb0000000000000000000000000000000006297267dd3b6f3de2329e837302427ab6235b3ad4a9f8c6bb45795852d3c3c61fe75747bbc78043102fc3f646f5d1f9", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_45", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000017147eda83f35d0b6c8894317da5b2e991818479674d7dd1aef6bfaebacbb61ad4b2a17ce7e799939f8c2004af4799530000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c74390000000000000000000000000000000009e2fdaeb5f35c5aeb9aaca231439c45ac022875d55575cbf25c15cb6177c6b67416ad22fa7e7cb1924d4c2501f98bd80000000000000000000000000000000012dcaeaa2e415f46b579d9e325d7d7c3cd94083d25fe38c872f1907bbb741aff660d28bb663edd502444e11d2d60d8f0", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_46", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f10000000000000000000000000000000016c1c9ca735535f801c58a9e35a80ce122d20abb327b44db4dea31b899982c4e136a2430c51cf3a31adc5611621f9dde000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000b49e02d9fb238a258f3a4307b6a2f64912b7fa91712b5639de24e90c09f9797654e0f7e2d31e968c040b867de03cd370000000000000000000000000000000005c56a16431ba175ad81260faeac87d8238f86b2828b0e74dbb0b296b34745ac17e6b684a25a16240183619c96b986bd", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_47", + "Gas": 194000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf20000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf2", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_48", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e500000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e5", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_49", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_50", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae850000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae85", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_51", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_52", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_53", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_54", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c50000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c5", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_55", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e790000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e79", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_56", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_57", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_58", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_59", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_60", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa4820000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa482", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_61", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_62", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_63", + "Gas": 237000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_64", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_65", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_66", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_67", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_68", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_69", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_70", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_71", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_72", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_73", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_74", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_75", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_76", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_77", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_78", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "matter_pairing_79", + "Gas": 280000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb963", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_80", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_81", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_82", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_83", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_84", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_85", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_86", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_87", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_88", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_89", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e6", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_90", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_91", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_92", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_93", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a7", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_94", + "Gas": 409000, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "matter_pairing_95", + "Gas": 409000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/bn256Add.json b/core/vm/testdata/precompiles/bn256Add.json new file mode 100644 index 0000000..b6fcd55 --- /dev/null +++ b/core/vm/testdata/precompiles/bn256Add.json @@ -0,0 +1,114 @@ +[ + { + "Input": "18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f3726607c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7", + "Expected": "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915", + "Name": "chfast1", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c91518b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266", + "Expected": "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204", + "Name": "chfast2", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "cdetrio1", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "cdetrio2", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "cdetrio3", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "cdetrio4", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "cdetrio5", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Name": "cdetrio6", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Name": "cdetrio7", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Name": "cdetrio8", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Gas": 150, + "Name": "cdetrio9", + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Gas": 150, + "Name": "cdetrio10", + "NoBenchmark": false + }, + { + "Input": "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Expected": "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4", + "Name": "cdetrio11", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4", + "Name": "cdetrio12", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", + "Expected": "15bf2bb17880144b5d1cd2b1f46eff9d617bffd1ca57c37fb5a49bd84e53cf66049c797f9ce0d17083deb32b5e36f2ea2a212ee036598dd7624c168993d1355f", + "Name": "cdetrio13", + "Gas": 150, + "NoBenchmark": false + }, + { + "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "cdetrio14", + "Gas": 150, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/bn256Pairing.json b/core/vm/testdata/precompiles/bn256Pairing.json new file mode 100644 index 0000000..3fbed6b --- /dev/null +++ b/core/vm/testdata/precompiles/bn256Pairing.json @@ -0,0 +1,100 @@ +[ + { + "Input": "1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "jeff1", + "Gas": 113000, + "NoBenchmark": false + }, + { + "Input": "2eca0c7238bf16e83e7a1e6c5d49540685ff51380f309842a98561558019fc0203d3260361bb8451de5ff5ecd17f010ff22f5c31cdf184e9020b06fa5997db841213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f06967a1237ebfeca9aaae0d6d0bab8e28c198c5a339ef8a2407e31cdac516db922160fa257a5fd5b280642ff47b65eca77e626cb685c84fa6d3b6882a283ddd1198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "jeff2", + "Gas": 113000, + "NoBenchmark": false + }, + { + "Input": "0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd216da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba2e89718ad33c8bed92e210e81d1853435399a271913a6520736a4729cf0d51eb01a9e2ffa2e92599b68e44de5bcf354fa2642bd4f26b259daa6f7ce3ed57aeb314a9a87b789a58af499b314e13c3d65bede56c07ea2d418d6874857b70763713178fb49a2d6cd347dc58973ff49613a20757d0fcc22079f9abd10c3baee245901b9e027bd5cfc2cb5db82d4dc9677ac795ec500ecd47deee3b5da006d6d049b811d7511c78158de484232fc68daf8a45cf217d1c2fae693ff5871e8752d73b21198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "jeff3", + "Gas": 113000, + "NoBenchmark": false + }, + { + "Input": "2f2ea0b3da1e8ef11914acf8b2e1b32d99df51f5f4f206fc6b947eae860eddb6068134ddb33dc888ef446b648d72338684d678d2eb2371c61a50734d78da4b7225f83c8b6ab9de74e7da488ef02645c5a16a6652c3c71a15dc37fe3a5dcb7cb122acdedd6308e3bb230d226d16a105295f523a8a02bfc5e8bd2da135ac4c245d065bbad92e7c4e31bf3757f1fe7362a63fbfee50e7dc68da116e67d600d9bf6806d302580dc0661002994e7cd3a7f224e7ddc27802777486bf80f40e4ca3cfdb186bac5188a98c45e6016873d107f5cd131f3a3e339d0375e58bd6219347b008122ae2b09e539e152ec5364e7e2204b03d11d3caa038bfc7cd499f8176aacbee1f39e4e4afc4bc74790a4a028aff2c3d2538731fb755edefd8cb48d6ea589b5e283f150794b6736f670d6a1033f9b46c6f5204f50813eb85c8dc4b59db1c5d39140d97ee4d2b36d99bc49974d18ecca3e7ad51011956051b464d9e27d46cc25e0764bb98575bd466d32db7b15f582b2d5c452b36aa394b789366e5e3ca5aabd415794ab061441e51d01e94640b7e3084a07e02c78cf3103c542bc5b298669f211b88da1679b0b64a63b7e0e7bfe52aae524f73a55be7fe70c7e9bfc94b4cf0da1213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "jeff4", + "Gas": 147000, + "NoBenchmark": false + }, + { + "Input": "20a754d2071d4d53903e3b31a7e98ad6882d58aec240ef981fdf0a9d22c5926a29c853fcea789887315916bbeb89ca37edb355b4f980c9a12a94f30deeed30211213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f1abb4a25eb9379ae96c84fff9f0540abcfc0a0d11aeda02d4f37e4baf74cb0c11073b3ff2cdbb38755f8691ea59e9606696b3ff278acfc098fa8226470d03869217cee0a9ad79a4493b5253e2e4e3a39fc2df38419f230d341f60cb064a0ac290a3d76f140db8418ba512272381446eb73958670f00cf46f1d9e64cba057b53c26f64a8ec70387a13e41430ed3ee4a7db2059cc5fc13c067194bcc0cb49a98552fd72bd9edb657346127da132e5b82ab908f5816c826acb499e22f2412d1a2d70f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd2198a1f162a73261f112401aa2db79c7dab1533c9935c77290a6ce3b191f2318d198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "jeff5", + "Gas": 147000, + "NoBenchmark": false + }, + { + "Input": "1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c103188585e2364128fe25c70558f1560f4f9350baf3959e603cc91486e110936198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "jeff6", + "Gas": 113000, + "NoBenchmark": false + }, + { + "Input": "", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "empty_data", + "Gas": 45000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "one_point", + "Gas": 79000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "two_point_match_2", + "Gas": 113000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "two_point_match_3", + "Gas": 113000, + "NoBenchmark": false + }, + { + "Input": "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "two_point_match_4", + "Gas": 113000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "ten_point_match_1", + "Gas": 385000, + "NoBenchmark": false + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "ten_point_match_2", + "Gas": 385000, + "NoBenchmark": false + }, + { + "Input": "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "ten_point_match_3", + "Gas": 113000, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/bn256ScalarMul.json b/core/vm/testdata/precompiles/bn256ScalarMul.json new file mode 100644 index 0000000..b0427fc --- /dev/null +++ b/core/vm/testdata/precompiles/bn256ScalarMul.json @@ -0,0 +1,135 @@ +[ + { + "Input": "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb20400000000000000000000000000000000000000000000000011138ce750fa15c2", + "Expected": "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc", + "Name": "chfast1", + "Gas": 6000, + "NoBenchmark": false + }, + { + "Input": "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46", + "Expected": "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e", + "Name": "chfast2", + "Gas": 6000, + "NoBenchmark": false + }, + { + "Input": "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3", + "Expected": "14789d0d4a730b354403b5fac948113739e276c23e0258d8596ee72f9cd9d3230af18a63153e0ec25ff9f2951dd3fa90ed0197bfef6e2a1a62b5095b9d2b4a27", + "Name": "chfast3", + "Gas": 6000, + "NoBenchmark": false + }, + { + "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "Expected": "2cde5879ba6f13c0b5aa4ef627f159a3347df9722efce88a9afbb20b763b4c411aa7e43076f6aee272755a7f9b84832e71559ba0d2e0b17d5f9f01755e5b0d11", + "Name": "cdetrio1", + "Gas": 6000, + "NoBenchmark": false + }, + { + "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f630644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", + "Expected": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3163511ddc1c3f25d396745388200081287b3fd1472d8339d5fecb2eae0830451", + "Name": "cdetrio2", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000100000000000000000000000000000000", + "Expected": "1051acb0700ec6d42a88215852d582efbaef31529b6fcbc3277b5c1b300f5cf0135b2394bb45ab04b8bd7611bd2dfe1de6a4e6e2ccea1ea1955f577cd66af85b", + "Name": "cdetrio3", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000000000000000000000000000000000009", + "Expected": "1dbad7d39dbc56379f78fac1bca147dc8e66de1b9d183c7b167351bfe0aeab742cd757d51289cd8dbd0acf9e673ad67d0f0a89f912af47ed1be53664f5692575", + "Name": "cdetrio4", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000000000000000000000000000000000001", + "Expected": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6", + "Name": "cdetrio5", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "Expected": "29e587aadd7c06722aabba753017c093f70ba7eb1f1c0104ec0564e7e3e21f6022b1143f6a41008e7755c71c3d00b6b915d386de21783ef590486d8afa8453b1", + "Name": "cdetrio6", + "Gas": 6000, + "NoBenchmark": false + }, + { + "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", + "Expected": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb", + "Name": "cdetrio7", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000100000000000000000000000000000000", + "Expected": "221a3577763877920d0d14a91cd59b9479f83b87a653bb41f82a3f6f120cea7c2752c7f64cdd7f0e494bff7b60419f242210f2026ed2ec70f89f78a4c56a1f15", + "Name": "cdetrio8", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000000000000000000000000000000000009", + "Expected": "228e687a379ba154554040f8821f4e41ee2be287c201aa9c3bc02c9dd12f1e691e0fd6ee672d04cfd924ed8fdc7ba5f2d06c53c1edc30f65f2af5a5b97f0a76a", + "Name": "cdetrio9", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000000000000000000000000000000000001", + "Expected": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", + "Name": "cdetrio10", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "Expected": "00a1a234d08efaa2616607e31eca1980128b00b415c845ff25bba3afcb81dc00242077290ed33906aeb8e42fd98c41bcb9057ba03421af3f2d08cfc441186024", + "Name": "cdetrio11", + "Gas": 6000, + "NoBenchmark": false + }, + { + "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d9830644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", + "Expected": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b8692929ee761a352600f54921df9bf472e66217e7bb0cee9032e00acc86b3c8bfaf", + "Name": "cdetrio12", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000100000000000000000000000000000000", + "Expected": "1071b63011e8c222c5a771dfa03c2e11aac9666dd097f2c620852c3951a4376a2f46fe2f73e1cf310a168d56baa5575a8319389d7bfa6b29ee2d908305791434", + "Name": "cdetrio13", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000009", + "Expected": "19f75b9dd68c080a688774a6213f131e3052bd353a304a189d7a2ee367e3c2582612f545fb9fc89fde80fd81c68fc7dcb27fea5fc124eeda69433cf5c46d2d7f", + "Name": "cdetrio14", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000001", + "Expected": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", + "Name": "cdetrio15", + "Gas": 6000, + "NoBenchmark": true + }, + { + "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000000", + "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "Name": "zeroScalar", + "Gas": 6000, + "NoBenchmark": true + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/ecRecover.json b/core/vm/testdata/precompiles/ecRecover.json new file mode 100644 index 0000000..4911d61 --- /dev/null +++ b/core/vm/testdata/precompiles/ecRecover.json @@ -0,0 +1,37 @@ +[ + { + "Input": "a8b53bdf3306a35a7103ab5504a0c9b492295564b6202b1942a84ef300107281000000000000000000000000000000000000000000000000000000000000001b307835653165303366353363653138623737326363623030393366663731663366353366356337356237346463623331613835616138623838393262346538621122334455667788991011121314151617181920212223242526272829303132", + "Expected": "", + "Gas": 3000, + "Name": "CallEcrecoverUnrecoverableKey", + "NoBenchmark": false + }, + { + "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", + "Expected": "000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "Gas": 3000, + "Name": "ValidKey", + "NoBenchmark": false + }, + { + "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c100000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", + "Expected": "", + "Gas": 3000, + "Name": "InvalidHighV-bits-1", + "NoBenchmark": false + }, + { + "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000001000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", + "Expected": "", + "Gas": 3000, + "Name": "InvalidHighV-bits-2", + "NoBenchmark": false + }, + { + "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000001000000000000000000000011c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", + "Expected": "", + "Gas": 3000, + "Name": "InvalidHighV-bits-3", + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blake2f.json b/core/vm/testdata/precompiles/fail-blake2f.json new file mode 100644 index 0000000..70835aa --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blake2f.json @@ -0,0 +1,22 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "vector 0: empty input" + }, + { + "Input": "00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "ExpectedError": "invalid input length", + "Name": "vector 1: less than 213 bytes input" + }, + { + "Input": "000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", + "ExpectedError": "invalid input length", + "Name": "vector 2: more than 213 bytes input" + }, + { + "Input": "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002", + "ExpectedError": "invalid final flag", + "Name": "vector 3: malformed final block indicator flag" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsG1Add.json b/core/vm/testdata/precompiles/fail-blsG1Add.json new file mode 100644 index 0000000..86bd3d6 --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsG1Add.json @@ -0,0 +1,32 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g1add_empty_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "ExpectedError": "invalid input length", + "Name": "bls_g1add_short_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "ExpectedError": "invalid input length", + "Name": "bls_g1add_large_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000108b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g1add_violate_top_bytes" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g1add_invalid_field_element" + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g1add_point_not_on_curve" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsG1Mul.json b/core/vm/testdata/precompiles/fail-blsG1Mul.json new file mode 100644 index 0000000..7473d4d --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsG1Mul.json @@ -0,0 +1,32 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g1mul_empty_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid input length", + "Name": "bls_g1mul_short_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid input length", + "Name": "bls_g1mul_large_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000108b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g1mul_violate_top_bytes" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g1mul_invalid_field_element" + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g1mul_point_not_on_curve" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsG1MultiExp.json b/core/vm/testdata/precompiles/fail-blsG1MultiExp.json new file mode 100644 index 0000000..24a46cc --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsG1MultiExp.json @@ -0,0 +1,32 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g1multiexp_empty_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid input length", + "Name": "bls_g1multiexp_short_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid input length", + "Name": "bls_g1multiexp_large_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g1multiexp_invalid_field_element" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000108b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g1multiexp_violate_top_bytes" + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g1multiexp_point_not_on_curve" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsG2Add.json b/core/vm/testdata/precompiles/fail-blsG2Add.json new file mode 100644 index 0000000..b28a052 --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsG2Add.json @@ -0,0 +1,32 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g2add_empty_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b828010000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "invalid input length", + "Name": "bls_g2add_short_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b8280100000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "invalid input length", + "Name": "bls_g2add_large_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000010606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g2add_violate_top_bytes" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g2add_invalid_field_element" + }, + { + "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g2add_point_not_on_curve" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsG2Mul.json b/core/vm/testdata/precompiles/fail-blsG2Mul.json new file mode 100644 index 0000000..54a13c7 --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsG2Mul.json @@ -0,0 +1,32 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g2mul_empty_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b828010000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid input length", + "Name": "bls_g2mul_short_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b8280100000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid input length", + "Name": "bls_g2mul_large_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000010606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g2mul_violate_top_bytes" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g2mul_invalid_field_element" + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g2mul_point_not_on_curve" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsG2MultiExp.json b/core/vm/testdata/precompiles/fail-blsG2MultiExp.json new file mode 100644 index 0000000..1679f17 --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsG2MultiExp.json @@ -0,0 +1,32 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_g2multiexp_empty_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b828010000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid input length", + "Name": "bls_g2multiexp_short_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b8280100000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid input length", + "Name": "bls_g2multiexp_large_input" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000010606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_g2multiexp_violate_top_bytes" + }, + { + "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000000000000000000000000000000000007", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_g2multiexp_invalid_field_element" + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_g2multiexp_point_not_on_curve" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsMapG1.json b/core/vm/testdata/precompiles/fail-blsMapG1.json new file mode 100644 index 0000000..8eacca4 --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsMapG1.json @@ -0,0 +1,22 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_mapg1_empty_input" + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "ExpectedError": "invalid input length", + "Name": "bls_mapg1_short_input" + }, + { + "Input": "00000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_mapg1_top_bytes" + }, + { + "Input": "000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_mapg1_invalid_fq_element" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsMapG2.json b/core/vm/testdata/precompiles/fail-blsMapG2.json new file mode 100644 index 0000000..184d3ec --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsMapG2.json @@ -0,0 +1,22 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_mapg2_empty_input" + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "ExpectedError": "invalid input length", + "Name": "bls_mapg2_short_input" + }, + { + "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_mapg2_top_bytes" + }, + { + "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_mapg2_invalid_fq_element" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/fail-blsPairing.json b/core/vm/testdata/precompiles/fail-blsPairing.json new file mode 100644 index 0000000..4314d73 --- /dev/null +++ b/core/vm/testdata/precompiles/fail-blsPairing.json @@ -0,0 +1,42 @@ +[ + { + "Input": "", + "ExpectedError": "invalid input length", + "Name": "bls_pairing_empty_input" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b8280100000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "invalid input length", + "Name": "bls_pairing_extra_data" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac", + "ExpectedError": "invalid fp.Element encoding", + "Name": "bls_pairing_invalid_field_element" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000010606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "invalid field element top bytes", + "Name": "bls_pairing_top_bytes" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_pairing_g1_not_on_curve" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "ExpectedError": "invalid point: not on curve", + "Name": "bls_pairing_g2_not_on_curve" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000a989badd40d6212b33cffc3f3763e9bc760f988c9926b26da9dd85e928483446346b8ed00e1de5d5ea93e354abe706c00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", + "ExpectedError": "g1 point is not on correct subgroup", + "Name": "bls_pairing_g1_not_in_correct_subgroup" + }, + { + "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013a59858b6809fca4d9a3b6539246a70051a3c88899964a42bc9a69cf9acdd9dd387cfa9086b894185b9a46a402be730000000000000000000000000000000002d27e0ec3356299a346a09ad7dc4ef68a483c3aed53f9139d2f929a3eecebf72082e5e58c6da24ee32e03040c406d4f", + "ExpectedError": "g2 point is not on correct subgroup", + "Name": "bls_pairing_g2_not_in_correct_subgroup" + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/modexp.json b/core/vm/testdata/precompiles/modexp.json new file mode 100644 index 0000000..4550eb9 --- /dev/null +++ b/core/vm/testdata/precompiles/modexp.json @@ -0,0 +1,121 @@ +[ + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "eip_example1", + "Gas": 13056, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "eip_example2", + "Gas": 13056, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc", + "Name": "nagydani-1-square", + "Gas": 204, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec", + "Name": "nagydani-1-qube", + "Gas": 204, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2", + "Name": "nagydani-1-pow0x10001", + "Gas": 3276, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70", + "Name": "nagydani-2-square", + "Gas": 665, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f", + "Name": "nagydani-2-qube", + "Gas": 665, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2", + "Name": "nagydani-2-pow0x10001", + "Gas": 10649, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8", + "Name": "nagydani-3-square", + "Gas": 1894, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e", + "Name": "nagydani-3-qube", + "Gas": 1894, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76", + "Name": "nagydani-3-pow0x10001", + "Gas": 30310, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10", + "Name": "nagydani-4-square", + "Gas": 5580, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc", + "Name": "nagydani-4-qube", + "Gas": 5580, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963", + "Name": "nagydani-4-pow0x10001", + "Gas": 89292, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb", + "Name": "nagydani-5-square", + "Gas": 17868, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f", + "Name": "nagydani-5-qube", + "Gas": 17868, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500", + "Name": "nagydani-5-pow0x10001", + "Gas": 285900, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/modexp_eip2565.json b/core/vm/testdata/precompiles/modexp_eip2565.json new file mode 100644 index 0000000..c554414 --- /dev/null +++ b/core/vm/testdata/precompiles/modexp_eip2565.json @@ -0,0 +1,121 @@ +[ + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "eip_example1", + "Gas": 1360, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "eip_example2", + "Gas": 1360, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc", + "Name": "nagydani-1-square", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec", + "Name": "nagydani-1-qube", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2", + "Name": "nagydani-1-pow0x10001", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70", + "Name": "nagydani-2-square", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f", + "Name": "nagydani-2-qube", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2", + "Name": "nagydani-2-pow0x10001", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8", + "Name": "nagydani-3-square", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e", + "Name": "nagydani-3-qube", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76", + "Name": "nagydani-3-pow0x10001", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10", + "Name": "nagydani-4-square", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc", + "Name": "nagydani-4-qube", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963", + "Name": "nagydani-4-pow0x10001", + "Gas": 21845, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb", + "Name": "nagydani-5-square", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f", + "Name": "nagydani-5-qube", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500", + "Name": "nagydani-5-pow0x10001", + "Gas": 87381, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/pointEvaluation.json b/core/vm/testdata/precompiles/pointEvaluation.json new file mode 100644 index 0000000..dfb2cad --- /dev/null +++ b/core/vm/testdata/precompiles/pointEvaluation.json @@ -0,0 +1,9 @@ +[ + { + "Input": "01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a", + "Expected": "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", + "Name": "pointEvaluation1", + "Gas": 50000, + "NoBenchmark": false + } +] diff --git a/core/vm/testdata/testcases_add.json b/core/vm/testdata/testcases_add.json new file mode 100644 index 0000000..c03ae96 --- /dev/null +++ b/core/vm/testdata/testcases_add.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000006"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000006"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"000000000000000000000000000000000000000000000000000000000000000a"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000003"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000004"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000006"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000003"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000004"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000006"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_and.json b/core/vm/testdata/testcases_and.json new file mode 100644 index 0000000..aba5f24 --- /dev/null +++ b/core/vm/testdata/testcases_and.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_byte.json b/core/vm/testdata/testcases_byte.json new file mode 100644 index 0000000..88d7c7d --- /dev/null +++ b/core/vm/testdata/testcases_byte.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"000000000000000000000000000000000000000000000000000000000000007f"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"000000000000000000000000000000000000000000000000000000000000007f"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000080"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000080"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"00000000000000000000000000000000000000000000000000000000000000ff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_div.json b/core/vm/testdata/testcases_div.json new file mode 100644 index 0000000..b1f9c7f --- /dev/null +++ b/core/vm/testdata/testcases_div.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"1999999999999999999999999999999999999999999999999999999999999999"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"1999999999999999999999999999999999999999999999999999999999999999"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"1999999999999999999999999999999999999999999999999999999999999999"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"1999999999999999999999999999999999999999999999999999999999999999"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"3333333333333333333333333333333333333333333333333333333333333332"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"3333333333333333333333333333333333333333333333333333333333333333"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_eq.json b/core/vm/testdata/testcases_eq.json new file mode 100644 index 0000000..937eadb --- /dev/null +++ b/core/vm/testdata/testcases_eq.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_exp.json b/core/vm/testdata/testcases_exp.json new file mode 100644 index 0000000..6181835 --- /dev/null +++ b/core/vm/testdata/testcases_exp.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000c35"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3cb"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c29"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c28f5c29"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccd"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"3333333333333333333333333333333333333333333333333333333333333333"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"58cd20afa2f05a708ede54b48d3ae685db76b3bb83cf2cf95d4e8fb00bcbe61d"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"a732df505d0fa58f7121ab4b72c5197a24894c447c30d306a2b1704ff43419e3"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccd"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"3333333333333333333333333333333333333333333333333333333333333333"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_gt.json b/core/vm/testdata/testcases_gt.json new file mode 100644 index 0000000..637bd3f --- /dev/null +++ b/core/vm/testdata/testcases_gt.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_lt.json b/core/vm/testdata/testcases_lt.json new file mode 100644 index 0000000..55252a4 --- /dev/null +++ b/core/vm/testdata/testcases_lt.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_mod.json b/core/vm/testdata/testcases_mod.json new file mode 100644 index 0000000..192503f --- /dev/null +++ b/core/vm/testdata/testcases_mod.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000003"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000003"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000003"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_mul.json b/core/vm/testdata/testcases_mul.json new file mode 100644 index 0000000..dc44c25 --- /dev/null +++ b/core/vm/testdata/testcases_mul.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000019"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"800000000000000000000000000000000000000000000000000000000000000a"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"800000000000000000000000000000000000000000000000000000000000000a"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000019"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_or.json b/core/vm/testdata/testcases_or.json new file mode 100644 index 0000000..bfa561b --- /dev/null +++ b/core/vm/testdata/testcases_or.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_sar.json b/core/vm/testdata/testcases_sar.json new file mode 100644 index 0000000..c93abbd --- /dev/null +++ b/core/vm/testdata/testcases_sar.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"c000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"fc00000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"c000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"fc00000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_sdiv.json b/core/vm/testdata/testcases_sdiv.json new file mode 100644 index 0000000..18cb666 --- /dev/null +++ b/core/vm/testdata/testcases_sdiv.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"1999999999999999999999999999999999999999999999999999999999999999"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"1999999999999999999999999999999999999999999999999999999999999999"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"e666666666666666666666666666666666666666666666666666666666666667"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"e666666666666666666666666666666666666666666666666666666666666667"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"e666666666666666666666666666666666666666666666666666666666666667"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"e666666666666666666666666666666666666666666666666666666666666667"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"1999999999999999999999999999999999999999999999999999999999999999"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"1999999999999999999999999999999999999999999999999999999999999999"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_sgt.json b/core/vm/testdata/testcases_sgt.json new file mode 100644 index 0000000..aa581a6 --- /dev/null +++ b/core/vm/testdata/testcases_sgt.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_shl.json b/core/vm/testdata/testcases_shl.json new file mode 100644 index 0000000..65e9c07 --- /dev/null +++ b/core/vm/testdata/testcases_shl.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000020"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"000000000000000000000000000000000000000000000000000000000000000a"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"00000000000000000000000000000000000000000000000000000000000000a0"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000020"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_shr.json b/core/vm/testdata/testcases_shr.json new file mode 100644 index 0000000..a384913 --- /dev/null +++ b/core/vm/testdata/testcases_shr.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"4000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0400000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"4000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0400000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_signext.json b/core/vm/testdata/testcases_signext.json new file mode 100644 index 0000000..bdadd40 --- /dev/null +++ b/core/vm/testdata/testcases_signext.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_slt.json b/core/vm/testdata/testcases_slt.json new file mode 100644 index 0000000..4369b96 --- /dev/null +++ b/core/vm/testdata/testcases_slt.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_smod.json b/core/vm/testdata/testcases_smod.json new file mode 100644 index 0000000..980e034 --- /dev/null +++ b/core/vm/testdata/testcases_smod.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_sub.json b/core/vm/testdata/testcases_sub.json new file mode 100644 index 0000000..b3881a5 --- /dev/null +++ b/core/vm/testdata/testcases_sub.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000003"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000007"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000003"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000006"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000004"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000006"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"000000000000000000000000000000000000000000000000000000000000000a"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000003"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000004"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000006"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000002"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000006"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000002"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/core/vm/testdata/testcases_xor.json b/core/vm/testdata/testcases_xor.json new file mode 100644 index 0000000..4cc2ddd --- /dev/null +++ b/core/vm/testdata/testcases_xor.json @@ -0,0 +1 @@ +[{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"0000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000004"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"0000000000000000000000000000000000000000000000000000000000000005","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"8000000000000000000000000000000000000000000000000000000000000004"},{"X":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"8000000000000000000000000000000000000000000000000000000000000000","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"8000000000000000000000000000000000000000000000000000000000000004"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"0000000000000000000000000000000000000000000000000000000000000001"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"8000000000000000000000000000000000000000000000000000000000000001","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000005"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000004"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000000"},{"X":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000000","Expected":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000001","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"0000000000000000000000000000000000000000000000000000000000000005","Expected":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe","Expected":"8000000000000000000000000000000000000000000000000000000000000001"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"8000000000000000000000000000000000000000000000000000000000000000"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000000","Expected":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"8000000000000000000000000000000000000000000000000000000000000001","Expected":"7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb","Expected":"0000000000000000000000000000000000000000000000000000000000000004"},{"X":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Y":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","Expected":"0000000000000000000000000000000000000000000000000000000000000000"}] \ No newline at end of file diff --git a/crypto/blake2b/blake2b.go b/crypto/blake2b/blake2b.go new file mode 100644 index 0000000..7ecaab8 --- /dev/null +++ b/crypto/blake2b/blake2b.go @@ -0,0 +1,321 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blake2b implements the BLAKE2b hash algorithm defined by RFC 7693 +// and the extendable output function (XOF) BLAKE2Xb. +// +// For a detailed specification of BLAKE2b see https://blake2.net/blake2.pdf +// and for BLAKE2Xb see https://blake2.net/blake2x.pdf +// +// If you aren't sure which function you need, use BLAKE2b (Sum512 or New512). +// If you need a secret-key MAC (message authentication code), use the New512 +// function with a non-nil key. +// +// BLAKE2X is a construction to compute hash values larger than 64 bytes. It +// can produce hash values between 0 and 4 GiB. +package blake2b + +import ( + "encoding/binary" + "errors" + "hash" +) + +const ( + // The blocksize of BLAKE2b in bytes. + BlockSize = 128 + // The hash size of BLAKE2b-512 in bytes. + Size = 64 + // The hash size of BLAKE2b-384 in bytes. + Size384 = 48 + // The hash size of BLAKE2b-256 in bytes. + Size256 = 32 +) + +var ( + useAVX2 bool + useAVX bool + useSSE4 bool +) + +var ( + errKeySize = errors.New("blake2b: invalid key size") + errHashSize = errors.New("blake2b: invalid hash size") +) + +var iv = [8]uint64{ + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, +} + +// Sum512 returns the BLAKE2b-512 checksum of the data. +func Sum512(data []byte) [Size]byte { + var sum [Size]byte + checkSum(&sum, Size, data) + return sum +} + +// Sum384 returns the BLAKE2b-384 checksum of the data. +func Sum384(data []byte) [Size384]byte { + var sum [Size]byte + var sum384 [Size384]byte + checkSum(&sum, Size384, data) + copy(sum384[:], sum[:Size384]) + return sum384 +} + +// Sum256 returns the BLAKE2b-256 checksum of the data. +func Sum256(data []byte) [Size256]byte { + var sum [Size]byte + var sum256 [Size256]byte + checkSum(&sum, Size256, data) + copy(sum256[:], sum[:Size256]) + return sum256 +} + +// New512 returns a new hash.Hash computing the BLAKE2b-512 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New512(key []byte) (hash.Hash, error) { return newDigest(Size, key) } + +// New384 returns a new hash.Hash computing the BLAKE2b-384 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New384(key []byte) (hash.Hash, error) { return newDigest(Size384, key) } + +// New256 returns a new hash.Hash computing the BLAKE2b-256 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New256(key []byte) (hash.Hash, error) { return newDigest(Size256, key) } + +// New returns a new hash.Hash computing the BLAKE2b checksum with a custom length. +// A non-nil key turns the hash into a MAC. The key must be between zero and 64 bytes long. +// The hash size can be a value between 1 and 64 but it is highly recommended to use +// values equal or greater than: +// - 32 if BLAKE2b is used as a hash function (The key is zero bytes long). +// - 16 if BLAKE2b is used as a MAC function (The key is at least 16 bytes long). +// When the key is nil, the returned hash.Hash implements BinaryMarshaler +// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash. +func New(size int, key []byte) (hash.Hash, error) { return newDigest(size, key) } + +// F is a compression function for BLAKE2b. It takes as an argument the state +// vector `h`, message block vector `m`, offset counter `t`, final block indicator +// flag `f`, and number of rounds `rounds`. The state vector provided as the first +// parameter is modified by the function. +func F(h *[8]uint64, m [16]uint64, c [2]uint64, final bool, rounds uint32) { + var flag uint64 + if final { + flag = 0xFFFFFFFFFFFFFFFF + } + f(h, &m, c[0], c[1], flag, uint64(rounds)) +} + +func newDigest(hashSize int, key []byte) (*digest, error) { + if hashSize < 1 || hashSize > Size { + return nil, errHashSize + } + if len(key) > Size { + return nil, errKeySize + } + d := &digest{ + size: hashSize, + keyLen: len(key), + } + copy(d.key[:], key) + d.Reset() + return d, nil +} + +func checkSum(sum *[Size]byte, hashSize int, data []byte) { + h := iv + h[0] ^= uint64(hashSize) | (1 << 16) | (1 << 24) + var c [2]uint64 + + if length := len(data); length > BlockSize { + n := length &^ (BlockSize - 1) + if length == n { + n -= BlockSize + } + hashBlocks(&h, &c, 0, data[:n]) + data = data[n:] + } + + var block [BlockSize]byte + offset := copy(block[:], data) + remaining := uint64(BlockSize - offset) + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) + + for i, v := range h[:(hashSize+7)/8] { + binary.LittleEndian.PutUint64(sum[8*i:], v) + } +} + +func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + var m [16]uint64 + c0, c1 := c[0], c[1] + + for i := 0; i < len(blocks); { + c0 += BlockSize + if c0 < BlockSize { + c1++ + } + for j := range m { + m[j] = binary.LittleEndian.Uint64(blocks[i:]) + i += 8 + } + f(h, &m, c0, c1, flag, 12) + } + c[0], c[1] = c0, c1 +} + +type digest struct { + h [8]uint64 + c [2]uint64 + size int + block [BlockSize]byte + offset int + + key [BlockSize]byte + keyLen int +} + +const ( + magic = "b2b" + marshaledSize = len(magic) + 8*8 + 2*8 + 1 + BlockSize + 1 +) + +func (d *digest) MarshalBinary() ([]byte, error) { + if d.keyLen != 0 { + return nil, errors.New("crypto/blake2b: cannot marshal MACs") + } + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + for i := 0; i < 8; i++ { + b = appendUint64(b, d.h[i]) + } + b = appendUint64(b, d.c[0]) + b = appendUint64(b, d.c[1]) + // Maximum value for size is 64 + b = append(b, byte(d.size)) + b = append(b, d.block[:]...) + b = append(b, byte(d.offset)) + return b, nil +} + +func (d *digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("crypto/blake2b: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/blake2b: invalid hash state size") + } + b = b[len(magic):] + for i := 0; i < 8; i++ { + b, d.h[i] = consumeUint64(b) + } + b, d.c[0] = consumeUint64(b) + b, d.c[1] = consumeUint64(b) + d.size = int(b[0]) + b = b[1:] + copy(d.block[:], b[:BlockSize]) + b = b[BlockSize:] + d.offset = int(b[0]) + return nil +} + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Size() int { return d.size } + +func (d *digest) Reset() { + d.h = iv + d.h[0] ^= uint64(d.size) | (uint64(d.keyLen) << 8) | (1 << 16) | (1 << 24) + d.offset, d.c[0], d.c[1] = 0, 0, 0 + if d.keyLen > 0 { + d.block = d.key + d.offset = BlockSize + } +} + +func (d *digest) Write(p []byte) (n int, err error) { + n = len(p) + + if d.offset > 0 { + remaining := BlockSize - d.offset + if n <= remaining { + d.offset += copy(d.block[d.offset:], p) + return + } + copy(d.block[d.offset:], p[:remaining]) + hashBlocks(&d.h, &d.c, 0, d.block[:]) + d.offset = 0 + p = p[remaining:] + } + + if length := len(p); length > BlockSize { + nn := length &^ (BlockSize - 1) + if length == nn { + nn -= BlockSize + } + hashBlocks(&d.h, &d.c, 0, p[:nn]) + p = p[nn:] + } + + if len(p) > 0 { + d.offset += copy(d.block[:], p) + } + + return +} + +func (d *digest) Sum(sum []byte) []byte { + var hash [Size]byte + d.finalize(&hash) + return append(sum, hash[:d.size]...) +} + +func (d *digest) finalize(hash *[Size]byte) { + var block [BlockSize]byte + copy(block[:], d.block[:d.offset]) + remaining := uint64(BlockSize - d.offset) + + c := d.c + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + h := d.h + hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) + + for i, v := range h { + binary.LittleEndian.PutUint64(hash[8*i:], v) + } +} + +func appendUint64(b []byte, x uint64) []byte { + var a [8]byte + binary.BigEndian.PutUint64(a[:], x) + return append(b, a[:]...) +} + +//nolint:unused,deadcode +func appendUint32(b []byte, x uint32) []byte { + var a [4]byte + binary.BigEndian.PutUint32(a[:], x) + return append(b, a[:]...) +} + +func consumeUint64(b []byte) ([]byte, uint64) { + x := binary.BigEndian.Uint64(b) + return b[8:], x +} + +//nolint:unused,deadcode +func consumeUint32(b []byte) ([]byte, uint32) { + x := binary.BigEndian.Uint32(b) + return b[4:], x +} diff --git a/crypto/blake2b/blake2bAVX2_amd64.go b/crypto/blake2b/blake2bAVX2_amd64.go new file mode 100644 index 0000000..3a85d0e --- /dev/null +++ b/crypto/blake2b/blake2bAVX2_amd64.go @@ -0,0 +1,38 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.7 && amd64 && !gccgo && !appengine +// +build go1.7,amd64,!gccgo,!appengine + +package blake2b + +import "golang.org/x/sys/cpu" + +func init() { + useAVX2 = cpu.X86.HasAVX2 + useAVX = cpu.X86.HasAVX + useSSE4 = cpu.X86.HasSSE41 +} + +//go:noescape +func fAVX2(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) + +//go:noescape +func fAVX(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) + +//go:noescape +func fSSE4(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) + +func f(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) { + switch { + case useAVX2: + fAVX2(h, m, c0, c1, flag, rounds) + case useAVX: + fAVX(h, m, c0, c1, flag, rounds) + case useSSE4: + fSSE4(h, m, c0, c1, flag, rounds) + default: + fGeneric(h, m, c0, c1, flag, rounds) + } +} diff --git a/crypto/blake2b/blake2bAVX2_amd64.s b/crypto/blake2b/blake2bAVX2_amd64.s new file mode 100644 index 0000000..4998af3 --- /dev/null +++ b/crypto/blake2b/blake2bAVX2_amd64.s @@ -0,0 +1,717 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7,amd64,!gccgo,!appengine + +#include "textflag.h" + +DATA ·AVX2_iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX2_iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +DATA ·AVX2_iv0<>+0x10(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX2_iv0<>+0x18(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX2_iv0<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_iv1<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·AVX2_iv1<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +DATA ·AVX2_iv1<>+0x10(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX2_iv1<>+0x18(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX2_iv1<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +DATA ·AVX2_c40<>+0x10(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+0x18(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX2_c40<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +DATA ·AVX2_c48<>+0x10(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+0x18(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX2_c48<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX_iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX_iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +GLOBL ·AVX_iv0<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv1<>+0x00(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX_iv1<>+0x08(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX_iv1<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv2<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·AVX_iv2<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·AVX_iv2<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv3<>+0x00(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX_iv3<>+0x08(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX_iv3<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·AVX_c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX_c40<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·AVX_c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX_c48<>(SB), (NOPTR+RODATA), $16 + +#define VPERMQ_0x39_Y1_Y1 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xc9; BYTE $0x39 +#define VPERMQ_0x93_Y1_Y1 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xc9; BYTE $0x93 +#define VPERMQ_0x4E_Y2_Y2 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xd2; BYTE $0x4e +#define VPERMQ_0x93_Y3_Y3 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xdb; BYTE $0x93 +#define VPERMQ_0x39_Y3_Y3 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xdb; BYTE $0x39 + +#define ROUND_AVX2(m0, m1, m2, m3, t, c40, c48) \ + VPADDQ m0, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFD $-79, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPSHUFB c40, Y1, Y1; \ + VPADDQ m1, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFB c48, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPADDQ Y1, Y1, t; \ + VPSRLQ $63, Y1, Y1; \ + VPXOR t, Y1, Y1; \ + VPERMQ_0x39_Y1_Y1; \ + VPERMQ_0x4E_Y2_Y2; \ + VPERMQ_0x93_Y3_Y3; \ + VPADDQ m2, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFD $-79, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPSHUFB c40, Y1, Y1; \ + VPADDQ m3, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFB c48, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPADDQ Y1, Y1, t; \ + VPSRLQ $63, Y1, Y1; \ + VPXOR t, Y1, Y1; \ + VPERMQ_0x39_Y3_Y3; \ + VPERMQ_0x4E_Y2_Y2; \ + VPERMQ_0x93_Y1_Y1 + +#define VMOVQ_SI_X11_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x1E +#define VMOVQ_SI_X12_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x26 +#define VMOVQ_SI_X13_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x2E +#define VMOVQ_SI_X14_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x36 +#define VMOVQ_SI_X15_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x3E + +#define VMOVQ_SI_X11(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x5E; BYTE $n +#define VMOVQ_SI_X12(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x66; BYTE $n +#define VMOVQ_SI_X13(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x6E; BYTE $n +#define VMOVQ_SI_X14(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x76; BYTE $n +#define VMOVQ_SI_X15(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x7E; BYTE $n + +#define VPINSRQ_1_SI_X11_0 BYTE $0xC4; BYTE $0x63; BYTE $0xA1; BYTE $0x22; BYTE $0x1E; BYTE $0x01 +#define VPINSRQ_1_SI_X12_0 BYTE $0xC4; BYTE $0x63; BYTE $0x99; BYTE $0x22; BYTE $0x26; BYTE $0x01 +#define VPINSRQ_1_SI_X13_0 BYTE $0xC4; BYTE $0x63; BYTE $0x91; BYTE $0x22; BYTE $0x2E; BYTE $0x01 +#define VPINSRQ_1_SI_X14_0 BYTE $0xC4; BYTE $0x63; BYTE $0x89; BYTE $0x22; BYTE $0x36; BYTE $0x01 +#define VPINSRQ_1_SI_X15_0 BYTE $0xC4; BYTE $0x63; BYTE $0x81; BYTE $0x22; BYTE $0x3E; BYTE $0x01 + +#define VPINSRQ_1_SI_X11(n) BYTE $0xC4; BYTE $0x63; BYTE $0xA1; BYTE $0x22; BYTE $0x5E; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X12(n) BYTE $0xC4; BYTE $0x63; BYTE $0x99; BYTE $0x22; BYTE $0x66; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X13(n) BYTE $0xC4; BYTE $0x63; BYTE $0x91; BYTE $0x22; BYTE $0x6E; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X14(n) BYTE $0xC4; BYTE $0x63; BYTE $0x89; BYTE $0x22; BYTE $0x76; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X15(n) BYTE $0xC4; BYTE $0x63; BYTE $0x81; BYTE $0x22; BYTE $0x7E; BYTE $n; BYTE $0x01 + +#define VMOVQ_R8_X15 BYTE $0xC4; BYTE $0x41; BYTE $0xF9; BYTE $0x6E; BYTE $0xF8 +#define VPINSRQ_1_R9_X15 BYTE $0xC4; BYTE $0x43; BYTE $0x81; BYTE $0x22; BYTE $0xF9; BYTE $0x01 + +// load msg: Y12 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y12(i0, i1, i2, i3) \ + VMOVQ_SI_X12(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X12(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y12, Y12 + +// load msg: Y13 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y13(i0, i1, i2, i3) \ + VMOVQ_SI_X13(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X13(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y13, Y13 + +// load msg: Y14 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y14(i0, i1, i2, i3) \ + VMOVQ_SI_X14(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X14(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y14, Y14 + +// load msg: Y15 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y15(i0, i1, i2, i3) \ + VMOVQ_SI_X15(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X15(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15() \ + VMOVQ_SI_X12_0; \ + VMOVQ_SI_X11(4*8); \ + VPINSRQ_1_SI_X12(2*8); \ + VPINSRQ_1_SI_X11(6*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(1, 3, 5, 7); \ + LOAD_MSG_AVX2_Y14(8, 10, 12, 14); \ + LOAD_MSG_AVX2_Y15(9, 11, 13, 15) + +#define LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3() \ + LOAD_MSG_AVX2_Y12(14, 4, 9, 13); \ + LOAD_MSG_AVX2_Y13(10, 8, 15, 6); \ + VMOVQ_SI_X11(11*8); \ + VPSHUFD $0x4E, 0*8(SI), X14; \ + VPINSRQ_1_SI_X11(5*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + LOAD_MSG_AVX2_Y15(12, 2, 7, 3) + +#define LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4() \ + VMOVQ_SI_X11(5*8); \ + VMOVDQU 11*8(SI), X12; \ + VPINSRQ_1_SI_X11(15*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + VMOVQ_SI_X13(8*8); \ + VMOVQ_SI_X11(2*8); \ + VPINSRQ_1_SI_X13_0; \ + VPINSRQ_1_SI_X11(13*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(10, 3, 7, 9); \ + LOAD_MSG_AVX2_Y15(14, 6, 1, 4) + +#define LOAD_MSG_AVX2_7_3_13_11_9_1_12_14_2_5_4_15_6_10_0_8() \ + LOAD_MSG_AVX2_Y12(7, 3, 13, 11); \ + LOAD_MSG_AVX2_Y13(9, 1, 12, 14); \ + LOAD_MSG_AVX2_Y14(2, 5, 4, 15); \ + VMOVQ_SI_X15(6*8); \ + VMOVQ_SI_X11_0; \ + VPINSRQ_1_SI_X15(10*8); \ + VPINSRQ_1_SI_X11(8*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_9_5_2_10_0_7_4_15_14_11_6_3_1_12_8_13() \ + LOAD_MSG_AVX2_Y12(9, 5, 2, 10); \ + VMOVQ_SI_X13_0; \ + VMOVQ_SI_X11(4*8); \ + VPINSRQ_1_SI_X13(7*8); \ + VPINSRQ_1_SI_X11(15*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(14, 11, 6, 3); \ + LOAD_MSG_AVX2_Y15(1, 12, 8, 13) + +#define LOAD_MSG_AVX2_2_6_0_8_12_10_11_3_4_7_15_1_13_5_14_9() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X11_0; \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X11(8*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(12, 10, 11, 3); \ + LOAD_MSG_AVX2_Y14(4, 7, 15, 1); \ + LOAD_MSG_AVX2_Y15(13, 5, 14, 9) + +#define LOAD_MSG_AVX2_12_1_14_4_5_15_13_10_0_6_9_8_7_3_2_11() \ + LOAD_MSG_AVX2_Y12(12, 1, 14, 4); \ + LOAD_MSG_AVX2_Y13(5, 15, 13, 10); \ + VMOVQ_SI_X14_0; \ + VPSHUFD $0x4E, 8*8(SI), X11; \ + VPINSRQ_1_SI_X14(6*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + LOAD_MSG_AVX2_Y15(7, 3, 2, 11) + +#define LOAD_MSG_AVX2_13_7_12_3_11_14_1_9_5_15_8_2_0_4_6_10() \ + LOAD_MSG_AVX2_Y12(13, 7, 12, 3); \ + LOAD_MSG_AVX2_Y13(11, 14, 1, 9); \ + LOAD_MSG_AVX2_Y14(5, 15, 8, 2); \ + VMOVQ_SI_X15_0; \ + VMOVQ_SI_X11(6*8); \ + VPINSRQ_1_SI_X15(4*8); \ + VPINSRQ_1_SI_X11(10*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_6_14_11_0_15_9_3_8_12_13_1_10_2_7_4_5() \ + VMOVQ_SI_X12(6*8); \ + VMOVQ_SI_X11(11*8); \ + VPINSRQ_1_SI_X12(14*8); \ + VPINSRQ_1_SI_X11_0; \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(15, 9, 3, 8); \ + VMOVQ_SI_X11(1*8); \ + VMOVDQU 12*8(SI), X14; \ + VPINSRQ_1_SI_X11(10*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + VMOVQ_SI_X15(2*8); \ + VMOVDQU 4*8(SI), X11; \ + VPINSRQ_1_SI_X15(7*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0() \ + LOAD_MSG_AVX2_Y12(10, 8, 7, 1); \ + VMOVQ_SI_X13(2*8); \ + VPSHUFD $0x4E, 5*8(SI), X11; \ + VPINSRQ_1_SI_X13(4*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(15, 9, 3, 13); \ + VMOVQ_SI_X15(11*8); \ + VMOVQ_SI_X11(12*8); \ + VPINSRQ_1_SI_X15(14*8); \ + VPINSRQ_1_SI_X11_0; \ + VINSERTI128 $1, X11, Y15, Y15 + +// func fAVX2(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) +TEXT ·fAVX2(SB), 4, $64-48 // frame size = 32 + 32 byte alignment + MOVQ h+0(FP), AX + MOVQ m+8(FP), SI + MOVQ c0+16(FP), R8 + MOVQ c1+24(FP), R9 + MOVQ flag+32(FP), CX + MOVQ rounds+40(FP), BX + + MOVQ SP, DX + MOVQ SP, R10 + ADDQ $31, R10 + ANDQ $~31, R10 + MOVQ R10, SP + + MOVQ CX, 16(SP) + XORQ CX, CX + MOVQ CX, 24(SP) + + VMOVDQU ·AVX2_c40<>(SB), Y4 + VMOVDQU ·AVX2_c48<>(SB), Y5 + + VMOVDQU 0(AX), Y8 + VMOVDQU 32(AX), Y9 + VMOVDQU ·AVX2_iv0<>(SB), Y6 + VMOVDQU ·AVX2_iv1<>(SB), Y7 + + MOVQ R8, 0(SP) + MOVQ R9, 8(SP) + + VMOVDQA Y8, Y0 + VMOVDQA Y9, Y1 + VMOVDQA Y6, Y2 + VPXOR 0(SP), Y7, Y3 + +loop: + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_7_3_13_11_9_1_12_14_2_5_4_15_6_10_0_8() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_9_5_2_10_0_7_4_15_14_11_6_3_1_12_8_13() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_2_6_0_8_12_10_11_3_4_7_15_1_13_5_14_9() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_12_1_14_4_5_15_13_10_0_6_9_8_7_3_2_11() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_13_7_12_3_11_14_1_9_5_15_8_2_0_4_6_10() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_6_14_11_0_15_9_3_8_12_13_1_10_2_7_4_5() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + JMP loop + +done: + VPXOR Y0, Y8, Y8 + VPXOR Y1, Y9, Y9 + VPXOR Y2, Y8, Y8 + VPXOR Y3, Y9, Y9 + + VMOVDQU Y8, 0(AX) + VMOVDQU Y9, 32(AX) + VZEROUPPER + + MOVQ DX, SP + RET + +#define VPUNPCKLQDQ_X2_X2_X15 BYTE $0xC5; BYTE $0x69; BYTE $0x6C; BYTE $0xFA +#define VPUNPCKLQDQ_X3_X3_X15 BYTE $0xC5; BYTE $0x61; BYTE $0x6C; BYTE $0xFB +#define VPUNPCKLQDQ_X7_X7_X15 BYTE $0xC5; BYTE $0x41; BYTE $0x6C; BYTE $0xFF +#define VPUNPCKLQDQ_X13_X13_X15 BYTE $0xC4; BYTE $0x41; BYTE $0x11; BYTE $0x6C; BYTE $0xFD +#define VPUNPCKLQDQ_X14_X14_X15 BYTE $0xC4; BYTE $0x41; BYTE $0x09; BYTE $0x6C; BYTE $0xFE + +#define VPUNPCKHQDQ_X15_X2_X2 BYTE $0xC4; BYTE $0xC1; BYTE $0x69; BYTE $0x6D; BYTE $0xD7 +#define VPUNPCKHQDQ_X15_X3_X3 BYTE $0xC4; BYTE $0xC1; BYTE $0x61; BYTE $0x6D; BYTE $0xDF +#define VPUNPCKHQDQ_X15_X6_X6 BYTE $0xC4; BYTE $0xC1; BYTE $0x49; BYTE $0x6D; BYTE $0xF7 +#define VPUNPCKHQDQ_X15_X7_X7 BYTE $0xC4; BYTE $0xC1; BYTE $0x41; BYTE $0x6D; BYTE $0xFF +#define VPUNPCKHQDQ_X15_X3_X2 BYTE $0xC4; BYTE $0xC1; BYTE $0x61; BYTE $0x6D; BYTE $0xD7 +#define VPUNPCKHQDQ_X15_X7_X6 BYTE $0xC4; BYTE $0xC1; BYTE $0x41; BYTE $0x6D; BYTE $0xF7 +#define VPUNPCKHQDQ_X15_X13_X3 BYTE $0xC4; BYTE $0xC1; BYTE $0x11; BYTE $0x6D; BYTE $0xDF +#define VPUNPCKHQDQ_X15_X13_X7 BYTE $0xC4; BYTE $0xC1; BYTE $0x11; BYTE $0x6D; BYTE $0xFF + +#define SHUFFLE_AVX() \ + VMOVDQA X6, X13; \ + VMOVDQA X2, X14; \ + VMOVDQA X4, X6; \ + VPUNPCKLQDQ_X13_X13_X15; \ + VMOVDQA X5, X4; \ + VMOVDQA X6, X5; \ + VPUNPCKHQDQ_X15_X7_X6; \ + VPUNPCKLQDQ_X7_X7_X15; \ + VPUNPCKHQDQ_X15_X13_X7; \ + VPUNPCKLQDQ_X3_X3_X15; \ + VPUNPCKHQDQ_X15_X2_X2; \ + VPUNPCKLQDQ_X14_X14_X15; \ + VPUNPCKHQDQ_X15_X3_X3; \ + +#define SHUFFLE_AVX_INV() \ + VMOVDQA X2, X13; \ + VMOVDQA X4, X14; \ + VPUNPCKLQDQ_X2_X2_X15; \ + VMOVDQA X5, X4; \ + VPUNPCKHQDQ_X15_X3_X2; \ + VMOVDQA X14, X5; \ + VPUNPCKLQDQ_X3_X3_X15; \ + VMOVDQA X6, X14; \ + VPUNPCKHQDQ_X15_X13_X3; \ + VPUNPCKLQDQ_X7_X7_X15; \ + VPUNPCKHQDQ_X15_X6_X6; \ + VPUNPCKLQDQ_X14_X14_X15; \ + VPUNPCKHQDQ_X15_X7_X7; \ + +#define HALF_ROUND_AVX(v0, v1, v2, v3, v4, v5, v6, v7, m0, m1, m2, m3, t0, c40, c48) \ + VPADDQ m0, v0, v0; \ + VPADDQ v2, v0, v0; \ + VPADDQ m1, v1, v1; \ + VPADDQ v3, v1, v1; \ + VPXOR v0, v6, v6; \ + VPXOR v1, v7, v7; \ + VPSHUFD $-79, v6, v6; \ + VPSHUFD $-79, v7, v7; \ + VPADDQ v6, v4, v4; \ + VPADDQ v7, v5, v5; \ + VPXOR v4, v2, v2; \ + VPXOR v5, v3, v3; \ + VPSHUFB c40, v2, v2; \ + VPSHUFB c40, v3, v3; \ + VPADDQ m2, v0, v0; \ + VPADDQ v2, v0, v0; \ + VPADDQ m3, v1, v1; \ + VPADDQ v3, v1, v1; \ + VPXOR v0, v6, v6; \ + VPXOR v1, v7, v7; \ + VPSHUFB c48, v6, v6; \ + VPSHUFB c48, v7, v7; \ + VPADDQ v6, v4, v4; \ + VPADDQ v7, v5, v5; \ + VPXOR v4, v2, v2; \ + VPXOR v5, v3, v3; \ + VPADDQ v2, v2, t0; \ + VPSRLQ $63, v2, v2; \ + VPXOR t0, v2, v2; \ + VPADDQ v3, v3, t0; \ + VPSRLQ $63, v3, v3; \ + VPXOR t0, v3, v3 + +// load msg: X12 = (i0, i1), X13 = (i2, i3), X14 = (i4, i5), X15 = (i6, i7) +// i0, i1, i2, i3, i4, i5, i6, i7 must not be 0 +#define LOAD_MSG_AVX(i0, i1, i2, i3, i4, i5, i6, i7) \ + VMOVQ_SI_X12(i0*8); \ + VMOVQ_SI_X13(i2*8); \ + VMOVQ_SI_X14(i4*8); \ + VMOVQ_SI_X15(i6*8); \ + VPINSRQ_1_SI_X12(i1*8); \ + VPINSRQ_1_SI_X13(i3*8); \ + VPINSRQ_1_SI_X14(i5*8); \ + VPINSRQ_1_SI_X15(i7*8) + +// load msg: X12 = (0, 2), X13 = (4, 6), X14 = (1, 3), X15 = (5, 7) +#define LOAD_MSG_AVX_0_2_4_6_1_3_5_7() \ + VMOVQ_SI_X12_0; \ + VMOVQ_SI_X13(4*8); \ + VMOVQ_SI_X14(1*8); \ + VMOVQ_SI_X15(5*8); \ + VPINSRQ_1_SI_X12(2*8); \ + VPINSRQ_1_SI_X13(6*8); \ + VPINSRQ_1_SI_X14(3*8); \ + VPINSRQ_1_SI_X15(7*8) + +// load msg: X12 = (1, 0), X13 = (11, 5), X14 = (12, 2), X15 = (7, 3) +#define LOAD_MSG_AVX_1_0_11_5_12_2_7_3() \ + VPSHUFD $0x4E, 0*8(SI), X12; \ + VMOVQ_SI_X13(11*8); \ + VMOVQ_SI_X14(12*8); \ + VMOVQ_SI_X15(7*8); \ + VPINSRQ_1_SI_X13(5*8); \ + VPINSRQ_1_SI_X14(2*8); \ + VPINSRQ_1_SI_X15(3*8) + +// load msg: X12 = (11, 12), X13 = (5, 15), X14 = (8, 0), X15 = (2, 13) +#define LOAD_MSG_AVX_11_12_5_15_8_0_2_13() \ + VMOVDQU 11*8(SI), X12; \ + VMOVQ_SI_X13(5*8); \ + VMOVQ_SI_X14(8*8); \ + VMOVQ_SI_X15(2*8); \ + VPINSRQ_1_SI_X13(15*8); \ + VPINSRQ_1_SI_X14_0; \ + VPINSRQ_1_SI_X15(13*8) + +// load msg: X12 = (2, 5), X13 = (4, 15), X14 = (6, 10), X15 = (0, 8) +#define LOAD_MSG_AVX_2_5_4_15_6_10_0_8() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X13(4*8); \ + VMOVQ_SI_X14(6*8); \ + VMOVQ_SI_X15_0; \ + VPINSRQ_1_SI_X12(5*8); \ + VPINSRQ_1_SI_X13(15*8); \ + VPINSRQ_1_SI_X14(10*8); \ + VPINSRQ_1_SI_X15(8*8) + +// load msg: X12 = (9, 5), X13 = (2, 10), X14 = (0, 7), X15 = (4, 15) +#define LOAD_MSG_AVX_9_5_2_10_0_7_4_15() \ + VMOVQ_SI_X12(9*8); \ + VMOVQ_SI_X13(2*8); \ + VMOVQ_SI_X14_0; \ + VMOVQ_SI_X15(4*8); \ + VPINSRQ_1_SI_X12(5*8); \ + VPINSRQ_1_SI_X13(10*8); \ + VPINSRQ_1_SI_X14(7*8); \ + VPINSRQ_1_SI_X15(15*8) + +// load msg: X12 = (2, 6), X13 = (0, 8), X14 = (12, 10), X15 = (11, 3) +#define LOAD_MSG_AVX_2_6_0_8_12_10_11_3() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X13_0; \ + VMOVQ_SI_X14(12*8); \ + VMOVQ_SI_X15(11*8); \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X13(8*8); \ + VPINSRQ_1_SI_X14(10*8); \ + VPINSRQ_1_SI_X15(3*8) + +// load msg: X12 = (0, 6), X13 = (9, 8), X14 = (7, 3), X15 = (2, 11) +#define LOAD_MSG_AVX_0_6_9_8_7_3_2_11() \ + MOVQ 0*8(SI), X12; \ + VPSHUFD $0x4E, 8*8(SI), X13; \ + MOVQ 7*8(SI), X14; \ + MOVQ 2*8(SI), X15; \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X14(3*8); \ + VPINSRQ_1_SI_X15(11*8) + +// load msg: X12 = (6, 14), X13 = (11, 0), X14 = (15, 9), X15 = (3, 8) +#define LOAD_MSG_AVX_6_14_11_0_15_9_3_8() \ + MOVQ 6*8(SI), X12; \ + MOVQ 11*8(SI), X13; \ + MOVQ 15*8(SI), X14; \ + MOVQ 3*8(SI), X15; \ + VPINSRQ_1_SI_X12(14*8); \ + VPINSRQ_1_SI_X13_0; \ + VPINSRQ_1_SI_X14(9*8); \ + VPINSRQ_1_SI_X15(8*8) + +// load msg: X12 = (5, 15), X13 = (8, 2), X14 = (0, 4), X15 = (6, 10) +#define LOAD_MSG_AVX_5_15_8_2_0_4_6_10() \ + MOVQ 5*8(SI), X12; \ + MOVQ 8*8(SI), X13; \ + MOVQ 0*8(SI), X14; \ + MOVQ 6*8(SI), X15; \ + VPINSRQ_1_SI_X12(15*8); \ + VPINSRQ_1_SI_X13(2*8); \ + VPINSRQ_1_SI_X14(4*8); \ + VPINSRQ_1_SI_X15(10*8) + +// load msg: X12 = (12, 13), X13 = (1, 10), X14 = (2, 7), X15 = (4, 5) +#define LOAD_MSG_AVX_12_13_1_10_2_7_4_5() \ + VMOVDQU 12*8(SI), X12; \ + MOVQ 1*8(SI), X13; \ + MOVQ 2*8(SI), X14; \ + VPINSRQ_1_SI_X13(10*8); \ + VPINSRQ_1_SI_X14(7*8); \ + VMOVDQU 4*8(SI), X15 + +// load msg: X12 = (15, 9), X13 = (3, 13), X14 = (11, 14), X15 = (12, 0) +#define LOAD_MSG_AVX_15_9_3_13_11_14_12_0() \ + MOVQ 15*8(SI), X12; \ + MOVQ 3*8(SI), X13; \ + MOVQ 11*8(SI), X14; \ + MOVQ 12*8(SI), X15; \ + VPINSRQ_1_SI_X12(9*8); \ + VPINSRQ_1_SI_X13(13*8); \ + VPINSRQ_1_SI_X14(14*8); \ + VPINSRQ_1_SI_X15_0 + +// func fAVX(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) +TEXT ·fAVX(SB), 4, $24-48 // frame size = 8 + 16 byte alignment + MOVQ h+0(FP), AX + MOVQ m+8(FP), SI + MOVQ c0+16(FP), R8 + MOVQ c1+24(FP), R9 + MOVQ flag+32(FP), CX + MOVQ rounds+40(FP), BX + + MOVQ SP, BP + MOVQ SP, R10 + ADDQ $15, R10 + ANDQ $~15, R10 + MOVQ R10, SP + + VMOVDQU ·AVX_c40<>(SB), X0 + VMOVDQU ·AVX_c48<>(SB), X1 + VMOVDQA X0, X8 + VMOVDQA X1, X9 + + VMOVDQU ·AVX_iv3<>(SB), X0 + VMOVDQA X0, 0(SP) + XORQ CX, 0(SP) // 0(SP) = ·AVX_iv3 ^ (CX || 0) + + VMOVDQU 0(AX), X10 + VMOVDQU 16(AX), X11 + VMOVDQU 32(AX), X2 + VMOVDQU 48(AX), X3 + + VMOVQ_R8_X15 + VPINSRQ_1_R9_X15 + + VMOVDQA X10, X0 + VMOVDQA X11, X1 + VMOVDQU ·AVX_iv0<>(SB), X4 + VMOVDQU ·AVX_iv1<>(SB), X5 + VMOVDQU ·AVX_iv2<>(SB), X6 + + VPXOR X15, X6, X6 + VMOVDQA 0(SP), X7 + +loop: + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_0_2_4_6_1_3_5_7() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(8, 10, 12, 14, 9, 11, 13, 15) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(14, 4, 9, 13, 10, 8, 15, 6) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_1_0_11_5_12_2_7_3() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_11_12_5_15_8_0_2_13() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(10, 3, 7, 9, 14, 6, 1, 4) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(7, 3, 13, 11, 9, 1, 12, 14) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_2_5_4_15_6_10_0_8() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_9_5_2_10_0_7_4_15() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(14, 11, 6, 3, 1, 12, 8, 13) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_2_6_0_8_12_10_11_3() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(4, 7, 15, 1, 13, 5, 14, 9) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(12, 1, 14, 4, 5, 15, 13, 10) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_0_6_9_8_7_3_2_11() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(13, 7, 12, 3, 11, 14, 1, 9) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_5_15_8_2_0_4_6_10() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX_6_14_11_0_15_9_3_8() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_12_13_1_10_2_7_4_5() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + SUBQ $1, BX; JCS done + LOAD_MSG_AVX(10, 8, 7, 1, 2, 4, 6, 5) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_15_9_3_13_11_14_12_0() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + JMP loop + +done: + VMOVDQU 32(AX), X14 + VMOVDQU 48(AX), X15 + VPXOR X0, X10, X10 + VPXOR X1, X11, X11 + VPXOR X2, X14, X14 + VPXOR X3, X15, X15 + VPXOR X4, X10, X10 + VPXOR X5, X11, X11 + VPXOR X6, X14, X2 + VPXOR X7, X15, X3 + VMOVDQU X2, 32(AX) + VMOVDQU X3, 48(AX) + + VMOVDQU X10, 0(AX) + VMOVDQU X11, 16(AX) + VZEROUPPER + + MOVQ BP, SP + RET diff --git a/crypto/blake2b/blake2b_amd64.go b/crypto/blake2b/blake2b_amd64.go new file mode 100644 index 0000000..a318b2b --- /dev/null +++ b/crypto/blake2b/blake2b_amd64.go @@ -0,0 +1,25 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.7 && amd64 && !gccgo && !appengine +// +build !go1.7,amd64,!gccgo,!appengine + +package blake2b + +import "golang.org/x/sys/cpu" + +func init() { + useSSE4 = cpu.X86.HasSSE41 +} + +//go:noescape +func fSSE4(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) + +func f(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) { + if useSSE4 { + fSSE4(h, m, c0, c1, flag, rounds) + } else { + fGeneric(h, m, c0, c1, flag, rounds) + } +} diff --git a/crypto/blake2b/blake2b_amd64.s b/crypto/blake2b/blake2b_amd64.s new file mode 100644 index 0000000..ce4b56d --- /dev/null +++ b/crypto/blake2b/blake2b_amd64.s @@ -0,0 +1,253 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64,!gccgo,!appengine + +#include "textflag.h" + +DATA ·iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +GLOBL ·iv0<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv1<>+0x00(SB)/8, $0x3c6ef372fe94f82b +DATA ·iv1<>+0x08(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·iv1<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv2<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·iv2<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·iv2<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv3<>+0x00(SB)/8, $0x1f83d9abfb41bd6b +DATA ·iv3<>+0x08(SB)/8, $0x5be0cd19137e2179 +GLOBL ·iv3<>(SB), (NOPTR+RODATA), $16 + +DATA ·c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·c40<>(SB), (NOPTR+RODATA), $16 + +DATA ·c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·c48<>(SB), (NOPTR+RODATA), $16 + +#define SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2) \ + MOVO v4, t1; \ + MOVO v5, v4; \ + MOVO t1, v5; \ + MOVO v6, t1; \ + PUNPCKLQDQ v6, t2; \ + PUNPCKHQDQ v7, v6; \ + PUNPCKHQDQ t2, v6; \ + PUNPCKLQDQ v7, t2; \ + MOVO t1, v7; \ + MOVO v2, t1; \ + PUNPCKHQDQ t2, v7; \ + PUNPCKLQDQ v3, t2; \ + PUNPCKHQDQ t2, v2; \ + PUNPCKLQDQ t1, t2; \ + PUNPCKHQDQ t2, v3 + +#define SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2) \ + MOVO v4, t1; \ + MOVO v5, v4; \ + MOVO t1, v5; \ + MOVO v2, t1; \ + PUNPCKLQDQ v2, t2; \ + PUNPCKHQDQ v3, v2; \ + PUNPCKHQDQ t2, v2; \ + PUNPCKLQDQ v3, t2; \ + MOVO t1, v3; \ + MOVO v6, t1; \ + PUNPCKHQDQ t2, v3; \ + PUNPCKLQDQ v7, t2; \ + PUNPCKHQDQ t2, v6; \ + PUNPCKLQDQ t1, t2; \ + PUNPCKHQDQ t2, v7 + +#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, m0, m1, m2, m3, t0, c40, c48) \ + PADDQ m0, v0; \ + PADDQ m1, v1; \ + PADDQ v2, v0; \ + PADDQ v3, v1; \ + PXOR v0, v6; \ + PXOR v1, v7; \ + PSHUFD $0xB1, v6, v6; \ + PSHUFD $0xB1, v7, v7; \ + PADDQ v6, v4; \ + PADDQ v7, v5; \ + PXOR v4, v2; \ + PXOR v5, v3; \ + PSHUFB c40, v2; \ + PSHUFB c40, v3; \ + PADDQ m2, v0; \ + PADDQ m3, v1; \ + PADDQ v2, v0; \ + PADDQ v3, v1; \ + PXOR v0, v6; \ + PXOR v1, v7; \ + PSHUFB c48, v6; \ + PSHUFB c48, v7; \ + PADDQ v6, v4; \ + PADDQ v7, v5; \ + PXOR v4, v2; \ + PXOR v5, v3; \ + MOVOU v2, t0; \ + PADDQ v2, t0; \ + PSRLQ $63, v2; \ + PXOR t0, v2; \ + MOVOU v3, t0; \ + PADDQ v3, t0; \ + PSRLQ $63, v3; \ + PXOR t0, v3 + +#define LOAD_MSG(m0, m1, m2, m3, i0, i1, i2, i3, i4, i5, i6, i7) \ + MOVQ i0*8(SI), m0; \ + PINSRQ $1, i1*8(SI), m0; \ + MOVQ i2*8(SI), m1; \ + PINSRQ $1, i3*8(SI), m1; \ + MOVQ i4*8(SI), m2; \ + PINSRQ $1, i5*8(SI), m2; \ + MOVQ i6*8(SI), m3; \ + PINSRQ $1, i7*8(SI), m3 + +// func fSSE4(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) +TEXT ·fSSE4(SB), 4, $24-48 // frame size = 8 + 16 byte alignment + MOVQ h+0(FP), AX + MOVQ m+8(FP), SI + MOVQ c0+16(FP), R8 + MOVQ c1+24(FP), R9 + MOVQ flag+32(FP), CX + MOVQ rounds+40(FP), BX + + MOVQ SP, BP + MOVQ SP, R10 + ADDQ $15, R10 + ANDQ $~15, R10 + MOVQ R10, SP + + MOVOU ·iv3<>(SB), X0 + MOVO X0, 0(SP) + XORQ CX, 0(SP) // 0(SP) = ·iv3 ^ (CX || 0) + + MOVOU ·c40<>(SB), X13 + MOVOU ·c48<>(SB), X14 + + MOVOU 0(AX), X12 + MOVOU 16(AX), X15 + + MOVQ R8, X8 + PINSRQ $1, R9, X8 + + MOVO X12, X0 + MOVO X15, X1 + MOVOU 32(AX), X2 + MOVOU 48(AX), X3 + MOVOU ·iv0<>(SB), X4 + MOVOU ·iv1<>(SB), X5 + MOVOU ·iv2<>(SB), X6 + + PXOR X8, X6 + MOVO 0(SP), X7 + +loop: + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 0, 2, 4, 6, 1, 3, 5, 7) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 8, 10, 12, 14, 9, 11, 13, 15) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 14, 4, 9, 13, 10, 8, 15, 6) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 1, 0, 11, 5, 12, 2, 7, 3) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 11, 12, 5, 15, 8, 0, 2, 13) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 10, 3, 7, 9, 14, 6, 1, 4) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 7, 3, 13, 11, 9, 1, 12, 14) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 2, 5, 4, 15, 6, 10, 0, 8) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 9, 5, 2, 10, 0, 7, 4, 15) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 14, 11, 6, 3, 1, 12, 8, 13) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 2, 6, 0, 8, 12, 10, 11, 3) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 4, 7, 15, 1, 13, 5, 14, 9) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 12, 1, 14, 4, 5, 15, 13, 10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 0, 6, 9, 8, 7, 3, 2, 11) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 13, 7, 12, 3, 11, 14, 1, 9) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 5, 15, 8, 2, 0, 4, 6, 10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 6, 14, 11, 0, 15, 9, 3, 8) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 12, 13, 1, 10, 2, 7, 4, 5) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + SUBQ $1, BX; JCS done + LOAD_MSG(X8, X9, X10, X11, 10, 8, 7, 1, 2, 4, 6, 5) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, 15, 9, 3, 13, 11, 14, 12, 0) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + JMP loop + +done: + MOVOU 32(AX), X10 + MOVOU 48(AX), X11 + PXOR X0, X12 + PXOR X1, X15 + PXOR X2, X10 + PXOR X3, X11 + PXOR X4, X12 + PXOR X5, X15 + PXOR X6, X10 + PXOR X7, X11 + MOVOU X10, 32(AX) + MOVOU X11, 48(AX) + + MOVOU X12, 0(AX) + MOVOU X15, 16(AX) + + MOVQ BP, SP + RET diff --git a/crypto/blake2b/blake2b_f_fuzz_test.go b/crypto/blake2b/blake2b_f_fuzz_test.go new file mode 100644 index 0000000..1de9a62 --- /dev/null +++ b/crypto/blake2b/blake2b_f_fuzz_test.go @@ -0,0 +1,75 @@ +// 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(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 + } + // Parse everything and call all the implementations + var ( + rounds = binary.BigEndian.Uint16(data[0:2]) + + h [8]uint64 + m [16]uint64 + t [2]uint64 + f uint64 + ) + + for i := 0; i < 8; i++ { + offset := 2 + i*8 + h[i] = binary.LittleEndian.Uint64(data[offset : offset+8]) + } + for i := 0; i < 16; i++ { + offset := 66 + i*8 + m[i] = binary.LittleEndian.Uint64(data[offset : offset+8]) + } + t[0] = binary.LittleEndian.Uint64(data[194:202]) + t[1] = binary.LittleEndian.Uint64(data[202:210]) + + 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 + if useSSE4 { + fSSE4(&have, &m, t[0], t[1], f, uint64(rounds)) + if have != want { + panic("SSE4 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") + } + } + + if useAVX2 { + have = h + fAVX2(&have, &m, t[0], t[1], f, uint64(rounds)) + if have != want { + panic("AVX2 mismatches generic algo") + } + } +} diff --git a/crypto/blake2b/blake2b_f_test.go b/crypto/blake2b/blake2b_f_test.go new file mode 100644 index 0000000..4e07d13 --- /dev/null +++ b/crypto/blake2b/blake2b_f_test.go @@ -0,0 +1,59 @@ +package blake2b + +import ( + "fmt" + "reflect" + "testing" +) + +func TestF(t *testing.T) { + for i, test := range testVectorsF { + t.Run(fmt.Sprintf("test vector %v", i), func(t *testing.T) { + //toEthereumTestCase(test) + + h := test.hIn + F(&h, test.m, test.c, test.f, test.rounds) + + if !reflect.DeepEqual(test.hOut, h) { + t.Errorf("Unexpected result\nExpected: [%#x]\nActual: [%#x]\n", test.hOut, h) + } + }) + } +} + +type testVector struct { + hIn [8]uint64 + m [16]uint64 + c [2]uint64 + f bool + rounds uint32 + hOut [8]uint64 +} + +// https://tools.ietf.org/html/rfc7693#appendix-A +var testVectorsF = []testVector{ + { + hIn: [8]uint64{ + 0x6a09e667f2bdc948, 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, + }, + m: [16]uint64{ + 0x0000000000636261, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, + 0x0000000000000000, + }, + c: [2]uint64{3, 0}, + f: true, + rounds: 12, + hOut: [8]uint64{ + 0x0D4D1C983FA580BA, 0xE9F6129FB697276A, 0xB7C45A68142F214C, + 0xD1A2FFDB6FBB124B, 0x2D79AB2A39C5877D, 0x95CC3345DED552C2, + 0x5A92F1DBA88AD318, 0x239900D4ED8623B9, + }, + }, +} diff --git a/crypto/blake2b/blake2b_generic.go b/crypto/blake2b/blake2b_generic.go new file mode 100644 index 0000000..61e678f --- /dev/null +++ b/crypto/blake2b/blake2b_generic.go @@ -0,0 +1,181 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "encoding/binary" + "math/bits" +) + +// the precomputed values for BLAKE2b +// there are 10 16-byte arrays - one for each round +// the entries are calculated from the sigma constants. +var precomputed = [10][16]byte{ + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, + {11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4}, + {7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8}, + {9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13}, + {2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9}, + {12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11}, + {13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10}, + {6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5}, + {10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0}, +} + +// nolint:unused,deadcode +func hashBlocksGeneric(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + var m [16]uint64 + c0, c1 := c[0], c[1] + + for i := 0; i < len(blocks); { + c0 += BlockSize + if c0 < BlockSize { + c1++ + } + for j := range m { + m[j] = binary.LittleEndian.Uint64(blocks[i:]) + i += 8 + } + fGeneric(h, &m, c0, c1, flag, 12) + } + c[0], c[1] = c0, c1 +} + +func fGeneric(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) { + v0, v1, v2, v3, v4, v5, v6, v7 := h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7] + v8, v9, v10, v11, v12, v13, v14, v15 := iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7] + v12 ^= c0 + v13 ^= c1 + v14 ^= flag + + for i := 0; i < int(rounds); i++ { + s := &(precomputed[i%10]) + + v0 += m[s[0]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -32) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -24) + v1 += m[s[1]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -32) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -24) + v2 += m[s[2]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -32) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -24) + v3 += m[s[3]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -32) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -24) + + v0 += m[s[4]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -16) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -63) + v1 += m[s[5]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -16) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -63) + v2 += m[s[6]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -16) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -63) + v3 += m[s[7]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -16) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -63) + + v0 += m[s[8]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -32) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -24) + v1 += m[s[9]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -32) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -24) + v2 += m[s[10]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -32) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -24) + v3 += m[s[11]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -32) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -24) + + v0 += m[s[12]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -16) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -63) + v1 += m[s[13]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -16) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -63) + v2 += m[s[14]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -16) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -63) + v3 += m[s[15]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -16) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -63) + } + h[0] ^= v0 ^ v8 + h[1] ^= v1 ^ v9 + h[2] ^= v2 ^ v10 + h[3] ^= v3 ^ v11 + h[4] ^= v4 ^ v12 + h[5] ^= v5 ^ v13 + h[6] ^= v6 ^ v14 + h[7] ^= v7 ^ v15 +} diff --git a/crypto/blake2b/blake2b_ref.go b/crypto/blake2b/blake2b_ref.go new file mode 100644 index 0000000..095c71a --- /dev/null +++ b/crypto/blake2b/blake2b_ref.go @@ -0,0 +1,12 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !amd64 || appengine || gccgo +// +build !amd64 appengine gccgo + +package blake2b + +func f(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds uint64) { + fGeneric(h, m, c0, c1, flag, rounds) +} diff --git a/crypto/blake2b/blake2b_test.go b/crypto/blake2b/blake2b_test.go new file mode 100644 index 0000000..9d24444 --- /dev/null +++ b/crypto/blake2b/blake2b_test.go @@ -0,0 +1,863 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "bytes" + "encoding" + "encoding/hex" + "fmt" + "hash" + "io" + "testing" +) + +func TestHashes(t *testing.T) { + defer func(sse4, avx, avx2 bool) { + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + }(useSSE4, useAVX, useAVX2) + + if useAVX2 { + t.Log("AVX2 version") + testHashes(t) + useAVX2 = false + } + if useAVX { + t.Log("AVX version") + testHashes(t) + useAVX = false + } + if useSSE4 { + t.Log("SSE4 version") + testHashes(t) + useSSE4 = false + } + t.Log("generic version") + testHashes(t) +} + +func TestHashes2X(t *testing.T) { + defer func(sse4, avx, avx2 bool) { + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + }(useSSE4, useAVX, useAVX2) + + if useAVX2 { + t.Log("AVX2 version") + testHashes2X(t) + useAVX2 = false + } + if useAVX { + t.Log("AVX version") + testHashes2X(t) + useAVX = false + } + if useSSE4 { + t.Log("SSE4 version") + testHashes2X(t) + useSSE4 = false + } + t.Log("generic version") + testHashes2X(t) +} + +func TestMarshal(t *testing.T) { + input := make([]byte, 255) + for i := range input { + input[i] = byte(i) + } + for _, size := range []int{Size, Size256, Size384, 12, 25, 63} { + for i := 0; i < 256; i++ { + h, err := New(size, nil) + if err != nil { + t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) + } + h2, err := New(size, nil) + if err != nil { + t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) + } + + h.Write(input[:i/2]) + halfstate, err := h.(encoding.BinaryMarshaler).MarshalBinary() + if err != nil { + t.Fatalf("size=%d, len(input)=%d: could not marshal: %v", size, i, err) + } + err = h2.(encoding.BinaryUnmarshaler).UnmarshalBinary(halfstate) + if err != nil { + t.Fatalf("size=%d, len(input)=%d: could not unmarshal: %v", size, i, err) + } + + h.Write(input[i/2 : i]) + sum := h.Sum(nil) + h2.Write(input[i/2 : i]) + sum2 := h2.Sum(nil) + + if !bytes.Equal(sum, sum2) { + t.Fatalf("size=%d, len(input)=%d: results do not match; sum = %v, sum2 = %v", size, i, sum, sum2) + } + + h3, err := New(size, nil) + if err != nil { + t.Fatalf("size=%d, len(input)=%d: error from New(%v, nil): %v", size, i, size, err) + } + h3.Write(input[:i]) + sum3 := h3.Sum(nil) + if !bytes.Equal(sum, sum3) { + t.Fatalf("size=%d, len(input)=%d: sum = %v, want %v", size, i, sum, sum3) + } + } + } +} + +func testHashes(t *testing.T) { + key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f") + + input := make([]byte, 255) + for i := range input { + input[i] = byte(i) + } + + for i, expectedHex := range hashes { + h, err := New512(key) + if err != nil { + t.Fatalf("#%d: error from New512: %v", i, err) + } + + h.Write(input[:i]) + sum := h.Sum(nil) + + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { + t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex) + } + + h.Reset() + for j := 0; j < i; j++ { + h.Write(input[j : j+1]) + } + + sum = h.Sum(sum[:0]) + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { + t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex) + } + } +} + +func testHashes2X(t *testing.T) { + key, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f") + + input := make([]byte, 256) + for i := range input { + input[i] = byte(i) + } + + for i, expectedHex := range hashes2X { + length := uint32(len(expectedHex) / 2) + sum := make([]byte, int(length)) + + h, err := NewXOF(length, key) + if err != nil { + t.Fatalf("#%d: error from NewXOF: %v", i, err) + } + + if _, err := h.Write(input); err != nil { + t.Fatalf("#%d (single write): error from Write: %v", i, err) + } + if _, err := h.Read(sum); err != nil { + t.Fatalf("#%d (single write): error from Read: %v", i, err) + } + if n, err := h.Read(sum); n != 0 || err != io.EOF { + t.Fatalf("#%d (single write): Read did not return (0, io.EOF) after exhaustion, got (%v, %v)", i, n, err) + } + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { + t.Fatalf("#%d (single write): got %s, wanted %s", i, gotHex, expectedHex) + } + + h.Reset() + for j := 0; j < len(input); j++ { + h.Write(input[j : j+1]) + } + for j := 0; j < len(sum); j++ { + h = h.Clone() + if _, err := h.Read(sum[j : j+1]); err != nil { + t.Fatalf("#%d (byte-by-byte) - Read %d: error from Read: %v", i, j, err) + } + } + if gotHex := fmt.Sprintf("%x", sum); gotHex != expectedHex { + t.Fatalf("#%d (byte-by-byte): got %s, wanted %s", i, gotHex, expectedHex) + } + } + + h, err := NewXOF(OutputLengthUnknown, key) + if err != nil { + t.Fatalf("#unknown length: error from NewXOF: %v", err) + } + if _, err := h.Write(input); err != nil { + t.Fatalf("#unknown length: error from Write: %v", err) + } + + var result [64]byte + if n, err := h.Read(result[:]); err != nil { + t.Fatalf("#unknown length: error from Read: %v", err) + } else if n != len(result) { + t.Fatalf("#unknown length: Read returned %d bytes, want %d", n, len(result)) + } + + const expected = "3dbba8516da76bf7330055c66ea36cf1005e92714262b24d9710f51d9e126406e1bcd6497059f9331f1091c3634b695428d475ed432f987040575520a1c29f5e" + if fmt.Sprintf("%x", result) != expected { + t.Fatalf("#unknown length: bad result %x, wanted %s", result, expected) + } +} + +func generateSequence(out []byte, seed uint32) { + a := 0xDEAD4BAD * seed // prime + b := uint32(1) + + for i := range out { // fill the buf + a, b = b, a+b + out[i] = byte(b >> 24) + } +} + +func computeMAC(msg []byte, hashSize int, key []byte) (sum []byte) { + var h hash.Hash + switch hashSize { + case Size: + h, _ = New512(key) + case Size384: + h, _ = New384(key) + case Size256: + h, _ = New256(key) + case 20: + h, _ = newDigest(20, key) + default: + panic("unexpected hashSize") + } + + h.Write(msg) + return h.Sum(sum) +} + +func computeHash(msg []byte, hashSize int) (sum []byte) { + switch hashSize { + case Size: + hash := Sum512(msg) + return hash[:] + case Size384: + hash := Sum384(msg) + return hash[:] + case Size256: + hash := Sum256(msg) + return hash[:] + case 20: + var hash [64]byte + checkSum(&hash, 20, msg) + return hash[:20] + default: + panic("unexpected hashSize") + } +} + +// Test function from RFC 7693. +func TestSelfTest(t *testing.T) { + hashLens := [4]int{20, 32, 48, 64} + msgLens := [6]int{0, 3, 128, 129, 255, 1024} + + msg := make([]byte, 1024) + key := make([]byte, 64) + + h, _ := New256(nil) + for _, hashSize := range hashLens { + for _, msgLength := range msgLens { + generateSequence(msg[:msgLength], uint32(msgLength)) // unkeyed hash + + md := computeHash(msg[:msgLength], hashSize) + h.Write(md) + + generateSequence(key[:], uint32(hashSize)) // keyed hash + md = computeMAC(msg[:msgLength], hashSize, key[:hashSize]) + h.Write(md) + } + } + + sum := h.Sum(nil) + expected := [32]byte{ + 0xc2, 0x3a, 0x78, 0x00, 0xd9, 0x81, 0x23, 0xbd, + 0x10, 0xf5, 0x06, 0xc6, 0x1e, 0x29, 0xda, 0x56, + 0x03, 0xd7, 0x63, 0xb8, 0xbb, 0xad, 0x2e, 0x73, + 0x7f, 0x5e, 0x76, 0x5a, 0x7b, 0xcc, 0xd4, 0x75, + } + if !bytes.Equal(sum, expected[:]) { + t.Fatalf("got %x, wanted %x", sum, expected) + } +} + +// Benchmarks + +func benchmarkSum(b *testing.B, size int, sse4, avx, avx2 bool) { + // Enable the correct set of instructions + defer func(sse4, avx, avx2 bool) { + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + }(useSSE4, useAVX, useAVX2) + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + + data := make([]byte, size) + b.SetBytes(int64(size)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Sum512(data) + } +} + +func benchmarkWrite(b *testing.B, size int, sse4, avx, avx2 bool) { + // Enable the correct set of instructions + defer func(sse4, avx, avx2 bool) { + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + }(useSSE4, useAVX, useAVX2) + useSSE4, useAVX, useAVX2 = sse4, avx, avx2 + + data := make([]byte, size) + h, _ := New512(nil) + b.SetBytes(int64(size)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + h.Write(data) + } +} + +func BenchmarkWrite128Generic(b *testing.B) { benchmarkWrite(b, 128, false, false, false) } +func BenchmarkWrite1KGeneric(b *testing.B) { benchmarkWrite(b, 1024, false, false, false) } +func BenchmarkWrite128SSE4(b *testing.B) { benchmarkWrite(b, 128, true, false, false) } +func BenchmarkWrite1KSSE4(b *testing.B) { benchmarkWrite(b, 1024, true, false, false) } +func BenchmarkWrite128AVX(b *testing.B) { benchmarkWrite(b, 128, false, true, false) } +func BenchmarkWrite1KAVX(b *testing.B) { benchmarkWrite(b, 1024, false, true, false) } +func BenchmarkWrite128AVX2(b *testing.B) { benchmarkWrite(b, 128, false, false, true) } +func BenchmarkWrite1KAVX2(b *testing.B) { benchmarkWrite(b, 1024, false, false, true) } + +func BenchmarkSum128Generic(b *testing.B) { benchmarkSum(b, 128, false, false, false) } +func BenchmarkSum1KGeneric(b *testing.B) { benchmarkSum(b, 1024, false, false, false) } +func BenchmarkSum128SSE4(b *testing.B) { benchmarkSum(b, 128, true, false, false) } +func BenchmarkSum1KSSE4(b *testing.B) { benchmarkSum(b, 1024, true, false, false) } +func BenchmarkSum128AVX(b *testing.B) { benchmarkSum(b, 128, false, true, false) } +func BenchmarkSum1KAVX(b *testing.B) { benchmarkSum(b, 1024, false, true, false) } +func BenchmarkSum128AVX2(b *testing.B) { benchmarkSum(b, 128, false, false, true) } +func BenchmarkSum1KAVX2(b *testing.B) { benchmarkSum(b, 1024, false, false, true) } + +// These values were taken from https://blake2.net/blake2b-test.txt. +var hashes = []string{ + "10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568", + "961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd", + "da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965", + "33d0825dddf7ada99b0e7e307104ad07ca9cfd9692214f1561356315e784f3e5a17e364ae9dbb14cb2036df932b77f4b292761365fb328de7afdc6d8998f5fc1", + "beaa5a3d08f3807143cf621d95cd690514d0b49efff9c91d24b59241ec0eefa5f60196d407048bba8d2146828ebcb0488d8842fd56bb4f6df8e19c4b4daab8ac", + "098084b51fd13deae5f4320de94a688ee07baea2800486689a8636117b46c1f4c1f6af7f74ae7c857600456a58a3af251dc4723a64cc7c0a5ab6d9cac91c20bb", + "6044540d560853eb1c57df0077dd381094781cdb9073e5b1b3d3f6c7829e12066bbaca96d989a690de72ca3133a83652ba284a6d62942b271ffa2620c9e75b1f", + "7a8cfe9b90f75f7ecb3acc053aaed6193112b6f6a4aeeb3f65d3de541942deb9e2228152a3c4bbbe72fc3b12629528cfbb09fe630f0474339f54abf453e2ed52", + "380beaf6ea7cc9365e270ef0e6f3a64fb902acae51dd5512f84259ad2c91f4bc4108db73192a5bbfb0cbcf71e46c3e21aee1c5e860dc96e8eb0b7b8426e6abe9", + "60fe3c4535e1b59d9a61ea8500bfac41a69dffb1ceadd9aca323e9a625b64da5763bad7226da02b9c8c4f1a5de140ac5a6c1124e4f718ce0b28ea47393aa6637", + "4fe181f54ad63a2983feaaf77d1e7235c2beb17fa328b6d9505bda327df19fc37f02c4b6f0368ce23147313a8e5738b5fa2a95b29de1c7f8264eb77b69f585cd", + "f228773ce3f3a42b5f144d63237a72d99693adb8837d0e112a8a0f8ffff2c362857ac49c11ec740d1500749dac9b1f4548108bf3155794dcc9e4082849e2b85b", + "962452a8455cc56c8511317e3b1f3b2c37df75f588e94325fdd77070359cf63a9ae6e930936fdf8e1e08ffca440cfb72c28f06d89a2151d1c46cd5b268ef8563", + "43d44bfa18768c59896bf7ed1765cb2d14af8c260266039099b25a603e4ddc5039d6ef3a91847d1088d401c0c7e847781a8a590d33a3c6cb4df0fab1c2f22355", + "dcffa9d58c2a4ca2cdbb0c7aa4c4c1d45165190089f4e983bb1c2cab4aaeff1fa2b5ee516fecd780540240bf37e56c8bcca7fab980e1e61c9400d8a9a5b14ac6", + "6fbf31b45ab0c0b8dad1c0f5f4061379912dde5aa922099a030b725c73346c524291adef89d2f6fd8dfcda6d07dad811a9314536c2915ed45da34947e83de34e", + "a0c65bddde8adef57282b04b11e7bc8aab105b99231b750c021f4a735cb1bcfab87553bba3abb0c3e64a0b6955285185a0bd35fb8cfde557329bebb1f629ee93", + "f99d815550558e81eca2f96718aed10d86f3f1cfb675cce06b0eff02f617c5a42c5aa760270f2679da2677c5aeb94f1142277f21c7f79f3c4f0cce4ed8ee62b1", + "95391da8fc7b917a2044b3d6f5374e1ca072b41454d572c7356c05fd4bc1e0f40b8bb8b4a9f6bce9be2c4623c399b0dca0dab05cb7281b71a21b0ebcd9e55670", + "04b9cd3d20d221c09ac86913d3dc63041989a9a1e694f1e639a3ba7e451840f750c2fc191d56ad61f2e7936bc0ac8e094b60caeed878c18799045402d61ceaf9", + "ec0e0ef707e4ed6c0c66f9e089e4954b058030d2dd86398fe84059631f9ee591d9d77375355149178c0cf8f8e7c49ed2a5e4f95488a2247067c208510fadc44c", + "9a37cce273b79c09913677510eaf7688e89b3314d3532fd2764c39de022a2945b5710d13517af8ddc0316624e73bec1ce67df15228302036f330ab0cb4d218dd", + "4cf9bb8fb3d4de8b38b2f262d3c40f46dfe747e8fc0a414c193d9fcf753106ce47a18f172f12e8a2f1c26726545358e5ee28c9e2213a8787aafbc516d2343152", + "64e0c63af9c808fd893137129867fd91939d53f2af04be4fa268006100069b2d69daa5c5d8ed7fddcb2a70eeecdf2b105dd46a1e3b7311728f639ab489326bc9", + "5e9c93158d659b2def06b0c3c7565045542662d6eee8a96a89b78ade09fe8b3dcc096d4fe48815d88d8f82620156602af541955e1f6ca30dce14e254c326b88f", + "7775dff889458dd11aef417276853e21335eb88e4dec9cfb4e9edb49820088551a2ca60339f12066101169f0dfe84b098fddb148d9da6b3d613df263889ad64b", + "f0d2805afbb91f743951351a6d024f9353a23c7ce1fc2b051b3a8b968c233f46f50f806ecb1568ffaa0b60661e334b21dde04f8fa155ac740eeb42e20b60d764", + "86a2af316e7d7754201b942e275364ac12ea8962ab5bd8d7fb276dc5fbffc8f9a28cae4e4867df6780d9b72524160927c855da5b6078e0b554aa91e31cb9ca1d", + "10bdf0caa0802705e706369baf8a3f79d72c0a03a80675a7bbb00be3a45e516424d1ee88efb56f6d5777545ae6e27765c3a8f5e493fc308915638933a1dfee55", + "b01781092b1748459e2e4ec178696627bf4ebafebba774ecf018b79a68aeb84917bf0b84bb79d17b743151144cd66b7b33a4b9e52c76c4e112050ff5385b7f0b", + "c6dbc61dec6eaeac81e3d5f755203c8e220551534a0b2fd105a91889945a638550204f44093dd998c076205dffad703a0e5cd3c7f438a7e634cd59fededb539e", + "eba51acffb4cea31db4b8d87e9bf7dd48fe97b0253ae67aa580f9ac4a9d941f2bea518ee286818cc9f633f2a3b9fb68e594b48cdd6d515bf1d52ba6c85a203a7", + "86221f3ada52037b72224f105d7999231c5e5534d03da9d9c0a12acb68460cd375daf8e24386286f9668f72326dbf99ba094392437d398e95bb8161d717f8991", + "5595e05c13a7ec4dc8f41fb70cb50a71bce17c024ff6de7af618d0cc4e9c32d9570d6d3ea45b86525491030c0d8f2b1836d5778c1ce735c17707df364d054347", + "ce0f4f6aca89590a37fe034dd74dd5fa65eb1cbd0a41508aaddc09351a3cea6d18cb2189c54b700c009f4cbf0521c7ea01be61c5ae09cb54f27bc1b44d658c82", + "7ee80b06a215a3bca970c77cda8761822bc103d44fa4b33f4d07dcb997e36d55298bceae12241b3fa07fa63be5576068da387b8d5859aeab701369848b176d42", + "940a84b6a84d109aab208c024c6ce9647676ba0aaa11f86dbb7018f9fd2220a6d901a9027f9abcf935372727cbf09ebd61a2a2eeb87653e8ecad1bab85dc8327", + "2020b78264a82d9f4151141adba8d44bf20c5ec062eee9b595a11f9e84901bf148f298e0c9f8777dcdbc7cc4670aac356cc2ad8ccb1629f16f6a76bcefbee760", + "d1b897b0e075ba68ab572adf9d9c436663e43eb3d8e62d92fc49c9be214e6f27873fe215a65170e6bea902408a25b49506f47babd07cecf7113ec10c5dd31252", + "b14d0c62abfa469a357177e594c10c194243ed2025ab8aa5ad2fa41ad318e0ff48cd5e60bec07b13634a711d2326e488a985f31e31153399e73088efc86a5c55", + "4169c5cc808d2697dc2a82430dc23e3cd356dc70a94566810502b8d655b39abf9e7f902fe717e0389219859e1945df1af6ada42e4ccda55a197b7100a30c30a1", + "258a4edb113d66c839c8b1c91f15f35ade609f11cd7f8681a4045b9fef7b0b24c82cda06a5f2067b368825e3914e53d6948ede92efd6e8387fa2e537239b5bee", + "79d2d8696d30f30fb34657761171a11e6c3f1e64cbe7bebee159cb95bfaf812b4f411e2f26d9c421dc2c284a3342d823ec293849e42d1e46b0a4ac1e3c86abaa", + "8b9436010dc5dee992ae38aea97f2cd63b946d94fedd2ec9671dcde3bd4ce9564d555c66c15bb2b900df72edb6b891ebcadfeff63c9ea4036a998be7973981e7", + "c8f68e696ed28242bf997f5b3b34959508e42d613810f1e2a435c96ed2ff560c7022f361a9234b9837feee90bf47922ee0fd5f8ddf823718d86d1e16c6090071", + "b02d3eee4860d5868b2c39ce39bfe81011290564dd678c85e8783f29302dfc1399ba95b6b53cd9ebbf400cca1db0ab67e19a325f2d115812d25d00978ad1bca4", + "7693ea73af3ac4dad21ca0d8da85b3118a7d1c6024cfaf557699868217bc0c2f44a199bc6c0edd519798ba05bd5b1b4484346a47c2cadf6bf30b785cc88b2baf", + "a0e5c1c0031c02e48b7f09a5e896ee9aef2f17fc9e18e997d7f6cac7ae316422c2b1e77984e5f3a73cb45deed5d3f84600105e6ee38f2d090c7d0442ea34c46d", + "41daa6adcfdb69f1440c37b596440165c15ada596813e2e22f060fcd551f24dee8e04ba6890387886ceec4a7a0d7fc6b44506392ec3822c0d8c1acfc7d5aebe8", + "14d4d40d5984d84c5cf7523b7798b254e275a3a8cc0a1bd06ebc0bee726856acc3cbf516ff667cda2058ad5c3412254460a82c92187041363cc77a4dc215e487", + "d0e7a1e2b9a447fee83e2277e9ff8010c2f375ae12fa7aaa8ca5a6317868a26a367a0b69fbc1cf32a55d34eb370663016f3d2110230eba754028a56f54acf57c", + "e771aa8db5a3e043e8178f39a0857ba04a3f18e4aa05743cf8d222b0b095825350ba422f63382a23d92e4149074e816a36c1cd28284d146267940b31f8818ea2", + "feb4fd6f9e87a56bef398b3284d2bda5b5b0e166583a66b61e538457ff0584872c21a32962b9928ffab58de4af2edd4e15d8b35570523207ff4e2a5aa7754caa", + "462f17bf005fb1c1b9e671779f665209ec2873e3e411f98dabf240a1d5ec3f95ce6796b6fc23fe171903b502023467dec7273ff74879b92967a2a43a5a183d33", + "d3338193b64553dbd38d144bea71c5915bb110e2d88180dbc5db364fd6171df317fc7268831b5aef75e4342b2fad8797ba39eddcef80e6ec08159350b1ad696d", + "e1590d585a3d39f7cb599abd479070966409a6846d4377acf4471d065d5db94129cc9be92573b05ed226be1e9b7cb0cabe87918589f80dadd4ef5ef25a93d28e", + "f8f3726ac5a26cc80132493a6fedcb0e60760c09cfc84cad178175986819665e76842d7b9fedf76dddebf5d3f56faaad4477587af21606d396ae570d8e719af2", + "30186055c07949948183c850e9a756cc09937e247d9d928e869e20bafc3cd9721719d34e04a0899b92c736084550186886efba2e790d8be6ebf040b209c439a4", + "f3c4276cb863637712c241c444c5cc1e3554e0fddb174d035819dd83eb700b4ce88df3ab3841ba02085e1a99b4e17310c5341075c0458ba376c95a6818fbb3e2", + "0aa007c4dd9d5832393040a1583c930bca7dc5e77ea53add7e2b3f7c8e231368043520d4a3ef53c969b6bbfd025946f632bd7f765d53c21003b8f983f75e2a6a", + "08e9464720533b23a04ec24f7ae8c103145f765387d738777d3d343477fd1c58db052142cab754ea674378e18766c53542f71970171cc4f81694246b717d7564", + "d37ff7ad297993e7ec21e0f1b4b5ae719cdc83c5db687527f27516cbffa822888a6810ee5c1ca7bfe3321119be1ab7bfa0a502671c8329494df7ad6f522d440f", + "dd9042f6e464dcf86b1262f6accfafbd8cfd902ed3ed89abf78ffa482dbdeeb6969842394c9a1168ae3d481a017842f660002d42447c6b22f7b72f21aae021c9", + "bd965bf31e87d70327536f2a341cebc4768eca275fa05ef98f7f1b71a0351298de006fba73fe6733ed01d75801b4a928e54231b38e38c562b2e33ea1284992fa", + "65676d800617972fbd87e4b9514e1c67402b7a331096d3bfac22f1abb95374abc942f16e9ab0ead33b87c91968a6e509e119ff07787b3ef483e1dcdccf6e3022", + "939fa189699c5d2c81ddd1ffc1fa207c970b6a3685bb29ce1d3e99d42f2f7442da53e95a72907314f4588399a3ff5b0a92beb3f6be2694f9f86ecf2952d5b41c", + "c516541701863f91005f314108ceece3c643e04fc8c42fd2ff556220e616aaa6a48aeb97a84bad74782e8dff96a1a2fa949339d722edcaa32b57067041df88cc", + "987fd6e0d6857c553eaebb3d34970a2c2f6e89a3548f492521722b80a1c21a153892346d2cba6444212d56da9a26e324dccbc0dcde85d4d2ee4399eec5a64e8f", + "ae56deb1c2328d9c4017706bce6e99d41349053ba9d336d677c4c27d9fd50ae6aee17e853154e1f4fe7672346da2eaa31eea53fcf24a22804f11d03da6abfc2b", + "49d6a608c9bde4491870498572ac31aac3fa40938b38a7818f72383eb040ad39532bc06571e13d767e6945ab77c0bdc3b0284253343f9f6c1244ebf2ff0df866", + "da582ad8c5370b4469af862aa6467a2293b2b28bd80ae0e91f425ad3d47249fdf98825cc86f14028c3308c9804c78bfeeeee461444ce243687e1a50522456a1d", + "d5266aa3331194aef852eed86d7b5b2633a0af1c735906f2e13279f14931a9fc3b0eac5ce9245273bd1aa92905abe16278ef7efd47694789a7283b77da3c70f8", + "2962734c28252186a9a1111c732ad4de4506d4b4480916303eb7991d659ccda07a9911914bc75c418ab7a4541757ad054796e26797feaf36e9f6ad43f14b35a4", + "e8b79ec5d06e111bdfafd71e9f5760f00ac8ac5d8bf768f9ff6f08b8f026096b1cc3a4c973333019f1e3553e77da3f98cb9f542e0a90e5f8a940cc58e59844b3", + "dfb320c44f9d41d1efdcc015f08dd5539e526e39c87d509ae6812a969e5431bf4fa7d91ffd03b981e0d544cf72d7b1c0374f8801482e6dea2ef903877eba675e", + "d88675118fdb55a5fb365ac2af1d217bf526ce1ee9c94b2f0090b2c58a06ca58187d7fe57c7bed9d26fca067b4110eefcd9a0a345de872abe20de368001b0745", + "b893f2fc41f7b0dd6e2f6aa2e0370c0cff7df09e3acfcc0e920b6e6fad0ef747c40668417d342b80d2351e8c175f20897a062e9765e6c67b539b6ba8b9170545", + "6c67ec5697accd235c59b486d7b70baeedcbd4aa64ebd4eef3c7eac189561a726250aec4d48cadcafbbe2ce3c16ce2d691a8cce06e8879556d4483ed7165c063", + "f1aa2b044f8f0c638a3f362e677b5d891d6fd2ab0765f6ee1e4987de057ead357883d9b405b9d609eea1b869d97fb16d9b51017c553f3b93c0a1e0f1296fedcd", + "cbaa259572d4aebfc1917acddc582b9f8dfaa928a198ca7acd0f2aa76a134a90252e6298a65b08186a350d5b7626699f8cb721a3ea5921b753ae3a2dce24ba3a", + "fa1549c9796cd4d303dcf452c1fbd5744fd9b9b47003d920b92de34839d07ef2a29ded68f6fc9e6c45e071a2e48bd50c5084e96b657dd0404045a1ddefe282ed", + "5cf2ac897ab444dcb5c8d87c495dbdb34e1838b6b629427caa51702ad0f9688525f13bec503a3c3a2c80a65e0b5715e8afab00ffa56ec455a49a1ad30aa24fcd", + "9aaf80207bace17bb7ab145757d5696bde32406ef22b44292ef65d4519c3bb2ad41a59b62cc3e94b6fa96d32a7faadae28af7d35097219aa3fd8cda31e40c275", + "af88b163402c86745cb650c2988fb95211b94b03ef290eed9662034241fd51cf398f8073e369354c43eae1052f9b63b08191caa138aa54fea889cc7024236897", + "48fa7d64e1ceee27b9864db5ada4b53d00c9bc7626555813d3cd6730ab3cc06ff342d727905e33171bde6e8476e77fb1720861e94b73a2c538d254746285f430", + "0e6fd97a85e904f87bfe85bbeb34f69e1f18105cf4ed4f87aec36c6e8b5f68bd2a6f3dc8a9ecb2b61db4eedb6b2ea10bf9cb0251fb0f8b344abf7f366b6de5ab", + "06622da5787176287fdc8fed440bad187d830099c94e6d04c8e9c954cda70c8bb9e1fc4a6d0baa831b9b78ef6648681a4867a11da93ee36e5e6a37d87fc63f6f", + "1da6772b58fabf9c61f68d412c82f182c0236d7d575ef0b58dd22458d643cd1dfc93b03871c316d8430d312995d4197f0874c99172ba004a01ee295abac24e46", + "3cd2d9320b7b1d5fb9aab951a76023fa667be14a9124e394513918a3f44096ae4904ba0ffc150b63bc7ab1eeb9a6e257e5c8f000a70394a5afd842715de15f29", + "04cdc14f7434e0b4be70cb41db4c779a88eaef6accebcb41f2d42fffe7f32a8e281b5c103a27021d0d08362250753cdf70292195a53a48728ceb5844c2d98bab", + "9071b7a8a075d0095b8fb3ae5113785735ab98e2b52faf91d5b89e44aac5b5d4ebbf91223b0ff4c71905da55342e64655d6ef8c89a4768c3f93a6dc0366b5bc8", + "ebb30240dd96c7bc8d0abe49aa4edcbb4afdc51ff9aaf720d3f9e7fbb0f9c6d6571350501769fc4ebd0b2141247ff400d4fd4be414edf37757bb90a32ac5c65a", + "8532c58bf3c8015d9d1cbe00eef1f5082f8f3632fbe9f1ed4f9dfb1fa79e8283066d77c44c4af943d76b300364aecbd0648c8a8939bd204123f4b56260422dec", + "fe9846d64f7c7708696f840e2d76cb4408b6595c2f81ec6a28a7f2f20cb88cfe6ac0b9e9b8244f08bd7095c350c1d0842f64fb01bb7f532dfcd47371b0aeeb79", + "28f17ea6fb6c42092dc264257e29746321fb5bdaea9873c2a7fa9d8f53818e899e161bc77dfe8090afd82bf2266c5c1bc930a8d1547624439e662ef695f26f24", + "ec6b7d7f030d4850acae3cb615c21dd25206d63e84d1db8d957370737ba0e98467ea0ce274c66199901eaec18a08525715f53bfdb0aacb613d342ebdceeddc3b", + "b403d3691c03b0d3418df327d5860d34bbfcc4519bfbce36bf33b208385fadb9186bc78a76c489d89fd57e7dc75412d23bcd1dae8470ce9274754bb8585b13c5", + "31fc79738b8772b3f55cd8178813b3b52d0db5a419d30ba9495c4b9da0219fac6df8e7c23a811551a62b827f256ecdb8124ac8a6792ccfecc3b3012722e94463", + "bb2039ec287091bcc9642fc90049e73732e02e577e2862b32216ae9bedcd730c4c284ef3968c368b7d37584f97bd4b4dc6ef6127acfe2e6ae2509124e66c8af4", + "f53d68d13f45edfcb9bd415e2831e938350d5380d3432278fc1c0c381fcb7c65c82dafe051d8c8b0d44e0974a0e59ec7bf7ed0459f86e96f329fc79752510fd3", + "8d568c7984f0ecdf7640fbc483b5d8c9f86634f6f43291841b309a350ab9c1137d24066b09da9944bac54d5bb6580d836047aac74ab724b887ebf93d4b32eca9", + "c0b65ce5a96ff774c456cac3b5f2c4cd359b4ff53ef93a3da0778be4900d1e8da1601e769e8f1b02d2a2f8c5b9fa10b44f1c186985468feeb008730283a6657d", + "4900bba6f5fb103ece8ec96ada13a5c3c85488e05551da6b6b33d988e611ec0fe2e3c2aa48ea6ae8986a3a231b223c5d27cec2eadde91ce07981ee652862d1e4", + "c7f5c37c7285f927f76443414d4357ff789647d7a005a5a787e03c346b57f49f21b64fa9cf4b7e45573e23049017567121a9c3d4b2b73ec5e9413577525db45a", + "ec7096330736fdb2d64b5653e7475da746c23a4613a82687a28062d3236364284ac01720ffb406cfe265c0df626a188c9e5963ace5d3d5bb363e32c38c2190a6", + "82e744c75f4649ec52b80771a77d475a3bc091989556960e276a5f9ead92a03f718742cdcfeaee5cb85c44af198adc43a4a428f5f0c2ddb0be36059f06d7df73", + "2834b7a7170f1f5b68559ab78c1050ec21c919740b784a9072f6e5d69f828d70c919c5039fb148e39e2c8a52118378b064ca8d5001cd10a5478387b966715ed6", + "16b4ada883f72f853bb7ef253efcab0c3e2161687ad61543a0d2824f91c1f81347d86be709b16996e17f2dd486927b0288ad38d13063c4a9672c39397d3789b6", + "78d048f3a69d8b54ae0ed63a573ae350d89f7c6cf1f3688930de899afa037697629b314e5cd303aa62feea72a25bf42b304b6c6bcb27fae21c16d925e1fbdac3", + "0f746a48749287ada77a82961f05a4da4abdb7d77b1220f836d09ec814359c0ec0239b8c7b9ff9e02f569d1b301ef67c4612d1de4f730f81c12c40cc063c5caa", + "f0fc859d3bd195fbdc2d591e4cdac15179ec0f1dc821c11df1f0c1d26e6260aaa65b79fafacafd7d3ad61e600f250905f5878c87452897647a35b995bcadc3a3", + "2620f687e8625f6a412460b42e2cef67634208ce10a0cbd4dff7044a41b7880077e9f8dc3b8d1216d3376a21e015b58fb279b521d83f9388c7382c8505590b9b", + "227e3aed8d2cb10b918fcb04f9de3e6d0a57e08476d93759cd7b2ed54a1cbf0239c528fb04bbf288253e601d3bc38b21794afef90b17094a182cac557745e75f", + "1a929901b09c25f27d6b35be7b2f1c4745131fdebca7f3e2451926720434e0db6e74fd693ad29b777dc3355c592a361c4873b01133a57c2e3b7075cbdb86f4fc", + "5fd7968bc2fe34f220b5e3dc5af9571742d73b7d60819f2888b629072b96a9d8ab2d91b82d0a9aaba61bbd39958132fcc4257023d1eca591b3054e2dc81c8200", + "dfcce8cf32870cc6a503eadafc87fd6f78918b9b4d0737db6810be996b5497e7e5cc80e312f61e71ff3e9624436073156403f735f56b0b01845c18f6caf772e6", + "02f7ef3a9ce0fff960f67032b296efca3061f4934d690749f2d01c35c81c14f39a67fa350bc8a0359bf1724bffc3bca6d7c7bba4791fd522a3ad353c02ec5aa8", + "64be5c6aba65d594844ae78bb022e5bebe127fd6b6ffa5a13703855ab63b624dcd1a363f99203f632ec386f3ea767fc992e8ed9686586aa27555a8599d5b808f", + "f78585505c4eaa54a8b5be70a61e735e0ff97af944ddb3001e35d86c4e2199d976104b6ae31750a36a726ed285064f5981b503889fef822fcdc2898dddb7889a", + "e4b5566033869572edfd87479a5bb73c80e8759b91232879d96b1dda36c012076ee5a2ed7ae2de63ef8406a06aea82c188031b560beafb583fb3de9e57952a7e", + "e1b3e7ed867f6c9484a2a97f7715f25e25294e992e41f6a7c161ffc2adc6daaeb7113102d5e6090287fe6ad94ce5d6b739c6ca240b05c76fb73f25dd024bf935", + "85fd085fdc12a080983df07bd7012b0d402a0f4043fcb2775adf0bad174f9b08d1676e476985785c0a5dcc41dbff6d95ef4d66a3fbdc4a74b82ba52da0512b74", + "aed8fa764b0fbff821e05233d2f7b0900ec44d826f95e93c343c1bc3ba5a24374b1d616e7e7aba453a0ada5e4fab5382409e0d42ce9c2bc7fb39a99c340c20f0", + "7ba3b2e297233522eeb343bd3ebcfd835a04007735e87f0ca300cbee6d416565162171581e4020ff4cf176450f1291ea2285cb9ebffe4c56660627685145051c", + "de748bcf89ec88084721e16b85f30adb1a6134d664b5843569babc5bbd1a15ca9b61803c901a4fef32965a1749c9f3a4e243e173939dc5a8dc495c671ab52145", + "aaf4d2bdf200a919706d9842dce16c98140d34bc433df320aba9bd429e549aa7a3397652a4d768277786cf993cde2338673ed2e6b66c961fefb82cd20c93338f", + "c408218968b788bf864f0997e6bc4c3dba68b276e2125a4843296052ff93bf5767b8cdce7131f0876430c1165fec6c4f47adaa4fd8bcfacef463b5d3d0fa61a0", + "76d2d819c92bce55fa8e092ab1bf9b9eab237a25267986cacf2b8ee14d214d730dc9a5aa2d7b596e86a1fd8fa0804c77402d2fcd45083688b218b1cdfa0dcbcb", + "72065ee4dd91c2d8509fa1fc28a37c7fc9fa7d5b3f8ad3d0d7a25626b57b1b44788d4caf806290425f9890a3a2a35a905ab4b37acfd0da6e4517b2525c9651e4", + "64475dfe7600d7171bea0b394e27c9b00d8e74dd1e416a79473682ad3dfdbb706631558055cfc8a40e07bd015a4540dcdea15883cbbf31412df1de1cd4152b91", + "12cd1674a4488a5d7c2b3160d2e2c4b58371bedad793418d6f19c6ee385d70b3e06739369d4df910edb0b0a54cbff43d54544cd37ab3a06cfa0a3ddac8b66c89", + "60756966479dedc6dd4bcff8ea7d1d4ce4d4af2e7b097e32e3763518441147cc12b3c0ee6d2ecabf1198cec92e86a3616fba4f4e872f5825330adbb4c1dee444", + "a7803bcb71bc1d0f4383dde1e0612e04f872b715ad30815c2249cf34abb8b024915cb2fc9f4e7cc4c8cfd45be2d5a91eab0941c7d270e2da4ca4a9f7ac68663a", + "b84ef6a7229a34a750d9a98ee2529871816b87fbe3bc45b45fa5ae82d5141540211165c3c5d7a7476ba5a4aa06d66476f0d9dc49a3f1ee72c3acabd498967414", + "fae4b6d8efc3f8c8e64d001dabec3a21f544e82714745251b2b4b393f2f43e0da3d403c64db95a2cb6e23ebb7b9e94cdd5ddac54f07c4a61bd3cb10aa6f93b49", + "34f7286605a122369540141ded79b8957255da2d4155abbf5a8dbb89c8eb7ede8eeef1daa46dc29d751d045dc3b1d658bb64b80ff8589eddb3824b13da235a6b", + "3b3b48434be27b9eababba43bf6b35f14b30f6a88dc2e750c358470d6b3aa3c18e47db4017fa55106d8252f016371a00f5f8b070b74ba5f23cffc5511c9f09f0", + "ba289ebd6562c48c3e10a8ad6ce02e73433d1e93d7c9279d4d60a7e879ee11f441a000f48ed9f7c4ed87a45136d7dccdca482109c78a51062b3ba4044ada2469", + "022939e2386c5a37049856c850a2bb10a13dfea4212b4c732a8840a9ffa5faf54875c5448816b2785a007da8a8d2bc7d71a54e4e6571f10b600cbdb25d13ede3", + "e6fec19d89ce8717b1a087024670fe026f6c7cbda11caef959bb2d351bf856f8055d1c0ebdaaa9d1b17886fc2c562b5e99642fc064710c0d3488a02b5ed7f6fd", + "94c96f02a8f576aca32ba61c2b206f907285d9299b83ac175c209a8d43d53bfe683dd1d83e7549cb906c28f59ab7c46f8751366a28c39dd5fe2693c9019666c8", + "31a0cd215ebd2cb61de5b9edc91e6195e31c59a5648d5c9f737e125b2605708f2e325ab3381c8dce1a3e958886f1ecdc60318f882cfe20a24191352e617b0f21", + "91ab504a522dce78779f4c6c6ba2e6b6db5565c76d3e7e7c920caf7f757ef9db7c8fcf10e57f03379ea9bf75eb59895d96e149800b6aae01db778bb90afbc989", + "d85cabc6bd5b1a01a5afd8c6734740da9fd1c1acc6db29bfc8a2e5b668b028b6b3154bfb8703fa3180251d589ad38040ceb707c4bad1b5343cb426b61eaa49c1", + "d62efbec2ca9c1f8bd66ce8b3f6a898cb3f7566ba6568c618ad1feb2b65b76c3ce1dd20f7395372faf28427f61c9278049cf0140df434f5633048c86b81e0399", + "7c8fdc6175439e2c3db15bafa7fb06143a6a23bc90f449e79deef73c3d492a671715c193b6fea9f036050b946069856b897e08c00768f5ee5ddcf70b7cd6d0e0", + "58602ee7468e6bc9df21bd51b23c005f72d6cb013f0a1b48cbec5eca299299f97f09f54a9a01483eaeb315a6478bad37ba47ca1347c7c8fc9e6695592c91d723", + "27f5b79ed256b050993d793496edf4807c1d85a7b0a67c9c4fa99860750b0ae66989670a8ffd7856d7ce411599e58c4d77b232a62bef64d15275be46a68235ff", + "3957a976b9f1887bf004a8dca942c92d2b37ea52600f25e0c9bc5707d0279c00c6e85a839b0d2d8eb59c51d94788ebe62474a791cadf52cccf20f5070b6573fc", + "eaa2376d55380bf772ecca9cb0aa4668c95c707162fa86d518c8ce0ca9bf7362b9f2a0adc3ff59922df921b94567e81e452f6c1a07fc817cebe99604b3505d38", + "c1e2c78b6b2734e2480ec550434cb5d613111adcc21d475545c3b1b7e6ff12444476e5c055132e2229dc0f807044bb919b1a5662dd38a9ee65e243a3911aed1a", + "8ab48713389dd0fcf9f965d3ce66b1e559a1f8c58741d67683cd971354f452e62d0207a65e436c5d5d8f8ee71c6abfe50e669004c302b31a7ea8311d4a916051", + "24ce0addaa4c65038bd1b1c0f1452a0b128777aabc94a29df2fd6c7e2f85f8ab9ac7eff516b0e0a825c84a24cfe492eaad0a6308e46dd42fe8333ab971bb30ca", + "5154f929ee03045b6b0c0004fa778edee1d139893267cc84825ad7b36c63de32798e4a166d24686561354f63b00709a1364b3c241de3febf0754045897467cd4", + "e74e907920fd87bd5ad636dd11085e50ee70459c443e1ce5809af2bc2eba39f9e6d7128e0e3712c316da06f4705d78a4838e28121d4344a2c79c5e0db307a677", + "bf91a22334bac20f3fd80663b3cd06c4e8802f30e6b59f90d3035cc9798a217ed5a31abbda7fa6842827bdf2a7a1c21f6fcfccbb54c6c52926f32da816269be1", + "d9d5c74be5121b0bd742f26bffb8c89f89171f3f934913492b0903c271bbe2b3395ef259669bef43b57f7fcc3027db01823f6baee66e4f9fead4d6726c741fce", + "50c8b8cf34cd879f80e2faab3230b0c0e1cc3e9dcadeb1b9d97ab923415dd9a1fe38addd5c11756c67990b256e95ad6d8f9fedce10bf1c90679cde0ecf1be347", + "0a386e7cd5dd9b77a035e09fe6fee2c8ce61b5383c87ea43205059c5e4cd4f4408319bb0a82360f6a58e6c9ce3f487c446063bf813bc6ba535e17fc1826cfc91", + "1f1459cb6b61cbac5f0efe8fc487538f42548987fcd56221cfa7beb22504769e792c45adfb1d6b3d60d7b749c8a75b0bdf14e8ea721b95dca538ca6e25711209", + "e58b3836b7d8fedbb50ca5725c6571e74c0785e97821dab8b6298c10e4c079d4a6cdf22f0fedb55032925c16748115f01a105e77e00cee3d07924dc0d8f90659", + "b929cc6505f020158672deda56d0db081a2ee34c00c1100029bdf8ea98034fa4bf3e8655ec697fe36f40553c5bb46801644a627d3342f4fc92b61f03290fb381", + "72d353994b49d3e03153929a1e4d4f188ee58ab9e72ee8e512f29bc773913819ce057ddd7002c0433ee0a16114e3d156dd2c4a7e80ee53378b8670f23e33ef56", + "c70ef9bfd775d408176737a0736d68517ce1aaad7e81a93c8c1ed967ea214f56c8a377b1763e676615b60f3988241eae6eab9685a5124929d28188f29eab06f7", + "c230f0802679cb33822ef8b3b21bf7a9a28942092901d7dac3760300831026cf354c9232df3e084d9903130c601f63c1f4a4a4b8106e468cd443bbe5a734f45f", + "6f43094cafb5ebf1f7a4937ec50f56a4c9da303cbb55ac1f27f1f1976cd96beda9464f0e7b9c54620b8a9fba983164b8be3578425a024f5fe199c36356b88972", + "3745273f4c38225db2337381871a0c6aafd3af9b018c88aa02025850a5dc3a42a1a3e03e56cbf1b0876d63a441f1d2856a39b8801eb5af325201c415d65e97fe", + "c50c44cca3ec3edaae779a7e179450ebdda2f97067c690aa6c5a4ac7c30139bb27c0df4db3220e63cb110d64f37ffe078db72653e2daacf93ae3f0a2d1a7eb2e", + "8aef263e385cbc61e19b28914243262af5afe8726af3ce39a79c27028cf3ecd3f8d2dfd9cfc9ad91b58f6f20778fd5f02894a3d91c7d57d1e4b866a7f364b6be", + "28696141de6e2d9bcb3235578a66166c1448d3e905a1b482d423be4bc5369bc8c74dae0acc9cc123e1d8ddce9f97917e8c019c552da32d39d2219b9abf0fa8c8", + "2fb9eb2085830181903a9dafe3db428ee15be7662224efd643371fb25646aee716e531eca69b2bdc8233f1a8081fa43da1500302975a77f42fa592136710e9dc", + "66f9a7143f7a3314a669bf2e24bbb35014261d639f495b6c9c1f104fe8e320aca60d4550d69d52edbd5a3cdeb4014ae65b1d87aa770b69ae5c15f4330b0b0ad8", + "f4c4dd1d594c3565e3e25ca43dad82f62abea4835ed4cd811bcd975e46279828d44d4c62c3679f1b7f7b9dd4571d7b49557347b8c5460cbdc1bef690fb2a08c0", + "8f1dc9649c3a84551f8f6e91cac68242a43b1f8f328ee92280257387fa7559aa6db12e4aeadc2d26099178749c6864b357f3f83b2fb3efa8d2a8db056bed6bcc", + "3139c1a7f97afd1675d460ebbc07f2728aa150df849624511ee04b743ba0a833092f18c12dc91b4dd243f333402f59fe28abdbbbae301e7b659c7a26d5c0f979", + "06f94a2996158a819fe34c40de3cf0379fd9fb85b3e363ba3926a0e7d960e3f4c2e0c70c7ce0ccb2a64fc29869f6e7ab12bd4d3f14fce943279027e785fb5c29", + "c29c399ef3eee8961e87565c1ce263925fc3d0ce267d13e48dd9e732ee67b0f69fad56401b0f10fcaac119201046cca28c5b14abdea3212ae65562f7f138db3d", + "4cec4c9df52eef05c3f6faaa9791bc7445937183224ecc37a1e58d0132d35617531d7e795f52af7b1eb9d147de1292d345fe341823f8e6bc1e5badca5c656108", + "898bfbae93b3e18d00697eab7d9704fa36ec339d076131cefdf30edbe8d9cc81c3a80b129659b163a323bab9793d4feed92d54dae966c77529764a09be88db45", + "ee9bd0469d3aaf4f14035be48a2c3b84d9b4b1fff1d945e1f1c1d38980a951be197b25fe22c731f20aeacc930ba9c4a1f4762227617ad350fdabb4e80273a0f4", + "3d4d3113300581cd96acbf091c3d0f3c310138cd6979e6026cde623e2dd1b24d4a8638bed1073344783ad0649cc6305ccec04beb49f31c633088a99b65130267", + "95c0591ad91f921ac7be6d9ce37e0663ed8011c1cfd6d0162a5572e94368bac02024485e6a39854aa46fe38e97d6c6b1947cd272d86b06bb5b2f78b9b68d559d", + "227b79ded368153bf46c0a3ca978bfdbef31f3024a5665842468490b0ff748ae04e7832ed4c9f49de9b1706709d623e5c8c15e3caecae8d5e433430ff72f20eb", + "5d34f3952f0105eef88ae8b64c6ce95ebfade0e02c69b08762a8712d2e4911ad3f941fc4034dc9b2e479fdbcd279b902faf5d838bb2e0c6495d372b5b7029813", + "7f939bf8353abce49e77f14f3750af20b7b03902e1a1e7fb6aaf76d0259cd401a83190f15640e74f3e6c5a90e839c7821f6474757f75c7bf9002084ddc7a62dc", + "062b61a2f9a33a71d7d0a06119644c70b0716a504de7e5e1be49bd7b86e7ed6817714f9f0fc313d06129597e9a2235ec8521de36f7290a90ccfc1ffa6d0aee29", + "f29e01eeae64311eb7f1c6422f946bf7bea36379523e7b2bbaba7d1d34a22d5ea5f1c5a09d5ce1fe682cced9a4798d1a05b46cd72dff5c1b355440b2a2d476bc", + "ec38cd3bbab3ef35d7cb6d5c914298351d8a9dc97fcee051a8a02f58e3ed6184d0b7810a5615411ab1b95209c3c810114fdeb22452084e77f3f847c6dbaafe16", + "c2aef5e0ca43e82641565b8cb943aa8ba53550caef793b6532fafad94b816082f0113a3ea2f63608ab40437ecc0f0229cb8fa224dcf1c478a67d9b64162b92d1", + "15f534efff7105cd1c254d074e27d5898b89313b7d366dc2d7d87113fa7d53aae13f6dba487ad8103d5e854c91fdb6e1e74b2ef6d1431769c30767dde067a35c", + "89acbca0b169897a0a2714c2df8c95b5b79cb69390142b7d6018bb3e3076b099b79a964152a9d912b1b86412b7e372e9cecad7f25d4cbab8a317be36492a67d7", + "e3c0739190ed849c9c962fd9dbb55e207e624fcac1eb417691515499eea8d8267b7e8f1287a63633af5011fde8c4ddf55bfdf722edf88831414f2cfaed59cb9a", + "8d6cf87c08380d2d1506eee46fd4222d21d8c04e585fbfd08269c98f702833a156326a0724656400ee09351d57b440175e2a5de93cc5f80db6daf83576cf75fa", + "da24bede383666d563eeed37f6319baf20d5c75d1635a6ba5ef4cfa1ac95487e96f8c08af600aab87c986ebad49fc70a58b4890b9c876e091016daf49e1d322e", + "f9d1d1b1e87ea7ae753a029750cc1cf3d0157d41805e245c5617bb934e732f0ae3180b78e05bfe76c7c3051e3e3ac78b9b50c05142657e1e03215d6ec7bfd0fc", + "11b7bc1668032048aa43343de476395e814bbbc223678db951a1b03a021efac948cfbe215f97fe9a72a2f6bc039e3956bfa417c1a9f10d6d7ba5d3d32ff323e5", + "b8d9000e4fc2b066edb91afee8e7eb0f24e3a201db8b6793c0608581e628ed0bcc4e5aa6787992a4bcc44e288093e63ee83abd0bc3ec6d0934a674a4da13838a", + "ce325e294f9b6719d6b61278276ae06a2564c03bb0b783fafe785bdf89c7d5acd83e78756d301b445699024eaeb77b54d477336ec2a4f332f2b3f88765ddb0c3", + "29acc30e9603ae2fccf90bf97e6cc463ebe28c1b2f9b4b765e70537c25c702a29dcbfbf14c99c54345ba2b51f17b77b5f15db92bbad8fa95c471f5d070a137cc", + "3379cbaae562a87b4c0425550ffdd6bfe1203f0d666cc7ea095be407a5dfe61ee91441cd5154b3e53b4f5fb31ad4c7a9ad5c7af4ae679aa51a54003a54ca6b2d", + "3095a349d245708c7cf550118703d7302c27b60af5d4e67fc978f8a4e60953c7a04f92fcf41aee64321ccb707a895851552b1e37b00bc5e6b72fa5bcef9e3fff", + "07262d738b09321f4dbccec4bb26f48cb0f0ed246ce0b31b9a6e7bc683049f1f3e5545f28ce932dd985c5ab0f43bd6de0770560af329065ed2e49d34624c2cbb", + "b6405eca8ee3316c87061cc6ec18dba53e6c250c63ba1f3bae9e55dd3498036af08cd272aa24d713c6020d77ab2f3919af1a32f307420618ab97e73953994fb4", + "7ee682f63148ee45f6e5315da81e5c6e557c2c34641fc509c7a5701088c38a74756168e2cd8d351e88fd1a451f360a01f5b2580f9b5a2e8cfc138f3dd59a3ffc", + "1d263c179d6b268f6fa016f3a4f29e943891125ed8593c81256059f5a7b44af2dcb2030d175c00e62ecaf7ee96682aa07ab20a611024a28532b1c25b86657902", + "106d132cbdb4cd2597812846e2bc1bf732fec5f0a5f65dbb39ec4e6dc64ab2ce6d24630d0f15a805c3540025d84afa98e36703c3dbee713e72dde8465bc1be7e", + "0e79968226650667a8d862ea8da4891af56a4e3a8b6d1750e394f0dea76d640d85077bcec2cc86886e506751b4f6a5838f7f0b5fef765d9dc90dcdcbaf079f08", + "521156a82ab0c4e566e5844d5e31ad9aaf144bbd5a464fdca34dbd5717e8ff711d3ffebbfa085d67fe996a34f6d3e4e60b1396bf4b1610c263bdbb834d560816", + "1aba88befc55bc25efbce02db8b9933e46f57661baeabeb21cc2574d2a518a3cba5dc5a38e49713440b25f9c744e75f6b85c9d8f4681f676160f6105357b8406", + "5a9949fcb2c473cda968ac1b5d08566dc2d816d960f57e63b898fa701cf8ebd3f59b124d95bfbbedc5f1cf0e17d5eaed0c02c50b69d8a402cabcca4433b51fd4", + "b0cead09807c672af2eb2b0f06dde46cf5370e15a4096b1a7d7cbb36ec31c205fbefca00b7a4162fa89fb4fb3eb78d79770c23f44e7206664ce3cd931c291e5d", + "bb6664931ec97044e45b2ae420ae1c551a8874bc937d08e969399c3964ebdba8346cdd5d09caafe4c28ba7ec788191ceca65ddd6f95f18583e040d0f30d0364d", + "65bc770a5faa3792369803683e844b0be7ee96f29f6d6a35568006bd5590f9a4ef639b7a8061c7b0424b66b60ac34af3119905f33a9d8c3ae18382ca9b689900", + "ea9b4dca333336aaf839a45c6eaa48b8cb4c7ddabffea4f643d6357ea6628a480a5b45f2b052c1b07d1fedca918b6f1139d80f74c24510dcbaa4be70eacc1b06", + "e6342fb4a780ad975d0e24bce149989b91d360557e87994f6b457b895575cc02d0c15bad3ce7577f4c63927ff13f3e381ff7e72bdbe745324844a9d27e3f1c01", + "3e209c9b33e8e461178ab46b1c64b49a07fb745f1c8bc95fbfb94c6b87c69516651b264ef980937fad41238b91ddc011a5dd777c7efd4494b4b6ecd3a9c22ac0", + "fd6a3d5b1875d80486d6e69694a56dbb04a99a4d051f15db2689776ba1c4882e6d462a603b7015dc9f4b7450f05394303b8652cfb404a266962c41bae6e18a94", + "951e27517e6bad9e4195fc8671dee3e7e9be69cee1422cb9fecfce0dba875f7b310b93ee3a3d558f941f635f668ff832d2c1d033c5e2f0997e4c66f147344e02", + "8eba2f874f1ae84041903c7c4253c82292530fc8509550bfdc34c95c7e2889d5650b0ad8cb988e5c4894cb87fbfbb19612ea93ccc4c5cad17158b9763464b492", + "16f712eaa1b7c6354719a8e7dbdfaf55e4063a4d277d947550019b38dfb564830911057d50506136e2394c3b28945cc964967d54e3000c2181626cfb9b73efd2", + "c39639e7d5c7fb8cdd0fd3e6a52096039437122f21c78f1679cea9d78a734c56ecbeb28654b4f18e342c331f6f7229ec4b4bc281b2d80a6eb50043f31796c88c", + "72d081af99f8a173dcc9a0ac4eb3557405639a29084b54a40172912a2f8a395129d5536f0918e902f9e8fa6000995f4168ddc5f893011be6a0dbc9b8a1a3f5bb", + "c11aa81e5efd24d5fc27ee586cfd8847fbb0e27601ccece5ecca0198e3c7765393bb74457c7e7a27eb9170350e1fb53857177506be3e762cc0f14d8c3afe9077", + "c28f2150b452e6c0c424bcde6f8d72007f9310fed7f2f87de0dbb64f4479d6c1441ba66f44b2accee61609177ed340128b407ecec7c64bbe50d63d22d8627727", + "f63d88122877ec30b8c8b00d22e89000a966426112bd44166e2f525b769ccbe9b286d437a0129130dde1a86c43e04bedb594e671d98283afe64ce331de9828fd", + "348b0532880b88a6614a8d7408c3f913357fbb60e995c60205be9139e74998aede7f4581e42f6b52698f7fa1219708c14498067fd1e09502de83a77dd281150c", + "5133dc8bef725359dff59792d85eaf75b7e1dcd1978b01c35b1b85fcebc63388ad99a17b6346a217dc1a9622ebd122ecf6913c4d31a6b52a695b86af00d741a0", + "2753c4c0e98ecad806e88780ec27fccd0f5c1ab547f9e4bf1659d192c23aa2cc971b58b6802580baef8adc3b776ef7086b2545c2987f348ee3719cdef258c403", + "b1663573ce4b9d8caefc865012f3e39714b9898a5da6ce17c25a6a47931a9ddb9bbe98adaa553beed436e89578455416c2a52a525cf2862b8d1d49a2531b7391", + "64f58bd6bfc856f5e873b2a2956ea0eda0d6db0da39c8c7fc67c9f9feefcff3072cdf9e6ea37f69a44f0c61aa0da3693c2db5b54960c0281a088151db42b11e8", + "0764c7be28125d9065c4b98a69d60aede703547c66a12e17e1c618994132f5ef82482c1e3fe3146cc65376cc109f0138ed9a80e49f1f3c7d610d2f2432f20605", + "f748784398a2ff03ebeb07e155e66116a839741a336e32da71ec696001f0ad1b25cd48c69cfca7265eca1dd71904a0ce748ac4124f3571076dfa7116a9cf00e9", + "3f0dbc0186bceb6b785ba78d2a2a013c910be157bdaffae81bb6663b1a73722f7f1228795f3ecada87cf6ef0078474af73f31eca0cc200ed975b6893f761cb6d", + "d4762cd4599876ca75b2b8fe249944dbd27ace741fdab93616cbc6e425460feb51d4e7adcc38180e7fc47c89024a7f56191adb878dfde4ead62223f5a2610efe", + "cd36b3d5b4c91b90fcbba79513cfee1907d8645a162afd0cd4cf4192d4a5f4c892183a8eacdb2b6b6a9d9aa8c11ac1b261b380dbee24ca468f1bfd043c58eefe", + "98593452281661a53c48a9d8cd790826c1a1ce567738053d0bee4a91a3d5bd92eefdbabebe3204f2031ca5f781bda99ef5d8ae56e5b04a9e1ecd21b0eb05d3e1", + "771f57dd2775ccdab55921d3e8e30ccf484d61fe1c1b9c2ae819d0fb2a12fab9be70c4a7a138da84e8280435daade5bbe66af0836a154f817fb17f3397e725a3", + "c60897c6f828e21f16fbb5f15b323f87b6c8955eabf1d38061f707f608abdd993fac3070633e286cf8339ce295dd352df4b4b40b2f29da1dd50b3a05d079e6bb", + "8210cd2c2d3b135c2cf07fa0d1433cd771f325d075c6469d9c7f1ba0943cd4ab09808cabf4acb9ce5bb88b498929b4b847f681ad2c490d042db2aec94214b06b", + "1d4edfffd8fd80f7e4107840fa3aa31e32598491e4af7013c197a65b7f36dd3ac4b478456111cd4309d9243510782fa31b7c4c95fa951520d020eb7e5c36e4ef", + "af8e6e91fab46ce4873e1a50a8ef448cc29121f7f74deef34a71ef89cc00d9274bc6c2454bbb3230d8b2ec94c62b1dec85f3593bfa30ea6f7a44d7c09465a253", + "29fd384ed4906f2d13aa9fe7af905990938bed807f1832454a372ab412eea1f5625a1fcc9ac8343b7c67c5aba6e0b1cc4644654913692c6b39eb9187ceacd3ec", + "a268c7885d9874a51c44dffed8ea53e94f78456e0b2ed99ff5a3924760813826d960a15edbedbb5de5226ba4b074e71b05c55b9756bb79e55c02754c2c7b6c8a", + "0cf8545488d56a86817cd7ecb10f7116b7ea530a45b6ea497b6c72c997e09e3d0da8698f46bb006fc977c2cd3d1177463ac9057fdd1662c85d0c126443c10473", + "b39614268fdd8781515e2cfebf89b4d5402bab10c226e6344e6b9ae000fb0d6c79cb2f3ec80e80eaeb1980d2f8698916bd2e9f747236655116649cd3ca23a837", + "74bef092fc6f1e5dba3663a3fb003b2a5ba257496536d99f62b9d73f8f9eb3ce9ff3eec709eb883655ec9eb896b9128f2afc89cf7d1ab58a72f4a3bf034d2b4a", + "3a988d38d75611f3ef38b8774980b33e573b6c57bee0469ba5eed9b44f29945e7347967fba2c162e1c3be7f310f2f75ee2381e7bfd6b3f0baea8d95dfb1dafb1", + "58aedfce6f67ddc85a28c992f1c0bd0969f041e66f1ee88020a125cbfcfebcd61709c9c4eba192c15e69f020d462486019fa8dea0cd7a42921a19d2fe546d43d", + "9347bd291473e6b4e368437b8e561e065f649a6d8ada479ad09b1999a8f26b91cf6120fd3bfe014e83f23acfa4c0ad7b3712b2c3c0733270663112ccd9285cd9", + "b32163e7c5dbb5f51fdc11d2eac875efbbcb7e7699090a7e7ff8a8d50795af5d74d9ff98543ef8cdf89ac13d0485278756e0ef00c817745661e1d59fe38e7537", + "1085d78307b1c4b008c57a2e7e5b234658a0a82e4ff1e4aaac72b312fda0fe27d233bc5b10e9cc17fdc7697b540c7d95eb215a19a1a0e20e1abfa126efd568c7", + "4e5c734c7dde011d83eac2b7347b373594f92d7091b9ca34cb9c6f39bdf5a8d2f134379e16d822f6522170ccf2ddd55c84b9e6c64fc927ac4cf8dfb2a17701f2", + "695d83bd990a1117b3d0ce06cc888027d12a054c2677fd82f0d4fbfc93575523e7991a5e35a3752e9b70ce62992e268a877744cdd435f5f130869c9a2074b338", + "a6213743568e3b3158b9184301f3690847554c68457cb40fc9a4b8cfd8d4a118c301a07737aeda0f929c68913c5f51c80394f53bff1c3e83b2e40ca97eba9e15", + "d444bfa2362a96df213d070e33fa841f51334e4e76866b8139e8af3bb3398be2dfaddcbc56b9146de9f68118dc5829e74b0c28d7711907b121f9161cb92b69a9", + "142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461", +} + +var hashes2X = []string{ + "64", + "f457", + "e8c045", + "a74c6d0d", + "eb02ae482a", + "be65b981275e", + "8540ccd083a455", + "074a02fa58d7c7c0", + "da6da05e10db3022b6", + "542a5aae2f28f2c3b68c", + "ca3af2afc4afe891da78b1", + "e0f66b8dcebf4edc85f12c85", + "744224d383733b3fa2c53bfcf5", + "b09b653e85b72ef5cdf8fcfa95f3", + "dd51877f31f1cf7b9f68bbb09064a3", + "f5ebf68e7ebed6ad445ffc0c47e82650", + "ebdcfe03bcb7e21a9091202c5938c0a1bb", + "860fa5a72ff92efafc48a89df1632a4e2809", + "0d6d49daa26ae2818041108df3ce0a4db48c8d", + "e5d7e1bc5715f5ae991e4043e39533af5d53e47f", + "5232028a43b9d4dfa7f37439b49495926481ab8a29", + "c118803c922f9ae2397fb676a2ab7603dd9c29c21fe4", + "2af924f48b9bd7076bfd68794bba6402e2a7ae048de3ea", + "61255ac38231087c79ea1a0fa14538c26be1c851b6f318c0", + "f9712b8e42f0532162822f142cb946c40369f2f0e77b6b186e", + "76da0b89558df66f9b1e66a61d1e795b178ce77a359087793ff2", + "9036fd1eb32061bdecebc4a32aa524b343b8098a16768ee774d93c", + "f4ce5a05934e125d159678bea521f585574bcf9572629f155f63efcc", + "5e1c0d9fae56393445d3024d6b82692d1339f7b5936f68b062c691d3bf", + "538e35f3e11111d7c4bab69f83b30ade4f67addf1f45cdd2ac74bf299509", + "17572c4dcbb17faf8785f3bba9f6903895394352eae79b01ebd758377694cc", + "29f6bb55de7f8868e053176c878c9fe6c2055c4c5413b51ab0386c277fdbac75", + "bad026c8b2bd3d294907f2280a7145253ec2117d76e3800357be6d431b16366e41", + "386b7cb6e0fd4b27783125cbe80065af8eb9981fafc3ed18d8120863d972fa7427d9", + "06e8e6e26e756fff0b83b226dce974c21f970e44fb5b3e5bbada6e4b12f81cca666f48", + "2f9bd300244f5bc093ba6dcdb4a89fa29da22b1de9d2c9762af919b5fedf6998fbda305b", + "cf6bdcc46d788074511f9e8f0a4b86704365b2d3f98340b8db53920c385b959a38c8869ae7", + "1171e603e5cdeb4cda8fd7890222dd8390ede87b6f3284cac0f0d832d8250c9200715af7913d", + "bda7b2ad5d02bd35ffb009bdd72b7d7bc9c28b3a32f32b0ba31d6cbd3ee87c60b7b98c03404621", + "2001455324e748503aa08eff2fb2e52ae0170e81a6e9368ada054a36ca340fb779393fb045ac72b3", + "45f0761aefafbf87a68f9f1f801148d9bba52616ad5ee8e8ac9207e9846a782f487d5cca8b20355a18", + "3a7e05708be62f087f17b41ac9f20e4ef8115c5ab6d08e84d46af8c273fb46d3ce1aabebae5eea14e018", + "ea318da9d042ca337ccdfb2bee3e96ecb8f907876c8d143e8e44569178353c2e593e4a82c265931ba1dd79", + "e0f7c08f5bd712f87094b04528fadb283d83c9ceb82a3e39ec31c19a42a1a1c3bee5613b5640abe069b0d690", + "d35e63fb1f3f52ab8f7c6cd7c8247e9799042e53922fbaea808ab979fa0c096588cfea3009181d2f93002dfc11", + "b8b0ab69e3ae55a8699eb481dd665b6a2424c89bc6b7cca02d15fdf1b9854139cab49d34de498b50b2c7e8b910cf", + "fb65e3222a2950eae1701d4cdd4736266f65bf2c0d2e77968996eadb60ef74fb786f6234973a2524bdfe32d100aa0e", + "f28b4bb3a2e2c4d5c01a23ff134558559a2d3d704b75402983ee4e0f71d273ae056842c4153b18ee5c47e2bfa54313d4", + "7bb78794e58a53c3e4b1aeb161e756af051583d14e0a5a3205e094b7c9a8cf62d098fa9ea1db12f330a51ab9852c17f983", + "a879a8ebae4d0987789bcc58ec3448e35ba1fa1ee58c668d8295aba4eaeaf2762b053a677e25404f635a53037996974d418a", + "695865b353ec701ecc1cb38f3154489eed0d39829fc192bb68db286d20fa0a64235cde5639137819f7e99f86bd89afcef84a0f", + "a6ec25f369f71176952fb9b33305dc768589a6070463ee4c35996e1ced4964a865a5c3dc8f0d809eab71366450de702318e4834d", + "604749f7bfadb069a036409ffac5ba291fa05be8cba2f141554132f56d9bcb88d1ce12f2004cd3ade1aa66a26e6ef64e327514096d", + "daf9fa7dc2464a899533594e7916fc9bc585bd29dd60c930f3bfa78bc47f6c8439448043a45119fc9228c15bce5fd24f46baf9de736b", + "943ea5647a8666763084da6a6f15dcf0e8dc24f27fd0d9194805d25180fe3a6d98f4b2b5e0d6a04e9b41869817030f16ae975dd41fc35c", + "af4f73cbfc093760dfeb52d57ef45207bbd1a515f5523404e5d95a73c237d97ae65bd195b472de6d514c2c448b12fafc282166da132258e9", + "605f4ed72ed7f5046a342fe4cf6808100d4632e610d59f7ebb016e367d0ff0a95cf45b02c727ba71f147e95212f52046804d376c918cadd260", + "3750d8ab0a6b13f78e51d321dfd1aa801680e958de45b7b977d05732ee39f856b27cb2bcce8fbf3db6666d35e21244c2881fdcc27fbfea6b1672", + "8f1b929e80ab752b58abe9731b7b34eb61369536995abef1c0980d93903c1880da3637d367456895f0cb4769d6de3a979e38ed6f5f6ac4d48e9b32", + "d8469b7aa538b36cdc711a591d60dafecca22bd421973a70e2deef72f69d8014a6f0064eabfbebf5383cbb90f452c6e113d2110e4b1092c54a38b857", + "7d1f1ad2029f4880e1898af8289c23bc933a40863cc4ab697fead79c58b6b8e25b68cf5324579b0fe879fe7a12e6d03907f0140dfe7b29d33d6109ecf1", + "87a77aca6d551642288a0dff66078225ae39d288801607429d6725ca949eed7a6f199dd8a65523b4ee7cfa4187400e96597bfffc3e38ade0ae0ab88536a9", + "e101f43179d8e8546e5ce6a96d7556b7e6b9d4a7d00e7aade5579d085d527ce34a9329551ebcaf6ba946949bbe38e30a62ae344c1950b4bde55306b3bac432", + "4324561d76c370ef35ac36a4adf8f3773a50d86504bd284f71f7ce9e2bc4c1f1d34a7fb2d67561d101955d448b67577eb30dfee96a95c7f921ef53e20be8bc44", + "78f0ed6e220b3da3cc9381563b2f72c8dc830cb0f39a48c6ae479a6a78dcfa94002631dec467e9e9b47cc8f0887eb680e340aec3ec009d4a33d241533c76c8ca8c", + "9f6589c31a472e0a736f4eb22b6c70a9d332cc15304ccb66a6b97cd051b6ed82f8990e1d9bee2e4bb1c3c45e550ae0e7b96e93ae23f2fb8f63b309131e72b36cba6a", + "c138077ee4ed3d7ffa85ba851dfdf6e9843fc1dc00889d117237bfaad9aa757192f73556b959f98e6d24886ce48869f2a01a48c371785f12b6484eb2078f08c22066e1", + "f83e7c9e0954a500576ea1fc90a3db2cbd7994eaef647dab5b34e88ab9dc0b47addbc807b21c8e6dd3d0bd357f008471d4f3e0abb18450e1d4919e03a34545b9643f870e", + "3277a11f2628544fc66f50428f1ad56bcba6ee36ba2ca6ecdf7e255effc0c30235c039d13e01f04cf1efe95b5c2033ab72adda30994b62f2851d17c9920eadca9a251752dc", + "c2a834281a06fe7b730d3a03f90761daf02714c066e33fc07e1f59ac801ec2f4433486b5a2da8faa51a0cf3c34e29b2960cd0013378938dbd47c3a3d12d70db01d7d06c3e91e", + "47680182924a51cabe142a6175c9253e8ba7ea579ece8d9bcb78b1e9ca00db844fa08abcf41702bd758ee2c608d9612fed50e85854469cb4ef3038acf1e35b6ba4390561d8ae82", + "cec45830cd71869e83b109a99a3cd7d935f83a95de7c582f3adbd34e4938fa2f3f922f52f14f169c38cc6618d3f306a8a4d607b345b8a9c48017136fbf825aecf7b620e85f837fae", + "46fb53c70ab105079d5d78dc60eaa30d938f26e4d0b9df122e21ec85deda94744c1daf8038b8a6652d1ff3e7e15376f5abd30e564784a999f665078340d66b0e939e0c2ef03f9c08bb", + "7b0dcb52791a170cc52f2e8b95d8956f325c3751d3ef3b2b83b41d82d4496b46228a750d02b71a96012e56b0720949ca77dc68be9b1ef1ad6d6a5ceb86bf565cb972279039e209dddcdc", + "7153fd43e6b05f5e1a4401e0fef954a737ed142ec2f60bc4daeef9ce73ea1b40a0fcaf1a1e03a3513f930dd5335723632f59f7297fe3a98b68e125eadf478eb045ed9fc4ee566d13f537f5", + "c7f569c79c801dab50e9d9ca6542f25774b3841e49c83efe0b89109f569509ce7887bc0d2b57b50320eb81fab9017f16c4c870e59edb6c26620d93748500231d70a36f48a7c60747ca2d5986", + "0a81e0c547648595adca65623ce783411aac7f7d30c3ad269efafab288e7186f6895261972f5137877669c550f34f5128850ebb50e1884814ea1055ee29a866afd04b2087abed02d9592573428", + "6a7b6769e1f1c95314b0c7fe77013567891bd23416374f23e4f43e27bc4c55cfada13b53b1581948e07fb96a50676baa2756db0988077b0f27d36ac088e0ff0fe72eda1e8eb4b8facff3218d9af0", + "a399474595cb1ccab6107f18e80f03b1707745c7bf769fc9f260094dc9f8bc6fe09271cb0b131ebb2acd073de4a6521c8368e664278be86be216d1622393f23435fae4fbc6a2e7c961282a777c2d75", + "4f0fc590b2755a515ae6b46e9628092369d9c8e589e3239320639aa8f7aa44f8111c7c4b3fdbe6e55e036fbf5ebc9c0aa87a4e66851c11e86f6cbf0bd9eb1c98a378c7a7d3af900f55ee108b59bc9e5c", + "ed96a046f08dd675107331d267379c6fce3c352a9f8d7b243008a74cb4e9410836afaabe871dab6038ca94ce5f6d41fa922ce08aba58169f94cfc86d9f688f396abd24c11a6a9b0830572105a477c33e92", + "379955f539abf0eb2972ee99ed9546c4bbee363403991833005dc27904c271ef22a799bc32cb39f08d2e4ba6717d55153feb692d7c5efae70890bf29d96df02333c7b05ccc314e4835b018fec9141a82c745", + "e16cc8d41b96547ede0d0cf4d908c5fa393399daa4a9696e76a4c1f6a2a9fef70f17fb53551a8145ed88f18db8fe780a079d94732437023f7c1d1849ef69ad536a76204239e8ba5d97e507c36c7d042f87fe0e", + "a81de50750ece3f84536728f227208bf01ec5b7721579d007de72c88ee20663318332efe5bc7c09ad1fa8342be51f0609046ccf760a7957a7d8dc88941adb93666a4521ebe76618e5ddc2dd3261493d400b50073", + "b72c5fb7c7f60d243928fa41a2d711157b96aef290185c64b4de3dcfa3d644da67a8f37c2ac55caad79ec695a473e8b481f658c497edb8a191526592b11a412282d2a4010c90ef4647bd6ce745ebc9244a71d4876b", + "9550703877079c90e200e830f277b605624954c549e729c359ee01ee2b07741ecc4255cb37f96682dafcdbaade1063e2c5ccbd1918fb669926a67744101fb6de3ac016be4c74165a1e5a696b704ba2ebf4a953d44b95", + "a17eb44d4de502dc04a80d5a5e9507d17f27c96467f24c79b06bc98a4c410741d4ac2db98ec02c2a976d788531f1a4451b6c6204cef6dae1b6ebbcd0bde23e6fffb02754043c8fd3c783d90a670b16879ce68b5554fe1c", + "41d3ea1eaba5be4a206732dbb5b70b79b66a6e5908795ad4fb7cf9e67efb13f06fef8f90acb080ce082aadec6a1b543af759ab63fa6f1d3941186482b0c2b312f1151ea8386253a13ed3708093279b8eb04185636488b226", + "5e7cdd8373dc42a243c96013cd29df9283b5f28bb50453a903c85e2ce57f35861bf93f03029072b70dac0804e7d51fd0c578c8d9fa619f1e9ce3d8044f65d55634dba611280c1d5cfb59c836a595c803124f696b07ddfac718", + "26a14c4aa168907cb5de0d12a82e1373a128fb21f2ed11feba108b1bebce934ad63ed89f4ed7ea5e0bc8846e4fc10142f82de0bebd39d68f7874f615c3a9c896bab34190e85df05aaa316e14820b5e478d838fa89dfc94a7fc1e", + "0211dfc3c35881adc170e4ba6daab1b702dff88933db9a6829a76b8f4a7c2a6d658117132a974f0a0b3a38ceea1efc2488da21905345909e1d859921dc2b5054f09bce8eeb91fa2fc6d048ce00b9cd655e6aafbdaa3a2f19270a16", + "ddf015b01b68c4f5f72c3145d54049867d99ee6bef24282abf0eecdb506e295bacf8f23ffa65a4cd891f76a046b9dd82cae43a8d01e18a8dff3b50aeb92672be69d7c087ec1fa2d3b2a39196ea5b49b7baede37a586fea71aded587f", + "6ee721f71ca4dd5c9ce7873c5c04c6ce76a2c824b984251c15535afc96adc9a4d48ca314bfeb6b8ee65092f14cf2a7ca9614e1dcf24c2a7f0f0c11207d3d8aed4af92873b56e8b9ba2fbd659c3f4ca90fa24f113f74a37181bf0fdf758", + "689bd150e65ac123612524f720f54def78c095eaab8a87b8bcc72b443408e3227f5c8e2bd5af9bcac684d497bc3e41b7a022c28fb5458b95e8dfa2e8caccde0492936ff1902476bb7b4ef2125b19aca2cd3384d922d9f36dddbcd96ae0d6", + "3a3c0ef066fa4390ec76ad6be1dc9c31ddf45fef43fbfa1f49b439caa2eb9f3042253a9853e96a9cf86b4f873785a5d2c5d3b05f6501bc876e09031188e05f48937bf3c9b667d14800db62437590b84ce96aa70bb5141ee2ea41b55a6fd944", + "741ce384e5e0edaebb136701ce38b3d33215415197758ae81235307a4115777d4dab23891db530c6d28f63a957428391421f742789a0e04c99c828373d9903b64dd57f26b3a38b67df829ae243feef731ead0abfca049924667fdec49d40f665", + "a513f450d66cd5a48a115aee862c65b26e836f35a5eb6894a80519e2cd96cc4cad8ed7eb922b4fc9bbc55c973089d627b1da9c3a95f6c019ef1d47143cc545b15e4244424be28199c51a5efc7234dcd94e72d229897c392af85f523c2633427825", + "71f1554d2d49bb7bd9e62e71fa049fb54a2c097032f61ebda669b3e1d4593962e47fc62a0ab5d85706aebd6a2f9a192c88aa1ee2f6a46710cf4af6d3c25b7e68ad5c3db23ac009c8f13625ff85dc8e50a9a1b2682d3329330b973ec8cbb7bb73b2bd", + "167cc1067bc08a8d2c1a0c10041ebe1fc327b37043f6bd8f1c63569e9d36ded58519e66b162f34b6d8f1107ef1e3de199d97b36b44141a1fc4f49b883f40507ff11f909a017869dc8a2357fc7336ae68703d25f75710b0ff5f9765321c0fa53a51675c", + "cb859b35dc70e264efaad2a809fea1e71cd4a3f924be3b5a13f8687a1166b538c40b2ad51d5c3e47b0de482497382673140f547068ff0b3b0fb7501209e1bf36082509ae85f60bb98fd02ac50d883a1a8daa704952d83c1f6da60c9624bc7c99912930bf", + "afb1f0c6b7125b04fa2578dd40f60cb411b35ebc7026c702e25b3f0ae3d4695d44cfdf37cb755691dd9c365edadf21ee44245620e6a24d4c2497135b37cd7ac67e3bd0aaee9f63f107746f9b88859ea902bc7d6895406aa2161f480cad56327d0a5bba2836", + "13e9c0522587460d90c7cb354604de8f1bf850e75b4b176bda92862d35ec810861f7d5e7ff6ba9302f2c2c8642ff8b7776a2f53665790f570fcef3cac069a90d50db42227331c4affb33d6c040d75b9aeafc9086eb83ced38bb02c759e95ba08c92b17031288", + "0549812d62d3ed497307673a4806a21060987a4dbbf43d352b9b170a29240954cf04bc3e1e250476e6800b79e843a8bd8253b7d743de01ab336e978d4bea384eaff700ce020691647411b10a60acacb6f8837fb08ad666b8dcc9eaa87ccb42aef6914a3f3bc30a", + "3a263efbe1f2d463f20526e1d0fd735035fd3f808925f058b32c4d8788aeeab9b8ce233b3c34894731cd73361f465bd350395aebcabd2fb63010298ca025d849c1fa3cd573309b74d7f824bbfe383f09db24bcc565f636b877333206a6ad70815c3bef5574c5fc1c", + "3c6a7d8a84ef7e3eaa812fc1eb8e85105467230d2c9e4562edbfd808f4d1ac15d16b786cc6a02959c2bc17149c2ce74c6f85ee5ef22a8a96b9be1f197cffd214c1ab02a06a9227f37cd432579f8c28ff2b5ac91cca8ffe6240932739d56788c354e92c591e1dd76499", + "b571859294b02af17541a0b5e899a5f67d6f5e36d38255bc417486e69240db56b09cf2607fbf4f95d085a779358a8a8b41f36503438c1860c8f361ce0f2783a08b21bd7232b50ca6d35428335272a5c05b436b2631d8d5c84d60e8040083768ce56a250727fb0579dd5c", + "98ee1b7269d2a0dd490ca38d447279870ea55326571a1b430adbb2cf65c492131136f504145df3ab113a13abfb72c33663266b8bc9c458db4bf5d7ef03e1d3b8a99d5de0c024be8fabc8dc4f5dac82a0342d8ed65c329e7018d6997e69e29a01350516c86beaf153da65ac", + "41c5c95f088df320d35269e5bf86d10248f17aec6776f0fe653f1c356aae409788c938befeb67c86d1c8870e8099ca0ce61a80fbb5a6654c44529368f70fc9b9c2f912f5092047d0ffc339577d24142300e34948e086f62e23ecaca410d24f8a36b5c8c5a80e0926bc8aa16a", + "9f93c41f533b2a82a4df893c78faaaa793c1506974ba2a604cd33101713ca4adfd30819ffd8403402b8d40aff78106f3357f3e2c24312c0d3603a17184d7b999fc9908d14d50192aebabd90d05073da7af4be37dd3d81c90acc80e8333df546f17ab6874f1ec204392d1c0571e", + "3da5207245ac270a915fc91cdb314e5a2577c4f8e269c4e701f0d7493ba716de79935918b917a2bd5db98050dbd1eb3894b65fac5abf13e075abebc011e651c03cafb6127147771a5c8418223e1548137a89206635c26ca9c235ccc108dc25cf846e4732444bd0c2782b197b262b", + "96011af3965bb941dc8f749932ea484eccb9ba94e34b39f24c1e80410f96ce1d4f6e0aa5be606def4f54301e930493d4b55d484d93ab9dd4dc2c9cfb79345363af31ad42f4bd1aa6c77b8afc9f0d551bef7570b13b927afe3e7ac4de7603a0876d5edb1ad9be05e9ee8b53941e8f59", + "51dbbf2a7ca224e524e3454fe82ddc901fafd2120fa8603bc343f129484e9600f688586e040566de0351d1693829045232d04ff31aa6b80125c763faab2a9b233313d931903dcfaba490538b06e4688a35886dc24cdd32a13875e6acf45454a8eb8a315ab95e608ad8b6a49aef0e299a", + "5a6a422529e22104681e8b18d64bc0463a45df19ae2633751c7aae412c250f8fb2cd5e1270d3d0cf009c8aa69688ccd4e2b6536f5747a5bc479b20c135bf4e89d33a26118705a614c6be7ecfe766932471ad4ba01c4f045b1abb5070f90ec78439a27a1788db9327d1c32f939e5fb1d5ba", + "5d26c983642093cb12ff0afabd87b7c56e211d01844ad6da3f623b9f20a0c968034299f2a65e6673530c5980a532beb831c7d0697d12760445986681076dfb6fae5f3a4d8f17a0db5008ce8619f566d2cfe4cf2a6d6f9c3664e3a48564a351c0b3c945c5ee24587521e4112c57e318be1b6a", + "52641dbc6e36be4d905d8d60311e303e8e859cc47901ce30d6f67f152343e3c4030e3a33463793c19effd81fb7c4d631a9479a7505a983a052b1e948ce093b30efa595fab3a00f4cef9a2f664ceeb07ec61719212d58966bca9f00a7d7a8cb4024cf6476bab7fbccee5fd4e7c3f5e2b2975aa2", + "a34ce135b37bf3db1c4aaa4878b4499bd2ee17b85578fcaf605d41e1826b45fdaa1b083d8235dc642787f11469a5493e36806504fe2a2063905e821475e2d5ee217057950370492f5024995e77b82aa51b4f5bd8ea24dc71e0a8a640b0592c0d80c24a726169cf0a10b40944747113d03b52708c", + "46b3cdf4946e15a5334fc3244d6680f5fc132afa67bf43bfade23d0c9e0ec64e7dab76faaeca1870c05f96b7d019411d8b0873d9fed04fa5057c039d5949a4d592827f619471359d6171691cfa8a5d7cb07ef2804f6ccad4821c56d4988bea7765f660f09ef87405f0a80bcf8559efa111f2a0b419", + "8b9fc21691477f11252fca050b121c5334eb4280aa11659e267297de1fec2b2294c7ccee9b59a149b9930b08bd320d3943130930a7d931b71d2f10234f4480c67f1de883d9894ada5ed5071660e221d78ae402f1f05af47761e13fec979f2671e3c63fb0ae7aa1327cf9b8313adab90794a52686bbc4", + "cd6598924ce847de7ff45b20ac940aa6292a8a99b56a74eddc24f2cfb45797188614a21d4e8867e23ff75afd7cd324248d58fcf1ddc73fbd115dfa8c09e62022fab540a59f87c989c12a86ded05130939f00cd2f3b512963dfe0289f0e54acad881c1027d2a0292138fdee902d67d9669c0ca1034a9456", + "594e1cd7337248704e691854af0fdb021067ddf7832b049ba7b684438c32b029eded2df2c89a6ff5f2f2c311522ae2dc6db5a815afc60637b15ec24ef9541f1550409db2a006da3affffe548a1eaee7bd114e9b805d0756c8e90c4dc33cb05226bc2b393b18d953f8730d4c7ae693159cdba758ad28964e2", + "1f0d292453f04406ada8be4c161b82e3cdd69099a8637659e0ee40b8f6da46005cfc6085db9804852decfbe9f7b4dda019a7112612895a144ed430a960c8b2f5458d3d56b7f427cee6358915aee7146278aed2a0296cdd929e4d21ef95a3adf8b7a6beba673cdccdbdcfb2474711732d972ad054b2dc64f38d", + "b65a72d4e1f9f9f75911cc46ad0806b9b18c87d105332a3fe183f45f063a746c892dc6c4b9181b1485b3e3a2cc3b453eba2d4c39d6905a774ed3fb755468beb190925ecd8e57ecb0d985125741650c6b6a1b2a3a50e93e3892c21d47ed5884eed83aa94e1602288f2f49fe286624de9d01fcb54433a0dc4ad70b", + "705ce0ffa469250782aff725248fc88fe98eb76659e8407edc1c4842c9867d61fe64fb86f74e980598b92bc213d06f337bd5654fc28643c7ba769a4c31563427543c00808b627a19c90d86c322f33566ce020121cc322229c3337943d46f68ef939d613dcef0077269f88151d6398b6b009abb763410b154ad76a3", + "7fa881ce87498440ab6af13854f0d851a7e0404de33896999a9b3292a5d2f5b3ad033530c558168fe5d2fdb9b89a2354c46cf32a0e612afc6c6485d789511bfef26800c74bf1a4cfbe30bda310d5f6029c3dccdedb6149e4971274e276dccfabd63bc4b9955e8303feb57f8a688db55ecb4b33d1f9fe1b3a8ba7ac32", + "23a98f71c01c0408ae16843dc03be7db0aeaf055f951709d4e0dfdf64fffbffaf900ee592ee10929648e56f6c1e9f5be5793f7df66453eb56502c7c56c0f0c88da77abc8fa371e434104627ef7c663c49f40998dbad63fa6c7aa4fac17ae138d8bbe081f9bd168cd33c1fbc92fa35ed687679f48a64b87db1fe5bae675", + "7b8970b6a33237e5a7bcb39272703edb92285c55842b30b9a48834b1b507cc02a6764739f2f7ee6ae02a7b715a1c455e59e8c77a1ae98abb10161853f1234d20da99016588cd8602d6b7ec7e177d4011edfa61e6b3766a3c6f8d6e9eac893c568903eb6e6aba9c4725774f6b4343b7acaa6c031593a36eef6c72806ff309", + "f7f4d328ba108b7b1de4443e889a985ed52f485f3ca4e0c246aa5526590cbed344e9f4fe53e4eea0e761c82324649206ca8c2b45152157d4115e68c818644b03b65bb47ad79f94d37cb03c1d953b74c2b8adfa0e1c418bda9c518ddcd7050e0f149044740a2b16479413b63fc13c36144f80c73687513dca761ba8642a8ae0", + "2d7dc80c19a1d12d5fe3963569547a5d1d3e821e6f06c5d5e2c09401f946c9f7e13cd019f2f9a878b62dd850453b6294b99ccaa068e542993524b0f63832d48e865be31e8ec1ee103c718340c904b32efb69170b67f038d50a3252794b1b4076c0620621ab3d91215d55ffea99f23d54e161a90d8d4902fda5931d9f6a27146a", + "77dff4c7ad30c954338c4b23639dae4b275086cbe654d401a2343528065e4c9f1f2eca22aa025d49ca823e76fdbb35df78b1e5075ff2c82b680bca385c6d57f7ea7d1030bb392527b25dd73e9eeff97bea397cf3b9dda0c817a9c870ed12c006cc054968c64000e0da874e9b7d7d621b0679866912243ea096c7b38a1344e98f74", + "83bed0d556798f2b419f7056e6d3ffada06e939b95a688d0ec8c6ac5ea45ab73a4cf01043e0a170766e21395f27ab4b78c435f5f0dfe6e93ab80df38610e41158429ddf20296f53a06a017723359fe22dc08b5da33f0800a4fe50118e8d7eab2f83a85cd764bf8a166903bd0e9dcfeeceba44ff4ca4439846458d31ea2bb564645d1", + "ea12cf5a113543e39504123036f15a5bafa9c555562469f99cd29996a4dfaaab2a34b00557ccf15f37fc0cc1b3be427e725f2cd952e50af7970dda9200cd5ce252b1f29c40067fea3027ed686190803b59d834179d1b8f5b55abe55ad174b2a1188f7753ec0ae2fc01316e7d498b68ee3598a0e9baaaa664a60f7fb4f90edbed494ad7", + "55266358332d8d9e68bd13432088beadf95833aab67a0eb3b10650414255f299e2670c3e1a5b2976159a46c72a7ce57d59b7be14c15798e09ed50fa312a431b0264d7a1396aa6168bde897e208ece53d2cfc83786113b1e6eac5e9bb98984abb6c8d64eebb991903254abc650c999bb9958a5d7937434b869bc940e21b9dc1cc8982f2ba", + "4d6104ded730aefe02873f4c741232c8234a6d66d85393aff57fbf56ba6347666988dfc4d58f3cc895a0da598822edeee4533d24ec0ee292fd5e1ad04898ffbc1ff4bef14dec220babcb0f28fffe32a6e2c28aaaac16442bf4feb02917d18bb3a415d84fa9358d5a9852688d846c92271911f934181c30f82434d915f93f155a1ffbf0b125", + "eb5f579a4c476af554aac11e5719d378549497e613b35a929d6f36bb8831d7a466aa76de9be24ebb55543f1c13924f64cfd648a5b3fa90387315c16174dbf1e9a183c196d9bb8f84af65f1f8212429aadc11ef2426d07d4716062b85c8d5d2dff8e21b9e62b7fa7dbd57d72633054b464fb28583a56ca13ccc5ddc74dae942492f31731e7046", + "ebddec3dcaf18063e45a76ebeac39af85a1adc2818881ccce48c106288f5988365cca2b4b1d7f037322da46840f42bebdcbc7193838d426e101087d8cea03aaff743d573eb4f4e9a71a2c884390769a6503874125d194bee8d46a3a0d5e4fcf28ff8465887d8e9df771d70157e75df3642b331d2778ceb32ceba868640171ab7a5d22eede1ee44", + "26d87ec70b57691e3bb359633d3ddba17f029d62cdfe977f5fd42274d79b444a32494d1c01e9f72d03cce78c806df96e93ea78da3a054209924ed765edc4d570f66168dc25ee3114e4017e387440349c8f0a94804761c3055f88e4fda2a49b860b1486a9609095f6250f268b6a4d1aecc03a505632ebf0b9dc22d0755a736faf7ad7000858b5864b", + "3880f5cc2d08fa70ef44b1f263fcf534d062a298c1bd5ee2eee8c3265806c4ce50b004f3a1fc1fa5b024aaac7f528c023c8181f67c6e1c357425dc4d573bd46b93a542afa3a19bdb140a2ce666e1a01f5c4d2dcd681fa9f5839b797813c394738d5ee4971386c12c7c117d17c7bec324b760aa30cda9ab2aa850284ba6fa97946f710f02449d1883c6", + "3317d2f452105dd3f4a96f9257af8285a80be58066b50f6f54bd633749b49f6ab9d57d45652d2ae852a2f6940cd5ec3159dd7f333358b12f502325df38843508faf7e246352d201280babd90b14fbf7722641c3601d0e458474439973c611bb5502fd0eb3078f87124ca7e1a016fcb6cfeff65f6a565985aca7122cfa8c5a11da0cb47797c5132333179", + "f2c5c955d0224e784a46b9125f8fef8a5e1271e145eb08bbbd07ca8e1cfc848cef14fa3b36221ac62006403dbb7f7d77958ccc54a8566c837858b809f3e310ace8ca682515bc655d2a397cab238a663b464d511f02dc5d033dad4cb5e0e519e94a54b62a3896e460ec70e5716b5921bf8396aa86a60123e6287e34570bb01bdc602e113670bf498af2ff10", + "180e275205691a83630cf4b0c7b80e6df8fad6ef1c23ba8013d2f09aef7abade1827f23af230de90676240b4b3b0673f8afdea0327330055041741f65560d90348de696d34ca80dfe8afae582fe4879d4594b80e9408fb53e800e01ca58552b905c365e7f1416e51c080f517d6bbd30e64ae1535d59decdc76c6624d737868f49f2f719da39ba1344d59eab9", + "c517a84e4631a7f65ace170d1e5c2fdb259841535d88da323e68c0883e6af7b041cfe05908815a5a9d1b14fa712c2c16fadcf1ca54d3aa954d411240df331b2aebdfb65aced84d0b8aace56ec0aa7c13ec7d75ca883b6bcf6db74c9e98463c484a8262684f29910373430651f90ecffe18b072170e61ee58de20e2a6ff67b3ab00fccbb80af943f20b56b98107", + "d1a56a5ee990e02b84b5862fde62f69ec07567be2d7ccb769a461c4989d11fdda6c945d942fb8b2da795ed97e43a5b7dbdde7f8fd2ff7154544336d5c50fb7380341e660d4898c7fbc39b2b782f28defac6873523c7c1de8e52c65e4395c686ba483c35a220b0416d46357a063fa4c33fa9c52d5c207a1304ae141c791e62ba6a7374ed922b8dd94079b72b69302", + "4720b88d6bfb1ab43958e26827730d852d9ec30173ebd0fe0d273edcece2e788558984cd9306fe5978086a5cb6d37975755d2a3daeb16f99a8a11544b8247a8b7ed5587afc5bea1daf85dcea5703c5905cf56ae7cc76408ccabb8fcc25cacc5ff456db3f62fa559c45b9c71505eb5073df1f10fc4c9060843f0cd68bbb4e8edfb48d0fd81d9c21e53b28a2aae4f7ba", + "f4639b511db9e092823d47d2947efacbaae0e5b912dec3b284d2350b9262f3a51796a0cd9f8bc5a65879d6578ec24a060e293100c2e12ad82d5b2a0e9d22965858030e7cdf2ab3562bfa8ac084c6e8237aa22f54b94c4e92d69f22169ced6c85a293f5e16bfc326153bf629cdd6393675c6627cd949cd367eef02e0f54779f4d5210197698e4754a5fe490a3a7521c1c", + "3d9e7a860a718565e3670c29079ce80e381969fea91017cfd5952e0d8a4a79bb08e2cd1e26161f30ee03a24891d1bfa8c212861b51618d07429fb48000ff87ef09c6fca526567777e9c076d58a642d5c521b1caa5fb0fb3a4b8982dc14a444732b72b239b8f01fc8ba8ee86b3013b5d3e98a92b2aeaecd4879fca5d5e9e0bd880dbfffa6f96f94f3998812aac6a714f331", + "4d9bf551d7fd531e7482e2ec875c0651b0bcc6caa738f7497befd11e67ae0e036c9d7ae4301cc3c7906f0d0e1ed4738753f414f9b3cd9b8a71176e325c4c74ce020680ecbfb146889597f5b40487e93f974cd866817fb9fb24c7c7c16177e6e120bfe349e83aa82ba40e59e917565788658a2b254f25cf99bc65070b3794cea2259eb10e42bb54852cba3110baa773dcd70c", + "b91f65ab5bc059bfa5b43b6ebae243b1c46826f3da061338b5af02b2da76bb5ebad2b426de3c3134a633499c7c36a120369727cb48a0c6cbab0acecdda137057159aa117a5d687c4286868f561a272e0c18966b2fec3e55d75abea818ce2d339e26adc005c2658493fe06271ad0cc33fcb25065e6a2a286af45a518aee5e2532f81ec9256f93ff2d0d41c9b9a2efdb1a2af899", + "736f6e387acb9acbee026a6080f8a9eb8dbb5d7c54ac7053ce75dd184b2cb7b942e22a3497419ddb3a04cf9e4eb9340a1a6f9474c06ee1dcfc8513979fee1fc4768087617fd424f4d65f54782c787a1d2de6efc81534343e855f20b3f3589027a5436201eee747d45b9b8375e4294d72ab6a52e04dfbb2914db92ee58f134b026527ed52d4f794459e02a43a17b0d51ea69bd7f3", + "9242d3eb31d26d923b99d66954cfade94f25a18912e6356810b63b971ae74bb53bc58b3c01424208ea1e0b1499936daea27e63d904f9ed65fdf69de40780a3027b2e89d94bdf214f585472613ce328f628f4f0d56217dfb53db5f7a07f54c8d71db16e27de7cdb8d23988837b49b65c12f1771d979e8b192c9f4a16b8d9fba917bcf74ce5a82aac2075608ba6c2d485fa59864b9de", + "5da68704f4b592d41f08aca08f62d85e2e2466e5f3be010315d11d113db674c4b98764a509a2f5aacc7ae72c9deff2bcc42810b47f64d429b35745b9efff0b18c58653461e968aaa3c2c7fc455bc5771a8f10cd184be831040df767201ab8d32cb9a58c89afbebecb524502c9b940c1b838f8361bbcde90d272715017f67609ea39b20fac985332d82daaa023999e3f8bfa5f3758bb8", + "71ea2af9c8ac2e5ae44a176662882e01027ca3cdb41ec2c6785606a07d7231cd4a2bded7155c2feef3d44d8fd42afa73265cef826f6e03aa761c5c51d5b1f129ddc27503ff50d9c2d748322df4b13dd5cdc7d46381528ab22b79b0049011e4d2e57fe2735e0d58d8d56e92c75dbeac8c76c4239d7f3f24fb56697593b3e4afa6671d5bbc96c079a1c154fe20212ade67b05d49ceaa7a84", + "1d133170582fa4bff59a21953ebbc01bc202d43cd79c083d1f5c02fa15a43a0f519e36acb710bdabac880f04bc003800641c2487930de9c03c0e0deb347fa815efca0a38c6c5de694db698743bc955581f6a945deec4ae988ef7cdf40498b77796ddea3fae0ea844891ab751c7ee20917c5a4af53cd4ebd82170078f41ada2795e6eea17593fa90cbf5290a1095e299fc7f507f360f187cd", + "5ec4ac45d48fc15c72471d795066bdf8e99a483d5fdd599511b9cdc408de7c0616491b73924d0266da34a495331a935c4b8884f57d7ad8cce4cbe586875aa52482215ed39d7626cce55d50349c7767981c8bd6890f132a196184247343566fc972b86fe3c5369d6a6519e9f07942f0522b77ad01c751dcf7defe31e471a0ec00963765dd8518144a3b8c3c978ad108056516a25dbe3092e73c", + "0d5e74b78290c689f2b3cfea45fc9b6a84c822639cd438a7f05c07c374adced42cdc12d2a9233a4ffe80307efc1ac13cb04300e165f8d90dd01c0ea955e7657332c6e86ad6b43e78ba4c13c675aed83192d8427866fb6484e6a3071b2369a46fba9005f31232da7ffec7952f831aaaddf63e225263531c2cf387f8cc14fa856c8795137142c3a52ffa69b8e30ebc88ce3bbc227597bcc8dddd89", + "a0fe36f983259921dc2fa7d89002b3066241d63bfc2448caf7e10522a35562be0bfedc3dce49cfce2e614a04d4c64cfc0ab898873a7fc26928dc1927c009d12f6f9b7a278205d3d0057604f4ac746f8b9287c3bc6b929832bf253b6586192ac43fdd29ba585dbd9059aab9c6ff6000a7867c67fec1457b733f6b620881166b8fed92bc8d84f0426002e7be7fcd6ee0abf3755e2babfe5636ca0b37", + "1d29b6d8eca793bb801becf90b7d7de215b17618ec32340da4bac707cdbb58b951d5036ec02e105d83b5960e2a72002d19b7fa8e1128cc7c5049ed1f76b82a59eac6ed09e56eb73d9ade38a6739f0e07155afa6ec0d9f5cf13c4b30f5f9a465b162a9c3ba04b5a0b3363c2a63f13f2a3b57c590ec6aa7f64f4dcf7f1582d0ca157eb3b3e53b20e306b1f24e9bda87397d413f01b453ceffeca1fb1e7", + "6a2860c110cd0fc5a19bcaafcd30762ee10242d34739638e716bd89fd537ea4dc630e6f85d1bd88a25ad3892ca554c232c9830bd56980c9f08d378d28f7fa6fa7df4fcbf6ad98b1adfff3ec1f63310e50f920c99a5200b8e64c2c2ca249399a149942261f737d5d72da949e914c024d57c4b639cb89990fed2b38a37e5bcd24d17ca12dfcd36ce04691fd03c32f6ed5de2a2191ed7c826375ba81f78d0", + "7132aa291ddc9210c60dbe7eb3c19f9053f2dd74742cf57fdc5df98312adbf4710a73245de4a0c3b24e21ab8b466a77ae29d15500d5142555ef3088cbccbe685ed9119a10755148f0b9f0dbcf02b2b9bcadc8517c88346ea4e78285e9cbab122f824cc18faf53b742a87c008bb6aa47eed8e1c8709b8c2b9adb4cc4f07fb423e5830a8e503ab4f7945a2a02ab0a019b65d4fd71dc364d07bdc6e637990e3", + "3e664da330f2c6007bff0d5101d88288aaacd3c07913c09e871cce16e55a39fde1ce4db6b8379977c46cce08983ca686778afe0a77a41baf447854b9aa286c398c2b83c95a127b053101b6799c1638e5efd67273b2618df6ec0b96d8d040e8c1ee01a99b9b5c8fe63fea2f749e6c90d31f6fae4e1469ac09884c4fe1a8539acb313f42c941224a0e79c059e18affc2bcb6724975c436f7bf949ebdd8aef51c", + "7a6ea63a271eb49470f5ce77519ed61ae9b2f1be07a96855726bc3df1d0723af3a703fdfc2e739c9d31d25814daf661a23558b50982e66ee37ad880f5c8f11c8130fac8a5d0250583700d5a324894fae6d61993f6bf9327214f8674649f355b23fd634940b2c467973a839e659169c773119919f5b81ee171edb2e5f6940d7551f9e5a70625d9ea88711ad0ed8ab2da720ad358bef954456cb2d5636425717c2", + "c5106bbda114168c449172e49590c7eeb827fa4e1a2a7a87a3c1f721a9047d0c0a50fbf244731be1b7eb1a2ef30f5ae846a9f38f0df44f32af61b68dbdcd0226e741dfb6ef81a2503691af5e4b3171f48c59ba4ef91eba344b5b697f261df7bbbb734ca6e6daebaa4a179feb17002823281b8534d55a6531c59305f6e3fd3fa63b747bcf0deb654c392a02fe687a269effb1238f38bcaea6b208b221c45fe7fbe7", + "597716a5ebeebc4bf524c15518816f0b5dcda39cc833c3d66b6368ce39f3fd02ceba8d12072bfe6137c68d3acd50c849873150928b320b4fbc31c1456679ea1d0acaeeabf666d1f1bad3e6b9312c5cbdecf9b799d3e30b0316bed5f41245107b693366accc8b2bcef2a6be54209ffabc0bb6f93377abdcd57d1b25a89e046f16d8fd00f99d1c0cd247aafa72234386ae484510c084ee609f08aad32a005a0a5710cb", + "0771ffe789f4135704b6970b617bae41666bc9a6939d47bd04282e140d5a861c44cf05e0aa57190f5b02e298f1431265a365d29e3127d6fccd86ec0df600e26bcdda2d8f487d2e4b38fbb20f1667591f9b5730930788f2691b9ee1564829d1ada15fffc53e785e0c5e5dd11705a5a71e390ca66f4a592785be188fefe89b4bd085b2024b22a210cb7f4a71c2ad215f082ec63746c7367c22aedb5601f513d9f1ffc1f3", + "be6556c94313739c115895a7bad2b620c0708e24f0390daa55521c31d2c6782acf41156271238885c367a57c72b4fe999c160e804ad58d8e565edbce14a2dd90e443eb80626b3eab9d7ab75d6f8a062d7ca89b7af8eb292c98eaf87ad1dfd0db103d1bb6188bd7e7a63502153cf3ce23d43b60c5782602bac8ad92fb2324f5a79453898c5de18415639ecc5c7974d3077f76fc1df5b956723bb19a624d7ea3ec13ba3d86", + "4bc33729f14cd2f1dc2ff459abee8f6860dda1062845e4adab78b53c835d106bdfa35dd9e77219eaef403d4e80488ca6bd1c93dd76ef9d543fbb7c8904dccc5f71509a6214f73d0f4e467c3e038ea639b29e7fc442ee29f57117740576188ada15a739827c647a46b0271817ab235c023c30c90f2115e5c90cd8501e7b286962fc66ffc3fe7e8978746168314908a41998bd83a1eeffda9d714b864f4d490fdeb9c7a6edfa", + "ab12faea205b3d3a803cf6cb32b9698c32301a1e7f7c6c23a20174c95e98b7c3cfe93fffb3c970face8f5751312a261741141b948d777b8a2ea286fe69fc8ac84d34116a4674bb09a1a0b6af90a748e511749de4697908f4acb22be08e96ebc58ab1690acf73914286c198a2b57f1dd70ea8a52325d3045b8bdfe9a09792521526b7564a2a5fcd01e291f1f8894017ce7d3e8a5dba15332fb410fcfc8d62195a48a9e7c86fc4", + "7d421e59a567af70594757a49809a9c22e07fe14061090b9a041875bb77933deae36c823a9b47044fa0599187c75426b6b5ed94982ab1af7882d9e952eca399ee80a8903c4bc8ebe7a0fb035b6b26a2a013536e57fa9c94b16f8c2753c9dd79fb568f638966b06da81ce87cd77ac0793b7a36c45b8687c995bf4414d28289dbee977e77bf05d931b4feaa359a397ca41be529910077c8d498e0e8fb06e8e660cc6ebf07b77a02f", + "0c18ab727725d62fd3a2714b7185c09faca130438eff1675b38beca7f93a6962d7b98cb300ea33067a2035cdd694348784aa2eda2f16c731eca119a050d3b3ce7d5c0fd6c234354a1da98c0642451922f670984d035f8c6f35031d6188bbeb31a95e99e21b26f6eb5e2af3c7f8eea426357b3b5f83e0029f4c4732bca366c9aa625748297f039327c276cd8d9c9bf692a47af098aa50ca97b99961bef8bc2a7a802e0b8cfdb84319", + "92d5909d18a8b2b9971cd1627b461e98a74ba377186a6a9df5bd133635250b300abccb2254cacb775df6d99f7c7d0952653c28e6909b9f9a45adce691f7adc1afffcd9b06e49f775364cc2c62825b9c1a86089080e26b57e732aac98d80d009bfe50df01b95205aa07ed8ec5c873da3b92d00d53af825aa64b3c634c5ece40bff152c331222d3453fd92e0ca17cef19ecb96a6eed4961b627aca48b12fecd091754f770d52ba861546", + "802f22e4a388e874927fef24c797408254e03910bab5bf372320207f8067f2b1ea543917d4a27df89f5bf936ba12e04302bde23119533d0976beca9e20cc16b4dbf17a2ddc44b66aba76c61ad59d5e90de02a88327ead0a8b75463a1a68e307a6e2e53ecc1986274b9ee80bc9f3140671d5285bc5fb57b281042a8978a1175900c6073fd7bd740122956602c1aa773dd2896674d0a6beab24454b107f7c847acb31a0d332b4dfc5e3f2f", + "3844fe65db11c92fb90bf15e2e0cd216b5b5be91604baf3b84a0ca480e41ecfaca3709b32f8c6e8761406a635b88eec91e075c48799a16ca08f295d9766d74475c47f3f2a274eae8a6ee1d191a7f37ee413a4bf42cad52acd5564a651715ae42ac2cddd52f819c692ecdef52ecb763270322cdca7bd5aef71428fa73e844568b96b43c89bf1ed42a0abf209ffad0eeec286c6f141e8af073ba4adfbbdeda253752ae36c9957dfc905b4c49", + "329377f7bf3c8d74991a7d61b0cf39baff5d485d79751b0d5ad017d23bec570fb19810105bab79ab5acb102ab972165224d4ec888ec7de5148077fa9c1bb6820e0d91ae4e2591a21fec2f820606ce4bafc1e377f8dc3a5bd1a9e2772a57abccd0b757164d768872c91d02789545ab5b203f688d71dd08522a3fd2f5bcd7df507aebf1ca27ddff0a82afb7aa9c180008f49d1325adf97d047e77238fc75f56356de4e87d8c961575c9f6362c9", + "f7f269929b0d71ea8eef7120e55ccba691c582dd534692abef35c0fe9dec7dae973cd9702e5ad420d278fe0e653fdcb22fdcb63148109ec7e94f2d0750b28157dd1764376ae10fdb0a4aef3b304bd82793e0595f941226a2d72abbc929f53134dc495b0d65ced409914f94c2523f3dfbbdeeac84ae247ab5d1b9ea33dce1a808885a55be1f3683b46f4be73d9b62eec2585f690056858dfc427aabf591cd276724885bcd4c00b93bb51fb7484d", + "ac022309aa2c4d7fb628255b8b7fb4c3e3ae64b1cb65e0de711a6def1653d95d8088871cb8905fe8ae76423604988a8f77589f3f776dc1e4b30dbe9dd262b2187db02518a132d219bd1a06ebac13132b5164b6c420b37dd2ccee7d69b3b7fa12e54f0a53b853d490a68379ea1fa2d79762830ffb71bf86aab506b51f85c4b6a41b69325c7d0c7aa85b93b7144489d213e8f33dbb879fce22849865337b620b155cb2d2d36a68832889e30194d36d", + "d009c2b78a8f02e5e5dbb586ef71fc324b375092e15913ca1a5bfd22d516baadb96867bee3562e77c4a4852344a1a76c30728be5e22400b4cc41711f66754c246a520498d8c24f0205b9c873748dbeb67fe1ad099ad04cf89f4b517f0aa481136d9f6de2d727df01c6aa4099da59d4382b51e25fd47c33d9842c32b62331e50794bfe8b61b3ba9de1b8b704779c6d65edff3af00f121ab4a7ea384edabe47c6d0098a48991f387ca4444135ec59d46", + "c00bab36cce69899817d1425016d222d7303197ed3e3fdcac744705e7f178a1ac745968900f69299163e19b3161f3e0a4cc55aa2e4e71e0ee6ac427d1f4d14e063f68d303ddfbb18118335cfa7a6a90d99c38319ee76f7a884846a9e0b68030bf28e78bfbd56359b9368842814da42b04cb0e307d5d846dc22f049147bae31b9a956d17676a8cc348dafa3cabc2007a30e730e3894dddf9999fb8819086311f0703e141613ed6dcd7af8510e2dc435b0", + "c9789152a9fc29698d49ed95f09bd11b75f18a8c5615a73dbe54ae5e550027fd0ae6a8b60667040c1b12de3d1ee3f6bf061c78c951a3210effc912e19f482dd4de152063c588c44903bc11761706fd935afa040df085b08144d83d0dde32b46ab52f4fae98ac116c7ff11d7f553450c2e37b9c5f0b1dd9e0b8640a24cba6f2a5246c41f197f46e3dc8a29131c79bef3351c6e277a0a34442274d546ccd058891277473d668420f121750d19cd684267405", + "06a15a0731ce52557e368bcbaa11ef3399299e36fb9f2eda6e5726907c1d29c5c6fc581405ba48c7e2e522206a8f128d7c1c939d1132a00bd7d6366aa82724e968964eb2e373563f607dfa649590dcf5589114df69da5547fef8d1604cc4c6de1ed5783c8746918a4dd31168d6bc8784cd0c769206bd803d6ca8557b66748770402b075ef44b38157d4c0da7c6281725a2065d087b1f7b23455fa673bdeeba45b983311c44eabe9ef4b7bde3420ae9881863", + "d08aacef2d7a41aec09473bd8a44f628e15addb7b9e5b77a1e09c8ab4942f379a0bfcb324d580b774666f18ae78dd36710824ff12393f059068fe4b559c53662c2b0e6c69e23785c8f32554e837ec1714bee902e60737b639dd933af4f68cb9d7de77e1f3b28e5b122891afce62b79acd5b1ab4ba411662cc77d806449e69c5a45a143b742d98ac84a0826d68433b9b700ace6cd472ba2d58a90847f42ce9c43f38ffc017db4bf40450b2eee1f4594dc740c0f", + "6a6058b0a498b7ea76a93c646eb9b8629f0cba4a0c726420c5f67ba9b0412cade356abdf0a4fb94384bad32ce0d5dd9e23dcaae1d6f28ff8683616b30f1392890c67b3a2c04b360893b801f127e527e4da82e239f4c878da13f4a4f1c76db07190e77ec123995168102fb274434a2d1e12913b9b5cbab4aacaad2bd89d88b3ca2b8e60dacf7c22c9379097ff60880f552e320ca3b571994f52534470feee2b39e0dadb5cd88257a3e459a4cc6f12f17b8d54e1bb", + "adeced01fc5671531cbb45679f5ddd42b3a95151677b6125aaf6f5e8f82fbabaa5ecf7c3552c2458587224f0042870f178f5fca5465250e75d71352e652eeed23cdb7f915f5ebb44099b6db116ca1be45530ac8ed32b7f161d60ed4397ad3d7d649ae6bf75ca5bec891d8e595605be9764f3a03965e1fe0eaffbf212e3df4f0fa35e08ff9d0091e6d4ac4748edfe43b611085a6ffec163014655fdd839fd9e81b63b1fa8cae4ec335ec343289758e389a79ceedfae", + "d014592f3a83ba40af366f137c674724916c3cdd3f6cf9d4c5c7c8d6d51ebf26e315e2c12b3546be56fb52382904046ecbd2f5b883aa4ff473de6f0c26ab862c3fa34bf3d880cc1911ce39a4088c6617c179dc5faf68a2c488bbde12d67b50f73abcfab0e3b062e68c95363e11f5f1de8ec36ed01ea21442518089045df67d346135283ad5b3fff80cf57f20876849f6db9fa139728358415a90610f69ec720fc92d8234e3e122551e9df2c644c4a2c4e3734d07de8e", + "c0d0c37838873ba8757d6e41b409605043bc1635edcd731219587676d94217e9f0ab44b71de25000661ce7303b7015f45e6eaa7b7ebef92b8f4a34c902c908d2172185505fa33aca5a41be83079316cdfdd430fc2c45f505f85d867e6d516f7e1bf19c001d9f43018968aab65ec031b3801399231c83ec9e622dab5629922a6b424cab938c135ff7310501c2c02971bfd2f577e25904d1a618baf0859f77f4e8b1d0cde9544e95ec52ff710c0672fdb3d891feeea2b017", + "7022e7f00902219ba97baa0e940e8ac7727f58955aa068c29680fac4a16bcd812c03eeb5adbcfe867a7f7c6b5d89f4641adb9173b76a1a8438866f9b4f640ce2aedf5f1080c890bcf515b4be4e3e512352f1e5323c62ec46cb73f3d71be8235fee55a154763f7c3f9aeb61ffd28f4cd93d3310f608e2133586bf1ab3f102de96f64c68a4668de8acb2a76a7ce0cddddc8fa3df5e9d230823da16ed9ebb402d36e38e6e018795e5a71517ecab5f9ca472b9ced8ff69d2d195", + "acaf4baf3681ab865ab9abfae41697141ead9d5e98523c2e0e1eeb6373dd15405242a3393611e19b693cabaa4e45ac866cc66663a6e898dc73095a4132d43fb78ff7166724f06562fc6c546c78f2d5087467fcfb780478ec871ac38d9516c2f62bdb66c00218747e959b24f1f1795fafe39ee4109a1f84e3f82e96436a3f8e2c74ef1a665b0daaa459c7a80757b52c905e2fb4e30c4a3f882e87bce35d70e2925a1671205c28c89886a49e045e31434abaab4a7aed077ff22c", + "84cb6ec8a2da4f6c3b15edf77f9af9e44e13d67acc17b24bd4c7a33980f37050c0301ba3aa15ad92efe842cd3ebd3636cf945bb1f199fe0682037b9dacf86f162dadabfa625239c37f8b8db9901df0e618ff56fa62a57499f7ba83baebc085eaf3dda850835520344a67e09419368d81012168e5de5ea45158397af9a5c6a1657b26f319b66f816cd2c28996547d697e8df2bb163ccb9dda4d6691dffd102a13667ab9cde60ffbfb872187d9c425a7f67c1d9fffff9276ed0aeb", + "6a52c9bbbba454c14540b2be58230d78ecbeb391646a0c6fcce2f789086a78364b81ae85d5396d7cfa8b46bda41e3083ec5cf7b4c47dc601c8a697df52f557defca248506dbebab25657f5a561d09625b7f4b2f0119a12beeac087efc9d350a735c35d2431c1da7dda99befb17f41a3dc4da0f00bb95366be128538ce27763d81f832fe3c1d4efc07b5b08ad8dc9e65fb5e48546664e18cb2d3bb3fe1f56fa7aae718c5e3bbdeaf70e15023f6a25b72a2d177fcfd04211d40664fe", + "c3c4d3b31f1f5f9538923df3478c84fffaef411520a542da9a220ee4132eabb9d718b5076fb2f985485e8ba058330aed27ddfd3afa3db34aa60301088caec3d0053828c0c2bc87e2e61db5ea5a29f62fdad9c8b5fc5063ec4ee865e5b2e35fac0c7a835d5f57a1b1079833c25fc38fcb14311c54f8a3bd251bca19342d69e5785f9c2e43cf189d421c76c8e8db925d70fa0fae5ee3a28c4047c23a2b8a167ce53f35ced33bec822b88b06f41558c47d4fed1bfa3e21eb060df4d8ba1", + "8d55e92136992ba23856c1aea109766fc44772477efc932b3194af2265e433ed77d63b44d2a1cff2e8680eff120a430fe012f0f09c6201d546e13ad46fc4ce910eab27bb1569879abed2d9c37fae9f1267c2216ec5debcb20d4de58461a621e6ce8946899de81c0add44d35e27b7982a97f2a5e6314901caebe41dbba35f48bc9244ca6dca2bdde7306435892f287036df088633a070c2e385815ab3e2bfc1a47c05a5b9fe0e80dd6e38e4713a70c8f82bd32475eea8400c7bc67f59cf", + "5016284e20362610fa05ca9d789cad25f6d43263787e7e085476764ce4a8908ce99b262b375e9d106170b1bec1f473d5e777e0c1896533040e39c8c1465e07907ef5860e14e4d8310013e35f12090e0bfc687474b1f15f3dd2033a0edac5246102da4deec7e188c3517d84d9c2a0a4497a4c5f82a30f1ba009e45ee6eb3ab4368c720ea6feee428ffd2c4cc52debb8d634a64176572c72368f94a66689f23f8a01218f532117af5a8060d140e7ca435a92882fcb5630ebe14a4805f1dc83", + "05456ec59b8d41bbd736727976b96b38c43827f9e16169be673ff37870c2ecd5f0d1ea1a136be4cc7b047a02a4421d484fd2a12ece418e42ee391a13a0b1df5a0162b29ab70d3fe3e04ba6ab26b37d62b7cf05a5e2f033611bf970b8e1f30e198e483e740fa9618c1e8677e07b61296b94a9787a68fba622d7653b5568f4a8628025939b0f74389ea8fced6098c065bf2a869fd8e07d705eadb53006be2abb716a3114ceb0236d7e916f037cb954cf977720855d12be76d900ca124a2a66bb", + "eb6f60b83fcee77060ff346aaf6ec34d82a8af469947d3b5074cde8eb26566eb1fa039bcc707738df1e95869bd827c246e88436f0614d9834ead5392ef376105c4a9f370071cdeaaff6ca0f18b74c3a48d19a717253c49bd9009ccbfdd5728a08b7d112a2ed8dbafbbb46d7a75dc9a05e09bfde1a0a92d74a51887f9d123d7896e9f9d0057b660ed7d55454c069d3c5260411db4cdc67e7b74f680d7ac4b9dcc2f8baf72e15e6b3cafebcdf449a6436ed2c398b675f79c644747c57553bf7ea2", + "187a88e88514f6c4157c1ba40b442baae1ae563a6c989277443b12a219aa484cb9fa8adbb9a29d429f50155321b15664926317477079c7060dfdaa84c1d74bba78892c34e6f21ad35208d2ae622012401696bff5cd57b6485944b3db7b9071fa5f57fbfb1085d91bb9cff5808d662cdc6c8157249478262c44b7fbc397ed42a4977b202e817717bfccc9f0467294062313f7705251ed09573f16d23429361fada259dfb300369c4198f07341b38e84d02cdb74af5de6aab1fc2026208ea7c418c0", + "be31bc96606d0fab007e5caeded2f1c9f747c759777e9b6eef962bed49e45a1d4fc993e279d024915e600865ecb087b960584be18c41114d3c43f92169b9e0e1f85a0ebcd4e196376ccdc920e66103cd3b1c58407d0aafd0e003c4e341a1daddb9f4faba974362a32f35db83384b05ae8e3322d728893861afd8b1c940de5a17f691e763ce4969b6d94f67fb4a0235d100225bd8602f291388f0ca4a568748ad0d6040f1262eac2aede6cd27419bb78a394c1ffad72c262be8c3f9d9619d633e51d0", + "4d83d85ca838b4518588f2a90228a4dd18f14dd5b4c012d26298a97d848abbd825d221d02cceb6e8c701b4ad00e1dee4889b5c533e4bb60f1f41a4a61ee5478be2c1b1016c30345afd7a5253668260515e70751f22c8b4022d7fe4877d7bbce90b46531507dd3e89549e7fd58ea28f4cb23d33662bd003c1345ba94cc4b06867f778957901a8c441bee0f3b12e16463a51f7e50690356971dd73a686a49fda1eae46c9d54fba262811d698025d0ee053f1c58591c3bb3cbde69de0b31549ef5b69cf10", + "cdeb07d36dc5f9a1cd717a9e9cca37a2ce93caa298eee63571f7d6c5fde2a11c666cf53cf2dcb41ca2ea2319e7230ca68e38c647905928713a13982bf47fe33d7095ebd50b2df976208920a43eb2e29b942f32467403c45cea18bf44e0f6aeb155b48a8e5c471fec972a9d62f7ae093d2758f0aaec7ca50cb4725bfa219f1a3a46ad6bde7361f445f86b94d66b8ece080e56c510250693a5d0ea0ae87b4421860b853bcf0381eae4f1bf7c5c0472a93ad18407bc88475ab8560d344a921d3e86a02da397", + "a598fad52852c5d51ae3b10528fc1f722e21d44fbd42ae5acdf20e85a28532e646a223d27fd907bfd38eb8bb75175636892f8242877aab89e8c0824d368f3339ce7a82aa4e5af6db1f3b588a4d667a00f67bee37cfd2724dde06d2909fb9e58d892f4cfd2c4ca85acdf8256f5458b030a6bda151154ff2e6d7a8da90b54a2884c8a99fab5a4ac211ff23dc0975f4f592fd1b6b9dc7783bdcd2d4ca4e68d2902f2013e122cb62e2bff6b0a98ec55ba25837e21f1cfe67739b568d43e6413dab2bd1dc471e5a", + "17b68c74c9fe4926e8102070916a4e381b9fe25f5973c9bd4b04ce25749fc18931f37a65a356d3f5e5a1ef125d546f4f0ea797c15fb2efea6fbfcc5739c564693d47adeb12dcb3d98a2830719b13247792cb2491dca159a28138c6cff925aca42f4fdb02e73fbd508ec49b25c60703a7595a3e8f44b155b371d525e48e7e5dc84ac7b17c52bf5e526a67e7187234a2f19f57c548c70fc0b27183df73ffa53fa58b658034c896fa791ae9a7fd2620f5e46ce84c842a6e60e9324ae4db224ffc87d9617cb85ca2", + "b9e4267ea39e1de1fed0579f93bb351007c9f8fcdd811053fae33f09e2753d7428f04e1a9efcd45ea701a5d87a35b3afb2e6b65365dee6ead0bbb611b7797b212ac688653f542e604a39df277f12514ddfee3b4e27b98395c2cd97a203f1f1153c50327965770802ec2c9783edc428271762b275471e7ac65ac36523df28b0d7e6e6ccc7674268a132a63411fc82c0738dbb68af003b769a0bf9e6587b36476cb465350fee13f88ea355d47ffac7b0f964f4139db11b7642cb8d75fe1bc74d859b6d9e884f75ac", + "8ca704fe7208fe5f9c23110c0b3b4eee0ef632cae82bda68d8db2436ad409aa05cf159223586e1e6d8bdae9f316ea786809fbe7fe81ec61c61552d3a83cd6beaf652d1263862664df6aae321d0323440430f400f291c3efbe5d5c690b0cc6b0bf871b3933befb40bc870e2ee1ebb68025a2dcc11b68daadef6be29b5f21e440374301bde1e80dcfade4c9d681480e65ec494a6af48df232c3d51447b9d06be714949249c44c43cf73ed13ef0d533e770284e51369d94ae241a5fb2f163893071b2b4c118aeaf9eae", + "4fd8dd01012bb4df82bf42e0683f998e6f52dd9c5617bae33f867d6c0b69798cead8179346d70acc941abbbdd26e3229d5651361d2252c72ff22db2938d06ff6fc29a42fdf800ae967d06479bc7bbb8e71f40b1190a4b7189ffc9a7096cdb76d40aec424e1388e1eb7ef4ac3b34f3f089da8fda7d1927f5d775c0b2801d22dd1265c973158f640cec93edfed06dc80b20ef8c496b98289d54d46ccd205951cbb0f4e7daeb866b60bacb483411e4382b6f04d472843186bd0e31fbaa93e5c901ec028efafeb45fc551a", + "e9ee1b22b04b321a5fdd8301627011f583887d77560fb0f35552e207561f81e38ac58a0d0aeaf832d1ee72d913720d01f75574e9a321864fe95f4d0d8f0b8db97649a53e71e940aede5c40b4b9105daa42a6fb2811b61209247534cbaf830b07abe338d75d2f5f4eb1c3cf151e9edabe2c8d5f6fff08fac1495ef48160b100d30dcb0676700bcceb28723a29980ab0766a93abb8cb3d1963007db8458ed99b689d2a7c28c788743c80e8c1239b20982c81dadd0eed6740c65fbc4ef15c7b5569cb9fc997c6550a34b3b2", + "ec01e3a60964360f7f23ab0b22e021815765ad706f242265ebc19a2bb9e4eac94393952dcf61aae47682671a10f9165f0b20adf83a6706bfbdcf04c6faba6114653a35584267267873291c6fe7ff5f7695243143421509502c8875aafa9e9afe5be5ef2c851c7f35d69be5d3896000ccdbbfab5c238bb34d607cfe2d55d748880545b4aa7ca61137992925189025c62654b1f20d49c3ccd75aa73ce99cd7258dabedd6480a9f5185531fc0118beb68cc0a9cd182f6973287cf9252e12be5b619f15c25b65c71b7a316ebfd", + "db51a2f84704b78414093aa93708ec5e78573595c6e3a16c9e15744fa0f98ec78a1b3ed1e16f9717c01f6cab1bff0d56367ffc516c2e33261074935e0735ccf0d018744b4d28450f9a4db0dcf7ff504d3183aa967f76a507357948da9018fc38f150db53e2df6cea14466f03792f8bc11bdb5266dd6d508cde9e12ff04305c0295de29de19d491ad86e766774bb517e7e65befb1c5e2c267f013e235d8483e177214f89978b4cdc81aa7eff8b39f2825ad3a1b6ac1424e30edd49b067d770f16e74dd7a9c3af2ad74289a676", + "00e40f30ae3746edad0f5dd03d0e640933cf3d1694804c1e1ed6399ac36611d405196ee48f129344a8512feda16a354517871322bd5d9c6a1b592933eab531923efb393ffb23d9109cbe1075cebfa5fb917b40df028a621460ff6783c798792cb1d9635b5a6f84ec13918fa302924649b5c7fcb1f7007f0d2f06e9cfd7c27491e565a96c68a0c3644f92cd8f38857258c33801c5d537a83dfe583cba59d7eec7e394199c0a2660a62fabe3ed2099d57f315a6cd8de1a4ade29d977f15d65759cff433e5ac0c182aef3761163e1", + "3c5ea24d0d9b618294a263f062b2414a722be4eb10dfc346a6ec3b821d7396eba61cd6ef33618b04cd087a811f299d4606820227f16000d7c839062b96d3e3f59cd1a082448d13fc8f56b3fa7fb5f66d0350aa3b72dd7c165d590282f7da2e12cfe9e60e1796122bb8c2d40fdc2997af634b9c6b127a893dfb3467909378300db3da911be1d7b616bb8e0572433e65527e15d936500a2c60e9f9909dcf22ab5e4b6700f0238c205b4a813626fac3d945bab2637fb08203044a73d20c9a3fcf7c3fc4eb7807c3276dd5f73ce89597", + "9271aeeebfac46f4de85df78f1bfd36136aa8905e15835c9e1941176f71e3aa5b1b131843d40479735e23e182a2bd71f66f6149dccb7ed8c16469079dc8590bbf165374951785f4531f7e7361de62f936cfb23a2b5bdf186632e7042a0dd451fdc9b7208f923f3a5f250ae590ec348c63a16c3aacaf7379f53b5dd4152dcd40d23e683e2156e64c592ffc07e2cd6bbeebef4dd590b2f6b2bcbf08fcd111c079f5c4033adb6c17574f8756ecd87be27eff1d7c8e8d0324438d59ae171d5a17128fbcb5533d921bd044a2038a5046b33", + "4e3e533d5bcb15793d1b9d0468aaee801f32fdb486b11027183553a09ddbee8213924296f2815dc61577297459e834bf1c7a53f87d43782209e589b8295219ba7073a8fff18ad647fdb474fa39e1faa69911bf83438d5f64fe52f38ce6a991f25812c8f548de7bf2fdea7e9b4782beb4011d3567184c817521a2ba0ebad75b892f7f8e35d68b099827a1b08a84ec5e8125651d6f260295684d0ab1011a9209d2bdeb75128bf5364774d7df91e0746b7b08bda9185035f4f226e7d0a1946fcaa9c607a66b185d8546aac2800e85b74e67", + "b5d89fa2d94531093365d1259cc6fe8827fea48e6374c8b9a8c4d2209c280fa5c44958a1847222a692a59e6aa2696e6cdc8a543dd89b0ce03bc293b4e78d6ef48e1839694ccd5c65661143095c705b07e3ced84a0f5959114dd89deb956ab3fac8130eb4a878278205b801ae41a29e34146192308c4e759b374757b0c3b00319bce92a1b95a4d2ee179fd6714ff96155d26f693a5bc973f84ac8b3b91e3926276297532d98b46992a3f104c08100bf1671c43134bac280c617da711e90a0100137525375ebb12802a428885ae7fce6514a", + "40e3d8048fc10650cb8a7fc2e7113e26dec34f9ca2d5129cd10a8e8e44d113d61ee48c7d003e19fd307fc6debd70feb30243f298c510ccc4418355ce143066f067ad7c6de7288c3080e7ad46a23c8d34deb55a43e652fe90444ad3c57d3ec1e1c489d63ef915a24bc74a7925a0a7b1e1523f21ca8fee78df24e3d0a68d0013423db97c280799a0618229c0f2c167289a891e5c8d6661ab21285951c31710e3b5fe55f6347fe16d9b40507948a59252efeb616df83e5c098b07d0a7247cd371daff0e50491c582503fd89f79ba94d6af9ed76", + "1fa444de01dd3901e2b4684e3d7a799ffa02d85afd35fb30fe4c9d672837bee6dd8a3b8608b4bb5e589220ad5a854f46b46e41c6d57ad124a46beab4169ff69fee7e3838a6165e19dad8eb5d7bf53d4edd3cd2769daf219510a02fdd2afe0c0e1da3cd30fcd1aa88b68965586f07a25a1720fbd90a096ea30fc8e945e3637d7857c8a9c0ab4154ffb2000e57b5f9adfa4e4eaf8065bc3c2b2e75f495963325588785a6ce417dcddffd299873b15dcccca128d63cd4eeeadb64cda28099a9ad7c80d34844901f26b88b00b9aafeb2f90286d29d", + "fde0a0d9d813983bd1f55cf778a003a2023b34a555322ab280584537bc6bdd844d22a7d6066c18da83ec09f3d8d5a1aab4be0d5ce19b436052f6e259a4b49017a1f47f1fe2bf115d5bc8599fb216351c60dd6b1bedb2e6f4dcadf424b833501b6f099cbfad9e2290680fb69c25032b42a6274f7cb9b5c5950401354838a45f7cb77b95bf54718e2f3d3d9fb91eb2311903980277396398d9736d8e92fd838594ac8a537c6c529db5a8a4f89290e6ba6f20ac0e5ed6fef40901d0e0e8e3e502990811f9acaae555dd54eb1bcd96b513e2fe751bec", + "9f8e0caec87858599f5ab29bff86da78a841a918a023a111098687ecdf2747612d3f3809d9ca400b878bd4f92c43a1004f1c17c7f19a3cd1ce449bd2b23aff551623c37dd8c0be56bf3fd857b500c2b9f9ccea62481944090a3cf3b6ee81d9af8eeb60f65ef150f9fa4d3ed6ce4762d3d4f174ee8ccd460c25cafac0ea5ec8a6a4b2f9e8c0520cb7061155e532cb65f188b01e4b9086db951f504b060c296b326b3fc1c590498ecce594f828f4a10ea416675720ae505295d38a791bd0e93f428448a8f4c1fc0af53604a9e8255384d29ae5c334e2", + "33d1e683a4c97ee6bbaa5f9df1a88cb53b7f3c157b6045d70a56fda0ccbd3a1fa1f049cd564da072b53f415bf5fb843771c1d2551fd075d33377362b2f7c0645f9723123d11975991db8a2b518f02e2c7c30342a044754290bae2c77496d755e5981f12e6b0a0174280b958bf11ed628a9062775993ced04bf752ea8d165e3ac2177d7cd1b9371c44efa98f0b3e68602a839d384eec007979f46429dafb138cbc231ad928a9f65f7d66fac77416395e8f1debaaf76ec2e4e03e8674102cd26f614739f3ec9f949033df1fb97e87c2326d65aef94ed5f", + "180048f09d0b480887af7fd548a85abf605440c1ddde6afe4c30c30670233f7bf928f43b4681f59279ebbda5e8f8f2a1abefdee129e18ac60f9224e90b38b0aabd01308e0a27f41b6fb2ee07ee176ec9048c5fe33c3f7c791469c81f30e28170585b9f3e7e3c8c2e9d74370cb4518f13bf2dee048cbd98ffa32d85e43bcc64a626b40efb51ce712925fdd6fee006dc68b88004a81549d2121986dd1966084cd654a7c6686b3bae32afbd9625e09344e85cf9611ea08dfce835a2e5b3726e69ae8a76a97db60fcc539944ba4b1e8449e4d9802ae99fae86", + "13c0bc2f5eb887cd90eae426143764cf82b3545998c386007cca871890912217aa143ac4ed4ddb5a7495b704aa4de18419b8664b15bc26cfc6596a4d2ae408f98b47a566476d5802d594ba84c2f538def9d016661f6404bb2337a3932a24f6e30073a6c9c274b940c62c727242e24466084a3ea336365d71ea8fa6499c0ea8d59eea505f1126b99c795023c4963aa0d99323d0391e8701110edf551b2d3799e1063ca443f1add162156e445502ca1a052fe70c289838593b58839fc63de128a03e2bbf389e22ae0cf957fd03315ee407b096cc1cfd92dee6", + "6f1eb607d679efef065df08987a1174aab41bdac8aece7726dfa65805d6fff5b3d17a672d96b770dc32165f144f0f7324822a5c87563b7cd9e37a742ae83ef245d09006d91576f435a03476f509ea2936636232f66aa7f6cdf1ac187bbd1fcb8e20f8791866e60ed96c73374c12ac16795e999b891c64507d2dbd97e5fc29fac750ad27f2937cbcd29fdafccf27ab22453834d475f6186eaf975a36fad5c8bd61c21da554e1ded46c4c39765dcf5c8f5ccfb49b6a4dc562c919d0c7d8940ec536ab2448ec3c9a9c8b0e8fd4870cad9de2577c7b0c38563f355", + "dcdd993c94d3acbc555f464871a32c5da6f13b3d5bbc3e34429705e8ad2e76393fdd96a69a94acb652f5dc3c120d41187e9aa919669f727c4868013b0cb6acc165c1b7706c52248e15c3bf81eb6c147619467945c7c48fa14a73e7c3d5bec91706c567145342a026c9d97eff97ec672c5debb9df1a998083b0b0081d65c517b3e5634c95e347e781aa30ca1c8af815e2e494d844e847fdcb41622894a518dc36571123a40bfdbe8c4f4cff44d83c61dd9dcd24c464c53b395edb31efee9f3aa080e87cdc3d22d613ae84a53c9249c32c96f9a3bc4629bb126a70", + "49971f9823e63c3a72574d977953329e813b22a8387cd13f56d8ea77a5d1a8a20012632d1d8732bbcb9f756b9675aab5db927beacab7ca263e5718b8dfa7b2eed9a91bf5ed163b16139d45f7b8cc7e3f7bdda6202106f67dfb23b7c315ee3e17a09d466b1e6b13e7c7428184a979f5358667b4fa8bd40bcc8ea46058db44587a85377ac46bf155136c09ac58cb6c27f28e17028c91e7e8f74d5b500e56293b316974f02b9d9ea205d9b6ac4cfb74eb8eb0c944577fd2f41316368307beab3e327bf7dbaa0a4428836ec4e895dea635234abeaf113ceeadac33c7a3", + "c57a9cc958cee983599b04fe694f15fb470fcbc53e4bfcc00a27351b12d5d2434444253ad4184e87b81b738922ffd7ff1dc1e54f39c5518b49fb8fe50d63e3935f99e4bd125e8dc0ba8a17fd62de709339a43fabe15cf86d96a54010112170c340cfac4132182eed7301402bc7c8276089dec38488af145cb6222525894658f03501204b7a66aba0be1b557b28a2f652d66f7313ed825ecc4d8596c1be7420d4425b86a1a90a5b7f30d0f24e0d1aae0eb619ca457a71699e44be612a4011c597ee80b94d5507e429d7fc6af22579cd6ad642723b05ef169fade526fb", + "0568a672cd1ecbaa947045b712e2ac27995392fbef8f9488f79803cbee561c212287f080eca95adb5ba42739d78e3ba667f06045d87850d3a0499358649caa257ad29f1a9c511e7054db20554d15cbb55ff854afa45cae475c729cea72ede953522031865bc02b95589ed4d9841c552a8cc94904a93ed09ed77222f6c178195056be59bc4e96a815adf534e6b466fb47e262ff79c803c157a21b6e2269c2e0abeb494113cd868d8466e82d4b2f6a28b73645853d96bc9242515d803e33294848d3fe42fdff68da53c03491636beede47ff1399dd3d54a5e914d55d7adf", + "3f19f61a4cd085796731ac9f85a75a8bce77031932c31762d87d8b8d07b8bd19ff78d6b7d1bd1e87f3a4f41aad03b6c4d17a6cbc86be55f7c8b88ada047bb04f8d49f1c34bcf81cc0f3389ad01a758fc7eeb0072aa9ad1481992bfdde82e438e75590a4423832dfbe3756e2229ea873bc3606e6d72174cb2163bf40b5d49c81009dab85ecc03e311351bbf96e32c030a2b276a7698cb25bc2c967acb3213161a1fdde7d912cd6a804490f8056c47da1333f6e35c41e749c2c23919cb9af5eec5652e6e072b034fb1682e9aaa194a9c0bd456ea0b008d14dbce37967a7a8e", + "705f98f632d99d3651793825c38dc4deda56c59eac539da6a0159c83131cf8ab6f2ee0c3b74111fde351f7aa1a8c500a0cecab17c212d2c58ca09eae608c8eefc922b9902ef8d6832f799ba48c3c28aa702b3242107edeba01daafe424406a3822965056cfe8783455a671e93b1e2eae2321364f1871471c82124df33bc09e1b52882bd7e1c4c7d0b2f3dd4a28c2a002a43246768af0700f9659de99d62167be93177aabf19d678e79e9c726ac510d94e74873eda99620a3961930cd91937c88a06d8153d64fd60da7ca38cf26d1d4f04a0df273f52127c53fdc593f0f8df9", + "ea6f8e977c954657b45f25480ff42c36c7a10c77caa26eb1c907062e24fbca5aebc65cacca0de10abea8c78322f08672e13d8ac16996eca1aa17402eaea4c1cc6c800b22dc18cb8d620192d74bac02c07b5cfa61e513c7f28b7e29b9700e0e442720bf4c669d4995da19d19f841d9eb68cc74153592591e3bf059ef616b95305aa453b32fe99a91afb35bd482cf2b7aa42702837a53be3c38883d2963020e347556f841254ec6b85854485fe8c520b05f2ea67a9bf3981555c20991e2bacd4db5b418228b6002d8d41c025cb472bf5443aaa885974a408ea7f2e3f932c600deb", + "408190134ed06556811b1af808ab2d986aff152a28de2c41a2207c0ccc18125ac20f48384de89ea7c80cda1da14e60cc1599943646b4c0082bbcda2d9fa55a13e9df2934edf15eb4fd41f25fa3dd706ab6de522ed351b106321e494e7a27d5f7caf44ec6fadf1122d227eefc0f57aefc140d2c63d07dcbfd65790b1099745ed042cfd1548242076b98e616b76ff0d53db5179df8dd62c06a36a8b9e95a671e2a9b9dd3fb187a31ae5828d218ec5851913e0b52e2532bd4bf9e7b349f32de2b6d5d3cdf9f372d49617b6220c93c05962327e99a0480488443349f0fd54c1860f7c8", + "5f9e5c6f38573a85010a9d84d33f29c057003b2645e3ea6f72cbc7af95d197ce6a06b13fea81722853e6991791b8b15091cd066f5ed913592ed3d3af5370d39ba22beeb2a582a414b16824b77e194a094c2afdcc09aa73ce36f4943cca5ae32c5017dc398801dd92a47382d9327c9f6cffd38ca4167cd836f7855fc5ff048d8efba378cdde224905a0425e6b1de061fc951c5e624a5153b008ad41160a710b3ff2081748d5e02deb9f841f4fc6cf4a15153dd4fe874fd447482696283e79ee0e6bc8c1c0409baa5ab02c5209c319e3169b2476149c0c6e541c6197ca46e004eef533", + "218c6b3508aec69574f2b5039b30b942b72a8349d05f48ff945bbbe5c8957d5a6199492a6bf54bab821c9377e2edfa4c908384664d2c80112d5e805d66e0a551b941021be17dd20bd825bea9a3b6afb1b8c605805b3bda58750f03ea5c953a698494b425d8980c69f34d1c3f6b5866e8717031152a127215c256e08873c21b0f5cc85875d0f7c94601659150c04cd5fe5d381ba29983a2d94fcd3a65a94c53c7279cd000dddd4253d8cff8d7f6ace10247fe3bc30d63ba4bb54f557b3d22a3924369430d71ab37b701e9500bda70b5a643704858beed4726a889b6c9c91584194c68f1", + "dac26aa7273fc25d6e044c79fc2bfa46e59892a42bbca59a86826c91e76ab03e4bd9f7c0b5f08d1931d88b36ea77d94f7ba67cd4f1d3086e529427201119096ae066ae6f170940830ed7900de7bb9d66e09788287403a4ecc93c6da975d2fb08e918840a236c15f5d3a8f7375c2eeebbf6f01a6e7f29ca2b8d42df158414c320777433663c59fdcd1f39ca68e3473db721be7ce8c6dba5fddc024f94fedb286b0477581d451313ca8c737484daf60d67f9b2d56d4bcc271f7e9ae958c7f258efbc74d25753e0516f28282461941bf2dcc7dd8c7df6173b89760cefcac07190243ff863fb", + "c46e6512e6797cc7a54254a1b26b2de29aa83d6c4b1ea5a2786fbcec388270625b12635eae39e1fba013f8a65219421bca8b52a8ddfd431cda60299bdf160734d5a7450ec79620058522702174ae451b9bfa7c4a455fbbee3e1d048c7d4bac5131018228f137c8e130440c7059b4f15eaa34ce872a851a16ce86f982df78a00be4d564da2003a450ddee9ab43ea876b8b4b65c84f0b39265fd5456417afb5bc54997c986e66fc222f2123ba5e719c4d6b9a177b188277df384f1125821cf19d5248cef0be183ccdc84ac194506f740ed2188b2689ea4c9236a9e9e3a2fff85b6af4e9b49a3", + "1ccd4d278d67b65cf2564ecd4de1b55fe07adc80e1f735fe2f08ea53fd3977323689122c29c798957abaff6aba09bdcbf661d77f4dc8913ab1fe2bef38846166e3834785e7105d746484eff8c656af5d8c7854abc1c62b7fadb65521dc6f793d978bda9838eb3800417d32e8a24d8c8cb1d18a5de6ca79d9e1b0ff9aa25e6218fe944cf18666fecc1e31334b390260dbe0997539e1b02f6366b2aea4f4a21efe04f4b97568fcb39e59919d5ebac6543d5d0f48fc66b923c34aac377dc95c20329b837b6ed5e8d9a3d2089cd0d8f025658006ff41cbdaccca618822ca590ab155253f8bc1c7f5", + "9875209588395ee3c9fdd793fd48717cc84c8c3ea622b2ccc4a1be4448e6034b7810569855255031f10be5ffd714b05f9ce01972d712d40abf03d4d0ce175813a7a668f761324996093fc2aa5912f7fc2abdadd8775d2b4d9ad492216293381460ed8f6db3d641d1525f4242c348bbfe504c704f215dc461de51b5c75c1aae967936963848f16c673eca5e78dfd47eb19001d52d1bcf96c98956dad5ddf594a5da757e7ca35f2f69803b784e66ac5a58b75c228b8266ec592505e5d1ca87d81225738855f15bc0914677e81593fd409e77d159f8a908f67788de9eb06c5561547aada96c47c535", + "40c90e375e366f3756d89091eb3eed9fe0fbfc5638700af4617d358812bac53124a2205dd6756456787d49cd6a35e302479a0992288f47532e4ea7ab62fc5ad5adc690a5d9a446f7e035ad4641bd8dae83946aee3338ec984ccb5cc633e1409f2531eeffe05532a8b0062ba99454c9aeabf8ecb94db195af7032bfebc22912f49d39330add47ff8fa5720612d697f0b602738930e060a1bb214efc5e292224cf34e29deaea6b1b1ff847e94ecc997325ac38df61db45d82bf0e74a664d2fe085c20b04c39e90d6a170b68d2f1d373f00c731c524456ada73d659aaac9df3191a7a3865083343fc13", + "e8800d82e072210ca6d7fa2472028974780b76aad4bcb9ad362422dd05ae3232668251d164daa375a43b26a38cce28dbeb3dee1a4a579f70d0fe7febb29b5ece8aa836e050fb3d188c63aa9c3c0da6c717d86458a6096b5effceb964efdec7035960c09ccd10dea3c5f1c7f9f478d5887ebbe2e15c5ff85dbacbc444bb951c4eec7abecb89ed80187e409e2972ffe1a5f01562af109f2cf09471cf72cf83a3bb8f4e2ef38ed0e326b698296394e5b2718a5000c01425708e8ad0461e62462d8819c2377f13ab1be2c7c9f33dc06fe23cad27b87569f2ce2e56e4b2c60c7b1b3d370841d89ebdc1f192", + "796d6d1447d5b7e8c55cd8b2f8b7010db39f27565f907e3fc0e464ea2d4bb52b37f10e7c6dcfc59231b9cdee12c32aeb4adbc42b86e86eb6defb5b69e6ca75e1f4d0dae3e124e5a1b8b6697f7e10b0403f1f0a5ff848eef3752837a9ba17780f16a9a709188a8d5b89a2fa74adb2e651163b1c2b3d261e225c9158dcd9eb7ac3d6704cee290cdff6bcb3cb90cee030aa0d19d4693655c3c30ac6fc06d2ae37787c47126d57ed9a6bef5f8a6c56859aefc08755739a95aac57a4dd916a92ba9f3afbf969df8085949615033365c751a9a3e1a18cee98a69d22e64009bebf8307169b6c61de0617ecfafdf", + "4f9057183566153cf337b07c3f5556006de54c56b2a1e5326c07aaeabd1886ec6f1641358925db232b2f0dbf75229c796a7395b2f934c1f99090bec1123f3c841b1cb3c5b1ec42ed5408f2940f0c48a9470b852c46d6557853d459cecd2c32bbcd8ee21fa11e385eef0857cba4d8545a61b52a484cdd779db4739fbc7aa9860dcabe0488b98fa0b60c3f7d6153db279000a52ffb573dab37d2ab1896a90e5deb7ac6bbe56239085c325d83a917dc6e8a448425b718c2356b9f3066163555ec444f372e184e02c8c4c69b1c1c2ae2b51e45b98f73d933d18750968945ca85d6bbb22014b4c4015262e3c40d", + "79dcca7d8b81a61359e4aece21f3df7b99518ce70bd2f57a18bab5e7114af2add0a0cea7f319d69f231f060e0a539d9a23fb3e95451ce8c6340cfb09edf931df84203a39226dd9eb278f11b691ef612585b973daab373e65d11325898badf6732100371fd759960fa8fec373268421d28bffdb9b12a430b92fe4b07566ca0c89e616e49f8fc75ccd9cdc66db820d7c02e109aa5ed86b89770262918a518f90a2292f6b68d68ae03992e4259a17a23c84ec2a417f082b5abf3a26e44d2278ecb8ba9456965303a75f25394d1aaf5544590e74b14d8a4cc4050be2b0ebcfe4d2db6b12a02c68a3bcdda70301f3", + "848755dc31e25e9a42f9ec12d847d19f292c14c162c9aba49e972cb123b58b8e57bb263a923929833373858594ff52dbc298dbbc078599194e4c07b0e5fc1e10808bbacdb6e93c72b333685cf961f28eb0d5a395c63266b01f130d25db384b356e5da6d01042fc2359581b89c63b3bb2d1ce897fbc9e83fe85d9666cb60e6a8c657f70caad5387b8a045bf91095606802c8424ea8ac52ef29386dc46183378a5fcb2cb927428b8c070f1c42aafd3bc70ca25437807696a46873cfeb7b80ba2ebc3c4272443d445e46343a1465253a9eebd532a0d1d2c18264b91ff45159f245404ae9335f2af55c802772426b4", + "ecaa6e999ef355a0768730edb835db411829a3764f79d764bb5682af6d00f51b313e017b83fffe2e332cd4a3de0a81d6a52084d5748346a1f81eb9b183ff6d93d05edc00e938d001c90872dfe234e8dd085f639af168af4a07e18f1c56ca6c7c1addffc4a70eb4660666dda0321636c3f83479ad3b64e23d749620413a2ecdcc52ad4e6e63f2b817ce99c15b5d2da3792721d7158297cce65e0c04fe810d7e2434b969e4c7892b3840623e153576356e9a696fd9e7a801c25de621a7849da3f99158d3d09bf039f43c510c8ffb00fa3e9a3c12d2c8062dd25b8dabe53d8581e30427e81c3dfc2d455352487e1255", + "23a3fe80e3636313fdf922a1359514d9f31775e1adf24285e8001c04dbce866df055edf25b506e18953492a173ba5aa0c1ec758123406a97025ba9b6b7a97eb14734424d1a7841ec0eaeba0051d6e9734263bea1af9895a3b8c83d8c854da2ae7832bdd7c285b73f8113c3821cced38b3656b4e6369a9f8327cd368f04128f1d78b6b4260f55995277feffa15e34532cd0306c1f47354667c17018ee012a791af2dbbc7afc92c388008c601740cccbbe66f1eb06ea657e9d478066c2bd2093ab62cd94abadc002722f50968e8acf361658fc64f50685a5b1b004888b3b4f64a4ddb67bec7e4ac64c9ee8deeda896b9", + "758f3567cd992228386a1c01930f7c52a9dcce28fdc1aaa54b0fed97d9a54f1df805f31bac12d559e90a2063cd7df8311a148f6904f78c5440f75e49877c0c0855d59c7f7ee52837e6ef3e54a568a7b38a0d5b896e298c8e46a56d24d8cabda8aeff85a622a3e7c87483ba921f34156defd185f608e2241224286e38121a162c2ba7604f68484717196f6628861a948180e8f06c6cc1ec66d032cf8d16da039cd74277cde31e535bc1692a44046e16881c954af3cd91dc49b443a3680e4bc42a954a46ebd1368b1398edd7580f935514b15c7fbfa9b40048a35122283af731f5e460aa85b66e65f49a9d158699bd2870", + "fe511e86971cea2b6af91b2afa898d9b067fa71780790bb409189f5debe719f405e16acf7c4306a6e6ac5cd535290efe088943b9e6c5d25bfc508023c1b105d20d57252fee8cdbddb4d34a6ec2f72e8d55be55afcafd2e922ab8c31888bec4e816d04f0b2cd23df6e04720969c5152b3563c6da37e4608554cc7b8715bc10aba6a2e3b6fbcd35408df0dd73a9076bfad32b741fcdb0edfb563b3f753508b9b26f0a91673255f9bcda2b9a120f6bfa0632b6551ca517d846a747b66ebda1b2170891ece94c19ce8bf682cc94afdf0053fba4e4f0530935c07cdd6f879c999a8c4328ef6d3e0a37974a230ada83910604337", + "a6024f5b959698c0de45f4f29e1803f99dc8112989c536e5a1337e281bc856ff721e986de183d7b0ea9eb61166830ae5d6d6bc857dc833ff189b52889b8e2bd3f35b4937624d9b36dc5f19db44f0772508029784c7dac9568d28609058bc437e2f79f95b12307d8a8fb042d7fd6ee910a9e8df609ede3283f958ba918a9925a0b1d0f9f9f232062315f28a52cbd60e71c09d83e0f6600f508f0ae8ad7642c080ffc618fcd2314e26f67f1529342569f6df37017f7e3b2dac32ad88d56d175ab22205ee7e3ee94720d76933a21132e110fefbb0689a3adbaa4c685f43652136d09b3a359b5c671e38f11915cb5612db2ae294", + "af6de0e227bd78494acb559ddf34d8a7d55a03912384831be21c38376f39cda8a864aff7a48aed758f6bdf777779a669068a75ce82a06f6b3325c855ed83daf5513a078a61f7dc6c1622a633367e5f3a33e765c8ec5d8d54f48494006fdbf8922063e5340013e312871b7f8f8e5ea439c0d4cb78e2f19dd11f010729b692c65dd0d347f0ce53de9d849224666ea2f6487f1c6f953e8f9dbfd3d6de291c3e9d045e633cfd83c89d2f2327d0b2f31f72ac1604a3db1febc5f22cad08153278047210cc2894582c251a014c652e3951593e70e52a5d7451be8924b64f85c8247dab6268d24710b39fc1c07b4ac829fbda34ed79b5", + "d7314e8b1ff82100b8f5870da62b61c31ab37ace9e6a7b6f7d294571523783c1fdedcbc00dd487dd6f848c34aab493507d07071b5eb59d1a2346068c7f356755fbde3d2cab67514f8c3a12d6ff9f96a977a9ac9263491bd33122a904da5386b943d35a6ba383932df07f259b6b45f69e9b27b4ca124fb3ae143d709853eed86690bc2754d5f8865c355a44b5279d8eb31cdc00f7407fb5f5b34edc57fc7ace943565da2222dc80632ccf42f2f125ceb19714ea964c2e50603c9f8960c3f27c2ed0e18a559931c4352bd7422109a28c5e145003f55c9b7c664fdc985168868950396eaf6fefc7b73d815c1aca721d7c67da632925", + "2928b55c0e4d0f5cb4b60af59e9a702e3d616a8cf427c8bb03981fb8c29026d8f7d89161f36c11654f9a5e8ccb703595a58d671ecdc22c6a784abe363158682be4643002a7da5c9d268a30ea9a8d4cc24f562ab59f55c2b43af7dbcecc7e5ebe7494e82d74145a1e7d442125eb0431c5ea0939b27afa47f8ca97849f341f707660c7fbe49b7a0712fbcb6f7562ae2961425f27c7779c7534ecdeb8047ff3cb89a25159f3e1cefe42f9ef16426241f2c4d62c11d7ac43c4500dfcd184436bb4ef33260366f875230f26d81613c334dbda4736ba9d1d2966502914ec01bbe72d885606ec11da7a2cb01b29d35eebedbb0ecc73ed6c35", + "fd993f50e8a68c7b2c7f87511ce65b93c0aa94dcbdf2c9cca93816f0f3b2ab34c62c586fc507b4900a34cf9d0517e0fe10a89d154c5419c1f5e38de00e8834fe3dc1032abdeb10729a81655a69a12856a78ca6e12110580de879b086fd6608726541cfa9616326bdd36064bc0d1e5f9c93b41278bff6a13b2494b81e238c0c45aea1b07d855e8f3fe1478e373bd9d3957cf8a5e5b9003386793d994c7c575cff2322e2428cbbaa4f47560316ae3354a7478842ff7cc5dcbacb6e871e72b36f06d63a9aaeb9044cfb7974afdc238a5816f537dcf33ee40b4e1a5eb3cff2402b46d548264e133008d284f11b7e4e450bc3c5ff9f79b9c4", + "8df21892f5fc303b0de4adef1970186db6fe71bb3ea3094922e13afcfabf1d0be009f36d6f6310c5f9fda51f1a946507a055b645c296370440e5e83d8e906a2fb51f2b42de8856a81a4f28a73a8825c68ea08e5e366730bce8047011cb7d6d9be8c6f4211308fad21856284d5bc47d199988e0abf5badf8693ceeed0a2d98e8ae94b7775a42925edb1f697ffbd8e806af23145054a85e071819cca4cd48875290ca65e5ee72a9a54ff9f19c10ef4adaf8d04c9a9afcc73853fc128bbebc61f78702787c966ca6e1b1a0e4dab646acdfcd3c6bf3e5cfbec5ebe3e06c8abaa1de56e48421d87c46b5c78030afcafd91f27e7d7c85eb4872b", + "48ec6ec520f8e593d7b3f653eb15553de246723b81a6d0c3221aaa42a37420fba98a23796338dff5f845dce6d5a449be5ecc1887356619270461087e08d05fb60433a83d7bd00c002b09ea210b428965124b9b27d9105a71c826c1a2491cfd60e4cfa86c2da0c7100a8dc1c3f2f94b280d54e01e043acf0e966200d9fa8a41daf3b9382820786c75cadbb8841a1b2be5b6cbeb64878e4a231ae063a99b4e2308960ef0c8e2a16bb3545cc43bdf171493fb89a84f47e7973dc60cf75aeeca71e0a7ebe17d161d4fb9fe009941cc438f16a5bae6c99fcad08cac486eb2a48060b023d8730bf1d82fe60a2f036e6f52a5bff95f43bbe088933f", + "f4d84ed3e564c102600a795eaa9b1eaf4ad12f1a4deca1d042a0a2750ddf6201db03073d8bf553cb9dde48a1b0083827a609f7242b86584cc180964ae794b12ce55661e00e36a6ba4dbc389e6a5a85f1b45df9af7ead1b0a54db56e68639b9d438a91504e82c35d40c7bc7e048a53ac0b04accd0dadf4ac9884b0ca0e3cb5ba4336e3581be4c4760a553823ffa283a1120d4e145af56a59f2533903650f0b9e9ad9fe2e8a3c3c3dd03a1fcb709032c8835324839c735b0c051d0cbd8b5d867617c11023432e4bd275d3d0eb98a0b6cf58071a5b712922f2bc751ac7c2588c447444cde2f37a8ea5ec126425bf517e0d17c9e2999f52fee14b3", + "2ccea21bac9c2b70d3923309cbf2d7cb7abd1fcc8b8b002688870a80029c62397350c3c898194e5deea360bb963d26d485cb7963f8167586976ec0556950b2e86135f4a2800991ce8473bfd44a3c5e937a48b5e355ba5141bccf2131a83988d9d2a9e8e7635a956105b3512c05ef708139ced51d7a4e204c12d8a49a21e8dc6de2629a2fd092326885d9f218745fe09f6d91fb6afce250a30a63689534b6be1f26899ffa3767d835cf586aa47776700f94241bc999b1e3deefe188f37ff734f5f16ee6a00914323dc7b8a143c9137cdcc5cd08ae9566f04bb2941532674c97dff6ffa5ce3405ef8e5d27ec403114253dd6394c0167d72a0044c5", + "2b681c6398aee63bf862770341648bbcd31d7de7903c5903fe3d9469311320bb24d914f2af0cdca199c97214c7c679dc32a2800ba484a03c010ea6be3bb9f2c87e30a98b606050b8a3f297f12b8f92caaeceb3e844652115934874e0a1ab093a73d759b53f6a6c3096940dd22c2bb96ce6820a7b9c6d71a208de9892aa6a7209b0fff56a0cafea52b952cdd6f5752cff3309d448800b4e4c878aa595595b56b12b83fcd6ca89520c7da664e449d7b4438fc455888aad5de0fad9a06eed14afd3513b5ebbffe01775549b701181bd26370764f56eba52fdb24286ad1ac0f5418a7c429f7dfc7f3168437fa8eed7a2ed7c723a485e4c3ed14dea2e07", + "aadfd505a89f4aade2c3018258a7e039401b1fc6a7f3d87910dddbb880d372ec8a13c70d92245de5b8e5f9a285c33b99dc82fa2b22decee72b93a72211656ad7a52696c8e570f78be28c0e427a371dafde856e8d5ed24f83b0660b51e7fac05d93a8666dfde6def59af863f80f3e5f6801182c87422203df390dcb736b8f830052a8832eeeb0b4e27e732aaf793d166b5a3ec7745aeef3766937c2b75a276bddd145f6010c29d035e343e267cb2d828436876ec3a7ebe3b6347d4172f7a99d6821ce152e039e53deb33340b324c7f068ffb94b3cde35a8eaa12d15c3806a7ad0acec3e8c7078c1d32a28fd3eec9f32cb86e4c22166ff69e83785e851", + "1605b8cce529a9d6262fd4390d9e4ae5e14e0adc0ec89b028ef68dd0f373ea259aaa96f2967091dd0874c0105385e9e6da9ca68297c31afa44ef834535fb302ce5b4e49edacbbdf359fe1228a8172495b3e57014c27edd58b685110980056c50c398a64f4923f2d720b4df16d75cb36b4233660694182099c35028a972519c24764fc94e18e582b24deb3491535fc06b83837c7958522800e822201d694af0bd0aa3834e17d4b1ba36f470905ae5f8bbeeb6c4c8604d8af02baa347b07086d6989867ddd5e8e8ed7740c3469bfa2810519c55c6add1332c4c54ee9097961d6741cb12a09713a0d07645f784f42f5ad94b48b836b34263130b0483f15e3", + "ff9c6125b2f60bfd6c2427b279df070e430075096647599bdc68c531152c58e13858b82385d78c856092d6c74106e87ccf51ac7e673936332d9b223444eaa0e762ee258d8a733d3a515ec68ed73285e5ca183ae3278b4820b0ab2797feb1e7d8cc864df585dfb5ebe02a993325a9ad5e2d7d49d3132cf66013898351d044e0fe908ccdfeeebf651983601e3673a1f92d36510c0cc19b2e75856db8e4a41f92a51efa66d6cc22e414944c2c34a5a89ccde0be76f51410824e330d8e7c613194338c93732e8aea651fca18bcf1ac1824340c5553aff1e58d4ab8d7c8842b4712021e517cd6c140f6743c69c7bee05b10a8f24050a8caa4f96d1664909c5a06", + "6e85c2f8e1fdc3aaeb969da1258cb504bbf0070cd03d23b3fb5ee08feea5ee2e0ee1c71a5d0f4f701b351f4e4b4d74cb1e2ae6184814f77b62d2f08134b7236ebf6b67d8a6c9f01b4248b30667c555f5d8646dbfe291151b23c9c9857e33a4d5c847be29a5ee7b402e03bac02d1a4319acc0dd8f25e9c7a266f5e5c896cc11b5b238df96a0963ae806cb277abc515c298a3e61a3036b177acf87a56ca4478c4c6d0d468913de602ec891318bbaf52c97a77c35c5b7d164816cf24e4c4b0b5f45853882f716d61eb947a45ce2efa78f1c70a918512af1ad536cbe6148083385b34e207f5f690d7a954021e4b5f4258a385fd8a87809a481f34202af4caccb82", + "1e9b2c454e9de3a2d723d850331037dbf54133dbe27488ff757dd255833a27d8eb8a128ad12d0978b6884e25737086a704fb289aaaccf930d5b582ab4df1f55f0c429b6875edec3fe45464fa74164be056a55e243c4222c586bec5b18f39036aa903d98180f24f83d09a454dfa1e03a60e6a3ba4613e99c35f874d790174ee48a557f4f021ade4d1b278d7997ef094569b37b3db0505951e9ee8400adaea275c6db51b325ee730c69df97745b556ae41cd98741e28aa3a49544541eeb3da1b1e8fa4e8e9100d66dd0c7f5e2c271b1ecc077de79c462b9fe4c273543ecd82a5bea63c5acc01eca5fb780c7d7c8c9fe208ae8bd50cad1769693d92c6c8649d20d8", +} diff --git a/crypto/blake2b/blake2x.go b/crypto/blake2b/blake2x.go new file mode 100644 index 0000000..52c414d --- /dev/null +++ b/crypto/blake2b/blake2x.go @@ -0,0 +1,177 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "encoding/binary" + "errors" + "io" +) + +// XOF defines the interface to hash functions that +// support arbitrary-length output. +type XOF interface { + // Write absorbs more data into the hash's state. It panics if called + // after Read. + io.Writer + + // Read reads more output from the hash. It returns io.EOF if the limit + // has been reached. + io.Reader + + // Clone returns a copy of the XOF in its current state. + Clone() XOF + + // Reset resets the XOF to its initial state. + Reset() +} + +// OutputLengthUnknown can be used as the size argument to NewXOF to indicate +// the length of the output is not known in advance. +const OutputLengthUnknown = 0 + +// magicUnknownOutputLength is a magic value for the output size that indicates +// an unknown number of output bytes. +const magicUnknownOutputLength = (1 << 32) - 1 + +// maxOutputLength is the absolute maximum number of bytes to produce when the +// number of output bytes is unknown. +const maxOutputLength = (1 << 32) * 64 + +// NewXOF creates a new variable-output-length hash. The hash either produce a +// known number of bytes (1 <= size < 2**32-1), or an unknown number of bytes +// (size == OutputLengthUnknown). In the latter case, an absolute limit of +// 256GiB applies. +// +// A non-nil key turns the hash into a MAC. The key must between +// zero and 32 bytes long. +func NewXOF(size uint32, key []byte) (XOF, error) { + if len(key) > Size { + return nil, errKeySize + } + if size == magicUnknownOutputLength { + // 2^32-1 indicates an unknown number of bytes and thus isn't a + // valid length. + return nil, errors.New("blake2b: XOF length too large") + } + if size == OutputLengthUnknown { + size = magicUnknownOutputLength + } + x := &xof{ + d: digest{ + size: Size, + keyLen: len(key), + }, + length: size, + } + copy(x.d.key[:], key) + x.Reset() + return x, nil +} + +type xof struct { + d digest + length uint32 + remaining uint64 + cfg, root, block [Size]byte + offset int + nodeOffset uint32 + readMode bool +} + +func (x *xof) Write(p []byte) (n int, err error) { + if x.readMode { + panic("blake2b: write to XOF after read") + } + return x.d.Write(p) +} + +func (x *xof) Clone() XOF { + clone := *x + return &clone +} + +func (x *xof) Reset() { + x.cfg[0] = byte(Size) + binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length + binary.LittleEndian.PutUint32(x.cfg[12:], x.length) // XOF length + x.cfg[17] = byte(Size) // inner hash size + + x.d.Reset() + x.d.h[1] ^= uint64(x.length) << 32 + + x.remaining = uint64(x.length) + if x.remaining == magicUnknownOutputLength { + x.remaining = maxOutputLength + } + x.offset, x.nodeOffset = 0, 0 + x.readMode = false +} + +func (x *xof) Read(p []byte) (n int, err error) { + if !x.readMode { + x.d.finalize(&x.root) + x.readMode = true + } + + if x.remaining == 0 { + return 0, io.EOF + } + + n = len(p) + if uint64(n) > x.remaining { + n = int(x.remaining) + p = p[:n] + } + + if x.offset > 0 { + blockRemaining := Size - x.offset + if n < blockRemaining { + x.offset += copy(p, x.block[x.offset:]) + x.remaining -= uint64(n) + return + } + copy(p, x.block[x.offset:]) + p = p[blockRemaining:] + x.offset = 0 + x.remaining -= uint64(blockRemaining) + } + + for len(p) >= Size { + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + copy(p, x.block[:]) + p = p[Size:] + x.remaining -= uint64(Size) + } + + if todo := len(p); todo > 0 { + if x.remaining < uint64(Size) { + x.cfg[0] = byte(x.remaining) + } + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + x.offset = copy(p, x.block[:todo]) + x.remaining -= uint64(todo) + } + return +} + +func (d *digest) initConfig(cfg *[Size]byte) { + d.offset, d.c[0], d.c[1] = 0, 0, 0 + for i := range d.h { + d.h[i] = iv[i] ^ binary.LittleEndian.Uint64(cfg[i*8:]) + } +} diff --git a/crypto/blake2b/register.go b/crypto/blake2b/register.go new file mode 100644 index 0000000..9d86339 --- /dev/null +++ b/crypto/blake2b/register.go @@ -0,0 +1,33 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.9 +// +build go1.9 + +package blake2b + +import ( + "crypto" + "hash" +) + +func init() { + newHash256 := func() hash.Hash { + h, _ := New256(nil) + return h + } + newHash384 := func() hash.Hash { + h, _ := New384(nil) + return h + } + + newHash512 := func() hash.Hash { + h, _ := New512(nil) + return h + } + + crypto.RegisterHash(crypto.BLAKE2b_256, newHash256) + crypto.RegisterHash(crypto.BLAKE2b_384, newHash384) + crypto.RegisterHash(crypto.BLAKE2b_512, newHash512) +} diff --git a/crypto/bn256/LICENSE b/crypto/bn256/LICENSE new file mode 100644 index 0000000..634e0cb --- /dev/null +++ b/crypto/bn256/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2018 Péter Szilágyi. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/crypto/bn256/bn256_fast.go b/crypto/bn256/bn256_fast.go new file mode 100644 index 0000000..e3c9b60 --- /dev/null +++ b/crypto/bn256/bn256_fast.go @@ -0,0 +1,26 @@ +// Copyright 2018 Péter Szilágyi. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +//go:build amd64 || arm64 +// +build amd64 arm64 + +// Package bn256 implements the Optimal Ate pairing over a 256-bit Barreto-Naehrig curve. +package bn256 + +import ( + bn256cf "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" +) + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 = bn256cf.G1 + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 = bn256cf.G2 + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + return bn256cf.PairingCheck(a, b) +} diff --git a/crypto/bn256/bn256_slow.go b/crypto/bn256/bn256_slow.go new file mode 100644 index 0000000..4c0c351 --- /dev/null +++ b/crypto/bn256/bn256_slow.go @@ -0,0 +1,24 @@ +// Copyright 2018 Péter Szilágyi. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +//go:build !amd64 && !arm64 +// +build !amd64,!arm64 + +// Package bn256 implements the Optimal Ate pairing over a 256-bit Barreto-Naehrig curve. +package bn256 + +import bn256 "github.com/ethereum/go-ethereum/crypto/bn256/google" + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 = bn256.G1 + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 = bn256.G2 + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + return bn256.PairingCheck(a, b) +} diff --git a/crypto/bn256/cloudflare/LICENSE b/crypto/bn256/cloudflare/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/crypto/bn256/cloudflare/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/crypto/bn256/cloudflare/bn256.go b/crypto/bn256/cloudflare/bn256.go new file mode 100644 index 0000000..4f607af --- /dev/null +++ b/crypto/bn256/cloudflare/bn256.go @@ -0,0 +1,495 @@ +// Package bn256 implements a particular bilinear group at the 128-bit security +// level. +// +// Bilinear groups are the basis of many of the new cryptographic protocols that +// have been proposed over the past decade. They consist of a triplet of groups +// (Gâ‚, Gâ‚‚ and GT) such that there exists a function e(gâ‚Ë£,g₂ʸ)=gTˣʸ (where gâ‚“ +// is a generator of the respective group). That function is called a pairing +// function. +// +// This package specifically implements the Optimal Ate pairing over a 256-bit +// Barreto-Naehrig curve as described in +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not +// compatible with the implementation described in that paper, as different +// parameters are chosen. +// +// (This package previously claimed to operate at a 128-bit security level. +// However, recent improvements in attacks mean that is no longer true. See +// https://moderncrypto.org/mail-archive/curves/2016/000740.html.) +package bn256 + +import ( + "crypto/rand" + "errors" + "io" + "math/big" +) + +func randomK(r io.Reader) (k *big.Int, err error) { + for { + k, err = rand.Int(r, Order) + if err != nil || k.Sign() > 0 { + return + } + } +} + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 struct { + p *curvePoint +} + +// RandomG1 returns x and gâ‚Ë£ where x is a random, non-zero number read from r. +func RandomG1(r io.Reader) (*big.Int, *G1, error) { + k, err := randomK(r) + if err != nil { + return nil, nil, err + } + + return k, new(G1).ScalarBaseMult(k), nil +} + +func (g *G1) String() string { + return "bn256.G1" + g.p.String() +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and then +// returns e. +func (e *G1) ScalarBaseMult(k *big.Int) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Mul(curveGen, k) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G1) ScalarMult(a *G1, k *big.Int) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Mul(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *G1) Add(a, b *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Add(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *G1) Neg(a *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Neg(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *G1) Set(a *G1) *G1 { + if e.p == nil { + e.p = &curvePoint{} + } + e.p.Set(a.p) + return e +} + +// Marshal converts e to a byte slice. +func (e *G1) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &curvePoint{} + } + + e.p.MakeAffine() + ret := make([]byte, numBytes*2) + if e.p.IsInfinity() { + return ret + } + temp := &gfP{} + + montDecode(temp, &e.p.x) + temp.Marshal(ret) + montDecode(temp, &e.p.y) + temp.Marshal(ret[numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G1) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) < 2*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &curvePoint{} + } else { + e.p.x, e.p.y = gfP{0}, gfP{0} + } + var err error + if err = e.p.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + // Encode into Montgomery form and ensure it's on the curve + montEncode(&e.p.x, &e.p.x) + montEncode(&e.p.y, &e.p.y) + + zero := gfP{0} + if e.p.x == zero && e.p.y == zero { + // This is the point at infinity. + e.p.y = *newGFp(1) + e.p.z = gfP{0} + e.p.t = gfP{0} + } else { + e.p.z = *newGFp(1) + e.p.t = *newGFp(1) + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[2*numBytes:], nil +} + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 struct { + p *twistPoint +} + +// RandomG2 returns x and gâ‚‚Ë£ where x is a random, non-zero number read from r. +func RandomG2(r io.Reader) (*big.Int, *G2, error) { + k, err := randomK(r) + if err != nil { + return nil, nil, err + } + + return k, new(G2).ScalarBaseMult(k), nil +} + +func (e *G2) String() string { + return "bn256.G2" + e.p.String() +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and then +// returns out. +func (e *G2) ScalarBaseMult(k *big.Int) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Mul(twistGen, k) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G2) ScalarMult(a *G2, k *big.Int) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Mul(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *G2) Add(a, b *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Add(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *G2) Neg(a *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Neg(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *G2) Set(a *G2) *G2 { + if e.p == nil { + e.p = &twistPoint{} + } + e.p.Set(a.p) + return e +} + +// Marshal converts e into a byte slice. +func (e *G2) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &twistPoint{} + } + + e.p.MakeAffine() + ret := make([]byte, numBytes*4) + if e.p.IsInfinity() { + return ret + } + temp := &gfP{} + + montDecode(temp, &e.p.x.x) + temp.Marshal(ret) + montDecode(temp, &e.p.x.y) + temp.Marshal(ret[numBytes:]) + montDecode(temp, &e.p.y.x) + temp.Marshal(ret[2*numBytes:]) + montDecode(temp, &e.p.y.y) + temp.Marshal(ret[3*numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G2) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) < 4*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = &twistPoint{} + } + var err error + if err = e.p.x.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.x.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.Unmarshal(m[2*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.Unmarshal(m[3*numBytes:]); err != nil { + return nil, err + } + // Encode into Montgomery form and ensure it's on the curve + montEncode(&e.p.x.x, &e.p.x.x) + montEncode(&e.p.x.y, &e.p.x.y) + montEncode(&e.p.y.x, &e.p.y.x) + montEncode(&e.p.y.y, &e.p.y.y) + + if e.p.x.IsZero() && e.p.y.IsZero() { + // This is the point at infinity. + e.p.y.SetOne() + e.p.z.SetZero() + e.p.t.SetZero() + } else { + e.p.z.SetOne() + e.p.t.SetOne() + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[4*numBytes:], nil +} + +// GT is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type GT struct { + p *gfP12 +} + +// Pair calculates an Optimal Ate pairing. +func Pair(g1 *G1, g2 *G2) *GT { + return >{optimalAte(g2.p, g1.p)} +} + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + acc := new(gfP12) + acc.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].p.IsInfinity() || b[i].p.IsInfinity() { + continue + } + acc.Mul(acc, miller(b[i].p, a[i].p)) + } + return finalExponentiation(acc).IsOne() +} + +// Miller applies Miller's algorithm, which is a bilinear function from the +// source groups to F_p^12. Miller(g1, g2).Finalize() is equivalent to Pair(g1, +// g2). +func Miller(g1 *G1, g2 *G2) *GT { + return >{miller(g2.p, g1.p)} +} + +func (g *GT) String() string { + return "bn256.GT" + g.p.String() +} + +// ScalarMult sets e to a*k and then returns e. +func (e *GT) ScalarMult(a *GT, k *big.Int) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Exp(a.p, k) + return e +} + +// Add sets e to a+b and then returns e. +func (e *GT) Add(a, b *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Mul(a.p, b.p) + return e +} + +// Neg sets e to -a and then returns e. +func (e *GT) Neg(a *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Conjugate(a.p) + return e +} + +// Set sets e to a and then returns e. +func (e *GT) Set(a *GT) *GT { + if e.p == nil { + e.p = &gfP12{} + } + e.p.Set(a.p) + return e +} + +// Finalize is a linear function from F_p^12 to GT. +func (e *GT) Finalize() *GT { + ret := finalExponentiation(e.p) + e.p.Set(ret) + return e +} + +// Marshal converts e into a byte slice. +func (e *GT) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p == nil { + e.p = &gfP12{} + e.p.SetOne() + } + + ret := make([]byte, numBytes*12) + temp := &gfP{} + + montDecode(temp, &e.p.x.x.x) + temp.Marshal(ret) + montDecode(temp, &e.p.x.x.y) + temp.Marshal(ret[numBytes:]) + montDecode(temp, &e.p.x.y.x) + temp.Marshal(ret[2*numBytes:]) + montDecode(temp, &e.p.x.y.y) + temp.Marshal(ret[3*numBytes:]) + montDecode(temp, &e.p.x.z.x) + temp.Marshal(ret[4*numBytes:]) + montDecode(temp, &e.p.x.z.y) + temp.Marshal(ret[5*numBytes:]) + montDecode(temp, &e.p.y.x.x) + temp.Marshal(ret[6*numBytes:]) + montDecode(temp, &e.p.y.x.y) + temp.Marshal(ret[7*numBytes:]) + montDecode(temp, &e.p.y.y.x) + temp.Marshal(ret[8*numBytes:]) + montDecode(temp, &e.p.y.y.y) + temp.Marshal(ret[9*numBytes:]) + montDecode(temp, &e.p.y.z.x) + temp.Marshal(ret[10*numBytes:]) + montDecode(temp, &e.p.y.z.y) + temp.Marshal(ret[11*numBytes:]) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *GT) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if len(m) < 12*numBytes { + return nil, errors.New("bn256: not enough data") + } + + if e.p == nil { + e.p = &gfP12{} + } + + var err error + if err = e.p.x.x.x.Unmarshal(m); err != nil { + return nil, err + } + if err = e.p.x.x.y.Unmarshal(m[numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.y.x.Unmarshal(m[2*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.y.y.Unmarshal(m[3*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.z.x.Unmarshal(m[4*numBytes:]); err != nil { + return nil, err + } + if err = e.p.x.z.y.Unmarshal(m[5*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.x.Unmarshal(m[6*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.x.y.Unmarshal(m[7*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.x.Unmarshal(m[8*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.y.y.Unmarshal(m[9*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.z.x.Unmarshal(m[10*numBytes:]); err != nil { + return nil, err + } + if err = e.p.y.z.y.Unmarshal(m[11*numBytes:]); err != nil { + return nil, err + } + montEncode(&e.p.x.x.x, &e.p.x.x.x) + montEncode(&e.p.x.x.y, &e.p.x.x.y) + montEncode(&e.p.x.y.x, &e.p.x.y.x) + montEncode(&e.p.x.y.y, &e.p.x.y.y) + montEncode(&e.p.x.z.x, &e.p.x.z.x) + montEncode(&e.p.x.z.y, &e.p.x.z.y) + montEncode(&e.p.y.x.x, &e.p.y.x.x) + montEncode(&e.p.y.x.y, &e.p.y.x.y) + montEncode(&e.p.y.y.x, &e.p.y.y.x) + montEncode(&e.p.y.y.y, &e.p.y.y.y) + montEncode(&e.p.y.z.x, &e.p.y.z.x) + montEncode(&e.p.y.z.y, &e.p.y.z.y) + + return m[12*numBytes:], nil +} diff --git a/crypto/bn256/cloudflare/bn256_test.go b/crypto/bn256/cloudflare/bn256_test.go new file mode 100644 index 0000000..481e2f7 --- /dev/null +++ b/crypto/bn256/cloudflare/bn256_test.go @@ -0,0 +1,129 @@ +package bn256 + +import ( + "bytes" + "crypto/rand" + "testing" +) + +func TestG1Marshal(t *testing.T) { + _, Ga, err := RandomG1(rand.Reader) + if err != nil { + t.Fatal(err) + } + ma := Ga.Marshal() + + Gb := new(G1) + _, err = Gb.Unmarshal(ma) + if err != nil { + t.Fatal(err) + } + mb := Gb.Marshal() + + if !bytes.Equal(ma, mb) { + t.Fatal("bytes are different") + } +} + +func TestG2Marshal(t *testing.T) { + _, Ga, err := RandomG2(rand.Reader) + if err != nil { + t.Fatal(err) + } + ma := Ga.Marshal() + + Gb := new(G2) + _, err = Gb.Unmarshal(ma) + if err != nil { + t.Fatal(err) + } + mb := Gb.Marshal() + + if !bytes.Equal(ma, mb) { + t.Fatal("bytes are different") + } +} + +func TestBilinearity(t *testing.T) { + for i := 0; i < 2; i++ { + a, p1, _ := RandomG1(rand.Reader) + b, p2, _ := RandomG2(rand.Reader) + e1 := Pair(p1, p2) + + e2 := Pair(&G1{curveGen}, &G2{twistGen}) + e2.ScalarMult(e2, a) + e2.ScalarMult(e2, b) + + if *e1.p != *e2.p { + t.Fatalf("bad pairing result: %s", e1) + } + } +} + +func TestTripartiteDiffieHellman(t *testing.T) { + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + pa, pb, pc := new(G1), new(G1), new(G1) + qa, qb, qc := new(G2), new(G2), new(G2) + + pa.Unmarshal(new(G1).ScalarBaseMult(a).Marshal()) + qa.Unmarshal(new(G2).ScalarBaseMult(a).Marshal()) + pb.Unmarshal(new(G1).ScalarBaseMult(b).Marshal()) + qb.Unmarshal(new(G2).ScalarBaseMult(b).Marshal()) + pc.Unmarshal(new(G1).ScalarBaseMult(c).Marshal()) + qc.Unmarshal(new(G2).ScalarBaseMult(c).Marshal()) + + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + k1Bytes := k1.Marshal() + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + k2Bytes := k2.Marshal() + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + k3Bytes := k3.Marshal() + + if !bytes.Equal(k1Bytes, k2Bytes) || !bytes.Equal(k2Bytes, k3Bytes) { + t.Errorf("keys didn't agree") + } +} + +func TestG2SelfAddition(t *testing.T) { + s, _ := rand.Int(rand.Reader, Order) + p := new(G2).ScalarBaseMult(s) + + if !p.p.IsOnCurve() { + t.Fatal("p isn't on curve") + } + m := p.Add(p, p).Marshal() + if _, err := p.Unmarshal(m); err != nil { + t.Fatalf("p.Add(p, p) ∉ Gâ‚‚: %v", err) + } +} + +func BenchmarkG1(b *testing.B) { + x, _ := rand.Int(rand.Reader, Order) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + new(G1).ScalarBaseMult(x) + } +} + +func BenchmarkG2(b *testing.B) { + x, _ := rand.Int(rand.Reader, Order) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + new(G2).ScalarBaseMult(x) + } +} +func BenchmarkPairing(b *testing.B) { + for i := 0; i < b.N; i++ { + Pair(&G1{curveGen}, &G2{twistGen}) + } +} diff --git a/crypto/bn256/cloudflare/constants.go b/crypto/bn256/cloudflare/constants.go new file mode 100644 index 0000000..f7d2c7c --- /dev/null +++ b/crypto/bn256/cloudflare/constants.go @@ -0,0 +1,62 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +func bigFromBase10(s string) *big.Int { + n, _ := new(big.Int).SetString(s, 10) + return n +} + +// u is the BN parameter. +var u = bigFromBase10("4965661367192848881") + +// Order is the number of elements in both Gâ‚ and Gâ‚‚: 36uâ´+36u³+18u²+6u+1. +// Needs to be highly 2-adic for efficient SNARK key and proof generation. +// Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081. +// Refer to https://eprint.iacr.org/2013/879.pdf and https://eprint.iacr.org/2013/507.pdf for more information on these parameters. +var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") + +// P is a prime over which we form a basic field: 36uâ´+36u³+24u²+6u+1. +var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") + +// p2 is p, represented as little-endian 64-bit words. +var p2 = [4]uint64{0x3c208c16d87cfd47, 0x97816a916871ca8d, 0xb85045b68181585d, 0x30644e72e131a029} + +// np is the negative inverse of p, mod 2^256. +var np = [4]uint64{0x87d20782e4866389, 0x9ede7d651eca6ac9, 0xd8afcbd01833da80, 0xf57a22b791888c6b} + +// rN1 is R^-1 where R = 2^256 mod p. +var rN1 = &gfP{0xed84884a014afa37, 0xeb2022850278edf8, 0xcf63e9cfb74492d9, 0x2e67157159e5c639} + +// r2 is R^2 where R = 2^256 mod p. +var r2 = &gfP{0xf32cfc5b538afa89, 0xb5e71911d44501fb, 0x47ab1eff0a417ff6, 0x06d89f71cab8351f} + +// r3 is R^3 where R = 2^256 mod p. +var r3 = &gfP{0xb1cd6dafda1530df, 0x62f210e6a7283db6, 0xef7f0b0c0ada0afb, 0x20fd6e902d592544} + +// xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. +var xiToPMinus1Over6 = &gfP2{gfP{0xa222ae234c492d72, 0xd00f02a4565de15b, 0xdc2ff3a253dfc926, 0x10a75716b3899551}, gfP{0xaf9ba69633144907, 0xca6b1d7387afb78a, 0x11bded5ef08a2087, 0x02f34d751a1f3a7c}} + +// xiToPMinus1Over3 is ξ^((p-1)/3) where ξ = i+9. +var xiToPMinus1Over3 = &gfP2{gfP{0x6e849f1ea0aa4757, 0xaa1c7b6d89f89141, 0xb6e713cdfae0ca3a, 0x26694fbb4e82ebc3}, gfP{0xb5773b104563ab30, 0x347f91c8a9aa6454, 0x7a007127242e0991, 0x1956bcd8118214ec}} + +// xiToPMinus1Over2 is ξ^((p-1)/2) where ξ = i+9. +var xiToPMinus1Over2 = &gfP2{gfP{0xa1d77ce45ffe77c7, 0x07affd117826d1db, 0x6d16bd27bb7edc6b, 0x2c87200285defecc}, gfP{0xe4bbdd0c2936b629, 0xbb30f162e133bacb, 0x31a9d1b6f9645366, 0x253570bea500f8dd}} + +// xiToPSquaredMinus1Over3 is ξ^((p²-1)/3) where ξ = i+9. +var xiToPSquaredMinus1Over3 = &gfP{0x3350c88e13e80b9c, 0x7dce557cdb5e56b9, 0x6001b4b8b615564a, 0x2682e617020217e0} + +// xiTo2PSquaredMinus2Over3 is ξ^((2p²-2)/3) where ξ = i+9 (a cubic root of unity, mod p). +var xiTo2PSquaredMinus2Over3 = &gfP{0x71930c11d782e155, 0xa6bb947cffbe3323, 0xaa303344d4741444, 0x2c3b3f0d26594943} + +// xiToPSquaredMinus1Over6 is ξ^((1p²-1)/6) where ξ = i+9 (a cubic root of -1, mod p). +var xiToPSquaredMinus1Over6 = &gfP{0xca8d800500fa1bf2, 0xf0c5d61468b39769, 0x0e201271ad0d4418, 0x04290f65bad856e6} + +// xiTo2PMinus2Over3 is ξ^((2p-2)/3) where ξ = i+9. +var xiTo2PMinus2Over3 = &gfP2{gfP{0x5dddfd154bd8c949, 0x62cb29a5a4445b60, 0x37bc870a0c7dd2b9, 0x24830a9d3171f0fd}, gfP{0x7361d77f843abe92, 0xa5bb2bd3273411fb, 0x9c941f314b3e2399, 0x15df9cddbb9fd3ec}} diff --git a/crypto/bn256/cloudflare/curve.go b/crypto/bn256/cloudflare/curve.go new file mode 100644 index 0000000..16f0489 --- /dev/null +++ b/crypto/bn256/cloudflare/curve.go @@ -0,0 +1,238 @@ +package bn256 + +import ( + "math/big" +) + +// curvePoint implements the elliptic curve y²=x³+3. Points are kept in Jacobian +// form and t=z² when valid. Gâ‚ is the set of points of this curve on GF(p). +type curvePoint struct { + x, y, z, t gfP +} + +var curveB = newGFp(3) + +// curveGen is the generator of Gâ‚. +var curveGen = &curvePoint{ + x: *newGFp(1), + y: *newGFp(2), + z: *newGFp(1), + t: *newGFp(1), +} + +func (c *curvePoint) String() string { + c.MakeAffine() + x, y := &gfP{}, &gfP{} + montDecode(x, &c.x) + montDecode(y, &c.y) + return "(" + x.String() + ", " + y.String() + ")" +} + +func (c *curvePoint) Set(a *curvePoint) { + c.x.Set(&a.x) + c.y.Set(&a.y) + c.z.Set(&a.z) + c.t.Set(&a.t) +} + +// IsOnCurve returns true iff c is on the curve. +func (c *curvePoint) IsOnCurve() bool { + c.MakeAffine() + if c.IsInfinity() { + return true + } + + y2, x3 := &gfP{}, &gfP{} + gfpMul(y2, &c.y, &c.y) + gfpMul(x3, &c.x, &c.x) + gfpMul(x3, x3, &c.x) + gfpAdd(x3, x3, curveB) + + return *y2 == *x3 +} + +func (c *curvePoint) SetInfinity() { + c.x = gfP{0} + c.y = *newGFp(1) + c.z = gfP{0} + c.t = gfP{0} +} + +func (c *curvePoint) IsInfinity() bool { + return c.z == gfP{0} +} + +func (c *curvePoint) Add(a, b *curvePoint) { + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + + // Normalize the points by replacing a = [x1:y1:z1] and b = [x2:y2:z2] + // by [u1:s1:z1·z2] and [u2:s2:z1·z2] + // where u1 = x1·z2², s1 = y1·z2³ and u1 = x2·z1², s2 = y2·z1³ + z12, z22 := &gfP{}, &gfP{} + gfpMul(z12, &a.z, &a.z) + gfpMul(z22, &b.z, &b.z) + + u1, u2 := &gfP{}, &gfP{} + gfpMul(u1, &a.x, z22) + gfpMul(u2, &b.x, z12) + + t, s1 := &gfP{}, &gfP{} + gfpMul(t, &b.z, z22) + gfpMul(s1, &a.y, t) + + s2 := &gfP{} + gfpMul(t, &a.z, z12) + gfpMul(s2, &b.y, t) + + // Compute x = (2h)²(s²-u1-u2) + // where s = (s2-s1)/(u2-u1) is the slope of the line through + // (u1,s1) and (u2,s2). The extra factor 2h = 2(u2-u1) comes from the value of z below. + // This is also: + // 4(s2-s1)² - 4h²(u1+u2) = 4(s2-s1)² - 4h³ - 4h²(2u1) + // = r² - j - 2v + // with the notations below. + h := &gfP{} + gfpSub(h, u2, u1) + xEqual := *h == gfP{0} + + gfpAdd(t, h, h) + // i = 4h² + i := &gfP{} + gfpMul(i, t, t) + // j = 4h³ + j := &gfP{} + gfpMul(j, h, i) + + gfpSub(t, s2, s1) + yEqual := *t == gfP{0} + if xEqual && yEqual { + c.Double(a) + return + } + r := &gfP{} + gfpAdd(r, t, t) + + v := &gfP{} + gfpMul(v, u1, i) + + // t4 = 4(s2-s1)² + t4, t6 := &gfP{}, &gfP{} + gfpMul(t4, r, r) + gfpAdd(t, v, v) + gfpSub(t6, t4, j) + + gfpSub(&c.x, t6, t) + + // Set y = -(2h)³(s1 + s*(x/4h²-u1)) + // This is also + // y = - 2·s1·j - (s2-s1)(2x - 2i·u1) = r(v-x) - 2·s1·j + gfpSub(t, v, &c.x) // t7 + gfpMul(t4, s1, j) // t8 + gfpAdd(t6, t4, t4) // t9 + gfpMul(t4, r, t) // t10 + gfpSub(&c.y, t4, t6) + + // Set z = 2(u2-u1)·z1·z2 = 2h·z1·z2 + gfpAdd(t, &a.z, &b.z) // t11 + gfpMul(t4, t, t) // t12 + gfpSub(t, t4, z12) // t13 + gfpSub(t4, t, z22) // t14 + gfpMul(&c.z, t4, h) +} + +func (c *curvePoint) Double(a *curvePoint) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A, B, C := &gfP{}, &gfP{}, &gfP{} + gfpMul(A, &a.x, &a.x) + gfpMul(B, &a.y, &a.y) + gfpMul(C, B, B) + + t, t2 := &gfP{}, &gfP{} + gfpAdd(t, &a.x, B) + gfpMul(t2, t, t) + gfpSub(t, t2, A) + gfpSub(t2, t, C) + + d, e, f := &gfP{}, &gfP{}, &gfP{} + gfpAdd(d, t2, t2) + gfpAdd(t, A, A) + gfpAdd(e, t, A) + gfpMul(f, e, e) + + gfpAdd(t, d, d) + gfpSub(&c.x, f, t) + + gfpMul(&c.z, &a.y, &a.z) + gfpAdd(&c.z, &c.z, &c.z) + + gfpAdd(t, C, C) + gfpAdd(t2, t, t) + gfpAdd(t, t2, t2) + gfpSub(&c.y, d, &c.x) + gfpMul(t2, e, &c.y) + gfpSub(&c.y, t2, t) +} + +func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int) { + precomp := [1 << 2]*curvePoint{nil, {}, {}, {}} + precomp[1].Set(a) + precomp[2].Set(a) + gfpMul(&precomp[2].x, &precomp[2].x, xiTo2PSquaredMinus2Over3) + precomp[3].Add(precomp[1], precomp[2]) + + multiScalar := curveLattice.Multi(scalar) + + sum := &curvePoint{} + sum.SetInfinity() + t := &curvePoint{} + + for i := len(multiScalar) - 1; i >= 0; i-- { + t.Double(sum) + if multiScalar[i] == 0 { + sum.Set(t) + } else { + sum.Add(t, precomp[multiScalar[i]]) + } + } + c.Set(sum) +} + +func (c *curvePoint) MakeAffine() { + if c.z == *newGFp(1) { + return + } else if c.z == *newGFp(0) { + c.x = gfP{0} + c.y = *newGFp(1) + c.t = gfP{0} + return + } + + zInv := &gfP{} + zInv.Invert(&c.z) + + t, zInv2 := &gfP{}, &gfP{} + gfpMul(t, &c.y, zInv) + gfpMul(zInv2, zInv, zInv) + + gfpMul(&c.x, &c.x, zInv2) + gfpMul(&c.y, t, zInv2) + + c.z = *newGFp(1) + c.t = *newGFp(1) +} + +func (c *curvePoint) Neg(a *curvePoint) { + c.x.Set(&a.x) + gfpNeg(&c.y, &a.y) + c.z.Set(&a.z) + c.t = gfP{0} +} diff --git a/crypto/bn256/cloudflare/example_test.go b/crypto/bn256/cloudflare/example_test.go new file mode 100644 index 0000000..6c28599 --- /dev/null +++ b/crypto/bn256/cloudflare/example_test.go @@ -0,0 +1,51 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExamplePair(t *testing.T) { + // This implements the tripartite Diffie-Hellman algorithm from "A One + // Round Protocol for Tripartite Diffie-Hellman", A. Joux. + // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf + + // Each of three parties, a, b and c, generate a private value. + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + // Then each party calculates gâ‚ and gâ‚‚ times their private value. + pa := new(G1).ScalarBaseMult(a) + qa := new(G2).ScalarBaseMult(a) + + pb := new(G1).ScalarBaseMult(b) + qb := new(G2).ScalarBaseMult(b) + + pc := new(G1).ScalarBaseMult(c) + qc := new(G2).ScalarBaseMult(c) + + // Now each party exchanges its public values with the other two and + // all parties can calculate the shared key. + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + + // k1, k2 and k3 will all be equal. + + require.Equal(t, k1, k2) + require.Equal(t, k1, k3) + + require.Equal(t, len(np), 4) //Avoid gometalinter varcheck err on np +} diff --git a/crypto/bn256/cloudflare/gfp.go b/crypto/bn256/cloudflare/gfp.go new file mode 100644 index 0000000..b15e169 --- /dev/null +++ b/crypto/bn256/cloudflare/gfp.go @@ -0,0 +1,82 @@ +package bn256 + +import ( + "errors" + "fmt" +) + +type gfP [4]uint64 + +func newGFp(x int64) (out *gfP) { + if x >= 0 { + out = &gfP{uint64(x)} + } else { + out = &gfP{uint64(-x)} + gfpNeg(out, out) + } + + montEncode(out, out) + return out +} + +func (e *gfP) String() string { + return fmt.Sprintf("%16.16x%16.16x%16.16x%16.16x", e[3], e[2], e[1], e[0]) +} + +func (e *gfP) Set(f *gfP) { + e[0] = f[0] + e[1] = f[1] + e[2] = f[2] + e[3] = f[3] +} + +func (e *gfP) Invert(f *gfP) { + bits := [4]uint64{0x3c208c16d87cfd45, 0x97816a916871ca8d, 0xb85045b68181585d, 0x30644e72e131a029} + + sum, power := &gfP{}, &gfP{} + sum.Set(rN1) + power.Set(f) + + for word := 0; word < 4; word++ { + for bit := uint(0); bit < 64; bit++ { + if (bits[word]>>bit)&1 == 1 { + gfpMul(sum, sum, power) + } + gfpMul(power, power, power) + } + } + + gfpMul(sum, sum, r3) + e.Set(sum) +} + +func (e *gfP) Marshal(out []byte) { + for w := uint(0); w < 4; w++ { + for b := uint(0); b < 8; b++ { + out[8*w+b] = byte(e[3-w] >> (56 - 8*b)) + } + } +} + +func (e *gfP) Unmarshal(in []byte) error { + // Unmarshal the bytes into little endian form + for w := uint(0); w < 4; w++ { + e[3-w] = 0 + for b := uint(0); b < 8; b++ { + e[3-w] += uint64(in[8*w+b]) << (56 - 8*b) + } + } + // Ensure the point respects the curve modulus + for i := 3; i >= 0; i-- { + if e[i] < p2[i] { + return nil + } + if e[i] > p2[i] { + return errors.New("bn256: coordinate exceeds modulus") + } + } + return errors.New("bn256: coordinate equals modulus") +} + +func montEncode(c, a *gfP) { gfpMul(c, a, r2) } +func montDecode(c, a *gfP) { gfpMul(c, a, &gfP{1}) } diff --git a/crypto/bn256/cloudflare/gfp12.go b/crypto/bn256/cloudflare/gfp12.go new file mode 100644 index 0000000..93fb368 --- /dev/null +++ b/crypto/bn256/cloudflare/gfp12.go @@ -0,0 +1,160 @@ +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP12 implements the field of size p¹² as a quadratic extension of gfP6 +// where ω²=Ï„. +type gfP12 struct { + x, y gfP6 // value is xω + y +} + +func (e *gfP12) String() string { + return "(" + e.x.String() + "," + e.y.String() + ")" +} + +func (e *gfP12) Set(a *gfP12) *gfP12 { + e.x.Set(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP12) SetZero() *gfP12 { + e.x.SetZero() + e.y.SetZero() + return e +} + +func (e *gfP12) SetOne() *gfP12 { + e.x.SetZero() + e.y.SetOne() + return e +} + +func (e *gfP12) IsZero() bool { + return e.x.IsZero() && e.y.IsZero() +} + +func (e *gfP12) IsOne() bool { + return e.x.IsZero() && e.y.IsOne() +} + +func (e *gfP12) Conjugate(a *gfP12) *gfP12 { + e.x.Neg(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP12) Neg(a *gfP12) *gfP12 { + e.x.Neg(&a.x) + e.y.Neg(&a.y) + return e +} + +// Frobenius computes (xω+y)^p = x^p ω·ξ^((p-1)/6) + y^p +func (e *gfP12) Frobenius(a *gfP12) *gfP12 { + e.x.Frobenius(&a.x) + e.y.Frobenius(&a.y) + e.x.MulScalar(&e.x, xiToPMinus1Over6) + return e +} + +// FrobeniusP2 computes (xω+y)^p² = x^p² ω·ξ^((p²-1)/6) + y^p² +func (e *gfP12) FrobeniusP2(a *gfP12) *gfP12 { + e.x.FrobeniusP2(&a.x) + e.x.MulGFP(&e.x, xiToPSquaredMinus1Over6) + e.y.FrobeniusP2(&a.y) + return e +} + +func (e *gfP12) FrobeniusP4(a *gfP12) *gfP12 { + e.x.FrobeniusP4(&a.x) + e.x.MulGFP(&e.x, xiToPSquaredMinus1Over3) + e.y.FrobeniusP4(&a.y) + return e +} + +func (e *gfP12) Add(a, b *gfP12) *gfP12 { + e.x.Add(&a.x, &b.x) + e.y.Add(&a.y, &b.y) + return e +} + +func (e *gfP12) Sub(a, b *gfP12) *gfP12 { + e.x.Sub(&a.x, &b.x) + e.y.Sub(&a.y, &b.y) + return e +} + +func (e *gfP12) Mul(a, b *gfP12) *gfP12 { + tx := (&gfP6{}).Mul(&a.x, &b.y) + t := (&gfP6{}).Mul(&b.x, &a.y) + tx.Add(tx, t) + + ty := (&gfP6{}).Mul(&a.y, &b.y) + t.Mul(&a.x, &b.x).MulTau(t) + + e.x.Set(tx) + e.y.Add(ty, t) + return e +} + +func (e *gfP12) MulScalar(a *gfP12, b *gfP6) *gfP12 { + e.x.Mul(&e.x, b) + e.y.Mul(&e.y, b) + return e +} + +func (c *gfP12) Exp(a *gfP12, power *big.Int) *gfP12 { + sum := (&gfP12{}).SetOne() + t := &gfP12{} + + for i := power.BitLen() - 1; i >= 0; i-- { + t.Square(sum) + if power.Bit(i) != 0 { + sum.Mul(t, a) + } else { + sum.Set(t) + } + } + + c.Set(sum) + return c +} + +func (e *gfP12) Square(a *gfP12) *gfP12 { + // Complex squaring algorithm + v0 := (&gfP6{}).Mul(&a.x, &a.y) + + t := (&gfP6{}).MulTau(&a.x) + t.Add(&a.y, t) + ty := (&gfP6{}).Add(&a.x, &a.y) + ty.Mul(ty, t).Sub(ty, v0) + t.MulTau(v0) + ty.Sub(ty, t) + + e.x.Add(v0, v0) + e.y.Set(ty) + return e +} + +func (e *gfP12) Invert(a *gfP12) *gfP12 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t1, t2 := &gfP6{}, &gfP6{} + + t1.Square(&a.x) + t2.Square(&a.y) + t1.MulTau(t1).Sub(t2, t1) + t2.Invert(t1) + + e.x.Neg(&a.x) + e.y.Set(&a.y) + e.MulScalar(e, t2) + return e +} diff --git a/crypto/bn256/cloudflare/gfp2.go b/crypto/bn256/cloudflare/gfp2.go new file mode 100644 index 0000000..90a89e8 --- /dev/null +++ b/crypto/bn256/cloudflare/gfp2.go @@ -0,0 +1,156 @@ +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +// gfP2 implements a field of size p² as a quadratic extension of the base field +// where i²=-1. +type gfP2 struct { + x, y gfP // value is xi+y. +} + +func gfP2Decode(in *gfP2) *gfP2 { + out := &gfP2{} + montDecode(&out.x, &in.x) + montDecode(&out.y, &in.y) + return out +} + +func (e *gfP2) String() string { + return "(" + e.x.String() + ", " + e.y.String() + ")" +} + +func (e *gfP2) Set(a *gfP2) *gfP2 { + e.x.Set(&a.x) + e.y.Set(&a.y) + return e +} + +func (e *gfP2) SetZero() *gfP2 { + e.x = gfP{0} + e.y = gfP{0} + return e +} + +func (e *gfP2) SetOne() *gfP2 { + e.x = gfP{0} + e.y = *newGFp(1) + return e +} + +func (e *gfP2) IsZero() bool { + zero := gfP{0} + return e.x == zero && e.y == zero +} + +func (e *gfP2) IsOne() bool { + zero, one := gfP{0}, *newGFp(1) + return e.x == zero && e.y == one +} + +func (e *gfP2) Conjugate(a *gfP2) *gfP2 { + e.y.Set(&a.y) + gfpNeg(&e.x, &a.x) + return e +} + +func (e *gfP2) Neg(a *gfP2) *gfP2 { + gfpNeg(&e.x, &a.x) + gfpNeg(&e.y, &a.y) + return e +} + +func (e *gfP2) Add(a, b *gfP2) *gfP2 { + gfpAdd(&e.x, &a.x, &b.x) + gfpAdd(&e.y, &a.y, &b.y) + return e +} + +func (e *gfP2) Sub(a, b *gfP2) *gfP2 { + gfpSub(&e.x, &a.x, &b.x) + gfpSub(&e.y, &a.y, &b.y) + return e +} + +// See "Multiplication and Squaring in Pairing-Friendly Fields", +// http://eprint.iacr.org/2006/471.pdf +func (e *gfP2) Mul(a, b *gfP2) *gfP2 { + tx, t := &gfP{}, &gfP{} + gfpMul(tx, &a.x, &b.y) + gfpMul(t, &b.x, &a.y) + gfpAdd(tx, tx, t) + + ty := &gfP{} + gfpMul(ty, &a.y, &b.y) + gfpMul(t, &a.x, &b.x) + gfpSub(ty, ty, t) + + e.x.Set(tx) + e.y.Set(ty) + return e +} + +func (e *gfP2) MulScalar(a *gfP2, b *gfP) *gfP2 { + gfpMul(&e.x, &a.x, b) + gfpMul(&e.y, &a.y, b) + return e +} + +// MulXi sets e=ξa where ξ=i+9 and then returns e. +func (e *gfP2) MulXi(a *gfP2) *gfP2 { + // (xi+y)(i+9) = (9x+y)i+(9y-x) + tx := &gfP{} + gfpAdd(tx, &a.x, &a.x) + gfpAdd(tx, tx, tx) + gfpAdd(tx, tx, tx) + gfpAdd(tx, tx, &a.x) + + gfpAdd(tx, tx, &a.y) + + ty := &gfP{} + gfpAdd(ty, &a.y, &a.y) + gfpAdd(ty, ty, ty) + gfpAdd(ty, ty, ty) + gfpAdd(ty, ty, &a.y) + + gfpSub(ty, ty, &a.x) + + e.x.Set(tx) + e.y.Set(ty) + return e +} + +func (e *gfP2) Square(a *gfP2) *gfP2 { + // Complex squaring algorithm: + // (xi+y)² = (x+y)(y-x) + 2*i*x*y + tx, ty := &gfP{}, &gfP{} + gfpSub(tx, &a.y, &a.x) + gfpAdd(ty, &a.x, &a.y) + gfpMul(ty, tx, ty) + + gfpMul(tx, &a.x, &a.y) + gfpAdd(tx, tx, tx) + + e.x.Set(tx) + e.y.Set(ty) + return e +} + +func (e *gfP2) Invert(a *gfP2) *gfP2 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t1, t2 := &gfP{}, &gfP{} + gfpMul(t1, &a.x, &a.x) + gfpMul(t2, &a.y, &a.y) + gfpAdd(t1, t1, t2) + + inv := &gfP{} + inv.Invert(t1) + + gfpNeg(t1, &a.x) + + gfpMul(&e.x, t1, inv) + gfpMul(&e.y, &a.y, inv) + return e +} diff --git a/crypto/bn256/cloudflare/gfp6.go b/crypto/bn256/cloudflare/gfp6.go new file mode 100644 index 0000000..a427349 --- /dev/null +++ b/crypto/bn256/cloudflare/gfp6.go @@ -0,0 +1,213 @@ +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +// gfP6 implements the field of size pⶠas a cubic extension of gfP2 where τ³=ξ +// and ξ=i+9. +type gfP6 struct { + x, y, z gfP2 // value is xτ² + yÏ„ + z +} + +func (e *gfP6) String() string { + return "(" + e.x.String() + ", " + e.y.String() + ", " + e.z.String() + ")" +} + +func (e *gfP6) Set(a *gfP6) *gfP6 { + e.x.Set(&a.x) + e.y.Set(&a.y) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) SetZero() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetZero() + return e +} + +func (e *gfP6) SetOne() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetOne() + return e +} + +func (e *gfP6) IsZero() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsZero() +} + +func (e *gfP6) IsOne() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsOne() +} + +func (e *gfP6) Neg(a *gfP6) *gfP6 { + e.x.Neg(&a.x) + e.y.Neg(&a.y) + e.z.Neg(&a.z) + return e +} + +func (e *gfP6) Frobenius(a *gfP6) *gfP6 { + e.x.Conjugate(&a.x) + e.y.Conjugate(&a.y) + e.z.Conjugate(&a.z) + + e.x.Mul(&e.x, xiTo2PMinus2Over3) + e.y.Mul(&e.y, xiToPMinus1Over3) + return e +} + +// FrobeniusP2 computes (xτ²+yÏ„+z)^(p²) = xÏ„^(2p²) + yÏ„^(p²) + z +func (e *gfP6) FrobeniusP2(a *gfP6) *gfP6 { + // Ï„^(2p²) = τ²τ^(2p²-2) = τ²ξ^((2p²-2)/3) + e.x.MulScalar(&a.x, xiTo2PSquaredMinus2Over3) + // Ï„^(p²) = Ï„Ï„^(p²-1) = τξ^((p²-1)/3) + e.y.MulScalar(&a.y, xiToPSquaredMinus1Over3) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) FrobeniusP4(a *gfP6) *gfP6 { + e.x.MulScalar(&a.x, xiToPSquaredMinus1Over3) + e.y.MulScalar(&a.y, xiTo2PSquaredMinus2Over3) + e.z.Set(&a.z) + return e +} + +func (e *gfP6) Add(a, b *gfP6) *gfP6 { + e.x.Add(&a.x, &b.x) + e.y.Add(&a.y, &b.y) + e.z.Add(&a.z, &b.z) + return e +} + +func (e *gfP6) Sub(a, b *gfP6) *gfP6 { + e.x.Sub(&a.x, &b.x) + e.y.Sub(&a.y, &b.y) + e.z.Sub(&a.z, &b.z) + return e +} + +func (e *gfP6) Mul(a, b *gfP6) *gfP6 { + // "Multiplication and Squaring on Pairing-Friendly Fields" + // Section 4, Karatsuba method. + // http://eprint.iacr.org/2006/471.pdf + v0 := (&gfP2{}).Mul(&a.z, &b.z) + v1 := (&gfP2{}).Mul(&a.y, &b.y) + v2 := (&gfP2{}).Mul(&a.x, &b.x) + + t0 := (&gfP2{}).Add(&a.x, &a.y) + t1 := (&gfP2{}).Add(&b.x, &b.y) + tz := (&gfP2{}).Mul(t0, t1) + tz.Sub(tz, v1).Sub(tz, v2).MulXi(tz).Add(tz, v0) + + t0.Add(&a.y, &a.z) + t1.Add(&b.y, &b.z) + ty := (&gfP2{}).Mul(t0, t1) + t0.MulXi(v2) + ty.Sub(ty, v0).Sub(ty, v1).Add(ty, t0) + + t0.Add(&a.x, &a.z) + t1.Add(&b.x, &b.z) + tx := (&gfP2{}).Mul(t0, t1) + tx.Sub(tx, v0).Add(tx, v1).Sub(tx, v2) + + e.x.Set(tx) + e.y.Set(ty) + e.z.Set(tz) + return e +} + +func (e *gfP6) MulScalar(a *gfP6, b *gfP2) *gfP6 { + e.x.Mul(&a.x, b) + e.y.Mul(&a.y, b) + e.z.Mul(&a.z, b) + return e +} + +func (e *gfP6) MulGFP(a *gfP6, b *gfP) *gfP6 { + e.x.MulScalar(&a.x, b) + e.y.MulScalar(&a.y, b) + e.z.MulScalar(&a.z, b) + return e +} + +// MulTau computes τ·(aτ²+bÏ„+c) = bτ²+cÏ„+aξ +func (e *gfP6) MulTau(a *gfP6) *gfP6 { + tz := (&gfP2{}).MulXi(&a.x) + ty := (&gfP2{}).Set(&a.y) + + e.y.Set(&a.z) + e.x.Set(ty) + e.z.Set(tz) + return e +} + +func (e *gfP6) Square(a *gfP6) *gfP6 { + v0 := (&gfP2{}).Square(&a.z) + v1 := (&gfP2{}).Square(&a.y) + v2 := (&gfP2{}).Square(&a.x) + + c0 := (&gfP2{}).Add(&a.x, &a.y) + c0.Square(c0).Sub(c0, v1).Sub(c0, v2).MulXi(c0).Add(c0, v0) + + c1 := (&gfP2{}).Add(&a.y, &a.z) + c1.Square(c1).Sub(c1, v0).Sub(c1, v1) + xiV2 := (&gfP2{}).MulXi(v2) + c1.Add(c1, xiV2) + + c2 := (&gfP2{}).Add(&a.x, &a.z) + c2.Square(c2).Sub(c2, v0).Add(c2, v1).Sub(c2, v2) + + e.x.Set(c2) + e.y.Set(c1) + e.z.Set(c0) + return e +} + +func (e *gfP6) Invert(a *gfP6) *gfP6 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + + // Here we can give a short explanation of how it works: let j be a cubic root of + // unity in GF(p²) so that 1+j+j²=0. + // Then (xτ² + yÏ„ + z)(xj²τ² + yjÏ„ + z)(xjτ² + yj²τ + z) + // = (xτ² + yÏ„ + z)(Cτ²+BÏ„+A) + // = (x³ξ²+y³ξ+z³-3ξxyz) = F is an element of the base field (the norm). + // + // On the other hand (xj²τ² + yjÏ„ + z)(xjτ² + yj²τ + z) + // = τ²(y²-ξxz) + Ï„(ξx²-yz) + (z²-ξxy) + // + // So that's why A = (z²-ξxy), B = (ξx²-yz), C = (y²-ξxz) + t1 := (&gfP2{}).Mul(&a.x, &a.y) + t1.MulXi(t1) + + A := (&gfP2{}).Square(&a.z) + A.Sub(A, t1) + + B := (&gfP2{}).Square(&a.x) + B.MulXi(B) + t1.Mul(&a.y, &a.z) + B.Sub(B, t1) + + C := (&gfP2{}).Square(&a.y) + t1.Mul(&a.x, &a.z) + C.Sub(C, t1) + + F := (&gfP2{}).Mul(C, &a.y) + F.MulXi(F) + t1.Mul(A, &a.z) + F.Add(F, t1) + t1.Mul(B, &a.x).MulXi(t1) + F.Add(F, t1) + + F.Invert(F) + + e.x.Mul(C, F) + e.y.Mul(B, F) + e.z.Mul(A, F) + return e +} diff --git a/crypto/bn256/cloudflare/gfp_amd64.s b/crypto/bn256/cloudflare/gfp_amd64.s new file mode 100644 index 0000000..64c97ea --- /dev/null +++ b/crypto/bn256/cloudflare/gfp_amd64.s @@ -0,0 +1,129 @@ +// +build amd64,!generic + +#define storeBlock(a0,a1,a2,a3, r) \ + MOVQ a0, 0+r \ + MOVQ a1, 8+r \ + MOVQ a2, 16+r \ + MOVQ a3, 24+r + +#define loadBlock(r, a0,a1,a2,a3) \ + MOVQ 0+r, a0 \ + MOVQ 8+r, a1 \ + MOVQ 16+r, a2 \ + MOVQ 24+r, a3 + +#define gfpCarry(a0,a1,a2,a3,a4, b0,b1,b2,b3,b4) \ + \ // b = a-p + MOVQ a0, b0 \ + MOVQ a1, b1 \ + MOVQ a2, b2 \ + MOVQ a3, b3 \ + MOVQ a4, b4 \ + \ + SUBQ ·p2+0(SB), b0 \ + SBBQ ·p2+8(SB), b1 \ + SBBQ ·p2+16(SB), b2 \ + SBBQ ·p2+24(SB), b3 \ + SBBQ $0, b4 \ + \ + \ // if b is negative then return a + \ // else return b + CMOVQCC b0, a0 \ + CMOVQCC b1, a1 \ + CMOVQCC b2, a2 \ + CMOVQCC b3, a3 + +#include "mul_amd64.h" +#include "mul_bmi2_amd64.h" + +TEXT ·gfpNeg(SB),0,$0-16 + MOVQ ·p2+0(SB), R8 + MOVQ ·p2+8(SB), R9 + MOVQ ·p2+16(SB), R10 + MOVQ ·p2+24(SB), R11 + + MOVQ a+8(FP), DI + SUBQ 0(DI), R8 + SBBQ 8(DI), R9 + SBBQ 16(DI), R10 + SBBQ 24(DI), R11 + + MOVQ $0, AX + gfpCarry(R8,R9,R10,R11,AX, R12,R13,R14,CX,BX) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpAdd(SB),0,$0-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + loadBlock(0(DI), R8,R9,R10,R11) + MOVQ $0, R12 + + ADDQ 0(SI), R8 + ADCQ 8(SI), R9 + ADCQ 16(SI), R10 + ADCQ 24(SI), R11 + ADCQ $0, R12 + + gfpCarry(R8,R9,R10,R11,R12, R13,R14,CX,AX,BX) + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpSub(SB),0,$0-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + loadBlock(0(DI), R8,R9,R10,R11) + + MOVQ ·p2+0(SB), R12 + MOVQ ·p2+8(SB), R13 + MOVQ ·p2+16(SB), R14 + MOVQ ·p2+24(SB), CX + MOVQ $0, AX + + SUBQ 0(SI), R8 + SBBQ 8(SI), R9 + SBBQ 16(SI), R10 + SBBQ 24(SI), R11 + + CMOVQCC AX, R12 + CMOVQCC AX, R13 + CMOVQCC AX, R14 + CMOVQCC AX, CX + + ADDQ R12, R8 + ADCQ R13, R9 + ADCQ R14, R10 + ADCQ CX, R11 + + MOVQ c+0(FP), DI + storeBlock(R8,R9,R10,R11, 0(DI)) + RET + +TEXT ·gfpMul(SB),0,$160-24 + MOVQ a+8(FP), DI + MOVQ b+16(FP), SI + + // Jump to a slightly different implementation if MULX isn't supported. + CMPB ·hasBMI2(SB), $0 + JE nobmi2Mul + + mulBMI2(0(DI),8(DI),16(DI),24(DI), 0(SI)) + storeBlock( R8, R9,R10,R11, 0(SP)) + storeBlock(R12,R13,R14,CX, 32(SP)) + gfpReduceBMI2() + JMP end + +nobmi2Mul: + mul(0(DI),8(DI),16(DI),24(DI), 0(SI), 0(SP)) + gfpReduce(0(SP)) + +end: + MOVQ c+0(FP), DI + storeBlock(R12,R13,R14,CX, 0(DI)) + RET diff --git a/crypto/bn256/cloudflare/gfp_arm64.s b/crypto/bn256/cloudflare/gfp_arm64.s new file mode 100644 index 0000000..c65e801 --- /dev/null +++ b/crypto/bn256/cloudflare/gfp_arm64.s @@ -0,0 +1,113 @@ +// +build arm64,!generic + +#define storeBlock(a0,a1,a2,a3, r) \ + MOVD a0, 0+r \ + MOVD a1, 8+r \ + MOVD a2, 16+r \ + MOVD a3, 24+r + +#define loadBlock(r, a0,a1,a2,a3) \ + MOVD 0+r, a0 \ + MOVD 8+r, a1 \ + MOVD 16+r, a2 \ + MOVD 24+r, a3 + +#define loadModulus(p0,p1,p2,p3) \ + MOVD ·p2+0(SB), p0 \ + MOVD ·p2+8(SB), p1 \ + MOVD ·p2+16(SB), p2 \ + MOVD ·p2+24(SB), p3 + +#include "mul_arm64.h" + +TEXT ·gfpNeg(SB),0,$0-16 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + loadModulus(R5,R6,R7,R8) + + SUBS R1, R5, R1 + SBCS R2, R6, R2 + SBCS R3, R7, R3 + SBCS R4, R8, R4 + + SUBS R5, R1, R5 + SBCS R6, R2, R6 + SBCS R7, R3, R7 + SBCS R8, R4, R8 + + CSEL CS, R5, R1, R1 + CSEL CS, R6, R2, R2 + CSEL CS, R7, R3, R3 + CSEL CS, R8, R4, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpAdd(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + loadModulus(R9,R10,R11,R12) + MOVD ZR, R0 + + ADDS R5, R1 + ADCS R6, R2 + ADCS R7, R3 + ADCS R8, R4 + ADCS ZR, R0 + + SUBS R9, R1, R5 + SBCS R10, R2, R6 + SBCS R11, R3, R7 + SBCS R12, R4, R8 + SBCS ZR, R0, R0 + + CSEL CS, R5, R1, R1 + CSEL CS, R6, R2, R2 + CSEL CS, R7, R3, R3 + CSEL CS, R8, R4, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpSub(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + loadModulus(R9,R10,R11,R12) + + SUBS R5, R1 + SBCS R6, R2 + SBCS R7, R3 + SBCS R8, R4 + + CSEL CS, ZR, R9, R9 + CSEL CS, ZR, R10, R10 + CSEL CS, ZR, R11, R11 + CSEL CS, ZR, R12, R12 + + ADDS R9, R1 + ADCS R10, R2 + ADCS R11, R3 + ADCS R12, R4 + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET + +TEXT ·gfpMul(SB),0,$0-24 + MOVD a+8(FP), R0 + loadBlock(0(R0), R1,R2,R3,R4) + MOVD b+16(FP), R0 + loadBlock(0(R0), R5,R6,R7,R8) + + mul(R9,R10,R11,R12,R13,R14,R15,R16) + gfpReduce() + + MOVD c+0(FP), R0 + storeBlock(R1,R2,R3,R4, 0(R0)) + RET diff --git a/crypto/bn256/cloudflare/gfp_decl.go b/crypto/bn256/cloudflare/gfp_decl.go new file mode 100644 index 0000000..1954d14 --- /dev/null +++ b/crypto/bn256/cloudflare/gfp_decl.go @@ -0,0 +1,26 @@ +//go:build (amd64 && !generic) || (arm64 && !generic) +// +build amd64,!generic arm64,!generic + +package bn256 + +// This file contains forward declarations for the architecture-specific +// assembly implementations of these functions, provided that they exist. + +import ( + "golang.org/x/sys/cpu" +) + +//nolint:varcheck,unused,deadcode +var hasBMI2 = cpu.X86.HasBMI2 + +//go:noescape +func gfpNeg(c, a *gfP) + +//go:noescape +func gfpAdd(c, a, b *gfP) + +//go:noescape +func gfpSub(c, a, b *gfP) + +//go:noescape +func gfpMul(c, a, b *gfP) diff --git a/crypto/bn256/cloudflare/gfp_generic.go b/crypto/bn256/cloudflare/gfp_generic.go new file mode 100644 index 0000000..7742dda --- /dev/null +++ b/crypto/bn256/cloudflare/gfp_generic.go @@ -0,0 +1,174 @@ +//go:build (!amd64 && !arm64) || generic +// +build !amd64,!arm64 generic + +package bn256 + +func gfpCarry(a *gfP, head uint64) { + b := &gfP{} + + var carry uint64 + for i, pi := range p2 { + ai := a[i] + bi := ai - pi - carry + b[i] = bi + carry = (pi&^ai | (pi|^ai)&bi) >> 63 + } + carry = carry &^ head + + // If b is negative, then return a. + // Else return b. + carry = -carry + ncarry := ^carry + for i := 0; i < 4; i++ { + a[i] = (a[i] & carry) | (b[i] & ncarry) + } +} + +func gfpNeg(c, a *gfP) { + var carry uint64 + for i, pi := range p2 { + ai := a[i] + ci := pi - ai - carry + c[i] = ci + carry = (ai&^pi | (ai|^pi)&ci) >> 63 + } + gfpCarry(c, 0) +} + +func gfpAdd(c, a, b *gfP) { + var carry uint64 + for i, ai := range a { + bi := b[i] + ci := ai + bi + carry + c[i] = ci + carry = (ai&bi | (ai|bi)&^ci) >> 63 + } + gfpCarry(c, carry) +} + +func gfpSub(c, a, b *gfP) { + t := &gfP{} + + var carry uint64 + for i, pi := range p2 { + bi := b[i] + ti := pi - bi - carry + t[i] = ti + carry = (bi&^pi | (bi|^pi)&ti) >> 63 + } + + carry = 0 + for i, ai := range a { + ti := t[i] + ci := ai + ti + carry + c[i] = ci + carry = (ai&ti | (ai|ti)&^ci) >> 63 + } + gfpCarry(c, carry) +} + +func mul(a, b [4]uint64) [8]uint64 { + const ( + mask16 uint64 = 0x0000ffff + mask32 uint64 = 0xffffffff + ) + + var buff [32]uint64 + for i, ai := range a { + a0, a1, a2, a3 := ai&mask16, (ai>>16)&mask16, (ai>>32)&mask16, ai>>48 + + for j, bj := range b { + b0, b2 := bj&mask32, bj>>32 + + off := 4 * (i + j) + buff[off+0] += a0 * b0 + buff[off+1] += a1 * b0 + buff[off+2] += a2*b0 + a0*b2 + buff[off+3] += a3*b0 + a1*b2 + buff[off+4] += a2 * b2 + buff[off+5] += a3 * b2 + } + } + + for i := uint(1); i < 4; i++ { + shift := 16 * i + + var head, carry uint64 + for j := uint(0); j < 8; j++ { + block := 4 * j + + xi := buff[block] + yi := (buff[block+i] << shift) + head + zi := xi + yi + carry + buff[block] = zi + carry = (xi&yi | (xi|yi)&^zi) >> 63 + + head = buff[block+i] >> (64 - shift) + } + } + + return [8]uint64{buff[0], buff[4], buff[8], buff[12], buff[16], buff[20], buff[24], buff[28]} +} + +func halfMul(a, b [4]uint64) [4]uint64 { + const ( + mask16 uint64 = 0x0000ffff + mask32 uint64 = 0xffffffff + ) + + var buff [18]uint64 + for i, ai := range a { + a0, a1, a2, a3 := ai&mask16, (ai>>16)&mask16, (ai>>32)&mask16, ai>>48 + + for j, bj := range b { + if i+j > 3 { + break + } + b0, b2 := bj&mask32, bj>>32 + + off := 4 * (i + j) + buff[off+0] += a0 * b0 + buff[off+1] += a1 * b0 + buff[off+2] += a2*b0 + a0*b2 + buff[off+3] += a3*b0 + a1*b2 + buff[off+4] += a2 * b2 + buff[off+5] += a3 * b2 + } + } + + for i := uint(1); i < 4; i++ { + shift := 16 * i + + var head, carry uint64 + for j := uint(0); j < 4; j++ { + block := 4 * j + + xi := buff[block] + yi := (buff[block+i] << shift) + head + zi := xi + yi + carry + buff[block] = zi + carry = (xi&yi | (xi|yi)&^zi) >> 63 + + head = buff[block+i] >> (64 - shift) + } + } + + return [4]uint64{buff[0], buff[4], buff[8], buff[12]} +} + +func gfpMul(c, a, b *gfP) { + T := mul(*a, *b) + m := halfMul([4]uint64{T[0], T[1], T[2], T[3]}, np) + t := mul([4]uint64{m[0], m[1], m[2], m[3]}, p2) + + var carry uint64 + for i, Ti := range T { + ti := t[i] + zi := Ti + ti + carry + T[i] = zi + carry = (Ti&ti | (Ti|ti)&^zi) >> 63 + } + + *c = gfP{T[4], T[5], T[6], T[7]} + gfpCarry(c, carry) +} diff --git a/crypto/bn256/cloudflare/gfp_test.go b/crypto/bn256/cloudflare/gfp_test.go new file mode 100644 index 0000000..16ab2a8 --- /dev/null +++ b/crypto/bn256/cloudflare/gfp_test.go @@ -0,0 +1,60 @@ +package bn256 + +import ( + "testing" +) + +// Tests that negation works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpNeg(t *testing.T) { + n := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + w := &gfP{0xfedcba9876543211, 0x0123456789abcdef, 0x2152411021524110, 0x0114251201142512} + h := &gfP{} + + gfpNeg(h, n) + if *h != *w { + t.Errorf("negation mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that addition works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpAdd(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0xc3df73e9278302b8, 0x687e956e978e3572, 0x254954275c18417f, 0xad354b6afc67f9b4} + h := &gfP{} + + gfpAdd(h, a, b) + if *h != *w { + t.Errorf("addition mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that subtraction works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpSub(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0x02468acf13579bdf, 0xfdb97530eca86420, 0xdfc1e401dfc1e402, 0x203e1bfe203e1bfd} + h := &gfP{} + + gfpSub(h, a, b) + if *h != *w { + t.Errorf("subtraction mismatch: have %#x, want %#x", *h, *w) + } +} + +// Tests that multiplication works the same way on both assembly-optimized and pure Go +// implementation. +func TestGFpMul(t *testing.T) { + a := &gfP{0x0123456789abcdef, 0xfedcba9876543210, 0xdeadbeefdeadbeef, 0xfeebdaedfeebdaed} + b := &gfP{0xfedcba9876543210, 0x0123456789abcdef, 0xfeebdaedfeebdaed, 0xdeadbeefdeadbeef} + w := &gfP{0xcbcbd377f7ad22d3, 0x3b89ba5d849379bf, 0x87b61627bd38b6d2, 0xc44052a2a0e654b2} + h := &gfP{} + + gfpMul(h, a, b) + if *h != *w { + t.Errorf("multiplication mismatch: have %#x, want %#x", *h, *w) + } +} diff --git a/crypto/bn256/cloudflare/lattice.go b/crypto/bn256/cloudflare/lattice.go new file mode 100644 index 0000000..f9ace4d --- /dev/null +++ b/crypto/bn256/cloudflare/lattice.go @@ -0,0 +1,115 @@ +package bn256 + +import ( + "math/big" +) + +var half = new(big.Int).Rsh(Order, 1) + +var curveLattice = &lattice{ + vectors: [][]*big.Int{ + {bigFromBase10("147946756881789319000765030803803410728"), bigFromBase10("147946756881789319010696353538189108491")}, + {bigFromBase10("147946756881789319020627676272574806254"), bigFromBase10("-147946756881789318990833708069417712965")}, + }, + inverse: []*big.Int{ + bigFromBase10("147946756881789318990833708069417712965"), + bigFromBase10("147946756881789319010696353538189108491"), + }, + det: bigFromBase10("43776485743678550444492811490514550177096728800832068687396408373151616991234"), +} + +var targetLattice = &lattice{ + vectors: [][]*big.Int{ + {bigFromBase10("9931322734385697761"), bigFromBase10("9931322734385697761"), bigFromBase10("9931322734385697763"), bigFromBase10("9931322734385697764")}, + {bigFromBase10("4965661367192848881"), bigFromBase10("4965661367192848881"), bigFromBase10("4965661367192848882"), bigFromBase10("-9931322734385697762")}, + {bigFromBase10("-9931322734385697762"), bigFromBase10("-4965661367192848881"), bigFromBase10("4965661367192848881"), bigFromBase10("-4965661367192848882")}, + {bigFromBase10("9931322734385697763"), bigFromBase10("-4965661367192848881"), bigFromBase10("-4965661367192848881"), bigFromBase10("-4965661367192848881")}, + }, + inverse: []*big.Int{ + bigFromBase10("734653495049373973658254490726798021314063399421879442165"), + bigFromBase10("147946756881789319000765030803803410728"), + bigFromBase10("-147946756881789319005730692170996259609"), + bigFromBase10("1469306990098747947464455738335385361643788813749140841702"), + }, + det: new(big.Int).Set(Order), +} + +type lattice struct { + vectors [][]*big.Int + inverse []*big.Int + det *big.Int +} + +// decompose takes a scalar mod Order as input and finds a short, positive decomposition of it wrt to the lattice basis. +func (l *lattice) decompose(k *big.Int) []*big.Int { + n := len(l.inverse) + + // Calculate closest vector in lattice to with Babai's rounding. + c := make([]*big.Int, n) + for i := 0; i < n; i++ { + c[i] = new(big.Int).Mul(k, l.inverse[i]) + round(c[i], l.det) + } + + // Transform vectors according to c and subtract . + out := make([]*big.Int, n) + temp := new(big.Int) + + for i := 0; i < n; i++ { + out[i] = new(big.Int) + + for j := 0; j < n; j++ { + temp.Mul(c[j], l.vectors[j][i]) + out[i].Add(out[i], temp) + } + + out[i].Neg(out[i]) + out[i].Add(out[i], l.vectors[0][i]).Add(out[i], l.vectors[0][i]) + } + out[0].Add(out[0], k) + + return out +} + +func (l *lattice) Precompute(add func(i, j uint)) { + n := uint(len(l.vectors)) + total := uint(1) << n + + for i := uint(0); i < n; i++ { + for j := uint(0); j < total; j++ { + if (j>>i)&1 == 1 { + add(i, j) + } + } + } +} + +func (l *lattice) Multi(scalar *big.Int) []uint8 { + decomp := l.decompose(scalar) + + maxLen := 0 + for _, x := range decomp { + if x.BitLen() > maxLen { + maxLen = x.BitLen() + } + } + + out := make([]uint8, maxLen) + for j, x := range decomp { + for i := 0; i < maxLen; i++ { + out[i] += uint8(x.Bit(i)) << uint(j) + } + } + + return out +} + +// round sets num to num/denom rounded to the nearest integer. +func round(num, denom *big.Int) { + r := new(big.Int) + num.DivMod(num, denom, r) + + if r.Cmp(half) == 1 { + num.Add(num, big.NewInt(1)) + } +} diff --git a/crypto/bn256/cloudflare/lattice_test.go b/crypto/bn256/cloudflare/lattice_test.go new file mode 100644 index 0000000..4d52ad9 --- /dev/null +++ b/crypto/bn256/cloudflare/lattice_test.go @@ -0,0 +1,29 @@ +package bn256 + +import ( + "crypto/rand" + + "testing" +) + +func TestLatticeReduceCurve(t *testing.T) { + k, _ := rand.Int(rand.Reader, Order) + ks := curveLattice.decompose(k) + + if ks[0].BitLen() > 130 || ks[1].BitLen() > 130 { + t.Fatal("reduction too large") + } else if ks[0].Sign() < 0 || ks[1].Sign() < 0 { + t.Fatal("reduction must be positive") + } +} + +func TestLatticeReduceTarget(t *testing.T) { + k, _ := rand.Int(rand.Reader, Order) + ks := targetLattice.decompose(k) + + if ks[0].BitLen() > 66 || ks[1].BitLen() > 66 || ks[2].BitLen() > 66 || ks[3].BitLen() > 66 { + t.Fatal("reduction too large") + } else if ks[0].Sign() < 0 || ks[1].Sign() < 0 || ks[2].Sign() < 0 || ks[3].Sign() < 0 { + t.Fatal("reduction must be positive") + } +} diff --git a/crypto/bn256/cloudflare/main_test.go b/crypto/bn256/cloudflare/main_test.go new file mode 100644 index 0000000..c0c8545 --- /dev/null +++ b/crypto/bn256/cloudflare/main_test.go @@ -0,0 +1,71 @@ +package bn256 + +import ( + "testing" + + "crypto/rand" +) + +func TestRandomG2Marshal(t *testing.T) { + for i := 0; i < 10; i++ { + n, g2, err := RandomG2(rand.Reader) + if err != nil { + t.Error(err) + continue + } + t.Logf("%v: %x\n", n, g2.Marshal()) + } +} + +func TestPairings(t *testing.T) { + a1 := new(G1).ScalarBaseMult(bigFromBase10("1")) + a2 := new(G1).ScalarBaseMult(bigFromBase10("2")) + a37 := new(G1).ScalarBaseMult(bigFromBase10("37")) + an1 := new(G1).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + b0 := new(G2).ScalarBaseMult(bigFromBase10("0")) + b1 := new(G2).ScalarBaseMult(bigFromBase10("1")) + b2 := new(G2).ScalarBaseMult(bigFromBase10("2")) + b27 := new(G2).ScalarBaseMult(bigFromBase10("27")) + b999 := new(G2).ScalarBaseMult(bigFromBase10("999")) + bn1 := new(G2).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + p1 := Pair(a1, b1) + pn1 := Pair(a1, bn1) + np1 := Pair(an1, b1) + if pn1.String() != np1.String() { + t.Error("Pairing mismatch: e(a, -b) != e(-a, b)") + } + if !PairingCheck([]*G1{a1, an1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false negative!") + } + p0 := new(GT).Add(p1, pn1) + p0_2 := Pair(a1, b0) + if p0.String() != p0_2.String() { + t.Error("Pairing mismatch: e(a, b) * e(a, -b) != 1") + } + p0_3 := new(GT).ScalarMult(p1, bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617")) + if p0.String() != p0_3.String() { + t.Error("Pairing mismatch: e(a, b) has wrong order") + } + p2 := Pair(a2, b1) + p2_2 := Pair(a1, b2) + p2_3 := new(GT).ScalarMult(p1, bigFromBase10("2")) + if p2.String() != p2_2.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a * 2, b)") + } + if p2.String() != p2_3.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a, b) ** 2") + } + if p2.String() == p1.String() { + t.Error("Pairing is degenerate!") + } + if PairingCheck([]*G1{a1, a1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false positive!") + } + p999 := Pair(a37, b27) + p999_2 := Pair(a1, b999) + if p999.String() != p999_2.String() { + t.Error("Pairing mismatch: e(a * 37, b * 27) != e(a, b * 999)") + } +} diff --git a/crypto/bn256/cloudflare/mul_amd64.h b/crypto/bn256/cloudflare/mul_amd64.h new file mode 100644 index 0000000..9d8e4b3 --- /dev/null +++ b/crypto/bn256/cloudflare/mul_amd64.h @@ -0,0 +1,181 @@ +#define mul(a0,a1,a2,a3, rb, stack) \ + MOVQ a0, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a0, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a0, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a0, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + storeBlock(R8,R9,R10,R11, 0+stack) \ + MOVQ R12, 32+stack \ + \ + MOVQ a1, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a1, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a1, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a1, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 8+stack, R8 \ + ADCQ 16+stack, R9 \ + ADCQ 24+stack, R10 \ + ADCQ 32+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 8+stack) \ + MOVQ R12, 40+stack \ + \ + MOVQ a2, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a2, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a2, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a2, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 16+stack, R8 \ + ADCQ 24+stack, R9 \ + ADCQ 32+stack, R10 \ + ADCQ 40+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 16+stack) \ + MOVQ R12, 48+stack \ + \ + MOVQ a3, AX \ + MULQ 0+rb \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ a3, AX \ + MULQ 8+rb \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ a3, AX \ + MULQ 16+rb \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ a3, AX \ + MULQ 24+rb \ + ADDQ AX, R11 \ + ADCQ $0, DX \ + MOVQ DX, R12 \ + \ + ADDQ 24+stack, R8 \ + ADCQ 32+stack, R9 \ + ADCQ 40+stack, R10 \ + ADCQ 48+stack, R11 \ + ADCQ $0, R12 \ + storeBlock(R8,R9,R10,R11, 24+stack) \ + MOVQ R12, 56+stack + +#define gfpReduce(stack) \ + \ // m = (T * N') mod R, store m in R8:R9:R10:R11 + MOVQ ·np+0(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R8 \ + MOVQ DX, R9 \ + MOVQ ·np+0(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R9 \ + ADCQ $0, DX \ + MOVQ DX, R10 \ + MOVQ ·np+0(SB), AX \ + MULQ 16+stack \ + ADDQ AX, R10 \ + ADCQ $0, DX \ + MOVQ DX, R11 \ + MOVQ ·np+0(SB), AX \ + MULQ 24+stack \ + ADDQ AX, R11 \ + \ + MOVQ ·np+8(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R12 \ + MOVQ DX, R13 \ + MOVQ ·np+8(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R13 \ + ADCQ $0, DX \ + MOVQ DX, R14 \ + MOVQ ·np+8(SB), AX \ + MULQ 16+stack \ + ADDQ AX, R14 \ + \ + ADDQ R12, R9 \ + ADCQ R13, R10 \ + ADCQ R14, R11 \ + \ + MOVQ ·np+16(SB), AX \ + MULQ 0+stack \ + MOVQ AX, R12 \ + MOVQ DX, R13 \ + MOVQ ·np+16(SB), AX \ + MULQ 8+stack \ + ADDQ AX, R13 \ + \ + ADDQ R12, R10 \ + ADCQ R13, R11 \ + \ + MOVQ ·np+24(SB), AX \ + MULQ 0+stack \ + ADDQ AX, R11 \ + \ + storeBlock(R8,R9,R10,R11, 64+stack) \ + \ + \ // m * N + mul(·p2+0(SB),·p2+8(SB),·p2+16(SB),·p2+24(SB), 64+stack, 96+stack) \ + \ + \ // Add the 512-bit intermediate to m*N + loadBlock(96+stack, R8,R9,R10,R11) \ + loadBlock(128+stack, R12,R13,R14,CX) \ + \ + MOVQ $0, AX \ + ADDQ 0+stack, R8 \ + ADCQ 8+stack, R9 \ + ADCQ 16+stack, R10 \ + ADCQ 24+stack, R11 \ + ADCQ 32+stack, R12 \ + ADCQ 40+stack, R13 \ + ADCQ 48+stack, R14 \ + ADCQ 56+stack, CX \ + ADCQ $0, AX \ + \ + gfpCarry(R12,R13,R14,CX,AX, R8,R9,R10,R11,BX) diff --git a/crypto/bn256/cloudflare/mul_arm64.h b/crypto/bn256/cloudflare/mul_arm64.h new file mode 100644 index 0000000..d405eb8 --- /dev/null +++ b/crypto/bn256/cloudflare/mul_arm64.h @@ -0,0 +1,133 @@ +#define mul(c0,c1,c2,c3,c4,c5,c6,c7) \ + MUL R1, R5, c0 \ + UMULH R1, R5, c1 \ + MUL R1, R6, R0 \ + ADDS R0, c1 \ + UMULH R1, R6, c2 \ + MUL R1, R7, R0 \ + ADCS R0, c2 \ + UMULH R1, R7, c3 \ + MUL R1, R8, R0 \ + ADCS R0, c3 \ + UMULH R1, R8, c4 \ + ADCS ZR, c4 \ + \ + MUL R2, R5, R1 \ + UMULH R2, R5, R26 \ + MUL R2, R6, R0 \ + ADDS R0, R26 \ + UMULH R2, R6, R27 \ + MUL R2, R7, R0 \ + ADCS R0, R27 \ + UMULH R2, R7, R29 \ + MUL R2, R8, R0 \ + ADCS R0, R29 \ + UMULH R2, R8, c5 \ + ADCS ZR, c5 \ + ADDS R1, c1 \ + ADCS R26, c2 \ + ADCS R27, c3 \ + ADCS R29, c4 \ + ADCS ZR, c5 \ + \ + MUL R3, R5, R1 \ + UMULH R3, R5, R26 \ + MUL R3, R6, R0 \ + ADDS R0, R26 \ + UMULH R3, R6, R27 \ + MUL R3, R7, R0 \ + ADCS R0, R27 \ + UMULH R3, R7, R29 \ + MUL R3, R8, R0 \ + ADCS R0, R29 \ + UMULH R3, R8, c6 \ + ADCS ZR, c6 \ + ADDS R1, c2 \ + ADCS R26, c3 \ + ADCS R27, c4 \ + ADCS R29, c5 \ + ADCS ZR, c6 \ + \ + MUL R4, R5, R1 \ + UMULH R4, R5, R26 \ + MUL R4, R6, R0 \ + ADDS R0, R26 \ + UMULH R4, R6, R27 \ + MUL R4, R7, R0 \ + ADCS R0, R27 \ + UMULH R4, R7, R29 \ + MUL R4, R8, R0 \ + ADCS R0, R29 \ + UMULH R4, R8, c7 \ + ADCS ZR, c7 \ + ADDS R1, c3 \ + ADCS R26, c4 \ + ADCS R27, c5 \ + ADCS R29, c6 \ + ADCS ZR, c7 + +#define gfpReduce() \ + \ // m = (T * N') mod R, store m in R1:R2:R3:R4 + MOVD ·np+0(SB), R17 \ + MOVD ·np+8(SB), R25 \ + MOVD ·np+16(SB), R19 \ + MOVD ·np+24(SB), R20 \ + \ + MUL R9, R17, R1 \ + UMULH R9, R17, R2 \ + MUL R9, R25, R0 \ + ADDS R0, R2 \ + UMULH R9, R25, R3 \ + MUL R9, R19, R0 \ + ADCS R0, R3 \ + UMULH R9, R19, R4 \ + MUL R9, R20, R0 \ + ADCS R0, R4 \ + \ + MUL R10, R17, R21 \ + UMULH R10, R17, R22 \ + MUL R10, R25, R0 \ + ADDS R0, R22 \ + UMULH R10, R25, R23 \ + MUL R10, R19, R0 \ + ADCS R0, R23 \ + ADDS R21, R2 \ + ADCS R22, R3 \ + ADCS R23, R4 \ + \ + MUL R11, R17, R21 \ + UMULH R11, R17, R22 \ + MUL R11, R25, R0 \ + ADDS R0, R22 \ + ADDS R21, R3 \ + ADCS R22, R4 \ + \ + MUL R12, R17, R21 \ + ADDS R21, R4 \ + \ + \ // m * N + loadModulus(R5,R6,R7,R8) \ + mul(R17,R25,R19,R20,R21,R22,R23,R24) \ + \ + \ // Add the 512-bit intermediate to m*N + MOVD ZR, R0 \ + ADDS R9, R17 \ + ADCS R10, R25 \ + ADCS R11, R19 \ + ADCS R12, R20 \ + ADCS R13, R21 \ + ADCS R14, R22 \ + ADCS R15, R23 \ + ADCS R16, R24 \ + ADCS ZR, R0 \ + \ + \ // Our output is R21:R22:R23:R24. Reduce mod p if necessary. + SUBS R5, R21, R10 \ + SBCS R6, R22, R11 \ + SBCS R7, R23, R12 \ + SBCS R8, R24, R13 \ + \ + CSEL CS, R10, R21, R1 \ + CSEL CS, R11, R22, R2 \ + CSEL CS, R12, R23, R3 \ + CSEL CS, R13, R24, R4 diff --git a/crypto/bn256/cloudflare/mul_bmi2_amd64.h b/crypto/bn256/cloudflare/mul_bmi2_amd64.h new file mode 100644 index 0000000..403566c --- /dev/null +++ b/crypto/bn256/cloudflare/mul_bmi2_amd64.h @@ -0,0 +1,112 @@ +#define mulBMI2(a0,a1,a2,a3, rb) \ + MOVQ a0, DX \ + MOVQ $0, R13 \ + MULXQ 0+rb, R8, R9 \ + MULXQ 8+rb, AX, R10 \ + ADDQ AX, R9 \ + MULXQ 16+rb, AX, R11 \ + ADCQ AX, R10 \ + MULXQ 24+rb, AX, R12 \ + ADCQ AX, R11 \ + ADCQ $0, R12 \ + ADCQ $0, R13 \ + \ + MOVQ a1, DX \ + MOVQ $0, R14 \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R9 \ + ADCQ BX, R10 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R11 \ + ADCQ BX, R12 \ + ADCQ $0, R13 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + ADCQ $0, R14 \ + \ + MOVQ a2, DX \ + MOVQ $0, CX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R12 \ + ADCQ BX, R13 \ + ADCQ $0, R14 \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + ADCQ $0, CX \ + \ + MOVQ a3, DX \ + MULXQ 0+rb, AX, BX \ + ADDQ AX, R11 \ + ADCQ BX, R12 \ + MULXQ 16+rb, AX, BX \ + ADCQ AX, R13 \ + ADCQ BX, R14 \ + ADCQ $0, CX \ + MULXQ 8+rb, AX, BX \ + ADDQ AX, R12 \ + ADCQ BX, R13 \ + MULXQ 24+rb, AX, BX \ + ADCQ AX, R14 \ + ADCQ BX, CX + +#define gfpReduceBMI2() \ + \ // m = (T * N') mod R, store m in R8:R9:R10:R11 + MOVQ ·np+0(SB), DX \ + MULXQ 0(SP), R8, R9 \ + MULXQ 8(SP), AX, R10 \ + ADDQ AX, R9 \ + MULXQ 16(SP), AX, R11 \ + ADCQ AX, R10 \ + MULXQ 24(SP), AX, BX \ + ADCQ AX, R11 \ + \ + MOVQ ·np+8(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R9 \ + ADCQ BX, R10 \ + MULXQ 16(SP), AX, BX \ + ADCQ AX, R11 \ + MULXQ 8(SP), AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + \ + MOVQ ·np+16(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R10 \ + ADCQ BX, R11 \ + MULXQ 8(SP), AX, BX \ + ADDQ AX, R11 \ + \ + MOVQ ·np+24(SB), DX \ + MULXQ 0(SP), AX, BX \ + ADDQ AX, R11 \ + \ + storeBlock(R8,R9,R10,R11, 64(SP)) \ + \ + \ // m * N + mulBMI2(·p2+0(SB),·p2+8(SB),·p2+16(SB),·p2+24(SB), 64(SP)) \ + \ + \ // Add the 512-bit intermediate to m*N + MOVQ $0, AX \ + ADDQ 0(SP), R8 \ + ADCQ 8(SP), R9 \ + ADCQ 16(SP), R10 \ + ADCQ 24(SP), R11 \ + ADCQ 32(SP), R12 \ + ADCQ 40(SP), R13 \ + ADCQ 48(SP), R14 \ + ADCQ 56(SP), CX \ + ADCQ $0, AX \ + \ + gfpCarry(R12,R13,R14,CX,AX, R8,R9,R10,R11,BX) diff --git a/crypto/bn256/cloudflare/optate.go b/crypto/bn256/cloudflare/optate.go new file mode 100644 index 0000000..e8caa7a --- /dev/null +++ b/crypto/bn256/cloudflare/optate.go @@ -0,0 +1,270 @@ +package bn256 + +func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2) (a, b, c *gfP2, rOut *twistPoint) { + // See the mixed addition algorithm from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + B := (&gfP2{}).Mul(&p.x, &r.t) + + D := (&gfP2{}).Add(&p.y, &r.z) + D.Square(D).Sub(D, r2).Sub(D, &r.t).Mul(D, &r.t) + + H := (&gfP2{}).Sub(B, &r.x) + I := (&gfP2{}).Square(H) + + E := (&gfP2{}).Add(I, I) + E.Add(E, E) + + J := (&gfP2{}).Mul(H, E) + + L1 := (&gfP2{}).Sub(D, &r.y) + L1.Sub(L1, &r.y) + + V := (&gfP2{}).Mul(&r.x, E) + + rOut = &twistPoint{} + rOut.x.Square(L1).Sub(&rOut.x, J).Sub(&rOut.x, V).Sub(&rOut.x, V) + + rOut.z.Add(&r.z, H).Square(&rOut.z).Sub(&rOut.z, &r.t).Sub(&rOut.z, I) + + t := (&gfP2{}).Sub(V, &rOut.x) + t.Mul(t, L1) + t2 := (&gfP2{}).Mul(&r.y, J) + t2.Add(t2, t2) + rOut.y.Sub(t, t2) + + rOut.t.Square(&rOut.z) + + t.Add(&p.y, &rOut.z).Square(t).Sub(t, r2).Sub(t, &rOut.t) + + t2.Mul(L1, &p.x) + t2.Add(t2, t2) + a = (&gfP2{}).Sub(t2, t) + + c = (&gfP2{}).MulScalar(&rOut.z, &q.y) + c.Add(c, c) + + b = (&gfP2{}).Neg(L1) + b.MulScalar(b, &q.x).Add(b, b) + + return +} + +func lineFunctionDouble(r *twistPoint, q *curvePoint) (a, b, c *gfP2, rOut *twistPoint) { + // See the doubling algorithm for a=0 from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + A := (&gfP2{}).Square(&r.x) + B := (&gfP2{}).Square(&r.y) + C := (&gfP2{}).Square(B) + + D := (&gfP2{}).Add(&r.x, B) + D.Square(D).Sub(D, A).Sub(D, C).Add(D, D) + + E := (&gfP2{}).Add(A, A) + E.Add(E, A) + + G := (&gfP2{}).Square(E) + + rOut = &twistPoint{} + rOut.x.Sub(G, D).Sub(&rOut.x, D) + + rOut.z.Add(&r.y, &r.z).Square(&rOut.z).Sub(&rOut.z, B).Sub(&rOut.z, &r.t) + + rOut.y.Sub(D, &rOut.x).Mul(&rOut.y, E) + t := (&gfP2{}).Add(C, C) + t.Add(t, t).Add(t, t) + rOut.y.Sub(&rOut.y, t) + + rOut.t.Square(&rOut.z) + + t.Mul(E, &r.t).Add(t, t) + b = (&gfP2{}).Neg(t) + b.MulScalar(b, &q.x) + + a = (&gfP2{}).Add(&r.x, E) + a.Square(a).Sub(a, A).Sub(a, G) + t.Add(B, B).Add(t, t) + a.Sub(a, t) + + c = (&gfP2{}).Mul(&rOut.z, &r.t) + c.Add(c, c).MulScalar(c, &q.y) + + return +} + +func mulLine(ret *gfP12, a, b, c *gfP2) { + a2 := &gfP6{} + a2.y.Set(a) + a2.z.Set(b) + a2.Mul(a2, &ret.x) + t3 := (&gfP6{}).MulScalar(&ret.y, c) + + t := (&gfP2{}).Add(b, c) + t2 := &gfP6{} + t2.y.Set(a) + t2.z.Set(t) + ret.x.Add(&ret.x, &ret.y) + + ret.y.Set(t3) + + ret.x.Mul(&ret.x, t2).Sub(&ret.x, a2).Sub(&ret.x, &ret.y) + a2.MulTau(a2) + ret.y.Add(&ret.y, a2) +} + +// sixuPlus2NAF is 6u+2 in non-adjacent form. +var sixuPlus2NAF = []int8{0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1} + +// miller implements the Miller loop for calculating the Optimal Ate pairing. +// See algorithm 1 from http://cryptojedi.org/papers/dclxvi-20100714.pdf +func miller(q *twistPoint, p *curvePoint) *gfP12 { + ret := (&gfP12{}).SetOne() + + aAffine := &twistPoint{} + aAffine.Set(q) + aAffine.MakeAffine() + + bAffine := &curvePoint{} + bAffine.Set(p) + bAffine.MakeAffine() + + minusA := &twistPoint{} + minusA.Neg(aAffine) + + r := &twistPoint{} + r.Set(aAffine) + + r2 := (&gfP2{}).Square(&aAffine.y) + + for i := len(sixuPlus2NAF) - 1; i > 0; i-- { + a, b, c, newR := lineFunctionDouble(r, bAffine) + if i != len(sixuPlus2NAF)-1 { + ret.Square(ret) + } + + mulLine(ret, a, b, c) + r = newR + + switch sixuPlus2NAF[i-1] { + case 1: + a, b, c, newR = lineFunctionAdd(r, aAffine, bAffine, r2) + case -1: + a, b, c, newR = lineFunctionAdd(r, minusA, bAffine, r2) + default: + continue + } + + mulLine(ret, a, b, c) + r = newR + } + + // In order to calculate Q1 we have to convert q from the sextic twist + // to the full GF(p^12) group, apply the Frobenius there, and convert + // back. + // + // The twist isomorphism is (x', y') -> (xω², yω³). If we consider just + // x for a moment, then after applying the Frobenius, we have x̄ω^(2p) + // where xÌ„ is the conjugate of x. If we are going to apply the inverse + // isomorphism we need a value with a single coefficient of ω² so we + // rewrite this as x̄ω^(2p-2)ω². ξⶠ= ω and, due to the construction of + // p, 2p-2 is a multiple of six. Therefore we can rewrite as + // x̄ξ^((p-1)/3)ω² and applying the inverse isomorphism eliminates the + // ω². + // + // A similar argument can be made for the y value. + + q1 := &twistPoint{} + q1.x.Conjugate(&aAffine.x).Mul(&q1.x, xiToPMinus1Over3) + q1.y.Conjugate(&aAffine.y).Mul(&q1.y, xiToPMinus1Over2) + q1.z.SetOne() + q1.t.SetOne() + + // For Q2 we are applying the p² Frobenius. The two conjugations cancel + // out and we are left only with the factors from the isomorphism. In + // the case of x, we end up with a pure number which is why + // xiToPSquaredMinus1Over3 is ∈ GF(p). With y we get a factor of -1. We + // ignore this to end up with -Q2. + + minusQ2 := &twistPoint{} + minusQ2.x.MulScalar(&aAffine.x, xiToPSquaredMinus1Over3) + minusQ2.y.Set(&aAffine.y) + minusQ2.z.SetOne() + minusQ2.t.SetOne() + + r2.Square(&q1.y) + a, b, c, newR := lineFunctionAdd(r, q1, bAffine, r2) + mulLine(ret, a, b, c) + r = newR + + r2.Square(&minusQ2.y) + a, b, c, _ = lineFunctionAdd(r, minusQ2, bAffine, r2) + mulLine(ret, a, b, c) + + return ret +} + +// finalExponentiation computes the (p¹²-1)/Order-th power of an element of +// GF(p¹²) to obtain an element of GT (steps 13-15 of algorithm 1 from +// http://cryptojedi.org/papers/dclxvi-20100714.pdf) +func finalExponentiation(in *gfP12) *gfP12 { + t1 := &gfP12{} + + // This is the p^6-Frobenius + t1.x.Neg(&in.x) + t1.y.Set(&in.y) + + inv := &gfP12{} + inv.Invert(in) + t1.Mul(t1, inv) + + t2 := (&gfP12{}).FrobeniusP2(t1) + t1.Mul(t1, t2) + + fp := (&gfP12{}).Frobenius(t1) + fp2 := (&gfP12{}).FrobeniusP2(t1) + fp3 := (&gfP12{}).Frobenius(fp2) + + fu := (&gfP12{}).Exp(t1, u) + fu2 := (&gfP12{}).Exp(fu, u) + fu3 := (&gfP12{}).Exp(fu2, u) + + y3 := (&gfP12{}).Frobenius(fu) + fu2p := (&gfP12{}).Frobenius(fu2) + fu3p := (&gfP12{}).Frobenius(fu3) + y2 := (&gfP12{}).FrobeniusP2(fu2) + + y0 := &gfP12{} + y0.Mul(fp, fp2).Mul(y0, fp3) + + y1 := (&gfP12{}).Conjugate(t1) + y5 := (&gfP12{}).Conjugate(fu2) + y3.Conjugate(y3) + y4 := (&gfP12{}).Mul(fu, fu2p) + y4.Conjugate(y4) + + y6 := (&gfP12{}).Mul(fu3, fu3p) + y6.Conjugate(y6) + + t0 := (&gfP12{}).Square(y6) + t0.Mul(t0, y4).Mul(t0, y5) + t1.Mul(y3, y5).Mul(t1, t0) + t0.Mul(t0, y2) + t1.Square(t1).Mul(t1, t0).Square(t1) + t0.Mul(t1, y1) + t1.Mul(t1, y0) + t0.Square(t0).Mul(t0, t1) + + return t0 +} + +func optimalAte(a *twistPoint, b *curvePoint) *gfP12 { + e := miller(a, b) + ret := finalExponentiation(e) + + if a.IsInfinity() || b.IsInfinity() { + ret.SetOne() + } + return ret +} diff --git a/crypto/bn256/cloudflare/twist.go b/crypto/bn256/cloudflare/twist.go new file mode 100644 index 0000000..2c7a69a --- /dev/null +++ b/crypto/bn256/cloudflare/twist.go @@ -0,0 +1,204 @@ +package bn256 + +import ( + "math/big" +) + +// twistPoint implements the elliptic curve y²=x³+3/ξ over GF(p²). Points are +// kept in Jacobian form and t=z² when valid. The group Gâ‚‚ is the set of +// n-torsion points of this curve over GF(p²) (where n = Order) +type twistPoint struct { + x, y, z, t gfP2 +} + +var twistB = &gfP2{ + gfP{0x38e7ecccd1dcff67, 0x65f0b37d93ce0d3e, 0xd749d0dd22ac00aa, 0x0141b9ce4a688d4d}, + gfP{0x3bf938e377b802a8, 0x020b1b273633535d, 0x26b7edf049755260, 0x2514c6324384a86d}, +} + +// twistGen is the generator of group Gâ‚‚. +var twistGen = &twistPoint{ + gfP2{ + gfP{0xafb4737da84c6140, 0x6043dd5a5802d8c4, 0x09e950fc52a02f86, 0x14fef0833aea7b6b}, + gfP{0x8e83b5d102bc2026, 0xdceb1935497b0172, 0xfbb8264797811adf, 0x19573841af96503b}, + }, + gfP2{ + gfP{0x64095b56c71856ee, 0xdc57f922327d3cbb, 0x55f935be33351076, 0x0da4a0e693fd6482}, + gfP{0x619dfa9d886be9f6, 0xfe7fd297f59e9b78, 0xff9e1a62231b7dfe, 0x28fd7eebae9e4206}, + }, + gfP2{*newGFp(0), *newGFp(1)}, + gfP2{*newGFp(0), *newGFp(1)}, +} + +func (c *twistPoint) String() string { + c.MakeAffine() + x, y := gfP2Decode(&c.x), gfP2Decode(&c.y) + return "(" + x.String() + ", " + y.String() + ")" +} + +func (c *twistPoint) Set(a *twistPoint) { + c.x.Set(&a.x) + c.y.Set(&a.y) + c.z.Set(&a.z) + c.t.Set(&a.t) +} + +// IsOnCurve returns true iff c is on the curve. +func (c *twistPoint) IsOnCurve() bool { + c.MakeAffine() + if c.IsInfinity() { + return true + } + + y2, x3 := &gfP2{}, &gfP2{} + y2.Square(&c.y) + x3.Square(&c.x).Mul(x3, &c.x).Add(x3, twistB) + + if *y2 != *x3 { + return false + } + cneg := &twistPoint{} + cneg.Mul(c, Order) + return cneg.z.IsZero() +} + +func (c *twistPoint) SetInfinity() { + c.x.SetZero() + c.y.SetOne() + c.z.SetZero() + c.t.SetZero() +} + +func (c *twistPoint) IsInfinity() bool { + return c.z.IsZero() +} + +func (c *twistPoint) Add(a, b *twistPoint) { + // For additional comments, see the same function in curve.go. + + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + z12 := (&gfP2{}).Square(&a.z) + z22 := (&gfP2{}).Square(&b.z) + u1 := (&gfP2{}).Mul(&a.x, z22) + u2 := (&gfP2{}).Mul(&b.x, z12) + + t := (&gfP2{}).Mul(&b.z, z22) + s1 := (&gfP2{}).Mul(&a.y, t) + + t.Mul(&a.z, z12) + s2 := (&gfP2{}).Mul(&b.y, t) + + h := (&gfP2{}).Sub(u2, u1) + xEqual := h.IsZero() + + t.Add(h, h) + i := (&gfP2{}).Square(t) + j := (&gfP2{}).Mul(h, i) + + t.Sub(s2, s1) + yEqual := t.IsZero() + if xEqual && yEqual { + c.Double(a) + return + } + r := (&gfP2{}).Add(t, t) + + v := (&gfP2{}).Mul(u1, i) + + t4 := (&gfP2{}).Square(r) + t.Add(v, v) + t6 := (&gfP2{}).Sub(t4, j) + c.x.Sub(t6, t) + + t.Sub(v, &c.x) // t7 + t4.Mul(s1, j) // t8 + t6.Add(t4, t4) // t9 + t4.Mul(r, t) // t10 + c.y.Sub(t4, t6) + + t.Add(&a.z, &b.z) // t11 + t4.Square(t) // t12 + t.Sub(t4, z12) // t13 + t4.Sub(t, z22) // t14 + c.z.Mul(t4, h) +} + +func (c *twistPoint) Double(a *twistPoint) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A := (&gfP2{}).Square(&a.x) + B := (&gfP2{}).Square(&a.y) + C := (&gfP2{}).Square(B) + + t := (&gfP2{}).Add(&a.x, B) + t2 := (&gfP2{}).Square(t) + t.Sub(t2, A) + t2.Sub(t, C) + d := (&gfP2{}).Add(t2, t2) + t.Add(A, A) + e := (&gfP2{}).Add(t, A) + f := (&gfP2{}).Square(e) + + t.Add(d, d) + c.x.Sub(f, t) + + c.z.Mul(&a.y, &a.z) + c.z.Add(&c.z, &c.z) + + t.Add(C, C) + t2.Add(t, t) + t.Add(t2, t2) + c.y.Sub(d, &c.x) + t2.Mul(e, &c.y) + c.y.Sub(t2, t) +} + +func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int) { + sum, t := &twistPoint{}, &twistPoint{} + + for i := scalar.BitLen(); i >= 0; i-- { + t.Double(sum) + if scalar.Bit(i) != 0 { + sum.Add(t, a) + } else { + sum.Set(t) + } + } + + c.Set(sum) +} + +func (c *twistPoint) MakeAffine() { + if c.z.IsOne() { + return + } else if c.z.IsZero() { + c.x.SetZero() + c.y.SetOne() + c.t.SetZero() + return + } + + zInv := (&gfP2{}).Invert(&c.z) + t := (&gfP2{}).Mul(&c.y, zInv) + zInv2 := (&gfP2{}).Square(zInv) + c.y.Mul(t, zInv2) + t.Mul(&c.x, zInv2) + c.x.Set(t) + c.z.SetOne() + c.t.SetOne() +} + +func (c *twistPoint) Neg(a *twistPoint) { + c.x.Set(&a.x) + c.y.Neg(&a.y) + c.z.Set(&a.z) + c.t.SetZero() +} diff --git a/crypto/bn256/google/bn256.go b/crypto/bn256/google/bn256.go new file mode 100644 index 0000000..aca9cf6 --- /dev/null +++ b/crypto/bn256/google/bn256.go @@ -0,0 +1,460 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bn256 implements a particular bilinear group. +// +// Bilinear groups are the basis of many of the new cryptographic protocols +// that have been proposed over the past decade. They consist of a triplet of +// groups (Gâ‚, Gâ‚‚ and GT) such that there exists a function e(gâ‚Ë£,g₂ʸ)=gTˣʸ +// (where gâ‚“ is a generator of the respective group). That function is called +// a pairing function. +// +// This package specifically implements the Optimal Ate pairing over a 256-bit +// Barreto-Naehrig curve as described in +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not +// compatible with the implementation described in that paper, as different +// parameters are chosen. +// +// (This package previously claimed to operate at a 128-bit security level. +// However, recent improvements in attacks mean that is no longer true. See +// https://moderncrypto.org/mail-archive/curves/2016/000740.html.) +package bn256 + +import ( + "crypto/rand" + "errors" + "io" + "math/big" +) + +// BUG(agl): this implementation is not constant time. +// TODO(agl): keep GF(p²) elements in Montgomery form. + +// G1 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G1 struct { + p *curvePoint +} + +// RandomG1 returns x and gâ‚Ë£ where x is a random, non-zero number read from r. +func RandomG1(r io.Reader) (*big.Int, *G1, error) { + var k *big.Int + var err error + + for { + k, err = rand.Int(r, Order) + if err != nil { + return nil, nil, err + } + if k.Sign() > 0 { + break + } + } + + return k, new(G1).ScalarBaseMult(k), nil +} + +func (e *G1) String() string { + return "bn256.G1" + e.p.String() +} + +// CurvePoints returns p's curve points in big integer +func (e *G1) CurvePoints() (*big.Int, *big.Int, *big.Int, *big.Int) { + return e.p.x, e.p.y, e.p.z, e.p.t +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and +// then returns e. +func (e *G1) ScalarBaseMult(k *big.Int) *G1 { + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.Mul(curveGen, k, new(bnPool)) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G1) ScalarMult(a *G1, k *big.Int) *G1 { + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.Mul(a.p, k, new(bnPool)) + return e +} + +// Add sets e to a+b and then returns e. +// BUG(agl): this function is not complete: a==b fails. +func (e *G1) Add(a, b *G1) *G1 { + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.Add(a.p, b.p, new(bnPool)) + return e +} + +// Neg sets e to -a and then returns e. +func (e *G1) Neg(a *G1) *G1 { + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.Negative(a.p) + return e +} + +// Marshal converts n to a byte slice. +func (e *G1) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if e.p.IsInfinity() { + return make([]byte, numBytes*2) + } + + e.p.MakeAffine(nil) + + xBytes := new(big.Int).Mod(e.p.x, P).Bytes() + yBytes := new(big.Int).Mod(e.p.y, P).Bytes() + + ret := make([]byte, numBytes*2) + copy(ret[1*numBytes-len(xBytes):], xBytes) + copy(ret[2*numBytes-len(yBytes):], yBytes) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G1) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) != 2*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = newCurvePoint(nil) + } + e.p.x.SetBytes(m[0*numBytes : 1*numBytes]) + if e.p.x.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + e.p.y.SetBytes(m[1*numBytes : 2*numBytes]) + if e.p.y.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + // Ensure the point is on the curve + if e.p.x.Sign() == 0 && e.p.y.Sign() == 0 { + // This is the point at infinity. + e.p.y.SetInt64(1) + e.p.z.SetInt64(0) + e.p.t.SetInt64(0) + } else { + e.p.z.SetInt64(1) + e.p.t.SetInt64(1) + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[2*numBytes:], nil +} + +// G2 is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type G2 struct { + p *twistPoint +} + +// RandomG2 returns x and gâ‚‚Ë£ where x is a random, non-zero number read from r. +func RandomG2(r io.Reader) (*big.Int, *G2, error) { + var k *big.Int + var err error + + for { + k, err = rand.Int(r, Order) + if err != nil { + return nil, nil, err + } + if k.Sign() > 0 { + break + } + } + + return k, new(G2).ScalarBaseMult(k), nil +} + +func (e *G2) String() string { + return "bn256.G2" + e.p.String() +} + +// CurvePoints returns the curve points of p which includes the real +// and imaginary parts of the curve point. +func (e *G2) CurvePoints() (*gfP2, *gfP2, *gfP2, *gfP2) { + return e.p.x, e.p.y, e.p.z, e.p.t +} + +// ScalarBaseMult sets e to g*k where g is the generator of the group and +// then returns out. +func (e *G2) ScalarBaseMult(k *big.Int) *G2 { + if e.p == nil { + e.p = newTwistPoint(nil) + } + e.p.Mul(twistGen, k, new(bnPool)) + return e +} + +// ScalarMult sets e to a*k and then returns e. +func (e *G2) ScalarMult(a *G2, k *big.Int) *G2 { + if e.p == nil { + e.p = newTwistPoint(nil) + } + e.p.Mul(a.p, k, new(bnPool)) + return e +} + +// Add sets e to a+b and then returns e. +// BUG(agl): this function is not complete: a==b fails. +func (e *G2) Add(a, b *G2) *G2 { + if e.p == nil { + e.p = newTwistPoint(nil) + } + e.p.Add(a.p, b.p, new(bnPool)) + return e +} + +// Marshal converts n into a byte slice. +func (n *G2) Marshal() []byte { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if n.p.IsInfinity() { + return make([]byte, numBytes*4) + } + + n.p.MakeAffine(nil) + + xxBytes := new(big.Int).Mod(n.p.x.x, P).Bytes() + xyBytes := new(big.Int).Mod(n.p.x.y, P).Bytes() + yxBytes := new(big.Int).Mod(n.p.y.x, P).Bytes() + yyBytes := new(big.Int).Mod(n.p.y.y, P).Bytes() + + ret := make([]byte, numBytes*4) + copy(ret[1*numBytes-len(xxBytes):], xxBytes) + copy(ret[2*numBytes-len(xyBytes):], xyBytes) + copy(ret[3*numBytes-len(yxBytes):], yxBytes) + copy(ret[4*numBytes-len(yyBytes):], yyBytes) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *G2) Unmarshal(m []byte) ([]byte, error) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + if len(m) != 4*numBytes { + return nil, errors.New("bn256: not enough data") + } + // Unmarshal the points and check their caps + if e.p == nil { + e.p = newTwistPoint(nil) + } + e.p.x.x.SetBytes(m[0*numBytes : 1*numBytes]) + if e.p.x.x.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + e.p.x.y.SetBytes(m[1*numBytes : 2*numBytes]) + if e.p.x.y.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + e.p.y.x.SetBytes(m[2*numBytes : 3*numBytes]) + if e.p.y.x.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + e.p.y.y.SetBytes(m[3*numBytes : 4*numBytes]) + if e.p.y.y.Cmp(P) >= 0 { + return nil, errors.New("bn256: coordinate exceeds modulus") + } + // Ensure the point is on the curve + if e.p.x.x.Sign() == 0 && + e.p.x.y.Sign() == 0 && + e.p.y.x.Sign() == 0 && + e.p.y.y.Sign() == 0 { + // This is the point at infinity. + e.p.y.SetOne() + e.p.z.SetZero() + e.p.t.SetZero() + } else { + e.p.z.SetOne() + e.p.t.SetOne() + + if !e.p.IsOnCurve() { + return nil, errors.New("bn256: malformed point") + } + } + return m[4*numBytes:], nil +} + +// GT is an abstract cyclic group. The zero value is suitable for use as the +// output of an operation, but cannot be used as an input. +type GT struct { + p *gfP12 +} + +func (g *GT) String() string { + return "bn256.GT" + g.p.String() +} + +// ScalarMult sets e to a*k and then returns e. +func (e *GT) ScalarMult(a *GT, k *big.Int) *GT { + if e.p == nil { + e.p = newGFp12(nil) + } + e.p.Exp(a.p, k, new(bnPool)) + return e +} + +// Add sets e to a+b and then returns e. +func (e *GT) Add(a, b *GT) *GT { + if e.p == nil { + e.p = newGFp12(nil) + } + e.p.Mul(a.p, b.p, new(bnPool)) + return e +} + +// Neg sets e to -a and then returns e. +func (e *GT) Neg(a *GT) *GT { + if e.p == nil { + e.p = newGFp12(nil) + } + e.p.Invert(a.p, new(bnPool)) + return e +} + +// Marshal converts n into a byte slice. +func (n *GT) Marshal() []byte { + n.p.Minimal() + + xxxBytes := n.p.x.x.x.Bytes() + xxyBytes := n.p.x.x.y.Bytes() + xyxBytes := n.p.x.y.x.Bytes() + xyyBytes := n.p.x.y.y.Bytes() + xzxBytes := n.p.x.z.x.Bytes() + xzyBytes := n.p.x.z.y.Bytes() + yxxBytes := n.p.y.x.x.Bytes() + yxyBytes := n.p.y.x.y.Bytes() + yyxBytes := n.p.y.y.x.Bytes() + yyyBytes := n.p.y.y.y.Bytes() + yzxBytes := n.p.y.z.x.Bytes() + yzyBytes := n.p.y.z.y.Bytes() + + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + ret := make([]byte, numBytes*12) + copy(ret[1*numBytes-len(xxxBytes):], xxxBytes) + copy(ret[2*numBytes-len(xxyBytes):], xxyBytes) + copy(ret[3*numBytes-len(xyxBytes):], xyxBytes) + copy(ret[4*numBytes-len(xyyBytes):], xyyBytes) + copy(ret[5*numBytes-len(xzxBytes):], xzxBytes) + copy(ret[6*numBytes-len(xzyBytes):], xzyBytes) + copy(ret[7*numBytes-len(yxxBytes):], yxxBytes) + copy(ret[8*numBytes-len(yxyBytes):], yxyBytes) + copy(ret[9*numBytes-len(yyxBytes):], yyxBytes) + copy(ret[10*numBytes-len(yyyBytes):], yyyBytes) + copy(ret[11*numBytes-len(yzxBytes):], yzxBytes) + copy(ret[12*numBytes-len(yzyBytes):], yzyBytes) + + return ret +} + +// Unmarshal sets e to the result of converting the output of Marshal back into +// a group element and then returns e. +func (e *GT) Unmarshal(m []byte) (*GT, bool) { + // Each value is a 256-bit number. + const numBytes = 256 / 8 + + if len(m) != 12*numBytes { + return nil, false + } + + if e.p == nil { + e.p = newGFp12(nil) + } + + e.p.x.x.x.SetBytes(m[0*numBytes : 1*numBytes]) + e.p.x.x.y.SetBytes(m[1*numBytes : 2*numBytes]) + e.p.x.y.x.SetBytes(m[2*numBytes : 3*numBytes]) + e.p.x.y.y.SetBytes(m[3*numBytes : 4*numBytes]) + e.p.x.z.x.SetBytes(m[4*numBytes : 5*numBytes]) + e.p.x.z.y.SetBytes(m[5*numBytes : 6*numBytes]) + e.p.y.x.x.SetBytes(m[6*numBytes : 7*numBytes]) + e.p.y.x.y.SetBytes(m[7*numBytes : 8*numBytes]) + e.p.y.y.x.SetBytes(m[8*numBytes : 9*numBytes]) + e.p.y.y.y.SetBytes(m[9*numBytes : 10*numBytes]) + e.p.y.z.x.SetBytes(m[10*numBytes : 11*numBytes]) + e.p.y.z.y.SetBytes(m[11*numBytes : 12*numBytes]) + + return e, true +} + +// Pair calculates an Optimal Ate pairing. +func Pair(g1 *G1, g2 *G2) *GT { + return >{optimalAte(g2.p, g1.p, new(bnPool))} +} + +// PairingCheck calculates the Optimal Ate pairing for a set of points. +func PairingCheck(a []*G1, b []*G2) bool { + pool := new(bnPool) + + acc := newGFp12(pool) + acc.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].p.IsInfinity() || b[i].p.IsInfinity() { + continue + } + acc.Mul(acc, miller(b[i].p, a[i].p, pool), pool) + } + ret := finalExponentiation(acc, pool) + acc.Put(pool) + + return ret.IsOne() +} + +// bnPool implements a tiny cache of *big.Int objects that's used to reduce the +// number of allocations made during processing. +type bnPool struct { + bns []*big.Int + count int +} + +func (pool *bnPool) Get() *big.Int { + if pool == nil { + return new(big.Int) + } + + pool.count++ + l := len(pool.bns) + if l == 0 { + return new(big.Int) + } + + bn := pool.bns[l-1] + pool.bns = pool.bns[:l-1] + return bn +} + +func (pool *bnPool) Put(bn *big.Int) { + if pool == nil { + return + } + pool.bns = append(pool.bns, bn) + pool.count-- +} + +func (pool *bnPool) Count() int { + return pool.count +} diff --git a/crypto/bn256/google/bn256_test.go b/crypto/bn256/google/bn256_test.go new file mode 100644 index 0000000..a4497ad --- /dev/null +++ b/crypto/bn256/google/bn256_test.go @@ -0,0 +1,311 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "bytes" + "crypto/rand" + "math/big" + "testing" +) + +func TestGFp2Invert(t *testing.T) { + pool := new(bnPool) + + a := newGFp2(pool) + a.x.SetString("23423492374", 10) + a.y.SetString("12934872398472394827398470", 10) + + inv := newGFp2(pool) + inv.Invert(a, pool) + + b := newGFp2(pool).Mul(inv, a, pool) + if b.x.Int64() != 0 || b.y.Int64() != 1 { + t.Fatalf("bad result for a^-1*a: %s %s", b.x, b.y) + } + + a.Put(pool) + b.Put(pool) + inv.Put(pool) + + if c := pool.Count(); c > 0 { + t.Errorf("Pool count non-zero: %d\n", c) + } +} + +func isZero(n *big.Int) bool { + return new(big.Int).Mod(n, P).Int64() == 0 +} + +func isOne(n *big.Int) bool { + return new(big.Int).Mod(n, P).Int64() == 1 +} + +func TestGFp6Invert(t *testing.T) { + pool := new(bnPool) + + a := newGFp6(pool) + a.x.x.SetString("239487238491", 10) + a.x.y.SetString("2356249827341", 10) + a.y.x.SetString("082659782", 10) + a.y.y.SetString("182703523765", 10) + a.z.x.SetString("978236549263", 10) + a.z.y.SetString("64893242", 10) + + inv := newGFp6(pool) + inv.Invert(a, pool) + + b := newGFp6(pool).Mul(inv, a, pool) + if !isZero(b.x.x) || + !isZero(b.x.y) || + !isZero(b.y.x) || + !isZero(b.y.y) || + !isZero(b.z.x) || + !isOne(b.z.y) { + t.Fatalf("bad result for a^-1*a: %s", b) + } + + a.Put(pool) + b.Put(pool) + inv.Put(pool) + + if c := pool.Count(); c > 0 { + t.Errorf("Pool count non-zero: %d\n", c) + } +} + +func TestGFp12Invert(t *testing.T) { + pool := new(bnPool) + + a := newGFp12(pool) + a.x.x.x.SetString("239846234862342323958623", 10) + a.x.x.y.SetString("2359862352529835623", 10) + a.x.y.x.SetString("928836523", 10) + a.x.y.y.SetString("9856234", 10) + a.x.z.x.SetString("235635286", 10) + a.x.z.y.SetString("5628392833", 10) + a.y.x.x.SetString("252936598265329856238956532167968", 10) + a.y.x.y.SetString("23596239865236954178968", 10) + a.y.y.x.SetString("95421692834", 10) + a.y.y.y.SetString("236548", 10) + a.y.z.x.SetString("924523", 10) + a.y.z.y.SetString("12954623", 10) + + inv := newGFp12(pool) + inv.Invert(a, pool) + + b := newGFp12(pool).Mul(inv, a, pool) + if !isZero(b.x.x.x) || + !isZero(b.x.x.y) || + !isZero(b.x.y.x) || + !isZero(b.x.y.y) || + !isZero(b.x.z.x) || + !isZero(b.x.z.y) || + !isZero(b.y.x.x) || + !isZero(b.y.x.y) || + !isZero(b.y.y.x) || + !isZero(b.y.y.y) || + !isZero(b.y.z.x) || + !isOne(b.y.z.y) { + t.Fatalf("bad result for a^-1*a: %s", b) + } + + a.Put(pool) + b.Put(pool) + inv.Put(pool) + + if c := pool.Count(); c > 0 { + t.Errorf("Pool count non-zero: %d\n", c) + } +} + +func TestCurveImpl(t *testing.T) { + pool := new(bnPool) + + g := &curvePoint{ + pool.Get().SetInt64(1), + pool.Get().SetInt64(-2), + pool.Get().SetInt64(1), + pool.Get().SetInt64(0), + } + + x := pool.Get().SetInt64(32498273234) + X := newCurvePoint(pool).Mul(g, x, pool) + + y := pool.Get().SetInt64(98732423523) + Y := newCurvePoint(pool).Mul(g, y, pool) + + s1 := newCurvePoint(pool).Mul(X, y, pool).MakeAffine(pool) + s2 := newCurvePoint(pool).Mul(Y, x, pool).MakeAffine(pool) + + if s1.x.Cmp(s2.x) != 0 || + s2.x.Cmp(s1.x) != 0 { + t.Errorf("DH points don't match: (%s, %s) (%s, %s)", s1.x, s1.y, s2.x, s2.y) + } + + pool.Put(x) + X.Put(pool) + pool.Put(y) + Y.Put(pool) + s1.Put(pool) + s2.Put(pool) + g.Put(pool) + + if c := pool.Count(); c > 0 { + t.Errorf("Pool count non-zero: %d\n", c) + } +} + +func TestOrderG1(t *testing.T) { + g := new(G1).ScalarBaseMult(Order) + if !g.p.IsInfinity() { + t.Error("G1 has incorrect order") + } + + one := new(G1).ScalarBaseMult(new(big.Int).SetInt64(1)) + g.Add(g, one) + g.p.MakeAffine(nil) + if g.p.x.Cmp(one.p.x) != 0 || g.p.y.Cmp(one.p.y) != 0 { + t.Errorf("1+0 != 1 in G1") + } +} + +func TestOrderG2(t *testing.T) { + g := new(G2).ScalarBaseMult(Order) + if !g.p.IsInfinity() { + t.Error("G2 has incorrect order") + } + + one := new(G2).ScalarBaseMult(new(big.Int).SetInt64(1)) + g.Add(g, one) + g.p.MakeAffine(nil) + if g.p.x.x.Cmp(one.p.x.x) != 0 || + g.p.x.y.Cmp(one.p.x.y) != 0 || + g.p.y.x.Cmp(one.p.y.x) != 0 || + g.p.y.y.Cmp(one.p.y.y) != 0 { + t.Errorf("1+0 != 1 in G2") + } +} + +func TestOrderGT(t *testing.T) { + gt := Pair(&G1{curveGen}, &G2{twistGen}) + g := new(GT).ScalarMult(gt, Order) + if !g.p.IsOne() { + t.Error("GT has incorrect order") + } +} + +func TestBilinearity(t *testing.T) { + for i := 0; i < 2; i++ { + a, p1, _ := RandomG1(rand.Reader) + b, p2, _ := RandomG2(rand.Reader) + e1 := Pair(p1, p2) + + e2 := Pair(&G1{curveGen}, &G2{twistGen}) + e2.ScalarMult(e2, a) + e2.ScalarMult(e2, b) + + minusE2 := new(GT).Neg(e2) + e1.Add(e1, minusE2) + + if !e1.p.IsOne() { + t.Fatalf("bad pairing result: %s", e1) + } + } +} + +func TestG1Marshal(t *testing.T) { + g := new(G1).ScalarBaseMult(new(big.Int).SetInt64(1)) + form := g.Marshal() + _, err := new(G1).Unmarshal(form) + if err != nil { + t.Fatalf("failed to unmarshal") + } + + g.ScalarBaseMult(Order) + form = g.Marshal() + + g2 := new(G1) + if _, err = g2.Unmarshal(form); err != nil { + t.Fatalf("failed to unmarshal ∞") + } + if !g2.p.IsInfinity() { + t.Fatalf("∞ unmarshaled incorrectly") + } +} + +func TestG2Marshal(t *testing.T) { + g := new(G2).ScalarBaseMult(new(big.Int).SetInt64(1)) + form := g.Marshal() + _, err := new(G2).Unmarshal(form) + if err != nil { + t.Fatalf("failed to unmarshal") + } + + g.ScalarBaseMult(Order) + form = g.Marshal() + g2 := new(G2) + if _, err = g2.Unmarshal(form); err != nil { + t.Fatalf("failed to unmarshal ∞") + } + if !g2.p.IsInfinity() { + t.Fatalf("∞ unmarshaled incorrectly") + } +} + +func TestG1Identity(t *testing.T) { + g := new(G1).ScalarBaseMult(new(big.Int).SetInt64(0)) + if !g.p.IsInfinity() { + t.Error("failure") + } +} + +func TestG2Identity(t *testing.T) { + g := new(G2).ScalarBaseMult(new(big.Int).SetInt64(0)) + if !g.p.IsInfinity() { + t.Error("failure") + } +} + +func TestTripartiteDiffieHellman(t *testing.T) { + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + pa := new(G1) + pa.Unmarshal(new(G1).ScalarBaseMult(a).Marshal()) + qa := new(G2) + qa.Unmarshal(new(G2).ScalarBaseMult(a).Marshal()) + pb := new(G1) + pb.Unmarshal(new(G1).ScalarBaseMult(b).Marshal()) + qb := new(G2) + qb.Unmarshal(new(G2).ScalarBaseMult(b).Marshal()) + pc := new(G1) + pc.Unmarshal(new(G1).ScalarBaseMult(c).Marshal()) + qc := new(G2) + qc.Unmarshal(new(G2).ScalarBaseMult(c).Marshal()) + + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + k1Bytes := k1.Marshal() + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + k2Bytes := k2.Marshal() + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + k3Bytes := k3.Marshal() + + if !bytes.Equal(k1Bytes, k2Bytes) || !bytes.Equal(k2Bytes, k3Bytes) { + t.Errorf("keys didn't agree") + } +} + +func BenchmarkPairing(b *testing.B) { + for i := 0; i < b.N; i++ { + Pair(&G1{curveGen}, &G2{twistGen}) + } +} diff --git a/crypto/bn256/google/constants.go b/crypto/bn256/google/constants.go new file mode 100644 index 0000000..2990bd9 --- /dev/null +++ b/crypto/bn256/google/constants.go @@ -0,0 +1,47 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +func bigFromBase10(s string) *big.Int { + n, _ := new(big.Int).SetString(s, 10) + return n +} + +// u is the BN parameter that determines the prime. +var u = bigFromBase10("4965661367192848881") + +// P is a prime over which we form a basic field: 36uâ´+36u³+24u²+6u+1. +var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") + +// Order is the number of elements in both Gâ‚ and Gâ‚‚: 36uâ´+36u³+18u²+6u+1. +// Needs to be highly 2-adic for efficient SNARK key and proof generation. +// Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081. +// Refer to https://eprint.iacr.org/2013/879.pdf and https://eprint.iacr.org/2013/507.pdf for more information on these parameters. +var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") + +// xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. +var xiToPMinus1Over6 = &gfP2{bigFromBase10("16469823323077808223889137241176536799009286646108169935659301613961712198316"), bigFromBase10("8376118865763821496583973867626364092589906065868298776909617916018768340080")} + +// xiToPMinus1Over3 is ξ^((p-1)/3) where ξ = i+9. +var xiToPMinus1Over3 = &gfP2{bigFromBase10("10307601595873709700152284273816112264069230130616436755625194854815875713954"), bigFromBase10("21575463638280843010398324269430826099269044274347216827212613867836435027261")} + +// xiToPMinus1Over2 is ξ^((p-1)/2) where ξ = i+9. +var xiToPMinus1Over2 = &gfP2{bigFromBase10("3505843767911556378687030309984248845540243509899259641013678093033130930403"), bigFromBase10("2821565182194536844548159561693502659359617185244120367078079554186484126554")} + +// xiToPSquaredMinus1Over3 is ξ^((p²-1)/3) where ξ = i+9. +var xiToPSquaredMinus1Over3 = bigFromBase10("21888242871839275220042445260109153167277707414472061641714758635765020556616") + +// xiTo2PSquaredMinus2Over3 is ξ^((2p²-2)/3) where ξ = i+9 (a cubic root of unity, mod p). +var xiTo2PSquaredMinus2Over3 = bigFromBase10("2203960485148121921418603742825762020974279258880205651966") + +// xiToPSquaredMinus1Over6 is ξ^((1p²-1)/6) where ξ = i+9 (a cubic root of -1, mod p). +var xiToPSquaredMinus1Over6 = bigFromBase10("21888242871839275220042445260109153167277707414472061641714758635765020556617") + +// xiTo2PMinus2Over3 is ξ^((2p-2)/3) where ξ = i+9. +var xiTo2PMinus2Over3 = &gfP2{bigFromBase10("19937756971775647987995932169929341994314640652964949448313374472400716661030"), bigFromBase10("2581911344467009335267311115468803099551665605076196740867805258568234346338")} diff --git a/crypto/bn256/google/curve.go b/crypto/bn256/google/curve.go new file mode 100644 index 0000000..819cb81 --- /dev/null +++ b/crypto/bn256/google/curve.go @@ -0,0 +1,286 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +// curvePoint implements the elliptic curve y²=x³+3. Points are kept in +// Jacobian form and t=z² when valid. Gâ‚ is the set of points of this curve on +// GF(p). +type curvePoint struct { + x, y, z, t *big.Int +} + +var curveB = new(big.Int).SetInt64(3) + +// curveGen is the generator of Gâ‚. +var curveGen = &curvePoint{ + new(big.Int).SetInt64(1), + new(big.Int).SetInt64(2), + new(big.Int).SetInt64(1), + new(big.Int).SetInt64(1), +} + +func newCurvePoint(pool *bnPool) *curvePoint { + return &curvePoint{ + pool.Get(), + pool.Get(), + pool.Get(), + pool.Get(), + } +} + +func (c *curvePoint) String() string { + c.MakeAffine(new(bnPool)) + return "(" + c.x.String() + ", " + c.y.String() + ")" +} + +func (c *curvePoint) Put(pool *bnPool) { + pool.Put(c.x) + pool.Put(c.y) + pool.Put(c.z) + pool.Put(c.t) +} + +func (c *curvePoint) Set(a *curvePoint) { + c.x.Set(a.x) + c.y.Set(a.y) + c.z.Set(a.z) + c.t.Set(a.t) +} + +// IsOnCurve returns true iff c is on the curve where c must be in affine form. +func (c *curvePoint) IsOnCurve() bool { + yy := new(big.Int).Mul(c.y, c.y) + xxx := new(big.Int).Mul(c.x, c.x) + xxx.Mul(xxx, c.x) + yy.Sub(yy, xxx) + yy.Sub(yy, curveB) + if yy.Sign() < 0 || yy.Cmp(P) >= 0 { + yy.Mod(yy, P) + } + return yy.Sign() == 0 +} + +func (c *curvePoint) SetInfinity() { + c.z.SetInt64(0) +} + +func (c *curvePoint) IsInfinity() bool { + return c.z.Sign() == 0 +} + +func (c *curvePoint) Add(a, b *curvePoint, pool *bnPool) { + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + + // Normalize the points by replacing a = [x1:y1:z1] and b = [x2:y2:z2] + // by [u1:s1:z1·z2] and [u2:s2:z1·z2] + // where u1 = x1·z2², s1 = y1·z2³ and u1 = x2·z1², s2 = y2·z1³ + z1z1 := pool.Get().Mul(a.z, a.z) + z1z1.Mod(z1z1, P) + z2z2 := pool.Get().Mul(b.z, b.z) + z2z2.Mod(z2z2, P) + u1 := pool.Get().Mul(a.x, z2z2) + u1.Mod(u1, P) + u2 := pool.Get().Mul(b.x, z1z1) + u2.Mod(u2, P) + + t := pool.Get().Mul(b.z, z2z2) + t.Mod(t, P) + s1 := pool.Get().Mul(a.y, t) + s1.Mod(s1, P) + + t.Mul(a.z, z1z1) + t.Mod(t, P) + s2 := pool.Get().Mul(b.y, t) + s2.Mod(s2, P) + + // Compute x = (2h)²(s²-u1-u2) + // where s = (s2-s1)/(u2-u1) is the slope of the line through + // (u1,s1) and (u2,s2). The extra factor 2h = 2(u2-u1) comes from the value of z below. + // This is also: + // 4(s2-s1)² - 4h²(u1+u2) = 4(s2-s1)² - 4h³ - 4h²(2u1) + // = r² - j - 2v + // with the notations below. + h := pool.Get().Sub(u2, u1) + xEqual := h.Sign() == 0 + + t.Add(h, h) + // i = 4h² + i := pool.Get().Mul(t, t) + i.Mod(i, P) + // j = 4h³ + j := pool.Get().Mul(h, i) + j.Mod(j, P) + + t.Sub(s2, s1) + yEqual := t.Sign() == 0 + if xEqual && yEqual { + c.Double(a, pool) + return + } + r := pool.Get().Add(t, t) + + v := pool.Get().Mul(u1, i) + v.Mod(v, P) + + // t4 = 4(s2-s1)² + t4 := pool.Get().Mul(r, r) + t4.Mod(t4, P) + t.Add(v, v) + t6 := pool.Get().Sub(t4, j) + c.x.Sub(t6, t) + + // Set y = -(2h)³(s1 + s*(x/4h²-u1)) + // This is also + // y = - 2·s1·j - (s2-s1)(2x - 2i·u1) = r(v-x) - 2·s1·j + t.Sub(v, c.x) // t7 + t4.Mul(s1, j) // t8 + t4.Mod(t4, P) + t6.Add(t4, t4) // t9 + t4.Mul(r, t) // t10 + t4.Mod(t4, P) + c.y.Sub(t4, t6) + + // Set z = 2(u2-u1)·z1·z2 = 2h·z1·z2 + t.Add(a.z, b.z) // t11 + t4.Mul(t, t) // t12 + t4.Mod(t4, P) + t.Sub(t4, z1z1) // t13 + t4.Sub(t, z2z2) // t14 + c.z.Mul(t4, h) + c.z.Mod(c.z, P) + + pool.Put(z1z1) + pool.Put(z2z2) + pool.Put(u1) + pool.Put(u2) + pool.Put(t) + pool.Put(s1) + pool.Put(s2) + pool.Put(h) + pool.Put(i) + pool.Put(j) + pool.Put(r) + pool.Put(v) + pool.Put(t4) + pool.Put(t6) +} + +func (c *curvePoint) Double(a *curvePoint, pool *bnPool) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A := pool.Get().Mul(a.x, a.x) + A.Mod(A, P) + B := pool.Get().Mul(a.y, a.y) + B.Mod(B, P) + C_ := pool.Get().Mul(B, B) + C_.Mod(C_, P) + + t := pool.Get().Add(a.x, B) + t2 := pool.Get().Mul(t, t) + t2.Mod(t2, P) + t.Sub(t2, A) + t2.Sub(t, C_) + d := pool.Get().Add(t2, t2) + t.Add(A, A) + e := pool.Get().Add(t, A) + f := pool.Get().Mul(e, e) + f.Mod(f, P) + + t.Add(d, d) + c.x.Sub(f, t) + + t.Add(C_, C_) + t2.Add(t, t) + t.Add(t2, t2) + c.y.Sub(d, c.x) + t2.Mul(e, c.y) + t2.Mod(t2, P) + c.y.Sub(t2, t) + + t.Mul(a.y, a.z) + t.Mod(t, P) + c.z.Add(t, t) + + pool.Put(A) + pool.Put(B) + pool.Put(C_) + pool.Put(t) + pool.Put(t2) + pool.Put(d) + pool.Put(e) + pool.Put(f) +} + +func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int, pool *bnPool) *curvePoint { + sum := newCurvePoint(pool) + sum.SetInfinity() + t := newCurvePoint(pool) + + for i := scalar.BitLen(); i >= 0; i-- { + t.Double(sum, pool) + if scalar.Bit(i) != 0 { + sum.Add(t, a, pool) + } else { + sum.Set(t) + } + } + + c.Set(sum) + sum.Put(pool) + t.Put(pool) + return c +} + +// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets +// c to 0 : 1 : 0. +func (c *curvePoint) MakeAffine(pool *bnPool) *curvePoint { + if words := c.z.Bits(); len(words) == 1 && words[0] == 1 { + return c + } + if c.IsInfinity() { + c.x.SetInt64(0) + c.y.SetInt64(1) + c.z.SetInt64(0) + c.t.SetInt64(0) + return c + } + zInv := pool.Get().ModInverse(c.z, P) + t := pool.Get().Mul(c.y, zInv) + t.Mod(t, P) + zInv2 := pool.Get().Mul(zInv, zInv) + zInv2.Mod(zInv2, P) + c.y.Mul(t, zInv2) + c.y.Mod(c.y, P) + t.Mul(c.x, zInv2) + t.Mod(t, P) + c.x.Set(t) + c.z.SetInt64(1) + c.t.SetInt64(1) + + pool.Put(zInv) + pool.Put(t) + pool.Put(zInv2) + + return c +} + +func (c *curvePoint) Negative(a *curvePoint) { + c.x.Set(a.x) + c.y.Neg(a.y) + c.z.Set(a.z) + c.t.SetInt64(0) +} diff --git a/crypto/bn256/google/example_test.go b/crypto/bn256/google/example_test.go new file mode 100644 index 0000000..b2d1980 --- /dev/null +++ b/crypto/bn256/google/example_test.go @@ -0,0 +1,43 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "crypto/rand" +) + +func ExamplePair() { + // This implements the tripartite Diffie-Hellman algorithm from "A One + // Round Protocol for Tripartite Diffie-Hellman", A. Joux. + // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf + + // Each of three parties, a, b and c, generate a private value. + a, _ := rand.Int(rand.Reader, Order) + b, _ := rand.Int(rand.Reader, Order) + c, _ := rand.Int(rand.Reader, Order) + + // Then each party calculates gâ‚ and gâ‚‚ times their private value. + pa := new(G1).ScalarBaseMult(a) + qa := new(G2).ScalarBaseMult(a) + + pb := new(G1).ScalarBaseMult(b) + qb := new(G2).ScalarBaseMult(b) + + pc := new(G1).ScalarBaseMult(c) + qc := new(G2).ScalarBaseMult(c) + + // Now each party exchanges its public values with the other two and + // all parties can calculate the shared key. + k1 := Pair(pb, qc) + k1.ScalarMult(k1, a) + + k2 := Pair(pc, qa) + k2.ScalarMult(k2, b) + + k3 := Pair(pa, qb) + k3.ScalarMult(k3, c) + + // k1, k2 and k3 will all be equal. +} diff --git a/crypto/bn256/google/gfp12.go b/crypto/bn256/google/gfp12.go new file mode 100644 index 0000000..f084edd --- /dev/null +++ b/crypto/bn256/google/gfp12.go @@ -0,0 +1,200 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP12 implements the field of size p¹² as a quadratic extension of gfP6 +// where ω²=Ï„. +type gfP12 struct { + x, y *gfP6 // value is xω + y +} + +func newGFp12(pool *bnPool) *gfP12 { + return &gfP12{newGFp6(pool), newGFp6(pool)} +} + +func (e *gfP12) String() string { + return "(" + e.x.String() + "," + e.y.String() + ")" +} + +func (e *gfP12) Put(pool *bnPool) { + e.x.Put(pool) + e.y.Put(pool) +} + +func (e *gfP12) Set(a *gfP12) *gfP12 { + e.x.Set(a.x) + e.y.Set(a.y) + return e +} + +func (e *gfP12) SetZero() *gfP12 { + e.x.SetZero() + e.y.SetZero() + return e +} + +func (e *gfP12) SetOne() *gfP12 { + e.x.SetZero() + e.y.SetOne() + return e +} + +func (e *gfP12) Minimal() { + e.x.Minimal() + e.y.Minimal() +} + +func (e *gfP12) IsZero() bool { + e.Minimal() + return e.x.IsZero() && e.y.IsZero() +} + +func (e *gfP12) IsOne() bool { + e.Minimal() + return e.x.IsZero() && e.y.IsOne() +} + +func (e *gfP12) Conjugate(a *gfP12) *gfP12 { + e.x.Negative(a.x) + e.y.Set(a.y) + return a +} + +func (e *gfP12) Negative(a *gfP12) *gfP12 { + e.x.Negative(a.x) + e.y.Negative(a.y) + return e +} + +// Frobenius computes (xω+y)^p = x^p ω·ξ^((p-1)/6) + y^p +func (e *gfP12) Frobenius(a *gfP12, pool *bnPool) *gfP12 { + e.x.Frobenius(a.x, pool) + e.y.Frobenius(a.y, pool) + e.x.MulScalar(e.x, xiToPMinus1Over6, pool) + return e +} + +// FrobeniusP2 computes (xω+y)^p² = x^p² ω·ξ^((p²-1)/6) + y^p² +func (e *gfP12) FrobeniusP2(a *gfP12, pool *bnPool) *gfP12 { + e.x.FrobeniusP2(a.x) + e.x.MulGFP(e.x, xiToPSquaredMinus1Over6) + e.y.FrobeniusP2(a.y) + return e +} + +func (e *gfP12) Add(a, b *gfP12) *gfP12 { + e.x.Add(a.x, b.x) + e.y.Add(a.y, b.y) + return e +} + +func (e *gfP12) Sub(a, b *gfP12) *gfP12 { + e.x.Sub(a.x, b.x) + e.y.Sub(a.y, b.y) + return e +} + +func (e *gfP12) Mul(a, b *gfP12, pool *bnPool) *gfP12 { + tx := newGFp6(pool) + tx.Mul(a.x, b.y, pool) + t := newGFp6(pool) + t.Mul(b.x, a.y, pool) + tx.Add(tx, t) + + ty := newGFp6(pool) + ty.Mul(a.y, b.y, pool) + t.Mul(a.x, b.x, pool) + t.MulTau(t, pool) + e.y.Add(ty, t) + e.x.Set(tx) + + tx.Put(pool) + ty.Put(pool) + t.Put(pool) + return e +} + +func (e *gfP12) MulScalar(a *gfP12, b *gfP6, pool *bnPool) *gfP12 { + e.x.Mul(e.x, b, pool) + e.y.Mul(e.y, b, pool) + return e +} + +func (c *gfP12) Exp(a *gfP12, power *big.Int, pool *bnPool) *gfP12 { + sum := newGFp12(pool) + sum.SetOne() + t := newGFp12(pool) + + for i := power.BitLen() - 1; i >= 0; i-- { + t.Square(sum, pool) + if power.Bit(i) != 0 { + sum.Mul(t, a, pool) + } else { + sum.Set(t) + } + } + + c.Set(sum) + + sum.Put(pool) + t.Put(pool) + + return c +} + +func (e *gfP12) Square(a *gfP12, pool *bnPool) *gfP12 { + // Complex squaring algorithm + v0 := newGFp6(pool) + v0.Mul(a.x, a.y, pool) + + t := newGFp6(pool) + t.MulTau(a.x, pool) + t.Add(a.y, t) + ty := newGFp6(pool) + ty.Add(a.x, a.y) + ty.Mul(ty, t, pool) + ty.Sub(ty, v0) + t.MulTau(v0, pool) + ty.Sub(ty, t) + + e.y.Set(ty) + e.x.Double(v0) + + v0.Put(pool) + t.Put(pool) + ty.Put(pool) + + return e +} + +func (e *gfP12) Invert(a *gfP12, pool *bnPool) *gfP12 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t1 := newGFp6(pool) + t2 := newGFp6(pool) + + t1.Square(a.x, pool) + t2.Square(a.y, pool) + t1.MulTau(t1, pool) + t1.Sub(t2, t1) + t2.Invert(t1, pool) + + e.x.Negative(a.x) + e.y.Set(a.y) + e.MulScalar(e, t2, pool) + + t1.Put(pool) + t2.Put(pool) + + return e +} diff --git a/crypto/bn256/google/gfp2.go b/crypto/bn256/google/gfp2.go new file mode 100644 index 0000000..3981f6c --- /dev/null +++ b/crypto/bn256/google/gfp2.go @@ -0,0 +1,227 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP2 implements a field of size p² as a quadratic extension of the base +// field where i²=-1. +type gfP2 struct { + x, y *big.Int // value is xi+y. +} + +func newGFp2(pool *bnPool) *gfP2 { + return &gfP2{pool.Get(), pool.Get()} +} + +func (e *gfP2) String() string { + x := new(big.Int).Mod(e.x, P) + y := new(big.Int).Mod(e.y, P) + return "(" + x.String() + "," + y.String() + ")" +} + +func (e *gfP2) Put(pool *bnPool) { + pool.Put(e.x) + pool.Put(e.y) +} + +func (e *gfP2) Set(a *gfP2) *gfP2 { + e.x.Set(a.x) + e.y.Set(a.y) + return e +} + +func (e *gfP2) SetZero() *gfP2 { + e.x.SetInt64(0) + e.y.SetInt64(0) + return e +} + +func (e *gfP2) SetOne() *gfP2 { + e.x.SetInt64(0) + e.y.SetInt64(1) + return e +} + +func (e *gfP2) Minimal() { + if e.x.Sign() < 0 || e.x.Cmp(P) >= 0 { + e.x.Mod(e.x, P) + } + if e.y.Sign() < 0 || e.y.Cmp(P) >= 0 { + e.y.Mod(e.y, P) + } +} + +func (e *gfP2) IsZero() bool { + return e.x.Sign() == 0 && e.y.Sign() == 0 +} + +func (e *gfP2) IsOne() bool { + if e.x.Sign() != 0 { + return false + } + words := e.y.Bits() + return len(words) == 1 && words[0] == 1 +} + +func (e *gfP2) Conjugate(a *gfP2) *gfP2 { + e.y.Set(a.y) + e.x.Neg(a.x) + return e +} + +func (e *gfP2) Negative(a *gfP2) *gfP2 { + e.x.Neg(a.x) + e.y.Neg(a.y) + return e +} + +func (e *gfP2) Add(a, b *gfP2) *gfP2 { + e.x.Add(a.x, b.x) + e.y.Add(a.y, b.y) + return e +} + +func (e *gfP2) Sub(a, b *gfP2) *gfP2 { + e.x.Sub(a.x, b.x) + e.y.Sub(a.y, b.y) + return e +} + +func (e *gfP2) Double(a *gfP2) *gfP2 { + e.x.Lsh(a.x, 1) + e.y.Lsh(a.y, 1) + return e +} + +func (c *gfP2) Exp(a *gfP2, power *big.Int, pool *bnPool) *gfP2 { + sum := newGFp2(pool) + sum.SetOne() + t := newGFp2(pool) + + for i := power.BitLen() - 1; i >= 0; i-- { + t.Square(sum, pool) + if power.Bit(i) != 0 { + sum.Mul(t, a, pool) + } else { + sum.Set(t) + } + } + + c.Set(sum) + + sum.Put(pool) + t.Put(pool) + + return c +} + +// See "Multiplication and Squaring in Pairing-Friendly Fields", +// http://eprint.iacr.org/2006/471.pdf +func (e *gfP2) Mul(a, b *gfP2, pool *bnPool) *gfP2 { + tx := pool.Get().Mul(a.x, b.y) + t := pool.Get().Mul(b.x, a.y) + tx.Add(tx, t) + tx.Mod(tx, P) + + ty := pool.Get().Mul(a.y, b.y) + t.Mul(a.x, b.x) + ty.Sub(ty, t) + e.y.Mod(ty, P) + e.x.Set(tx) + + pool.Put(tx) + pool.Put(ty) + pool.Put(t) + + return e +} + +func (e *gfP2) MulScalar(a *gfP2, b *big.Int) *gfP2 { + e.x.Mul(a.x, b) + e.y.Mul(a.y, b) + return e +} + +// MulXi sets e=ξa where ξ=i+9 and then returns e. +func (e *gfP2) MulXi(a *gfP2, pool *bnPool) *gfP2 { + // (xi+y)(i+3) = (9x+y)i+(9y-x) + tx := pool.Get().Lsh(a.x, 3) + tx.Add(tx, a.x) + tx.Add(tx, a.y) + + ty := pool.Get().Lsh(a.y, 3) + ty.Add(ty, a.y) + ty.Sub(ty, a.x) + + e.x.Set(tx) + e.y.Set(ty) + + pool.Put(tx) + pool.Put(ty) + + return e +} + +func (e *gfP2) Square(a *gfP2, pool *bnPool) *gfP2 { + // Complex squaring algorithm: + // (xi+b)² = (x+y)(y-x) + 2*i*x*y + t1 := pool.Get().Sub(a.y, a.x) + t2 := pool.Get().Add(a.x, a.y) + ty := pool.Get().Mul(t1, t2) + ty.Mod(ty, P) + + t1.Mul(a.x, a.y) + t1.Lsh(t1, 1) + + e.x.Mod(t1, P) + e.y.Set(ty) + + pool.Put(t1) + pool.Put(t2) + pool.Put(ty) + + return e +} + +func (e *gfP2) Invert(a *gfP2, pool *bnPool) *gfP2 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + t := pool.Get() + t.Mul(a.y, a.y) + t2 := pool.Get() + t2.Mul(a.x, a.x) + t.Add(t, t2) + + inv := pool.Get() + inv.ModInverse(t, P) + + e.x.Neg(a.x) + e.x.Mul(e.x, inv) + e.x.Mod(e.x, P) + + e.y.Mul(a.y, inv) + e.y.Mod(e.y, P) + + pool.Put(t) + pool.Put(t2) + pool.Put(inv) + + return e +} + +func (e *gfP2) Real() *big.Int { + return e.x +} + +func (e *gfP2) Imag() *big.Int { + return e.y +} diff --git a/crypto/bn256/google/gfp6.go b/crypto/bn256/google/gfp6.go new file mode 100644 index 0000000..2188566 --- /dev/null +++ b/crypto/bn256/google/gfp6.go @@ -0,0 +1,296 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +// For details of the algorithms used, see "Multiplication and Squaring on +// Pairing-Friendly Fields, Devegili et al. +// http://eprint.iacr.org/2006/471.pdf. + +import ( + "math/big" +) + +// gfP6 implements the field of size pⶠas a cubic extension of gfP2 where τ³=ξ +// and ξ=i+9. +type gfP6 struct { + x, y, z *gfP2 // value is xτ² + yÏ„ + z +} + +func newGFp6(pool *bnPool) *gfP6 { + return &gfP6{newGFp2(pool), newGFp2(pool), newGFp2(pool)} +} + +func (e *gfP6) String() string { + return "(" + e.x.String() + "," + e.y.String() + "," + e.z.String() + ")" +} + +func (e *gfP6) Put(pool *bnPool) { + e.x.Put(pool) + e.y.Put(pool) + e.z.Put(pool) +} + +func (e *gfP6) Set(a *gfP6) *gfP6 { + e.x.Set(a.x) + e.y.Set(a.y) + e.z.Set(a.z) + return e +} + +func (e *gfP6) SetZero() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetZero() + return e +} + +func (e *gfP6) SetOne() *gfP6 { + e.x.SetZero() + e.y.SetZero() + e.z.SetOne() + return e +} + +func (e *gfP6) Minimal() { + e.x.Minimal() + e.y.Minimal() + e.z.Minimal() +} + +func (e *gfP6) IsZero() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsZero() +} + +func (e *gfP6) IsOne() bool { + return e.x.IsZero() && e.y.IsZero() && e.z.IsOne() +} + +func (e *gfP6) Negative(a *gfP6) *gfP6 { + e.x.Negative(a.x) + e.y.Negative(a.y) + e.z.Negative(a.z) + return e +} + +func (e *gfP6) Frobenius(a *gfP6, pool *bnPool) *gfP6 { + e.x.Conjugate(a.x) + e.y.Conjugate(a.y) + e.z.Conjugate(a.z) + + e.x.Mul(e.x, xiTo2PMinus2Over3, pool) + e.y.Mul(e.y, xiToPMinus1Over3, pool) + return e +} + +// FrobeniusP2 computes (xτ²+yÏ„+z)^(p²) = xÏ„^(2p²) + yÏ„^(p²) + z +func (e *gfP6) FrobeniusP2(a *gfP6) *gfP6 { + // Ï„^(2p²) = τ²τ^(2p²-2) = τ²ξ^((2p²-2)/3) + e.x.MulScalar(a.x, xiTo2PSquaredMinus2Over3) + // Ï„^(p²) = Ï„Ï„^(p²-1) = τξ^((p²-1)/3) + e.y.MulScalar(a.y, xiToPSquaredMinus1Over3) + e.z.Set(a.z) + return e +} + +func (e *gfP6) Add(a, b *gfP6) *gfP6 { + e.x.Add(a.x, b.x) + e.y.Add(a.y, b.y) + e.z.Add(a.z, b.z) + return e +} + +func (e *gfP6) Sub(a, b *gfP6) *gfP6 { + e.x.Sub(a.x, b.x) + e.y.Sub(a.y, b.y) + e.z.Sub(a.z, b.z) + return e +} + +func (e *gfP6) Double(a *gfP6) *gfP6 { + e.x.Double(a.x) + e.y.Double(a.y) + e.z.Double(a.z) + return e +} + +func (e *gfP6) Mul(a, b *gfP6, pool *bnPool) *gfP6 { + // "Multiplication and Squaring on Pairing-Friendly Fields" + // Section 4, Karatsuba method. + // http://eprint.iacr.org/2006/471.pdf + + v0 := newGFp2(pool) + v0.Mul(a.z, b.z, pool) + v1 := newGFp2(pool) + v1.Mul(a.y, b.y, pool) + v2 := newGFp2(pool) + v2.Mul(a.x, b.x, pool) + + t0 := newGFp2(pool) + t0.Add(a.x, a.y) + t1 := newGFp2(pool) + t1.Add(b.x, b.y) + tz := newGFp2(pool) + tz.Mul(t0, t1, pool) + + tz.Sub(tz, v1) + tz.Sub(tz, v2) + tz.MulXi(tz, pool) + tz.Add(tz, v0) + + t0.Add(a.y, a.z) + t1.Add(b.y, b.z) + ty := newGFp2(pool) + ty.Mul(t0, t1, pool) + ty.Sub(ty, v0) + ty.Sub(ty, v1) + t0.MulXi(v2, pool) + ty.Add(ty, t0) + + t0.Add(a.x, a.z) + t1.Add(b.x, b.z) + tx := newGFp2(pool) + tx.Mul(t0, t1, pool) + tx.Sub(tx, v0) + tx.Add(tx, v1) + tx.Sub(tx, v2) + + e.x.Set(tx) + e.y.Set(ty) + e.z.Set(tz) + + t0.Put(pool) + t1.Put(pool) + tx.Put(pool) + ty.Put(pool) + tz.Put(pool) + v0.Put(pool) + v1.Put(pool) + v2.Put(pool) + return e +} + +func (e *gfP6) MulScalar(a *gfP6, b *gfP2, pool *bnPool) *gfP6 { + e.x.Mul(a.x, b, pool) + e.y.Mul(a.y, b, pool) + e.z.Mul(a.z, b, pool) + return e +} + +func (e *gfP6) MulGFP(a *gfP6, b *big.Int) *gfP6 { + e.x.MulScalar(a.x, b) + e.y.MulScalar(a.y, b) + e.z.MulScalar(a.z, b) + return e +} + +// MulTau computes τ·(aτ²+bÏ„+c) = bτ²+cÏ„+aξ +func (e *gfP6) MulTau(a *gfP6, pool *bnPool) { + tz := newGFp2(pool) + tz.MulXi(a.x, pool) + ty := newGFp2(pool) + ty.Set(a.y) + e.y.Set(a.z) + e.x.Set(ty) + e.z.Set(tz) + tz.Put(pool) + ty.Put(pool) +} + +func (e *gfP6) Square(a *gfP6, pool *bnPool) *gfP6 { + v0 := newGFp2(pool).Square(a.z, pool) + v1 := newGFp2(pool).Square(a.y, pool) + v2 := newGFp2(pool).Square(a.x, pool) + + c0 := newGFp2(pool).Add(a.x, a.y) + c0.Square(c0, pool) + c0.Sub(c0, v1) + c0.Sub(c0, v2) + c0.MulXi(c0, pool) + c0.Add(c0, v0) + + c1 := newGFp2(pool).Add(a.y, a.z) + c1.Square(c1, pool) + c1.Sub(c1, v0) + c1.Sub(c1, v1) + xiV2 := newGFp2(pool).MulXi(v2, pool) + c1.Add(c1, xiV2) + + c2 := newGFp2(pool).Add(a.x, a.z) + c2.Square(c2, pool) + c2.Sub(c2, v0) + c2.Add(c2, v1) + c2.Sub(c2, v2) + + e.x.Set(c2) + e.y.Set(c1) + e.z.Set(c0) + + v0.Put(pool) + v1.Put(pool) + v2.Put(pool) + c0.Put(pool) + c1.Put(pool) + c2.Put(pool) + xiV2.Put(pool) + + return e +} + +func (e *gfP6) Invert(a *gfP6, pool *bnPool) *gfP6 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + + // Here we can give a short explanation of how it works: let j be a cubic root of + // unity in GF(p²) so that 1+j+j²=0. + // Then (xτ² + yÏ„ + z)(xj²τ² + yjÏ„ + z)(xjτ² + yj²τ + z) + // = (xτ² + yÏ„ + z)(Cτ²+BÏ„+A) + // = (x³ξ²+y³ξ+z³-3ξxyz) = F is an element of the base field (the norm). + // + // On the other hand (xj²τ² + yjÏ„ + z)(xjτ² + yj²τ + z) + // = τ²(y²-ξxz) + Ï„(ξx²-yz) + (z²-ξxy) + // + // So that's why A = (z²-ξxy), B = (ξx²-yz), C = (y²-ξxz) + t1 := newGFp2(pool) + + A := newGFp2(pool) + A.Square(a.z, pool) + t1.Mul(a.x, a.y, pool) + t1.MulXi(t1, pool) + A.Sub(A, t1) + + B := newGFp2(pool) + B.Square(a.x, pool) + B.MulXi(B, pool) + t1.Mul(a.y, a.z, pool) + B.Sub(B, t1) + + C_ := newGFp2(pool) + C_.Square(a.y, pool) + t1.Mul(a.x, a.z, pool) + C_.Sub(C_, t1) + + F := newGFp2(pool) + F.Mul(C_, a.y, pool) + F.MulXi(F, pool) + t1.Mul(A, a.z, pool) + F.Add(F, t1) + t1.Mul(B, a.x, pool) + t1.MulXi(t1, pool) + F.Add(F, t1) + + F.Invert(F, pool) + + e.x.Mul(C_, F, pool) + e.y.Mul(B, F, pool) + e.z.Mul(A, F, pool) + + t1.Put(pool) + A.Put(pool) + B.Put(pool) + C_.Put(pool) + F.Put(pool) + + return e +} diff --git a/crypto/bn256/google/main_test.go b/crypto/bn256/google/main_test.go new file mode 100644 index 0000000..c0c8545 --- /dev/null +++ b/crypto/bn256/google/main_test.go @@ -0,0 +1,71 @@ +package bn256 + +import ( + "testing" + + "crypto/rand" +) + +func TestRandomG2Marshal(t *testing.T) { + for i := 0; i < 10; i++ { + n, g2, err := RandomG2(rand.Reader) + if err != nil { + t.Error(err) + continue + } + t.Logf("%v: %x\n", n, g2.Marshal()) + } +} + +func TestPairings(t *testing.T) { + a1 := new(G1).ScalarBaseMult(bigFromBase10("1")) + a2 := new(G1).ScalarBaseMult(bigFromBase10("2")) + a37 := new(G1).ScalarBaseMult(bigFromBase10("37")) + an1 := new(G1).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + b0 := new(G2).ScalarBaseMult(bigFromBase10("0")) + b1 := new(G2).ScalarBaseMult(bigFromBase10("1")) + b2 := new(G2).ScalarBaseMult(bigFromBase10("2")) + b27 := new(G2).ScalarBaseMult(bigFromBase10("27")) + b999 := new(G2).ScalarBaseMult(bigFromBase10("999")) + bn1 := new(G2).ScalarBaseMult(bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495616")) + + p1 := Pair(a1, b1) + pn1 := Pair(a1, bn1) + np1 := Pair(an1, b1) + if pn1.String() != np1.String() { + t.Error("Pairing mismatch: e(a, -b) != e(-a, b)") + } + if !PairingCheck([]*G1{a1, an1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false negative!") + } + p0 := new(GT).Add(p1, pn1) + p0_2 := Pair(a1, b0) + if p0.String() != p0_2.String() { + t.Error("Pairing mismatch: e(a, b) * e(a, -b) != 1") + } + p0_3 := new(GT).ScalarMult(p1, bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617")) + if p0.String() != p0_3.String() { + t.Error("Pairing mismatch: e(a, b) has wrong order") + } + p2 := Pair(a2, b1) + p2_2 := Pair(a1, b2) + p2_3 := new(GT).ScalarMult(p1, bigFromBase10("2")) + if p2.String() != p2_2.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a * 2, b)") + } + if p2.String() != p2_3.String() { + t.Error("Pairing mismatch: e(a, b * 2) != e(a, b) ** 2") + } + if p2.String() == p1.String() { + t.Error("Pairing is degenerate!") + } + if PairingCheck([]*G1{a1, a1}, []*G2{b1, b1}) { + t.Error("MultiAte check gave false positive!") + } + p999 := Pair(a37, b27) + p999_2 := Pair(a1, b999) + if p999.String() != p999_2.String() { + t.Error("Pairing mismatch: e(a * 37, b * 27) != e(a, b * 999)") + } +} diff --git a/crypto/bn256/google/optate.go b/crypto/bn256/google/optate.go new file mode 100644 index 0000000..9d69570 --- /dev/null +++ b/crypto/bn256/google/optate.go @@ -0,0 +1,397 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +func lineFunctionAdd(r, p *twistPoint, q *curvePoint, r2 *gfP2, pool *bnPool) (a, b, c *gfP2, rOut *twistPoint) { + // See the mixed addition algorithm from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + + B := newGFp2(pool).Mul(p.x, r.t, pool) + + D := newGFp2(pool).Add(p.y, r.z) + D.Square(D, pool) + D.Sub(D, r2) + D.Sub(D, r.t) + D.Mul(D, r.t, pool) + + H := newGFp2(pool).Sub(B, r.x) + I := newGFp2(pool).Square(H, pool) + + E := newGFp2(pool).Add(I, I) + E.Add(E, E) + + J := newGFp2(pool).Mul(H, E, pool) + + L1 := newGFp2(pool).Sub(D, r.y) + L1.Sub(L1, r.y) + + V := newGFp2(pool).Mul(r.x, E, pool) + + rOut = newTwistPoint(pool) + rOut.x.Square(L1, pool) + rOut.x.Sub(rOut.x, J) + rOut.x.Sub(rOut.x, V) + rOut.x.Sub(rOut.x, V) + + rOut.z.Add(r.z, H) + rOut.z.Square(rOut.z, pool) + rOut.z.Sub(rOut.z, r.t) + rOut.z.Sub(rOut.z, I) + + t := newGFp2(pool).Sub(V, rOut.x) + t.Mul(t, L1, pool) + t2 := newGFp2(pool).Mul(r.y, J, pool) + t2.Add(t2, t2) + rOut.y.Sub(t, t2) + + rOut.t.Square(rOut.z, pool) + + t.Add(p.y, rOut.z) + t.Square(t, pool) + t.Sub(t, r2) + t.Sub(t, rOut.t) + + t2.Mul(L1, p.x, pool) + t2.Add(t2, t2) + a = newGFp2(pool) + a.Sub(t2, t) + + c = newGFp2(pool) + c.MulScalar(rOut.z, q.y) + c.Add(c, c) + + b = newGFp2(pool) + b.SetZero() + b.Sub(b, L1) + b.MulScalar(b, q.x) + b.Add(b, b) + + B.Put(pool) + D.Put(pool) + H.Put(pool) + I.Put(pool) + E.Put(pool) + J.Put(pool) + L1.Put(pool) + V.Put(pool) + t.Put(pool) + t2.Put(pool) + + return +} + +func lineFunctionDouble(r *twistPoint, q *curvePoint, pool *bnPool) (a, b, c *gfP2, rOut *twistPoint) { + // See the doubling algorithm for a=0 from "Faster Computation of the + // Tate Pairing", http://arxiv.org/pdf/0904.0854v3.pdf + + A := newGFp2(pool).Square(r.x, pool) + B := newGFp2(pool).Square(r.y, pool) + C_ := newGFp2(pool).Square(B, pool) + + D := newGFp2(pool).Add(r.x, B) + D.Square(D, pool) + D.Sub(D, A) + D.Sub(D, C_) + D.Add(D, D) + + E := newGFp2(pool).Add(A, A) + E.Add(E, A) + + G := newGFp2(pool).Square(E, pool) + + rOut = newTwistPoint(pool) + rOut.x.Sub(G, D) + rOut.x.Sub(rOut.x, D) + + rOut.z.Add(r.y, r.z) + rOut.z.Square(rOut.z, pool) + rOut.z.Sub(rOut.z, B) + rOut.z.Sub(rOut.z, r.t) + + rOut.y.Sub(D, rOut.x) + rOut.y.Mul(rOut.y, E, pool) + t := newGFp2(pool).Add(C_, C_) + t.Add(t, t) + t.Add(t, t) + rOut.y.Sub(rOut.y, t) + + rOut.t.Square(rOut.z, pool) + + t.Mul(E, r.t, pool) + t.Add(t, t) + b = newGFp2(pool) + b.SetZero() + b.Sub(b, t) + b.MulScalar(b, q.x) + + a = newGFp2(pool) + a.Add(r.x, E) + a.Square(a, pool) + a.Sub(a, A) + a.Sub(a, G) + t.Add(B, B) + t.Add(t, t) + a.Sub(a, t) + + c = newGFp2(pool) + c.Mul(rOut.z, r.t, pool) + c.Add(c, c) + c.MulScalar(c, q.y) + + A.Put(pool) + B.Put(pool) + C_.Put(pool) + D.Put(pool) + E.Put(pool) + G.Put(pool) + t.Put(pool) + + return +} + +func mulLine(ret *gfP12, a, b, c *gfP2, pool *bnPool) { + a2 := newGFp6(pool) + a2.x.SetZero() + a2.y.Set(a) + a2.z.Set(b) + a2.Mul(a2, ret.x, pool) + t3 := newGFp6(pool).MulScalar(ret.y, c, pool) + + t := newGFp2(pool) + t.Add(b, c) + t2 := newGFp6(pool) + t2.x.SetZero() + t2.y.Set(a) + t2.z.Set(t) + ret.x.Add(ret.x, ret.y) + + ret.y.Set(t3) + + ret.x.Mul(ret.x, t2, pool) + ret.x.Sub(ret.x, a2) + ret.x.Sub(ret.x, ret.y) + a2.MulTau(a2, pool) + ret.y.Add(ret.y, a2) + + a2.Put(pool) + t3.Put(pool) + t2.Put(pool) + t.Put(pool) +} + +// sixuPlus2NAF is 6u+2 in non-adjacent form. +var sixuPlus2NAF = []int8{0, 0, 0, 1, 0, 1, 0, -1, 0, 0, 1, -1, 0, 0, 1, 0, + 0, 1, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, 1, 1, + 1, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, -1, 0, 0, 1, + 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, -1, 0, 0, 1, 0, 1, 1} + +// miller implements the Miller loop for calculating the Optimal Ate pairing. +// See algorithm 1 from http://cryptojedi.org/papers/dclxvi-20100714.pdf +func miller(q *twistPoint, p *curvePoint, pool *bnPool) *gfP12 { + ret := newGFp12(pool) + ret.SetOne() + + aAffine := newTwistPoint(pool) + aAffine.Set(q) + aAffine.MakeAffine(pool) + + bAffine := newCurvePoint(pool) + bAffine.Set(p) + bAffine.MakeAffine(pool) + + minusA := newTwistPoint(pool) + minusA.Negative(aAffine, pool) + + r := newTwistPoint(pool) + r.Set(aAffine) + + r2 := newGFp2(pool) + r2.Square(aAffine.y, pool) + + for i := len(sixuPlus2NAF) - 1; i > 0; i-- { + a, b, c, newR := lineFunctionDouble(r, bAffine, pool) + if i != len(sixuPlus2NAF)-1 { + ret.Square(ret, pool) + } + + mulLine(ret, a, b, c, pool) + a.Put(pool) + b.Put(pool) + c.Put(pool) + r.Put(pool) + r = newR + + switch sixuPlus2NAF[i-1] { + case 1: + a, b, c, newR = lineFunctionAdd(r, aAffine, bAffine, r2, pool) + case -1: + a, b, c, newR = lineFunctionAdd(r, minusA, bAffine, r2, pool) + default: + continue + } + + mulLine(ret, a, b, c, pool) + a.Put(pool) + b.Put(pool) + c.Put(pool) + r.Put(pool) + r = newR + } + + // In order to calculate Q1 we have to convert q from the sextic twist + // to the full GF(p^12) group, apply the Frobenius there, and convert + // back. + // + // The twist isomorphism is (x', y') -> (xω², yω³). If we consider just + // x for a moment, then after applying the Frobenius, we have x̄ω^(2p) + // where xÌ„ is the conjugate of x. If we are going to apply the inverse + // isomorphism we need a value with a single coefficient of ω² so we + // rewrite this as x̄ω^(2p-2)ω². ξⶠ= ω and, due to the construction of + // p, 2p-2 is a multiple of six. Therefore we can rewrite as + // x̄ξ^((p-1)/3)ω² and applying the inverse isomorphism eliminates the + // ω². + // + // A similar argument can be made for the y value. + + q1 := newTwistPoint(pool) + q1.x.Conjugate(aAffine.x) + q1.x.Mul(q1.x, xiToPMinus1Over3, pool) + q1.y.Conjugate(aAffine.y) + q1.y.Mul(q1.y, xiToPMinus1Over2, pool) + q1.z.SetOne() + q1.t.SetOne() + + // For Q2 we are applying the p² Frobenius. The two conjugations cancel + // out and we are left only with the factors from the isomorphism. In + // the case of x, we end up with a pure number which is why + // xiToPSquaredMinus1Over3 is ∈ GF(p). With y we get a factor of -1. We + // ignore this to end up with -Q2. + + minusQ2 := newTwistPoint(pool) + minusQ2.x.MulScalar(aAffine.x, xiToPSquaredMinus1Over3) + minusQ2.y.Set(aAffine.y) + minusQ2.z.SetOne() + minusQ2.t.SetOne() + + r2.Square(q1.y, pool) + a, b, c, newR := lineFunctionAdd(r, q1, bAffine, r2, pool) + mulLine(ret, a, b, c, pool) + a.Put(pool) + b.Put(pool) + c.Put(pool) + r.Put(pool) + r = newR + + r2.Square(minusQ2.y, pool) + a, b, c, newR = lineFunctionAdd(r, minusQ2, bAffine, r2, pool) + mulLine(ret, a, b, c, pool) + a.Put(pool) + b.Put(pool) + c.Put(pool) + r.Put(pool) + r = newR + + aAffine.Put(pool) + bAffine.Put(pool) + minusA.Put(pool) + r.Put(pool) + r2.Put(pool) + + return ret +} + +// finalExponentiation computes the (p¹²-1)/Order-th power of an element of +// GF(p¹²) to obtain an element of GT (steps 13-15 of algorithm 1 from +// http://cryptojedi.org/papers/dclxvi-20100714.pdf) +func finalExponentiation(in *gfP12, pool *bnPool) *gfP12 { + t1 := newGFp12(pool) + + // This is the p^6-Frobenius + t1.x.Negative(in.x) + t1.y.Set(in.y) + + inv := newGFp12(pool) + inv.Invert(in, pool) + t1.Mul(t1, inv, pool) + + t2 := newGFp12(pool).FrobeniusP2(t1, pool) + t1.Mul(t1, t2, pool) + + fp := newGFp12(pool).Frobenius(t1, pool) + fp2 := newGFp12(pool).FrobeniusP2(t1, pool) + fp3 := newGFp12(pool).Frobenius(fp2, pool) + + fu, fu2, fu3 := newGFp12(pool), newGFp12(pool), newGFp12(pool) + fu.Exp(t1, u, pool) + fu2.Exp(fu, u, pool) + fu3.Exp(fu2, u, pool) + + y3 := newGFp12(pool).Frobenius(fu, pool) + fu2p := newGFp12(pool).Frobenius(fu2, pool) + fu3p := newGFp12(pool).Frobenius(fu3, pool) + y2 := newGFp12(pool).FrobeniusP2(fu2, pool) + + y0 := newGFp12(pool) + y0.Mul(fp, fp2, pool) + y0.Mul(y0, fp3, pool) + + y1, y4, y5 := newGFp12(pool), newGFp12(pool), newGFp12(pool) + y1.Conjugate(t1) + y5.Conjugate(fu2) + y3.Conjugate(y3) + y4.Mul(fu, fu2p, pool) + y4.Conjugate(y4) + + y6 := newGFp12(pool) + y6.Mul(fu3, fu3p, pool) + y6.Conjugate(y6) + + t0 := newGFp12(pool) + t0.Square(y6, pool) + t0.Mul(t0, y4, pool) + t0.Mul(t0, y5, pool) + t1.Mul(y3, y5, pool) + t1.Mul(t1, t0, pool) + t0.Mul(t0, y2, pool) + t1.Square(t1, pool) + t1.Mul(t1, t0, pool) + t1.Square(t1, pool) + t0.Mul(t1, y1, pool) + t1.Mul(t1, y0, pool) + t0.Square(t0, pool) + t0.Mul(t0, t1, pool) + + inv.Put(pool) + t1.Put(pool) + t2.Put(pool) + fp.Put(pool) + fp2.Put(pool) + fp3.Put(pool) + fu.Put(pool) + fu2.Put(pool) + fu3.Put(pool) + fu2p.Put(pool) + fu3p.Put(pool) + y0.Put(pool) + y1.Put(pool) + y2.Put(pool) + y3.Put(pool) + y4.Put(pool) + y5.Put(pool) + y6.Put(pool) + + return t0 +} + +func optimalAte(a *twistPoint, b *curvePoint, pool *bnPool) *gfP12 { + e := miller(a, b, pool) + ret := finalExponentiation(e, pool) + e.Put(pool) + + if a.IsInfinity() || b.IsInfinity() { + ret.SetOne() + } + return ret +} diff --git a/crypto/bn256/google/twist.go b/crypto/bn256/google/twist.go new file mode 100644 index 0000000..43364ff --- /dev/null +++ b/crypto/bn256/google/twist.go @@ -0,0 +1,263 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bn256 + +import ( + "math/big" +) + +// twistPoint implements the elliptic curve y²=x³+3/ξ over GF(p²). Points are +// kept in Jacobian form and t=z² when valid. The group Gâ‚‚ is the set of +// n-torsion points of this curve over GF(p²) (where n = Order) +type twistPoint struct { + x, y, z, t *gfP2 +} + +var twistB = &gfP2{ + bigFromBase10("266929791119991161246907387137283842545076965332900288569378510910307636690"), + bigFromBase10("19485874751759354771024239261021720505790618469301721065564631296452457478373"), +} + +// twistGen is the generator of group Gâ‚‚. +var twistGen = &twistPoint{ + &gfP2{ + bigFromBase10("11559732032986387107991004021392285783925812861821192530917403151452391805634"), + bigFromBase10("10857046999023057135944570762232829481370756359578518086990519993285655852781"), + }, + &gfP2{ + bigFromBase10("4082367875863433681332203403145435568316851327593401208105741076214120093531"), + bigFromBase10("8495653923123431417604973247489272438418190587263600148770280649306958101930"), + }, + &gfP2{ + bigFromBase10("0"), + bigFromBase10("1"), + }, + &gfP2{ + bigFromBase10("0"), + bigFromBase10("1"), + }, +} + +func newTwistPoint(pool *bnPool) *twistPoint { + return &twistPoint{ + newGFp2(pool), + newGFp2(pool), + newGFp2(pool), + newGFp2(pool), + } +} + +func (c *twistPoint) String() string { + return "(" + c.x.String() + ", " + c.y.String() + ", " + c.z.String() + ")" +} + +func (c *twistPoint) Put(pool *bnPool) { + c.x.Put(pool) + c.y.Put(pool) + c.z.Put(pool) + c.t.Put(pool) +} + +func (c *twistPoint) Set(a *twistPoint) { + c.x.Set(a.x) + c.y.Set(a.y) + c.z.Set(a.z) + c.t.Set(a.t) +} + +// IsOnCurve returns true iff c is on the curve where c must be in affine form. +func (c *twistPoint) IsOnCurve() bool { + pool := new(bnPool) + yy := newGFp2(pool).Square(c.y, pool) + xxx := newGFp2(pool).Square(c.x, pool) + xxx.Mul(xxx, c.x, pool) + yy.Sub(yy, xxx) + yy.Sub(yy, twistB) + yy.Minimal() + + if yy.x.Sign() != 0 || yy.y.Sign() != 0 { + return false + } + cneg := newTwistPoint(pool) + cneg.Mul(c, Order, pool) + return cneg.z.IsZero() +} + +func (c *twistPoint) SetInfinity() { + c.z.SetZero() +} + +func (c *twistPoint) IsInfinity() bool { + return c.z.IsZero() +} + +func (c *twistPoint) Add(a, b *twistPoint, pool *bnPool) { + // For additional comments, see the same function in curve.go. + + if a.IsInfinity() { + c.Set(b) + return + } + if b.IsInfinity() { + c.Set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + z1z1 := newGFp2(pool).Square(a.z, pool) + z2z2 := newGFp2(pool).Square(b.z, pool) + u1 := newGFp2(pool).Mul(a.x, z2z2, pool) + u2 := newGFp2(pool).Mul(b.x, z1z1, pool) + + t := newGFp2(pool).Mul(b.z, z2z2, pool) + s1 := newGFp2(pool).Mul(a.y, t, pool) + + t.Mul(a.z, z1z1, pool) + s2 := newGFp2(pool).Mul(b.y, t, pool) + + h := newGFp2(pool).Sub(u2, u1) + xEqual := h.IsZero() + + t.Add(h, h) + i := newGFp2(pool).Square(t, pool) + j := newGFp2(pool).Mul(h, i, pool) + + t.Sub(s2, s1) + yEqual := t.IsZero() + if xEqual && yEqual { + c.Double(a, pool) + return + } + r := newGFp2(pool).Add(t, t) + + v := newGFp2(pool).Mul(u1, i, pool) + + t4 := newGFp2(pool).Square(r, pool) + t.Add(v, v) + t6 := newGFp2(pool).Sub(t4, j) + c.x.Sub(t6, t) + + t.Sub(v, c.x) // t7 + t4.Mul(s1, j, pool) // t8 + t6.Add(t4, t4) // t9 + t4.Mul(r, t, pool) // t10 + c.y.Sub(t4, t6) + + t.Add(a.z, b.z) // t11 + t4.Square(t, pool) // t12 + t.Sub(t4, z1z1) // t13 + t4.Sub(t, z2z2) // t14 + c.z.Mul(t4, h, pool) + + z1z1.Put(pool) + z2z2.Put(pool) + u1.Put(pool) + u2.Put(pool) + t.Put(pool) + s1.Put(pool) + s2.Put(pool) + h.Put(pool) + i.Put(pool) + j.Put(pool) + r.Put(pool) + v.Put(pool) + t4.Put(pool) + t6.Put(pool) +} + +func (c *twistPoint) Double(a *twistPoint, pool *bnPool) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + A := newGFp2(pool).Square(a.x, pool) + B := newGFp2(pool).Square(a.y, pool) + C_ := newGFp2(pool).Square(B, pool) + + t := newGFp2(pool).Add(a.x, B) + t2 := newGFp2(pool).Square(t, pool) + t.Sub(t2, A) + t2.Sub(t, C_) + d := newGFp2(pool).Add(t2, t2) + t.Add(A, A) + e := newGFp2(pool).Add(t, A) + f := newGFp2(pool).Square(e, pool) + + t.Add(d, d) + c.x.Sub(f, t) + + t.Add(C_, C_) + t2.Add(t, t) + t.Add(t2, t2) + c.y.Sub(d, c.x) + t2.Mul(e, c.y, pool) + c.y.Sub(t2, t) + + t.Mul(a.y, a.z, pool) + c.z.Add(t, t) + + A.Put(pool) + B.Put(pool) + C_.Put(pool) + t.Put(pool) + t2.Put(pool) + d.Put(pool) + e.Put(pool) + f.Put(pool) +} + +func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int, pool *bnPool) *twistPoint { + sum := newTwistPoint(pool) + sum.SetInfinity() + t := newTwistPoint(pool) + + for i := scalar.BitLen(); i >= 0; i-- { + t.Double(sum, pool) + if scalar.Bit(i) != 0 { + sum.Add(t, a, pool) + } else { + sum.Set(t) + } + } + + c.Set(sum) + sum.Put(pool) + t.Put(pool) + return c +} + +// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets +// c to 0 : 1 : 0. +func (c *twistPoint) MakeAffine(pool *bnPool) *twistPoint { + if c.z.IsOne() { + return c + } + if c.IsInfinity() { + c.x.SetZero() + c.y.SetOne() + c.z.SetZero() + c.t.SetZero() + return c + } + zInv := newGFp2(pool).Invert(c.z, pool) + t := newGFp2(pool).Mul(c.y, zInv, pool) + zInv2 := newGFp2(pool).Square(zInv, pool) + c.y.Mul(t, zInv2, pool) + t.Mul(c.x, zInv2, pool) + c.x.Set(t) + c.z.SetOne() + c.t.SetOne() + + zInv.Put(pool) + t.Put(pool) + zInv2.Put(pool) + + return c +} + +func (c *twistPoint) Negative(a *twistPoint, pool *bnPool) { + c.x.Set(a.x) + c.y.SetZero() + c.y.Sub(c.y, a.y) + c.z.Set(a.z) + c.t.SetZero() +} diff --git a/crypto/crypto.go b/crypto/crypto.go new file mode 100644 index 0000000..aaa5cc4 --- /dev/null +++ b/crypto/crypto.go @@ -0,0 +1,291 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package crypto + +import ( + "bufio" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "hash" + "io" + "math/big" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +// SignatureLength indicates the byte length required to carry a signature with recovery id. +const SignatureLength = 64 + 1 // 64 bytes ECDSA signature + 1 byte recovery id + +// RecoveryIDOffset points to the byte offset within the signature that contains the recovery id. +const RecoveryIDOffset = 64 + +// DigestLength sets the signature digest exact length +const DigestLength = 32 + +var ( + secp256k1N = S256().Params().N + secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) +) + +var errInvalidPubkey = errors.New("invalid secp256k1 public key") + +// EllipticCurve contains curve operations. +type EllipticCurve interface { + elliptic.Curve + + // Point marshaling/unmarshaing. + Marshal(x, y *big.Int) []byte + Unmarshal(data []byte) (x, y *big.Int) +} + +// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports +// Read to get a variable amount of data from the hash state. Read is faster than Sum +// because it doesn't copy the internal state, but also modifies the internal state. +type KeccakState interface { + hash.Hash + Read([]byte) (int, error) +} + +// NewKeccakState creates a new KeccakState +func NewKeccakState() KeccakState { + return sha3.NewLegacyKeccak256().(KeccakState) +} + +// HashData hashes the provided data using the KeccakState and returns a 32 byte hash +func HashData(kh KeccakState, data []byte) (h common.Hash) { + kh.Reset() + kh.Write(data) + kh.Read(h[:]) + return h +} + +// Keccak256 calculates and returns the Keccak256 hash of the input data. +func Keccak256(data ...[]byte) []byte { + b := make([]byte, 32) + d := NewKeccakState() + for _, b := range data { + d.Write(b) + } + d.Read(b) + return b +} + +// Keccak256Hash calculates and returns the Keccak256 hash of the input data, +// converting it to an internal Hash data structure. +func Keccak256Hash(data ...[]byte) (h common.Hash) { + d := NewKeccakState() + for _, b := range data { + d.Write(b) + } + d.Read(h[:]) + return h +} + +// Keccak512 calculates and returns the Keccak512 hash of the input data. +func Keccak512(data ...[]byte) []byte { + d := sha3.NewLegacyKeccak512() + for _, b := range data { + d.Write(b) + } + return d.Sum(nil) +} + +// CreateAddress creates an ethereum address given the bytes and the nonce +func CreateAddress(b common.Address, nonce uint64) common.Address { + data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) + return common.BytesToAddress(Keccak256(data)[12:]) +} + +// CreateAddress2 creates an ethereum address given the address bytes, initial +// contract code hash and a salt. +func CreateAddress2(b common.Address, salt [32]byte, inithash []byte) common.Address { + return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:]) +} + +// ToECDSA creates a private key with the given D value. +func ToECDSA(d []byte) (*ecdsa.PrivateKey, error) { + return toECDSA(d, true) +} + +// ToECDSAUnsafe blindly converts a binary blob to a private key. It should almost +// never be used unless you are sure the input is valid and want to avoid hitting +// errors due to bad origin encoding (0 prefixes cut off). +func ToECDSAUnsafe(d []byte) *ecdsa.PrivateKey { + priv, _ := toECDSA(d, false) + return priv +} + +// toECDSA creates a private key with the given D value. The strict parameter +// controls whether the key's length should be enforced at the curve size or +// it can also accept legacy encodings (0 prefixes). +func toECDSA(d []byte, strict bool) (*ecdsa.PrivateKey, error) { + priv := new(ecdsa.PrivateKey) + priv.PublicKey.Curve = S256() + if strict && 8*len(d) != priv.Params().BitSize { + return nil, fmt.Errorf("invalid length, need %d bits", priv.Params().BitSize) + } + priv.D = new(big.Int).SetBytes(d) + + // The priv.D must < N + if priv.D.Cmp(secp256k1N) >= 0 { + return nil, errors.New("invalid private key, >=N") + } + // The priv.D must not be zero or negative. + if priv.D.Sign() <= 0 { + return nil, errors.New("invalid private key, zero or negative") + } + + priv.PublicKey.X, priv.PublicKey.Y = S256().ScalarBaseMult(d) + if priv.PublicKey.X == nil { + return nil, errors.New("invalid private key") + } + return priv, nil +} + +// FromECDSA exports a private key into a binary dump. +func FromECDSA(priv *ecdsa.PrivateKey) []byte { + if priv == nil { + return nil + } + return math.PaddedBigBytes(priv.D, priv.Params().BitSize/8) +} + +// UnmarshalPubkey converts bytes to a secp256k1 public key. +func UnmarshalPubkey(pub []byte) (*ecdsa.PublicKey, error) { + x, y := S256().Unmarshal(pub) + if x == nil { + return nil, errInvalidPubkey + } + return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil +} + +func FromECDSAPub(pub *ecdsa.PublicKey) []byte { + if pub == nil || pub.X == nil || pub.Y == nil { + return nil + } + return S256().Marshal(pub.X, pub.Y) +} + +// HexToECDSA parses a secp256k1 private key. +func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error) { + b, err := hex.DecodeString(hexkey) + if byteErr, ok := err.(hex.InvalidByteError); ok { + return nil, fmt.Errorf("invalid hex character %q in private key", byte(byteErr)) + } else if err != nil { + return nil, errors.New("invalid hex data for private key") + } + return ToECDSA(b) +} + +// LoadECDSA loads a secp256k1 private key from the given file. +func LoadECDSA(file string) (*ecdsa.PrivateKey, error) { + fd, err := os.Open(file) + if err != nil { + return nil, err + } + defer fd.Close() + + r := bufio.NewReader(fd) + buf := make([]byte, 64) + n, err := readASCII(buf, r) + if err != nil { + return nil, err + } else if n != len(buf) { + return nil, errors.New("key file too short, want 64 hex characters") + } + if err := checkKeyFileEnd(r); err != nil { + return nil, err + } + + return HexToECDSA(string(buf)) +} + +// readASCII reads into 'buf', stopping when the buffer is full or +// when a non-printable control character is encountered. +func readASCII(buf []byte, r *bufio.Reader) (n int, err error) { + for ; n < len(buf); n++ { + buf[n], err = r.ReadByte() + switch { + case err == io.EOF || buf[n] < '!': + return n, nil + case err != nil: + return n, err + } + } + return n, nil +} + +// checkKeyFileEnd skips over additional newlines at the end of a key file. +func checkKeyFileEnd(r *bufio.Reader) error { + for i := 0; ; i++ { + b, err := r.ReadByte() + switch { + case err == io.EOF: + return nil + case err != nil: + return err + case b != '\n' && b != '\r': + return fmt.Errorf("invalid character %q at end of key file", b) + case i >= 2: + return errors.New("key file too long, want 64 hex characters") + } + } +} + +// SaveECDSA saves a secp256k1 private key to the given file with +// restrictive permissions. The key data is saved hex-encoded. +func SaveECDSA(file string, key *ecdsa.PrivateKey) error { + k := hex.EncodeToString(FromECDSA(key)) + return os.WriteFile(file, []byte(k), 0600) +} + +// GenerateKey generates a new private key. +func GenerateKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(S256(), rand.Reader) +} + +// ValidateSignatureValues verifies whether the signature values are valid with +// the given chain rules. The v value is assumed to be either 0 or 1. +func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool { + if r.Cmp(common.Big1) < 0 || s.Cmp(common.Big1) < 0 { + return false + } + // reject upper range of s values (ECDSA malleability) + // see discussion in secp256k1/libsecp256k1/include/secp256k1.h + if homestead && s.Cmp(secp256k1halfN) > 0 { + return false + } + // Frontier: allow s to be in full N range + return r.Cmp(secp256k1N) < 0 && s.Cmp(secp256k1N) < 0 && (v == 0 || v == 1) +} + +func PubkeyToAddress(p ecdsa.PublicKey) common.Address { + pubBytes := FromECDSAPub(&p) + return common.BytesToAddress(Keccak256(pubBytes[1:])[12:]) +} + +func zeroBytes(bytes []byte) { + clear(bytes) +} diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go new file mode 100644 index 0000000..da123cf --- /dev/null +++ b/crypto/crypto_test.go @@ -0,0 +1,299 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package crypto + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "math/big" + "os" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var testAddrHex = "970e8128ab834e8eac17ab8e3812f010678cf791" +var testPrivHex = "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032" + +// These tests are sanity checks. +// They should ensure that we don't e.g. use Sha3-224 instead of Sha3-256 +// and that the sha3 library uses keccak-f permutation. +func TestKeccak256Hash(t *testing.T) { + msg := []byte("abc") + exp, _ := hex.DecodeString("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") + checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := Keccak256Hash(in); return h[:] }, msg, exp) +} + +func TestKeccak256Hasher(t *testing.T) { + msg := []byte("abc") + exp, _ := hex.DecodeString("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") + hasher := NewKeccakState() + checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := HashData(hasher, in); return h[:] }, msg, exp) +} + +func TestToECDSAErrors(t *testing.T) { + if _, err := HexToECDSA("0000000000000000000000000000000000000000000000000000000000000000"); err == nil { + t.Fatal("HexToECDSA should've returned error") + } + if _, err := HexToECDSA("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); err == nil { + t.Fatal("HexToECDSA should've returned error") + } +} + +func BenchmarkSha3(b *testing.B) { + a := []byte("hello world") + for i := 0; i < b.N; i++ { + Keccak256(a) + } +} + +func TestUnmarshalPubkey(t *testing.T) { + key, err := UnmarshalPubkey(nil) + if err != errInvalidPubkey || key != nil { + t.Fatalf("expected error, got %v, %v", err, key) + } + key, err = UnmarshalPubkey([]byte{1, 2, 3}) + if err != errInvalidPubkey || key != nil { + t.Fatalf("expected error, got %v, %v", err, key) + } + + var ( + enc, _ = hex.DecodeString("04760c4460e5336ac9bbd87952a3c7ec4363fc0a97bd31c86430806e287b437fd1b01abc6e1db640cf3106b520344af1d58b00b57823db3e1407cbc433e1b6d04d") + dec = &ecdsa.PublicKey{ + Curve: S256(), + X: hexutil.MustDecodeBig("0x760c4460e5336ac9bbd87952a3c7ec4363fc0a97bd31c86430806e287b437fd1"), + Y: hexutil.MustDecodeBig("0xb01abc6e1db640cf3106b520344af1d58b00b57823db3e1407cbc433e1b6d04d"), + } + ) + key, err = UnmarshalPubkey(enc) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if !reflect.DeepEqual(key, dec) { + t.Fatal("wrong result") + } +} + +func TestSign(t *testing.T) { + key, _ := HexToECDSA(testPrivHex) + addr := common.HexToAddress(testAddrHex) + + msg := Keccak256([]byte("foo")) + sig, err := Sign(msg, key) + if err != nil { + t.Errorf("Sign error: %s", err) + } + recoveredPub, err := Ecrecover(msg, sig) + if err != nil { + t.Errorf("ECRecover error: %s", err) + } + pubKey, _ := UnmarshalPubkey(recoveredPub) + recoveredAddr := PubkeyToAddress(*pubKey) + if addr != recoveredAddr { + t.Errorf("Address mismatch: want: %x have: %x", addr, recoveredAddr) + } + + // should be equal to SigToPub + recoveredPub2, err := SigToPub(msg, sig) + if err != nil { + t.Errorf("ECRecover error: %s", err) + } + recoveredAddr2 := PubkeyToAddress(*recoveredPub2) + if addr != recoveredAddr2 { + t.Errorf("Address mismatch: want: %x have: %x", addr, recoveredAddr2) + } +} + +func TestInvalidSign(t *testing.T) { + if _, err := Sign(make([]byte, 1), nil); err == nil { + t.Errorf("expected sign with hash 1 byte to error") + } + if _, err := Sign(make([]byte, 33), nil); err == nil { + t.Errorf("expected sign with hash 33 byte to error") + } +} + +func TestNewContractAddress(t *testing.T) { + key, _ := HexToECDSA(testPrivHex) + addr := common.HexToAddress(testAddrHex) + genAddr := PubkeyToAddress(key.PublicKey) + // sanity check before using addr to create contract address + checkAddr(t, genAddr, addr) + + caddr0 := CreateAddress(addr, 0) + caddr1 := CreateAddress(addr, 1) + caddr2 := CreateAddress(addr, 2) + checkAddr(t, common.HexToAddress("333c3310824b7c685133f2bedb2ca4b8b4df633d"), caddr0) + checkAddr(t, common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), caddr1) + checkAddr(t, common.HexToAddress("c9ddedf451bc62ce88bf9292afb13df35b670699"), caddr2) +} + +func TestLoadECDSA(t *testing.T) { + tests := []struct { + input string + err string + }{ + // good + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\r"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\r\n"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\n"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\r"}, + // bad + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde", + err: "key file too short, want 64 hex characters", + }, + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\n", + err: "key file too short, want 64 hex characters", + }, + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeX", + err: "invalid hex character 'X' in private key", + }, + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefX", + err: "invalid character 'X' at end of key file", + }, + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\n\n", + err: "key file too long, want 64 hex characters", + }, + } + + for _, test := range tests { + f, err := os.CreateTemp("", "loadecdsa_test.*.txt") + if err != nil { + t.Fatal(err) + } + filename := f.Name() + f.WriteString(test.input) + f.Close() + + _, err = LoadECDSA(filename) + switch { + case err != nil && test.err == "": + t.Fatalf("unexpected error for input %q:\n %v", test.input, err) + case err != nil && err.Error() != test.err: + t.Fatalf("wrong error for input %q:\n %v", test.input, err) + case err == nil && test.err != "": + t.Fatalf("LoadECDSA did not return error for input %q", test.input) + } + } +} + +func TestSaveECDSA(t *testing.T) { + f, err := os.CreateTemp("", "saveecdsa_test.*.txt") + if err != nil { + t.Fatal(err) + } + file := f.Name() + f.Close() + defer os.Remove(file) + + key, _ := HexToECDSA(testPrivHex) + if err := SaveECDSA(file, key); err != nil { + t.Fatal(err) + } + loaded, err := LoadECDSA(file) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(key, loaded) { + t.Fatal("loaded key not equal to saved key") + } +} + +func TestValidateSignatureValues(t *testing.T) { + check := func(expected bool, v byte, r, s *big.Int) { + if ValidateSignatureValues(v, r, s, false) != expected { + t.Errorf("mismatch for v: %d r: %d s: %d want: %v", v, r, s, expected) + } + } + minusOne := big.NewInt(-1) + one := common.Big1 + zero := common.Big0 + secp256k1nMinus1 := new(big.Int).Sub(secp256k1N, common.Big1) + + // correct v,r,s + check(true, 0, one, one) + check(true, 1, one, one) + // incorrect v, correct r,s, + check(false, 2, one, one) + check(false, 3, one, one) + + // incorrect v, combinations of incorrect/correct r,s at lower limit + check(false, 2, zero, zero) + check(false, 2, zero, one) + check(false, 2, one, zero) + check(false, 2, one, one) + + // correct v for any combination of incorrect r,s + check(false, 0, zero, zero) + check(false, 0, zero, one) + check(false, 0, one, zero) + + check(false, 1, zero, zero) + check(false, 1, zero, one) + check(false, 1, one, zero) + + // correct sig with max r,s + check(true, 0, secp256k1nMinus1, secp256k1nMinus1) + // correct v, combinations of incorrect r,s at upper limit + check(false, 0, secp256k1N, secp256k1nMinus1) + check(false, 0, secp256k1nMinus1, secp256k1N) + check(false, 0, secp256k1N, secp256k1N) + + // current callers ensures r,s cannot be negative, but let's test for that too + // as crypto package could be used stand-alone + check(false, 0, minusOne, one) + check(false, 0, one, minusOne) +} + +func checkhash(t *testing.T, name string, f func([]byte) []byte, msg, exp []byte) { + sum := f(msg) + if !bytes.Equal(exp, sum) { + t.Fatalf("hash %s mismatch: want: %x have: %x", name, exp, sum) + } +} + +func checkAddr(t *testing.T, addr0, addr1 common.Address) { + if addr0 != addr1 { + t.Fatalf("address mismatch: want: %x have: %x", addr0, addr1) + } +} + +// test to help Python team with integration of libsecp256k1 +// skip but keep it after they are done +func TestPythonIntegration(t *testing.T) { + kh := "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032" + k0, _ := HexToECDSA(kh) + + msg0 := Keccak256([]byte("foo")) + sig0, _ := Sign(msg0, k0) + + msg1 := common.FromHex("00000000000000000000000000000000") + sig1, _ := Sign(msg0, k0) + + t.Logf("msg: %x, privkey: %s sig: %x\n", msg0, kh, sig0) + t.Logf("msg: %x, privkey: %s sig: %x\n", msg1, kh, sig1) +} diff --git a/crypto/ecies/.gitignore b/crypto/ecies/.gitignore new file mode 100644 index 0000000..802b674 --- /dev/null +++ b/crypto/ecies/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe + +*~ diff --git a/crypto/ecies/LICENSE b/crypto/ecies/LICENSE new file mode 100644 index 0000000..e1ed19a --- /dev/null +++ b/crypto/ecies/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2013 Kyle Isom +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/crypto/ecies/README b/crypto/ecies/README new file mode 100644 index 0000000..2650c7b --- /dev/null +++ b/crypto/ecies/README @@ -0,0 +1,94 @@ +# NOTE + +This implementation is direct fork of Kylom's implementation. I claim no authorship over this code apart from some minor modifications. +Please be aware this code **has not yet been reviewed**. + +ecies implements the Elliptic Curve Integrated Encryption Scheme. + +The package is designed to be compliant with the appropriate NIST +standards, and therefore doesn't support the full SEC 1 algorithm set. + + +STATUS: + +ecies should be ready for use. The ASN.1 support is only complete so +far as to supported the listed algorithms before. + + +CAVEATS + +1. CMAC support is currently not present. + + +SUPPORTED ALGORITHMS + + SYMMETRIC CIPHERS HASH FUNCTIONS + AES128 SHA-1 + AES192 SHA-224 + AES256 SHA-256 + SHA-384 + ELLIPTIC CURVE SHA-512 + P256 + P384 KEY DERIVATION FUNCTION + P521 NIST SP 800-65a Concatenation KDF + +Curve P224 isn't supported because it does not provide a minimum security +level of AES128 with HMAC-SHA1. According to NIST SP 800-57, the security +level of P224 is 112 bits of security. Symmetric ciphers use CTR-mode; +message tags are computed using HMAC- function. + + +CURVE SELECTION + +According to NIST SP 800-57, the following curves should be selected: + + +----------------+-------+ + | SYMMETRIC SIZE | CURVE | + +----------------+-------+ + | 128-bit | P256 | + +----------------+-------+ + | 192-bit | P384 | + +----------------+-------+ + | 256-bit | P521 | + +----------------+-------+ + + +TODO + +1. Look at serialising the parameters with the SEC 1 ASN.1 module. +2. Validate ASN.1 formats with SEC 1. + + +TEST VECTORS + +The only test vectors I've found so far date from 1993, predating AES +and including only 163-bit curves. Therefore, there are no published +test vectors to compare to. + + +LICENSE + +ecies is released under the same license as the Go source code. See the +LICENSE file for details. + + +REFERENCES + +* SEC (Standard for Efficient Cryptography) 1, version 2.0: Elliptic + Curve Cryptography; Certicom, May 2009. + http://www.secg.org/sec1-v2.pdf +* GEC (Guidelines for Efficient Cryptography) 2, version 0.3: Test + Vectors for SEC 1; Certicom, September 1999. + http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf +* NIST SP 800-56a: Recommendation for Pair-Wise Key Establishment Schemes + Using Discrete Logarithm Cryptography. National Institute of Standards + and Technology, May 2007. + http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf +* Suite B Implementer’s Guide to NIST SP 800-56A. National Security + Agency, July 28, 2009. + http://www.nsa.gov/ia/_files/SuiteB_Implementer_G-113808.pdf +* NIST SP 800-57: Recommendation for Key Management – Part 1: General + (Revision 3). National Institute of Standards and Technology, July + 2012. + http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf + diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go new file mode 100644 index 0000000..1b6c9e9 --- /dev/null +++ b/crypto/ecies/ecies.go @@ -0,0 +1,325 @@ +// Copyright (c) 2013 Kyle Isom +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ecies + +import ( + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/hmac" + "crypto/subtle" + "encoding/binary" + "errors" + "hash" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/crypto" +) + +var ( + ErrImport = errors.New("ecies: failed to import key") + ErrInvalidCurve = errors.New("ecies: invalid elliptic curve") + ErrInvalidPublicKey = errors.New("ecies: invalid public key") + ErrSharedKeyIsPointAtInfinity = errors.New("ecies: shared key is point at infinity") + ErrSharedKeyTooBig = errors.New("ecies: shared key params are too big") +) + +// PublicKey is a representation of an elliptic curve public key. +type PublicKey struct { + X *big.Int + Y *big.Int + elliptic.Curve + Params *ECIESParams +} + +// Export an ECIES public key as an ECDSA public key. +func (pub *PublicKey) ExportECDSA() *ecdsa.PublicKey { + return &ecdsa.PublicKey{Curve: pub.Curve, X: pub.X, Y: pub.Y} +} + +// Import an ECDSA public key as an ECIES public key. +func ImportECDSAPublic(pub *ecdsa.PublicKey) *PublicKey { + return &PublicKey{ + X: pub.X, + Y: pub.Y, + Curve: pub.Curve, + Params: ParamsFromCurve(pub.Curve), + } +} + +// PrivateKey is a representation of an elliptic curve private key. +type PrivateKey struct { + PublicKey + D *big.Int +} + +// Export an ECIES private key as an ECDSA private key. +func (prv *PrivateKey) ExportECDSA() *ecdsa.PrivateKey { + pub := &prv.PublicKey + pubECDSA := pub.ExportECDSA() + return &ecdsa.PrivateKey{PublicKey: *pubECDSA, D: prv.D} +} + +// Import an ECDSA private key as an ECIES private key. +func ImportECDSA(prv *ecdsa.PrivateKey) *PrivateKey { + pub := ImportECDSAPublic(&prv.PublicKey) + return &PrivateKey{*pub, prv.D} +} + +// Generate an elliptic curve public / private keypair. If params is nil, +// the recommended default parameters for the key will be chosen. +func GenerateKey(rand io.Reader, curve elliptic.Curve, params *ECIESParams) (prv *PrivateKey, err error) { + sk, err := ecdsa.GenerateKey(curve, rand) + if err != nil { + return + } + prv = new(PrivateKey) + prv.PublicKey.X = sk.X + prv.PublicKey.Y = sk.Y + prv.PublicKey.Curve = curve + prv.D = new(big.Int).Set(sk.D) + if params == nil { + params = ParamsFromCurve(curve) + } + prv.PublicKey.Params = params + return +} + +// MaxSharedKeyLength returns the maximum length of the shared key the +// public key can produce. +func MaxSharedKeyLength(pub *PublicKey) int { + return (pub.Curve.Params().BitSize + 7) / 8 +} + +// ECDH key agreement method used to establish secret keys for encryption. +func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []byte, err error) { + if prv.PublicKey.Curve != pub.Curve { + return nil, ErrInvalidCurve + } + if skLen+macLen > MaxSharedKeyLength(pub) { + return nil, ErrSharedKeyTooBig + } + + x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) + if x == nil { + return nil, ErrSharedKeyIsPointAtInfinity + } + + sk = make([]byte, skLen+macLen) + skBytes := x.Bytes() + copy(sk[len(sk)-len(skBytes):], skBytes) + return sk, nil +} + +var ( + ErrSharedTooLong = errors.New("ecies: shared secret is too long") + ErrInvalidMessage = errors.New("ecies: invalid message") +) + +// NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). +func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { + counterBytes := make([]byte, 4) + k := make([]byte, 0, roundup(kdLen, hash.Size())) + for counter := uint32(1); len(k) < kdLen; counter++ { + binary.BigEndian.PutUint32(counterBytes, counter) + hash.Reset() + hash.Write(counterBytes) + hash.Write(z) + hash.Write(s1) + k = hash.Sum(k) + } + return k[:kdLen] +} + +// roundup rounds size up to the next multiple of blocksize. +func roundup(size, blocksize int) int { + return size + blocksize - (size % blocksize) +} + +// deriveKeys creates the encryption and MAC keys using concatKDF. +func deriveKeys(hash hash.Hash, z, s1 []byte, keyLen int) (Ke, Km []byte) { + K := concatKDF(hash, z, s1, 2*keyLen) + Ke = K[:keyLen] + Km = K[keyLen:] + hash.Reset() + hash.Write(Km) + Km = hash.Sum(Km[:0]) + return Ke, Km +} + +// messageTag computes the MAC of a message (called the tag) as per +// SEC 1, 3.5. +func messageTag(hash func() hash.Hash, km, msg, shared []byte) []byte { + mac := hmac.New(hash, km) + mac.Write(msg) + mac.Write(shared) + tag := mac.Sum(nil) + return tag +} + +// Generate an initialisation vector for CTR mode. +func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) { + iv = make([]byte, params.BlockSize) + _, err = io.ReadFull(rand, iv) + return +} + +// symEncrypt carries out CTR encryption using the block cipher specified in the +func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) { + c, err := params.Cipher(key) + if err != nil { + return + } + + iv, err := generateIV(params, rand) + if err != nil { + return + } + ctr := cipher.NewCTR(c, iv) + + ct = make([]byte, len(m)+params.BlockSize) + copy(ct, iv) + ctr.XORKeyStream(ct[params.BlockSize:], m) + return +} + +// symDecrypt carries out CTR decryption using the block cipher specified in +// the parameters +func symDecrypt(params *ECIESParams, key, ct []byte) (m []byte, err error) { + c, err := params.Cipher(key) + if err != nil { + return + } + + ctr := cipher.NewCTR(c, ct[:params.BlockSize]) + + m = make([]byte, len(ct)-params.BlockSize) + ctr.XORKeyStream(m, ct[params.BlockSize:]) + return +} + +// Encrypt encrypts a message using ECIES as specified in SEC 1, 5.1. +// +// s1 and s2 contain shared information that is not part of the resulting +// ciphertext. s1 is fed into key derivation, s2 is fed into the MAC. If the +// shared information parameters aren't being used, they should be nil. +func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err error) { + params, err := pubkeyParams(pub) + if err != nil { + return nil, err + } + + R, err := GenerateKey(rand, pub.Curve, params) + if err != nil { + return nil, err + } + + z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen) + if err != nil { + return nil, err + } + + hash := params.Hash() + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) + + em, err := symEncrypt(rand, params, Ke, m) + if err != nil || len(em) <= params.BlockSize { + return nil, err + } + + d := messageTag(params.Hash, Km, em, s2) + + if curve, ok := pub.Curve.(crypto.EllipticCurve); ok { + Rb := curve.Marshal(R.PublicKey.X, R.PublicKey.Y) + ct = make([]byte, len(Rb)+len(em)+len(d)) + copy(ct, Rb) + copy(ct[len(Rb):], em) + copy(ct[len(Rb)+len(em):], d) + return ct, nil + } + return nil, ErrInvalidCurve +} + +// Decrypt decrypts an ECIES ciphertext. +func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { + if len(c) == 0 { + return nil, ErrInvalidMessage + } + params, err := pubkeyParams(&prv.PublicKey) + if err != nil { + return nil, err + } + + hash := params.Hash() + + var ( + rLen int + hLen int = hash.Size() + mStart int + mEnd int + ) + + switch c[0] { + case 2, 3, 4: + rLen = (prv.PublicKey.Curve.Params().BitSize + 7) / 4 + if len(c) < (rLen + hLen + 1) { + return nil, ErrInvalidMessage + } + default: + return nil, ErrInvalidPublicKey + } + + mStart = rLen + mEnd = len(c) - hLen + + R := new(PublicKey) + R.Curve = prv.PublicKey.Curve + + if curve, ok := R.Curve.(crypto.EllipticCurve); ok { + R.X, R.Y = curve.Unmarshal(c[:rLen]) + if R.X == nil { + return nil, ErrInvalidPublicKey + } + + z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen) + if err != nil { + return nil, err + } + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) + + d := messageTag(params.Hash, Km, c[mStart:mEnd], s2) + if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 { + return nil, ErrInvalidMessage + } + return symDecrypt(params, Ke, c[mStart:mEnd]) + } + return nil, ErrInvalidCurve +} diff --git a/crypto/ecies/ecies_test.go b/crypto/ecies/ecies_test.go new file mode 100644 index 0000000..e3da710 --- /dev/null +++ b/crypto/ecies/ecies_test.go @@ -0,0 +1,429 @@ +// Copyright (c) 2013 Kyle Isom +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ecies + +import ( + "bytes" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/crypto" +) + +func TestKDF(t *testing.T) { + tests := []struct { + length int + output []byte + }{ + {6, decode("858b192fa2ed")}, + {32, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0")}, + {48, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955")}, + {64, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955f3467fd6672cce1024c5b1effccc0f61")}, + } + + for _, test := range tests { + h := sha256.New() + k := concatKDF(h, []byte("input"), nil, test.length) + if !bytes.Equal(k, test.output) { + t.Fatalf("KDF: generated key %x does not match expected output %x", k, test.output) + } + } +} + +var ErrBadSharedKeys = errors.New("ecies: shared keys don't match") + +// cmpParams compares a set of ECIES parameters. We assume, as per the +// docs, that AES is the only supported symmetric encryption algorithm. +func cmpParams(p1, p2 *ECIESParams) bool { + return p1.hashAlgo == p2.hashAlgo && + p1.KeyLen == p2.KeyLen && + p1.BlockSize == p2.BlockSize +} + +// Validate the ECDH component. +func TestSharedKey(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + skLen := MaxSharedKeyLength(&prv1.PublicKey) / 2 + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + sk1, err := prv1.GenerateShared(&prv2.PublicKey, skLen, skLen) + if err != nil { + t.Fatal(err) + } + + sk2, err := prv2.GenerateShared(&prv1.PublicKey, skLen, skLen) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(sk1, sk2) { + t.Fatal(ErrBadSharedKeys) + } +} + +func TestSharedKeyPadding(t *testing.T) { + // sanity checks + prv0 := hexKey("1adf5c18167d96a1f9a0b1ef63be8aa27eaf6032c233b2b38f7850cf5b859fd9") + prv1 := hexKey("0097a076fc7fcd9208240668e31c9abee952cbb6e375d1b8febc7499d6e16f1a") + x0, _ := new(big.Int).SetString("1a8ed022ff7aec59dc1b440446bdda5ff6bcb3509a8b109077282b361efffbd8", 16) + x1, _ := new(big.Int).SetString("6ab3ac374251f638d0abb3ef596d1dc67955b507c104e5f2009724812dc027b8", 16) + y0, _ := new(big.Int).SetString("e040bd480b1deccc3bc40bd5b1fdcb7bfd352500b477cb9471366dbd4493f923", 16) + y1, _ := new(big.Int).SetString("8ad915f2b503a8be6facab6588731fefeb584fd2dfa9a77a5e0bba1ec439e4fa", 16) + + if prv0.PublicKey.X.Cmp(x0) != 0 { + t.Errorf("mismatched prv0.X:\nhave: %x\nwant: %x\n", prv0.PublicKey.X.Bytes(), x0.Bytes()) + } + if prv0.PublicKey.Y.Cmp(y0) != 0 { + t.Errorf("mismatched prv0.Y:\nhave: %x\nwant: %x\n", prv0.PublicKey.Y.Bytes(), y0.Bytes()) + } + if prv1.PublicKey.X.Cmp(x1) != 0 { + t.Errorf("mismatched prv1.X:\nhave: %x\nwant: %x\n", prv1.PublicKey.X.Bytes(), x1.Bytes()) + } + if prv1.PublicKey.Y.Cmp(y1) != 0 { + t.Errorf("mismatched prv1.Y:\nhave: %x\nwant: %x\n", prv1.PublicKey.Y.Bytes(), y1.Bytes()) + } + + // test shared secret generation + sk1, err := prv0.GenerateShared(&prv1.PublicKey, 16, 16) + if err != nil { + t.Log(err.Error()) + } + + sk2, err := prv1.GenerateShared(&prv0.PublicKey, 16, 16) + if err != nil { + t.Fatal(err.Error()) + } + + if !bytes.Equal(sk1, sk2) { + t.Fatal(ErrBadSharedKeys.Error()) + } +} + +// Verify that the key generation code fails when too much key data is +// requested. +func TestTooBigSharedKey(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + _, err = prv1.GenerateShared(&prv2.PublicKey, 32, 32) + if err != ErrSharedKeyTooBig { + t.Fatal("ecdh: shared key should be too large for curve") + } + + _, err = prv2.GenerateShared(&prv1.PublicKey, 32, 32) + if err != ErrSharedKeyTooBig { + t.Fatal("ecdh: shared key should be too large for curve") + } +} + +// Benchmark the generation of P256 keys. +func BenchmarkGenerateKeyP256(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := GenerateKey(rand.Reader, elliptic.P256(), nil); err != nil { + b.Fatal(err) + } + } +} + +// Benchmark the generation of P256 shared keys. +func BenchmarkGenSharedKeyP256(b *testing.B) { + prv, err := GenerateKey(rand.Reader, elliptic.P256(), nil) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) + if err != nil { + b.Fatal(err) + } + } +} + +// Benchmark the generation of S256 shared keys. +func BenchmarkGenSharedKeyS256(b *testing.B) { + prv, err := GenerateKey(rand.Reader, crypto.S256(), nil) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) + if err != nil { + b.Fatal(err) + } + } +} + +// Verify that an encrypted message can be successfully decrypted. +func TestEncryptDecrypt(t *testing.T) { + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) + if err != nil { + t.Fatal(err) + } + + pt, err := prv2.Decrypt(ct, nil, nil) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(pt, message) { + t.Fatal("ecies: plaintext doesn't match message") + } + + _, err = prv1.Decrypt(ct, nil, nil) + if err == nil { + t.Fatal("ecies: encryption should not have succeeded") + } +} + +func TestDecryptShared2(t *testing.T) { + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + message := []byte("Hello, world.") + shared2 := []byte("shared data 2") + ct, err := Encrypt(rand.Reader, &prv.PublicKey, message, nil, shared2) + if err != nil { + t.Fatal(err) + } + + // Check that decrypting with correct shared data works. + pt, err := prv.Decrypt(ct, nil, shared2) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(pt, message) { + t.Fatal("ecies: plaintext doesn't match message") + } + + // Decrypting without shared data or incorrect shared data fails. + if _, err = prv.Decrypt(ct, nil, nil); err == nil { + t.Fatal("ecies: decrypting without shared data didn't fail") + } + if _, err = prv.Decrypt(ct, nil, []byte("garbage")); err == nil { + t.Fatal("ecies: decrypting with incorrect shared data didn't fail") + } +} + +type testCase struct { + Curve elliptic.Curve + Name string + Expected *ECIESParams +} + +var testCases = []testCase{ + { + Curve: elliptic.P256(), + Name: "P256", + Expected: ECIES_AES128_SHA256, + }, + { + Curve: elliptic.P384(), + Name: "P384", + Expected: ECIES_AES192_SHA384, + }, + { + Curve: elliptic.P521(), + Name: "P521", + Expected: ECIES_AES256_SHA512, + }, +} + +// Test parameter selection for each curve, and that P224 fails automatic +// parameter selection (see README for a discussion of P224). Ensures that +// selecting a set of parameters automatically for the given curve works. +func TestParamSelection(t *testing.T) { + for _, c := range testCases { + testParamSelection(t, c) + } +} + +func testParamSelection(t *testing.T, c testCase) { + params := ParamsFromCurve(c.Curve) + if params == nil { + t.Fatal("ParamsFromCurve returned nil") + } else if params != nil && !cmpParams(params, c.Expected) { + t.Fatalf("ecies: parameters should be invalid (%s)\n", c.Name) + } + + prv1, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatalf("%s (%s)\n", err.Error(), c.Name) + } + + prv2, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatalf("%s (%s)\n", err.Error(), c.Name) + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv2.PublicKey, message, nil, nil) + if err != nil { + t.Fatalf("%s (%s)\n", err.Error(), c.Name) + } + + pt, err := prv2.Decrypt(ct, nil, nil) + if err != nil { + t.Fatalf("%s (%s)\n", err.Error(), c.Name) + } + + if !bytes.Equal(pt, message) { + t.Fatalf("ecies: plaintext doesn't match message (%s)\n", c.Name) + } + + _, err = prv1.Decrypt(ct, nil, nil) + if err == nil { + t.Fatalf("ecies: encryption should not have succeeded (%s)\n", c.Name) + } +} + +// Ensure that the basic public key validation in the decryption operation +// works. +func TestBasicKeyValidation(t *testing.T) { + badBytes := []byte{0, 1, 5, 6, 7, 8, 9} + + prv, err := GenerateKey(rand.Reader, DefaultCurve, nil) + if err != nil { + t.Fatal(err) + } + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, &prv.PublicKey, message, nil, nil) + if err != nil { + t.Fatal(err) + } + + for _, b := range badBytes { + ct[0] = b + _, err := prv.Decrypt(ct, nil, nil) + if err != ErrInvalidPublicKey { + t.Fatal("ecies: validated an invalid key") + } + } +} + +func TestBox(t *testing.T) { + prv1 := hexKey("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f") + prv2 := hexKey("d0b043b4c5d657670778242d82d68a29d25d7d711127d17b8e299f156dad361a") + pub2 := &prv2.PublicKey + + message := []byte("Hello, world.") + ct, err := Encrypt(rand.Reader, pub2, message, nil, nil) + if err != nil { + t.Fatal(err) + } + + pt, err := prv2.Decrypt(ct, nil, nil) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(pt, message) { + t.Fatal("ecies: plaintext doesn't match message") + } + if _, err = prv1.Decrypt(ct, nil, nil); err == nil { + t.Fatal("ecies: encryption should not have succeeded") + } +} + +// Verify GenerateShared against static values - useful when +// debugging changes in underlying libs +func TestSharedKeyStatic(t *testing.T) { + prv1 := hexKey("7ebbc6a8358bc76dd73ebc557056702c8cfc34e5cfcd90eb83af0347575fd2ad") + prv2 := hexKey("6a3d6396903245bba5837752b9e0348874e72db0c4e11e9c485a81b4ea4353b9") + + skLen := MaxSharedKeyLength(&prv1.PublicKey) / 2 + + sk1, err := prv1.GenerateShared(&prv2.PublicKey, skLen, skLen) + if err != nil { + t.Fatal(err) + } + + sk2, err := prv2.GenerateShared(&prv1.PublicKey, skLen, skLen) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(sk1, sk2) { + t.Fatal(ErrBadSharedKeys) + } + + sk := decode("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62") + if !bytes.Equal(sk1, sk) { + t.Fatalf("shared secret mismatch: want: %x have: %x", sk, sk1) + } +} + +func hexKey(prv string) *PrivateKey { + key, err := crypto.HexToECDSA(prv) + if err != nil { + panic(err) + } + return ImportECDSA(key) +} + +func decode(s string) []byte { + bytes, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return bytes +} diff --git a/crypto/ecies/params.go b/crypto/ecies/params.go new file mode 100644 index 0000000..df7698e --- /dev/null +++ b/crypto/ecies/params.go @@ -0,0 +1,145 @@ +// Copyright (c) 2013 Kyle Isom +// Copyright (c) 2012 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package ecies + +// This file contains parameters for ECIES encryption, specifying the +// symmetric encryption and HMAC parameters. + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/elliptic" + "crypto/sha256" + "crypto/sha512" + "errors" + "fmt" + "hash" + + ethcrypto "github.com/ethereum/go-ethereum/crypto" +) + +var ( + DefaultCurve = ethcrypto.S256() + ErrUnsupportedECDHAlgorithm = errors.New("ecies: unsupported ECDH algorithm") + ErrUnsupportedECIESParameters = errors.New("ecies: unsupported ECIES parameters") + ErrInvalidKeyLen = fmt.Errorf("ecies: invalid key size (> %d) in ECIESParams", maxKeyLen) +) + +// KeyLen is limited to prevent overflow of the counter +// in concatKDF. While the theoretical limit is much higher, +// no known cipher uses keys larger than 512 bytes. +const maxKeyLen = 512 + +type ECIESParams struct { + Hash func() hash.Hash // hash function + hashAlgo crypto.Hash + Cipher func([]byte) (cipher.Block, error) // symmetric cipher + BlockSize int // block size of symmetric cipher + KeyLen int // length of symmetric key +} + +// Standard ECIES parameters: +// * ECIES using AES128 and HMAC-SHA-256-16 +// * ECIES using AES256 and HMAC-SHA-256-32 +// * ECIES using AES256 and HMAC-SHA-384-48 +// * ECIES using AES256 and HMAC-SHA-512-64 + +var ( + ECIES_AES128_SHA256 = &ECIESParams{ + Hash: sha256.New, + hashAlgo: crypto.SHA256, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 16, + } + + ECIES_AES192_SHA384 = &ECIESParams{ + Hash: sha512.New384, + hashAlgo: crypto.SHA384, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 24, + } + + ECIES_AES256_SHA256 = &ECIESParams{ + Hash: sha256.New, + hashAlgo: crypto.SHA256, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } + + ECIES_AES256_SHA384 = &ECIESParams{ + Hash: sha512.New384, + hashAlgo: crypto.SHA384, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } + + ECIES_AES256_SHA512 = &ECIESParams{ + Hash: sha512.New, + hashAlgo: crypto.SHA512, + Cipher: aes.NewCipher, + BlockSize: aes.BlockSize, + KeyLen: 32, + } +) + +var paramsFromCurve = map[elliptic.Curve]*ECIESParams{ + ethcrypto.S256(): ECIES_AES128_SHA256, + elliptic.P256(): ECIES_AES128_SHA256, + elliptic.P384(): ECIES_AES192_SHA384, + elliptic.P521(): ECIES_AES256_SHA512, +} + +func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) { + paramsFromCurve[curve] = params +} + +// ParamsFromCurve selects parameters optimal for the selected elliptic curve. +// Only the curves P256, P384, and P512 are supported. +func ParamsFromCurve(curve elliptic.Curve) (params *ECIESParams) { + return paramsFromCurve[curve] +} + +func pubkeyParams(key *PublicKey) (*ECIESParams, error) { + params := key.Params + if params == nil { + if params = ParamsFromCurve(key.Curve); params == nil { + return nil, ErrUnsupportedECIESParameters + } + } + if params.KeyLen > maxKeyLen { + return nil, ErrInvalidKeyLen + } + return params, nil +} diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go new file mode 100644 index 0000000..39fdfbe --- /dev/null +++ b/crypto/kzg4844/kzg4844.go @@ -0,0 +1,168 @@ +// 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 kzg4844 implements the KZG crypto for EIP-4844. +package kzg4844 + +import ( + "embed" + "errors" + "hash" + "reflect" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +//go:embed trusted_setup.json +var content embed.FS + +var ( + blobT = reflect.TypeOf(Blob{}) + commitmentT = reflect.TypeOf(Commitment{}) + proofT = reflect.TypeOf(Proof{}) +) + +// Blob represents a 4844 data blob. +type Blob [131072]byte + +// UnmarshalJSON parses a blob in hex syntax. +func (b *Blob) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(blobT, input, b[:]) +} + +// MarshalText returns the hex representation of b. +func (b Blob) MarshalText() ([]byte, error) { + return hexutil.Bytes(b[:]).MarshalText() +} + +// Commitment is a serialized commitment to a polynomial. +type Commitment [48]byte + +// UnmarshalJSON parses a commitment in hex syntax. +func (c *Commitment) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(commitmentT, input, c[:]) +} + +// MarshalText returns the hex representation of c. +func (c Commitment) MarshalText() ([]byte, error) { + return hexutil.Bytes(c[:]).MarshalText() +} + +// Proof is a serialized commitment to the quotient polynomial. +type Proof [48]byte + +// UnmarshalJSON parses a proof in hex syntax. +func (p *Proof) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(proofT, input, p[:]) +} + +// MarshalText returns the hex representation of p. +func (p Proof) MarshalText() ([]byte, error) { + return hexutil.Bytes(p[:]).MarshalText() +} + +// Point is a BLS field element. +type Point [32]byte + +// Claim is a claimed evaluation value in a specific point. +type Claim [32]byte + +// useCKZG controls whether the cryptography should use the Go or C backend. +var useCKZG atomic.Bool + +// UseCKZG can be called to switch the default Go implementation of KZG to the C +// library if for some reason the user wishes to do so (e.g. consensus bug in one +// or the other). +func UseCKZG(use bool) error { + if use && !ckzgAvailable { + return errors.New("CKZG unavailable on your platform") + } + useCKZG.Store(use) + + // Initializing the library can take 2-4 seconds - and can potentially crash + // on CKZG and non-ADX CPUs - so might as well do it now and don't wait until + // a crypto operation is actually needed live. + if use { + ckzgIniter.Do(ckzgInit) + } else { + gokzgIniter.Do(gokzgInit) + } + return nil +} + +// BlobToCommitment creates a small commitment out of a data blob. +func BlobToCommitment(blob *Blob) (Commitment, error) { + if useCKZG.Load() { + return ckzgBlobToCommitment(blob) + } + return gokzgBlobToCommitment(blob) +} + +// ComputeProof computes the KZG proof at the given point for the polynomial +// represented by the blob. +func ComputeProof(blob *Blob, point Point) (Proof, Claim, error) { + if useCKZG.Load() { + return ckzgComputeProof(blob, point) + } + return gokzgComputeProof(blob, point) +} + +// VerifyProof verifies the KZG proof that the polynomial represented by the blob +// evaluated at the given point is the claimed value. +func VerifyProof(commitment Commitment, point Point, claim Claim, proof Proof) error { + if useCKZG.Load() { + return ckzgVerifyProof(commitment, point, claim, proof) + } + return gokzgVerifyProof(commitment, point, claim, proof) +} + +// ComputeBlobProof returns the KZG proof that is used to verify the blob against +// the commitment. +// +// This method does not verify that the commitment is correct with respect to blob. +func ComputeBlobProof(blob *Blob, commitment Commitment) (Proof, error) { + if useCKZG.Load() { + return ckzgComputeBlobProof(blob, commitment) + } + return gokzgComputeBlobProof(blob, commitment) +} + +// VerifyBlobProof verifies that the blob data corresponds to the provided commitment. +func VerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error { + if useCKZG.Load() { + return ckzgVerifyBlobProof(blob, commitment, proof) + } + return gokzgVerifyBlobProof(blob, commitment, proof) +} + +// CalcBlobHashV1 calculates the 'versioned blob hash' of a commitment. +// The given hasher must be a sha256 hash instance, otherwise the result will be invalid! +func CalcBlobHashV1(hasher hash.Hash, commit *Commitment) (vh [32]byte) { + if hasher.Size() != 32 { + panic("wrong hash size") + } + hasher.Reset() + hasher.Write(commit[:]) + hasher.Sum(vh[:0]) + vh[0] = 0x01 // version + return vh +} + +// IsValidVersionedHash checks that h is a structurally-valid versioned blob hash. +func IsValidVersionedHash(h []byte) bool { + return len(h) == 32 && h[0] == 0x01 +} diff --git a/crypto/kzg4844/kzg4844_ckzg_cgo.go b/crypto/kzg4844/kzg4844_ckzg_cgo.go new file mode 100644 index 0000000..11bc451 --- /dev/null +++ b/crypto/kzg4844/kzg4844_ckzg_cgo.go @@ -0,0 +1,127 @@ +// 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 ckzg && !nacl && !js && cgo && !gofuzz + +package kzg4844 + +import ( + "encoding/json" + "errors" + "sync" + + gokzg4844 "github.com/crate-crypto/go-kzg-4844" + ckzg4844 "github.com/ethereum/c-kzg-4844/bindings/go" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// ckzgAvailable signals whether the library was compiled into Geth. +const ckzgAvailable = true + +// ckzgIniter ensures that we initialize the KZG library once before using it. +var ckzgIniter sync.Once + +// ckzgInit initializes the KZG library with the provided trusted setup. +func ckzgInit() { + config, err := content.ReadFile("trusted_setup.json") + if err != nil { + panic(err) + } + params := new(gokzg4844.JSONTrustedSetup) + if err = json.Unmarshal(config, params); err != nil { + panic(err) + } + if err = gokzg4844.CheckTrustedSetupIsWellFormed(params); err != nil { + panic(err) + } + g1s := make([]byte, len(params.SetupG1Lagrange)*(len(params.SetupG1Lagrange[0])-2)/2) + for i, g1 := range params.SetupG1Lagrange { + copy(g1s[i*(len(g1)-2)/2:], hexutil.MustDecode(g1)) + } + g2s := make([]byte, len(params.SetupG2)*(len(params.SetupG2[0])-2)/2) + for i, g2 := range params.SetupG2 { + copy(g2s[i*(len(g2)-2)/2:], hexutil.MustDecode(g2)) + } + if err = ckzg4844.LoadTrustedSetup(g1s, g2s); err != nil { + panic(err) + } +} + +// ckzgBlobToCommitment creates a small commitment out of a data blob. +func ckzgBlobToCommitment(blob *Blob) (Commitment, error) { + ckzgIniter.Do(ckzgInit) + + commitment, err := ckzg4844.BlobToKZGCommitment((*ckzg4844.Blob)(blob)) + if err != nil { + return Commitment{}, err + } + return (Commitment)(commitment), nil +} + +// ckzgComputeProof computes the KZG proof at the given point for the polynomial +// represented by the blob. +func ckzgComputeProof(blob *Blob, point Point) (Proof, Claim, error) { + ckzgIniter.Do(ckzgInit) + + proof, claim, err := ckzg4844.ComputeKZGProof((*ckzg4844.Blob)(blob), (ckzg4844.Bytes32)(point)) + if err != nil { + return Proof{}, Claim{}, err + } + return (Proof)(proof), (Claim)(claim), nil +} + +// ckzgVerifyProof verifies the KZG proof that the polynomial represented by the blob +// evaluated at the given point is the claimed value. +func ckzgVerifyProof(commitment Commitment, point Point, claim Claim, proof Proof) error { + ckzgIniter.Do(ckzgInit) + + valid, err := ckzg4844.VerifyKZGProof((ckzg4844.Bytes48)(commitment), (ckzg4844.Bytes32)(point), (ckzg4844.Bytes32)(claim), (ckzg4844.Bytes48)(proof)) + if err != nil { + return err + } + if !valid { + return errors.New("invalid proof") + } + return nil +} + +// ckzgComputeBlobProof returns the KZG proof that is used to verify the blob against +// the commitment. +// +// This method does not verify that the commitment is correct with respect to blob. +func ckzgComputeBlobProof(blob *Blob, commitment Commitment) (Proof, error) { + ckzgIniter.Do(ckzgInit) + + proof, err := ckzg4844.ComputeBlobKZGProof((*ckzg4844.Blob)(blob), (ckzg4844.Bytes48)(commitment)) + if err != nil { + return Proof{}, err + } + return (Proof)(proof), nil +} + +// ckzgVerifyBlobProof verifies that the blob data corresponds to the provided commitment. +func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error { + ckzgIniter.Do(ckzgInit) + + valid, err := ckzg4844.VerifyBlobKZGProof((*ckzg4844.Blob)(blob), (ckzg4844.Bytes48)(commitment), (ckzg4844.Bytes48)(proof)) + if err != nil { + return err + } + if !valid { + return errors.New("invalid proof") + } + return nil +} diff --git a/crypto/kzg4844/kzg4844_ckzg_nocgo.go b/crypto/kzg4844/kzg4844_ckzg_nocgo.go new file mode 100644 index 0000000..70a78e8 --- /dev/null +++ b/crypto/kzg4844/kzg4844_ckzg_nocgo.go @@ -0,0 +1,62 @@ +// 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 !ckzg || nacl || js || !cgo || gofuzz + +package kzg4844 + +import "sync" + +// ckzgAvailable signals whether the library was compiled into Geth. +const ckzgAvailable = false + +// ckzgIniter ensures that we initialize the KZG library once before using it. +var ckzgIniter sync.Once + +// ckzgInit initializes the KZG library with the provided trusted setup. +func ckzgInit() { + panic("unsupported platform") +} + +// ckzgBlobToCommitment creates a small commitment out of a data blob. +func ckzgBlobToCommitment(blob *Blob) (Commitment, error) { + panic("unsupported platform") +} + +// ckzgComputeProof computes the KZG proof at the given point for the polynomial +// represented by the blob. +func ckzgComputeProof(blob *Blob, point Point) (Proof, Claim, error) { + panic("unsupported platform") +} + +// ckzgVerifyProof verifies the KZG proof that the polynomial represented by the blob +// evaluated at the given point is the claimed value. +func ckzgVerifyProof(commitment Commitment, point Point, claim Claim, proof Proof) error { + panic("unsupported platform") +} + +// ckzgComputeBlobProof returns the KZG proof that is used to verify the blob against +// the commitment. +// +// This method does not verify that the commitment is correct with respect to blob. +func ckzgComputeBlobProof(blob *Blob, commitment Commitment) (Proof, error) { + panic("unsupported platform") +} + +// ckzgVerifyBlobProof verifies that the blob data corresponds to the provided commitment. +func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error { + panic("unsupported platform") +} diff --git a/crypto/kzg4844/kzg4844_gokzg.go b/crypto/kzg4844/kzg4844_gokzg.go new file mode 100644 index 0000000..b4af9b1 --- /dev/null +++ b/crypto/kzg4844/kzg4844_gokzg.go @@ -0,0 +1,98 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package kzg4844 + +import ( + "encoding/json" + "sync" + + gokzg4844 "github.com/crate-crypto/go-kzg-4844" +) + +// context is the crypto primitive pre-seeded with the trusted setup parameters. +var context *gokzg4844.Context + +// gokzgIniter ensures that we initialize the KZG library once before using it. +var gokzgIniter sync.Once + +// gokzgInit initializes the KZG library with the provided trusted setup. +func gokzgInit() { + config, err := content.ReadFile("trusted_setup.json") + if err != nil { + panic(err) + } + params := new(gokzg4844.JSONTrustedSetup) + if err = json.Unmarshal(config, params); err != nil { + panic(err) + } + context, err = gokzg4844.NewContext4096(params) + if err != nil { + panic(err) + } +} + +// gokzgBlobToCommitment creates a small commitment out of a data blob. +func gokzgBlobToCommitment(blob *Blob) (Commitment, error) { + gokzgIniter.Do(gokzgInit) + + commitment, err := context.BlobToKZGCommitment((*gokzg4844.Blob)(blob), 0) + if err != nil { + return Commitment{}, err + } + return (Commitment)(commitment), nil +} + +// gokzgComputeProof computes the KZG proof at the given point for the polynomial +// represented by the blob. +func gokzgComputeProof(blob *Blob, point Point) (Proof, Claim, error) { + gokzgIniter.Do(gokzgInit) + + proof, claim, err := context.ComputeKZGProof((*gokzg4844.Blob)(blob), (gokzg4844.Scalar)(point), 0) + if err != nil { + return Proof{}, Claim{}, err + } + return (Proof)(proof), (Claim)(claim), nil +} + +// gokzgVerifyProof verifies the KZG proof that the polynomial represented by the blob +// evaluated at the given point is the claimed value. +func gokzgVerifyProof(commitment Commitment, point Point, claim Claim, proof Proof) error { + gokzgIniter.Do(gokzgInit) + + return context.VerifyKZGProof((gokzg4844.KZGCommitment)(commitment), (gokzg4844.Scalar)(point), (gokzg4844.Scalar)(claim), (gokzg4844.KZGProof)(proof)) +} + +// gokzgComputeBlobProof returns the KZG proof that is used to verify the blob against +// the commitment. +// +// This method does not verify that the commitment is correct with respect to blob. +func gokzgComputeBlobProof(blob *Blob, commitment Commitment) (Proof, error) { + gokzgIniter.Do(gokzgInit) + + proof, err := context.ComputeBlobKZGProof((*gokzg4844.Blob)(blob), (gokzg4844.KZGCommitment)(commitment), 0) + if err != nil { + return Proof{}, err + } + return (Proof)(proof), nil +} + +// gokzgVerifyBlobProof verifies that the blob data corresponds to the provided commitment. +func gokzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error { + gokzgIniter.Do(gokzgInit) + + return context.VerifyBlobKZGProof((*gokzg4844.Blob)(blob), (gokzg4844.KZGCommitment)(commitment), (gokzg4844.KZGProof)(proof)) +} diff --git a/crypto/kzg4844/kzg4844_test.go b/crypto/kzg4844/kzg4844_test.go new file mode 100644 index 0000000..a6782d4 --- /dev/null +++ b/crypto/kzg4844/kzg4844_test.go @@ -0,0 +1,195 @@ +// 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 kzg4844 + +import ( + "crypto/rand" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + gokzg4844 "github.com/crate-crypto/go-kzg-4844" +) + +func randFieldElement() [32]byte { + bytes := make([]byte, 32) + _, err := rand.Read(bytes) + if err != nil { + panic("failed to get random field element") + } + var r fr.Element + r.SetBytes(bytes) + + return gokzg4844.SerializeScalar(r) +} + +func randBlob() *Blob { + var blob Blob + for i := 0; i < len(blob); i += gokzg4844.SerializedScalarSize { + fieldElementBytes := randFieldElement() + copy(blob[i:i+gokzg4844.SerializedScalarSize], fieldElementBytes[:]) + } + return &blob +} + +func TestCKZGWithPoint(t *testing.T) { testKZGWithPoint(t, true) } +func TestGoKZGWithPoint(t *testing.T) { testKZGWithPoint(t, false) } +func testKZGWithPoint(t *testing.T, ckzg bool) { + if ckzg && !ckzgAvailable { + t.Skip("CKZG unavailable in this test build") + } + defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load()) + useCKZG.Store(ckzg) + + blob := randBlob() + + commitment, err := BlobToCommitment(blob) + if err != nil { + t.Fatalf("failed to create KZG commitment from blob: %v", err) + } + point := randFieldElement() + proof, claim, err := ComputeProof(blob, point) + if err != nil { + t.Fatalf("failed to create KZG proof at point: %v", err) + } + if err := VerifyProof(commitment, point, claim, proof); err != nil { + t.Fatalf("failed to verify KZG proof at point: %v", err) + } +} + +func TestCKZGWithBlob(t *testing.T) { testKZGWithBlob(t, true) } +func TestGoKZGWithBlob(t *testing.T) { testKZGWithBlob(t, false) } +func testKZGWithBlob(t *testing.T, ckzg bool) { + if ckzg && !ckzgAvailable { + t.Skip("CKZG unavailable in this test build") + } + defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load()) + useCKZG.Store(ckzg) + + blob := randBlob() + + commitment, err := BlobToCommitment(blob) + if err != nil { + t.Fatalf("failed to create KZG commitment from blob: %v", err) + } + proof, err := ComputeBlobProof(blob, commitment) + if err != nil { + t.Fatalf("failed to create KZG proof for blob: %v", err) + } + if err := VerifyBlobProof(blob, commitment, proof); err != nil { + t.Fatalf("failed to verify KZG proof for blob: %v", err) + } +} + +func BenchmarkCKZGBlobToCommitment(b *testing.B) { benchmarkBlobToCommitment(b, true) } +func BenchmarkGoKZGBlobToCommitment(b *testing.B) { benchmarkBlobToCommitment(b, false) } +func benchmarkBlobToCommitment(b *testing.B, ckzg bool) { + if ckzg && !ckzgAvailable { + b.Skip("CKZG unavailable in this test build") + } + defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load()) + useCKZG.Store(ckzg) + + blob := randBlob() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + BlobToCommitment(blob) + } +} + +func BenchmarkCKZGComputeProof(b *testing.B) { benchmarkComputeProof(b, true) } +func BenchmarkGoKZGComputeProof(b *testing.B) { benchmarkComputeProof(b, false) } +func benchmarkComputeProof(b *testing.B, ckzg bool) { + if ckzg && !ckzgAvailable { + b.Skip("CKZG unavailable in this test build") + } + defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load()) + useCKZG.Store(ckzg) + + var ( + blob = randBlob() + point = randFieldElement() + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ComputeProof(blob, point) + } +} + +func BenchmarkCKZGVerifyProof(b *testing.B) { benchmarkVerifyProof(b, true) } +func BenchmarkGoKZGVerifyProof(b *testing.B) { benchmarkVerifyProof(b, false) } +func benchmarkVerifyProof(b *testing.B, ckzg bool) { + if ckzg && !ckzgAvailable { + b.Skip("CKZG unavailable in this test build") + } + defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load()) + useCKZG.Store(ckzg) + + var ( + blob = randBlob() + point = randFieldElement() + commitment, _ = BlobToCommitment(blob) + proof, claim, _ = ComputeProof(blob, point) + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + VerifyProof(commitment, point, claim, proof) + } +} + +func BenchmarkCKZGComputeBlobProof(b *testing.B) { benchmarkComputeBlobProof(b, true) } +func BenchmarkGoKZGComputeBlobProof(b *testing.B) { benchmarkComputeBlobProof(b, false) } +func benchmarkComputeBlobProof(b *testing.B, ckzg bool) { + if ckzg && !ckzgAvailable { + b.Skip("CKZG unavailable in this test build") + } + defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load()) + useCKZG.Store(ckzg) + + var ( + blob = randBlob() + commitment, _ = BlobToCommitment(blob) + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + ComputeBlobProof(blob, commitment) + } +} + +func BenchmarkCKZGVerifyBlobProof(b *testing.B) { benchmarkVerifyBlobProof(b, true) } +func BenchmarkGoKZGVerifyBlobProof(b *testing.B) { benchmarkVerifyBlobProof(b, false) } +func benchmarkVerifyBlobProof(b *testing.B, ckzg bool) { + if ckzg && !ckzgAvailable { + b.Skip("CKZG unavailable in this test build") + } + defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load()) + useCKZG.Store(ckzg) + + var ( + blob = randBlob() + commitment, _ = BlobToCommitment(blob) + proof, _ = ComputeBlobProof(blob, commitment) + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + VerifyBlobProof(blob, commitment, proof) + } +} diff --git a/crypto/kzg4844/trusted_setup.json b/crypto/kzg4844/trusted_setup.json new file mode 100644 index 0000000..c6d724e --- /dev/null +++ b/crypto/kzg4844/trusted_setup.json @@ -0,0 +1,4167 @@ +{ + "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" + ], + "g2_monomial": [ + "0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", + "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/.gitignore b/crypto/secp256k1/.gitignore new file mode 100644 index 0000000..802b674 --- /dev/null +++ b/crypto/secp256k1/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe + +*~ diff --git a/crypto/secp256k1/LICENSE b/crypto/secp256k1/LICENSE new file mode 100644 index 0000000..f9090e1 --- /dev/null +++ b/crypto/secp256k1/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2010 The Go Authors. All rights reserved. +Copyright (c) 2011 ThePiachu. All rights reserved. +Copyright (c) 2015 Jeffrey Wilcke. All rights reserved. +Copyright (c) 2015 Felix Lange. All rights reserved. +Copyright (c) 2015 Gustav Simonsson. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the copyright holder. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go new file mode 100644 index 0000000..85ba885 --- /dev/null +++ b/crypto/secp256k1/curve.go @@ -0,0 +1,297 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Copyright 2011 ThePiachu. All rights reserved. +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// * The name of ThePiachu may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package secp256k1 + +import ( + "crypto/elliptic" + "math/big" +) + +const ( + // number of bits in a big.Word + wordBits = 32 << (uint64(^big.Word(0)) >> 63) + // number of bytes in a big.Word + wordBytes = wordBits / 8 +) + +// readBits encodes the absolute value of bigint as big-endian bytes. Callers +// must ensure that buf has enough space. If buf is too short the result will +// be incomplete. +func readBits(bigint *big.Int, buf []byte) { + i := len(buf) + for _, d := range bigint.Bits() { + for j := 0; j < wordBytes && i > 0; j++ { + i-- + buf[i] = byte(d) + d >>= 8 + } + } +} + +// This code is from https://github.com/ThePiachu/GoBit and implements +// several Koblitz elliptic curves over prime fields. +// +// The curve methods, internally, on Jacobian coordinates. For a given +// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, +// z1) where x = x1/z1² and y = y1/z1³. The greatest speedups come +// when the whole calculation can be performed within the transform +// (as in ScalarMult and ScalarBaseMult). But even for Add and Double, +// it's faster to apply and reverse the transform than to operate in +// affine coordinates. + +// A BitCurve represents a Koblitz Curve with a=0. +// See http://www.hyperelliptic.org/EFD/g1p/auto-shortw.html +type BitCurve struct { + P *big.Int // the order of the underlying field + N *big.Int // the order of the base point + B *big.Int // the constant of the BitCurve equation + Gx, Gy *big.Int // (x,y) of the base point + BitSize int // the size of the underlying field +} + +func (bitCurve *BitCurve) Params() *elliptic.CurveParams { + return &elliptic.CurveParams{ + P: bitCurve.P, + N: bitCurve.N, + B: bitCurve.B, + Gx: bitCurve.Gx, + Gy: bitCurve.Gy, + BitSize: bitCurve.BitSize, + } +} + +// IsOnCurve returns true if the given (x,y) lies on the BitCurve. +func (bitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool { + // y² = x³ + b + y2 := new(big.Int).Mul(y, y) //y² + y2.Mod(y2, bitCurve.P) //y²%P + + x3 := new(big.Int).Mul(x, x) //x² + x3.Mul(x3, x) //x³ + + x3.Add(x3, bitCurve.B) //x³+B + x3.Mod(x3, bitCurve.P) //(x³+B)%P + + return x3.Cmp(y2) == 0 +} + +// affineFromJacobian reverses the Jacobian transform. See the comment at the +// top of the file. +func (bitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) { + if z.Sign() == 0 { + return new(big.Int), new(big.Int) + } + + zinv := new(big.Int).ModInverse(z, bitCurve.P) + zinvsq := new(big.Int).Mul(zinv, zinv) + + xOut = new(big.Int).Mul(x, zinvsq) + xOut.Mod(xOut, bitCurve.P) + zinvsq.Mul(zinvsq, zinv) + yOut = new(big.Int).Mul(y, zinvsq) + yOut.Mod(yOut, bitCurve.P) + return +} + +// Add returns the sum of (x1,y1) and (x2,y2) +func (bitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) { + // If one point is at infinity, return the other point. + // Adding the point at infinity to any point will preserve the other point. + if x1.Sign() == 0 && y1.Sign() == 0 { + return x2, y2 + } + if x2.Sign() == 0 && y2.Sign() == 0 { + return x1, y1 + } + z := new(big.Int).SetInt64(1) + if x1.Cmp(x2) == 0 && y1.Cmp(y2) == 0 { + return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z)) + } + return bitCurve.affineFromJacobian(bitCurve.addJacobian(x1, y1, z, x2, y2, z)) +} + +// addJacobian takes two points in Jacobian coordinates, (x1, y1, z1) and +// (x2, y2, z2) and returns their sum, also in Jacobian form. +func (bitCurve *BitCurve) addJacobian(x1, y1, z1, x2, y2, z2 *big.Int) (*big.Int, *big.Int, *big.Int) { + // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl + z1z1 := new(big.Int).Mul(z1, z1) + z1z1.Mod(z1z1, bitCurve.P) + z2z2 := new(big.Int).Mul(z2, z2) + z2z2.Mod(z2z2, bitCurve.P) + + u1 := new(big.Int).Mul(x1, z2z2) + u1.Mod(u1, bitCurve.P) + u2 := new(big.Int).Mul(x2, z1z1) + u2.Mod(u2, bitCurve.P) + h := new(big.Int).Sub(u2, u1) + if h.Sign() == -1 { + h.Add(h, bitCurve.P) + } + i := new(big.Int).Lsh(h, 1) + i.Mul(i, i) + j := new(big.Int).Mul(h, i) + + s1 := new(big.Int).Mul(y1, z2) + s1.Mul(s1, z2z2) + s1.Mod(s1, bitCurve.P) + s2 := new(big.Int).Mul(y2, z1) + s2.Mul(s2, z1z1) + s2.Mod(s2, bitCurve.P) + r := new(big.Int).Sub(s2, s1) + if r.Sign() == -1 { + r.Add(r, bitCurve.P) + } + r.Lsh(r, 1) + v := new(big.Int).Mul(u1, i) + + x3 := new(big.Int).Set(r) + x3.Mul(x3, x3) + x3.Sub(x3, j) + x3.Sub(x3, v) + x3.Sub(x3, v) + x3.Mod(x3, bitCurve.P) + + y3 := new(big.Int).Set(r) + v.Sub(v, x3) + y3.Mul(y3, v) + s1.Mul(s1, j) + s1.Lsh(s1, 1) + y3.Sub(y3, s1) + y3.Mod(y3, bitCurve.P) + + z3 := new(big.Int).Add(z1, z2) + z3.Mul(z3, z3) + z3.Sub(z3, z1z1) + if z3.Sign() == -1 { + z3.Add(z3, bitCurve.P) + } + z3.Sub(z3, z2z2) + if z3.Sign() == -1 { + z3.Add(z3, bitCurve.P) + } + z3.Mul(z3, h) + z3.Mod(z3, bitCurve.P) + + return x3, y3, z3 +} + +// Double returns 2*(x,y) +func (bitCurve *BitCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) { + z1 := new(big.Int).SetInt64(1) + return bitCurve.affineFromJacobian(bitCurve.doubleJacobian(x1, y1, z1)) +} + +// doubleJacobian takes a point in Jacobian coordinates, (x, y, z), and +// returns its double, also in Jacobian form. +func (bitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, *big.Int) { + // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + + a := new(big.Int).Mul(x, x) //X1² + b := new(big.Int).Mul(y, y) //Y1² + c := new(big.Int).Mul(b, b) //B² + + d := new(big.Int).Add(x, b) //X1+B + d.Mul(d, d) //(X1+B)² + d.Sub(d, a) //(X1+B)²-A + d.Sub(d, c) //(X1+B)²-A-C + d.Mul(d, big.NewInt(2)) //2*((X1+B)²-A-C) + + e := new(big.Int).Mul(big.NewInt(3), a) //3*A + f := new(big.Int).Mul(e, e) //E² + + x3 := new(big.Int).Mul(big.NewInt(2), d) //2*D + x3.Sub(f, x3) //F-2*D + x3.Mod(x3, bitCurve.P) + + y3 := new(big.Int).Sub(d, x3) //D-X3 + y3.Mul(e, y3) //E*(D-X3) + y3.Sub(y3, new(big.Int).Mul(big.NewInt(8), c)) //E*(D-X3)-8*C + y3.Mod(y3, bitCurve.P) + + z3 := new(big.Int).Mul(y, z) //Y1*Z1 + z3.Mul(big.NewInt(2), z3) //3*Y1*Z1 + z3.Mod(z3, bitCurve.P) + + return x3, y3, z3 +} + +// ScalarBaseMult returns k*G, where G is the base point of the group and k is +// an integer in big-endian form. +func (bitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) { + return bitCurve.ScalarMult(bitCurve.Gx, bitCurve.Gy, k) +} + +// Marshal converts a point into the form specified in section 4.3.6 of ANSI +// X9.62. +func (bitCurve *BitCurve) Marshal(x, y *big.Int) []byte { + byteLen := (bitCurve.BitSize + 7) >> 3 + ret := make([]byte, 1+2*byteLen) + ret[0] = 4 // uncompressed point flag + readBits(x, ret[1:1+byteLen]) + readBits(y, ret[1+byteLen:]) + return ret +} + +// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On +// error, x = nil. +func (bitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) { + byteLen := (bitCurve.BitSize + 7) >> 3 + if len(data) != 1+2*byteLen { + return + } + if data[0] != 4 { // uncompressed form + return + } + x = new(big.Int).SetBytes(data[1 : 1+byteLen]) + y = new(big.Int).SetBytes(data[1+byteLen:]) + return +} + +var theCurve = new(BitCurve) + +func init() { + // See SEC 2 section 2.7.1 + // curve parameters taken from: + // http://www.secg.org/sec2-v2.pdf + theCurve.P, _ = new(big.Int).SetString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 0) + theCurve.N, _ = new(big.Int).SetString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 0) + theCurve.B, _ = new(big.Int).SetString("0x0000000000000000000000000000000000000000000000000000000000000007", 0) + theCurve.Gx, _ = new(big.Int).SetString("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 0) + theCurve.Gy, _ = new(big.Int).SetString("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 0) + theCurve.BitSize = 256 +} + +// S256 returns a BitCurve which implements secp256k1. +func S256() *BitCurve { + return theCurve +} diff --git a/crypto/secp256k1/dummy.go b/crypto/secp256k1/dummy.go new file mode 100644 index 0000000..65a7508 --- /dev/null +++ b/crypto/secp256k1/dummy.go @@ -0,0 +1,21 @@ +//go:build dummy +// +build dummy + +// This file is part of a workaround for `go mod vendor` which won't vendor +// C files if there's no Go file in the same directory. +// This would prevent the crypto/secp256k1/libsecp256k1/include/secp256k1.h file to be vendored. +// +// This Go file imports the c directory where there is another dummy.go file which +// is the second part of this workaround. +// +// These two files combined make it so `go mod vendor` behaves correctly. +// +// See this issue for reference: https://github.com/golang/go/issues/26366 + +package secp256k1 + +import ( + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/include" + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/src" + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/src/modules/recovery" +) diff --git a/crypto/secp256k1/ext.h b/crypto/secp256k1/ext.h new file mode 100644 index 0000000..e422fe4 --- /dev/null +++ b/crypto/secp256k1/ext.h @@ -0,0 +1,130 @@ +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// secp256k1_context_create_sign_verify creates a context for signing and signature verification. +static secp256k1_context* secp256k1_context_create_sign_verify() { + return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); +} + +// secp256k1_ext_ecdsa_recover recovers the public key of an encoded compact signature. +// +// Returns: 1: recovery was successful +// 0: recovery was not successful +// Args: ctx: pointer to a context object (cannot be NULL) +// Out: pubkey_out: the serialized 65-byte public key of the signer (cannot be NULL) +// In: sigdata: pointer to a 65-byte signature with the recovery id at the end (cannot be NULL) +// msgdata: pointer to a 32-byte message (cannot be NULL) +static int secp256k1_ext_ecdsa_recover( + const secp256k1_context* ctx, + unsigned char *pubkey_out, + const unsigned char *sigdata, + const unsigned char *msgdata +) { + secp256k1_ecdsa_recoverable_signature sig; + secp256k1_pubkey pubkey; + + if (!secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &sig, sigdata, (int)sigdata[64])) { + return 0; + } + if (!secp256k1_ecdsa_recover(ctx, &pubkey, &sig, msgdata)) { + return 0; + } + size_t outputlen = 65; + return secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &outputlen, &pubkey, SECP256K1_EC_UNCOMPRESSED); +} + +// secp256k1_ext_ecdsa_verify verifies an encoded compact signature. +// +// Returns: 1: signature is valid +// 0: signature is invalid +// Args: ctx: pointer to a context object (cannot be NULL) +// In: sigdata: pointer to a 64-byte signature (cannot be NULL) +// msgdata: pointer to a 32-byte message (cannot be NULL) +// pubkeydata: pointer to public key data (cannot be NULL) +// pubkeylen: length of pubkeydata +static int secp256k1_ext_ecdsa_verify( + const secp256k1_context* ctx, + const unsigned char *sigdata, + const unsigned char *msgdata, + const unsigned char *pubkeydata, + size_t pubkeylen +) { + secp256k1_ecdsa_signature sig; + secp256k1_pubkey pubkey; + + if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, sigdata)) { + return 0; + } + if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeydata, pubkeylen)) { + return 0; + } + return secp256k1_ecdsa_verify(ctx, &sig, msgdata, &pubkey); +} + +// secp256k1_ext_reencode_pubkey decodes then encodes a public key. It can be used to +// convert between public key formats. The input/output formats are chosen depending on the +// length of the input/output buffers. +// +// Returns: 1: conversion successful +// 0: conversion unsuccessful +// Args: ctx: pointer to a context object (cannot be NULL) +// Out: out: output buffer that will contain the reencoded key (cannot be NULL) +// In: outlen: length of out (33 for compressed keys, 65 for uncompressed keys) +// pubkeydata: the input public key (cannot be NULL) +// pubkeylen: length of pubkeydata +static int secp256k1_ext_reencode_pubkey( + const secp256k1_context* ctx, + unsigned char *out, + size_t outlen, + const unsigned char *pubkeydata, + size_t pubkeylen +) { + secp256k1_pubkey pubkey; + + if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeydata, pubkeylen)) { + return 0; + } + unsigned int flag = (outlen == 33) ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED; + return secp256k1_ec_pubkey_serialize(ctx, out, &outlen, &pubkey, flag); +} + +// secp256k1_ext_scalar_mul multiplies a point by a scalar in constant time. +// +// Returns: 1: multiplication was successful +// 0: scalar was invalid (zero or overflow) +// Args: ctx: pointer to a context object (cannot be NULL) +// Out: point: the multiplied point (usually secret) +// In: point: pointer to a 64-byte public point, +// encoded as two 256bit big-endian numbers. +// scalar: a 32-byte scalar with which to multiply the point +int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, unsigned char *point, const unsigned char *scalar) { + int ret = 0; + int overflow = 0; + secp256k1_fe feX, feY; + secp256k1_gej res; + secp256k1_ge ge; + secp256k1_scalar s; + ARG_CHECK(point != NULL); + ARG_CHECK(scalar != NULL); + (void)ctx; + + secp256k1_fe_set_b32(&feX, point); + secp256k1_fe_set_b32(&feY, point+32); + secp256k1_ge_set_xy(&ge, &feX, &feY); + secp256k1_scalar_set_b32(&s, scalar, &overflow); + if (overflow || secp256k1_scalar_is_zero(&s)) { + ret = 0; + } else { + secp256k1_ecmult_const(&res, &ge, &s); + secp256k1_ge_set_gej(&ge, &res); + /* Note: can't use secp256k1_pubkey_save here because it is not constant time. */ + secp256k1_fe_normalize(&ge.x); + secp256k1_fe_normalize(&ge.y); + secp256k1_fe_get_b32(point, &ge.x); + secp256k1_fe_get_b32(point+32, &ge.y); + ret = 1; + } + secp256k1_scalar_clear(&s); + return ret; +} diff --git a/crypto/secp256k1/libsecp256k1/.gitignore b/crypto/secp256k1/libsecp256k1/.gitignore new file mode 100644 index 0000000..87fea16 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/.gitignore @@ -0,0 +1,49 @@ +bench_inv +bench_ecdh +bench_sign +bench_verify +bench_schnorr_verify +bench_recover +bench_internal +tests +exhaustive_tests +gen_context +*.exe +*.so +*.a +!.gitignore + +Makefile +configure +.libs/ +Makefile.in +aclocal.m4 +autom4te.cache/ +config.log +config.status +*.tar.gz +*.la +libtool +.deps/ +.dirstamp +*.lo +*.o +*~ +src/libsecp256k1-config.h +src/libsecp256k1-config.h.in +src/ecmult_static_context.h +build-aux/config.guess +build-aux/config.sub +build-aux/depcomp +build-aux/install-sh +build-aux/ltmain.sh +build-aux/m4/libtool.m4 +build-aux/m4/lt~obsolete.m4 +build-aux/m4/ltoptions.m4 +build-aux/m4/ltsugar.m4 +build-aux/m4/ltversion.m4 +build-aux/missing +build-aux/compile +build-aux/test-driver +src/stamp-h1 +libsecp256k1.pc diff --git a/crypto/secp256k1/libsecp256k1/.travis.yml b/crypto/secp256k1/libsecp256k1/.travis.yml new file mode 100644 index 0000000..2439529 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/.travis.yml @@ -0,0 +1,69 @@ +language: c +sudo: false +addons: + apt: + packages: libgmp-dev +compiler: + - clang + - gcc +cache: + directories: + - src/java/guava/ +env: + global: + - FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no EXPERIMENTAL=no + - GUAVA_URL=https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar GUAVA_JAR=src/java/guava/guava-18.0.jar + matrix: + - SCALAR=32bit RECOVERY=yes + - SCALAR=32bit FIELD=32bit ECDH=yes EXPERIMENTAL=yes + - SCALAR=64bit + - FIELD=64bit RECOVERY=yes + - FIELD=64bit ENDOMORPHISM=yes + - FIELD=64bit ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes + - FIELD=64bit ASM=x86_64 + - FIELD=64bit ENDOMORPHISM=yes ASM=x86_64 + - FIELD=32bit ENDOMORPHISM=yes + - BIGNUM=no + - BIGNUM=no ENDOMORPHISM=yes RECOVERY=yes EXPERIMENTAL=yes + - BIGNUM=no STATICPRECOMPUTATION=no + - BUILD=distcheck + - EXTRAFLAGS=CPPFLAGS=-DDETERMINISTIC + - EXTRAFLAGS=CFLAGS=-O0 + - BUILD=check-java ECDH=yes EXPERIMENTAL=yes +matrix: + fast_finish: true + include: + - compiler: clang + env: HOST=i686-linux-gnu ENDOMORPHISM=yes + addons: + apt: + packages: + - gcc-multilib + - libgmp-dev:i386 + - compiler: clang + env: HOST=i686-linux-gnu + addons: + apt: + packages: + - gcc-multilib + - compiler: gcc + env: HOST=i686-linux-gnu ENDOMORPHISM=yes + addons: + apt: + packages: + - gcc-multilib + - compiler: gcc + env: HOST=i686-linux-gnu + addons: + apt: + packages: + - gcc-multilib + - libgmp-dev:i386 +before_install: mkdir -p `dirname $GUAVA_JAR` +install: if [ ! -f $GUAVA_JAR ]; then wget $GUAVA_URL -O $GUAVA_JAR; fi +before_script: ./autogen.sh +script: + - if [ -n "$HOST" ]; then export USE_HOST="--host=$HOST"; fi + - if [ "x$HOST" = "xi686-linux-gnu" ]; then export CC="$CC -m32"; fi + - ./configure --enable-experimental=$EXPERIMENTAL --enable-endomorphism=$ENDOMORPHISM --with-field=$FIELD --with-bignum=$BIGNUM --with-scalar=$SCALAR --enable-ecmult-static-precomputation=$STATICPRECOMPUTATION --enable-module-ecdh=$ECDH --enable-module-recovery=$RECOVERY $EXTRAFLAGS $USE_HOST && make -j2 $BUILD +os: linux diff --git a/crypto/secp256k1/libsecp256k1/COPYING b/crypto/secp256k1/libsecp256k1/COPYING new file mode 100644 index 0000000..4522a59 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/COPYING @@ -0,0 +1,19 @@ +Copyright (c) 2013 Pieter Wuille + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/crypto/secp256k1/libsecp256k1/Makefile.am b/crypto/secp256k1/libsecp256k1/Makefile.am new file mode 100644 index 0000000..c071fbe --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/Makefile.am @@ -0,0 +1,177 @@ +ACLOCAL_AMFLAGS = -I build-aux/m4 + +lib_LTLIBRARIES = libsecp256k1.la +if USE_JNI +JNI_LIB = libsecp256k1_jni.la +noinst_LTLIBRARIES = $(JNI_LIB) +else +JNI_LIB = +endif +include_HEADERS = include/secp256k1.h +noinst_HEADERS = +noinst_HEADERS += src/scalar.h +noinst_HEADERS += src/scalar_4x64.h +noinst_HEADERS += src/scalar_8x32.h +noinst_HEADERS += src/scalar_low.h +noinst_HEADERS += src/scalar_impl.h +noinst_HEADERS += src/scalar_4x64_impl.h +noinst_HEADERS += src/scalar_8x32_impl.h +noinst_HEADERS += src/scalar_low_impl.h +noinst_HEADERS += src/group.h +noinst_HEADERS += src/group_impl.h +noinst_HEADERS += src/num_gmp.h +noinst_HEADERS += src/num_gmp_impl.h +noinst_HEADERS += src/ecdsa.h +noinst_HEADERS += src/ecdsa_impl.h +noinst_HEADERS += src/eckey.h +noinst_HEADERS += src/eckey_impl.h +noinst_HEADERS += src/ecmult.h +noinst_HEADERS += src/ecmult_impl.h +noinst_HEADERS += src/ecmult_const.h +noinst_HEADERS += src/ecmult_const_impl.h +noinst_HEADERS += src/ecmult_gen.h +noinst_HEADERS += src/ecmult_gen_impl.h +noinst_HEADERS += src/num.h +noinst_HEADERS += src/num_impl.h +noinst_HEADERS += src/field_10x26.h +noinst_HEADERS += src/field_10x26_impl.h +noinst_HEADERS += src/field_5x52.h +noinst_HEADERS += src/field_5x52_impl.h +noinst_HEADERS += src/field_5x52_int128_impl.h +noinst_HEADERS += src/field_5x52_asm_impl.h +noinst_HEADERS += src/java/org_bitcoin_NativeSecp256k1.h +noinst_HEADERS += src/java/org_bitcoin_Secp256k1Context.h +noinst_HEADERS += src/util.h +noinst_HEADERS += src/testrand.h +noinst_HEADERS += src/testrand_impl.h +noinst_HEADERS += src/hash.h +noinst_HEADERS += src/hash_impl.h +noinst_HEADERS += src/field.h +noinst_HEADERS += src/field_impl.h +noinst_HEADERS += src/bench.h +noinst_HEADERS += contrib/lax_der_parsing.h +noinst_HEADERS += contrib/lax_der_parsing.c +noinst_HEADERS += contrib/lax_der_privatekey_parsing.h +noinst_HEADERS += contrib/lax_der_privatekey_parsing.c + +if USE_EXTERNAL_ASM +COMMON_LIB = libsecp256k1_common.la +noinst_LTLIBRARIES = $(COMMON_LIB) +else +COMMON_LIB = +endif + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libsecp256k1.pc + +if USE_EXTERNAL_ASM +if USE_ASM_ARM +libsecp256k1_common_la_SOURCES = src/asm/field_10x26_arm.s +endif +endif + +libsecp256k1_la_SOURCES = src/secp256k1.c +libsecp256k1_la_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/include -I$(top_srcdir)/src $(SECP_INCLUDES) +libsecp256k1_la_LIBADD = $(JNI_LIB) $(SECP_LIBS) $(COMMON_LIB) + +libsecp256k1_jni_la_SOURCES = src/java/org_bitcoin_NativeSecp256k1.c src/java/org_bitcoin_Secp256k1Context.c +libsecp256k1_jni_la_CPPFLAGS = -DSECP256K1_BUILD $(JNI_INCLUDES) + +noinst_PROGRAMS = +if USE_BENCHMARK +noinst_PROGRAMS += bench_verify bench_sign bench_internal +bench_verify_SOURCES = src/bench_verify.c +bench_verify_LDADD = libsecp256k1.la $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) +bench_sign_SOURCES = src/bench_sign.c +bench_sign_LDADD = libsecp256k1.la $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) +bench_internal_SOURCES = src/bench_internal.c +bench_internal_LDADD = $(SECP_LIBS) $(COMMON_LIB) +bench_internal_CPPFLAGS = -DSECP256K1_BUILD $(SECP_INCLUDES) +endif + +TESTS = +if USE_TESTS +noinst_PROGRAMS += tests +tests_SOURCES = src/tests.c +tests_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/src -I$(top_srcdir)/include $(SECP_INCLUDES) $(SECP_TEST_INCLUDES) +if !ENABLE_COVERAGE +tests_CPPFLAGS += -DVERIFY +endif +tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) +tests_LDFLAGS = -static +TESTS += tests +endif + +if USE_EXHAUSTIVE_TESTS +noinst_PROGRAMS += exhaustive_tests +exhaustive_tests_SOURCES = src/tests_exhaustive.c +exhaustive_tests_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/src $(SECP_INCLUDES) +if !ENABLE_COVERAGE +exhaustive_tests_CPPFLAGS += -DVERIFY +endif +exhaustive_tests_LDADD = $(SECP_LIBS) +exhaustive_tests_LDFLAGS = -static +TESTS += exhaustive_tests +endif + +JAVAROOT=src/java +JAVAORG=org/bitcoin +JAVA_GUAVA=$(srcdir)/$(JAVAROOT)/guava/guava-18.0.jar +CLASSPATH_ENV=CLASSPATH=$(JAVA_GUAVA) +JAVA_FILES= \ + $(JAVAROOT)/$(JAVAORG)/NativeSecp256k1.java \ + $(JAVAROOT)/$(JAVAORG)/NativeSecp256k1Test.java \ + $(JAVAROOT)/$(JAVAORG)/NativeSecp256k1Util.java \ + $(JAVAROOT)/$(JAVAORG)/Secp256k1Context.java + +if USE_JNI + +$(JAVA_GUAVA): + @echo Guava is missing. Fetch it via: \ + wget https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar -O $(@) + @false + +.stamp-java: $(JAVA_FILES) + @echo Compiling $^ + $(AM_V_at)$(CLASSPATH_ENV) javac $^ + @touch $@ + +if USE_TESTS + +check-java: libsecp256k1.la $(JAVA_GUAVA) .stamp-java + $(AM_V_at)java -Djava.library.path="./:./src:./src/.libs:.libs/" -cp "$(JAVA_GUAVA):$(JAVAROOT)" $(JAVAORG)/NativeSecp256k1Test + +endif +endif + +if USE_ECMULT_STATIC_PRECOMPUTATION +CPPFLAGS_FOR_BUILD +=-I$(top_srcdir) +CFLAGS_FOR_BUILD += -Wall -Wextra -Wno-unused-function + +gen_context_OBJECTS = gen_context.o +gen_context_BIN = gen_context$(BUILD_EXEEXT) +gen_%.o: src/gen_%.c + $(CC_FOR_BUILD) $(CPPFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD) -c $< -o $@ + +$(gen_context_BIN): $(gen_context_OBJECTS) + $(CC_FOR_BUILD) $^ -o $@ + +$(libsecp256k1_la_OBJECTS): src/ecmult_static_context.h +$(tests_OBJECTS): src/ecmult_static_context.h +$(bench_internal_OBJECTS): src/ecmult_static_context.h + +src/ecmult_static_context.h: $(gen_context_BIN) + ./$(gen_context_BIN) + +CLEANFILES = $(gen_context_BIN) src/ecmult_static_context.h $(JAVAROOT)/$(JAVAORG)/*.class .stamp-java +endif + +EXTRA_DIST = autogen.sh src/gen_context.c src/basic-config.h $(JAVA_FILES) + +if ENABLE_MODULE_ECDH +include src/modules/ecdh/Makefile.am.include +endif + +if ENABLE_MODULE_RECOVERY +include src/modules/recovery/Makefile.am.include +endif diff --git a/crypto/secp256k1/libsecp256k1/README.md b/crypto/secp256k1/libsecp256k1/README.md new file mode 100644 index 0000000..8cd344e --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/README.md @@ -0,0 +1,61 @@ +libsecp256k1 +============ + +[![Build Status](https://travis-ci.org/bitcoin-core/secp256k1.svg?branch=master)](https://travis-ci.org/bitcoin-core/secp256k1) + +Optimized C library for EC operations on curve secp256k1. + +This library is a work in progress and is being used to research best practices. Use at your own risk. + +Features: +* secp256k1 ECDSA signing/verification and key generation. +* Adding/multiplying private/public keys. +* Serialization/parsing of private keys, public keys, signatures. +* Constant time, constant memory access signing and pubkey generation. +* Derandomized DSA (via RFC6979 or with a caller provided function.) +* Very efficient implementation. + +Implementation details +---------------------- + +* General + * No runtime heap allocation. + * Extensive testing infrastructure. + * Structured to facilitate review and analysis. + * Intended to be portable to any system with a C89 compiler and uint64_t support. + * Expose only higher level interfaces to minimize the API surface and improve application security. ("Be difficult to use insecurely.") +* Field operations + * Optimized implementation of arithmetic modulo the curve's field size (2^256 - 0x1000003D1). + * Using 5 52-bit limbs (including hand-optimized assembly for x86_64, by Diederik Huys). + * Using 10 26-bit limbs. + * Field inverses and square roots using a sliding window over blocks of 1s (by Peter Dettman). +* Scalar operations + * Optimized implementation without data-dependent branches of arithmetic modulo the curve's order. + * Using 4 64-bit limbs (relying on __int128 support in the compiler). + * Using 8 32-bit limbs. +* Group operations + * Point addition formula specifically simplified for the curve equation (y^2 = x^3 + 7). + * Use addition between points in Jacobian and affine coordinates where possible. + * Use a unified addition/doubling formula where necessary to avoid data-dependent branches. + * Point/x comparison without a field inversion by comparison in the Jacobian coordinate space. +* Point multiplication for verification (a*P + b*G). + * Use wNAF notation for point multiplicands. + * Use a much larger window for multiples of G, using precomputed multiples. + * Use Shamir's trick to do the multiplication with the public key and the generator simultaneously. + * Optionally (off by default) use secp256k1's efficiently-computable endomorphism to split the P multiplicand into 2 half-sized ones. +* Point multiplication for signing + * Use a precomputed table of multiples of powers of 16 multiplied with the generator, so general multiplication becomes a series of additions. + * Access the table with branch-free conditional moves so memory access is uniform. + * No data-dependent branches + * The precomputed tables add and eventually subtract points for which no known scalar (private key) is known, preventing even an attacker with control over the private key used to control the data internally. + +Build steps +----------- + +libsecp256k1 is built using autotools: + + $ ./autogen.sh + $ ./configure + $ make + $ ./tests + $ sudo make install # optional diff --git a/crypto/secp256k1/libsecp256k1/TODO b/crypto/secp256k1/libsecp256k1/TODO new file mode 100644 index 0000000..a300e1c --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/TODO @@ -0,0 +1,3 @@ +* Unit tests for fieldelem/groupelem, including ones intended to + trigger fieldelem's boundary cases. +* Complete constant-time operations for signing/keygen diff --git a/crypto/secp256k1/libsecp256k1/autogen.sh b/crypto/secp256k1/libsecp256k1/autogen.sh new file mode 100755 index 0000000..65286b9 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/autogen.sh @@ -0,0 +1,3 @@ +#!/bin/sh +set -e +autoreconf -if --warnings=all diff --git a/crypto/secp256k1/libsecp256k1/build-aux/m4/ax_jni_include_dir.m4 b/crypto/secp256k1/libsecp256k1/build-aux/m4/ax_jni_include_dir.m4 new file mode 100644 index 0000000..1fc3627 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/build-aux/m4/ax_jni_include_dir.m4 @@ -0,0 +1,140 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_jni_include_dir.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_JNI_INCLUDE_DIR +# +# DESCRIPTION +# +# AX_JNI_INCLUDE_DIR finds include directories needed for compiling +# programs using the JNI interface. +# +# JNI include directories are usually in the Java distribution. This is +# deduced from the value of $JAVA_HOME, $JAVAC, or the path to "javac", in +# that order. When this macro completes, a list of directories is left in +# the variable JNI_INCLUDE_DIRS. +# +# Example usage follows: +# +# AX_JNI_INCLUDE_DIR +# +# for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS +# do +# CPPFLAGS="$CPPFLAGS -I$JNI_INCLUDE_DIR" +# done +# +# If you want to force a specific compiler: +# +# - at the configure.in level, set JAVAC=yourcompiler before calling +# AX_JNI_INCLUDE_DIR +# +# - at the configure level, setenv JAVAC +# +# Note: This macro can work with the autoconf M4 macros for Java programs. +# This particular macro is not part of the original set of macros. +# +# LICENSE +# +# Copyright (c) 2008 Don Anderson +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 10 + +AU_ALIAS([AC_JNI_INCLUDE_DIR], [AX_JNI_INCLUDE_DIR]) +AC_DEFUN([AX_JNI_INCLUDE_DIR],[ + +JNI_INCLUDE_DIRS="" + +if test "x$JAVA_HOME" != x; then + _JTOPDIR="$JAVA_HOME" +else + if test "x$JAVAC" = x; then + JAVAC=javac + fi + AC_PATH_PROG([_ACJNI_JAVAC], [$JAVAC], [no]) + if test "x$_ACJNI_JAVAC" = xno; then + AC_MSG_WARN([cannot find JDK; try setting \$JAVAC or \$JAVA_HOME]) + fi + _ACJNI_FOLLOW_SYMLINKS("$_ACJNI_JAVAC") + _JTOPDIR=`echo "$_ACJNI_FOLLOWED" | sed -e 's://*:/:g' -e 's:/[[^/]]*$::'` +fi + +case "$host_os" in + darwin*) _JTOPDIR=`echo "$_JTOPDIR" | sed -e 's:/[[^/]]*$::'` + _JINC="$_JTOPDIR/Headers";; + *) _JINC="$_JTOPDIR/include";; +esac +_AS_ECHO_LOG([_JTOPDIR=$_JTOPDIR]) +_AS_ECHO_LOG([_JINC=$_JINC]) + +# On Mac OS X 10.6.4, jni.h is a symlink: +# /System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/jni.h +# -> ../../CurrentJDK/Headers/jni.h. + +AC_CACHE_CHECK(jni headers, ac_cv_jni_header_path, +[ +if test -f "$_JINC/jni.h"; then + ac_cv_jni_header_path="$_JINC" + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path" +else + _JTOPDIR=`echo "$_JTOPDIR" | sed -e 's:/[[^/]]*$::'` + if test -f "$_JTOPDIR/include/jni.h"; then + ac_cv_jni_header_path="$_JTOPDIR/include" + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path" + else + ac_cv_jni_header_path=none + fi +fi +]) + + + +# get the likely subdirectories for system specific java includes +case "$host_os" in +bsdi*) _JNI_INC_SUBDIRS="bsdos";; +darwin*) _JNI_INC_SUBDIRS="darwin";; +freebsd*) _JNI_INC_SUBDIRS="freebsd";; +linux*) _JNI_INC_SUBDIRS="linux genunix";; +osf*) _JNI_INC_SUBDIRS="alpha";; +solaris*) _JNI_INC_SUBDIRS="solaris";; +mingw*) _JNI_INC_SUBDIRS="win32";; +cygwin*) _JNI_INC_SUBDIRS="win32";; +*) _JNI_INC_SUBDIRS="genunix";; +esac + +if test "x$ac_cv_jni_header_path" != "xnone"; then + # add any subdirectories that are present + for JINCSUBDIR in $_JNI_INC_SUBDIRS + do + if test -d "$_JTOPDIR/include/$JINCSUBDIR"; then + JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $_JTOPDIR/include/$JINCSUBDIR" + fi + done +fi +]) + +# _ACJNI_FOLLOW_SYMLINKS +# Follows symbolic links on , +# finally setting variable _ACJNI_FOLLOWED +# ---------------------------------------- +AC_DEFUN([_ACJNI_FOLLOW_SYMLINKS],[ +# find the include directory relative to the javac executable +_cur="$1" +while ls -ld "$_cur" 2>/dev/null | grep " -> " >/dev/null; do + AC_MSG_CHECKING([symlink for $_cur]) + _slink=`ls -ld "$_cur" | sed 's/.* -> //'` + case "$_slink" in + /*) _cur="$_slink";; + # 'X' avoids triggering unwanted echo options. + *) _cur=`echo "X$_cur" | sed -e 's/^X//' -e 's:[[^/]]*$::'`"$_slink";; + esac + AC_MSG_RESULT([$_cur]) +done +_ACJNI_FOLLOWED="$_cur" +])# _ACJNI diff --git a/crypto/secp256k1/libsecp256k1/build-aux/m4/ax_prog_cc_for_build.m4 b/crypto/secp256k1/libsecp256k1/build-aux/m4/ax_prog_cc_for_build.m4 new file mode 100644 index 0000000..77fd346 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/build-aux/m4/ax_prog_cc_for_build.m4 @@ -0,0 +1,125 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_prog_cc_for_build.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_CC_FOR_BUILD +# +# DESCRIPTION +# +# This macro searches for a C compiler that generates native executables, +# that is a C compiler that surely is not a cross-compiler. This can be +# useful if you have to generate source code at compile-time like for +# example GCC does. +# +# The macro sets the CC_FOR_BUILD and CPP_FOR_BUILD macros to anything +# needed to compile or link (CC_FOR_BUILD) and preprocess (CPP_FOR_BUILD). +# The value of these variables can be overridden by the user by specifying +# a compiler with an environment variable (like you do for standard CC). +# +# It also sets BUILD_EXEEXT and BUILD_OBJEXT to the executable and object +# file extensions for the build platform, and GCC_FOR_BUILD to `yes' if +# the compiler we found is GCC. All these variables but GCC_FOR_BUILD are +# substituted in the Makefile. +# +# LICENSE +# +# Copyright (c) 2008 Paolo Bonzini +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 8 + +AU_ALIAS([AC_PROG_CC_FOR_BUILD], [AX_PROG_CC_FOR_BUILD]) +AC_DEFUN([AX_PROG_CC_FOR_BUILD], [dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_CPP])dnl +AC_REQUIRE([AC_EXEEXT])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl + +dnl Use the standard macros, but make them use other variable names +dnl +pushdef([ac_cv_prog_CPP], ac_cv_build_prog_CPP)dnl +pushdef([ac_cv_prog_gcc], ac_cv_build_prog_gcc)dnl +pushdef([ac_cv_prog_cc_works], ac_cv_build_prog_cc_works)dnl +pushdef([ac_cv_prog_cc_cross], ac_cv_build_prog_cc_cross)dnl +pushdef([ac_cv_prog_cc_g], ac_cv_build_prog_cc_g)dnl +pushdef([ac_cv_exeext], ac_cv_build_exeext)dnl +pushdef([ac_cv_objext], ac_cv_build_objext)dnl +pushdef([ac_exeext], ac_build_exeext)dnl +pushdef([ac_objext], ac_build_objext)dnl +pushdef([CC], CC_FOR_BUILD)dnl +pushdef([CPP], CPP_FOR_BUILD)dnl +pushdef([CFLAGS], CFLAGS_FOR_BUILD)dnl +pushdef([CPPFLAGS], CPPFLAGS_FOR_BUILD)dnl +pushdef([LDFLAGS], LDFLAGS_FOR_BUILD)dnl +pushdef([host], build)dnl +pushdef([host_alias], build_alias)dnl +pushdef([host_cpu], build_cpu)dnl +pushdef([host_vendor], build_vendor)dnl +pushdef([host_os], build_os)dnl +pushdef([ac_cv_host], ac_cv_build)dnl +pushdef([ac_cv_host_alias], ac_cv_build_alias)dnl +pushdef([ac_cv_host_cpu], ac_cv_build_cpu)dnl +pushdef([ac_cv_host_vendor], ac_cv_build_vendor)dnl +pushdef([ac_cv_host_os], ac_cv_build_os)dnl +pushdef([ac_cpp], ac_build_cpp)dnl +pushdef([ac_compile], ac_build_compile)dnl +pushdef([ac_link], ac_build_link)dnl + +save_cross_compiling=$cross_compiling +save_ac_tool_prefix=$ac_tool_prefix +cross_compiling=no +ac_tool_prefix= + +AC_PROG_CC +AC_PROG_CPP +AC_EXEEXT + +ac_tool_prefix=$save_ac_tool_prefix +cross_compiling=$save_cross_compiling + +dnl Restore the old definitions +dnl +popdef([ac_link])dnl +popdef([ac_compile])dnl +popdef([ac_cpp])dnl +popdef([ac_cv_host_os])dnl +popdef([ac_cv_host_vendor])dnl +popdef([ac_cv_host_cpu])dnl +popdef([ac_cv_host_alias])dnl +popdef([ac_cv_host])dnl +popdef([host_os])dnl +popdef([host_vendor])dnl +popdef([host_cpu])dnl +popdef([host_alias])dnl +popdef([host])dnl +popdef([LDFLAGS])dnl +popdef([CPPFLAGS])dnl +popdef([CFLAGS])dnl +popdef([CPP])dnl +popdef([CC])dnl +popdef([ac_objext])dnl +popdef([ac_exeext])dnl +popdef([ac_cv_objext])dnl +popdef([ac_cv_exeext])dnl +popdef([ac_cv_prog_cc_g])dnl +popdef([ac_cv_prog_cc_cross])dnl +popdef([ac_cv_prog_cc_works])dnl +popdef([ac_cv_prog_gcc])dnl +popdef([ac_cv_prog_CPP])dnl + +dnl Finally, set Makefile variables +dnl +BUILD_EXEEXT=$ac_build_exeext +BUILD_OBJEXT=$ac_build_objext +AC_SUBST(BUILD_EXEEXT)dnl +AC_SUBST(BUILD_OBJEXT)dnl +AC_SUBST([CFLAGS_FOR_BUILD])dnl +AC_SUBST([CPPFLAGS_FOR_BUILD])dnl +AC_SUBST([LDFLAGS_FOR_BUILD])dnl +]) diff --git a/crypto/secp256k1/libsecp256k1/build-aux/m4/bitcoin_secp.m4 b/crypto/secp256k1/libsecp256k1/build-aux/m4/bitcoin_secp.m4 new file mode 100644 index 0000000..b74acb8 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/build-aux/m4/bitcoin_secp.m4 @@ -0,0 +1,69 @@ +dnl libsecp25k1 helper checks +AC_DEFUN([SECP_INT128_CHECK],[ +has_int128=$ac_cv_type___int128 +]) + +dnl escape "$0x" below using the m4 quadrigaph @S|@, and escape it again with a \ for the shell. +AC_DEFUN([SECP_64BIT_ASM_CHECK],[ +AC_MSG_CHECKING(for x86_64 assembly availability) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include ]],[[ + uint64_t a = 11, tmp; + __asm__ __volatile__("movq \@S|@0x100000000,%1; mulq %%rsi" : "+a"(a) : "S"(tmp) : "cc", "%rdx"); + ]])],[has_64bit_asm=yes],[has_64bit_asm=no]) +AC_MSG_RESULT([$has_64bit_asm]) +]) + +dnl +AC_DEFUN([SECP_OPENSSL_CHECK],[ + has_libcrypto=no + m4_ifdef([PKG_CHECK_MODULES],[ + PKG_CHECK_MODULES([CRYPTO], [libcrypto], [has_libcrypto=yes],[has_libcrypto=no]) + if test x"$has_libcrypto" = x"yes"; then + TEMP_LIBS="$LIBS" + LIBS="$LIBS $CRYPTO_LIBS" + AC_CHECK_LIB(crypto, main,[AC_DEFINE(HAVE_LIBCRYPTO,1,[Define this symbol if libcrypto is installed])],[has_libcrypto=no]) + LIBS="$TEMP_LIBS" + fi + ]) + if test x$has_libcrypto = xno; then + AC_CHECK_HEADER(openssl/crypto.h,[ + AC_CHECK_LIB(crypto, main,[ + has_libcrypto=yes + CRYPTO_LIBS=-lcrypto + AC_DEFINE(HAVE_LIBCRYPTO,1,[Define this symbol if libcrypto is installed]) + ]) + ]) + LIBS= + fi +if test x"$has_libcrypto" = x"yes" && test x"$has_openssl_ec" = x; then + AC_MSG_CHECKING(for EC functions in libcrypto) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include + #include + #include ]],[[ + EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_secp256k1); + ECDSA_sign(0, NULL, 0, NULL, NULL, eckey); + ECDSA_verify(0, NULL, 0, NULL, 0, eckey); + EC_KEY_free(eckey); + ECDSA_SIG *sig_openssl; + sig_openssl = ECDSA_SIG_new(); + (void)sig_openssl->r; + ECDSA_SIG_free(sig_openssl); + ]])],[has_openssl_ec=yes],[has_openssl_ec=no]) + AC_MSG_RESULT([$has_openssl_ec]) +fi +]) + +dnl +AC_DEFUN([SECP_GMP_CHECK],[ +if test x"$has_gmp" != x"yes"; then + CPPFLAGS_TEMP="$CPPFLAGS" + CPPFLAGS="$GMP_CPPFLAGS $CPPFLAGS" + LIBS_TEMP="$LIBS" + LIBS="$GMP_LIBS $LIBS" + AC_CHECK_HEADER(gmp.h,[AC_CHECK_LIB(gmp, __gmpz_init,[has_gmp=yes; GMP_LIBS="$GMP_LIBS -lgmp"; AC_DEFINE(HAVE_LIBGMP,1,[Define this symbol if libgmp is installed])])]) + CPPFLAGS="$CPPFLAGS_TEMP" + LIBS="$LIBS_TEMP" +fi +]) diff --git a/crypto/secp256k1/libsecp256k1/configure.ac b/crypto/secp256k1/libsecp256k1/configure.ac new file mode 100644 index 0000000..e5fcbcb --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/configure.ac @@ -0,0 +1,493 @@ +AC_PREREQ([2.60]) +AC_INIT([libsecp256k1],[0.1]) +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([build-aux/m4]) +AC_CANONICAL_HOST +AH_TOP([#ifndef LIBSECP256K1_CONFIG_H]) +AH_TOP([#define LIBSECP256K1_CONFIG_H]) +AH_BOTTOM([#endif /*LIBSECP256K1_CONFIG_H*/]) +AM_INIT_AUTOMAKE([foreign subdir-objects]) +LT_INIT + +dnl make the compilation flags quiet unless V=1 is used +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +PKG_PROG_PKG_CONFIG + +AC_PATH_TOOL(AR, ar) +AC_PATH_TOOL(RANLIB, ranlib) +AC_PATH_TOOL(STRIP, strip) +AX_PROG_CC_FOR_BUILD + +if test "x$CFLAGS" = "x"; then + CFLAGS="-g" +fi + +AM_PROG_CC_C_O + +AC_PROG_CC_C89 +if test x"$ac_cv_prog_cc_c89" = x"no"; then + AC_MSG_ERROR([c89 compiler support required]) +fi +AM_PROG_AS + +case $host_os in + *darwin*) + if test x$cross_compiling != xyes; then + AC_PATH_PROG([BREW],brew,) + if test x$BREW != x; then + dnl These Homebrew packages may be keg-only, meaning that they won't be found + dnl in expected paths because they may conflict with system files. Ask + dnl Homebrew where each one is located, then adjust paths accordingly. + + openssl_prefix=`$BREW --prefix openssl 2>/dev/null` + gmp_prefix=`$BREW --prefix gmp 2>/dev/null` + if test x$openssl_prefix != x; then + PKG_CONFIG_PATH="$openssl_prefix/lib/pkgconfig:$PKG_CONFIG_PATH" + export PKG_CONFIG_PATH + fi + if test x$gmp_prefix != x; then + GMP_CPPFLAGS="-I$gmp_prefix/include" + GMP_LIBS="-L$gmp_prefix/lib" + fi + else + AC_PATH_PROG([PORT],port,) + dnl if homebrew isn't installed and macports is, add the macports default paths + dnl as a last resort. + if test x$PORT != x; then + CPPFLAGS="$CPPFLAGS -isystem /opt/local/include" + LDFLAGS="$LDFLAGS -L/opt/local/lib" + fi + fi + fi + ;; +esac + +CFLAGS="$CFLAGS -W" + +warn_CFLAGS="-std=c89 -pedantic -Wall -Wextra -Wcast-align -Wnested-externs -Wshadow -Wstrict-prototypes -Wno-unused-function -Wno-long-long -Wno-overlength-strings" +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $warn_CFLAGS" +AC_MSG_CHECKING([if ${CC} supports ${warn_CFLAGS}]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([[char foo;]])], + [ AC_MSG_RESULT([yes]) ], + [ AC_MSG_RESULT([no]) + CFLAGS="$saved_CFLAGS" + ]) + +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden" +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([[char foo;]])], + [ AC_MSG_RESULT([yes]) ], + [ AC_MSG_RESULT([no]) + CFLAGS="$saved_CFLAGS" + ]) + +AC_ARG_ENABLE(benchmark, + AS_HELP_STRING([--enable-benchmark],[compile benchmark (default is no)]), + [use_benchmark=$enableval], + [use_benchmark=no]) + +AC_ARG_ENABLE(coverage, + AS_HELP_STRING([--enable-coverage],[enable compiler flags to support kcov coverage analysis]), + [enable_coverage=$enableval], + [enable_coverage=no]) + +AC_ARG_ENABLE(tests, + AS_HELP_STRING([--enable-tests],[compile tests (default is yes)]), + [use_tests=$enableval], + [use_tests=yes]) + +AC_ARG_ENABLE(openssl_tests, + AS_HELP_STRING([--enable-openssl-tests],[enable OpenSSL tests, if OpenSSL is available (default is auto)]), + [enable_openssl_tests=$enableval], + [enable_openssl_tests=auto]) + +AC_ARG_ENABLE(experimental, + AS_HELP_STRING([--enable-experimental],[allow experimental configure options (default is no)]), + [use_experimental=$enableval], + [use_experimental=no]) + +AC_ARG_ENABLE(exhaustive_tests, + AS_HELP_STRING([--enable-exhaustive-tests],[compile exhaustive tests (default is yes)]), + [use_exhaustive_tests=$enableval], + [use_exhaustive_tests=yes]) + +AC_ARG_ENABLE(endomorphism, + AS_HELP_STRING([--enable-endomorphism],[enable endomorphism (default is no)]), + [use_endomorphism=$enableval], + [use_endomorphism=no]) + +AC_ARG_ENABLE(ecmult_static_precomputation, + AS_HELP_STRING([--enable-ecmult-static-precomputation],[enable precomputed ecmult table for signing (default is yes)]), + [use_ecmult_static_precomputation=$enableval], + [use_ecmult_static_precomputation=auto]) + +AC_ARG_ENABLE(module_ecdh, + AS_HELP_STRING([--enable-module-ecdh],[enable ECDH shared secret computation (experimental)]), + [enable_module_ecdh=$enableval], + [enable_module_ecdh=no]) + +AC_ARG_ENABLE(module_recovery, + AS_HELP_STRING([--enable-module-recovery],[enable ECDSA pubkey recovery module (default is no)]), + [enable_module_recovery=$enableval], + [enable_module_recovery=no]) + +AC_ARG_ENABLE(jni, + AS_HELP_STRING([--enable-jni],[enable libsecp256k1_jni (default is auto)]), + [use_jni=$enableval], + [use_jni=auto]) + +AC_ARG_WITH([field], [AS_HELP_STRING([--with-field=64bit|32bit|auto], +[Specify Field Implementation. Default is auto])],[req_field=$withval], [req_field=auto]) + +AC_ARG_WITH([bignum], [AS_HELP_STRING([--with-bignum=gmp|no|auto], +[Specify Bignum Implementation. Default is auto])],[req_bignum=$withval], [req_bignum=auto]) + +AC_ARG_WITH([scalar], [AS_HELP_STRING([--with-scalar=64bit|32bit|auto], +[Specify scalar implementation. Default is auto])],[req_scalar=$withval], [req_scalar=auto]) + +AC_ARG_WITH([asm], [AS_HELP_STRING([--with-asm=x86_64|arm|no|auto] +[Specify assembly optimizations to use. Default is auto (experimental: arm)])],[req_asm=$withval], [req_asm=auto]) + +AC_CHECK_TYPES([__int128]) + +AC_MSG_CHECKING([for __builtin_expect]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([[void myfunc() {__builtin_expect(0,0);}]])], + [ AC_MSG_RESULT([yes]);AC_DEFINE(HAVE_BUILTIN_EXPECT,1,[Define this symbol if __builtin_expect is available]) ], + [ AC_MSG_RESULT([no]) + ]) + +if test x"$enable_coverage" = x"yes"; then + AC_DEFINE(COVERAGE, 1, [Define this symbol to compile out all VERIFY code]) + CFLAGS="$CFLAGS -O0 --coverage" + LDFLAGS="--coverage" +else + CFLAGS="$CFLAGS -O3" +fi + +if test x"$use_ecmult_static_precomputation" != x"no"; then + save_cross_compiling=$cross_compiling + cross_compiling=no + TEMP_CC="$CC" + CC="$CC_FOR_BUILD" + AC_MSG_CHECKING([native compiler: ${CC_FOR_BUILD}]) + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([], [return 0])], + [working_native_cc=yes], + [working_native_cc=no],[dnl]) + CC="$TEMP_CC" + cross_compiling=$save_cross_compiling + + if test x"$working_native_cc" = x"no"; then + set_precomp=no + if test x"$use_ecmult_static_precomputation" = x"yes"; then + AC_MSG_ERROR([${CC_FOR_BUILD} does not produce working binaries. Please set CC_FOR_BUILD]) + else + AC_MSG_RESULT([${CC_FOR_BUILD} does not produce working binaries. Please set CC_FOR_BUILD]) + fi + else + AC_MSG_RESULT([ok]) + set_precomp=yes + fi +else + set_precomp=no +fi + +if test x"$req_asm" = x"auto"; then + SECP_64BIT_ASM_CHECK + if test x"$has_64bit_asm" = x"yes"; then + set_asm=x86_64 + fi + if test x"$set_asm" = x; then + set_asm=no + fi +else + set_asm=$req_asm + case $set_asm in + x86_64) + SECP_64BIT_ASM_CHECK + if test x"$has_64bit_asm" != x"yes"; then + AC_MSG_ERROR([x86_64 assembly optimization requested but not available]) + fi + ;; + arm) + ;; + no) + ;; + *) + AC_MSG_ERROR([invalid assembly optimization selection]) + ;; + esac +fi + +if test x"$req_field" = x"auto"; then + if test x"set_asm" = x"x86_64"; then + set_field=64bit + fi + if test x"$set_field" = x; then + SECP_INT128_CHECK + if test x"$has_int128" = x"yes"; then + set_field=64bit + fi + fi + if test x"$set_field" = x; then + set_field=32bit + fi +else + set_field=$req_field + case $set_field in + 64bit) + if test x"$set_asm" != x"x86_64"; then + SECP_INT128_CHECK + if test x"$has_int128" != x"yes"; then + AC_MSG_ERROR([64bit field explicitly requested but neither __int128 support or x86_64 assembly available]) + fi + fi + ;; + 32bit) + ;; + *) + AC_MSG_ERROR([invalid field implementation selection]) + ;; + esac +fi + +if test x"$req_scalar" = x"auto"; then + SECP_INT128_CHECK + if test x"$has_int128" = x"yes"; then + set_scalar=64bit + fi + if test x"$set_scalar" = x; then + set_scalar=32bit + fi +else + set_scalar=$req_scalar + case $set_scalar in + 64bit) + SECP_INT128_CHECK + if test x"$has_int128" != x"yes"; then + AC_MSG_ERROR([64bit scalar explicitly requested but __int128 support not available]) + fi + ;; + 32bit) + ;; + *) + AC_MSG_ERROR([invalid scalar implementation selected]) + ;; + esac +fi + +if test x"$req_bignum" = x"auto"; then + SECP_GMP_CHECK + if test x"$has_gmp" = x"yes"; then + set_bignum=gmp + fi + + if test x"$set_bignum" = x; then + set_bignum=no + fi +else + set_bignum=$req_bignum + case $set_bignum in + gmp) + SECP_GMP_CHECK + if test x"$has_gmp" != x"yes"; then + AC_MSG_ERROR([gmp bignum explicitly requested but libgmp not available]) + fi + ;; + no) + ;; + *) + AC_MSG_ERROR([invalid bignum implementation selection]) + ;; + esac +fi + +# select assembly optimization +use_external_asm=no + +case $set_asm in +x86_64) + AC_DEFINE(USE_ASM_X86_64, 1, [Define this symbol to enable x86_64 assembly optimizations]) + ;; +arm) + use_external_asm=yes + ;; +no) + ;; +*) + AC_MSG_ERROR([invalid assembly optimizations]) + ;; +esac + +# select field implementation +case $set_field in +64bit) + AC_DEFINE(USE_FIELD_5X52, 1, [Define this symbol to use the FIELD_5X52 implementation]) + ;; +32bit) + AC_DEFINE(USE_FIELD_10X26, 1, [Define this symbol to use the FIELD_10X26 implementation]) + ;; +*) + AC_MSG_ERROR([invalid field implementation]) + ;; +esac + +# select bignum implementation +case $set_bignum in +gmp) + AC_DEFINE(HAVE_LIBGMP, 1, [Define this symbol if libgmp is installed]) + AC_DEFINE(USE_NUM_GMP, 1, [Define this symbol to use the gmp implementation for num]) + AC_DEFINE(USE_FIELD_INV_NUM, 1, [Define this symbol to use the num-based field inverse implementation]) + AC_DEFINE(USE_SCALAR_INV_NUM, 1, [Define this symbol to use the num-based scalar inverse implementation]) + ;; +no) + AC_DEFINE(USE_NUM_NONE, 1, [Define this symbol to use no num implementation]) + AC_DEFINE(USE_FIELD_INV_BUILTIN, 1, [Define this symbol to use the native field inverse implementation]) + AC_DEFINE(USE_SCALAR_INV_BUILTIN, 1, [Define this symbol to use the native scalar inverse implementation]) + ;; +*) + AC_MSG_ERROR([invalid bignum implementation]) + ;; +esac + +#select scalar implementation +case $set_scalar in +64bit) + AC_DEFINE(USE_SCALAR_4X64, 1, [Define this symbol to use the 4x64 scalar implementation]) + ;; +32bit) + AC_DEFINE(USE_SCALAR_8X32, 1, [Define this symbol to use the 8x32 scalar implementation]) + ;; +*) + AC_MSG_ERROR([invalid scalar implementation]) + ;; +esac + +if test x"$use_tests" = x"yes"; then + SECP_OPENSSL_CHECK + if test x"$has_openssl_ec" = x"yes"; then + if test x"$enable_openssl_tests" != x"no"; then + AC_DEFINE(ENABLE_OPENSSL_TESTS, 1, [Define this symbol if OpenSSL EC functions are available]) + SECP_TEST_INCLUDES="$SSL_CFLAGS $CRYPTO_CFLAGS" + SECP_TEST_LIBS="$CRYPTO_LIBS" + + case $host in + *mingw*) + SECP_TEST_LIBS="$SECP_TEST_LIBS -lgdi32" + ;; + esac + fi + else + if test x"$enable_openssl_tests" = x"yes"; then + AC_MSG_ERROR([OpenSSL tests requested but OpenSSL with EC support is not available]) + fi + fi +else + if test x"$enable_openssl_tests" = x"yes"; then + AC_MSG_ERROR([OpenSSL tests requested but tests are not enabled]) + fi +fi + +if test x"$use_jni" != x"no"; then + AX_JNI_INCLUDE_DIR + have_jni_dependencies=yes + if test x"$enable_module_ecdh" = x"no"; then + have_jni_dependencies=no + fi + if test "x$JNI_INCLUDE_DIRS" = "x"; then + have_jni_dependencies=no + fi + if test "x$have_jni_dependencies" = "xno"; then + if test x"$use_jni" = x"yes"; then + AC_MSG_ERROR([jni support explicitly requested but headers/dependencies were not found. Enable ECDH and try again.]) + fi + AC_MSG_WARN([jni headers/dependencies not found. jni support disabled]) + use_jni=no + else + use_jni=yes + for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS; do + JNI_INCLUDES="$JNI_INCLUDES -I$JNI_INCLUDE_DIR" + done + fi +fi + +if test x"$set_bignum" = x"gmp"; then + SECP_LIBS="$SECP_LIBS $GMP_LIBS" + SECP_INCLUDES="$SECP_INCLUDES $GMP_CPPFLAGS" +fi + +if test x"$use_endomorphism" = x"yes"; then + AC_DEFINE(USE_ENDOMORPHISM, 1, [Define this symbol to use endomorphism optimization]) +fi + +if test x"$set_precomp" = x"yes"; then + AC_DEFINE(USE_ECMULT_STATIC_PRECOMPUTATION, 1, [Define this symbol to use a statically generated ecmult table]) +fi + +if test x"$enable_module_ecdh" = x"yes"; then + AC_DEFINE(ENABLE_MODULE_ECDH, 1, [Define this symbol to enable the ECDH module]) +fi + +if test x"$enable_module_recovery" = x"yes"; then + AC_DEFINE(ENABLE_MODULE_RECOVERY, 1, [Define this symbol to enable the ECDSA pubkey recovery module]) +fi + +AC_C_BIGENDIAN() + +if test x"$use_external_asm" = x"yes"; then + AC_DEFINE(USE_EXTERNAL_ASM, 1, [Define this symbol if an external (non-inline) assembly implementation is used]) +fi + +AC_MSG_NOTICE([Using static precomputation: $set_precomp]) +AC_MSG_NOTICE([Using assembly optimizations: $set_asm]) +AC_MSG_NOTICE([Using field implementation: $set_field]) +AC_MSG_NOTICE([Using bignum implementation: $set_bignum]) +AC_MSG_NOTICE([Using scalar implementation: $set_scalar]) +AC_MSG_NOTICE([Using endomorphism optimizations: $use_endomorphism]) +AC_MSG_NOTICE([Building for coverage analysis: $enable_coverage]) +AC_MSG_NOTICE([Building ECDH module: $enable_module_ecdh]) +AC_MSG_NOTICE([Building ECDSA pubkey recovery module: $enable_module_recovery]) +AC_MSG_NOTICE([Using jni: $use_jni]) + +if test x"$enable_experimental" = x"yes"; then + AC_MSG_NOTICE([******]) + AC_MSG_NOTICE([WARNING: experimental build]) + AC_MSG_NOTICE([Experimental features do not have stable APIs or properties, and may not be safe for production use.]) + AC_MSG_NOTICE([Building ECDH module: $enable_module_ecdh]) + AC_MSG_NOTICE([******]) +else + if test x"$enable_module_ecdh" = x"yes"; then + AC_MSG_ERROR([ECDH module is experimental. Use --enable-experimental to allow.]) + fi + if test x"$set_asm" = x"arm"; then + AC_MSG_ERROR([ARM assembly optimization is experimental. Use --enable-experimental to allow.]) + fi +fi + +AC_CONFIG_HEADERS([src/libsecp256k1-config.h]) +AC_CONFIG_FILES([Makefile libsecp256k1.pc]) +AC_SUBST(JNI_INCLUDES) +AC_SUBST(SECP_INCLUDES) +AC_SUBST(SECP_LIBS) +AC_SUBST(SECP_TEST_LIBS) +AC_SUBST(SECP_TEST_INCLUDES) +AM_CONDITIONAL([ENABLE_COVERAGE], [test x"$enable_coverage" = x"yes"]) +AM_CONDITIONAL([USE_TESTS], [test x"$use_tests" != x"no"]) +AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$use_exhaustive_tests" != x"no"]) +AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"]) +AM_CONDITIONAL([USE_ECMULT_STATIC_PRECOMPUTATION], [test x"$set_precomp" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"]) +AM_CONDITIONAL([USE_JNI], [test x"$use_jni" == x"yes"]) +AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$use_external_asm" = x"yes"]) +AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm"]) + +dnl make sure nothing new is exported so that we don't break the cache +PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH" +unset PKG_CONFIG_PATH +PKG_CONFIG_PATH="$PKGCONFIG_PATH_TEMP" + +AC_OUTPUT diff --git a/crypto/secp256k1/libsecp256k1/contrib/dummy.go b/crypto/secp256k1/libsecp256k1/contrib/dummy.go new file mode 100644 index 0000000..2c94621 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/contrib/dummy.go @@ -0,0 +1,8 @@ +//go:build dummy +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package contrib diff --git a/crypto/secp256k1/libsecp256k1/contrib/lax_der_parsing.c b/crypto/secp256k1/libsecp256k1/contrib/lax_der_parsing.c new file mode 100644 index 0000000..5b141a9 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/contrib/lax_der_parsing.c @@ -0,0 +1,150 @@ +/********************************************************************** + * Copyright (c) 2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include +#include + +#include "lax_der_parsing.h" + +int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen) { + size_t rpos, rlen, spos, slen; + size_t pos = 0; + size_t lenbyte; + unsigned char tmpsig[64] = {0}; + int overflow = 0; + + /* Hack to initialize sig with a correctly-parsed but invalid signature. */ + secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig); + + /* Sequence tag byte */ + if (pos == inputlen || input[pos] != 0x30) { + return 0; + } + pos++; + + /* Sequence length bytes */ + if (pos == inputlen) { + return 0; + } + lenbyte = input[pos++]; + if (lenbyte & 0x80) { + lenbyte -= 0x80; + if (pos + lenbyte > inputlen) { + return 0; + } + pos += lenbyte; + } + + /* Integer tag byte for R */ + if (pos == inputlen || input[pos] != 0x02) { + return 0; + } + pos++; + + /* Integer length for R */ + if (pos == inputlen) { + return 0; + } + lenbyte = input[pos++]; + if (lenbyte & 0x80) { + lenbyte -= 0x80; + if (pos + lenbyte > inputlen) { + return 0; + } + while (lenbyte > 0 && input[pos] == 0) { + pos++; + lenbyte--; + } + if (lenbyte >= sizeof(size_t)) { + return 0; + } + rlen = 0; + while (lenbyte > 0) { + rlen = (rlen << 8) + input[pos]; + pos++; + lenbyte--; + } + } else { + rlen = lenbyte; + } + if (rlen > inputlen - pos) { + return 0; + } + rpos = pos; + pos += rlen; + + /* Integer tag byte for S */ + if (pos == inputlen || input[pos] != 0x02) { + return 0; + } + pos++; + + /* Integer length for S */ + if (pos == inputlen) { + return 0; + } + lenbyte = input[pos++]; + if (lenbyte & 0x80) { + lenbyte -= 0x80; + if (pos + lenbyte > inputlen) { + return 0; + } + while (lenbyte > 0 && input[pos] == 0) { + pos++; + lenbyte--; + } + if (lenbyte >= sizeof(size_t)) { + return 0; + } + slen = 0; + while (lenbyte > 0) { + slen = (slen << 8) + input[pos]; + pos++; + lenbyte--; + } + } else { + slen = lenbyte; + } + if (slen > inputlen - pos) { + return 0; + } + spos = pos; + pos += slen; + + /* Ignore leading zeroes in R */ + while (rlen > 0 && input[rpos] == 0) { + rlen--; + rpos++; + } + /* Copy R value */ + if (rlen > 32) { + overflow = 1; + } else { + memcpy(tmpsig + 32 - rlen, input + rpos, rlen); + } + + /* Ignore leading zeroes in S */ + while (slen > 0 && input[spos] == 0) { + slen--; + spos++; + } + /* Copy S value */ + if (slen > 32) { + overflow = 1; + } else { + memcpy(tmpsig + 64 - slen, input + spos, slen); + } + + if (!overflow) { + overflow = !secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig); + } + if (overflow) { + memset(tmpsig, 0, 64); + secp256k1_ecdsa_signature_parse_compact(ctx, sig, tmpsig); + } + return 1; +} + diff --git a/crypto/secp256k1/libsecp256k1/contrib/lax_der_parsing.h b/crypto/secp256k1/libsecp256k1/contrib/lax_der_parsing.h new file mode 100644 index 0000000..6d27871 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/contrib/lax_der_parsing.h @@ -0,0 +1,91 @@ +/********************************************************************** + * Copyright (c) 2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +/**** + * Please do not link this file directly. It is not part of the libsecp256k1 + * project and does not promise any stability in its API, functionality or + * presence. Projects which use this code should instead copy this header + * and its accompanying .c file directly into their codebase. + ****/ + +/* This file defines a function that parses DER with various errors and + * violations. This is not a part of the library itself, because the allowed + * violations are chosen arbitrarily and do not follow or establish any + * standard. + * + * In many places it matters that different implementations do not only accept + * the same set of valid signatures, but also reject the same set of signatures. + * The only means to accomplish that is by strictly obeying a standard, and not + * accepting anything else. + * + * Nonetheless, sometimes there is a need for compatibility with systems that + * use signatures which do not strictly obey DER. The snippet below shows how + * certain violations are easily supported. You may need to adapt it. + * + * Do not use this for new systems. Use well-defined DER or compact signatures + * instead if you have the choice (see secp256k1_ecdsa_signature_parse_der and + * secp256k1_ecdsa_signature_parse_compact). + * + * The supported violations are: + * - All numbers are parsed as nonnegative integers, even though X.609-0207 + * section 8.3.3 specifies that integers are always encoded as two's + * complement. + * - Integers can have length 0, even though section 8.3.1 says they can't. + * - Integers with overly long padding are accepted, violation section + * 8.3.2. + * - 127-byte long length descriptors are accepted, even though section + * 8.1.3.5.c says that they are not. + * - Trailing garbage data inside or after the signature is ignored. + * - The length descriptor of the sequence is ignored. + * + * Compared to for example OpenSSL, many violations are NOT supported: + * - Using overly long tag descriptors for the sequence or integers inside, + * violating section 8.1.2.2. + * - Encoding primitive integers as constructed values, violating section + * 8.3.1. + */ + +#ifndef _SECP256K1_CONTRIB_LAX_DER_PARSING_H_ +#define _SECP256K1_CONTRIB_LAX_DER_PARSING_H_ + +#include + +# ifdef __cplusplus +extern "C" { +# endif + +/** Parse a signature in "lax DER" format + * + * Returns: 1 when the signature could be parsed, 0 otherwise. + * Args: ctx: a secp256k1 context object + * Out: sig: a pointer to a signature object + * In: input: a pointer to the signature to be parsed + * inputlen: the length of the array pointed to be input + * + * This function will accept any valid DER encoded signature, even if the + * encoded numbers are out of range. In addition, it will accept signatures + * which violate the DER spec in various ways. Its purpose is to allow + * validation of the Bitcoin blockchain, which includes non-DER signatures + * from before the network rules were updated to enforce DER. Note that + * the set of supported violations is a strict subset of what OpenSSL will + * accept. + * + * After the call, sig will always be initialized. If parsing failed or the + * encoded numbers are out of range, signature validation with it is + * guaranteed to fail for every message and public key. + */ +int ecdsa_signature_parse_der_lax( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature* sig, + const unsigned char *input, + size_t inputlen +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/crypto/secp256k1/libsecp256k1/contrib/lax_der_privatekey_parsing.c b/crypto/secp256k1/libsecp256k1/contrib/lax_der_privatekey_parsing.c new file mode 100644 index 0000000..c2e63b4 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/contrib/lax_der_privatekey_parsing.c @@ -0,0 +1,113 @@ +/********************************************************************** + * Copyright (c) 2014, 2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include +#include + +#include "lax_der_privatekey_parsing.h" + +int ec_privkey_import_der(const secp256k1_context* ctx, unsigned char *out32, const unsigned char *privkey, size_t privkeylen) { + const unsigned char *end = privkey + privkeylen; + int lenb = 0; + int len = 0; + memset(out32, 0, 32); + /* sequence header */ + if (end < privkey+1 || *privkey != 0x30) { + return 0; + } + privkey++; + /* sequence length constructor */ + if (end < privkey+1 || !(*privkey & 0x80)) { + return 0; + } + lenb = *privkey & ~0x80; privkey++; + if (lenb < 1 || lenb > 2) { + return 0; + } + if (end < privkey+lenb) { + return 0; + } + /* sequence length */ + len = privkey[lenb-1] | (lenb > 1 ? privkey[lenb-2] << 8 : 0); + privkey += lenb; + if (end < privkey+len) { + return 0; + } + /* sequence element 0: version number (=1) */ + if (end < privkey+3 || privkey[0] != 0x02 || privkey[1] != 0x01 || privkey[2] != 0x01) { + return 0; + } + privkey += 3; + /* sequence element 1: octet string, up to 32 bytes */ + if (end < privkey+2 || privkey[0] != 0x04 || privkey[1] > 0x20 || end < privkey+2+privkey[1]) { + return 0; + } + memcpy(out32 + 32 - privkey[1], privkey + 2, privkey[1]); + if (!secp256k1_ec_seckey_verify(ctx, out32)) { + memset(out32, 0, 32); + return 0; + } + return 1; +} + +int ec_privkey_export_der(const secp256k1_context *ctx, unsigned char *privkey, size_t *privkeylen, const unsigned char *key32, int compressed) { + secp256k1_pubkey pubkey; + size_t pubkeylen = 0; + if (!secp256k1_ec_pubkey_create(ctx, &pubkey, key32)) { + *privkeylen = 0; + return 0; + } + if (compressed) { + static const unsigned char begin[] = { + 0x30,0x81,0xD3,0x02,0x01,0x01,0x04,0x20 + }; + static const unsigned char middle[] = { + 0xA0,0x81,0x85,0x30,0x81,0x82,0x02,0x01,0x01,0x30,0x2C,0x06,0x07,0x2A,0x86,0x48, + 0xCE,0x3D,0x01,0x01,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFE,0xFF,0xFF,0xFC,0x2F,0x30,0x06,0x04,0x01,0x00,0x04,0x01,0x07,0x04, + 0x21,0x02,0x79,0xBE,0x66,0x7E,0xF9,0xDC,0xBB,0xAC,0x55,0xA0,0x62,0x95,0xCE,0x87, + 0x0B,0x07,0x02,0x9B,0xFC,0xDB,0x2D,0xCE,0x28,0xD9,0x59,0xF2,0x81,0x5B,0x16,0xF8, + 0x17,0x98,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFE,0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,0x3B,0xBF,0xD2,0x5E, + 0x8C,0xD0,0x36,0x41,0x41,0x02,0x01,0x01,0xA1,0x24,0x03,0x22,0x00 + }; + unsigned char *ptr = privkey; + memcpy(ptr, begin, sizeof(begin)); ptr += sizeof(begin); + memcpy(ptr, key32, 32); ptr += 32; + memcpy(ptr, middle, sizeof(middle)); ptr += sizeof(middle); + pubkeylen = 33; + secp256k1_ec_pubkey_serialize(ctx, ptr, &pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED); + ptr += pubkeylen; + *privkeylen = ptr - privkey; + } else { + static const unsigned char begin[] = { + 0x30,0x82,0x01,0x13,0x02,0x01,0x01,0x04,0x20 + }; + static const unsigned char middle[] = { + 0xA0,0x81,0xA5,0x30,0x81,0xA2,0x02,0x01,0x01,0x30,0x2C,0x06,0x07,0x2A,0x86,0x48, + 0xCE,0x3D,0x01,0x01,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFE,0xFF,0xFF,0xFC,0x2F,0x30,0x06,0x04,0x01,0x00,0x04,0x01,0x07,0x04, + 0x41,0x04,0x79,0xBE,0x66,0x7E,0xF9,0xDC,0xBB,0xAC,0x55,0xA0,0x62,0x95,0xCE,0x87, + 0x0B,0x07,0x02,0x9B,0xFC,0xDB,0x2D,0xCE,0x28,0xD9,0x59,0xF2,0x81,0x5B,0x16,0xF8, + 0x17,0x98,0x48,0x3A,0xDA,0x77,0x26,0xA3,0xC4,0x65,0x5D,0xA4,0xFB,0xFC,0x0E,0x11, + 0x08,0xA8,0xFD,0x17,0xB4,0x48,0xA6,0x85,0x54,0x19,0x9C,0x47,0xD0,0x8F,0xFB,0x10, + 0xD4,0xB8,0x02,0x21,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFE,0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,0x3B,0xBF,0xD2,0x5E, + 0x8C,0xD0,0x36,0x41,0x41,0x02,0x01,0x01,0xA1,0x44,0x03,0x42,0x00 + }; + unsigned char *ptr = privkey; + memcpy(ptr, begin, sizeof(begin)); ptr += sizeof(begin); + memcpy(ptr, key32, 32); ptr += 32; + memcpy(ptr, middle, sizeof(middle)); ptr += sizeof(middle); + pubkeylen = 65; + secp256k1_ec_pubkey_serialize(ctx, ptr, &pubkeylen, &pubkey, SECP256K1_EC_UNCOMPRESSED); + ptr += pubkeylen; + *privkeylen = ptr - privkey; + } + return 1; +} diff --git a/crypto/secp256k1/libsecp256k1/contrib/lax_der_privatekey_parsing.h b/crypto/secp256k1/libsecp256k1/contrib/lax_der_privatekey_parsing.h new file mode 100644 index 0000000..2fd088f --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/contrib/lax_der_privatekey_parsing.h @@ -0,0 +1,90 @@ +/********************************************************************** + * Copyright (c) 2014, 2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +/**** + * Please do not link this file directly. It is not part of the libsecp256k1 + * project and does not promise any stability in its API, functionality or + * presence. Projects which use this code should instead copy this header + * and its accompanying .c file directly into their codebase. + ****/ + +/* This file contains code snippets that parse DER private keys with + * various errors and violations. This is not a part of the library + * itself, because the allowed violations are chosen arbitrarily and + * do not follow or establish any standard. + * + * It also contains code to serialize private keys in a compatible + * manner. + * + * These functions are meant for compatibility with applications + * that require BER encoded keys. When working with secp256k1-specific + * code, the simple 32-byte private keys normally used by the + * library are sufficient. + */ + +#ifndef _SECP256K1_CONTRIB_BER_PRIVATEKEY_H_ +#define _SECP256K1_CONTRIB_BER_PRIVATEKEY_H_ + +#include + +# ifdef __cplusplus +extern "C" { +# endif + +/** Export a private key in DER format. + * + * Returns: 1 if the private key was valid. + * Args: ctx: pointer to a context object, initialized for signing (cannot + * be NULL) + * Out: privkey: pointer to an array for storing the private key in BER. + * Should have space for 279 bytes, and cannot be NULL. + * privkeylen: Pointer to an int where the length of the private key in + * privkey will be stored. + * In: seckey: pointer to a 32-byte secret key to export. + * compressed: 1 if the key should be exported in + * compressed format, 0 otherwise + * + * This function is purely meant for compatibility with applications that + * require BER encoded keys. When working with secp256k1-specific code, the + * simple 32-byte private keys are sufficient. + * + * Note that this function does not guarantee correct DER output. It is + * guaranteed to be parsable by secp256k1_ec_privkey_import_der + */ +SECP256K1_WARN_UNUSED_RESULT int ec_privkey_export_der( + const secp256k1_context* ctx, + unsigned char *privkey, + size_t *privkeylen, + const unsigned char *seckey, + int compressed +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Import a private key in DER format. + * Returns: 1 if a private key was extracted. + * Args: ctx: pointer to a context object (cannot be NULL). + * Out: seckey: pointer to a 32-byte array for storing the private key. + * (cannot be NULL). + * In: privkey: pointer to a private key in DER format (cannot be NULL). + * privkeylen: length of the DER private key pointed to be privkey. + * + * This function will accept more than just strict DER, and even allow some BER + * violations. The public key stored inside the DER-encoded private key is not + * verified for correctness, nor are the curve parameters. Use this function + * only if you know in advance it is supposed to contain a secp256k1 private + * key. + */ +SECP256K1_WARN_UNUSED_RESULT int ec_privkey_import_der( + const secp256k1_context* ctx, + unsigned char *seckey, + const unsigned char *privkey, + size_t privkeylen +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/crypto/secp256k1/libsecp256k1/dummy.go b/crypto/secp256k1/libsecp256k1/dummy.go new file mode 100644 index 0000000..04bbe3d --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/dummy.go @@ -0,0 +1,8 @@ +//go:build dummy +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package libsecp256k1 diff --git a/crypto/secp256k1/libsecp256k1/include/dummy.go b/crypto/secp256k1/libsecp256k1/include/dummy.go new file mode 100644 index 0000000..64c71b8 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/include/dummy.go @@ -0,0 +1,8 @@ +//go:build dummy +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package include diff --git a/crypto/secp256k1/libsecp256k1/include/secp256k1.h b/crypto/secp256k1/libsecp256k1/include/secp256k1.h new file mode 100644 index 0000000..76af839 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/include/secp256k1.h @@ -0,0 +1,577 @@ +#ifndef _SECP256K1_ +# define _SECP256K1_ + +# ifdef __cplusplus +extern "C" { +# endif + +#include + +/* These rules specify the order of arguments in API calls: + * + * 1. Context pointers go first, followed by output arguments, combined + * output/input arguments, and finally input-only arguments. + * 2. Array lengths always immediately the follow the argument whose length + * they describe, even if this violates rule 1. + * 3. Within the OUT/OUTIN/IN groups, pointers to data that is typically generated + * later go first. This means: signatures, public nonces, private nonces, + * messages, public keys, secret keys, tweaks. + * 4. Arguments that are not data pointers go last, from more complex to less + * complex: function pointers, algorithm names, messages, void pointers, + * counts, flags, booleans. + * 5. Opaque data pointers follow the function pointer they are to be passed to. + */ + +/** Opaque data structure that holds context information (precomputed tables etc.). + * + * The purpose of context structures is to cache large precomputed data tables + * that are expensive to construct, and also to maintain the randomization data + * for blinding. + * + * Do not create a new context object for each operation, as construction is + * far slower than all other API calls (~100 times slower than an ECDSA + * verification). + * + * A constructed context can safely be used from multiple threads + * simultaneously, but API call that take a non-const pointer to a context + * need exclusive access to it. In particular this is the case for + * secp256k1_context_destroy and secp256k1_context_randomize. + * + * Regarding randomization, either do it once at creation time (in which case + * you do not need any locking for the other calls), or use a read-write lock. + */ +typedef struct secp256k1_context_struct secp256k1_context; + +/** Opaque data structure that holds a parsed and valid public key. + * + * The exact representation of data inside is implementation defined and not + * guaranteed to be portable between different platforms or versions. It is + * however guaranteed to be 64 bytes in size, and can be safely copied/moved. + * If you need to convert to a format suitable for storage, transmission, or + * comparison, use secp256k1_ec_pubkey_serialize and secp256k1_ec_pubkey_parse. + */ +typedef struct { + unsigned char data[64]; +} secp256k1_pubkey; + +/** Opaque data structured that holds a parsed ECDSA signature. + * + * The exact representation of data inside is implementation defined and not + * guaranteed to be portable between different platforms or versions. It is + * however guaranteed to be 64 bytes in size, and can be safely copied/moved. + * If you need to convert to a format suitable for storage, transmission, or + * comparison, use the secp256k1_ecdsa_signature_serialize_* and + * secp256k1_ecdsa_signature_serialize_* functions. + */ +typedef struct { + unsigned char data[64]; +} secp256k1_ecdsa_signature; + +/** A pointer to a function to deterministically generate a nonce. + * + * Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail. + * Out: nonce32: pointer to a 32-byte array to be filled by the function. + * In: msg32: the 32-byte message hash being verified (will not be NULL) + * key32: pointer to a 32-byte secret key (will not be NULL) + * algo16: pointer to a 16-byte array describing the signature + * algorithm (will be NULL for ECDSA for compatibility). + * data: Arbitrary data pointer that is passed through. + * attempt: how many iterations we have tried to find a nonce. + * This will almost always be 0, but different attempt values + * are required to result in a different nonce. + * + * Except for test cases, this function should compute some cryptographic hash of + * the message, the algorithm, the key and the attempt. + */ +typedef int (*secp256k1_nonce_function)( + unsigned char *nonce32, + const unsigned char *msg32, + const unsigned char *key32, + const unsigned char *algo16, + void *data, + unsigned int attempt +); + +# if !defined(SECP256K1_GNUC_PREREQ) +# if defined(__GNUC__)&&defined(__GNUC_MINOR__) +# define SECP256K1_GNUC_PREREQ(_maj,_min) \ + ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) +# else +# define SECP256K1_GNUC_PREREQ(_maj,_min) 0 +# endif +# endif + +# if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if SECP256K1_GNUC_PREREQ(2,7) +# define SECP256K1_INLINE __inline__ +# elif (defined(_MSC_VER)) +# define SECP256K1_INLINE __inline +# else +# define SECP256K1_INLINE +# endif +# else +# define SECP256K1_INLINE inline +# endif + +#ifndef SECP256K1_API +# if defined(_WIN32) +# ifdef SECP256K1_BUILD +# define SECP256K1_API __declspec(dllexport) +# else +# define SECP256K1_API +# endif +# elif defined(__GNUC__) && defined(SECP256K1_BUILD) +# define SECP256K1_API __attribute__ ((visibility ("default"))) +# else +# define SECP256K1_API +# endif +#endif + +/**Warning attributes + * NONNULL is not used if SECP256K1_BUILD is set to avoid the compiler optimizing out + * some paranoid null checks. */ +# if defined(__GNUC__) && SECP256K1_GNUC_PREREQ(3, 4) +# define SECP256K1_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__)) +# else +# define SECP256K1_WARN_UNUSED_RESULT +# endif +# if !defined(SECP256K1_BUILD) && defined(__GNUC__) && SECP256K1_GNUC_PREREQ(3, 4) +# define SECP256K1_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x))) +# else +# define SECP256K1_ARG_NONNULL(_x) +# endif + +/** All flags' lower 8 bits indicate what they're for. Do not use directly. */ +#define SECP256K1_FLAGS_TYPE_MASK ((1 << 8) - 1) +#define SECP256K1_FLAGS_TYPE_CONTEXT (1 << 0) +#define SECP256K1_FLAGS_TYPE_COMPRESSION (1 << 1) +/** The higher bits contain the actual data. Do not use directly. */ +#define SECP256K1_FLAGS_BIT_CONTEXT_VERIFY (1 << 8) +#define SECP256K1_FLAGS_BIT_CONTEXT_SIGN (1 << 9) +#define SECP256K1_FLAGS_BIT_COMPRESSION (1 << 8) + +/** Flags to pass to secp256k1_context_create. */ +#define SECP256K1_CONTEXT_VERIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) +#define SECP256K1_CONTEXT_SIGN (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN) +#define SECP256K1_CONTEXT_NONE (SECP256K1_FLAGS_TYPE_CONTEXT) + +/** Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export. */ +#define SECP256K1_EC_COMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION) +#define SECP256K1_EC_UNCOMPRESSED (SECP256K1_FLAGS_TYPE_COMPRESSION) + +/** Create a secp256k1 context object. + * + * Returns: a newly created context object. + * In: flags: which parts of the context to initialize. + */ +SECP256K1_API secp256k1_context* secp256k1_context_create( + unsigned int flags +) SECP256K1_WARN_UNUSED_RESULT; + +/** Copies a secp256k1 context object. + * + * Returns: a newly created context object. + * Args: ctx: an existing context to copy (cannot be NULL) + */ +SECP256K1_API secp256k1_context* secp256k1_context_clone( + const secp256k1_context* ctx +) SECP256K1_ARG_NONNULL(1) SECP256K1_WARN_UNUSED_RESULT; + +/** Destroy a secp256k1 context object. + * + * The context pointer may not be used afterwards. + * Args: ctx: an existing context to destroy (cannot be NULL) + */ +SECP256K1_API void secp256k1_context_destroy( + secp256k1_context* ctx +); + +/** Set a callback function to be called when an illegal argument is passed to + * an API call. It will only trigger for violations that are mentioned + * explicitly in the header. + * + * The philosophy is that these shouldn't be dealt with through a + * specific return value, as calling code should not have branches to deal with + * the case that this code itself is broken. + * + * On the other hand, during debug stage, one would want to be informed about + * such mistakes, and the default (crashing) may be inadvisable. + * When this callback is triggered, the API function called is guaranteed not + * to cause a crash, though its return value and output arguments are + * undefined. + * + * Args: ctx: an existing context object (cannot be NULL) + * In: fun: a pointer to a function to call when an illegal argument is + * passed to the API, taking a message and an opaque pointer + * (NULL restores a default handler that calls abort). + * data: the opaque pointer to pass to fun above. + */ +SECP256K1_API void secp256k1_context_set_illegal_callback( + secp256k1_context* ctx, + void (*fun)(const char* message, void* data), + const void* data +) SECP256K1_ARG_NONNULL(1); + +/** Set a callback function to be called when an internal consistency check + * fails. The default is crashing. + * + * This can only trigger in case of a hardware failure, miscompilation, + * memory corruption, serious bug in the library, or other error would can + * otherwise result in undefined behaviour. It will not trigger due to mere + * incorrect usage of the API (see secp256k1_context_set_illegal_callback + * for that). After this callback returns, anything may happen, including + * crashing. + * + * Args: ctx: an existing context object (cannot be NULL) + * In: fun: a pointer to a function to call when an internal error occurs, + * taking a message and an opaque pointer (NULL restores a default + * handler that calls abort). + * data: the opaque pointer to pass to fun above. + */ +SECP256K1_API void secp256k1_context_set_error_callback( + secp256k1_context* ctx, + void (*fun)(const char* message, void* data), + const void* data +) SECP256K1_ARG_NONNULL(1); + +/** Parse a variable-length public key into the pubkey object. + * + * Returns: 1 if the public key was fully valid. + * 0 if the public key could not be parsed or is invalid. + * Args: ctx: a secp256k1 context object. + * Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a + * parsed version of input. If not, its value is undefined. + * In: input: pointer to a serialized public key + * inputlen: length of the array pointed to by input + * + * This function supports parsing compressed (33 bytes, header byte 0x02 or + * 0x03), uncompressed (65 bytes, header byte 0x04), or hybrid (65 bytes, header + * byte 0x06 or 0x07) format public keys. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_parse( + const secp256k1_context* ctx, + secp256k1_pubkey* pubkey, + const unsigned char *input, + size_t inputlen +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize a pubkey object into a serialized byte sequence. + * + * Returns: 1 always. + * Args: ctx: a secp256k1 context object. + * Out: output: a pointer to a 65-byte (if compressed==0) or 33-byte (if + * compressed==1) byte array to place the serialized key + * in. + * In/Out: outputlen: a pointer to an integer which is initially set to the + * size of output, and is overwritten with the written + * size. + * In: pubkey: a pointer to a secp256k1_pubkey containing an + * initialized public key. + * flags: SECP256K1_EC_COMPRESSED if serialization should be in + * compressed format, otherwise SECP256K1_EC_UNCOMPRESSED. + */ +SECP256K1_API int secp256k1_ec_pubkey_serialize( + const secp256k1_context* ctx, + unsigned char *output, + size_t *outputlen, + const secp256k1_pubkey* pubkey, + unsigned int flags +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Parse an ECDSA signature in compact (64 bytes) format. + * + * Returns: 1 when the signature could be parsed, 0 otherwise. + * Args: ctx: a secp256k1 context object + * Out: sig: a pointer to a signature object + * In: input64: a pointer to the 64-byte array to parse + * + * The signature must consist of a 32-byte big endian R value, followed by a + * 32-byte big endian S value. If R or S fall outside of [0..order-1], the + * encoding is invalid. R and S with value 0 are allowed in the encoding. + * + * After the call, sig will always be initialized. If parsing failed or R or + * S are zero, the resulting sig value is guaranteed to fail validation for any + * message and public key. + */ +SECP256K1_API int secp256k1_ecdsa_signature_parse_compact( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature* sig, + const unsigned char *input64 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Parse a DER ECDSA signature. + * + * Returns: 1 when the signature could be parsed, 0 otherwise. + * Args: ctx: a secp256k1 context object + * Out: sig: a pointer to a signature object + * In: input: a pointer to the signature to be parsed + * inputlen: the length of the array pointed to be input + * + * This function will accept any valid DER encoded signature, even if the + * encoded numbers are out of range. + * + * After the call, sig will always be initialized. If parsing failed or the + * encoded numbers are out of range, signature validation with it is + * guaranteed to fail for every message and public key. + */ +SECP256K1_API int secp256k1_ecdsa_signature_parse_der( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature* sig, + const unsigned char *input, + size_t inputlen +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize an ECDSA signature in DER format. + * + * Returns: 1 if enough space was available to serialize, 0 otherwise + * Args: ctx: a secp256k1 context object + * Out: output: a pointer to an array to store the DER serialization + * In/Out: outputlen: a pointer to a length integer. Initially, this integer + * should be set to the length of output. After the call + * it will be set to the length of the serialization (even + * if 0 was returned). + * In: sig: a pointer to an initialized signature object + */ +SECP256K1_API int secp256k1_ecdsa_signature_serialize_der( + const secp256k1_context* ctx, + unsigned char *output, + size_t *outputlen, + const secp256k1_ecdsa_signature* sig +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Serialize an ECDSA signature in compact (64 byte) format. + * + * Returns: 1 + * Args: ctx: a secp256k1 context object + * Out: output64: a pointer to a 64-byte array to store the compact serialization + * In: sig: a pointer to an initialized signature object + * + * See secp256k1_ecdsa_signature_parse_compact for details about the encoding. + */ +SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact( + const secp256k1_context* ctx, + unsigned char *output64, + const secp256k1_ecdsa_signature* sig +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Verify an ECDSA signature. + * + * Returns: 1: correct signature + * 0: incorrect or unparsable signature + * Args: ctx: a secp256k1 context object, initialized for verification. + * In: sig: the signature being verified (cannot be NULL) + * msg32: the 32-byte message hash being verified (cannot be NULL) + * pubkey: pointer to an initialized public key to verify with (cannot be NULL) + * + * To avoid accepting malleable signatures, only ECDSA signatures in lower-S + * form are accepted. + * + * If you need to accept ECDSA signatures from sources that do not obey this + * rule, apply secp256k1_ecdsa_signature_normalize to the signature prior to + * validation, but be aware that doing so results in malleable signatures. + * + * For details, see the comments for that function. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_verify( + const secp256k1_context* ctx, + const secp256k1_ecdsa_signature *sig, + const unsigned char *msg32, + const secp256k1_pubkey *pubkey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Convert a signature to a normalized lower-S form. + * + * Returns: 1 if sigin was not normalized, 0 if it already was. + * Args: ctx: a secp256k1 context object + * Out: sigout: a pointer to a signature to fill with the normalized form, + * or copy if the input was already normalized. (can be NULL if + * you're only interested in whether the input was already + * normalized). + * In: sigin: a pointer to a signature to check/normalize (cannot be NULL, + * can be identical to sigout) + * + * With ECDSA a third-party can forge a second distinct signature of the same + * message, given a single initial signature, but without knowing the key. This + * is done by negating the S value modulo the order of the curve, 'flipping' + * the sign of the random point R which is not included in the signature. + * + * Forgery of the same message isn't universally problematic, but in systems + * where message malleability or uniqueness of signatures is important this can + * cause issues. This forgery can be blocked by all verifiers forcing signers + * to use a normalized form. + * + * The lower-S form reduces the size of signatures slightly on average when + * variable length encodings (such as DER) are used and is cheap to verify, + * making it a good choice. Security of always using lower-S is assured because + * anyone can trivially modify a signature after the fact to enforce this + * property anyway. + * + * The lower S value is always between 0x1 and + * 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, + * inclusive. + * + * No other forms of ECDSA malleability are known and none seem likely, but + * there is no formal proof that ECDSA, even with this additional restriction, + * is free of other malleability. Commonly used serialization schemes will also + * accept various non-unique encodings, so care should be taken when this + * property is required for an application. + * + * The secp256k1_ecdsa_sign function will by default create signatures in the + * lower-S form, and secp256k1_ecdsa_verify will not accept others. In case + * signatures come from a system that cannot enforce this property, + * secp256k1_ecdsa_signature_normalize must be called before verification. + */ +SECP256K1_API int secp256k1_ecdsa_signature_normalize( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature *sigout, + const secp256k1_ecdsa_signature *sigin +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3); + +/** An implementation of RFC6979 (using HMAC-SHA256) as nonce generation function. + * If a data pointer is passed, it is assumed to be a pointer to 32 bytes of + * extra entropy. + */ +SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_rfc6979; + +/** A default safe nonce generation function (currently equal to secp256k1_nonce_function_rfc6979). */ +SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_default; + +/** Create an ECDSA signature. + * + * Returns: 1: signature created + * 0: the nonce generation function failed, or the private key was invalid. + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: sig: pointer to an array where the signature will be placed (cannot be NULL) + * In: msg32: the 32-byte message hash being signed (cannot be NULL) + * seckey: pointer to a 32-byte secret key (cannot be NULL) + * noncefp:pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used + * ndata: pointer to arbitrary data used by the nonce generation function (can be NULL) + * + * The created signature is always in lower-S form. See + * secp256k1_ecdsa_signature_normalize for more details. + */ +SECP256K1_API int secp256k1_ecdsa_sign( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature *sig, + const unsigned char *msg32, + const unsigned char *seckey, + secp256k1_nonce_function noncefp, + const void *ndata +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Verify an ECDSA secret key. + * + * Returns: 1: secret key is valid + * 0: secret key is invalid + * Args: ctx: pointer to a context object (cannot be NULL) + * In: seckey: pointer to a 32-byte secret key (cannot be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_verify( + const secp256k1_context* ctx, + const unsigned char *seckey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); + +/** Compute the public key for a secret key. + * + * Returns: 1: secret was valid, public key stores + * 0: secret was invalid, try again + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: pubkey: pointer to the created public key (cannot be NULL) + * In: seckey: pointer to a 32-byte private key (cannot be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_create( + const secp256k1_context* ctx, + secp256k1_pubkey *pubkey, + const unsigned char *seckey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Tweak a private key by adding tweak to it. + * Returns: 0 if the tweak was out of range (chance of around 1 in 2^128 for + * uniformly random 32-byte arrays, or if the resulting private key + * would be invalid (only when the tweak is the complement of the + * private key). 1 otherwise. + * Args: ctx: pointer to a context object (cannot be NULL). + * In/Out: seckey: pointer to a 32-byte private key. + * In: tweak: pointer to a 32-byte tweak. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_add( + const secp256k1_context* ctx, + unsigned char *seckey, + const unsigned char *tweak +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Tweak a public key by adding tweak times the generator to it. + * Returns: 0 if the tweak was out of range (chance of around 1 in 2^128 for + * uniformly random 32-byte arrays, or if the resulting public key + * would be invalid (only when the tweak is the complement of the + * corresponding private key). 1 otherwise. + * Args: ctx: pointer to a context object initialized for validation + * (cannot be NULL). + * In/Out: pubkey: pointer to a public key object. + * In: tweak: pointer to a 32-byte tweak. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_tweak_add( + const secp256k1_context* ctx, + secp256k1_pubkey *pubkey, + const unsigned char *tweak +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Tweak a private key by multiplying it by a tweak. + * Returns: 0 if the tweak was out of range (chance of around 1 in 2^128 for + * uniformly random 32-byte arrays, or equal to zero. 1 otherwise. + * Args: ctx: pointer to a context object (cannot be NULL). + * In/Out: seckey: pointer to a 32-byte private key. + * In: tweak: pointer to a 32-byte tweak. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_mul( + const secp256k1_context* ctx, + unsigned char *seckey, + const unsigned char *tweak +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Tweak a public key by multiplying it by a tweak value. + * Returns: 0 if the tweak was out of range (chance of around 1 in 2^128 for + * uniformly random 32-byte arrays, or equal to zero. 1 otherwise. + * Args: ctx: pointer to a context object initialized for validation + * (cannot be NULL). + * In/Out: pubkey: pointer to a public key obkect. + * In: tweak: pointer to a 32-byte tweak. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_tweak_mul( + const secp256k1_context* ctx, + secp256k1_pubkey *pubkey, + const unsigned char *tweak +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Updates the context randomization. + * Returns: 1: randomization successfully updated + * 0: error + * Args: ctx: pointer to a context object (cannot be NULL) + * In: seed32: pointer to a 32-byte random seed (NULL resets to initial state) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_context_randomize( + secp256k1_context* ctx, + const unsigned char *seed32 +) SECP256K1_ARG_NONNULL(1); + +/** Add a number of public keys together. + * Returns: 1: the sum of the public keys is valid. + * 0: the sum of the public keys is not valid. + * Args: ctx: pointer to a context object + * Out: out: pointer to a public key object for placing the resulting public key + * (cannot be NULL) + * In: ins: pointer to array of pointers to public keys (cannot be NULL) + * n: the number of public keys to add together (must be at least 1) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine( + const secp256k1_context* ctx, + secp256k1_pubkey *out, + const secp256k1_pubkey * const * ins, + size_t n +) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/crypto/secp256k1/libsecp256k1/include/secp256k1_ecdh.h b/crypto/secp256k1/libsecp256k1/include/secp256k1_ecdh.h new file mode 100644 index 0000000..4b84d7a --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/include/secp256k1_ecdh.h @@ -0,0 +1,31 @@ +#ifndef _SECP256K1_ECDH_ +# define _SECP256K1_ECDH_ + +# include "secp256k1.h" + +# ifdef __cplusplus +extern "C" { +# endif + +/** Compute an EC Diffie-Hellman secret in constant time + * Returns: 1: exponentiation was successful + * 0: scalar was invalid (zero or overflow) + * Args: ctx: pointer to a context object (cannot be NULL) + * Out: result: a 32-byte array which will be populated by an ECDH + * secret computed from the point and scalar + * In: pubkey: a pointer to a secp256k1_pubkey containing an + * initialized public key + * privkey: a 32-byte scalar with which to multiply the point + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdh( + const secp256k1_context* ctx, + unsigned char *result, + const secp256k1_pubkey *pubkey, + const unsigned char *privkey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/crypto/secp256k1/libsecp256k1/include/secp256k1_recovery.h b/crypto/secp256k1/libsecp256k1/include/secp256k1_recovery.h new file mode 100644 index 0000000..0553797 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/include/secp256k1_recovery.h @@ -0,0 +1,110 @@ +#ifndef _SECP256K1_RECOVERY_ +# define _SECP256K1_RECOVERY_ + +# include "secp256k1.h" + +# ifdef __cplusplus +extern "C" { +# endif + +/** Opaque data structured that holds a parsed ECDSA signature, + * supporting pubkey recovery. + * + * The exact representation of data inside is implementation defined and not + * guaranteed to be portable between different platforms or versions. It is + * however guaranteed to be 65 bytes in size, and can be safely copied/moved. + * If you need to convert to a format suitable for storage or transmission, use + * the secp256k1_ecdsa_signature_serialize_* and + * secp256k1_ecdsa_signature_parse_* functions. + * + * Furthermore, it is guaranteed that identical signatures (including their + * recoverability) will have identical representation, so they can be + * memcmp'ed. + */ +typedef struct { + unsigned char data[65]; +} secp256k1_ecdsa_recoverable_signature; + +/** Parse a compact ECDSA signature (64 bytes + recovery id). + * + * Returns: 1 when the signature could be parsed, 0 otherwise + * Args: ctx: a secp256k1 context object + * Out: sig: a pointer to a signature object + * In: input64: a pointer to a 64-byte compact signature + * recid: the recovery id (0, 1, 2 or 3) + */ +SECP256K1_API int secp256k1_ecdsa_recoverable_signature_parse_compact( + const secp256k1_context* ctx, + secp256k1_ecdsa_recoverable_signature* sig, + const unsigned char *input64, + int recid +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Convert a recoverable signature into a normal signature. + * + * Returns: 1 + * Out: sig: a pointer to a normal signature (cannot be NULL). + * In: sigin: a pointer to a recoverable signature (cannot be NULL). + */ +SECP256K1_API int secp256k1_ecdsa_recoverable_signature_convert( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature* sig, + const secp256k1_ecdsa_recoverable_signature* sigin +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize an ECDSA signature in compact format (64 bytes + recovery id). + * + * Returns: 1 + * Args: ctx: a secp256k1 context object + * Out: output64: a pointer to a 64-byte array of the compact signature (cannot be NULL) + * recid: a pointer to an integer to hold the recovery id (can be NULL). + * In: sig: a pointer to an initialized signature object (cannot be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_recoverable_signature_serialize_compact( + const secp256k1_context* ctx, + unsigned char *output64, + int *recid, + const secp256k1_ecdsa_recoverable_signature* sig +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create a recoverable ECDSA signature. + * + * Returns: 1: signature created + * 0: the nonce generation function failed, or the private key was invalid. + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: sig: pointer to an array where the signature will be placed (cannot be NULL) + * In: msg32: the 32-byte message hash being signed (cannot be NULL) + * seckey: pointer to a 32-byte secret key (cannot be NULL) + * noncefp:pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used + * ndata: pointer to arbitrary data used by the nonce generation function (can be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_sign_recoverable( + const secp256k1_context* ctx, + secp256k1_ecdsa_recoverable_signature *sig, + const unsigned char *msg32, + const unsigned char *seckey, + secp256k1_nonce_function noncefp, + const void *ndata +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Recover an ECDSA public key from a signature. + * + * Returns: 1: public key successfully recovered (which guarantees a correct signature). + * 0: otherwise. + * Args: ctx: pointer to a context object, initialized for verification (cannot be NULL) + * Out: pubkey: pointer to the recovered public key (cannot be NULL) + * In: sig: pointer to initialized signature that supports pubkey recovery (cannot be NULL) + * msg32: the 32-byte message hash assumed to be signed (cannot be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_recover( + const secp256k1_context* ctx, + secp256k1_pubkey *pubkey, + const secp256k1_ecdsa_recoverable_signature *sig, + const unsigned char *msg32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/crypto/secp256k1/libsecp256k1/libsecp256k1.pc.in b/crypto/secp256k1/libsecp256k1/libsecp256k1.pc.in new file mode 100644 index 0000000..a0d006f --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/libsecp256k1.pc.in @@ -0,0 +1,13 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libsecp256k1 +Description: Optimized C library for EC operations on curve secp256k1 +URL: https://github.com/bitcoin-core/secp256k1 +Version: @PACKAGE_VERSION@ +Cflags: -I${includedir} +Libs.private: @SECP_LIBS@ +Libs: -L${libdir} -lsecp256k1 + diff --git a/crypto/secp256k1/libsecp256k1/obj/.gitignore b/crypto/secp256k1/libsecp256k1/obj/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/crypto/secp256k1/libsecp256k1/sage/group_prover.sage b/crypto/secp256k1/libsecp256k1/sage/group_prover.sage new file mode 100644 index 0000000..68882e9 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/sage/group_prover.sage @@ -0,0 +1,322 @@ +# This code supports verifying group implementations which have branches +# or conditional statements (like cmovs), by allowing each execution path +# to independently set assumptions on input or intermediary variables. +# +# The general approach is: +# * A constraint is a tuple of two sets of of symbolic expressions: +# the first of which are required to evaluate to zero, the second of which +# are required to evaluate to nonzero. +# - A constraint is said to be conflicting if any of its nonzero expressions +# is in the ideal with basis the zero expressions (in other words: when the +# zero expressions imply that one of the nonzero expressions are zero). +# * There is a list of laws that describe the intended behaviour, including +# laws for addition and doubling. Each law is called with the symbolic point +# coordinates as arguments, and returns: +# - A constraint describing the assumptions under which it is applicable, +# called "assumeLaw" +# - A constraint describing the requirements of the law, called "require" +# * Implementations are transliterated into functions that operate as well on +# algebraic input points, and are called once per combination of branches +# executed. Each execution returns: +# - A constraint describing the assumptions this implementation requires +# (such as Z1=1), called "assumeFormula" +# - A constraint describing the assumptions this specific branch requires, +# but which is by construction guaranteed to cover the entire space by +# merging the results from all branches, called "assumeBranch" +# - The result of the computation +# * All combinations of laws with implementation branches are tried, and: +# - If the combination of assumeLaw, assumeFormula, and assumeBranch results +# in a conflict, it means this law does not apply to this branch, and it is +# skipped. +# - For others, we try to prove the require constraints hold, assuming the +# information in assumeLaw + assumeFormula + assumeBranch, and if this does +# not succeed, we fail. +# + To prove an expression is zero, we check whether it belongs to the +# ideal with the assumed zero expressions as basis. This test is exact. +# + To prove an expression is nonzero, we check whether each of its +# factors is contained in the set of nonzero assumptions' factors. +# This test is not exact, so various combinations of original and +# reduced expressions' factors are tried. +# - If we succeed, we print out the assumptions from assumeFormula that +# weren't implied by assumeLaw already. Those from assumeBranch are skipped, +# as we assume that all constraints in it are complementary with each other. +# +# Based on the sage verification scripts used in the Explicit-Formulas Database +# by Tanja Lange and others, see http://hyperelliptic.org/EFD + +class fastfrac: + """Fractions over rings.""" + + def __init__(self,R,top,bot=1): + """Construct a fractional, given a ring, a numerator, and denominator.""" + self.R = R + if parent(top) == ZZ or parent(top) == R: + self.top = R(top) + self.bot = R(bot) + elif top.__class__ == fastfrac: + self.top = top.top + self.bot = top.bot * bot + else: + self.top = R(numerator(top)) + self.bot = R(denominator(top)) * bot + + def iszero(self,I): + """Return whether this fraction is zero given an ideal.""" + return self.top in I and self.bot not in I + + def reduce(self,assumeZero): + zero = self.R.ideal(map(numerator, assumeZero)) + return fastfrac(self.R, zero.reduce(self.top)) / fastfrac(self.R, zero.reduce(self.bot)) + + def __add__(self,other): + """Add two fractions.""" + if parent(other) == ZZ: + return fastfrac(self.R,self.top + self.bot * other,self.bot) + if other.__class__ == fastfrac: + return fastfrac(self.R,self.top * other.bot + self.bot * other.top,self.bot * other.bot) + return NotImplemented + + def __sub__(self,other): + """Subtract two fractions.""" + if parent(other) == ZZ: + return fastfrac(self.R,self.top - self.bot * other,self.bot) + if other.__class__ == fastfrac: + return fastfrac(self.R,self.top * other.bot - self.bot * other.top,self.bot * other.bot) + return NotImplemented + + def __neg__(self): + """Return the negation of a fraction.""" + return fastfrac(self.R,-self.top,self.bot) + + def __mul__(self,other): + """Multiply two fractions.""" + if parent(other) == ZZ: + return fastfrac(self.R,self.top * other,self.bot) + if other.__class__ == fastfrac: + return fastfrac(self.R,self.top * other.top,self.bot * other.bot) + return NotImplemented + + def __rmul__(self,other): + """Multiply something else with a fraction.""" + return self.__mul__(other) + + def __div__(self,other): + """Divide two fractions.""" + if parent(other) == ZZ: + return fastfrac(self.R,self.top,self.bot * other) + if other.__class__ == fastfrac: + return fastfrac(self.R,self.top * other.bot,self.bot * other.top) + return NotImplemented + + def __pow__(self,other): + """Compute a power of a fraction.""" + if parent(other) == ZZ: + if other < 0: + # Negative powers require flipping top and bottom + return fastfrac(self.R,self.bot ^ (-other),self.top ^ (-other)) + else: + return fastfrac(self.R,self.top ^ other,self.bot ^ other) + return NotImplemented + + def __str__(self): + return "fastfrac((" + str(self.top) + ") / (" + str(self.bot) + "))" + def __repr__(self): + return "%s" % self + + def numerator(self): + return self.top + +class constraints: + """A set of constraints, consisting of zero and nonzero expressions. + + Constraints can either be used to express knowledge or a requirement. + + Both the fields zero and nonzero are maps from expressions to description + strings. The expressions that are the keys in zero are required to be zero, + and the expressions that are the keys in nonzero are required to be nonzero. + + Note that (a != 0) and (b != 0) is the same as (a*b != 0), so all keys in + nonzero could be multiplied into a single key. This is often much less + efficient to work with though, so we keep them separate inside the + constraints. This allows higher-level code to do fast checks on the individual + nonzero elements, or combine them if needed for stronger checks. + + We can't multiply the different zero elements, as it would suffice for one of + the factors to be zero, instead of all of them. Instead, the zero elements are + typically combined into an ideal first. + """ + + def __init__(self, **kwargs): + if 'zero' in kwargs: + self.zero = dict(kwargs['zero']) + else: + self.zero = dict() + if 'nonzero' in kwargs: + self.nonzero = dict(kwargs['nonzero']) + else: + self.nonzero = dict() + + def negate(self): + return constraints(zero=self.nonzero, nonzero=self.zero) + + def __add__(self, other): + zero = self.zero.copy() + zero.update(other.zero) + nonzero = self.nonzero.copy() + nonzero.update(other.nonzero) + return constraints(zero=zero, nonzero=nonzero) + + def __str__(self): + return "constraints(zero=%s,nonzero=%s)" % (self.zero, self.nonzero) + + def __repr__(self): + return "%s" % self + + +def conflicts(R, con): + """Check whether any of the passed non-zero assumptions is implied by the zero assumptions""" + zero = R.ideal(map(numerator, con.zero)) + if 1 in zero: + return True + # First a cheap check whether any of the individual nonzero terms conflict on + # their own. + for nonzero in con.nonzero: + if nonzero.iszero(zero): + return True + # It can be the case that entries in the nonzero set do not individually + # conflict with the zero set, but their combination does. For example, knowing + # that either x or y is zero is equivalent to having x*y in the zero set. + # Having x or y individually in the nonzero set is not a conflict, but both + # simultaneously is, so that is the right thing to check for. + if reduce(lambda a,b: a * b, con.nonzero, fastfrac(R, 1)).iszero(zero): + return True + return False + + +def get_nonzero_set(R, assume): + """Calculate a simple set of nonzero expressions""" + zero = R.ideal(map(numerator, assume.zero)) + nonzero = set() + for nz in map(numerator, assume.nonzero): + for (f,n) in nz.factor(): + nonzero.add(f) + rnz = zero.reduce(nz) + for (f,n) in rnz.factor(): + nonzero.add(f) + return nonzero + + +def prove_nonzero(R, exprs, assume): + """Check whether an expression is provably nonzero, given assumptions""" + zero = R.ideal(map(numerator, assume.zero)) + nonzero = get_nonzero_set(R, assume) + expl = set() + ok = True + for expr in exprs: + if numerator(expr) in zero: + return (False, [exprs[expr]]) + allexprs = reduce(lambda a,b: numerator(a)*numerator(b), exprs, 1) + for (f, n) in allexprs.factor(): + if f not in nonzero: + ok = False + if ok: + return (True, None) + ok = True + for (f, n) in zero.reduce(numerator(allexprs)).factor(): + if f not in nonzero: + ok = False + if ok: + return (True, None) + ok = True + for expr in exprs: + for (f,n) in numerator(expr).factor(): + if f not in nonzero: + ok = False + if ok: + return (True, None) + ok = True + for expr in exprs: + for (f,n) in zero.reduce(numerator(expr)).factor(): + if f not in nonzero: + expl.add(exprs[expr]) + if expl: + return (False, list(expl)) + else: + return (True, None) + + +def prove_zero(R, exprs, assume): + """Check whether all of the passed expressions are provably zero, given assumptions""" + r, e = prove_nonzero(R, dict(map(lambda x: (fastfrac(R, x.bot, 1), exprs[x]), exprs)), assume) + if not r: + return (False, map(lambda x: "Possibly zero denominator: %s" % x, e)) + zero = R.ideal(map(numerator, assume.zero)) + nonzero = prod(x for x in assume.nonzero) + expl = [] + for expr in exprs: + if not expr.iszero(zero): + expl.append(exprs[expr]) + if not expl: + return (True, None) + return (False, expl) + + +def describe_extra(R, assume, assumeExtra): + """Describe what assumptions are added, given existing assumptions""" + zerox = assume.zero.copy() + zerox.update(assumeExtra.zero) + zero = R.ideal(map(numerator, assume.zero)) + zeroextra = R.ideal(map(numerator, zerox)) + nonzero = get_nonzero_set(R, assume) + ret = set() + # Iterate over the extra zero expressions + for base in assumeExtra.zero: + if base not in zero: + add = [] + for (f, n) in numerator(base).factor(): + if f not in nonzero: + add += ["%s" % f] + if add: + ret.add((" * ".join(add)) + " = 0 [%s]" % assumeExtra.zero[base]) + # Iterate over the extra nonzero expressions + for nz in assumeExtra.nonzero: + nzr = zeroextra.reduce(numerator(nz)) + if nzr not in zeroextra: + for (f,n) in nzr.factor(): + if zeroextra.reduce(f) not in nonzero: + ret.add("%s != 0" % zeroextra.reduce(f)) + return ", ".join(x for x in ret) + + +def check_symbolic(R, assumeLaw, assumeAssert, assumeBranch, require): + """Check a set of zero and nonzero requirements, given a set of zero and nonzero assumptions""" + assume = assumeLaw + assumeAssert + assumeBranch + + if conflicts(R, assume): + # This formula does not apply + return None + + describe = describe_extra(R, assumeLaw + assumeBranch, assumeAssert) + + ok, msg = prove_zero(R, require.zero, assume) + if not ok: + return "FAIL, %s fails (assuming %s)" % (str(msg), describe) + + res, expl = prove_nonzero(R, require.nonzero, assume) + if not res: + return "FAIL, %s fails (assuming %s)" % (str(expl), describe) + + if describe != "": + return "OK (assuming %s)" % describe + else: + return "OK" + + +def concrete_verify(c): + for k in c.zero: + if k != 0: + return (False, c.zero[k]) + for k in c.nonzero: + if k == 0: + return (False, c.nonzero[k]) + return (True, None) diff --git a/crypto/secp256k1/libsecp256k1/sage/secp256k1.sage b/crypto/secp256k1/libsecp256k1/sage/secp256k1.sage new file mode 100644 index 0000000..a97e732 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/sage/secp256k1.sage @@ -0,0 +1,306 @@ +# Test libsecp256k1' group operation implementations using prover.sage + +import sys + +load("group_prover.sage") +load("weierstrass_prover.sage") + +def formula_secp256k1_gej_double_var(a): + """libsecp256k1's secp256k1_gej_double_var, used by various addition functions""" + rz = a.Z * a.Y + rz = rz * 2 + t1 = a.X^2 + t1 = t1 * 3 + t2 = t1^2 + t3 = a.Y^2 + t3 = t3 * 2 + t4 = t3^2 + t4 = t4 * 2 + t3 = t3 * a.X + rx = t3 + rx = rx * 4 + rx = -rx + rx = rx + t2 + t2 = -t2 + t3 = t3 * 6 + t3 = t3 + t2 + ry = t1 * t3 + t2 = -t4 + ry = ry + t2 + return jacobianpoint(rx, ry, rz) + +def formula_secp256k1_gej_add_var(branch, a, b): + """libsecp256k1's secp256k1_gej_add_var""" + if branch == 0: + return (constraints(), constraints(nonzero={a.Infinity : 'a_infinite'}), b) + if branch == 1: + return (constraints(), constraints(zero={a.Infinity : 'a_finite'}, nonzero={b.Infinity : 'b_infinite'}), a) + z22 = b.Z^2 + z12 = a.Z^2 + u1 = a.X * z22 + u2 = b.X * z12 + s1 = a.Y * z22 + s1 = s1 * b.Z + s2 = b.Y * z12 + s2 = s2 * a.Z + h = -u1 + h = h + u2 + i = -s1 + i = i + s2 + if branch == 2: + r = formula_secp256k1_gej_double_var(a) + return (constraints(), constraints(zero={h : 'h=0', i : 'i=0', a.Infinity : 'a_finite', b.Infinity : 'b_finite'}), r) + if branch == 3: + return (constraints(), constraints(zero={h : 'h=0', a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={i : 'i!=0'}), point_at_infinity()) + i2 = i^2 + h2 = h^2 + h3 = h2 * h + h = h * b.Z + rz = a.Z * h + t = u1 * h2 + rx = t + rx = rx * 2 + rx = rx + h3 + rx = -rx + rx = rx + i2 + ry = -rx + ry = ry + t + ry = ry * i + h3 = h3 * s1 + h3 = -h3 + ry = ry + h3 + return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) + +def formula_secp256k1_gej_add_ge_var(branch, a, b): + """libsecp256k1's secp256k1_gej_add_ge_var, which assume bz==1""" + if branch == 0: + return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(nonzero={a.Infinity : 'a_infinite'}), b) + if branch == 1: + return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite'}, nonzero={b.Infinity : 'b_infinite'}), a) + z12 = a.Z^2 + u1 = a.X + u2 = b.X * z12 + s1 = a.Y + s2 = b.Y * z12 + s2 = s2 * a.Z + h = -u1 + h = h + u2 + i = -s1 + i = i + s2 + if (branch == 2): + r = formula_secp256k1_gej_double_var(a) + return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0', i : 'i=0'}), r) + if (branch == 3): + return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0'}, nonzero={i : 'i!=0'}), point_at_infinity()) + i2 = i^2 + h2 = h^2 + h3 = h * h2 + rz = a.Z * h + t = u1 * h2 + rx = t + rx = rx * 2 + rx = rx + h3 + rx = -rx + rx = rx + i2 + ry = -rx + ry = ry + t + ry = ry * i + h3 = h3 * s1 + h3 = -h3 + ry = ry + h3 + return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) + +def formula_secp256k1_gej_add_zinv_var(branch, a, b): + """libsecp256k1's secp256k1_gej_add_zinv_var""" + bzinv = b.Z^(-1) + if branch == 0: + return (constraints(), constraints(nonzero={b.Infinity : 'b_infinite'}), a) + if branch == 1: + bzinv2 = bzinv^2 + bzinv3 = bzinv2 * bzinv + rx = b.X * bzinv2 + ry = b.Y * bzinv3 + rz = 1 + return (constraints(), constraints(zero={b.Infinity : 'b_finite'}, nonzero={a.Infinity : 'a_infinite'}), jacobianpoint(rx, ry, rz)) + azz = a.Z * bzinv + z12 = azz^2 + u1 = a.X + u2 = b.X * z12 + s1 = a.Y + s2 = b.Y * z12 + s2 = s2 * azz + h = -u1 + h = h + u2 + i = -s1 + i = i + s2 + if branch == 2: + r = formula_secp256k1_gej_double_var(a) + return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0', i : 'i=0'}), r) + if branch == 3: + return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0'}, nonzero={i : 'i!=0'}), point_at_infinity()) + i2 = i^2 + h2 = h^2 + h3 = h * h2 + rz = a.Z + rz = rz * h + t = u1 * h2 + rx = t + rx = rx * 2 + rx = rx + h3 + rx = -rx + rx = rx + i2 + ry = -rx + ry = ry + t + ry = ry * i + h3 = h3 * s1 + h3 = -h3 + ry = ry + h3 + return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) + +def formula_secp256k1_gej_add_ge(branch, a, b): + """libsecp256k1's secp256k1_gej_add_ge""" + zeroes = {} + nonzeroes = {} + a_infinity = False + if (branch & 4) != 0: + nonzeroes.update({a.Infinity : 'a_infinite'}) + a_infinity = True + else: + zeroes.update({a.Infinity : 'a_finite'}) + zz = a.Z^2 + u1 = a.X + u2 = b.X * zz + s1 = a.Y + s2 = b.Y * zz + s2 = s2 * a.Z + t = u1 + t = t + u2 + m = s1 + m = m + s2 + rr = t^2 + m_alt = -u2 + tt = u1 * m_alt + rr = rr + tt + degenerate = (branch & 3) == 3 + if (branch & 1) != 0: + zeroes.update({m : 'm_zero'}) + else: + nonzeroes.update({m : 'm_nonzero'}) + if (branch & 2) != 0: + zeroes.update({rr : 'rr_zero'}) + else: + nonzeroes.update({rr : 'rr_nonzero'}) + rr_alt = s1 + rr_alt = rr_alt * 2 + m_alt = m_alt + u1 + if not degenerate: + rr_alt = rr + m_alt = m + n = m_alt^2 + q = n * t + n = n^2 + if degenerate: + n = m + t = rr_alt^2 + rz = a.Z * m_alt + infinity = False + if (branch & 8) != 0: + if not a_infinity: + infinity = True + zeroes.update({rz : 'r.z=0'}) + else: + nonzeroes.update({rz : 'r.z!=0'}) + rz = rz * 2 + q = -q + t = t + q + rx = t + t = t * 2 + t = t + q + t = t * rr_alt + t = t + n + ry = -t + rx = rx * 4 + ry = ry * 4 + if a_infinity: + rx = b.X + ry = b.Y + rz = 1 + if infinity: + return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zeroes, nonzero=nonzeroes), point_at_infinity()) + return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zeroes, nonzero=nonzeroes), jacobianpoint(rx, ry, rz)) + +def formula_secp256k1_gej_add_ge_old(branch, a, b): + """libsecp256k1's old secp256k1_gej_add_ge, which fails when ay+by=0 but ax!=bx""" + a_infinity = (branch & 1) != 0 + zero = {} + nonzero = {} + if a_infinity: + nonzero.update({a.Infinity : 'a_infinite'}) + else: + zero.update({a.Infinity : 'a_finite'}) + zz = a.Z^2 + u1 = a.X + u2 = b.X * zz + s1 = a.Y + s2 = b.Y * zz + s2 = s2 * a.Z + z = a.Z + t = u1 + t = t + u2 + m = s1 + m = m + s2 + n = m^2 + q = n * t + n = n^2 + rr = t^2 + t = u1 * u2 + t = -t + rr = rr + t + t = rr^2 + rz = m * z + infinity = False + if (branch & 2) != 0: + if not a_infinity: + infinity = True + else: + return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(nonzero={z : 'conflict_a'}, zero={z : 'conflict_b'}), point_at_infinity()) + zero.update({rz : 'r.z=0'}) + else: + nonzero.update({rz : 'r.z!=0'}) + rz = rz * (0 if a_infinity else 2) + rx = t + q = -q + rx = rx + q + q = q * 3 + t = t * 2 + t = t + q + t = t * rr + t = t + n + ry = -t + rx = rx * (0 if a_infinity else 4) + ry = ry * (0 if a_infinity else 4) + t = b.X + t = t * (1 if a_infinity else 0) + rx = rx + t + t = b.Y + t = t * (1 if a_infinity else 0) + ry = ry + t + t = (1 if a_infinity else 0) + rz = rz + t + if infinity: + return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zero, nonzero=nonzero), point_at_infinity()) + return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zero, nonzero=nonzero), jacobianpoint(rx, ry, rz)) + +if __name__ == "__main__": + check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var) + check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var) + check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var) + check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge) + check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old) + + if len(sys.argv) >= 2 and sys.argv[1] == "--exhaustive": + check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43) + check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43) + check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43) + check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge, 43) + check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43) diff --git a/crypto/secp256k1/libsecp256k1/sage/weierstrass_prover.sage b/crypto/secp256k1/libsecp256k1/sage/weierstrass_prover.sage new file mode 100644 index 0000000..03ef2ec --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/sage/weierstrass_prover.sage @@ -0,0 +1,264 @@ +# Prover implementation for Weierstrass curves of the form +# y^2 = x^3 + A * x + B, specifically with a = 0 and b = 7, with group laws +# operating on affine and Jacobian coordinates, including the point at infinity +# represented by a 4th variable in coordinates. + +load("group_prover.sage") + + +class affinepoint: + def __init__(self, x, y, infinity=0): + self.x = x + self.y = y + self.infinity = infinity + def __str__(self): + return "affinepoint(x=%s,y=%s,inf=%s)" % (self.x, self.y, self.infinity) + + +class jacobianpoint: + def __init__(self, x, y, z, infinity=0): + self.X = x + self.Y = y + self.Z = z + self.Infinity = infinity + def __str__(self): + return "jacobianpoint(X=%s,Y=%s,Z=%s,inf=%s)" % (self.X, self.Y, self.Z, self.Infinity) + + +def point_at_infinity(): + return jacobianpoint(1, 1, 1, 1) + + +def negate(p): + if p.__class__ == affinepoint: + return affinepoint(p.x, -p.y) + if p.__class__ == jacobianpoint: + return jacobianpoint(p.X, -p.Y, p.Z) + assert(False) + + +def on_weierstrass_curve(A, B, p): + """Return a set of zero-expressions for an affine point to be on the curve""" + return constraints(zero={p.x^3 + A*p.x + B - p.y^2: 'on_curve'}) + + +def tangential_to_weierstrass_curve(A, B, p12, p3): + """Return a set of zero-expressions for ((x12,y12),(x3,y3)) to be a line that is tangential to the curve at (x12,y12)""" + return constraints(zero={ + (p12.y - p3.y) * (p12.y * 2) - (p12.x^2 * 3 + A) * (p12.x - p3.x): 'tangential_to_curve' + }) + + +def colinear(p1, p2, p3): + """Return a set of zero-expressions for ((x1,y1),(x2,y2),(x3,y3)) to be collinear""" + return constraints(zero={ + (p1.y - p2.y) * (p1.x - p3.x) - (p1.y - p3.y) * (p1.x - p2.x): 'colinear_1', + (p2.y - p3.y) * (p2.x - p1.x) - (p2.y - p1.y) * (p2.x - p3.x): 'colinear_2', + (p3.y - p1.y) * (p3.x - p2.x) - (p3.y - p2.y) * (p3.x - p1.x): 'colinear_3' + }) + + +def good_affine_point(p): + return constraints(nonzero={p.x : 'nonzero_x', p.y : 'nonzero_y'}) + + +def good_jacobian_point(p): + return constraints(nonzero={p.X : 'nonzero_X', p.Y : 'nonzero_Y', p.Z^6 : 'nonzero_Z'}) + + +def good_point(p): + return constraints(nonzero={p.Z^6 : 'nonzero_X'}) + + +def finite(p, *affine_fns): + con = good_point(p) + constraints(zero={p.Infinity : 'finite_point'}) + if p.Z != 0: + return con + reduce(lambda a, b: a + b, (f(affinepoint(p.X / p.Z^2, p.Y / p.Z^3)) for f in affine_fns), con) + else: + return con + +def infinite(p): + return constraints(nonzero={p.Infinity : 'infinite_point'}) + + +def law_jacobian_weierstrass_add(A, B, pa, pb, pA, pB, pC): + """Check whether the passed set of coordinates is a valid Jacobian add, given assumptions""" + assumeLaw = (good_affine_point(pa) + + good_affine_point(pb) + + good_jacobian_point(pA) + + good_jacobian_point(pB) + + on_weierstrass_curve(A, B, pa) + + on_weierstrass_curve(A, B, pb) + + finite(pA) + + finite(pB) + + constraints(nonzero={pa.x - pb.x : 'different_x'})) + require = (finite(pC, lambda pc: on_weierstrass_curve(A, B, pc) + + colinear(pa, pb, negate(pc)))) + return (assumeLaw, require) + + +def law_jacobian_weierstrass_double(A, B, pa, pb, pA, pB, pC): + """Check whether the passed set of coordinates is a valid Jacobian doubling, given assumptions""" + assumeLaw = (good_affine_point(pa) + + good_affine_point(pb) + + good_jacobian_point(pA) + + good_jacobian_point(pB) + + on_weierstrass_curve(A, B, pa) + + on_weierstrass_curve(A, B, pb) + + finite(pA) + + finite(pB) + + constraints(zero={pa.x - pb.x : 'equal_x', pa.y - pb.y : 'equal_y'})) + require = (finite(pC, lambda pc: on_weierstrass_curve(A, B, pc) + + tangential_to_weierstrass_curve(A, B, pa, negate(pc)))) + return (assumeLaw, require) + + +def law_jacobian_weierstrass_add_opposites(A, B, pa, pb, pA, pB, pC): + assumeLaw = (good_affine_point(pa) + + good_affine_point(pb) + + good_jacobian_point(pA) + + good_jacobian_point(pB) + + on_weierstrass_curve(A, B, pa) + + on_weierstrass_curve(A, B, pb) + + finite(pA) + + finite(pB) + + constraints(zero={pa.x - pb.x : 'equal_x', pa.y + pb.y : 'opposite_y'})) + require = infinite(pC) + return (assumeLaw, require) + + +def law_jacobian_weierstrass_add_infinite_a(A, B, pa, pb, pA, pB, pC): + assumeLaw = (good_affine_point(pa) + + good_affine_point(pb) + + good_jacobian_point(pA) + + good_jacobian_point(pB) + + on_weierstrass_curve(A, B, pb) + + infinite(pA) + + finite(pB)) + require = finite(pC, lambda pc: constraints(zero={pc.x - pb.x : 'c.x=b.x', pc.y - pb.y : 'c.y=b.y'})) + return (assumeLaw, require) + + +def law_jacobian_weierstrass_add_infinite_b(A, B, pa, pb, pA, pB, pC): + assumeLaw = (good_affine_point(pa) + + good_affine_point(pb) + + good_jacobian_point(pA) + + good_jacobian_point(pB) + + on_weierstrass_curve(A, B, pa) + + infinite(pB) + + finite(pA)) + require = finite(pC, lambda pc: constraints(zero={pc.x - pa.x : 'c.x=a.x', pc.y - pa.y : 'c.y=a.y'})) + return (assumeLaw, require) + + +def law_jacobian_weierstrass_add_infinite_ab(A, B, pa, pb, pA, pB, pC): + assumeLaw = (good_affine_point(pa) + + good_affine_point(pb) + + good_jacobian_point(pA) + + good_jacobian_point(pB) + + infinite(pA) + + infinite(pB)) + require = infinite(pC) + return (assumeLaw, require) + + +laws_jacobian_weierstrass = { + 'add': law_jacobian_weierstrass_add, + 'double': law_jacobian_weierstrass_double, + 'add_opposite': law_jacobian_weierstrass_add_opposites, + 'add_infinite_a': law_jacobian_weierstrass_add_infinite_a, + 'add_infinite_b': law_jacobian_weierstrass_add_infinite_b, + 'add_infinite_ab': law_jacobian_weierstrass_add_infinite_ab +} + + +def check_exhaustive_jacobian_weierstrass(name, A, B, branches, formula, p): + """Verify an implementation of addition of Jacobian points on a Weierstrass curve, by executing and validating the result for every possible addition in a prime field""" + F = Integers(p) + print "Formula %s on Z%i:" % (name, p) + points = [] + for x in xrange(0, p): + for y in xrange(0, p): + point = affinepoint(F(x), F(y)) + r, e = concrete_verify(on_weierstrass_curve(A, B, point)) + if r: + points.append(point) + + for za in xrange(1, p): + for zb in xrange(1, p): + for pa in points: + for pb in points: + for ia in xrange(2): + for ib in xrange(2): + pA = jacobianpoint(pa.x * F(za)^2, pa.y * F(za)^3, F(za), ia) + pB = jacobianpoint(pb.x * F(zb)^2, pb.y * F(zb)^3, F(zb), ib) + for branch in xrange(0, branches): + assumeAssert, assumeBranch, pC = formula(branch, pA, pB) + pC.X = F(pC.X) + pC.Y = F(pC.Y) + pC.Z = F(pC.Z) + pC.Infinity = F(pC.Infinity) + r, e = concrete_verify(assumeAssert + assumeBranch) + if r: + match = False + for key in laws_jacobian_weierstrass: + assumeLaw, require = laws_jacobian_weierstrass[key](A, B, pa, pb, pA, pB, pC) + r, e = concrete_verify(assumeLaw) + if r: + if match: + print " multiple branches for (%s,%s,%s,%s) + (%s,%s,%s,%s)" % (pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity) + else: + match = True + r, e = concrete_verify(require) + if not r: + print " failure in branch %i for (%s,%s,%s,%s) + (%s,%s,%s,%s) = (%s,%s,%s,%s): %s" % (branch, pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity, pC.X, pC.Y, pC.Z, pC.Infinity, e) + print + + +def check_symbolic_function(R, assumeAssert, assumeBranch, f, A, B, pa, pb, pA, pB, pC): + assumeLaw, require = f(A, B, pa, pb, pA, pB, pC) + return check_symbolic(R, assumeLaw, assumeAssert, assumeBranch, require) + +def check_symbolic_jacobian_weierstrass(name, A, B, branches, formula): + """Verify an implementation of addition of Jacobian points on a Weierstrass curve symbolically""" + R. = PolynomialRing(QQ,8,order='invlex') + lift = lambda x: fastfrac(R,x) + ax = lift(ax) + ay = lift(ay) + Az = lift(Az) + bx = lift(bx) + by = lift(by) + Bz = lift(Bz) + Ai = lift(Ai) + Bi = lift(Bi) + + pa = affinepoint(ax, ay, Ai) + pb = affinepoint(bx, by, Bi) + pA = jacobianpoint(ax * Az^2, ay * Az^3, Az, Ai) + pB = jacobianpoint(bx * Bz^2, by * Bz^3, Bz, Bi) + + res = {} + + for key in laws_jacobian_weierstrass: + res[key] = [] + + print ("Formula " + name + ":") + count = 0 + for branch in xrange(branches): + assumeFormula, assumeBranch, pC = formula(branch, pA, pB) + pC.X = lift(pC.X) + pC.Y = lift(pC.Y) + pC.Z = lift(pC.Z) + pC.Infinity = lift(pC.Infinity) + + for key in laws_jacobian_weierstrass: + res[key].append((check_symbolic_function(R, assumeFormula, assumeBranch, laws_jacobian_weierstrass[key], A, B, pa, pb, pA, pB, pC), branch)) + + for key in res: + print " %s:" % key + val = res[key] + for x in val: + if x[0] is not None: + print " branch %i: %s" % (x[1], x[0]) + + print diff --git a/crypto/secp256k1/libsecp256k1/src/asm/field_10x26_arm.s b/crypto/secp256k1/libsecp256k1/src/asm/field_10x26_arm.s new file mode 100644 index 0000000..5a9cc3f --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/asm/field_10x26_arm.s @@ -0,0 +1,919 @@ +@ vim: set tabstop=8 softtabstop=8 shiftwidth=8 noexpandtab syntax=armasm: +/********************************************************************** + * Copyright (c) 2014 Wladimir J. van der Laan * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ +/* +ARM implementation of field_10x26 inner loops. + +Note: + +- To avoid unnecessary loads and make use of available registers, two + 'passes' have every time been interleaved, with the odd passes accumulating c' and d' + which will be added to c and d respectively in the even passes + +*/ + + .syntax unified + .arch armv7-a + @ eabi attributes - see readelf -A + .eabi_attribute 8, 1 @ Tag_ARM_ISA_use = yes + .eabi_attribute 9, 0 @ Tag_Thumb_ISA_use = no + .eabi_attribute 10, 0 @ Tag_FP_arch = none + .eabi_attribute 24, 1 @ Tag_ABI_align_needed = 8-byte + .eabi_attribute 25, 1 @ Tag_ABI_align_preserved = 8-byte, except leaf SP + .eabi_attribute 30, 2 @ Tag_ABI_optimization_goals = Aggressive Speed + .eabi_attribute 34, 1 @ Tag_CPU_unaligned_access = v6 + .text + + @ Field constants + .set field_R0, 0x3d10 + .set field_R1, 0x400 + .set field_not_M, 0xfc000000 @ ~M = ~0x3ffffff + + .align 2 + .global secp256k1_fe_mul_inner + .type secp256k1_fe_mul_inner, %function + @ Arguments: + @ r0 r Restrict: can overlap with a, not with b + @ r1 a + @ r2 b + @ Stack (total 4+10*4 = 44) + @ sp + #0 saved 'r' pointer + @ sp + #4 + 4*X t0,t1,t2,t3,t4,t5,t6,t7,u8,t9 +secp256k1_fe_mul_inner: + stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r14} + sub sp, sp, #48 @ frame=44 + alignment + str r0, [sp, #0] @ save result address, we need it only at the end + + /****************************************** + * Main computation code. + ****************************************** + + Allocation: + r0,r14,r7,r8 scratch + r1 a (pointer) + r2 b (pointer) + r3:r4 c + r5:r6 d + r11:r12 c' + r9:r10 d' + + Note: do not write to r[] here, it may overlap with a[] + */ + + /* A - interleaved with B */ + ldr r7, [r1, #0*4] @ a[0] + ldr r8, [r2, #9*4] @ b[9] + ldr r0, [r1, #1*4] @ a[1] + umull r5, r6, r7, r8 @ d = a[0] * b[9] + ldr r14, [r2, #8*4] @ b[8] + umull r9, r10, r0, r8 @ d' = a[1] * b[9] + ldr r7, [r1, #2*4] @ a[2] + umlal r5, r6, r0, r14 @ d += a[1] * b[8] + ldr r8, [r2, #7*4] @ b[7] + umlal r9, r10, r7, r14 @ d' += a[2] * b[8] + ldr r0, [r1, #3*4] @ a[3] + umlal r5, r6, r7, r8 @ d += a[2] * b[7] + ldr r14, [r2, #6*4] @ b[6] + umlal r9, r10, r0, r8 @ d' += a[3] * b[7] + ldr r7, [r1, #4*4] @ a[4] + umlal r5, r6, r0, r14 @ d += a[3] * b[6] + ldr r8, [r2, #5*4] @ b[5] + umlal r9, r10, r7, r14 @ d' += a[4] * b[6] + ldr r0, [r1, #5*4] @ a[5] + umlal r5, r6, r7, r8 @ d += a[4] * b[5] + ldr r14, [r2, #4*4] @ b[4] + umlal r9, r10, r0, r8 @ d' += a[5] * b[5] + ldr r7, [r1, #6*4] @ a[6] + umlal r5, r6, r0, r14 @ d += a[5] * b[4] + ldr r8, [r2, #3*4] @ b[3] + umlal r9, r10, r7, r14 @ d' += a[6] * b[4] + ldr r0, [r1, #7*4] @ a[7] + umlal r5, r6, r7, r8 @ d += a[6] * b[3] + ldr r14, [r2, #2*4] @ b[2] + umlal r9, r10, r0, r8 @ d' += a[7] * b[3] + ldr r7, [r1, #8*4] @ a[8] + umlal r5, r6, r0, r14 @ d += a[7] * b[2] + ldr r8, [r2, #1*4] @ b[1] + umlal r9, r10, r7, r14 @ d' += a[8] * b[2] + ldr r0, [r1, #9*4] @ a[9] + umlal r5, r6, r7, r8 @ d += a[8] * b[1] + ldr r14, [r2, #0*4] @ b[0] + umlal r9, r10, r0, r8 @ d' += a[9] * b[1] + ldr r7, [r1, #0*4] @ a[0] + umlal r5, r6, r0, r14 @ d += a[9] * b[0] + @ r7,r14 used in B + + bic r0, r5, field_not_M @ t9 = d & M + str r0, [sp, #4 + 4*9] + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + + /* B */ + umull r3, r4, r7, r14 @ c = a[0] * b[0] + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u0 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u0 * R0 + umlal r3, r4, r0, r14 + + bic r14, r3, field_not_M @ t0 = c & M + str r14, [sp, #4 + 0*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u0 * R1 + umlal r3, r4, r0, r14 + + /* C - interleaved with D */ + ldr r7, [r1, #0*4] @ a[0] + ldr r8, [r2, #2*4] @ b[2] + ldr r14, [r2, #1*4] @ b[1] + umull r11, r12, r7, r8 @ c' = a[0] * b[2] + ldr r0, [r1, #1*4] @ a[1] + umlal r3, r4, r7, r14 @ c += a[0] * b[1] + ldr r8, [r2, #0*4] @ b[0] + umlal r11, r12, r0, r14 @ c' += a[1] * b[1] + ldr r7, [r1, #2*4] @ a[2] + umlal r3, r4, r0, r8 @ c += a[1] * b[0] + ldr r14, [r2, #9*4] @ b[9] + umlal r11, r12, r7, r8 @ c' += a[2] * b[0] + ldr r0, [r1, #3*4] @ a[3] + umlal r5, r6, r7, r14 @ d += a[2] * b[9] + ldr r8, [r2, #8*4] @ b[8] + umull r9, r10, r0, r14 @ d' = a[3] * b[9] + ldr r7, [r1, #4*4] @ a[4] + umlal r5, r6, r0, r8 @ d += a[3] * b[8] + ldr r14, [r2, #7*4] @ b[7] + umlal r9, r10, r7, r8 @ d' += a[4] * b[8] + ldr r0, [r1, #5*4] @ a[5] + umlal r5, r6, r7, r14 @ d += a[4] * b[7] + ldr r8, [r2, #6*4] @ b[6] + umlal r9, r10, r0, r14 @ d' += a[5] * b[7] + ldr r7, [r1, #6*4] @ a[6] + umlal r5, r6, r0, r8 @ d += a[5] * b[6] + ldr r14, [r2, #5*4] @ b[5] + umlal r9, r10, r7, r8 @ d' += a[6] * b[6] + ldr r0, [r1, #7*4] @ a[7] + umlal r5, r6, r7, r14 @ d += a[6] * b[5] + ldr r8, [r2, #4*4] @ b[4] + umlal r9, r10, r0, r14 @ d' += a[7] * b[5] + ldr r7, [r1, #8*4] @ a[8] + umlal r5, r6, r0, r8 @ d += a[7] * b[4] + ldr r14, [r2, #3*4] @ b[3] + umlal r9, r10, r7, r8 @ d' += a[8] * b[4] + ldr r0, [r1, #9*4] @ a[9] + umlal r5, r6, r7, r14 @ d += a[8] * b[3] + ldr r8, [r2, #2*4] @ b[2] + umlal r9, r10, r0, r14 @ d' += a[9] * b[3] + umlal r5, r6, r0, r8 @ d += a[9] * b[2] + + bic r0, r5, field_not_M @ u1 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u1 * R0 + umlal r3, r4, r0, r14 + + bic r14, r3, field_not_M @ t1 = c & M + str r14, [sp, #4 + 1*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u1 * R1 + umlal r3, r4, r0, r14 + + /* D */ + adds r3, r3, r11 @ c += c' + adc r4, r4, r12 + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u2 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u2 * R0 + umlal r3, r4, r0, r14 + + bic r14, r3, field_not_M @ t2 = c & M + str r14, [sp, #4 + 2*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u2 * R1 + umlal r3, r4, r0, r14 + + /* E - interleaved with F */ + ldr r7, [r1, #0*4] @ a[0] + ldr r8, [r2, #4*4] @ b[4] + umull r11, r12, r7, r8 @ c' = a[0] * b[4] + ldr r8, [r2, #3*4] @ b[3] + umlal r3, r4, r7, r8 @ c += a[0] * b[3] + ldr r7, [r1, #1*4] @ a[1] + umlal r11, r12, r7, r8 @ c' += a[1] * b[3] + ldr r8, [r2, #2*4] @ b[2] + umlal r3, r4, r7, r8 @ c += a[1] * b[2] + ldr r7, [r1, #2*4] @ a[2] + umlal r11, r12, r7, r8 @ c' += a[2] * b[2] + ldr r8, [r2, #1*4] @ b[1] + umlal r3, r4, r7, r8 @ c += a[2] * b[1] + ldr r7, [r1, #3*4] @ a[3] + umlal r11, r12, r7, r8 @ c' += a[3] * b[1] + ldr r8, [r2, #0*4] @ b[0] + umlal r3, r4, r7, r8 @ c += a[3] * b[0] + ldr r7, [r1, #4*4] @ a[4] + umlal r11, r12, r7, r8 @ c' += a[4] * b[0] + ldr r8, [r2, #9*4] @ b[9] + umlal r5, r6, r7, r8 @ d += a[4] * b[9] + ldr r7, [r1, #5*4] @ a[5] + umull r9, r10, r7, r8 @ d' = a[5] * b[9] + ldr r8, [r2, #8*4] @ b[8] + umlal r5, r6, r7, r8 @ d += a[5] * b[8] + ldr r7, [r1, #6*4] @ a[6] + umlal r9, r10, r7, r8 @ d' += a[6] * b[8] + ldr r8, [r2, #7*4] @ b[7] + umlal r5, r6, r7, r8 @ d += a[6] * b[7] + ldr r7, [r1, #7*4] @ a[7] + umlal r9, r10, r7, r8 @ d' += a[7] * b[7] + ldr r8, [r2, #6*4] @ b[6] + umlal r5, r6, r7, r8 @ d += a[7] * b[6] + ldr r7, [r1, #8*4] @ a[8] + umlal r9, r10, r7, r8 @ d' += a[8] * b[6] + ldr r8, [r2, #5*4] @ b[5] + umlal r5, r6, r7, r8 @ d += a[8] * b[5] + ldr r7, [r1, #9*4] @ a[9] + umlal r9, r10, r7, r8 @ d' += a[9] * b[5] + ldr r8, [r2, #4*4] @ b[4] + umlal r5, r6, r7, r8 @ d += a[9] * b[4] + + bic r0, r5, field_not_M @ u3 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u3 * R0 + umlal r3, r4, r0, r14 + + bic r14, r3, field_not_M @ t3 = c & M + str r14, [sp, #4 + 3*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u3 * R1 + umlal r3, r4, r0, r14 + + /* F */ + adds r3, r3, r11 @ c += c' + adc r4, r4, r12 + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u4 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u4 * R0 + umlal r3, r4, r0, r14 + + bic r14, r3, field_not_M @ t4 = c & M + str r14, [sp, #4 + 4*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u4 * R1 + umlal r3, r4, r0, r14 + + /* G - interleaved with H */ + ldr r7, [r1, #0*4] @ a[0] + ldr r8, [r2, #6*4] @ b[6] + ldr r14, [r2, #5*4] @ b[5] + umull r11, r12, r7, r8 @ c' = a[0] * b[6] + ldr r0, [r1, #1*4] @ a[1] + umlal r3, r4, r7, r14 @ c += a[0] * b[5] + ldr r8, [r2, #4*4] @ b[4] + umlal r11, r12, r0, r14 @ c' += a[1] * b[5] + ldr r7, [r1, #2*4] @ a[2] + umlal r3, r4, r0, r8 @ c += a[1] * b[4] + ldr r14, [r2, #3*4] @ b[3] + umlal r11, r12, r7, r8 @ c' += a[2] * b[4] + ldr r0, [r1, #3*4] @ a[3] + umlal r3, r4, r7, r14 @ c += a[2] * b[3] + ldr r8, [r2, #2*4] @ b[2] + umlal r11, r12, r0, r14 @ c' += a[3] * b[3] + ldr r7, [r1, #4*4] @ a[4] + umlal r3, r4, r0, r8 @ c += a[3] * b[2] + ldr r14, [r2, #1*4] @ b[1] + umlal r11, r12, r7, r8 @ c' += a[4] * b[2] + ldr r0, [r1, #5*4] @ a[5] + umlal r3, r4, r7, r14 @ c += a[4] * b[1] + ldr r8, [r2, #0*4] @ b[0] + umlal r11, r12, r0, r14 @ c' += a[5] * b[1] + ldr r7, [r1, #6*4] @ a[6] + umlal r3, r4, r0, r8 @ c += a[5] * b[0] + ldr r14, [r2, #9*4] @ b[9] + umlal r11, r12, r7, r8 @ c' += a[6] * b[0] + ldr r0, [r1, #7*4] @ a[7] + umlal r5, r6, r7, r14 @ d += a[6] * b[9] + ldr r8, [r2, #8*4] @ b[8] + umull r9, r10, r0, r14 @ d' = a[7] * b[9] + ldr r7, [r1, #8*4] @ a[8] + umlal r5, r6, r0, r8 @ d += a[7] * b[8] + ldr r14, [r2, #7*4] @ b[7] + umlal r9, r10, r7, r8 @ d' += a[8] * b[8] + ldr r0, [r1, #9*4] @ a[9] + umlal r5, r6, r7, r14 @ d += a[8] * b[7] + ldr r8, [r2, #6*4] @ b[6] + umlal r9, r10, r0, r14 @ d' += a[9] * b[7] + umlal r5, r6, r0, r8 @ d += a[9] * b[6] + + bic r0, r5, field_not_M @ u5 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u5 * R0 + umlal r3, r4, r0, r14 + + bic r14, r3, field_not_M @ t5 = c & M + str r14, [sp, #4 + 5*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u5 * R1 + umlal r3, r4, r0, r14 + + /* H */ + adds r3, r3, r11 @ c += c' + adc r4, r4, r12 + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u6 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u6 * R0 + umlal r3, r4, r0, r14 + + bic r14, r3, field_not_M @ t6 = c & M + str r14, [sp, #4 + 6*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u6 * R1 + umlal r3, r4, r0, r14 + + /* I - interleaved with J */ + ldr r8, [r2, #8*4] @ b[8] + ldr r7, [r1, #0*4] @ a[0] + ldr r14, [r2, #7*4] @ b[7] + umull r11, r12, r7, r8 @ c' = a[0] * b[8] + ldr r0, [r1, #1*4] @ a[1] + umlal r3, r4, r7, r14 @ c += a[0] * b[7] + ldr r8, [r2, #6*4] @ b[6] + umlal r11, r12, r0, r14 @ c' += a[1] * b[7] + ldr r7, [r1, #2*4] @ a[2] + umlal r3, r4, r0, r8 @ c += a[1] * b[6] + ldr r14, [r2, #5*4] @ b[5] + umlal r11, r12, r7, r8 @ c' += a[2] * b[6] + ldr r0, [r1, #3*4] @ a[3] + umlal r3, r4, r7, r14 @ c += a[2] * b[5] + ldr r8, [r2, #4*4] @ b[4] + umlal r11, r12, r0, r14 @ c' += a[3] * b[5] + ldr r7, [r1, #4*4] @ a[4] + umlal r3, r4, r0, r8 @ c += a[3] * b[4] + ldr r14, [r2, #3*4] @ b[3] + umlal r11, r12, r7, r8 @ c' += a[4] * b[4] + ldr r0, [r1, #5*4] @ a[5] + umlal r3, r4, r7, r14 @ c += a[4] * b[3] + ldr r8, [r2, #2*4] @ b[2] + umlal r11, r12, r0, r14 @ c' += a[5] * b[3] + ldr r7, [r1, #6*4] @ a[6] + umlal r3, r4, r0, r8 @ c += a[5] * b[2] + ldr r14, [r2, #1*4] @ b[1] + umlal r11, r12, r7, r8 @ c' += a[6] * b[2] + ldr r0, [r1, #7*4] @ a[7] + umlal r3, r4, r7, r14 @ c += a[6] * b[1] + ldr r8, [r2, #0*4] @ b[0] + umlal r11, r12, r0, r14 @ c' += a[7] * b[1] + ldr r7, [r1, #8*4] @ a[8] + umlal r3, r4, r0, r8 @ c += a[7] * b[0] + ldr r14, [r2, #9*4] @ b[9] + umlal r11, r12, r7, r8 @ c' += a[8] * b[0] + ldr r0, [r1, #9*4] @ a[9] + umlal r5, r6, r7, r14 @ d += a[8] * b[9] + ldr r8, [r2, #8*4] @ b[8] + umull r9, r10, r0, r14 @ d' = a[9] * b[9] + umlal r5, r6, r0, r8 @ d += a[9] * b[8] + + bic r0, r5, field_not_M @ u7 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u7 * R0 + umlal r3, r4, r0, r14 + + bic r14, r3, field_not_M @ t7 = c & M + str r14, [sp, #4 + 7*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u7 * R1 + umlal r3, r4, r0, r14 + + /* J */ + adds r3, r3, r11 @ c += c' + adc r4, r4, r12 + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u8 = d & M + str r0, [sp, #4 + 8*4] + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u8 * R0 + umlal r3, r4, r0, r14 + + /****************************************** + * compute and write back result + ****************************************** + Allocation: + r0 r + r3:r4 c + r5:r6 d + r7 t0 + r8 t1 + r9 t2 + r11 u8 + r12 t9 + r1,r2,r10,r14 scratch + + Note: do not read from a[] after here, it may overlap with r[] + */ + ldr r0, [sp, #0] + add r1, sp, #4 + 3*4 @ r[3..7] = t3..7, r11=u8, r12=t9 + ldmia r1, {r2,r7,r8,r9,r10,r11,r12} + add r1, r0, #3*4 + stmia r1, {r2,r7,r8,r9,r10} + + bic r2, r3, field_not_M @ r[8] = c & M + str r2, [r0, #8*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u8 * R1 + umlal r3, r4, r11, r14 + movw r14, field_R0 @ c += d * R0 + umlal r3, r4, r5, r14 + adds r3, r3, r12 @ c += t9 + adc r4, r4, #0 + + add r1, sp, #4 + 0*4 @ r7,r8,r9 = t0,t1,t2 + ldmia r1, {r7,r8,r9} + + ubfx r2, r3, #0, #22 @ r[9] = c & (M >> 4) + str r2, [r0, #9*4] + mov r3, r3, lsr #22 @ c >>= 22 + orr r3, r3, r4, asl #10 + mov r4, r4, lsr #22 + movw r14, field_R1 << 4 @ c += d * (R1 << 4) + umlal r3, r4, r5, r14 + + movw r14, field_R0 >> 4 @ d = c * (R0 >> 4) + t0 (64x64 multiply+add) + umull r5, r6, r3, r14 @ d = c.lo * (R0 >> 4) + adds r5, r5, r7 @ d.lo += t0 + mla r6, r14, r4, r6 @ d.hi += c.hi * (R0 >> 4) + adc r6, r6, 0 @ d.hi += carry + + bic r2, r5, field_not_M @ r[0] = d & M + str r2, [r0, #0*4] + + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + + movw r14, field_R1 >> 4 @ d += c * (R1 >> 4) + t1 (64x64 multiply+add) + umull r1, r2, r3, r14 @ tmp = c.lo * (R1 >> 4) + adds r5, r5, r8 @ d.lo += t1 + adc r6, r6, #0 @ d.hi += carry + adds r5, r5, r1 @ d.lo += tmp.lo + mla r2, r14, r4, r2 @ tmp.hi += c.hi * (R1 >> 4) + adc r6, r6, r2 @ d.hi += carry + tmp.hi + + bic r2, r5, field_not_M @ r[1] = d & M + str r2, [r0, #1*4] + mov r5, r5, lsr #26 @ d >>= 26 (ignore hi) + orr r5, r5, r6, asl #6 + + add r5, r5, r9 @ d += t2 + str r5, [r0, #2*4] @ r[2] = d + + add sp, sp, #48 + ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc} + .size secp256k1_fe_mul_inner, .-secp256k1_fe_mul_inner + + .align 2 + .global secp256k1_fe_sqr_inner + .type secp256k1_fe_sqr_inner, %function + @ Arguments: + @ r0 r Can overlap with a + @ r1 a + @ Stack (total 4+10*4 = 44) + @ sp + #0 saved 'r' pointer + @ sp + #4 + 4*X t0,t1,t2,t3,t4,t5,t6,t7,u8,t9 +secp256k1_fe_sqr_inner: + stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r14} + sub sp, sp, #48 @ frame=44 + alignment + str r0, [sp, #0] @ save result address, we need it only at the end + /****************************************** + * Main computation code. + ****************************************** + + Allocation: + r0,r14,r2,r7,r8 scratch + r1 a (pointer) + r3:r4 c + r5:r6 d + r11:r12 c' + r9:r10 d' + + Note: do not write to r[] here, it may overlap with a[] + */ + /* A interleaved with B */ + ldr r0, [r1, #1*4] @ a[1]*2 + ldr r7, [r1, #0*4] @ a[0] + mov r0, r0, asl #1 + ldr r14, [r1, #9*4] @ a[9] + umull r3, r4, r7, r7 @ c = a[0] * a[0] + ldr r8, [r1, #8*4] @ a[8] + mov r7, r7, asl #1 + umull r5, r6, r7, r14 @ d = a[0]*2 * a[9] + ldr r7, [r1, #2*4] @ a[2]*2 + umull r9, r10, r0, r14 @ d' = a[1]*2 * a[9] + ldr r14, [r1, #7*4] @ a[7] + umlal r5, r6, r0, r8 @ d += a[1]*2 * a[8] + mov r7, r7, asl #1 + ldr r0, [r1, #3*4] @ a[3]*2 + umlal r9, r10, r7, r8 @ d' += a[2]*2 * a[8] + ldr r8, [r1, #6*4] @ a[6] + umlal r5, r6, r7, r14 @ d += a[2]*2 * a[7] + mov r0, r0, asl #1 + ldr r7, [r1, #4*4] @ a[4]*2 + umlal r9, r10, r0, r14 @ d' += a[3]*2 * a[7] + ldr r14, [r1, #5*4] @ a[5] + mov r7, r7, asl #1 + umlal r5, r6, r0, r8 @ d += a[3]*2 * a[6] + umlal r9, r10, r7, r8 @ d' += a[4]*2 * a[6] + umlal r5, r6, r7, r14 @ d += a[4]*2 * a[5] + umlal r9, r10, r14, r14 @ d' += a[5] * a[5] + + bic r0, r5, field_not_M @ t9 = d & M + str r0, [sp, #4 + 9*4] + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + + /* B */ + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u0 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u0 * R0 + umlal r3, r4, r0, r14 + bic r14, r3, field_not_M @ t0 = c & M + str r14, [sp, #4 + 0*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u0 * R1 + umlal r3, r4, r0, r14 + + /* C interleaved with D */ + ldr r0, [r1, #0*4] @ a[0]*2 + ldr r14, [r1, #1*4] @ a[1] + mov r0, r0, asl #1 + ldr r8, [r1, #2*4] @ a[2] + umlal r3, r4, r0, r14 @ c += a[0]*2 * a[1] + mov r7, r8, asl #1 @ a[2]*2 + umull r11, r12, r14, r14 @ c' = a[1] * a[1] + ldr r14, [r1, #9*4] @ a[9] + umlal r11, r12, r0, r8 @ c' += a[0]*2 * a[2] + ldr r0, [r1, #3*4] @ a[3]*2 + ldr r8, [r1, #8*4] @ a[8] + umlal r5, r6, r7, r14 @ d += a[2]*2 * a[9] + mov r0, r0, asl #1 + ldr r7, [r1, #4*4] @ a[4]*2 + umull r9, r10, r0, r14 @ d' = a[3]*2 * a[9] + ldr r14, [r1, #7*4] @ a[7] + umlal r5, r6, r0, r8 @ d += a[3]*2 * a[8] + mov r7, r7, asl #1 + ldr r0, [r1, #5*4] @ a[5]*2 + umlal r9, r10, r7, r8 @ d' += a[4]*2 * a[8] + ldr r8, [r1, #6*4] @ a[6] + mov r0, r0, asl #1 + umlal r5, r6, r7, r14 @ d += a[4]*2 * a[7] + umlal r9, r10, r0, r14 @ d' += a[5]*2 * a[7] + umlal r5, r6, r0, r8 @ d += a[5]*2 * a[6] + umlal r9, r10, r8, r8 @ d' += a[6] * a[6] + + bic r0, r5, field_not_M @ u1 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u1 * R0 + umlal r3, r4, r0, r14 + bic r14, r3, field_not_M @ t1 = c & M + str r14, [sp, #4 + 1*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u1 * R1 + umlal r3, r4, r0, r14 + + /* D */ + adds r3, r3, r11 @ c += c' + adc r4, r4, r12 + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u2 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u2 * R0 + umlal r3, r4, r0, r14 + bic r14, r3, field_not_M @ t2 = c & M + str r14, [sp, #4 + 2*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u2 * R1 + umlal r3, r4, r0, r14 + + /* E interleaved with F */ + ldr r7, [r1, #0*4] @ a[0]*2 + ldr r0, [r1, #1*4] @ a[1]*2 + ldr r14, [r1, #2*4] @ a[2] + mov r7, r7, asl #1 + ldr r8, [r1, #3*4] @ a[3] + ldr r2, [r1, #4*4] + umlal r3, r4, r7, r8 @ c += a[0]*2 * a[3] + mov r0, r0, asl #1 + umull r11, r12, r7, r2 @ c' = a[0]*2 * a[4] + mov r2, r2, asl #1 @ a[4]*2 + umlal r11, r12, r0, r8 @ c' += a[1]*2 * a[3] + ldr r8, [r1, #9*4] @ a[9] + umlal r3, r4, r0, r14 @ c += a[1]*2 * a[2] + ldr r0, [r1, #5*4] @ a[5]*2 + umlal r11, r12, r14, r14 @ c' += a[2] * a[2] + ldr r14, [r1, #8*4] @ a[8] + mov r0, r0, asl #1 + umlal r5, r6, r2, r8 @ d += a[4]*2 * a[9] + ldr r7, [r1, #6*4] @ a[6]*2 + umull r9, r10, r0, r8 @ d' = a[5]*2 * a[9] + mov r7, r7, asl #1 + ldr r8, [r1, #7*4] @ a[7] + umlal r5, r6, r0, r14 @ d += a[5]*2 * a[8] + umlal r9, r10, r7, r14 @ d' += a[6]*2 * a[8] + umlal r5, r6, r7, r8 @ d += a[6]*2 * a[7] + umlal r9, r10, r8, r8 @ d' += a[7] * a[7] + + bic r0, r5, field_not_M @ u3 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u3 * R0 + umlal r3, r4, r0, r14 + bic r14, r3, field_not_M @ t3 = c & M + str r14, [sp, #4 + 3*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u3 * R1 + umlal r3, r4, r0, r14 + + /* F */ + adds r3, r3, r11 @ c += c' + adc r4, r4, r12 + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u4 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u4 * R0 + umlal r3, r4, r0, r14 + bic r14, r3, field_not_M @ t4 = c & M + str r14, [sp, #4 + 4*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u4 * R1 + umlal r3, r4, r0, r14 + + /* G interleaved with H */ + ldr r7, [r1, #0*4] @ a[0]*2 + ldr r0, [r1, #1*4] @ a[1]*2 + mov r7, r7, asl #1 + ldr r8, [r1, #5*4] @ a[5] + ldr r2, [r1, #6*4] @ a[6] + umlal r3, r4, r7, r8 @ c += a[0]*2 * a[5] + ldr r14, [r1, #4*4] @ a[4] + mov r0, r0, asl #1 + umull r11, r12, r7, r2 @ c' = a[0]*2 * a[6] + ldr r7, [r1, #2*4] @ a[2]*2 + umlal r11, r12, r0, r8 @ c' += a[1]*2 * a[5] + mov r7, r7, asl #1 + ldr r8, [r1, #3*4] @ a[3] + umlal r3, r4, r0, r14 @ c += a[1]*2 * a[4] + mov r0, r2, asl #1 @ a[6]*2 + umlal r11, r12, r7, r14 @ c' += a[2]*2 * a[4] + ldr r14, [r1, #9*4] @ a[9] + umlal r3, r4, r7, r8 @ c += a[2]*2 * a[3] + ldr r7, [r1, #7*4] @ a[7]*2 + umlal r11, r12, r8, r8 @ c' += a[3] * a[3] + mov r7, r7, asl #1 + ldr r8, [r1, #8*4] @ a[8] + umlal r5, r6, r0, r14 @ d += a[6]*2 * a[9] + umull r9, r10, r7, r14 @ d' = a[7]*2 * a[9] + umlal r5, r6, r7, r8 @ d += a[7]*2 * a[8] + umlal r9, r10, r8, r8 @ d' += a[8] * a[8] + + bic r0, r5, field_not_M @ u5 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u5 * R0 + umlal r3, r4, r0, r14 + bic r14, r3, field_not_M @ t5 = c & M + str r14, [sp, #4 + 5*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u5 * R1 + umlal r3, r4, r0, r14 + + /* H */ + adds r3, r3, r11 @ c += c' + adc r4, r4, r12 + adds r5, r5, r9 @ d += d' + adc r6, r6, r10 + + bic r0, r5, field_not_M @ u6 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u6 * R0 + umlal r3, r4, r0, r14 + bic r14, r3, field_not_M @ t6 = c & M + str r14, [sp, #4 + 6*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u6 * R1 + umlal r3, r4, r0, r14 + + /* I interleaved with J */ + ldr r7, [r1, #0*4] @ a[0]*2 + ldr r0, [r1, #1*4] @ a[1]*2 + mov r7, r7, asl #1 + ldr r8, [r1, #7*4] @ a[7] + ldr r2, [r1, #8*4] @ a[8] + umlal r3, r4, r7, r8 @ c += a[0]*2 * a[7] + ldr r14, [r1, #6*4] @ a[6] + mov r0, r0, asl #1 + umull r11, r12, r7, r2 @ c' = a[0]*2 * a[8] + ldr r7, [r1, #2*4] @ a[2]*2 + umlal r11, r12, r0, r8 @ c' += a[1]*2 * a[7] + ldr r8, [r1, #5*4] @ a[5] + umlal r3, r4, r0, r14 @ c += a[1]*2 * a[6] + ldr r0, [r1, #3*4] @ a[3]*2 + mov r7, r7, asl #1 + umlal r11, r12, r7, r14 @ c' += a[2]*2 * a[6] + ldr r14, [r1, #4*4] @ a[4] + mov r0, r0, asl #1 + umlal r3, r4, r7, r8 @ c += a[2]*2 * a[5] + mov r2, r2, asl #1 @ a[8]*2 + umlal r11, r12, r0, r8 @ c' += a[3]*2 * a[5] + umlal r3, r4, r0, r14 @ c += a[3]*2 * a[4] + umlal r11, r12, r14, r14 @ c' += a[4] * a[4] + ldr r8, [r1, #9*4] @ a[9] + umlal r5, r6, r2, r8 @ d += a[8]*2 * a[9] + @ r8 will be used in J + + bic r0, r5, field_not_M @ u7 = d & M + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u7 * R0 + umlal r3, r4, r0, r14 + bic r14, r3, field_not_M @ t7 = c & M + str r14, [sp, #4 + 7*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u7 * R1 + umlal r3, r4, r0, r14 + + /* J */ + adds r3, r3, r11 @ c += c' + adc r4, r4, r12 + umlal r5, r6, r8, r8 @ d += a[9] * a[9] + + bic r0, r5, field_not_M @ u8 = d & M + str r0, [sp, #4 + 8*4] + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + movw r14, field_R0 @ c += u8 * R0 + umlal r3, r4, r0, r14 + + /****************************************** + * compute and write back result + ****************************************** + Allocation: + r0 r + r3:r4 c + r5:r6 d + r7 t0 + r8 t1 + r9 t2 + r11 u8 + r12 t9 + r1,r2,r10,r14 scratch + + Note: do not read from a[] after here, it may overlap with r[] + */ + ldr r0, [sp, #0] + add r1, sp, #4 + 3*4 @ r[3..7] = t3..7, r11=u8, r12=t9 + ldmia r1, {r2,r7,r8,r9,r10,r11,r12} + add r1, r0, #3*4 + stmia r1, {r2,r7,r8,r9,r10} + + bic r2, r3, field_not_M @ r[8] = c & M + str r2, [r0, #8*4] + mov r3, r3, lsr #26 @ c >>= 26 + orr r3, r3, r4, asl #6 + mov r4, r4, lsr #26 + mov r14, field_R1 @ c += u8 * R1 + umlal r3, r4, r11, r14 + movw r14, field_R0 @ c += d * R0 + umlal r3, r4, r5, r14 + adds r3, r3, r12 @ c += t9 + adc r4, r4, #0 + + add r1, sp, #4 + 0*4 @ r7,r8,r9 = t0,t1,t2 + ldmia r1, {r7,r8,r9} + + ubfx r2, r3, #0, #22 @ r[9] = c & (M >> 4) + str r2, [r0, #9*4] + mov r3, r3, lsr #22 @ c >>= 22 + orr r3, r3, r4, asl #10 + mov r4, r4, lsr #22 + movw r14, field_R1 << 4 @ c += d * (R1 << 4) + umlal r3, r4, r5, r14 + + movw r14, field_R0 >> 4 @ d = c * (R0 >> 4) + t0 (64x64 multiply+add) + umull r5, r6, r3, r14 @ d = c.lo * (R0 >> 4) + adds r5, r5, r7 @ d.lo += t0 + mla r6, r14, r4, r6 @ d.hi += c.hi * (R0 >> 4) + adc r6, r6, 0 @ d.hi += carry + + bic r2, r5, field_not_M @ r[0] = d & M + str r2, [r0, #0*4] + + mov r5, r5, lsr #26 @ d >>= 26 + orr r5, r5, r6, asl #6 + mov r6, r6, lsr #26 + + movw r14, field_R1 >> 4 @ d += c * (R1 >> 4) + t1 (64x64 multiply+add) + umull r1, r2, r3, r14 @ tmp = c.lo * (R1 >> 4) + adds r5, r5, r8 @ d.lo += t1 + adc r6, r6, #0 @ d.hi += carry + adds r5, r5, r1 @ d.lo += tmp.lo + mla r2, r14, r4, r2 @ tmp.hi += c.hi * (R1 >> 4) + adc r6, r6, r2 @ d.hi += carry + tmp.hi + + bic r2, r5, field_not_M @ r[1] = d & M + str r2, [r0, #1*4] + mov r5, r5, lsr #26 @ d >>= 26 (ignore hi) + orr r5, r5, r6, asl #6 + + add r5, r5, r9 @ d += t2 + str r5, [r0, #2*4] @ r[2] = d + + add sp, sp, #48 + ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc} + .size secp256k1_fe_sqr_inner, .-secp256k1_fe_sqr_inner + diff --git a/crypto/secp256k1/libsecp256k1/src/basic-config.h b/crypto/secp256k1/libsecp256k1/src/basic-config.h new file mode 100644 index 0000000..c4c16eb --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/basic-config.h @@ -0,0 +1,32 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_BASIC_CONFIG_ +#define _SECP256K1_BASIC_CONFIG_ + +#ifdef USE_BASIC_CONFIG + +#undef USE_ASM_X86_64 +#undef USE_ENDOMORPHISM +#undef USE_FIELD_10X26 +#undef USE_FIELD_5X52 +#undef USE_FIELD_INV_BUILTIN +#undef USE_FIELD_INV_NUM +#undef USE_NUM_GMP +#undef USE_NUM_NONE +#undef USE_SCALAR_4X64 +#undef USE_SCALAR_8X32 +#undef USE_SCALAR_INV_BUILTIN +#undef USE_SCALAR_INV_NUM + +#define USE_NUM_NONE 1 +#define USE_FIELD_INV_BUILTIN 1 +#define USE_SCALAR_INV_BUILTIN 1 +#define USE_FIELD_10X26 1 +#define USE_SCALAR_8X32 1 + +#endif // USE_BASIC_CONFIG +#endif // _SECP256K1_BASIC_CONFIG_ diff --git a/crypto/secp256k1/libsecp256k1/src/bench.h b/crypto/secp256k1/libsecp256k1/src/bench.h new file mode 100644 index 0000000..3a71b4a --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/bench.h @@ -0,0 +1,66 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_BENCH_H_ +#define _SECP256K1_BENCH_H_ + +#include +#include +#include "sys/time.h" + +static double gettimedouble(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_usec * 0.000001 + tv.tv_sec; +} + +void print_number(double x) { + double y = x; + int c = 0; + if (y < 0.0) { + y = -y; + } + while (y < 100.0) { + y *= 10.0; + c++; + } + printf("%.*f", c, x); +} + +void run_benchmark(char *name, void (*benchmark)(void*), void (*setup)(void*), void (*teardown)(void*), void* data, int count, int iter) { + int i; + double min = HUGE_VAL; + double sum = 0.0; + double max = 0.0; + for (i = 0; i < count; i++) { + double begin, total; + if (setup != NULL) { + setup(data); + } + begin = gettimedouble(); + benchmark(data); + total = gettimedouble() - begin; + if (teardown != NULL) { + teardown(data); + } + if (total < min) { + min = total; + } + if (total > max) { + max = total; + } + sum += total; + } + printf("%s: min ", name); + print_number(min * 1000000.0 / iter); + printf("us / avg "); + print_number((sum / count) * 1000000.0 / iter); + printf("us / max "); + print_number(max * 1000000.0 / iter); + printf("us\n"); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/bench_ecdh.c b/crypto/secp256k1/libsecp256k1/src/bench_ecdh.c new file mode 100644 index 0000000..cde5e2d --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/bench_ecdh.c @@ -0,0 +1,54 @@ +/********************************************************************** + * Copyright (c) 2015 Pieter Wuille, Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include + +#include "include/secp256k1.h" +#include "include/secp256k1_ecdh.h" +#include "util.h" +#include "bench.h" + +typedef struct { + secp256k1_context *ctx; + secp256k1_pubkey point; + unsigned char scalar[32]; +} bench_ecdh_t; + +static void bench_ecdh_setup(void* arg) { + int i; + bench_ecdh_t *data = (bench_ecdh_t*)arg; + const unsigned char point[] = { + 0x03, + 0x54, 0x94, 0xc1, 0x5d, 0x32, 0x09, 0x97, 0x06, + 0xc2, 0x39, 0x5f, 0x94, 0x34, 0x87, 0x45, 0xfd, + 0x75, 0x7c, 0xe3, 0x0e, 0x4e, 0x8c, 0x90, 0xfb, + 0xa2, 0xba, 0xd1, 0x84, 0xf8, 0x83, 0xc6, 0x9f + }; + + /* create a context with no capabilities */ + data->ctx = secp256k1_context_create(SECP256K1_FLAGS_TYPE_CONTEXT); + for (i = 0; i < 32; i++) { + data->scalar[i] = i + 1; + } + CHECK(secp256k1_ec_pubkey_parse(data->ctx, &data->point, point, sizeof(point)) == 1); +} + +static void bench_ecdh(void* arg) { + int i; + unsigned char res[32]; + bench_ecdh_t *data = (bench_ecdh_t*)arg; + + for (i = 0; i < 20000; i++) { + CHECK(secp256k1_ecdh(data->ctx, res, &data->point, data->scalar) == 1); + } +} + +int main(void) { + bench_ecdh_t data; + + run_benchmark("ecdh", bench_ecdh, bench_ecdh_setup, NULL, &data, 10, 20000); + return 0; +} diff --git a/crypto/secp256k1/libsecp256k1/src/bench_internal.c b/crypto/secp256k1/libsecp256k1/src/bench_internal.c new file mode 100644 index 0000000..0809f77 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/bench_internal.c @@ -0,0 +1,382 @@ +/********************************************************************** + * Copyright (c) 2014-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ +#include + +#include "include/secp256k1.h" + +#include "util.h" +#include "hash_impl.h" +#include "num_impl.h" +#include "field_impl.h" +#include "group_impl.h" +#include "scalar_impl.h" +#include "ecmult_const_impl.h" +#include "ecmult_impl.h" +#include "bench.h" +#include "secp256k1.c" + +typedef struct { + secp256k1_scalar scalar_x, scalar_y; + secp256k1_fe fe_x, fe_y; + secp256k1_ge ge_x, ge_y; + secp256k1_gej gej_x, gej_y; + unsigned char data[64]; + int wnaf[256]; +} bench_inv_t; + +void bench_setup(void* arg) { + bench_inv_t *data = (bench_inv_t*)arg; + + static const unsigned char init_x[32] = { + 0x02, 0x03, 0x05, 0x07, 0x0b, 0x0d, 0x11, 0x13, + 0x17, 0x1d, 0x1f, 0x25, 0x29, 0x2b, 0x2f, 0x35, + 0x3b, 0x3d, 0x43, 0x47, 0x49, 0x4f, 0x53, 0x59, + 0x61, 0x65, 0x67, 0x6b, 0x6d, 0x71, 0x7f, 0x83 + }; + + static const unsigned char init_y[32] = { + 0x82, 0x83, 0x85, 0x87, 0x8b, 0x8d, 0x81, 0x83, + 0x97, 0xad, 0xaf, 0xb5, 0xb9, 0xbb, 0xbf, 0xc5, + 0xdb, 0xdd, 0xe3, 0xe7, 0xe9, 0xef, 0xf3, 0xf9, + 0x11, 0x15, 0x17, 0x1b, 0x1d, 0xb1, 0xbf, 0xd3 + }; + + secp256k1_scalar_set_b32(&data->scalar_x, init_x, NULL); + secp256k1_scalar_set_b32(&data->scalar_y, init_y, NULL); + secp256k1_fe_set_b32(&data->fe_x, init_x); + secp256k1_fe_set_b32(&data->fe_y, init_y); + CHECK(secp256k1_ge_set_xo_var(&data->ge_x, &data->fe_x, 0)); + CHECK(secp256k1_ge_set_xo_var(&data->ge_y, &data->fe_y, 1)); + secp256k1_gej_set_ge(&data->gej_x, &data->ge_x); + secp256k1_gej_set_ge(&data->gej_y, &data->ge_y); + memcpy(data->data, init_x, 32); + memcpy(data->data + 32, init_y, 32); +} + +void bench_scalar_add(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 2000000; i++) { + secp256k1_scalar_add(&data->scalar_x, &data->scalar_x, &data->scalar_y); + } +} + +void bench_scalar_negate(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 2000000; i++) { + secp256k1_scalar_negate(&data->scalar_x, &data->scalar_x); + } +} + +void bench_scalar_sqr(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 200000; i++) { + secp256k1_scalar_sqr(&data->scalar_x, &data->scalar_x); + } +} + +void bench_scalar_mul(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 200000; i++) { + secp256k1_scalar_mul(&data->scalar_x, &data->scalar_x, &data->scalar_y); + } +} + +#ifdef USE_ENDOMORPHISM +void bench_scalar_split(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 20000; i++) { + secp256k1_scalar l, r; + secp256k1_scalar_split_lambda(&l, &r, &data->scalar_x); + secp256k1_scalar_add(&data->scalar_x, &data->scalar_x, &data->scalar_y); + } +} +#endif + +void bench_scalar_inverse(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 2000; i++) { + secp256k1_scalar_inverse(&data->scalar_x, &data->scalar_x); + secp256k1_scalar_add(&data->scalar_x, &data->scalar_x, &data->scalar_y); + } +} + +void bench_scalar_inverse_var(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 2000; i++) { + secp256k1_scalar_inverse_var(&data->scalar_x, &data->scalar_x); + secp256k1_scalar_add(&data->scalar_x, &data->scalar_x, &data->scalar_y); + } +} + +void bench_field_normalize(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 2000000; i++) { + secp256k1_fe_normalize(&data->fe_x); + } +} + +void bench_field_normalize_weak(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 2000000; i++) { + secp256k1_fe_normalize_weak(&data->fe_x); + } +} + +void bench_field_mul(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 200000; i++) { + secp256k1_fe_mul(&data->fe_x, &data->fe_x, &data->fe_y); + } +} + +void bench_field_sqr(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 200000; i++) { + secp256k1_fe_sqr(&data->fe_x, &data->fe_x); + } +} + +void bench_field_inverse(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 20000; i++) { + secp256k1_fe_inv(&data->fe_x, &data->fe_x); + secp256k1_fe_add(&data->fe_x, &data->fe_y); + } +} + +void bench_field_inverse_var(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 20000; i++) { + secp256k1_fe_inv_var(&data->fe_x, &data->fe_x); + secp256k1_fe_add(&data->fe_x, &data->fe_y); + } +} + +void bench_field_sqrt(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 20000; i++) { + secp256k1_fe_sqrt(&data->fe_x, &data->fe_x); + secp256k1_fe_add(&data->fe_x, &data->fe_y); + } +} + +void bench_group_double_var(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 200000; i++) { + secp256k1_gej_double_var(&data->gej_x, &data->gej_x, NULL); + } +} + +void bench_group_add_var(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 200000; i++) { + secp256k1_gej_add_var(&data->gej_x, &data->gej_x, &data->gej_y, NULL); + } +} + +void bench_group_add_affine(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 200000; i++) { + secp256k1_gej_add_ge(&data->gej_x, &data->gej_x, &data->ge_y); + } +} + +void bench_group_add_affine_var(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 200000; i++) { + secp256k1_gej_add_ge_var(&data->gej_x, &data->gej_x, &data->ge_y, NULL); + } +} + +void bench_group_jacobi_var(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 20000; i++) { + secp256k1_gej_has_quad_y_var(&data->gej_x); + } +} + +void bench_ecmult_wnaf(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 20000; i++) { + secp256k1_ecmult_wnaf(data->wnaf, 256, &data->scalar_x, WINDOW_A); + secp256k1_scalar_add(&data->scalar_x, &data->scalar_x, &data->scalar_y); + } +} + +void bench_wnaf_const(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + + for (i = 0; i < 20000; i++) { + secp256k1_wnaf_const(data->wnaf, data->scalar_x, WINDOW_A); + secp256k1_scalar_add(&data->scalar_x, &data->scalar_x, &data->scalar_y); + } +} + + +void bench_sha256(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + secp256k1_sha256_t sha; + + for (i = 0; i < 20000; i++) { + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, data->data, 32); + secp256k1_sha256_finalize(&sha, data->data); + } +} + +void bench_hmac_sha256(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + secp256k1_hmac_sha256_t hmac; + + for (i = 0; i < 20000; i++) { + secp256k1_hmac_sha256_initialize(&hmac, data->data, 32); + secp256k1_hmac_sha256_write(&hmac, data->data, 32); + secp256k1_hmac_sha256_finalize(&hmac, data->data); + } +} + +void bench_rfc6979_hmac_sha256(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + secp256k1_rfc6979_hmac_sha256_t rng; + + for (i = 0; i < 20000; i++) { + secp256k1_rfc6979_hmac_sha256_initialize(&rng, data->data, 64); + secp256k1_rfc6979_hmac_sha256_generate(&rng, data->data, 32); + } +} + +void bench_context_verify(void* arg) { + int i; + (void)arg; + for (i = 0; i < 20; i++) { + secp256k1_context_destroy(secp256k1_context_create(SECP256K1_CONTEXT_VERIFY)); + } +} + +void bench_context_sign(void* arg) { + int i; + (void)arg; + for (i = 0; i < 200; i++) { + secp256k1_context_destroy(secp256k1_context_create(SECP256K1_CONTEXT_SIGN)); + } +} + +#ifndef USE_NUM_NONE +void bench_num_jacobi(void* arg) { + int i; + bench_inv_t *data = (bench_inv_t*)arg; + secp256k1_num nx, norder; + + secp256k1_scalar_get_num(&nx, &data->scalar_x); + secp256k1_scalar_order_get_num(&norder); + secp256k1_scalar_get_num(&norder, &data->scalar_y); + + for (i = 0; i < 200000; i++) { + secp256k1_num_jacobi(&nx, &norder); + } +} +#endif + +int have_flag(int argc, char** argv, char *flag) { + char** argm = argv + argc; + argv++; + if (argv == argm) { + return 1; + } + while (argv != NULL && argv != argm) { + if (strcmp(*argv, flag) == 0) { + return 1; + } + argv++; + } + return 0; +} + +int main(int argc, char **argv) { + bench_inv_t data; + if (have_flag(argc, argv, "scalar") || have_flag(argc, argv, "add")) run_benchmark("scalar_add", bench_scalar_add, bench_setup, NULL, &data, 10, 2000000); + if (have_flag(argc, argv, "scalar") || have_flag(argc, argv, "negate")) run_benchmark("scalar_negate", bench_scalar_negate, bench_setup, NULL, &data, 10, 2000000); + if (have_flag(argc, argv, "scalar") || have_flag(argc, argv, "sqr")) run_benchmark("scalar_sqr", bench_scalar_sqr, bench_setup, NULL, &data, 10, 200000); + if (have_flag(argc, argv, "scalar") || have_flag(argc, argv, "mul")) run_benchmark("scalar_mul", bench_scalar_mul, bench_setup, NULL, &data, 10, 200000); +#ifdef USE_ENDOMORPHISM + if (have_flag(argc, argv, "scalar") || have_flag(argc, argv, "split")) run_benchmark("scalar_split", bench_scalar_split, bench_setup, NULL, &data, 10, 20000); +#endif + if (have_flag(argc, argv, "scalar") || have_flag(argc, argv, "inverse")) run_benchmark("scalar_inverse", bench_scalar_inverse, bench_setup, NULL, &data, 10, 2000); + if (have_flag(argc, argv, "scalar") || have_flag(argc, argv, "inverse")) run_benchmark("scalar_inverse_var", bench_scalar_inverse_var, bench_setup, NULL, &data, 10, 2000); + + if (have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize", bench_field_normalize, bench_setup, NULL, &data, 10, 2000000); + if (have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize_weak", bench_field_normalize_weak, bench_setup, NULL, &data, 10, 2000000); + if (have_flag(argc, argv, "field") || have_flag(argc, argv, "sqr")) run_benchmark("field_sqr", bench_field_sqr, bench_setup, NULL, &data, 10, 200000); + if (have_flag(argc, argv, "field") || have_flag(argc, argv, "mul")) run_benchmark("field_mul", bench_field_mul, bench_setup, NULL, &data, 10, 200000); + if (have_flag(argc, argv, "field") || have_flag(argc, argv, "inverse")) run_benchmark("field_inverse", bench_field_inverse, bench_setup, NULL, &data, 10, 20000); + if (have_flag(argc, argv, "field") || have_flag(argc, argv, "inverse")) run_benchmark("field_inverse_var", bench_field_inverse_var, bench_setup, NULL, &data, 10, 20000); + if (have_flag(argc, argv, "field") || have_flag(argc, argv, "sqrt")) run_benchmark("field_sqrt", bench_field_sqrt, bench_setup, NULL, &data, 10, 20000); + + if (have_flag(argc, argv, "group") || have_flag(argc, argv, "double")) run_benchmark("group_double_var", bench_group_double_var, bench_setup, NULL, &data, 10, 200000); + if (have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_var", bench_group_add_var, bench_setup, NULL, &data, 10, 200000); + if (have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_affine", bench_group_add_affine, bench_setup, NULL, &data, 10, 200000); + if (have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_affine_var", bench_group_add_affine_var, bench_setup, NULL, &data, 10, 200000); + if (have_flag(argc, argv, "group") || have_flag(argc, argv, "jacobi")) run_benchmark("group_jacobi_var", bench_group_jacobi_var, bench_setup, NULL, &data, 10, 20000); + + if (have_flag(argc, argv, "ecmult") || have_flag(argc, argv, "wnaf")) run_benchmark("wnaf_const", bench_wnaf_const, bench_setup, NULL, &data, 10, 20000); + if (have_flag(argc, argv, "ecmult") || have_flag(argc, argv, "wnaf")) run_benchmark("ecmult_wnaf", bench_ecmult_wnaf, bench_setup, NULL, &data, 10, 20000); + + if (have_flag(argc, argv, "hash") || have_flag(argc, argv, "sha256")) run_benchmark("hash_sha256", bench_sha256, bench_setup, NULL, &data, 10, 20000); + if (have_flag(argc, argv, "hash") || have_flag(argc, argv, "hmac")) run_benchmark("hash_hmac_sha256", bench_hmac_sha256, bench_setup, NULL, &data, 10, 20000); + if (have_flag(argc, argv, "hash") || have_flag(argc, argv, "rng6979")) run_benchmark("hash_rfc6979_hmac_sha256", bench_rfc6979_hmac_sha256, bench_setup, NULL, &data, 10, 20000); + + if (have_flag(argc, argv, "context") || have_flag(argc, argv, "verify")) run_benchmark("context_verify", bench_context_verify, bench_setup, NULL, &data, 10, 20); + if (have_flag(argc, argv, "context") || have_flag(argc, argv, "sign")) run_benchmark("context_sign", bench_context_sign, bench_setup, NULL, &data, 10, 200); + +#ifndef USE_NUM_NONE + if (have_flag(argc, argv, "num") || have_flag(argc, argv, "jacobi")) run_benchmark("num_jacobi", bench_num_jacobi, bench_setup, NULL, &data, 10, 200000); +#endif + return 0; +} diff --git a/crypto/secp256k1/libsecp256k1/src/bench_recover.c b/crypto/secp256k1/libsecp256k1/src/bench_recover.c new file mode 100644 index 0000000..6489378 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/bench_recover.c @@ -0,0 +1,60 @@ +/********************************************************************** + * Copyright (c) 2014-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include "include/secp256k1.h" +#include "include/secp256k1_recovery.h" +#include "util.h" +#include "bench.h" + +typedef struct { + secp256k1_context *ctx; + unsigned char msg[32]; + unsigned char sig[64]; +} bench_recover_t; + +void bench_recover(void* arg) { + int i; + bench_recover_t *data = (bench_recover_t*)arg; + secp256k1_pubkey pubkey; + unsigned char pubkeyc[33]; + + for (i = 0; i < 20000; i++) { + int j; + size_t pubkeylen = 33; + secp256k1_ecdsa_recoverable_signature sig; + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(data->ctx, &sig, data->sig, i % 2)); + CHECK(secp256k1_ecdsa_recover(data->ctx, &pubkey, &sig, data->msg)); + CHECK(secp256k1_ec_pubkey_serialize(data->ctx, pubkeyc, &pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED)); + for (j = 0; j < 32; j++) { + data->sig[j + 32] = data->msg[j]; /* Move former message to S. */ + data->msg[j] = data->sig[j]; /* Move former R to message. */ + data->sig[j] = pubkeyc[j + 1]; /* Move recovered pubkey X coordinate to R (which must be a valid X coordinate). */ + } + } +} + +void bench_recover_setup(void* arg) { + int i; + bench_recover_t *data = (bench_recover_t*)arg; + + for (i = 0; i < 32; i++) { + data->msg[i] = 1 + i; + } + for (i = 0; i < 64; i++) { + data->sig[i] = 65 + i; + } +} + +int main(void) { + bench_recover_t data; + + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + + run_benchmark("ecdsa_recover", bench_recover, bench_recover_setup, NULL, &data, 10, 20000); + + secp256k1_context_destroy(data.ctx); + return 0; +} diff --git a/crypto/secp256k1/libsecp256k1/src/bench_schnorr_verify.c b/crypto/secp256k1/libsecp256k1/src/bench_schnorr_verify.c new file mode 100644 index 0000000..5f137dd --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/bench_schnorr_verify.c @@ -0,0 +1,73 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include +#include + +#include "include/secp256k1.h" +#include "include/secp256k1_schnorr.h" +#include "util.h" +#include "bench.h" + +typedef struct { + unsigned char key[32]; + unsigned char sig[64]; + unsigned char pubkey[33]; + size_t pubkeylen; +} benchmark_schnorr_sig_t; + +typedef struct { + secp256k1_context *ctx; + unsigned char msg[32]; + benchmark_schnorr_sig_t sigs[64]; + int numsigs; +} benchmark_schnorr_verify_t; + +static void benchmark_schnorr_init(void* arg) { + int i, k; + benchmark_schnorr_verify_t* data = (benchmark_schnorr_verify_t*)arg; + + for (i = 0; i < 32; i++) { + data->msg[i] = 1 + i; + } + for (k = 0; k < data->numsigs; k++) { + secp256k1_pubkey pubkey; + for (i = 0; i < 32; i++) { + data->sigs[k].key[i] = 33 + i + k; + } + secp256k1_schnorr_sign(data->ctx, data->sigs[k].sig, data->msg, data->sigs[k].key, NULL, NULL); + data->sigs[k].pubkeylen = 33; + CHECK(secp256k1_ec_pubkey_create(data->ctx, &pubkey, data->sigs[k].key)); + CHECK(secp256k1_ec_pubkey_serialize(data->ctx, data->sigs[k].pubkey, &data->sigs[k].pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED)); + } +} + +static void benchmark_schnorr_verify(void* arg) { + int i; + benchmark_schnorr_verify_t* data = (benchmark_schnorr_verify_t*)arg; + + for (i = 0; i < 20000 / data->numsigs; i++) { + secp256k1_pubkey pubkey; + data->sigs[0].sig[(i >> 8) % 64] ^= (i & 0xFF); + CHECK(secp256k1_ec_pubkey_parse(data->ctx, &pubkey, data->sigs[0].pubkey, data->sigs[0].pubkeylen)); + CHECK(secp256k1_schnorr_verify(data->ctx, data->sigs[0].sig, data->msg, &pubkey) == ((i & 0xFF) == 0)); + data->sigs[0].sig[(i >> 8) % 64] ^= (i & 0xFF); + } +} + + + +int main(void) { + benchmark_schnorr_verify_t data; + + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + + data.numsigs = 1; + run_benchmark("schnorr_verify", benchmark_schnorr_verify, benchmark_schnorr_init, NULL, &data, 10, 20000); + + secp256k1_context_destroy(data.ctx); + return 0; +} diff --git a/crypto/secp256k1/libsecp256k1/src/bench_sign.c b/crypto/secp256k1/libsecp256k1/src/bench_sign.c new file mode 100644 index 0000000..ed7224d --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/bench_sign.c @@ -0,0 +1,56 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include "include/secp256k1.h" +#include "util.h" +#include "bench.h" + +typedef struct { + secp256k1_context* ctx; + unsigned char msg[32]; + unsigned char key[32]; +} bench_sign_t; + +static void bench_sign_setup(void* arg) { + int i; + bench_sign_t *data = (bench_sign_t*)arg; + + for (i = 0; i < 32; i++) { + data->msg[i] = i + 1; + } + for (i = 0; i < 32; i++) { + data->key[i] = i + 65; + } +} + +static void bench_sign(void* arg) { + int i; + bench_sign_t *data = (bench_sign_t*)arg; + + unsigned char sig[74]; + for (i = 0; i < 20000; i++) { + size_t siglen = 74; + int j; + secp256k1_ecdsa_signature signature; + CHECK(secp256k1_ecdsa_sign(data->ctx, &signature, data->msg, data->key, NULL, NULL)); + CHECK(secp256k1_ecdsa_signature_serialize_der(data->ctx, sig, &siglen, &signature)); + for (j = 0; j < 32; j++) { + data->msg[j] = sig[j]; + data->key[j] = sig[j + 32]; + } + } +} + +int main(void) { + bench_sign_t data; + + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + + run_benchmark("ecdsa_sign", bench_sign, bench_sign_setup, NULL, &data, 10, 20000); + + secp256k1_context_destroy(data.ctx); + return 0; +} diff --git a/crypto/secp256k1/libsecp256k1/src/bench_verify.c b/crypto/secp256k1/libsecp256k1/src/bench_verify.c new file mode 100644 index 0000000..418defa --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/bench_verify.c @@ -0,0 +1,112 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include +#include + +#include "include/secp256k1.h" +#include "util.h" +#include "bench.h" + +#ifdef ENABLE_OPENSSL_TESTS +#include +#include +#include +#endif + +typedef struct { + secp256k1_context *ctx; + unsigned char msg[32]; + unsigned char key[32]; + unsigned char sig[72]; + size_t siglen; + unsigned char pubkey[33]; + size_t pubkeylen; +#ifdef ENABLE_OPENSSL_TESTS + EC_GROUP* ec_group; +#endif +} benchmark_verify_t; + +static void benchmark_verify(void* arg) { + int i; + benchmark_verify_t* data = (benchmark_verify_t*)arg; + + for (i = 0; i < 20000; i++) { + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature sig; + data->sig[data->siglen - 1] ^= (i & 0xFF); + data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF); + data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF); + CHECK(secp256k1_ec_pubkey_parse(data->ctx, &pubkey, data->pubkey, data->pubkeylen) == 1); + CHECK(secp256k1_ecdsa_signature_parse_der(data->ctx, &sig, data->sig, data->siglen) == 1); + CHECK(secp256k1_ecdsa_verify(data->ctx, &sig, data->msg, &pubkey) == (i == 0)); + data->sig[data->siglen - 1] ^= (i & 0xFF); + data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF); + data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF); + } +} + +#ifdef ENABLE_OPENSSL_TESTS +static void benchmark_verify_openssl(void* arg) { + int i; + benchmark_verify_t* data = (benchmark_verify_t*)arg; + + for (i = 0; i < 20000; i++) { + data->sig[data->siglen - 1] ^= (i & 0xFF); + data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF); + data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF); + { + EC_KEY *pkey = EC_KEY_new(); + const unsigned char *pubkey = &data->pubkey[0]; + int result; + + CHECK(pkey != NULL); + result = EC_KEY_set_group(pkey, data->ec_group); + CHECK(result); + result = (o2i_ECPublicKey(&pkey, &pubkey, data->pubkeylen)) != NULL; + CHECK(result); + result = ECDSA_verify(0, &data->msg[0], sizeof(data->msg), &data->sig[0], data->siglen, pkey) == (i == 0); + CHECK(result); + EC_KEY_free(pkey); + } + data->sig[data->siglen - 1] ^= (i & 0xFF); + data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF); + data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF); + } +} +#endif + +int main(void) { + int i; + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature sig; + benchmark_verify_t data; + + data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + + for (i = 0; i < 32; i++) { + data.msg[i] = 1 + i; + } + for (i = 0; i < 32; i++) { + data.key[i] = 33 + i; + } + data.siglen = 72; + CHECK(secp256k1_ecdsa_sign(data.ctx, &sig, data.msg, data.key, NULL, NULL)); + CHECK(secp256k1_ecdsa_signature_serialize_der(data.ctx, data.sig, &data.siglen, &sig)); + CHECK(secp256k1_ec_pubkey_create(data.ctx, &pubkey, data.key)); + data.pubkeylen = 33; + CHECK(secp256k1_ec_pubkey_serialize(data.ctx, data.pubkey, &data.pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + + run_benchmark("ecdsa_verify", benchmark_verify, NULL, NULL, &data, 10, 20000); +#ifdef ENABLE_OPENSSL_TESTS + data.ec_group = EC_GROUP_new_by_curve_name(NID_secp256k1); + run_benchmark("ecdsa_verify_openssl", benchmark_verify_openssl, NULL, NULL, &data, 10, 20000); + EC_GROUP_free(data.ec_group); +#endif + + secp256k1_context_destroy(data.ctx); + return 0; +} diff --git a/crypto/secp256k1/libsecp256k1/src/dummy.go b/crypto/secp256k1/libsecp256k1/src/dummy.go new file mode 100644 index 0000000..2df270a --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/dummy.go @@ -0,0 +1,8 @@ +//go:build dummy +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package src diff --git a/crypto/secp256k1/libsecp256k1/src/ecdsa.h b/crypto/secp256k1/libsecp256k1/src/ecdsa.h new file mode 100644 index 0000000..54ae101 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/ecdsa.h @@ -0,0 +1,21 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECDSA_ +#define _SECP256K1_ECDSA_ + +#include + +#include "scalar.h" +#include "group.h" +#include "ecmult.h" + +static int secp256k1_ecdsa_sig_parse(secp256k1_scalar *r, secp256k1_scalar *s, const unsigned char *sig, size_t size); +static int secp256k1_ecdsa_sig_serialize(unsigned char *sig, size_t *size, const secp256k1_scalar *r, const secp256k1_scalar *s); +static int secp256k1_ecdsa_sig_verify(const secp256k1_ecmult_context *ctx, const secp256k1_scalar* r, const secp256k1_scalar* s, const secp256k1_ge *pubkey, const secp256k1_scalar *message); +static int secp256k1_ecdsa_sig_sign(const secp256k1_ecmult_gen_context *ctx, secp256k1_scalar* r, secp256k1_scalar* s, const secp256k1_scalar *seckey, const secp256k1_scalar *message, const secp256k1_scalar *nonce, int *recid); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/ecdsa_impl.h b/crypto/secp256k1/libsecp256k1/src/ecdsa_impl.h new file mode 100644 index 0000000..453bb11 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/ecdsa_impl.h @@ -0,0 +1,315 @@ +/********************************************************************** + * Copyright (c) 2013-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + + +#ifndef _SECP256K1_ECDSA_IMPL_H_ +#define _SECP256K1_ECDSA_IMPL_H_ + +#include "scalar.h" +#include "field.h" +#include "group.h" +#include "ecmult.h" +#include "ecmult_gen.h" +#include "ecdsa.h" + +/** Group order for secp256k1 defined as 'n' in "Standards for Efficient Cryptography" (SEC2) 2.7.1 + * sage: for t in xrange(1023, -1, -1): + * .. p = 2**256 - 2**32 - t + * .. if p.is_prime(): + * .. print '%x'%p + * .. break + * 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f' + * sage: a = 0 + * sage: b = 7 + * sage: F = FiniteField (p) + * sage: '%x' % (EllipticCurve ([F (a), F (b)]).order()) + * 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141' + */ +static const secp256k1_fe secp256k1_ecdsa_const_order_as_fe = SECP256K1_FE_CONST( + 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFEUL, + 0xBAAEDCE6UL, 0xAF48A03BUL, 0xBFD25E8CUL, 0xD0364141UL +); + +/** Difference between field and order, values 'p' and 'n' values defined in + * "Standards for Efficient Cryptography" (SEC2) 2.7.1. + * sage: p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F + * sage: a = 0 + * sage: b = 7 + * sage: F = FiniteField (p) + * sage: '%x' % (p - EllipticCurve ([F (a), F (b)]).order()) + * '14551231950b75fc4402da1722fc9baee' + */ +static const secp256k1_fe secp256k1_ecdsa_const_p_minus_order = SECP256K1_FE_CONST( + 0, 0, 0, 1, 0x45512319UL, 0x50B75FC4UL, 0x402DA172UL, 0x2FC9BAEEUL +); + +static int secp256k1_der_read_len(const unsigned char **sigp, const unsigned char *sigend) { + int lenleft, b1; + size_t ret = 0; + if (*sigp >= sigend) { + return -1; + } + b1 = *((*sigp)++); + if (b1 == 0xFF) { + /* X.690-0207 8.1.3.5.c the value 0xFF shall not be used. */ + return -1; + } + if ((b1 & 0x80) == 0) { + /* X.690-0207 8.1.3.4 short form length octets */ + return b1; + } + if (b1 == 0x80) { + /* Indefinite length is not allowed in DER. */ + return -1; + } + /* X.690-207 8.1.3.5 long form length octets */ + lenleft = b1 & 0x7F; + if (lenleft > sigend - *sigp) { + return -1; + } + if (**sigp == 0) { + /* Not the shortest possible length encoding. */ + return -1; + } + if ((size_t)lenleft > sizeof(size_t)) { + /* The resulting length would exceed the range of a size_t, so + * certainly longer than the passed array size. + */ + return -1; + } + while (lenleft > 0) { + if ((ret >> ((sizeof(size_t) - 1) * 8)) != 0) { + } + ret = (ret << 8) | **sigp; + if (ret + lenleft > (size_t)(sigend - *sigp)) { + /* Result exceeds the length of the passed array. */ + return -1; + } + (*sigp)++; + lenleft--; + } + if (ret < 128) { + /* Not the shortest possible length encoding. */ + return -1; + } + return ret; +} + +static int secp256k1_der_parse_integer(secp256k1_scalar *r, const unsigned char **sig, const unsigned char *sigend) { + int overflow = 0; + unsigned char ra[32] = {0}; + int rlen; + + if (*sig == sigend || **sig != 0x02) { + /* Not a primitive integer (X.690-0207 8.3.1). */ + return 0; + } + (*sig)++; + rlen = secp256k1_der_read_len(sig, sigend); + if (rlen <= 0 || (*sig) + rlen > sigend) { + /* Exceeds bounds or not at least length 1 (X.690-0207 8.3.1). */ + return 0; + } + if (**sig == 0x00 && rlen > 1 && (((*sig)[1]) & 0x80) == 0x00) { + /* Excessive 0x00 padding. */ + return 0; + } + if (**sig == 0xFF && rlen > 1 && (((*sig)[1]) & 0x80) == 0x80) { + /* Excessive 0xFF padding. */ + return 0; + } + if ((**sig & 0x80) == 0x80) { + /* Negative. */ + overflow = 1; + } + while (rlen > 0 && **sig == 0) { + /* Skip leading zero bytes */ + rlen--; + (*sig)++; + } + if (rlen > 32) { + overflow = 1; + } + if (!overflow) { + memcpy(ra + 32 - rlen, *sig, rlen); + secp256k1_scalar_set_b32(r, ra, &overflow); + } + if (overflow) { + secp256k1_scalar_set_int(r, 0); + } + (*sig) += rlen; + return 1; +} + +static int secp256k1_ecdsa_sig_parse(secp256k1_scalar *rr, secp256k1_scalar *rs, const unsigned char *sig, size_t size) { + const unsigned char *sigend = sig + size; + int rlen; + if (sig == sigend || *(sig++) != 0x30) { + /* The encoding doesn't start with a constructed sequence (X.690-0207 8.9.1). */ + return 0; + } + rlen = secp256k1_der_read_len(&sig, sigend); + if (rlen < 0 || sig + rlen > sigend) { + /* Tuple exceeds bounds */ + return 0; + } + if (sig + rlen != sigend) { + /* Garbage after tuple. */ + return 0; + } + + if (!secp256k1_der_parse_integer(rr, &sig, sigend)) { + return 0; + } + if (!secp256k1_der_parse_integer(rs, &sig, sigend)) { + return 0; + } + + if (sig != sigend) { + /* Trailing garbage inside tuple. */ + return 0; + } + + return 1; +} + +static int secp256k1_ecdsa_sig_serialize(unsigned char *sig, size_t *size, const secp256k1_scalar* ar, const secp256k1_scalar* as) { + unsigned char r[33] = {0}, s[33] = {0}; + unsigned char *rp = r, *sp = s; + size_t lenR = 33, lenS = 33; + secp256k1_scalar_get_b32(&r[1], ar); + secp256k1_scalar_get_b32(&s[1], as); + while (lenR > 1 && rp[0] == 0 && rp[1] < 0x80) { lenR--; rp++; } + while (lenS > 1 && sp[0] == 0 && sp[1] < 0x80) { lenS--; sp++; } + if (*size < 6+lenS+lenR) { + *size = 6 + lenS + lenR; + return 0; + } + *size = 6 + lenS + lenR; + sig[0] = 0x30; + sig[1] = 4 + lenS + lenR; + sig[2] = 0x02; + sig[3] = lenR; + memcpy(sig+4, rp, lenR); + sig[4+lenR] = 0x02; + sig[5+lenR] = lenS; + memcpy(sig+lenR+6, sp, lenS); + return 1; +} + +static int secp256k1_ecdsa_sig_verify(const secp256k1_ecmult_context *ctx, const secp256k1_scalar *sigr, const secp256k1_scalar *sigs, const secp256k1_ge *pubkey, const secp256k1_scalar *message) { + unsigned char c[32]; + secp256k1_scalar sn, u1, u2; +#if !defined(EXHAUSTIVE_TEST_ORDER) + secp256k1_fe xr; +#endif + secp256k1_gej pubkeyj; + secp256k1_gej pr; + + if (secp256k1_scalar_is_zero(sigr) || secp256k1_scalar_is_zero(sigs)) { + return 0; + } + + secp256k1_scalar_inverse_var(&sn, sigs); + secp256k1_scalar_mul(&u1, &sn, message); + secp256k1_scalar_mul(&u2, &sn, sigr); + secp256k1_gej_set_ge(&pubkeyj, pubkey); + secp256k1_ecmult(ctx, &pr, &pubkeyj, &u2, &u1); + if (secp256k1_gej_is_infinity(&pr)) { + return 0; + } + +#if defined(EXHAUSTIVE_TEST_ORDER) +{ + secp256k1_scalar computed_r; + secp256k1_ge pr_ge; + secp256k1_ge_set_gej(&pr_ge, &pr); + secp256k1_fe_normalize(&pr_ge.x); + + secp256k1_fe_get_b32(c, &pr_ge.x); + secp256k1_scalar_set_b32(&computed_r, c, NULL); + return secp256k1_scalar_eq(sigr, &computed_r); +} +#else + secp256k1_scalar_get_b32(c, sigr); + secp256k1_fe_set_b32(&xr, c); + + /** We now have the recomputed R point in pr, and its claimed x coordinate (modulo n) + * in xr. Naively, we would extract the x coordinate from pr (requiring a inversion modulo p), + * compute the remainder modulo n, and compare it to xr. However: + * + * xr == X(pr) mod n + * <=> exists h. (xr + h * n < p && xr + h * n == X(pr)) + * [Since 2 * n > p, h can only be 0 or 1] + * <=> (xr == X(pr)) || (xr + n < p && xr + n == X(pr)) + * [In Jacobian coordinates, X(pr) is pr.x / pr.z^2 mod p] + * <=> (xr == pr.x / pr.z^2 mod p) || (xr + n < p && xr + n == pr.x / pr.z^2 mod p) + * [Multiplying both sides of the equations by pr.z^2 mod p] + * <=> (xr * pr.z^2 mod p == pr.x) || (xr + n < p && (xr + n) * pr.z^2 mod p == pr.x) + * + * Thus, we can avoid the inversion, but we have to check both cases separately. + * secp256k1_gej_eq_x implements the (xr * pr.z^2 mod p == pr.x) test. + */ + if (secp256k1_gej_eq_x_var(&xr, &pr)) { + /* xr * pr.z^2 mod p == pr.x, so the signature is valid. */ + return 1; + } + if (secp256k1_fe_cmp_var(&xr, &secp256k1_ecdsa_const_p_minus_order) >= 0) { + /* xr + n >= p, so we can skip testing the second case. */ + return 0; + } + secp256k1_fe_add(&xr, &secp256k1_ecdsa_const_order_as_fe); + if (secp256k1_gej_eq_x_var(&xr, &pr)) { + /* (xr + n) * pr.z^2 mod p == pr.x, so the signature is valid. */ + return 1; + } + return 0; +#endif +} + +static int secp256k1_ecdsa_sig_sign(const secp256k1_ecmult_gen_context *ctx, secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *seckey, const secp256k1_scalar *message, const secp256k1_scalar *nonce, int *recid) { + unsigned char b[32]; + secp256k1_gej rp; + secp256k1_ge r; + secp256k1_scalar n; + int overflow = 0; + + secp256k1_ecmult_gen(ctx, &rp, nonce); + secp256k1_ge_set_gej(&r, &rp); + secp256k1_fe_normalize(&r.x); + secp256k1_fe_normalize(&r.y); + secp256k1_fe_get_b32(b, &r.x); + secp256k1_scalar_set_b32(sigr, b, &overflow); + /* These two conditions should be checked before calling */ + VERIFY_CHECK(!secp256k1_scalar_is_zero(sigr)); + VERIFY_CHECK(overflow == 0); + + if (recid) { + /* The overflow condition is cryptographically unreachable as hitting it requires finding the discrete log + * of some P where P.x >= order, and only 1 in about 2^127 points meet this criteria. + */ + *recid = (overflow ? 2 : 0) | (secp256k1_fe_is_odd(&r.y) ? 1 : 0); + } + secp256k1_scalar_mul(&n, sigr, seckey); + secp256k1_scalar_add(&n, &n, message); + secp256k1_scalar_inverse(sigs, nonce); + secp256k1_scalar_mul(sigs, sigs, &n); + secp256k1_scalar_clear(&n); + secp256k1_gej_clear(&rp); + secp256k1_ge_clear(&r); + if (secp256k1_scalar_is_zero(sigs)) { + return 0; + } + if (secp256k1_scalar_is_high(sigs)) { + secp256k1_scalar_negate(sigs, sigs); + if (recid) { + *recid ^= 1; + } + } + return 1; +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/eckey.h b/crypto/secp256k1/libsecp256k1/src/eckey.h new file mode 100644 index 0000000..42739a3 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/eckey.h @@ -0,0 +1,25 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECKEY_ +#define _SECP256K1_ECKEY_ + +#include + +#include "group.h" +#include "scalar.h" +#include "ecmult.h" +#include "ecmult_gen.h" + +static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char *pub, size_t size); +static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed); + +static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp256k1_scalar *tweak); +static int secp256k1_eckey_pubkey_tweak_add(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak); +static int secp256k1_eckey_privkey_tweak_mul(secp256k1_scalar *key, const secp256k1_scalar *tweak); +static int secp256k1_eckey_pubkey_tweak_mul(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/eckey_impl.h b/crypto/secp256k1/libsecp256k1/src/eckey_impl.h new file mode 100644 index 0000000..ce38071 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/eckey_impl.h @@ -0,0 +1,99 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECKEY_IMPL_H_ +#define _SECP256K1_ECKEY_IMPL_H_ + +#include "eckey.h" + +#include "scalar.h" +#include "field.h" +#include "group.h" +#include "ecmult_gen.h" + +static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char *pub, size_t size) { + if (size == 33 && (pub[0] == 0x02 || pub[0] == 0x03)) { + secp256k1_fe x; + return secp256k1_fe_set_b32(&x, pub+1) && secp256k1_ge_set_xo_var(elem, &x, pub[0] == 0x03); + } else if (size == 65 && (pub[0] == 0x04 || pub[0] == 0x06 || pub[0] == 0x07)) { + secp256k1_fe x, y; + if (!secp256k1_fe_set_b32(&x, pub+1) || !secp256k1_fe_set_b32(&y, pub+33)) { + return 0; + } + secp256k1_ge_set_xy(elem, &x, &y); + if ((pub[0] == 0x06 || pub[0] == 0x07) && secp256k1_fe_is_odd(&y) != (pub[0] == 0x07)) { + return 0; + } + return secp256k1_ge_is_valid_var(elem); + } else { + return 0; + } +} + +static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed) { + if (secp256k1_ge_is_infinity(elem)) { + return 0; + } + secp256k1_fe_normalize_var(&elem->x); + secp256k1_fe_normalize_var(&elem->y); + secp256k1_fe_get_b32(&pub[1], &elem->x); + if (compressed) { + *size = 33; + pub[0] = 0x02 | (secp256k1_fe_is_odd(&elem->y) ? 0x01 : 0x00); + } else { + *size = 65; + pub[0] = 0x04; + secp256k1_fe_get_b32(&pub[33], &elem->y); + } + return 1; +} + +static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp256k1_scalar *tweak) { + secp256k1_scalar_add(key, key, tweak); + if (secp256k1_scalar_is_zero(key)) { + return 0; + } + return 1; +} + +static int secp256k1_eckey_pubkey_tweak_add(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak) { + secp256k1_gej pt; + secp256k1_scalar one; + secp256k1_gej_set_ge(&pt, key); + secp256k1_scalar_set_int(&one, 1); + secp256k1_ecmult(ctx, &pt, &pt, &one, tweak); + + if (secp256k1_gej_is_infinity(&pt)) { + return 0; + } + secp256k1_ge_set_gej(key, &pt); + return 1; +} + +static int secp256k1_eckey_privkey_tweak_mul(secp256k1_scalar *key, const secp256k1_scalar *tweak) { + if (secp256k1_scalar_is_zero(tweak)) { + return 0; + } + + secp256k1_scalar_mul(key, key, tweak); + return 1; +} + +static int secp256k1_eckey_pubkey_tweak_mul(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak) { + secp256k1_scalar zero; + secp256k1_gej pt; + if (secp256k1_scalar_is_zero(tweak)) { + return 0; + } + + secp256k1_scalar_set_int(&zero, 0); + secp256k1_gej_set_ge(&pt, key); + secp256k1_ecmult(ctx, &pt, &pt, tweak, &zero); + secp256k1_ge_set_gej(key, &pt); + return 1; +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/ecmult.h b/crypto/secp256k1/libsecp256k1/src/ecmult.h new file mode 100644 index 0000000..2048413 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/ecmult.h @@ -0,0 +1,31 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECMULT_ +#define _SECP256K1_ECMULT_ + +#include "num.h" +#include "group.h" + +typedef struct { + /* For accelerating the computation of a*P + b*G: */ + secp256k1_ge_storage (*pre_g)[]; /* odd multiples of the generator */ +#ifdef USE_ENDOMORPHISM + secp256k1_ge_storage (*pre_g_128)[]; /* odd multiples of 2^128*generator */ +#endif +} secp256k1_ecmult_context; + +static void secp256k1_ecmult_context_init(secp256k1_ecmult_context *ctx); +static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, const secp256k1_callback *cb); +static void secp256k1_ecmult_context_clone(secp256k1_ecmult_context *dst, + const secp256k1_ecmult_context *src, const secp256k1_callback *cb); +static void secp256k1_ecmult_context_clear(secp256k1_ecmult_context *ctx); +static int secp256k1_ecmult_context_is_built(const secp256k1_ecmult_context *ctx); + +/** Double multiply: R = na*A + ng*G */ +static void secp256k1_ecmult(const secp256k1_ecmult_context *ctx, secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_scalar *na, const secp256k1_scalar *ng); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/ecmult_const.h b/crypto/secp256k1/libsecp256k1/src/ecmult_const.h new file mode 100644 index 0000000..2b00976 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/ecmult_const.h @@ -0,0 +1,15 @@ +/********************************************************************** + * Copyright (c) 2015 Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECMULT_CONST_ +#define _SECP256K1_ECMULT_CONST_ + +#include "scalar.h" +#include "group.h" + +static void secp256k1_ecmult_const(secp256k1_gej *r, const secp256k1_ge *a, const secp256k1_scalar *q); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/ecmult_const_impl.h b/crypto/secp256k1/libsecp256k1/src/ecmult_const_impl.h new file mode 100644 index 0000000..0db314c --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/ecmult_const_impl.h @@ -0,0 +1,239 @@ +/********************************************************************** + * Copyright (c) 2015 Pieter Wuille, Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECMULT_CONST_IMPL_ +#define _SECP256K1_ECMULT_CONST_IMPL_ + +#include "scalar.h" +#include "group.h" +#include "ecmult_const.h" +#include "ecmult_impl.h" + +#ifdef USE_ENDOMORPHISM + #define WNAF_BITS 128 +#else + #define WNAF_BITS 256 +#endif +#define WNAF_SIZE(w) ((WNAF_BITS + (w) - 1) / (w)) + +/* This is like `ECMULT_TABLE_GET_GE` but is constant time */ +#define ECMULT_CONST_TABLE_GET_GE(r,pre,n,w) do { \ + int m; \ + int abs_n = (n) * (((n) > 0) * 2 - 1); \ + int idx_n = abs_n / 2; \ + secp256k1_fe neg_y; \ + VERIFY_CHECK(((n) & 1) == 1); \ + VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \ + VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); \ + VERIFY_SETUP(secp256k1_fe_clear(&(r)->x)); \ + VERIFY_SETUP(secp256k1_fe_clear(&(r)->y)); \ + for (m = 0; m < ECMULT_TABLE_SIZE(w); m++) { \ + /* This loop is used to avoid secret data in array indices. See + * the comment in ecmult_gen_impl.h for rationale. */ \ + secp256k1_fe_cmov(&(r)->x, &(pre)[m].x, m == idx_n); \ + secp256k1_fe_cmov(&(r)->y, &(pre)[m].y, m == idx_n); \ + } \ + (r)->infinity = 0; \ + secp256k1_fe_negate(&neg_y, &(r)->y, 1); \ + secp256k1_fe_cmov(&(r)->y, &neg_y, (n) != abs_n); \ +} while(0) + + +/** Convert a number to WNAF notation. The number becomes represented by sum(2^{wi} * wnaf[i], i=0..return_val) + * with the following guarantees: + * - each wnaf[i] an odd integer between -(1 << w) and (1 << w) + * - each wnaf[i] is nonzero + * - the number of words set is returned; this is always (WNAF_BITS + w - 1) / w + * + * Adapted from `The Width-w NAF Method Provides Small Memory and Fast Elliptic Scalar + * Multiplications Secure against Side Channel Attacks`, Okeya and Tagaki. M. Joye (Ed.) + * CT-RSA 2003, LNCS 2612, pp. 328-443, 2003. Springer-Verlagy Berlin Heidelberg 2003 + * + * Numbers reference steps of `Algorithm SPA-resistant Width-w NAF with Odd Scalar` on pp. 335 + */ +static int secp256k1_wnaf_const(int *wnaf, secp256k1_scalar s, int w) { + int global_sign; + int skew = 0; + int word = 0; + + /* 1 2 3 */ + int u_last; + int u; + + int flip; + int bit; + secp256k1_scalar neg_s; + int not_neg_one; + /* Note that we cannot handle even numbers by negating them to be odd, as is + * done in other implementations, since if our scalars were specified to have + * width < 256 for performance reasons, their negations would have width 256 + * and we'd lose any performance benefit. Instead, we use a technique from + * Section 4.2 of the Okeya/Tagaki paper, which is to add either 1 (for even) + * or 2 (for odd) to the number we are encoding, returning a skew value indicating + * this, and having the caller compensate after doing the multiplication. */ + + /* Negative numbers will be negated to keep their bit representation below the maximum width */ + flip = secp256k1_scalar_is_high(&s); + /* We add 1 to even numbers, 2 to odd ones, noting that negation flips parity */ + bit = flip ^ !secp256k1_scalar_is_even(&s); + /* We check for negative one, since adding 2 to it will cause an overflow */ + secp256k1_scalar_negate(&neg_s, &s); + not_neg_one = !secp256k1_scalar_is_one(&neg_s); + secp256k1_scalar_cadd_bit(&s, bit, not_neg_one); + /* If we had negative one, flip == 1, s.d[0] == 0, bit == 1, so caller expects + * that we added two to it and flipped it. In fact for -1 these operations are + * identical. We only flipped, but since skewing is required (in the sense that + * the skew must be 1 or 2, never zero) and flipping is not, we need to change + * our flags to claim that we only skewed. */ + global_sign = secp256k1_scalar_cond_negate(&s, flip); + global_sign *= not_neg_one * 2 - 1; + skew = 1 << bit; + + /* 4 */ + u_last = secp256k1_scalar_shr_int(&s, w); + while (word * w < WNAF_BITS) { + int sign; + int even; + + /* 4.1 4.4 */ + u = secp256k1_scalar_shr_int(&s, w); + /* 4.2 */ + even = ((u & 1) == 0); + sign = 2 * (u_last > 0) - 1; + u += sign * even; + u_last -= sign * even * (1 << w); + + /* 4.3, adapted for global sign change */ + wnaf[word++] = u_last * global_sign; + + u_last = u; + } + wnaf[word] = u * global_sign; + + VERIFY_CHECK(secp256k1_scalar_is_zero(&s)); + VERIFY_CHECK(word == WNAF_SIZE(w)); + return skew; +} + + +static void secp256k1_ecmult_const(secp256k1_gej *r, const secp256k1_ge *a, const secp256k1_scalar *scalar) { + secp256k1_ge pre_a[ECMULT_TABLE_SIZE(WINDOW_A)]; + secp256k1_ge tmpa; + secp256k1_fe Z; + + int skew_1; + int wnaf_1[1 + WNAF_SIZE(WINDOW_A - 1)]; +#ifdef USE_ENDOMORPHISM + secp256k1_ge pre_a_lam[ECMULT_TABLE_SIZE(WINDOW_A)]; + int wnaf_lam[1 + WNAF_SIZE(WINDOW_A - 1)]; + int skew_lam; + secp256k1_scalar q_1, q_lam; +#endif + + int i; + secp256k1_scalar sc = *scalar; + + /* build wnaf representation for q. */ +#ifdef USE_ENDOMORPHISM + /* split q into q_1 and q_lam (where q = q_1 + q_lam*lambda, and q_1 and q_lam are ~128 bit) */ + secp256k1_scalar_split_lambda(&q_1, &q_lam, &sc); + skew_1 = secp256k1_wnaf_const(wnaf_1, q_1, WINDOW_A - 1); + skew_lam = secp256k1_wnaf_const(wnaf_lam, q_lam, WINDOW_A - 1); +#else + skew_1 = secp256k1_wnaf_const(wnaf_1, sc, WINDOW_A - 1); +#endif + + /* Calculate odd multiples of a. + * All multiples are brought to the same Z 'denominator', which is stored + * in Z. Due to secp256k1' isomorphism we can do all operations pretending + * that the Z coordinate was 1, use affine addition formulae, and correct + * the Z coordinate of the result once at the end. + */ + secp256k1_gej_set_ge(r, a); + secp256k1_ecmult_odd_multiples_table_globalz_windowa(pre_a, &Z, r); + for (i = 0; i < ECMULT_TABLE_SIZE(WINDOW_A); i++) { + secp256k1_fe_normalize_weak(&pre_a[i].y); + } +#ifdef USE_ENDOMORPHISM + for (i = 0; i < ECMULT_TABLE_SIZE(WINDOW_A); i++) { + secp256k1_ge_mul_lambda(&pre_a_lam[i], &pre_a[i]); + } +#endif + + /* first loop iteration (separated out so we can directly set r, rather + * than having it start at infinity, get doubled several times, then have + * its new value added to it) */ + i = wnaf_1[WNAF_SIZE(WINDOW_A - 1)]; + VERIFY_CHECK(i != 0); + ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a, i, WINDOW_A); + secp256k1_gej_set_ge(r, &tmpa); +#ifdef USE_ENDOMORPHISM + i = wnaf_lam[WNAF_SIZE(WINDOW_A - 1)]; + VERIFY_CHECK(i != 0); + ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a_lam, i, WINDOW_A); + secp256k1_gej_add_ge(r, r, &tmpa); +#endif + /* remaining loop iterations */ + for (i = WNAF_SIZE(WINDOW_A - 1) - 1; i >= 0; i--) { + int n; + int j; + for (j = 0; j < WINDOW_A - 1; ++j) { + secp256k1_gej_double_nonzero(r, r, NULL); + } + + n = wnaf_1[i]; + ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a, n, WINDOW_A); + VERIFY_CHECK(n != 0); + secp256k1_gej_add_ge(r, r, &tmpa); +#ifdef USE_ENDOMORPHISM + n = wnaf_lam[i]; + ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a_lam, n, WINDOW_A); + VERIFY_CHECK(n != 0); + secp256k1_gej_add_ge(r, r, &tmpa); +#endif + } + + secp256k1_fe_mul(&r->z, &r->z, &Z); + + { + /* Correct for wNAF skew */ + secp256k1_ge correction = *a; + secp256k1_ge_storage correction_1_stor; +#ifdef USE_ENDOMORPHISM + secp256k1_ge_storage correction_lam_stor; +#endif + secp256k1_ge_storage a2_stor; + secp256k1_gej tmpj; + secp256k1_gej_set_ge(&tmpj, &correction); + secp256k1_gej_double_var(&tmpj, &tmpj, NULL); + secp256k1_ge_set_gej(&correction, &tmpj); + secp256k1_ge_to_storage(&correction_1_stor, a); +#ifdef USE_ENDOMORPHISM + secp256k1_ge_to_storage(&correction_lam_stor, a); +#endif + secp256k1_ge_to_storage(&a2_stor, &correction); + + /* For odd numbers this is 2a (so replace it), for even ones a (so no-op) */ + secp256k1_ge_storage_cmov(&correction_1_stor, &a2_stor, skew_1 == 2); +#ifdef USE_ENDOMORPHISM + secp256k1_ge_storage_cmov(&correction_lam_stor, &a2_stor, skew_lam == 2); +#endif + + /* Apply the correction */ + secp256k1_ge_from_storage(&correction, &correction_1_stor); + secp256k1_ge_neg(&correction, &correction); + secp256k1_gej_add_ge(r, r, &correction); + +#ifdef USE_ENDOMORPHISM + secp256k1_ge_from_storage(&correction, &correction_lam_stor); + secp256k1_ge_neg(&correction, &correction); + secp256k1_ge_mul_lambda(&correction, &correction); + secp256k1_gej_add_ge(r, r, &correction); +#endif + } +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/ecmult_gen.h b/crypto/secp256k1/libsecp256k1/src/ecmult_gen.h new file mode 100644 index 0000000..eb2cc9e --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/ecmult_gen.h @@ -0,0 +1,43 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECMULT_GEN_ +#define _SECP256K1_ECMULT_GEN_ + +#include "scalar.h" +#include "group.h" + +typedef struct { + /* For accelerating the computation of a*G: + * To harden against timing attacks, use the following mechanism: + * * Break up the multiplicand into groups of 4 bits, called n_0, n_1, n_2, ..., n_63. + * * Compute sum(n_i * 16^i * G + U_i, i=0..63), where: + * * U_i = U * 2^i (for i=0..62) + * * U_i = U * (1-2^63) (for i=63) + * where U is a point with no known corresponding scalar. Note that sum(U_i, i=0..63) = 0. + * For each i, and each of the 16 possible values of n_i, (n_i * 16^i * G + U_i) is + * precomputed (call it prec(i, n_i)). The formula now becomes sum(prec(i, n_i), i=0..63). + * None of the resulting prec group elements have a known scalar, and neither do any of + * the intermediate sums while computing a*G. + */ + secp256k1_ge_storage (*prec)[64][16]; /* prec[j][i] = 16^j * i * G + U_i */ + secp256k1_scalar blind; + secp256k1_gej initial; +} secp256k1_ecmult_gen_context; + +static void secp256k1_ecmult_gen_context_init(secp256k1_ecmult_gen_context* ctx); +static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context* ctx, const secp256k1_callback* cb); +static void secp256k1_ecmult_gen_context_clone(secp256k1_ecmult_gen_context *dst, + const secp256k1_ecmult_gen_context* src, const secp256k1_callback* cb); +static void secp256k1_ecmult_gen_context_clear(secp256k1_ecmult_gen_context* ctx); +static int secp256k1_ecmult_gen_context_is_built(const secp256k1_ecmult_gen_context* ctx); + +/** Multiply with the generator: R = a*G */ +static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context* ctx, secp256k1_gej *r, const secp256k1_scalar *a); + +static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const unsigned char *seed32); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/ecmult_gen_impl.h b/crypto/secp256k1/libsecp256k1/src/ecmult_gen_impl.h new file mode 100644 index 0000000..35f2546 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/ecmult_gen_impl.h @@ -0,0 +1,210 @@ +/********************************************************************** + * Copyright (c) 2013, 2014, 2015 Pieter Wuille, Gregory Maxwell * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECMULT_GEN_IMPL_H_ +#define _SECP256K1_ECMULT_GEN_IMPL_H_ + +#include "scalar.h" +#include "group.h" +#include "ecmult_gen.h" +#include "hash_impl.h" +#ifdef USE_ECMULT_STATIC_PRECOMPUTATION +#include "ecmult_static_context.h" +#endif +static void secp256k1_ecmult_gen_context_init(secp256k1_ecmult_gen_context *ctx) { + ctx->prec = NULL; +} + +static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context *ctx, const secp256k1_callback* cb) { +#ifndef USE_ECMULT_STATIC_PRECOMPUTATION + secp256k1_ge prec[1024]; + secp256k1_gej gj; + secp256k1_gej nums_gej; + int i, j; +#endif + + if (ctx->prec != NULL) { + return; + } +#ifndef USE_ECMULT_STATIC_PRECOMPUTATION + ctx->prec = (secp256k1_ge_storage (*)[64][16])checked_malloc(cb, sizeof(*ctx->prec)); + + /* get the generator */ + secp256k1_gej_set_ge(&gj, &secp256k1_ge_const_g); + + /* Construct a group element with no known corresponding scalar (nothing up my sleeve). */ + { + static const unsigned char nums_b32[33] = "The scalar for this x is unknown"; + secp256k1_fe nums_x; + secp256k1_ge nums_ge; + int r; + r = secp256k1_fe_set_b32(&nums_x, nums_b32); + (void)r; + VERIFY_CHECK(r); + r = secp256k1_ge_set_xo_var(&nums_ge, &nums_x, 0); + (void)r; + VERIFY_CHECK(r); + secp256k1_gej_set_ge(&nums_gej, &nums_ge); + /* Add G to make the bits in x uniformly distributed. */ + secp256k1_gej_add_ge_var(&nums_gej, &nums_gej, &secp256k1_ge_const_g, NULL); + } + + /* compute prec. */ + { + secp256k1_gej precj[1024]; /* Jacobian versions of prec. */ + secp256k1_gej gbase; + secp256k1_gej numsbase; + gbase = gj; /* 16^j * G */ + numsbase = nums_gej; /* 2^j * nums. */ + for (j = 0; j < 64; j++) { + /* Set precj[j*16 .. j*16+15] to (numsbase, numsbase + gbase, ..., numsbase + 15*gbase). */ + precj[j*16] = numsbase; + for (i = 1; i < 16; i++) { + secp256k1_gej_add_var(&precj[j*16 + i], &precj[j*16 + i - 1], &gbase, NULL); + } + /* Multiply gbase by 16. */ + for (i = 0; i < 4; i++) { + secp256k1_gej_double_var(&gbase, &gbase, NULL); + } + /* Multiply numbase by 2. */ + secp256k1_gej_double_var(&numsbase, &numsbase, NULL); + if (j == 62) { + /* In the last iteration, numsbase is (1 - 2^j) * nums instead. */ + secp256k1_gej_neg(&numsbase, &numsbase); + secp256k1_gej_add_var(&numsbase, &numsbase, &nums_gej, NULL); + } + } + secp256k1_ge_set_all_gej_var(prec, precj, 1024, cb); + } + for (j = 0; j < 64; j++) { + for (i = 0; i < 16; i++) { + secp256k1_ge_to_storage(&(*ctx->prec)[j][i], &prec[j*16 + i]); + } + } +#else + (void)cb; + ctx->prec = (secp256k1_ge_storage (*)[64][16])secp256k1_ecmult_static_context; +#endif + secp256k1_ecmult_gen_blind(ctx, NULL); +} + +static int secp256k1_ecmult_gen_context_is_built(const secp256k1_ecmult_gen_context* ctx) { + return ctx->prec != NULL; +} + +static void secp256k1_ecmult_gen_context_clone(secp256k1_ecmult_gen_context *dst, + const secp256k1_ecmult_gen_context *src, const secp256k1_callback* cb) { + if (src->prec == NULL) { + dst->prec = NULL; + } else { +#ifndef USE_ECMULT_STATIC_PRECOMPUTATION + dst->prec = (secp256k1_ge_storage (*)[64][16])checked_malloc(cb, sizeof(*dst->prec)); + memcpy(dst->prec, src->prec, sizeof(*dst->prec)); +#else + (void)cb; + dst->prec = src->prec; +#endif + dst->initial = src->initial; + dst->blind = src->blind; + } +} + +static void secp256k1_ecmult_gen_context_clear(secp256k1_ecmult_gen_context *ctx) { +#ifndef USE_ECMULT_STATIC_PRECOMPUTATION + free(ctx->prec); +#endif + secp256k1_scalar_clear(&ctx->blind); + secp256k1_gej_clear(&ctx->initial); + ctx->prec = NULL; +} + +static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context *ctx, secp256k1_gej *r, const secp256k1_scalar *gn) { + secp256k1_ge add; + secp256k1_ge_storage adds; + secp256k1_scalar gnb; + int bits; + int i, j; + memset(&adds, 0, sizeof(adds)); + *r = ctx->initial; + /* Blind scalar/point multiplication by computing (n-b)G + bG instead of nG. */ + secp256k1_scalar_add(&gnb, gn, &ctx->blind); + add.infinity = 0; + for (j = 0; j < 64; j++) { + bits = secp256k1_scalar_get_bits(&gnb, j * 4, 4); + for (i = 0; i < 16; i++) { + /** This uses a conditional move to avoid any secret data in array indexes. + * _Any_ use of secret indexes has been demonstrated to result in timing + * sidechannels, even when the cache-line access patterns are uniform. + * See also: + * "A word of warning", CHES 2013 Rump Session, by Daniel J. Bernstein and Peter Schwabe + * (https://cryptojedi.org/peter/data/chesrump-20130822.pdf) and + * "Cache Attacks and Countermeasures: the Case of AES", RSA 2006, + * by Dag Arne Osvik, Adi Shamir, and Eran Tromer + * (http://www.tau.ac.il/~tromer/papers/cache.pdf) + */ + secp256k1_ge_storage_cmov(&adds, &(*ctx->prec)[j][i], i == bits); + } + secp256k1_ge_from_storage(&add, &adds); + secp256k1_gej_add_ge(r, r, &add); + } + bits = 0; + secp256k1_ge_clear(&add); + secp256k1_scalar_clear(&gnb); +} + +/* Setup blinding values for secp256k1_ecmult_gen. */ +static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const unsigned char *seed32) { + secp256k1_scalar b; + secp256k1_gej gb; + secp256k1_fe s; + unsigned char nonce32[32]; + secp256k1_rfc6979_hmac_sha256_t rng; + int retry; + unsigned char keydata[64] = {0}; + if (seed32 == NULL) { + /* When seed is NULL, reset the initial point and blinding value. */ + secp256k1_gej_set_ge(&ctx->initial, &secp256k1_ge_const_g); + secp256k1_gej_neg(&ctx->initial, &ctx->initial); + secp256k1_scalar_set_int(&ctx->blind, 1); + } + /* The prior blinding value (if not reset) is chained forward by including it in the hash. */ + secp256k1_scalar_get_b32(nonce32, &ctx->blind); + /** Using a CSPRNG allows a failure free interface, avoids needing large amounts of random data, + * and guards against weak or adversarial seeds. This is a simpler and safer interface than + * asking the caller for blinding values directly and expecting them to retry on failure. + */ + memcpy(keydata, nonce32, 32); + if (seed32 != NULL) { + memcpy(keydata + 32, seed32, 32); + } + secp256k1_rfc6979_hmac_sha256_initialize(&rng, keydata, seed32 ? 64 : 32); + memset(keydata, 0, sizeof(keydata)); + /* Retry for out of range results to achieve uniformity. */ + do { + secp256k1_rfc6979_hmac_sha256_generate(&rng, nonce32, 32); + retry = !secp256k1_fe_set_b32(&s, nonce32); + retry |= secp256k1_fe_is_zero(&s); + } while (retry); /* This branch true is cryptographically unreachable. Requires sha256_hmac output > Fp. */ + /* Randomize the projection to defend against multiplier sidechannels. */ + secp256k1_gej_rescale(&ctx->initial, &s); + secp256k1_fe_clear(&s); + do { + secp256k1_rfc6979_hmac_sha256_generate(&rng, nonce32, 32); + secp256k1_scalar_set_b32(&b, nonce32, &retry); + /* A blinding value of 0 works, but would undermine the projection hardening. */ + retry |= secp256k1_scalar_is_zero(&b); + } while (retry); /* This branch true is cryptographically unreachable. Requires sha256_hmac output > order. */ + secp256k1_rfc6979_hmac_sha256_finalize(&rng); + memset(nonce32, 0, 32); + secp256k1_ecmult_gen(ctx, &gb, &b); + secp256k1_scalar_negate(&b, &b); + ctx->blind = b; + ctx->initial = gb; + secp256k1_scalar_clear(&b); + secp256k1_gej_clear(&gb); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/ecmult_impl.h b/crypto/secp256k1/libsecp256k1/src/ecmult_impl.h new file mode 100644 index 0000000..4e40104 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/ecmult_impl.h @@ -0,0 +1,406 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_ECMULT_IMPL_H_ +#define _SECP256K1_ECMULT_IMPL_H_ + +#include + +#include "group.h" +#include "scalar.h" +#include "ecmult.h" + +#if defined(EXHAUSTIVE_TEST_ORDER) +/* We need to lower these values for exhaustive tests because + * the tables cannot have infinities in them (this breaks the + * affine-isomorphism stuff which tracks z-ratios) */ +# if EXHAUSTIVE_TEST_ORDER > 128 +# define WINDOW_A 5 +# define WINDOW_G 8 +# elif EXHAUSTIVE_TEST_ORDER > 8 +# define WINDOW_A 4 +# define WINDOW_G 4 +# else +# define WINDOW_A 2 +# define WINDOW_G 2 +# endif +#else +/* optimal for 128-bit and 256-bit exponents. */ +#define WINDOW_A 5 +/** larger numbers may result in slightly better performance, at the cost of + exponentially larger precomputed tables. */ +#ifdef USE_ENDOMORPHISM +/** Two tables for window size 15: 1.375 MiB. */ +#define WINDOW_G 15 +#else +/** One table for window size 16: 1.375 MiB. */ +#define WINDOW_G 16 +#endif +#endif + +/** The number of entries a table with precomputed multiples needs to have. */ +#define ECMULT_TABLE_SIZE(w) (1 << ((w)-2)) + +/** Fill a table 'prej' with precomputed odd multiples of a. Prej will contain + * the values [1*a,3*a,...,(2*n-1)*a], so it space for n values. zr[0] will + * contain prej[0].z / a.z. The other zr[i] values = prej[i].z / prej[i-1].z. + * Prej's Z values are undefined, except for the last value. + */ +static void secp256k1_ecmult_odd_multiples_table(int n, secp256k1_gej *prej, secp256k1_fe *zr, const secp256k1_gej *a) { + secp256k1_gej d; + secp256k1_ge a_ge, d_ge; + int i; + + VERIFY_CHECK(!a->infinity); + + secp256k1_gej_double_var(&d, a, NULL); + + /* + * Perform the additions on an isomorphism where 'd' is affine: drop the z coordinate + * of 'd', and scale the 1P starting value's x/y coordinates without changing its z. + */ + d_ge.x = d.x; + d_ge.y = d.y; + d_ge.infinity = 0; + + secp256k1_ge_set_gej_zinv(&a_ge, a, &d.z); + prej[0].x = a_ge.x; + prej[0].y = a_ge.y; + prej[0].z = a->z; + prej[0].infinity = 0; + + zr[0] = d.z; + for (i = 1; i < n; i++) { + secp256k1_gej_add_ge_var(&prej[i], &prej[i-1], &d_ge, &zr[i]); + } + + /* + * Each point in 'prej' has a z coordinate too small by a factor of 'd.z'. Only + * the final point's z coordinate is actually used though, so just update that. + */ + secp256k1_fe_mul(&prej[n-1].z, &prej[n-1].z, &d.z); +} + +/** Fill a table 'pre' with precomputed odd multiples of a. + * + * There are two versions of this function: + * - secp256k1_ecmult_odd_multiples_table_globalz_windowa which brings its + * resulting point set to a single constant Z denominator, stores the X and Y + * coordinates as ge_storage points in pre, and stores the global Z in rz. + * It only operates on tables sized for WINDOW_A wnaf multiples. + * - secp256k1_ecmult_odd_multiples_table_storage_var, which converts its + * resulting point set to actually affine points, and stores those in pre. + * It operates on tables of any size, but uses heap-allocated temporaries. + * + * To compute a*P + b*G, we compute a table for P using the first function, + * and for G using the second (which requires an inverse, but it only needs to + * happen once). + */ +static void secp256k1_ecmult_odd_multiples_table_globalz_windowa(secp256k1_ge *pre, secp256k1_fe *globalz, const secp256k1_gej *a) { + secp256k1_gej prej[ECMULT_TABLE_SIZE(WINDOW_A)]; + secp256k1_fe zr[ECMULT_TABLE_SIZE(WINDOW_A)]; + + /* Compute the odd multiples in Jacobian form. */ + secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), prej, zr, a); + /* Bring them to the same Z denominator. */ + secp256k1_ge_globalz_set_table_gej(ECMULT_TABLE_SIZE(WINDOW_A), pre, globalz, prej, zr); +} + +static void secp256k1_ecmult_odd_multiples_table_storage_var(int n, secp256k1_ge_storage *pre, const secp256k1_gej *a, const secp256k1_callback *cb) { + secp256k1_gej *prej = (secp256k1_gej*)checked_malloc(cb, sizeof(secp256k1_gej) * n); + secp256k1_ge *prea = (secp256k1_ge*)checked_malloc(cb, sizeof(secp256k1_ge) * n); + secp256k1_fe *zr = (secp256k1_fe*)checked_malloc(cb, sizeof(secp256k1_fe) * n); + int i; + + /* Compute the odd multiples in Jacobian form. */ + secp256k1_ecmult_odd_multiples_table(n, prej, zr, a); + /* Convert them in batch to affine coordinates. */ + secp256k1_ge_set_table_gej_var(prea, prej, zr, n); + /* Convert them to compact storage form. */ + for (i = 0; i < n; i++) { + secp256k1_ge_to_storage(&pre[i], &prea[i]); + } + + free(prea); + free(prej); + free(zr); +} + +/** The following two macro retrieves a particular odd multiple from a table + * of precomputed multiples. */ +#define ECMULT_TABLE_GET_GE(r,pre,n,w) do { \ + VERIFY_CHECK(((n) & 1) == 1); \ + VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \ + VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); \ + if ((n) > 0) { \ + *(r) = (pre)[((n)-1)/2]; \ + } else { \ + secp256k1_ge_neg((r), &(pre)[(-(n)-1)/2]); \ + } \ +} while(0) + +#define ECMULT_TABLE_GET_GE_STORAGE(r,pre,n,w) do { \ + VERIFY_CHECK(((n) & 1) == 1); \ + VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \ + VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); \ + if ((n) > 0) { \ + secp256k1_ge_from_storage((r), &(pre)[((n)-1)/2]); \ + } else { \ + secp256k1_ge_from_storage((r), &(pre)[(-(n)-1)/2]); \ + secp256k1_ge_neg((r), (r)); \ + } \ +} while(0) + +static void secp256k1_ecmult_context_init(secp256k1_ecmult_context *ctx) { + ctx->pre_g = NULL; +#ifdef USE_ENDOMORPHISM + ctx->pre_g_128 = NULL; +#endif +} + +static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, const secp256k1_callback *cb) { + secp256k1_gej gj; + + if (ctx->pre_g != NULL) { + return; + } + + /* get the generator */ + secp256k1_gej_set_ge(&gj, &secp256k1_ge_const_g); + + ctx->pre_g = (secp256k1_ge_storage (*)[])checked_malloc(cb, sizeof((*ctx->pre_g)[0]) * ECMULT_TABLE_SIZE(WINDOW_G)); + + /* precompute the tables with odd multiples */ + secp256k1_ecmult_odd_multiples_table_storage_var(ECMULT_TABLE_SIZE(WINDOW_G), *ctx->pre_g, &gj, cb); + +#ifdef USE_ENDOMORPHISM + { + secp256k1_gej g_128j; + int i; + + ctx->pre_g_128 = (secp256k1_ge_storage (*)[])checked_malloc(cb, sizeof((*ctx->pre_g_128)[0]) * ECMULT_TABLE_SIZE(WINDOW_G)); + + /* calculate 2^128*generator */ + g_128j = gj; + for (i = 0; i < 128; i++) { + secp256k1_gej_double_var(&g_128j, &g_128j, NULL); + } + secp256k1_ecmult_odd_multiples_table_storage_var(ECMULT_TABLE_SIZE(WINDOW_G), *ctx->pre_g_128, &g_128j, cb); + } +#endif +} + +static void secp256k1_ecmult_context_clone(secp256k1_ecmult_context *dst, + const secp256k1_ecmult_context *src, const secp256k1_callback *cb) { + if (src->pre_g == NULL) { + dst->pre_g = NULL; + } else { + size_t size = sizeof((*dst->pre_g)[0]) * ECMULT_TABLE_SIZE(WINDOW_G); + dst->pre_g = (secp256k1_ge_storage (*)[])checked_malloc(cb, size); + memcpy(dst->pre_g, src->pre_g, size); + } +#ifdef USE_ENDOMORPHISM + if (src->pre_g_128 == NULL) { + dst->pre_g_128 = NULL; + } else { + size_t size = sizeof((*dst->pre_g_128)[0]) * ECMULT_TABLE_SIZE(WINDOW_G); + dst->pre_g_128 = (secp256k1_ge_storage (*)[])checked_malloc(cb, size); + memcpy(dst->pre_g_128, src->pre_g_128, size); + } +#endif +} + +static int secp256k1_ecmult_context_is_built(const secp256k1_ecmult_context *ctx) { + return ctx->pre_g != NULL; +} + +static void secp256k1_ecmult_context_clear(secp256k1_ecmult_context *ctx) { + free(ctx->pre_g); +#ifdef USE_ENDOMORPHISM + free(ctx->pre_g_128); +#endif + secp256k1_ecmult_context_init(ctx); +} + +/** Convert a number to WNAF notation. The number becomes represented by sum(2^i * wnaf[i], i=0..bits), + * with the following guarantees: + * - each wnaf[i] is either 0, or an odd integer between -(1<<(w-1) - 1) and (1<<(w-1) - 1) + * - two non-zero entries in wnaf are separated by at least w-1 zeroes. + * - the number of set values in wnaf is returned. This number is at most 256, and at most one more + * than the number of bits in the (absolute value) of the input. + */ +static int secp256k1_ecmult_wnaf(int *wnaf, int len, const secp256k1_scalar *a, int w) { + secp256k1_scalar s = *a; + int last_set_bit = -1; + int bit = 0; + int sign = 1; + int carry = 0; + + VERIFY_CHECK(wnaf != NULL); + VERIFY_CHECK(0 <= len && len <= 256); + VERIFY_CHECK(a != NULL); + VERIFY_CHECK(2 <= w && w <= 31); + + memset(wnaf, 0, len * sizeof(wnaf[0])); + + if (secp256k1_scalar_get_bits(&s, 255, 1)) { + secp256k1_scalar_negate(&s, &s); + sign = -1; + } + + while (bit < len) { + int now; + int word; + if (secp256k1_scalar_get_bits(&s, bit, 1) == (unsigned int)carry) { + bit++; + continue; + } + + now = w; + if (now > len - bit) { + now = len - bit; + } + + word = secp256k1_scalar_get_bits_var(&s, bit, now) + carry; + + carry = (word >> (w-1)) & 1; + word -= carry << w; + + wnaf[bit] = sign * word; + last_set_bit = bit; + + bit += now; + } +#ifdef VERIFY + CHECK(carry == 0); + while (bit < 256) { + CHECK(secp256k1_scalar_get_bits(&s, bit++, 1) == 0); + } +#endif + return last_set_bit + 1; +} + +static void secp256k1_ecmult(const secp256k1_ecmult_context *ctx, secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_scalar *na, const secp256k1_scalar *ng) { + secp256k1_ge pre_a[ECMULT_TABLE_SIZE(WINDOW_A)]; + secp256k1_ge tmpa; + secp256k1_fe Z; +#ifdef USE_ENDOMORPHISM + secp256k1_ge pre_a_lam[ECMULT_TABLE_SIZE(WINDOW_A)]; + secp256k1_scalar na_1, na_lam; + /* Splitted G factors. */ + secp256k1_scalar ng_1, ng_128; + int wnaf_na_1[130]; + int wnaf_na_lam[130]; + int bits_na_1; + int bits_na_lam; + int wnaf_ng_1[129]; + int bits_ng_1; + int wnaf_ng_128[129]; + int bits_ng_128; +#else + int wnaf_na[256]; + int bits_na; + int wnaf_ng[256]; + int bits_ng; +#endif + int i; + int bits; + +#ifdef USE_ENDOMORPHISM + /* split na into na_1 and na_lam (where na = na_1 + na_lam*lambda, and na_1 and na_lam are ~128 bit) */ + secp256k1_scalar_split_lambda(&na_1, &na_lam, na); + + /* build wnaf representation for na_1 and na_lam. */ + bits_na_1 = secp256k1_ecmult_wnaf(wnaf_na_1, 130, &na_1, WINDOW_A); + bits_na_lam = secp256k1_ecmult_wnaf(wnaf_na_lam, 130, &na_lam, WINDOW_A); + VERIFY_CHECK(bits_na_1 <= 130); + VERIFY_CHECK(bits_na_lam <= 130); + bits = bits_na_1; + if (bits_na_lam > bits) { + bits = bits_na_lam; + } +#else + /* build wnaf representation for na. */ + bits_na = secp256k1_ecmult_wnaf(wnaf_na, 256, na, WINDOW_A); + bits = bits_na; +#endif + + /* Calculate odd multiples of a. + * All multiples are brought to the same Z 'denominator', which is stored + * in Z. Due to secp256k1' isomorphism we can do all operations pretending + * that the Z coordinate was 1, use affine addition formulae, and correct + * the Z coordinate of the result once at the end. + * The exception is the precomputed G table points, which are actually + * affine. Compared to the base used for other points, they have a Z ratio + * of 1/Z, so we can use secp256k1_gej_add_zinv_var, which uses the same + * isomorphism to efficiently add with a known Z inverse. + */ + secp256k1_ecmult_odd_multiples_table_globalz_windowa(pre_a, &Z, a); + +#ifdef USE_ENDOMORPHISM + for (i = 0; i < ECMULT_TABLE_SIZE(WINDOW_A); i++) { + secp256k1_ge_mul_lambda(&pre_a_lam[i], &pre_a[i]); + } + + /* split ng into ng_1 and ng_128 (where gn = gn_1 + gn_128*2^128, and gn_1 and gn_128 are ~128 bit) */ + secp256k1_scalar_split_128(&ng_1, &ng_128, ng); + + /* Build wnaf representation for ng_1 and ng_128 */ + bits_ng_1 = secp256k1_ecmult_wnaf(wnaf_ng_1, 129, &ng_1, WINDOW_G); + bits_ng_128 = secp256k1_ecmult_wnaf(wnaf_ng_128, 129, &ng_128, WINDOW_G); + if (bits_ng_1 > bits) { + bits = bits_ng_1; + } + if (bits_ng_128 > bits) { + bits = bits_ng_128; + } +#else + bits_ng = secp256k1_ecmult_wnaf(wnaf_ng, 256, ng, WINDOW_G); + if (bits_ng > bits) { + bits = bits_ng; + } +#endif + + secp256k1_gej_set_infinity(r); + + for (i = bits - 1; i >= 0; i--) { + int n; + secp256k1_gej_double_var(r, r, NULL); +#ifdef USE_ENDOMORPHISM + if (i < bits_na_1 && (n = wnaf_na_1[i])) { + ECMULT_TABLE_GET_GE(&tmpa, pre_a, n, WINDOW_A); + secp256k1_gej_add_ge_var(r, r, &tmpa, NULL); + } + if (i < bits_na_lam && (n = wnaf_na_lam[i])) { + ECMULT_TABLE_GET_GE(&tmpa, pre_a_lam, n, WINDOW_A); + secp256k1_gej_add_ge_var(r, r, &tmpa, NULL); + } + if (i < bits_ng_1 && (n = wnaf_ng_1[i])) { + ECMULT_TABLE_GET_GE_STORAGE(&tmpa, *ctx->pre_g, n, WINDOW_G); + secp256k1_gej_add_zinv_var(r, r, &tmpa, &Z); + } + if (i < bits_ng_128 && (n = wnaf_ng_128[i])) { + ECMULT_TABLE_GET_GE_STORAGE(&tmpa, *ctx->pre_g_128, n, WINDOW_G); + secp256k1_gej_add_zinv_var(r, r, &tmpa, &Z); + } +#else + if (i < bits_na && (n = wnaf_na[i])) { + ECMULT_TABLE_GET_GE(&tmpa, pre_a, n, WINDOW_A); + secp256k1_gej_add_ge_var(r, r, &tmpa, NULL); + } + if (i < bits_ng && (n = wnaf_ng[i])) { + ECMULT_TABLE_GET_GE_STORAGE(&tmpa, *ctx->pre_g, n, WINDOW_G); + secp256k1_gej_add_zinv_var(r, r, &tmpa, &Z); + } +#endif + } + + if (!r->infinity) { + secp256k1_fe_mul(&r->z, &r->z, &Z); + } +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/field.h b/crypto/secp256k1/libsecp256k1/src/field.h new file mode 100644 index 0000000..bbb1ee8 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/field.h @@ -0,0 +1,132 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_FIELD_ +#define _SECP256K1_FIELD_ + +/** Field element module. + * + * Field elements can be represented in several ways, but code accessing + * it (and implementations) need to take certain properties into account: + * - Each field element can be normalized or not. + * - Each field element has a magnitude, which represents how far away + * its representation is away from normalization. Normalized elements + * always have a magnitude of 1, but a magnitude of 1 doesn't imply + * normality. + */ + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#if defined(USE_FIELD_10X26) +#include "field_10x26.h" +#elif defined(USE_FIELD_5X52) +#include "field_5x52.h" +#else +#error "Please select field implementation" +#endif + +#include "util.h" + +/** Normalize a field element. */ +static void secp256k1_fe_normalize(secp256k1_fe *r); + +/** Weakly normalize a field element: reduce it magnitude to 1, but don't fully normalize. */ +static void secp256k1_fe_normalize_weak(secp256k1_fe *r); + +/** Normalize a field element, without constant-time guarantee. */ +static void secp256k1_fe_normalize_var(secp256k1_fe *r); + +/** Verify whether a field element represents zero i.e. would normalize to a zero value. The field + * implementation may optionally normalize the input, but this should not be relied upon. */ +static int secp256k1_fe_normalizes_to_zero(secp256k1_fe *r); + +/** Verify whether a field element represents zero i.e. would normalize to a zero value. The field + * implementation may optionally normalize the input, but this should not be relied upon. */ +static int secp256k1_fe_normalizes_to_zero_var(secp256k1_fe *r); + +/** Set a field element equal to a small integer. Resulting field element is normalized. */ +static void secp256k1_fe_set_int(secp256k1_fe *r, int a); + +/** Sets a field element equal to zero, initializing all fields. */ +static void secp256k1_fe_clear(secp256k1_fe *a); + +/** Verify whether a field element is zero. Requires the input to be normalized. */ +static int secp256k1_fe_is_zero(const secp256k1_fe *a); + +/** Check the "oddness" of a field element. Requires the input to be normalized. */ +static int secp256k1_fe_is_odd(const secp256k1_fe *a); + +/** Compare two field elements. Requires magnitude-1 inputs. */ +static int secp256k1_fe_equal(const secp256k1_fe *a, const secp256k1_fe *b); + +/** Same as secp256k1_fe_equal, but may be variable time. */ +static int secp256k1_fe_equal_var(const secp256k1_fe *a, const secp256k1_fe *b); + +/** Compare two field elements. Requires both inputs to be normalized */ +static int secp256k1_fe_cmp_var(const secp256k1_fe *a, const secp256k1_fe *b); + +/** Set a field element equal to 32-byte big endian value. If successful, the resulting field element is normalized. */ +static int secp256k1_fe_set_b32(secp256k1_fe *r, const unsigned char *a); + +/** Convert a field element to a 32-byte big endian value. Requires the input to be normalized */ +static void secp256k1_fe_get_b32(unsigned char *r, const secp256k1_fe *a); + +/** Set a field element equal to the additive inverse of another. Takes a maximum magnitude of the input + * as an argument. The magnitude of the output is one higher. */ +static void secp256k1_fe_negate(secp256k1_fe *r, const secp256k1_fe *a, int m); + +/** Multiplies the passed field element with a small integer constant. Multiplies the magnitude by that + * small integer. */ +static void secp256k1_fe_mul_int(secp256k1_fe *r, int a); + +/** Adds a field element to another. The result has the sum of the inputs' magnitudes as magnitude. */ +static void secp256k1_fe_add(secp256k1_fe *r, const secp256k1_fe *a); + +/** Sets a field element to be the product of two others. Requires the inputs' magnitudes to be at most 8. + * The output magnitude is 1 (but not guaranteed to be normalized). */ +static void secp256k1_fe_mul(secp256k1_fe *r, const secp256k1_fe *a, const secp256k1_fe * SECP256K1_RESTRICT b); + +/** Sets a field element to be the square of another. Requires the input's magnitude to be at most 8. + * The output magnitude is 1 (but not guaranteed to be normalized). */ +static void secp256k1_fe_sqr(secp256k1_fe *r, const secp256k1_fe *a); + +/** If a has a square root, it is computed in r and 1 is returned. If a does not + * have a square root, the root of its negation is computed and 0 is returned. + * The input's magnitude can be at most 8. The output magnitude is 1 (but not + * guaranteed to be normalized). The result in r will always be a square + * itself. */ +static int secp256k1_fe_sqrt(secp256k1_fe *r, const secp256k1_fe *a); + +/** Checks whether a field element is a quadratic residue. */ +static int secp256k1_fe_is_quad_var(const secp256k1_fe *a); + +/** Sets a field element to be the (modular) inverse of another. Requires the input's magnitude to be + * at most 8. The output magnitude is 1 (but not guaranteed to be normalized). */ +static void secp256k1_fe_inv(secp256k1_fe *r, const secp256k1_fe *a); + +/** Potentially faster version of secp256k1_fe_inv, without constant-time guarantee. */ +static void secp256k1_fe_inv_var(secp256k1_fe *r, const secp256k1_fe *a); + +/** Calculate the (modular) inverses of a batch of field elements. Requires the inputs' magnitudes to be + * at most 8. The output magnitudes are 1 (but not guaranteed to be normalized). The inputs and + * outputs must not overlap in memory. */ +static void secp256k1_fe_inv_all_var(secp256k1_fe *r, const secp256k1_fe *a, size_t len); + +/** Convert a field element to the storage type. */ +static void secp256k1_fe_to_storage(secp256k1_fe_storage *r, const secp256k1_fe *a); + +/** Convert a field element back from the storage type. */ +static void secp256k1_fe_from_storage(secp256k1_fe *r, const secp256k1_fe_storage *a); + +/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. */ +static void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag); + +/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. */ +static void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/field_10x26.h b/crypto/secp256k1/libsecp256k1/src/field_10x26.h new file mode 100644 index 0000000..61ee1e0 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/field_10x26.h @@ -0,0 +1,47 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_FIELD_REPR_ +#define _SECP256K1_FIELD_REPR_ + +#include + +typedef struct { + /* X = sum(i=0..9, elem[i]*2^26) mod n */ + uint32_t n[10]; +#ifdef VERIFY + int magnitude; + int normalized; +#endif +} secp256k1_fe; + +/* Unpacks a constant into a overlapping multi-limbed FE element. */ +#define SECP256K1_FE_CONST_INNER(d7, d6, d5, d4, d3, d2, d1, d0) { \ + (d0) & 0x3FFFFFFUL, \ + (((uint32_t)d0) >> 26) | (((uint32_t)(d1) & 0xFFFFFUL) << 6), \ + (((uint32_t)d1) >> 20) | (((uint32_t)(d2) & 0x3FFFUL) << 12), \ + (((uint32_t)d2) >> 14) | (((uint32_t)(d3) & 0xFFUL) << 18), \ + (((uint32_t)d3) >> 8) | (((uint32_t)(d4) & 0x3UL) << 24), \ + (((uint32_t)d4) >> 2) & 0x3FFFFFFUL, \ + (((uint32_t)d4) >> 28) | (((uint32_t)(d5) & 0x3FFFFFUL) << 4), \ + (((uint32_t)d5) >> 22) | (((uint32_t)(d6) & 0xFFFFUL) << 10), \ + (((uint32_t)d6) >> 16) | (((uint32_t)(d7) & 0x3FFUL) << 16), \ + (((uint32_t)d7) >> 10) \ +} + +#ifdef VERIFY +#define SECP256K1_FE_CONST(d7, d6, d5, d4, d3, d2, d1, d0) {SECP256K1_FE_CONST_INNER((d7), (d6), (d5), (d4), (d3), (d2), (d1), (d0)), 1, 1} +#else +#define SECP256K1_FE_CONST(d7, d6, d5, d4, d3, d2, d1, d0) {SECP256K1_FE_CONST_INNER((d7), (d6), (d5), (d4), (d3), (d2), (d1), (d0))} +#endif + +typedef struct { + uint32_t n[8]; +} secp256k1_fe_storage; + +#define SECP256K1_FE_STORAGE_CONST(d7, d6, d5, d4, d3, d2, d1, d0) {{ (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }} +#define SECP256K1_FE_STORAGE_CONST_GET(d) d.n[7], d.n[6], d.n[5], d.n[4],d.n[3], d.n[2], d.n[1], d.n[0] +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/field_10x26_impl.h b/crypto/secp256k1/libsecp256k1/src/field_10x26_impl.h new file mode 100644 index 0000000..5fb092f --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/field_10x26_impl.h @@ -0,0 +1,1140 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_FIELD_REPR_IMPL_H_ +#define _SECP256K1_FIELD_REPR_IMPL_H_ + +#include "util.h" +#include "num.h" +#include "field.h" + +#ifdef VERIFY +static void secp256k1_fe_verify(const secp256k1_fe *a) { + const uint32_t *d = a->n; + int m = a->normalized ? 1 : 2 * a->magnitude, r = 1; + r &= (d[0] <= 0x3FFFFFFUL * m); + r &= (d[1] <= 0x3FFFFFFUL * m); + r &= (d[2] <= 0x3FFFFFFUL * m); + r &= (d[3] <= 0x3FFFFFFUL * m); + r &= (d[4] <= 0x3FFFFFFUL * m); + r &= (d[5] <= 0x3FFFFFFUL * m); + r &= (d[6] <= 0x3FFFFFFUL * m); + r &= (d[7] <= 0x3FFFFFFUL * m); + r &= (d[8] <= 0x3FFFFFFUL * m); + r &= (d[9] <= 0x03FFFFFUL * m); + r &= (a->magnitude >= 0); + r &= (a->magnitude <= 32); + if (a->normalized) { + r &= (a->magnitude <= 1); + if (r && (d[9] == 0x03FFFFFUL)) { + uint32_t mid = d[8] & d[7] & d[6] & d[5] & d[4] & d[3] & d[2]; + if (mid == 0x3FFFFFFUL) { + r &= ((d[1] + 0x40UL + ((d[0] + 0x3D1UL) >> 26)) <= 0x3FFFFFFUL); + } + } + } + VERIFY_CHECK(r == 1); +} +#endif + +static void secp256k1_fe_normalize(secp256k1_fe *r) { + uint32_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4], + t5 = r->n[5], t6 = r->n[6], t7 = r->n[7], t8 = r->n[8], t9 = r->n[9]; + + /* Reduce t9 at the start so there will be at most a single carry from the first pass */ + uint32_t m; + uint32_t x = t9 >> 22; t9 &= 0x03FFFFFUL; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x3D1UL; t1 += (x << 6); + t1 += (t0 >> 26); t0 &= 0x3FFFFFFUL; + t2 += (t1 >> 26); t1 &= 0x3FFFFFFUL; + t3 += (t2 >> 26); t2 &= 0x3FFFFFFUL; m = t2; + t4 += (t3 >> 26); t3 &= 0x3FFFFFFUL; m &= t3; + t5 += (t4 >> 26); t4 &= 0x3FFFFFFUL; m &= t4; + t6 += (t5 >> 26); t5 &= 0x3FFFFFFUL; m &= t5; + t7 += (t6 >> 26); t6 &= 0x3FFFFFFUL; m &= t6; + t8 += (t7 >> 26); t7 &= 0x3FFFFFFUL; m &= t7; + t9 += (t8 >> 26); t8 &= 0x3FFFFFFUL; m &= t8; + + /* ... except for a possible carry at bit 22 of t9 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t9 >> 23 == 0); + + /* At most a single final reduction is needed; check if the value is >= the field characteristic */ + x = (t9 >> 22) | ((t9 == 0x03FFFFFUL) & (m == 0x3FFFFFFUL) + & ((t1 + 0x40UL + ((t0 + 0x3D1UL) >> 26)) > 0x3FFFFFFUL)); + + /* Apply the final reduction (for constant-time behaviour, we do it always) */ + t0 += x * 0x3D1UL; t1 += (x << 6); + t1 += (t0 >> 26); t0 &= 0x3FFFFFFUL; + t2 += (t1 >> 26); t1 &= 0x3FFFFFFUL; + t3 += (t2 >> 26); t2 &= 0x3FFFFFFUL; + t4 += (t3 >> 26); t3 &= 0x3FFFFFFUL; + t5 += (t4 >> 26); t4 &= 0x3FFFFFFUL; + t6 += (t5 >> 26); t5 &= 0x3FFFFFFUL; + t7 += (t6 >> 26); t6 &= 0x3FFFFFFUL; + t8 += (t7 >> 26); t7 &= 0x3FFFFFFUL; + t9 += (t8 >> 26); t8 &= 0x3FFFFFFUL; + + /* If t9 didn't carry to bit 22 already, then it should have after any final reduction */ + VERIFY_CHECK(t9 >> 22 == x); + + /* Mask off the possible multiple of 2^256 from the final reduction */ + t9 &= 0x03FFFFFUL; + + r->n[0] = t0; r->n[1] = t1; r->n[2] = t2; r->n[3] = t3; r->n[4] = t4; + r->n[5] = t5; r->n[6] = t6; r->n[7] = t7; r->n[8] = t8; r->n[9] = t9; + +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; + secp256k1_fe_verify(r); +#endif +} + +static void secp256k1_fe_normalize_weak(secp256k1_fe *r) { + uint32_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4], + t5 = r->n[5], t6 = r->n[6], t7 = r->n[7], t8 = r->n[8], t9 = r->n[9]; + + /* Reduce t9 at the start so there will be at most a single carry from the first pass */ + uint32_t x = t9 >> 22; t9 &= 0x03FFFFFUL; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x3D1UL; t1 += (x << 6); + t1 += (t0 >> 26); t0 &= 0x3FFFFFFUL; + t2 += (t1 >> 26); t1 &= 0x3FFFFFFUL; + t3 += (t2 >> 26); t2 &= 0x3FFFFFFUL; + t4 += (t3 >> 26); t3 &= 0x3FFFFFFUL; + t5 += (t4 >> 26); t4 &= 0x3FFFFFFUL; + t6 += (t5 >> 26); t5 &= 0x3FFFFFFUL; + t7 += (t6 >> 26); t6 &= 0x3FFFFFFUL; + t8 += (t7 >> 26); t7 &= 0x3FFFFFFUL; + t9 += (t8 >> 26); t8 &= 0x3FFFFFFUL; + + /* ... except for a possible carry at bit 22 of t9 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t9 >> 23 == 0); + + r->n[0] = t0; r->n[1] = t1; r->n[2] = t2; r->n[3] = t3; r->n[4] = t4; + r->n[5] = t5; r->n[6] = t6; r->n[7] = t7; r->n[8] = t8; r->n[9] = t9; + +#ifdef VERIFY + r->magnitude = 1; + secp256k1_fe_verify(r); +#endif +} + +static void secp256k1_fe_normalize_var(secp256k1_fe *r) { + uint32_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4], + t5 = r->n[5], t6 = r->n[6], t7 = r->n[7], t8 = r->n[8], t9 = r->n[9]; + + /* Reduce t9 at the start so there will be at most a single carry from the first pass */ + uint32_t m; + uint32_t x = t9 >> 22; t9 &= 0x03FFFFFUL; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x3D1UL; t1 += (x << 6); + t1 += (t0 >> 26); t0 &= 0x3FFFFFFUL; + t2 += (t1 >> 26); t1 &= 0x3FFFFFFUL; + t3 += (t2 >> 26); t2 &= 0x3FFFFFFUL; m = t2; + t4 += (t3 >> 26); t3 &= 0x3FFFFFFUL; m &= t3; + t5 += (t4 >> 26); t4 &= 0x3FFFFFFUL; m &= t4; + t6 += (t5 >> 26); t5 &= 0x3FFFFFFUL; m &= t5; + t7 += (t6 >> 26); t6 &= 0x3FFFFFFUL; m &= t6; + t8 += (t7 >> 26); t7 &= 0x3FFFFFFUL; m &= t7; + t9 += (t8 >> 26); t8 &= 0x3FFFFFFUL; m &= t8; + + /* ... except for a possible carry at bit 22 of t9 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t9 >> 23 == 0); + + /* At most a single final reduction is needed; check if the value is >= the field characteristic */ + x = (t9 >> 22) | ((t9 == 0x03FFFFFUL) & (m == 0x3FFFFFFUL) + & ((t1 + 0x40UL + ((t0 + 0x3D1UL) >> 26)) > 0x3FFFFFFUL)); + + if (x) { + t0 += 0x3D1UL; t1 += (x << 6); + t1 += (t0 >> 26); t0 &= 0x3FFFFFFUL; + t2 += (t1 >> 26); t1 &= 0x3FFFFFFUL; + t3 += (t2 >> 26); t2 &= 0x3FFFFFFUL; + t4 += (t3 >> 26); t3 &= 0x3FFFFFFUL; + t5 += (t4 >> 26); t4 &= 0x3FFFFFFUL; + t6 += (t5 >> 26); t5 &= 0x3FFFFFFUL; + t7 += (t6 >> 26); t6 &= 0x3FFFFFFUL; + t8 += (t7 >> 26); t7 &= 0x3FFFFFFUL; + t9 += (t8 >> 26); t8 &= 0x3FFFFFFUL; + + /* If t9 didn't carry to bit 22 already, then it should have after any final reduction */ + VERIFY_CHECK(t9 >> 22 == x); + + /* Mask off the possible multiple of 2^256 from the final reduction */ + t9 &= 0x03FFFFFUL; + } + + r->n[0] = t0; r->n[1] = t1; r->n[2] = t2; r->n[3] = t3; r->n[4] = t4; + r->n[5] = t5; r->n[6] = t6; r->n[7] = t7; r->n[8] = t8; r->n[9] = t9; + +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; + secp256k1_fe_verify(r); +#endif +} + +static int secp256k1_fe_normalizes_to_zero(secp256k1_fe *r) { + uint32_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4], + t5 = r->n[5], t6 = r->n[6], t7 = r->n[7], t8 = r->n[8], t9 = r->n[9]; + + /* z0 tracks a possible raw value of 0, z1 tracks a possible raw value of P */ + uint32_t z0, z1; + + /* Reduce t9 at the start so there will be at most a single carry from the first pass */ + uint32_t x = t9 >> 22; t9 &= 0x03FFFFFUL; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x3D1UL; t1 += (x << 6); + t1 += (t0 >> 26); t0 &= 0x3FFFFFFUL; z0 = t0; z1 = t0 ^ 0x3D0UL; + t2 += (t1 >> 26); t1 &= 0x3FFFFFFUL; z0 |= t1; z1 &= t1 ^ 0x40UL; + t3 += (t2 >> 26); t2 &= 0x3FFFFFFUL; z0 |= t2; z1 &= t2; + t4 += (t3 >> 26); t3 &= 0x3FFFFFFUL; z0 |= t3; z1 &= t3; + t5 += (t4 >> 26); t4 &= 0x3FFFFFFUL; z0 |= t4; z1 &= t4; + t6 += (t5 >> 26); t5 &= 0x3FFFFFFUL; z0 |= t5; z1 &= t5; + t7 += (t6 >> 26); t6 &= 0x3FFFFFFUL; z0 |= t6; z1 &= t6; + t8 += (t7 >> 26); t7 &= 0x3FFFFFFUL; z0 |= t7; z1 &= t7; + t9 += (t8 >> 26); t8 &= 0x3FFFFFFUL; z0 |= t8; z1 &= t8; + z0 |= t9; z1 &= t9 ^ 0x3C00000UL; + + /* ... except for a possible carry at bit 22 of t9 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t9 >> 23 == 0); + + return (z0 == 0) | (z1 == 0x3FFFFFFUL); +} + +static int secp256k1_fe_normalizes_to_zero_var(secp256k1_fe *r) { + uint32_t t0, t1, t2, t3, t4, t5, t6, t7, t8, t9; + uint32_t z0, z1; + uint32_t x; + + t0 = r->n[0]; + t9 = r->n[9]; + + /* Reduce t9 at the start so there will be at most a single carry from the first pass */ + x = t9 >> 22; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x3D1UL; + + /* z0 tracks a possible raw value of 0, z1 tracks a possible raw value of P */ + z0 = t0 & 0x3FFFFFFUL; + z1 = z0 ^ 0x3D0UL; + + /* Fast return path should catch the majority of cases */ + if ((z0 != 0UL) & (z1 != 0x3FFFFFFUL)) { + return 0; + } + + t1 = r->n[1]; + t2 = r->n[2]; + t3 = r->n[3]; + t4 = r->n[4]; + t5 = r->n[5]; + t6 = r->n[6]; + t7 = r->n[7]; + t8 = r->n[8]; + + t9 &= 0x03FFFFFUL; + t1 += (x << 6); + + t1 += (t0 >> 26); + t2 += (t1 >> 26); t1 &= 0x3FFFFFFUL; z0 |= t1; z1 &= t1 ^ 0x40UL; + t3 += (t2 >> 26); t2 &= 0x3FFFFFFUL; z0 |= t2; z1 &= t2; + t4 += (t3 >> 26); t3 &= 0x3FFFFFFUL; z0 |= t3; z1 &= t3; + t5 += (t4 >> 26); t4 &= 0x3FFFFFFUL; z0 |= t4; z1 &= t4; + t6 += (t5 >> 26); t5 &= 0x3FFFFFFUL; z0 |= t5; z1 &= t5; + t7 += (t6 >> 26); t6 &= 0x3FFFFFFUL; z0 |= t6; z1 &= t6; + t8 += (t7 >> 26); t7 &= 0x3FFFFFFUL; z0 |= t7; z1 &= t7; + t9 += (t8 >> 26); t8 &= 0x3FFFFFFUL; z0 |= t8; z1 &= t8; + z0 |= t9; z1 &= t9 ^ 0x3C00000UL; + + /* ... except for a possible carry at bit 22 of t9 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t9 >> 23 == 0); + + return (z0 == 0) | (z1 == 0x3FFFFFFUL); +} + +SECP256K1_INLINE static void secp256k1_fe_set_int(secp256k1_fe *r, int a) { + r->n[0] = a; + r->n[1] = r->n[2] = r->n[3] = r->n[4] = r->n[5] = r->n[6] = r->n[7] = r->n[8] = r->n[9] = 0; +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; + secp256k1_fe_verify(r); +#endif +} + +SECP256K1_INLINE static int secp256k1_fe_is_zero(const secp256k1_fe *a) { + const uint32_t *t = a->n; +#ifdef VERIFY + VERIFY_CHECK(a->normalized); + secp256k1_fe_verify(a); +#endif + return (t[0] | t[1] | t[2] | t[3] | t[4] | t[5] | t[6] | t[7] | t[8] | t[9]) == 0; +} + +SECP256K1_INLINE static int secp256k1_fe_is_odd(const secp256k1_fe *a) { +#ifdef VERIFY + VERIFY_CHECK(a->normalized); + secp256k1_fe_verify(a); +#endif + return a->n[0] & 1; +} + +SECP256K1_INLINE static void secp256k1_fe_clear(secp256k1_fe *a) { + int i; +#ifdef VERIFY + a->magnitude = 0; + a->normalized = 1; +#endif + for (i=0; i<10; i++) { + a->n[i] = 0; + } +} + +static int secp256k1_fe_cmp_var(const secp256k1_fe *a, const secp256k1_fe *b) { + int i; +#ifdef VERIFY + VERIFY_CHECK(a->normalized); + VERIFY_CHECK(b->normalized); + secp256k1_fe_verify(a); + secp256k1_fe_verify(b); +#endif + for (i = 9; i >= 0; i--) { + if (a->n[i] > b->n[i]) { + return 1; + } + if (a->n[i] < b->n[i]) { + return -1; + } + } + return 0; +} + +static int secp256k1_fe_set_b32(secp256k1_fe *r, const unsigned char *a) { + int i; + r->n[0] = r->n[1] = r->n[2] = r->n[3] = r->n[4] = 0; + r->n[5] = r->n[6] = r->n[7] = r->n[8] = r->n[9] = 0; + for (i=0; i<32; i++) { + int j; + for (j=0; j<4; j++) { + int limb = (8*i+2*j)/26; + int shift = (8*i+2*j)%26; + r->n[limb] |= (uint32_t)((a[31-i] >> (2*j)) & 0x3) << shift; + } + } + if (r->n[9] == 0x3FFFFFUL && (r->n[8] & r->n[7] & r->n[6] & r->n[5] & r->n[4] & r->n[3] & r->n[2]) == 0x3FFFFFFUL && (r->n[1] + 0x40UL + ((r->n[0] + 0x3D1UL) >> 26)) > 0x3FFFFFFUL) { + return 0; + } +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; + secp256k1_fe_verify(r); +#endif + return 1; +} + +/** Convert a field element to a 32-byte big endian value. Requires the input to be normalized */ +static void secp256k1_fe_get_b32(unsigned char *r, const secp256k1_fe *a) { + int i; +#ifdef VERIFY + VERIFY_CHECK(a->normalized); + secp256k1_fe_verify(a); +#endif + for (i=0; i<32; i++) { + int j; + int c = 0; + for (j=0; j<4; j++) { + int limb = (8*i+2*j)/26; + int shift = (8*i+2*j)%26; + c |= ((a->n[limb] >> shift) & 0x3) << (2 * j); + } + r[31-i] = c; + } +} + +SECP256K1_INLINE static void secp256k1_fe_negate(secp256k1_fe *r, const secp256k1_fe *a, int m) { +#ifdef VERIFY + VERIFY_CHECK(a->magnitude <= m); + secp256k1_fe_verify(a); +#endif + r->n[0] = 0x3FFFC2FUL * 2 * (m + 1) - a->n[0]; + r->n[1] = 0x3FFFFBFUL * 2 * (m + 1) - a->n[1]; + r->n[2] = 0x3FFFFFFUL * 2 * (m + 1) - a->n[2]; + r->n[3] = 0x3FFFFFFUL * 2 * (m + 1) - a->n[3]; + r->n[4] = 0x3FFFFFFUL * 2 * (m + 1) - a->n[4]; + r->n[5] = 0x3FFFFFFUL * 2 * (m + 1) - a->n[5]; + r->n[6] = 0x3FFFFFFUL * 2 * (m + 1) - a->n[6]; + r->n[7] = 0x3FFFFFFUL * 2 * (m + 1) - a->n[7]; + r->n[8] = 0x3FFFFFFUL * 2 * (m + 1) - a->n[8]; + r->n[9] = 0x03FFFFFUL * 2 * (m + 1) - a->n[9]; +#ifdef VERIFY + r->magnitude = m + 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +SECP256K1_INLINE static void secp256k1_fe_mul_int(secp256k1_fe *r, int a) { + r->n[0] *= a; + r->n[1] *= a; + r->n[2] *= a; + r->n[3] *= a; + r->n[4] *= a; + r->n[5] *= a; + r->n[6] *= a; + r->n[7] *= a; + r->n[8] *= a; + r->n[9] *= a; +#ifdef VERIFY + r->magnitude *= a; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +SECP256K1_INLINE static void secp256k1_fe_add(secp256k1_fe *r, const secp256k1_fe *a) { +#ifdef VERIFY + secp256k1_fe_verify(a); +#endif + r->n[0] += a->n[0]; + r->n[1] += a->n[1]; + r->n[2] += a->n[2]; + r->n[3] += a->n[3]; + r->n[4] += a->n[4]; + r->n[5] += a->n[5]; + r->n[6] += a->n[6]; + r->n[7] += a->n[7]; + r->n[8] += a->n[8]; + r->n[9] += a->n[9]; +#ifdef VERIFY + r->magnitude += a->magnitude; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +#if defined(USE_EXTERNAL_ASM) + +/* External assembler implementation */ +void secp256k1_fe_mul_inner(uint32_t *r, const uint32_t *a, const uint32_t * SECP256K1_RESTRICT b); +void secp256k1_fe_sqr_inner(uint32_t *r, const uint32_t *a); + +#else + +#ifdef VERIFY +#define VERIFY_BITS(x, n) VERIFY_CHECK(((x) >> (n)) == 0) +#else +#define VERIFY_BITS(x, n) do { } while(0) +#endif + +SECP256K1_INLINE static void secp256k1_fe_mul_inner(uint32_t *r, const uint32_t *a, const uint32_t * SECP256K1_RESTRICT b) { + uint64_t c, d; + uint64_t u0, u1, u2, u3, u4, u5, u6, u7, u8; + uint32_t t9, t1, t0, t2, t3, t4, t5, t6, t7; + const uint32_t M = 0x3FFFFFFUL, R0 = 0x3D10UL, R1 = 0x400UL; + + VERIFY_BITS(a[0], 30); + VERIFY_BITS(a[1], 30); + VERIFY_BITS(a[2], 30); + VERIFY_BITS(a[3], 30); + VERIFY_BITS(a[4], 30); + VERIFY_BITS(a[5], 30); + VERIFY_BITS(a[6], 30); + VERIFY_BITS(a[7], 30); + VERIFY_BITS(a[8], 30); + VERIFY_BITS(a[9], 26); + VERIFY_BITS(b[0], 30); + VERIFY_BITS(b[1], 30); + VERIFY_BITS(b[2], 30); + VERIFY_BITS(b[3], 30); + VERIFY_BITS(b[4], 30); + VERIFY_BITS(b[5], 30); + VERIFY_BITS(b[6], 30); + VERIFY_BITS(b[7], 30); + VERIFY_BITS(b[8], 30); + VERIFY_BITS(b[9], 26); + + /** [... a b c] is a shorthand for ... + a<<52 + b<<26 + c<<0 mod n. + * px is a shorthand for sum(a[i]*b[x-i], i=0..x). + * Note that [x 0 0 0 0 0 0 0 0 0 0] = [x*R1 x*R0]. + */ + + d = (uint64_t)a[0] * b[9] + + (uint64_t)a[1] * b[8] + + (uint64_t)a[2] * b[7] + + (uint64_t)a[3] * b[6] + + (uint64_t)a[4] * b[5] + + (uint64_t)a[5] * b[4] + + (uint64_t)a[6] * b[3] + + (uint64_t)a[7] * b[2] + + (uint64_t)a[8] * b[1] + + (uint64_t)a[9] * b[0]; + /* VERIFY_BITS(d, 64); */ + /* [d 0 0 0 0 0 0 0 0 0] = [p9 0 0 0 0 0 0 0 0 0] */ + t9 = d & M; d >>= 26; + VERIFY_BITS(t9, 26); + VERIFY_BITS(d, 38); + /* [d t9 0 0 0 0 0 0 0 0 0] = [p9 0 0 0 0 0 0 0 0 0] */ + + c = (uint64_t)a[0] * b[0]; + VERIFY_BITS(c, 60); + /* [d t9 0 0 0 0 0 0 0 0 c] = [p9 0 0 0 0 0 0 0 0 p0] */ + d += (uint64_t)a[1] * b[9] + + (uint64_t)a[2] * b[8] + + (uint64_t)a[3] * b[7] + + (uint64_t)a[4] * b[6] + + (uint64_t)a[5] * b[5] + + (uint64_t)a[6] * b[4] + + (uint64_t)a[7] * b[3] + + (uint64_t)a[8] * b[2] + + (uint64_t)a[9] * b[1]; + VERIFY_BITS(d, 63); + /* [d t9 0 0 0 0 0 0 0 0 c] = [p10 p9 0 0 0 0 0 0 0 0 p0] */ + u0 = d & M; d >>= 26; c += u0 * R0; + VERIFY_BITS(u0, 26); + VERIFY_BITS(d, 37); + VERIFY_BITS(c, 61); + /* [d u0 t9 0 0 0 0 0 0 0 0 c-u0*R0] = [p10 p9 0 0 0 0 0 0 0 0 p0] */ + t0 = c & M; c >>= 26; c += u0 * R1; + VERIFY_BITS(t0, 26); + VERIFY_BITS(c, 37); + /* [d u0 t9 0 0 0 0 0 0 0 c-u0*R1 t0-u0*R0] = [p10 p9 0 0 0 0 0 0 0 0 p0] */ + /* [d 0 t9 0 0 0 0 0 0 0 c t0] = [p10 p9 0 0 0 0 0 0 0 0 p0] */ + + c += (uint64_t)a[0] * b[1] + + (uint64_t)a[1] * b[0]; + VERIFY_BITS(c, 62); + /* [d 0 t9 0 0 0 0 0 0 0 c t0] = [p10 p9 0 0 0 0 0 0 0 p1 p0] */ + d += (uint64_t)a[2] * b[9] + + (uint64_t)a[3] * b[8] + + (uint64_t)a[4] * b[7] + + (uint64_t)a[5] * b[6] + + (uint64_t)a[6] * b[5] + + (uint64_t)a[7] * b[4] + + (uint64_t)a[8] * b[3] + + (uint64_t)a[9] * b[2]; + VERIFY_BITS(d, 63); + /* [d 0 t9 0 0 0 0 0 0 0 c t0] = [p11 p10 p9 0 0 0 0 0 0 0 p1 p0] */ + u1 = d & M; d >>= 26; c += u1 * R0; + VERIFY_BITS(u1, 26); + VERIFY_BITS(d, 37); + VERIFY_BITS(c, 63); + /* [d u1 0 t9 0 0 0 0 0 0 0 c-u1*R0 t0] = [p11 p10 p9 0 0 0 0 0 0 0 p1 p0] */ + t1 = c & M; c >>= 26; c += u1 * R1; + VERIFY_BITS(t1, 26); + VERIFY_BITS(c, 38); + /* [d u1 0 t9 0 0 0 0 0 0 c-u1*R1 t1-u1*R0 t0] = [p11 p10 p9 0 0 0 0 0 0 0 p1 p0] */ + /* [d 0 0 t9 0 0 0 0 0 0 c t1 t0] = [p11 p10 p9 0 0 0 0 0 0 0 p1 p0] */ + + c += (uint64_t)a[0] * b[2] + + (uint64_t)a[1] * b[1] + + (uint64_t)a[2] * b[0]; + VERIFY_BITS(c, 62); + /* [d 0 0 t9 0 0 0 0 0 0 c t1 t0] = [p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + d += (uint64_t)a[3] * b[9] + + (uint64_t)a[4] * b[8] + + (uint64_t)a[5] * b[7] + + (uint64_t)a[6] * b[6] + + (uint64_t)a[7] * b[5] + + (uint64_t)a[8] * b[4] + + (uint64_t)a[9] * b[3]; + VERIFY_BITS(d, 63); + /* [d 0 0 t9 0 0 0 0 0 0 c t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + u2 = d & M; d >>= 26; c += u2 * R0; + VERIFY_BITS(u2, 26); + VERIFY_BITS(d, 37); + VERIFY_BITS(c, 63); + /* [d u2 0 0 t9 0 0 0 0 0 0 c-u2*R0 t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + t2 = c & M; c >>= 26; c += u2 * R1; + VERIFY_BITS(t2, 26); + VERIFY_BITS(c, 38); + /* [d u2 0 0 t9 0 0 0 0 0 c-u2*R1 t2-u2*R0 t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + /* [d 0 0 0 t9 0 0 0 0 0 c t2 t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + + c += (uint64_t)a[0] * b[3] + + (uint64_t)a[1] * b[2] + + (uint64_t)a[2] * b[1] + + (uint64_t)a[3] * b[0]; + VERIFY_BITS(c, 63); + /* [d 0 0 0 t9 0 0 0 0 0 c t2 t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + d += (uint64_t)a[4] * b[9] + + (uint64_t)a[5] * b[8] + + (uint64_t)a[6] * b[7] + + (uint64_t)a[7] * b[6] + + (uint64_t)a[8] * b[5] + + (uint64_t)a[9] * b[4]; + VERIFY_BITS(d, 63); + /* [d 0 0 0 t9 0 0 0 0 0 c t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + u3 = d & M; d >>= 26; c += u3 * R0; + VERIFY_BITS(u3, 26); + VERIFY_BITS(d, 37); + /* VERIFY_BITS(c, 64); */ + /* [d u3 0 0 0 t9 0 0 0 0 0 c-u3*R0 t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + t3 = c & M; c >>= 26; c += u3 * R1; + VERIFY_BITS(t3, 26); + VERIFY_BITS(c, 39); + /* [d u3 0 0 0 t9 0 0 0 0 c-u3*R1 t3-u3*R0 t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + /* [d 0 0 0 0 t9 0 0 0 0 c t3 t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + + c += (uint64_t)a[0] * b[4] + + (uint64_t)a[1] * b[3] + + (uint64_t)a[2] * b[2] + + (uint64_t)a[3] * b[1] + + (uint64_t)a[4] * b[0]; + VERIFY_BITS(c, 63); + /* [d 0 0 0 0 t9 0 0 0 0 c t3 t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + d += (uint64_t)a[5] * b[9] + + (uint64_t)a[6] * b[8] + + (uint64_t)a[7] * b[7] + + (uint64_t)a[8] * b[6] + + (uint64_t)a[9] * b[5]; + VERIFY_BITS(d, 62); + /* [d 0 0 0 0 t9 0 0 0 0 c t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + u4 = d & M; d >>= 26; c += u4 * R0; + VERIFY_BITS(u4, 26); + VERIFY_BITS(d, 36); + /* VERIFY_BITS(c, 64); */ + /* [d u4 0 0 0 0 t9 0 0 0 0 c-u4*R0 t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + t4 = c & M; c >>= 26; c += u4 * R1; + VERIFY_BITS(t4, 26); + VERIFY_BITS(c, 39); + /* [d u4 0 0 0 0 t9 0 0 0 c-u4*R1 t4-u4*R0 t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 t9 0 0 0 c t4 t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + + c += (uint64_t)a[0] * b[5] + + (uint64_t)a[1] * b[4] + + (uint64_t)a[2] * b[3] + + (uint64_t)a[3] * b[2] + + (uint64_t)a[4] * b[1] + + (uint64_t)a[5] * b[0]; + VERIFY_BITS(c, 63); + /* [d 0 0 0 0 0 t9 0 0 0 c t4 t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + d += (uint64_t)a[6] * b[9] + + (uint64_t)a[7] * b[8] + + (uint64_t)a[8] * b[7] + + (uint64_t)a[9] * b[6]; + VERIFY_BITS(d, 62); + /* [d 0 0 0 0 0 t9 0 0 0 c t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + u5 = d & M; d >>= 26; c += u5 * R0; + VERIFY_BITS(u5, 26); + VERIFY_BITS(d, 36); + /* VERIFY_BITS(c, 64); */ + /* [d u5 0 0 0 0 0 t9 0 0 0 c-u5*R0 t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + t5 = c & M; c >>= 26; c += u5 * R1; + VERIFY_BITS(t5, 26); + VERIFY_BITS(c, 39); + /* [d u5 0 0 0 0 0 t9 0 0 c-u5*R1 t5-u5*R0 t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 t9 0 0 c t5 t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + + c += (uint64_t)a[0] * b[6] + + (uint64_t)a[1] * b[5] + + (uint64_t)a[2] * b[4] + + (uint64_t)a[3] * b[3] + + (uint64_t)a[4] * b[2] + + (uint64_t)a[5] * b[1] + + (uint64_t)a[6] * b[0]; + VERIFY_BITS(c, 63); + /* [d 0 0 0 0 0 0 t9 0 0 c t5 t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + d += (uint64_t)a[7] * b[9] + + (uint64_t)a[8] * b[8] + + (uint64_t)a[9] * b[7]; + VERIFY_BITS(d, 61); + /* [d 0 0 0 0 0 0 t9 0 0 c t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + u6 = d & M; d >>= 26; c += u6 * R0; + VERIFY_BITS(u6, 26); + VERIFY_BITS(d, 35); + /* VERIFY_BITS(c, 64); */ + /* [d u6 0 0 0 0 0 0 t9 0 0 c-u6*R0 t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + t6 = c & M; c >>= 26; c += u6 * R1; + VERIFY_BITS(t6, 26); + VERIFY_BITS(c, 39); + /* [d u6 0 0 0 0 0 0 t9 0 c-u6*R1 t6-u6*R0 t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 0 t9 0 c t6 t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + + c += (uint64_t)a[0] * b[7] + + (uint64_t)a[1] * b[6] + + (uint64_t)a[2] * b[5] + + (uint64_t)a[3] * b[4] + + (uint64_t)a[4] * b[3] + + (uint64_t)a[5] * b[2] + + (uint64_t)a[6] * b[1] + + (uint64_t)a[7] * b[0]; + /* VERIFY_BITS(c, 64); */ + VERIFY_CHECK(c <= 0x8000007C00000007ULL); + /* [d 0 0 0 0 0 0 0 t9 0 c t6 t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + d += (uint64_t)a[8] * b[9] + + (uint64_t)a[9] * b[8]; + VERIFY_BITS(d, 58); + /* [d 0 0 0 0 0 0 0 t9 0 c t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + u7 = d & M; d >>= 26; c += u7 * R0; + VERIFY_BITS(u7, 26); + VERIFY_BITS(d, 32); + /* VERIFY_BITS(c, 64); */ + VERIFY_CHECK(c <= 0x800001703FFFC2F7ULL); + /* [d u7 0 0 0 0 0 0 0 t9 0 c-u7*R0 t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + t7 = c & M; c >>= 26; c += u7 * R1; + VERIFY_BITS(t7, 26); + VERIFY_BITS(c, 38); + /* [d u7 0 0 0 0 0 0 0 t9 c-u7*R1 t7-u7*R0 t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 0 0 t9 c t7 t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + + c += (uint64_t)a[0] * b[8] + + (uint64_t)a[1] * b[7] + + (uint64_t)a[2] * b[6] + + (uint64_t)a[3] * b[5] + + (uint64_t)a[4] * b[4] + + (uint64_t)a[5] * b[3] + + (uint64_t)a[6] * b[2] + + (uint64_t)a[7] * b[1] + + (uint64_t)a[8] * b[0]; + /* VERIFY_BITS(c, 64); */ + VERIFY_CHECK(c <= 0x9000007B80000008ULL); + /* [d 0 0 0 0 0 0 0 0 t9 c t7 t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + d += (uint64_t)a[9] * b[9]; + VERIFY_BITS(d, 57); + /* [d 0 0 0 0 0 0 0 0 t9 c t7 t6 t5 t4 t3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + u8 = d & M; d >>= 26; c += u8 * R0; + VERIFY_BITS(u8, 26); + VERIFY_BITS(d, 31); + /* VERIFY_BITS(c, 64); */ + VERIFY_CHECK(c <= 0x9000016FBFFFC2F8ULL); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 t6 t5 t4 t3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + + r[3] = t3; + VERIFY_BITS(r[3], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 t6 t5 t4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[4] = t4; + VERIFY_BITS(r[4], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 t6 t5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[5] = t5; + VERIFY_BITS(r[5], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 t6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[6] = t6; + VERIFY_BITS(r[6], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[7] = t7; + VERIFY_BITS(r[7], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + + r[8] = c & M; c >>= 26; c += u8 * R1; + VERIFY_BITS(r[8], 26); + VERIFY_BITS(c, 39); + /* [d u8 0 0 0 0 0 0 0 0 t9+c-u8*R1 r8-u8*R0 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 0 0 0 t9+c r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + c += d * R0 + t9; + VERIFY_BITS(c, 45); + /* [d 0 0 0 0 0 0 0 0 0 c-d*R0 r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[9] = c & (M >> 4); c >>= 22; c += d * (R1 << 4); + VERIFY_BITS(r[9], 22); + VERIFY_BITS(c, 46); + /* [d 0 0 0 0 0 0 0 0 r9+((c-d*R1<<4)<<22)-d*R0 r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 0 -d*R1 r9+(c<<22)-d*R0 r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [r9+(c<<22) r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + + d = c * (R0 >> 4) + t0; + VERIFY_BITS(d, 56); + /* [r9+(c<<22) r8 r7 r6 r5 r4 r3 t2 t1 d-c*R0>>4] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[0] = d & M; d >>= 26; + VERIFY_BITS(r[0], 26); + VERIFY_BITS(d, 30); + /* [r9+(c<<22) r8 r7 r6 r5 r4 r3 t2 t1+d r0-c*R0>>4] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + d += c * (R1 >> 4) + t1; + VERIFY_BITS(d, 53); + VERIFY_CHECK(d <= 0x10000003FFFFBFULL); + /* [r9+(c<<22) r8 r7 r6 r5 r4 r3 t2 d-c*R1>>4 r0-c*R0>>4] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [r9 r8 r7 r6 r5 r4 r3 t2 d r0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[1] = d & M; d >>= 26; + VERIFY_BITS(r[1], 26); + VERIFY_BITS(d, 27); + VERIFY_CHECK(d <= 0x4000000ULL); + /* [r9 r8 r7 r6 r5 r4 r3 t2+d r1 r0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + d += t2; + VERIFY_BITS(d, 27); + /* [r9 r8 r7 r6 r5 r4 r3 d r1 r0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[2] = d; + VERIFY_BITS(r[2], 27); + /* [r9 r8 r7 r6 r5 r4 r3 r2 r1 r0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ +} + +SECP256K1_INLINE static void secp256k1_fe_sqr_inner(uint32_t *r, const uint32_t *a) { + uint64_t c, d; + uint64_t u0, u1, u2, u3, u4, u5, u6, u7, u8; + uint32_t t9, t0, t1, t2, t3, t4, t5, t6, t7; + const uint32_t M = 0x3FFFFFFUL, R0 = 0x3D10UL, R1 = 0x400UL; + + VERIFY_BITS(a[0], 30); + VERIFY_BITS(a[1], 30); + VERIFY_BITS(a[2], 30); + VERIFY_BITS(a[3], 30); + VERIFY_BITS(a[4], 30); + VERIFY_BITS(a[5], 30); + VERIFY_BITS(a[6], 30); + VERIFY_BITS(a[7], 30); + VERIFY_BITS(a[8], 30); + VERIFY_BITS(a[9], 26); + + /** [... a b c] is a shorthand for ... + a<<52 + b<<26 + c<<0 mod n. + * px is a shorthand for sum(a[i]*a[x-i], i=0..x). + * Note that [x 0 0 0 0 0 0 0 0 0 0] = [x*R1 x*R0]. + */ + + d = (uint64_t)(a[0]*2) * a[9] + + (uint64_t)(a[1]*2) * a[8] + + (uint64_t)(a[2]*2) * a[7] + + (uint64_t)(a[3]*2) * a[6] + + (uint64_t)(a[4]*2) * a[5]; + /* VERIFY_BITS(d, 64); */ + /* [d 0 0 0 0 0 0 0 0 0] = [p9 0 0 0 0 0 0 0 0 0] */ + t9 = d & M; d >>= 26; + VERIFY_BITS(t9, 26); + VERIFY_BITS(d, 38); + /* [d t9 0 0 0 0 0 0 0 0 0] = [p9 0 0 0 0 0 0 0 0 0] */ + + c = (uint64_t)a[0] * a[0]; + VERIFY_BITS(c, 60); + /* [d t9 0 0 0 0 0 0 0 0 c] = [p9 0 0 0 0 0 0 0 0 p0] */ + d += (uint64_t)(a[1]*2) * a[9] + + (uint64_t)(a[2]*2) * a[8] + + (uint64_t)(a[3]*2) * a[7] + + (uint64_t)(a[4]*2) * a[6] + + (uint64_t)a[5] * a[5]; + VERIFY_BITS(d, 63); + /* [d t9 0 0 0 0 0 0 0 0 c] = [p10 p9 0 0 0 0 0 0 0 0 p0] */ + u0 = d & M; d >>= 26; c += u0 * R0; + VERIFY_BITS(u0, 26); + VERIFY_BITS(d, 37); + VERIFY_BITS(c, 61); + /* [d u0 t9 0 0 0 0 0 0 0 0 c-u0*R0] = [p10 p9 0 0 0 0 0 0 0 0 p0] */ + t0 = c & M; c >>= 26; c += u0 * R1; + VERIFY_BITS(t0, 26); + VERIFY_BITS(c, 37); + /* [d u0 t9 0 0 0 0 0 0 0 c-u0*R1 t0-u0*R0] = [p10 p9 0 0 0 0 0 0 0 0 p0] */ + /* [d 0 t9 0 0 0 0 0 0 0 c t0] = [p10 p9 0 0 0 0 0 0 0 0 p0] */ + + c += (uint64_t)(a[0]*2) * a[1]; + VERIFY_BITS(c, 62); + /* [d 0 t9 0 0 0 0 0 0 0 c t0] = [p10 p9 0 0 0 0 0 0 0 p1 p0] */ + d += (uint64_t)(a[2]*2) * a[9] + + (uint64_t)(a[3]*2) * a[8] + + (uint64_t)(a[4]*2) * a[7] + + (uint64_t)(a[5]*2) * a[6]; + VERIFY_BITS(d, 63); + /* [d 0 t9 0 0 0 0 0 0 0 c t0] = [p11 p10 p9 0 0 0 0 0 0 0 p1 p0] */ + u1 = d & M; d >>= 26; c += u1 * R0; + VERIFY_BITS(u1, 26); + VERIFY_BITS(d, 37); + VERIFY_BITS(c, 63); + /* [d u1 0 t9 0 0 0 0 0 0 0 c-u1*R0 t0] = [p11 p10 p9 0 0 0 0 0 0 0 p1 p0] */ + t1 = c & M; c >>= 26; c += u1 * R1; + VERIFY_BITS(t1, 26); + VERIFY_BITS(c, 38); + /* [d u1 0 t9 0 0 0 0 0 0 c-u1*R1 t1-u1*R0 t0] = [p11 p10 p9 0 0 0 0 0 0 0 p1 p0] */ + /* [d 0 0 t9 0 0 0 0 0 0 c t1 t0] = [p11 p10 p9 0 0 0 0 0 0 0 p1 p0] */ + + c += (uint64_t)(a[0]*2) * a[2] + + (uint64_t)a[1] * a[1]; + VERIFY_BITS(c, 62); + /* [d 0 0 t9 0 0 0 0 0 0 c t1 t0] = [p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + d += (uint64_t)(a[3]*2) * a[9] + + (uint64_t)(a[4]*2) * a[8] + + (uint64_t)(a[5]*2) * a[7] + + (uint64_t)a[6] * a[6]; + VERIFY_BITS(d, 63); + /* [d 0 0 t9 0 0 0 0 0 0 c t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + u2 = d & M; d >>= 26; c += u2 * R0; + VERIFY_BITS(u2, 26); + VERIFY_BITS(d, 37); + VERIFY_BITS(c, 63); + /* [d u2 0 0 t9 0 0 0 0 0 0 c-u2*R0 t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + t2 = c & M; c >>= 26; c += u2 * R1; + VERIFY_BITS(t2, 26); + VERIFY_BITS(c, 38); + /* [d u2 0 0 t9 0 0 0 0 0 c-u2*R1 t2-u2*R0 t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + /* [d 0 0 0 t9 0 0 0 0 0 c t2 t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 0 p2 p1 p0] */ + + c += (uint64_t)(a[0]*2) * a[3] + + (uint64_t)(a[1]*2) * a[2]; + VERIFY_BITS(c, 63); + /* [d 0 0 0 t9 0 0 0 0 0 c t2 t1 t0] = [p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + d += (uint64_t)(a[4]*2) * a[9] + + (uint64_t)(a[5]*2) * a[8] + + (uint64_t)(a[6]*2) * a[7]; + VERIFY_BITS(d, 63); + /* [d 0 0 0 t9 0 0 0 0 0 c t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + u3 = d & M; d >>= 26; c += u3 * R0; + VERIFY_BITS(u3, 26); + VERIFY_BITS(d, 37); + /* VERIFY_BITS(c, 64); */ + /* [d u3 0 0 0 t9 0 0 0 0 0 c-u3*R0 t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + t3 = c & M; c >>= 26; c += u3 * R1; + VERIFY_BITS(t3, 26); + VERIFY_BITS(c, 39); + /* [d u3 0 0 0 t9 0 0 0 0 c-u3*R1 t3-u3*R0 t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + /* [d 0 0 0 0 t9 0 0 0 0 c t3 t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 0 p3 p2 p1 p0] */ + + c += (uint64_t)(a[0]*2) * a[4] + + (uint64_t)(a[1]*2) * a[3] + + (uint64_t)a[2] * a[2]; + VERIFY_BITS(c, 63); + /* [d 0 0 0 0 t9 0 0 0 0 c t3 t2 t1 t0] = [p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + d += (uint64_t)(a[5]*2) * a[9] + + (uint64_t)(a[6]*2) * a[8] + + (uint64_t)a[7] * a[7]; + VERIFY_BITS(d, 62); + /* [d 0 0 0 0 t9 0 0 0 0 c t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + u4 = d & M; d >>= 26; c += u4 * R0; + VERIFY_BITS(u4, 26); + VERIFY_BITS(d, 36); + /* VERIFY_BITS(c, 64); */ + /* [d u4 0 0 0 0 t9 0 0 0 0 c-u4*R0 t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + t4 = c & M; c >>= 26; c += u4 * R1; + VERIFY_BITS(t4, 26); + VERIFY_BITS(c, 39); + /* [d u4 0 0 0 0 t9 0 0 0 c-u4*R1 t4-u4*R0 t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 t9 0 0 0 c t4 t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 0 p4 p3 p2 p1 p0] */ + + c += (uint64_t)(a[0]*2) * a[5] + + (uint64_t)(a[1]*2) * a[4] + + (uint64_t)(a[2]*2) * a[3]; + VERIFY_BITS(c, 63); + /* [d 0 0 0 0 0 t9 0 0 0 c t4 t3 t2 t1 t0] = [p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + d += (uint64_t)(a[6]*2) * a[9] + + (uint64_t)(a[7]*2) * a[8]; + VERIFY_BITS(d, 62); + /* [d 0 0 0 0 0 t9 0 0 0 c t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + u5 = d & M; d >>= 26; c += u5 * R0; + VERIFY_BITS(u5, 26); + VERIFY_BITS(d, 36); + /* VERIFY_BITS(c, 64); */ + /* [d u5 0 0 0 0 0 t9 0 0 0 c-u5*R0 t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + t5 = c & M; c >>= 26; c += u5 * R1; + VERIFY_BITS(t5, 26); + VERIFY_BITS(c, 39); + /* [d u5 0 0 0 0 0 t9 0 0 c-u5*R1 t5-u5*R0 t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 t9 0 0 c t5 t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 0 p5 p4 p3 p2 p1 p0] */ + + c += (uint64_t)(a[0]*2) * a[6] + + (uint64_t)(a[1]*2) * a[5] + + (uint64_t)(a[2]*2) * a[4] + + (uint64_t)a[3] * a[3]; + VERIFY_BITS(c, 63); + /* [d 0 0 0 0 0 0 t9 0 0 c t5 t4 t3 t2 t1 t0] = [p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + d += (uint64_t)(a[7]*2) * a[9] + + (uint64_t)a[8] * a[8]; + VERIFY_BITS(d, 61); + /* [d 0 0 0 0 0 0 t9 0 0 c t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + u6 = d & M; d >>= 26; c += u6 * R0; + VERIFY_BITS(u6, 26); + VERIFY_BITS(d, 35); + /* VERIFY_BITS(c, 64); */ + /* [d u6 0 0 0 0 0 0 t9 0 0 c-u6*R0 t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + t6 = c & M; c >>= 26; c += u6 * R1; + VERIFY_BITS(t6, 26); + VERIFY_BITS(c, 39); + /* [d u6 0 0 0 0 0 0 t9 0 c-u6*R1 t6-u6*R0 t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 0 t9 0 c t6 t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 0 p6 p5 p4 p3 p2 p1 p0] */ + + c += (uint64_t)(a[0]*2) * a[7] + + (uint64_t)(a[1]*2) * a[6] + + (uint64_t)(a[2]*2) * a[5] + + (uint64_t)(a[3]*2) * a[4]; + /* VERIFY_BITS(c, 64); */ + VERIFY_CHECK(c <= 0x8000007C00000007ULL); + /* [d 0 0 0 0 0 0 0 t9 0 c t6 t5 t4 t3 t2 t1 t0] = [p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + d += (uint64_t)(a[8]*2) * a[9]; + VERIFY_BITS(d, 58); + /* [d 0 0 0 0 0 0 0 t9 0 c t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + u7 = d & M; d >>= 26; c += u7 * R0; + VERIFY_BITS(u7, 26); + VERIFY_BITS(d, 32); + /* VERIFY_BITS(c, 64); */ + VERIFY_CHECK(c <= 0x800001703FFFC2F7ULL); + /* [d u7 0 0 0 0 0 0 0 t9 0 c-u7*R0 t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + t7 = c & M; c >>= 26; c += u7 * R1; + VERIFY_BITS(t7, 26); + VERIFY_BITS(c, 38); + /* [d u7 0 0 0 0 0 0 0 t9 c-u7*R1 t7-u7*R0 t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 0 0 t9 c t7 t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 0 p7 p6 p5 p4 p3 p2 p1 p0] */ + + c += (uint64_t)(a[0]*2) * a[8] + + (uint64_t)(a[1]*2) * a[7] + + (uint64_t)(a[2]*2) * a[6] + + (uint64_t)(a[3]*2) * a[5] + + (uint64_t)a[4] * a[4]; + /* VERIFY_BITS(c, 64); */ + VERIFY_CHECK(c <= 0x9000007B80000008ULL); + /* [d 0 0 0 0 0 0 0 0 t9 c t7 t6 t5 t4 t3 t2 t1 t0] = [p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + d += (uint64_t)a[9] * a[9]; + VERIFY_BITS(d, 57); + /* [d 0 0 0 0 0 0 0 0 t9 c t7 t6 t5 t4 t3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + u8 = d & M; d >>= 26; c += u8 * R0; + VERIFY_BITS(u8, 26); + VERIFY_BITS(d, 31); + /* VERIFY_BITS(c, 64); */ + VERIFY_CHECK(c <= 0x9000016FBFFFC2F8ULL); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 t6 t5 t4 t3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + + r[3] = t3; + VERIFY_BITS(r[3], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 t6 t5 t4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[4] = t4; + VERIFY_BITS(r[4], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 t6 t5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[5] = t5; + VERIFY_BITS(r[5], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 t6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[6] = t6; + VERIFY_BITS(r[6], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 t7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[7] = t7; + VERIFY_BITS(r[7], 26); + /* [d u8 0 0 0 0 0 0 0 0 t9 c-u8*R0 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + + r[8] = c & M; c >>= 26; c += u8 * R1; + VERIFY_BITS(r[8], 26); + VERIFY_BITS(c, 39); + /* [d u8 0 0 0 0 0 0 0 0 t9+c-u8*R1 r8-u8*R0 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 0 0 0 t9+c r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + c += d * R0 + t9; + VERIFY_BITS(c, 45); + /* [d 0 0 0 0 0 0 0 0 0 c-d*R0 r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[9] = c & (M >> 4); c >>= 22; c += d * (R1 << 4); + VERIFY_BITS(r[9], 22); + VERIFY_BITS(c, 46); + /* [d 0 0 0 0 0 0 0 0 r9+((c-d*R1<<4)<<22)-d*R0 r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [d 0 0 0 0 0 0 0 -d*R1 r9+(c<<22)-d*R0 r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [r9+(c<<22) r8 r7 r6 r5 r4 r3 t2 t1 t0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + + d = c * (R0 >> 4) + t0; + VERIFY_BITS(d, 56); + /* [r9+(c<<22) r8 r7 r6 r5 r4 r3 t2 t1 d-c*R0>>4] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[0] = d & M; d >>= 26; + VERIFY_BITS(r[0], 26); + VERIFY_BITS(d, 30); + /* [r9+(c<<22) r8 r7 r6 r5 r4 r3 t2 t1+d r0-c*R0>>4] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + d += c * (R1 >> 4) + t1; + VERIFY_BITS(d, 53); + VERIFY_CHECK(d <= 0x10000003FFFFBFULL); + /* [r9+(c<<22) r8 r7 r6 r5 r4 r3 t2 d-c*R1>>4 r0-c*R0>>4] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + /* [r9 r8 r7 r6 r5 r4 r3 t2 d r0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[1] = d & M; d >>= 26; + VERIFY_BITS(r[1], 26); + VERIFY_BITS(d, 27); + VERIFY_CHECK(d <= 0x4000000ULL); + /* [r9 r8 r7 r6 r5 r4 r3 t2+d r1 r0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + d += t2; + VERIFY_BITS(d, 27); + /* [r9 r8 r7 r6 r5 r4 r3 d r1 r0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[2] = d; + VERIFY_BITS(r[2], 27); + /* [r9 r8 r7 r6 r5 r4 r3 r2 r1 r0] = [p18 p17 p16 p15 p14 p13 p12 p11 p10 p9 p8 p7 p6 p5 p4 p3 p2 p1 p0] */ +} +#endif + +static void secp256k1_fe_mul(secp256k1_fe *r, const secp256k1_fe *a, const secp256k1_fe * SECP256K1_RESTRICT b) { +#ifdef VERIFY + VERIFY_CHECK(a->magnitude <= 8); + VERIFY_CHECK(b->magnitude <= 8); + secp256k1_fe_verify(a); + secp256k1_fe_verify(b); + VERIFY_CHECK(r != b); +#endif + secp256k1_fe_mul_inner(r->n, a->n, b->n); +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +static void secp256k1_fe_sqr(secp256k1_fe *r, const secp256k1_fe *a) { +#ifdef VERIFY + VERIFY_CHECK(a->magnitude <= 8); + secp256k1_fe_verify(a); +#endif + secp256k1_fe_sqr_inner(r->n, a->n); +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +static SECP256K1_INLINE void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag) { + uint32_t mask0, mask1; + mask0 = flag + ~((uint32_t)0); + mask1 = ~mask0; + r->n[0] = (r->n[0] & mask0) | (a->n[0] & mask1); + r->n[1] = (r->n[1] & mask0) | (a->n[1] & mask1); + r->n[2] = (r->n[2] & mask0) | (a->n[2] & mask1); + r->n[3] = (r->n[3] & mask0) | (a->n[3] & mask1); + r->n[4] = (r->n[4] & mask0) | (a->n[4] & mask1); + r->n[5] = (r->n[5] & mask0) | (a->n[5] & mask1); + r->n[6] = (r->n[6] & mask0) | (a->n[6] & mask1); + r->n[7] = (r->n[7] & mask0) | (a->n[7] & mask1); + r->n[8] = (r->n[8] & mask0) | (a->n[8] & mask1); + r->n[9] = (r->n[9] & mask0) | (a->n[9] & mask1); +#ifdef VERIFY + if (a->magnitude > r->magnitude) { + r->magnitude = a->magnitude; + } + r->normalized &= a->normalized; +#endif +} + +static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) { + uint32_t mask0, mask1; + mask0 = flag + ~((uint32_t)0); + mask1 = ~mask0; + r->n[0] = (r->n[0] & mask0) | (a->n[0] & mask1); + r->n[1] = (r->n[1] & mask0) | (a->n[1] & mask1); + r->n[2] = (r->n[2] & mask0) | (a->n[2] & mask1); + r->n[3] = (r->n[3] & mask0) | (a->n[3] & mask1); + r->n[4] = (r->n[4] & mask0) | (a->n[4] & mask1); + r->n[5] = (r->n[5] & mask0) | (a->n[5] & mask1); + r->n[6] = (r->n[6] & mask0) | (a->n[6] & mask1); + r->n[7] = (r->n[7] & mask0) | (a->n[7] & mask1); +} + +static void secp256k1_fe_to_storage(secp256k1_fe_storage *r, const secp256k1_fe *a) { +#ifdef VERIFY + VERIFY_CHECK(a->normalized); +#endif + r->n[0] = a->n[0] | a->n[1] << 26; + r->n[1] = a->n[1] >> 6 | a->n[2] << 20; + r->n[2] = a->n[2] >> 12 | a->n[3] << 14; + r->n[3] = a->n[3] >> 18 | a->n[4] << 8; + r->n[4] = a->n[4] >> 24 | a->n[5] << 2 | a->n[6] << 28; + r->n[5] = a->n[6] >> 4 | a->n[7] << 22; + r->n[6] = a->n[7] >> 10 | a->n[8] << 16; + r->n[7] = a->n[8] >> 16 | a->n[9] << 10; +} + +static SECP256K1_INLINE void secp256k1_fe_from_storage(secp256k1_fe *r, const secp256k1_fe_storage *a) { + r->n[0] = a->n[0] & 0x3FFFFFFUL; + r->n[1] = a->n[0] >> 26 | ((a->n[1] << 6) & 0x3FFFFFFUL); + r->n[2] = a->n[1] >> 20 | ((a->n[2] << 12) & 0x3FFFFFFUL); + r->n[3] = a->n[2] >> 14 | ((a->n[3] << 18) & 0x3FFFFFFUL); + r->n[4] = a->n[3] >> 8 | ((a->n[4] << 24) & 0x3FFFFFFUL); + r->n[5] = (a->n[4] >> 2) & 0x3FFFFFFUL; + r->n[6] = a->n[4] >> 28 | ((a->n[5] << 4) & 0x3FFFFFFUL); + r->n[7] = a->n[5] >> 22 | ((a->n[6] << 10) & 0x3FFFFFFUL); + r->n[8] = a->n[6] >> 16 | ((a->n[7] << 16) & 0x3FFFFFFUL); + r->n[9] = a->n[7] >> 10; +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; +#endif +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/field_5x52.h b/crypto/secp256k1/libsecp256k1/src/field_5x52.h new file mode 100644 index 0000000..8e69a56 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/field_5x52.h @@ -0,0 +1,47 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_FIELD_REPR_ +#define _SECP256K1_FIELD_REPR_ + +#include + +typedef struct { + /* X = sum(i=0..4, elem[i]*2^52) mod n */ + uint64_t n[5]; +#ifdef VERIFY + int magnitude; + int normalized; +#endif +} secp256k1_fe; + +/* Unpacks a constant into a overlapping multi-limbed FE element. */ +#define SECP256K1_FE_CONST_INNER(d7, d6, d5, d4, d3, d2, d1, d0) { \ + (d0) | (((uint64_t)(d1) & 0xFFFFFUL) << 32), \ + ((uint64_t)(d1) >> 20) | (((uint64_t)(d2)) << 12) | (((uint64_t)(d3) & 0xFFUL) << 44), \ + ((uint64_t)(d3) >> 8) | (((uint64_t)(d4) & 0xFFFFFFFUL) << 24), \ + ((uint64_t)(d4) >> 28) | (((uint64_t)(d5)) << 4) | (((uint64_t)(d6) & 0xFFFFUL) << 36), \ + ((uint64_t)(d6) >> 16) | (((uint64_t)(d7)) << 16) \ +} + +#ifdef VERIFY +#define SECP256K1_FE_CONST(d7, d6, d5, d4, d3, d2, d1, d0) {SECP256K1_FE_CONST_INNER((d7), (d6), (d5), (d4), (d3), (d2), (d1), (d0)), 1, 1} +#else +#define SECP256K1_FE_CONST(d7, d6, d5, d4, d3, d2, d1, d0) {SECP256K1_FE_CONST_INNER((d7), (d6), (d5), (d4), (d3), (d2), (d1), (d0))} +#endif + +typedef struct { + uint64_t n[4]; +} secp256k1_fe_storage; + +#define SECP256K1_FE_STORAGE_CONST(d7, d6, d5, d4, d3, d2, d1, d0) {{ \ + (d0) | (((uint64_t)(d1)) << 32), \ + (d2) | (((uint64_t)(d3)) << 32), \ + (d4) | (((uint64_t)(d5)) << 32), \ + (d6) | (((uint64_t)(d7)) << 32) \ +}} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/field_5x52_asm_impl.h b/crypto/secp256k1/libsecp256k1/src/field_5x52_asm_impl.h new file mode 100644 index 0000000..98cc004 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/field_5x52_asm_impl.h @@ -0,0 +1,502 @@ +/********************************************************************** + * Copyright (c) 2013-2014 Diederik Huys, Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +/** + * Changelog: + * - March 2013, Diederik Huys: original version + * - November 2014, Pieter Wuille: updated to use Peter Dettman's parallel multiplication algorithm + * - December 2014, Pieter Wuille: converted from YASM to GCC inline assembly + */ + +#ifndef _SECP256K1_FIELD_INNER5X52_IMPL_H_ +#define _SECP256K1_FIELD_INNER5X52_IMPL_H_ + +SECP256K1_INLINE static void secp256k1_fe_mul_inner(uint64_t *r, const uint64_t *a, const uint64_t * SECP256K1_RESTRICT b) { +/** + * Registers: rdx:rax = multiplication accumulator + * r9:r8 = c + * r15:rcx = d + * r10-r14 = a0-a4 + * rbx = b + * rdi = r + * rsi = a / t? + */ + uint64_t tmp1, tmp2, tmp3; +__asm__ __volatile__( + "movq 0(%%rsi),%%r10\n" + "movq 8(%%rsi),%%r11\n" + "movq 16(%%rsi),%%r12\n" + "movq 24(%%rsi),%%r13\n" + "movq 32(%%rsi),%%r14\n" + + /* d += a3 * b0 */ + "movq 0(%%rbx),%%rax\n" + "mulq %%r13\n" + "movq %%rax,%%rcx\n" + "movq %%rdx,%%r15\n" + /* d += a2 * b1 */ + "movq 8(%%rbx),%%rax\n" + "mulq %%r12\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a1 * b2 */ + "movq 16(%%rbx),%%rax\n" + "mulq %%r11\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d = a0 * b3 */ + "movq 24(%%rbx),%%rax\n" + "mulq %%r10\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* c = a4 * b4 */ + "movq 32(%%rbx),%%rax\n" + "mulq %%r14\n" + "movq %%rax,%%r8\n" + "movq %%rdx,%%r9\n" + /* d += (c & M) * R */ + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* c >>= 52 (%%r8 only) */ + "shrdq $52,%%r9,%%r8\n" + /* t3 (tmp1) = d & M */ + "movq %%rcx,%%rsi\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rsi\n" + "movq %%rsi,%q1\n" + /* d >>= 52 */ + "shrdq $52,%%r15,%%rcx\n" + "xorq %%r15,%%r15\n" + /* d += a4 * b0 */ + "movq 0(%%rbx),%%rax\n" + "mulq %%r14\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a3 * b1 */ + "movq 8(%%rbx),%%rax\n" + "mulq %%r13\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a2 * b2 */ + "movq 16(%%rbx),%%rax\n" + "mulq %%r12\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a1 * b3 */ + "movq 24(%%rbx),%%rax\n" + "mulq %%r11\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a0 * b4 */ + "movq 32(%%rbx),%%rax\n" + "mulq %%r10\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += c * R */ + "movq %%r8,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* t4 = d & M (%%rsi) */ + "movq %%rcx,%%rsi\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rsi\n" + /* d >>= 52 */ + "shrdq $52,%%r15,%%rcx\n" + "xorq %%r15,%%r15\n" + /* tx = t4 >> 48 (tmp3) */ + "movq %%rsi,%%rax\n" + "shrq $48,%%rax\n" + "movq %%rax,%q3\n" + /* t4 &= (M >> 4) (tmp2) */ + "movq $0xffffffffffff,%%rax\n" + "andq %%rax,%%rsi\n" + "movq %%rsi,%q2\n" + /* c = a0 * b0 */ + "movq 0(%%rbx),%%rax\n" + "mulq %%r10\n" + "movq %%rax,%%r8\n" + "movq %%rdx,%%r9\n" + /* d += a4 * b1 */ + "movq 8(%%rbx),%%rax\n" + "mulq %%r14\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a3 * b2 */ + "movq 16(%%rbx),%%rax\n" + "mulq %%r13\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a2 * b3 */ + "movq 24(%%rbx),%%rax\n" + "mulq %%r12\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a1 * b4 */ + "movq 32(%%rbx),%%rax\n" + "mulq %%r11\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* u0 = d & M (%%rsi) */ + "movq %%rcx,%%rsi\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rsi\n" + /* d >>= 52 */ + "shrdq $52,%%r15,%%rcx\n" + "xorq %%r15,%%r15\n" + /* u0 = (u0 << 4) | tx (%%rsi) */ + "shlq $4,%%rsi\n" + "movq %q3,%%rax\n" + "orq %%rax,%%rsi\n" + /* c += u0 * (R >> 4) */ + "movq $0x1000003d1,%%rax\n" + "mulq %%rsi\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* r[0] = c & M */ + "movq %%r8,%%rax\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rax\n" + "movq %%rax,0(%%rdi)\n" + /* c >>= 52 */ + "shrdq $52,%%r9,%%r8\n" + "xorq %%r9,%%r9\n" + /* c += a1 * b0 */ + "movq 0(%%rbx),%%rax\n" + "mulq %%r11\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* c += a0 * b1 */ + "movq 8(%%rbx),%%rax\n" + "mulq %%r10\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* d += a4 * b2 */ + "movq 16(%%rbx),%%rax\n" + "mulq %%r14\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a3 * b3 */ + "movq 24(%%rbx),%%rax\n" + "mulq %%r13\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a2 * b4 */ + "movq 32(%%rbx),%%rax\n" + "mulq %%r12\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* c += (d & M) * R */ + "movq %%rcx,%%rax\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* d >>= 52 */ + "shrdq $52,%%r15,%%rcx\n" + "xorq %%r15,%%r15\n" + /* r[1] = c & M */ + "movq %%r8,%%rax\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rax\n" + "movq %%rax,8(%%rdi)\n" + /* c >>= 52 */ + "shrdq $52,%%r9,%%r8\n" + "xorq %%r9,%%r9\n" + /* c += a2 * b0 */ + "movq 0(%%rbx),%%rax\n" + "mulq %%r12\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* c += a1 * b1 */ + "movq 8(%%rbx),%%rax\n" + "mulq %%r11\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* c += a0 * b2 (last use of %%r10 = a0) */ + "movq 16(%%rbx),%%rax\n" + "mulq %%r10\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* fetch t3 (%%r10, overwrites a0), t4 (%%rsi) */ + "movq %q2,%%rsi\n" + "movq %q1,%%r10\n" + /* d += a4 * b3 */ + "movq 24(%%rbx),%%rax\n" + "mulq %%r14\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* d += a3 * b4 */ + "movq 32(%%rbx),%%rax\n" + "mulq %%r13\n" + "addq %%rax,%%rcx\n" + "adcq %%rdx,%%r15\n" + /* c += (d & M) * R */ + "movq %%rcx,%%rax\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* d >>= 52 (%%rcx only) */ + "shrdq $52,%%r15,%%rcx\n" + /* r[2] = c & M */ + "movq %%r8,%%rax\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rax\n" + "movq %%rax,16(%%rdi)\n" + /* c >>= 52 */ + "shrdq $52,%%r9,%%r8\n" + "xorq %%r9,%%r9\n" + /* c += t3 */ + "addq %%r10,%%r8\n" + /* c += d * R */ + "movq %%rcx,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* r[3] = c & M */ + "movq %%r8,%%rax\n" + "movq $0xfffffffffffff,%%rdx\n" + "andq %%rdx,%%rax\n" + "movq %%rax,24(%%rdi)\n" + /* c >>= 52 (%%r8 only) */ + "shrdq $52,%%r9,%%r8\n" + /* c += t4 (%%r8 only) */ + "addq %%rsi,%%r8\n" + /* r[4] = c */ + "movq %%r8,32(%%rdi)\n" +: "+S"(a), "=m"(tmp1), "=m"(tmp2), "=m"(tmp3) +: "b"(b), "D"(r) +: "%rax", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15", "cc", "memory" +); +} + +SECP256K1_INLINE static void secp256k1_fe_sqr_inner(uint64_t *r, const uint64_t *a) { +/** + * Registers: rdx:rax = multiplication accumulator + * r9:r8 = c + * rcx:rbx = d + * r10-r14 = a0-a4 + * r15 = M (0xfffffffffffff) + * rdi = r + * rsi = a / t? + */ + uint64_t tmp1, tmp2, tmp3; +__asm__ __volatile__( + "movq 0(%%rsi),%%r10\n" + "movq 8(%%rsi),%%r11\n" + "movq 16(%%rsi),%%r12\n" + "movq 24(%%rsi),%%r13\n" + "movq 32(%%rsi),%%r14\n" + "movq $0xfffffffffffff,%%r15\n" + + /* d = (a0*2) * a3 */ + "leaq (%%r10,%%r10,1),%%rax\n" + "mulq %%r13\n" + "movq %%rax,%%rbx\n" + "movq %%rdx,%%rcx\n" + /* d += (a1*2) * a2 */ + "leaq (%%r11,%%r11,1),%%rax\n" + "mulq %%r12\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* c = a4 * a4 */ + "movq %%r14,%%rax\n" + "mulq %%r14\n" + "movq %%rax,%%r8\n" + "movq %%rdx,%%r9\n" + /* d += (c & M) * R */ + "andq %%r15,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* c >>= 52 (%%r8 only) */ + "shrdq $52,%%r9,%%r8\n" + /* t3 (tmp1) = d & M */ + "movq %%rbx,%%rsi\n" + "andq %%r15,%%rsi\n" + "movq %%rsi,%q1\n" + /* d >>= 52 */ + "shrdq $52,%%rcx,%%rbx\n" + "xorq %%rcx,%%rcx\n" + /* a4 *= 2 */ + "addq %%r14,%%r14\n" + /* d += a0 * a4 */ + "movq %%r10,%%rax\n" + "mulq %%r14\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* d+= (a1*2) * a3 */ + "leaq (%%r11,%%r11,1),%%rax\n" + "mulq %%r13\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* d += a2 * a2 */ + "movq %%r12,%%rax\n" + "mulq %%r12\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* d += c * R */ + "movq %%r8,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* t4 = d & M (%%rsi) */ + "movq %%rbx,%%rsi\n" + "andq %%r15,%%rsi\n" + /* d >>= 52 */ + "shrdq $52,%%rcx,%%rbx\n" + "xorq %%rcx,%%rcx\n" + /* tx = t4 >> 48 (tmp3) */ + "movq %%rsi,%%rax\n" + "shrq $48,%%rax\n" + "movq %%rax,%q3\n" + /* t4 &= (M >> 4) (tmp2) */ + "movq $0xffffffffffff,%%rax\n" + "andq %%rax,%%rsi\n" + "movq %%rsi,%q2\n" + /* c = a0 * a0 */ + "movq %%r10,%%rax\n" + "mulq %%r10\n" + "movq %%rax,%%r8\n" + "movq %%rdx,%%r9\n" + /* d += a1 * a4 */ + "movq %%r11,%%rax\n" + "mulq %%r14\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* d += (a2*2) * a3 */ + "leaq (%%r12,%%r12,1),%%rax\n" + "mulq %%r13\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* u0 = d & M (%%rsi) */ + "movq %%rbx,%%rsi\n" + "andq %%r15,%%rsi\n" + /* d >>= 52 */ + "shrdq $52,%%rcx,%%rbx\n" + "xorq %%rcx,%%rcx\n" + /* u0 = (u0 << 4) | tx (%%rsi) */ + "shlq $4,%%rsi\n" + "movq %q3,%%rax\n" + "orq %%rax,%%rsi\n" + /* c += u0 * (R >> 4) */ + "movq $0x1000003d1,%%rax\n" + "mulq %%rsi\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* r[0] = c & M */ + "movq %%r8,%%rax\n" + "andq %%r15,%%rax\n" + "movq %%rax,0(%%rdi)\n" + /* c >>= 52 */ + "shrdq $52,%%r9,%%r8\n" + "xorq %%r9,%%r9\n" + /* a0 *= 2 */ + "addq %%r10,%%r10\n" + /* c += a0 * a1 */ + "movq %%r10,%%rax\n" + "mulq %%r11\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* d += a2 * a4 */ + "movq %%r12,%%rax\n" + "mulq %%r14\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* d += a3 * a3 */ + "movq %%r13,%%rax\n" + "mulq %%r13\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* c += (d & M) * R */ + "movq %%rbx,%%rax\n" + "andq %%r15,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* d >>= 52 */ + "shrdq $52,%%rcx,%%rbx\n" + "xorq %%rcx,%%rcx\n" + /* r[1] = c & M */ + "movq %%r8,%%rax\n" + "andq %%r15,%%rax\n" + "movq %%rax,8(%%rdi)\n" + /* c >>= 52 */ + "shrdq $52,%%r9,%%r8\n" + "xorq %%r9,%%r9\n" + /* c += a0 * a2 (last use of %%r10) */ + "movq %%r10,%%rax\n" + "mulq %%r12\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* fetch t3 (%%r10, overwrites a0),t4 (%%rsi) */ + "movq %q2,%%rsi\n" + "movq %q1,%%r10\n" + /* c += a1 * a1 */ + "movq %%r11,%%rax\n" + "mulq %%r11\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* d += a3 * a4 */ + "movq %%r13,%%rax\n" + "mulq %%r14\n" + "addq %%rax,%%rbx\n" + "adcq %%rdx,%%rcx\n" + /* c += (d & M) * R */ + "movq %%rbx,%%rax\n" + "andq %%r15,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* d >>= 52 (%%rbx only) */ + "shrdq $52,%%rcx,%%rbx\n" + /* r[2] = c & M */ + "movq %%r8,%%rax\n" + "andq %%r15,%%rax\n" + "movq %%rax,16(%%rdi)\n" + /* c >>= 52 */ + "shrdq $52,%%r9,%%r8\n" + "xorq %%r9,%%r9\n" + /* c += t3 */ + "addq %%r10,%%r8\n" + /* c += d * R */ + "movq %%rbx,%%rax\n" + "movq $0x1000003d10,%%rdx\n" + "mulq %%rdx\n" + "addq %%rax,%%r8\n" + "adcq %%rdx,%%r9\n" + /* r[3] = c & M */ + "movq %%r8,%%rax\n" + "andq %%r15,%%rax\n" + "movq %%rax,24(%%rdi)\n" + /* c >>= 52 (%%r8 only) */ + "shrdq $52,%%r9,%%r8\n" + /* c += t4 (%%r8 only) */ + "addq %%rsi,%%r8\n" + /* r[4] = c */ + "movq %%r8,32(%%rdi)\n" +: "+S"(a), "=m"(tmp1), "=m"(tmp2), "=m"(tmp3) +: "D"(r) +: "%rax", "%rbx", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15", "cc", "memory" +); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/field_5x52_impl.h b/crypto/secp256k1/libsecp256k1/src/field_5x52_impl.h new file mode 100644 index 0000000..dd88f38 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/field_5x52_impl.h @@ -0,0 +1,451 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_FIELD_REPR_IMPL_H_ +#define _SECP256K1_FIELD_REPR_IMPL_H_ + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#include "util.h" +#include "num.h" +#include "field.h" + +#if defined(USE_ASM_X86_64) +#include "field_5x52_asm_impl.h" +#else +#include "field_5x52_int128_impl.h" +#endif + +/** Implements arithmetic modulo FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F, + * represented as 5 uint64_t's in base 2^52. The values are allowed to contain >52 each. In particular, + * each FieldElem has a 'magnitude' associated with it. Internally, a magnitude M means each element + * is at most M*(2^53-1), except the most significant one, which is limited to M*(2^49-1). All operations + * accept any input with magnitude at most M, and have different rules for propagating magnitude to their + * output. + */ + +#ifdef VERIFY +static void secp256k1_fe_verify(const secp256k1_fe *a) { + const uint64_t *d = a->n; + int m = a->normalized ? 1 : 2 * a->magnitude, r = 1; + /* secp256k1 'p' value defined in "Standards for Efficient Cryptography" (SEC2) 2.7.1. */ + r &= (d[0] <= 0xFFFFFFFFFFFFFULL * m); + r &= (d[1] <= 0xFFFFFFFFFFFFFULL * m); + r &= (d[2] <= 0xFFFFFFFFFFFFFULL * m); + r &= (d[3] <= 0xFFFFFFFFFFFFFULL * m); + r &= (d[4] <= 0x0FFFFFFFFFFFFULL * m); + r &= (a->magnitude >= 0); + r &= (a->magnitude <= 2048); + if (a->normalized) { + r &= (a->magnitude <= 1); + if (r && (d[4] == 0x0FFFFFFFFFFFFULL) && ((d[3] & d[2] & d[1]) == 0xFFFFFFFFFFFFFULL)) { + r &= (d[0] < 0xFFFFEFFFFFC2FULL); + } + } + VERIFY_CHECK(r == 1); +} +#endif + +static void secp256k1_fe_normalize(secp256k1_fe *r) { + uint64_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4]; + + /* Reduce t4 at the start so there will be at most a single carry from the first pass */ + uint64_t m; + uint64_t x = t4 >> 48; t4 &= 0x0FFFFFFFFFFFFULL; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x1000003D1ULL; + t1 += (t0 >> 52); t0 &= 0xFFFFFFFFFFFFFULL; + t2 += (t1 >> 52); t1 &= 0xFFFFFFFFFFFFFULL; m = t1; + t3 += (t2 >> 52); t2 &= 0xFFFFFFFFFFFFFULL; m &= t2; + t4 += (t3 >> 52); t3 &= 0xFFFFFFFFFFFFFULL; m &= t3; + + /* ... except for a possible carry at bit 48 of t4 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t4 >> 49 == 0); + + /* At most a single final reduction is needed; check if the value is >= the field characteristic */ + x = (t4 >> 48) | ((t4 == 0x0FFFFFFFFFFFFULL) & (m == 0xFFFFFFFFFFFFFULL) + & (t0 >= 0xFFFFEFFFFFC2FULL)); + + /* Apply the final reduction (for constant-time behaviour, we do it always) */ + t0 += x * 0x1000003D1ULL; + t1 += (t0 >> 52); t0 &= 0xFFFFFFFFFFFFFULL; + t2 += (t1 >> 52); t1 &= 0xFFFFFFFFFFFFFULL; + t3 += (t2 >> 52); t2 &= 0xFFFFFFFFFFFFFULL; + t4 += (t3 >> 52); t3 &= 0xFFFFFFFFFFFFFULL; + + /* If t4 didn't carry to bit 48 already, then it should have after any final reduction */ + VERIFY_CHECK(t4 >> 48 == x); + + /* Mask off the possible multiple of 2^256 from the final reduction */ + t4 &= 0x0FFFFFFFFFFFFULL; + + r->n[0] = t0; r->n[1] = t1; r->n[2] = t2; r->n[3] = t3; r->n[4] = t4; + +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; + secp256k1_fe_verify(r); +#endif +} + +static void secp256k1_fe_normalize_weak(secp256k1_fe *r) { + uint64_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4]; + + /* Reduce t4 at the start so there will be at most a single carry from the first pass */ + uint64_t x = t4 >> 48; t4 &= 0x0FFFFFFFFFFFFULL; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x1000003D1ULL; + t1 += (t0 >> 52); t0 &= 0xFFFFFFFFFFFFFULL; + t2 += (t1 >> 52); t1 &= 0xFFFFFFFFFFFFFULL; + t3 += (t2 >> 52); t2 &= 0xFFFFFFFFFFFFFULL; + t4 += (t3 >> 52); t3 &= 0xFFFFFFFFFFFFFULL; + + /* ... except for a possible carry at bit 48 of t4 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t4 >> 49 == 0); + + r->n[0] = t0; r->n[1] = t1; r->n[2] = t2; r->n[3] = t3; r->n[4] = t4; + +#ifdef VERIFY + r->magnitude = 1; + secp256k1_fe_verify(r); +#endif +} + +static void secp256k1_fe_normalize_var(secp256k1_fe *r) { + uint64_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4]; + + /* Reduce t4 at the start so there will be at most a single carry from the first pass */ + uint64_t m; + uint64_t x = t4 >> 48; t4 &= 0x0FFFFFFFFFFFFULL; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x1000003D1ULL; + t1 += (t0 >> 52); t0 &= 0xFFFFFFFFFFFFFULL; + t2 += (t1 >> 52); t1 &= 0xFFFFFFFFFFFFFULL; m = t1; + t3 += (t2 >> 52); t2 &= 0xFFFFFFFFFFFFFULL; m &= t2; + t4 += (t3 >> 52); t3 &= 0xFFFFFFFFFFFFFULL; m &= t3; + + /* ... except for a possible carry at bit 48 of t4 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t4 >> 49 == 0); + + /* At most a single final reduction is needed; check if the value is >= the field characteristic */ + x = (t4 >> 48) | ((t4 == 0x0FFFFFFFFFFFFULL) & (m == 0xFFFFFFFFFFFFFULL) + & (t0 >= 0xFFFFEFFFFFC2FULL)); + + if (x) { + t0 += 0x1000003D1ULL; + t1 += (t0 >> 52); t0 &= 0xFFFFFFFFFFFFFULL; + t2 += (t1 >> 52); t1 &= 0xFFFFFFFFFFFFFULL; + t3 += (t2 >> 52); t2 &= 0xFFFFFFFFFFFFFULL; + t4 += (t3 >> 52); t3 &= 0xFFFFFFFFFFFFFULL; + + /* If t4 didn't carry to bit 48 already, then it should have after any final reduction */ + VERIFY_CHECK(t4 >> 48 == x); + + /* Mask off the possible multiple of 2^256 from the final reduction */ + t4 &= 0x0FFFFFFFFFFFFULL; + } + + r->n[0] = t0; r->n[1] = t1; r->n[2] = t2; r->n[3] = t3; r->n[4] = t4; + +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; + secp256k1_fe_verify(r); +#endif +} + +static int secp256k1_fe_normalizes_to_zero(secp256k1_fe *r) { + uint64_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4]; + + /* z0 tracks a possible raw value of 0, z1 tracks a possible raw value of P */ + uint64_t z0, z1; + + /* Reduce t4 at the start so there will be at most a single carry from the first pass */ + uint64_t x = t4 >> 48; t4 &= 0x0FFFFFFFFFFFFULL; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x1000003D1ULL; + t1 += (t0 >> 52); t0 &= 0xFFFFFFFFFFFFFULL; z0 = t0; z1 = t0 ^ 0x1000003D0ULL; + t2 += (t1 >> 52); t1 &= 0xFFFFFFFFFFFFFULL; z0 |= t1; z1 &= t1; + t3 += (t2 >> 52); t2 &= 0xFFFFFFFFFFFFFULL; z0 |= t2; z1 &= t2; + t4 += (t3 >> 52); t3 &= 0xFFFFFFFFFFFFFULL; z0 |= t3; z1 &= t3; + z0 |= t4; z1 &= t4 ^ 0xF000000000000ULL; + + /* ... except for a possible carry at bit 48 of t4 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t4 >> 49 == 0); + + return (z0 == 0) | (z1 == 0xFFFFFFFFFFFFFULL); +} + +static int secp256k1_fe_normalizes_to_zero_var(secp256k1_fe *r) { + uint64_t t0, t1, t2, t3, t4; + uint64_t z0, z1; + uint64_t x; + + t0 = r->n[0]; + t4 = r->n[4]; + + /* Reduce t4 at the start so there will be at most a single carry from the first pass */ + x = t4 >> 48; + + /* The first pass ensures the magnitude is 1, ... */ + t0 += x * 0x1000003D1ULL; + + /* z0 tracks a possible raw value of 0, z1 tracks a possible raw value of P */ + z0 = t0 & 0xFFFFFFFFFFFFFULL; + z1 = z0 ^ 0x1000003D0ULL; + + /* Fast return path should catch the majority of cases */ + if ((z0 != 0ULL) & (z1 != 0xFFFFFFFFFFFFFULL)) { + return 0; + } + + t1 = r->n[1]; + t2 = r->n[2]; + t3 = r->n[3]; + + t4 &= 0x0FFFFFFFFFFFFULL; + + t1 += (t0 >> 52); + t2 += (t1 >> 52); t1 &= 0xFFFFFFFFFFFFFULL; z0 |= t1; z1 &= t1; + t3 += (t2 >> 52); t2 &= 0xFFFFFFFFFFFFFULL; z0 |= t2; z1 &= t2; + t4 += (t3 >> 52); t3 &= 0xFFFFFFFFFFFFFULL; z0 |= t3; z1 &= t3; + z0 |= t4; z1 &= t4 ^ 0xF000000000000ULL; + + /* ... except for a possible carry at bit 48 of t4 (i.e. bit 256 of the field element) */ + VERIFY_CHECK(t4 >> 49 == 0); + + return (z0 == 0) | (z1 == 0xFFFFFFFFFFFFFULL); +} + +SECP256K1_INLINE static void secp256k1_fe_set_int(secp256k1_fe *r, int a) { + r->n[0] = a; + r->n[1] = r->n[2] = r->n[3] = r->n[4] = 0; +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; + secp256k1_fe_verify(r); +#endif +} + +SECP256K1_INLINE static int secp256k1_fe_is_zero(const secp256k1_fe *a) { + const uint64_t *t = a->n; +#ifdef VERIFY + VERIFY_CHECK(a->normalized); + secp256k1_fe_verify(a); +#endif + return (t[0] | t[1] | t[2] | t[3] | t[4]) == 0; +} + +SECP256K1_INLINE static int secp256k1_fe_is_odd(const secp256k1_fe *a) { +#ifdef VERIFY + VERIFY_CHECK(a->normalized); + secp256k1_fe_verify(a); +#endif + return a->n[0] & 1; +} + +SECP256K1_INLINE static void secp256k1_fe_clear(secp256k1_fe *a) { + int i; +#ifdef VERIFY + a->magnitude = 0; + a->normalized = 1; +#endif + for (i=0; i<5; i++) { + a->n[i] = 0; + } +} + +static int secp256k1_fe_cmp_var(const secp256k1_fe *a, const secp256k1_fe *b) { + int i; +#ifdef VERIFY + VERIFY_CHECK(a->normalized); + VERIFY_CHECK(b->normalized); + secp256k1_fe_verify(a); + secp256k1_fe_verify(b); +#endif + for (i = 4; i >= 0; i--) { + if (a->n[i] > b->n[i]) { + return 1; + } + if (a->n[i] < b->n[i]) { + return -1; + } + } + return 0; +} + +static int secp256k1_fe_set_b32(secp256k1_fe *r, const unsigned char *a) { + int i; + r->n[0] = r->n[1] = r->n[2] = r->n[3] = r->n[4] = 0; + for (i=0; i<32; i++) { + int j; + for (j=0; j<2; j++) { + int limb = (8*i+4*j)/52; + int shift = (8*i+4*j)%52; + r->n[limb] |= (uint64_t)((a[31-i] >> (4*j)) & 0xF) << shift; + } + } + if (r->n[4] == 0x0FFFFFFFFFFFFULL && (r->n[3] & r->n[2] & r->n[1]) == 0xFFFFFFFFFFFFFULL && r->n[0] >= 0xFFFFEFFFFFC2FULL) { + return 0; + } +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; + secp256k1_fe_verify(r); +#endif + return 1; +} + +/** Convert a field element to a 32-byte big endian value. Requires the input to be normalized */ +static void secp256k1_fe_get_b32(unsigned char *r, const secp256k1_fe *a) { + int i; +#ifdef VERIFY + VERIFY_CHECK(a->normalized); + secp256k1_fe_verify(a); +#endif + for (i=0; i<32; i++) { + int j; + int c = 0; + for (j=0; j<2; j++) { + int limb = (8*i+4*j)/52; + int shift = (8*i+4*j)%52; + c |= ((a->n[limb] >> shift) & 0xF) << (4 * j); + } + r[31-i] = c; + } +} + +SECP256K1_INLINE static void secp256k1_fe_negate(secp256k1_fe *r, const secp256k1_fe *a, int m) { +#ifdef VERIFY + VERIFY_CHECK(a->magnitude <= m); + secp256k1_fe_verify(a); +#endif + r->n[0] = 0xFFFFEFFFFFC2FULL * 2 * (m + 1) - a->n[0]; + r->n[1] = 0xFFFFFFFFFFFFFULL * 2 * (m + 1) - a->n[1]; + r->n[2] = 0xFFFFFFFFFFFFFULL * 2 * (m + 1) - a->n[2]; + r->n[3] = 0xFFFFFFFFFFFFFULL * 2 * (m + 1) - a->n[3]; + r->n[4] = 0x0FFFFFFFFFFFFULL * 2 * (m + 1) - a->n[4]; +#ifdef VERIFY + r->magnitude = m + 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +SECP256K1_INLINE static void secp256k1_fe_mul_int(secp256k1_fe *r, int a) { + r->n[0] *= a; + r->n[1] *= a; + r->n[2] *= a; + r->n[3] *= a; + r->n[4] *= a; +#ifdef VERIFY + r->magnitude *= a; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +SECP256K1_INLINE static void secp256k1_fe_add(secp256k1_fe *r, const secp256k1_fe *a) { +#ifdef VERIFY + secp256k1_fe_verify(a); +#endif + r->n[0] += a->n[0]; + r->n[1] += a->n[1]; + r->n[2] += a->n[2]; + r->n[3] += a->n[3]; + r->n[4] += a->n[4]; +#ifdef VERIFY + r->magnitude += a->magnitude; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +static void secp256k1_fe_mul(secp256k1_fe *r, const secp256k1_fe *a, const secp256k1_fe * SECP256K1_RESTRICT b) { +#ifdef VERIFY + VERIFY_CHECK(a->magnitude <= 8); + VERIFY_CHECK(b->magnitude <= 8); + secp256k1_fe_verify(a); + secp256k1_fe_verify(b); + VERIFY_CHECK(r != b); +#endif + secp256k1_fe_mul_inner(r->n, a->n, b->n); +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +static void secp256k1_fe_sqr(secp256k1_fe *r, const secp256k1_fe *a) { +#ifdef VERIFY + VERIFY_CHECK(a->magnitude <= 8); + secp256k1_fe_verify(a); +#endif + secp256k1_fe_sqr_inner(r->n, a->n); +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + +static SECP256K1_INLINE void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag) { + uint64_t mask0, mask1; + mask0 = flag + ~((uint64_t)0); + mask1 = ~mask0; + r->n[0] = (r->n[0] & mask0) | (a->n[0] & mask1); + r->n[1] = (r->n[1] & mask0) | (a->n[1] & mask1); + r->n[2] = (r->n[2] & mask0) | (a->n[2] & mask1); + r->n[3] = (r->n[3] & mask0) | (a->n[3] & mask1); + r->n[4] = (r->n[4] & mask0) | (a->n[4] & mask1); +#ifdef VERIFY + if (a->magnitude > r->magnitude) { + r->magnitude = a->magnitude; + } + r->normalized &= a->normalized; +#endif +} + +static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) { + uint64_t mask0, mask1; + mask0 = flag + ~((uint64_t)0); + mask1 = ~mask0; + r->n[0] = (r->n[0] & mask0) | (a->n[0] & mask1); + r->n[1] = (r->n[1] & mask0) | (a->n[1] & mask1); + r->n[2] = (r->n[2] & mask0) | (a->n[2] & mask1); + r->n[3] = (r->n[3] & mask0) | (a->n[3] & mask1); +} + +static void secp256k1_fe_to_storage(secp256k1_fe_storage *r, const secp256k1_fe *a) { +#ifdef VERIFY + VERIFY_CHECK(a->normalized); +#endif + r->n[0] = a->n[0] | a->n[1] << 52; + r->n[1] = a->n[1] >> 12 | a->n[2] << 40; + r->n[2] = a->n[2] >> 24 | a->n[3] << 28; + r->n[3] = a->n[3] >> 36 | a->n[4] << 16; +} + +static SECP256K1_INLINE void secp256k1_fe_from_storage(secp256k1_fe *r, const secp256k1_fe_storage *a) { + r->n[0] = a->n[0] & 0xFFFFFFFFFFFFFULL; + r->n[1] = a->n[0] >> 52 | ((a->n[1] << 12) & 0xFFFFFFFFFFFFFULL); + r->n[2] = a->n[1] >> 40 | ((a->n[2] << 24) & 0xFFFFFFFFFFFFFULL); + r->n[3] = a->n[2] >> 28 | ((a->n[3] << 36) & 0xFFFFFFFFFFFFFULL); + r->n[4] = a->n[3] >> 16; +#ifdef VERIFY + r->magnitude = 1; + r->normalized = 1; +#endif +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/field_5x52_int128_impl.h b/crypto/secp256k1/libsecp256k1/src/field_5x52_int128_impl.h new file mode 100644 index 0000000..0bf22bd --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/field_5x52_int128_impl.h @@ -0,0 +1,277 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_FIELD_INNER5X52_IMPL_H_ +#define _SECP256K1_FIELD_INNER5X52_IMPL_H_ + +#include + +#ifdef VERIFY +#define VERIFY_BITS(x, n) VERIFY_CHECK(((x) >> (n)) == 0) +#else +#define VERIFY_BITS(x, n) do { } while(0) +#endif + +SECP256K1_INLINE static void secp256k1_fe_mul_inner(uint64_t *r, const uint64_t *a, const uint64_t * SECP256K1_RESTRICT b) { + uint128_t c, d; + uint64_t t3, t4, tx, u0; + uint64_t a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4]; + const uint64_t M = 0xFFFFFFFFFFFFFULL, R = 0x1000003D10ULL; + + VERIFY_BITS(a[0], 56); + VERIFY_BITS(a[1], 56); + VERIFY_BITS(a[2], 56); + VERIFY_BITS(a[3], 56); + VERIFY_BITS(a[4], 52); + VERIFY_BITS(b[0], 56); + VERIFY_BITS(b[1], 56); + VERIFY_BITS(b[2], 56); + VERIFY_BITS(b[3], 56); + VERIFY_BITS(b[4], 52); + VERIFY_CHECK(r != b); + + /* [... a b c] is a shorthand for ... + a<<104 + b<<52 + c<<0 mod n. + * px is a shorthand for sum(a[i]*b[x-i], i=0..x). + * Note that [x 0 0 0 0 0] = [x*R]. + */ + + d = (uint128_t)a0 * b[3] + + (uint128_t)a1 * b[2] + + (uint128_t)a2 * b[1] + + (uint128_t)a3 * b[0]; + VERIFY_BITS(d, 114); + /* [d 0 0 0] = [p3 0 0 0] */ + c = (uint128_t)a4 * b[4]; + VERIFY_BITS(c, 112); + /* [c 0 0 0 0 d 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ + d += (c & M) * R; c >>= 52; + VERIFY_BITS(d, 115); + VERIFY_BITS(c, 60); + /* [c 0 0 0 0 0 d 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ + t3 = d & M; d >>= 52; + VERIFY_BITS(t3, 52); + VERIFY_BITS(d, 63); + /* [c 0 0 0 0 d t3 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ + + d += (uint128_t)a0 * b[4] + + (uint128_t)a1 * b[3] + + (uint128_t)a2 * b[2] + + (uint128_t)a3 * b[1] + + (uint128_t)a4 * b[0]; + VERIFY_BITS(d, 115); + /* [c 0 0 0 0 d t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ + d += c * R; + VERIFY_BITS(d, 116); + /* [d t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ + t4 = d & M; d >>= 52; + VERIFY_BITS(t4, 52); + VERIFY_BITS(d, 64); + /* [d t4 t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ + tx = (t4 >> 48); t4 &= (M >> 4); + VERIFY_BITS(tx, 4); + VERIFY_BITS(t4, 48); + /* [d t4+(tx<<48) t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ + + c = (uint128_t)a0 * b[0]; + VERIFY_BITS(c, 112); + /* [d t4+(tx<<48) t3 0 0 c] = [p8 0 0 0 p4 p3 0 0 p0] */ + d += (uint128_t)a1 * b[4] + + (uint128_t)a2 * b[3] + + (uint128_t)a3 * b[2] + + (uint128_t)a4 * b[1]; + VERIFY_BITS(d, 115); + /* [d t4+(tx<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + u0 = d & M; d >>= 52; + VERIFY_BITS(u0, 52); + VERIFY_BITS(d, 63); + /* [d u0 t4+(tx<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + /* [d 0 t4+(tx<<48)+(u0<<52) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + u0 = (u0 << 4) | tx; + VERIFY_BITS(u0, 56); + /* [d 0 t4+(u0<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + c += (uint128_t)u0 * (R >> 4); + VERIFY_BITS(c, 115); + /* [d 0 t4 t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + r[0] = c & M; c >>= 52; + VERIFY_BITS(r[0], 52); + VERIFY_BITS(c, 61); + /* [d 0 t4 t3 0 c r0] = [p8 0 0 p5 p4 p3 0 0 p0] */ + + c += (uint128_t)a0 * b[1] + + (uint128_t)a1 * b[0]; + VERIFY_BITS(c, 114); + /* [d 0 t4 t3 0 c r0] = [p8 0 0 p5 p4 p3 0 p1 p0] */ + d += (uint128_t)a2 * b[4] + + (uint128_t)a3 * b[3] + + (uint128_t)a4 * b[2]; + VERIFY_BITS(d, 114); + /* [d 0 t4 t3 0 c r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ + c += (d & M) * R; d >>= 52; + VERIFY_BITS(c, 115); + VERIFY_BITS(d, 62); + /* [d 0 0 t4 t3 0 c r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ + r[1] = c & M; c >>= 52; + VERIFY_BITS(r[1], 52); + VERIFY_BITS(c, 63); + /* [d 0 0 t4 t3 c r1 r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ + + c += (uint128_t)a0 * b[2] + + (uint128_t)a1 * b[1] + + (uint128_t)a2 * b[0]; + VERIFY_BITS(c, 114); + /* [d 0 0 t4 t3 c r1 r0] = [p8 0 p6 p5 p4 p3 p2 p1 p0] */ + d += (uint128_t)a3 * b[4] + + (uint128_t)a4 * b[3]; + VERIFY_BITS(d, 114); + /* [d 0 0 t4 t3 c t1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + c += (d & M) * R; d >>= 52; + VERIFY_BITS(c, 115); + VERIFY_BITS(d, 62); + /* [d 0 0 0 t4 t3 c r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + + /* [d 0 0 0 t4 t3 c r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[2] = c & M; c >>= 52; + VERIFY_BITS(r[2], 52); + VERIFY_BITS(c, 63); + /* [d 0 0 0 t4 t3+c r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + c += d * R + t3; + VERIFY_BITS(c, 100); + /* [t4 c r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[3] = c & M; c >>= 52; + VERIFY_BITS(r[3], 52); + VERIFY_BITS(c, 48); + /* [t4+c r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + c += t4; + VERIFY_BITS(c, 49); + /* [c r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[4] = c; + VERIFY_BITS(r[4], 49); + /* [r4 r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ +} + +SECP256K1_INLINE static void secp256k1_fe_sqr_inner(uint64_t *r, const uint64_t *a) { + uint128_t c, d; + uint64_t a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4]; + int64_t t3, t4, tx, u0; + const uint64_t M = 0xFFFFFFFFFFFFFULL, R = 0x1000003D10ULL; + + VERIFY_BITS(a[0], 56); + VERIFY_BITS(a[1], 56); + VERIFY_BITS(a[2], 56); + VERIFY_BITS(a[3], 56); + VERIFY_BITS(a[4], 52); + + /** [... a b c] is a shorthand for ... + a<<104 + b<<52 + c<<0 mod n. + * px is a shorthand for sum(a[i]*a[x-i], i=0..x). + * Note that [x 0 0 0 0 0] = [x*R]. + */ + + d = (uint128_t)(a0*2) * a3 + + (uint128_t)(a1*2) * a2; + VERIFY_BITS(d, 114); + /* [d 0 0 0] = [p3 0 0 0] */ + c = (uint128_t)a4 * a4; + VERIFY_BITS(c, 112); + /* [c 0 0 0 0 d 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ + d += (c & M) * R; c >>= 52; + VERIFY_BITS(d, 115); + VERIFY_BITS(c, 60); + /* [c 0 0 0 0 0 d 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ + t3 = d & M; d >>= 52; + VERIFY_BITS(t3, 52); + VERIFY_BITS(d, 63); + /* [c 0 0 0 0 d t3 0 0 0] = [p8 0 0 0 0 p3 0 0 0] */ + + a4 *= 2; + d += (uint128_t)a0 * a4 + + (uint128_t)(a1*2) * a3 + + (uint128_t)a2 * a2; + VERIFY_BITS(d, 115); + /* [c 0 0 0 0 d t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ + d += c * R; + VERIFY_BITS(d, 116); + /* [d t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ + t4 = d & M; d >>= 52; + VERIFY_BITS(t4, 52); + VERIFY_BITS(d, 64); + /* [d t4 t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ + tx = (t4 >> 48); t4 &= (M >> 4); + VERIFY_BITS(tx, 4); + VERIFY_BITS(t4, 48); + /* [d t4+(tx<<48) t3 0 0 0] = [p8 0 0 0 p4 p3 0 0 0] */ + + c = (uint128_t)a0 * a0; + VERIFY_BITS(c, 112); + /* [d t4+(tx<<48) t3 0 0 c] = [p8 0 0 0 p4 p3 0 0 p0] */ + d += (uint128_t)a1 * a4 + + (uint128_t)(a2*2) * a3; + VERIFY_BITS(d, 114); + /* [d t4+(tx<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + u0 = d & M; d >>= 52; + VERIFY_BITS(u0, 52); + VERIFY_BITS(d, 62); + /* [d u0 t4+(tx<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + /* [d 0 t4+(tx<<48)+(u0<<52) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + u0 = (u0 << 4) | tx; + VERIFY_BITS(u0, 56); + /* [d 0 t4+(u0<<48) t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + c += (uint128_t)u0 * (R >> 4); + VERIFY_BITS(c, 113); + /* [d 0 t4 t3 0 0 c] = [p8 0 0 p5 p4 p3 0 0 p0] */ + r[0] = c & M; c >>= 52; + VERIFY_BITS(r[0], 52); + VERIFY_BITS(c, 61); + /* [d 0 t4 t3 0 c r0] = [p8 0 0 p5 p4 p3 0 0 p0] */ + + a0 *= 2; + c += (uint128_t)a0 * a1; + VERIFY_BITS(c, 114); + /* [d 0 t4 t3 0 c r0] = [p8 0 0 p5 p4 p3 0 p1 p0] */ + d += (uint128_t)a2 * a4 + + (uint128_t)a3 * a3; + VERIFY_BITS(d, 114); + /* [d 0 t4 t3 0 c r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ + c += (d & M) * R; d >>= 52; + VERIFY_BITS(c, 115); + VERIFY_BITS(d, 62); + /* [d 0 0 t4 t3 0 c r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ + r[1] = c & M; c >>= 52; + VERIFY_BITS(r[1], 52); + VERIFY_BITS(c, 63); + /* [d 0 0 t4 t3 c r1 r0] = [p8 0 p6 p5 p4 p3 0 p1 p0] */ + + c += (uint128_t)a0 * a2 + + (uint128_t)a1 * a1; + VERIFY_BITS(c, 114); + /* [d 0 0 t4 t3 c r1 r0] = [p8 0 p6 p5 p4 p3 p2 p1 p0] */ + d += (uint128_t)a3 * a4; + VERIFY_BITS(d, 114); + /* [d 0 0 t4 t3 c r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + c += (d & M) * R; d >>= 52; + VERIFY_BITS(c, 115); + VERIFY_BITS(d, 62); + /* [d 0 0 0 t4 t3 c r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[2] = c & M; c >>= 52; + VERIFY_BITS(r[2], 52); + VERIFY_BITS(c, 63); + /* [d 0 0 0 t4 t3+c r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + + c += d * R + t3; + VERIFY_BITS(c, 100); + /* [t4 c r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[3] = c & M; c >>= 52; + VERIFY_BITS(r[3], 52); + VERIFY_BITS(c, 48); + /* [t4+c r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + c += t4; + VERIFY_BITS(c, 49); + /* [c r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ + r[4] = c; + VERIFY_BITS(r[4], 49); + /* [r4 r3 r2 r1 r0] = [p8 p7 p6 p5 p4 p3 p2 p1 p0] */ +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/field_impl.h b/crypto/secp256k1/libsecp256k1/src/field_impl.h new file mode 100644 index 0000000..5127b27 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/field_impl.h @@ -0,0 +1,315 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_FIELD_IMPL_H_ +#define _SECP256K1_FIELD_IMPL_H_ + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#include "util.h" + +#if defined(USE_FIELD_10X26) +#include "field_10x26_impl.h" +#elif defined(USE_FIELD_5X52) +#include "field_5x52_impl.h" +#else +#error "Please select field implementation" +#endif + +SECP256K1_INLINE static int secp256k1_fe_equal(const secp256k1_fe *a, const secp256k1_fe *b) { + secp256k1_fe na; + secp256k1_fe_negate(&na, a, 1); + secp256k1_fe_add(&na, b); + return secp256k1_fe_normalizes_to_zero(&na); +} + +SECP256K1_INLINE static int secp256k1_fe_equal_var(const secp256k1_fe *a, const secp256k1_fe *b) { + secp256k1_fe na; + secp256k1_fe_negate(&na, a, 1); + secp256k1_fe_add(&na, b); + return secp256k1_fe_normalizes_to_zero_var(&na); +} + +static int secp256k1_fe_sqrt(secp256k1_fe *r, const secp256k1_fe *a) { + /** Given that p is congruent to 3 mod 4, we can compute the square root of + * a mod p as the (p+1)/4'th power of a. + * + * As (p+1)/4 is an even number, it will have the same result for a and for + * (-a). Only one of these two numbers actually has a square root however, + * so we test at the end by squaring and comparing to the input. + * Also because (p+1)/4 is an even number, the computed square root is + * itself always a square (a ** ((p+1)/4) is the square of a ** ((p+1)/8)). + */ + secp256k1_fe x2, x3, x6, x9, x11, x22, x44, x88, x176, x220, x223, t1; + int j; + + /** The binary representation of (p + 1)/4 has 3 blocks of 1s, with lengths in + * { 2, 22, 223 }. Use an addition chain to calculate 2^n - 1 for each block: + * 1, [2], 3, 6, 9, 11, [22], 44, 88, 176, 220, [223] + */ + + secp256k1_fe_sqr(&x2, a); + secp256k1_fe_mul(&x2, &x2, a); + + secp256k1_fe_sqr(&x3, &x2); + secp256k1_fe_mul(&x3, &x3, a); + + x6 = x3; + for (j=0; j<3; j++) { + secp256k1_fe_sqr(&x6, &x6); + } + secp256k1_fe_mul(&x6, &x6, &x3); + + x9 = x6; + for (j=0; j<3; j++) { + secp256k1_fe_sqr(&x9, &x9); + } + secp256k1_fe_mul(&x9, &x9, &x3); + + x11 = x9; + for (j=0; j<2; j++) { + secp256k1_fe_sqr(&x11, &x11); + } + secp256k1_fe_mul(&x11, &x11, &x2); + + x22 = x11; + for (j=0; j<11; j++) { + secp256k1_fe_sqr(&x22, &x22); + } + secp256k1_fe_mul(&x22, &x22, &x11); + + x44 = x22; + for (j=0; j<22; j++) { + secp256k1_fe_sqr(&x44, &x44); + } + secp256k1_fe_mul(&x44, &x44, &x22); + + x88 = x44; + for (j=0; j<44; j++) { + secp256k1_fe_sqr(&x88, &x88); + } + secp256k1_fe_mul(&x88, &x88, &x44); + + x176 = x88; + for (j=0; j<88; j++) { + secp256k1_fe_sqr(&x176, &x176); + } + secp256k1_fe_mul(&x176, &x176, &x88); + + x220 = x176; + for (j=0; j<44; j++) { + secp256k1_fe_sqr(&x220, &x220); + } + secp256k1_fe_mul(&x220, &x220, &x44); + + x223 = x220; + for (j=0; j<3; j++) { + secp256k1_fe_sqr(&x223, &x223); + } + secp256k1_fe_mul(&x223, &x223, &x3); + + /* The final result is then assembled using a sliding window over the blocks. */ + + t1 = x223; + for (j=0; j<23; j++) { + secp256k1_fe_sqr(&t1, &t1); + } + secp256k1_fe_mul(&t1, &t1, &x22); + for (j=0; j<6; j++) { + secp256k1_fe_sqr(&t1, &t1); + } + secp256k1_fe_mul(&t1, &t1, &x2); + secp256k1_fe_sqr(&t1, &t1); + secp256k1_fe_sqr(r, &t1); + + /* Check that a square root was actually calculated */ + + secp256k1_fe_sqr(&t1, r); + return secp256k1_fe_equal(&t1, a); +} + +static void secp256k1_fe_inv(secp256k1_fe *r, const secp256k1_fe *a) { + secp256k1_fe x2, x3, x6, x9, x11, x22, x44, x88, x176, x220, x223, t1; + int j; + + /** The binary representation of (p - 2) has 5 blocks of 1s, with lengths in + * { 1, 2, 22, 223 }. Use an addition chain to calculate 2^n - 1 for each block: + * [1], [2], 3, 6, 9, 11, [22], 44, 88, 176, 220, [223] + */ + + secp256k1_fe_sqr(&x2, a); + secp256k1_fe_mul(&x2, &x2, a); + + secp256k1_fe_sqr(&x3, &x2); + secp256k1_fe_mul(&x3, &x3, a); + + x6 = x3; + for (j=0; j<3; j++) { + secp256k1_fe_sqr(&x6, &x6); + } + secp256k1_fe_mul(&x6, &x6, &x3); + + x9 = x6; + for (j=0; j<3; j++) { + secp256k1_fe_sqr(&x9, &x9); + } + secp256k1_fe_mul(&x9, &x9, &x3); + + x11 = x9; + for (j=0; j<2; j++) { + secp256k1_fe_sqr(&x11, &x11); + } + secp256k1_fe_mul(&x11, &x11, &x2); + + x22 = x11; + for (j=0; j<11; j++) { + secp256k1_fe_sqr(&x22, &x22); + } + secp256k1_fe_mul(&x22, &x22, &x11); + + x44 = x22; + for (j=0; j<22; j++) { + secp256k1_fe_sqr(&x44, &x44); + } + secp256k1_fe_mul(&x44, &x44, &x22); + + x88 = x44; + for (j=0; j<44; j++) { + secp256k1_fe_sqr(&x88, &x88); + } + secp256k1_fe_mul(&x88, &x88, &x44); + + x176 = x88; + for (j=0; j<88; j++) { + secp256k1_fe_sqr(&x176, &x176); + } + secp256k1_fe_mul(&x176, &x176, &x88); + + x220 = x176; + for (j=0; j<44; j++) { + secp256k1_fe_sqr(&x220, &x220); + } + secp256k1_fe_mul(&x220, &x220, &x44); + + x223 = x220; + for (j=0; j<3; j++) { + secp256k1_fe_sqr(&x223, &x223); + } + secp256k1_fe_mul(&x223, &x223, &x3); + + /* The final result is then assembled using a sliding window over the blocks. */ + + t1 = x223; + for (j=0; j<23; j++) { + secp256k1_fe_sqr(&t1, &t1); + } + secp256k1_fe_mul(&t1, &t1, &x22); + for (j=0; j<5; j++) { + secp256k1_fe_sqr(&t1, &t1); + } + secp256k1_fe_mul(&t1, &t1, a); + for (j=0; j<3; j++) { + secp256k1_fe_sqr(&t1, &t1); + } + secp256k1_fe_mul(&t1, &t1, &x2); + for (j=0; j<2; j++) { + secp256k1_fe_sqr(&t1, &t1); + } + secp256k1_fe_mul(r, a, &t1); +} + +static void secp256k1_fe_inv_var(secp256k1_fe *r, const secp256k1_fe *a) { +#if defined(USE_FIELD_INV_BUILTIN) + secp256k1_fe_inv(r, a); +#elif defined(USE_FIELD_INV_NUM) + secp256k1_num n, m; + static const secp256k1_fe negone = SECP256K1_FE_CONST( + 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, + 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFEUL, 0xFFFFFC2EUL + ); + /* secp256k1 field prime, value p defined in "Standards for Efficient Cryptography" (SEC2) 2.7.1. */ + static const unsigned char prime[32] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFC,0x2F + }; + unsigned char b[32]; + int res; + secp256k1_fe c = *a; + secp256k1_fe_normalize_var(&c); + secp256k1_fe_get_b32(b, &c); + secp256k1_num_set_bin(&n, b, 32); + secp256k1_num_set_bin(&m, prime, 32); + secp256k1_num_mod_inverse(&n, &n, &m); + secp256k1_num_get_bin(b, 32, &n); + res = secp256k1_fe_set_b32(r, b); + (void)res; + VERIFY_CHECK(res); + /* Verify the result is the (unique) valid inverse using non-GMP code. */ + secp256k1_fe_mul(&c, &c, r); + secp256k1_fe_add(&c, &negone); + CHECK(secp256k1_fe_normalizes_to_zero_var(&c)); +#else +#error "Please select field inverse implementation" +#endif +} + +static void secp256k1_fe_inv_all_var(secp256k1_fe *r, const secp256k1_fe *a, size_t len) { + secp256k1_fe u; + size_t i; + if (len < 1) { + return; + } + + VERIFY_CHECK((r + len <= a) || (a + len <= r)); + + r[0] = a[0]; + + i = 0; + while (++i < len) { + secp256k1_fe_mul(&r[i], &r[i - 1], &a[i]); + } + + secp256k1_fe_inv_var(&u, &r[--i]); + + while (i > 0) { + size_t j = i--; + secp256k1_fe_mul(&r[j], &r[i], &u); + secp256k1_fe_mul(&u, &u, &a[j]); + } + + r[0] = u; +} + +static int secp256k1_fe_is_quad_var(const secp256k1_fe *a) { +#ifndef USE_NUM_NONE + unsigned char b[32]; + secp256k1_num n; + secp256k1_num m; + /* secp256k1 field prime, value p defined in "Standards for Efficient Cryptography" (SEC2) 2.7.1. */ + static const unsigned char prime[32] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFE,0xFF,0xFF,0xFC,0x2F + }; + + secp256k1_fe c = *a; + secp256k1_fe_normalize_var(&c); + secp256k1_fe_get_b32(b, &c); + secp256k1_num_set_bin(&n, b, 32); + secp256k1_num_set_bin(&m, prime, 32); + return secp256k1_num_jacobi(&n, &m) >= 0; +#else + secp256k1_fe r; + return secp256k1_fe_sqrt(&r, a); +#endif +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/gen_context.c b/crypto/secp256k1/libsecp256k1/src/gen_context.c new file mode 100644 index 0000000..1835fd4 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/gen_context.c @@ -0,0 +1,74 @@ +/********************************************************************** + * Copyright (c) 2013, 2014, 2015 Thomas Daede, Cory Fields * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#define USE_BASIC_CONFIG 1 + +#include "basic-config.h" +#include "include/secp256k1.h" +#include "field_impl.h" +#include "scalar_impl.h" +#include "group_impl.h" +#include "ecmult_gen_impl.h" + +static void default_error_callback_fn(const char* str, void* data) { + (void)data; + fprintf(stderr, "[libsecp256k1] internal consistency check failed: %s\n", str); + abort(); +} + +static const secp256k1_callback default_error_callback = { + default_error_callback_fn, + NULL +}; + +int main(int argc, char **argv) { + secp256k1_ecmult_gen_context ctx; + int inner; + int outer; + FILE* fp; + + (void)argc; + (void)argv; + + fp = fopen("src/ecmult_static_context.h","w"); + if (fp == NULL) { + fprintf(stderr, "Could not open src/ecmult_static_context.h for writing!\n"); + return -1; + } + + fprintf(fp, "#ifndef _SECP256K1_ECMULT_STATIC_CONTEXT_\n"); + fprintf(fp, "#define _SECP256K1_ECMULT_STATIC_CONTEXT_\n"); + fprintf(fp, "#include \"group.h\"\n"); + fprintf(fp, "#define SC SECP256K1_GE_STORAGE_CONST\n"); + fprintf(fp, "static const secp256k1_ge_storage secp256k1_ecmult_static_context[64][16] = {\n"); + + secp256k1_ecmult_gen_context_init(&ctx); + secp256k1_ecmult_gen_context_build(&ctx, &default_error_callback); + for(outer = 0; outer != 64; outer++) { + fprintf(fp,"{\n"); + for(inner = 0; inner != 16; inner++) { + fprintf(fp," SC(%uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu, %uu)", SECP256K1_GE_STORAGE_CONST_GET((*ctx.prec)[outer][inner])); + if (inner != 15) { + fprintf(fp,",\n"); + } else { + fprintf(fp,"\n"); + } + } + if (outer != 63) { + fprintf(fp,"},\n"); + } else { + fprintf(fp,"}\n"); + } + } + fprintf(fp,"};\n"); + secp256k1_ecmult_gen_context_clear(&ctx); + + fprintf(fp, "#undef SC\n"); + fprintf(fp, "#endif\n"); + fclose(fp); + + return 0; +} diff --git a/crypto/secp256k1/libsecp256k1/src/group.h b/crypto/secp256k1/libsecp256k1/src/group.h new file mode 100644 index 0000000..4957b24 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/group.h @@ -0,0 +1,144 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_GROUP_ +#define _SECP256K1_GROUP_ + +#include "num.h" +#include "field.h" + +/** A group element of the secp256k1 curve, in affine coordinates. */ +typedef struct { + secp256k1_fe x; + secp256k1_fe y; + int infinity; /* whether this represents the point at infinity */ +} secp256k1_ge; + +#define SECP256K1_GE_CONST(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {SECP256K1_FE_CONST((a),(b),(c),(d),(e),(f),(g),(h)), SECP256K1_FE_CONST((i),(j),(k),(l),(m),(n),(o),(p)), 0} +#define SECP256K1_GE_CONST_INFINITY {SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), 1} + +/** A group element of the secp256k1 curve, in jacobian coordinates. */ +typedef struct { + secp256k1_fe x; /* actual X: x/z^2 */ + secp256k1_fe y; /* actual Y: y/z^3 */ + secp256k1_fe z; + int infinity; /* whether this represents the point at infinity */ +} secp256k1_gej; + +#define SECP256K1_GEJ_CONST(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {SECP256K1_FE_CONST((a),(b),(c),(d),(e),(f),(g),(h)), SECP256K1_FE_CONST((i),(j),(k),(l),(m),(n),(o),(p)), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1), 0} +#define SECP256K1_GEJ_CONST_INFINITY {SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), 1} + +typedef struct { + secp256k1_fe_storage x; + secp256k1_fe_storage y; +} secp256k1_ge_storage; + +#define SECP256K1_GE_STORAGE_CONST(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {SECP256K1_FE_STORAGE_CONST((a),(b),(c),(d),(e),(f),(g),(h)), SECP256K1_FE_STORAGE_CONST((i),(j),(k),(l),(m),(n),(o),(p))} + +#define SECP256K1_GE_STORAGE_CONST_GET(t) SECP256K1_FE_STORAGE_CONST_GET(t.x), SECP256K1_FE_STORAGE_CONST_GET(t.y) + +/** Set a group element equal to the point with given X and Y coordinates */ +static void secp256k1_ge_set_xy(secp256k1_ge *r, const secp256k1_fe *x, const secp256k1_fe *y); + +/** Set a group element (affine) equal to the point with the given X coordinate + * and a Y coordinate that is a quadratic residue modulo p. The return value + * is true iff a coordinate with the given X coordinate exists. + */ +static int secp256k1_ge_set_xquad(secp256k1_ge *r, const secp256k1_fe *x); + +/** Set a group element (affine) equal to the point with the given X coordinate, and given oddness + * for Y. Return value indicates whether the result is valid. */ +static int secp256k1_ge_set_xo_var(secp256k1_ge *r, const secp256k1_fe *x, int odd); + +/** Check whether a group element is the point at infinity. */ +static int secp256k1_ge_is_infinity(const secp256k1_ge *a); + +/** Check whether a group element is valid (i.e., on the curve). */ +static int secp256k1_ge_is_valid_var(const secp256k1_ge *a); + +static void secp256k1_ge_neg(secp256k1_ge *r, const secp256k1_ge *a); + +/** Set a group element equal to another which is given in jacobian coordinates */ +static void secp256k1_ge_set_gej(secp256k1_ge *r, secp256k1_gej *a); + +/** Set a batch of group elements equal to the inputs given in jacobian coordinates */ +static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len, const secp256k1_callback *cb); + +/** Set a batch of group elements equal to the inputs given in jacobian + * coordinates (with known z-ratios). zr must contain the known z-ratios such + * that mul(a[i].z, zr[i+1]) == a[i+1].z. zr[0] is ignored. */ +static void secp256k1_ge_set_table_gej_var(secp256k1_ge *r, const secp256k1_gej *a, const secp256k1_fe *zr, size_t len); + +/** Bring a batch inputs given in jacobian coordinates (with known z-ratios) to + * the same global z "denominator". zr must contain the known z-ratios such + * that mul(a[i].z, zr[i+1]) == a[i+1].z. zr[0] is ignored. The x and y + * coordinates of the result are stored in r, the common z coordinate is + * stored in globalz. */ +static void secp256k1_ge_globalz_set_table_gej(size_t len, secp256k1_ge *r, secp256k1_fe *globalz, const secp256k1_gej *a, const secp256k1_fe *zr); + +/** Set a group element (jacobian) equal to the point at infinity. */ +static void secp256k1_gej_set_infinity(secp256k1_gej *r); + +/** Set a group element (jacobian) equal to another which is given in affine coordinates. */ +static void secp256k1_gej_set_ge(secp256k1_gej *r, const secp256k1_ge *a); + +/** Compare the X coordinate of a group element (jacobian). */ +static int secp256k1_gej_eq_x_var(const secp256k1_fe *x, const secp256k1_gej *a); + +/** Set r equal to the inverse of a (i.e., mirrored around the X axis) */ +static void secp256k1_gej_neg(secp256k1_gej *r, const secp256k1_gej *a); + +/** Check whether a group element is the point at infinity. */ +static int secp256k1_gej_is_infinity(const secp256k1_gej *a); + +/** Check whether a group element's y coordinate is a quadratic residue. */ +static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a); + +/** Set r equal to the double of a. If rzr is not-NULL, r->z = a->z * *rzr (where infinity means an implicit z = 0). + * a may not be zero. Constant time. */ +static void secp256k1_gej_double_nonzero(secp256k1_gej *r, const secp256k1_gej *a, secp256k1_fe *rzr); + +/** Set r equal to the double of a. If rzr is not-NULL, r->z = a->z * *rzr (where infinity means an implicit z = 0). */ +static void secp256k1_gej_double_var(secp256k1_gej *r, const secp256k1_gej *a, secp256k1_fe *rzr); + +/** Set r equal to the sum of a and b. If rzr is non-NULL, r->z = a->z * *rzr (a cannot be infinity in that case). */ +static void secp256k1_gej_add_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_gej *b, secp256k1_fe *rzr); + +/** Set r equal to the sum of a and b (with b given in affine coordinates, and not infinity). */ +static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b); + +/** Set r equal to the sum of a and b (with b given in affine coordinates). This is more efficient + than secp256k1_gej_add_var. It is identical to secp256k1_gej_add_ge but without constant-time + guarantee, and b is allowed to be infinity. If rzr is non-NULL, r->z = a->z * *rzr (a cannot be infinity in that case). */ +static void secp256k1_gej_add_ge_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b, secp256k1_fe *rzr); + +/** Set r equal to the sum of a and b (with the inverse of b's Z coordinate passed as bzinv). */ +static void secp256k1_gej_add_zinv_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b, const secp256k1_fe *bzinv); + +#ifdef USE_ENDOMORPHISM +/** Set r to be equal to lambda times a, where lambda is chosen in a way such that this is very fast. */ +static void secp256k1_ge_mul_lambda(secp256k1_ge *r, const secp256k1_ge *a); +#endif + +/** Clear a secp256k1_gej to prevent leaking sensitive information. */ +static void secp256k1_gej_clear(secp256k1_gej *r); + +/** Clear a secp256k1_ge to prevent leaking sensitive information. */ +static void secp256k1_ge_clear(secp256k1_ge *r); + +/** Convert a group element to the storage type. */ +static void secp256k1_ge_to_storage(secp256k1_ge_storage *r, const secp256k1_ge *a); + +/** Convert a group element back from the storage type. */ +static void secp256k1_ge_from_storage(secp256k1_ge *r, const secp256k1_ge_storage *a); + +/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. */ +static void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_ge_storage *a, int flag); + +/** Rescale a jacobian point by b which must be non-zero. Constant-time. */ +static void secp256k1_gej_rescale(secp256k1_gej *r, const secp256k1_fe *b); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/group_impl.h b/crypto/secp256k1/libsecp256k1/src/group_impl.h new file mode 100644 index 0000000..7d72353 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/group_impl.h @@ -0,0 +1,700 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_GROUP_IMPL_H_ +#define _SECP256K1_GROUP_IMPL_H_ + +#include "num.h" +#include "field.h" +#include "group.h" + +/* These points can be generated in sage as follows: + * + * 0. Setup a worksheet with the following parameters. + * b = 4 # whatever CURVE_B will be set to + * F = FiniteField (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F) + * C = EllipticCurve ([F (0), F (b)]) + * + * 1. Determine all the small orders available to you. (If there are + * no satisfactory ones, go back and change b.) + * print C.order().factor(limit=1000) + * + * 2. Choose an order as one of the prime factors listed in the above step. + * (You can also multiply some to get a composite order, though the + * tests will crash trying to invert scalars during signing.) We take a + * random point and scale it to drop its order to the desired value. + * There is some probability this won't work; just try again. + * order = 199 + * P = C.random_point() + * P = (int(P.order()) / int(order)) * P + * assert(P.order() == order) + * + * 3. Print the values. You'll need to use a vim macro or something to + * split the hex output into 4-byte chunks. + * print "%x %x" % P.xy() + */ +#if defined(EXHAUSTIVE_TEST_ORDER) +# if EXHAUSTIVE_TEST_ORDER == 199 +const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST( + 0xFA7CC9A7, 0x0737F2DB, 0xA749DD39, 0x2B4FB069, + 0x3B017A7D, 0xA808C2F1, 0xFB12940C, 0x9EA66C18, + 0x78AC123A, 0x5ED8AEF3, 0x8732BC91, 0x1F3A2868, + 0x48DF246C, 0x808DAE72, 0xCFE52572, 0x7F0501ED +); + +const int CURVE_B = 4; +# elif EXHAUSTIVE_TEST_ORDER == 13 +const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST( + 0xedc60018, 0xa51a786b, 0x2ea91f4d, 0x4c9416c0, + 0x9de54c3b, 0xa1316554, 0x6cf4345c, 0x7277ef15, + 0x54cb1b6b, 0xdc8c1273, 0x087844ea, 0x43f4603e, + 0x0eaf9a43, 0xf6effe55, 0x939f806d, 0x37adf8ac +); +const int CURVE_B = 2; +# else +# error No known generator for the specified exhaustive test group order. +# endif +#else +/** Generator for secp256k1, value 'g' defined in + * "Standards for Efficient Cryptography" (SEC2) 2.7.1. + */ +static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_GE_CONST( + 0x79BE667EUL, 0xF9DCBBACUL, 0x55A06295UL, 0xCE870B07UL, + 0x029BFCDBUL, 0x2DCE28D9UL, 0x59F2815BUL, 0x16F81798UL, + 0x483ADA77UL, 0x26A3C465UL, 0x5DA4FBFCUL, 0x0E1108A8UL, + 0xFD17B448UL, 0xA6855419UL, 0x9C47D08FUL, 0xFB10D4B8UL +); + +const int CURVE_B = 7; +#endif + +static void secp256k1_ge_set_gej_zinv(secp256k1_ge *r, const secp256k1_gej *a, const secp256k1_fe *zi) { + secp256k1_fe zi2; + secp256k1_fe zi3; + secp256k1_fe_sqr(&zi2, zi); + secp256k1_fe_mul(&zi3, &zi2, zi); + secp256k1_fe_mul(&r->x, &a->x, &zi2); + secp256k1_fe_mul(&r->y, &a->y, &zi3); + r->infinity = a->infinity; +} + +static void secp256k1_ge_set_xy(secp256k1_ge *r, const secp256k1_fe *x, const secp256k1_fe *y) { + r->infinity = 0; + r->x = *x; + r->y = *y; +} + +static int secp256k1_ge_is_infinity(const secp256k1_ge *a) { + return a->infinity; +} + +static void secp256k1_ge_neg(secp256k1_ge *r, const secp256k1_ge *a) { + *r = *a; + secp256k1_fe_normalize_weak(&r->y); + secp256k1_fe_negate(&r->y, &r->y, 1); +} + +static void secp256k1_ge_set_gej(secp256k1_ge *r, secp256k1_gej *a) { + secp256k1_fe z2, z3; + r->infinity = a->infinity; + secp256k1_fe_inv(&a->z, &a->z); + secp256k1_fe_sqr(&z2, &a->z); + secp256k1_fe_mul(&z3, &a->z, &z2); + secp256k1_fe_mul(&a->x, &a->x, &z2); + secp256k1_fe_mul(&a->y, &a->y, &z3); + secp256k1_fe_set_int(&a->z, 1); + r->x = a->x; + r->y = a->y; +} + +static void secp256k1_ge_set_gej_var(secp256k1_ge *r, secp256k1_gej *a) { + secp256k1_fe z2, z3; + r->infinity = a->infinity; + if (a->infinity) { + return; + } + secp256k1_fe_inv_var(&a->z, &a->z); + secp256k1_fe_sqr(&z2, &a->z); + secp256k1_fe_mul(&z3, &a->z, &z2); + secp256k1_fe_mul(&a->x, &a->x, &z2); + secp256k1_fe_mul(&a->y, &a->y, &z3); + secp256k1_fe_set_int(&a->z, 1); + r->x = a->x; + r->y = a->y; +} + +static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len, const secp256k1_callback *cb) { + secp256k1_fe *az; + secp256k1_fe *azi; + size_t i; + size_t count = 0; + az = (secp256k1_fe *)checked_malloc(cb, sizeof(secp256k1_fe) * len); + for (i = 0; i < len; i++) { + if (!a[i].infinity) { + az[count++] = a[i].z; + } + } + + azi = (secp256k1_fe *)checked_malloc(cb, sizeof(secp256k1_fe) * count); + secp256k1_fe_inv_all_var(azi, az, count); + free(az); + + count = 0; + for (i = 0; i < len; i++) { + r[i].infinity = a[i].infinity; + if (!a[i].infinity) { + secp256k1_ge_set_gej_zinv(&r[i], &a[i], &azi[count++]); + } + } + free(azi); +} + +static void secp256k1_ge_set_table_gej_var(secp256k1_ge *r, const secp256k1_gej *a, const secp256k1_fe *zr, size_t len) { + size_t i = len - 1; + secp256k1_fe zi; + + if (len > 0) { + /* Compute the inverse of the last z coordinate, and use it to compute the last affine output. */ + secp256k1_fe_inv(&zi, &a[i].z); + secp256k1_ge_set_gej_zinv(&r[i], &a[i], &zi); + + /* Work out way backwards, using the z-ratios to scale the x/y values. */ + while (i > 0) { + secp256k1_fe_mul(&zi, &zi, &zr[i]); + i--; + secp256k1_ge_set_gej_zinv(&r[i], &a[i], &zi); + } + } +} + +static void secp256k1_ge_globalz_set_table_gej(size_t len, secp256k1_ge *r, secp256k1_fe *globalz, const secp256k1_gej *a, const secp256k1_fe *zr) { + size_t i = len - 1; + secp256k1_fe zs; + + if (len > 0) { + /* The z of the final point gives us the "global Z" for the table. */ + r[i].x = a[i].x; + r[i].y = a[i].y; + *globalz = a[i].z; + r[i].infinity = 0; + zs = zr[i]; + + /* Work our way backwards, using the z-ratios to scale the x/y values. */ + while (i > 0) { + if (i != len - 1) { + secp256k1_fe_mul(&zs, &zs, &zr[i]); + } + i--; + secp256k1_ge_set_gej_zinv(&r[i], &a[i], &zs); + } + } +} + +static void secp256k1_gej_set_infinity(secp256k1_gej *r) { + r->infinity = 1; + secp256k1_fe_clear(&r->x); + secp256k1_fe_clear(&r->y); + secp256k1_fe_clear(&r->z); +} + +static void secp256k1_gej_clear(secp256k1_gej *r) { + r->infinity = 0; + secp256k1_fe_clear(&r->x); + secp256k1_fe_clear(&r->y); + secp256k1_fe_clear(&r->z); +} + +static void secp256k1_ge_clear(secp256k1_ge *r) { + r->infinity = 0; + secp256k1_fe_clear(&r->x); + secp256k1_fe_clear(&r->y); +} + +static int secp256k1_ge_set_xquad(secp256k1_ge *r, const secp256k1_fe *x) { + secp256k1_fe x2, x3, c; + r->x = *x; + secp256k1_fe_sqr(&x2, x); + secp256k1_fe_mul(&x3, x, &x2); + r->infinity = 0; + secp256k1_fe_set_int(&c, CURVE_B); + secp256k1_fe_add(&c, &x3); + return secp256k1_fe_sqrt(&r->y, &c); +} + +static int secp256k1_ge_set_xo_var(secp256k1_ge *r, const secp256k1_fe *x, int odd) { + if (!secp256k1_ge_set_xquad(r, x)) { + return 0; + } + secp256k1_fe_normalize_var(&r->y); + if (secp256k1_fe_is_odd(&r->y) != odd) { + secp256k1_fe_negate(&r->y, &r->y, 1); + } + return 1; + +} + +static void secp256k1_gej_set_ge(secp256k1_gej *r, const secp256k1_ge *a) { + r->infinity = a->infinity; + r->x = a->x; + r->y = a->y; + secp256k1_fe_set_int(&r->z, 1); +} + +static int secp256k1_gej_eq_x_var(const secp256k1_fe *x, const secp256k1_gej *a) { + secp256k1_fe r, r2; + VERIFY_CHECK(!a->infinity); + secp256k1_fe_sqr(&r, &a->z); secp256k1_fe_mul(&r, &r, x); + r2 = a->x; secp256k1_fe_normalize_weak(&r2); + return secp256k1_fe_equal_var(&r, &r2); +} + +static void secp256k1_gej_neg(secp256k1_gej *r, const secp256k1_gej *a) { + r->infinity = a->infinity; + r->x = a->x; + r->y = a->y; + r->z = a->z; + secp256k1_fe_normalize_weak(&r->y); + secp256k1_fe_negate(&r->y, &r->y, 1); +} + +static int secp256k1_gej_is_infinity(const secp256k1_gej *a) { + return a->infinity; +} + +static int secp256k1_gej_is_valid_var(const secp256k1_gej *a) { + secp256k1_fe y2, x3, z2, z6; + if (a->infinity) { + return 0; + } + /** y^2 = x^3 + 7 + * (Y/Z^3)^2 = (X/Z^2)^3 + 7 + * Y^2 / Z^6 = X^3 / Z^6 + 7 + * Y^2 = X^3 + 7*Z^6 + */ + secp256k1_fe_sqr(&y2, &a->y); + secp256k1_fe_sqr(&x3, &a->x); secp256k1_fe_mul(&x3, &x3, &a->x); + secp256k1_fe_sqr(&z2, &a->z); + secp256k1_fe_sqr(&z6, &z2); secp256k1_fe_mul(&z6, &z6, &z2); + secp256k1_fe_mul_int(&z6, CURVE_B); + secp256k1_fe_add(&x3, &z6); + secp256k1_fe_normalize_weak(&x3); + return secp256k1_fe_equal_var(&y2, &x3); +} + +static int secp256k1_ge_is_valid_var(const secp256k1_ge *a) { + secp256k1_fe y2, x3, c; + if (a->infinity) { + return 0; + } + /* y^2 = x^3 + 7 */ + secp256k1_fe_sqr(&y2, &a->y); + secp256k1_fe_sqr(&x3, &a->x); secp256k1_fe_mul(&x3, &x3, &a->x); + secp256k1_fe_set_int(&c, CURVE_B); + secp256k1_fe_add(&x3, &c); + secp256k1_fe_normalize_weak(&x3); + return secp256k1_fe_equal_var(&y2, &x3); +} + +static void secp256k1_gej_double_var(secp256k1_gej *r, const secp256k1_gej *a, secp256k1_fe *rzr) { + /* Operations: 3 mul, 4 sqr, 0 normalize, 12 mul_int/add/negate. + * + * Note that there is an implementation described at + * https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + * which trades a multiply for a square, but in practice this is actually slower, + * mainly because it requires more normalizations. + */ + secp256k1_fe t1,t2,t3,t4; + /** For secp256k1, 2Q is infinity if and only if Q is infinity. This is because if 2Q = infinity, + * Q must equal -Q, or that Q.y == -(Q.y), or Q.y is 0. For a point on y^2 = x^3 + 7 to have + * y=0, x^3 must be -7 mod p. However, -7 has no cube root mod p. + * + * Having said this, if this function receives a point on a sextic twist, e.g. by + * a fault attack, it is possible for y to be 0. This happens for y^2 = x^3 + 6, + * since -6 does have a cube root mod p. For this point, this function will not set + * the infinity flag even though the point doubles to infinity, and the result + * point will be gibberish (z = 0 but infinity = 0). + */ + r->infinity = a->infinity; + if (r->infinity) { + if (rzr != NULL) { + secp256k1_fe_set_int(rzr, 1); + } + return; + } + + if (rzr != NULL) { + *rzr = a->y; + secp256k1_fe_normalize_weak(rzr); + secp256k1_fe_mul_int(rzr, 2); + } + + secp256k1_fe_mul(&r->z, &a->z, &a->y); + secp256k1_fe_mul_int(&r->z, 2); /* Z' = 2*Y*Z (2) */ + secp256k1_fe_sqr(&t1, &a->x); + secp256k1_fe_mul_int(&t1, 3); /* T1 = 3*X^2 (3) */ + secp256k1_fe_sqr(&t2, &t1); /* T2 = 9*X^4 (1) */ + secp256k1_fe_sqr(&t3, &a->y); + secp256k1_fe_mul_int(&t3, 2); /* T3 = 2*Y^2 (2) */ + secp256k1_fe_sqr(&t4, &t3); + secp256k1_fe_mul_int(&t4, 2); /* T4 = 8*Y^4 (2) */ + secp256k1_fe_mul(&t3, &t3, &a->x); /* T3 = 2*X*Y^2 (1) */ + r->x = t3; + secp256k1_fe_mul_int(&r->x, 4); /* X' = 8*X*Y^2 (4) */ + secp256k1_fe_negate(&r->x, &r->x, 4); /* X' = -8*X*Y^2 (5) */ + secp256k1_fe_add(&r->x, &t2); /* X' = 9*X^4 - 8*X*Y^2 (6) */ + secp256k1_fe_negate(&t2, &t2, 1); /* T2 = -9*X^4 (2) */ + secp256k1_fe_mul_int(&t3, 6); /* T3 = 12*X*Y^2 (6) */ + secp256k1_fe_add(&t3, &t2); /* T3 = 12*X*Y^2 - 9*X^4 (8) */ + secp256k1_fe_mul(&r->y, &t1, &t3); /* Y' = 36*X^3*Y^2 - 27*X^6 (1) */ + secp256k1_fe_negate(&t2, &t4, 2); /* T2 = -8*Y^4 (3) */ + secp256k1_fe_add(&r->y, &t2); /* Y' = 36*X^3*Y^2 - 27*X^6 - 8*Y^4 (4) */ +} + +static SECP256K1_INLINE void secp256k1_gej_double_nonzero(secp256k1_gej *r, const secp256k1_gej *a, secp256k1_fe *rzr) { + VERIFY_CHECK(!secp256k1_gej_is_infinity(a)); + secp256k1_gej_double_var(r, a, rzr); +} + +static void secp256k1_gej_add_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_gej *b, secp256k1_fe *rzr) { + /* Operations: 12 mul, 4 sqr, 2 normalize, 12 mul_int/add/negate */ + secp256k1_fe z22, z12, u1, u2, s1, s2, h, i, i2, h2, h3, t; + + if (a->infinity) { + VERIFY_CHECK(rzr == NULL); + *r = *b; + return; + } + + if (b->infinity) { + if (rzr != NULL) { + secp256k1_fe_set_int(rzr, 1); + } + *r = *a; + return; + } + + r->infinity = 0; + secp256k1_fe_sqr(&z22, &b->z); + secp256k1_fe_sqr(&z12, &a->z); + secp256k1_fe_mul(&u1, &a->x, &z22); + secp256k1_fe_mul(&u2, &b->x, &z12); + secp256k1_fe_mul(&s1, &a->y, &z22); secp256k1_fe_mul(&s1, &s1, &b->z); + secp256k1_fe_mul(&s2, &b->y, &z12); secp256k1_fe_mul(&s2, &s2, &a->z); + secp256k1_fe_negate(&h, &u1, 1); secp256k1_fe_add(&h, &u2); + secp256k1_fe_negate(&i, &s1, 1); secp256k1_fe_add(&i, &s2); + if (secp256k1_fe_normalizes_to_zero_var(&h)) { + if (secp256k1_fe_normalizes_to_zero_var(&i)) { + secp256k1_gej_double_var(r, a, rzr); + } else { + if (rzr != NULL) { + secp256k1_fe_set_int(rzr, 0); + } + r->infinity = 1; + } + return; + } + secp256k1_fe_sqr(&i2, &i); + secp256k1_fe_sqr(&h2, &h); + secp256k1_fe_mul(&h3, &h, &h2); + secp256k1_fe_mul(&h, &h, &b->z); + if (rzr != NULL) { + *rzr = h; + } + secp256k1_fe_mul(&r->z, &a->z, &h); + secp256k1_fe_mul(&t, &u1, &h2); + r->x = t; secp256k1_fe_mul_int(&r->x, 2); secp256k1_fe_add(&r->x, &h3); secp256k1_fe_negate(&r->x, &r->x, 3); secp256k1_fe_add(&r->x, &i2); + secp256k1_fe_negate(&r->y, &r->x, 5); secp256k1_fe_add(&r->y, &t); secp256k1_fe_mul(&r->y, &r->y, &i); + secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_negate(&h3, &h3, 1); + secp256k1_fe_add(&r->y, &h3); +} + +static void secp256k1_gej_add_ge_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b, secp256k1_fe *rzr) { + /* 8 mul, 3 sqr, 4 normalize, 12 mul_int/add/negate */ + secp256k1_fe z12, u1, u2, s1, s2, h, i, i2, h2, h3, t; + if (a->infinity) { + VERIFY_CHECK(rzr == NULL); + secp256k1_gej_set_ge(r, b); + return; + } + if (b->infinity) { + if (rzr != NULL) { + secp256k1_fe_set_int(rzr, 1); + } + *r = *a; + return; + } + r->infinity = 0; + + secp256k1_fe_sqr(&z12, &a->z); + u1 = a->x; secp256k1_fe_normalize_weak(&u1); + secp256k1_fe_mul(&u2, &b->x, &z12); + s1 = a->y; secp256k1_fe_normalize_weak(&s1); + secp256k1_fe_mul(&s2, &b->y, &z12); secp256k1_fe_mul(&s2, &s2, &a->z); + secp256k1_fe_negate(&h, &u1, 1); secp256k1_fe_add(&h, &u2); + secp256k1_fe_negate(&i, &s1, 1); secp256k1_fe_add(&i, &s2); + if (secp256k1_fe_normalizes_to_zero_var(&h)) { + if (secp256k1_fe_normalizes_to_zero_var(&i)) { + secp256k1_gej_double_var(r, a, rzr); + } else { + if (rzr != NULL) { + secp256k1_fe_set_int(rzr, 0); + } + r->infinity = 1; + } + return; + } + secp256k1_fe_sqr(&i2, &i); + secp256k1_fe_sqr(&h2, &h); + secp256k1_fe_mul(&h3, &h, &h2); + if (rzr != NULL) { + *rzr = h; + } + secp256k1_fe_mul(&r->z, &a->z, &h); + secp256k1_fe_mul(&t, &u1, &h2); + r->x = t; secp256k1_fe_mul_int(&r->x, 2); secp256k1_fe_add(&r->x, &h3); secp256k1_fe_negate(&r->x, &r->x, 3); secp256k1_fe_add(&r->x, &i2); + secp256k1_fe_negate(&r->y, &r->x, 5); secp256k1_fe_add(&r->y, &t); secp256k1_fe_mul(&r->y, &r->y, &i); + secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_negate(&h3, &h3, 1); + secp256k1_fe_add(&r->y, &h3); +} + +static void secp256k1_gej_add_zinv_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b, const secp256k1_fe *bzinv) { + /* 9 mul, 3 sqr, 4 normalize, 12 mul_int/add/negate */ + secp256k1_fe az, z12, u1, u2, s1, s2, h, i, i2, h2, h3, t; + + if (b->infinity) { + *r = *a; + return; + } + if (a->infinity) { + secp256k1_fe bzinv2, bzinv3; + r->infinity = b->infinity; + secp256k1_fe_sqr(&bzinv2, bzinv); + secp256k1_fe_mul(&bzinv3, &bzinv2, bzinv); + secp256k1_fe_mul(&r->x, &b->x, &bzinv2); + secp256k1_fe_mul(&r->y, &b->y, &bzinv3); + secp256k1_fe_set_int(&r->z, 1); + return; + } + r->infinity = 0; + + /** We need to calculate (rx,ry,rz) = (ax,ay,az) + (bx,by,1/bzinv). Due to + * secp256k1's isomorphism we can multiply the Z coordinates on both sides + * by bzinv, and get: (rx,ry,rz*bzinv) = (ax,ay,az*bzinv) + (bx,by,1). + * This means that (rx,ry,rz) can be calculated as + * (ax,ay,az*bzinv) + (bx,by,1), when not applying the bzinv factor to rz. + * The variable az below holds the modified Z coordinate for a, which is used + * for the computation of rx and ry, but not for rz. + */ + secp256k1_fe_mul(&az, &a->z, bzinv); + + secp256k1_fe_sqr(&z12, &az); + u1 = a->x; secp256k1_fe_normalize_weak(&u1); + secp256k1_fe_mul(&u2, &b->x, &z12); + s1 = a->y; secp256k1_fe_normalize_weak(&s1); + secp256k1_fe_mul(&s2, &b->y, &z12); secp256k1_fe_mul(&s2, &s2, &az); + secp256k1_fe_negate(&h, &u1, 1); secp256k1_fe_add(&h, &u2); + secp256k1_fe_negate(&i, &s1, 1); secp256k1_fe_add(&i, &s2); + if (secp256k1_fe_normalizes_to_zero_var(&h)) { + if (secp256k1_fe_normalizes_to_zero_var(&i)) { + secp256k1_gej_double_var(r, a, NULL); + } else { + r->infinity = 1; + } + return; + } + secp256k1_fe_sqr(&i2, &i); + secp256k1_fe_sqr(&h2, &h); + secp256k1_fe_mul(&h3, &h, &h2); + r->z = a->z; secp256k1_fe_mul(&r->z, &r->z, &h); + secp256k1_fe_mul(&t, &u1, &h2); + r->x = t; secp256k1_fe_mul_int(&r->x, 2); secp256k1_fe_add(&r->x, &h3); secp256k1_fe_negate(&r->x, &r->x, 3); secp256k1_fe_add(&r->x, &i2); + secp256k1_fe_negate(&r->y, &r->x, 5); secp256k1_fe_add(&r->y, &t); secp256k1_fe_mul(&r->y, &r->y, &i); + secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_negate(&h3, &h3, 1); + secp256k1_fe_add(&r->y, &h3); +} + + +static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b) { + /* Operations: 7 mul, 5 sqr, 4 normalize, 21 mul_int/add/negate/cmov */ + static const secp256k1_fe fe_1 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1); + secp256k1_fe zz, u1, u2, s1, s2, t, tt, m, n, q, rr; + secp256k1_fe m_alt, rr_alt; + int infinity, degenerate; + VERIFY_CHECK(!b->infinity); + VERIFY_CHECK(a->infinity == 0 || a->infinity == 1); + + /** In: + * Eric Brier and Marc Joye, Weierstrass Elliptic Curves and Side-Channel Attacks. + * In D. Naccache and P. Paillier, Eds., Public Key Cryptography, vol. 2274 of Lecture Notes in Computer Science, pages 335-345. Springer-Verlag, 2002. + * we find as solution for a unified addition/doubling formula: + * lambda = ((x1 + x2)^2 - x1 * x2 + a) / (y1 + y2), with a = 0 for secp256k1's curve equation. + * x3 = lambda^2 - (x1 + x2) + * 2*y3 = lambda * (x1 + x2 - 2 * x3) - (y1 + y2). + * + * Substituting x_i = Xi / Zi^2 and yi = Yi / Zi^3, for i=1,2,3, gives: + * U1 = X1*Z2^2, U2 = X2*Z1^2 + * S1 = Y1*Z2^3, S2 = Y2*Z1^3 + * Z = Z1*Z2 + * T = U1+U2 + * M = S1+S2 + * Q = T*M^2 + * R = T^2-U1*U2 + * X3 = 4*(R^2-Q) + * Y3 = 4*(R*(3*Q-2*R^2)-M^4) + * Z3 = 2*M*Z + * (Note that the paper uses xi = Xi / Zi and yi = Yi / Zi instead.) + * + * This formula has the benefit of being the same for both addition + * of distinct points and doubling. However, it breaks down in the + * case that either point is infinity, or that y1 = -y2. We handle + * these cases in the following ways: + * + * - If b is infinity we simply bail by means of a VERIFY_CHECK. + * + * - If a is infinity, we detect this, and at the end of the + * computation replace the result (which will be meaningless, + * but we compute to be constant-time) with b.x : b.y : 1. + * + * - If a = -b, we have y1 = -y2, which is a degenerate case. + * But here the answer is infinity, so we simply set the + * infinity flag of the result, overriding the computed values + * without even needing to cmov. + * + * - If y1 = -y2 but x1 != x2, which does occur thanks to certain + * properties of our curve (specifically, 1 has nontrivial cube + * roots in our field, and the curve equation has no x coefficient) + * then the answer is not infinity but also not given by the above + * equation. In this case, we cmov in place an alternate expression + * for lambda. Specifically (y1 - y2)/(x1 - x2). Where both these + * expressions for lambda are defined, they are equal, and can be + * obtained from each other by multiplication by (y1 + y2)/(y1 + y2) + * then substitution of x^3 + 7 for y^2 (using the curve equation). + * For all pairs of nonzero points (a, b) at least one is defined, + * so this covers everything. + */ + + secp256k1_fe_sqr(&zz, &a->z); /* z = Z1^2 */ + u1 = a->x; secp256k1_fe_normalize_weak(&u1); /* u1 = U1 = X1*Z2^2 (1) */ + secp256k1_fe_mul(&u2, &b->x, &zz); /* u2 = U2 = X2*Z1^2 (1) */ + s1 = a->y; secp256k1_fe_normalize_weak(&s1); /* s1 = S1 = Y1*Z2^3 (1) */ + secp256k1_fe_mul(&s2, &b->y, &zz); /* s2 = Y2*Z1^2 (1) */ + secp256k1_fe_mul(&s2, &s2, &a->z); /* s2 = S2 = Y2*Z1^3 (1) */ + t = u1; secp256k1_fe_add(&t, &u2); /* t = T = U1+U2 (2) */ + m = s1; secp256k1_fe_add(&m, &s2); /* m = M = S1+S2 (2) */ + secp256k1_fe_sqr(&rr, &t); /* rr = T^2 (1) */ + secp256k1_fe_negate(&m_alt, &u2, 1); /* Malt = -X2*Z1^2 */ + secp256k1_fe_mul(&tt, &u1, &m_alt); /* tt = -U1*U2 (2) */ + secp256k1_fe_add(&rr, &tt); /* rr = R = T^2-U1*U2 (3) */ + /** If lambda = R/M = 0/0 we have a problem (except in the "trivial" + * case that Z = z1z2 = 0, and this is special-cased later on). */ + degenerate = secp256k1_fe_normalizes_to_zero(&m) & + secp256k1_fe_normalizes_to_zero(&rr); + /* This only occurs when y1 == -y2 and x1^3 == x2^3, but x1 != x2. + * This means either x1 == beta*x2 or beta*x1 == x2, where beta is + * a nontrivial cube root of one. In either case, an alternate + * non-indeterminate expression for lambda is (y1 - y2)/(x1 - x2), + * so we set R/M equal to this. */ + rr_alt = s1; + secp256k1_fe_mul_int(&rr_alt, 2); /* rr = Y1*Z2^3 - Y2*Z1^3 (2) */ + secp256k1_fe_add(&m_alt, &u1); /* Malt = X1*Z2^2 - X2*Z1^2 */ + + secp256k1_fe_cmov(&rr_alt, &rr, !degenerate); + secp256k1_fe_cmov(&m_alt, &m, !degenerate); + /* Now Ralt / Malt = lambda and is guaranteed not to be 0/0. + * From here on out Ralt and Malt represent the numerator + * and denominator of lambda; R and M represent the explicit + * expressions x1^2 + x2^2 + x1x2 and y1 + y2. */ + secp256k1_fe_sqr(&n, &m_alt); /* n = Malt^2 (1) */ + secp256k1_fe_mul(&q, &n, &t); /* q = Q = T*Malt^2 (1) */ + /* These two lines use the observation that either M == Malt or M == 0, + * so M^3 * Malt is either Malt^4 (which is computed by squaring), or + * zero (which is "computed" by cmov). So the cost is one squaring + * versus two multiplications. */ + secp256k1_fe_sqr(&n, &n); + secp256k1_fe_cmov(&n, &m, degenerate); /* n = M^3 * Malt (2) */ + secp256k1_fe_sqr(&t, &rr_alt); /* t = Ralt^2 (1) */ + secp256k1_fe_mul(&r->z, &a->z, &m_alt); /* r->z = Malt*Z (1) */ + infinity = secp256k1_fe_normalizes_to_zero(&r->z) * (1 - a->infinity); + secp256k1_fe_mul_int(&r->z, 2); /* r->z = Z3 = 2*Malt*Z (2) */ + secp256k1_fe_negate(&q, &q, 1); /* q = -Q (2) */ + secp256k1_fe_add(&t, &q); /* t = Ralt^2-Q (3) */ + secp256k1_fe_normalize_weak(&t); + r->x = t; /* r->x = Ralt^2-Q (1) */ + secp256k1_fe_mul_int(&t, 2); /* t = 2*x3 (2) */ + secp256k1_fe_add(&t, &q); /* t = 2*x3 - Q: (4) */ + secp256k1_fe_mul(&t, &t, &rr_alt); /* t = Ralt*(2*x3 - Q) (1) */ + secp256k1_fe_add(&t, &n); /* t = Ralt*(2*x3 - Q) + M^3*Malt (3) */ + secp256k1_fe_negate(&r->y, &t, 3); /* r->y = Ralt*(Q - 2x3) - M^3*Malt (4) */ + secp256k1_fe_normalize_weak(&r->y); + secp256k1_fe_mul_int(&r->x, 4); /* r->x = X3 = 4*(Ralt^2-Q) */ + secp256k1_fe_mul_int(&r->y, 4); /* r->y = Y3 = 4*Ralt*(Q - 2x3) - 4*M^3*Malt (4) */ + + /** In case a->infinity == 1, replace r with (b->x, b->y, 1). */ + secp256k1_fe_cmov(&r->x, &b->x, a->infinity); + secp256k1_fe_cmov(&r->y, &b->y, a->infinity); + secp256k1_fe_cmov(&r->z, &fe_1, a->infinity); + r->infinity = infinity; +} + +static void secp256k1_gej_rescale(secp256k1_gej *r, const secp256k1_fe *s) { + /* Operations: 4 mul, 1 sqr */ + secp256k1_fe zz; + VERIFY_CHECK(!secp256k1_fe_is_zero(s)); + secp256k1_fe_sqr(&zz, s); + secp256k1_fe_mul(&r->x, &r->x, &zz); /* r->x *= s^2 */ + secp256k1_fe_mul(&r->y, &r->y, &zz); + secp256k1_fe_mul(&r->y, &r->y, s); /* r->y *= s^3 */ + secp256k1_fe_mul(&r->z, &r->z, s); /* r->z *= s */ +} + +static void secp256k1_ge_to_storage(secp256k1_ge_storage *r, const secp256k1_ge *a) { + secp256k1_fe x, y; + VERIFY_CHECK(!a->infinity); + x = a->x; + secp256k1_fe_normalize(&x); + y = a->y; + secp256k1_fe_normalize(&y); + secp256k1_fe_to_storage(&r->x, &x); + secp256k1_fe_to_storage(&r->y, &y); +} + +static void secp256k1_ge_from_storage(secp256k1_ge *r, const secp256k1_ge_storage *a) { + secp256k1_fe_from_storage(&r->x, &a->x); + secp256k1_fe_from_storage(&r->y, &a->y); + r->infinity = 0; +} + +static SECP256K1_INLINE void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_ge_storage *a, int flag) { + secp256k1_fe_storage_cmov(&r->x, &a->x, flag); + secp256k1_fe_storage_cmov(&r->y, &a->y, flag); +} + +#ifdef USE_ENDOMORPHISM +static void secp256k1_ge_mul_lambda(secp256k1_ge *r, const secp256k1_ge *a) { + static const secp256k1_fe beta = SECP256K1_FE_CONST( + 0x7ae96a2bul, 0x657c0710ul, 0x6e64479eul, 0xac3434e9ul, + 0x9cf04975ul, 0x12f58995ul, 0xc1396c28ul, 0x719501eeul + ); + *r = *a; + secp256k1_fe_mul(&r->x, &r->x, &beta); +} +#endif + +static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a) { + secp256k1_fe yz; + + if (a->infinity) { + return 0; + } + + /* We rely on the fact that the Jacobi symbol of 1 / a->z^3 is the same as + * that of a->z. Thus a->y / a->z^3 is a quadratic residue iff a->y * a->z + is */ + secp256k1_fe_mul(&yz, &a->y, &a->z); + return secp256k1_fe_is_quad_var(&yz); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/hash.h b/crypto/secp256k1/libsecp256k1/src/hash.h new file mode 100644 index 0000000..fca98ca --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/hash.h @@ -0,0 +1,41 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_HASH_ +#define _SECP256K1_HASH_ + +#include +#include + +typedef struct { + uint32_t s[8]; + uint32_t buf[16]; /* In big endian */ + size_t bytes; +} secp256k1_sha256_t; + +static void secp256k1_sha256_initialize(secp256k1_sha256_t *hash); +static void secp256k1_sha256_write(secp256k1_sha256_t *hash, const unsigned char *data, size_t size); +static void secp256k1_sha256_finalize(secp256k1_sha256_t *hash, unsigned char *out32); + +typedef struct { + secp256k1_sha256_t inner, outer; +} secp256k1_hmac_sha256_t; + +static void secp256k1_hmac_sha256_initialize(secp256k1_hmac_sha256_t *hash, const unsigned char *key, size_t size); +static void secp256k1_hmac_sha256_write(secp256k1_hmac_sha256_t *hash, const unsigned char *data, size_t size); +static void secp256k1_hmac_sha256_finalize(secp256k1_hmac_sha256_t *hash, unsigned char *out32); + +typedef struct { + unsigned char v[32]; + unsigned char k[32]; + int retry; +} secp256k1_rfc6979_hmac_sha256_t; + +static void secp256k1_rfc6979_hmac_sha256_initialize(secp256k1_rfc6979_hmac_sha256_t *rng, const unsigned char *key, size_t keylen); +static void secp256k1_rfc6979_hmac_sha256_generate(secp256k1_rfc6979_hmac_sha256_t *rng, unsigned char *out, size_t outlen); +static void secp256k1_rfc6979_hmac_sha256_finalize(secp256k1_rfc6979_hmac_sha256_t *rng); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/hash_impl.h b/crypto/secp256k1/libsecp256k1/src/hash_impl.h new file mode 100644 index 0000000..b47e65f --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/hash_impl.h @@ -0,0 +1,281 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_HASH_IMPL_H_ +#define _SECP256K1_HASH_IMPL_H_ + +#include "hash.h" + +#include +#include +#include + +#define Ch(x,y,z) ((z) ^ ((x) & ((y) ^ (z)))) +#define Maj(x,y,z) (((x) & (y)) | ((z) & ((x) | (y)))) +#define Sigma0(x) (((x) >> 2 | (x) << 30) ^ ((x) >> 13 | (x) << 19) ^ ((x) >> 22 | (x) << 10)) +#define Sigma1(x) (((x) >> 6 | (x) << 26) ^ ((x) >> 11 | (x) << 21) ^ ((x) >> 25 | (x) << 7)) +#define sigma0(x) (((x) >> 7 | (x) << 25) ^ ((x) >> 18 | (x) << 14) ^ ((x) >> 3)) +#define sigma1(x) (((x) >> 17 | (x) << 15) ^ ((x) >> 19 | (x) << 13) ^ ((x) >> 10)) + +#define Round(a,b,c,d,e,f,g,h,k,w) do { \ + uint32_t t1 = (h) + Sigma1(e) + Ch((e), (f), (g)) + (k) + (w); \ + uint32_t t2 = Sigma0(a) + Maj((a), (b), (c)); \ + (d) += t1; \ + (h) = t1 + t2; \ +} while(0) + +#ifdef WORDS_BIGENDIAN +#define BE32(x) (x) +#else +#define BE32(p) ((((p) & 0xFF) << 24) | (((p) & 0xFF00) << 8) | (((p) & 0xFF0000) >> 8) | (((p) & 0xFF000000) >> 24)) +#endif + +static void secp256k1_sha256_initialize(secp256k1_sha256_t *hash) { + hash->s[0] = 0x6a09e667ul; + hash->s[1] = 0xbb67ae85ul; + hash->s[2] = 0x3c6ef372ul; + hash->s[3] = 0xa54ff53aul; + hash->s[4] = 0x510e527ful; + hash->s[5] = 0x9b05688cul; + hash->s[6] = 0x1f83d9abul; + hash->s[7] = 0x5be0cd19ul; + hash->bytes = 0; +} + +/** Perform one SHA-256 transformation, processing 16 big endian 32-bit words. */ +static void secp256k1_sha256_transform(uint32_t* s, const uint32_t* chunk) { + uint32_t a = s[0], b = s[1], c = s[2], d = s[3], e = s[4], f = s[5], g = s[6], h = s[7]; + uint32_t w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15; + + Round(a, b, c, d, e, f, g, h, 0x428a2f98, w0 = BE32(chunk[0])); + Round(h, a, b, c, d, e, f, g, 0x71374491, w1 = BE32(chunk[1])); + Round(g, h, a, b, c, d, e, f, 0xb5c0fbcf, w2 = BE32(chunk[2])); + Round(f, g, h, a, b, c, d, e, 0xe9b5dba5, w3 = BE32(chunk[3])); + Round(e, f, g, h, a, b, c, d, 0x3956c25b, w4 = BE32(chunk[4])); + Round(d, e, f, g, h, a, b, c, 0x59f111f1, w5 = BE32(chunk[5])); + Round(c, d, e, f, g, h, a, b, 0x923f82a4, w6 = BE32(chunk[6])); + Round(b, c, d, e, f, g, h, a, 0xab1c5ed5, w7 = BE32(chunk[7])); + Round(a, b, c, d, e, f, g, h, 0xd807aa98, w8 = BE32(chunk[8])); + Round(h, a, b, c, d, e, f, g, 0x12835b01, w9 = BE32(chunk[9])); + Round(g, h, a, b, c, d, e, f, 0x243185be, w10 = BE32(chunk[10])); + Round(f, g, h, a, b, c, d, e, 0x550c7dc3, w11 = BE32(chunk[11])); + Round(e, f, g, h, a, b, c, d, 0x72be5d74, w12 = BE32(chunk[12])); + Round(d, e, f, g, h, a, b, c, 0x80deb1fe, w13 = BE32(chunk[13])); + Round(c, d, e, f, g, h, a, b, 0x9bdc06a7, w14 = BE32(chunk[14])); + Round(b, c, d, e, f, g, h, a, 0xc19bf174, w15 = BE32(chunk[15])); + + Round(a, b, c, d, e, f, g, h, 0xe49b69c1, w0 += sigma1(w14) + w9 + sigma0(w1)); + Round(h, a, b, c, d, e, f, g, 0xefbe4786, w1 += sigma1(w15) + w10 + sigma0(w2)); + Round(g, h, a, b, c, d, e, f, 0x0fc19dc6, w2 += sigma1(w0) + w11 + sigma0(w3)); + Round(f, g, h, a, b, c, d, e, 0x240ca1cc, w3 += sigma1(w1) + w12 + sigma0(w4)); + Round(e, f, g, h, a, b, c, d, 0x2de92c6f, w4 += sigma1(w2) + w13 + sigma0(w5)); + Round(d, e, f, g, h, a, b, c, 0x4a7484aa, w5 += sigma1(w3) + w14 + sigma0(w6)); + Round(c, d, e, f, g, h, a, b, 0x5cb0a9dc, w6 += sigma1(w4) + w15 + sigma0(w7)); + Round(b, c, d, e, f, g, h, a, 0x76f988da, w7 += sigma1(w5) + w0 + sigma0(w8)); + Round(a, b, c, d, e, f, g, h, 0x983e5152, w8 += sigma1(w6) + w1 + sigma0(w9)); + Round(h, a, b, c, d, e, f, g, 0xa831c66d, w9 += sigma1(w7) + w2 + sigma0(w10)); + Round(g, h, a, b, c, d, e, f, 0xb00327c8, w10 += sigma1(w8) + w3 + sigma0(w11)); + Round(f, g, h, a, b, c, d, e, 0xbf597fc7, w11 += sigma1(w9) + w4 + sigma0(w12)); + Round(e, f, g, h, a, b, c, d, 0xc6e00bf3, w12 += sigma1(w10) + w5 + sigma0(w13)); + Round(d, e, f, g, h, a, b, c, 0xd5a79147, w13 += sigma1(w11) + w6 + sigma0(w14)); + Round(c, d, e, f, g, h, a, b, 0x06ca6351, w14 += sigma1(w12) + w7 + sigma0(w15)); + Round(b, c, d, e, f, g, h, a, 0x14292967, w15 += sigma1(w13) + w8 + sigma0(w0)); + + Round(a, b, c, d, e, f, g, h, 0x27b70a85, w0 += sigma1(w14) + w9 + sigma0(w1)); + Round(h, a, b, c, d, e, f, g, 0x2e1b2138, w1 += sigma1(w15) + w10 + sigma0(w2)); + Round(g, h, a, b, c, d, e, f, 0x4d2c6dfc, w2 += sigma1(w0) + w11 + sigma0(w3)); + Round(f, g, h, a, b, c, d, e, 0x53380d13, w3 += sigma1(w1) + w12 + sigma0(w4)); + Round(e, f, g, h, a, b, c, d, 0x650a7354, w4 += sigma1(w2) + w13 + sigma0(w5)); + Round(d, e, f, g, h, a, b, c, 0x766a0abb, w5 += sigma1(w3) + w14 + sigma0(w6)); + Round(c, d, e, f, g, h, a, b, 0x81c2c92e, w6 += sigma1(w4) + w15 + sigma0(w7)); + Round(b, c, d, e, f, g, h, a, 0x92722c85, w7 += sigma1(w5) + w0 + sigma0(w8)); + Round(a, b, c, d, e, f, g, h, 0xa2bfe8a1, w8 += sigma1(w6) + w1 + sigma0(w9)); + Round(h, a, b, c, d, e, f, g, 0xa81a664b, w9 += sigma1(w7) + w2 + sigma0(w10)); + Round(g, h, a, b, c, d, e, f, 0xc24b8b70, w10 += sigma1(w8) + w3 + sigma0(w11)); + Round(f, g, h, a, b, c, d, e, 0xc76c51a3, w11 += sigma1(w9) + w4 + sigma0(w12)); + Round(e, f, g, h, a, b, c, d, 0xd192e819, w12 += sigma1(w10) + w5 + sigma0(w13)); + Round(d, e, f, g, h, a, b, c, 0xd6990624, w13 += sigma1(w11) + w6 + sigma0(w14)); + Round(c, d, e, f, g, h, a, b, 0xf40e3585, w14 += sigma1(w12) + w7 + sigma0(w15)); + Round(b, c, d, e, f, g, h, a, 0x106aa070, w15 += sigma1(w13) + w8 + sigma0(w0)); + + Round(a, b, c, d, e, f, g, h, 0x19a4c116, w0 += sigma1(w14) + w9 + sigma0(w1)); + Round(h, a, b, c, d, e, f, g, 0x1e376c08, w1 += sigma1(w15) + w10 + sigma0(w2)); + Round(g, h, a, b, c, d, e, f, 0x2748774c, w2 += sigma1(w0) + w11 + sigma0(w3)); + Round(f, g, h, a, b, c, d, e, 0x34b0bcb5, w3 += sigma1(w1) + w12 + sigma0(w4)); + Round(e, f, g, h, a, b, c, d, 0x391c0cb3, w4 += sigma1(w2) + w13 + sigma0(w5)); + Round(d, e, f, g, h, a, b, c, 0x4ed8aa4a, w5 += sigma1(w3) + w14 + sigma0(w6)); + Round(c, d, e, f, g, h, a, b, 0x5b9cca4f, w6 += sigma1(w4) + w15 + sigma0(w7)); + Round(b, c, d, e, f, g, h, a, 0x682e6ff3, w7 += sigma1(w5) + w0 + sigma0(w8)); + Round(a, b, c, d, e, f, g, h, 0x748f82ee, w8 += sigma1(w6) + w1 + sigma0(w9)); + Round(h, a, b, c, d, e, f, g, 0x78a5636f, w9 += sigma1(w7) + w2 + sigma0(w10)); + Round(g, h, a, b, c, d, e, f, 0x84c87814, w10 += sigma1(w8) + w3 + sigma0(w11)); + Round(f, g, h, a, b, c, d, e, 0x8cc70208, w11 += sigma1(w9) + w4 + sigma0(w12)); + Round(e, f, g, h, a, b, c, d, 0x90befffa, w12 += sigma1(w10) + w5 + sigma0(w13)); + Round(d, e, f, g, h, a, b, c, 0xa4506ceb, w13 += sigma1(w11) + w6 + sigma0(w14)); + Round(c, d, e, f, g, h, a, b, 0xbef9a3f7, w14 + sigma1(w12) + w7 + sigma0(w15)); + Round(b, c, d, e, f, g, h, a, 0xc67178f2, w15 + sigma1(w13) + w8 + sigma0(w0)); + + s[0] += a; + s[1] += b; + s[2] += c; + s[3] += d; + s[4] += e; + s[5] += f; + s[6] += g; + s[7] += h; +} + +static void secp256k1_sha256_write(secp256k1_sha256_t *hash, const unsigned char *data, size_t len) { + size_t bufsize = hash->bytes & 0x3F; + hash->bytes += len; + while (bufsize + len >= 64) { + /* Fill the buffer, and process it. */ + memcpy(((unsigned char*)hash->buf) + bufsize, data, 64 - bufsize); + data += 64 - bufsize; + len -= 64 - bufsize; + secp256k1_sha256_transform(hash->s, hash->buf); + bufsize = 0; + } + if (len) { + /* Fill the buffer with what remains. */ + memcpy(((unsigned char*)hash->buf) + bufsize, data, len); + } +} + +static void secp256k1_sha256_finalize(secp256k1_sha256_t *hash, unsigned char *out32) { + static const unsigned char pad[64] = {0x80, 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, 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, 0}; + uint32_t sizedesc[2]; + uint32_t out[8]; + int i = 0; + sizedesc[0] = BE32(hash->bytes >> 29); + sizedesc[1] = BE32(hash->bytes << 3); + secp256k1_sha256_write(hash, pad, 1 + ((119 - (hash->bytes % 64)) % 64)); + secp256k1_sha256_write(hash, (const unsigned char*)sizedesc, 8); + for (i = 0; i < 8; i++) { + out[i] = BE32(hash->s[i]); + hash->s[i] = 0; + } + memcpy(out32, (const unsigned char*)out, 32); +} + +static void secp256k1_hmac_sha256_initialize(secp256k1_hmac_sha256_t *hash, const unsigned char *key, size_t keylen) { + int n; + unsigned char rkey[64]; + if (keylen <= 64) { + memcpy(rkey, key, keylen); + memset(rkey + keylen, 0, 64 - keylen); + } else { + secp256k1_sha256_t sha256; + secp256k1_sha256_initialize(&sha256); + secp256k1_sha256_write(&sha256, key, keylen); + secp256k1_sha256_finalize(&sha256, rkey); + memset(rkey + 32, 0, 32); + } + + secp256k1_sha256_initialize(&hash->outer); + for (n = 0; n < 64; n++) { + rkey[n] ^= 0x5c; + } + secp256k1_sha256_write(&hash->outer, rkey, 64); + + secp256k1_sha256_initialize(&hash->inner); + for (n = 0; n < 64; n++) { + rkey[n] ^= 0x5c ^ 0x36; + } + secp256k1_sha256_write(&hash->inner, rkey, 64); + memset(rkey, 0, 64); +} + +static void secp256k1_hmac_sha256_write(secp256k1_hmac_sha256_t *hash, const unsigned char *data, size_t size) { + secp256k1_sha256_write(&hash->inner, data, size); +} + +static void secp256k1_hmac_sha256_finalize(secp256k1_hmac_sha256_t *hash, unsigned char *out32) { + unsigned char temp[32]; + secp256k1_sha256_finalize(&hash->inner, temp); + secp256k1_sha256_write(&hash->outer, temp, 32); + memset(temp, 0, 32); + secp256k1_sha256_finalize(&hash->outer, out32); +} + + +static void secp256k1_rfc6979_hmac_sha256_initialize(secp256k1_rfc6979_hmac_sha256_t *rng, const unsigned char *key, size_t keylen) { + secp256k1_hmac_sha256_t hmac; + static const unsigned char zero[1] = {0x00}; + static const unsigned char one[1] = {0x01}; + + memset(rng->v, 0x01, 32); /* RFC6979 3.2.b. */ + memset(rng->k, 0x00, 32); /* RFC6979 3.2.c. */ + + /* RFC6979 3.2.d. */ + secp256k1_hmac_sha256_initialize(&hmac, rng->k, 32); + secp256k1_hmac_sha256_write(&hmac, rng->v, 32); + secp256k1_hmac_sha256_write(&hmac, zero, 1); + secp256k1_hmac_sha256_write(&hmac, key, keylen); + secp256k1_hmac_sha256_finalize(&hmac, rng->k); + secp256k1_hmac_sha256_initialize(&hmac, rng->k, 32); + secp256k1_hmac_sha256_write(&hmac, rng->v, 32); + secp256k1_hmac_sha256_finalize(&hmac, rng->v); + + /* RFC6979 3.2.f. */ + secp256k1_hmac_sha256_initialize(&hmac, rng->k, 32); + secp256k1_hmac_sha256_write(&hmac, rng->v, 32); + secp256k1_hmac_sha256_write(&hmac, one, 1); + secp256k1_hmac_sha256_write(&hmac, key, keylen); + secp256k1_hmac_sha256_finalize(&hmac, rng->k); + secp256k1_hmac_sha256_initialize(&hmac, rng->k, 32); + secp256k1_hmac_sha256_write(&hmac, rng->v, 32); + secp256k1_hmac_sha256_finalize(&hmac, rng->v); + rng->retry = 0; +} + +static void secp256k1_rfc6979_hmac_sha256_generate(secp256k1_rfc6979_hmac_sha256_t *rng, unsigned char *out, size_t outlen) { + /* RFC6979 3.2.h. */ + static const unsigned char zero[1] = {0x00}; + if (rng->retry) { + secp256k1_hmac_sha256_t hmac; + secp256k1_hmac_sha256_initialize(&hmac, rng->k, 32); + secp256k1_hmac_sha256_write(&hmac, rng->v, 32); + secp256k1_hmac_sha256_write(&hmac, zero, 1); + secp256k1_hmac_sha256_finalize(&hmac, rng->k); + secp256k1_hmac_sha256_initialize(&hmac, rng->k, 32); + secp256k1_hmac_sha256_write(&hmac, rng->v, 32); + secp256k1_hmac_sha256_finalize(&hmac, rng->v); + } + + while (outlen > 0) { + secp256k1_hmac_sha256_t hmac; + int now = outlen; + secp256k1_hmac_sha256_initialize(&hmac, rng->k, 32); + secp256k1_hmac_sha256_write(&hmac, rng->v, 32); + secp256k1_hmac_sha256_finalize(&hmac, rng->v); + if (now > 32) { + now = 32; + } + memcpy(out, rng->v, now); + out += now; + outlen -= now; + } + + rng->retry = 1; +} + +static void secp256k1_rfc6979_hmac_sha256_finalize(secp256k1_rfc6979_hmac_sha256_t *rng) { + memset(rng->k, 0, 32); + memset(rng->v, 0, 32); + rng->retry = 0; +} + +#undef BE32 +#undef Round +#undef sigma1 +#undef sigma0 +#undef Sigma1 +#undef Sigma0 +#undef Maj +#undef Ch + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1.java b/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1.java new file mode 100644 index 0000000..1c67802 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1.java @@ -0,0 +1,446 @@ +/* + * Copyright 2013 Google Inc. + * Copyright 2014-2016 the libsecp256k1 contributors + * + * 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. + */ + +package org.bitcoin; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import java.math.BigInteger; +import com.google.common.base.Preconditions; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import static org.bitcoin.NativeSecp256k1Util.*; + +/** + *

This class holds native methods to handle ECDSA verification.

+ * + *

You can find an example library that can be used for this at https://github.com/bitcoin/secp256k1

+ * + *

To build secp256k1 for use with bitcoinj, run + * `./configure --enable-jni --enable-experimental --enable-module-ecdh` + * and `make` then copy `.libs/libsecp256k1.so` to your system library path + * or point the JVM to the folder containing it with -Djava.library.path + *

+ */ +public class NativeSecp256k1 { + + private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + private static final Lock r = rwl.readLock(); + private static final Lock w = rwl.writeLock(); + private static ThreadLocal nativeECDSABuffer = new ThreadLocal(); + /** + * Verifies the given secp256k1 signature in native code. + * Calling when enabled == false is undefined (probably library not loaded) + * + * @param data The data which was signed, must be exactly 32 bytes + * @param signature The signature + * @param pub The public key which did the signing + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) throws AssertFailException{ + Preconditions.checkArgument(data.length == 32 && signature.length <= 520 && pub.length <= 520); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < 520) { + byteBuff = ByteBuffer.allocateDirect(520); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(data); + byteBuff.put(signature); + byteBuff.put(pub); + + byte[][] retByteArray; + + r.lock(); + try { + return secp256k1_ecdsa_verify(byteBuff, Secp256k1Context.getContext(), signature.length, pub.length) == 1; + } finally { + r.unlock(); + } + } + + /** + * libsecp256k1 Create an ECDSA signature. + * + * @param data Message hash, 32 bytes + * @param key Secret key, 32 bytes + * + * Return values + * @param sig byte array of signature + */ + public static byte[] sign(byte[] data, byte[] sec) throws AssertFailException{ + Preconditions.checkArgument(data.length == 32 && sec.length <= 32); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < 32 + 32) { + byteBuff = ByteBuffer.allocateDirect(32 + 32); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(data); + byteBuff.put(sec); + + byte[][] retByteArray; + + r.lock(); + try { + retByteArray = secp256k1_ecdsa_sign(byteBuff, Secp256k1Context.getContext()); + } finally { + r.unlock(); + } + + byte[] sigArr = retByteArray[0]; + int sigLen = new BigInteger(new byte[] { retByteArray[1][0] }).intValue(); + int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); + + assertEquals(sigArr.length, sigLen, "Got bad signature length."); + + return retVal == 0 ? new byte[0] : sigArr; + } + + /** + * libsecp256k1 Seckey Verify - returns 1 if valid, 0 if invalid + * + * @param seckey ECDSA Secret key, 32 bytes + */ + public static boolean secKeyVerify(byte[] seckey) { + Preconditions.checkArgument(seckey.length == 32); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < seckey.length) { + byteBuff = ByteBuffer.allocateDirect(seckey.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(seckey); + + r.lock(); + try { + return secp256k1_ec_seckey_verify(byteBuff,Secp256k1Context.getContext()) == 1; + } finally { + r.unlock(); + } + } + + + /** + * libsecp256k1 Compute Pubkey - computes public key from secret key + * + * @param seckey ECDSA Secret key, 32 bytes + * + * Return values + * @param pubkey ECDSA Public key, 33 or 65 bytes + */ + //TODO add a 'compressed' arg + public static byte[] computePubkey(byte[] seckey) throws AssertFailException{ + Preconditions.checkArgument(seckey.length == 32); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < seckey.length) { + byteBuff = ByteBuffer.allocateDirect(seckey.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(seckey); + + byte[][] retByteArray; + + r.lock(); + try { + retByteArray = secp256k1_ec_pubkey_create(byteBuff, Secp256k1Context.getContext()); + } finally { + r.unlock(); + } + + byte[] pubArr = retByteArray[0]; + int pubLen = new BigInteger(new byte[] { retByteArray[1][0] }).intValue(); + int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); + + assertEquals(pubArr.length, pubLen, "Got bad pubkey length."); + + return retVal == 0 ? new byte[0]: pubArr; + } + + /** + * libsecp256k1 Cleanup - This destroys the secp256k1 context object + * This should be called at the end of the program for proper cleanup of the context. + */ + public static synchronized void cleanup() { + w.lock(); + try { + secp256k1_destroy_context(Secp256k1Context.getContext()); + } finally { + w.unlock(); + } + } + + public static long cloneContext() { + r.lock(); + try { + return secp256k1_ctx_clone(Secp256k1Context.getContext()); + } finally { r.unlock(); } + } + + /** + * libsecp256k1 PrivKey Tweak-Mul - Tweak privkey by multiplying to it + * + * @param tweak some bytes to tweak with + * @param seckey 32-byte seckey + */ + public static byte[] privKeyTweakMul(byte[] privkey, byte[] tweak) throws AssertFailException{ + Preconditions.checkArgument(privkey.length == 32); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) { + byteBuff = ByteBuffer.allocateDirect(privkey.length + tweak.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(privkey); + byteBuff.put(tweak); + + byte[][] retByteArray; + r.lock(); + try { + retByteArray = secp256k1_privkey_tweak_mul(byteBuff,Secp256k1Context.getContext()); + } finally { + r.unlock(); + } + + byte[] privArr = retByteArray[0]; + + int privLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; + int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); + + assertEquals(privArr.length, privLen, "Got bad pubkey length."); + + assertEquals(retVal, 1, "Failed return value check."); + + return privArr; + } + + /** + * libsecp256k1 PrivKey Tweak-Add - Tweak privkey by adding to it + * + * @param tweak some bytes to tweak with + * @param seckey 32-byte seckey + */ + public static byte[] privKeyTweakAdd(byte[] privkey, byte[] tweak) throws AssertFailException{ + Preconditions.checkArgument(privkey.length == 32); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < privkey.length + tweak.length) { + byteBuff = ByteBuffer.allocateDirect(privkey.length + tweak.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(privkey); + byteBuff.put(tweak); + + byte[][] retByteArray; + r.lock(); + try { + retByteArray = secp256k1_privkey_tweak_add(byteBuff,Secp256k1Context.getContext()); + } finally { + r.unlock(); + } + + byte[] privArr = retByteArray[0]; + + int privLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; + int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); + + assertEquals(privArr.length, privLen, "Got bad pubkey length."); + + assertEquals(retVal, 1, "Failed return value check."); + + return privArr; + } + + /** + * libsecp256k1 PubKey Tweak-Add - Tweak pubkey by adding to it + * + * @param tweak some bytes to tweak with + * @param pubkey 32-byte seckey + */ + public static byte[] pubKeyTweakAdd(byte[] pubkey, byte[] tweak) throws AssertFailException{ + Preconditions.checkArgument(pubkey.length == 33 || pubkey.length == 65); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) { + byteBuff = ByteBuffer.allocateDirect(pubkey.length + tweak.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(pubkey); + byteBuff.put(tweak); + + byte[][] retByteArray; + r.lock(); + try { + retByteArray = secp256k1_pubkey_tweak_add(byteBuff,Secp256k1Context.getContext(), pubkey.length); + } finally { + r.unlock(); + } + + byte[] pubArr = retByteArray[0]; + + int pubLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; + int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); + + assertEquals(pubArr.length, pubLen, "Got bad pubkey length."); + + assertEquals(retVal, 1, "Failed return value check."); + + return pubArr; + } + + /** + * libsecp256k1 PubKey Tweak-Mul - Tweak pubkey by multiplying to it + * + * @param tweak some bytes to tweak with + * @param pubkey 32-byte seckey + */ + public static byte[] pubKeyTweakMul(byte[] pubkey, byte[] tweak) throws AssertFailException{ + Preconditions.checkArgument(pubkey.length == 33 || pubkey.length == 65); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < pubkey.length + tweak.length) { + byteBuff = ByteBuffer.allocateDirect(pubkey.length + tweak.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(pubkey); + byteBuff.put(tweak); + + byte[][] retByteArray; + r.lock(); + try { + retByteArray = secp256k1_pubkey_tweak_mul(byteBuff,Secp256k1Context.getContext(), pubkey.length); + } finally { + r.unlock(); + } + + byte[] pubArr = retByteArray[0]; + + int pubLen = (byte) new BigInteger(new byte[] { retByteArray[1][0] }).intValue() & 0xFF; + int retVal = new BigInteger(new byte[] { retByteArray[1][1] }).intValue(); + + assertEquals(pubArr.length, pubLen, "Got bad pubkey length."); + + assertEquals(retVal, 1, "Failed return value check."); + + return pubArr; + } + + /** + * libsecp256k1 create ECDH secret - constant time ECDH calculation + * + * @param seckey byte array of secret key used in exponentiaion + * @param pubkey byte array of public key used in exponentiaion + */ + public static byte[] createECDHSecret(byte[] seckey, byte[] pubkey) throws AssertFailException{ + Preconditions.checkArgument(seckey.length <= 32 && pubkey.length <= 65); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < 32 + pubkey.length) { + byteBuff = ByteBuffer.allocateDirect(32 + pubkey.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(seckey); + byteBuff.put(pubkey); + + byte[][] retByteArray; + r.lock(); + try { + retByteArray = secp256k1_ecdh(byteBuff, Secp256k1Context.getContext(), pubkey.length); + } finally { + r.unlock(); + } + + byte[] resArr = retByteArray[0]; + int retVal = new BigInteger(new byte[] { retByteArray[1][0] }).intValue(); + + assertEquals(resArr.length, 32, "Got bad result length."); + assertEquals(retVal, 1, "Failed return value check."); + + return resArr; + } + + /** + * libsecp256k1 randomize - updates the context randomization + * + * @param seed 32-byte random seed + */ + public static synchronized boolean randomize(byte[] seed) throws AssertFailException{ + Preconditions.checkArgument(seed.length == 32 || seed == null); + + ByteBuffer byteBuff = nativeECDSABuffer.get(); + if (byteBuff == null || byteBuff.capacity() < seed.length) { + byteBuff = ByteBuffer.allocateDirect(seed.length); + byteBuff.order(ByteOrder.nativeOrder()); + nativeECDSABuffer.set(byteBuff); + } + byteBuff.rewind(); + byteBuff.put(seed); + + w.lock(); + try { + return secp256k1_context_randomize(byteBuff, Secp256k1Context.getContext()) == 1; + } finally { + w.unlock(); + } + } + + private static native long secp256k1_ctx_clone(long context); + + private static native int secp256k1_context_randomize(ByteBuffer byteBuff, long context); + + private static native byte[][] secp256k1_privkey_tweak_add(ByteBuffer byteBuff, long context); + + private static native byte[][] secp256k1_privkey_tweak_mul(ByteBuffer byteBuff, long context); + + private static native byte[][] secp256k1_pubkey_tweak_add(ByteBuffer byteBuff, long context, int pubLen); + + private static native byte[][] secp256k1_pubkey_tweak_mul(ByteBuffer byteBuff, long context, int pubLen); + + private static native void secp256k1_destroy_context(long context); + + private static native int secp256k1_ecdsa_verify(ByteBuffer byteBuff, long context, int sigLen, int pubLen); + + private static native byte[][] secp256k1_ecdsa_sign(ByteBuffer byteBuff, long context); + + private static native int secp256k1_ec_seckey_verify(ByteBuffer byteBuff, long context); + + private static native byte[][] secp256k1_ec_pubkey_create(ByteBuffer byteBuff, long context); + + private static native byte[][] secp256k1_ec_pubkey_parse(ByteBuffer byteBuff, long context, int inputLen); + + private static native byte[][] secp256k1_ecdh(ByteBuffer byteBuff, long context, int inputLen); + +} diff --git a/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java b/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java new file mode 100644 index 0000000..c00d088 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1Test.java @@ -0,0 +1,226 @@ +package org.bitcoin; + +import com.google.common.io.BaseEncoding; +import java.util.Arrays; +import java.math.BigInteger; +import javax.xml.bind.DatatypeConverter; +import static org.bitcoin.NativeSecp256k1Util.*; + +/** + * This class holds test cases defined for testing this library. + */ +public class NativeSecp256k1Test { + + //TODO improve comments/add more tests + /** + * This tests verify() for a valid signature + */ + public static void testVerifyPos() throws AssertFailException{ + boolean result = false; + byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" + byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()); + byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + + result = NativeSecp256k1.verify( data, sig, pub); + assertEquals( result, true , "testVerifyPos"); + } + + /** + * This tests verify() for a non-valid signature + */ + public static void testVerifyNeg() throws AssertFailException{ + boolean result = false; + byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A91".toLowerCase()); //sha256hash of "testing" + byte[] sig = BaseEncoding.base16().lowerCase().decode("3044022079BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980220294F14E883B3F525B5367756C2A11EF6CF84B730B36C17CB0C56F0AAB2C98589".toLowerCase()); + byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + + result = NativeSecp256k1.verify( data, sig, pub); + //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); + assertEquals( result, false , "testVerifyNeg"); + } + + /** + * This tests secret key verify() for a valid secretkey + */ + public static void testSecKeyVerifyPos() throws AssertFailException{ + boolean result = false; + byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + + result = NativeSecp256k1.secKeyVerify( sec ); + //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); + assertEquals( result, true , "testSecKeyVerifyPos"); + } + + /** + * This tests secret key verify() for a invalid secretkey + */ + public static void testSecKeyVerifyNeg() throws AssertFailException{ + boolean result = false; + byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + + result = NativeSecp256k1.secKeyVerify( sec ); + //System.out.println(" TEST " + new BigInteger(1, resultbytes).toString(16)); + assertEquals( result, false , "testSecKeyVerifyNeg"); + } + + /** + * This tests public key create() for a valid secretkey + */ + public static void testPubKeyCreatePos() throws AssertFailException{ + byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + + byte[] resultArr = NativeSecp256k1.computePubkey( sec); + String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( pubkeyString , "04C591A8FF19AC9C4E4E5793673B83123437E975285E7B442F4EE2654DFFCA5E2D2103ED494718C697AC9AEBCFD19612E224DB46661011863ED2FC54E71861E2A6" , "testPubKeyCreatePos"); + } + + /** + * This tests public key create() for a invalid secretkey + */ + public static void testPubKeyCreateNeg() throws AssertFailException{ + byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + + byte[] resultArr = NativeSecp256k1.computePubkey( sec); + String pubkeyString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( pubkeyString, "" , "testPubKeyCreateNeg"); + } + + /** + * This tests sign() for a valid secretkey + */ + public static void testSignPos() throws AssertFailException{ + + byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" + byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + + byte[] resultArr = NativeSecp256k1.sign(data, sec); + String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( sigString, "30440220182A108E1448DC8F1FB467D06A0F3BB8EA0533584CB954EF8DA112F1D60E39A202201C66F36DA211C087F3AF88B50EDF4F9BDAA6CF5FD6817E74DCA34DB12390C6E9" , "testSignPos"); + } + + /** + * This tests sign() for a invalid secretkey + */ + public static void testSignNeg() throws AssertFailException{ + byte[] data = BaseEncoding.base16().lowerCase().decode("CF80CD8AED482D5D1527D7DC72FCEFF84E6326592848447D2DC0B0E87DFC9A90".toLowerCase()); //sha256hash of "testing" + byte[] sec = BaseEncoding.base16().lowerCase().decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase()); + + byte[] resultArr = NativeSecp256k1.sign(data, sec); + String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( sigString, "" , "testSignNeg"); + } + + /** + * This tests private key tweak-add + */ + public static void testPrivKeyTweakAdd_1() throws AssertFailException { + byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + + byte[] resultArr = NativeSecp256k1.privKeyTweakAdd( sec , data ); + String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( sigString , "A168571E189E6F9A7E2D657A4B53AE99B909F7E712D1C23CED28093CD57C88F3" , "testPrivKeyAdd_1"); + } + + /** + * This tests private key tweak-mul + */ + public static void testPrivKeyTweakMul_1() throws AssertFailException { + byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + + byte[] resultArr = NativeSecp256k1.privKeyTweakMul( sec , data ); + String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( sigString , "97F8184235F101550F3C71C927507651BD3F1CDB4A5A33B8986ACF0DEE20FFFC" , "testPrivKeyMul_1"); + } + + /** + * This tests private key tweak-add uncompressed + */ + public static void testPrivKeyTweakAdd_2() throws AssertFailException { + byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + + byte[] resultArr = NativeSecp256k1.pubKeyTweakAdd( pub , data ); + String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( sigString , "0411C6790F4B663CCE607BAAE08C43557EDC1A4D11D88DFCB3D841D0C6A941AF525A268E2A863C148555C48FB5FBA368E88718A46E205FABC3DBA2CCFFAB0796EF" , "testPrivKeyAdd_2"); + } + + /** + * This tests private key tweak-mul uncompressed + */ + public static void testPrivKeyTweakMul_2() throws AssertFailException { + byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + byte[] data = BaseEncoding.base16().lowerCase().decode("3982F19BEF1615BCCFBB05E321C10E1D4CBA3DF0E841C2E41EEB6016347653C3".toLowerCase()); //sha256hash of "tweak" + + byte[] resultArr = NativeSecp256k1.pubKeyTweakMul( pub , data ); + String sigString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( sigString , "04E0FE6FE55EBCA626B98A807F6CAF654139E14E5E3698F01A9A658E21DC1D2791EC060D4F412A794D5370F672BC94B722640B5F76914151CFCA6E712CA48CC589" , "testPrivKeyMul_2"); + } + + /** + * This tests seed randomization + */ + public static void testRandomize() throws AssertFailException { + byte[] seed = BaseEncoding.base16().lowerCase().decode("A441B15FE9A3CF56661190A0B93B9DEC7D04127288CC87250967CF3B52894D11".toLowerCase()); //sha256hash of "random" + boolean result = NativeSecp256k1.randomize(seed); + assertEquals( result, true, "testRandomize"); + } + + public static void testCreateECDHSecret() throws AssertFailException{ + + byte[] sec = BaseEncoding.base16().lowerCase().decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".toLowerCase()); + byte[] pub = BaseEncoding.base16().lowerCase().decode("040A629506E1B65CD9D2E0BA9C75DF9C4FED0DB16DC9625ED14397F0AFC836FAE595DC53F8B0EFE61E703075BD9B143BAC75EC0E19F82A2208CAEB32BE53414C40".toLowerCase()); + + byte[] resultArr = NativeSecp256k1.createECDHSecret(sec, pub); + String ecdhString = javax.xml.bind.DatatypeConverter.printHexBinary(resultArr); + assertEquals( ecdhString, "2A2A67007A926E6594AF3EB564FC74005B37A9C8AEF2033C4552051B5C87F043" , "testCreateECDHSecret"); + } + + public static void main(String[] args) throws AssertFailException{ + + + System.out.println("\n libsecp256k1 enabled: " + Secp256k1Context.isEnabled() + "\n"); + + assertEquals( Secp256k1Context.isEnabled(), true, "isEnabled" ); + + //Test verify() success/fail + testVerifyPos(); + testVerifyNeg(); + + //Test secKeyVerify() success/fail + testSecKeyVerifyPos(); + testSecKeyVerifyNeg(); + + //Test computePubkey() success/fail + testPubKeyCreatePos(); + testPubKeyCreateNeg(); + + //Test sign() success/fail + testSignPos(); + testSignNeg(); + + //Test privKeyTweakAdd() 1 + testPrivKeyTweakAdd_1(); + + //Test privKeyTweakMul() 2 + testPrivKeyTweakMul_1(); + + //Test privKeyTweakAdd() 3 + testPrivKeyTweakAdd_2(); + + //Test privKeyTweakMul() 4 + testPrivKeyTweakMul_2(); + + //Test randomize() + testRandomize(); + + //Test ECDH + testCreateECDHSecret(); + + NativeSecp256k1.cleanup(); + + System.out.println(" All tests passed." ); + + } +} diff --git a/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java b/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java new file mode 100644 index 0000000..04732ba --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/NativeSecp256k1Util.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014-2016 the libsecp256k1 contributors + * + * 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. + */ + +package org.bitcoin; + +public class NativeSecp256k1Util{ + + public static void assertEquals( int val, int val2, String message ) throws AssertFailException{ + if( val != val2 ) + throw new AssertFailException("FAIL: " + message); + } + + public static void assertEquals( boolean val, boolean val2, String message ) throws AssertFailException{ + if( val != val2 ) + throw new AssertFailException("FAIL: " + message); + else + System.out.println("PASS: " + message); + } + + public static void assertEquals( String val, String val2, String message ) throws AssertFailException{ + if( !val.equals(val2) ) + throw new AssertFailException("FAIL: " + message); + else + System.out.println("PASS: " + message); + } + + public static class AssertFailException extends Exception { + public AssertFailException(String message) { + super( message ); + } + } +} diff --git a/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/Secp256k1Context.java b/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/Secp256k1Context.java new file mode 100644 index 0000000..216c986 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/java/org/bitcoin/Secp256k1Context.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014-2016 the libsecp256k1 contributors + * + * 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. + */ + +package org.bitcoin; + +/** + * This class holds the context reference used in native methods + * to handle ECDSA operations. + */ +public class Secp256k1Context { + private static final boolean enabled; //true if the library is loaded + private static final long context; //ref to pointer to context obj + + static { //static initializer + boolean isEnabled = true; + long contextRef = -1; + try { + System.loadLibrary("secp256k1"); + contextRef = secp256k1_init_context(); + } catch (UnsatisfiedLinkError e) { + System.out.println("UnsatisfiedLinkError: " + e.toString()); + isEnabled = false; + } + enabled = isEnabled; + context = contextRef; + } + + public static boolean isEnabled() { + return enabled; + } + + public static long getContext() { + if(!enabled) return -1; //sanity check + return context; + } + + private static native long secp256k1_init_context(); +} diff --git a/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_NativeSecp256k1.c b/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_NativeSecp256k1.c new file mode 100644 index 0000000..bcef7b3 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_NativeSecp256k1.c @@ -0,0 +1,377 @@ +#include +#include +#include +#include "org_bitcoin_NativeSecp256k1.h" +#include "include/secp256k1.h" +#include "include/secp256k1_ecdh.h" +#include "include/secp256k1_recovery.h" + + +SECP256K1_API jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ctx_1clone + (JNIEnv* env, jclass classObject, jlong ctx_l) +{ + const secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + + jlong ctx_clone_l = (uintptr_t) secp256k1_context_clone(ctx); + + (void)classObject;(void)env; + + return ctx_clone_l; + +} + +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1context_1randomize + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + + const unsigned char* seed = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); + + (void)classObject; + + return secp256k1_context_randomize(ctx, seed); + +} + +SECP256K1_API void JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1destroy_1context + (JNIEnv* env, jclass classObject, jlong ctx_l) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + + secp256k1_context_destroy(ctx); + + (void)classObject;(void)env; +} + +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1verify + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint siglen, jint publen) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + + unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); + const unsigned char* sigdata = { (unsigned char*) (data + 32) }; + const unsigned char* pubdata = { (unsigned char*) (data + siglen + 32) }; + + secp256k1_ecdsa_signature sig; + secp256k1_pubkey pubkey; + + int ret = secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigdata, siglen); + + if( ret ) { + ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pubdata, publen); + + if( ret ) { + ret = secp256k1_ecdsa_verify(ctx, &sig, data, &pubkey); + } + } + + (void)classObject; + + return ret; +} + +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + unsigned char* data = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); + unsigned char* secKey = (unsigned char*) (data + 32); + + jobjectArray retArray; + jbyteArray sigArray, intsByteArray; + unsigned char intsarray[2]; + + secp256k1_ecdsa_signature sig[72]; + + int ret = secp256k1_ecdsa_sign(ctx, sig, data, secKey, NULL, NULL ); + + unsigned char outputSer[72]; + size_t outputLen = 72; + + if( ret ) { + int ret2 = secp256k1_ecdsa_signature_serialize_der(ctx,outputSer, &outputLen, sig ); (void)ret2; + } + + intsarray[0] = outputLen; + intsarray[1] = ret; + + retArray = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), + (*env)->NewByteArray(env, 1)); + + sigArray = (*env)->NewByteArray(env, outputLen); + (*env)->SetByteArrayRegion(env, sigArray, 0, outputLen, (jbyte*)outputSer); + (*env)->SetObjectArrayElement(env, retArray, 0, sigArray); + + intsByteArray = (*env)->NewByteArray(env, 2); + (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); + (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); + + (void)classObject; + + return retArray; +} + +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1seckey_1verify + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + unsigned char* secKey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); + + (void)classObject; + + return secp256k1_ec_seckey_verify(ctx, secKey); +} + +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1create + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + const unsigned char* secKey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); + + secp256k1_pubkey pubkey; + + jobjectArray retArray; + jbyteArray pubkeyArray, intsByteArray; + unsigned char intsarray[2]; + + int ret = secp256k1_ec_pubkey_create(ctx, &pubkey, secKey); + + unsigned char outputSer[65]; + size_t outputLen = 65; + + if( ret ) { + int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; + } + + intsarray[0] = outputLen; + intsarray[1] = ret; + + retArray = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), + (*env)->NewByteArray(env, 1)); + + pubkeyArray = (*env)->NewByteArray(env, outputLen); + (*env)->SetByteArrayRegion(env, pubkeyArray, 0, outputLen, (jbyte*)outputSer); + (*env)->SetObjectArrayElement(env, retArray, 0, pubkeyArray); + + intsByteArray = (*env)->NewByteArray(env, 2); + (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); + (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); + + (void)classObject; + + return retArray; + +} + +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1add + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + unsigned char* privkey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); + const unsigned char* tweak = (unsigned char*) (privkey + 32); + + jobjectArray retArray; + jbyteArray privArray, intsByteArray; + unsigned char intsarray[2]; + + int privkeylen = 32; + + int ret = secp256k1_ec_privkey_tweak_add(ctx, privkey, tweak); + + intsarray[0] = privkeylen; + intsarray[1] = ret; + + retArray = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), + (*env)->NewByteArray(env, 1)); + + privArray = (*env)->NewByteArray(env, privkeylen); + (*env)->SetByteArrayRegion(env, privArray, 0, privkeylen, (jbyte*)privkey); + (*env)->SetObjectArrayElement(env, retArray, 0, privArray); + + intsByteArray = (*env)->NewByteArray(env, 2); + (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); + (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); + + (void)classObject; + + return retArray; +} + +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1mul + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + unsigned char* privkey = (unsigned char*) (*env)->GetDirectBufferAddress(env, byteBufferObject); + const unsigned char* tweak = (unsigned char*) (privkey + 32); + + jobjectArray retArray; + jbyteArray privArray, intsByteArray; + unsigned char intsarray[2]; + + int privkeylen = 32; + + int ret = secp256k1_ec_privkey_tweak_mul(ctx, privkey, tweak); + + intsarray[0] = privkeylen; + intsarray[1] = ret; + + retArray = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), + (*env)->NewByteArray(env, 1)); + + privArray = (*env)->NewByteArray(env, privkeylen); + (*env)->SetByteArrayRegion(env, privArray, 0, privkeylen, (jbyte*)privkey); + (*env)->SetObjectArrayElement(env, retArray, 0, privArray); + + intsByteArray = (*env)->NewByteArray(env, 2); + (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); + (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); + + (void)classObject; + + return retArray; +} + +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; +/* secp256k1_pubkey* pubkey = (secp256k1_pubkey*) (*env)->GetDirectBufferAddress(env, byteBufferObject);*/ + unsigned char* pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject); + const unsigned char* tweak = (unsigned char*) (pkey + publen); + + jobjectArray retArray; + jbyteArray pubArray, intsByteArray; + unsigned char intsarray[2]; + unsigned char outputSer[65]; + size_t outputLen = 65; + + secp256k1_pubkey pubkey; + int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen); + + if( ret ) { + ret = secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, tweak); + } + + if( ret ) { + int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; + } + + intsarray[0] = outputLen; + intsarray[1] = ret; + + retArray = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), + (*env)->NewByteArray(env, 1)); + + pubArray = (*env)->NewByteArray(env, outputLen); + (*env)->SetByteArrayRegion(env, pubArray, 0, outputLen, (jbyte*)outputSer); + (*env)->SetObjectArrayElement(env, retArray, 0, pubArray); + + intsByteArray = (*env)->NewByteArray(env, 2); + (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); + (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); + + (void)classObject; + + return retArray; +} + +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1mul + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + unsigned char* pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject); + const unsigned char* tweak = (unsigned char*) (pkey + publen); + + jobjectArray retArray; + jbyteArray pubArray, intsByteArray; + unsigned char intsarray[2]; + unsigned char outputSer[65]; + size_t outputLen = 65; + + secp256k1_pubkey pubkey; + int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen); + + if ( ret ) { + ret = secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, tweak); + } + + if( ret ) { + int ret2 = secp256k1_ec_pubkey_serialize(ctx,outputSer, &outputLen, &pubkey,SECP256K1_EC_UNCOMPRESSED );(void)ret2; + } + + intsarray[0] = outputLen; + intsarray[1] = ret; + + retArray = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), + (*env)->NewByteArray(env, 1)); + + pubArray = (*env)->NewByteArray(env, outputLen); + (*env)->SetByteArrayRegion(env, pubArray, 0, outputLen, (jbyte*)outputSer); + (*env)->SetObjectArrayElement(env, retArray, 0, pubArray); + + intsByteArray = (*env)->NewByteArray(env, 2); + (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte*)intsarray); + (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); + + (void)classObject; + + return retArray; +} + +SECP256K1_API jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1pubkey_1combine + (JNIEnv * env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint numkeys) +{ + (void)classObject;(void)env;(void)byteBufferObject;(void)ctx_l;(void)numkeys; + + return 0; +} + +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdh + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) +{ + secp256k1_context *ctx = (secp256k1_context*)(uintptr_t)ctx_l; + const unsigned char* secdata = (*env)->GetDirectBufferAddress(env, byteBufferObject); + const unsigned char* pubdata = (const unsigned char*) (secdata + 32); + + jobjectArray retArray; + jbyteArray outArray, intsByteArray; + unsigned char intsarray[1]; + secp256k1_pubkey pubkey; + unsigned char nonce_res[32]; + size_t outputLen = 32; + + int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pubdata, publen); + + if (ret) { + ret = secp256k1_ecdh( + ctx, + nonce_res, + &pubkey, + secdata + ); + } + + intsarray[0] = ret; + + retArray = (*env)->NewObjectArray(env, 2, + (*env)->FindClass(env, "[B"), + (*env)->NewByteArray(env, 1)); + + outArray = (*env)->NewByteArray(env, outputLen); + (*env)->SetByteArrayRegion(env, outArray, 0, 32, (jbyte*)nonce_res); + (*env)->SetObjectArrayElement(env, retArray, 0, outArray); + + intsByteArray = (*env)->NewByteArray(env, 1); + (*env)->SetByteArrayRegion(env, intsByteArray, 0, 1, (jbyte*)intsarray); + (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray); + + (void)classObject; + + return retArray; +} diff --git a/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_NativeSecp256k1.h b/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_NativeSecp256k1.h new file mode 100644 index 0000000..fe613c9 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_NativeSecp256k1.h @@ -0,0 +1,119 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +#include "include/secp256k1.h" +/* Header for class org_bitcoin_NativeSecp256k1 */ + +#ifndef _Included_org_bitcoin_NativeSecp256k1 +#define _Included_org_bitcoin_NativeSecp256k1 +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_ctx_clone + * Signature: (J)J + */ +SECP256K1_API jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ctx_1clone + (JNIEnv *, jclass, jlong); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_context_randomize + * Signature: (Ljava/nio/ByteBuffer;J)I + */ +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1context_1randomize + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_privkey_tweak_add + * Signature: (Ljava/nio/ByteBuffer;J)[[B + */ +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1add + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_privkey_tweak_mul + * Signature: (Ljava/nio/ByteBuffer;J)[[B + */ +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1mul + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_pubkey_tweak_add + * Signature: (Ljava/nio/ByteBuffer;JI)[[B + */ +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add + (JNIEnv *, jclass, jobject, jlong, jint); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_pubkey_tweak_mul + * Signature: (Ljava/nio/ByteBuffer;JI)[[B + */ +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1mul + (JNIEnv *, jclass, jobject, jlong, jint); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_destroy_context + * Signature: (J)V + */ +SECP256K1_API void JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1destroy_1context + (JNIEnv *, jclass, jlong); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_ecdsa_verify + * Signature: (Ljava/nio/ByteBuffer;JII)I + */ +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1verify + (JNIEnv *, jclass, jobject, jlong, jint, jint); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_ecdsa_sign + * Signature: (Ljava/nio/ByteBuffer;J)[[B + */ +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_ec_seckey_verify + * Signature: (Ljava/nio/ByteBuffer;J)I + */ +SECP256K1_API jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1seckey_1verify + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_ec_pubkey_create + * Signature: (Ljava/nio/ByteBuffer;J)[[B + */ +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1create + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_ec_pubkey_parse + * Signature: (Ljava/nio/ByteBuffer;JI)[[B + */ +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1parse + (JNIEnv *, jclass, jobject, jlong, jint); + +/* + * Class: org_bitcoin_NativeSecp256k1 + * Method: secp256k1_ecdh + * Signature: (Ljava/nio/ByteBuffer;JI)[[B + */ +SECP256K1_API jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdh + (JNIEnv* env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen); + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_Secp256k1Context.c b/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_Secp256k1Context.c new file mode 100644 index 0000000..a52939e --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_Secp256k1Context.c @@ -0,0 +1,15 @@ +#include +#include +#include "org_bitcoin_Secp256k1Context.h" +#include "include/secp256k1.h" + +SECP256K1_API jlong JNICALL Java_org_bitcoin_Secp256k1Context_secp256k1_1init_1context + (JNIEnv* env, jclass classObject) +{ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + + (void)classObject;(void)env; + + return (uintptr_t)ctx; +} + diff --git a/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_Secp256k1Context.h b/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_Secp256k1Context.h new file mode 100644 index 0000000..0d2bc84 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/java/org_bitcoin_Secp256k1Context.h @@ -0,0 +1,22 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +#include "include/secp256k1.h" +/* Header for class org_bitcoin_Secp256k1Context */ + +#ifndef _Included_org_bitcoin_Secp256k1Context +#define _Included_org_bitcoin_Secp256k1Context +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_bitcoin_Secp256k1Context + * Method: secp256k1_init_context + * Signature: ()J + */ +SECP256K1_API jlong JNICALL Java_org_bitcoin_Secp256k1Context_secp256k1_1init_1context + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/modules/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/dummy.go new file mode 100644 index 0000000..99c538d --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/dummy.go @@ -0,0 +1,8 @@ +//go:build dummy +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package module diff --git a/crypto/secp256k1/libsecp256k1/src/modules/ecdh/Makefile.am.include b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/Makefile.am.include new file mode 100644 index 0000000..e3088b4 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/Makefile.am.include @@ -0,0 +1,8 @@ +include_HEADERS += include/secp256k1_ecdh.h +noinst_HEADERS += src/modules/ecdh/main_impl.h +noinst_HEADERS += src/modules/ecdh/tests_impl.h +if USE_BENCHMARK +noinst_PROGRAMS += bench_ecdh +bench_ecdh_SOURCES = src/bench_ecdh.c +bench_ecdh_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB) +endif diff --git a/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go new file mode 100644 index 0000000..48c2e0a --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go @@ -0,0 +1,8 @@ +//go:build dummy +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package ecdh diff --git a/crypto/secp256k1/libsecp256k1/src/modules/ecdh/main_impl.h b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/main_impl.h new file mode 100644 index 0000000..9e30fb7 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/main_impl.h @@ -0,0 +1,54 @@ +/********************************************************************** + * Copyright (c) 2015 Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_MODULE_ECDH_MAIN_ +#define _SECP256K1_MODULE_ECDH_MAIN_ + +#include "include/secp256k1_ecdh.h" +#include "ecmult_const_impl.h" + +int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *result, const secp256k1_pubkey *point, const unsigned char *scalar) { + int ret = 0; + int overflow = 0; + secp256k1_gej res; + secp256k1_ge pt; + secp256k1_scalar s; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(result != NULL); + ARG_CHECK(point != NULL); + ARG_CHECK(scalar != NULL); + + secp256k1_pubkey_load(ctx, &pt, point); + secp256k1_scalar_set_b32(&s, scalar, &overflow); + if (overflow || secp256k1_scalar_is_zero(&s)) { + ret = 0; + } else { + unsigned char x[32]; + unsigned char y[1]; + secp256k1_sha256_t sha; + + secp256k1_ecmult_const(&res, &pt, &s); + secp256k1_ge_set_gej(&pt, &res); + /* Compute a hash of the point in compressed form + * Note we cannot use secp256k1_eckey_pubkey_serialize here since it does not + * expect its output to be secret and has a timing sidechannel. */ + secp256k1_fe_normalize(&pt.x); + secp256k1_fe_normalize(&pt.y); + secp256k1_fe_get_b32(x, &pt.x); + y[0] = 0x02 | secp256k1_fe_is_odd(&pt.y); + + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, y, sizeof(y)); + secp256k1_sha256_write(&sha, x, sizeof(x)); + secp256k1_sha256_finalize(&sha, result); + ret = 1; + } + + secp256k1_scalar_clear(&s); + return ret; +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/modules/ecdh/tests_impl.h b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/tests_impl.h new file mode 100644 index 0000000..85a5d0a --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/tests_impl.h @@ -0,0 +1,105 @@ +/********************************************************************** + * Copyright (c) 2015 Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_MODULE_ECDH_TESTS_ +#define _SECP256K1_MODULE_ECDH_TESTS_ + +void test_ecdh_api(void) { + /* Setup context that just counts errors */ + secp256k1_context *tctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_pubkey point; + unsigned char res[32]; + unsigned char s_one[32] = { 0 }; + int32_t ecount = 0; + s_one[31] = 1; + + secp256k1_context_set_error_callback(tctx, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(tctx, counting_illegal_callback_fn, &ecount); + CHECK(secp256k1_ec_pubkey_create(tctx, &point, s_one) == 1); + + /* Check all NULLs are detected */ + CHECK(secp256k1_ecdh(tctx, res, &point, s_one) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_ecdh(tctx, NULL, &point, s_one) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdh(tctx, res, NULL, s_one) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdh(tctx, res, &point, NULL) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdh(tctx, res, &point, s_one) == 1); + CHECK(ecount == 3); + + /* Cleanup */ + secp256k1_context_destroy(tctx); +} + +void test_ecdh_generator_basepoint(void) { + unsigned char s_one[32] = { 0 }; + secp256k1_pubkey point[2]; + int i; + + s_one[31] = 1; + /* Check against pubkey creation when the basepoint is the generator */ + for (i = 0; i < 100; ++i) { + secp256k1_sha256_t sha; + unsigned char s_b32[32]; + unsigned char output_ecdh[32]; + unsigned char output_ser[32]; + unsigned char point_ser[33]; + size_t point_ser_len = sizeof(point_ser); + secp256k1_scalar s; + + random_scalar_order(&s); + secp256k1_scalar_get_b32(s_b32, &s); + + /* compute using ECDH function */ + CHECK(secp256k1_ec_pubkey_create(ctx, &point[0], s_one) == 1); + CHECK(secp256k1_ecdh(ctx, output_ecdh, &point[0], s_b32) == 1); + /* compute "explicitly" */ + CHECK(secp256k1_ec_pubkey_create(ctx, &point[1], s_b32) == 1); + CHECK(secp256k1_ec_pubkey_serialize(ctx, point_ser, &point_ser_len, &point[1], SECP256K1_EC_COMPRESSED) == 1); + CHECK(point_ser_len == sizeof(point_ser)); + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, point_ser, point_ser_len); + secp256k1_sha256_finalize(&sha, output_ser); + /* compare */ + CHECK(memcmp(output_ecdh, output_ser, sizeof(output_ser)) == 0); + } +} + +void test_bad_scalar(void) { + unsigned char s_zero[32] = { 0 }; + unsigned char s_overflow[32] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41 + }; + unsigned char s_rand[32] = { 0 }; + unsigned char output[32]; + secp256k1_scalar rand; + secp256k1_pubkey point; + + /* Create random point */ + random_scalar_order(&rand); + secp256k1_scalar_get_b32(s_rand, &rand); + CHECK(secp256k1_ec_pubkey_create(ctx, &point, s_rand) == 1); + + /* Try to multiply it by bad values */ + CHECK(secp256k1_ecdh(ctx, output, &point, s_zero) == 0); + CHECK(secp256k1_ecdh(ctx, output, &point, s_overflow) == 0); + /* ...and a good one */ + s_overflow[31] -= 1; + CHECK(secp256k1_ecdh(ctx, output, &point, s_overflow) == 1); +} + +void run_ecdh_tests(void) { + test_ecdh_api(); + test_ecdh_generator_basepoint(); + test_bad_scalar(); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/modules/recovery/Makefile.am.include b/crypto/secp256k1/libsecp256k1/src/modules/recovery/Makefile.am.include new file mode 100644 index 0000000..bf23c26 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/recovery/Makefile.am.include @@ -0,0 +1,8 @@ +include_HEADERS += include/secp256k1_recovery.h +noinst_HEADERS += src/modules/recovery/main_impl.h +noinst_HEADERS += src/modules/recovery/tests_impl.h +if USE_BENCHMARK +noinst_PROGRAMS += bench_recover +bench_recover_SOURCES = src/bench_recover.c +bench_recover_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB) +endif diff --git a/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go new file mode 100644 index 0000000..8efbd7a --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go @@ -0,0 +1,8 @@ +//go:build dummy +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package recovery diff --git a/crypto/secp256k1/libsecp256k1/src/modules/recovery/main_impl.h b/crypto/secp256k1/libsecp256k1/src/modules/recovery/main_impl.h new file mode 100755 index 0000000..c6fbe23 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/recovery/main_impl.h @@ -0,0 +1,193 @@ +/********************************************************************** + * Copyright (c) 2013-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_MODULE_RECOVERY_MAIN_ +#define _SECP256K1_MODULE_RECOVERY_MAIN_ + +#include "include/secp256k1_recovery.h" + +static void secp256k1_ecdsa_recoverable_signature_load(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, const secp256k1_ecdsa_recoverable_signature* sig) { + (void)ctx; + if (sizeof(secp256k1_scalar) == 32) { + /* When the secp256k1_scalar type is exactly 32 byte, use its + * representation inside secp256k1_ecdsa_signature, as conversion is very fast. + * Note that secp256k1_ecdsa_signature_save must use the same representation. */ + memcpy(r, &sig->data[0], 32); + memcpy(s, &sig->data[32], 32); + } else { + secp256k1_scalar_set_b32(r, &sig->data[0], NULL); + secp256k1_scalar_set_b32(s, &sig->data[32], NULL); + } + *recid = sig->data[64]; +} + +static void secp256k1_ecdsa_recoverable_signature_save(secp256k1_ecdsa_recoverable_signature* sig, const secp256k1_scalar* r, const secp256k1_scalar* s, int recid) { + if (sizeof(secp256k1_scalar) == 32) { + memcpy(&sig->data[0], r, 32); + memcpy(&sig->data[32], s, 32); + } else { + secp256k1_scalar_get_b32(&sig->data[0], r); + secp256k1_scalar_get_b32(&sig->data[32], s); + } + sig->data[64] = recid; +} + +int secp256k1_ecdsa_recoverable_signature_parse_compact(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature* sig, const unsigned char *input64, int recid) { + secp256k1_scalar r, s; + int ret = 1; + int overflow = 0; + + (void)ctx; + ARG_CHECK(sig != NULL); + ARG_CHECK(input64 != NULL); + ARG_CHECK(recid >= 0 && recid <= 3); + + secp256k1_scalar_set_b32(&r, &input64[0], &overflow); + ret &= !overflow; + secp256k1_scalar_set_b32(&s, &input64[32], &overflow); + ret &= !overflow; + if (ret) { + secp256k1_ecdsa_recoverable_signature_save(sig, &r, &s, recid); + } else { + memset(sig, 0, sizeof(*sig)); + } + return ret; +} + +int secp256k1_ecdsa_recoverable_signature_serialize_compact(const secp256k1_context* ctx, unsigned char *output64, int *recid, const secp256k1_ecdsa_recoverable_signature* sig) { + secp256k1_scalar r, s; + + (void)ctx; + ARG_CHECK(output64 != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(recid != NULL); + + secp256k1_ecdsa_recoverable_signature_load(ctx, &r, &s, recid, sig); + secp256k1_scalar_get_b32(&output64[0], &r); + secp256k1_scalar_get_b32(&output64[32], &s); + return 1; +} + +int secp256k1_ecdsa_recoverable_signature_convert(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const secp256k1_ecdsa_recoverable_signature* sigin) { + secp256k1_scalar r, s; + int recid; + + (void)ctx; + ARG_CHECK(sig != NULL); + ARG_CHECK(sigin != NULL); + + secp256k1_ecdsa_recoverable_signature_load(ctx, &r, &s, &recid, sigin); + secp256k1_ecdsa_signature_save(sig, &r, &s); + return 1; +} + +static int secp256k1_ecdsa_sig_recover(const secp256k1_ecmult_context *ctx, const secp256k1_scalar *sigr, const secp256k1_scalar* sigs, secp256k1_ge *pubkey, const secp256k1_scalar *message, int recid) { + unsigned char brx[32]; + secp256k1_fe fx; + secp256k1_ge x; + secp256k1_gej xj; + secp256k1_scalar rn, u1, u2; + secp256k1_gej qj; + int r; + + if (secp256k1_scalar_is_zero(sigr) || secp256k1_scalar_is_zero(sigs)) { + return 0; + } + + secp256k1_scalar_get_b32(brx, sigr); + r = secp256k1_fe_set_b32(&fx, brx); + (void)r; + VERIFY_CHECK(r); /* brx comes from a scalar, so is less than the order; certainly less than p */ + if (recid & 2) { + if (secp256k1_fe_cmp_var(&fx, &secp256k1_ecdsa_const_p_minus_order) >= 0) { + return 0; + } + secp256k1_fe_add(&fx, &secp256k1_ecdsa_const_order_as_fe); + } + if (!secp256k1_ge_set_xo_var(&x, &fx, recid & 1)) { + return 0; + } + secp256k1_gej_set_ge(&xj, &x); + secp256k1_scalar_inverse_var(&rn, sigr); + secp256k1_scalar_mul(&u1, &rn, message); + secp256k1_scalar_negate(&u1, &u1); + secp256k1_scalar_mul(&u2, &rn, sigs); + secp256k1_ecmult(ctx, &qj, &xj, &u2, &u1); + secp256k1_ge_set_gej_var(pubkey, &qj); + return !secp256k1_gej_is_infinity(&qj); +} + +int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { + secp256k1_scalar r, s; + secp256k1_scalar sec, non, msg; + int recid; + int ret = 0; + int overflow = 0; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(signature != NULL); + ARG_CHECK(seckey != NULL); + if (noncefp == NULL) { + noncefp = secp256k1_nonce_function_default; + } + + secp256k1_scalar_set_b32(&sec, seckey, &overflow); + /* Fail if the secret key is invalid. */ + if (!overflow && !secp256k1_scalar_is_zero(&sec)) { + unsigned char nonce32[32]; + unsigned int count = 0; + secp256k1_scalar_set_b32(&msg, msg32, NULL); + while (1) { + ret = noncefp(nonce32, msg32, seckey, NULL, (void*)noncedata, count); + if (!ret) { + break; + } + secp256k1_scalar_set_b32(&non, nonce32, &overflow); + if (!secp256k1_scalar_is_zero(&non) && !overflow) { + if (secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, &recid)) { + break; + } + } + count++; + } + memset(nonce32, 0, 32); + secp256k1_scalar_clear(&msg); + secp256k1_scalar_clear(&non); + secp256k1_scalar_clear(&sec); + } + if (ret) { + secp256k1_ecdsa_recoverable_signature_save(signature, &r, &s, recid); + } else { + memset(signature, 0, sizeof(*signature)); + } + return ret; +} + +int secp256k1_ecdsa_recover(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const secp256k1_ecdsa_recoverable_signature *signature, const unsigned char *msg32) { + secp256k1_ge q; + secp256k1_scalar r, s; + secp256k1_scalar m; + int recid; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(signature != NULL); + ARG_CHECK(pubkey != NULL); + + secp256k1_ecdsa_recoverable_signature_load(ctx, &r, &s, &recid, signature); + VERIFY_CHECK(recid >= 0 && recid < 4); /* should have been caught in parse_compact */ + secp256k1_scalar_set_b32(&m, msg32, NULL); + if (secp256k1_ecdsa_sig_recover(&ctx->ecmult_ctx, &r, &s, &q, &m, recid)) { + secp256k1_pubkey_save(pubkey, &q); + return 1; + } else { + memset(pubkey, 0, sizeof(*pubkey)); + return 0; + } +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/modules/recovery/tests_impl.h b/crypto/secp256k1/libsecp256k1/src/modules/recovery/tests_impl.h new file mode 100644 index 0000000..765c7dd --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/recovery/tests_impl.h @@ -0,0 +1,393 @@ +/********************************************************************** + * Copyright (c) 2013-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_MODULE_RECOVERY_TESTS_ +#define _SECP256K1_MODULE_RECOVERY_TESTS_ + +static int recovery_test_nonce_function(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) { + (void) msg32; + (void) key32; + (void) algo16; + (void) data; + + /* On the first run, return 0 to force a second run */ + if (counter == 0) { + memset(nonce32, 0, 32); + return 1; + } + /* On the second run, return an overflow to force a third run */ + if (counter == 1) { + memset(nonce32, 0xff, 32); + return 1; + } + /* On the next run, return a valid nonce, but flip a coin as to whether or not to fail signing. */ + memset(nonce32, 1, 32); + return secp256k1_rand_bits(1); +} + +void test_ecdsa_recovery_api(void) { + /* Setup contexts that just count errors */ + secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + secp256k1_pubkey pubkey; + secp256k1_pubkey recpubkey; + secp256k1_ecdsa_signature normal_sig; + secp256k1_ecdsa_recoverable_signature recsig; + unsigned char privkey[32] = { 1 }; + unsigned char message[32] = { 2 }; + int32_t ecount = 0; + int recid = 0; + unsigned char sig[74]; + unsigned char zero_privkey[32] = { 0 }; + unsigned char over_privkey[32] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + secp256k1_context_set_error_callback(none, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_error_callback(sign, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_error_callback(vrfy, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_error_callback(both, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(vrfy, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(both, counting_illegal_callback_fn, &ecount); + + /* Construct and verify corresponding public key. */ + CHECK(secp256k1_ec_seckey_verify(ctx, privkey) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1); + + /* Check bad contexts and NULLs for signing */ + ecount = 0; + CHECK(secp256k1_ecdsa_sign_recoverable(none, &recsig, message, privkey, NULL, NULL) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(sign, &recsig, message, privkey, NULL, NULL) == 1); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(vrfy, &recsig, message, privkey, NULL, NULL) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_sign_recoverable(both, NULL, message, privkey, NULL, NULL) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, NULL, privkey, NULL, NULL) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, NULL, NULL, NULL) == 0); + CHECK(ecount == 5); + /* This will fail or succeed randomly, and in either case will not ARG_CHECK failure */ + secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, recovery_test_nonce_function, NULL); + CHECK(ecount == 5); + /* These will all fail, but not in ARG_CHECK way */ + CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, zero_privkey, NULL, NULL) == 0); + CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, over_privkey, NULL, NULL) == 0); + /* This one will succeed. */ + CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1); + CHECK(ecount == 5); + + /* Check signing with a goofy nonce function */ + + /* Check bad contexts and NULLs for recovery */ + ecount = 0; + CHECK(secp256k1_ecdsa_recover(none, &recpubkey, &recsig, message) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_recover(sign, &recpubkey, &recsig, message) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_recover(vrfy, &recpubkey, &recsig, message) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_recover(both, &recpubkey, &recsig, message) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_recover(both, NULL, &recsig, message) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_recover(both, &recpubkey, NULL, message) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_recover(both, &recpubkey, &recsig, NULL) == 0); + CHECK(ecount == 5); + + /* Check NULLs for conversion */ + CHECK(secp256k1_ecdsa_sign(both, &normal_sig, message, privkey, NULL, NULL) == 1); + ecount = 0; + CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, NULL, &recsig) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, &normal_sig, NULL) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, &normal_sig, &recsig) == 1); + + /* Check NULLs for de/serialization */ + CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1); + ecount = 0; + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, NULL, &recid, &recsig) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, NULL, &recsig) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, &recid, NULL) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, &recid, &recsig) == 1); + + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, NULL, sig, recid) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, NULL, recid) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, -1) == 0); + CHECK(ecount == 6); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, 5) == 0); + CHECK(ecount == 7); + /* overflow in signature will fail but not affect ecount */ + memcpy(sig, over_privkey, 32); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, recid) == 0); + CHECK(ecount == 7); + + /* cleanup */ + secp256k1_context_destroy(none); + secp256k1_context_destroy(sign); + secp256k1_context_destroy(vrfy); + secp256k1_context_destroy(both); +} + +void test_ecdsa_recovery_end_to_end(void) { + unsigned char extra[32] = {0x00}; + unsigned char privkey[32]; + unsigned char message[32]; + secp256k1_ecdsa_signature signature[5]; + secp256k1_ecdsa_recoverable_signature rsignature[5]; + unsigned char sig[74]; + secp256k1_pubkey pubkey; + secp256k1_pubkey recpubkey; + int recid = 0; + + /* Generate a random key and message. */ + { + secp256k1_scalar msg, key; + random_scalar_order_test(&msg); + random_scalar_order_test(&key); + secp256k1_scalar_get_b32(privkey, &key); + secp256k1_scalar_get_b32(message, &msg); + } + + /* Construct and verify corresponding public key. */ + CHECK(secp256k1_ec_seckey_verify(ctx, privkey) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1); + + /* Serialize/parse compact and verify/recover. */ + extra[0] = 0; + CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[0], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign(ctx, &signature[0], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[4], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[1], message, privkey, NULL, extra) == 1); + extra[31] = 1; + CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[2], message, privkey, NULL, extra) == 1); + extra[31] = 0; + extra[0] = 1; + CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &rsignature[3], message, privkey, NULL, extra) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, sig, &recid, &rsignature[4]) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(ctx, &signature[4], &rsignature[4]) == 1); + CHECK(memcmp(&signature[4], &signature[0], 64) == 0); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[4], message, &pubkey) == 1); + memset(&rsignature[4], 0, sizeof(rsignature[4])); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsignature[4], sig, recid) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(ctx, &signature[4], &rsignature[4]) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[4], message, &pubkey) == 1); + /* Parse compact (with recovery id) and recover. */ + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsignature[4], sig, recid) == 1); + CHECK(secp256k1_ecdsa_recover(ctx, &recpubkey, &rsignature[4], message) == 1); + CHECK(memcmp(&pubkey, &recpubkey, sizeof(pubkey)) == 0); + /* Serialize/destroy/parse signature and verify again. */ + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, sig, &recid, &rsignature[4]) == 1); + sig[secp256k1_rand_bits(6)] += 1 + secp256k1_rand_int(255); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsignature[4], sig, recid) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_convert(ctx, &signature[4], &rsignature[4]) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[4], message, &pubkey) == 0); + /* Recover again */ + CHECK(secp256k1_ecdsa_recover(ctx, &recpubkey, &rsignature[4], message) == 0 || + memcmp(&pubkey, &recpubkey, sizeof(pubkey)) != 0); +} + +/* Tests several edge cases. */ +void test_ecdsa_recovery_edge_cases(void) { + const unsigned char msg32[32] = { + 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', + 'a', ' ', 'v', 'e', 'r', 'y', ' ', 's', + 'e', 'c', 'r', 'e', 't', ' ', 'm', 'e', + 's', 's', 'a', 'g', 'e', '.', '.', '.' + }; + const unsigned char sig64[64] = { + /* Generated by signing the above message with nonce 'This is the nonce we will use...' + * and secret key 0 (which is not valid), resulting in recid 0. */ + 0x67, 0xCB, 0x28, 0x5F, 0x9C, 0xD1, 0x94, 0xE8, + 0x40, 0xD6, 0x29, 0x39, 0x7A, 0xF5, 0x56, 0x96, + 0x62, 0xFD, 0xE4, 0x46, 0x49, 0x99, 0x59, 0x63, + 0x17, 0x9A, 0x7D, 0xD1, 0x7B, 0xD2, 0x35, 0x32, + 0x4B, 0x1B, 0x7D, 0xF3, 0x4C, 0xE1, 0xF6, 0x8E, + 0x69, 0x4F, 0xF6, 0xF1, 0x1A, 0xC7, 0x51, 0xDD, + 0x7D, 0xD7, 0x3E, 0x38, 0x7E, 0xE4, 0xFC, 0x86, + 0x6E, 0x1B, 0xE8, 0xEC, 0xC7, 0xDD, 0x95, 0x57 + }; + secp256k1_pubkey pubkey; + /* signature (r,s) = (4,4), which can be recovered with all 4 recids. */ + const unsigned char sigb64[64] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + }; + secp256k1_pubkey pubkeyb; + secp256k1_ecdsa_recoverable_signature rsig; + secp256k1_ecdsa_signature sig; + int recid; + + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sig64, 0)); + CHECK(!secp256k1_ecdsa_recover(ctx, &pubkey, &rsig, msg32)); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sig64, 1)); + CHECK(secp256k1_ecdsa_recover(ctx, &pubkey, &rsig, msg32)); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sig64, 2)); + CHECK(!secp256k1_ecdsa_recover(ctx, &pubkey, &rsig, msg32)); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sig64, 3)); + CHECK(!secp256k1_ecdsa_recover(ctx, &pubkey, &rsig, msg32)); + + for (recid = 0; recid < 4; recid++) { + int i; + int recid2; + /* (4,4) encoded in DER. */ + unsigned char sigbder[8] = {0x30, 0x06, 0x02, 0x01, 0x04, 0x02, 0x01, 0x04}; + unsigned char sigcder_zr[7] = {0x30, 0x05, 0x02, 0x00, 0x02, 0x01, 0x01}; + unsigned char sigcder_zs[7] = {0x30, 0x05, 0x02, 0x01, 0x01, 0x02, 0x00}; + unsigned char sigbderalt1[39] = { + 0x30, 0x25, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x02, 0x01, 0x04, + }; + unsigned char sigbderalt2[39] = { + 0x30, 0x25, 0x02, 0x01, 0x04, 0x02, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + }; + unsigned char sigbderalt3[40] = { + 0x30, 0x26, 0x02, 0x21, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, 0x01, 0x04, + }; + unsigned char sigbderalt4[40] = { + 0x30, 0x26, 0x02, 0x01, 0x04, 0x02, 0x21, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + }; + /* (order + r,4) encoded in DER. */ + unsigned char sigbderlong[40] = { + 0x30, 0x26, 0x02, 0x21, 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, + 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, + 0x8C, 0xD0, 0x36, 0x41, 0x45, 0x02, 0x01, 0x04 + }; + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigb64, recid) == 1); + CHECK(secp256k1_ecdsa_recover(ctx, &pubkeyb, &rsig, msg32) == 1); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, sizeof(sigbder)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 1); + for (recid2 = 0; recid2 < 4; recid2++) { + secp256k1_pubkey pubkey2b; + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigb64, recid2) == 1); + CHECK(secp256k1_ecdsa_recover(ctx, &pubkey2b, &rsig, msg32) == 1); + /* Verifying with (order + r,4) should always fail. */ + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderlong, sizeof(sigbderlong)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + } + /* DER parsing tests. */ + /* Zero length r/s. */ + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder_zr, sizeof(sigcder_zr)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder_zs, sizeof(sigcder_zs)) == 0); + /* Leading zeros. */ + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt1, sizeof(sigbderalt1)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt2, sizeof(sigbderalt2)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt3, sizeof(sigbderalt3)) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt4, sizeof(sigbderalt4)) == 0); + sigbderalt3[4] = 1; + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt3, sizeof(sigbderalt3)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + sigbderalt4[7] = 1; + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbderalt4, sizeof(sigbderalt4)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + /* Damage signature. */ + sigbder[7]++; + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, sizeof(sigbder)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + sigbder[7]--; + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, 6) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, sizeof(sigbder) - 1) == 0); + for(i = 0; i < 8; i++) { + int c; + unsigned char orig = sigbder[i]; + /*Try every single-byte change.*/ + for (c = 0; c < 256; c++) { + if (c == orig ) { + continue; + } + sigbder[i] = c; + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigbder, sizeof(sigbder)) == 0 || secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyb) == 0); + } + sigbder[i] = orig; + } + } + + /* Test r/s equal to zero */ + { + /* (1,1) encoded in DER. */ + unsigned char sigcder[8] = {0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01}; + unsigned char sigc64[64] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }; + secp256k1_pubkey pubkeyc; + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigc64, 0) == 1); + CHECK(secp256k1_ecdsa_recover(ctx, &pubkeyc, &rsig, msg32) == 1); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder, sizeof(sigcder)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyc) == 1); + sigcder[4] = 0; + sigc64[31] = 0; + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigc64, 0) == 1); + CHECK(secp256k1_ecdsa_recover(ctx, &pubkeyb, &rsig, msg32) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder, sizeof(sigcder)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyc) == 0); + sigcder[4] = 1; + sigcder[7] = 0; + sigc64[31] = 1; + sigc64[63] = 0; + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &rsig, sigc64, 0) == 1); + CHECK(secp256k1_ecdsa_recover(ctx, &pubkeyb, &rsig, msg32) == 0); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, sigcder, sizeof(sigcder)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg32, &pubkeyc) == 0); + } +} + +void run_recovery_tests(void) { + int i; + for (i = 0; i < count; i++) { + test_ecdsa_recovery_api(); + } + for (i = 0; i < 64*count; i++) { + test_ecdsa_recovery_end_to_end(); + } + test_ecdsa_recovery_edge_cases(); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/num.h b/crypto/secp256k1/libsecp256k1/src/num.h new file mode 100644 index 0000000..eff8422 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/num.h @@ -0,0 +1,74 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_NUM_ +#define _SECP256K1_NUM_ + +#ifndef USE_NUM_NONE + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#if defined(USE_NUM_GMP) +#include "num_gmp.h" +#else +#error "Please select num implementation" +#endif + +/** Copy a number. */ +static void secp256k1_num_copy(secp256k1_num *r, const secp256k1_num *a); + +/** Convert a number's absolute value to a binary big-endian string. + * There must be enough place. */ +static void secp256k1_num_get_bin(unsigned char *r, unsigned int rlen, const secp256k1_num *a); + +/** Set a number to the value of a binary big-endian string. */ +static void secp256k1_num_set_bin(secp256k1_num *r, const unsigned char *a, unsigned int alen); + +/** Compute a modular inverse. The input must be less than the modulus. */ +static void secp256k1_num_mod_inverse(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *m); + +/** Compute the jacobi symbol (a|b). b must be positive and odd. */ +static int secp256k1_num_jacobi(const secp256k1_num *a, const secp256k1_num *b); + +/** Compare the absolute value of two numbers. */ +static int secp256k1_num_cmp(const secp256k1_num *a, const secp256k1_num *b); + +/** Test whether two number are equal (including sign). */ +static int secp256k1_num_eq(const secp256k1_num *a, const secp256k1_num *b); + +/** Add two (signed) numbers. */ +static void secp256k1_num_add(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b); + +/** Subtract two (signed) numbers. */ +static void secp256k1_num_sub(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b); + +/** Multiply two (signed) numbers. */ +static void secp256k1_num_mul(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b); + +/** Replace a number by its remainder modulo m. M's sign is ignored. The result is a number between 0 and m-1, + even if r was negative. */ +static void secp256k1_num_mod(secp256k1_num *r, const secp256k1_num *m); + +/** Right-shift the passed number by bits. */ +static void secp256k1_num_shift(secp256k1_num *r, int bits); + +/** Check whether a number is zero. */ +static int secp256k1_num_is_zero(const secp256k1_num *a); + +/** Check whether a number is one. */ +static int secp256k1_num_is_one(const secp256k1_num *a); + +/** Check whether a number is strictly negative. */ +static int secp256k1_num_is_neg(const secp256k1_num *a); + +/** Change a number's sign. */ +static void secp256k1_num_negate(secp256k1_num *r); + +#endif + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/num_gmp.h b/crypto/secp256k1/libsecp256k1/src/num_gmp.h new file mode 100644 index 0000000..7dd8130 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/num_gmp.h @@ -0,0 +1,20 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_NUM_REPR_ +#define _SECP256K1_NUM_REPR_ + +#include + +#define NUM_LIMBS ((256+GMP_NUMB_BITS-1)/GMP_NUMB_BITS) + +typedef struct { + mp_limb_t data[2*NUM_LIMBS]; + int neg; + int limbs; +} secp256k1_num; + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/num_gmp_impl.h b/crypto/secp256k1/libsecp256k1/src/num_gmp_impl.h new file mode 100644 index 0000000..3a46495 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/num_gmp_impl.h @@ -0,0 +1,288 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_NUM_REPR_IMPL_H_ +#define _SECP256K1_NUM_REPR_IMPL_H_ + +#include +#include +#include + +#include "util.h" +#include "num.h" + +#ifdef VERIFY +static void secp256k1_num_sanity(const secp256k1_num *a) { + VERIFY_CHECK(a->limbs == 1 || (a->limbs > 1 && a->data[a->limbs-1] != 0)); +} +#else +#define secp256k1_num_sanity(a) do { } while(0) +#endif + +static void secp256k1_num_copy(secp256k1_num *r, const secp256k1_num *a) { + *r = *a; +} + +static void secp256k1_num_get_bin(unsigned char *r, unsigned int rlen, const secp256k1_num *a) { + unsigned char tmp[65]; + int len = 0; + int shift = 0; + if (a->limbs>1 || a->data[0] != 0) { + len = mpn_get_str(tmp, 256, (mp_limb_t*)a->data, a->limbs); + } + while (shift < len && tmp[shift] == 0) shift++; + VERIFY_CHECK(len-shift <= (int)rlen); + memset(r, 0, rlen - len + shift); + if (len > shift) { + memcpy(r + rlen - len + shift, tmp + shift, len - shift); + } + memset(tmp, 0, sizeof(tmp)); +} + +static void secp256k1_num_set_bin(secp256k1_num *r, const unsigned char *a, unsigned int alen) { + int len; + VERIFY_CHECK(alen > 0); + VERIFY_CHECK(alen <= 64); + len = mpn_set_str(r->data, a, alen, 256); + if (len == 0) { + r->data[0] = 0; + len = 1; + } + VERIFY_CHECK(len <= NUM_LIMBS*2); + r->limbs = len; + r->neg = 0; + while (r->limbs > 1 && r->data[r->limbs-1]==0) { + r->limbs--; + } +} + +static void secp256k1_num_add_abs(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { + mp_limb_t c = mpn_add(r->data, a->data, a->limbs, b->data, b->limbs); + r->limbs = a->limbs; + if (c != 0) { + VERIFY_CHECK(r->limbs < 2*NUM_LIMBS); + r->data[r->limbs++] = c; + } +} + +static void secp256k1_num_sub_abs(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { + mp_limb_t c = mpn_sub(r->data, a->data, a->limbs, b->data, b->limbs); + (void)c; + VERIFY_CHECK(c == 0); + r->limbs = a->limbs; + while (r->limbs > 1 && r->data[r->limbs-1]==0) { + r->limbs--; + } +} + +static void secp256k1_num_mod(secp256k1_num *r, const secp256k1_num *m) { + secp256k1_num_sanity(r); + secp256k1_num_sanity(m); + + if (r->limbs >= m->limbs) { + mp_limb_t t[2*NUM_LIMBS]; + mpn_tdiv_qr(t, r->data, 0, r->data, r->limbs, m->data, m->limbs); + memset(t, 0, sizeof(t)); + r->limbs = m->limbs; + while (r->limbs > 1 && r->data[r->limbs-1]==0) { + r->limbs--; + } + } + + if (r->neg && (r->limbs > 1 || r->data[0] != 0)) { + secp256k1_num_sub_abs(r, m, r); + r->neg = 0; + } +} + +static void secp256k1_num_mod_inverse(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *m) { + int i; + mp_limb_t g[NUM_LIMBS+1]; + mp_limb_t u[NUM_LIMBS+1]; + mp_limb_t v[NUM_LIMBS+1]; + mp_size_t sn; + mp_size_t gn; + secp256k1_num_sanity(a); + secp256k1_num_sanity(m); + + /** mpn_gcdext computes: (G,S) = gcdext(U,V), where + * * G = gcd(U,V) + * * G = U*S + V*T + * * U has equal or more limbs than V, and V has no padding + * If we set U to be (a padded version of) a, and V = m: + * G = a*S + m*T + * G = a*S mod m + * Assuming G=1: + * S = 1/a mod m + */ + VERIFY_CHECK(m->limbs <= NUM_LIMBS); + VERIFY_CHECK(m->data[m->limbs-1] != 0); + for (i = 0; i < m->limbs; i++) { + u[i] = (i < a->limbs) ? a->data[i] : 0; + v[i] = m->data[i]; + } + sn = NUM_LIMBS+1; + gn = mpn_gcdext(g, r->data, &sn, u, m->limbs, v, m->limbs); + (void)gn; + VERIFY_CHECK(gn == 1); + VERIFY_CHECK(g[0] == 1); + r->neg = a->neg ^ m->neg; + if (sn < 0) { + mpn_sub(r->data, m->data, m->limbs, r->data, -sn); + r->limbs = m->limbs; + while (r->limbs > 1 && r->data[r->limbs-1]==0) { + r->limbs--; + } + } else { + r->limbs = sn; + } + memset(g, 0, sizeof(g)); + memset(u, 0, sizeof(u)); + memset(v, 0, sizeof(v)); +} + +static int secp256k1_num_jacobi(const secp256k1_num *a, const secp256k1_num *b) { + int ret; + mpz_t ga, gb; + secp256k1_num_sanity(a); + secp256k1_num_sanity(b); + VERIFY_CHECK(!b->neg && (b->limbs > 0) && (b->data[0] & 1)); + + mpz_inits(ga, gb, NULL); + + mpz_import(gb, b->limbs, -1, sizeof(mp_limb_t), 0, 0, b->data); + mpz_import(ga, a->limbs, -1, sizeof(mp_limb_t), 0, 0, a->data); + if (a->neg) { + mpz_neg(ga, ga); + } + + ret = mpz_jacobi(ga, gb); + + mpz_clears(ga, gb, NULL); + + return ret; +} + +static int secp256k1_num_is_one(const secp256k1_num *a) { + return (a->limbs == 1 && a->data[0] == 1); +} + +static int secp256k1_num_is_zero(const secp256k1_num *a) { + return (a->limbs == 1 && a->data[0] == 0); +} + +static int secp256k1_num_is_neg(const secp256k1_num *a) { + return (a->limbs > 1 || a->data[0] != 0) && a->neg; +} + +static int secp256k1_num_cmp(const secp256k1_num *a, const secp256k1_num *b) { + if (a->limbs > b->limbs) { + return 1; + } + if (a->limbs < b->limbs) { + return -1; + } + return mpn_cmp(a->data, b->data, a->limbs); +} + +static int secp256k1_num_eq(const secp256k1_num *a, const secp256k1_num *b) { + if (a->limbs > b->limbs) { + return 0; + } + if (a->limbs < b->limbs) { + return 0; + } + if ((a->neg && !secp256k1_num_is_zero(a)) != (b->neg && !secp256k1_num_is_zero(b))) { + return 0; + } + return mpn_cmp(a->data, b->data, a->limbs) == 0; +} + +static void secp256k1_num_subadd(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b, int bneg) { + if (!(b->neg ^ bneg ^ a->neg)) { /* a and b have the same sign */ + r->neg = a->neg; + if (a->limbs >= b->limbs) { + secp256k1_num_add_abs(r, a, b); + } else { + secp256k1_num_add_abs(r, b, a); + } + } else { + if (secp256k1_num_cmp(a, b) > 0) { + r->neg = a->neg; + secp256k1_num_sub_abs(r, a, b); + } else { + r->neg = b->neg ^ bneg; + secp256k1_num_sub_abs(r, b, a); + } + } +} + +static void secp256k1_num_add(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { + secp256k1_num_sanity(a); + secp256k1_num_sanity(b); + secp256k1_num_subadd(r, a, b, 0); +} + +static void secp256k1_num_sub(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { + secp256k1_num_sanity(a); + secp256k1_num_sanity(b); + secp256k1_num_subadd(r, a, b, 1); +} + +static void secp256k1_num_mul(secp256k1_num *r, const secp256k1_num *a, const secp256k1_num *b) { + mp_limb_t tmp[2*NUM_LIMBS+1]; + secp256k1_num_sanity(a); + secp256k1_num_sanity(b); + + VERIFY_CHECK(a->limbs + b->limbs <= 2*NUM_LIMBS+1); + if ((a->limbs==1 && a->data[0]==0) || (b->limbs==1 && b->data[0]==0)) { + r->limbs = 1; + r->neg = 0; + r->data[0] = 0; + return; + } + if (a->limbs >= b->limbs) { + mpn_mul(tmp, a->data, a->limbs, b->data, b->limbs); + } else { + mpn_mul(tmp, b->data, b->limbs, a->data, a->limbs); + } + r->limbs = a->limbs + b->limbs; + if (r->limbs > 1 && tmp[r->limbs - 1]==0) { + r->limbs--; + } + VERIFY_CHECK(r->limbs <= 2*NUM_LIMBS); + mpn_copyi(r->data, tmp, r->limbs); + r->neg = a->neg ^ b->neg; + memset(tmp, 0, sizeof(tmp)); +} + +static void secp256k1_num_shift(secp256k1_num *r, int bits) { + if (bits % GMP_NUMB_BITS) { + /* Shift within limbs. */ + mpn_rshift(r->data, r->data, r->limbs, bits % GMP_NUMB_BITS); + } + if (bits >= GMP_NUMB_BITS) { + int i; + /* Shift full limbs. */ + for (i = 0; i < r->limbs; i++) { + int index = i + (bits / GMP_NUMB_BITS); + if (index < r->limbs && index < 2*NUM_LIMBS) { + r->data[i] = r->data[index]; + } else { + r->data[i] = 0; + } + } + } + while (r->limbs>1 && r->data[r->limbs-1]==0) { + r->limbs--; + } +} + +static void secp256k1_num_negate(secp256k1_num *r) { + r->neg ^= 1; +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/num_impl.h b/crypto/secp256k1/libsecp256k1/src/num_impl.h new file mode 100644 index 0000000..0b0e3a0 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/num_impl.h @@ -0,0 +1,24 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_NUM_IMPL_H_ +#define _SECP256K1_NUM_IMPL_H_ + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#include "num.h" + +#if defined(USE_NUM_GMP) +#include "num_gmp_impl.h" +#elif defined(USE_NUM_NONE) +/* Nothing. */ +#else +#error "Please select num implementation" +#endif + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/scalar.h b/crypto/secp256k1/libsecp256k1/src/scalar.h new file mode 100644 index 0000000..27e9d83 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/scalar.h @@ -0,0 +1,106 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_SCALAR_ +#define _SECP256K1_SCALAR_ + +#include "num.h" + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#if defined(EXHAUSTIVE_TEST_ORDER) +#include "scalar_low.h" +#elif defined(USE_SCALAR_4X64) +#include "scalar_4x64.h" +#elif defined(USE_SCALAR_8X32) +#include "scalar_8x32.h" +#else +#error "Please select scalar implementation" +#endif + +/** Clear a scalar to prevent the leak of sensitive data. */ +static void secp256k1_scalar_clear(secp256k1_scalar *r); + +/** Access bits from a scalar. All requested bits must belong to the same 32-bit limb. */ +static unsigned int secp256k1_scalar_get_bits(const secp256k1_scalar *a, unsigned int offset, unsigned int count); + +/** Access bits from a scalar. Not constant time. */ +static unsigned int secp256k1_scalar_get_bits_var(const secp256k1_scalar *a, unsigned int offset, unsigned int count); + +/** Set a scalar from a big endian byte array. */ +static void secp256k1_scalar_set_b32(secp256k1_scalar *r, const unsigned char *bin, int *overflow); + +/** Set a scalar to an unsigned integer. */ +static void secp256k1_scalar_set_int(secp256k1_scalar *r, unsigned int v); + +/** Convert a scalar to a byte array. */ +static void secp256k1_scalar_get_b32(unsigned char *bin, const secp256k1_scalar* a); + +/** Add two scalars together (modulo the group order). Returns whether it overflowed. */ +static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b); + +/** Conditionally add a power of two to a scalar. The result is not allowed to overflow. */ +static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag); + +/** Multiply two scalars (modulo the group order). */ +static void secp256k1_scalar_mul(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b); + +/** Shift a scalar right by some amount strictly between 0 and 16, returning + * the low bits that were shifted off */ +static int secp256k1_scalar_shr_int(secp256k1_scalar *r, int n); + +/** Compute the square of a scalar (modulo the group order). */ +static void secp256k1_scalar_sqr(secp256k1_scalar *r, const secp256k1_scalar *a); + +/** Compute the inverse of a scalar (modulo the group order). */ +static void secp256k1_scalar_inverse(secp256k1_scalar *r, const secp256k1_scalar *a); + +/** Compute the inverse of a scalar (modulo the group order), without constant-time guarantee. */ +static void secp256k1_scalar_inverse_var(secp256k1_scalar *r, const secp256k1_scalar *a); + +/** Compute the complement of a scalar (modulo the group order). */ +static void secp256k1_scalar_negate(secp256k1_scalar *r, const secp256k1_scalar *a); + +/** Check whether a scalar equals zero. */ +static int secp256k1_scalar_is_zero(const secp256k1_scalar *a); + +/** Check whether a scalar equals one. */ +static int secp256k1_scalar_is_one(const secp256k1_scalar *a); + +/** Check whether a scalar, considered as an nonnegative integer, is even. */ +static int secp256k1_scalar_is_even(const secp256k1_scalar *a); + +/** Check whether a scalar is higher than the group order divided by 2. */ +static int secp256k1_scalar_is_high(const secp256k1_scalar *a); + +/** Conditionally negate a number, in constant time. + * Returns -1 if the number was negated, 1 otherwise */ +static int secp256k1_scalar_cond_negate(secp256k1_scalar *a, int flag); + +#ifndef USE_NUM_NONE +/** Convert a scalar to a number. */ +static void secp256k1_scalar_get_num(secp256k1_num *r, const secp256k1_scalar *a); + +/** Get the order of the group as a number. */ +static void secp256k1_scalar_order_get_num(secp256k1_num *r); +#endif + +/** Compare two scalars. */ +static int secp256k1_scalar_eq(const secp256k1_scalar *a, const secp256k1_scalar *b); + +#ifdef USE_ENDOMORPHISM +/** Find r1 and r2 such that r1+r2*2^128 = a. */ +static void secp256k1_scalar_split_128(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *a); +/** Find r1 and r2 such that r1+r2*lambda = a, and r1 and r2 are maximum 128 bits long (see secp256k1_gej_mul_lambda). */ +static void secp256k1_scalar_split_lambda(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *a); +#endif + +/** Multiply a and b (without taking the modulus!), divide by 2**shift, and round to the nearest integer. Shift must be at least 256. */ +static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b, unsigned int shift); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/scalar_4x64.h b/crypto/secp256k1/libsecp256k1/src/scalar_4x64.h new file mode 100644 index 0000000..cff4060 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/scalar_4x64.h @@ -0,0 +1,19 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_SCALAR_REPR_ +#define _SECP256K1_SCALAR_REPR_ + +#include + +/** A scalar modulo the group order of the secp256k1 curve. */ +typedef struct { + uint64_t d[4]; +} secp256k1_scalar; + +#define SECP256K1_SCALAR_CONST(d7, d6, d5, d4, d3, d2, d1, d0) {{((uint64_t)(d1)) << 32 | (d0), ((uint64_t)(d3)) << 32 | (d2), ((uint64_t)(d5)) << 32 | (d4), ((uint64_t)(d7)) << 32 | (d6)}} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/scalar_4x64_impl.h b/crypto/secp256k1/libsecp256k1/src/scalar_4x64_impl.h new file mode 100644 index 0000000..56e7bd8 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/scalar_4x64_impl.h @@ -0,0 +1,949 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_SCALAR_REPR_IMPL_H_ +#define _SECP256K1_SCALAR_REPR_IMPL_H_ + +/* Limbs of the secp256k1 order. */ +#define SECP256K1_N_0 ((uint64_t)0xBFD25E8CD0364141ULL) +#define SECP256K1_N_1 ((uint64_t)0xBAAEDCE6AF48A03BULL) +#define SECP256K1_N_2 ((uint64_t)0xFFFFFFFFFFFFFFFEULL) +#define SECP256K1_N_3 ((uint64_t)0xFFFFFFFFFFFFFFFFULL) + +/* Limbs of 2^256 minus the secp256k1 order. */ +#define SECP256K1_N_C_0 (~SECP256K1_N_0 + 1) +#define SECP256K1_N_C_1 (~SECP256K1_N_1) +#define SECP256K1_N_C_2 (1) + +/* Limbs of half the secp256k1 order. */ +#define SECP256K1_N_H_0 ((uint64_t)0xDFE92F46681B20A0ULL) +#define SECP256K1_N_H_1 ((uint64_t)0x5D576E7357A4501DULL) +#define SECP256K1_N_H_2 ((uint64_t)0xFFFFFFFFFFFFFFFFULL) +#define SECP256K1_N_H_3 ((uint64_t)0x7FFFFFFFFFFFFFFFULL) + +SECP256K1_INLINE static void secp256k1_scalar_clear(secp256k1_scalar *r) { + r->d[0] = 0; + r->d[1] = 0; + r->d[2] = 0; + r->d[3] = 0; +} + +SECP256K1_INLINE static void secp256k1_scalar_set_int(secp256k1_scalar *r, unsigned int v) { + r->d[0] = v; + r->d[1] = 0; + r->d[2] = 0; + r->d[3] = 0; +} + +SECP256K1_INLINE static unsigned int secp256k1_scalar_get_bits(const secp256k1_scalar *a, unsigned int offset, unsigned int count) { + VERIFY_CHECK((offset + count - 1) >> 6 == offset >> 6); + return (a->d[offset >> 6] >> (offset & 0x3F)) & ((((uint64_t)1) << count) - 1); +} + +SECP256K1_INLINE static unsigned int secp256k1_scalar_get_bits_var(const secp256k1_scalar *a, unsigned int offset, unsigned int count) { + VERIFY_CHECK(count < 32); + VERIFY_CHECK(offset + count <= 256); + if ((offset + count - 1) >> 6 == offset >> 6) { + return secp256k1_scalar_get_bits(a, offset, count); + } else { + VERIFY_CHECK((offset >> 6) + 1 < 4); + return ((a->d[offset >> 6] >> (offset & 0x3F)) | (a->d[(offset >> 6) + 1] << (64 - (offset & 0x3F)))) & ((((uint64_t)1) << count) - 1); + } +} + +SECP256K1_INLINE static int secp256k1_scalar_check_overflow(const secp256k1_scalar *a) { + int yes = 0; + int no = 0; + no |= (a->d[3] < SECP256K1_N_3); /* No need for a > check. */ + no |= (a->d[2] < SECP256K1_N_2); + yes |= (a->d[2] > SECP256K1_N_2) & ~no; + no |= (a->d[1] < SECP256K1_N_1); + yes |= (a->d[1] > SECP256K1_N_1) & ~no; + yes |= (a->d[0] >= SECP256K1_N_0) & ~no; + return yes; +} + +SECP256K1_INLINE static int secp256k1_scalar_reduce(secp256k1_scalar *r, unsigned int overflow) { + uint128_t t; + VERIFY_CHECK(overflow <= 1); + t = (uint128_t)r->d[0] + overflow * SECP256K1_N_C_0; + r->d[0] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint128_t)r->d[1] + overflow * SECP256K1_N_C_1; + r->d[1] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint128_t)r->d[2] + overflow * SECP256K1_N_C_2; + r->d[2] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint64_t)r->d[3]; + r->d[3] = t & 0xFFFFFFFFFFFFFFFFULL; + return overflow; +} + +static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b) { + int overflow; + uint128_t t = (uint128_t)a->d[0] + b->d[0]; + r->d[0] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint128_t)a->d[1] + b->d[1]; + r->d[1] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint128_t)a->d[2] + b->d[2]; + r->d[2] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint128_t)a->d[3] + b->d[3]; + r->d[3] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + overflow = t + secp256k1_scalar_check_overflow(r); + VERIFY_CHECK(overflow == 0 || overflow == 1); + secp256k1_scalar_reduce(r, overflow); + return overflow; +} + +static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag) { + uint128_t t; + VERIFY_CHECK(bit < 256); + bit += ((uint32_t) flag - 1) & 0x100; /* forcing (bit >> 6) > 3 makes this a noop */ + t = (uint128_t)r->d[0] + (((uint64_t)((bit >> 6) == 0)) << (bit & 0x3F)); + r->d[0] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint128_t)r->d[1] + (((uint64_t)((bit >> 6) == 1)) << (bit & 0x3F)); + r->d[1] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint128_t)r->d[2] + (((uint64_t)((bit >> 6) == 2)) << (bit & 0x3F)); + r->d[2] = t & 0xFFFFFFFFFFFFFFFFULL; t >>= 64; + t += (uint128_t)r->d[3] + (((uint64_t)((bit >> 6) == 3)) << (bit & 0x3F)); + r->d[3] = t & 0xFFFFFFFFFFFFFFFFULL; +#ifdef VERIFY + VERIFY_CHECK((t >> 64) == 0); + VERIFY_CHECK(secp256k1_scalar_check_overflow(r) == 0); +#endif +} + +static void secp256k1_scalar_set_b32(secp256k1_scalar *r, const unsigned char *b32, int *overflow) { + int over; + r->d[0] = (uint64_t)b32[31] | (uint64_t)b32[30] << 8 | (uint64_t)b32[29] << 16 | (uint64_t)b32[28] << 24 | (uint64_t)b32[27] << 32 | (uint64_t)b32[26] << 40 | (uint64_t)b32[25] << 48 | (uint64_t)b32[24] << 56; + r->d[1] = (uint64_t)b32[23] | (uint64_t)b32[22] << 8 | (uint64_t)b32[21] << 16 | (uint64_t)b32[20] << 24 | (uint64_t)b32[19] << 32 | (uint64_t)b32[18] << 40 | (uint64_t)b32[17] << 48 | (uint64_t)b32[16] << 56; + r->d[2] = (uint64_t)b32[15] | (uint64_t)b32[14] << 8 | (uint64_t)b32[13] << 16 | (uint64_t)b32[12] << 24 | (uint64_t)b32[11] << 32 | (uint64_t)b32[10] << 40 | (uint64_t)b32[9] << 48 | (uint64_t)b32[8] << 56; + r->d[3] = (uint64_t)b32[7] | (uint64_t)b32[6] << 8 | (uint64_t)b32[5] << 16 | (uint64_t)b32[4] << 24 | (uint64_t)b32[3] << 32 | (uint64_t)b32[2] << 40 | (uint64_t)b32[1] << 48 | (uint64_t)b32[0] << 56; + over = secp256k1_scalar_reduce(r, secp256k1_scalar_check_overflow(r)); + if (overflow) { + *overflow = over; + } +} + +static void secp256k1_scalar_get_b32(unsigned char *bin, const secp256k1_scalar* a) { + bin[0] = a->d[3] >> 56; bin[1] = a->d[3] >> 48; bin[2] = a->d[3] >> 40; bin[3] = a->d[3] >> 32; bin[4] = a->d[3] >> 24; bin[5] = a->d[3] >> 16; bin[6] = a->d[3] >> 8; bin[7] = a->d[3]; + bin[8] = a->d[2] >> 56; bin[9] = a->d[2] >> 48; bin[10] = a->d[2] >> 40; bin[11] = a->d[2] >> 32; bin[12] = a->d[2] >> 24; bin[13] = a->d[2] >> 16; bin[14] = a->d[2] >> 8; bin[15] = a->d[2]; + bin[16] = a->d[1] >> 56; bin[17] = a->d[1] >> 48; bin[18] = a->d[1] >> 40; bin[19] = a->d[1] >> 32; bin[20] = a->d[1] >> 24; bin[21] = a->d[1] >> 16; bin[22] = a->d[1] >> 8; bin[23] = a->d[1]; + bin[24] = a->d[0] >> 56; bin[25] = a->d[0] >> 48; bin[26] = a->d[0] >> 40; bin[27] = a->d[0] >> 32; bin[28] = a->d[0] >> 24; bin[29] = a->d[0] >> 16; bin[30] = a->d[0] >> 8; bin[31] = a->d[0]; +} + +SECP256K1_INLINE static int secp256k1_scalar_is_zero(const secp256k1_scalar *a) { + return (a->d[0] | a->d[1] | a->d[2] | a->d[3]) == 0; +} + +static void secp256k1_scalar_negate(secp256k1_scalar *r, const secp256k1_scalar *a) { + uint64_t nonzero = 0xFFFFFFFFFFFFFFFFULL * (secp256k1_scalar_is_zero(a) == 0); + uint128_t t = (uint128_t)(~a->d[0]) + SECP256K1_N_0 + 1; + r->d[0] = t & nonzero; t >>= 64; + t += (uint128_t)(~a->d[1]) + SECP256K1_N_1; + r->d[1] = t & nonzero; t >>= 64; + t += (uint128_t)(~a->d[2]) + SECP256K1_N_2; + r->d[2] = t & nonzero; t >>= 64; + t += (uint128_t)(~a->d[3]) + SECP256K1_N_3; + r->d[3] = t & nonzero; +} + +SECP256K1_INLINE static int secp256k1_scalar_is_one(const secp256k1_scalar *a) { + return ((a->d[0] ^ 1) | a->d[1] | a->d[2] | a->d[3]) == 0; +} + +static int secp256k1_scalar_is_high(const secp256k1_scalar *a) { + int yes = 0; + int no = 0; + no |= (a->d[3] < SECP256K1_N_H_3); + yes |= (a->d[3] > SECP256K1_N_H_3) & ~no; + no |= (a->d[2] < SECP256K1_N_H_2) & ~yes; /* No need for a > check. */ + no |= (a->d[1] < SECP256K1_N_H_1) & ~yes; + yes |= (a->d[1] > SECP256K1_N_H_1) & ~no; + yes |= (a->d[0] > SECP256K1_N_H_0) & ~no; + return yes; +} + +static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { + /* If we are flag = 0, mask = 00...00 and this is a no-op; + * if we are flag = 1, mask = 11...11 and this is identical to secp256k1_scalar_negate */ + uint64_t mask = !flag - 1; + uint64_t nonzero = (secp256k1_scalar_is_zero(r) != 0) - 1; + uint128_t t = (uint128_t)(r->d[0] ^ mask) + ((SECP256K1_N_0 + 1) & mask); + r->d[0] = t & nonzero; t >>= 64; + t += (uint128_t)(r->d[1] ^ mask) + (SECP256K1_N_1 & mask); + r->d[1] = t & nonzero; t >>= 64; + t += (uint128_t)(r->d[2] ^ mask) + (SECP256K1_N_2 & mask); + r->d[2] = t & nonzero; t >>= 64; + t += (uint128_t)(r->d[3] ^ mask) + (SECP256K1_N_3 & mask); + r->d[3] = t & nonzero; + return 2 * (mask == 0) - 1; +} + +/* Inspired by the macros in OpenSSL's crypto/bn/asm/x86_64-gcc.c. */ + +/** Add a*b to the number defined by (c0,c1,c2). c2 must never overflow. */ +#define muladd(a,b) { \ + uint64_t tl, th; \ + { \ + uint128_t t = (uint128_t)a * b; \ + th = t >> 64; /* at most 0xFFFFFFFFFFFFFFFE */ \ + tl = t; \ + } \ + c0 += tl; /* overflow is handled on the next line */ \ + th += (c0 < tl) ? 1 : 0; /* at most 0xFFFFFFFFFFFFFFFF */ \ + c1 += th; /* overflow is handled on the next line */ \ + c2 += (c1 < th) ? 1 : 0; /* never overflows by contract (verified in the next line) */ \ + VERIFY_CHECK((c1 >= th) || (c2 != 0)); \ +} + +/** Add a*b to the number defined by (c0,c1). c1 must never overflow. */ +#define muladd_fast(a,b) { \ + uint64_t tl, th; \ + { \ + uint128_t t = (uint128_t)a * b; \ + th = t >> 64; /* at most 0xFFFFFFFFFFFFFFFE */ \ + tl = t; \ + } \ + c0 += tl; /* overflow is handled on the next line */ \ + th += (c0 < tl) ? 1 : 0; /* at most 0xFFFFFFFFFFFFFFFF */ \ + c1 += th; /* never overflows by contract (verified in the next line) */ \ + VERIFY_CHECK(c1 >= th); \ +} + +/** Add 2*a*b to the number defined by (c0,c1,c2). c2 must never overflow. */ +#define muladd2(a,b) { \ + uint64_t tl, th, th2, tl2; \ + { \ + uint128_t t = (uint128_t)a * b; \ + th = t >> 64; /* at most 0xFFFFFFFFFFFFFFFE */ \ + tl = t; \ + } \ + th2 = th + th; /* at most 0xFFFFFFFFFFFFFFFE (in case th was 0x7FFFFFFFFFFFFFFF) */ \ + c2 += (th2 < th) ? 1 : 0; /* never overflows by contract (verified the next line) */ \ + VERIFY_CHECK((th2 >= th) || (c2 != 0)); \ + tl2 = tl + tl; /* at most 0xFFFFFFFFFFFFFFFE (in case the lowest 63 bits of tl were 0x7FFFFFFFFFFFFFFF) */ \ + th2 += (tl2 < tl) ? 1 : 0; /* at most 0xFFFFFFFFFFFFFFFF */ \ + c0 += tl2; /* overflow is handled on the next line */ \ + th2 += (c0 < tl2) ? 1 : 0; /* second overflow is handled on the next line */ \ + c2 += (c0 < tl2) & (th2 == 0); /* never overflows by contract (verified the next line) */ \ + VERIFY_CHECK((c0 >= tl2) || (th2 != 0) || (c2 != 0)); \ + c1 += th2; /* overflow is handled on the next line */ \ + c2 += (c1 < th2) ? 1 : 0; /* never overflows by contract (verified the next line) */ \ + VERIFY_CHECK((c1 >= th2) || (c2 != 0)); \ +} + +/** Add a to the number defined by (c0,c1,c2). c2 must never overflow. */ +#define sumadd(a) { \ + unsigned int over; \ + c0 += (a); /* overflow is handled on the next line */ \ + over = (c0 < (a)) ? 1 : 0; \ + c1 += over; /* overflow is handled on the next line */ \ + c2 += (c1 < over) ? 1 : 0; /* never overflows by contract */ \ +} + +/** Add a to the number defined by (c0,c1). c1 must never overflow, c2 must be zero. */ +#define sumadd_fast(a) { \ + c0 += (a); /* overflow is handled on the next line */ \ + c1 += (c0 < (a)) ? 1 : 0; /* never overflows by contract (verified the next line) */ \ + VERIFY_CHECK((c1 != 0) | (c0 >= (a))); \ + VERIFY_CHECK(c2 == 0); \ +} + +/** Extract the lowest 64 bits of (c0,c1,c2) into n, and left shift the number 64 bits. */ +#define extract(n) { \ + (n) = c0; \ + c0 = c1; \ + c1 = c2; \ + c2 = 0; \ +} + +/** Extract the lowest 64 bits of (c0,c1,c2) into n, and left shift the number 64 bits. c2 is required to be zero. */ +#define extract_fast(n) { \ + (n) = c0; \ + c0 = c1; \ + c1 = 0; \ + VERIFY_CHECK(c2 == 0); \ +} + +static void secp256k1_scalar_reduce_512(secp256k1_scalar *r, const uint64_t *l) { +#ifdef USE_ASM_X86_64 + /* Reduce 512 bits into 385. */ + uint64_t m0, m1, m2, m3, m4, m5, m6; + uint64_t p0, p1, p2, p3, p4; + uint64_t c; + + __asm__ __volatile__( + /* Preload. */ + "movq 32(%%rsi), %%r11\n" + "movq 40(%%rsi), %%r12\n" + "movq 48(%%rsi), %%r13\n" + "movq 56(%%rsi), %%r14\n" + /* Initialize r8,r9,r10 */ + "movq 0(%%rsi), %%r8\n" + "xorq %%r9, %%r9\n" + "xorq %%r10, %%r10\n" + /* (r8,r9) += n0 * c0 */ + "movq %8, %%rax\n" + "mulq %%r11\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + /* extract m0 */ + "movq %%r8, %q0\n" + "xorq %%r8, %%r8\n" + /* (r9,r10) += l1 */ + "addq 8(%%rsi), %%r9\n" + "adcq $0, %%r10\n" + /* (r9,r10,r8) += n1 * c0 */ + "movq %8, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* (r9,r10,r8) += n0 * c1 */ + "movq %9, %%rax\n" + "mulq %%r11\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* extract m1 */ + "movq %%r9, %q1\n" + "xorq %%r9, %%r9\n" + /* (r10,r8,r9) += l2 */ + "addq 16(%%rsi), %%r10\n" + "adcq $0, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += n2 * c0 */ + "movq %8, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += n1 * c1 */ + "movq %9, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += n0 */ + "addq %%r11, %%r10\n" + "adcq $0, %%r8\n" + "adcq $0, %%r9\n" + /* extract m2 */ + "movq %%r10, %q2\n" + "xorq %%r10, %%r10\n" + /* (r8,r9,r10) += l3 */ + "addq 24(%%rsi), %%r8\n" + "adcq $0, %%r9\n" + "adcq $0, %%r10\n" + /* (r8,r9,r10) += n3 * c0 */ + "movq %8, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* (r8,r9,r10) += n2 * c1 */ + "movq %9, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* (r8,r9,r10) += n1 */ + "addq %%r12, %%r8\n" + "adcq $0, %%r9\n" + "adcq $0, %%r10\n" + /* extract m3 */ + "movq %%r8, %q3\n" + "xorq %%r8, %%r8\n" + /* (r9,r10,r8) += n3 * c1 */ + "movq %9, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* (r9,r10,r8) += n2 */ + "addq %%r13, %%r9\n" + "adcq $0, %%r10\n" + "adcq $0, %%r8\n" + /* extract m4 */ + "movq %%r9, %q4\n" + /* (r10,r8) += n3 */ + "addq %%r14, %%r10\n" + "adcq $0, %%r8\n" + /* extract m5 */ + "movq %%r10, %q5\n" + /* extract m6 */ + "movq %%r8, %q6\n" + : "=g"(m0), "=g"(m1), "=g"(m2), "=g"(m3), "=g"(m4), "=g"(m5), "=g"(m6) + : "S"(l), "n"(SECP256K1_N_C_0), "n"(SECP256K1_N_C_1) + : "rax", "rdx", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "cc"); + + /* Reduce 385 bits into 258. */ + __asm__ __volatile__( + /* Preload */ + "movq %q9, %%r11\n" + "movq %q10, %%r12\n" + "movq %q11, %%r13\n" + /* Initialize (r8,r9,r10) */ + "movq %q5, %%r8\n" + "xorq %%r9, %%r9\n" + "xorq %%r10, %%r10\n" + /* (r8,r9) += m4 * c0 */ + "movq %12, %%rax\n" + "mulq %%r11\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + /* extract p0 */ + "movq %%r8, %q0\n" + "xorq %%r8, %%r8\n" + /* (r9,r10) += m1 */ + "addq %q6, %%r9\n" + "adcq $0, %%r10\n" + /* (r9,r10,r8) += m5 * c0 */ + "movq %12, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* (r9,r10,r8) += m4 * c1 */ + "movq %13, %%rax\n" + "mulq %%r11\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* extract p1 */ + "movq %%r9, %q1\n" + "xorq %%r9, %%r9\n" + /* (r10,r8,r9) += m2 */ + "addq %q7, %%r10\n" + "adcq $0, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += m6 * c0 */ + "movq %12, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += m5 * c1 */ + "movq %13, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += m4 */ + "addq %%r11, %%r10\n" + "adcq $0, %%r8\n" + "adcq $0, %%r9\n" + /* extract p2 */ + "movq %%r10, %q2\n" + /* (r8,r9) += m3 */ + "addq %q8, %%r8\n" + "adcq $0, %%r9\n" + /* (r8,r9) += m6 * c1 */ + "movq %13, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + /* (r8,r9) += m5 */ + "addq %%r12, %%r8\n" + "adcq $0, %%r9\n" + /* extract p3 */ + "movq %%r8, %q3\n" + /* (r9) += m6 */ + "addq %%r13, %%r9\n" + /* extract p4 */ + "movq %%r9, %q4\n" + : "=&g"(p0), "=&g"(p1), "=&g"(p2), "=g"(p3), "=g"(p4) + : "g"(m0), "g"(m1), "g"(m2), "g"(m3), "g"(m4), "g"(m5), "g"(m6), "n"(SECP256K1_N_C_0), "n"(SECP256K1_N_C_1) + : "rax", "rdx", "r8", "r9", "r10", "r11", "r12", "r13", "cc"); + + /* Reduce 258 bits into 256. */ + __asm__ __volatile__( + /* Preload */ + "movq %q5, %%r10\n" + /* (rax,rdx) = p4 * c0 */ + "movq %7, %%rax\n" + "mulq %%r10\n" + /* (rax,rdx) += p0 */ + "addq %q1, %%rax\n" + "adcq $0, %%rdx\n" + /* extract r0 */ + "movq %%rax, 0(%q6)\n" + /* Move to (r8,r9) */ + "movq %%rdx, %%r8\n" + "xorq %%r9, %%r9\n" + /* (r8,r9) += p1 */ + "addq %q2, %%r8\n" + "adcq $0, %%r9\n" + /* (r8,r9) += p4 * c1 */ + "movq %8, %%rax\n" + "mulq %%r10\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + /* Extract r1 */ + "movq %%r8, 8(%q6)\n" + "xorq %%r8, %%r8\n" + /* (r9,r8) += p4 */ + "addq %%r10, %%r9\n" + "adcq $0, %%r8\n" + /* (r9,r8) += p2 */ + "addq %q3, %%r9\n" + "adcq $0, %%r8\n" + /* Extract r2 */ + "movq %%r9, 16(%q6)\n" + "xorq %%r9, %%r9\n" + /* (r8,r9) += p3 */ + "addq %q4, %%r8\n" + "adcq $0, %%r9\n" + /* Extract r3 */ + "movq %%r8, 24(%q6)\n" + /* Extract c */ + "movq %%r9, %q0\n" + : "=g"(c) + : "g"(p0), "g"(p1), "g"(p2), "g"(p3), "g"(p4), "D"(r), "n"(SECP256K1_N_C_0), "n"(SECP256K1_N_C_1) + : "rax", "rdx", "r8", "r9", "r10", "cc", "memory"); +#else + uint128_t c; + uint64_t c0, c1, c2; + uint64_t n0 = l[4], n1 = l[5], n2 = l[6], n3 = l[7]; + uint64_t m0, m1, m2, m3, m4, m5; + uint32_t m6; + uint64_t p0, p1, p2, p3; + uint32_t p4; + + /* Reduce 512 bits into 385. */ + /* m[0..6] = l[0..3] + n[0..3] * SECP256K1_N_C. */ + c0 = l[0]; c1 = 0; c2 = 0; + muladd_fast(n0, SECP256K1_N_C_0); + extract_fast(m0); + sumadd_fast(l[1]); + muladd(n1, SECP256K1_N_C_0); + muladd(n0, SECP256K1_N_C_1); + extract(m1); + sumadd(l[2]); + muladd(n2, SECP256K1_N_C_0); + muladd(n1, SECP256K1_N_C_1); + sumadd(n0); + extract(m2); + sumadd(l[3]); + muladd(n3, SECP256K1_N_C_0); + muladd(n2, SECP256K1_N_C_1); + sumadd(n1); + extract(m3); + muladd(n3, SECP256K1_N_C_1); + sumadd(n2); + extract(m4); + sumadd_fast(n3); + extract_fast(m5); + VERIFY_CHECK(c0 <= 1); + m6 = c0; + + /* Reduce 385 bits into 258. */ + /* p[0..4] = m[0..3] + m[4..6] * SECP256K1_N_C. */ + c0 = m0; c1 = 0; c2 = 0; + muladd_fast(m4, SECP256K1_N_C_0); + extract_fast(p0); + sumadd_fast(m1); + muladd(m5, SECP256K1_N_C_0); + muladd(m4, SECP256K1_N_C_1); + extract(p1); + sumadd(m2); + muladd(m6, SECP256K1_N_C_0); + muladd(m5, SECP256K1_N_C_1); + sumadd(m4); + extract(p2); + sumadd_fast(m3); + muladd_fast(m6, SECP256K1_N_C_1); + sumadd_fast(m5); + extract_fast(p3); + p4 = c0 + m6; + VERIFY_CHECK(p4 <= 2); + + /* Reduce 258 bits into 256. */ + /* r[0..3] = p[0..3] + p[4] * SECP256K1_N_C. */ + c = p0 + (uint128_t)SECP256K1_N_C_0 * p4; + r->d[0] = c & 0xFFFFFFFFFFFFFFFFULL; c >>= 64; + c += p1 + (uint128_t)SECP256K1_N_C_1 * p4; + r->d[1] = c & 0xFFFFFFFFFFFFFFFFULL; c >>= 64; + c += p2 + (uint128_t)p4; + r->d[2] = c & 0xFFFFFFFFFFFFFFFFULL; c >>= 64; + c += p3; + r->d[3] = c & 0xFFFFFFFFFFFFFFFFULL; c >>= 64; +#endif + + /* Final reduction of r. */ + secp256k1_scalar_reduce(r, c + secp256k1_scalar_check_overflow(r)); +} + +static void secp256k1_scalar_mul_512(uint64_t l[8], const secp256k1_scalar *a, const secp256k1_scalar *b) { +#ifdef USE_ASM_X86_64 + const uint64_t *pb = b->d; + __asm__ __volatile__( + /* Preload */ + "movq 0(%%rdi), %%r15\n" + "movq 8(%%rdi), %%rbx\n" + "movq 16(%%rdi), %%rcx\n" + "movq 0(%%rdx), %%r11\n" + "movq 8(%%rdx), %%r12\n" + "movq 16(%%rdx), %%r13\n" + "movq 24(%%rdx), %%r14\n" + /* (rax,rdx) = a0 * b0 */ + "movq %%r15, %%rax\n" + "mulq %%r11\n" + /* Extract l0 */ + "movq %%rax, 0(%%rsi)\n" + /* (r8,r9,r10) = (rdx) */ + "movq %%rdx, %%r8\n" + "xorq %%r9, %%r9\n" + "xorq %%r10, %%r10\n" + /* (r8,r9,r10) += a0 * b1 */ + "movq %%r15, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* (r8,r9,r10) += a1 * b0 */ + "movq %%rbx, %%rax\n" + "mulq %%r11\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* Extract l1 */ + "movq %%r8, 8(%%rsi)\n" + "xorq %%r8, %%r8\n" + /* (r9,r10,r8) += a0 * b2 */ + "movq %%r15, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* (r9,r10,r8) += a1 * b1 */ + "movq %%rbx, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* (r9,r10,r8) += a2 * b0 */ + "movq %%rcx, %%rax\n" + "mulq %%r11\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* Extract l2 */ + "movq %%r9, 16(%%rsi)\n" + "xorq %%r9, %%r9\n" + /* (r10,r8,r9) += a0 * b3 */ + "movq %%r15, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* Preload a3 */ + "movq 24(%%rdi), %%r15\n" + /* (r10,r8,r9) += a1 * b2 */ + "movq %%rbx, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += a2 * b1 */ + "movq %%rcx, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += a3 * b0 */ + "movq %%r15, %%rax\n" + "mulq %%r11\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* Extract l3 */ + "movq %%r10, 24(%%rsi)\n" + "xorq %%r10, %%r10\n" + /* (r8,r9,r10) += a1 * b3 */ + "movq %%rbx, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* (r8,r9,r10) += a2 * b2 */ + "movq %%rcx, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* (r8,r9,r10) += a3 * b1 */ + "movq %%r15, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* Extract l4 */ + "movq %%r8, 32(%%rsi)\n" + "xorq %%r8, %%r8\n" + /* (r9,r10,r8) += a2 * b3 */ + "movq %%rcx, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* (r9,r10,r8) += a3 * b2 */ + "movq %%r15, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* Extract l5 */ + "movq %%r9, 40(%%rsi)\n" + /* (r10,r8) += a3 * b3 */ + "movq %%r15, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + /* Extract l6 */ + "movq %%r10, 48(%%rsi)\n" + /* Extract l7 */ + "movq %%r8, 56(%%rsi)\n" + : "+d"(pb) + : "S"(l), "D"(a->d) + : "rax", "rbx", "rcx", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "cc", "memory"); +#else + /* 160 bit accumulator. */ + uint64_t c0 = 0, c1 = 0; + uint32_t c2 = 0; + + /* l[0..7] = a[0..3] * b[0..3]. */ + muladd_fast(a->d[0], b->d[0]); + extract_fast(l[0]); + muladd(a->d[0], b->d[1]); + muladd(a->d[1], b->d[0]); + extract(l[1]); + muladd(a->d[0], b->d[2]); + muladd(a->d[1], b->d[1]); + muladd(a->d[2], b->d[0]); + extract(l[2]); + muladd(a->d[0], b->d[3]); + muladd(a->d[1], b->d[2]); + muladd(a->d[2], b->d[1]); + muladd(a->d[3], b->d[0]); + extract(l[3]); + muladd(a->d[1], b->d[3]); + muladd(a->d[2], b->d[2]); + muladd(a->d[3], b->d[1]); + extract(l[4]); + muladd(a->d[2], b->d[3]); + muladd(a->d[3], b->d[2]); + extract(l[5]); + muladd_fast(a->d[3], b->d[3]); + extract_fast(l[6]); + VERIFY_CHECK(c1 == 0); + l[7] = c0; +#endif +} + +static void secp256k1_scalar_sqr_512(uint64_t l[8], const secp256k1_scalar *a) { +#ifdef USE_ASM_X86_64 + __asm__ __volatile__( + /* Preload */ + "movq 0(%%rdi), %%r11\n" + "movq 8(%%rdi), %%r12\n" + "movq 16(%%rdi), %%r13\n" + "movq 24(%%rdi), %%r14\n" + /* (rax,rdx) = a0 * a0 */ + "movq %%r11, %%rax\n" + "mulq %%r11\n" + /* Extract l0 */ + "movq %%rax, 0(%%rsi)\n" + /* (r8,r9,r10) = (rdx,0) */ + "movq %%rdx, %%r8\n" + "xorq %%r9, %%r9\n" + "xorq %%r10, %%r10\n" + /* (r8,r9,r10) += 2 * a0 * a1 */ + "movq %%r11, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* Extract l1 */ + "movq %%r8, 8(%%rsi)\n" + "xorq %%r8, %%r8\n" + /* (r9,r10,r8) += 2 * a0 * a2 */ + "movq %%r11, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* (r9,r10,r8) += a1 * a1 */ + "movq %%r12, %%rax\n" + "mulq %%r12\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* Extract l2 */ + "movq %%r9, 16(%%rsi)\n" + "xorq %%r9, %%r9\n" + /* (r10,r8,r9) += 2 * a0 * a3 */ + "movq %%r11, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* (r10,r8,r9) += 2 * a1 * a2 */ + "movq %%r12, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + "adcq $0, %%r9\n" + /* Extract l3 */ + "movq %%r10, 24(%%rsi)\n" + "xorq %%r10, %%r10\n" + /* (r8,r9,r10) += 2 * a1 * a3 */ + "movq %%r12, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* (r8,r9,r10) += a2 * a2 */ + "movq %%r13, %%rax\n" + "mulq %%r13\n" + "addq %%rax, %%r8\n" + "adcq %%rdx, %%r9\n" + "adcq $0, %%r10\n" + /* Extract l4 */ + "movq %%r8, 32(%%rsi)\n" + "xorq %%r8, %%r8\n" + /* (r9,r10,r8) += 2 * a2 * a3 */ + "movq %%r13, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + "addq %%rax, %%r9\n" + "adcq %%rdx, %%r10\n" + "adcq $0, %%r8\n" + /* Extract l5 */ + "movq %%r9, 40(%%rsi)\n" + /* (r10,r8) += a3 * a3 */ + "movq %%r14, %%rax\n" + "mulq %%r14\n" + "addq %%rax, %%r10\n" + "adcq %%rdx, %%r8\n" + /* Extract l6 */ + "movq %%r10, 48(%%rsi)\n" + /* Extract l7 */ + "movq %%r8, 56(%%rsi)\n" + : + : "S"(l), "D"(a->d) + : "rax", "rdx", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "cc", "memory"); +#else + /* 160 bit accumulator. */ + uint64_t c0 = 0, c1 = 0; + uint32_t c2 = 0; + + /* l[0..7] = a[0..3] * b[0..3]. */ + muladd_fast(a->d[0], a->d[0]); + extract_fast(l[0]); + muladd2(a->d[0], a->d[1]); + extract(l[1]); + muladd2(a->d[0], a->d[2]); + muladd(a->d[1], a->d[1]); + extract(l[2]); + muladd2(a->d[0], a->d[3]); + muladd2(a->d[1], a->d[2]); + extract(l[3]); + muladd2(a->d[1], a->d[3]); + muladd(a->d[2], a->d[2]); + extract(l[4]); + muladd2(a->d[2], a->d[3]); + extract(l[5]); + muladd_fast(a->d[3], a->d[3]); + extract_fast(l[6]); + VERIFY_CHECK(c1 == 0); + l[7] = c0; +#endif +} + +#undef sumadd +#undef sumadd_fast +#undef muladd +#undef muladd_fast +#undef muladd2 +#undef extract +#undef extract_fast + +static void secp256k1_scalar_mul(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b) { + uint64_t l[8]; + secp256k1_scalar_mul_512(l, a, b); + secp256k1_scalar_reduce_512(r, l); +} + +static int secp256k1_scalar_shr_int(secp256k1_scalar *r, int n) { + int ret; + VERIFY_CHECK(n > 0); + VERIFY_CHECK(n < 16); + ret = r->d[0] & ((1 << n) - 1); + r->d[0] = (r->d[0] >> n) + (r->d[1] << (64 - n)); + r->d[1] = (r->d[1] >> n) + (r->d[2] << (64 - n)); + r->d[2] = (r->d[2] >> n) + (r->d[3] << (64 - n)); + r->d[3] = (r->d[3] >> n); + return ret; +} + +static void secp256k1_scalar_sqr(secp256k1_scalar *r, const secp256k1_scalar *a) { + uint64_t l[8]; + secp256k1_scalar_sqr_512(l, a); + secp256k1_scalar_reduce_512(r, l); +} + +#ifdef USE_ENDOMORPHISM +static void secp256k1_scalar_split_128(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *a) { + r1->d[0] = a->d[0]; + r1->d[1] = a->d[1]; + r1->d[2] = 0; + r1->d[3] = 0; + r2->d[0] = a->d[2]; + r2->d[1] = a->d[3]; + r2->d[2] = 0; + r2->d[3] = 0; +} +#endif + +SECP256K1_INLINE static int secp256k1_scalar_eq(const secp256k1_scalar *a, const secp256k1_scalar *b) { + return ((a->d[0] ^ b->d[0]) | (a->d[1] ^ b->d[1]) | (a->d[2] ^ b->d[2]) | (a->d[3] ^ b->d[3])) == 0; +} + +SECP256K1_INLINE static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b, unsigned int shift) { + uint64_t l[8]; + unsigned int shiftlimbs; + unsigned int shiftlow; + unsigned int shifthigh; + VERIFY_CHECK(shift >= 256); + secp256k1_scalar_mul_512(l, a, b); + shiftlimbs = shift >> 6; + shiftlow = shift & 0x3F; + shifthigh = 64 - shiftlow; + r->d[0] = shift < 512 ? (l[0 + shiftlimbs] >> shiftlow | (shift < 448 && shiftlow ? (l[1 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[1] = shift < 448 ? (l[1 + shiftlimbs] >> shiftlow | (shift < 384 && shiftlow ? (l[2 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[2] = shift < 384 ? (l[2 + shiftlimbs] >> shiftlow | (shift < 320 && shiftlow ? (l[3 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[3] = shift < 320 ? (l[3 + shiftlimbs] >> shiftlow) : 0; + secp256k1_scalar_cadd_bit(r, 0, (l[(shift - 1) >> 6] >> ((shift - 1) & 0x3f)) & 1); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/scalar_8x32.h b/crypto/secp256k1/libsecp256k1/src/scalar_8x32.h new file mode 100644 index 0000000..1319664 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/scalar_8x32.h @@ -0,0 +1,19 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_SCALAR_REPR_ +#define _SECP256K1_SCALAR_REPR_ + +#include + +/** A scalar modulo the group order of the secp256k1 curve. */ +typedef struct { + uint32_t d[8]; +} secp256k1_scalar; + +#define SECP256K1_SCALAR_CONST(d7, d6, d5, d4, d3, d2, d1, d0) {{(d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7)}} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/scalar_8x32_impl.h b/crypto/secp256k1/libsecp256k1/src/scalar_8x32_impl.h new file mode 100644 index 0000000..aae4f35 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/scalar_8x32_impl.h @@ -0,0 +1,721 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_SCALAR_REPR_IMPL_H_ +#define _SECP256K1_SCALAR_REPR_IMPL_H_ + +/* Limbs of the secp256k1 order. */ +#define SECP256K1_N_0 ((uint32_t)0xD0364141UL) +#define SECP256K1_N_1 ((uint32_t)0xBFD25E8CUL) +#define SECP256K1_N_2 ((uint32_t)0xAF48A03BUL) +#define SECP256K1_N_3 ((uint32_t)0xBAAEDCE6UL) +#define SECP256K1_N_4 ((uint32_t)0xFFFFFFFEUL) +#define SECP256K1_N_5 ((uint32_t)0xFFFFFFFFUL) +#define SECP256K1_N_6 ((uint32_t)0xFFFFFFFFUL) +#define SECP256K1_N_7 ((uint32_t)0xFFFFFFFFUL) + +/* Limbs of 2^256 minus the secp256k1 order. */ +#define SECP256K1_N_C_0 (~SECP256K1_N_0 + 1) +#define SECP256K1_N_C_1 (~SECP256K1_N_1) +#define SECP256K1_N_C_2 (~SECP256K1_N_2) +#define SECP256K1_N_C_3 (~SECP256K1_N_3) +#define SECP256K1_N_C_4 (1) + +/* Limbs of half the secp256k1 order. */ +#define SECP256K1_N_H_0 ((uint32_t)0x681B20A0UL) +#define SECP256K1_N_H_1 ((uint32_t)0xDFE92F46UL) +#define SECP256K1_N_H_2 ((uint32_t)0x57A4501DUL) +#define SECP256K1_N_H_3 ((uint32_t)0x5D576E73UL) +#define SECP256K1_N_H_4 ((uint32_t)0xFFFFFFFFUL) +#define SECP256K1_N_H_5 ((uint32_t)0xFFFFFFFFUL) +#define SECP256K1_N_H_6 ((uint32_t)0xFFFFFFFFUL) +#define SECP256K1_N_H_7 ((uint32_t)0x7FFFFFFFUL) + +SECP256K1_INLINE static void secp256k1_scalar_clear(secp256k1_scalar *r) { + r->d[0] = 0; + r->d[1] = 0; + r->d[2] = 0; + r->d[3] = 0; + r->d[4] = 0; + r->d[5] = 0; + r->d[6] = 0; + r->d[7] = 0; +} + +SECP256K1_INLINE static void secp256k1_scalar_set_int(secp256k1_scalar *r, unsigned int v) { + r->d[0] = v; + r->d[1] = 0; + r->d[2] = 0; + r->d[3] = 0; + r->d[4] = 0; + r->d[5] = 0; + r->d[6] = 0; + r->d[7] = 0; +} + +SECP256K1_INLINE static unsigned int secp256k1_scalar_get_bits(const secp256k1_scalar *a, unsigned int offset, unsigned int count) { + VERIFY_CHECK((offset + count - 1) >> 5 == offset >> 5); + return (a->d[offset >> 5] >> (offset & 0x1F)) & ((1 << count) - 1); +} + +SECP256K1_INLINE static unsigned int secp256k1_scalar_get_bits_var(const secp256k1_scalar *a, unsigned int offset, unsigned int count) { + VERIFY_CHECK(count < 32); + VERIFY_CHECK(offset + count <= 256); + if ((offset + count - 1) >> 5 == offset >> 5) { + return secp256k1_scalar_get_bits(a, offset, count); + } else { + VERIFY_CHECK((offset >> 5) + 1 < 8); + return ((a->d[offset >> 5] >> (offset & 0x1F)) | (a->d[(offset >> 5) + 1] << (32 - (offset & 0x1F)))) & ((((uint32_t)1) << count) - 1); + } +} + +SECP256K1_INLINE static int secp256k1_scalar_check_overflow(const secp256k1_scalar *a) { + int yes = 0; + int no = 0; + no |= (a->d[7] < SECP256K1_N_7); /* No need for a > check. */ + no |= (a->d[6] < SECP256K1_N_6); /* No need for a > check. */ + no |= (a->d[5] < SECP256K1_N_5); /* No need for a > check. */ + no |= (a->d[4] < SECP256K1_N_4); + yes |= (a->d[4] > SECP256K1_N_4) & ~no; + no |= (a->d[3] < SECP256K1_N_3) & ~yes; + yes |= (a->d[3] > SECP256K1_N_3) & ~no; + no |= (a->d[2] < SECP256K1_N_2) & ~yes; + yes |= (a->d[2] > SECP256K1_N_2) & ~no; + no |= (a->d[1] < SECP256K1_N_1) & ~yes; + yes |= (a->d[1] > SECP256K1_N_1) & ~no; + yes |= (a->d[0] >= SECP256K1_N_0) & ~no; + return yes; +} + +SECP256K1_INLINE static int secp256k1_scalar_reduce(secp256k1_scalar *r, uint32_t overflow) { + uint64_t t; + VERIFY_CHECK(overflow <= 1); + t = (uint64_t)r->d[0] + overflow * SECP256K1_N_C_0; + r->d[0] = t & 0xFFFFFFFFUL; t >>= 32; + t += (uint64_t)r->d[1] + overflow * SECP256K1_N_C_1; + r->d[1] = t & 0xFFFFFFFFUL; t >>= 32; + t += (uint64_t)r->d[2] + overflow * SECP256K1_N_C_2; + r->d[2] = t & 0xFFFFFFFFUL; t >>= 32; + t += (uint64_t)r->d[3] + overflow * SECP256K1_N_C_3; + r->d[3] = t & 0xFFFFFFFFUL; t >>= 32; + t += (uint64_t)r->d[4] + overflow * SECP256K1_N_C_4; + r->d[4] = t & 0xFFFFFFFFUL; t >>= 32; + t += (uint64_t)r->d[5]; + r->d[5] = t & 0xFFFFFFFFUL; t >>= 32; + t += (uint64_t)r->d[6]; + r->d[6] = t & 0xFFFFFFFFUL; t >>= 32; + t += (uint64_t)r->d[7]; + r->d[7] = t & 0xFFFFFFFFUL; + return overflow; +} + +static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b) { + int overflow; + uint64_t t = (uint64_t)a->d[0] + b->d[0]; + r->d[0] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)a->d[1] + b->d[1]; + r->d[1] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)a->d[2] + b->d[2]; + r->d[2] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)a->d[3] + b->d[3]; + r->d[3] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)a->d[4] + b->d[4]; + r->d[4] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)a->d[5] + b->d[5]; + r->d[5] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)a->d[6] + b->d[6]; + r->d[6] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)a->d[7] + b->d[7]; + r->d[7] = t & 0xFFFFFFFFULL; t >>= 32; + overflow = t + secp256k1_scalar_check_overflow(r); + VERIFY_CHECK(overflow == 0 || overflow == 1); + secp256k1_scalar_reduce(r, overflow); + return overflow; +} + +static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag) { + uint64_t t; + VERIFY_CHECK(bit < 256); + bit += ((uint32_t) flag - 1) & 0x100; /* forcing (bit >> 5) > 7 makes this a noop */ + t = (uint64_t)r->d[0] + (((uint32_t)((bit >> 5) == 0)) << (bit & 0x1F)); + r->d[0] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)r->d[1] + (((uint32_t)((bit >> 5) == 1)) << (bit & 0x1F)); + r->d[1] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)r->d[2] + (((uint32_t)((bit >> 5) == 2)) << (bit & 0x1F)); + r->d[2] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)r->d[3] + (((uint32_t)((bit >> 5) == 3)) << (bit & 0x1F)); + r->d[3] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)r->d[4] + (((uint32_t)((bit >> 5) == 4)) << (bit & 0x1F)); + r->d[4] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)r->d[5] + (((uint32_t)((bit >> 5) == 5)) << (bit & 0x1F)); + r->d[5] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)r->d[6] + (((uint32_t)((bit >> 5) == 6)) << (bit & 0x1F)); + r->d[6] = t & 0xFFFFFFFFULL; t >>= 32; + t += (uint64_t)r->d[7] + (((uint32_t)((bit >> 5) == 7)) << (bit & 0x1F)); + r->d[7] = t & 0xFFFFFFFFULL; +#ifdef VERIFY + VERIFY_CHECK((t >> 32) == 0); + VERIFY_CHECK(secp256k1_scalar_check_overflow(r) == 0); +#endif +} + +static void secp256k1_scalar_set_b32(secp256k1_scalar *r, const unsigned char *b32, int *overflow) { + int over; + r->d[0] = (uint32_t)b32[31] | (uint32_t)b32[30] << 8 | (uint32_t)b32[29] << 16 | (uint32_t)b32[28] << 24; + r->d[1] = (uint32_t)b32[27] | (uint32_t)b32[26] << 8 | (uint32_t)b32[25] << 16 | (uint32_t)b32[24] << 24; + r->d[2] = (uint32_t)b32[23] | (uint32_t)b32[22] << 8 | (uint32_t)b32[21] << 16 | (uint32_t)b32[20] << 24; + r->d[3] = (uint32_t)b32[19] | (uint32_t)b32[18] << 8 | (uint32_t)b32[17] << 16 | (uint32_t)b32[16] << 24; + r->d[4] = (uint32_t)b32[15] | (uint32_t)b32[14] << 8 | (uint32_t)b32[13] << 16 | (uint32_t)b32[12] << 24; + r->d[5] = (uint32_t)b32[11] | (uint32_t)b32[10] << 8 | (uint32_t)b32[9] << 16 | (uint32_t)b32[8] << 24; + r->d[6] = (uint32_t)b32[7] | (uint32_t)b32[6] << 8 | (uint32_t)b32[5] << 16 | (uint32_t)b32[4] << 24; + r->d[7] = (uint32_t)b32[3] | (uint32_t)b32[2] << 8 | (uint32_t)b32[1] << 16 | (uint32_t)b32[0] << 24; + over = secp256k1_scalar_reduce(r, secp256k1_scalar_check_overflow(r)); + if (overflow) { + *overflow = over; + } +} + +static void secp256k1_scalar_get_b32(unsigned char *bin, const secp256k1_scalar* a) { + bin[0] = a->d[7] >> 24; bin[1] = a->d[7] >> 16; bin[2] = a->d[7] >> 8; bin[3] = a->d[7]; + bin[4] = a->d[6] >> 24; bin[5] = a->d[6] >> 16; bin[6] = a->d[6] >> 8; bin[7] = a->d[6]; + bin[8] = a->d[5] >> 24; bin[9] = a->d[5] >> 16; bin[10] = a->d[5] >> 8; bin[11] = a->d[5]; + bin[12] = a->d[4] >> 24; bin[13] = a->d[4] >> 16; bin[14] = a->d[4] >> 8; bin[15] = a->d[4]; + bin[16] = a->d[3] >> 24; bin[17] = a->d[3] >> 16; bin[18] = a->d[3] >> 8; bin[19] = a->d[3]; + bin[20] = a->d[2] >> 24; bin[21] = a->d[2] >> 16; bin[22] = a->d[2] >> 8; bin[23] = a->d[2]; + bin[24] = a->d[1] >> 24; bin[25] = a->d[1] >> 16; bin[26] = a->d[1] >> 8; bin[27] = a->d[1]; + bin[28] = a->d[0] >> 24; bin[29] = a->d[0] >> 16; bin[30] = a->d[0] >> 8; bin[31] = a->d[0]; +} + +SECP256K1_INLINE static int secp256k1_scalar_is_zero(const secp256k1_scalar *a) { + return (a->d[0] | a->d[1] | a->d[2] | a->d[3] | a->d[4] | a->d[5] | a->d[6] | a->d[7]) == 0; +} + +static void secp256k1_scalar_negate(secp256k1_scalar *r, const secp256k1_scalar *a) { + uint32_t nonzero = 0xFFFFFFFFUL * (secp256k1_scalar_is_zero(a) == 0); + uint64_t t = (uint64_t)(~a->d[0]) + SECP256K1_N_0 + 1; + r->d[0] = t & nonzero; t >>= 32; + t += (uint64_t)(~a->d[1]) + SECP256K1_N_1; + r->d[1] = t & nonzero; t >>= 32; + t += (uint64_t)(~a->d[2]) + SECP256K1_N_2; + r->d[2] = t & nonzero; t >>= 32; + t += (uint64_t)(~a->d[3]) + SECP256K1_N_3; + r->d[3] = t & nonzero; t >>= 32; + t += (uint64_t)(~a->d[4]) + SECP256K1_N_4; + r->d[4] = t & nonzero; t >>= 32; + t += (uint64_t)(~a->d[5]) + SECP256K1_N_5; + r->d[5] = t & nonzero; t >>= 32; + t += (uint64_t)(~a->d[6]) + SECP256K1_N_6; + r->d[6] = t & nonzero; t >>= 32; + t += (uint64_t)(~a->d[7]) + SECP256K1_N_7; + r->d[7] = t & nonzero; +} + +SECP256K1_INLINE static int secp256k1_scalar_is_one(const secp256k1_scalar *a) { + return ((a->d[0] ^ 1) | a->d[1] | a->d[2] | a->d[3] | a->d[4] | a->d[5] | a->d[6] | a->d[7]) == 0; +} + +static int secp256k1_scalar_is_high(const secp256k1_scalar *a) { + int yes = 0; + int no = 0; + no |= (a->d[7] < SECP256K1_N_H_7); + yes |= (a->d[7] > SECP256K1_N_H_7) & ~no; + no |= (a->d[6] < SECP256K1_N_H_6) & ~yes; /* No need for a > check. */ + no |= (a->d[5] < SECP256K1_N_H_5) & ~yes; /* No need for a > check. */ + no |= (a->d[4] < SECP256K1_N_H_4) & ~yes; /* No need for a > check. */ + no |= (a->d[3] < SECP256K1_N_H_3) & ~yes; + yes |= (a->d[3] > SECP256K1_N_H_3) & ~no; + no |= (a->d[2] < SECP256K1_N_H_2) & ~yes; + yes |= (a->d[2] > SECP256K1_N_H_2) & ~no; + no |= (a->d[1] < SECP256K1_N_H_1) & ~yes; + yes |= (a->d[1] > SECP256K1_N_H_1) & ~no; + yes |= (a->d[0] > SECP256K1_N_H_0) & ~no; + return yes; +} + +static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { + /* If we are flag = 0, mask = 00...00 and this is a no-op; + * if we are flag = 1, mask = 11...11 and this is identical to secp256k1_scalar_negate */ + uint32_t mask = !flag - 1; + uint32_t nonzero = 0xFFFFFFFFUL * (secp256k1_scalar_is_zero(r) == 0); + uint64_t t = (uint64_t)(r->d[0] ^ mask) + ((SECP256K1_N_0 + 1) & mask); + r->d[0] = t & nonzero; t >>= 32; + t += (uint64_t)(r->d[1] ^ mask) + (SECP256K1_N_1 & mask); + r->d[1] = t & nonzero; t >>= 32; + t += (uint64_t)(r->d[2] ^ mask) + (SECP256K1_N_2 & mask); + r->d[2] = t & nonzero; t >>= 32; + t += (uint64_t)(r->d[3] ^ mask) + (SECP256K1_N_3 & mask); + r->d[3] = t & nonzero; t >>= 32; + t += (uint64_t)(r->d[4] ^ mask) + (SECP256K1_N_4 & mask); + r->d[4] = t & nonzero; t >>= 32; + t += (uint64_t)(r->d[5] ^ mask) + (SECP256K1_N_5 & mask); + r->d[5] = t & nonzero; t >>= 32; + t += (uint64_t)(r->d[6] ^ mask) + (SECP256K1_N_6 & mask); + r->d[6] = t & nonzero; t >>= 32; + t += (uint64_t)(r->d[7] ^ mask) + (SECP256K1_N_7 & mask); + r->d[7] = t & nonzero; + return 2 * (mask == 0) - 1; +} + + +/* Inspired by the macros in OpenSSL's crypto/bn/asm/x86_64-gcc.c. */ + +/** Add a*b to the number defined by (c0,c1,c2). c2 must never overflow. */ +#define muladd(a,b) { \ + uint32_t tl, th; \ + { \ + uint64_t t = (uint64_t)a * b; \ + th = t >> 32; /* at most 0xFFFFFFFE */ \ + tl = t; \ + } \ + c0 += tl; /* overflow is handled on the next line */ \ + th += (c0 < tl) ? 1 : 0; /* at most 0xFFFFFFFF */ \ + c1 += th; /* overflow is handled on the next line */ \ + c2 += (c1 < th) ? 1 : 0; /* never overflows by contract (verified in the next line) */ \ + VERIFY_CHECK((c1 >= th) || (c2 != 0)); \ +} + +/** Add a*b to the number defined by (c0,c1). c1 must never overflow. */ +#define muladd_fast(a,b) { \ + uint32_t tl, th; \ + { \ + uint64_t t = (uint64_t)a * b; \ + th = t >> 32; /* at most 0xFFFFFFFE */ \ + tl = t; \ + } \ + c0 += tl; /* overflow is handled on the next line */ \ + th += (c0 < tl) ? 1 : 0; /* at most 0xFFFFFFFF */ \ + c1 += th; /* never overflows by contract (verified in the next line) */ \ + VERIFY_CHECK(c1 >= th); \ +} + +/** Add 2*a*b to the number defined by (c0,c1,c2). c2 must never overflow. */ +#define muladd2(a,b) { \ + uint32_t tl, th, th2, tl2; \ + { \ + uint64_t t = (uint64_t)a * b; \ + th = t >> 32; /* at most 0xFFFFFFFE */ \ + tl = t; \ + } \ + th2 = th + th; /* at most 0xFFFFFFFE (in case th was 0x7FFFFFFF) */ \ + c2 += (th2 < th) ? 1 : 0; /* never overflows by contract (verified the next line) */ \ + VERIFY_CHECK((th2 >= th) || (c2 != 0)); \ + tl2 = tl + tl; /* at most 0xFFFFFFFE (in case the lowest 63 bits of tl were 0x7FFFFFFF) */ \ + th2 += (tl2 < tl) ? 1 : 0; /* at most 0xFFFFFFFF */ \ + c0 += tl2; /* overflow is handled on the next line */ \ + th2 += (c0 < tl2) ? 1 : 0; /* second overflow is handled on the next line */ \ + c2 += (c0 < tl2) & (th2 == 0); /* never overflows by contract (verified the next line) */ \ + VERIFY_CHECK((c0 >= tl2) || (th2 != 0) || (c2 != 0)); \ + c1 += th2; /* overflow is handled on the next line */ \ + c2 += (c1 < th2) ? 1 : 0; /* never overflows by contract (verified the next line) */ \ + VERIFY_CHECK((c1 >= th2) || (c2 != 0)); \ +} + +/** Add a to the number defined by (c0,c1,c2). c2 must never overflow. */ +#define sumadd(a) { \ + unsigned int over; \ + c0 += (a); /* overflow is handled on the next line */ \ + over = (c0 < (a)) ? 1 : 0; \ + c1 += over; /* overflow is handled on the next line */ \ + c2 += (c1 < over) ? 1 : 0; /* never overflows by contract */ \ +} + +/** Add a to the number defined by (c0,c1). c1 must never overflow, c2 must be zero. */ +#define sumadd_fast(a) { \ + c0 += (a); /* overflow is handled on the next line */ \ + c1 += (c0 < (a)) ? 1 : 0; /* never overflows by contract (verified the next line) */ \ + VERIFY_CHECK((c1 != 0) | (c0 >= (a))); \ + VERIFY_CHECK(c2 == 0); \ +} + +/** Extract the lowest 32 bits of (c0,c1,c2) into n, and left shift the number 32 bits. */ +#define extract(n) { \ + (n) = c0; \ + c0 = c1; \ + c1 = c2; \ + c2 = 0; \ +} + +/** Extract the lowest 32 bits of (c0,c1,c2) into n, and left shift the number 32 bits. c2 is required to be zero. */ +#define extract_fast(n) { \ + (n) = c0; \ + c0 = c1; \ + c1 = 0; \ + VERIFY_CHECK(c2 == 0); \ +} + +static void secp256k1_scalar_reduce_512(secp256k1_scalar *r, const uint32_t *l) { + uint64_t c; + uint32_t n0 = l[8], n1 = l[9], n2 = l[10], n3 = l[11], n4 = l[12], n5 = l[13], n6 = l[14], n7 = l[15]; + uint32_t m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12; + uint32_t p0, p1, p2, p3, p4, p5, p6, p7, p8; + + /* 96 bit accumulator. */ + uint32_t c0, c1, c2; + + /* Reduce 512 bits into 385. */ + /* m[0..12] = l[0..7] + n[0..7] * SECP256K1_N_C. */ + c0 = l[0]; c1 = 0; c2 = 0; + muladd_fast(n0, SECP256K1_N_C_0); + extract_fast(m0); + sumadd_fast(l[1]); + muladd(n1, SECP256K1_N_C_0); + muladd(n0, SECP256K1_N_C_1); + extract(m1); + sumadd(l[2]); + muladd(n2, SECP256K1_N_C_0); + muladd(n1, SECP256K1_N_C_1); + muladd(n0, SECP256K1_N_C_2); + extract(m2); + sumadd(l[3]); + muladd(n3, SECP256K1_N_C_0); + muladd(n2, SECP256K1_N_C_1); + muladd(n1, SECP256K1_N_C_2); + muladd(n0, SECP256K1_N_C_3); + extract(m3); + sumadd(l[4]); + muladd(n4, SECP256K1_N_C_0); + muladd(n3, SECP256K1_N_C_1); + muladd(n2, SECP256K1_N_C_2); + muladd(n1, SECP256K1_N_C_3); + sumadd(n0); + extract(m4); + sumadd(l[5]); + muladd(n5, SECP256K1_N_C_0); + muladd(n4, SECP256K1_N_C_1); + muladd(n3, SECP256K1_N_C_2); + muladd(n2, SECP256K1_N_C_3); + sumadd(n1); + extract(m5); + sumadd(l[6]); + muladd(n6, SECP256K1_N_C_0); + muladd(n5, SECP256K1_N_C_1); + muladd(n4, SECP256K1_N_C_2); + muladd(n3, SECP256K1_N_C_3); + sumadd(n2); + extract(m6); + sumadd(l[7]); + muladd(n7, SECP256K1_N_C_0); + muladd(n6, SECP256K1_N_C_1); + muladd(n5, SECP256K1_N_C_2); + muladd(n4, SECP256K1_N_C_3); + sumadd(n3); + extract(m7); + muladd(n7, SECP256K1_N_C_1); + muladd(n6, SECP256K1_N_C_2); + muladd(n5, SECP256K1_N_C_3); + sumadd(n4); + extract(m8); + muladd(n7, SECP256K1_N_C_2); + muladd(n6, SECP256K1_N_C_3); + sumadd(n5); + extract(m9); + muladd(n7, SECP256K1_N_C_3); + sumadd(n6); + extract(m10); + sumadd_fast(n7); + extract_fast(m11); + VERIFY_CHECK(c0 <= 1); + m12 = c0; + + /* Reduce 385 bits into 258. */ + /* p[0..8] = m[0..7] + m[8..12] * SECP256K1_N_C. */ + c0 = m0; c1 = 0; c2 = 0; + muladd_fast(m8, SECP256K1_N_C_0); + extract_fast(p0); + sumadd_fast(m1); + muladd(m9, SECP256K1_N_C_0); + muladd(m8, SECP256K1_N_C_1); + extract(p1); + sumadd(m2); + muladd(m10, SECP256K1_N_C_0); + muladd(m9, SECP256K1_N_C_1); + muladd(m8, SECP256K1_N_C_2); + extract(p2); + sumadd(m3); + muladd(m11, SECP256K1_N_C_0); + muladd(m10, SECP256K1_N_C_1); + muladd(m9, SECP256K1_N_C_2); + muladd(m8, SECP256K1_N_C_3); + extract(p3); + sumadd(m4); + muladd(m12, SECP256K1_N_C_0); + muladd(m11, SECP256K1_N_C_1); + muladd(m10, SECP256K1_N_C_2); + muladd(m9, SECP256K1_N_C_3); + sumadd(m8); + extract(p4); + sumadd(m5); + muladd(m12, SECP256K1_N_C_1); + muladd(m11, SECP256K1_N_C_2); + muladd(m10, SECP256K1_N_C_3); + sumadd(m9); + extract(p5); + sumadd(m6); + muladd(m12, SECP256K1_N_C_2); + muladd(m11, SECP256K1_N_C_3); + sumadd(m10); + extract(p6); + sumadd_fast(m7); + muladd_fast(m12, SECP256K1_N_C_3); + sumadd_fast(m11); + extract_fast(p7); + p8 = c0 + m12; + VERIFY_CHECK(p8 <= 2); + + /* Reduce 258 bits into 256. */ + /* r[0..7] = p[0..7] + p[8] * SECP256K1_N_C. */ + c = p0 + (uint64_t)SECP256K1_N_C_0 * p8; + r->d[0] = c & 0xFFFFFFFFUL; c >>= 32; + c += p1 + (uint64_t)SECP256K1_N_C_1 * p8; + r->d[1] = c & 0xFFFFFFFFUL; c >>= 32; + c += p2 + (uint64_t)SECP256K1_N_C_2 * p8; + r->d[2] = c & 0xFFFFFFFFUL; c >>= 32; + c += p3 + (uint64_t)SECP256K1_N_C_3 * p8; + r->d[3] = c & 0xFFFFFFFFUL; c >>= 32; + c += p4 + (uint64_t)p8; + r->d[4] = c & 0xFFFFFFFFUL; c >>= 32; + c += p5; + r->d[5] = c & 0xFFFFFFFFUL; c >>= 32; + c += p6; + r->d[6] = c & 0xFFFFFFFFUL; c >>= 32; + c += p7; + r->d[7] = c & 0xFFFFFFFFUL; c >>= 32; + + /* Final reduction of r. */ + secp256k1_scalar_reduce(r, c + secp256k1_scalar_check_overflow(r)); +} + +static void secp256k1_scalar_mul_512(uint32_t *l, const secp256k1_scalar *a, const secp256k1_scalar *b) { + /* 96 bit accumulator. */ + uint32_t c0 = 0, c1 = 0, c2 = 0; + + /* l[0..15] = a[0..7] * b[0..7]. */ + muladd_fast(a->d[0], b->d[0]); + extract_fast(l[0]); + muladd(a->d[0], b->d[1]); + muladd(a->d[1], b->d[0]); + extract(l[1]); + muladd(a->d[0], b->d[2]); + muladd(a->d[1], b->d[1]); + muladd(a->d[2], b->d[0]); + extract(l[2]); + muladd(a->d[0], b->d[3]); + muladd(a->d[1], b->d[2]); + muladd(a->d[2], b->d[1]); + muladd(a->d[3], b->d[0]); + extract(l[3]); + muladd(a->d[0], b->d[4]); + muladd(a->d[1], b->d[3]); + muladd(a->d[2], b->d[2]); + muladd(a->d[3], b->d[1]); + muladd(a->d[4], b->d[0]); + extract(l[4]); + muladd(a->d[0], b->d[5]); + muladd(a->d[1], b->d[4]); + muladd(a->d[2], b->d[3]); + muladd(a->d[3], b->d[2]); + muladd(a->d[4], b->d[1]); + muladd(a->d[5], b->d[0]); + extract(l[5]); + muladd(a->d[0], b->d[6]); + muladd(a->d[1], b->d[5]); + muladd(a->d[2], b->d[4]); + muladd(a->d[3], b->d[3]); + muladd(a->d[4], b->d[2]); + muladd(a->d[5], b->d[1]); + muladd(a->d[6], b->d[0]); + extract(l[6]); + muladd(a->d[0], b->d[7]); + muladd(a->d[1], b->d[6]); + muladd(a->d[2], b->d[5]); + muladd(a->d[3], b->d[4]); + muladd(a->d[4], b->d[3]); + muladd(a->d[5], b->d[2]); + muladd(a->d[6], b->d[1]); + muladd(a->d[7], b->d[0]); + extract(l[7]); + muladd(a->d[1], b->d[7]); + muladd(a->d[2], b->d[6]); + muladd(a->d[3], b->d[5]); + muladd(a->d[4], b->d[4]); + muladd(a->d[5], b->d[3]); + muladd(a->d[6], b->d[2]); + muladd(a->d[7], b->d[1]); + extract(l[8]); + muladd(a->d[2], b->d[7]); + muladd(a->d[3], b->d[6]); + muladd(a->d[4], b->d[5]); + muladd(a->d[5], b->d[4]); + muladd(a->d[6], b->d[3]); + muladd(a->d[7], b->d[2]); + extract(l[9]); + muladd(a->d[3], b->d[7]); + muladd(a->d[4], b->d[6]); + muladd(a->d[5], b->d[5]); + muladd(a->d[6], b->d[4]); + muladd(a->d[7], b->d[3]); + extract(l[10]); + muladd(a->d[4], b->d[7]); + muladd(a->d[5], b->d[6]); + muladd(a->d[6], b->d[5]); + muladd(a->d[7], b->d[4]); + extract(l[11]); + muladd(a->d[5], b->d[7]); + muladd(a->d[6], b->d[6]); + muladd(a->d[7], b->d[5]); + extract(l[12]); + muladd(a->d[6], b->d[7]); + muladd(a->d[7], b->d[6]); + extract(l[13]); + muladd_fast(a->d[7], b->d[7]); + extract_fast(l[14]); + VERIFY_CHECK(c1 == 0); + l[15] = c0; +} + +static void secp256k1_scalar_sqr_512(uint32_t *l, const secp256k1_scalar *a) { + /* 96 bit accumulator. */ + uint32_t c0 = 0, c1 = 0, c2 = 0; + + /* l[0..15] = a[0..7]^2. */ + muladd_fast(a->d[0], a->d[0]); + extract_fast(l[0]); + muladd2(a->d[0], a->d[1]); + extract(l[1]); + muladd2(a->d[0], a->d[2]); + muladd(a->d[1], a->d[1]); + extract(l[2]); + muladd2(a->d[0], a->d[3]); + muladd2(a->d[1], a->d[2]); + extract(l[3]); + muladd2(a->d[0], a->d[4]); + muladd2(a->d[1], a->d[3]); + muladd(a->d[2], a->d[2]); + extract(l[4]); + muladd2(a->d[0], a->d[5]); + muladd2(a->d[1], a->d[4]); + muladd2(a->d[2], a->d[3]); + extract(l[5]); + muladd2(a->d[0], a->d[6]); + muladd2(a->d[1], a->d[5]); + muladd2(a->d[2], a->d[4]); + muladd(a->d[3], a->d[3]); + extract(l[6]); + muladd2(a->d[0], a->d[7]); + muladd2(a->d[1], a->d[6]); + muladd2(a->d[2], a->d[5]); + muladd2(a->d[3], a->d[4]); + extract(l[7]); + muladd2(a->d[1], a->d[7]); + muladd2(a->d[2], a->d[6]); + muladd2(a->d[3], a->d[5]); + muladd(a->d[4], a->d[4]); + extract(l[8]); + muladd2(a->d[2], a->d[7]); + muladd2(a->d[3], a->d[6]); + muladd2(a->d[4], a->d[5]); + extract(l[9]); + muladd2(a->d[3], a->d[7]); + muladd2(a->d[4], a->d[6]); + muladd(a->d[5], a->d[5]); + extract(l[10]); + muladd2(a->d[4], a->d[7]); + muladd2(a->d[5], a->d[6]); + extract(l[11]); + muladd2(a->d[5], a->d[7]); + muladd(a->d[6], a->d[6]); + extract(l[12]); + muladd2(a->d[6], a->d[7]); + extract(l[13]); + muladd_fast(a->d[7], a->d[7]); + extract_fast(l[14]); + VERIFY_CHECK(c1 == 0); + l[15] = c0; +} + +#undef sumadd +#undef sumadd_fast +#undef muladd +#undef muladd_fast +#undef muladd2 +#undef extract +#undef extract_fast + +static void secp256k1_scalar_mul(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b) { + uint32_t l[16]; + secp256k1_scalar_mul_512(l, a, b); + secp256k1_scalar_reduce_512(r, l); +} + +static int secp256k1_scalar_shr_int(secp256k1_scalar *r, int n) { + int ret; + VERIFY_CHECK(n > 0); + VERIFY_CHECK(n < 16); + ret = r->d[0] & ((1 << n) - 1); + r->d[0] = (r->d[0] >> n) + (r->d[1] << (32 - n)); + r->d[1] = (r->d[1] >> n) + (r->d[2] << (32 - n)); + r->d[2] = (r->d[2] >> n) + (r->d[3] << (32 - n)); + r->d[3] = (r->d[3] >> n) + (r->d[4] << (32 - n)); + r->d[4] = (r->d[4] >> n) + (r->d[5] << (32 - n)); + r->d[5] = (r->d[5] >> n) + (r->d[6] << (32 - n)); + r->d[6] = (r->d[6] >> n) + (r->d[7] << (32 - n)); + r->d[7] = (r->d[7] >> n); + return ret; +} + +static void secp256k1_scalar_sqr(secp256k1_scalar *r, const secp256k1_scalar *a) { + uint32_t l[16]; + secp256k1_scalar_sqr_512(l, a); + secp256k1_scalar_reduce_512(r, l); +} + +#ifdef USE_ENDOMORPHISM +static void secp256k1_scalar_split_128(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *a) { + r1->d[0] = a->d[0]; + r1->d[1] = a->d[1]; + r1->d[2] = a->d[2]; + r1->d[3] = a->d[3]; + r1->d[4] = 0; + r1->d[5] = 0; + r1->d[6] = 0; + r1->d[7] = 0; + r2->d[0] = a->d[4]; + r2->d[1] = a->d[5]; + r2->d[2] = a->d[6]; + r2->d[3] = a->d[7]; + r2->d[4] = 0; + r2->d[5] = 0; + r2->d[6] = 0; + r2->d[7] = 0; +} +#endif + +SECP256K1_INLINE static int secp256k1_scalar_eq(const secp256k1_scalar *a, const secp256k1_scalar *b) { + return ((a->d[0] ^ b->d[0]) | (a->d[1] ^ b->d[1]) | (a->d[2] ^ b->d[2]) | (a->d[3] ^ b->d[3]) | (a->d[4] ^ b->d[4]) | (a->d[5] ^ b->d[5]) | (a->d[6] ^ b->d[6]) | (a->d[7] ^ b->d[7])) == 0; +} + +SECP256K1_INLINE static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b, unsigned int shift) { + uint32_t l[16]; + unsigned int shiftlimbs; + unsigned int shiftlow; + unsigned int shifthigh; + VERIFY_CHECK(shift >= 256); + secp256k1_scalar_mul_512(l, a, b); + shiftlimbs = shift >> 5; + shiftlow = shift & 0x1F; + shifthigh = 32 - shiftlow; + r->d[0] = shift < 512 ? (l[0 + shiftlimbs] >> shiftlow | (shift < 480 && shiftlow ? (l[1 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[1] = shift < 480 ? (l[1 + shiftlimbs] >> shiftlow | (shift < 448 && shiftlow ? (l[2 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[2] = shift < 448 ? (l[2 + shiftlimbs] >> shiftlow | (shift < 416 && shiftlow ? (l[3 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[3] = shift < 416 ? (l[3 + shiftlimbs] >> shiftlow | (shift < 384 && shiftlow ? (l[4 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[4] = shift < 384 ? (l[4 + shiftlimbs] >> shiftlow | (shift < 352 && shiftlow ? (l[5 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[5] = shift < 352 ? (l[5 + shiftlimbs] >> shiftlow | (shift < 320 && shiftlow ? (l[6 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[6] = shift < 320 ? (l[6 + shiftlimbs] >> shiftlow | (shift < 288 && shiftlow ? (l[7 + shiftlimbs] << shifthigh) : 0)) : 0; + r->d[7] = shift < 288 ? (l[7 + shiftlimbs] >> shiftlow) : 0; + secp256k1_scalar_cadd_bit(r, 0, (l[(shift - 1) >> 5] >> ((shift - 1) & 0x1f)) & 1); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/scalar_impl.h b/crypto/secp256k1/libsecp256k1/src/scalar_impl.h new file mode 100644 index 0000000..f5b2376 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/scalar_impl.h @@ -0,0 +1,370 @@ +/********************************************************************** + * Copyright (c) 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_SCALAR_IMPL_H_ +#define _SECP256K1_SCALAR_IMPL_H_ + +#include "group.h" +#include "scalar.h" + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#if defined(EXHAUSTIVE_TEST_ORDER) +#include "scalar_low_impl.h" +#elif defined(USE_SCALAR_4X64) +#include "scalar_4x64_impl.h" +#elif defined(USE_SCALAR_8X32) +#include "scalar_8x32_impl.h" +#else +#error "Please select scalar implementation" +#endif + +#ifndef USE_NUM_NONE +static void secp256k1_scalar_get_num(secp256k1_num *r, const secp256k1_scalar *a) { + unsigned char c[32]; + secp256k1_scalar_get_b32(c, a); + secp256k1_num_set_bin(r, c, 32); +} + +/** secp256k1 curve order, see secp256k1_ecdsa_const_order_as_fe in ecdsa_impl.h */ +static void secp256k1_scalar_order_get_num(secp256k1_num *r) { +#if defined(EXHAUSTIVE_TEST_ORDER) + static const unsigned char order[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,EXHAUSTIVE_TEST_ORDER + }; +#else + static const unsigned char order[32] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE, + 0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,0x3B, + 0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41 + }; +#endif + secp256k1_num_set_bin(r, order, 32); +} +#endif + +static void secp256k1_scalar_inverse(secp256k1_scalar *r, const secp256k1_scalar *x) { +#if defined(EXHAUSTIVE_TEST_ORDER) + int i; + *r = 0; + for (i = 0; i < EXHAUSTIVE_TEST_ORDER; i++) + if ((i * *x) % EXHAUSTIVE_TEST_ORDER == 1) + *r = i; + /* If this VERIFY_CHECK triggers we were given a noninvertible scalar (and thus + * have a composite group order; fix it in exhaustive_tests.c). */ + VERIFY_CHECK(*r != 0); +} +#else + secp256k1_scalar *t; + int i; + /* First compute x ^ (2^N - 1) for some values of N. */ + secp256k1_scalar x2, x3, x4, x6, x7, x8, x15, x30, x60, x120, x127; + + secp256k1_scalar_sqr(&x2, x); + secp256k1_scalar_mul(&x2, &x2, x); + + secp256k1_scalar_sqr(&x3, &x2); + secp256k1_scalar_mul(&x3, &x3, x); + + secp256k1_scalar_sqr(&x4, &x3); + secp256k1_scalar_mul(&x4, &x4, x); + + secp256k1_scalar_sqr(&x6, &x4); + secp256k1_scalar_sqr(&x6, &x6); + secp256k1_scalar_mul(&x6, &x6, &x2); + + secp256k1_scalar_sqr(&x7, &x6); + secp256k1_scalar_mul(&x7, &x7, x); + + secp256k1_scalar_sqr(&x8, &x7); + secp256k1_scalar_mul(&x8, &x8, x); + + secp256k1_scalar_sqr(&x15, &x8); + for (i = 0; i < 6; i++) { + secp256k1_scalar_sqr(&x15, &x15); + } + secp256k1_scalar_mul(&x15, &x15, &x7); + + secp256k1_scalar_sqr(&x30, &x15); + for (i = 0; i < 14; i++) { + secp256k1_scalar_sqr(&x30, &x30); + } + secp256k1_scalar_mul(&x30, &x30, &x15); + + secp256k1_scalar_sqr(&x60, &x30); + for (i = 0; i < 29; i++) { + secp256k1_scalar_sqr(&x60, &x60); + } + secp256k1_scalar_mul(&x60, &x60, &x30); + + secp256k1_scalar_sqr(&x120, &x60); + for (i = 0; i < 59; i++) { + secp256k1_scalar_sqr(&x120, &x120); + } + secp256k1_scalar_mul(&x120, &x120, &x60); + + secp256k1_scalar_sqr(&x127, &x120); + for (i = 0; i < 6; i++) { + secp256k1_scalar_sqr(&x127, &x127); + } + secp256k1_scalar_mul(&x127, &x127, &x7); + + /* Then accumulate the final result (t starts at x127). */ + t = &x127; + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 4; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x3); /* 111 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 4; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x3); /* 111 */ + for (i = 0; i < 3; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x2); /* 11 */ + for (i = 0; i < 4; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x3); /* 111 */ + for (i = 0; i < 5; i++) { /* 00 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x3); /* 111 */ + for (i = 0; i < 4; i++) { /* 00 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x2); /* 11 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 5; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x4); /* 1111 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 3; i++) { /* 00 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 4; i++) { /* 000 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 10; i++) { /* 0000000 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x3); /* 111 */ + for (i = 0; i < 4; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x3); /* 111 */ + for (i = 0; i < 9; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x8); /* 11111111 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 3; i++) { /* 00 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 3; i++) { /* 00 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 5; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x4); /* 1111 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 5; i++) { /* 000 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x2); /* 11 */ + for (i = 0; i < 4; i++) { /* 00 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x2); /* 11 */ + for (i = 0; i < 2; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 8; i++) { /* 000000 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x2); /* 11 */ + for (i = 0; i < 3; i++) { /* 0 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, &x2); /* 11 */ + for (i = 0; i < 3; i++) { /* 00 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 6; i++) { /* 00000 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(t, t, x); /* 1 */ + for (i = 0; i < 8; i++) { /* 00 */ + secp256k1_scalar_sqr(t, t); + } + secp256k1_scalar_mul(r, t, &x6); /* 111111 */ +} + +SECP256K1_INLINE static int secp256k1_scalar_is_even(const secp256k1_scalar *a) { + return !(a->d[0] & 1); +} +#endif + +static void secp256k1_scalar_inverse_var(secp256k1_scalar *r, const secp256k1_scalar *x) { +#if defined(USE_SCALAR_INV_BUILTIN) + secp256k1_scalar_inverse(r, x); +#elif defined(USE_SCALAR_INV_NUM) + unsigned char b[32]; + secp256k1_num n, m; + secp256k1_scalar t = *x; + secp256k1_scalar_get_b32(b, &t); + secp256k1_num_set_bin(&n, b, 32); + secp256k1_scalar_order_get_num(&m); + secp256k1_num_mod_inverse(&n, &n, &m); + secp256k1_num_get_bin(b, 32, &n); + secp256k1_scalar_set_b32(r, b, NULL); + /* Verify that the inverse was computed correctly, without GMP code. */ + secp256k1_scalar_mul(&t, &t, r); + CHECK(secp256k1_scalar_is_one(&t)); +#else +#error "Please select scalar inverse implementation" +#endif +} + +#ifdef USE_ENDOMORPHISM +#if defined(EXHAUSTIVE_TEST_ORDER) +/** + * Find k1 and k2 given k, such that k1 + k2 * lambda == k mod n; unlike in the + * full case we don't bother making k1 and k2 be small, we just want them to be + * nontrivial to get full test coverage for the exhaustive tests. We therefore + * (arbitrarily) set k2 = k + 5 and k1 = k - k2 * lambda. + */ +static void secp256k1_scalar_split_lambda(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *a) { + *r2 = (*a + 5) % EXHAUSTIVE_TEST_ORDER; + *r1 = (*a + (EXHAUSTIVE_TEST_ORDER - *r2) * EXHAUSTIVE_TEST_LAMBDA) % EXHAUSTIVE_TEST_ORDER; +} +#else +/** + * The Secp256k1 curve has an endomorphism, where lambda * (x, y) = (beta * x, y), where + * lambda is {0x53,0x63,0xad,0x4c,0xc0,0x5c,0x30,0xe0,0xa5,0x26,0x1c,0x02,0x88,0x12,0x64,0x5a, + * 0x12,0x2e,0x22,0xea,0x20,0x81,0x66,0x78,0xdf,0x02,0x96,0x7c,0x1b,0x23,0xbd,0x72} + * + * "Guide to Elliptic Curve Cryptography" (Hankerson, Menezes, Vanstone) gives an algorithm + * (algorithm 3.74) to find k1 and k2 given k, such that k1 + k2 * lambda == k mod n, and k1 + * and k2 have a small size. + * It relies on constants a1, b1, a2, b2. These constants for the value of lambda above are: + * + * - a1 = {0x30,0x86,0xd2,0x21,0xa7,0xd4,0x6b,0xcd,0xe8,0x6c,0x90,0xe4,0x92,0x84,0xeb,0x15} + * - b1 = -{0xe4,0x43,0x7e,0xd6,0x01,0x0e,0x88,0x28,0x6f,0x54,0x7f,0xa9,0x0a,0xbf,0xe4,0xc3} + * - a2 = {0x01,0x14,0xca,0x50,0xf7,0xa8,0xe2,0xf3,0xf6,0x57,0xc1,0x10,0x8d,0x9d,0x44,0xcf,0xd8} + * - b2 = {0x30,0x86,0xd2,0x21,0xa7,0xd4,0x6b,0xcd,0xe8,0x6c,0x90,0xe4,0x92,0x84,0xeb,0x15} + * + * The algorithm then computes c1 = round(b1 * k / n) and c2 = round(b2 * k / n), and gives + * k1 = k - (c1*a1 + c2*a2) and k2 = -(c1*b1 + c2*b2). Instead, we use modular arithmetic, and + * compute k1 as k - k2 * lambda, avoiding the need for constants a1 and a2. + * + * g1, g2 are precomputed constants used to replace division with a rounded multiplication + * when decomposing the scalar for an endomorphism-based point multiplication. + * + * The possibility of using precomputed estimates is mentioned in "Guide to Elliptic Curve + * Cryptography" (Hankerson, Menezes, Vanstone) in section 3.5. + * + * The derivation is described in the paper "Efficient Software Implementation of Public-Key + * Cryptography on Sensor Networks Using the MSP430X Microcontroller" (Gouvea, Oliveira, Lopez), + * Section 4.3 (here we use a somewhat higher-precision estimate): + * d = a1*b2 - b1*a2 + * g1 = round((2^272)*b2/d) + * g2 = round((2^272)*b1/d) + * + * (Note that 'd' is also equal to the curve order here because [a1,b1] and [a2,b2] are found + * as outputs of the Extended Euclidean Algorithm on inputs 'order' and 'lambda'). + * + * The function below splits a in r1 and r2, such that r1 + lambda * r2 == a (mod order). + */ + +static void secp256k1_scalar_split_lambda(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *a) { + secp256k1_scalar c1, c2; + static const secp256k1_scalar minus_lambda = SECP256K1_SCALAR_CONST( + 0xAC9C52B3UL, 0x3FA3CF1FUL, 0x5AD9E3FDUL, 0x77ED9BA4UL, + 0xA880B9FCUL, 0x8EC739C2UL, 0xE0CFC810UL, 0xB51283CFUL + ); + static const secp256k1_scalar minus_b1 = SECP256K1_SCALAR_CONST( + 0x00000000UL, 0x00000000UL, 0x00000000UL, 0x00000000UL, + 0xE4437ED6UL, 0x010E8828UL, 0x6F547FA9UL, 0x0ABFE4C3UL + ); + static const secp256k1_scalar minus_b2 = SECP256K1_SCALAR_CONST( + 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFEUL, + 0x8A280AC5UL, 0x0774346DUL, 0xD765CDA8UL, 0x3DB1562CUL + ); + static const secp256k1_scalar g1 = SECP256K1_SCALAR_CONST( + 0x00000000UL, 0x00000000UL, 0x00000000UL, 0x00003086UL, + 0xD221A7D4UL, 0x6BCDE86CUL, 0x90E49284UL, 0xEB153DABUL + ); + static const secp256k1_scalar g2 = SECP256K1_SCALAR_CONST( + 0x00000000UL, 0x00000000UL, 0x00000000UL, 0x0000E443UL, + 0x7ED6010EUL, 0x88286F54UL, 0x7FA90ABFUL, 0xE4C42212UL + ); + VERIFY_CHECK(r1 != a); + VERIFY_CHECK(r2 != a); + /* these _var calls are constant time since the shift amount is constant */ + secp256k1_scalar_mul_shift_var(&c1, a, &g1, 272); + secp256k1_scalar_mul_shift_var(&c2, a, &g2, 272); + secp256k1_scalar_mul(&c1, &c1, &minus_b1); + secp256k1_scalar_mul(&c2, &c2, &minus_b2); + secp256k1_scalar_add(r2, &c1, &c2); + secp256k1_scalar_mul(r1, r2, &minus_lambda); + secp256k1_scalar_add(r1, r1, a); +} +#endif +#endif + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/scalar_low.h b/crypto/secp256k1/libsecp256k1/src/scalar_low.h new file mode 100644 index 0000000..5574c44 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/scalar_low.h @@ -0,0 +1,15 @@ +/********************************************************************** + * Copyright (c) 2015 Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_SCALAR_REPR_ +#define _SECP256K1_SCALAR_REPR_ + +#include + +/** A scalar modulo the group order of the secp256k1 curve. */ +typedef uint32_t secp256k1_scalar; + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/scalar_low_impl.h b/crypto/secp256k1/libsecp256k1/src/scalar_low_impl.h new file mode 100644 index 0000000..4f94441 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/scalar_low_impl.h @@ -0,0 +1,114 @@ +/********************************************************************** + * Copyright (c) 2015 Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_SCALAR_REPR_IMPL_H_ +#define _SECP256K1_SCALAR_REPR_IMPL_H_ + +#include "scalar.h" + +#include + +SECP256K1_INLINE static int secp256k1_scalar_is_even(const secp256k1_scalar *a) { + return !(*a & 1); +} + +SECP256K1_INLINE static void secp256k1_scalar_clear(secp256k1_scalar *r) { *r = 0; } +SECP256K1_INLINE static void secp256k1_scalar_set_int(secp256k1_scalar *r, unsigned int v) { *r = v; } + +SECP256K1_INLINE static unsigned int secp256k1_scalar_get_bits(const secp256k1_scalar *a, unsigned int offset, unsigned int count) { + if (offset < 32) + return ((*a >> offset) & ((((uint32_t)1) << count) - 1)); + else + return 0; +} + +SECP256K1_INLINE static unsigned int secp256k1_scalar_get_bits_var(const secp256k1_scalar *a, unsigned int offset, unsigned int count) { + return secp256k1_scalar_get_bits(a, offset, count); +} + +SECP256K1_INLINE static int secp256k1_scalar_check_overflow(const secp256k1_scalar *a) { return *a >= EXHAUSTIVE_TEST_ORDER; } + +static int secp256k1_scalar_add(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b) { + *r = (*a + *b) % EXHAUSTIVE_TEST_ORDER; + return *r < *b; +} + +static void secp256k1_scalar_cadd_bit(secp256k1_scalar *r, unsigned int bit, int flag) { + if (flag && bit < 32) + *r += (1 << bit); +#ifdef VERIFY + VERIFY_CHECK(secp256k1_scalar_check_overflow(r) == 0); +#endif +} + +static void secp256k1_scalar_set_b32(secp256k1_scalar *r, const unsigned char *b32, int *overflow) { + const int base = 0x100 % EXHAUSTIVE_TEST_ORDER; + int i; + *r = 0; + for (i = 0; i < 32; i++) { + *r = ((*r * base) + b32[i]) % EXHAUSTIVE_TEST_ORDER; + } + /* just deny overflow, it basically always happens */ + if (overflow) *overflow = 0; +} + +static void secp256k1_scalar_get_b32(unsigned char *bin, const secp256k1_scalar* a) { + memset(bin, 0, 32); + bin[28] = *a >> 24; bin[29] = *a >> 16; bin[30] = *a >> 8; bin[31] = *a; +} + +SECP256K1_INLINE static int secp256k1_scalar_is_zero(const secp256k1_scalar *a) { + return *a == 0; +} + +static void secp256k1_scalar_negate(secp256k1_scalar *r, const secp256k1_scalar *a) { + if (*a == 0) { + *r = 0; + } else { + *r = EXHAUSTIVE_TEST_ORDER - *a; + } +} + +SECP256K1_INLINE static int secp256k1_scalar_is_one(const secp256k1_scalar *a) { + return *a == 1; +} + +static int secp256k1_scalar_is_high(const secp256k1_scalar *a) { + return *a > EXHAUSTIVE_TEST_ORDER / 2; +} + +static int secp256k1_scalar_cond_negate(secp256k1_scalar *r, int flag) { + if (flag) secp256k1_scalar_negate(r, r); + return flag ? -1 : 1; +} + +static void secp256k1_scalar_mul(secp256k1_scalar *r, const secp256k1_scalar *a, const secp256k1_scalar *b) { + *r = (*a * *b) % EXHAUSTIVE_TEST_ORDER; +} + +static int secp256k1_scalar_shr_int(secp256k1_scalar *r, int n) { + int ret; + VERIFY_CHECK(n > 0); + VERIFY_CHECK(n < 16); + ret = *r & ((1 << n) - 1); + *r >>= n; + return ret; +} + +static void secp256k1_scalar_sqr(secp256k1_scalar *r, const secp256k1_scalar *a) { + *r = (*a * *a) % EXHAUSTIVE_TEST_ORDER; +} + +static void secp256k1_scalar_split_128(secp256k1_scalar *r1, secp256k1_scalar *r2, const secp256k1_scalar *a) { + *r1 = *a; + *r2 = 0; +} + +SECP256K1_INLINE static int secp256k1_scalar_eq(const secp256k1_scalar *a, const secp256k1_scalar *b) { + return *a == *b; +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/secp256k1.c b/crypto/secp256k1/libsecp256k1/src/secp256k1.c new file mode 100755 index 0000000..7d637bf --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/secp256k1.c @@ -0,0 +1,559 @@ +/********************************************************************** + * Copyright (c) 2013-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include "include/secp256k1.h" + +#include "util.h" +#include "num_impl.h" +#include "field_impl.h" +#include "scalar_impl.h" +#include "group_impl.h" +#include "ecmult_impl.h" +#include "ecmult_const_impl.h" +#include "ecmult_gen_impl.h" +#include "ecdsa_impl.h" +#include "eckey_impl.h" +#include "hash_impl.h" + +#define ARG_CHECK(cond) do { \ + if (EXPECT(!(cond), 0)) { \ + secp256k1_callback_call(&ctx->illegal_callback, #cond); \ + return 0; \ + } \ +} while(0) + +static void default_illegal_callback_fn(const char* str, void* data) { + fprintf(stderr, "[libsecp256k1] illegal argument: %s\n", str); + abort(); +} + +static const secp256k1_callback default_illegal_callback = { + default_illegal_callback_fn, + NULL +}; + +static void default_error_callback_fn(const char* str, void* data) { + fprintf(stderr, "[libsecp256k1] internal consistency check failed: %s\n", str); + abort(); +} + +static const secp256k1_callback default_error_callback = { + default_error_callback_fn, + NULL +}; + + +struct secp256k1_context_struct { + secp256k1_ecmult_context ecmult_ctx; + secp256k1_ecmult_gen_context ecmult_gen_ctx; + secp256k1_callback illegal_callback; + secp256k1_callback error_callback; +}; + +secp256k1_context* secp256k1_context_create(unsigned int flags) { + secp256k1_context* ret = (secp256k1_context*)checked_malloc(&default_error_callback, sizeof(secp256k1_context)); + ret->illegal_callback = default_illegal_callback; + ret->error_callback = default_error_callback; + + if (EXPECT((flags & SECP256K1_FLAGS_TYPE_MASK) != SECP256K1_FLAGS_TYPE_CONTEXT, 0)) { + secp256k1_callback_call(&ret->illegal_callback, + "Invalid flags"); + free(ret); + return NULL; + } + + secp256k1_ecmult_context_init(&ret->ecmult_ctx); + secp256k1_ecmult_gen_context_init(&ret->ecmult_gen_ctx); + + if (flags & SECP256K1_FLAGS_BIT_CONTEXT_SIGN) { + secp256k1_ecmult_gen_context_build(&ret->ecmult_gen_ctx, &ret->error_callback); + } + if (flags & SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) { + secp256k1_ecmult_context_build(&ret->ecmult_ctx, &ret->error_callback); + } + + return ret; +} + +secp256k1_context* secp256k1_context_clone(const secp256k1_context* ctx) { + secp256k1_context* ret = (secp256k1_context*)checked_malloc(&ctx->error_callback, sizeof(secp256k1_context)); + ret->illegal_callback = ctx->illegal_callback; + ret->error_callback = ctx->error_callback; + secp256k1_ecmult_context_clone(&ret->ecmult_ctx, &ctx->ecmult_ctx, &ctx->error_callback); + secp256k1_ecmult_gen_context_clone(&ret->ecmult_gen_ctx, &ctx->ecmult_gen_ctx, &ctx->error_callback); + return ret; +} + +void secp256k1_context_destroy(secp256k1_context* ctx) { + if (ctx != NULL) { + secp256k1_ecmult_context_clear(&ctx->ecmult_ctx); + secp256k1_ecmult_gen_context_clear(&ctx->ecmult_gen_ctx); + + free(ctx); + } +} + +void secp256k1_context_set_illegal_callback(secp256k1_context* ctx, void (*fun)(const char* message, void* data), const void* data) { + if (fun == NULL) { + fun = default_illegal_callback_fn; + } + ctx->illegal_callback.fn = fun; + ctx->illegal_callback.data = data; +} + +void secp256k1_context_set_error_callback(secp256k1_context* ctx, void (*fun)(const char* message, void* data), const void* data) { + if (fun == NULL) { + fun = default_error_callback_fn; + } + ctx->error_callback.fn = fun; + ctx->error_callback.data = data; +} + +static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey) { + if (sizeof(secp256k1_ge_storage) == 64) { + /* When the secp256k1_ge_storage type is exactly 64 byte, use its + * representation inside secp256k1_pubkey, as conversion is very fast. + * Note that secp256k1_pubkey_save must use the same representation. */ + secp256k1_ge_storage s; + memcpy(&s, &pubkey->data[0], 64); + secp256k1_ge_from_storage(ge, &s); + } else { + /* Otherwise, fall back to 32-byte big endian for X and Y. */ + secp256k1_fe x, y; + secp256k1_fe_set_b32(&x, pubkey->data); + secp256k1_fe_set_b32(&y, pubkey->data + 32); + secp256k1_ge_set_xy(ge, &x, &y); + } + ARG_CHECK(!secp256k1_fe_is_zero(&ge->x)); + return 1; +} + +static void secp256k1_pubkey_save(secp256k1_pubkey* pubkey, secp256k1_ge* ge) { + if (sizeof(secp256k1_ge_storage) == 64) { + secp256k1_ge_storage s; + secp256k1_ge_to_storage(&s, ge); + memcpy(&pubkey->data[0], &s, 64); + } else { + VERIFY_CHECK(!secp256k1_ge_is_infinity(ge)); + secp256k1_fe_normalize_var(&ge->x); + secp256k1_fe_normalize_var(&ge->y); + secp256k1_fe_get_b32(pubkey->data, &ge->x); + secp256k1_fe_get_b32(pubkey->data + 32, &ge->y); + } +} + +int secp256k1_ec_pubkey_parse(const secp256k1_context* ctx, secp256k1_pubkey* pubkey, const unsigned char *input, size_t inputlen) { + secp256k1_ge Q; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pubkey != NULL); + memset(pubkey, 0, sizeof(*pubkey)); + ARG_CHECK(input != NULL); + if (!secp256k1_eckey_pubkey_parse(&Q, input, inputlen)) { + return 0; + } + secp256k1_pubkey_save(pubkey, &Q); + secp256k1_ge_clear(&Q); + return 1; +} + +int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_pubkey* pubkey, unsigned int flags) { + secp256k1_ge Q; + size_t len; + int ret = 0; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(outputlen != NULL); + ARG_CHECK(*outputlen >= ((flags & SECP256K1_FLAGS_BIT_COMPRESSION) ? 33 : 65)); + len = *outputlen; + *outputlen = 0; + ARG_CHECK(output != NULL); + memset(output, 0, len); + ARG_CHECK(pubkey != NULL); + ARG_CHECK((flags & SECP256K1_FLAGS_TYPE_MASK) == SECP256K1_FLAGS_TYPE_COMPRESSION); + if (secp256k1_pubkey_load(ctx, &Q, pubkey)) { + ret = secp256k1_eckey_pubkey_serialize(&Q, output, &len, flags & SECP256K1_FLAGS_BIT_COMPRESSION); + if (ret) { + *outputlen = len; + } + } + return ret; +} + +static void secp256k1_ecdsa_signature_load(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, const secp256k1_ecdsa_signature* sig) { + (void)ctx; + if (sizeof(secp256k1_scalar) == 32) { + /* When the secp256k1_scalar type is exactly 32 byte, use its + * representation inside secp256k1_ecdsa_signature, as conversion is very fast. + * Note that secp256k1_ecdsa_signature_save must use the same representation. */ + memcpy(r, &sig->data[0], 32); + memcpy(s, &sig->data[32], 32); + } else { + secp256k1_scalar_set_b32(r, &sig->data[0], NULL); + secp256k1_scalar_set_b32(s, &sig->data[32], NULL); + } +} + +static void secp256k1_ecdsa_signature_save(secp256k1_ecdsa_signature* sig, const secp256k1_scalar* r, const secp256k1_scalar* s) { + if (sizeof(secp256k1_scalar) == 32) { + memcpy(&sig->data[0], r, 32); + memcpy(&sig->data[32], s, 32); + } else { + secp256k1_scalar_get_b32(&sig->data[0], r); + secp256k1_scalar_get_b32(&sig->data[32], s); + } +} + +int secp256k1_ecdsa_signature_parse_der(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen) { + secp256k1_scalar r, s; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(input != NULL); + + if (secp256k1_ecdsa_sig_parse(&r, &s, input, inputlen)) { + secp256k1_ecdsa_signature_save(sig, &r, &s); + return 1; + } else { + memset(sig, 0, sizeof(*sig)); + return 0; + } +} + +int secp256k1_ecdsa_signature_parse_compact(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input64) { + secp256k1_scalar r, s; + int ret = 1; + int overflow = 0; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(input64 != NULL); + + secp256k1_scalar_set_b32(&r, &input64[0], &overflow); + ret &= !overflow; + secp256k1_scalar_set_b32(&s, &input64[32], &overflow); + ret &= !overflow; + if (ret) { + secp256k1_ecdsa_signature_save(sig, &r, &s); + } else { + memset(sig, 0, sizeof(*sig)); + } + return ret; +} + +int secp256k1_ecdsa_signature_serialize_der(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_ecdsa_signature* sig) { + secp256k1_scalar r, s; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output != NULL); + ARG_CHECK(outputlen != NULL); + ARG_CHECK(sig != NULL); + + secp256k1_ecdsa_signature_load(ctx, &r, &s, sig); + return secp256k1_ecdsa_sig_serialize(output, outputlen, &r, &s); +} + +int secp256k1_ecdsa_signature_serialize_compact(const secp256k1_context* ctx, unsigned char *output64, const secp256k1_ecdsa_signature* sig) { + secp256k1_scalar r, s; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output64 != NULL); + ARG_CHECK(sig != NULL); + + secp256k1_ecdsa_signature_load(ctx, &r, &s, sig); + secp256k1_scalar_get_b32(&output64[0], &r); + secp256k1_scalar_get_b32(&output64[32], &s); + return 1; +} + +int secp256k1_ecdsa_signature_normalize(const secp256k1_context* ctx, secp256k1_ecdsa_signature *sigout, const secp256k1_ecdsa_signature *sigin) { + secp256k1_scalar r, s; + int ret = 0; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sigin != NULL); + + secp256k1_ecdsa_signature_load(ctx, &r, &s, sigin); + ret = secp256k1_scalar_is_high(&s); + if (sigout != NULL) { + if (ret) { + secp256k1_scalar_negate(&s, &s); + } + secp256k1_ecdsa_signature_save(sigout, &r, &s); + } + + return ret; +} + +int secp256k1_ecdsa_verify(const secp256k1_context* ctx, const secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const secp256k1_pubkey *pubkey) { + secp256k1_ge q; + secp256k1_scalar r, s; + secp256k1_scalar m; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(pubkey != NULL); + + secp256k1_scalar_set_b32(&m, msg32, NULL); + secp256k1_ecdsa_signature_load(ctx, &r, &s, sig); + return (!secp256k1_scalar_is_high(&s) && + secp256k1_pubkey_load(ctx, &q, pubkey) && + secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &r, &s, &q, &m)); +} + +static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) { + unsigned char keydata[112]; + int keylen = 64; + secp256k1_rfc6979_hmac_sha256_t rng; + unsigned int i; + /* We feed a byte array to the PRNG as input, consisting of: + * - the private key (32 bytes) and message (32 bytes), see RFC 6979 3.2d. + * - optionally 32 extra bytes of data, see RFC 6979 3.6 Additional Data. + * - optionally 16 extra bytes with the algorithm name. + * Because the arguments have distinct fixed lengths it is not possible for + * different argument mixtures to emulate each other and result in the same + * nonces. + */ + memcpy(keydata, key32, 32); + memcpy(keydata + 32, msg32, 32); + if (data != NULL) { + memcpy(keydata + 64, data, 32); + keylen = 96; + } + if (algo16 != NULL) { + memcpy(keydata + keylen, algo16, 16); + keylen += 16; + } + secp256k1_rfc6979_hmac_sha256_initialize(&rng, keydata, keylen); + memset(keydata, 0, sizeof(keydata)); + for (i = 0; i <= counter; i++) { + secp256k1_rfc6979_hmac_sha256_generate(&rng, nonce32, 32); + } + secp256k1_rfc6979_hmac_sha256_finalize(&rng); + return 1; +} + +const secp256k1_nonce_function secp256k1_nonce_function_rfc6979 = nonce_function_rfc6979; +const secp256k1_nonce_function secp256k1_nonce_function_default = nonce_function_rfc6979; + +int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { + secp256k1_scalar r, s; + secp256k1_scalar sec, non, msg; + int ret = 0; + int overflow = 0; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(signature != NULL); + ARG_CHECK(seckey != NULL); + if (noncefp == NULL) { + noncefp = secp256k1_nonce_function_default; + } + + secp256k1_scalar_set_b32(&sec, seckey, &overflow); + /* Fail if the secret key is invalid. */ + if (!overflow && !secp256k1_scalar_is_zero(&sec)) { + unsigned char nonce32[32]; + unsigned int count = 0; + secp256k1_scalar_set_b32(&msg, msg32, NULL); + while (1) { + ret = noncefp(nonce32, msg32, seckey, NULL, (void*)noncedata, count); + if (!ret) { + break; + } + secp256k1_scalar_set_b32(&non, nonce32, &overflow); + if (!overflow && !secp256k1_scalar_is_zero(&non)) { + if (secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, NULL)) { + break; + } + } + count++; + } + memset(nonce32, 0, 32); + secp256k1_scalar_clear(&msg); + secp256k1_scalar_clear(&non); + secp256k1_scalar_clear(&sec); + } + if (ret) { + secp256k1_ecdsa_signature_save(signature, &r, &s); + } else { + memset(signature, 0, sizeof(*signature)); + } + return ret; +} + +int secp256k1_ec_seckey_verify(const secp256k1_context* ctx, const unsigned char *seckey) { + secp256k1_scalar sec; + int ret; + int overflow; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(seckey != NULL); + + secp256k1_scalar_set_b32(&sec, seckey, &overflow); + ret = !overflow && !secp256k1_scalar_is_zero(&sec); + secp256k1_scalar_clear(&sec); + return ret; +} + +int secp256k1_ec_pubkey_create(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *seckey) { + secp256k1_gej pj; + secp256k1_ge p; + secp256k1_scalar sec; + int overflow; + int ret = 0; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(pubkey != NULL); + memset(pubkey, 0, sizeof(*pubkey)); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(seckey != NULL); + + secp256k1_scalar_set_b32(&sec, seckey, &overflow); + ret = (!overflow) & (!secp256k1_scalar_is_zero(&sec)); + if (ret) { + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pj, &sec); + secp256k1_ge_set_gej(&p, &pj); + secp256k1_pubkey_save(pubkey, &p); + } + secp256k1_scalar_clear(&sec); + return ret; +} + +int secp256k1_ec_privkey_tweak_add(const secp256k1_context* ctx, unsigned char *seckey, const unsigned char *tweak) { + secp256k1_scalar term; + secp256k1_scalar sec; + int ret = 0; + int overflow = 0; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(seckey != NULL); + ARG_CHECK(tweak != NULL); + + secp256k1_scalar_set_b32(&term, tweak, &overflow); + secp256k1_scalar_set_b32(&sec, seckey, NULL); + + ret = !overflow && secp256k1_eckey_privkey_tweak_add(&sec, &term); + memset(seckey, 0, 32); + if (ret) { + secp256k1_scalar_get_b32(seckey, &sec); + } + + secp256k1_scalar_clear(&sec); + secp256k1_scalar_clear(&term); + return ret; +} + +int secp256k1_ec_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *tweak) { + secp256k1_ge p; + secp256k1_scalar term; + int ret = 0; + int overflow = 0; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + ARG_CHECK(pubkey != NULL); + ARG_CHECK(tweak != NULL); + + secp256k1_scalar_set_b32(&term, tweak, &overflow); + ret = !overflow && secp256k1_pubkey_load(ctx, &p, pubkey); + memset(pubkey, 0, sizeof(*pubkey)); + if (ret) { + if (secp256k1_eckey_pubkey_tweak_add(&ctx->ecmult_ctx, &p, &term)) { + secp256k1_pubkey_save(pubkey, &p); + } else { + ret = 0; + } + } + + return ret; +} + +int secp256k1_ec_privkey_tweak_mul(const secp256k1_context* ctx, unsigned char *seckey, const unsigned char *tweak) { + secp256k1_scalar factor; + secp256k1_scalar sec; + int ret = 0; + int overflow = 0; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(seckey != NULL); + ARG_CHECK(tweak != NULL); + + secp256k1_scalar_set_b32(&factor, tweak, &overflow); + secp256k1_scalar_set_b32(&sec, seckey, NULL); + ret = !overflow && secp256k1_eckey_privkey_tweak_mul(&sec, &factor); + memset(seckey, 0, 32); + if (ret) { + secp256k1_scalar_get_b32(seckey, &sec); + } + + secp256k1_scalar_clear(&sec); + secp256k1_scalar_clear(&factor); + return ret; +} + +int secp256k1_ec_pubkey_tweak_mul(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *tweak) { + secp256k1_ge p; + secp256k1_scalar factor; + int ret = 0; + int overflow = 0; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + ARG_CHECK(pubkey != NULL); + ARG_CHECK(tweak != NULL); + + secp256k1_scalar_set_b32(&factor, tweak, &overflow); + ret = !overflow && secp256k1_pubkey_load(ctx, &p, pubkey); + memset(pubkey, 0, sizeof(*pubkey)); + if (ret) { + if (secp256k1_eckey_pubkey_tweak_mul(&ctx->ecmult_ctx, &p, &factor)) { + secp256k1_pubkey_save(pubkey, &p); + } else { + ret = 0; + } + } + + return ret; +} + +int secp256k1_context_randomize(secp256k1_context* ctx, const unsigned char *seed32) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, seed32); + return 1; +} + +int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey *pubnonce, const secp256k1_pubkey * const *pubnonces, size_t n) { + size_t i; + secp256k1_gej Qj; + secp256k1_ge Q; + + ARG_CHECK(pubnonce != NULL); + memset(pubnonce, 0, sizeof(*pubnonce)); + ARG_CHECK(n >= 1); + ARG_CHECK(pubnonces != NULL); + + secp256k1_gej_set_infinity(&Qj); + + for (i = 0; i < n; i++) { + secp256k1_pubkey_load(ctx, &Q, pubnonces[i]); + secp256k1_gej_add_ge(&Qj, &Qj, &Q); + } + if (secp256k1_gej_is_infinity(&Qj)) { + return 0; + } + secp256k1_ge_set_gej(&Q, &Qj); + secp256k1_pubkey_save(pubnonce, &Q); + return 1; +} + +#ifdef ENABLE_MODULE_ECDH +# include "modules/ecdh/main_impl.h" +#endif + +#ifdef ENABLE_MODULE_SCHNORR +# include "modules/schnorr/main_impl.h" +#endif + +#ifdef ENABLE_MODULE_RECOVERY +# include "modules/recovery/main_impl.h" +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/testrand.h b/crypto/secp256k1/libsecp256k1/src/testrand.h new file mode 100644 index 0000000..f8efa93 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/testrand.h @@ -0,0 +1,38 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_TESTRAND_H_ +#define _SECP256K1_TESTRAND_H_ + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +/* A non-cryptographic RNG used only for test infrastructure. */ + +/** Seed the pseudorandom number generator for testing. */ +SECP256K1_INLINE static void secp256k1_rand_seed(const unsigned char *seed16); + +/** Generate a pseudorandom number in the range [0..2**32-1]. */ +static uint32_t secp256k1_rand32(void); + +/** Generate a pseudorandom number in the range [0..2**bits-1]. Bits must be 1 or + * more. */ +static uint32_t secp256k1_rand_bits(int bits); + +/** Generate a pseudorandom number in the range [0..range-1]. */ +static uint32_t secp256k1_rand_int(uint32_t range); + +/** Generate a pseudorandom 32-byte array. */ +static void secp256k1_rand256(unsigned char *b32); + +/** Generate a pseudorandom 32-byte array with long sequences of zero and one bits. */ +static void secp256k1_rand256_test(unsigned char *b32); + +/** Generate pseudorandom bytes with long sequences of zero and one bits. */ +static void secp256k1_rand_bytes_test(unsigned char *bytes, size_t len); + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/testrand_impl.h b/crypto/secp256k1/libsecp256k1/src/testrand_impl.h new file mode 100644 index 0000000..15c7b9f --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/testrand_impl.h @@ -0,0 +1,110 @@ +/********************************************************************** + * Copyright (c) 2013-2015 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_TESTRAND_IMPL_H_ +#define _SECP256K1_TESTRAND_IMPL_H_ + +#include +#include + +#include "testrand.h" +#include "hash.h" + +static secp256k1_rfc6979_hmac_sha256_t secp256k1_test_rng; +static uint32_t secp256k1_test_rng_precomputed[8]; +static int secp256k1_test_rng_precomputed_used = 8; +static uint64_t secp256k1_test_rng_integer; +static int secp256k1_test_rng_integer_bits_left = 0; + +SECP256K1_INLINE static void secp256k1_rand_seed(const unsigned char *seed16) { + secp256k1_rfc6979_hmac_sha256_initialize(&secp256k1_test_rng, seed16, 16); +} + +SECP256K1_INLINE static uint32_t secp256k1_rand32(void) { + if (secp256k1_test_rng_precomputed_used == 8) { + secp256k1_rfc6979_hmac_sha256_generate(&secp256k1_test_rng, (unsigned char*)(&secp256k1_test_rng_precomputed[0]), sizeof(secp256k1_test_rng_precomputed)); + secp256k1_test_rng_precomputed_used = 0; + } + return secp256k1_test_rng_precomputed[secp256k1_test_rng_precomputed_used++]; +} + +static uint32_t secp256k1_rand_bits(int bits) { + uint32_t ret; + if (secp256k1_test_rng_integer_bits_left < bits) { + secp256k1_test_rng_integer |= (((uint64_t)secp256k1_rand32()) << secp256k1_test_rng_integer_bits_left); + secp256k1_test_rng_integer_bits_left += 32; + } + ret = secp256k1_test_rng_integer; + secp256k1_test_rng_integer >>= bits; + secp256k1_test_rng_integer_bits_left -= bits; + ret &= ((~((uint32_t)0)) >> (32 - bits)); + return ret; +} + +static uint32_t secp256k1_rand_int(uint32_t range) { + /* We want a uniform integer between 0 and range-1, inclusive. + * B is the smallest number such that range <= 2**B. + * two mechanisms implemented here: + * - generate B bits numbers until one below range is found, and return it + * - find the largest multiple M of range that is <= 2**(B+A), generate B+A + * bits numbers until one below M is found, and return it modulo range + * The second mechanism consumes A more bits of entropy in every iteration, + * but may need fewer iterations due to M being closer to 2**(B+A) then + * range is to 2**B. The array below (indexed by B) contains a 0 when the + * first mechanism is to be used, and the number A otherwise. + */ + static const int addbits[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0}; + uint32_t trange, mult; + int bits = 0; + if (range <= 1) { + return 0; + } + trange = range - 1; + while (trange > 0) { + trange >>= 1; + bits++; + } + if (addbits[bits]) { + bits = bits + addbits[bits]; + mult = ((~((uint32_t)0)) >> (32 - bits)) / range; + trange = range * mult; + } else { + trange = range; + mult = 1; + } + while(1) { + uint32_t x = secp256k1_rand_bits(bits); + if (x < trange) { + return (mult == 1) ? x : (x % range); + } + } +} + +static void secp256k1_rand256(unsigned char *b32) { + secp256k1_rfc6979_hmac_sha256_generate(&secp256k1_test_rng, b32, 32); +} + +static void secp256k1_rand_bytes_test(unsigned char *bytes, size_t len) { + size_t bits = 0; + memset(bytes, 0, len); + while (bits < len * 8) { + int now; + uint32_t val; + now = 1 + (secp256k1_rand_bits(6) * secp256k1_rand_bits(5) + 16) / 31; + val = secp256k1_rand_bits(1); + while (now > 0 && bits < len * 8) { + bytes[bits / 8] |= val << (bits % 8); + now--; + bits++; + } + } +} + +static void secp256k1_rand256_test(unsigned char *b32) { + secp256k1_rand_bytes_test(b32, 32); +} + +#endif diff --git a/crypto/secp256k1/libsecp256k1/src/tests.c b/crypto/secp256k1/libsecp256k1/src/tests.c new file mode 100644 index 0000000..9ae7d30 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/tests.c @@ -0,0 +1,4525 @@ +/********************************************************************** + * Copyright (c) 2013, 2014, 2015 Pieter Wuille, Gregory Maxwell * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#include +#include + +#include + +#include "secp256k1.c" +#include "include/secp256k1.h" +#include "testrand_impl.h" + +#ifdef ENABLE_OPENSSL_TESTS +#include "openssl/bn.h" +#include "openssl/ec.h" +#include "openssl/ecdsa.h" +#include "openssl/obj_mac.h" +#endif + +#include "contrib/lax_der_parsing.c" +#include "contrib/lax_der_privatekey_parsing.c" + +#if !defined(VG_CHECK) +# if defined(VALGRIND) +# include +# define VG_UNDEF(x,y) VALGRIND_MAKE_MEM_UNDEFINED((x),(y)) +# define VG_CHECK(x,y) VALGRIND_CHECK_MEM_IS_DEFINED((x),(y)) +# else +# define VG_UNDEF(x,y) +# define VG_CHECK(x,y) +# endif +#endif + +static int count = 64; +static secp256k1_context *ctx = NULL; + +static void counting_illegal_callback_fn(const char* str, void* data) { + /* Dummy callback function that just counts. */ + int32_t *p; + (void)str; + p = data; + (*p)++; +} + +static void uncounting_illegal_callback_fn(const char* str, void* data) { + /* Dummy callback function that just counts (backwards). */ + int32_t *p; + (void)str; + p = data; + (*p)--; +} + +void random_field_element_test(secp256k1_fe *fe) { + do { + unsigned char b32[32]; + secp256k1_rand256_test(b32); + if (secp256k1_fe_set_b32(fe, b32)) { + break; + } + } while(1); +} + +void random_field_element_magnitude(secp256k1_fe *fe) { + secp256k1_fe zero; + int n = secp256k1_rand_int(9); + secp256k1_fe_normalize(fe); + if (n == 0) { + return; + } + secp256k1_fe_clear(&zero); + secp256k1_fe_negate(&zero, &zero, 0); + secp256k1_fe_mul_int(&zero, n - 1); + secp256k1_fe_add(fe, &zero); + VERIFY_CHECK(fe->magnitude == n); +} + +void random_group_element_test(secp256k1_ge *ge) { + secp256k1_fe fe; + do { + random_field_element_test(&fe); + if (secp256k1_ge_set_xo_var(ge, &fe, secp256k1_rand_bits(1))) { + secp256k1_fe_normalize(&ge->y); + break; + } + } while(1); +} + +void random_group_element_jacobian_test(secp256k1_gej *gej, const secp256k1_ge *ge) { + secp256k1_fe z2, z3; + do { + random_field_element_test(&gej->z); + if (!secp256k1_fe_is_zero(&gej->z)) { + break; + } + } while(1); + secp256k1_fe_sqr(&z2, &gej->z); + secp256k1_fe_mul(&z3, &z2, &gej->z); + secp256k1_fe_mul(&gej->x, &ge->x, &z2); + secp256k1_fe_mul(&gej->y, &ge->y, &z3); + gej->infinity = ge->infinity; +} + +void random_scalar_order_test(secp256k1_scalar *num) { + do { + unsigned char b32[32]; + int overflow = 0; + secp256k1_rand256_test(b32); + secp256k1_scalar_set_b32(num, b32, &overflow); + if (overflow || secp256k1_scalar_is_zero(num)) { + continue; + } + break; + } while(1); +} + +void random_scalar_order(secp256k1_scalar *num) { + do { + unsigned char b32[32]; + int overflow = 0; + secp256k1_rand256(b32); + secp256k1_scalar_set_b32(num, b32, &overflow); + if (overflow || secp256k1_scalar_is_zero(num)) { + continue; + } + break; + } while(1); +} + +void run_context_tests(void) { + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature sig; + unsigned char ctmp[32]; + int32_t ecount; + int32_t ecount2; + secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + + secp256k1_gej pubj; + secp256k1_ge pub; + secp256k1_scalar msg, key, nonce; + secp256k1_scalar sigr, sigs; + + ecount = 0; + ecount2 = 10; + secp256k1_context_set_illegal_callback(vrfy, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount2); + secp256k1_context_set_error_callback(sign, counting_illegal_callback_fn, NULL); + CHECK(vrfy->error_callback.fn != sign->error_callback.fn); + + /*** clone and destroy all of them to make sure cloning was complete ***/ + { + secp256k1_context *ctx_tmp; + + ctx_tmp = none; none = secp256k1_context_clone(none); secp256k1_context_destroy(ctx_tmp); + ctx_tmp = sign; sign = secp256k1_context_clone(sign); secp256k1_context_destroy(ctx_tmp); + ctx_tmp = vrfy; vrfy = secp256k1_context_clone(vrfy); secp256k1_context_destroy(ctx_tmp); + ctx_tmp = both; both = secp256k1_context_clone(both); secp256k1_context_destroy(ctx_tmp); + } + + /* Verify that the error callback makes it across the clone. */ + CHECK(vrfy->error_callback.fn != sign->error_callback.fn); + /* And that it resets back to default. */ + secp256k1_context_set_error_callback(sign, NULL, NULL); + CHECK(vrfy->error_callback.fn == sign->error_callback.fn); + + /*** attempt to use them ***/ + random_scalar_order_test(&msg); + random_scalar_order_test(&key); + secp256k1_ecmult_gen(&both->ecmult_gen_ctx, &pubj, &key); + secp256k1_ge_set_gej(&pub, &pubj); + + /* Verify context-type checking illegal-argument errors. */ + memset(ctmp, 1, 32); + CHECK(secp256k1_ec_pubkey_create(vrfy, &pubkey, ctmp) == 0); + CHECK(ecount == 1); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(sign, &pubkey, ctmp) == 1); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ecdsa_sign(vrfy, &sig, ctmp, ctmp, NULL, NULL) == 0); + CHECK(ecount == 2); + VG_UNDEF(&sig, sizeof(sig)); + CHECK(secp256k1_ecdsa_sign(sign, &sig, ctmp, ctmp, NULL, NULL) == 1); + VG_CHECK(&sig, sizeof(sig)); + CHECK(ecount2 == 10); + CHECK(secp256k1_ecdsa_verify(sign, &sig, ctmp, &pubkey) == 0); + CHECK(ecount2 == 11); + CHECK(secp256k1_ecdsa_verify(vrfy, &sig, ctmp, &pubkey) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ec_pubkey_tweak_add(sign, &pubkey, ctmp) == 0); + CHECK(ecount2 == 12); + CHECK(secp256k1_ec_pubkey_tweak_add(vrfy, &pubkey, ctmp) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ec_pubkey_tweak_mul(sign, &pubkey, ctmp) == 0); + CHECK(ecount2 == 13); + CHECK(secp256k1_ec_pubkey_tweak_mul(vrfy, &pubkey, ctmp) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_context_randomize(vrfy, ctmp) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_context_randomize(sign, NULL) == 1); + CHECK(ecount2 == 13); + secp256k1_context_set_illegal_callback(vrfy, NULL, NULL); + secp256k1_context_set_illegal_callback(sign, NULL, NULL); + + /* This shouldn't leak memory, due to already-set tests. */ + secp256k1_ecmult_gen_context_build(&sign->ecmult_gen_ctx, NULL); + secp256k1_ecmult_context_build(&vrfy->ecmult_ctx, NULL); + + /* obtain a working nonce */ + do { + random_scalar_order_test(&nonce); + } while(!secp256k1_ecdsa_sig_sign(&both->ecmult_gen_ctx, &sigr, &sigs, &key, &msg, &nonce, NULL)); + + /* try signing */ + CHECK(secp256k1_ecdsa_sig_sign(&sign->ecmult_gen_ctx, &sigr, &sigs, &key, &msg, &nonce, NULL)); + CHECK(secp256k1_ecdsa_sig_sign(&both->ecmult_gen_ctx, &sigr, &sigs, &key, &msg, &nonce, NULL)); + + /* try verifying */ + CHECK(secp256k1_ecdsa_sig_verify(&vrfy->ecmult_ctx, &sigr, &sigs, &pub, &msg)); + CHECK(secp256k1_ecdsa_sig_verify(&both->ecmult_ctx, &sigr, &sigs, &pub, &msg)); + + /* cleanup */ + secp256k1_context_destroy(none); + secp256k1_context_destroy(sign); + secp256k1_context_destroy(vrfy); + secp256k1_context_destroy(both); + /* Defined as no-op. */ + secp256k1_context_destroy(NULL); +} + +/***** HASH TESTS *****/ + +void run_sha256_tests(void) { + static const char *inputs[8] = { + "", "abc", "message digest", "secure hash algorithm", "SHA256 is considered to be safe", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "For this sample, this 63-byte string will be used as input data", + "This is exactly 64 bytes long, not counting the terminating byte" + }; + static const unsigned char outputs[8][32] = { + {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}, + {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}, + {0xf7, 0x84, 0x6f, 0x55, 0xcf, 0x23, 0xe1, 0x4e, 0xeb, 0xea, 0xb5, 0xb4, 0xe1, 0x55, 0x0c, 0xad, 0x5b, 0x50, 0x9e, 0x33, 0x48, 0xfb, 0xc4, 0xef, 0xa3, 0xa1, 0x41, 0x3d, 0x39, 0x3c, 0xb6, 0x50}, + {0xf3, 0x0c, 0xeb, 0x2b, 0xb2, 0x82, 0x9e, 0x79, 0xe4, 0xca, 0x97, 0x53, 0xd3, 0x5a, 0x8e, 0xcc, 0x00, 0x26, 0x2d, 0x16, 0x4c, 0xc0, 0x77, 0x08, 0x02, 0x95, 0x38, 0x1c, 0xbd, 0x64, 0x3f, 0x0d}, + {0x68, 0x19, 0xd9, 0x15, 0xc7, 0x3f, 0x4d, 0x1e, 0x77, 0xe4, 0xe1, 0xb5, 0x2d, 0x1f, 0xa0, 0xf9, 0xcf, 0x9b, 0xea, 0xea, 0xd3, 0x93, 0x9f, 0x15, 0x87, 0x4b, 0xd9, 0x88, 0xe2, 0xa2, 0x36, 0x30}, + {0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1}, + {0xf0, 0x8a, 0x78, 0xcb, 0xba, 0xee, 0x08, 0x2b, 0x05, 0x2a, 0xe0, 0x70, 0x8f, 0x32, 0xfa, 0x1e, 0x50, 0xc5, 0xc4, 0x21, 0xaa, 0x77, 0x2b, 0xa5, 0xdb, 0xb4, 0x06, 0xa2, 0xea, 0x6b, 0xe3, 0x42}, + {0xab, 0x64, 0xef, 0xf7, 0xe8, 0x8e, 0x2e, 0x46, 0x16, 0x5e, 0x29, 0xf2, 0xbc, 0xe4, 0x18, 0x26, 0xbd, 0x4c, 0x7b, 0x35, 0x52, 0xf6, 0xb3, 0x82, 0xa9, 0xe7, 0xd3, 0xaf, 0x47, 0xc2, 0x45, 0xf8} + }; + int i; + for (i = 0; i < 8; i++) { + unsigned char out[32]; + secp256k1_sha256_t hasher; + secp256k1_sha256_initialize(&hasher); + secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), strlen(inputs[i])); + secp256k1_sha256_finalize(&hasher, out); + CHECK(memcmp(out, outputs[i], 32) == 0); + if (strlen(inputs[i]) > 0) { + int split = secp256k1_rand_int(strlen(inputs[i])); + secp256k1_sha256_initialize(&hasher); + secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), split); + secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i] + split), strlen(inputs[i]) - split); + secp256k1_sha256_finalize(&hasher, out); + CHECK(memcmp(out, outputs[i], 32) == 0); + } + } +} + +void run_hmac_sha256_tests(void) { + static const char *keys[6] = { + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + "\x4a\x65\x66\x65", + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + }; + static const char *inputs[6] = { + "\x48\x69\x20\x54\x68\x65\x72\x65", + "\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20\x6e\x6f\x74\x68\x69\x6e\x67\x3f", + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd", + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd", + "\x54\x65\x73\x74\x20\x55\x73\x69\x6e\x67\x20\x4c\x61\x72\x67\x65\x72\x20\x54\x68\x61\x6e\x20\x42\x6c\x6f\x63\x6b\x2d\x53\x69\x7a\x65\x20\x4b\x65\x79\x20\x2d\x20\x48\x61\x73\x68\x20\x4b\x65\x79\x20\x46\x69\x72\x73\x74", + "\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68\x6d\x2e" + }; + static const unsigned char outputs[6][32] = { + {0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7}, + {0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, 0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75, 0xc7, 0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83, 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec, 0x38, 0x43}, + {0x77, 0x3e, 0xa9, 0x1e, 0x36, 0x80, 0x0e, 0x46, 0x85, 0x4d, 0xb8, 0xeb, 0xd0, 0x91, 0x81, 0xa7, 0x29, 0x59, 0x09, 0x8b, 0x3e, 0xf8, 0xc1, 0x22, 0xd9, 0x63, 0x55, 0x14, 0xce, 0xd5, 0x65, 0xfe}, + {0x82, 0x55, 0x8a, 0x38, 0x9a, 0x44, 0x3c, 0x0e, 0xa4, 0xcc, 0x81, 0x98, 0x99, 0xf2, 0x08, 0x3a, 0x85, 0xf0, 0xfa, 0xa3, 0xe5, 0x78, 0xf8, 0x07, 0x7a, 0x2e, 0x3f, 0xf4, 0x67, 0x29, 0x66, 0x5b}, + {0x60, 0xe4, 0x31, 0x59, 0x1e, 0xe0, 0xb6, 0x7f, 0x0d, 0x8a, 0x26, 0xaa, 0xcb, 0xf5, 0xb7, 0x7f, 0x8e, 0x0b, 0xc6, 0x21, 0x37, 0x28, 0xc5, 0x14, 0x05, 0x46, 0x04, 0x0f, 0x0e, 0xe3, 0x7f, 0x54}, + {0x9b, 0x09, 0xff, 0xa7, 0x1b, 0x94, 0x2f, 0xcb, 0x27, 0x63, 0x5f, 0xbc, 0xd5, 0xb0, 0xe9, 0x44, 0xbf, 0xdc, 0x63, 0x64, 0x4f, 0x07, 0x13, 0x93, 0x8a, 0x7f, 0x51, 0x53, 0x5c, 0x3a, 0x35, 0xe2} + }; + int i; + for (i = 0; i < 6; i++) { + secp256k1_hmac_sha256_t hasher; + unsigned char out[32]; + secp256k1_hmac_sha256_initialize(&hasher, (const unsigned char*)(keys[i]), strlen(keys[i])); + secp256k1_hmac_sha256_write(&hasher, (const unsigned char*)(inputs[i]), strlen(inputs[i])); + secp256k1_hmac_sha256_finalize(&hasher, out); + CHECK(memcmp(out, outputs[i], 32) == 0); + if (strlen(inputs[i]) > 0) { + int split = secp256k1_rand_int(strlen(inputs[i])); + secp256k1_hmac_sha256_initialize(&hasher, (const unsigned char*)(keys[i]), strlen(keys[i])); + secp256k1_hmac_sha256_write(&hasher, (const unsigned char*)(inputs[i]), split); + secp256k1_hmac_sha256_write(&hasher, (const unsigned char*)(inputs[i] + split), strlen(inputs[i]) - split); + secp256k1_hmac_sha256_finalize(&hasher, out); + CHECK(memcmp(out, outputs[i], 32) == 0); + } + } +} + +void run_rfc6979_hmac_sha256_tests(void) { + static const unsigned char key1[65] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x4b, 0xf5, 0x12, 0x2f, 0x34, 0x45, 0x54, 0xc5, 0x3b, 0xde, 0x2e, 0xbb, 0x8c, 0xd2, 0xb7, 0xe3, 0xd1, 0x60, 0x0a, 0xd6, 0x31, 0xc3, 0x85, 0xa5, 0xd7, 0xcc, 0xe2, 0x3c, 0x77, 0x85, 0x45, 0x9a, 0}; + static const unsigned char out1[3][32] = { + {0x4f, 0xe2, 0x95, 0x25, 0xb2, 0x08, 0x68, 0x09, 0x15, 0x9a, 0xcd, 0xf0, 0x50, 0x6e, 0xfb, 0x86, 0xb0, 0xec, 0x93, 0x2c, 0x7b, 0xa4, 0x42, 0x56, 0xab, 0x32, 0x1e, 0x42, 0x1e, 0x67, 0xe9, 0xfb}, + {0x2b, 0xf0, 0xff, 0xf1, 0xd3, 0xc3, 0x78, 0xa2, 0x2d, 0xc5, 0xde, 0x1d, 0x85, 0x65, 0x22, 0x32, 0x5c, 0x65, 0xb5, 0x04, 0x49, 0x1a, 0x0c, 0xbd, 0x01, 0xcb, 0x8f, 0x3a, 0xa6, 0x7f, 0xfd, 0x4a}, + {0xf5, 0x28, 0xb4, 0x10, 0xcb, 0x54, 0x1f, 0x77, 0x00, 0x0d, 0x7a, 0xfb, 0x6c, 0x5b, 0x53, 0xc5, 0xc4, 0x71, 0xea, 0xb4, 0x3e, 0x46, 0x6d, 0x9a, 0xc5, 0x19, 0x0c, 0x39, 0xc8, 0x2f, 0xd8, 0x2e} + }; + + static const unsigned char key2[64] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; + static const unsigned char out2[3][32] = { + {0x9c, 0x23, 0x6c, 0x16, 0x5b, 0x82, 0xae, 0x0c, 0xd5, 0x90, 0x65, 0x9e, 0x10, 0x0b, 0x6b, 0xab, 0x30, 0x36, 0xe7, 0xba, 0x8b, 0x06, 0x74, 0x9b, 0xaf, 0x69, 0x81, 0xe1, 0x6f, 0x1a, 0x2b, 0x95}, + {0xdf, 0x47, 0x10, 0x61, 0x62, 0x5b, 0xc0, 0xea, 0x14, 0xb6, 0x82, 0xfe, 0xee, 0x2c, 0x9c, 0x02, 0xf2, 0x35, 0xda, 0x04, 0x20, 0x4c, 0x1d, 0x62, 0xa1, 0x53, 0x6c, 0x6e, 0x17, 0xae, 0xd7, 0xa9}, + {0x75, 0x97, 0x88, 0x7c, 0xbd, 0x76, 0x32, 0x1f, 0x32, 0xe3, 0x04, 0x40, 0x67, 0x9a, 0x22, 0xcf, 0x7f, 0x8d, 0x9d, 0x2e, 0xac, 0x39, 0x0e, 0x58, 0x1f, 0xea, 0x09, 0x1c, 0xe2, 0x02, 0xba, 0x94} + }; + + secp256k1_rfc6979_hmac_sha256_t rng; + unsigned char out[32]; + int i; + + secp256k1_rfc6979_hmac_sha256_initialize(&rng, key1, 64); + for (i = 0; i < 3; i++) { + secp256k1_rfc6979_hmac_sha256_generate(&rng, out, 32); + CHECK(memcmp(out, out1[i], 32) == 0); + } + secp256k1_rfc6979_hmac_sha256_finalize(&rng); + + secp256k1_rfc6979_hmac_sha256_initialize(&rng, key1, 65); + for (i = 0; i < 3; i++) { + secp256k1_rfc6979_hmac_sha256_generate(&rng, out, 32); + CHECK(memcmp(out, out1[i], 32) != 0); + } + secp256k1_rfc6979_hmac_sha256_finalize(&rng); + + secp256k1_rfc6979_hmac_sha256_initialize(&rng, key2, 64); + for (i = 0; i < 3; i++) { + secp256k1_rfc6979_hmac_sha256_generate(&rng, out, 32); + CHECK(memcmp(out, out2[i], 32) == 0); + } + secp256k1_rfc6979_hmac_sha256_finalize(&rng); +} + +/***** RANDOM TESTS *****/ + +void test_rand_bits(int rand32, int bits) { + /* (1-1/2^B)^rounds[B] < 1/10^9, so rounds is the number of iterations to + * get a false negative chance below once in a billion */ + static const unsigned int rounds[7] = {1, 30, 73, 156, 322, 653, 1316}; + /* We try multiplying the results with various odd numbers, which shouldn't + * influence the uniform distribution modulo a power of 2. */ + static const uint32_t mults[6] = {1, 3, 21, 289, 0x9999, 0x80402011}; + /* We only select up to 6 bits from the output to analyse */ + unsigned int usebits = bits > 6 ? 6 : bits; + unsigned int maxshift = bits - usebits; + /* For each of the maxshift+1 usebits-bit sequences inside a bits-bit + number, track all observed outcomes, one per bit in a uint64_t. */ + uint64_t x[6][27] = {{0}}; + unsigned int i, shift, m; + /* Multiply the output of all rand calls with the odd number m, which + should not change the uniformity of its distribution. */ + for (i = 0; i < rounds[usebits]; i++) { + uint32_t r = (rand32 ? secp256k1_rand32() : secp256k1_rand_bits(bits)); + CHECK((((uint64_t)r) >> bits) == 0); + for (m = 0; m < sizeof(mults) / sizeof(mults[0]); m++) { + uint32_t rm = r * mults[m]; + for (shift = 0; shift <= maxshift; shift++) { + x[m][shift] |= (((uint64_t)1) << ((rm >> shift) & ((1 << usebits) - 1))); + } + } + } + for (m = 0; m < sizeof(mults) / sizeof(mults[0]); m++) { + for (shift = 0; shift <= maxshift; shift++) { + /* Test that the lower usebits bits of x[shift] are 1 */ + CHECK(((~x[m][shift]) << (64 - (1 << usebits))) == 0); + } + } +} + +/* Subrange must be a whole divisor of range, and at most 64 */ +void test_rand_int(uint32_t range, uint32_t subrange) { + /* (1-1/subrange)^rounds < 1/10^9 */ + int rounds = (subrange * 2073) / 100; + int i; + uint64_t x = 0; + CHECK((range % subrange) == 0); + for (i = 0; i < rounds; i++) { + uint32_t r = secp256k1_rand_int(range); + CHECK(r < range); + r = r % subrange; + x |= (((uint64_t)1) << r); + } + /* Test that the lower subrange bits of x are 1. */ + CHECK(((~x) << (64 - subrange)) == 0); +} + +void run_rand_bits(void) { + size_t b; + test_rand_bits(1, 32); + for (b = 1; b <= 32; b++) { + test_rand_bits(0, b); + } +} + +void run_rand_int(void) { + static const uint32_t ms[] = {1, 3, 17, 1000, 13771, 999999, 33554432}; + static const uint32_t ss[] = {1, 3, 6, 9, 13, 31, 64}; + unsigned int m, s; + for (m = 0; m < sizeof(ms) / sizeof(ms[0]); m++) { + for (s = 0; s < sizeof(ss) / sizeof(ss[0]); s++) { + test_rand_int(ms[m] * ss[s], ss[s]); + } + } +} + +/***** NUM TESTS *****/ + +#ifndef USE_NUM_NONE +void random_num_negate(secp256k1_num *num) { + if (secp256k1_rand_bits(1)) { + secp256k1_num_negate(num); + } +} + +void random_num_order_test(secp256k1_num *num) { + secp256k1_scalar sc; + random_scalar_order_test(&sc); + secp256k1_scalar_get_num(num, &sc); +} + +void random_num_order(secp256k1_num *num) { + secp256k1_scalar sc; + random_scalar_order(&sc); + secp256k1_scalar_get_num(num, &sc); +} + +void test_num_negate(void) { + secp256k1_num n1; + secp256k1_num n2; + random_num_order_test(&n1); /* n1 = R */ + random_num_negate(&n1); + secp256k1_num_copy(&n2, &n1); /* n2 = R */ + secp256k1_num_sub(&n1, &n2, &n1); /* n1 = n2-n1 = 0 */ + CHECK(secp256k1_num_is_zero(&n1)); + secp256k1_num_copy(&n1, &n2); /* n1 = R */ + secp256k1_num_negate(&n1); /* n1 = -R */ + CHECK(!secp256k1_num_is_zero(&n1)); + secp256k1_num_add(&n1, &n2, &n1); /* n1 = n2+n1 = 0 */ + CHECK(secp256k1_num_is_zero(&n1)); + secp256k1_num_copy(&n1, &n2); /* n1 = R */ + secp256k1_num_negate(&n1); /* n1 = -R */ + CHECK(secp256k1_num_is_neg(&n1) != secp256k1_num_is_neg(&n2)); + secp256k1_num_negate(&n1); /* n1 = R */ + CHECK(secp256k1_num_eq(&n1, &n2)); +} + +void test_num_add_sub(void) { + int i; + secp256k1_scalar s; + secp256k1_num n1; + secp256k1_num n2; + secp256k1_num n1p2, n2p1, n1m2, n2m1; + random_num_order_test(&n1); /* n1 = R1 */ + if (secp256k1_rand_bits(1)) { + random_num_negate(&n1); + } + random_num_order_test(&n2); /* n2 = R2 */ + if (secp256k1_rand_bits(1)) { + random_num_negate(&n2); + } + secp256k1_num_add(&n1p2, &n1, &n2); /* n1p2 = R1 + R2 */ + secp256k1_num_add(&n2p1, &n2, &n1); /* n2p1 = R2 + R1 */ + secp256k1_num_sub(&n1m2, &n1, &n2); /* n1m2 = R1 - R2 */ + secp256k1_num_sub(&n2m1, &n2, &n1); /* n2m1 = R2 - R1 */ + CHECK(secp256k1_num_eq(&n1p2, &n2p1)); + CHECK(!secp256k1_num_eq(&n1p2, &n1m2)); + secp256k1_num_negate(&n2m1); /* n2m1 = -R2 + R1 */ + CHECK(secp256k1_num_eq(&n2m1, &n1m2)); + CHECK(!secp256k1_num_eq(&n2m1, &n1)); + secp256k1_num_add(&n2m1, &n2m1, &n2); /* n2m1 = -R2 + R1 + R2 = R1 */ + CHECK(secp256k1_num_eq(&n2m1, &n1)); + CHECK(!secp256k1_num_eq(&n2p1, &n1)); + secp256k1_num_sub(&n2p1, &n2p1, &n2); /* n2p1 = R2 + R1 - R2 = R1 */ + CHECK(secp256k1_num_eq(&n2p1, &n1)); + + /* check is_one */ + secp256k1_scalar_set_int(&s, 1); + secp256k1_scalar_get_num(&n1, &s); + CHECK(secp256k1_num_is_one(&n1)); + /* check that 2^n + 1 is never 1 */ + secp256k1_scalar_get_num(&n2, &s); + for (i = 0; i < 250; ++i) { + secp256k1_num_add(&n1, &n1, &n1); /* n1 *= 2 */ + secp256k1_num_add(&n1p2, &n1, &n2); /* n1p2 = n1 + 1 */ + CHECK(!secp256k1_num_is_one(&n1p2)); + } +} + +void test_num_mod(void) { + int i; + secp256k1_scalar s; + secp256k1_num order, n; + + /* check that 0 mod anything is 0 */ + random_scalar_order_test(&s); + secp256k1_scalar_get_num(&order, &s); + secp256k1_scalar_set_int(&s, 0); + secp256k1_scalar_get_num(&n, &s); + secp256k1_num_mod(&n, &order); + CHECK(secp256k1_num_is_zero(&n)); + + /* check that anything mod 1 is 0 */ + secp256k1_scalar_set_int(&s, 1); + secp256k1_scalar_get_num(&order, &s); + secp256k1_scalar_get_num(&n, &s); + secp256k1_num_mod(&n, &order); + CHECK(secp256k1_num_is_zero(&n)); + + /* check that increasing the number past 2^256 does not break this */ + random_scalar_order_test(&s); + secp256k1_scalar_get_num(&n, &s); + /* multiply by 2^8, which'll test this case with high probability */ + for (i = 0; i < 8; ++i) { + secp256k1_num_add(&n, &n, &n); + } + secp256k1_num_mod(&n, &order); + CHECK(secp256k1_num_is_zero(&n)); +} + +void test_num_jacobi(void) { + secp256k1_scalar sqr; + secp256k1_scalar small; + secp256k1_scalar five; /* five is not a quadratic residue */ + secp256k1_num order, n; + int i; + /* squares mod 5 are 1, 4 */ + const int jacobi5[10] = { 0, 1, -1, -1, 1, 0, 1, -1, -1, 1 }; + + /* check some small values with 5 as the order */ + secp256k1_scalar_set_int(&five, 5); + secp256k1_scalar_get_num(&order, &five); + for (i = 0; i < 10; ++i) { + secp256k1_scalar_set_int(&small, i); + secp256k1_scalar_get_num(&n, &small); + CHECK(secp256k1_num_jacobi(&n, &order) == jacobi5[i]); + } + + /** test large values with 5 as group order */ + secp256k1_scalar_get_num(&order, &five); + /* we first need a scalar which is not a multiple of 5 */ + do { + secp256k1_num fiven; + random_scalar_order_test(&sqr); + secp256k1_scalar_get_num(&fiven, &five); + secp256k1_scalar_get_num(&n, &sqr); + secp256k1_num_mod(&n, &fiven); + } while (secp256k1_num_is_zero(&n)); + /* next force it to be a residue. 2 is a nonresidue mod 5 so we can + * just multiply by two, i.e. add the number to itself */ + if (secp256k1_num_jacobi(&n, &order) == -1) { + secp256k1_num_add(&n, &n, &n); + } + + /* test residue */ + CHECK(secp256k1_num_jacobi(&n, &order) == 1); + /* test nonresidue */ + secp256k1_num_add(&n, &n, &n); + CHECK(secp256k1_num_jacobi(&n, &order) == -1); + + /** test with secp group order as order */ + secp256k1_scalar_order_get_num(&order); + random_scalar_order_test(&sqr); + secp256k1_scalar_sqr(&sqr, &sqr); + /* test residue */ + secp256k1_scalar_get_num(&n, &sqr); + CHECK(secp256k1_num_jacobi(&n, &order) == 1); + /* test nonresidue */ + secp256k1_scalar_mul(&sqr, &sqr, &five); + secp256k1_scalar_get_num(&n, &sqr); + CHECK(secp256k1_num_jacobi(&n, &order) == -1); + /* test multiple of the order*/ + CHECK(secp256k1_num_jacobi(&order, &order) == 0); + + /* check one less than the order */ + secp256k1_scalar_set_int(&small, 1); + secp256k1_scalar_get_num(&n, &small); + secp256k1_num_sub(&n, &order, &n); + CHECK(secp256k1_num_jacobi(&n, &order) == 1); /* sage confirms this is 1 */ +} + +void run_num_smalltests(void) { + int i; + for (i = 0; i < 100*count; i++) { + test_num_negate(); + test_num_add_sub(); + test_num_mod(); + test_num_jacobi(); + } +} +#endif + +/***** SCALAR TESTS *****/ + +void scalar_test(void) { + secp256k1_scalar s; + secp256k1_scalar s1; + secp256k1_scalar s2; +#ifndef USE_NUM_NONE + secp256k1_num snum, s1num, s2num; + secp256k1_num order, half_order; +#endif + unsigned char c[32]; + + /* Set 's' to a random scalar, with value 'snum'. */ + random_scalar_order_test(&s); + + /* Set 's1' to a random scalar, with value 's1num'. */ + random_scalar_order_test(&s1); + + /* Set 's2' to a random scalar, with value 'snum2', and byte array representation 'c'. */ + random_scalar_order_test(&s2); + secp256k1_scalar_get_b32(c, &s2); + +#ifndef USE_NUM_NONE + secp256k1_scalar_get_num(&snum, &s); + secp256k1_scalar_get_num(&s1num, &s1); + secp256k1_scalar_get_num(&s2num, &s2); + + secp256k1_scalar_order_get_num(&order); + half_order = order; + secp256k1_num_shift(&half_order, 1); +#endif + + { + int i; + /* Test that fetching groups of 4 bits from a scalar and recursing n(i)=16*n(i-1)+p(i) reconstructs it. */ + secp256k1_scalar n; + secp256k1_scalar_set_int(&n, 0); + for (i = 0; i < 256; i += 4) { + secp256k1_scalar t; + int j; + secp256k1_scalar_set_int(&t, secp256k1_scalar_get_bits(&s, 256 - 4 - i, 4)); + for (j = 0; j < 4; j++) { + secp256k1_scalar_add(&n, &n, &n); + } + secp256k1_scalar_add(&n, &n, &t); + } + CHECK(secp256k1_scalar_eq(&n, &s)); + } + + { + /* Test that fetching groups of randomly-sized bits from a scalar and recursing n(i)=b*n(i-1)+p(i) reconstructs it. */ + secp256k1_scalar n; + int i = 0; + secp256k1_scalar_set_int(&n, 0); + while (i < 256) { + secp256k1_scalar t; + int j; + int now = secp256k1_rand_int(15) + 1; + if (now + i > 256) { + now = 256 - i; + } + secp256k1_scalar_set_int(&t, secp256k1_scalar_get_bits_var(&s, 256 - now - i, now)); + for (j = 0; j < now; j++) { + secp256k1_scalar_add(&n, &n, &n); + } + secp256k1_scalar_add(&n, &n, &t); + i += now; + } + CHECK(secp256k1_scalar_eq(&n, &s)); + } + +#ifndef USE_NUM_NONE + { + /* Test that adding the scalars together is equal to adding their numbers together modulo the order. */ + secp256k1_num rnum; + secp256k1_num r2num; + secp256k1_scalar r; + secp256k1_num_add(&rnum, &snum, &s2num); + secp256k1_num_mod(&rnum, &order); + secp256k1_scalar_add(&r, &s, &s2); + secp256k1_scalar_get_num(&r2num, &r); + CHECK(secp256k1_num_eq(&rnum, &r2num)); + } + + { + /* Test that multiplying the scalars is equal to multiplying their numbers modulo the order. */ + secp256k1_scalar r; + secp256k1_num r2num; + secp256k1_num rnum; + secp256k1_num_mul(&rnum, &snum, &s2num); + secp256k1_num_mod(&rnum, &order); + secp256k1_scalar_mul(&r, &s, &s2); + secp256k1_scalar_get_num(&r2num, &r); + CHECK(secp256k1_num_eq(&rnum, &r2num)); + /* The result can only be zero if at least one of the factors was zero. */ + CHECK(secp256k1_scalar_is_zero(&r) == (secp256k1_scalar_is_zero(&s) || secp256k1_scalar_is_zero(&s2))); + /* The results can only be equal to one of the factors if that factor was zero, or the other factor was one. */ + CHECK(secp256k1_num_eq(&rnum, &snum) == (secp256k1_scalar_is_zero(&s) || secp256k1_scalar_is_one(&s2))); + CHECK(secp256k1_num_eq(&rnum, &s2num) == (secp256k1_scalar_is_zero(&s2) || secp256k1_scalar_is_one(&s))); + } + + { + secp256k1_scalar neg; + secp256k1_num negnum; + secp256k1_num negnum2; + /* Check that comparison with zero matches comparison with zero on the number. */ + CHECK(secp256k1_num_is_zero(&snum) == secp256k1_scalar_is_zero(&s)); + /* Check that comparison with the half order is equal to testing for high scalar. */ + CHECK(secp256k1_scalar_is_high(&s) == (secp256k1_num_cmp(&snum, &half_order) > 0)); + secp256k1_scalar_negate(&neg, &s); + secp256k1_num_sub(&negnum, &order, &snum); + secp256k1_num_mod(&negnum, &order); + /* Check that comparison with the half order is equal to testing for high scalar after negation. */ + CHECK(secp256k1_scalar_is_high(&neg) == (secp256k1_num_cmp(&negnum, &half_order) > 0)); + /* Negating should change the high property, unless the value was already zero. */ + CHECK((secp256k1_scalar_is_high(&s) == secp256k1_scalar_is_high(&neg)) == secp256k1_scalar_is_zero(&s)); + secp256k1_scalar_get_num(&negnum2, &neg); + /* Negating a scalar should be equal to (order - n) mod order on the number. */ + CHECK(secp256k1_num_eq(&negnum, &negnum2)); + secp256k1_scalar_add(&neg, &neg, &s); + /* Adding a number to its negation should result in zero. */ + CHECK(secp256k1_scalar_is_zero(&neg)); + secp256k1_scalar_negate(&neg, &neg); + /* Negating zero should still result in zero. */ + CHECK(secp256k1_scalar_is_zero(&neg)); + } + + { + /* Test secp256k1_scalar_mul_shift_var. */ + secp256k1_scalar r; + secp256k1_num one; + secp256k1_num rnum; + secp256k1_num rnum2; + unsigned char cone[1] = {0x01}; + unsigned int shift = 256 + secp256k1_rand_int(257); + secp256k1_scalar_mul_shift_var(&r, &s1, &s2, shift); + secp256k1_num_mul(&rnum, &s1num, &s2num); + secp256k1_num_shift(&rnum, shift - 1); + secp256k1_num_set_bin(&one, cone, 1); + secp256k1_num_add(&rnum, &rnum, &one); + secp256k1_num_shift(&rnum, 1); + secp256k1_scalar_get_num(&rnum2, &r); + CHECK(secp256k1_num_eq(&rnum, &rnum2)); + } + + { + /* test secp256k1_scalar_shr_int */ + secp256k1_scalar r; + int i; + random_scalar_order_test(&r); + for (i = 0; i < 100; ++i) { + int low; + int shift = 1 + secp256k1_rand_int(15); + int expected = r.d[0] % (1 << shift); + low = secp256k1_scalar_shr_int(&r, shift); + CHECK(expected == low); + } + } +#endif + + { + /* Test that scalar inverses are equal to the inverse of their number modulo the order. */ + if (!secp256k1_scalar_is_zero(&s)) { + secp256k1_scalar inv; +#ifndef USE_NUM_NONE + secp256k1_num invnum; + secp256k1_num invnum2; +#endif + secp256k1_scalar_inverse(&inv, &s); +#ifndef USE_NUM_NONE + secp256k1_num_mod_inverse(&invnum, &snum, &order); + secp256k1_scalar_get_num(&invnum2, &inv); + CHECK(secp256k1_num_eq(&invnum, &invnum2)); +#endif + secp256k1_scalar_mul(&inv, &inv, &s); + /* Multiplying a scalar with its inverse must result in one. */ + CHECK(secp256k1_scalar_is_one(&inv)); + secp256k1_scalar_inverse(&inv, &inv); + /* Inverting one must result in one. */ + CHECK(secp256k1_scalar_is_one(&inv)); +#ifndef USE_NUM_NONE + secp256k1_scalar_get_num(&invnum, &inv); + CHECK(secp256k1_num_is_one(&invnum)); +#endif + } + } + + { + /* Test commutativity of add. */ + secp256k1_scalar r1, r2; + secp256k1_scalar_add(&r1, &s1, &s2); + secp256k1_scalar_add(&r2, &s2, &s1); + CHECK(secp256k1_scalar_eq(&r1, &r2)); + } + + { + secp256k1_scalar r1, r2; + secp256k1_scalar b; + int i; + /* Test add_bit. */ + int bit = secp256k1_rand_bits(8); + secp256k1_scalar_set_int(&b, 1); + CHECK(secp256k1_scalar_is_one(&b)); + for (i = 0; i < bit; i++) { + secp256k1_scalar_add(&b, &b, &b); + } + r1 = s1; + r2 = s1; + if (!secp256k1_scalar_add(&r1, &r1, &b)) { + /* No overflow happened. */ + secp256k1_scalar_cadd_bit(&r2, bit, 1); + CHECK(secp256k1_scalar_eq(&r1, &r2)); + /* cadd is a noop when flag is zero */ + secp256k1_scalar_cadd_bit(&r2, bit, 0); + CHECK(secp256k1_scalar_eq(&r1, &r2)); + } + } + + { + /* Test commutativity of mul. */ + secp256k1_scalar r1, r2; + secp256k1_scalar_mul(&r1, &s1, &s2); + secp256k1_scalar_mul(&r2, &s2, &s1); + CHECK(secp256k1_scalar_eq(&r1, &r2)); + } + + { + /* Test associativity of add. */ + secp256k1_scalar r1, r2; + secp256k1_scalar_add(&r1, &s1, &s2); + secp256k1_scalar_add(&r1, &r1, &s); + secp256k1_scalar_add(&r2, &s2, &s); + secp256k1_scalar_add(&r2, &s1, &r2); + CHECK(secp256k1_scalar_eq(&r1, &r2)); + } + + { + /* Test associativity of mul. */ + secp256k1_scalar r1, r2; + secp256k1_scalar_mul(&r1, &s1, &s2); + secp256k1_scalar_mul(&r1, &r1, &s); + secp256k1_scalar_mul(&r2, &s2, &s); + secp256k1_scalar_mul(&r2, &s1, &r2); + CHECK(secp256k1_scalar_eq(&r1, &r2)); + } + + { + /* Test distributitivity of mul over add. */ + secp256k1_scalar r1, r2, t; + secp256k1_scalar_add(&r1, &s1, &s2); + secp256k1_scalar_mul(&r1, &r1, &s); + secp256k1_scalar_mul(&r2, &s1, &s); + secp256k1_scalar_mul(&t, &s2, &s); + secp256k1_scalar_add(&r2, &r2, &t); + CHECK(secp256k1_scalar_eq(&r1, &r2)); + } + + { + /* Test square. */ + secp256k1_scalar r1, r2; + secp256k1_scalar_sqr(&r1, &s1); + secp256k1_scalar_mul(&r2, &s1, &s1); + CHECK(secp256k1_scalar_eq(&r1, &r2)); + } + + { + /* Test multiplicative identity. */ + secp256k1_scalar r1, v1; + secp256k1_scalar_set_int(&v1,1); + secp256k1_scalar_mul(&r1, &s1, &v1); + CHECK(secp256k1_scalar_eq(&r1, &s1)); + } + + { + /* Test additive identity. */ + secp256k1_scalar r1, v0; + secp256k1_scalar_set_int(&v0,0); + secp256k1_scalar_add(&r1, &s1, &v0); + CHECK(secp256k1_scalar_eq(&r1, &s1)); + } + + { + /* Test zero product property. */ + secp256k1_scalar r1, v0; + secp256k1_scalar_set_int(&v0,0); + secp256k1_scalar_mul(&r1, &s1, &v0); + CHECK(secp256k1_scalar_eq(&r1, &v0)); + } + +} + +void run_scalar_tests(void) { + int i; + for (i = 0; i < 128 * count; i++) { + scalar_test(); + } + + { + /* (-1)+1 should be zero. */ + secp256k1_scalar s, o; + secp256k1_scalar_set_int(&s, 1); + CHECK(secp256k1_scalar_is_one(&s)); + secp256k1_scalar_negate(&o, &s); + secp256k1_scalar_add(&o, &o, &s); + CHECK(secp256k1_scalar_is_zero(&o)); + secp256k1_scalar_negate(&o, &o); + CHECK(secp256k1_scalar_is_zero(&o)); + } + +#ifndef USE_NUM_NONE + { + /* A scalar with value of the curve order should be 0. */ + secp256k1_num order; + secp256k1_scalar zero; + unsigned char bin[32]; + int overflow = 0; + secp256k1_scalar_order_get_num(&order); + secp256k1_num_get_bin(bin, 32, &order); + secp256k1_scalar_set_b32(&zero, bin, &overflow); + CHECK(overflow == 1); + CHECK(secp256k1_scalar_is_zero(&zero)); + } +#endif + + { + /* Does check_overflow check catch all ones? */ + static const secp256k1_scalar overflowed = SECP256K1_SCALAR_CONST( + 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, + 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL + ); + CHECK(secp256k1_scalar_check_overflow(&overflowed)); + } + + { + /* Static test vectors. + * These were reduced from ~10^12 random vectors based on comparison-decision + * and edge-case coverage on 32-bit and 64-bit implementations. + * The responses were generated with Sage 5.9. + */ + secp256k1_scalar x; + secp256k1_scalar y; + secp256k1_scalar z; + secp256k1_scalar zz; + secp256k1_scalar one; + secp256k1_scalar r1; + secp256k1_scalar r2; +#if defined(USE_SCALAR_INV_NUM) + secp256k1_scalar zzv; +#endif + int overflow; + unsigned char chal[33][2][32] = { + {{0xff, 0xff, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, + 0xff, 0xff, 0x03, 0x00, 0xc0, 0xff, 0xff, 0xff}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff}}, + {{0xef, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x80, 0xff}}, + {{0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x80, 0xff, 0x3f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x00}, + {0x00, 0x00, 0xfc, 0xff, 0xff, 0xff, 0xff, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0xe0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff}}, + {{0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x1e, 0xf8, 0xff, 0xff, 0xff, 0xfd, 0xff}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, + 0x00, 0x00, 0x00, 0xf8, 0xff, 0x03, 0x00, 0xe0, + 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, + 0xf3, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x80, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x1c, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0x00, + 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1f, 0x00, 0x00, 0x80, 0xff, 0xff, 0x3f, + 0x00, 0xfe, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff}}, + {{0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xfc, 0x9f, + 0xff, 0xff, 0xff, 0x00, 0x80, 0x00, 0x00, 0x80, + 0xff, 0x0f, 0xfc, 0xff, 0x7f, 0x00, 0x00, 0x00, + 0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}, + {0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0xf8, 0xff, 0x0f, 0xc0, 0xff, 0xff, + 0xff, 0x1f, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x07, 0x80, 0xff, 0xff, 0xff}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, + 0xf7, 0xff, 0xff, 0xef, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xf0}, + {0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {{0x00, 0xf8, 0xff, 0x03, 0xff, 0xff, 0xff, 0x00, + 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x80, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x03, 0xc0, 0xff, 0x0f, 0xfc, 0xff}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, + 0xff, 0x01, 0x00, 0x00, 0x00, 0x3f, 0x00, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {{0x8f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x7f, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x03, 0x00, 0x80, 0x00, 0x00, 0x80, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x80, 0xff, 0x7f}, + {0xff, 0xcf, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, + 0x00, 0xc0, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, + 0xbf, 0xff, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, + 0xff, 0xff, 0x00, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x80, 0x00, 0x00, 0x80, + 0xff, 0x01, 0xfc, 0xff, 0x01, 0x00, 0xfe, 0xff}, + {0xff, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0x00}}, + {{0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7f, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0xff, + 0xe0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0x00}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0xfc, 0xff, 0xff, 0x3f, 0xf0, 0xff, 0xff, 0x3f, + 0x00, 0x00, 0xf8, 0x07, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0f, 0x7e, 0x00, 0x00}}, + {{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x1f, 0x00, 0x00, 0xfe, 0x07, 0x00}, + {0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfb, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60}}, + {{0xff, 0x01, 0x00, 0xff, 0xff, 0xff, 0x0f, 0x00, + 0x80, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + {0xff, 0xff, 0x1f, 0x00, 0xf0, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00}}, + {{0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, + 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xff}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0xff, 0xff, 0xcf, 0xff, 0x1f, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x7e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00}, + {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xff, 0xff, 0x7f, 0x00, 0x80, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}, + {0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x80, + 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0x7f, 0xf8, 0xff, 0xff, 0x1f, 0x00, 0xfe}}, + {{0xff, 0xff, 0xff, 0x3f, 0xf8, 0xff, 0xff, 0xff, + 0xff, 0x03, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00, + 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xff, 0xff, 0xff, 0xff, 0x01, 0x80, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x40}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + {0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xc0, + 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, + 0xf0, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff}}, + {{0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x40}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x7e, 0x00, 0x00, 0xc0, 0xff, 0xff, 0x07, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + {0xff, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + {{0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x00, + 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, + 0x80, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, + 0xff, 0xff, 0x3f, 0x00, 0xf8, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x3f, 0x00, 0x00, 0xc0, 0xf1, 0x7f, 0x00}}, + {{0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x00}, + {0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, + 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x80, 0x1f, + 0x00, 0x00, 0xfc, 0xff, 0xff, 0x01, 0xff, 0xff}}, + {{0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x80, 0x00, 0x00, 0x80, 0xff, 0x03, 0xe0, 0x01, + 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xfc, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}, + {0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0xff, 0xff, 0xf0, 0x07, 0x00, 0x3c, 0x80, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x07, 0xe0, 0xff, 0x00, 0x00, 0x00}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf8, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80}, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x80, 0x00, + 0x00, 0x00, 0x00, 0xc0, 0x7f, 0xfe, 0xff, 0x1f, + 0x00, 0xfe, 0xff, 0x03, 0x00, 0x00, 0xfe, 0xff}}, + {{0xff, 0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x83, + 0xff, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, + 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0xf0}, + {0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, + 0xf8, 0x07, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xc7, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff}}, + {{0x82, 0xc9, 0xfa, 0xb0, 0x68, 0x04, 0xa0, 0x00, + 0x82, 0xc9, 0xfa, 0xb0, 0x68, 0x04, 0xa0, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x6f, 0x03, 0xfb, + 0xfa, 0x8a, 0x7d, 0xdf, 0x13, 0x86, 0xe2, 0x03}, + {0x82, 0xc9, 0xfa, 0xb0, 0x68, 0x04, 0xa0, 0x00, + 0x82, 0xc9, 0xfa, 0xb0, 0x68, 0x04, 0xa0, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x6f, 0x03, 0xfb, + 0xfa, 0x8a, 0x7d, 0xdf, 0x13, 0x86, 0xe2, 0x03}} + }; + unsigned char res[33][2][32] = { + {{0x0c, 0x3b, 0x0a, 0xca, 0x8d, 0x1a, 0x2f, 0xb9, + 0x8a, 0x7b, 0x53, 0x5a, 0x1f, 0xc5, 0x22, 0xa1, + 0x07, 0x2a, 0x48, 0xea, 0x02, 0xeb, 0xb3, 0xd6, + 0x20, 0x1e, 0x86, 0xd0, 0x95, 0xf6, 0x92, 0x35}, + {0xdc, 0x90, 0x7a, 0x07, 0x2e, 0x1e, 0x44, 0x6d, + 0xf8, 0x15, 0x24, 0x5b, 0x5a, 0x96, 0x37, 0x9c, + 0x37, 0x7b, 0x0d, 0xac, 0x1b, 0x65, 0x58, 0x49, + 0x43, 0xb7, 0x31, 0xbb, 0xa7, 0xf4, 0x97, 0x15}}, + {{0xf1, 0xf7, 0x3a, 0x50, 0xe6, 0x10, 0xba, 0x22, + 0x43, 0x4d, 0x1f, 0x1f, 0x7c, 0x27, 0xca, 0x9c, + 0xb8, 0xb6, 0xa0, 0xfc, 0xd8, 0xc0, 0x05, 0x2f, + 0xf7, 0x08, 0xe1, 0x76, 0xdd, 0xd0, 0x80, 0xc8}, + {0xe3, 0x80, 0x80, 0xb8, 0xdb, 0xe3, 0xa9, 0x77, + 0x00, 0xb0, 0xf5, 0x2e, 0x27, 0xe2, 0x68, 0xc4, + 0x88, 0xe8, 0x04, 0xc1, 0x12, 0xbf, 0x78, 0x59, + 0xe6, 0xa9, 0x7c, 0xe1, 0x81, 0xdd, 0xb9, 0xd5}}, + {{0x96, 0xe2, 0xee, 0x01, 0xa6, 0x80, 0x31, 0xef, + 0x5c, 0xd0, 0x19, 0xb4, 0x7d, 0x5f, 0x79, 0xab, + 0xa1, 0x97, 0xd3, 0x7e, 0x33, 0xbb, 0x86, 0x55, + 0x60, 0x20, 0x10, 0x0d, 0x94, 0x2d, 0x11, 0x7c}, + {0xcc, 0xab, 0xe0, 0xe8, 0x98, 0x65, 0x12, 0x96, + 0x38, 0x5a, 0x1a, 0xf2, 0x85, 0x23, 0x59, 0x5f, + 0xf9, 0xf3, 0xc2, 0x81, 0x70, 0x92, 0x65, 0x12, + 0x9c, 0x65, 0x1e, 0x96, 0x00, 0xef, 0xe7, 0x63}}, + {{0xac, 0x1e, 0x62, 0xc2, 0x59, 0xfc, 0x4e, 0x5c, + 0x83, 0xb0, 0xd0, 0x6f, 0xce, 0x19, 0xf6, 0xbf, + 0xa4, 0xb0, 0xe0, 0x53, 0x66, 0x1f, 0xbf, 0xc9, + 0x33, 0x47, 0x37, 0xa9, 0x3d, 0x5d, 0xb0, 0x48}, + {0x86, 0xb9, 0x2a, 0x7f, 0x8e, 0xa8, 0x60, 0x42, + 0x26, 0x6d, 0x6e, 0x1c, 0xa2, 0xec, 0xe0, 0xe5, + 0x3e, 0x0a, 0x33, 0xbb, 0x61, 0x4c, 0x9f, 0x3c, + 0xd1, 0xdf, 0x49, 0x33, 0xcd, 0x72, 0x78, 0x18}}, + {{0xf7, 0xd3, 0xcd, 0x49, 0x5c, 0x13, 0x22, 0xfb, + 0x2e, 0xb2, 0x2f, 0x27, 0xf5, 0x8a, 0x5d, 0x74, + 0xc1, 0x58, 0xc5, 0xc2, 0x2d, 0x9f, 0x52, 0xc6, + 0x63, 0x9f, 0xba, 0x05, 0x76, 0x45, 0x7a, 0x63}, + {0x8a, 0xfa, 0x55, 0x4d, 0xdd, 0xa3, 0xb2, 0xc3, + 0x44, 0xfd, 0xec, 0x72, 0xde, 0xef, 0xc0, 0x99, + 0xf5, 0x9f, 0xe2, 0x52, 0xb4, 0x05, 0x32, 0x58, + 0x57, 0xc1, 0x8f, 0xea, 0xc3, 0x24, 0x5b, 0x94}}, + {{0x05, 0x83, 0xee, 0xdd, 0x64, 0xf0, 0x14, 0x3b, + 0xa0, 0x14, 0x4a, 0x3a, 0x41, 0x82, 0x7c, 0xa7, + 0x2c, 0xaa, 0xb1, 0x76, 0xbb, 0x59, 0x64, 0x5f, + 0x52, 0xad, 0x25, 0x29, 0x9d, 0x8f, 0x0b, 0xb0}, + {0x7e, 0xe3, 0x7c, 0xca, 0xcd, 0x4f, 0xb0, 0x6d, + 0x7a, 0xb2, 0x3e, 0xa0, 0x08, 0xb9, 0xa8, 0x2d, + 0xc2, 0xf4, 0x99, 0x66, 0xcc, 0xac, 0xd8, 0xb9, + 0x72, 0x2a, 0x4a, 0x3e, 0x0f, 0x7b, 0xbf, 0xf4}}, + {{0x8c, 0x9c, 0x78, 0x2b, 0x39, 0x61, 0x7e, 0xf7, + 0x65, 0x37, 0x66, 0x09, 0x38, 0xb9, 0x6f, 0x70, + 0x78, 0x87, 0xff, 0xcf, 0x93, 0xca, 0x85, 0x06, + 0x44, 0x84, 0xa7, 0xfe, 0xd3, 0xa4, 0xe3, 0x7e}, + {0xa2, 0x56, 0x49, 0x23, 0x54, 0xa5, 0x50, 0xe9, + 0x5f, 0xf0, 0x4d, 0xe7, 0xdc, 0x38, 0x32, 0x79, + 0x4f, 0x1c, 0xb7, 0xe4, 0xbb, 0xf8, 0xbb, 0x2e, + 0x40, 0x41, 0x4b, 0xcc, 0xe3, 0x1e, 0x16, 0x36}}, + {{0x0c, 0x1e, 0xd7, 0x09, 0x25, 0x40, 0x97, 0xcb, + 0x5c, 0x46, 0xa8, 0xda, 0xef, 0x25, 0xd5, 0xe5, + 0x92, 0x4d, 0xcf, 0xa3, 0xc4, 0x5d, 0x35, 0x4a, + 0xe4, 0x61, 0x92, 0xf3, 0xbf, 0x0e, 0xcd, 0xbe}, + {0xe4, 0xaf, 0x0a, 0xb3, 0x30, 0x8b, 0x9b, 0x48, + 0x49, 0x43, 0xc7, 0x64, 0x60, 0x4a, 0x2b, 0x9e, + 0x95, 0x5f, 0x56, 0xe8, 0x35, 0xdc, 0xeb, 0xdc, + 0xc7, 0xc4, 0xfe, 0x30, 0x40, 0xc7, 0xbf, 0xa4}}, + {{0xd4, 0xa0, 0xf5, 0x81, 0x49, 0x6b, 0xb6, 0x8b, + 0x0a, 0x69, 0xf9, 0xfe, 0xa8, 0x32, 0xe5, 0xe0, + 0xa5, 0xcd, 0x02, 0x53, 0xf9, 0x2c, 0xe3, 0x53, + 0x83, 0x36, 0xc6, 0x02, 0xb5, 0xeb, 0x64, 0xb8}, + {0x1d, 0x42, 0xb9, 0xf9, 0xe9, 0xe3, 0x93, 0x2c, + 0x4c, 0xee, 0x6c, 0x5a, 0x47, 0x9e, 0x62, 0x01, + 0x6b, 0x04, 0xfe, 0xa4, 0x30, 0x2b, 0x0d, 0x4f, + 0x71, 0x10, 0xd3, 0x55, 0xca, 0xf3, 0x5e, 0x80}}, + {{0x77, 0x05, 0xf6, 0x0c, 0x15, 0x9b, 0x45, 0xe7, + 0xb9, 0x11, 0xb8, 0xf5, 0xd6, 0xda, 0x73, 0x0c, + 0xda, 0x92, 0xea, 0xd0, 0x9d, 0xd0, 0x18, 0x92, + 0xce, 0x9a, 0xaa, 0xee, 0x0f, 0xef, 0xde, 0x30}, + {0xf1, 0xf1, 0xd6, 0x9b, 0x51, 0xd7, 0x77, 0x62, + 0x52, 0x10, 0xb8, 0x7a, 0x84, 0x9d, 0x15, 0x4e, + 0x07, 0xdc, 0x1e, 0x75, 0x0d, 0x0c, 0x3b, 0xdb, + 0x74, 0x58, 0x62, 0x02, 0x90, 0x54, 0x8b, 0x43}}, + {{0xa6, 0xfe, 0x0b, 0x87, 0x80, 0x43, 0x67, 0x25, + 0x57, 0x5d, 0xec, 0x40, 0x50, 0x08, 0xd5, 0x5d, + 0x43, 0xd7, 0xe0, 0xaa, 0xe0, 0x13, 0xb6, 0xb0, + 0xc0, 0xd4, 0xe5, 0x0d, 0x45, 0x83, 0xd6, 0x13}, + {0x40, 0x45, 0x0a, 0x92, 0x31, 0xea, 0x8c, 0x60, + 0x8c, 0x1f, 0xd8, 0x76, 0x45, 0xb9, 0x29, 0x00, + 0x26, 0x32, 0xd8, 0xa6, 0x96, 0x88, 0xe2, 0xc4, + 0x8b, 0xdb, 0x7f, 0x17, 0x87, 0xcc, 0xc8, 0xf2}}, + {{0xc2, 0x56, 0xe2, 0xb6, 0x1a, 0x81, 0xe7, 0x31, + 0x63, 0x2e, 0xbb, 0x0d, 0x2f, 0x81, 0x67, 0xd4, + 0x22, 0xe2, 0x38, 0x02, 0x25, 0x97, 0xc7, 0x88, + 0x6e, 0xdf, 0xbe, 0x2a, 0xa5, 0x73, 0x63, 0xaa}, + {0x50, 0x45, 0xe2, 0xc3, 0xbd, 0x89, 0xfc, 0x57, + 0xbd, 0x3c, 0xa3, 0x98, 0x7e, 0x7f, 0x36, 0x38, + 0x92, 0x39, 0x1f, 0x0f, 0x81, 0x1a, 0x06, 0x51, + 0x1f, 0x8d, 0x6a, 0xff, 0x47, 0x16, 0x06, 0x9c}}, + {{0x33, 0x95, 0xa2, 0x6f, 0x27, 0x5f, 0x9c, 0x9c, + 0x64, 0x45, 0xcb, 0xd1, 0x3c, 0xee, 0x5e, 0x5f, + 0x48, 0xa6, 0xaf, 0xe3, 0x79, 0xcf, 0xb1, 0xe2, + 0xbf, 0x55, 0x0e, 0xa2, 0x3b, 0x62, 0xf0, 0xe4}, + {0x14, 0xe8, 0x06, 0xe3, 0xbe, 0x7e, 0x67, 0x01, + 0xc5, 0x21, 0x67, 0xd8, 0x54, 0xb5, 0x7f, 0xa4, + 0xf9, 0x75, 0x70, 0x1c, 0xfd, 0x79, 0xdb, 0x86, + 0xad, 0x37, 0x85, 0x83, 0x56, 0x4e, 0xf0, 0xbf}}, + {{0xbc, 0xa6, 0xe0, 0x56, 0x4e, 0xef, 0xfa, 0xf5, + 0x1d, 0x5d, 0x3f, 0x2a, 0x5b, 0x19, 0xab, 0x51, + 0xc5, 0x8b, 0xdd, 0x98, 0x28, 0x35, 0x2f, 0xc3, + 0x81, 0x4f, 0x5c, 0xe5, 0x70, 0xb9, 0xeb, 0x62}, + {0xc4, 0x6d, 0x26, 0xb0, 0x17, 0x6b, 0xfe, 0x6c, + 0x12, 0xf8, 0xe7, 0xc1, 0xf5, 0x2f, 0xfa, 0x91, + 0x13, 0x27, 0xbd, 0x73, 0xcc, 0x33, 0x31, 0x1c, + 0x39, 0xe3, 0x27, 0x6a, 0x95, 0xcf, 0xc5, 0xfb}}, + {{0x30, 0xb2, 0x99, 0x84, 0xf0, 0x18, 0x2a, 0x6e, + 0x1e, 0x27, 0xed, 0xa2, 0x29, 0x99, 0x41, 0x56, + 0xe8, 0xd4, 0x0d, 0xef, 0x99, 0x9c, 0xf3, 0x58, + 0x29, 0x55, 0x1a, 0xc0, 0x68, 0xd6, 0x74, 0xa4}, + {0x07, 0x9c, 0xe7, 0xec, 0xf5, 0x36, 0x73, 0x41, + 0xa3, 0x1c, 0xe5, 0x93, 0x97, 0x6a, 0xfd, 0xf7, + 0x53, 0x18, 0xab, 0xaf, 0xeb, 0x85, 0xbd, 0x92, + 0x90, 0xab, 0x3c, 0xbf, 0x30, 0x82, 0xad, 0xf6}}, + {{0xc6, 0x87, 0x8a, 0x2a, 0xea, 0xc0, 0xa9, 0xec, + 0x6d, 0xd3, 0xdc, 0x32, 0x23, 0xce, 0x62, 0x19, + 0xa4, 0x7e, 0xa8, 0xdd, 0x1c, 0x33, 0xae, 0xd3, + 0x4f, 0x62, 0x9f, 0x52, 0xe7, 0x65, 0x46, 0xf4}, + {0x97, 0x51, 0x27, 0x67, 0x2d, 0xa2, 0x82, 0x87, + 0x98, 0xd3, 0xb6, 0x14, 0x7f, 0x51, 0xd3, 0x9a, + 0x0b, 0xd0, 0x76, 0x81, 0xb2, 0x4f, 0x58, 0x92, + 0xa4, 0x86, 0xa1, 0xa7, 0x09, 0x1d, 0xef, 0x9b}}, + {{0xb3, 0x0f, 0x2b, 0x69, 0x0d, 0x06, 0x90, 0x64, + 0xbd, 0x43, 0x4c, 0x10, 0xe8, 0x98, 0x1c, 0xa3, + 0xe1, 0x68, 0xe9, 0x79, 0x6c, 0x29, 0x51, 0x3f, + 0x41, 0xdc, 0xdf, 0x1f, 0xf3, 0x60, 0xbe, 0x33}, + {0xa1, 0x5f, 0xf7, 0x1d, 0xb4, 0x3e, 0x9b, 0x3c, + 0xe7, 0xbd, 0xb6, 0x06, 0xd5, 0x60, 0x06, 0x6d, + 0x50, 0xd2, 0xf4, 0x1a, 0x31, 0x08, 0xf2, 0xea, + 0x8e, 0xef, 0x5f, 0x7d, 0xb6, 0xd0, 0xc0, 0x27}}, + {{0x62, 0x9a, 0xd9, 0xbb, 0x38, 0x36, 0xce, 0xf7, + 0x5d, 0x2f, 0x13, 0xec, 0xc8, 0x2d, 0x02, 0x8a, + 0x2e, 0x72, 0xf0, 0xe5, 0x15, 0x9d, 0x72, 0xae, + 0xfc, 0xb3, 0x4f, 0x02, 0xea, 0xe1, 0x09, 0xfe}, + {0x00, 0x00, 0x00, 0x00, 0xfa, 0x0a, 0x3d, 0xbc, + 0xad, 0x16, 0x0c, 0xb6, 0xe7, 0x7c, 0x8b, 0x39, + 0x9a, 0x43, 0xbb, 0xe3, 0xc2, 0x55, 0x15, 0x14, + 0x75, 0xac, 0x90, 0x9b, 0x7f, 0x9a, 0x92, 0x00}}, + {{0x8b, 0xac, 0x70, 0x86, 0x29, 0x8f, 0x00, 0x23, + 0x7b, 0x45, 0x30, 0xaa, 0xb8, 0x4c, 0xc7, 0x8d, + 0x4e, 0x47, 0x85, 0xc6, 0x19, 0xe3, 0x96, 0xc2, + 0x9a, 0xa0, 0x12, 0xed, 0x6f, 0xd7, 0x76, 0x16}, + {0x45, 0xaf, 0x7e, 0x33, 0xc7, 0x7f, 0x10, 0x6c, + 0x7c, 0x9f, 0x29, 0xc1, 0xa8, 0x7e, 0x15, 0x84, + 0xe7, 0x7d, 0xc0, 0x6d, 0xab, 0x71, 0x5d, 0xd0, + 0x6b, 0x9f, 0x97, 0xab, 0xcb, 0x51, 0x0c, 0x9f}}, + {{0x9e, 0xc3, 0x92, 0xb4, 0x04, 0x9f, 0xc8, 0xbb, + 0xdd, 0x9e, 0xc6, 0x05, 0xfd, 0x65, 0xec, 0x94, + 0x7f, 0x2c, 0x16, 0xc4, 0x40, 0xac, 0x63, 0x7b, + 0x7d, 0xb8, 0x0c, 0xe4, 0x5b, 0xe3, 0xa7, 0x0e}, + {0x43, 0xf4, 0x44, 0xe8, 0xcc, 0xc8, 0xd4, 0x54, + 0x33, 0x37, 0x50, 0xf2, 0x87, 0x42, 0x2e, 0x00, + 0x49, 0x60, 0x62, 0x02, 0xfd, 0x1a, 0x7c, 0xdb, + 0x29, 0x6c, 0x6d, 0x54, 0x53, 0x08, 0xd1, 0xc8}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}, + {{0x27, 0x59, 0xc7, 0x35, 0x60, 0x71, 0xa6, 0xf1, + 0x79, 0xa5, 0xfd, 0x79, 0x16, 0xf3, 0x41, 0xf0, + 0x57, 0xb4, 0x02, 0x97, 0x32, 0xe7, 0xde, 0x59, + 0xe2, 0x2d, 0x9b, 0x11, 0xea, 0x2c, 0x35, 0x92}, + {0x27, 0x59, 0xc7, 0x35, 0x60, 0x71, 0xa6, 0xf1, + 0x79, 0xa5, 0xfd, 0x79, 0x16, 0xf3, 0x41, 0xf0, + 0x57, 0xb4, 0x02, 0x97, 0x32, 0xe7, 0xde, 0x59, + 0xe2, 0x2d, 0x9b, 0x11, 0xea, 0x2c, 0x35, 0x92}}, + {{0x28, 0x56, 0xac, 0x0e, 0x4f, 0x98, 0x09, 0xf0, + 0x49, 0xfa, 0x7f, 0x84, 0xac, 0x7e, 0x50, 0x5b, + 0x17, 0x43, 0x14, 0x89, 0x9c, 0x53, 0xa8, 0x94, + 0x30, 0xf2, 0x11, 0x4d, 0x92, 0x14, 0x27, 0xe8}, + {0x39, 0x7a, 0x84, 0x56, 0x79, 0x9d, 0xec, 0x26, + 0x2c, 0x53, 0xc1, 0x94, 0xc9, 0x8d, 0x9e, 0x9d, + 0x32, 0x1f, 0xdd, 0x84, 0x04, 0xe8, 0xe2, 0x0a, + 0x6b, 0xbe, 0xbb, 0x42, 0x40, 0x67, 0x30, 0x6c}}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x45, 0x51, 0x23, 0x19, 0x50, 0xb7, 0x5f, 0xc4, + 0x40, 0x2d, 0xa1, 0x73, 0x2f, 0xc9, 0xbe, 0xbd}, + {0x27, 0x59, 0xc7, 0x35, 0x60, 0x71, 0xa6, 0xf1, + 0x79, 0xa5, 0xfd, 0x79, 0x16, 0xf3, 0x41, 0xf0, + 0x57, 0xb4, 0x02, 0x97, 0x32, 0xe7, 0xde, 0x59, + 0xe2, 0x2d, 0x9b, 0x11, 0xea, 0x2c, 0x35, 0x92}}, + {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x40}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}, + {{0x1c, 0xc4, 0xf7, 0xda, 0x0f, 0x65, 0xca, 0x39, + 0x70, 0x52, 0x92, 0x8e, 0xc3, 0xc8, 0x15, 0xea, + 0x7f, 0x10, 0x9e, 0x77, 0x4b, 0x6e, 0x2d, 0xdf, + 0xe8, 0x30, 0x9d, 0xda, 0xe8, 0x9a, 0x65, 0xae}, + {0x02, 0xb0, 0x16, 0xb1, 0x1d, 0xc8, 0x57, 0x7b, + 0xa2, 0x3a, 0xa2, 0xa3, 0x38, 0x5c, 0x8f, 0xeb, + 0x66, 0x37, 0x91, 0xa8, 0x5f, 0xef, 0x04, 0xf6, + 0x59, 0x75, 0xe1, 0xee, 0x92, 0xf6, 0x0e, 0x30}}, + {{0x8d, 0x76, 0x14, 0xa4, 0x14, 0x06, 0x9f, 0x9a, + 0xdf, 0x4a, 0x85, 0xa7, 0x6b, 0xbf, 0x29, 0x6f, + 0xbc, 0x34, 0x87, 0x5d, 0xeb, 0xbb, 0x2e, 0xa9, + 0xc9, 0x1f, 0x58, 0xd6, 0x9a, 0x82, 0xa0, 0x56}, + {0xd4, 0xb9, 0xdb, 0x88, 0x1d, 0x04, 0xe9, 0x93, + 0x8d, 0x3f, 0x20, 0xd5, 0x86, 0xa8, 0x83, 0x07, + 0xdb, 0x09, 0xd8, 0x22, 0x1f, 0x7f, 0xf1, 0x71, + 0xc8, 0xe7, 0x5d, 0x47, 0xaf, 0x8b, 0x72, 0xe9}}, + {{0x83, 0xb9, 0x39, 0xb2, 0xa4, 0xdf, 0x46, 0x87, + 0xc2, 0xb8, 0xf1, 0xe6, 0x4c, 0xd1, 0xe2, 0xa9, + 0xe4, 0x70, 0x30, 0x34, 0xbc, 0x52, 0x7c, 0x55, + 0xa6, 0xec, 0x80, 0xa4, 0xe5, 0xd2, 0xdc, 0x73}, + {0x08, 0xf1, 0x03, 0xcf, 0x16, 0x73, 0xe8, 0x7d, + 0xb6, 0x7e, 0x9b, 0xc0, 0xb4, 0xc2, 0xa5, 0x86, + 0x02, 0x77, 0xd5, 0x27, 0x86, 0xa5, 0x15, 0xfb, + 0xae, 0x9b, 0x8c, 0xa9, 0xf9, 0xf8, 0xa8, 0x4a}}, + {{0x8b, 0x00, 0x49, 0xdb, 0xfa, 0xf0, 0x1b, 0xa2, + 0xed, 0x8a, 0x9a, 0x7a, 0x36, 0x78, 0x4a, 0xc7, + 0xf7, 0xad, 0x39, 0xd0, 0x6c, 0x65, 0x7a, 0x41, + 0xce, 0xd6, 0xd6, 0x4c, 0x20, 0x21, 0x6b, 0xc7}, + {0xc6, 0xca, 0x78, 0x1d, 0x32, 0x6c, 0x6c, 0x06, + 0x91, 0xf2, 0x1a, 0xe8, 0x43, 0x16, 0xea, 0x04, + 0x3c, 0x1f, 0x07, 0x85, 0xf7, 0x09, 0x22, 0x08, + 0xba, 0x13, 0xfd, 0x78, 0x1e, 0x3f, 0x6f, 0x62}}, + {{0x25, 0x9b, 0x7c, 0xb0, 0xac, 0x72, 0x6f, 0xb2, + 0xe3, 0x53, 0x84, 0x7a, 0x1a, 0x9a, 0x98, 0x9b, + 0x44, 0xd3, 0x59, 0xd0, 0x8e, 0x57, 0x41, 0x40, + 0x78, 0xa7, 0x30, 0x2f, 0x4c, 0x9c, 0xb9, 0x68}, + {0xb7, 0x75, 0x03, 0x63, 0x61, 0xc2, 0x48, 0x6e, + 0x12, 0x3d, 0xbf, 0x4b, 0x27, 0xdf, 0xb1, 0x7a, + 0xff, 0x4e, 0x31, 0x07, 0x83, 0xf4, 0x62, 0x5b, + 0x19, 0xa5, 0xac, 0xa0, 0x32, 0x58, 0x0d, 0xa7}}, + {{0x43, 0x4f, 0x10, 0xa4, 0xca, 0xdb, 0x38, 0x67, + 0xfa, 0xae, 0x96, 0xb5, 0x6d, 0x97, 0xff, 0x1f, + 0xb6, 0x83, 0x43, 0xd3, 0xa0, 0x2d, 0x70, 0x7a, + 0x64, 0x05, 0x4c, 0xa7, 0xc1, 0xa5, 0x21, 0x51}, + {0xe4, 0xf1, 0x23, 0x84, 0xe1, 0xb5, 0x9d, 0xf2, + 0xb8, 0x73, 0x8b, 0x45, 0x2b, 0x35, 0x46, 0x38, + 0x10, 0x2b, 0x50, 0xf8, 0x8b, 0x35, 0xcd, 0x34, + 0xc8, 0x0e, 0xf6, 0xdb, 0x09, 0x35, 0xf0, 0xda}}, + {{0xdb, 0x21, 0x5c, 0x8d, 0x83, 0x1d, 0xb3, 0x34, + 0xc7, 0x0e, 0x43, 0xa1, 0x58, 0x79, 0x67, 0x13, + 0x1e, 0x86, 0x5d, 0x89, 0x63, 0xe6, 0x0a, 0x46, + 0x5c, 0x02, 0x97, 0x1b, 0x62, 0x43, 0x86, 0xf5}, + {0xdb, 0x21, 0x5c, 0x8d, 0x83, 0x1d, 0xb3, 0x34, + 0xc7, 0x0e, 0x43, 0xa1, 0x58, 0x79, 0x67, 0x13, + 0x1e, 0x86, 0x5d, 0x89, 0x63, 0xe6, 0x0a, 0x46, + 0x5c, 0x02, 0x97, 0x1b, 0x62, 0x43, 0x86, 0xf5}} + }; + secp256k1_scalar_set_int(&one, 1); + for (i = 0; i < 33; i++) { + secp256k1_scalar_set_b32(&x, chal[i][0], &overflow); + CHECK(!overflow); + secp256k1_scalar_set_b32(&y, chal[i][1], &overflow); + CHECK(!overflow); + secp256k1_scalar_set_b32(&r1, res[i][0], &overflow); + CHECK(!overflow); + secp256k1_scalar_set_b32(&r2, res[i][1], &overflow); + CHECK(!overflow); + secp256k1_scalar_mul(&z, &x, &y); + CHECK(!secp256k1_scalar_check_overflow(&z)); + CHECK(secp256k1_scalar_eq(&r1, &z)); + if (!secp256k1_scalar_is_zero(&y)) { + secp256k1_scalar_inverse(&zz, &y); + CHECK(!secp256k1_scalar_check_overflow(&zz)); +#if defined(USE_SCALAR_INV_NUM) + secp256k1_scalar_inverse_var(&zzv, &y); + CHECK(secp256k1_scalar_eq(&zzv, &zz)); +#endif + secp256k1_scalar_mul(&z, &z, &zz); + CHECK(!secp256k1_scalar_check_overflow(&z)); + CHECK(secp256k1_scalar_eq(&x, &z)); + secp256k1_scalar_mul(&zz, &zz, &y); + CHECK(!secp256k1_scalar_check_overflow(&zz)); + CHECK(secp256k1_scalar_eq(&one, &zz)); + } + secp256k1_scalar_mul(&z, &x, &x); + CHECK(!secp256k1_scalar_check_overflow(&z)); + secp256k1_scalar_sqr(&zz, &x); + CHECK(!secp256k1_scalar_check_overflow(&zz)); + CHECK(secp256k1_scalar_eq(&zz, &z)); + CHECK(secp256k1_scalar_eq(&r2, &zz)); + } + } +} + +/***** FIELD TESTS *****/ + +void random_fe(secp256k1_fe *x) { + unsigned char bin[32]; + do { + secp256k1_rand256(bin); + if (secp256k1_fe_set_b32(x, bin)) { + return; + } + } while(1); +} + +void random_fe_test(secp256k1_fe *x) { + unsigned char bin[32]; + do { + secp256k1_rand256_test(bin); + if (secp256k1_fe_set_b32(x, bin)) { + return; + } + } while(1); +} + +void random_fe_non_zero(secp256k1_fe *nz) { + int tries = 10; + while (--tries >= 0) { + random_fe(nz); + secp256k1_fe_normalize(nz); + if (!secp256k1_fe_is_zero(nz)) { + break; + } + } + /* Infinitesimal probability of spurious failure here */ + CHECK(tries >= 0); +} + +void random_fe_non_square(secp256k1_fe *ns) { + secp256k1_fe r; + random_fe_non_zero(ns); + if (secp256k1_fe_sqrt(&r, ns)) { + secp256k1_fe_negate(ns, ns, 1); + } +} + +int check_fe_equal(const secp256k1_fe *a, const secp256k1_fe *b) { + secp256k1_fe an = *a; + secp256k1_fe bn = *b; + secp256k1_fe_normalize_weak(&an); + secp256k1_fe_normalize_var(&bn); + return secp256k1_fe_equal_var(&an, &bn); +} + +int check_fe_inverse(const secp256k1_fe *a, const secp256k1_fe *ai) { + secp256k1_fe x; + secp256k1_fe one = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1); + secp256k1_fe_mul(&x, a, ai); + return check_fe_equal(&x, &one); +} + +void run_field_convert(void) { + static const unsigned char b32[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40 + }; + static const secp256k1_fe_storage fes = SECP256K1_FE_STORAGE_CONST( + 0x00010203UL, 0x04050607UL, 0x11121314UL, 0x15161718UL, + 0x22232425UL, 0x26272829UL, 0x33343536UL, 0x37383940UL + ); + static const secp256k1_fe fe = SECP256K1_FE_CONST( + 0x00010203UL, 0x04050607UL, 0x11121314UL, 0x15161718UL, + 0x22232425UL, 0x26272829UL, 0x33343536UL, 0x37383940UL + ); + secp256k1_fe fe2; + unsigned char b322[32]; + secp256k1_fe_storage fes2; + /* Check conversions to fe. */ + CHECK(secp256k1_fe_set_b32(&fe2, b32)); + CHECK(secp256k1_fe_equal_var(&fe, &fe2)); + secp256k1_fe_from_storage(&fe2, &fes); + CHECK(secp256k1_fe_equal_var(&fe, &fe2)); + /* Check conversion from fe. */ + secp256k1_fe_get_b32(b322, &fe); + CHECK(memcmp(b322, b32, 32) == 0); + secp256k1_fe_to_storage(&fes2, &fe); + CHECK(memcmp(&fes2, &fes, sizeof(fes)) == 0); +} + +int fe_memcmp(const secp256k1_fe *a, const secp256k1_fe *b) { + secp256k1_fe t = *b; +#ifdef VERIFY + t.magnitude = a->magnitude; + t.normalized = a->normalized; +#endif + return memcmp(a, &t, sizeof(secp256k1_fe)); +} + +void run_field_misc(void) { + secp256k1_fe x; + secp256k1_fe y; + secp256k1_fe z; + secp256k1_fe q; + secp256k1_fe fe5 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 5); + int i, j; + for (i = 0; i < 5*count; i++) { + secp256k1_fe_storage xs, ys, zs; + random_fe(&x); + random_fe_non_zero(&y); + /* Test the fe equality and comparison operations. */ + CHECK(secp256k1_fe_cmp_var(&x, &x) == 0); + CHECK(secp256k1_fe_equal_var(&x, &x)); + z = x; + secp256k1_fe_add(&z,&y); + /* Test fe conditional move; z is not normalized here. */ + q = x; + secp256k1_fe_cmov(&x, &z, 0); + VERIFY_CHECK(!x.normalized && x.magnitude == z.magnitude); + secp256k1_fe_cmov(&x, &x, 1); + CHECK(fe_memcmp(&x, &z) != 0); + CHECK(fe_memcmp(&x, &q) == 0); + secp256k1_fe_cmov(&q, &z, 1); + VERIFY_CHECK(!q.normalized && q.magnitude == z.magnitude); + CHECK(fe_memcmp(&q, &z) == 0); + secp256k1_fe_normalize_var(&x); + secp256k1_fe_normalize_var(&z); + CHECK(!secp256k1_fe_equal_var(&x, &z)); + secp256k1_fe_normalize_var(&q); + secp256k1_fe_cmov(&q, &z, (i&1)); + VERIFY_CHECK(q.normalized && q.magnitude == 1); + for (j = 0; j < 6; j++) { + secp256k1_fe_negate(&z, &z, j+1); + secp256k1_fe_normalize_var(&q); + secp256k1_fe_cmov(&q, &z, (j&1)); + VERIFY_CHECK(!q.normalized && q.magnitude == (j+2)); + } + secp256k1_fe_normalize_var(&z); + /* Test storage conversion and conditional moves. */ + secp256k1_fe_to_storage(&xs, &x); + secp256k1_fe_to_storage(&ys, &y); + secp256k1_fe_to_storage(&zs, &z); + secp256k1_fe_storage_cmov(&zs, &xs, 0); + secp256k1_fe_storage_cmov(&zs, &zs, 1); + CHECK(memcmp(&xs, &zs, sizeof(xs)) != 0); + secp256k1_fe_storage_cmov(&ys, &xs, 1); + CHECK(memcmp(&xs, &ys, sizeof(xs)) == 0); + secp256k1_fe_from_storage(&x, &xs); + secp256k1_fe_from_storage(&y, &ys); + secp256k1_fe_from_storage(&z, &zs); + /* Test that mul_int, mul, and add agree. */ + secp256k1_fe_add(&y, &x); + secp256k1_fe_add(&y, &x); + z = x; + secp256k1_fe_mul_int(&z, 3); + CHECK(check_fe_equal(&y, &z)); + secp256k1_fe_add(&y, &x); + secp256k1_fe_add(&z, &x); + CHECK(check_fe_equal(&z, &y)); + z = x; + secp256k1_fe_mul_int(&z, 5); + secp256k1_fe_mul(&q, &x, &fe5); + CHECK(check_fe_equal(&z, &q)); + secp256k1_fe_negate(&x, &x, 1); + secp256k1_fe_add(&z, &x); + secp256k1_fe_add(&q, &x); + CHECK(check_fe_equal(&y, &z)); + CHECK(check_fe_equal(&q, &y)); + } +} + +void run_field_inv(void) { + secp256k1_fe x, xi, xii; + int i; + for (i = 0; i < 10*count; i++) { + random_fe_non_zero(&x); + secp256k1_fe_inv(&xi, &x); + CHECK(check_fe_inverse(&x, &xi)); + secp256k1_fe_inv(&xii, &xi); + CHECK(check_fe_equal(&x, &xii)); + } +} + +void run_field_inv_var(void) { + secp256k1_fe x, xi, xii; + int i; + for (i = 0; i < 10*count; i++) { + random_fe_non_zero(&x); + secp256k1_fe_inv_var(&xi, &x); + CHECK(check_fe_inverse(&x, &xi)); + secp256k1_fe_inv_var(&xii, &xi); + CHECK(check_fe_equal(&x, &xii)); + } +} + +void run_field_inv_all_var(void) { + secp256k1_fe x[16], xi[16], xii[16]; + int i; + /* Check it's safe to call for 0 elements */ + secp256k1_fe_inv_all_var(xi, x, 0); + for (i = 0; i < count; i++) { + size_t j; + size_t len = secp256k1_rand_int(15) + 1; + for (j = 0; j < len; j++) { + random_fe_non_zero(&x[j]); + } + secp256k1_fe_inv_all_var(xi, x, len); + for (j = 0; j < len; j++) { + CHECK(check_fe_inverse(&x[j], &xi[j])); + } + secp256k1_fe_inv_all_var(xii, xi, len); + for (j = 0; j < len; j++) { + CHECK(check_fe_equal(&x[j], &xii[j])); + } + } +} + +void run_sqr(void) { + secp256k1_fe x, s; + + { + int i; + secp256k1_fe_set_int(&x, 1); + secp256k1_fe_negate(&x, &x, 1); + + for (i = 1; i <= 512; ++i) { + secp256k1_fe_mul_int(&x, 2); + secp256k1_fe_normalize(&x); + secp256k1_fe_sqr(&s, &x); + } + } +} + +void test_sqrt(const secp256k1_fe *a, const secp256k1_fe *k) { + secp256k1_fe r1, r2; + int v = secp256k1_fe_sqrt(&r1, a); + CHECK((v == 0) == (k == NULL)); + + if (k != NULL) { + /* Check that the returned root is +/- the given known answer */ + secp256k1_fe_negate(&r2, &r1, 1); + secp256k1_fe_add(&r1, k); secp256k1_fe_add(&r2, k); + secp256k1_fe_normalize(&r1); secp256k1_fe_normalize(&r2); + CHECK(secp256k1_fe_is_zero(&r1) || secp256k1_fe_is_zero(&r2)); + } +} + +void run_sqrt(void) { + secp256k1_fe ns, x, s, t; + int i; + + /* Check sqrt(0) is 0 */ + secp256k1_fe_set_int(&x, 0); + secp256k1_fe_sqr(&s, &x); + test_sqrt(&s, &x); + + /* Check sqrt of small squares (and their negatives) */ + for (i = 1; i <= 100; i++) { + secp256k1_fe_set_int(&x, i); + secp256k1_fe_sqr(&s, &x); + test_sqrt(&s, &x); + secp256k1_fe_negate(&t, &s, 1); + test_sqrt(&t, NULL); + } + + /* Consistency checks for large random values */ + for (i = 0; i < 10; i++) { + int j; + random_fe_non_square(&ns); + for (j = 0; j < count; j++) { + random_fe(&x); + secp256k1_fe_sqr(&s, &x); + test_sqrt(&s, &x); + secp256k1_fe_negate(&t, &s, 1); + test_sqrt(&t, NULL); + secp256k1_fe_mul(&t, &s, &ns); + test_sqrt(&t, NULL); + } + } +} + +/***** GROUP TESTS *****/ + +void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) { + CHECK(a->infinity == b->infinity); + if (a->infinity) { + return; + } + CHECK(secp256k1_fe_equal_var(&a->x, &b->x)); + CHECK(secp256k1_fe_equal_var(&a->y, &b->y)); +} + +/* This compares jacobian points including their Z, not just their geometric meaning. */ +int gej_xyz_equals_gej(const secp256k1_gej *a, const secp256k1_gej *b) { + secp256k1_gej a2; + secp256k1_gej b2; + int ret = 1; + ret &= a->infinity == b->infinity; + if (ret && !a->infinity) { + a2 = *a; + b2 = *b; + secp256k1_fe_normalize(&a2.x); + secp256k1_fe_normalize(&a2.y); + secp256k1_fe_normalize(&a2.z); + secp256k1_fe_normalize(&b2.x); + secp256k1_fe_normalize(&b2.y); + secp256k1_fe_normalize(&b2.z); + ret &= secp256k1_fe_cmp_var(&a2.x, &b2.x) == 0; + ret &= secp256k1_fe_cmp_var(&a2.y, &b2.y) == 0; + ret &= secp256k1_fe_cmp_var(&a2.z, &b2.z) == 0; + } + return ret; +} + +void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) { + secp256k1_fe z2s; + secp256k1_fe u1, u2, s1, s2; + CHECK(a->infinity == b->infinity); + if (a->infinity) { + return; + } + /* Check a.x * b.z^2 == b.x && a.y * b.z^3 == b.y, to avoid inverses. */ + secp256k1_fe_sqr(&z2s, &b->z); + secp256k1_fe_mul(&u1, &a->x, &z2s); + u2 = b->x; secp256k1_fe_normalize_weak(&u2); + secp256k1_fe_mul(&s1, &a->y, &z2s); secp256k1_fe_mul(&s1, &s1, &b->z); + s2 = b->y; secp256k1_fe_normalize_weak(&s2); + CHECK(secp256k1_fe_equal_var(&u1, &u2)); + CHECK(secp256k1_fe_equal_var(&s1, &s2)); +} + +void test_ge(void) { + int i, i1; +#ifdef USE_ENDOMORPHISM + int runs = 6; +#else + int runs = 4; +#endif + /* Points: (infinity, p1, p1, -p1, -p1, p2, p2, -p2, -p2, p3, p3, -p3, -p3, p4, p4, -p4, -p4). + * The second in each pair of identical points uses a random Z coordinate in the Jacobian form. + * All magnitudes are randomized. + * All 17*17 combinations of points are added to each other, using all applicable methods. + * + * When the endomorphism code is compiled in, p5 = lambda*p1 and p6 = lambda^2*p1 are added as well. + */ + secp256k1_ge *ge = (secp256k1_ge *)malloc(sizeof(secp256k1_ge) * (1 + 4 * runs)); + secp256k1_gej *gej = (secp256k1_gej *)malloc(sizeof(secp256k1_gej) * (1 + 4 * runs)); + secp256k1_fe *zinv = (secp256k1_fe *)malloc(sizeof(secp256k1_fe) * (1 + 4 * runs)); + secp256k1_fe zf; + secp256k1_fe zfi2, zfi3; + + secp256k1_gej_set_infinity(&gej[0]); + secp256k1_ge_clear(&ge[0]); + secp256k1_ge_set_gej_var(&ge[0], &gej[0]); + for (i = 0; i < runs; i++) { + int j; + secp256k1_ge g; + random_group_element_test(&g); +#ifdef USE_ENDOMORPHISM + if (i >= runs - 2) { + secp256k1_ge_mul_lambda(&g, &ge[1]); + } + if (i >= runs - 1) { + secp256k1_ge_mul_lambda(&g, &g); + } +#endif + ge[1 + 4 * i] = g; + ge[2 + 4 * i] = g; + secp256k1_ge_neg(&ge[3 + 4 * i], &g); + secp256k1_ge_neg(&ge[4 + 4 * i], &g); + secp256k1_gej_set_ge(&gej[1 + 4 * i], &ge[1 + 4 * i]); + random_group_element_jacobian_test(&gej[2 + 4 * i], &ge[2 + 4 * i]); + secp256k1_gej_set_ge(&gej[3 + 4 * i], &ge[3 + 4 * i]); + random_group_element_jacobian_test(&gej[4 + 4 * i], &ge[4 + 4 * i]); + for (j = 0; j < 4; j++) { + random_field_element_magnitude(&ge[1 + j + 4 * i].x); + random_field_element_magnitude(&ge[1 + j + 4 * i].y); + random_field_element_magnitude(&gej[1 + j + 4 * i].x); + random_field_element_magnitude(&gej[1 + j + 4 * i].y); + random_field_element_magnitude(&gej[1 + j + 4 * i].z); + } + } + + /* Compute z inverses. */ + { + secp256k1_fe *zs = malloc(sizeof(secp256k1_fe) * (1 + 4 * runs)); + for (i = 0; i < 4 * runs + 1; i++) { + if (i == 0) { + /* The point at infinity does not have a meaningful z inverse. Any should do. */ + do { + random_field_element_test(&zs[i]); + } while(secp256k1_fe_is_zero(&zs[i])); + } else { + zs[i] = gej[i].z; + } + } + secp256k1_fe_inv_all_var(zinv, zs, 4 * runs + 1); + free(zs); + } + + /* Generate random zf, and zfi2 = 1/zf^2, zfi3 = 1/zf^3 */ + do { + random_field_element_test(&zf); + } while(secp256k1_fe_is_zero(&zf)); + random_field_element_magnitude(&zf); + secp256k1_fe_inv_var(&zfi3, &zf); + secp256k1_fe_sqr(&zfi2, &zfi3); + secp256k1_fe_mul(&zfi3, &zfi3, &zfi2); + + for (i1 = 0; i1 < 1 + 4 * runs; i1++) { + int i2; + for (i2 = 0; i2 < 1 + 4 * runs; i2++) { + /* Compute reference result using gej + gej (var). */ + secp256k1_gej refj, resj; + secp256k1_ge ref; + secp256k1_fe zr; + secp256k1_gej_add_var(&refj, &gej[i1], &gej[i2], secp256k1_gej_is_infinity(&gej[i1]) ? NULL : &zr); + /* Check Z ratio. */ + if (!secp256k1_gej_is_infinity(&gej[i1]) && !secp256k1_gej_is_infinity(&refj)) { + secp256k1_fe zrz; secp256k1_fe_mul(&zrz, &zr, &gej[i1].z); + CHECK(secp256k1_fe_equal_var(&zrz, &refj.z)); + } + secp256k1_ge_set_gej_var(&ref, &refj); + + /* Test gej + ge with Z ratio result (var). */ + secp256k1_gej_add_ge_var(&resj, &gej[i1], &ge[i2], secp256k1_gej_is_infinity(&gej[i1]) ? NULL : &zr); + ge_equals_gej(&ref, &resj); + if (!secp256k1_gej_is_infinity(&gej[i1]) && !secp256k1_gej_is_infinity(&resj)) { + secp256k1_fe zrz; secp256k1_fe_mul(&zrz, &zr, &gej[i1].z); + CHECK(secp256k1_fe_equal_var(&zrz, &resj.z)); + } + + /* Test gej + ge (var, with additional Z factor). */ + { + secp256k1_ge ge2_zfi = ge[i2]; /* the second term with x and y rescaled for z = 1/zf */ + secp256k1_fe_mul(&ge2_zfi.x, &ge2_zfi.x, &zfi2); + secp256k1_fe_mul(&ge2_zfi.y, &ge2_zfi.y, &zfi3); + random_field_element_magnitude(&ge2_zfi.x); + random_field_element_magnitude(&ge2_zfi.y); + secp256k1_gej_add_zinv_var(&resj, &gej[i1], &ge2_zfi, &zf); + ge_equals_gej(&ref, &resj); + } + + /* Test gej + ge (const). */ + if (i2 != 0) { + /* secp256k1_gej_add_ge does not support its second argument being infinity. */ + secp256k1_gej_add_ge(&resj, &gej[i1], &ge[i2]); + ge_equals_gej(&ref, &resj); + } + + /* Test doubling (var). */ + if ((i1 == 0 && i2 == 0) || ((i1 + 3)/4 == (i2 + 3)/4 && ((i1 + 3)%4)/2 == ((i2 + 3)%4)/2)) { + secp256k1_fe zr2; + /* Normal doubling with Z ratio result. */ + secp256k1_gej_double_var(&resj, &gej[i1], &zr2); + ge_equals_gej(&ref, &resj); + /* Check Z ratio. */ + secp256k1_fe_mul(&zr2, &zr2, &gej[i1].z); + CHECK(secp256k1_fe_equal_var(&zr2, &resj.z)); + /* Normal doubling. */ + secp256k1_gej_double_var(&resj, &gej[i2], NULL); + ge_equals_gej(&ref, &resj); + } + + /* Test adding opposites. */ + if ((i1 == 0 && i2 == 0) || ((i1 + 3)/4 == (i2 + 3)/4 && ((i1 + 3)%4)/2 != ((i2 + 3)%4)/2)) { + CHECK(secp256k1_ge_is_infinity(&ref)); + } + + /* Test adding infinity. */ + if (i1 == 0) { + CHECK(secp256k1_ge_is_infinity(&ge[i1])); + CHECK(secp256k1_gej_is_infinity(&gej[i1])); + ge_equals_gej(&ref, &gej[i2]); + } + if (i2 == 0) { + CHECK(secp256k1_ge_is_infinity(&ge[i2])); + CHECK(secp256k1_gej_is_infinity(&gej[i2])); + ge_equals_gej(&ref, &gej[i1]); + } + } + } + + /* Test adding all points together in random order equals infinity. */ + { + secp256k1_gej sum = SECP256K1_GEJ_CONST_INFINITY; + secp256k1_gej *gej_shuffled = (secp256k1_gej *)malloc((4 * runs + 1) * sizeof(secp256k1_gej)); + for (i = 0; i < 4 * runs + 1; i++) { + gej_shuffled[i] = gej[i]; + } + for (i = 0; i < 4 * runs + 1; i++) { + int swap = i + secp256k1_rand_int(4 * runs + 1 - i); + if (swap != i) { + secp256k1_gej t = gej_shuffled[i]; + gej_shuffled[i] = gej_shuffled[swap]; + gej_shuffled[swap] = t; + } + } + for (i = 0; i < 4 * runs + 1; i++) { + secp256k1_gej_add_var(&sum, &sum, &gej_shuffled[i], NULL); + } + CHECK(secp256k1_gej_is_infinity(&sum)); + free(gej_shuffled); + } + + /* Test batch gej -> ge conversion with and without known z ratios. */ + { + secp256k1_fe *zr = (secp256k1_fe *)malloc((4 * runs + 1) * sizeof(secp256k1_fe)); + secp256k1_ge *ge_set_table = (secp256k1_ge *)malloc((4 * runs + 1) * sizeof(secp256k1_ge)); + secp256k1_ge *ge_set_all = (secp256k1_ge *)malloc((4 * runs + 1) * sizeof(secp256k1_ge)); + for (i = 0; i < 4 * runs + 1; i++) { + /* Compute gej[i + 1].z / gez[i].z (with gej[n].z taken to be 1). */ + if (i < 4 * runs) { + secp256k1_fe_mul(&zr[i + 1], &zinv[i], &gej[i + 1].z); + } + } + secp256k1_ge_set_table_gej_var(ge_set_table, gej, zr, 4 * runs + 1); + secp256k1_ge_set_all_gej_var(ge_set_all, gej, 4 * runs + 1, &ctx->error_callback); + for (i = 0; i < 4 * runs + 1; i++) { + secp256k1_fe s; + random_fe_non_zero(&s); + secp256k1_gej_rescale(&gej[i], &s); + ge_equals_gej(&ge_set_table[i], &gej[i]); + ge_equals_gej(&ge_set_all[i], &gej[i]); + } + free(ge_set_table); + free(ge_set_all); + free(zr); + } + + free(ge); + free(gej); + free(zinv); +} + +void test_add_neg_y_diff_x(void) { + /* The point of this test is to check that we can add two points + * whose y-coordinates are negatives of each other but whose x + * coordinates differ. If the x-coordinates were the same, these + * points would be negatives of each other and their sum is + * infinity. This is cool because it "covers up" any degeneracy + * in the addition algorithm that would cause the xy coordinates + * of the sum to be wrong (since infinity has no xy coordinates). + * HOWEVER, if the x-coordinates are different, infinity is the + * wrong answer, and such degeneracies are exposed. This is the + * root of https://github.com/bitcoin-core/secp256k1/issues/257 + * which this test is a regression test for. + * + * These points were generated in sage as + * # secp256k1 params + * F = FiniteField (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F) + * C = EllipticCurve ([F (0), F (7)]) + * G = C.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) + * N = FiniteField(G.order()) + * + * # endomorphism values (lambda is 1^{1/3} in N, beta is 1^{1/3} in F) + * x = polygen(N) + * lam = (1 - x^3).roots()[1][0] + * + * # random "bad pair" + * P = C.random_element() + * Q = -int(lam) * P + * print " P: %x %x" % P.xy() + * print " Q: %x %x" % Q.xy() + * print "P + Q: %x %x" % (P + Q).xy() + */ + secp256k1_gej aj = SECP256K1_GEJ_CONST( + 0x8d24cd95, 0x0a355af1, 0x3c543505, 0x44238d30, + 0x0643d79f, 0x05a59614, 0x2f8ec030, 0xd58977cb, + 0x001e337a, 0x38093dcd, 0x6c0f386d, 0x0b1293a8, + 0x4d72c879, 0xd7681924, 0x44e6d2f3, 0x9190117d + ); + secp256k1_gej bj = SECP256K1_GEJ_CONST( + 0xc7b74206, 0x1f788cd9, 0xabd0937d, 0x164a0d86, + 0x95f6ff75, 0xf19a4ce9, 0xd013bd7b, 0xbf92d2a7, + 0xffe1cc85, 0xc7f6c232, 0x93f0c792, 0xf4ed6c57, + 0xb28d3786, 0x2897e6db, 0xbb192d0b, 0x6e6feab2 + ); + secp256k1_gej sumj = SECP256K1_GEJ_CONST( + 0x671a63c0, 0x3efdad4c, 0x389a7798, 0x24356027, + 0xb3d69010, 0x278625c3, 0x5c86d390, 0x184a8f7a, + 0x5f6409c2, 0x2ce01f2b, 0x511fd375, 0x25071d08, + 0xda651801, 0x70e95caf, 0x8f0d893c, 0xbed8fbbe + ); + secp256k1_ge b; + secp256k1_gej resj; + secp256k1_ge res; + secp256k1_ge_set_gej(&b, &bj); + + secp256k1_gej_add_var(&resj, &aj, &bj, NULL); + secp256k1_ge_set_gej(&res, &resj); + ge_equals_gej(&res, &sumj); + + secp256k1_gej_add_ge(&resj, &aj, &b); + secp256k1_ge_set_gej(&res, &resj); + ge_equals_gej(&res, &sumj); + + secp256k1_gej_add_ge_var(&resj, &aj, &b, NULL); + secp256k1_ge_set_gej(&res, &resj); + ge_equals_gej(&res, &sumj); +} + +void run_ge(void) { + int i; + for (i = 0; i < count * 32; i++) { + test_ge(); + } + test_add_neg_y_diff_x(); +} + +void test_ec_combine(void) { + secp256k1_scalar sum = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + secp256k1_pubkey data[6]; + const secp256k1_pubkey* d[6]; + secp256k1_pubkey sd; + secp256k1_pubkey sd2; + secp256k1_gej Qj; + secp256k1_ge Q; + int i; + for (i = 1; i <= 6; i++) { + secp256k1_scalar s; + random_scalar_order_test(&s); + secp256k1_scalar_add(&sum, &sum, &s); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &Qj, &s); + secp256k1_ge_set_gej(&Q, &Qj); + secp256k1_pubkey_save(&data[i - 1], &Q); + d[i - 1] = &data[i - 1]; + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &Qj, &sum); + secp256k1_ge_set_gej(&Q, &Qj); + secp256k1_pubkey_save(&sd, &Q); + CHECK(secp256k1_ec_pubkey_combine(ctx, &sd2, d, i) == 1); + CHECK(memcmp(&sd, &sd2, sizeof(sd)) == 0); + } +} + +void run_ec_combine(void) { + int i; + for (i = 0; i < count * 8; i++) { + test_ec_combine(); + } +} + +void test_group_decompress(const secp256k1_fe* x) { + /* The input itself, normalized. */ + secp256k1_fe fex = *x; + secp256k1_fe fez; + /* Results of set_xquad_var, set_xo_var(..., 0), set_xo_var(..., 1). */ + secp256k1_ge ge_quad, ge_even, ge_odd; + secp256k1_gej gej_quad; + /* Return values of the above calls. */ + int res_quad, res_even, res_odd; + + secp256k1_fe_normalize_var(&fex); + + res_quad = secp256k1_ge_set_xquad(&ge_quad, &fex); + res_even = secp256k1_ge_set_xo_var(&ge_even, &fex, 0); + res_odd = secp256k1_ge_set_xo_var(&ge_odd, &fex, 1); + + CHECK(res_quad == res_even); + CHECK(res_quad == res_odd); + + if (res_quad) { + secp256k1_fe_normalize_var(&ge_quad.x); + secp256k1_fe_normalize_var(&ge_odd.x); + secp256k1_fe_normalize_var(&ge_even.x); + secp256k1_fe_normalize_var(&ge_quad.y); + secp256k1_fe_normalize_var(&ge_odd.y); + secp256k1_fe_normalize_var(&ge_even.y); + + /* No infinity allowed. */ + CHECK(!ge_quad.infinity); + CHECK(!ge_even.infinity); + CHECK(!ge_odd.infinity); + + /* Check that the x coordinates check out. */ + CHECK(secp256k1_fe_equal_var(&ge_quad.x, x)); + CHECK(secp256k1_fe_equal_var(&ge_even.x, x)); + CHECK(secp256k1_fe_equal_var(&ge_odd.x, x)); + + /* Check that the Y coordinate result in ge_quad is a square. */ + CHECK(secp256k1_fe_is_quad_var(&ge_quad.y)); + + /* Check odd/even Y in ge_odd, ge_even. */ + CHECK(secp256k1_fe_is_odd(&ge_odd.y)); + CHECK(!secp256k1_fe_is_odd(&ge_even.y)); + + /* Check secp256k1_gej_has_quad_y_var. */ + secp256k1_gej_set_ge(&gej_quad, &ge_quad); + CHECK(secp256k1_gej_has_quad_y_var(&gej_quad)); + do { + random_fe_test(&fez); + } while (secp256k1_fe_is_zero(&fez)); + secp256k1_gej_rescale(&gej_quad, &fez); + CHECK(secp256k1_gej_has_quad_y_var(&gej_quad)); + secp256k1_gej_neg(&gej_quad, &gej_quad); + CHECK(!secp256k1_gej_has_quad_y_var(&gej_quad)); + do { + random_fe_test(&fez); + } while (secp256k1_fe_is_zero(&fez)); + secp256k1_gej_rescale(&gej_quad, &fez); + CHECK(!secp256k1_gej_has_quad_y_var(&gej_quad)); + secp256k1_gej_neg(&gej_quad, &gej_quad); + CHECK(secp256k1_gej_has_quad_y_var(&gej_quad)); + } +} + +void run_group_decompress(void) { + int i; + for (i = 0; i < count * 4; i++) { + secp256k1_fe fe; + random_fe_test(&fe); + test_group_decompress(&fe); + } +} + +/***** ECMULT TESTS *****/ + +void run_ecmult_chain(void) { + /* random starting point A (on the curve) */ + secp256k1_gej a = SECP256K1_GEJ_CONST( + 0x8b30bbe9, 0xae2a9906, 0x96b22f67, 0x0709dff3, + 0x727fd8bc, 0x04d3362c, 0x6c7bf458, 0xe2846004, + 0xa357ae91, 0x5c4a6528, 0x1309edf2, 0x0504740f, + 0x0eb33439, 0x90216b4f, 0x81063cb6, 0x5f2f7e0f + ); + /* two random initial factors xn and gn */ + secp256k1_scalar xn = SECP256K1_SCALAR_CONST( + 0x84cc5452, 0xf7fde1ed, 0xb4d38a8c, 0xe9b1b84c, + 0xcef31f14, 0x6e569be9, 0x705d357a, 0x42985407 + ); + secp256k1_scalar gn = SECP256K1_SCALAR_CONST( + 0xa1e58d22, 0x553dcd42, 0xb2398062, 0x5d4c57a9, + 0x6e9323d4, 0x2b3152e5, 0xca2c3990, 0xedc7c9de + ); + /* two small multipliers to be applied to xn and gn in every iteration: */ + static const secp256k1_scalar xf = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0x1337); + static const secp256k1_scalar gf = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0x7113); + /* accumulators with the resulting coefficients to A and G */ + secp256k1_scalar ae = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 1); + secp256k1_scalar ge = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + /* actual points */ + secp256k1_gej x; + secp256k1_gej x2; + int i; + + /* the point being computed */ + x = a; + for (i = 0; i < 200*count; i++) { + /* in each iteration, compute X = xn*X + gn*G; */ + secp256k1_ecmult(&ctx->ecmult_ctx, &x, &x, &xn, &gn); + /* also compute ae and ge: the actual accumulated factors for A and G */ + /* if X was (ae*A+ge*G), xn*X + gn*G results in (xn*ae*A + (xn*ge+gn)*G) */ + secp256k1_scalar_mul(&ae, &ae, &xn); + secp256k1_scalar_mul(&ge, &ge, &xn); + secp256k1_scalar_add(&ge, &ge, &gn); + /* modify xn and gn */ + secp256k1_scalar_mul(&xn, &xn, &xf); + secp256k1_scalar_mul(&gn, &gn, &gf); + + /* verify */ + if (i == 19999) { + /* expected result after 19999 iterations */ + secp256k1_gej rp = SECP256K1_GEJ_CONST( + 0xD6E96687, 0xF9B10D09, 0x2A6F3543, 0x9D86CEBE, + 0xA4535D0D, 0x409F5358, 0x6440BD74, 0xB933E830, + 0xB95CBCA2, 0xC77DA786, 0x539BE8FD, 0x53354D2D, + 0x3B4F566A, 0xE6580454, 0x07ED6015, 0xEE1B2A88 + ); + + secp256k1_gej_neg(&rp, &rp); + secp256k1_gej_add_var(&rp, &rp, &x, NULL); + CHECK(secp256k1_gej_is_infinity(&rp)); + } + } + /* redo the computation, but directly with the resulting ae and ge coefficients: */ + secp256k1_ecmult(&ctx->ecmult_ctx, &x2, &a, &ae, &ge); + secp256k1_gej_neg(&x2, &x2); + secp256k1_gej_add_var(&x2, &x2, &x, NULL); + CHECK(secp256k1_gej_is_infinity(&x2)); +} + +void test_point_times_order(const secp256k1_gej *point) { + /* X * (point + G) + (order-X) * (pointer + G) = 0 */ + secp256k1_scalar x; + secp256k1_scalar nx; + secp256k1_scalar zero = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + secp256k1_scalar one = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 1); + secp256k1_gej res1, res2; + secp256k1_ge res3; + unsigned char pub[65]; + size_t psize = 65; + random_scalar_order_test(&x); + secp256k1_scalar_negate(&nx, &x); + secp256k1_ecmult(&ctx->ecmult_ctx, &res1, point, &x, &x); /* calc res1 = x * point + x * G; */ + secp256k1_ecmult(&ctx->ecmult_ctx, &res2, point, &nx, &nx); /* calc res2 = (order - x) * point + (order - x) * G; */ + secp256k1_gej_add_var(&res1, &res1, &res2, NULL); + CHECK(secp256k1_gej_is_infinity(&res1)); + CHECK(secp256k1_gej_is_valid_var(&res1) == 0); + secp256k1_ge_set_gej(&res3, &res1); + CHECK(secp256k1_ge_is_infinity(&res3)); + CHECK(secp256k1_ge_is_valid_var(&res3) == 0); + CHECK(secp256k1_eckey_pubkey_serialize(&res3, pub, &psize, 0) == 0); + psize = 65; + CHECK(secp256k1_eckey_pubkey_serialize(&res3, pub, &psize, 1) == 0); + /* check zero/one edge cases */ + secp256k1_ecmult(&ctx->ecmult_ctx, &res1, point, &zero, &zero); + secp256k1_ge_set_gej(&res3, &res1); + CHECK(secp256k1_ge_is_infinity(&res3)); + secp256k1_ecmult(&ctx->ecmult_ctx, &res1, point, &one, &zero); + secp256k1_ge_set_gej(&res3, &res1); + ge_equals_gej(&res3, point); + secp256k1_ecmult(&ctx->ecmult_ctx, &res1, point, &zero, &one); + secp256k1_ge_set_gej(&res3, &res1); + ge_equals_ge(&res3, &secp256k1_ge_const_g); +} + +void run_point_times_order(void) { + int i; + secp256k1_fe x = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 2); + static const secp256k1_fe xr = SECP256K1_FE_CONST( + 0x7603CB59, 0xB0EF6C63, 0xFE608479, 0x2A0C378C, + 0xDB3233A8, 0x0F8A9A09, 0xA877DEAD, 0x31B38C45 + ); + for (i = 0; i < 500; i++) { + secp256k1_ge p; + if (secp256k1_ge_set_xo_var(&p, &x, 1)) { + secp256k1_gej j; + CHECK(secp256k1_ge_is_valid_var(&p)); + secp256k1_gej_set_ge(&j, &p); + CHECK(secp256k1_gej_is_valid_var(&j)); + test_point_times_order(&j); + } + secp256k1_fe_sqr(&x, &x); + } + secp256k1_fe_normalize_var(&x); + CHECK(secp256k1_fe_equal_var(&x, &xr)); +} + +void ecmult_const_random_mult(void) { + /* random starting point A (on the curve) */ + secp256k1_ge a = SECP256K1_GE_CONST( + 0x6d986544, 0x57ff52b8, 0xcf1b8126, 0x5b802a5b, + 0xa97f9263, 0xb1e88044, 0x93351325, 0x91bc450a, + 0x535c59f7, 0x325e5d2b, 0xc391fbe8, 0x3c12787c, + 0x337e4a98, 0xe82a9011, 0x0123ba37, 0xdd769c7d + ); + /* random initial factor xn */ + secp256k1_scalar xn = SECP256K1_SCALAR_CONST( + 0x649d4f77, 0xc4242df7, 0x7f2079c9, 0x14530327, + 0xa31b876a, 0xd2d8ce2a, 0x2236d5c6, 0xd7b2029b + ); + /* expected xn * A (from sage) */ + secp256k1_ge expected_b = SECP256K1_GE_CONST( + 0x23773684, 0x4d209dc7, 0x098a786f, 0x20d06fcd, + 0x070a38bf, 0xc11ac651, 0x03004319, 0x1e2a8786, + 0xed8c3b8e, 0xc06dd57b, 0xd06ea66e, 0x45492b0f, + 0xb84e4e1b, 0xfb77e21f, 0x96baae2a, 0x63dec956 + ); + secp256k1_gej b; + secp256k1_ecmult_const(&b, &a, &xn); + + CHECK(secp256k1_ge_is_valid_var(&a)); + ge_equals_gej(&expected_b, &b); +} + +void ecmult_const_commutativity(void) { + secp256k1_scalar a; + secp256k1_scalar b; + secp256k1_gej res1; + secp256k1_gej res2; + secp256k1_ge mid1; + secp256k1_ge mid2; + random_scalar_order_test(&a); + random_scalar_order_test(&b); + + secp256k1_ecmult_const(&res1, &secp256k1_ge_const_g, &a); + secp256k1_ecmult_const(&res2, &secp256k1_ge_const_g, &b); + secp256k1_ge_set_gej(&mid1, &res1); + secp256k1_ge_set_gej(&mid2, &res2); + secp256k1_ecmult_const(&res1, &mid1, &b); + secp256k1_ecmult_const(&res2, &mid2, &a); + secp256k1_ge_set_gej(&mid1, &res1); + secp256k1_ge_set_gej(&mid2, &res2); + ge_equals_ge(&mid1, &mid2); +} + +void ecmult_const_mult_zero_one(void) { + secp256k1_scalar zero = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + secp256k1_scalar one = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 1); + secp256k1_scalar negone; + secp256k1_gej res1; + secp256k1_ge res2; + secp256k1_ge point; + secp256k1_scalar_negate(&negone, &one); + + random_group_element_test(&point); + secp256k1_ecmult_const(&res1, &point, &zero); + secp256k1_ge_set_gej(&res2, &res1); + CHECK(secp256k1_ge_is_infinity(&res2)); + secp256k1_ecmult_const(&res1, &point, &one); + secp256k1_ge_set_gej(&res2, &res1); + ge_equals_ge(&res2, &point); + secp256k1_ecmult_const(&res1, &point, &negone); + secp256k1_gej_neg(&res1, &res1); + secp256k1_ge_set_gej(&res2, &res1); + ge_equals_ge(&res2, &point); +} + +void ecmult_const_chain_multiply(void) { + /* Check known result (randomly generated test problem from sage) */ + const secp256k1_scalar scalar = SECP256K1_SCALAR_CONST( + 0x4968d524, 0x2abf9b7a, 0x466abbcf, 0x34b11b6d, + 0xcd83d307, 0x827bed62, 0x05fad0ce, 0x18fae63b + ); + const secp256k1_gej expected_point = SECP256K1_GEJ_CONST( + 0x5494c15d, 0x32099706, 0xc2395f94, 0x348745fd, + 0x757ce30e, 0x4e8c90fb, 0xa2bad184, 0xf883c69f, + 0x5d195d20, 0xe191bf7f, 0x1be3e55f, 0x56a80196, + 0x6071ad01, 0xf1462f66, 0xc997fa94, 0xdb858435 + ); + secp256k1_gej point; + secp256k1_ge res; + int i; + + secp256k1_gej_set_ge(&point, &secp256k1_ge_const_g); + for (i = 0; i < 100; ++i) { + secp256k1_ge tmp; + secp256k1_ge_set_gej(&tmp, &point); + secp256k1_ecmult_const(&point, &tmp, &scalar); + } + secp256k1_ge_set_gej(&res, &point); + ge_equals_gej(&res, &expected_point); +} + +void run_ecmult_const_tests(void) { + ecmult_const_mult_zero_one(); + ecmult_const_random_mult(); + ecmult_const_commutativity(); + ecmult_const_chain_multiply(); +} + +void test_wnaf(const secp256k1_scalar *number, int w) { + secp256k1_scalar x, two, t; + int wnaf[256]; + int zeroes = -1; + int i; + int bits; + secp256k1_scalar_set_int(&x, 0); + secp256k1_scalar_set_int(&two, 2); + bits = secp256k1_ecmult_wnaf(wnaf, 256, number, w); + CHECK(bits <= 256); + for (i = bits-1; i >= 0; i--) { + int v = wnaf[i]; + secp256k1_scalar_mul(&x, &x, &two); + if (v) { + CHECK(zeroes == -1 || zeroes >= w-1); /* check that distance between non-zero elements is at least w-1 */ + zeroes=0; + CHECK((v & 1) == 1); /* check non-zero elements are odd */ + CHECK(v <= (1 << (w-1)) - 1); /* check range below */ + CHECK(v >= -(1 << (w-1)) - 1); /* check range above */ + } else { + CHECK(zeroes != -1); /* check that no unnecessary zero padding exists */ + zeroes++; + } + if (v >= 0) { + secp256k1_scalar_set_int(&t, v); + } else { + secp256k1_scalar_set_int(&t, -v); + secp256k1_scalar_negate(&t, &t); + } + secp256k1_scalar_add(&x, &x, &t); + } + CHECK(secp256k1_scalar_eq(&x, number)); /* check that wnaf represents number */ +} + +void test_constant_wnaf_negate(const secp256k1_scalar *number) { + secp256k1_scalar neg1 = *number; + secp256k1_scalar neg2 = *number; + int sign1 = 1; + int sign2 = 1; + + if (!secp256k1_scalar_get_bits(&neg1, 0, 1)) { + secp256k1_scalar_negate(&neg1, &neg1); + sign1 = -1; + } + sign2 = secp256k1_scalar_cond_negate(&neg2, secp256k1_scalar_is_even(&neg2)); + CHECK(sign1 == sign2); + CHECK(secp256k1_scalar_eq(&neg1, &neg2)); +} + +void test_constant_wnaf(const secp256k1_scalar *number, int w) { + secp256k1_scalar x, shift; + int wnaf[256] = {0}; + int i; + int skew; + secp256k1_scalar num = *number; + + secp256k1_scalar_set_int(&x, 0); + secp256k1_scalar_set_int(&shift, 1 << w); + /* With USE_ENDOMORPHISM on we only consider 128-bit numbers */ +#ifdef USE_ENDOMORPHISM + for (i = 0; i < 16; ++i) { + secp256k1_scalar_shr_int(&num, 8); + } +#endif + skew = secp256k1_wnaf_const(wnaf, num, w); + + for (i = WNAF_SIZE(w); i >= 0; --i) { + secp256k1_scalar t; + int v = wnaf[i]; + CHECK(v != 0); /* check nonzero */ + CHECK(v & 1); /* check parity */ + CHECK(v > -(1 << w)); /* check range above */ + CHECK(v < (1 << w)); /* check range below */ + + secp256k1_scalar_mul(&x, &x, &shift); + if (v >= 0) { + secp256k1_scalar_set_int(&t, v); + } else { + secp256k1_scalar_set_int(&t, -v); + secp256k1_scalar_negate(&t, &t); + } + secp256k1_scalar_add(&x, &x, &t); + } + /* Skew num because when encoding numbers as odd we use an offset */ + secp256k1_scalar_cadd_bit(&num, skew == 2, 1); + CHECK(secp256k1_scalar_eq(&x, &num)); +} + +void run_wnaf(void) { + int i; + secp256k1_scalar n = {{0}}; + + /* Sanity check: 1 and 2 are the smallest odd and even numbers and should + * have easier-to-diagnose failure modes */ + n.d[0] = 1; + test_constant_wnaf(&n, 4); + n.d[0] = 2; + test_constant_wnaf(&n, 4); + /* Random tests */ + for (i = 0; i < count; i++) { + random_scalar_order(&n); + test_wnaf(&n, 4+(i%10)); + test_constant_wnaf_negate(&n); + test_constant_wnaf(&n, 4 + (i % 10)); + } + secp256k1_scalar_set_int(&n, 0); + CHECK(secp256k1_scalar_cond_negate(&n, 1) == -1); + CHECK(secp256k1_scalar_is_zero(&n)); + CHECK(secp256k1_scalar_cond_negate(&n, 0) == 1); + CHECK(secp256k1_scalar_is_zero(&n)); +} + +void test_ecmult_constants(void) { + /* Test ecmult_gen() for [0..36) and [order-36..0). */ + secp256k1_scalar x; + secp256k1_gej r; + secp256k1_ge ng; + int i; + int j; + secp256k1_ge_neg(&ng, &secp256k1_ge_const_g); + for (i = 0; i < 36; i++ ) { + secp256k1_scalar_set_int(&x, i); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &r, &x); + for (j = 0; j < i; j++) { + if (j == i - 1) { + ge_equals_gej(&secp256k1_ge_const_g, &r); + } + secp256k1_gej_add_ge(&r, &r, &ng); + } + CHECK(secp256k1_gej_is_infinity(&r)); + } + for (i = 1; i <= 36; i++ ) { + secp256k1_scalar_set_int(&x, i); + secp256k1_scalar_negate(&x, &x); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &r, &x); + for (j = 0; j < i; j++) { + if (j == i - 1) { + ge_equals_gej(&ng, &r); + } + secp256k1_gej_add_ge(&r, &r, &secp256k1_ge_const_g); + } + CHECK(secp256k1_gej_is_infinity(&r)); + } +} + +void run_ecmult_constants(void) { + test_ecmult_constants(); +} + +void test_ecmult_gen_blind(void) { + /* Test ecmult_gen() blinding and confirm that the blinding changes, the affine points match, and the z's don't match. */ + secp256k1_scalar key; + secp256k1_scalar b; + unsigned char seed32[32]; + secp256k1_gej pgej; + secp256k1_gej pgej2; + secp256k1_gej i; + secp256k1_ge pge; + random_scalar_order_test(&key); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pgej, &key); + secp256k1_rand256(seed32); + b = ctx->ecmult_gen_ctx.blind; + i = ctx->ecmult_gen_ctx.initial; + secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, seed32); + CHECK(!secp256k1_scalar_eq(&b, &ctx->ecmult_gen_ctx.blind)); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pgej2, &key); + CHECK(!gej_xyz_equals_gej(&pgej, &pgej2)); + CHECK(!gej_xyz_equals_gej(&i, &ctx->ecmult_gen_ctx.initial)); + secp256k1_ge_set_gej(&pge, &pgej); + ge_equals_gej(&pge, &pgej2); +} + +void test_ecmult_gen_blind_reset(void) { + /* Test ecmult_gen() blinding reset and confirm that the blinding is consistent. */ + secp256k1_scalar b; + secp256k1_gej initial; + secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, 0); + b = ctx->ecmult_gen_ctx.blind; + initial = ctx->ecmult_gen_ctx.initial; + secp256k1_ecmult_gen_blind(&ctx->ecmult_gen_ctx, 0); + CHECK(secp256k1_scalar_eq(&b, &ctx->ecmult_gen_ctx.blind)); + CHECK(gej_xyz_equals_gej(&initial, &ctx->ecmult_gen_ctx.initial)); +} + +void run_ecmult_gen_blind(void) { + int i; + test_ecmult_gen_blind_reset(); + for (i = 0; i < 10; i++) { + test_ecmult_gen_blind(); + } +} + +#ifdef USE_ENDOMORPHISM +/***** ENDOMORPHISH TESTS *****/ +void test_scalar_split(void) { + secp256k1_scalar full; + secp256k1_scalar s1, slam; + const unsigned char zero[32] = {0}; + unsigned char tmp[32]; + + random_scalar_order_test(&full); + secp256k1_scalar_split_lambda(&s1, &slam, &full); + + /* check that both are <= 128 bits in size */ + if (secp256k1_scalar_is_high(&s1)) { + secp256k1_scalar_negate(&s1, &s1); + } + if (secp256k1_scalar_is_high(&slam)) { + secp256k1_scalar_negate(&slam, &slam); + } + + secp256k1_scalar_get_b32(tmp, &s1); + CHECK(memcmp(zero, tmp, 16) == 0); + secp256k1_scalar_get_b32(tmp, &slam); + CHECK(memcmp(zero, tmp, 16) == 0); +} + +void run_endomorphism_tests(void) { + test_scalar_split(); +} +#endif + +void ec_pubkey_parse_pointtest(const unsigned char *input, int xvalid, int yvalid) { + unsigned char pubkeyc[65]; + secp256k1_pubkey pubkey; + secp256k1_ge ge; + size_t pubkeyclen; + int32_t ecount; + ecount = 0; + secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); + for (pubkeyclen = 3; pubkeyclen <= 65; pubkeyclen++) { + /* Smaller sizes are tested exhaustively elsewhere. */ + int32_t i; + memcpy(&pubkeyc[1], input, 64); + VG_UNDEF(&pubkeyc[pubkeyclen], 65 - pubkeyclen); + for (i = 0; i < 256; i++) { + /* Try all type bytes. */ + int xpass; + int ypass; + int ysign; + pubkeyc[0] = i; + /* What sign does this point have? */ + ysign = (input[63] & 1) + 2; + /* For the current type (i) do we expect parsing to work? Handled all of compressed/uncompressed/hybrid. */ + xpass = xvalid && (pubkeyclen == 33) && ((i & 254) == 2); + /* Do we expect a parse and re-serialize as uncompressed to give a matching y? */ + ypass = xvalid && yvalid && ((i & 4) == ((pubkeyclen == 65) << 2)) && + ((i == 4) || ((i & 251) == ysign)) && ((pubkeyclen == 33) || (pubkeyclen == 65)); + if (xpass || ypass) { + /* These cases must parse. */ + unsigned char pubkeyo[65]; + size_t outl; + memset(&pubkey, 0, sizeof(pubkey)); + VG_UNDEF(&pubkey, sizeof(pubkey)); + ecount = 0; + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, pubkeyclen) == 1); + VG_CHECK(&pubkey, sizeof(pubkey)); + outl = 65; + VG_UNDEF(pubkeyo, 65); + CHECK(secp256k1_ec_pubkey_serialize(ctx, pubkeyo, &outl, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + VG_CHECK(pubkeyo, outl); + CHECK(outl == 33); + CHECK(memcmp(&pubkeyo[1], &pubkeyc[1], 32) == 0); + CHECK((pubkeyclen != 33) || (pubkeyo[0] == pubkeyc[0])); + if (ypass) { + /* This test isn't always done because we decode with alternative signs, so the y won't match. */ + CHECK(pubkeyo[0] == ysign); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 1); + memset(&pubkey, 0, sizeof(pubkey)); + VG_UNDEF(&pubkey, sizeof(pubkey)); + secp256k1_pubkey_save(&pubkey, &ge); + VG_CHECK(&pubkey, sizeof(pubkey)); + outl = 65; + VG_UNDEF(pubkeyo, 65); + CHECK(secp256k1_ec_pubkey_serialize(ctx, pubkeyo, &outl, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 1); + VG_CHECK(pubkeyo, outl); + CHECK(outl == 65); + CHECK(pubkeyo[0] == 4); + CHECK(memcmp(&pubkeyo[1], input, 64) == 0); + } + CHECK(ecount == 0); + } else { + /* These cases must fail to parse. */ + memset(&pubkey, 0xfe, sizeof(pubkey)); + ecount = 0; + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, pubkeyclen) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 0); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(ecount == 1); + } + } + } + secp256k1_context_set_illegal_callback(ctx, NULL, NULL); +} + +void run_ec_pubkey_parse_test(void) { +#define SECP256K1_EC_PARSE_TEST_NVALID (12) + const unsigned char valid[SECP256K1_EC_PARSE_TEST_NVALID][64] = { + { + /* Point with leading and trailing zeros in x and y serialization. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x52, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x64, 0xef, 0xa1, 0x7b, 0x77, 0x61, 0xe1, 0xe4, 0x27, 0x06, 0x98, 0x9f, 0xb4, 0x83, + 0xb8, 0xd2, 0xd4, 0x9b, 0xf7, 0x8f, 0xae, 0x98, 0x03, 0xf0, 0x99, 0xb8, 0x34, 0xed, 0xeb, 0x00 + }, + { + /* Point with x equal to a 3rd root of unity.*/ + 0x7a, 0xe9, 0x6a, 0x2b, 0x65, 0x7c, 0x07, 0x10, 0x6e, 0x64, 0x47, 0x9e, 0xac, 0x34, 0x34, 0xe9, + 0x9c, 0xf0, 0x49, 0x75, 0x12, 0xf5, 0x89, 0x95, 0xc1, 0x39, 0x6c, 0x28, 0x71, 0x95, 0x01, 0xee, + 0x42, 0x18, 0xf2, 0x0a, 0xe6, 0xc6, 0x46, 0xb3, 0x63, 0xdb, 0x68, 0x60, 0x58, 0x22, 0xfb, 0x14, + 0x26, 0x4c, 0xa8, 0xd2, 0x58, 0x7f, 0xdd, 0x6f, 0xbc, 0x75, 0x0d, 0x58, 0x7e, 0x76, 0xa7, 0xee, + }, + { + /* Point with largest x. (1/2) */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2c, + 0x0e, 0x99, 0x4b, 0x14, 0xea, 0x72, 0xf8, 0xc3, 0xeb, 0x95, 0xc7, 0x1e, 0xf6, 0x92, 0x57, 0x5e, + 0x77, 0x50, 0x58, 0x33, 0x2d, 0x7e, 0x52, 0xd0, 0x99, 0x5c, 0xf8, 0x03, 0x88, 0x71, 0xb6, 0x7d, + }, + { + /* Point with largest x. (2/2) */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2c, + 0xf1, 0x66, 0xb4, 0xeb, 0x15, 0x8d, 0x07, 0x3c, 0x14, 0x6a, 0x38, 0xe1, 0x09, 0x6d, 0xa8, 0xa1, + 0x88, 0xaf, 0xa7, 0xcc, 0xd2, 0x81, 0xad, 0x2f, 0x66, 0xa3, 0x07, 0xfb, 0x77, 0x8e, 0x45, 0xb2, + }, + { + /* Point with smallest x. (1/2) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x42, 0x18, 0xf2, 0x0a, 0xe6, 0xc6, 0x46, 0xb3, 0x63, 0xdb, 0x68, 0x60, 0x58, 0x22, 0xfb, 0x14, + 0x26, 0x4c, 0xa8, 0xd2, 0x58, 0x7f, 0xdd, 0x6f, 0xbc, 0x75, 0x0d, 0x58, 0x7e, 0x76, 0xa7, 0xee, + }, + { + /* Point with smallest x. (2/2) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xbd, 0xe7, 0x0d, 0xf5, 0x19, 0x39, 0xb9, 0x4c, 0x9c, 0x24, 0x97, 0x9f, 0xa7, 0xdd, 0x04, 0xeb, + 0xd9, 0xb3, 0x57, 0x2d, 0xa7, 0x80, 0x22, 0x90, 0x43, 0x8a, 0xf2, 0xa6, 0x81, 0x89, 0x54, 0x41, + }, + { + /* Point with largest y. (1/3) */ + 0x1f, 0xe1, 0xe5, 0xef, 0x3f, 0xce, 0xb5, 0xc1, 0x35, 0xab, 0x77, 0x41, 0x33, 0x3c, 0xe5, 0xa6, + 0xe8, 0x0d, 0x68, 0x16, 0x76, 0x53, 0xf6, 0xb2, 0xb2, 0x4b, 0xcb, 0xcf, 0xaa, 0xaf, 0xf5, 0x07, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2e, + }, + { + /* Point with largest y. (2/3) */ + 0xcb, 0xb0, 0xde, 0xab, 0x12, 0x57, 0x54, 0xf1, 0xfd, 0xb2, 0x03, 0x8b, 0x04, 0x34, 0xed, 0x9c, + 0xb3, 0xfb, 0x53, 0xab, 0x73, 0x53, 0x91, 0x12, 0x99, 0x94, 0xa5, 0x35, 0xd9, 0x25, 0xf6, 0x73, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2e, + }, + { + /* Point with largest y. (3/3) */ + 0x14, 0x6d, 0x3b, 0x65, 0xad, 0xd9, 0xf5, 0x4c, 0xcc, 0xa2, 0x85, 0x33, 0xc8, 0x8e, 0x2c, 0xbc, + 0x63, 0xf7, 0x44, 0x3e, 0x16, 0x58, 0x78, 0x3a, 0xb4, 0x1f, 0x8e, 0xf9, 0x7c, 0x2a, 0x10, 0xb5, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2e, + }, + { + /* Point with smallest y. (1/3) */ + 0x1f, 0xe1, 0xe5, 0xef, 0x3f, 0xce, 0xb5, 0xc1, 0x35, 0xab, 0x77, 0x41, 0x33, 0x3c, 0xe5, 0xa6, + 0xe8, 0x0d, 0x68, 0x16, 0x76, 0x53, 0xf6, 0xb2, 0xb2, 0x4b, 0xcb, 0xcf, 0xaa, 0xaf, 0xf5, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, + { + /* Point with smallest y. (2/3) */ + 0xcb, 0xb0, 0xde, 0xab, 0x12, 0x57, 0x54, 0xf1, 0xfd, 0xb2, 0x03, 0x8b, 0x04, 0x34, 0xed, 0x9c, + 0xb3, 0xfb, 0x53, 0xab, 0x73, 0x53, 0x91, 0x12, 0x99, 0x94, 0xa5, 0x35, 0xd9, 0x25, 0xf6, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, + { + /* Point with smallest y. (3/3) */ + 0x14, 0x6d, 0x3b, 0x65, 0xad, 0xd9, 0xf5, 0x4c, 0xcc, 0xa2, 0x85, 0x33, 0xc8, 0x8e, 0x2c, 0xbc, + 0x63, 0xf7, 0x44, 0x3e, 0x16, 0x58, 0x78, 0x3a, 0xb4, 0x1f, 0x8e, 0xf9, 0x7c, 0x2a, 0x10, 0xb5, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + } + }; +#define SECP256K1_EC_PARSE_TEST_NXVALID (4) + const unsigned char onlyxvalid[SECP256K1_EC_PARSE_TEST_NXVALID][64] = { + { + /* Valid if y overflow ignored (y = 1 mod p). (1/3) */ + 0x1f, 0xe1, 0xe5, 0xef, 0x3f, 0xce, 0xb5, 0xc1, 0x35, 0xab, 0x77, 0x41, 0x33, 0x3c, 0xe5, 0xa6, + 0xe8, 0x0d, 0x68, 0x16, 0x76, 0x53, 0xf6, 0xb2, 0xb2, 0x4b, 0xcb, 0xcf, 0xaa, 0xaf, 0xf5, 0x07, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x30, + }, + { + /* Valid if y overflow ignored (y = 1 mod p). (2/3) */ + 0xcb, 0xb0, 0xde, 0xab, 0x12, 0x57, 0x54, 0xf1, 0xfd, 0xb2, 0x03, 0x8b, 0x04, 0x34, 0xed, 0x9c, + 0xb3, 0xfb, 0x53, 0xab, 0x73, 0x53, 0x91, 0x12, 0x99, 0x94, 0xa5, 0x35, 0xd9, 0x25, 0xf6, 0x73, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x30, + }, + { + /* Valid if y overflow ignored (y = 1 mod p). (3/3)*/ + 0x14, 0x6d, 0x3b, 0x65, 0xad, 0xd9, 0xf5, 0x4c, 0xcc, 0xa2, 0x85, 0x33, 0xc8, 0x8e, 0x2c, 0xbc, + 0x63, 0xf7, 0x44, 0x3e, 0x16, 0x58, 0x78, 0x3a, 0xb4, 0x1f, 0x8e, 0xf9, 0x7c, 0x2a, 0x10, 0xb5, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x30, + }, + { + /* x on curve, y is from y^2 = x^3 + 8. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 + } + }; +#define SECP256K1_EC_PARSE_TEST_NINVALID (7) + const unsigned char invalid[SECP256K1_EC_PARSE_TEST_NINVALID][64] = { + { + /* x is third root of -8, y is -1 * (x^3+7); also on the curve for y^2 = x^3 + 9. */ + 0x0a, 0x2d, 0x2b, 0xa9, 0x35, 0x07, 0xf1, 0xdf, 0x23, 0x37, 0x70, 0xc2, 0xa7, 0x97, 0x96, 0x2c, + 0xc6, 0x1f, 0x6d, 0x15, 0xda, 0x14, 0xec, 0xd4, 0x7d, 0x8d, 0x27, 0xae, 0x1c, 0xd5, 0xf8, 0x53, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, + { + /* Valid if x overflow ignored (x = 1 mod p). */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x30, + 0x42, 0x18, 0xf2, 0x0a, 0xe6, 0xc6, 0x46, 0xb3, 0x63, 0xdb, 0x68, 0x60, 0x58, 0x22, 0xfb, 0x14, + 0x26, 0x4c, 0xa8, 0xd2, 0x58, 0x7f, 0xdd, 0x6f, 0xbc, 0x75, 0x0d, 0x58, 0x7e, 0x76, 0xa7, 0xee, + }, + { + /* Valid if x overflow ignored (x = 1 mod p). */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x30, + 0xbd, 0xe7, 0x0d, 0xf5, 0x19, 0x39, 0xb9, 0x4c, 0x9c, 0x24, 0x97, 0x9f, 0xa7, 0xdd, 0x04, 0xeb, + 0xd9, 0xb3, 0x57, 0x2d, 0xa7, 0x80, 0x22, 0x90, 0x43, 0x8a, 0xf2, 0xa6, 0x81, 0x89, 0x54, 0x41, + }, + { + /* x is -1, y is the result of the sqrt ladder; also on the curve for y^2 = x^3 - 5. */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2e, + 0xf4, 0x84, 0x14, 0x5c, 0xb0, 0x14, 0x9b, 0x82, 0x5d, 0xff, 0x41, 0x2f, 0xa0, 0x52, 0xa8, 0x3f, + 0xcb, 0x72, 0xdb, 0x61, 0xd5, 0x6f, 0x37, 0x70, 0xce, 0x06, 0x6b, 0x73, 0x49, 0xa2, 0xaa, 0x28, + }, + { + /* x is -1, y is the result of the sqrt ladder; also on the curve for y^2 = x^3 - 5. */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2e, + 0x0b, 0x7b, 0xeb, 0xa3, 0x4f, 0xeb, 0x64, 0x7d, 0xa2, 0x00, 0xbe, 0xd0, 0x5f, 0xad, 0x57, 0xc0, + 0x34, 0x8d, 0x24, 0x9e, 0x2a, 0x90, 0xc8, 0x8f, 0x31, 0xf9, 0x94, 0x8b, 0xb6, 0x5d, 0x52, 0x07, + }, + { + /* x is zero, y is the result of the sqrt ladder; also on the curve for y^2 = x^3 - 7. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8f, 0x53, 0x7e, 0xef, 0xdf, 0xc1, 0x60, 0x6a, 0x07, 0x27, 0xcd, 0x69, 0xb4, 0xa7, 0x33, 0x3d, + 0x38, 0xed, 0x44, 0xe3, 0x93, 0x2a, 0x71, 0x79, 0xee, 0xcb, 0x4b, 0x6f, 0xba, 0x93, 0x60, 0xdc, + }, + { + /* x is zero, y is the result of the sqrt ladder; also on the curve for y^2 = x^3 - 7. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x70, 0xac, 0x81, 0x10, 0x20, 0x3e, 0x9f, 0x95, 0xf8, 0xd8, 0x32, 0x96, 0x4b, 0x58, 0xcc, 0xc2, + 0xc7, 0x12, 0xbb, 0x1c, 0x6c, 0xd5, 0x8e, 0x86, 0x11, 0x34, 0xb4, 0x8f, 0x45, 0x6c, 0x9b, 0x53 + } + }; + const unsigned char pubkeyc[66] = { + /* Serialization of G. */ + 0x04, 0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC, 0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, + 0x07, 0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9, 0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, + 0x98, 0x48, 0x3A, 0xDA, 0x77, 0x26, 0xA3, 0xC4, 0x65, 0x5D, 0xA4, 0xFB, 0xFC, 0x0E, 0x11, 0x08, + 0xA8, 0xFD, 0x17, 0xB4, 0x48, 0xA6, 0x85, 0x54, 0x19, 0x9C, 0x47, 0xD0, 0x8F, 0xFB, 0x10, 0xD4, + 0xB8, 0x00 + }; + unsigned char sout[65]; + unsigned char shortkey[2]; + secp256k1_ge ge; + secp256k1_pubkey pubkey; + size_t len; + int32_t i; + int32_t ecount; + int32_t ecount2; + ecount = 0; + /* Nothing should be reading this far into pubkeyc. */ + VG_UNDEF(&pubkeyc[65], 1); + secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); + /* Zero length claimed, fail, zeroize, no illegal arg error. */ + memset(&pubkey, 0xfe, sizeof(pubkey)); + ecount = 0; + VG_UNDEF(shortkey, 2); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, shortkey, 0) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 0); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(ecount == 1); + /* Length one claimed, fail, zeroize, no illegal arg error. */ + for (i = 0; i < 256 ; i++) { + memset(&pubkey, 0xfe, sizeof(pubkey)); + ecount = 0; + shortkey[0] = i; + VG_UNDEF(&shortkey[1], 1); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, shortkey, 1) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 0); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(ecount == 1); + } + /* Length two claimed, fail, zeroize, no illegal arg error. */ + for (i = 0; i < 65536 ; i++) { + memset(&pubkey, 0xfe, sizeof(pubkey)); + ecount = 0; + shortkey[0] = i & 255; + shortkey[1] = i >> 8; + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, shortkey, 2) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 0); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(ecount == 1); + } + memset(&pubkey, 0xfe, sizeof(pubkey)); + ecount = 0; + VG_UNDEF(&pubkey, sizeof(pubkey)); + /* 33 bytes claimed on otherwise valid input starting with 0x04, fail, zeroize output, no illegal arg error. */ + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, 33) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 0); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(ecount == 1); + /* NULL pubkey, illegal arg error. Pubkey isn't rewritten before this step, since it's NULL into the parser. */ + CHECK(secp256k1_ec_pubkey_parse(ctx, NULL, pubkeyc, 65) == 0); + CHECK(ecount == 2); + /* NULL input string. Illegal arg and zeroize output. */ + memset(&pubkey, 0xfe, sizeof(pubkey)); + ecount = 0; + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, NULL, 65) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 1); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(ecount == 2); + /* 64 bytes claimed on input starting with 0x04, fail, zeroize output, no illegal arg error. */ + memset(&pubkey, 0xfe, sizeof(pubkey)); + ecount = 0; + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, 64) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 0); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(ecount == 1); + /* 66 bytes claimed, fail, zeroize output, no illegal arg error. */ + memset(&pubkey, 0xfe, sizeof(pubkey)); + ecount = 0; + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, 66) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 0); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 0); + CHECK(ecount == 1); + /* Valid parse. */ + memset(&pubkey, 0, sizeof(pubkey)); + ecount = 0; + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, 65) == 1); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(ecount == 0); + VG_UNDEF(&ge, sizeof(ge)); + CHECK(secp256k1_pubkey_load(ctx, &ge, &pubkey) == 1); + VG_CHECK(&ge.x, sizeof(ge.x)); + VG_CHECK(&ge.y, sizeof(ge.y)); + VG_CHECK(&ge.infinity, sizeof(ge.infinity)); + ge_equals_ge(&secp256k1_ge_const_g, &ge); + CHECK(ecount == 0); + /* secp256k1_ec_pubkey_serialize illegal args. */ + ecount = 0; + len = 65; + CHECK(secp256k1_ec_pubkey_serialize(ctx, NULL, &len, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 0); + CHECK(ecount == 1); + CHECK(len == 0); + CHECK(secp256k1_ec_pubkey_serialize(ctx, sout, NULL, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 0); + CHECK(ecount == 2); + len = 65; + VG_UNDEF(sout, 65); + CHECK(secp256k1_ec_pubkey_serialize(ctx, sout, &len, NULL, SECP256K1_EC_UNCOMPRESSED) == 0); + VG_CHECK(sout, 65); + CHECK(ecount == 3); + CHECK(len == 0); + len = 65; + CHECK(secp256k1_ec_pubkey_serialize(ctx, sout, &len, &pubkey, ~0) == 0); + CHECK(ecount == 4); + CHECK(len == 0); + len = 65; + VG_UNDEF(sout, 65); + CHECK(secp256k1_ec_pubkey_serialize(ctx, sout, &len, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 1); + VG_CHECK(sout, 65); + CHECK(ecount == 4); + CHECK(len == 65); + /* Multiple illegal args. Should still set arg error only once. */ + ecount = 0; + ecount2 = 11; + CHECK(secp256k1_ec_pubkey_parse(ctx, NULL, NULL, 65) == 0); + CHECK(ecount == 1); + /* Does the illegal arg callback actually change the behavior? */ + secp256k1_context_set_illegal_callback(ctx, uncounting_illegal_callback_fn, &ecount2); + CHECK(secp256k1_ec_pubkey_parse(ctx, NULL, NULL, 65) == 0); + CHECK(ecount == 1); + CHECK(ecount2 == 10); + secp256k1_context_set_illegal_callback(ctx, NULL, NULL); + /* Try a bunch of prefabbed points with all possible encodings. */ + for (i = 0; i < SECP256K1_EC_PARSE_TEST_NVALID; i++) { + ec_pubkey_parse_pointtest(valid[i], 1, 1); + } + for (i = 0; i < SECP256K1_EC_PARSE_TEST_NXVALID; i++) { + ec_pubkey_parse_pointtest(onlyxvalid[i], 1, 0); + } + for (i = 0; i < SECP256K1_EC_PARSE_TEST_NINVALID; i++) { + ec_pubkey_parse_pointtest(invalid[i], 0, 0); + } +} + +void run_eckey_edge_case_test(void) { + const unsigned char orderc[32] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41 + }; + const unsigned char zeros[sizeof(secp256k1_pubkey)] = {0x00}; + unsigned char ctmp[33]; + unsigned char ctmp2[33]; + secp256k1_pubkey pubkey; + secp256k1_pubkey pubkey2; + secp256k1_pubkey pubkey_one; + secp256k1_pubkey pubkey_negone; + const secp256k1_pubkey *pubkeys[3]; + size_t len; + int32_t ecount; + /* Group order is too large, reject. */ + CHECK(secp256k1_ec_seckey_verify(ctx, orderc) == 0); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, orderc) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + /* Maximum value is too large, reject. */ + memset(ctmp, 255, 32); + CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 0); + memset(&pubkey, 1, sizeof(pubkey)); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + /* Zero is too small, reject. */ + memset(ctmp, 0, 32); + CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 0); + memset(&pubkey, 1, sizeof(pubkey)); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + /* One must be accepted. */ + ctmp[31] = 0x01; + CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 1); + memset(&pubkey, 0, sizeof(pubkey)); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 1); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); + pubkey_one = pubkey; + /* Group order + 1 is too large, reject. */ + memcpy(ctmp, orderc, 32); + ctmp[31] = 0x42; + CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 0); + memset(&pubkey, 1, sizeof(pubkey)); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 0); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + /* -1 must be accepted. */ + ctmp[31] = 0x40; + CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 1); + memset(&pubkey, 0, sizeof(pubkey)); + VG_UNDEF(&pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, ctmp) == 1); + VG_CHECK(&pubkey, sizeof(pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); + pubkey_negone = pubkey; + /* Tweak of zero leaves the value changed. */ + memset(ctmp2, 0, 32); + CHECK(secp256k1_ec_privkey_tweak_add(ctx, ctmp, ctmp2) == 1); + CHECK(memcmp(orderc, ctmp, 31) == 0 && ctmp[31] == 0x40); + memcpy(&pubkey2, &pubkey, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 1); + CHECK(memcmp(&pubkey, &pubkey2, sizeof(pubkey)) == 0); + /* Multiply tweak of zero zeroizes the output. */ + CHECK(secp256k1_ec_privkey_tweak_mul(ctx, ctmp, ctmp2) == 0); + CHECK(memcmp(zeros, ctmp, 32) == 0); + CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, ctmp2) == 0); + CHECK(memcmp(&pubkey, zeros, sizeof(pubkey)) == 0); + memcpy(&pubkey, &pubkey2, sizeof(pubkey)); + /* Overflowing key tweak zeroizes. */ + memcpy(ctmp, orderc, 32); + ctmp[31] = 0x40; + CHECK(secp256k1_ec_privkey_tweak_add(ctx, ctmp, orderc) == 0); + CHECK(memcmp(zeros, ctmp, 32) == 0); + memcpy(ctmp, orderc, 32); + ctmp[31] = 0x40; + CHECK(secp256k1_ec_privkey_tweak_mul(ctx, ctmp, orderc) == 0); + CHECK(memcmp(zeros, ctmp, 32) == 0); + memcpy(ctmp, orderc, 32); + ctmp[31] = 0x40; + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, orderc) == 0); + CHECK(memcmp(&pubkey, zeros, sizeof(pubkey)) == 0); + memcpy(&pubkey, &pubkey2, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, orderc) == 0); + CHECK(memcmp(&pubkey, zeros, sizeof(pubkey)) == 0); + memcpy(&pubkey, &pubkey2, sizeof(pubkey)); + /* Private key tweaks results in a key of zero. */ + ctmp2[31] = 1; + CHECK(secp256k1_ec_privkey_tweak_add(ctx, ctmp2, ctmp) == 0); + CHECK(memcmp(zeros, ctmp2, 32) == 0); + ctmp2[31] = 1; + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 0); + CHECK(memcmp(&pubkey, zeros, sizeof(pubkey)) == 0); + memcpy(&pubkey, &pubkey2, sizeof(pubkey)); + /* Tweak computation wraps and results in a key of 1. */ + ctmp2[31] = 2; + CHECK(secp256k1_ec_privkey_tweak_add(ctx, ctmp2, ctmp) == 1); + CHECK(memcmp(ctmp2, zeros, 31) == 0 && ctmp2[31] == 1); + ctmp2[31] = 2; + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 1); + ctmp2[31] = 1; + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey2, ctmp2) == 1); + CHECK(memcmp(&pubkey, &pubkey2, sizeof(pubkey)) == 0); + /* Tweak mul * 2 = 1+1. */ + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 1); + ctmp2[31] = 2; + CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey2, ctmp2) == 1); + CHECK(memcmp(&pubkey, &pubkey2, sizeof(pubkey)) == 0); + /* Test argument errors. */ + ecount = 0; + secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); + CHECK(ecount == 0); + /* Zeroize pubkey on parse error. */ + memset(&pubkey, 0, 32); + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, ctmp2) == 0); + CHECK(ecount == 1); + CHECK(memcmp(&pubkey, zeros, sizeof(pubkey)) == 0); + memcpy(&pubkey, &pubkey2, sizeof(pubkey)); + memset(&pubkey2, 0, 32); + CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey2, ctmp2) == 0); + CHECK(ecount == 2); + CHECK(memcmp(&pubkey2, zeros, sizeof(pubkey2)) == 0); + /* Plain argument errors. */ + ecount = 0; + CHECK(secp256k1_ec_seckey_verify(ctx, ctmp) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_ec_seckey_verify(ctx, NULL) == 0); + CHECK(ecount == 1); + ecount = 0; + memset(ctmp2, 0, 32); + ctmp2[31] = 4; + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, NULL, ctmp2) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, NULL) == 0); + CHECK(ecount == 2); + ecount = 0; + memset(ctmp2, 0, 32); + ctmp2[31] = 4; + CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, NULL, ctmp2) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, NULL) == 0); + CHECK(ecount == 2); + ecount = 0; + memset(ctmp2, 0, 32); + CHECK(secp256k1_ec_privkey_tweak_add(ctx, NULL, ctmp2) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ec_privkey_tweak_add(ctx, ctmp, NULL) == 0); + CHECK(ecount == 2); + ecount = 0; + memset(ctmp2, 0, 32); + ctmp2[31] = 1; + CHECK(secp256k1_ec_privkey_tweak_mul(ctx, NULL, ctmp2) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ec_privkey_tweak_mul(ctx, ctmp, NULL) == 0); + CHECK(ecount == 2); + ecount = 0; + CHECK(secp256k1_ec_pubkey_create(ctx, NULL, ctmp) == 0); + CHECK(ecount == 1); + memset(&pubkey, 1, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, NULL) == 0); + CHECK(ecount == 2); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + /* secp256k1_ec_pubkey_combine tests. */ + ecount = 0; + pubkeys[0] = &pubkey_one; + VG_UNDEF(&pubkeys[0], sizeof(secp256k1_pubkey *)); + VG_UNDEF(&pubkeys[1], sizeof(secp256k1_pubkey *)); + VG_UNDEF(&pubkeys[2], sizeof(secp256k1_pubkey *)); + memset(&pubkey, 255, sizeof(secp256k1_pubkey)); + VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 0) == 0); + VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ec_pubkey_combine(ctx, NULL, pubkeys, 1) == 0); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + CHECK(ecount == 2); + memset(&pubkey, 255, sizeof(secp256k1_pubkey)); + VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, NULL, 1) == 0); + VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + CHECK(ecount == 3); + pubkeys[0] = &pubkey_negone; + memset(&pubkey, 255, sizeof(secp256k1_pubkey)); + VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 1) == 1); + VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); + CHECK(ecount == 3); + len = 33; + CHECK(secp256k1_ec_pubkey_serialize(ctx, ctmp, &len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + CHECK(secp256k1_ec_pubkey_serialize(ctx, ctmp2, &len, &pubkey_negone, SECP256K1_EC_COMPRESSED) == 1); + CHECK(memcmp(ctmp, ctmp2, 33) == 0); + /* Result is infinity. */ + pubkeys[0] = &pubkey_one; + pubkeys[1] = &pubkey_negone; + memset(&pubkey, 255, sizeof(secp256k1_pubkey)); + VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 2) == 0); + VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) == 0); + CHECK(ecount == 3); + /* Passes through infinity but comes out one. */ + pubkeys[2] = &pubkey_one; + memset(&pubkey, 255, sizeof(secp256k1_pubkey)); + VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 3) == 1); + VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); + CHECK(ecount == 3); + len = 33; + CHECK(secp256k1_ec_pubkey_serialize(ctx, ctmp, &len, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + CHECK(secp256k1_ec_pubkey_serialize(ctx, ctmp2, &len, &pubkey_one, SECP256K1_EC_COMPRESSED) == 1); + CHECK(memcmp(ctmp, ctmp2, 33) == 0); + /* Adds to two. */ + pubkeys[1] = &pubkey_one; + memset(&pubkey, 255, sizeof(secp256k1_pubkey)); + VG_UNDEF(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(secp256k1_ec_pubkey_combine(ctx, &pubkey, pubkeys, 2) == 1); + VG_CHECK(&pubkey, sizeof(secp256k1_pubkey)); + CHECK(memcmp(&pubkey, zeros, sizeof(secp256k1_pubkey)) > 0); + CHECK(ecount == 3); + secp256k1_context_set_illegal_callback(ctx, NULL, NULL); +} + +void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) { + secp256k1_scalar nonce; + do { + random_scalar_order_test(&nonce); + } while(!secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, sigr, sigs, key, msg, &nonce, recid)); +} + +void test_ecdsa_sign_verify(void) { + secp256k1_gej pubj; + secp256k1_ge pub; + secp256k1_scalar one; + secp256k1_scalar msg, key; + secp256k1_scalar sigr, sigs; + int recid; + int getrec; + random_scalar_order_test(&msg); + random_scalar_order_test(&key); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pubj, &key); + secp256k1_ge_set_gej(&pub, &pubj); + getrec = secp256k1_rand_bits(1); + random_sign(&sigr, &sigs, &key, &msg, getrec?&recid:NULL); + if (getrec) { + CHECK(recid >= 0 && recid < 4); + } + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sigr, &sigs, &pub, &msg)); + secp256k1_scalar_set_int(&one, 1); + secp256k1_scalar_add(&msg, &msg, &one); + CHECK(!secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sigr, &sigs, &pub, &msg)); +} + +void run_ecdsa_sign_verify(void) { + int i; + for (i = 0; i < 10*count; i++) { + test_ecdsa_sign_verify(); + } +} + +/** Dummy nonce generation function that just uses a precomputed nonce, and fails if it is not accepted. Use only for testing. */ +static int precomputed_nonce_function(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) { + (void)msg32; + (void)key32; + (void)algo16; + memcpy(nonce32, data, 32); + return (counter == 0); +} + +static int nonce_function_test_fail(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) { + /* Dummy nonce generator that has a fatal error on the first counter value. */ + if (counter == 0) { + return 0; + } + return nonce_function_rfc6979(nonce32, msg32, key32, algo16, data, counter - 1); +} + +static int nonce_function_test_retry(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) { + /* Dummy nonce generator that produces unacceptable nonces for the first several counter values. */ + if (counter < 3) { + memset(nonce32, counter==0 ? 0 : 255, 32); + if (counter == 2) { + nonce32[31]--; + } + return 1; + } + if (counter < 5) { + static const unsigned char order[] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE, + 0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,0x3B, + 0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41 + }; + memcpy(nonce32, order, 32); + if (counter == 4) { + nonce32[31]++; + } + return 1; + } + /* Retry rate of 6979 is negligible esp. as we only call this in deterministic tests. */ + /* If someone does fine a case where it retries for secp256k1, we'd like to know. */ + if (counter > 5) { + return 0; + } + return nonce_function_rfc6979(nonce32, msg32, key32, algo16, data, counter - 5); +} + +int is_empty_signature(const secp256k1_ecdsa_signature *sig) { + static const unsigned char res[sizeof(secp256k1_ecdsa_signature)] = {0}; + return memcmp(sig, res, sizeof(secp256k1_ecdsa_signature)) == 0; +} + +void test_ecdsa_end_to_end(void) { + unsigned char extra[32] = {0x00}; + unsigned char privkey[32]; + unsigned char message[32]; + unsigned char privkey2[32]; + secp256k1_ecdsa_signature signature[6]; + secp256k1_scalar r, s; + unsigned char sig[74]; + size_t siglen = 74; + unsigned char pubkeyc[65]; + size_t pubkeyclen = 65; + secp256k1_pubkey pubkey; + unsigned char seckey[300]; + size_t seckeylen = 300; + + /* Generate a random key and message. */ + { + secp256k1_scalar msg, key; + random_scalar_order_test(&msg); + random_scalar_order_test(&key); + secp256k1_scalar_get_b32(privkey, &key); + secp256k1_scalar_get_b32(message, &msg); + } + + /* Construct and verify corresponding public key. */ + CHECK(secp256k1_ec_seckey_verify(ctx, privkey) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1); + + /* Verify exporting and importing public key. */ + CHECK(secp256k1_ec_pubkey_serialize(ctx, pubkeyc, &pubkeyclen, &pubkey, secp256k1_rand_bits(1) == 1 ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED)); + memset(&pubkey, 0, sizeof(pubkey)); + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeyc, pubkeyclen) == 1); + + /* Verify private key import and export. */ + CHECK(ec_privkey_export_der(ctx, seckey, &seckeylen, privkey, secp256k1_rand_bits(1) == 1)); + CHECK(ec_privkey_import_der(ctx, privkey2, seckey, seckeylen) == 1); + CHECK(memcmp(privkey, privkey2, 32) == 0); + + /* Optionally tweak the keys using addition. */ + if (secp256k1_rand_int(3) == 0) { + int ret1; + int ret2; + unsigned char rnd[32]; + secp256k1_pubkey pubkey2; + secp256k1_rand256_test(rnd); + ret1 = secp256k1_ec_privkey_tweak_add(ctx, privkey, rnd); + ret2 = secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, rnd); + CHECK(ret1 == ret2); + if (ret1 == 0) { + return; + } + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey2, privkey) == 1); + CHECK(memcmp(&pubkey, &pubkey2, sizeof(pubkey)) == 0); + } + + /* Optionally tweak the keys using multiplication. */ + if (secp256k1_rand_int(3) == 0) { + int ret1; + int ret2; + unsigned char rnd[32]; + secp256k1_pubkey pubkey2; + secp256k1_rand256_test(rnd); + ret1 = secp256k1_ec_privkey_tweak_mul(ctx, privkey, rnd); + ret2 = secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, rnd); + CHECK(ret1 == ret2); + if (ret1 == 0) { + return; + } + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey2, privkey) == 1); + CHECK(memcmp(&pubkey, &pubkey2, sizeof(pubkey)) == 0); + } + + /* Sign. */ + CHECK(secp256k1_ecdsa_sign(ctx, &signature[0], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign(ctx, &signature[4], message, privkey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_sign(ctx, &signature[1], message, privkey, NULL, extra) == 1); + extra[31] = 1; + CHECK(secp256k1_ecdsa_sign(ctx, &signature[2], message, privkey, NULL, extra) == 1); + extra[31] = 0; + extra[0] = 1; + CHECK(secp256k1_ecdsa_sign(ctx, &signature[3], message, privkey, NULL, extra) == 1); + CHECK(memcmp(&signature[0], &signature[4], sizeof(signature[0])) == 0); + CHECK(memcmp(&signature[0], &signature[1], sizeof(signature[0])) != 0); + CHECK(memcmp(&signature[0], &signature[2], sizeof(signature[0])) != 0); + CHECK(memcmp(&signature[0], &signature[3], sizeof(signature[0])) != 0); + CHECK(memcmp(&signature[1], &signature[2], sizeof(signature[0])) != 0); + CHECK(memcmp(&signature[1], &signature[3], sizeof(signature[0])) != 0); + CHECK(memcmp(&signature[2], &signature[3], sizeof(signature[0])) != 0); + /* Verify. */ + CHECK(secp256k1_ecdsa_verify(ctx, &signature[0], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[1], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[2], message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[3], message, &pubkey) == 1); + /* Test lower-S form, malleate, verify and fail, test again, malleate again */ + CHECK(!secp256k1_ecdsa_signature_normalize(ctx, NULL, &signature[0])); + secp256k1_ecdsa_signature_load(ctx, &r, &s, &signature[0]); + secp256k1_scalar_negate(&s, &s); + secp256k1_ecdsa_signature_save(&signature[5], &r, &s); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[5], message, &pubkey) == 0); + CHECK(secp256k1_ecdsa_signature_normalize(ctx, NULL, &signature[5])); + CHECK(secp256k1_ecdsa_signature_normalize(ctx, &signature[5], &signature[5])); + CHECK(!secp256k1_ecdsa_signature_normalize(ctx, NULL, &signature[5])); + CHECK(!secp256k1_ecdsa_signature_normalize(ctx, &signature[5], &signature[5])); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[5], message, &pubkey) == 1); + secp256k1_scalar_negate(&s, &s); + secp256k1_ecdsa_signature_save(&signature[5], &r, &s); + CHECK(!secp256k1_ecdsa_signature_normalize(ctx, NULL, &signature[5])); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[5], message, &pubkey) == 1); + CHECK(memcmp(&signature[5], &signature[0], 64) == 0); + + /* Serialize/parse DER and verify again */ + CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, sig, &siglen, &signature[0]) == 1); + memset(&signature[0], 0, sizeof(signature[0])); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &signature[0], sig, siglen) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature[0], message, &pubkey) == 1); + /* Serialize/destroy/parse DER and verify again. */ + siglen = 74; + CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, sig, &siglen, &signature[0]) == 1); + sig[secp256k1_rand_int(siglen)] += 1 + secp256k1_rand_int(255); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &signature[0], sig, siglen) == 0 || + secp256k1_ecdsa_verify(ctx, &signature[0], message, &pubkey) == 0); +} + +void test_random_pubkeys(void) { + secp256k1_ge elem; + secp256k1_ge elem2; + unsigned char in[65]; + /* Generate some randomly sized pubkeys. */ + size_t len = secp256k1_rand_bits(2) == 0 ? 65 : 33; + if (secp256k1_rand_bits(2) == 0) { + len = secp256k1_rand_bits(6); + } + if (len == 65) { + in[0] = secp256k1_rand_bits(1) ? 4 : (secp256k1_rand_bits(1) ? 6 : 7); + } else { + in[0] = secp256k1_rand_bits(1) ? 2 : 3; + } + if (secp256k1_rand_bits(3) == 0) { + in[0] = secp256k1_rand_bits(8); + } + if (len > 1) { + secp256k1_rand256(&in[1]); + } + if (len > 33) { + secp256k1_rand256(&in[33]); + } + if (secp256k1_eckey_pubkey_parse(&elem, in, len)) { + unsigned char out[65]; + unsigned char firstb; + int res; + size_t size = len; + firstb = in[0]; + /* If the pubkey can be parsed, it should round-trip... */ + CHECK(secp256k1_eckey_pubkey_serialize(&elem, out, &size, len == 33)); + CHECK(size == len); + CHECK(memcmp(&in[1], &out[1], len-1) == 0); + /* ... except for the type of hybrid inputs. */ + if ((in[0] != 6) && (in[0] != 7)) { + CHECK(in[0] == out[0]); + } + size = 65; + CHECK(secp256k1_eckey_pubkey_serialize(&elem, in, &size, 0)); + CHECK(size == 65); + CHECK(secp256k1_eckey_pubkey_parse(&elem2, in, size)); + ge_equals_ge(&elem,&elem2); + /* Check that the X9.62 hybrid type is checked. */ + in[0] = secp256k1_rand_bits(1) ? 6 : 7; + res = secp256k1_eckey_pubkey_parse(&elem2, in, size); + if (firstb == 2 || firstb == 3) { + if (in[0] == firstb + 4) { + CHECK(res); + } else { + CHECK(!res); + } + } + if (res) { + ge_equals_ge(&elem,&elem2); + CHECK(secp256k1_eckey_pubkey_serialize(&elem, out, &size, 0)); + CHECK(memcmp(&in[1], &out[1], 64) == 0); + } + } +} + +void run_random_pubkeys(void) { + int i; + for (i = 0; i < 10*count; i++) { + test_random_pubkeys(); + } +} + +void run_ecdsa_end_to_end(void) { + int i; + for (i = 0; i < 64*count; i++) { + test_ecdsa_end_to_end(); + } +} + +int test_ecdsa_der_parse(const unsigned char *sig, size_t siglen, int certainly_der, int certainly_not_der) { + static const unsigned char zeroes[32] = {0}; +#ifdef ENABLE_OPENSSL_TESTS + static const unsigned char max_scalar[32] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x40 + }; +#endif + + int ret = 0; + + secp256k1_ecdsa_signature sig_der; + unsigned char roundtrip_der[2048]; + unsigned char compact_der[64]; + size_t len_der = 2048; + int parsed_der = 0, valid_der = 0, roundtrips_der = 0; + + secp256k1_ecdsa_signature sig_der_lax; + unsigned char roundtrip_der_lax[2048]; + unsigned char compact_der_lax[64]; + size_t len_der_lax = 2048; + int parsed_der_lax = 0, valid_der_lax = 0, roundtrips_der_lax = 0; + +#ifdef ENABLE_OPENSSL_TESTS + ECDSA_SIG *sig_openssl; + const unsigned char *sigptr; + unsigned char roundtrip_openssl[2048]; + int len_openssl = 2048; + int parsed_openssl, valid_openssl = 0, roundtrips_openssl = 0; +#endif + + parsed_der = secp256k1_ecdsa_signature_parse_der(ctx, &sig_der, sig, siglen); + if (parsed_der) { + ret |= (!secp256k1_ecdsa_signature_serialize_compact(ctx, compact_der, &sig_der)) << 0; + valid_der = (memcmp(compact_der, zeroes, 32) != 0) && (memcmp(compact_der + 32, zeroes, 32) != 0); + } + if (valid_der) { + ret |= (!secp256k1_ecdsa_signature_serialize_der(ctx, roundtrip_der, &len_der, &sig_der)) << 1; + roundtrips_der = (len_der == siglen) && memcmp(roundtrip_der, sig, siglen) == 0; + } + + parsed_der_lax = ecdsa_signature_parse_der_lax(ctx, &sig_der_lax, sig, siglen); + if (parsed_der_lax) { + ret |= (!secp256k1_ecdsa_signature_serialize_compact(ctx, compact_der_lax, &sig_der_lax)) << 10; + valid_der_lax = (memcmp(compact_der_lax, zeroes, 32) != 0) && (memcmp(compact_der_lax + 32, zeroes, 32) != 0); + } + if (valid_der_lax) { + ret |= (!secp256k1_ecdsa_signature_serialize_der(ctx, roundtrip_der_lax, &len_der_lax, &sig_der_lax)) << 11; + roundtrips_der_lax = (len_der_lax == siglen) && memcmp(roundtrip_der_lax, sig, siglen) == 0; + } + + if (certainly_der) { + ret |= (!parsed_der) << 2; + } + if (certainly_not_der) { + ret |= (parsed_der) << 17; + } + if (valid_der) { + ret |= (!roundtrips_der) << 3; + } + + if (valid_der) { + ret |= (!roundtrips_der_lax) << 12; + ret |= (len_der != len_der_lax) << 13; + ret |= (memcmp(roundtrip_der_lax, roundtrip_der, len_der) != 0) << 14; + } + ret |= (roundtrips_der != roundtrips_der_lax) << 15; + if (parsed_der) { + ret |= (!parsed_der_lax) << 16; + } + +#ifdef ENABLE_OPENSSL_TESTS + sig_openssl = ECDSA_SIG_new(); + sigptr = sig; + parsed_openssl = (d2i_ECDSA_SIG(&sig_openssl, &sigptr, siglen) != NULL); + if (parsed_openssl) { + valid_openssl = !BN_is_negative(sig_openssl->r) && !BN_is_negative(sig_openssl->s) && BN_num_bits(sig_openssl->r) > 0 && BN_num_bits(sig_openssl->r) <= 256 && BN_num_bits(sig_openssl->s) > 0 && BN_num_bits(sig_openssl->s) <= 256; + if (valid_openssl) { + unsigned char tmp[32] = {0}; + BN_bn2bin(sig_openssl->r, tmp + 32 - BN_num_bytes(sig_openssl->r)); + valid_openssl = memcmp(tmp, max_scalar, 32) < 0; + } + if (valid_openssl) { + unsigned char tmp[32] = {0}; + BN_bn2bin(sig_openssl->s, tmp + 32 - BN_num_bytes(sig_openssl->s)); + valid_openssl = memcmp(tmp, max_scalar, 32) < 0; + } + } + len_openssl = i2d_ECDSA_SIG(sig_openssl, NULL); + if (len_openssl <= 2048) { + unsigned char *ptr = roundtrip_openssl; + CHECK(i2d_ECDSA_SIG(sig_openssl, &ptr) == len_openssl); + roundtrips_openssl = valid_openssl && ((size_t)len_openssl == siglen) && (memcmp(roundtrip_openssl, sig, siglen) == 0); + } else { + len_openssl = 0; + } + ECDSA_SIG_free(sig_openssl); + + ret |= (parsed_der && !parsed_openssl) << 4; + ret |= (valid_der && !valid_openssl) << 5; + ret |= (roundtrips_openssl && !parsed_der) << 6; + ret |= (roundtrips_der != roundtrips_openssl) << 7; + if (roundtrips_openssl) { + ret |= (len_der != (size_t)len_openssl) << 8; + ret |= (memcmp(roundtrip_der, roundtrip_openssl, len_der) != 0) << 9; + } +#endif + return ret; +} + +static void assign_big_endian(unsigned char *ptr, size_t ptrlen, uint32_t val) { + size_t i; + for (i = 0; i < ptrlen; i++) { + int shift = ptrlen - 1 - i; + if (shift >= 4) { + ptr[i] = 0; + } else { + ptr[i] = (val >> shift) & 0xFF; + } + } +} + +static void damage_array(unsigned char *sig, size_t *len) { + int pos; + int action = secp256k1_rand_bits(3); + if (action < 1 && *len > 3) { + /* Delete a byte. */ + pos = secp256k1_rand_int(*len); + memmove(sig + pos, sig + pos + 1, *len - pos - 1); + (*len)--; + return; + } else if (action < 2 && *len < 2048) { + /* Insert a byte. */ + pos = secp256k1_rand_int(1 + *len); + memmove(sig + pos + 1, sig + pos, *len - pos); + sig[pos] = secp256k1_rand_bits(8); + (*len)++; + return; + } else if (action < 4) { + /* Modify a byte. */ + sig[secp256k1_rand_int(*len)] += 1 + secp256k1_rand_int(255); + return; + } else { /* action < 8 */ + /* Modify a bit. */ + sig[secp256k1_rand_int(*len)] ^= 1 << secp256k1_rand_bits(3); + return; + } +} + +static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly_der, int* certainly_not_der) { + int der; + int nlow[2], nlen[2], nlenlen[2], nhbit[2], nhbyte[2], nzlen[2]; + size_t tlen, elen, glen; + int indet; + int n; + + *len = 0; + der = secp256k1_rand_bits(2) == 0; + *certainly_der = der; + *certainly_not_der = 0; + indet = der ? 0 : secp256k1_rand_int(10) == 0; + + for (n = 0; n < 2; n++) { + /* We generate two classes of numbers: nlow==1 "low" ones (up to 32 bytes), nlow==0 "high" ones (32 bytes with 129 top bits set, or larger than 32 bytes) */ + nlow[n] = der ? 1 : (secp256k1_rand_bits(3) != 0); + /* The length of the number in bytes (the first byte of which will always be nonzero) */ + nlen[n] = nlow[n] ? secp256k1_rand_int(33) : 32 + secp256k1_rand_int(200) * secp256k1_rand_int(8) / 8; + CHECK(nlen[n] <= 232); + /* The top bit of the number. */ + nhbit[n] = (nlow[n] == 0 && nlen[n] == 32) ? 1 : (nlen[n] == 0 ? 0 : secp256k1_rand_bits(1)); + /* The top byte of the number (after the potential hardcoded 16 0xFF characters for "high" 32 bytes numbers) */ + nhbyte[n] = nlen[n] == 0 ? 0 : (nhbit[n] ? 128 + secp256k1_rand_bits(7) : 1 + secp256k1_rand_int(127)); + /* The number of zero bytes in front of the number (which is 0 or 1 in case of DER, otherwise we extend up to 300 bytes) */ + nzlen[n] = der ? ((nlen[n] == 0 || nhbit[n]) ? 1 : 0) : (nlow[n] ? secp256k1_rand_int(3) : secp256k1_rand_int(300 - nlen[n]) * secp256k1_rand_int(8) / 8); + if (nzlen[n] > ((nlen[n] == 0 || nhbit[n]) ? 1 : 0)) { + *certainly_not_der = 1; + } + CHECK(nlen[n] + nzlen[n] <= 300); + /* The length of the length descriptor for the number. 0 means short encoding, anything else is long encoding. */ + nlenlen[n] = nlen[n] + nzlen[n] < 128 ? 0 : (nlen[n] + nzlen[n] < 256 ? 1 : 2); + if (!der) { + /* nlenlen[n] max 127 bytes */ + int add = secp256k1_rand_int(127 - nlenlen[n]) * secp256k1_rand_int(16) * secp256k1_rand_int(16) / 256; + nlenlen[n] += add; + if (add != 0) { + *certainly_not_der = 1; + } + } + CHECK(nlen[n] + nzlen[n] + nlenlen[n] <= 427); + } + + /* The total length of the data to go, so far */ + tlen = 2 + nlenlen[0] + nlen[0] + nzlen[0] + 2 + nlenlen[1] + nlen[1] + nzlen[1]; + CHECK(tlen <= 856); + + /* The length of the garbage inside the tuple. */ + elen = (der || indet) ? 0 : secp256k1_rand_int(980 - tlen) * secp256k1_rand_int(8) / 8; + if (elen != 0) { + *certainly_not_der = 1; + } + tlen += elen; + CHECK(tlen <= 980); + + /* The length of the garbage after the end of the tuple. */ + glen = der ? 0 : secp256k1_rand_int(990 - tlen) * secp256k1_rand_int(8) / 8; + if (glen != 0) { + *certainly_not_der = 1; + } + CHECK(tlen + glen <= 990); + + /* Write the tuple header. */ + sig[(*len)++] = 0x30; + if (indet) { + /* Indeterminate length */ + sig[(*len)++] = 0x80; + *certainly_not_der = 1; + } else { + int tlenlen = tlen < 128 ? 0 : (tlen < 256 ? 1 : 2); + if (!der) { + int add = secp256k1_rand_int(127 - tlenlen) * secp256k1_rand_int(16) * secp256k1_rand_int(16) / 256; + tlenlen += add; + if (add != 0) { + *certainly_not_der = 1; + } + } + if (tlenlen == 0) { + /* Short length notation */ + sig[(*len)++] = tlen; + } else { + /* Long length notation */ + sig[(*len)++] = 128 + tlenlen; + assign_big_endian(sig + *len, tlenlen, tlen); + *len += tlenlen; + } + tlen += tlenlen; + } + tlen += 2; + CHECK(tlen + glen <= 1119); + + for (n = 0; n < 2; n++) { + /* Write the integer header. */ + sig[(*len)++] = 0x02; + if (nlenlen[n] == 0) { + /* Short length notation */ + sig[(*len)++] = nlen[n] + nzlen[n]; + } else { + /* Long length notation. */ + sig[(*len)++] = 128 + nlenlen[n]; + assign_big_endian(sig + *len, nlenlen[n], nlen[n] + nzlen[n]); + *len += nlenlen[n]; + } + /* Write zero padding */ + while (nzlen[n] > 0) { + sig[(*len)++] = 0x00; + nzlen[n]--; + } + if (nlen[n] == 32 && !nlow[n]) { + /* Special extra 16 0xFF bytes in "high" 32-byte numbers */ + int i; + for (i = 0; i < 16; i++) { + sig[(*len)++] = 0xFF; + } + nlen[n] -= 16; + } + /* Write first byte of number */ + if (nlen[n] > 0) { + sig[(*len)++] = nhbyte[n]; + nlen[n]--; + } + /* Generate remaining random bytes of number */ + secp256k1_rand_bytes_test(sig + *len, nlen[n]); + *len += nlen[n]; + nlen[n] = 0; + } + + /* Generate random garbage inside tuple. */ + secp256k1_rand_bytes_test(sig + *len, elen); + *len += elen; + + /* Generate end-of-contents bytes. */ + if (indet) { + sig[(*len)++] = 0; + sig[(*len)++] = 0; + tlen += 2; + } + CHECK(tlen + glen <= 1121); + + /* Generate random garbage outside tuple. */ + secp256k1_rand_bytes_test(sig + *len, glen); + *len += glen; + tlen += glen; + CHECK(tlen <= 1121); + CHECK(tlen == *len); +} + +void run_ecdsa_der_parse(void) { + int i,j; + for (i = 0; i < 200 * count; i++) { + unsigned char buffer[2048]; + size_t buflen = 0; + int certainly_der = 0; + int certainly_not_der = 0; + random_ber_signature(buffer, &buflen, &certainly_der, &certainly_not_der); + CHECK(buflen <= 2048); + for (j = 0; j < 16; j++) { + int ret = 0; + if (j > 0) { + damage_array(buffer, &buflen); + /* We don't know anything anymore about the DERness of the result */ + certainly_der = 0; + certainly_not_der = 0; + } + ret = test_ecdsa_der_parse(buffer, buflen, certainly_der, certainly_not_der); + if (ret != 0) { + size_t k; + fprintf(stderr, "Failure %x on ", ret); + for (k = 0; k < buflen; k++) { + fprintf(stderr, "%02x ", buffer[k]); + } + fprintf(stderr, "\n"); + } + CHECK(ret == 0); + } + } +} + +/* Tests several edge cases. */ +void test_ecdsa_edge_cases(void) { + int t; + secp256k1_ecdsa_signature sig; + + /* Test the case where ECDSA recomputes a point that is infinity. */ + { + secp256k1_gej keyj; + secp256k1_ge key; + secp256k1_scalar msg; + secp256k1_scalar sr, ss; + secp256k1_scalar_set_int(&ss, 1); + secp256k1_scalar_negate(&ss, &ss); + secp256k1_scalar_inverse(&ss, &ss); + secp256k1_scalar_set_int(&sr, 1); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &keyj, &sr); + secp256k1_ge_set_gej(&key, &keyj); + msg = ss; + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 0); + } + + /* Verify signature with r of zero fails. */ + { + const unsigned char pubkey_mods_zero[33] = { + 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, + 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, + 0x41 + }; + secp256k1_ge key; + secp256k1_scalar msg; + secp256k1_scalar sr, ss; + secp256k1_scalar_set_int(&ss, 1); + secp256k1_scalar_set_int(&msg, 0); + secp256k1_scalar_set_int(&sr, 0); + CHECK(secp256k1_eckey_pubkey_parse(&key, pubkey_mods_zero, 33)); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 0); + } + + /* Verify signature with s of zero fails. */ + { + const unsigned char pubkey[33] = { + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01 + }; + secp256k1_ge key; + secp256k1_scalar msg; + secp256k1_scalar sr, ss; + secp256k1_scalar_set_int(&ss, 0); + secp256k1_scalar_set_int(&msg, 0); + secp256k1_scalar_set_int(&sr, 1); + CHECK(secp256k1_eckey_pubkey_parse(&key, pubkey, 33)); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 0); + } + + /* Verify signature with message 0 passes. */ + { + const unsigned char pubkey[33] = { + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02 + }; + const unsigned char pubkey2[33] = { + 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, + 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, + 0x43 + }; + secp256k1_ge key; + secp256k1_ge key2; + secp256k1_scalar msg; + secp256k1_scalar sr, ss; + secp256k1_scalar_set_int(&ss, 2); + secp256k1_scalar_set_int(&msg, 0); + secp256k1_scalar_set_int(&sr, 2); + CHECK(secp256k1_eckey_pubkey_parse(&key, pubkey, 33)); + CHECK(secp256k1_eckey_pubkey_parse(&key2, pubkey2, 33)); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 1); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key2, &msg) == 1); + secp256k1_scalar_negate(&ss, &ss); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 1); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key2, &msg) == 1); + secp256k1_scalar_set_int(&ss, 1); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 0); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key2, &msg) == 0); + } + + /* Verify signature with message 1 passes. */ + { + const unsigned char pubkey[33] = { + 0x02, 0x14, 0x4e, 0x5a, 0x58, 0xef, 0x5b, 0x22, + 0x6f, 0xd2, 0xe2, 0x07, 0x6a, 0x77, 0xcf, 0x05, + 0xb4, 0x1d, 0xe7, 0x4a, 0x30, 0x98, 0x27, 0x8c, + 0x93, 0xe6, 0xe6, 0x3c, 0x0b, 0xc4, 0x73, 0x76, + 0x25 + }; + const unsigned char pubkey2[33] = { + 0x02, 0x8a, 0xd5, 0x37, 0xed, 0x73, 0xd9, 0x40, + 0x1d, 0xa0, 0x33, 0xd2, 0xdc, 0xf0, 0xaf, 0xae, + 0x34, 0xcf, 0x5f, 0x96, 0x4c, 0x73, 0x28, 0x0f, + 0x92, 0xc0, 0xf6, 0x9d, 0xd9, 0xb2, 0x09, 0x10, + 0x62 + }; + const unsigned char csr[32] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x45, 0x51, 0x23, 0x19, 0x50, 0xb7, 0x5f, 0xc4, + 0x40, 0x2d, 0xa1, 0x72, 0x2f, 0xc9, 0xba, 0xeb + }; + secp256k1_ge key; + secp256k1_ge key2; + secp256k1_scalar msg; + secp256k1_scalar sr, ss; + secp256k1_scalar_set_int(&ss, 1); + secp256k1_scalar_set_int(&msg, 1); + secp256k1_scalar_set_b32(&sr, csr, NULL); + CHECK(secp256k1_eckey_pubkey_parse(&key, pubkey, 33)); + CHECK(secp256k1_eckey_pubkey_parse(&key2, pubkey2, 33)); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 1); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key2, &msg) == 1); + secp256k1_scalar_negate(&ss, &ss); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 1); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key2, &msg) == 1); + secp256k1_scalar_set_int(&ss, 2); + secp256k1_scalar_inverse_var(&ss, &ss); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 0); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key2, &msg) == 0); + } + + /* Verify signature with message -1 passes. */ + { + const unsigned char pubkey[33] = { + 0x03, 0xaf, 0x97, 0xff, 0x7d, 0x3a, 0xf6, 0xa0, + 0x02, 0x94, 0xbd, 0x9f, 0x4b, 0x2e, 0xd7, 0x52, + 0x28, 0xdb, 0x49, 0x2a, 0x65, 0xcb, 0x1e, 0x27, + 0x57, 0x9c, 0xba, 0x74, 0x20, 0xd5, 0x1d, 0x20, + 0xf1 + }; + const unsigned char csr[32] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x45, 0x51, 0x23, 0x19, 0x50, 0xb7, 0x5f, 0xc4, + 0x40, 0x2d, 0xa1, 0x72, 0x2f, 0xc9, 0xba, 0xee + }; + secp256k1_ge key; + secp256k1_scalar msg; + secp256k1_scalar sr, ss; + secp256k1_scalar_set_int(&ss, 1); + secp256k1_scalar_set_int(&msg, 1); + secp256k1_scalar_negate(&msg, &msg); + secp256k1_scalar_set_b32(&sr, csr, NULL); + CHECK(secp256k1_eckey_pubkey_parse(&key, pubkey, 33)); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 1); + secp256k1_scalar_negate(&ss, &ss); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 1); + secp256k1_scalar_set_int(&ss, 3); + secp256k1_scalar_inverse_var(&ss, &ss); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sr, &ss, &key, &msg) == 0); + } + + /* Signature where s would be zero. */ + { + secp256k1_pubkey pubkey; + size_t siglen; + int32_t ecount; + unsigned char signature[72]; + static const unsigned char nonce[32] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }; + static const unsigned char nonce2[32] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE, + 0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,0x3B, + 0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x40 + }; + const unsigned char key[32] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }; + unsigned char msg[32] = { + 0x86, 0x41, 0x99, 0x81, 0x06, 0x23, 0x44, 0x53, + 0xaa, 0x5f, 0x9d, 0x6a, 0x31, 0x78, 0xf4, 0xf7, + 0xb8, 0x12, 0xe0, 0x0b, 0x81, 0x7a, 0x77, 0x62, + 0x65, 0xdf, 0xdd, 0x31, 0xb9, 0x3e, 0x29, 0xa9, + }; + ecount = 0; + secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, precomputed_nonce_function, nonce) == 0); + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, precomputed_nonce_function, nonce2) == 0); + msg[31] = 0xaa; + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, precomputed_nonce_function, nonce) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_ecdsa_sign(ctx, NULL, msg, key, precomputed_nonce_function, nonce2) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_sign(ctx, &sig, NULL, key, precomputed_nonce_function, nonce2) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, NULL, precomputed_nonce_function, nonce2) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, precomputed_nonce_function, nonce2) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, key) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, NULL, msg, &pubkey) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, NULL, &pubkey) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, NULL) == 0); + CHECK(ecount == 6); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, &pubkey) == 1); + CHECK(ecount == 6); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, NULL) == 0); + CHECK(ecount == 7); + /* That pubkeyload fails via an ARGCHECK is a little odd but makes sense because pubkeys are an opaque data type. */ + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, &pubkey) == 0); + CHECK(ecount == 8); + siglen = 72; + CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, NULL, &siglen, &sig) == 0); + CHECK(ecount == 9); + CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, signature, NULL, &sig) == 0); + CHECK(ecount == 10); + CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, signature, &siglen, NULL) == 0); + CHECK(ecount == 11); + CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, signature, &siglen, &sig) == 1); + CHECK(ecount == 11); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, NULL, signature, siglen) == 0); + CHECK(ecount == 12); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, NULL, siglen) == 0); + CHECK(ecount == 13); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &sig, signature, siglen) == 1); + CHECK(ecount == 13); + siglen = 10; + /* Too little room for a signature does not fail via ARGCHECK. */ + CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, signature, &siglen, &sig) == 0); + CHECK(ecount == 13); + ecount = 0; + CHECK(secp256k1_ecdsa_signature_normalize(ctx, NULL, NULL) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, NULL, &sig) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, signature, NULL) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, signature, &sig) == 1); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, NULL, signature) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &sig, NULL) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &sig, signature) == 1); + CHECK(ecount == 5); + memset(signature, 255, 64); + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &sig, signature) == 0); + CHECK(ecount == 5); + secp256k1_context_set_illegal_callback(ctx, NULL, NULL); + } + + /* Nonce function corner cases. */ + for (t = 0; t < 2; t++) { + static const unsigned char zero[32] = {0x00}; + int i; + unsigned char key[32]; + unsigned char msg[32]; + secp256k1_ecdsa_signature sig2; + secp256k1_scalar sr[512], ss; + const unsigned char *extra; + extra = t == 0 ? NULL : zero; + memset(msg, 0, 32); + msg[31] = 1; + /* High key results in signature failure. */ + memset(key, 0xFF, 32); + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, NULL, extra) == 0); + CHECK(is_empty_signature(&sig)); + /* Zero key results in signature failure. */ + memset(key, 0, 32); + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, NULL, extra) == 0); + CHECK(is_empty_signature(&sig)); + /* Nonce function failure results in signature failure. */ + key[31] = 1; + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, nonce_function_test_fail, extra) == 0); + CHECK(is_empty_signature(&sig)); + /* The retry loop successfully makes its way to the first good value. */ + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, key, nonce_function_test_retry, extra) == 1); + CHECK(!is_empty_signature(&sig)); + CHECK(secp256k1_ecdsa_sign(ctx, &sig2, msg, key, nonce_function_rfc6979, extra) == 1); + CHECK(!is_empty_signature(&sig2)); + CHECK(memcmp(&sig, &sig2, sizeof(sig)) == 0); + /* The default nonce function is deterministic. */ + CHECK(secp256k1_ecdsa_sign(ctx, &sig2, msg, key, NULL, extra) == 1); + CHECK(!is_empty_signature(&sig2)); + CHECK(memcmp(&sig, &sig2, sizeof(sig)) == 0); + /* The default nonce function changes output with different messages. */ + for(i = 0; i < 256; i++) { + int j; + msg[0] = i; + CHECK(secp256k1_ecdsa_sign(ctx, &sig2, msg, key, NULL, extra) == 1); + CHECK(!is_empty_signature(&sig2)); + secp256k1_ecdsa_signature_load(ctx, &sr[i], &ss, &sig2); + for (j = 0; j < i; j++) { + CHECK(!secp256k1_scalar_eq(&sr[i], &sr[j])); + } + } + msg[0] = 0; + msg[31] = 2; + /* The default nonce function changes output with different keys. */ + for(i = 256; i < 512; i++) { + int j; + key[0] = i - 256; + CHECK(secp256k1_ecdsa_sign(ctx, &sig2, msg, key, NULL, extra) == 1); + CHECK(!is_empty_signature(&sig2)); + secp256k1_ecdsa_signature_load(ctx, &sr[i], &ss, &sig2); + for (j = 0; j < i; j++) { + CHECK(!secp256k1_scalar_eq(&sr[i], &sr[j])); + } + } + key[0] = 0; + } + + { + /* Check that optional nonce arguments do not have equivalent effect. */ + const unsigned char zeros[32] = {0}; + unsigned char nonce[32]; + unsigned char nonce2[32]; + unsigned char nonce3[32]; + unsigned char nonce4[32]; + VG_UNDEF(nonce,32); + VG_UNDEF(nonce2,32); + VG_UNDEF(nonce3,32); + VG_UNDEF(nonce4,32); + CHECK(nonce_function_rfc6979(nonce, zeros, zeros, NULL, NULL, 0) == 1); + VG_CHECK(nonce,32); + CHECK(nonce_function_rfc6979(nonce2, zeros, zeros, zeros, NULL, 0) == 1); + VG_CHECK(nonce2,32); + CHECK(nonce_function_rfc6979(nonce3, zeros, zeros, NULL, (void *)zeros, 0) == 1); + VG_CHECK(nonce3,32); + CHECK(nonce_function_rfc6979(nonce4, zeros, zeros, zeros, (void *)zeros, 0) == 1); + VG_CHECK(nonce4,32); + CHECK(memcmp(nonce, nonce2, 32) != 0); + CHECK(memcmp(nonce, nonce3, 32) != 0); + CHECK(memcmp(nonce, nonce4, 32) != 0); + CHECK(memcmp(nonce2, nonce3, 32) != 0); + CHECK(memcmp(nonce2, nonce4, 32) != 0); + CHECK(memcmp(nonce3, nonce4, 32) != 0); + } + + + /* Privkey export where pubkey is the point at infinity. */ + { + unsigned char privkey[300]; + unsigned char seckey[32] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41, + }; + size_t outlen = 300; + CHECK(!ec_privkey_export_der(ctx, privkey, &outlen, seckey, 0)); + outlen = 300; + CHECK(!ec_privkey_export_der(ctx, privkey, &outlen, seckey, 1)); + } +} + +void run_ecdsa_edge_cases(void) { + test_ecdsa_edge_cases(); +} + +#ifdef ENABLE_OPENSSL_TESTS +EC_KEY *get_openssl_key(const unsigned char *key32) { + unsigned char privkey[300]; + size_t privkeylen; + const unsigned char* pbegin = privkey; + int compr = secp256k1_rand_bits(1); + EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_secp256k1); + CHECK(ec_privkey_export_der(ctx, privkey, &privkeylen, key32, compr)); + CHECK(d2i_ECPrivateKey(&ec_key, &pbegin, privkeylen)); + CHECK(EC_KEY_check_key(ec_key)); + return ec_key; +} + +void test_ecdsa_openssl(void) { + secp256k1_gej qj; + secp256k1_ge q; + secp256k1_scalar sigr, sigs; + secp256k1_scalar one; + secp256k1_scalar msg2; + secp256k1_scalar key, msg; + EC_KEY *ec_key; + unsigned int sigsize = 80; + size_t secp_sigsize = 80; + unsigned char message[32]; + unsigned char signature[80]; + unsigned char key32[32]; + secp256k1_rand256_test(message); + secp256k1_scalar_set_b32(&msg, message, NULL); + random_scalar_order_test(&key); + secp256k1_scalar_get_b32(key32, &key); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &qj, &key); + secp256k1_ge_set_gej(&q, &qj); + ec_key = get_openssl_key(key32); + CHECK(ec_key != NULL); + CHECK(ECDSA_sign(0, message, sizeof(message), signature, &sigsize, ec_key)); + CHECK(secp256k1_ecdsa_sig_parse(&sigr, &sigs, signature, sigsize)); + CHECK(secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sigr, &sigs, &q, &msg)); + secp256k1_scalar_set_int(&one, 1); + secp256k1_scalar_add(&msg2, &msg, &one); + CHECK(!secp256k1_ecdsa_sig_verify(&ctx->ecmult_ctx, &sigr, &sigs, &q, &msg2)); + + random_sign(&sigr, &sigs, &key, &msg, NULL); + CHECK(secp256k1_ecdsa_sig_serialize(signature, &secp_sigsize, &sigr, &sigs)); + CHECK(ECDSA_verify(0, message, sizeof(message), signature, secp_sigsize, ec_key) == 1); + + EC_KEY_free(ec_key); +} + +void run_ecdsa_openssl(void) { + int i; + for (i = 0; i < 10*count; i++) { + test_ecdsa_openssl(); + } +} +#endif + +#ifdef ENABLE_MODULE_ECDH +# include "modules/ecdh/tests_impl.h" +#endif + +#ifdef ENABLE_MODULE_SCHNORR +# include "modules/schnorr/tests_impl.h" +#endif + +#ifdef ENABLE_MODULE_RECOVERY +# include "modules/recovery/tests_impl.h" +#endif + +int main(int argc, char **argv) { + unsigned char seed16[16] = {0}; + unsigned char run32[32] = {0}; + /* find iteration count */ + if (argc > 1) { + count = strtol(argv[1], NULL, 0); + } + + /* find random seed */ + if (argc > 2) { + int pos = 0; + const char* ch = argv[2]; + while (pos < 16 && ch[0] != 0 && ch[1] != 0) { + unsigned short sh; + if (sscanf(ch, "%2hx", &sh)) { + seed16[pos] = sh; + } else { + break; + } + ch += 2; + pos++; + } + } else { + FILE *frand = fopen("/dev/urandom", "r"); + if ((frand == NULL) || !fread(&seed16, sizeof(seed16), 1, frand)) { + uint64_t t = time(NULL) * (uint64_t)1337; + seed16[0] ^= t; + seed16[1] ^= t >> 8; + seed16[2] ^= t >> 16; + seed16[3] ^= t >> 24; + seed16[4] ^= t >> 32; + seed16[5] ^= t >> 40; + seed16[6] ^= t >> 48; + seed16[7] ^= t >> 56; + } + fclose(frand); + } + secp256k1_rand_seed(seed16); + + printf("test count = %i\n", count); + printf("random seed = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", seed16[0], seed16[1], seed16[2], seed16[3], seed16[4], seed16[5], seed16[6], seed16[7], seed16[8], seed16[9], seed16[10], seed16[11], seed16[12], seed16[13], seed16[14], seed16[15]); + + /* initialize */ + run_context_tests(); + ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + if (secp256k1_rand_bits(1)) { + secp256k1_rand256(run32); + CHECK(secp256k1_context_randomize(ctx, secp256k1_rand_bits(1) ? run32 : NULL)); + } + + run_rand_bits(); + run_rand_int(); + + run_sha256_tests(); + run_hmac_sha256_tests(); + run_rfc6979_hmac_sha256_tests(); + +#ifndef USE_NUM_NONE + /* num tests */ + run_num_smalltests(); +#endif + + /* scalar tests */ + run_scalar_tests(); + + /* field tests */ + run_field_inv(); + run_field_inv_var(); + run_field_inv_all_var(); + run_field_misc(); + run_field_convert(); + run_sqr(); + run_sqrt(); + + /* group tests */ + run_ge(); + run_group_decompress(); + + /* ecmult tests */ + run_wnaf(); + run_point_times_order(); + run_ecmult_chain(); + run_ecmult_constants(); + run_ecmult_gen_blind(); + run_ecmult_const_tests(); + run_ec_combine(); + + /* endomorphism tests */ +#ifdef USE_ENDOMORPHISM + run_endomorphism_tests(); +#endif + + /* EC point parser test */ + run_ec_pubkey_parse_test(); + + /* EC key edge cases */ + run_eckey_edge_case_test(); + +#ifdef ENABLE_MODULE_ECDH + /* ecdh tests */ + run_ecdh_tests(); +#endif + + /* ecdsa tests */ + run_random_pubkeys(); + run_ecdsa_der_parse(); + run_ecdsa_sign_verify(); + run_ecdsa_end_to_end(); + run_ecdsa_edge_cases(); +#ifdef ENABLE_OPENSSL_TESTS + run_ecdsa_openssl(); +#endif + +#ifdef ENABLE_MODULE_SCHNORR + /* Schnorr tests */ + run_schnorr_tests(); +#endif + +#ifdef ENABLE_MODULE_RECOVERY + /* ECDSA pubkey recovery tests */ + run_recovery_tests(); +#endif + + secp256k1_rand256(run32); + printf("random run = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", run32[0], run32[1], run32[2], run32[3], run32[4], run32[5], run32[6], run32[7], run32[8], run32[9], run32[10], run32[11], run32[12], run32[13], run32[14], run32[15]); + + /* shutdown */ + secp256k1_context_destroy(ctx); + + printf("no problems found\n"); + return 0; +} diff --git a/crypto/secp256k1/libsecp256k1/src/tests_exhaustive.c b/crypto/secp256k1/libsecp256k1/src/tests_exhaustive.c new file mode 100644 index 0000000..b040bb0 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/tests_exhaustive.c @@ -0,0 +1,470 @@ +/*********************************************************************** + * Copyright (c) 2016 Andrew Poelstra * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#include +#include + +#include + +#undef USE_ECMULT_STATIC_PRECOMPUTATION + +#ifndef EXHAUSTIVE_TEST_ORDER +/* see group_impl.h for allowable values */ +#define EXHAUSTIVE_TEST_ORDER 13 +#define EXHAUSTIVE_TEST_LAMBDA 9 /* cube root of 1 mod 13 */ +#endif + +#include "include/secp256k1.h" +#include "group.h" +#include "secp256k1.c" +#include "testrand_impl.h" + +#ifdef ENABLE_MODULE_RECOVERY +#include "src/modules/recovery/main_impl.h" +#include "include/secp256k1_recovery.h" +#endif + +/** stolen from tests.c */ +void ge_equals_ge(const secp256k1_ge *a, const secp256k1_ge *b) { + CHECK(a->infinity == b->infinity); + if (a->infinity) { + return; + } + CHECK(secp256k1_fe_equal_var(&a->x, &b->x)); + CHECK(secp256k1_fe_equal_var(&a->y, &b->y)); +} + +void ge_equals_gej(const secp256k1_ge *a, const secp256k1_gej *b) { + secp256k1_fe z2s; + secp256k1_fe u1, u2, s1, s2; + CHECK(a->infinity == b->infinity); + if (a->infinity) { + return; + } + /* Check a.x * b.z^2 == b.x && a.y * b.z^3 == b.y, to avoid inverses. */ + secp256k1_fe_sqr(&z2s, &b->z); + secp256k1_fe_mul(&u1, &a->x, &z2s); + u2 = b->x; secp256k1_fe_normalize_weak(&u2); + secp256k1_fe_mul(&s1, &a->y, &z2s); secp256k1_fe_mul(&s1, &s1, &b->z); + s2 = b->y; secp256k1_fe_normalize_weak(&s2); + CHECK(secp256k1_fe_equal_var(&u1, &u2)); + CHECK(secp256k1_fe_equal_var(&s1, &s2)); +} + +void random_fe(secp256k1_fe *x) { + unsigned char bin[32]; + do { + secp256k1_rand256(bin); + if (secp256k1_fe_set_b32(x, bin)) { + return; + } + } while(1); +} +/** END stolen from tests.c */ + +int secp256k1_nonce_function_smallint(unsigned char *nonce32, const unsigned char *msg32, + const unsigned char *key32, const unsigned char *algo16, + void *data, unsigned int attempt) { + secp256k1_scalar s; + int *idata = data; + (void)msg32; + (void)key32; + (void)algo16; + /* Some nonces cannot be used because they'd cause s and/or r to be zero. + * The signing function has retry logic here that just re-calls the nonce + * function with an increased `attempt`. So if attempt > 0 this means we + * need to change the nonce to avoid an infinite loop. */ + if (attempt > 0) { + *idata = (*idata + 1) % EXHAUSTIVE_TEST_ORDER; + } + secp256k1_scalar_set_int(&s, *idata); + secp256k1_scalar_get_b32(nonce32, &s); + return 1; +} + +#ifdef USE_ENDOMORPHISM +void test_exhaustive_endomorphism(const secp256k1_ge *group, int order) { + int i; + for (i = 0; i < order; i++) { + secp256k1_ge res; + secp256k1_ge_mul_lambda(&res, &group[i]); + ge_equals_ge(&group[i * EXHAUSTIVE_TEST_LAMBDA % EXHAUSTIVE_TEST_ORDER], &res); + } +} +#endif + +void test_exhaustive_addition(const secp256k1_ge *group, const secp256k1_gej *groupj, int order) { + int i, j; + + /* Sanity-check (and check infinity functions) */ + CHECK(secp256k1_ge_is_infinity(&group[0])); + CHECK(secp256k1_gej_is_infinity(&groupj[0])); + for (i = 1; i < order; i++) { + CHECK(!secp256k1_ge_is_infinity(&group[i])); + CHECK(!secp256k1_gej_is_infinity(&groupj[i])); + } + + /* Check all addition formulae */ + for (j = 0; j < order; j++) { + secp256k1_fe fe_inv; + secp256k1_fe_inv(&fe_inv, &groupj[j].z); + for (i = 0; i < order; i++) { + secp256k1_ge zless_gej; + secp256k1_gej tmp; + /* add_var */ + secp256k1_gej_add_var(&tmp, &groupj[i], &groupj[j], NULL); + ge_equals_gej(&group[(i + j) % order], &tmp); + /* add_ge */ + if (j > 0) { + secp256k1_gej_add_ge(&tmp, &groupj[i], &group[j]); + ge_equals_gej(&group[(i + j) % order], &tmp); + } + /* add_ge_var */ + secp256k1_gej_add_ge_var(&tmp, &groupj[i], &group[j], NULL); + ge_equals_gej(&group[(i + j) % order], &tmp); + /* add_zinv_var */ + zless_gej.infinity = groupj[j].infinity; + zless_gej.x = groupj[j].x; + zless_gej.y = groupj[j].y; + secp256k1_gej_add_zinv_var(&tmp, &groupj[i], &zless_gej, &fe_inv); + ge_equals_gej(&group[(i + j) % order], &tmp); + } + } + + /* Check doubling */ + for (i = 0; i < order; i++) { + secp256k1_gej tmp; + if (i > 0) { + secp256k1_gej_double_nonzero(&tmp, &groupj[i], NULL); + ge_equals_gej(&group[(2 * i) % order], &tmp); + } + secp256k1_gej_double_var(&tmp, &groupj[i], NULL); + ge_equals_gej(&group[(2 * i) % order], &tmp); + } + + /* Check negation */ + for (i = 1; i < order; i++) { + secp256k1_ge tmp; + secp256k1_gej tmpj; + secp256k1_ge_neg(&tmp, &group[i]); + ge_equals_ge(&group[order - i], &tmp); + secp256k1_gej_neg(&tmpj, &groupj[i]); + ge_equals_gej(&group[order - i], &tmpj); + } +} + +void test_exhaustive_ecmult(const secp256k1_context *ctx, const secp256k1_ge *group, const secp256k1_gej *groupj, int order) { + int i, j, r_log; + for (r_log = 1; r_log < order; r_log++) { + for (j = 0; j < order; j++) { + for (i = 0; i < order; i++) { + secp256k1_gej tmp; + secp256k1_scalar na, ng; + secp256k1_scalar_set_int(&na, i); + secp256k1_scalar_set_int(&ng, j); + + secp256k1_ecmult(&ctx->ecmult_ctx, &tmp, &groupj[r_log], &na, &ng); + ge_equals_gej(&group[(i * r_log + j) % order], &tmp); + + if (i > 0) { + secp256k1_ecmult_const(&tmp, &group[i], &ng); + ge_equals_gej(&group[(i * j) % order], &tmp); + } + } + } + } +} + +void r_from_k(secp256k1_scalar *r, const secp256k1_ge *group, int k) { + secp256k1_fe x; + unsigned char x_bin[32]; + k %= EXHAUSTIVE_TEST_ORDER; + x = group[k].x; + secp256k1_fe_normalize(&x); + secp256k1_fe_get_b32(x_bin, &x); + secp256k1_scalar_set_b32(r, x_bin, NULL); +} + +void test_exhaustive_verify(const secp256k1_context *ctx, const secp256k1_ge *group, int order) { + int s, r, msg, key; + for (s = 1; s < order; s++) { + for (r = 1; r < order; r++) { + for (msg = 1; msg < order; msg++) { + for (key = 1; key < order; key++) { + secp256k1_ge nonconst_ge; + secp256k1_ecdsa_signature sig; + secp256k1_pubkey pk; + secp256k1_scalar sk_s, msg_s, r_s, s_s; + secp256k1_scalar s_times_k_s, msg_plus_r_times_sk_s; + int k, should_verify; + unsigned char msg32[32]; + + secp256k1_scalar_set_int(&s_s, s); + secp256k1_scalar_set_int(&r_s, r); + secp256k1_scalar_set_int(&msg_s, msg); + secp256k1_scalar_set_int(&sk_s, key); + + /* Verify by hand */ + /* Run through every k value that gives us this r and check that *one* works. + * Note there could be none, there could be multiple, ECDSA is weird. */ + should_verify = 0; + for (k = 0; k < order; k++) { + secp256k1_scalar check_x_s; + r_from_k(&check_x_s, group, k); + if (r_s == check_x_s) { + secp256k1_scalar_set_int(&s_times_k_s, k); + secp256k1_scalar_mul(&s_times_k_s, &s_times_k_s, &s_s); + secp256k1_scalar_mul(&msg_plus_r_times_sk_s, &r_s, &sk_s); + secp256k1_scalar_add(&msg_plus_r_times_sk_s, &msg_plus_r_times_sk_s, &msg_s); + should_verify |= secp256k1_scalar_eq(&s_times_k_s, &msg_plus_r_times_sk_s); + } + } + /* nb we have a "high s" rule */ + should_verify &= !secp256k1_scalar_is_high(&s_s); + + /* Verify by calling verify */ + secp256k1_ecdsa_signature_save(&sig, &r_s, &s_s); + memcpy(&nonconst_ge, &group[sk_s], sizeof(nonconst_ge)); + secp256k1_pubkey_save(&pk, &nonconst_ge); + secp256k1_scalar_get_b32(msg32, &msg_s); + CHECK(should_verify == + secp256k1_ecdsa_verify(ctx, &sig, msg32, &pk)); + } + } + } + } +} + +void test_exhaustive_sign(const secp256k1_context *ctx, const secp256k1_ge *group, int order) { + int i, j, k; + + /* Loop */ + for (i = 1; i < order; i++) { /* message */ + for (j = 1; j < order; j++) { /* key */ + for (k = 1; k < order; k++) { /* nonce */ + const int starting_k = k; + secp256k1_ecdsa_signature sig; + secp256k1_scalar sk, msg, r, s, expected_r; + unsigned char sk32[32], msg32[32]; + secp256k1_scalar_set_int(&msg, i); + secp256k1_scalar_set_int(&sk, j); + secp256k1_scalar_get_b32(sk32, &sk); + secp256k1_scalar_get_b32(msg32, &msg); + + secp256k1_ecdsa_sign(ctx, &sig, msg32, sk32, secp256k1_nonce_function_smallint, &k); + + secp256k1_ecdsa_signature_load(ctx, &r, &s, &sig); + /* Note that we compute expected_r *after* signing -- this is important + * because our nonce-computing function function might change k during + * signing. */ + r_from_k(&expected_r, group, k); + CHECK(r == expected_r); + CHECK((k * s) % order == (i + r * j) % order || + (k * (EXHAUSTIVE_TEST_ORDER - s)) % order == (i + r * j) % order); + + /* Overflow means we've tried every possible nonce */ + if (k < starting_k) { + break; + } + } + } + } + + /* We would like to verify zero-knowledge here by counting how often every + * possible (s, r) tuple appears, but because the group order is larger + * than the field order, when coercing the x-values to scalar values, some + * appear more often than others, so we are actually not zero-knowledge. + * (This effect also appears in the real code, but the difference is on the + * order of 1/2^128th the field order, so the deviation is not useful to a + * computationally bounded attacker.) + */ +} + +#ifdef ENABLE_MODULE_RECOVERY +void test_exhaustive_recovery_sign(const secp256k1_context *ctx, const secp256k1_ge *group, int order) { + int i, j, k; + + /* Loop */ + for (i = 1; i < order; i++) { /* message */ + for (j = 1; j < order; j++) { /* key */ + for (k = 1; k < order; k++) { /* nonce */ + const int starting_k = k; + secp256k1_fe r_dot_y_normalized; + secp256k1_ecdsa_recoverable_signature rsig; + secp256k1_ecdsa_signature sig; + secp256k1_scalar sk, msg, r, s, expected_r; + unsigned char sk32[32], msg32[32]; + int expected_recid; + int recid; + secp256k1_scalar_set_int(&msg, i); + secp256k1_scalar_set_int(&sk, j); + secp256k1_scalar_get_b32(sk32, &sk); + secp256k1_scalar_get_b32(msg32, &msg); + + secp256k1_ecdsa_sign_recoverable(ctx, &rsig, msg32, sk32, secp256k1_nonce_function_smallint, &k); + + /* Check directly */ + secp256k1_ecdsa_recoverable_signature_load(ctx, &r, &s, &recid, &rsig); + r_from_k(&expected_r, group, k); + CHECK(r == expected_r); + CHECK((k * s) % order == (i + r * j) % order || + (k * (EXHAUSTIVE_TEST_ORDER - s)) % order == (i + r * j) % order); + /* In computing the recid, there is an overflow condition that is disabled in + * scalar_low_impl.h `secp256k1_scalar_set_b32` because almost every r.y value + * will exceed the group order, and our signing code always holds out for r + * values that don't overflow, so with a proper overflow check the tests would + * loop indefinitely. */ + r_dot_y_normalized = group[k].y; + secp256k1_fe_normalize(&r_dot_y_normalized); + /* Also the recovery id is flipped depending if we hit the low-s branch */ + if ((k * s) % order == (i + r * j) % order) { + expected_recid = secp256k1_fe_is_odd(&r_dot_y_normalized) ? 1 : 0; + } else { + expected_recid = secp256k1_fe_is_odd(&r_dot_y_normalized) ? 0 : 1; + } + CHECK(recid == expected_recid); + + /* Convert to a standard sig then check */ + secp256k1_ecdsa_recoverable_signature_convert(ctx, &sig, &rsig); + secp256k1_ecdsa_signature_load(ctx, &r, &s, &sig); + /* Note that we compute expected_r *after* signing -- this is important + * because our nonce-computing function function might change k during + * signing. */ + r_from_k(&expected_r, group, k); + CHECK(r == expected_r); + CHECK((k * s) % order == (i + r * j) % order || + (k * (EXHAUSTIVE_TEST_ORDER - s)) % order == (i + r * j) % order); + + /* Overflow means we've tried every possible nonce */ + if (k < starting_k) { + break; + } + } + } + } +} + +void test_exhaustive_recovery_verify(const secp256k1_context *ctx, const secp256k1_ge *group, int order) { + /* This is essentially a copy of test_exhaustive_verify, with recovery added */ + int s, r, msg, key; + for (s = 1; s < order; s++) { + for (r = 1; r < order; r++) { + for (msg = 1; msg < order; msg++) { + for (key = 1; key < order; key++) { + secp256k1_ge nonconst_ge; + secp256k1_ecdsa_recoverable_signature rsig; + secp256k1_ecdsa_signature sig; + secp256k1_pubkey pk; + secp256k1_scalar sk_s, msg_s, r_s, s_s; + secp256k1_scalar s_times_k_s, msg_plus_r_times_sk_s; + int recid = 0; + int k, should_verify; + unsigned char msg32[32]; + + secp256k1_scalar_set_int(&s_s, s); + secp256k1_scalar_set_int(&r_s, r); + secp256k1_scalar_set_int(&msg_s, msg); + secp256k1_scalar_set_int(&sk_s, key); + secp256k1_scalar_get_b32(msg32, &msg_s); + + /* Verify by hand */ + /* Run through every k value that gives us this r and check that *one* works. + * Note there could be none, there could be multiple, ECDSA is weird. */ + should_verify = 0; + for (k = 0; k < order; k++) { + secp256k1_scalar check_x_s; + r_from_k(&check_x_s, group, k); + if (r_s == check_x_s) { + secp256k1_scalar_set_int(&s_times_k_s, k); + secp256k1_scalar_mul(&s_times_k_s, &s_times_k_s, &s_s); + secp256k1_scalar_mul(&msg_plus_r_times_sk_s, &r_s, &sk_s); + secp256k1_scalar_add(&msg_plus_r_times_sk_s, &msg_plus_r_times_sk_s, &msg_s); + should_verify |= secp256k1_scalar_eq(&s_times_k_s, &msg_plus_r_times_sk_s); + } + } + /* nb we have a "high s" rule */ + should_verify &= !secp256k1_scalar_is_high(&s_s); + + /* We would like to try recovering the pubkey and checking that it matches, + * but pubkey recovery is impossible in the exhaustive tests (the reason + * being that there are 12 nonzero r values, 12 nonzero points, and no + * overlap between the sets, so there are no valid signatures). */ + + /* Verify by converting to a standard signature and calling verify */ + secp256k1_ecdsa_recoverable_signature_save(&rsig, &r_s, &s_s, recid); + secp256k1_ecdsa_recoverable_signature_convert(ctx, &sig, &rsig); + memcpy(&nonconst_ge, &group[sk_s], sizeof(nonconst_ge)); + secp256k1_pubkey_save(&pk, &nonconst_ge); + CHECK(should_verify == + secp256k1_ecdsa_verify(ctx, &sig, msg32, &pk)); + } + } + } + } +} +#endif + +int main(void) { + int i; + secp256k1_gej groupj[EXHAUSTIVE_TEST_ORDER]; + secp256k1_ge group[EXHAUSTIVE_TEST_ORDER]; + + /* Build context */ + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + + /* TODO set z = 1, then do num_tests runs with random z values */ + + /* Generate the entire group */ + secp256k1_gej_set_infinity(&groupj[0]); + secp256k1_ge_set_gej(&group[0], &groupj[0]); + for (i = 1; i < EXHAUSTIVE_TEST_ORDER; i++) { + /* Set a different random z-value for each Jacobian point */ + secp256k1_fe z; + random_fe(&z); + + secp256k1_gej_add_ge(&groupj[i], &groupj[i - 1], &secp256k1_ge_const_g); + secp256k1_ge_set_gej(&group[i], &groupj[i]); + secp256k1_gej_rescale(&groupj[i], &z); + + /* Verify against ecmult_gen */ + { + secp256k1_scalar scalar_i; + secp256k1_gej generatedj; + secp256k1_ge generated; + + secp256k1_scalar_set_int(&scalar_i, i); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &generatedj, &scalar_i); + secp256k1_ge_set_gej(&generated, &generatedj); + + CHECK(group[i].infinity == 0); + CHECK(generated.infinity == 0); + CHECK(secp256k1_fe_equal_var(&generated.x, &group[i].x)); + CHECK(secp256k1_fe_equal_var(&generated.y, &group[i].y)); + } + } + + /* Run the tests */ +#ifdef USE_ENDOMORPHISM + test_exhaustive_endomorphism(group, EXHAUSTIVE_TEST_ORDER); +#endif + test_exhaustive_addition(group, groupj, EXHAUSTIVE_TEST_ORDER); + test_exhaustive_ecmult(ctx, group, groupj, EXHAUSTIVE_TEST_ORDER); + test_exhaustive_sign(ctx, group, EXHAUSTIVE_TEST_ORDER); + test_exhaustive_verify(ctx, group, EXHAUSTIVE_TEST_ORDER); + +#ifdef ENABLE_MODULE_RECOVERY + test_exhaustive_recovery_sign(ctx, group, EXHAUSTIVE_TEST_ORDER); + test_exhaustive_recovery_verify(ctx, group, EXHAUSTIVE_TEST_ORDER); +#endif + + secp256k1_context_destroy(ctx); + return 0; +} + diff --git a/crypto/secp256k1/libsecp256k1/src/util.h b/crypto/secp256k1/libsecp256k1/src/util.h new file mode 100644 index 0000000..4092a86 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/util.h @@ -0,0 +1,113 @@ +/********************************************************************** + * Copyright (c) 2013, 2014 Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef _SECP256K1_UTIL_H_ +#define _SECP256K1_UTIL_H_ + +#if defined HAVE_CONFIG_H +#include "libsecp256k1-config.h" +#endif + +#include +#include +#include + +typedef struct { + void (*fn)(const char *text, void* data); + const void* data; +} secp256k1_callback; + +static SECP256K1_INLINE void secp256k1_callback_call(const secp256k1_callback * const cb, const char * const text) { + cb->fn(text, (void*)cb->data); +} + +#ifdef DETERMINISTIC +#define TEST_FAILURE(msg) do { \ + fprintf(stderr, "%s\n", msg); \ + abort(); \ +} while(0); +#else +#define TEST_FAILURE(msg) do { \ + fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, msg); \ + abort(); \ +} while(0) +#endif + +#ifdef HAVE_BUILTIN_EXPECT +#define EXPECT(x,c) __builtin_expect((x),(c)) +#else +#define EXPECT(x,c) (x) +#endif + +#ifdef DETERMINISTIC +#define CHECK(cond) do { \ + if (EXPECT(!(cond), 0)) { \ + TEST_FAILURE("test condition failed"); \ + } \ +} while(0) +#else +#define CHECK(cond) do { \ + if (EXPECT(!(cond), 0)) { \ + TEST_FAILURE("test condition failed: " #cond); \ + } \ +} while(0) +#endif + +/* Like assert(), but when VERIFY is defined, and side-effect safe. */ +#if defined(COVERAGE) +#define VERIFY_CHECK(check) +#define VERIFY_SETUP(stmt) +#elif defined(VERIFY) +#define VERIFY_CHECK CHECK +#define VERIFY_SETUP(stmt) do { stmt; } while(0) +#else +#define VERIFY_CHECK(cond) do { (void)(cond); } while(0) +#define VERIFY_SETUP(stmt) +#endif + +static SECP256K1_INLINE void *checked_malloc(const secp256k1_callback* cb, size_t size) { + void *ret = malloc(size); + if (ret == NULL) { + secp256k1_callback_call(cb, "Out of memory"); + } + return ret; +} + +/* Macro for restrict, when available and not in a VERIFY build. */ +#if defined(SECP256K1_BUILD) && defined(VERIFY) +# define SECP256K1_RESTRICT +#else +# if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if SECP256K1_GNUC_PREREQ(3,0) +# define SECP256K1_RESTRICT __restrict__ +# elif (defined(_MSC_VER) && _MSC_VER >= 1400) +# define SECP256K1_RESTRICT __restrict +# else +# define SECP256K1_RESTRICT +# endif +# else +# define SECP256K1_RESTRICT restrict +# endif +#endif + +#if defined(_WIN32) +# define I64FORMAT "I64d" +# define I64uFORMAT "I64u" +#else +# define I64FORMAT "lld" +# define I64uFORMAT "llu" +#endif + +#if defined(HAVE___INT128) +# if defined(__GNUC__) +# define SECP256K1_GNUC_EXT __extension__ +# else +# define SECP256K1_GNUC_EXT +# endif +SECP256K1_GNUC_EXT typedef unsigned __int128 uint128_t; +#endif + +#endif diff --git a/crypto/secp256k1/panic_cb.go b/crypto/secp256k1/panic_cb.go new file mode 100644 index 0000000..a30b04f --- /dev/null +++ b/crypto/secp256k1/panic_cb.go @@ -0,0 +1,24 @@ +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// 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 "C" +import "unsafe" + +// Callbacks for converting libsecp256k1 internal faults into +// recoverable Go panics. + +//export secp256k1GoPanicIllegal +func secp256k1GoPanicIllegal(msg *C.char, data unsafe.Pointer) { + panic("illegal argument: " + C.GoString(msg)) +} + +//export secp256k1GoPanicError +func secp256k1GoPanicError(msg *C.char, data unsafe.Pointer) { + panic("internal error: " + C.GoString(msg)) +} diff --git a/crypto/secp256k1/scalar_mult_cgo.go b/crypto/secp256k1/scalar_mult_cgo.go new file mode 100644 index 0000000..d11c11f --- /dev/null +++ b/crypto/secp256k1/scalar_mult_cgo.go @@ -0,0 +1,53 @@ +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// 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 ( + "math/big" + "unsafe" +) + +/* + +#include "libsecp256k1/include/secp256k1.h" + +extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned char *point, const unsigned char *scalar); + +*/ +import "C" + +func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) { + // Ensure scalar is exactly 32 bytes. We pad always, even if + // scalar is 32 bytes long, to avoid a timing side channel. + if len(scalar) > 32 { + panic("can't handle scalars > 256 bits") + } + // NOTE: potential timing issue + padded := make([]byte, 32) + copy(padded[32-len(scalar):], scalar) + scalar = padded + + // Do the multiplication in C, updating point. + point := make([]byte, 64) + readBits(Bx, point[:32]) + readBits(By, point[32:]) + + pointPtr := (*C.uchar)(unsafe.Pointer(&point[0])) + scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0])) + res := C.secp256k1_ext_scalar_mul(context, pointPtr, scalarPtr) + + // Unpack the result and clear temporaries. + x := new(big.Int).SetBytes(point[:32]) + y := new(big.Int).SetBytes(point[32:]) + clear(point) + clear(scalar) + if res != 1 { + return nil, nil + } + return x, y +} diff --git a/crypto/secp256k1/scalar_mult_nocgo.go b/crypto/secp256k1/scalar_mult_nocgo.go new file mode 100644 index 0000000..feb13a8 --- /dev/null +++ b/crypto/secp256k1/scalar_mult_nocgo.go @@ -0,0 +1,14 @@ +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// 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 "math/big" + +func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) { + panic("ScalarMult is not available when secp256k1 is built without cgo") +} diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go new file mode 100644 index 0000000..61abc1e --- /dev/null +++ b/crypto/secp256k1/secp256.go @@ -0,0 +1,182 @@ +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// 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 wraps the bitcoin secp256k1 C library. +package secp256k1 + +/* +#cgo CFLAGS: -I./libsecp256k1 +#cgo CFLAGS: -I./libsecp256k1/src/ + +#ifdef __SIZEOF_INT128__ +# define HAVE___INT128 +# define USE_FIELD_5X52 +# define USE_SCALAR_4X64 +#else +# define USE_FIELD_10X26 +# define USE_SCALAR_8X32 +#endif + +#ifndef NDEBUG +# define NDEBUG +#endif + +#define USE_ENDOMORPHISM +#define USE_NUM_NONE +#define USE_FIELD_INV_BUILTIN +#define USE_SCALAR_INV_BUILTIN +#include "./libsecp256k1/src/secp256k1.c" +#include "./libsecp256k1/src/modules/recovery/main_impl.h" +#include "ext.h" + +typedef void (*callbackFunc) (const char* msg, void* data); +extern void secp256k1GoPanicIllegal(const char* msg, void* data); +extern void secp256k1GoPanicError(const char* msg, void* data); +*/ +import "C" + +import ( + "errors" + "math/big" + "unsafe" +) + +var context *C.secp256k1_context + +func init() { + // around 20 ms on a modern CPU. + context = C.secp256k1_context_create_sign_verify() + C.secp256k1_context_set_illegal_callback(context, C.callbackFunc(C.secp256k1GoPanicIllegal), nil) + C.secp256k1_context_set_error_callback(context, C.callbackFunc(C.secp256k1GoPanicError), nil) +} + +var ( + ErrInvalidMsgLen = errors.New("invalid message length, need 32 bytes") + ErrInvalidSignatureLen = errors.New("invalid signature length") + ErrInvalidRecoveryID = errors.New("invalid signature recovery id") + ErrInvalidKey = errors.New("invalid private key") + ErrInvalidPubkey = errors.New("invalid public key") + ErrSignFailed = errors.New("signing failed") + ErrRecoverFailed = errors.New("recovery failed") +) + +// Sign creates a recoverable ECDSA signature. +// The produced signature is in the 65-byte [R || S || V] format where V is 0 or 1. +// +// The caller is responsible for ensuring that msg cannot be chosen +// directly by an attacker. It is usually preferable to use a cryptographic +// hash function on any input before handing it to this function. +func Sign(msg []byte, seckey []byte) ([]byte, error) { + if len(msg) != 32 { + return nil, ErrInvalidMsgLen + } + if len(seckey) != 32 { + return nil, ErrInvalidKey + } + seckeydata := (*C.uchar)(unsafe.Pointer(&seckey[0])) + if C.secp256k1_ec_seckey_verify(context, seckeydata) != 1 { + return nil, ErrInvalidKey + } + + var ( + msgdata = (*C.uchar)(unsafe.Pointer(&msg[0])) + noncefunc = C.secp256k1_nonce_function_rfc6979 + sigstruct C.secp256k1_ecdsa_recoverable_signature + ) + if C.secp256k1_ecdsa_sign_recoverable(context, &sigstruct, msgdata, seckeydata, noncefunc, nil) == 0 { + return nil, ErrSignFailed + } + + var ( + sig = make([]byte, 65) + sigdata = (*C.uchar)(unsafe.Pointer(&sig[0])) + recid C.int + ) + C.secp256k1_ecdsa_recoverable_signature_serialize_compact(context, sigdata, &recid, &sigstruct) + sig[64] = byte(recid) // add back recid to get 65 bytes sig + return sig, nil +} + +// RecoverPubkey returns the public key of the signer. +// msg must be the 32-byte hash of the message to be signed. +// sig must be a 65-byte compact ECDSA signature containing the +// recovery id as the last element. +func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) { + if len(msg) != 32 { + return nil, ErrInvalidMsgLen + } + if err := checkSignature(sig); err != nil { + return nil, err + } + + var ( + pubkey = make([]byte, 65) + sigdata = (*C.uchar)(unsafe.Pointer(&sig[0])) + msgdata = (*C.uchar)(unsafe.Pointer(&msg[0])) + ) + if C.secp256k1_ext_ecdsa_recover(context, (*C.uchar)(unsafe.Pointer(&pubkey[0])), sigdata, msgdata) == 0 { + return nil, ErrRecoverFailed + } + return pubkey, nil +} + +// VerifySignature checks that the given pubkey created signature over message. +// The signature should be in [R || S] format. +func VerifySignature(pubkey, msg, signature []byte) bool { + if len(msg) != 32 || len(signature) != 64 || len(pubkey) == 0 { + return false + } + sigdata := (*C.uchar)(unsafe.Pointer(&signature[0])) + msgdata := (*C.uchar)(unsafe.Pointer(&msg[0])) + keydata := (*C.uchar)(unsafe.Pointer(&pubkey[0])) + return C.secp256k1_ext_ecdsa_verify(context, sigdata, msgdata, keydata, C.size_t(len(pubkey))) != 0 +} + +// DecompressPubkey parses a public key in the 33-byte compressed format. +// It returns non-nil coordinates if the public key is valid. +func DecompressPubkey(pubkey []byte) (x, y *big.Int) { + if len(pubkey) != 33 { + return nil, nil + } + var ( + pubkeydata = (*C.uchar)(unsafe.Pointer(&pubkey[0])) + pubkeylen = C.size_t(len(pubkey)) + out = make([]byte, 65) + outdata = (*C.uchar)(unsafe.Pointer(&out[0])) + outlen = C.size_t(len(out)) + ) + if C.secp256k1_ext_reencode_pubkey(context, outdata, outlen, pubkeydata, pubkeylen) == 0 { + return nil, nil + } + return new(big.Int).SetBytes(out[1:33]), new(big.Int).SetBytes(out[33:]) +} + +// CompressPubkey encodes a public key to 33-byte compressed format. +func CompressPubkey(x, y *big.Int) []byte { + var ( + pubkey = S256().Marshal(x, y) + pubkeydata = (*C.uchar)(unsafe.Pointer(&pubkey[0])) + pubkeylen = C.size_t(len(pubkey)) + out = make([]byte, 33) + outdata = (*C.uchar)(unsafe.Pointer(&out[0])) + outlen = C.size_t(len(out)) + ) + if C.secp256k1_ext_reencode_pubkey(context, outdata, outlen, pubkeydata, pubkeylen) == 0 { + panic("libsecp256k1 error") + } + return out +} + +func checkSignature(sig []byte) error { + if len(sig) != 65 { + return ErrInvalidSignatureLen + } + if sig[64] >= 4 { + return ErrInvalidRecoveryID + } + return nil +} diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go new file mode 100644 index 0000000..4827cc5 --- /dev/null +++ b/crypto/secp256k1/secp256_test.go @@ -0,0 +1,240 @@ +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// 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 ( + "bytes" + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" + "io" + "testing" +) + +const TestCount = 1000 + +func generateKeyPair() (pubkey, privkey []byte) { + key, err := ecdsa.GenerateKey(S256(), rand.Reader) + if err != nil { + panic(err) + } + pubkey = S256().Marshal(key.X, key.Y) + + privkey = make([]byte, 32) + blob := key.D.Bytes() + copy(privkey[32-len(blob):], blob) + + return pubkey, privkey +} + +func csprngEntropy(n int) []byte { + buf := make([]byte, n) + if _, err := io.ReadFull(rand.Reader, buf); err != nil { + panic("reading from crypto/rand failed: " + err.Error()) + } + return buf +} + +func randSig() []byte { + sig := csprngEntropy(65) + sig[32] &= 0x70 + sig[64] %= 4 + return sig +} + +// tests for malleability +// the highest bit of signature ECDSA s value must be 0, in the 33th byte +func compactSigCheck(t *testing.T, sig []byte) { + var b = int(sig[32]) + if b < 0 { + t.Errorf("highest bit is negative: %d", b) + } + if ((b >> 7) == 1) != ((b & 0x80) == 0x80) { + t.Errorf("highest bit: %d bit >> 7: %d", b, b>>7) + } + if (b & 0x80) == 0x80 { + t.Errorf("highest bit: %d bit & 0x80: %d", b, b&0x80) + } +} + +func TestSignatureValidity(t *testing.T) { + pubkey, seckey := generateKeyPair() + msg := csprngEntropy(32) + sig, err := Sign(msg, seckey) + if err != nil { + t.Errorf("signature error: %s", err) + } + compactSigCheck(t, sig) + if len(pubkey) != 65 { + t.Errorf("pubkey length mismatch: want: 65 have: %d", len(pubkey)) + } + if len(seckey) != 32 { + t.Errorf("seckey length mismatch: want: 32 have: %d", len(seckey)) + } + if len(sig) != 65 { + t.Errorf("sig length mismatch: want: 65 have: %d", len(sig)) + } + recid := int(sig[64]) + if recid > 4 || recid < 0 { + t.Errorf("sig recid mismatch: want: within 0 to 4 have: %d", int(sig[64])) + } +} + +func TestInvalidRecoveryID(t *testing.T) { + _, seckey := generateKeyPair() + msg := csprngEntropy(32) + sig, _ := Sign(msg, seckey) + sig[64] = 99 + _, err := RecoverPubkey(msg, sig) + if err != ErrInvalidRecoveryID { + t.Fatalf("got %q, want %q", err, ErrInvalidRecoveryID) + } +} + +func TestSignAndRecover(t *testing.T) { + pubkey1, seckey := generateKeyPair() + msg := csprngEntropy(32) + sig, err := Sign(msg, seckey) + if err != nil { + t.Errorf("signature error: %s", err) + } + pubkey2, err := RecoverPubkey(msg, sig) + if err != nil { + t.Errorf("recover error: %s", err) + } + if !bytes.Equal(pubkey1, pubkey2) { + t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) + } +} + +func TestSignDeterministic(t *testing.T) { + _, seckey := generateKeyPair() + msg := make([]byte, 32) + copy(msg, "hi there") + + sig1, err := Sign(msg, seckey) + if err != nil { + t.Fatal(err) + } + sig2, err := Sign(msg, seckey) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sig1, sig2) { + t.Fatal("signatures not equal") + } +} + +func TestRandomMessagesWithSameKey(t *testing.T) { + pubkey, seckey := generateKeyPair() + keys := func() ([]byte, []byte) { + return pubkey, seckey + } + signAndRecoverWithRandomMessages(t, keys) +} + +func TestRandomMessagesWithRandomKeys(t *testing.T) { + keys := func() ([]byte, []byte) { + pubkey, seckey := generateKeyPair() + return pubkey, seckey + } + signAndRecoverWithRandomMessages(t, keys) +} + +func signAndRecoverWithRandomMessages(t *testing.T, keys func() ([]byte, []byte)) { + for i := 0; i < TestCount; i++ { + pubkey1, seckey := keys() + msg := csprngEntropy(32) + sig, err := Sign(msg, seckey) + if err != nil { + t.Fatalf("signature error: %s", err) + } + if sig == nil { + t.Fatal("signature is nil") + } + compactSigCheck(t, sig) + + // TODO: why do we flip around the recovery id? + sig[len(sig)-1] %= 4 + + pubkey2, err := RecoverPubkey(msg, sig) + if err != nil { + t.Fatalf("recover error: %s", err) + } + if pubkey2 == nil { + t.Error("pubkey is nil") + } + if !bytes.Equal(pubkey1, pubkey2) { + t.Fatalf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) + } + } +} + +func TestRecoveryOfRandomSignature(t *testing.T) { + pubkey1, _ := generateKeyPair() + msg := csprngEntropy(32) + + for i := 0; i < TestCount; i++ { + // recovery can sometimes work, but if so should always give wrong pubkey + pubkey2, _ := RecoverPubkey(msg, randSig()) + if bytes.Equal(pubkey1, pubkey2) { + t.Fatalf("iteration: %d: pubkey mismatch: do NOT want %x: ", i, pubkey2) + } + } +} + +func TestRandomMessagesAgainstValidSig(t *testing.T) { + pubkey1, seckey := generateKeyPair() + msg := csprngEntropy(32) + sig, _ := Sign(msg, seckey) + + for i := 0; i < TestCount; i++ { + msg = csprngEntropy(32) + pubkey2, _ := RecoverPubkey(msg, sig) + // recovery can sometimes work, but if so should always give wrong pubkey + if bytes.Equal(pubkey1, pubkey2) { + t.Fatalf("iteration: %d: pubkey mismatch: do NOT want %x: ", i, pubkey2) + } + } +} + +// Useful when the underlying libsecp256k1 API changes to quickly +// check only recover function without use of signature function +func TestRecoverSanity(t *testing.T) { + msg, _ := hex.DecodeString("ce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008") + sig, _ := hex.DecodeString("90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301") + pubkey1, _ := hex.DecodeString("04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652") + pubkey2, err := RecoverPubkey(msg, sig) + if err != nil { + t.Fatalf("recover error: %s", err) + } + if !bytes.Equal(pubkey1, pubkey2) { + t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2) + } +} + +func BenchmarkSign(b *testing.B) { + _, seckey := generateKeyPair() + msg := csprngEntropy(32) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + Sign(msg, seckey) + } +} + +func BenchmarkRecover(b *testing.B) { + msg := csprngEntropy(32) + _, seckey := generateKeyPair() + sig, _ := Sign(msg, seckey) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + RecoverPubkey(msg, sig) + } +} diff --git a/crypto/signature_cgo.go b/crypto/signature_cgo.go new file mode 100644 index 0000000..8728925 --- /dev/null +++ b/crypto/signature_cgo.go @@ -0,0 +1,86 @@ +// 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 . + +//go:build !nacl && !js && cgo && !gofuzz +// +build !nacl,!js,cgo,!gofuzz + +package crypto + +import ( + "crypto/ecdsa" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto/secp256k1" +) + +// Ecrecover returns the uncompressed public key that created the given signature. +func Ecrecover(hash, sig []byte) ([]byte, error) { + return secp256k1.RecoverPubkey(hash, sig) +} + +// SigToPub returns the public key that created the given signature. +func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { + s, err := Ecrecover(hash, sig) + if err != nil { + return nil, err + } + return UnmarshalPubkey(s) +} + +// Sign calculates an ECDSA signature. +// +// This function is susceptible to chosen plaintext attacks that can leak +// information about the private key that is used for signing. Callers must +// be aware that the given digest cannot be chosen by an adversary. Common +// solution is to hash any input before calculating the signature. +// +// The produced signature is in the [R || S || V] format where V is 0 or 1. +func Sign(digestHash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { + if len(digestHash) != DigestLength { + return nil, fmt.Errorf("hash is required to be exactly %d bytes (%d)", DigestLength, len(digestHash)) + } + seckey := math.PaddedBigBytes(prv.D, prv.Params().BitSize/8) + defer zeroBytes(seckey) + return secp256k1.Sign(digestHash, seckey) +} + +// VerifySignature checks that the given public key created signature over digest. +// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format. +// The signature should have the 64 byte [R || S] format. +func VerifySignature(pubkey, digestHash, signature []byte) bool { + return secp256k1.VerifySignature(pubkey, digestHash, signature) +} + +// DecompressPubkey parses a public key in the 33-byte compressed format. +func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) { + x, y := secp256k1.DecompressPubkey(pubkey) + if x == nil { + return nil, errors.New("invalid public key") + } + return &ecdsa.PublicKey{X: x, Y: y, Curve: S256()}, nil +} + +// CompressPubkey encodes a public key to the 33-byte compressed format. +func CompressPubkey(pubkey *ecdsa.PublicKey) []byte { + return secp256k1.CompressPubkey(pubkey.X, pubkey.Y) +} + +// S256 returns an instance of the secp256k1 curve. +func S256() EllipticCurve { + return secp256k1.S256() +} diff --git a/crypto/signature_nocgo.go b/crypto/signature_nocgo.go new file mode 100644 index 0000000..9890574 --- /dev/null +++ b/crypto/signature_nocgo.go @@ -0,0 +1,196 @@ +// 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 . + +//go:build nacl || js || !cgo || gofuzz +// +build nacl js !cgo gofuzz + +package crypto + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + + "github.com/btcsuite/btcd/btcec/v2" + btc_ecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa" +) + +// Ecrecover returns the uncompressed public key that created the given signature. +func Ecrecover(hash, sig []byte) ([]byte, error) { + pub, err := sigToPub(hash, sig) + if err != nil { + return nil, err + } + bytes := pub.SerializeUncompressed() + return bytes, err +} + +func sigToPub(hash, sig []byte) (*btcec.PublicKey, error) { + if len(sig) != SignatureLength { + return nil, errors.New("invalid signature") + } + // Convert to btcec input format with 'recovery id' v at the beginning. + btcsig := make([]byte, SignatureLength) + btcsig[0] = sig[RecoveryIDOffset] + 27 + copy(btcsig[1:], sig) + + pub, _, err := btc_ecdsa.RecoverCompact(btcsig, hash) + return pub, err +} + +// SigToPub returns the public key that created the given signature. +func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { + pub, err := sigToPub(hash, sig) + if err != nil { + return nil, err + } + // We need to explicitly set the curve here, because we're wrapping + // the original curve to add (un-)marshalling + return &ecdsa.PublicKey{ + Curve: S256(), + X: pub.X(), + Y: pub.Y(), + }, nil +} + +// Sign calculates an ECDSA signature. +// +// This function is susceptible to chosen plaintext attacks that can leak +// information about the private key that is used for signing. Callers must +// be aware that the given hash cannot be chosen by an adversary. Common +// solution is to hash any input before calculating the signature. +// +// The produced signature is in the [R || S || V] format where V is 0 or 1. +func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) { + if len(hash) != 32 { + return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash)) + } + if prv.Curve != S256() { + return nil, errors.New("private key curve is not secp256k1") + } + // ecdsa.PrivateKey -> btcec.PrivateKey + var priv btcec.PrivateKey + if overflow := priv.Key.SetByteSlice(prv.D.Bytes()); overflow || priv.Key.IsZero() { + return nil, errors.New("invalid private key") + } + defer priv.Zero() + sig, err := btc_ecdsa.SignCompact(&priv, hash, false) // ref uncompressed pubkey + if err != nil { + return nil, err + } + // Convert to Ethereum signature format with 'recovery id' v at the end. + v := sig[0] - 27 + copy(sig, sig[1:]) + sig[RecoveryIDOffset] = v + return sig, nil +} + +// VerifySignature checks that the given public key created signature over hash. +// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format. +// The signature should have the 64 byte [R || S] format. +func VerifySignature(pubkey, hash, signature []byte) bool { + if len(signature) != 64 { + return false + } + var r, s btcec.ModNScalar + if r.SetByteSlice(signature[:32]) { + return false // overflow + } + if s.SetByteSlice(signature[32:]) { + return false + } + sig := btc_ecdsa.NewSignature(&r, &s) + key, err := btcec.ParsePubKey(pubkey) + if err != nil { + return false + } + // Reject malleable signatures. libsecp256k1 does this check but btcec doesn't. + if s.IsOverHalfOrder() { + return false + } + return sig.Verify(hash, key) +} + +// DecompressPubkey parses a public key in the 33-byte compressed format. +func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) { + if len(pubkey) != 33 { + return nil, errors.New("invalid compressed public key length") + } + key, err := btcec.ParsePubKey(pubkey) + if err != nil { + return nil, err + } + // We need to explicitly set the curve here, because we're wrapping + // the original curve to add (un-)marshalling + return &ecdsa.PublicKey{ + Curve: S256(), + X: key.X(), + Y: key.Y(), + }, nil +} + +// CompressPubkey encodes a public key to the 33-byte compressed format. The +// provided PublicKey must be valid. Namely, the coordinates must not be larger +// than 32 bytes each, they must be less than the field prime, and it must be a +// point on the secp256k1 curve. This is the case for a PublicKey constructed by +// elliptic.Unmarshal (see UnmarshalPubkey), or by ToECDSA and ecdsa.GenerateKey +// when constructing a PrivateKey. +func CompressPubkey(pubkey *ecdsa.PublicKey) []byte { + // NOTE: the coordinates may be validated with + // btcec.ParsePubKey(FromECDSAPub(pubkey)) + var x, y btcec.FieldVal + x.SetByteSlice(pubkey.X.Bytes()) + y.SetByteSlice(pubkey.Y.Bytes()) + return btcec.NewPublicKey(&x, &y).SerializeCompressed() +} + +// S256 returns an instance of the secp256k1 curve. +func S256() EllipticCurve { + return btCurve{btcec.S256()} +} + +type btCurve struct { + *btcec.KoblitzCurve +} + +// Marshal converts a point given as (x, y) into a byte slice. +func (curve btCurve) Marshal(x, y *big.Int) []byte { + byteLen := (curve.Params().BitSize + 7) / 8 + + ret := make([]byte, 1+2*byteLen) + ret[0] = 4 // uncompressed point + + x.FillBytes(ret[1 : 1+byteLen]) + y.FillBytes(ret[1+byteLen : 1+2*byteLen]) + + return ret +} + +// Unmarshal converts a point, serialised by Marshal, into an x, y pair. On +// error, x = nil. +func (curve btCurve) Unmarshal(data []byte) (x, y *big.Int) { + byteLen := (curve.Params().BitSize + 7) / 8 + if len(data) != 1+2*byteLen { + return nil, nil + } + if data[0] != 4 { // uncompressed form + return nil, nil + } + x = new(big.Int).SetBytes(data[1 : 1+byteLen]) + y = new(big.Int).SetBytes(data[1+byteLen:]) + return +} diff --git a/crypto/signature_test.go b/crypto/signature_test.go new file mode 100644 index 0000000..74d683b --- /dev/null +++ b/crypto/signature_test.go @@ -0,0 +1,160 @@ +// 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 crypto + +import ( + "bytes" + "crypto/ecdsa" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" +) + +var ( + testmsg = hexutil.MustDecode("0xce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008") + testsig = hexutil.MustDecode("0x90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301") + testpubkey = hexutil.MustDecode("0x04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652") + testpubkeyc = hexutil.MustDecode("0x02e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a") +) + +func TestEcrecover(t *testing.T) { + pubkey, err := Ecrecover(testmsg, testsig) + if err != nil { + t.Fatalf("recover error: %s", err) + } + if !bytes.Equal(pubkey, testpubkey) { + t.Errorf("pubkey mismatch: want: %x have: %x", testpubkey, pubkey) + } +} + +func TestVerifySignature(t *testing.T) { + sig := testsig[:len(testsig)-1] // remove recovery id + if !VerifySignature(testpubkey, testmsg, sig) { + t.Errorf("can't verify signature with uncompressed key") + } + if !VerifySignature(testpubkeyc, testmsg, sig) { + t.Errorf("can't verify signature with compressed key") + } + + if VerifySignature(nil, testmsg, sig) { + t.Errorf("signature valid with no key") + } + if VerifySignature(testpubkey, nil, sig) { + t.Errorf("signature valid with no message") + } + if VerifySignature(testpubkey, testmsg, nil) { + t.Errorf("nil signature valid") + } + if VerifySignature(testpubkey, testmsg, append(common.CopyBytes(sig), 1, 2, 3)) { + t.Errorf("signature valid with extra bytes at the end") + } + if VerifySignature(testpubkey, testmsg, sig[:len(sig)-2]) { + t.Errorf("signature valid even though it's incomplete") + } + wrongkey := common.CopyBytes(testpubkey) + wrongkey[10]++ + if VerifySignature(wrongkey, testmsg, sig) { + t.Errorf("signature valid with wrong public key") + } +} + +// This test checks that VerifySignature rejects malleable signatures with s > N/2. +func TestVerifySignatureMalleable(t *testing.T) { + sig := hexutil.MustDecode("0x638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454") + key := hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138") + msg := hexutil.MustDecode("0xd301ce462d3e639518f482c7f03821fec1e602018630ce621e1e7851c12343a6") + if VerifySignature(key, msg, sig) { + t.Error("VerifySignature returned true for malleable signature") + } +} + +func TestDecompressPubkey(t *testing.T) { + key, err := DecompressPubkey(testpubkeyc) + if err != nil { + t.Fatal(err) + } + if uncompressed := FromECDSAPub(key); !bytes.Equal(uncompressed, testpubkey) { + t.Errorf("wrong public key result: got %x, want %x", uncompressed, testpubkey) + } + if _, err := DecompressPubkey(nil); err == nil { + t.Errorf("no error for nil pubkey") + } + if _, err := DecompressPubkey(testpubkeyc[:5]); err == nil { + t.Errorf("no error for incomplete pubkey") + } + if _, err := DecompressPubkey(append(common.CopyBytes(testpubkeyc), 1, 2, 3)); err == nil { + t.Errorf("no error for pubkey with extra bytes at the end") + } +} + +func TestCompressPubkey(t *testing.T) { + key := &ecdsa.PublicKey{ + Curve: S256(), + X: math.MustParseBig256("0xe32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a"), + Y: math.MustParseBig256("0x0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652"), + } + compressed := CompressPubkey(key) + if !bytes.Equal(compressed, testpubkeyc) { + t.Errorf("wrong public key result: got %x, want %x", compressed, testpubkeyc) + } +} + +func TestPubkeyRandom(t *testing.T) { + const runs = 200 + + for i := 0; i < runs; i++ { + key, err := GenerateKey() + if err != nil { + t.Fatalf("iteration %d: %v", i, err) + } + pubkey2, err := DecompressPubkey(CompressPubkey(&key.PublicKey)) + if err != nil { + t.Fatalf("iteration %d: %v", i, err) + } + if !reflect.DeepEqual(key.PublicKey, *pubkey2) { + t.Fatalf("iteration %d: keys not equal", i) + } + } +} + +func BenchmarkEcrecoverSignature(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := Ecrecover(testmsg, testsig); err != nil { + b.Fatal("ecrecover error", err) + } + } +} + +func BenchmarkVerifySignature(b *testing.B) { + sig := testsig[:len(testsig)-1] // remove recovery id + for i := 0; i < b.N; i++ { + if !VerifySignature(testpubkey, testmsg, sig) { + b.Fatal("verify error") + } + } +} + +func BenchmarkDecompressPubkey(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := DecompressPubkey(testpubkeyc); err != nil { + b.Fatal(err) + } + } +} diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go new file mode 100644 index 0000000..eb029e5 --- /dev/null +++ b/crypto/signify/signify.go @@ -0,0 +1,100 @@ +// 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 . + +// signFile reads the contents of an input file and signs it (in armored format) +// with the key provided, placing the signature into the output file. + +package signify + +import ( + "bytes" + "crypto/ed25519" + "encoding/base64" + "errors" + "fmt" + "os" + "strings" + "time" +) + +var ( + errInvalidKeyHeader = errors.New("incorrect key header") + errInvalidKeyLength = errors.New("invalid, key length != 104") +) + +func parsePrivateKey(key string) (k ed25519.PrivateKey, header []byte, keyNum []byte, err error) { + keydata, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, nil, nil, err + } + if len(keydata) != 104 { + return nil, nil, nil, errInvalidKeyLength + } + if string(keydata[:2]) != "Ed" { + return nil, nil, nil, errInvalidKeyHeader + } + return keydata[40:], keydata[:2], keydata[32:40], nil +} + +// SignFile creates a signature of the input file. +// +// This accepts base64 keys in the format created by the 'signify' tool. +// The signature is written to the 'output' file. +func SignFile(input string, output string, key string, untrustedComment string, trustedComment string) error { + // Pre-check comments and ensure they're set to something. + if strings.IndexByte(untrustedComment, '\n') >= 0 { + return errors.New("untrusted comment must not contain newline") + } + if strings.IndexByte(trustedComment, '\n') >= 0 { + return errors.New("trusted comment must not contain newline") + } + if untrustedComment == "" { + untrustedComment = "verify with " + input + ".pub" + } + if trustedComment == "" { + trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) + } + + filedata, err := os.ReadFile(input) + if err != nil { + return err + } + skey, header, keyNum, err := parsePrivateKey(key) + if err != nil { + return err + } + + // Create the main data signature. + rawSig := ed25519.Sign(skey, filedata) + var dataSig []byte + dataSig = append(dataSig, header...) + dataSig = append(dataSig, keyNum...) + dataSig = append(dataSig, rawSig...) + + // Create the comment signature. + var commentSigInput []byte + commentSigInput = append(commentSigInput, rawSig...) + commentSigInput = append(commentSigInput, []byte(trustedComment)...) + commentSig := ed25519.Sign(skey, commentSigInput) + + // Create the output file. + var out = new(bytes.Buffer) + fmt.Fprintln(out, "untrusted comment:", untrustedComment) + fmt.Fprintln(out, base64.StdEncoding.EncodeToString(dataSig)) + fmt.Fprintln(out, "trusted comment:", trustedComment) + fmt.Fprintln(out, base64.StdEncoding.EncodeToString(commentSig)) + return os.WriteFile(output, out.Bytes(), 0644) +} diff --git a/crypto/signify/signify_fuzz.go b/crypto/signify/signify_fuzz.go new file mode 100644 index 0000000..239a213 --- /dev/null +++ b/crypto/signify/signify_fuzz.go @@ -0,0 +1,151 @@ +// 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 . + +//go:build gofuzz +// +build gofuzz + +package signify + +import ( + "bufio" + "fmt" + "log" + "os" + "os/exec" + + fuzz "github.com/google/gofuzz" + "github.com/jedisct1/go-minisign" +) + +func Fuzz(data []byte) int { + if len(data) < 32 { + return -1 + } + tmpFile, err := os.CreateTemp("", "") + if err != nil { + panic(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + testSecKey, testPubKey := createKeyPair() + // Create message + tmpFile.Write(data) + if err = tmpFile.Close(); err != nil { + panic(err) + } + // Fuzz comments + var untrustedComment string + var trustedComment string + f := fuzz.NewFromGoFuzz(data) + f.Fuzz(&untrustedComment) + f.Fuzz(&trustedComment) + fmt.Printf("untrusted: %v\n", untrustedComment) + fmt.Printf("trusted: %v\n", trustedComment) + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment) + if err != nil { + panic(err) + } + defer os.Remove(tmpFile.Name() + ".sig") + + signify := "signify" + path := os.Getenv("SIGNIFY") + if path != "" { + signify = path + } + + _, err := exec.LookPath(signify) + if err != nil { + panic(err) + } + + // Write the public key into the file to pass it as + // an argument to signify-openbsd + pubKeyFile, err := os.CreateTemp("", "") + if err != nil { + panic(err) + } + defer os.Remove(pubKeyFile.Name()) + defer pubKeyFile.Close() + pubKeyFile.WriteString("untrusted comment: signify public key\n") + pubKeyFile.WriteString(testPubKey) + pubKeyFile.WriteString("\n") + + cmd := exec.Command(signify, "-V", "-p", pubKeyFile.Name(), "-x", tmpFile.Name()+".sig", "-m", tmpFile.Name()) + if output, err := cmd.CombinedOutput(); err != nil { + panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) + } + + // Verify the signature using a golang library + sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig") + if err != nil { + panic(err) + } + + pKey, err := minisign.NewPublicKey(testPubKey) + if err != nil { + panic(err) + } + + valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig) + if err != nil { + panic(err) + } + if !valid { + panic("invalid signature") + } + return 1 +} + +func getKey(fileS string) (string, error) { + file, err := os.Open(fileS) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + // Discard the first line + scanner.Scan() + scanner.Scan() + return scanner.Text(), scanner.Err() +} + +func createKeyPair() (string, string) { + // Create key and put it in correct format + tmpKey, err := os.CreateTemp("", "") + if err != nil { + panic(err) + } + defer os.Remove(tmpKey.Name()) + defer os.Remove(tmpKey.Name() + ".pub") + defer os.Remove(tmpKey.Name() + ".sec") + defer tmpKey.Close() + cmd := exec.Command("signify", "-G", "-n", "-p", tmpKey.Name()+".pub", "-s", tmpKey.Name()+".sec") + if output, err := cmd.CombinedOutput(); err != nil { + panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) + } + secKey, err := getKey(tmpKey.Name() + ".sec") + if err != nil { + panic(err) + } + pubKey, err := getKey(tmpKey.Name() + ".pub") + if err != nil { + panic(err) + } + return secKey, pubKey +} diff --git a/crypto/signify/signify_test.go b/crypto/signify/signify_test.go new file mode 100644 index 0000000..9bac2c8 --- /dev/null +++ b/crypto/signify/signify_test.go @@ -0,0 +1,144 @@ +// 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 . + +// signFile reads the contents of an input file and signs it (in armored format) +// with the key provided, placing the signature into the output file. + +package signify + +import ( + "crypto/rand" + "os" + "testing" + + "github.com/jedisct1/go-minisign" +) + +var ( + testSecKey = "RWRCSwAAAABVN5lr2JViGBN8DhX3/Qb/0g0wBdsNAR/APRW2qy9Fjsfr12sK2cd3URUFis1jgzQzaoayK8x4syT4G3Gvlt9RwGIwUYIQW/0mTeI+ECHu1lv5U4Wa2YHEPIesVPyRm5M=" + testPubKey = "RWTAPRW2qy9FjsBiMFGCEFv9Jk3iPhAh7tZb+VOFmtmBxDyHrFT8kZuT" +) + +func TestSignify(t *testing.T) { + tmpFile, err := os.CreateTemp("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") + + // Verify the signature using a golang library + sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig") + if err != nil { + t.Fatal(err) + } + + pKey, err := minisign.NewPublicKey(testPubKey) + if err != nil { + t.Fatal(err) + } + + valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig) + if err != nil { + t.Fatal(err) + } + if !valid { + t.Fatal("invalid signature") + } +} + +func TestSignifyTrustedCommentTooManyLines(t *testing.T) { + tmpFile, err := os.CreateTemp("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants") + if err == nil || err.Error() == "" { + t.Fatalf("should have errored on a multi-line trusted comment, got %v", err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} + +func TestSignifyTrustedCommentTooManyLinesLF(t *testing.T) { + tmpFile, err := os.CreateTemp("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} + +func TestSignifyTrustedCommentEmpty(t *testing.T) { + tmpFile, err := os.CreateTemp("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} diff --git a/docs/audits/2017-04-25_Geth-audit_Truesec.pdf b/docs/audits/2017-04-25_Geth-audit_Truesec.pdf new file mode 100644 index 0000000000000000000000000000000000000000..702dc99567f3240a94fcfef5d2d8be44b0d167c5 GIT binary patch literal 124427 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

%C|-5`w-Zm|nrEd?zM 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

#rW7ck*D(TCon3>{%aas-^4(q!A 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 z

    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 literal 0 HcmV?d00001 diff --git a/docs/audits/2018-09-14_Clef-audit_NCC.pdf b/docs/audits/2018-09-14_Clef-audit_NCC.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1a5c66f178af6dc73289a4575a0d982d411e664e GIT binary patch literal 755237 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 vxVb!Sgi7UMD_dDRb2Zsn#GLcQ2|IdZl6< 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%0A#+gYi9g9nm)QjA;=rVZweu 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 i-ff#rMWOrm~Ssg`tq$+ zyL>lOagLj1KcTW*yc%6xSD$_#WP%LOS;;B=m3gH=z< zS=k57)qY+T(~3)uyVuWv$#D~QgF1Y`rHki2#VyPOx$PKQ$A@U@pvC}36)Jv0on9~c znPh%CGa=Tn!0mzg8|)UTwf__AX732SnR1{eVqG|`ZB$kk1rV)exS_(?CMY>c&OW|3 z(rS3QWZWRgzVu;0y6_kLYyE{f-J9J*0z4Yjjo&^s2}jnS5C=5EhI%%=($R`TM@juN zAg<0Evc@8HI$!9{JP^PzGO-V+pNj)Tovn0VvAeMYBW_~HEA!jK?bNKYH5<4oPh@ja zbXyls#t#z5lUkJYdX|wY;T*4$JqrXR@_@byBegyNWOujYzBWfdQWq+i>h8=)6!FxlIda10 z*Lq$7MO$=PdV>9gP9f?@FHO9hiGaMwD0{_i=(qwZKa<$-cVBFs~V83V9e zjcbE)!78%d`FDd(Eep}8vTp&NZJLXf=?Q}>YxU9$n&^Ou?iLPJ@ob4OCgcG}cy25H z>wr@3ypOR_uP5#(PDN=J%5u#21Egw7-38=*Mg-(Z5@nNF8=@y(2k>fYUV>`?PFq!Z zz>r!V%gGzru9U1yf zOxSj0bFKnYoG}t~90lkLW{8Rv?<^gji7fJIcrMY02?>S4RSv~ZpccnzQ+U3e&P|NBftfH!nn0i zC)aU0HQoe_#|DhQF)&v^>ANiHnaCv|<6Qhg2Td1DAppmz#@F*e7XePCk`m{FHq_I@ z#7b?MI;0ajN6`V^9ewi*vc%3^GN8+i*N&`}{en63SY?QrbfG=WH!z zIip?(lG83;@YAuQky}FibM((o%0Dpl1)Z=UbgNS6?j}C<0465i?W7LW`gdLl6XpXt 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%0ARv$M-RxF5>ytPGZ66OPJ>CP!}vrUmbEo)vaVJ~ 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 r2y&<+1 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∓6IPzlZpho!}#%yVv-E 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�)&21umJ?XH`Mk@vHbgI=#R=PAUu#{Xc!U(|ZV%s&-_)lr5g`Bz|xvj8T1lwMYH|;gR@i@)PQTrabS-8gBExv8K?N0vzoCeJ=oECc51C4T!{FjlE z!x9stM#XFa7HJlD2r#0;rRE0&rd8I`(5y_3Jc~=+NIZj6>+`JK7~I-sqS{{`DfGkJH2fIxY)?N{--NghKKokazRAA``@^0LkLNzopg zsf=^*2Q&#pj3gQ2q)|^N7+mo}^D>`qyJOS{2^2+g-d+AQBpc*_y*{%^M#+~A4uU~y z0(@XtIslB~)Ok4;LCj)Y)RYpZlZg{3V`nNmJZ-7qY<&fU>vglKCTA-w>cwqnVaIlO z>pt{EZS5a|rAjSYIFSw7TdmADiS6ZMf0hT1QrkZ?%r4I!&f9Rnk&=#A#i)h=b-0F* zf*aLG?-~%ET5q}0fo+u5N0iQ89MElJ5qZlM1>}O3P0DNLV_)sOm6zvglI|O*5r7Wk zrnbV>8g2e4#g&bi^Q#zr#9qfVE=k$eC2XUGrB#hY*KA` zf;=!^*QFySUAK25!9LJjh9Fsk$06i^su#3-V2b9Kr=-0ZSeTLt4oTnJ*oM-(`^*A6 z2&S?Y^RDJ>Zt5-<%^R>w^WM~>7~|=d^qGuk!2T_4G-b4zSDRKQ)W>s?!Mm$vw>5jd zWUc8aX=2SMZ2{PnX$keBS&W68vvbI!QgXtxPmdHry1XSi{9H=5e2qF-B&YL#3R;A|Ego`o% zwCpdNZ{#T<4{k=flN%Lh!Eo$A&vAwr4hHQsKJMKU1DmHZ>j=R;CJ50byB~X2rk0CG zrN=^5{ysB%Fzu~)NZOly)Ll9`wA=B%Xlv{w5SqQ2xl}%BQJOjL-Ze#c;(g407<(}O z*}2^?hi0Sc`xVB#4zrVucZAQ0p6;w3-Edw`T#vh+VS9Fu!I-NwR#1{$Eur7}w6U>c zFiD|+S{EqsG$NL>^_@d4su&>bzj8S|3L0u4`c27S3)q3w{M)B&igYD$2X?mK4~}0I zVkLRNbi5{FHJ@34!)*FAt9ZnP&oRaM6e%5TO^AaV&6-qY(~bDqd1vD|sFWTD0mW+y z_qi!P6{gow)QXaHsJE=FXJHk(YLRKNMEtkUCQOrOGxl#+T_BGil)Ki%=4!*=uA#@? zY*+)&*)rA8F4+VgUM}d~DsXBr>{mx}E0w1JE=#0#^^0rje%;`TWRCLRv;;}M`V*y- zorV(-qL!CTv!wxCcxNT{@>5|aB7BmeoNh!r_!$QtY*GHsVtJ>q!J#NoVspr+szoms zh_lVHo`SuTY9;p53{icIWNPXCbg^tMSa8TPqp7i<%@J_r!pGi*1~ZhDS|*_$^dy#M zO`6FS6rC1EMz+NdHc#;5R#${cjF{<4{V<;W{B#?1koXKu~Lrd!OJ-svN2Z9A>k>o&l*c4|sfy&g*0byd(ijZfV3br`wfStdCRVbfO~iE_{-Y z=3SqA!){OzZz_v@*AG@=Ls5_zX#0x7!W_WGDg%pJbaj%edvYr9O#ehsz7lw0hA_pS zRdKJZ+7ylUXijK0C{JoDCW~5M*wI=^!?!h*sY8;N=`-8&A)wNYsiJL>vtChVM ziB}Vj?{+-W%fzQv((aJm`@4TyBlXnX9mZ%g4mg+Fu)#A&4#lj@K-3Q+Xb0y7(OqOj zVl;X2@7V_;_M4x9bS^8EbXuLpurJaArmwrV7t8dlOdA&sBdG2A_hr`MBi`j9hFWTm z1Y{|7e3}qK1G{(fz96S59rv?yzpqBF*3r1y?0RfJUeCy|B-9_&ah;5eNUW1K=nep_ zhqtIrNbJOz;2o&aOoc1q;aJ98B1{{f^pB7&1Jc8sZnrfuQgA<+g4sES@vuBD%{28L z_%J+)ZJZ959;ox}K~il!@jzwuDk9`+37g26z|AF9v4s(;z84=@q!g8yHwEOGZiI}) ztT+5hVb;t!Njtu%Q3sSt-Vyrz&Vk8`>O30~pyvyatxbKs=F0`R7qOF0Up?VD6ZP}mgK1n-5Y6?26>v=!NI z6#nZx=0X75~1#2TSkM#EhI|T4f>^#SzP==-tcRfWU>Gy|{tApDsK;j=^5w0J7|d z9lb!Gcn04oOwV-R394uzV?Q{?9uwV(H|7znmr{r0aGe*piXSLq&R=XV>)|h_V4xz@ zuG~BJUB`W!!1+x>Ou1E>nWtbx0r-;(w<8e-(}h@8rs}$_x?~vr-wgK?MV9vzjkgPWwg< zmlz2PrU!nz+{UX+Ftids2Ny3WNp_1UUT8B6Fojma@95FR-Id$2?VQm6+Mka9@X-3R zNh7DYz#3CAm8e>FOyb-jC~fgNU$Y+k5wgV^FBpweLiVtc-*6Z9G!py!rdFuDNj{Ui zH9@rq<)k4!gRJXma{Xl$q*~I0bgM1W!v1BFy>#DboyklMQ-d6_4ofs;# zB~tkqmX+^BUr29R4R@2Rjh&O-UPzVlWj{Zv24B^ywcFv`z1wyp<^mvd)`{aWa|94#NnJ%88!k(v`+`VO;nNTAJaqrSjyJlyiQ4> zr-zQav$m*q>IWW$u!MY|^ZsnT_C0>o$q(G?-r zrfvIAK&Ile+35XT{gS=H1&o;SJ$mv}jWB|j)EBM%F_Yv714w#&1{CwU#N<}WLJRV_ zwBX70(XE3f2lfkfEKshFma36dBTW|s5%RtDcIAC<=?Iqy7tNv~FUN*CUMkcahlOBM zCibKLu_-JrEbfli^U4U)C7~o1nS0lN4xYIyEbR~NqphzFIYV>XPRX|>6f->B%E?0s z4Bj*e<>0p@*x2_;=S{8y{Qp*KLIXrMSdKgm1vuXQ3s`5wtk*`i$V`U(PZGJKJ@Hzf z@CwK*4$55<(D<|8(Q>4F2M=_3!{^w54yZoe^}kK*%dNcDP-i?1#nASTK1k?9(Y-5C zcqSSIPOpUF-+F)BQc>Z}oqfhji-s@U;K9w6URq%tXsoy)cT_}IFey{cWSxyZ*_Xtbo1X<~?WsBzY0M z86pLjwN{zG)n#V-NxBv3h#FQkj#cp{LTx;ymuf_fXf|cGQBs;wzADE;2wL4XX_&Y!L=1^sFoM8BNZ7p>ay9|WP8cL;c*F5R-=c#6>m)?tMA1WI)THB7(l3}iD1=V#673k75{>0aT z3TDtiaN3^V!0Z@44(?jAJVl`0JsWg@{_1@y`_+xuYmMLK1vIpL56-ier8K%^?|Vn{ z@W0LGHN4r~D8V(R;HFf=TzUKo_yKR)Jyz0KX&p#Ti}38MBWcR2N`%|MI8HGb{!km% z;uTJNDgCSM44Q*YNZl|$Rk|CeleRBG%Cggp$SkG2n5%x&dR7XK7*zajsAsZiHCNV3 zknZ&=+PO+r6dK9x|Fa+ndrLd5_JWb)+ud_H7Q8d<6&KIFmeKm}$ zVp846{wz~`$z@@6KWilIvWz}&^0ASMi^GJtv9RtAQK{NRwr~&rnWm0JH>V&604#I7 z+-RJ~=pm67aw?c1}#Os&|!9@@I{zOZg4jLRh5aL{(GG? z70$cQt}d{N^Xby8z{%7-_B2mgp~{?U5s}B~$No~COcaEP*eapm9Oz@8+T?1TRmEh} zS-C=Z6C;RG40k?r-+V9zW^+Yz2eS0d#zrTEcmxn({oa>gj7RT9&pY zGi8N=#4W3NMLNdP^(kUr8&%J2$|lEt+U-tZo-Pt~>Q=(2MM0-bEmUt^K%H{p{*`R9 z(Mmx8_xk=FNKSwjR)bPP_7?z)!#-Y>Et`Bx6YGf@n6~ys45;hXhb8Z6+AeV7MaoQ{ zh!^ZeL$KniT#2!rtP!A&H~g~#aQL&resI3K_SqUqtgPQpoQUXsz zjIc$k(s>fNd?C=yVjhKAUL}LrTPCbWb0@#$&ZCghK#7fGZiDNL7osnbF za;LHyMF@{WFJbhp+}Wc^k9-fF94m&q%!l6=aq2IXj0e$z%^fqxfy)~MMRX z@-GGwF(Gf})0NsJ1ozP-f=)5qnq7i@8%%v{NVI|F`FN#A$))Rq>L_EbTA<^d1 z#G5>vQv7N76Mo(>v4N(*=#EMor`hmhk~x8q**tuzQBmlg2fJ*nqR+rAS8oyvq}vQF zDwr#It}MqaMz+gNo&b~S5FQ?9*inW=e%Y{eqwju^nzd~(0r5H)I zai#jUiXp)bRxDrwTl~$1Ay|#9*{I9>Cbn55SGDItCKt0HbIgit{IYWz%)XkwE_>If zF?)4!7mva3PCQ`-r|3kGHkU#xj}z!>Y?-FT1#7ktg~8UB%d}cJZER7e{?;kSZQxf{ zuQ>yy9C8i2BH}ac=nfLdH>1`2+7~3ZJx1XgFlVK!Cx7$XEt?W9zAGowFyCBOfakU7U zHDBkSnj34ahvp&W`^oNeRXP|Er?<9KRnuA<&mr>YwT){UkLPLqk9y&mQhUI-%li?W zuI;ah$(iWUHG5 z^Et%!jiuQSF@k$33FOO--!qgQbiEdON0em%Igwx9)9%Hi?Bf;<|Niyu6#Xqcf$R}y zom|rbm*PL?5*?x)H2RY7RvtbB`qoSw3C~P-tc;{L7GgTAk4y5+;+}azMqWGl{wx<@ z_>SrR8_o!NHJMJ}6&BOI9N|jDbuU?*&~}FoNBvFniSv4$BF6=*^N`gN9>TUQL8qGT zDG!r|yb~h8$ElX-W&#M~V>h2W;YdkL2>8!TWvclgJH2a~uoz9UfYN+qOMpC!LOM+qP}n zwmWvRv%X#R*Is|EgLOF1-0#Kt}iF*>8|o>_>~fk zW`=43L9}1qNM$jFyI@$cd~kEKF$_4ssAfoH^U2VReJNYgmyUtRJ}B+RjByHVf@@Ig? zVkKO3_7=;8BtlH0avaV(dzsalFp;x^ernvqLe6+}BZcYy19l7;)HXMMW{*dOr^kvO z*aLCblR%Y0l!;FxOBtEoQi@Lt@C}PZM*&la?ba7>k(UPrI{{Qs8-myWmUJ2Nt!Oz>w>&CnQB|kriUkd8{!x z6Y=>_tM_sbLtgwtp7+j80>;jgvE^vAEXOn#`Uqp31Bg0Ts&;W|4Ni2@?bq_P8CP7A zY{p~d>DKVq^br_6H3}Rsd(Qr+>W+X5ySY(697T^k${A&=UQdNTwDBi~B&PKThK(X) zcZcUJt-_8SjuxZ$#toAZWj3zeI&x+mjM<0-K6utKOnJaiO{U>U2Mscj-UD9U`fjFE zi-(1kdWt@vMLIb!1?|zZOQ(6)XJV=u%$8F}5^D3b+Wb4N(^e@*(tIAicBwhA##hsK zHp6N!Ut9E>KnXa?o52ke}B5X|5TtBrpSLE^Z|+m1zSDk?Z00jTDB_8g(ESo;+ zKi;%o-6>Cf>1yOhaT!+D_>Yzn|3i)mbc;eoI#bz0`Jxrr`=iAkYl!^^9oukiBcyD+ zxd$el`55^ox%9jLckzRc5$zY*Ymo}rEK7K+{bzw7ZAjh=&;R)uq4o8KKUlep;pCx? zIJyKyxg$>mT0wpJ@!Mz)a9hDVkuo&2DwdPt2krayQD<{FQCt`MaNGQ&!b6f@ugQzl z_x;RG9FuC3rL37SN7PJaKxd!@A>WCI?1kS=^93k#*T)U-~rs-U2aG4GXO?3@9*n8+3wGpe+ zA>C!OgW1U+2lBl?=X8&oSX0|jEgENCdD}~=K{; znHbsupeb0D>z*GaISw)sF}Nr>SRs$irk3`ANllbHl`)8++L{8W>+$Q5m4q7}c`QiE z;6Fd+#`k?TTZaMU>(d7Iny5wz1p)Y!bZ$V$ zo?bggGM*N83uZ@GpZFq3E|zPT3(K-43UD41Oir{XPwt@}y;YuPBw{LFILTu1+a>7f zT$r8WUs_wBjg8LAXI9{g$fr7w8_C#uxu04pq7CL$UN(-ItOp zq|=u47ohiNt&7q|jQ+Whjo*~zCVjBg5){JArn%b!VC92NPcq%sP5k1wkCGyHv-+pX zZj_*K&L!1?i+H=H7hX7_3Z|wVJuiFD{ar4eAD#917DZvQNT;rL-N(#$m6@?b)SUH8 zt8C1*yXyF&x@oCX6QfhSIp|h?U z_c@J6LjT5V545d@$?1wtUtN6fNGAi8^Y_(-$rKU3gq}K85Qpui_4w33vqo0mXqMlW z9E)#oCvZdC!d~T=1{jRH?gjgEd$I)Ys%>>xfk(A2y8*^AOC1}Co{rceXqI#R05|Y; zg|IG(l59Cox0)pv?Wv`XB_(y*aM>n__L4VG``pr>b(uY@Yba+t66`6sPey~^A5Y?Y z6VL0T3$JnAq@avW{)PKq+<>;E=SBsSn?TUDl#bzbYw~MR6UaZq6xKuO5U+xHTEZdZ z|1Nf)+=v?AVT~pc;Ig7}s0}2y&rG9vr;1mkyWP&gJSp;*anv_yW6q&EGK#x0wl*WA zyN4`zO{U*3*Ii<&w83lKz*_<4#+;(!a6v2DJXWlDzU5nKs)l}IN%Bp@Dwkg{QQmYO z3Db73GRCb#Uf4h-6GPaEjx<)MR2tYneD8nOD$i>(4BCL76jc+0DU7?_>AMWEf%YKH z&67taas1ma0S9sru?;0b!(bjn1jp)P#%dadZ>?l)bX`8QtO>~uL2nl3@YrJz7n69< z{J`7sfndcT3`Y)WL~{NGlZRx6J(5Whh1%#2`6!{v2Bp6~(WhtFDnBVIOEKsKvshuF z*&{{%lUL&ZN>No|x~o6;^hjI4*hYksX#@n`(;pfkNA>s~B#Y_%V7^GI&epc}8wRk0 zTqB0UBrXoDA2J}*~5O;OgV49*!s2oG|Z)BZacaaQ+a zDRh+{flBP2g6aa+XG`0XN-7oEl6Nb(IX7IBLNzr-$@`J=iN)Px#pBkn%&o|(<>(w5 zqZLa@dZ}d;Uv`{qOC1zKs;d5|WNw(E4vOhJr8Cs#ze!Z@zIZ**W&pLDY4 zF?-5`#`=A{)I`v0okK1FnX9sk1?(!B);Z#`7D>H}+~RErIlP-%*t{Ln16r0u;=khR zy|=UFt4ol*!y$P=-1si8*ax+$Bagrpebk%iI_Swy$|F6)j{oryr|eADpB|PtC=?0{ z+_TG>9`@?q%VU88gIh{vnWinR!(q8p5s}-VF6)8otJSP3Yy2pbqY(p_A6@*{87hLa z0J%wjdICYrF?#rsH*ABMW<4set21}Pu5+51=OmxOye4q?ULxU-7Pv?|b@(l<=%{DZ zZhN?p`@J!xBkSktLJR6XAe zwU^jEbOhF`6U7_TzN@}FSJp3ue3?sLBX04-N5v{(_nJ}3!|<9GYeKtw%Nmj%SyFvX z-Y>Q;KC1{f|GxA4o?3I9ZZ9qdEougJ3f=&P5v4`V#ihxxeY9mi3W*ex7gbQt@}|hC zD?->S24qbE0ieUv=~mRPZPDpBXYFD=rvmk?)vy{_5u?)j)GJyTN@hdb^Dunl&by@x z^r_>J5K|jm0I>0*G0F%JaWlC)U20AdyGib9Lus?#8(Wy_($x=yNq)}7IqgJ;%gY0% z<#4GQTR%hu^eom-B)J3XQO7QJO2Ai7MwZ{~LM3u6{Djr7EJgD6C*Tf3vG*Q?cqt=pyT8m<> zv~#m0Dkc3=vzNGMG@@*h7TzGB^^W>Ia<2m33CLFXB$S-(yKOb58dU!j zka&_vmeZ~%#aNXJVS33n6oKYF^F2ML-}nk3=V;5e@fT>4509`cS^c{eTEM55sThm4 zY6VN7Da7=4c8-NXH7$(pt@8CMjo7`{RTm)?eGr)ySYw$E%~Mmbatc%OBznXpIjVzl zB===3Ykyt4MeP4`)#+yes&qQ-OFd;;W~5BwaEJH(5&8{Em77%hKRn2p|5x)JGXuka zE7$149<$SF`|-ECi(?8px6=C)nXogk%X?>}30GV#lgb&LF!bQZtBRt%u11&;#4di~ zr5M#cQ7LV#$l5Y}m(reoP`50kTv>mO$=*ziNPi)aiMD(;Cmq8-GJk*l%SpM%|Gpz{ z>iR&}mlJm%`@tLk-7dV}er`;-$rV4s`?L9~7V_&@C2z!R%=8@B$YXB4e>^L>sk5!L z_(=$z$m17E8ca^^q^KvVUhP-z)*a>X5 zr|m;%#%<&@x_s7bAg``+HuzV69_5Vmhj;me1rN8KYZOf35J$l?H#xq(iAv;@SCUdC zcWwqVBPBsf+lhpX-yW1v6H~8i$L1wDD&szMf*H>85LZ$Wzl+edee52YTCl*Y=oBKF zU#lW1Blr&7LvF}5hP1q6;N-Bkw5pAZCQ{e&A%%?%OtsCjEt~$VoIvC5W@h_-z8Du7 zqDY}5l}`$G;kqPKN0C%wpwy#A@*luvm1Uo)7oQSz&U^?2=3r)zaJ|{%N3fx1AITqT zp`sq0$FD=DB=x=!9YPjWlHcb5Q!QIhSGu~}dD2VYA zUot!02P4qTUoBdyuWyAdKh@}U%=t^x7UVP$9bI)0*cfcs_qNUEvJl?~phhOrX@hKN zCmq)}wrbqk-GuWiT zkfe4X&I$M8i|QD)9k|JXy3(ff;ee!3_JeD&04b?%wx-KGv=!5nZlXe@&l~WmsSGqQ7yPm2QAKoeiflET#b}DX z0JW-dT!=}Uf_*T=oV#svYw>77|zaeWw0Z6R=4B+L#&QHJ}a%A2g1jW&o8 z7VsBvoWWal-2hzO09soqT9cU+1Dp7+*)~(f2-<~x$&FtgU=CAKj&1Yjj?L0lOM%u4 zBG*bP>%X(KgK2#{;T zTri3tc76M55H(|S;&pi~+#;r{Y>4*JBCYF*{A+3&;WCcDUB5Y7rTSy;xX0%DO52-Q zB#Ui$le5*(8e%6Yrl8Kvxs{JtI*574W?1Zvz#g>hSQW?DVIP`8J~+}b20ye~KpQKV zl)!6u$M@axb;nO81TiOC-uIK+el64&%C#5E@fq;3Hu-pAg35IqMKXl04c&24o6PN{2B8a4 zN7(-U=ynd{Oq?J8n`&{8Y%Wu6DnsLUyTsF44N2Wna;-d}($D^Fa&FWeaHq?iepw_c zHOzV>GZE<478|UZXy>KZ)R2nP^C!qkZ~ZoEXAcOCa!+f3blu2glxSS2fPaKKVzpw` zI0L6;guK4s&hZ(IV!Hs%$@#Nt!(}Y~0m%&oyU-TfOWh?^e8Q!LH%Hg@5j}IBM(tN? zG?Cm`o!tS_MED^Sc^)Qp4dsDEf-a45AiZlLS{RK{5@<9gXBwC2Uwkexw7!c}38~!A zX^l>j2{kOdq-@0^<4jd7t!zSS+q!og*_>h2cbRzF;DJ{$%Foo;gVFtzqyz4X4!(wd zxCRiP5JB+(KD*U1Ygd$9Cw)wGoSdeA4_cmiGcj**?>F;Cf5Q(l?Crxmni(Sp5sjCF z=MP`;D4BvRDo@NrFA#?bkdLLrLT1w=tIapG^{~aRI@z?Ck0?jf)Rze~|1oc4HY7dw zVE!%WfqJSlXGZHvrdkRP)Ca-O57+xO^@{puGP4xEYQ!#$dV9u}jg$;dXz8W4SU$)t zj63gj+_?p9sr{E3gh3YNZJNQqLdhmqPIMsXiU`H-7-My$(8Vzqy}=m+i25bCW5;q_ z@#MV7uIJU~0C|q@&4y*I?a2dg7iTr+IQZFtwkd;+W-`rY7BuCtbpn({GIwfCnW|oi zrh>8EtbpiOuxGvOd??Juu{R8xxXFFHNDG*rya<_3Cm*IOt=^rOLJ`nZ?@W<8NYj2)VyD08J z+q@=We^XKJE#-@8u#ksXV`Y3?23So92>LrLwB!Lm2fa?SKu$KZDs+Zx)wR$a!gWg( z&uP=B>g`D`4e{+wYv_&W%c{ex1s{;qXDl>MLBNO_;7YYkU}nO!;0bP?0|YUN1JlBp z;yB$gTRVbnhfD>5y>80TEzk6j5zvrJiNi>eps38MR?Wvyknp%qD_o+G-29I;C_uR% zCskyC4Mz+S3WwIMsiGVDGy3a`#tKw3I}qQ@xlyWredXa)k68ovG&l=z)Px%k8*$HL zc+%}j&!+7Q*I!gS6-T*;1c}Ow#sZP)fN-jt$j@bnHNP%Y@5HA%l&j#f0&;wG4J+=1 z%Cx=V)L+)O0@?cI$;F468!4TVjBSRD)_t9rN~^P-^Y(&6`Z^r@^U=v^Fu%h#3uWq39 zXt0JLat@*5;e&rV(g=Z!h7M^^66(kW+R{D;AJ~FZoTIiy8c~j#r2Zwgjn9cjK~zsX z#=f$PmV!(>%YpCWd}uD)_L6v0OTW#5A*x8dLbzK~?wj1&Mk*PcU}HlS+gx4T9c!g5PZ8y@>xAg4<5Br$)309s9D(pNH}^&VLc zN;VILWSP83KHqb+;UvD{qN-=83t@a6KGoox+Q#g z>JYu?{b{iP!Nz0o={=ydosB}!5xq(yV1D?M3y=zFMg`#3_hc#+NIIQ>-n;R*BriAc zlr2N`3XvCS@vtUyG=BO$Fg0?nfDl93a1Rvcn}B{>9E;DyPu%_`a<1Hua^_2l81OeNzgJ32pz(4XZBbMKK98y^ZL(d{QQ0eL zDt2vtYc9S9a1qj50O$TQm91u4H!b*SPsDyVT{VGGQZN6GX(cHA*;>igQKbV$tnx|= zH$QuQ@VJW#f=+X{&x5mEJbN8m#}2EkoO67C+EGsO8EuS_o4pl3`M+iQLnVLW9Mjex zqg$?JCij%fk6=MirVN|+Ufh?I^0(W}@JKN`})SyDE$01v<_0U%zW;UkOG^Migi9dz~ zQqoLmeXFk+g~k8n%Y8F1xjgSlqth?RxzM+L1G3jY-?&UcD$ysi`1*VE_#Nu!-S+k7 z)>F5T;&(T6B!Zr_i$mP?c{cC^_D#6)(H!9awfzz(=HIqrTZmcE_btbtN!kp3dkdTo zaM1l2VCs0wu1`yWmG2&e(xETvMs2OC?grK*Xg~B!a!L=%>GsL49)*s2cMMI4`Ps`C zUiRD`%`9Zfj!C3+YbyKKW-yOY2EJUHp%+uh1!D7m+*YLde!CUg9A_rK*)i2x!#H)@ ziU1!g484Lc8hxUi>=xH3kGAQ4y1q5UpS0wh3)KBirt7+) zB~n(IlkF1O>s$HfWkd+0SZzzLoMZ&r+5Cr3zqn~*Yl-2X_|@AppNr_-V7$qw`&GB)sL>1D_<1scKdhf=vPkBkN?J=rHn8A zw^FLCho3b?{dd9Hu45Io1`?>jR}k&<-US;-P*&HC#VMgbz8=KC*D|t)qB%;~c}!^W zu|J|9J=86{$gMRuan7J_W38owfTIq4*cp9@>3S^(lBP@ztUK3Z$urV%-LYlfgoT6_ z1D_S-m!sd?GPRxhdsCGlY;_yD8qKs%IG3`A>0PSRf@kQ9h^^=I)s1k_<4#8<`-t?a zUw_+N!=+y4vucN=;z+OA8K^d@*L-M4A-1nr`DxuJv75-Dl?n2 zO+1UdTlzLI(5H-52sM!5Fc8j3?es_*-9VW^__@lO{Ob>hqH$%H+}t2tqfbZv0oE<^~d^;fbw&*;pSLzdlnnz*HVgt;sWs?3ev7y z2R6XnS$P=j^53d#7V zpT`v>5rp}QvjM%WagM2oVqqYH3P!g>g zsnf%n`JSwGrq_X}IVFeNIF7{CxJ`hIorBUp+i-esMK2PD=%Wkior2*e-5n?jrr(Ax z0xN0X$igo?mrh=kr5iMB2)S|yzkS5namC^5wy9}3%N37GPgw*=q@4< zQSoVhIDzSp6``8utWg}?Y^~tMsT&{OFP(Af0$Agzk;zvNmpsBt>T$2^JU2xd)!ek2 zH=A1hEk9f9W(oUYom)o8bFzl#xXw556FeHo_@yEW9}hmy;Gz8e*cn_!uNg#9Hpw{L z4(BO-(qm;@xT3~zk-a1Xu$zZ(KV|(>RiXed%}TRQ2|59@D&VJYI>e>9%DF~Mu}ux! z{K5@f`}+d2)(h=$`Q81f>v)w5c%2K_vqykaVXE%-=l2`1XZeLOKLjsnneA=tvW0y? zCc#BBH&utR#kFHR7h&UISGf3Dl1Lf2`-Q4%00pNezerPA?n0L7$oWQ1h;}2ji{Sp5MLlo4@9NJXQP2-Q*9pJ^FGn(5J{A=JPxkTt!Q5DzdbhY1tH)FEKlI@ ze|Xo$0lv#^>=S=g{4JUo1*Jb+CV~X1Q5v*IV^qk_+O)yT{*8k8(ojH2_|(aWvd&Gk zmvbFE-aFxqG)-gI>ZGLryL@Oj=f}$}U!@Kt8H#r|ZQiZbjL&ZcU{K_!WAkW|YX9xB z$?=?AqhTrPUtV6(Ni)&Y(+nn0H;$6DxDtA&RmdByd7XRDn(d(=hW@yt=6)q%%n67Mp&7J;I!F2_o!58 z20vly%|T66)y9QREh`MDKgZg|>r!pYHCiV(96DAWZA`fh)bSimID1H zBC5L5bYz+xxJHut;!>do>!MVVTE(VQx#?4RzHZV6B`->R_NV4Hiy(1#5ETb4kTF-L zz@-d;=RV%9y4McjXqlGVc%Esk2~>SaO?4D*q=4-QYQaWT5&!Fj6yrrkQ(NB%g2tB7Fi`jW zlCUjLRQvS0lrW9Ij?;5#jBp#tW0#llS9aCOJnuYs+wUoZbAFLAcN2IaQE}=f)o@7#jXv&1rS;L5(0Gc3&m&)i($hoO0IXB{ig5BhNG}a-ULjx(K}kn0!yjwl>W$o?7)A zo<$)Tmf2*DHRsEnC2BYg5(z#suN=*)KC&;FU8Av>lwF9BA5~Wbf~E zwLxVt;$gCq*#UB+LmIhZs!BXj;z(DLK6jdA9CC;9 zo<9vSf;ki-uFn%nasIwsR@3FA~LYX6A_Y2O)k+tUF-GNoxQ#S5L6 z6D7a^89_L|){lx7`Z?WFeWFi1dA!Dd6;O&b>PdWA=++M&$Gj3w+%Pta zeM19TNtSo-MHaCbp&Rwn{4nfg<)igA_V4xny)jNU;|*;7ePt+^HNZU%;9B;%w?a;+ zg3@mzq>!(&@NC5 zY(vi=Ov*(Xd5?0Weu4xZAc83euAw_|+S4_2mr|C=7chO~P(-Uu)v!L;yl&bYtO|r( zLEZR^AO%00e*FbD38p+tfoG zl%(Y|1fWh+?3@tUoO>EANf1VPy&u2~xquWnMOQ#Na0ML-LL*mU&jA2QyAAo1`H6y; z#jQh(E5!-BwHpC=WRvvOVWlY?lx9zL&Sj`L|E2y1`SF3|dfGCw@l1sGQ96@-fbJ}r z!GPWLfw$|wY-kU^xDcq(=fF&fFO?Cm#+zHnI@zc}46_ZY^Xq~ z=jEKNG|@M=YveZYd^c>*_W@j<0T&OCagUPdT_dGUZYLg#q&C$w4dj%d^uF+Swh{-_ zcDuLUCngx>6l7;UIV<&SM-7khqauU7Q>bhOTU|_(T%FyqL62prB65%-F(exNT^=H* z103_Zxkx*{)6{WlnTAOMx(kR)+@n~rtyVNc956(op~4rYxEtYlAQpoUnD=-YRk^fV zhJNbDFtmD6p+@85NLZnyc+Y+hTvkuSKOR&c-XxYOW;PCAs1yhp%^|ng_O2?WGd}lk zvrRXhr(q;y1+sg8M)z{5_HyqLh=2R&^FLL0YAPjWfW(o&XpJDnkH+cVLY8TK2eLP68>dbWnOj(-!(^qsjRnmr+UdQoR8%Djyo_O`gG zn{maB^!5eeI9w3~3A@2$HDX7>d98qFx0*HNj;N(tN3cXyss!jY9ayjOF8~p=Jt=;G zsm0E<7qc|JHF^AA(^3mn5jF$rLc`ct^K_sbqPX{86K;=GYo4;4vfo17BiLS(ON z48kdbu|-}HalcEPueIgVq`Qu4%GNb9Dn^aIJTQ?}A8>I}6#OCOa77x9;s~HsI9XO$ zhg`BzWcsb|i6>90@=YG%4Nh>BC6b?=Z6zDRcPq8|6gS3mUGx?Q8#T`%>E!)A7Ppf{ zA?nsoZEU)7ob1=-z=wv9>9UGPntfMP1y|9QVK#iu->GD5Lt&kQh+3M#OODn0%H_&i zBl50*ll*8Wtcmq9k!J=8WW{^e7m(xL66Zt%%1x@BxV@&*=jxipxt6!(n^v@SSIWEE zs7?6tjymwf#4e9aY5BsIM_IIZRT>gZ-E0(XqRBRfGgXOu=mKfbTpOtTEM zZRE0wHPb+mT8EDNW@-%fKaiM8>TzH%r)hMSu{_h|oNb#YOIk91jI{aDHq2B*iuJ85 zq)XvhY!B)(F4n$THs?cBBeF*mkQc@;()bC&j5KNl^m0?!Xh#Ucwc(D6A*CnwZZY*z z?MqS8e+wgrSTiUUUJQJto}Qum@&+7t#B`IKZw|WYcf=r2<6E8(+%}0rrz6?$UjH;~ zXN?ZZb-KS6)c8bRGTgxzA1n}y`pAM%ArV~H^Q}KwL5|n{hTE$UzOGp7uv_NQ#DG? ztB)Ay=ftqF2n-8ZO95hcfdpY5V$1}Ai$fY4kWNzj$!(m6-c#1b1W+%{1QTdzb{brN zRn{vo>4fCQg0&K#+LTcH$`iI_0r=Vu3R1VdlR`WPI#{i`{i?WLG#<$;fJ}VBY0qtxlwY_1r8MOz()#OcfH0!drUwx{V_$0E&yR~ zv$koZr;j#gPJGnm-{zZB|EvfFU_nl6WoD2*H2?l$npBgv6Z%`UH$aSntkNe%4XPM#ChgQX z#2kXUD>>(umO@$OWsLQJRc216&+#Uulb@dmJz#M&+%DXHyw7wI5?^PLR~y&y*Z4a1 zniIgbC7d(VCMkszWU7pqhu9TH^@}{xN5vD-;~h!hnbxyk%gK3b&993j0*TaU1yeuq+)z& z(pT_;s8NP{n*3vA7|{;$`Kjy0eW}6JE^{9yT}kM@t*^D4CB!QFRD*QQQ;^TYWuj0* z6GrG=uZOka`gxy}i??68MhE)4Y~qN7Vyd^dUV1SyYU;X3`A&-)ttG6?zaEL34cu}- zj$Pw`II-K^@+n{re{Uf>6+dJFDYe%^KZDY`I-hPvQ`rF=$0Je6#n}qDOz_Tl;7PdO zTvw(aJ|h%Z4<|*@+~~(wv*2D=WFpcu!JPUxOJP_$io-EG04*ZZ7gkfq^06CVViGCJ z!JDaw=h6Ho!8{x#Y;I;SO6>Qc)6asK3?$rXzOEAl&CeX z=%44tWNcm=~$-QWPtzr?`lc#9xMK~xoOqNQ|aB|936Et ztQtC6F-1`Itp{`tvKm+n$@82LTotPlhTxi!n24*{>fbR3uuth{HtmJS=`a&al^oFf zo0zc247#!h^7H@90ESSM3yMzuz-wBKBFloFzMaVG4PT{7YH{1d%+LU$uqV&hJe>`%;S>TlV`3pQ4Xf9QZ&YJ#{GC@FbJP79ym3Ek%d zBbZ>Ei1fXLZi+r_d>bV~_xC#ZhopOUMiElG{@Xpf!ny8Wy)(_nlezcss75Xlm2bDn1a3iIt98S~bhx ztLXK^UIbpj^O@d^&tBoZ;*gwq&S}XZs|1poOoyT2!79R8H_#FWJWk*swHrrhdaj#O zvh^n)$Q1?pn5JgfWg}KMIzXhL@v%s{j#qd?#=bW%URGwIDDv^AF%)|KCn`P7{9z%e z0<%EYl52WM)K@GsV^7^COT@GrG^jPwco#G8gYU=aITP=@gw1X_bpjcT8G^Bzh#`yE$N1aln^-cP`4iyc;8+T%l|ak{;$f8Sy-6a z{@>eJ6V62ZX6uhQXBNIO*VU@Ir1Fb zsX7^7rrNS!zZI*rgR^5O0xoEeYQS2Cmed7cBwA8ygkUQgCRSRGq6=qR@yQasDZa=3 zjGlVNkN&SZC*}XDbHvig$}gVY8P zm3Hl4&fQATNkPibH}5*S>k&r=c2{#d%-xr!haU8_O-P7+YG)z4WPqRX$DTW$QVbY; zrgjU@f7&ikG8IttUB>E=a+Od3(0~`4jOzM@<7_enOih0({ry7$>DwCxvN%X*J1=0} zIgfa*%MjOF18$Pt^4oyaeVC?PcU4C_WoYJbs(%!o9p#JIb2_TYZF^Yc4zRp=bE1dE zDnbK7u%R6F?g})`itVqgZtf38f{=!GRr<9k|kbL_)|Y4w3wAmwWf9HVzqLz)^rvA1Y2z5f1QaH>cYjt z31RgX^v2RuSh)wZ`7;}O)A#e#)YCiT?*$FYcqgd$;<{9<8adfp8&;zZk230JM3L%> zL$mE+tU@(-T^;}S1VS6V1^%*ANwg+q;b&U5(@`#T-#BAgpmc$MPsk+jV7M6mjhxq6 z+g8QlGe7Y>8qs)8;T}7E2RS=}njSxUpdfCdmqCR~?n3A4CVrNomp>bGi_}6LiO3=v zkYZr~$7t10c=)E;ul8yAWU38W=Ix9uiP@CFe7`X=y#nuaF)TXM_DqhmcwCP;;5Q>n zcmS=)N(hBRG(qVONPJ!7k;{ggFEsz+7NFvD&s=2*AyFJ|{Q1LoClo3%X)H&)c?BuN zM9qd{f<;w`IgX)CDqHD$h`$ss)~$|Qm%F3H!FVZ}QkEVh*-@lIrB&K0LIvGHyz^lz zF7jP~6gMGqRmBDe*bEFp2dpswK-v|`CCW9S? zSk#)PK=&~DV4NfV8sP5eV&HHrlO0xr zsUxC-6+AF|nK4Ohnu0>@Wnm$-OqT3Q;U$MPHqJ_~J2o3Ydr2liPVFNqjK?JwoNd7S zFv%+m(IpN_-a1=yO!v4>z2GnPY)eI*)+s#``>1cPIDh!}I*-+HLYN)Yf6ubV%pf7J zW8MV%!>;uHRGD#W*plEvd4_G|TD}4lnL@=OS%(0NdKEmmBK|!|@hbNAkDx%=KD{bgvNTx^@jMCQ(g97P76 zu9CuhU}}qD3hRoHe$v*mqT|I>cG=8)-lWHbe)y}?2NK9z1Oy>D`KByVWqsP*|t~&BfBEMgdo|jJ1H+k zbW*-d^Nny+_}d~D%)pJ(_F;}l`0N%Yu}faL1Q_Q}<`6|xg)Y(7__4_-Hq5p>{Y>O7 zl}nRtyhTk4{iRyCGod^At-g4S_V>)3Vr%J`?gV77S@lqW}E%AlyV_@mqWX!#}C((Likw7y+tE zR5grt%uDJA%d4$+_U3ERt3}f7(bN_vKQrGhO%B(Mrx2L%+sd)T4hqh;nravg^n;jQ>pvv#}jBPv~5<2$6hW+w$uSaZp1d%>T9aM#C*{d^*Xk z0fnhj?s*_-R;bg6`e3n5!A~KQ{=-zeR@KYC-&oO-F*M9p77&}WmpeTsOw+X64&Ja~ zu}14~xqL<1p{>rtpLkZSQu9u$T2^E$Nm%{c+pedz@8K|4fSy)7I4_Hww&CJrp;h}> zVPReE&yT*F|TILiLG3TJhg5LTC|@ z6(!zJN6c?W1n&w&l@GmbbHPQQS#4z%mgITy*7xzCdy8* zHcOF`XBrBdDg#o{s^~`TpWDB99li#67YkRz&h*GftN<`38WJ3QFb}QJ-SD<)UV?6X zgO;%5-BnOrWe4j+d{^w|CJHy|;vG&N@!e;Jdw6-)dGC-L1wIER@XI?GWb<}@-XJ?T z!iF2Z4U}@NOXo!W#o^aBUADw6b#DxGETyJsP90L0vR0&b7m&&pUJI&AR5KzkF`uib z2Xkd$_#M!aZv0PYhJ6#(cfQ*l2`CG1$0Xo%C^*>$ocwwJc4Z>M;b-1QrwUm%;MH!s z;#C~m8w0rwl<|wal>kWL1r%&~TZ$Z5=^;$Wg-Bqs89@J(Q+`4fr}WioBc2*n_BS-@ zD!x@dU_GW?#2n8B7pv?W9(P-&s(wZJ z!nJQ;V4u4$BwZeSTD8gWaXLO3u*Zg%$2gBX7b(9V)L6P|rvQ;bYiAHxJAn-D>2gT! zN1{Q6G9R#r zTRjpOXaICl<8DHGIkWf(sg7G}^RG#Tk_)OxE1B{aTj}NPl9VkpeW*Fr6@5&l$EDGT z-=0wbC$!^_T%C!>&heWiUR~oj^ zZkuHfuBwe7Mrb@MrWXa}zekcKc~=)DUwz0e!AnOBIjR%wu<^;#Ptg@lmu0(qF;^Xb z5Jo&4Q#K%4-Y9Y)r4pF_4`c5TBuc<#VU}&%wr$&ZW!rvL^~$zw+qP}nwymk|Sxk4# zzvx{qGItp_F3$N5nBD%=s=Peon6j#?Zued?{8p_wLh@4yQMo0D>k{5(npk4HAPq?IF$-NO9qC2@H zCQR@k>PZgu4W*;$Bg}TXm?dqfQ$7<#QvVbFN7TiHXbDRVoNM75Ha$icmn$6uwkw># zi)ND-)(6HBn4N#G{5br&1kpb#33f38$s-9u%{q2xXQK}%8Vy4ptwS;S)lm3iQ_CFfAdZ*(jT9PM06DJ;5O`wUk8#LO*`fM{@=T9~o*5ixTeRfq(%#A`UGGe_#2~%6_ z!`rO0w=JTL$$fk;HX@1x@P}+zyXVdJ>aG2lisb1nSk;J3FK;LHo7e}Afyp3H^Y(7C{$ZO`X{+m@*=zs_ zG?e(@%4OUcL4ed4zL|g zZK++nC$o@}As)mz>(!B=xrm9K*}OKow9 zB0M4GgJi}OW_(FSxE#8>tv%gvE1K9l(}1^-lVkXpod;K3nTLn zK!r^1+7{Vq0V3DmMdt$m$EV)YGp7k&Qdp1xHS<6L+hagh>5&p6vFbV+Fju8Cl0`F! zLDzIifL|e`E_A)kPyXyMklfu@4)hRuDNdf+lowM&%X%QOz_2vqJKD)tGQV(;=A-rq2zdIlJZr;n)1uukm^AgcjXvtyf7 z#LX^*+^+uafZ_mkn3Sd-K$Vez^oQS1LIVfqYUJF0t$yM8f~(0uu>8u{*$kj(k<#*X zhi8B6oM0Y)8BHA?X-tQ$K#8Q#*p1RZgpJlekXz3BVLFgRYMYBatRC;=6F_CC11FVl znBGAnreIy;sl=1`0*i0{PP+(rDYvhhQ_1ge8&k2S|4@e)lrdkZ$%wq)T>JYuyhP=Z z{Tj0K1<#lDRPrBIGi(h17aS=QEA#)tk#=cGI~}sYb)Tr8!m|kb-;^g(kMR{#vD^KV zKAMf&nl>jgO|)X zy+G{=jP7IeVHNR~*?Il>^3k*7|Gpw$>imv@zG6j>&8ube`MOxB{e1SQbvXNpA3Bys zdUpt;$+h5GnXtX{IRRiJ*_AGBOu+^YbRk z=JO-JPZQ*Z*i%IApehkBA0`QCppexAfmVb~4a1^`b>OA`zWJ$n_V9r0n#iTv?B32g z2W97gQEA`q?(uyEPT7<)|LK==YY#{sXPBRecIz>L4-HR6w540Oxv#qS6y9W$Z%KpM z*<9pGhkf{q!(F)5NqI%3O0quA{>u?xbik7|dHAiA(?SD#C*eFZFPN;r%m4ZPDE$yq zVhI!eE(Hzl{`Xp*g0gqMiU~bXrU6hqXqcQLP;H!crc$hmSqwSEL?D{d2v?(qZ-Ii^l-C@P<&sm?byWK4EwWcx0#<)D$bejNW{O^ z*F!8kIx^1KkO(49mhMyePHW=Q?dT)7yl|Z=Hw=W)@=8K$d?Hj4&A{04(V`2z#A%z$x^3RLu4v zp~#w5Z)prQxErDCL)PIa3QtvA4_HtDMts4oK&oMm{d(a1^Z-lconB-_ybyMKQ1h|W z@wah=Re(V+Kt-lzoo((w!zWx%vA4ywwX_-A8TU7M(_LHTJjKubsUV#!5u8CIJsgap72P7+jht}{t}i}j z7o<7w+zr|_A~;4V1w6^fDcJGabSLt&^k|cYlj67+5Cp{iwED^jd)qlo?MF^Srf37Y zc(eE9c>_?_3N748+^18YQb7@zvcoa2GAF$!_XkPXz^SwcgNMczm_$%j@R=nWLH8!_ zB=a3mu@3yXL^Fw+-Ax&3LqcDMBIDfr>`rH zU-OqiEt1q4TqdRzv4Fd9&K`)I%B`oW*~tz#5n(qUI2xc~OGH_~=j>mZr*rev^8EZp zv#qcRYd+m_{TN4a8ewClN-;Nb8_&XPBR^Q(%WZtaDQSMU83b~b|Cq6`US^=1VeY;O4OuW?}Y@ts=Ca}~Rs>N$tG7fsfv%XyDDvE4W68p_%vVVg-r@>p4>e zs@(r|fLLx_a6&Hn;dvsLvL_K1a8v$%&aIIpz{_;``x2Nhe!4)ERAvT$ z6^84XvMMNI3tcEiM!Y?0%akCizCN^RzS#$v&!Bgh4#?9o6>l6xl3Q7<#b83(J-*u#0Q#Tn3BIN03AY^ zUi?8q@v*P8>u6+ZD@!*QZL*pmqjJ!V9BPV9fO2uE;rpip7dFCa1Wz=e`CeKdg}n|4 zW|KsL>;{LyUyib?>MscWKb~;$z{Fh*0Ff#<<@q_Q(h~q6DM*M6z`R4#MQ}jCO_{Y? z4xLF$fyTVcc}d?>i5p-$Fpw3^d3?~pT{+P0hFH4(YQ5FJ2=CcLmuv0Z9)kZ!%BhUd zKgg@Qy@4i-sPw|+iuKA?P*E>JdrvC#IyMSeCq>jJ9IbT`FmoKYq0!oNx57!I)%Q2= zX8BH5k4J>l2rGx2$&Qp};A*yN6QfX@p$nEQy?U(p_tlr=FTm{6flYgC72?hnH!?ZZ zKb8LFTTGXxFm=`Ht_1zdIj1ZaUa|U?Ri`4?+qL%=bRHm#2hmiF zOgZp?TgH~tB+*k=LY-6FT4XZ@${x*sI|j=JQ?5E8kl`B2z?T(A;cQEi^Ubae z%!o1b5eEU_`=#`l}?`OFF5n0%fc{cTyF z1NKags|SbJ_OYd_xI?(u^|WGU^P+(=)RP0gR)+Q5iyAII6~|L2*IGySdLLl^pQh(0 z!NNcwLUKgz$DScdL1Rv9TK<$Hdm#i~gZRcPvRp$eb1PXcPOCvQgZt)86Wm{q$|bkKoeFy&(IaFemiuKYr~@xqveM$uLYFZmsQtHr=_mp{ zw;kM7!4Js1EYpkq5HDP(?VRPB?n#djqQ!5{)I9`|;%!)23dAh)DT<@hDTr65q%o&1 zY*cFTLNJtd{0J@q5_K1Voi zLPPjHAN*Wd$(jGDS^U3w+!&cz|K}aA3VR|RyY2UmcZ#n-zY|klR@T{KX(#t@V?96& zixZqXENkG~8z7cI(ABCiPKVheR@VP7FiGCgMLs&{FLe&=d&%*={$CLP=`U!GuW!TB z>3-q{+SMo5?e*uZCx_1Gb0X*Q{>-Q87hDeppxs6PU(`A4U+P>|g8P@SV23xtJbtxH z#Zk)9%FgZq%&vFv3VA+U*u^3Kz2z>iNYioXZ-STqRpu`3p3&TUus))B^ehx(xZ ztrrbYnRH~xr7o@?V!|_NVj2J-_ao;R8e|38buifnU8!)!w|Ly)%MZ%O+3k|@AFSb} z{XO9c*4YRQRVl~;8L5{6L_WQ}>FaD(AH2VE>0v?n0Rj?qkW5r;4x5|Yq zlJxe$ z2sDBbxokpR?9Taz`(|v1^d}0w!BD@_1p^3$tu;_0bVvq4AA@Y64cQ=&stMMt#aNn= z4HeyJ-aAET01l+Kje&f1L)z9k#1(St4@i-?yBJpf;gz7Z9wGl=1=7GG@EahO5bJ^~ zC?ce2Nq2Bo{x%&J%813Yxh`_TkoK~Raw*2fkAf8P=c(o+UQK^8jmg<{Ss0wr0p5{X zJX>0?wu)}`Vp!&L;bbX*lfmB`E%;-|GQ<0HXY*yy$q%J|H@||F-C^l6mBwW4NCSi( zpRyG~OM*oz3tNuvp?07Ugv<%SlCG2jIx8d>yO|f&NmsQpjQ?@os5{`>I3balj!N!_ zzwd1O8~_<5xIyiRbqlSe&Ldq=eWBI+*pSoNNM7fe>WBVShM5b}c8``)fkcxEx2ysb zvFynQy)Z3Vv9=SEO~jE#6p3f_r~^{{=H=JK`tEH9RuCq}y%tFQtRa?Kaz4c*%&v;h z(d}Fvbx;0Hr5hyH8t$6BeHIkX{o7$1c3!fHw# z)$a5*QkG?=kP-G3)$FYVpJaE9TCF%J{LHZg%cVLy!7Hi>6bbc(I#<|rbAj22Hx=oi zRBCthgK|sK+m<%iXKcbUa;&{{EuxT06J7N)rflt&-nG^>msIUW;*#E<7*Dv+w+K4lj>L z@Z90%NrP?@9zVzfgQQO6UYA~r>*~(ZVF$kWBFWaVuLXOSN~c#d$XVNn_&=IPDlQDA z+cJW=pvPTl3yV#7vQenynJ!|hEL?vVOjrH&k|mI+6EYLa8xGnC+23W&D@{AU5qqUY zd|W}@3{W5q8s-NSS&69g3M+q%7idE_gyA-0-?GJC*(%Zgcws*ISuNpSp8|uf`*~QO*aqe}F0ydjhtpZ~wwiAd_6HJG(jVhXWSAq{ zvDs~PhKmS>=lHTBai-=+ggQ`x6Ih?=FVDzqjL+q@L)D1CpR@Bowh<%DoT;M0z$6P1 zaL*ziTD+=r9+DL+l9xGA>=kVn+%jxzHOvVTR$fg3wm$5)Y=HEfWR^2_TDqJ! z9lYb`Bc%;|1WXBVtOH%5NSC{6ya|iiDCeO=*dZ(6alnEbu#PgEX$!O z!Ng0?1ZEs(;ym+l68@s;BxN3pZ|Q!`+I*yhXh$fz3+eQz#J)cj=V3`}(v}$gp)T!6 zbKId=**t(w5%+leOwcab%ZoBLMY=XD8%dp#idqxvN8UE@zMe)Zav;D@mciM{3r1|0 zFo?pjU?3Whz{YqTUH1xnXYS2m;GGd=i{X-uu+r!~KYgsj2(ZWA!={J>ny0S_gb`(sLh$J<&e$FN{_5*veesV8_YpqovjoH8+BZ(q{Kf#&UpOCi@ zV;J=0k;UmJ-gr~5lNJNoV?vAQQ7GZ`OHHXZFUq8w&-(ZTH_e0qaow*5HKR@S?zB2k z7NyOjs1wdr#@5-9I*Pem6Q(H<$^v;SuMm?sgU5F5j@x}itT2Cvb9Q3M4abeqB-`T{ z*x&Usm-HGBRqxs|?U63{0}R%kWow75%{dacV7xEG=a3-rpriQ^1P)xDAa!U5^|%ff zEXvngf>VguMsK?Id7p{oRa1wFYSG}|sQEzGW2CQVP?E-ExN8GP6zx5D?8s(I?Y7zV z@$~RaQ8t)|w`cip{Z4A9i?ptml<`cQl>LaqejIl*;0`G35ax0EFW_>m;7RyW@|7=# zzZ37#+#j+aQkJ@Ll|hJPz>WIEGG5-&^1t>h%AFbT)-W12oKO1JO75`O`hTchoqnuc z*3138a9Lh-AMDb<_M!?7rYIX}_;Ib|Po&DZmd;ww8Pru4nIx^IvR0~H4(0ZWn$m&8 z&Q{z_Iy-Z5Rt!%BSs9+7s;AFZ%re>`aCw)_%df5tsfe^}=_=WFU`JigT7ZjxTnVbklogBx#`fzBWX#O)!uEezSu-;*{V&ws*8iG9tkuej?_K{( zP1xKNk}Q^vb4lU~_x6QA0N`J>%+uKs)^KDY8AWzS29SSt_Fzn|{If>c@orZ9P;%z< zr7n20dr;;**-hi{{sOZ5@~ZLE{c*DJ)Z+7+m6iQ1%6`(cr4r~Ad38X4GyFj>&+ovr zz|Ycr2A$^Z18-e?Q-beZsTYH~)#1UL=S-lD{-a>3&aceoG{*2iDyOdv!tx4R3ZOXl zz*LlZyjwz_au$AwUG>7fxU<0jZUyztz2%*KY@HnWcZ1#vP2lo))7a?P%e4Vo8I`av z;O6^tOvdLE+>p&luE;wb258j;(7@JD&o_fJv1q-my#JkYx=%KzIJRNaEAzYj@a-!^ zib5^Ft^RS3E6Ajp2G2TExy$Es&peWPIIw|nZuzFty9OTIDxpUBQz;}1AC3;Qm6_%A zQD3?X{>eh-$s&*&+^xhzRfTarRDfbHtzv~iINP*%7hwO42xpF$<1doJ60->G}ZM_omfeuV;w2aFCg~2twp**FNZ+Fc2k#W96cu@0v$P!6rQ9W#8CiG+r!rbsQ0so7t5>6_)^n z^`}T3!AMu3*hk`+%j(*(@4WbKS+`ae&A>1$M|*I* zj1Sj3KtkWyQ!E3gPK4`M9##Npl&9j!L#6?;{W3K9tFZGnLdI4zn%H}2CsS1RdUke69fZ8Ud~r3zJL>lO-U|6ZBWwd*aY3Y9^s zR%6_|sS_Xu+Nb#kCu!MH>(<6P1=6hiam9bBmzZ4os&GZtW1J~{QrKCoh!Jza#dop5 z@wHggql{XVeh_wB-@+`d;yj-40FO5}l(~Tq}yMaDIj>8IEJ1 zAttnWg-#luP)+U-bu{nqP`*1*^ns$bBXE;wp&V73p5(84fO@lVSF_mn1fH{1K`v0j zT~To+&O*C~-KA2)j#w|cLHUom>T6bT6FY;`ZJk=_f#s)YIgk!Y2FnYVB!}_KJlmiHhd!TiTy*0QC+pYNQASgO034CT z<~WOl=Qf9G1slAqQ$gMRFvSF!)NZ&NJ6)ebm)pZMzGo6Xh3(+?;kK=u)H@@bhnmc8 zQ=d_1xP{;iLwvls8F$5BIpFLBL~*Wrqoc%y7&4|A`rKdzZX(asoX_91Dcj>$nv29 z;CIFT>d`yqA;$G}I2$4*sMp*embmGj80lg*7GHrJ1nJRI*=e zPP*r=d&(UV?$0jRb|o>6IbNf_%!%#pF%#Cwu{`n#9}g@`Riil4;YGsyK#fXH5z0n@ zhZm1Tr{EGsyF8Q>iXR;PSkhSl44Y}b9S?)bdvGR}3F(KXPBFw6nLV-#O%>Ib6?BP1 z!Gd0}Oyvstgvk)rh3CPD01lFZBncmiF)&}Nxt#C8m4bkczMd|hz!H@4d75#!%YV;N zQf;ax!RDZB5EAPNPr3GPX#dIgVGr@GVE0`7=`~^BDo%s&BW3l*~pH9K$RFP&drY4~Co6U7tyS98LzF-xELuWUNLa zg|a0u{B>xUa=0IklU?X1g(eWWPVm?^6`=!(;7r<|Br~_18y~m_#p0@MYqsL?rx=w zf-_#To+kD`bAJpC5r8o-t?jFs4iA!ZZj(jUNhQACC!6<1;4|Y4Ba8*3{8ckE(vtUor?^q|Rh(sJazqNjfIqPH4@w@13g9 z_WMzzr1PemYO_i<;bV;}57Hd@4-Ng`Sfd0yb><<>F`Vvvw3e+=*8E7xlP&~3v{Ibr z*R6fAUD$3FHk|EiDX7rFGh=UBm>Ek9c zJ_y}2M3m@#b&EX*M&wLSLO~^`*GO@+NI|neD@&7yiPZkId4bE{F4jYTM_XFk_SA7r zJldRTSnq58MiRQ8wPMnLKeN~topkAqe*smzgF+uk%D1IU!H>>UMJVfz>|UtUk%3C) zl9f6u&lEmCU+bPs*itExsOfD|L~)(8To^ucjPOA$A;wzG;b3kI8b;4Af&z`Z5vrc3 zajgoJ*B5GHoIu)2UQz4#WsiJs&PlsVqvA$uwF~Tn$)%xrgVQO0YLwk{ujlr6Tqc>U zQ(WGyv&Yru8q#aOD1aK`{3)f-7m;wXlZr3|)>9%!Ctat4y#|)+kp`BAt{LAm@boKrJ z(RP%P_J>n0+I-ba zPnXEIo{s0UTX`M@ThZ$&Nw82K=oBQ6DTJ=SQWR@+ixEJs zP+M;~sI%*9-o~pA{9GAIF%sB7D1gs}FL~ejIp&YoUp@Q}LjgPE|79p(WMJX^pQni; zwW;_GHkj_0-+(1kh;JuTvl5&1YZ+TxuZhFlC9!VjjO*js6?#A*6w-|~3+`qSM3TFh z7zKK$#4>(`vE?4chX#+b9x0FBmA39iYR=Lg)cBak>K`|stQ7p6ACU9UDDsCzvZOq? zye@Cg)oHz%pNHB}ewHS8f})s~Q86!-9KMykvU<wREW@&2CKtdcW>gzVcRt&CiuQrF4xk+9Rh5StUlHGj@E70e z!$TPqQ69pgg(B(cc_=UlZWb4>S35J=;QQe_hY?S7-rVg*$@`=WoJ;04JR25|-=g>? z<8Kqctx&@C&In}jkHUu!RWtv+$cc4~qyf$I6ZABkC5?L$$E_(UuMhdW zoenLzMYP}5RGx*bGs;Y%%F>J6Tm`u4!{=2A*Rr$+fQaioD%nv8*Ib-y64TMU9rbUy z*2EB0tNCcAGenrDjB+fFQ;)6YQm_h_-N_BS%?KsAbj_=I)4Py??^1PA?Cu5CjdNjZfu;bBUG zuvbF|NZf4F1pcmLY0kQG&3SZ}vEIGHr9$Ztx!SUGSujDQLfc}>zj{DntdkuM&189) zX+^_P(AbB+1Vp&K*zR>#hxH`Ms=Qn*c!n)meK(|gQEb0 zlXPTienxXTo9#fRNYl-;aDCO(HZM`dPp>+=L$m6rpPb#E3KjPOt;Bj$b1Tl@7&=^% zHym6OPCH)}d3J8RB~&Zn8Vp8KxEOwPohD@q*K6N*1lhCvDz-CRom2v3BV0xFjL|6P z8P}rKWm8k$V0+6#>&n)^Xu}2i?6@mhC8^#CZTuL{R?~F*p>B3hZdi-Xg{RG5 z^Auk1oYUSjO{9;a-7>67a{$@l)xJL)6dPSSN?K-f>e9R>WeT!2U%9MGc2TY!xkvnR zlL)M~i@&crbQ^KKGD+?t(u6_m)Zqyw%6+$`Cr1NbBVgVTRQV%#5$_N*(<@} zpYk5pIzrlvh_fa*-Wg5xl+4R#BL{Bh%CKF)QcLaMx9dGPUV%;A@00EX&0Fo|Wo3ws z>xSWsbh{4Me2tBLNc&Vp*HlbvK+Ynewz^c?h<|Gl`mnX)fXJl{2PS`-6<8ZnLCm$C z7q+QAWo^dR`{b5<@{};&BR95iE1;UGVa??ZER9asqFM=GX3V`uCUMgrx!te-_6Fno zyn@3F0>S*J`oi*msV__%jQob54$oXz&PSTF%mAG!C z(7=4`_u%tVg#Xe#m%6U>@fUPR6;Xyp#FZL8n{R`H`2W=reVR->+q~Im&^od4&g5`7 zRm4a9vGLYBmY`P4L+gP;*86<2+gZIosF|4AgV(CM+w(CU*iGVY`Fywu2SdX z^`SxUacnzP4LjP%uN<=b(fLNDr2D1CP5>wRyh7021!XL0wEf#mj?gO7YSj$V;p_hV zh+@pcSYY~5ru#hHD}nuK{^jX>5jBP=eoDWZ{`oxK%Wof2d>!srmLmS9 zaTf6|p8D|N`>y+Z^xKfjkgPxEx+0al=zp>#u@(~}%5m_k*VZPBmyy}rKXJojFRa1c zu*g^ka55cL$b;09C^(RS%~*0a={hKilz!-5E=eOU8cl~)KZ7k1imcdw9lb$)g`qx$ z6))>l)3ggv#4-IdZWb$**0996p5&F>!Ze9S_CO6oHPUoV zm=o{S=)EeyrCL)tE7mq@r$G*;h)y=ia2}Su%vKZ3h_;a1LE=bmFmA_EeTfE&tu`f^ zs{6&h(E<&)1LX$?f9IAu@QnE5sMXdne+sw;OS`

    lO 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 z zFL5B{Uw1N0{(E(N8{pAIE7BsOi5Ql_Cm$lt4sG%vgA|ZE-ylCk@7r>muh?sV#V21& zBK`}Pvriw%R=88O=mJMFkcET#M1&KOLtdUrJ$9`LqVHJg#QyFk=mMz0q1pyXI_NGq zDQSkUTk71uplj{^ZM?&K8v%3={F$oqCL<_ z!pO1Gq3&W`CkGj&qtdjA{0?Wf0t?-P;v!G%#aqeTSm$$q9c^P`6i0xSkr%GFSpJ4v zn}u8|7-Wj@On@K3Y)v6Hv_K)!0l7L75gI*i<8-A?H&fIqjpY6;VVgX8@OdF$jlsJF zy~Pk$0Je`jT<&eZ1S7(_15z+3B9=!t39AU(#9T$;MB#-0L2_q$5Ijm2FX|i1jIRO` zP|pBZ6N}REy}a@q5g0trmdRm-1Rs0-z4}dsK4p%*R=;)6Pgr^2asz$r@}Z)^vNjLN z|Mn;47~M33M|mjd>8xfKBmaGmP1LtM%_-=)-^cE&istHO5N$5_uA{)2 zC$=XfygAgaF)88n9IZw{J^^P|nWzT%Bhh^W{TO`OJqW zdFiY(ii&mfq9tt_`h7Z&;^iq34~6AKI>o@GqA+T;ucZuF(kPG-8_xl&{uua zhJ8fXIK~*`i(6zd*z~CrD7N5e`#z^te0w)$| z@a9cN3k0{sWI&Wq zF|3IwvP`f^eTfnaB|0o7Og4fptR^f@A1ano|vc|$s0H8sgBs6|Np=`Qw43TKb?3CL8a57;i6M$2jv10@r6T;<+`!)wZYG+kIK_`Oi^BQCJsI${u$+FX{ z3O7CDx8@hUD5sJ6&Xg!coFwO!GP=iDRF@>SE4iN&rAsM*g`%CdA1jH1)I-9*NOFO~ zULu5~FrhF(DIqe!5~+oiAk+KIg3>=~_?rfwHL}HwkXX__P9@P|1;pOfuIK13`}y{r zv1RaIrS9_QZkqnxSER)EJo8G(dN;j%b1{jYWZN@TJ&**Fbf}C+FV-@=wX}d3vrkJ- z_*`G48|2nj}_8Lw>V@dc-+M>{U#EHJDH* zBU-QYAJKck(WI=v>jGYC2o^=Vc+_zOY@XxqWD_*WA-WQ|5JQx1g_|&fxpcQS1>i+! zKn{8^#slY2!t=0w&=_KZPe;3dc7IXilYd`zJ~+0eQ+(b^(EE~qk=)}a8*zFR1&=}K z;)4ornf_718X!eKDxSiyf(yjaX}?$N}RQzQZRZDo^^^{^e?MJzCbJ zm%YVwx51hhyw2JLe)9aNC-1cWkCKy*hvt8AQ!@mTx?9#7lwIR zR!=hR58>a{)0n@@H(Ehw=f<4%q01Jrh7gwuZ-nrs&3q=c&&w{U&-RBwbW4@I$Ck)A z!~A`G>HQ7g_BBIKKkD6<@#ysZB1S*=9{CkxvQJI@b7*_#QGT4Xbop#c9P8fg=$rOQ z9#D6D5}BQy z1W^!(mjD2~w?!HT>+xa!%X-DKnnUK<>GQft*T8=Dwgvs;tV93K^$deRN4)zubGRgs zr!M^`+|P;TB^RUTk$*;ZLGUXJfTN0kN)Yot8~nB;0JpNP|gEdZ~JTgcn| zCM0zx9lPjhExA|B2V0V~AxbY-;SoQm| zijpW~yFD$B_y)vQ8zcq(G_`c?c%2qt;*3u`Cl~WLOi#RHzz7LwIG@> zFIVVMEpx2!9NF*(|GhRUe#d-5ob%)d+Bq|F{B`2|@U;i|0VkQD?v^3&@(TM2_aY&W z@M*p5&I#IC;3-K6D5lYPXejjq@f0#={!XK#}I11hS=>xVEM=&5c|7$xXAX?+|O z#3KaqnVdO}RW^;WgPA;y_2iFZ`?|>u1AN+RI((*F+H59$CSnKkF2my)9N#mE_MI9n zuS8%XOOhApfK^-(P;2o_H)(sYex!H;#k|A=X);ph5al}IIz47$lvD|+DqxFrjrgF< z9LsnRnwVv^VCAw#6Y9qFhU|5oi?FA#r=q8Tr$}T$^GgX@pKi-gD(B@Sh`AZ(Uddjr z=v)Il(O7)ONKWTVe*>y=8bejll1;)XHn)t*^=Xb}hUc+mdNJ7Wqoh)^jCE|0 zQF`*p09(y3c*j3Zlql9cULWd`8YsQ=X`>p5QT^Gv1mg~cC{UqjBeWjL(?uk~5;rfrU;p9lF41H$@%oBME4YUVukci;cApuPmT&y8B_S3!d zplN3Xd<`D1ws&2}<<(tB;73TdbFG_d?8*}CKa3W8Ywo6Yx~UBU#kYx)!19ePpEmbG zk3}ipNJejkH^}D{tq1#Bd(0asm3uD3htVI4mB()jOn~(=?lVn5qH&PG>CeJVPXSyW zbGuB9jyjcC_@;9Fj#M$i4TJGJI};gI zg~1sxp_vsP=NZG0@sgW~?Zj<4Ur*O$Y27^BdwiM;wOVk@m6e#LnhI*zA(%R)r>3J@ zg-c!Zvo5oDUZC*YDiV82h~zOSnrT!>Y8L7Tm89Cb>N7RQI)mMaL2B75j^2Mw;3U(i z>?%PlkVW|6pg?RHA;C!{;)^Bf3sehirOqX=CDBE=65Q!7gx63Tv%l^e(=2rgl5rtU z1f!_XR9N^c%J)8|XJh~uFY{hb{144@Il#f8E1lj#DwlDy-9PgCFzOz8cX~3oVXJ;q z&V42`wOigc+zB=7(rj+0WYv{w>jCu#ZeHj-wLmTvB#Pns2KT9UZ{WzKiuX#L@f?#t zk4*t93uqkyJJPNTflmK#S!q>LGjcW3E#%yZ zt|Yf|cv5(BIkG(&QESN!#x+DsWk(t*`*-te>8aYI&(p`dzCD*)Ig*!kwVrkkb$cSg zwyzyJn^cJISJb?k_Z=&;R?X!G|hfuU=XQ zS4d$DM)(lW0$4D+=)A&vQbvTz7^)^yL@2{PjFdpcL$UY#!eF=yO*Uk&xs*rnTEMXs2A=w}+K!)OwoFr#~{ za5kgeX60Yy_cc|I5 z8hve3g&?azG2{+N8DM77B+?zTeK}GRUMH2GF)TsEjK(7Q^I)QYRrec|F<7xVVS&LC zhY*J(hCqi{akT3VZJDyLhs%e_?$s#`g8dY>*{g`jyfG;ICI+jezofJ z?C*SL<@yXA4Bofh*M0#*F1v(76=lVH)x@E>s(DQx)#U1Zht|N=*uGDDq+iHd+oRJ) z*M?6`ngj22Zg-R*LC^}sAx6v9x0YBx|7d*}=PhOxE(L{Fs^WwMPJkIMY$xR>LL`oI z2J#*t>94(qa)oe(9tAl9x)@Ligdjmfc#=7BC4}OzB@)ZdXpbP*5mFIqonBaI?Xw_T zz3ra2PaZmF~0NXl?ILezHSe3i=u~=ul`@W}b^ys<^k}>e56H@~6L<{?r zHDg3mgWUf@W;8G54U4o%_`xPfvXdVkmZ{OSxbYg*lJ?(mBZKEvC!4BK-PG` zwf=+^cb8hyTUk}JsNzyp7`@XebL!K7{)w@3`L;E9ML`=JyA*cB(seN7ed#Jkv9mm-nHy zurO2yVhB8Ldo&h)p;yH#Op}Or3?Bpk?6B3|&95u)rIudb=jI>P`g?WfHlCOFlP7h; zCD+mQ-(MW;cH71cpOx*pymGT~*la6cCo}3uu7lk5ugVS3mV>_Q-{l)fIh73C*w;%{LOtmG87b#nqJtv#q%`nAW<@6Bn^2maGv3 z{4IF0Ew_#hGO1I1+9>xgK@fkKEL@l}!qU*`_sG=)q=_dJlP9ej;MV=DabWD4-AknaT8~inOsK3uk>D+4W~Gm zr?o%@(CDVilC8wA_c*UNE~V<=)NwtkN7aBB{*WdqwqoB zIM94F+dgPxKt+pZeJU@9wf%hHYHmMP8oRupq21qkzpr^vwOinKNNdv{5q@g?FNk&} zclyP{CgE%SyikYm#zf|==mcpW%({Zf1%0XZ3V$#FbVKth0t5x~Z4}ffl!=N9AIc>s zWI4-mmwYUuuu4nHD^ald5eoY3448NoN3c-Pm)+v36Gkfmz!qk!Zq3|kC{MX$ZDlsc z?x0l3swHcm@XDWcM**+{w7LQaZoeT7wgmRbQ{6^QnLbnw(r}T#?RKXh)9-Rg#aU#} z>LJgOpRl}hdbDg8UC+RuKA%FL$)8T2Q=i9zp4gwf^agpVg8i+jqzH_AsgR?a@v6gC z{yDFx3)sb6Md3wbh4I6AF}(>tBv}?bo~F&9e1-nNj?-VOV5R747-Z1V@>MfU=bT7y z#<%cU(^&tp=DN1fxJyAC5Sj{@F1UNwK(R)_t=jy4zR-5fTyfIbmd%i1d(FtGz1dJ> ze5V)yL?g=hAvWEWm8LiOJNK(wfV?w zxCqVMEb8U{mwPu*c(;=O`?FhV!3?p5ut<&aBV!b)?ENP{$~KNbH=MuDG3DB#@J5U% z#h`#lA=6BvN_;D8EuABcBf*X2-sm8Bh&o(6BAgvLFn3PqFP4Xja81NvCY~v&s!q-3 zu2qkZchaAu*D@&V2QE=!iSN{3Pn}0`+V>Q6R`YJ3YICV_m(;*rse)K=eE>{!Z?Z8n zkOZ%JD^-+!03t2`V7rD?x?u)15i16M{(TLQv@e3p}c?DCdx@k?W8y=_k^fB=u`J7rpL z5RVw%Ms53deF46LZfJwYbUze4rTFe#Wj=FxWmn>sw+^rQ@*VYVu(!7IJ*5`*x=GIq zw>3ZdEo*zc&X#X`SRM|3QpFHxm(tt8^#1sFMEkUVr()(&hGS?Qc&@kQl6c?kAbFqq zU`y%adm4t1a5F(c`xM@fEYx(IKVpB)c8eqVViCHo%jT|&-mE{8C%EZ{Mcw!Eu?Vvz zHV+Q^@O^!J)gJH^?|yVYVfQKh$=4B_=@P16Mqa>j0I%t4QL6*ny^Cy`-OR{LjM7X= z%_su>IWaV%ZO`HT(YL+CdiJBS(LN#k=ycsJiXQ8YHsga>K}LQdeGTOI&uY2pBqjN7 z(-d4>y4%vGx6ay>A}s`n?HM@ErY3*qNp}TZg@je$EsjBGnDr1X?A2n<4{r}#$`kD# z0nKDqR1E2P6%yqK&A>q%*AI5;QW^}2Otx^W!8FxM)XJucHSAplxg|q>rURL!`gZLp zti8}$TypYIH?jBdh__!qC;Jr`^e{!_3NoJBTx5)-SdrWxHO{tF(6DC`K84#HwNm%A+B&Y94~Oi9X8MO+6`K{!$`Txe?q z2z`cQtu^L%V(#rcno+GOSI|ox-$TcIeza0;$yM>Zc$7Qx-9p6SVUqQydPm+0A%ZD# zwKTzWbUWl3xANQh5Ms4qcXR(IMd1Odb68+^0WdYG!1mlI<3gI5nOXjJ<7^SXi#zcC z9!7io8DJoO_`tSn%e19WH=CbtiB`K*e`JpVBFTsNo$TW{M~DFOOMF17zVeqno@Bt6 z_447DE>q-I_4l*p!87;Ob1-Bua*rk8`-aXF|MRtJpk;g0< z-eh8b$MoiXoteWo^jpL?9y-Ry={DN4bq*sfs&4F22F05UFxyuumNxV^|<^&<6RY57_u6&6#0j7{}n2oNVi_6o|cjJ zzH|b2l4Y_d110zA@mOT?mHs#M=x1jZwlD~^a!=~n=E3>+8Q>8=*y46anbNvse{Qk!S!hgSBCkLYTNDO<~v zwdsZS_6=TaXXyC!4Bgn&^vw8-L6x>P4Bm1 zwZ*v*^4)8#fuLxW!JIOiSnepO;q&7Qn#uO>4_lMSQC{hj52u{Rj=*@Y{U za~4dRgR2~$(&EB3#4)QBjg1`;{M(_2tJtreR4y>%^09^EliQxCtz2he?71sf+DCAL!kM&`Dn%MLtA=^Kboql zlx1olX;Uy|t51yix$dxCmdD12R`+)fO>8CgU8n}{0STABFu?8&? zF;Q@s3rv!4ANIp@SLeXq=RmtHZ|&>A8W-@z8w>!-7Z>h5vb5sqZO{Y9*XE~AFdS*{ zjEdIT@20})-9-P=yO)=a&yRbHx5XA-@F-AOTwL5cK(gXu*!H|iSj&>h<;8R7{sn6W zch}a|(dAnQH?`{iAcjwXg2LeQGe?fQzw8GEh458SV_P$A9%%xlmDAy?kAC`a{s)+` z$Y~a)&b!fM{>T{Xn+vPEzzwb)9_r9`z{TEpR&fNm4;Bo_3|R0c1bWd|Q~a>fSHaMj zFfO1@bJsSm&TVbUdLny8v&$FPVy=Mw+CtLMX(gSd$lZfQLTYO~H$EeWH$cd^g6N$* zF*SgpE-g=ov2Ws0zsl9*{Imwx3Y~Fr1R3=ZCw$;o**5|VxxCUe z86aoZj!v8!CpShLNGXP)pfkK@OpjSf17+?AHNnRY3;YDX*#$|}=AqU0GSj5MN8u!p&XWf)fQtUwd+WZz18*UON4DyIO~u!6 zB);{Ms%r1E>>xbKTi4jAmwWDH-dm&-9eAi3d>){&nZl}D zK#t=mtwG4pd*5j2i2nQhyay2#Lb^du?+KPCh~ihmA%*8rOU;qm%TYFoiqOM@)Fbbr-~OVXxpu!SYm32|{G4 zY8LnM0wT*Od$(n(UI&r1>gMm%reMH76wtJxJr>7PIdp)-qjYFjk7yN7<+r6%X#0m^VOA4vt8T802h%@m#26-Ra()?5a(tQZZHAQd&=lGzCN`d)35^6s z3|bhxipxkaPnyjhNDu=e1{EQOaG;)&hhk?R171H?%2-R!(v-u!;A|28)u@I42c*~T z>g=>IdvSAnArUga3)hhINp&M$u5KoR@(fha^HU3A$_Y4r5A$)b6ZhM02|jM%_{`!} zk8R%D4DcwMKn}pbiUfXq$0jzgH?$D5pbI+S*>95ZpP-~q zO&n*uu3ZYe!PN+mz)>EGf6#oSouHh2AHQ^ARJ&p{fpzJh)$c6(c4>Gn3bgHX&0oP6PK@Z#AVQT^Z?u62yg)eNomrsr0m-e)$P ztfbakpgq0-v*iGx@N2!zKtU*Y8Mu+5iE$&r9LNBoG%J1OvvclLbx3X*#&@r!}1hu%LyqvV!Pi2ff=?9rsW~5|osQp*OSO{!u2>_ir zEriR`d|lLXI|N2}pG>bkreCPRPcrS_vGOx7sjHWl)d>7K(`-la$`n*_6 z5Ew+f*H3eTpc!Ixft=*kLs9`Us_%M1GQm#Tz&L~-S*$87A>ta^D|%=G>sS6R6C+Jr zvH`Y>2it)k+)E%3E~vta(JMlCw5$>6p>$*#)-_FOz#^{@GoeLKBl%jDQK0d0RZ*bP zgGJ`z(M1gB!qM-bGY){q0x?Al$3igwTft(97{*6u<%Qa!K=XCjdkf1mEW6^RbU}&a z<8c!nczZfSjC@iW5UYPv*dG-OA{agp11J0!!v2NNfH5&p;!~bS{eXC&Ehv%rux%jOJWx*m`RoRXWz|s>0SZu%cTD=^*kt_^qdu**NLAi zco%%1C3jTQ0FGG>;h#$M*oxrUZDHJ~vmk11H|v~$;LF+p%Wd13zc}7uRMCNcc&2u>V~uy=Et~Z$4_TK%Z>{Zi0#`+-J@(sh z7yI-oaNcgH3b1zA{Yh}tm>!u`L#O=S)F%oY7_5JF$zwBR(+DO6SC7lh2e};WlbQxR z(X}v#8e8-_PP%)bCy{Vr`)Z*_%T(N)gT$La7cEHtC7GZ0EKtkk;3^_)3z3J0XeFJ{ zJKJ{6iTu7g9XM_%hq+U;N91Zh&BB;!KnLc?UF>@As{Yl5mCtt`eCNz=Ia);UyO#hG z&;BLxv@oDw{Q+X$Xnf)=&3fO)dF9bVTzlnFjc7&FvU@?x?D)&>onL02!n{)!^0Bmj z&EGy&oMXQ6x()Buxs5nQuXJ@RMnczbSRS~0R`uwg1f57y&<*Q!Gap+V@cHWCciFgr z6(04zys)a9b=k%s3j4uwp7-6~0$tO)n<3>Ur^e0~esbJx+$Y5gK*aAfVMYwM%wwA# zc4V&hTCMg;u7*FJb-itNPmnLHDS|04{(gTn7@Wh%#OBmo`O$IJpu}aXSXvEhulpCA z51O+DzTDfwIEUQ-B!nd{Dtm_Qx`I>K+o^gmIMb@Z@nmgt*VBWK08D^d~qOV;;1xe1o8zHF?ZTQ472(pM-y8mD-UB4%>4;$ zS&wuxkYK)FP=b9K&u2=B3)OIiH$@w0Xo!w35(-4z$(XIJ7Lda)>=I;`(l5~hJ5uj; zNBy#wzP7bDpy0Z*;4ahn+PXjA<)qP`wSXE2QBZ`M+YDaABaT+p73CJKw-a#qM=IM? zAqTp7$3+&6R!#Ct+dJ~X2Hvzkht%j2c1`F~NW#dRb5^ro@wL&x$sp6HP);|_$X!zH z{Y5{Ut4@YyX8%v@qmCa5Lr`~eEKYt3o-}>NCngU2N8QTX+G_u~LA}_MJw?{@Ot6{3 z`PD9*YeT~cr)585ePpAyYuI=DzK$&d?c;s?=?-$;#t-Sy4cq`5omW>A*qWLGRC^hY zgB9!-Ti-3&zd{7jGvFCja-tA8y0aUen+3w^BW4jWF`QT)7xc1m-7k|=!G`1v{+tF^ zo80f5!zpcDv}R*$B1f#I`|YEhJCw+cJkF@t&0g64-q#$oW0U>DDd*JQfq&%Vp8ON) z6>`2t&a6+BiXuJ~^{yOz7cHn~JGiItW&j876erhhp9)%3^k&LjSZJZ!j-gujU6bvB zXeR@xcBK5K*gAYHry5ItA<#+&UeNn`)NZ60N5DZGxRlrV+NZ=-?5X2n!PKjr7ca-E zlOsiFX*K9+ynC#xA+_b{pYq&K65_pg@!V=0xQo%Oht~!(ZWPr!*J)x5c2(oL!qY>` zQ6N~_lqbQb4?XHJ2)nfqW)*n$7G?zB&U&pALoDu$(FAD2y+E44RPMlY+#${maMNjC zFLc|{E|Y3C8`-XfL9_Wl&TGgcMlnBZu|2HJa&FG2HQctH*z2uu1q*EQase9d9rW$3 zORtd2V>5ej^XO&CStK^R3+>lbB{bFYB!Q=Oi1s`@Rg+9Rk&^x${}?HBqX-RYDfXAz z_l~IOT$*k;Sx1^%9vp2D7y_u%dL0ThJP(2k=o&$_sf}lTlIvl%j=RNopMXNZ*EYDTq=xEd}Fx$)sF-v?HF9aCs?O)bYS=Znaw*fIv3 zHY(Q;H(dN+wON=Nj&;0&K5u)_$QW&$ij3NY)DhQ5j-A1246Wo~q81lv%R29ewbEQo za^%aHW-2za+dV3OJ*(59G}!I<@%)FEyq>JY&VkeLy(upGKpokF?ljjP=EZ4=glSBv z>7D3C9?$CBOU1T2o|eO9H|vMXdz`!W#Adk`Vzj97TV2mi&hd~MWGYj%$xaR<9zQWS zy71~QoE&5C(IFAoS0T3v@_S@@F>c@4!M*dni-6=9aK|>_RSUYPoX@}pN4{0W3vYff zm}z%t?+&M@5pnl7YT1Hia@&{JeSs?S!ix+)repm4n$Pr7a*-wH@eQpOUIJj$`saL@O z&){vhC}O{-jr$HfxX~y3wCrn(C&O=!0D~(rbx$x;M~a1KFk@2`{1(Fxu=ciHOoRQ% zd3^5O(nLo)l%C$APr(KWyW9OlcX&(o6NFB8h644d7`2KNL0L?AZP0pV&wA5)Hv3+h z$&bLuOM4g|qf{ac$eCIci{Mwy`@#>s-`Ce}-ij1+h$SXgy>?iCi<2L2=V}b(PGIju zye{7b)S_a*x20j{GwvHrLi!;t==#Tl9_q~9_Zpmv9?39^ybh={F)e}$>ie1buNyU< zn|pSIOnoifpQ?z;@rylV?5zAB9m<cf>v|IRnup6E2R zlm)Oh-d+oezkl6Ca+p-;Nc(_4%TZ5i1|A7NnCpt>>}C9B9AXryQxG%S0dMJf!%4M6 zYk47Wn;BxZZJP{o)GaI-;E(S(C#5h;{FXta6d-p7t%iuH{A$3YMQB@iT#Ud@5bl*C zZpA8X`c%~?*z7fG2GfFbNh1?UrN9$ylvasFn5TChGoF+@E=D`BL;V{{_Dhb6wfL zgcWdXSedry;Svua)v_&!dc&#G7*UTwm7++3UV%sWe$=XSXmO||6;LqL8D-6alLErp z4zf0{S7tMer5-G-Qp0}+q)alT-yT!=F^a>z_wY(+9FPH=5Immrgyy@^Cj#L{?{z5y zle))qW);)-=EuV4p^wn?{jMR3h>kVXMVxmAL}ZOs z%|%prT6e`lMD@MaIXqmj6?XDM!yO&1ynG|*{EWTfCepwQogucxj1{`I-;w0oydf?) zw7=xz)!`8ro<3X)M%g`;IGpW)ryTKZx;brtu`w6 z{qvC=mNJ@eI6ks5T5LN0qKKDULz)SQm)b*GaAJR<)!_5e_C)vZ7DBHH&HG}Dy-$c> z1_>~wFh@lY-8u->nwi--;$bl8L>H?y`msdZmn#_e3d*d%=1x4mBsS%TBg170NA-wy z!de8t>`MNb=h0uZ`u^r1{>MT7<^%8L;p1y{c7d^a6D}P{Qorz-LF7FD4VJ_Hzjpcm zx0#NS?f=Bt9{$3E_hQWT0RsGRLihc*P5+CKj+v2xf%X4K;9+23`M(IZ*WS=}%B$%o z=RFrIz|Cfa<)nlpH6%{~DOiLg{?UN`8j68S37XMy7}4CdB&4Jr?t`j*{*xuOFWqIs@Zei z{*2RHX0~|nc#qyShqruwF7#d6qh0m)e4poFqHki*&j2ABeJr6kF|`HhmhCY(xYiNq}tF^@`Js_^z}*^*j&*n zxyiKV&C;p&q$S^x1fP$+#s{;fu%YQwYqrfF(2nRBi@I~d`S0X!(j8^g!^`{K@t|vj zXojV0pf17PGd<9|Zl@@nGc{&8A$Y}(v;ASJYUMR?$s_6RzJEOR+%yW^eU7ZBvrP}P zj~CY$$aBo1ETYvjftcI7-J+nUl(+I$QR|3uC* zaNz7}qnQ$)&OWL*8-Jxgo=(mLf?0fOczjyJX8IbsUcmDAeYBjU1A072I~!x}l+{$# zfIy0IVIQuadPy4xHSdr9An?#$cAFQWvc=F{KM_HO2Vof@Q?3-j^ zmO6)I(#%v1+E*7_X;^M>3c`ZHnrv}0W#{)|u40uwe`Nma^re2JX1z-pXzd}qZvW0Y zIoQ(IKDX_q(GDuU`8tqIeTul@Q5nrpqyCgjr3u-Jl1a6A!0?&~`G}IK)6n9J%&jy% zWF2w)xjH_4mRq)rz0yh6*46kod)K|AQp?T6tXRv5RLedhWpYT0cGZ^BVVkjSJ*O$1 zq9K)8JjrA1ipY5UiY~u9>P!h>Ch*&Kz06s+qsMQbHLd>(Nd2G2$BiFFm>8M3wPu#>yJ+sg3Y&h8dnjHXx7s$A?+e_6{US(>|vt-2(G_YJZVj2li$S(d&8Wd@vW&dcGyp$KgcRd6rK)u^Zdy_ucyq?e3S>yHj-lE%=sDW!yIN+W z;|1zEqd>4)Cs(~yX@TwW`In{3Q0z@^_@jCsf46M|_VRagt2dXM{k5O|-w-o3yU)Pt zs9OTCmi8c=&IG%7A1b8!KzKXWibgdOGcXora6vq9bk@Lo(lU^-W7Oih`k0oUy4N&~ zLu)fBpIn8_c7qbMDy`zo_35uz&N6Fr<>S;_%_Vz{*>8J$Yw?xG;XZWDt`DX$6<9iN zabw*-iCS&zkg=3kH)9SnbR^L-({Aqg*f@xa%<1uV)TPNG7c`y41oBQ3T9JAsR5oZd z&2nZ#UnYN3^T;{S_w-~=fEwUI)?ax=dEL6|yi&b3eqWI3irJ;SspRU6mKGI&_6|v1 zqWldYlM=tzI&nX?(i}v1st>$N5lorW!_&sbat`7@ak-H0D zy){aknlI`RypLP4yklpvQ`u_x>uRl&~ zK_ShJ%*It9C{d=^htFp%i)e$>q*`&8S^ySkWhWHDqU8!KRCx-3(NZ1K%owVsQKr)d z;#3T>r$i9{!`NBHR1&q@7I$}dcXw#qVdL)ZH16*1H10HwySr=SH12HN-MRcXxw$#X z$;o;7l9g0eJ=9Cpsxjwiuh#=3ZKY@x^3ZhvT>h;2lUpat&|zV{J|M>A?61-~DL!si>P-vh&mD&hUlbbg(*WAtCptR#pVv*D-vS z-YBhBEJ)oLjp4&K%8%ZXc$L32CKS0D5i1 zP$lCHUC>z*6iPMJYZvh{PEf+LHb+b-E&E27Z`XM_+ii2hd_#D{g?URu5?gm)s#f)_ zG&Eu^97|y}n7mLhVy;sM80z=W9h4$WO3Vk$bvkdlaQb^Gi>3~^A-iU$j?h3xTBdS( zYvvOsUajE1boq3UI$MojN=*z_J~Hi;MgNBAIl!C>FU~B_PDQ0tijttfp13!`4_blge zcXps2mpl=n&Y!cMAi-_b9I99qk3wuQr;A3b$iMp$sw?%ETWV}KXZcFDI*$3$yk80e zdZJ@N@t~SXMZ`qS5JpZ+#DDW>$d6(C!dS+3q`#ykVuCV@4L1>&WK+~cV@n%Ob4$BO z+f0L|N6nlVmu2)cN3>L_oM=jaw;3WsCaDPIx4)Rr6uXiv3V5*d@HpSwEuNV){CefR z`VF{u+`RSNQ3l@G&k(IGxV?U#q%KddK)p`?T?KS&bu)7q1CS3C41NFG=jMQIR`aN$Q%3vhs#Rvy!K0PuNbbPjpU1OrWORTJf5Rr?48_2}%8i zxo2i8oj3FElUIQkOO-#J9Cps#VDgxRHdiyuOKlz_!o_AT8VjS)iSew7t6n1VVz;#) zUwdtyPvUN^zR>1Jz(#RaA64B215~Aker3W`Uq286K*PW~-5~=-0T*pmyguDj6EYap zxHg;rTn=wx#VecDQ#>9XV-T|q_&qMwDU#B8?)7(+Xcr6ecRH|w(Za09K~3YUB0|G^ zsFG|{>eZ;5OMQ>4JVTwqp>=>~S~hjTxP?5>Qj%>bGk6XiEw)=E898 zVdYV1)w(u07DhD)QTItr*55O$_b{l4(hOwGcdzL^3w$Avj&S^DG-DQnsb#Ao?`I?x2 zf*%QqFUz&)xYAN+Id}~PgNkX6Ps8+2i6>MPP0aZ0Ju;CNhG|d4b7t|^ z=GW819QGEEllzky-7=}3UwY%NUP)zP()e0q(+x4U(7)h8xru z=dsIs-tOdV5g-#q8fX((LZDm?#KNSK%tzD$5i+%ZrR0*x>O9NCQ&}Um@z~bYL*ac7 ze_puZI}1zsGq)x_ztR-i@d`2%_4=KE!)NB|{PeZ8mHu`g5BR?e^9U1aYqlJJc+2~E z&(y%rOye#&p6s>vfUaNGbkB4r*71)NBVVzHAx~Sz!u$QU;>}ND`pnUjJZSfj{^WZ! z+<(CO-iY?kyv$&ADiuZ5N%&~8@0NHBA|^r2h1Ov_e1{&V?+EwS zLf8E5Wf_;?+#f#=1(A^b)Vne@v-kIfB+lVBk7v&3LCD$SOh44t8peZ}%DIxtd>sWU zef^FO8^kp@OBDG{FREouL{xu$w|)Af;>I5GS=WG+eW3V-43#TWm;H&qrL z2PX%82OkHI%YHo$olA;yO{V~zkjTX$8lLC)7u^qnYlZljbIB;-8L z!bI9S8V*cN>3PV}%pWI$erMN-U)6(vhy%ZRF+od7;tQWw^Ukl1P2&KkHj9GBn{(`l zJmT#d+RqRr&D9=4!`00-8`>Hg#=n!*TY?>q0R>W9Td$>ur&W6$Zk*|R8(I1ePAy=b z-JmZ}7Y0USV4arAuM8}~%9QIy4y+92C>e3dinUbHCpR zJRGjfI^GDYbmb6TDf4pG@dnKDqaM#4^UX_Y zR#h%EpLMuFyZwoCKr?xZ?H^m*WTl-{ppbwOaZ? zbx|IG;1Ym-IBiZIGo=6=E`s?3Uy99xR?U|(+^bQ}CY^@%4sB9EvHgBe$i?R#;rH4Z zUmu`EK$Fn&^5~X$(zpiF^I|}J5PTb&R$rdF8QZ~U55WFgojE&(!8t1rH91I9kv)uv zaIPEZ?d9qoN=g;rZh;tJz!Gx=Io#;!o+Ac5YF=beFj@*TALNs#=hvVv)2Q7$=wcJy3VG!iTo2Ol3k_LGV)F|Fc+(wx)3{^n%l zhr{Bx(8k7LPj@i_&mAlCIckaDe|Vzd_*dT7BmEhJf52O18ULm0UkP?a%ga#z)jFZC zZ`sR_XMCj*bdQe9cboP#=ojT*C!H_oLC-JMhQ19}md&Yw4aJfY1314zW7}!}&x1s@hh{&ig0a z{@C7yi`fsoUci@(ryn4I{tlTvAQ^LFI`|hiHTjX}^!DMqh5Av&vgXIZ6Z1n_wp`u| z;GAM8W>h=;dVv6eQitBxs8f$U>v~&iNuS&ouVA>gnWaHWPp&HcA^Hp<(udTkI^;8e zrsjbm-%mr&vVrO`x@|gKsBzC5liv6T+HyPXa=4rEBfXQo{CCt`BAm0S211z%Und2K zzNa(zFa3+zsoGo{*o(T1c2;e)#}{J%=NNODucd|Td(Wzhy{@)tvj}(`6u5I(&(WVve9x%77xl3-#7RyK zVNVgMN-N-JoZR+XU6eOCd0cx7_n&!n=gH)x%NcuO+Ti;qY}}>IRAfmzJ7$y!6QvS+7OD0E;7O*-?dkcxq_qYTSPC27hA5skl z;3$}7$;S>`Zv+)UQ{k$21<^L1-zFrTbngb9XSsTH)hO2vK)Q|V6DCa7jFC6Ya-1W1 z1tyX3ys%)?es>365E&h+P_8Cx^UL)-K2e5d(^oI`HsE;CB5XbH%eo}zZ**s zE@qjwM}wgT0|(dUTFaK<>S%83L5}0pq!_SwXqH2Q5!s4}VaIkON3kC|$UqcH4hXYv zV`*&UFHW3T3_DPK} zBfUf)pMmAD2|mI9;K5l;8t7ZIKlcDd#8 z$m>3WA_>BuDH2Ame=}EkQm6ey*N(+v52ihD&1{g2TD0f_N#W$ zMDF3MkBnT?>BA*XqQTrO@f;a<)SbG383cL{U=~W*ME(wN#8i}yu_R70yS2jY$dYA|0-5OD2k^Pli2^X81$G3vukbm3V8mh^`XpC!AkGx|0V#RURIN z@k9(CxY(9Kj{|XE$h?dFQ_31^kDQ5E(uy2*6&%A}pim7m5Ht55Q}*Uz)5AJ6u%hXw zIwV^xxZ6+29IC3skXWBCS)Ovr*4i9|W z-Wgk86z88@%Q!D;6h`4?29#ik?07#22*YqNqV^DBdTpxuq%e%Y62FyIH*R_~sVF*# zwRa>e`@xs6zrJu3;ocwGc@}oKBxTZqPK*l98w@j(F~3TvF|Nr+{@6)zu%W^*s?JG|Z@x0%46L$hU3p$|K&>e`-58Q6-2D8Wos)4J&!w!YD&zl7Up{wGB z)^|vFg7xUHE?Pa{%5aoKg#a1OxYy{1>cYHQo5cM};;i@!P~!Wh(b||g41_pwf#|A{ zLZQzRFo&Av`pa zNnnDZ#E2Lyd`T!gB^x>cQGMzVvTzA|gzza@JV@BWR}cb)DEmnQj6To<(R`9)2+CXF z{oeD%@|4Axb(|3YtU+xcuyD++v-?=Q8(0+YTtW9j&4Fepvv=ZSx#UVPkP8G#o_Tap z{3yY`MB)R%NO#45r0HhCtC&R_iRrP%a*nZsUqFfzKt!BaBuH>Xs?gtQLBZ2TV%wX8 z*jf!E8JqMOKyOp&@7mJmxe0M;4*EhQ9)#6lYT^7qU7d!1V8MVo99x+x*@z3IN}9qO zMbuDl$|i2C0)=*wdX(fNipLp6~nGob6Nfh$w+98o_ZR4GGJ zyy`I^a*K^c$(GFfk(4NccCpei1YqV#K#foPUN~2-x*oIg&2uu$&j~Yh=DFR9;l$;0 z1mMhs)2P6SXpTo!(HrpG7LRlO==X2er#2#9UR=~YXcIOxj!v|ihJ5HxhY%uF+J(p; z9uqgX>F@(9;zg`9Gl3yomVhtNG6X>aLiZz>bL0c0x5%z-iz|-Ff5t0Th)5G3NUQ(2 zXKfn?1hf4duYwe_yLGp~_s>7tcF!7lja4jvAY!VnR)z53jy1GGXA%0?b#om>YF{?+ zUhr6A%de3lyYl3;bVGad6WsQJjtc)c$LBxKWvSk@xN`2J`zJ(C1B!leL5$kR?x^><&(RJz5@mH zkrDkv3ss#!9Ed*)& zLp#ldvVge>fS?q0pNkrBz;EeF!yJbdhXv>Ui#N#li88D;+d6(-C?^rJ+rJbIH8@~$ z^Evsw_&gn^q?h!(CXtm4*#%vSg&Oq3j)NMEsKz4}-?U=!y&t#%O^ZKS@mpM*Giv+V zL+x+-I!z+!p1GYf@>F`*2d^MO42RYf#X~@@3+KQ|XCNb1!_B;(O3;$RLwGWjW`ejV zR!a`lu%Ue+iIxf3^{@OMJ(DuAq)(avJOnxHRBOu8=cQeLTUqJ0s3NxJs?ZZ+VSQ_s zjR0fVDnFDoN;O*Kk|qdfPn71;{%fOXxw_SmKTL?>Z_Lb~yFP!IR}HX?M6gUr4~<7x zAYxcDm)N;&1FwV*gWsrkY*6xE3(E4tPE;*)S1dg3E#q955U!}&*897zlT|I?Rpo*l zA$zEIT@bw8`v`adu@-SEwV<9vMm{?iVM3>Ccbfj88rn8ZIXGXDiKvvFzdpq%3~@B} zUpYfk|LmqqNZ!@QMb%IKy(&zH-$HHM58i^iS8+nDyD@ii&^g2{!UtU!9ALQ4D$+#1 zhY2w=_CyP7R?4QTvjDkdwzOW#I_%Wy{uHZ?X0Ld?RMZQ@J|6esR{Em##0yF@JIoj* z$aGTxM78wu%vu%&euPE)hzW8nw|2K?T{8LbpZHyvK zwAj81yXOrQHY`zQdLx?){In6LHA|*td)=&bRa`NN!B1Zuk9&2BoB}t@k$EGtNL!ym zbN;M5l+r+9U>)2ZJNA0sPDWgx!WP;mr-mG(bKV{=Ts?_gB>Im6MJsj>_|GRCfvbp{ z?$An5yH6H_@@HfgdKOT((+EfET}SYm{Y{Z)JvhJ2!_|Q`t12+*u)g`RFs%XNwesab zPtq3fK7V?x3S^T2X6rqSbAG6-pFWk!`at03OQg^gEs`8hvjo)wrgxKWCf7%MlvLI1IN@faLUqDA`ya3_?K-MuSqN~Er=tOzKQObh` z0M+%+Bv+c0?W*^51B-7U1SF9?oH%aXx*Y#*7h4(GR@9E+2L&Y~e607ol=@{s!XHQmZ&CtVre$PoC!dySI!GBGO=QtemxsB? zk5gJNm=|eRp{OV2fF3JS%)*we6+?bbX{JYJ12qHR9L z$V`f(C~iinz}_hQ5Amao=rr0tE9wXeSz?o7L0#_d#^JQOh@H+knmY(amQA+0eyAH* zx8td86{ohTvM=z9sf%Q$w9!M}XqPN?QMnM(Ynd5`zyM<1Dj2!=DRgX)hg!RC#WQ{% z!YNrQU+KF!m4aX!WmSz-kzs1LB3_1B7{cVB`n$N#(z~LI=P3n)Thxq`^Dz!ym*GA# z@y|fJ$8;?f*373VOR{OT&0$f-G3Ewh$qh4zX)$dB#eQ*xA5Z_<7+D*%iVZ%pKC=TT z5j`I%wwp+N^WZw{mH$K&TcytLjtVrM*qA@Xrv{TOsajQVOQJu zg#Oskd0?s8&2!SkJ;t&PhuE{(tnfC}I-rf+IC0CtYgZ)?Cwpgsb7_Notq8Y3MHUzc zS*Wa4%a?9-W_xq0RNGXo?n)(8V=DFb$`BY3WL7M*iNA0%(YCQaHy9!_CUu%OZPQm< zeUorRmf6u)YTk_dVe_}Y61JIc$4;l_@Cf&Zv|wr~4+hl`c0xCCSU^RdA3WQ3_er~N z%}o@YRN7w&FN%{JP?Ob+>yk)M6GcyKD)>}nh4$sWC-3`Sa$5B*kSukVZ*yDn$*a@kq}61i7}pK3Rg?FAlFfl+VPqTb#nn1x*cls!wq1Qf$8TJrVsHR?<)U%s4E;<3z`Y}_*q1rIkh}xg6 z(u9gRcBPjK^G`T>OifE|*QtypFH(kgTlQRrEnzb|J>ODfd3>Il`H%KA(M`B3x`R4De2rbhu;$eV(nh z2E7)tMzVqQSz$}4zijF!#(ifxNrYTd1>}H3D%$C9AyPR`Lzg$HWX)*uHL24Br5au9 zyek3kF14K(<~dQ_v4k0C^5+)mpQR3|HhaQ*8$*EJd{5OX4p@NTUZ}tCIN|NPj-~%a z*Y!r)k;j7?l82#>HnX%n_=_@pYDa`Wuk0rum$c=(qC3hf?{TKW`8dI~8H|o+&$7&s z7vVibz9#A!(!5X&^ebUO`!fO3^H96g&=oXWbA5BZ(s@a}1{Z_>9A1g`n~YGZYoLF9 zW3)dHSTm0>-l{hDM<;Ag9-(A;@AX3xgPscY2B0R3DJ@PzjdH*39Fp*P0V!i$@~-gR zLKLp{0+zQkw1hAeb^5$y8ygvA9;w3zqzAfy_^Z1<#Px7~O(ufLbK0#w8DaxXujo5d zehBk$4Aq`PF%06A@Whc4{s%xEH>&=$pwFb;Th|a`gXNY^6-4<9{DxSzNQ5(`(?KxD^sJQ8}IN5HieDP&2EbqXnB_ za4IN$gzRJeduzy(;H2PfI+d5VvmNN8%CbQ}O;vU7q_sqsu^<9dYTN$-0tDFfJ6)pW z`=}O4&X2eo4|c=GJfi=?DsoN~n3nD48zTyV#poE5dkZ~)R`D!Zy9qQh4iIf{^;LIN ze%q7_-Ek?mEz_+_@h`r_L_?yQZF}p&X%+fZm57a_ztKYlcKk{iPW#yr$tEy$H;f!G zGA31(sw$akVV=ugB#!)5f?E3AHiM@*;YB`i!=gx_`LBH6x$8|opTCHKe)mAxd_;vm&5=0D3NA0;zyL8by-pjaBBO)i$b;zO_Aa% z%(}OdU1qwvRFP71Vw_^)rf!BxIXohOulye=@|`c5`7;f5s$9Ox?=MF}qDNlJ?Oi5; za=p{0WVv3pX?o@Kw4Wy9rWdljjR%&r^hb-ymVs%`4pH+}B`I>3mh`}Lv{G%@vR^ra z$cOLA!>d$W#pCl1^HXM)14p{6KJHxVn2l8mjzEOVjs98B#K zO|OJ<%}RP%_}hlv=59GO0gl~wk8(SLE+*|>*szkc+6 zd_K;`%SQy6{7ERb9?K^6^f#vx7fy9FO0{T^*}2$6p8Kr=o<>ZGjZkFf+DGS28g)5$=e5pU49wp5uu5I#s>kE%Yyau1OYr*%f4bg#5MJ{lW4ni2If! z72HsVkXH-tjd11h8oH@^lpl737>=8R7#_Ma^7WR#fxv#F?)iCzqu7)GfmdvVGfW?i zmG~#i-=&uIJEDM5C10*LJZ=^%`iV#W_9Sw+{mDuG`O>w^0-hB>r=Ko08+?J)p?Gd{A_z>rj*EzXo=UeEj4U$HtPT?nzL1XwY~f zA&frs{N#;S#z)%_TY{*eO=euvM_b&G(ls@1f3NVj1g-Gr0@Ff59$3vqC7=D*iVNp0 za7`wdmqr_C!+L^NT5fUJc?1=tIwn@w7Z!jE1wkDC)PBBU=2%-R>E7TdEc(Xz^}8vS zu4%x7@ER3OSTrQ}f1?_>?vIEW!b21N-X=t=?=v`(28djH84SOngt-xi%;JFT4SUrg zJVr)+RjYhF+e_QqfP zy05J@V1HfiY+kU2kxFn)@lEx}XU!g}d?(8s5yupw=L25Lq|#(vJ73oCT7kULl2qB_ zmKB-ksqVD1OlcgIp(2zlS!wF&hu8vG^b$ZOzml%%RKezI0o`)te>CfRYQCd1<;?l{5B#FQzwB__O%)_%nT|o8-7qXg0dEXB>b!S<4d*8ZI3Vhz&=}7L)#WV?&=S z)b_gDzJgm>`Ha0j9eX>r#EOarP2)u6*S=>*3#!{^K(jIf9x5OAtDxry7r^KWpT?Gp zuiX1(6u%>}(EDR9I`yTVpzrf3VK8GOB*MGUN=u-1>1!=$+A>4aIX-W+`fUeJab6Pb z)zm~LCq(Q!etj_h&}=nh7>R-Horbn8{4!Z~&YQ5$-NkKTv1;yFi~l~L`}CxfyLe-6 zt;}8UHOKrMNSJ?VYPO@^+RkIto3}dkE(0|UheHE%)I5Q=(eu1Cg`bP9i^pBw;oRZP;rJo@ zU+gSqZbMtDXYOsAbU{ca(d*5+-8l^~4{NLpqhd)vLP=5qNJ+t#ul{V`{8!Jq|^+{aCv zGvY9aDTRyltZ)g&j7g}C%w5>f#MAg{k{9Y32}Yeq4Xg6s?#r0RYxM1#h72u=Z&cwf zHj&M@GvDAwK!&)USg*DJrn0Nca$44s?Mk-TUn+# z6Nd5!$z`e!ppMbC%Le~DMD%RVB=gvwG;i4|D79?Fjhm;H0SBZQA5ef27=Qa z?W^;v9s)KlBiVVp2UXV%Tx-PHVaT}@XRcYp;-uvH8K_WQmE*G>=8IW6zE=970@Sa0PD(1GebJ20VWx(Y9El>s4Xo1TU_9nJ#gTg zcWpSaTNwULudtl07WG~_GmqI4XzIFXLiuCCCS69`Zf3f4^IjCbG_+!+t{xYDjXixe|+`ud1>_&lU^j}v_1z~YD z;n%4!`B~i$pB?4S3k;tQa-+)H_gmq0bf4CxUsk$==sxyix2o*sL>-Y=R3$ z!Rm)n^YzEPKu_mDzqkG#+YI;UJDWZ`w;M3W=xPmSY?Ue`@0fH3G%X`n&o7}h%rDGP zOi9gm&ELoVmjWJv7w`8WG(%eN_K(a_j{3nJE_t03c7a+#*(x`bgBpDWq7CMl2o;fx zm=ke3R!SdBLu9AxHcgQ_#Wl*UNvmJEuDe9-Vf@}JQ z70YM351X%ougV{7{we{I0U`m~0SbWgn%>f1f|%=n;+M7~m&68(%F@c3Fcp4J%{y+z zrolurtZ9p!uXhA#(3;Db_V2n}5&Uu3UQ}uKR;V_2EIqObxCr`sxt|UYRMYkJt!S9r z7gP%Xaz6h2S_T(fg&Zk<;8%pM;;nqzCdA>cP8msn}i%eNC>wGzobD%; z+sbJ5y3*4TKKo4;(he{3@8D#Jx(n*+z!AsW*@FLva`7Zr3 ze%lDdo~%tZWR9>vJGJ8+(9qYZu63xstl3f()Dbig)b{)RD*6CCwOxW}In`Y%$t>5P zlWAO`^HTOcZ{=`w0P@)f_^iEkE9bT>emvwqpAu{`Rub{r2v*Sfi9R=&PAudaDlM`* zco^E`ZyufT3Z3ykhwL8~t&QHN+^FhGxi1D^a;+aIb#|vbLl_0R6+;xs-bb0tHf!C& z+MUC(tH*}|)C!%|OVwX1{#9I72&xyW@2jV%TUIcs>DP2{6r;3a5Z zL{8f5kucvo+MMzFpP&SpFHrqM&CTX)QB~^WhoS0)==H>Z7lkLhRF0NGrO~D6PXrGk zNY?q6;s;=m5*#Fj(4#Fo(rI^aK<1u^b4l62y#({f>>NRbY11V*h$4HChg9y^bBCnU zFD(C4ZdG|)j`QcICP6umwo}WV$#7cq8K(G&s5vaFr*)Q`uxOzTv1;#g!`t5$>L-8K zwI@rW+!nL@DL~hL?S+1I=9t&+^J5gM5A56KFO!e;7UZ}h{4S>fJN}PL&wG64k_-?bpm(+@@eGinZr`hQ6>Cosijokg8B zA$D5IzX$EK6dH|fVP8e*+XlkL4HMI7$TM)@qQmpvVhUAjxxMN0o^t$Ki|$JuKU+QU z2STrn`&TaigRtlAU*MfPk4-8;|LrUz^pJg@hwPN7VXT03kA@2J4i4ax_HM zf6M+&=KL4*?S{hsG-gO_<4qZdEme> z>89zJ=xS(c=!K6sV`WeIB&bKR+6uysNV=#e?9tQ9`)b}nlOGcT$fuE3%mD&|o0x*8 z30DzB_xud#0a%Y^JIf7UVU|R67PjG=5?SnWpK?gh(lnN)>u8gmsH(`!hNuahZ=lbs%GZsWNsdvAZ@+IOpy??1olA5067hTtouL}? zba1@E%}Ujr=UVR_|C$GEpfNf>cy#ccY4$35_EdgB8@&SFK08KDmY-tnj_`^-p7dYa zo1k|t8G4~<_1uv~xrt}rEmuMWZZvNUyS8N$1LPj>eO$O#uX@nF1ns1Hr~L^$c8DOH zsE<%9O9VFspLEz*e^7X6)A}&DZ|iRu`*kGbAO<<~hT+thYgg3LftHsnV6~WuY|&A+)5oj}edKg_rz~|cvAFRW z_Li?Ms$^nkczbjVyuPdL&Mxe?xHY)61l=+J{fPH%y?SuWdn5Glzt#N)GV=i|Y4@}z ztXj_z(f#idtT3kx@%#pR#SR_2$6NG?yzqGCTzO%U|kzZVpuZTA{BPz_}xd`}2cSgVde)<=d`ATy? z_zrzh{R7;&v4V4LaVtyQO+(hUTK@5$&b)4R0w-~X8T@bfnI4ep2l8!=+AaTQnKQY~ zh|>g7$Qu#Ii)_knw=VUEB69V{2xD4WOC2?&9t$?i%jO?h@|$P&FhLHu4LNkh~IV{By>omxXMV1!!=T z9;K#>H=>I*TkfjtPeF%=a@?HfJ->fxnf~Iof~X@knLA* zod4vet#+?ZcnAntoO>&5H`S2vdtB@@`7BFQf35r$vwyIyXk9$Rzj5z`zk5UKGlRSJ z;YFXGg=e_9oIi~6Jf~AJcbvVmY+#=!amZ4MGgzoN`FB1SYcs-`lEt0{pEdbyYzxl* z_NeS@+l9zzYOMs91nQgNOo|4}ahi&hCUn5#I} zI9HCP8^KJ8W{-xCo{YAID+=7hD%o7+d_nlgEeKPMP9x&-sAeNHeBQfPu67%6rmJiG zX>{66J63rU!*%(-!4Ci=O-=YeUG}WsA^2d}&B{!_2pg2DnJyeid`I8m!P8USAmw-) ztKw~G&45cT*k89J`Q0P_1G&AO$Xh@5vu1nNCDy#AjG)%Err_B*(b0 zxh0^m;NYaz;Jn=4S8OMqnR}~!3q$R5rYtI>#2c262nu|o&acMt?aX4NfP9ISfpg}G za?YG<>ZnLV%4mq}HdWcGj$X}Q!U1v@!V;q`(Lu<)E_ojr{9o2Tk0(3oi{39D%G!yy zKLU$;?RVcG+TSO#M!%A1XGRP~0$vk-;~%`e<`bXgFh@#Zo>o=Q+SUYff0&Ll7au$+ zzkI#~dsT|&{;hLlDGy)}c)@Kv*nS_i>wj;5SwHX~yaA*;ZK0GV7jO#Ks$Na^j`O4+ zxLAK$)?Ixqy)$P9y)?1}7=61AeZaLkR5iBZN5#bTG9?k{%iR)dCuMV z!=TwK&35T)+DqU_<#PYpo6}KjYx&l{nob$9{|vJU*lUy{k7YzLiaL0JqP5EM5~vKx zPqAf*;-S%03MW}*1x~p@Zr~!1Xo^`2lt*Q!GN3p5Z1SuQ)pM5YnhqvBdKx}cXF2MP zTyBpIf8oF3V+QIxNM~fC*Dler9h8kJ1^V({|5j(gtAvSOW>D2|lIu!m^|Hmi$uhOG zy&JOSRc&<7_pCOW_~)ASR%me7zTn`G*8(hv=+Ap=)o%%@hp_Uyn_t=?t3>9m$1a`Xt%b~hu>{LR2KG< zh=L_*8eG|bjQmXP8)TKy8U}V4xp0O{byf=ec5p)EOni@d=VY(?KRYu15b@E0^hZ7)GsJ$tc{%l zrBqnCpEx{BxmOP62DP>CDU;FHQR#YN9E-2NkNU>OM(Lw*E>y~7Pe;^(2jw0liL@Ud zhx=Nw7hd5C5!&-8V`1M7$qSP1Ps$aW&^SN&Mb{r|Po4u<$pJ*;gjP}%Jp1#DhCgZ* zuQOj#z}CzvU(MR3e%&cTD%*Tob>E&m$E_(3Sh2!C>>MKJvlBl+#L^}#xN$w~TQfkg zf`}0Vw}zHr(_?-f*b4XS_++6c&S0U%I9*qmnv@)?ZO za)_PknmkmE1%rnML23Ys0u>Poyb#8DuxyYN^f37Mz!;2xuyW2I1%%+B)i*u*1MdOk z%$VfzPU0gkQVK@35>%u>Nb=z4Ejg)3UngkMury2;+>pSK&-;7ZI{Ue#zwys0SQY_d z_Yf5(8y{j)Fz^gq*KjibzO4&b6=ayel0p_fik$^>P;SsN*kUkP$C6YLJQ9L1d?+HZ zAiZZWrkO~o0|)|5RQv}o z66|yn#Zc@5cYg9`Y1yS`$C&|G>!>yHwXoolu~-y}{E*noKAtuJOc1tT)DVUKr7nm6{3FN zXj5n?Irv6MQC7WrS*GDXSphUV)HvwoadVOUC^}UpE9|G#tW+pmVC}&LL}B;Gfq=R$GdC6BSqhf35a+w(`dR>v! z<3gxfs#l?}uuh}|QPQi^gb%zQ4~Y}{E0ak9?g}QsQ1r4W5F#O@$kzXXNW~?cI{=a2 z5UCDa!j4;*vO+^V3&|X;rzp83yO+)bQYfT#91Xjf7c#{eYXVE&l*UdqX`Lc`o1{^a za(W9a!yt9c9S0*eiDCwJJOrp90Ai3AEKZ4q5oCBQg#s5Tv>*>UYA_fHzVVNinh}(i zq5NO}`Fdd>g%hc_P~Wkba3oKOI0{Q}3E;Yn0-=bbJR{+s3Ggw-BxcQmF0kp~mZP?G zNR1lFnl3N93c8_kNzij$0pp%^{JD56{{Jd7l~(d9Yu2Mbcq z54uR2s1xMFO)-np0c+6+tBl!8W86Csu521Ecr*nvLeO$EXErlB5f)xNNeUG)taA_r zG#m#fIgu0+CNxrc{kA&jx8f%Q_{!K2HQdUi)bx9W%|sB~6qJNuc9gUcFe0mCDHZsJ zRJW4zoMU~`Eg5>t%d-9ZbOru1_n?u=!$*VqZe{+8;`ghkOkDMXC z6Ezl>d9;Q^;JOztvBJE8Z&?u@cw|-QLTzz?;8*tu^(;?kd#o~8pl`72@-uYu|NZCS z&tKK}n2fm1)CeBQ=j~K3Jz;lBJ|vtTPlSWln$2i2S`c)_#$Od`fsMj>%T>H-e7R!J zy5_a+&{>WB7$<2Cxpv<%Su+Qt3{;Y;wV!pkymZ1bN&mS@LHz-~ZM|WB&h~J`u9Rw zuvqq?PvLuLcaB8NaaXA2_d5AXSDW&==eK&Ol@oP&Q5Xq$K1C)98NK~c5v=;WA$3cn zTN<<_Rj-Ix5vx#><4QuVjFXozz%uz;*$}kZ%*^Y`CB6UP_X$u`YtHO zNs<2nzOUy_wPq}Rt{e<`OjY`cu3&DX3wuB;YFMr!=xGXT@W#R?cdSArYYPi{&EhSW zcDl>)%cwD8PQOA8dCLH7;Azf~jZi)imJMA#@`j~2Ham(%Ky?s-z4^L3#%bU~?B^0R zz>j5NCiEu7GAlFg+B>#6DI` z2aUsCp9{u#{ensL<^kmmpFNcIRm1tw*xpFBFAhQb&^>w&pUJ6 zi+8{+$vOv0d;?R|Uu6ZoI`M(J8mv2DqqrX_J`5f7X)Y6jZX9x?-B0I(J^WXsF}zh) ze1Vln=4zhWE2<;)o~7Q5b~3nj+u@(*H^a-jYc5Oy0ugcvu&m7CW&@03MGFTM0U5%_ zzQ@SCJI3M-)`56PiI>p40w86JzdNfaAFm0SI>qt*D^fn0x|Fg~i75XYlfo#=kp>DB z{f&%7HtN>`L9TK@f5b)qX2w>GLaf3dVo)()n%B=9W0$xKXm zG`WrsZFf@=`9kuWC>=-j^to5TWo)Qub{_2vZheJN1(2<3Kz?qDP*M$CxH;V$E10b z^t1t45X_55MKJB|4&Zegot300?qjg#3+g4^$l|++2={d`L9!;9c3nH_cLtAePoz3M zg&j4xrchs@^+ml1O#(aULowM@(p>mjK8qoZ+F{`ne-fIq3{fn56$ed@9MvNydB-6u zm(yq?7^~1kl!qTlhZ`#!fkPGz*gDFRaq0zXOGUj=%tvsSQ`WRz#8f`EfddxVorTh3 zR%EB?OyYHSmE_|xi|c38M^4H>N(g}Ep4*GNq;+nr>f_}<3tM-oWF@j6&D(b}MeY+M z{g42(F;DnK7tsYK+y2dyCbN?-5zoDPo@Z#%#leuG^PD)N-TpmDrM0YVP{W?7_bZ8F zg<6X%xUmNXe}XWTh#QGm0B`IZguW+#qb3a40M~4N2@$y`XrHr><{P--a$wT-ac0Nb z4@q6@4V%g2f9|OSm@E|dSZClPy}S_fKFw|#IAK~v%rdU}_q?M(?~&uMb<(_@u+w3#T?qEYlMOtdgA8PcHgM zgei3vb`QV{yppUBDD3=SVjdJ$|1rB`JEL#>kl2d3qcwDP(}==#k4#LC>V*4j{6_oU zK*l|13J1O4`3ac634o@v4QzBvCC6T3_+@Iu7Q-*Q6X5~SQIJ>e+Wy#il71CcL{Hiq<4XE4N%$GtS+!c5LaN6hIC(M zP&!_8E4kj>BgGdLb5zjd+uw1fJGJmh z2`Hck`us-IpgEsQgSp3XlIQNJTrQ)j#_A;V{wvMIKraRUt*#s+;C{cD1uSuQ1p9qw zWc{V!w`4RZOa9Z>PwIr!OU^eAf9oO-{m zi`eb2eeD=2Zi2CX%?0;y;qW>RH2xfee|I%ci^k~hB@Xmut*X%5ztR@#T3$}Sx=;1X zgZGy)I;*rrZ>+JQw3(Zbo>rsE29q(SExhX{Bm9d=J77l}EhsTL$+N=!@dvCFoID+? zIT>j9XS)sVqrRf>4#pxrkVPu5hV{uy_rcT!{Um!e?tc&rcaqA3Wjg>5NThd#vL$ot zkkQe%d~rFHLT?`Lv+2q~vcXETC88G-kg|ONE-A~&!zcSwGQVV1bjv-#G9>r{dXFaF zEGlY7j!D7ZF=|>HI!5`Q6>dvYsE73i`OWM>vg;lz$%a3b6+crpkB(B^x+DXfObUl0 zP9EQrGDKQCI3!$g74kd;S`qxzM*g|o$G~#X=WH?`()5^l?x9LMZNsyE_9Gk>U)XWPZ;+X z%+_l-99NARFIU@{?b?iC+!6mUeq(zTZ|m+2E*YGZq}lhU0&}Yf)wAarmJg2m9K!AB z$u4}=ljiU^B}@EQbm?@-0O;m%ephWyP*1(AYZu+ zXv4}FkHUkjAHdR1Gds(Jub02yPkV2_ir@PtMNOdUjB|yc1n)53taDxM8ZzY`NoU9k zj>eDvK;`}*gj`#KM=qp}WJckT{*$wqmA{T0>!)Y$>Vy#|{rH7FE|dL(u&%a0tn$2-$w4dg5%n+C7~JFsL``{w zJyO|=nqk3&r=LjdKO`|GEd{G4y?LWJBA}zo1_g6TA71nCeiVHdr`56J zsiJth6}tI~icI}EzXHcj`;s88yRvxy`g3llEq_?)BT@Ei7|O5nEnu-E`7GMtv(+f8 zjstOBu?O)Dnk*ar@*Tn5IGb-jRMMxh5xZq>44;$4-wH)Fz%E6OF1{zZk~*5vVB1+p z*D*sFw>|@11$P(FH3_mXQf4niZ4xo44kQ~w4Cao$u|;s6xj6c2;72l`X2TJ_q|I62 zQ6W~vZR4L3%*TMHd!nQ4q7@gTL61z?##0eq4fP`rTLsN;)Yy_xM1j-Ix+8Q5dh>3?>Y&tH zlaYhFUz_pZ2fXu`%l98>soyr0|F!Cxp~GeZ@9xuN)do@si@wH<#wzfRK3VmA7Q*`1 zoP%{~)>WTu0y}ACIN1RDs8N18ll|;J*wUoc7`YGZpn#*Y6kVocrCbOpjoc{S`gmFD z_CuNjRiL(-cXJGcCta$lc)TX|M4fd=U81hI#j7OSXuKiyy{DrZZ@aoAlKmfWDed2I z>EFN527V%v&py%M?41;9a7wF;&l%>-OK(8R*VP$!SXP0F&SfrAt<~e~w^l38oE1H;Y^8p24-C0y zs>(fM_@B^E;eVo^|9fsE=szVoT3H13TNJN%S=`^>VPcT|c(6~}xLA9_JZvnT|E^r2 zj`$#Pp?^O7%Gb>X2!T54!JL8G%;F*sSgD66j9CoycjW}L@c=42S;B03 zAM_n7G+7l9O^&ui3SePA9yyc7#Z7Dsap%l|8VGh^oe;_nho5Oa=>q@U(vXQ%tH{(% z!{g4NB(1WY^9}I)VPBbKZ?yz^gK0dPy{5@EoQ>4<8~OTH)4nSdg9&K(WZ9}VjX={a z#M&*1u6Y6ws~VAuE#JdZq@-eGT=iUy`>O>wioJoQBcIS<96-xKORSd2M|zD%s!R+_ z7)X|q)9xL_Fe(n=GQo>Xz=W>!xRWlW;9mlW8MxT6`1@+3>J`SQ4B>PGf-lq_>2|bR zGgCa)i<}@GN~I8a1|*~mWqH;9v@%Rz!3=Mtk+lG80;iR?SxGvUr*h(vZ3xy=>)v3f zvQY%pXe>qIB#4Zi#Ql(KucX-8z@eThyRgXQhtx;xnqL-U>Rt3#gpqDMgzc`%Y99Du z@Z=;M0qSPbR&`yh+*ixkC^I@t;u9Q$bc{rfoM$JwoR%`dpO&r3n)io4>8Kn6Qg-*RmGH1(FQH3-Kv z2Rx=m{*NOCEX1qjD5XVf%$kv=CYy(_NY>|hEKqky*Wkua3H7hpHl}P7aNHaGSkP9D zpEB3R9m}JZuHdQ^<@ha!nC~F}C-$S7Ir3My*hnGlFC*;r`~fzkyu5n9B0g%(f+fBU zE^*UT6RRR0dlas_3SV5XDl`Zw+zQi{2D>xhc$r?F{i$4~1VunBH}!B1d4+>#K8&AN zJzW+bvVW%a#!!6u>q@+0k5O!ddg9z=Q*n+Ma>~P~$E&SpO5rtup*7RsT|QF&8UXlW z-GxI^}c#m7>4xo?WE7M!8DJLDy<>J~rc1y>Y8bXhnNaEq*ZXO5bEP zg%r^ik(t{)T__1Mw!<`@$zm!$h}?=2`K}o#T;}Q+)ViZ0c^IcYoRwQ}RgaP{qT0AA zlCe}^5>9t5B{KinAb+6Z`+^p7otYM(!zeq`2XAR}(t->>Y$&h)1mVv!N(EE_@Wb*?hc*R8E8pn*Yo9XgXeD zzr!|h+bp{S|Luinr|;t25787%7kAA!CsQ3!qoFFHQR{1tGR-Qgy}~brpNRt6xh@U{ zJBGcTTFh+xQ+U<+#I|BLexeBpb7t}Q#GB{n7JsM3+HFo8?_;LaBav+%@Xj3zTOci# zxNoQ8i;M77-o^&=qman3`Q&(*zN`YK*A&e@uu-2*x#xcpMw8_hx5#Qdv#gWn6@S`SHixX`s=WXMAnneuM% z$2Z?qg5qxRCJr+#eUSPX%M=D3V^Bc;4JoYOb3~-7Fw4LJ{@_A=5nuKBrIb%|nsJ;S`$1^5pmwa;!KBt7m0F}|H3q-R1lw=*Jp zgO{`leoxo!&kx$olTGf;UvYxDKeaQDxec?Ze9@Lm`HK#A%aK`^}G{H_%d z9A0%3`hh=soI%N3IJa+z-?VXDJx6P#ECI>UeKkoJ&$&*geXn*GhkH}e(!sXvOy&2( zt!^>$F(;ns!jFJ;@`Wp!;kEH=#pYjJ&#?-2+@?i)DUCPPBNU~Xl^WVj!%5?3m7_W( zFrpeTf*2Sy_PW73DrZDh(L_T`LyIZK`>6Rv9~z^Tt3;c`TY=S{S-r${Q$THOdQ5v~K`jpD zy20*e>@*|XN>Lom+Vbk}wzcIh9p#>e+EhW~pA=a~C5%*Q=&X5TPHL3iLMd!`t%)a> zBCD?xEuOf;PShlEpNkCEW5`I*6J-OM`=4|)NB6~Uq$XT?W`Ss;YZ z#48Ax;b>68EJR#<;ae%;ua_w{acN5%slhIZTFoG_}QdC^O-S_8j>*&2&Ym^UXx!?E>ONPM5$R z&oPO7B=>gX#Y~u3jXx##x;S9)s|-v`Fo@KbDlJyDS8RRG7MQ7+rlvX34N%cxWxzIKAiNt)<5Vnd(Z{_G;qT3LqPJ$?LYGBbX>>SMe5^ODIO3{45AmWq-j`^1rP5OR(D@w04;BdX?T!j-kGNWIodeRP-D?aJqizS)kW^tK|LsGO;!)q6lT zYvt?@Uds)N**_Er-C4Ny%%y0x(VONu`QgdR%kw))316hQ@0hioL;V^H$6ZZlq=0x~ z)JG@tEu&#Vd7fFh zImx%!Z-lrRW+>R|=aohGr}J=c!;L@4Yo~Fv&9o)Kbj46XoW#7CUC$L|wzPPE zTn5@^14^(_Ar`82X&{d)el^H{>1ErZm4T+9T;gn>4nI`p+Jt7lGp)a&f*5|u`|r|N zSXku0NMl1?8(W~Rt1FE8Z_%s^RCBR)W&Ycf^-zLe%Lqb+tZZxqL4qJ5TM)>~#@fo- zR?HS^EhH`>Vrl)L&Hw)!ra(OhKO1~uVNu{;uZ5U}nL(!ihxC>ZlKfZYEjnfNYD%BH z@2G)h(zDtJkDge8uY*QMBvGxqKxDu}^}|a&O)b3?*5@K);q1;oNt-(Ol(e#=@a@$6 z@dFRXiWy(G7!R$7$tm4@w>vpDvb#N(+pPBAH`}DO()9NaPpYMXLzJi|da^r9?7R6j zg(92aLYAh$u63y5Y!=vpT^WIA=U_HCz~;^x->W7;35dyQQbrdYJ@pmS?l1|6bfnsg zUihgy+-IjoAK5*^J(cohRWi1Nk-FEgZfLIY1n-7sRglp?+H}DetF4yC--=1I{aXh7 zRuC_xPQG**BY^bwmWceduYo)V1#p?PP82N)>EgXC{dG+}jJsk%ANs;Muw6kj&KfRwiPuw{B) zSGrqu^X0onsRT!eyF*jfSIuz^lU^DvGnQ?B0eO{_ztnE5zeU!>e2{uk!SRZ$hk|Lw zT23vw**C}gOK>;jKrsM|3c24^OdIB#Q_WLdl{cj!(xf2A%OCJVSw&(a@@E$Qw zLm3QKRGKMfR?051gAh~AjN%q-x!p`h#EiXcKI9(`sTupoZnEyyTf#SwL~?ub4q5LM zzrXVbuGaWa)aacFyXh&e|LJIDLnwEs#`Y0kdw-GcwIqt|dGrexZHZZG0V<8=eO;XQ zp0k?u#Y|YY{?xYL6T&VMR5=sQ{Tn(SOmSP>=|*gqMaE_hdc!YbwfhCRi6Psl&(pY9 z)ttk^?y1d@Tg>u4ik|mbaU?U;bZeXY_Hu*`-8rLAkC!eSa-$?s#%y6X`%76=aCFf} zHoTJR@#0#m1yc4NVNvcKL#AiR$Gz=Zh~68bxbrUIA+P-WE~^Q$VhgmL}e>m95ORYA-k-y z_a5KJ(d&Kte!k;&`~AMZKfZtbZZF-s9nZ(O9@llh#^buK=W#(t>k)yqlLf|T0}%xTv!+_ppG_13kiyfqD2J7(57es zZM2j)T0j*oE+sAmrf;By#Lxn_!EzxnnBsNd?%`%*;YjL}VP$EJnY@d!#9$;z*i(ao)vt01+7Vx@R>&D;r!rii zWH*!xQJ)osPv_8+WvX#NVgCZlP@9Vd3C>_rLh56y9Y2L*akx zB)Vu}LE!TL_>!=&*#Br@7!2OLs4XzIn8|KiDhw7bm`+C=oIuGa8T`$hskN|`$%oH( zZ|@wX+j;9>_1$ZsOHeJ3j-zxZtQDztSt`Wl$t7X#{lo(6FN^8#MvM=2ZvA_M0CUVi>!rBUkq!Yt^};QUR8mefs?vU6v<7UaG%c0l56nx*kfI@s|4i_uPJ@= z%~SZG_=xDR2$I%~58fL$LsPhwA=WOh@J|e^mJ~gW5FpBcl|ViJDCCxnyR)a86;KgA@~HZFsOfru&~*Bw2Eq)$ z93M;Yq30?%<#hb2C>Z}||5Y*Y`(IV#*XX!8Tj|<(fO7>vlm{W%#>WH1DMw%gO83X<%T6z5C*d4GT@Yj0q7WH3E2fyb(rlVx% z;jUxjrtIwK;_QSMRPr>OI=kt*SXiBgS}!{*n_Fstng6YosoU7yy$9eC#0P6#Eem&h zwA4R_tm|p%aT?T5rTGst{}Dhy-_9Ducp+g){Jr&0z!w)2{~xS$aqOcLN5=yq%u+$p zCv%FQ&%di5exc*@C-6fe=gx?`{l-0Zy-^WNGZEac#mpc{L|*U{R}JGr`SW97u<51!K~C4fA4z66j{;PL4zu2JFDV4!;z@sc z$TEc&vk@jgwp?i|bGb>MsN9Eg9f8>a0!F%b0(-G-b&@v;bt~Y{({zaB@!#!8#U5>|!W~{}SYR&;xaCe8!3ueZyw~eUfO`3L}1MANJ zSzUMj_*t58B4fu*ULK5~l-1)`=EXaIa4gNep8W3c+e{FQ#!@?fJC8e>h%KxU8?Y7X zIYNVze}5R#pT}-En?$*awHRDxfFNw_kQV*tEDNUMRl@;KwS`eiIxyHnVkczX`pa;7 zqUxQtDAR0U5uoUCLp_R3#J0DPMBnr510nw69yfU-FfN_*$Zn1C8McxK;ONia_5vLA zs3?!CGC6e41F?VX#titzK2MhrP$qSHbJ)U6H&N__zI zL2R-t7R=98Nq^+i_n>xp-Pz=hN({_b+mG1!GMoT{m_N!UVHd7fvF#Pjy}C1IzYxkq zyeDzkBaU8~lUv_@_8J3D;;6_w<6@W`F!=uG{#3_KfY56v{dqLs>Hg}`dlJLBlPa(0cklo4CV?P@h=)}*X%zh#A@wq)b$Ny1hw1n8 zdzA1zy+~L2nB7@(;=XaLU$|5;x}k^HVj3|0guqVM^GQ-v~z$)KyL)V@;>SkAziUM(bjYN8H7l-ufbn$Qlcm7y;jlX>$_{FGn(j z5^W=&F5rNqZJg(cPLxJzsc42>8GKj<}9NA_gbL|NUZh(_buOMX4p#>}Na! z4yyhb{?31&R0&{E;P2*eDVl0t_))Uw35?N4)-h-W(6sJ5lyog3W-ca>h{my6F$@LMPu5 zFf*NCUE}oV<-$NLjecG>^Iy+}bYwRXe>O=Rv2 zkGh8tFQS2`zj_&QA7Ljti~tl#}nqIf^OY~spGk@R%beEgef z-9VxD8c;s^m8B5>>?@#)XMfc1{1Mk6k?iN=0tX9|?n!98ba+u@#1f3MlkIR?n3^~k zxddj$5_1~YbLGfPRll|Pu%_2#W{%eRrcM)kMB}dEz`Ppw9Ym2{z_scj<>0N^I{I5K z??OE3-WWSr;8mcO6OHrq{{5k_)Y2rt`-^r(#{iNMSW_R%8sz?tmU-Q|J*CuXq;E;} z1ccGBtqF!;Qv?xkL8G@9DaK+`*($>a`It;7#ywMSqy{a>be`h;C9*r|DOYz{W)tFsc(0EzTzqm!Rq9#)9d1o#EozfQrauw9qapx?6DVG#0 ztsYSsWjT(wET#B%)?77dHLf`b+8K?-8}6YJt*I&M3`%`|;42{+81VbW`RdSBZ0l0; zQy*5T9ReUyHt#!)tG%UE{(fJ~d&8@<`0?PDy)FK|d|>31o^NntVB?D}QUsjkLZ12E z^|$mF`X>qis#P}d5EUrIIzw5~tyi7VTz$PdiyOb0ZrwTKPNHn9xFOrg@%sKZGzN^y z6{dZ!&7ei5heYik7asyS0X^6tl=l={P+4U6sD2kl!< zuG?7?nCLAD@uq$S*s*}p*RD*j33K^C+4ds`Fz6E=n6h1VcYF+x36z)YKEki`sQ?uB=1_^D zLDH)(WA{qD8QB8Qe_etXexQ===E3H|bTgA+FmZ{ZUcCu^M_FX(CqJJ%|>p-Njz|)cQt-^&u^D$Wm_4`}F+xZN+8B`Ky&b>5iAu1DAX8 z?S_V$%*;C3m5*epm8gdFmfg3dJ2bAl+H;}Ke&(~skH8A%TB<uReEEYU{TW#n)Mk z$!Xhbb}xQmu4Q_M;IsuW6lEUOP`$C@bmSp-8wC_wO zW4w2zI=d&-idb_$j`rf~p-*SmR|JZ+KMkS=f>;Y!8_sJkh*I8N5=V`{Wu7=s+?9}25v9)*d z`nw+0ta!*wsg*4E4na@jms;l$7O?X>jD1^sJc$1HkbO<`$9F{)0qMp*{qF>l7&XB1 zjEv^6sy2j?ckZQ?5U<0InjiBNO^64%EoZ&5#!5(A!QOt)O1X;3#)u;<>#jN76x~2N^T|5hJf>*m^{0ss~Ab*+Y=jzC*QGS0uJ$+A z1SepeCHWFO*>732yw*GelfQr31IPk7aQqu;y@X@^EWPp9gPgTvYUTKrN{`X^DQ_Ci z&kqW2kulx)UMD?0(ZKk;qQn9alD3p(rHhGrS1LTxlk?&>M|6^EBKl#x+w3C zOaUS<+qjz0-&_oBfUg@Tzc}^yu@Qpyz{br3YMx^#Q5oPlu$~F{Y|r=AeQOYRdG^)7 zHYgPZ&as9a`^M)*(F1bTqOmxOmI|Z7C0|((R7PETPXd0J+2R=_OUU3J=i|!nRFnyL z@u_yy43)OB$C~FjVUG9cV$Oiq;+MA{qE^m~=kJ9pJDf}zsNDlBaDXmEUlCQ^%2Z#A zKkc}UyG|+#7+zL)WASm9V6h8od+~|4q4u`~t99L5W1m9XPhK~i@Hnu5E@-3Y`MF_YHAE~vm`!L`^|73@Xd0Zf<%0Q@%_O9X|Zl`n9S%$ z+ccJen;*LsTfrU=HP1G2Q(QFNh>uhC3!gG+iz9E{tdRPT%sD;|V4gX%65kHsecuYJ z*ZSO8du-Hk>D$&j%dPZek}P~{6PXj44?1Sg2274v@GKZ_H(_yS8_ zf2=6PyV`-$wB~o(1w|>z?aWJ{tNVu7d7eZ|)$~Nm_%MbyeBo>+7a;bW?!Cpn(kegk z;193bvr|Roo-x4d72SK`#CY^I-m%d>$+>Wng{hPaeR~|e_kj06c*PQkP?AT89C|=IJT4b!~AU!be zRr}Ad{Mjg1*Ak$yp8TU;wQ5$p7bCN;#czGiyerRjGQhjTf%kU|vn6Q~zQo?W!ggJ6 z>;)w!VJH41QR;PP!-@RvOqU>zlkqY0o`0%+)iI;J2M@k>`!-tW*YXckvx2rPgcPvu zO5Yg2FV5$V9FN%Bnde)))FTXF!QTE{Gi}tRX3C9R@jB!6?4S{>UL|)d9-YlRS4S}w zegX>llDnYqW`=q<(kRvB?76Yymc0ua6$&z7z}YPPyAjospWZvUU}Ilyibj5#gRG35 z08Q9JW3!E^_t`+am+((d-2d)cZnSpL%fCBNT=L_i(MPu+6|iNOY~-+(q4<~0&(#3+ za!>YS1Mcx-{iF1sJD`B^l6bu$lN;KbW0{vYB7Jm7JkSR*<2AaT$ICC)b z@6W#o{ENW92ml0*8)PN%aTKE3JSi8@{1=n|j|g0W-#>V;{J(rn<@62m|JfUAH8%_2 z|5vZ5|BqiviwKE}O8t*-r9a9(*}PI&v_t+PJe!(97Dm9VM5!2z)FBWgN4<#1Zhj); z@utrruLm)OAVNL4$u)F0Vy38QIpMZZ&>_u*?*W8f@t7 zChSagM?hX+@VL@_$MboA(1*NXTNtEZF)kO*+dWwQ))7=kv{TbkTL&z}73G%^5SCr^ z`I|s_^7BQ4(4(H+b}&3Z04ZFF-v|owC|^QC*n3|$R~{U_2>{nGm4@bFXGX>L1#oH8I+h)q3TQ1w zfxDkLEs-!zzW%;BsJT@7?)Eh-d>^a_sW$P&Py&B~8-ftVJ3|z?`!&dCMgF_%ue(dU zFbJzqS8(5PV|Ql6@JW*E_aZ>vGL8tF96S4G@WwghHeDAu+b0Y*c~|*xoaB{4W!VyV z69AWo>gq&PeL9-?&=5wuFTfG$QwhY<|H_uK5zrg{(nI*qi&G@X_A#;7U!Apn zqB|dH3{;$&%>~;k9}SnIqtV4e*drYIbP=mtDUEx!bb`m9$x*L3H2FaU48r)81b4$elna2o)Kc<`hK17a`{tm; zXM+J^D5X9-)8GVi;_+%+k%LVUa$2#v_jy{3f5Yv0OghWLn4|`9lPJ$#G z0b;^V;RnUAeTn0@wU6bAr{rfOkl>N?5m zCOmE+i)jv4hp@6rPKe`QVlKM^-yW5`4FDxF75Q`On!TN4MHXoEpfc$0I|cItX;8Q@ z2(!F9+3WAvD>`}BczxpFeJwp(?9~Vw01;CT!Ik#>nrRI;7tnu@dBw(pxm>hz>bWOz z5l7obJP*dH@a$>FEie+@P10l~=3;VFIRQlOBH4#v{U_ zAcKR+IVh8&Gl0!(vq@~8(^pvb!B3egfdY3~3!fIq6YE|uf8U$e|e!Cif`&qv! zO^iS6ss%IVGKHuX5|Cewa4*`bKROO7zijbhY(TC^SXxRHEHaDkuU)x&-{p_|c>1v3 z1n0Rh*uEl`j%clFl z=9(5>HOR}ZmOCD`N#h>2Yr7lK`}B7FLSS#ZDybE1UM=EJF5BA4P^v9+6N zQg#n%$Z>4GBE^AMc(U_tg#+Sj&a0JMj{%se^MeLgKXg`OUfzMDni4Uy1d|8?mn&yB zi_^^2=Zbqh!;nvz&jDkJ;Ltd^)J-6K<9-DNQ`afLD55#dG`f->PrCOi7KInk05Zw5 zia&d4>0VkeASI`u7!u(w(Kcdeh)7dZgLrIM+#ThTUP^JpDm~f}g65m-G^lusDO^x4 zS?{TxSUBxDAZ%LB4QSlZ*AdH@p~@V_)5sYS4tTc^%x(B;;OU5E`Y?OL=(FY%;7yZg z7u_Qc^CO9U>Bq*swmlNvnH8C}QUSAG5abyrg7$wtHbFVjQ$}n{s&J!J>O_T8;tHM? z_36E2dptF(oF?EfSeBZewy+`5q7{dyNlZ+Dd*Z^at<>IClF*Ssz#Kr8+Z<_01{p90 zLhUxbuKoiB&y*}AySMWzhyea$pZH-LelojtZx{Fw?p9?lG~K({QmZ+@y`1m5jW&B2&;=rogh`U-h@6AnY9s-1x&N5m%9k zZiZVs6J!B$+5<|v`EH!yld*L+++hv*e_EwqrPw)=BO}f;2&K!Kz zIy;B96tznFgu8Y>LzaLZ!mzYce{V4YS7U&p`ws^b`{<^jFL#%j3m~i~TEcg0;A@V) zfeT|*8KP7QJqip75>_Vq?rU#%D{kMiwG6d-Eib;r*BuyN$@Q;@xD3U`D8^1{oMY+5 zxsp@M8sf--r0wn8)W0Q{J@dpymG6=VJ;*ffM1Wh_ixcbrYamxA>Uz}`ay$xPZCbx{ zF%Nc8FZvEgyrBRyu;2qLd3Wrh24_C1e%IoS@fT7^;jB7LG}czngmCuR-Pcy?fj`1g z1dv43y6TVJp6wT95@g+*(mS$7Z8}(EIq3v!t)s7^}Fshw=9z$T8M51(r0vEo4N^HdXembq?PZ}R2U|C zOeOHA=N4U@jDy>~)0tVD<;{d>t|42AcrC?}&peB?i>L0nt-6@rwfFkRM}@$;J4;vC zx8pQV*W3Dg-gM)0C;9bWCb*8k#C3-;Jq3SuZ3x-o(U~@3E`DBx^bxsn*s`01O@uk} z?Q9YOWI1`&AOT*spFBn|%QE?H{9=QkshtH8M5WqR|G9wvuoZ=^>1rhb{$2f6a5vb_ z{5wCSKy?@`N00KEy~DHf$07YY<3AjbdJ*p~5!KH0vMe0W>R%X#6$LU&HQ;XnEK8%a z(?<;>nG@H}Hk|4E;!aYO%Mp3kp?7w%<2eZTL=aI&aZj#y`B=vLc$)5Qb9!&o649@v zT~8{!Xv7wBywMknO&6(EMuuD)gCy|g#Id_qfc7`bN0=^eRM19DX77|cg7k}eKx}bZ zj!rqn(P6!U_OYPSsh>~{Di&CqCDTo}%sKL$`&oO_Fa8{aQPW}Kl`B>aA*f3w7z$gt zTCPF?VO^%5VF;?KE~@q;gF(J4sfMp%J#7ntshnA{-8>>@o}FFJw`rNLo8xqP9E2 zo2?A{0NL?)J1)?y` zL<#6C965_2!1?Q4g0HhBA1eAL5Y)9?wrwhr@^+zuvib6|<9_$TovS7xXE!}9Be~+N z3Mz;yOr)5IvPfYgf!^%pN005hLJ;=n^n}q<1j_O8>;NC}qPTr;}( zU^tr^)=)LWIwrvzZ5?*_fN7B0uIJl#aTJ7hTTiB_D*+<9YP+@N60FX5O_`Zo;027y z*k#-0?5-76xI$3UT>D-94pawhnfHhF)oZAazFS0-HonG-HlCj3@pbJn;Lc$y40R9> zp~ep8vYAv$Cp|)y6kF+1-Qq8$cy0xUc4t&tSq4j0-LMhT7ay{nFCqsb6}slm6}}r{ zm&O%EYP?WCg92lYYdw1zV$swrlg}mNeo43AD1u=JacjDIVqNiG{Z8{}O%k5d6hvH^ zgX9LGpUR(AI`T+mBV;g;Q(RKx{T(rcj>mb4RHC%pcqd+9G2%Ffkr8o(&bioSYr^R# zK5uRtk4Qt<%w=ECrz*%QE+v+7p zGB~$1&iD$k?k^pB0$gx*#Yaa4s70~=2{2*xju+r!(IN9(dX+Hy2(_Bquh9Q!0UhAD z0QqR8I67~c-}-WQ)a%^6bFT3uz%my;Ir-)DV{e^(`>_x^O6@I%w@k+{N9S@voebd- zyXg$c+$DQsB+lN#^x(~UWaQVncNg~J?uy~Buk)WPDnFNTNaJMG3A8wBw+@U4VVGir z_zBci@YP6!Lh!!{XFCqS-(_R1Vy4&pJ+d&9aQwt1|^Gk!A9G7k6;oX*?+l<3AeEIe+to_j$p=$jYHXaY`(Q)%doaE@Xoo zDz;YTnO>6s&_;f_*gkPTHL69IbH|k`H_YD`L}>1Zi-B)ZJQN`d}if#zZM%W70SRWj= z%Wz;bz?E~agS89j1VJ_C}U*R<`iFi<}nZPo*wI_Y($#J$it*l_T zrH*;ol5~5=i&%+iLX1qaLxelw(w8Lbv=GNZr5lG2!kQ%@ho^74utnL5k@zgelyb)5 z5(wI~Y*!GuCD0|*s^Xx^Go15(h6Vy`nl(Gv#ne}#I zZyvvt$S2FrVBcc^I3!jZfSuM{7ZJHR(ETa2`J1uJ*#)hvUW;T3Vu>wn-wAaX<4_ON z#t@%RFGpdT0LffltJq%<>lFcP*?CXQ4lDNDJrtL=ZcApUU+^&8$9zNqLaCY%cf|;A zbE8&&4#GabR^z|o;5zB;R4JD5M?2<*cxP3RV^_vpBgvkHSf_zgUluYv-Kp$AtjPoW zwmJ0do`==Fr)Sz&fz_W;WE0e8Tp&$(#i=4?0zAov1pz$CmzXqoO*`W5tc2ReF{%OX zrpA=CQ%-@Hq(VHz9p+$C&u6AioWIO#k>1du^->D~%pFYtNG<2J+jSWvj4^k^mfu%a z-TW5sqC27lZENcgYwqaJ|3u!EP-BWczW~@;(p`l|{e%)Paw2S{kG$$>sTI0mfiWPD z82*)5?1+Z^xVRCv6=Wyh=%^GNzq$8`_KD8F_kI3F5V9dA3I3Dyxu$sGLb2F8ffvy{dj}ct7l$pBHR76PQ zt6BwSHa5W-HS>AQ+$2!>a%c6C9lyN0qY9B>0Vmr>Kg=Se1nd zL@;zBez22r{`xqbgvI-N5!UtVZ7{4l9PAg6e}h_$$_dWBIjD%2=&Cxg=9;Vv*LPUp z(q`NJ&%%Hy1T_`BpI+kX^C$j40J`@Rn89Ee)fu?1e{rzfxqz)D)I2Q@@VmvZFx>91 z);F(i`uJ3-Lem^*gzX#WuIt6v(J#o3;%Ar6ViAC86FaL-@7l|bO(>l~#nopJPF;am z({=EgH}>G`#YI@a*IyUhRUxVyV06#*;c`sFK0q?y2PrVCvw-KwhTAm^_>~ATy)$5p z2tQVPr-mRJiZ$QVCIpP!#*4NR+>Z(jvyynJeRk3emi@xMC-fIn-#61D0~q6%1``kt zb#yjR1>ADs`ZYX`pPuwlx`Jv7tEoU7ZLznNC_1YXI#L1`Ru1w4OXK>U_n_BYlPhvA zEBnu=sjz;uY9_~q$s%k^ZqL=T%nV7&e!3qWRj~i&H~xO1fei`XH;PwS+k4!-`)DK7 z6Um@!r2YG*vOuO3HS`!u#V~Td%Fo3pk+nbuw>o!rG8aY_D~o7lxVc9EoM{=!^noEX z^}@ciy7`C-Bz_IBmI{6% zWnmGZQofQ1+~%xw*2L9UtPbkxp~)%AS+e*fQUt~(=%w%V*7~gc&?L08M*qNr+?#Si z4Z31M8N9ZA?d{FifaSARaYSitf{7p-MPQ7Flrc)-l+(3u_8N(h40%_sz3F(d27_Uq z04WK-eI~!E0%$c^5UMnnoL&MX+f*spjr0pKB!MN0k@4R(S_Pl~v>zeHg$qXD!k#xa zYO9q7mNdeap9<_u8l3$AjCD0a7ID#G^}_e-2aV3*-jyA$9TBaobogvTAl$Cf94?~M z(R6LEF`UFdYbGt0!ifX2?P3#rkx%)#-R%#ODg31*aX;4PCMz%^3G)2#hOp%Zovphm{CP(xdO_$pDOL(`(XyxAXlIO4ToR`| z8znE8)Jsx98=~KK9zNB?D-pHOO<8d$*%Y2kxI+Wcxf5%4-D|A>v^#8kv9JVw25aL4 z`d&VcK#-fo*RYV0!kI8MA5+~=L}+p=-_4spy;BLP?6NsNl!$avP** zc5i~xt`%S##ZiETuX|m*SpJok1}F(%X-wJ(6@R@don=T!<$PZ=nfBXUZHt-RBb0$ZLrTcKxC?O#=uvTMk)(eox(b054*Rjj%5xz}AS! zD$zibh6OCmx1b2t_;UUhH0D=@_(d4gR`!h$tY!eKX_sCQu1<6Wl8!x>8@$uNo^(n( z%HnaXdY}jc>@aDfg>xBA87^$ZP9lr3f6_q-Q3oHOO4&TBya)?pts~0y8y&1TgI#A(#t1Jq zl#`$jC+SKYk3e)n#fl+XUrX4~DeN}R2%T0KYHjD>Pp!P)m_k31^lhRWBlxXl3=c1M z4!nT?Ig@k7`()-Ln$R^%ieNi;(X*V`)lf34gUz1Z}1@GtC%Z($}jUuUTURE(R9*DgqkS% zBf#Hq-4K-I22yBRVI}d$qI6F&$YyBMtM18;#&g_Kq7^)u8^U2p9^BzYWK)OV8Rm%3 z7bMxQ_I{nYAL~C-AV7aO$)1v+3@$EYGXwnTN|;Jn*%5Xk#(B_UR=c%XLic>*}*jpiOsmBCmJIx&|6We&oJg-m0#<8nH%yTljW4JMEM|=<>Qy zIj{*<%kNE}adPIaOfQdptg~{7_DfaO^8FDK1=OX7Cv7B?HwNVDrMeF{Whvk>3uwmB zU$8e3-LJYTk;};pu+h%pHi^8sZBd=*sQM@BD;U0dhe>ovb4D+wXaP z_`LG2sHcE}9d*9jI!oM!cHVEVX08tn1A2aYkpRLl1FKuNe6eph?EQ}Ax9j7MA&w3AhuP|t= z0k^6+Etc5fs5T}4>VD<%PAoWr$@StAm#FL;*7w|2GMqFw1cxr|z!^=V}gx ztVZ^z3j&X>w*G_+gdXB(e-P}D@nBg`r|S=e+6KkVai>Mx5L0fZshh`{$=?Z^gpOn! zmU~vMNyy+=t2(0oD@j+R4g0{#&#l7SzE*)VWkCWoiu<$tJ~> z9=}SKw{?(^4_S-rDjSDGzCtlZCK_bXhJ&j0^Sqd*^|t83unoRa>D>!+QLy$%p1K#j z_m%dc)fyO~rZG_Z55;UT?tXv~#Bp|uCw;Cx#X;j$89bLisH^J3o#KZre|aU#ef{yJ zOqZJ^PX60%95XOdaiO=>&F4}o%^>%%s{)XcljB7+A?@%9qH zC!%lT=T@~MSw}`tbdEH|bI+=WXoDkVP|@GjB$CRNQ%Z_#93VNh8&Z;NBkQ>v! z9&tiH!%u0(zPD0d(5jpb>G=e1KzVeWD>BUy{$En-3N1%Wqtw!JP#xMD9^@vbQY z{gGRc58rzKGf{LqEKqJm`CNU)g~CiK^rU&WbE>XF(xBQ;M+$g%F9pyWe^SaoncpOw z8N;rq;7Lesc>Md~jAg~~o)~|7wA7u;KxRX}V&|K8g)xUJnL9TPVaiYBveId$h~%%v z8RJen3=VYHiM#GP5-hvS1>C-a!_LG$L^4egIbYI9+QRo+G5HL(Wl_t)@6QXdRR_`0 z31aqMaj2kMDaLOl@NJw%1`WQ6)t6SNjVlQsXjIo<252l0jC$|$*Q@4e5A~t@GVqad zh&^dd-~207OR$)8Kj(2*QOX*ulhuLN{2MA^XaTi;~ns;%%gX2RkE{fsakFJ_lxtG@xiUG{-NgGr6gGwy}^LUKnA07Igta z!$S|m5*;R%p@n`kd*1ATNiWxHcWyeZ>JbIcK3gnn%z+SAUs&}^0%}jJJ6g(%;KRxC zd@uZNzD2yXnb|63wEIpU{v<}u? zq&UiOs+5;bQDd-*$B(MGaZk7j8WEa9-8U>`@ zr;q6Zg=3O8AnV^IcXE zA3Ji>ifLmTZ>+LJd?YXJTjax`Wcioon{WEX?B~=%nl3*buGs4stjcDpNw> z4R&^h(sLsM8|4~uCqngs=26dk8zJH=QhF(faRM{pJU_WVr z3*;Tcz~Z$h7r@Iwmr}>}8(vH%S-yK5#*s-WQ%dhp@THrhE+L9{Jnj}**S(Vsx)sT| zo^hMWApK5ECaPYOefC?IeT!nYY&Fo64oyIP&2O&Z<2RY2~285Map=x!8r$$ygd{}Nu0cx@v`j!y49NBLT3XS2MCxk*U#O1 ztVw#$OO?!DoPBKaTByPSTWUw|S*$ww#}CtUQkcZaTO$c{eIu7jXSvkJY}9D}5a~-l zx`Gb;qAx@R9mT8@8dJK=*K7_<(|`Sj{XS`VvwiMO98P+V8a@Du`_N0j*#wpGM@{m@ zKU$wxO*U$L)QjO${^rVNqh(pjrHZ8?f0x~$RfJi9pVuIskVjd}8>_nUPj+$b5n?r==?2)~G9m=p7el8d3>$$2ccY00g6D1NES%CFl%yix3n46`{G?4H) z;xvRUpaUI8a8a)!aEYS?^n#M@X;F%q$-hvfLYI2((>RtotqzCcu=wf)WBQF}K0yPc z#5`FrrDMYrdRt*(U9DmiDec(lBb9YRt5pk`+ORF%=UsrHm`WiP>6F$#j> zm~o+Wx0nb?oSff+1?fHiP+rc=NYBv0axq+cf+7;q{9;ODUb6LB;9)Mdzcj=3Oy^wX zSA_zmI@`doxL6k8;b-Rab%ptPn_>LFig`#SbA<)@gA&IM+3*0Ra`LLY9=op>oP3os z*mdR{<>MG`+Q+Y$=hyPEtuwKa3(4_7EGc60(`KEXVt^y5GzXiZQl!l*ah_~f|2u=O zQ8Hp1HNVh(f$FwPek5iHPjbEW>&(B->{;qD(Dqdv-rU;lU~0iU|NP*kad~2^w%t9 z@I48i3wx;2V&fL})uuP^zh2nEJ(R><*kM2Ee1Tn0(n|IBw6|y+y9K1i-UgjRRPN1`6`~Gyo{k$hn zRWj46ZzUAO*?n#z2wMI1CZLpO(CZOF-POo0TEfv`-|=|7G>ix$X8dd5L={h^fR>89 zDmSla6<$nvH|v!(r&Ut-R5tvpS4F80u1bx;`t6z6awSU+x{{&BWDS+tM&7Cowkci7 z#%$VonO~pFS%j4g9cigvV*@WtDLwWMjcRj8#vFqPBK#(_B!r+iTXnD^gGD=xGW{K1 z2C#qqAP`olCO|&J({U3-ewY%$*dSa*~pbdUYE%lrkfB0#OqP*^f-Z3 zF}Sy1Q@c(c5WFY@ze(qko-Xh6QoEaPk2u7wvIjYumqk@nk!Umd`<}0_`%>^i@``p} zM1$T9j4CXECI8kVSrA3|2ZT7KHb+UkKAEu+3=x5HCV9f${_go3)U_ZYSK^DgB!^UGNAsCUjjXp4W?Hh3GSHlIsQCWWg*@UD>?^t@=QLVwAXYA?IeeXCTNewayec!*D-Ip z=;Hr@iXpl1bz9rM#6A8l*!GXbN5LOFKLHk+S?O^n786cj-&iYz= zTxq+uqWowQWlr-eJ14*an1{UB`{-@tg^2q7%Idvef)kJ%#tmj}I9sS(mei6;FB8Z+x zZ)s+YoLeR3Hq*&^Ul=z2@$r=uUBb~p5JzbxB!dFO5~tJ;x+<{x3V&P;_02xz`pi05 z6)%l!2=`E{ugSdU>WI`frhp-+x#c2yf9UD-ypk%{S2*6SMVODoWmM6&iEN++`2;bo z7xLLAFX+@C`+9OILK0KX$Ib7S+<`$gxw8F029r#EDewY^kOg<-%dEkxUbswo1>ph8 zO7rcOWq!i?#8!-;4t|YT({}9fS)5h@_VA07ddvoO(EV94_bb+icZFY31NurBH!kzX zX20w=hGE1Bqm$re^F3wZVaN=A0p4We#P9VWuh#*I)^H0ILBT&Iuxs8cO9hiQ8@}7q zyruA}RJyp(r_)4tyJD&GoB7iTC+T#WmN&HPR7taY9g!GyDSpno2?gz%ru_N`;JD2< zrnQRUMt0fe31PY-fF#GDqDk&-N#$waDhJSu8MO`s(;o6Vvh(0tVC0t0fbFTnk( zi8;fM4S14IB!Fdx>+v9_7}OqhoPYy#aBPPPNP`+xe!>{A{iBG9`5fpC3Jf^;B=fuOixZ<~K&G^bE_p2Twy4){X)?Qg!G7lK z`Ii+{bJvC7mg*o|I42RqB(u!yW`0GI)9qxvWjLmDEncXX`4QU}3e<7Pg+*l^*S*Pm z?QDXCy;;$+i);&zY0xPu5|WE#NgqgFe`A5vlu*Gu+~R9_W4mrwVb5<%NMfi`JXC}b zRKMGWr}NAC6807isnlOGIhb}{yWT|q?jzuRh;Cx2{+xV zg|=h3m0GrOjKg{i!!L7T1aF;6#N+*>Uti1q3iuVq9X%cLtk5)_H+GWO&_~8 zu@vWJ(td{lF>af8v~u#~nSwu0@S`EWsGZC=%0JOTBkW?gW5)*F7g(k^Ju(pc&z#^x zT$aR|jj}EcVffQSbDpR9+E~von^aE8H>;x!MIi5K{Hr3TP(rH4-gM3%6c>qt8zOKN z{Xy=HEUm>JNE|=4zD2;^Ho<}WV4?&`bKD}4fZs?~ZpHf`if1_0a_9<`GA434_~61D z0p;Tg%`&FLMAo12@Z6yL+?w#(XZ);-)Ix~XLX3jCwv%27viYfxNxQB?Kc{q~Y$Yvw z@X`H2O0Nh8cz>Zq@JCh&`xJr%br~`D?ZmvN{YPc52kHnOa+Bj&{ya7Hp&S($Fk2iD zB+5z1sGa$GJRrKEb?zlkK4LrH^vV#EC2^OuFOfsQP@yH6I@b@1T8 zjH`QvHXwt0r__5t*mNY6%$A3;u!Z9aK7`()QONQC!4}W8du^5GYYn2v_YX!5?41F7 z;|ZzOD|OGrlCe?AwWlG9ynkS{9}T}Bc=0rWI5*mBrk`@k) zuC6Zhsqa?W-_q_j>5J^vFg!C*jVTey5lylOz06O0*VOmQ{Df;8L%%W$1l#+H0D^3S zPGTazD-rA72rzNOKK7|XIod}t=U$Ihkov3+HmrCZG8oX~s6h zacfhfA(u`2Bi*{UXo7ZkM&D4He-f@@dMntJ-9K4>m|&-~pjheniMpJ`?x*3L^oKrs zA@oSE$KSXHDH_s=F|jTWtrEH^P$?Oiwi>H5{1l64co4bfgNYFk`%I^6$1KqS_Np2Q zIS-*3j0a&mV_`tK=SSG%s|1h8zIwvhM_cP=fj!2}&7pt$}v z)qA)^Z>3Fi?+iU+JBkV}GVQdmxqPrUc(Sl@h0$hw*hFNJ=r;S&x2}}qy{8vm2)IaL z8i1bo?XP#I?k$_w$bV7(oSe+I_pOVmk+svAWRc8-d`O@vEkmjYhkHx+G4uM42+BdE zg1ABpB~eA`ydAV}-h2G&r14;2rv6fiNElU0wBID(+s{HmUuttT%DIfU%L1gQNXhK!NGH6waY;ewlEQTWR7w4 zlsd1l0zD%sWbYH_XQ*;oBJj_ZPtjP`9saz6cU@y+v!+T3iq5c!~-f5Q+e`Io4(p!D#8po88!{ zj#kiTlRJi{&L0}CUc2Oqovk=h7$b4{j}6z3%m1ut_QMm`rO>_`J*H}AW1m*HVRYSR z4y(3#jc3ZFbRylYiIjia&&#jV{owQqLrAoq7(yekUmQHGbQ+9AAwTpV$27^?CJPLY zY9#r*s_2KQ8>a|n`9p9`WP4gVV=B2!iw#>g!$MeF&tk=81vO5^R1|w#DStY0wOO&0 zqSx;kz0siw@Mi0$ZcLruVnSoUL-rMYxbslZ)3?$KUU%>#>t3u#Zu3rMcYR~i_#cDgIwpcEvvICGDa6xV#_4eG=G<8wi?6O&_B@;?KK|f=;eI( z@a8)jZt0-mKgwbYRzkCbD-*O2vAX^wqN4;4BoW}*X~gsqSO+Be-G!~C zMtvToqrFSsr7vLzp8VcJe>tC(1=4JnP2n*y;o7o-3zT;S!xAx4#hX`~8&5J5H)Gwu96yLs-dnh4!&Mkl zzq4x->}PMR2Yq@iuIXGEdXM_?Il<~`h_hKr+b!D#FE3X(CkrnYT;Te;fEv$li<%q$ zj+psuF%xViJkW`dHc3Yk(U>UwJGXNhG$Wbg+DXGc6#d7qC(IqF5f+_BzB_Czrb8&E zk~B66{4mQZWS;cQ5D5=rUpLr$u*#_?*?;=UE7E2QB8@edb2$NE9ut zR*{nDe7G;} zxUAu|ENQP%0e$CXp+Rh#5z5a|h6F=`awuGojk3{(IDtk1A9UB1f~FO^{OKF}1+!>R z?Isz+LyA~}4T9ivcb8}LlY4!cjW@dwpNY~nL8|>Ts&mTPy4Nh@I%sGNJ=N94A(YX} z8AkB#HE2W-F7hA7GG6I?X5#!xIKvrHa*u=Ea%8&plzoa>xRq=8zPs~FH7yspdSwA7 zR5T>vx1f`k+}PZ`KT`{h)c0601vue^Faf;%yO$OQ#Z&_xgbF+BO_5*A=&urhq|$&= z@2FmSCgl8I1*%$&W0_Rl7s`oh;)}Q-j)+9ABtyP{#QppNgmn>U?I_c zYiQKE(e!nFXtKr}=ma@zg2pYjE>M4?zUmBvZ{)1z>bvv~gO6jK&CtzF?Zbpw_a(V+ zCYL9o*xL%3c@^Ku8<~V32K4!%ozG{=g;)DF+ONK>f5XEvnDw1m6rem^#K6OTAmkgB zq^FvZT$3`n1v_w)v*1RtN4wF{b5=`xif0r{M_H{1>Z*b^3x=fnak(eImc=-CYh(@r zU^%%+m8+%7ind&lrT3-na(3r$zXZVFxbpK)d=Dnr_Tn5Sh&z#3_{$hl<3!69)_1w@ zrJk+034Y$fqZJ&|RdKbmhPPz&p`!DE>Z7%$y>||UlM#be@^4L@A$cdo#*jbyKcJHi z+^CJ7jmY1{DD3E0&d*7*R5InvamgTEBa+66)+CObXg9f@cn$S&zW7U!$Nesq=0Bbs zis-ytP-6Z~O$`ZtU4vio^SJ~D7}2k085YypeZLz&wi4pCa}bo`#^6$%v$8-JE$^oH z(cU4F8NO^@e~YPVzy5f+=;=6gjBY%k=p;9{V=>v|~B$8~)SmZrNVChfQ`;J-h{ zr*gXDlAj`VO#a>eT*ijCla??Xw(2%%U7`L;QNkQ(^d&PspGb5}9g!!N_eNHK>vL-e zn7*6wcYPceNvgk0f-y9ravQh*LpmEvRZ9@>-{1$G*KtA$Iz!uxjL_<`;(9?FG-0CEttDTbB8Xe!U9Jz2=X;ZE{}BF0GbJSyFDf=5m{Ms^}>ra${seW=#6b-}~$=y@cbB!pP7 z=BD|n8M;E@=XQ&<1-I)a@n~QInO3kzof3NKc#!IIt@;A{K!&*|5D#rZ6OoM_1)iFz zl!+g%{wQq-ocfS+Kz2m0+RFoX4&5#Mm;CoEv##&nt9E-j7%}qK#3Q6)op%xQ*t!RQ zgc4{J8SGG?U&mQe@y|OSx*BCU5O|6cqt>N4VW4K|A^eNAYzKNLTuV!TL5Wb#3b`oSiuI}LA@Czsz*~t<@Ex5WS zroj|~6jeEp02%PQ_PxNH<0FEO5XlHQw1pl(pync-5|HFTuAmzRPK*Emv;xQnMj>Rt zlnA^aq-QG_fStzCawT2CK>+;4I4xIYy)FQ#QX)aik)S|g#L{WoPf;giS+KwaNG6*F zfMZJ_JwNJx{MWQK0T1bX$OZt+_~hUa$_4;N3Rl04K+?lVVR%8E)Pnyf`Sk)a=B$MG z00G&45DN0nlm$j~xdZazUt$E(yTVoSxzpAtY=w>HhJ+R!j5#M5%kp`g2P zc{K9I@SyV9EF9kGbW~M3Y!jrm*Vma`IZ@%kroHn=U$38+u*#UjDlCYq@{2ErEuo?@>=`&nuJGQ3%$?=_8Mbsn}YBDwv&l8gPW||z^o}NY&J@z1d|HS zJ~Hv}^H(TmNQU?oZGaO0&u&SOr1IaQeA(s&Qb4OyRQy`Z1z*)R;%>y(5K;Gr$wQVw z+KZ3`?9~>~gVQ%5oH^!igK(#f6~uzH1Q~;*fU1<^SmPMHty5zvFD2#65)+{Ta648x3l?Bh1N;@N`7vhMcSD=>l@ilnz zIGBjTf2-}t$6-~-%rqu8*YMInvq@-4^-!pDZ+xiCgIZf4{nwKFq7Nx($i>C8XIkGp zkIhZ6`egy;&Y`zM!?(W;;V(8rbXJx^IzpHO(TwoBQdpb&#yNy2=Lov4QgK`yeA*>i z>Q4}nA10T)iR;EbOi7&`p_<9*$Ks*aLyjz*)(ovKU%r@meXl8~^wweOwYY6Ml;FAQ zGt2MYLAafxw#L^3n*#OH%@;VUe7HOQ0nzEn_3HH;r-zNLRCqVvC0{HNM~zd3;m!ZB zyhQV+ZyPIMscWNqIW)AmMu3emVd9fKhphFk-u9cmV-qQ?*A9KF`=6#-c^`&Y{x$y2 zJiy{F=1x=dWojF9P%1rm&hcSp&t0(Ek}RU995rp-ez%d~JaM7tZs{y>!RWhZKUIEr zP`<>N0&aMGZIMBZu8d0Q57Kc^{C6TthlcJT(t_{4mAu53T4x2NnOUfAFpcEMnwbM9 zU|?Lbw5^owht^a7H?WWd>sZVBsi&aDU(}Ov??^p$T_2&lr%2xq@DDfkLmrJB75yk& zLH8wk#hL@}8)q>+P-Ke!YtSfGJ&eL7HKb|9mg!T8>qW>IS%c-)SWS%&ht$Z{^>rD@ zkpe2`l?D;m2WP;&Go5duyWv;eKPV=Hq93#(@wUu-#DJ&90kG?d{U>xmG4aS6c{f4Q zKPR9@$xB&ZYZf{K3835e8{QV3kQg8^@Hm$dMej%s`SZlN(NW|sNV;?YYDBK9%1;B; zo>J%544fL!5qK=H%kE_;3>vzUwC%)^w~pdO@2vb{2LqO+UwKbNH5zv13k2jQRHV5o zZu-7~uieb!tL1e!rDo&9zZOuxq>RUfM|V(lZqQ5$QMyQlSPxKNn6ix<2)X+)Ow( zTeWzkOFN+JkW9^bW%+~;x~ZfP6~%fm>9&)!PAs}f7t_4CgMPyH*Y{5^FZn>|mdT_- zff0M%zM??KxbnLcRg#@*IC8&65-D(+sWnh)SB1IGW|B(nuRB2;oSmaicPFIEfv)d& z=2pz_w=TcxCv=14A}qWa2DF4I0nb$DRCR2gt=fhBTk{XG@X%-NghKKem$Pf|Z*L}- zZO7ZonHZl%8+Tjt?$T}#SgA*68_f%PM4sXkir-P(VI>iP zq92UOab2|4{+CH8{Q@m{V?vdQ-IGA%To8zCdi$SMUU#!|o{Pk+FSj@vP8w$_DeEa; zwf)KM4q~UxI*_T@;Jcr__HmdIph?xSn@~%=zmwi~`hB{!?Uc*>_h!>y`0k)(isD36 z3~i#IP{jBqn|zF?$O-ratLSp4(C!~nHkfA-;V6BL9)Dg32xvMVLTaJND=Kol!{lK-%t@CI z5`MoC&)Zf!xY`__u4lT zs9WM-iT^|&eTC0z^Jf-ah@KgYlrw28;}pQbgE-tB>LS%6L6&PbYisl0+3*Y!h{ZY7;(22NI*ah0MfY8 zS;?#_s{a7Ma3laeA>;;j`hP)5CV~J~uiHz%>3VD+4iBPQw05YXzsLhf@DNCln}Lr< zRTpc>GPg<`$aTI2?M-=#*)BFPBCFb~TBrHh!F@STMfH{@qAL~;i(3t%BpA6Edm!Vg z_Id|G2_OhD-TJo$A4Kfz$9YrXpbsICI6hi^_YCQL8ukhdB7Sq$R#5eS6&p@=>=o8W z{#U&A90iPViAg>uv=!#zD+RoR!VuOa)W-@pZ~_Trqzpl_AC-Z#dwUzf!N3<2LI`EF zN)%9E_rm}eBlj}_&p*3G5)h=C-%z~Ra1oa9?&@GdY>R^Ga4l z8hG{VI`_7Yoy!~EavDS$e<&?44Oq^-{MD)E^JmdO*NzVgOVaIu-qo?27+=2;miKP* z8uXL1!N|oC#@uEv?BMs%rbSW+5(ikKg#J+C^+-IhK8e4Hn;?)t{W$7XeT}?DwJD$R z3I$X#%!4*xWgkQ^FJ7!WUu~VtnTF4T2htE^7vmD(C>m`2SEQglr-zDRVwh~K-cf^U z@u_I;D}JjVvOi~xTp8(e@{C8qQVRoJeI!$wv|1()%_KUa2&%$R-F6GH0zruCzuN0Q zw|YJvv&q6&c4cUgYSz%Fo!L4GjQzlmOgk%|-}}(0QG=~>->Aq0b<3Kw$y7jFg0)`2 z0Ijg&m-o#i%%!Tu`wZ-?{|(@jK!W~|DdW@^9(2M)7e1cEI}V0aGiqqGRXKwCtII5Z zsv-wa3V?NRSU0Ao`7psc&sYgds~kHUrVeyKB`G5j8UTn5&E3w>FaZkV`|k1lMw30a zurmK$uKskb9P^~VEvmkk<`sSuVsm!^0;<4B`#h!HfbenHar_xkTe6|BDQyb_l7sg( z9YgpZRA(zwU`v`i<@!!-*{KK0!g5ja9@aE!D3EI6f|j-aA)sf4_g2|}v*y>M?xY)v z9Gv6+V=?GCJS!CBKyq8YyZZtaRPjG5YtTB#*P~-=Q@v~aKce90a4E(PoLVH(|7hAU ze^>oS)A937%>pvAWpFzkjT&+z21mnh;$j;0`1dk1e`TX~U@^-dc-yYv>gV~;%`-jb z&H|~nw_-)U9}4*U-VOTO*~`^%_%~NIOwpoybqc0qkWf zp7y+8gTrtAA>^PpWsJWSbuY`$_!9H#{8|68G1@ zETb<_j2$87SMdt=X?|S)r zCzz}C2Mg>+TLjwhr0?}-)@oeR+klhz!I^I`ccS6?d0jducV=Av{BU$5O(5f#7EL{i z6}FP6Ds3>{Soz~wNM@T+-t}bg48<%;q2Qm#xW;i`hMZ5SBp z@wNTuj3RONC;JT*8LvuK4S-%;DRwvXB;z;w$2`MNncC0b$CAgrLNnF3I2sf)V{VDV zx0796q-K=3D4>Q#Hzl`M-K(qQ!UwP9_ApJ3w++gaN#Fu^>l9L?i*!C|)6+#m$|J%j zth^%H&lzFd&wiclLLT(yWa#`fmF`pFux8V+hULB~Q$c zZyy`!N8T+?=2gCgDUEMb=}}#U-XFwD=5VgRA@9iin~9@Oq4CGVUhjDHcB?EH!ZmuA zrOl*a+U)iC&A;+F8a-RLoDMa|Q}X<_ds0$!#Axkf$shv#dL~?vx^!&F!p`?U%!KPoiu9RNTAn&MWRTL!w0pXAJnIMpCzk%H{Ld+9<0Uhet%PS_wD&q3uwrJYiQW-*D z5!bdPh1UvLRVZVb|M|rJG%i+9K6urP47Ec}%;kYdZzF!AJb%s`L|00(fz7$892T$U`pCooK!s;dASxj(vqv> zj&{xyTbDM+_vUvrywg?srv%4zufOs4zNTS!cn^^GoA8@W&qO}QV}uzy^Xi2h<}mK% zh>bH~FH_eySFC53mqB4&QTaZf@70BeHSL}+%F$%+9O?T|+)D`k{51M%B@S?TSWyY= z@o$Yj(?fp-*Ae;C7af=XUYFQ7j>C;d(XM%`j1>dPOx_JV7@pHpK6uvrPLYE1L)PbW zjzuTUVq73+cNvKi)43mFo63|310JC_qyoy3{`;gi>}x~Y&orWFwNOFC$&zR{n^>gR zEXGRNv+N316F#cpv?kg`n@s%{(%=EV)bN|C^CUA&3`CYz`&6P%6XvPK8^a=ALBXgp zTa4VAcv^TYeC1#H;A!*DGuQ*}f$XlD%1>Q*&vQzFDhFZkfcw4UBd;Us(k6;DaD?+W z39V1-?EJ%(y5$E=ToADcw=U&%i|>a@#{NAukVh~)3H9l~3B zRSfSfJ0@y8CjwV~u?C#GU39BEED}mMPMvJXI*V01Wbi|dNp0fdE#tRi&Ncqp8ER<0 z18%gf2rBhnboyC8kGfchliJSy1<5R+Qp%c{kSe*|T3Y*>$sK;V8#*{^CqNn{K&O80 z8oLw?xMYZs&;}&enUP>1I47Vl*+<;MUuzN@+H{nZeF3mOXoB7%(;Czr~-^DOjF6y6&p#?c|?v7#fbLrEYLz4aQ0EDzNi zX;n5uZkJ)$&BgVz^;aGJ#C%6S@ve_Tg`OKmm-%f}%5E-)6-C0Np)+nu#1DC_EkJ$p z;1Pze=NvApIxg|o zkH)-JhPG&peT!Q(E>r^>C{=VY^|}6k?3<6K1n@N$`nKfV22+XzrZi3!S%Fp-*9kx2 zTR!gdsIq;qb#u8gD_@@S=C|Xky()B4Vu`i&8J>4Ks2ZffM=v?$vzVH+P{GAF3AYX5;WYQ!ur^ z`mVC+D&w)j+_KfVb3+}BGhY@T{hU+n<|{CmOkA8;duX*gx-+%$c?sGuUXEe!jz|sM zoqlof*K}HYVX3dpX}8wbPWXM~Q;cdyOrY)W{=(;hs_H<~OXsei|BM6_=<3a}ptHV( z+$LjnFGmKef3YEBxsb9NPkK3rOB=qzgo^8I`kOr7os#h-Ka+TT^Ja&qiNka$ps>@J za~imt(rbI!i}(T_@F@Ivqwm<}#Z~Jn2ijmV50xtqVxhB6i$iV?En2w7{7B&~Ds5MSm?r1f@S zpS~k2VTLWSLznd&+p8xR-{b-El^v>a)T1XypV=6tj@|l+zg6y_ug}vx?PBIyiU>qJ zsYa9PD+0Q0)QQ0(+8461$wO^oqX=! zQD6Ld+YY_b3AMZZ&byRqT!smK;^$d6(&Fx5OG-b;Gu;ZUBj7n)mpyvZpRBeH}vubn-LfBfGs){AKPSu9OJBoc++8u4|qU9 zPxe0bsa^43O(Ia!tx`o|skl3dqh@P`qaF(hSovX7ln%HAkP0`iQ5U|zauWefxd{J4 zeUIWr`nyynoCD){mRO`o;{z~X#L^Mp)<&wH6bT+M`b6QIB%LlPf6(dg_I5sSL{$OM z70AQoAj~Pz7vL?kwgG80(Eoh-9_2dRjUEL(BOdVZvEyaMf*##Gs~N*1wOZd(0}qTI z?amI1F1`+V#GpuD7%jiC?LH`Z3EO|`(oM3KUC8eqAP9;cQ&fHdub)IZfUu1`iiou4 zWdiko-m#!ud-l})Nzck8AISOT5cbKoj0IL9P;(wM|82B&k%UOX`{s2_ z-`wTmUBhme_R-)_k3=eY8#S3gVLVVXOOLZP$Mbb*W(_j_j`UZYacD*E3Ji6BNFH6D?#=ko@OHH zg%g;}IFSeTR4){gbRVT4_8z&Vs4G*31zq&II+tES5u0aKd?wMp;8iQ^ZkvEdcJB5= zQY~QT#U51|J**-bSFLn_5_!J@PTS6GDUlm0O30kEU*#*UFVoUn#)AoM!8zSW`9*!4 zFdRq*@>3Btn&y3uK4X^z7XlB2?|zw7-XCV{C`Gmx`>*{%dH?EC^6i^DyCI&mGVnl0 zjEY$Gv^EC=44JYHt1C-rP_{ko*kIX~4e^X0vw|MBUnf{fd^ewDwHM0>yLFKqm;Sd( z7?l*{%(Y*ouVBu*iv`{L=Kd?^C-w1}+|M{bW~>KOu)dDJf4(fc+)zk9vad#=@e7bn z^_fXsy(p7Y?d!=%E!#5-fqGKF+~09;IHG$uK{X?p_A|HyX$-1C1_FoF9VViPe^QU_s9YIOzI?3=Vt#yrn|P%j zZxdEglJN&&z!x+LAkb+Zhs@t(2E|B-t%U?$5tVM#R-*3YW=`Q8;U;XxLYT>7Xci3g zJr4s`wu1v4Z0%&+K^Fa<_}=T3(q=HMfF!MGIf34GHmGHwR=aP)$@3#!Amw68ZsvB-OT)Hp!Z}Lsywtq-@_lo_AU1> zD{8zX@D62;rs@TTgN>cCKWM|}boHybsI-|#jXCmz8gDE%rUXazT`%O5bPclqv4?F! zh_7=R^bAt{m9?1@v@yHEu5~Jq(mGdAzd1!J;X%GJi@qshVk@X72h4Zs9_8C)FT@#J zA6CcUa1NH%aTeD^LCUcarJvE+a9y53xGMDz6@W=2(Z|jBmbmx@o`q$F!Wt~7mDJUl zwuh7Aa+(x)Kx^@(rf`@_iEqeqOvctr=gkYN%7tmvI2w(Y6He@m7$!^G+sxp`k3wwH z>RK(nr5akG4Ti%*q+xLtV%lMwQ-E|=CvLsgdSD1;lXy-x9SXb8N|HZN0B))kqniA#nEYujXTyuK=Yj#3+rLkk zqFH{=urW=fRvWtNp+{pwOt4iQMwB$`?K;%zRP+&F`;#mQRlCx;b_f?&{1IjVKJm?2 zwQ-caB+$gU@f+gsb=p6bJLpz(0h0dweAKD%m_~zJHO9>7|?LCDR;j9Jj-0e2gP6mxe>| zc(R7Q6?kBk=E1`4(R)IxMqFE8uNoI*@$pu~S36BiTOPQHpzsI~#O8#ZveEBV-xAvu zsK4L-GW#4JPtA4rAu6D=P{~)VDGL)?B_A>qcPz344iZn93kP^OPnqnfd4H%PA%rV) zepu3RsIj?W7n0N3i5Z?WgLRv{zAxd8{>q;pH61Bd zH@$0R8cv_U1H-9Es2ryCou3`rRW_Y6khqAl4E~A% zJwxNRgYHjAbAyPfD-|kk(J#YgvL_SRL@NGHKK~`qu235id<6?>m$`4ue;GKoYCnjc zy!Gnk-6bQeYfGm-HIM*BJ(xWg2{r;Q$$<__j1lv$qIt|Tr2<&^o*3d$ZtpX(*?P3E z--hTo6u$cS$*=u)F#SEmQuS&*;$v+LmgnNh4~)Xf)5(8rF>&PvAAm3fgAIu)b6?xG z?%_z*2zV|`{}}s*+@!)>TV@zDmrD`fJy`$mYMxe5p|zmO{iCSKpim0%F`Yh(uA&T$ zk8w&mU=qTzH!qX;`Q2c<$anNtSh>~zjwY&q$XT^G70!Wo`Pc{8VA74K@wDfSQ8b!~ z&m>r$3`o_+ymRd%BT>)9yJMHY5gGRncBMIIvjg~| zVMm+z@!qY#C5W^Mxptn+GB)T>@Zt8&vd2Vfyay8+Sm@A*=?OM#@scgh1irY=XVt|w z97-*Aso4GGtSbq$AF5Z_QcSKv`_mgXKVYy}ND~^qU44AQvY|xUJMxX)`j*Kp1jzMHbsBVdAcjiB+)_}lR*j1(_nxH`tC5QXbcv4``< zsd&n}Qm=+rM->;V6#RZ6(CXzG9{ca&w~4W#qo!W7z`F5`~ z%v1KG^x_5f;vb(cPs;PB&fIB?HT=u5a$iI4lM}Z-HN|`9Te6!+mCV*cm4QNRs-?54 zjLXHdXM1;&?G*G~#7^%)8h+>BEq~kwtXd_c^{5mKMf}@p|29if6<+Z#WsFzYgeaV2 zX;#+H%XocieK1Qn0BSFx`h%mEBfuu-Jg5$2c=gyg(lE0*{&_PJ-#az)+rI_;%j`;4 zm|l8~!qubIMJ)4YoK{PF^8YC^H#4q*Mp$b7ek*sDDT7D$OY|!inC-umLG0tk59@bg zQo0-}+rRhx1zHVOTs%2)0(SO>(R-cg4SyGydPj4sCj=uJ(2j;lD9Lx%b4RzorLfSY z7kdZ}iyCSJy*9y-^T{Y=dMiBj(LPGpbouOce->uP^3&RHUHk>2hoKWQXL(>R9-I#y zK1XAtO~0<%*Nq@UF7G77#wwd0M*IU+6^lK+&X&pBl{~H%p4z^eV}^$}oe^S|g>Es! zS4)mZM5JQ#eRSbMhRMDJ&gUmJ@$RTx>-+~s)zRz)NUN0t;fN>5RC4C3*%Y;u0K2kR zFTS%0L~{fmTOQ(dFj5(%%#jL02*1oF4g^l1?QUIu`ybHpd)RN~HeqhCHTlaqYsm1N z6w<0b6T^@3+YMIni#W5%_B6EsSO1lgGS^rRzqFRgnuZmRvL)gj$r=o**oLp~ymG&Z zInDSIa5RG41uqVJFih${1UHrqB&$`~NEQI$9aS#=o@~bWCw;^uaK%W$!7|>Qv+by* zte}pIzjSXR&!M`ZOaz3mm(lvdyI1q!0`yFJ7MKS=w_W_4gLbq>-5U zVz5t>&rFISe6C2_;4Uu3A^pYu`wPZsrCvqvL8H-QHVp>KfG_ zaQgoZOKTP1_DM~OY6#l|57ojp#+jVOmjWc7a6!1iW8lfcI<34XK-FDMjbivJ1*KB`Jo{yk0) zFhD(3-#A;%Qug-vo=;yWgk>6RDDD&emY_EI)_;o4=_+<_KHdBKr}>)j7szegnA$Q+ z&}xEB=(gL9O-WUH!A{s7`|Z6I zq(3X$G3HwF!9KQFlySNpZdG5A`voW(L9smx6aZHm(;ERO($v$p0jlj@dR+~gkmFSr zkqvV+eP@%lK*KN}0?SFL}Hw&$6jQscH7m!dV7u z{HQ>+7G2*>CqDkhYz+A71;V)}*}Zsxt_nC5exd?Rx&9nDzCdF^(7y+}Pm7%`b9<+n zX2%xFoE_|R*|pGPC@Q32Xuwy`<<9N%9gS1R+SrWW14)Y7Y*{Snyog&qA_!Ln6WMMH ztqOyUrxiQsezu^89s2@}8IkH4{i64}!gI@PgqNTIR?k|DQ722I%DF^5y6T1BG;~-+ z%)*3*^6bcl5A?c#P0!@O3Mt8NbTCOQ>(Zm}4VlswQRLahCr>ty1;ULHp3Ga5U!;Ux7A|3NM*|r z!3j`bPcxCb@%e3P6T^?9V&mY$t_vfm^=rK%J9OW1vP+szJ~+45n29%-KJBAV`N)z{ zGi8plEj{z-`)9FlJ;{k)TFZ!yvN@R?W`~u_&azLlTh2}7^zgz4e0}u$UVeV}nN{IM z*_jc@rAs(lr)p`)PHKB!`I zBct9SpkHbc+fmbRs7V43Y``l{6H_904F17fl8ijpJicr$dw8&mZ`Thyin~>qylkrO z$Su~=OjJ0^dO*9+$jqrV*)H*heKZYxrM1d2sliF};u~UNs3opWbEuc|?@jS^?+YL4 zZPXt+$32^E9jeB7vabnDiV{|M+e7?&uAJ(vDNHWECY=9yXxCOU@xQ0X{vj6^4IA)j zr}yIxHKKV13o34}92~rfQ9OI_r+N5P|4$;Bj_;gi%`mLL?(rAX-9POoXOF?@yRoo= zOXRWX9ZQYK)$MO{FZxmK>s|kr2;YU)yLX>j=2xcp+3=bEo^muiu0XVV@lwsl zJ{ZJKjzy-gbnwdB$!K05E{Y^^zxFgA6ED5b8ouSpI=osh{>y09yZCpDN8k9I`!|K~ z>fIc5;-|i;>xFt^#FpW$|F=9}u<$tNY6H?smbC&slVAtwpV0!lfaRM%Ad@06IZ-?9 zJ9+}Z9JYP6wnaPAhjtFv{`lV^Nh<(AaroC4!$SZN0L9)1obDuH$m|l3oKsTd1I)a8 zOGB0bnf(KCoEAv{!2ksL{iw+xATSW~HBNbXwjP-bLY|I_>CY50lrexsmlgKszo5pz zkTa2rv@0^`EC4ebV4^<%-;+V3H{MYL{y#-r?LlRvXUOOQw5aNFa`hS-I|0W*c9I)P zW#nyRyNJvsGmr61!g$dDNhKQx(BYE+2ssEQl;}IM|1m|keU&+|ML+92tr9f_a(^S zcR->9DZF#Ko)7@O15mwk+q=1vP!PUFZ ztJ>%nNDpAhC*-w}TVpZ5JzEOM|ARx!|I4as2c$cwx)dLi05#9@%~gZ_r9;NYR$nCt zeGjR#!3693i_oIh-a$Kiav+V1=MIAf(*|VGLwc?d+%Zf|; zT34He{@K4nzA9f3Tq}BOmA5~$`G@EPe*MdP3`l)gNn70MlM44$@*pJH`9gd_u@NdI?$GPUa^LTDi!6DHs`oR! zc0w8OU!N>YI+{LB2eSLcn6!BRkUeDGXD-Z@h#Nbdrj)}^%b3jH)i(_`vY!6-ijPvh z@fLWx>9UUT&>P}{B|A=oPrg_nd8WsGvSk<8JEzrfrWH#zF|1wr!{U;c^kh$rLJYF+ zE9*&@5<$!pY~Frqp>Qy@G>la3%cIa|fDp^jSN1lKC!9-U1HE=airSSDj z94*Pffg14czvu5JAmf`Tv7dg4T|FJ`lv8dxWhZ{RP1>K?C2*p9l^li|Z`Bm{>elbJ zj<^1FV{!+@`XWH-Ta{cvti5ROv2yEa!Y1oLZzmQRbxjE@{PYwm`Ai}MU!?tame1g) z**89sHl~gz(xpyx0vncYL#ym;8&WTfGqVHtqko7j0}mnjtJRo8&)@&Y9yv!0sOeuU zx^kO$E?07d2nMH`t+yt?)b{zvKA}OEW{kb45am8@QtgSO0Z}<$+)j(dO5Zw|BizEL z!nTq3Jm7vGq;K4JwdJylYrMpb`R@w@Qb&_Gr0s`e4c9XtK0a8DJ5n)^UcbH%8MERX zqZqp}O0>TIXqT-! z5VnEBDdOG5`lT1AWcTNtRo~+u&PRUE1-3^qEV--u;$(aO*KQw3=L-yK_i}ty#*D8u z_QTI|)cB8Lfr)K0jkvETgi22rQ=@McQ_Bm5yB0+hmDBe$QJr|2%{%Jz1Gd%GVrfV6 z<3x#_9xufsTJBf)eB`F0KPh)yufJ_>ohK_zHom@FVGG!sTUGWJ`D!{&VM<2d?r;4k zQXFBA+J}wf7{#%WxwVQn6SJc;KlbEiE1rKyIe@n){_!XEeeoCna(#VoLjZU8~#H;vWF4?m3WJxqxQ%`28gKN1D^ zb0haXKP{b~jmE&3jE6mLX4UVWw=JNac3#Eq5IyFm92K5MiN|b%OYYz#oHwx_*!syd z&6yGQ;5clsFrR@<0EQrWbD`I}SX*kW?pTK>4$I6cry_pp{G3a}Pobt)V8I-$wJ))4 zsPYYlp<9B6><&|>Z93Tk$mufDg)8sbUyyjusU44*n!*DYdADIlm#pnQfsxs%jQurx(-D>omCAUq9@xGNHiv^A;>ct)@jE?m8`7Jx3>c z^8Z#1S~0nC{suvun!EOPCf2V4F&xP&!q4LOFLg~N9-{`PWHCTgdfH-4$9{02unIWP-Ys+yUC)sly#VwhU`b9vt!6SYiUwNtm&Nk3Ql&WCDEY~>V~d5W!#amrcrdihoU=%m_%r!?nN7v%T^8on4P z+AK@@af=q>)cQG_0~fS}YJ>0n5|rp~V%JyUtt4(f*pIEG`_yA0lhB|Mq<4^t17ytk z94SJob?bT8xHE1D8DZ;e+x<(Y)%aa@7>JFQXs?cg_{%tLBKROQ@;J!h#_Vt;g6o;6 z{ZwAUOM-`foGAjQ)_RWmg{_5_L8b4ExC?_cG(UvffoY(&d2Pni{E?Fe8wIbHnhN*05+>;P@-eZ$bXNrBkQL9G43-mspL<2j0vj zifc+jFBSBjs8h=!!ZL5BqkGyJ*j_Jh<9#kMi0;V97~QanO7>)T=Ovs$AdF87yY2TU z;v*pB!_+m5Y(ed7_DbXUe9_c2PyLJXsczx!_lYTPIj{ zDAgxsLUmM;C%>EO1RrPPH1_a$L4G(NNHDvouWBAh8DQ%wG*9aDLd zc3kOE1ekO`Wd~ed<77UfVj{NN1Y!eu@pFqch$Mt4a#erpoZoOxT41mncUmlL^z{nx zXLS0^QM(kU9OXQ6DHZpU3Sc7y$1`FXWlbm4zY|HPMly>T1KRwT<3r*_((sc&rkm+8 z{>z3Zp9)ns74f^4zhk=OnyNBC5Bmct-*KCGslNwoAkX?}L?29jW)`8fZ6h=*k43y4Q=5SPP%0fy5`5 zj?9OC`%7(d9`gUtQA7~V7`#-DZSxB`71qK(Xv03dSM;14H2`GME3zJTj|Po4Ey2m1 z#liBp*nF2vut5Koi$@lw_L_U8tnXoiaCUC7l#akRO_#|1W=y+ly{G0P>Zlv^1C$6a zc>eJSi2=>5kX#s3dBEUuiWf`G+xH@vh;^T>a%pqAR9$i<)jna-@)%0ZSNke}uF#Jg zb(O?mh$DKpG)YUTQF9V5a#>8~#i#g~l<}f~ti_D?!OF}g)r1aaEB)fv$gQ~Bp!Ipkc^~15KGGX?Cn6B5f5q^ zz+qD$M`NBk1g?}TW?@MYt9GsKKk^YoDLRKtBOQUxL2lgQ@fgwnip~4a8$(3ziGx^E zpLgBEL&s{)<6WOQ^x?$*%d{vMe}fE|GoT8d59f-b#zn*X7JsE_AH&teEi1wMY+B7pyVKOCm7 zi-B1GL80yc2-(i? z!aA%&8Ue-~#UUR^>1z(+(VG`8sIW{nGD1`%Kt+H0P9Tf z4=T$-Ls}KTw7)nT#L4^hzPH|4q%*)O3BPyX%{tIp@UDlI$<-p7yR66DgK?B_e73UBB3<2V=bB33CCy zk)QI+2;;+nO!Rw5zU=NYY`?yqYMwgWP{Z6qqIb}EAt+p-BuBpC z9vw$BOzfRD*?r(=1sA0~+>!?SBJCO`!i8k8%voCXqQ|4~$*3ahQ$VDj4OvAv{fik3 z@%J!dN_|0+3PQK}GrM|QoYUgL(Yrq$j3HPqi!cLhHc=W0ofIByCIt=ike@f(GY3e2HIe{PJ=`;c;3cue}`}zXesd@QINf9@iU%RsbPUjraj9D+YX_5UQGR3!}fq< zj?#yU$c6cHonPwB-Msm52ZIHiyOzlhFkfK zxuehjhZY}1m=a1my||v65cFPO-s0|T6qx4lqrfMzcSX3`dJ3MEaf{hoDUQ`UPmz!U>)yn<{{SyE7kfYn=!3Nw783C%_|HI! zzNLVfmYhjt34Kaw>acL|2jjOdlHK)KO~)c?w>(BXW1o+%~>zBXJV3r>wgqA3qAb zY7%h!+J=rJGj`Fy6Zg5!Fo~h8e;i71V_tEUgTf2YS?T08G8@hM-@vW^TR@IB*#4P;xyeKB&?w~4T}TX4B+ zkQOYytbg~P+?z|;dR(-lA%1-S#eLj|rZwR3z^b_QznM7riVc%njtKq+07_0FCwkakP{hqTB247>EUD%Hd*2RIS z0NTF&(PK@@ugt3C-9@KN6vPjw?2Yl~R`G5Eo}55>%KF6nVC~TW8uehL|7j=AL&{A0 z|Lnvit!!P*oc~#c+Zwr=iJO@?n3|C?$(z|*xLT6(uyOr2qwi|X_1JYzRKI7Ub!bFOm~wOoq{hCF|Lg-DcN;c3=h>%P#|mncfD+m;3YG z;e0_E2@v>TBBI*M>*>uq({7f@EIKt-q0#2u6pl&Rvct95`|bVs?x{dHQveEUwY2c} znmxhZC-KIj>a>E8C#N6F+cn3oJpA1+QG^+?AcVAO*zB%g$5-zWbcx2|o3M{h6HkN} z+upEogYe^VqIDbjOz)k!g?C69^knvz-cRa4q__q}b1|$%f7g3e(wK@`9ZF!>AND%d zbV=MrJCDCmMbuI-O&;qN7&sSSOs;L%Sb1(vmwuF0=i ziLJT+n@Z#VBzFe4Sny*oKJ4ML^%7N_TQ()1GWThCB`GLbc>RcQ-t&@874XJ5UTI^B zYX@^9R^s}K9d932mF40l@G&J$l9|?|jY8L_fMZ%KG&ViS)!Ei;a4*4CaYwb{dx*kB zxcN#qLhn2;_5{1~q2Xlt@VwAZoGtQIkxVvH?a}j&a%Gt%HY$Hai~VaT<6&wR-aXzR zWY^SvIpRR&DPNe@XHK9bXtqL>*Xd@4n>3gG+1Ri~VMLpkJcf%ACIUf40z>=TVX|87 zFP}Z|BOb;P2}ex7S*b+&#G|t3s9=Tarb;@E&J?@SW3wVVYErJ=?7Njb9$E2vk1N<29}Ne1&fty0lyBrrqT+((>@@&%mNa^h<@%S&C_3H_@)Z6TXp5$oVb* z8lAVzk|MTc33U(6I2(S|?+_I2I`1Ehl@$$akk#`#UAa*@JmB3mU4=YpzvrssV!`GG z(5*1l`-)l13r{1;Hj-D38k%Gv zhrwTs7>2{4k}>;oS7YB}efLte2_BWXiNSL}XBtLltG0~L&Kug3cKs1B{ z+~XBiTfq|Q@ZyQYp>@-vsiBm2#>jOggvEtTRk~;k+8n{j=I;eceJD-Ix86T{u6@3e zZcony=jZiG6&Kaq6g|F6iW{+qLM>ji6)oRb411Kznr9%b zJ`ijLjI3k{Y?D^-q(@wy33>Yd6L+C3K{GqTSR@k6bEw@&6d9)4+0YX%1h@RQX_OTN zvJq|u7n6)T|FY`Vf4LM8Pf&wFIiIhbuhXz`0>wpCec{g8Y&b^gt=#xYWM31m*+_3A zOmFq3m1)AEq2LKNT;Du`?1{)L^dOJ<{`AF}JRni2z?DfkB=k;XD4^w! zs<;=|CI#kEXA8lvAi6?!Kx*|l8^X-s<02n47+7M{6=cqd#Et1{Q=ZS#JH->Xmdul0 z66?Nm+RZgsRxL$yj`|uh$4bpY+0ohUxmjfqifd(xzb7?~ss>%p)F|Y$Xt$At3qQv= zb2X>HmIZ3$-Zgtx*j9@_?{htmQDD-bs8Q*sVE>jXhbdBUEjcp&AtOk|+kp9$-CcL;H2{lS>)ed9rv6JI$( z39Cvx)MtjKuCtMsxraI^;y%PPxq96M&8kfxGG8yG^b}HUP*`&^Eu0~ea3Q5l-&@K! zQ(Jq;az=|iZ(N`DHk!}AItPE5WhMAL$NtLvxTkq4l1Gy(IX%v=&FG&O?n%fnlpZVj z4s{D(7yf3%&!^o`R(+_C52L63lj_AkiGf;bRp}~k`9R1LXvQQ!v&k;QSS00GUE})I))1@KKZqJ76`rS7P6b!I~O*=DsVWeG_od#QaLaWEa|cx`9*gJ6RRo98ZhwZYMOWG4M^ACsr$Y z;bt@n@#mIZzB3*}cht>uzUp%0#m8dUs=@I{>ZTcz3M^sv4ADzE-#MBD#ea{H%xVNXq(6wqDx z2c2nUW71tNXw{UYG!WILo>^*IrRawGHT_mo7keK#xt7fk12ODd7MpRrxdbQD$2jU+ zxKirsXsu>;)ia+;UI7`aZ(yt1wPw#loF zTu0C)JL!il>)jLJ?Gm>diGv-Alyz=@2;>imP4%yYt7c=WPjiPcY2npq#ZW@mRC5#^ zA5hFJ%IzE{e)j$Iv=cIfoUInmiyf4dh&WA=MPWdz7l8d-2w#eVKABH@Q4Id7E)|z! ziX43LJaE@8Up>JZ6iD^8&Q}=reCfj}oh?bF=ME( zsus&wW4kHUQ>RAQRqH2(opl}INp-cR+0@F|qklHRUmTWiAm6(6@OMt?DEy)!kExS~fU|)%uH{I$;{7-fCd8O^) zN96u#mU@l&fS>@2zQo(_QjuU=6nUV!mdyxF*r_ulhgpwBsby@Q5)-CyJ3d%*j$--jV^X2J1@ zXZVQ{(=Y8V5Hm|V$`9Zt@U9T}kaoX@#mTUK)oR)-j`VjmN!sR>-z+2T(IR2>zIRYi zA!087;$R0CWoDmClYdvr<$aLHP&H%BT+Ena&x zUZZR8^wfCnm;$@34klQ4-28Cjxi;g4z;e@$#_yEd8xW&)%kOp#L=L47h8ql9ePQD% z;K_ub-BXW-?u7N}5h$1-BtM6nzrF){9eaC|%dnbcN0uw5Wz)XM0Rs8XZw9g}(uY1P zHUw|=@OLKCC71P?prU@m`5cOc&B4hzh5~{WXuFF7)(aE*ut)E9{j-LC7TP{57tsk5;5%5FIcN z%+?eOZ04~~o5-X2H7&!;l4F|622jT1bu?#`W+;I1vbTQRIA<1o+~YZ1wO5o!py-ZA z@v??@6{m4uGPn@R0uuOdUg<^Ju`3v%WGFb%42C{bUd4ppcm(>U%{;Yl@2loK z;WqgCSTN*X-L<`lktUe1N})`xY~Nm$`0}{uulaDlGOQD?q|9tpfLceQ1lqlB*U)w38dqqo0T9U`C1UMQ}4Jj-9dUwIth_I`GKH zrZpbmx65FU65U07i0UVQEv1_;7bN{uZ7j-=>7@h#AEXwS6tmbSP92AMld?yr*e(1l z!W!!ve>aA@G`Tdm1iLieD_A7Bgx~w69YwkiwkPV9*Lc@ljq+-$M z9so!EDoH*w48Cq!7W@Qjr!7*;Pnt2h^~3dh6?{%eiHfgJaPC<6qKwO{{;B2i)&XsQ z^)B12Vn4iL*PdKIfsYkZ-Z{=&0~Q>&|jaC&c)8z_YRM4j}#Ba8rg2coS6LQbX z#b6+gk6-t*@znAl&bq#%O9)SL`2JFIDIqk9;FqA-y5*0}uOP`_OjIJN05ndTq{ySeQ?Vpqr5GpBwmw}tBDUn6FY+VLlc-#QZip_`%4oTR&j5_dP#52aEep4


    ?zIrZsDQLhI$-M-`IEYU5h|B{dzv=_H@_^ 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�C6O&*o>b44Knygv-0~9>0Eb|G=YRNt+ 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 literal 0 HcmV?d00001 diff --git a/docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf b/docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f490e1d0ec53c3c731cc70854886d62d08584daf GIT binary patch literal 86510 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 out + + => memory: [0,0,1,2,3] + => returndata: [0,0,1,2] +``` + + +#### Summary + +A memory-corruption bug within the EVM can cause a consensus error, where vulnerable nodes obtain a different `stateRoot` when processing a maliciously crafted transaction. This, in turn, would lead to the chain being split: mainnet splitting in two forks. + +#### Handling + +On the evening of 17th, we discussed options on how to handle it. We made a state test to reproduce the issue, and verified that neither `openethereum`, `nethermind` nor `besu` were affected by the same vulnerability, and started a full-sync with a patched version of `geth`. + +It was decided that in this specific instance, it would be possible to make a public announcement and a patch release: + +- The fix can be made pretty 'generically', e.g. always copying data on input to precompiles. +- The flaw is pretty difficult to find, given a generic fix in the call. The attacker needs to figure out that it concerns the precompiles, specifically the datcopy, and that it concerns the `RETURNDATA` buffer rather than the regular memory, and lastly the special circumstances to trigger it (overlapping but shifted input/output). + +Since we had merged the removal of `ETH65`, if the entire network were to upgrade, then nodes which have not yet implemented `ETH66` would be cut off from the network. After further discussions, we decided to: + +- Announce an upcoming security release on Tuesday (August 24th), via Twitter and official channels, plus reach out to downstream projects. +- Temporarily revert the `ETH65`-removal. +- Place the fix into the PR optimizing the jumpdest analysis [233381](https://github.com/ethereum/go-ethereum/pull/23381). +- After 4-8 weeks, release details about the vulnerability. + + +## Exploit + +At block [13107518](https://etherscan.io/block/13107518), mined at Aug-27-2021 12:50:07 PM +UTC, a minority chain split occurred. The discord user @AlexSSD7 notified the allcoredevs-channel on the Eth R&D discord, on Aug 27 13:09 UTC. + + +At 14:09 UTC, it was confirmed that the transaction `0x1cb6fb36633d270edefc04d048145b4298e67b8aa82a9e5ec4aa1435dd770ce4` had triggered the bug, leading to a minority-split of the chain. The term 'minority split' means that the majority of miners continued to mine on the correct chain. + +At 14:17 UTC, @mhswende tweeted out about the issue [2]. + +The attack was sent from an account funded from Tornado cash. + +It was also found that the same attack had been carried out on the BSC chain at roughly the same time -- at a block mined [12 minutes earlier](https://bscscan.com/tx/0xf667f820631f6adbd04a4c92274374034a3e41fa9057dc42cb4e787535136dce), at Aug-27-2021 12:38:30 PM +UTC. + +The blocks on the 'bad' chain were investigated, and Tim Beiko reached out to those mining operators on the minority chain who could be identified via block extradata. + + +## Lessons learned + + +### Disclosure decision + +The geth-team have an official policy regarding [vulnerability disclosure](https://geth.ethereum.org/docs/developers/geth-developer/disclosures). + +> The primary goal for the Geth team is the health of the Ethereum network as a whole, and the decision whether or not to publish details about a serious vulnerability boils down to minimizing the risk and/or impact of discovery and exploitation. + +In this case, it was decided that public pre-announce + patch would likely lead to sufficient update-window for a critical mass of nodes/miners to upgrade in time before it could be exploited. In hindsight, this was a dangerous decision, and it's unlikely that the same decision would be reached were a similar incident to happen again. + + +### Disclosure path + +Several subprojects were informed about the upcoming security patch: + +- Polygon/Matic +- MEV +- Avalanche +- Erigon +- BSC +- EWF +- Quorum +- ETC +- xDAI + +However, some were 'lost', and only notified later + +- Optimism +- Summa +- Harmony + +Action point: create a low-volume geth-announce@ethereum.org email list where dependent projects/operators can receive public announcements. +- This has been done. If you wish to receive release- and security announcements, sign up [here](https://groups.google.com/a/ethereum.org/g/geth-announce/about) + +### Fork monitoring + +The fork monitor behaved 'ok' during the incident, but had to be restarted during the evening. + +Action point: improve the resiliency of the forkmon, which is currently not performing great when many nodes are connected. + +Action point: enable push-based alerts to be sent from the forkmon, to speed up the fork detection. + + +## Links + +- [1] https://twitter.com/go_ethereum/status/1428051458763763721 +- [2] https://twitter.com/mhswende/status/1431259601530458112 + + +## Appendix + +### Subprojects + + +The projects were sent variations of the following text: +``` +We have identified a security issue with go-ethereum, and will issue a +new release (v1.10.8) on Tuesday next week. + +At this point, we will not disclose details about the issue, but +recommend downstream/dependent projects to be ready to take actions to +upgrade to the latest go-ethereum codebase. More information about the +issue will be disclosed at a later date. + +https://twitter.com/go_ethereum/status/1428051458763763721 + +``` +### Patch + +```diff +diff --git a/core/vm/instructions.go b/core/vm/instructions.go +index f7ef2f900e..6c8c6e6e6f 100644 +--- a/core/vm/instructions.go ++++ b/core/vm/instructions.go +@@ -669,6 +669,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { ++ ret = common.CopyBytes(ret) + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas +@@ -703,6 +704,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { ++ ret = common.CopyBytes(ret) + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas +@@ -730,6 +732,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { ++ ret = common.CopyBytes(ret) + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas +@@ -757,6 +760,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) + } + stack.push(&temp) + if err == nil || err == ErrExecutionReverted { ++ ret = common.CopyBytes(ret) + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas +diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go +index 9cf0c4e2c1..9fb83799c9 100644 +--- a/core/vm/interpreter.go ++++ b/core/vm/interpreter.go +@@ -262,7 +262,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( + // if the operation clears the return data (e.g. it has returning data) + // set the last return to the result of the operation. + if operation.returns { +- in.returnData = common.CopyBytes(res) ++ in.returnData = res + } + + switch { +``` + +### Statetest to test for the issue + +```json +{ + "trigger-issue": { + "env": { + "currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x1", + "currentTimestamp": "0x3e8", + "previousHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "pre": { + "0x00000000000000000000000000000000000000bb": { + "code": "0x6001600053600260015360036002536004600353600560045360066005536006600260066000600060047f7ef0367e633852132a0ebbf70eb714015dd44bc82e1e55a96ef1389c999c1bcaf13d600060003e596000208055", + "storage": {}, + "balance": "0x5", + "nonce": "0x0" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "storage": {}, + "balance": "0xffffffff", + "nonce": "0x0" + } + }, + "transaction": { + "gasPrice": "0x1", + "nonce": "0x0", + "to": "0x00000000000000000000000000000000000000bb", + "data": [ + "0x" + ], + "gasLimit": [ + "0x7a1200" + ], + "value": [ + "0x01" + ], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "out": "0x", + "post": { + "Berlin": [ + { + "hash": "2a38a040bab1e1fa499253d98b2fd363e5756ecc52db47dd59af7116c068368c", + "logs": "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ] + } + } +} +``` + diff --git a/eth/api_admin.go b/eth/api_admin.go new file mode 100644 index 0000000..4a3ccb8 --- /dev/null +++ b/eth/api_admin.go @@ -0,0 +1,143 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "compress/gzip" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// AdminAPI is the collection of Ethereum full node related APIs for node +// administration. +type AdminAPI struct { + eth *Ethereum +} + +// NewAdminAPI creates a new instance of AdminAPI. +func NewAdminAPI(eth *Ethereum) *AdminAPI { + return &AdminAPI{eth: eth} +} + +// ExportChain exports the current blockchain into a local file, +// or a range of blocks if first and last are non-nil. +func (api *AdminAPI) ExportChain(file string, first *uint64, last *uint64) (bool, error) { + if first == nil && last != nil { + return false, errors.New("last cannot be specified without first") + } + if first != nil && last == nil { + head := api.eth.BlockChain().CurrentHeader().Number.Uint64() + last = &head + } + if _, err := os.Stat(file); err == nil { + // File already exists. Allowing overwrite could be a DoS vector, + // since the 'file' may point to arbitrary paths on the drive. + return false, errors.New("location would overwrite an existing file") + } + // Make sure we can create the file to export into + out, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return false, err + } + defer out.Close() + + var writer io.Writer = out + if strings.HasSuffix(file, ".gz") { + writer = gzip.NewWriter(writer) + defer writer.(*gzip.Writer).Close() + } + + // Export the blockchain + if first != nil { + if err := api.eth.BlockChain().ExportN(writer, *first, *last); err != nil { + return false, err + } + } else if err := api.eth.BlockChain().Export(writer); err != nil { + return false, err + } + return true, nil +} + +func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool { + for _, b := range bs { + if !chain.HasBlock(b.Hash(), b.NumberU64()) { + return false + } + } + + return true +} + +// ImportChain imports a blockchain from a local file. +func (api *AdminAPI) ImportChain(file string) (bool, error) { + // Make sure the can access the file to import + in, err := os.Open(file) + if err != nil { + return false, err + } + defer in.Close() + + var reader io.Reader = in + if strings.HasSuffix(file, ".gz") { + if reader, err = gzip.NewReader(reader); err != nil { + return false, err + } + } + + // Run actual the import in pre-configured batches + stream := rlp.NewStream(reader, 0) + + blocks, index := make([]*types.Block, 0, 2500), 0 + for batch := 0; ; batch++ { + // Load a batch of blocks from the input file + for len(blocks) < cap(blocks) { + block := new(types.Block) + if err := stream.Decode(block); err == io.EOF { + break + } else if err != nil { + return false, fmt.Errorf("block %d: failed to parse: %v", index, err) + } + // ignore the genesis block when importing blocks + if block.NumberU64() == 0 { + continue + } + blocks = append(blocks, block) + index++ + } + if len(blocks) == 0 { + break + } + + if hasAllBlocks(api.eth.BlockChain(), blocks) { + blocks = blocks[:0] + continue + } + // Import the batch and reset the buffer + if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { + return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err) + } + blocks = blocks[:0] + } + return true, nil +} diff --git a/eth/api_backend.go b/eth/api_backend.go new file mode 100644 index 0000000..8a9898b --- /dev/null +++ b/eth/api_backend.go @@ -0,0 +1,433 @@ +// 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 eth + +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/consensus/misc/eip4844" + "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/txpool" + "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/params" + "github.com/ethereum/go-ethereum/rpc" +) + +// EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes +type EthAPIBackend struct { + extRPCEnabled bool + allowUnprotectedTxs bool + eth *Ethereum + gpo *gasprice.Oracle +} + +// ChainConfig returns the active chain configuration. +func (b *EthAPIBackend) ChainConfig() *params.ChainConfig { + return b.eth.blockchain.Config() +} + +func (b *EthAPIBackend) CurrentBlock() *types.Header { + return b.eth.blockchain.CurrentBlock() +} + +func (b *EthAPIBackend) SetHead(number uint64) { + b.eth.handler.downloader.Cancel() + b.eth.blockchain.SetHead(number) +} + +func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + // Pending block is only known by the miner + if number == rpc.PendingBlockNumber { + block, _, _ := b.eth.miner.Pending() + if block == nil { + return nil, errors.New("pending block is not available") + } + return block.Header(), nil + } + // Otherwise resolve and return the block + if number == rpc.LatestBlockNumber { + return b.eth.blockchain.CurrentBlock(), nil + } + if number == rpc.FinalizedBlockNumber { + block := b.eth.blockchain.CurrentFinalBlock() + if block == nil { + return nil, errors.New("finalized block not found") + } + return block, nil + } + if number == rpc.SafeBlockNumber { + block := b.eth.blockchain.CurrentSafeBlock() + if block == nil { + return nil, errors.New("safe block not found") + } + return block, nil + } + return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil +} + +func (b *EthAPIBackend) 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 := b.eth.blockchain.GetHeaderByHash(hash) + 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 *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + return b.eth.blockchain.GetHeaderByHash(hash), nil +} + +func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + // Pending block is only known by the miner + if number == rpc.PendingBlockNumber { + block, _, _ := b.eth.miner.Pending() + if block == nil { + return nil, errors.New("pending block is not available") + } + return block, nil + } + // Otherwise resolve and return the block + if number == rpc.LatestBlockNumber { + header := b.eth.blockchain.CurrentBlock() + return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil + } + if number == rpc.FinalizedBlockNumber { + header := b.eth.blockchain.CurrentFinalBlock() + if header == nil { + return nil, errors.New("finalized block not found") + } + return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil + } + if number == rpc.SafeBlockNumber { + header := b.eth.blockchain.CurrentSafeBlock() + if header == nil { + return nil, errors.New("safe block not found") + } + return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil + } + return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil +} + +func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return b.eth.blockchain.GetBlockByHash(hash), nil +} + +// GetBody returns body of a block. It does not resolve special block numbers. +func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { + if number < 0 || hash == (common.Hash{}) { + return nil, errors.New("invalid arguments; expect hash and no special block numbers") + } + if body := b.eth.blockchain.GetBody(hash); body != nil { + return body, nil + } + return nil, errors.New("block body not found") +} + +func (b *EthAPIBackend) 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 { + header := b.eth.blockchain.GetHeaderByHash(hash) + 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") + } + block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64()) + if block == nil { + return nil, errors.New("header found, but block body is missing") + } + return block, nil + } + return nil, errors.New("invalid arguments; neither block nor hash specified") +} + +func (b *EthAPIBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) { + return b.eth.miner.Pending() +} + +func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { + // Pending state is only known by the miner + if number == rpc.PendingBlockNumber { + block, _, state := b.eth.miner.Pending() + if block == nil || state == nil { + return nil, nil, errors.New("pending state is not available") + } + return state, block.Header(), nil + } + // Otherwise resolve the block number and return its state + header, err := b.HeaderByNumber(ctx, number) + if err != nil { + return nil, nil, err + } + if header == nil { + return nil, nil, errors.New("header not found") + } + stateDb, err := b.eth.BlockChain().StateAt(header.Root) + 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) { + if blockNr, ok := blockNrOrHash.Number(); ok { + return b.StateAndHeaderByNumber(ctx, blockNr) + } + if hash, ok := blockNrOrHash.Hash(); ok { + header, err := b.HeaderByHash(ctx, hash) + if err != nil { + return nil, nil, err + } + 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") + } + stateDb, err := b.eth.BlockChain().StateAt(header.Root) + if err != nil { + return nil, nil, err + } + return stateDb, header, nil + } + return nil, nil, errors.New("invalid arguments; neither block nor hash specified") +} + +func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + return b.eth.blockchain.GetReceiptsByHash(hash), nil +} + +func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { + return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil +} + +func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { + if header := b.eth.blockchain.GetHeaderByHash(hash); header != nil { + return b.eth.blockchain.GetTd(hash, header.Number.Uint64()) + } + 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 { + if vmConfig == nil { + vmConfig = b.eth.blockchain.GetVMConfig() + } + txContext := core.NewEVMTxContext(msg) + var context vm.BlockContext + if blockCtx != nil { + context = *blockCtx + } else { + context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) + } + return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig) +} + +func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { + return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch) +} + +func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + return b.eth.BlockChain().SubscribeChainEvent(ch) +} + +func (b *EthAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return b.eth.BlockChain().SubscribeChainHeadEvent(ch) +} + +func (b *EthAPIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { + return b.eth.BlockChain().SubscribeChainSideEvent(ch) +} + +func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return b.eth.BlockChain().SubscribeLogsEvent(ch) +} + +func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0] +} + +func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { + pending := b.eth.txPool.Pending(txpool.PendingFilter{}) + var txs types.Transactions + for _, batch := range pending { + for _, lazy := range batch { + if tx := lazy.Resolve(); tx != nil { + txs = append(txs, tx) + } + } + } + return txs, nil +} + +func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction { + return b.eth.txPool.Get(hash) +} + +// GetTransaction retrieves the lookup along with the transaction itself associate +// with the given transaction hash. +// +// An error will be returned if the transaction is not found, and background +// indexing for transactions is still in progress. The error is used to indicate the +// scenario explicitly that the transaction might be reachable shortly. +// +// A null will be returned in the transaction is not found and background transaction +// indexing is already finished. The transaction is not existent from the perspective +// of node. +func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { + lookup, tx, err := b.eth.blockchain.GetTransactionLookup(txHash) + if err != nil { + return false, nil, common.Hash{}, 0, 0, err + } + if lookup == nil || tx == nil { + return false, nil, common.Hash{}, 0, 0, nil + } + return true, tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index, nil +} + +func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { + return b.eth.txPool.Nonce(addr), nil +} + +func (b *EthAPIBackend) Stats() (runnable int, blocked int) { + return b.eth.txPool.Stats() +} + +func (b *EthAPIBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + return b.eth.txPool.Content() +} + +func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + return b.eth.txPool.ContentFrom(addr) +} + +func (b *EthAPIBackend) TxPool() *txpool.TxPool { + return b.eth.txPool +} + +func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return b.eth.txPool.SubscribeTransactions(ch, true) +} + +func (b *EthAPIBackend) SyncProgress() ethereum.SyncProgress { + prog := b.eth.Downloader().Progress() + if txProg, err := b.eth.blockchain.TxIndexProgress(); err == nil { + prog.TxIndexFinishedBlocks = txProg.Indexed + prog.TxIndexRemainingBlocks = txProg.Remaining + } + return prog +} + +func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + return b.gpo.SuggestTipCap(ctx) +} + +func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, baseFeePerBlobGas []*big.Int, blobGasUsedRatio []float64, err error) { + return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) +} + +func (b *EthAPIBackend) BlobBaseFee(ctx context.Context) *big.Int { + if excess := b.CurrentHeader().ExcessBlobGas; excess != nil { + return eip4844.CalcBlobFee(*excess) + } + return nil +} + +func (b *EthAPIBackend) ChainDb() ethdb.Database { + return b.eth.ChainDb() +} + +func (b *EthAPIBackend) EventMux() *event.TypeMux { + return b.eth.EventMux() +} + +func (b *EthAPIBackend) AccountManager() *accounts.Manager { + return b.eth.AccountManager() +} + +func (b *EthAPIBackend) ExtRPCEnabled() bool { + return b.extRPCEnabled +} + +func (b *EthAPIBackend) UnprotectedAllowed() bool { + return b.allowUnprotectedTxs +} + +func (b *EthAPIBackend) RPCGasCap() uint64 { + return b.eth.config.RPCGasCap +} + +func (b *EthAPIBackend) RPCEVMTimeout() time.Duration { + return b.eth.config.RPCEVMTimeout +} + +func (b *EthAPIBackend) RPCTxFeeCap() float64 { + return b.eth.config.RPCTxFeeCap +} + +func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { + sections, _, _ := b.eth.bloomIndexer.Sections() + return params.BloomBitsBlocks, sections +} + +func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { + for i := 0; i < bloomFilterThreads; i++ { + go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) + } +} + +func (b *EthAPIBackend) Engine() consensus.Engine { + return b.eth.engine +} + +func (b *EthAPIBackend) CurrentHeader() *types.Header { + return b.eth.blockchain.CurrentHeader() +} + +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) +} + +func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { + return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) +} diff --git a/eth/api_debug.go b/eth/api_debug.go new file mode 100644 index 0000000..d5e4dda --- /dev/null +++ b/eth/api_debug.go @@ -0,0 +1,445 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" +) + +// DebugAPI is the collection of Ethereum full node APIs for debugging the +// protocol. +type DebugAPI struct { + eth *Ethereum +} + +// NewDebugAPI creates a new DebugAPI instance. +func NewDebugAPI(eth *Ethereum) *DebugAPI { + return &DebugAPI{eth: eth} +} + +// DumpBlock retrieves the entire state of the database at a given block. +func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { + opts := &state.DumpConfig{ + OnlyWithAddresses: true, + Max: AccountRangeMaxResults, // Sanity limit over RPC + } + if blockNr == rpc.PendingBlockNumber { + // If we're dumping the pending state, we need to request + // both the pending block as well as the pending state from + // the miner and operate on those + _, _, stateDb := api.eth.miner.Pending() + if stateDb == nil { + return state.Dump{}, errors.New("pending state is not available") + } + return stateDb.RawDump(opts), nil + } + var header *types.Header + switch blockNr { + case rpc.LatestBlockNumber: + header = api.eth.blockchain.CurrentBlock() + case rpc.FinalizedBlockNumber: + header = api.eth.blockchain.CurrentFinalBlock() + case rpc.SafeBlockNumber: + header = api.eth.blockchain.CurrentSafeBlock() + default: + block := api.eth.blockchain.GetBlockByNumber(uint64(blockNr)) + if block == nil { + return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) + } + header = block.Header() + } + if header == nil { + return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) + } + stateDb, err := api.eth.BlockChain().StateAt(header.Root) + if err != nil { + return state.Dump{}, err + } + return stateDb.RawDump(opts), nil +} + +// Preimage is a debug API function that returns the preimage for a sha3 hash, if known. +func (api *DebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { + if preimage := rawdb.ReadPreimage(api.eth.ChainDb(), hash); preimage != nil { + return preimage, nil + } + return nil, errors.New("unknown preimage") +} + +// BadBlockArgs represents the entries in the list returned when bad blocks are queried. +type BadBlockArgs struct { + Hash common.Hash `json:"hash"` + Block map[string]interface{} `json:"block"` + RLP string `json:"rlp"` +} + +// GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network +// and returns them as a JSON list of block hashes. +func (api *DebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) { + var ( + blocks = rawdb.ReadAllBadBlocks(api.eth.chainDb) + results = make([]*BadBlockArgs, 0, len(blocks)) + ) + for _, block := range blocks { + var ( + blockRlp string + blockJSON map[string]interface{} + ) + if rlpBytes, err := rlp.EncodeToBytes(block); err != nil { + blockRlp = err.Error() // Hacky, but hey, it works + } else { + blockRlp = fmt.Sprintf("%#x", rlpBytes) + } + blockJSON = ethapi.RPCMarshalBlock(block, true, true, api.eth.APIBackend.ChainConfig()) + results = append(results, &BadBlockArgs{ + Hash: block.Hash(), + RLP: blockRlp, + Block: blockJSON, + }) + } + return results, nil +} + +// AccountRangeMaxResults is the maximum number of results to be returned per call +const AccountRangeMaxResults = 256 + +// AccountRange enumerates all accounts in the given block and start point in paging request +func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.Dump, error) { + var stateDb *state.StateDB + var err error + + if number, ok := blockNrOrHash.Number(); ok { + if number == rpc.PendingBlockNumber { + // If we're dumping the pending state, we need to request + // both the pending block as well as the pending state from + // the miner and operate on those + _, _, stateDb = api.eth.miner.Pending() + if stateDb == nil { + return state.Dump{}, errors.New("pending state is not available") + } + } else { + var header *types.Header + switch number { + case rpc.LatestBlockNumber: + header = api.eth.blockchain.CurrentBlock() + case rpc.FinalizedBlockNumber: + header = api.eth.blockchain.CurrentFinalBlock() + case rpc.SafeBlockNumber: + header = api.eth.blockchain.CurrentSafeBlock() + default: + block := api.eth.blockchain.GetBlockByNumber(uint64(number)) + if block == nil { + return state.Dump{}, fmt.Errorf("block #%d not found", number) + } + header = block.Header() + } + if header == nil { + return state.Dump{}, fmt.Errorf("block #%d not found", number) + } + stateDb, err = api.eth.BlockChain().StateAt(header.Root) + if err != nil { + return state.Dump{}, err + } + } + } else if hash, ok := blockNrOrHash.Hash(); ok { + block := api.eth.blockchain.GetBlockByHash(hash) + if block == nil { + return state.Dump{}, fmt.Errorf("block %s not found", hash.Hex()) + } + stateDb, err = api.eth.BlockChain().StateAt(block.Root()) + if err != nil { + return state.Dump{}, err + } + } else { + return state.Dump{}, errors.New("either block number or block hash must be specified") + } + + opts := &state.DumpConfig{ + SkipCode: nocode, + SkipStorage: nostorage, + OnlyWithAddresses: !incompletes, + Start: start, + Max: uint64(maxResults), + } + if maxResults > AccountRangeMaxResults || maxResults <= 0 { + opts.Max = AccountRangeMaxResults + } + return stateDb.RawDump(opts), nil +} + +// StorageRangeResult is the result of a debug_storageRangeAt API call. +type StorageRangeResult struct { + Storage storageMap `json:"storage"` + NextKey *common.Hash `json:"nextKey"` // nil if Storage includes the last key in the trie. +} + +type storageMap map[common.Hash]storageEntry + +type storageEntry struct { + Key *common.Hash `json:"key"` + Value common.Hash `json:"value"` +} + +// StorageRangeAt returns the storage at the given block height and transaction index. +func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { + var block *types.Block + + block, err := api.eth.APIBackend.BlockByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return StorageRangeResult{}, err + } + if block == nil { + return StorageRangeResult{}, fmt.Errorf("block %v not found", blockNrOrHash) + } + _, _, statedb, release, err := api.eth.stateAtTransaction(ctx, block, txIndex, 0) + if err != nil { + return StorageRangeResult{}, err + } + defer release() + + return storageRangeAt(statedb, block.Root(), contractAddress, keyStart, maxResult) +} + +func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Address, start []byte, maxResult int) (StorageRangeResult, error) { + storageRoot := statedb.GetStorageRoot(address) + if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) { + return StorageRangeResult{}, nil // empty storage + } + id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot) + tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB()) + if err != nil { + return StorageRangeResult{}, err + } + trieIt, err := tr.NodeIterator(start) + if err != nil { + return StorageRangeResult{}, err + } + it := trie.NewIterator(trieIt) + result := StorageRangeResult{Storage: storageMap{}} + for i := 0; i < maxResult && it.Next(); i++ { + _, content, _, err := rlp.Split(it.Value) + if err != nil { + return StorageRangeResult{}, err + } + e := storageEntry{Value: common.BytesToHash(content)} + if preimage := tr.GetKey(it.Key); preimage != nil { + preimage := common.BytesToHash(preimage) + e.Key = &preimage + } + result.Storage[common.BytesToHash(it.Key)] = e + } + // Add the 'next key' so clients can continue downloading. + if it.Next() { + next := common.BytesToHash(it.Key) + result.NextKey = &next + } + return result, nil +} + +// GetModifiedAccountsByNumber returns all accounts that have changed between the +// two blocks specified. A change is defined as a difference in nonce, balance, +// code hash, or storage hash. +// +// With one parameter, returns the list of accounts modified in the specified block. +func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64) ([]common.Address, error) { + var startBlock, endBlock *types.Block + + startBlock = api.eth.blockchain.GetBlockByNumber(startNum) + if startBlock == nil { + return nil, fmt.Errorf("start block %x not found", startNum) + } + + if endNum == nil { + endBlock = startBlock + startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) + if startBlock == nil { + return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) + } + } else { + endBlock = api.eth.blockchain.GetBlockByNumber(*endNum) + if endBlock == nil { + return nil, fmt.Errorf("end block %d not found", *endNum) + } + } + return api.getModifiedAccounts(startBlock, endBlock) +} + +// GetModifiedAccountsByHash returns all accounts that have changed between the +// two blocks specified. A change is defined as a difference in nonce, balance, +// code hash, or storage hash. +// +// With one parameter, returns the list of accounts modified in the specified block. +func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) { + var startBlock, endBlock *types.Block + startBlock = api.eth.blockchain.GetBlockByHash(startHash) + if startBlock == nil { + return nil, fmt.Errorf("start block %x not found", startHash) + } + + if endHash == nil { + endBlock = startBlock + startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) + if startBlock == nil { + return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) + } + } else { + endBlock = api.eth.blockchain.GetBlockByHash(*endHash) + if endBlock == nil { + return nil, fmt.Errorf("end block %x not found", *endHash) + } + } + return api.getModifiedAccounts(startBlock, endBlock) +} + +func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]common.Address, error) { + if startBlock.Number().Uint64() >= endBlock.Number().Uint64() { + return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) + } + triedb := api.eth.BlockChain().TrieDB() + + oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startBlock.Root()), triedb) + if err != nil { + return nil, err + } + newTrie, err := trie.NewStateTrie(trie.StateTrieID(endBlock.Root()), triedb) + if err != nil { + return nil, err + } + oldIt, err := oldTrie.NodeIterator([]byte{}) + if err != nil { + return nil, err + } + newIt, err := newTrie.NodeIterator([]byte{}) + if err != nil { + return nil, err + } + diff, _ := trie.NewDifferenceIterator(oldIt, newIt) + iter := trie.NewIterator(diff) + + var dirty []common.Address + for iter.Next() { + key := newTrie.GetKey(iter.Key) + if key == nil { + return nil, fmt.Errorf("no preimage found for hash %x", iter.Key) + } + dirty = append(dirty, common.BytesToAddress(key)) + } + return dirty, nil +} + +// GetAccessibleState returns the first number where the node has accessible +// state on disk. Note this being the post-state of that block and the pre-state +// of the next block. +// The (from, to) parameters are the sequence of blocks to search, which can go +// either forwards or backwards +func (api *DebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64, error) { + if api.eth.blockchain.TrieDB().Scheme() == rawdb.PathScheme { + return 0, errors.New("state history is not yet available in path-based scheme") + } + db := api.eth.ChainDb() + var pivot uint64 + if p := rawdb.ReadLastPivotNumber(db); p != nil { + pivot = *p + log.Info("Found fast-sync pivot marker", "number", pivot) + } + var resolveNum = func(num rpc.BlockNumber) (uint64, error) { + // We don't have state for pending (-2), so treat it as latest + if num.Int64() < 0 { + block := api.eth.blockchain.CurrentBlock() + if block == nil { + return 0, errors.New("current block missing") + } + return block.Number.Uint64(), nil + } + return uint64(num.Int64()), nil + } + var ( + start uint64 + end uint64 + delta = int64(1) + lastLog time.Time + err error + ) + if start, err = resolveNum(from); err != nil { + return 0, err + } + if end, err = resolveNum(to); err != nil { + return 0, err + } + if start == end { + return 0, errors.New("from and to needs to be different") + } + if start > end { + delta = -1 + } + for i := int64(start); i != int64(end); i += delta { + if time.Since(lastLog) > 8*time.Second { + log.Info("Finding roots", "from", start, "to", end, "at", i) + lastLog = time.Now() + } + if i < int64(pivot) { + continue + } + h := api.eth.BlockChain().GetHeaderByNumber(uint64(i)) + if h == nil { + return 0, fmt.Errorf("missing header %d", i) + } + if ok, _ := api.eth.ChainDb().Has(h.Root[:]); ok { + return uint64(i), nil + } + } + return 0, errors.New("no state found") +} + +// SetTrieFlushInterval configures how often in-memory tries are persisted +// to disk. The value is in terms of block processing time, not wall clock. +// If the value is shorter than the block generation time, or even 0 or negative, +// the node will flush trie after processing each block (effectively archive mode). +func (api *DebugAPI) SetTrieFlushInterval(interval string) error { + if api.eth.blockchain.TrieDB().Scheme() == rawdb.PathScheme { + return errors.New("trie flush interval is undefined for path-based scheme") + } + t, err := time.ParseDuration(interval) + if err != nil { + return err + } + api.eth.blockchain.SetTrieFlushInterval(t) + return nil +} + +// GetTrieFlushInterval gets the current value of in-memory trie flush interval +func (api *DebugAPI) GetTrieFlushInterval() (string, error) { + if api.eth.blockchain.TrieDB().Scheme() == rawdb.PathScheme { + return "", errors.New("trie flush interval is undefined for path-based scheme") + } + return api.eth.blockchain.GetTrieFlushInterval().String(), nil +} diff --git a/eth/api_debug_test.go b/eth/api_debug_test.go new file mode 100644 index 0000000..750cee5 --- /dev/null +++ b/eth/api_debug_test.go @@ -0,0 +1,223 @@ +// 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 eth + +import ( + "bytes" + "fmt" + "reflect" + "slices" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" + "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/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/triedb" + "github.com/holiman/uint256" +) + +var dumper = spew.ConfigState{Indent: " "} + +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, + Start: start.Bytes(), + Max: uint64(requestedNum), + }) + + if len(result.Accounts) != expectedNum { + t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts)) + } + 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(*acc.Address) { + t.Fatalf("account not found in state %s", acc.Address.Hex()) + } + } + return result +} + +func TestAccountRange(t *testing.T) { + t.Parallel() + + var ( + statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &triedb.Config{Preimages: true}) + sdb, _ = state.New(types.EmptyRootHash, statedb, nil) + addrs = [AccountRangeMaxResults * 2]common.Address{} + m = map[common.Address]bool{} + ) + + for i := range addrs { + hash := common.HexToHash(fmt.Sprintf("%x", i)) + addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes()) + addrs[i] = addr + sdb.SetBalance(addrs[i], uint256.NewInt(1), tracing.BalanceChangeUnspecified) + if _, ok := m[addr]; ok { + t.Fatalf("bad") + } else { + m[addr] = true + } + } + root, _ := sdb.Commit(0, true) + sdb, _ = state.New(root, statedb, nil) + + trie, err := statedb.OpenTrie(root) + if err != nil { + t.Fatal(err) + } + accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) + // test pagination + firstResult := accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults) + secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) + + hList := make([]common.Hash, 0) + 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 acc.Address == nil { + continue + } + if _, duplicate := secondResult.Accounts[addr1]; duplicate { + t.Fatalf("pagination test failed: results should not overlap") + } + 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. + slices.SortFunc(hList, common.Hash.Cmp) + middleH := hList[AccountRangeMaxResults/2] + middleResult := accountRangeTest(t, &trie, sdb, middleH, AccountRangeMaxResults, AccountRangeMaxResults) + missing, infirst, insecond := 0, 0, 0 + for h := range middleResult.Accounts { + if _, ok := firstResult.Accounts[h]; ok { + infirst++ + } else if _, ok := secondResult.Accounts[h]; ok { + insecond++ + } else { + missing++ + } + } + if missing != 0 { + t.Fatalf("%d hashes in the 'middle' set were neither in the first not the second set", missing) + } + if infirst != AccountRangeMaxResults/2 { + t.Fatalf("Imbalance in the number of first-test results: %d != %d", infirst, AccountRangeMaxResults/2) + } + if insecond != AccountRangeMaxResults/2 { + t.Fatalf("Imbalance in the number of second-test results: %d != %d", insecond, AccountRangeMaxResults/2) + } +} + +func TestEmptyAccountRange(t *testing.T) { + t.Parallel() + + var ( + statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) + st, _ = state.New(types.EmptyRootHash, statedb, nil) + ) + // Commit(although nothing to flush) and re-init the statedb + st.Commit(0, true) + st, _ = state.New(types.EmptyRootHash, statedb, nil) + + results := st.RawDump(&state.DumpConfig{ + SkipCode: true, + SkipStorage: true, + OnlyWithAddresses: true, + Max: uint64(AccountRangeMaxResults), + }) + if bytes.Equal(results.Next, (common.Hash{}).Bytes()) { + t.Fatalf("Empty results should not return a second page") + } + if len(results.Accounts) != 0 { + t.Fatalf("Empty state should not return addresses: %v", results.Accounts) + } +} + +func TestStorageRangeAt(t *testing.T) { + t.Parallel() + + // Create a state where account 0x010000... has a few storage entries. + var ( + db = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &triedb.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"), + common.HexToHash("5723d2c3a83af9b735e3b7f21531e5623d183a9095a56604ead41f3582fdfb75"), + } + storage = storageMap{ + keys[0]: {Key: &common.Hash{0x02}, Value: common.Hash{0x01}}, + keys[1]: {Key: &common.Hash{0x04}, Value: common.Hash{0x02}}, + keys[2]: {Key: &common.Hash{0x01}, Value: common.Hash{0x03}}, + keys[3]: {Key: &common.Hash{0x03}, Value: common.Hash{0x04}}, + } + ) + for _, entry := range storage { + 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 { + start []byte + limit int + want StorageRangeResult + }{ + { + start: []byte{}, limit: 0, + want: StorageRangeResult{storageMap{}, &keys[0]}, + }, + { + start: []byte{}, limit: 100, + want: StorageRangeResult{storage, nil}, + }, + { + start: []byte{}, limit: 2, + want: StorageRangeResult{storageMap{keys[0]: storage[keys[0]], keys[1]: storage[keys[1]]}, &keys[2]}, + }, + { + start: []byte{0x00}, limit: 4, + want: StorageRangeResult{storage, nil}, + }, + { + start: []byte{0x40}, limit: 2, + want: StorageRangeResult{storageMap{keys[1]: storage[keys[1]], keys[2]: storage[keys[2]]}, &keys[3]}, + }, + } + for _, test := range tests { + result, err := storageRangeAt(sdb, root, addr, test.start, test.limit) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(result, test.want) { + t.Fatalf("wrong result for range %#x.., limit %d:\ngot %s\nwant %s", + test.start, test.limit, dumper.Sdump(result), dumper.Sdump(&test.want)) + } + } +} diff --git a/eth/api_miner.go b/eth/api_miner.go new file mode 100644 index 0000000..8c96f4c --- /dev/null +++ b/eth/api_miner.go @@ -0,0 +1,58 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// MinerAPI provides an API to control the miner. +type MinerAPI struct { + e *Ethereum +} + +// NewMinerAPI creates a new MinerAPI instance. +func NewMinerAPI(e *Ethereum) *MinerAPI { + return &MinerAPI{e} +} + +// SetExtra sets the extra data string that is included when this miner mines a block. +func (api *MinerAPI) SetExtra(extra string) (bool, error) { + if err := api.e.Miner().SetExtra([]byte(extra)); err != nil { + return false, err + } + return true, nil +} + +// SetGasPrice sets the minimum accepted gas price for the miner. +func (api *MinerAPI) SetGasPrice(gasPrice hexutil.Big) bool { + api.e.lock.Lock() + api.e.gasPrice = (*big.Int)(&gasPrice) + api.e.lock.Unlock() + + api.e.txPool.SetGasTip((*big.Int)(&gasPrice)) + api.e.Miner().SetGasTip((*big.Int)(&gasPrice)) + return true +} + +// SetGasLimit sets the gaslimit to target towards during mining. +func (api *MinerAPI) SetGasLimit(gasLimit hexutil.Uint64) bool { + api.e.Miner().SetGasCeil(uint64(gasLimit)) + return true +} diff --git a/eth/backend.go b/eth/backend.go new file mode 100644 index 0000000..91a0781 --- /dev/null +++ b/eth/backend.go @@ -0,0 +1,445 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package eth implements the Ethereum protocol. +package eth + +import ( + "encoding/json" + "fmt" + "math/big" + "runtime" + "sync" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "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/pruner" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/blobpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/ethdb" + "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/log" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/dnsdisc" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" +) + +// Config contains the configuration options of the ETH protocol. +// Deprecated: use ethconfig.Config instead. +type Config = ethconfig.Config + +// Ethereum implements the Ethereum full node service. +type Ethereum struct { + config *ethconfig.Config + + // Handlers + txPool *txpool.TxPool + + blockchain *core.BlockChain + handler *handler + ethDialCandidates enode.Iterator + snapDialCandidates enode.Iterator + + // DB interfaces + chainDb ethdb.Database // Block chain database + + eventMux *event.TypeMux + engine consensus.Engine + accountManager *accounts.Manager + + bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests + bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports + closeBloomHandler chan struct{} + + APIBackend *EthAPIBackend + + miner *miner.Miner + gasPrice *big.Int + + networkID uint64 + netRPCService *ethapi.NetAPI + + p2pServer *p2p.Server + + lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase) + + shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully +} + +// New creates a new Ethereum object (including the initialisation of the common Ethereum object), +// whose lifecycle will be managed by the provided node. +func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { + // Ensure configuration values are compatible and sane + if !config.SyncMode.IsValid() { + return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode) + } + if config.Miner.GasPrice == nil || config.Miner.GasPrice.Sign() <= 0 { + log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice) + config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice) + } + if config.NoPruning && config.TrieDirtyCache > 0 { + if config.SnapshotCache > 0 { + config.TrieCleanCache += config.TrieDirtyCache * 3 / 5 + config.SnapshotCache += config.TrieDirtyCache * 2 / 5 + } else { + config.TrieCleanCache += config.TrieDirtyCache + } + config.TrieDirtyCache = 0 + } + log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) + + // Assemble the Ethereum object + chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) + if err != nil { + return nil, 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) + if err != nil { + return nil, err + } + engine, err := ethconfig.CreateConsensusEngine(chainConfig, chainDb) + if err != nil { + return nil, err + } + networkID := config.NetworkId + if networkID == 0 { + networkID = chainConfig.ChainID.Uint64() + } + eth := &Ethereum{ + config: config, + chainDb: chainDb, + eventMux: stack.EventMux(), + accountManager: stack.AccountManager(), + engine: engine, + closeBloomHandler: make(chan struct{}), + networkID: networkID, + gasPrice: config.Miner.GasPrice, + bloomRequests: make(chan chan *bloombits.Retrieval), + bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), + p2pServer: stack.Server(), + shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb), + } + bcVersion := rawdb.ReadDatabaseVersion(chainDb) + var dbVer = "" + if bcVersion != nil { + dbVer = fmt.Sprintf("%d", *bcVersion) + } + log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer) + + if !config.SkipBcVersionCheck { + if bcVersion != nil && *bcVersion > core.BlockChainVersion { + return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion) + } else if bcVersion == nil || *bcVersion < core.BlockChainVersion { + if bcVersion != nil { // only print warning on upgrade, not on init + log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) + } + rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) + } + } + var ( + vmConfig = vm.Config{ + EnablePreimageRecording: config.EnablePreimageRecording, + EnableWitnessCollection: config.EnableWitnessCollection, + } + cacheConfig = &core.CacheConfig{ + TrieCleanLimit: config.TrieCleanCache, + TrieCleanNoPrefetch: config.NoPrefetch, + TrieDirtyLimit: config.TrieDirtyCache, + TrieDirtyDisabled: config.NoPruning, + TrieTimeLimit: config.TrieTimeout, + SnapshotLimit: config.SnapshotCache, + Preimages: config.Preimages, + StateHistory: config.StateHistory, + StateScheme: scheme, + } + ) + if config.VMTrace != "" { + var traceConfig json.RawMessage + if config.VMTraceJsonConfig != "" { + traceConfig = json.RawMessage(config.VMTraceJsonConfig) + } + t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig) + if err != nil { + return nil, fmt.Errorf("failed to create tracer %s: %v", config.VMTrace, err) + } + vmConfig.Tracer = t + } + // Override the chain config with provided settings. + var overrides core.ChainOverrides + if config.OverrideCancun != nil { + overrides.OverrideCancun = config.OverrideCancun + } + if config.OverrideVerkle != nil { + overrides.OverrideVerkle = config.OverrideVerkle + } + // TODO (MariusVanDerWijden) get rid of shouldPreserve in a follow-up PR + shouldPreserve := func(header *types.Header) bool { + return false + } + eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, shouldPreserve, &config.TransactionHistory) + if err != nil { + return nil, err + } + eth.bloomIndexer.Start(eth.blockchain) + + if config.BlobPool.Datadir != "" { + config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir) + } + blobPool := blobpool.New(config.BlobPool, eth.blockchain) + + if config.TxPool.Journal != "" { + config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) + } + legacyPool := legacypool.New(config.TxPool, eth.blockchain) + + eth.txPool, err = txpool.New(config.TxPool.PriceLimit, eth.blockchain, []txpool.SubPool{legacyPool, blobPool}) + if err != nil { + return nil, err + } + // Permit the downloader to use the trie cache allowance during fast sync + cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit + if eth.handler, err = newHandler(&handlerConfig{ + NodeID: eth.p2pServer.Self().ID(), + Database: chainDb, + Chain: eth.blockchain, + TxPool: eth.txPool, + Network: networkID, + Sync: config.SyncMode, + BloomCache: uint64(cacheLimit), + EventMux: eth.eventMux, + RequiredBlocks: config.RequiredBlocks, + }); err != nil { + return nil, err + } + + eth.miner = miner.New(eth, config.Miner, eth.engine) + eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) + + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} + if eth.APIBackend.allowUnprotectedTxs { + log.Info("Unprotected transactions allowed") + } + gpoParams := config.GPO + if gpoParams.Default == nil { + gpoParams.Default = config.Miner.GasPrice + } + eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) + + // Setup DNS discovery iterators. + dnsclient := dnsdisc.NewClient(dnsdisc.Config{}) + eth.ethDialCandidates, err = dnsclient.NewIterator(eth.config.EthDiscoveryURLs...) + if err != nil { + return nil, err + } + eth.snapDialCandidates, err = dnsclient.NewIterator(eth.config.SnapDiscoveryURLs...) + if err != nil { + return nil, err + } + + // Start the RPC service + eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, networkID) + + // Register the backend on the node + stack.RegisterAPIs(eth.APIs()) + stack.RegisterProtocols(eth.Protocols()) + stack.RegisterLifecycle(eth) + + // Successful startup; push a marker and check previous unclean shutdowns. + eth.shutdownTracker.MarkStartup() + + return eth, nil +} + +func makeExtraData(extra []byte) []byte { + if len(extra) == 0 { + // create default extradata + extra, _ = rlp.EncodeToBytes([]interface{}{ + uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch), + "geth", + runtime.Version(), + runtime.GOOS, + }) + } + if uint64(len(extra)) > params.MaximumExtraDataSize { + log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize) + extra = nil + } + return extra +} + +// APIs return the collection of RPC services the ethereum package offers. +// NOTE, some of these services probably need to be moved to somewhere else. +func (s *Ethereum) APIs() []rpc.API { + apis := ethapi.GetAPIs(s.APIBackend) + + // Append any APIs exposed explicitly by the consensus engine + apis = append(apis, s.engine.APIs(s.BlockChain())...) + + // Append all the local APIs and return + return append(apis, []rpc.API{ + { + Namespace: "miner", + Service: NewMinerAPI(s), + }, { + Namespace: "eth", + Service: downloader.NewDownloaderAPI(s.handler.downloader, s.blockchain, s.eventMux), + }, { + Namespace: "admin", + Service: NewAdminAPI(s), + }, { + Namespace: "debug", + Service: NewDebugAPI(s), + }, { + Namespace: "net", + Service: s.netRPCService, + }, + }...) +} + +func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { + s.blockchain.ResetWithGenesisBlock(gb) +} + +func (s *Ethereum) Miner() *miner.Miner { return s.miner } + +func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } +func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } +func (s *Ethereum) TxPool() *txpool.TxPool { return s.txPool } +func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } +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.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 } + +// Protocols returns all the currently configured +// network protocols to start. +func (s *Ethereum) Protocols() []p2p.Protocol { + protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates) + if s.config.SnapshotCache > 0 { + protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...) + } + return protos +} + +// Start implements node.Lifecycle, starting all internal goroutines needed by the +// Ethereum protocol implementation. +func (s *Ethereum) Start() error { + eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode()) + + // Start the bloom bits servicing goroutines + s.startBloomHandlers(params.BloomBitsBlocks) + + // Regularly update shutdown marker + s.shutdownTracker.Start() + + // Figure out a max peers count based on the server limits + maxPeers := s.p2pServer.MaxPeers + if s.config.LightServ > 0 { + if s.config.LightPeers >= s.p2pServer.MaxPeers { + return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers) + } + maxPeers -= s.config.LightPeers + } + // Start the networking layer and the light server if requested + s.handler.Start(maxPeers) + return nil +} + +// Stop implements node.Lifecycle, terminating all internal goroutines used by the +// Ethereum protocol. +func (s *Ethereum) Stop() error { + // Stop all the peer-related stuff first. + s.ethDialCandidates.Close() + s.snapDialCandidates.Close() + s.handler.Stop() + + // Then stop everything else. + s.bloomIndexer.Close() + close(s.closeBloomHandler) + s.txPool.Close() + s.blockchain.Stop() + s.engine.Close() + + // Clean shutdown marker as the last thing before closing db + s.shutdownTracker.Stop() + + s.chainDb.Close() + s.eventMux.Stop() + + return nil +} + +// SyncMode retrieves the current sync mode, either explicitly set, or derived +// from the chain status. +func (s *Ethereum) SyncMode() downloader.SyncMode { + // If we're in snap sync mode, return that directly + if s.handler.snapSync.Load() { + return downloader.SnapSync + } + // We are probably in full sync, but we might have rewound to before the + // snap sync pivot, check if we should re-enable snap sync. + head := s.blockchain.CurrentBlock() + if pivot := rawdb.ReadLastPivotNumber(s.chainDb); pivot != nil { + if head.Number.Uint64() < *pivot { + return downloader.SnapSync + } + } + // 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 !s.blockchain.HasState(head.Root) { + log.Info("Reenabled snap sync as chain is stateless") + return downloader.SnapSync + } + // Nope, we're really full syncing + return downloader.FullSync +} diff --git a/eth/bloombits.go b/eth/bloombits.go new file mode 100644 index 0000000..0cb7050 --- /dev/null +++ b/eth/bloombits.go @@ -0,0 +1,74 @@ +// 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 eth + +import ( + "time" + + "github.com/ethereum/go-ethereum/common/bitutil" + "github.com/ethereum/go-ethereum/core/rawdb" +) + +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.Duration(0) +) + +// 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 *Ethereum) startBloomHandlers(sectionSize uint64) { + for i := 0; i < bloomServiceThreads; i++ { + go func() { + for { + select { + case <-eth.closeBloomHandler: + return + + case request := <-eth.bloomRequests: + task := <-request + task.Bitsets = make([][]byte, len(task.Sections)) + for i, section := range task.Sections { + head := rawdb.ReadCanonicalHash(eth.chainDb, (section+1)*sectionSize-1) + if compVector, err := rawdb.ReadBloomBits(eth.chainDb, task.Bit, section, head); err == nil { + if blob, err := bitutil.DecompressBytes(compVector, int(sectionSize/8)); err == nil { + task.Bitsets[i] = blob + } else { + task.Error = err + } + } else { + task.Error = err + } + } + request <- task + } + } + }() + } +} diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go new file mode 100644 index 0000000..00cce25 --- /dev/null +++ b/eth/catalyst/api.go @@ -0,0 +1,898 @@ +// 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 catalyst implements the temporary eth1/eth2 RPC integration. +package catalyst + +import ( + "errors" + "fmt" + "strconv" + "sync" + "time" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/internal/version" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" + "github.com/ethereum/go-ethereum/rpc" +) + +// Register adds the engine API to the full node. +func Register(stack *node.Node, backend *eth.Ethereum) error { + log.Warn("Engine API enabled", "protocol", "eth") + stack.RegisterAPIs([]rpc.API{ + { + Namespace: "engine", + Service: NewConsensusAPI(backend), + Authenticated: true, + }, + }) + return nil +} + +const ( + // invalidBlockHitEviction is the number of times an invalid block can be + // referenced in forkchoice update or new payload before it is attempted + // to be reprocessed again. + invalidBlockHitEviction = 128 + + // invalidTipsetsCap is the max number of recent block hashes tracked that + // have lead to some bad ancestor block. It's just an OOM protection. + invalidTipsetsCap = 512 + + // beaconUpdateStartupTimeout is the time to wait for a beacon client to get + // attached before starting to issue warnings. + beaconUpdateStartupTimeout = 30 * time.Second + + // beaconUpdateConsensusTimeout is the max time allowed for a beacon client + // to send a consensus update before it's considered offline and the user is + // warned. + beaconUpdateConsensusTimeout = 2 * time.Minute + + // beaconUpdateWarnFrequency is the frequency at which to warn the user that + // the beacon client is offline. + beaconUpdateWarnFrequency = 5 * time.Minute +) + +// All methods provided over the engine endpoint. +var caps = []string{ + "engine_forkchoiceUpdatedV1", + "engine_forkchoiceUpdatedV2", + "engine_forkchoiceUpdatedV3", + "engine_exchangeTransitionConfigurationV1", + "engine_getPayloadV1", + "engine_getPayloadV2", + "engine_getPayloadV3", + "engine_newPayloadV1", + "engine_newPayloadV2", + "engine_newPayloadV3", + "engine_getPayloadBodiesByHashV1", + "engine_getPayloadBodiesByRangeV1", + "engine_getClientVersionV1", +} + +type ConsensusAPI struct { + eth *eth.Ethereum + + remoteBlocks *headerQueue // Cache of remote payloads received + localBlocks *payloadQueue // Cache of local payloads generated + + // The forkchoice update and new payload method require us to return the + // latest valid hash in an invalid chain. To support that return, we need + // to track historical bad blocks as well as bad tipsets in case a chain + // is constantly built on it. + // + // There are a few important caveats in this mechanism: + // - The bad block tracking is ephemeral, in-memory only. We must never + // persist any bad block information to disk as a bug in Geth could end + // up blocking a valid chain, even if a later Geth update would accept + // it. + // - Bad blocks will get forgotten after a certain threshold of import + // attempts and will be retried. The rationale is that if the network + // really-really-really tries to feed us a block, we should give it a + // new chance, perhaps us being racey instead of the block being legit + // bad (this happened in Geth at a point with import vs. pending race). + // - Tracking all the blocks built on top of the bad one could be a bit + // problematic, so we will only track the head chain segment of a bad + // chain to allow discarding progressing bad chains and side chains, + // without tracking too much bad data. + invalidBlocksHits map[common.Hash]int // Ephemeral cache to track invalid blocks and their hit count + invalidTipsets map[common.Hash]*types.Header // Ephemeral cache to track invalid tipsets and their bad ancestor + invalidLock sync.Mutex // Protects the invalid maps from concurrent access + + // Geth can appear to be stuck or do strange things if the beacon client is + // offline or is sending us strange data. Stash some update stats away so + // that we can warn the user and not have them open issues on our tracker. + lastTransitionUpdate time.Time + lastTransitionLock sync.Mutex + lastForkchoiceUpdate time.Time + lastForkchoiceLock sync.Mutex + lastNewPayloadUpdate time.Time + lastNewPayloadLock sync.Mutex + + forkchoiceLock sync.Mutex // Lock for the forkChoiceUpdated method + newPayloadLock sync.Mutex // Lock for the NewPayload method +} + +// 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") + } + api := &ConsensusAPI{ + eth: eth, + remoteBlocks: newHeaderQueue(), + localBlocks: newPayloadQueue(), + invalidBlocksHits: make(map[common.Hash]int), + invalidTipsets: make(map[common.Hash]*types.Header), + } + eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor) + return api +} + +// ForkchoiceUpdatedV1 has several responsibilities: +// +// We try to set our blockchain to the headBlock. +// +// If the method is called with an empty head block: we return success, which can be used +// to check if the engine API is enabled. +// +// If the total difficulty was not reached: we return INVALID. +// +// If the finalizedBlockHash is set: we check if we have the finalizedBlockHash in our db, +// if not we start a sync. +// +// If there are payloadAttributes: we try to assemble a block with the payloadAttributes +// and return its payloadID. +func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { + if payloadAttributes != nil { + if payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil { + return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals and beacon root not supported in V1")) + } + if api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, payloadAttributes.Timestamp) { + return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai")) + } + } + return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false) +} + +// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload +// attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2. +func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { + if params != nil { + if params.BeaconRoot != nil { + return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("unexpected beacon root")) + } + switch api.eth.BlockChain().Config().LatestFork(params.Timestamp) { + case forks.Paris: + if params.Withdrawals != nil { + return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("withdrawals before shanghai")) + } + case forks.Shanghai: + if params.Withdrawals == nil { + return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals")) + } + default: + return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads")) + } + } + return api.forkchoiceUpdated(update, params, engine.PayloadV2, false) +} + +// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root +// in the payload attributes. It supports only PayloadAttributesV3. +func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { + if params != nil { + if params.Withdrawals == nil { + return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals")) + } + if params.BeaconRoot == nil { + return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root")) + } + if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun { + return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads")) + } + } + // TODO(matt): the spec requires that fcu is applied when called on a valid + // hash, even if params are wrong. To do this we need to split up + // forkchoiceUpdate into a function that only updates the head and then a + // function that kicks off block construction. + return api.forkchoiceUpdated(update, params, engine.PayloadV3, false) +} + +func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, simulatorMode bool) (engine.ForkChoiceResponse, error) { + api.forkchoiceLock.Lock() + defer api.forkchoiceLock.Unlock() + + log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash) + if update.HeadBlockHash == (common.Hash{}) { + log.Warn("Forkchoice requested update to zero hash") + return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? + } + // Stash away the last update to warn the user if the beacon client goes offline + api.lastForkchoiceLock.Lock() + api.lastForkchoiceUpdate = time.Now() + api.lastForkchoiceLock.Unlock() + + // Check whether we have the block yet in our database or not. If not, we'll + // need to either trigger a sync, or to reject this forkchoice update for a + // reason. + block := api.eth.BlockChain().GetBlockByHash(update.HeadBlockHash) + if block == nil { + // If this block was previously invalidated, keep rejecting it here too + if res := api.checkInvalidAncestor(update.HeadBlockHash, update.HeadBlockHash); res != nil { + return engine.ForkChoiceResponse{PayloadStatus: *res, PayloadID: nil}, nil + } + // If the head hash is unknown (was not given to us in a newPayload request), + // we cannot resolve the header, so not much to do. This could be extended in + // the future to resolve from the `eth` network, but it's an unexpected case + // that should be fixed, not papered over. + header := api.remoteBlocks.get(update.HeadBlockHash) + if header == nil { + log.Warn("Forkchoice requested unknown head", "hash", update.HeadBlockHash) + return engine.STATUS_SYNCING, nil + } + // If the finalized hash is known, we can direct the downloader to move + // potentially more data to the freezer from the get go. + finalized := api.remoteBlocks.get(update.FinalizedBlockHash) + + // Header advertised via a past newPayload request. Start syncing to it. + context := []interface{}{"number", header.Number, "hash", header.Hash()} + if update.FinalizedBlockHash != (common.Hash{}) { + if finalized == nil { + context = append(context, []interface{}{"finalized", "unknown"}...) + } else { + context = append(context, []interface{}{"finalized", finalized.Number}...) + } + } + log.Info("Forkchoice requested sync to new head", context...) + if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header, finalized); err != nil { + return engine.STATUS_SYNCING, err + } + return engine.STATUS_SYNCING, nil + } + // Block is known locally, just sanity check that the beacon client does not + // attempt to push us back to before the merge. + if block.Difficulty().BitLen() > 0 || block.NumberU64() == 0 { + var ( + td = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64()) + ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1) + ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty + ) + if td == nil || (block.NumberU64() > 0 && ptd == nil) { + log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd) + return engine.STATUS_INVALID, errors.New("TDs unavailable for TDD check") + } + if td.Cmp(ttd) < 0 { + log.Error("Refusing beacon update to pre-merge", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) + return engine.ForkChoiceResponse{PayloadStatus: engine.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil + } + if block.NumberU64() > 0 && ptd.Cmp(ttd) >= 0 { + log.Error("Parent block is already post-ttd", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) + return engine.ForkChoiceResponse{PayloadStatus: engine.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil + } + } + valid := func(id *engine.PayloadID) engine.ForkChoiceResponse { + return engine.ForkChoiceResponse{ + PayloadStatus: engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &update.HeadBlockHash}, + PayloadID: id, + } + } + if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash { + // Block is not canonical, set head. + if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil { + return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err + } + } else if api.eth.BlockChain().CurrentBlock().Hash() == update.HeadBlockHash { + // If the specified head matches with our local head, do nothing and keep + // generating the payload. It's a special corner case that a few slots are + // missing and we are requested to generate the payload in slot. + } else { + // If the head block is already in our canonical chain, the beacon client is + // probably resyncing. Ignore the update. + log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number) + return valid(nil), nil + } + api.eth.SetSynced() + + // If the beacon client also advertised a finalized block, mark the local + // chain final and completely in PoS mode. + if update.FinalizedBlockHash != (common.Hash{}) { + // If the finalized block is not in our canonical tree, something is wrong + finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash) + if finalBlock == nil { + log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash) + return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not available in database")) + } else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash { + log.Warn("Final block not in canonical chain", "number", finalBlock.NumberU64(), "hash", update.FinalizedBlockHash) + return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not in canonical chain")) + } + // Set the finalized block + api.eth.BlockChain().SetFinalized(finalBlock.Header()) + } + // Check if the safe block hash is in our canonical tree, if not something is wrong + if update.SafeBlockHash != (common.Hash{}) { + safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash) + if safeBlock == nil { + log.Warn("Safe block not available in database") + return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not available in database")) + } + if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash { + log.Warn("Safe block not in canonical chain") + return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain")) + } + // Set the safe block + api.eth.BlockChain().SetSafe(safeBlock.Header()) + } + // If payload generation was requested, create a new block to be potentially + // sealed by the beacon client. The payload will be requested later, and we + // will replace it arbitrarily many times in between. + if payloadAttributes != nil { + args := &miner.BuildPayloadArgs{ + Parent: update.HeadBlockHash, + Timestamp: payloadAttributes.Timestamp, + FeeRecipient: payloadAttributes.SuggestedFeeRecipient, + Random: payloadAttributes.Random, + Withdrawals: payloadAttributes.Withdrawals, + BeaconRoot: payloadAttributes.BeaconRoot, + Version: payloadVersion, + } + id := args.Id() + // If we already are busy generating this work, then we do not need + // to start a second process. + if api.localBlocks.has(id) { + return valid(&id), nil + } + // If the beacon chain is ran by a simulator, then transaction insertion, + // block insertion and block production will happen without any timing + // delay between them. This will cause flaky simulator executions due to + // the transaction pool running its internal reset operation on a back- + // ground thread. To avoid the racey behavior - in simulator mode - the + // pool will be explicitly blocked on its reset before continuing to the + // block production below. + if simulatorMode { + if err := api.eth.TxPool().Sync(); err != nil { + log.Error("Failed to sync transaction pool", "err", err) + return valid(nil), engine.InvalidPayloadAttributes.With(err) + } + } + payload, err := api.eth.Miner().BuildPayload(args) + if err != nil { + log.Error("Failed to build payload", "err", err) + return valid(nil), engine.InvalidPayloadAttributes.With(err) + } + api.localBlocks.put(id, payload) + return valid(&id), nil + } + return valid(nil), nil +} + +// ExchangeTransitionConfigurationV1 checks the given configuration against +// the configuration of the node. +func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.TransitionConfigurationV1) (*engine.TransitionConfigurationV1, error) { + log.Trace("Engine API request received", "method", "ExchangeTransitionConfiguration", "ttd", config.TerminalTotalDifficulty) + if config.TerminalTotalDifficulty == nil { + return nil, errors.New("invalid terminal total difficulty") + } + // Stash away the last update to warn the user if the beacon client goes offline + api.lastTransitionLock.Lock() + api.lastTransitionUpdate = time.Now() + api.lastTransitionLock.Unlock() + + ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty + if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 { + log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty) + return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty) + } + if config.TerminalBlockHash != (common.Hash{}) { + if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash { + return &engine.TransitionConfigurationV1{ + TerminalTotalDifficulty: (*hexutil.Big)(ttd), + TerminalBlockHash: config.TerminalBlockHash, + TerminalBlockNumber: config.TerminalBlockNumber, + }, nil + } + return nil, errors.New("invalid terminal block hash") + } + return &engine.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil +} + +// GetPayloadV1 returns a cached payload by id. +func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.ExecutableData, error) { + if !payloadID.Is(engine.PayloadV1) { + return nil, engine.UnsupportedFork + } + data, err := api.getPayload(payloadID, false) + if err != nil { + return nil, err + } + return data.ExecutionPayload, nil +} + +// GetPayloadV2 returns a cached payload by id. +func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { + if !payloadID.Is(engine.PayloadV1, engine.PayloadV2) { + return nil, engine.UnsupportedFork + } + return api.getPayload(payloadID, false) +} + +// GetPayloadV3 returns a cached payload by id. +func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { + if !payloadID.Is(engine.PayloadV3) { + return nil, engine.UnsupportedFork + } + return api.getPayload(payloadID, false) +} + +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, full) + if data == nil { + return nil, engine.UnknownPayload + } + return data, nil +} + +// NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. +func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) { + 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, nil) +} + +// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. +func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) { + if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun")) + } + if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai { + if params.Withdrawals == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) + } + } else { + if params.Withdrawals != nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai")) + } + } + if params.ExcessBlobGas != nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun")) + } + if params.BlobGasUsed != nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun")) + } + return api.newPayload(params, nil, nil) +} + +// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. +func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) { + if params.Withdrawals == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) + } + if params.ExcessBlobGas == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) + } + if params.BlobGasUsed == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil 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 beaconRoot post-cancun")) + } + + if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads")) + } + return api.newPayload(params, versionedHashes, beaconRoot) +} + +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 + // api.eth.BlockChain().InsertBlockWithoutSetHead, where it is blocked on + // e.g database compaction. + // 2. The call times out on the CL layer, which issues another NewPayload (execdata-N) call. + // Similarly, this also get stuck on the same place. Importantly, since the + // first call has not gone through, the early checks for "do we already have this block" + // will all return false. + // 3. When the db compaction ends, then N calls inserting the same payload are processed + // sequentially. + // Hence, we use a lock here, to be sure that the previous call has finished before we + // check whether we already have the block locally. + api.newPayloadLock.Lock() + defer api.newPayloadLock.Unlock() + + log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) + block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot) + if err != nil { + bgu := "nil" + if params.BlobGasUsed != nil { + bgu = strconv.Itoa(int(*params.BlobGasUsed)) + } + ebg := "nil" + if params.BlobGasUsed != nil { + ebg = strconv.Itoa(int(*params.ExcessBlobGas)) + } + log.Warn("Invalid NewPayload params", + "params.Number", params.Number, + "params.ParentHash", params.ParentHash, + "params.BlockHash", params.BlockHash, + "params.StateRoot", params.StateRoot, + "params.FeeRecipient", params.FeeRecipient, + "params.LogsBloom", common.PrettyBytes(params.LogsBloom), + "params.Random", params.Random, + "params.GasLimit", params.GasLimit, + "params.GasUsed", params.GasUsed, + "params.Timestamp", params.Timestamp, + "params.ExtraData", common.PrettyBytes(params.ExtraData), + "params.BaseFeePerGas", params.BaseFeePerGas, + "params.BlobGasUsed", bgu, + "params.ExcessBlobGas", ebg, + "len(params.Transactions)", len(params.Transactions), + "len(params.Withdrawals)", len(params.Withdrawals), + "beaconRoot", beaconRoot, + "error", err) + return api.invalid(err, nil), nil + } + // Stash away the last update to warn the user if the beacon client goes offline + api.lastNewPayloadLock.Lock() + api.lastNewPayloadUpdate = time.Now() + api.lastNewPayloadLock.Unlock() + + // If we already have the block locally, ignore the entire execution and just + // return a fake success. + if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil { + log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) + hash := block.Hash() + return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil + } + // If this block was rejected previously, keep rejecting it + if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil { + return *res, nil + } + // If the parent is missing, we - in theory - could trigger a sync, but that + // would also entail a reorg. That is problematic if multiple sibling blocks + // are being fed to us, and even more so, if some semi-distant uncle shortens + // our live chain. As such, payload execution will not permit reorgs and thus + // will not trigger a sync cycle. That is fine though, if we get a fork choice + // update after legit payload executions. + parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return api.delayPayloadImport(block), nil + } + // We have an existing parent, do some sanity checks to avoid the beacon client + // triggering too early + var ( + ptd = api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64()) + ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty + gptd = api.eth.BlockChain().GetTd(parent.ParentHash(), parent.NumberU64()-1) + ) + if ptd.Cmp(ttd) < 0 { + log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd) + return engine.INVALID_TERMINAL_BLOCK, nil + } + if parent.Difficulty().BitLen() > 0 && gptd != nil && gptd.Cmp(ttd) >= 0 { + log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd) + return engine.INVALID_TERMINAL_BLOCK, nil + } + if block.Time() <= parent.Time() { + log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) + return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil + } + // 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. + if api.eth.SyncMode() != downloader.FullSync { + return api.delayPayloadImport(block), nil + } + if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { + api.remoteBlocks.put(block.Hash(), block.Header()) + log.Warn("State not available, ignoring new payload") + return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil + } + log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number()) + if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil { + log.Warn("NewPayloadV1: inserting block failed", "error", err) + + api.invalidLock.Lock() + api.invalidBlocksHits[block.Hash()] = 1 + api.invalidTipsets[block.Hash()] = block.Header() + api.invalidLock.Unlock() + + return api.invalid(err, parent.Header()), nil + } + hash := block.Hash() + return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil +} + +// delayPayloadImport stashes the given block away for import at a later time, +// either via a forkchoice update or a sync extension. This method is meant to +// be called by the newpayload command when the block seems to be ok, but some +// prerequisite prevents it from being processed (e.g. no parent, or snap sync). +func (api *ConsensusAPI) delayPayloadImport(block *types.Block) engine.PayloadStatusV1 { + // Sanity check that this block's parent is not on a previously invalidated + // chain. If it is, mark the block as invalid too. + if res := api.checkInvalidAncestor(block.ParentHash(), block.Hash()); res != nil { + return *res + } + // Stash the block away for a potential forced forkchoice update to it + // at a later time. + api.remoteBlocks.put(block.Hash(), block.Header()) + + // Although we don't want to trigger a sync, if there is one already in + // progress, try to extend it with the current payload request to relieve + // some strain from the forkchoice update. + 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} + } + // Either no beacon sync was started yet, or it rejected the delivered + // payload as non-integratable on top of the existing sync. We'll just + // have to rely on the beacon client to forcefully update the head with + // a forkchoice update request. + if api.eth.SyncMode() == downloader.FullSync { + // 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(), "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(), "reason", err) + } + return engine.PayloadStatusV1{Status: engine.SYNCING} +} + +// setInvalidAncestor is a callback for the downloader to notify us if a bad block +// is encountered during the async sync. +func (api *ConsensusAPI) setInvalidAncestor(invalid *types.Header, origin *types.Header) { + api.invalidLock.Lock() + defer api.invalidLock.Unlock() + + api.invalidTipsets[origin.Hash()] = invalid + api.invalidBlocksHits[invalid.Hash()]++ +} + +// checkInvalidAncestor checks whether the specified chain end links to a known +// bad ancestor. If yes, it constructs the payload failure response to return. +func (api *ConsensusAPI) checkInvalidAncestor(check common.Hash, head common.Hash) *engine.PayloadStatusV1 { + api.invalidLock.Lock() + defer api.invalidLock.Unlock() + + // If the hash to check is unknown, return valid + invalid, ok := api.invalidTipsets[check] + if !ok { + return nil + } + // If the bad hash was hit too many times, evict it and try to reprocess in + // the hopes that we have a data race that we can exit out of. + badHash := invalid.Hash() + + api.invalidBlocksHits[badHash]++ + if api.invalidBlocksHits[badHash] >= invalidBlockHitEviction { + log.Warn("Too many bad block import attempt, trying", "number", invalid.Number, "hash", badHash) + delete(api.invalidBlocksHits, badHash) + + for descendant, badHeader := range api.invalidTipsets { + if badHeader.Hash() == badHash { + delete(api.invalidTipsets, descendant) + } + } + return nil + } + // Not too many failures yet, mark the head of the invalid chain as invalid + if check != head { + log.Warn("Marked new chain head as invalid", "hash", head, "badnumber", invalid.Number, "badhash", badHash) + for len(api.invalidTipsets) >= invalidTipsetsCap { + for key := range api.invalidTipsets { + delete(api.invalidTipsets, key) + break + } + } + api.invalidTipsets[head] = invalid + } + // If the last valid hash is the terminal pow block, return 0x0 for latest valid hash + lastValid := &invalid.ParentHash + if header := api.eth.BlockChain().GetHeader(invalid.ParentHash, invalid.Number.Uint64()-1); header != nil && header.Difficulty.Sign() != 0 { + lastValid = &common.Hash{} + } + failure := "links to previously rejected block" + return &engine.PayloadStatusV1{ + Status: engine.INVALID, + LatestValidHash: lastValid, + ValidationError: &failure, + } +} + +// invalid returns a response "INVALID" with the latest valid hash supplied by latest. +func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.PayloadStatusV1 { + var currentHash *common.Hash + if latestValid != nil { + 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 + h := latestValid.Hash() + currentHash = &h + } + } + errorMsg := err.Error() + return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: currentHash, ValidationError: &errorMsg} +} + +// heartbeat loops indefinitely, and checks if there have been beacon client updates +// received in the last while. If not - or if they but strange ones - it warns the +// user that something might be off with their consensus node. +// +// TODO(karalabe): Spin this goroutine down somehow +func (api *ConsensusAPI) heartbeat() { + // Sleep a bit on startup since there's obviously no beacon client yet + // attached, so no need to print scary warnings to the user. + time.Sleep(beaconUpdateStartupTimeout) + + // If the network is not yet merged/merging, don't bother continuing. + if api.eth.BlockChain().Config().TerminalTotalDifficulty == nil { + return + } + + var offlineLogged time.Time + + for { + // Sleep a bit and retrieve the last known consensus updates + time.Sleep(5 * time.Second) + + api.lastTransitionLock.Lock() + lastTransitionUpdate := api.lastTransitionUpdate + api.lastTransitionLock.Unlock() + + api.lastForkchoiceLock.Lock() + lastForkchoiceUpdate := api.lastForkchoiceUpdate + api.lastForkchoiceLock.Unlock() + + api.lastNewPayloadLock.Lock() + lastNewPayloadUpdate := api.lastNewPayloadUpdate + api.lastNewPayloadLock.Unlock() + + // If there have been no updates for the past while, warn the user + // that the beacon client is probably offline + if time.Since(lastForkchoiceUpdate) <= beaconUpdateConsensusTimeout || time.Since(lastNewPayloadUpdate) <= beaconUpdateConsensusTimeout { + offlineLogged = time.Time{} + continue + } + if time.Since(offlineLogged) > beaconUpdateWarnFrequency { + if lastForkchoiceUpdate.IsZero() && lastNewPayloadUpdate.IsZero() { + if lastTransitionUpdate.IsZero() { + log.Warn("Post-merge network, but no beacon client seen. Please launch one to follow the chain!") + } else { + log.Warn("Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!") + } + } else { + log.Warn("Beacon client online, but no consensus updates received in a while. Please fix your beacon client to follow the chain!") + } + offlineLogged = time.Now() + } + continue + } +} + +// ExchangeCapabilities returns the current methods provided by this node. +func (api *ConsensusAPI) ExchangeCapabilities([]string) []string { + return caps +} + +// GetClientVersionV1 exchanges client version data of this node. +func (api *ConsensusAPI) GetClientVersionV1(info engine.ClientVersionV1) []engine.ClientVersionV1 { + log.Trace("Engine API request received", "method", "GetClientVersionV1", "info", info.String()) + commit := make([]byte, 4) + if vcs, ok := version.VCS(); ok { + commit = common.FromHex(vcs.Commit)[0:4] + } + return []engine.ClientVersionV1{ + { + Code: engine.ClientCode, + Name: engine.ClientName, + Version: params.VersionWithMeta, + Commit: hexutil.Encode(commit), + }, + } +} + +// 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 { + bodies := make([]*engine.ExecutionPayloadBodyV1, len(hashes)) + for i, hash := range hashes { + block := api.eth.BlockChain().GetBlockByHash(hash) + bodies[i] = getBody(block) + } + return bodies +} + +// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range +// of block bodies by the engine api. +func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBodyV1, error) { + if start == 0 || count == 0 { + return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count)) + } + if count > 1024 { + return nil, engine.TooLargeRequest.With(fmt.Errorf("requested count too large: %v", count)) + } + // limit count up until current + current := api.eth.BlockChain().CurrentBlock().Number.Uint64() + last := uint64(start) + uint64(count) - 1 + if last > current { + last = current + } + bodies := make([]*engine.ExecutionPayloadBodyV1, 0, uint64(count)) + for i := uint64(start); i <= last; i++ { + block := api.eth.BlockChain().GetBlockByNumber(i) + bodies = append(bodies, getBody(block)) + } + return bodies, nil +} + +func getBody(block *types.Block) *engine.ExecutionPayloadBodyV1 { + if block == nil { + return nil + } + + var ( + body = block.Body() + txs = make([]hexutil.Bytes, len(body.Transactions)) + withdrawals = body.Withdrawals + ) + + for j, tx := range body.Transactions { + txs[j], _ = tx.MarshalBinary() + } + + // Post-shanghai withdrawals MUST be set to empty slice instead of nil + if withdrawals == nil && block.Header().WithdrawalsHash != nil { + withdrawals = make([]*types.Withdrawal, 0) + } + + return &engine.ExecutionPayloadBodyV1{ + TransactionData: txs, + Withdrawals: withdrawals, + } +} diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go new file mode 100644 index 0000000..64e6684 --- /dev/null +++ b/eth/catalyst/api_test.go @@ -0,0 +1,1682 @@ +// 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 catalyst + +import ( + "bytes" + "context" + crand "crypto/rand" + "fmt" + "math/big" + "math/rand" + "reflect" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + 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/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 ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + + testBalance = big.NewInt(2e18) +) + +func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { + config := *params.AllEthashProtocolChanges + engine := consensus.Engine(beaconConsensus.New(ethash.NewFaker())) + if merged { + config.TerminalTotalDifficulty = common.Big0 + config.TerminalTotalDifficultyPassed = true + engine = beaconConsensus.NewFaker() + } + genesis := &core.Genesis{ + Config: &config, + Alloc: types.GenesisAlloc{ + testAddr: {Balance: testBalance}, + params.BeaconRootsAddress: {Balance: common.Big0, Code: common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500")}, + }, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + BaseFee: big.NewInt(params.InitialBaseFee), + Difficulty: big.NewInt(0), + } + testNonce := uint64(0) + generate := func(i int, g *core.BlockGen) { + g.OffsetTime(5) + g.SetExtra([]byte("test")) + tx, _ := types.SignTx(types.NewTransaction(testNonce, common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"), big.NewInt(1), params.TxGas, big.NewInt(params.InitialBaseFee*2), nil), types.LatestSigner(&config), testKey) + g.AddTx(tx) + testNonce++ + } + _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, n, generate) + + if !merged { + totalDifficulty := big.NewInt(0) + for _, b := range blocks { + totalDifficulty.Add(totalDifficulty, b.Difficulty()) + } + config.TerminalTotalDifficulty = totalDifficulty + } + + return genesis, blocks +} + +func TestEth2AssembleBlock(t *testing.T) { + genesis, blocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, blocks) + defer n.Close() + + api := NewConsensusAPI(ethservice) + signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID) + tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) + if err != nil { + t.Fatalf("error signing transaction, err=%v", err) + } + ethservice.TxPool().Add([]*types.Transaction{tx}, true, true) + blockParams := engine.PayloadAttributes{ + Timestamp: blocks[9].Time() + 5, + } + // The miner needs to pick up on the txs in the pool, so a few retries might be + // needed. + if _, testErr := assembleWithTransactions(api, blocks[9].Hash(), &blockParams, 1); testErr != nil { + t.Fatal(testErr) + } +} + +// assembleWithTransactions tries to assemble a block, retrying until it has 'want', +// number of transactions in it, or it has retried three times. +func assembleWithTransactions(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes, want int) (execData *engine.ExecutableData, err error) { + for retries := 3; retries > 0; retries-- { + execData, err = assembleBlock(api, parentHash, params) + if err != nil { + return nil, err + } + if have, want := len(execData.Transactions), want; have != want { + err = fmt.Errorf("invalid number of transactions, have %d want %d", have, want) + continue + } + return execData, nil + } + return nil, err +} + +func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { + genesis, blocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, blocks[:9]) + defer n.Close() + + api := NewConsensusAPI(ethservice) + + // Put the 10th block's tx in the pool and produce a new block + txs := blocks[9].Transactions() + api.eth.TxPool().Add(txs, false, true) + blockParams := engine.PayloadAttributes{ + Timestamp: blocks[8].Time() + 5, + } + // The miner needs to pick up on the txs in the pool, so a few retries might be + // needed. + if _, err := assembleWithTransactions(api, blocks[8].Hash(), &blockParams, blocks[9].Transactions().Len()); err != nil { + t.Fatal(err) + } +} + +func TestSetHeadBeforeTotalDifficulty(t *testing.T) { + genesis, blocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, blocks) + defer n.Close() + + api := NewConsensusAPI(ethservice) + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: blocks[5].Hash(), + SafeBlockHash: common.Hash{}, + FinalizedBlockHash: common.Hash{}, + } + if resp, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + t.Errorf("fork choice updated should not error: %v", err) + } else if resp.PayloadStatus.Status != engine.INVALID_TERMINAL_BLOCK.Status { + t.Errorf("fork choice updated before total terminal difficulty should be INVALID") + } +} + +func TestEth2PrepareAndGetPayload(t *testing.T) { + genesis, blocks := generateMergeChain(10, false) + // We need to properly set the terminal total difficulty + genesis.Config.TerminalTotalDifficulty.Sub(genesis.Config.TerminalTotalDifficulty, blocks[9].Difficulty()) + n, ethservice := startEthService(t, genesis, blocks[:9]) + defer n.Close() + + api := NewConsensusAPI(ethservice) + + // Put the 10th block's tx in the pool and produce a new block + txs := blocks[9].Transactions() + ethservice.TxPool().Add(txs, true, true) + blockParams := engine.PayloadAttributes{ + Timestamp: blocks[8].Time() + 5, + } + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: blocks[8].Hash(), + SafeBlockHash: common.Hash{}, + FinalizedBlockHash: common.Hash{}, + } + _, err := api.ForkchoiceUpdatedV1(fcState, &blockParams) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + // give the payload some time to be built + time.Sleep(100 * time.Millisecond) + payloadID := (&miner.BuildPayloadArgs{ + Parent: fcState.HeadBlockHash, + Timestamp: blockParams.Timestamp, + FeeRecipient: blockParams.SuggestedFeeRecipient, + Random: blockParams.Random, + BeaconRoot: blockParams.BeaconRoot, + Version: engine.PayloadV1, + }).Id() + execData, err := api.GetPayloadV1(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + if len(execData.Transactions) != blocks[9].Transactions().Len() { + t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions)) + } + // Test invalid payloadID + var invPayload engine.PayloadID + copy(invPayload[:], payloadID[:]) + invPayload[0] = ^invPayload[0] + _, err = api.GetPayloadV1(invPayload) + if err == nil { + t.Fatal("expected error retrieving invalid payload") + } +} + +func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan core.RemovedLogsEvent, wantNew, wantRemoved int) { + t.Helper() + + if len(logsCh) != wantNew { + t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew) + } + if len(rmLogsCh) != wantRemoved { + t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved) + } + // Drain events. + for i := 0; i < len(logsCh); i++ { + <-logsCh + } + for i := 0; i < len(rmLogsCh); i++ { + <-rmLogsCh + } +} + +func TestInvalidPayloadTimestamp(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + var ( + api = NewConsensusAPI(ethservice) + parent = ethservice.BlockChain().CurrentBlock() + ) + tests := []struct { + time uint64 + shouldErr bool + }{ + {0, true}, + {parent.Time, true}, + {parent.Time - 1, true}, + {parent.Time + 1, false}, + {uint64(time.Now().Unix()) + uint64(time.Minute), false}, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) { + params := engine.PayloadAttributes{ + Timestamp: test.time, + Random: crypto.Keccak256Hash([]byte{byte(123)}), + SuggestedFeeRecipient: parent.Coinbase, + } + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: parent.Hash(), + SafeBlockHash: common.Hash{}, + FinalizedBlockHash: common.Hash{}, + } + _, err := api.ForkchoiceUpdatedV1(fcState, ¶ms) + if test.shouldErr && err == nil { + t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err) + } else if !test.shouldErr && err != nil { + t.Fatalf("error preparing payload with valid timestamp, err=%v", err) + } + }) + } +} + +func TestEth2NewBlock(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + var ( + api = NewConsensusAPI(ethservice) + parent = preMergeBlocks[len(preMergeBlocks)-1] + + // This EVM code generates a log when the contract is created. + logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") + ) + // The event channels. + newLogCh := make(chan []*types.Log, 10) + rmLogsCh := make(chan core.RemovedLogsEvent, 10) + ethservice.BlockChain().SubscribeLogsEvent(newLogCh) + ethservice.BlockChain().SubscribeRemovedLogsEvent(rmLogsCh) + + for i := 0; i < 10; i++ { + 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([]*types.Transaction{tx}, true, true) + + execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{ + Timestamp: parent.Time() + 5, + }, 1) + if err != nil { + t.Fatalf("Failed to create the executable data, block %d: %v", i, err) + } + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) + if err != nil { + t.Fatalf("Failed to convert executable data to block %v", err) + } + newResp, err := api.NewPayloadV1(*execData) + switch { + case err != nil: + t.Fatalf("Failed to insert block: %v", err) + case newResp.Status != "VALID": + t.Fatalf("Failed to insert block: %v", newResp.Status) + case ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1: + t.Fatalf("Chain head shouldn't be updated") + } + checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: block.Hash(), + SafeBlockHash: block.Hash(), + FinalizedBlockHash: block.Hash(), + } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + t.Fatalf("Failed to insert block: %v", err) + } + if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want { + t.Fatalf("Chain head should be updated, have %d want %d", have, want) + } + checkLogEvents(t, newLogCh, rmLogsCh, 1, 0) + + parent = block + } + + // Introduce fork chain + var ( + head = ethservice.BlockChain().CurrentBlock().Number.Uint64() + ) + parent = preMergeBlocks[len(preMergeBlocks)-1] + for i := 0; i < 10; i++ { + execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{ + Timestamp: parent.Time() + 6, + }) + if err != nil { + t.Fatalf("Failed to create the executable data %v", err) + } + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) + if err != nil { + t.Fatalf("Failed to convert executable data to block %v", err) + } + newResp, err := api.NewPayloadV1(*execData) + if err != nil || newResp.Status != "VALID" { + t.Fatalf("Failed to insert block: %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64() != head { + t.Fatalf("Chain head shouldn't be updated") + } + + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: block.Hash(), + SafeBlockHash: block.Hash(), + FinalizedBlockHash: block.Hash(), + } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + t.Fatalf("Failed to insert block: %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() { + t.Fatalf("Chain head should be updated") + } + parent, head = block, block.NumberU64() + } +} + +func TestEth2DeepReorg(t *testing.T) { + // TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg + // before the totalTerminalDifficulty threshold + /* + genesis, preMergeBlocks := generateMergeChain(core.TriesInMemory * 2, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + var ( + api = NewConsensusAPI(ethservice, nil) + parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1] + head = ethservice.BlockChain().CurrentBlock().Number.Uint64()() + ) + if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) { + t.Errorf("Block %d not pruned", parent.NumberU64()) + } + for i := 0; i < 10; i++ { + execData, err := api.assembleBlock(AssembleBlockParams{ + ParentHash: parent.Hash(), + Timestamp: parent.Time() + 5, + }) + if err != nil { + t.Fatalf("Failed to create the executable data %v", err) + } + block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData) + if err != nil { + t.Fatalf("Failed to convert executable data to block %v", err) + } + newResp, err := api.ExecutePayload(*execData) + if err != nil || newResp.Status != "VALID" { + t.Fatalf("Failed to insert block: %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != head { + t.Fatalf("Chain head shouldn't be updated") + } + if err := api.setHead(block.Hash()); err != nil { + t.Fatalf("Failed to set head: %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64()() != block.NumberU64() { + t.Fatalf("Chain head should be updated") + } + parent, head = block, block.NumberU64() + } + */ +} + +// startEthService creates a full node instance for testing. +func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) { + t.Helper() + + n, err := node.New(&node.Config{ + P2P: p2p.Config{ + ListenAddr: "0.0.0.0:0", + NoDiscovery: true, + MaxPeers: 25, + }}) + if err != nil { + t.Fatal("can't create node:", err) + } + + mcfg := miner.DefaultConfig + mcfg.PendingFeeRecipient = testAddr + ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: mcfg} + ethservice, err := eth.New(n, ethcfg) + if err != nil { + t.Fatal("can't create eth service:", err) + } + if err := n.Start(); err != nil { + t.Fatal("can't start node:", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil { + n.Close() + t.Fatal("can't import test blocks:", err) + } + + ethservice.SetSynced() + return n, ethservice +} + +func TestFullAPI(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + var ( + parent = ethservice.BlockChain().CurrentBlock() + // This EVM code generates a log when the contract is created. + logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") + ) + + callback := func(parent *types.Header) { + 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([]*types.Transaction{tx}, true, false) + } + + setupBlocks(t, ethservice, 10, parent, callback, nil) +} + +func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal) []*types.Header { + api := NewConsensusAPI(ethservice) + var blocks []*types.Header + for i := 0; i < n; i++ { + callback(parent) + var w []*types.Withdrawal + if withdrawals != nil { + w = withdrawals[i] + } + + payload := getNewPayload(t, api, parent, w) + execResp, err := api.NewPayloadV2(*payload) + if err != nil { + t.Fatalf("can't execute payload: %v", err) + } + if execResp.Status != engine.VALID { + t.Fatalf("invalid status: %v", execResp.Status) + } + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: payload.BlockHash, + SafeBlockHash: payload.ParentHash, + FinalizedBlockHash: payload.ParentHash, + } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + t.Fatalf("Failed to insert block: %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number { + t.Fatal("Chain head should be updated") + } + if ethservice.BlockChain().CurrentFinalBlock().Number.Uint64() != payload.Number-1 { + t.Fatal("Finalized block should be updated") + } + parent = ethservice.BlockChain().CurrentBlock() + blocks = append(blocks, parent) + } + return blocks +} + +func TestExchangeTransitionConfig(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + // invalid ttd + api := NewConsensusAPI(ethservice) + config := engine.TransitionConfigurationV1{ + TerminalTotalDifficulty: (*hexutil.Big)(big.NewInt(0)), + TerminalBlockHash: common.Hash{}, + TerminalBlockNumber: 0, + } + if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil { + t.Fatal("expected error on invalid config, invalid ttd") + } + // invalid terminal block hash + config = engine.TransitionConfigurationV1{ + TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty), + TerminalBlockHash: common.Hash{1}, + TerminalBlockNumber: 0, + } + if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil { + t.Fatal("expected error on invalid config, invalid hash") + } + // valid config + config = engine.TransitionConfigurationV1{ + TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty), + TerminalBlockHash: common.Hash{}, + TerminalBlockNumber: 0, + } + if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil { + t.Fatalf("expected no error on valid config, got %v", err) + } + // valid config + config = engine.TransitionConfigurationV1{ + TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty), + TerminalBlockHash: preMergeBlocks[5].Hash(), + TerminalBlockNumber: 6, + } + if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil { + t.Fatalf("expected no error on valid config, got %v", err) + } +} + +/* +TestNewPayloadOnInvalidChain sets up a valid chain and tries to feed blocks +from an invalid chain to test if latestValidHash (LVH) works correctly. + +We set up the following chain where P1 ... Pn and P1†are valid while +P1' is invalid. +We expect +(1) The LVH to point to the current inserted payload if it was valid. +(2) The LVH to point to the valid parent on an invalid payload (if the parent is available). +(3) If the parent is unavailable, the LVH should not be set. + + CommonAncestor◄─▲── P1 ◄── P2 ◄─ P3 ◄─ ... ◄─ Pn + │ + └── P1' ◄─ P2' ◄─ P3' ◄─ ... ◄─ Pn' + │ + └── P1'' +*/ +func TestNewPayloadOnInvalidChain(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + var ( + api = NewConsensusAPI(ethservice) + parent = ethservice.BlockChain().CurrentBlock() + signer = types.LatestSigner(ethservice.BlockChain().Config()) + // This EVM code generates a log when the contract is created. + logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") + ) + for i := 0; i < 10; i++ { + statedb, _ := ethservice.BlockChain().StateAt(parent.Root) + tx := types.MustSignNewTx(testKey, signer, &types.LegacyTx{ + Nonce: statedb.GetNonce(testAddr), + Value: new(big.Int), + Gas: 1000000, + GasPrice: big.NewInt(2 * params.InitialBaseFee), + Data: logCode, + }) + ethservice.TxPool().Add([]*types.Transaction{tx}, false, true) + var ( + params = engine.PayloadAttributes{ + Timestamp: parent.Time + 1, + Random: crypto.Keccak256Hash([]byte{byte(i)}), + SuggestedFeeRecipient: parent.Coinbase, + } + fcState = engine.ForkchoiceStateV1{ + HeadBlockHash: parent.Hash(), + SafeBlockHash: common.Hash{}, + FinalizedBlockHash: common.Hash{}, + } + payload *engine.ExecutableData + resp engine.ForkChoiceResponse + err error + ) + for i := 0; ; i++ { + if resp, err = api.ForkchoiceUpdatedV1(fcState, ¶ms); err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + if resp.PayloadStatus.Status != engine.VALID { + t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status) + } + // give the payload some time to be built + time.Sleep(50 * time.Millisecond) + if payload, err = api.GetPayloadV1(*resp.PayloadID); err != nil { + t.Fatalf("can't get payload: %v", err) + } + if len(payload.Transactions) > 0 { + break + } + // No luck this time we need to update the params and try again. + params.Timestamp = params.Timestamp + 1 + if i > 10 { + t.Fatalf("payload should not be empty") + } + } + execResp, err := api.NewPayloadV1(*payload) + if err != nil { + t.Fatalf("can't execute payload: %v", err) + } + if execResp.Status != engine.VALID { + t.Fatalf("invalid status: %v", execResp.Status) + } + fcState = engine.ForkchoiceStateV1{ + HeadBlockHash: payload.BlockHash, + SafeBlockHash: payload.ParentHash, + FinalizedBlockHash: payload.ParentHash, + } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + t.Fatalf("Failed to insert block: %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number { + t.Fatalf("Chain head should be updated") + } + parent = ethservice.BlockChain().CurrentBlock() + } +} + +func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutableData, error) { + args := &miner.BuildPayloadArgs{ + Parent: parentHash, + Timestamp: params.Timestamp, + FeeRecipient: params.SuggestedFeeRecipient, + Random: params.Random, + Withdrawals: params.Withdrawals, + BeaconRoot: params.BeaconRoot, + } + payload, err := api.eth.Miner().BuildPayload(args) + if err != nil { + return nil, err + } + return payload.ResolveFull().ExecutionPayload, nil +} + +func TestEmptyBlocks(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + commonAncestor := ethservice.BlockChain().CurrentBlock() + api := NewConsensusAPI(ethservice) + + // Setup 10 blocks on the canonical chain + setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil) + + // (1) check LatestValidHash by sending a normal payload (P1'') + payload := getNewPayload(t, api, commonAncestor, nil) + + status, err := api.NewPayloadV1(*payload) + if err != nil { + t.Fatal(err) + } + if status.Status != engine.VALID { + t.Errorf("invalid status: expected VALID got: %v", status.Status) + } + if !bytes.Equal(status.LatestValidHash[:], payload.BlockHash[:]) { + t.Fatalf("invalid LVH: got %v want %v", status.LatestValidHash, payload.BlockHash) + } + + // (2) Now send P1' which is invalid + payload = getNewPayload(t, api, commonAncestor, nil) + payload.GasUsed += 1 + payload = setBlockhash(payload) + // Now latestValidHash should be the common ancestor + status, err = api.NewPayloadV1(*payload) + if err != nil { + t.Fatal(err) + } + if status.Status != engine.INVALID { + t.Errorf("invalid status: expected INVALID got: %v", status.Status) + } + // Expect 0x0 on INVALID block on top of PoW block + expected := common.Hash{} + if !bytes.Equal(status.LatestValidHash[:], expected[:]) { + t.Fatalf("invalid LVH: got %v want %v", status.LatestValidHash, expected) + } + + // (3) Now send a payload with unknown parent + payload = getNewPayload(t, api, commonAncestor, nil) + payload.ParentHash = common.Hash{1} + payload = setBlockhash(payload) + // Now latestValidHash should be the common ancestor + status, err = api.NewPayloadV1(*payload) + if err != nil { + t.Fatal(err) + } + if status.Status != engine.SYNCING { + t.Errorf("invalid status: expected SYNCING got: %v", status.Status) + } + if status.LatestValidHash != nil { + t.Fatalf("invalid LVH: got %v wanted nil", status.LatestValidHash) + } +} + +func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal) *engine.ExecutableData { + params := engine.PayloadAttributes{ + Timestamp: parent.Time + 1, + Random: crypto.Keccak256Hash([]byte{byte(1)}), + SuggestedFeeRecipient: parent.Coinbase, + Withdrawals: withdrawals, + } + + payload, err := assembleBlock(api, parent.Hash(), ¶ms) + if err != nil { + t.Fatal(err) + } + return payload +} + +// setBlockhash sets the blockhash of a modified ExecutableData. +// Can be used to make modified payloads look valid. +func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData { + txs, _ := decodeTransactions(data.Transactions) + number := big.NewInt(0) + number.SetUint64(data.Number) + header := &types.Header{ + ParentHash: data.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: data.FeeRecipient, + Root: data.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: data.ReceiptsRoot, + Bloom: types.BytesToBloom(data.LogsBloom), + Difficulty: common.Big0, + Number: number, + GasLimit: data.GasLimit, + GasUsed: data.GasUsed, + Time: data.Timestamp, + BaseFee: data.BaseFeePerGas, + Extra: data.ExtraData, + MixDigest: data.Random, + } + block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs}) + data.BlockHash = block.Hash() + return data +} + +func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { + var txs = make([]*types.Transaction, len(enc)) + for i, encTx := range enc { + var tx types.Transaction + if err := tx.UnmarshalBinary(encTx); err != nil { + return nil, fmt.Errorf("invalid transaction %d: %v", i, err) + } + txs[i] = &tx + } + return txs, nil +} + +func TestTrickRemoteBlockCache(t *testing.T) { + // Setup two nodes + genesis, preMergeBlocks := generateMergeChain(10, false) + nodeA, ethserviceA := startEthService(t, genesis, preMergeBlocks) + nodeB, ethserviceB := startEthService(t, genesis, preMergeBlocks) + defer nodeA.Close() + defer nodeB.Close() + for nodeB.Server().NodeInfo().Ports.Listener == 0 { + time.Sleep(250 * time.Millisecond) + } + nodeA.Server().AddPeer(nodeB.Server().Self()) + nodeB.Server().AddPeer(nodeA.Server().Self()) + apiA := NewConsensusAPI(ethserviceA) + apiB := NewConsensusAPI(ethserviceB) + + commonAncestor := ethserviceA.BlockChain().CurrentBlock() + + // Setup 10 blocks on the canonical chain + setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil) + commonAncestor = ethserviceA.BlockChain().CurrentBlock() + + var invalidChain []*engine.ExecutableData + // create a valid payload (P1) + //payload1 := getNewPayload(t, apiA, commonAncestor) + //invalidChain = append(invalidChain, payload1) + + // create an invalid payload2 (P2) + payload2 := getNewPayload(t, apiA, commonAncestor, nil) + //payload2.ParentHash = payload1.BlockHash + payload2.GasUsed += 1 + payload2 = setBlockhash(payload2) + invalidChain = append(invalidChain, payload2) + + head := payload2 + // create some valid payloads on top + for i := 0; i < 10; i++ { + payload := getNewPayload(t, apiA, commonAncestor, nil) + payload.ParentHash = head.BlockHash + payload = setBlockhash(payload) + invalidChain = append(invalidChain, payload) + head = payload + } + + // feed the payloads to node B + for _, payload := range invalidChain { + status, err := apiB.NewPayloadV1(*payload) + if err != nil { + panic(err) + } + if status.Status == engine.VALID { + t.Error("invalid status: VALID on an invalid chain") + } + // Now reorg to the head of the invalid chain + resp, err := apiB.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil) + if err != nil { + t.Fatal(err) + } + if resp.PayloadStatus.Status == engine.VALID { + t.Error("invalid status: VALID on an invalid chain") + } + time.Sleep(100 * time.Millisecond) + } +} + +func TestInvalidBloom(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + commonAncestor := ethservice.BlockChain().CurrentBlock() + api := NewConsensusAPI(ethservice) + + // Setup 10 blocks on the canonical chain + setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil) + + // (1) check LatestValidHash by sending a normal payload (P1'') + payload := getNewPayload(t, api, commonAncestor, nil) + payload.LogsBloom = append(payload.LogsBloom, byte(1)) + status, err := api.NewPayloadV1(*payload) + if err != nil { + t.Fatal(err) + } + if status.Status != engine.INVALID { + t.Errorf("invalid status: expected INVALID got: %v", status.Status) + } +} + +func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(100, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + api := NewConsensusAPI(ethservice) + + // Test parent already post TTD in FCU + parent := preMergeBlocks[len(preMergeBlocks)-2] + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: parent.Hash(), + SafeBlockHash: common.Hash{}, + FinalizedBlockHash: common.Hash{}, + } + resp, err := api.ForkchoiceUpdatedV1(fcState, nil) + if err != nil { + t.Fatalf("error sending forkchoice, err=%v", err) + } + if resp.PayloadStatus != engine.INVALID_TERMINAL_BLOCK { + t.Fatalf("error sending invalid forkchoice, invalid status: %v", resp.PayloadStatus.Status) + } + + // Test parent already post TTD in NewPayload + args := &miner.BuildPayloadArgs{ + Parent: parent.Hash(), + Timestamp: parent.Time() + 1, + Random: crypto.Keccak256Hash([]byte{byte(1)}), + FeeRecipient: parent.Coinbase(), + } + payload, err := api.eth.Miner().BuildPayload(args) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + data := *payload.Resolve().ExecutionPayload + // We need to recompute the blockhash, since the miner computes a wrong (correct) blockhash + txs, _ := decodeTransactions(data.Transactions) + header := &types.Header{ + ParentHash: data.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: data.FeeRecipient, + Root: data.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: data.ReceiptsRoot, + Bloom: types.BytesToBloom(data.LogsBloom), + Difficulty: common.Big0, + Number: new(big.Int).SetUint64(data.Number), + GasLimit: data.GasLimit, + GasUsed: data.GasUsed, + Time: data.Timestamp, + BaseFee: data.BaseFeePerGas, + Extra: data.ExtraData, + MixDigest: data.Random, + } + block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs}) + data.BlockHash = block.Hash() + // Send the new payload + resp2, err := api.NewPayloadV1(data) + if err != nil { + t.Fatalf("error sending NewPayload, err=%v", err) + } + if resp2 != engine.INVALID_TERMINAL_BLOCK { + t.Fatalf("error sending invalid forkchoice, invalid status: %v", resp.PayloadStatus.Status) + } +} + +// TestSimultaneousNewBlock does several parallel inserts, both as +// newPayLoad and forkchoiceUpdate. This is to test that the api behaves +// well even of the caller is not being 'serial'. +func TestSimultaneousNewBlock(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + var ( + api = NewConsensusAPI(ethservice) + parent = preMergeBlocks[len(preMergeBlocks)-1] + ) + for i := 0; i < 10; i++ { + execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{ + Timestamp: parent.Time() + 5, + }) + if err != nil { + t.Fatalf("Failed to create the executable data %v", err) + } + // Insert it 10 times in parallel. Should be ignored. + { + var ( + wg sync.WaitGroup + testErr error + errMu sync.Mutex + ) + wg.Add(10) + for ii := 0; ii < 10; ii++ { + go func() { + defer wg.Done() + if newResp, err := api.NewPayloadV1(*execData); err != nil { + errMu.Lock() + testErr = fmt.Errorf("failed to insert block: %w", err) + errMu.Unlock() + } else if newResp.Status != "VALID" { + errMu.Lock() + testErr = fmt.Errorf("failed to insert block: %v", newResp.Status) + errMu.Unlock() + } + }() + } + wg.Wait() + if testErr != nil { + t.Fatal(testErr) + } + } + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) + if err != nil { + t.Fatalf("Failed to convert executable data to block %v", err) + } + if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64()-1 { + t.Fatalf("Chain head shouldn't be updated") + } + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: block.Hash(), + SafeBlockHash: block.Hash(), + FinalizedBlockHash: block.Hash(), + } + { + var ( + wg sync.WaitGroup + testErr error + errMu sync.Mutex + ) + wg.Add(10) + // Do each FCU 10 times + for ii := 0; ii < 10; ii++ { + go func() { + defer wg.Done() + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + errMu.Lock() + testErr = fmt.Errorf("failed to insert block: %w", err) + errMu.Unlock() + } + }() + } + wg.Wait() + if testErr != nil { + t.Fatal(testErr) + } + } + if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want { + t.Fatalf("Chain head should be updated, have %d want %d", have, want) + } + parent = block + } +} + +// TestWithdrawals creates and verifies two post-Shanghai blocks. The first +// includes zero withdrawals and the second includes two. +func TestWithdrawals(t *testing.T) { + genesis, blocks := generateMergeChain(10, true) + // Set shanghai time to last block + 5 seconds (first post-merge block) + time := blocks[len(blocks)-1].Time() + 5 + genesis.Config.ShanghaiTime = &time + + n, ethservice := startEthService(t, genesis, blocks) + defer n.Close() + + api := NewConsensusAPI(ethservice) + + // 10: Build Shanghai block with no withdrawals. + parent := ethservice.BlockChain().CurrentHeader() + blockParams := engine.PayloadAttributes{ + Timestamp: parent.Time + 5, + Withdrawals: make([]*types.Withdrawal, 0), + } + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: parent.Hash(), + } + resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + if resp.PayloadStatus.Status != engine.VALID { + t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID) + } + + // 10: 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, + Version: engine.PayloadV2, + }).Id() + execData, err := api.GetPayloadV2(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + if execData.ExecutionPayload.StateRoot != parent.Root { + t.Fatalf("mismatch state roots (got: %s, want: %s)", execData.ExecutionPayload.StateRoot, blocks[8].Root()) + } + + // 10: verify locally built block + if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { + t.Fatalf("error validating payload: %v", err) + } else if status.Status != engine.VALID { + t.Fatalf("invalid payload") + } + + // 11: build shanghai block with withdrawal + aa := common.Address{0xaa} + bb := common.Address{0xbb} + blockParams = engine.PayloadAttributes{ + Timestamp: execData.ExecutionPayload.Timestamp + 5, + Withdrawals: []*types.Withdrawal{ + { + Index: 0, + Address: aa, + Amount: 32, + }, + { + Index: 1, + Address: bb, + Amount: 33, + }, + }, + } + fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash + _, err = api.ForkchoiceUpdatedV2(fcState, &blockParams) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + + // 11: verify locally build block. + payloadID = (&miner.BuildPayloadArgs{ + Parent: fcState.HeadBlockHash, + Timestamp: blockParams.Timestamp, + FeeRecipient: blockParams.SuggestedFeeRecipient, + Random: blockParams.Random, + Withdrawals: blockParams.Withdrawals, + BeaconRoot: blockParams.BeaconRoot, + Version: engine.PayloadV2, + }).Id() + execData, err = api.GetPayloadV2(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil { + t.Fatalf("error validating payload: %v", err) + } else if status.Status != engine.VALID { + t.Fatalf("invalid payload") + } + + // 11: set block as head. + fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash + _, err = api.ForkchoiceUpdatedV2(fcState, nil) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + + // 11: verify withdrawals were processed. + db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number)) + if err != nil { + t.Fatalf("unable to load db: %v", err) + } + for i, w := range blockParams.Withdrawals { + // w.Amount is in gwei, balance in wei + if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei { + t.Fatalf("failed to process withdrawal %d", i) + } + } +} + +func TestNilWithdrawals(t *testing.T) { + genesis, blocks := generateMergeChain(10, true) + // Set shanghai time to last block + 4 seconds (first post-merge block) + time := blocks[len(blocks)-1].Time() + 4 + genesis.Config.ShanghaiTime = &time + + n, ethservice := startEthService(t, genesis, blocks) + defer n.Close() + + api := NewConsensusAPI(ethservice) + parent := ethservice.BlockChain().CurrentHeader() + aa := common.Address{0xaa} + + type test struct { + blockParams engine.PayloadAttributes + wantErr bool + } + tests := []test{ + // Before Shanghai + { + blockParams: engine.PayloadAttributes{ + Timestamp: parent.Time + 2, + Withdrawals: nil, + }, + wantErr: false, + }, + { + blockParams: engine.PayloadAttributes{ + Timestamp: parent.Time + 2, + Withdrawals: make([]*types.Withdrawal, 0), + }, + wantErr: true, + }, + { + blockParams: engine.PayloadAttributes{ + Timestamp: parent.Time + 2, + Withdrawals: []*types.Withdrawal{ + { + Index: 0, + Address: aa, + Amount: 32, + }, + }, + }, + wantErr: true, + }, + // After Shanghai + { + blockParams: engine.PayloadAttributes{ + Timestamp: parent.Time + 5, + Withdrawals: nil, + }, + wantErr: true, + }, + { + blockParams: engine.PayloadAttributes{ + Timestamp: parent.Time + 5, + Withdrawals: make([]*types.Withdrawal, 0), + }, + wantErr: false, + }, + { + blockParams: engine.PayloadAttributes{ + Timestamp: parent.Time + 5, + Withdrawals: []*types.Withdrawal{ + { + Index: 0, + Address: aa, + Amount: 32, + }, + }, + }, + wantErr: false, + }, + } + + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: parent.Hash(), + } + + for _, test := range tests { + var ( + err error + payloadVersion engine.PayloadVersion + shanghai = genesis.Config.IsShanghai(genesis.Config.LondonBlock, test.blockParams.Timestamp) + ) + if !shanghai { + payloadVersion = engine.PayloadV1 + _, err = api.ForkchoiceUpdatedV1(fcState, &test.blockParams) + } else { + payloadVersion = engine.PayloadV2 + _, err = api.ForkchoiceUpdatedV2(fcState, &test.blockParams) + } + if test.wantErr { + if err == nil { + t.Fatal("wanted error on fcuv2 with invalid withdrawals") + } + continue + } + if err != nil { + t.Fatalf("error preparing payload, err=%v", err) + } + + // 11: verify locally build block. + payloadID := (&miner.BuildPayloadArgs{ + Parent: fcState.HeadBlockHash, + Timestamp: test.blockParams.Timestamp, + FeeRecipient: test.blockParams.SuggestedFeeRecipient, + Random: test.blockParams.Random, + Version: payloadVersion, + }).Id() + execData, err := api.GetPayloadV2(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + var status engine.PayloadStatusV1 + if !shanghai { + status, err = api.NewPayloadV1(*execData.ExecutionPayload) + } else { + status, err = api.NewPayloadV2(*execData.ExecutionPayload) + } + if err != nil { + t.Fatalf("error validating payload: %v", err.(*engine.EngineAPIError).ErrorData()) + } else if status.Status != engine.VALID { + t.Fatalf("invalid payload") + } + } +} + +func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) { + genesis, blocks := generateMergeChain(10, true) + // enable shanghai on the last block + time := blocks[len(blocks)-1].Header().Time + 1 + genesis.Config.ShanghaiTime = &time + n, ethservice := startEthService(t, genesis, blocks) + + var ( + parent = ethservice.BlockChain().CurrentBlock() + // This EVM code generates a log when the contract is created. + logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") + ) + + callback := func(parent *types.Header) { + 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([]*types.Transaction{tx}, false, false) + } + + withdrawals := make([][]*types.Withdrawal, 10) + withdrawals[0] = nil // should be filtered out by miner + withdrawals[1] = make([]*types.Withdrawal, 0) + for i := 2; i < len(withdrawals); i++ { + addr := make([]byte, 20) + crand.Read(addr) + withdrawals[i] = []*types.Withdrawal{ + {Index: rand.Uint64(), Validator: rand.Uint64(), Amount: rand.Uint64(), Address: common.BytesToAddress(addr)}, + } + } + + postShanghaiHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals) + postShanghaiBlocks := make([]*types.Block, len(postShanghaiHeaders)) + for i, header := range postShanghaiHeaders { + postShanghaiBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64()) + } + return n, ethservice, append(blocks, postShanghaiBlocks...) +} + +func allHashes(blocks []*types.Block) []common.Hash { + var hashes []common.Hash + for _, b := range blocks { + hashes = append(hashes, b.Hash()) + } + return hashes +} +func allBodies(blocks []*types.Block) []*types.Body { + var bodies []*types.Body + for _, b := range blocks { + bodies = append(bodies, b.Body()) + } + return bodies +} + +func TestGetBlockBodiesByHash(t *testing.T) { + node, eth, blocks := setupBodies(t) + api := NewConsensusAPI(eth) + defer node.Close() + + tests := []struct { + results []*types.Body + hashes []common.Hash + }{ + // First pow block + { + results: []*types.Body{eth.BlockChain().GetBlockByNumber(0).Body()}, + hashes: []common.Hash{eth.BlockChain().GetBlockByNumber(0).Hash()}, + }, + // Last pow block + { + results: []*types.Body{blocks[9].Body()}, + hashes: []common.Hash{blocks[9].Hash()}, + }, + // First post-merge block + { + results: []*types.Body{blocks[10].Body()}, + hashes: []common.Hash{blocks[10].Hash()}, + }, + // Pre & post merge blocks + { + results: []*types.Body{blocks[0].Body(), blocks[9].Body(), blocks[14].Body()}, + hashes: []common.Hash{blocks[0].Hash(), blocks[9].Hash(), blocks[14].Hash()}, + }, + // unavailable block + { + results: []*types.Body{blocks[0].Body(), nil, blocks[14].Body()}, + hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[14].Hash()}, + }, + // same block multiple times + { + results: []*types.Body{blocks[0].Body(), nil, blocks[0].Body(), blocks[0].Body()}, + hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[0].Hash(), blocks[0].Hash()}, + }, + // all blocks + { + results: allBodies(blocks), + hashes: allHashes(blocks), + }, + } + + for k, test := range tests { + result := api.GetPayloadBodiesByHashV1(test.hashes) + for i, r := range result { + if !equalBody(test.results[i], r) { + t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r) + } + } + } +} + +func TestGetBlockBodiesByRange(t *testing.T) { + node, eth, blocks := setupBodies(t) + api := NewConsensusAPI(eth) + defer node.Close() + + tests := []struct { + results []*types.Body + start hexutil.Uint64 + count hexutil.Uint64 + }{ + { + results: []*types.Body{blocks[9].Body()}, + start: 10, + count: 1, + }, + // Genesis + { + results: []*types.Body{blocks[0].Body()}, + start: 1, + count: 1, + }, + // First post-merge block + { + results: []*types.Body{blocks[9].Body()}, + start: 10, + count: 1, + }, + // Pre & post merge blocks + { + results: []*types.Body{blocks[7].Body(), blocks[8].Body(), blocks[9].Body(), blocks[10].Body()}, + start: 8, + count: 4, + }, + // unavailable block + { + results: []*types.Body{blocks[18].Body(), blocks[19].Body()}, + start: 19, + count: 3, + }, + // unavailable block + { + results: []*types.Body{blocks[19].Body()}, + start: 20, + count: 2, + }, + { + results: []*types.Body{blocks[19].Body()}, + start: 20, + count: 1, + }, + // whole range unavailable + { + results: make([]*types.Body, 0), + start: 22, + count: 2, + }, + // allBlocks + { + results: allBodies(blocks), + start: 1, + count: hexutil.Uint64(len(blocks)), + }, + } + + for k, test := range tests { + result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count) + if err != nil { + t.Fatal(err) + } + if len(result) == len(test.results) { + for i, r := range result { + if !equalBody(test.results[i], r) { + t.Fatalf("test %d: invalid response: expected \n%+v\ngot\n%+v", k, test.results[i], r) + } + } + } else { + t.Fatalf("test %d: invalid length want %v got %v", k, len(test.results), len(result)) + } + } +} + +func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) { + node, eth, _ := setupBodies(t) + api := NewConsensusAPI(eth) + defer node.Close() + tests := []struct { + start hexutil.Uint64 + count hexutil.Uint64 + want *engine.EngineAPIError + }{ + // Genesis + { + start: 0, + count: 1, + want: engine.InvalidParams, + }, + // No block requested + { + start: 1, + count: 0, + want: engine.InvalidParams, + }, + // Genesis & no block + { + start: 0, + count: 0, + want: engine.InvalidParams, + }, + // More than 1024 blocks + { + start: 1, + count: 1025, + want: engine.TooLargeRequest, + }, + } + for i, tc := range tests { + result, err := api.GetPayloadBodiesByRangeV1(tc.start, tc.count) + if err == nil { + t.Fatalf("test %d: expected error, got %v", i, result) + } + if have, want := err.Error(), tc.want.Error(); have != want { + t.Fatalf("test %d: have %s, want %s", i, have, want) + } + } +} + +func equalBody(a *types.Body, b *engine.ExecutionPayloadBodyV1) bool { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { + return false + } + if len(a.Transactions) != len(b.TransactionData) { + return false + } + for i, tx := range a.Transactions { + data, _ := tx.MarshalBinary() + if !bytes.Equal(data, b.TransactionData[i]) { + return false + } + } + return reflect.DeepEqual(a.Withdrawals, b.Withdrawals) +} + +func TestBlockToPayloadWithBlobs(t *testing.T) { + header := types.Header{} + var txs []*types.Transaction + + inner := types.BlobTx{ + BlobHashes: make([]common.Hash, 1), + } + + txs = append(txs, types.NewTx(&inner)) + sidecars := []*types.BlobTxSidecar{ + { + Blobs: make([]kzg4844.Blob, 1), + Commitments: make([]kzg4844.Commitment, 1), + Proofs: make([]kzg4844.Proof, 1), + }, + } + + block := types.NewBlock(&header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil)) + envelope := engine.BlockToExecutableData(block, nil, sidecars) + var want int + for _, tx := range txs { + want += len(tx.BlobHashes()) + } + if got := len(envelope.BlobsBundle.Commitments); got != want { + t.Fatalf("invalid number of commitments: got %v, want %v", got, want) + } + if got := len(envelope.BlobsBundle.Proofs); got != want { + t.Fatalf("invalid number of proofs: got %v, want %v", got, want) + } + if got := len(envelope.BlobsBundle.Blobs); got != want { + t.Fatalf("invalid number of blobs: got %v, want %v", got, want) + } + _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil) + if err != nil { + t.Error(err) + } +} + +// This checks that beaconRoot is applied to the state from the engine API. +func TestParentBeaconBlockRoot(t *testing.T) { + log.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) + 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.ForkchoiceUpdatedV3(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, + Version: engine.PayloadV3, + }).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.BeaconRootsAddress, timeIdx); num != timeIdx { + t.Fatalf("incorrect number stored: want %s, got %s", timeIdx, num) + } + if root := db.GetState(params.BeaconRootsAddress, rootIdx); root != *blockParams.BeaconRoot { + t.Fatalf("incorrect root stored: want %s, got %s", *blockParams.BeaconRoot, root) + } +} + +// TestGetClientVersion verifies the expected version info is returned. +func TestGetClientVersion(t *testing.T) { + genesis, preMergeBlocks := generateMergeChain(10, false) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + api := NewConsensusAPI(ethservice) + info := engine.ClientVersionV1{ + Code: "TT", + Name: "test", + Version: "1.1.1", + Commit: "0x12345678", + } + infos := api.GetClientVersionV1(info) + if len(infos) != 1 { + t.Fatalf("expected only one returned client version, got %d", len(infos)) + } + info = infos[0] + if info.Code != engine.ClientCode || info.Name != engine.ClientName || info.Version != params.VersionWithMeta { + t.Fatalf("client info does match expected, got %s", info.String()) + } +} diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go new file mode 100644 index 0000000..634dc1b --- /dev/null +++ b/eth/catalyst/queue.go @@ -0,0 +1,158 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package catalyst + +import ( + "sync" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/miner" +) + +// maxTrackedPayloads is the maximum number of prepared payloads the execution +// engine tracks before evicting old ones. Ideally we should only ever track the +// latest one; but have a slight wiggle room for non-ideal conditions. +const maxTrackedPayloads = 10 + +// maxTrackedHeaders is the maximum number of executed payloads the execution +// engine tracks before evicting old ones. These are tracked outside the chain +// during initial sync to allow ForkchoiceUpdate to reference past blocks via +// hashes only. For the sync target it would be enough to track only the latest +// header, but snap sync also needs the latest finalized height for the ancient +// limit. +const maxTrackedHeaders = 96 + +// payloadQueueItem represents an id->payload tuple to store until it's retrieved +// or evicted. +type payloadQueueItem struct { + id engine.PayloadID + payload *miner.Payload +} + +// payloadQueue tracks the latest handful of constructed payloads to be retrieved +// by the beacon chain if block production is requested. +type payloadQueue struct { + payloads []*payloadQueueItem + lock sync.RWMutex +} + +// newPayloadQueue creates a pre-initialized queue with a fixed number of slots +// all containing empty items. +func newPayloadQueue() *payloadQueue { + return &payloadQueue{ + payloads: make([]*payloadQueueItem, maxTrackedPayloads), + } +} + +// put inserts a new payload into the queue at the given id. +func (q *payloadQueue) put(id engine.PayloadID, payload *miner.Payload) { + q.lock.Lock() + defer q.lock.Unlock() + + copy(q.payloads[1:], q.payloads) + q.payloads[0] = &payloadQueueItem{ + id: id, + payload: payload, + } +} + +// get retrieves a previously stored payload item or nil if it does not exist. +func (q *payloadQueue) get(id engine.PayloadID, full bool) *engine.ExecutionPayloadEnvelope { + q.lock.RLock() + defer q.lock.RUnlock() + + for _, item := range q.payloads { + if item == nil { + return nil // no more items + } + if item.id == id { + if !full { + return item.payload.Resolve() + } + return item.payload.ResolveFull() + } + } + return nil +} + +// has checks if a particular payload is already tracked. +func (q *payloadQueue) has(id engine.PayloadID) bool { + q.lock.RLock() + defer q.lock.RUnlock() + + for _, item := range q.payloads { + if item == nil { + return false + } + if item.id == id { + return true + } + } + return false +} + +// headerQueueItem represents an hash->header tuple to store until it's retrieved +// or evicted. +type headerQueueItem struct { + hash common.Hash + header *types.Header +} + +// headerQueue tracks the latest handful of constructed headers to be retrieved +// by the beacon chain if block production is requested. +type headerQueue struct { + headers []*headerQueueItem + lock sync.RWMutex +} + +// newHeaderQueue creates a pre-initialized queue with a fixed number of slots +// all containing empty items. +func newHeaderQueue() *headerQueue { + return &headerQueue{ + headers: make([]*headerQueueItem, maxTrackedHeaders), + } +} + +// put inserts a new header into the queue at the given hash. +func (q *headerQueue) put(hash common.Hash, data *types.Header) { + q.lock.Lock() + defer q.lock.Unlock() + + copy(q.headers[1:], q.headers) + q.headers[0] = &headerQueueItem{ + hash: hash, + header: data, + } +} + +// get retrieves a previously stored header item or nil if it does not exist. +func (q *headerQueue) get(hash common.Hash) *types.Header { + q.lock.RLock() + defer q.lock.RUnlock() + + for _, item := range q.headers { + if item == nil { + return nil // no more items + } + if item.hash == hash { + return item.header + } + } + return nil +} diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go new file mode 100644 index 0000000..2d6569e --- /dev/null +++ b/eth/catalyst/simulated_beacon.go @@ -0,0 +1,321 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package catalyst + +import ( + "crypto/rand" + "crypto/sha256" + "errors" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "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 { + pending chan *types.Withdrawal +} + +// add queues a withdrawal for future inclusion. +func (w *withdrawalQueue) add(withdrawal *types.Withdrawal) error { + select { + case w.pending <- withdrawal: + break + default: + return errors.New("withdrawal queue full") + } + return nil +} + +// gatherPending returns a number of queued withdrawals up to a maximum count. +func (w *withdrawalQueue) gatherPending(maxCount int) []*types.Withdrawal { + withdrawals := []*types.Withdrawal{} + for { + select { + case withdrawal := <-w.pending: + withdrawals = append(withdrawals, withdrawal) + if len(withdrawals) == maxCount { + return withdrawals + } + default: + return withdrawals + } + } +} + +type SimulatedBeacon struct { + shutdownCh chan struct{} + eth *eth.Ethereum + period uint64 + withdrawals withdrawalQueue + + feeRecipient common.Address + feeRecipientLock sync.Mutex // lock gates concurrent access to the feeRecipient + + engineAPI *ConsensusAPI + curForkchoiceState engine.ForkchoiceStateV1 + lastBlockTime uint64 +} + +// 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) { + block := eth.BlockChain().CurrentBlock() + current := engine.ForkchoiceStateV1{ + HeadBlockHash: block.Hash(), + SafeBlockHash: block.Hash(), + FinalizedBlockHash: block.Hash(), + } + engineAPI := newConsensusAPIWithoutHeartbeat(eth) + + // if genesis block, send forkchoiceUpdated to trigger transition to PoS + if block.Number.Sign() == 0 { + if _, err := engineAPI.ForkchoiceUpdatedV2(current, nil); err != nil { + return nil, err + } + } + return &SimulatedBeacon{ + eth: eth, + period: period, + shutdownCh: make(chan struct{}), + engineAPI: engineAPI, + lastBlockTime: block.Time, + curForkchoiceState: current, + withdrawals: withdrawalQueue{make(chan *types.Withdrawal, 20)}, + }, nil +} + +func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) { + c.feeRecipientLock.Lock() + c.feeRecipient = feeRecipient + c.feeRecipientLock.Unlock() +} + +// Start invokes the SimulatedBeacon life-cycle function in a goroutine. +func (c *SimulatedBeacon) Start() error { + if c.period == 0 { + // 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() + } + return nil +} + +// Stop halts the SimulatedBeacon service. +func (c *SimulatedBeacon) Stop() error { + close(c.shutdownCh) + return nil +} + +// sealBlock initiates payload building for a new block and creates a new block +// with the completed payload. +func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, 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.forkchoiceUpdated(c.curForkchoiceState, &engine.PayloadAttributes{ + Timestamp: timestamp, + SuggestedFeeRecipient: feeRecipient, + Withdrawals: withdrawals, + Random: random, + BeaconRoot: &common.Hash{}, + }, engine.PayloadV3, true) + if err != nil { + return err + } + if fcResponse == engine.STATUS_SYNCING { + return errors.New("chain rewind prevented invocation of payload creation") + } + envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true) + if err != nil { + return err + } + payload := envelope.ExecutionPayload + + var finalizedHash common.Hash + if payload.Number%devEpochLength == 0 { + finalizedHash = payload.BlockHash + } else { + if fh := c.finalizedBlockHash(payload.Number); fh == nil { + return errors.New("chain rewind interrupted calculation of finalized block hash") + } else { + finalizedHash = *fh + } + } + + // Independently calculate the blob hashes from sidecars. + blobHashes := make([]common.Hash, 0) + if envelope.BlobsBundle != nil { + hasher := sha256.New() + for _, commit := range envelope.BlobsBundle.Commitments { + var c kzg4844.Commitment + if len(commit) != len(c) { + return errors.New("invalid commitment length") + } + copy(c[:], commit) + blobHashes = append(blobHashes, kzg4844.CalcBlobHashV1(hasher, &c)) + } + } + // Mark the payload as canon + if _, err = c.engineAPI.NewPayloadV3(*payload, blobHashes, &common.Hash{}); err != nil { + return err + } + c.setCurrentState(payload.BlockHash, finalizedHash) + + // Mark the block containing the payload as canonical + if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil { + return err + } + c.lastBlockTime = payload.Timestamp + return nil +} + +// loop runs the block production loop for non-zero period configuration +func (c *SimulatedBeacon) loop() { + timer := time.NewTimer(0) + for { + select { + case <-c.shutdownCh: + return + case <-timer.C: + withdrawals := c.withdrawals.gatherPending(10) + if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { + log.Warn("Error performing sealing work", "err", err) + } else { + timer.Reset(time.Second * time.Duration(c.period)) + } + } + } +} + +// finalizedBlockHash returns the block hash of the finalized block corresponding +// to the given number or nil if doesn't exist in the chain. +func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash { + var finalizedNumber uint64 + if number%devEpochLength == 0 { + finalizedNumber = number + } else { + finalizedNumber = (number - 1) / devEpochLength * devEpochLength + } + if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil { + fh := finalizedBlock.Hash() + return &fh + } + return nil +} + +// setCurrentState sets the current forkchoice state +func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) { + c.curForkchoiceState = engine.ForkchoiceStateV1{ + HeadBlockHash: headHash, + SafeBlockHash: headHash, + FinalizedBlockHash: finalizedHash, + } +} + +// 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 { + // Ensure no pending transactions. + c.eth.TxPool().Sync() + if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 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(txpool.PendingFilter{})) != 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, + Version: "1.0", + }, + }) +} diff --git a/eth/catalyst/simulated_beacon_api.go b/eth/catalyst/simulated_beacon_api.go new file mode 100644 index 0000000..73d0a59 --- /dev/null +++ b/eth/catalyst/simulated_beacon_api.go @@ -0,0 +1,61 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package 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 { + 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.sim.withdrawals.add(withdrawal) +} + +func (a *api) SetFeeRecipient(ctx context.Context, feeRecipient common.Address) { + a.sim.setFeeRecipient(feeRecipient) +} diff --git a/eth/catalyst/simulated_beacon_test.go b/eth/catalyst/simulated_beacon_test.go new file mode 100644 index 0000000..bb10938 --- /dev/null +++ b/eth/catalyst/simulated_beacon_test.go @@ -0,0 +1,142 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package catalyst + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/params" +) + +func startSimulatedBeaconEthService(t *testing.T, genesis *core.Genesis) (*node.Node, *eth.Ethereum, *SimulatedBeacon) { + t.Helper() + + n, err := node.New(&node.Config{ + P2P: p2p.Config{ + ListenAddr: "127.0.0.1:8545", + NoDiscovery: true, + MaxPeers: 0, + }, + }) + if err != nil { + t.Fatal("can't create node:", err) + } + + ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256, Miner: miner.DefaultConfig} + ethservice, err := eth.New(n, ethcfg) + if err != nil { + t.Fatal("can't create eth service:", err) + } + + simBeacon, err := NewSimulatedBeacon(1, ethservice) + if err != nil { + t.Fatal("can't create simulated beacon:", err) + } + + n.RegisterLifecycle(simBeacon) + + if err := n.Start(); err != nil { + t.Fatal("can't start node:", err) + } + + ethservice.SetSynced() + return n, ethservice, simBeacon +} + +// send 20 transactions, >10 withdrawals and ensure they are included in order +// send enough transactions to fill multiple blocks +func TestSimulatedBeaconSendWithdrawals(t *testing.T) { + var withdrawals []types.Withdrawal + txs := make(map[common.Hash]*types.Transaction) + + var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + ) + + // short period (1 second) for testing purposes + var gasLimit uint64 = 10_000_000 + genesis := core.DeveloperGenesisBlock(gasLimit, &testAddr) + node, ethService, mock := startSimulatedBeaconEthService(t, genesis) + _ = mock + defer node.Close() + + chainHeadCh := make(chan core.ChainHeadEvent, 10) + subscription := ethService.BlockChain().SubscribeChainHeadEvent(chainHeadCh) + defer subscription.Unsubscribe() + + // generate some withdrawals + for i := 0; i < 20; i++ { + withdrawals = append(withdrawals, types.Withdrawal{Index: uint64(i)}) + if err := mock.withdrawals.add(&withdrawals[i]); err != nil { + t.Fatal("addWithdrawal failed", err) + } + } + + // generate a bunch of transactions + signer := types.NewEIP155Signer(ethService.BlockChain().Config().ChainID) + for i := 0; i < 20; i++ { + tx, err := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) + if err != nil { + t.Fatalf("error signing transaction, err=%v", err) + } + txs[tx.Hash()] = tx + + if err := ethService.APIBackend.SendTx(context.Background(), tx); err != nil { + t.Fatal("SendTx failed", err) + } + } + + includedTxs := make(map[common.Hash]struct{}) + var includedWithdrawals []uint64 + + timer := time.NewTimer(12 * time.Second) + for { + select { + case evt := <-chainHeadCh: + for _, includedTx := range evt.Block.Transactions() { + includedTxs[includedTx.Hash()] = struct{}{} + } + for _, includedWithdrawal := range evt.Block.Withdrawals() { + includedWithdrawals = append(includedWithdrawals, includedWithdrawal.Index) + } + + // ensure all withdrawals/txs included. this will take two blocks b/c number of withdrawals > 10 + if len(includedTxs) == len(txs) && len(includedWithdrawals) == len(withdrawals) && evt.Block.Number().Cmp(big.NewInt(2)) == 0 { + return + } + case <-timer.C: + t.Fatal("timed out without including all withdrawals/txs") + } + } +} diff --git a/eth/catalyst/tester.go b/eth/catalyst/tester.go new file mode 100644 index 0000000..0922ac0 --- /dev/null +++ b/eth/catalyst/tester.go @@ -0,0 +1,97 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package catalyst + +import ( + "sync" + "time" + + "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" + "github.com/ethereum/go-ethereum/node" +) + +// FullSyncTester is an auxiliary service that allows Geth to perform 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 { + 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, target common.Hash) (*FullSyncTester, error) { + cl := &FullSyncTester{ + stack: stack, + backend: backend, + target: target, + closed: make(chan struct{}), + } + stack.RegisterLifecycle(cl) + return cl, nil +} + +// Start launches the beacon sync with provided sync target. +func (tester *FullSyncTester) Start() error { + tester.wg.Add(1) + 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: + // 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 + } + + case <-tester.closed: + return + } + } + }() + return nil +} + +// Stop stops the full-sync tester to stop all background activities. +// This function can only be called for one time. +func (tester *FullSyncTester) Stop() error { + close(tester.closed) + tester.wg.Wait() + return nil +} diff --git a/eth/downloader/api.go b/eth/downloader/api.go new file mode 100644 index 0000000..ac17567 --- /dev/null +++ b/eth/downloader/api.go @@ -0,0 +1,203 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package downloader + +import ( + "context" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/rpc" +) + +// DownloaderAPI provides an API which gives information about the current +// synchronisation status. It offers only methods that operates on data that +// can be available to anyone without security risks. +type DownloaderAPI struct { + d *Downloader + chain *core.BlockChain + mux *event.TypeMux + installSyncSubscription chan chan interface{} + uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest +} + +// NewDownloaderAPI creates a new DownloaderAPI. The API has an internal event loop that +// listens for events from the downloader through the global event mux. In case it receives one of +// these events it broadcasts it to all syncing subscriptions that are installed through the +// installSyncSubscription channel. +func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *DownloaderAPI { + api := &DownloaderAPI{ + d: d, + chain: chain, + mux: m, + installSyncSubscription: make(chan chan interface{}), + uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest), + } + go api.eventLoop() + return api +} + +// eventLoop runs a loop until the event mux closes. It will install and uninstall +// new sync subscriptions and broadcasts sync status updates to the installed sync +// subscriptions. +// +// The sync status pushed to subscriptions can be a stream like: +// >>> {Syncing: true, Progress: {...}} +// >>> {false} +// +// If the node is already synced up, then only a single event subscribers will +// receive is {false}. +func (api *DownloaderAPI) eventLoop() { + var ( + sub = api.mux.Subscribe(StartEvent{}) + syncSubscriptions = make(map[chan interface{}]struct{}) + checkInterval = time.Second * 60 + checkTimer = time.NewTimer(checkInterval) + + // status flags + started bool + done bool + + getProgress = func() ethereum.SyncProgress { + prog := api.d.Progress() + if txProg, err := api.chain.TxIndexProgress(); err == nil { + prog.TxIndexFinishedBlocks = txProg.Indexed + prog.TxIndexRemainingBlocks = txProg.Remaining + } + return prog + } + ) + defer checkTimer.Stop() + + for { + select { + case i := <-api.installSyncSubscription: + syncSubscriptions[i] = struct{}{} + if done { + i <- false + } + case u := <-api.uninstallSyncSubscription: + delete(syncSubscriptions, u.c) + close(u.uninstalled) + case event := <-sub.Chan(): + if event == nil { + return + } + switch event.Data.(type) { + case StartEvent: + started = true + } + case <-checkTimer.C: + if !started { + checkTimer.Reset(checkInterval) + continue + } + prog := getProgress() + if !prog.Done() { + notification := &SyncingResult{ + Syncing: true, + Status: prog, + } + for c := range syncSubscriptions { + c <- notification + } + checkTimer.Reset(checkInterval) + continue + } + for c := range syncSubscriptions { + c <- false + } + done = true + } + } +} + +// Syncing provides information when this node starts synchronising with the Ethereum network and when it's finished. +func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + rpcSub := notifier.CreateSubscription() + + go func() { + statuses := make(chan interface{}) + sub := api.SubscribeSyncStatus(statuses) + defer sub.Unsubscribe() + + for { + select { + case status := <-statuses: + notifier.Notify(rpcSub.ID, status) + case <-rpcSub.Err(): + return + } + } + }() + + return rpcSub, nil +} + +// SyncingResult provides information about the current synchronisation status for this node. +type SyncingResult struct { + Syncing bool `json:"syncing"` + Status ethereum.SyncProgress `json:"status"` +} + +// uninstallSyncSubscriptionRequest uninstalls a syncing subscription in the API event loop. +type uninstallSyncSubscriptionRequest struct { + c chan interface{} + uninstalled chan interface{} +} + +// SyncStatusSubscription represents a syncing subscription. +type SyncStatusSubscription struct { + api *DownloaderAPI // register subscription in event loop of this api instance + c chan interface{} // channel where events are broadcasted to + unsubOnce sync.Once // make sure unsubscribe logic is executed once +} + +// Unsubscribe uninstalls the subscription from the DownloadAPI event loop. +// The status channel that was passed to subscribeSyncStatus isn't used anymore +// after this method returns. +func (s *SyncStatusSubscription) Unsubscribe() { + s.unsubOnce.Do(func() { + req := uninstallSyncSubscriptionRequest{s.c, make(chan interface{})} + s.api.uninstallSyncSubscription <- &req + + for { + select { + case <-s.c: + // drop new status events until uninstall confirmation + continue + case <-req.uninstalled: + return + } + } + }) +} + +// SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates. +// The given channel must receive interface values, the result can either. +func (api *DownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription { + api.installSyncSubscription <- status + return &SyncStatusSubscription{api: api, c: status} +} diff --git a/eth/downloader/beacondevsync.go b/eth/downloader/beacondevsync.go new file mode 100644 index 0000000..9a38fed --- /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/beaconsync.go b/eth/downloader/beaconsync.go new file mode 100644 index 0000000..57c6eee --- /dev/null +++ b/eth/downloader/beaconsync.go @@ -0,0 +1,394 @@ +// 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 downloader + +import ( + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// beaconBackfiller is the chain and state backfilling that can be commenced once +// the skeleton syncer has successfully reverse downloaded all the headers up to +// the genesis block or an existing header in the database. Its operation is fully +// directed by the skeleton sync's head/tail events. +type beaconBackfiller struct { + downloader *Downloader // Downloader to direct via this callback implementation + syncMode SyncMode // Sync mode to use for backfilling the skeleton chains + success func() // Callback to run on successful sync cycle completion + filling bool // Flag whether the downloader is backfilling or not + filled *types.Header // Last header filled by the last terminated sync loop + started chan struct{} // Notification channel whether the downloader inited + lock sync.Mutex // Mutex protecting the sync lock +} + +// newBeaconBackfiller is a helper method to create the backfiller. +func newBeaconBackfiller(dl *Downloader, success func()) backfiller { + return &beaconBackfiller{ + downloader: dl, + success: success, + } +} + +// suspend cancels any background downloader threads and returns the last header +// that has been successfully backfilled (potentially in a previous run), or the +// genesis. +func (b *beaconBackfiller) suspend() *types.Header { + // If no filling is running, don't waste cycles + b.lock.Lock() + filling := b.filling + filled := b.filled + started := b.started + b.lock.Unlock() + + if !filling { + return filled // Return the filled header on the previous sync completion + } + // A previous filling should be running, though it may happen that it hasn't + // yet started (being done on a new goroutine). Many concurrent beacon head + // announcements can lead to sync start/stop thrashing. In that case we need + // to wait for initialization before we can safely cancel it. It is safe to + // read this channel multiple times, it gets closed on startup. + <-started + + // Now that we're sure the downloader successfully started up, we can cancel + // it safely without running the risk of data races. + b.downloader.Cancel() + + // Sync cycle was just terminated, retrieve and return the last filled header. + // Can't use `filled` as that contains a stale value from before cancellation. + return b.downloader.blockchain.CurrentSnapBlock() +} + +// resume starts the downloader threads for backfilling state and chain data. +func (b *beaconBackfiller) resume() { + b.lock.Lock() + if b.filling { + // If a previous filling cycle is still running, just ignore this start + // request. // TODO(karalabe): We should make this channel driven + b.lock.Unlock() + return + } + b.filling = true + b.filled = nil + b.started = make(chan struct{}) + mode := b.syncMode + b.lock.Unlock() + + // Start the backfilling on its own thread since the downloader does not have + // its own lifecycle runloop. + go func() { + // Set the backfiller to non-filling when download completes + defer func() { + b.lock.Lock() + b.filling = false + b.filled = b.downloader.blockchain.CurrentSnapBlock() + b.lock.Unlock() + }() + // If the downloader fails, report an error as in beacon chain mode there + // should be no errors as long as the chain we're syncing to is valid. + if err := b.downloader.synchronise(mode, b.started); err != nil { + log.Error("Beacon backfilling failed", "err", err) + return + } + // Synchronization succeeded. Since this happens async, notify the outer + // context to disable snap syncing and enable transaction propagation. + if b.success != nil { + b.success() + } + }() +} + +// setMode updates the sync mode from the current one to the requested one. If +// there's an active sync in progress, it will be cancelled and restarted. +func (b *beaconBackfiller) setMode(mode SyncMode) { + // Update the old sync mode and track if it was changed + b.lock.Lock() + updated := b.syncMode != mode + filling := b.filling + b.syncMode = mode + b.lock.Unlock() + + // If the sync mode was changed mid-sync, restart. This should never ever + // really happen, we just handle it to detect programming errors. + if !updated || !filling { + return + } + log.Error("Downloader sync mode changed mid-run", "old", mode.String(), "new", mode.String()) + b.suspend() + b.resume() +} + +// SetBadBlockCallback sets the callback to run when a bad block is hit by the +// block processor. This method is not thread safe and should be set only once +// on startup before system events are fired. +func (d *Downloader) SetBadBlockCallback(onBadBlock badBlockFn) { + d.badBlock = onBadBlock +} + +// BeaconSync is the post-merge version of the chain synchronization, where the +// chain is not downloaded from genesis onward, rather from trusted head announces +// backwards. +// +// Internally backfilling and state sync is done the same way, but the header +// retrieval and scheduling is replaced. +func (d *Downloader) BeaconSync(mode SyncMode, head *types.Header, final *types.Header) error { + return d.beaconSync(mode, head, final, true) +} + +// BeaconExtend is an optimistic version of BeaconSync, where an attempt is made +// to extend the current beacon chain with a new header, but in case of a mismatch, +// the old sync will not be terminated and reorged, rather the new head is dropped. +// +// This is useful if a beacon client is feeding us large chunks of payloads to run, +// but is not setting the head after each. +func (d *Downloader) BeaconExtend(mode SyncMode, head *types.Header) error { + return d.beaconSync(mode, head, nil, false) +} + +// beaconSync is the post-merge version of the chain synchronization, where the +// chain is not downloaded from genesis onward, rather from trusted head announces +// backwards. +// +// Internally backfilling and state sync is done the same way, but the header +// retrieval and scheduling is replaced. +func (d *Downloader) beaconSync(mode SyncMode, head *types.Header, final *types.Header, force bool) error { + // When the downloader starts a sync cycle, it needs to be aware of the sync + // mode to use (full, snap). To keep the skeleton chain oblivious, inject the + // mode into the backfiller directly. + // + // Super crazy dangerous type cast. Should be fine (TM), we're only using a + // different backfiller implementation for skeleton tests. + d.skeleton.filler.(*beaconBackfiller).setMode(mode) + + // Signal the skeleton sync to switch to a new head, however it wants + if err := d.skeleton.Sync(head, final, force); err != nil { + return err + } + return nil +} + +// findBeaconAncestor tries to locate the common ancestor link of the local chain +// and the beacon chain just requested. In the general case when our node was in +// sync and on the correct chain, checking the top N links should already get us +// a match. In the rare scenario when we ended up on a long reorganisation (i.e. +// none of the head links match), we do a binary search to find the ancestor. +func (d *Downloader) findBeaconAncestor() (uint64, error) { + // Figure out the current local head position + var chainHead *types.Header + + switch d.getMode() { + case FullSync: + chainHead = d.blockchain.CurrentBlock() + case SnapSync: + chainHead = d.blockchain.CurrentSnapBlock() + default: + panic("unknown sync mode") + } + number := chainHead.Number.Uint64() + + // Retrieve the skeleton bounds and ensure they are linked to the local chain + beaconHead, beaconTail, _, err := d.skeleton.Bounds() + if err != nil { + // This is a programming error. The chain backfiller was called with an + // invalid beacon sync state. Ideally we would panic here, but erroring + // gives us at least a remote chance to recover. It's still a big fault! + log.Error("Failed to retrieve beacon bounds", "err", err) + return 0, err + } + var linked bool + switch d.getMode() { + case FullSync: + linked = d.blockchain.HasBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1) + case SnapSync: + linked = d.blockchain.HasFastBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1) + default: + panic("unknown sync mode") + } + if !linked { + // This is a programming error. The chain backfiller was called with a + // tail that's not linked to the local chain. Whilst this should never + // happen, there might be some weirdnesses if beacon sync backfilling + // races with the user (or beacon client) calling setHead. Whilst panic + // would be the ideal thing to do, it is safer long term to attempt a + // recovery and fix any noticed issue after the fact. + log.Error("Beacon sync linkup unavailable", "number", beaconTail.Number.Uint64()-1, "hash", beaconTail.ParentHash) + return 0, fmt.Errorf("beacon linkup unavailable locally: %d [%x]", beaconTail.Number.Uint64()-1, beaconTail.ParentHash) + } + // Binary search to find the ancestor + start, end := beaconTail.Number.Uint64()-1, number + if number := beaconHead.Number.Uint64(); end > number { + // This shouldn't really happen in a healthy network, but if the consensus + // clients feeds us a shorter chain as the canonical, we should not attempt + // to access non-existent skeleton items. + log.Warn("Beacon head lower than local chain", "beacon", number, "local", end) + end = number + } + for start+1 < end { + // Split our chain interval in two, and request the hash to cross check + check := (start + end) / 2 + + h := d.skeleton.Header(check) + n := h.Number.Uint64() + + var known bool + switch d.getMode() { + case FullSync: + known = d.blockchain.HasBlock(h.Hash(), n) + case SnapSync: + known = d.blockchain.HasFastBlock(h.Hash(), n) + default: + panic("unknown sync mode") + } + if !known { + end = check + continue + } + start = check + } + return start, nil +} + +// fetchHeaders feeds skeleton headers to the downloader queue for scheduling +// until sync errors or is finished. +func (d *Downloader) fetchHeaders(from uint64) error { + var head *types.Header + _, tail, _, err := d.skeleton.Bounds() + if err != nil { + return err + } + // A part of headers are not in the skeleton space, try to resolve + // them from the local chain. Note the range should be very short + // and it should only happen when there are less than 64 post-merge + // blocks in the network. + var localHeaders []*types.Header + if from < tail.Number.Uint64() { + count := tail.Number.Uint64() - from + if count > uint64(fsMinFullBlocks) { + return fmt.Errorf("invalid origin (%d) of beacon sync (%d)", from, tail.Number) + } + localHeaders = d.readHeaderRange(tail, int(count)) + log.Warn("Retrieved beacon headers from local", "from", from, "count", count) + } + fsHeaderContCheckTimer := time.NewTimer(fsHeaderContCheck) + defer fsHeaderContCheckTimer.Stop() + + for { + // Some beacon headers might have appeared since the last cycle, make + // sure we're always syncing to all available ones + head, _, _, err = d.skeleton.Bounds() + if err != nil { + return err + } + // If the pivot became stale (older than 2*64-8 (bit of wiggle room)), + // move it ahead to HEAD-64 + d.pivotLock.Lock() + if d.pivotHeader != nil { + if head.Number.Uint64() > d.pivotHeader.Number.Uint64()+2*uint64(fsMinFullBlocks)-8 { + // Retrieve the next pivot header, either from skeleton chain + // or the filled chain + number := head.Number.Uint64() - uint64(fsMinFullBlocks) + + log.Warn("Pivot seemingly stale, moving", "old", d.pivotHeader.Number, "new", number) + if d.pivotHeader = d.skeleton.Header(number); d.pivotHeader == nil { + if number < tail.Number.Uint64() { + dist := tail.Number.Uint64() - number + if len(localHeaders) >= int(dist) { + d.pivotHeader = localHeaders[dist-1] + log.Warn("Retrieved pivot header from local", "number", d.pivotHeader.Number, "hash", d.pivotHeader.Hash(), "latest", head.Number, "oldest", tail.Number) + } + } + } + // Print an error log and return directly in case the pivot header + // is still not found. It means the skeleton chain is not linked + // correctly with local chain. + if d.pivotHeader == nil { + log.Error("Pivot header is not found", "number", number) + d.pivotLock.Unlock() + return errNoPivotHeader + } + // Write out the pivot into the database so a rollback beyond + // it will reenable snap sync and update the state root that + // the state syncer will be downloading + rawdb.WriteLastPivotNumber(d.stateDB, d.pivotHeader.Number.Uint64()) + } + } + d.pivotLock.Unlock() + + // Retrieve a batch of headers and feed it to the header processor + var ( + headers = make([]*types.Header, 0, maxHeadersProcess) + hashes = make([]common.Hash, 0, maxHeadersProcess) + ) + for i := 0; i < maxHeadersProcess && from <= head.Number.Uint64(); i++ { + header := d.skeleton.Header(from) + + // The header is not found in skeleton space, try to find it in local chain. + if header == nil && from < tail.Number.Uint64() { + dist := tail.Number.Uint64() - from + if len(localHeaders) >= int(dist) { + header = localHeaders[dist-1] + } + } + // The header is still missing, the beacon sync is corrupted and bail out + // the error here. + if header == nil { + return fmt.Errorf("missing beacon header %d", from) + } + headers = append(headers, header) + hashes = append(hashes, headers[i].Hash()) + from++ + } + if len(headers) > 0 { + log.Trace("Scheduling new beacon headers", "count", len(headers), "from", from-uint64(len(headers))) + select { + case d.headerProcCh <- &headerTask{ + headers: headers, + hashes: hashes, + }: + case <-d.cancelCh: + return errCanceled + } + } + // If we still have headers to import, loop and keep pushing them + if from <= head.Number.Uint64() { + continue + } + // If the pivot block is committed, signal header sync termination + if d.committed.Load() { + select { + case d.headerProcCh <- nil: + return nil + case <-d.cancelCh: + return errCanceled + } + } + // State sync still going, wait a bit for new headers and retry + log.Trace("Pivot not yet committed, waiting...") + fsHeaderContCheckTimer.Reset(fsHeaderContCheck) + select { + case <-fsHeaderContCheckTimer.C: + case <-d.cancelCh: + return errCanceled + } + } +} diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go new file mode 100644 index 0000000..d147414 --- /dev/null +++ b/eth/downloader/downloader.go @@ -0,0 +1,1108 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package downloader contains the manual full chain synchronisation. +package downloader + +import ( + "errors" + "fmt" + "math/big" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "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/triedb" +) + +var ( + MaxBlockFetch = 128 // Number of blocks to be fetched per retrieval request + MaxHeaderFetch = 192 // Number of block headers to be fetched per retrieval request + MaxReceiptFetch = 256 // Number of transaction receipts to allow fetching per request + + maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) + maxHeadersProcess = 2048 // Number of header download results to import at once into the chain + maxResultsProcess = 2048 // Number of content download results to import at once into the chain + fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) + + reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs + + fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected + fsHeaderContCheck = 3 * time.Second // Time interval to check for header continuations during state download + fsMinFullBlocks = 64 // Number of blocks to retrieve fully even in snap sync +) + +var ( + errBusy = errors.New("busy") + errBadPeer = errors.New("action from bad peer ignored") + + errTimeout = errors.New("timeout") + errInvalidChain = errors.New("retrieved hash chain is invalid") + errInvalidBody = errors.New("retrieved block body is invalid") + errInvalidReceipt = errors.New("retrieved receipt is invalid") + errCancelStateFetch = errors.New("state data download canceled (requested)") + errCancelContentProcessing = errors.New("content processing canceled (requested)") + errCanceled = errors.New("syncing canceled (requested)") + errNoPivotHeader = errors.New("pivot header is not found") +) + +// peerDropFn is a callback type for dropping a peer detected as malicious. +type peerDropFn func(id string) + +// badBlockFn is a callback for the async beacon sync to notify the caller that +// the origin header requested to sync to, produced a chain with a bad block. +type badBlockFn func(invalid *types.Header, origin *types.Header) + +// headerTask is a set of downloaded headers to queue along with their precomputed +// hashes to avoid constant rehashing. +type headerTask struct { + headers []*types.Header + hashes []common.Hash +} + +type Downloader struct { + mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode + mux *event.TypeMux // Event multiplexer to announce sync operation events + + queue *queue // Scheduler for selecting the hashes to download + peers *peerSet // Set of active peers from which download can proceed + + stateDB ethdb.Database // Database to state sync into (and deduplicate via) + + // Statistics + syncStatsChainOrigin uint64 // Origin block number where syncing started at + syncStatsChainHeight uint64 // Highest block number known when syncing started + syncStatsLock sync.RWMutex // Lock protecting the sync stats fields + + blockchain BlockChain + + // Callbacks + dropPeer peerDropFn // Drops a peer for misbehaving + badBlock badBlockFn // Reports a block as rejected by the chain + + // Status + synchronising atomic.Bool + notified atomic.Bool + committed atomic.Bool + ancientLimit uint64 // The maximum block number which can be regarded as ancient data. + + // Channels + headerProcCh chan *headerTask // Channel to feed the header processor new tasks + + // Skeleton sync + skeleton *skeleton // Header skeleton to backfill the chain with (eth2 mode) + + // State sync + pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root + pivotLock sync.RWMutex // Lock protecting pivot header reads from updates + + SnapSyncer *snap.Syncer // TODO(karalabe): make private! hack for now + stateSyncStart chan *stateSync + + // Cancellation and termination + cancelCh chan struct{} // Channel to cancel mid-flight syncs + cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers + cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited. + + quitCh chan struct{} // Quit channel to signal termination + quitLock sync.Mutex // Lock to prevent double closes + + // Testing hooks + bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch + receiptFetchHook func([]*types.Header) // Method to call upon starting a receipt fetch + chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) + + // Progress reporting metrics + syncStartBlock uint64 // Head snap block when Geth was started + syncStartTime time.Time // Time instance when chain sync started + syncLogTime time.Time // Time instance when status was last reported +} + +// BlockChain encapsulates functions required to sync a (full or snap) blockchain. +type BlockChain interface { + // HasHeader verifies a header's presence in the local chain. + HasHeader(common.Hash, uint64) bool + + // GetHeaderByHash retrieves a header from the local chain. + GetHeaderByHash(common.Hash) *types.Header + + // CurrentHeader retrieves the head header from the local chain. + CurrentHeader() *types.Header + + // GetTd returns the total difficulty of a local block. + GetTd(common.Hash, uint64) *big.Int + + // InsertHeaderChain inserts a batch of headers into the local chain. + InsertHeaderChain([]*types.Header) (int, error) + + // SetHead rewinds the local chain to a new head. + SetHead(uint64) error + + // HasBlock verifies a block's presence in the local chain. + HasBlock(common.Hash, uint64) bool + + // HasFastBlock verifies a snap block's presence in the local chain. + HasFastBlock(common.Hash, uint64) bool + + // GetBlockByHash retrieves a block from the local chain. + GetBlockByHash(common.Hash) *types.Block + + // CurrentBlock retrieves the head block from the local chain. + CurrentBlock() *types.Header + + // CurrentSnapBlock retrieves the head snap block from the local chain. + CurrentSnapBlock() *types.Header + + // SnapSyncCommitHead directly commits the head block to a certain entity. + SnapSyncCommitHead(common.Hash) error + + // InsertChain inserts a batch of blocks into the local chain. + InsertChain(types.Blocks) (int, error) + + // InsertReceiptChain inserts a batch of receipts into the local chain. + InsertReceiptChain(types.Blocks, []types.Receipts, uint64) (int, error) + + // Snapshots returns the blockchain snapshot tree to paused it during sync. + Snapshots() *snapshot.Tree + + // TrieDB retrieves the low level trie database used for interacting + // with trie nodes. + TrieDB() *triedb.Database +} + +// New creates a new downloader to fetch hashes and blocks from remote peers. +func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader { + dl := &Downloader{ + stateDB: stateDb, + mux: mux, + queue: newQueue(blockCacheMaxItems, blockCacheInitialItems), + peers: newPeerSet(), + blockchain: chain, + dropPeer: dropPeer, + headerProcCh: make(chan *headerTask, 1), + quitCh: make(chan struct{}), + SnapSyncer: snap.NewSyncer(stateDb, chain.TrieDB().Scheme()), + stateSyncStart: make(chan *stateSync), + syncStartBlock: chain.CurrentSnapBlock().Number.Uint64(), + } + // Create the post-merge skeleton syncer and start the process + dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success)) + + go dl.stateFetcher() + return dl +} + +// Progress retrieves the synchronisation boundaries, specifically the origin +// block where synchronisation started at (may have failed/suspended); the block +// or header sync is currently at; and the latest known block which the sync targets. +// +// In addition, during the state download phase of snap synchronisation the number +// of processed and the total number of known states are also returned. Otherwise +// these are zero. +func (d *Downloader) Progress() ethereum.SyncProgress { + // Lock the current stats and return the progress + d.syncStatsLock.RLock() + defer d.syncStatsLock.RUnlock() + + current := uint64(0) + mode := d.getMode() + switch mode { + case FullSync: + current = d.blockchain.CurrentBlock().Number.Uint64() + case SnapSync: + current = d.blockchain.CurrentSnapBlock().Number.Uint64() + default: + log.Error("Unknown downloader mode", "mode", mode) + } + progress, pending := d.SnapSyncer.Progress() + + return ethereum.SyncProgress{ + StartingBlock: d.syncStatsChainOrigin, + CurrentBlock: current, + HighestBlock: d.syncStatsChainHeight, + SyncedAccounts: progress.AccountSynced, + SyncedAccountBytes: uint64(progress.AccountBytes), + SyncedBytecodes: progress.BytecodeSynced, + SyncedBytecodeBytes: uint64(progress.BytecodeBytes), + SyncedStorage: progress.StorageSynced, + SyncedStorageBytes: uint64(progress.StorageBytes), + HealedTrienodes: progress.TrienodeHealSynced, + HealedTrienodeBytes: uint64(progress.TrienodeHealBytes), + HealedBytecodes: progress.BytecodeHealSynced, + HealedBytecodeBytes: uint64(progress.BytecodeHealBytes), + HealingTrienodes: pending.TrienodeHeal, + HealingBytecode: pending.BytecodeHeal, + } +} + +// RegisterPeer injects a new download peer into the set of block source to be +// used for fetching hashes and blocks from. +func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:8]) + } + logger.Trace("Registering sync peer") + if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil { + logger.Error("Failed to register sync peer", "err", err) + return err + } + return nil +} + +// UnregisterPeer remove a peer from the known list, preventing any action from +// the specified peer. An effort is also made to return any pending fetches into +// the queue. +func (d *Downloader) UnregisterPeer(id string) error { + // Unregister the peer from the active peer set and revoke any fetch tasks + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:8]) + } + logger.Trace("Unregistering sync peer") + if err := d.peers.Unregister(id); err != nil { + logger.Error("Failed to unregister sync peer", "err", err) + return err + } + d.queue.Revoke(id) + + return nil +} + +// synchronise will select the peer and use it for synchronising. If an empty string is given +// it will use the best peer possible and synchronize if its TD is higher than our own. If any of the +// checks fail an error will be returned. This method is synchronous +func (d *Downloader) synchronise(mode SyncMode, beaconPing chan struct{}) error { + // The beacon header syncer is async. It will start this synchronization and + // will continue doing other tasks. However, if synchronization needs to be + // cancelled, the syncer needs to know if we reached the startup point (and + // inited the cancel channel) or not yet. Make sure that we'll signal even in + // case of a failure. + if beaconPing != nil { + defer func() { + select { + case <-beaconPing: // already notified + default: + close(beaconPing) // weird exit condition, notify that it's safe to cancel (the nothing) + } + }() + } + // Make sure only one goroutine is ever allowed past this point at once + if !d.synchronising.CompareAndSwap(false, true) { + return errBusy + } + defer d.synchronising.Store(false) + + // Post a user notification of the sync (only once per session) + if d.notified.CompareAndSwap(false, true) { + log.Info("Block synchronisation started") + } + if mode == SnapSync { + // 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 + snapshots.Disable() + } + } + // Reset the queue, peer set and wake channels to clean any internal leftover state + d.queue.Reset(blockCacheMaxItems, blockCacheInitialItems) + d.peers.Reset() + + for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} { + select { + case <-ch: + default: + } + } + for empty := false; !empty; { + select { + case <-d.headerProcCh: + default: + empty = true + } + } + // Create cancel channel for aborting mid-flight and mark the master peer + d.cancelLock.Lock() + d.cancelCh = make(chan struct{}) + d.cancelLock.Unlock() + + defer d.Cancel() // No matter what, we can't leave the cancel channel open + + // Atomically set the requested sync mode + d.mode.Store(uint32(mode)) + + if beaconPing != nil { + close(beaconPing) + } + return d.syncToHead() +} + +func (d *Downloader) getMode() SyncMode { + return SyncMode(d.mode.Load()) +} + +// syncToHead starts a block synchronization based on the hash chain from +// the specified head hash. +func (d *Downloader) syncToHead() (err error) { + d.mux.Post(StartEvent{}) + defer func() { + // reset on error + if err != nil { + d.mux.Post(FailedEvent{err}) + } else { + latest := d.blockchain.CurrentHeader() + d.mux.Post(DoneEvent{latest}) + } + }() + mode := d.getMode() + + log.Debug("Backfilling with the network", "mode", mode) + defer func(start time.Time) { + log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start))) + }(time.Now()) + + // Look up the sync boundaries: the common ancestor and the target block + var latest, pivot, final *types.Header + latest, _, final, err = d.skeleton.Bounds() + if err != nil { + return err + } + if latest.Number.Uint64() > uint64(fsMinFullBlocks) { + number := latest.Number.Uint64() - uint64(fsMinFullBlocks) + + // Retrieve the pivot header from the skeleton chain segment but + // fallback to local chain if it's not found in skeleton space. + if pivot = d.skeleton.Header(number); pivot == nil { + _, oldest, _, _ := d.skeleton.Bounds() // error is already checked + if number < oldest.Number.Uint64() { + count := int(oldest.Number.Uint64() - number) // it's capped by fsMinFullBlocks + headers := d.readHeaderRange(oldest, count) + if len(headers) == count { + pivot = headers[len(headers)-1] + log.Warn("Retrieved pivot header from local", "number", pivot.Number, "hash", pivot.Hash(), "latest", latest.Number, "oldest", oldest.Number) + } + } + } + // Print an error log and return directly in case the pivot header + // is still not found. It means the skeleton chain is not linked + // correctly with local chain. + if pivot == nil { + log.Error("Pivot header is not found", "number", number) + return errNoPivotHeader + } + } + // If no pivot block was returned, the head is below the min full block + // threshold (i.e. new chain). In that case we won't really snap sync + // anyway, but still need a valid pivot block to avoid some code hitting + // nil panics on access. + if mode == SnapSync && pivot == nil { + pivot = d.blockchain.CurrentBlock() + } + height := latest.Number.Uint64() + + // In beacon mode, use the skeleton chain for the ancestor lookup + origin, err := d.findBeaconAncestor() + if err != nil { + return err + } + d.syncStatsLock.Lock() + if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin { + d.syncStatsChainOrigin = origin + } + d.syncStatsChainHeight = height + d.syncStatsLock.Unlock() + + // Ensure our origin point is below any snap sync pivot point + if mode == SnapSync { + if height <= uint64(fsMinFullBlocks) { + origin = 0 + } else { + pivotNumber := pivot.Number.Uint64() + if pivotNumber <= origin { + origin = pivotNumber - 1 + } + // Write out the pivot into the database so a rollback beyond it will + // reenable snap sync + rawdb.WriteLastPivotNumber(d.stateDB, pivotNumber) + } + } + d.committed.Store(true) + if mode == SnapSync && pivot.Number.Uint64() != 0 { + d.committed.Store(false) + } + if mode == SnapSync { + // Set the ancient data limitation. If we are running snap sync, all block + // data older than ancientLimit will be written to the ancient store. More + // recent data will be written to the active database and will wait for the + // freezer to migrate. + // + // If the network is post-merge, use either the last announced finalized + // block as the ancient limit, or if we haven't yet received one, the head- + // a max fork ancestry limit. One quirky case if we've already passed the + // finalized block, in which case the skeleton.Bounds will return nil and + // we'll revert to head - 90K. That's fine, we're finishing sync anyway. + // + // 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 dropped. TODO(karalabe). + // + // Beacon sync, use the latest finalized block as the ancient limit + // or a reasonable height if no finalized block is yet announced. + if final != nil { + d.ancientLimit = final.Number.Uint64() + } else if height > fullMaxForkAncestry+1 { + d.ancientLimit = height - fullMaxForkAncestry - 1 + } else { + d.ancientLimit = 0 + } + frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. + + // If a part of blockchain data has already been written into active store, + // disable the ancient style insertion explicitly. + if origin >= frozen && frozen != 0 { + d.ancientLimit = 0 + log.Info("Disabling direct-ancient mode", "origin", origin, "ancient", frozen-1) + } else if d.ancientLimit > 0 { + log.Debug("Enabling direct-ancient mode", "ancient", d.ancientLimit) + } + // Rewind the ancient store and blockchain if reorg happens. + if origin+1 < frozen { + if err := d.blockchain.SetHead(origin); err != nil { + return err + } + log.Info("Truncated excess ancient chain segment", "oldhead", frozen-1, "newhead", origin) + } + } + // Initiate the sync using a concurrent header and content retrieval algorithm + d.queue.Prepare(origin+1, mode) + + // In beacon mode, headers are served by the skeleton syncer + fetchers := []func() error{ + func() error { return d.fetchHeaders(origin + 1) }, // Headers are always retrieved + func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and snap sync + func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during snap sync + func() error { return d.processHeaders(origin + 1) }, + } + if mode == SnapSync { + d.pivotLock.Lock() + d.pivotHeader = pivot + d.pivotLock.Unlock() + + fetchers = append(fetchers, func() error { return d.processSnapSyncContent() }) + } else if mode == FullSync { + fetchers = append(fetchers, func() error { return d.processFullSyncContent() }) + } + return d.spawnSync(fetchers) +} + +// spawnSync runs d.process and all given fetcher functions to completion in +// separate goroutines, returning the first error that appears. +func (d *Downloader) spawnSync(fetchers []func() error) error { + errc := make(chan error, len(fetchers)) + d.cancelWg.Add(len(fetchers)) + for _, fn := range fetchers { + fn := fn + go func() { defer d.cancelWg.Done(); errc <- fn() }() + } + // Wait for the first error, then terminate the others. + var err error + for i := 0; i < len(fetchers); i++ { + if i == len(fetchers)-1 { + // Close the queue when all fetchers have exited. + // This will cause the block processor to end when + // it has processed the queue. + d.queue.Close() + } + if got := <-errc; got != nil { + err = got + if got != errCanceled { + break // receive a meaningful error, bubble it up + } + } + } + d.queue.Close() + d.Cancel() + return err +} + +// cancel aborts all of the operations and resets the queue. However, cancel does +// not wait for the running download goroutines to finish. This method should be +// used when cancelling the downloads from inside the downloader. +func (d *Downloader) cancel() { + // Close the current cancel channel + d.cancelLock.Lock() + defer d.cancelLock.Unlock() + + if d.cancelCh != nil { + select { + case <-d.cancelCh: + // Channel was already closed + default: + close(d.cancelCh) + } + } +} + +// Cancel aborts all of the operations and waits for all download goroutines to +// finish before returning. +func (d *Downloader) Cancel() { + d.cancel() + d.cancelWg.Wait() +} + +// Terminate interrupts the downloader, canceling all pending operations. +// The downloader cannot be reused after calling Terminate. +func (d *Downloader) Terminate() { + // Close the termination channel (make sure double close is allowed) + d.quitLock.Lock() + select { + case <-d.quitCh: + default: + close(d.quitCh) + + // Terminate the internal beacon syncer + d.skeleton.Terminate() + } + d.quitLock.Unlock() + + // Cancel any pending download requests + d.Cancel() +} + +// fetchBodies iteratively downloads the scheduled block bodies, taking any +// available peers, reserving a chunk of blocks for each, waiting for delivery +// and also periodically checking for timeouts. +func (d *Downloader) fetchBodies(from uint64) error { + log.Debug("Downloading block bodies", "origin", from) + err := d.concurrentFetch((*bodyQueue)(d)) + + log.Debug("Block body download terminated", "err", err) + return err +} + +// fetchReceipts iteratively downloads the scheduled block receipts, taking any +// available peers, reserving a chunk of receipts for each, waiting for delivery +// and also periodically checking for timeouts. +func (d *Downloader) fetchReceipts(from uint64) error { + log.Debug("Downloading receipts", "origin", from) + err := d.concurrentFetch((*receiptQueue)(d)) + + log.Debug("Receipt download terminated", "err", err) + return err +} + +// processHeaders takes batches of retrieved headers from an input channel and +// keeps processing and scheduling them into the header chain and downloader's +// queue until the stream ends or a failure occurs. +func (d *Downloader) processHeaders(origin uint64) error { + var ( + mode = d.getMode() + timer = time.NewTimer(time.Second) + ) + defer timer.Stop() + + for { + select { + case <-d.cancelCh: + return errCanceled + + case task := <-d.headerProcCh: + // Terminate header processing if we synced up + if task == nil || len(task.headers) == 0 { + // Notify everyone that headers are fully processed + for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} { + select { + case ch <- false: + case <-d.cancelCh: + } + } + return nil + } + // Otherwise split the chunk of headers into batches and process them + headers, hashes := task.headers, task.hashes + + for len(headers) > 0 { + // Terminate if something failed in between processing chunks + select { + case <-d.cancelCh: + return errCanceled + default: + } + // Select the next chunk of headers to import + limit := maxHeadersProcess + if limit > len(headers) { + limit = len(headers) + } + chunkHeaders := headers[:limit] + chunkHashes := hashes[:limit] + + // In case of header only syncing, validate the chunk immediately + if mode == SnapSync { + // Although the received headers might be all valid, a legacy + // PoW/PoA sync must not accept post-merge headers. Make sure + // that any transition is rejected at this point. + if len(chunkHeaders) > 0 { + if n, err := d.blockchain.InsertHeaderChain(chunkHeaders); err != nil { + 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) + } + } + } + // If we've reached the allowed number of pending headers, stall a bit + for d.queue.PendingBodies() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders { + timer.Reset(time.Second) + select { + case <-d.cancelCh: + return errCanceled + case <-timer.C: + } + } + // Otherwise insert the headers for content retrieval + inserts := d.queue.Schedule(chunkHeaders, chunkHashes, origin) + if len(inserts) != len(chunkHeaders) { + return fmt.Errorf("%w: stale headers", errBadPeer) + } + + headers = headers[limit:] + hashes = hashes[limit:] + origin += uint64(limit) + } + // Update the highest block number we know if a higher one is found. + d.syncStatsLock.Lock() + if d.syncStatsChainHeight < origin { + d.syncStatsChainHeight = origin - 1 + } + d.syncStatsLock.Unlock() + + // Signal the content downloaders of the availability of new tasks + for _, ch := range []chan bool{d.queue.blockWakeCh, d.queue.receiptWakeCh} { + select { + case ch <- true: + default: + } + } + } + } +} + +// processFullSyncContent takes fetch results from the queue and imports them into the chain. +func (d *Downloader) processFullSyncContent() error { + for { + results := d.queue.Results(true) + if len(results) == 0 { + return nil + } + if d.chainInsertHook != nil { + d.chainInsertHook(results) + } + if err := d.importBlockResults(results); err != nil { + return err + } + } +} + +func (d *Downloader) importBlockResults(results []*fetchResult) error { + // Check for any early termination requests + if len(results) == 0 { + return nil + } + select { + case <-d.quitCh: + return errCancelContentProcessing + default: + } + // Retrieve a batch of results to import + first, last := results[0].Header, results[len(results)-1].Header + log.Debug("Inserting downloaded chain", "items", len(results), + "firstnum", first.Number, "firsthash", first.Hash(), + "lastnum", last.Number, "lasthash", last.Hash(), + ) + blocks := make([]*types.Block, len(results)) + for i, result := range results { + blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body()) + } + // Downloaded blocks are always regarded as trusted after the + // transition. Because the downloaded chain is guided by the + // consensus-layer. + if index, err := d.blockchain.InsertChain(blocks); err != nil { + if index < len(results) { + log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) + + // In post-merge, notify the engine API of encountered bad chains + if d.badBlock != nil { + head, _, _, err := d.skeleton.Bounds() + if err != nil { + log.Error("Failed to retrieve beacon bounds for bad block reporting", "err", err) + } else { + d.badBlock(blocks[index].Header(), head) + } + } + } else { + // The InsertChain method in blockchain.go will sometimes return an out-of-bounds index, + // when it needs to preprocess blocks to import a sidechain. + // The importer will put together a new list of blocks to import, which is a superset + // of the blocks delivered from the downloader, and the indexing will be off. + log.Debug("Downloaded item processing failed on sidechain import", "index", index, "err", err) + } + return fmt.Errorf("%w: %v", errInvalidChain, err) + } + return nil +} + +// processSnapSyncContent takes fetch results from the queue and writes them to the +// database. It also controls the synchronisation of state nodes of the pivot block. +func (d *Downloader) processSnapSyncContent() error { + // Start syncing state of the reported head block. This should get us most of + // the state of the pivot block. + d.pivotLock.RLock() + sync := d.syncState(d.pivotHeader.Root) + d.pivotLock.RUnlock() + + defer func() { + // The `sync` object is replaced every time the pivot moves. We need to + // defer close the very last active one, hence the lazy evaluation vs. + // calling defer sync.Cancel() !!! + sync.Cancel() + }() + + closeOnErr := func(s *stateSync) { + if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled && err != snap.ErrCancelled { + d.queue.Close() // wake up Results + } + } + go closeOnErr(sync) + + // To cater for moving pivot points, track the pivot block and subsequently + // accumulated download results separately. + // + // 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 + timer = time.NewTimer(time.Second) + ) + defer timer.Stop() + + for { + // 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 d.committed.Load() { + d.reportSnapSyncProgress(true) + return sync.Cancel() + } + // If sync failed, stop + select { + case <-d.cancelCh: + sync.Cancel() + return errCanceled + default: + } + } + if d.chainInsertHook != nil { + d.chainInsertHook(results) + } + d.reportSnapSyncProgress(false) + + // If we haven't downloaded the pivot block yet, check pivot staleness + // notifications from the header downloader + d.pivotLock.RLock() + pivot := d.pivotHeader + d.pivotLock.RUnlock() + + if oldPivot == nil { // 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) + } + } + } 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 + // new pivot. + // + // Note, we have `reorgProtHeaderDelay` number of blocks withheld, Those + // need to be taken into account, otherwise we're detecting the pivot move + // late and will drop peers due to unavailable state!!! + if height := latest.Number.Uint64(); height >= pivot.Number.Uint64()+2*uint64(fsMinFullBlocks)-uint64(reorgProtHeaderDelay) { + log.Warn("Pivot became stale, moving", "old", pivot.Number.Uint64(), "new", height-uint64(fsMinFullBlocks)+uint64(reorgProtHeaderDelay)) + pivot = results[len(results)-1-fsMinFullBlocks+reorgProtHeaderDelay].Header // must exist as lower old pivot is uncommitted + + d.pivotLock.Lock() + d.pivotHeader = pivot + d.pivotLock.Unlock() + + // Write out the pivot into the database so a rollback beyond it will + // reenable snap sync + rawdb.WriteLastPivotNumber(d.stateDB, pivot.Number.Uint64()) + } + } + P, beforeP, afterP := splitAroundPivot(pivot.Number.Uint64(), results) + if err := d.commitSnapSyncData(beforeP, sync); err != nil { + return err + } + if P != nil { + // If new pivot block found, cancel old state retrieval and restart + if oldPivot != P { + sync.Cancel() + sync = d.syncState(P.Header.Root) + + go closeOnErr(sync) + oldPivot = P + } + // Wait for completion, occasionally checking for pivot staleness + timer.Reset(time.Second) + select { + case <-sync.done: + if sync.err != nil { + return sync.err + } + if err := d.commitPivotBlock(P); err != nil { + return err + } + oldPivot = nil + + case <-timer.C: + oldTail = afterP + continue + } + } + // Fast sync done, pivot commit done, full import + if err := d.importBlockResults(afterP); err != nil { + return err + } + } +} + +func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, before, after []*fetchResult) { + if len(results) == 0 { + return nil, nil, nil + } + if lastNum := results[len(results)-1].Header.Number.Uint64(); lastNum < pivot { + // the pivot is somewhere in the future + return nil, results, nil + } + // This can also be optimized, but only happens very seldom + for _, result := range results { + num := result.Header.Number.Uint64() + switch { + case num < pivot: + before = append(before, result) + case num == pivot: + p = result + default: + after = append(after, result) + } + } + return p, before, after +} + +func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *stateSync) error { + // Check for any early termination requests + if len(results) == 0 { + return nil + } + select { + case <-d.quitCh: + return errCancelContentProcessing + case <-stateSync.done: + if err := stateSync.Wait(); err != nil { + return err + } + default: + } + // Retrieve the batch of results to import + first, last := results[0].Header, results[len(results)-1].Header + log.Debug("Inserting snap-sync blocks", "items", len(results), + "firstnum", first.Number, "firsthash", first.Hash(), + "lastnumn", last.Number, "lasthash", last.Hash(), + ) + blocks := make([]*types.Block, len(results)) + receipts := make([]types.Receipts, len(results)) + for i, result := range results { + blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body()) + receipts[i] = result.Receipts + } + if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { + log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) + return fmt.Errorf("%w: %v", errInvalidChain, err) + } + return nil +} + +func (d *Downloader) commitPivotBlock(result *fetchResult) error { + block := types.NewBlockWithHeader(result.Header).WithBody(result.body()) + log.Debug("Committing snap sync pivot as new head", "number", block.Number(), "hash", block.Hash()) + + // Commit the pivot block as the new head, will require full sync from here on + if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{result.Receipts}, d.ancientLimit); err != nil { + return err + } + if err := d.blockchain.SnapSyncCommitHead(block.Hash()); err != nil { + return err + } + d.committed.Store(true) + return nil +} + +// DeliverSnapPacket is invoked from a peer's message handler when it transmits a +// data packet for the local node to consume. +func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) error { + switch packet := packet.(type) { + case *snap.AccountRangePacket: + hashes, accounts, err := packet.Unpack() + if err != nil { + return err + } + return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof) + + case *snap.StorageRangesPacket: + hashset, slotset := packet.Unpack() + return d.SnapSyncer.OnStorage(peer, packet.ID, hashset, slotset, packet.Proof) + + case *snap.ByteCodesPacket: + return d.SnapSyncer.OnByteCodes(peer, packet.ID, packet.Codes) + + case *snap.TrieNodesPacket: + return d.SnapSyncer.OnTrieNodes(peer, packet.ID, packet.Nodes) + + default: + return fmt.Errorf("unexpected snap packet type: %T", packet) + } +} + +// readHeaderRange returns a list of headers, using the given last header as the base, +// and going backwards towards genesis. This method assumes that the caller already has +// placed a reasonable cap on count. +func (d *Downloader) readHeaderRange(last *types.Header, count int) []*types.Header { + var ( + current = last + headers []*types.Header + ) + for { + parent := d.blockchain.GetHeaderByHash(current.ParentHash) + if parent == nil { + break // The chain is not continuous, or the chain is exhausted + } + headers = append(headers, parent) + if len(headers) >= count { + break + } + current = parent + } + return headers +} + +// reportSnapSyncProgress calculates various status reports and provides it to the user. +func (d *Downloader) reportSnapSyncProgress(force bool) { + // Initialize the sync start time if it's the first time we're reporting + if d.syncStartTime.IsZero() { + d.syncStartTime = time.Now().Add(-time.Millisecond) // -1ms offset to avoid division by zero + } + // Don't report all the events, just occasionally + if !force && time.Since(d.syncLogTime) < 8*time.Second { + return + } + // Don't report anything until we have a meaningful progress + var ( + headerBytes, _ = d.stateDB.AncientSize(rawdb.ChainFreezerHeaderTable) + bodyBytes, _ = d.stateDB.AncientSize(rawdb.ChainFreezerBodiesTable) + receiptBytes, _ = d.stateDB.AncientSize(rawdb.ChainFreezerReceiptTable) + ) + syncedBytes := common.StorageSize(headerBytes + bodyBytes + receiptBytes) + if syncedBytes == 0 { + return + } + var ( + header = d.blockchain.CurrentHeader() + block = d.blockchain.CurrentSnapBlock() + ) + syncedBlocks := block.Number.Uint64() - d.syncStartBlock + if syncedBlocks == 0 { + return + } + // Retrieve the current chain head and calculate the ETA + latest, _, _, err := d.skeleton.Bounds() + if err != nil { + // We're going to cheat for non-merged networks, but that's fine + latest = d.pivotHeader + } + if latest == nil { + // This should really never happen, but add some defensive code for now. + // TODO(karalabe): Remove it eventually if we don't see it blow. + log.Error("Nil latest block in sync progress report") + return + } + var ( + left = latest.Number.Uint64() - block.Number.Uint64() + eta = time.Since(d.syncStartTime) / time.Duration(syncedBlocks) * time.Duration(left) + + progress = fmt.Sprintf("%.2f%%", float64(block.Number.Uint64())*100/float64(latest.Number.Uint64())) + headers = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(header.Number.Uint64()), common.StorageSize(headerBytes).TerminalString()) + bodies = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(block.Number.Uint64()), common.StorageSize(bodyBytes).TerminalString()) + receipts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(block.Number.Uint64()), common.StorageSize(receiptBytes).TerminalString()) + ) + log.Info("Syncing: chain download in progress", "synced", progress, "chain", syncedBytes, "headers", headers, "bodies", bodies, "receipts", receipts, "eta", common.PrettyDuration(eta)) + d.syncLogTime = time.Now() +} diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go new file mode 100644 index 0000000..0cbddee --- /dev/null +++ b/eth/downloader/downloader_test.go @@ -0,0 +1,742 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package downloader + +import ( + "fmt" + "math/big" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "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/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// downloadTester is a test simulator for mocking out local block chain. +type downloadTester struct { + chain *core.BlockChain + downloader *Downloader + + peers map[string]*downloadTesterPeer + lock sync.RWMutex +} + +// newTester creates a new downloader test mocker. +func newTester(t *testing.T) *downloadTester { + return newTesterWithNotification(t, nil) +} + +// newTesterWithNotification creates a new downloader test mocker. +func newTesterWithNotification(t *testing.T, success func()) *downloadTester { + db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) + if err != nil { + panic(err) + } + t.Cleanup(func() { + db.Close() + }) + gspec := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + chain, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + if err != nil { + panic(err) + } + tester := &downloadTester{ + chain: chain, + peers: make(map[string]*downloadTesterPeer), + } + tester.downloader = New(db, new(event.TypeMux), tester.chain, tester.dropPeer, success) + return tester +} + +// terminate aborts any operations on the embedded downloader and releases all +// held resources. +func (dl *downloadTester) terminate() { + dl.downloader.Terminate() + dl.chain.Stop() +} + +// newPeer registers a new block download source into the downloader. +func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block) *downloadTesterPeer { + dl.lock.Lock() + defer dl.lock.Unlock() + + peer := &downloadTesterPeer{ + dl: dl, + id: id, + chain: newTestBlockchain(blocks), + withholdBodies: make(map[common.Hash]struct{}), + } + dl.peers[id] = peer + + if err := dl.downloader.RegisterPeer(id, version, peer); err != nil { + panic(err) + } + if err := dl.downloader.SnapSyncer.Register(peer); err != nil { + panic(err) + } + return peer +} + +// dropPeer simulates a hard peer removal from the connection pool. +func (dl *downloadTester) dropPeer(id string) { + dl.lock.Lock() + defer dl.lock.Unlock() + + delete(dl.peers, id) + dl.downloader.SnapSyncer.Unregister(id) + dl.downloader.UnregisterPeer(id) +} + +type downloadTesterPeer struct { + dl *downloadTester + withholdBodies map[common.Hash]struct{} + id string + chain *core.BlockChain +} + +// Head constructs a function to retrieve a peer's current head hash +// and total difficulty. +func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) { + head := dlp.chain.CurrentBlock() + return head.Hash(), dlp.chain.GetTd(head.Hash(), head.Number.Uint64()) +} + +func unmarshalRlpHeaders(rlpdata []rlp.RawValue) []*types.Header { + var headers = make([]*types.Header, len(rlpdata)) + for i, data := range rlpdata { + var h types.Header + if err := rlp.DecodeBytes(data, &h); err != nil { + panic(err) + } + headers[i] = &h + } + return headers +} + +// RequestHeadersByHash constructs a GetBlockHeaders function based on a hashed +// origin; associated with a particular peer in the download tester. The returned +// function can be used to retrieve batches of headers from the particular peer. +func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) { + // Service the header query via the live handler code + rlpHeaders := eth.ServiceGetBlockHeadersQuery(dlp.chain, ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{ + Hash: origin, + }, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + }, nil) + headers := unmarshalRlpHeaders(rlpHeaders) + hashes := make([]common.Hash, len(headers)) + for i, header := range headers { + hashes[i] = header.Hash() + } + // Deliver the headers to the downloader + req := ð.Request{ + Peer: dlp.id, + } + res := ð.Response{ + Req: req, + Res: (*eth.BlockHeadersRequest)(&headers), + Meta: hashes, + Time: 1, + Done: make(chan error, 1), // Ignore the returned status + } + go func() { + sink <- res + }() + return req, nil +} + +// RequestHeadersByNumber constructs a GetBlockHeaders function based on a numbered +// origin; associated with a particular peer in the download tester. The returned +// function can be used to retrieve batches of headers from the particular peer. +func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) { + // Service the header query via the live handler code + rlpHeaders := eth.ServiceGetBlockHeadersQuery(dlp.chain, ð.GetBlockHeadersRequest{ + Origin: eth.HashOrNumber{ + Number: origin, + }, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + }, nil) + headers := unmarshalRlpHeaders(rlpHeaders) + hashes := make([]common.Hash, len(headers)) + for i, header := range headers { + hashes[i] = header.Hash() + } + // Deliver the headers to the downloader + req := ð.Request{ + Peer: dlp.id, + } + res := ð.Response{ + Req: req, + Res: (*eth.BlockHeadersRequest)(&headers), + Meta: hashes, + Time: 1, + Done: make(chan error, 1), // Ignore the returned status + } + go func() { + sink <- res + }() + return req, nil +} + +// RequestBodies constructs a getBlockBodies method associated with a particular +// peer in the download tester. The returned function can be used to retrieve +// batches of block bodies from the particularly requested peer. +func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) { + blobs := eth.ServiceGetBlockBodiesQuery(dlp.chain, hashes) + + bodies := make([]*eth.BlockBody, len(blobs)) + for i, blob := range blobs { + bodies[i] = new(eth.BlockBody) + rlp.DecodeBytes(blob, bodies[i]) + } + var ( + txsHashes = make([]common.Hash, len(bodies)) + uncleHashes = make([]common.Hash, len(bodies)) + withdrawalHashes = make([]common.Hash, len(bodies)) + ) + hasher := trie.NewStackTrie(nil) + for i, body := range bodies { + hash := types.DeriveSha(types.Transactions(body.Transactions), hasher) + if _, ok := dlp.withholdBodies[hash]; ok { + txsHashes = append(txsHashes[:i], txsHashes[i+1:]...) + uncleHashes = append(uncleHashes[:i], uncleHashes[i+1:]...) + continue + } + txsHashes[i] = hash + uncleHashes[i] = types.CalcUncleHash(body.Uncles) + } + req := ð.Request{ + Peer: dlp.id, + } + res := ð.Response{ + Req: req, + Res: (*eth.BlockBodiesResponse)(&bodies), + Meta: [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes}, + Time: 1, + Done: make(chan error, 1), // Ignore the returned status + } + go func() { + sink <- res + }() + return req, nil +} + +// RequestReceipts constructs a getReceipts method associated with a particular +// peer in the download tester. The returned function can be used to retrieve +// batches of block receipts from the particularly requested peer. +func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) { + blobs := eth.ServiceGetReceiptsQuery(dlp.chain, hashes) + + receipts := make([][]*types.Receipt, len(blobs)) + for i, blob := range blobs { + rlp.DecodeBytes(blob, &receipts[i]) + } + hasher := trie.NewStackTrie(nil) + hashes = make([]common.Hash, len(receipts)) + for i, receipt := range receipts { + hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher) + } + req := ð.Request{ + Peer: dlp.id, + } + res := ð.Response{ + Req: req, + Res: (*eth.ReceiptsResponse)(&receipts), + Meta: hashes, + Time: 1, + Done: make(chan error, 1), // Ignore the returned status + } + go func() { + sink <- res + }() + return req, nil +} + +// ID retrieves the peer's unique identifier. +func (dlp *downloadTesterPeer) ID() string { + return dlp.id +} + +// RequestAccountRange fetches a batch of accounts rooted in a specific account +// trie, starting with the origin. +func (dlp *downloadTesterPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error { + // Create the request and service it + req := &snap.GetAccountRangePacket{ + ID: id, + Root: root, + Origin: origin, + Limit: limit, + Bytes: bytes, + } + slimaccs, proofs := snap.ServiceGetAccountRangeQuery(dlp.chain, req) + + // We need to convert to non-slim format, delegate to the packet code + res := &snap.AccountRangePacket{ + ID: id, + Accounts: slimaccs, + Proof: proofs, + } + hashes, accounts, _ := res.Unpack() + + go dlp.dl.downloader.SnapSyncer.OnAccounts(dlp, id, hashes, accounts, proofs) + return nil +} + +// RequestStorageRanges fetches a batch of storage slots belonging to one or +// more accounts. If slots from only one account is requested, an origin marker +// may also be used to retrieve from there. +func (dlp *downloadTesterPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error { + // Create the request and service it + req := &snap.GetStorageRangesPacket{ + ID: id, + Accounts: accounts, + Root: root, + Origin: origin, + Limit: limit, + Bytes: bytes, + } + storage, proofs := snap.ServiceGetStorageRangesQuery(dlp.chain, req) + + // We need to convert to demultiplex, delegate to the packet code + res := &snap.StorageRangesPacket{ + ID: id, + Slots: storage, + Proof: proofs, + } + hashes, slots := res.Unpack() + + go dlp.dl.downloader.SnapSyncer.OnStorage(dlp, id, hashes, slots, proofs) + return nil +} + +// RequestByteCodes fetches a batch of bytecodes by hash. +func (dlp *downloadTesterPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { + req := &snap.GetByteCodesPacket{ + ID: id, + Hashes: hashes, + Bytes: bytes, + } + codes := snap.ServiceGetByteCodesQuery(dlp.chain, req) + go dlp.dl.downloader.SnapSyncer.OnByteCodes(dlp, id, codes) + return nil +} + +// RequestTrieNodes fetches a batch of account or storage trie nodes rooted in +// a specific state trie. +func (dlp *downloadTesterPeer) RequestTrieNodes(id uint64, root common.Hash, paths []snap.TrieNodePathSet, bytes uint64) error { + req := &snap.GetTrieNodesPacket{ + ID: id, + Root: root, + Paths: paths, + Bytes: bytes, + } + nodes, _ := snap.ServiceGetTrieNodesQuery(dlp.chain, req, time.Now()) + go dlp.dl.downloader.SnapSyncer.OnTrieNodes(dlp, id, nodes) + return nil +} + +// Log retrieves the peer's own contextual logger. +func (dlp *downloadTesterPeer) Log() log.Logger { + return log.New("peer", dlp.id) +} + +// assertOwnChain checks if the local chain contains the correct number of items +// of the various chain components. +func assertOwnChain(t *testing.T, tester *downloadTester, length int) { + // Mark this method as a helper to report errors at callsite, not in here + t.Helper() + + headers, blocks, receipts := length, length, length + if hs := int(tester.chain.CurrentHeader().Number.Uint64()) + 1; hs != headers { + t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers) + } + if bs := int(tester.chain.CurrentBlock().Number.Uint64()) + 1; bs != blocks { + t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, blocks) + } + if rs := int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1; rs != receipts { + t.Fatalf("synchronised receipts mismatch: have %v, want %v", rs, receipts) + } +} + +func TestCanonicalSynchronisation68Full(t *testing.T) { testCanonSync(t, eth.ETH68, FullSync) } +func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH68, SnapSync) } + +func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { + success := make(chan struct{}) + tester := newTesterWithNotification(t, func() { + close(success) + }) + defer tester.terminate() + + // Create a small enough block chain to download + chain := testChainBase.shorten(blockCacheMaxItems - 15) + tester.newPeer("peer", protocol, chain.blocks[1:]) + + // Synchronise with the peer and make sure all relevant data was retrieved + if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { + t.Fatalf("failed to beacon-sync chain: %v", err) + } + select { + case <-success: + assertOwnChain(t, tester, len(chain.blocks)) + case <-time.NewTimer(time.Second * 3).C: + t.Fatalf("Failed to sync chain in three seconds") + } +} + +// Tests that if a large batch of blocks are being downloaded, it is throttled +// until the cached blocks are retrieved. +func TestThrottling68Full(t *testing.T) { testThrottling(t, eth.ETH68, FullSync) } +func TestThrottling68Snap(t *testing.T) { testThrottling(t, eth.ETH68, SnapSync) } + +func testThrottling(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) + defer tester.terminate() + + // Create a long block chain to download and the tester + targetBlocks := len(testChainBase.blocks) - 1 + tester.newPeer("peer", protocol, testChainBase.blocks[1:]) + + // Wrap the importer to allow stepping + var blocked atomic.Uint32 + proceed := make(chan struct{}) + tester.downloader.chainInsertHook = func(results []*fetchResult) { + blocked.Store(uint32(len(results))) + <-proceed + } + // Start a synchronisation concurrently + errc := make(chan error, 1) + go func() { + errc <- tester.downloader.BeaconSync(mode, testChainBase.blocks[len(testChainBase.blocks)-1].Header(), nil) + }() + // Iteratively take some blocks, always checking the retrieval count + for { + // Check the retrieval count synchronously (! reason for this ugly block) + tester.lock.RLock() + retrieved := int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1 + tester.lock.RUnlock() + if retrieved >= targetBlocks+1 { + break + } + // Wait a bit for sync to throttle itself + var cached, frozen int + for start := time.Now(); time.Since(start) < 3*time.Second; { + time.Sleep(25 * time.Millisecond) + + tester.lock.Lock() + tester.downloader.queue.lock.Lock() + tester.downloader.queue.resultCache.lock.Lock() + { + cached = tester.downloader.queue.resultCache.countCompleted() + frozen = int(blocked.Load()) + retrieved = int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1 + } + tester.downloader.queue.resultCache.lock.Unlock() + tester.downloader.queue.lock.Unlock() + tester.lock.Unlock() + + if cached == blockCacheMaxItems || + cached == blockCacheMaxItems-reorgProtHeaderDelay || + retrieved+cached+frozen == targetBlocks+1 || + retrieved+cached+frozen == targetBlocks+1-reorgProtHeaderDelay { + break + } + } + // Make sure we filled up the cache, then exhaust it + time.Sleep(25 * time.Millisecond) // give it a chance to screw up + tester.lock.RLock() + retrieved = int(tester.chain.CurrentSnapBlock().Number.Uint64()) + 1 + tester.lock.RUnlock() + if cached != blockCacheMaxItems && cached != blockCacheMaxItems-reorgProtHeaderDelay && retrieved+cached+frozen != targetBlocks+1 && retrieved+cached+frozen != targetBlocks+1-reorgProtHeaderDelay { + t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheMaxItems, retrieved, frozen, targetBlocks+1) + } + // Permit the blocked blocks to import + if blocked.Load() > 0 { + blocked.Store(uint32(0)) + proceed <- struct{}{} + } + } + // Check that we haven't pulled more blocks than available + assertOwnChain(t, tester, targetBlocks+1) + if err := <-errc; err != nil { + t.Fatalf("block synchronization failed: %v", err) + } +} + +// Tests that a canceled download wipes all previously accumulated state. +func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) } +func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) } + +func testCancel(t *testing.T, protocol uint, mode SyncMode) { + complete := make(chan struct{}) + success := func() { + close(complete) + } + tester := newTesterWithNotification(t, success) + defer tester.terminate() + + chain := testChainBase.shorten(MaxHeaderFetch) + tester.newPeer("peer", protocol, chain.blocks[1:]) + + // Make sure canceling works with a pristine downloader + tester.downloader.Cancel() + if !tester.downloader.queue.Idle() { + t.Errorf("download queue not idle") + } + // Synchronise with the peer, but cancel afterwards + if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + <-complete + tester.downloader.Cancel() + if !tester.downloader.queue.Idle() { + t.Errorf("download queue not idle") + } +} + +// Tests that synchronisations behave well in multi-version protocol environments +// and not wreak havoc on other nodes in the network. +func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) } +func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH68, SnapSync) } + +func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { + complete := make(chan struct{}) + success := func() { + close(complete) + } + tester := newTesterWithNotification(t, success) + defer tester.terminate() + + // Create a small enough block chain to download + chain := testChainBase.shorten(blockCacheMaxItems - 15) + + // Create peers of every type + tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:]) + + if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { + t.Fatalf("failed to start beacon sync: #{err}") + } + select { + case <-complete: + break + case <-time.NewTimer(time.Second * 3).C: + t.Fatalf("Failed to sync chain in three seconds") + } + assertOwnChain(t, tester, len(chain.blocks)) + + // Check that no peers have been dropped off + for _, version := range []int{68} { + peer := fmt.Sprintf("peer %d", version) + if _, ok := tester.peers[peer]; !ok { + t.Errorf("%s dropped", peer) + } + } +} + +// Tests that if a block is empty (e.g. header only), no body request should be +// made, and instead the header should be assembled into a whole block in itself. +func TestEmptyShortCircuit68Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, FullSync) } +func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, SnapSync) } + +func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { + success := make(chan struct{}) + tester := newTesterWithNotification(t, func() { + close(success) + }) + defer tester.terminate() + + // Create a block chain to download + chain := testChainBase + tester.newPeer("peer", protocol, chain.blocks[1:]) + + // Instrument the downloader to signal body requests + var bodiesHave, receiptsHave atomic.Int32 + tester.downloader.bodyFetchHook = func(headers []*types.Header) { + bodiesHave.Add(int32(len(headers))) + } + tester.downloader.receiptFetchHook = func(headers []*types.Header) { + receiptsHave.Add(int32(len(headers))) + } + + if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + select { + case <-success: + checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{ + HighestBlock: uint64(len(chain.blocks) - 1), + CurrentBlock: uint64(len(chain.blocks) - 1), + }) + case <-time.NewTimer(time.Second * 3).C: + t.Fatalf("Failed to sync chain in three seconds") + } + assertOwnChain(t, tester, len(chain.blocks)) + + // Validate the number of block bodies that should have been requested + bodiesNeeded, receiptsNeeded := 0, 0 + for _, block := range chain.blocks[1:] { + if len(block.Transactions()) > 0 || len(block.Uncles()) > 0 { + bodiesNeeded++ + } + } + for _, block := range chain.blocks[1:] { + if mode == SnapSync && len(block.Transactions()) > 0 { + receiptsNeeded++ + } + } + if int(bodiesHave.Load()) != bodiesNeeded { + t.Errorf("body retrieval count mismatch: have %v, want %v", bodiesHave.Load(), bodiesNeeded) + } + if int(receiptsHave.Load()) != receiptsNeeded { + t.Errorf("receipt retrieval count mismatch: have %v, want %v", receiptsHave.Load(), receiptsNeeded) + } +} + +func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) { + // Mark this method as a helper to report errors at callsite, not in here + t.Helper() + + p := d.Progress() + if p.StartingBlock != want.StartingBlock || p.CurrentBlock != want.CurrentBlock || p.HighestBlock != want.HighestBlock { + t.Fatalf("%s progress mismatch:\nhave %+v\nwant %+v", stage, p, want) + } +} + +// Tests that peers below a pre-configured checkpoint block are prevented from +// being fast-synced from, avoiding potential cheap eclipse attacks. +func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) } +func TestBeaconSync68Snap(t *testing.T) { testBeaconSync(t, eth.ETH68, SnapSync) } + +func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) { + var cases = []struct { + name string // The name of testing scenario + local int // The length of local chain(canonical chain assumed), 0 means genesis is the head + }{ + {name: "Beacon sync since genesis", local: 0}, + {name: "Beacon sync with short local chain", local: 1}, + {name: "Beacon sync with long local chain", local: blockCacheMaxItems - 15 - fsMinFullBlocks/2}, + {name: "Beacon sync with full local chain", local: blockCacheMaxItems - 15 - 1}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + success := make(chan struct{}) + tester := newTesterWithNotification(t, func() { + close(success) + }) + defer tester.terminate() + + chain := testChainBase.shorten(blockCacheMaxItems - 15) + tester.newPeer("peer", protocol, chain.blocks[1:]) + + // Build the local chain segment if it's required + if c.local > 0 { + tester.chain.InsertChain(chain.blocks[1 : c.local+1]) + } + if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { + t.Fatalf("Failed to beacon sync chain %v %v", c.name, err) + } + select { + case <-success: + // Ok, downloader fully cancelled after sync cycle + if bs := int(tester.chain.CurrentBlock().Number.Uint64()) + 1; bs != len(chain.blocks) { + t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, len(chain.blocks)) + } + case <-time.NewTimer(time.Second * 3).C: + t.Fatalf("Failed to sync chain in three seconds") + } + }) + } +} + +// Tests that synchronisation progress (origin block number, current block number +// and highest block number) is tracked and updated correctly. +func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) } +func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) } + +func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { + success := make(chan struct{}) + tester := newTesterWithNotification(t, func() { + success <- struct{}{} + }) + defer tester.terminate() + checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) + + chain := testChainBase.shorten(blockCacheMaxItems - 15) + shortChain := chain.shorten(len(chain.blocks) / 2).blocks[1:] + + // Connect to peer that provides all headers and part of the bodies + faultyPeer := tester.newPeer("peer-half", protocol, shortChain) + for _, header := range shortChain { + faultyPeer.withholdBodies[header.Hash()] = struct{}{} + } + + if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)/2-1].Header(), nil); err != nil { + t.Fatalf("failed to beacon-sync chain: %v", err) + } + select { + case <-success: + // Ok, downloader fully cancelled after sync cycle + checkProgress(t, tester.downloader, "peer-half", ethereum.SyncProgress{ + CurrentBlock: uint64(len(chain.blocks)/2 - 1), + HighestBlock: uint64(len(chain.blocks)/2 - 1), + }) + case <-time.NewTimer(time.Second * 3).C: + t.Fatalf("Failed to sync chain in three seconds") + } + + // Synchronise all the blocks and check continuation progress + tester.newPeer("peer-full", protocol, chain.blocks[1:]) + if err := tester.downloader.BeaconSync(mode, chain.blocks[len(chain.blocks)-1].Header(), nil); err != nil { + t.Fatalf("failed to beacon-sync chain: %v", err) + } + startingBlock := uint64(len(chain.blocks)/2 - 1) + + select { + case <-success: + // Ok, downloader fully cancelled after sync cycle + checkProgress(t, tester.downloader, "peer-full", ethereum.SyncProgress{ + StartingBlock: startingBlock, + CurrentBlock: uint64(len(chain.blocks) - 1), + HighestBlock: uint64(len(chain.blocks) - 1), + }) + case <-time.NewTimer(time.Second * 3).C: + t.Fatalf("Failed to sync chain in three seconds") + } +} diff --git a/eth/downloader/events.go b/eth/downloader/events.go new file mode 100644 index 0000000..25255a3 --- /dev/null +++ b/eth/downloader/events.go @@ -0,0 +1,25 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package downloader + +import "github.com/ethereum/go-ethereum/core/types" + +type DoneEvent struct { + Latest *types.Header +} +type StartEvent struct{} +type FailedEvent struct{ Err error } diff --git a/eth/downloader/fetchers.go b/eth/downloader/fetchers.go new file mode 100644 index 0000000..4ebb9bb --- /dev/null +++ b/eth/downloader/fetchers.go @@ -0,0 +1,70 @@ +// 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 downloader + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" +) + +// fetchHeadersByHash is a blocking version of Peer.RequestHeadersByHash which +// handles all the cancellation, interruption and timeout mechanisms of a data +// retrieval to allow blocking API calls. +func (d *Downloader) fetchHeadersByHash(p *peerConnection, hash common.Hash, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error) { + // Create the response sink and send the network request + start := time.Now() + resCh := make(chan *eth.Response) + + req, err := p.peer.RequestHeadersByHash(hash, amount, skip, reverse, resCh) + if err != nil { + return nil, nil, err + } + defer req.Close() + + // Wait until the response arrives, the request is cancelled or times out + ttl := d.peers.rates.TargetTimeout() + + timeoutTimer := time.NewTimer(ttl) + defer timeoutTimer.Stop() + + select { + case <-d.cancelCh: + return nil, nil, errCanceled + + case <-timeoutTimer.C: + // Header retrieval timed out, update the metrics + p.log.Debug("Header request timed out", "elapsed", ttl) + headerTimeoutMeter.Mark(1) + + return nil, nil, errTimeout + + case res := <-resCh: + // Headers successfully retrieved, update the metrics + headerReqTimer.Update(time.Since(start)) + 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.BlockHeadersRequest), res.Meta.([]common.Hash), nil + } +} diff --git a/eth/downloader/fetchers_concurrent.go b/eth/downloader/fetchers_concurrent.go new file mode 100644 index 0000000..9d8cd11 --- /dev/null +++ b/eth/downloader/fetchers_concurrent.go @@ -0,0 +1,354 @@ +// 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 downloader + +import ( + "errors" + "sort" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/log" +) + +// timeoutGracePeriod is the amount of time to allow for a peer to deliver a +// response to a locally already timed out request. Timeouts are not penalized +// as a peer might be temporarily overloaded, however, they still must reply +// to each request. Failing to do so is considered a protocol violation. +var timeoutGracePeriod = 2 * time.Minute + +// typedQueue is an interface defining the adaptor needed to translate the type +// specific downloader/queue schedulers into the type-agnostic general concurrent +// fetcher algorithm calls. +type typedQueue interface { + // waker returns a notification channel that gets pinged in case more fetches + // have been queued up, so the fetcher might assign it to idle peers. + waker() chan bool + + // pending returns the number of wrapped items that are currently queued for + // fetching by the concurrent downloader. + pending() int + + // capacity is responsible for calculating how many items of the abstracted + // type a particular peer is estimated to be able to retrieve within the + // allotted round trip time. + capacity(peer *peerConnection, rtt time.Duration) int + + // updateCapacity is responsible for updating how many items of the abstracted + // type a particular peer is estimated to be able to retrieve in a unit time. + updateCapacity(peer *peerConnection, items int, elapsed time.Duration) + + // reserve is responsible for allocating a requested number of pending items + // from the download queue to the specified peer. + reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) + + // unreserve is responsible for removing the current retrieval allocation + // assigned to a specific peer and placing it back into the pool to allow + // reassigning to some other peer. + unreserve(peer string) int + + // request is responsible for converting a generic fetch request into a typed + // one and sending it to the remote peer for fulfillment. + request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) + + // deliver is responsible for taking a generic response packet from the + // concurrent fetcher, unpacking the type specific data and delivering + // it to the downloader's queue. + deliver(peer *peerConnection, packet *eth.Response) (int, error) +} + +// concurrentFetch iteratively downloads scheduled block parts, taking available +// peers, reserving a chunk of fetch requests for each and waiting for delivery +// or timeouts. +func (d *Downloader) concurrentFetch(queue typedQueue) error { + // Create a delivery channel to accept responses from all peers + responses := make(chan *eth.Response) + + // Track the currently active requests and their timeout order + pending := make(map[string]*eth.Request) + defer func() { + // Abort all requests on sync cycle cancellation. The requests may still + // be fulfilled by the remote side, but the dispatcher will not wait to + // deliver them since nobody's going to be listening. + for _, req := range pending { + req.Close() + } + }() + ordering := make(map[*eth.Request]int) + timeouts := prque.New[int64, *eth.Request](func(data *eth.Request, index int) { + ordering[data] = index + }) + + timeout := time.NewTimer(0) + if !timeout.Stop() { + <-timeout.C + } + defer timeout.Stop() + + // Track the timed-out but not-yet-answered requests separately. We want to + // keep tracking which peers are busy (potentially overloaded), so removing + // all trace of a timed out request is not good. We also can't just cancel + // the pending request altogether as that would prevent a late response from + // being delivered, thus never unblocking the peer. + stales := make(map[string]*eth.Request) + defer func() { + // Abort all requests on sync cycle cancellation. The requests may still + // be fulfilled by the remote side, but the dispatcher will not wait to + // deliver them since nobody's going to be listening. + for _, req := range stales { + req.Close() + } + }() + // Subscribe to peer lifecycle events to schedule tasks to new joiners and + // reschedule tasks upon disconnections. We don't care which event happened + // for simplicity, so just use a single channel. + peering := make(chan *peeringEvent, 64) // arbitrary buffer, just some burst protection + + peeringSub := d.peers.SubscribeEvents(peering) + defer peeringSub.Unsubscribe() + + // Prepare the queue and fetch block parts until the block header fetcher's done + finished := false + for { + // If there's nothing more to fetch, wait or terminate + if queue.pending() == 0 { + if len(pending) == 0 && finished { + return nil + } + } else { + // Send a download request to all idle peers, until throttled + var ( + idles []*peerConnection + caps []int + ) + for _, peer := range d.peers.AllPeers() { + pending, stale := pending[peer.id], stales[peer.id] + if pending == nil && stale == nil { + idles = append(idles, peer) + caps = append(caps, queue.capacity(peer, time.Second)) + } else if stale != nil { + if waited := time.Since(stale.Sent); waited > timeoutGracePeriod { + // Request has been in flight longer than the grace period + // permitted it, consider the peer malicious attempting to + // stall the sync. + peer.log.Warn("Peer stalling, dropping", "waited", common.PrettyDuration(waited)) + d.dropPeer(peer.id) + } + } + } + sort.Sort(&peerCapacitySort{idles, caps}) + + var throttled bool + for _, peer := range idles { + // Short circuit if throttling activated or there are no more + // queued tasks to be retrieved + if throttled { + break + } + if queued := queue.pending(); queued == 0 { + break + } + // Reserve a chunk of fetches for a peer. A nil can mean either that + // no more headers are available, or that the peer is known not to + // have them. + request, _, throttle := queue.reserve(peer, queue.capacity(peer, d.peers.rates.TargetRoundTrip())) + if throttle { + throttled = true + throttleCounter.Inc(1) + } + if request == nil { + continue + } + // Fetch the chunk and make sure any errors return the hashes to the queue + req, err := queue.request(peer, request, responses) + if err != nil { + // Sending the request failed, which generally means the peer + // was disconnected in between assignment and network send. + // Although all peer removal operations return allocated tasks + // to the queue, that is async, and we can do better here by + // immediately pushing the unfulfilled requests. + queue.unreserve(peer.id) // TODO(karalabe): This needs a non-expiration method + continue + } + pending[peer.id] = req + + ttl := d.peers.rates.TargetTimeout() + ordering[req] = timeouts.Size() + + timeouts.Push(req, -time.Now().Add(ttl).UnixNano()) + if timeouts.Size() == 1 { + timeout.Reset(ttl) + } + } + } + // Wait for something to happen + select { + case <-d.cancelCh: + // If sync was cancelled, tear down the parallel retriever. Pending + // requests will be cancelled locally, and the remote responses will + // be dropped when they arrive + return errCanceled + + case event := <-peering: + // A peer joined or left, the tasks queue and allocations need to be + // checked for potential assignment or reassignment + peerid := event.peer.id + + if event.join { + // Sanity check the internal state; this can be dropped later + if _, ok := pending[peerid]; ok { + event.peer.log.Error("Pending request exists for joining peer") + } + if _, ok := stales[peerid]; ok { + event.peer.log.Error("Stale request exists for joining peer") + } + // Loop back to the entry point for task assignment + continue + } + // A peer left, any existing requests need to be untracked, pending + // tasks returned and possible reassignment checked + if req, ok := pending[peerid]; ok { + queue.unreserve(peerid) // TODO(karalabe): This needs a non-expiration method + delete(pending, peerid) + req.Close() + + if index, live := ordering[req]; live { + timeouts.Remove(index) + if index == 0 { + if !timeout.Stop() { + <-timeout.C + } + if timeouts.Size() > 0 { + _, exp := timeouts.Peek() + timeout.Reset(time.Until(time.Unix(0, -exp))) + } + } + delete(ordering, req) + } + } + if req, ok := stales[peerid]; ok { + delete(stales, peerid) + req.Close() + } + + case <-timeout.C: + // Retrieve the next request which should have timed out. The check + // below is purely for to catch programming errors, given the correct + // code, there's no possible order of events that should result in a + // timeout firing for a non-existent event. + req, exp := timeouts.Peek() + if now, at := time.Now(), time.Unix(0, -exp); now.Before(at) { + log.Error("Timeout triggered but not reached", "left", at.Sub(now)) + timeout.Reset(at.Sub(now)) + continue + } + // Stop tracking the timed out request from a timing perspective, + // cancel it, so it's not considered in-flight anymore, but keep + // the peer marked busy to prevent assigning a second request and + // overloading it further. + delete(pending, req.Peer) + stales[req.Peer] = req + + timeouts.Pop() // Popping an item will reorder indices in `ordering`, delete after, otherwise will resurrect! + if timeouts.Size() > 0 { + _, exp := timeouts.Peek() + timeout.Reset(time.Until(time.Unix(0, -exp))) + } + delete(ordering, req) + + // New timeout potentially set if there are more requests pending, + // reschedule the failed one to a free peer + fails := queue.unreserve(req.Peer) + + // Finally, update the peer's retrieval capacity, or if it's already + // below the minimum allowance, drop the peer. If a lot of retrieval + // elements expired, we might have overestimated the remote peer or + // perhaps ourselves. Only reset to minimal throughput but don't drop + // just yet. + // + // The reason the minimum threshold is 2 is that the downloader tries + // to estimate the bandwidth and latency of a peer separately, which + // requires pushing the measured capacity a bit and seeing how response + // times reacts, to it always requests one more than the minimum (i.e. + // min 2). + peer := d.peers.Peer(req.Peer) + if peer == nil { + // If the peer got disconnected in between, we should really have + // short-circuited it already. Just in case there's some strange + // codepath, leave this check in not to crash. + log.Error("Delivery timeout from unknown peer", "peer", req.Peer) + continue + } + if fails > 2 { + queue.updateCapacity(peer, 0, 0) + } else { + d.dropPeer(peer.id) + } + + case res := <-responses: + // Response arrived, it may be for an existing or an already timed + // out request. If the former, update the timeout heap and perhaps + // reschedule the timeout timer. + index, live := ordering[res.Req] + if live { + timeouts.Remove(index) + if index == 0 { + if !timeout.Stop() { + <-timeout.C + } + if timeouts.Size() > 0 { + _, exp := timeouts.Peek() + timeout.Reset(time.Until(time.Unix(0, -exp))) + } + } + delete(ordering, res.Req) + } + // Delete the pending request (if it still exists) and mark the peer idle + delete(pending, res.Req.Peer) + delete(stales, res.Req.Peer) + + // Signal the dispatcher that the round trip is done. We'll drop the + // peer if the data turns out to be junk. + res.Done <- nil + res.Req.Close() + + // If the peer was previously banned and failed to deliver its pack + // in a reasonable time frame, ignore its message. + if peer := d.peers.Peer(res.Req.Peer); peer != nil { + // Deliver the received chunk of data and check chain validity + accepted, err := queue.deliver(peer, res) + if errors.Is(err, errInvalidChain) { + return err + } + // Unless a peer delivered something completely else than requested (usually + // caused by a timed out request which came through in the end), set it to + // idle. If the delivery's stale, the peer should have already been idled. + if !errors.Is(err, errStaleDelivery) { + queue.updateCapacity(peer, accepted, res.Time) + } + } + + case cont := <-queue.waker(): + // The header fetcher sent a continuation flag, check if it's done + if !cont { + finished = true + } + } + } +} diff --git a/eth/downloader/fetchers_concurrent_bodies.go b/eth/downloader/fetchers_concurrent_bodies.go new file mode 100644 index 0000000..56359b3 --- /dev/null +++ b/eth/downloader/fetchers_concurrent_bodies.go @@ -0,0 +1,104 @@ +// 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 downloader + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/log" +) + +// bodyQueue implements typedQueue and is a type adapter between the generic +// concurrent fetcher and the downloader. +type bodyQueue Downloader + +// waker returns a notification channel that gets pinged in case more body +// fetches have been queued up, so the fetcher might assign it to idle peers. +func (q *bodyQueue) waker() chan bool { + return q.queue.blockWakeCh +} + +// pending returns the number of bodies that are currently queued for fetching +// by the concurrent downloader. +func (q *bodyQueue) pending() int { + return q.queue.PendingBodies() +} + +// capacity is responsible for calculating how many bodies a particular peer is +// estimated to be able to retrieve within the allotted round trip time. +func (q *bodyQueue) capacity(peer *peerConnection, rtt time.Duration) int { + return peer.BodyCapacity(rtt) +} + +// updateCapacity is responsible for updating how many bodies a particular peer +// is estimated to be able to retrieve in a unit time. +func (q *bodyQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) { + peer.UpdateBodyRate(items, span) +} + +// reserve is responsible for allocating a requested number of pending bodies +// from the download queue to the specified peer. +func (q *bodyQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) { + return q.queue.ReserveBodies(peer, items) +} + +// unreserve is responsible for removing the current body retrieval allocation +// assigned to a specific peer and placing it back into the pool to allow +// reassigning to some other peer. +func (q *bodyQueue) unreserve(peer string) int { + fails := q.queue.ExpireBodies(peer) + if fails > 2 { + log.Trace("Body delivery timed out", "peer", peer) + } else { + log.Debug("Body delivery stalling", "peer", peer) + } + return fails +} + +// request is responsible for converting a generic fetch request into a body +// one and sending it to the remote peer for fulfillment. +func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) { + peer.log.Trace("Requesting new batch of bodies", "count", len(req.Headers), "from", req.Headers[0].Number) + if q.bodyFetchHook != nil { + q.bodyFetchHook(req.Headers) + } + hashes := make([]common.Hash, 0, len(req.Headers)) + for _, header := range req.Headers { + hashes = append(hashes, header.Hash()) + } + return peer.peer.RequestBodies(hashes, resCh) +} + +// 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.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]) + switch { + case err == nil && len(txs) == 0: + peer.log.Trace("Requested bodies delivered") + case err == nil: + peer.log.Trace("Delivered new batch of bodies", "count", len(txs), "accepted", accepted) + default: + peer.log.Debug("Failed to deliver retrieved bodies", "err", err) + } + return accepted, err +} diff --git a/eth/downloader/fetchers_concurrent_receipts.go b/eth/downloader/fetchers_concurrent_receipts.go new file mode 100644 index 0000000..3169f03 --- /dev/null +++ b/eth/downloader/fetchers_concurrent_receipts.go @@ -0,0 +1,104 @@ +// 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 downloader + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/log" +) + +// receiptQueue implements typedQueue and is a type adapter between the generic +// concurrent fetcher and the downloader. +type receiptQueue Downloader + +// waker returns a notification channel that gets pinged in case more receipt +// fetches have been queued up, so the fetcher might assign it to idle peers. +func (q *receiptQueue) waker() chan bool { + return q.queue.receiptWakeCh +} + +// pending returns the number of receipt that are currently queued for fetching +// by the concurrent downloader. +func (q *receiptQueue) pending() int { + return q.queue.PendingReceipts() +} + +// capacity is responsible for calculating how many receipts a particular peer is +// estimated to be able to retrieve within the allotted round trip time. +func (q *receiptQueue) capacity(peer *peerConnection, rtt time.Duration) int { + return peer.ReceiptCapacity(rtt) +} + +// updateCapacity is responsible for updating how many receipts a particular peer +// is estimated to be able to retrieve in a unit time. +func (q *receiptQueue) updateCapacity(peer *peerConnection, items int, span time.Duration) { + peer.UpdateReceiptRate(items, span) +} + +// reserve is responsible for allocating a requested number of pending receipts +// from the download queue to the specified peer. +func (q *receiptQueue) reserve(peer *peerConnection, items int) (*fetchRequest, bool, bool) { + return q.queue.ReserveReceipts(peer, items) +} + +// unreserve is responsible for removing the current receipt retrieval allocation +// assigned to a specific peer and placing it back into the pool to allow +// reassigning to some other peer. +func (q *receiptQueue) unreserve(peer string) int { + fails := q.queue.ExpireReceipts(peer) + if fails > 2 { + log.Trace("Receipt delivery timed out", "peer", peer) + } else { + log.Debug("Receipt delivery stalling", "peer", peer) + } + return fails +} + +// request is responsible for converting a generic fetch request into a receipt +// one and sending it to the remote peer for fulfillment. +func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh chan *eth.Response) (*eth.Request, error) { + peer.log.Trace("Requesting new batch of receipts", "count", len(req.Headers), "from", req.Headers[0].Number) + if q.receiptFetchHook != nil { + q.receiptFetchHook(req.Headers) + } + hashes := make([]common.Hash, 0, len(req.Headers)) + for _, header := range req.Headers { + hashes = append(hashes, header.Hash()) + } + return peer.peer.RequestReceipts(hashes, resCh) +} + +// 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.ReceiptsResponse) + hashes := packet.Meta.([]common.Hash) // {receipt hashes} + + accepted, err := q.queue.DeliverReceipts(peer.id, receipts, hashes) + switch { + case err == nil && len(receipts) == 0: + peer.log.Trace("Requested receipts delivered") + case err == nil: + peer.log.Trace("Delivered new batch of receipts", "count", len(receipts), "accepted", accepted) + default: + peer.log.Debug("Failed to deliver retrieved receipts", "err", err) + } + return accepted, err +} diff --git a/eth/downloader/metrics.go b/eth/downloader/metrics.go new file mode 100644 index 0000000..23c033a --- /dev/null +++ b/eth/downloader/metrics.go @@ -0,0 +1,42 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains the metrics collected by the downloader. + +package downloader + +import ( + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + headerInMeter = metrics.NewRegisteredMeter("eth/downloader/headers/in", nil) + headerReqTimer = metrics.NewRegisteredTimer("eth/downloader/headers/req", nil) + headerDropMeter = metrics.NewRegisteredMeter("eth/downloader/headers/drop", nil) + headerTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/headers/timeout", nil) + + bodyInMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/in", nil) + bodyReqTimer = metrics.NewRegisteredTimer("eth/downloader/bodies/req", nil) + bodyDropMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/drop", nil) + bodyTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/timeout", nil) + + receiptInMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/in", nil) + receiptReqTimer = metrics.NewRegisteredTimer("eth/downloader/receipts/req", nil) + receiptDropMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/drop", nil) + receiptTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/timeout", nil) + + throttleCounter = metrics.NewRegisteredCounter("eth/downloader/throttle", nil) +) diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go new file mode 100644 index 0000000..9d8e1f3 --- /dev/null +++ b/eth/downloader/modes.go @@ -0,0 +1,67 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package downloader + +import "fmt" + +// SyncMode represents the synchronisation mode of the downloader. +// It is a uint32 as it is used with atomic operations. +type SyncMode uint32 + +const ( + FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks + SnapSync // Download the chain and the state via compact snapshots +) + +func (mode SyncMode) IsValid() bool { + return mode == FullSync || mode == SnapSync +} + +// String implements the stringer interface. +func (mode SyncMode) String() string { + switch mode { + case FullSync: + return "full" + case SnapSync: + return "snap" + default: + return "unknown" + } +} + +func (mode SyncMode) MarshalText() ([]byte, error) { + switch mode { + case FullSync: + return []byte("full"), nil + case SnapSync: + return []byte("snap"), nil + default: + return nil, fmt.Errorf("unknown sync mode %d", mode) + } +} + +func (mode *SyncMode) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *mode = FullSync + case "snap": + *mode = SnapSync + default: + return fmt.Errorf(`unknown sync mode %q, want "full" or "snap"`, text) + } + return nil +} diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go new file mode 100644 index 0000000..4c43af5 --- /dev/null +++ b/eth/downloader/peer.go @@ -0,0 +1,290 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains the active peer-set of the downloader, maintaining both failures +// as well as reputation metrics to prioritize the block retrievals. + +package downloader + +import ( + "errors" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/msgrate" +) + +const ( + maxLackingHashes = 4096 // Maximum number of entries allowed on the list or lacking items +) + +var ( + errAlreadyRegistered = errors.New("peer is already registered") + errNotRegistered = errors.New("peer is not registered") +) + +// peerConnection represents an active peer from which hashes and blocks are retrieved. +type peerConnection struct { + id string // Unique identifier of the peer + + rates *msgrate.Tracker // Tracker to hone in on the number of items retrievable per second + lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously) + + peer Peer + + version uint // Eth protocol version number to switch strategies + log log.Logger // Contextual logger to add extra infos to peer logs + lock sync.RWMutex +} + +// 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) + + RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error) + RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) +} + +// newPeerConnection creates a new downloader peer. +func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection { + return &peerConnection{ + id: id, + lacking: make(map[common.Hash]struct{}), + peer: peer, + version: version, + log: logger, + } +} + +// Reset clears the internal state of a peer entity. +func (p *peerConnection) Reset() { + p.lock.Lock() + defer p.lock.Unlock() + + p.lacking = make(map[common.Hash]struct{}) +} + +// UpdateHeaderRate updates the peer's estimated header retrieval throughput with +// the current measurement. +func (p *peerConnection) UpdateHeaderRate(delivered int, elapsed time.Duration) { + p.rates.Update(eth.BlockHeadersMsg, elapsed, delivered) +} + +// UpdateBodyRate updates the peer's estimated body retrieval throughput with the +// current measurement. +func (p *peerConnection) UpdateBodyRate(delivered int, elapsed time.Duration) { + p.rates.Update(eth.BlockBodiesMsg, elapsed, delivered) +} + +// UpdateReceiptRate updates the peer's estimated receipt retrieval throughput +// with the current measurement. +func (p *peerConnection) UpdateReceiptRate(delivered int, elapsed time.Duration) { + p.rates.Update(eth.ReceiptsMsg, elapsed, delivered) +} + +// HeaderCapacity retrieves the peer's header download allowance based on its +// previously discovered throughput. +func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int { + cap := p.rates.Capacity(eth.BlockHeadersMsg, targetRTT) + if cap > MaxHeaderFetch { + cap = MaxHeaderFetch + } + return cap +} + +// BodyCapacity retrieves the peer's body download allowance based on its +// previously discovered throughput. +func (p *peerConnection) BodyCapacity(targetRTT time.Duration) int { + cap := p.rates.Capacity(eth.BlockBodiesMsg, targetRTT) + if cap > MaxBlockFetch { + cap = MaxBlockFetch + } + return cap +} + +// ReceiptCapacity retrieves the peers receipt download allowance based on its +// previously discovered throughput. +func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int { + cap := p.rates.Capacity(eth.ReceiptsMsg, targetRTT) + if cap > MaxReceiptFetch { + cap = MaxReceiptFetch + } + return cap +} + +// MarkLacking appends a new entity to the set of items (blocks, receipts, states) +// that a peer is known not to have (i.e. have been requested before). If the +// set reaches its maximum allowed capacity, items are randomly dropped off. +func (p *peerConnection) MarkLacking(hash common.Hash) { + p.lock.Lock() + defer p.lock.Unlock() + + for len(p.lacking) >= maxLackingHashes { + for drop := range p.lacking { + delete(p.lacking, drop) + break + } + } + p.lacking[hash] = struct{}{} +} + +// Lacks retrieves whether the hash of a blockchain item is on the peers lacking +// list (i.e. whether we know that the peer does not have it). +func (p *peerConnection) Lacks(hash common.Hash) bool { + p.lock.RLock() + defer p.lock.RUnlock() + + _, ok := p.lacking[hash] + return ok +} + +// peeringEvent is sent on the peer event feed when a remote peer connects or +// disconnects. +type peeringEvent struct { + peer *peerConnection + join bool +} + +// peerSet represents the collection of active peer participating in the chain +// download procedure. +type peerSet struct { + peers map[string]*peerConnection + rates *msgrate.Trackers // Set of rate trackers to give the sync a common beat + events event.Feed // Feed to publish peer lifecycle events on + + lock sync.RWMutex +} + +// newPeerSet creates a new peer set top track the active download sources. +func newPeerSet() *peerSet { + return &peerSet{ + peers: make(map[string]*peerConnection), + rates: msgrate.NewTrackers(log.New("proto", "eth")), + } +} + +// SubscribeEvents subscribes to peer arrival and departure events. +func (ps *peerSet) SubscribeEvents(ch chan<- *peeringEvent) event.Subscription { + return ps.events.Subscribe(ch) +} + +// Reset iterates over the current peer set, and resets each of the known peers +// to prepare for a next batch of block retrieval. +func (ps *peerSet) Reset() { + ps.lock.RLock() + defer ps.lock.RUnlock() + + for _, peer := range ps.peers { + peer.Reset() + } +} + +// Register injects a new peer into the working set, or returns an error if the +// peer is already known. +// +// The method also sets the starting throughput values of the new peer to the +// average of all existing peers, to give it a realistic chance of being used +// for data retrievals. +func (ps *peerSet) Register(p *peerConnection) error { + // Register the new peer with some meaningful defaults + ps.lock.Lock() + if _, ok := ps.peers[p.id]; ok { + ps.lock.Unlock() + return errAlreadyRegistered + } + p.rates = msgrate.NewTracker(ps.rates.MeanCapacities(), ps.rates.MedianRoundTrip()) + if err := ps.rates.Track(p.id, p.rates); err != nil { + ps.lock.Unlock() + return err + } + ps.peers[p.id] = p + ps.lock.Unlock() + + ps.events.Send(&peeringEvent{peer: p, join: true}) + return nil +} + +// Unregister removes a remote peer from the active set, disabling any further +// actions to/from that particular entity. +func (ps *peerSet) Unregister(id string) error { + ps.lock.Lock() + p, ok := ps.peers[id] + if !ok { + ps.lock.Unlock() + return errNotRegistered + } + delete(ps.peers, id) + ps.rates.Untrack(id) + ps.lock.Unlock() + + ps.events.Send(&peeringEvent{peer: p, join: false}) + return nil +} + +// Peer retrieves the registered peer with the given id. +func (ps *peerSet) Peer(id string) *peerConnection { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return ps.peers[id] +} + +// Len returns if the current number of peers in the set. +func (ps *peerSet) Len() int { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return len(ps.peers) +} + +// AllPeers retrieves a flat list of all the peers within the set. +func (ps *peerSet) AllPeers() []*peerConnection { + ps.lock.RLock() + defer ps.lock.RUnlock() + + list := make([]*peerConnection, 0, len(ps.peers)) + for _, p := range ps.peers { + list = append(list, p) + } + return list +} + +// peerCapacitySort implements sort.Interface. +// It sorts peer connections by capacity (descending). +type peerCapacitySort struct { + peers []*peerConnection + caps []int +} + +func (ps *peerCapacitySort) Len() int { + return len(ps.peers) +} + +func (ps *peerCapacitySort) Less(i, j int) bool { + return ps.caps[i] > ps.caps[j] +} + +func (ps *peerCapacitySort) Swap(i, j int) { + ps.peers[i], ps.peers[j] = ps.peers[j], ps.peers[i] + ps.caps[i], ps.caps[j] = ps.caps[j], ps.caps[i] +} diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go new file mode 100644 index 0000000..267c234 --- /dev/null +++ b/eth/downloader/queue.go @@ -0,0 +1,966 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains the block download scheduler to collect download tasks and schedule +// them in an ordered, and throttled way. + +package downloader + +import ( + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" +) + +const ( + bodyType = uint(0) + receiptType = uint(1) +) + +var ( + blockCacheMaxItems = 8192 // Maximum number of blocks to cache before throttling the download + blockCacheInitialItems = 2048 // Initial number of blocks to start fetching, before we know the sizes of the blocks + blockCacheMemory = 256 * 1024 * 1024 // Maximum amount of memory to use for block caching + blockCacheSizeWeight = 0.1 // Multiplier to approximate the average block size based on past ones +) + +var ( + errNoFetchesPending = errors.New("no fetches pending") + errStaleDelivery = errors.New("stale delivery") +) + +// fetchRequest is a currently running data retrieval operation. +type fetchRequest struct { + Peer *peerConnection // Peer to which the request was sent + From uint64 // Requested chain element index (used for skeleton fills only) + Headers []*types.Header // Requested headers, sorted by request order + Time time.Time // Time when the request was made +} + +// fetchResult is a struct collecting partial results from data fetchers until +// all outstanding pieces complete and the result as a whole can be processed. +type fetchResult struct { + pending atomic.Int32 // Flag telling what deliveries are outstanding + + Header *types.Header + Uncles []*types.Header + Transactions types.Transactions + Receipts types.Receipts + Withdrawals types.Withdrawals +} + +func newFetchResult(header *types.Header, fastSync bool) *fetchResult { + item := &fetchResult{ + Header: header, + } + if !header.EmptyBody() { + item.pending.Store(item.pending.Load() | (1 << bodyType)) + } else if header.WithdrawalsHash != nil { + item.Withdrawals = make(types.Withdrawals, 0) + } + if fastSync && !header.EmptyReceipts() { + item.pending.Store(item.pending.Load() | (1 << receiptType)) + } + return item +} + +// body returns a representation of the fetch result as a types.Body object. +func (f *fetchResult) body() types.Body { + return types.Body{ + Transactions: f.Transactions, + Uncles: f.Uncles, + Withdrawals: f.Withdrawals, + } +} + +// SetBodyDone flags the body as finished. +func (f *fetchResult) SetBodyDone() { + if v := f.pending.Load(); (v & (1 << bodyType)) != 0 { + f.pending.Add(-1) + } +} + +// AllDone checks if item is done. +func (f *fetchResult) AllDone() bool { + return f.pending.Load() == 0 +} + +// SetReceiptsDone flags the receipts as finished. +func (f *fetchResult) SetReceiptsDone() { + if v := f.pending.Load(); (v & (1 << receiptType)) != 0 { + f.pending.Add(-2) + } +} + +// Done checks if the given type is done already +func (f *fetchResult) Done(kind uint) bool { + v := f.pending.Load() + return v&(1< 0 +} + +// InFlightReceipts retrieves whether there are receipt fetch requests currently +// in flight. +func (q *queue) InFlightReceipts() bool { + q.lock.Lock() + defer q.lock.Unlock() + + return len(q.receiptPendPool) > 0 +} + +// Idle returns if the queue is fully idle or has some data still inside. +func (q *queue) Idle() bool { + q.lock.Lock() + defer q.lock.Unlock() + + queued := q.blockTaskQueue.Size() + q.receiptTaskQueue.Size() + pending := len(q.blockPendPool) + len(q.receiptPendPool) + + return (queued + pending) == 0 +} + +// ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill +// up an already retrieved header skeleton. +func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) { + q.lock.Lock() + defer q.lock.Unlock() + + // No skeleton retrieval can be in progress, fail hard if so (huge implementation bug) + if q.headerResults != nil { + panic("skeleton assembly already in progress") + } + // Schedule all the header retrieval tasks for the skeleton assembly + q.headerTaskPool = make(map[uint64]*types.Header) + q.headerTaskQueue = prque.New[int64, uint64](nil) + q.headerPeerMiss = make(map[string]map[uint64]struct{}) // Reset availability to correct invalid chains + q.headerResults = make([]*types.Header, len(skeleton)*MaxHeaderFetch) + q.headerHashes = make([]common.Hash, len(skeleton)*MaxHeaderFetch) + q.headerProced = 0 + q.headerOffset = from + q.headerContCh = make(chan bool, 1) + + for i, header := range skeleton { + index := from + uint64(i*MaxHeaderFetch) + + q.headerTaskPool[index] = header + q.headerTaskQueue.Push(index, -int64(index)) + } +} + +// RetrieveHeaders retrieves the header chain assemble based on the scheduled +// skeleton. +func (q *queue) RetrieveHeaders() ([]*types.Header, []common.Hash, int) { + q.lock.Lock() + defer q.lock.Unlock() + + headers, hashes, proced := q.headerResults, q.headerHashes, q.headerProced + q.headerResults, q.headerHashes, q.headerProced = nil, nil, 0 + + return headers, hashes, proced +} + +// Schedule adds a set of headers for the download queue for scheduling, returning +// the new headers encountered. +func (q *queue) Schedule(headers []*types.Header, hashes []common.Hash, from uint64) []*types.Header { + q.lock.Lock() + defer q.lock.Unlock() + + // Insert all the headers prioritised by the contained block number + inserts := make([]*types.Header, 0, len(headers)) + for i, header := range headers { + // Make sure chain order is honoured and preserved throughout + hash := hashes[i] + if header.Number == nil || header.Number.Uint64() != from { + log.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", from) + break + } + if q.headerHead != (common.Hash{}) && q.headerHead != header.ParentHash { + log.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) + break + } + // Make sure no duplicate requests are executed + // We cannot skip this, even if the block is empty, since this is + // what triggers the fetchResult creation. + if _, ok := q.blockTaskPool[hash]; ok { + log.Warn("Header already scheduled for block fetch", "number", header.Number, "hash", hash) + } else { + q.blockTaskPool[hash] = header + q.blockTaskQueue.Push(header, -int64(header.Number.Uint64())) + } + // Queue for receipt retrieval + if q.mode == SnapSync && !header.EmptyReceipts() { + if _, ok := q.receiptTaskPool[hash]; ok { + log.Warn("Header already scheduled for receipt fetch", "number", header.Number, "hash", hash) + } else { + q.receiptTaskPool[hash] = header + q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64())) + } + } + inserts = append(inserts, header) + q.headerHead = hash + from++ + } + return inserts +} + +// Results retrieves and permanently removes a batch of fetch results from +// the cache. the result slice will be empty if the queue has been closed. +// Results can be called concurrently with Deliver and Schedule, +// but assumes that there are not two simultaneous callers to Results +func (q *queue) Results(block bool) []*fetchResult { + // Abort early if there are no items and non-blocking requested + if !block && !q.resultCache.HasCompletedItems() { + return nil + } + closed := false + for !closed && !q.resultCache.HasCompletedItems() { + // In order to wait on 'active', we need to obtain the lock. + // That may take a while, if someone is delivering at the same + // time, so after obtaining the lock, we check again if there + // are any results to fetch. + // Also, in-between we ask for the lock and the lock is obtained, + // someone can have closed the queue. In that case, we should + // return the available results and stop blocking + q.lock.Lock() + if q.resultCache.HasCompletedItems() || q.closed { + q.lock.Unlock() + break + } + // No items available, and not closed + q.active.Wait() + closed = q.closed + q.lock.Unlock() + } + // Regardless if closed or not, we can still deliver whatever we have + results := q.resultCache.GetCompleted(maxResultsProcess) + for _, result := range results { + // Recalculate the result item weights to prevent memory exhaustion + size := result.Header.Size() + for _, uncle := range result.Uncles { + size += uncle.Size() + } + for _, receipt := range result.Receipts { + size += receipt.Size() + } + for _, tx := range result.Transactions { + size += common.StorageSize(tx.Size()) + } + q.resultSize = common.StorageSize(blockCacheSizeWeight)*size + + (1-common.StorageSize(blockCacheSizeWeight))*q.resultSize + } + // Using the newly calibrated resultsize, figure out the new throttle limit + // on the result cache + throttleThreshold := uint64((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize) + throttleThreshold = q.resultCache.SetThrottleThreshold(throttleThreshold) + + // With results removed from the cache, wake throttled fetchers + for _, ch := range []chan bool{q.blockWakeCh, q.receiptWakeCh} { + select { + case ch <- true: + default: + } + } + // Log some info at certain times + if time.Since(q.logTime) >= 60*time.Second { + q.logTime = time.Now() + + info := q.Stats() + info = append(info, "throttle", throttleThreshold) + log.Debug("Downloader queue stats", info...) + } + return results +} + +func (q *queue) Stats() []interface{} { + q.lock.RLock() + defer q.lock.RUnlock() + + return q.stats() +} + +func (q *queue) stats() []interface{} { + return []interface{}{ + "receiptTasks", q.receiptTaskQueue.Size(), + "blockTasks", q.blockTaskQueue.Size(), + "itemSize", q.resultSize, + } +} + +// ReserveHeaders reserves a set of headers for the given peer, skipping any +// previously failed batches. +func (q *queue) ReserveHeaders(p *peerConnection, count int) *fetchRequest { + q.lock.Lock() + defer q.lock.Unlock() + + // Short circuit if the peer's already downloading something (sanity check to + // not corrupt state) + if _, ok := q.headerPendPool[p.id]; ok { + return nil + } + // Retrieve a batch of hashes, skipping previously failed ones + send, skip := uint64(0), []uint64{} + for send == 0 && !q.headerTaskQueue.Empty() { + from, _ := q.headerTaskQueue.Pop() + if q.headerPeerMiss[p.id] != nil { + if _, ok := q.headerPeerMiss[p.id][from]; ok { + skip = append(skip, from) + continue + } + } + send = from + } + // Merge all the skipped batches back + for _, from := range skip { + q.headerTaskQueue.Push(from, -int64(from)) + } + // Assemble and return the block download request + if send == 0 { + return nil + } + request := &fetchRequest{ + Peer: p, + From: send, + Time: time.Now(), + } + q.headerPendPool[p.id] = request + return request +} + +// ReserveBodies reserves a set of body fetches for the given peer, skipping any +// previously failed downloads. Beside the next batch of needed fetches, it also +// returns a flag whether empty blocks were queued requiring processing. +func (q *queue) ReserveBodies(p *peerConnection, count int) (*fetchRequest, bool, bool) { + q.lock.Lock() + defer q.lock.Unlock() + + return q.reserveHeaders(p, count, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, bodyType) +} + +// ReserveReceipts reserves a set of receipt fetches for the given peer, skipping +// any previously failed downloads. Beside the next batch of needed fetches, it +// also returns a flag whether empty receipts were queued requiring importing. +func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bool, bool) { + q.lock.Lock() + defer q.lock.Unlock() + + return q.reserveHeaders(p, count, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, receiptType) +} + +// reserveHeaders reserves a set of data download operations for a given peer, +// skipping any previously failed ones. This method is a generic version used +// by the individual special reservation functions. +// +// Note, this method expects the queue lock to be already held for writing. The +// reason the lock is not obtained in here is because the parameters already need +// to access the queue, so they already need a lock anyway. +// +// Returns: +// +// item - the fetchRequest +// progress - whether any progress was made +// throttle - if the caller should throttle for a while +func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque[int64, *types.Header], + pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) { + // Short circuit if the pool has been depleted, or if the peer's already + // downloading something (sanity check not to corrupt state) + if taskQueue.Empty() { + return nil, false, true + } + if _, ok := pendPool[p.id]; ok { + return nil, false, false + } + // Retrieve a batch of tasks, skipping previously failed ones + send := make([]*types.Header, 0, count) + skip := make([]*types.Header, 0) + progress := false + throttled := false + for proc := 0; len(send) < count && !taskQueue.Empty(); proc++ { + // the task queue will pop items in order, so the highest prio block + // is also the lowest block number. + header, _ := taskQueue.Peek() + + // we can ask the resultcache if this header is within the + // "prioritized" segment of blocks. If it is not, we need to throttle + + stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == SnapSync) + if stale { + // Don't put back in the task queue, this item has already been + // delivered upstream + taskQueue.PopItem() + progress = true + delete(taskPool, header.Hash()) + proc = proc - 1 + log.Error("Fetch reservation already delivered", "number", header.Number.Uint64()) + continue + } + if throttle { + // There are no resultslots available. Leave it in the task queue + // However, if there are any left as 'skipped', we should not tell + // the caller to throttle, since we still want some other + // peer to fetch those for us + throttled = len(skip) == 0 + break + } + if err != nil { + // this most definitely should _not_ happen + log.Warn("Failed to reserve headers", "err", err) + // There are no resultslots available. Leave it in the task queue + break + } + if item.Done(kind) { + // If it's a noop, we can skip this task + delete(taskPool, header.Hash()) + taskQueue.PopItem() + proc = proc - 1 + progress = true + continue + } + // Remove it from the task queue + taskQueue.PopItem() + // Otherwise unless the peer is known not to have the data, add to the retrieve list + if p.Lacks(header.Hash()) { + skip = append(skip, header) + } else { + send = append(send, header) + } + } + // Merge all the skipped headers back + for _, header := range skip { + taskQueue.Push(header, -int64(header.Number.Uint64())) + } + if q.resultCache.HasCompletedItems() { + // Wake Results, resultCache was modified + q.active.Signal() + } + // Assemble and return the block download request + if len(send) == 0 { + return nil, progress, throttled + } + request := &fetchRequest{ + Peer: p, + Headers: send, + Time: time.Now(), + } + pendPool[p.id] = request + return request, progress, throttled +} + +// Revoke cancels all pending requests belonging to a given peer. This method is +// meant to be called during a peer drop to quickly reassign owned data fetches +// to remaining nodes. +func (q *queue) Revoke(peerID string) { + q.lock.Lock() + defer q.lock.Unlock() + + if request, ok := q.headerPendPool[peerID]; ok { + q.headerTaskQueue.Push(request.From, -int64(request.From)) + delete(q.headerPendPool, peerID) + } + if request, ok := q.blockPendPool[peerID]; ok { + for _, header := range request.Headers { + q.blockTaskQueue.Push(header, -int64(header.Number.Uint64())) + } + delete(q.blockPendPool, peerID) + } + if request, ok := q.receiptPendPool[peerID]; ok { + for _, header := range request.Headers { + q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64())) + } + delete(q.receiptPendPool, peerID) + } +} + +// ExpireHeaders cancels a request that timed out and moves the pending fetch +// task back into the queue for rescheduling. +func (q *queue) ExpireHeaders(peer string) int { + q.lock.Lock() + defer q.lock.Unlock() + + headerTimeoutMeter.Mark(1) + return q.expire(peer, q.headerPendPool, q.headerTaskQueue) +} + +// ExpireBodies checks for in flight block body requests that exceeded a timeout +// allowance, canceling them and returning the responsible peers for penalisation. +func (q *queue) ExpireBodies(peer string) int { + q.lock.Lock() + defer q.lock.Unlock() + + bodyTimeoutMeter.Mark(1) + return q.expire(peer, q.blockPendPool, q.blockTaskQueue) +} + +// ExpireReceipts checks for in flight receipt requests that exceeded a timeout +// allowance, canceling them and returning the responsible peers for penalisation. +func (q *queue) ExpireReceipts(peer string) int { + q.lock.Lock() + defer q.lock.Unlock() + + receiptTimeoutMeter.Mark(1) + return q.expire(peer, q.receiptPendPool, q.receiptTaskQueue) +} + +// expire is the generic check that moves a specific expired task from a pending +// pool back into a task pool. The syntax on the passed taskQueue is a bit weird +// as we would need a generic expire method to handle both types, but that is not +// supported at the moment at least (Go 1.19). +// +// Note, this method expects the queue lock to be already held. The reason the +// lock is not obtained in here is that the parameters already need to access +// the queue, so they already need a lock anyway. +func (q *queue) expire(peer string, pendPool map[string]*fetchRequest, taskQueue interface{}) int { + // Retrieve the request being expired and log an error if it's non-existent, + // as there's no order of events that should lead to such expirations. + req := pendPool[peer] + if req == nil { + log.Error("Expired request does not exist", "peer", peer) + return 0 + } + delete(pendPool, peer) + + // Return any non-satisfied requests to the pool + if req.From > 0 { + taskQueue.(*prque.Prque[int64, uint64]).Push(req.From, -int64(req.From)) + } + for _, header := range req.Headers { + taskQueue.(*prque.Prque[int64, *types.Header]).Push(header, -int64(header.Number.Uint64())) + } + return len(req.Headers) +} + +// DeliverHeaders injects a header retrieval response into the header results +// cache. This method either accepts all headers it received, or none of them +// if they do not map correctly to the skeleton. +// +// If the headers are accepted, the method makes an attempt to deliver the set +// of ready headers to the processor to keep the pipeline full. However, it will +// not block to prevent stalling other pending deliveries. +func (q *queue) DeliverHeaders(id string, headers []*types.Header, hashes []common.Hash, headerProcCh chan *headerTask) (int, error) { + q.lock.Lock() + defer q.lock.Unlock() + + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:16]) + } + // Short circuit if the data was never requested + request := q.headerPendPool[id] + if request == nil { + headerDropMeter.Mark(int64(len(headers))) + return 0, errNoFetchesPending + } + delete(q.headerPendPool, id) + + headerReqTimer.UpdateSince(request.Time) + headerInMeter.Mark(int64(len(headers))) + + // Ensure headers can be mapped onto the skeleton chain + target := q.headerTaskPool[request.From].Hash() + + accepted := len(headers) == MaxHeaderFetch + if accepted { + if headers[0].Number.Uint64() != request.From { + logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", hashes[0], "expected", request.From) + accepted = false + } else if hashes[len(headers)-1] != target { + logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", hashes[len(headers)-1], "expected", target) + accepted = false + } + } + if accepted { + parentHash := hashes[0] + for i, header := range headers[1:] { + hash := hashes[i+1] + if want := request.From + 1 + uint64(i); header.Number.Uint64() != want { + logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want) + accepted = false + break + } + if parentHash != header.ParentHash { + logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) + accepted = false + break + } + // Set-up parent hash for next round + parentHash = hash + } + } + // If the batch of headers wasn't accepted, mark as unavailable + if !accepted { + logger.Trace("Skeleton filling not accepted", "from", request.From) + headerDropMeter.Mark(int64(len(headers))) + + miss := q.headerPeerMiss[id] + if miss == nil { + q.headerPeerMiss[id] = make(map[uint64]struct{}) + miss = q.headerPeerMiss[id] + } + miss[request.From] = struct{}{} + + q.headerTaskQueue.Push(request.From, -int64(request.From)) + return 0, errors.New("delivery not accepted") + } + // Clean up a successful fetch and try to deliver any sub-results + copy(q.headerResults[request.From-q.headerOffset:], headers) + copy(q.headerHashes[request.From-q.headerOffset:], hashes) + + delete(q.headerTaskPool, request.From) + + ready := 0 + for q.headerProced+ready < len(q.headerResults) && q.headerResults[q.headerProced+ready] != nil { + ready += MaxHeaderFetch + } + if ready > 0 { + // Headers are ready for delivery, gather them and push forward (non blocking) + processHeaders := make([]*types.Header, ready) + copy(processHeaders, q.headerResults[q.headerProced:q.headerProced+ready]) + + processHashes := make([]common.Hash, ready) + copy(processHashes, q.headerHashes[q.headerProced:q.headerProced+ready]) + + select { + case headerProcCh <- &headerTask{ + headers: processHeaders, + hashes: processHashes, + }: + logger.Trace("Pre-scheduled new headers", "count", len(processHeaders), "from", processHeaders[0].Number) + q.headerProced += len(processHeaders) + default: + } + } + // Check for termination and return + if len(q.headerTaskPool) == 0 { + q.headerContCh <- false + } + return len(headers), nil +} + +// DeliverBodies injects a block body retrieval response into the results queue. +// The method returns the number of blocks bodies accepted from the delivery and +// also wakes any threads waiting for data delivery. +func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListHashes []common.Hash, + uncleLists [][]*types.Header, uncleListHashes []common.Hash, + withdrawalLists [][]*types.Withdrawal, withdrawalListHashes []common.Hash) (int, error) { + q.lock.Lock() + defer q.lock.Unlock() + + validate := func(index int, header *types.Header) error { + if txListHashes[index] != header.TxHash { + return errInvalidBody + } + if uncleListHashes[index] != header.UncleHash { + return errInvalidBody + } + if header.WithdrawalsHash == nil { + // nil hash means that withdrawals should not be present in body + if withdrawalLists[index] != nil { + return errInvalidBody + } + } else { // non-nil hash: body must have withdrawals + if withdrawalLists[index] == nil { + return errInvalidBody + } + if withdrawalListHashes[index] != *header.WithdrawalsHash { + return errInvalidBody + } + } + // Blocks must have a number of blobs corresponding to the header gas usage, + // and zero before the Cancun hardfork. + var blobs int + for _, tx := range txLists[index] { + // Count the number of blobs to validate against the header's blobGasUsed + blobs += len(tx.BlobHashes()) + + // Validate the data blobs individually too + if tx.Type() == types.BlobTxType { + if len(tx.BlobHashes()) == 0 { + return errInvalidBody + } + for _, hash := range tx.BlobHashes() { + if !kzg4844.IsValidVersionedHash(hash[:]) { + return errInvalidBody + } + } + if tx.BlobTxSidecar() != nil { + return errInvalidBody + } + } + } + if header.BlobGasUsed != nil { + if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated + return errInvalidBody + } + } else { + if blobs != 0 { + return errInvalidBody + } + } + return nil + } + + reconstruct := func(index int, result *fetchResult) { + result.Transactions = txLists[index] + result.Uncles = uncleLists[index] + result.Withdrawals = withdrawalLists[index] + result.SetBodyDone() + } + return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, + bodyReqTimer, bodyInMeter, bodyDropMeter, len(txLists), validate, reconstruct) +} + +// DeliverReceipts injects a receipt retrieval response into the results queue. +// The method returns the number of transaction receipts accepted from the delivery +// and also wakes any threads waiting for data delivery. +func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt, receiptListHashes []common.Hash) (int, error) { + q.lock.Lock() + defer q.lock.Unlock() + + validate := func(index int, header *types.Header) error { + if receiptListHashes[index] != header.ReceiptHash { + return errInvalidReceipt + } + return nil + } + reconstruct := func(index int, result *fetchResult) { + result.Receipts = receiptList[index] + result.SetReceiptsDone() + } + return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, + receiptReqTimer, receiptInMeter, receiptDropMeter, len(receiptList), validate, reconstruct) +} + +// deliver injects a data retrieval response into the results queue. +// +// Note, this method expects the queue lock to be already held for writing. The +// reason this lock is not obtained in here is because the parameters already need +// to access the queue, so they already need a lock anyway. +func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, + taskQueue *prque.Prque[int64, *types.Header], pendPool map[string]*fetchRequest, + reqTimer metrics.Timer, resInMeter metrics.Meter, resDropMeter metrics.Meter, + results int, validate func(index int, header *types.Header) error, + reconstruct func(index int, result *fetchResult)) (int, error) { + // Short circuit if the data was never requested + request := pendPool[id] + if request == nil { + resDropMeter.Mark(int64(results)) + return 0, errNoFetchesPending + } + delete(pendPool, id) + + reqTimer.UpdateSince(request.Time) + resInMeter.Mark(int64(results)) + + // If no data items were retrieved, mark them as unavailable for the origin peer + if results == 0 { + for _, header := range request.Headers { + request.Peer.MarkLacking(header.Hash()) + } + } + // Assemble each of the results with their headers and retrieved data parts + var ( + accepted int + failure error + i int + hashes []common.Hash + ) + for _, header := range request.Headers { + // Short circuit assembly if no more fetch results are found + if i >= results { + break + } + // Validate the fields + if err := validate(i, header); err != nil { + failure = err + break + } + hashes = append(hashes, header.Hash()) + i++ + } + + for _, header := range request.Headers[:i] { + if res, stale, err := q.resultCache.GetDeliverySlot(header.Number.Uint64()); err == nil && !stale { + reconstruct(accepted, res) + } else { + // else: between here and above, some other peer filled this result, + // or it was indeed a no-op. This should not happen, but if it does it's + // not something to panic about + log.Error("Delivery stale", "stale", stale, "number", header.Number.Uint64(), "err", err) + failure = errStaleDelivery + } + // Clean up a successful fetch + delete(taskPool, hashes[accepted]) + accepted++ + } + resDropMeter.Mark(int64(results - accepted)) + + // Return all failed or missing fetches to the queue + for _, header := range request.Headers[accepted:] { + taskQueue.Push(header, -int64(header.Number.Uint64())) + } + // Wake up Results + if accepted > 0 { + q.active.Signal() + } + if failure == nil { + return accepted, nil + } + // If none of the data was good, it's a stale delivery + if accepted > 0 { + return accepted, fmt.Errorf("partial failure: %v", failure) + } + return accepted, fmt.Errorf("%w: %v", failure, errStaleDelivery) +} + +// Prepare configures the result cache to allow accepting and caching inbound +// fetch results. +func (q *queue) Prepare(offset uint64, mode SyncMode) { + q.lock.Lock() + defer q.lock.Unlock() + + // Prepare the queue for sync results + q.resultCache.Prepare(offset) + q.mode = mode +} diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go new file mode 100644 index 0000000..857ac48 --- /dev/null +++ b/eth/downloader/queue_test.go @@ -0,0 +1,474 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package downloader + +import ( + "fmt" + "log/slog" + "math/big" + "math/rand" + "os" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +// makeChain creates a chain of n blocks starting at and including parent. +// the returned hash chain is ordered head->parent. In addition, every 3rd block +// contains a transaction and every 5th an uncle to allow testing correct block +// reassembly. +func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Block, []types.Receipts) { + blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) { + block.SetCoinbase(common.Address{seed}) + // Add one tx to every secondblock + if !empty && i%2 == 0 { + signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) + if err != nil { + panic(err) + } + block.AddTx(tx) + } + }) + return blocks, receipts +} + +type chainData struct { + blocks []*types.Block + offset int +} + +var chain *chainData +var emptyChain *chainData + +func init() { + // Create a chain of blocks to import + targetBlocks := 128 + blocks, _ := makeChain(targetBlocks, 0, testGenesis, false) + chain = &chainData{blocks, 0} + + blocks, _ = makeChain(targetBlocks, 0, testGenesis, true) + emptyChain = &chainData{blocks, 0} +} + +func (chain *chainData) headers() []*types.Header { + hdrs := make([]*types.Header, len(chain.blocks)) + for i, b := range chain.blocks { + hdrs[i] = b.Header() + } + return hdrs +} + +func (chain *chainData) Len() int { + return len(chain.blocks) +} + +func dummyPeer(id string) *peerConnection { + p := &peerConnection{ + id: id, + lacking: make(map[common.Hash]struct{}), + } + return p +} + +func TestBasics(t *testing.T) { + numOfBlocks := len(emptyChain.blocks) + numOfReceipts := len(emptyChain.blocks) / 2 + + q := newQueue(10, 10) + if !q.Idle() { + t.Errorf("new queue should be idle") + } + q.Prepare(1, SnapSync) + if res := q.Results(false); len(res) != 0 { + t.Fatal("new queue should have 0 results") + } + + // Schedule a batch of headers + headers := chain.headers() + hashes := make([]common.Hash, len(headers)) + for i, header := range headers { + hashes[i] = header.Hash() + } + q.Schedule(headers, hashes, 1) + if q.Idle() { + t.Errorf("queue should not be idle") + } + if got, exp := q.PendingBodies(), chain.Len(); got != exp { + t.Errorf("wrong pending block count, got %d, exp %d", got, exp) + } + // Only non-empty receipts get added to task-queue + if got, exp := q.PendingReceipts(), 64; got != exp { + t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp) + } + // Items are now queued for downloading, next step is that we tell the + // queue that a certain peer will deliver them for us + { + peer := dummyPeer("peer-1") + fetchReq, _, throttle := q.ReserveBodies(peer, 50) + if !throttle { + // queue size is only 10, so throttling should occur + t.Fatal("should throttle") + } + // But we should still get the first things to fetch + if got, exp := len(fetchReq.Headers), 5; got != exp { + t.Fatalf("expected %d requests, got %d", exp, got) + } + if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp { + t.Fatalf("expected header %d, got %d", exp, got) + } + } + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } + { + peer := dummyPeer("peer-2") + fetchReq, _, throttle := q.ReserveBodies(peer, 50) + + // The second peer should hit throttling + if !throttle { + t.Fatalf("should throttle") + } + // And not get any fetches at all, since it was throttled to begin with + if fetchReq != nil { + t.Fatalf("should have no fetches, got %d", len(fetchReq.Headers)) + } + } + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } + { + // The receipt delivering peer should not be affected + // by the throttling of body deliveries + peer := dummyPeer("peer-3") + fetchReq, _, throttle := q.ReserveReceipts(peer, 50) + if !throttle { + // queue size is only 10, so throttling should occur + t.Fatal("should throttle") + } + // But we should still get the first things to fetch + if got, exp := len(fetchReq.Headers), 5; got != exp { + t.Fatalf("expected %d requests, got %d", exp, got) + } + if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp { + t.Fatalf("expected header %d, got %d", exp, got) + } + } + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts-5; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } + if got, exp := q.resultCache.countCompleted(), 0; got != exp { + t.Errorf("wrong processable count, got %d, exp %d", got, exp) + } +} + +func TestEmptyBlocks(t *testing.T) { + numOfBlocks := len(emptyChain.blocks) + + q := newQueue(10, 10) + + q.Prepare(1, SnapSync) + + // Schedule a batch of headers + headers := emptyChain.headers() + hashes := make([]common.Hash, len(headers)) + for i, header := range headers { + hashes[i] = header.Hash() + } + q.Schedule(headers, hashes, 1) + if q.Idle() { + t.Errorf("queue should not be idle") + } + if got, exp := q.PendingBodies(), len(emptyChain.blocks); got != exp { + t.Errorf("wrong pending block count, got %d, exp %d", got, exp) + } + if got, exp := q.PendingReceipts(), 0; got != exp { + t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp) + } + // They won't be processable, because the fetchresults haven't been + // created yet + if got, exp := q.resultCache.countCompleted(), 0; got != exp { + t.Errorf("wrong processable count, got %d, exp %d", got, exp) + } + + // Items are now queued for downloading, next step is that we tell the + // queue that a certain peer will deliver them for us + // That should trigger all of them to suddenly become 'done' + { + // Reserve blocks + peer := dummyPeer("peer-1") + fetchReq, _, _ := q.ReserveBodies(peer, 50) + + // there should be nothing to fetch, blocks are empty + if fetchReq != nil { + t.Fatal("there should be no body fetch tasks remaining") + } + } + if q.blockTaskQueue.Size() != numOfBlocks-10 { + t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size()) + } + if q.receiptTaskQueue.Size() != 0 { + t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size()) + } + { + peer := dummyPeer("peer-3") + fetchReq, _, _ := q.ReserveReceipts(peer, 50) + + // there should be nothing to fetch, blocks are empty + if fetchReq != nil { + t.Fatal("there should be no receipt fetch tasks remaining") + } + } + if q.blockTaskQueue.Size() != numOfBlocks-10 { + t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size()) + } + if q.receiptTaskQueue.Size() != 0 { + t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size()) + } + if got, exp := q.resultCache.countCompleted(), 10; got != exp { + t.Errorf("wrong processable count, got %d, exp %d", got, exp) + } +} + +// XTestDelivery does some more extensive testing of events that happen, +// blocks that become known and peers that make reservations and deliveries. +// disabled since it's not really a unit-test, but can be executed to test +// some more advanced scenarios +func XTestDelivery(t *testing.T) { + // the outside network, holding blocks + blo, rec := makeChain(128, 0, testGenesis, false) + world := newNetwork() + world.receipts = rec + world.chain = blo + world.progress(10) + if false { + log.SetDefault(log.NewLogger(slog.NewTextHandler(os.Stdout, nil))) + } + q := newQueue(10, 10) + var wg sync.WaitGroup + q.Prepare(1, SnapSync) + wg.Add(1) + go func() { + // deliver headers + defer wg.Done() + c := 1 + for { + //fmt.Printf("getting headers from %d\n", c) + headers := world.headers(c) + hashes := make([]common.Hash, len(headers)) + for i, header := range headers { + hashes[i] = header.Hash() + } + l := len(headers) + //fmt.Printf("scheduling %d headers, first %d last %d\n", + // l, headers[0].Number.Uint64(), headers[len(headers)-1].Number.Uint64()) + q.Schedule(headers, hashes, uint64(c)) + c += l + } + }() + wg.Add(1) + go func() { + // collect results + defer wg.Done() + tot := 0 + for { + res := q.Results(true) + tot += len(res) + fmt.Printf("got %d results, %d tot\n", len(res), tot) + // Now we can forget about these + world.forget(res[len(res)-1].Header.Number.Uint64()) + } + }() + wg.Add(1) + go func() { + defer wg.Done() + // reserve body fetch + i := 4 + for { + peer := dummyPeer(fmt.Sprintf("peer-%d", i)) + f, _, _ := q.ReserveBodies(peer, rand.Intn(30)) + if f != nil { + var ( + emptyList []*types.Header + txset [][]*types.Transaction + uncleset [][]*types.Header + ) + numToSkip := rand.Intn(len(f.Headers)) + for _, hdr := range f.Headers[0 : len(f.Headers)-numToSkip] { + txset = append(txset, world.getTransactions(hdr.Number.Uint64())) + uncleset = append(uncleset, emptyList) + } + var ( + txsHashes = make([]common.Hash, len(txset)) + uncleHashes = make([]common.Hash, len(uncleset)) + ) + hasher := trie.NewStackTrie(nil) + for i, txs := range txset { + txsHashes[i] = types.DeriveSha(types.Transactions(txs), hasher) + } + for i, uncles := range uncleset { + uncleHashes[i] = types.CalcUncleHash(uncles) + } + time.Sleep(100 * time.Millisecond) + _, err := q.DeliverBodies(peer.id, txset, txsHashes, uncleset, uncleHashes, nil, nil) + if err != nil { + fmt.Printf("delivered %d bodies %v\n", len(txset), err) + } + } else { + i++ + time.Sleep(200 * time.Millisecond) + } + } + }() + go func() { + defer wg.Done() + // reserve receiptfetch + peer := dummyPeer("peer-3") + for { + f, _, _ := q.ReserveReceipts(peer, rand.Intn(50)) + if f != nil { + var rcs [][]*types.Receipt + for _, hdr := range f.Headers { + rcs = append(rcs, world.getReceipts(hdr.Number.Uint64())) + } + hasher := trie.NewStackTrie(nil) + hashes := make([]common.Hash, len(rcs)) + for i, receipt := range rcs { + hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher) + } + _, err := q.DeliverReceipts(peer.id, rcs, hashes) + if err != nil { + fmt.Printf("delivered %d receipts %v\n", len(rcs), err) + } + time.Sleep(100 * time.Millisecond) + } else { + time.Sleep(200 * time.Millisecond) + } + } + }() + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 50; i++ { + time.Sleep(300 * time.Millisecond) + //world.tick() + //fmt.Printf("trying to progress\n") + world.progress(rand.Intn(100)) + } + for i := 0; i < 50; i++ { + time.Sleep(2990 * time.Millisecond) + } + }() + wg.Add(1) + go func() { + defer wg.Done() + for { + time.Sleep(990 * time.Millisecond) + fmt.Printf("world block tip is %d\n", + world.chain[len(world.chain)-1].Header().Number.Uint64()) + fmt.Println(q.Stats()) + } + }() + wg.Wait() +} + +func newNetwork() *network { + var l sync.RWMutex + return &network{ + cond: sync.NewCond(&l), + offset: 1, // block 1 is at blocks[0] + } +} + +// represents the network +type network struct { + offset int + chain []*types.Block + receipts []types.Receipts + lock sync.RWMutex + cond *sync.Cond +} + +func (n *network) getTransactions(blocknum uint64) types.Transactions { + index := blocknum - uint64(n.offset) + return n.chain[index].Transactions() +} +func (n *network) getReceipts(blocknum uint64) types.Receipts { + index := blocknum - uint64(n.offset) + if got := n.chain[index].Header().Number.Uint64(); got != blocknum { + fmt.Printf("Err, got %d exp %d\n", got, blocknum) + panic("sd") + } + return n.receipts[index] +} + +func (n *network) forget(blocknum uint64) { + index := blocknum - uint64(n.offset) + n.chain = n.chain[index:] + n.receipts = n.receipts[index:] + n.offset = int(blocknum) +} +func (n *network) progress(numBlocks int) { + n.lock.Lock() + defer n.lock.Unlock() + //fmt.Printf("progressing...\n") + newBlocks, newR := makeChain(numBlocks, 0, n.chain[len(n.chain)-1], false) + n.chain = append(n.chain, newBlocks...) + n.receipts = append(n.receipts, newR...) + n.cond.Broadcast() +} + +func (n *network) headers(from int) []*types.Header { + numHeaders := 128 + var hdrs []*types.Header + index := from - n.offset + + for index >= len(n.chain) { + // wait for progress + n.cond.L.Lock() + //fmt.Printf("header going into wait\n") + n.cond.Wait() + index = from - n.offset + n.cond.L.Unlock() + } + n.lock.RLock() + defer n.lock.RUnlock() + for i, b := range n.chain[index:] { + hdrs = append(hdrs, b.Header()) + if i >= numHeaders { + break + } + } + return hdrs +} diff --git a/eth/downloader/resultstore.go b/eth/downloader/resultstore.go new file mode 100644 index 0000000..e4323c0 --- /dev/null +++ b/eth/downloader/resultstore.go @@ -0,0 +1,195 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package downloader + +import ( + "fmt" + "sync" + "sync/atomic" + + "github.com/ethereum/go-ethereum/core/types" +) + +// resultStore implements a structure for maintaining fetchResults, tracking their +// download-progress and delivering (finished) results. +type resultStore struct { + items []*fetchResult // Downloaded but not yet delivered fetch results + resultOffset uint64 // Offset of the first cached fetch result in the block chain + + // Internal index of first non-completed entry, updated atomically when needed. + // If all items are complete, this will equal length(items), so + // *important* : is not safe to use for indexing without checking against length + indexIncomplete atomic.Int32 + + // throttleThreshold is the limit up to which we _want_ to fill the + // results. If blocks are large, we want to limit the results to less + // than the number of available slots, and maybe only fill 1024 out of + // 8192 possible places. The queue will, at certain times, recalibrate + // this index. + throttleThreshold uint64 + + lock sync.RWMutex +} + +func newResultStore(size int) *resultStore { + return &resultStore{ + resultOffset: 0, + items: make([]*fetchResult, size), + throttleThreshold: uint64(size), + } +} + +// SetThrottleThreshold updates the throttling threshold based on the requested +// limit and the total queue capacity. It returns the (possibly capped) threshold +func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { + r.lock.Lock() + defer r.lock.Unlock() + + limit := uint64(len(r.items)) + if threshold >= limit { + threshold = limit + } + r.throttleThreshold = threshold + return r.throttleThreshold +} + +// AddFetch adds a header for body/receipt fetching. This is used when the queue +// wants to reserve headers for fetching. +// +// It returns the following: +// +// stale - if true, this item is already passed, and should not be requested again +// throttled - if true, the store is at capacity, this particular header is not prio now +// item - the result to store data into +// err - any error that occurred +func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) { + r.lock.Lock() + defer r.lock.Unlock() + + var index int + item, index, stale, throttled, err = r.getFetchResult(header.Number.Uint64()) + if err != nil || stale || throttled { + return stale, throttled, item, err + } + if item == nil { + item = newFetchResult(header, fastSync) + r.items[index] = item + } + return stale, throttled, item, err +} + +// GetDeliverySlot returns the fetchResult for the given header. If the 'stale' flag +// is true, that means the header has already been delivered 'upstream'. This method +// does not bubble up the 'throttle' flag, since it's moot at the point in time when +// the item is downloaded and ready for delivery +func (r *resultStore) GetDeliverySlot(headerNumber uint64) (*fetchResult, bool, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + res, _, stale, _, err := r.getFetchResult(headerNumber) + return res, stale, err +} + +// getFetchResult returns the fetchResult corresponding to the given item, and +// the index where the result is stored. +func (r *resultStore) getFetchResult(headerNumber uint64) (item *fetchResult, index int, stale, throttle bool, err error) { + index = int(int64(headerNumber) - int64(r.resultOffset)) + throttle = index >= int(r.throttleThreshold) + stale = index < 0 + + if index >= len(r.items) { + err = fmt.Errorf("%w: index allocation went beyond available resultStore space "+ + "(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d", errInvalidChain, + index, headerNumber, r.resultOffset, len(r.items)) + return nil, index, stale, throttle, err + } + if stale { + return nil, index, stale, throttle, nil + } + item = r.items[index] + return item, index, stale, throttle, nil +} + +// HasCompletedItems returns true if there are processable items available +// this method is cheaper than countCompleted +func (r *resultStore) HasCompletedItems() bool { + r.lock.RLock() + defer r.lock.RUnlock() + + if len(r.items) == 0 { + return false + } + if item := r.items[0]; item != nil && item.AllDone() { + return true + } + return false +} + +// countCompleted returns the number of items ready for delivery, stopping at +// the first non-complete item. +// +// The method assumes (at least) rlock is held. +func (r *resultStore) countCompleted() int { + // We iterate from the already known complete point, and see + // if any more has completed since last count + index := r.indexIncomplete.Load() + for ; ; index++ { + if index >= int32(len(r.items)) { + break + } + result := r.items[index] + if result == nil || !result.AllDone() { + break + } + } + r.indexIncomplete.Store(index) + return int(index) +} + +// GetCompleted returns the next batch of completed fetchResults +func (r *resultStore) GetCompleted(limit int) []*fetchResult { + r.lock.Lock() + defer r.lock.Unlock() + + completed := r.countCompleted() + if limit > completed { + limit = completed + } + results := make([]*fetchResult, limit) + copy(results, r.items[:limit]) + + // Delete the results from the cache and clear the tail. + copy(r.items, r.items[limit:]) + for i := len(r.items) - limit; i < len(r.items); i++ { + r.items[i] = nil + } + // Advance the expected block number of the first cache entry + r.resultOffset += uint64(limit) + r.indexIncomplete.Add(int32(-limit)) + + return results +} + +// Prepare initialises the offset with the given block number +func (r *resultStore) Prepare(offset uint64) { + r.lock.Lock() + defer r.lock.Unlock() + + if r.resultOffset < offset { + r.resultOffset = offset + } +} diff --git a/eth/downloader/skeleton.go b/eth/downloader/skeleton.go new file mode 100644 index 0000000..04421a2 --- /dev/null +++ b/eth/downloader/skeleton.go @@ -0,0 +1,1258 @@ +// 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 downloader + +import ( + "encoding/json" + "errors" + "fmt" + "math/rand" + "sort" + "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/eth/protocols/eth" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// scratchHeaders is the number of headers to store in a scratch space to allow +// concurrent downloads. A header is about 0.5KB in size, so there is no worry +// about using too much memory. The only catch is that we can only validate gaps +// after they're linked to the head, so the bigger the scratch space, the larger +// potential for invalid headers. +// +// The current scratch space of 131072 headers is expected to use 64MB RAM. +const scratchHeaders = 131072 + +// requestHeaders is the number of header to request from a remote peer in a single +// network packet. Although the skeleton downloader takes into consideration peer +// capacities when picking idlers, the packet size was decided to remain constant +// since headers are relatively small and it's easier to work with fixed batches +// vs. dynamic interval fillings. +const requestHeaders = 512 + +// errSyncLinked is an internal helper error to signal that the current sync +// cycle linked up to the genesis block, this the skeleton syncer should ping +// the backfiller to resume. Since we already have that logic on sync start, +// piggy-back on that instead of 2 entrypoints. +var errSyncLinked = errors.New("sync linked") + +// errSyncMerged is an internal helper error to signal that the current sync +// cycle merged with a previously aborted subchain, thus the skeleton syncer +// should abort and restart with the new state. +var errSyncMerged = errors.New("sync merged") + +// errSyncReorged is an internal helper error to signal that the head chain of +// the current sync cycle was (partially) reorged, thus the skeleton syncer +// should abort and restart with the new state. +var errSyncReorged = errors.New("sync reorged") + +// errTerminated is returned if the sync mechanism was terminated for this run of +// the process. This is usually the case when Geth is shutting down and some events +// might still be propagating. +var errTerminated = errors.New("terminated") + +// 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 + // full to peers. It's a useless cornercase to support a dangling half-group. + if scratchHeaders%requestHeaders != 0 { + panic("Please make scratchHeaders divisible by requestHeaders") + } +} + +// subchain is a contiguous header chain segment that is backed by the database, +// but may not be linked to the live chain. The skeleton downloader may produce +// a new one of these every time it is restarted until the subchain grows large +// enough to connect with a previous subchain. +// +// The subchains use the exact same database namespace and are not disjoint from +// each other. As such, extending one to overlap the other entails reducing the +// second one first. This combined buffer model is used to avoid having to move +// data on disk when two subchains are joined together. +type subchain struct { + Head uint64 // Block number of the newest header in the subchain + Tail uint64 // Block number of the oldest header in the subchain + Next common.Hash // Block hash of the next oldest header in the subchain +} + +// skeletonProgress is a database entry to allow suspending and resuming a chain +// sync. As the skeleton header chain is downloaded backwards, restarts can and +// will produce temporarily disjoint subchains. There is no way to restart a +// suspended skeleton sync without prior knowledge of all prior suspension points. +type skeletonProgress struct { + Subchains []*subchain // Disjoint subchains downloaded until now + Finalized *uint64 // Last known finalized block number +} + +// headUpdate is a notification that the beacon sync should switch to a new target. +// The update might request whether to forcefully change the target, or only try to +// extend it and fail if it's not possible. +type headUpdate struct { + header *types.Header // Header to update the sync target to + final *types.Header // Finalized header to use as thresholds + force bool // Whether to force the update or only extend if possible + errc chan error // Channel to signal acceptance of the new head +} + +// headerRequest tracks a pending header request to ensure responses are to +// actual requests and to validate any security constraints. +// +// Concurrency note: header requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. subchains). +// That is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type headerRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + deliver chan *headerResponse // Channel to deliver successful response on + revert chan *headerRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + stale chan struct{} // Channel to signal the request was dropped + + head uint64 // Head number of the requested batch of headers +} + +// headerResponse is an already verified remote response to a header request. +type headerResponse struct { + peer *peerConnection // Peer from which this response originates + reqid uint64 // Request ID that this response fulfils + headers []*types.Header // Chain of headers +} + +// backfiller is a callback interface through which the skeleton sync can tell +// the downloader that it should suspend or resume backfilling on specific head +// events (e.g. suspend on forks or gaps, resume on successful linkups). +type backfiller interface { + // suspend requests the backfiller to abort any running full or snap sync + // based on the skeleton chain as it might be invalid. The backfiller should + // gracefully handle multiple consecutive suspends without a resume, even + // on initial startup. + // + // The method should return the last block header that has been successfully + // backfilled (in the current or a previous run), falling back to the genesis. + suspend() *types.Header + + // resume requests the backfiller to start running fill or snap sync based on + // the skeleton chain as it has successfully been linked. Appending new heads + // to the end of the chain will not result in suspend/resume cycles. + // leaking too much sync logic out to the filler. + resume() +} + +// skeleton represents a header chain synchronized after the merge where blocks +// aren't validated any more via PoW in a forward fashion, rather are dictated +// and extended at the head via the beacon chain and backfilled on the original +// Ethereum block sync protocol. +// +// Since the skeleton is grown backwards from head to genesis, it is handled as +// a separate entity, not mixed in with the logical sequential transition of the +// blocks. Once the skeleton is connected to an existing, validated chain, the +// headers will be moved into the main downloader for filling and execution. +// +// Opposed to the original Ethereum block synchronization which is trustless (and +// uses a master peer to minimize the attack surface), post-merge block sync starts +// from a trusted head. As such, there is no need for a master peer any more and +// headers can be requested fully concurrently (though some batches might be +// discarded if they don't link up correctly). +// +// Although a skeleton is part of a sync cycle, it is not recreated, rather stays +// alive throughout the lifetime of the downloader. This allows it to be extended +// concurrently with the sync cycle, since extensions arrive from an API surface, +// not from within (vs. legacy Ethereum sync). +// +// Since the skeleton tracks the entire header chain until it is consumed by the +// forward block filling, it needs 0.5KB/block storage. At current mainnet sizes +// this is only possible with a disk backend. Since the skeleton is separate from +// the node's header chain, storing the headers ephemerally until sync finishes +// is wasted disk IO, but it's a price we're going to pay to keep things simple +// for now. +type skeleton struct { + db ethdb.Database // Database backing the skeleton + filler backfiller // Chain syncer suspended/resumed by head events + + peers *peerSet // Set of peers we can sync from + idles map[string]*peerConnection // Set of idle peers in the current sync cycle + drop peerDropFn // Drops a peer for misbehaving + + progress *skeletonProgress // Sync progress tracker for resumption and metrics + started time.Time // Timestamp when the skeleton syncer was created + logged time.Time // Timestamp when progress was last logged to the user + pulled uint64 // Number of headers downloaded in this run + + scratchSpace []*types.Header // Scratch space to accumulate headers in (first = recent) + scratchOwners []string // Peer IDs owning chunks of the scratch space (pend or delivered) + scratchHead uint64 // Block number of the first item in the scratch space + + requests map[uint64]*headerRequest // Header requests currently running + + headEvents chan *headUpdate // Notification channel for new heads + terminate chan chan error // Termination channel to abort sync + terminated chan struct{} // Channel to signal that the syncer is dead + + // Callback hooks used during testing + syncStarting func() // callback triggered after a sync cycle is inited but before started +} + +// newSkeleton creates a new sync skeleton that tracks a potentially dangling +// header chain until it's linked into an existing set of blocks. +func newSkeleton(db ethdb.Database, peers *peerSet, drop peerDropFn, filler backfiller) *skeleton { + sk := &skeleton{ + db: db, + filler: filler, + peers: peers, + drop: drop, + requests: make(map[uint64]*headerRequest), + headEvents: make(chan *headUpdate), + terminate: make(chan chan error), + terminated: make(chan struct{}), + } + go sk.startup() + return sk +} + +// startup is an initial background loop which waits for an event to start or +// tear the syncer down. This is required to make the skeleton sync loop once +// per process but at the same time not start before the beacon chain announces +// a new (existing) head. +func (s *skeleton) startup() { + // Close a notification channel so anyone sending us events will know if the + // sync loop was torn down for good. + defer close(s.terminated) + + // Wait for startup or teardown. This wait might loop a few times if a beacon + // client requests sync head extensions, but not forced reorgs (i.e. they are + // giving us new payloads without setting a starting head initially). + for { + select { + case errc := <-s.terminate: + // No head was announced but Geth is shutting down + errc <- nil + return + + case event := <-s.headEvents: + // New head announced, start syncing to it, looping every time a current + // cycle is terminated due to a chain event (head reorg, old chain merge). + if !event.force { + event.errc <- errors.New("forced head needed for startup") + continue + } + event.errc <- nil // forced head accepted for startup + head := event.header + s.started = time.Now() + + for { + // If the sync cycle terminated or was terminated, propagate up when + // higher layers request termination. There's no fancy explicit error + // signalling as the sync loop should never terminate (TM). + newhead, err := s.sync(head) + switch { + case err == errSyncLinked: + // 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: + // Subchains were merged, we just need to reinit the internal + // start to continue on the tail of the merged chain. Don't + // announce a new head, + head = nil + + case err == errSyncReorged: + // The subchain being synced got modified at the head in a + // way that requires resyncing it. Restart sync with the new + // head to force a cleanup. + head = newhead + + case err == errTerminated: + // Sync was requested to be terminated from within, stop and + // return (no need to pass a message, was already done internally) + return + + default: + // Sync either successfully terminated or failed with an unhandled + // error. Abort and wait until Geth requests a termination. + errc := <-s.terminate + errc <- err + return + } + } + } + } +} + +// Terminate tears down the syncer indefinitely. +func (s *skeleton) Terminate() error { + // Request termination and fetch any errors + errc := make(chan error) + s.terminate <- errc + err := <-errc + + // Wait for full shutdown (not necessary, but cleaner) + <-s.terminated + return err +} + +// Sync starts or resumes a previous sync cycle to download and maintain a reverse +// header chain starting at the head and leading towards genesis to an available +// ancestor. +// +// This method does not block, rather it just waits until the syncer receives the +// fed header. What the syncer does with it is the syncer's problem. +func (s *skeleton) Sync(head *types.Header, final *types.Header, force bool) error { + log.Trace("New skeleton head announced", "number", head.Number, "hash", head.Hash(), "force", force) + errc := make(chan error) + + select { + case s.headEvents <- &headUpdate{header: head, final: final, force: force, errc: errc}: + return <-errc + case <-s.terminated: + return errTerminated + } +} + +// sync is the internal version of Sync that executes a single sync cycle, either +// until some termination condition is reached, or until the current cycle merges +// with a previously aborted run. +func (s *skeleton) sync(head *types.Header) (*types.Header, error) { + // If we're continuing a previous merge interrupt, just access the existing + // old state without initing from disk. + if head == nil { + head = rawdb.ReadSkeletonHeader(s.db, s.progress.Subchains[0].Head) + } else { + // Otherwise, initialize the sync, trimming and previous leftovers until + // we're consistent with the newly requested chain head + s.initSync(head) + } + // Create the scratch space to fill with concurrently downloaded headers + s.scratchSpace = make([]*types.Header, scratchHeaders) + defer func() { s.scratchSpace = nil }() // don't hold on to references after sync + + s.scratchOwners = make([]string, scratchHeaders/requestHeaders) + defer func() { s.scratchOwners = nil }() // don't hold on to references after sync + + s.scratchHead = s.progress.Subchains[0].Tail - 1 // tail must not be 0! + + // If the sync is already done, resume the backfiller. When the loop stops, + // terminate the backfiller too. + linked := len(s.progress.Subchains) == 1 && + rawdb.HasHeader(s.db, s.progress.Subchains[0].Next, s.scratchHead) && + rawdb.HasBody(s.db, s.progress.Subchains[0].Next, s.scratchHead) && + rawdb.HasReceipts(s.db, s.progress.Subchains[0].Next, s.scratchHead) + if linked { + s.filler.resume() + } + defer func() { + // The filler needs to be suspended, but since it can block for a while + // when there are many blocks queued up for full-sync importing, run it + // on a separate goroutine and consume head messages that need instant + // replies. + done := make(chan struct{}) + go func() { + defer close(done) + filled := s.filler.suspend() + if filled == nil { + log.Error("Latest filled block is not available") + return + } + // If something was filled, try to delete stale sync helpers. If + // unsuccessful, warn the user, but not much else we can do (it's + // a programming error, just let users report an issue and don't + // choke in the meantime). + if err := s.cleanStales(filled); err != nil { + log.Error("Failed to clean stale beacon headers", "err", err) + } + }() + // Wait for the suspend to finish, consuming head events in the meantime + // and dropping them on the floor. + for { + select { + case <-done: + return + case event := <-s.headEvents: + event.errc <- errors.New("beacon syncer reorging") + } + } + }() + // Create a set of unique channels for this sync cycle. We need these to be + // ephemeral so a data race doesn't accidentally deliver something stale on + // a persistent channel across syncs (yup, this happened) + var ( + requestFails = make(chan *headerRequest) + responses = make(chan *headerResponse) + ) + cancel := make(chan struct{}) + defer close(cancel) + + log.Debug("Starting reverse header sync cycle", "head", head.Number, "hash", head.Hash(), "cont", s.scratchHead) + + // Whether sync completed or not, disregard any future packets + defer func() { + log.Debug("Terminating reverse header sync cycle", "head", head.Number, "hash", head.Hash(), "cont", s.scratchHead) + s.requests = make(map[uint64]*headerRequest) + }() + + // Start tracking idle peers for task assignments + peering := make(chan *peeringEvent, 64) // arbitrary buffer, just some burst protection + + peeringSub := s.peers.SubscribeEvents(peering) + defer peeringSub.Unsubscribe() + + s.idles = make(map[string]*peerConnection) + for _, peer := range s.peers.AllPeers() { + s.idles[peer.id] = peer + } + // Notify any tester listening for startup events + if s.syncStarting != nil { + s.syncStarting() + } + for { + // Something happened, try to assign new tasks to any idle peers + if !linked { + s.assignTasks(responses, requestFails, cancel) + } + // Wait for something to happen + select { + case event := <-peering: + // A peer joined or left, the tasks queue and allocations need to be + // checked for potential assignment or reassignment + peerid := event.peer.id + if event.join { + log.Debug("Joining skeleton peer", "id", peerid) + s.idles[peerid] = event.peer + } else { + log.Debug("Leaving skeleton peer", "id", peerid) + s.revertRequests(peerid) + delete(s.idles, peerid) + } + + case errc := <-s.terminate: + errc <- nil + return nil, errTerminated + + case event := <-s.headEvents: + // New head was announced, try to integrate it. If successful, nothing + // needs to be done as the head simply extended the last range. For now + // 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 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 <- err + continue + } + event.errc <- nil // head extension accepted + + // New head was integrated into the skeleton chain. If the backfiller + // is still running, it will pick it up. If it already terminated, + // a new cycle needs to be spun up. + if linked { + s.filler.resume() + } + + case req := <-requestFails: + s.revertRequest(req) + + case res := <-responses: + // Process the batch of headers. If though processing we managed to + // link the current subchain to a previously downloaded one, abort the + // sync and restart with the merged subchains. + // + // If we managed to link to the existing local chain or genesis block, + // abort sync altogether. + linked, merged := s.processResponse(res) + if linked { + log.Debug("Beacon sync linked to local chain") + return nil, errSyncLinked + } + if merged { + log.Debug("Beacon sync merged subchains") + return nil, errSyncMerged + } + // We still have work to do, loop and repeat + } + } +} + +// initSync attempts to get the skeleton sync into a consistent state wrt any +// past state on disk and the newly requested head to sync to. If the new head +// is nil, the method will return and continue from the previous head. +func (s *skeleton) initSync(head *types.Header) { + // Extract the head number, we'll need it all over + number := head.Number.Uint64() + + // Retrieve the previously saved sync progress + if status := rawdb.ReadSkeletonSyncStatus(s.db); len(status) > 0 { + s.progress = new(skeletonProgress) + if err := json.Unmarshal(status, s.progress); err != nil { + log.Error("Failed to decode skeleton sync status", "err", err) + } else { + // Previous sync was available, print some continuation logs + for _, subchain := range s.progress.Subchains { + log.Debug("Restarting skeleton subchain", "head", subchain.Head, "tail", subchain.Tail) + } + // Create a new subchain for the head (unless the last can be extended), + // trimming anything it would overwrite + headchain := &subchain{ + Head: number, + Tail: number, + Next: head.ParentHash, + } + for len(s.progress.Subchains) > 0 { + // If the last chain is above the new head, delete altogether + lastchain := s.progress.Subchains[0] + if lastchain.Tail >= headchain.Tail { + log.Debug("Dropping skeleton subchain", "head", lastchain.Head, "tail", lastchain.Tail) + s.progress.Subchains = s.progress.Subchains[1:] + continue + } + // Otherwise truncate the last chain if needed and abort trimming + if lastchain.Head >= headchain.Tail { + log.Debug("Trimming skeleton subchain", "oldhead", lastchain.Head, "newhead", headchain.Tail-1, "tail", lastchain.Tail) + lastchain.Head = headchain.Tail - 1 + } + break + } + // If the last subchain can be extended, we're lucky. Otherwise, create + // a new subchain sync task. + var extended bool + if n := len(s.progress.Subchains); n > 0 { + lastchain := s.progress.Subchains[0] + if lastchain.Head == headchain.Tail-1 { + lasthead := rawdb.ReadSkeletonHeader(s.db, lastchain.Head) + if lasthead.Hash() == head.ParentHash { + log.Debug("Extended skeleton subchain with new head", "head", headchain.Tail, "tail", lastchain.Tail) + lastchain.Head = headchain.Tail + extended = true + } + } + } + if !extended { + log.Debug("Created new skeleton subchain", "head", number, "tail", number) + s.progress.Subchains = append([]*subchain{headchain}, s.progress.Subchains...) + } + // Update the database with the new sync stats and insert the new + // head header. We won't delete any trimmed skeleton headers since + // those will be outside the index space of the many subchains and + // the database space will be reclaimed eventually when processing + // blocks above the current head (TODO(karalabe): don't forget). + batch := s.db.NewBatch() + + rawdb.WriteSkeletonHeader(batch, head) + s.saveSyncStatus(batch) + + if err := batch.Write(); err != nil { + log.Crit("Failed to write skeleton sync status", "err", err) + } + return + } + } + // Either we've failed to decode the previous state, or there was none. Start + // a fresh sync with a single subchain represented by the currently sent + // chain head. + s.progress = &skeletonProgress{ + Subchains: []*subchain{ + { + Head: number, + Tail: number, + Next: head.ParentHash, + }, + }, + } + batch := s.db.NewBatch() + + rawdb.WriteSkeletonHeader(batch, head) + s.saveSyncStatus(batch) + + if err := batch.Write(); err != nil { + log.Crit("Failed to write initial skeleton sync status", "err", err) + } + log.Debug("Created initial skeleton subchain", "head", number, "tail", number) +} + +// saveSyncStatus marshals the remaining sync tasks into leveldb. +func (s *skeleton) saveSyncStatus(db ethdb.KeyValueWriter) { + status, err := json.Marshal(s.progress) + if err != nil { + panic(err) // This can only fail during implementation + } + rawdb.WriteSkeletonSyncStatus(db, status) +} + +// processNewHead does the internal shuffling for a new head marker and either +// 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) error { + // If a new finalized block was announced, update the sync process independent + // of what happens with the sync head below + if final != nil { + if number := final.Number.Uint64(); s.progress.Finalized == nil || *s.progress.Finalized != number { + s.progress.Finalized = new(uint64) + *s.progress.Finalized = final.Number.Uint64() + + s.saveSyncStatus(s.db) + } + } + // If the header cannot be inserted without interruption, return an error for + // the outer loop to tear down the skeleton sync and restart it + number := head.Number.Uint64() + + lastchain := s.progress.Subchains[0] + if lastchain.Tail >= number { + // If the chain is down to a single beacon header, and it is re-announced + // 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 nil + } + } + // Not a noop / double head announce, abort with a reorg + return fmt.Errorf("%w, tail: %d, head: %d, newHead: %d", errChainReorged, lastchain.Tail, lastchain.Head, number) + } + if lastchain.Head+1 < number { + return fmt.Errorf("%w, head: %d, newHead: %d", errChainGapped, lastchain.Head, number) + } + if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash { + 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 + // skeleton headers since those will be outside the index space of the many + // subchains and the database space will be reclaimed eventually when processing + // blocks above the current head (TODO(karalabe): don't forget). + batch := s.db.NewBatch() + + rawdb.WriteSkeletonHeader(batch, head) + lastchain.Head = number + s.saveSyncStatus(batch) + + if err := batch.Write(); err != nil { + log.Crit("Failed to write skeleton sync status", "err", err) + } + return nil +} + +// assignTasks attempts to match idle peers to pending header retrievals. +func (s *skeleton) assignTasks(success chan *headerResponse, fail chan *headerRequest, cancel chan struct{}) { + // Sort the peers by download capacity to use faster ones if many available + idlers := &peerCapacitySort{ + peers: make([]*peerConnection, 0, len(s.idles)), + caps: make([]int, 0, len(s.idles)), + } + targetTTL := s.peers.rates.TargetTimeout() + for _, peer := range s.idles { + idlers.peers = append(idlers.peers, peer) + idlers.caps = append(idlers.caps, s.peers.rates.Capacity(peer.id, eth.BlockHeadersMsg, targetTTL)) + } + if len(idlers.peers) == 0 { + return + } + sort.Sort(idlers) + + // Find header regions not yet downloading and fill them + for task, owner := range s.scratchOwners { + // If we're out of idle peers, stop assigning tasks + if len(idlers.peers) == 0 { + return + } + // Skip any tasks already filling + if owner != "" { + continue + } + // If we've reached the genesis, stop assigning tasks + if uint64(task*requestHeaders) >= s.scratchHead { + return + } + // Found a task and have peers available, assign it + idle := idlers.peers[0] + + idlers.peers = idlers.peers[1:] + idlers.caps = idlers.caps[1:] + + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.requests[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + req := &headerRequest{ + peer: idle.id, + id: reqid, + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + head: s.scratchHead - uint64(task*requestHeaders), + } + s.requests[reqid] = req + delete(s.idles, idle.id) + + // Generate the network query and send it to the peer + go s.executeTask(idle, req) + + // Inject the request into the task to block further assignments + s.scratchOwners[task] = idle.id + } +} + +// executeTask executes a single fetch request, blocking until either a result +// arrives or a timeouts / cancellation is triggered. The method should be run +// on its own goroutine and will deliver on the requested channels. +func (s *skeleton) executeTask(peer *peerConnection, req *headerRequest) { + start := time.Now() + resCh := make(chan *eth.Response) + + // Figure out how many headers to fetch. Usually this will be a full batch, + // but for the very tail of the chain, trim the request to the number left. + // Since nodes may or may not return the genesis header for a batch request, + // don't even request it. The parent hash of block #1 is enough to link. + requestCount := requestHeaders + if req.head < requestHeaders { + requestCount = int(req.head) + } + peer.log.Trace("Fetching skeleton headers", "from", req.head, "count", requestCount) + netreq, err := peer.peer.RequestHeadersByNumber(req.head, requestCount, 0, true, resCh) + if err != nil { + peer.log.Trace("Failed to request headers", "err", err) + s.scheduleRevertRequest(req) + return + } + defer netreq.Close() + + // Wait until the response arrives, the request is cancelled or times out + ttl := s.peers.rates.TargetTimeout() + + timeoutTimer := time.NewTimer(ttl) + defer timeoutTimer.Stop() + + select { + case <-req.cancel: + peer.log.Debug("Header request cancelled") + s.scheduleRevertRequest(req) + + case <-timeoutTimer.C: + // Header retrieval timed out, update the metrics + peer.log.Warn("Header request timed out, dropping peer", "elapsed", ttl) + headerTimeoutMeter.Mark(1) + s.peers.rates.Update(peer.id, eth.BlockHeadersMsg, 0, 0) + s.scheduleRevertRequest(req) + + // At this point we either need to drop the offending peer, or we need a + // mechanism to allow waiting for the response and not cancel it. For now + // lets go with dropping since the header sizes are deterministic and the + // beacon sync runs exclusive (downloader is idle) so there should be no + // other load to make timeouts probable. If we notice that timeouts happen + // more often than we'd like, we can introduce a tracker for the requests + // gone stale and monitor them. However, in that case too, we need a way + // to protect against malicious peers never responding, so it would need + // a second, hard-timeout mechanism. + s.drop(peer.id) + + case res := <-resCh: + // Headers successfully retrieved, update the metrics + headers := *res.Res.(*eth.BlockHeadersRequest) + + headerReqTimer.Update(time.Since(start)) + s.peers.rates.Update(peer.id, eth.BlockHeadersMsg, res.Time, len(headers)) + + // Cross validate the headers with the requests + switch { + case len(headers) == 0: + // No headers were delivered, reject the response and reschedule + peer.log.Debug("No headers delivered") + res.Done <- errors.New("no headers delivered") + s.scheduleRevertRequest(req) + + case headers[0].Number.Uint64() != req.head: + // Header batch anchored at non-requested number + peer.log.Debug("Invalid header response head", "have", headers[0].Number, "want", req.head) + res.Done <- errors.New("invalid header batch anchor") + s.scheduleRevertRequest(req) + + case req.head >= requestHeaders && len(headers) != requestHeaders: + // Invalid number of non-genesis headers delivered, reject the response and reschedule + peer.log.Debug("Invalid non-genesis header count", "have", len(headers), "want", requestHeaders) + res.Done <- errors.New("not enough non-genesis headers delivered") + s.scheduleRevertRequest(req) + + case req.head < requestHeaders && uint64(len(headers)) != req.head: + // Invalid number of genesis headers delivered, reject the response and reschedule + peer.log.Debug("Invalid genesis header count", "have", len(headers), "want", headers[0].Number.Uint64()) + res.Done <- errors.New("not enough genesis headers delivered") + s.scheduleRevertRequest(req) + + default: + // Packet seems structurally valid, check hash progression and if it + // is correct too, deliver for storage + for i := 0; i < len(headers)-1; i++ { + if headers[i].ParentHash != headers[i+1].Hash() { + peer.log.Debug("Invalid hash progression", "index", i, "wantparenthash", headers[i].ParentHash, "haveparenthash", headers[i+1].Hash()) + res.Done <- errors.New("invalid hash progression") + s.scheduleRevertRequest(req) + return + } + } + // Hash chain is valid. The delivery might still be junk as we're + // downloading batches concurrently (so no way to link the headers + // until gaps are filled); in that case, we'll nuke the peer when + // we detect the fault. + res.Done <- nil + + select { + case req.deliver <- &headerResponse{ + peer: peer, + reqid: req.id, + headers: headers, + }: + case <-req.cancel: + } + } + } +} + +// revertRequests locates all the currently pending requests from a particular +// peer and reverts them, rescheduling for others to fulfill. +func (s *skeleton) revertRequests(peer string) { + // Gather the requests first, revertals need the lock too + var requests []*headerRequest + for _, req := range s.requests { + if req.peer == peer { + requests = append(requests, req) + } + } + // Revert all the requests matching the peer + for _, req := range requests { + s.revertRequest(req) + } +} + +// scheduleRevertRequest asks the event loop to clean up a request and return +// all failed retrieval tasks to the scheduler for reassignment. +func (s *skeleton) scheduleRevertRequest(req *headerRequest) { + select { + case req.revert <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertRequest cleans up a request and returns all failed retrieval tasks to +// the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertRequest. +func (s *skeleton) revertRequest(req *headerRequest) { + log.Trace("Reverting header request", "peer", req.peer, "reqid", req.id) + select { + case <-req.stale: + log.Trace("Header request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + delete(s.requests, req.id) + + // Remove the request from the tracked set and mark the task as not-pending, + // ready for rescheduling + s.scratchOwners[(s.scratchHead-req.head)/requestHeaders] = "" +} + +func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged bool) { + res.peer.log.Trace("Processing header response", "head", res.headers[0].Number, "hash", res.headers[0].Hash(), "count", len(res.headers)) + + // Whether the response is valid, we can mark the peer as idle and notify + // the scheduler to assign a new task. If the response is invalid, we'll + // drop the peer in a bit. + s.idles[res.peer.id] = res.peer + + // Ensure the response is for a valid request + if _, ok := s.requests[res.reqid]; !ok { + // Some internal accounting is broken. A request either times out or it + // gets fulfilled successfully. It should not be possible to deliver a + // response to a non-existing request. + res.peer.log.Error("Unexpected header packet") + return false, false + } + delete(s.requests, res.reqid) + + // Insert the delivered headers into the scratch space independent of the + // content or continuation; those will be validated in a moment + head := res.headers[0].Number.Uint64() + copy(s.scratchSpace[s.scratchHead-head:], res.headers) + + // If there's still a gap in the head of the scratch space, abort + if s.scratchSpace[0] == nil { + return false, false + } + // Try to consume any head headers, validating the boundary conditions + batch := s.db.NewBatch() + for s.scratchSpace[0] != nil { + // Next batch of headers available, cross-reference with the subchain + // we are extending and either accept or discard + if s.progress.Subchains[0].Next != s.scratchSpace[0].Hash() { + // Print a log messages to track what's going on + tail := s.progress.Subchains[0].Tail + want := s.progress.Subchains[0].Next + have := s.scratchSpace[0].Hash() + + log.Warn("Invalid skeleton headers", "peer", s.scratchOwners[0], "number", tail-1, "want", want, "have", have) + + // The peer delivered junk, or at least not the subchain we are + // syncing to. Free up the scratch space and assignment, reassign + // and drop the original peer. + for i := 0; i < requestHeaders; i++ { + s.scratchSpace[i] = nil + } + s.drop(s.scratchOwners[0]) + s.scratchOwners[0] = "" + break + } + // Scratch delivery matches required subchain, deliver the batch of + // headers and push the subchain forward + var consumed int + for _, header := range s.scratchSpace[:requestHeaders] { + if header != nil { // nil when the genesis is reached + consumed++ + + rawdb.WriteSkeletonHeader(batch, header) + s.pulled++ + + s.progress.Subchains[0].Tail-- + s.progress.Subchains[0].Next = header.ParentHash + + // If we've reached an existing block in the chain, stop retrieving + // headers. Note, if we want to support light clients with the same + // code we'd need to switch here based on the downloader mode. That + // said, there's no such functionality for now, so don't complicate. + // + // In the case of full sync it would be enough to check for the body, + // but even a full syncing node will generate a receipt once block + // processing is done, so it's just one more "needless" check. + // + // The weird cascading checks are done to minimize the database reads. + linked = rawdb.HasHeader(s.db, header.ParentHash, header.Number.Uint64()-1) && + rawdb.HasBody(s.db, header.ParentHash, header.Number.Uint64()-1) && + rawdb.HasReceipts(s.db, header.ParentHash, header.Number.Uint64()-1) + if linked { + break + } + } + } + head := s.progress.Subchains[0].Head + tail := s.progress.Subchains[0].Tail + next := s.progress.Subchains[0].Next + + log.Trace("Primary subchain extended", "head", head, "tail", tail, "next", next) + + // If the beacon chain was linked to the local chain, completely swap out + // all internal progress and abort header synchronization. + if linked { + // Linking into the local chain should also mean that there are no + // leftover subchains, but in the case of importing the blocks via + // the engine API, we will not push the subchains forward. This will + // lead to a gap between an old sync cycle and a future one. + if subchains := len(s.progress.Subchains); subchains > 1 { + switch { + // If there are only 2 subchains - the current one and an older + // one - and the old one consists of a single block, then it's + // the expected new sync cycle after some propagated blocks. Log + // it for debugging purposes, explicitly clean and don't escalate. + case subchains == 2 && s.progress.Subchains[1].Head == s.progress.Subchains[1].Tail: + // Remove the leftover skeleton header associated with old + // skeleton chain only if it's not covered by the current + // skeleton range. + if s.progress.Subchains[1].Head < s.progress.Subchains[0].Tail { + log.Debug("Cleaning previous beacon sync state", "head", s.progress.Subchains[1].Head) + rawdb.DeleteSkeletonHeader(batch, s.progress.Subchains[1].Head) + } + // Drop the leftover skeleton chain since it's stale. + s.progress.Subchains = s.progress.Subchains[:1] + + // If we have more than one header or more than one leftover chain, + // the syncer's internal state is corrupted. Do try to fix it, but + // be very vocal about the fault. + default: + var context []interface{} + + for i := range s.progress.Subchains[1:] { + context = append(context, fmt.Sprintf("stale_head_%d", i+1)) + context = append(context, s.progress.Subchains[i+1].Head) + context = append(context, fmt.Sprintf("stale_tail_%d", i+1)) + context = append(context, s.progress.Subchains[i+1].Tail) + context = append(context, fmt.Sprintf("stale_next_%d", i+1)) + context = append(context, s.progress.Subchains[i+1].Next) + } + log.Error("Cleaning spurious beacon sync leftovers", context...) + s.progress.Subchains = s.progress.Subchains[:1] + + // Note, here we didn't actually delete the headers at all, + // just the metadata. We could implement a cleanup mechanism, + // but further modifying corrupted state is kind of asking + // for it. Unless there's a good enough reason to risk it, + // better to live with the small database junk. + } + } + break + } + // Batch of headers consumed, shift the download window forward + copy(s.scratchSpace, s.scratchSpace[requestHeaders:]) + for i := 0; i < requestHeaders; i++ { + s.scratchSpace[scratchHeaders-i-1] = nil + } + copy(s.scratchOwners, s.scratchOwners[1:]) + s.scratchOwners[scratchHeaders/requestHeaders-1] = "" + + s.scratchHead -= uint64(consumed) + + // If the subchain extended into the next subchain, we need to handle + // the overlap. Since there could be many overlaps (come on), do this + // in a loop. + for len(s.progress.Subchains) > 1 && s.progress.Subchains[1].Head >= s.progress.Subchains[0].Tail { + // Extract some stats from the second subchain + head := s.progress.Subchains[1].Head + tail := s.progress.Subchains[1].Tail + next := s.progress.Subchains[1].Next + + // Since we just overwrote part of the next subchain, we need to trim + // its head independent of matching or mismatching content + if s.progress.Subchains[1].Tail >= s.progress.Subchains[0].Tail { + // Fully overwritten, get rid of the subchain as a whole + log.Debug("Previous subchain fully overwritten", "head", head, "tail", tail, "next", next) + s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...) + continue + } else { + // Partially overwritten, trim the head to the overwritten size + log.Debug("Previous subchain partially overwritten", "head", head, "tail", tail, "next", next) + s.progress.Subchains[1].Head = s.progress.Subchains[0].Tail - 1 + } + // If the old subchain is an extension of the new one, merge the two + // and let the skeleton syncer restart (to clean internal state) + if rawdb.ReadSkeletonHeader(s.db, s.progress.Subchains[1].Head).Hash() == s.progress.Subchains[0].Next { + log.Debug("Previous subchain merged", "head", head, "tail", tail, "next", next) + s.progress.Subchains[0].Tail = s.progress.Subchains[1].Tail + s.progress.Subchains[0].Next = s.progress.Subchains[1].Next + + s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...) + merged = true + } + } + // If subchains were merged, all further available headers in the scratch + // space are invalid since we skipped ahead. Stop processing the scratch + // space to avoid dropping peers thinking they delivered invalid data. + if merged { + break + } + } + s.saveSyncStatus(batch) + if err := batch.Write(); err != nil { + log.Crit("Failed to write skeleton headers and progress", "err", err) + } + // Print a progress report making the UX a bit nicer + left := s.progress.Subchains[0].Tail - 1 + if linked { + left = 0 + } + if time.Since(s.logged) > 8*time.Second || left == 0 { + s.logged = time.Now() + + if s.pulled == 0 { + log.Info("Beacon sync starting", "left", left) + } else { + eta := float64(time.Since(s.started)) / float64(s.pulled) * float64(left) + log.Info("Syncing beacon headers", "downloaded", s.pulled, "left", left, "eta", common.PrettyDuration(eta)) + } + } + return linked, merged +} + +// cleanStales removes previously synced beacon headers that have become stale +// due to the downloader backfilling past the tracked tail. +func (s *skeleton) cleanStales(filled *types.Header) error { + number := filled.Number.Uint64() + log.Trace("Cleaning stale beacon headers", "filled", number, "hash", filled.Hash()) + + // If the filled header is below the linked subchain, something's corrupted + // internally. Report and error and refuse to do anything. + if number+1 < s.progress.Subchains[0].Tail { + return fmt.Errorf("filled header below beacon header tail: %d < %d", number, s.progress.Subchains[0].Tail) + } + // If nothing in subchain is filled, don't bother to do cleanup. + if number+1 == s.progress.Subchains[0].Tail { + return nil + } + // If the latest fill was on a different subchain, it means the backfiller + // was interrupted before it got to do any meaningful work, no cleanup + header := rawdb.ReadSkeletonHeader(s.db, filled.Number.Uint64()) + if header == nil { + log.Debug("Filled header outside of skeleton range", "number", number, "head", s.progress.Subchains[0].Head, "tail", s.progress.Subchains[0].Tail) + return nil + } else if header.Hash() != filled.Hash() { + log.Debug("Filled header on different sidechain", "number", number, "filled", filled.Hash(), "skeleton", header.Hash()) + return nil + } + var ( + start uint64 + end uint64 + batch = s.db.NewBatch() + ) + if number < s.progress.Subchains[0].Head { + // The skeleton chain is partially consumed, set the new tail as filled+1. + tail := rawdb.ReadSkeletonHeader(s.db, number+1) + if tail.ParentHash != filled.Hash() { + return fmt.Errorf("filled header is discontinuous with subchain: %d %s, please file an issue", number, filled.Hash()) + } + start, end = s.progress.Subchains[0].Tail, number+1 // remove headers in [tail, filled] + s.progress.Subchains[0].Tail = tail.Number.Uint64() + s.progress.Subchains[0].Next = tail.ParentHash + } else { + // The skeleton chain is fully consumed, set both head and tail as filled. + start, end = s.progress.Subchains[0].Tail, filled.Number.Uint64() // remove headers in [tail, filled) + s.progress.Subchains[0].Tail = filled.Number.Uint64() + s.progress.Subchains[0].Next = filled.ParentHash + + // If more headers were filled than available, push the entire subchain + // forward to keep tracking the node's block imports. + if number > s.progress.Subchains[0].Head { + end = s.progress.Subchains[0].Head + 1 // delete the entire original range, including the head + s.progress.Subchains[0].Head = number // assign a new head (tail is already assigned to this) + + // The entire original skeleton chain was deleted and a new one + // defined. Make sure the new single-header chain gets pushed to + // disk to keep internal state consistent. + rawdb.WriteSkeletonHeader(batch, filled) + } + } + // Execute the trimming and the potential rewiring of the progress + s.saveSyncStatus(batch) + for n := start; n < end; n++ { + // If the batch grew too big, flush it and continue with a new batch. + // The catch is that the sync metadata needs to reflect the actually + // flushed state, so temporarily change the subchain progress and + // revert after the flush. + if batch.ValueSize() >= ethdb.IdealBatchSize { + tmpTail := s.progress.Subchains[0].Tail + tmpNext := s.progress.Subchains[0].Next + + s.progress.Subchains[0].Tail = n + s.progress.Subchains[0].Next = rawdb.ReadSkeletonHeader(s.db, n).ParentHash + s.saveSyncStatus(batch) + + if err := batch.Write(); err != nil { + log.Crit("Failed to write beacon trim data", "err", err) + } + batch.Reset() + + s.progress.Subchains[0].Tail = tmpTail + s.progress.Subchains[0].Next = tmpNext + s.saveSyncStatus(batch) + } + rawdb.DeleteSkeletonHeader(batch, n) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to write beacon trim data", "err", err) + } + return nil +} + +// Bounds retrieves the current head and tail tracked by the skeleton syncer +// and optionally the last known finalized header if any was announced and if +// it is still in the sync range. This method is used by the backfiller, whose +// life cycle is controlled by the skeleton syncer. +// +// Note, the method will not use the internal state of the skeleton, but will +// rather blindly pull stuff from the database. This is fine, because the back- +// filler will only run when the skeleton chain is fully downloaded and stable. +// There might be new heads appended, but those are atomic from the perspective +// of this method. Any head reorg will first tear down the backfiller and only +// then make the modification. +func (s *skeleton) Bounds() (head *types.Header, tail *types.Header, final *types.Header, err error) { + // Read the current sync progress from disk and figure out the current head. + // Although there's a lot of error handling here, these are mostly as sanity + // checks to avoid crashing if a programming error happens. These should not + // happen in live code. + status := rawdb.ReadSkeletonSyncStatus(s.db) + if len(status) == 0 { + return nil, nil, nil, errors.New("beacon sync not yet started") + } + progress := new(skeletonProgress) + if err := json.Unmarshal(status, progress); err != nil { + return nil, nil, nil, err + } + head = rawdb.ReadSkeletonHeader(s.db, progress.Subchains[0].Head) + if head == nil { + return nil, nil, nil, fmt.Errorf("head skeleton header %d is missing", progress.Subchains[0].Head) + } + tail = rawdb.ReadSkeletonHeader(s.db, progress.Subchains[0].Tail) + if tail == nil { + return nil, nil, nil, fmt.Errorf("tail skeleton header %d is missing", progress.Subchains[0].Tail) + } + if progress.Finalized != nil && tail.Number.Uint64() <= *progress.Finalized && *progress.Finalized <= head.Number.Uint64() { + final = rawdb.ReadSkeletonHeader(s.db, *progress.Finalized) + if final == nil { + return nil, nil, nil, fmt.Errorf("finalized skeleton header %d is missing", *progress.Finalized) + } + } + return head, tail, final, nil +} + +// Header retrieves a specific header tracked by the skeleton syncer. This method +// is meant to be used by the backfiller, whose life cycle is controlled by the +// skeleton syncer. +// +// Note, outside the permitted runtimes, this method might return nil results and +// subsequent calls might return headers from different chains. +func (s *skeleton) Header(number uint64) *types.Header { + return rawdb.ReadSkeletonHeader(s.db, number) +} diff --git a/eth/downloader/skeleton_test.go b/eth/downloader/skeleton_test.go new file mode 100644 index 0000000..4aa97cf --- /dev/null +++ b/eth/downloader/skeleton_test.go @@ -0,0 +1,970 @@ +// 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 downloader + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// hookedBackfiller is a tester backfiller with all interface methods mocked and +// hooked so tests can implement only the things they need. +type hookedBackfiller struct { + // suspendHook is an optional hook to be called when the filler is requested + // to be suspended. + suspendHook func() *types.Header + + // resumeHook is an optional hook to be called when the filler is requested + // to be resumed. + resumeHook func() +} + +// newHookedBackfiller creates a hooked backfiller with all callbacks disabled, +// essentially acting as a noop. +func newHookedBackfiller() backfiller { + return new(hookedBackfiller) +} + +// suspend requests the backfiller to abort any running full or snap sync +// based on the skeleton chain as it might be invalid. The backfiller should +// gracefully handle multiple consecutive suspends without a resume, even +// on initial startup. +func (hf *hookedBackfiller) suspend() *types.Header { + if hf.suspendHook != nil { + return hf.suspendHook() + } + return nil // we don't really care about header cleanups for now +} + +// resume requests the backfiller to start running fill or snap sync based on +// the skeleton chain as it has successfully been linked. Appending new heads +// to the end of the chain will not result in suspend/resume cycles. +func (hf *hookedBackfiller) resume() { + if hf.resumeHook != nil { + hf.resumeHook() + } +} + +// skeletonTestPeer is a mock peer that can only serve header requests from a +// pre-perated header chain (which may be arbitrarily wrong for testing). +// +// Requesting anything else from these peers will hard panic. Note, do *not* +// implement any other methods. We actually want to make sure that the skeleton +// syncer only depends on - and will only ever do so - on header requests. +type skeletonTestPeer struct { + id string // Unique identifier of the mock peer + headers []*types.Header // Headers to serve when requested + + serve func(origin uint64) []*types.Header // Hook to allow custom responses + + served atomic.Uint64 // Number of headers served by this peer + dropped atomic.Uint64 // Flag whether the peer was dropped (stop responding) +} + +// newSkeletonTestPeer creates a new mock peer to test the skeleton sync with. +func newSkeletonTestPeer(id string, headers []*types.Header) *skeletonTestPeer { + return &skeletonTestPeer{ + id: id, + headers: headers, + } +} + +// newSkeletonTestPeerWithHook creates a new mock peer to test the skeleton sync with, +// and sets an optional serve hook that can return headers for delivery instead +// of the predefined chain. Useful for emulating malicious behavior that would +// otherwise require dedicated peer types. +func newSkeletonTestPeerWithHook(id string, headers []*types.Header, serve func(origin uint64) []*types.Header) *skeletonTestPeer { + return &skeletonTestPeer{ + id: id, + headers: headers, + serve: serve, + } +} + +// RequestHeadersByNumber constructs a GetBlockHeaders function based on a numbered +// origin; associated with a particular peer in the download tester. The returned +// function can be used to retrieve batches of headers from the particular peer. +func (p *skeletonTestPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) { + // Since skeleton test peer are in-memory mocks, dropping the does not make + // them inaccessible. As such, check a local `dropped` field to see if the + // peer has been dropped and should not respond any more. + if p.dropped.Load() != 0 { + return nil, errors.New("peer already dropped") + } + // Skeleton sync retrieves batches of headers going backward without gaps. + // This ensures we can follow a clean parent progression without any reorg + // hiccups. There is no need for any other type of header retrieval, so do + // panic if there's such a request. + if !reverse || skip != 0 { + // Note, if other clients want to do these kinds of requests, it's their + // problem, it will still work. We just don't want *us* making complicated + // requests without a very strong reason to. + panic(fmt.Sprintf("invalid header retrieval: reverse %v, want true; skip %d, want 0", reverse, skip)) + } + // If the skeleton syncer requests the genesis block, panic. Whilst it could + // be considered a valid request, our code specifically should not request it + // ever since we want to link up headers to an existing local chain, which at + // worse will be the genesis. + if int64(origin)-int64(amount) < 0 { + panic(fmt.Sprintf("headers requested before (or at) genesis: origin %d, amount %d", origin, amount)) + } + // To make concurrency easier, the skeleton syncer always requests fixed size + // batches of headers. Panic if the peer is requested an amount other than the + // configured batch size (apart from the request leading to the genesis). + if amount > requestHeaders || (amount < requestHeaders && origin > uint64(amount)) { + panic(fmt.Sprintf("non-chunk size header batch requested: requested %d, want %d, origin %d", amount, requestHeaders, origin)) + } + // Simple reverse header retrieval. Fill from the peer's chain and return. + // If the tester has a serve hook set, try to use that before falling back + // to the default behavior. + var headers []*types.Header + if p.serve != nil { + headers = p.serve(origin) + } + if headers == nil { + headers = make([]*types.Header, 0, amount) + if len(p.headers) > int(origin) { // Don't serve headers if we're missing the origin + for i := 0; i < amount; i++ { + // Consider nil headers as a form of attack and withhold them. Nil + // cannot be decoded from RLP, so it's not possible to produce an + // attack by sending/receiving those over eth. + header := p.headers[int(origin)-i] + if header == nil { + continue + } + headers = append(headers, header) + } + } + } + p.served.Add(uint64(len(headers))) + + hashes := make([]common.Hash, len(headers)) + for i, header := range headers { + hashes[i] = header.Hash() + } + // Deliver the headers to the downloader + req := ð.Request{ + Peer: p.id, + } + res := ð.Response{ + Req: req, + Res: (*eth.BlockHeadersRequest)(&headers), + Meta: hashes, + Time: 1, + Done: make(chan error), + } + go func() { + sink <- res + if err := <-res.Done; err != nil { + log.Warn("Skeleton test peer response rejected", "err", err) + p.dropped.Add(1) + } + }() + return req, nil +} + +func (p *skeletonTestPeer) Head() (common.Hash, *big.Int) { + panic("skeleton sync must not request the remote head") +} + +func (p *skeletonTestPeer) RequestHeadersByHash(common.Hash, int, int, bool, chan *eth.Response) (*eth.Request, error) { + panic("skeleton sync must not request headers by hash") +} + +func (p *skeletonTestPeer) RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error) { + panic("skeleton sync must not request block bodies") +} + +func (p *skeletonTestPeer) RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) { + panic("skeleton sync must not request receipts") +} + +// Tests various sync initializations based on previous leftovers in the database +// and announced heads. +func TestSkeletonSyncInit(t *testing.T) { + // Create a few key headers + var ( + genesis = &types.Header{Number: big.NewInt(0)} + block49 = &types.Header{Number: big.NewInt(49)} + block49B = &types.Header{Number: big.NewInt(49), Extra: []byte("B")} + block50 = &types.Header{Number: big.NewInt(50), ParentHash: block49.Hash()} + ) + tests := []struct { + headers []*types.Header // Database content (beside the genesis) + oldstate []*subchain // Old sync state with various interrupted subchains + head *types.Header // New head header to announce to reorg to + newstate []*subchain // Expected sync state after the reorg + }{ + // Completely empty database with only the genesis set. The sync is expected + // to create a single subchain with the requested head. + { + head: block50, + newstate: []*subchain{{Head: 50, Tail: 50}}, + }, + // Empty database with only the genesis set with a leftover empty sync + // progress. This is a synthetic case, just for the sake of covering things. + { + oldstate: []*subchain{}, + head: block50, + newstate: []*subchain{{Head: 50, Tail: 50}}, + }, + // A single leftover subchain is present, older than the new head. The + // old subchain should be left as is and a new one appended to the sync + // status. + { + oldstate: []*subchain{{Head: 10, Tail: 5}}, + head: block50, + newstate: []*subchain{ + {Head: 50, Tail: 50}, + {Head: 10, Tail: 5}, + }, + }, + // Multiple leftover subchains are present, older than the new head. The + // old subchains should be left as is and a new one appended to the sync + // status. + { + oldstate: []*subchain{ + {Head: 20, Tail: 15}, + {Head: 10, Tail: 5}, + }, + head: block50, + newstate: []*subchain{ + {Head: 50, Tail: 50}, + {Head: 20, Tail: 15}, + {Head: 10, Tail: 5}, + }, + }, + // A single leftover subchain is present, newer than the new head. The + // newer subchain should be deleted and a fresh one created for the head. + { + oldstate: []*subchain{{Head: 65, Tail: 60}}, + head: block50, + newstate: []*subchain{{Head: 50, Tail: 50}}, + }, + // Multiple leftover subchain is present, newer than the new head. The + // newer subchains should be deleted and a fresh one created for the head. + { + oldstate: []*subchain{ + {Head: 75, Tail: 70}, + {Head: 65, Tail: 60}, + }, + head: block50, + newstate: []*subchain{{Head: 50, Tail: 50}}, + }, + + // Two leftover subchains are present, one fully older and one fully + // newer than the announced head. The head should delete the newer one, + // keeping the older one. + { + oldstate: []*subchain{ + {Head: 65, Tail: 60}, + {Head: 10, Tail: 5}, + }, + head: block50, + newstate: []*subchain{ + {Head: 50, Tail: 50}, + {Head: 10, Tail: 5}, + }, + }, + // Multiple leftover subchains are present, some fully older and some + // fully newer than the announced head. The head should delete the newer + // ones, keeping the older ones. + { + oldstate: []*subchain{ + {Head: 75, Tail: 70}, + {Head: 65, Tail: 60}, + {Head: 20, Tail: 15}, + {Head: 10, Tail: 5}, + }, + head: block50, + newstate: []*subchain{ + {Head: 50, Tail: 50}, + {Head: 20, Tail: 15}, + {Head: 10, Tail: 5}, + }, + }, + // A single leftover subchain is present and the new head is extending + // it with one more header. We expect the subchain head to be pushed + // forward. + { + headers: []*types.Header{block49}, + oldstate: []*subchain{{Head: 49, Tail: 5}}, + head: block50, + newstate: []*subchain{{Head: 50, Tail: 5}}, + }, + // A single leftover subchain is present and although the new head does + // extend it number wise, the hash chain does not link up. We expect a + // new subchain to be created for the dangling head. + { + headers: []*types.Header{block49B}, + oldstate: []*subchain{{Head: 49, Tail: 5}}, + head: block50, + newstate: []*subchain{ + {Head: 50, Tail: 50}, + {Head: 49, Tail: 5}, + }, + }, + // A single leftover subchain is present. A new head is announced that + // links into the middle of it, correctly anchoring into an existing + // header. We expect the old subchain to be truncated and extended with + // the new head. + { + headers: []*types.Header{block49}, + oldstate: []*subchain{{Head: 100, Tail: 5}}, + head: block50, + newstate: []*subchain{{Head: 50, Tail: 5}}, + }, + // A single leftover subchain is present. A new head is announced that + // links into the middle of it, but does not anchor into an existing + // header. We expect the old subchain to be truncated and a new chain + // be created for the dangling head. + { + headers: []*types.Header{block49B}, + oldstate: []*subchain{{Head: 100, Tail: 5}}, + head: block50, + newstate: []*subchain{ + {Head: 50, Tail: 50}, + {Head: 49, Tail: 5}, + }, + }, + } + for i, tt := range tests { + // Create a fresh database and initialize it with the starting state + db := rawdb.NewMemoryDatabase() + + rawdb.WriteHeader(db, genesis) + for _, header := range tt.headers { + rawdb.WriteSkeletonHeader(db, header) + } + if tt.oldstate != nil { + blob, _ := json.Marshal(&skeletonProgress{Subchains: tt.oldstate}) + rawdb.WriteSkeletonSyncStatus(db, blob) + } + // Create a skeleton sync and run a cycle + wait := make(chan struct{}) + + skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller()) + skeleton.syncStarting = func() { close(wait) } + skeleton.Sync(tt.head, nil, true) + + <-wait + skeleton.Terminate() + + // Ensure the correct resulting sync status + expect := skeletonExpect{state: tt.newstate} + if err := checkSkeletonProgress(db, false, nil, expect); err != nil { + t.Errorf("test %d: %v", i, err) + } + } +} + +// Tests that a running skeleton sync can be extended with properly linked up +// headers but not with side chains. +func TestSkeletonSyncExtend(t *testing.T) { + // Create a few key headers + var ( + genesis = &types.Header{Number: big.NewInt(0)} + block49 = &types.Header{Number: big.NewInt(49)} + block49B = &types.Header{Number: big.NewInt(49), Extra: []byte("B")} + block50 = &types.Header{Number: big.NewInt(50), ParentHash: block49.Hash()} + block51 = &types.Header{Number: big.NewInt(51), ParentHash: block50.Hash()} + ) + tests := []struct { + head *types.Header // New head header to announce to reorg to + extend *types.Header // New head header to announce to extend with + newstate []*subchain // Expected sync state after the reorg + err error // Whether extension succeeds or not + }{ + // Initialize a sync and try to extend it with a subsequent block. + { + head: block49, + extend: block50, + newstate: []*subchain{ + {Head: 50, Tail: 49}, + }, + }, + // Initialize a sync and try to extend it with the existing head block. + { + head: block49, + extend: block49, + newstate: []*subchain{ + {Head: 49, Tail: 49}, + }, + }, + // Initialize a sync and try to extend it with a sibling block. + { + head: block49, + extend: block49B, + newstate: []*subchain{ + {Head: 49, Tail: 49}, + }, + err: errChainReorged, + }, + // Initialize a sync and try to extend it with a number-wise sequential + // header, but a hash wise non-linking one. + { + head: block49B, + extend: block50, + newstate: []*subchain{ + {Head: 49, Tail: 49}, + }, + err: errChainForked, + }, + // Initialize a sync and try to extend it with a non-linking future block. + { + head: block49, + extend: block51, + newstate: []*subchain{ + {Head: 49, Tail: 49}, + }, + err: errChainGapped, + }, + // Initialize a sync and try to extend it with a past canonical block. + { + head: block50, + extend: block49, + newstate: []*subchain{ + {Head: 50, Tail: 50}, + }, + err: errChainReorged, + }, + // Initialize a sync and try to extend it with a past sidechain block. + { + head: block50, + extend: block49B, + newstate: []*subchain{ + {Head: 50, Tail: 50}, + }, + err: errChainReorged, + }, + } + for i, tt := range tests { + // Create a fresh database and initialize it with the starting state + db := rawdb.NewMemoryDatabase() + rawdb.WriteHeader(db, genesis) + + // Create a skeleton sync and run a cycle + wait := make(chan struct{}) + + skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller()) + skeleton.syncStarting = func() { close(wait) } + skeleton.Sync(tt.head, nil, true) + + <-wait + 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() + + // Ensure the correct resulting sync status + expect := skeletonExpect{state: tt.newstate} + if err := checkSkeletonProgress(db, false, nil, expect); err != nil { + t.Errorf("test %d: %v", i, err) + } + } +} + +type skeletonExpect struct { + state []*subchain // Expected sync state after the post-init event + serve uint64 // Expected number of header retrievals after initial cycle + drop uint64 // Expected number of peers dropped after initial cycle +} + +type skeletonTest struct { + fill bool // Whether to run a real backfiller in this test case + unpredictable bool // Whether to ignore drops/serves due to uncertain packet assignments + + head *types.Header // New head header to announce to reorg to + peers []*skeletonTestPeer // Initial peer set to start the sync with + mid skeletonExpect + + newHead *types.Header // New header to anoint on top of the old one + newPeer *skeletonTestPeer // New peer to join the skeleton syncer + end skeletonExpect +} + +// Tests that the skeleton sync correctly retrieves headers from one or more +// peers without duplicates or other strange side effects. +func TestSkeletonSyncRetrievals(t *testing.T) { + //log.SetDefault(log.NewLogger(log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false)))) + + // Since skeleton headers don't need to be meaningful, beyond a parent hash + // progression, create a long fake chain to test with. + chain := []*types.Header{{Number: big.NewInt(0)}} + for i := 1; i < 10000; i++ { + chain = append(chain, &types.Header{ + ParentHash: chain[i-1].Hash(), + Number: big.NewInt(int64(i)), + }) + } + // Some tests require a forking side chain to trigger cornercases. + var sidechain []*types.Header + for i := 0; i < len(chain)/2; i++ { // Fork at block #5000 + sidechain = append(sidechain, chain[i]) + } + for i := len(chain) / 2; i < len(chain); i++ { + sidechain = append(sidechain, &types.Header{ + ParentHash: sidechain[i-1].Hash(), + Number: big.NewInt(int64(i)), + Extra: []byte("B"), // force a different hash + }) + } + tests := []skeletonTest{ + // Completely empty database with only the genesis set. The sync is expected + // to create a single subchain with the requested head. No peers however, so + // the sync should be stuck without any progression. + // + // When a new peer is added, it should detect the join and fill the headers + // to the genesis block. + { + head: chain[len(chain)-1], + mid: skeletonExpect{ + state: []*subchain{{Head: uint64(len(chain) - 1), Tail: uint64(len(chain) - 1)}}, + }, + + newPeer: newSkeletonTestPeer("test-peer", chain), + end: skeletonExpect{ + state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}}, + serve: uint64(len(chain) - 2), // len - head - genesis + }, + }, + // Completely empty database with only the genesis set. The sync is expected + // to create a single subchain with the requested head. With one valid peer, + // the sync is expected to complete already in the initial round. + // + // Adding a second peer should not have any effect. + { + head: chain[len(chain)-1], + peers: []*skeletonTestPeer{newSkeletonTestPeer("test-peer-1", chain)}, + mid: skeletonExpect{ + state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}}, + serve: uint64(len(chain) - 2), // len - head - genesis + }, + + newPeer: newSkeletonTestPeer("test-peer-2", chain), + end: skeletonExpect{ + state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}}, + serve: uint64(len(chain) - 2), // len - head - genesis + }, + }, + // Completely empty database with only the genesis set. The sync is expected + // to create a single subchain with the requested head. With many valid peers, + // the sync is expected to complete already in the initial round. + // + // Adding a new peer should not have any effect. + { + head: chain[len(chain)-1], + peers: []*skeletonTestPeer{ + newSkeletonTestPeer("test-peer-1", chain), + newSkeletonTestPeer("test-peer-2", chain), + newSkeletonTestPeer("test-peer-3", chain), + }, + mid: skeletonExpect{ + state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}}, + serve: uint64(len(chain) - 2), // len - head - genesis + }, + + newPeer: newSkeletonTestPeer("test-peer-4", chain), + end: skeletonExpect{ + state: []*subchain{{Head: uint64(len(chain) - 1), Tail: 1}}, + serve: uint64(len(chain) - 2), // len - head - genesis + }, + }, + // This test checks if a peer tries to withhold a header - *on* the sync + // boundary - instead of sending the requested amount. The malicious short + // package should not be accepted. + // + // Joining with a new peer should however unblock the sync. + { + head: chain[requestHeaders+100], + peers: []*skeletonTestPeer{ + newSkeletonTestPeer("header-skipper", append(append(append([]*types.Header{}, chain[:99]...), nil), chain[100:]...)), + }, + mid: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 100}}, + serve: requestHeaders + 101 - 3, // len - head - genesis - missing + drop: 1, // penalize shortened header deliveries + }, + + newPeer: newSkeletonTestPeer("good-peer", chain), + end: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 1}}, + serve: (requestHeaders + 101 - 3) + (100 - 1), // midserve + lenrest - genesis + drop: 1, // no new drops + }, + }, + // This test checks if a peer tries to withhold a header - *off* the sync + // boundary - instead of sending the requested amount. The malicious short + // package should not be accepted. + // + // Joining with a new peer should however unblock the sync. + { + head: chain[requestHeaders+100], + peers: []*skeletonTestPeer{ + newSkeletonTestPeer("header-skipper", append(append(append([]*types.Header{}, chain[:50]...), nil), chain[51:]...)), + }, + mid: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 100}}, + serve: requestHeaders + 101 - 3, // len - head - genesis - missing + drop: 1, // penalize shortened header deliveries + }, + + newPeer: newSkeletonTestPeer("good-peer", chain), + end: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 1}}, + serve: (requestHeaders + 101 - 3) + (100 - 1), // midserve + lenrest - genesis + drop: 1, // no new drops + }, + }, + // This test checks if a peer tries to duplicate a header - *on* the sync + // boundary - instead of sending the correct sequence. The malicious duped + // package should not be accepted. + // + // Joining with a new peer should however unblock the sync. + { + head: chain[requestHeaders+100], // We want to force the 100th header to be a request boundary + peers: []*skeletonTestPeer{ + newSkeletonTestPeer("header-duper", append(append(append([]*types.Header{}, chain[:99]...), chain[98]), chain[100:]...)), + }, + mid: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 100}}, + serve: requestHeaders + 101 - 2, // len - head - genesis + drop: 1, // penalize invalid header sequences + }, + + newPeer: newSkeletonTestPeer("good-peer", chain), + end: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 1}}, + serve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis + drop: 1, // no new drops + }, + }, + // This test checks if a peer tries to duplicate a header - *off* the sync + // boundary - instead of sending the correct sequence. The malicious duped + // package should not be accepted. + // + // Joining with a new peer should however unblock the sync. + { + head: chain[requestHeaders+100], // We want to force the 100th header to be a request boundary + peers: []*skeletonTestPeer{ + newSkeletonTestPeer("header-duper", append(append(append([]*types.Header{}, chain[:50]...), chain[49]), chain[51:]...)), + }, + mid: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 100}}, + serve: requestHeaders + 101 - 2, // len - head - genesis + drop: 1, // penalize invalid header sequences + }, + + newPeer: newSkeletonTestPeer("good-peer", chain), + end: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 1}}, + serve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis + drop: 1, // no new drops + }, + }, + // This test checks if a peer tries to inject a different header - *on* + // the sync boundary - instead of sending the correct sequence. The bad + // package should not be accepted. + // + // Joining with a new peer should however unblock the sync. + { + head: chain[requestHeaders+100], // We want to force the 100th header to be a request boundary + peers: []*skeletonTestPeer{ + newSkeletonTestPeer("header-changer", + append( + append( + append([]*types.Header{}, chain[:99]...), + &types.Header{ + ParentHash: chain[98].Hash(), + Number: big.NewInt(int64(99)), + GasLimit: 1, + }, + ), chain[100:]..., + ), + ), + }, + mid: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 100}}, + serve: requestHeaders + 101 - 2, // len - head - genesis + drop: 1, // different set of headers, drop // TODO(karalabe): maybe just diff sync? + }, + + newPeer: newSkeletonTestPeer("good-peer", chain), + end: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 1}}, + serve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis + drop: 1, // no new drops + }, + }, + // This test checks if a peer tries to inject a different header - *off* + // the sync boundary - instead of sending the correct sequence. The bad + // package should not be accepted. + // + // Joining with a new peer should however unblock the sync. + { + head: chain[requestHeaders+100], // We want to force the 100th header to be a request boundary + peers: []*skeletonTestPeer{ + newSkeletonTestPeer("header-changer", + append( + append( + append([]*types.Header{}, chain[:50]...), + &types.Header{ + ParentHash: chain[49].Hash(), + Number: big.NewInt(int64(50)), + GasLimit: 1, + }, + ), chain[51:]..., + ), + ), + }, + mid: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 100}}, + serve: requestHeaders + 101 - 2, // len - head - genesis + drop: 1, // different set of headers, drop + }, + + newPeer: newSkeletonTestPeer("good-peer", chain), + end: skeletonExpect{ + state: []*subchain{{Head: requestHeaders + 100, Tail: 1}}, + serve: (requestHeaders + 101 - 2) + (100 - 1), // midserve + lenrest - genesis + drop: 1, // no new drops + }, + }, + // This test reproduces a bug caught during review (kudos to @holiman) + // where a subchain is merged with a previously interrupted one, causing + // pending data in the scratch space to become "invalid" (since we jump + // ahead during subchain merge). In that case it is expected to ignore + // the queued up data instead of trying to process on top of a shifted + // task set. + // + // The test is a bit convoluted since it needs to trigger a concurrency + // issue. First we sync up an initial chain of 2x512 items. Then announce + // 2x512+2 as head and delay delivering the head batch to fill the scratch + // space first. The delivery head should merge with the previous download + // and the scratch space must not be consumed further. + { + head: chain[2*requestHeaders], + peers: []*skeletonTestPeer{ + newSkeletonTestPeerWithHook("peer-1", chain, func(origin uint64) []*types.Header { + if origin == chain[2*requestHeaders+1].Number.Uint64() { + time.Sleep(100 * time.Millisecond) + } + return nil // Fallback to default behavior, just delayed + }), + newSkeletonTestPeerWithHook("peer-2", chain, func(origin uint64) []*types.Header { + if origin == chain[2*requestHeaders+1].Number.Uint64() { + time.Sleep(100 * time.Millisecond) + } + return nil // Fallback to default behavior, just delayed + }), + }, + mid: skeletonExpect{ + state: []*subchain{{Head: 2 * requestHeaders, Tail: 1}}, + serve: 2*requestHeaders - 1, // len - head - genesis + }, + + newHead: chain[2*requestHeaders+2], + end: skeletonExpect{ + state: []*subchain{{Head: 2*requestHeaders + 2, Tail: 1}}, + serve: 4 * requestHeaders, + }, + }, + // This test reproduces a bug caught by (@rjl493456442) where a skeleton + // header goes missing, causing the sync to get stuck and/or panic. + // + // The setup requires a previously successfully synced chain up to a block + // height N. That results is a single skeleton header (block N) and a single + // subchain (head N, Tail N) being stored on disk. + // + // The following step requires a new sync cycle to a new side chain of a + // height higher than N, and an ancestor lower than N (e.g. N-2, N+2). + // In this scenario, when processing a batch of headers, a link point of + // N-2 will be found, meaning that N-1 and N have been overwritten. + // + // The link event triggers an early exit, noticing that the previous sub- + // chain is a leftover and deletes it (with it's skeleton header N). But + // since skeleton header N has been overwritten to the new side chain, we + // end up losing it and creating a gap. + { + fill: true, + unpredictable: true, // We have good and bad peer too, bad may be dropped, test too short for certainty + + head: chain[len(chain)/2+1], // Sync up until the sidechain common ancestor + 2 + peers: []*skeletonTestPeer{newSkeletonTestPeer("test-peer-oldchain", chain)}, + mid: skeletonExpect{ + state: []*subchain{{Head: uint64(len(chain)/2 + 1), Tail: 1}}, + }, + + newHead: sidechain[len(sidechain)/2+3], // Sync up until the sidechain common ancestor + 4 + newPeer: newSkeletonTestPeer("test-peer-newchain", sidechain), + end: skeletonExpect{ + state: []*subchain{{Head: uint64(len(sidechain)/2 + 3), Tail: uint64(len(chain) / 2)}}, + }, + }, + } + for i, tt := range tests { + // Create a fresh database and initialize it with the starting state + db := rawdb.NewMemoryDatabase() + + rawdb.WriteBlock(db, types.NewBlockWithHeader(chain[0])) + rawdb.WriteReceipts(db, chain[0].Hash(), chain[0].Number.Uint64(), types.Receipts{}) + + // Create a peer set to feed headers through + peerset := newPeerSet() + for _, peer := range tt.peers { + peerset.Register(newPeerConnection(peer.id, eth.ETH68, peer, log.New("id", peer.id))) + } + // Create a peer dropper to track malicious peers + dropped := make(map[string]int) + drop := func(peer string) { + if p := peerset.Peer(peer); p != nil { + p.peer.(*skeletonTestPeer).dropped.Add(1) + } + peerset.Unregister(peer) + dropped[peer]++ + } + // Create a backfiller if we need to run more advanced tests + filler := newHookedBackfiller() + if tt.fill { + var filled *types.Header + + filler = &hookedBackfiller{ + resumeHook: func() { + var progress skeletonProgress + json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress) + + for progress.Subchains[0].Tail < progress.Subchains[0].Head { + header := rawdb.ReadSkeletonHeader(db, progress.Subchains[0].Tail) + + rawdb.WriteBlock(db, types.NewBlockWithHeader(header)) + rawdb.WriteReceipts(db, header.Hash(), header.Number.Uint64(), types.Receipts{}) + + rawdb.DeleteSkeletonHeader(db, header.Number.Uint64()) + + progress.Subchains[0].Tail++ + progress.Subchains[0].Next = header.Hash() + } + filled = rawdb.ReadSkeletonHeader(db, progress.Subchains[0].Tail) + + rawdb.WriteBlock(db, types.NewBlockWithHeader(filled)) + rawdb.WriteReceipts(db, filled.Hash(), filled.Number.Uint64(), types.Receipts{}) + }, + + suspendHook: func() *types.Header { + prev := filled + filled = nil + + return prev + }, + } + } + // Create a skeleton sync and run a cycle + skeleton := newSkeleton(db, peerset, drop, filler) + skeleton.Sync(tt.head, nil, true) + + // Wait a bit (bleah) for the initial sync loop to go to idle. This might + // be either a finish or a never-start hence why there's no event to hook. + waitStart := time.Now() + for waitTime := 20 * time.Millisecond; time.Since(waitStart) < 2*time.Second; waitTime = waitTime * 2 { + time.Sleep(waitTime) + if err := checkSkeletonProgress(db, tt.unpredictable, tt.peers, tt.mid); err == nil { + break + } + } + if err := checkSkeletonProgress(db, tt.unpredictable, tt.peers, tt.mid); err != nil { + t.Errorf("test %d, mid: %v", i, err) + continue + } + + // Apply the post-init events if there's any + endpeers := tt.peers + if tt.newPeer != nil { + if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH68, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil { + t.Errorf("test %d: failed to register new peer: %v", i, err) + } + time.Sleep(time.Millisecond * 50) // given time for peer registration + endpeers = append(tt.peers, tt.newPeer) + } + if tt.newHead != nil { + skeleton.Sync(tt.newHead, nil, true) + } + + // Wait a bit (bleah) for the second sync loop to go to idle. This might + // be either a finish or a never-start hence why there's no event to hook. + waitStart = time.Now() + for waitTime := 20 * time.Millisecond; time.Since(waitStart) < 2*time.Second; waitTime = waitTime * 2 { + time.Sleep(waitTime) + if err := checkSkeletonProgress(db, tt.unpredictable, endpeers, tt.end); err == nil { + break + } + } + if err := checkSkeletonProgress(db, tt.unpredictable, endpeers, tt.end); err != nil { + t.Errorf("test %d, end: %v", i, err) + continue + } + // Check that the peers served no more headers than we actually needed + // Clean up any leftover skeleton sync resources + skeleton.Terminate() + } +} + +func checkSkeletonProgress(db ethdb.KeyValueReader, unpredictable bool, peers []*skeletonTestPeer, expected skeletonExpect) error { + var progress skeletonProgress + // Check the post-init end state if it matches the required results + json.Unmarshal(rawdb.ReadSkeletonSyncStatus(db), &progress) + + if len(progress.Subchains) != len(expected.state) { + return fmt.Errorf("subchain count mismatch: have %d, want %d", len(progress.Subchains), len(expected.state)) + } + for j := 0; j < len(progress.Subchains); j++ { + if progress.Subchains[j].Head != expected.state[j].Head { + return fmt.Errorf("subchain %d head mismatch: have %d, want %d", j, progress.Subchains[j].Head, expected.state[j].Head) + } + if progress.Subchains[j].Tail != expected.state[j].Tail { + return fmt.Errorf("subchain %d tail mismatch: have %d, want %d", j, progress.Subchains[j].Tail, expected.state[j].Tail) + } + } + if !unpredictable { + var served uint64 + for _, peer := range peers { + served += peer.served.Load() + } + if served != expected.serve { + return fmt.Errorf("served headers mismatch: have %d, want %d", served, expected.serve) + } + var drops uint64 + for _, peer := range peers { + drops += peer.dropped.Load() + } + if drops != expected.drop { + return fmt.Errorf("dropped peers mismatch: have %d, want %d", drops, expected.drop) + } + } + return nil +} diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go new file mode 100644 index 0000000..501af63 --- /dev/null +++ b/eth/downloader/statesync.go @@ -0,0 +1,123 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package downloader + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// syncState starts downloading state with the given root hash. +func (d *Downloader) syncState(root common.Hash) *stateSync { + // Create the state sync + s := newStateSync(d, root) + select { + case d.stateSyncStart <- s: + // If we tell the statesync to restart with a new root, we also need + // to wait for it to actually also start -- when old requests have timed + // out or been delivered + <-s.started + case <-d.quitCh: + s.err = errCancelStateFetch + close(s.done) + } + return s +} + +// stateFetcher manages the active state sync and accepts requests +// on its behalf. +func (d *Downloader) stateFetcher() { + for { + select { + case s := <-d.stateSyncStart: + for next := s; next != nil; { + next = d.runStateSync(next) + } + case <-d.quitCh: + return + } + } +} + +// runStateSync runs a state synchronisation until it completes or another root +// hash is requested to be switched over to. +func (d *Downloader) runStateSync(s *stateSync) *stateSync { + log.Trace("State sync starting", "root", s.root) + + go s.run() + defer s.Cancel() + + for { + select { + case next := <-d.stateSyncStart: + return next + + case <-s.done: + return nil + } + } +} + +// stateSync schedules requests for downloading a particular state trie defined +// by a given state root. +type stateSync struct { + d *Downloader // Downloader instance to access and manage current peerset + root common.Hash // State root currently being synced + + started chan struct{} // Started is signalled once the sync loop starts + cancel chan struct{} // Channel to signal a termination request + cancelOnce sync.Once // Ensures cancel only ever gets called once + done chan struct{} // Channel to signal termination completion + err error // Any error hit during sync (set before completion) +} + +// newStateSync creates a new state trie download scheduler. This method does not +// yet start the sync. The user needs to call run to initiate. +func newStateSync(d *Downloader, root common.Hash) *stateSync { + return &stateSync{ + d: d, + root: root, + cancel: make(chan struct{}), + done: make(chan struct{}), + started: make(chan struct{}), + } +} + +// run starts the task assignment and response processing loop, blocking until +// it finishes, and finally notifying any goroutines waiting for the loop to +// finish. +func (s *stateSync) run() { + close(s.started) + s.err = s.d.SnapSyncer.Sync(s.root, s.cancel) + close(s.done) +} + +// Wait blocks until the sync is done or canceled. +func (s *stateSync) Wait() error { + <-s.done + return s.err +} + +// Cancel cancels the sync and waits until it has shut down. +func (s *stateSync) Cancel() error { + s.cancelOnce.Do(func() { + close(s.cancel) + }) + return s.Wait() +} diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go new file mode 100644 index 0000000..6043f51 --- /dev/null +++ b/eth/downloader/testchain_test.go @@ -0,0 +1,230 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package downloader + +import ( + "fmt" + "math/big" + "sync" + "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/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" +) + +// Test chain parameters. +var ( + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddress = crypto.PubkeyToAddress(testKey.PublicKey) + testDB = rawdb.NewMemoryDatabase() + + testGspec = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + testGenesis = testGspec.MustCommit(testDB, triedb.NewDatabase(testDB, triedb.HashDefaults)) +) + +// The common prefix of all test chains: +var testChainBase *testChain + +// Different forks on top of the base chain: +var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain + +var pregenerated bool + +func init() { + // Reduce some of the parameters to make the tester faster + fullMaxForkAncestry = 10000 + blockCacheMaxItems = 1024 + fsHeaderSafetyNet = 256 + fsHeaderContCheck = 500 * time.Millisecond + + testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis) + + var forkLen = int(fullMaxForkAncestry + 50) + var wg sync.WaitGroup + + // Generate the test chains to seed the peers with + wg.Add(3) + go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }() + go func() { testChainForkLightB = testChainBase.makeFork(forkLen, false, 2); wg.Done() }() + go func() { testChainForkHeavy = testChainBase.makeFork(forkLen, true, 3); wg.Done() }() + wg.Wait() + + // Generate the test peers used by the tests to avoid overloading during testing. + // These seemingly random chains are used in various downloader tests. We're just + // pre-generating them here. + chains := []*testChain{ + testChainBase, + testChainForkLightA, + testChainForkLightB, + testChainForkHeavy, + testChainBase.shorten(1), + testChainBase.shorten(blockCacheMaxItems - 15), + testChainBase.shorten((blockCacheMaxItems - 15) / 2), + testChainBase.shorten(blockCacheMaxItems - 15 - 5), + testChainBase.shorten(MaxHeaderFetch), + testChainBase.shorten(800), + testChainBase.shorten(800 / 2), + testChainBase.shorten(800 / 3), + testChainBase.shorten(800 / 4), + testChainBase.shorten(800 / 5), + testChainBase.shorten(800 / 6), + testChainBase.shorten(800 / 7), + testChainBase.shorten(800 / 8), + testChainBase.shorten(3*fsHeaderSafetyNet + 256 + fsMinFullBlocks), + testChainBase.shorten(fsMinFullBlocks + 256 - 1), + testChainForkLightA.shorten(len(testChainBase.blocks) + 80), + testChainForkLightB.shorten(len(testChainBase.blocks) + 81), + testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch), + testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch), + testChainForkHeavy.shorten(len(testChainBase.blocks) + 79), + } + wg.Add(len(chains)) + for _, chain := range chains { + go func(blocks []*types.Block) { + newTestBlockchain(blocks) + wg.Done() + }(chain.blocks[1:]) + } + wg.Wait() + + // Mark the chains pregenerated. Generating a new one will lead to a panic. + pregenerated = true +} + +type testChain struct { + blocks []*types.Block +} + +// newTestChain creates a blockchain of the given length. +func newTestChain(length int, genesis *types.Block) *testChain { + tc := &testChain{ + blocks: []*types.Block{genesis}, + } + tc.generate(length-1, 0, genesis, false) + return tc +} + +// makeFork creates a fork on top of the test chain. +func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain { + fork := tc.copy(len(tc.blocks) + length) + fork.generate(length, seed, tc.blocks[len(tc.blocks)-1], heavy) + return fork +} + +// shorten creates a copy of the chain with the given length. It panics if the +// length is longer than the number of available blocks. +func (tc *testChain) shorten(length int) *testChain { + if length > len(tc.blocks) { + panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, len(tc.blocks))) + } + return tc.copy(length) +} + +func (tc *testChain) copy(newlen int) *testChain { + if newlen > len(tc.blocks) { + newlen = len(tc.blocks) + } + cpy := &testChain{ + blocks: append([]*types.Block{}, tc.blocks[:newlen]...), + } + return cpy +} + +// generate creates a chain of n blocks starting at and including parent. +// the returned hash chain is ordered head->parent. In addition, every 22th block +// contains a transaction and every 5th an uncle to allow testing correct block +// reassembly. +func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) { + blocks, _ := core.GenerateChain(testGspec.Config, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) { + block.SetCoinbase(common.Address{seed}) + // If a heavy chain is requested, delay blocks to raise difficulty + if heavy { + block.OffsetTime(-9) + } + // Include transactions to the miner to make blocks more interesting. + if parent == tc.blocks[0] && i%22 == 0 { + signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) + if err != nil { + panic(err) + } + block.AddTx(tx) + } + // if the block number is a multiple of 5, add a bonus uncle to the block + if i > 0 && i%5 == 0 { + block.AddUncle(&types.Header{ + ParentHash: block.PrevBlock(i - 2).Hash(), + Number: big.NewInt(block.Number().Int64() - 1), + }) + } + }) + tc.blocks = append(tc.blocks, blocks...) +} + +var ( + testBlockchains = make(map[common.Hash]*testBlockchain) + testBlockchainsLock sync.Mutex +) + +type testBlockchain struct { + chain *core.BlockChain + gen sync.Once +} + +// newTestBlockchain creates a blockchain database built by running the given blocks, +// either actually running them, or reusing a previously created one. The returned +// chains are *shared*, so *do not* mutate them. +func newTestBlockchain(blocks []*types.Block) *core.BlockChain { + // Retrieve an existing database, or create a new one + head := testGenesis.Hash() + if len(blocks) > 0 { + head = blocks[len(blocks)-1].Hash() + } + testBlockchainsLock.Lock() + if _, ok := testBlockchains[head]; !ok { + testBlockchains[head] = new(testBlockchain) + } + tbc := testBlockchains[head] + testBlockchainsLock.Unlock() + + // Ensure that the database is generated + tbc.gen.Do(func() { + if pregenerated { + panic("Requested chain generation outside of init") + } + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, testGspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + if err != nil { + panic(err) + } + if n, err := chain.InsertChain(blocks); err != nil { + panic(fmt.Sprintf("block %d: %v", n, err)) + } + tbc.chain = chain + }) + return tbc.chain +} diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go new file mode 100644 index 0000000..7453fb1 --- /dev/null +++ b/eth/ethconfig/config.go @@ -0,0 +1,185 @@ +// 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 ethconfig contains the configuration of the ETH and LES protocols. +package ethconfig + +import ( + "errors" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/txpool/blobpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/params" +) + +// FullNodeGPO contains default gasprice oracle settings for full node. +var FullNodeGPO = gasprice.Config{ + Blocks: 20, + Percentile: 60, + MaxHeaderHistory: 1024, + MaxBlockHistory: 1024, + MaxPrice: gasprice.DefaultMaxPrice, + IgnorePrice: gasprice.DefaultIgnorePrice, +} + +// Defaults contains default settings for use on the Ethereum main net. +var Defaults = Config{ + SyncMode: downloader.SnapSync, + NetworkId: 0, // enable auto configuration of networkID == chainID + TxLookupLimit: 2350000, + TransactionHistory: 2350000, + StateHistory: params.FullImmutabilityThreshold, + LightPeers: 100, + DatabaseCache: 512, + TrieCleanCache: 154, + TrieDirtyCache: 256, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 102, + FilterLogCacheSize: 32, + Miner: miner.DefaultConfig, + TxPool: legacypool.DefaultConfig, + BlobPool: blobpool.DefaultConfig, + RPCGasCap: 50000000, + RPCEVMTimeout: 5 * time.Second, + GPO: FullNodeGPO, + RPCTxFeeCap: 1, // 1 ether +} + +//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go + +// Config contains configuration options for 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"` + + // 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 + // nodes to connect to. + EthDiscoveryURLs []string + SnapDiscoveryURLs []string + + NoPruning bool // Whether to disable pruning and flush everything to disk + NoPrefetch bool // Whether to disable prefetching and only load state on demand + + // 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 + // presence of these blocks for every new peer connection. + RequiredBlocks map[uint64]common.Hash `toml:"-"` + + // Light client options + LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests + LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers + LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers + LightPeers int `toml:",omitempty"` // Maximum number of LES client peers + LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning + LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing + + // Database options + SkipBcVersionCheck bool `toml:"-"` + DatabaseHandles int `toml:"-"` + DatabaseCache int + DatabaseFreezer string + + TrieCleanCache int + TrieDirtyCache int + TrieTimeout time.Duration + SnapshotCache int + Preimages bool + + // This is the number of blocks for which logs will be cached in the filter system. + FilterLogCacheSize int + + // Mining options + Miner miner.Config + + // Transaction pool options + TxPool legacypool.Config + BlobPool blobpool.Config + + // Gas Price Oracle options + GPO gasprice.Config + + // Enables tracking of SHA3 preimages in the VM + EnablePreimageRecording bool + + // Enables prefetching trie nodes for read operations too + EnableWitnessCollection bool `toml:"-"` + + // Enables VM tracing + VMTrace string + VMTraceJsonConfig string + + // Miscellaneous options + DocRoot string `toml:"-"` + + // RPCGasCap is the global gas cap for eth-call variants. + RPCGasCap uint64 + + // RPCEVMTimeout is the global timeout for eth-call. + RPCEVMTimeout time.Duration + + // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for + // send-transaction variants. The unit is ether. + RPCTxFeeCap float64 + + // OverrideCancun (TODO: remove after the fork) + OverrideCancun *uint64 `toml:",omitempty"` + + // OverrideVerkle (TODO: remove after the fork) + OverrideVerkle *uint64 `toml:",omitempty"` +} + +// CreateConsensusEngine creates a consensus engine for the given chain config. +// Clique is allowed for now to live standalone, but ethash is forbidden and can +// only exist on already merged networks. +func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (consensus.Engine, error) { + // Geth v1.14.0 dropped support for non-merged networks in any consensus + // mode. If such a network is requested, reject startup. + if !config.TerminalTotalDifficultyPassed { + return nil, errors.New("only PoS networks are supported, please transition old ones with Geth v1.13.x") + } + // Wrap previously supported consensus engines into their post-merge counterpart + if config.Clique != nil { + return beacon.New(clique.New(config.Clique, db)), nil + } + return beacon.New(ethash.NewFaker()), nil +} diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go new file mode 100644 index 0000000..147a559 --- /dev/null +++ b/eth/ethconfig/gen_config.go @@ -0,0 +1,286 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package ethconfig + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/txpool/blobpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/miner" +) + +// MarshalTOML marshals as TOML. +func (c Config) MarshalTOML() (interface{}, error) { + type Config struct { + Genesis *core.Genesis `toml:",omitempty"` + NetworkId uint64 + SyncMode downloader.SyncMode + EthDiscoveryURLs []string + SnapDiscoveryURLs []string + 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"` + LightEgress int `toml:",omitempty"` + LightPeers int `toml:",omitempty"` + LightNoPrune bool `toml:",omitempty"` + LightNoSyncServe bool `toml:",omitempty"` + SkipBcVersionCheck bool `toml:"-"` + DatabaseHandles int `toml:"-"` + DatabaseCache int + DatabaseFreezer string + TrieCleanCache int + TrieDirtyCache int + TrieTimeout time.Duration + SnapshotCache int + Preimages bool + FilterLogCacheSize int + Miner miner.Config + TxPool legacypool.Config + BlobPool blobpool.Config + GPO gasprice.Config + EnablePreimageRecording bool + EnableWitnessCollection bool `toml:"-"` + VMTrace string + VMTraceJsonConfig string + DocRoot string `toml:"-"` + RPCGasCap uint64 + RPCEVMTimeout time.Duration + RPCTxFeeCap float64 + OverrideCancun *uint64 `toml:",omitempty"` + OverrideVerkle *uint64 `toml:",omitempty"` + } + var enc Config + enc.Genesis = c.Genesis + enc.NetworkId = c.NetworkId + enc.SyncMode = c.SyncMode + enc.EthDiscoveryURLs = c.EthDiscoveryURLs + enc.SnapDiscoveryURLs = c.SnapDiscoveryURLs + 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 + enc.LightEgress = c.LightEgress + enc.LightPeers = c.LightPeers + enc.LightNoPrune = c.LightNoPrune + enc.LightNoSyncServe = c.LightNoSyncServe + enc.SkipBcVersionCheck = c.SkipBcVersionCheck + enc.DatabaseHandles = c.DatabaseHandles + enc.DatabaseCache = c.DatabaseCache + enc.DatabaseFreezer = c.DatabaseFreezer + enc.TrieCleanCache = c.TrieCleanCache + enc.TrieDirtyCache = c.TrieDirtyCache + enc.TrieTimeout = c.TrieTimeout + enc.SnapshotCache = c.SnapshotCache + enc.Preimages = c.Preimages + enc.FilterLogCacheSize = c.FilterLogCacheSize + enc.Miner = c.Miner + enc.TxPool = c.TxPool + enc.BlobPool = c.BlobPool + enc.GPO = c.GPO + enc.EnablePreimageRecording = c.EnablePreimageRecording + enc.EnableWitnessCollection = c.EnableWitnessCollection + enc.VMTrace = c.VMTrace + enc.VMTraceJsonConfig = c.VMTraceJsonConfig + enc.DocRoot = c.DocRoot + enc.RPCGasCap = c.RPCGasCap + enc.RPCEVMTimeout = c.RPCEVMTimeout + enc.RPCTxFeeCap = c.RPCTxFeeCap + enc.OverrideCancun = c.OverrideCancun + enc.OverrideVerkle = c.OverrideVerkle + return &enc, nil +} + +// UnmarshalTOML unmarshals from TOML. +func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { + type Config struct { + Genesis *core.Genesis `toml:",omitempty"` + NetworkId *uint64 + SyncMode *downloader.SyncMode + EthDiscoveryURLs []string + SnapDiscoveryURLs []string + 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"` + LightEgress *int `toml:",omitempty"` + LightPeers *int `toml:",omitempty"` + LightNoPrune *bool `toml:",omitempty"` + LightNoSyncServe *bool `toml:",omitempty"` + SkipBcVersionCheck *bool `toml:"-"` + DatabaseHandles *int `toml:"-"` + DatabaseCache *int + DatabaseFreezer *string + TrieCleanCache *int + TrieDirtyCache *int + TrieTimeout *time.Duration + SnapshotCache *int + Preimages *bool + FilterLogCacheSize *int + Miner *miner.Config + TxPool *legacypool.Config + BlobPool *blobpool.Config + GPO *gasprice.Config + EnablePreimageRecording *bool + EnableWitnessCollection *bool `toml:"-"` + VMTrace *string + VMTraceJsonConfig *string + DocRoot *string `toml:"-"` + RPCGasCap *uint64 + RPCEVMTimeout *time.Duration + RPCTxFeeCap *float64 + OverrideCancun *uint64 `toml:",omitempty"` + OverrideVerkle *uint64 `toml:",omitempty"` + } + var dec Config + if err := unmarshal(&dec); err != nil { + return err + } + if dec.Genesis != nil { + c.Genesis = dec.Genesis + } + if dec.NetworkId != nil { + c.NetworkId = *dec.NetworkId + } + if dec.SyncMode != nil { + c.SyncMode = *dec.SyncMode + } + if dec.EthDiscoveryURLs != nil { + c.EthDiscoveryURLs = dec.EthDiscoveryURLs + } + if dec.SnapDiscoveryURLs != nil { + c.SnapDiscoveryURLs = dec.SnapDiscoveryURLs + } + if dec.NoPruning != nil { + c.NoPruning = *dec.NoPruning + } + if dec.NoPrefetch != nil { + c.NoPrefetch = *dec.NoPrefetch + } + 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 + } + if dec.LightServ != nil { + c.LightServ = *dec.LightServ + } + if dec.LightIngress != nil { + c.LightIngress = *dec.LightIngress + } + if dec.LightEgress != nil { + c.LightEgress = *dec.LightEgress + } + if dec.LightPeers != nil { + c.LightPeers = *dec.LightPeers + } + if dec.LightNoPrune != nil { + c.LightNoPrune = *dec.LightNoPrune + } + if dec.LightNoSyncServe != nil { + c.LightNoSyncServe = *dec.LightNoSyncServe + } + if dec.SkipBcVersionCheck != nil { + c.SkipBcVersionCheck = *dec.SkipBcVersionCheck + } + if dec.DatabaseHandles != nil { + c.DatabaseHandles = *dec.DatabaseHandles + } + if dec.DatabaseCache != nil { + c.DatabaseCache = *dec.DatabaseCache + } + if dec.DatabaseFreezer != nil { + c.DatabaseFreezer = *dec.DatabaseFreezer + } + if dec.TrieCleanCache != nil { + c.TrieCleanCache = *dec.TrieCleanCache + } + if dec.TrieDirtyCache != nil { + c.TrieDirtyCache = *dec.TrieDirtyCache + } + if dec.TrieTimeout != nil { + c.TrieTimeout = *dec.TrieTimeout + } + if dec.SnapshotCache != nil { + c.SnapshotCache = *dec.SnapshotCache + } + if dec.Preimages != nil { + c.Preimages = *dec.Preimages + } + if dec.FilterLogCacheSize != nil { + c.FilterLogCacheSize = *dec.FilterLogCacheSize + } + if dec.Miner != nil { + c.Miner = *dec.Miner + } + if dec.TxPool != nil { + c.TxPool = *dec.TxPool + } + if dec.BlobPool != nil { + c.BlobPool = *dec.BlobPool + } + if dec.GPO != nil { + c.GPO = *dec.GPO + } + if dec.EnablePreimageRecording != nil { + c.EnablePreimageRecording = *dec.EnablePreimageRecording + } + if dec.EnableWitnessCollection != nil { + c.EnableWitnessCollection = *dec.EnableWitnessCollection + } + if dec.VMTrace != nil { + c.VMTrace = *dec.VMTrace + } + if dec.VMTraceJsonConfig != nil { + c.VMTraceJsonConfig = *dec.VMTraceJsonConfig + } + if dec.DocRoot != nil { + c.DocRoot = *dec.DocRoot + } + if dec.RPCGasCap != nil { + c.RPCGasCap = *dec.RPCGasCap + } + if dec.RPCEVMTimeout != nil { + c.RPCEVMTimeout = *dec.RPCEVMTimeout + } + if dec.RPCTxFeeCap != nil { + c.RPCTxFeeCap = *dec.RPCTxFeeCap + } + if dec.OverrideCancun != nil { + c.OverrideCancun = dec.OverrideCancun + } + if dec.OverrideVerkle != nil { + c.OverrideVerkle = dec.OverrideVerkle + } + return nil +} diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go new file mode 100644 index 0000000..18c5ff0 --- /dev/null +++ b/eth/fetcher/tx_fetcher.go @@ -0,0 +1,1005 @@ +// 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 fetcher + +import ( + "bytes" + "errors" + "fmt" + "math" + mrand "math/rand" + "sort" + "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/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +const ( + // maxTxAnnounces is the maximum number of unique transaction a peer + // can announce in a short time. + maxTxAnnounces = 4096 + + // 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 + + // txGatherSlack is the interval used to collate almost-expired announces + // with network fetches. + txGatherSlack = 100 * time.Millisecond +) + +var ( + // txFetchTimeout is the maximum allotted time to return an explicitly + // requested transaction. + txFetchTimeout = 5 * time.Second +) + +var ( + txAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/in", nil) + txAnnounceKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/known", nil) + txAnnounceUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/underpriced", nil) + txAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/announces/dos", nil) + + txBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/in", nil) + txBroadcastKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/known", nil) + txBroadcastUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/underpriced", nil) + txBroadcastOtherRejectMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/broadcasts/otherreject", nil) + + txRequestOutMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/out", nil) + txRequestFailMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/fail", nil) + txRequestDoneMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/done", nil) + txRequestTimeoutMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/request/timeout", nil) + + txReplyInMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/in", nil) + txReplyKnownMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/known", nil) + txReplyUnderpricedMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/underpriced", nil) + txReplyOtherRejectMeter = metrics.NewRegisteredMeter("eth/fetcher/transaction/replies/otherreject", nil) + + txFetcherWaitingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/waiting/peers", nil) + txFetcherWaitingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/waiting/hashes", nil) + txFetcherQueueingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/queueing/peers", nil) + txFetcherQueueingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/queueing/hashes", nil) + txFetcherFetchingPeers = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/peers", nil) + txFetcherFetchingHashes = metrics.NewRegisteredGauge("eth/fetcher/transaction/fetching/hashes", nil) +) + +var errTerminated = errors.New("terminated") + +// txAnnounce is the notification of the availability of a batch +// of new transactions in the network. +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 +// a specific peers. +type txRequest struct { + hashes []common.Hash // Transactions having been requested + stolen map[common.Hash]struct{} // Deliveries by someone else (don't re-request) + time mclock.AbsTime // Timestamp of the request +} + +// txDelivery is the notification that a batch of transactions have been added +// to the pool and should be untracked. +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 +} + +// txDrop is the notification that a peer has disconnected. +type txDrop struct { + peer string +} + +// TxFetcher is responsible for retrieving new transaction based on announcements. +// +// The fetcher operates in 3 stages: +// - Transactions that are newly discovered are moved into a wait list. +// - After ~500ms passes, transactions from the wait list that have not been +// broadcast to us in whole are moved into a queueing area. +// - When a connected peer doesn't have in-flight retrieval requests, any +// transaction queued up (and announced by the peer) are allocated to the +// peer and moved into a fetching status until it's fulfilled or fails. +// +// The invariants of the fetcher are: +// - Each tracked transaction (hash) must only be present in one of the +// three stages. This ensures that the fetcher operates akin to a finite +// state automata and there's do data leak. +// - Each peer that announced transactions may be scheduled retrievals, but +// only ever one concurrently. This ensures we can immediately know what is +// missing from a reply and reschedule it. +type TxFetcher struct { + notify chan *txAnnounce + cleanup chan *txDelivery + drop chan *txDrop + quit chan struct{} + + 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]*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]*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 + // previous stage to avoid having to duplicate (need it for DoS checks). + fetching map[common.Hash]string // Transaction set currently being retrieved + requests map[string]*txRequest // In-flight transaction retrievals + 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([]*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 + rand *mrand.Rand // Randomizer to use in tests instead of map range loops (soft-random) +} + +// NewTxFetcher creates a transaction fetcher to retrieve transaction +// based on hash announcements. +func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher { + return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, dropPeer, mclock.System{}, nil) +} + +// NewTxFetcherForTests is a testing method to mock out the realtime clock with +// a simulated version and the internal randomness with a deterministic one. +func NewTxFetcherForTests( + hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string), + clock mclock.Clock, rand *mrand.Rand) *TxFetcher { + return &TxFetcher{ + notify: make(chan *txAnnounce), + cleanup: make(chan *txDelivery), + drop: make(chan *txDrop), + 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]*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: lru.NewCache[common.Hash, time.Time](maxTxUnderpricedSetSize), + hasTx: hasTx, + addTxs: addTxs, + fetchTxs: fetchTxs, + dropPeer: dropPeer, + clock: clock, + rand: rand, + } +} + +// Notify announces the fetcher of the potential availability of a new batch of +// transactions in the network. +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))) + + // Skip any transaction announcements that we already know of, or that we've + // previously marked as cheap and discarded. This check is of course racy, + // because multiple concurrent notifies will still manage to pass it, but it's + // still valuable to check here because it runs concurrent to the internal + // loop, so anything caught here is time saved internally. + var ( + unknownHashes = make([]common.Hash, 0, len(hashes)) + unknownMetas = make([]*txMetadata, 0, len(hashes)) + + duplicate int64 + underpriced int64 + ) + for i, hash := range hashes { + switch { + case f.hasTx(hash): + duplicate++ + case f.isKnownUnderpriced(hash): + underpriced++ + default: + 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(unknownHashes) == 0 { + return nil + } + announce := &txAnnounce{origin: peer, hashes: unknownHashes, metas: unknownMetas} + select { + case f.notify <- announce: + return nil + case <-f.quit: + return errTerminated + } +} + +// 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 +// re-schedule missing transactions as soon as possible. +func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) error { + var ( + inMeter = txReplyInMeter + knownMeter = txReplyKnownMeter + underpricedMeter = txReplyUnderpricedMeter + otherRejectMeter = txReplyOtherRejectMeter + ) + if !direct { + inMeter = txBroadcastInMeter + knownMeter = txBroadcastKnownMeter + underpricedMeter = txBroadcastUnderpricedMeter + otherRejectMeter = txBroadcastOtherRejectMeter + } + // Keep track of all the propagated transactions + inMeter.Mark(int64(len(txs))) + + // Push all the transactions into the pool, tracking underpriced ones to avoid + // 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 { + end := i + 128 + if end > len(txs) { + end = len(txs) + } + var ( + duplicate int64 + underpriced int64 + otherreject int64 + ) + batch := txs[i:end] + + for j, err := range f.addTxs(batch) { + // Track the transaction hash if the price is too low for us. + // Avoid re-request this transaction when we receive another + // announcement. + if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) { + f.underpriced.Add(batch[j].Hash(), batch[j].Time()) + } + // Track a few interesting failure types + switch { + case err == nil: // Noop, but need to handle to not count these + + case errors.Is(err, txpool.ErrAlreadyKnown): + duplicate++ + + case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced): + underpriced++ + + default: + 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) + otherRejectMeter.Mark(otherreject) + + // If 'other reject' is >25% of the deliveries in any batch, sleep a bit. + if otherreject > 128/4 { + time.Sleep(200 * time.Millisecond) + log.Debug("Peer delivering stale transactions", "peer", peer, "rejected", otherreject) + } + } + select { + case f.cleanup <- &txDelivery{origin: peer, hashes: added, metas: metas, direct: direct}: + return nil + case <-f.quit: + return errTerminated + } +} + +// Drop should be called when a peer disconnects. It cleans up all the internal +// data structures of the given node. +func (f *TxFetcher) Drop(peer string) error { + select { + case f.drop <- &txDrop{peer: peer}: + return nil + case <-f.quit: + return errTerminated + } +} + +// Start boots up the announcement based synchroniser, accepting and processing +// hash notifications and block fetches until termination requested. +func (f *TxFetcher) Start() { + go f.loop() +} + +// Stop terminates the announcement based synchroniser, canceling all pending +// operations. +func (f *TxFetcher) Stop() { + close(f.quit) +} + +func (f *TxFetcher) loop() { + var ( + waitTimer = new(mclock.Timer) + timeoutTimer = new(mclock.Timer) + + waitTrigger = make(chan struct{}, 1) + timeoutTrigger = make(chan struct{}, 1) + ) + for { + select { + case ann := <-f.notify: + // Drop part of the new announcements if there are too many accumulated. + // Note, we could but do not filter already known transactions here as + // the probability of something arriving between this call and the pre- + // filter outside is essentially zero. + used := len(f.waitslots[ann.origin]) + len(f.announces[ann.origin]) + if used >= maxTxAnnounces { + // This can happen if a set of transactions are requested but not + // all fulfilled, so the remainder are rescheduled without the cap + // check. Should be fine as the limit is in the thousands and the + // request size in the hundreds. + txAnnounceDOSMeter.Mark(int64(len(ann.hashes))) + break + } + 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 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. + if f.alternates[hash] != nil { + f.alternates[hash][ann.origin] = struct{}{} + + // Stage 2 and 3 share the set of origins per tx + if announces := f.announces[ann.origin]; announces != nil { + announces[hash] = ann.metas[i] + } else { + f.announces[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]} + } + continue + } + // If the transaction is not downloading, but is already queued + // from a different peer, track it for the new peer too. + if f.announced[hash] != nil { + f.announced[hash][ann.origin] = struct{}{} + + // Stage 2 and 3 share the set of origins per tx + if announces := f.announces[ann.origin]; announces != nil { + announces[hash] = ann.metas[i] + } else { + f.announces[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]} + } + continue + } + // If the transaction is already known to the fetcher, but not + // 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] = ann.metas[i] + } else { + f.waitslots[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]} + } + continue + } + // Transaction unknown to the fetcher, insert it into the waiting list + f.waitlist[hash] = map[string]struct{}{ann.origin: {}} + f.waittime[hash] = f.clock.Now() + + if waitslots := f.waitslots[ann.origin]; waitslots != nil { + waitslots[hash] = ann.metas[i] + } else { + 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 + if idleWait && len(f.waittime) > 0 { + f.rescheduleWait(waitTimer, waitTrigger) + } + // If this peer is new and announced something already queued, maybe + // request transactions from them + if !oldPeer && len(f.announces[ann.origin]) > 0 { + f.scheduleFetches(timeoutTimer, timeoutTrigger, map[string]struct{}{ann.origin: {}}) + } + + case <-waitTrigger: + // At least one transaction's waiting time ran out, push all expired + // ones into the retrieval queues + actives := make(map[string]struct{}) + for hash, instance := range f.waittime { + if time.Duration(f.clock.Now()-instance)+txGatherSlack > txArriveTimeout { + // Transaction expired without propagation, schedule for retrieval + if f.announced[hash] != nil { + panic("announce tracker already contains waitlist item") + } + f.announced[hash] = f.waitlist[hash] + for peer := range f.waitlist[hash] { + if announces := f.announces[peer]; announces != nil { + announces[hash] = f.waitslots[peer][hash] + } else { + f.announces[peer] = map[common.Hash]*txMetadata{hash: f.waitslots[peer][hash]} + } + delete(f.waitslots[peer], hash) + if len(f.waitslots[peer]) == 0 { + delete(f.waitslots, peer) + } + actives[peer] = struct{}{} + } + delete(f.waittime, hash) + delete(f.waitlist, hash) + } + } + // If transactions are still waiting for propagation, reschedule the wait timer + if len(f.waittime) > 0 { + f.rescheduleWait(waitTimer, waitTrigger) + } + // If any peers became active and are idle, request transactions from them + if len(actives) > 0 { + f.scheduleFetches(timeoutTimer, timeoutTrigger, actives) + } + + case <-timeoutTrigger: + // Clean up any expired retrievals and avoid re-requesting them from the + // same peer (either overloaded or malicious, useless in both cases). We + // could also penalize (Drop), but there's nothing to gain, and if could + // possibly further increase the load on it. + for peer, req := range f.requests { + if time.Duration(f.clock.Now()-req.time)+txGatherSlack > txFetchTimeout { + txRequestTimeoutMeter.Mark(int64(len(req.hashes))) + + // Reschedule all the not-yet-delivered fetches to alternate peers + for _, hash := range req.hashes { + // Skip rescheduling hashes already delivered by someone else + if req.stolen != nil { + if _, ok := req.stolen[hash]; ok { + continue + } + } + // Move the delivery back from fetching to queued + if _, ok := f.announced[hash]; ok { + panic("announced tracker already contains alternate item") + } + if f.alternates[hash] != nil { // nil if tx was broadcast during fetch + f.announced[hash] = f.alternates[hash] + } + delete(f.announced[hash], peer) + if len(f.announced[hash]) == 0 { + delete(f.announced, hash) + } + delete(f.announces[peer], hash) + delete(f.alternates, hash) + delete(f.fetching, hash) + } + if len(f.announces[peer]) == 0 { + delete(f.announces, peer) + } + // Keep track of the request as dangling, but never expire + f.requests[peer].hashes = nil + } + } + // Schedule a new transaction retrieval + f.scheduleFetches(timeoutTimer, timeoutTrigger, nil) + + // No idea if we scheduled something or not, trigger the timer if needed + // TODO(karalabe): this is kind of lame, can't we dump it into scheduleFetches somehow? + f.rescheduleTimeout(timeoutTimer, timeoutTrigger) + + case delivery := <-f.cleanup: + // Independent if the delivery was direct or broadcast, remove all + // 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) + } + } + delete(f.waitlist, hash) + 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) + } + } + delete(f.announced, hash) + delete(f.alternates, hash) + + // If a transaction currently being fetched from a different + // origin was delivered (delivery stolen), mark it so the + // actual delivery won't double schedule it. + if origin, ok := f.fetching[hash]; ok && (origin != delivery.origin || !delivery.direct) { + stolen := f.requests[origin].stolen + if stolen == nil { + f.requests[origin].stolen = make(map[common.Hash]struct{}) + stolen = f.requests[origin].stolen + } + stolen[hash] = struct{}{} + } + delete(f.fetching, hash) + } + } + // In case of a direct delivery, also reschedule anything missing + // from the original query + if delivery.direct { + // Mark the requesting successful (independent of individual status) + txRequestDoneMeter.Mark(int64(len(delivery.hashes))) + + // Make sure something was pending, nuke it + req := f.requests[delivery.origin] + if req == nil { + log.Warn("Unexpected transaction delivery", "peer", delivery.origin) + break + } + delete(f.requests, delivery.origin) + + // Anything not delivered should be re-scheduled (with or without + // this peer, depending on the response cutoff) + delivered := make(map[common.Hash]struct{}) + for _, hash := range delivery.hashes { + delivered[hash] = struct{}{} + } + cutoff := len(req.hashes) // If nothing is delivered, assume everything is missing, don't retry!!! + for i, hash := range req.hashes { + if _, ok := delivered[hash]; ok { + cutoff = i + } + } + // Reschedule missing hashes from alternates, not-fulfilled from alt+self + for i, hash := range req.hashes { + // Skip rescheduling hashes already delivered by someone else + if req.stolen != nil { + if _, ok := req.stolen[hash]; ok { + continue + } + } + if _, ok := delivered[hash]; !ok { + if i < cutoff { + delete(f.alternates[hash], delivery.origin) + delete(f.announces[delivery.origin], hash) + if len(f.announces[delivery.origin]) == 0 { + delete(f.announces, delivery.origin) + } + } + if len(f.alternates[hash]) > 0 { + if _, ok := f.announced[hash]; ok { + panic(fmt.Sprintf("announced tracker already contains alternate item: %v", f.announced[hash])) + } + f.announced[hash] = f.alternates[hash] + } + } + delete(f.alternates, hash) + delete(f.fetching, hash) + } + // Something was delivered, try to reschedule requests + f.scheduleFetches(timeoutTimer, timeoutTrigger, nil) // Partial delivery may enable others to deliver too + } + + case drop := <-f.drop: + // A peer was dropped, remove all traces of it + if _, ok := f.waitslots[drop.peer]; ok { + for hash := range f.waitslots[drop.peer] { + delete(f.waitlist[hash], drop.peer) + if len(f.waitlist[hash]) == 0 { + delete(f.waitlist, hash) + delete(f.waittime, hash) + } + } + delete(f.waitslots, drop.peer) + if len(f.waitlist) > 0 { + f.rescheduleWait(waitTimer, waitTrigger) + } + } + // Clean up any active requests + var request *txRequest + if request = f.requests[drop.peer]; request != nil { + for _, hash := range request.hashes { + // Skip rescheduling hashes already delivered by someone else + if request.stolen != nil { + if _, ok := request.stolen[hash]; ok { + continue + } + } + // Undelivered hash, reschedule if there's an alternative origin available + delete(f.alternates[hash], drop.peer) + if len(f.alternates[hash]) == 0 { + delete(f.alternates, hash) + } else { + f.announced[hash] = f.alternates[hash] + delete(f.alternates, hash) + } + delete(f.fetching, hash) + } + delete(f.requests, drop.peer) + } + // Clean up general announcement tracking + if _, ok := f.announces[drop.peer]; ok { + for hash := range f.announces[drop.peer] { + delete(f.announced[hash], drop.peer) + if len(f.announced[hash]) == 0 { + delete(f.announced, hash) + } + } + delete(f.announces, drop.peer) + } + // If a request was cancelled, check if anything needs to be rescheduled + if request != nil { + f.scheduleFetches(timeoutTimer, timeoutTrigger, nil) + f.rescheduleTimeout(timeoutTimer, timeoutTrigger) + } + + case <-f.quit: + return + } + // No idea what happened, but bump some sanity metrics + txFetcherWaitingPeers.Update(int64(len(f.waitslots))) + txFetcherWaitingHashes.Update(int64(len(f.waitlist))) + txFetcherQueueingPeers.Update(int64(len(f.announces) - len(f.requests))) + txFetcherQueueingHashes.Update(int64(len(f.announced))) + txFetcherFetchingPeers.Update(int64(len(f.requests))) + txFetcherFetchingHashes.Update(int64(len(f.fetching))) + + // Loop did something, ping the step notifier if needed (tests) + if f.step != nil { + f.step <- struct{}{} + } + } +} + +// rescheduleWait iterates over all the transactions currently in the waitlist +// and schedules the movement into the fetcher for the earliest. +// +// The method has a granularity of 'txGatherSlack', since there's not much point in +// spinning over all the transactions just to maybe find one that should trigger +// a few ms earlier. +func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) { + if *timer != nil { + (*timer).Stop() + } + now := f.clock.Now() + + earliest := now + for _, instance := range f.waittime { + if earliest > instance { + earliest = instance + if txArriveTimeout-time.Duration(now-earliest) < txGatherSlack { + break + } + } + } + *timer = f.clock.AfterFunc(txArriveTimeout-time.Duration(now-earliest), func() { + trigger <- struct{}{} + }) +} + +// rescheduleTimeout iterates over all the transactions currently in flight and +// schedules a cleanup run when the first would trigger. +// +// The method has a granularity of 'txGatherSlack', since there's not much point in +// spinning over all the transactions just to maybe find one that should trigger +// a few ms earlier. +// +// This method is a bit "flaky" "by design". In theory the timeout timer only ever +// should be rescheduled if some request is pending. In practice, a timeout will +// cause the timer to be rescheduled every 5 secs (until the peer comes through or +// disconnects). This is a limitation of the fetcher code because we don't trac +// pending requests and timed out requests separately. Without double tracking, if +// we simply didn't reschedule the timer on all-timeout then the timer would never +// be set again since len(request) > 0 => something's running. +func (f *TxFetcher) rescheduleTimeout(timer *mclock.Timer, trigger chan struct{}) { + if *timer != nil { + (*timer).Stop() + } + now := f.clock.Now() + + earliest := now + for _, req := range f.requests { + // If this request already timed out, skip it altogether + if req.hashes == nil { + continue + } + if earliest > req.time { + earliest = req.time + if txFetchTimeout-time.Duration(now-earliest) < txGatherSlack { + break + } + } + } + *timer = f.clock.AfterFunc(txFetchTimeout-time.Duration(now-earliest), func() { + trigger <- struct{}{} + }) +} + +// scheduleFetches starts a batch of retrievals for all available idle peers. +func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, whitelist map[string]struct{}) { + // Gather the set of peers we want to retrieve from (default to all) + actives := whitelist + if actives == nil { + actives = make(map[string]struct{}) + for peer := range f.announces { + actives[peer] = struct{}{} + } + } + if len(actives) == 0 { + return + } + // For each active peer, try to schedule some transaction fetches + idle := len(f.requests) == 0 + + f.forEachPeer(actives, func(peer string) { + if f.requests[peer] != nil { + return // continue in the for-each + } + if len(f.announces[peer]) == 0 { + return // continue in the for-each + } + 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 + + 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 // scheduled, try to add more + }) + // If any hashes were allocated, request them from the peer + if len(hashes) > 0 { + f.requests[peer] = &txRequest{hashes: hashes, time: f.clock.Now()} + txRequestOutMeter.Mark(int64(len(hashes))) + + go func(peer string, hashes []common.Hash) { + // Try to fetch the transactions, but in case of a request + // failure (e.g. peer disconnected), reschedule the hashes. + if err := f.fetchTxs(peer, hashes); err != nil { + txRequestFailMeter.Mark(int64(len(hashes))) + f.Drop(peer) + } + }(peer, hashes) + } + }) + // If a new request was fired, schedule a timeout timer + if idle && len(f.requests) > 0 { + f.rescheduleTimeout(timer, timeout) + } +} + +// forEachPeer does a range loop over a map of peers in production, but during +// testing it does a deterministic sorted random to allow reproducing issues. +func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string)) { + // If we're running production, use whatever Go's map gives us + if f.rand == nil { + for peer := range peers { + do(peer) + } + return + } + // We're running the test suite, make iteration deterministic + list := make([]string, 0, len(peers)) + for peer := range peers { + list = append(list, peer) + } + sort.Strings(list) + rotateStrings(list, f.rand.Intn(len(list))) + for _, peer := range list { + do(peer) + } +} + +// 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, meta := range announces { + if !do(hash, meta) { + return + } + } + return + } + // We're running the test suite, make iteration deterministic + 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, announces[hash]) { + return + } + } +} + +// rotateStrings rotates the contents of a slice by n steps. This method is only +// used in tests to simulate random map iteration but keep it deterministic. +func rotateStrings(slice []string, n int) { + orig := make([]string, len(slice)) + copy(orig, slice) + + for i := 0; i < len(orig); i++ { + slice[i] = orig[(i+n)%len(orig)] + } +} + +// sortHashes sorts a slice of hashes. This method is only used in tests in order +// to simulate random map iteration but keep it deterministic. +func sortHashes(slice []common.Hash) { + for i := 0; i < len(slice); i++ { + for j := i + 1; j < len(slice); j++ { + if bytes.Compare(slice[i][:], slice[j][:]) > 0 { + slice[i], slice[j] = slice[j], slice[i] + } + } + } +} + +// rotateHashes rotates the contents of a slice by n steps. This method is only +// used in tests to simulate random map iteration but keep it deterministic. +func rotateHashes(slice []common.Hash, n int) { + orig := make([]common.Hash, len(slice)) + copy(orig, slice) + + for i := 0; i < len(orig); i++ { + slice[i] = orig[(i+n)%len(orig)] + } +} diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go new file mode 100644 index 0000000..3d3ef81 --- /dev/null +++ b/eth/fetcher/tx_fetcher_test.go @@ -0,0 +1,2021 @@ +// 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 fetcher + +import ( + "errors" + "math/big" + "math/rand" + "slices" + "testing" + "time" + + "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/params" +) + +var ( + // testTxs is a set of transactions to use during testing that have meaningful hashes. + testTxs = []*types.Transaction{ + types.NewTransaction(5577006791947779410, common.Address{0x0f}, new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(15352856648520921629, common.Address{0xbb}, new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(3916589616287113937, common.Address{0x86}, new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(9828766684487745566, common.Address{0xac}, new(big.Int), 0, new(big.Int), nil), + } + // testTxsHashes is the hashes of the test transactions above + 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 + txs []*types.Transaction + direct bool +} +type doWait struct { + time time.Duration + step bool +} +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 + dangling map[string][]common.Hash +} +type isUnderpriced int + +// txFetcherTest represents a test scenario that can be executed by the test +// runner. +type txFetcherTest struct { + init func() *TxFetcher + steps []interface{} +} + +// Tests that transaction announcements are added to a waitlist, and none +// of them are scheduled for retrieval until the wait expires. +func TestTransactionFetcherWaiting(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}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }), + // Announce from a new peer to check that no overwrite happens + doTxNotify{peer: "B", hashes: []common.Hash{{0x03}, {0x04}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + "B": {{0x03}, {0x04}}, + }), + // Announce clashing hashes but unique new peer + doTxNotify{peer: "C", hashes: []common.Hash{{0x01}, {0x04}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + "B": {{0x03}, {0x04}}, + "C": {{0x01}, {0x04}}, + }), + // Announce existing and clashing hashes from existing peer + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x03}, {0x05}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}, {0x02}, {0x03}, {0x05}}, + "B": {{0x03}, {0x04}}, + "C": {{0x01}, {0x04}}, + }), + 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), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}, {0x03}, {0x05}}, + "B": {{0x03}, {0x04}}, + "C": {{0x01}, {0x04}}, + }, + fetching: map[string][]common.Hash{ // Depends on deterministic test randomizer + "A": {{0x02}, {0x03}, {0x05}}, + "C": {{0x01}, {0x04}}, + }, + }, + // 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}}}, + isWaiting(map[string][]common.Hash{ + "C": {{0x06}, {0x07}}, + }), + doWait{time: txArriveTimeout, step: true}, + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}, {0x03}, {0x05}}, + "B": {{0x03}, {0x04}}, + "C": {{0x01}, {0x04}, {0x06}, {0x07}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x02}, {0x03}, {0x05}}, + "C": {{0x01}, {0x04}}, + }, + }, + doTxNotify{peer: "D", hashes: []common.Hash{{0x06}, {0x07}}}, + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}, {0x03}, {0x05}}, + "B": {{0x03}, {0x04}}, + "C": {{0x01}, {0x04}, {0x06}, {0x07}}, + "D": {{0x06}, {0x07}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x02}, {0x03}, {0x05}}, + "C": {{0x01}, {0x04}}, + "D": {{0x06}, {0x07}}, + }, + }, + }, + }) +} + +// 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) { + 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{}{ + // Push an initial announcement through to the scheduled stage + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }), + isScheduled{tracking: nil, fetching: nil}, + + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + }, + // Announce overlaps from the same peer, ensure the new ones end up + // in stage one, and clashing ones don't get double tracked + doTxNotify{peer: "A", hashes: []common.Hash{{0x02}, {0x03}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x03}}, + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + }, + // Announce overlaps from a new peer, ensure new transactions end up + // in stage one and clashing ones get tracked for the new peer + doTxNotify{peer: "B", hashes: []common.Hash{{0x02}, {0x03}, {0x04}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x03}}, + "B": {{0x03}, {0x04}}, + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + "B": {{0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + }, + }, + }) +} + +// Tests that only a single transaction request gets scheduled to a peer +// and subsequent announces block or get allotted to someone else. +func TestTransactionFetcherSingletonRequesting(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{}{ + // Push an initial announcement through to the scheduled stage + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }), + isScheduled{tracking: nil, fetching: nil}, + + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + }, + // Announce a new set of transactions from the same peer and ensure + // they do not start fetching since the peer is already busy + doTxNotify{peer: "A", hashes: []common.Hash{{0x03}, {0x04}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x03}, {0x04}}, + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + }, + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}, {0x03}, {0x04}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + }, + // Announce a duplicate set of transactions from a new peer and ensure + // uniquely new ones start downloading, even if clashing. + doTxNotify{peer: "B", hashes: []common.Hash{{0x02}, {0x03}, {0x05}, {0x06}}}, + isWaiting(map[string][]common.Hash{ + "B": {{0x05}, {0x06}}, + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}, {0x03}, {0x04}}, + "B": {{0x02}, {0x03}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + "B": {{0x03}}, + }, + }, + }, + }) +} + +// Tests that if a transaction retrieval fails, all the transactions get +// instantly schedule back to someone else or the announcements dropped +// if no alternate source is available. +func TestTransactionFetcherFailedRescheduling(t *testing.T) { + // Create a channel to control when tx requests can fail + proceed := make(chan struct{}) + testTransactionFetcherParallel(t, txFetcherTest{ + init: func() *TxFetcher { + return NewTxFetcher( + func(common.Hash) bool { return false }, + nil, + func(origin string, hashes []common.Hash) error { + <-proceed + return errors.New("peer disconnected") + }, + nil, + ) + }, + steps: []interface{}{ + // Push an initial announcement through to the scheduled stage + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }), + isScheduled{tracking: nil, fetching: nil}, + + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + }, + // While the original peer is stuck in the request, push in an second + // data source. + doTxNotify{peer: "B", hashes: []common.Hash{{0x02}}}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + "B": {{0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + }, + // Wait until the original request fails and check that transactions + // are either rescheduled or dropped + doFunc(func() { + proceed <- struct{}{} // Allow peer A to return the failure + }), + doWait{time: 0, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "B": {{0x02}}, + }, + fetching: map[string][]common.Hash{ + "B": {{0x02}}, + }, + }, + doFunc(func() { + proceed <- struct{}{} // Allow peer B to return the failure + }), + doWait{time: 0, step: true}, + isWaiting(nil), + isScheduled{nil, nil, nil}, + }, + }) +} + +// Tests that if a transaction retrieval succeeds, all alternate origins +// are cleaned up. +func TestTransactionFetcherCleanup(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Push an initial announcement through to the scheduled stage + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + isWaiting(map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }), + isScheduled{tracking: nil, fetching: nil}, + + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + // Request should be delivered + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}, direct: true}, + isScheduled{nil, nil, nil}, + }, + }) +} + +// Tests that if a transaction retrieval succeeds, but the response is empty (no +// transactions available, then all are nuked instead of being rescheduled (yes, +// this was a bug)). +func TestTransactionFetcherCleanupEmpty(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Push an initial announcement through to the scheduled stage + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + isWaiting(map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }), + isScheduled{tracking: nil, fetching: nil}, + + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + // Deliver an empty response and ensure the transaction is cleared, not rescheduled + doTxEnqueue{peer: "A", txs: []*types.Transaction{}, direct: true}, + isScheduled{nil, nil, nil}, + }, + }) +} + +// Tests that non-returned transactions are either re-scheduled from a +// different peer, or self if they are after the cutoff point. +func TestTransactionFetcherMissingRescheduling(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Push an initial announcement through to the scheduled stage + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]}}, + isWaiting(map[string][]common.Hash{ + "A": {testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]}, + }), + isScheduled{tracking: nil, fetching: nil}, + + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]}, + }, + }, + // Deliver the middle transaction requested, the one before which + // should be dropped and the one after re-requested. + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}, direct: true}, // This depends on the deterministic random + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[2]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[2]}, + }, + }, + }, + }) +} + +// Tests that out of two transactions, if one is missing and the last is +// delivered, the peer gets properly cleaned out from the internal state. +func TestTransactionFetcherMissingCleanup(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Push an initial announcement through to the scheduled stage + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}}, + isWaiting(map[string][]common.Hash{ + "A": {testTxsHashes[0], testTxsHashes[1]}, + }), + isScheduled{tracking: nil, fetching: nil}, + + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[0], testTxsHashes[1]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0], testTxsHashes[1]}, + }, + }, + // Deliver the middle transaction requested, the one before which + // should be dropped and the one after re-requested. + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[1]}, direct: true}, // This depends on the deterministic random + isScheduled{nil, nil, nil}, + }, + }) +} + +// Tests that transaction broadcasts properly clean up announcements. +func TestTransactionFetcherBroadcasts(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Set up three transactions to be in different stats, waiting, queued and fetching + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[2]}}, + + isWaiting(map[string][]common.Hash{ + "A": {testTxsHashes[2]}, + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[0], testTxsHashes[1]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + // Broadcast all the transactions and ensure everything gets cleaned + // up, but the dangling request is left alone to avoid doing multiple + // concurrent requests. + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1], testTxs[2]}, direct: false}, + isWaiting(nil), + isScheduled{ + tracking: nil, + fetching: nil, + dangling: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + // Deliver the requested hashes + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1], testTxs[2]}, direct: true}, + isScheduled{nil, nil, nil}, + }, + }) +} + +// Tests that the waiting list timers properly reset and reschedule. +func TestTransactionFetcherWaitTimerResets(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{}{ + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}}, + }), + isScheduled{nil, nil, nil}, + doWait{time: txArriveTimeout / 2, step: false}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}}, + }), + isScheduled{nil, nil, nil}, + + doTxNotify{peer: "A", hashes: []common.Hash{{0x02}}}, + isWaiting(map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }), + isScheduled{nil, nil, nil}, + doWait{time: txArriveTimeout / 2, step: true}, + isWaiting(map[string][]common.Hash{ + "A": {{0x02}}, + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}}, + }, + }, + + doWait{time: txArriveTimeout / 2, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}}, + }, + }, + }, + }) +} + +// Tests that if a transaction request is not replied to, it will time +// out and be re-scheduled for someone else. +func TestTransactionFetcherTimeoutRescheduling(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Push an initial announcement through to the scheduled stage + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + isWaiting(map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }), + isScheduled{tracking: nil, fetching: nil}, + + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + // Wait until the delivery times out, everything should be cleaned up + doWait{time: txFetchTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: nil, + fetching: nil, + dangling: map[string][]common.Hash{ + "A": {}, + }, + }, + // Ensure that followup announcements don't get scheduled + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}}, + doWait{time: txArriveTimeout, step: true}, + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[1]}, + }, + fetching: nil, + dangling: map[string][]common.Hash{ + "A": {}, + }, + }, + // If the dangling request arrives a bit later, do not choke + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}, direct: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[1]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[1]}, + }, + }, + }, + }) +} + +// Tests that the fetching timeout timers properly reset and reschedule. +func TestTransactionFetcherTimeoutTimerResets(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{}{ + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "B", hashes: []common.Hash{{0x02}}}, + doWait{time: txArriveTimeout, step: true}, + + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}}, + "B": {{0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}}, + "B": {{0x02}}, + }, + }, + doWait{time: txFetchTimeout - txArriveTimeout, step: true}, + isScheduled{ + tracking: map[string][]common.Hash{ + "B": {{0x02}}, + }, + fetching: map[string][]common.Hash{ + "B": {{0x02}}, + }, + dangling: map[string][]common.Hash{ + "A": {}, + }, + }, + doWait{time: txArriveTimeout, step: true}, + isScheduled{ + tracking: nil, + fetching: nil, + dangling: map[string][]common.Hash{ + "A": {}, + "B": {}, + }, + }, + }, + }) +} + +// 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 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{}{ + // Announce all the transactions, wait a bit and ensure only a small + // percentage gets requested + doTxNotify{peer: "A", hashes: hashes}, + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": hashes, + }, + fetching: map[string][]common.Hash{ + "A": hashes[1643 : 1643+maxTxRetrievals], + }, + }, + }, + }) +} + +// 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) { + // Create a slew of transactions and to announce them + var hashesA []common.Hash + for i := 0; i < maxTxAnnounces+1; i++ { + hashesA = append(hashesA, common.Hash{0x01, byte(i / 256), byte(i % 256)}) + } + var hashesB []common.Hash + for i := 0; i < maxTxAnnounces+1; i++ { + hashesB = append(hashesB, common.Hash{0x02, 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{}{ + // Announce half of the transaction and wait for them to be scheduled + doTxNotify{peer: "A", hashes: hashesA[:maxTxAnnounces/2]}, + doTxNotify{peer: "B", hashes: hashesB[:maxTxAnnounces/2-1]}, + doWait{time: txArriveTimeout, step: true}, + + // Announce the second half and keep them in the wait list + doTxNotify{peer: "A", hashes: hashesA[maxTxAnnounces/2 : maxTxAnnounces]}, + doTxNotify{peer: "B", hashes: hashesB[maxTxAnnounces/2-1 : maxTxAnnounces-1]}, + + // Ensure the hashes are split half and half + isWaiting(map[string][]common.Hash{ + "A": hashesA[maxTxAnnounces/2 : maxTxAnnounces], + "B": hashesB[maxTxAnnounces/2-1 : maxTxAnnounces-1], + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": hashesA[:maxTxAnnounces/2], + "B": hashesB[:maxTxAnnounces/2-1], + }, + fetching: map[string][]common.Hash{ + "A": hashesA[1643 : 1643+maxTxRetrievals], + "B": append(append([]common.Hash{}, hashesB[maxTxAnnounces/2-3:maxTxAnnounces/2-1]...), hashesB[:maxTxRetrievals-2]...), + }, + }, + // Ensure that adding even one more hash results in dropping the hash + doTxNotify{peer: "A", hashes: []common.Hash{hashesA[maxTxAnnounces]}}, + doTxNotify{peer: "B", hashes: hashesB[maxTxAnnounces-1 : maxTxAnnounces+1]}, + + isWaiting(map[string][]common.Hash{ + "A": hashesA[maxTxAnnounces/2 : maxTxAnnounces], + "B": hashesB[maxTxAnnounces/2-1 : maxTxAnnounces], + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": hashesA[:maxTxAnnounces/2], + "B": hashesB[:maxTxAnnounces/2-1], + }, + fetching: map[string][]common.Hash{ + "A": hashesA[1643 : 1643+maxTxRetrievals], + "B": append(append([]common.Hash{}, hashesB[maxTxAnnounces/2-3:maxTxAnnounces/2-1]...), hashesB[:maxTxRetrievals-2]...), + }, + }, + }, + }) +} + +// Tests that underpriced transactions don't get rescheduled after being rejected. +func TestTransactionFetcherUnderpricedDedup(t *testing.T) { + testTransactionFetcherParallel(t, txFetcherTest{ + init: func() *TxFetcher { + return NewTxFetcher( + func(common.Hash) bool { return false }, + func(txs []*types.Transaction) []error { + errs := make([]error, len(txs)) + for i := 0; i < len(errs); i++ { + if i%2 == 0 { + errs[i] = txpool.ErrUnderpriced + } else { + errs[i] = txpool.ErrReplaceUnderpriced + } + } + return errs + }, + func(string, []common.Hash) error { return nil }, + nil, + ) + }, + steps: []interface{}{ + // Deliver a transaction through the fetcher, but reject as underpriced + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}}, + doWait{time: txArriveTimeout, step: true}, + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}, direct: true}, + isScheduled{nil, nil, nil}, + + // Try to announce the transaction again, ensure it's not scheduled back + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1], testTxsHashes[2]}}, // [2] is needed to force a step in the fetcher + isWaiting(map[string][]common.Hash{ + "A": {testTxsHashes[2]}, + }), + isScheduled{nil, nil, nil}, + }, + }) +} + +// Tests that underpriced transactions don't get rescheduled after being rejected, +// but at the same time there's a hard cap on the number of transactions that are +// tracked. +func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) { + // Temporarily disable fetch timeouts as they massively mess up the simulated clock + defer func(timeout time.Duration) { txFetchTimeout = timeout }(txFetchTimeout) + txFetchTimeout = 24 * time.Hour + + // Create a slew of transactions to max out the underpriced set + var txs []*types.Transaction + for i := 0; i < maxTxUnderpricedSetSize+1; i++ { + txs = append(txs, types.NewTransaction(rand.Uint64(), common.Address{byte(rand.Intn(256))}, new(big.Int), 0, new(big.Int), nil)) + } + hashes := make([]common.Hash, len(txs)) + for i, tx := range txs { + hashes[i] = tx.Hash() + } + // Generate a set of steps to announce and deliver the entire set of transactions + var steps []interface{} + for i := 0; i < maxTxUnderpricedSetSize/maxTxRetrievals; i++ { + steps = append(steps, doTxNotify{peer: "A", hashes: hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals]}) + steps = append(steps, isWaiting(map[string][]common.Hash{ + "A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals], + })) + steps = append(steps, doWait{time: txArriveTimeout, step: true}) + steps = append(steps, isScheduled{ + tracking: map[string][]common.Hash{ + "A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals], + }, + fetching: map[string][]common.Hash{ + "A": hashes[i*maxTxRetrievals : (i+1)*maxTxRetrievals], + }, + }) + steps = append(steps, doTxEnqueue{peer: "A", txs: txs[i*maxTxRetrievals : (i+1)*maxTxRetrievals], direct: true}) + steps = append(steps, isWaiting(nil)) + steps = append(steps, isScheduled{nil, nil, nil}) + steps = append(steps, isUnderpriced((i+1)*maxTxRetrievals)) + } + testTransactionFetcher(t, txFetcherTest{ + init: func() *TxFetcher { + return 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 }, + nil, + ) + }, + steps: append(steps, []interface{}{ + // The preparation of the test has already been done in `steps`, add the last check + doTxNotify{peer: "A", hashes: []common.Hash{hashes[maxTxUnderpricedSetSize]}}, + doWait{time: txArriveTimeout, step: true}, + doTxEnqueue{peer: "A", txs: []*types.Transaction{txs[maxTxUnderpricedSetSize]}, direct: true}, + isUnderpriced(maxTxUnderpricedSetSize), + }...), + }) +} + +// Tests that unexpected deliveries don't corrupt the internal state. +func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Deliver something out of the blue + isWaiting(nil), + isScheduled{nil, nil, nil}, + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}, direct: false}, + isWaiting(nil), + isScheduled{nil, nil, nil}, + + // Set up a few hashes into various stages + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[1]}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[2]}}, + + isWaiting(map[string][]common.Hash{ + "A": {testTxsHashes[2]}, + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[0], testTxsHashes[1]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + // Deliver everything and more out of the blue + doTxEnqueue{peer: "B", txs: []*types.Transaction{testTxs[0], testTxs[1], testTxs[2], testTxs[3]}, direct: true}, + isWaiting(nil), + isScheduled{ + tracking: nil, + fetching: nil, + dangling: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + }, + }) +} + +// Tests that dropping a peer cleans out all internal data structures in all the +// live or dangling stages. +func TestTransactionFetcherDrop(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Set up a few hashes into various stages + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "A", hashes: []common.Hash{{0x02}}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "A", hashes: []common.Hash{{0x03}}}, + + isWaiting(map[string][]common.Hash{ + "A": {{0x03}}, + }), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}, {0x02}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}}, + }, + }, + // Drop the peer and ensure everything's cleaned out + doDrop("A"), + isWaiting(nil), + isScheduled{nil, nil, nil}, + + // Push the node into a dangling (timeout) state + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + doWait{time: txFetchTimeout, step: true}, + isWaiting(nil), + isScheduled{ + tracking: nil, + fetching: nil, + dangling: map[string][]common.Hash{ + "A": {}, + }, + }, + // Drop the peer and ensure everything's cleaned out + doDrop("A"), + isWaiting(nil), + isScheduled{nil, nil, nil}, + }, + }) +} + +// Tests that dropping a peer instantly reschedules failed announcements to any +// available peer. +func TestTransactionFetcherDropRescheduling(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Set up a few hashes into various stages + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "B", hashes: []common.Hash{{0x01}}}, + + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "A": {{0x01}}, + "B": {{0x01}}, + }, + fetching: map[string][]common.Hash{ + "A": {{0x01}}, + }, + }, + // Drop the peer and ensure everything's cleaned out + doDrop("A"), + isWaiting(nil), + isScheduled{ + tracking: map[string][]common.Hash{ + "B": {{0x01}}, + }, + fetching: map[string][]common.Hash{ + "B": {{0x01}}, + }, + }, + }, + }) +} + +// 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. +func TestTransactionFetcherFuzzCrash01(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Get a transaction into fetching mode and make it dangling with a broadcast + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}}, + + // Notify the dangling transaction once more and crash via a timeout + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txFetchTimeout, step: true}, + }, + }) +} + +// This test reproduces a crash caught by the fuzzer. The root cause was a +// dangling transaction getting peer-dropped and clashing on re-add with a +// concurrently announced one. +func TestTransactionFetcherFuzzCrash02(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Get a transaction into fetching mode and make it dangling with a broadcast + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}}, + + // Notify the dangling transaction once more, re-fetch, and crash via a drop and timeout + doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + doDrop("A"), + doWait{time: txFetchTimeout, step: true}, + }, + }) +} + +// This test reproduces a crash caught by the fuzzer. The root cause was a +// dangling transaction getting rescheduled via a partial delivery, clashing +// with a concurrent notify. +func TestTransactionFetcherFuzzCrash03(t *testing.T) { + 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 }, + nil, + ) + }, + steps: []interface{}{ + // Get a transaction into fetching mode and make it dangling with a broadcast + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}}, + doWait{time: txFetchTimeout, step: true}, + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}}, + + // Notify the dangling transaction once more, partially deliver, clash&crash with a timeout + doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[1]}, direct: true}, + doWait{time: txFetchTimeout, step: true}, + }, + }) +} + +// This test reproduces a crash caught by the fuzzer. The root cause was a +// dangling transaction getting rescheduled via a disconnect, clashing with +// a concurrent notify. +func TestTransactionFetcherFuzzCrash04(t *testing.T) { + // Create a channel to control when tx requests can fail + proceed := make(chan struct{}) + + 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 { + <-proceed + return errors.New("peer disconnected") + }, + nil, + ) + }, + steps: []interface{}{ + // Get a transaction into fetching mode and make it dangling with a broadcast + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0]}}, + + // Notify the dangling transaction once more, re-fetch, and crash via an in-flight disconnect + doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}}, + doWait{time: txArriveTimeout, step: true}, + doFunc(func() { + proceed <- struct{}{} // Allow peer A to return the failure + }), + doWait{time: 0, step: true}, + doWait{time: txFetchTimeout, step: true}, + }, + }) +} + +func testTransactionFetcherParallel(t *testing.T, tt txFetcherTest) { + t.Parallel() + testTransactionFetcher(t, tt) +} + +func testTransactionFetcher(t *testing.T, tt txFetcherTest) { + // Create a fetcher and hook into it's simulated fields + clock := new(mclock.Simulated) + wait := make(chan struct{}) + + fetcher := tt.init() + fetcher.clock = clock + fetcher.step = wait + fetcher.rand = rand.New(rand.NewSource(0x3a29)) + + fetcher.Start() + defer fetcher.Stop() + + defer func() { // drain the wait chan on exit + for { + select { + case <-wait: + default: + return + } + } + }() + + // 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.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 + select { + case <-wait: + panic("wtf") + case <-time.After(time.Millisecond): + } + + case doTxEnqueue: + if err := fetcher.Enqueue(step.peer, step.txs, step.direct); err != nil { + t.Errorf("step %d: %v", i, err) + } + <-wait // Fetcher needs to process this, wait until it's done + + case doWait: + clock.Run(step.time) + if step.step { + <-wait // Fetcher supposed to do something, wait until it's done + } + + case doDrop: + if err := fetcher.Drop(string(step)); err != nil { + t.Errorf("step %d: %v", i, err) + } + <-wait // Fetcher needs to process this, wait until it's done + + case doFunc: + step() + + 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, announces := range step { + waiting := fetcher.waitslots[peer] + if waiting == nil { + t.Errorf("step %d: peer %s missing from waitslots", i, peer) + continue + } + 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, 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) + } + } + } + for peer := range fetcher.waitslots { + if _, ok := step[peer]; !ok { + t.Errorf("step %d: peer %s extra in waitslots", i, peer) + } + } + // Peer->hash sets correct, check the hash->peer and timeout sets + 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[ann.hash]; !ok { + t.Errorf("step %d: hash %x missing from waittime", i, ann.hash) + } + } + } + for hash, peers := range fetcher.waitlist { + if len(peers) == 0 { + t.Errorf("step %d, hash %x: empty peerset in waitlist", i, hash) + } + for peer := range peers { + 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 _, announces := range step { + if containsHashInAnnounces(announces, hash) { + found = true + break + } + } + if !found { + t.Errorf("step %d,: hash %x extra in waittime", i, hash) + } + } + + case isScheduledWithMeta: + // Check that all scheduled announces are accounted for and no + // extra ones are present. + 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 _, 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, 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) + } + } + } + for peer := range fetcher.announces { + if _, ok := step.tracking[peer]; !ok { + t.Errorf("step %d: peer %s extra in announces", i, peer) + } + } + // Check that all announces required to be fetching are in the + // appropriate sets + for peer, hashes := range step.fetching { + request := fetcher.requests[peer] + if request == nil { + t.Errorf("step %d: peer %s missing from requests", i, peer) + continue + } + for _, hash := range hashes { + if !slices.Contains(request.hashes, hash) { + t.Errorf("step %d, peer %s: hash %x missing from requests", i, peer, hash) + } + } + for _, hash := range request.hashes { + if !slices.Contains(hashes, hash) { + t.Errorf("step %d, peer %s: hash %x extra in requests", i, peer, hash) + } + } + } + for peer := range fetcher.requests { + if _, ok := step.fetching[peer]; !ok { + if _, ok := step.dangling[peer]; !ok { + t.Errorf("step %d: peer %s extra in requests", i, peer) + } + } + } + for peer, hashes := range step.fetching { + for _, hash := range hashes { + if _, ok := fetcher.fetching[hash]; !ok { + t.Errorf("step %d, peer %s: hash %x missing from fetching", i, peer, hash) + } + } + } + for hash := range fetcher.fetching { + var found bool + for _, req := range fetcher.requests { + if slices.Contains(req.hashes, hash) { + found = true + break + } + } + if !found { + t.Errorf("step %d: hash %x extra in fetching", i, hash) + } + } + for _, hashes := range step.fetching { + for _, hash := range hashes { + alternates := fetcher.alternates[hash] + if alternates == nil { + t.Errorf("step %d: hash %x missing from alternates", i, hash) + continue + } + for peer := range alternates { + if _, ok := fetcher.announces[peer]; !ok { + t.Errorf("step %d: peer %s extra in alternates", i, peer) + continue + } + if _, ok := fetcher.announces[peer][hash]; !ok { + t.Errorf("step %d, peer %s: hash %x extra in alternates", i, hash, peer) + continue + } + } + for p := range fetcher.announced[hash] { + if _, ok := alternates[p]; !ok { + t.Errorf("step %d, hash %x: peer %s missing from alternates", i, hash, p) + continue + } + } + } + } + for peer, hashes := range step.dangling { + request := fetcher.requests[peer] + if request == nil { + t.Errorf("step %d: peer %s missing from requests", i, peer) + continue + } + for _, hash := range hashes { + if !slices.Contains(request.hashes, hash) { + t.Errorf("step %d, peer %s: hash %x missing from requests", i, peer, hash) + } + } + for _, hash := range request.hashes { + if !slices.Contains(hashes, hash) { + t.Errorf("step %d, peer %s: hash %x extra in requests", i, peer, hash) + } + } + } + // Check that all transaction announces that are scheduled for + // retrieval but not actively being downloaded are tracked only + // in the stage 2 `announced` map. + var queued []common.Hash + for _, announces := range step.tracking { + for _, ann := range announces { + var found bool + for _, hs := range step.fetching { + if slices.Contains(hs, ann.hash) { + found = true + break + } + } + if !found { + queued = append(queued, ann.hash) + } + } + } + for _, hash := range queued { + if _, ok := fetcher.announced[hash]; !ok { + t.Errorf("step %d: hash %x missing from announced", i, hash) + } + } + for hash := range fetcher.announced { + if !slices.Contains(queued, hash) { + t.Errorf("step %d: hash %x extra in announced", i, hash) + } + } + + case isUnderpriced: + if fetcher.underpriced.Len() != int(step) { + t.Errorf("step %d: underpriced set size mismatch: have %d, want %d", i, fetcher.underpriced.Len(), step) + } + + default: + t.Fatalf("step %d: unknown step type %T", i, step) + } + // After every step, cross validate the internal uniqueness invariants + // between stage one and stage two. + for hash := range fetcher.waittime { + if _, ok := fetcher.announced[hash]; ok { + t.Errorf("step %d: hash %s present in both stage 1 and 2", i, hash) + } + } + } +} + +// 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 +} + +// 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 new file mode 100644 index 0000000..23fb1fa --- /dev/null +++ b/eth/filters/api.go @@ -0,0 +1,611 @@ +// 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 filters + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "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/internal/ethapi" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + errInvalidTopic = errors.New("invalid topic(s)") + errFilterNotFound = errors.New("filter not found") + errInvalidBlockRange = errors.New("invalid block range params") + errPendingLogsUnsupported = errors.New("pending logs are not supported") + errExceedMaxTopics = errors.New("exceed max topics") +) + +// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 +const maxTopics = 4 + +// The maximum number of allowed topics within a topic criteria +const maxSubTopics = 1000 + +// filter is a helper struct that holds meta information over the filter type +// and associated subscription in the event system. +type filter struct { + typ Type + deadline *time.Timer // filter is inactive when deadline triggers + hashes []common.Hash + fullTx bool + txs []*types.Transaction + crit FilterCriteria + logs []*types.Log + s *Subscription // associated subscription in event system +} + +// FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various +// information related to the Ethereum protocol such as blocks, transactions and logs. +type FilterAPI struct { + sys *FilterSystem + events *EventSystem + filtersMu sync.Mutex + filters map[rpc.ID]*filter + timeout time.Duration +} + +// NewFilterAPI returns a new FilterAPI instance. +func NewFilterAPI(system *FilterSystem) *FilterAPI { + api := &FilterAPI{ + sys: system, + events: NewEventSystem(system), + filters: make(map[rpc.ID]*filter), + timeout: system.cfg.Timeout, + } + go api.timeoutLoop(system.cfg.Timeout) + + return api +} + +// timeoutLoop runs at the interval set by 'timeout' and deletes filters +// that have not been recently used. It is started when the API is created. +func (api *FilterAPI) timeoutLoop(timeout time.Duration) { + var toUninstall []*Subscription + ticker := time.NewTicker(timeout) + defer ticker.Stop() + for { + <-ticker.C + api.filtersMu.Lock() + for id, f := range api.filters { + select { + case <-f.deadline.C: + toUninstall = append(toUninstall, f.s) + delete(api.filters, id) + default: + continue + } + } + api.filtersMu.Unlock() + + // Unsubscribes are processed outside the lock to avoid the following scenario: + // event loop attempts broadcasting events to still active filters while + // Unsubscribe is waiting for it to process the uninstall request. + for _, s := range toUninstall { + s.Unsubscribe() + } + toUninstall = nil + } +} + +// NewPendingTransactionFilter creates a filter that fetches pending transactions +// as transactions enter the pending state. +// +// It is part of the filter package because this filter can be used through the +// `eth_getFilterChanges` polling method that is also used for log filters. +func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { + var ( + pendingTxs = make(chan []*types.Transaction) + pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) + ) + + api.filtersMu.Lock() + api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, fullTx: fullTx != nil && *fullTx, deadline: time.NewTimer(api.timeout), txs: make([]*types.Transaction, 0), s: pendingTxSub} + api.filtersMu.Unlock() + + go func() { + for { + select { + case pTx := <-pendingTxs: + api.filtersMu.Lock() + if f, found := api.filters[pendingTxSub.ID]; found { + f.txs = append(f.txs, pTx...) + } + api.filtersMu.Unlock() + case <-pendingTxSub.Err(): + api.filtersMu.Lock() + delete(api.filters, pendingTxSub.ID) + api.filtersMu.Unlock() + return + } + } + }() + + return pendingTxSub.ID +} + +// NewPendingTransactions creates a subscription that is triggered each time a +// transaction enters the transaction pool. If fullTx is true the full tx is +// sent to the client, otherwise the hash is sent. +func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + rpcSub := notifier.CreateSubscription() + + go func() { + txs := make(chan []*types.Transaction, 128) + pendingTxSub := api.events.SubscribePendingTxs(txs) + defer pendingTxSub.Unsubscribe() + + chainConfig := api.sys.backend.ChainConfig() + + for { + select { + case txs := <-txs: + // To keep the original behaviour, send a single tx hash in one notification. + // TODO(rjl493456442) Send a batch of tx hashes in one notification + latest := api.sys.backend.CurrentHeader() + for _, tx := range txs { + if fullTx != nil && *fullTx { + rpcTx := ethapi.NewRPCPendingTransaction(tx, latest, chainConfig) + notifier.Notify(rpcSub.ID, rpcTx) + } else { + notifier.Notify(rpcSub.ID, tx.Hash()) + } + } + case <-rpcSub.Err(): + return + } + } + }() + + return rpcSub, nil +} + +// NewBlockFilter creates a filter that fetches blocks that are imported into the chain. +// It is part of the filter package since polling goes with eth_getFilterChanges. +func (api *FilterAPI) NewBlockFilter() rpc.ID { + var ( + headers = make(chan *types.Header) + headerSub = api.events.SubscribeNewHeads(headers) + ) + + api.filtersMu.Lock() + api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub} + api.filtersMu.Unlock() + + go func() { + for { + select { + case h := <-headers: + api.filtersMu.Lock() + if f, found := api.filters[headerSub.ID]; found { + f.hashes = append(f.hashes, h.Hash()) + } + api.filtersMu.Unlock() + case <-headerSub.Err(): + api.filtersMu.Lock() + delete(api.filters, headerSub.ID) + api.filtersMu.Unlock() + return + } + } + }() + + return headerSub.ID +} + +// NewHeads send a notification each time a new (header) block is appended to the chain. +func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + rpcSub := notifier.CreateSubscription() + + 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(): + return + } + } + }() + + return rpcSub, nil +} + +// Logs creates a subscription that fires for all new log that match the given filter criteria. +func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + var ( + rpcSub = notifier.CreateSubscription() + matchedLogs = make(chan []*types.Log) + ) + + logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), matchedLogs) + if err != nil { + return nil, err + } + + go func() { + defer logsSub.Unsubscribe() + for { + select { + case logs := <-matchedLogs: + for _, log := range logs { + log := log + notifier.Notify(rpcSub.ID, &log) + } + case <-rpcSub.Err(): // client send an unsubscribe request + return + } + } + }() + + return rpcSub, nil +} + +// FilterCriteria represents a request to create a new filter. +// Same as ethereum.FilterQuery but with UnmarshalJSON() method. +type FilterCriteria ethereum.FilterQuery + +// NewFilter creates a new filter and returns the filter id. It can be +// used to retrieve logs when the state changes. This method cannot be +// used to fetch logs that are already stored in the state. +// +// Default criteria for the from and to block are "latest". +// Using "latest" as block number will return logs for mined blocks. +// Using "pending" as block number returns logs for not yet mined (pending) blocks. +// In case logs are removed (chain reorg) previously returned logs are returned +// again but with the removed property set to true. +// +// In case "fromBlock" > "toBlock" an error is returned. +func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { + logs := make(chan []*types.Log) + logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) + if err != nil { + return "", err + } + + api.filtersMu.Lock() + api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub} + api.filtersMu.Unlock() + + go func() { + for { + select { + case l := <-logs: + api.filtersMu.Lock() + if f, found := api.filters[logsSub.ID]; found { + f.logs = append(f.logs, l...) + } + api.filtersMu.Unlock() + case <-logsSub.Err(): + api.filtersMu.Lock() + delete(api.filters, logsSub.ID) + api.filtersMu.Unlock() + return + } + } + }() + + return logsSub.ID, nil +} + +// 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 + filter = api.sys.NewBlockFilter(*crit.BlockHash, crit.Addresses, crit.Topics) + } else { + // Convert the RPC block numbers into internal representations + begin := rpc.LatestBlockNumber.Int64() + if crit.FromBlock != nil { + begin = crit.FromBlock.Int64() + } + end := rpc.LatestBlockNumber.Int64() + 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) + } + // Run the filter and return all the logs + logs, err := filter.Logs(ctx) + if err != nil { + return nil, err + } + return returnLogs(logs), err +} + +// UninstallFilter removes the filter with the given filter id. +func (api *FilterAPI) UninstallFilter(id rpc.ID) bool { + api.filtersMu.Lock() + f, found := api.filters[id] + if found { + delete(api.filters, id) + } + api.filtersMu.Unlock() + if found { + f.s.Unsubscribe() + } + + return found +} + +// GetFilterLogs returns the logs for the filter with the given id. +// If the filter could not be found an empty array of logs is returned. +func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { + api.filtersMu.Lock() + f, found := api.filters[id] + api.filtersMu.Unlock() + + if !found || f.typ != LogsSubscription { + return nil, errFilterNotFound + } + + var filter *Filter + if f.crit.BlockHash != nil { + // Block filter requested, construct a single-shot filter + filter = api.sys.NewBlockFilter(*f.crit.BlockHash, f.crit.Addresses, f.crit.Topics) + } else { + // Convert the RPC block numbers into internal representations + begin := rpc.LatestBlockNumber.Int64() + if f.crit.FromBlock != nil { + begin = f.crit.FromBlock.Int64() + } + end := rpc.LatestBlockNumber.Int64() + if f.crit.ToBlock != nil { + end = f.crit.ToBlock.Int64() + } + // Construct the range filter + filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics) + } + // Run the filter and return all the logs + logs, err := filter.Logs(ctx) + if err != nil { + return nil, err + } + return returnLogs(logs), nil +} + +// GetFilterChanges returns the logs for the filter with the given id since +// last time it was called. This can be used for polling. +// +// For pending transaction and block filters the result is []common.Hash. +// (pending)Log filters return []Log. +func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { + api.filtersMu.Lock() + defer api.filtersMu.Unlock() + + chainConfig := api.sys.backend.ChainConfig() + latest := api.sys.backend.CurrentHeader() + + if f, found := api.filters[id]; found { + if !f.deadline.Stop() { + // timer expired but filter is not yet removed in timeout loop + // receive timer value and reset timer + <-f.deadline.C + } + f.deadline.Reset(api.timeout) + + switch f.typ { + case BlocksSubscription: + hashes := f.hashes + f.hashes = nil + return returnHashes(hashes), nil + case PendingTransactionsSubscription: + if f.fullTx { + txs := make([]*ethapi.RPCTransaction, 0, len(f.txs)) + for _, tx := range f.txs { + txs = append(txs, ethapi.NewRPCPendingTransaction(tx, latest, chainConfig)) + } + f.txs = nil + return txs, nil + } else { + hashes := make([]common.Hash, 0, len(f.txs)) + for _, tx := range f.txs { + hashes = append(hashes, tx.Hash()) + } + f.txs = nil + return hashes, nil + } + case LogsSubscription: + logs := f.logs + f.logs = nil + return returnLogs(logs), nil + } + } + + return []interface{}{}, errFilterNotFound +} + +// returnHashes is a helper that will return an empty hash array case the given hash array is nil, +// otherwise the given hashes array is returned. +func returnHashes(hashes []common.Hash) []common.Hash { + if hashes == nil { + return []common.Hash{} + } + return hashes +} + +// returnLogs is a helper that will return an empty log array in case the given logs array is nil, +// otherwise the given logs array is returned. +func returnLogs(logs []*types.Log) []*types.Log { + if logs == nil { + return []*types.Log{} + } + return logs +} + +// UnmarshalJSON sets *args fields with given data. +func (args *FilterCriteria) UnmarshalJSON(data []byte) error { + type input struct { + BlockHash *common.Hash `json:"blockHash"` + FromBlock *rpc.BlockNumber `json:"fromBlock"` + ToBlock *rpc.BlockNumber `json:"toBlock"` + Addresses interface{} `json:"address"` + Topics []interface{} `json:"topics"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.BlockHash != nil { + if raw.FromBlock != nil || raw.ToBlock != nil { + // BlockHash is mutually exclusive with FromBlock/ToBlock criteria + return errors.New("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other") + } + args.BlockHash = raw.BlockHash + } else { + if raw.FromBlock != nil { + args.FromBlock = big.NewInt(raw.FromBlock.Int64()) + } + + if raw.ToBlock != nil { + args.ToBlock = big.NewInt(raw.ToBlock.Int64()) + } + } + + args.Addresses = []common.Address{} + + if raw.Addresses != nil { + // raw.Address can contain a single address or an array of addresses + switch rawAddr := raw.Addresses.(type) { + case []interface{}: + for i, addr := range rawAddr { + if strAddr, ok := addr.(string); ok { + addr, err := decodeAddress(strAddr) + if err != nil { + return fmt.Errorf("invalid address at index %d: %v", i, err) + } + args.Addresses = append(args.Addresses, addr) + } else { + return fmt.Errorf("non-string address at index %d", i) + } + } + case string: + addr, err := decodeAddress(rawAddr) + if err != nil { + return fmt.Errorf("invalid address: %v", err) + } + args.Addresses = []common.Address{addr} + default: + return errors.New("invalid addresses in query") + } + } + if len(raw.Topics) > maxTopics { + return errExceedMaxTopics + } + + // topics is an array consisting of strings and/or arrays of strings. + // JSON null values are converted to common.Hash{} and ignored by the filter manager. + if len(raw.Topics) > 0 { + args.Topics = make([][]common.Hash, len(raw.Topics)) + for i, t := range raw.Topics { + switch topic := t.(type) { + case nil: + // ignore topic when matching logs + + case string: + // match specific topic + top, err := decodeTopic(topic) + if err != nil { + return err + } + args.Topics[i] = []common.Hash{top} + + case []interface{}: + // or case e.g. [null, "topic0", "topic1"] + if len(topic) > maxSubTopics { + return errExceedMaxTopics + } + for _, rawTopic := range topic { + if rawTopic == nil { + // null component, match all + args.Topics[i] = nil + break + } + if topic, ok := rawTopic.(string); ok { + parsed, err := decodeTopic(topic) + if err != nil { + return err + } + args.Topics[i] = append(args.Topics[i], parsed) + } else { + return errInvalidTopic + } + } + default: + return errInvalidTopic + } + } + } + + return nil +} + +func decodeAddress(s string) (common.Address, error) { + b, err := hexutil.Decode(s) + if err == nil && len(b) != common.AddressLength { + err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength) + } + return common.BytesToAddress(b), err +} + +func decodeTopic(s string) (common.Hash, error) { + b, err := hexutil.Decode(s) + if err == nil && len(b) != common.HashLength { + err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength) + } + return common.BytesToHash(b), err +} diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go new file mode 100644 index 0000000..822bc82 --- /dev/null +++ b/eth/filters/api_test.go @@ -0,0 +1,185 @@ +// 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 filters + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" +) + +func TestUnmarshalJSONNewFilterArgs(t *testing.T) { + var ( + fromBlock rpc.BlockNumber = 0x123435 + toBlock rpc.BlockNumber = 0xabcdef + address0 = common.HexToAddress("70c87d191324e6712a591f304b4eedef6ad9bb9d") + address1 = common.HexToAddress("9b2055d370f73ec7d8a03e965129118dc8f5bf83") + topic0 = common.HexToHash("3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1ca") + topic1 = common.HexToHash("9084a792d2f8b16a62b882fd56f7860c07bf5fa91dd8a2ae7e809e5180fef0b3") + topic2 = common.HexToHash("6ccae1c4af4152f460ff510e573399795dfab5dcf1fa60d1f33ac8fdc1e480ce") + ) + + // default values + var test0 FilterCriteria + if err := json.Unmarshal([]byte("{}"), &test0); err != nil { + t.Fatal(err) + } + if test0.FromBlock != nil { + t.Fatalf("expected nil, got %d", test0.FromBlock) + } + if test0.ToBlock != nil { + t.Fatalf("expected nil, got %d", test0.ToBlock) + } + if len(test0.Addresses) != 0 { + t.Fatalf("expected 0 addresses, got %d", len(test0.Addresses)) + } + if len(test0.Topics) != 0 { + t.Fatalf("expected 0 topics, got %d topics", len(test0.Topics)) + } + + // from, to block number + var test1 FilterCriteria + vector := fmt.Sprintf(`{"fromBlock":"%v","toBlock":"%v"}`, fromBlock, toBlock) + if err := json.Unmarshal([]byte(vector), &test1); err != nil { + t.Fatal(err) + } + if test1.FromBlock.Int64() != fromBlock.Int64() { + t.Fatalf("expected FromBlock %d, got %d", fromBlock, test1.FromBlock) + } + if test1.ToBlock.Int64() != toBlock.Int64() { + t.Fatalf("expected ToBlock %d, got %d", toBlock, test1.ToBlock) + } + + // single address + var test2 FilterCriteria + vector = fmt.Sprintf(`{"address": "%s"}`, address0.Hex()) + if err := json.Unmarshal([]byte(vector), &test2); err != nil { + t.Fatal(err) + } + if len(test2.Addresses) != 1 { + t.Fatalf("expected 1 address, got %d address(es)", len(test2.Addresses)) + } + if test2.Addresses[0] != address0 { + t.Fatalf("expected address %x, got %x", address0, test2.Addresses[0]) + } + + // multiple address + var test3 FilterCriteria + vector = fmt.Sprintf(`{"address": ["%s", "%s"]}`, address0.Hex(), address1.Hex()) + if err := json.Unmarshal([]byte(vector), &test3); err != nil { + t.Fatal(err) + } + if len(test3.Addresses) != 2 { + t.Fatalf("expected 2 addresses, got %d address(es)", len(test3.Addresses)) + } + if test3.Addresses[0] != address0 { + t.Fatalf("expected address %x, got %x", address0, test3.Addresses[0]) + } + if test3.Addresses[1] != address1 { + t.Fatalf("expected address %x, got %x", address1, test3.Addresses[1]) + } + + // single topic + var test4 FilterCriteria + vector = fmt.Sprintf(`{"topics": ["%s"]}`, topic0.Hex()) + if err := json.Unmarshal([]byte(vector), &test4); err != nil { + t.Fatal(err) + } + if len(test4.Topics) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test4.Topics)) + } + if len(test4.Topics[0]) != 1 { + t.Fatalf("expected len(topics[0]) to be 1, got %d", len(test4.Topics[0])) + } + if test4.Topics[0][0] != topic0 { + t.Fatalf("got %x, expected %x", test4.Topics[0][0], topic0) + } + + // test multiple "AND" topics + var test5 FilterCriteria + vector = fmt.Sprintf(`{"topics": ["%s", "%s"]}`, topic0.Hex(), topic1.Hex()) + if err := json.Unmarshal([]byte(vector), &test5); err != nil { + t.Fatal(err) + } + if len(test5.Topics) != 2 { + t.Fatalf("expected 2 topics, got %d", len(test5.Topics)) + } + if len(test5.Topics[0]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test5.Topics[0])) + } + if test5.Topics[0][0] != topic0 { + t.Fatalf("got %x, expected %x", test5.Topics[0][0], topic0) + } + if len(test5.Topics[1]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test5.Topics[1])) + } + if test5.Topics[1][0] != topic1 { + t.Fatalf("got %x, expected %x", test5.Topics[1][0], topic1) + } + + // test optional topic + var test6 FilterCriteria + vector = fmt.Sprintf(`{"topics": ["%s", null, "%s"]}`, topic0.Hex(), topic2.Hex()) + if err := json.Unmarshal([]byte(vector), &test6); err != nil { + t.Fatal(err) + } + if len(test6.Topics) != 3 { + t.Fatalf("expected 3 topics, got %d", len(test6.Topics)) + } + if len(test6.Topics[0]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test6.Topics[0])) + } + if test6.Topics[0][0] != topic0 { + t.Fatalf("got %x, expected %x", test6.Topics[0][0], topic0) + } + if len(test6.Topics[1]) != 0 { + t.Fatalf("expected 0 topic, got %d", len(test6.Topics[1])) + } + if len(test6.Topics[2]) != 1 { + t.Fatalf("expected 1 topic, got %d", len(test6.Topics[2])) + } + if test6.Topics[2][0] != topic2 { + t.Fatalf("got %x, expected %x", test6.Topics[2][0], topic2) + } + + // test OR topics + var test7 FilterCriteria + vector = fmt.Sprintf(`{"topics": [["%s", "%s"], null, ["%s", null]]}`, topic0.Hex(), topic1.Hex(), topic2.Hex()) + if err := json.Unmarshal([]byte(vector), &test7); err != nil { + t.Fatal(err) + } + if len(test7.Topics) != 3 { + t.Fatalf("expected 3 topics, got %d topics", len(test7.Topics)) + } + if len(test7.Topics[0]) != 2 { + t.Fatalf("expected 2 topics, got %d topics", len(test7.Topics[0])) + } + if test7.Topics[0][0] != topic0 || test7.Topics[0][1] != topic1 { + t.Fatalf("invalid topics expected [%x,%x], got [%x,%x]", + topic0, topic1, test7.Topics[0][0], test7.Topics[0][1], + ) + } + if len(test7.Topics[1]) != 0 { + t.Fatalf("expected 0 topic, got %d topics", len(test7.Topics[1])) + } + if len(test7.Topics[2]) != 0 { + t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2])) + } +} diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go new file mode 100644 index 0000000..73b96b7 --- /dev/null +++ b/eth/filters/bench_test.go @@ -0,0 +1,189 @@ +// 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 filters + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/bitutil" + "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/ethdb" + "github.com/ethereum/go-ethereum/node" +) + +func BenchmarkBloomBits512(b *testing.B) { + benchmarkBloomBits(b, 512) +} + +func BenchmarkBloomBits1k(b *testing.B) { + benchmarkBloomBits(b, 1024) +} + +func BenchmarkBloomBits2k(b *testing.B) { + benchmarkBloomBits(b, 2048) +} + +func BenchmarkBloomBits4k(b *testing.B) { + benchmarkBloomBits(b, 4096) +} + +func BenchmarkBloomBits8k(b *testing.B) { + benchmarkBloomBits(b, 8192) +} + +func BenchmarkBloomBits16k(b *testing.B) { + benchmarkBloomBits(b, 16384) +} + +func BenchmarkBloomBits32k(b *testing.B) { + benchmarkBloomBits(b, 32768) +} + +const benchFilterCnt = 2000 + +func benchmarkBloomBits(b *testing.B, sectionSize uint64) { + b.Skip("test disabled: this tests presume (and modify) an existing datadir.") + benchDataDir := node.DefaultDataDir() + "/geth/chaindata" + b.Log("Running bloombits benchmark section size:", sectionSize) + + db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) + if err != nil { + b.Fatalf("error opening database at %v: %v", benchDataDir, err) + } + head := rawdb.ReadHeadBlockHash(db) + if head == (common.Hash{}) { + b.Fatalf("chain data not found at %v", benchDataDir) + } + + clearBloomBits(db) + b.Log("Generating bloombits data...") + headNum := rawdb.ReadHeaderNumber(db, head) + if headNum == nil || *headNum < sectionSize+512 { + b.Fatalf("not enough blocks for running a benchmark") + } + + start := time.Now() + cnt := (*headNum - 512) / sectionSize + var dataSize, compSize uint64 + for sectionIdx := uint64(0); sectionIdx < cnt; sectionIdx++ { + bc, err := bloombits.NewGenerator(uint(sectionSize)) + if err != nil { + b.Fatalf("failed to create generator: %v", err) + } + var header *types.Header + for i := sectionIdx * sectionSize; i < (sectionIdx+1)*sectionSize; i++ { + hash := rawdb.ReadCanonicalHash(db, i) + if header = rawdb.ReadHeader(db, hash, i); header == nil { + b.Fatalf("Error creating bloomBits data") + return + } + bc.AddBloom(uint(i-sectionIdx*sectionSize), header.Bloom) + } + sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*sectionSize-1) + for i := 0; i < types.BloomBitLength; i++ { + data, err := bc.Bitset(uint(i)) + if err != nil { + b.Fatalf("failed to retrieve bitset: %v", err) + } + comp := bitutil.CompressBytes(data) + dataSize += uint64(len(data)) + compSize += uint64(len(comp)) + rawdb.WriteBloomBits(db, uint(i), sectionIdx, sectionHead, comp) + } + //if sectionIdx%50 == 0 { + // b.Log(" section", sectionIdx, "/", cnt) + //} + } + + d := time.Since(start) + b.Log("Finished generating bloombits data") + b.Log(" ", d, "total ", d/time.Duration(cnt*sectionSize), "per block") + b.Log(" data size:", dataSize, " compressed size:", compSize, " compression ratio:", float64(compSize)/float64(dataSize)) + + b.Log("Running filter benchmarks...") + start = time.Now() + + var ( + backend *testBackend + sys *FilterSystem + ) + for i := 0; i < benchFilterCnt; i++ { + if i%20 == 0 { + db.Close() + db, _ = rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) + backend = &testBackend{db: db, sections: cnt} + sys = NewFilterSystem(backend, Config{}) + } + var addr common.Address + addr[0] = byte(i) + addr[1] = byte(i / 256) + filter := sys.NewRangeFilter(0, int64(cnt*sectionSize-1), []common.Address{addr}, nil) + if _, err := filter.Logs(context.Background()); err != nil { + b.Error("filter.Logs error:", err) + } + } + + d = time.Since(start) + b.Log("Finished running filter benchmarks") + b.Log(" ", d, "total ", d/time.Duration(benchFilterCnt), "per address", d*time.Duration(1000000)/time.Duration(benchFilterCnt*cnt*sectionSize), "per million blocks") + db.Close() +} + +//nolint:unused +func clearBloomBits(db ethdb.Database) { + var bloomBitsPrefix = []byte("bloomBits-") + fmt.Println("Clearing bloombits data...") + it := db.NewIterator(bloomBitsPrefix, nil) + for it.Next() { + db.Delete(it.Key()) + } + it.Release() +} + +func BenchmarkNoBloomBits(b *testing.B) { + b.Skip("test disabled: this tests presume (and modify) an existing datadir.") + benchDataDir := node.DefaultDataDir() + "/geth/chaindata" + b.Log("Running benchmark without bloombits") + db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) + if err != nil { + b.Fatalf("error opening database at %v: %v", benchDataDir, err) + } + head := rawdb.ReadHeadBlockHash(db) + if head == (common.Hash{}) { + b.Fatalf("chain data not found at %v", benchDataDir) + } + headNum := rawdb.ReadHeaderNumber(db, head) + + clearBloomBits(db) + + _, sys := newTestFilterSystem(b, db, Config{}) + + b.Log("Running filter benchmarks...") + start := time.Now() + filter := sys.NewRangeFilter(0, int64(*headNum), []common.Address{{}}, nil) + filter.Logs(context.Background()) + d := time.Since(start) + b.Log("Finished running filter benchmarks") + b.Log(" ", d, "total ", d*time.Duration(1000000)/time.Duration(*headNum+1), "per million blocks") + db.Close() +} diff --git a/eth/filters/filter.go b/eth/filters/filter.go new file mode 100644 index 0000000..09ccb93 --- /dev/null +++ b/eth/filters/filter.go @@ -0,0 +1,378 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package filters + +import ( + "context" + "errors" + "math/big" + "slices" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" +) + +// Filter can be used to retrieve and filter logs. +type Filter struct { + sys *FilterSystem + + addresses []common.Address + topics [][]common.Hash + + block *common.Hash // Block hash if filtering a single block + begin, end int64 // Range interval if filtering multiple blocks + + matcher *bloombits.Matcher +} + +// NewRangeFilter creates a new filter which uses a bloom filter on blocks to +// figure out whether a particular block is interesting or not. +func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { + // Flatten the address and topic filter clauses into a single bloombits filter + // system. Since the bloombits are not positional, nil topics are permitted, + // which get flattened into a nil byte slice. + var filters [][][]byte + if len(addresses) > 0 { + filter := make([][]byte, len(addresses)) + for i, address := range addresses { + filter[i] = address.Bytes() + } + filters = append(filters, filter) + } + for _, topicList := range topics { + filter := make([][]byte, len(topicList)) + for i, topic := range topicList { + filter[i] = topic.Bytes() + } + filters = append(filters, filter) + } + size, _ := sys.backend.BloomStatus() + + // Create a generic filter and convert it into a range filter + filter := newFilter(sys, addresses, topics) + + filter.matcher = bloombits.NewMatcher(size, filters) + filter.begin = begin + filter.end = end + + return filter +} + +// NewBlockFilter creates a new filter which directly inspects the contents of +// a block to figure out whether it is interesting or not. +func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { + // Create a generic filter and convert it into a block filter + filter := newFilter(sys, addresses, topics) + filter.block = &block + return filter +} + +// newFilter creates a generic filter that can either filter based on a block hash, +// or based on range queries. The search criteria needs to be explicitly set. +func newFilter(sys *FilterSystem, addresses []common.Address, topics [][]common.Hash) *Filter { + return &Filter{ + sys: sys, + addresses: addresses, + topics: topics, + } +} + +// Logs searches the blockchain for matching log entries, returning all from the +// first block that contains matches, updating the start of the filter accordingly. +func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { + // If we're doing singleton block filtering, execute and return + if f.block != nil { + header, err := f.sys.backend.HeaderByHash(ctx, *f.block) + if err != nil { + return nil, err + } + if header == nil { + return nil, errors.New("unknown block") + } + return f.blockLogs(ctx, header) + } + + // Disallow pending logs. + if f.begin == rpc.PendingBlockNumber.Int64() || f.end == rpc.PendingBlockNumber.Int64() { + return nil, errPendingLogsUnsupported + } + + resolveSpecial := func(number int64) (int64, error) { + var hdr *types.Header + switch number { + case rpc.LatestBlockNumber.Int64(), rpc.PendingBlockNumber.Int64(): + // we should return head here since we've already captured + // that we need to get the pending logs in the pending boolean above + hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if hdr == nil { + return 0, errors.New("latest header not found") + } + case rpc.FinalizedBlockNumber.Int64(): + hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) + if hdr == nil { + return 0, errors.New("finalized header not found") + } + case rpc.SafeBlockNumber.Int64(): + hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber) + if hdr == nil { + return 0, errors.New("safe header not found") + } + default: + return number, nil + } + return hdr.Number.Int64(), nil + } + + var err error + // range query need to resolve the special begin/end block number + if f.begin, err = resolveSpecial(f.begin); err != nil { + return nil, err + } + if f.end, err = resolveSpecial(f.end); err != nil { + return nil, err + } + + logChan, errChan := f.rangeLogsAsync(ctx) + var logs []*types.Log + for { + select { + case log := <-logChan: + logs = append(logs, log) + case err := <-errChan: + return logs, err + } + } +} + +// rangeLogsAsync retrieves block-range logs that match the filter criteria asynchronously, +// it creates and returns two channels: one for delivering log data, and one for reporting errors. +func (f *Filter) rangeLogsAsync(ctx context.Context) (chan *types.Log, chan error) { + var ( + logChan = make(chan *types.Log) + errChan = make(chan error) + ) + + go func() { + defer func() { + close(errChan) + close(logChan) + }() + + // Gather all indexed logs, and finish with non indexed ones + var ( + end = uint64(f.end) + size, sections = f.sys.backend.BloomStatus() + err error + ) + if indexed := sections * size; indexed > uint64(f.begin) { + if indexed > end { + indexed = end + 1 + } + if err = f.indexedLogs(ctx, indexed-1, logChan); err != nil { + errChan <- err + return + } + } + + if err := f.unindexedLogs(ctx, end, logChan); err != nil { + errChan <- err + return + } + + errChan <- nil + }() + + return logChan, errChan +} + +// indexedLogs returns the logs matching the filter criteria based on the bloom +// bits indexed available locally or via the network. +func (f *Filter) indexedLogs(ctx context.Context, end uint64, logChan chan *types.Log) error { + // Create a matcher session and request servicing from the backend + matches := make(chan uint64, 64) + + session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches) + if err != nil { + return err + } + defer session.Close() + + f.sys.backend.ServiceFilter(ctx, session) + + for { + select { + case number, ok := <-matches: + // Abort if all matches have been fulfilled + if !ok { + err := session.Error() + if err == nil { + f.begin = int64(end) + 1 + } + return err + } + f.begin = int64(number) + 1 + + // Retrieve the suggested block and pull any truly matching logs + header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) + if header == nil || err != nil { + return err + } + found, err := f.checkMatches(ctx, header) + if err != nil { + return err + } + for _, log := range found { + logChan <- log + } + + case <-ctx.Done(): + return ctx.Err() + } + } +} + +// unindexedLogs returns the logs matching the filter criteria based on raw block +// iteration and bloom matching. +func (f *Filter) unindexedLogs(ctx context.Context, end uint64, logChan chan *types.Log) error { + for ; f.begin <= int64(end); f.begin++ { + header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) + if header == nil || err != nil { + return err + } + found, err := f.blockLogs(ctx, header) + if err != nil { + return err + } + for _, log := range found { + select { + case logChan <- log: + case <-ctx.Done(): + return ctx.Err() + } + } + } + return nil +} + +// blockLogs returns the logs matching the filter criteria within a single block. +func (f *Filter) blockLogs(ctx context.Context, header *types.Header) ([]*types.Log, error) { + if bloomFilter(header.Bloom, f.addresses, f.topics) { + return f.checkMatches(ctx, header) + } + return nil, nil +} + +// checkMatches checks if the receipts belonging to the given header contain any log events that +// match the filter criteria. This function is called when the bloom filter signals a potential match. +// skipFilter signals all logs of the given block are requested. +func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) { + hash := header.Hash() + // Logs in cache are partially filled with context data + // such as tx index, block hash, etc. + // Notably tx hash is NOT filled in because it needs + // access to block body data. + cached, err := f.sys.cachedLogElem(ctx, hash, header.Number.Uint64()) + if err != nil { + return nil, err + } + logs := filterLogs(cached.logs, nil, nil, f.addresses, f.topics) + if len(logs) == 0 { + return nil, nil + } + // Most backends will deliver un-derived logs, but check nevertheless. + if len(logs) > 0 && logs[0].TxHash != (common.Hash{}) { + return logs, nil + } + + body, err := f.sys.cachedGetBody(ctx, cached, hash, header.Number.Uint64()) + if err != nil { + return nil, err + } + for i, log := range logs { + // Copy log not to modify cache elements + logcopy := *log + logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash() + logs[i] = &logcopy + } + return logs, nil +} + +// 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 check = func(log *types.Log) bool { + if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { + return false + } + if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { + return false + } + if len(addresses) > 0 && !slices.Contains(addresses, log.Address) { + return false + } + // If the to filtered topics is greater than the amount of topics in logs, skip. + if len(topics) > len(log.Topics) { + return false + } + for i, sub := range topics { + if len(sub) == 0 { + continue // empty rule set == wildcard + } + if !slices.Contains(sub, log.Topics[i]) { + return false + } + } + return true + } + var ret []*types.Log + for _, log := range logs { + if check(log) { + ret = append(ret, log) + } + } + return ret +} + +func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool { + if len(addresses) > 0 { + var included bool + for _, addr := range addresses { + if types.BloomLookup(bloom, addr) { + included = true + break + } + } + if !included { + return false + } + } + + for _, sub := range topics { + included := len(sub) == 0 // empty rule set == wildcard + for _, topic := range sub { + if types.BloomLookup(bloom, topic) { + included = true + break + } + } + if !included { + return false + } + } + return true +} diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go new file mode 100644 index 0000000..a3a2787 --- /dev/null +++ b/eth/filters/filter_system.go @@ -0,0 +1,443 @@ +// 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 filters implements an ethereum filtering system for block, +// transactions and log events. +package filters + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/bloombits" + "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/rpc" +) + +// Config represents the configuration of the filter system. +type Config struct { + LogCacheSize int // maximum number of cached blocks (default: 32) + Timeout time.Duration // how long filters stay active (default: 5min) +} + +func (cfg Config) withDefaults() Config { + if cfg.Timeout == 0 { + cfg.Timeout = 5 * time.Minute + } + if cfg.LogCacheSize == 0 { + cfg.LogCacheSize = 32 + } + return cfg +} + +type Backend interface { + ChainDb() ethdb.Database + HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) + HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) + GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) + GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) + GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) + + CurrentHeader() *types.Header + ChainConfig() *params.ChainConfig + SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription + SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription + SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription + SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription + + BloomStatus() (uint64, uint64) + ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) +} + +// FilterSystem holds resources shared by all filters. +type FilterSystem struct { + backend Backend + logsCache *lru.Cache[common.Hash, *logCacheElem] + cfg *Config +} + +// NewFilterSystem creates a filter system. +func NewFilterSystem(backend Backend, config Config) *FilterSystem { + config = config.withDefaults() + return &FilterSystem{ + backend: backend, + logsCache: lru.NewCache[common.Hash, *logCacheElem](config.LogCacheSize), + cfg: &config, + } +} + +type logCacheElem struct { + logs []*types.Log + body atomic.Pointer[types.Body] +} + +// cachedLogElem loads block logs from the backend and caches the result. +func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Hash, number uint64) (*logCacheElem, error) { + cached, ok := sys.logsCache.Get(blockHash) + if ok { + return cached, nil + } + + logs, err := sys.backend.GetLogs(ctx, blockHash, number) + if err != nil { + return nil, err + } + if logs == nil { + return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString()) + } + // Database logs are un-derived. + // Fill in whatever we can (txHash is inaccessible at this point). + flattened := make([]*types.Log, 0) + var logIdx uint + for i, txLogs := range logs { + for _, log := range txLogs { + log.BlockHash = blockHash + log.BlockNumber = number + log.TxIndex = uint(i) + log.Index = logIdx + logIdx++ + flattened = append(flattened, log) + } + } + elem := &logCacheElem{logs: flattened} + sys.logsCache.Add(blockHash, elem) + return elem, nil +} + +func (sys *FilterSystem) cachedGetBody(ctx context.Context, elem *logCacheElem, hash common.Hash, number uint64) (*types.Body, error) { + if body := elem.body.Load(); body != nil { + return body, nil + } + body, err := sys.backend.GetBody(ctx, hash, rpc.BlockNumber(number)) + if err != nil { + return nil, err + } + elem.body.Store(body) + return body, nil +} + +// Type determines the kind of filter and is used to put the filter in to +// the correct bucket when added. +type Type byte + +const ( + // UnknownSubscription indicates an unknown subscription type + UnknownSubscription Type = iota + // LogsSubscription queries for new or removed (chain reorg) logs + LogsSubscription + // PendingTransactionsSubscription queries for pending transactions entering + // the pending state + PendingTransactionsSubscription + // BlocksSubscription queries hashes for blocks that are imported + BlocksSubscription + // LastIndexSubscription keeps track of the last index + LastIndexSubscription +) + +const ( + // txChanSize is the size of channel listening to NewTxsEvent. + // The number is referenced from the size of tx pool. + txChanSize = 4096 + // rmLogsChanSize is the size of channel listening to RemovedLogsEvent. + rmLogsChanSize = 10 + // logsChanSize is the size of channel listening to LogsEvent. + logsChanSize = 10 + // chainEvChanSize is the size of channel listening to ChainEvent. + chainEvChanSize = 10 +) + +type subscription struct { + id rpc.ID + typ Type + created time.Time + logsCrit ethereum.FilterQuery + logs chan []*types.Log + txs chan []*types.Transaction + headers chan *types.Header + installed chan struct{} // closed when the filter is installed + err chan error // closed when the filter is uninstalled +} + +// EventSystem creates subscriptions, processes events and broadcasts them to the +// subscription which match the subscription criteria. +type EventSystem struct { + backend Backend + sys *FilterSystem + + // Subscriptions + txsSub event.Subscription // Subscription for new transaction event + logsSub event.Subscription // Subscription for new log event + rmLogsSub event.Subscription // Subscription for removed log event + chainSub event.Subscription // Subscription for new chain event + + // Channels + install chan *subscription // install filter for event notification + uninstall chan *subscription // remove filter for event notification + txsCh chan core.NewTxsEvent // Channel to receive new transactions event + logsCh chan []*types.Log // Channel to receive new log event + rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event + chainCh chan core.ChainEvent // Channel to receive new chain event +} + +// NewEventSystem creates a new manager that listens for event on the given mux, +// parses and filters them. It uses the all map to retrieve filter changes. The +// work loop holds its own index that is used to forward events to filters. +// +// The returned manager has a loop that needs to be stopped with the Stop function +// or by stopping the given mux. +func NewEventSystem(sys *FilterSystem) *EventSystem { + m := &EventSystem{ + sys: sys, + backend: sys.backend, + install: make(chan *subscription), + uninstall: make(chan *subscription), + txsCh: make(chan core.NewTxsEvent, txChanSize), + logsCh: make(chan []*types.Log, logsChanSize), + rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize), + chainCh: make(chan core.ChainEvent, chainEvChanSize), + } + + // Subscribe events + m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh) + m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh) + m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh) + m.chainSub = m.backend.SubscribeChainEvent(m.chainCh) + + // Make sure none of the subscriptions are empty + if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil { + log.Crit("Subscribe for event system failed") + } + + go m.eventLoop() + return m +} + +// Subscription is created when the client registers itself for a particular event. +type Subscription struct { + ID rpc.ID + f *subscription + es *EventSystem + unsubOnce sync.Once +} + +// Err returns a channel that is closed when unsubscribed. +func (sub *Subscription) Err() <-chan error { + return sub.f.err +} + +// Unsubscribe uninstalls the subscription from the event broadcast loop. +func (sub *Subscription) Unsubscribe() { + sub.unsubOnce.Do(func() { + uninstallLoop: + for { + // write uninstall request and consume logs/hashes. This prevents + // the eventLoop broadcast method to deadlock when writing to the + // filter event channel while the subscription loop is waiting for + // this method to return (and thus not reading these events). + select { + case sub.es.uninstall <- sub.f: + break uninstallLoop + case <-sub.f.logs: + case <-sub.f.txs: + case <-sub.f.headers: + } + } + + // wait for filter to be uninstalled in work loop before returning + // this ensures that the manager won't use the event channel which + // will probably be closed by the client asap after this method returns. + <-sub.Err() + }) +} + +// subscribe installs the subscription in the event broadcast loop. +func (es *EventSystem) subscribe(sub *subscription) *Subscription { + es.install <- sub + <-sub.installed + return &Subscription{ID: sub.id, f: sub, es: es} +} + +// SubscribeLogs creates a subscription that will write all logs matching the +// 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 + } else { + from = rpc.BlockNumber(crit.FromBlock.Int64()) + } + if crit.ToBlock == nil { + to = rpc.LatestBlockNumber + } else { + to = rpc.BlockNumber(crit.ToBlock.Int64()) + } + + // Pending logs are not supported anymore. + if from == rpc.PendingBlockNumber || to == rpc.PendingBlockNumber { + return nil, errPendingLogsUnsupported + } + + // only interested in new mined logs + if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { + return es.subscribeLogs(crit, logs), nil + } + // only interested in mined logs within a specific block range + if from >= 0 && to >= 0 && to >= from { + return es.subscribeLogs(crit, logs), nil + } + // interested in logs from a specific block number to new mined blocks + if from >= 0 && to == rpc.LatestBlockNumber { + return es.subscribeLogs(crit, logs), nil + } + return nil, errInvalidBlockRange +} + +// subscribeLogs creates a subscription that will write all logs matching the +// given criteria to the given logs channel. +func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription { + sub := &subscription{ + id: rpc.NewID(), + typ: LogsSubscription, + logsCrit: crit, + created: time.Now(), + logs: logs, + txs: make(chan []*types.Transaction), + headers: make(chan *types.Header), + installed: make(chan struct{}), + err: make(chan error), + } + return es.subscribe(sub) +} + +// SubscribeNewHeads creates a subscription that writes the header of a block that is +// imported in the chain. +func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription { + sub := &subscription{ + id: rpc.NewID(), + typ: BlocksSubscription, + created: time.Now(), + logs: make(chan []*types.Log), + txs: make(chan []*types.Transaction), + headers: headers, + installed: make(chan struct{}), + err: make(chan error), + } + return es.subscribe(sub) +} + +// SubscribePendingTxs creates a subscription that writes transactions for +// transactions that enter the transaction pool. +func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subscription { + sub := &subscription{ + id: rpc.NewID(), + typ: PendingTransactionsSubscription, + created: time.Now(), + logs: make(chan []*types.Log), + txs: txs, + headers: make(chan *types.Header), + installed: make(chan struct{}), + err: make(chan error), + } + return es.subscribe(sub) +} + +type filterIndex map[Type]map[rpc.ID]*subscription + +func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) { + if len(ev) == 0 { + return + } + for _, f := range filters[LogsSubscription] { + matchedLogs := filterLogs(ev, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) + if len(matchedLogs) > 0 { + f.logs <- matchedLogs + } + } +} + +func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { + for _, f := range filters[PendingTransactionsSubscription] { + f.txs <- ev.Txs + } +} + +func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) { + for _, f := range filters[BlocksSubscription] { + f.headers <- ev.Block.Header() + } +} + +// eventLoop (un)installs filters and processes mux events. +func (es *EventSystem) eventLoop() { + // Ensure all subscriptions get cleaned up + defer func() { + es.txsSub.Unsubscribe() + es.logsSub.Unsubscribe() + es.rmLogsSub.Unsubscribe() + es.chainSub.Unsubscribe() + }() + + index := make(filterIndex) + for i := UnknownSubscription; i < LastIndexSubscription; i++ { + index[i] = make(map[rpc.ID]*subscription) + } + + for { + select { + case ev := <-es.txsCh: + es.handleTxsEvent(index, ev) + case ev := <-es.logsCh: + es.handleLogs(index, ev) + case ev := <-es.rmLogsCh: + es.handleLogs(index, ev.Logs) + case ev := <-es.chainCh: + es.handleChainEvent(index, ev) + + case f := <-es.install: + index[f.typ][f.id] = f + close(f.installed) + + case f := <-es.uninstall: + delete(index[f.typ], f.id) + close(f.err) + + // System stopped + case <-es.txsSub.Err(): + return + case <-es.logsSub.Err(): + return + case <-es.rmLogsSub.Err(): + return + case <-es.chainSub.Err(): + return + } + } +} diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go new file mode 100644 index 0000000..013b9f7 --- /dev/null +++ b/eth/filters/filter_system_test.go @@ -0,0 +1,630 @@ +// 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 filters + +import ( + "context" + "errors" + "math/big" + "math/rand" + "reflect" + "runtime" + "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/bloombits" + "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/event" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +type testBackend struct { + db ethdb.Database + sections uint64 + txFeed event.Feed + logsFeed event.Feed + rmLogsFeed event.Feed + chainFeed event.Feed + pendingBlock *types.Block + pendingReceipts types.Receipts +} + +func (b *testBackend) ChainConfig() *params.ChainConfig { + return params.TestChainConfig +} + +func (b *testBackend) CurrentHeader() *types.Header { + hdr, _ := b.HeaderByNumber(context.TODO(), rpc.LatestBlockNumber) + return hdr +} + +func (b *testBackend) ChainDb() ethdb.Database { + return b.db +} + +func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { + var ( + hash common.Hash + num uint64 + ) + switch blockNr { + case rpc.LatestBlockNumber: + hash = rawdb.ReadHeadBlockHash(b.db) + number := rawdb.ReadHeaderNumber(b.db, hash) + if number == nil { + return nil, nil + } + num = *number + case rpc.FinalizedBlockNumber: + hash = rawdb.ReadFinalizedBlockHash(b.db) + number := rawdb.ReadHeaderNumber(b.db, hash) + if number == nil { + return nil, nil + } + num = *number + case rpc.SafeBlockNumber: + return nil, errors.New("safe block not found") + default: + num = uint64(blockNr) + hash = rawdb.ReadCanonicalHash(b.db, num) + } + return rawdb.ReadHeader(b.db, hash, num), nil +} + +func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + number := rawdb.ReadHeaderNumber(b.db, hash) + if number == nil { + return nil, nil + } + return rawdb.ReadHeader(b.db, hash, *number), nil +} + +func (b *testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { + if body := rawdb.ReadBody(b.db, hash, uint64(number)); body != nil { + return body, nil + } + return nil, errors.New("block body not found") +} + +func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + if number := rawdb.ReadHeaderNumber(b.db, hash); number != nil { + if header := rawdb.ReadHeader(b.db, hash, *number); header != nil { + return rawdb.ReadReceipts(b.db, hash, *number, header.Time, params.TestChainConfig), nil + } + } + return nil, nil +} + +func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { + logs := rawdb.ReadLogs(b.db, hash, number) + return logs, nil +} + +func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return b.txFeed.Subscribe(ch) +} + +func (b *testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { + return b.rmLogsFeed.Subscribe(ch) +} + +func (b *testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { + return b.logsFeed.Subscribe(ch) +} + +func (b *testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { + return b.chainFeed.Subscribe(ch) +} + +func (b *testBackend) BloomStatus() (uint64, uint64) { + return params.BloomBitsBlocks, b.sections +} + +func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { + requests := make(chan chan *bloombits.Retrieval) + + go session.Multiplex(16, 0, requests) + go func() { + for { + // Wait for a service request or a shutdown + select { + case <-ctx.Done(): + return + + case request := <-requests: + task := <-request + + task.Bitsets = make([][]byte, len(task.Sections)) + for i, section := range task.Sections { + if rand.Int()%4 != 0 { // Handle occasional missing deliveries + head := rawdb.ReadCanonicalHash(b.db, (section+1)*params.BloomBitsBlocks-1) + task.Bitsets[i], _ = rawdb.ReadBloomBits(b.db, task.Bit, section, head) + } + } + request <- task + } + } + }() +} + +func (b *testBackend) setPending(block *types.Block, receipts types.Receipts) { + b.pendingBlock = block + b.pendingReceipts = receipts +} + +func newTestFilterSystem(t testing.TB, db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) { + backend := &testBackend{db: db} + sys := NewFilterSystem(backend, cfg) + return backend, sys +} + +// TestBlockSubscription tests if a block subscription returns block hashes for posted chain events. +// It creates multiple subscriptions: +// - one at the start and should receive all posted chain events and a second (blockHashes) +// - one that is created after a cutoff moment and uninstalled after a second cutoff moment (blockHashes[cutoff1:cutoff2]) +// - one that is created after the second cutoff moment (blockHashes[cutoff2:]) +func TestBlockSubscription(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys) + genesis = &core.Genesis{ + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + _, chain, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {}) + chainEvents []core.ChainEvent + ) + + for _, blk := range chain { + chainEvents = append(chainEvents, core.ChainEvent{Hash: blk.Hash(), Block: blk}) + } + + chan0 := make(chan *types.Header) + sub0 := api.events.SubscribeNewHeads(chan0) + chan1 := make(chan *types.Header) + sub1 := api.events.SubscribeNewHeads(chan1) + + go func() { // simulate client + i1, i2 := 0, 0 + for i1 != len(chainEvents) || i2 != len(chainEvents) { + select { + case header := <-chan0: + if chainEvents[i1].Hash != header.Hash() { + t.Errorf("sub0 received invalid hash on index %d, want %x, got %x", i1, chainEvents[i1].Hash, header.Hash()) + } + i1++ + case header := <-chan1: + if chainEvents[i2].Hash != header.Hash() { + t.Errorf("sub1 received invalid hash on index %d, want %x, got %x", i2, chainEvents[i2].Hash, header.Hash()) + } + i2++ + } + } + + sub0.Unsubscribe() + sub1.Unsubscribe() + }() + + time.Sleep(1 * time.Second) + for _, e := range chainEvents { + backend.chainFeed.Send(e) + } + + <-sub0.Err() + <-sub1.Err() +} + +// TestPendingTxFilter tests whether pending tx filters retrieve all pending transactions that are posted to the event mux. +func TestPendingTxFilter(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys) + + transactions = []*types.Transaction{ + types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(1, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(2, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(3, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(4, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + } + + hashes []common.Hash + ) + + fid0 := api.NewPendingTransactionFilter(nil) + + time.Sleep(1 * time.Second) + backend.txFeed.Send(core.NewTxsEvent{Txs: transactions}) + + timeout := time.Now().Add(1 * time.Second) + for { + results, err := api.GetFilterChanges(fid0) + if err != nil { + t.Fatalf("Unable to retrieve logs: %v", err) + } + + h := results.([]common.Hash) + hashes = append(hashes, h...) + if len(hashes) >= len(transactions) { + break + } + // check timeout + if time.Now().After(timeout) { + break + } + + time.Sleep(100 * time.Millisecond) + } + + if len(hashes) != len(transactions) { + t.Errorf("invalid number of transactions, want %d transactions(s), got %d", len(transactions), len(hashes)) + return + } + for i := range hashes { + if hashes[i] != transactions[i].Hash() { + t.Errorf("hashes[%d] invalid, want %x, got %x", i, transactions[i].Hash(), hashes[i]) + } + } +} + +// TestPendingTxFilterFullTx tests whether pending tx filters retrieve all pending transactions that are posted to the event mux. +func TestPendingTxFilterFullTx(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys) + + transactions = []*types.Transaction{ + types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(1, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(2, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(3, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + types.NewTransaction(4, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), + } + + txs []*ethapi.RPCTransaction + ) + + fullTx := true + fid0 := api.NewPendingTransactionFilter(&fullTx) + + time.Sleep(1 * time.Second) + backend.txFeed.Send(core.NewTxsEvent{Txs: transactions}) + + timeout := time.Now().Add(1 * time.Second) + for { + results, err := api.GetFilterChanges(fid0) + if err != nil { + t.Fatalf("Unable to retrieve logs: %v", err) + } + + tx := results.([]*ethapi.RPCTransaction) + txs = append(txs, tx...) + if len(txs) >= len(transactions) { + break + } + // check timeout + if time.Now().After(timeout) { + break + } + + time.Sleep(100 * time.Millisecond) + } + + if len(txs) != len(transactions) { + t.Errorf("invalid number of transactions, want %d transactions(s), got %d", len(transactions), len(txs)) + return + } + for i := range txs { + if txs[i].Hash != transactions[i].Hash() { + t.Errorf("hashes[%d] invalid, want %x, got %x", i, transactions[i].Hash(), txs[i].Hash) + } + } +} + +// TestLogFilterCreation test whether a given filter criteria makes sense. +// If not it must return an error. +func TestLogFilterCreation(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + _, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys) + + testCases = []struct { + crit FilterCriteria + success bool + }{ + // defaults + {FilterCriteria{}, true}, + // valid block number range + {FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2)}, true}, + // "mined" block range to pending + {FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, true}, + // from block "higher" than to block + {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(1)}, false}, + // from block "higher" than to block + {FilterCriteria{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, false}, + // from block "higher" than to block + {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 than 4 + {FilterCriteria{Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, false}, + } + ) + + for i, test := range testCases { + id, err := api.NewFilter(test.crit) + if err != nil && test.success { + t.Errorf("expected filter creation for case %d to success, got %v", i, err) + } + if err == nil { + api.UninstallFilter(id) + if !test.success { + t.Errorf("expected testcase %d to fail with an error", i) + } + } + } +} + +// TestInvalidLogFilterCreation tests whether invalid filter log criteria results in an error +// when the filter is created. +func TestInvalidLogFilterCreation(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + _, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys) + ) + + // different situations where log filter creation should fail. + // Reason: fromBlock > toBlock + testCases := []FilterCriteria{ + 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 { + if _, err := api.NewFilter(test); err == nil { + t.Errorf("Expected NewFilter for case #%d to fail", i) + } + } +} + +// TestInvalidGetLogsRequest tests invalid getLogs requests +func TestInvalidGetLogsRequest(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + _, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys) + blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") + ) + + // Reason: Cannot specify both BlockHash and FromBlock/ToBlock) + testCases := []FilterCriteria{ + 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 { + if _, err := api.GetLogs(context.Background(), test); err == nil { + t.Errorf("Expected Logs for case #%d to fail", i) + } + } +} + +// 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) + ) + + 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() + + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys) + + firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") + secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") + thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333") + notUsedAddress = common.HexToAddress("0x9999999999999999999999999999999999999999") + firstTopic = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") + secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222") + notUsedTopic = common.HexToHash("0x9999999999999999999999999999999999999999999999999999999999999999") + + // posted twice, once as regular logs and once as pending logs. + allLogs = []*types.Log{ + {Address: firstAddr}, + {Address: firstAddr, Topics: []common.Hash{firstTopic}, BlockNumber: 1}, + {Address: secondAddr, Topics: []common.Hash{firstTopic}, BlockNumber: 1}, + {Address: thirdAddress, Topics: []common.Hash{secondTopic}, BlockNumber: 2}, + {Address: thirdAddress, Topics: []common.Hash{secondTopic}, BlockNumber: 3}, + } + + testCases = []struct { + crit FilterCriteria + expected []*types.Log + id rpc.ID + }{ + // match all + 0: {FilterCriteria{}, allLogs, ""}, + // match none due to no matching addresses + 1: {FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, []*types.Log{}, ""}, + // match logs based on addresses, ignore topics + 2: {FilterCriteria{Addresses: []common.Address{firstAddr}}, allLogs[:2], ""}, + // match none due to no matching topics (match with address) + 3: {FilterCriteria{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{{notUsedTopic}}}, []*types.Log{}, ""}, + // match logs based on addresses and topics + 4: {FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, allLogs[3:5], ""}, + // match logs based on multiple addresses and "or" topics + 5: {FilterCriteria{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, allLogs[2:5], ""}, + // all "mined" logs with block num >= 2 + 6: {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, allLogs[3:], ""}, + // all "mined" logs + 7: {FilterCriteria{ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, allLogs, ""}, + // all "mined" logs with 1>= block num <=2 and topic secondTopic + 8: {FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2), Topics: [][]common.Hash{{secondTopic}}}, allLogs[3:4], ""}, + // match all logs due to wildcard topic + 9: {FilterCriteria{Topics: [][]common.Hash{nil}}, allLogs[1:], ""}, + } + ) + + // create all filters + for i := range testCases { + testCases[i].id, _ = api.NewFilter(testCases[i].crit) + } + + // raise events + time.Sleep(1 * time.Second) + if nsend := backend.logsFeed.Send(allLogs); nsend == 0 { + t.Fatal("Logs event not delivered") + } + + for i, tt := range testCases { + var fetched []*types.Log + timeout := time.Now().Add(1 * time.Second) + for { // fetch all expected logs + results, err := api.GetFilterChanges(tt.id) + if err != nil { + t.Fatalf("test %d: unable to fetch logs: %v", i, err) + } + + fetched = append(fetched, results.([]*types.Log)...) + if len(fetched) >= len(tt.expected) { + break + } + // check timeout + if time.Now().After(timeout) { + break + } + + time.Sleep(100 * time.Millisecond) + } + + if len(fetched) != len(tt.expected) { + t.Errorf("invalid number of logs for case %d, want %d log(s), got %d", i, len(tt.expected), len(fetched)) + return + } + + for l := range fetched { + if fetched[l].Removed { + t.Errorf("expected log not to be removed for log %d in case %d", l, i) + } + if !reflect.DeepEqual(fetched[l], tt.expected[l]) { + t.Errorf("invalid log on index %d for case %d", l, i) + } + } + } +} + +// TestPendingTxFilterDeadlock tests if the event loop hangs when pending +// txes arrive at the same time that one of multiple filters is timing out. +// Please refer to #22131 for more details. +func TestPendingTxFilterDeadlock(t *testing.T) { + t.Parallel() + timeout := 100 * time.Millisecond + + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{Timeout: timeout}) + api = NewFilterAPI(sys) + done = make(chan struct{}) + ) + + go func() { + // Bombard feed with txes until signal was received to stop + i := uint64(0) + for { + select { + case <-done: + return + default: + } + + tx := types.NewTransaction(i, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil) + backend.txFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx}}) + i++ + } + }() + + // Create a bunch of filters that will + // timeout either in 100ms or 200ms + subs := make([]*Subscription, 20) + for i := 0; i < len(subs); i++ { + fid := api.NewPendingTransactionFilter(nil) + 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) + if err != nil { + t.Fatalf("Filter should exist: %v\n", err) + } + if len(hashes.([]common.Hash)) > 0 { + break + } + runtime.Gosched() + } + } + + // 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") + } + } +} diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go new file mode 100644 index 0000000..2b3efb5 --- /dev/null +++ b/eth/filters/filter_test.go @@ -0,0 +1,389 @@ +// 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 filters + +import ( + "context" + "encoding/json" + "math/big" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/triedb" +) + +func makeReceipt(addr common.Address) *types.Receipt { + receipt := types.NewReceipt(nil, false, 0) + receipt.Logs = []*types.Log{ + {Address: addr}, + } + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + return receipt +} + +func BenchmarkFilters(b *testing.B) { + var ( + db, _ = rawdb.NewLevelDBDatabase(b.TempDir(), 0, 0, "", false) + _, sys = newTestFilterSystem(b, db, Config{}) + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = common.BytesToAddress([]byte("jeff")) + addr3 = common.BytesToAddress([]byte("ethereum")) + addr4 = common.BytesToAddress([]byte("random addresses please")) + + gspec = &core.Genesis{ + Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.TestChainConfig, + } + ) + defer db.Close() + _, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 100010, func(i int, gen *core.BlockGen) { + switch i { + case 2403: + receipt := makeReceipt(addr1) + gen.AddUncheckedReceipt(receipt) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) + case 1034: + receipt := makeReceipt(addr2) + gen.AddUncheckedReceipt(receipt) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) + case 34: + receipt := makeReceipt(addr3) + gen.AddUncheckedReceipt(receipt) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) + case 99999: + receipt := makeReceipt(addr4) + gen.AddUncheckedReceipt(receipt) + gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) + } + }) + // 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, triedb.NewDatabase(db, triedb.HashDefaults)) + + for i, block := range chain { + rawdb.WriteBlock(db, block) + rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) + rawdb.WriteHeadBlockHash(db, block.Hash()) + rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) + } + b.ResetTimer() + + filter := sys.NewRangeFilter(0, -1, []common.Address{addr1, addr2, addr3, addr4}, nil) + + for i := 0; i < b.N; i++ { + filter.begin = 0 + logs, _ := filter.Logs(context.Background()) + if len(logs) != 4 { + b.Fatal("expected 4 logs, got", len(logs)) + } + } +} + +func TestFilters(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(t, db, Config{}) + // Sender account + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key1.PublicKey) + signer = types.NewLondonSigner(big.NewInt(1)) + // Logging contract + contract = common.Address{0xfe} + contract2 = common.Address{0xff} + abiStr = `[{"inputs":[],"name":"log0","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"}],"name":"log1","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"}],"name":"log2","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"},{"internalType":"uint256","name":"t3","type":"uint256"}],"name":"log3","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"},{"internalType":"uint256","name":"t3","type":"uint256"},{"internalType":"uint256","name":"t4","type":"uint256"}],"name":"log4","outputs":[],"stateMutability":"nonpayable","type":"function"}]` + /* + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.7.0 <0.9.0; + + contract Logger { + function log0() external { + assembly { + log0(0, 0) + } + } + + function log1(uint t1) external { + assembly { + log1(0, 0, t1) + } + } + + function log2(uint t1, uint t2) external { + assembly { + log2(0, 0, t1, t2) + } + } + + function log3(uint t1, uint t2, uint t3) external { + assembly { + log3(0, 0, t1, t2, t3) + } + } + + function log4(uint t1, uint t2, uint t3, uint t4) external { + assembly { + log4(0, 0, t1, t2, t3, t4) + } + } + } + */ + bytecode = common.FromHex("608060405234801561001057600080fd5b50600436106100575760003560e01c80630aa731851461005c5780632a4c08961461006657806378b9a1f314610082578063c670f8641461009e578063c683d6a3146100ba575b600080fd5b6100646100d6565b005b610080600480360381019061007b9190610143565b6100dc565b005b61009c60048036038101906100979190610196565b6100e8565b005b6100b860048036038101906100b391906101d6565b6100f2565b005b6100d460048036038101906100cf9190610203565b6100fa565b005b600080a0565b808284600080a3505050565b8082600080a25050565b80600080a150565b80828486600080a450505050565b600080fd5b6000819050919050565b6101208161010d565b811461012b57600080fd5b50565b60008135905061013d81610117565b92915050565b60008060006060848603121561015c5761015b610108565b5b600061016a8682870161012e565b935050602061017b8682870161012e565b925050604061018c8682870161012e565b9150509250925092565b600080604083850312156101ad576101ac610108565b5b60006101bb8582860161012e565b92505060206101cc8582860161012e565b9150509250929050565b6000602082840312156101ec576101eb610108565b5b60006101fa8482850161012e565b91505092915050565b6000806000806080858703121561021d5761021c610108565b5b600061022b8782880161012e565b945050602061023c8782880161012e565b935050604061024d8782880161012e565b925050606061025e8782880161012e565b9150509295919450925056fea264697066735822122073a4b156f487e59970dc1ef449cc0d51467268f676033a17188edafcee861f9864736f6c63430008110033") + + hash1 = common.BytesToHash([]byte("topic1")) + hash2 = common.BytesToHash([]byte("topic2")) + hash3 = common.BytesToHash([]byte("topic3")) + hash4 = common.BytesToHash([]byte("topic4")) + hash5 = common.BytesToHash([]byte("topic5")) + + gspec = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + addr: {Balance: big.NewInt(0).Mul(big.NewInt(100), big.NewInt(params.Ether))}, + contract: {Balance: big.NewInt(0), Code: bytecode}, + contract2: {Balance: big.NewInt(0), Code: bytecode}, + }, + BaseFee: big.NewInt(params.InitialBaseFee), + } + ) + + contractABI, err := abi.JSON(strings.NewReader(abiStr)) + if err != nil { + t.Fatal(err) + } + + // Hack: GenerateChainWithGenesis creates a new db. + // Commit the genesis manually and use GenerateChain. + _, err = gspec.Commit(db, triedb.NewDatabase(db, nil)) + if err != nil { + t.Fatal(err) + } + chain, _ := core.GenerateChain(gspec.Config, gspec.ToBlock(), ethash.NewFaker(), db, 1000, func(i int, gen *core.BlockGen) { + switch i { + case 1: + data, err := contractABI.Pack("log1", hash1.Big()) + if err != nil { + t.Fatal(err) + } + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 0, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract, + Data: data, + }), signer, key1) + gen.AddTx(tx) + tx2, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 1, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract2, + Data: data, + }), signer, key1) + gen.AddTx(tx2) + case 2: + data, err := contractABI.Pack("log2", hash2.Big(), hash1.Big()) + if err != nil { + t.Fatal(err) + } + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 2, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract, + Data: data, + }), signer, key1) + gen.AddTx(tx) + case 998: + data, err := contractABI.Pack("log1", hash3.Big()) + if err != nil { + t.Fatal(err) + } + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 3, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract2, + Data: data, + }), signer, key1) + gen.AddTx(tx) + case 999: + data, err := contractABI.Pack("log1", hash4.Big()) + if err != nil { + t.Fatal(err) + } + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 4, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract, + Data: data, + }), signer, key1) + gen.AddTx(tx) + } + }) + var l uint64 + bc, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) + if err != nil { + t.Fatal(err) + } + _, err = bc.InsertChain(chain) + if err != nil { + t.Fatal(err) + } + + // Set block 998 as Finalized (-3) + bc.SetFinalized(chain[998].Header()) + + // Generate pending block + pchain, preceipts := core.GenerateChain(gspec.Config, chain[len(chain)-1], ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) { + data, err := contractABI.Pack("log1", hash5.Big()) + if err != nil { + t.Fatal(err) + } + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 5, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract, + Data: data, + }), signer, key1) + gen.AddTx(tx) + }) + backend.setPending(pchain[0], preceipts[0]) + + for i, tc := range []struct { + f *Filter + want string + err string + }{ + { + 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), + err: errPendingLogsUnsupported.Error(), + }, + { + f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.PendingBlockNumber), nil, nil), + err: errPendingLogsUnsupported.Error(), + }, + { + f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), + err: errPendingLogsUnsupported.Error(), + }, + } { + logs, err := tc.f.Logs(context.Background()) + if err == nil && tc.err != "" { + t.Fatalf("test %d, expected error %q, got nil", i, tc.err) + } else if err != nil && err.Error() != tc.err { + t.Fatalf("test %d, expected error %q, got %q", i, tc.err, err.Error()) + } + if tc.want == "" && len(logs) == 0 { + continue + } + have, err := json.Marshal(logs) + if err != nil { + t.Fatal(err) + } + if string(have) != tc.want { + t.Fatalf("test %d, have:\n%s\nwant:\n%s", i, have, tc.want) + } + } + + t.Run("timeout", func(t *testing.T) { + f := sys.NewRangeFilter(0, rpc.LatestBlockNumber.Int64(), nil, nil) + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Hour)) + defer cancel() + _, err := f.Logs(ctx) + if err == nil { + t.Fatal("expected error") + } + if err != context.DeadlineExceeded { + t.Fatalf("expected context.DeadlineExceeded, got %v", err) + } + }) +} diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go new file mode 100644 index 0000000..ac3b59e --- /dev/null +++ b/eth/gasestimator/gasestimator.go @@ -0,0 +1,245 @@ +// 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).ToBig() + + available := balance + if call.Value != nil { + if call.Value.Cmp(available) >= 0 { + return 0, nil, core.ErrInsufficientFundsForTransfer + } + available.Sub(available, call.Value) + } + if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 { + blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob) + blobBalanceUsage := new(big.Int).SetInt64(int64(len(call.BlobHashes))) + blobBalanceUsage.Mul(blobBalanceUsage, blobGasPerBlob) + blobBalanceUsage.Mul(blobBalanceUsage, call.BlobGasFeeCap) + if blobBalanceUsage.Cmp(available) >= 0 { + return 0, nil, core.ErrInsufficientFunds + } + available.Sub(available, blobBalanceUsage) + } + 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/gasprice/feehistory.go b/eth/gasprice/feehistory.go new file mode 100644 index 0000000..1e625e2 --- /dev/null +++ b/eth/gasprice/feehistory.go @@ -0,0 +1,358 @@ +// 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 gasprice + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "math" + "math/big" + "slices" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + errInvalidPercentile = errors.New("invalid reward percentile") + errRequestBeyondHead = errors.New("request beyond head block") +) + +const ( + // maxBlockFetchers is the max number of goroutines to spin up to pull blocks + // for the fee history calculation (mostly relevant for LES). + maxBlockFetchers = 4 + // maxQueryLimit is the max number of requested percentiles. + maxQueryLimit = 100 +) + +// blockFees represents a single block for processing +type blockFees struct { + // set by the caller + blockNumber uint64 + header *types.Header + block *types.Block // only set if reward percentiles are requested + receipts types.Receipts + // filled by processBlock + results processedFees + err error +} + +type cacheKey struct { + number uint64 + percentiles string +} + +// processedFees contains the results of a processed block. +type processedFees struct { + reward []*big.Int + baseFee, nextBaseFee *big.Int + gasUsedRatio float64 + blobGasUsedRatio float64 + blobBaseFee, nextBlobBaseFee *big.Int +} + +// txGasAndReward is sorted in ascending order based on reward +type txGasAndReward struct { + gasUsed uint64 + reward *big.Int +} + +// processBlock takes a blockFees structure with the blockNumber, the header and optionally +// the block field filled in, retrieves the block from the backend if not present yet and +// fills in the rest of the fields. +func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { + config := oracle.backend.ChainConfig() + + // Fill in base fee and next base fee. + if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil { + bf.results.baseFee = new(big.Int) + } + if config.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) { + bf.results.nextBaseFee = eip1559.CalcBaseFee(config, bf.header) + } else { + bf.results.nextBaseFee = new(big.Int) + } + // Fill in blob base fee and next blob base fee. + if excessBlobGas := bf.header.ExcessBlobGas; excessBlobGas != nil { + bf.results.blobBaseFee = eip4844.CalcBlobFee(*excessBlobGas) + bf.results.nextBlobBaseFee = eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*excessBlobGas, *bf.header.BlobGasUsed)) + } else { + bf.results.blobBaseFee = new(big.Int) + bf.results.nextBlobBaseFee = new(big.Int) + } + // Compute gas used ratio for normal and blob gas. + bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit) + if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil { + bf.results.blobGasUsedRatio = float64(*blobGasUsed) / params.MaxBlobGasPerBlock + } + + if len(percentiles) == 0 { + // rewards were not requested, return null + return + } + if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) { + log.Error("Block or receipts are missing while reward percentiles are requested") + return + } + + bf.results.reward = make([]*big.Int, len(percentiles)) + if len(bf.block.Transactions()) == 0 { + // return an all zero row if there are no transactions to gather data from + for i := range bf.results.reward { + bf.results.reward[i] = new(big.Int) + } + return + } + + sorter := make([]txGasAndReward, len(bf.block.Transactions())) + for i, tx := range bf.block.Transactions() { + reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) + sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} + } + slices.SortStableFunc(sorter, func(a, b txGasAndReward) int { + return a.reward.Cmp(b.reward) + }) + + var txIndex int + sumGasUsed := sorter[0].gasUsed + + for i, p := range percentiles { + thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100) + for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 { + txIndex++ + sumGasUsed += sorter[txIndex].gasUsed + } + bf.results.reward[i] = sorter[txIndex].reward + } +} + +// resolveBlockRange resolves the specified block range to absolute block numbers while also +// enforcing backend specific limitations. The pending block and corresponding receipts are +// also returned if requested and available. +// Note: an error is only returned if retrieving the head header has failed. If there are no +// retrievable blocks in the specified range then zero block count is returned with no error. +func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNumber, blocks uint64) (*types.Block, []*types.Receipt, uint64, uint64, error) { + var ( + headBlock *types.Header + pendingBlock *types.Block + pendingReceipts types.Receipts + err error + ) + + // Get the chain's current head. + if headBlock, err = oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err != nil { + return nil, nil, 0, 0, err + } + head := rpc.BlockNumber(headBlock.Number.Uint64()) + + // Fail if request block is beyond the chain's current head. + if head < reqEnd { + return nil, nil, 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, reqEnd, head) + } + + // Resolve block tag. + if reqEnd < 0 { + var ( + resolved *types.Header + err error + ) + switch reqEnd { + case rpc.PendingBlockNumber: + if pendingBlock, pendingReceipts, _ = oracle.backend.Pending(); pendingBlock != nil { + resolved = pendingBlock.Header() + } else { + // Pending block not supported by backend, process only until latest block. + resolved = headBlock + + // Update total blocks to return to account for this. + blocks-- + } + case rpc.LatestBlockNumber: + // Retrieved above. + resolved = headBlock + case rpc.SafeBlockNumber: + resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber) + case rpc.FinalizedBlockNumber: + resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) + case rpc.EarliestBlockNumber: + resolved, err = oracle.backend.HeaderByNumber(ctx, rpc.EarliestBlockNumber) + } + if resolved == nil || err != nil { + return nil, nil, 0, 0, err + } + // Absolute number resolved. + reqEnd = rpc.BlockNumber(resolved.Number.Uint64()) + } + + // If there are no blocks to return, short circuit. + if blocks == 0 { + return nil, nil, 0, 0, nil + } + // Ensure not trying to retrieve before genesis. + if uint64(reqEnd+1) < blocks { + blocks = uint64(reqEnd + 1) + } + return pendingBlock, pendingReceipts, uint64(reqEnd), blocks, nil +} + +// FeeHistory returns data relevant for fee estimation based on the specified range of blocks. +// The range can be specified either with absolute block numbers or ending with the latest +// or pending block. Backends may or may not support gathering data from the pending block +// or blocks older than a certain age (specified in maxHistory). The first block of the +// actually processed range is returned to avoid ambiguity when parts of the requested range +// are not available or when the head has changed during processing this request. +// Five arrays are returned based on the processed blocks: +// - reward: the requested percentiles of effective priority fees per gas of transactions in each +// block, sorted in ascending order and weighted by gas used. +// - baseFee: base fee per gas in the given block +// - gasUsedRatio: gasUsed/gasLimit in the given block +// - blobBaseFee: the blob base fee per gas in the given block +// - blobGasUsedRatio: blobGasUsed/blobGasLimit in the given block +// +// Note: baseFee and blobBaseFee both include the next block after the newest of the returned range, +// because this value can be derived from the newest block. +func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, []*big.Int, []float64, error) { + if blocks < 1 { + return common.Big0, nil, nil, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks + } + maxFeeHistory := oracle.maxHeaderHistory + if len(rewardPercentiles) != 0 { + maxFeeHistory = oracle.maxBlockHistory + } + if len(rewardPercentiles) > maxQueryLimit { + return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: over the query limit %d", errInvalidPercentile, maxQueryLimit) + } + if blocks > maxFeeHistory { + log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory) + blocks = maxFeeHistory + } + for i, p := range rewardPercentiles { + if p < 0 || p > 100 { + return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p) + } + if i > 0 && p <= rewardPercentiles[i-1] { + return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) + } + } + var ( + pendingBlock *types.Block + pendingReceipts []*types.Receipt + err error + ) + pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks) + if err != nil || blocks == 0 { + return common.Big0, nil, nil, nil, nil, nil, err + } + oldestBlock := lastBlock + 1 - blocks + + var next atomic.Uint64 + next.Store(oldestBlock) + results := make(chan *blockFees, blocks) + + percentileKey := make([]byte, 8*len(rewardPercentiles)) + for i, p := range rewardPercentiles { + binary.LittleEndian.PutUint64(percentileKey[i*8:(i+1)*8], math.Float64bits(p)) + } + for i := 0; i < maxBlockFetchers && i < int(blocks); i++ { + go func() { + for { + // Retrieve the next block number to fetch with this goroutine + blockNumber := next.Add(1) - 1 + if blockNumber > lastBlock { + return + } + + fees := &blockFees{blockNumber: blockNumber} + if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() { + fees.block, fees.receipts = pendingBlock, pendingReceipts + fees.header = fees.block.Header() + oracle.processBlock(fees, rewardPercentiles) + results <- fees + } else { + cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)} + + if p, ok := oracle.historyCache.Get(cacheKey); ok { + fees.results = p + results <- fees + } else { + if len(rewardPercentiles) != 0 { + fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber)) + if fees.block != nil && fees.err == nil { + fees.receipts, fees.err = oracle.backend.GetReceipts(ctx, fees.block.Hash()) + fees.header = fees.block.Header() + } + } else { + fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) + } + if fees.header != nil && fees.err == nil { + oracle.processBlock(fees, rewardPercentiles) + if fees.err == nil { + oracle.historyCache.Add(cacheKey, fees.results) + } + } + // send to results even if empty to guarantee that blocks items are sent in total + results <- fees + } + } + } + }() + } + var ( + reward = make([][]*big.Int, blocks) + baseFee = make([]*big.Int, blocks+1) + gasUsedRatio = make([]float64, blocks) + blobGasUsedRatio = make([]float64, blocks) + blobBaseFee = make([]*big.Int, blocks+1) + firstMissing = blocks + ) + for ; blocks > 0; blocks-- { + fees := <-results + if fees.err != nil { + return common.Big0, nil, nil, nil, nil, nil, fees.err + } + i := fees.blockNumber - oldestBlock + if fees.results.baseFee != nil { + reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio + blobGasUsedRatio[i], blobBaseFee[i], blobBaseFee[i+1] = fees.results.blobGasUsedRatio, fees.results.blobBaseFee, fees.results.nextBlobBaseFee + } else { + // getting no block and no error means we are requesting into the future (might happen because of a reorg) + if i < firstMissing { + firstMissing = i + } + } + } + if firstMissing == 0 { + return common.Big0, nil, nil, nil, nil, nil, nil + } + if len(rewardPercentiles) != 0 { + reward = reward[:firstMissing] + } else { + reward = nil + } + baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing] + blobBaseFee, blobGasUsedRatio = blobBaseFee[:firstMissing+1], blobGasUsedRatio[:firstMissing] + return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, blobBaseFee, blobGasUsedRatio, nil +} diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go new file mode 100644 index 0000000..3d426db --- /dev/null +++ b/eth/gasprice/feehistory_test.go @@ -0,0 +1,97 @@ +// 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 gasprice + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/rpc" +) + +func TestFeeHistory(t *testing.T) { + var cases = []struct { + pending bool + maxHeader, maxBlock uint64 + count uint64 + last rpc.BlockNumber + percent []float64 + expFirst uint64 + expCount int + expErr error + }{ + {false, 1000, 1000, 10, 30, nil, 21, 10, nil}, + {false, 1000, 1000, 10, 30, []float64{0, 10}, 21, 10, nil}, + {false, 1000, 1000, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile}, + {false, 1000, 1000, 1000000000, 30, nil, 0, 31, nil}, + {false, 1000, 1000, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil}, + {false, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead}, + {true, 1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead}, + {false, 20, 2, 100, rpc.LatestBlockNumber, nil, 13, 20, nil}, + {false, 20, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil}, + {false, 20, 2, 100, 32, []float64{0, 10}, 31, 2, nil}, + {false, 1000, 1000, 1, rpc.PendingBlockNumber, nil, 0, 0, nil}, + {false, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil}, + {true, 1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 2, nil}, + {true, 1000, 1000, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 2, nil}, + {false, 1000, 1000, 2, rpc.FinalizedBlockNumber, []float64{0, 10}, 24, 2, nil}, + {false, 1000, 1000, 2, rpc.SafeBlockNumber, []float64{0, 10}, 24, 2, nil}, + } + for i, c := range cases { + config := Config{ + MaxHeaderHistory: c.maxHeader, + MaxBlockHistory: c.maxBlock, + } + backend := newTestBackend(t, big.NewInt(16), big.NewInt(28), c.pending) + oracle := NewOracle(backend, config) + + first, reward, baseFee, ratio, blobBaseFee, blobRatio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent) + backend.teardown() + expReward := c.expCount + if len(c.percent) == 0 { + expReward = 0 + } + expBaseFee := c.expCount + if expBaseFee != 0 { + expBaseFee++ + } + + if first.Uint64() != c.expFirst { + t.Fatalf("Test case %d: first block mismatch, want %d, got %d", i, c.expFirst, first) + } + if len(reward) != expReward { + t.Fatalf("Test case %d: reward array length mismatch, want %d, got %d", i, expReward, len(reward)) + } + if len(baseFee) != expBaseFee { + t.Fatalf("Test case %d: baseFee array length mismatch, want %d, got %d", i, expBaseFee, len(baseFee)) + } + if len(ratio) != c.expCount { + t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio)) + } + if len(blobRatio) != c.expCount { + t.Fatalf("Test case %d: blobGasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(blobRatio)) + } + if len(blobBaseFee) != len(baseFee) { + t.Fatalf("Test case %d: blobBaseFee array length mismatch, want %d, got %d", i, len(baseFee), len(blobBaseFee)) + } + if err != c.expErr && !errors.Is(err, c.expErr) { + t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err) + } + } +} diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go new file mode 100644 index 0000000..c90408e --- /dev/null +++ b/eth/gasprice/gasprice.go @@ -0,0 +1,277 @@ +// 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 gasprice + +import ( + "context" + "math/big" + "slices" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +const sampleNumber = 3 // Number of transactions sampled in a block + +var ( + DefaultMaxPrice = big.NewInt(500 * params.GWei) + DefaultIgnorePrice = big.NewInt(2 * params.Wei) +) + +type Config struct { + Blocks int + Percentile int + MaxHeaderHistory uint64 + MaxBlockHistory uint64 + Default *big.Int `toml:",omitempty"` + MaxPrice *big.Int `toml:",omitempty"` + IgnorePrice *big.Int `toml:",omitempty"` +} + +// OracleBackend includes all necessary background APIs for oracle. +type OracleBackend interface { + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) + Pending() (*types.Block, types.Receipts, *state.StateDB) + ChainConfig() *params.ChainConfig + SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription +} + +// Oracle recommends gas prices based on the content of recent +// blocks. Suitable for both light and full clients. +type Oracle struct { + backend OracleBackend + lastHead common.Hash + lastPrice *big.Int + maxPrice *big.Int + ignorePrice *big.Int + cacheLock sync.RWMutex + fetchLock sync.Mutex + + checkBlocks, percentile int + maxHeaderHistory, maxBlockHistory uint64 + + historyCache *lru.Cache[cacheKey, processedFees] +} + +// NewOracle returns a new gasprice oracle which can recommend suitable +// gasprice for newly created transaction. +func NewOracle(backend OracleBackend, params Config) *Oracle { + blocks := params.Blocks + if blocks < 1 { + blocks = 1 + log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks) + } + percent := params.Percentile + if percent < 0 { + percent = 0 + log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) + } else if percent > 100 { + percent = 100 + log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) + } + maxPrice := params.MaxPrice + if maxPrice == nil || maxPrice.Int64() <= 0 { + maxPrice = DefaultMaxPrice + log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice) + } + ignorePrice := params.IgnorePrice + if ignorePrice == nil || ignorePrice.Int64() <= 0 { + ignorePrice = DefaultIgnorePrice + log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", params.IgnorePrice, "updated", ignorePrice) + } else if ignorePrice.Int64() > 0 { + log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice) + } + maxHeaderHistory := params.MaxHeaderHistory + if maxHeaderHistory < 1 { + maxHeaderHistory = 1 + log.Warn("Sanitizing invalid gasprice oracle max header history", "provided", params.MaxHeaderHistory, "updated", maxHeaderHistory) + } + maxBlockHistory := params.MaxBlockHistory + if maxBlockHistory < 1 { + maxBlockHistory = 1 + log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory) + } + + cache := lru.NewCache[cacheKey, processedFees](2048) + headEvent := make(chan core.ChainHeadEvent, 1) + backend.SubscribeChainHeadEvent(headEvent) + go func() { + var lastHead common.Hash + for ev := range headEvent { + if ev.Block.ParentHash() != lastHead { + cache.Purge() + } + lastHead = ev.Block.Hash() + } + }() + + return &Oracle{ + backend: backend, + lastPrice: params.Default, + maxPrice: maxPrice, + ignorePrice: ignorePrice, + checkBlocks: blocks, + percentile: percent, + maxHeaderHistory: maxHeaderHistory, + maxBlockHistory: maxBlockHistory, + historyCache: cache, + } +} + +// SuggestTipCap returns a tip cap so that newly created transaction can have a +// very high chance to be included in the following blocks. +// +// Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be +// necessary to add the basefee to the returned number to fall back to the legacy +// behavior. +func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { + head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + headHash := head.Hash() + + // If the latest gasprice is still available, return it. + oracle.cacheLock.RLock() + lastHead, lastPrice := oracle.lastHead, oracle.lastPrice + oracle.cacheLock.RUnlock() + if headHash == lastHead { + return new(big.Int).Set(lastPrice), nil + } + oracle.fetchLock.Lock() + defer oracle.fetchLock.Unlock() + + // Try checking the cache again, maybe the last fetch fetched what we need + oracle.cacheLock.RLock() + lastHead, lastPrice = oracle.lastHead, oracle.lastPrice + oracle.cacheLock.RUnlock() + if headHash == lastHead { + return new(big.Int).Set(lastPrice), nil + } + var ( + sent, exp int + number = head.Number.Uint64() + result = make(chan results, oracle.checkBlocks) + quit = make(chan struct{}) + results []*big.Int + ) + for sent < oracle.checkBlocks && number > 0 { + go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit) + sent++ + exp++ + number-- + } + for exp > 0 { + res := <-result + if res.err != nil { + close(quit) + return new(big.Int).Set(lastPrice), res.err + } + exp-- + // Nothing returned. There are two special cases here: + // - The block is empty + // - All the transactions included are sent by the miner itself. + // In these cases, use the latest calculated price for sampling. + if len(res.values) == 0 { + res.values = []*big.Int{lastPrice} + } + // Besides, in order to collect enough data for sampling, if nothing + // meaningful returned, try to query more blocks. But the maximum + // is 2*checkBlocks. + if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 { + go oracle.getBlockValues(ctx, number, sampleNumber, oracle.ignorePrice, result, quit) + sent++ + exp++ + number-- + } + results = append(results, res.values...) + } + price := lastPrice + if len(results) > 0 { + slices.SortFunc(results, func(a, b *big.Int) int { return a.Cmp(b) }) + price = results[(len(results)-1)*oracle.percentile/100] + } + if price.Cmp(oracle.maxPrice) > 0 { + price = new(big.Int).Set(oracle.maxPrice) + } + oracle.cacheLock.Lock() + oracle.lastHead = headHash + oracle.lastPrice = price + oracle.cacheLock.Unlock() + + return new(big.Int).Set(price), nil +} + +type results struct { + values []*big.Int + err error +} + +// getBlockValues calculates the lowest transaction gas price in a given block +// and sends it to the result channel. If the block is empty or all transactions +// are sent by the miner itself(it doesn't make any sense to include this kind of +// transaction prices for sampling), nil gasprice is returned. +func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) { + block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) + if block == nil { + select { + case result <- results{nil, err}: + case <-quit: + } + return + } + signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number(), block.Time()) + + // Sort the transaction by effective tip in ascending sort. + txs := block.Transactions() + sortedTxs := make([]*types.Transaction, len(txs)) + copy(sortedTxs, txs) + baseFee := block.BaseFee() + slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int { + // It's okay to discard the error because a tx would never be + // accepted into a block with an invalid effective tip. + tip1, _ := a.EffectiveGasTip(baseFee) + tip2, _ := b.EffectiveGasTip(baseFee) + return tip1.Cmp(tip2) + }) + + var prices []*big.Int + for _, tx := range sortedTxs { + tip, _ := tx.EffectiveGasTip(baseFee) + if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 { + continue + } + sender, err := types.Sender(signer, tx) + if err == nil && sender != block.Coinbase() { + prices = append(prices, tip) + if len(prices) >= limit { + break + } + } + } + select { + case result <- results{prices, nil}: + case <-quit: + } +} diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go new file mode 100644 index 0000000..b22e756 --- /dev/null +++ b/eth/gasprice/gasprice_test.go @@ -0,0 +1,264 @@ +// 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 gasprice + +import ( + "context" + "crypto/sha256" + "fmt" + "math" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/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/crypto/kzg4844" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/holiman/uint256" +) + +const testHead = 32 + +type testBackend struct { + chain *core.BlockChain + pending bool // pending block available +} + +func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + if number > testHead { + return nil, nil + } + if number == rpc.EarliestBlockNumber { + number = 0 + } + if number == rpc.FinalizedBlockNumber { + return b.chain.CurrentFinalBlock(), nil + } + if number == rpc.SafeBlockNumber { + return b.chain.CurrentSafeBlock(), nil + } + if number == rpc.LatestBlockNumber { + number = testHead + } + if number == rpc.PendingBlockNumber { + if b.pending { + number = testHead + 1 + } else { + return nil, nil + } + } + return b.chain.GetHeaderByNumber(uint64(number)), nil +} + +func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number > testHead { + return nil, nil + } + if number == rpc.EarliestBlockNumber { + number = 0 + } + if number == rpc.FinalizedBlockNumber { + number = rpc.BlockNumber(b.chain.CurrentFinalBlock().Number.Uint64()) + } + if number == rpc.SafeBlockNumber { + number = rpc.BlockNumber(b.chain.CurrentSafeBlock().Number.Uint64()) + } + if number == rpc.LatestBlockNumber { + number = testHead + } + if number == rpc.PendingBlockNumber { + if b.pending { + number = testHead + 1 + } else { + return nil, nil + } + } + return b.chain.GetBlockByNumber(uint64(number)), nil +} + +func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + return b.chain.GetReceiptsByHash(hash), nil +} + +func (b *testBackend) Pending() (*types.Block, types.Receipts, *state.StateDB) { + if b.pending { + block := b.chain.GetBlockByNumber(testHead + 1) + state, _ := b.chain.StateAt(block.Root()) + return block, b.chain.GetReceiptsByHash(block.Hash()), state + } + return nil, nil, nil +} + +func (b *testBackend) ChainConfig() *params.ChainConfig { + return b.chain.Config() +} + +func (b *testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return nil +} + +func (b *testBackend) teardown() { + b.chain.Stop() +} + +// newTestBackend creates a test backend. OBS: don't forget to invoke tearDown +// after use, otherwise the blockchain instance will mem-leak via goroutines. +func newTestBackend(t *testing.T, londonBlock *big.Int, cancunBlock *big.Int, pending bool) *testBackend { + if londonBlock != nil && cancunBlock != nil && londonBlock.Cmp(cancunBlock) == 1 { + panic("cannot define test backend with cancun before london") + } + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + config = *params.TestChainConfig // needs copy because it is modified below + gspec = &core.Genesis{ + Config: &config, + Alloc: types.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, + } + signer = types.LatestSigner(gspec.Config) + + // Compute empty blob hash. + emptyBlob = kzg4844.Blob{} + emptyBlobCommit, _ = kzg4844.BlobToCommitment(&emptyBlob) + emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) + ) + config.LondonBlock = londonBlock + config.ArrowGlacierBlock = londonBlock + config.GrayGlacierBlock = londonBlock + var engine consensus.Engine = beacon.New(ethash.NewFaker()) + td := params.GenesisDifficulty.Uint64() + + if cancunBlock != nil { + ts := gspec.Timestamp + cancunBlock.Uint64()*10 // fixed 10 sec block time in blockgen + config.ShanghaiTime = &ts + config.CancunTime = &ts + signer = types.LatestSigner(gspec.Config) + } + + // Generate testing blocks + db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, testHead+1, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.Address{1}) + + var txdata types.TxData + if londonBlock != nil && b.Number().Cmp(londonBlock) >= 0 { + txdata = &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: b.TxNonce(addr), + To: &common.Address{}, + Gas: 30000, + GasFeeCap: big.NewInt(100 * params.GWei), + GasTipCap: big.NewInt(int64(i+1) * params.GWei), + Data: []byte{}, + } + } else { + txdata = &types.LegacyTx{ + Nonce: b.TxNonce(addr), + To: &common.Address{}, + Gas: 21000, + GasPrice: big.NewInt(int64(i+1) * params.GWei), + Value: big.NewInt(100), + Data: []byte{}, + } + } + b.AddTx(types.MustSignNewTx(key, signer, txdata)) + + if cancunBlock != nil && b.Number().Cmp(cancunBlock) >= 0 { + b.SetPoS() + + // put more blobs in each new block + for j := 0; j < i && j < 6; j++ { + blobTx := &types.BlobTx{ + ChainID: uint256.MustFromBig(gspec.Config.ChainID), + Nonce: b.TxNonce(addr), + To: common.Address{}, + Gas: 30000, + GasFeeCap: uint256.NewInt(100 * params.GWei), + GasTipCap: uint256.NewInt(uint64(i+1) * params.GWei), + Data: []byte{}, + BlobFeeCap: uint256.NewInt(1), + BlobHashes: []common.Hash{emptyBlobVHash}, + Value: uint256.NewInt(100), + Sidecar: nil, + } + b.AddTx(types.MustSignNewTx(key, signer, blobTx)) + } + } + td += b.Difficulty().Uint64() + }) + // Construct testing chain + gspec.Config.TerminalTotalDifficulty = new(big.Int).SetUint64(td) + chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to create local chain, %v", err) + } + if i, err := chain.InsertChain(blocks); err != nil { + panic(fmt.Errorf("error inserting block %d: %w", i, err)) + } + chain.SetFinalized(chain.GetBlockByNumber(25).Header()) + chain.SetSafe(chain.GetBlockByNumber(25).Header()) + + return &testBackend{chain: chain, pending: pending} +} + +func (b *testBackend) CurrentHeader() *types.Header { + return b.chain.CurrentHeader() +} + +func (b *testBackend) GetBlockByNumber(number uint64) *types.Block { + return b.chain.GetBlockByNumber(number) +} + +func TestSuggestTipCap(t *testing.T) { + config := Config{ + Blocks: 3, + Percentile: 60, + Default: big.NewInt(params.GWei), + } + var cases = []struct { + fork *big.Int // London fork number + expect *big.Int // Expected gasprice suggestion + }{ + {nil, big.NewInt(params.GWei * int64(30))}, + {big.NewInt(0), big.NewInt(params.GWei * int64(30))}, // Fork point in genesis + {big.NewInt(1), big.NewInt(params.GWei * int64(30))}, // Fork point in first block + {big.NewInt(32), big.NewInt(params.GWei * int64(30))}, // Fork point in last block + {big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future + } + for _, c := range cases { + backend := newTestBackend(t, c.fork, nil, false) + oracle := NewOracle(backend, config) + + // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G + got, err := oracle.SuggestTipCap(context.Background()) + backend.teardown() + if err != nil { + t.Fatalf("Failed to retrieve recommended gas price: %v", err) + } + if got.Cmp(c.expect) != 0 { + t.Fatalf("Gas price mismatch, want %d, got %d", c.expect, got) + } + } +} diff --git a/eth/handler.go b/eth/handler.go new file mode 100644 index 0000000..d511758 --- /dev/null +++ b/eth/handler.go @@ -0,0 +1,564 @@ +// 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 eth + +import ( + "errors" + "math" + "math/big" + "sync" + "sync/atomic" + "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/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/eth/fetcher" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/triedb/pathdb" +) + +const ( + // txChanSize is the size of channel listening to NewTxsEvent. + // The number is referenced from the size of tx pool. + txChanSize = 4096 + + // txMaxBroadcastSize is the max size of a transaction that will be broadcasted. + // All transactions with a higher size will be announced and need to be fetched + // by the peer. + txMaxBroadcastSize = 4096 +) + +var syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge + +// txPool defines the methods needed from a transaction pool implementation to +// support all the operations needed by the Ethereum chain protocols. +type txPool interface { + // Has returns an indicator whether txpool has a transaction + // cached with the given hash. + Has(hash common.Hash) bool + + // Get retrieves the transaction from local txpool with given + // tx hash. + Get(hash common.Hash) *types.Transaction + + // Add should add the given transactions to the pool. + Add(txs []*types.Transaction, local bool, sync bool) []error + + // Pending should return pending transactions. + // The slice should be modifiable by the caller. + Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction + + // SubscribeTransactions subscribes to new transaction events. The subscriber + // can decide whether to receive notifications only for newly seen transactions + // or also for reorged out ones. + SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription +} + +// handlerConfig is the collection of initialization parameters to create a full +// node network handler. +type handlerConfig struct { + NodeID enode.ID // P2P node ID used for tx propagation topology + Database ethdb.Database // Database for direct sync insertions + Chain *core.BlockChain // Blockchain to serve data from + TxPool txPool // Transaction pool to propagate from + 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` + RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges +} + +type handler struct { + nodeID enode.ID + 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) + synced atomic.Bool // Flag whether we're considered synchronised (enables transaction processing) + + database ethdb.Database + txpool txPool + chain *core.BlockChain + maxPeers int + + downloader *downloader.Downloader + txFetcher *fetcher.TxFetcher + peers *peerSet + + eventMux *event.TypeMux + txsCh chan core.NewTxsEvent + txsSub event.Subscription + + requiredBlocks map[uint64]common.Hash + + // channels for fetcher, syncer, txsyncLoop + quitSync chan struct{} + + wg sync.WaitGroup + + handlerStartCh chan struct{} + handlerDoneCh chan struct{} +} + +// newHandler returns a handler for all Ethereum chain management protocol. +func newHandler(config *handlerConfig) (*handler, error) { + // Create the protocol manager with the base fields + if config.EventMux == nil { + config.EventMux = new(event.TypeMux) // Nicety initialization for tests + } + h := &handler{ + nodeID: config.NodeID, + networkID: config.Network, + forkFilter: forkid.NewFilter(config.Chain), + eventMux: config.EventMux, + database: config.Database, + txpool: config.TxPool, + chain: config.Chain, + peers: newPeerSet(), + requiredBlocks: config.RequiredBlocks, + quitSync: make(chan struct{}), + handlerDoneCh: make(chan struct{}), + handlerStartCh: make(chan struct{}), + } + if config.Sync == downloader.FullSync { + // The database seems empty as the current block is the genesis. Yet the snap + // block is ahead, so snap sync was enabled for this node at a certain point. + // The scenarios where this can happen is + // * if the user manually (or via a bad block) rolled back a snap sync node + // below the sync point. + // * the last snap sync is not finished while user specifies a full sync this + // time. But we don't have any recent state for full sync. + // In these cases however it's safe to reenable snap sync. + 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", "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 { + 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", "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 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, h.removePeer, h.enableSyncedFeatures) + + fetchTx := func(peer string, hashes []common.Hash) error { + p := h.peers.peer(peer) + if p == nil { + return errors.New("unknown peer") + } + return p.RequestTxs(hashes) + } + addTxs := func(txs []*types.Transaction) []error { + return h.txpool.Add(txs, false, false) + } + h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx, h.removePeer) + return h, nil +} + +// protoTracker tracks the number of active protocol handlers. +func (h *handler) protoTracker() { + defer h.wg.Done() + var active int + for { + select { + case <-h.handlerStartCh: + active++ + case <-h.handlerDoneCh: + active-- + case <-h.quitSync: + // Wait for all active handlers to finish. + for ; active > 0; active-- { + <-h.handlerDoneCh + } + return + } + } +} + +// incHandlers signals to increment the number of active handlers if not +// quitting. +func (h *handler) incHandlers() bool { + select { + case h.handlerStartCh <- struct{}{}: + return true + case <-h.quitSync: + return false + } +} + +// decHandlers signals to decrement the number of active handlers. +func (h *handler) decHandlers() { + h.handlerDoneCh <- struct{}{} +} + +// runEthPeer registers an eth peer into the joint eth/snap peerset, adds it to +// various subsystems and starts handling messages. +func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { + if !h.incHandlers() { + return p2p.DiscQuitting + } + defer h.decHandlers() + + // If the peer has a `snap` extension, wait for it to connect so we can have + // a uniform initialization/teardown mechanism + snap, err := h.peers.waitSnapExtension(peer) + if err != nil { + peer.Log().Error("Snapshot extension barrier failed", "err", err) + return err + } + + // Execute the Ethereum handshake + var ( + genesis = h.chain.Genesis() + head = h.chain.CurrentHeader() + hash = head.Hash() + number = head.Number.Uint64() + td = h.chain.GetTd(hash, number) + ) + 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 + } + reject := false // reserved peer slots + if h.snapSync.Load() { + if snap == nil { + // If we are running snap-sync, we want to reserve roughly half the peer + // slots for peers supporting the snap protocol. + // The logic here is; we only allow up to 5 more non-snap peers than snap-peers. + if all, snp := h.peers.len(), h.peers.snapLen(); all-snp > snp+5 { + reject = true + } + } + } + // Ignore maxPeers if this is a trusted peer + if !peer.Peer.Info().Network.Trusted { + if reject || h.peers.len() >= h.maxPeers { + return p2p.DiscTooManyPeers + } + } + peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) + + // Register the peer locally + if err := h.peers.registerPeer(peer, snap); err != nil { + peer.Log().Error("Ethereum peer registration failed", "err", err) + return err + } + defer h.unregisterPeer(peer.ID()) + + p := h.peers.peer(peer.ID()) + if p == nil { + return errors.New("peer dropped during handling") + } + // Register the peer in the downloader. If the downloader considers it banned, we disconnect + if err := h.downloader.RegisterPeer(peer.ID(), peer.Version(), peer); err != nil { + peer.Log().Error("Failed to register peer in eth syncer", "err", err) + return err + } + if snap != nil { + if err := h.downloader.SnapSyncer.Register(snap); err != nil { + peer.Log().Error("Failed to register peer in snap syncer", "err", err) + return err + } + } + // Propagate existing transactions. new transactions appearing + // after this will be sent via broadcasts. + h.syncTransactions(peer) + + // Create a notification channel for pending requests if the peer goes down + dead := make(chan struct{}) + defer close(dead) + + // If we have any explicit peer required block hashes, request them + for number, hash := range h.requiredBlocks { + resCh := make(chan *eth.Response) + + req, err := peer.RequestHeadersByNumber(number, 1, 0, false, resCh) + if err != nil { + return err + } + go func(number uint64, hash common.Hash, req *eth.Request) { + // Ensure the request gets cancelled in case of error/drop + defer req.Close() + + timeout := time.NewTimer(syncChallengeTimeout) + defer timeout.Stop() + + select { + case res := <-resCh: + 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 + res.Done <- nil + return + } + // Validate the header and either drop the peer or continue + if len(headers) > 1 { + res.Done <- errors.New("too many headers in required block response") + return + } + if headers[0].Number.Uint64() != number || headers[0].Hash() != hash { + peer.Log().Info("Required block mismatch, dropping peer", "number", number, "hash", headers[0].Hash(), "want", hash) + res.Done <- errors.New("required block mismatch") + return + } + peer.Log().Debug("Peer required block verified", "number", number, "hash", hash) + res.Done <- nil + case <-timeout.C: + peer.Log().Warn("Required block challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name()) + h.removePeer(peer.ID()) + } + }(number, hash, req) + } + // Handle incoming messages until the connection is torn down + return handler(peer) +} + +// runSnapExtension registers a `snap` peer into the joint eth/snap peerset and +// starts handling inbound messages. As `snap` is only a satellite protocol to +// `eth`, all subsystem registrations and lifecycle management will be done by +// the main `eth` handler to prevent strange races. +func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error { + if !h.incHandlers() { + return p2p.DiscQuitting + } + defer h.decHandlers() + + if err := h.peers.registerSnapExtension(peer); err != nil { + if metrics.Enabled { + if peer.Inbound() { + snap.IngressRegistrationErrorMeter.Mark(1) + } else { + snap.EgressRegistrationErrorMeter.Mark(1) + } + } + peer.Log().Debug("Snapshot extension registration failed", "err", err) + return err + } + return handler(peer) +} + +// removePeer requests disconnection of a peer. +func (h *handler) removePeer(id string) { + peer := h.peers.peer(id) + if peer != nil { + peer.Peer.Disconnect(p2p.DiscUselessPeer) + } +} + +// unregisterPeer removes a peer from the downloader, fetchers and main peer set. +func (h *handler) unregisterPeer(id string) { + // Create a custom logger to avoid printing the entire id + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:8]) + } + // Abort if the peer does not exist + peer := h.peers.peer(id) + if peer == nil { + logger.Error("Ethereum peer removal failed", "err", errPeerNotRegistered) + return + } + // Remove the `eth` peer if it exists + logger.Debug("Removing Ethereum peer", "snap", peer.snapExt != nil) + + // Remove the `snap` extension if it exists + if peer.snapExt != nil { + h.downloader.SnapSyncer.Unregister(id) + } + h.downloader.UnregisterPeer(id) + h.txFetcher.Drop(id) + + if err := h.peers.unregisterPeer(id); err != nil { + logger.Error("Ethereum peer removal failed", "err", err) + } +} + +func (h *handler) Start(maxPeers int) { + h.maxPeers = maxPeers + + // 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.SubscribeTransactions(h.txsCh, false) + go h.txBroadcastLoop() + + // start sync handlers + h.txFetcher.Start() + + // start peer handler tracker + h.wg.Add(1) + go h.protoTracker() +} + +func (h *handler) Stop() { + h.txsSub.Unsubscribe() // quits txBroadcastLoop + h.txFetcher.Stop() + h.downloader.Terminate() + + // Quit chainSync and txsync64. + // After this is done, no new peers will be accepted. + close(h.quitSync) + + // Disconnect existing sessions. + // This also closes the gate for any new registrations on the peer set. + // sessions which are already established but not added to h.peers yet + // will exit when they try to register. + h.peers.close() + h.wg.Wait() + + log.Info("Ethereum protocol stopped") +} + +// BroadcastTransactions will propagate a batch of transactions +// - 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 ( + 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) + annCount int // Number of transactions announced across all peers (duplicates included) + + 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 + direct := big.NewInt(int64(math.Sqrt(float64(h.peers.len())))) // Approximate number of peers to broadcast to + if direct.BitLen() == 0 { + direct = big.NewInt(1) + } + total := new(big.Int).Exp(direct, big.NewInt(2), nil) // Stabilise total peer count a bit based on sqrt peers + + var ( + signer = types.LatestSignerForChainID(h.chain.Config().ChainID) // Don't care about chain status, we just need *a* sender + hasher = crypto.NewKeccakState() + hash = make([]byte, 32) + ) + for _, tx := range txs { + var maybeDirect bool + switch { + case tx.Type() == types.BlobTxType: + blobTxs++ + case tx.Size() > txMaxBroadcastSize: + largeTxs++ + default: + maybeDirect = true + } + // Send the transaction (if it's small enough) directly to a subset of + // the peers that have not received it yet, ensuring that the flow of + // transactions is grouped by account to (try and) avoid nonce gaps. + // + // To do this, we hash the local enode IW with together with a peer's + // enode ID together with the transaction sender and broadcast if + // `sha(self, peer, sender) mod peers < sqrt(peers)`. + for _, peer := range h.peers.peersWithoutTransaction(tx.Hash()) { + var broadcast bool + if maybeDirect { + hasher.Reset() + hasher.Write(h.nodeID.Bytes()) + hasher.Write(peer.Node().ID().Bytes()) + + from, _ := types.Sender(signer, tx) // Ignore error, we only use the addr as a propagation target splitter + hasher.Write(from.Bytes()) + + hasher.Read(hash) + if new(big.Int).Mod(new(big.Int).SetBytes(hash), total).Cmp(direct) < 0 { + broadcast = true + } + } + if broadcast { + txset[peer] = append(txset[peer], tx.Hash()) + } else { + annos[peer] = append(annos[peer], tx.Hash()) + } + } + } + for peer, hashes := range txset { + directCount += len(hashes) + peer.AsyncSendTransactions(hashes) + } + for peer, hashes := range annos { + annCount += len(hashes) + peer.AsyncSendPooledTransactionHashes(hashes) + } + log.Debug("Distributed transactions", "plaintxs", len(txs)-blobTxs-largeTxs, "blobtxs", blobTxs, "largetxs", largeTxs, + "bcastpeers", len(txset), "bcastcount", directCount, "annpeers", len(annos), "anncount", annCount) +} + +// txBroadcastLoop announces new transactions to connected peers. +func (h *handler) txBroadcastLoop() { + defer h.wg.Done() + for { + select { + case event := <-h.txsCh: + h.BroadcastTransactions(event.Txs) + case <-h.txsSub.Err(): + return + } + } +} + +// 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 new file mode 100644 index 0000000..b2cd52a --- /dev/null +++ b/eth/handler_eth.go @@ -0,0 +1,77 @@ +// 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 eth + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// ethHandler implements the eth.Backend interface to handle the various network +// packets that are sent as replies or broadcasts. +type ethHandler handler + +func (h *ethHandler) Chain() *core.BlockChain { return h.chain } +func (h *ethHandler) TxPool() eth.TxPool { return h.txpool } + +// RunPeer is invoked when a peer joins on the `eth` protocol. +func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error { + return (*handler)(h).runEthPeer(peer, hand) +} + +// PeerInfo retrieves all known `eth` information about a peer. +func (h *ethHandler) PeerInfo(id enode.ID) interface{} { + if p := h.peers.peer(id.String()); p != nil { + return p.info() + } + return nil +} + +// 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.synced.Load() +} + +// Handle is invoked from a peer's message handler when it receives a new remote +// message that the handler couldn't consume and serve itself. +func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { + // Consume any broadcasts and announces, forwarding the rest to the downloader + switch packet := packet.(type) { + case *eth.NewPooledTransactionHashesPacket: + 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.PooledTransactionsResponse: + return h.txFetcher.Enqueue(peer.ID(), *packet, true) + + default: + return fmt.Errorf("unexpected eth packet type: %T", packet) + } +} diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go new file mode 100644 index 0000000..a38059c --- /dev/null +++ b/eth/handler_eth_test.go @@ -0,0 +1,440 @@ +// 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 eth + +import ( + "fmt" + "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/forkid" + "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/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" +) + +// testEthHandler is a mock event handler to listen for inbound network requests +// on the `eth` protocol and convert them into a more easily testable form. +type testEthHandler struct { + blockBroadcasts event.Feed + txAnnounces event.Feed + txBroadcasts event.Feed +} + +func (h *testEthHandler) Chain() *core.BlockChain { panic("no backing chain") } +func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") } +func (h *testEthHandler) AcceptTxs() bool { return true } +func (h *testEthHandler) RunPeer(*eth.Peer, eth.Handler) error { panic("not used in tests") } +func (h *testEthHandler) PeerInfo(enode.ID) interface{} { panic("not used in tests") } + +func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { + switch packet := packet.(type) { + case *eth.NewBlockPacket: + h.blockBroadcasts.Send(packet.Block) + return nil + + case *eth.NewPooledTransactionHashesPacket: + h.txAnnounces.Send(packet.Hashes) + return nil + + case *eth.TransactionsPacket: + h.txBroadcasts.Send(([]*types.Transaction)(*packet)) + return nil + + case *eth.PooledTransactionsResponse: + h.txBroadcasts.Send(([]*types.Transaction)(*packet)) + return nil + + default: + panic(fmt.Sprintf("unexpected eth packet type in tests: %T", packet)) + } +} + +// Tests that peers are correctly accepted (or rejected) based on the advertised +// fork IDs in the protocol handshake. +func TestForkIDSplit68(t *testing.T) { testForkIDSplit(t, eth.ETH68) } + +func testForkIDSplit(t *testing.T, protocol uint) { + t.Parallel() + + var ( + engine = ethash.NewFaker() + + configNoFork = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)} + configProFork = ¶ms.ChainConfig{ + HomesteadBlock: big.NewInt(1), + EIP150Block: big.NewInt(2), + EIP155Block: big.NewInt(2), + EIP158Block: big.NewInt(2), + ByzantiumBlock: big.NewInt(3), + } + dbNoFork = rawdb.NewMemoryDatabase() + dbProFork = rawdb.NewMemoryDatabase() + + gspecNoFork = &core.Genesis{Config: configNoFork} + gspecProFork = &core.Genesis{Config: configProFork} + + chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, gspecNoFork, nil, engine, vm.Config{}, nil, nil) + chainProFork, _ = core.NewBlockChain(dbProFork, nil, gspecProFork, nil, engine, vm.Config{}, nil, nil) + + _, blocksNoFork, _ = core.GenerateChainWithGenesis(gspecNoFork, engine, 2, nil) + _, blocksProFork, _ = core.GenerateChainWithGenesis(gspecProFork, engine, 2, nil) + + ethNoFork, _ = newHandler(&handlerConfig{ + Database: dbNoFork, + Chain: chainNoFork, + TxPool: newTestTxPool(), + Network: 1, + Sync: downloader.FullSync, + BloomCache: 1, + }) + ethProFork, _ = newHandler(&handlerConfig{ + Database: dbProFork, + Chain: chainProFork, + TxPool: newTestTxPool(), + Network: 1, + Sync: downloader.FullSync, + BloomCache: 1, + }) + ) + ethNoFork.Start(1000) + ethProFork.Start(1000) + + // Clean up everything after ourselves + defer chainNoFork.Stop() + defer chainProFork.Stop() + + defer ethNoFork.Stop() + defer ethProFork.Stop() + + // Both nodes should allow the other to connect (same genesis, next fork is the same) + p2pNoFork, p2pProFork := p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pNoFork), p2pNoFork, nil) + peerProFork := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pProFork), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc := make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + t.Fatalf("frontier nofork <-> profork failed: %v", err) + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("frontier nofork <-> profork handler timeout") + } + } + // Progress into Homestead. Fork's match, so we don't care what the future holds + chainNoFork.InsertChain(blocksNoFork[:1]) + chainProFork.InsertChain(blocksProFork[:1]) + + p2pNoFork, p2pProFork = p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) + peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc = make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + t.Fatalf("homestead nofork <-> profork failed: %v", err) + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("homestead nofork <-> profork handler timeout") + } + } + // Progress into Spurious. Forks mismatch, signalling differing chains, reject + chainNoFork.InsertChain(blocksNoFork[1:2]) + chainProFork.InsertChain(blocksProFork[1:2]) + + p2pNoFork, p2pProFork = p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork = eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pNoFork), p2pNoFork, nil) + peerProFork = eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pProFork), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc = make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + var successes int + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err == nil { + successes++ + if successes == 2 { // Only one side disconnects + t.Fatalf("fork ID rejection didn't happen") + } + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("split peers not rejected") + } + } +} + +// Tests that received transactions are added to the local pool. +func TestRecvTransactions68(t *testing.T) { testRecvTransactions(t, eth.ETH68) } + +func testRecvTransactions(t *testing.T, protocol uint) { + t.Parallel() + + // Create a message handler, configure it to accept transactions and watch them + handler := newTestHandler() + defer handler.close() + + handler.handler.synced.Store(true) // mark synced to accept transactions + + txs := make(chan core.NewTxsEvent) + sub := handler.txpool.SubscribeTransactions(txs, false) + defer sub.Unsubscribe() + + // Create a source peer to send messages through and a sink handler to receive them + p2pSrc, p2pSink := p2p.MsgPipe() + defer p2pSrc.Close() + defer p2pSink.Close() + + src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, handler.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, handler.txpool) + defer src.Close() + defer sink.Close() + + go handler.handler.runEthPeer(sink, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + // Run the handshake locally to avoid spinning up a source handler + var ( + genesis = handler.chain.Genesis() + head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.Number.Uint64()) + ) + if err := src.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // Send the transaction to the sink and verify that it's added to the tx pool + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + if err := src.SendTransactions([]*types.Transaction{tx}); err != nil { + t.Fatalf("failed to send transaction: %v", err) + } + select { + case event := <-txs: + if len(event.Txs) != 1 { + t.Errorf("wrong number of added transactions: got %d, want 1", len(event.Txs)) + } else if event.Txs[0].Hash() != tx.Hash() { + t.Errorf("added wrong tx hash: got %v, want %v", event.Txs[0].Hash(), tx.Hash()) + } + case <-time.After(2 * time.Second): + t.Errorf("no NewTxsEvent received within 2 seconds") + } +} + +// This test checks that pending transactions are sent. +func TestSendTransactions68(t *testing.T) { testSendTransactions(t, eth.ETH68) } + +func testSendTransactions(t *testing.T, protocol uint) { + t.Parallel() + + // Create a message handler and fill the pool with big transactions + handler := newTestHandler() + defer handler.close() + + 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] = 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) + + // Create a source handler to send messages through and a sink peer to receive them + p2pSrc, p2pSink := p2p.MsgPipe() + defer p2pSrc.Close() + defer p2pSink.Close() + + src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, handler.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, handler.txpool) + defer src.Close() + defer sink.Close() + + go handler.handler.runEthPeer(src, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + // Run the handshake locally to avoid spinning up a source handler + var ( + genesis = handler.chain.Genesis() + head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.Number.Uint64()) + ) + if err := sink.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // After the handshake completes, the source handler should stream the sink + // the transactions, subscribe to all inbound network events + backend := new(testEthHandler) + + anns := make(chan []common.Hash) + annSub := backend.txAnnounces.Subscribe(anns) + defer annSub.Unsubscribe() + + bcasts := make(chan []*types.Transaction) + bcastSub := backend.txBroadcasts.Subscribe(bcasts) + defer bcastSub.Unsubscribe() + + go eth.Handle(backend, sink) + + // Make sure we get all the transactions on the correct channels + seen := make(map[common.Hash]struct{}) + for len(seen) < len(insert) { + switch protocol { + case 68: + select { + case hashes := <-anns: + for _, hash := range hashes { + if _, ok := seen[hash]; ok { + t.Errorf("duplicate transaction announced: %x", hash) + } + seen[hash] = struct{}{} + } + case <-bcasts: + t.Errorf("initial tx broadcast received on post eth/66") + } + + default: + panic("unsupported protocol, please extend test") + } + } + for _, tx := range insert { + 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 TestTransactionPropagation68(t *testing.T) { testTransactionPropagation(t, eth.ETH68) } + +func testTransactionPropagation(t *testing.T, protocol uint) { + t.Parallel() + + // Create a source handler to send transactions from and a number of sinks + // to receive them. We need multiple sinks since a one-to-one peering would + // broadcast all transactions without announcement. + source := newTestHandler() + source.handler.snapSync.Store(false) // Avoid requiring snap, otherwise some will be dropped below + defer source.close() + + sinks := make([]*testHandler, 10) + for i := 0; i < len(sinks); i++ { + sinks[i] = newTestHandler() + defer sinks[i].close() + + 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 { + sink := sink // Closure for goroutine below + + sourcePipe, sinkPipe := p2p.MsgPipe() + defer sourcePipe.Close() + defer sinkPipe.Close() + + sourcePeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{byte(i + 1)}, "", nil, sourcePipe), sourcePipe, source.txpool) + sinkPeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, sink.txpool) + defer sourcePeer.Close() + defer sinkPeer.Close() + + go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(source.handler), peer) + }) + go sink.handler.runEthPeer(sinkPeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(sink.handler), peer) + }) + } + // Subscribe to all the transaction pools + txChs := make([]chan core.NewTxsEvent, len(sinks)) + for i := 0; i < len(sinks); i++ { + txChs[i] = make(chan core.NewTxsEvent, 1024) + + 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([]*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] = tx + } + source.txpool.Add(txs, false, false) + + // Iterate through all the sinks and ensure they all got the transactions + for i := range sinks { + for arrived, timeout := 0, false; arrived < len(txs) && !timeout; { + select { + case event := <-txChs[i]: + arrived += len(event.Txs) + case <-time.After(2 * time.Second): + t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs)) + timeout = true + } + } + } +} diff --git a/eth/handler_snap.go b/eth/handler_snap.go new file mode 100644 index 0000000..767416f --- /dev/null +++ b/eth/handler_snap.go @@ -0,0 +1,50 @@ +// 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 eth + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// snapHandler implements the snap.Backend interface to handle the various network +// packets that are sent as replies or broadcasts. +type snapHandler handler + +func (h *snapHandler) Chain() *core.BlockChain { return h.chain } + +// RunPeer is invoked when a peer joins on the `snap` protocol. +func (h *snapHandler) RunPeer(peer *snap.Peer, hand snap.Handler) error { + return (*handler)(h).runSnapExtension(peer, hand) +} + +// PeerInfo retrieves all known `snap` information about a peer. +func (h *snapHandler) PeerInfo(id enode.ID) interface{} { + if p := h.peers.peer(id.String()); p != nil { + if p.snapExt != nil { + return p.snapExt.info() + } + } + return nil +} + +// Handle is invoked from a peer's message handler when it receives a new remote +// message that the handler couldn't consume and serve itself. +func (h *snapHandler) Handle(peer *snap.Peer, packet snap.Packet) error { + return h.downloader.DeliverSnapPacket(peer, packet) +} diff --git a/eth/handler_test.go b/eth/handler_test.go new file mode 100644 index 0000000..bcc8ea3 --- /dev/null +++ b/eth/handler_test.go @@ -0,0 +1,184 @@ +// 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 eth + +import ( + "math/big" + "sort" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +// testTxPool is a mock transaction pool that blindly accepts all transactions. +// Its goal is to get around setting up a valid statedb for the balance and nonce +// checks. +type testTxPool struct { + pool map[common.Hash]*types.Transaction // Hash map of collected transactions + + txFeed event.Feed // Notification feed to allow waiting for inclusion + lock sync.RWMutex // Protects the transaction pool +} + +// newTestTxPool creates a mock transaction pool. +func newTestTxPool() *testTxPool { + return &testTxPool{ + pool: make(map[common.Hash]*types.Transaction), + } +} + +// Has returns an indicator whether txpool has a transaction +// cached with the given hash. +func (p *testTxPool) Has(hash common.Hash) bool { + p.lock.Lock() + defer p.lock.Unlock() + + return p.pool[hash] != nil +} + +// Get retrieves the transaction from local txpool with given +// tx hash. +func (p *testTxPool) Get(hash common.Hash) *types.Transaction { + p.lock.Lock() + defer p.lock.Unlock() + 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 []*types.Transaction, local bool, sync bool) []error { + p.lock.Lock() + defer p.lock.Unlock() + + for _, tx := range txs { + p.pool[tx.Hash()] = tx + } + p.txFeed.Send(core.NewTxsEvent{Txs: txs}) + return make([]error, len(txs)) +} + +// Pending returns all the transactions known to the pool +func (p *testTxPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction { + p.lock.RLock() + defer p.lock.RUnlock() + + batches := make(map[common.Address][]*types.Transaction) + for _, tx := range p.pool { + from, _ := types.Sender(types.HomesteadSigner{}, tx) + batches[from] = append(batches[from], tx) + } + for _, batch := range batches { + sort.Sort(types.TxByNonce(batch)) + } + pending := make(map[common.Address][]*txpool.LazyTransaction) + for addr, batch := range batches { + for _, tx := range batch { + pending[addr] = append(pending[addr], &txpool.LazyTransaction{ + Hash: tx.Hash(), + Tx: tx, + Time: tx.Time(), + GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()), + GasTipCap: uint256.MustFromBig(tx.GasTipCap()), + Gas: tx.Gas(), + BlobGas: tx.BlobGas(), + }) + } + } + return pending +} + +// SubscribeTransactions should return an event subscription of NewTxsEvent and +// send events to the given channel. +func (p *testTxPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { + return p.txFeed.Subscribe(ch) +} + +// testHandler is a live implementation of the Ethereum protocol handler, just +// preinitialized with some sane testing defaults and the transaction pool mocked +// out. +type testHandler struct { + db ethdb.Database + chain *core.BlockChain + txpool *testTxPool + handler *handler +} + +// newTestHandler creates a new handler for testing purposes with no blocks. +func newTestHandler() *testHandler { + return newTestHandlerWithBlocks(0) +} + +// newTestHandlerWithBlocks creates a new handler for testing purposes, with a +// given number of initial blocks. +func newTestHandlerWithBlocks(blocks int) *testHandler { + // Create a database pre-initialize with a genesis block + db := rawdb.NewMemoryDatabase() + gspec := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, + } + chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + + _, bs, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), blocks, nil) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + txpool := newTestTxPool() + + handler, _ := newHandler(&handlerConfig{ + Database: db, + Chain: chain, + TxPool: txpool, + Network: 1, + Sync: downloader.SnapSync, + BloomCache: 1, + }) + handler.Start(1000) + + return &testHandler{ + db: db, + chain: chain, + txpool: txpool, + handler: handler, + } +} + +// close tears down the handler and all its internal constructs. +func (b *testHandler) close() { + b.handler.Stop() + b.chain.Stop() +} diff --git a/eth/peer.go b/eth/peer.go new file mode 100644 index 0000000..7618777 --- /dev/null +++ b/eth/peer.go @@ -0,0 +1,59 @@ +// 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 eth + +import ( + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" +) + +// ethPeerInfo represents a short summary of the `eth` sub-protocol metadata known +// about a connected peer. +type ethPeerInfo struct { + Version uint `json:"version"` // Ethereum protocol version negotiated +} + +// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata. +type ethPeer struct { + *eth.Peer + snapExt *snapPeer // Satellite `snap` connection +} + +// info gathers and returns some `eth` protocol metadata known about a peer. +func (p *ethPeer) info() *ethPeerInfo { + return ðPeerInfo{ + Version: p.Version(), + } +} + +// snapPeerInfo represents a short summary of the `snap` sub-protocol metadata known +// about a connected peer. +type snapPeerInfo struct { + Version uint `json:"version"` // Snapshot protocol version negotiated +} + +// snapPeer is a wrapper around snap.Peer to maintain a few extra metadata. +type snapPeer struct { + *snap.Peer +} + +// info gathers and returns some `snap` protocol metadata known about a peer. +func (p *snapPeer) info() *snapPeerInfo { + return &snapPeerInfo{ + Version: p.Version(), + } +} diff --git a/eth/peerset.go b/eth/peerset.go new file mode 100644 index 0000000..6b0aff2 --- /dev/null +++ b/eth/peerset.go @@ -0,0 +1,239 @@ +// 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 eth + +import ( + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + // errPeerSetClosed is returned if a peer is attempted to be added or removed + // from the peer set after it has been terminated. + errPeerSetClosed = errors.New("peerset closed") + + // errPeerAlreadyRegistered is returned if a peer is attempted to be added + // to the peer set, but one with the same id already exists. + errPeerAlreadyRegistered = errors.New("peer already registered") + + // errPeerNotRegistered is returned if a peer is attempted to be removed from + // a peer set, but no peer with the given id exists. + errPeerNotRegistered = errors.New("peer not registered") + + // errSnapWithoutEth is returned if a peer attempts to connect only on the + // snap protocol without advertising the eth main protocol. + errSnapWithoutEth = errors.New("peer connected on snap without compatible eth support") +) + +// peerSet represents the collection of active peers currently participating in +// the `eth` protocol, with or without the `snap` extension. +type peerSet struct { + peers map[string]*ethPeer // Peers connected on the `eth` protocol + snapPeers int // Number of `snap` compatible peers for connection prioritization + + snapWait map[string]chan *snap.Peer // Peers connected on `eth` waiting for their snap extension + snapPend map[string]*snap.Peer // Peers connected on the `snap` protocol, but not yet on `eth` + + lock sync.RWMutex + closed bool + quitCh chan struct{} // Quit channel to signal termination +} + +// newPeerSet creates a new peer set to track the active participants. +func newPeerSet() *peerSet { + return &peerSet{ + peers: make(map[string]*ethPeer), + snapWait: make(map[string]chan *snap.Peer), + snapPend: make(map[string]*snap.Peer), + quitCh: make(chan struct{}), + } +} + +// registerSnapExtension unblocks an already connected `eth` peer waiting for its +// `snap` extension, or if no such peer exists, tracks the extension for the time +// being until the `eth` main protocol starts looking for it. +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 fmt.Errorf("%w: have %v", errSnapWithoutEth, peer.Caps()) + } + // Ensure nobody can double connect + ps.lock.Lock() + defer ps.lock.Unlock() + + id := peer.ID() + if _, ok := ps.peers[id]; ok { + return errPeerAlreadyRegistered // avoid connections with the same id as existing ones + } + if _, ok := ps.snapPend[id]; ok { + return errPeerAlreadyRegistered // avoid connections with the same id as pending ones + } + // Inject the peer into an `eth` counterpart is available, otherwise save for later + if wait, ok := ps.snapWait[id]; ok { + delete(ps.snapWait, id) + wait <- peer + return nil + } + ps.snapPend[id] = peer + return nil +} + +// waitSnapExtension blocks until all satellite protocols are connected and tracked +// by the peerset. +func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { + // If the peer does not support a compatible `snap`, don't wait + if !peer.RunningCap(snap.ProtocolName, snap.ProtocolVersions) { + return nil, nil + } + // Ensure nobody can double connect + ps.lock.Lock() + + id := peer.ID() + if _, ok := ps.peers[id]; ok { + ps.lock.Unlock() + return nil, errPeerAlreadyRegistered // avoid connections with the same id as existing ones + } + if _, ok := ps.snapWait[id]; ok { + ps.lock.Unlock() + return nil, errPeerAlreadyRegistered // avoid connections with the same id as pending ones + } + // If `snap` already connected, retrieve the peer from the pending set + if snap, ok := ps.snapPend[id]; ok { + delete(ps.snapPend, id) + + ps.lock.Unlock() + return snap, nil + } + // Otherwise wait for `snap` to connect concurrently + wait := make(chan *snap.Peer) + ps.snapWait[id] = wait + ps.lock.Unlock() + + select { + case p := <-wait: + return p, nil + case <-ps.quitCh: + ps.lock.Lock() + delete(ps.snapWait, id) + ps.lock.Unlock() + return nil, errPeerSetClosed + } +} + +// registerPeer injects a new `eth` peer into the working set, or returns an error +// if the peer is already known. +func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error { + // Start tracking the new peer + ps.lock.Lock() + defer ps.lock.Unlock() + + if ps.closed { + return errPeerSetClosed + } + id := peer.ID() + if _, ok := ps.peers[id]; ok { + return errPeerAlreadyRegistered + } + eth := ðPeer{ + Peer: peer, + } + if ext != nil { + eth.snapExt = &snapPeer{ext} + ps.snapPeers++ + } + ps.peers[id] = eth + return nil +} + +// unregisterPeer removes a remote peer from the active set, disabling any further +// actions to/from that particular entity. +func (ps *peerSet) unregisterPeer(id string) error { + ps.lock.Lock() + defer ps.lock.Unlock() + + peer, ok := ps.peers[id] + if !ok { + return errPeerNotRegistered + } + delete(ps.peers, id) + if peer.snapExt != nil { + ps.snapPeers-- + } + return nil +} + +// peer retrieves the registered peer with the given id. +func (ps *peerSet) peer(id string) *ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return ps.peers[id] +} + +// peersWithoutTransaction retrieves a list of peers that do not have a given +// transaction in their set of known hashes. +func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + list := make([]*ethPeer, 0, len(ps.peers)) + for _, p := range ps.peers { + if !p.KnownTransaction(hash) { + list = append(list, p) + } + } + return list +} + +// len returns if the current number of `eth` peers in the set. Since the `snap` +// peers are tied to the existence of an `eth` connection, that will always be a +// subset of `eth`. +func (ps *peerSet) len() int { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return len(ps.peers) +} + +// snapLen returns if the current number of `snap` peers in the set. +func (ps *peerSet) snapLen() int { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return ps.snapPeers +} + +// close disconnects all peers. +func (ps *peerSet) close() { + ps.lock.Lock() + defer ps.lock.Unlock() + + for _, p := range ps.peers { + p.Disconnect(p2p.DiscQuitting) + } + if !ps.closed { + close(ps.quitCh) + } + ps.closed = true +} diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go new file mode 100644 index 0000000..f0ed1d6 --- /dev/null +++ b/eth/protocols/eth/broadcast.go @@ -0,0 +1,166 @@ +// 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 eth + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +const ( + // This is the target size for the packs of transactions or announcements. A + // pack can get larger than this if a single transactions exceeds this size. + maxTxPacketSize = 100 * 1024 +) + +// broadcastTransactions is a write loop that schedules transaction broadcasts +// to the remote peer. The goal is to have an async writer that does not lock up +// node internals and at the same time rate limits queued data. +func (p *Peer) broadcastTransactions() { + var ( + queue []common.Hash // Queue of hashes to broadcast as full transactions + done chan struct{} // Non-nil if background broadcaster is running + fail = make(chan error, 1) // Channel used to receive network error + failed bool // Flag whether a send failed, discard everything onward + ) + for { + // If there's no in-flight broadcast running, check if a new one is needed + if done == nil && len(queue) > 0 { + // Pile transaction until we reach our allowed network limit + var ( + hashesCount uint64 + txs []*types.Transaction + size common.StorageSize + ) + for i := 0; i < len(queue) && size < maxTxPacketSize; i++ { + if tx := p.txpool.Get(queue[i]); tx != nil { + txs = append(txs, tx) + size += common.StorageSize(tx.Size()) + } + hashesCount++ + } + queue = queue[:copy(queue, queue[hashesCount:])] + + // If there's anything available to transfer, fire up an async writer + if len(txs) > 0 { + done = make(chan struct{}) + go func() { + if err := p.SendTransactions(txs); err != nil { + fail <- err + return + } + close(done) + p.Log().Trace("Sent transactions", "count", len(txs)) + }() + } + } + // Transfer goroutine may or may not have been started, listen for events + select { + case hashes := <-p.txBroadcast: + // If the connection failed, discard all transaction events + if failed { + continue + } + // New batch of transactions to be broadcast, queue them (with cap) + queue = append(queue, hashes...) + if len(queue) > maxQueuedTxs { + // Fancy copy and resize to ensure buffer doesn't grow indefinitely + queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] + } + + case <-done: + done = nil + + case <-fail: + failed = true + + case <-p.term: + return + } + } +} + +// announceTransactions is a write loop that schedules transaction broadcasts +// to the remote peer. The goal is to have an async writer that does not lock up +// node internals and at the same time rate limits queued data. +func (p *Peer) announceTransactions() { + var ( + queue []common.Hash // Queue of hashes to announce as transaction stubs + done chan struct{} // Non-nil if background announcer is running + fail = make(chan error, 1) // Channel used to receive network error + failed bool // Flag whether a send failed, discard everything onward + ) + for { + // If there's no in-flight announce running, check if a new one is needed + if done == nil && len(queue) > 0 { + // Pile transaction hashes until we reach our allowed network limit + var ( + count int + pending []common.Hash + pendingTypes []byte + pendingSizes []uint32 + size common.StorageSize + ) + 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.Type()) + pendingSizes = append(pendingSizes, uint32(tx.Size())) + size += common.HashLength + } + } + // Shift and trim queue + queue = queue[:copy(queue, queue[count:])] + + // If there's anything available to transfer, fire up an async writer + if len(pending) > 0 { + done = make(chan struct{}) + go func() { + if err := p.sendPooledTransactionHashes(pending, pendingTypes, pendingSizes); err != nil { + fail <- err + return + } + close(done) + p.Log().Trace("Sent transaction announcements", "count", len(pending)) + }() + } + } + // Transfer goroutine may or may not have been started, listen for events + select { + case hashes := <-p.txAnnounce: + // If the connection failed, discard all transaction events + if failed { + continue + } + // New batch of transactions to be broadcast, queue them (with cap) + queue = append(queue, hashes...) + if len(queue) > maxQueuedTxAnns { + // Fancy copy and resize to ensure buffer doesn't grow indefinitely + queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxAnns:])] + } + + case <-done: + done = nil + + case <-fail: + failed = true + + case <-p.term: + return + } + } +} diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go new file mode 100644 index 0000000..a7bdd47 --- /dev/null +++ b/eth/protocols/eth/discovery.go @@ -0,0 +1,66 @@ +// 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 eth + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +// enrEntry is the ENR entry which advertises `eth` protocol on the discovery. +type enrEntry struct { + ForkID forkid.ID // Fork identifier per EIP-2124 + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +// ENRKey implements enr.Entry. +func (e enrEntry) ENRKey() string { + return "eth" +} + +// StartENRUpdater starts the `eth` ENR updater loop, which listens for chain +// head events and updates the requested node record whenever a fork is passed. +func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) { + var newHead = make(chan core.ChainHeadEvent, 10) + sub := chain.SubscribeChainHeadEvent(newHead) + + go func() { + defer sub.Unsubscribe() + for { + select { + case <-newHead: + ln.Set(currentENREntry(chain)) + case <-sub.Err(): + // Would be nice to sync with Stop, but there is no + // good way to do that. + return + } + } + }() +} + +// currentENREntry constructs an `eth` ENR entry based on the current state of the chain. +func currentENREntry(chain *core.BlockChain) *enrEntry { + head := chain.CurrentHeader() + return &enrEntry{ + 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 new file mode 100644 index 0000000..146eec3 --- /dev/null +++ b/eth/protocols/eth/dispatcher.go @@ -0,0 +1,253 @@ +// 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 eth + +import ( + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + // errDisconnected is returned if a request is attempted to be made to a peer + // that was already closed. + errDisconnected = errors.New("disconnected") + + // errDanglingResponse is returned if a response arrives with a request id + // which does not match to any existing pending requests. + errDanglingResponse = errors.New("response to non-existent request") + + // errMismatchingResponseType is returned if the remote peer sent a different + // packet type as a response to a request than what the local node expected. + errMismatchingResponseType = errors.New("mismatching response type") +) + +// 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 belongs for untracking + id uint64 // Request ID to match up replies to + + sink chan *Response // Channel to deliver the response on + cancel chan struct{} // Channel to cancel requests ahead of time + + code uint64 // Message code of the request packet + want uint64 // Message code of the response packet + data interface{} // Data content of the request packet + + Peer string // Demultiplexer if cross-peer requests are batched together + Sent time.Time // Timestamp when the request was sent +} + +// Close aborts an in-flight request. Although there's no way to notify the +// remote peer about the cancellation, this method notifies the dispatcher to +// discard any late responses. +func (r *Request) Close() error { + if r.peer == nil { // Tests mock out the dispatcher, skip internal cancellation + return nil + } + cancelOp := &cancel{ + id: r.id, + fail: make(chan error), + } + select { + case r.peer.reqCancel <- cancelOp: + if err := <-cancelOp.fail; err != nil { + return err + } + close(r.cancel) + return nil + case <-r.peer.term: + return errDisconnected + } +} + +// request is a wrapper around a client Request that has an error channel to +// signal on if sending the request already failed on a network level. +type request struct { + req *Request + fail chan error +} + +// cancel is a maintenance type on the dispatcher to stop tracking a pending +// request. +type cancel struct { + id uint64 // Request ID to stop tracking + fail chan error +} + +// Response is a reply packet to a previously created request. It is delivered +// on the channel assigned by the requester subsystem and contains the original +// request embedded to allow uniquely matching it caller side. +type Response struct { + id uint64 // Request ID to match up this reply to + recv time.Time // Timestamp when the request was received + code uint64 // Response packet type to cross validate with request + + Req *Request // Original request to cross-reference with + Res interface{} // Remote response for the request query + Meta interface{} // Metadata generated locally on the receiver thread + Time time.Duration // Time it took for the request to be served + Done chan error // Channel to signal message handling to the reader +} + +// response is a wrapper around a remote Response that has an error channel to +// signal on if processing the response failed. +type response struct { + res *Response + fail chan error +} + +// dispatchRequest schedules the request to the dispatcher for tracking and +// network serialization, blocking until it's successfully sent. +// +// The returned Request must either be closed before discarding it, or the reply +// must be waited for and the Response's Done channel signalled. +func (p *Peer) dispatchRequest(req *Request) error { + reqOp := &request{ + req: req, + fail: make(chan error), + } + req.cancel = make(chan struct{}) + req.peer = p + req.Peer = p.id + + select { + case p.reqDispatch <- reqOp: + return <-reqOp.fail + case <-p.term: + return errDisconnected + } +} + +// dispatchResponse fulfils a pending request and delivers it to the requested +// sink. +func (p *Peer) dispatchResponse(res *Response, metadata func() interface{}) error { + resOp := &response{ + res: res, + fail: make(chan error), + } + res.recv = time.Now() + res.Done = make(chan error) + + select { + case p.resDispatch <- resOp: + // Ensure the response is accepted by the dispatcher + if err := <-resOp.fail; err != nil { + return nil + } + // Request was accepted, run any postprocessing step to generate metadata + // on the receiver thread, not the sink thread + if metadata != nil { + res.Meta = metadata() + } + // Deliver the filled out response and wait until it's handled. This + // path is a bit funky as Go's select has no order, so if a response + // arrives to an already cancelled request, there's a 50-50% changes + // of picking on channel or the other. To avoid such cases delivering + // the packet upstream, check for cancellation first and only after + // block on delivery. + select { + case <-res.Req.cancel: + return nil // Request cancelled, silently discard response + default: + // Request not yet cancelled, attempt to deliver it, but do watch + // for fresh cancellations too + select { + case res.Req.sink <- res: + return <-res.Done // Response delivered, return any errors + case <-res.Req.cancel: + return nil // Request cancelled, silently discard response + } + } + + case <-p.term: + return errDisconnected + } +} + +// dispatcher is a loop that accepts requests from higher layer packages, pushes +// it to the network and tracks and dispatches the responses back to the original +// requester. +func (p *Peer) dispatcher() { + pending := make(map[uint64]*Request) + + for { + select { + case reqOp := <-p.reqDispatch: + req := reqOp.req + req.Sent = time.Now() + + requestTracker.Track(p.id, p.version, req.code, req.want, req.id) + err := p2p.Send(p.rw, req.code, req.data) + reqOp.fail <- err + + if err == nil { + pending[req.id] = req + } + + case cancelOp := <-p.reqCancel: + // Retrieve the pending request to cancel and short circuit if it + // has already been serviced and is not available anymore + req := pending[cancelOp.id] + if req == nil { + cancelOp.fail <- nil + continue + } + // Stop tracking the request + delete(pending, cancelOp.id) + cancelOp.fail <- nil + + case resOp := <-p.resDispatch: + res := resOp.res + res.Req = pending[res.id] + + // Independent if the request exists or not, track this packet + requestTracker.Fulfil(p.id, p.version, res.code, res.id) + + switch { + case res.Req == nil: + // Response arrived with an untracked ID. Since even cancelled + // requests are tracked until fulfillment, a dangling response + // means the remote peer implements the protocol badly. + resOp.fail <- errDanglingResponse + + case res.Req.want != res.code: + // Response arrived, but it's a different packet type than the + // one expected by the requester. Either the local code is bad, + // or the remote peer send junk. In neither cases can we handle + // the packet. + resOp.fail <- fmt.Errorf("%w: have %d, want %d", errMismatchingResponseType, res.code, res.Req.want) + + default: + // All dispatcher checks passed and the response was initialized + // with the matching request. Signal to the delivery routine that + // it can wait for a handler response and dispatch the data. + res.Time = res.recv.Sub(res.Req.Sent) + resOp.fail <- nil + + // Stop tracking the request, the response dispatcher will deliver + delete(pending, res.id) + } + + case <-p.term: + return + } + } +} diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go new file mode 100644 index 0000000..2d69ecd --- /dev/null +++ b/eth/protocols/eth/handler.go @@ -0,0 +1,211 @@ +// 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 eth + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/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/params" +) + +const ( + // softResponseLimit is the target maximum size of replies to data retrievals. + softResponseLimit = 2 * 1024 * 1024 + + // maxHeadersServe is the maximum number of block headers to serve. This number + // is there to limit the number of disk lookups. + maxHeadersServe = 1024 + + // maxBodiesServe is the maximum number of block bodies to serve. This number + // is mostly there to limit the number of disk lookups. With 24KB block sizes + // nowadays, the practical limit will always be softResponseLimit. + maxBodiesServe = 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 + // be softResponseLimit. + maxReceiptsServe = 1024 +) + +// Handler is a callback to invoke from an outside runner after the boilerplate +// exchanges have passed. +type Handler func(peer *Peer) error + +// Backend defines the data retrieval methods to serve remote requests and the +// callback methods to invoke on remote deliveries. +type Backend interface { + // Chain retrieves the blockchain object to serve data. + Chain() *core.BlockChain + + // TxPool retrieves the transaction pool object to serve data. + TxPool() TxPool + + // AcceptTxs retrieves whether transaction processing is enabled on the node + // or if inbound transactions should simply be dropped. + AcceptTxs() bool + + // RunPeer is invoked when a peer joins on the `eth` protocol. The handler + // should do any peer maintenance work, handshakes and validations. If all + // is passed, control should be given back to the `handler` to process the + // inbound messages going forward. + RunPeer(peer *Peer, handler Handler) error + + // PeerInfo retrieves all known `eth` information about a peer. + PeerInfo(id enode.ID) interface{} + + // Handle is a callback to be invoked when a data packet is received from + // the remote peer. Only packets not consumed by the protocol handler will + // be forwarded to the backend. + Handle(peer *Peer, packet Packet) error +} + +// 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) *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, 0, len(ProtocolVersions)) + for _, version := range ProtocolVersions { + version := version // Closure + + protocols = append(protocols, p2p.Protocol{ + Name: ProtocolName, + Version: version, + Length: protocolLengths[version], + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + peer := NewPeer(version, p, rw, backend.TxPool()) + defer peer.Close() + + return backend.RunPeer(peer, func(peer *Peer) error { + return Handle(backend, peer) + }) + }, + NodeInfo: func() interface{} { + return nodeInfo(backend.Chain(), network) + }, + PeerInfo: func(id enode.ID) interface{} { + return backend.PeerInfo(id) + }, + Attributes: []enr.Entry{currentENREntry(backend.Chain())}, + DialCandidates: dnsdisc, + }) + } + return protocols +} + +// NodeInfo represents a short summary of the `eth` sub-protocol metadata +// known about the host peer. +type NodeInfo struct { + Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, 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"` // Hex hash of the host's best owned block +} + +// nodeInfo retrieves some `eth` protocol metadata about the running host node. +func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo { + head := chain.CurrentBlock() + hash := head.Hash() + + return &NodeInfo{ + Network: network, + Difficulty: chain.GetTd(hash, head.Number.Uint64()), + Genesis: chain.Genesis().Hash(), + Config: chain.Config(), + Head: hash, + } +} + +// Handle is invoked whenever an `eth` connection is made that successfully passes +// the protocol handshake. This method will keep processing messages until the +// connection is torn down. +func Handle(backend Backend, peer *Peer) error { + for { + if err := handleMessage(backend, peer); err != nil { + peer.Log().Debug("Message handling failed in `eth`", "err", err) + return err + } + } +} + +type msgHandler func(backend Backend, msg Decoder, peer *Peer) error +type Decoder interface { + Decode(val interface{}) error + Time() time.Time +} + +var eth68 = map[uint64]msgHandler{ + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, + NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, + 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 +// peer. The remote connection is torn down upon returning any error. +func handleMessage(backend Backend, peer *Peer) error { + // Read the next message from the remote peer, and ensure it's fully consumed + msg, err := peer.rw.ReadMsg() + if err != nil { + return err + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + defer msg.Discard() + + var 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) + defer func(start time.Time) { + sampler := func() metrics.Sample { + return metrics.ResettingSample( + metrics.NewExpDecaySample(1028, 0.015), + ) + } + metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) + }(time.Now()) + } + if handler := handlers[msg.Code]; handler != nil { + return handler(backend, msg, peer) + } + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) +} diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go new file mode 100644 index 0000000..934dadc --- /dev/null +++ b/eth/protocols/eth/handler_test.go @@ -0,0 +1,501 @@ +// 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 eth + +import ( + "math" + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/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" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" +) + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +func u64(val uint64) *uint64 { return &val } + +// testBackend is a mock implementation of the live Ethereum message handler. Its +// purpose is to allow testing the request/reply workflows and wire serialization +// in the `eth` protocol without actually doing any data processing. +type testBackend struct { + db ethdb.Database + chain *core.BlockChain + txpool *txpool.TxPool +} + +// newTestBackend creates an empty chain and wraps it into a mock backend. +func newTestBackend(blocks int) *testBackend { + return newTestBackendWithGenerator(blocks, false, nil) +} + +// newTestBackendWithGenerator creates a chain with a number of explicitly defined blocks and +// wraps it into a mock backend. +func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, *core.BlockGen)) *testBackend { + var ( + // Create a database pre-initialize with a genesis block + db = rawdb.NewMemoryDatabase() + config = params.TestChainConfig + engine consensus.Engine = ethash.NewFaker() + ) + + if shanghai { + config = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + 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), + ArrowGlacierBlock: big.NewInt(0), + GrayGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + ShanghaiTime: u64(0), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + Ethash: new(params.EthashConfig), + } + engine = beacon.NewFaker() + } + + gspec := &core.Genesis{ + Config: config, + Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}}, + } + chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil) + + _, bs, _ := core.GenerateChainWithGenesis(gspec, engine, blocks, generator) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + for _, block := range bs { + chain.TrieDB().Commit(block.Root(), false) + } + txconfig := legacypool.DefaultConfig + txconfig.Journal = "" // Don't litter the disk with test journals + + pool := legacypool.New(txconfig, chain) + txpool, _ := txpool.New(txconfig.PriceLimit, chain, []txpool.SubPool{pool}) + + return &testBackend{ + db: db, + chain: chain, + txpool: txpool, + } +} + +// close tears down the transaction pool and chain behind the mock backend. +func (b *testBackend) close() { + b.txpool.Close() + b.chain.Stop() +} + +func (b *testBackend) Chain() *core.BlockChain { return b.chain } +func (b *testBackend) TxPool() TxPool { return b.txpool } + +func (b *testBackend) RunPeer(peer *Peer, handler Handler) error { + // Normally the backend would do peer maintenance and handshakes. All that + // is omitted and we will just give control back to the handler. + return handler(peer) +} +func (b *testBackend) PeerInfo(enode.ID) interface{} { panic("not implemented") } + +func (b *testBackend) AcceptTxs() bool { + panic("data processing tests should be done in the handler package") +} +func (b *testBackend) Handle(*Peer, Packet) error { + panic("data processing tests should be done in the handler package") +} + +// Tests that block headers can be retrieved from a remote chain based on user queries. +func TestGetBlockHeaders68(t *testing.T) { testGetBlockHeaders(t, ETH68) } + +func testGetBlockHeaders(t *testing.T, protocol uint) { + t.Parallel() + + backend := newTestBackend(maxHeadersServe + 15) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Create a "random" unknown hash for testing + var unknown common.Hash + for i := range unknown { + unknown[i] = byte(i) + } + getHashes := func(from, limit uint64) (hashes []common.Hash) { + for i := uint64(0); i < limit; i++ { + hashes = append(hashes, backend.chain.GetCanonicalHash(from-1-i)) + } + return hashes + } + // Create a batch of tests for various scenarios + limit := uint64(maxHeadersServe) + tests := []struct { + 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 + { + &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 + { + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Amount: 1}, + []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()}, + }, + // Multiple headers should be retrievable in both directions + { + &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(), + }, + }, { + &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(), + backend.chain.GetBlockByNumber(limit/2 - 2).Hash(), + }, + }, + // Multiple headers with skip lists should be retrievable + { + &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(), + }, + }, { + &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(), + backend.chain.GetBlockByNumber(limit/2 - 8).Hash(), + }, + }, + // The chain endpoints should be retrievable + { + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 0}, Amount: 1}, + []common.Hash{backend.chain.GetBlockByNumber(0).Hash()}, + }, + { + &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 + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64()}, Amount: 10}, + []common.Hash{backend.chain.CurrentBlock().Hash()}, + }, + // Ensure protocol limits are honored + { + &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 + { + &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(), + }, + }, { + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(4).Hash(), + backend.chain.GetBlockByNumber(0).Hash(), + }, + }, + // Check that requesting more than available is handled gracefully, even if mid skip + { + &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(), + }, + }, { + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(4).Hash(), + backend.chain.GetBlockByNumber(1).Hash(), + }, + }, + // Check a corner case where requesting more can iterate past the endpoints + { + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 2}, Amount: 5, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(2).Hash(), + backend.chain.GetBlockByNumber(1).Hash(), + backend.chain.GetBlockByNumber(0).Hash(), + }, + }, + // Check a corner case where skipping overflow loops back into the chain start + { + &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 + { + &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 + { + &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: unknown}, Amount: 1}, + []common.Hash{}, + }, { + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() + 1}, Amount: 1}, + []common.Hash{}, + }, + } + // Run each of the tests and verify the results against the chain + 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, backend.chain.GetBlockByHash(hash).Header()) + } + // Send the hash request and verify the response + p2p.Send(peer.app, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + RequestId: 123, + GetBlockHeadersRequest: tt.query, + }) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, &BlockHeadersPacket{ + RequestId: 123, + BlockHeadersRequest: headers, + }); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + // If the test used number origins, repeat with hashes as the too + if tt.query.Origin.Hash == (common.Hash{}) { + 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, &GetBlockHeadersPacket{ + RequestId: 456, + GetBlockHeadersRequest: tt.query, + }) + 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) + } + } + } + } +} + +// Tests that block contents can be retrieved from a remote chain based on their hashes. +func TestGetBlockBodies68(t *testing.T) { testGetBlockBodies(t, ETH68) } + +func testGetBlockBodies(t *testing.T, protocol uint) { + t.Parallel() + + gen := func(n int, g *core.BlockGen) { + if n%2 == 0 { + w := &types.Withdrawal{ + Address: common.Address{0xaa}, + Amount: 42, + } + g.AddWithdrawal(w) + } + } + + backend := newTestBackendWithGenerator(maxBodiesServe+15, true, gen) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Create a batch of tests for various scenarios + limit := maxBodiesServe + 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{backend.chain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable + {0, []common.Hash{backend.chain.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{ + {}, + backend.chain.GetBlockByNumber(1).Hash(), + {}, + backend.chain.GetBlockByNumber(10).Hash(), + {}, + backend.chain.GetBlockByNumber(100).Hash(), + {}, + }, []bool{false, true, false, true, false, true, false}, 3}, + } + // Run each of the tests and verify the results against the chain + for i, tt := range tests { + // Collect the hashes to request, and the response to expect + var ( + hashes []common.Hash + bodies []*BlockBody + seen = make(map[int64]bool) + ) + for j := 0; j < tt.random; j++ { + for { + num := rand.Int63n(int64(backend.chain.CurrentBlock().Number.Uint64())) + if !seen[num] { + seen[num] = true + + block := backend.chain.GetBlockByNumber(uint64(num)) + hashes = append(hashes, block.Hash()) + if len(bodies) < tt.expected { + bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles(), Withdrawals: block.Withdrawals()}) + } + break + } + } + } + for j, hash := range tt.explicit { + hashes = append(hashes, hash) + if tt.available[j] && len(bodies) < tt.expected { + block := backend.chain.GetBlockByHash(hash) + bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles(), Withdrawals: block.Withdrawals()}) + } + } + + // Send the hash request and verify the response + p2p.Send(peer.app, GetBlockBodiesMsg, &GetBlockBodiesPacket{ + RequestId: 123, + GetBlockBodiesRequest: hashes, + }) + 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 transaction receipts can be retrieved based on hashes. +func TestGetBlockReceipts68(t *testing.T) { testGetBlockReceipts(t, ETH68) } + +func testGetBlockReceipts(t *testing.T, protocol uint) { + 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_markets_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 the hashes to request, and the response to expect + var ( + hashes []common.Hash + receipts [][]*types.Receipt + ) + for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ { + block := backend.chain.GetBlockByNumber(i) + + hashes = append(hashes, block.Hash()) + receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash())) + } + // Send the hash request and verify the response + p2p.Send(peer.app, GetReceiptsMsg, &GetReceiptsPacket{ + RequestId: 123, + GetReceiptsRequest: hashes, + }) + 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 new file mode 100644 index 0000000..bdc630a --- /dev/null +++ b/eth/protocols/eth/handlers.go @@ -0,0 +1,453 @@ +// 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 eth + +import ( + "encoding/json" + "errors" + "fmt" + + "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" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +func handleGetBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { + // Decode the complex header query + 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.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 *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) + } else { + return serviceNonContiguousBlockHeaderQuery(chain, query, peer) + } +} + +func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersRequest, peer *Peer) []rlp.RawValue { + hashMode := query.Origin.Hash != (common.Hash{}) + first := true + maxNonCanonical := uint64(100) + + // Gather headers until the fetch or network limits is reached + var ( + bytes common.StorageSize + headers []rlp.RawValue + unknown bool + lookups int + ) + for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && + len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe { + lookups++ + // Retrieve the next header satisfying the query + var origin *types.Header + if hashMode { + if first { + first = false + origin = chain.GetHeaderByHash(query.Origin.Hash) + if origin != nil { + query.Origin.Number = origin.Number.Uint64() + } + } else { + origin = chain.GetHeader(query.Origin.Hash, query.Origin.Number) + } + } else { + origin = chain.GetHeaderByNumber(query.Origin.Number) + } + if origin == nil { + break + } + if rlpData, err := rlp.EncodeToBytes(origin); err != nil { + log.Crit("Unable to encode our own headers", "err", err) + } else { + headers = append(headers, rlp.RawValue(rlpData)) + bytes += common.StorageSize(len(rlpData)) + } + // Advance to the next header of the query + switch { + case hashMode && query.Reverse: + // Hash based traversal towards the genesis block + ancestor := query.Skip + 1 + if ancestor == 0 { + unknown = true + } else { + query.Origin.Hash, query.Origin.Number = chain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) + unknown = (query.Origin.Hash == common.Hash{}) + } + case hashMode && !query.Reverse: + // Hash based traversal towards the leaf block + var ( + current = origin.Number.Uint64() + next = current + query.Skip + 1 + ) + if next <= current { + infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ") + peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) + unknown = true + } else { + if header := chain.GetHeaderByNumber(next); header != nil { + nextHash := header.Hash() + expOldHash, _ := chain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) + if expOldHash == query.Origin.Hash { + query.Origin.Hash, query.Origin.Number = nextHash, next + } else { + unknown = true + } + } else { + unknown = true + } + } + case query.Reverse: + // Number based traversal towards the genesis block + if query.Origin.Number >= query.Skip+1 { + query.Origin.Number -= query.Skip + 1 + } else { + unknown = true + } + + case !query.Reverse: + // Number based traversal towards the leaf block + query.Origin.Number += query.Skip + 1 + } + } + return headers +} + +func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersRequest) []rlp.RawValue { + count := query.Amount + if count > maxHeadersServe { + count = maxHeadersServe + } + if query.Origin.Hash == (common.Hash{}) { + // Number mode, just return the canon chain segment. The backend + // delivers in [N, N-1, N-2..] descending order, so we need to + // accommodate for that. + from := query.Origin.Number + if !query.Reverse { + from = from + count - 1 + } + headers := chain.GetHeadersFrom(from, count) + if !query.Reverse { + for i, j := 0, len(headers)-1; i < j; i, j = i+1, j-1 { + headers[i], headers[j] = headers[j], headers[i] + } + } + return headers + } + // Hash mode. + var ( + headers []rlp.RawValue + hash = query.Origin.Hash + header = chain.GetHeaderByHash(hash) + ) + if header != nil { + rlpData, _ := rlp.EncodeToBytes(header) + headers = append(headers, rlpData) + } else { + // We don't even have the origin header + return headers + } + num := header.Number.Uint64() + if !query.Reverse { + // Theoretically, we are tasked to deliver header by hash H, and onwards. + // However, if H is not canon, we will be unable to deliver any descendants of + // H. + if canonHash := chain.GetCanonicalHash(num); canonHash != hash { + // Not canon, we can't deliver descendants + return headers + } + descendants := chain.GetHeadersFrom(num+count-1, count-1) + for i, j := 0, len(descendants)-1; i < j; i, j = i+1, j-1 { + descendants[i], descendants[j] = descendants[j], descendants[i] + } + headers = append(headers, descendants...) + return headers + } + { // Last mode: deliver ancestors of H + for i := uint64(1); i < count; i++ { + header = chain.GetHeaderByHash(header.ParentHash) + if header == nil { + break + } + rlpData, _ := rlp.EncodeToBytes(header) + headers = append(headers, rlpData) + } + return headers + } +} + +func handleGetBlockBodies(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block body retrieval message + 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.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 GetBlockBodiesRequest) []rlp.RawValue { + // Gather blocks until the fetch or network limits is reached + var ( + bytes int + bodies []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe || + lookups >= 2*maxBodiesServe { + break + } + if data := chain.GetBodyRLP(hash); len(data) != 0 { + bodies = append(bodies, data) + bytes += len(data) + } + } + return bodies +} + +func handleGetReceipts(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block receipts retrieval message + 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.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 GetReceiptsRequest) []rlp.RawValue { + // Gather state data until the fetch or network limits is reached + var ( + bytes int + receipts []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || + lookups >= 2*maxReceiptsServe { + break + } + // Retrieve the requested block's receipts + results := chain.GetReceiptsByHash(hash) + if results == nil { + if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { + 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 receipts +} + +func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error { + return errors.New("block announcements disallowed") // We dropped support for non-merge networks +} + +func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { + return errors.New("block broadcasts disallowed") // We dropped support for non-merge networks +} + +func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { + // A batch of headers arrived to one of our previous requests + 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.BlockHeadersRequest)) + for i, header := range res.BlockHeadersRequest { + hashes[i] = header.Hash() + } + return hashes + } + return peer.dispatchResponse(&Response{ + id: res.RequestId, + code: BlockHeadersMsg, + Res: &res.BlockHeadersRequest, + }, metadata) +} + +func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error { + // A batch of block bodies arrived to one of our previous requests + 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.BlockBodiesResponse)) + uncleHashes = make([]common.Hash, len(res.BlockBodiesResponse)) + withdrawalHashes = make([]common.Hash, len(res.BlockBodiesResponse)) + ) + hasher := trie.NewStackTrie(nil) + 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 { + withdrawalHashes[i] = types.DeriveSha(types.Withdrawals(body.Withdrawals), hasher) + } + } + return [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes} + } + return peer.dispatchResponse(&Response{ + id: res.RequestId, + code: BlockBodiesMsg, + Res: &res.BlockBodiesResponse, + }, metadata) +} + +func handleReceipts(backend Backend, msg Decoder, peer *Peer) error { + // A batch of receipts arrived to one of our previous requests + 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.ReceiptsResponse)) + for i, receipt := range res.ReceiptsResponse { + hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher) + } + return hashes + } + return peer.dispatchResponse(&Response{ + id: res.RequestId, + code: ReceiptsMsg, + Res: &res.ReceiptsResponse, + }, metadata) +} + +func handleNewPooledTransactionHashes(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(NewPooledTransactionHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if len(ann.Hashes) != len(ann.Types) || len(ann.Hashes) != len(ann.Sizes) { + return fmt.Errorf("%w: message %v: invalid len of fields: %v %v %v", errDecode, msg, len(ann.Hashes), len(ann.Types), len(ann.Sizes)) + } + // Schedule all the unknown hashes for retrieval + for _, hash := range ann.Hashes { + peer.markTransaction(hash) + } + return backend.Handle(peer, ann) +} + +func handleGetPooledTransactions(backend Backend, msg Decoder, peer *Peer) error { + // Decode the pooled transactions retrieval message + 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.GetPooledTransactionsRequest) + return peer.ReplyPooledTransactionsRLP(query.RequestId, hashes, txs) +} + +func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsRequest) ([]common.Hash, []rlp.RawValue) { + // Gather transactions until the fetch or network limits is reached + var ( + bytes int + hashes []common.Hash + txs []rlp.RawValue + ) + for _, hash := range query { + if bytes >= softResponseLimit { + break + } + // Retrieve the requested transaction, skipping if unknown to us + tx := backend.TxPool().Get(hash) + if tx == nil { + continue + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(tx); err != nil { + log.Error("Failed to encode transaction", "err", err) + } else { + hashes = append(hashes, hash) + txs = append(txs, encoded) + bytes += len(encoded) + } + } + return hashes, txs +} + +func handleTransactions(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 TransactionsPacket + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + return backend.Handle(peer, &txs) +} + +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 PooledTransactionsPacket + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + 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) + } + peer.markTransaction(tx.Hash()) + } + requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId) + + return backend.Handle(peer, &txs.PooledTransactionsResponse) +} diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go new file mode 100644 index 0000000..ea16a85 --- /dev/null +++ b/eth/protocols/eth/handshake.go @@ -0,0 +1,133 @@ +// 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 eth + +import ( + "errors" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/p2p" +) + +const ( + // handshakeTimeout is the maximum allowed time for the `eth` handshake to + // complete before dropping the connection.= as malicious. + handshakeTimeout = 5 * time.Second +) + +// Handshake executes the eth protocol handshake, negotiating version number, +// network IDs, difficulties, head and genesis blocks. +func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { + // Send out own handshake in a new thread + errc := make(chan error, 2) + + var status StatusPacket // safe to read after two values have been received from errc + + go func() { + errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{ + ProtocolVersion: uint32(p.version), + NetworkID: network, + TD: td, + Head: head, + Genesis: genesis, + ForkID: forkID, + }) + }() + go func() { + errc <- p.readStatus(network, &status, genesis, forkFilter) + }() + timeout := time.NewTimer(handshakeTimeout) + defer timeout.Stop() + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + markError(p, err) + return err + } + case <-timeout.C: + markError(p, p2p.DiscReadTimeout) + return p2p.DiscReadTimeout + } + } + p.td, p.head = status.TD, status.Head + + // TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times + // larger, it will still fit within 100 bits + if tdlen := p.td.BitLen(); tdlen > 100 { + return fmt.Errorf("too large total difficulty: bitlen %d", tdlen) + } + return nil +} + +// readStatus reads the remote handshake message. +func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.Hash, forkFilter forkid.Filter) error { + msg, err := p.rw.ReadMsg() + if err != nil { + return err + } + if msg.Code != StatusMsg { + return fmt.Errorf("%w: first msg has code %x (!= %x)", errNoStatusMsg, msg.Code, StatusMsg) + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + // Decode the handshake and make sure everything matches + if err := msg.Decode(&status); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if status.NetworkID != network { + return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, network) + } + if uint(status.ProtocolVersion) != p.version { + return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version) + } + if status.Genesis != genesis { + return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis) + } + if err := forkFilter(status.ForkID); err != nil { + return fmt.Errorf("%w: %v", errForkIDRejected, err) + } + return nil +} + +// markError registers the error with the corresponding metric. +func markError(p *Peer, err error) { + if !metrics.Enabled { + return + } + m := meters.get(p.Inbound()) + switch errors.Unwrap(err) { + case errNetworkIDMismatch: + m.networkIDMismatch.Mark(1) + case errProtocolVersionMismatch: + m.protocolVersionMismatch.Mark(1) + case errGenesisMismatch: + m.genesisMismatch.Mark(1) + case errForkIDRejected: + m.forkidRejected.Mark(1) + case p2p.DiscReadTimeout: + m.timeoutError.Mark(1) + default: + m.peerError.Mark(1) + } +} diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go new file mode 100644 index 0000000..b9fd13d --- /dev/null +++ b/eth/protocols/eth/handshake_test.go @@ -0,0 +1,90 @@ +// 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 eth + +import ( + "errors" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// Tests that handshake failures are detected and reported correctly. +func TestHandshake68(t *testing.T) { testHandshake(t, ETH68) } + +func testHandshake(t *testing.T, protocol uint) { + t.Parallel() + + // Create a test backend only to have some valid genesis chain + backend := newTestBackend(3) + defer backend.close() + + var ( + 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(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time) + ) + tests := []struct { + code uint64 + data interface{} + want error + }{ + { + code: TransactionsMsg, data: []interface{}{}, + want: errNoStatusMsg, + }, + { + code: StatusMsg, data: StatusPacket{10, 1, td, head.Hash(), genesis.Hash(), forkID}, + want: errProtocolVersionMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 999, td, head.Hash(), genesis.Hash(), forkID}, + want: errNetworkIDMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), common.Hash{3}, forkID}, + want: errGenesisMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}}, + want: errForkIDRejected, + }, + } + for i, test := range tests { + // Create the two peers to shake with each other + app, net := p2p.MsgPipe() + defer app.Close() + defer net.Close() + + peer := NewPeer(protocol, p2p.NewPeer(enode.ID{}, "peer", nil), net, nil) + defer peer.Close() + + // Send the junk test with one peer, check the handshake failure + go p2p.Send(app, test.code, test.data) + + err := peer.Handshake(1, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain)) + if err == nil { + t.Errorf("test %d: protocol returned nil error, want %q", i, test.want) + } else if !errors.Is(err, test.want) { + t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.want) + } + } +} diff --git a/eth/protocols/eth/metrics.go b/eth/protocols/eth/metrics.go new file mode 100644 index 0000000..5e0aee3 --- /dev/null +++ b/eth/protocols/eth/metrics.go @@ -0,0 +1,81 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import "github.com/ethereum/go-ethereum/metrics" + +// meters stores ingress and egress handshake meters. +var meters bidirectionalMeters + +// bidirectionalMeters stores ingress and egress handshake meters. +type bidirectionalMeters struct { + ingress *hsMeters + egress *hsMeters +} + +// get returns the corresponding meter depending if ingress or egress is +// desired. +func (h *bidirectionalMeters) get(ingress bool) *hsMeters { + if ingress { + return h.ingress + } + return h.egress +} + +// hsMeters is a collection of meters which track metrics related to the +// eth subprotocol handshake. +type hsMeters struct { + // peerError measures the number of errors related to incorrect peer + // behaviour, such as invalid message code, size, encoding, etc. + peerError metrics.Meter + + // timeoutError measures the number of timeouts. + timeoutError metrics.Meter + + // networkIDMismatch measures the number of network id mismatch errors. + networkIDMismatch metrics.Meter + + // protocolVersionMismatch measures the number of differing protocol + // versions. + protocolVersionMismatch metrics.Meter + + // genesisMismatch measures the number of differing genesises. + genesisMismatch metrics.Meter + + // forkidRejected measures the number of differing forkids. + forkidRejected metrics.Meter +} + +// newHandshakeMeters registers and returns handshake meters for the given +// base. +func newHandshakeMeters(base string) *hsMeters { + return &hsMeters{ + peerError: metrics.NewRegisteredMeter(base+"error/peer", nil), + timeoutError: metrics.NewRegisteredMeter(base+"error/timeout", nil), + networkIDMismatch: metrics.NewRegisteredMeter(base+"error/network", nil), + protocolVersionMismatch: metrics.NewRegisteredMeter(base+"error/version", nil), + genesisMismatch: metrics.NewRegisteredMeter(base+"error/genesis", nil), + forkidRejected: metrics.NewRegisteredMeter(base+"error/forkid", nil), + } +} + +func init() { + meters = bidirectionalMeters{ + ingress: newHandshakeMeters("eth/protocols/eth/ingress/handshake/"), + egress: newHandshakeMeters("eth/protocols/eth/egress/handshake/"), + } +} diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go new file mode 100644 index 0000000..f53782a --- /dev/null +++ b/eth/protocols/eth/peer.go @@ -0,0 +1,402 @@ +// 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 eth + +import ( + "math/big" + "math/rand" + "sync" + + mapset "github.com/deckarep/golang-set/v2" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + // maxKnownTxs is the maximum transactions hashes to keep in the known list + // before starting to randomly evict them. + maxKnownTxs = 32768 + + // maxQueuedTxs is the maximum number of transactions to queue up before dropping + // older broadcasts. + maxQueuedTxs = 4096 + + // maxQueuedTxAnns is the maximum number of transaction announcements to queue up + // before dropping older announcements. + maxQueuedTxAnns = 4096 +) + +// Peer is a collection of relevant information we have about a `eth` peer. +type Peer struct { + id string // Unique ID for the peer, cached + + *p2p.Peer // The embedded P2P package peer + rw p2p.MsgReadWriter // Input/output streams for snap + version uint // Protocol version negotiated + + head common.Hash // Latest advertised head block hash + td *big.Int // Latest advertised head block total difficulty + + txpool TxPool // Transaction pool used by the broadcasters for liveness checks + knownTxs *knownCache // Set of transaction hashes known to be known by this peer + 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 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 + + term chan struct{} // Termination channel to stop the broadcasters + lock sync.RWMutex // Mutex protecting the internal fields +} + +// NewPeer creates a wrapper for a network connection and negotiated protocol +// version. +func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer { + peer := &Peer{ + id: p.ID().String(), + Peer: p, + rw: rw, + version: version, + knownTxs: newKnownCache(maxKnownTxs), + txBroadcast: make(chan []common.Hash), + txAnnounce: make(chan []common.Hash), + reqDispatch: make(chan *request), + reqCancel: make(chan *cancel), + resDispatch: make(chan *response), + txpool: txpool, + term: make(chan struct{}), + } + // Start up all the broadcasters + go peer.broadcastTransactions() + go peer.announceTransactions() + go peer.dispatcher() + + return peer +} + +// Close signals the broadcast goroutine to terminate. Only ever call this if +// you created the peer yourself via NewPeer. Otherwise let whoever created it +// clean it up! +func (p *Peer) Close() { + close(p.term) +} + +// ID retrieves the peer's unique identifier. +func (p *Peer) ID() string { + return p.id +} + +// Version retrieves the peer's negotiated `eth` protocol version. +func (p *Peer) Version() uint { + return p.version +} + +// Head retrieves the current head hash and total difficulty of the peer. +func (p *Peer) Head() (hash common.Hash, td *big.Int) { + p.lock.RLock() + defer p.lock.RUnlock() + + copy(hash[:], p.head[:]) + return hash, new(big.Int).Set(p.td) +} + +// SetHead updates the head hash and total difficulty of the peer. +func (p *Peer) SetHead(hash common.Hash, td *big.Int) { + p.lock.Lock() + defer p.lock.Unlock() + + copy(p.head[:], hash[:]) + p.td.Set(td) +} + +// KnownTransaction returns whether peer is known to already have a transaction. +func (p *Peer) KnownTransaction(hash common.Hash) bool { + return p.knownTxs.Contains(hash) +} + +// markTransaction marks a transaction as known for the peer, ensuring that it +// will never be propagated to this particular peer. +func (p *Peer) markTransaction(hash common.Hash) { + // If we reached the memory allowance, drop a previously known transaction hash + p.knownTxs.Add(hash) +} + +// SendTransactions sends transactions to the peer and includes the hashes +// in its transaction hash set for future reference. +// +// This method is a helper used by the async transaction sender. Don't call it +// directly as the queueing (memory) and transmission (bandwidth) costs should +// not be managed directly. +// +// The reasons this is public is to allow packages using this protocol to write +// tests that directly send messages without having to do the async queueing. +func (p *Peer) SendTransactions(txs types.Transactions) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for _, tx := range txs { + p.knownTxs.Add(tx.Hash()) + } + return p2p.Send(p.rw, TransactionsMsg, txs) +} + +// AsyncSendTransactions queues a list of transactions (by hash) to eventually +// propagate to a remote peer. The number of pending sends are capped (new ones +// will force old sends to be dropped) +func (p *Peer) AsyncSendTransactions(hashes []common.Hash) { + select { + case p.txBroadcast <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + p.knownTxs.Add(hashes...) + case <-p.term: + p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) + } +} + +// sendPooledTransactionHashes sends transaction hashes (tagged with their type +// and size) to the peer and includes them in its transaction hash set for future +// reference. +// +// This method is a helper used by the async transaction announcer. Don't call it +// directly as the queueing (memory) and transmission (bandwidth) costs should +// not be managed directly. +func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash, types []byte, sizes []uint32) 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, NewPooledTransactionHashesPacket{Types: types, Sizes: sizes, Hashes: hashes}) +} + +// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually +// announce to a remote peer. The number of pending sends are capped (new ones +// will force old sends to be dropped) +func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { + select { + case p.txAnnounce <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + p.knownTxs.Add(hashes...) + case <-p.term: + p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) + } +} + +// 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 PooledTransactionsResponse to avoid RLP decoding + return p2p.Send(p.rw, PooledTransactionsMsg, &PooledTransactionsRLPPacket{ + RequestId: id, + PooledTransactionsRLPResponse: txs, + }) +} + +// ReplyBlockHeadersRLP is the response to GetBlockHeaders. +func (p *Peer) ReplyBlockHeadersRLP(id uint64, headers []rlp.RawValue) error { + return p2p.Send(p.rw, BlockHeadersMsg, &BlockHeadersRLPPacket{ + RequestId: id, + BlockHeadersRLPResponse: headers, + }) +} + +// ReplyBlockBodiesRLP is the response to GetBlockBodies. +func (p *Peer) ReplyBlockBodiesRLP(id uint64, bodies []rlp.RawValue) error { + // Not packed into BlockBodiesResponse to avoid RLP decoding + return p2p.Send(p.rw, BlockBodiesMsg, &BlockBodiesRLPPacket{ + RequestId: id, + BlockBodiesRLPResponse: bodies, + }) +} + +// ReplyReceiptsRLP is the response to GetReceipts. +func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error { + return p2p.Send(p.rw, ReceiptsMsg, &ReceiptsRLPPacket{ + RequestId: id, + ReceiptsRLPResponse: receipts, + }) +} + +// RequestOneHeader is a wrapper around the header query functions to fetch a +// single header. It is used solely by the fetcher. +func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request, error) { + p.Log().Debug("Fetching single header", "hash", hash) + id := rand.Uint64() + + req := &Request{ + id: id, + sink: sink, + code: GetBlockHeadersMsg, + want: BlockHeadersMsg, + data: &GetBlockHeadersPacket{ + RequestId: id, + GetBlockHeadersRequest: &GetBlockHeadersRequest{ + Origin: HashOrNumber{Hash: hash}, + Amount: uint64(1), + Skip: uint64(0), + Reverse: false, + }, + }, + } + if err := p.dispatchRequest(req); err != nil { + return nil, err + } + return req, nil +} + +// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the +// specified header query, based on the hash of an origin block. +func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool, sink chan *Response) (*Request, error) { + p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) + id := rand.Uint64() + + req := &Request{ + id: id, + sink: sink, + code: GetBlockHeadersMsg, + want: BlockHeadersMsg, + data: &GetBlockHeadersPacket{ + RequestId: id, + GetBlockHeadersRequest: &GetBlockHeadersRequest{ + Origin: HashOrNumber{Hash: origin}, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + }, + }, + } + if err := p.dispatchRequest(req); err != nil { + return nil, err + } + return req, nil +} + +// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the +// specified header query, based on the number of an origin block. +func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *Response) (*Request, error) { + p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) + id := rand.Uint64() + + req := &Request{ + id: id, + sink: sink, + code: GetBlockHeadersMsg, + want: BlockHeadersMsg, + data: &GetBlockHeadersPacket{ + RequestId: id, + GetBlockHeadersRequest: &GetBlockHeadersRequest{ + Origin: HashOrNumber{Number: origin}, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + }, + }, + } + if err := p.dispatchRequest(req); err != nil { + return nil, err + } + return req, nil +} + +// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes +// specified. +func (p *Peer) RequestBodies(hashes []common.Hash, sink chan *Response) (*Request, error) { + p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) + id := rand.Uint64() + + req := &Request{ + id: id, + sink: sink, + code: GetBlockBodiesMsg, + want: BlockBodiesMsg, + data: &GetBlockBodiesPacket{ + RequestId: id, + GetBlockBodiesRequest: hashes, + }, + } + if err := p.dispatchRequest(req); err != nil { + return nil, err + } + return req, nil +} + +// RequestReceipts fetches a batch of transaction receipts from a remote node. +func (p *Peer) RequestReceipts(hashes []common.Hash, sink chan *Response) (*Request, error) { + p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) + id := rand.Uint64() + + req := &Request{ + id: id, + sink: sink, + code: GetReceiptsMsg, + want: ReceiptsMsg, + data: &GetReceiptsPacket{ + RequestId: id, + GetReceiptsRequest: hashes, + }, + } + if err := p.dispatchRequest(req); err != nil { + return nil, err + } + return req, nil +} + +// RequestTxs fetches a batch of transactions from a remote node. +func (p *Peer) RequestTxs(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id) + return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket{ + RequestId: id, + GetPooledTransactionsRequest: hashes, + }) +} + +// knownCache is a cache for known hashes. +type knownCache struct { + hashes mapset.Set[common.Hash] + max int +} + +// newKnownCache creates a new knownCache with a max capacity. +func newKnownCache(max int) *knownCache { + return &knownCache{ + max: max, + hashes: mapset.NewSet[common.Hash](), + } +} + +// Add adds a list of elements to the set. +func (k *knownCache) Add(hashes ...common.Hash) { + for k.hashes.Cardinality() > max(0, k.max-len(hashes)) { + k.hashes.Pop() + } + for _, hash := range hashes { + k.hashes.Add(hash) + } +} + +// Contains returns whether the given item is in the set. +func (k *knownCache) Contains(hash common.Hash) bool { + return k.hashes.Contains(hash) +} + +// Cardinality returns the number of elements in the set. +func (k *knownCache) Cardinality() int { + return k.hashes.Cardinality() +} diff --git a/eth/protocols/eth/peer_test.go b/eth/protocols/eth/peer_test.go new file mode 100644 index 0000000..efbbbc6 --- /dev/null +++ b/eth/protocols/eth/peer_test.go @@ -0,0 +1,90 @@ +// 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 . + +// This file contains some shares testing functionality, common to multiple +// different files and modules being tested. + +package eth + +import ( + "crypto/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// testPeer is a simulated peer to allow testing direct network calls. +type testPeer struct { + *Peer + + net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging + app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side +} + +// newTestPeer creates a new peer registered at the given data backend. +func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Start the peer on a new thread + var id enode.ID + rand.Read(id[:]) + + peer := NewPeer(version, p2p.NewPeer(id, name, nil), net, backend.TxPool()) + errc := make(chan error, 1) + go func() { + defer app.Close() + + errc <- backend.RunPeer(peer, func(peer *Peer) error { + return Handle(backend, peer) + }) + }() + return &testPeer{app: app, net: net, Peer: peer}, errc +} + +// close terminates the local side of the peer, notifying the remote protocol +// manager of termination. +func (p *testPeer) close() { + p.Peer.Close() + p.app.Close() +} + +func TestPeerSet(t *testing.T) { + size := 5 + s := newKnownCache(size) + + // add 10 items + for i := 0; i < size*2; i++ { + s.Add(common.Hash{byte(i)}) + } + + if s.Cardinality() != size { + t.Fatalf("wrong size, expected %d but found %d", size, s.Cardinality()) + } + + vals := []common.Hash{} + for i := 10; i < 20; i++ { + vals = append(vals, common.Hash{byte(i)}) + } + + // add item in batch + s.Add(vals...) + if s.Cardinality() < size { + t.Fatalf("bad size") + } +} diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go new file mode 100644 index 0000000..c5cb2dd --- /dev/null +++ b/eth/protocols/eth/protocol.go @@ -0,0 +1,345 @@ +// 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 eth + +import ( + "errors" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Constants to match up protocol versions and messages +const ( + ETH68 = 68 +) + +// ProtocolName is the official short name of the `eth` protocol used during +// devp2p capability negotiation. +const ProtocolName = "eth" + +// ProtocolVersions are the supported versions of the `eth` protocol (first +// is primary). +var ProtocolVersions = []uint{ETH68} + +// protocolLengths are the number of implemented message corresponding to +// different protocol versions. +var protocolLengths = map[uint]uint64{ETH68: 17} + +// maxMessageSize is the maximum cap on the size of a protocol message. +const maxMessageSize = 10 * 1024 * 1024 + +const ( + StatusMsg = 0x00 + NewBlockHashesMsg = 0x01 + TransactionsMsg = 0x02 + GetBlockHeadersMsg = 0x03 + BlockHeadersMsg = 0x04 + GetBlockBodiesMsg = 0x05 + BlockBodiesMsg = 0x06 + NewBlockMsg = 0x07 + NewPooledTransactionHashesMsg = 0x08 + GetPooledTransactionsMsg = 0x09 + PooledTransactionsMsg = 0x0a + GetReceiptsMsg = 0x0f + ReceiptsMsg = 0x10 +) + +var ( + errNoStatusMsg = errors.New("no status message") + errMsgTooLarge = errors.New("message too long") + errDecode = errors.New("invalid message") + errInvalidMsgCode = errors.New("invalid message code") + errProtocolVersionMismatch = errors.New("protocol version mismatch") + errNetworkIDMismatch = errors.New("network ID mismatch") + errGenesisMismatch = errors.New("genesis mismatch") + errForkIDRejected = errors.New("fork ID rejected") +) + +// Packet represents a p2p message in the `eth` protocol. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. +} + +// StatusPacket is the network packet for the status message. +type StatusPacket struct { + ProtocolVersion uint32 + NetworkID uint64 + TD *big.Int + Head common.Hash + Genesis common.Hash + ForkID forkid.ID +} + +// NewBlockHashesPacket is the network packet for the block announcements. +type NewBlockHashesPacket []struct { + Hash common.Hash // Hash of one particular block being announced + Number uint64 // Number of one particular block being announced +} + +// Unpack retrieves the block hashes and numbers from the announcement packet +// and returns them in a split flat format that's more consistent with the +// internal data structures. +func (p *NewBlockHashesPacket) Unpack() ([]common.Hash, []uint64) { + var ( + hashes = make([]common.Hash, len(*p)) + numbers = make([]uint64, len(*p)) + ) + for i, body := range *p { + hashes[i], numbers[i] = body.Hash, body.Number + } + return hashes, numbers +} + +// TransactionsPacket is the network packet for broadcasting new transactions. +type TransactionsPacket []*types.Transaction + +// 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) +} + +// GetBlockHeadersPacket represents a block header query with request ID wrapping. +type GetBlockHeadersPacket struct { + RequestId uint64 + *GetBlockHeadersRequest +} + +// 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) + } +} + +// BlockHeadersRequest represents a block header response. +type BlockHeadersRequest []*types.Header + +// BlockHeadersPacket represents a block header response over with request ID wrapping. +type BlockHeadersPacket struct { + RequestId uint64 + BlockHeadersRequest +} + +// BlockHeadersRLPResponse represents a block header response, to use when we already +// have the headers rlp encoded. +type BlockHeadersRLPResponse []rlp.RawValue + +// BlockHeadersRLPPacket represents a block header response with request ID wrapping. +type BlockHeadersRLPPacket struct { + RequestId uint64 + BlockHeadersRLPResponse +} + +// NewBlockPacket is the network packet for the block propagation message. +type NewBlockPacket struct { + Block *types.Block + TD *big.Int +} + +// GetBlockBodiesRequest represents a block body query. +type GetBlockBodiesRequest []common.Hash + +// GetBlockBodiesPacket represents a block body query with request ID wrapping. +type GetBlockBodiesPacket struct { + RequestId uint64 + GetBlockBodiesRequest +} + +// BlockBodiesResponse is the network packet for block content distribution. +type BlockBodiesResponse []*BlockBody + +// BlockBodiesPacket is the network packet for block content distribution with +// request ID wrapping. +type BlockBodiesPacket struct { + RequestId uint64 + BlockBodiesResponse +} + +// 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 BlockBodiesRLPResponse []rlp.RawValue + +// BlockBodiesRLPPacket is the BlockBodiesRLPResponse with request ID wrapping. +type BlockBodiesRLPPacket struct { + RequestId uint64 + BlockBodiesRLPResponse +} + +// BlockBody represents the data content of a single block. +type BlockBody struct { + Transactions []*types.Transaction // Transactions contained within a block + Uncles []*types.Header // Uncles contained within a block + Withdrawals []*types.Withdrawal `rlp:"optional"` // Withdrawals contained within a block +} + +// 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 *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) { + // TODO(matt): add support for withdrawals to fetchers + var ( + txset = make([][]*types.Transaction, len(*p)) + uncleset = make([][]*types.Header, len(*p)) + withdrawalset = make([][]*types.Withdrawal, len(*p)) + ) + for i, body := range *p { + txset[i], uncleset[i], withdrawalset[i] = body.Transactions, body.Uncles, body.Withdrawals + } + return txset, uncleset, withdrawalset +} + +// GetReceiptsRequest represents a block receipts query. +type GetReceiptsRequest []common.Hash + +// GetReceiptsPacket represents a block receipts query with request ID wrapping. +type GetReceiptsPacket struct { + RequestId uint64 + GetReceiptsRequest +} + +// ReceiptsResponse is the network packet for block receipts distribution. +type ReceiptsResponse [][]*types.Receipt + +// ReceiptsPacket is the network packet for block receipts distribution with +// request ID wrapping. +type ReceiptsPacket struct { + RequestId uint64 + ReceiptsResponse +} + +// ReceiptsRLPResponse is used for receipts, when we already have it encoded +type ReceiptsRLPResponse []rlp.RawValue + +// ReceiptsRLPPacket is ReceiptsRLPResponse with request ID wrapping. +type ReceiptsRLPPacket struct { + RequestId uint64 + ReceiptsRLPResponse +} + +// NewPooledTransactionHashesPacket represents a transaction announcement packet on eth/68 and newer. +type NewPooledTransactionHashesPacket struct { + Types []byte + Sizes []uint32 + Hashes []common.Hash +} + +// GetPooledTransactionsRequest represents a transaction query. +type GetPooledTransactionsRequest []common.Hash + +// GetPooledTransactionsPacket represents a transaction query with request ID wrapping. +type GetPooledTransactionsPacket struct { + RequestId uint64 + GetPooledTransactionsRequest +} + +// PooledTransactionsResponse is the network packet for transaction distribution. +type PooledTransactionsResponse []*types.Transaction + +// PooledTransactionsPacket is the network packet for transaction distribution +// with request ID wrapping. +type PooledTransactionsPacket struct { + RequestId uint64 + PooledTransactionsResponse +} + +// PooledTransactionsRLPResponse is the network packet for transaction distribution, used +// in the cases we already have them in rlp-encoded form +type PooledTransactionsRLPResponse []rlp.RawValue + +// PooledTransactionsRLPPacket is PooledTransactionsRLPResponse with request ID wrapping. +type PooledTransactionsRLPPacket struct { + RequestId uint64 + PooledTransactionsRLPResponse +} + +func (*StatusPacket) Name() string { return "Status" } +func (*StatusPacket) Kind() byte { return StatusMsg } + +func (*NewBlockHashesPacket) Name() string { return "NewBlockHashes" } +func (*NewBlockHashesPacket) Kind() byte { return NewBlockHashesMsg } + +func (*TransactionsPacket) Name() string { return "Transactions" } +func (*TransactionsPacket) Kind() byte { return TransactionsMsg } + +func (*GetBlockHeadersRequest) Name() string { return "GetBlockHeaders" } +func (*GetBlockHeadersRequest) Kind() byte { return GetBlockHeadersMsg } + +func (*BlockHeadersRequest) Name() string { return "BlockHeaders" } +func (*BlockHeadersRequest) Kind() byte { return BlockHeadersMsg } + +func (*GetBlockBodiesRequest) Name() string { return "GetBlockBodies" } +func (*GetBlockBodiesRequest) Kind() byte { return GetBlockBodiesMsg } + +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 (*NewPooledTransactionHashesPacket) Name() string { return "NewPooledTransactionHashes" } +func (*NewPooledTransactionHashesPacket) Kind() byte { return NewPooledTransactionHashesMsg } + +func (*GetPooledTransactionsRequest) Name() string { return "GetPooledTransactions" } +func (*GetPooledTransactionsRequest) Kind() byte { return GetPooledTransactionsMsg } + +func (*PooledTransactionsResponse) Name() string { return "PooledTransactions" } +func (*PooledTransactionsResponse) Kind() byte { return PooledTransactionsMsg } + +func (*GetReceiptsRequest) Name() string { return "GetReceipts" } +func (*GetReceiptsRequest) Kind() byte { return GetReceiptsMsg } + +func (*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 new file mode 100644 index 0000000..bc2545d --- /dev/null +++ b/eth/protocols/eth/protocol_test.go @@ -0,0 +1,248 @@ +// 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 eth + +import ( + "bytes" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Tests that the custom union field encoder and decoder works correctly. +func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { + // Create a "random" hash for testing + var hash common.Hash + for i := range hash { + hash[i] = byte(i) + } + // Assemble some table driven tests + tests := []struct { + packet *GetBlockHeadersRequest + fail bool + }{ + // Providing the origin as either a hash or a number should both work + {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: &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: &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 { + bytes, err := rlp.EncodeToBytes(tt.packet) + if err != nil && !tt.fail { + t.Fatalf("test %d: failed to encode packet: %v", i, err) + } else if err == nil && tt.fail { + t.Fatalf("test %d: encode should have failed", i) + } + if !tt.fail { + packet := new(GetBlockHeadersRequest) + if err := rlp.DecodeBytes(bytes, packet); err != nil { + t.Fatalf("test %d: failed to decode packet: %v", i, err) + } + if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount || + packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse { + t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet) + } + } + } +} + +// 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 + GetBlockHeadersPacket{1111, nil}, + BlockHeadersPacket{1111, nil}, + // Bodies + GetBlockBodiesPacket{1111, nil}, + BlockBodiesPacket{1111, nil}, + BlockBodiesRLPPacket{1111, nil}, + // Receipts + GetReceiptsPacket{1111, nil}, + ReceiptsPacket{1111, nil}, + // Transactions + GetPooledTransactionsPacket{1111, nil}, + PooledTransactionsPacket{1111, nil}, + PooledTransactionsRLPPacket{1111, nil}, + + // Headers + BlockHeadersPacket{1111, BlockHeadersRequest([]*types.Header{})}, + // Bodies + GetBlockBodiesPacket{1111, GetBlockBodiesRequest([]common.Hash{})}, + BlockBodiesPacket{1111, BlockBodiesResponse([]*BlockBody{})}, + BlockBodiesRLPPacket{1111, BlockBodiesRLPResponse([]rlp.RawValue{})}, + // Receipts + GetReceiptsPacket{1111, GetReceiptsRequest([]common.Hash{})}, + ReceiptsPacket{1111, ReceiptsResponse([][]*types.Receipt{})}, + // Transactions + 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) + } + } +} + +// TestMessages tests the encoding of all messages. +func TestMessages(t *testing.T) { + // Some basic structs used during testing + var ( + header *types.Header + blockBody *BlockBody + blockBodyRlp rlp.RawValue + txs []*types.Transaction + txRlps []rlp.RawValue + hashes []common.Hash + receipts []*types.Receipt + receiptsRlp rlp.RawValue + + err error + ) + header = &types.Header{ + Difficulty: big.NewInt(2222), + Number: big.NewInt(3333), + GasLimit: 4444, + GasUsed: 5555, + Time: 6666, + Extra: []byte{0x77, 0x88}, + } + // Init the transactions, taken from a different test + { + for _, hexrlp := range []string{ + "f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", + "f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", + } { + var tx *types.Transaction + rlpdata := common.FromHex(hexrlp) + if err := rlp.DecodeBytes(rlpdata, &tx); err != nil { + t.Fatal(err) + } + txs = append(txs, tx) + txRlps = append(txRlps, rlpdata) + } + } + // init the block body data, both object and rlp form + blockBody = &BlockBody{ + Transactions: txs, + Uncles: []*types.Header{header}, + } + blockBodyRlp, err = rlp.EncodeToBytes(blockBody) + if err != nil { + t.Fatal(err) + } + + hashes = []common.Hash{ + common.HexToHash("deadc0de"), + common.HexToHash("feedbeef"), + } + // init the receipts + { + receipts = []*types.Receipt{ + { + Status: types.ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*types.Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + TxHash: hashes[0], + ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 111111, + }, + } + rlpData, err := rlp.EncodeToBytes(receipts) + if err != nil { + t.Fatal(err) + } + receiptsRlp = rlpData + } + + for i, tc := range []struct { + message interface{} + want []byte + }{ + { + GetBlockHeadersPacket{1111, &GetBlockHeadersRequest{HashOrNumber{hashes[0], 0}, 5, 5, false}}, + common.FromHex("e8820457e4a000000000000000000000000000000000000000000000000000000000deadc0de050580"), + }, + { + GetBlockHeadersPacket{1111, &GetBlockHeadersRequest{HashOrNumber{common.Hash{}, 9999}, 5, 5, false}}, + common.FromHex("ca820457c682270f050580"), + }, + { + BlockHeadersPacket{1111, BlockHeadersRequest{header}}, + common.FromHex("f90202820457f901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { + GetBlockBodiesPacket{1111, GetBlockBodiesRequest(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + BlockBodiesPacket{1111, BlockBodiesResponse([]*BlockBody{blockBody})}, + common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { // Identical to non-rlp-shortcut version + BlockBodiesRLPPacket{1111, BlockBodiesRLPResponse([]rlp.RawValue{blockBodyRlp})}, + common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { + GetReceiptsPacket{1111, GetReceiptsRequest(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + ReceiptsPacket{1111, ReceiptsResponse([][]*types.Receipt{receipts})}, + common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), + }, + { + ReceiptsRLPPacket{1111, ReceiptsRLPResponse([]rlp.RawValue{receiptsRlp})}, + common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), + }, + { + GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + PooledTransactionsPacket{1111, PooledTransactionsResponse(txs)}, + common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"), + }, + { + PooledTransactionsRLPPacket{1111, PooledTransactionsRLPResponse(txRlps)}, + common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"), + }, + } { + if have, _ := rlp.EncodeToBytes(tc.message); !bytes.Equal(have, tc.want) { + t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, tc.message, have, tc.want) + } + } +} diff --git a/eth/protocols/eth/tracker.go b/eth/protocols/eth/tracker.go new file mode 100644 index 0000000..324fd22 --- /dev/null +++ b/eth/protocols/eth/tracker.go @@ -0,0 +1,26 @@ +// 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 eth + +import ( + "time" + + "github.com/ethereum/go-ethereum/p2p/tracker" +) + +// requestTracker is a singleton tracker for eth/66 and newer request times. +var requestTracker = tracker.New(ProtocolName, 5*time.Minute) diff --git a/eth/protocols/snap/discovery.go b/eth/protocols/snap/discovery.go new file mode 100644 index 0000000..684ec7e --- /dev/null +++ b/eth/protocols/snap/discovery.go @@ -0,0 +1,32 @@ +// 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 snap + +import ( + "github.com/ethereum/go-ethereum/rlp" +) + +// enrEntry is the ENR entry which advertises `snap` protocol on the discovery. +type enrEntry struct { + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +// ENRKey implements enr.Entry. +func (e enrEntry) ENRKey() string { + return "snap" +} diff --git a/eth/protocols/snap/gentrie.go b/eth/protocols/snap/gentrie.go new file mode 100644 index 0000000..6255fb2 --- /dev/null +++ b/eth/protocols/snap/gentrie.go @@ -0,0 +1,287 @@ +// Copyright 2024 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 snap + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie" +) + +// genTrie interface is used by the snap syncer to generate merkle tree nodes +// based on a received batch of states. +type genTrie interface { + // update inserts the state item into generator trie. + update(key, value []byte) error + + // commit flushes the right boundary nodes if complete flag is true. This + // function must be called before flushing the associated database batch. + commit(complete bool) common.Hash +} + +// pathTrie is a wrapper over the stackTrie, incorporating numerous additional +// logics to handle the semi-completed trie and potential leftover dangling +// nodes in the database. It is utilized for constructing the merkle tree nodes +// in path mode during the snap sync process. +type pathTrie struct { + owner common.Hash // identifier of trie owner, empty for account trie + tr *trie.StackTrie // underlying raw stack trie + first []byte // the path of first committed node by stackTrie + last []byte // the path of last committed node by stackTrie + + // This flag indicates whether nodes on the left boundary are skipped for + // committing. If set, the left boundary nodes are considered incomplete + // due to potentially missing left children. + skipLeftBoundary bool + db ethdb.KeyValueReader + batch ethdb.Batch +} + +// newPathTrie initializes the path trie. +func newPathTrie(owner common.Hash, skipLeftBoundary bool, db ethdb.KeyValueReader, batch ethdb.Batch) *pathTrie { + tr := &pathTrie{ + owner: owner, + skipLeftBoundary: skipLeftBoundary, + db: db, + batch: batch, + } + tr.tr = trie.NewStackTrie(tr.onTrieNode) + return tr +} + +// onTrieNode is invoked whenever a new node is committed by the stackTrie. +// +// As the committed nodes might be incomplete if they are on the boundaries +// (left or right), this function has the ability to detect the incomplete +// ones and filter them out for committing. +// +// Additionally, the assumption is made that there may exist leftover dangling +// nodes in the database. This function has the ability to detect the dangling +// nodes that fall within the path space of committed nodes (specifically on +// the path covered by internal extension nodes) and remove them from the +// database. This property ensures that the entire path space is uniquely +// occupied by committed nodes. +// +// Furthermore, all leftover dangling nodes along the path from committed nodes +// to the trie root (left and right boundaries) should be removed as well; +// otherwise, they might potentially disrupt the state healing process. +func (t *pathTrie) onTrieNode(path []byte, hash common.Hash, blob []byte) { + // Filter out the nodes on the left boundary if skipLeftBoundary is + // configured. Nodes are considered to be on the left boundary if + // it's the first one to be committed, or the parent/ancestor of the + // first committed node. + if t.skipLeftBoundary && (t.first == nil || bytes.HasPrefix(t.first, path)) { + if t.first == nil { + // Memorize the path of first committed node, which is regarded + // as left boundary. Deep-copy is necessary as the path given + // is volatile. + t.first = append([]byte{}, path...) + + // The left boundary can be uniquely determined by the first committed node + // from stackTrie (e.g., N_1), as the shared path prefix between the first + // two inserted state items is deterministic (the path of N_3). The path + // from trie root towards the first committed node is considered the left + // boundary. The potential leftover dangling nodes on left boundary should + // be cleaned out. + // + // +-----+ + // | N_3 | shared path prefix of state_1 and state_2 + // +-----+ + // /- -\ + // +-----+ +-----+ + // First committed node | N_1 | | N_2 | latest inserted node (contain state_2) + // +-----+ +-----+ + // + // The node with the path of the first committed one (e.g, N_1) is not + // removed because it's a sibling of the nodes we want to commit, not + // the parent or ancestor. + for i := 0; i < len(path); i++ { + t.delete(path[:i], false) + } + } + return + } + // If boundary filtering is not configured, or the node is not on the left + // boundary, commit it to database. + // + // Note: If the current committed node is an extension node, then the nodes + // falling within the path between itself and its standalone (not embedded + // in parent) child should be cleaned out for exclusively occupy the inner + // path. + // + // This is essential in snap sync to avoid leaving dangling nodes within + // this range covered by extension node which could potentially break the + // state healing. + // + // The extension node is detected if its path is the prefix of last committed + // one and path gap is larger than one. If the path gap is only one byte, + // the current node could either be a full node, or an extension with single + // byte key. In either case, no gaps will be left in the path. + if t.last != nil && bytes.HasPrefix(t.last, path) && len(t.last)-len(path) > 1 { + for i := len(path) + 1; i < len(t.last); i++ { + t.delete(t.last[:i], true) + } + } + t.write(path, blob) + + // Update the last flag. Deep-copy is necessary as the provided path is volatile. + if t.last == nil { + t.last = append([]byte{}, path...) + } else { + t.last = append(t.last[:0], path...) + } +} + +// write commits the node write to provided database batch in path mode. +func (t *pathTrie) write(path []byte, blob []byte) { + if t.owner == (common.Hash{}) { + rawdb.WriteAccountTrieNode(t.batch, path, blob) + } else { + rawdb.WriteStorageTrieNode(t.batch, t.owner, path, blob) + } +} + +func (t *pathTrie) deleteAccountNode(path []byte, inner bool) { + if inner { + accountInnerLookupGauge.Inc(1) + } else { + accountOuterLookupGauge.Inc(1) + } + if !rawdb.HasAccountTrieNode(t.db, path) { + return + } + if inner { + accountInnerDeleteGauge.Inc(1) + } else { + accountOuterDeleteGauge.Inc(1) + } + rawdb.DeleteAccountTrieNode(t.batch, path) +} + +func (t *pathTrie) deleteStorageNode(path []byte, inner bool) { + if inner { + storageInnerLookupGauge.Inc(1) + } else { + storageOuterLookupGauge.Inc(1) + } + if !rawdb.HasStorageTrieNode(t.db, t.owner, path) { + return + } + if inner { + storageInnerDeleteGauge.Inc(1) + } else { + storageOuterDeleteGauge.Inc(1) + } + rawdb.DeleteStorageTrieNode(t.batch, t.owner, path) +} + +// delete commits the node deletion to provided database batch in path mode. +func (t *pathTrie) delete(path []byte, inner bool) { + if t.owner == (common.Hash{}) { + t.deleteAccountNode(path, inner) + } else { + t.deleteStorageNode(path, inner) + } +} + +// update implements genTrie interface, inserting a (key, value) pair into the +// stack trie. +func (t *pathTrie) update(key, value []byte) error { + return t.tr.Update(key, value) +} + +// commit implements genTrie interface, flushing the right boundary if it's +// considered as complete. Otherwise, the nodes on the right boundary are +// discarded and cleaned up. +// +// Note, this function must be called before flushing database batch, otherwise, +// dangling nodes might be left in database. +func (t *pathTrie) commit(complete bool) common.Hash { + // If the right boundary is claimed as complete, flush them out. + // The nodes on both left and right boundary will still be filtered + // out if left boundary filtering is configured. + if complete { + // Commit all inserted but not yet committed nodes(on the right + // boundary) in the stackTrie. + hash := t.tr.Hash() + if t.skipLeftBoundary { + return common.Hash{} // hash is meaningless if left side is incomplete + } + return hash + } + // Discard nodes on the right boundary as it's claimed as incomplete. These + // nodes might be incomplete due to missing children on the right side. + // Furthermore, the potential leftover nodes on right boundary should also + // be cleaned out. + // + // The right boundary can be uniquely determined by the last committed node + // from stackTrie (e.g., N_1), as the shared path prefix between the last + // two inserted state items is deterministic (the path of N_3). The path + // from trie root towards the last committed node is considered the right + // boundary (root to N_3). + // + // +-----+ + // | N_3 | shared path prefix of last two states + // +-----+ + // /- -\ + // +-----+ +-----+ + // Last committed node | N_1 | | N_2 | latest inserted node (contain last state) + // +-----+ +-----+ + // + // Another interesting scenario occurs when the trie is committed due to + // too many items being accumulated in the batch. To flush them out to + // the database, the path of the last inserted node (N_2) is temporarily + // treated as an incomplete right boundary, and nodes on this path are + // removed (e.g. from root to N_3). + // However, this path will be reclaimed as an internal path by inserting + // more items after the batch flush. New nodes on this path can be committed + // with no issues as they are actually complete. Also, from a database + // perspective, first deleting and then rewriting is a valid data update. + for i := 0; i < len(t.last); i++ { + t.delete(t.last[:i], false) + } + return common.Hash{} // the hash is meaningless for incomplete commit +} + +// hashTrie is a wrapper over the stackTrie for implementing genTrie interface. +type hashTrie struct { + tr *trie.StackTrie +} + +// newHashTrie initializes the hash trie. +func newHashTrie(batch ethdb.Batch) *hashTrie { + return &hashTrie{tr: trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteLegacyTrieNode(batch, hash, blob) + })} +} + +// update implements genTrie interface, inserting a (key, value) pair into +// the stack trie. +func (t *hashTrie) update(key, value []byte) error { + return t.tr.Update(key, value) +} + +// commit implements genTrie interface, committing the nodes on right boundary. +func (t *hashTrie) commit(complete bool) common.Hash { + if !complete { + return common.Hash{} // the hash is meaningless for incomplete commit + } + return t.tr.Hash() // return hash only if it's claimed as complete +} diff --git a/eth/protocols/snap/gentrie_test.go b/eth/protocols/snap/gentrie_test.go new file mode 100644 index 0000000..1fb2dbc --- /dev/null +++ b/eth/protocols/snap/gentrie_test.go @@ -0,0 +1,553 @@ +// Copyright 2024 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 snap + +import ( + "bytes" + "math/rand" + "slices" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/testrand" + "github.com/ethereum/go-ethereum/trie" +) + +type replayer struct { + paths []string // sort in fifo order + hashes []common.Hash // empty for deletion + unknowns int // counter for unknown write +} + +func newBatchReplay() *replayer { + return &replayer{} +} + +func (r *replayer) decode(key []byte, value []byte) { + account := rawdb.IsAccountTrieNode(key) + storage := rawdb.IsStorageTrieNode(key) + if !account && !storage { + r.unknowns += 1 + return + } + var path []byte + if account { + _, path = rawdb.ResolveAccountTrieNodeKey(key) + } else { + _, owner, inner := rawdb.ResolveStorageTrieNode(key) + path = append(owner.Bytes(), inner...) + } + r.paths = append(r.paths, string(path)) + + if len(value) == 0 { + r.hashes = append(r.hashes, common.Hash{}) + } else { + r.hashes = append(r.hashes, crypto.Keccak256Hash(value)) + } +} + +// updates returns a set of effective mutations. Multiple mutations targeting +// the same node path will be merged in FIFO order. +func (r *replayer) modifies() map[string]common.Hash { + set := make(map[string]common.Hash) + for i, path := range r.paths { + set[path] = r.hashes[i] + } + return set +} + +// updates returns the number of updates. +func (r *replayer) updates() int { + var count int + for _, hash := range r.modifies() { + if hash == (common.Hash{}) { + continue + } + count++ + } + return count +} + +// Put inserts the given value into the key-value data store. +func (r *replayer) Put(key []byte, value []byte) error { + r.decode(key, value) + return nil +} + +// Delete removes the key from the key-value data store. +func (r *replayer) Delete(key []byte) error { + r.decode(key, nil) + return nil +} + +func byteToHex(str []byte) []byte { + l := len(str) * 2 + var nibbles = make([]byte, l) + for i, b := range str { + nibbles[i*2] = b / 16 + nibbles[i*2+1] = b % 16 + } + return nibbles +} + +// innerNodes returns the internal nodes narrowed by two boundaries along with +// the leftmost and rightmost sub-trie roots. +func innerNodes(first, last []byte, includeLeft, includeRight bool, nodes map[string]common.Hash, t *testing.T) (map[string]common.Hash, []byte, []byte) { + var ( + leftRoot []byte + rightRoot []byte + firstHex = byteToHex(first) + lastHex = byteToHex(last) + inner = make(map[string]common.Hash) + ) + for path, hash := range nodes { + if hash == (common.Hash{}) { + t.Fatalf("Unexpected deletion, %v", []byte(path)) + } + // Filter out the siblings on the left side or the left boundary nodes. + if !includeLeft && (bytes.Compare(firstHex, []byte(path)) > 0 || bytes.HasPrefix(firstHex, []byte(path))) { + continue + } + // Filter out the siblings on the right side or the right boundary nodes. + if !includeRight && (bytes.Compare(lastHex, []byte(path)) < 0 || bytes.HasPrefix(lastHex, []byte(path))) { + continue + } + inner[path] = hash + + // Track the path of the leftmost sub trie root + if leftRoot == nil || bytes.Compare(leftRoot, []byte(path)) > 0 { + leftRoot = []byte(path) + } + // Track the path of the rightmost sub trie root + if rightRoot == nil || + (bytes.Compare(rightRoot, []byte(path)) < 0) || + (bytes.Compare(rightRoot, []byte(path)) > 0 && bytes.HasPrefix(rightRoot, []byte(path))) { + rightRoot = []byte(path) + } + } + return inner, leftRoot, rightRoot +} + +func buildPartial(owner common.Hash, db ethdb.KeyValueReader, batch ethdb.Batch, entries []*kv, first, last int) *replayer { + tr := newPathTrie(owner, first != 0, db, batch) + for i := first; i <= last; i++ { + tr.update(entries[i].k, entries[i].v) + } + tr.commit(last == len(entries)-1) + + replay := newBatchReplay() + batch.Replay(replay) + + return replay +} + +// TestPartialGentree verifies if the trie constructed with partial states can +// generate consistent trie nodes that match those of the full trie. +func TestPartialGentree(t *testing.T) { + for round := 0; round < 100; round++ { + var ( + n = rand.Intn(1024) + 10 + entries []*kv + ) + for i := 0; i < n; i++ { + var val []byte + if rand.Intn(3) == 0 { + val = testrand.Bytes(3) + } else { + val = testrand.Bytes(32) + } + entries = append(entries, &kv{ + k: testrand.Bytes(32), + v: val, + }) + } + slices.SortFunc(entries, (*kv).cmp) + + nodes := make(map[string]common.Hash) + tr := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + nodes[string(path)] = hash + }) + for i := 0; i < len(entries); i++ { + tr.Update(entries[i].k, entries[i].v) + } + tr.Hash() + + check := func(first, last int) { + var ( + db = rawdb.NewMemoryDatabase() + batch = db.NewBatch() + ) + // Build the partial tree with specific boundaries + r := buildPartial(common.Hash{}, db, batch, entries, first, last) + if r.unknowns > 0 { + t.Fatalf("Unknown database write: %d", r.unknowns) + } + + // Ensure all the internal nodes are produced + var ( + set = r.modifies() + inner, _, _ = innerNodes(entries[first].k, entries[last].k, first == 0, last == len(entries)-1, nodes, t) + ) + for path, hash := range inner { + if _, ok := set[path]; !ok { + t.Fatalf("Missing nodes %v", []byte(path)) + } + if hash != set[path] { + t.Fatalf("Inconsistent node, want %x, got: %x", hash, set[path]) + } + } + if r.updates() != len(inner) { + t.Fatalf("Unexpected node write detected, want: %d, got: %d", len(inner), r.updates()) + } + } + for j := 0; j < 100; j++ { + var ( + first int + last int + ) + for { + first = rand.Intn(len(entries)) + last = rand.Intn(len(entries)) + if first <= last { + break + } + } + check(first, last) + } + var cases = []struct { + first int + last int + }{ + {0, len(entries) - 1}, // full + {1, len(entries) - 1}, // no left + {2, len(entries) - 1}, // no left + {2, len(entries) - 2}, // no left and right + {2, len(entries) - 2}, // no left and right + {len(entries) / 2, len(entries) / 2}, // single + {0, 0}, // single first + {len(entries) - 1, len(entries) - 1}, // single last + } + for _, c := range cases { + check(c.first, c.last) + } + } +} + +// TestGentreeDanglingClearing tests if the dangling nodes falling within the +// path space of constructed tree can be correctly removed. +func TestGentreeDanglingClearing(t *testing.T) { + for round := 0; round < 100; round++ { + var ( + n = rand.Intn(1024) + 10 + entries []*kv + ) + for i := 0; i < n; i++ { + var val []byte + if rand.Intn(3) == 0 { + val = testrand.Bytes(3) + } else { + val = testrand.Bytes(32) + } + entries = append(entries, &kv{ + k: testrand.Bytes(32), + v: val, + }) + } + slices.SortFunc(entries, (*kv).cmp) + + nodes := make(map[string]common.Hash) + tr := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + nodes[string(path)] = hash + }) + for i := 0; i < len(entries); i++ { + tr.Update(entries[i].k, entries[i].v) + } + tr.Hash() + + check := func(first, last int) { + var ( + db = rawdb.NewMemoryDatabase() + batch = db.NewBatch() + ) + // Write the junk nodes as the dangling + var injects []string + for path := range nodes { + for i := 0; i < len(path); i++ { + _, ok := nodes[path[:i]] + if ok { + continue + } + injects = append(injects, path[:i]) + } + } + if len(injects) == 0 { + return + } + for _, path := range injects { + rawdb.WriteAccountTrieNode(db, []byte(path), testrand.Bytes(32)) + } + + // Build the partial tree with specific range + replay := buildPartial(common.Hash{}, db, batch, entries, first, last) + if replay.unknowns > 0 { + t.Fatalf("Unknown database write: %d", replay.unknowns) + } + set := replay.modifies() + + // Make sure the injected junks falling within the path space of + // committed trie nodes are correctly deleted. + _, leftRoot, rightRoot := innerNodes(entries[first].k, entries[last].k, first == 0, last == len(entries)-1, nodes, t) + for _, path := range injects { + if bytes.Compare([]byte(path), leftRoot) < 0 && !bytes.HasPrefix(leftRoot, []byte(path)) { + continue + } + if bytes.Compare([]byte(path), rightRoot) > 0 { + continue + } + if hash, ok := set[path]; !ok || hash != (common.Hash{}) { + t.Fatalf("Missing delete, %v", []byte(path)) + } + } + } + for j := 0; j < 100; j++ { + var ( + first int + last int + ) + for { + first = rand.Intn(len(entries)) + last = rand.Intn(len(entries)) + if first <= last { + break + } + } + check(first, last) + } + var cases = []struct { + first int + last int + }{ + {0, len(entries) - 1}, // full + {1, len(entries) - 1}, // no left + {2, len(entries) - 1}, // no left + {2, len(entries) - 2}, // no left and right + {2, len(entries) - 2}, // no left and right + {len(entries) / 2, len(entries) / 2}, // single + {0, 0}, // single first + {len(entries) - 1, len(entries) - 1}, // single last + } + for _, c := range cases { + check(c.first, c.last) + } + } +} + +// TestFlushPartialTree tests the gentrie can produce complete inner trie nodes +// even with lots of batch flushes. +func TestFlushPartialTree(t *testing.T) { + var entries []*kv + for i := 0; i < 1024; i++ { + var val []byte + if rand.Intn(3) == 0 { + val = testrand.Bytes(3) + } else { + val = testrand.Bytes(32) + } + entries = append(entries, &kv{ + k: testrand.Bytes(32), + v: val, + }) + } + slices.SortFunc(entries, (*kv).cmp) + + nodes := make(map[string]common.Hash) + tr := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + nodes[string(path)] = hash + }) + for i := 0; i < len(entries); i++ { + tr.Update(entries[i].k, entries[i].v) + } + tr.Hash() + + var cases = []struct { + first int + last int + }{ + {0, len(entries) - 1}, // full + {1, len(entries) - 1}, // no left + {10, len(entries) - 1}, // no left + {10, len(entries) - 2}, // no left and right + {10, len(entries) - 10}, // no left and right + {11, 11}, // single + {0, 0}, // single first + {len(entries) - 1, len(entries) - 1}, // single last + } + for _, c := range cases { + var ( + db = rawdb.NewMemoryDatabase() + batch = db.NewBatch() + combined = db.NewBatch() + ) + inner, _, _ := innerNodes(entries[c.first].k, entries[c.last].k, c.first == 0, c.last == len(entries)-1, nodes, t) + + tr := newPathTrie(common.Hash{}, c.first != 0, db, batch) + for i := c.first; i <= c.last; i++ { + tr.update(entries[i].k, entries[i].v) + if rand.Intn(2) == 0 { + tr.commit(false) + + batch.Replay(combined) + batch.Write() + batch.Reset() + } + } + tr.commit(c.last == len(entries)-1) + + batch.Replay(combined) + batch.Write() + batch.Reset() + + r := newBatchReplay() + combined.Replay(r) + + // Ensure all the internal nodes are produced + set := r.modifies() + for path, hash := range inner { + if _, ok := set[path]; !ok { + t.Fatalf("Missing nodes %v", []byte(path)) + } + if hash != set[path] { + t.Fatalf("Inconsistent node, want %x, got: %x", hash, set[path]) + } + } + if r.updates() != len(inner) { + t.Fatalf("Unexpected node write detected, want: %d, got: %d", len(inner), r.updates()) + } + } +} + +// TestBoundSplit ensures two consecutive trie chunks are not overlapped with +// each other. +func TestBoundSplit(t *testing.T) { + var entries []*kv + for i := 0; i < 1024; i++ { + var val []byte + if rand.Intn(3) == 0 { + val = testrand.Bytes(3) + } else { + val = testrand.Bytes(32) + } + entries = append(entries, &kv{ + k: testrand.Bytes(32), + v: val, + }) + } + slices.SortFunc(entries, (*kv).cmp) + + for j := 0; j < 100; j++ { + var ( + next int + last int + db = rawdb.NewMemoryDatabase() + + lastRightRoot []byte + ) + for { + if next == len(entries) { + break + } + last = rand.Intn(len(entries)-next) + next + + r := buildPartial(common.Hash{}, db, db.NewBatch(), entries, next, last) + set := r.modifies() + + // Skip if the chunk is zero-size + if r.updates() == 0 { + next = last + 1 + continue + } + + // Ensure the updates in two consecutive chunks are not overlapped. + // The only overlapping part should be deletion. + if lastRightRoot != nil && len(set) > 0 { + // Derive the path of left-most node in this chunk + var leftRoot []byte + for path, hash := range r.modifies() { + if hash == (common.Hash{}) { + t.Fatalf("Unexpected deletion %v", []byte(path)) + } + if leftRoot == nil || bytes.Compare(leftRoot, []byte(path)) > 0 { + leftRoot = []byte(path) + } + } + if bytes.HasPrefix(lastRightRoot, leftRoot) || bytes.HasPrefix(leftRoot, lastRightRoot) { + t.Fatalf("Two chunks are not correctly separated, lastRight: %v, left: %v", lastRightRoot, leftRoot) + } + } + + // Track the updates as the last chunk + var rightRoot []byte + for path := range set { + if rightRoot == nil || + (bytes.Compare(rightRoot, []byte(path)) < 0) || + (bytes.Compare(rightRoot, []byte(path)) > 0 && bytes.HasPrefix(rightRoot, []byte(path))) { + rightRoot = []byte(path) + } + } + lastRightRoot = rightRoot + next = last + 1 + } + } +} + +// TestTinyPartialTree tests if the partial tree is too tiny(has less than two +// states), then nothing should be committed. +func TestTinyPartialTree(t *testing.T) { + var entries []*kv + for i := 0; i < 1024; i++ { + var val []byte + if rand.Intn(3) == 0 { + val = testrand.Bytes(3) + } else { + val = testrand.Bytes(32) + } + entries = append(entries, &kv{ + k: testrand.Bytes(32), + v: val, + }) + } + slices.SortFunc(entries, (*kv).cmp) + + for i := 0; i < len(entries); i++ { + next := i + last := i + 1 + if last >= len(entries) { + last = len(entries) - 1 + } + db := rawdb.NewMemoryDatabase() + r := buildPartial(common.Hash{}, db, db.NewBatch(), entries, next, last) + + if next != 0 && last != len(entries)-1 { + if r.updates() != 0 { + t.Fatalf("Unexpected data writes, got: %d", r.updates()) + } + } + } +} diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go new file mode 100644 index 0000000..5cbe9d1 --- /dev/null +++ b/eth/protocols/snap/handler.go @@ -0,0 +1,571 @@ +// 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 snap + +import ( + "bytes" + "fmt" + "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" + "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 ( + // softResponseLimit is the target maximum size of replies to data retrievals. + softResponseLimit = 2 * 1024 * 1024 + + // maxCodeLookups is the maximum number of bytecodes to serve. This number is + // there to limit the number of disk lookups. + maxCodeLookups = 1024 + + // stateLookupSlack defines the ratio by how much a state response can exceed + // the requested limit in order to try and avoid breaking up contracts into + // multiple packages and proving them. + stateLookupSlack = 0.1 + + // maxTrieNodeLookups is the maximum number of state trie nodes to serve. This + // number is there to limit the number of disk lookups. + maxTrieNodeLookups = 1024 + + // maxTrieNodeTimeSpent is the maximum time we should spend on looking up trie nodes. + // If we spend too much time, then it's a fairly high chance of timing out + // at the remote side, which means all the work is in vain. + maxTrieNodeTimeSpent = 5 * time.Second +) + +// Handler is a callback to invoke from an outside runner after the boilerplate +// exchanges have passed. +type Handler func(peer *Peer) error + +// Backend defines the data retrieval methods to serve remote requests and the +// callback methods to invoke on remote deliveries. +type Backend interface { + // Chain retrieves the blockchain object to serve data. + Chain() *core.BlockChain + + // RunPeer is invoked when a peer joins on the `eth` protocol. The handler + // should do any peer maintenance work, handshakes and validations. If all + // is passed, control should be given back to the `handler` to process the + // inbound messages going forward. + RunPeer(peer *Peer, handler Handler) error + + // PeerInfo retrieves all known `snap` information about a peer. + PeerInfo(id enode.ID) interface{} + + // Handle is a callback to be invoked when a data packet is received from + // the remote peer. Only packets not consumed by the protocol handler will + // be forwarded to the backend. + Handle(peer *Peer, packet Packet) error +} + +// MakeProtocols constructs the P2P protocol definitions for `snap`. +func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { + // Filter the discovery iterator for nodes advertising snap support. + dnsdisc = enode.Filter(dnsdisc, func(n *enode.Node) bool { + var snap enrEntry + return n.Load(&snap) == nil + }) + + protocols := make([]p2p.Protocol, len(ProtocolVersions)) + for i, version := range ProtocolVersions { + version := version // Closure + + protocols[i] = p2p.Protocol{ + Name: ProtocolName, + Version: version, + Length: protocolLengths[version], + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return backend.RunPeer(NewPeer(version, p, rw), func(peer *Peer) error { + return Handle(backend, peer) + }) + }, + NodeInfo: func() interface{} { + return nodeInfo(backend.Chain()) + }, + PeerInfo: func(id enode.ID) interface{} { + return backend.PeerInfo(id) + }, + Attributes: []enr.Entry{&enrEntry{}}, + DialCandidates: dnsdisc, + } + } + return protocols +} + +// Handle is the callback invoked to manage the life cycle of a `snap` peer. +// When this function terminates, the peer is disconnected. +func Handle(backend Backend, peer *Peer) error { + for { + if err := HandleMessage(backend, peer); err != nil { + peer.Log().Debug("Message handling failed in `snap`", "err", err) + return err + } + } +} + +// HandleMessage is invoked whenever an inbound message is received from a +// remote peer on the `snap` protocol. The remote connection is torn down upon +// returning any error. +func HandleMessage(backend Backend, peer *Peer) error { + // Read the next message from the remote peer, and ensure it's fully consumed + msg, err := peer.rw.ReadMsg() + if err != nil { + return err + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + defer msg.Discard() + start := time.Now() + // 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) + defer func(start time.Time) { + sampler := func() metrics.Sample { + return metrics.ResettingSample( + metrics.NewExpDecaySample(1028, 0.015), + ) + } + metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) + }(start) + } + // Handle the message depending on its contents + switch { + case msg.Code == GetAccountRangeMsg: + // Decode the account retrieval request + var req GetAccountRangePacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Service the request, potentially returning nothing in case of errors + accounts, proofs := ServiceGetAccountRangeQuery(backend.Chain(), &req) + + // Send back anything accumulated (or empty in case of errors) + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ + ID: req.ID, + Accounts: accounts, + Proof: proofs, + }) + + case msg.Code == AccountRangeMsg: + // A range of accounts arrived to one of our previous requests + res := new(AccountRangePacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Ensure the range is monotonically increasing + for i := 1; i < len(res.Accounts); i++ { + if bytes.Compare(res.Accounts[i-1].Hash[:], res.Accounts[i].Hash[:]) >= 0 { + return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:]) + } + } + requestTracker.Fulfil(peer.id, peer.version, AccountRangeMsg, res.ID) + + return backend.Handle(peer, res) + + case msg.Code == GetStorageRangesMsg: + // Decode the storage retrieval request + var req GetStorageRangesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Service the request, potentially returning nothing in case of errors + slots, proofs := ServiceGetStorageRangesQuery(backend.Chain(), &req) + + // Send back anything accumulated (or empty in case of errors) + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ + ID: req.ID, + Slots: slots, + Proof: proofs, + }) + + case msg.Code == StorageRangesMsg: + // A range of storage slots arrived to one of our previous requests + res := new(StorageRangesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Ensure the ranges are monotonically increasing + for i, slots := range res.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[:]) + } + } + } + requestTracker.Fulfil(peer.id, peer.version, StorageRangesMsg, res.ID) + + return backend.Handle(peer, res) + + case msg.Code == GetByteCodesMsg: + // Decode bytecode retrieval request + var req GetByteCodesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Service the request, potentially returning nothing in case of errors + codes := ServiceGetByteCodesQuery(backend.Chain(), &req) + + // Send back anything accumulated (or empty in case of errors) + return p2p.Send(peer.rw, ByteCodesMsg, &ByteCodesPacket{ + ID: req.ID, + Codes: codes, + }) + + case msg.Code == ByteCodesMsg: + // A batch of byte codes arrived to one of our previous requests + res := new(ByteCodesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + requestTracker.Fulfil(peer.id, peer.version, ByteCodesMsg, res.ID) + + return backend.Handle(peer, res) + + case msg.Code == GetTrieNodesMsg: + // Decode trie node retrieval request + var req GetTrieNodesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Service the request, potentially returning nothing in case of errors + nodes, err := ServiceGetTrieNodesQuery(backend.Chain(), &req, start) + if err != nil { + return err + } + // Send back anything accumulated (or empty in case of errors) + return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ + ID: req.ID, + Nodes: nodes, + }) + + case msg.Code == TrieNodesMsg: + // A batch of trie nodes arrived to one of our previous requests + res := new(TrieNodesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + requestTracker.Fulfil(peer.id, peer.version, TrieNodesMsg, res.ID) + + return backend.Handle(peer, res) + + default: + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) + } +} + +// ServiceGetAccountRangeQuery assembles the response to an account range query. +// It is exposed to allow external packages to test protocol behavior. +func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePacket) ([]*AccountData, [][]byte) { + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // Retrieve the requested state and bail out if non existent + tr, err := trie.New(trie.StateTrieID(req.Root), chain.TrieDB()) + if err != nil { + return nil, nil + } + it, err := chain.Snapshots().AccountIterator(req.Root, req.Origin) + if err != nil { + return nil, nil + } + // Iterate over the requested range and pile accounts up + var ( + accounts []*AccountData + size uint64 + last common.Hash + ) + for it.Next() { + hash, account := it.Hash(), common.CopyBytes(it.Account()) + + // Track the returned interval for the Merkle proofs + last = hash + + // Assemble the reply item + size += uint64(common.HashLength + len(account)) + accounts = append(accounts, &AccountData{ + Hash: hash, + Body: account, + }) + // If we've exceeded the request threshold, abort + if bytes.Compare(hash[:], req.Limit[:]) >= 0 { + break + } + if size > req.Bytes { + break + } + } + it.Release() + + // Generate the Merkle proofs for the first and last account + proof := trienode.NewProofSet() + if err := tr.Prove(req.Origin[:], proof); err != nil { + log.Warn("Failed to prove account range", "origin", req.Origin, "err", err) + return nil, nil + } + if last != (common.Hash{}) { + if err := tr.Prove(last[:], proof); err != nil { + log.Warn("Failed to prove account range", "last", last, "err", err) + return nil, nil + } + } + return accounts, proof.List() +} + +func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesPacket) ([][]*StorageData, [][]byte) { + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // TODO(karalabe): Do we want to enforce > 0 accounts and 1 account if origin is set? + // TODO(karalabe): - Logging locally is not ideal as remote faults annoy the local user + // TODO(karalabe): - Dropping the remote peer is less flexible wrt client bugs (slow is better than non-functional) + + // Calculate the hard limit at which to abort, even if mid storage trie + hardLimit := uint64(float64(req.Bytes) * (1 + stateLookupSlack)) + + // Retrieve storage ranges until the packet limit is reached + var ( + slots [][]*StorageData + proofs [][]byte + size uint64 + ) + for _, account := range req.Accounts { + // If we've exceeded the requested data limit, abort without opening + // a new storage range (that we'd need to prove due to exceeded size) + if size >= req.Bytes { + break + } + // The first account might start from a different origin and end sooner + var origin common.Hash + if len(req.Origin) > 0 { + origin, req.Origin = common.BytesToHash(req.Origin), nil + } + var limit = common.MaxHash + if len(req.Limit) > 0 { + limit, req.Limit = common.BytesToHash(req.Limit), nil + } + // Retrieve the requested state and bail out if non existent + it, err := chain.Snapshots().StorageIterator(req.Root, account, origin) + if err != nil { + return nil, nil + } + // Iterate over the requested range and pile slots up + var ( + storage []*StorageData + last common.Hash + abort bool + ) + for it.Next() { + if size >= hardLimit { + abort = true + break + } + hash, slot := it.Hash(), common.CopyBytes(it.Slot()) + + // Track the returned interval for the Merkle proofs + last = hash + + // Assemble the reply item + size += uint64(common.HashLength + len(slot)) + storage = append(storage, &StorageData{ + Hash: hash, + Body: slot, + }) + // If we've exceeded the request threshold, abort + if bytes.Compare(hash[:], limit[:]) >= 0 { + break + } + } + if len(storage) > 0 { + slots = append(slots, storage) + } + it.Release() + + // Generate the Merkle proofs for the first and last storage slot, but + // only if the response was capped. If the entire storage trie included + // in the response, no need for any proofs. + 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.TrieDB()) + if err != nil { + return nil, nil + } + acc, err := accTrie.GetAccountByHash(account) + if err != nil || acc == nil { + return nil, nil + } + id := trie.StorageTrieID(req.Root, account, acc.Root) + stTrie, err := trie.NewStateTrie(id, chain.TrieDB()) + if err != nil { + return nil, nil + } + proof := trienode.NewProofSet() + if err := stTrie.Prove(origin[:], proof); err != nil { + log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err) + return nil, nil + } + if last != (common.Hash{}) { + if err := stTrie.Prove(last[:], proof); err != nil { + log.Warn("Failed to prove storage range", "last", last, "err", err) + return nil, nil + } + } + proofs = append(proofs, proof.List()...) + // Proof terminates the reply as proofs are only added if a node + // refuses to serve more data (exception when a contract fetch is + // finishing, but that's that). + break + } + } + return slots, proofs +} + +// ServiceGetByteCodesQuery assembles the response to a byte codes query. +// It is exposed to allow external packages to test protocol behavior. +func ServiceGetByteCodesQuery(chain *core.BlockChain, req *GetByteCodesPacket) [][]byte { + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + if len(req.Hashes) > maxCodeLookups { + req.Hashes = req.Hashes[:maxCodeLookups] + } + // Retrieve bytecodes until the packet size limit is reached + var ( + codes [][]byte + bytes uint64 + ) + for _, hash := range req.Hashes { + if hash == types.EmptyCodeHash { + // Peers should not request the empty code, but if they do, at + // least sent them back a correct response without db lookups + codes = append(codes, []byte{}) + } else if blob, err := chain.ContractCodeWithPrefix(hash); err == nil { + codes = append(codes, blob) + bytes += uint64(len(blob)) + } + if bytes > req.Bytes { + break + } + } + return codes +} + +// ServiceGetTrieNodesQuery assembles the response to a trie nodes query. +// It is exposed to allow external packages to test protocol behavior. +func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, start time.Time) ([][]byte, error) { + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // Make sure we have the state associated with the request + triedb := chain.TrieDB() + + accTrie, err := trie.NewStateTrie(trie.StateTrieID(req.Root), triedb) + if err != nil { + // We don't have the requested state available, bail out + return nil, nil + } + // The 'snap' might be nil, in which case we cannot serve storage slots. + snap := chain.Snapshots().Snapshot(req.Root) + // Retrieve trie nodes until the packet size limit is reached + var ( + nodes [][]byte + bytes uint64 + loads int // Trie hash expansions to count database reads + ) + for _, pathset := range req.Paths { + switch len(pathset) { + case 0: + // Ensure we penalize invalid requests + return nil, fmt.Errorf("%w: zero-item pathset requested", errBadRequest) + + case 1: + // If we're only retrieving an account trie node, fetch it directly + blob, resolved, err := accTrie.GetNode(pathset[0]) + loads += resolved // always account database reads, even for failures + if err != nil { + break + } + nodes = append(nodes, blob) + bytes += uint64(len(blob)) + + default: + var stRoot common.Hash + // Storage slots requested, open the storage trie and retrieve from there + if snap == nil { + // We don't have the requested state snapshotted yet (or it is stale), + // but can look up the account via the trie instead. + account, err := accTrie.GetAccountByHash(common.BytesToHash(pathset[0])) + loads += 8 // We don't know the exact cost of lookup, this is an estimate + if err != nil || account == nil { + break + } + stRoot = account.Root + } else { + account, err := snap.Account(common.BytesToHash(pathset[0])) + loads++ // always account database reads, even for failures + if err != nil || account == nil { + break + } + stRoot = common.BytesToHash(account.Root) + } + id := trie.StorageTrieID(req.Root, common.BytesToHash(pathset[0]), stRoot) + stTrie, err := trie.NewStateTrie(id, triedb) + loads++ // always account database reads, even for failures + if err != nil { + break + } + for _, path := range pathset[1:] { + blob, resolved, err := stTrie.GetNode(path) + loads += resolved // always account database reads, even for failures + if err != nil { + break + } + nodes = append(nodes, blob) + bytes += uint64(len(blob)) + + // Sanity check limits to avoid DoS on the store trie loads + if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent { + break + } + } + } + // Abort request processing if we've exceeded our limits + if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent { + break + } + } + return nodes, nil +} + +// NodeInfo represents a short summary of the `snap` sub-protocol metadata +// known about the host peer. +type NodeInfo struct{} + +// nodeInfo retrieves some `snap` protocol metadata about the running host node. +func nodeInfo(chain *core.BlockChain) *NodeInfo { + return &NodeInfo{} +} diff --git a/eth/protocols/snap/handler_fuzzing_test.go b/eth/protocols/snap/handler_fuzzing_test.go new file mode 100644 index 0000000..4e234ad --- /dev/null +++ b/eth/protocols/snap/handler_fuzzing_test.go @@ -0,0 +1,163 @@ +// 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 snap + +import ( + "bytes" + "encoding/binary" + "fmt" + "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/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + 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 { + ga := make(types.GenesisAlloc, 1000) + var a = make([]byte, 20) + var mkStorage = func(k, v int) (common.Hash, common.Hash) { + var kB = make([]byte, 32) + var vB = make([]byte, 32) + binary.LittleEndian.PutUint64(kB, uint64(k)) + binary.LittleEndian.PutUint64(vB, uint64(v)) + return common.BytesToHash(kB), common.BytesToHash(vB) + } + storage := make(map[common.Hash]common.Hash) + for i := 0; i < 10; i++ { + k, v := mkStorage(i, i) + storage[k] = v + } + for i := 0; i < 1000; i++ { + binary.LittleEndian.PutUint64(a, uint64(i+0xff)) + acc := types.Account{Balance: big.NewInt(int64(i))} + if i%2 == 1 { + acc.Storage = storage + } + ga[common.BytesToAddress(a)] = acc + } + gspec := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: ga, + } + _, blocks, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, gen *core.BlockGen) {}) + cacheConf := &core.CacheConfig{ + TrieCleanLimit: 0, + TrieDirtyLimit: 0, + TrieTimeLimit: 5 * time.Minute, + TrieCleanNoPrefetch: true, + SnapshotLimit: 100, + SnapshotWait: true, + } + trieRoot = blocks[len(blocks)-1].Root() + bc, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), cacheConf, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + if _, err := bc.InsertChain(blocks); err != nil { + panic(err) + } + return bc +} + +type dummyBackend struct { + chain *core.BlockChain +} + +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 + data []byte + writeCount int +} + +func (d *dummyRW) ReadMsg() (p2p.Msg, error) { + return p2p.Msg{ + Code: d.code, + Payload: bytes.NewReader(d.data), + ReceivedAt: time.Now(), + Size: uint32(len(d.data)), + }, nil +} + +func (d *dummyRW) WriteMsg(msg p2p.Msg) error { + d.writeCount++ + return nil +} diff --git a/eth/protocols/snap/metrics.go b/eth/protocols/snap/metrics.go new file mode 100644 index 0000000..6878e5b --- /dev/null +++ b/eth/protocols/snap/metrics.go @@ -0,0 +1,69 @@ +// 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 snap + +import ( + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + ingressRegistrationErrorName = "eth/protocols/snap/ingress/registration/error" + egressRegistrationErrorName = "eth/protocols/snap/egress/registration/error" + + IngressRegistrationErrorMeter = metrics.NewRegisteredMeter(ingressRegistrationErrorName, nil) + EgressRegistrationErrorMeter = metrics.NewRegisteredMeter(egressRegistrationErrorName, nil) + + // accountInnerDeleteGauge is the metric to track how many dangling trie nodes + // covered by extension node in account trie are deleted during the sync. + accountInnerDeleteGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/delete/account/inner", nil) + + // storageInnerDeleteGauge is the metric to track how many dangling trie nodes + // covered by extension node in storage trie are deleted during the sync. + storageInnerDeleteGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/delete/storage/inner", nil) + + // accountOuterDeleteGauge is the metric to track how many dangling trie nodes + // above the committed nodes in account trie are deleted during the sync. + accountOuterDeleteGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/delete/account/outer", nil) + + // storageOuterDeleteGauge is the metric to track how many dangling trie nodes + // above the committed nodes in storage trie are deleted during the sync. + storageOuterDeleteGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/delete/storage/outer", nil) + + // lookupGauge is the metric to track how many trie node lookups are + // performed to determine if node needs to be deleted. + accountInnerLookupGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/account/lookup/inner", nil) + accountOuterLookupGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/account/lookup/outer", nil) + storageInnerLookupGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/lookup/inner", nil) + storageOuterLookupGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/lookup/outer", 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) + + // largeStorageDiscardGauge is the metric to track how many chunked storages are + // discarded during the snap sync. + largeStorageDiscardGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/chunk/discard", nil) + largeStorageResumedGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/chunk/resume", nil) +) diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go new file mode 100644 index 0000000..c579316 --- /dev/null +++ b/eth/protocols/snap/peer.go @@ -0,0 +1,133 @@ +// 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 snap + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" +) + +// Peer is a collection of relevant information we have about a `snap` peer. +type Peer struct { + id string // Unique ID for the peer, cached + + *p2p.Peer // The embedded P2P package peer + rw p2p.MsgReadWriter // Input/output streams for snap + version uint // Protocol version negotiated + + logger log.Logger // Contextual logger with the peer id injected +} + +// NewPeer creates a wrapper for a network connection and negotiated protocol +// version. +func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { + id := p.ID().String() + return &Peer{ + id: id, + Peer: p, + rw: rw, + version: version, + logger: log.New("peer", id[:8]), + } +} + +// NewFakePeer creates a fake snap peer without a backing p2p peer, for testing purposes. +func NewFakePeer(version uint, id string, rw p2p.MsgReadWriter) *Peer { + return &Peer{ + id: id, + rw: rw, + version: version, + logger: log.New("peer", id[:8]), + } +} + +// ID retrieves the peer's unique identifier. +func (p *Peer) ID() string { + return p.id +} + +// Version retrieves the peer's negotiated `snap` protocol version. +func (p *Peer) Version() uint { + return p.version +} + +// Log overrides the P2P logger with the higher level one containing only the id. +func (p *Peer) Log() log.Logger { + return p.logger +} + +// RequestAccountRange fetches a batch of accounts rooted in a specific account +// trie, starting with the origin. +func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error { + p.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) + + requestTracker.Track(p.id, p.version, GetAccountRangeMsg, AccountRangeMsg, id) + return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{ + ID: id, + Root: root, + Origin: origin, + Limit: limit, + Bytes: bytes, + }) +} + +// RequestStorageRanges fetches a batch of storage slots belonging to one or more +// accounts. If slots from only one account is requested, an origin marker may also +// be used to retrieve from there. +func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error { + if len(accounts) == 1 && origin != nil { + p.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes)) + } else { + p.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes)) + } + requestTracker.Track(p.id, p.version, GetStorageRangesMsg, StorageRangesMsg, id) + return p2p.Send(p.rw, GetStorageRangesMsg, &GetStorageRangesPacket{ + ID: id, + Root: root, + Accounts: accounts, + Origin: origin, + Limit: limit, + Bytes: bytes, + }) +} + +// RequestByteCodes fetches a batch of bytecodes by hash. +func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { + p.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) + + requestTracker.Track(p.id, p.version, GetByteCodesMsg, ByteCodesMsg, id) + return p2p.Send(p.rw, GetByteCodesMsg, &GetByteCodesPacket{ + ID: id, + Hashes: hashes, + Bytes: bytes, + }) +} + +// RequestTrieNodes fetches a batch of account or storage trie nodes rooted in +// a specific state trie. +func (p *Peer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error { + p.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) + + requestTracker.Track(p.id, p.version, GetTrieNodesMsg, TrieNodesMsg, id) + return p2p.Send(p.rw, GetTrieNodesMsg, &GetTrieNodesPacket{ + ID: id, + Root: root, + Paths: paths, + Bytes: bytes, + }) +} diff --git a/eth/protocols/snap/progress_test.go b/eth/protocols/snap/progress_test.go new file mode 100644 index 0000000..1d9a6b8 --- /dev/null +++ b/eth/protocols/snap/progress_test.go @@ -0,0 +1,154 @@ +// Copyright 2024 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 snap + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +// Legacy sync progress definitions +type legacyStorageTask struct { + Next common.Hash // Next account to sync in this interval + Last common.Hash // Last account to sync in this interval +} + +type legacyAccountTask struct { + Next common.Hash // Next account to sync in this interval + Last common.Hash // Last account to sync in this interval + SubTasks map[common.Hash][]*legacyStorageTask // Storage intervals needing fetching for large contracts +} + +type legacyProgress struct { + Tasks []*legacyAccountTask // The suspended account tasks (contract tasks within) +} + +func compareProgress(a legacyProgress, b SyncProgress) bool { + if len(a.Tasks) != len(b.Tasks) { + return false + } + for i := 0; i < len(a.Tasks); i++ { + if a.Tasks[i].Next != b.Tasks[i].Next { + return false + } + if a.Tasks[i].Last != b.Tasks[i].Last { + return false + } + // new fields are not checked here + + if len(a.Tasks[i].SubTasks) != len(b.Tasks[i].SubTasks) { + return false + } + for addrHash, subTasksA := range a.Tasks[i].SubTasks { + subTasksB, ok := b.Tasks[i].SubTasks[addrHash] + if !ok || len(subTasksB) != len(subTasksA) { + return false + } + for j := 0; j < len(subTasksA); j++ { + if subTasksA[j].Next != subTasksB[j].Next { + return false + } + if subTasksA[j].Last != subTasksB[j].Last { + return false + } + } + } + } + return true +} + +func makeLegacyProgress() legacyProgress { + return legacyProgress{ + Tasks: []*legacyAccountTask{ + { + Next: common.Hash{}, + Last: common.Hash{0x77}, + SubTasks: map[common.Hash][]*legacyStorageTask{ + {0x1}: { + { + Next: common.Hash{}, + Last: common.Hash{0xff}, + }, + }, + }, + }, + { + Next: common.Hash{0x88}, + Last: common.Hash{0xff}, + }, + }, + } +} + +func convertLegacy(legacy legacyProgress) SyncProgress { + var progress SyncProgress + for i, task := range legacy.Tasks { + subTasks := make(map[common.Hash][]*storageTask) + for owner, list := range task.SubTasks { + var cpy []*storageTask + for i := 0; i < len(list); i++ { + cpy = append(cpy, &storageTask{ + Next: list[i].Next, + Last: list[i].Last, + }) + } + subTasks[owner] = cpy + } + accountTask := &accountTask{ + Next: task.Next, + Last: task.Last, + SubTasks: subTasks, + } + if i == 0 { + accountTask.StorageCompleted = []common.Hash{{0xaa}, {0xbb}} // fulfill new fields + } + progress.Tasks = append(progress.Tasks, accountTask) + } + return progress +} + +func TestSyncProgressCompatibility(t *testing.T) { + // Decode serialized bytes of legacy progress, backward compatibility + legacy := makeLegacyProgress() + blob, err := json.Marshal(legacy) + if err != nil { + t.Fatalf("Failed to marshal progress %v", err) + } + var dec SyncProgress + if err := json.Unmarshal(blob, &dec); err != nil { + t.Fatalf("Failed to unmarshal progress %v", err) + } + if !compareProgress(legacy, dec) { + t.Fatal("sync progress is not backward compatible") + } + + // Decode serialized bytes of new format progress + progress := convertLegacy(legacy) + blob, err = json.Marshal(progress) + if err != nil { + t.Fatalf("Failed to marshal progress %v", err) + } + var legacyDec legacyProgress + if err := json.Unmarshal(blob, &legacyDec); err != nil { + t.Fatalf("Failed to unmarshal progress %v", err) + } + if !compareProgress(legacyDec, progress) { + t.Fatal("sync progress is not forward compatible") + } +} diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go new file mode 100644 index 0000000..0db206b --- /dev/null +++ b/eth/protocols/snap/protocol.go @@ -0,0 +1,218 @@ +// 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 snap + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Constants to match up protocol versions and messages +const ( + SNAP1 = 1 +) + +// ProtocolName is the official short name of the `snap` protocol used during +// devp2p capability negotiation. +const ProtocolName = "snap" + +// ProtocolVersions are the supported versions of the `snap` protocol (first +// is primary). +var ProtocolVersions = []uint{SNAP1} + +// protocolLengths are the number of implemented message corresponding to +// different protocol versions. +var protocolLengths = map[uint]uint64{SNAP1: 8} + +// maxMessageSize is the maximum cap on the size of a protocol message. +const maxMessageSize = 10 * 1024 * 1024 + +const ( + GetAccountRangeMsg = 0x00 + AccountRangeMsg = 0x01 + GetStorageRangesMsg = 0x02 + StorageRangesMsg = 0x03 + GetByteCodesMsg = 0x04 + ByteCodesMsg = 0x05 + GetTrieNodesMsg = 0x06 + TrieNodesMsg = 0x07 +) + +var ( + errMsgTooLarge = errors.New("message too long") + errDecode = errors.New("invalid message") + errInvalidMsgCode = errors.New("invalid message code") + errBadRequest = errors.New("bad request") +) + +// Packet represents a p2p message in the `snap` protocol. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. +} + +// GetAccountRangePacket represents an account query. +type GetAccountRangePacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Origin common.Hash // Hash of the first account to retrieve + Limit common.Hash // Hash of the last account to retrieve + Bytes uint64 // Soft limit at which to stop returning data +} + +// AccountRangePacket represents an account query response. +type AccountRangePacket struct { + ID uint64 // ID of the request this is a response for + Accounts []*AccountData // List of consecutive accounts from the trie + Proof [][]byte // List of trie nodes proving the account range +} + +// AccountData represents a single account in a query response. +type AccountData struct { + Hash common.Hash // Hash of the account + Body rlp.RawValue // Account body in slim format +} + +// Unpack retrieves the accounts from the range packet and converts from slim +// wire representation to consensus format. The returned data is RLP encoded +// since it's expected to be serialized to disk without further interpretation. +// +// Note, this method does a round of RLP decoding and reencoding, so only use it +// once and cache the results if need be. Ideally discard the packet afterwards +// to not double the memory use. +func (p *AccountRangePacket) Unpack() ([]common.Hash, [][]byte, error) { + var ( + hashes = make([]common.Hash, len(p.Accounts)) + accounts = make([][]byte, len(p.Accounts)) + ) + for i, acc := range p.Accounts { + val, err := types.FullAccountRLP(acc.Body) + if err != nil { + return nil, nil, fmt.Errorf("invalid account %x: %v", acc.Body, err) + } + hashes[i], accounts[i] = acc.Hash, val + } + return hashes, accounts, nil +} + +// GetStorageRangesPacket represents an storage slot query. +type GetStorageRangesPacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Accounts []common.Hash // Account hashes of the storage tries to serve + Origin []byte // Hash of the first storage slot to retrieve (large contract mode) + Limit []byte // Hash of the last storage slot to retrieve (large contract mode) + Bytes uint64 // Soft limit at which to stop returning data +} + +// StorageRangesPacket represents a storage slot query response. +type StorageRangesPacket struct { + ID uint64 // ID of the request this is a response for + Slots [][]*StorageData // Lists of consecutive storage slots for the requested accounts + Proof [][]byte // Merkle proofs for the *last* slot range, if it's incomplete +} + +// StorageData represents a single storage slot in a query response. +type StorageData struct { + Hash common.Hash // Hash of the storage slot + Body []byte // Data content of the slot +} + +// Unpack retrieves the storage slots from the range packet and returns them in +// a split flat format that's more consistent with the internal data structures. +func (p *StorageRangesPacket) Unpack() ([][]common.Hash, [][][]byte) { + var ( + hashset = make([][]common.Hash, len(p.Slots)) + slotset = make([][][]byte, len(p.Slots)) + ) + for i, slots := range p.Slots { + hashset[i] = make([]common.Hash, len(slots)) + slotset[i] = make([][]byte, len(slots)) + for j, slot := range slots { + hashset[i][j] = slot.Hash + slotset[i][j] = slot.Body + } + } + return hashset, slotset +} + +// GetByteCodesPacket represents a contract bytecode query. +type GetByteCodesPacket struct { + ID uint64 // Request ID to match up responses with + Hashes []common.Hash // Code hashes to retrieve the code for + Bytes uint64 // Soft limit at which to stop returning data +} + +// ByteCodesPacket represents a contract bytecode query response. +type ByteCodesPacket struct { + ID uint64 // ID of the request this is a response for + Codes [][]byte // Requested contract bytecodes +} + +// GetTrieNodesPacket represents a state trie node query. +type GetTrieNodesPacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Paths []TrieNodePathSet // Trie node hashes to retrieve the nodes for + Bytes uint64 // Soft limit at which to stop returning data +} + +// TrieNodePathSet is a list of trie node paths to retrieve. A naive way to +// represent trie nodes would be a simple list of `account || storage` path +// segments concatenated, but that would be very wasteful on the network. +// +// Instead, this array special cases the first element as the path in the +// account trie and the remaining elements as paths in the storage trie. To +// address an account node, the slice should have a length of 1 consisting +// of only the account path. There's no need to be able to address both an +// account node and a storage node in the same request as it cannot happen +// that a slot is accessed before the account path is fully expanded. +type TrieNodePathSet [][]byte + +// TrieNodesPacket represents a state trie node query response. +type TrieNodesPacket struct { + ID uint64 // ID of the request this is a response for + Nodes [][]byte // Requested state trie nodes +} + +func (*GetAccountRangePacket) Name() string { return "GetAccountRange" } +func (*GetAccountRangePacket) Kind() byte { return GetAccountRangeMsg } + +func (*AccountRangePacket) Name() string { return "AccountRange" } +func (*AccountRangePacket) Kind() byte { return AccountRangeMsg } + +func (*GetStorageRangesPacket) Name() string { return "GetStorageRanges" } +func (*GetStorageRangesPacket) Kind() byte { return GetStorageRangesMsg } + +func (*StorageRangesPacket) Name() string { return "StorageRanges" } +func (*StorageRangesPacket) Kind() byte { return StorageRangesMsg } + +func (*GetByteCodesPacket) Name() string { return "GetByteCodes" } +func (*GetByteCodesPacket) Kind() byte { return GetByteCodesMsg } + +func (*ByteCodesPacket) Name() string { return "ByteCodes" } +func (*ByteCodesPacket) Kind() byte { return ByteCodesMsg } + +func (*GetTrieNodesPacket) Name() string { return "GetTrieNodes" } +func (*GetTrieNodesPacket) Kind() byte { return GetTrieNodesMsg } + +func (*TrieNodesPacket) Name() string { return "TrieNodes" } +func (*TrieNodesPacket) Kind() byte { return TrieNodesMsg } diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go new file mode 100644 index 0000000..8c98c71 --- /dev/null +++ b/eth/protocols/snap/range.go @@ -0,0 +1,81 @@ +// 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 snap + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +// hashRange is a utility to handle ranges of hashes, Split up the +// hash-space into sections, and 'walk' over the sections +type hashRange struct { + current *uint256.Int + step *uint256.Int +} + +// newHashRange creates a new hashRange, initiated at the start position, +// and with the step set to fill the desired 'num' chunks +func newHashRange(start common.Hash, num uint64) *hashRange { + left := new(big.Int).Sub(hashSpace, start.Big()) + step := new(big.Int).Div( + new(big.Int).Add(left, new(big.Int).SetUint64(num-1)), + new(big.Int).SetUint64(num), + ) + step256 := new(uint256.Int) + step256.SetFromBig(step) + + return &hashRange{ + current: new(uint256.Int).SetBytes32(start[:]), + step: step256, + } +} + +// Next pushes the hash range to the next interval. +func (r *hashRange) Next() bool { + next, overflow := new(uint256.Int).AddOverflow(r.current, r.step) + if overflow { + return false + } + r.current = next + return true +} + +// Start returns the first hash in the current interval. +func (r *hashRange) Start() common.Hash { + return r.current.Bytes32() +} + +// End returns the last hash in the current interval. +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.MaxHash + } + return next.SubUint64(next, 1).Bytes32() +} + +// incHash returns the next hash, in lexicographical order (a.k.a plus one) +func incHash(h common.Hash) common.Hash { + var a uint256.Int + a.SetBytes32(h[:]) + a.AddUint64(&a, 1) + return common.Hash(a.Bytes32()) +} diff --git a/eth/protocols/snap/range_test.go b/eth/protocols/snap/range_test.go new file mode 100644 index 0000000..ea643f1 --- /dev/null +++ b/eth/protocols/snap/range_test.go @@ -0,0 +1,143 @@ +// 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 snap + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +// Tests that given a starting hash and a density, the hash ranger can correctly +// split up the remaining hash space into a fixed number of chunks. +func TestHashRanges(t *testing.T) { + tests := []struct { + head common.Hash + chunks uint64 + starts []common.Hash + ends []common.Hash + }{ + // Simple test case to split the entire hash range into 4 chunks + { + head: common.Hash{}, + chunks: 4, + starts: []common.Hash{ + {}, + common.HexToHash("0x4000000000000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0x8000000000000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0xc000000000000000000000000000000000000000000000000000000000000000"), + }, + ends: []common.Hash{ + common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.HexToHash("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.MaxHash, + }, + }, + // Split a divisible part of the hash range up into 2 chunks + { + head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"), + chunks: 2, + starts: []common.Hash{ + {}, + common.HexToHash("0x9000000000000000000000000000000000000000000000000000000000000000"), + }, + ends: []common.Hash{ + common.HexToHash("0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.MaxHash, + }, + }, + // Split the entire hash range into a non divisible 3 chunks + { + head: common.Hash{}, + chunks: 3, + starts: []common.Hash{ + {}, + common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555556"), + common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"), + }, + ends: []common.Hash{ + common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), + common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"), + common.MaxHash, + }, + }, + // Split a part of hash range into a non divisible 3 chunks + { + head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"), + chunks: 3, + starts: []common.Hash{ + {}, + common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"), + common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555556"), + }, + ends: []common.Hash{ + common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555555"), + common.MaxHash, + }, + }, + // Split a part of hash range into a non divisible 3 chunks, but with a + // meaningful space size for manual verification. + // - The head being 0xff...f0, we have 14 hashes left in the space + // - Chunking up 14 into 3 pieces is 4.(6), but we need the ceil of 5 to avoid a micro-last-chunk + // - Since the range is not divisible, the last interval will be shorter, capped at 0xff...f + // - The chunk ranges thus needs to be [..0, ..5], [..6, ..b], [..c, ..f] + { + head: common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"), + chunks: 3, + starts: []common.Hash{ + {}, + common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"), + common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"), + }, + ends: []common.Hash{ + common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"), + common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"), + common.MaxHash, + }, + }, + } + for i, tt := range tests { + r := newHashRange(tt.head, tt.chunks) + + var ( + starts = []common.Hash{{}} + ends = []common.Hash{r.End()} + ) + for r.Next() { + starts = append(starts, r.Start()) + ends = append(ends, r.End()) + } + if len(starts) != len(tt.starts) { + t.Errorf("test %d: starts count mismatch: have %d, want %d", i, len(starts), len(tt.starts)) + } + for j := 0; j < len(starts) && j < len(tt.starts); j++ { + if starts[j] != tt.starts[j] { + t.Errorf("test %d, start %d: hash mismatch: have %x, want %x", i, j, starts[j], tt.starts[j]) + } + } + if len(ends) != len(tt.ends) { + t.Errorf("test %d: ends count mismatch: have %d, want %d", i, len(ends), len(tt.ends)) + } + for j := 0; j < len(ends) && j < len(tt.ends); j++ { + if ends[j] != tt.ends[j] { + t.Errorf("test %d, end %d: hash mismatch: have %x, want %x", i, j, ends[j], tt.ends[j]) + } + } + } +} diff --git a/eth/protocols/snap/sort_test.go b/eth/protocols/snap/sort_test.go new file mode 100644 index 0000000..be0a8c5 --- /dev/null +++ b/eth/protocols/snap/sort_test.go @@ -0,0 +1,101 @@ +// 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 snap + +import ( + "bytes" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func hexToNibbles(s string) []byte { + if len(s) >= 2 && s[0] == '0' && s[1] == 'x' { + s = s[2:] + } + var s2 []byte + for _, ch := range []byte(s) { + s2 = append(s2, '0') + s2 = append(s2, ch) + } + return common.Hex2Bytes(string(s2)) +} + +func TestRequestSorting(t *testing.T) { + // - Path 0x9 -> {0x19} + // - Path 0x99 -> {0x0099} + // - Path 0x01234567890123456789012345678901012345678901234567890123456789019 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x19} + // - Path 0x012345678901234567890123456789010123456789012345678901234567890199 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x0099} + var f = func(path string) string { + data := hexToNibbles(path) + return string(data) + } + var ( + hashes []common.Hash + paths []string + ) + for _, x := range []string{ + "0x9", + "0x012345678901234567890123456789010123456789012345678901234567890195", + "0x012345678901234567890123456789010123456789012345678901234567890197", + "0x012345678901234567890123456789010123456789012345678901234567890196", + "0x99", + "0x012345678901234567890123456789010123456789012345678901234567890199", + "0x01234567890123456789012345678901012345678901234567890123456789019", + "0x0123456789012345678901234567890101234567890123456789012345678901", + "0x01234567890123456789012345678901012345678901234567890123456789010", + "0x01234567890123456789012345678901012345678901234567890123456789011", + } { + paths = append(paths, f(x)) + hashes = append(hashes, common.Hash{}) + } + _, _, syncPaths, pathsets := sortByAccountPath(paths, hashes) + { + var b = new(bytes.Buffer) + for i := 0; i < len(syncPaths); i++ { + fmt.Fprintf(b, "\n%d. paths %x", i, syncPaths[i]) + } + want := ` +0. paths [0099] +1. paths [0123456789012345678901234567890101234567890123456789012345678901 00] +2. paths [0123456789012345678901234567890101234567890123456789012345678901 0095] +3. paths [0123456789012345678901234567890101234567890123456789012345678901 0096] +4. paths [0123456789012345678901234567890101234567890123456789012345678901 0097] +5. paths [0123456789012345678901234567890101234567890123456789012345678901 0099] +6. paths [0123456789012345678901234567890101234567890123456789012345678901 10] +7. paths [0123456789012345678901234567890101234567890123456789012345678901 11] +8. paths [0123456789012345678901234567890101234567890123456789012345678901 19] +9. paths [19]` + if have := b.String(); have != want { + t.Errorf("have:%v\nwant:%v\n", have, want) + } + } + { + var b = new(bytes.Buffer) + for i := 0; i < len(pathsets); i++ { + fmt.Fprintf(b, "\n%d. pathset %x", i, pathsets[i]) + } + want := ` +0. pathset [0099] +1. pathset [0123456789012345678901234567890101234567890123456789012345678901 00 0095 0096 0097 0099 10 11 19] +2. pathset [19]` + if have := b.String(); have != want { + t.Errorf("have:%v\nwant:%v\n", have, want) + } + } +} diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go new file mode 100644 index 0000000..88d7d34 --- /dev/null +++ b/eth/protocols/snap/sync.go @@ -0,0 +1,3261 @@ +// 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 snap + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + gomath "math" + "math/big" + "math/rand" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "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" +) + +const ( + // minRequestSize is the minimum number of bytes to request from a remote peer. + // This number is used as the low cap for account and storage range requests. + // Bytecode and trienode are limited inherently by item count (1). + minRequestSize = 64 * 1024 + + // maxRequestSize is the maximum number of bytes to request from a remote peer. + // This number is used as the high cap for account and storage range requests. + // Bytecode and trienode are limited more explicitly by the caps below. + maxRequestSize = 512 * 1024 + + // maxCodeRequestCount is the maximum number of bytecode blobs to request in a + // single query. If this number is too low, we're not filling responses fully + // and waste round trip times. If it's too high, we're capping responses and + // waste bandwidth. + // + // Deployed bytecodes are currently capped at 24KB, so the minimum request + // size should be maxRequestSize / 24K. Assuming that most contracts do not + // come close to that, requesting 4x should be a good approximation. + maxCodeRequestCount = maxRequestSize / (24 * 1024) * 4 + + // maxTrieRequestCount is the maximum number of trie node blobs to request in + // a single query. If this number is too low, we're not filling responses fully + // and waste round trip times. If it's too high, we're capping responses and + // waste bandwidth. + maxTrieRequestCount = maxRequestSize / 512 + + // trienodeHealRateMeasurementImpact is the impact a single measurement has on + // the local node's trienode processing capacity. A value closer to 0 reacts + // slower to sudden changes, but it is also more stable against temporary hiccups. + trienodeHealRateMeasurementImpact = 0.005 + + // minTrienodeHealThrottle is the minimum divisor for throttling trie node + // heal requests to avoid overloading the local node and excessively expanding + // the state trie breadth wise. + minTrienodeHealThrottle = 1 + + // maxTrienodeHealThrottle is the maximum divisor for throttling trie node + // heal requests to avoid overloading the local node and exessively expanding + // the state trie bedth wise. + maxTrienodeHealThrottle = maxTrieRequestCount + + // trienodeHealThrottleIncrease is the multiplier for the throttle when the + // rate of arriving data is higher than the rate of processing it. + trienodeHealThrottleIncrease = 1.33 + + // trienodeHealThrottleDecrease is the divisor for the throttle when the + // rate of arriving data is lower than the rate of processing it. + trienodeHealThrottleDecrease = 1.25 + + // batchSizeThreshold is the maximum size allowed for gentrie batch. + batchSizeThreshold = 8 * 1024 * 1024 +) + +var ( + // accountConcurrency is the number of chunks to split the account trie into + // to allow concurrent retrievals. + accountConcurrency = 16 + + // storageConcurrency is the number of chunks to split a large contract + // storage trie into to allow concurrent retrievals. + storageConcurrency = 16 +) + +// ErrCancelled is returned from snap syncing if the operation was prematurely +// terminated. +var ErrCancelled = errors.New("sync cancelled") + +// accountRequest tracks a pending account range request to ensure responses are +// to actual requests and to validate any security constraints. +// +// Concurrency note: account requests and responses are handled concurrently from +// the main runloop to allow Merkle proof verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type accountRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent + + deliver chan *accountResponse // Channel to deliver successful response on + revert chan *accountRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + origin common.Hash // First account requested to allow continuation checks + limit common.Hash // Last account requested to allow non-overlapping chunking + + task *accountTask // Task which this request is filling (only access fields through the runloop!!) +} + +// accountResponse is an already Merkle-verified remote response to an account +// range request. It contains the subtrie for the requested account range and +// the database that's going to be filled with the internal nodes on commit. +type accountResponse struct { + task *accountTask // Task which this request is filling + + hashes []common.Hash // Account hashes in the returned range + accounts []*types.StateAccount // Expanded accounts in the returned range + + cont bool // Whether the account range has a continuation +} + +// bytecodeRequest tracks a pending bytecode request to ensure responses are to +// actual requests and to validate any security constraints. +// +// Concurrency note: bytecode requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type bytecodeRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent + + deliver chan *bytecodeResponse // Channel to deliver successful response on + revert chan *bytecodeRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + hashes []common.Hash // Bytecode hashes to validate responses + task *accountTask // Task which this request is filling (only access fields through the runloop!!) +} + +// bytecodeResponse is an already verified remote response to a bytecode request. +type bytecodeResponse struct { + task *accountTask // Task which this request is filling + + hashes []common.Hash // Hashes of the bytecode to avoid double hashing + codes [][]byte // Actual bytecodes to store into the database (nil = missing) +} + +// storageRequest tracks a pending storage ranges request to ensure responses are +// to actual requests and to validate any security constraints. +// +// Concurrency note: storage requests and responses are handled concurrently from +// the main runloop to allow Merkle proof verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. tasks). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type storageRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent + + deliver chan *storageResponse // Channel to deliver successful response on + revert chan *storageRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + accounts []common.Hash // Account hashes to validate responses + roots []common.Hash // Storage roots to validate responses + + origin common.Hash // First storage slot requested to allow continuation checks + limit common.Hash // Last storage slot requested to allow non-overlapping chunking + + mainTask *accountTask // Task which this response belongs to (only access fields through the runloop!!) + subTask *storageTask // Task which this response is filling (only access fields through the runloop!!) +} + +// storageResponse is an already Merkle-verified remote response to a storage +// range request. It contains the subtries for the requested storage ranges and +// the databases that's going to be filled with the internal nodes on commit. +type storageResponse struct { + mainTask *accountTask // Task which this response belongs to + subTask *storageTask // Task which this response is filling + + accounts []common.Hash // Account hashes requested, may be only partially filled + roots []common.Hash // Storage roots requested, may be only partially filled + + hashes [][]common.Hash // Storage slot hashes in the returned range + slots [][][]byte // Storage slot values in the returned range + + cont bool // Whether the last storage range has a continuation +} + +// trienodeHealRequest tracks a pending state trie request to ensure responses +// are to actual requests and to validate any security constraints. +// +// Concurrency note: trie node requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type trienodeHealRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent + + deliver chan *trienodeHealResponse // Channel to deliver successful response on + revert chan *trienodeHealRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + paths []string // Trie node paths for identifying trie node + hashes []common.Hash // Trie node hashes to validate responses + + task *healTask // Task which this request is filling (only access fields through the runloop!!) +} + +// trienodeHealResponse is an already verified remote response to a trie node request. +type trienodeHealResponse struct { + task *healTask // Task which this request is filling + + paths []string // Paths of the trie nodes + hashes []common.Hash // Hashes of the trie nodes to avoid double hashing + nodes [][]byte // Actual trie nodes to store into the database (nil = missing) +} + +// bytecodeHealRequest tracks a pending bytecode request to ensure responses are to +// actual requests and to validate any security constraints. +// +// Concurrency note: bytecode requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type bytecodeHealRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent + + deliver chan *bytecodeHealResponse // Channel to deliver successful response on + revert chan *bytecodeHealRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + hashes []common.Hash // Bytecode hashes to validate responses + task *healTask // Task which this request is filling (only access fields through the runloop!!) +} + +// bytecodeHealResponse is an already verified remote response to a bytecode request. +type bytecodeHealResponse struct { + task *healTask // Task which this request is filling + + hashes []common.Hash // Hashes of the bytecode to avoid double hashing + codes [][]byte // Actual bytecodes to store into the database (nil = missing) +} + +// accountTask represents the sync task for a chunk of the account snapshot. +type accountTask struct { + // These fields get serialized to key-value store on shutdown + Next common.Hash // Next account to sync in this interval + Last common.Hash // Last account to sync in this interval + SubTasks map[common.Hash][]*storageTask // Storage intervals needing fetching for large contracts + + // This is a list of account hashes whose storage are already completed + // in this cycle. This field is newly introduced in v1.14 and will be + // empty if the task is resolved from legacy progress data. Furthermore, + // this additional field will be ignored by legacy Geth. The only side + // effect is that these contracts might be resynced in the new cycle, + // retaining the legacy behavior. + StorageCompleted []common.Hash `json:",omitempty"` + + // These fields are internals used during runtime + req *accountRequest // Pending request to fill this task + res *accountResponse // Validate response filling this task + pend int // Number of pending subtasks for this round + + needCode []bool // Flags whether the filling accounts need code retrieval + needState []bool // Flags whether the filling accounts need storage retrieval + needHeal []bool // Flags whether the filling accounts's state was chunked and need healing + + codeTasks map[common.Hash]struct{} // Code hashes that need retrieval + stateTasks map[common.Hash]common.Hash // Account hashes->roots that need full state retrieval + stateCompleted map[common.Hash]struct{} // Account hashes whose storage have been completed + + genBatch ethdb.Batch // Batch used by the node generator + genTrie genTrie // Node generator from storage slots + + done bool // Flag whether the task can be removed +} + +// activeSubTasks returns the set of storage tasks covered by the current account +// range. Normally this would be the entire subTask set, but on a sync interrupt +// and later resume it can happen that a shorter account range is retrieved. This +// method ensures that we only start up the subtasks covered by the latest account +// response. +// +// Nil is returned if the account range is empty. +func (task *accountTask) activeSubTasks() map[common.Hash][]*storageTask { + if len(task.res.hashes) == 0 { + return nil + } + var ( + tasks = make(map[common.Hash][]*storageTask) + last = task.res.hashes[len(task.res.hashes)-1] + ) + for hash, subTasks := range task.SubTasks { + subTasks := subTasks // closure + if hash.Cmp(last) <= 0 { + tasks[hash] = subTasks + } + } + return tasks +} + +// storageTask represents the sync task for a chunk of the storage snapshot. +type storageTask struct { + Next common.Hash // Next account to sync in this interval + Last common.Hash // Last account to sync in this interval + + // These fields are internals used during runtime + root common.Hash // Storage root hash for this instance + req *storageRequest // Pending request to fill this task + + genBatch ethdb.Batch // Batch used by the node generator + genTrie genTrie // Node generator from storage slots + + done bool // Flag whether the task can be removed +} + +// healTask represents the sync task for healing the snap-synced chunk boundaries. +type healTask struct { + scheduler *trie.Sync // State trie sync scheduler defining the tasks + + trieTasks map[string]common.Hash // Set of trie node tasks currently queued for retrieval, indexed by node path + codeTasks map[common.Hash]struct{} // Set of byte code tasks currently queued for retrieval, indexed by code hash +} + +// SyncProgress is a database entry to allow suspending and resuming a snapshot state +// sync. Opposed to full and fast sync, there is no way to restart a suspended +// snap sync without prior knowledge of the suspension point. +type SyncProgress struct { + Tasks []*accountTask // The suspended account tasks (contract tasks within) + + // Status report during syncing phase + AccountSynced uint64 // Number of accounts downloaded + AccountBytes common.StorageSize // Number of account trie bytes persisted to disk + BytecodeSynced uint64 // Number of bytecodes downloaded + BytecodeBytes common.StorageSize // Number of bytecode bytes downloaded + StorageSynced uint64 // Number of storage slots downloaded + StorageBytes common.StorageSize // Number of storage trie bytes persisted to disk + + // Status report during healing phase + TrienodeHealSynced uint64 // Number of state trie nodes downloaded + TrienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk + BytecodeHealSynced uint64 // Number of bytecodes downloaded + BytecodeHealBytes common.StorageSize // Number of bytecodes persisted to disk +} + +// SyncPending is analogous to SyncProgress, but it's used to report on pending +// ephemeral sync progress that doesn't get persisted into the database. +type SyncPending struct { + TrienodeHeal uint64 // Number of state trie nodes pending + BytecodeHeal uint64 // Number of bytecodes pending +} + +// SyncPeer abstracts out the methods required for a peer to be synced against +// with the goal of allowing the construction of mock peers without the full +// blown networking. +type SyncPeer interface { + // ID retrieves the peer's unique identifier. + ID() string + + // RequestAccountRange fetches a batch of accounts rooted in a specific account + // trie, starting with the origin. + RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error + + // RequestStorageRanges fetches a batch of storage slots belonging to one or + // more accounts. If slots from only one account is requested, an origin marker + // may also be used to retrieve from there. + RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error + + // RequestByteCodes fetches a batch of bytecodes by hash. + RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error + + // RequestTrieNodes fetches a batch of account or storage trie nodes rooted in + // a specific state trie. + RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error + + // Log retrieves the peer's own contextual logger. + Log() log.Logger +} + +// Syncer is an Ethereum account and storage trie syncer based on snapshots and +// the snap protocol. It's purpose is to download all the accounts and storage +// slots from remote peers and reassemble chunks of the state trie, on top of +// which a state sync can be run to fix any gaps / overlaps. +// +// Every network request has a variety of failure events: +// - The peer disconnects after task assignment, failing to send the request +// - The peer disconnects after sending the request, before delivering on it +// - The peer remains connected, but does not deliver a response in time +// - The peer delivers a stale response after a previous timeout +// - The peer delivers a refusal to serve the requested state +type Syncer struct { + db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup) + scheme string // Node scheme used in node database + + root common.Hash // Current state trie root being synced + tasks []*accountTask // Current account task set being synced + snapped bool // Flag to signal that snap phase is done + healer *healTask // Current state healing task being executed + update chan struct{} // Notification channel for possible sync progression + + peers map[string]SyncPeer // Currently active peers to download from + peerJoin *event.Feed // Event feed to react to peers joining + peerDrop *event.Feed // Event feed to react to peers dropping + rates *msgrate.Trackers // Message throughput rates for peers + + // Request tracking during syncing phase + statelessPeers map[string]struct{} // Peers that failed to deliver state data + accountIdlers map[string]struct{} // Peers that aren't serving account requests + bytecodeIdlers map[string]struct{} // Peers that aren't serving bytecode requests + storageIdlers map[string]struct{} // Peers that aren't serving storage requests + + accountReqs map[uint64]*accountRequest // Account requests currently running + bytecodeReqs map[uint64]*bytecodeRequest // Bytecode requests currently running + storageReqs map[uint64]*storageRequest // Storage requests currently running + + accountSynced uint64 // Number of accounts downloaded + accountBytes common.StorageSize // Number of account trie bytes persisted to disk + bytecodeSynced uint64 // Number of bytecodes downloaded + bytecodeBytes common.StorageSize // Number of bytecode bytes downloaded + storageSynced uint64 // Number of storage slots downloaded + storageBytes common.StorageSize // Number of storage trie bytes persisted to disk + + extProgress *SyncProgress // progress that can be exposed to external caller. + + // Request tracking during healing phase + trienodeHealIdlers map[string]struct{} // Peers that aren't serving trie node requests + bytecodeHealIdlers map[string]struct{} // Peers that aren't serving bytecode requests + + trienodeHealReqs map[uint64]*trienodeHealRequest // Trie node requests currently running + bytecodeHealReqs map[uint64]*bytecodeHealRequest // Bytecode requests currently running + + trienodeHealRate float64 // Average heal rate for processing trie node data + trienodeHealPend atomic.Uint64 // Number of trie nodes currently pending for processing + trienodeHealThrottle float64 // Divisor for throttling the amount of trienode heal data requested + trienodeHealThrottled time.Time // Timestamp the last time the throttle was updated + + trienodeHealSynced uint64 // Number of state trie nodes downloaded + trienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk + trienodeHealDups uint64 // Number of state trie nodes already processed + trienodeHealNops uint64 // Number of state trie nodes not requested + bytecodeHealSynced uint64 // Number of bytecodes downloaded + bytecodeHealBytes common.StorageSize // Number of bytecodes persisted to disk + bytecodeHealDups uint64 // Number of bytecodes already processed + bytecodeHealNops uint64 // Number of bytecodes not requested + + stateWriter ethdb.Batch // Shared batch writer used for persisting raw states + accountHealed uint64 // Number of accounts downloaded during the healing stage + accountHealedBytes common.StorageSize // Number of raw account bytes persisted to disk during the healing stage + storageHealed uint64 // Number of storage slots downloaded during the healing stage + storageHealedBytes common.StorageSize // Number of raw storage bytes persisted to disk during the healing stage + + startTime time.Time // Time instance when snapshot sync started + logTime time.Time // Time instance when status was last reported + + pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown + lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, root) +} + +// NewSyncer creates a new snapshot syncer to download the Ethereum state over the +// snap protocol. +func NewSyncer(db ethdb.KeyValueStore, scheme string) *Syncer { + return &Syncer{ + db: db, + scheme: scheme, + + peers: make(map[string]SyncPeer), + peerJoin: new(event.Feed), + peerDrop: new(event.Feed), + rates: msgrate.NewTrackers(log.New("proto", "snap")), + update: make(chan struct{}, 1), + + accountIdlers: make(map[string]struct{}), + storageIdlers: make(map[string]struct{}), + bytecodeIdlers: make(map[string]struct{}), + + accountReqs: make(map[uint64]*accountRequest), + storageReqs: make(map[uint64]*storageRequest), + bytecodeReqs: make(map[uint64]*bytecodeRequest), + + trienodeHealIdlers: make(map[string]struct{}), + bytecodeHealIdlers: make(map[string]struct{}), + + trienodeHealReqs: make(map[uint64]*trienodeHealRequest), + bytecodeHealReqs: make(map[uint64]*bytecodeHealRequest), + trienodeHealThrottle: maxTrienodeHealThrottle, // Tune downward instead of insta-filling with junk + stateWriter: db.NewBatch(), + + extProgress: new(SyncProgress), + } +} + +// Register injects a new data source into the syncer's peerset. +func (s *Syncer) Register(peer SyncPeer) error { + // Make sure the peer is not registered yet + id := peer.ID() + + s.lock.Lock() + if _, ok := s.peers[id]; ok { + log.Error("Snap peer already registered", "id", id) + + s.lock.Unlock() + return errors.New("already registered") + } + s.peers[id] = peer + s.rates.Track(id, msgrate.NewTracker(s.rates.MeanCapacities(), s.rates.MedianRoundTrip())) + + // Mark the peer as idle, even if no sync is running + s.accountIdlers[id] = struct{}{} + s.storageIdlers[id] = struct{}{} + s.bytecodeIdlers[id] = struct{}{} + s.trienodeHealIdlers[id] = struct{}{} + s.bytecodeHealIdlers[id] = struct{}{} + s.lock.Unlock() + + // Notify any active syncs that a new peer can be assigned data + s.peerJoin.Send(id) + return nil +} + +// Unregister injects a new data source into the syncer's peerset. +func (s *Syncer) Unregister(id string) error { + // Remove all traces of the peer from the registry + s.lock.Lock() + if _, ok := s.peers[id]; !ok { + log.Error("Snap peer not registered", "id", id) + + s.lock.Unlock() + return errors.New("not registered") + } + delete(s.peers, id) + s.rates.Untrack(id) + + // Remove status markers, even if no sync is running + delete(s.statelessPeers, id) + + delete(s.accountIdlers, id) + delete(s.storageIdlers, id) + delete(s.bytecodeIdlers, id) + delete(s.trienodeHealIdlers, id) + delete(s.bytecodeHealIdlers, id) + s.lock.Unlock() + + // Notify any active syncs that pending requests need to be reverted + s.peerDrop.Send(id) + return nil +} + +// Sync starts (or resumes a previous) sync cycle to iterate over a state trie +// with the given root and reconstruct the nodes based on the snapshot leaves. +// Previously downloaded segments will not be redownloaded of fixed, rather any +// errors will be healed after the leaves are fully accumulated. +func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { + // Move the trie root from any previous value, revert stateless markers for + // any peers and initialize the syncer if it was not yet run + s.lock.Lock() + s.root = root + s.healer = &healTask{ + scheduler: state.NewStateSync(root, s.db, s.onHealState, s.scheme), + trieTasks: make(map[string]common.Hash), + codeTasks: make(map[common.Hash]struct{}), + } + s.statelessPeers = make(map[string]struct{}) + s.lock.Unlock() + + if s.startTime == (time.Time{}) { + s.startTime = time.Now() + } + // Retrieve the previous sync status from LevelDB and abort if already synced + s.loadSyncStatus() + if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 { + log.Debug("Snapshot sync already completed") + return nil + } + defer func() { // Persist any progress, independent of failure + for _, task := range s.tasks { + s.forwardAccountTask(task) + } + s.cleanAccountTasks() + s.saveSyncStatus() + }() + + log.Debug("Starting snapshot sync cycle", "root", root) + + // Flush out the last committed raw states + defer func() { + if s.stateWriter.ValueSize() > 0 { + s.stateWriter.Write() + s.stateWriter.Reset() + } + }() + defer s.report(true) + // commit any trie- and bytecode-healing data. + defer s.commitHealer(true) + + // Whether sync completed or not, disregard any future packets + defer func() { + log.Debug("Terminating snapshot sync cycle", "root", root) + s.lock.Lock() + s.accountReqs = make(map[uint64]*accountRequest) + s.storageReqs = make(map[uint64]*storageRequest) + s.bytecodeReqs = make(map[uint64]*bytecodeRequest) + s.trienodeHealReqs = make(map[uint64]*trienodeHealRequest) + s.bytecodeHealReqs = make(map[uint64]*bytecodeHealRequest) + s.lock.Unlock() + }() + // Keep scheduling sync tasks + peerJoin := make(chan string, 16) + peerJoinSub := s.peerJoin.Subscribe(peerJoin) + defer peerJoinSub.Unsubscribe() + + peerDrop := make(chan string, 16) + peerDropSub := s.peerDrop.Subscribe(peerDrop) + defer peerDropSub.Unsubscribe() + + // Create a set of unique channels for this sync cycle. We need these to be + // ephemeral so a data race doesn't accidentally deliver something stale on + // a persistent channel across syncs (yup, this happened) + var ( + accountReqFails = make(chan *accountRequest) + storageReqFails = make(chan *storageRequest) + bytecodeReqFails = make(chan *bytecodeRequest) + accountResps = make(chan *accountResponse) + storageResps = make(chan *storageResponse) + bytecodeResps = make(chan *bytecodeResponse) + trienodeHealReqFails = make(chan *trienodeHealRequest) + bytecodeHealReqFails = make(chan *bytecodeHealRequest) + trienodeHealResps = make(chan *trienodeHealResponse) + bytecodeHealResps = make(chan *bytecodeHealResponse) + ) + for { + // Remove all completed tasks and terminate sync if everything's done + s.cleanStorageTasks() + s.cleanAccountTasks() + if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 { + return nil + } + // Assign all the data retrieval tasks to any free peers + s.assignAccountTasks(accountResps, accountReqFails, cancel) + s.assignBytecodeTasks(bytecodeResps, bytecodeReqFails, cancel) + s.assignStorageTasks(storageResps, storageReqFails, cancel) + + if len(s.tasks) == 0 { + // Sync phase done, run heal phase + s.assignTrienodeHealTasks(trienodeHealResps, trienodeHealReqFails, cancel) + s.assignBytecodeHealTasks(bytecodeHealResps, bytecodeHealReqFails, cancel) + } + // Update sync progress + s.lock.Lock() + s.extProgress = &SyncProgress{ + AccountSynced: s.accountSynced, + AccountBytes: s.accountBytes, + BytecodeSynced: s.bytecodeSynced, + BytecodeBytes: s.bytecodeBytes, + StorageSynced: s.storageSynced, + StorageBytes: s.storageBytes, + TrienodeHealSynced: s.trienodeHealSynced, + TrienodeHealBytes: s.trienodeHealBytes, + BytecodeHealSynced: s.bytecodeHealSynced, + BytecodeHealBytes: s.bytecodeHealBytes, + } + s.lock.Unlock() + // Wait for something to happen + select { + case <-s.update: + // Something happened (new peer, delivery, timeout), recheck tasks + case <-peerJoin: + // A new peer joined, try to schedule it new tasks + case id := <-peerDrop: + s.revertRequests(id) + case <-cancel: + return ErrCancelled + + case req := <-accountReqFails: + s.revertAccountRequest(req) + case req := <-bytecodeReqFails: + s.revertBytecodeRequest(req) + case req := <-storageReqFails: + s.revertStorageRequest(req) + case req := <-trienodeHealReqFails: + s.revertTrienodeHealRequest(req) + case req := <-bytecodeHealReqFails: + s.revertBytecodeHealRequest(req) + + case res := <-accountResps: + s.processAccountResponse(res) + case res := <-bytecodeResps: + s.processBytecodeResponse(res) + case res := <-storageResps: + s.processStorageResponse(res) + case res := <-trienodeHealResps: + s.processTrienodeHealResponse(res) + case res := <-bytecodeHealResps: + s.processBytecodeHealResponse(res) + } + // Report stats if something meaningful happened + s.report(false) + } +} + +// loadSyncStatus retrieves a previously aborted sync status from the database, +// or generates a fresh one if none is available. +func (s *Syncer) loadSyncStatus() { + var progress SyncProgress + + if status := rawdb.ReadSnapshotSyncStatus(s.db); status != nil { + if err := json.Unmarshal(status, &progress); err != nil { + log.Error("Failed to decode snap sync status", "err", err) + } else { + for _, task := range progress.Tasks { + log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last) + } + s.tasks = progress.Tasks + for _, task := range s.tasks { + task := task // closure for task.genBatch in the stacktrie writer callback + + // Restore the completed storages + task.stateCompleted = make(map[common.Hash]struct{}) + for _, hash := range task.StorageCompleted { + task.stateCompleted[hash] = struct{}{} + } + task.StorageCompleted = nil + + // Allocate batch for account trie generation + task.genBatch = ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.accountBytes += common.StorageSize(len(key) + len(value)) + }, + } + if s.scheme == rawdb.HashScheme { + task.genTrie = newHashTrie(task.genBatch) + } + if s.scheme == rawdb.PathScheme { + task.genTrie = newPathTrie(common.Hash{}, task.Next != common.Hash{}, s.db, task.genBatch) + } + // Restore leftover storage tasks + for accountHash, subtasks := range task.SubTasks { + for _, subtask := range subtasks { + subtask := subtask // closure for subtask.genBatch in the stacktrie writer callback + + subtask.genBatch = ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.storageBytes += common.StorageSize(len(key) + len(value)) + }, + } + if s.scheme == rawdb.HashScheme { + subtask.genTrie = newHashTrie(subtask.genBatch) + } + if s.scheme == rawdb.PathScheme { + subtask.genTrie = newPathTrie(accountHash, subtask.Next != common.Hash{}, s.db, subtask.genBatch) + } + } + } + } + s.lock.Lock() + defer s.lock.Unlock() + + s.snapped = len(s.tasks) == 0 + + s.accountSynced = progress.AccountSynced + s.accountBytes = progress.AccountBytes + s.bytecodeSynced = progress.BytecodeSynced + s.bytecodeBytes = progress.BytecodeBytes + s.storageSynced = progress.StorageSynced + s.storageBytes = progress.StorageBytes + + s.trienodeHealSynced = progress.TrienodeHealSynced + s.trienodeHealBytes = progress.TrienodeHealBytes + s.bytecodeHealSynced = progress.BytecodeHealSynced + s.bytecodeHealBytes = progress.BytecodeHealBytes + return + } + } + // Either we've failed to decode the previous state, or there was none. + // Start a fresh sync by chunking up the account range and scheduling + // them for retrieval. + s.tasks = nil + s.accountSynced, s.accountBytes = 0, 0 + s.bytecodeSynced, s.bytecodeBytes = 0, 0 + s.storageSynced, s.storageBytes = 0, 0 + s.trienodeHealSynced, s.trienodeHealBytes = 0, 0 + s.bytecodeHealSynced, s.bytecodeHealBytes = 0, 0 + + var next common.Hash + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(int64(accountConcurrency)), + ), common.Big1, + ) + for i := 0; i < accountConcurrency; i++ { + 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.MaxHash + } + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.accountBytes += common.StorageSize(len(key) + len(value)) + }, + } + var tr genTrie + if s.scheme == rawdb.HashScheme { + tr = newHashTrie(batch) + } + if s.scheme == rawdb.PathScheme { + tr = newPathTrie(common.Hash{}, next != common.Hash{}, s.db, batch) + } + s.tasks = append(s.tasks, &accountTask{ + Next: next, + Last: last, + SubTasks: make(map[common.Hash][]*storageTask), + genBatch: batch, + stateCompleted: make(map[common.Hash]struct{}), + genTrie: tr, + }) + log.Debug("Created account sync task", "from", next, "last", last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } +} + +// saveSyncStatus marshals the remaining sync tasks into leveldb. +func (s *Syncer) saveSyncStatus() { + // Serialize any partial progress to disk before spinning down + for _, task := range s.tasks { + // Claim the right boundary as incomplete before flushing the + // accumulated nodes in batch, the nodes on right boundary + // will be discarded and cleaned up by this call. + task.genTrie.commit(false) + if err := task.genBatch.Write(); err != nil { + log.Error("Failed to persist account slots", "err", err) + } + for _, subtasks := range task.SubTasks { + for _, subtask := range subtasks { + // Same for account trie, discard and cleanup the + // incomplete right boundary. + subtask.genTrie.commit(false) + if err := subtask.genBatch.Write(); err != nil { + log.Error("Failed to persist storage slots", "err", err) + } + } + } + // Save the account hashes of completed storage. + task.StorageCompleted = make([]common.Hash, 0, len(task.stateCompleted)) + for hash := range task.stateCompleted { + task.StorageCompleted = append(task.StorageCompleted, hash) + } + if len(task.StorageCompleted) > 0 { + log.Debug("Leftover completed storages", "number", len(task.StorageCompleted), "next", task.Next, "last", task.Last) + } + } + // Store the actual progress markers + progress := &SyncProgress{ + Tasks: s.tasks, + AccountSynced: s.accountSynced, + AccountBytes: s.accountBytes, + BytecodeSynced: s.bytecodeSynced, + BytecodeBytes: s.bytecodeBytes, + StorageSynced: s.storageSynced, + StorageBytes: s.storageBytes, + TrienodeHealSynced: s.trienodeHealSynced, + TrienodeHealBytes: s.trienodeHealBytes, + BytecodeHealSynced: s.bytecodeHealSynced, + BytecodeHealBytes: s.bytecodeHealBytes, + } + status, err := json.Marshal(progress) + if err != nil { + panic(err) // This can only fail during implementation + } + rawdb.WriteSnapshotSyncStatus(s.db, status) +} + +// Progress returns the snap sync status statistics. +func (s *Syncer) Progress() (*SyncProgress, *SyncPending) { + s.lock.Lock() + defer s.lock.Unlock() + pending := new(SyncPending) + if s.healer != nil { + pending.TrienodeHeal = uint64(len(s.healer.trieTasks)) + pending.BytecodeHeal = uint64(len(s.healer.codeTasks)) + } + return s.extProgress, pending +} + +// cleanAccountTasks removes account range retrieval tasks that have already been +// completed. +func (s *Syncer) cleanAccountTasks() { + // If the sync was already done before, don't even bother + if len(s.tasks) == 0 { + return + } + // Sync wasn't finished previously, check for any task that can be finalized + for i := 0; i < len(s.tasks); i++ { + if s.tasks[i].done { + s.tasks = append(s.tasks[:i], s.tasks[i+1:]...) + i-- + } + } + // If everything was just finalized just, generate the account trie and start heal + if len(s.tasks) == 0 { + s.lock.Lock() + s.snapped = true + s.lock.Unlock() + + // Push the final sync report + s.reportSyncProgress(true) + } +} + +// cleanStorageTasks iterates over all the account tasks and storage sub-tasks +// within, cleaning any that have been completed. +func (s *Syncer) cleanStorageTasks() { + for _, task := range s.tasks { + for account, subtasks := range task.SubTasks { + // Remove storage range retrieval tasks that completed + for j := 0; j < len(subtasks); j++ { + if subtasks[j].done { + subtasks = append(subtasks[:j], subtasks[j+1:]...) + j-- + } + } + if len(subtasks) > 0 { + task.SubTasks[account] = subtasks + continue + } + // If all storage chunks are done, mark the account as done too + for j, hash := range task.res.hashes { + if hash == account { + task.needState[j] = false + } + } + delete(task.SubTasks, account) + task.pend-- + + // Mark the state as complete to prevent resyncing, regardless + // if state healing is necessary. + task.stateCompleted[account] = struct{}{} + + // If this was the last pending task, forward the account task + if task.pend == 0 { + s.forwardAccountTask(task) + } + } + } +} + +// assignAccountTasks attempts to match idle peers to pending account range +// retrievals. +func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *accountRequest, cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.accountIdlers)), + caps: make([]int, 0, len(s.accountIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.accountIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, AccountRangeMsg, targetTTL)) + } + if len(idlers.ids) == 0 { + return + } + sort.Sort(sort.Reverse(idlers)) + + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks already filling + if task.req != nil || task.res != nil { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + if len(idlers.ids) == 0 { + return + } + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] + + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.accountReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + req := &accountRequest{ + peer: idle, + id: reqid, + time: time.Now(), + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + origin: task.Next, + limit: task.Last, + task: task, + } + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { + peer.Log().Debug("Account range request timed out", "reqid", reqid) + s.rates.Update(idle, AccountRangeMsg, 0, 0) + s.scheduleRevertAccountRequest(req) + }) + s.accountReqs[reqid] = req + delete(s.accountIdlers, idle) + + s.pend.Add(1) + go func(root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if cap > maxRequestSize { + cap = maxRequestSize + } + if cap < minRequestSize { // Don't bother with peers below a bare minimum performance + cap = minRequestSize + } + if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, uint64(cap)); err != nil { + peer.Log().Debug("Failed to request account range", "err", err) + s.scheduleRevertAccountRequest(req) + } + }(s.root) + + // Inject the request into the task to block further assignments + task.req = req + } +} + +// assignBytecodeTasks attempts to match idle peers to pending code retrievals. +func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan *bytecodeRequest, cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.bytecodeIdlers)), + caps: make([]int, 0, len(s.bytecodeIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.bytecodeIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, ByteCodesMsg, targetTTL)) + } + if len(idlers.ids) == 0 { + return + } + sort.Sort(sort.Reverse(idlers)) + + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks not in the bytecode retrieval phase + if task.res == nil { + continue + } + // Skip tasks that are already retrieving (or done with) all codes + if len(task.codeTasks) == 0 { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + if len(idlers.ids) == 0 { + return + } + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] + + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.bytecodeReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + if cap > maxCodeRequestCount { + cap = maxCodeRequestCount + } + hashes := make([]common.Hash, 0, cap) + for hash := range task.codeTasks { + delete(task.codeTasks, hash) + hashes = append(hashes, hash) + if len(hashes) >= cap { + break + } + } + req := &bytecodeRequest{ + peer: idle, + id: reqid, + time: time.Now(), + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + task: task, + } + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { + peer.Log().Debug("Bytecode request timed out", "reqid", reqid) + s.rates.Update(idle, ByteCodesMsg, 0, 0) + s.scheduleRevertBytecodeRequest(req) + }) + s.bytecodeReqs[reqid] = req + delete(s.bytecodeIdlers, idle) + + s.pend.Add(1) + go func() { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { + log.Debug("Failed to request bytecodes", "err", err) + s.scheduleRevertBytecodeRequest(req) + } + }() + } +} + +// assignStorageTasks attempts to match idle peers to pending storage range +// retrievals. +func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *storageRequest, cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.storageIdlers)), + caps: make([]int, 0, len(s.storageIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.storageIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, StorageRangesMsg, targetTTL)) + } + if len(idlers.ids) == 0 { + return + } + sort.Sort(sort.Reverse(idlers)) + + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks not in the storage retrieval phase + if task.res == nil { + continue + } + // Skip tasks that are already retrieving (or done with) all small states + storageTasks := task.activeSubTasks() + if len(storageTasks) == 0 && len(task.stateTasks) == 0 { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + if len(idlers.ids) == 0 { + return + } + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] + + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.storageReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer. If there are + // large contract tasks pending, complete those before diving into + // even more new contracts. + if cap > maxRequestSize { + cap = maxRequestSize + } + if cap < minRequestSize { // Don't bother with peers below a bare minimum performance + cap = minRequestSize + } + storageSets := cap / 1024 + + var ( + accounts = make([]common.Hash, 0, storageSets) + roots = make([]common.Hash, 0, storageSets) + subtask *storageTask + ) + for account, subtasks := range storageTasks { + for _, st := range subtasks { + // Skip any subtasks already filling + if st.req != nil { + continue + } + // Found an incomplete storage chunk, schedule it + accounts = append(accounts, account) + roots = append(roots, st.root) + subtask = st + break // Large contract chunks are downloaded individually + } + if subtask != nil { + break // Large contract chunks are downloaded individually + } + } + if subtask == nil { + // No large contract required retrieval, but small ones available + for account, root := range task.stateTasks { + delete(task.stateTasks, account) + + accounts = append(accounts, account) + roots = append(roots, root) + + if len(accounts) >= storageSets { + break + } + } + } + // If nothing was found, it means this task is actually already fully + // retrieving, but large contracts are hard to detect. Skip to the next. + if len(accounts) == 0 { + continue + } + req := &storageRequest{ + peer: idle, + id: reqid, + time: time.Now(), + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + accounts: accounts, + roots: roots, + mainTask: task, + subTask: subtask, + } + if subtask != nil { + req.origin = subtask.Next + req.limit = subtask.Last + } + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { + peer.Log().Debug("Storage request timed out", "reqid", reqid) + s.rates.Update(idle, StorageRangesMsg, 0, 0) + s.scheduleRevertStorageRequest(req) + }) + s.storageReqs[reqid] = req + delete(s.storageIdlers, idle) + + s.pend.Add(1) + go func(root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + var origin, limit []byte + if subtask != nil { + origin, limit = req.origin[:], req.limit[:] + } + if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, uint64(cap)); err != nil { + log.Debug("Failed to request storage", "err", err) + s.scheduleRevertStorageRequest(req) + } + }(s.root) + + // Inject the request into the subtask to block further assignments + if subtask != nil { + subtask.req = req + } + } +} + +// assignTrienodeHealTasks attempts to match idle peers to trie node requests to +// heal any trie errors caused by the snap sync's chunked retrieval model. +func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fail chan *trienodeHealRequest, cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.trienodeHealIdlers)), + caps: make([]int, 0, len(s.trienodeHealIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.trienodeHealIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, TrieNodesMsg, targetTTL)) + } + if len(idlers.ids) == 0 { + return + } + sort.Sort(sort.Reverse(idlers)) + + // Iterate over pending tasks and try to find a peer to retrieve with + for len(s.healer.trieTasks) > 0 || s.healer.scheduler.Pending() > 0 { + // If there are not enough trie tasks queued to fully assign, fill the + // queue from the state sync scheduler. The trie synced schedules these + // together with bytecodes, so we need to queue them combined. + var ( + have = len(s.healer.trieTasks) + len(s.healer.codeTasks) + want = maxTrieRequestCount + maxCodeRequestCount + ) + if have < want { + paths, hashes, codes := s.healer.scheduler.Missing(want - have) + for i, path := range paths { + s.healer.trieTasks[path] = hashes[i] + } + for _, hash := range codes { + s.healer.codeTasks[hash] = struct{}{} + } + } + // If all the heal tasks are bytecodes or already downloading, bail + if len(s.healer.trieTasks) == 0 { + return + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + if len(idlers.ids) == 0 { + return + } + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] + + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.trienodeHealReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + if cap > maxTrieRequestCount { + cap = maxTrieRequestCount + } + cap = int(float64(cap) / s.trienodeHealThrottle) + if cap <= 0 { + cap = 1 + } + var ( + hashes = make([]common.Hash, 0, cap) + paths = make([]string, 0, cap) + pathsets = make([]TrieNodePathSet, 0, cap) + ) + for path, hash := range s.healer.trieTasks { + delete(s.healer.trieTasks, path) + + paths = append(paths, path) + hashes = append(hashes, hash) + if len(paths) >= cap { + break + } + } + // Group requests by account hash + paths, hashes, _, pathsets = sortByAccountPath(paths, hashes) + req := &trienodeHealRequest{ + peer: idle, + id: reqid, + time: time.Now(), + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + paths: paths, + hashes: hashes, + task: s.healer, + } + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { + peer.Log().Debug("Trienode heal request timed out", "reqid", reqid) + s.rates.Update(idle, TrieNodesMsg, 0, 0) + s.scheduleRevertTrienodeHealRequest(req) + }) + s.trienodeHealReqs[reqid] = req + delete(s.trienodeHealIdlers, idle) + + s.pend.Add(1) + go func(root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestTrieNodes(reqid, root, pathsets, maxRequestSize); err != nil { + log.Debug("Failed to request trienode healers", "err", err) + s.scheduleRevertTrienodeHealRequest(req) + } + }(s.root) + } +} + +// assignBytecodeHealTasks attempts to match idle peers to bytecode requests to +// heal any trie errors caused by the snap sync's chunked retrieval model. +func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fail chan *bytecodeHealRequest, cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.bytecodeHealIdlers)), + caps: make([]int, 0, len(s.bytecodeHealIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.bytecodeHealIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, ByteCodesMsg, targetTTL)) + } + if len(idlers.ids) == 0 { + return + } + sort.Sort(sort.Reverse(idlers)) + + // Iterate over pending tasks and try to find a peer to retrieve with + for len(s.healer.codeTasks) > 0 || s.healer.scheduler.Pending() > 0 { + // If there are not enough trie tasks queued to fully assign, fill the + // queue from the state sync scheduler. The trie synced schedules these + // together with trie nodes, so we need to queue them combined. + var ( + have = len(s.healer.trieTasks) + len(s.healer.codeTasks) + want = maxTrieRequestCount + maxCodeRequestCount + ) + if have < want { + paths, hashes, codes := s.healer.scheduler.Missing(want - have) + for i, path := range paths { + s.healer.trieTasks[path] = hashes[i] + } + for _, hash := range codes { + s.healer.codeTasks[hash] = struct{}{} + } + } + // If all the heal tasks are trienodes or already downloading, bail + if len(s.healer.codeTasks) == 0 { + return + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + if len(idlers.ids) == 0 { + return + } + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] + + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.bytecodeHealReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + if cap > maxCodeRequestCount { + cap = maxCodeRequestCount + } + hashes := make([]common.Hash, 0, cap) + for hash := range s.healer.codeTasks { + delete(s.healer.codeTasks, hash) + + hashes = append(hashes, hash) + if len(hashes) >= cap { + break + } + } + req := &bytecodeHealRequest{ + peer: idle, + id: reqid, + time: time.Now(), + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + task: s.healer, + } + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { + peer.Log().Debug("Bytecode heal request timed out", "reqid", reqid) + s.rates.Update(idle, ByteCodesMsg, 0, 0) + s.scheduleRevertBytecodeHealRequest(req) + }) + s.bytecodeHealReqs[reqid] = req + delete(s.bytecodeHealIdlers, idle) + + s.pend.Add(1) + go func() { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { + log.Debug("Failed to request bytecode healers", "err", err) + s.scheduleRevertBytecodeHealRequest(req) + } + }() + } +} + +// revertRequests locates all the currently pending requests from a particular +// peer and reverts them, rescheduling for others to fulfill. +func (s *Syncer) revertRequests(peer string) { + // Gather the requests first, revertals need the lock too + s.lock.Lock() + var accountReqs []*accountRequest + for _, req := range s.accountReqs { + if req.peer == peer { + accountReqs = append(accountReqs, req) + } + } + var bytecodeReqs []*bytecodeRequest + for _, req := range s.bytecodeReqs { + if req.peer == peer { + bytecodeReqs = append(bytecodeReqs, req) + } + } + var storageReqs []*storageRequest + for _, req := range s.storageReqs { + if req.peer == peer { + storageReqs = append(storageReqs, req) + } + } + var trienodeHealReqs []*trienodeHealRequest + for _, req := range s.trienodeHealReqs { + if req.peer == peer { + trienodeHealReqs = append(trienodeHealReqs, req) + } + } + var bytecodeHealReqs []*bytecodeHealRequest + for _, req := range s.bytecodeHealReqs { + if req.peer == peer { + bytecodeHealReqs = append(bytecodeHealReqs, req) + } + } + s.lock.Unlock() + + // Revert all the requests matching the peer + for _, req := range accountReqs { + s.revertAccountRequest(req) + } + for _, req := range bytecodeReqs { + s.revertBytecodeRequest(req) + } + for _, req := range storageReqs { + s.revertStorageRequest(req) + } + for _, req := range trienodeHealReqs { + s.revertTrienodeHealRequest(req) + } + for _, req := range bytecodeHealReqs { + s.revertBytecodeHealRequest(req) + } +} + +// scheduleRevertAccountRequest asks the event loop to clean up an account range +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertAccountRequest(req *accountRequest) { + select { + case req.revert <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertAccountRequest cleans up an account range request and returns all failed +// retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertAccountRequest. +func (s *Syncer) revertAccountRequest(req *accountRequest) { + log.Debug("Reverting account request", "peer", req.peer, "reqid", req.id) + select { + case <-req.stale: + log.Trace("Account request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.accountReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the account + // task as not-pending, ready for rescheduling + req.timeout.Stop() + if req.task.req == req { + req.task.req = nil + } +} + +// scheduleRevertBytecodeRequest asks the event loop to clean up a bytecode request +// and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertBytecodeRequest(req *bytecodeRequest) { + select { + case req.revert <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertBytecodeRequest cleans up a bytecode request and returns all failed +// retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertBytecodeRequest. +func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { + log.Debug("Reverting bytecode request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Bytecode request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.bytecodeReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the code + // retrievals as not-pending, ready for rescheduling + req.timeout.Stop() + for _, hash := range req.hashes { + req.task.codeTasks[hash] = struct{}{} + } +} + +// scheduleRevertStorageRequest asks the event loop to clean up a storage range +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertStorageRequest(req *storageRequest) { + select { + case req.revert <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertStorageRequest cleans up a storage range request and returns all failed +// retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertStorageRequest. +func (s *Syncer) revertStorageRequest(req *storageRequest) { + log.Debug("Reverting storage request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Storage request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.storageReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the storage + // task as not-pending, ready for rescheduling + req.timeout.Stop() + if req.subTask != nil { + req.subTask.req = nil + } else { + for i, account := range req.accounts { + req.mainTask.stateTasks[account] = req.roots[i] + } + } +} + +// scheduleRevertTrienodeHealRequest asks the event loop to clean up a trienode heal +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertTrienodeHealRequest(req *trienodeHealRequest) { + select { + case req.revert <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertTrienodeHealRequest cleans up a trienode heal request and returns all +// failed retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertTrienodeHealRequest. +func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { + log.Debug("Reverting trienode heal request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Trienode heal request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.trienodeHealReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the trie node + // retrievals as not-pending, ready for rescheduling + req.timeout.Stop() + for i, path := range req.paths { + req.task.trieTasks[path] = req.hashes[i] + } +} + +// scheduleRevertBytecodeHealRequest asks the event loop to clean up a bytecode heal +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertBytecodeHealRequest(req *bytecodeHealRequest) { + select { + case req.revert <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertBytecodeHealRequest cleans up a bytecode heal request and returns all +// failed retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertBytecodeHealRequest. +func (s *Syncer) revertBytecodeHealRequest(req *bytecodeHealRequest) { + log.Debug("Reverting bytecode heal request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Bytecode heal request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.bytecodeHealReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the code + // retrievals as not-pending, ready for rescheduling + req.timeout.Stop() + for _, hash := range req.hashes { + req.task.codeTasks[hash] = struct{}{} + } +} + +// processAccountResponse integrates an already validated account range response +// into the account tasks. +func (s *Syncer) processAccountResponse(res *accountResponse) { + // Switch the task from pending to filling + res.task.req = nil + res.task.res = res + + // Ensure that the response doesn't overflow into the subsequent task + lastBig := res.task.Last.Big() + for i, hash := range res.hashes { + // Mark the range complete if the last is already included. + // Keep iteration to delete the extra states if exists. + cmp := hash.Big().Cmp(lastBig) + if cmp == 0 { + res.cont = false + continue + } + if cmp > 0 { + // Chunk overflown, cut off excess + res.hashes = res.hashes[:i] + res.accounts = res.accounts[:i] + res.cont = false // Mark range completed + break + } + } + // Iterate over all the accounts and assemble which ones need further sub- + // filling before the entire account range can be persisted. + res.task.needCode = make([]bool, len(res.accounts)) + res.task.needState = make([]bool, len(res.accounts)) + res.task.needHeal = make([]bool, len(res.accounts)) + + res.task.codeTasks = make(map[common.Hash]struct{}) + res.task.stateTasks = make(map[common.Hash]common.Hash) + + resumed := make(map[common.Hash]struct{}) + + res.task.pend = 0 + for i, account := range res.accounts { + // Check if the account is a contract with an unknown code + if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { + if !rawdb.HasCodeWithPrefix(s.db, common.BytesToHash(account.CodeHash)) { + res.task.codeTasks[common.BytesToHash(account.CodeHash)] = struct{}{} + res.task.needCode[i] = true + res.task.pend++ + } + } + // Check if the account is a contract with an unknown storage trie + if account.Root != types.EmptyRootHash { + // If the storage was already retrieved in the last cycle, there's no need + // to resync it again, regardless of whether the storage root is consistent + // or not. + if _, exist := res.task.stateCompleted[res.hashes[i]]; exist { + // The leftover storage tasks are not expected, unless system is + // very wrong. + if _, ok := res.task.SubTasks[res.hashes[i]]; ok { + panic(fmt.Errorf("unexpected leftover storage tasks, owner: %x", res.hashes[i])) + } + // Mark the healing tag if storage root node is inconsistent, or + // it's non-existent due to storage chunking. + if !rawdb.HasTrieNode(s.db, res.hashes[i], nil, account.Root, s.scheme) { + res.task.needHeal[i] = true + } + } else { + // If there was a previous large state retrieval in progress, + // don't restart it from scratch. This happens if a sync cycle + // is interrupted and resumed later. However, *do* update the + // previous root hash. + if subtasks, ok := res.task.SubTasks[res.hashes[i]]; ok { + log.Debug("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root) + for _, subtask := range subtasks { + subtask.root = account.Root + } + res.task.needHeal[i] = true + resumed[res.hashes[i]] = struct{}{} + largeStorageResumedGauge.Inc(1) + } else { + // It's possible that in the hash scheme, the storage, along + // with the trie nodes of the given root, is already present + // in the database. Schedule the storage task anyway to simplify + // the logic here. + res.task.stateTasks[res.hashes[i]] = account.Root + } + res.task.needState[i] = true + res.task.pend++ + } + } + } + // Delete any subtasks that have been aborted but not resumed. It's essential + // as the corresponding contract might be self-destructed in this cycle(it's + // no longer possible in ethereum as self-destruction is disabled in Cancun + // Fork, but the condition is still necessary for other networks). + // + // Keep the leftover storage tasks if they are not covered by the responded + // account range which should be picked up in next account wave. + if len(res.hashes) > 0 { + // The hash of last delivered account in the response + last := res.hashes[len(res.hashes)-1] + for hash := range res.task.SubTasks { + // TODO(rjl493456442) degrade the log level before merging. + if hash.Cmp(last) > 0 { + log.Info("Keeping suspended storage retrieval", "account", hash) + continue + } + // TODO(rjl493456442) degrade the log level before merging. + // It should never happen in ethereum. + if _, ok := resumed[hash]; !ok { + log.Error("Aborting suspended storage retrieval", "account", hash) + delete(res.task.SubTasks, hash) + largeStorageDiscardGauge.Inc(1) + } + } + } + // If the account range contained no contracts, or all have been fully filled + // beforehand, short circuit storage filling and forward to the next task + if res.task.pend == 0 { + s.forwardAccountTask(res.task) + return + } + // Some accounts are incomplete, leave as is for the storage and contract + // task assigners to pick up and fill +} + +// processBytecodeResponse integrates an already validated bytecode response +// into the account tasks. +func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { + batch := s.db.NewBatch() + + var ( + codes uint64 + ) + for i, hash := range res.hashes { + code := res.codes[i] + + // If the bytecode was not delivered, reschedule it + if code == nil { + res.task.codeTasks[hash] = struct{}{} + continue + } + // Code was delivered, mark it not needed any more + for j, account := range res.task.res.accounts { + if res.task.needCode[j] && hash == common.BytesToHash(account.CodeHash) { + res.task.needCode[j] = false + res.task.pend-- + } + } + // Push the bytecode into a database batch + codes++ + rawdb.WriteCode(batch, hash, code) + } + bytes := common.StorageSize(batch.ValueSize()) + if err := batch.Write(); err != nil { + log.Crit("Failed to persist bytecodes", "err", err) + } + s.bytecodeSynced += codes + s.bytecodeBytes += bytes + + log.Debug("Persisted set of bytecodes", "count", codes, "bytes", bytes) + + // If this delivery completed the last pending task, forward the account task + // to the next chunk + if res.task.pend == 0 { + s.forwardAccountTask(res.task) + return + } + // Some accounts are still incomplete, leave as is for the storage and contract + // task assigners to pick up and fill. +} + +// processStorageResponse integrates an already validated storage response +// into the account tasks. +func (s *Syncer) processStorageResponse(res *storageResponse) { + // Switch the subtask from pending to idle + if res.subTask != nil { + res.subTask.req = nil + } + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.storageBytes += common.StorageSize(len(key) + len(value)) + }, + } + var ( + slots int + oldStorageBytes = s.storageBytes + ) + // Iterate over all the accounts and reconstruct their storage tries from the + // delivered slots + for i, account := range res.accounts { + // If the account was not delivered, reschedule it + if i >= len(res.hashes) { + res.mainTask.stateTasks[account] = res.roots[i] + continue + } + // State was delivered, if complete mark as not needed any more, otherwise + // mark the account as needing healing + for j, hash := range res.mainTask.res.hashes { + if account != hash { + continue + } + acc := res.mainTask.res.accounts[j] + + // If the packet contains multiple contract storage slots, all + // but the last are surely complete. The last contract may be + // chunked, so check it's continuation flag. + if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { + res.mainTask.needState[j] = false + res.mainTask.pend-- + res.mainTask.stateCompleted[account] = struct{}{} // mark it as completed + smallStorageGauge.Inc(1) + } + // If the last contract was chunked, mark it as needing healing + // to avoid writing it out to disk prematurely. + if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont { + res.mainTask.needHeal[j] = true + } + // If the last contract was chunked, we need to switch to large + // contract handling mode + if res.subTask == nil && i == len(res.hashes)-1 && res.cont { + // If we haven't yet started a large-contract retrieval, create + // the subtasks for it within the main account task + if tasks, ok := res.mainTask.SubTasks[account]; !ok { + var ( + keys = res.hashes[i] + chunks = uint64(storageConcurrency) + lastKey common.Hash + ) + if len(keys) > 0 { + lastKey = keys[len(keys)-1] + } + // If the number of slots remaining is low, decrease the + // number of chunks. Somewhere on the order of 10-15K slots + // fit into a packet of 500KB. A key/slot pair is maximum 64 + // bytes, so pessimistically maxRequestSize/64 = 8K. + // + // Chunk so that at least 2 packets are needed to fill a task. + if estimate, err := estimateRemainingSlots(len(keys), lastKey); err == nil { + if n := estimate / (2 * (maxRequestSize / 64)); n+1 < chunks { + chunks = n + 1 + } + log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "remaining", estimate, "chunks", chunks) + } else { + 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(), + OnPut: func(key []byte, value []byte) { + s.storageBytes += common.StorageSize(len(key) + len(value)) + }, + } + var tr genTrie + if s.scheme == rawdb.HashScheme { + tr = newHashTrie(batch) + } + if s.scheme == rawdb.PathScheme { + // Keep the left boundary as it's the first range. + tr = newPathTrie(account, false, s.db, batch) + } + tasks = append(tasks, &storageTask{ + Next: common.Hash{}, + Last: r.End(), + root: acc.Root, + genBatch: batch, + genTrie: tr, + }) + for r.Next() { + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.storageBytes += common.StorageSize(len(key) + len(value)) + }, + } + var tr genTrie + if s.scheme == rawdb.HashScheme { + tr = newHashTrie(batch) + } + if s.scheme == rawdb.PathScheme { + tr = newPathTrie(account, true, s.db, batch) + } + tasks = append(tasks, &storageTask{ + Next: r.Start(), + Last: r.End(), + root: acc.Root, + genBatch: batch, + genTrie: tr, + }) + } + for _, task := range tasks { + log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", task.Next, "last", task.Last) + } + res.mainTask.SubTasks[account] = tasks + + // Since we've just created the sub-tasks, this response + // is surely for the first one (zero origin) + res.subTask = tasks[0] + } + } + // If we're in large contract delivery mode, forward the subtask + if res.subTask != nil { + // Ensure the response doesn't overflow into the subsequent task + last := res.subTask.Last.Big() + // Find the first overflowing key. While at it, mark res as complete + // if we find the range to include or pass the 'last' + index := sort.Search(len(res.hashes[i]), func(k int) bool { + cmp := res.hashes[i][k].Big().Cmp(last) + if cmp >= 0 { + res.cont = false + } + return cmp > 0 + }) + if index >= 0 { + // cut off excess + res.hashes[i] = res.hashes[i][:index] + res.slots[i] = res.slots[i][:index] + } + // Forward the relevant storage chunk (even if created just now) + if res.cont { + res.subTask.Next = incHash(res.hashes[i][len(res.hashes[i])-1]) + } else { + res.subTask.done = true + } + } + } + // Iterate over all the complete contracts, reconstruct the trie nodes and + // push them to disk. If the contract is chunked, the trie nodes will be + // reconstructed later. + slots += len(res.hashes[i]) + + if i < len(res.hashes)-1 || res.subTask == nil { + // no need to make local reassignment of account: this closure does not outlive the loop + var tr genTrie + if s.scheme == rawdb.HashScheme { + tr = newHashTrie(batch) + } + if s.scheme == rawdb.PathScheme { + // Keep the left boundary as it's complete + tr = newPathTrie(account, false, s.db, batch) + } + for j := 0; j < len(res.hashes[i]); j++ { + tr.update(res.hashes[i][j][:], res.slots[i][j]) + } + tr.commit(true) + } + // Persist the received storage segments. These flat state maybe + // outdated during the sync, but it can be fixed later during the + // snapshot generation. + for j := 0; j < len(res.hashes[i]); j++ { + rawdb.WriteStorageSnapshot(batch, account, res.hashes[i][j], res.slots[i][j]) + + // If we're storing large contracts, generate the trie nodes + // on the fly to not trash the gluing points + if i == len(res.hashes)-1 && res.subTask != nil { + res.subTask.genTrie.update(res.hashes[i][j][:], res.slots[i][j]) + } + } + } + // Large contracts could have generated new trie nodes, flush them to disk + if res.subTask != nil { + if res.subTask.done { + root := res.subTask.genTrie.commit(res.subTask.Last == common.MaxHash) + 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.HasTrieNode(s.db, accountHash, nil, root, s.scheme) { + for i, account := range res.mainTask.res.hashes { + if account == accountHash { + res.mainTask.needHeal[i] = false + skipStorageHealingGauge.Inc(1) + } + } + } + } else if res.subTask.genBatch.ValueSize() > batchSizeThreshold { + res.subTask.genTrie.commit(false) + if err := res.subTask.genBatch.Write(); err != nil { + log.Error("Failed to persist stack slots", "err", err) + } + res.subTask.genBatch.Reset() + } + } + // Flush anything written just now and update the stats + if err := batch.Write(); err != nil { + log.Crit("Failed to persist storage slots", "err", err) + } + s.storageSynced += uint64(slots) + + log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "bytes", s.storageBytes-oldStorageBytes) + + // If this delivery completed the last pending task, forward the account task + // to the next chunk + if res.mainTask.pend == 0 { + s.forwardAccountTask(res.mainTask) + return + } + // Some accounts are still incomplete, leave as is for the storage and contract + // task assigners to pick up and fill. +} + +// processTrienodeHealResponse integrates an already validated trienode response +// into the healer tasks. +func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { + var ( + start = time.Now() + fills int + ) + for i, hash := range res.hashes { + node := res.nodes[i] + + // If the trie node was not delivered, reschedule it + if node == nil { + res.task.trieTasks[res.paths[i]] = res.hashes[i] + continue + } + fills++ + + // Push the trie node into the state syncer + s.trienodeHealSynced++ + s.trienodeHealBytes += common.StorageSize(len(node)) + + err := s.healer.scheduler.ProcessNode(trie.NodeSyncResult{Path: res.paths[i], Data: node}) + switch err { + case nil: + case trie.ErrAlreadyProcessed: + s.trienodeHealDups++ + case trie.ErrNotRequested: + s.trienodeHealNops++ + default: + log.Error("Invalid trienode processed", "hash", hash, "err", err) + } + } + s.commitHealer(false) + + // Calculate the processing rate of one filled trie node + rate := float64(fills) / (float64(time.Since(start)) / float64(time.Second)) + + // Update the currently measured trienode queueing and processing throughput. + // + // The processing rate needs to be updated uniformly independent if we've + // processed 1x100 trie nodes or 100x1 to keep the rate consistent even in + // the face of varying network packets. As such, we cannot just measure the + // time it took to process N trie nodes and update once, we need one update + // per trie node. + // + // Naively, that would be: + // + // for i:=0; i time.Second { + // Periodically adjust the trie node throttler + if float64(pending) > 2*s.trienodeHealRate { + s.trienodeHealThrottle *= trienodeHealThrottleIncrease + } else { + s.trienodeHealThrottle /= trienodeHealThrottleDecrease + } + if s.trienodeHealThrottle > maxTrienodeHealThrottle { + s.trienodeHealThrottle = maxTrienodeHealThrottle + } else if s.trienodeHealThrottle < minTrienodeHealThrottle { + s.trienodeHealThrottle = minTrienodeHealThrottle + } + s.trienodeHealThrottled = time.Now() + + log.Debug("Updated trie node heal throttler", "rate", s.trienodeHealRate, "pending", pending, "throttle", s.trienodeHealThrottle) + } +} + +func (s *Syncer) commitHealer(force bool) { + if !force && s.healer.scheduler.MemSize() < ethdb.IdealBatchSize { + return + } + batch := s.db.NewBatch() + if err := s.healer.scheduler.Commit(batch); err != nil { + log.Crit("Failed to commit healing data", "err", err) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist healing data", "err", err) + } + log.Debug("Persisted set of healing data", "type", "trienodes", "bytes", common.StorageSize(batch.ValueSize())) +} + +// processBytecodeHealResponse integrates an already validated bytecode response +// into the healer tasks. +func (s *Syncer) processBytecodeHealResponse(res *bytecodeHealResponse) { + for i, hash := range res.hashes { + node := res.codes[i] + + // If the trie node was not delivered, reschedule it + if node == nil { + res.task.codeTasks[hash] = struct{}{} + continue + } + // Push the trie node into the state syncer + s.bytecodeHealSynced++ + s.bytecodeHealBytes += common.StorageSize(len(node)) + + err := s.healer.scheduler.ProcessCode(trie.CodeSyncResult{Hash: hash, Data: node}) + switch err { + case nil: + case trie.ErrAlreadyProcessed: + s.bytecodeHealDups++ + case trie.ErrNotRequested: + s.bytecodeHealNops++ + default: + log.Error("Invalid bytecode processed", "hash", hash, "err", err) + } + } + s.commitHealer(false) +} + +// forwardAccountTask takes a filled account task and persists anything available +// into the database, after which it forwards the next account marker so that the +// task's next chunk may be filled. +func (s *Syncer) forwardAccountTask(task *accountTask) { + // Remove any pending delivery + res := task.res + if res == nil { + return // nothing to forward + } + task.res = nil + + // Persist the received account segments. These flat state maybe + // outdated during the sync, but it can be fixed later during the + // snapshot generation. + oldAccountBytes := s.accountBytes + + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.accountBytes += common.StorageSize(len(key) + len(value)) + }, + } + for i, hash := range res.hashes { + if task.needCode[i] || task.needState[i] { + break + } + slim := types.SlimAccountRLP(*res.accounts[i]) + rawdb.WriteAccountSnapshot(batch, hash, slim) + + // If the task is complete, drop it into the stack trie to generate + // account trie nodes for it + if !task.needHeal[i] { + full, err := types.FullAccountRLP(slim) // TODO(karalabe): Slim parsing can be omitted + if err != nil { + panic(err) // Really shouldn't ever happen + } + task.genTrie.update(hash[:], full) + } + } + // Flush anything written just now and update the stats + if err := batch.Write(); err != nil { + log.Crit("Failed to persist accounts", "err", err) + } + s.accountSynced += uint64(len(res.accounts)) + + // Task filling persisted, push it the chunk marker forward to the first + // account still missing data. + for i, hash := range res.hashes { + if task.needCode[i] || task.needState[i] { + return + } + task.Next = incHash(hash) + + // Remove the completion flag once the account range is pushed + // forward. The leftover accounts will be skipped in the next + // cycle. + delete(task.stateCompleted, hash) + } + // All accounts marked as complete, track if the entire task is done + task.done = !res.cont + + // Error out if there is any leftover completion flag. + if task.done && len(task.stateCompleted) != 0 { + panic(fmt.Errorf("storage completion flags should be emptied, %d left", len(task.stateCompleted))) + } + // Stack trie could have generated trie nodes, push them to disk (we need to + // 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 { + task.genTrie.commit(task.Last == common.MaxHash) + if err := task.genBatch.Write(); err != nil { + log.Error("Failed to persist stack account", "err", err) + } + task.genBatch.Reset() + } else if task.genBatch.ValueSize() > batchSizeThreshold { + task.genTrie.commit(false) + if err := task.genBatch.Write(); err != nil { + log.Error("Failed to persist stack account", "err", err) + } + task.genBatch.Reset() + } + log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "bytes", s.accountBytes-oldAccountBytes) +} + +// OnAccounts is a callback method to invoke when a range of accounts are +// received from a remote peer. +func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error { + size := common.StorageSize(len(hashes) * common.HashLength) + for _, account := range accounts { + size += common.StorageSize(len(account)) + } + for _, node := range proof { + size += common.StorageSize(len(node)) + } + logger := peer.Log().New("reqid", id) + logger.Trace("Delivering range of accounts", "hashes", len(hashes), "accounts", len(accounts), "proofs", len(proof), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + defer func() { + s.lock.Lock() + defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { + s.accountIdlers[peer.ID()] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + }() + s.lock.Lock() + // Ensure the response is for a valid request + req, ok := s.accountReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected account range packet") + s.lock.Unlock() + return nil + } + delete(s.accountReqs, id) + s.rates.Update(peer.ID(), AccountRangeMsg, time.Since(req.time), int(size)) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For account 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 && len(accounts) == 0 && len(proof) == 0 { + logger.Debug("Peer rejected account range request", "root", s.root) + s.statelessPeers[peer.ID()] = struct{}{} + s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertAccountRequest(req) + return nil + } + root := s.root + s.lock.Unlock() + + // Reconstruct a partial trie from the response and verify it + keys := make([][]byte, len(hashes)) + for i, key := range hashes { + keys[i] = common.CopyBytes(key[:]) + } + nodes := make(trienode.ProofList, len(proof)) + for i, node := range proof { + nodes[i] = node + } + 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 + s.scheduleRevertAccountRequest(req) + return err + } + accs := make([]*types.StateAccount, len(accounts)) + for i, account := range accounts { + acc := new(types.StateAccount) + if err := rlp.DecodeBytes(account, acc); err != nil { + panic(err) // We created these blobs, we must be able to decode them + } + accs[i] = acc + } + response := &accountResponse{ + task: req.task, + hashes: hashes, + accounts: accs, + cont: cont, + } + select { + case req.deliver <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer. +func (s *Syncer) OnByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { + s.lock.RLock() + syncing := !s.snapped + s.lock.RUnlock() + + if syncing { + return s.onByteCodes(peer, id, bytecodes) + } + return s.onHealByteCodes(peer, id, bytecodes) +} + +// onByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer in the syncing phase. +func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { + var size common.StorageSize + for _, code := range bytecodes { + size += common.StorageSize(len(code)) + } + logger := peer.Log().New("reqid", id) + logger.Trace("Delivering set of bytecodes", "bytecodes", len(bytecodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + defer func() { + s.lock.Lock() + defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { + s.bytecodeIdlers[peer.ID()] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + }() + s.lock.Lock() + // Ensure the response is for a valid request + req, ok := s.bytecodeReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected bytecode packet") + s.lock.Unlock() + return nil + } + delete(s.bytecodeReqs, id) + s.rates.Update(peer.ID(), ByteCodesMsg, time.Since(req.time), len(bytecodes)) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(bytecodes) == 0 { + logger.Debug("Peer rejected bytecode request") + s.statelessPeers[peer.ID()] = struct{}{} + s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeRequest(req) + return nil + } + s.lock.Unlock() + + // Cross reference the requested bytecodes with the response to find gaps + // that the serving node is missing + hasher := crypto.NewKeccakState() + hash := make([]byte, 32) + + codes := make([][]byte, len(req.hashes)) + for i, j := 0, 0; i < len(bytecodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + codes[j] = bytecodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected bytecodes", "count", len(bytecodes)-i) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeRequest(req) + return errors.New("unexpected bytecode") + } + // Response validated, send it to the scheduler for filling + response := &bytecodeResponse{ + task: req.task, + hashes: req.hashes, + codes: codes, + } + select { + case req.deliver <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnStorage is a callback method to invoke when ranges of storage slots +// are received from a remote peer. +func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error { + // Gather some trace stats to aid in debugging issues + var ( + hashCount int + slotCount int + size common.StorageSize + ) + for _, hashset := range hashes { + size += common.StorageSize(common.HashLength * len(hashset)) + hashCount += len(hashset) + } + for _, slotset := range slots { + for _, slot := range slotset { + size += common.StorageSize(len(slot)) + } + slotCount += len(slotset) + } + for _, node := range proof { + size += common.StorageSize(len(node)) + } + logger := peer.Log().New("reqid", id) + logger.Trace("Delivering ranges of storage slots", "accounts", len(hashes), "hashes", hashCount, "slots", slotCount, "proofs", len(proof), "size", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + defer func() { + s.lock.Lock() + defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { + s.storageIdlers[peer.ID()] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + }() + s.lock.Lock() + // Ensure the response is for a valid request + req, ok := s.storageReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected storage ranges packet") + s.lock.Unlock() + return nil + } + delete(s.storageReqs, id) + s.rates.Update(peer.ID(), StorageRangesMsg, time.Since(req.time), int(size)) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } + + // Reject the response if the hash sets and slot sets don't match, or if the + // peer sent more data than requested. + if len(hashes) != len(slots) { + s.lock.Unlock() + s.scheduleRevertStorageRequest(req) // reschedule request + logger.Warn("Hash and slot set size mismatch", "hashset", len(hashes), "slotset", len(slots)) + return errors.New("hash and slot set size mismatch") + } + if len(hashes) > len(req.accounts) { + s.lock.Unlock() + s.scheduleRevertStorageRequest(req) // reschedule request + logger.Warn("Hash set larger than requested", "hashset", len(hashes), "requested", len(req.accounts)) + return errors.New("hash set larger than requested") + } + // Response is valid, but check if peer is signalling that it does not have + // 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 && len(proof) == 0 { + logger.Debug("Peer rejected storage request") + s.statelessPeers[peer.ID()] = struct{}{} + s.lock.Unlock() + s.scheduleRevertStorageRequest(req) // reschedule request + return nil + } + s.lock.Unlock() + + // 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(trienode.ProofList, 0, len(proof)) + if i == len(hashes)-1 { + for _, node := range proof { + nodes = append(nodes, node) + } + } + var err error + 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, keys, slots[i], nil) + if err != nil { + s.scheduleRevertStorageRequest(req) // reschedule request + logger.Warn("Storage slots failed proof", "err", err) + return err + } + } else { + // A proof was attached, the response is only partial, check that the + // returned data is indeed part of the storage trie + proofdb := nodes.Set() + + 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) + return err + } + } + } + // Partial tries reconstructed, send them to the scheduler for storage filling + response := &storageResponse{ + mainTask: req.mainTask, + subTask: req.subTask, + accounts: req.accounts, + roots: req.roots, + hashes: hashes, + slots: slots, + cont: cont, + } + select { + case req.deliver <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnTrieNodes is a callback method to invoke when a batch of trie nodes +// are received from a remote peer. +func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error { + var size common.StorageSize + for _, node := range trienodes { + size += common.StorageSize(len(node)) + } + logger := peer.Log().New("reqid", id) + logger.Trace("Delivering set of healing trienodes", "trienodes", len(trienodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + defer func() { + s.lock.Lock() + defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { + s.trienodeHealIdlers[peer.ID()] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + }() + s.lock.Lock() + // Ensure the response is for a valid request + req, ok := s.trienodeHealReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected trienode heal packet") + s.lock.Unlock() + return nil + } + delete(s.trienodeHealReqs, id) + s.rates.Update(peer.ID(), TrieNodesMsg, time.Since(req.time), len(trienodes)) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(trienodes) == 0 { + logger.Debug("Peer rejected trienode heal request") + s.statelessPeers[peer.ID()] = struct{}{} + s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertTrienodeHealRequest(req) + return nil + } + s.lock.Unlock() + + // Cross reference the requested trienodes with the response to find gaps + // that the serving node is missing + var ( + hasher = crypto.NewKeccakState() + hash = make([]byte, 32) + nodes = make([][]byte, len(req.hashes)) + fills uint64 + ) + for i, j := 0, 0; i < len(trienodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(trienodes[i]) + hasher.Read(hash) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + nodes[j] = trienodes[i] + fills++ + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected healing trienodes", "count", len(trienodes)-i) + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertTrienodeHealRequest(req) + return errors.New("unexpected healing trienode") + } + // Response validated, send it to the scheduler for filling + s.trienodeHealPend.Add(fills) + defer func() { + s.trienodeHealPend.Add(^(fills - 1)) + }() + response := &trienodeHealResponse{ + paths: req.paths, + task: req.task, + hashes: req.hashes, + nodes: nodes, + } + select { + case req.deliver <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// onHealByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer in the healing phase. +func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { + var size common.StorageSize + for _, code := range bytecodes { + size += common.StorageSize(len(code)) + } + logger := peer.Log().New("reqid", id) + logger.Trace("Delivering set of healing bytecodes", "bytecodes", len(bytecodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + defer func() { + s.lock.Lock() + defer s.lock.Unlock() + if _, ok := s.peers[peer.ID()]; ok { + s.bytecodeHealIdlers[peer.ID()] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + }() + s.lock.Lock() + // Ensure the response is for a valid request + req, ok := s.bytecodeHealReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected bytecode heal packet") + s.lock.Unlock() + return nil + } + delete(s.bytecodeHealReqs, id) + s.rates.Update(peer.ID(), ByteCodesMsg, time.Since(req.time), len(bytecodes)) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(bytecodes) == 0 { + logger.Debug("Peer rejected bytecode heal request") + s.statelessPeers[peer.ID()] = struct{}{} + s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeHealRequest(req) + return nil + } + s.lock.Unlock() + + // Cross reference the requested bytecodes with the response to find gaps + // that the serving node is missing + hasher := crypto.NewKeccakState() + hash := make([]byte, 32) + + codes := make([][]byte, len(req.hashes)) + for i, j := 0, 0; i < len(bytecodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + codes[j] = bytecodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected healing bytecodes", "count", len(bytecodes)-i) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeHealRequest(req) + return errors.New("unexpected healing bytecode") + } + // Response validated, send it to the scheduler for filling + response := &bytecodeHealResponse{ + task: req.task, + hashes: req.hashes, + codes: codes, + } + select { + case req.deliver <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// onHealState is a callback method to invoke when a flat state(account +// or storage slot) is downloaded during the healing stage. The flat states +// can be persisted blindly and can be fixed later in the generation stage. +// Note it's not concurrent safe, please handle the concurrent issue outside. +func (s *Syncer) onHealState(paths [][]byte, value []byte) error { + if len(paths) == 1 { + var account types.StateAccount + if err := rlp.DecodeBytes(value, &account); err != nil { + return nil // Returning the error here would drop the remote peer + } + blob := types.SlimAccountRLP(account) + rawdb.WriteAccountSnapshot(s.stateWriter, common.BytesToHash(paths[0]), blob) + s.accountHealed += 1 + s.accountHealedBytes += common.StorageSize(1 + common.HashLength + len(blob)) + } + if len(paths) == 2 { + rawdb.WriteStorageSnapshot(s.stateWriter, common.BytesToHash(paths[0]), common.BytesToHash(paths[1]), value) + s.storageHealed += 1 + s.storageHealedBytes += common.StorageSize(1 + 2*common.HashLength + len(value)) + } + if s.stateWriter.ValueSize() > ethdb.IdealBatchSize { + s.stateWriter.Write() // It's fine to ignore the error here + s.stateWriter.Reset() + } + return nil +} + +// hashSpace is the total size of the 256 bit hash space for accounts. +var hashSpace = new(big.Int).Exp(common.Big2, common.Big256, nil) + +// report calculates various status reports and provides it to the user. +func (s *Syncer) report(force bool) { + if len(s.tasks) > 0 { + s.reportSyncProgress(force) + return + } + s.reportHealProgress(force) +} + +// reportSyncProgress calculates various status reports and provides it to the user. +func (s *Syncer) reportSyncProgress(force bool) { + // Don't report all the events, just occasionally + if !force && time.Since(s.logTime) < 8*time.Second { + return + } + // Don't report anything until we have a meaningful progress + synced := s.accountBytes + s.bytecodeBytes + s.storageBytes + if synced == 0 { + return + } + accountGaps := new(big.Int) + for _, task := range s.tasks { + accountGaps.Add(accountGaps, new(big.Int).Sub(task.Last.Big(), task.Next.Big())) + } + accountFills := new(big.Int).Sub(hashSpace, accountGaps) + if accountFills.BitLen() == 0 { + return + } + s.logTime = time.Now() + estBytes := float64(new(big.Int).Div( + new(big.Int).Mul(new(big.Int).SetUint64(uint64(synced)), hashSpace), + accountFills, + ).Uint64()) + // Don't report anything until we have a meaningful progress + if estBytes < 1.0 { + return + } + elapsed := time.Since(s.startTime) + estTime := elapsed / time.Duration(synced) * time.Duration(estBytes) + + // Create a mega progress report + var ( + progress = fmt.Sprintf("%.2f%%", float64(synced)*100/estBytes) + accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountSynced), s.accountBytes.TerminalString()) + storage = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageSynced), s.storageBytes.TerminalString()) + bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeSynced), s.bytecodeBytes.TerminalString()) + ) + log.Info("Syncing: state download in progress", "synced", progress, "state", synced, + "accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed)) +} + +// reportHealProgress calculates various status reports and provides it to the user. +func (s *Syncer) reportHealProgress(force bool) { + // Don't report all the events, just occasionally + if !force && time.Since(s.logTime) < 8*time.Second { + return + } + s.logTime = time.Now() + + // Create a mega progress report + var ( + trienode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.trienodeHealSynced), s.trienodeHealBytes.TerminalString()) + bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeHealSynced), s.bytecodeHealBytes.TerminalString()) + accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountHealed), s.accountHealedBytes.TerminalString()) + storage = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageHealed), s.storageHealedBytes.TerminalString()) + ) + log.Info("Syncing: state healing in progress", "accounts", accounts, "slots", storage, + "codes", bytecode, "nodes", trienode, "pending", s.healer.scheduler.Pending()) +} + +// estimateRemainingSlots tries to determine roughly how many slots are left in +// a contract storage, based on the number of keys and the last hash. This method +// assumes that the hashes are lexicographically ordered and evenly distributed. +func estimateRemainingSlots(hashes int, last common.Hash) (uint64, error) { + if last == (common.Hash{}) { + return 0, errors.New("last hash empty") + } + space := new(big.Int).Mul(math.MaxBig256, big.NewInt(int64(hashes))) + space.Div(space, last.Big()) + if !space.IsUint64() { + // Gigantic address space probably due to too few or malicious slots + return 0, errors.New("too few slots for estimation") + } + return space.Uint64() - uint64(hashes), nil +} + +// capacitySort implements the Sort interface, allowing sorting by peer message +// throughput. Note, callers should use sort.Reverse to get the desired effect +// of highest capacity being at the front. +type capacitySort struct { + ids []string + caps []int +} + +func (s *capacitySort) Len() int { + return len(s.ids) +} + +func (s *capacitySort) Less(i, j int) bool { + return s.caps[i] < s.caps[j] +} + +func (s *capacitySort) Swap(i, j int) { + s.ids[i], s.ids[j] = s.ids[j], s.ids[i] + s.caps[i], s.caps[j] = s.caps[j], s.caps[i] +} + +// healRequestSort implements the Sort interface, allowing sorting trienode +// heal requests, which is a prerequisite for merging storage-requests. +type healRequestSort struct { + paths []string + hashes []common.Hash + syncPaths []trie.SyncPath +} + +func (t *healRequestSort) Len() int { + return len(t.hashes) +} + +func (t *healRequestSort) Less(i, j int) bool { + a := t.syncPaths[i] + b := t.syncPaths[j] + switch bytes.Compare(a[0], b[0]) { + case -1: + return true + case 1: + return false + } + // identical first part + if len(a) < len(b) { + return true + } + if len(b) < len(a) { + return false + } + if len(a) == 2 { + return bytes.Compare(a[1], b[1]) < 0 + } + return false +} + +func (t *healRequestSort) Swap(i, j int) { + t.paths[i], t.paths[j] = t.paths[j], t.paths[i] + t.hashes[i], t.hashes[j] = t.hashes[j], t.hashes[i] + t.syncPaths[i], t.syncPaths[j] = t.syncPaths[j], t.syncPaths[i] +} + +// Merge merges the pathsets, so that several storage requests concerning the +// same account are merged into one, to reduce bandwidth. +// OBS: This operation is moot if t has not first been sorted. +func (t *healRequestSort) Merge() []TrieNodePathSet { + var result []TrieNodePathSet + for _, path := range t.syncPaths { + pathset := TrieNodePathSet(path) + if len(path) == 1 { + // It's an account reference. + result = append(result, pathset) + } else { + // It's a storage reference. + end := len(result) - 1 + if len(result) == 0 || !bytes.Equal(pathset[0], result[end][0]) { + // The account doesn't match last, create a new entry. + result = append(result, pathset) + } else { + // It's the same account as the previous one, add to the storage + // paths of that request. + result[end] = append(result[end], pathset[1]) + } + } + } + return result +} + +// sortByAccountPath takes hashes and paths, and sorts them. After that, it generates +// the TrieNodePaths and merges paths which belongs to the same account path. +func sortByAccountPath(paths []string, hashes []common.Hash) ([]string, []common.Hash, []trie.SyncPath, []TrieNodePathSet) { + syncPaths := make([]trie.SyncPath, len(paths)) + for i, path := range paths { + syncPaths[i] = trie.NewSyncPath([]byte(path)) + } + n := &healRequestSort{paths, hashes, syncPaths} + sort.Sort(n) + pathsets := n.Merge() + return n.paths, n.hashes, n.syncPaths, pathsets +} diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go new file mode 100644 index 0000000..c97c3b9 --- /dev/null +++ b/eth/protocols/snap/sync_test.go @@ -0,0 +1,1966 @@ +// 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 snap + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "fmt" + "math/big" + mrand "math/rand" + "slices" + "sync" + "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/internal/testrand" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" +) + +func TestHashing(t *testing.T) { + t.Parallel() + + var bytecodes = make([][]byte, 10) + for i := 0; i < len(bytecodes); i++ { + buf := make([]byte, 100) + rand.Read(buf) + bytecodes[i] = buf + } + var want, got string + var old = func() { + hasher := sha3.NewLegacyKeccak256() + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hash := hasher.Sum(nil) + got = fmt.Sprintf("%v\n%v", got, hash) + } + } + var new = func() { + hasher := crypto.NewKeccakState() + var hash = make([]byte, 32) + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + want = fmt.Sprintf("%v\n%v", want, hash) + } + } + old() + new() + if want != got { + t.Errorf("want\n%v\ngot\n%v\n", want, got) + } +} + +func BenchmarkHashing(b *testing.B) { + var bytecodes = make([][]byte, 10000) + for i := 0; i < len(bytecodes); i++ { + buf := make([]byte, 100) + rand.Read(buf) + bytecodes[i] = buf + } + var old = func() { + hasher := sha3.NewLegacyKeccak256() + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Sum(nil) + } + } + var new = func() { + hasher := crypto.NewKeccakState() + var hash = make([]byte, 32) + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + } + } + b.Run("old", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + old() + } + }) + b.Run("new", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + new() + } + }) +} + +type ( + accountHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error + storageHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error + trieHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error + codeHandlerFunc func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error +) + +type testPeer struct { + id string + test *testing.T + remote *Syncer + logger log.Logger + accountTrie *trie.Trie + accountValues []*kv + storageTries map[common.Hash]*trie.Trie + storageValues map[common.Hash][]*kv + + accountRequestHandler accountHandlerFunc + storageRequestHandler storageHandlerFunc + trieRequestHandler trieHandlerFunc + codeRequestHandler codeHandlerFunc + term func() + + // counters + nAccountRequests int + nStorageRequests int + nBytecodeRequests int + nTrienodeRequests int +} + +func newTestPeer(id string, t *testing.T, term func()) *testPeer { + peer := &testPeer{ + id: id, + test: t, + logger: log.New("id", id), + accountRequestHandler: defaultAccountRequestHandler, + trieRequestHandler: defaultTrieRequestHandler, + storageRequestHandler: defaultStorageRequestHandler, + codeRequestHandler: defaultCodeRequestHandler, + term: term, + } + //stderrHandler := log.StreamHandler(os.Stderr, log.TerminalFormat(true)) + //peer.logger.SetHandler(stderrHandler) + return peer +} + +func (t *testPeer) setStorageTries(tries map[common.Hash]*trie.Trie) { + t.storageTries = make(map[common.Hash]*trie.Trie) + for root, trie := range tries { + t.storageTries[root] = trie.Copy() + } +} + +func (t *testPeer) ID() string { return t.id } +func (t *testPeer) Log() log.Logger { return t.logger } + +func (t *testPeer) Stats() string { + return fmt.Sprintf(`Account requests: %d +Storage requests: %d +Bytecode requests: %d +Trienode requests: %d +`, t.nAccountRequests, t.nStorageRequests, t.nBytecodeRequests, t.nTrienodeRequests) +} + +func (t *testPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error { + t.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) + t.nAccountRequests++ + go t.accountRequestHandler(t, id, root, origin, limit, bytes) + return nil +} + +func (t *testPeer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error { + t.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) + t.nTrienodeRequests++ + go t.trieRequestHandler(t, id, root, paths, bytes) + return nil +} + +func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error { + t.nStorageRequests++ + if len(accounts) == 1 && origin != nil { + t.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes)) + } else { + t.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes)) + } + go t.storageRequestHandler(t, id, root, accounts, origin, limit, bytes) + return nil +} + +func (t *testPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { + t.nBytecodeRequests++ + t.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) + go t.codeRequestHandler(t, id, hashes, bytes) + return nil +} + +// defaultTrieRequestHandler is a well-behaving handler for trie healing requests +func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + // Pass the response + var nodes [][]byte + for _, pathset := range paths { + switch len(pathset) { + case 1: + blob, _, err := t.accountTrie.GetNode(pathset[0]) + if err != nil { + t.logger.Info("Error handling req", "error", err) + break + } + nodes = append(nodes, blob) + default: + account := t.storageTries[(common.BytesToHash(pathset[0]))] + for _, path := range pathset[1:] { + blob, _, err := account.GetNode(path) + if err != nil { + t.logger.Info("Error handling req", "error", err) + break + } + nodes = append(nodes, blob) + } + } + } + t.remote.OnTrieNodes(t, requestId, nodes) + return nil +} + +// defaultAccountRequestHandler is a well-behaving handler for AccountRangeRequests +func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + keys, vals, proofs := createAccountRequestResponse(t, root, origin, limit, cap) + if err := t.remote.OnAccounts(t, id, keys, vals, proofs); err != nil { + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.term() + return err + } + return nil +} + +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.MaxHash + } + for _, entry := range t.accountValues { + if size > cap { + break + } + if bytes.Compare(origin[:], entry.k) <= 0 { + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) + } + // If we've exceeded the request threshold, abort + if bytes.Compare(entry.k, limit[:]) >= 0 { + break + } + } + // 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 := trienode.NewProofSet() + if err := t.accountTrie.Prove(origin[:], proof); err != nil { + t.logger.Error("Could not prove inexistence of origin", "origin", origin, "error", err) + } + if len(keys) > 0 { + lastK := (keys[len(keys)-1])[:] + if err := t.accountTrie.Prove(lastK, proof); err != nil { + t.logger.Error("Could not prove last item", "error", err) + } + } + return keys, vals, proof.List() +} + +// defaultStorageRequestHandler is a well-behaving storage request handler +func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) error { + hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, bOrigin, bLimit, max) + if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.term() + } + return nil +} + +func defaultCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes { + bytecodes = append(bytecodes, getCodeByHash(h)) + } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.term() + } + return nil +} + +func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { + var size uint64 + for _, account := range accounts { + // The first account might start from a different origin and end sooner + var originHash common.Hash + if len(origin) > 0 { + originHash = common.BytesToHash(origin) + } + var limitHash = common.MaxHash + if len(limit) > 0 { + limitHash = common.BytesToHash(limit) + } + var ( + keys []common.Hash + vals [][]byte + abort bool + ) + for _, entry := range t.storageValues[account] { + if size >= max { + abort = true + break + } + if bytes.Compare(entry.k, originHash[:]) < 0 { + continue + } + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) + if bytes.Compare(entry.k, limitHash[:]) >= 0 { + break + } + } + if len(keys) > 0 { + hashes = append(hashes, keys) + slots = append(slots, vals) + } + // Generate the Merkle proofs for the first and last storage slot, but + // only if the response was capped. If the entire storage trie included + // in the response, no need for any proofs. + 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 := trienode.NewProofSet() + stTrie := t.storageTries[account] + + // Here's a potential gotcha: when constructing the proof, we cannot + // use the 'origin' slice directly, but must use the full 32-byte + // hash form. + if err := stTrie.Prove(originHash[:], proof); err != nil { + t.logger.Error("Could not prove inexistence of origin", "origin", originHash, "error", err) + } + if len(keys) > 0 { + lastK := (keys[len(keys)-1])[:] + if err := stTrie.Prove(lastK, proof); err != nil { + t.logger.Error("Could not prove last item", "error", err) + } + } + proofs = append(proofs, proof.List()...) + break + } + } + return hashes, slots, proofs +} + +// createStorageRequestResponseAlwaysProve tests a cornercase, where the peer always +// supplies the proof for the last account, even if it is 'complete'. +func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { + var size uint64 + max = max * 3 / 4 + + var origin common.Hash + if len(bOrigin) > 0 { + origin = common.BytesToHash(bOrigin) + } + var exit bool + for i, account := range accounts { + var keys []common.Hash + var vals [][]byte + for _, entry := range t.storageValues[account] { + if bytes.Compare(entry.k, origin[:]) < 0 { + exit = true + } + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) + if size > max { + exit = true + } + } + if i == len(accounts)-1 { + exit = true + } + hashes = append(hashes, keys) + slots = append(slots, vals) + + if exit { + // If we're aborting, we need to prove the first and last item + // This terminates the response (and thus the loop) + proof := trienode.NewProofSet() + stTrie := t.storageTries[account] + + // Here's a potential gotcha: when constructing the proof, we cannot + // use the 'origin' slice directly, but must use the full 32-byte + // hash form. + if err := stTrie.Prove(origin[:], proof); err != nil { + t.logger.Error("Could not prove inexistence of origin", "origin", origin, + "error", err) + } + if len(keys) > 0 { + lastK := (keys[len(keys)-1])[:] + if err := stTrie.Prove(lastK, proof); err != nil { + t.logger.Error("Could not prove last item", "error", err) + } + } + proofs = append(proofs, proof.List()...) + break + } + } + return hashes, slots, proofs +} + +// emptyRequestAccountRangeFn is a rejects AccountRangeRequests +func emptyRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + t.remote.OnAccounts(t, requestId, nil, nil, nil) + return nil +} + +func nonResponsiveRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + return nil +} + +func emptyTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + t.remote.OnTrieNodes(t, requestId, nil) + return nil +} + +func nonResponsiveTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + return nil +} + +func emptyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + t.remote.OnStorage(t, requestId, nil, nil, nil) + return nil +} + +func nonResponsiveStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + return nil +} + +func proofHappyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + hashes, slots, proofs := createStorageRequestResponseAlwaysProve(t, root, accounts, origin, limit, max) + if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.term() + } + return nil +} + +//func emptyCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { +// var bytecodes [][]byte +// t.remote.OnByteCodes(t, id, bytecodes) +// return nil +//} + +func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes { + // Send back the hashes + bytecodes = append(bytecodes, h[:]) + } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +func cappedCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes[:1] { + bytecodes = append(bytecodes, getCodeByHash(h)) + } + // Missing bytecode can be retrieved again, no error expected + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.term() + } + return nil +} + +// starvingStorageRequestHandler is somewhat well-behaving storage handler, but it caps the returned results to be very small +func starvingStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + return defaultStorageRequestHandler(t, requestId, root, accounts, origin, limit, 500) +} + +func starvingAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + return defaultAccountRequestHandler(t, requestId, root, origin, limit, 500) +} + +//func misdeliveringAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { +// return defaultAccountRequestHandler(t, requestId-1, root, origin, 500) +//} + +func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + hashes, accounts, proofs := createAccountRequestResponse(t, root, origin, limit, cap) + if len(proofs) > 0 { + proofs = proofs[1:] + } + if err := t.remote.OnAccounts(t, requestId, hashes, accounts, proofs); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +// corruptStorageRequestHandler doesn't provide good proofs +func corruptStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, origin, limit, max) + if len(proofs) > 0 { + proofs = proofs[1:] + } + if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + hashes, slots, _ := createStorageRequestResponse(t, root, accounts, origin, limit, max) + if err := t.remote.OnStorage(t, requestId, hashes, slots, nil); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +// TestSyncBloatedProof tests a scenario where we provide only _one_ value, but +// also ship the entire trie inside the proof. If the attack is successful, +// the remote side does not do any follow-up requests +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme) + source := newTestPeer("source", t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + + source.accountRequestHandler = func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + var ( + keys []common.Hash + vals [][]byte + ) + // The values + for _, entry := range t.accountValues { + if bytes.Compare(entry.k, origin[:]) < 0 { + continue + } + if bytes.Compare(entry.k, limit[:]) > 0 { + continue + } + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + } + // The proofs + proof := trienode.NewProofSet() + if err := t.accountTrie.Prove(origin[:], proof); err != nil { + t.logger.Error("Could not prove origin", "origin", origin, "error", err) + t.logger.Error("Could not prove origin", "origin", origin, "error", err) + } + // The bloat: add proof of every single element + for _, entry := range t.accountValues { + if err := t.accountTrie.Prove(entry.k, proof); err != nil { + t.logger.Error("Could not prove item", "error", err) + } + } + // And remove one item from the elements + if len(keys) > 2 { + keys = append(keys[:1], keys[2:]...) + vals = append(vals[:1], vals[2:]...) + } + if err := t.remote.OnAccounts(t, requestId, keys, vals, proof.List()); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + t.term() + // This is actually correct, signal to exit the test successfully + } + return nil + } + syncer := setupSyncer(nodeScheme, source) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err == nil { + t.Fatal("No error returned from incomplete/cancelled sync") + } +} + +func setupSyncer(scheme string, peers ...*testPeer) *Syncer { + stateDb := rawdb.NewMemoryDatabase() + syncer := NewSyncer(stateDb, scheme) + for _, peer := range peers { + syncer.Register(peer) + peer.remote = syncer + } + return syncer +} + +// TestSync tests a basic sync with one peer +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + return source + } + syncer := setupSyncer(nodeScheme, mkSource("source")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncTinyTriePanic tests a basic sync with one peer, and a tiny trie. This caused a +// panic within the prover +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(1, scheme) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + return source + } + syncer := setupSyncer(nodeScheme, mkSource("source")) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + 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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + return source + } + syncer := setupSyncer(nodeScheme, mkSource("sourceA"), mkSource("sourceB")) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + 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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 3000, true, false, false) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + return source + } + 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(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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + 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) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + + if !noAccount { + source.accountRequestHandler = emptyRequestAccountRangeFn + } + if !noStorage { + source.storageRequestHandler = emptyStorageRequestHandler + } + if !noTrieNode { + source.trieRequestHandler = emptyTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + scheme, + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestMultiSyncManyUselessWithLowTimeout 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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + 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) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + + if !noAccount { + source.accountRequestHandler = emptyRequestAccountRangeFn + } + if !noStorage { + source.storageRequestHandler = emptyStorageRequestHandler + } + if !noTrieNode { + source.trieRequestHandler = emptyTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + scheme, + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + // We're setting the timeout to very low, to increase the chance of the timeout + // being triggered. This was previously a cause of panic, when a response + // arrived simultaneously as a timeout was triggered. + syncer.rates.OverrideTTLLimit = time.Millisecond + + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + 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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + 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) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + + if !noAccount { + source.accountRequestHandler = nonResponsiveRequestAccountRangeFn + } + if !noStorage { + source.storageRequestHandler = nonResponsiveStorageRequestHandler + } + if !noTrieNode { + source.trieRequestHandler = nonResponsiveTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + scheme, + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + // We're setting the timeout to very low, to make the test run a bit faster + syncer.rates.OverrideTTLLimit = time.Millisecond + + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +func checkStall(t *testing.T, term func()) chan struct{} { + testDone := make(chan struct{}) + go func() { + select { + case <-time.After(time.Minute): // TODO(karalabe): Make tests smaller, this is too much + t.Log("Sync stalled") + term() + case <-testDone: + return + } + }() + return testDone +} + +// TestSyncBoundaryAccountTrie tests sync against a few normal peers, but the +// account trie has a few boundary elements. +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeBoundaryAccountTrie(scheme, 3000) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + return source + } + syncer := setupSyncer( + nodeScheme, + mkSource("peer-a"), + mkSource("peer-b"), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncNoStorageAndOneCappedPeer tests sync using accounts and no storage, where one peer is +// consistently returning very small results +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme) + + mkSource := func(name string, slow bool) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + + if slow { + source.accountRequestHandler = starvingAccountRequestHandler + } + return source + } + + syncer := setupSyncer( + nodeScheme, + mkSource("nice-a", false), + mkSource("nice-b", false), + mkSource("nice-c", false), + mkSource("capped", true), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncNoStorageAndOneCodeCorruptPeer has one peer which doesn't deliver +// code requests properly. +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme) + + mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.codeRequestHandler = codeFn + return source + } + // One is capped, one is corrupt. If we don't use a capped one, there's a 50% + // chance that the full set of codes requested are sent only to the + // non-corrupt peer, which delivers everything in one go, and makes the + // test moot + syncer := setupSyncer( + nodeScheme, + mkSource("capped", cappedCodeRequestHandler), + mkSource("corrupt", corruptCodeRequestHandler), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + 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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme) + + mkSource := func(name string, accFn accountHandlerFunc) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.accountRequestHandler = accFn + return source + } + // One is capped, one is corrupt. If we don't use a capped one, there's a 50% + // chance that the full set of codes requested are sent only to the + // non-corrupt peer, which delivers everything in one go, and makes the + // test moot + syncer := setupSyncer( + nodeScheme, + mkSource("capped", defaultAccountRequestHandler), + mkSource("corrupt", corruptAccountRequestHandler), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncNoStorageAndOneCodeCappedPeer has one peer which delivers code hashes +// one by one +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme) + + mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.codeRequestHandler = codeFn + return source + } + // Count how many times it's invoked. Remember, there are only 8 unique hashes, + // so it shouldn't be more than that + var counter int + syncer := setupSyncer( + nodeScheme, + mkSource("capped", func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + counter++ + return cappedCodeRequestHandler(t, id, hashes, max) + }), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + + // There are only 8 unique hashes, and 3K accounts. However, the code + // deduplication is per request batch. If it were a perfect global dedup, + // we would expect only 8 requests. If there were no dedup, there would be + // 3k requests. + // We expect somewhere below 100 requests for these 8 unique hashes. But + // the number can be flaky, so don't limit it so strictly. + if threshold := 100; counter > threshold { + t.Logf("Error, expected < %d invocations, got %d", threshold, counter) + } + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncBoundaryStorageTrie tests sync against a few normal peers, but the +// storage trie has a few boundary elements. +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 10, 1000, false, true, false) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + return source + } + syncer := setupSyncer( + scheme, + mkSource("peer-a"), + mkSource("peer-b"), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncWithStorageAndOneCappedPeer tests sync using accounts + storage, where one peer is +// consistently returning very small results +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 300, 1000, false, false, false) + + mkSource := func(name string, slow bool) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + + if slow { + source.storageRequestHandler = starvingStorageRequestHandler + } + return source + } + + syncer := setupSyncer( + scheme, + mkSource("nice-a", false), + mkSource("slow", true), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncWithStorageAndCorruptPeer tests sync using accounts + storage, where one peer is +// sometimes sending bad proofs +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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false) + + mkSource := func(name string, handler storageHandlerFunc) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + source.storageRequestHandler = handler + return source + } + + syncer := setupSyncer( + scheme, + mkSource("nice-a", defaultStorageRequestHandler), + mkSource("nice-b", defaultStorageRequestHandler), + mkSource("nice-c", defaultStorageRequestHandler), + mkSource("corrupt", corruptStorageRequestHandler), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + 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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false) + + mkSource := func(name string, handler storageHandlerFunc) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + source.storageRequestHandler = handler + return source + } + syncer := setupSyncer( + scheme, + mkSource("nice-a", defaultStorageRequestHandler), + mkSource("nice-b", defaultStorageRequestHandler), + mkSource("nice-c", defaultStorageRequestHandler), + mkSource("corrupt", noProofStorageRequestHandler), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncWithStorageMisbehavingProve tests basic sync using accounts + storage + code, against +// a peer who insists on delivering full storage sets _and_ proofs. This triggered +// an error, where the recipient erroneously clipped the boundary nodes, but +// 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{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorageWithUniqueStorage(scheme, 10, 30, false) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + source.setStorageTries(storageTries) + source.storageValues = storageElems + source.storageRequestHandler = proofHappyStorageRequestHandler + return source + } + syncer := setupSyncer(nodeScheme, mkSource("sourceA")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + 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 { + k, v []byte +} + +func (k *kv) cmp(other *kv) int { + return bytes.Compare(k.k, other.k) +} + +func key32(i uint64) []byte { + key := make([]byte, 32) + binary.LittleEndian.PutUint64(key, i) + return key +} + +var ( + codehashes = []common.Hash{ + crypto.Keccak256Hash([]byte{0}), + crypto.Keccak256Hash([]byte{1}), + crypto.Keccak256Hash([]byte{2}), + crypto.Keccak256Hash([]byte{3}), + crypto.Keccak256Hash([]byte{4}), + crypto.Keccak256Hash([]byte{5}), + crypto.Keccak256Hash([]byte{6}), + crypto.Keccak256Hash([]byte{7}), + } +) + +// getCodeHash returns a pseudo-random code hash +func getCodeHash(i uint64) []byte { + h := codehashes[int(i)%len(codehashes)] + return common.CopyBytes(h[:]) +} + +// getCodeByHash convenience function to lookup the code from the code hash +func getCodeByHash(hash common.Hash) []byte { + if hash == types.EmptyCodeHash { + return nil + } + for i, h := range codehashes { + if h == hash { + return []byte{byte(i)} + } + } + return nil +} + +// makeAccountTrieNoStorage spits out a trie, along with the leaves +func makeAccountTrieNoStorage(n int, scheme string) (string, *trie.Trie, []*kv) { + var ( + db = triedb.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) + accTrie = trie.NewEmpty(db) + entries []*kv + ) + for i := uint64(1); i <= uint64(n); i++ { + value, _ := rlp.EncodeToBytes(&types.StateAccount{ + Nonce: i, + Balance: uint256.NewInt(i), + Root: types.EmptyRootHash, + CodeHash: getCodeHash(i), + }) + key := key32(i) + elem := &kv{key, value} + accTrie.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + } + slices.SortFunc(entries, (*kv).cmp) + + // Commit the state changes into db and re-create the trie + // for accessing later. + root, nodes := accTrie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) + + accTrie, _ = trie.New(trie.StateTrieID(root), db) + return db.Scheme(), accTrie, entries +} + +// makeBoundaryAccountTrie constructs an account trie. Instead of filling +// accounts normally, this function will fill a few accounts which have +// boundary hash. +func makeBoundaryAccountTrie(scheme string, n int) (string, *trie.Trie, []*kv) { + var ( + entries []*kv + boundaries []common.Hash + + db = triedb.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) + accTrie = trie.NewEmpty(db) + ) + // Initialize boundaries + var next common.Hash + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(int64(accountConcurrency)), + ), common.Big1, + ) + for i := 0; i < accountConcurrency; i++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if i == accountConcurrency-1 { + last = common.MaxHash + } + boundaries = append(boundaries, last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } + // Fill boundary accounts + for i := 0; i < len(boundaries); i++ { + value, _ := rlp.EncodeToBytes(&types.StateAccount{ + Nonce: uint64(0), + Balance: uint256.NewInt(uint64(i)), + Root: types.EmptyRootHash, + CodeHash: getCodeHash(uint64(i)), + }) + elem := &kv{boundaries[i].Bytes(), value} + accTrie.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + } + // Fill other accounts if required + for i := uint64(1); i <= uint64(n); i++ { + value, _ := rlp.EncodeToBytes(&types.StateAccount{ + Nonce: i, + Balance: uint256.NewInt(i), + Root: types.EmptyRootHash, + CodeHash: getCodeHash(i), + }) + elem := &kv{key32(i), value} + accTrie.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + } + slices.SortFunc(entries, (*kv).cmp) + + // Commit the state changes into db and re-create the trie + // for accessing later. + root, nodes := accTrie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) + + accTrie, _ = trie.New(trie.StateTrieID(root), db) + return db.Scheme(), accTrie, entries +} + +// makeAccountTrieWithStorageWithUniqueStorage creates an account trie where each accounts +// has a unique storage set. +func makeAccountTrieWithStorageWithUniqueStorage(scheme string, accounts, slots int, code bool) (string, *trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) { + var ( + db = triedb.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) + accTrie = trie.NewEmpty(db) + entries []*kv + storageRoots = make(map[common.Hash]common.Hash) + storageTries = make(map[common.Hash]*trie.Trie) + storageEntries = make(map[common.Hash][]*kv) + nodes = trienode.NewMergedNodeSet() + ) + // Create n accounts in the trie + for i := uint64(1); i <= uint64(accounts); i++ { + key := key32(i) + codehash := types.EmptyCodeHash.Bytes() + if code { + codehash = getCodeHash(i) + } + // Create a storage trie + stRoot, stNodes, stEntries := makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), i, db) + nodes.Merge(stNodes) + + value, _ := rlp.EncodeToBytes(&types.StateAccount{ + Nonce: i, + Balance: uint256.NewInt(i), + Root: stRoot, + CodeHash: codehash, + }) + elem := &kv{key, value} + accTrie.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + + storageRoots[common.BytesToHash(key)] = stRoot + storageEntries[common.BytesToHash(key)] = stEntries + } + slices.SortFunc(entries, (*kv).cmp) + + // Commit account trie + root, set := accTrie.Commit(true) + nodes.Merge(set) + + // Commit gathered dirty nodes into database + db.Update(root, types.EmptyRootHash, 0, nodes, nil) + + // Re-create tries with new root + accTrie, _ = trie.New(trie.StateTrieID(root), db) + for i := uint64(1); i <= uint64(accounts); i++ { + key := key32(i) + id := trie.StorageTrieID(root, common.BytesToHash(key), storageRoots[common.BytesToHash(key)]) + trie, _ := trie.New(id, db) + storageTries[common.BytesToHash(key)] = trie + } + return db.Scheme(), accTrie, entries, storageTries, storageEntries +} + +// makeAccountTrieWithStorage spits out a trie, along with the leaves +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 = triedb.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) + accTrie = trie.NewEmpty(db) + entries []*kv + storageRoots = make(map[common.Hash]common.Hash) + storageTries = make(map[common.Hash]*trie.Trie) + storageEntries = make(map[common.Hash][]*kv) + nodes = trienode.NewMergedNodeSet() + ) + // Create n accounts in the trie + for i := uint64(1); i <= uint64(accounts); i++ { + key := key32(i) + codehash := types.EmptyCodeHash.Bytes() + if code { + codehash = getCodeHash(i) + } + // Make a storage trie + var ( + stRoot common.Hash + stNodes *trienode.NodeSet + stEntries []*kv + ) + 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) + } + nodes.Merge(stNodes) + + value, _ := rlp.EncodeToBytes(&types.StateAccount{ + Nonce: i, + Balance: uint256.NewInt(i), + Root: stRoot, + CodeHash: codehash, + }) + elem := &kv{key, value} + accTrie.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + + // we reuse the same one for all accounts + storageRoots[common.BytesToHash(key)] = stRoot + storageEntries[common.BytesToHash(key)] = stEntries + } + slices.SortFunc(entries, (*kv).cmp) + + // Commit account trie + root, set := accTrie.Commit(true) + nodes.Merge(set) + + // Commit gathered dirty nodes into database + db.Update(root, types.EmptyRootHash, 0, nodes, nil) + + // Re-create tries with new root + accTrie, err := trie.New(trie.StateTrieID(root), db) + if err != nil { + panic(err) + } + for i := uint64(1); i <= uint64(accounts); i++ { + key := key32(i) + id := trie.StorageTrieID(root, common.BytesToHash(key), storageRoots[common.BytesToHash(key)]) + trie, err := trie.New(id, db) + if err != nil { + panic(err) + } + storageTries[common.BytesToHash(key)] = trie + } + return accTrie, entries, storageTries, storageEntries +} + +// makeStorageTrieWithSeed fills a storage trie with n items, returning the +// not-yet-committed trie and the sorted entries. The seeds can be used to ensure +// that tries are unique. +func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *triedb.Database) (common.Hash, *trienode.NodeSet, []*kv) { + trie, _ := trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db) + var entries []*kv + for i := uint64(1); i <= n; i++ { + // store 'x' at slot 'x' + slotValue := key32(i + seed) + rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:])) + + slotKey := key32(i) + key := crypto.Keccak256Hash(slotKey[:]) + + elem := &kv{key[:], rlpSlotValue} + trie.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + } + slices.SortFunc(entries, (*kv).cmp) + root, nodes := trie.Commit(false) + return root, nodes, entries +} + +// makeBoundaryStorageTrie constructs a storage trie. Instead of filling +// storage slots normally, this function will fill a few slots which have +// boundary hash. +func makeBoundaryStorageTrie(owner common.Hash, n int, db *triedb.Database) (common.Hash, *trienode.NodeSet, []*kv) { + var ( + entries []*kv + boundaries []common.Hash + trie, _ = trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db) + ) + // Initialize boundaries + var next common.Hash + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(int64(accountConcurrency)), + ), common.Big1, + ) + for i := 0; i < accountConcurrency; i++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if i == accountConcurrency-1 { + last = common.MaxHash + } + boundaries = append(boundaries, last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } + // Fill boundary slots + for i := 0; i < len(boundaries); i++ { + key := boundaries[i] + val := []byte{0xde, 0xad, 0xbe, 0xef} + + elem := &kv{key[:], val} + trie.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + } + // Fill other slots if required + for i := uint64(1); i <= uint64(n); i++ { + slotKey := key32(i) + key := crypto.Keccak256Hash(slotKey[:]) + + slotValue := key32(i) + rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:])) + + elem := &kv{key[:], rlpSlotValue} + trie.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + } + slices.SortFunc(entries, (*kv).cmp) + root, nodes := trie.Commit(false) + return root, nodes, entries +} + +// makeUnevenStorageTrie constructs a storage tries will states distributed in +// different range unevenly. +func makeUnevenStorageTrie(owner common.Hash, slots int, db *triedb.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)}, testrand.Bytes(31)...) + val, _ := rlp.EncodeToBytes(testrand.Bytes(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 := triedb.NewDatabase(rawdb.NewDatabase(db), newDbConfig(scheme)) + accTrie, err := trie.New(trie.StateTrieID(root), triedb) + if err != nil { + t.Fatal(err) + } + accounts, slots := 0, 0 + accIt := trie.NewIterator(accTrie.MustNodeIterator(nil)) + for accIt.Next() { + var acc struct { + Nonce uint64 + Balance *big.Int + Root common.Hash + CodeHash []byte + } + if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { + log.Crit("Invalid account encountered during snapshot creation", "err", err) + } + accounts++ + if acc.Root != types.EmptyRootHash { + id := trie.StorageTrieID(root, common.BytesToHash(accIt.Key), acc.Root) + storeTrie, err := trie.NewStateTrie(id, triedb) + if err != nil { + t.Fatal(err) + } + storeIt := trie.NewIterator(storeTrie.MustNodeIterator(nil)) + for storeIt.Next() { + slots++ + } + if err := storeIt.Err; err != nil { + t.Fatal(err) + } + } + } + if err := accIt.Err; err != nil { + t.Fatal(err) + } + t.Logf("accounts: %d, slots: %d", accounts, slots) +} + +// TestSyncAccountPerformance tests how efficient the snap algo is at minimizing +// state healing +func TestSyncAccountPerformance(t *testing.T) { + // These tests must not run in parallel: they modify the + // global var accountConcurrency + // 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) + accountConcurrency = 1 + + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie.Copy() + source.accountValues = elems + return source + } + src := mkSource("source") + syncer := setupSyncer(nodeScheme, src) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + 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, + // but only add extra IO for no reason in practice. + if have, want := src.nTrienodeRequests, 1; have != want { + fmt.Print(src.Stats()) + t.Errorf("trie node heal requests wrong, want %d, have %d", want, have) + } +} + +func TestSlotEstimation(t *testing.T) { + for i, tc := range []struct { + last common.Hash + count int + want uint64 + }{ + { + // Half the space + common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + 100, + 100, + }, + { + // 1 / 16th + common.HexToHash("0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + 100, + 1500, + }, + { + // Bit more than 1 / 16th + common.HexToHash("0x1000000000000000000000000000000000000000000000000000000000000000"), + 100, + 1499, + }, + { + // Almost everything + common.HexToHash("0xF000000000000000000000000000000000000000000000000000000000000000"), + 100, + 6, + }, + { + // Almost nothing -- should lead to error + common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), + 1, + 0, + }, + { + // Nothing -- should lead to error + common.Hash{}, + 100, + 0, + }, + } { + have, _ := estimateRemainingSlots(tc.count, tc.last) + if want := tc.want; have != want { + t.Errorf("test %d: have %d want %d", i, have, want) + } + } +} + +func newDbConfig(scheme string) *triedb.Config { + if scheme == rawdb.HashScheme { + return &triedb.Config{} + } + return &triedb.Config{PathDB: pathdb.Defaults} +} diff --git a/eth/protocols/snap/tracker.go b/eth/protocols/snap/tracker.go new file mode 100644 index 0000000..2cf59cc --- /dev/null +++ b/eth/protocols/snap/tracker.go @@ -0,0 +1,26 @@ +// 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 snap + +import ( + "time" + + "github.com/ethereum/go-ethereum/p2p/tracker" +) + +// requestTracker is a singleton tracker for request times. +var requestTracker = tracker.New(ProtocolName, time.Minute) diff --git a/eth/state_accessor.go b/eth/state_accessor.go new file mode 100644 index 0000000..372c76f --- /dev/null +++ b/eth/state_accessor.go @@ -0,0 +1,266 @@ +// 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 eth + +import ( + "context" + "errors" + "fmt" + "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/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +// noopReleaser is returned in case there is no operation expected +// for releasing state. +var noopReleaser = tracers.StateReleaseFunc(func() {}) + +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 + tdb *triedb.Database + report = true + origin = block.NumberU64() + ) + // The state is only for reading purposes, check the state presence in + // live database. + if readOnly { + // The state is available in live database, create a reference + // on top to prevent garbage collection and return a release + // function to deref it. + if statedb, err = eth.blockchain.StateAt(block.Root()); err == nil { + eth.blockchain.TrieDB().Reference(block.Root(), common.Hash{}) + return statedb, func() { + eth.blockchain.TrieDB().Dereference(block.Root()) + }, nil + } + } + // The state is both for reading and writing, or it's unavailable in disk, + // try to construct/recover the state over an ephemeral trie.Database for + // isolating the live one. + if base != nil { + 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. + // TODO(rjl493456442), clean cache is disabled to prevent memory leak, + // please re-enable it for better performance. + database = state.NewDatabaseWithConfig(eth.chainDb, triedb.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, tdb, 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 + current = block + + // Create an ephemeral trie.Database for isolating the live one. Otherwise + // the internal junks created by tracing will be persisted into the disk. + // TODO(rjl493456442), clean cache is disabled to prevent memory leak, + // please re-enable it for better performance. + tdb = triedb.NewDatabase(eth.chainDb, triedb.HashDefaults) + database = state.NewDatabaseWithNodeDB(eth.chainDb, tdb) + + // 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 + // chain tracing from the genesis). + if !readOnly { + statedb, err = state.New(current.Root(), database, nil) + if err == nil { + return statedb, noopReleaser, nil + } + } + // Database does not have the state for the given block, try to regenerate + for i := uint64(0); i < reexec; i++ { + if err := ctx.Err(); err != nil { + return nil, nil, err + } + if current.NumberU64() == 0 { + return nil, nil, errors.New("genesis state is missing") + } + parent := eth.blockchain.GetBlock(current.ParentHash(), current.NumberU64()-1) + if parent == nil { + return nil, nil, fmt.Errorf("missing block %v %d", current.ParentHash(), current.NumberU64()-1) + } + current = parent + + statedb, err = state.New(current.Root(), database, nil) + if err == nil { + break + } + } + if err != nil { + switch err.(type) { + case *trie.MissingNodeError: + return nil, nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) + default: + return nil, nil, err + } + } + } + // State is available at historical point, re-execute the blocks on top for + // the desired state. + var ( + start = time.Now() + logged time.Time + parent common.Hash + ) + for current.NumberU64() < origin { + if err := ctx.Err(); err != nil { + return nil, nil, err + } + // Print progress logs if long enough time elapsed + if time.Since(logged) > 8*time.Second && report { + log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64()-1, "elapsed", time.Since(start)) + logged = time.Now() + } + // Retrieve the next block to regenerate and process it + next := current.NumberU64() + 1 + if current = eth.blockchain.GetBlockByNumber(next); current == nil { + return nil, nil, fmt.Errorf("block #%d not found", next) + } + _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}) + if err != nil { + return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) + } + // Finalize the state so any modifications are written to the trie + root, err := statedb.Commit(current.NumberU64(), eth.blockchain.Config().IsEIP158(current.Number())) + if err != nil { + return nil, nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w", + current.NumberU64(), current.Root().Hex(), err) + } + statedb, err = state.New(root, database, nil) + if err != nil { + return nil, nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err) + } + // Hold the state reference and also drop the parent state + // to prevent accumulating too many nodes in memory. + tdb.Reference(root, common.Hash{}) + if parent != (common.Hash{}) { + tdb.Dereference(parent) + } + parent = root + } + if report { + _, nodes, imgs := tdb.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() { tdb.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. +func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, 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 := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + } + // 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) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + // Insert parent beacon block root in the state as per EIP-4788. + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{}) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, release, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(eth.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(), eth.blockchain, nil) + if idx == txIndex { + return tx, context, statedb, release, nil + } + // Not yet the searched for transaction, execute on top of the current state + vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) + statedb.SetTxContext(tx.Hash(), idx) + 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/eth/sync.go b/eth/sync.go new file mode 100644 index 0000000..61f2b2b --- /dev/null +++ b/eth/sync.go @@ -0,0 +1,37 @@ +// 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 eth + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/eth/protocols/eth" +) + +// syncTransactions starts sending all currently pending transactions to the given peer. +func (h *handler) syncTransactions(p *eth.Peer) { + var hashes []common.Hash + for _, batch := range h.txpool.Pending(txpool.PendingFilter{OnlyPlainTxs: true}) { + for _, tx := range batch { + hashes = append(hashes, tx.Hash) + } + } + if len(hashes) == 0 { + return + } + p.AsyncSendPooledTransactionHashes(hashes) +} diff --git a/eth/sync_test.go b/eth/sync_test.go new file mode 100644 index 0000000..7ede0a8 --- /dev/null +++ b/eth/sync_test.go @@ -0,0 +1,96 @@ +// 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 eth + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/eth/downloader" + "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/enode" +) + +// Tests that snap sync is disabled after a successful sync cycle. +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. +func testSnapSyncDisabling(t *testing.T, ethVer uint, snapVer uint) { + t.Parallel() + + // Create an empty handler and ensure it's in snap sync mode + empty := newTestHandler() + if !empty.handler.snapSync.Load() { + t.Fatalf("snap sync disabled on pristine blockchain") + } + defer empty.close() + + // Create a full handler and ensure snap sync ends up disabled + full := newTestHandlerWithBlocks(1024) + if full.handler.snapSync.Load() { + t.Fatalf("snap sync not disabled on non-empty blockchain") + } + defer full.close() + + // Sync up the two handlers via both `eth` and `snap` + caps := []p2p.Cap{{Name: "eth", Version: ethVer}, {Name: "snap", Version: snapVer}} + + emptyPipeEth, fullPipeEth := p2p.MsgPipe() + defer emptyPipeEth.Close() + defer fullPipeEth.Close() + + emptyPeerEth := eth.NewPeer(ethVer, p2p.NewPeer(enode.ID{1}, "", caps), emptyPipeEth, empty.txpool) + fullPeerEth := eth.NewPeer(ethVer, p2p.NewPeer(enode.ID{2}, "", caps), fullPipeEth, full.txpool) + defer emptyPeerEth.Close() + defer fullPeerEth.Close() + + go empty.handler.runEthPeer(emptyPeerEth, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(empty.handler), peer) + }) + go full.handler.runEthPeer(fullPeerEth, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(full.handler), peer) + }) + + emptyPipeSnap, fullPipeSnap := p2p.MsgPipe() + defer emptyPipeSnap.Close() + defer fullPipeSnap.Close() + + emptyPeerSnap := snap.NewPeer(snapVer, p2p.NewPeer(enode.ID{1}, "", caps), emptyPipeSnap) + fullPeerSnap := snap.NewPeer(snapVer, p2p.NewPeer(enode.ID{2}, "", caps), fullPipeSnap) + + go empty.handler.runSnapExtension(emptyPeerSnap, func(peer *snap.Peer) error { + return snap.Handle((*snapHandler)(empty.handler), peer) + }) + go full.handler.runSnapExtension(fullPeerSnap, func(peer *snap.Peer) error { + return snap.Handle((*snapHandler)(full.handler), peer) + }) + // Wait a bit for the above handlers to start + time.Sleep(250 * time.Millisecond) + + // Check that snap sync was disabled + if err := empty.handler.downloader.BeaconSync(downloader.SnapSync, full.chain.CurrentBlock(), nil); err != nil { + t.Fatal("sync failed:", err) + } + empty.handler.enableSyncedFeatures() + + if empty.handler.snapSync.Load() { + t.Fatalf("snap sync not disabled after successful synchronisation") + } +} diff --git a/eth/tracers/api.go b/eth/tracers/api.go new file mode 100644 index 0000000..51b55ff --- /dev/null +++ b/eth/tracers/api.go @@ -0,0 +1,1076 @@ +// 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 tracers + +import ( + "bufio" + "context" + "encoding/json" + "errors" + "fmt" + "os" + "runtime" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "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/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" +) + +const ( + // defaultTraceTimeout is the amount of time a single transaction can execute + // by default before being forcefully aborted. + defaultTraceTimeout = 5 * time.Second + + // defaultTraceReexec is the number of blocks the tracer is willing to go back + // and reexecute to produce missing historical state necessary to run a specific + // trace. + defaultTraceReexec = uint64(128) + + // defaultTracechainMemLimit is the size of the triedb, at which traceChain + // switches over and tries to use a disk-backed database instead of building + // on top of memory. + // For non-archive nodes, this limit _will_ be overblown, as disk-backed tries + // will only be found every ~15K blocks or so. + defaultTracechainMemLimit = common.StorageSize(500 * 1024 * 1024) + + // maximumPendingTraceStates is the maximum number of states allowed waiting + // for tracing. The creation of trace state will be paused if the unused + // trace states exceed this limit. + maximumPendingTraceStates = 128 +) + +var errTxNotFound = errors.New("transaction not found") + +// StateReleaseFunc is used to deallocate resources held by constructing a +// historical state for tracing purposes. +type StateReleaseFunc func() + +// Backend interface provides the common API services (that are provided by +// both full and light clients) with access to necessary functions. +type Backend interface { + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) + RPCGasCap() uint64 + ChainConfig() *params.ChainConfig + Engine() consensus.Engine + ChainDb() ethdb.Database + StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error) + StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, StateReleaseFunc, error) +} + +// API is the collection of tracing APIs exposed over the private debugging endpoint. +type API struct { + backend Backend +} + +// NewAPI creates a new API definition for the tracing methods of the Ethereum service. +func NewAPI(backend Backend) *API { + return &API{backend: backend} +} + +// chainContext constructs the context reader which is used by the evm for reading +// the necessary chain context. +func (api *API) chainContext(ctx context.Context) core.ChainContext { + return ethapi.NewChainContext(ctx, api.backend) +} + +// blockByNumber is the wrapper of the chain access function offered by the backend. +// It will return an error if the block is not found. +func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + block, err := api.backend.BlockByNumber(ctx, number) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block #%d not found", number) + } + return block, nil +} + +// blockByHash is the wrapper of the chain access function offered by the backend. +// It will return an error if the block is not found. +func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + block, err := api.backend.BlockByHash(ctx, hash) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block %s not found", hash.Hex()) + } + return block, nil +} + +// blockByNumberAndHash is the wrapper of the chain access function offered by +// the backend. It will return an error if the block is not found. +// +// Note this function is friendly for the light client which can only retrieve the +// historical(before the CHT) header/block by number. +func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) { + block, err := api.blockByNumber(ctx, number) + if err != nil { + return nil, err + } + if block.Hash() == hash { + return block, nil + } + return api.blockByHash(ctx, hash) +} + +// TraceConfig holds extra parameters to trace functions. +type TraceConfig struct { + *logger.Config + Tracer *string + Timeout *string + Reexec *uint64 + // Config specific to given tracer. Note struct logger + // config are historically embedded in main object. + TracerConfig json.RawMessage +} + +// TraceCallConfig is the config for traceCall API. It holds one more +// field to override the state for tracing. +type TraceCallConfig struct { + TraceConfig + StateOverrides *ethapi.StateOverride + BlockOverrides *ethapi.BlockOverrides + TxIndex *hexutil.Uint +} + +// StdTraceConfig holds extra parameters to standard-json trace functions. +type StdTraceConfig struct { + logger.Config + Reexec *uint64 + TxHash common.Hash +} + +// txTraceResult is the result of a single transaction trace. +type txTraceResult struct { + TxHash common.Hash `json:"txHash"` // transaction hash + Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer + Error string `json:"error,omitempty"` // Trace failure produced by the tracer +} + +// blockTraceTask represents a single block trace task when an entire chain is +// being traced. +type blockTraceTask struct { + statedb *state.StateDB // Intermediate state prepped for tracing + block *types.Block // Block to trace the transactions from + release StateReleaseFunc // The function to release the held resource for this task + results []*txTraceResult // Trace results produced by the task +} + +// blockTraceResult represents the results of tracing a single block when an entire +// chain is being traced. +type blockTraceResult struct { + Block hexutil.Uint64 `json:"block"` // Block number corresponding to this trace + Hash common.Hash `json:"hash"` // Block hash corresponding to this trace + Traces []*txTraceResult `json:"traces"` // Trace results produced by the task +} + +// txTraceTask represents a single transaction trace task when an entire block +// is being traced. +type txTraceTask struct { + statedb *state.StateDB // Intermediate state prepped for tracing + index int // Transaction offset in the block +} + +// TraceChain returns the structured logs created during the execution of EVM +// between two blocks (excluding start) and returns them as a JSON object. +func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { // Fetch the block interval that we want to trace + from, err := api.blockByNumber(ctx, start) + if err != nil { + return nil, err + } + to, err := api.blockByNumber(ctx, end) + if err != nil { + return nil, err + } + if from.Number().Cmp(to.Number()) >= 0 { + return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start) + } + // Tracing a chain is a **long** operation, only do with subscriptions + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + sub := notifier.CreateSubscription() + + resCh := api.traceChain(from, to, config, sub.Err()) + go func() { + for result := range resCh { + notifier.Notify(sub.ID, result) + } + }() + return sub, nil +} + +// traceChain configures a new tracer according to the provided configuration, and +// executes all the transactions contained within. The tracing chain range includes +// the end block but excludes the start one. The return value will be one item per +// transaction, dependent on the requested tracer. +// The tracing procedure should be aborted in case the closed signal is received. +func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed <-chan error) chan *blockTraceResult { + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + blocks := int(end.NumberU64() - start.NumberU64()) + threads := runtime.NumCPU() + if threads > blocks { + threads = blocks + } + var ( + pend = new(sync.WaitGroup) + ctx = context.Background() + taskCh = make(chan *blockTraceTask, threads) + resCh = make(chan *blockTraceTask, threads) + tracker = newStateTracker(maximumPendingTraceStates, start.NumberU64()) + ) + for th := 0; th < threads; th++ { + pend.Add(1) + go func() { + defer pend.Done() + + // Fetch and execute the block trace taskCh + for task := range taskCh { + var ( + signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time()) + blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil) + ) + // Trace all the transactions contained within + for i, tx := range task.block.Transactions() { + msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee()) + txctx := &Context{ + BlockHash: task.block.Hash(), + BlockNumber: task.block.Number(), + TxIndex: i, + TxHash: tx.Hash(), + } + res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, task.statedb, config) + if err != nil { + task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} + log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) + break + } + task.results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} + } + // Tracing state is used up, queue it for de-referencing. Note the + // state is the parent state of trace block, use block.number-1 as + // the state number. + tracker.releaseState(task.block.NumberU64()-1, task.release) + + // Stream the result back to the result catcher or abort on teardown + select { + case resCh <- task: + case <-closed: + return + } + } + }() + } + // Start a goroutine to feed all the blocks into the tracers + go func() { + var ( + logged time.Time + begin = time.Now() + number uint64 + traced uint64 + failed error + statedb *state.StateDB + release StateReleaseFunc + ) + // Ensure everything is properly cleaned up on any exit path + defer func() { + close(taskCh) + pend.Wait() + + // Clean out any pending release functions of trace states. + tracker.callReleases() + + // Log the chain result + switch { + case failed != nil: + log.Warn("Chain tracing failed", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin), "err", failed) + case number < end.NumberU64(): + log.Warn("Chain tracing aborted", "start", start.NumberU64(), "end", end.NumberU64(), "abort", number, "transactions", traced, "elapsed", time.Since(begin)) + default: + log.Info("Chain tracing finished", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin)) + } + close(resCh) + }() + // Feed all the blocks both into the tracer, as well as fast process concurrently + for number = start.NumberU64(); number < end.NumberU64(); number++ { + // Stop tracing if interruption was requested + select { + case <-closed: + return + default: + } + // Print progress logs if long enough time elapsed + if time.Since(logged) > 8*time.Second { + logged = time.Now() + log.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin)) + } + // Retrieve the parent block and target block for tracing. + block, err := api.blockByNumber(ctx, rpc.BlockNumber(number)) + if err != nil { + failed = err + break + } + next, err := api.blockByNumber(ctx, rpc.BlockNumber(number+1)) + if err != nil { + failed = err + break + } + // Make sure the state creator doesn't go too far. Too many unprocessed + // trace state may cause the oldest state to become stale(e.g. in + // path-based scheme). + if err = tracker.wait(number); err != nil { + failed = err + break + } + // Prepare the statedb for tracing. Don't use the live database for + // tracing to avoid persisting state junks into the database. Switch + // over to `preferDisk` mode only if the memory usage exceeds the + // limit, the trie database will be reconstructed from scratch only + // if the relevant state is available in disk. + var preferDisk bool + if statedb != nil { + 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 { + failed = err + break + } + // Insert block's parent beacon block root in the state + // as per EIP-4788. + if beaconRoot := next.BeaconRoot(); beaconRoot != nil { + context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } + // Clean out any pending release functions of trace state. Note this + // step must be done after constructing tracing state, because the + // tracing state of block next depends on the parent state and construction + // may fail if we release too early. + tracker.callReleases() + + // Send the block over to the concurrent tracers (if not in the fast-forward phase) + txs := next.Transactions() + select { + case taskCh <- &blockTraceTask{statedb: statedb.Copy(), block: next, release: release, results: make([]*txTraceResult, len(txs))}: + case <-closed: + tracker.releaseState(number, release) + return + } + traced += uint64(len(txs)) + } + }() + + // Keep reading the trace results and stream them to result channel. + retCh := make(chan *blockTraceResult) + go func() { + defer close(retCh) + var ( + next = start.NumberU64() + 1 + done = make(map[uint64]*blockTraceResult) + ) + for res := range resCh { + // Queue up next received result + result := &blockTraceResult{ + Block: hexutil.Uint64(res.block.NumberU64()), + Hash: res.block.Hash(), + Traces: res.results, + } + done[uint64(result.Block)] = result + + // Stream completed traces to the result channel + for result, ok := done[next]; ok; result, ok = done[next] { + if len(result.Traces) > 0 || next == end.NumberU64() { + // It will be blocked in case the channel consumer doesn't take the + // tracing result in time(e.g. the websocket connect is not stable) + // which will eventually block the entire chain tracer. It's the + // expected behavior to not waste node resources for a non-active user. + retCh <- result + } + delete(done, next) + next++ + } + } + }() + return retCh +} + +// TraceBlockByNumber returns the structured logs created during the execution of +// EVM and returns them as a JSON object. +func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { + block, err := api.blockByNumber(ctx, number) + if err != nil { + return nil, err + } + return api.traceBlock(ctx, block, config) +} + +// TraceBlockByHash returns the structured logs created during the execution of +// EVM and returns them as a JSON object. +func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { + block, err := api.blockByHash(ctx, hash) + if err != nil { + return nil, err + } + return api.traceBlock(ctx, block, config) +} + +// TraceBlock returns the structured logs created during the execution of EVM +// 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.DecodeBytes(blob, block); err != nil { + return nil, fmt.Errorf("could not decode block: %v", err) + } + return api.traceBlock(ctx, block, config) +} + +// TraceBlockFromFile returns the structured logs created during the execution of +// EVM and returns them as a JSON object. +func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { + blob, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("could not read file: %v", err) + } + return api.TraceBlock(ctx, blob, config) +} + +// TraceBadBlock returns the structured logs created during the execution of +// EVM against a block pulled from the pool of bad ones and returns them as a JSON +// object. +func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { + block := rawdb.ReadBadBlock(api.backend.ChainDb(), hash) + if block == nil { + return nil, fmt.Errorf("bad block %#x not found", hash) + } + return api.traceBlock(ctx, block, config) +} + +// StandardTraceBlockToFile dumps the structured logs created during the +// execution of EVM to the local file system and returns a list of files +// to the caller. +func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { + block, err := api.blockByHash(ctx, hash) + if err != nil { + return nil, err + } + return api.standardTraceBlockToFile(ctx, block, config) +} + +// IntermediateRoots executes a block (bad- or canon- or side-), and returns a list +// of intermediate roots: the stateroot after each transaction. +func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) { + block, _ := api.blockByHash(ctx, hash) + if block == nil { + // Check in the bad blocks + block = rawdb.ReadBadBlock(api.backend.ChainDb(), hash) + } + if block == nil { + return nil, fmt.Errorf("block %#x not found", hash) + } + if block.NumberU64() == 0 { + return nil, errors.New("genesis is not traceable") + } + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) + if err != nil { + return nil, err + } + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) + if err != nil { + return nil, err + } + defer release() + var ( + roots []common.Hash + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) + chainConfig = api.backend.ChainConfig() + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) + ) + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } + for i, tx := range block.Transactions() { + if err := ctx.Err(); err != nil { + return nil, err + } + var ( + msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee()) + txContext = core.NewEVMTxContext(msg) + vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) + ) + statedb.SetTxContext(tx.Hash(), i) + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil { + log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) + // We intentionally don't return the error here: if we do, then the RPC server will not + // return the roots. Most likely, the caller already knows that a certain transaction fails to + // be included, but still want the intermediate roots that led to that point. + // It may happen the tx_N causes an erroneous state, which in turn causes tx_N+M to not be + // executable. + // N.B: This should never happen while tracing canon blocks, only when tracing bad blocks. + return roots, nil + } + // calling IntermediateRoot will internally call Finalize on the state + // so any modifications are written to the trie + roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects)) + } + return roots, nil +} + +// StandardTraceBadBlockToFile dumps the structured logs created during the +// execution of EVM against a block pulled from the pool of bad ones to the +// local file system and returns a list of files to the caller. +func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { + block := rawdb.ReadBadBlock(api.backend.ChainDb(), hash) + if block == nil { + return nil, fmt.Errorf("bad block %#x not found", hash) + } + return api.standardTraceBlockToFile(ctx, block, config) +} + +// traceBlock configures a new tracer according to the provided configuration, and +// executes all the transactions contained within. The return value will be one item +// per transaction, dependent on the requested tracer. +func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { + if block.NumberU64() == 0 { + return nil, errors.New("genesis is not traceable") + } + // Prepare base state + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) + if err != nil { + return nil, err + } + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) + if err != nil { + return nil, err + } + defer release() + // JS tracers have high overhead. In this case run a parallel + // process that generates states in one thread and traces txes + // in separate worker threads. + if config != nil && config.Tracer != nil && *config.Tracer != "" { + if isJS := DefaultDirectory.IsJS(*config.Tracer); isJS { + return api.traceBlockParallel(ctx, block, statedb, config) + } + } + // Native tracers have low overhead + var ( + txs = block.Transactions() + blockHash = block.Hash() + blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) + results = make([]*txTraceResult, len(txs)) + ) + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + vmenv := vm.NewEVM(blockCtx, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } + for i, tx := range txs { + // Generate the next state snapshot fast without tracing + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) + txctx := &Context{ + BlockHash: blockHash, + BlockNumber: block.Number(), + TxIndex: i, + TxHash: tx.Hash(), + } + res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config) + if err != nil { + return nil, err + } + results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} + } + return results, nil +} + +// traceBlockParallel is for tracers that have a high overhead (read JS tracers). One thread +// runs along and executes txes without tracing enabled to generate their prestate. +// Worker threads take the tasks and the prestate and trace them. +func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, statedb *state.StateDB, config *TraceConfig) ([]*txTraceResult, error) { + // Execute all the transaction contained within the block concurrently + var ( + txs = block.Transactions() + blockHash = block.Hash() + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) + results = make([]*txTraceResult, len(txs)) + pend sync.WaitGroup + ) + threads := runtime.NumCPU() + if threads > len(txs) { + threads = len(txs) + } + jobs := make(chan *txTraceTask, threads) + for th := 0; th < threads; th++ { + pend.Add(1) + go func() { + defer pend.Done() + // Fetch and execute the next transaction trace tasks + for task := range jobs { + msg, _ := core.TransactionToMessage(txs[task.index], signer, block.BaseFee()) + txctx := &Context{ + BlockHash: blockHash, + BlockNumber: block.Number(), + TxIndex: task.index, + TxHash: txs[task.index].Hash(), + } + // Reconstruct the block context for each transaction + // as the GetHash function of BlockContext is not safe for + // concurrent use. + // See: https://github.com/ethereum/go-ethereum/issues/29114 + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config) + if err != nil { + results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} + continue + } + results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Result: res} + } + }() + } + + // Feed the transactions into the tracers and return + var failed error + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) +txloop: + for i, tx := range txs { + // Send the trace task over for execution + task := &txTraceTask{statedb: statedb.Copy(), index: i} + select { + case <-ctx.Done(): + failed = ctx.Err() + break txloop + case jobs <- task: + } + + // Generate the next state snapshot fast without tracing + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) + statedb.SetTxContext(tx.Hash(), i) + vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil { + failed = err + break txloop + } + // Finalize the state so any modifications are written to the trie + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + + close(jobs) + pend.Wait() + + // If execution failed in between, abort + if failed != nil { + return nil, failed + } + return results, nil +} + +// standardTraceBlockToFile configures a new tracer which uses standard JSON output, +// and traces either a full block or an individual transaction. The return value will +// be one filename per transaction traced. +func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { + // If we're tracing a single transaction, make sure it's present + if config != nil && config.TxHash != (common.Hash{}) { + if !containsTx(block, config.TxHash) { + return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) + } + } + if block.NumberU64() == 0 { + return nil, errors.New("genesis is not traceable") + } + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) + if err != nil { + return nil, err + } + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) + if err != nil { + return nil, err + } + defer release() + // Retrieve the tracing configurations, or use default values + var ( + logConfig logger.Config + txHash common.Hash + ) + if config != nil { + logConfig = config.Config + txHash = config.TxHash + } + logConfig.Debug = true + + // Execute transaction, either tracing all or just the requested one + var ( + dumps []string + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) + chainConfig = api.backend.ChainConfig() + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + canon = true + ) + // Check if there are any overrides: the caller may wish to enable a future + // fork when executing this block. Note, such overrides are only applicable to the + // actual specified block, not any preceding blocks that we have to go through + // in order to obtain the state. + // Therefore, it's perfectly valid to specify `"futureForkBlock": 0`, to enable `futureFork` + if config != nil && config.Overrides != nil { + // Note: This copies the config, to not screw up the main config + chainConfig, canon = overrideConfig(chainConfig, config.Overrides) + } + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + vmenv := vm.NewEVM(vmctx, vm.TxContext{}, statedb, chainConfig, vm.Config{}) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } + for i, tx := range block.Transactions() { + // Prepare the transaction for un-traced execution + var ( + msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee()) + txContext = core.NewEVMTxContext(msg) + vmConf vm.Config + dump *os.File + writer *bufio.Writer + err error + ) + // If the transaction needs tracing, swap out the configs + if tx.Hash() == txHash || txHash == (common.Hash{}) { + // Generate a unique temporary file to dump it into + prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) + if !canon { + prefix = fmt.Sprintf("%valt-", prefix) + } + dump, err = os.CreateTemp(os.TempDir(), prefix) + if err != nil { + return nil, err + } + dumps = append(dumps, dump.Name()) + + // Swap out the noop logger to the standard tracer + writer = bufio.NewWriter(dump) + vmConf = vm.Config{ + Tracer: logger.NewJSONLogger(&logConfig, writer), + EnablePreimageRecording: true, + } + } + // Execute the transaction and flush any traces to disk + vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) + statedb.SetTxContext(tx.Hash(), i) + if vmConf.Tracer.OnTxStart != nil { + vmConf.Tracer.OnTxStart(vmenv.GetVMContext(), tx, msg.From) + } + vmRet, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) + if vmConf.Tracer.OnTxEnd != nil { + vmConf.Tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err) + } + if writer != nil { + writer.Flush() + } + if dump != nil { + dump.Close() + log.Info("Wrote standard trace", "file", dump.Name()) + } + if err != nil { + return dumps, err + } + // Finalize the state so any modifications are written to the trie + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + + // If we've traced the transaction we were looking for, abort + if tx.Hash() == txHash { + break + } + } + return dumps, nil +} + +// containsTx reports whether the transaction with a certain hash +// is contained within the specified block. +func containsTx(block *types.Block, hash common.Hash) bool { + for _, tx := range block.Transactions() { + if tx.Hash() == hash { + return true + } + } + return false +} + +// TraceTransaction returns the structured logs created during the execution of EVM +// and returns them as a JSON object. +func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { + found, _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) + if err != nil { + return nil, ethapi.NewTxIndexingError() + } + // Only mined txes are supported + if !found { + return nil, errTxNotFound + } + // It shouldn't happen in practice. + if blockNumber == 0 { + return nil, errors.New("genesis is not traceable") + } + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash) + if err != nil { + return nil, err + } + tx, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) + if err != nil { + return nil, err + } + defer release() + msg, err := core.TransactionToMessage(tx, types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()), block.BaseFee()) + if err != nil { + return nil, err + } + + txctx := &Context{ + BlockHash: blockHash, + BlockNumber: block.Number(), + TxIndex: int(index), + TxHash: hash, + } + return api.traceTx(ctx, tx, msg, txctx, vmctx, statedb, 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 + statedb *state.StateDB + release StateReleaseFunc + ) + if hash, ok := blockNrOrHash.Hash(); ok { + block, err = api.blockByHash(ctx, hash) + } else if number, ok := blockNrOrHash.Number(); ok { + if number == rpc.PendingBlockNumber { + // We don't have access to the miner here. For tracing 'future' transactions, + // it can be done with block- and state-overrides instead, which offers + // more flexibility and stability than trying to trace on 'pending', since + // the contents of 'pending' is unstable and probably not a true representation + // of what the next actual block is likely to contain. + return nil, errors.New("tracing on top of pending is not supported") + } + block, err = api.blockByNumber(ctx, number) + } else { + return nil, errors.New("invalid arguments; neither block nor hash specified") + } + if err != nil { + return nil, err + } + // try to recompute the state + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + + 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 + } + defer release() + + vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + // Apply the customization rules if required. + if config != nil { + if err := config.StateOverrides.Apply(statedb); err != nil { + return nil, err + } + config.BlockOverrides.Apply(&vmctx) + } + // Execute the trace + if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil { + return nil, err + } + var ( + msg = args.ToMessage(vmctx.BaseFee) + tx = args.ToTransaction() + traceConfig *TraceConfig + ) + if config != nil { + traceConfig = &config.TraceConfig + } + return api.traceTx(ctx, tx, msg, new(Context), vmctx, statedb, traceConfig) +} + +// traceTx configures a new tracer according to the provided configuration, and +// executes the given message in the provided environment. The return value will +// be tracer dependent. +func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { + var ( + tracer *Tracer + err error + timeout = defaultTraceTimeout + usedGas uint64 + ) + if config == nil { + config = &TraceConfig{} + } + // Default tracer is the struct logger + if config.Tracer == nil { + logger := logger.NewStructLogger(config.Config) + tracer = &Tracer{ + Hooks: logger.Hooks(), + GetResult: logger.GetResult, + Stop: logger.Stop, + } + } else { + tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) + if err != nil { + return nil, err + } + } + // The actual TxContext will be created as part of ApplyTransactionWithEVM. + vmenv := vm.NewEVM(vmctx, vm.TxContext{GasPrice: message.GasPrice, BlobFeeCap: message.BlobGasFeeCap}, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true}) + statedb.SetLogger(tracer.Hooks) + + // Define a meaningful timeout of a single transaction trace + if config.Timeout != nil { + if timeout, err = time.ParseDuration(*config.Timeout); err != nil { + return nil, err + } + } + deadlineCtx, cancel := context.WithTimeout(ctx, timeout) + go func() { + <-deadlineCtx.Done() + if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) { + tracer.Stop(errors.New("execution timeout")) + // Stop evm execution. Note cancellation is not necessarily immediate. + vmenv.Cancel() + } + }() + defer cancel() + + // Call Prepare to clear out the statedb access list + statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) + _, err = core.ApplyTransactionWithEVM(message, api.backend.ChainConfig(), new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, tx, &usedGas, vmenv) + if err != nil { + return nil, fmt.Errorf("tracing failed: %w", err) + } + return tracer.GetResult() +} + +// APIs return the collection of RPC services the tracer package offers. +func APIs(backend Backend) []rpc.API { + // Append all the local APIs and return + return []rpc.API{ + { + Namespace: "debug", + Service: NewAPI(backend), + }, + } +} + +// overrideConfig returns a copy of original with forks enabled by override enabled, +// along with a boolean that indicates whether the copy is canonical (equivalent to the original). +// Note: the Clique-part is _not_ deep copied +func overrideConfig(original *params.ChainConfig, override *params.ChainConfig) (*params.ChainConfig, bool) { + copy := new(params.ChainConfig) + *copy = *original + canon := true + + // Apply forks (after Berlin) to the copy. + if block := override.BerlinBlock; block != nil { + copy.BerlinBlock = block + canon = false + } + if block := override.LondonBlock; block != nil { + copy.LondonBlock = block + canon = false + } + if block := override.ArrowGlacierBlock; block != nil { + copy.ArrowGlacierBlock = block + canon = false + } + if block := override.GrayGlacierBlock; block != nil { + copy.GrayGlacierBlock = block + canon = false + } + if block := override.MergeNetsplitBlock; block != nil { + copy.MergeNetsplitBlock = block + canon = false + } + if timestamp := override.ShanghaiTime; timestamp != nil { + copy.ShanghaiTime = timestamp + canon = false + } + if timestamp := override.CancunTime; timestamp != nil { + copy.CancunTime = timestamp + canon = false + } + if timestamp := override.PragueTime; timestamp != nil { + copy.PragueTime = timestamp + canon = false + } + if timestamp := override.VerkleTime; timestamp != nil { + copy.VerkleTime = timestamp + canon = false + } + + return copy, canon +} diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go new file mode 100644 index 0000000..6fbb508 --- /dev/null +++ b/eth/tracers/api_test.go @@ -0,0 +1,1084 @@ +// 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 tracers + +import ( + "context" + "crypto/ecdsa" + "encoding/json" + "errors" + "fmt" + "math/big" + "reflect" + "slices" + "sync/atomic" + "testing" + "time" + + "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/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/eth/tracers/logger" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + errStateNotFound = errors.New("state not found") + errBlockNotFound = errors.New("block not found") +) + +type testBackend struct { + chainConfig *params.ChainConfig + engine consensus.Engine + chaindb ethdb.Database + chain *core.BlockChain + + refHook func() // Hook is invoked when the requested state is referenced + relHook func() // Hook is invoked when the requested state is released +} + +// newTestBackend creates a new test backend. OBS: After test is done, teardown must be +// invoked in order to release associated resources. +func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + backend := &testBackend{ + chainConfig: gspec.Config, + engine: ethash.NewFaker(), + chaindb: rawdb.NewMemoryDatabase(), + } + // Generate blocks for testing + _, blocks, _ := core.GenerateChainWithGenesis(gspec, backend.engine, n, generator) + + // Import the canonical chain + cacheConfig := &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + TrieDirtyDisabled: true, // Archive mode + } + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, nil, backend.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + backend.chain = chain + return backend +} + +func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + return b.chain.GetHeaderByHash(hash), nil +} + +func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { + return b.chain.CurrentHeader(), nil + } + return b.chain.GetHeaderByNumber(uint64(number)), nil +} + +func (b *testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return b.chain.GetBlockByHash(hash), nil +} + +func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { + return b.chain.GetBlockByNumber(b.chain.CurrentBlock().Number.Uint64()), nil + } + return b.chain.GetBlockByNumber(uint64(number)), nil +} + +func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) { + tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) + return tx != nil, tx, hash, blockNumber, index, nil +} + +func (b *testBackend) RPCGasCap() uint64 { + return 25000000 +} + +func (b *testBackend) ChainConfig() *params.ChainConfig { + return b.chainConfig +} + +func (b *testBackend) Engine() consensus.Engine { + return b.engine +} + +func (b *testBackend) ChainDb() ethdb.Database { + return b.chaindb +} + +// teardown releases the associated resources. +func (b *testBackend) teardown() { + b.chain.Stop() +} + +func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error) { + statedb, err := b.chain.StateAt(block.Root()) + if err != nil { + return nil, nil, errStateNotFound + } + if b.refHook != nil { + b.refHook() + } + release := func() { + if b.relHook != nil { + b.relHook() + } + } + return statedb, release, nil +} + +func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, StateReleaseFunc, error) { + parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, vm.BlockContext{}, nil, nil, errBlockNotFound + } + statedb, release, err := b.StateAtBlock(ctx, parent, reexec, nil, true, false) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, errStateNotFound + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, release, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(b.chainConfig, block.Number(), block.Time()) + for idx, tx := range block.Transactions() { + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), b.chain, nil) + if idx == txIndex { + return tx, context, statedb, release, nil + } + vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, 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) + } + 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()) +} + +func TestTraceCall(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + 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.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 { + blockNumber rpc.BlockNumber + call ethapi.TransactionArgs + config *TraceCallConfig + expectErr error + expect string + }{ + // Standard JSON trace upon the genesis, plain transfer. + { + blockNumber: rpc.BlockNumber(0), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + }, + // Standard JSON trace upon the head, plain transfer. + { + blockNumber: rpc.BlockNumber(genBlocks), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + 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 succeeded + { + 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), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), + //expect: nil, + }, + // Standard JSON trace upon the latest block + { + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + }, + // Tracing on 'pending' should fail: + { + blockNumber: rpc.PendingBlockNumber, + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: errors.New("tracing on top of pending is not supported"), + }, + { + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + Input: &hexutil.Bytes{0x43}, // blocknumber + }, + config: &TraceCallConfig{ + BlockOverrides: ðapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, + }, + expectErr: nil, + expect: ` {"gas":53018,"failed":false,"returnValue":"","structLogs":[ + {"pc":0,"op":"NUMBER","gas":24946984,"gasCost":2,"depth":1,"stack":[]}, + {"pc":1,"op":"STOP","gas":24946982,"gasCost":0,"depth":1,"stack":["0x1337"]}]}`, + }, + } + for i, testspec := range testSuite { + result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config) + if testspec.expectErr != nil { + if err == nil { + t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr) + continue + } + 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 { + t.Errorf("test %d: expect no error, got %v", i, err) + continue + } + var have *logger.ExecutionResult + if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil { + t.Errorf("test %d: failed to unmarshal result %v", i, err) + } + var want *logger.ExecutionResult + if err := json.Unmarshal([]byte(testspec.expect), &want); err != nil { + t.Errorf("test %d: failed to unmarshal result %v", i, err) + } + if !reflect.DeepEqual(have, want) { + t.Errorf("test %d: result mismatch, want %v, got %v", i, testspec.expect, string(result.(json.RawMessage))) + } + } + } +} + +func TestTraceTransaction(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + target := common.Hash{} + signer := types.HomesteadSigner{} + backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &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() + }) + defer backend.chain.Stop() + api := NewAPI(backend) + result, err := api.TraceTransaction(context.Background(), target, nil) + if err != nil { + t.Errorf("Failed to trace transaction %v", err) + } + var have *logger.ExecutionResult + if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil { + t.Errorf("failed to unmarshal result %v", err) + } + if !reflect.DeepEqual(have, &logger.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []logger.StructLogRes{}, + }) { + t.Error("Transaction tracing result is different") + } + + // Test non-existent transaction + _, err = api.TraceTransaction(context.Background(), common.Hash{42}, nil) + if !errors.Is(err, errTxNotFound) { + t.Fatalf("want %v, have %v", errTxNotFound, err) + } +} + +func TestTraceBlock(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + genBlocks := 10 + signer := types.HomesteadSigner{} + var txHash common.Hash + 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.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() + }) + defer backend.chain.Stop() + api := NewAPI(backend) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + config *TraceConfig + want string + expectErr error + }{ + // Trace genesis block, expect error + { + blockNumber: rpc.BlockNumber(0), + expectErr: errors.New("genesis is not traceable"), + }, + // Trace head block + { + blockNumber: rpc.BlockNumber(genBlocks), + want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), + }, + // Trace non-existent block + { + blockNumber: rpc.BlockNumber(genBlocks + 1), + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), + }, + // Trace latest block + { + blockNumber: rpc.LatestBlockNumber, + want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), + }, + // Trace pending block + { + blockNumber: rpc.PendingBlockNumber, + want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash), + }, + } + for i, tc := range testSuite { + result, err := api.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config) + if tc.expectErr != nil { + if err == nil { + t.Errorf("test %d, want error %v", i, tc.expectErr) + continue + } + if !reflect.DeepEqual(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err) + } + continue + } + if err != nil { + t.Errorf("test %d, want no error, have %v", i, err) + continue + } + have, _ := json.Marshal(result) + want := tc.want + if string(have) != want { + t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(have), want) + } + } +} + +func TestTracingWithOverrides(t *testing.T) { + t.Parallel() + // Initialize test accounts + accounts := newAccounts(3) + storageAccount := common.Address{0x13, 37} + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + // An account with existing storage + storageAccount: { + Balance: new(big.Int), + Storage: map[common.Hash]common.Hash{ + common.HexToHash("0x03"): common.HexToHash("0x33"), + common.HexToHash("0x04"): common.HexToHash("0x44"), + }, + }, + }, + } + genBlocks := 10 + signer := types.HomesteadSigner{} + 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.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() + api := NewAPI(backend) + randomAccounts := newAccounts(3) + type res struct { + Gas int + Failed bool + ReturnValue string + } + var testSuite = []struct { + blockNumber rpc.BlockNumber + call ethapi.TransactionArgs + config *TraceCallConfig + expectErr error + want string + }{ + // Call which can only succeed if state is state overridden + { + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: &TraceCallConfig{ + StateOverrides: ðapi.StateOverride{ + randomAccounts[0].addr: ethapi.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))}, + }, + }, + want: `{"gas":21000,"failed":false,"returnValue":""}`, + }, + // Invalid call without state overriding + { + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: &TraceCallConfig{}, + expectErr: core.ErrInsufficientFunds, + }, + // Successful simple contract call + // + // // SPDX-License-Identifier: GPL-3.0 + // + // pragma solidity >=0.7.0 <0.8.0; + // + // /** + // * @title Storage + // * @dev Store & retrieve value in a variable + // */ + // contract Storage { + // uint256 public number; + // constructor() { + // number = block.number; + // } + // } + { + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number() + }, + config: &TraceCallConfig{ + //Tracer: &tracer, + StateOverrides: ðapi.StateOverride{ + randomAccounts[2].addr: ethapi.OverrideAccount{ + Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033")), + StateDiff: newStates([]common.Hash{{}}, []common.Hash{common.BigToHash(big.NewInt(123))}), + }, + }, + }, + want: `{"gas":23347,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000007b"}`, + }, + { // Override blocknumber + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + // BLOCKNUMBER PUSH1 MSTORE + Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")), + }, + config: &TraceCallConfig{ + BlockOverrides: ðapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, + }, + want: `{"gas":59537,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000001337"}`, + }, + { // Override blocknumber, and query a blockhash + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + Input: &hexutil.Bytes{ + 0x60, 0x00, 0x40, // BLOCKHASH(0) + 0x60, 0x00, 0x52, // STORE memory offset 0 + 0x61, 0x13, 0x36, 0x40, // BLOCKHASH(0x1336) + 0x60, 0x20, 0x52, // STORE memory offset 32 + 0x61, 0x13, 0x37, 0x40, // BLOCKHASH(0x1337) + 0x60, 0x40, 0x52, // STORE memory offset 64 + 0x60, 0x60, 0x60, 0x00, 0xf3, // RETURN (0-96) + + }, // blocknumber + }, + config: &TraceCallConfig{ + BlockOverrides: ðapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, + }, + want: `{"gas":72666,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`, + }, + /* + pragma solidity =0.8.12; + + contract Test { + uint private x; + + function test2() external { + x = 1337; + revert(); + } + + function test() external returns (uint) { + x = 1; + try this.test2() {} catch (bytes memory) {} + return x; + } + } + */ + { // First with only code override, not storage override + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), // + }, + config: &TraceCallConfig{ + StateOverrides: ðapi.StateOverride{ + randomAccounts[2].addr: ethapi.OverrideAccount{ + Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060325760003560e01c806366e41cb7146037578063f8a8fd6d14603f575b600080fd5b603d6057565b005b60456062565b60405190815260200160405180910390f35b610539600090815580fd5b60006001600081905550306001600160a01b03166366e41cb76040518163ffffffff1660e01b8152600401600060405180830381600087803b15801560a657600080fd5b505af192505050801560b6575060015b60e9573d80801560e1576040519150601f19603f3d011682016040523d82523d6000602084013e60e6565b606091505b50505b506000549056fea26469706673582212205ce45de745a5308f713cb2f448589177ba5a442d1a2eff945afaa8915961b4d064736f6c634300080c0033")), + }, + }, + }, + want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`, + }, + { // Same again, this time with storage override + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), // + }, + config: &TraceCallConfig{ + StateOverrides: ðapi.StateOverride{ + randomAccounts[2].addr: ethapi.OverrideAccount{ + Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060325760003560e01c806366e41cb7146037578063f8a8fd6d14603f575b600080fd5b603d6057565b005b60456062565b60405190815260200160405180910390f35b610539600090815580fd5b60006001600081905550306001600160a01b03166366e41cb76040518163ffffffff1660e01b8152600401600060405180830381600087803b15801560a657600080fd5b505af192505050801560b6575060015b60e9573d80801560e1576040519150601f19603f3d011682016040523d82523d6000602084013e60e6565b606091505b50505b506000549056fea26469706673582212205ce45de745a5308f713cb2f448589177ba5a442d1a2eff945afaa8915961b4d064736f6c634300080c0033")), + State: newStates([]common.Hash{{}}, []common.Hash{{}}), + }, + }, + }, + //want: `{"gas":46900,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000539"}`, + want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`, + }, + { // No state override + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &randomAccounts[0].addr, + To: &storageAccount, + Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), // + }, + config: &TraceCallConfig{ + StateOverrides: ðapi.StateOverride{ + storageAccount: ethapi.OverrideAccount{ + Code: newRPCBytes([]byte{ + // SLOAD(3) + SLOAD(4) (which is 0x77) + byte(vm.PUSH1), 0x04, + byte(vm.SLOAD), + byte(vm.PUSH1), 0x03, + byte(vm.SLOAD), + byte(vm.ADD), + // 0x77 -> MSTORE(0) + byte(vm.PUSH1), 0x00, + byte(vm.MSTORE), + // RETURN (0, 32) + byte(vm.PUSH1), 32, + byte(vm.PUSH1), 00, + byte(vm.RETURN), + }), + }, + }, + }, + want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000077"}`, + }, + { // Full state override + // The original storage is + // 3: 0x33 + // 4: 0x44 + // With a full override, where we set 3:0x11, the slot 4 should be + // removed. So SLOT(3)+SLOT(4) should be 0x11. + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &randomAccounts[0].addr, + To: &storageAccount, + Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), // + }, + config: &TraceCallConfig{ + StateOverrides: ðapi.StateOverride{ + storageAccount: ethapi.OverrideAccount{ + Code: newRPCBytes([]byte{ + // SLOAD(3) + SLOAD(4) (which is now 0x11 + 0x00) + byte(vm.PUSH1), 0x04, + byte(vm.SLOAD), + byte(vm.PUSH1), 0x03, + byte(vm.SLOAD), + byte(vm.ADD), + // 0x11 -> MSTORE(0) + byte(vm.PUSH1), 0x00, + byte(vm.MSTORE), + // RETURN (0, 32) + byte(vm.PUSH1), 32, + byte(vm.PUSH1), 00, + byte(vm.RETURN), + }), + State: newStates( + []common.Hash{common.HexToHash("0x03")}, + []common.Hash{common.HexToHash("0x11")}), + }, + }, + }, + want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000011"}`, + }, + { // Partial state override + // The original storage is + // 3: 0x33 + // 4: 0x44 + // With a partial override, where we set 3:0x11, the slot 4 as before. + // So SLOT(3)+SLOT(4) should be 0x55. + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &randomAccounts[0].addr, + To: &storageAccount, + Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), // + }, + config: &TraceCallConfig{ + StateOverrides: ðapi.StateOverride{ + storageAccount: ethapi.OverrideAccount{ + Code: newRPCBytes([]byte{ + // SLOAD(3) + SLOAD(4) (which is now 0x11 + 0x44) + byte(vm.PUSH1), 0x04, + byte(vm.SLOAD), + byte(vm.PUSH1), 0x03, + byte(vm.SLOAD), + byte(vm.ADD), + // 0x55 -> MSTORE(0) + byte(vm.PUSH1), 0x00, + byte(vm.MSTORE), + // RETURN (0, 32) + byte(vm.PUSH1), 32, + byte(vm.PUSH1), 00, + byte(vm.RETURN), + }), + StateDiff: &map[common.Hash]common.Hash{ + common.HexToHash("0x03"): common.HexToHash("0x11"), + }, + }, + }, + }, + want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000055"}`, + }, + } + for i, tc := range testSuite { + result, err := api.TraceCall(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, tc.config) + if tc.expectErr != nil { + if err == nil { + t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) + continue + } + if !errors.Is(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + } + continue + } + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) + continue + } + // Turn result into res-struct + var ( + have res + want res + ) + resBytes, _ := json.Marshal(result) + json.Unmarshal(resBytes, &have) + json.Unmarshal([]byte(tc.want), &want) + if !reflect.DeepEqual(have, want) { + t.Logf("result: %v\n", string(resBytes)) + t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, have, want) + } + } +} + +type Account struct { + key *ecdsa.PrivateKey + addr common.Address +} + +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}) + } + slices.SortFunc(accounts, func(a, b Account) int { return a.addr.Cmp(b.addr) }) + return accounts +} + +func newRPCBalance(balance *big.Int) **hexutil.Big { + rpcBalance := (*hexutil.Big)(balance) + return &rpcBalance +} + +func newRPCBytes(bytes []byte) *hexutil.Bytes { + rpcBytes := hexutil.Bytes(bytes) + return &rpcBytes +} + +func newStates(keys []common.Hash, vals []common.Hash) *map[common.Hash]common.Hash { + if len(keys) != len(vals) { + panic("invalid input") + } + m := make(map[common.Hash]common.Hash) + for i := 0; i < len(keys); i++ { + m[keys[i]] = vals[i] + } + return &m +} + +func TestTraceChain(t *testing.T) { + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + genBlocks := 50 + signer := types.HomesteadSigner{} + + var ( + ref atomic.Uint32 // total refs has made + rel atomic.Uint32 // total rels has made + nonce uint64 + ) + backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + for j := 0; j < i+1; j++ { + tx, _ := types.SignTx(types.NewTransaction(nonce, accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + b.AddTx(tx) + nonce += 1 + } + }) + backend.refHook = func() { ref.Add(1) } + backend.relHook = func() { rel.Add(1) } + api := NewAPI(backend) + + single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}` + var cases = []struct { + start uint64 + end uint64 + config *TraceConfig + }{ + {0, 50, nil}, // the entire chain range, blocks [1, 50] + {10, 20, nil}, // the middle chain range, blocks [11, 20] + } + for _, c := range cases { + ref.Store(0) + rel.Store(0) + + from, _ := api.blockByNumber(context.Background(), rpc.BlockNumber(c.start)) + to, _ := api.blockByNumber(context.Background(), rpc.BlockNumber(c.end)) + resCh := api.traceChain(from, to, c.config, nil) + + next := c.start + 1 + for result := range resCh { + if have, want := uint64(result.Block), next; have != want { + t.Fatalf("unexpected tracing block, have %d want %d", have, want) + } + if have, want := len(result.Traces), int(next); have != want { + t.Fatalf("unexpected result length, have %d want %d", have, want) + } + for _, trace := range result.Traces { + trace.TxHash = common.Hash{} + blob, _ := json.Marshal(trace) + if have, want := string(blob), single; have != want { + t.Fatalf("unexpected tracing result, have\n%v\nwant:\n%v", have, want) + } + } + next += 1 + } + if next != c.end+1 { + t.Error("Missing tracing block") + } + + if nref, nrel := ref.Load(), rel.Load(); nref != nrel { + t.Errorf("Ref and deref actions are not equal, ref %d rel %d", nref, nrel) + } + } +} + +// newTestMergedBackend creates a post-merge chain +func newTestMergedBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + backend := &testBackend{ + chainConfig: gspec.Config, + engine: beacon.NewFaker(), + chaindb: rawdb.NewMemoryDatabase(), + } + // Generate blocks for testing + _, blocks, _ := core.GenerateChainWithGenesis(gspec, backend.engine, n, generator) + + // Import the canonical chain + cacheConfig := &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + TrieDirtyDisabled: true, // Archive mode + } + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, nil, backend.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + backend.chain = chain + return backend +} + +func TestTraceBlockWithBasefee(t *testing.T) { + t.Parallel() + accounts := newAccounts(1) + target := common.HexToAddress("0x1111111111111111111111111111111111111111") + genesis := &core.Genesis{ + Config: params.AllDevChainProtocolChanges, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(1 * params.Ether)}, + target: {Nonce: 1, Code: []byte{ + byte(vm.BASEFEE), byte(vm.STOP), + }}, + }, + } + genBlocks := 1 + signer := types.HomesteadSigner{} + var txHash common.Hash + var baseFee = new(big.Int) + backend := newTestMergedBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &target, + Value: big.NewInt(0), + Gas: 5 * params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, accounts[0].key) + b.AddTx(tx) + txHash = tx.Hash() + baseFee.Set(b.BaseFee()) + }) + defer backend.chain.Stop() + api := NewAPI(backend) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + config *TraceConfig + want string + }{ + // Trace head block + { + blockNumber: rpc.BlockNumber(genBlocks), + want: fmt.Sprintf(`[{"txHash":"%#x","result":{"gas":21002,"failed":false,"returnValue":"","structLogs":[{"pc":0,"op":"BASEFEE","gas":84000,"gasCost":2,"depth":1,"stack":[]},{"pc":1,"op":"STOP","gas":83998,"gasCost":0,"depth":1,"stack":["%#x"]}]}}]`, txHash, baseFee), + }, + } + for i, tc := range testSuite { + result, err := api.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config) + if err != nil { + t.Errorf("test %d, want no error, have %v", i, err) + continue + } + have, _ := json.Marshal(result) + want := tc.want + if string(have) != want { + t.Errorf("test %d, result mismatch\nhave: %v\nwant: %v\n", i, string(have), want) + } + } +} diff --git a/eth/tracers/dir.go b/eth/tracers/dir.go new file mode 100644 index 0000000..6508153 --- /dev/null +++ b/eth/tracers/dir.go @@ -0,0 +1,98 @@ +// 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 tracers + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" +) + +// Context contains some contextual infos for a transaction execution that is not +// available from within the EVM object. +type Context struct { + BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) + BlockNumber *big.Int // Number of the block the tx is contained within (zero if dangling tx or call) + TxIndex int // Index of the transaction within a block (zero if dangling tx or call) + TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) +} + +// The set of methods that must be exposed by a tracer +// for it to be available through the RPC interface. +// This involves a method to retrieve results and one to +// stop tracing. +type Tracer struct { + *tracing.Hooks + GetResult func() (json.RawMessage, error) + // Stop terminates execution of the tracer at the first opportune moment. + Stop func(err error) +} + +type ctorFn func(*Context, json.RawMessage) (*Tracer, error) +type jsCtorFn func(string, *Context, json.RawMessage) (*Tracer, error) + +type elem struct { + ctor ctorFn + isJS bool +} + +// DefaultDirectory is the collection of tracers bundled by default. +var DefaultDirectory = directory{elems: make(map[string]elem)} + +// directory provides functionality to lookup a tracer by name +// and a function to instantiate it. It falls back to a JS code evaluator +// if no tracer of the given name exists. +type directory struct { + elems map[string]elem + jsEval jsCtorFn +} + +// Register registers a method as a lookup for tracers, meaning that +// users can invoke a named tracer through that lookup. +func (d *directory) Register(name string, f ctorFn, isJS bool) { + d.elems[name] = elem{ctor: f, isJS: isJS} +} + +// RegisterJSEval registers a tracer that is able to parse +// dynamic user-provided JS code. +func (d *directory) RegisterJSEval(f jsCtorFn) { + d.jsEval = f +} + +// New returns a new instance of a tracer, by iterating through the +// registered lookups. Name is either name of an existing tracer +// or an arbitrary JS code. +func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (*Tracer, error) { + if elem, ok := d.elems[name]; ok { + return elem.ctor(ctx, cfg) + } + // Assume JS code + return d.jsEval(name, ctx, cfg) +} + +// IsJS will return true if the given tracer will evaluate +// JS code. Because code evaluation has high overhead, this +// info will be used in determining fast and slow code paths. +func (d *directory) IsJS(name string) bool { + if elem, ok := d.elems[name]; ok { + return elem.isJS + } + // JS eval will execute JS code + return true +} diff --git a/eth/tracers/internal/tracetest/README.md b/eth/tracers/internal/tracetest/README.md new file mode 100644 index 0000000..8c3d5d2 --- /dev/null +++ b/eth/tracers/internal/tracetest/README.md @@ -0,0 +1,10 @@ +# Filling test cases + +To fill test cases for the built-in tracers, the `makeTest.js` script can be used. Given a transaction on a dev/test network, `makeTest.js` will fetch its prestate and then traces with the given configuration. +In the Geth console do: + +```terminal +let tx = '0x...' +loadScript('makeTest.js') +makeTest(tx, { tracer: 'callTracer' }) +``` \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go new file mode 100644 index 0000000..31b2ef6 --- /dev/null +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -0,0 +1,399 @@ +// 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 tracetest + +import ( + "encoding/json" + "fmt" + "math/big" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/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/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/tests" +) + +// 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"` + Position hexutil.Uint `json:"position"` +} + +// callTrace is the result of a callTracer run. +type callTrace struct { + From common.Address `json:"from"` + Gas *hexutil.Uint64 `json:"gas"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + To *common.Address `json:"to,omitempty"` + Input hexutil.Bytes `json:"input"` + Output hexutil.Bytes `json:"output,omitempty"` + Error string `json:"error,omitempty"` + RevertReason string `json:"revertReason,omitempty"` + Calls []callTrace `json:"calls,omitempty"` + Logs []callLog `json:"logs,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + // Gencodec adds overridden fields at the end + Type string `json:"type"` +} + +// callTracerTest defines a single test to check the call tracer against. +type callTracerTest struct { + Genesis *core.Genesis `json:"genesis"` + Context *callContext `json:"context"` + Input string `json:"input"` + TracerConfig json.RawMessage `json:"tracerConfig"` + Result *callTrace `json:"result"` +} + +// Iterates over all the input-output datasets in the tracer test harness and +// runs the JavaScript tracers against them. +func TestCallTracerLegacy(t *testing.T) { + testCallTracer("callTracerLegacy", "call_tracer_legacy", t) +} + +func TestCallTracerNative(t *testing.T) { + testCallTracer("callTracer", "call_tracer", t) +} + +func TestCallTracerNativeWithLog(t *testing.T) { + testCallTracer("callTracer", "call_tracer_withLog", t) +} + +func testCallTracer(tracerName string, dirPath string, t *testing.T) { + isLegacy := strings.HasSuffix(dirPath, "_legacy") + files, err := os.ReadDir(filepath.Join("testdata", dirPath)) + if err != nil { + t.Fatalf("failed to retrieve tracer test suite: %v", err) + } + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".json") { + continue + } + file := file // capture range variable + t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { + t.Parallel() + + var ( + test = new(callTracerTest) + tx = new(types.Transaction) + ) + // Call tracer test found, read if from disk + if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil { + t.Fatalf("failed to read testcase: %v", err) + } else if err := json.Unmarshal(blob, test); err != nil { + t.Fatalf("failed to parse testcase: %v", err) + } + if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil { + t.Fatalf("failed to parse testcase input: %v", err) + } + // Configure a blockchain with the given prestate + var ( + signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) + context = test.Context.toBlockContext(test.Genesis) + state = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) + ) + state.Close() + + tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) + if err != nil { + t.Fatalf("failed to create call tracer: %v", err) + } + + state.StateDB.SetLogger(tracer.Hooks) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) + if err != nil { + t.Fatalf("failed to prepare transaction for tracing: %v", err) + } + evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) + tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) + vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + if err != nil { + t.Fatalf("failed to execute transaction: %v", err) + } + tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) + // Retrieve the trace result and compare against the expected. + res, err := tracer.GetResult() + if err != nil { + t.Fatalf("failed to retrieve trace result: %v", err) + } + // The legacy javascript calltracer marshals json in js, which + // is not deterministic (as opposed to the golang json encoder). + if isLegacy { + // This is a tweak to make it deterministic. Can be removed when + // we remove the legacy tracer. + var x callTrace + json.Unmarshal(res, &x) + res, _ = json.Marshal(x) + } + want, err := json.Marshal(test.Result) + if err != nil { + t.Fatalf("failed to marshal test: %v", err) + } + if string(want) != string(res) { + t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want)) + } + // Sanity check: compare top call's gas used against vm result + type simpleResult struct { + GasUsed hexutil.Uint64 + } + var topCall simpleResult + if err := json.Unmarshal(res, &topCall); err != nil { + t.Fatalf("failed to unmarshal top calls gasUsed: %v", err) + } + if uint64(topCall.GasUsed) != vmRet.UsedGas { + t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas) + } + }) + } +} + +func BenchmarkTracers(b *testing.B) { + files, err := os.ReadDir(filepath.Join("testdata", "call_tracer")) + if err != nil { + b.Fatalf("failed to retrieve tracer test suite: %v", err) + } + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".json") { + continue + } + file := file // capture range variable + b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { + blob, err := os.ReadFile(filepath.Join("testdata", "call_tracer", file.Name())) + if err != nil { + b.Fatalf("failed to read testcase: %v", err) + } + test := new(callTracerTest) + if err := json.Unmarshal(blob, test); err != nil { + b.Fatalf("failed to parse testcase: %v", err) + } + benchTracer("callTracer", test, b) + }) + } +} + +func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { + // Configure a blockchain with the given prestate + tx := new(types.Transaction) + if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { + b.Fatalf("failed to parse testcase input: %v", err) + } + signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) + origin, _ := signer.Sender(tx) + txContext := vm.TxContext{ + Origin: origin, + GasPrice: tx.GasPrice(), + } + context := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: test.Context.Miner, + BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), + Time: uint64(test.Context.Time), + Difficulty: (*big.Int)(test.Context.Difficulty), + GasLimit: uint64(test.Context.GasLimit), + } + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) + if err != nil { + b.Fatalf("failed to prepare transaction for tracing: %v", err) + } + state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) + defer state.Close() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil) + if err != nil { + b.Fatalf("failed to create call tracer: %v", err) + } + evm := vm.NewEVM(context, txContext, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) + snap := state.StateDB.Snapshot() + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + if _, err = st.TransitionDb(); err != nil { + b.Fatalf("failed to execute transaction: %v", err) + } + if _, err = tracer.GetResult(); err != nil { + b.Fatal(err) + } + state.StateDB.RevertToSnapshot(snap) + } +} + +func TestInternals(t *testing.T) { + var ( + config = params.MainnetChainConfig + to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") + originHex = "0x71562b71999873db5b286df957af199ec94617f7" + origin = common.HexToAddress(originHex) + signer = types.LatestSigner(config) + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + context = vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: common.Address{}, + BlockNumber: new(big.Int).SetUint64(8000000), + Time: 5, + Difficulty: big.NewInt(0x30000), + GasLimit: uint64(6000000), + BaseFee: new(big.Int), + } + ) + mkTracer := func(name string, cfg json.RawMessage) *tracers.Tracer { + tr, err := tracers.DefaultDirectory.New(name, nil, cfg) + if err != nil { + t.Fatalf("failed to create call tracer: %v", err) + } + return tr + } + + for _, tc := range []struct { + name string + code []byte + tracer *tracers.Tracer + want string + }{ + { + // TestZeroValueToNotExitCall tests the calltracer(s) on the following: + // Tx to A, A calls B with zero value. B does not already exist. + // Expected: that enter/exit is invoked and the inner call is shown in the result + name: "ZeroValueToNotExitCall", + code: []byte{ + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), // in and outs zero + byte(vm.DUP1), byte(vm.PUSH1), 0xff, byte(vm.GAS), // value=0,address=0xff, gas=GAS + byte(vm.CALL), + }, + tracer: mkTracer("callTracer", nil), + want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, originHex), + }, + { + name: "Stack depletion in LOG0", + code: []byte{byte(vm.LOG3)}, + tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), + want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, originHex), + }, + { + name: "Mem expansion in LOG0", + code: []byte{ + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x0, + byte(vm.MSTORE), + byte(vm.PUSH1), 0xff, + byte(vm.PUSH1), 0x0, + byte(vm.LOG0), + }, + tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), + want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex), + }, + { + // Leads to OOM on the prestate tracer + name: "Prestate-tracer - CREATE2 OOM", + code: []byte{ + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x0, + byte(vm.MSTORE), + byte(vm.PUSH1), 0x1, + byte(vm.PUSH5), 0xff, 0xff, 0xff, 0xff, 0xff, + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x0, + byte(vm.CREATE2), + byte(vm.PUSH1), 0xff, + byte(vm.PUSH1), 0x0, + byte(vm.LOG0), + }, + tracer: mkTracer("prestateTracer", nil), + want: fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex), + }, + { + // CREATE2 which requires padding memory by prestate tracer + name: "Prestate-tracer - CREATE2 Memory padding", + code: []byte{ + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x0, + byte(vm.MSTORE), + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0xff, + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x0, + byte(vm.CREATE2), + byte(vm.PUSH1), 0xff, + byte(vm.PUSH1), 0x0, + byte(vm.LOG0), + }, + tracer: mkTracer("prestateTracer", nil), + want: fmt.Sprintf(`{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"%s":{"balance":"0x1c6bf52634000"}}`, originHex), + }, + } { + t.Run(tc.name, func(t *testing.T) { + state := tests.MakePreState(rawdb.NewMemoryDatabase(), + types.GenesisAlloc{ + to: types.Account{ + Code: tc.code, + }, + origin: types.Account{ + Balance: big.NewInt(500000000000000), + }, + }, false, rawdb.HashScheme) + defer state.Close() + state.StateDB.SetLogger(tc.tracer.Hooks) + tx, err := types.SignNewTx(key, signer, &types.LegacyTx{ + To: &to, + Value: big.NewInt(0), + Gas: 80000, + GasPrice: big.NewInt(1), + }) + if err != nil { + t.Fatalf("test %v: failed to sign transaction: %v", tc.name, err) + } + txContext := vm.TxContext{ + Origin: origin, + GasPrice: tx.GasPrice(), + } + evm := vm.NewEVM(context, txContext, state.StateDB, config, vm.Config{Tracer: tc.tracer.Hooks}) + msg, err := core.TransactionToMessage(tx, signer, big.NewInt(0)) + if err != nil { + t.Fatalf("test %v: failed to create message: %v", tc.name, err) + } + tc.tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) + vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + if err != nil { + t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) + } + tc.tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) + // Retrieve the trace result and compare against the expected + res, err := tc.tracer.GetResult() + if err != nil { + t.Fatalf("test %v: failed to retrieve trace result: %v", tc.name, err) + } + if string(res) != tc.want { + t.Errorf("test %v: trace mismatch\n have: %v\n want: %v\n", tc.name, string(res), tc.want) + } + }) + } +} diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go new file mode 100644 index 0000000..ec7a944 --- /dev/null +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -0,0 +1,201 @@ +package tracetest + +import ( + "encoding/json" + "fmt" + "math/big" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/tests" +) + +// flatCallTrace is the result of a callTracerParity run. +type flatCallTrace struct { + Action flatCallTraceAction `json:"action"` + BlockHash common.Hash `json:"-"` + BlockNumber uint64 `json:"-"` + Error string `json:"error,omitempty"` + Result flatCallTraceResult `json:"result,omitempty"` + Subtraces int `json:"subtraces"` + TraceAddress []int `json:"traceAddress"` + TransactionHash common.Hash `json:"-"` + TransactionPosition uint64 `json:"-"` + Type string `json:"type"` + Time string `json:"-"` +} + +type flatCallTraceAction struct { + Author common.Address `json:"author,omitempty"` + RewardType string `json:"rewardType,omitempty"` + SelfDestructed common.Address `json:"address,omitempty"` + Balance hexutil.Big `json:"balance,omitempty"` + CallType string `json:"callType,omitempty"` + CreationMethod string `json:"creationMethod,omitempty"` + From common.Address `json:"from,omitempty"` + Gas hexutil.Uint64 `json:"gas,omitempty"` + Init hexutil.Bytes `json:"init,omitempty"` + Input hexutil.Bytes `json:"input,omitempty"` + RefundAddress common.Address `json:"refundAddress,omitempty"` + To common.Address `json:"to,omitempty"` + Value hexutil.Big `json:"value,omitempty"` +} + +type flatCallTraceResult struct { + Address common.Address `json:"address,omitempty"` + Code hexutil.Bytes `json:"code,omitempty"` + GasUsed hexutil.Uint64 `json:"gasUsed,omitempty"` + Output hexutil.Bytes `json:"output,omitempty"` +} + +// flatCallTracerTest defines a single test to check the call tracer against. +type flatCallTracerTest struct { + Genesis *core.Genesis `json:"genesis"` + Context *callContext `json:"context"` + Input string `json:"input"` + TracerConfig json.RawMessage `json:"tracerConfig"` + Result []flatCallTrace `json:"result"` +} + +func flatCallTracerTestRunner(tracerName string, filename string, dirPath string, t testing.TB) error { + // Call tracer test found, read if from disk + blob, err := os.ReadFile(filepath.Join("testdata", dirPath, filename)) + if err != nil { + return fmt.Errorf("failed to read testcase: %v", err) + } + test := new(flatCallTracerTest) + if err := json.Unmarshal(blob, test); err != nil { + return fmt.Errorf("failed to parse testcase: %v", err) + } + // Configure a blockchain with the given prestate + tx := new(types.Transaction) + if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { + return fmt.Errorf("failed to parse testcase input: %v", err) + } + signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) + context := test.Context.toBlockContext(test.Genesis) + state := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) + defer state.Close() + + // Create the tracer, the EVM environment and run it + tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) + if err != nil { + return fmt.Errorf("failed to create call tracer: %v", err) + } + + state.StateDB.SetLogger(tracer.Hooks) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) + if err != nil { + return fmt.Errorf("failed to prepare transaction for tracing: %v", err) + } + evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) + tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) + vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + if err != nil { + return fmt.Errorf("failed to execute transaction: %v", err) + } + tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) + + // Retrieve the trace result and compare against the etalon + res, err := tracer.GetResult() + if err != nil { + return fmt.Errorf("failed to retrieve trace result: %v", err) + } + ret := make([]flatCallTrace, 0) + if err := json.Unmarshal(res, &ret); err != nil { + return fmt.Errorf("failed to unmarshal trace result: %v", err) + } + if !jsonEqualFlat(ret, test.Result) { + t.Logf("test %s failed", filename) + + // uncomment this for easier debugging + // have, _ := json.MarshalIndent(ret, "", " ") + // want, _ := json.MarshalIndent(test.Result, "", " ") + // t.Logf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) + + // uncomment this for harder debugging <3 meowsbits + // lines := deep.Equal(ret, test.Result) + // for _, l := range lines { + // t.Logf("%s", l) + // t.FailNow() + // } + + t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) + } + return nil +} + +// Iterates over all the input-output datasets in the tracer parity test harness and +// runs the Native tracer against them. +func TestFlatCallTracerNative(t *testing.T) { + testFlatCallTracer("flatCallTracer", "call_tracer_flat", t) +} + +func testFlatCallTracer(tracerName string, dirPath string, t *testing.T) { + files, err := os.ReadDir(filepath.Join("testdata", dirPath)) + if err != nil { + t.Fatalf("failed to retrieve tracer test suite: %v", err) + } + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".json") { + continue + } + file := file // capture range variable + t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { + t.Parallel() + + err := flatCallTracerTestRunner(tracerName, file.Name(), dirPath, t) + if err != nil { + t.Fatal(err) + } + }) + } +} + +// jsonEqualFlat is similar to reflect.DeepEqual, but does a 'bounce' via json prior to +// comparison +func jsonEqualFlat(x, y interface{}) bool { + xTrace := new([]flatCallTrace) + yTrace := new([]flatCallTrace) + if xj, err := json.Marshal(x); err == nil { + json.Unmarshal(xj, xTrace) + } else { + return false + } + if yj, err := json.Marshal(y); err == nil { + json.Unmarshal(yj, yTrace) + } else { + return false + } + return reflect.DeepEqual(xTrace, yTrace) +} + +func BenchmarkFlatCallTracer(b *testing.B) { + files, err := filepath.Glob("testdata/call_tracer_flat/*.json") + if err != nil { + b.Fatalf("failed to read testdata: %v", err) + } + + for _, file := range files { + filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/") + b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) { + for n := 0; n < b.N; n++ { + err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/eth/tracers/internal/tracetest/makeTest.js b/eth/tracers/internal/tracetest/makeTest.js new file mode 100644 index 0000000..3ad7a5d --- /dev/null +++ b/eth/tracers/internal/tracetest/makeTest.js @@ -0,0 +1,53 @@ +// makeTest generates a test for the configured tracer by running +// a prestate reassembled and a call trace run, assembling all the +// gathered information into a test case. +var makeTest = function(tx, traceConfig) { + // Generate the genesis block from the block, transaction and prestate data + var block = eth.getBlock(eth.getTransaction(tx).blockHash); + var genesis = eth.getBlock(block.parentHash); + + delete genesis.gasUsed; + delete genesis.logsBloom; + delete genesis.parentHash; + delete genesis.receiptsRoot; + delete genesis.sha3Uncles; + delete genesis.size; + delete genesis.transactions; + delete genesis.transactionsRoot; + delete genesis.uncles; + + genesis.gasLimit = genesis.gasLimit.toString(); + genesis.number = genesis.number.toString(); + genesis.timestamp = genesis.timestamp.toString(); + + genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer"}); + for (var key in genesis.alloc) { + var nonce = genesis.alloc[key].nonce; + if (nonce) { + genesis.alloc[key].nonce = nonce.toString(); + } + } + genesis.config = admin.nodeInfo.protocols.eth.config; + + // Generate the call trace and produce the test input + var result = debug.traceTransaction(tx, traceConfig); + delete result.time; + + var context = { + number: block.number.toString(), + difficulty: block.difficulty, + timestamp: block.timestamp.toString(), + gasLimit: block.gasLimit.toString(), + miner: block.miner, + }; + if (block.baseFeePerGas) { + context.baseFeePerGas = block.baseFeePerGas.toString(); + } + + console.log(JSON.stringify({ + genesis: genesis, + context: context, + input: eth.getRawTransaction(tx), + result: result, + }, null, 2)); +} diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go new file mode 100644 index 0000000..9cbd126 --- /dev/null +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -0,0 +1,141 @@ +// 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 tracetest + +import ( + "encoding/json" + "math/big" + "os" + "path/filepath" + "strings" + "testing" + + "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/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/tests" +) + +// prestateTrace is the result of a prestateTrace run. +type prestateTrace = map[common.Address]*account + +type account struct { + Balance string `json:"balance"` + Code string `json:"code"` + Nonce uint64 `json:"nonce"` + Storage map[common.Hash]common.Hash `json:"storage"` +} + +// testcase defines a single test to check the stateDiff tracer against. +type testcase struct { + Genesis *core.Genesis `json:"genesis"` + Context *callContext `json:"context"` + Input string `json:"input"` + TracerConfig json.RawMessage `json:"tracerConfig"` + Result interface{} `json:"result"` +} + +func TestPrestateTracerLegacy(t *testing.T) { + testPrestateDiffTracer("prestateTracerLegacy", "prestate_tracer_legacy", t) +} + +func TestPrestateTracer(t *testing.T) { + testPrestateDiffTracer("prestateTracer", "prestate_tracer", t) +} + +func TestPrestateWithDiffModeTracer(t *testing.T) { + testPrestateDiffTracer("prestateTracer", "prestate_tracer_with_diff_mode", t) +} + +func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { + files, err := os.ReadDir(filepath.Join("testdata", dirPath)) + if err != nil { + t.Fatalf("failed to retrieve tracer test suite: %v", err) + } + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".json") { + continue + } + file := file // capture range variable + t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { + t.Parallel() + + var ( + test = new(testcase) + tx = new(types.Transaction) + ) + // Call tracer test found, read if from disk + if blob, err := os.ReadFile(filepath.Join("testdata", dirPath, file.Name())); err != nil { + t.Fatalf("failed to read testcase: %v", err) + } else if err := json.Unmarshal(blob, test); err != nil { + t.Fatalf("failed to parse testcase: %v", err) + } + if err := tx.UnmarshalBinary(common.FromHex(test.Input)); err != nil { + t.Fatalf("failed to parse testcase input: %v", err) + } + // Configure a blockchain with the given prestate + var ( + signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)), uint64(test.Context.Time)) + context = test.Context.toBlockContext(test.Genesis) + state = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) + ) + defer state.Close() + + tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) + if err != nil { + t.Fatalf("failed to create call tracer: %v", err) + } + + state.StateDB.SetLogger(tracer.Hooks) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) + if err != nil { + t.Fatalf("failed to prepare transaction for tracing: %v", err) + } + evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) + tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) + vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + if err != nil { + t.Fatalf("failed to execute transaction: %v", err) + } + tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) + // Retrieve the trace result and compare against the expected + res, err := tracer.GetResult() + if err != nil { + t.Fatalf("failed to retrieve trace result: %v", err) + } + // The legacy javascript calltracer marshals json in js, which + // is not deterministic (as opposed to the golang json encoder). + if strings.HasSuffix(dirPath, "_legacy") { + // This is a tweak to make it deterministic. Can be removed when + // we remove the legacy tracer. + var x prestateTrace + json.Unmarshal(res, &x) + res, _ = json.Marshal(x) + } + want, err := json.Marshal(test.Result) + if err != nil { + t.Fatalf("failed to marshal test: %v", err) + } + if string(want) != string(res) { + t.Fatalf("trace mismatch\n have: %v\n want: %v\n", string(res), string(want)) + } + }) + } +} diff --git a/eth/tracers/internal/tracetest/supply_test.go b/eth/tracers/internal/tracetest/supply_test.go new file mode 100644 index 0000000..d608b1e --- /dev/null +++ b/eth/tracers/internal/tracetest/supply_test.go @@ -0,0 +1,613 @@ +// 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 tracetest + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "math/big" + "os" + "path" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "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/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/params" + + // Force-load live packages, to trigger registration + _ "github.com/ethereum/go-ethereum/eth/tracers/live" +) + +type supplyInfoIssuance struct { + GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"` + Reward *hexutil.Big `json:"reward,omitempty"` + Withdrawals *hexutil.Big `json:"withdrawals,omitempty"` +} + +type supplyInfoBurn struct { + EIP1559 *hexutil.Big `json:"1559,omitempty"` + Blob *hexutil.Big `json:"blob,omitempty"` + Misc *hexutil.Big `json:"misc,omitempty"` +} + +type supplyInfo struct { + Issuance *supplyInfoIssuance `json:"issuance,omitempty"` + Burn *supplyInfoBurn `json:"burn,omitempty"` + + // Block info + Number uint64 `json:"blockNumber"` + Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash"` +} + +func emptyBlockGenerationFunc(b *core.BlockGen) {} + +func TestSupplyOmittedFields(t *testing.T) { + var ( + config = *params.MergedTestChainConfig + gspec = &core.Genesis{ + Config: &config, + } + ) + + gspec.Config.TerminalTotalDifficulty = big.NewInt(0) + + out, _, err := testSupplyTracer(t, gspec, func(b *core.BlockGen) { + b.SetPoS() + }) + if err != nil { + t.Fatalf("failed to test supply tracer: %v", err) + } + + expected := supplyInfo{ + Number: 0, + Hash: common.HexToHash("0x52f276d96f0afaaf2c3cb358868bdc2779c4b0cb8de3e7e5302e247c0b66a703"), + ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), + } + actual := out[expected.Number] + + compareAsJSON(t, expected, actual) +} + +func TestSupplyGenesisAlloc(t *testing.T) { + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + + config = *params.AllEthashProtocolChanges + + gspec = &core.Genesis{ + Config: &config, + Alloc: types.GenesisAlloc{ + addr1: {Balance: eth1}, + addr2: {Balance: eth1}, + }, + } + ) + + expected := supplyInfo{ + Issuance: &supplyInfoIssuance{ + GenesisAlloc: (*hexutil.Big)(new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))), + }, + Number: 0, + Hash: common.HexToHash("0xbcc9466e9fc6a8b56f4b29ca353a421ff8b51a0c1a58ca4743b427605b08f2ca"), + ParentHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), + } + + out, _, err := testSupplyTracer(t, gspec, emptyBlockGenerationFunc) + if err != nil { + t.Fatalf("failed to test supply tracer: %v", err) + } + + actual := out[expected.Number] + + compareAsJSON(t, expected, actual) +} + +func TestSupplyRewards(t *testing.T) { + var ( + config = *params.AllEthashProtocolChanges + + gspec = &core.Genesis{ + Config: &config, + } + ) + + expected := supplyInfo{ + Issuance: &supplyInfoIssuance{ + Reward: (*hexutil.Big)(new(big.Int).Mul(common.Big2, big.NewInt(params.Ether))), + }, + Number: 1, + Hash: common.HexToHash("0xcbb08370505be503dafedc4e96d139ea27aba3cbc580148568b8a307b3f51052"), + ParentHash: common.HexToHash("0xadeda0a83e337b6c073e3f0e9a17531a04009b397a9588c093b628f21b8bc5a3"), + } + + out, _, err := testSupplyTracer(t, gspec, emptyBlockGenerationFunc) + if err != nil { + t.Fatalf("failed to test supply tracer: %v", err) + } + + actual := out[expected.Number] + + compareAsJSON(t, expected, actual) +} + +func TestSupplyEip1559Burn(t *testing.T) { + var ( + config = *params.AllEthashProtocolChanges + + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + // A sender who makes transactions, has some eth1 + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei)) + eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + + gspec = &core.Genesis{ + Config: &config, + BaseFee: big.NewInt(params.InitialBaseFee), + Alloc: types.GenesisAlloc{ + addr1: {Balance: eth1}, + }, + } + ) + + signer := types.LatestSigner(gspec.Config) + + eip1559BlockGenerationFunc := func(b *core.BlockGen) { + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 21000, + GasFeeCap: gwei5, + GasTipCap: big.NewInt(2), + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key1) + + b.AddTx(tx) + } + + out, chain, err := testSupplyTracer(t, gspec, eip1559BlockGenerationFunc) + if err != nil { + t.Fatalf("failed to test supply tracer: %v", err) + } + var ( + head = chain.CurrentBlock() + reward = new(big.Int).Mul(common.Big2, big.NewInt(params.Ether)) + burn = new(big.Int).Mul(big.NewInt(21000), head.BaseFee) + expected = supplyInfo{ + Issuance: &supplyInfoIssuance{ + Reward: (*hexutil.Big)(reward), + }, + Burn: &supplyInfoBurn{ + EIP1559: (*hexutil.Big)(burn), + }, + Number: 1, + Hash: head.Hash(), + ParentHash: head.ParentHash, + } + ) + + actual := out[expected.Number] + compareAsJSON(t, expected, actual) +} + +func TestSupplyWithdrawals(t *testing.T) { + var ( + config = *params.MergedTestChainConfig + gspec = &core.Genesis{ + Config: &config, + } + ) + + withdrawalsBlockGenerationFunc := func(b *core.BlockGen) { + b.SetPoS() + + b.AddWithdrawal(&types.Withdrawal{ + Validator: 42, + Address: common.Address{0xee}, + Amount: 1337, + }) + } + + out, chain, err := testSupplyTracer(t, gspec, withdrawalsBlockGenerationFunc) + if err != nil { + t.Fatalf("failed to test supply tracer: %v", err) + } + + var ( + head = chain.CurrentBlock() + expected = supplyInfo{ + Issuance: &supplyInfoIssuance{ + Withdrawals: (*hexutil.Big)(big.NewInt(1337000000000)), + }, + Number: 1, + Hash: head.Hash(), + ParentHash: head.ParentHash, + } + actual = out[expected.Number] + ) + + compareAsJSON(t, expected, actual) +} + +// Tests fund retrieval after contract's selfdestruct. +// Contract A calls contract B which selfdestructs, but B receives eth1 +// after the selfdestruct opcode executes from Contract A. +// Because Contract B is removed only at the end of the transaction +// the ether sent in between is burnt before Cancun hard fork. +func TestSupplySelfdestruct(t *testing.T) { + var ( + config = *params.TestChainConfig + + aa = common.HexToAddress("0x1111111111111111111111111111111111111111") + bb = common.HexToAddress("0x2222222222222222222222222222222222222222") + dad = common.HexToAddress("0x0000000000000000000000000000000000000dad") + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei)) + eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + + gspec = &core.Genesis{ + Config: &config, + BaseFee: big.NewInt(params.InitialBaseFee), + Alloc: types.GenesisAlloc{ + addr1: {Balance: eth1}, + aa: { + Code: common.FromHex("0x61face60f01b6000527322222222222222222222222222222222222222226000806002600080855af160008103603457600080fd5b60008060008034865af1905060008103604c57600080fd5b5050"), + // Nonce: 0, + Balance: big.NewInt(0), + }, + bb: { + Code: common.FromHex("0x6000357fface000000000000000000000000000000000000000000000000000000000000808203602f57610dad80ff5b5050"), + Nonce: 0, + Balance: eth1, + }, + }, + } + ) + + gspec.Config.TerminalTotalDifficulty = big.NewInt(0) + + signer := types.LatestSigner(gspec.Config) + + testBlockGenerationFunc := func(b *core.BlockGen) { + b.SetPoS() + + txdata := &types.LegacyTx{ + Nonce: 0, + To: &aa, + Value: gwei5, + Gas: 150000, + GasPrice: gwei5, + Data: []byte{}, + } + + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key1) + + b.AddTx(tx) + } + + // 1. Test pre Cancun + preCancunOutput, preCancunChain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc) + if err != nil { + t.Fatalf("Pre-cancun failed to test supply tracer: %v", err) + } + + // Check balance at state: + // 1. 0x0000...000dad has 1 ether + // 2. A has 0 ether + // 3. B has 0 ether + statedb, _ := preCancunChain.State() + if got, exp := statedb.GetBalance(dad), eth1; got.CmpBig(exp) != 0 { + t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", dad, got, exp) + } + if got, exp := statedb.GetBalance(aa), big.NewInt(0); got.CmpBig(exp) != 0 { + t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", aa, got, exp) + } + if got, exp := statedb.GetBalance(bb), big.NewInt(0); got.CmpBig(exp) != 0 { + t.Fatalf("Pre-cancun address \"%v\" balance, got %v exp %v\n", bb, got, exp) + } + + head := preCancunChain.CurrentBlock() + // Check live trace output + expected := supplyInfo{ + Burn: &supplyInfoBurn{ + EIP1559: (*hexutil.Big)(big.NewInt(55289500000000)), + Misc: (*hexutil.Big)(big.NewInt(5000000000)), + }, + Number: 1, + Hash: head.Hash(), + ParentHash: head.ParentHash, + } + + actual := preCancunOutput[expected.Number] + + compareAsJSON(t, expected, actual) + + // 2. Test post Cancun + cancunTime := uint64(0) + gspec.Config.ShanghaiTime = &cancunTime + gspec.Config.CancunTime = &cancunTime + + postCancunOutput, postCancunChain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc) + if err != nil { + t.Fatalf("Post-cancun failed to test supply tracer: %v", err) + } + + // Check balance at state: + // 1. 0x0000...000dad has 1 ether + // 3. A has 0 ether + // 3. B has 5 gwei + statedb, _ = postCancunChain.State() + if got, exp := statedb.GetBalance(dad), eth1; got.CmpBig(exp) != 0 { + t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", dad, got, exp) + } + if got, exp := statedb.GetBalance(aa), big.NewInt(0); got.CmpBig(exp) != 0 { + t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", aa, got, exp) + } + if got, exp := statedb.GetBalance(bb), gwei5; got.CmpBig(exp) != 0 { + t.Fatalf("Post-shanghai address \"%v\" balance, got %v exp %v\n", bb, got, exp) + } + + // Check live trace output + head = postCancunChain.CurrentBlock() + expected = supplyInfo{ + Burn: &supplyInfoBurn{ + EIP1559: (*hexutil.Big)(big.NewInt(55289500000000)), + }, + Number: 1, + Hash: head.Hash(), + ParentHash: head.ParentHash, + } + + actual = postCancunOutput[expected.Number] + + compareAsJSON(t, expected, actual) +} + +// Tests selfdestructing contract to send its balance to itself (burn). +// It tests both cases of selfdestructing succeeding and being reverted. +// - Contract A calls B and D. +// - Contract B selfdestructs and sends the eth1 to itself (Burn amount to be counted). +// - Contract C selfdestructs and sends the eth1 to itself. +// - Contract D calls C and reverts (Burn amount of C +// has to be reverted as well). +func TestSupplySelfdestructItselfAndRevert(t *testing.T) { + var ( + config = *params.TestChainConfig + + aa = common.HexToAddress("0x1111111111111111111111111111111111111111") + bb = common.HexToAddress("0x2222222222222222222222222222222222222222") + cc = common.HexToAddress("0x3333333333333333333333333333333333333333") + dd = common.HexToAddress("0x4444444444444444444444444444444444444444") + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + gwei5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.GWei)) + eth1 = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + eth2 = new(big.Int).Mul(common.Big2, big.NewInt(params.Ether)) + eth5 = new(big.Int).Mul(big.NewInt(5), big.NewInt(params.Ether)) + + gspec = &core.Genesis{ + Config: &config, + // BaseFee: big.NewInt(params.InitialBaseFee), + Alloc: types.GenesisAlloc{ + addr1: {Balance: eth1}, + aa: { + // Contract code in YUL: + // + // object "ContractA" { + // code { + // let B := 0x2222222222222222222222222222222222222222 + // let D := 0x4444444444444444444444444444444444444444 + + // // Call to Contract B + // let resB:= call(gas(), B, 0, 0x0, 0x0, 0, 0) + + // // Call to Contract D + // let resD := call(gas(), D, 0, 0x0, 0x0, 0, 0) + // } + // } + Code: common.FromHex("0x73222222222222222222222222222222222222222273444444444444444444444444444444444444444460006000600060006000865af160006000600060006000865af150505050"), + Balance: common.Big0, + }, + bb: { + // Contract code in YUL: + // + // object "ContractB" { + // code { + // let self := address() + // selfdestruct(self) + // } + // } + Code: common.FromHex("0x3080ff50"), + Balance: eth5, + }, + cc: { + Code: common.FromHex("0x3080ff50"), + Balance: eth1, + }, + dd: { + // Contract code in YUL: + // + // object "ContractD" { + // code { + // let C := 0x3333333333333333333333333333333333333333 + + // // Call to Contract C + // let resC := call(gas(), C, 0, 0x0, 0x0, 0, 0) + + // // Revert + // revert(0, 0) + // } + // } + Code: common.FromHex("0x73333333333333333333333333333333333333333360006000600060006000855af160006000fd5050"), + Balance: eth2, + }, + }, + } + ) + + gspec.Config.TerminalTotalDifficulty = big.NewInt(0) + + signer := types.LatestSigner(gspec.Config) + + testBlockGenerationFunc := func(b *core.BlockGen) { + b.SetPoS() + + txdata := &types.LegacyTx{ + Nonce: 0, + To: &aa, + Value: common.Big0, + Gas: 150000, + GasPrice: gwei5, + Data: []byte{}, + } + + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key1) + + b.AddTx(tx) + } + + output, chain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc) + if err != nil { + t.Fatalf("failed to test supply tracer: %v", err) + } + + // Check balance at state: + // 1. A has 0 ether + // 2. B has 0 ether, burned + // 3. C has 2 ether, selfdestructed but parent D reverted + // 4. D has 1 ether, reverted + statedb, _ := chain.State() + if got, exp := statedb.GetBalance(aa), common.Big0; got.CmpBig(exp) != 0 { + t.Fatalf("address \"%v\" balance, got %v exp %v\n", aa, got, exp) + } + if got, exp := statedb.GetBalance(bb), common.Big0; got.CmpBig(exp) != 0 { + t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp) + } + if got, exp := statedb.GetBalance(cc), eth1; got.CmpBig(exp) != 0 { + t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp) + } + if got, exp := statedb.GetBalance(dd), eth2; got.CmpBig(exp) != 0 { + t.Fatalf("address \"%v\" balance, got %v exp %v\n", bb, got, exp) + } + + // Check live trace output + block := chain.GetBlockByNumber(1) + + expected := supplyInfo{ + Burn: &supplyInfoBurn{ + EIP1559: (*hexutil.Big)(new(big.Int).Mul(block.BaseFee(), big.NewInt(int64(block.GasUsed())))), + Misc: (*hexutil.Big)(eth5), // 5ETH burned from contract B + }, + Number: 1, + Hash: block.Hash(), + ParentHash: block.ParentHash(), + } + + actual := output[expected.Number] + + compareAsJSON(t, expected, actual) +} + +func testSupplyTracer(t *testing.T, genesis *core.Genesis, gen func(*core.BlockGen)) ([]supplyInfo, *core.BlockChain, error) { + var ( + engine = beacon.New(ethash.NewFaker()) + ) + + traceOutputPath := filepath.ToSlash(t.TempDir()) + traceOutputFilename := path.Join(traceOutputPath, "supply.jsonl") + + // Load supply tracer + tracer, err := tracers.LiveDirectory.New("supply", json.RawMessage(fmt.Sprintf(`{"path":"%s"}`, traceOutputPath))) + if err != nil { + return nil, nil, fmt.Errorf("failed to create call tracer: %v", err) + } + + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), core.DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, engine, vm.Config{Tracer: tracer}, nil, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + _, blocks, _ := core.GenerateChainWithGenesis(genesis, engine, 1, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.Address{1}) + gen(b) + }) + + if n, err := chain.InsertChain(blocks); err != nil { + return nil, chain, fmt.Errorf("block %d: failed to insert into chain: %v", n, err) + } + + // Check and compare the results + file, err := os.OpenFile(traceOutputFilename, os.O_RDONLY, 0666) + if err != nil { + return nil, chain, fmt.Errorf("failed to open output file: %v", err) + } + defer file.Close() + + var output []supplyInfo + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + blockBytes := scanner.Bytes() + + var info supplyInfo + if err := json.Unmarshal(blockBytes, &info); err != nil { + return nil, chain, fmt.Errorf("failed to unmarshal result: %v", err) + } + + output = append(output, info) + } + + return output, chain, nil +} + +func compareAsJSON(t *testing.T, expected interface{}, actual interface{}) { + want, err := json.Marshal(expected) + if err != nil { + t.Fatalf("failed to marshal expected value to JSON: %v", err) + } + + have, err := json.Marshal(actual) + if err != nil { + t.Fatalf("failed to marshal actual value to JSON: %v", err) + } + + if !bytes.Equal(want, have) { + t.Fatalf("incorrect supply info: expected %s, got %s", string(want), string(have)) + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json b/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json new file mode 100644 index 0000000..549acb1 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json @@ -0,0 +1,67 @@ +{ + "genesis": { + "baseFeePerGas": "7", + "blobGasUsed": "0", + "difficulty": "0", + "excessBlobGas": "36306944", + "extraData": "0xd983010e00846765746888676f312e32312e308664617277696e", + "gasLimit": "15639172", + "hash": "0xc682259fda061bb9ce8ccb491d5b2d436cb73daf04e1025dd116d045ce4ad28c", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0xae1a5ba939a4c9ac38aabeff361169fb55a6fc2c9511457e0be6eff9514faec0", + "nonce": "0x0000000000000000", + "number": "315", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "stateRoot": "0x577f42ab21ccfd946511c57869ace0bdf7c217c36f02b7cd3459df0ed1cffc1a", + "timestamp": "1709626771", + "totalDifficulty": "1", + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "alloc": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x272e0528" + }, + "0x0c2c51a0990aee1d73c1228de158688341557508": { + "balance": "0xde0b6b3a7640000" + } + }, + "config": { + "chainId": 1337, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true + } + }, + "context": { + "number": "316", + "difficulty": "0", + "timestamp": "1709626785", + "gasLimit": "15654443", + "miner": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "7" + }, + "input": "0x03f8b1820539806485174876e800825208940c2c51a0990aee1d73c1228de1586883415575088080c083020000f842a00100c9fbdf97f747e85847b4f3fff408f89c26842f77c882858bf2c89923849aa00138e3896f3c27f2389147507f8bcec52028b0efca6ee842ed83c9158873943880a0dbac3f97a532c9b00e6239b29036245a5bfbb96940b9d848634661abee98b945a03eec8525f261c2e79798f7b45a5d6ccaefa24576d53ba5023e919b86841c0675", + "result": { + "from": "0x0c2c51a0990aee1d73c1228de158688341557508", + "gas": "0x5208", + "gasUsed": "0x5208", + "to": "0x0c2c51a0990aee1d73c1228de158688341557508", + "input": "0x", + "value": "0x0", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/create.json b/eth/tracers/internal/tracetest/testdata/call_tracer/create.json new file mode 100644 index 0000000..df0b287 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/create.json @@ -0,0 +1,58 @@ +{ + "context": { + "difficulty": "3755480783", + "gasLimit": "5401723", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "number": "2294702", + "timestamp": "1513676146" + }, + "genesis": { + "alloc": { + "0x13e4acefe6a6700604929946e70e6443e4e73447": { + "balance": "0xcf3e0938579f000", + "code": "0x", + "nonce": "9", + "storage": {} + }, + "0x7dc9c9730689ff0b0fd506c67db815f12d90a448": { + "balance": "0x0", + "code": "0x", + "nonce": "0", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3757315409", + "extraData": "0x566961425443", + "gasLimit": "5406414", + "hash": "0xae107f592eebdd9ff8d6ba00363676096e6afb0e1007a7d3d0af88173077378d", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "mixHash": "0xc927aa05a38bc3de864e95c33b3ae559d3f39c4ccd51cef6f113f9c50ba0caf1", + "nonce": "0x93363bbd2c95f410", + "number": "2294701", + "stateRoot": "0x6b6737d5bde8058990483e915866bd1578014baeff57bd5e4ed228a2bfad635c", + "timestamp": "1513676127", + "totalDifficulty": "7160808139332585" + }, + "input": "0xf907ef098504e3b29200830897be8080b9079c606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a1129a01060f46676a5dff6f407f0f51eb6f37f5c8c54e238c70221e18e65fc29d3ea65a0557b01c50ff4ffaac8ed6e5d31237a4ecbac843ab1bfe8bb0165a0060df7c54f", + "result": { + "from": "0x13e4acefe6a6700604929946e70e6443e4e73447", + "gas": "0x897be", + "gasUsed": "0x897be", + "input": "0x606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a11", + "output": "0x606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029", + "to": "0x7dc9c9730689ff0b0fd506c67db815f12d90a448", + "type": "CREATE", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json b/eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json new file mode 100644 index 0000000..9756160 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json @@ -0,0 +1,409 @@ +{ + "context": { + "difficulty": "117066904", + "gasLimit": "4712384", + "miner": "0x1977c248e1014cc103929dd7f154199c916e39ec", + "number": "25001", + "timestamp": "1479891545" + }, + "genesis": { + "alloc": { + "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38": { + "balance": "0x0", + "code": "0x606060405236156100825760e060020a600035046302d05d3f811461008a5780630accce061461009c5780631ab9075a146100c757806331ed274614610102578063645a3b7214610133578063772fdae314610155578063a7f4377914610180578063ae5f80801461019e578063c9bded21146101ea578063f905c15a14610231575b61023a610002565b61023c600054600160a060020a031681565b61023a600435602435604435606435608435600254600160a060020a03166000141561024657610002565b61023a600435600254600160a060020a03166000148015906100f8575060025433600160a060020a03908116911614155b156102f457610002565b61023a60043560243560443560643560843560a43560c435600254600160a060020a03166000141561031657610002565b61023a600435602435600254600160a060020a0316600014156103d057610002565b61023a600435602435604435606435608435600254600160a060020a03166000141561046157610002565b61023a60025433600160a060020a0390811691161461051657610002565b61023a6004356024356044356060828152600160a060020a0382169060ff8516907fa6c2f0913db6f79ff0a4365762c61718973b3413d6e40382e704782a9a5099f690602090a3505050565b61023a600435602435600160a060020a038116606090815260ff8316907fee6348a7ec70f74e3d6cba55a53e9f9110d180d7698e9117fc466ae29a43e34790602090a25050565b61023c60035481565b005b6060908152602090f35b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061029d57610002565b60408051858152602081018390528151600160a060020a03858116939087169260ff8a16927f5a690ecd0cb15c1c1fd6b6f8a32df0d4f56cb41a54fea7e94020f013595de796929181900390910190a45050505050565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061036d57610002565b6040805186815260208101869052808201859052606081018490529051600160a060020a03831691889160ff8b16917fd65d9ddafbad8824e2bbd6f56cc9f4ac27ba60737035c10a321ea2f681c94d47919081900360800190a450505050505050565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061042757610002565b60408051828152905183917fa9c6cbc4bd352a6940479f6d802a1001550581858b310d7f68f7bea51218cda6919081900360200190a25050565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506104b857610002565b80600160a060020a031684600160a060020a03168660ff167f69bdaf789251e1d3a0151259c0c715315496a7404bce9fd0b714674685c2cab78686604051808381526020018281526020019250505060405180910390a45050505050565b600254600160a060020a0316ff", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0x2cccf5e0538493c235d1c5ef6580f77d99e91396": { + "balance": "0x0", + "code": "0x606060405236156100775760e060020a600035046302d05d3f811461007f57806313bc6d4b146100915780633688a877146100b95780635188f9961461012f5780637eadc976146101545780638ad79680146101d3578063a43e04d814610238578063a7f437791461025e578063e16c7d981461027c575b61029f610002565b6102a1600054600160a060020a031681565b6102be600435600160a060020a03811660009081526002602052604090205460ff165b919050565b6102d26004356040805160208181018352600080835284815260038252835190849020805460026001821615610100026000190190911604601f8101849004840283018401909552848252929390929183018282801561037d5780601f106103525761010080835404028352916020019161037d565b61029f6004356024356000805433600160a060020a039081169116146104a957610002565b61034060043560008181526001602090815260408083205481517ff905c15a0000000000000000000000000000000000000000000000000000000081529151600160a060020a03909116928392839263f905c15a92600483810193919291829003018189876161da5a03f1156100025750506040515195945050505050565b60408051602060248035600481810135601f810185900485028601850190965285855261029f9581359591946044949293909201918190840183828082843750949650505050505050600054600160a060020a0390811633909116146104f657610002565b61029f6004355b600080548190600160a060020a0390811633909116146105a457610002565b61029f60005433600160a060020a0390811691161461072957610002565b6102a1600435600081815260016020526040902054600160a060020a03166100b4565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156103325780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161036057829003601f168201915b505050505090506100b4565b506000828152600160208181526040808420805473ffffffffffffffffffffffffffffffffffffffff191686179055600160a060020a038581168086526002909352818520805460ff191690941790935580517f1ab9075a0000000000000000000000000000000000000000000000000000000081523090931660048401525184939192631ab9075a926024828101939192829003018183876161da5a03f11561000257505060408051602081018690528082019290925243606083015260808083526003908301527f414444000000000000000000000000000000000000000000000000000000000060a0830152517f8ac68d4e97d65912f220b4c5f87978b8186320a5e378c1369850b5b5f90323d39181900360c00190a15b505050565b600083815260016020526040902054600160a060020a03838116911614156104d0576104a4565b600083815260016020526040812054600160a060020a031614610389576103898361023f565b600082815260036020908152604082208054845182855293839020919360026001831615610100026000190190921691909104601f90810184900483019391929186019083901061056a57805160ff19168380011785555b5061059a9291505b808211156105a05760008155600101610556565b8280016001018555821561054e579182015b8281111561054e57825182600050559160200191906001019061057c565b50505050565b5090565b600083815260016020526040812054600160a060020a031614156105c757610002565b50506000818152600160205260408082205481517fa7f437790000000000000000000000000000000000000000000000000000000081529151600160a060020a0391909116928392839263a7f4377992600483810193919291829003018183876161da5a03f11561000257505050600160005060008460001916815260200190815260200160002060006101000a815490600160a060020a0302191690556002600050600083600160a060020a0316815260200190815260200160002060006101000a81549060ff02191690557f8ac68d4e97d65912f220b4c5f87978b8186320a5e378c1369850b5b5f90323d383834360405180806020018560001916815260200184600160a060020a03168152602001838152602001828103825260038152602001807f44454c000000000000000000000000000000000000000000000000000000000081526020015060200194505050505060405180910390a1505050565b600054600160a060020a0316ff", + "nonce": "1", + "storage": { + "0x0684ac65a9fa32414dda56996f4183597d695987fdb82b145d722743891a6fe8": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "0x1cd76f78169a420d99346e3501dd3e541622c38a226f9b63e01cfebc69879dc7": "0x000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f", + "0x8e54a4494fe5da016bfc01363f4f6cdc91013bb5434bd2a4a3359f13a23afa2f": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf", + "0x94edf7f600ba56655fd65fca1f1424334ce369326c1dc3e53151dcd1ad06bc13": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xbbee47108b275f55f98482c6800f6372165e88b0330d3f5dae6419df4734366c": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "0xd38c0c4e84de118cfdcc775130155d83b8bbaaf23dc7f3c83a626b10473213bd": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xfb3aa5c655c2ec9d40609401f88d505d1da61afaa550e36ef5da0509ada257ba": "0x0000000000000000000000007986bad81f4cbd9317f5a46861437dae58d69113" + } + }, + "0x3e9286eafa2db8101246c2131c09b49080d00690": { + "balance": "0x0", + "code": "0x606060405236156100cf5760e060020a600035046302d05d3f81146100d7578063056d4470146100e957806316c66cc61461010c5780631ab9075a146101935780633ae1005c146101ce57806358541662146101fe5780635ed61af014610231578063644e3b791461025457806384dbac3b146102db578063949ae479146102fd5780639859387b14610321578063a7f4377914610340578063ab03fc261461035e578063e8161b7814610385578063e964d4e114610395578063f905c15a146103a5578063f92eb774146103ae575b6103be610002565b6103c0600054600160a060020a031681565b6103be6004356002546000908190600160a060020a031681141561040357610002565b6103dd60043560006108365b6040805160025460e360020a631c2d8fb30282527f636f6e747261637464620000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b6103be600435600254600160a060020a03166000148015906101c4575060025433600160a060020a03908116911614155b1561088d57610002565b6103be600435602435604435606435600254600090819081908190600160a060020a03168114156108af57610002565b6103c0600435602435604435606435608435600254600090819081908190600160a060020a03168114156110e857610002565b6103be6004356002546000908190600160a060020a03168114156115ec57610002565b6103c06004356000611b635b6040805160025460e360020a631c2d8fb30282527f6d61726b6574646200000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b6103be600435602435600254600160a060020a031660001415611bb557610002565b6103be600435602435600254600090600160a060020a0316811415611d2e57610002565b6103be600435600254600160a060020a031660001415611fc657610002565b6103be60025433600160a060020a0390811691161461207e57610002565b6103be600435602435604435600254600090600160a060020a031681141561208c57610002565b6103dd60043560006124b8610260565b6103c0600435600061250a610118565b6103f160035481565b6103f16004356000612561610260565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061046557610002565b8291506104e55b6040805160025460e360020a631c2d8fb30282527f63706f6f6c00000000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f115610002575050604051519150505b90565b600160a060020a031663b2206e6d83600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fb2206e6d0000000000000000000000000000000000000000000000000000000082526004820152600160a060020a038816602482015290516044808301935060209282900301816000876161da5a03f11561000257505060405151915061059b90506106ba565b600160a060020a031663d5b205ce83600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a636ad902e7028252600160a060020a0390811660048301526024820187905288166044820152905160648281019350600092829003018183876161da5a03f115610002575050506107355b6040805160025460e360020a631c2d8fb30282527f6c6f676d6772000000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b50826120ee5b6040805160025460e360020a631c2d8fb30282527f6163636f756e7463746c0000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b600160a060020a0316630accce06600684600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d02825291519192899290916336da446891600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150866040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050505050565b600160a060020a03166316c66cc6836040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051519150505b919050565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061091157610002565b87935061091c610260565b600160a060020a031663bdbdb08685600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fbdbdb0860000000000000000000000000000000000000000000000000000000082526004820152602481018a905290516044808301935060209282900301816000876161da5a03f1156100025750506040515193506109ca90506106ba565b600160a060020a03166381982a7a8885876040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f11561000257505050610a3661046c565b600160a060020a03166308636bdb85600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517f08636bdb000000000000000000000000000000000000000000000000000000008252600482015260248101889052604481019290925251606482810192602092919082900301816000876161da5a03f11561000257505060408051805160e160020a630a5d50db028252600482018190529151919450600160a060020a03871692506314baa1b6916024828101926000929190829003018183876161da5a03f11561000257505050610b3561046c565b600160a060020a0316630a3b6ede85600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63051db76f0282526004820152600160a060020a038d16602482015290516044808301935060209282900301816000876161da5a03f115610002575050604051519150610bd590506106ba565b600160a060020a031663d5b205ce87838b6040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f11561000257505050610c41610118565b600160a060020a031663988db79c888a6040518360e060020a0281526004018083600160a060020a0316815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050610ca5610260565b600160a060020a031663f4f2821b896040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050610d6f5b6040805160025460e360020a631c2d8fb30282527f747261646564620000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b600160a060020a0316635f539d69896040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050610dc2610639565b600160a060020a0316630accce06600386600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6315b1ea01028252915191928e928e9263ad8f500891600482810192602092919082900301816000876161da5a03f11561000257505050604051805190602001506040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050610ec5610639565b600160a060020a0316630accce06600386600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6315b1ea01028252915191928e928d9263ad8f500891600482810192602092919082900301816000876161da5a03f11561000257505050604051805190602001506040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050610fc8610639565b600160a060020a031663645a3b7285600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151905061101e610260565b600160a060020a031663f92eb77488600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f115610002575050505050505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061114a57610002565b604051600254600160a060020a0316908a908a908a908a908a90611579806125b38339018087600160a060020a0316815260200186600160a060020a03168152602001856000191681526020018481526020018381526020018281526020019650505050505050604051809103906000f092506111c5610118565b600160a060020a031663b9858a288a856040518360e060020a0281526004018083600160a060020a0316815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611229610260565b600160a060020a0316635188f99689856040518360e060020a028152600401808360001916815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611288610260565b600160a060020a031663bdbdb08689896040518360e060020a0281526004018083600019168152602001828152602001925050506020604051808303816000876161da5a03f1156100025750506040515192506112e590506106ba565b600160a060020a03166346d88e7d8a858a6040518460e060020a0281526004018084600160a060020a0316815260200183600160a060020a0316815260200182815260200193505050506000604051808303816000876161da5a03f115610002575050506113516106ba565b600160a060020a03166381982a7a8a84866040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f115610002575050506113bd61046c565b600160a060020a0316632b58469689856040518360e060020a028152600401808360001916815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f1156100025750505061141c61046c565b600160a060020a03166308636bdb8984866040518460e060020a028152600401808460001916815260200183815260200182600160a060020a0316815260200193505050506020604051808303816000876161da5a03f11561000257505060408051805160e160020a630a5d50db028252600482018190529151919350600160a060020a03861692506314baa1b6916024828101926000929190829003018183876161da5a03f115610002575050506114d3610639565b6040805160e160020a630566670302815260016004820152602481018b9052600160a060020a0386811660448301528c811660648301526000608483018190529251931692630accce069260a480840193919291829003018183876161da5a03f11561000257505050611544610639565b600160a060020a031663645a3b728961155b610260565b600160a060020a031663f92eb7748c6040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448084019360009350829003018183876161da5a03f1156100025750939a9950505050505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061164e57610002565b82915061165961046c565b600160a060020a0316630a3b6ede83600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63051db76f0282526004820152600160a060020a038816602482015290516044808301935060209282900301816000876161da5a03f1156100025750506040515191506116f990506106ba565b600160a060020a031663d5b205ce83600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a636ad902e7028252600160a060020a0390811660048301526024820187905288166044820152905160648281019350600092829003018183876161da5a03f1156100025750505061179b6106ba565b600160a060020a031663d653078983600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517ff1ff78a0000000000000000000000000000000000000000000000000000000008252915191929163f1ff78a09160048181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150866040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f1156100025750505061189f610260565b600160a060020a031663f4f2821b846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506118f2610118565b600160a060020a031663f4f2821b846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050611945610639565b600160a060020a0316630accce06600484600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d02825291519192899290916336da44689181870191602091908190038801816000876161da5a03f115610002575050506040518051906020015060006040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050611a48610639565b600160a060020a031663645a3b7283600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519050611a9e610260565b600160a060020a031663f92eb77486600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f11561000257505050505050565b600160a060020a03166381738c59836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f1156100025750506040515115159050611c1757610002565b611c1f610260565b600160a060020a03166338a699a4836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f11561000257505060405151159050611c7457610002565b611c7c610260565b600160a060020a0316632243118a836040518260e060020a02815260040180826000191681526020019150506000604051808303816000876161da5a03f11561000257505050611cca610639565b600160a060020a031663ae5f8080600184846040518460e060020a028152600401808481526020018360001916815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f115610002575050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f1156100025750506040515115159050611d9057610002565b5081611d9a610260565b600160a060020a031663581d5d6084846040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506000604051808303816000876161da5a03f11561000257505050611df5610639565b600160a060020a0316630accce06600283600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a630566670302825260048201949094526024810193909352600160a060020a038816604484015260006064840181905260848401819052905160a4808501949293509091829003018183876161da5a03f11561000257505050611eab610639565b600160a060020a031663645a3b7282600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519050611f01610260565b600160a060020a031663f92eb77485600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f11561000257505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061202857610002565b612030610118565b600160a060020a0316639859387b826040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f1156100025750505050565b600254600160a060020a0316ff5b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f11561000257505060405151151590506106b457610002565b600160a060020a031663d65307898383600160a060020a031663f1ff78a06040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fd6530789000000000000000000000000000000000000000000000000000000008252600160a060020a039485166004830152602482015292891660448401525160648381019360009350829003018183876161da5a03f115610002575050506121a5610118565b600160a060020a031663f4f2821b856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506121f8610cf4565b600160a060020a031663f4f2821b856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f1156100025750505061224b610639565b600160a060020a0316630accce06600583600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d028252915191928a9290916336da446891600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150886040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f1156100025750505080600160a060020a031663ea71b02d6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151600160a060020a031660001490506124b25761239f610639565b600160a060020a0316630accce06600583600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fea71b02d000000000000000000000000000000000000000000000000000000008252915191928a92909163ea71b02d91600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150886040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f115610002575050505b50505050565b600160a060020a03166338a699a4836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b600160a060020a031663213fe2b7836040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b600160a060020a031663f92eb774836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f115610002575050604051519150610888905056606060405260405160c08061157983396101206040819052825160805160a051935160e0516101005160008054600160a060020a03199081163317909155600180546005805484168817905560048a90556006869055600b8590556008849055909116861760a060020a60ff02191690554360038190556002558686526101408390526101608190529396929594919390929091600160a060020a033016917f76885d242fb71c6f74a7e717416e42eff4d96faf54f6de75c6a0a6bbd8890c6b91a230600160a060020a03167fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff600b600050546040518082815260200191505060405180910390a250505050505061145e8061011b6000396000f3606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "16", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0x70c9217d814985faef62b124420f8dfbddd96433": { + "balance": "0x4ef436dcbda6cd4a", + "code": "0x", + "nonce": "1634", + "storage": {} + }, + "0x7986bad81f4cbd9317f5a46861437dae58d69113": { + "balance": "0x0", + "code": "0x6060604052361561008d5760e060020a600035046302d05d3f811461009557806316c66cc6146100a75780631ab9075a146100d7578063213fe2b7146101125780639859387b1461013f578063988db79c1461015e578063a7f4377914610180578063b9858a281461019e578063c8e40fbf146101c0578063f4f2821b146101e8578063f905c15a14610209575b610212610002565b610214600054600160a060020a031681565b600160a060020a0360043581811660009081526005602052604081205461023193168114610257575060016101e3565b610212600435600254600160a060020a0316600014801590610108575060025433600160a060020a03908116911614155b1561025f57610002565b610214600435600160a060020a03811660009081526004602052604081205460ff16151561027557610002565b610212600435600254600160a060020a03166000141561029b57610002565b610212600435602435600254600160a060020a03166000141561050457610002565b61021260025433600160a060020a0390811691161461056757610002565b610212600435602435600254600160a060020a03166000141561057557610002565b610231600435600160a060020a03811660009081526004602052604090205460ff165b919050565b610212600435600254600090600160a060020a031681141561072057610002565b61024560035481565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060006101e3565b60028054600160a060020a031916821790555b50565b50600160a060020a038181166000908152600460205260409020546101009004166101e3565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506102fe57610002565b600160a060020a03811660009081526004602052604090205460ff161515610272576040516104028061092e833901809050604051809103906000f06004600050600083600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600083600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555050565b600160a060020a03821660009081526004602052604090205460ff1615156104725760405161040280610d30833901809050604051809103906000f06004600050600084600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff021916908302179055505b600160a060020a03828116600090815260046020819052604080518184205460e060020a630a3b0a4f02825286861693820193909352905161010090920490931692630a3b0a4f926024828101939192829003018183876161da5a03f11561000257505050600160a060020a03811660009081526006602052604090208054600160a060020a031916831790555b5050565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506103b957610002565b600254600160a060020a0316ff5b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506105d857610002565b600160a060020a03821660009081526004602052604090205460ff1615156106915760405161040280611132833901809050604051809103906000f06004600050600084600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff021916908302179055505b600160a060020a03828116600090815260046020819052604080518184205460e060020a630a3b0a4f02825286861693820193909352905161010090920490931692630a3b0a4f926024828101939192829003018183876161da5a03f11561000257505050600160a060020a031660009081526005602052604090208054600160a060020a0319169091179055565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f115610002575050604051511515905061078357610002565b50600160a060020a0381811660009081526005602090815260408083205490931680835260049091529190205460ff161561080f576040600081812054825160e260020a632e72bafd028152600160a060020a03868116600483015293516101009092049093169263b9caebf4926024828101939192829003018183876161da5a03f115610002575050505b600160a060020a03828116600090815260056020526040812054909116146108545760406000908120600160a060020a0384169091528054600160a060020a03191690555b50600160a060020a0381811660009081526006602090815260408083205490931680835260049091529190205460ff16156108e657600160a060020a038181166000908152604080518183205460e260020a632e72bafd028252868516600483015291516101009092049093169263b9caebf4926024828101939192829003018183876161da5a03f115610002575050505b600160a060020a03828116600090815260066020526040812054909116146105005760406000908120600160a060020a0384169091528054600160a060020a0319169055505056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056", + "nonce": "7", + "storage": { + "0xffc4df2d4f3d2cffad590bed6296406ab7926ca9e74784f74a95191fa069a174": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433" + } + }, + "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f": { + "balance": "0x0", + "code": "0x606060405236156100ae5760e060020a600035046302d05d3f81146100b65780631ab9075a146100c85780632b68bb2d146101035780634cc927d7146101c557806351a34eb81461028e57806356ccb6f0146103545780635928d37f1461041d578063599efa6b146104e9578063759297bb146105b2578063771d50e11461067e578063a7f4377914610740578063f905c15a1461075e578063f92eb77414610767578063febf661214610836575b610902610002565b610904600054600160a060020a031681565b610902600435600254600160a060020a03166000148015906100f9575060025433600160a060020a03908116911614155b1561092057610002565b60025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b02606452610902916000918291600160a060020a03169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051511515905061094257610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610a0d57610002565b61090260043560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610ae957610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610bbc57610002565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610c9657610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610de057610002565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610ebb57610002565b60025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b02606452610902916000918291600160a060020a03169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f9e57610002565b61090260025433600160a060020a0390811691161461106957610002565b61090e60035481565b61090e60043560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750506040805180517ff92eb774000000000000000000000000000000000000000000000000000000008252600482018790529151919350600160a060020a038416925063f92eb774916024828101926020929190829003018188876161da5a03f11561000257505060405151949350505050565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051511515905061107757610002565b005b6060908152602090f35b60408051918252519081900360200190f35b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5ed61af000000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152925190959286169350635ed61af092602483810193919291829003018183876161da5a03f115610002575050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517fab03fc2600000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015260248301899052808816604484015292519095928616935063ab03fc2692606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f949ae47900000000000000000000000000000000000000000000000000000000825233600160a060020a0390811660048401526024830188905292519095928616935063949ae47992604483810193919291829003018183876161da5a03f11561000257505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f46d88e7d000000000000000000000000000000000000000000000000000000008252600160a060020a0380891660048401523381166024840152604483018890529251909592861693506346d88e7d92606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5315cdde00000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152808a16602484015260448301889052925190959286169350635315cdde92606483810193919291829003018183876161da5a03f115610002575050604080517f5928d37f00000000000000000000000000000000000000000000000000000000815233600160a060020a03908116600483015287166024820152604481018690529051635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517fe68e401c00000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015280891660248401526044830188905292519095928616935063e68e401c92606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5152f381000000000000000000000000000000000000000000000000000000008252600160a060020a03808a1660048401528089166024840152604483018890523381166064840152925190959286169350635152f38192608483810193919291829003018183876161da5a03f115610002575050505050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f056d447000000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015292519095928616935063056d447092602483810193919291829003018183876161da5a03f115610002575050505050565b600254600160a060020a0316ff5b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f3ae1005c00000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152808a166024840152808916604484015260648301889052925190959286169350633ae1005c92608483810193919291829003018183876161da5a03f11561000257505050505050505056", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0xc212e03b9e060e36facad5fd8f4435412ca22e6b": { + "balance": "0x0", + "code": "0x606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000006195", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x5842545553440000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000008ac7230489e80000", + "0x000000000000000000000000000000000000000000000000000000000000000b": "0x0000000000000000000000000000000000000000000000283c7b9181eca20000" + } + }, + "0xcf00ffd997ad14939736f026006498e3f099baaf": { + "balance": "0x0", + "code": "0x606060405236156100cf5760e060020a600035046302d05d3f81146100d7578063031e7f5d146100e95780631ab9075a1461010b5780632243118a1461014657806327aad68a1461016557806338a699a4146101da5780635188f996146101f8578063581d5d601461021e57806381738c5914610246578063977da54014610269578063a07421ce14610288578063a7f43779146102be578063bdbdb086146102dc578063e1c7111914610303578063f4f2821b14610325578063f905c15a1461034a578063f92eb77414610353575b610387610002565b610389600054600160a060020a031681565b610387600435602435600254600160a060020a0316600014156103a857610002565b610387600435600254600160a060020a031660001480159061013c575060025433600160a060020a03908116911614155b1561042957610002565b610387600435600254600160a060020a03166000141561044b57610002565b6102ac60043560008181526004602081815260408320547f524d81d3000000000000000000000000000000000000000000000000000000006060908152610100909104600160a060020a031692839263524d81d3926064928188876161da5a03f1156100025750506040515192506103819050565b61039c60043560008181526004602052604090205460ff165b919050565b6103876004356024356002546000908190600160a060020a031681141561079457610002565b61038760043560243560025460009081908190600160a060020a031681141561080457610002565b61038960043560008181526004602052604081205460ff1615156109e357610002565b610387600435600254600160a060020a0316600014156109fb57610002565b600435600090815260096020526040902054670de0b6b3a764000090810360243502045b60408051918252519081900360200190f35b61038760025433600160a060020a03908116911614610a9257610002565b600435600090815260086020526040902054670de0b6b3a7640000602435909102046102ac565b610387600435602435600254600160a060020a031660001415610aa057610002565b61038760043560025460009081908190600160a060020a0316811415610b3657610002565b6102ac60035481565b6102ac600435600081815260076020908152604080832054600690925290912054670de0b6b3a76400000204805b50919050565b005b600160a060020a03166060908152602090f35b15156060908152602090f35b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506103fe57610002565b60008281526004602052604090205460ff16151561041b57610002565b600860205260406000205550565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506104a157610002565b604080516000838152600460205291909120805460ff1916600117905561040280610de2833901809050604051809103906000f0600460005060008360001916815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555066470de4df8200006008600050600083600019168152602001908152602001600020600050819055506703782dace9d9000060096000506000836000191681526020019081526020016000206000508190555050565b600460005060008560001916815260200190815260200160002060005060000160019054906101000a9004600160a060020a0316915081600160a060020a031663524d81d36040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151821415905061060057838152600660209081526040808320839055600790915281208190555b81600160a060020a0316630a3b0a4f846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050600160a060020a038316808252600560209081526040808420879055805160e160020a6364a81ff102815290518694670de0b6b3a7640000949363c9503fe29360048181019492939183900301908290876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b939160048181019260209290919082900301816000876161da5a03f11561000257505050604051805190602001500204600660005060008660001916815260200190815260200160002060008282825054019250508190555080600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050506040518051906020015060076000506000866000191681526020019081526020016000206000828282505401925050819055505b50505050565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f11561000257505060405151151590506107e957610002565b8381526004602052604081205460ff16151561056657610002565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f115610002575050604051511515905061085957610002565b849250670de0b6b3a764000083600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575060408051805160e160020a6364a81ff102825291519189028590049650600481810192602092909190829003018188876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b9391600481810192602092909190829003018189876161da5a03f115610002575050506040518051906020015002049050806006600050600085600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750604080518051855260208681528286208054989098039097557f2e94420f00000000000000000000000000000000000000000000000000000000815290518896600483810193919291829003018187876161da5a03f115610002575050604080515183526020939093525020805490910190555050505050565b60409020546101009004600160a060020a03166101f3565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f1156100025750506040515115159050610a5157610002565b60008181526004602052604090205460ff161515610a6e57610002565b6040600020805474ffffffffffffffffffffffffffffffffffffffffff1916905550565b600254600160a060020a0316ff5b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f1156100025750506040515115159050610af657610002565b60008281526004602052604090205460ff161515610b1357610002565b670de0b6b3a7640000811115610b2857610002565b600960205260406000205550565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f1156100025750506040515115159050610b8b57610002565b600160a060020a038416815260056020908152604080832054808452600490925282205490935060ff161515610bc057610002565b600460005060008460001916815260200190815260200160002060005060000160019054906101000a9004600160a060020a0316915081600160a060020a031663b9caebf4856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506005600050600085600160a060020a0316815260200190815260200160002060005060009055839050600082600160a060020a031663524d81d36040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519190911115905061078e57670de0b6b3a764000081600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b939160048181019260209290919082900301816000876161da5a03f11561000257505050604051805190602001500204600660005060008560001916815260200190815260200160002060008282825054039250508190555080600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050506040518051906020015060076000506000856000191681526020019081526020016000206000828282505403925050819055505050505056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056", + "nonce": "3", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x3571d73f14f31a1463bd0a2f92f7fde1653d4e1ead7aedf4b0a5df02f16092ab": "0x0000000000000000000000000000000000000000000007d634e4c55188be0000", + "0x4e64fe2d1b72d95a0a31945cc6e4f4e524ac5ad56d6bd44a85ec7bc9cc0462c0": "0x000000000000000000000000000000000000000000000002b5e3af16b1880000" + } + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "117124093", + "extraData": "0xd5830105008650617269747986312e31322e31826d61", + "gasLimit": "4707788", + "hash": "0xad325e4c49145fb7a4058a68ac741cc8607a71114e23fc88083c7e881dd653e7", + "miner": "0x00714b9ac97fd6bd9325a059a70c9b9fa94ce050", + "mixHash": "0x0af918f65cb4af04b608fc1f14a849707696986a0e7049e97ef3981808bcc65f", + "nonce": "0x38dee147326a8d40", + "number": "25000", + "stateRoot": "0xc5d6bbcd46236fcdcc80b332ffaaa5476b980b01608f9708408cfef01b58bd5b", + "timestamp": "1479891517", + "totalDifficulty": "1895410389427" + }, + "input": "0xf88b8206628504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb80000000000000000000000000000000000000000000000280faf689c35ac00002aa0a7ee5b7877811bf671d121b40569462e722657044808dc1d6c4f1e4233ec145ba0417e7543d52b65738d9df419cbe40a708424f4d54b0fc145c0a64545a2bb1065", + "result": { + "calls": [ + { + "from": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "gas": "0x31217", + "gasUsed": "0x334", + "input": "0xe16c7d98636f6e7472616374617069000000000000000000000000000000000000000000", + "output": "0x000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x2a68d", + "gasUsed": "0x334", + "input": "0xe16c7d98636f6e747261637463746c000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x23ac9", + "gasUsed": "0x334", + "input": "0xe16c7d98636f6e7472616374646200000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000007986bad81f4cbd9317f5a46861437dae58d69113", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x23366", + "gasUsed": "0x273", + "input": "0x16c66cc6000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x7986bad81f4cbd9317f5a46861437dae58d69113", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x29f35", + "gasUsed": "0xf8d", + "input": "0x16c66cc6000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x28a9e", + "gasUsed": "0x334", + "input": "0xe16c7d98636f6e747261637463746c000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x21d79", + "gasUsed": "0x24d", + "input": "0x13bc6d4b000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x2165b", + "gasUsed": "0x334", + "input": "0xe16c7d986d61726b65746462000000000000000000000000000000000000000000000000", + "output": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1a8e8", + "gasUsed": "0x24d", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1a2c6", + "gasUsed": "0x3cb", + "input": "0xc9503fe2", + "output": "0x0000000000000000000000000000000000000000000000008ac7230489e80000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x19b72", + "gasUsed": "0x3cb", + "input": "0xc9503fe2", + "output": "0x0000000000000000000000000000000000000000000000008ac7230489e80000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x19428", + "gasUsed": "0x305", + "input": "0x6f265b93", + "output": "0x0000000000000000000000000000000000000000000000283c7b9181eca20000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x18d45", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1734e", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x20ee1", + "gasUsed": "0x5374", + "input": "0x581d5d60000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b0000000000000000000000000000000000000000000000280faf689c35ac0000", + "to": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1b6c1", + "gasUsed": "0x334", + "input": "0xe16c7d986c6f676d67720000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1af69", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "gas": "0x143a5", + "gasUsed": "0x24d", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1a91d", + "gasUsed": "0x12fa", + "input": "0x0accce0600000000000000000000000000000000000000000000000000000000000000025842545553440000000000000000000000000000000000000000000000000000000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "to": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x19177", + "gasUsed": "0x334", + "input": "0xe16c7d986c6f676d67720000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x18a22", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x18341", + "gasUsed": "0x334", + "input": "0xe16c7d986d61726b65746462000000000000000000000000000000000000000000000000", + "output": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x17bec", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1764e", + "gasUsed": "0x45c", + "input": "0xf92eb7745842545553440000000000000000000000000000000000000000000000000000", + "output": "0x00000000000000000000000000000000000000000000002816d180e30c390000", + "to": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "gas": "0x108ba", + "gasUsed": "0x24d", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x16e62", + "gasUsed": "0xebb", + "input": "0x645a3b72584254555344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002816d180e30c390000", + "to": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x283b9", + "gasUsed": "0xc51c", + "input": "0x949ae479000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b0000000000000000000000000000000000000000000000280faf689c35ac0000", + "to": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "gas": "0x30b4a", + "gasUsed": "0xedb7", + "input": "0x51a34eb80000000000000000000000000000000000000000000000280faf689c35ac0000", + "to": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0x70c9217d814985faef62b124420f8dfbddd96433", + "gas": "0x3d090", + "gasUsed": "0x1810b", + "input": "0x51a34eb80000000000000000000000000000000000000000000000280faf689c35ac0000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json b/eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json new file mode 100644 index 0000000..6a2cda7 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json @@ -0,0 +1,97 @@ +{ + "context": { + "difficulty": "31927752", + "gasLimit": "4707788", + "miner": "0x5659922ce141eedbc2733678f9806c77b4eebee8", + "number": "11495", + "timestamp": "1479735917" + }, + "genesis": { + "alloc": { + "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff": { + "balance": "0x0", + "code": "0x606060405236156100825760e060020a60003504630a0313a981146100875780630a3b0a4f146101095780630cd40fea1461021257806329092d0e1461021f5780634cd06a5f146103295780635dbe47e8146103395780637a9e5410146103d9578063825db5f7146103e6578063a820b44d146103f3578063efa52fb31461047a575b610002565b34610002576104fc600435600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a26333556e849091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f415610002575050604051519150505b919050565b346100025761051060043560006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a2637d65837a9091336000604051602001526040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515115905061008257604080517f21ce24d4000000000000000000000000000000000000000000000000000000008152600060048201819052600160a060020a038416602483015291517342b02b5deeb78f34cd5ac896473b63e6c99a71a2926321ce24d49260448082019391829003018186803b156100025760325a03f415610002575050505b50565b3461000257610512600181565b346100025761051060043560006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a2637d65837a9091336000604051602001526040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515115905061008257604080517f89489a87000000000000000000000000000000000000000000000000000000008152600060048201819052600160a060020a038416602483015291517342b02b5deeb78f34cd5ac896473b63e6c99a71a2926389489a879260448082019391829003018186803b156100025760325a03f4156100025750505061020f565b3461000257610528600435610403565b34610002576104fc600435604080516000602091820181905282517f7d65837a00000000000000000000000000000000000000000000000000000000815260048101829052600160a060020a0385166024820152925190927342b02b5deeb78f34cd5ac896473b63e6c99a71a292637d65837a92604480840193829003018186803b156100025760325a03f4156100025750506040515191506101049050565b3461000257610512600c81565b3461000257610512600081565b3461000257610528600061055660005b600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a263685a1f3c9091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515191506101049050565b346100025761053a600435600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a263f775b6b59091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515191506101049050565b604080519115158252519081900360200190f35b005b6040805160ff9092168252519081900360200190f35b60408051918252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b90509056", + "nonce": "1", + "storage": { + "0x4d140b25abf3c71052885c66f73ce07cff141c1afabffdaf5cba04d625b7ebcc": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + }, + "0x269296dddce321a6bcbaa2f0181127593d732cba": { + "balance": "0x0", + "code": "0x606060405236156101275760e060020a60003504630cd40fea811461012c578063173825d9146101395780631849cb5a146101c7578063285791371461030f5780632a58b3301461033f5780632cb0d48a146103565780632f54bf6e1461036a578063332b9f061461039d5780633ca8b002146103c55780633df4ddf4146103d557806341c0e1b5146103f457806347799da81461040557806362a51eee1461042457806366907d13146104575780637065cb48146104825780637a9e541014610496578063825db5f7146104a3578063949d225d146104b0578063a51687df146104c7578063b4da4e37146104e6578063b4e6850b146104ff578063bd7474ca14610541578063e75623d814610541578063e9938e1114610555578063f5d241d314610643575b610002565b3461000257610682600181565b34610002576106986004356106ff335b60006001600a9054906101000a9004600160a060020a0316600160a060020a0316635dbe47e8836000604051602001526040518260e060020a0281526004018082600160a060020a03168152602001915050602060405180830381600087803b156100025760325a03f1156100025750506040515191506103989050565b3461000257604080516101008082018352600080835260208084018290528385018290526060808501839052608080860184905260a080870185905260c080880186905260e09788018690526001605060020a0360043581168752600586529589902089519788018a528054808816808a52605060020a91829004600160a060020a0316978a01889052600183015463ffffffff8082169d8c018e905264010000000082048116988c01899052604060020a90910416958a018690526002830154948a01859052600390920154808916938a01849052049096169690970186905293969495949293604080516001605060020a03998a16815297891660208901529590971686860152600160a060020a03909316606086015263ffffffff9182166080860152811660a08501521660c083015260e08201929092529051908190036101000190f35b346100025761069a60043560018054600091829160ff60f060020a909104161515141561063d5761072833610376565b34610002576106ae6004546001605060020a031681565b34610002576106986004356108b333610149565b346100025761069a6004355b600160a060020a03811660009081526002602052604090205460ff1615156001145b919050565b34610002576106986001805460ff60f060020a9091041615151415610913576108ed33610376565b346100025761069a600435610149565b34610002576106ae6003546001605060020a03605060020a9091041681565b346100025761069861091533610149565b34610002576106ae6003546001605060020a0360a060020a9091041681565b346100025761069a60043560243560018054600091829160ff60f060020a909104161515141561095e5761092633610376565b34610002576106986004356001805460ff60f060020a909104161515141561072557610a8b33610376565b3461000257610698600435610aa533610149565b3461000257610682600c81565b3461000257610682600081565b34610002576106ae6003546001605060020a031681565b34610002576106ca600154600160a060020a03605060020a9091041681565b346100025761069a60015460ff60f060020a9091041681565b346100025761069a60043560243560443560643560843560a43560c43560018054600091829160ff60f060020a9091041615151415610b5857610ad233610376565b3461000257610698600435610bd633610149565b34610002576106e6600435604080516101008181018352600080835260208084018290528385018290526060808501839052608080860184905260a080870185905260c080880186905260e09788018690526001605060020a03808b168752600586529589902089519788018a5280548088168952600160a060020a03605060020a918290041696890196909652600181015463ffffffff8082169b8a019b909b5264010000000081048b1695890195909552604060020a90940490981691860182905260028301549086015260039091015480841696850196909652940416918101919091525b50919050565b346100025761069a60043560243560443560643560843560a43560018054600091829160ff60f060020a9091041615151415610c8e57610bfb33610376565b6040805160ff9092168252519081900360200190f35b005b604080519115158252519081900360200190f35b604080516001605060020a039092168252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b6040805163ffffffff9092168252519081900360200190f35b1561012757600160a060020a0381166000908152600260205260409020805460ff191690555b50565b1561063d57506001605060020a0380831660009081526005602052604090208054909116151561075b576000915061063d565b604080516101008101825282546001605060020a038082168352600160a060020a03605060020a92839004166020840152600185015463ffffffff80821695850195909552640100000000810485166060850152604060020a90049093166080830152600284015460a0830152600384015480841660c08401520490911660e0820152610817905b8051600354600090819060016001605060020a0390911611610c995760038054605060020a60f060020a0319169055610ddf565b600380546001605060020a031981166000196001605060020a03928316011782558416600090815260056020526040812080547fffff000000000000000000000000000000000000000000000000000000000000168155600181810180546bffffffffffffffffffffffff191690556002820192909255909101805473ffffffffffffffffffffffffffffffffffffffff19169055915061063d565b1561012757600180547fff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660f060020a8302179055610725565b1561091357600480546001605060020a031981166001605060020a039091166001011790555b565b156101275733600160a060020a0316ff5b1561095e57506001605060020a03808416600090815260056020526040902080549091161515610965576000915061095e565b600191505b5092915050565b60038101546001605060020a0384811691161415610986576001915061095e565b604080516101008101825282546001605060020a038082168352600160a060020a03605060020a92839004166020840152600185015463ffffffff80821695850195909552640100000000810485166060850152604060020a90049093166080830152600284015460a0830152600384015480841660c08401520490911660e0820152610a12906107e3565b61095983825b80546003546001605060020a0391821691600091161515610de55760038054605060020a60a060020a031916605060020a84021760a060020a69ffffffffffffffffffff02191660a060020a84021781558301805473ffffffffffffffffffffffffffffffffffffffff19169055610ddf565b1561072557600480546001605060020a0319168217905550565b1561012757600160a060020a0381166000908152600260205260409020805460ff19166001179055610725565b15610b5857506001605060020a038088166000908152600560205260409020805490911615610b645760009150610b58565b6004546001605060020a0390811690891610610b3057600480546001605060020a03191660018a011790555b6003805460016001605060020a03821681016001605060020a03199092169190911790915591505b50979650505050505050565b80546001605060020a0319168817605060020a60f060020a031916605060020a880217815560018101805463ffffffff1916871767ffffffff0000000019166401000000008702176bffffffff00000000000000001916604060020a860217905560028101839055610b048982610a18565b156101275760018054605060020a60f060020a031916605060020a8302179055610725565b15610c8e57506001605060020a03808816600090815260056020526040902080549091161515610c2e5760009150610c8e565b8054605060020a60f060020a031916605060020a88021781556001808201805463ffffffff1916881767ffffffff0000000019166401000000008802176bffffffff00000000000000001916604060020a87021790556002820184905591505b509695505050505050565b6003546001605060020a03848116605060020a909204161415610d095760e084015160038054605060020a928302605060020a60a060020a031990911617808255919091046001605060020a031660009081526005602052604090200180546001605060020a0319169055610ddf565b6003546001605060020a0384811660a060020a909204161415610d825760c08401516003805460a060020a92830260a060020a69ffffffffffffffffffff021990911617808255919091046001605060020a03166000908152600560205260409020018054605060020a60a060020a0319169055610ddf565b505060c082015160e08301516001605060020a0380831660009081526005602052604080822060039081018054605060020a60a060020a031916605060020a8702179055928416825290200180546001605060020a031916831790555b50505050565b6001605060020a0384161515610e6457600380546001605060020a03605060020a9182900481166000908152600560205260409020830180546001605060020a0319908116871790915583548785018054918590049093168402605060020a60a060020a03199182161790911690915582549185029116179055610ddf565b506001605060020a038381166000908152600560205260409020600390810180549185018054605060020a60a060020a0319908116605060020a94859004909516808502959095176001605060020a0319168817909155815416918402919091179055801515610ef4576003805460a060020a69ffffffffffffffffffff02191660a060020a8402179055610ddf565b6003808401546001605060020a03605060020a9091041660009081526005602052604090200180546001605060020a031916831790555050505056", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000113204f5d64c28326fd7bd05fd4ea855302d7f2ff00000000000000000000" + } + }, + "0x42b02b5deeb78f34cd5ac896473b63e6c99a71a2": { + "balance": "0x0", + "code": "0x6504032353da7150606060405236156100695760e060020a60003504631bf7509d811461006e57806321ce24d41461008157806333556e84146100ec578063685a1f3c146101035780637d65837a1461011757806389489a8714610140578063f775b6b5146101fc575b610007565b61023460043560006100fd82600061010d565b610246600435602435600160a060020a03811660009081526020839052604081205415156102cb57826001016000508054806001018281815481835581811511610278576000838152602090206102789181019083015b808211156102d057600081556001016100d8565b610248600435602435600182015481105b92915050565b6102346004356024355b60018101906100fd565b610248600435602435600160a060020a03811660009081526020839052604090205415156100fd565b61024660043560243580600160a060020a031632600160a060020a03161415156101f857600160a060020a038116600090815260208390526040902054156101f857600160a060020a038116600090815260208390526040902054600183018054909160001901908110156100075760009182526020808320909101805473ffffffffffffffffffffffffffffffffffffffff19169055600160a060020a038316825283905260408120556002820180546000190190555b5050565b61025c60043560243560008260010160005082815481101561000757600091825260209091200154600160a060020a03169392505050565b60408051918252519081900360200190f35b005b604080519115158252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b50505060009283526020808420909201805473ffffffffffffffffffffffffffffffffffffffff191686179055600160a060020a0385168352908590526040909120819055600284018054600101905590505b505050565b509056", + "nonce": "1", + "storage": {} + }, + "0xa529806c67cc6486d4d62024471772f47f6fd672": { + "balance": "0x67820e39ac8fe9800", + "code": "0x", + "nonce": "68", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "31912170", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712388", + "hash": "0x0855914bdc581bccdc62591fd438498386ffb59ea4d5361ed5c3702e26e2c72f", + "miner": "0x334391aa808257952a462d1475562ee2106a6c90", + "mixHash": "0x64bb70b8ca883cadb8fbbda2c70a861612407864089ed87b98e5de20acceada6", + "nonce": "0x684129f283aaef18", + "number": "11494", + "stateRoot": "0x7057f31fe3dab1d620771adad35224aae43eb70e94861208bc84c557ff5b9d10", + "timestamp": "1479735912", + "totalDifficulty": "90744064339" + }, + "input": "0xf889448504a817c800832dc6c094269296dddce321a6bcbaa2f0181127593d732cba80a47065cb480000000000000000000000001523e55a1ca4efbae03355775ae89f8d7699ad9e29a080ed81e4c5e9971a730efab4885566e2c868cd80bd4166d0ed8c287fdf181650a069d7c49215e3d4416ad239cd09dbb71b9f04c16b33b385d14f40b618a7a65115", + "result": { + "calls": [ + { + "calls": [ + { + "from": "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff", + "gas": "0x2bf459", + "gasUsed": "0x2aa", + "input": "0x7d65837a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a529806c67cc6486d4d62024471772f47f6fd672", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x42b02b5deeb78f34cd5ac896473b63e6c99a71a2", + "type": "DELEGATECALL", + "value": "0x0" + } + ], + "from": "0x269296dddce321a6bcbaa2f0181127593d732cba", + "gas": "0x2cae73", + "gasUsed": "0xa9d", + "input": "0x5dbe47e8000000000000000000000000a529806c67cc6486d4d62024471772f47f6fd672", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0xa529806c67cc6486d4d62024471772f47f6fd672", + "gas": "0x2dc6c0", + "gasUsed": "0xbd55", + "input": "0x7065cb480000000000000000000000001523e55a1ca4efbae03355775ae89f8d7699ad9e", + "to": "0x269296dddce321a6bcbaa2f0181127593d732cba", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json new file mode 100644 index 0000000..bb16a4a --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json @@ -0,0 +1,77 @@ +{ + "context": { + "difficulty": "3451177886", + "gasLimit": "4709286", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2290744", + "timestamp": "1513616439" + }, + "genesis": { + "alloc": { + "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a": { + "balance": "0x0", + "code": "0x606060405263ffffffff60e060020a6000350416633b91f50681146100505780635bb47808146100715780635f51fca01461008c578063bc7647a9146100ad578063f1bd0d7a146100c8575b610000565b346100005761006f600160a060020a03600435811690602435166100e9565b005b346100005761006f600160a060020a0360043516610152565b005b346100005761006f600160a060020a036004358116906024351661019c565b005b346100005761006f600160a060020a03600435166101fa565b005b346100005761006f600160a060020a0360043581169060243516610db8565b005b600160a060020a038083166000908152602081905260408120549091908116903316811461011657610000565b839150600160a060020a038316151561012d573392505b6101378284610e2e565b6101418284610db8565b61014a826101fa565b5b5b50505050565b600154600160a060020a03908116903316811461016e57610000565b6002805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0384161790555b5b5050565b600254600160a060020a0390811690331681146101b857610000565b600160a060020a038381166000908152602081905260409020805473ffffffffffffffffffffffffffffffffffffffff19169184169190911790555b5b505050565b6040805160e260020a631a481fc102815260016024820181905260026044830152606482015262093a8060848201819052600060a4830181905260c06004840152601e60c48401527f736574456e7469747953746174757328616464726573732c75696e743829000060e484015292519091600160a060020a038516916369207f049161010480820192879290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526000602482018190526001604483015260606004830152602360648301527f626567696e506f6c6c28616464726573732c75696e7436342c626f6f6c2c626f60848301527f6f6c29000000000000000000000000000000000000000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601960c48201527f61646453746f636b28616464726573732c75696e74323536290000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601960c48201527f697373756553746f636b2875696e74382c75696e74323536290000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152602160648301527f6772616e7453746f636b2875696e74382c75696e743235362c61646472657373608483015260f860020a60290260a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f115610000575050604080517f010555b8000000000000000000000000000000000000000000000000000000008152600160a060020a03338116602483015260006044830181905260606004840152603c60648401527f6772616e7456657374656453746f636b2875696e74382c75696e743235362c6160848401527f6464726573732c75696e7436342c75696e7436342c75696e743634290000000060a48401529251908716935063010555b89260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601260c48201527f626567696e53616c65286164647265737329000000000000000000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152601a60648301527f7472616e7366657253616c6546756e64732875696e743235362900000000000060848301529151600160a060020a038716935063de64e15c9260a48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152602d60c48201527f7365744163636f756e74696e6753657474696e67732875696e743235362c756960e48201527f6e7436342c75696e7432353629000000000000000000000000000000000000006101048201529051600160a060020a03861692506369207f04916101248082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152603460648301527f637265617465526563757272696e6752657761726428616464726573732c756960848301527f6e743235362c75696e7436342c737472696e672900000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152601b60648301527f72656d6f7665526563757272696e675265776172642875696e7429000000000060848301529151600160a060020a038716935063de64e15c9260a48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152602360648301527f697373756552657761726428616464726573732c75696e743235362c7374726960848301527f6e6729000000000000000000000000000000000000000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a6337993857028152600160248201819052604482015260606004820152602260648201527f61737369676e53746f636b2875696e74382c616464726573732c75696e743235608482015260f060020a6136290260a48201529051600160a060020a038616925063de64e15c9160c48082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a6337993857028152600160248201819052604482015260606004820152602260648201527f72656d6f766553746f636b2875696e74382c616464726573732c75696e743235608482015260f060020a6136290260a48201529051600160a060020a038616925063de64e15c9160c48082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260026024808301919091526003604483015260006064830181905267ffffffffffffffff8616608484015260ff871660a484015260c0600484015260c48301919091527f7365744164647265737342796c617728737472696e672c616464726573732c6260e48301527f6f6f6c29000000000000000000000000000000000000000000000000000000006101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc1028152600260248201526003604482015260006064820181905267ffffffffffffffff8516608483015260ff861660a483015260c06004830152602160c48301527f73657453746174757342796c617728737472696e672c75696e74382c626f6f6c60e483015260f860020a6029026101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc1028152600260248201526003604482015260006064820181905267ffffffffffffffff8516608483015260ff861660a483015260c06004830152603860c48301527f736574566f74696e6742796c617728737472696e672c75696e743235362c756960e48301527f6e743235362c626f6f6c2c75696e7436342c75696e74382900000000000000006101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f115610000575050505b505050565b604080517f225553a4000000000000000000000000000000000000000000000000000000008152600160a060020a0383811660048301526002602483015291519184169163225553a49160448082019260009290919082900301818387803b156100005760325a03f115610000575050505b5050565b600082604051611fd280610f488339600160a060020a03909216910190815260405190819003602001906000f0801561000057905082600160a060020a03166308b027418260016040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a0316815260200182815260200192505050600060405180830381600087803b156100005760325a03f115610000575050604080517fa14e3ee300000000000000000000000000000000000000000000000000000000815260006004820181905260016024830152600160a060020a0386811660448401529251928716935063a14e3ee39260648084019382900301818387803b156100005760325a03f115610000575050505b5050505600606060405234620000005760405160208062001fd283398101604052515b805b600a8054600160a060020a031916600160a060020a0383161790555b506001600d819055600e81905560408051808201909152600c8082527f566f74696e672053746f636b00000000000000000000000000000000000000006020928301908152600b805460008290528251601860ff1990911617825590947f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9600291831615610100026000190190921604601f0193909304830192906200010c565b828001600101855582156200010c579182015b828111156200010c578251825591602001919060010190620000ef565b5b50620001309291505b808211156200012c576000815560010162000116565b5090565b50506040805180820190915260038082527f43565300000000000000000000000000000000000000000000000000000000006020928301908152600c805460008290528251600660ff1990911617825590937fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c760026001841615610100026000190190931692909204601f010481019291620001f7565b82800160010185558215620001f7579182015b82811115620001f7578251825591602001919060010190620001da565b5b506200021b9291505b808211156200012c576000815560010162000116565b5090565b50505b505b611da280620002306000396000f3006060604052361561019a5763ffffffff60e060020a600035041662e1986d811461019f57806302a72a4c146101d657806306eb4e421461020157806306fdde0314610220578063095ea7b3146102ad578063158ccb99146102dd57806318160ddd146102f85780631cf65a781461031757806323b872dd146103365780632c71e60a1461036c57806333148fd6146103ca578063435ebc2c146103f55780635eeb6e451461041e578063600e85b71461043c5780636103d70b146104a157806362c1e46a146104b05780636c182e99146104ba578063706dc87c146104f057806370a082311461052557806377174f851461055057806395d89b411461056f578063a7771ee3146105fc578063a9059cbb14610629578063ab377daa14610659578063b25dbb5e14610685578063b89a73cb14610699578063ca5eb5e1146106c6578063cbcf2e5a146106e1578063d21f05ba1461070e578063d347c2051461072d578063d96831e114610765578063dd62ed3e14610777578063df3c211b146107a8578063e2982c21146107d6578063eb944e4c14610801575b610000565b34610000576101d4600160a060020a036004351660243567ffffffffffffffff6044358116906064358116906084351661081f565b005b34610000576101ef600160a060020a0360043516610a30565b60408051918252519081900360200190f35b34610000576101ef610a4f565b60408051918252519081900360200190f35b346100005761022d610a55565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516602435610ae3565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516610b4e565b005b34610000576101ef610b89565b60408051918252519081900360200190f35b34610000576101ef610b8f565b60408051918252519081900360200190f35b34610000576102c9600160a060020a0360043581169060243516604435610b95565b604080519115158252519081900360200190f35b3461000057610388600160a060020a0360043516602435610bb7565b60408051600160a060020a039096168652602086019490945267ffffffffffffffff928316858501529082166060850152166080830152519081900360a00190f35b34610000576101ef600160a060020a0360043516610c21565b60408051918252519081900360200190f35b3461000057610402610c40565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d4600160a060020a0360043516602435610c4f565b005b3461000057610458600160a060020a0360043516602435610cc9565b60408051600160a060020a03909716875260208701959095528585019390935267ffffffffffffffff9182166060860152811660808501521660a0830152519081900360c00190f35b34610000576101d4610d9e565b005b6101d4610e1e565b005b34610000576104d3600160a060020a0360043516610e21565b6040805167ffffffffffffffff9092168252519081900360200190f35b3461000057610402600160a060020a0360043516610ead565b60408051600160a060020a039092168252519081900360200190f35b34610000576101ef600160a060020a0360043516610ef9565b60408051918252519081900360200190f35b34610000576101ef610f18565b60408051918252519081900360200190f35b346100005761022d610f1e565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516610fac565b604080519115158252519081900360200190f35b34610000576102c9600160a060020a0360043516602435610fc2565b604080519115158252519081900360200190f35b3461000057610402600435610fe2565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d46004351515610ffd565b005b34610000576102c9600160a060020a036004351661104c565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516611062565b005b34610000576102c9600160a060020a0360043516611070565b604080519115158252519081900360200190f35b34610000576101ef6110f4565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351667ffffffffffffffff602435166110fa565b60408051918252519081900360200190f35b34610000576101d4600435611121565b005b34610000576101ef600160a060020a03600435811690602435166111c6565b60408051918252519081900360200190f35b34610000576101ef6004356024356044356064356084356111f3565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351661128c565b60408051918252519081900360200190f35b34610000576101d4600160a060020a036004351660243561129e565b005b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915267ffffffffffffffff848116908416101561086457610000565b8367ffffffffffffffff168267ffffffffffffffff16101561088557610000565b8267ffffffffffffffff168267ffffffffffffffff1610156108a657610000565b506040805160a081018252600160a060020a033381168252602080830188905267ffffffffffffffff80871684860152858116606085015287166080840152908816600090815260039091529190912080546001810180835582818380158290116109615760030281600302836000526020600020918201910161096191905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050916000526020600020906003020160005b5082518154600160a060020a031916600160a060020a03909116178155602083015160018201556040830151600290910180546060850151608086015167ffffffffffffffff1990921667ffffffffffffffff948516176fffffffffffffffff00000000000000001916604060020a918516919091021777ffffffffffffffff000000000000000000000000000000001916608060020a939091169290920291909117905550610a268686610fc2565b505b505050505050565b600160a060020a0381166000908152600360205260409020545b919050565b60055481565b600b805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b600160a060020a03338116600081815260026020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b600a5433600160a060020a03908116911614610b6957610000565b600a8054600160a060020a031916600160a060020a0383161790555b5b50565b60005481565b60005b90565b6000610ba2848484611600565b610bad8484846116e2565b90505b9392505050565b600360205281600052604060002081815481101561000057906000526020600020906003020160005b5080546001820154600290920154600160a060020a03909116935090915067ffffffffffffffff80821691604060020a8104821691608060020a9091041685565b600160a060020a0381166000908152600860205260409020545b919050565b600a54600160a060020a031681565b600a5433600160a060020a03908116911614610c6a57610000565b610c7660005482611714565b6000908155600160a060020a038316815260016020526040902054610c9b9082611714565b600160a060020a038316600090815260016020526040812091909155610cc390839083611600565b5b5b5050565b6000600060006000600060006000600360008a600160a060020a0316600160a060020a0316815260200190815260200160002088815481101561000057906000526020600020906003020160005b508054600182015460028301546040805160a081018252600160a060020a039094168085526020850184905267ffffffffffffffff808416928601839052604060020a8404811660608701819052608060020a9094041660808601819052909c50929a509197509095509350909150610d90904261172d565b94505b509295509295509295565b33600160a060020a038116600090815260066020526040902054801515610dc457610000565b8030600160a060020a0316311015610ddb57610000565b600160a060020a0382166000818152600660205260408082208290555183156108fc0291849190818181858888f193505050501515610cc357610000565b5b5050565b5b565b600160a060020a03811660009081526003602052604081205442915b81811015610ea557600160a060020a03841660009081526003602052604090208054610e9a9190839081101561000057906000526020600020906003020160005b5060020154604060020a900467ffffffffffffffff168461177d565b92505b600101610e3d565b5b5050919050565b600160a060020a0380821660009081526007602052604081205490911615610eef57600160a060020a0380831660009081526007602052604090205416610ef1565b815b90505b919050565b600160a060020a0381166000908152600160205260409020545b919050565b600d5481565b600c805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b60006000610fb983610c21565b1190505b919050565b6000610fcf338484611600565b610fd983836117ac565b90505b92915050565b600460205260009081526040902054600160a060020a031681565b8015801561101a575061100f33610ef9565b61101833610c21565b115b1561102457610000565b33600160a060020a03166000908152600960205260409020805460ff19168215151790555b50565b60006000610fb983610ef9565b1190505b919050565b610b8533826117dc565b5b50565b600a54604080516000602091820181905282517fcbcf2e5a000000000000000000000000000000000000000000000000000000008152600160a060020a03868116600483015293519194939093169263cbcf2e5a92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b600e5481565b6000610fd961110984846118b2565b61111385856119b6565b611a05565b90505b92915050565b600a5433600160a060020a0390811691161461113c57610000565b61114860005482611a1f565b600055600554600190101561116c57600a5461116c90600160a060020a0316611a47565b5b600a54600160a060020a03166000908152600160205260409020546111929082611a1f565b600a8054600160a060020a039081166000908152600160205260408120939093559054610b8592911683611600565b5b5b50565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b6000600060008487101561120a5760009250611281565b8387111561121a57879250611281565b61123f6112308961122b888a611714565b611a90565b61123a8689611714565b611abc565b915081925061124e8883611714565b905061127e8361127961126a8461122b8c8b611714565b611a90565b61123a888b611714565b611abc565b611a1f565b92505b505095945050505050565b60066020526000908152604090205481565b600160a060020a03821660009081526003602052604081208054829190849081101561000057906000526020600020906003020160005b50805490925033600160a060020a039081169116146112f357610000565b6040805160a0810182528354600160a060020a0316815260018401546020820152600284015467ffffffffffffffff80821693830193909352604060020a810483166060830152608060020a900490911660808201526113539042611af9565b600160a060020a0385166000908152600360205260409020805491925090849081101561000057906000526020600020906003020160005b508054600160a060020a031916815560006001820181905560029091018054600160c060020a0319169055600160a060020a0385168152600360205260409020805460001981019081101561000057906000526020600020906003020160005b50600160a060020a03851660009081526003602052604090208054859081101561000057906000526020600020906003020160005b5081548154600160a060020a031916600160a060020a03918216178255600180840154908301556002928301805493909201805467ffffffffffffffff191667ffffffffffffffff948516178082558354604060020a908190048616026fffffffffffffffff000000000000000019909116178082559254608060020a9081900490941690930277ffffffffffffffff00000000000000000000000000000000199092169190911790915584166000908152600360205260409020805460001981018083559190829080158290116115485760030281600302836000526020600020918201910161154891905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050600160a060020a033316600090815260016020526040902054611570915082611a1f565b600160a060020a03338116600090815260016020526040808220939093559086168152205461159f9082611714565b600160a060020a038086166000818152600160209081526040918290209490945580518581529051339093169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35b50505050565b600160a060020a0383161561166e576116466008600061161f86610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611714565b6008600061165386610ead565b600160a060020a031681526020810191909152604001600020555b600160a060020a038216156116dc576116b46008600061168d85610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611a1f565b600860006116c185610ead565b600160a060020a031681526020810191909152604001600020555b5b505050565b600083826116f082426110fa565b8111156116fc57610000565b611707868686611b1b565b92505b5b50509392505050565b600061172283831115611b4d565b508082035b92915050565b6000610fd983602001518367ffffffffffffffff16856080015167ffffffffffffffff16866040015167ffffffffffffffff16876060015167ffffffffffffffff166111f3565b90505b92915050565b60008167ffffffffffffffff168367ffffffffffffffff1610156117a15781610fd9565b825b90505b92915050565b600033826117ba82426110fa565b8111156117c657610000565b6117d08585611b5d565b92505b5b505092915050565b6117e582610ef9565b6117ee83610c21565b11156117f957610000565b600160a060020a03811660009081526009602052604090205460ff16158015611834575081600160a060020a031681600160a060020a031614155b1561183e57610000565b61184782611070565b1561185157610000565b611864828261185f85610ef9565b611600565b600160a060020a0382811660009081526007602052604090208054600160a060020a031916918316918217905561189a82610ead565b600160a060020a031614610cc357610000565b5b5050565b600160a060020a038216600090815260036020526040812054815b818110156119885761197d836112796003600089600160a060020a0316600160a060020a0316815260200190815260200160002084815481101561000057906000526020600020906003020160005b506040805160a0810182528254600160a060020a031681526001830154602082015260029092015467ffffffffffffffff80821692840192909252604060020a810482166060840152608060020a900416608082015287611af9565b611a1f565b92505b6001016118cd565b600160a060020a0385166000908152600160205260409020546117d09084611714565b92505b505092915050565b600060006119c384611070565b80156119d157506000600d54115b90506119fb816119e9576119e485610ef9565b6119ec565b60005b6111138686611b7b565b611a05565b91505b5092915050565b60008183106117a15781610fd9565b825b90505b92915050565b6000828201611a3c848210801590611a375750838210155b611b4d565b8091505b5092915050565b611a508161104c565b15611a5a57610b85565b6005805460009081526004602052604090208054600160a060020a031916600160a060020a038416179055805460010190555b50565b6000828202611a3c841580611a37575083858381156100005704145b611b4d565b8091505b5092915050565b60006000611acc60008411611b4d565b8284811561000057049050611a3c838581156100005706828502018514611b4d565b8091505b5092915050565b6000610fd98360200151611b0d858561172d565b611714565b90505b92915050565b60008382611b2982426110fa565b811115611b3557610000565b611707868686611b8f565b92505b5b50509392505050565b801515610b8557610000565b5b50565b6000611b6883611a47565b610fd98383611c92565b90505b92915050565b6000610fd983610ef9565b90505b92915050565b600160a060020a038084166000908152600260209081526040808320338516845282528083205493861683526001909152812054909190611bd09084611a1f565b600160a060020a038086166000908152600160205260408082209390935590871681522054611bff9084611714565b600160a060020a038616600090815260016020526040902055611c228184611714565b600160a060020a038087166000818152600260209081526040808320338616845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600191505b509392505050565b60003382611ca082426110fa565b811115611cac57610000565b6117d08585611cc2565b92505b5b505092915050565b600160a060020a033316600090815260016020526040812054611ce59083611714565b600160a060020a033381166000908152600160205260408082209390935590851681522054611d149083611a1f565b600160a060020a038085166000818152600160209081526040918290209490945580518681529051919333909316927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b929150505600a165627a7a72305820bfa5ddd3fecf3f43aed25385ec7ec3ef79638c2e58d99f85d9a3cc494183bf160029a165627a7a723058200e78a5f7e0f91739035d0fbf5eca02f79377210b722f63431f29a22e2880b3bd0029", + "nonce": "789", + "storage": { + "0xfe9ec0542a1c009be8b1f3acf43af97100ffff42eb736850fb038fa1151ad4d9": "0x000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e8" + } + }, + "0x5cb4a6b902fcb21588c86c3517e797b07cdaadb9": { + "balance": "0x0", + "code": "0x", + "nonce": "0", + "storage": {} + }, + "0xe4a13bc304682a903e9472f469c33801dd18d9e8": { + "balance": "0x33c763c929f62c4f", + "code": "0x", + "nonce": "14", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3451177886", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4713874", + "hash": "0x5d52a672417cd1269bf4f7095e25dcbf837747bba908cd5ef809dc1bd06144b5", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x01a12845ed546b94a038a7a03e8df8d7952024ed41ccb3db7a7ade4abc290ce1", + "nonce": "0x28c446f1cb9748c1", + "number": "2290743", + "stateRoot": "0x4898aceede76739daef76448a367d10015a2c022c9e7909b99a10fbf6fb16708", + "timestamp": "1513616414", + "totalDifficulty": "7146523769022564" + }, + "input": "0xf8aa0e8509502f9000830493e0941d3ddf7caf024f253487e18bc4a15b1a360c170a80b8443b91f506000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e829a0524564944fa419f5c189b5074044f89210c6d6b2d77ee8f7f12a927d59b636dfa0015b28986807a424b18b186ee6642d76739df36cad802d20e8c00e79a61d7281", + "result": { + "calls": [ + { + "error": "contract creation code storage out of gas", + "from": "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a", + "gas": "0x39ff0", + "gasUsed": "0x39ff0", + "input": "0x606060405234620000005760405160208062001fd283398101604052515b805b600a8054600160a060020a031916600160a060020a0383161790555b506001600d819055600e81905560408051808201909152600c8082527f566f74696e672053746f636b00000000000000000000000000000000000000006020928301908152600b805460008290528251601860ff1990911617825590947f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9600291831615610100026000190190921604601f0193909304830192906200010c565b828001600101855582156200010c579182015b828111156200010c578251825591602001919060010190620000ef565b5b50620001309291505b808211156200012c576000815560010162000116565b5090565b50506040805180820190915260038082527f43565300000000000000000000000000000000000000000000000000000000006020928301908152600c805460008290528251600660ff1990911617825590937fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c760026001841615610100026000190190931692909204601f010481019291620001f7565b82800160010185558215620001f7579182015b82811115620001f7578251825591602001919060010190620001da565b5b506200021b9291505b808211156200012c576000815560010162000116565b5090565b50505b505b611da280620002306000396000f3006060604052361561019a5763ffffffff60e060020a600035041662e1986d811461019f57806302a72a4c146101d657806306eb4e421461020157806306fdde0314610220578063095ea7b3146102ad578063158ccb99146102dd57806318160ddd146102f85780631cf65a781461031757806323b872dd146103365780632c71e60a1461036c57806333148fd6146103ca578063435ebc2c146103f55780635eeb6e451461041e578063600e85b71461043c5780636103d70b146104a157806362c1e46a146104b05780636c182e99146104ba578063706dc87c146104f057806370a082311461052557806377174f851461055057806395d89b411461056f578063a7771ee3146105fc578063a9059cbb14610629578063ab377daa14610659578063b25dbb5e14610685578063b89a73cb14610699578063ca5eb5e1146106c6578063cbcf2e5a146106e1578063d21f05ba1461070e578063d347c2051461072d578063d96831e114610765578063dd62ed3e14610777578063df3c211b146107a8578063e2982c21146107d6578063eb944e4c14610801575b610000565b34610000576101d4600160a060020a036004351660243567ffffffffffffffff6044358116906064358116906084351661081f565b005b34610000576101ef600160a060020a0360043516610a30565b60408051918252519081900360200190f35b34610000576101ef610a4f565b60408051918252519081900360200190f35b346100005761022d610a55565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516602435610ae3565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516610b4e565b005b34610000576101ef610b89565b60408051918252519081900360200190f35b34610000576101ef610b8f565b60408051918252519081900360200190f35b34610000576102c9600160a060020a0360043581169060243516604435610b95565b604080519115158252519081900360200190f35b3461000057610388600160a060020a0360043516602435610bb7565b60408051600160a060020a039096168652602086019490945267ffffffffffffffff928316858501529082166060850152166080830152519081900360a00190f35b34610000576101ef600160a060020a0360043516610c21565b60408051918252519081900360200190f35b3461000057610402610c40565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d4600160a060020a0360043516602435610c4f565b005b3461000057610458600160a060020a0360043516602435610cc9565b60408051600160a060020a03909716875260208701959095528585019390935267ffffffffffffffff9182166060860152811660808501521660a0830152519081900360c00190f35b34610000576101d4610d9e565b005b6101d4610e1e565b005b34610000576104d3600160a060020a0360043516610e21565b6040805167ffffffffffffffff9092168252519081900360200190f35b3461000057610402600160a060020a0360043516610ead565b60408051600160a060020a039092168252519081900360200190f35b34610000576101ef600160a060020a0360043516610ef9565b60408051918252519081900360200190f35b34610000576101ef610f18565b60408051918252519081900360200190f35b346100005761022d610f1e565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516610fac565b604080519115158252519081900360200190f35b34610000576102c9600160a060020a0360043516602435610fc2565b604080519115158252519081900360200190f35b3461000057610402600435610fe2565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d46004351515610ffd565b005b34610000576102c9600160a060020a036004351661104c565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516611062565b005b34610000576102c9600160a060020a0360043516611070565b604080519115158252519081900360200190f35b34610000576101ef6110f4565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351667ffffffffffffffff602435166110fa565b60408051918252519081900360200190f35b34610000576101d4600435611121565b005b34610000576101ef600160a060020a03600435811690602435166111c6565b60408051918252519081900360200190f35b34610000576101ef6004356024356044356064356084356111f3565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351661128c565b60408051918252519081900360200190f35b34610000576101d4600160a060020a036004351660243561129e565b005b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915267ffffffffffffffff848116908416101561086457610000565b8367ffffffffffffffff168267ffffffffffffffff16101561088557610000565b8267ffffffffffffffff168267ffffffffffffffff1610156108a657610000565b506040805160a081018252600160a060020a033381168252602080830188905267ffffffffffffffff80871684860152858116606085015287166080840152908816600090815260039091529190912080546001810180835582818380158290116109615760030281600302836000526020600020918201910161096191905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050916000526020600020906003020160005b5082518154600160a060020a031916600160a060020a03909116178155602083015160018201556040830151600290910180546060850151608086015167ffffffffffffffff1990921667ffffffffffffffff948516176fffffffffffffffff00000000000000001916604060020a918516919091021777ffffffffffffffff000000000000000000000000000000001916608060020a939091169290920291909117905550610a268686610fc2565b505b505050505050565b600160a060020a0381166000908152600360205260409020545b919050565b60055481565b600b805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b600160a060020a03338116600081815260026020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b600a5433600160a060020a03908116911614610b6957610000565b600a8054600160a060020a031916600160a060020a0383161790555b5b50565b60005481565b60005b90565b6000610ba2848484611600565b610bad8484846116e2565b90505b9392505050565b600360205281600052604060002081815481101561000057906000526020600020906003020160005b5080546001820154600290920154600160a060020a03909116935090915067ffffffffffffffff80821691604060020a8104821691608060020a9091041685565b600160a060020a0381166000908152600860205260409020545b919050565b600a54600160a060020a031681565b600a5433600160a060020a03908116911614610c6a57610000565b610c7660005482611714565b6000908155600160a060020a038316815260016020526040902054610c9b9082611714565b600160a060020a038316600090815260016020526040812091909155610cc390839083611600565b5b5b5050565b6000600060006000600060006000600360008a600160a060020a0316600160a060020a0316815260200190815260200160002088815481101561000057906000526020600020906003020160005b508054600182015460028301546040805160a081018252600160a060020a039094168085526020850184905267ffffffffffffffff808416928601839052604060020a8404811660608701819052608060020a9094041660808601819052909c50929a509197509095509350909150610d90904261172d565b94505b509295509295509295565b33600160a060020a038116600090815260066020526040902054801515610dc457610000565b8030600160a060020a0316311015610ddb57610000565b600160a060020a0382166000818152600660205260408082208290555183156108fc0291849190818181858888f193505050501515610cc357610000565b5b5050565b5b565b600160a060020a03811660009081526003602052604081205442915b81811015610ea557600160a060020a03841660009081526003602052604090208054610e9a9190839081101561000057906000526020600020906003020160005b5060020154604060020a900467ffffffffffffffff168461177d565b92505b600101610e3d565b5b5050919050565b600160a060020a0380821660009081526007602052604081205490911615610eef57600160a060020a0380831660009081526007602052604090205416610ef1565b815b90505b919050565b600160a060020a0381166000908152600160205260409020545b919050565b600d5481565b600c805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b60006000610fb983610c21565b1190505b919050565b6000610fcf338484611600565b610fd983836117ac565b90505b92915050565b600460205260009081526040902054600160a060020a031681565b8015801561101a575061100f33610ef9565b61101833610c21565b115b1561102457610000565b33600160a060020a03166000908152600960205260409020805460ff19168215151790555b50565b60006000610fb983610ef9565b1190505b919050565b610b8533826117dc565b5b50565b600a54604080516000602091820181905282517fcbcf2e5a000000000000000000000000000000000000000000000000000000008152600160a060020a03868116600483015293519194939093169263cbcf2e5a92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b600e5481565b6000610fd961110984846118b2565b61111385856119b6565b611a05565b90505b92915050565b600a5433600160a060020a0390811691161461113c57610000565b61114860005482611a1f565b600055600554600190101561116c57600a5461116c90600160a060020a0316611a47565b5b600a54600160a060020a03166000908152600160205260409020546111929082611a1f565b600a8054600160a060020a039081166000908152600160205260408120939093559054610b8592911683611600565b5b5b50565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b6000600060008487101561120a5760009250611281565b8387111561121a57879250611281565b61123f6112308961122b888a611714565b611a90565b61123a8689611714565b611abc565b915081925061124e8883611714565b905061127e8361127961126a8461122b8c8b611714565b611a90565b61123a888b611714565b611abc565b611a1f565b92505b505095945050505050565b60066020526000908152604090205481565b600160a060020a03821660009081526003602052604081208054829190849081101561000057906000526020600020906003020160005b50805490925033600160a060020a039081169116146112f357610000565b6040805160a0810182528354600160a060020a0316815260018401546020820152600284015467ffffffffffffffff80821693830193909352604060020a810483166060830152608060020a900490911660808201526113539042611af9565b600160a060020a0385166000908152600360205260409020805491925090849081101561000057906000526020600020906003020160005b508054600160a060020a031916815560006001820181905560029091018054600160c060020a0319169055600160a060020a0385168152600360205260409020805460001981019081101561000057906000526020600020906003020160005b50600160a060020a03851660009081526003602052604090208054859081101561000057906000526020600020906003020160005b5081548154600160a060020a031916600160a060020a03918216178255600180840154908301556002928301805493909201805467ffffffffffffffff191667ffffffffffffffff948516178082558354604060020a908190048616026fffffffffffffffff000000000000000019909116178082559254608060020a9081900490941690930277ffffffffffffffff00000000000000000000000000000000199092169190911790915584166000908152600360205260409020805460001981018083559190829080158290116115485760030281600302836000526020600020918201910161154891905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050600160a060020a033316600090815260016020526040902054611570915082611a1f565b600160a060020a03338116600090815260016020526040808220939093559086168152205461159f9082611714565b600160a060020a038086166000818152600160209081526040918290209490945580518581529051339093169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35b50505050565b600160a060020a0383161561166e576116466008600061161f86610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611714565b6008600061165386610ead565b600160a060020a031681526020810191909152604001600020555b600160a060020a038216156116dc576116b46008600061168d85610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611a1f565b600860006116c185610ead565b600160a060020a031681526020810191909152604001600020555b5b505050565b600083826116f082426110fa565b8111156116fc57610000565b611707868686611b1b565b92505b5b50509392505050565b600061172283831115611b4d565b508082035b92915050565b6000610fd983602001518367ffffffffffffffff16856080015167ffffffffffffffff16866040015167ffffffffffffffff16876060015167ffffffffffffffff166111f3565b90505b92915050565b60008167ffffffffffffffff168367ffffffffffffffff1610156117a15781610fd9565b825b90505b92915050565b600033826117ba82426110fa565b8111156117c657610000565b6117d08585611b5d565b92505b5b505092915050565b6117e582610ef9565b6117ee83610c21565b11156117f957610000565b600160a060020a03811660009081526009602052604090205460ff16158015611834575081600160a060020a031681600160a060020a031614155b1561183e57610000565b61184782611070565b1561185157610000565b611864828261185f85610ef9565b611600565b600160a060020a0382811660009081526007602052604090208054600160a060020a031916918316918217905561189a82610ead565b600160a060020a031614610cc357610000565b5b5050565b600160a060020a038216600090815260036020526040812054815b818110156119885761197d836112796003600089600160a060020a0316600160a060020a0316815260200190815260200160002084815481101561000057906000526020600020906003020160005b506040805160a0810182528254600160a060020a031681526001830154602082015260029092015467ffffffffffffffff80821692840192909252604060020a810482166060840152608060020a900416608082015287611af9565b611a1f565b92505b6001016118cd565b600160a060020a0385166000908152600160205260409020546117d09084611714565b92505b505092915050565b600060006119c384611070565b80156119d157506000600d54115b90506119fb816119e9576119e485610ef9565b6119ec565b60005b6111138686611b7b565b611a05565b91505b5092915050565b60008183106117a15781610fd9565b825b90505b92915050565b6000828201611a3c848210801590611a375750838210155b611b4d565b8091505b5092915050565b611a508161104c565b15611a5a57610b85565b6005805460009081526004602052604090208054600160a060020a031916600160a060020a038416179055805460010190555b50565b6000828202611a3c841580611a37575083858381156100005704145b611b4d565b8091505b5092915050565b60006000611acc60008411611b4d565b8284811561000057049050611a3c838581156100005706828502018514611b4d565b8091505b5092915050565b6000610fd98360200151611b0d858561172d565b611714565b90505b92915050565b60008382611b2982426110fa565b811115611b3557610000565b611707868686611b8f565b92505b5b50509392505050565b801515610b8557610000565b5b50565b6000611b6883611a47565b610fd98383611c92565b90505b92915050565b6000610fd983610ef9565b90505b92915050565b600160a060020a038084166000908152600260209081526040808320338516845282528083205493861683526001909152812054909190611bd09084611a1f565b600160a060020a038086166000908152600160205260408082209390935590871681522054611bff9084611714565b600160a060020a038616600090815260016020526040902055611c228184611714565b600160a060020a038087166000818152600260209081526040808320338616845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600191505b509392505050565b60003382611ca082426110fa565b811115611cac57610000565b6117d08585611cc2565b92505b5b505092915050565b600160a060020a033316600090815260016020526040812054611ce59083611714565b600160a060020a033381166000908152600160205260408082209390935590851681522054611d149083611a1f565b600160a060020a038085166000818152600160209081526040918290209490945580518681529051919333909316927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b929150505600a165627a7a72305820bfa5ddd3fecf3f43aed25385ec7ec3ef79638c2e58d99f85d9a3cc494183bf160029000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182", + "type": "CREATE", + "value": "0x0" + } + ], + "error": "invalid jump destination", + "from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8", + "gas": "0x493e0", + "gasUsed": "0x493e0", + "input": "0x3b91f506000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e8", + "to": "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json new file mode 100644 index 0000000..ed3688a --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json @@ -0,0 +1,71 @@ +{ + "genesis": { + "difficulty": "117067574", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712380", + "hash": "0xe05db05eeb3f288041ecb10a787df121c0ed69499355716e17c307de313a4486", + "miner": "0x0c062b329265c965deef1eede55183b3acb8f611", + "mixHash": "0xb669ae39118a53d2c65fd3b1e1d3850dd3f8c6842030698ed846a2762d68b61d", + "nonce": "0x2b469722b8e28c45", + "number": "24973", + "stateRoot": "0x532a5c3f75453a696428db078e32ae283c85cb97e4d8560dbdf022adac6df369", + "timestamp": "1479891145", + "totalDifficulty": "1892250259406", + "alloc": { + "0x6c06b16512b332e6cd8293a2974872674716ce18": { + "balance": "0x0", + "nonce": "1", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900480632e1a7d4d146036575b6000565b34600057604e60048080359060200190919050506050565b005b3373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f19350505050505b5056", + "storage": {} + }, + "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31": { + "balance": "0x229ebbb36c3e0f20", + "nonce": "3", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 3, + "homesteadBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "byzantiumBlock": 1700000, + "constantinopleBlock": 4230000, + "petersburgBlock": 4939394, + "istanbulBlock": 6485846, + "muirGlacierBlock": 7117117, + "ethash": {} + } + }, + "context": { + "number": "24974", + "difficulty": "117067574", + "timestamp": "1479891162", + "gasLimit": "4712388", + "miner": "0xc822ef32e6d26e170b70cf761e204c1806265914" + }, + "input": "0xf889038504a81557008301f97e946c06b16512b332e6cd8293a2974872674716ce1880a42e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b1600002aa0e2a6558040c5d72bc59f2fb62a38993a314c849cd22fb393018d2c5af3112095a01bdb6d7ba32263ccc2ecc880d38c49d9f0c5a72d8b7908e3122b31356d349745", + "result": { + "type": "CALL", + "from": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31", + "to": "0x6c06b16512b332e6cd8293a2974872674716ce18", + "value": "0x0", + "gas": "0x1f97e", + "gasUsed": "0x72de", + "input": "0x2e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b160000", + "calls": [{ + "from":"0x6c06b16512b332e6cd8293a2974872674716ce18", + "gas":"0x8fc", + "gasUsed":"0x0", + "to":"0x66fdfd05e46126a07465ad24e40cc0597bc1ef31", + "input":"0x", + "error":"insufficient balance for transfer", + "value":"0x14d1120d7b160000", + "type":"CALL" + }] + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json new file mode 100644 index 0000000..0108c93 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json @@ -0,0 +1,86 @@ +{ + "genesis": { + "baseFeePerGas": "1000000000", + "difficulty": "1", + "extraData": "0x00000000000000000000000000000000000000000000000000000000000000003623191d4ccfbbdf09e8ebf6382a1f8257417bc10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "11500000", + "hash": "0x2af138b8a06e65b8dd0999df70b9e87609e9fc91ea201f08b1cc4f25ef01fcf6", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0", + "stateRoot": "0xa775801d572e9b79585eb131d18d79f8a0f71895455ab9a5b656911428e11708", + "timestamp": "0", + "totalDifficulty": "1", + "alloc": { + "0x3623191d4ccfbbdf09e8ebf6382a1f8257417bc1": { + "balance": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7" + }, + "0xd15abca351f79181dedfb6d019e382db90f3628a": { + "balance": "0x0" + } + }, + "config": { + "chainId": 1337, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "clique": { + "period": 0, + "epoch": 30000 + } + } + }, + "context": { + "number": "1", + "difficulty": "2", + "timestamp": "1665537018", + "gasLimit": "11511229", + "miner": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "875000000" + }, + "input": "0x02f9029d82053980849502f90085010c388d00832dc6c08080b90241608060405234801561001057600080fd5b50600060405161001f906100a2565b604051809103906000f08015801561003b573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c04062266040518163ffffffff1660e01b815260040160006040518083038186803b15801561008457600080fd5b505afa158015610098573d6000803e3d6000fd5b50505050506100af565b610145806100fc83390190565b603f806100bd6000396000f3fe6080604052600080fdfea264697066735822122077f7dbd3450d6e817079cf3fe27107de5768bb3163a402b94e2206b468eb025664736f6c63430008070033608060405234801561001057600080fd5b50610125806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c040622614602d575b600080fd5b60336035565b005b60036002116076576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401606d906097565b60405180910390fd5b565b6000608360128360b5565b9150608c8260c6565b602082019050919050565b6000602082019050818103600083015260ae816078565b9050919050565b600082825260208201905092915050565b7f546869732063616c6c6564206661696c6564000000000000000000000000000060008201525056fea264697066735822122033f8d92e29d467e5ea08d0024eab0b36b86b8cdb3542c6e89dbaabeb8ffaa42064736f6c63430008070033c001a07566181071cabaf58b70fc41557eb813bfc7a24f5c58554e7fed0bf7c031f169a0420af50b5fe791a4d839e181a676db5250b415dfb35cb85d544db7a1475ae2cc", + "result": { + "from": "0x3623191d4ccfbbdf09e8ebf6382a1f8257417bc1", + "gas": "0x2dc6c0", + "gasUsed": "0x25590", + "input": "0x608060405234801561001057600080fd5b50600060405161001f906100a2565b604051809103906000f08015801561003b573d6000803e3d6000fd5b5090508073ffffffffffffffffffffffffffffffffffffffff1663c04062266040518163ffffffff1660e01b815260040160006040518083038186803b15801561008457600080fd5b505afa158015610098573d6000803e3d6000fd5b50505050506100af565b610145806100fc83390190565b603f806100bd6000396000f3fe6080604052600080fdfea264697066735822122077f7dbd3450d6e817079cf3fe27107de5768bb3163a402b94e2206b468eb025664736f6c63430008070033608060405234801561001057600080fd5b50610125806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c040622614602d575b600080fd5b60336035565b005b60036002116076576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401606d906097565b60405180910390fd5b565b6000608360128360b5565b9150608c8260c6565b602082019050919050565b6000602082019050818103600083015260ae816078565b9050919050565b600082825260208201905092915050565b7f546869732063616c6c6564206661696c6564000000000000000000000000000060008201525056fea264697066735822122033f8d92e29d467e5ea08d0024eab0b36b86b8cdb3542c6e89dbaabeb8ffaa42064736f6c63430008070033", + "output": "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012546869732063616c6c6564206661696c65640000000000000000000000000000", + "error": "execution reverted", + "revertReason": "This called failed", + "calls": [ + { + "from": "0xdebfb4b387033eac57af7b3de5116dd60056803b", + "gas": "0x2ba851", + "gasUsed": "0xe557", + "to": "0xd15abca351f79181dedfb6d019e382db90f3628a", + "input": "0x608060405234801561001057600080fd5b50610125806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c040622614602d575b600080fd5b60336035565b005b60036002116076576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401606d906097565b60405180910390fd5b565b6000608360128360b5565b9150608c8260c6565b602082019050919050565b6000602082019050818103600083015260ae816078565b9050919050565b600082825260208201905092915050565b7f546869732063616c6c6564206661696c6564000000000000000000000000000060008201525056fea264697066735822122033f8d92e29d467e5ea08d0024eab0b36b86b8cdb3542c6e89dbaabeb8ffaa42064736f6c63430008070033", + "output": "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063c040622614602d575b600080fd5b60336035565b005b60036002116076576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401606d906097565b60405180910390fd5b565b6000608360128360b5565b9150608c8260c6565b602082019050919050565b6000602082019050818103600083015260ae816078565b9050919050565b600082825260208201905092915050565b7f546869732063616c6c6564206661696c6564000000000000000000000000000060008201525056fea264697066735822122033f8d92e29d467e5ea08d0024eab0b36b86b8cdb3542c6e89dbaabeb8ffaa42064736f6c63430008070033", + "value": "0x0", + "type": "CREATE" + }, + { + "from": "0xdebfb4b387033eac57af7b3de5116dd60056803b", + "gas": "0x2ac548", + "gasUsed": "0x1b2", + "to": "0xd15abca351f79181dedfb6d019e382db90f3628a", + "input": "0xc0406226", + "output": "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012546869732063616c6c6564206661696c65640000000000000000000000000000", + "error": "execution reverted", + "revertReason": "This called failed", + "type": "STATICCALL" + } + ], + "value": "0x0", + "type": "CREATE" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json new file mode 100644 index 0000000..a023ed6 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json @@ -0,0 +1,81 @@ +{ + "context": { + "difficulty": "3956606365", + "gasLimit": "5413248", + "miner": "0x00d8ae40d9a06d0e7a2877b62e32eb959afbe16d", + "number": "2295104", + "timestamp": "1513681256" + }, + "genesis": { + "alloc": { + "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76": { + "balance": "0x0", + "code": "0x60606040526004361061015e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680625b4487146101a257806311df9995146101cb578063278ecde11461022057806330adce0e146102435780633197cbb61461026c5780634bb278f3146102955780636103d70b146102aa57806363a599a4146102bf5780636a2d1cb8146102d457806375f12b21146102fd57806378e979251461032a578063801db9cc1461035357806386d1a69f1461037c5780638da5cb5b146103915780638ef26a71146103e65780639890220b1461040f5780639b39caef14610424578063b85dfb801461044d578063be9a6555146104a1578063ccb07cef146104b6578063d06c91e4146104e3578063d669e1d414610538578063df40503c14610561578063e2982c2114610576578063f02e030d146105c3578063f2fde38b146105d8578063f3283fba14610611575b600060149054906101000a900460ff1615151561017a57600080fd5b60075442108061018b575060085442115b15151561019757600080fd5b6101a03361064a565b005b34156101ad57600080fd5b6101b5610925565b6040518082815260200191505060405180910390f35b34156101d657600080fd5b6101de61092b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561022b57600080fd5b6102416004808035906020019091905050610951565b005b341561024e57600080fd5b610256610c48565b6040518082815260200191505060405180910390f35b341561027757600080fd5b61027f610c4e565b6040518082815260200191505060405180910390f35b34156102a057600080fd5b6102a8610c54565b005b34156102b557600080fd5b6102bd610f3e565b005b34156102ca57600080fd5b6102d261105d565b005b34156102df57600080fd5b6102e76110d5565b6040518082815260200191505060405180910390f35b341561030857600080fd5b6103106110e1565b604051808215151515815260200191505060405180910390f35b341561033557600080fd5b61033d6110f4565b6040518082815260200191505060405180910390f35b341561035e57600080fd5b6103666110fa565b6040518082815260200191505060405180910390f35b341561038757600080fd5b61038f611104565b005b341561039c57600080fd5b6103a4611196565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156103f157600080fd5b6103f96111bb565b6040518082815260200191505060405180910390f35b341561041a57600080fd5b6104226111c1565b005b341561042f57600080fd5b610437611296565b6040518082815260200191505060405180910390f35b341561045857600080fd5b610484600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061129c565b604051808381526020018281526020019250505060405180910390f35b34156104ac57600080fd5b6104b46112c0565b005b34156104c157600080fd5b6104c9611341565b604051808215151515815260200191505060405180910390f35b34156104ee57600080fd5b6104f6611354565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561054357600080fd5b61054b61137a565b6040518082815260200191505060405180910390f35b341561056c57600080fd5b610574611385565b005b341561058157600080fd5b6105ad600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506116c3565b6040518082815260200191505060405180910390f35b34156105ce57600080fd5b6105d66116db565b005b34156105e357600080fd5b61060f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611829565b005b341561061c57600080fd5b610648600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506118fe565b005b600080670de0b6b3a7640000341015151561066457600080fd5b61069b610696670de0b6b3a7640000610688610258346119d990919063ffffffff16565b611a0c90919063ffffffff16565b611a27565b9150660221b262dd80006106ba60065484611a7e90919063ffffffff16565b111515156106c757600080fd5b600a60008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84846000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b15156107d557600080fd5b6102c65a03f115156107e657600080fd5b5050506040518051905050610808828260010154611a7e90919063ffffffff16565b8160010181905550610827348260000154611a7e90919063ffffffff16565b816000018190555061084434600554611a7e90919063ffffffff16565b60058190555061085f82600654611a7e90919063ffffffff16565b6006819055503373ffffffffffffffffffffffffffffffffffffffff167ff3c1c7c0eb1328ddc834c4c9e579c06d35f443bf1102b034653624a239c7a40c836040518082815260200191505060405180910390a27fd1dc370699ae69fb860ed754789a4327413ec1cd379b93f2cbedf449a26b0e8583600554604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1505050565b60025481565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600060085442108061096b5750651b48eb57e00060065410155b15151561097757600080fd5b600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154821415156109c757600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166323b872dd3330856000604051602001526040518463ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b1515610ac857600080fd5b6102c65a03f11515610ad957600080fd5b5050506040518051905050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166342966c68836000604051602001526040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b1515610b7d57600080fd5b6102c65a03f11515610b8e57600080fd5b505050604051805190501515610ba357600080fd5b600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000015490506000600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001819055506000811115610c4457610c433382611a9c565b5b5050565b60055481565b60085481565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610cb157600080fd5b600854421015610cd357660221b262dd8000600654141515610cd257600080fd5b5b651b48eb57e000600654108015610cf057506213c6806008540142105b151515610cfc57600080fd5b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f193505050501515610d7557600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231306000604051602001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b1515610e3a57600080fd5b6102c65a03f11515610e4b57600080fd5b5050506040518051905090506000811115610f2057600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166342966c68826000604051602001526040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b1515610ef957600080fd5b6102c65a03f11515610f0a57600080fd5b505050604051805190501515610f1f57600080fd5b5b6001600960006101000a81548160ff02191690831515021790555050565b600080339150600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008114151515610f9657600080fd5b803073ffffffffffffffffffffffffffffffffffffffff163110151515610fbc57600080fd5b610fd181600254611b5090919063ffffffff16565b6002819055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561105957fe5b5050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156110b857600080fd5b6001600060146101000a81548160ff021916908315150217905550565b670de0b6b3a764000081565b600060149054906101000a900460ff1681565b60075481565b651b48eb57e00081565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561115f57600080fd5b600060149054906101000a900460ff16151561117a57600080fd5b60008060146101000a81548160ff021916908315150217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60065481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561121c57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151561129457600080fd5b565b61025881565b600a6020528060005260406000206000915090508060000154908060010154905082565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561131b57600080fd5b600060075414151561132c57600080fd5b4260078190555062278d004201600881905550565b600960009054906101000a900460ff1681565b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b660221b262dd800081565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156113e557600080fd5b600654660221b262dd800003925061142b670de0b6b3a764000061141c610258670de0b6b3a76400006119d990919063ffffffff16565b81151561142557fe5b04611a27565b915081831115151561143c57600080fd5b600a60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16856000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b151561158c57600080fd5b6102c65a03f1151561159d57600080fd5b50505060405180519050506115bf838260010154611a7e90919063ffffffff16565b81600101819055506115dc83600654611a7e90919063ffffffff16565b6006819055503073ffffffffffffffffffffffffffffffffffffffff167ff3c1c7c0eb1328ddc834c4c9e579c06d35f443bf1102b034653624a239c7a40c846040518082815260200191505060405180910390a27fd1dc370699ae69fb860ed754789a4327413ec1cd379b93f2cbedf449a26b0e856000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600554604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1505050565b60016020528060005260406000206000915090505481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561173657600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2fde38b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b151561181357600080fd5b6102c65a03f1151561182457600080fd5b505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561188457600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415156118fb57806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561195957600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415151561199557600080fd5b80600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600080828402905060008414806119fa57508284828115156119f757fe5b04145b1515611a0257fe5b8091505092915050565b6000808284811515611a1a57fe5b0490508091505092915050565b6000611a416202a300600754611a7e90919063ffffffff16565b421015611a7557611a6e611a5f600584611a0c90919063ffffffff16565b83611a7e90919063ffffffff16565b9050611a79565b8190505b919050565b6000808284019050838110151515611a9257fe5b8091505092915050565b611aee81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054611a7e90919063ffffffff16565b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611b4681600254611a7e90919063ffffffff16565b6002819055505050565b6000828211151515611b5e57fe5b8183039050929150505600a165627a7a72305820ec0d82a406896ccf20989b3d6e650abe4dc104e400837f1f58e67ef499493ae90029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000008d69d00910d0b2afb2a99ed6c16c8129fa8e1751", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000e819f024b41358d2c08e3a868a5c5dd0566078d4", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x000000000000000000000000000000000000000000000000000000005a388981", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x000000000000000000000000000000000000000000000000000000005a3b38e6" + } + }, + "0xd4fcab9f0a6dc0493af47c864f6f17a8a5e2e826": { + "balance": "0x2a2dd979a35cf000", + "code": "0x", + "nonce": "0", + "storage": {} + }, + "0xe819f024b41358d2c08e3a868a5c5dd0566078d4": { + "balance": "0x0", + "code": "0x6060604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100bf578063095ea7b31461014d57806318160ddd146101a757806323b872dd146101d0578063313ce5671461024957806342966c681461027257806370a08231146102ad5780638da5cb5b146102fa57806395d89b411461034f578063a9059cbb146103dd578063dd62ed3e14610437578063f2fde38b146104a3575b600080fd5b34156100ca57600080fd5b6100d26104dc565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101125780820151818401526020810190506100f7565b50505050905090810190601f16801561013f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015857600080fd5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610515565b604051808215151515815260200191505060405180910390f35b34156101b257600080fd5b6101ba61069c565b6040518082815260200191505060405180910390f35b34156101db57600080fd5b61022f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506106a2565b604051808215151515815260200191505060405180910390f35b341561025457600080fd5b61025c610952565b6040518082815260200191505060405180910390f35b341561027d57600080fd5b6102936004808035906020019091905050610957565b604051808215151515815260200191505060405180910390f35b34156102b857600080fd5b6102e4600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610abe565b6040518082815260200191505060405180910390f35b341561030557600080fd5b61030d610b07565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561035a57600080fd5b610362610b2d565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103a2578082015181840152602081019050610387565b50505050905090810190601f1680156103cf5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156103e857600080fd5b61041d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610b66565b604051808215151515815260200191505060405180910390f35b341561044257600080fd5b61048d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610d01565b6040518082815260200191505060405180910390f35b34156104ae57600080fd5b6104da600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610d88565b005b6040805190810160405280600b81526020017f416c6c436f6465436f696e00000000000000000000000000000000000000000081525081565b6000808214806105a157506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054145b15156105ac57600080fd5b81600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60005481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905061077683600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e5f90919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061080b83600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506108618382610e7d90919063ffffffff16565b600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a360019150509392505050565b600681565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156109b557600080fd5b610a0782600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610a5f82600054610e7d90919063ffffffff16565b60008190555060003373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050919050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6040805190810160405280600481526020017f414c4c430000000000000000000000000000000000000000000000000000000081525081565b6000610bba82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610c4f82600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e5f90919063ffffffff16565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610de457600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141515610e5c5780600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50565b6000808284019050838110151515610e7357fe5b8091505092915050565b6000828211151515610e8b57fe5b8183039050929150505600a165627a7a7230582059f3ea3df0b054e9ab711f37969684ba83fe38f255ffe2c8d850d951121c51100029", + "nonce": "1", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3956606365", + "extraData": "0x566961425443", + "gasLimit": "5418523", + "hash": "0x6f37eb930a25da673ea1bb80fd9e32ddac19cdf7cd4bb2eac62cc13598624077", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "mixHash": "0x10971cde68c587c750c23b8589ae868ce82c2c646636b97e7d9856470c5297c7", + "nonce": "0x810f923ff4b450a1", + "number": "2295103", + "stateRoot": "0xff403612573d76dfdaf4fea2429b77dbe9764021ae0e38dc8ac79a3cf551179e", + "timestamp": "1513681246", + "totalDifficulty": "7162347056825919" + }, + "input": "0xf86d808504e3b292008307dfa69433056b5dcac09a9b4becad0e1dcf92c19bd0af76880e92596fd62900008029a0e5f27bb66431f7081bb7f1f242003056d7f3f35414c352cd3d1848b52716dac2a07d0be78980edb0bd2a0678fc53aa90ea9558ce346b0d947967216918ac74ccea", + "result": { + "calls": [ + { + "error": "invalid opcode: INVALID", + "from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76", + "gas": "0x75fe3", + "gasUsed": "0x75fe3", + "input": "0xa9059cbb000000000000000000000000d4fcab9f0a6dc0493af47c864f6f17a8a5e2e82600000000000000000000000000000000000000000000000000000000000002f4", + "to": "0xe819f024b41358d2c08e3a868a5c5dd0566078d4", + "type": "CALL", + "value": "0x0" + } + ], + "error": "execution reverted", + "from": "0xd4fcab9f0a6dc0493af47c864f6f17a8a5e2e826", + "gas": "0x7dfa6", + "gasUsed": "0x7c1c8", + "input": "0x", + "to": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76", + "type": "CALL", + "value": "0xe92596fd6290000" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.md b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.md new file mode 100644 index 0000000..2700578 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.md @@ -0,0 +1,19 @@ +This test tests out the trace generated by the deployment of this contract: + +```solidity +contract Revertor { + function run() public pure { + require(2 > 3, "This called failed"); + } +} + +contract Contract { + constructor() { + Revertor r = new Revertor(); + r.run(); + } +} +``` + +The trace should show a revert, with the revert reason for both the top-call as well +as the inner call. diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/oog.json b/eth/tracers/internal/tracetest/testdata/call_tracer/oog.json new file mode 100644 index 0000000..333bdd0 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/oog.json @@ -0,0 +1,60 @@ +{ + "context": { + "difficulty": "3699098917", + "gasLimit": "5258985", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "number": "2294631", + "timestamp": "1513675366" + }, + "genesis": { + "alloc": { + "0x43064693d3d38ad6a7cb579e0d6d9718c8aa6b62": { + "balance": "0x0", + "code": "0x6060604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100bf578063095ea7b31461014d57806318160ddd146101a757806323b872dd146101d0578063313ce5671461024957806342966c68146102785780635a3b7e42146102b357806370a082311461034157806379cc67901461038e57806395d89b41146103e8578063a9059cbb14610476578063dd62ed3e146104b8575b600080fd5b34156100ca57600080fd5b6100d2610524565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101125780820151818401526020810190506100f7565b50505050905090810190601f16801561013f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015857600080fd5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061055d565b604051808215151515815260200191505060405180910390f35b34156101b257600080fd5b6101ba6105ea565b6040518082815260200191505060405180910390f35b34156101db57600080fd5b61022f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506105f0565b604051808215151515815260200191505060405180910390f35b341561025457600080fd5b61025c610910565b604051808260ff1660ff16815260200191505060405180910390f35b341561028357600080fd5b6102996004808035906020019091905050610915565b604051808215151515815260200191505060405180910390f35b34156102be57600080fd5b6102c6610a18565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103065780820151818401526020810190506102eb565b50505050905090810190601f1680156103335780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561034c57600080fd5b610378600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a51565b6040518082815260200191505060405180910390f35b341561039957600080fd5b6103ce600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610a69565b604051808215151515815260200191505060405180910390f35b34156103f357600080fd5b6103fb610bf8565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561043b578082015181840152602081019050610420565b50505050905090810190601f1680156104685780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561048157600080fd5b6104b6600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610c31565b005b34156104c357600080fd5b61050e600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610e34565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f446f70616d696e6500000000000000000000000000000000000000000000000081525081565b600081600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506001905092915050565b60005481565b6000808373ffffffffffffffffffffffffffffffffffffffff161415151561061757600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561066557600080fd5b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401101515156106f157fe5b600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115151561077c57600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555081600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b601281565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561096557600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5836040518082815260200191505060405180910390a260019050919050565b6040805190810160405280600981526020017f446f706d6e20302e32000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b600081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ab957600080fd5b600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151515610b4457600080fd5b81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160008082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5836040518082815260200191505060405180910390a26001905092915050565b6040805190810160405280600581526020017f444f504d4e00000000000000000000000000000000000000000000000000000081525081565b60008273ffffffffffffffffffffffffffffffffffffffff1614151515610c5757600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ca557600080fd5b600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205481600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020540110151515610d3157fe5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b60026020528160005260406000206020528060005260406000206000915091505054815600a165627a7a723058206d93424f4e7b11929b8276a269038402c10c0ddf21800e999916ddd9dff4a7630029", + "nonce": "1", + "storage": { + "0x296b66049cc4f9c8bf3d4f14752add261d1a980b39bdd194a7897baf39ac7579": "0x0000000000000000000000000000000000000000033b2e3c9fc9653f9e72b1e0" + } + }, + "0x94194bc2aaf494501d7880b61274a169f6502a54": { + "balance": "0xea8c39a876d19888d", + "code": "0x", + "nonce": "265", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3699098917", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "5263953", + "hash": "0x03a0f62a8106793dafcfae7b75fd2654322062d585a19cea568314d7205790dc", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x15482cc64b7c00a947f5bf015dfc010db1a6a668c74df61974d6a7848c174408", + "nonce": "0xd1bdb150f6fd170e", + "number": "2294630", + "stateRoot": "0x1ab1a534e84cc787cda1db21e0d5920ab06017948075b759166cfea7274657a1", + "timestamp": "1513675347", + "totalDifficulty": "7160543502214733" + }, + "input": "0xf8ab820109855d21dba00082ca1d9443064693d3d38ad6a7cb579e0d6d9718c8aa6b6280b844a9059cbb000000000000000000000000e77b1ac803616503510bed0086e3a7be2627a69900000000000000000000000000000000000000000000000000000009502f90001ba0ce3ad83f5530136467b7c2bb225f406bd170f4ad59c254e5103c34eeabb5bd69a0455154527224a42ab405cacf0fe92918a75641ce4152f8db292019a5527aa956", + "result": { + "error": "out of gas", + "from": "0x94194bc2aaf494501d7880b61274a169f6502a54", + "gas": "0xca1d", + "gasUsed": "0xca1d", + "input": "0xa9059cbb000000000000000000000000e77b1ac803616503510bed0086e3a7be2627a69900000000000000000000000000000000000000000000000000000009502f9000", + "to": "0x43064693d3d38ad6a7cb579e0d6d9718c8aa6b62", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer/revert.json new file mode 100644 index 0000000..3207a29 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/revert.json @@ -0,0 +1,58 @@ +{ + "context": { + "difficulty": "3665057456", + "gasLimit": "5232723", + "miner": "0xf4d8e706cfb25c0decbbdd4d2e2cc10c66376a3f", + "number": "2294501", + "timestamp": "1513673601" + }, + "genesis": { + "alloc": { + "0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9": { + "balance": "0x2a3fc32bcc019283", + "code": "0x", + "nonce": "10", + "storage": {} + }, + "0xabbcd5b340c80b5f1c0545c04c987b87310296ae": { + "balance": "0x0", + "code": "0x606060405236156100755763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416632d0335ab811461007a578063548db174146100ab5780637f649783146100fc578063b092145e1461014d578063c3f44c0a14610186578063c47cf5de14610203575b600080fd5b341561008557600080fd5b610099600160a060020a0360043516610270565b60405190815260200160405180910390f35b34156100b657600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061028f95505050505050565b005b341561010757600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061029e95505050505050565b005b341561015857600080fd5b610172600160a060020a03600435811690602435166102ad565b604051901515815260200160405180910390f35b341561019157600080fd5b6100fa6004803560ff1690602480359160443591606435600160a060020a0316919060a49060843590810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965050509235600160a060020a031692506102cd915050565b005b341561020e57600080fd5b61025460046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061056a95505050505050565b604051600160a060020a03909116815260200160405180910390f35b600160a060020a0381166000908152602081905260409020545b919050565b61029a816000610594565b5b50565b61029a816001610594565b5b50565b600160209081526000928352604080842090915290825290205460ff1681565b60008080600160a060020a038416158061030d5750600160a060020a038085166000908152600160209081526040808320339094168352929052205460ff165b151561031857600080fd5b6103218561056a565b600160a060020a038116600090815260208190526040808220549295507f19000000000000000000000000000000000000000000000000000000000000009230918891908b908b90517fff000000000000000000000000000000000000000000000000000000000000008089168252871660018201526c01000000000000000000000000600160a060020a038088168202600284015286811682026016840152602a8301869052841602604a820152605e810182805190602001908083835b6020831061040057805182525b601f1990920191602091820191016103e0565b6001836020036101000a0380198251168184511617909252505050919091019850604097505050505050505051809103902091506001828a8a8a6040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f1151561049957600080fd5b5050602060405103519050600160a060020a03838116908216146104bc57600080fd5b600160a060020a0380841660009081526020819052604090819020805460010190559087169086905180828051906020019080838360005b8381101561050d5780820151818401525b6020016104f4565b50505050905090810190601f16801561053a5780820380516001836020036101000a031916815260200191505b5091505060006040518083038160008661646e5a03f1915050151561055e57600080fd5b5b505050505050505050565b600060248251101561057e5750600061028a565b600160a060020a0360248301511690505b919050565b60005b825181101561060157600160a060020a033316600090815260016020526040812083918584815181106105c657fe5b90602001906020020151600160a060020a031681526020810191909152604001600020805460ff19169115159190911790555b600101610597565b5b5050505600a165627a7a723058200027e8b695e9d2dea9f3629519022a69f3a1d23055ce86406e686ea54f31ee9c0029", + "nonce": "1", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3672229776", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "5227619", + "hash": "0xa07b3d6c6bf63f5f981016db9f2d1d93033833f2c17e8bf7209e85f1faf08076", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x806e151ce2817be922e93e8d5921fa0f0d0fd213d6b2b9a3fa17458e74a163d0", + "nonce": "0xbc5d43adc2c30c7d", + "number": "2294500", + "stateRoot": "0xca645b335888352ef9d8b1ef083e9019648180b259026572e3139717270de97d", + "timestamp": "1513673552", + "totalDifficulty": "7160066586979149" + }, + "input": "0xf9018b0a8505d21dba00832dc6c094abbcd5b340c80b5f1c0545c04c987b87310296ae80b9012473b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988000000000000000000000000000000000000000000000000000000000000000000000000000000001ba0fd659d76a4edbd2a823e324c93f78ad6803b30ff4a9c8bce71ba82798975c70ca06571eecc0b765688ec6c78942c5ee8b585e00988c0141b518287e9be919bc48a", + "result": { + "error": "execution reverted", + "from": "0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9", + "gas": "0x2dc6c0", + "gasUsed": "0x719b", + "input": "0x73b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a98800000000000000000000000000000000000000000000000000000000000000000000000000000000", + "to": "0xabbcd5b340c80b5f1c0545c04c987b87310296ae", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json new file mode 100644 index 0000000..f02e5c6 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json @@ -0,0 +1,65 @@ +{ + "context": { + "difficulty": "2", + "gasLimit": "8000000", + "miner": "0x0000000000000000000000000000000000000000", + "number": "3212651", + "timestamp": "1597246515" + }, + "genesis": { + "alloc": { + "0xf58833cf0c791881b494eb79d461e08a1f043f52": { + "balance": "0x0", + "code": "0x608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063609ff1bd11610078578063609ff1bd146101af5780639e7b8d61146101cd578063a3ec138d14610211578063e2ba53f0146102ae576100a5565b80630121b93f146100aa578063013cf08b146100d85780632e4176cf146101215780635c19a95c1461016b575b600080fd5b6100d6600480360360208110156100c057600080fd5b81019080803590602001909291905050506102cc565b005b610104600480360360208110156100ee57600080fd5b8101908080359060200190929190505050610469565b604051808381526020018281526020019250505060405180910390f35b61012961049a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101ad6004803603602081101561018157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506104bf565b005b6101b76108db565b6040518082815260200191505060405180910390f35b61020f600480360360208110156101e357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610952565b005b6102536004803603602081101561022757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b53565b60405180858152602001841515151581526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200194505050505060405180910390f35b6102b6610bb0565b6040518082815260200191505060405180910390f35b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160000154141561038a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f486173206e6f20726967687420746f20766f746500000000000000000000000081525060200191505060405180910390fd5b8060010160009054906101000a900460ff161561040f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f416c726561647920766f7465642e00000000000000000000000000000000000081525060200191505060405180910390fd5b60018160010160006101000a81548160ff02191690831515021790555081816002018190555080600001546002838154811061044757fe5b9060005260206000209060020201600101600082825401925050819055505050565b6002818154811061047657fe5b90600052602060002090600202016000915090508060000154908060010154905082565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060010160009054906101000a900460ff1615610587576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f596f7520616c726561647920766f7465642e000000000000000000000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610629576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e000081525060200191505060405180910390fd5b5b600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146107cc57600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691503373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156107c7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f466f756e64206c6f6f7020696e2064656c65676174696f6e2e0000000000000081525060200191505060405180910390fd5b61062a565b60018160010160006101000a81548160ff021916908315150217905550818160010160016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060010160009054906101000a900460ff16156108bf578160000154600282600201548154811061089c57fe5b9060005260206000209060020201600101600082825401925050819055506108d6565b816000015481600001600082825401925050819055505b505050565b6000806000905060008090505b60028054905081101561094d57816002828154811061090357fe5b9060005260206000209060020201600101541115610940576002818154811061092857fe5b90600052602060002090600202016001015491508092505b80806001019150506108e8565b505090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146109f7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180610bde6028913960400191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160009054906101000a900460ff1615610aba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f54686520766f74657220616c726561647920766f7465642e000000000000000081525060200191505060405180910390fd5b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000015414610b0957600080fd5b60018060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018190555050565b60016020528060005260406000206000915090508060000154908060010160009054906101000a900460ff16908060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060020154905084565b60006002610bbc6108db565b81548110610bc657fe5b90600052602060002090600202016000015490509056fe4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652ea26469706673582212201d282819f8f06fed792100d60a8b08809b081a34a1ecd225e83a4b41122165ed64736f6c63430006060033", + "nonce": "1", + "storage": { + "0x6200beec95762de01ce05f2a0e58ce3299dbb53c68c9f3254a242121223cdf58": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0xf7579c3d8a669c89d5ed246a22eb6db8f6fedbf1": { + "balance": "0x57af9d6b3df812900", + "code": "0x", + "nonce": "6", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "IstanbulBlock": 1561651, + "chainId": 5, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf888068449504f80832dc6c094f58833cf0c791881b494eb79d461e08a1f043f5280a45c19a95c000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf12da0264664db3e71fae1dbdaf2f53954be149ad3b7ba8a5054b4d89c70febfacc8b1a0212e8398757963f419681839ae8c5a54b411e252473c82d93dda68405ca63294", + "result": { + "error": "execution reverted", + "from": "0xf7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "gas": "0x2dc6c0", + "gasUsed": "0x5940", + "input": "0x5c19a95c000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "to": "0xf58833cf0c791881b494eb79d461e08a1f043f52", + "type": "CALL", + "value": "0x0", + "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e0000", + "revertReason": "Self-delegation is disallowed." + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json b/eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json new file mode 100644 index 0000000..620df1d --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json @@ -0,0 +1,74 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x61deadff", + "nonce": "1", + "storage": {} + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "result": { + "calls": [ + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x0", + "gasUsed": "0x0", + "input": "0x", + "to": "0x000000000000000000000000000000000000dead", + "type": "SELFDESTRUCT", + "value": "0x4d87094125a369d9bd5" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x15f90", + "gasUsed": "0x6fcb", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/simple.json b/eth/tracers/internal/tracetest/testdata/call_tracer/simple.json new file mode 100644 index 0000000..6c7d01d --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/simple.json @@ -0,0 +1,80 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "result": { + "calls": [ + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "gasUsed": "0x0", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x6f05b59d3b20000" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x15f90", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/simple_onlytop.json b/eth/tracers/internal/tracetest/testdata/call_tracer/simple_onlytop.json new file mode 100644 index 0000000..affb4ab --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/simple_onlytop.json @@ -0,0 +1,72 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "tracerConfig": { + "onlyTopCall": true + }, + "result": { + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x15f90", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer/throw.json new file mode 100644 index 0000000..499b449 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/throw.json @@ -0,0 +1,62 @@ +{ + "context": { + "difficulty": "117009631", + "gasLimit": "4712388", + "miner": "0x294e5d6c39a36ce38af1dca70c1060f78dee8070", + "number": "25009", + "timestamp": "1479891666" + }, + "genesis": { + "alloc": { + "0x70c9217d814985faef62b124420f8dfbddd96433": { + "balance": "0x4ecd70668f5d854a", + "code": "0x", + "nonce": "1638", + "storage": {} + }, + "0xc212e03b9e060e36facad5fd8f4435412ca22e6b": { + "balance": "0x0", + "code": "0x606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000000000000061a9", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433" + } + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "117066792", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712388", + "hash": "0xe23e8d4562a1045b70cbc99fefb20c101a8f0fc8559a80d65fea8896e2f1d46e", + "miner": "0x71842f946b98800fe6feb49f0ae4e253259031c9", + "mixHash": "0x0aada9d6e93dd4db0d09c0488dc0a048fca2ccdc1f3fc7b83ba2a8d393a3a4ff", + "nonce": "0x70849d5838dee2e9", + "number": "25008", + "stateRoot": "0x1e01d2161794768c5b917069e73d86e8dca80cd7f3168c0597de420ab93a3b7b", + "timestamp": "1479891641", + "totalDifficulty": "1896347038589" + }, + "input": "0xf88b8206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d59750", + "result": { + "error": "invalid jump destination", + "from": "0x70c9217d814985faef62b124420f8dfbddd96433", + "gas": "0x3d090", + "gasUsed": "0x3d090", + "input": "0x51a34eb8000000000000000000000000000000000000000000000027fad02094277c0000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/big_slow.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/big_slow.json new file mode 100644 index 0000000..617f52a --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/big_slow.json @@ -0,0 +1,64 @@ +{ + "genesis": { + "difficulty": "50486697699375", + "extraData": "0xd783010406844765746887676f312e362e32856c696e7578", + "gasLimit": "4788482", + "hash": "0xf6bbc5bbe34d5c93fd5b4712cd498d1026b8b0f586efefe7fe30231ed6b8a1a5", + "miner": "0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1", + "mixHash": "0xabca93555584c0463ee5c212251dd002bb3a93a157e06614276f93de53d4fdb8", + "nonce": "0xa64136fcb9c2d4ca", + "number": "1719576", + "stateRoot": "0xab5eec2177a92d633e282936af66c46e24cfa8f2fdc2b8155f33885f483d06f3", + "timestamp": "1466150166", + "totalDifficulty": "28295412423546970038", + "alloc": { + "0xf8bda96b67036ee48107f2a0695ea673479dda56": { + "balance": "0x1529e844f9ecdeec", + "nonce": "33", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 1, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 3000000, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 1150000, + "byzantiumBlock": 8772000, + "constantinopleBlock": 9573000, + "petersburgBlock": 10500839, + "istanbulBlock": 10500839 + } + }, + "context": { + "number": "1719577", + "difficulty": "50486697732143", + "timestamp": "1466150178", + "gasLimit": "4788484", + "miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226" + }, + "input": "0xf874218504a817c800832318608080a35b620186a05a131560135760016020526000565b600080601f600039601f565b6000f31ba0575fa000a1f06659a7b6d3c7877601519a4997f04293f0dfa0eee6d8cd840c77a04c52ce50719ee2ff7a0c5753f4ee69c0340666f582dbb5148845a354ca726e4a", + "result": [ + { + "action": { + "from": "0xf8bda96b67036ee48107f2a0695ea673479dda56", + "gas": "0x231860", + "init": "0x5b620186a05a131560135760016020526000565b600080601f600039601f565b6000f3", + "value": "0x0" + }, + "blockNumber": 1719577, + "result": { + "address": "0xb2e6a2546c45889427757171ab05b8b438525b42", + "code": "0x", + "gasUsed": "0x219202" + }, + "subtraces": 0, + "traceAddress": [], + "type": "create" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_fail_hide.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_fail_hide.json new file mode 100644 index 0000000..a2386ea --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_fail_hide.json @@ -0,0 +1,89 @@ +{ + "genesis": { + "difficulty": "4671584", + "extraData": "0xd683010b05846765746886676f312e3133856c696e7578", + "gasLimit": "9435026", + "hash": "0x755bd54de4b2f5a7a589a10d69888b4ead48a6311d5d69f2f69ca85ec35fbe0b", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "mixHash": "0x3a44525624571c31344ba57780f7664098fe7cbeafe532bcdee76a23fc474ba0", + "nonce": "0x6dca647c00c72bbf", + "number": "1555278", + "stateRoot": "0x5f56d8323ee384b0c8d1de49d63e150e17283eea813483698362bc0ec9e0242a", + "timestamp": "1590795319", + "totalDifficulty": "2242614315030", + "alloc": { + "0x0000000000000000000000000000000000000004": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x62436e941792f02a5fb1", + "nonce": "265356", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "1555279", + "difficulty": "4669303", + "timestamp": "1590795340", + "gasLimit": "9444238", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf86f83040c8c843b9aca0083019f7880809b60206000600060006013600462030d40f26002556000516000550081a2a086ad228c89ad9664287b12a5602a635a803506904f4ce39795990ac4f945cd57a025b30ea8042d773f6c5b13d7cc1b3979f9f10ee674410b6a2112ce840d0302dc", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x19f78", + "init": "0x60206000600060006013600462030d40f260025560005160005500" + }, + "result": { + "gasUsed": "0xf3bc", + "code": "0x", + "address": "0x5f8a7e007172ba80afbff1b15f800eb0b260f224" + }, + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 74, + "transactionHash": "0x5ef60b27ac971c22a7d484e546e50093ca62300c8986d165154e47773764b6a4", + "blockNumber": 1555279, + "blockHash": "0xd6c98d1b87dfa92a210d99bad2873adaf0c9e51fe43addc63fd9cca03a5c6f46", + "time": "209.346µs" + }, + { + "action": { + "balance": "0x0", + "callType": "callcode", + "from": "0x5f8a7e007172ba80afbff1b15f800eb0b260f224", + "gas": "0xaf64", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x13" + }, + "error": "insufficient balance for transfer", + "result": {}, + "subtraces": 0, + "traceAddress": [0], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_oog.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_oog.json new file mode 100644 index 0000000..fb29e49 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_oog.json @@ -0,0 +1,94 @@ +{ + "genesis": { + "difficulty": "4671584", + "extraData": "0xd883010b05846765746888676f312e31342e33856c696e7578", + "gasLimit": "9425823", + "hash": "0x27dd7d052dbc8a29cc5b9487e1e41d842e7a643fcaea4964caa22b834964acaf", + "miner": "0x73f26d124436b0791169d63a3af29c2ae47765a3", + "mixHash": "0xb4a050624f5d147fdf02857cbfd55da3ddc1451743acc5c163861584589c3034", + "nonce": "0x3c255875b17e0573", + "number": "1555277", + "stateRoot": "0x6290d79215a2eebc25d5e456b35876c6d78ffc1ea47bdd70e375ebb3cf325620", + "timestamp": "1590795308", + "totalDifficulty": "2242609643446", + "alloc": { + "0x0000000000000000000000000000000000000001": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x624329308610ab365fb1", + "nonce": "265194", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "1555278", + "difficulty": "4671584", + "timestamp": "1590795319", + "gasLimit": "9435026", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf8ee83040bea843b9aca008301a7588080b8997f18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c600052601c6020527f73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f6040527feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549606052602060806080600060006001610bb7f260025560a060020a6080510660005560005432146001550081a1a05b9a162d84bfe84faa7c176e21c26c0083645d4dd0d566547b7be2c2da0b4259a05b37ff12a4c27634cb0da6008d9b69726d415ff4694f9bc38c7806eb1fb60ae9", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x1a758", + "init": "0x7f18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c600052601c6020527f73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f6040527feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549606052602060806080600060006001610bb7f260025560a060020a60805106600055600054321460015500" + }, + "result": { + "gasUsed": "0xf3e9", + "code": "0x", + "address": "0x568c19ecb14b87e4aec29b4d2d700a3ad3fd0613" + }, + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 141, + "transactionHash": "0x1592cbda0d928b8d18eed98857942b91ade32d088e55b8bf63418917cb0231f1", + "blockNumber": 1555278, + "blockHash": "0x755bd54de4b2f5a7a589a10d69888b4ead48a6311d5d69f2f69ca85ec35fbe0b", + "time": "300.9µs" + }, + { + "type": "call", + "action": { + "from": "0x568c19ecb14b87e4aec29b4d2d700a3ad3fd0613", + "to": "0x0000000000000000000000000000000000000001", + "value": "0x0", + "gas": "0xbb7", + "input": "0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", + "callType": "callcode" + }, + "error": "out of gas", + "traceAddress": [ + 0 + ], + "subtraces": 0, + "transactionPosition": 141, + "transactionHash": "0x1592cbda0d928b8d18eed98857942b91ade32d088e55b8bf63418917cb0231f1", + "blockNumber": 1555278, + "blockHash": "0x755bd54de4b2f5a7a589a10d69888b4ead48a6311d5d69f2f69ca85ec35fbe0b" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_throw.json new file mode 100644 index 0000000..5e27261 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/callcode_precompiled_throw.json @@ -0,0 +1,90 @@ +{ + "genesis": { + "difficulty": "4683014", + "extraData": "0x537465762d63676574682d76312e31312e34", + "gasLimit": "9435044", + "hash": "0x3452ca5005cb73cd60dfa488a7b124251168e564491f80eb66765e79d78cfd95", + "miner": "0x415aa6292d1db797a467b22139704956c030e62f", + "mixHash": "0x6037612618507ae70c74a72bc2580253662971db959cfbc06d3f8527d4d01575", + "nonce": "0x314fc90dee5e39a2", + "number": "1555274", + "stateRoot": "0x795751f3f96a5de1fd3944ddd78cbfe4ef10491e1086be47609869a30929d0e5", + "timestamp": "1590795228", + "totalDifficulty": "2242595605834", + "alloc": { + "0x0000000000000000000000000000000000000009": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x6242e3ccf48e66425fb1", + "nonce": "264981", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "1555275", + "difficulty": "4683014", + "timestamp": "1590795244", + "gasLimit": "9444256", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf87a83040b15843b9aca008301a0348080a636600060003760406103e8366000600060095af26001556103e851600255610408516003550081a1a0dd883fbbb489b640dadc8c1bf151767155228d0a1321f687f070f35f14374b05a02dd0ccb16a8de39bc8ee61381bbbbb54f0ab18422afd7b03c6163da1f5023934", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x1a034", + "init": "0x36600060003760406103e8366000600060095af26001556103e8516002556104085160035500" + }, + "error": "out of gas: not enough gas for reentrancy sentry", + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 117, + "transactionHash": "0x7fe4dec901e1a62c1a1d96b8267bb9ff9dc1f75def43aa45b998743455eff8f9", + "blockNumber": 1555275, + "blockHash": "0x80945caaff2fc67253cbb0217d2e5a307afde943929e97d8b36e58b88cbb02fd", + "time": "332.877µs" + }, + { + "type": "call", + "action": { + "from": "0x8832ef498070145c3a5b30f47fbca71fd7b1de9f", + "to": "0x0000000000000000000000000000000000000009", + "value": "0x0", + "gas": "0xc897", + "input": "0x", + "callType": "callcode" + }, + "error": "invalid input length", + "traceAddress": [ + 0 + ], + "subtraces": 0, + "transactionPosition": 117, + "transactionHash": "0x7fe4dec901e1a62c1a1d96b8267bb9ff9dc1f75def43aa45b998743455eff8f9", + "blockNumber": 1555275, + "blockHash": "0x80945caaff2fc67253cbb0217d2e5a307afde943929e97d8b36e58b88cbb02fd" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/create.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/create.json new file mode 100644 index 0000000..11bc4ea --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/create.json @@ -0,0 +1,67 @@ +{ + "context": { + "difficulty": "3755480783", + "gasLimit": "5401723", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "number": "2294702", + "timestamp": "1513676146" + }, + "genesis": { + "alloc": { + "0x13e4acefe6a6700604929946e70e6443e4e73447": { + "balance": "0xcf3e0938579f000", + "code": "0x", + "nonce": "9", + "storage": {} + }, + "0x7dc9c9730689ff0b0fd506c67db815f12d90a448": { + "balance": "0x0", + "code": "0x", + "nonce": "0", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3757315409", + "extraData": "0x566961425443", + "gasLimit": "5406414", + "hash": "0xae107f592eebdd9ff8d6ba00363676096e6afb0e1007a7d3d0af88173077378d", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "mixHash": "0xc927aa05a38bc3de864e95c33b3ae559d3f39c4ccd51cef6f113f9c50ba0caf1", + "nonce": "0x93363bbd2c95f410", + "number": "2294701", + "stateRoot": "0x6b6737d5bde8058990483e915866bd1578014baeff57bd5e4ed228a2bfad635c", + "timestamp": "1513676127", + "totalDifficulty": "7160808139332585" + }, + "input": "0xf907ef098504e3b29200830897be8080b9079c606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a1129a01060f46676a5dff6f407f0f51eb6f37f5c8c54e238c70221e18e65fc29d3ea65a0557b01c50ff4ffaac8ed6e5d31237a4ecbac843ab1bfe8bb0165a0060df7c54f", + "result": [ + { + "action": { + "from": "0x13e4acefe6a6700604929946e70e6443e4e73447", + "gas": "0x897be", + "init": "0x606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a11", + "value": "0x0" + }, + "blockNumber": 2294702, + "result": { + "address": "0x7dc9c9730689ff0b0fd506c67db815f12d90a448", + "code": "0x606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029", + "gasUsed": "0x897be" + }, + "subtraces": 0, + "traceAddress": [], + "type": "create" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/deep_calls.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/deep_calls.json new file mode 100644 index 0000000..375a163 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/deep_calls.json @@ -0,0 +1,635 @@ +{ + "context": { + "difficulty": "117066904", + "gasLimit": "4712384", + "miner": "0x1977c248e1014cc103929dd7f154199c916e39ec", + "number": "25001", + "timestamp": "1479891545" + }, + "genesis": { + "alloc": { + "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38": { + "balance": "0x0", + "code": "0x606060405236156100825760e060020a600035046302d05d3f811461008a5780630accce061461009c5780631ab9075a146100c757806331ed274614610102578063645a3b7214610133578063772fdae314610155578063a7f4377914610180578063ae5f80801461019e578063c9bded21146101ea578063f905c15a14610231575b61023a610002565b61023c600054600160a060020a031681565b61023a600435602435604435606435608435600254600160a060020a03166000141561024657610002565b61023a600435600254600160a060020a03166000148015906100f8575060025433600160a060020a03908116911614155b156102f457610002565b61023a60043560243560443560643560843560a43560c435600254600160a060020a03166000141561031657610002565b61023a600435602435600254600160a060020a0316600014156103d057610002565b61023a600435602435604435606435608435600254600160a060020a03166000141561046157610002565b61023a60025433600160a060020a0390811691161461051657610002565b61023a6004356024356044356060828152600160a060020a0382169060ff8516907fa6c2f0913db6f79ff0a4365762c61718973b3413d6e40382e704782a9a5099f690602090a3505050565b61023a600435602435600160a060020a038116606090815260ff8316907fee6348a7ec70f74e3d6cba55a53e9f9110d180d7698e9117fc466ae29a43e34790602090a25050565b61023c60035481565b005b6060908152602090f35b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061029d57610002565b60408051858152602081018390528151600160a060020a03858116939087169260ff8a16927f5a690ecd0cb15c1c1fd6b6f8a32df0d4f56cb41a54fea7e94020f013595de796929181900390910190a45050505050565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061036d57610002565b6040805186815260208101869052808201859052606081018490529051600160a060020a03831691889160ff8b16917fd65d9ddafbad8824e2bbd6f56cc9f4ac27ba60737035c10a321ea2f681c94d47919081900360800190a450505050505050565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061042757610002565b60408051828152905183917fa9c6cbc4bd352a6940479f6d802a1001550581858b310d7f68f7bea51218cda6919081900360200190a25050565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506104b857610002565b80600160a060020a031684600160a060020a03168660ff167f69bdaf789251e1d3a0151259c0c715315496a7404bce9fd0b714674685c2cab78686604051808381526020018281526020019250505060405180910390a45050505050565b600254600160a060020a0316ff", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0x2cccf5e0538493c235d1c5ef6580f77d99e91396": { + "balance": "0x0", + "code": "0x606060405236156100775760e060020a600035046302d05d3f811461007f57806313bc6d4b146100915780633688a877146100b95780635188f9961461012f5780637eadc976146101545780638ad79680146101d3578063a43e04d814610238578063a7f437791461025e578063e16c7d981461027c575b61029f610002565b6102a1600054600160a060020a031681565b6102be600435600160a060020a03811660009081526002602052604090205460ff165b919050565b6102d26004356040805160208181018352600080835284815260038252835190849020805460026001821615610100026000190190911604601f8101849004840283018401909552848252929390929183018282801561037d5780601f106103525761010080835404028352916020019161037d565b61029f6004356024356000805433600160a060020a039081169116146104a957610002565b61034060043560008181526001602090815260408083205481517ff905c15a0000000000000000000000000000000000000000000000000000000081529151600160a060020a03909116928392839263f905c15a92600483810193919291829003018189876161da5a03f1156100025750506040515195945050505050565b60408051602060248035600481810135601f810185900485028601850190965285855261029f9581359591946044949293909201918190840183828082843750949650505050505050600054600160a060020a0390811633909116146104f657610002565b61029f6004355b600080548190600160a060020a0390811633909116146105a457610002565b61029f60005433600160a060020a0390811691161461072957610002565b6102a1600435600081815260016020526040902054600160a060020a03166100b4565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156103325780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161036057829003601f168201915b505050505090506100b4565b506000828152600160208181526040808420805473ffffffffffffffffffffffffffffffffffffffff191686179055600160a060020a038581168086526002909352818520805460ff191690941790935580517f1ab9075a0000000000000000000000000000000000000000000000000000000081523090931660048401525184939192631ab9075a926024828101939192829003018183876161da5a03f11561000257505060408051602081018690528082019290925243606083015260808083526003908301527f414444000000000000000000000000000000000000000000000000000000000060a0830152517f8ac68d4e97d65912f220b4c5f87978b8186320a5e378c1369850b5b5f90323d39181900360c00190a15b505050565b600083815260016020526040902054600160a060020a03838116911614156104d0576104a4565b600083815260016020526040812054600160a060020a031614610389576103898361023f565b600082815260036020908152604082208054845182855293839020919360026001831615610100026000190190921691909104601f90810184900483019391929186019083901061056a57805160ff19168380011785555b5061059a9291505b808211156105a05760008155600101610556565b8280016001018555821561054e579182015b8281111561054e57825182600050559160200191906001019061057c565b50505050565b5090565b600083815260016020526040812054600160a060020a031614156105c757610002565b50506000818152600160205260408082205481517fa7f437790000000000000000000000000000000000000000000000000000000081529151600160a060020a0391909116928392839263a7f4377992600483810193919291829003018183876161da5a03f11561000257505050600160005060008460001916815260200190815260200160002060006101000a815490600160a060020a0302191690556002600050600083600160a060020a0316815260200190815260200160002060006101000a81549060ff02191690557f8ac68d4e97d65912f220b4c5f87978b8186320a5e378c1369850b5b5f90323d383834360405180806020018560001916815260200184600160a060020a03168152602001838152602001828103825260038152602001807f44454c000000000000000000000000000000000000000000000000000000000081526020015060200194505050505060405180910390a1505050565b600054600160a060020a0316ff", + "nonce": "1", + "storage": { + "0x0684ac65a9fa32414dda56996f4183597d695987fdb82b145d722743891a6fe8": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "0x1cd76f78169a420d99346e3501dd3e541622c38a226f9b63e01cfebc69879dc7": "0x000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f", + "0x8e54a4494fe5da016bfc01363f4f6cdc91013bb5434bd2a4a3359f13a23afa2f": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf", + "0x94edf7f600ba56655fd65fca1f1424334ce369326c1dc3e53151dcd1ad06bc13": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xbbee47108b275f55f98482c6800f6372165e88b0330d3f5dae6419df4734366c": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "0xd38c0c4e84de118cfdcc775130155d83b8bbaaf23dc7f3c83a626b10473213bd": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xfb3aa5c655c2ec9d40609401f88d505d1da61afaa550e36ef5da0509ada257ba": "0x0000000000000000000000007986bad81f4cbd9317f5a46861437dae58d69113" + } + }, + "0x3e9286eafa2db8101246c2131c09b49080d00690": { + "balance": "0x0", + "code": "0x606060405236156100cf5760e060020a600035046302d05d3f81146100d7578063056d4470146100e957806316c66cc61461010c5780631ab9075a146101935780633ae1005c146101ce57806358541662146101fe5780635ed61af014610231578063644e3b791461025457806384dbac3b146102db578063949ae479146102fd5780639859387b14610321578063a7f4377914610340578063ab03fc261461035e578063e8161b7814610385578063e964d4e114610395578063f905c15a146103a5578063f92eb774146103ae575b6103be610002565b6103c0600054600160a060020a031681565b6103be6004356002546000908190600160a060020a031681141561040357610002565b6103dd60043560006108365b6040805160025460e360020a631c2d8fb30282527f636f6e747261637464620000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b6103be600435600254600160a060020a03166000148015906101c4575060025433600160a060020a03908116911614155b1561088d57610002565b6103be600435602435604435606435600254600090819081908190600160a060020a03168114156108af57610002565b6103c0600435602435604435606435608435600254600090819081908190600160a060020a03168114156110e857610002565b6103be6004356002546000908190600160a060020a03168114156115ec57610002565b6103c06004356000611b635b6040805160025460e360020a631c2d8fb30282527f6d61726b6574646200000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b6103be600435602435600254600160a060020a031660001415611bb557610002565b6103be600435602435600254600090600160a060020a0316811415611d2e57610002565b6103be600435600254600160a060020a031660001415611fc657610002565b6103be60025433600160a060020a0390811691161461207e57610002565b6103be600435602435604435600254600090600160a060020a031681141561208c57610002565b6103dd60043560006124b8610260565b6103c0600435600061250a610118565b6103f160035481565b6103f16004356000612561610260565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061046557610002565b8291506104e55b6040805160025460e360020a631c2d8fb30282527f63706f6f6c00000000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f115610002575050604051519150505b90565b600160a060020a031663b2206e6d83600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fb2206e6d0000000000000000000000000000000000000000000000000000000082526004820152600160a060020a038816602482015290516044808301935060209282900301816000876161da5a03f11561000257505060405151915061059b90506106ba565b600160a060020a031663d5b205ce83600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a636ad902e7028252600160a060020a0390811660048301526024820187905288166044820152905160648281019350600092829003018183876161da5a03f115610002575050506107355b6040805160025460e360020a631c2d8fb30282527f6c6f676d6772000000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b50826120ee5b6040805160025460e360020a631c2d8fb30282527f6163636f756e7463746c0000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b600160a060020a0316630accce06600684600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d02825291519192899290916336da446891600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150866040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050505050565b600160a060020a03166316c66cc6836040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051519150505b919050565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061091157610002565b87935061091c610260565b600160a060020a031663bdbdb08685600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fbdbdb0860000000000000000000000000000000000000000000000000000000082526004820152602481018a905290516044808301935060209282900301816000876161da5a03f1156100025750506040515193506109ca90506106ba565b600160a060020a03166381982a7a8885876040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f11561000257505050610a3661046c565b600160a060020a03166308636bdb85600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517f08636bdb000000000000000000000000000000000000000000000000000000008252600482015260248101889052604481019290925251606482810192602092919082900301816000876161da5a03f11561000257505060408051805160e160020a630a5d50db028252600482018190529151919450600160a060020a03871692506314baa1b6916024828101926000929190829003018183876161da5a03f11561000257505050610b3561046c565b600160a060020a0316630a3b6ede85600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63051db76f0282526004820152600160a060020a038d16602482015290516044808301935060209282900301816000876161da5a03f115610002575050604051519150610bd590506106ba565b600160a060020a031663d5b205ce87838b6040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f11561000257505050610c41610118565b600160a060020a031663988db79c888a6040518360e060020a0281526004018083600160a060020a0316815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050610ca5610260565b600160a060020a031663f4f2821b896040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050610d6f5b6040805160025460e360020a631c2d8fb30282527f747261646564620000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b600160a060020a0316635f539d69896040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050610dc2610639565b600160a060020a0316630accce06600386600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6315b1ea01028252915191928e928e9263ad8f500891600482810192602092919082900301816000876161da5a03f11561000257505050604051805190602001506040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050610ec5610639565b600160a060020a0316630accce06600386600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6315b1ea01028252915191928e928d9263ad8f500891600482810192602092919082900301816000876161da5a03f11561000257505050604051805190602001506040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050610fc8610639565b600160a060020a031663645a3b7285600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151905061101e610260565b600160a060020a031663f92eb77488600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f115610002575050505050505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061114a57610002565b604051600254600160a060020a0316908a908a908a908a908a90611579806125b38339018087600160a060020a0316815260200186600160a060020a03168152602001856000191681526020018481526020018381526020018281526020019650505050505050604051809103906000f092506111c5610118565b600160a060020a031663b9858a288a856040518360e060020a0281526004018083600160a060020a0316815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611229610260565b600160a060020a0316635188f99689856040518360e060020a028152600401808360001916815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611288610260565b600160a060020a031663bdbdb08689896040518360e060020a0281526004018083600019168152602001828152602001925050506020604051808303816000876161da5a03f1156100025750506040515192506112e590506106ba565b600160a060020a03166346d88e7d8a858a6040518460e060020a0281526004018084600160a060020a0316815260200183600160a060020a0316815260200182815260200193505050506000604051808303816000876161da5a03f115610002575050506113516106ba565b600160a060020a03166381982a7a8a84866040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f115610002575050506113bd61046c565b600160a060020a0316632b58469689856040518360e060020a028152600401808360001916815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f1156100025750505061141c61046c565b600160a060020a03166308636bdb8984866040518460e060020a028152600401808460001916815260200183815260200182600160a060020a0316815260200193505050506020604051808303816000876161da5a03f11561000257505060408051805160e160020a630a5d50db028252600482018190529151919350600160a060020a03861692506314baa1b6916024828101926000929190829003018183876161da5a03f115610002575050506114d3610639565b6040805160e160020a630566670302815260016004820152602481018b9052600160a060020a0386811660448301528c811660648301526000608483018190529251931692630accce069260a480840193919291829003018183876161da5a03f11561000257505050611544610639565b600160a060020a031663645a3b728961155b610260565b600160a060020a031663f92eb7748c6040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448084019360009350829003018183876161da5a03f1156100025750939a9950505050505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061164e57610002565b82915061165961046c565b600160a060020a0316630a3b6ede83600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63051db76f0282526004820152600160a060020a038816602482015290516044808301935060209282900301816000876161da5a03f1156100025750506040515191506116f990506106ba565b600160a060020a031663d5b205ce83600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a636ad902e7028252600160a060020a0390811660048301526024820187905288166044820152905160648281019350600092829003018183876161da5a03f1156100025750505061179b6106ba565b600160a060020a031663d653078983600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517ff1ff78a0000000000000000000000000000000000000000000000000000000008252915191929163f1ff78a09160048181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150866040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f1156100025750505061189f610260565b600160a060020a031663f4f2821b846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506118f2610118565b600160a060020a031663f4f2821b846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050611945610639565b600160a060020a0316630accce06600484600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d02825291519192899290916336da44689181870191602091908190038801816000876161da5a03f115610002575050506040518051906020015060006040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050611a48610639565b600160a060020a031663645a3b7283600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519050611a9e610260565b600160a060020a031663f92eb77486600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f11561000257505050505050565b600160a060020a03166381738c59836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f1156100025750506040515115159050611c1757610002565b611c1f610260565b600160a060020a03166338a699a4836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f11561000257505060405151159050611c7457610002565b611c7c610260565b600160a060020a0316632243118a836040518260e060020a02815260040180826000191681526020019150506000604051808303816000876161da5a03f11561000257505050611cca610639565b600160a060020a031663ae5f8080600184846040518460e060020a028152600401808481526020018360001916815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f115610002575050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f1156100025750506040515115159050611d9057610002565b5081611d9a610260565b600160a060020a031663581d5d6084846040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506000604051808303816000876161da5a03f11561000257505050611df5610639565b600160a060020a0316630accce06600283600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a630566670302825260048201949094526024810193909352600160a060020a038816604484015260006064840181905260848401819052905160a4808501949293509091829003018183876161da5a03f11561000257505050611eab610639565b600160a060020a031663645a3b7282600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519050611f01610260565b600160a060020a031663f92eb77485600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f11561000257505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061202857610002565b612030610118565b600160a060020a0316639859387b826040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f1156100025750505050565b600254600160a060020a0316ff5b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f11561000257505060405151151590506106b457610002565b600160a060020a031663d65307898383600160a060020a031663f1ff78a06040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fd6530789000000000000000000000000000000000000000000000000000000008252600160a060020a039485166004830152602482015292891660448401525160648381019360009350829003018183876161da5a03f115610002575050506121a5610118565b600160a060020a031663f4f2821b856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506121f8610cf4565b600160a060020a031663f4f2821b856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f1156100025750505061224b610639565b600160a060020a0316630accce06600583600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d028252915191928a9290916336da446891600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150886040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f1156100025750505080600160a060020a031663ea71b02d6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151600160a060020a031660001490506124b25761239f610639565b600160a060020a0316630accce06600583600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fea71b02d000000000000000000000000000000000000000000000000000000008252915191928a92909163ea71b02d91600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150886040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f115610002575050505b50505050565b600160a060020a03166338a699a4836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b600160a060020a031663213fe2b7836040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b600160a060020a031663f92eb774836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f115610002575050604051519150610888905056606060405260405160c08061157983396101206040819052825160805160a051935160e0516101005160008054600160a060020a03199081163317909155600180546005805484168817905560048a90556006869055600b8590556008849055909116861760a060020a60ff02191690554360038190556002558686526101408390526101608190529396929594919390929091600160a060020a033016917f76885d242fb71c6f74a7e717416e42eff4d96faf54f6de75c6a0a6bbd8890c6b91a230600160a060020a03167fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff600b600050546040518082815260200191505060405180910390a250505050505061145e8061011b6000396000f3606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "16", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0x70c9217d814985faef62b124420f8dfbddd96433": { + "balance": "0x4ef436dcbda6cd4a", + "code": "0x", + "nonce": "1634", + "storage": {} + }, + "0x7986bad81f4cbd9317f5a46861437dae58d69113": { + "balance": "0x0", + "code": "0x6060604052361561008d5760e060020a600035046302d05d3f811461009557806316c66cc6146100a75780631ab9075a146100d7578063213fe2b7146101125780639859387b1461013f578063988db79c1461015e578063a7f4377914610180578063b9858a281461019e578063c8e40fbf146101c0578063f4f2821b146101e8578063f905c15a14610209575b610212610002565b610214600054600160a060020a031681565b600160a060020a0360043581811660009081526005602052604081205461023193168114610257575060016101e3565b610212600435600254600160a060020a0316600014801590610108575060025433600160a060020a03908116911614155b1561025f57610002565b610214600435600160a060020a03811660009081526004602052604081205460ff16151561027557610002565b610212600435600254600160a060020a03166000141561029b57610002565b610212600435602435600254600160a060020a03166000141561050457610002565b61021260025433600160a060020a0390811691161461056757610002565b610212600435602435600254600160a060020a03166000141561057557610002565b610231600435600160a060020a03811660009081526004602052604090205460ff165b919050565b610212600435600254600090600160a060020a031681141561072057610002565b61024560035481565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060006101e3565b60028054600160a060020a031916821790555b50565b50600160a060020a038181166000908152600460205260409020546101009004166101e3565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506102fe57610002565b600160a060020a03811660009081526004602052604090205460ff161515610272576040516104028061092e833901809050604051809103906000f06004600050600083600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600083600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555050565b600160a060020a03821660009081526004602052604090205460ff1615156104725760405161040280610d30833901809050604051809103906000f06004600050600084600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff021916908302179055505b600160a060020a03828116600090815260046020819052604080518184205460e060020a630a3b0a4f02825286861693820193909352905161010090920490931692630a3b0a4f926024828101939192829003018183876161da5a03f11561000257505050600160a060020a03811660009081526006602052604090208054600160a060020a031916831790555b5050565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506103b957610002565b600254600160a060020a0316ff5b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506105d857610002565b600160a060020a03821660009081526004602052604090205460ff1615156106915760405161040280611132833901809050604051809103906000f06004600050600084600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff021916908302179055505b600160a060020a03828116600090815260046020819052604080518184205460e060020a630a3b0a4f02825286861693820193909352905161010090920490931692630a3b0a4f926024828101939192829003018183876161da5a03f11561000257505050600160a060020a031660009081526005602052604090208054600160a060020a0319169091179055565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f115610002575050604051511515905061078357610002565b50600160a060020a0381811660009081526005602090815260408083205490931680835260049091529190205460ff161561080f576040600081812054825160e260020a632e72bafd028152600160a060020a03868116600483015293516101009092049093169263b9caebf4926024828101939192829003018183876161da5a03f115610002575050505b600160a060020a03828116600090815260056020526040812054909116146108545760406000908120600160a060020a0384169091528054600160a060020a03191690555b50600160a060020a0381811660009081526006602090815260408083205490931680835260049091529190205460ff16156108e657600160a060020a038181166000908152604080518183205460e260020a632e72bafd028252868516600483015291516101009092049093169263b9caebf4926024828101939192829003018183876161da5a03f115610002575050505b600160a060020a03828116600090815260066020526040812054909116146105005760406000908120600160a060020a0384169091528054600160a060020a0319169055505056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056", + "nonce": "7", + "storage": { + "0xffc4df2d4f3d2cffad590bed6296406ab7926ca9e74784f74a95191fa069a174": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433" + } + }, + "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f": { + "balance": "0x0", + "code": "0x606060405236156100ae5760e060020a600035046302d05d3f81146100b65780631ab9075a146100c85780632b68bb2d146101035780634cc927d7146101c557806351a34eb81461028e57806356ccb6f0146103545780635928d37f1461041d578063599efa6b146104e9578063759297bb146105b2578063771d50e11461067e578063a7f4377914610740578063f905c15a1461075e578063f92eb77414610767578063febf661214610836575b610902610002565b610904600054600160a060020a031681565b610902600435600254600160a060020a03166000148015906100f9575060025433600160a060020a03908116911614155b1561092057610002565b60025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b02606452610902916000918291600160a060020a03169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051511515905061094257610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610a0d57610002565b61090260043560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610ae957610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610bbc57610002565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610c9657610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610de057610002565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610ebb57610002565b60025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b02606452610902916000918291600160a060020a03169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f9e57610002565b61090260025433600160a060020a0390811691161461106957610002565b61090e60035481565b61090e60043560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750506040805180517ff92eb774000000000000000000000000000000000000000000000000000000008252600482018790529151919350600160a060020a038416925063f92eb774916024828101926020929190829003018188876161da5a03f11561000257505060405151949350505050565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051511515905061107757610002565b005b6060908152602090f35b60408051918252519081900360200190f35b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5ed61af000000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152925190959286169350635ed61af092602483810193919291829003018183876161da5a03f115610002575050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517fab03fc2600000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015260248301899052808816604484015292519095928616935063ab03fc2692606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f949ae47900000000000000000000000000000000000000000000000000000000825233600160a060020a0390811660048401526024830188905292519095928616935063949ae47992604483810193919291829003018183876161da5a03f11561000257505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f46d88e7d000000000000000000000000000000000000000000000000000000008252600160a060020a0380891660048401523381166024840152604483018890529251909592861693506346d88e7d92606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5315cdde00000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152808a16602484015260448301889052925190959286169350635315cdde92606483810193919291829003018183876161da5a03f115610002575050604080517f5928d37f00000000000000000000000000000000000000000000000000000000815233600160a060020a03908116600483015287166024820152604481018690529051635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517fe68e401c00000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015280891660248401526044830188905292519095928616935063e68e401c92606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5152f381000000000000000000000000000000000000000000000000000000008252600160a060020a03808a1660048401528089166024840152604483018890523381166064840152925190959286169350635152f38192608483810193919291829003018183876161da5a03f115610002575050505050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f056d447000000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015292519095928616935063056d447092602483810193919291829003018183876161da5a03f115610002575050505050565b600254600160a060020a0316ff5b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f3ae1005c00000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152808a166024840152808916604484015260648301889052925190959286169350633ae1005c92608483810193919291829003018183876161da5a03f11561000257505050505050505056", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0xc212e03b9e060e36facad5fd8f4435412ca22e6b": { + "balance": "0x0", + "code": "0x606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000006195", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x5842545553440000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000008ac7230489e80000", + "0x000000000000000000000000000000000000000000000000000000000000000b": "0x0000000000000000000000000000000000000000000000283c7b9181eca20000" + } + }, + "0xcf00ffd997ad14939736f026006498e3f099baaf": { + "balance": "0x0", + "code": "0x606060405236156100cf5760e060020a600035046302d05d3f81146100d7578063031e7f5d146100e95780631ab9075a1461010b5780632243118a1461014657806327aad68a1461016557806338a699a4146101da5780635188f996146101f8578063581d5d601461021e57806381738c5914610246578063977da54014610269578063a07421ce14610288578063a7f43779146102be578063bdbdb086146102dc578063e1c7111914610303578063f4f2821b14610325578063f905c15a1461034a578063f92eb77414610353575b610387610002565b610389600054600160a060020a031681565b610387600435602435600254600160a060020a0316600014156103a857610002565b610387600435600254600160a060020a031660001480159061013c575060025433600160a060020a03908116911614155b1561042957610002565b610387600435600254600160a060020a03166000141561044b57610002565b6102ac60043560008181526004602081815260408320547f524d81d3000000000000000000000000000000000000000000000000000000006060908152610100909104600160a060020a031692839263524d81d3926064928188876161da5a03f1156100025750506040515192506103819050565b61039c60043560008181526004602052604090205460ff165b919050565b6103876004356024356002546000908190600160a060020a031681141561079457610002565b61038760043560243560025460009081908190600160a060020a031681141561080457610002565b61038960043560008181526004602052604081205460ff1615156109e357610002565b610387600435600254600160a060020a0316600014156109fb57610002565b600435600090815260096020526040902054670de0b6b3a764000090810360243502045b60408051918252519081900360200190f35b61038760025433600160a060020a03908116911614610a9257610002565b600435600090815260086020526040902054670de0b6b3a7640000602435909102046102ac565b610387600435602435600254600160a060020a031660001415610aa057610002565b61038760043560025460009081908190600160a060020a0316811415610b3657610002565b6102ac60035481565b6102ac600435600081815260076020908152604080832054600690925290912054670de0b6b3a76400000204805b50919050565b005b600160a060020a03166060908152602090f35b15156060908152602090f35b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506103fe57610002565b60008281526004602052604090205460ff16151561041b57610002565b600860205260406000205550565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506104a157610002565b604080516000838152600460205291909120805460ff1916600117905561040280610de2833901809050604051809103906000f0600460005060008360001916815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555066470de4df8200006008600050600083600019168152602001908152602001600020600050819055506703782dace9d9000060096000506000836000191681526020019081526020016000206000508190555050565b600460005060008560001916815260200190815260200160002060005060000160019054906101000a9004600160a060020a0316915081600160a060020a031663524d81d36040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151821415905061060057838152600660209081526040808320839055600790915281208190555b81600160a060020a0316630a3b0a4f846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050600160a060020a038316808252600560209081526040808420879055805160e160020a6364a81ff102815290518694670de0b6b3a7640000949363c9503fe29360048181019492939183900301908290876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b939160048181019260209290919082900301816000876161da5a03f11561000257505050604051805190602001500204600660005060008660001916815260200190815260200160002060008282825054019250508190555080600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050506040518051906020015060076000506000866000191681526020019081526020016000206000828282505401925050819055505b50505050565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f11561000257505060405151151590506107e957610002565b8381526004602052604081205460ff16151561056657610002565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f115610002575050604051511515905061085957610002565b849250670de0b6b3a764000083600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575060408051805160e160020a6364a81ff102825291519189028590049650600481810192602092909190829003018188876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b9391600481810192602092909190829003018189876161da5a03f115610002575050506040518051906020015002049050806006600050600085600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750604080518051855260208681528286208054989098039097557f2e94420f00000000000000000000000000000000000000000000000000000000815290518896600483810193919291829003018187876161da5a03f115610002575050604080515183526020939093525020805490910190555050505050565b60409020546101009004600160a060020a03166101f3565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f1156100025750506040515115159050610a5157610002565b60008181526004602052604090205460ff161515610a6e57610002565b6040600020805474ffffffffffffffffffffffffffffffffffffffffff1916905550565b600254600160a060020a0316ff5b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f1156100025750506040515115159050610af657610002565b60008281526004602052604090205460ff161515610b1357610002565b670de0b6b3a7640000811115610b2857610002565b600960205260406000205550565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f1156100025750506040515115159050610b8b57610002565b600160a060020a038416815260056020908152604080832054808452600490925282205490935060ff161515610bc057610002565b600460005060008460001916815260200190815260200160002060005060000160019054906101000a9004600160a060020a0316915081600160a060020a031663b9caebf4856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506005600050600085600160a060020a0316815260200190815260200160002060005060009055839050600082600160a060020a031663524d81d36040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519190911115905061078e57670de0b6b3a764000081600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b939160048181019260209290919082900301816000876161da5a03f11561000257505050604051805190602001500204600660005060008560001916815260200190815260200160002060008282825054039250508190555080600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050506040518051906020015060076000506000856000191681526020019081526020016000206000828282505403925050819055505050505056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056", + "nonce": "3", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x3571d73f14f31a1463bd0a2f92f7fde1653d4e1ead7aedf4b0a5df02f16092ab": "0x0000000000000000000000000000000000000000000007d634e4c55188be0000", + "0x4e64fe2d1b72d95a0a31945cc6e4f4e524ac5ad56d6bd44a85ec7bc9cc0462c0": "0x000000000000000000000000000000000000000000000002b5e3af16b1880000" + } + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "117124093", + "extraData": "0xd5830105008650617269747986312e31322e31826d61", + "gasLimit": "4707788", + "hash": "0xad325e4c49145fb7a4058a68ac741cc8607a71114e23fc88083c7e881dd653e7", + "miner": "0x00714b9ac97fd6bd9325a059a70c9b9fa94ce050", + "mixHash": "0x0af918f65cb4af04b608fc1f14a849707696986a0e7049e97ef3981808bcc65f", + "nonce": "0x38dee147326a8d40", + "number": "25000", + "stateRoot": "0xc5d6bbcd46236fcdcc80b332ffaaa5476b980b01608f9708408cfef01b58bd5b", + "timestamp": "1479891517", + "totalDifficulty": "1895410389427" + }, + "input": "0xf88b8206628504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb80000000000000000000000000000000000000000000000280faf689c35ac00002aa0a7ee5b7877811bf671d121b40569462e722657044808dc1d6c4f1e4233ec145ba0417e7543d52b65738d9df419cbe40a708424f4d54b0fc145c0a64545a2bb1065", + "result": [ + { + "action": { + "callType": "call", + "from": "0x70c9217d814985faef62b124420f8dfbddd96433", + "gas": "0x3d090", + "input": "0x51a34eb80000000000000000000000000000000000000000000000280faf689c35ac0000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 25001, + "result": { + "gasUsed": "0x1810b", + "output": "0x" + }, + "subtraces": 2, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "gas": "0x31217", + "input": "0xe16c7d98636f6e7472616374617069000000000000000000000000000000000000000000", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x334", + "output": "0x000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f" + }, + "subtraces": 0, + "traceAddress": [0], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "gas": "0x30b4a", + "input": "0x51a34eb80000000000000000000000000000000000000000000000280faf689c35ac0000", + "to": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0xedb7", + "output": "0x" + }, + "subtraces": 4, + "traceAddress": [1], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x2a68d", + "input": "0xe16c7d98636f6e747261637463746c000000000000000000000000000000000000000000", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x334", + "output": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690" + }, + "subtraces": 0, + "traceAddress": [1, 0], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x29f35", + "input": "0x16c66cc6000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b", + "to": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0xf8d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 2, + "traceAddress": [1, 1], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x23ac9", + "input": "0xe16c7d98636f6e7472616374646200000000000000000000000000000000000000000000", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x334", + "output": "0x0000000000000000000000007986bad81f4cbd9317f5a46861437dae58d69113" + }, + "subtraces": 0, + "traceAddress": [1, 1, 0], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x23366", + "input": "0x16c66cc6000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b", + "to": "0x7986bad81f4cbd9317f5a46861437dae58d69113", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x273", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [1, 1, 1], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x28a9e", + "input": "0xe16c7d98636f6e747261637463746c000000000000000000000000000000000000000000", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x334", + "output": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690" + }, + "subtraces": 0, + "traceAddress": [1, 2], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x283b9", + "input": "0x949ae479000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b0000000000000000000000000000000000000000000000280faf689c35ac0000", + "to": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0xc51c", + "output": "0x" + }, + "subtraces": 12, + "traceAddress": [1, 3], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x21d79", + "input": "0x13bc6d4b000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x24d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [1, 3, 0], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x2165b", + "input": "0xe16c7d986d61726b65746462000000000000000000000000000000000000000000000000", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x334", + "output": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf" + }, + "subtraces": 0, + "traceAddress": [1, 3, 1], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x20ee1", + "input": "0x581d5d60000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b0000000000000000000000000000000000000000000000280faf689c35ac0000", + "to": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x5374", + "output": "0x" + }, + "subtraces": 6, + "traceAddress": [1, 3, 2], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1a8e8", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x24d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [1, 3, 2, 0], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1a2c6", + "input": "0xc9503fe2", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x3cb", + "output": "0x0000000000000000000000000000000000000000000000008ac7230489e80000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 2, 1], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x19b72", + "input": "0xc9503fe2", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x3cb", + "output": "0x0000000000000000000000000000000000000000000000008ac7230489e80000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 2, 2], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x19428", + "input": "0x6f265b93", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x305", + "output": "0x0000000000000000000000000000000000000000000000283c7b9181eca20000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 2, 3], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x18d45", + "input": "0x2e94420f", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x229", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 2, 4], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1734e", + "input": "0x2e94420f", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x229", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 2, 5], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1b6c1", + "input": "0xe16c7d986c6f676d67720000000000000000000000000000000000000000000000000000", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x334", + "output": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38" + }, + "subtraces": 0, + "traceAddress": [1, 3, 3], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1af69", + "input": "0x2e94420f", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x229", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 4], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1a91d", + "input": "0x0accce0600000000000000000000000000000000000000000000000000000000000000025842545553440000000000000000000000000000000000000000000000000000000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "to": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x12fa", + "output": "0x" + }, + "subtraces": 1, + "traceAddress": [1, 3, 5], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "gas": "0x143a5", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x24d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [1, 3, 5, 0], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x19177", + "input": "0xe16c7d986c6f676d67720000000000000000000000000000000000000000000000000000", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x334", + "output": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38" + }, + "subtraces": 0, + "traceAddress": [1, 3, 6], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x18a22", + "input": "0x2e94420f", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x229", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 7], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x18341", + "input": "0xe16c7d986d61726b65746462000000000000000000000000000000000000000000000000", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x334", + "output": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf" + }, + "subtraces": 0, + "traceAddress": [1, 3, 8], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x17bec", + "input": "0x2e94420f", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x229", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 9], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1764e", + "input": "0xf92eb7745842545553440000000000000000000000000000000000000000000000000000", + "to": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x45c", + "output": "0x00000000000000000000000000000000000000000000002816d180e30c390000" + }, + "subtraces": 0, + "traceAddress": [1, 3, 10], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x16e62", + "input": "0x645a3b72584254555344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002816d180e30c390000", + "to": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0xebb", + "output": "0x" + }, + "subtraces": 1, + "traceAddress": [1, 3, 11], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "gas": "0x108ba", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x24d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [1, 3, 11, 0], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/delegatecall.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/delegatecall.json new file mode 100644 index 0000000..e5a37cb --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/delegatecall.json @@ -0,0 +1,120 @@ +{ + "context": { + "difficulty": "31927752", + "gasLimit": "4707788", + "miner": "0x5659922ce141eedbc2733678f9806c77b4eebee8", + "number": "11495", + "timestamp": "1479735917" + }, + "genesis": { + "alloc": { + "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff": { + "balance": "0x0", + "code": "0x606060405236156100825760e060020a60003504630a0313a981146100875780630a3b0a4f146101095780630cd40fea1461021257806329092d0e1461021f5780634cd06a5f146103295780635dbe47e8146103395780637a9e5410146103d9578063825db5f7146103e6578063a820b44d146103f3578063efa52fb31461047a575b610002565b34610002576104fc600435600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a26333556e849091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f415610002575050604051519150505b919050565b346100025761051060043560006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a2637d65837a9091336000604051602001526040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515115905061008257604080517f21ce24d4000000000000000000000000000000000000000000000000000000008152600060048201819052600160a060020a038416602483015291517342b02b5deeb78f34cd5ac896473b63e6c99a71a2926321ce24d49260448082019391829003018186803b156100025760325a03f415610002575050505b50565b3461000257610512600181565b346100025761051060043560006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a2637d65837a9091336000604051602001526040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515115905061008257604080517f89489a87000000000000000000000000000000000000000000000000000000008152600060048201819052600160a060020a038416602483015291517342b02b5deeb78f34cd5ac896473b63e6c99a71a2926389489a879260448082019391829003018186803b156100025760325a03f4156100025750505061020f565b3461000257610528600435610403565b34610002576104fc600435604080516000602091820181905282517f7d65837a00000000000000000000000000000000000000000000000000000000815260048101829052600160a060020a0385166024820152925190927342b02b5deeb78f34cd5ac896473b63e6c99a71a292637d65837a92604480840193829003018186803b156100025760325a03f4156100025750506040515191506101049050565b3461000257610512600c81565b3461000257610512600081565b3461000257610528600061055660005b600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a263685a1f3c9091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515191506101049050565b346100025761053a600435600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a263f775b6b59091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515191506101049050565b604080519115158252519081900360200190f35b005b6040805160ff9092168252519081900360200190f35b60408051918252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b90509056", + "nonce": "1", + "storage": { + "0x4d140b25abf3c71052885c66f73ce07cff141c1afabffdaf5cba04d625b7ebcc": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + }, + "0x269296dddce321a6bcbaa2f0181127593d732cba": { + "balance": "0x0", + "code": "0x606060405236156101275760e060020a60003504630cd40fea811461012c578063173825d9146101395780631849cb5a146101c7578063285791371461030f5780632a58b3301461033f5780632cb0d48a146103565780632f54bf6e1461036a578063332b9f061461039d5780633ca8b002146103c55780633df4ddf4146103d557806341c0e1b5146103f457806347799da81461040557806362a51eee1461042457806366907d13146104575780637065cb48146104825780637a9e541014610496578063825db5f7146104a3578063949d225d146104b0578063a51687df146104c7578063b4da4e37146104e6578063b4e6850b146104ff578063bd7474ca14610541578063e75623d814610541578063e9938e1114610555578063f5d241d314610643575b610002565b3461000257610682600181565b34610002576106986004356106ff335b60006001600a9054906101000a9004600160a060020a0316600160a060020a0316635dbe47e8836000604051602001526040518260e060020a0281526004018082600160a060020a03168152602001915050602060405180830381600087803b156100025760325a03f1156100025750506040515191506103989050565b3461000257604080516101008082018352600080835260208084018290528385018290526060808501839052608080860184905260a080870185905260c080880186905260e09788018690526001605060020a0360043581168752600586529589902089519788018a528054808816808a52605060020a91829004600160a060020a0316978a01889052600183015463ffffffff8082169d8c018e905264010000000082048116988c01899052604060020a90910416958a018690526002830154948a01859052600390920154808916938a01849052049096169690970186905293969495949293604080516001605060020a03998a16815297891660208901529590971686860152600160a060020a03909316606086015263ffffffff9182166080860152811660a08501521660c083015260e08201929092529051908190036101000190f35b346100025761069a60043560018054600091829160ff60f060020a909104161515141561063d5761072833610376565b34610002576106ae6004546001605060020a031681565b34610002576106986004356108b333610149565b346100025761069a6004355b600160a060020a03811660009081526002602052604090205460ff1615156001145b919050565b34610002576106986001805460ff60f060020a9091041615151415610913576108ed33610376565b346100025761069a600435610149565b34610002576106ae6003546001605060020a03605060020a9091041681565b346100025761069861091533610149565b34610002576106ae6003546001605060020a0360a060020a9091041681565b346100025761069a60043560243560018054600091829160ff60f060020a909104161515141561095e5761092633610376565b34610002576106986004356001805460ff60f060020a909104161515141561072557610a8b33610376565b3461000257610698600435610aa533610149565b3461000257610682600c81565b3461000257610682600081565b34610002576106ae6003546001605060020a031681565b34610002576106ca600154600160a060020a03605060020a9091041681565b346100025761069a60015460ff60f060020a9091041681565b346100025761069a60043560243560443560643560843560a43560c43560018054600091829160ff60f060020a9091041615151415610b5857610ad233610376565b3461000257610698600435610bd633610149565b34610002576106e6600435604080516101008181018352600080835260208084018290528385018290526060808501839052608080860184905260a080870185905260c080880186905260e09788018690526001605060020a03808b168752600586529589902089519788018a5280548088168952600160a060020a03605060020a918290041696890196909652600181015463ffffffff8082169b8a019b909b5264010000000081048b1695890195909552604060020a90940490981691860182905260028301549086015260039091015480841696850196909652940416918101919091525b50919050565b346100025761069a60043560243560443560643560843560a43560018054600091829160ff60f060020a9091041615151415610c8e57610bfb33610376565b6040805160ff9092168252519081900360200190f35b005b604080519115158252519081900360200190f35b604080516001605060020a039092168252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b6040805163ffffffff9092168252519081900360200190f35b1561012757600160a060020a0381166000908152600260205260409020805460ff191690555b50565b1561063d57506001605060020a0380831660009081526005602052604090208054909116151561075b576000915061063d565b604080516101008101825282546001605060020a038082168352600160a060020a03605060020a92839004166020840152600185015463ffffffff80821695850195909552640100000000810485166060850152604060020a90049093166080830152600284015460a0830152600384015480841660c08401520490911660e0820152610817905b8051600354600090819060016001605060020a0390911611610c995760038054605060020a60f060020a0319169055610ddf565b600380546001605060020a031981166000196001605060020a03928316011782558416600090815260056020526040812080547fffff000000000000000000000000000000000000000000000000000000000000168155600181810180546bffffffffffffffffffffffff191690556002820192909255909101805473ffffffffffffffffffffffffffffffffffffffff19169055915061063d565b1561012757600180547fff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660f060020a8302179055610725565b1561091357600480546001605060020a031981166001605060020a039091166001011790555b565b156101275733600160a060020a0316ff5b1561095e57506001605060020a03808416600090815260056020526040902080549091161515610965576000915061095e565b600191505b5092915050565b60038101546001605060020a0384811691161415610986576001915061095e565b604080516101008101825282546001605060020a038082168352600160a060020a03605060020a92839004166020840152600185015463ffffffff80821695850195909552640100000000810485166060850152604060020a90049093166080830152600284015460a0830152600384015480841660c08401520490911660e0820152610a12906107e3565b61095983825b80546003546001605060020a0391821691600091161515610de55760038054605060020a60a060020a031916605060020a84021760a060020a69ffffffffffffffffffff02191660a060020a84021781558301805473ffffffffffffffffffffffffffffffffffffffff19169055610ddf565b1561072557600480546001605060020a0319168217905550565b1561012757600160a060020a0381166000908152600260205260409020805460ff19166001179055610725565b15610b5857506001605060020a038088166000908152600560205260409020805490911615610b645760009150610b58565b6004546001605060020a0390811690891610610b3057600480546001605060020a03191660018a011790555b6003805460016001605060020a03821681016001605060020a03199092169190911790915591505b50979650505050505050565b80546001605060020a0319168817605060020a60f060020a031916605060020a880217815560018101805463ffffffff1916871767ffffffff0000000019166401000000008702176bffffffff00000000000000001916604060020a860217905560028101839055610b048982610a18565b156101275760018054605060020a60f060020a031916605060020a8302179055610725565b15610c8e57506001605060020a03808816600090815260056020526040902080549091161515610c2e5760009150610c8e565b8054605060020a60f060020a031916605060020a88021781556001808201805463ffffffff1916881767ffffffff0000000019166401000000008802176bffffffff00000000000000001916604060020a87021790556002820184905591505b509695505050505050565b6003546001605060020a03848116605060020a909204161415610d095760e084015160038054605060020a928302605060020a60a060020a031990911617808255919091046001605060020a031660009081526005602052604090200180546001605060020a0319169055610ddf565b6003546001605060020a0384811660a060020a909204161415610d825760c08401516003805460a060020a92830260a060020a69ffffffffffffffffffff021990911617808255919091046001605060020a03166000908152600560205260409020018054605060020a60a060020a0319169055610ddf565b505060c082015160e08301516001605060020a0380831660009081526005602052604080822060039081018054605060020a60a060020a031916605060020a8702179055928416825290200180546001605060020a031916831790555b50505050565b6001605060020a0384161515610e6457600380546001605060020a03605060020a9182900481166000908152600560205260409020830180546001605060020a0319908116871790915583548785018054918590049093168402605060020a60a060020a03199182161790911690915582549185029116179055610ddf565b506001605060020a038381166000908152600560205260409020600390810180549185018054605060020a60a060020a0319908116605060020a94859004909516808502959095176001605060020a0319168817909155815416918402919091179055801515610ef4576003805460a060020a69ffffffffffffffffffff02191660a060020a8402179055610ddf565b6003808401546001605060020a03605060020a9091041660009081526005602052604090200180546001605060020a031916831790555050505056", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000113204f5d64c28326fd7bd05fd4ea855302d7f2ff00000000000000000000" + } + }, + "0x42b02b5deeb78f34cd5ac896473b63e6c99a71a2": { + "balance": "0x0", + "code": "0x6504032353da7150606060405236156100695760e060020a60003504631bf7509d811461006e57806321ce24d41461008157806333556e84146100ec578063685a1f3c146101035780637d65837a1461011757806389489a8714610140578063f775b6b5146101fc575b610007565b61023460043560006100fd82600061010d565b610246600435602435600160a060020a03811660009081526020839052604081205415156102cb57826001016000508054806001018281815481835581811511610278576000838152602090206102789181019083015b808211156102d057600081556001016100d8565b610248600435602435600182015481105b92915050565b6102346004356024355b60018101906100fd565b610248600435602435600160a060020a03811660009081526020839052604090205415156100fd565b61024660043560243580600160a060020a031632600160a060020a03161415156101f857600160a060020a038116600090815260208390526040902054156101f857600160a060020a038116600090815260208390526040902054600183018054909160001901908110156100075760009182526020808320909101805473ffffffffffffffffffffffffffffffffffffffff19169055600160a060020a038316825283905260408120556002820180546000190190555b5050565b61025c60043560243560008260010160005082815481101561000757600091825260209091200154600160a060020a03169392505050565b60408051918252519081900360200190f35b005b604080519115158252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b50505060009283526020808420909201805473ffffffffffffffffffffffffffffffffffffffff191686179055600160a060020a0385168352908590526040909120819055600284018054600101905590505b505050565b509056", + "nonce": "1", + "storage": {} + }, + "0xa529806c67cc6486d4d62024471772f47f6fd672": { + "balance": "0x67820e39ac8fe9800", + "code": "0x", + "nonce": "68", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "31912170", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712388", + "hash": "0x0855914bdc581bccdc62591fd438498386ffb59ea4d5361ed5c3702e26e2c72f", + "miner": "0x334391aa808257952a462d1475562ee2106a6c90", + "mixHash": "0x64bb70b8ca883cadb8fbbda2c70a861612407864089ed87b98e5de20acceada6", + "nonce": "0x684129f283aaef18", + "number": "11494", + "stateRoot": "0x7057f31fe3dab1d620771adad35224aae43eb70e94861208bc84c557ff5b9d10", + "timestamp": "1479735912", + "totalDifficulty": "90744064339" + }, + "input": "0xf889448504a817c800832dc6c094269296dddce321a6bcbaa2f0181127593d732cba80a47065cb480000000000000000000000001523e55a1ca4efbae03355775ae89f8d7699ad9e29a080ed81e4c5e9971a730efab4885566e2c868cd80bd4166d0ed8c287fdf181650a069d7c49215e3d4416ad239cd09dbb71b9f04c16b33b385d14f40b618a7a65115", + "result": [ + { + "action": { + "callType": "call", + "from": "0xa529806c67cc6486d4d62024471772f47f6fd672", + "gas": "0x2dc6c0", + "input": "0x7065cb480000000000000000000000001523e55a1ca4efbae03355775ae89f8d7699ad9e", + "to": "0x269296dddce321a6bcbaa2f0181127593d732cba", + "value": "0x0" + }, + "blockNumber": 11495, + "result": { + "gasUsed": "0xbd55", + "output": "0x" + }, + "subtraces": 1, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x269296dddce321a6bcbaa2f0181127593d732cba", + "gas": "0x2cae73", + "input": "0x5dbe47e8000000000000000000000000a529806c67cc6486d4d62024471772f47f6fd672", + "to": "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0xa9d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 1, + "traceAddress": [0], + "type": "call" + }, + { + "action": { + "callType": "delegatecall", + "from": "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff", + "gas": "0x2bf459", + "input": "0x7d65837a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a529806c67cc6486d4d62024471772f47f6fd672", + "to": "0x42b02b5deeb78f34cd5ac896473b63e6c99a71a2", + "value": "0x0" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x2aa", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [0, 0], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/delegatecall_parent_value.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/delegatecall_parent_value.json new file mode 100644 index 0000000..1779124 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/delegatecall_parent_value.json @@ -0,0 +1,103 @@ +{ + "genesis": { + "number": "566098", + "hash": "0xba134562590a59291892395a29c5088899c2c64d720135dad88f7f076cf55f5f", + "nonce": "0x4b281be9594e3eb3", + "mixHash": "0xdb4ec386166d9c0dc9ba147755ecbb87af9f0a22563cbda02c799efa4e29db6e", + "stateRoot": "0xfc01993ad96a8fb8790a093cea4f505f8db1b0e1143c5f57bb1d173db0baa9e3", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "difficulty": "1926740", + "totalDifficulty": "482216286599", + "extraData": "0xd883010906846765746888676f312e31332e35856c696e7578", + "gasLimit": "19388354", + "timestamp": "1577558314", + "alloc": { + "0x6ab9dd83108698b9ca8d03af3c7eb91c0e54c3fc": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0xcbd5b9b25d1c38c2aad", + "nonce": "134969", + "code": "0x", + "storage": {} + }, + "0x91765918420bcb5ad22ee0997abed04056705798": { + "balance": "0x0", + "nonce": "1", + "code": "0x366000803760206000366000736ab9dd83108698b9ca8d03af3c7eb91c0e54c3fc60325a03f41560015760206000f3", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "566099", + "difficulty": "1927680", + "timestamp": "1577558317", + "gasLimit": "19369422", + "miner": "0x774c398d763161f55b66a646f17edda4addad2ca" + }, + "input": "0xf87983020f3985746a52880083015f909491765918420bcb5ad22ee0997abed04056705798888ac7230489e80000884e45375a4741394181a1a04b7260723fd02830754916b3bdf1537b6a851a7ae27c7e9296cfe1fc8275ec08a049d32158988eb717d61b4503b27c7583037c067daba1eb56f4bdfafc1b0045f6", + "result": [ + { + "action": { + "callType": "call", + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "gas": "0x15f90", + "input": "0x4e45375a47413941", + "to": "0x91765918420bcb5ad22ee0997abed04056705798", + "value": "0x8ac7230489e80000" + }, + "blockHash": "0xb05cc5c8f11df2b5d53ced342ee79e2805785f04c2f40add4539f27bd349f74e", + "blockNumber": 566099, + "result": { + "gasUsed": "0x5721", + "output": "0x4e45375a47413941000000000000000000000000000000000000000000000000" + }, + "subtraces": 1, + "traceAddress": [], + "transactionHash": "0x6e26dffe2f66186f03a2c36a16a4cd9724d07622c83746f1e35f988515713d4b", + "transactionPosition": 10, + "type": "call" + }, + { + "action": { + "callType": "delegatecall", + "from": "0x91765918420bcb5ad22ee0997abed04056705798", + "gas": "0x10463", + "input": "0x4e45375a47413941", + "to": "0x6ab9dd83108698b9ca8d03af3c7eb91c0e54c3fc", + "value": "0x8ac7230489e80000" + }, + "blockHash": "0xb05cc5c8f11df2b5d53ced342ee79e2805785f04c2f40add4539f27bd349f74e", + "blockNumber": 566099, + "result": { + "gasUsed": "0x0", + "output": "0x" + }, + "subtraces": 0, + "traceAddress": [ + 0 + ], + "transactionHash": "0x6e26dffe2f66186f03a2c36a16a4cd9724d07622c83746f1e35f988515713d4b", + "transactionPosition": 10, + "type": "call" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/gas.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/gas.json new file mode 100644 index 0000000..d977dbe --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/gas.json @@ -0,0 +1,95 @@ +{ + "genesis": { + "difficulty": "4683014", + "extraData": "0x537465762d63676574682d76312e31312e34", + "gasLimit": "9435044", + "hash": "0x3452ca5005cb73cd60dfa488a7b124251168e564491f80eb66765e79d78cfd95", + "miner": "0x415aa6292d1db797a467b22139704956c030e62f", + "mixHash": "0x6037612618507ae70c74a72bc2580253662971db959cfbc06d3f8527d4d01575", + "nonce": "0x314fc90dee5e39a2", + "number": "1555274", + "stateRoot": "0x795751f3f96a5de1fd3944ddd78cbfe4ef10491e1086be47609869a30929d0e5", + "timestamp": "1590795228", + "totalDifficulty": "2242595605834", + "alloc": { + "0x0000000000000000000000000000000000000001": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x6242e3ccf48e66425fb1", + "nonce": "264882", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "1555275", + "difficulty": "4683014", + "timestamp": "1590795244", + "gasLimit": "9444256", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf9011583040ab2843b9aca008301a9c88080b8c0601b565b6000555b005b630badf00d6003565b63c001f00d6003565b7319e7e376e7c213b7e7e7e46cc70a5dd086daff2a7f22ae6da6b482f9b1b19b0b897c3fd43884180a1c5ee361e1107a1bc635649dda600052601b603f537f16433dce375ce6dc8151d3f0a22728bc4a1d9fd6ed39dfd18b4609331937367f6040527f306964c0cf5d74f04129fdc60b54d35b596dde1bf89ad92cb4123318f4c0e40060605260206080607f60006000600161fffff2156007576080511460125760095681a1a07682fc43dbe1fb13c6474f5e70e121c826dd996168d8bb1d8ca7a63470127b46a00a25b308ba417b7770899e8f98a3f0c14aa9bf7db0edacfe4e78d00dbbd3c31e", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x1a9c8", + "init": "0x601b565b6000555b005b630badf00d6003565b63c001f00d6003565b7319e7e376e7c213b7e7e7e46cc70a5dd086daff2a7f22ae6da6b482f9b1b19b0b897c3fd43884180a1c5ee361e1107a1bc635649dda600052601b603f537f16433dce375ce6dc8151d3f0a22728bc4a1d9fd6ed39dfd18b4609331937367f6040527f306964c0cf5d74f04129fdc60b54d35b596dde1bf89ad92cb4123318f4c0e40060605260206080607f60006000600161fffff21560075760805114601257600956" + }, + "result": { + "gasUsed": "0x137e5", + "code": "0x", + "address": "0x1a05d76017ca02010533a470e05e8925a0380d8f" + }, + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 18, + "transactionHash": "0xc1c42a325856d513523aec464811923b2e2926f54015c7ba37877064cf889803", + "blockNumber": 1555275, + "blockHash": "0x80945caaff2fc67253cbb0217d2e5a307afde943929e97d8b36e58b88cbb02fd", + "time": "453.925µs" + }, + { + "type": "call", + "action": { + "from": "0x1a05d76017ca02010533a470e05e8925a0380d8f", + "to": "0x0000000000000000000000000000000000000001", + "value": "0x0", + "gas": "0xc8c6", + "input": "0x22ae6da6b482f9b1b19b0b897c3fd43884180a1c5ee361e1107a1bc635649dda000000000000000000000000000000000000000000000000000000000000001b16433dce375ce6dc8151d3f0a22728bc4a1d9fd6ed39dfd18b4609331937367f306964c0cf5d74f04129fdc60b54d35b596dde1bf89ad92cb4123318f4c0e4", + "callType": "callcode" + }, + "result": { + "gasUsed": "0xbb8", + "output": "0x00000000000000000000000019e7e376e7c213b7e7e7e46cc70a5dd086daff2a" + }, + "traceAddress": [0], + "subtraces": 0, + "transactionPosition": 18, + "transactionHash": "0xc1c42a325856d513523aec464811923b2e2926f54015c7ba37877064cf889803", + "blockNumber": 1555275, + "blockHash": "0x80945caaff2fc67253cbb0217d2e5a307afde943929e97d8b36e58b88cbb02fd" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/include_precompiled.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/include_precompiled.json new file mode 100644 index 0000000..0f28c07 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/include_precompiled.json @@ -0,0 +1,832 @@ +{ + "genesis": { + "number": "559197", + "hash": "0x0742a2bfab0452e2c634f3685b7e49ceb065c7000609b2b73f086e01fd1dfb58", + "nonce": "0x3060ad521440e1c2", + "mixHash": "0x59e7d4ae6cc3c38d23dac3f869b21984c7ba8f38070f4116a4941d9c403b6299", + "stateRoot": "0x68418fb5cf4afa9b807dc079e8cdde0e148ac2c8afb378e675465b5bed1fbd02", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "difficulty": "1813945", + "totalDifficulty": "469107641961", + "extraData": "0xd883010906846765746888676f312e31332e35856c696e7578", + "gasLimit": "6321166", + "timestamp": "1577471202", + "alloc": { + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0xc5e6fdae52af83f7e28", + "nonce": "77947", + "code": "0x", + "storage": {} + }, + "0x774c398d763161f55b66a646f17edda4addad2ca": { + "balance": "0xf09ef316eff819ee488", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc": { + "balance": "0x0", + "nonce": "1", + "code": "0x60006121df537c01000000000000000000000000000000000000000000000000000000006000350463b041b2858114156100d257600435604052780100000000000000000000000000000000000000000000000060606060599059016000905260038152604051816020015260008160400152809050205404606052606051151561008f57600060a052602060a0f35b604051601c604459905901600090520163e0e9a17b601c82035260605160048201526020610100602483600030602d5a03f1506101005190501460c052602060c0f35b632cce81aa81141561019957600435610120526001610120511280156100f85780610143565b78010000000000000000000000000000000000000000000000006060606059905901600090526003815266040000000000025481602001526000816040015280905020540461012051135b905015610157576000610180526020610180f35b601c604459905901600090520163e0e9a17b601c82035261012051600482015260206101c0602483600030602d5a03f1506101c05190506101a05260206101a0f35b63e0e9a17b8114156102e957600435610120526604000000000002546101e0526007610200525b610120517801000000000000000000000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540413156102da575b6102005160050a610120517801000000000000000000000000000000000000000000000000606060605990590160009052600381526101e051816020015260008160400152809050205404031215610269576000610200511361026c565b60005b1561028157600161020051036102005261020b565b7c01000000000000000000000000000000000000000000000000000000006102005160200260020a606060605990590160009052600381526101e05181602001526001816040015280905020540204546101e0526101c0565b6101e051610280526020610280f35b63cef887b08114156103e757365990590160009052366004823760043560208201016102c0526024356102e052506060601c61014c5990590160009052016390fa337d601c8203526102c0516020601f602083035101046020026020018360048401526020820360648401528060c8840152808401935050506102e051602482015233604482015281600401599059016000905260648160648460006004601cf161039057fe5b60648101925060c882015180808582606487015160006004600a8705601201f16103b657fe5b5080840193505080830360206103a08284600030602d5a03f1506103a0519050905090509050610300526020610300f35b6390fa337d81141561065f57365990590160009052366004823760043560208201016102c0526024356102e0526044356103e052505a610400526020601c608c599059016000905201632b861629601c8203526102c0516020601f6020830351010460200260200183600484015260208203602484015280604884015280840193505050816004015990590160009052602481602484600060046015f161048a57fe5b602481019250604882015180808582602487015160006004600a8705601201f16104b057fe5b5080840193505080830360206104408284600030602d5a03f15061044051905090509050905061042052610420511561065e576102c05160208103516020599059016000905260208183856000600287604801f150805190509050905061046052602059905901600090526020816020610460600060026068f1508051905060005b6020811215610552578181601f031a816105400153600181019050610532565b5050610540516101e0526102e0516c010000000000000000000000006103e0510217606060605990590160009052600381526101e05181602001526003816040015280905020555a61058052700100000000000000000000000000000000660400000000000154046105a0526104006105a0516103ff02056105c0526104006105a05161040102056105e0526105c0513a12156105f6576105c05161060052610615565b6105e0513a131561060e576105e05161060052610614565b3a610600525b5b6105805161040051036106005160020202610620526106205170010000000000000000000000000000000061060051021766040000000000015561042051610640526020610640f35b5b63d467ae0381141561073257600435604052602435610660526106605134121515610725576000341315610718576c01000000000000000000000000606060605990590160009052600381526040518160200152600381604001528090502054046103e0526000600060006000346103e051611388f115156106dd57fe5b601c60405990590160009052013481526103e0517f15e746bf513b8a58e4265cc1162d7fc445da5c9b1928d7cfcde2582735d4677f602083a2505b60016106a05260206106a0f35b60006106c05260206106c0f35b63ea4971ee811415610851576004356101e0526024356102e0526044356103e052601c606459905901600090520163d467ae03601c8203526101e05160048201526604000000000001546fffffffffffffffffffffffffffffffff16602482015260206106e060448334306123555a03f1506106e051905015156107bd576000610700526020610700f35b606060605990590160009052600381526101e05181602001526003816040015280905020546bffffffffffffffffffffffff166102e0511215610844576102e0516c010000000000000000000000006103e0510217606060605990590160009052600381526101e05181602001526003816040015280905020556001610760526020610760f35b6000610780526020610780f35b6387def0818114156108a3576004356101e0526c01000000000000000000000000606060605990590160009052600381526101e0518160200152600381604001528090502054046107a05260206107a0f35b630aece23c8114156108f4576004356101e052606060605990590160009052600381526101e05181602001526003816040015280905020546bffffffffffffffffffffffff166107e05260206107e0f35b63fa14df6b811415610926576604000000000001546fffffffffffffffffffffffffffffffff16610820526020610820f35b63b8c48f8c811415610b1b576004356101e0526024356108405260443561086052600066040000000000035414151561096a576000610880526020610880f3610976565b60016604000000000003555b6101e051660400000000000255606060605990590160009052600381526101e05181602001526000816040015280905020546108a0526108a0610840518060181a82538060191a600183015380601a1a600283015380601b1a600383015380601c1a600483015380601d1a600583015380601e1a600683015380601f1a600783015350506108a051606060605990590160009052600381526101e0518160200152600081604001528090502055606060605990590160009052600381526101e051816020015260008160400152809050205461094052601061094001610860518060101a82538060111a60018301538060121a60028301538060131a60038301538060141a60048301538060151a60058301538060161a60068301538060171a60078301538060181a60088301538060191a600983015380601a1a600a83015380601b1a600b83015380601c1a600c83015380601d1a600d83015380601e1a600e83015380601f1a600f830153505061094051606060605990590160009052600381526101e051816020015260008160400152809050205560016109e05260206109e0f35b632b86162981141561179457365990590160009052366004823760043560208201016102c0525060483560005b6020811215610b68578181601f031a81610a600153600181019050610b48565b5050610a6051610a00526102c05160208103516020599059016000905260208183856000600287604801f1508051905090509050610a8052602059905901600090526020816020610a80600060026068f1508051905060005b6020811215610be1578181601f031a81610b600153600181019050610bc1565b5050610b60516101e05270010000000000000000000000000000000070010000000000000000000000000000000060606060599059016000905260038152610a005181602001526000816040015280905020540204610b8052610b80511515610c8b57601c602059905901600090520161272e6101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a3506000610bc0526020610bc0f35b700100000000000000000000000000000000700100000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540204610be0526000610be051141515610d2e57601c60205990590160009052016127386101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a3506000610c20526020610c20f35b608c35610c40526301000000610c405160031a0262010000610c405160021a02610100610c405160011a02610c405160001a010101610c60526301000000610c605104610ca05262ffffff610c605116610cc0526003610ca051036101000a610cc05102610c805260006101e0511315610db057610c80516101e05112610db3565b60005b1561174d57780100000000000000000000000000000000000000000000000060606060599059016000905260038152610a00518160200152600081604001528090502054046001016101205260806080599059016000905260038152610a005181602001526002816040015260008160600152809050206002810154610d405250610d405160081a610d405160091a61010002610d4051600a1a6201000002610d4051600b1a630100000002010101610d005260006107e0610120510614158015610e7e5780610e8b565b6001660400000000000054145b905015610f0257610d0051610c6051141515610eae576000610d00511415610eb1565b60005b15610efd57601c602059905901600090520161271a6101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a3506000610da0526020610da0f35b6111b4565b6301000000610d005104610de05262ffffff610d005116610e00526003610de051036101000a610e005102610dc05260806080599059016000905260038152610a005181602001526002816040015260008160600152809050206002810154610e605250610e605160041a610e605160051a61010002610e605160061a6201000002610e605160071a630100000002010101610e2052601c604459905901600090520163e0e9a17b601c8203526107e0610120510360048201526020610ec0602483600030602d5a03f150610ec0519050610ea05260806080599059016000905260038152610ea05181602001526002816040015260008160600152809050206002810154610f205250610f205160041a610f205160051a61010002610f205160061a6201000002610f205160071a630100000002010101610ee052610ee051610e20510362049d408112156110595762049d4090505b6249d40081131561106b576249d40090505b62127500610dc0518202047bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8113156110ba577bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90505b600860076000835b80156110d9576002810490506001820191506110c2565b5080905001046000600382131515611103578160030360080260020a62ffffff841602905061111a565b6003820360080260020a8304905062ffffff811690505b6280000081161561113357610100810490506001820191505b6301000000820281179050905090509050610f6052610f6051610c6051141515611164576000610f60511415611167565b60005b156111b357601c60205990590160009052016127246101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a3506000611040526020611040f35b5b6101e0516101e0516101e05166040000000000005455606060605990590160009052600381526101e0518160200152600081604001528090502054611060526008611060016604000000000000548060181a82538060191a600183015380601a1a600283015380601b1a600383015380601c1a600483015380601d1a600583015380601e1a600683015380601f1a6007830153505061106051606060605990590160009052600381526101e0518160200152600081604001528090502055600166040000000000005401660400000000000055606060605990590160009052600381526101e0518160200152600081604001528090502054611100526111006001780100000000000000000000000000000000000000000000000060606060599059016000905260038152610a0051816020015260008160400152809050205404018060181a82538060191a600183015380601a1a600283015380601b1a600383015380601c1a600483015380601d1a600583015380601e1a600683015380601f1a6007830153505061110051606060605990590160009052600381526101e051816020015260008160400152809050205560006111c05278010000000000000000000000000000000000000000000000006801000000000000000060606060599059016000905260038152610a0051816020015260008160400152809050205402046111e0526111c06111e05180601c1a825380601d1a600183015380601e1a600283015380601f1a600383015350506001611260525b6008611260511215611515576112605160050a611280526001611280517801000000000000000000000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540407141561148757611260516004026111c0016111e05180601c1a825380601d1a600183015380601e1a600283015380601f1a60038301535050611505565b611260516004026111c0017c01000000000000000000000000000000000000000000000000000000006112605160200260020a60606060599059016000905260038152610a00518160200152600181604001528090502054020480601c1a825380601d1a600183015380601e1a600283015380601f1a600383015350505b60016112605101611260526113ec565b6111c051606060605990590160009052600381526101e05181602001526001816040015280905020555050608060805990590160009052600381526101e051816020015260028160400152600081606001528090502060005b600281121561159057806020026102c05101518282015560018101905061156e565b700100000000000000000000000000000000600003816020026102c051015116828201555050610c80517bffff0000000000000000000000000000000000000000000000000000056113e0526113e051610b805101610be052606060605990590160009052600381526101e051816020015260008160400152809050205461140052601061140001610be0518060101a82538060111a60018301538060121a60028301538060131a60038301538060141a60048301538060151a60058301538060161a60068301538060171a60078301538060181a60088301538060191a600983015380601a1a600a83015380601b1a600b83015380601c1a600c83015380601d1a600d83015380601e1a600e83015380601f1a600f830153505061140051606060605990590160009052600381526101e0518160200152600081604001528090502055660400000000000354610be051121515611703576101e051660400000000000255610be0516604000000000003555b601c6020599059016000905201610120516101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a350610120516114a05260206114a0f35b601c602059905901600090520161276a6101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a35060006114c05260206114c0f35b630f5995ce8114156119a157365990590160009052366004823760043560208201016114e05260243561150052604435602082010161152052606435604052506114e05160208103516020599059016000905260208183856000600287604801f150805190509050905061156052602059905901600090526020816020611560600060026068f1508051905060005b6020811215611843578181601f031a816116400153600181019050611823565b50506116405161154052604060206114e051035114156118a457601c6020599059016000905201614e52611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a3506000611660526020611660f35b6080601c6101ac59905901600090520163bd136cb3601c8203526115405160048201526115005160248201526115205160208103516020026020018360448401526020820360c48401528061014884015280840193505050604051606482015281600401599059016000905260848160848460006004601ff161192357fe5b6084810192506101488201518080858260c487015160006004600a8705601201f161194a57fe5b508084019350508083036020611680828434306123555a03f15061168051905090509050905061042052600161042051141561199357611540516116a05260206116a0f36119a0565b60006116c05260206116c0f35b5b63bd136cb3811415611d8c573659905901600090523660048237600435611540526024356115005260443560208201016115205260643560405250601c606459905901600090520163d467ae03601c82035260405160048201526060606059905901600090526003815260405181602001526003816040015280905020546bffffffffffffffffffffffff166024820152602061170060448334306123555a03f1506117005190501515611a9757601c6020599059016000905201614e2a611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a350614e2a611720526020611720f35b601c6044599059016000905201633d73b705601c82035260405160048201526020611740602483600030602d5a03f15061174051905015611b1a57601c6020599059016000905201614e34611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a350614e34611760526020611760f35b601c604459905901600090520163b041b285601c82035260405160048201526020611780602483600030602d5a03f1506117805190501515611b9e57601c6020599059016000905201614e3e611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a350614e3e6117a05260206117a0f35b6060601c61014c59905901600090520163b7129afb601c8203526115405160048201526115005160248201526115205160208103516020026020018360448401526020820360a4840152806101088401528084019350505081600401599059016000905260648160648460006004601cf1611c1557fe5b6064810192506101088201518080858260a487015160006004600a8705601201f1611c3c57fe5b5080840193505080830360206117e08284600030602d5a03f1506117e05190509050905090506117c0526080608059905901600090526003815260405181602001526002816040015260008160600152809050207c01000000000000000000000000000000000000000000000000000000006002820154046401000000006001830154020160005b6020811215611ce4578181601f031a816118a00153600181019050611cc4565b50506118a051905061180052611800516117c0511415611d4457601c60205990590160009052016001611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a35060016118c05260206118c0f35b601c6020599059016000905201614e48611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a350614e486118e05260206118e0f35b63318a3fee81141561205657365990590160009052366004823760043560208201016114e0526024356115005260443560208201016115205260643560405260843561190052506080601c6101ac599059016000905201630f5995ce601c8203526114e0516020601f6020830351010460200260200183600484015260208203608484015280610108840152808401935050506115005160248201526115205160208103516020026020018360448401526020820360c48401528061014884015280840193505050604051606482015281600401599059016000905260848160848460006004601ff1611e7b57fe5b60848101925061010882015180808582608487015160006004600a8705601201f1611ea257fe5b508084019350506101488201518080858260c487015160006004600a8705601201f1611eca57fe5b508084019350508083036020611920828434306123555a03f15061192051905090509050905061154052600061154051141515612010576040601c60ec599059016000905201631c0b6367601c8203526114e0516020601f6020830351010460200260200183600484015260208203604484015280608884015280840193505050611540516024820152816004015990590160009052604481604484600060046018f1611f7357fe5b604481019250608882015180808582604487015160006004600a8705601201f1611f9957fe5b5080840193505080830360206119608284600061190051602d5a03f15061196051905090509050905061194052601c602059905901600090520161194051611540517f2d0d11d0f27e21fab56a8712078721096066b7faaa8540a3ea566e70b97de2d4600084a35061194051611980526020611980f35b601c602059905901600090520161753a60007f2d0d11d0f27e21fab56a8712078721096066b7faaa8540a3ea566e70b97de2d4600084a35061753a6119a05260206119a0f35b6309dd0e81811415612076576604000000000002546119c05260206119c0f35b63023948728114156120d2577801000000000000000000000000000000000000000000000000606060605990590160009052600381526604000000000002548160200152600081604001528090502054046119e05260206119e0f35b632c181929811415612139577001000000000000000000000000000000007001000000000000000000000000000000006060606059905901600090526003815266040000000000025481602001526000816040015280905020540204611a20526020611a20f35b637ca823d58114156122af576604000000000002546101e052700100000000000000000000000000000000700100000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540204611a60526000611260525b600a61126051121561224c57608060805990590160009052600381526101e05181602001526002816040015260008160600152809050207c01000000000000000000000000000000000000000000000000000000006001820154046401000000008254020160005b6020811215612230578181601f031a81611b200153600181019050612210565b5050611b205190506101e05260016112605101611260526121a8565b700100000000000000000000000000000000700100000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540204611b4052611b4051611a605103611b80526020611b80f35b63b7129afb81141561246a57365990590160009052366004823760043561154052602435611500526044356020820101611520525061154051611ba0526020611520510351611bc0526000611260525b611bc05161126051121561245b5761126051602002611520510151611be05260026115005107611c00526001611c0051141561234a57611be051611c2052611ba051611c4052612368565b6000611c0051141561236757611ba051611c2052611be051611c40525b5b60405990590160009052611c205160005b6020811215612399578181601f031a81611ca00153600181019050612379565b5050611ca0518152611c405160005b60208112156123c8578181601f031a81611d2001536001810190506123a8565b5050611d2051602082015260205990590160009052602081604084600060026088f15080519050611d4052602059905901600090526020816020611d40600060026068f1508051905060005b6020811215612434578181601f031a81611de00153600181019050612414565b5050611de0519050611ba052600261150051056115005260016112605101611260526122ff565b611ba051611e00526020611e00f35b633d73b70581141561255b576004356040526604000000000002546101e0526000611260525b600661126051121561254e576101e05160405114156124b6576001611e20526020611e20f35b608060805990590160009052600381526101e05181602001526002816040015260008160600152809050207c01000000000000000000000000000000000000000000000000000000006001820154046401000000008254020160005b6020811215612532578181601f031a81611ec00153600181019050612512565b5050611ec05190506101e0526001611260510161126052612490565b6000611ee0526020611ee0f35b631f794436811415612737576004356101e052601c606459905901600090520163d467ae03601c8203526101e0516004820152606060605990590160009052600381526101e05181602001526003816040015280905020546bffffffffffffffffffffffff1660248201526020611f2060448334306123555a03f150611f20519050151561265657601c602059905901600090520160006101e0517f60ab231f060fa320acea170017564b7ee77f477e6465a8c964380cffb270aaf4600084a350602159905901600090526001815260006020820152602081019050602060408203526020601f6020830351604001010460200260408203f3505b601c602059905901600090520160016101e0517f60ab231f060fa320acea170017564b7ee77f477e6465a8c964380cffb270aaf4600084a350608060805990590160009052600381526101e0518160200152600281604001526000816060015280905020607059905901600090526050815260208101905060005b60028112156126f05780830154816020028301526001810190506126d1565b70010000000000000000000000000000000060000381840154168160200283015281905090509050602060408203526020601f6020830351604001010460200260408203f3505b6313f955e18114156128ca573659905901600090523660048237600435602082010161204052602435612060525060506120805260006120a052612080516120c0526000611260525b612060516112605112156128bb576120a051806120c051038080602001599059016000905281815260208101905090508180828286612040510160006004600a8705601201f16127cc57fe5b50809050905090506120e0526020601c608c599059016000905201632b861629601c8203526120e0516020601f6020830351010460200260200183600484015260208203602484015280604884015280840193505050816004015990590160009052602481602484600060046015f161284157fe5b602481019250604882015180808582602487015160006004600a8705601201f161286757fe5b5080840193505080830360206121a08284600030602d5a03f1506121a051905090509050905061042052612080516120a051016120a052612080516120c051016120c0526001611260510161126052612780565b610420516121c05260206121c0f35b50", + "storage": { + "0x292b7a8d467a95cffd303c7edd99875892cdb3eaee87e5ca29057dc88a09ffbd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4d2fcf8ac901ad7dcf5b1c3979801430d9979c87157230ae066a0276984c6ac7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xdf951a5d1d9283b06d4f1de58542f1e1e310d8d17aada46586ddb9598bc42894": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x9c8d09d387f3ba5dd4733e24c63e4d549864a7cd57a1bdf1fdd831a2a0184815": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4ab3b783bb170e11b0932a5ce8f5f343f67058b3925da271001a75ae498bd655": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x0000000000000000000000000000000000000004": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x0000000000000000000000000000000000000002": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "559198", + "difficulty": "1814830", + "timestamp": "1577471205", + "gasLimit": "6327338", + "miner": "0x774c398d763161f55b66a646f17edda4addad2ca" + }, + "tracerConfig": { + "includePrecompiles": true + }, + "input": "0xf9026f8301307b85746a52880083124f80946cc68eb482a757c690dd151d2bd5e774ada38bdc80b9020413f955e100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000019004000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a67040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae704000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc30304000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de04000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e00000000000000000000000000000000081a1a01c9e9d742c8e69daba2a026ccafdde618f2e44c96db281c2209c22f183ad03a2a049a61d267d22226896d4c065525819c238784c439dc2afa7d17fce76595730d1", + "result": [ + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "gas": "0x124f80", + "input": "0x13f955e100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000019004000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a67040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae704000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc30304000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de04000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e000000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1c6ff", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 20, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1a", + "input": "0x04000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a67", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x18", + "output": "0x04000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a67" + }, + "subtraces": 0, + "traceAddress": [ + 0 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x15", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x15", + "output": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020" + }, + "subtraces": 0, + "traceAddress": [ + 1 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1e", + "input": "0x000000000000000000000000000000000000000000000000000000000000005004000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a6700000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1b", + "output": "0x000000000000000000000000000000000000000000000000000000000000005004000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a6700000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 2 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x114243", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005004000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a6700000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 2, + "traceAddress": [ + 3 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x98", + "input": "0x04000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a67", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x60", + "output": "0xb099ea4048830027371dc31039920ae4fd19a641a7cbe57c198edd19d60f158a" + }, + "subtraces": 0, + "traceAddress": [ + 3, + 0 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x68", + "input": "0xb099ea4048830027371dc31039920ae4fd19a641a7cbe57c198edd19d60f158a", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x48", + "output": "0x5b53875b0f1381589859adcf938980f4a8fb0af4c88450070000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 3, + 1 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1a", + "input": "0x040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae7", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x18", + "output": "0x040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae7" + }, + "subtraces": 0, + "traceAddress": [ + 4 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x15", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x15", + "output": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020" + }, + "subtraces": 0, + "traceAddress": [ + 5 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1e", + "input": "0x0000000000000000000000000000000000000000000000000000000000000050040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae700000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000050040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae700000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 6 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x110d3b", + "input": "0x2b86162900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae700000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 2, + "traceAddress": [ + 7 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x98", + "input": "0x040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae7", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x60", + "output": "0xa0c6939b58a99b0d940f4435ab7db7d54d6b7786e68e00d9ff3890d69f95565d" + }, + "subtraces": 0, + "traceAddress": [ + 7, + 0 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x68", + "input": "0xa0c6939b58a99b0d940f4435ab7db7d54d6b7786e68e00d9ff3890d69f95565d", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x48", + "output": "0xabbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 7, + 1 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1a", + "input": "0x04000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc303", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x18", + "output": "0x04000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc303" + }, + "subtraces": 0, + "traceAddress": [ + 8 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x15", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x15", + "output": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020" + }, + "subtraces": 0, + "traceAddress": [ + 9 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1e", + "input": "0x000000000000000000000000000000000000000000000000000000000000005004000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc30300000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1b", + "output": "0x000000000000000000000000000000000000000000000000000000000000005004000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc30300000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 10 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x10d833", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005004000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc30300000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 2, + "traceAddress": [ + 11 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x98", + "input": "0x04000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc303", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x60", + "output": "0x6defff59ba277fa4511f8675ca98ca7d9c237c7433684490cf1ce09a9249e32f" + }, + "subtraces": 0, + "traceAddress": [ + 11, + 0 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x68", + "input": "0x6defff59ba277fa4511f8675ca98ca7d9c237c7433684490cf1ce09a9249e32f", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x48", + "output": "0xe93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 11, + 1 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1a", + "input": "0x04000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x18", + "output": "0x04000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de" + }, + "subtraces": 0, + "traceAddress": [ + 12 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x15", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x15", + "output": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020" + }, + "subtraces": 0, + "traceAddress": [ + 13 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1e", + "input": "0x000000000000000000000000000000000000000000000000000000000000005004000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de00000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1b", + "output": "0x000000000000000000000000000000000000000000000000000000000000005004000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de00000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 14 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x10a328", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005004000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de00000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 2, + "traceAddress": [ + 15 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x98", + "input": "0x04000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x60", + "output": "0x996652142ffecd9cc272f376ca0e8228871a903772996289f847a6dbe2ce2698" + }, + "subtraces": 0, + "traceAddress": [ + 15, + 0 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x68", + "input": "0x996652142ffecd9cc272f376ca0e8228871a903772996289f847a6dbe2ce2698", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x48", + "output": "0xf2e372a0b5b837116eee8f968840393d85975a15313468070000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 15, + 1 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1a", + "input": "0x04000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e0", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x18", + "output": "0x04000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e0" + }, + "subtraces": 0, + "traceAddress": [ + 16 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x15", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x15", + "output": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020" + }, + "subtraces": 0, + "traceAddress": [ + 17 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x1e", + "input": "0x000000000000000000000000000000000000000000000000000000000000005004000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e000000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000004", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1b", + "output": "0x000000000000000000000000000000000000000000000000000000000000005004000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 18 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x106e1d", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005004000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e000000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 2, + "traceAddress": [ + 19 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x98", + "input": "0x04000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e0", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x60", + "output": "0xe57cf1c1d6132b9cfd9e90f54f907c038b47941b2a7f3800783af26e852ec116" + }, + "subtraces": 0, + "traceAddress": [ + 19, + 0 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x68", + "input": "0xe57cf1c1d6132b9cfd9e90f54f907c038b47941b2a7f3800783af26e852ec116", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x0000000000000000000000000000000000000002", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x48", + "output": "0x8d5b6fafc6216500f9ef1ab16b30a59df9122d7de0f4910a0000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 19, + 1 + ], + "type": "call" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_create_oog_outer_throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_create_oog_outer_throw.json new file mode 100644 index 0000000..6c4ce18 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_create_oog_outer_throw.json @@ -0,0 +1,88 @@ +{ + "context": { + "difficulty": "3451177886", + "gasLimit": "4709286", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2290744", + "timestamp": "1513616439" + }, + "genesis": { + "alloc": { + "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a": { + "balance": "0x0", + "code": "0x606060405263ffffffff60e060020a6000350416633b91f50681146100505780635bb47808146100715780635f51fca01461008c578063bc7647a9146100ad578063f1bd0d7a146100c8575b610000565b346100005761006f600160a060020a03600435811690602435166100e9565b005b346100005761006f600160a060020a0360043516610152565b005b346100005761006f600160a060020a036004358116906024351661019c565b005b346100005761006f600160a060020a03600435166101fa565b005b346100005761006f600160a060020a0360043581169060243516610db8565b005b600160a060020a038083166000908152602081905260408120549091908116903316811461011657610000565b839150600160a060020a038316151561012d573392505b6101378284610e2e565b6101418284610db8565b61014a826101fa565b5b5b50505050565b600154600160a060020a03908116903316811461016e57610000565b6002805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0384161790555b5b5050565b600254600160a060020a0390811690331681146101b857610000565b600160a060020a038381166000908152602081905260409020805473ffffffffffffffffffffffffffffffffffffffff19169184169190911790555b5b505050565b6040805160e260020a631a481fc102815260016024820181905260026044830152606482015262093a8060848201819052600060a4830181905260c06004840152601e60c48401527f736574456e7469747953746174757328616464726573732c75696e743829000060e484015292519091600160a060020a038516916369207f049161010480820192879290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526000602482018190526001604483015260606004830152602360648301527f626567696e506f6c6c28616464726573732c75696e7436342c626f6f6c2c626f60848301527f6f6c29000000000000000000000000000000000000000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601960c48201527f61646453746f636b28616464726573732c75696e74323536290000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601960c48201527f697373756553746f636b2875696e74382c75696e74323536290000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152602160648301527f6772616e7453746f636b2875696e74382c75696e743235362c61646472657373608483015260f860020a60290260a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f115610000575050604080517f010555b8000000000000000000000000000000000000000000000000000000008152600160a060020a03338116602483015260006044830181905260606004840152603c60648401527f6772616e7456657374656453746f636b2875696e74382c75696e743235362c6160848401527f6464726573732c75696e7436342c75696e7436342c75696e743634290000000060a48401529251908716935063010555b89260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601260c48201527f626567696e53616c65286164647265737329000000000000000000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152601a60648301527f7472616e7366657253616c6546756e64732875696e743235362900000000000060848301529151600160a060020a038716935063de64e15c9260a48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152602d60c48201527f7365744163636f756e74696e6753657474696e67732875696e743235362c756960e48201527f6e7436342c75696e7432353629000000000000000000000000000000000000006101048201529051600160a060020a03861692506369207f04916101248082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152603460648301527f637265617465526563757272696e6752657761726428616464726573732c756960848301527f6e743235362c75696e7436342c737472696e672900000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152601b60648301527f72656d6f7665526563757272696e675265776172642875696e7429000000000060848301529151600160a060020a038716935063de64e15c9260a48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152602360648301527f697373756552657761726428616464726573732c75696e743235362c7374726960848301527f6e6729000000000000000000000000000000000000000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a6337993857028152600160248201819052604482015260606004820152602260648201527f61737369676e53746f636b2875696e74382c616464726573732c75696e743235608482015260f060020a6136290260a48201529051600160a060020a038616925063de64e15c9160c48082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a6337993857028152600160248201819052604482015260606004820152602260648201527f72656d6f766553746f636b2875696e74382c616464726573732c75696e743235608482015260f060020a6136290260a48201529051600160a060020a038616925063de64e15c9160c48082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260026024808301919091526003604483015260006064830181905267ffffffffffffffff8616608484015260ff871660a484015260c0600484015260c48301919091527f7365744164647265737342796c617728737472696e672c616464726573732c6260e48301527f6f6f6c29000000000000000000000000000000000000000000000000000000006101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc1028152600260248201526003604482015260006064820181905267ffffffffffffffff8516608483015260ff861660a483015260c06004830152602160c48301527f73657453746174757342796c617728737472696e672c75696e74382c626f6f6c60e483015260f860020a6029026101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc1028152600260248201526003604482015260006064820181905267ffffffffffffffff8516608483015260ff861660a483015260c06004830152603860c48301527f736574566f74696e6742796c617728737472696e672c75696e743235362c756960e48301527f6e743235362c626f6f6c2c75696e7436342c75696e74382900000000000000006101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f115610000575050505b505050565b604080517f225553a4000000000000000000000000000000000000000000000000000000008152600160a060020a0383811660048301526002602483015291519184169163225553a49160448082019260009290919082900301818387803b156100005760325a03f115610000575050505b5050565b600082604051611fd280610f488339600160a060020a03909216910190815260405190819003602001906000f0801561000057905082600160a060020a03166308b027418260016040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a0316815260200182815260200192505050600060405180830381600087803b156100005760325a03f115610000575050604080517fa14e3ee300000000000000000000000000000000000000000000000000000000815260006004820181905260016024830152600160a060020a0386811660448401529251928716935063a14e3ee39260648084019382900301818387803b156100005760325a03f115610000575050505b5050505600606060405234620000005760405160208062001fd283398101604052515b805b600a8054600160a060020a031916600160a060020a0383161790555b506001600d819055600e81905560408051808201909152600c8082527f566f74696e672053746f636b00000000000000000000000000000000000000006020928301908152600b805460008290528251601860ff1990911617825590947f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9600291831615610100026000190190921604601f0193909304830192906200010c565b828001600101855582156200010c579182015b828111156200010c578251825591602001919060010190620000ef565b5b50620001309291505b808211156200012c576000815560010162000116565b5090565b50506040805180820190915260038082527f43565300000000000000000000000000000000000000000000000000000000006020928301908152600c805460008290528251600660ff1990911617825590937fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c760026001841615610100026000190190931692909204601f010481019291620001f7565b82800160010185558215620001f7579182015b82811115620001f7578251825591602001919060010190620001da565b5b506200021b9291505b808211156200012c576000815560010162000116565b5090565b50505b505b611da280620002306000396000f3006060604052361561019a5763ffffffff60e060020a600035041662e1986d811461019f57806302a72a4c146101d657806306eb4e421461020157806306fdde0314610220578063095ea7b3146102ad578063158ccb99146102dd57806318160ddd146102f85780631cf65a781461031757806323b872dd146103365780632c71e60a1461036c57806333148fd6146103ca578063435ebc2c146103f55780635eeb6e451461041e578063600e85b71461043c5780636103d70b146104a157806362c1e46a146104b05780636c182e99146104ba578063706dc87c146104f057806370a082311461052557806377174f851461055057806395d89b411461056f578063a7771ee3146105fc578063a9059cbb14610629578063ab377daa14610659578063b25dbb5e14610685578063b89a73cb14610699578063ca5eb5e1146106c6578063cbcf2e5a146106e1578063d21f05ba1461070e578063d347c2051461072d578063d96831e114610765578063dd62ed3e14610777578063df3c211b146107a8578063e2982c21146107d6578063eb944e4c14610801575b610000565b34610000576101d4600160a060020a036004351660243567ffffffffffffffff6044358116906064358116906084351661081f565b005b34610000576101ef600160a060020a0360043516610a30565b60408051918252519081900360200190f35b34610000576101ef610a4f565b60408051918252519081900360200190f35b346100005761022d610a55565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516602435610ae3565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516610b4e565b005b34610000576101ef610b89565b60408051918252519081900360200190f35b34610000576101ef610b8f565b60408051918252519081900360200190f35b34610000576102c9600160a060020a0360043581169060243516604435610b95565b604080519115158252519081900360200190f35b3461000057610388600160a060020a0360043516602435610bb7565b60408051600160a060020a039096168652602086019490945267ffffffffffffffff928316858501529082166060850152166080830152519081900360a00190f35b34610000576101ef600160a060020a0360043516610c21565b60408051918252519081900360200190f35b3461000057610402610c40565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d4600160a060020a0360043516602435610c4f565b005b3461000057610458600160a060020a0360043516602435610cc9565b60408051600160a060020a03909716875260208701959095528585019390935267ffffffffffffffff9182166060860152811660808501521660a0830152519081900360c00190f35b34610000576101d4610d9e565b005b6101d4610e1e565b005b34610000576104d3600160a060020a0360043516610e21565b6040805167ffffffffffffffff9092168252519081900360200190f35b3461000057610402600160a060020a0360043516610ead565b60408051600160a060020a039092168252519081900360200190f35b34610000576101ef600160a060020a0360043516610ef9565b60408051918252519081900360200190f35b34610000576101ef610f18565b60408051918252519081900360200190f35b346100005761022d610f1e565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516610fac565b604080519115158252519081900360200190f35b34610000576102c9600160a060020a0360043516602435610fc2565b604080519115158252519081900360200190f35b3461000057610402600435610fe2565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d46004351515610ffd565b005b34610000576102c9600160a060020a036004351661104c565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516611062565b005b34610000576102c9600160a060020a0360043516611070565b604080519115158252519081900360200190f35b34610000576101ef6110f4565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351667ffffffffffffffff602435166110fa565b60408051918252519081900360200190f35b34610000576101d4600435611121565b005b34610000576101ef600160a060020a03600435811690602435166111c6565b60408051918252519081900360200190f35b34610000576101ef6004356024356044356064356084356111f3565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351661128c565b60408051918252519081900360200190f35b34610000576101d4600160a060020a036004351660243561129e565b005b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915267ffffffffffffffff848116908416101561086457610000565b8367ffffffffffffffff168267ffffffffffffffff16101561088557610000565b8267ffffffffffffffff168267ffffffffffffffff1610156108a657610000565b506040805160a081018252600160a060020a033381168252602080830188905267ffffffffffffffff80871684860152858116606085015287166080840152908816600090815260039091529190912080546001810180835582818380158290116109615760030281600302836000526020600020918201910161096191905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050916000526020600020906003020160005b5082518154600160a060020a031916600160a060020a03909116178155602083015160018201556040830151600290910180546060850151608086015167ffffffffffffffff1990921667ffffffffffffffff948516176fffffffffffffffff00000000000000001916604060020a918516919091021777ffffffffffffffff000000000000000000000000000000001916608060020a939091169290920291909117905550610a268686610fc2565b505b505050505050565b600160a060020a0381166000908152600360205260409020545b919050565b60055481565b600b805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b600160a060020a03338116600081815260026020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b600a5433600160a060020a03908116911614610b6957610000565b600a8054600160a060020a031916600160a060020a0383161790555b5b50565b60005481565b60005b90565b6000610ba2848484611600565b610bad8484846116e2565b90505b9392505050565b600360205281600052604060002081815481101561000057906000526020600020906003020160005b5080546001820154600290920154600160a060020a03909116935090915067ffffffffffffffff80821691604060020a8104821691608060020a9091041685565b600160a060020a0381166000908152600860205260409020545b919050565b600a54600160a060020a031681565b600a5433600160a060020a03908116911614610c6a57610000565b610c7660005482611714565b6000908155600160a060020a038316815260016020526040902054610c9b9082611714565b600160a060020a038316600090815260016020526040812091909155610cc390839083611600565b5b5b5050565b6000600060006000600060006000600360008a600160a060020a0316600160a060020a0316815260200190815260200160002088815481101561000057906000526020600020906003020160005b508054600182015460028301546040805160a081018252600160a060020a039094168085526020850184905267ffffffffffffffff808416928601839052604060020a8404811660608701819052608060020a9094041660808601819052909c50929a509197509095509350909150610d90904261172d565b94505b509295509295509295565b33600160a060020a038116600090815260066020526040902054801515610dc457610000565b8030600160a060020a0316311015610ddb57610000565b600160a060020a0382166000818152600660205260408082208290555183156108fc0291849190818181858888f193505050501515610cc357610000565b5b5050565b5b565b600160a060020a03811660009081526003602052604081205442915b81811015610ea557600160a060020a03841660009081526003602052604090208054610e9a9190839081101561000057906000526020600020906003020160005b5060020154604060020a900467ffffffffffffffff168461177d565b92505b600101610e3d565b5b5050919050565b600160a060020a0380821660009081526007602052604081205490911615610eef57600160a060020a0380831660009081526007602052604090205416610ef1565b815b90505b919050565b600160a060020a0381166000908152600160205260409020545b919050565b600d5481565b600c805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b60006000610fb983610c21565b1190505b919050565b6000610fcf338484611600565b610fd983836117ac565b90505b92915050565b600460205260009081526040902054600160a060020a031681565b8015801561101a575061100f33610ef9565b61101833610c21565b115b1561102457610000565b33600160a060020a03166000908152600960205260409020805460ff19168215151790555b50565b60006000610fb983610ef9565b1190505b919050565b610b8533826117dc565b5b50565b600a54604080516000602091820181905282517fcbcf2e5a000000000000000000000000000000000000000000000000000000008152600160a060020a03868116600483015293519194939093169263cbcf2e5a92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b600e5481565b6000610fd961110984846118b2565b61111385856119b6565b611a05565b90505b92915050565b600a5433600160a060020a0390811691161461113c57610000565b61114860005482611a1f565b600055600554600190101561116c57600a5461116c90600160a060020a0316611a47565b5b600a54600160a060020a03166000908152600160205260409020546111929082611a1f565b600a8054600160a060020a039081166000908152600160205260408120939093559054610b8592911683611600565b5b5b50565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b6000600060008487101561120a5760009250611281565b8387111561121a57879250611281565b61123f6112308961122b888a611714565b611a90565b61123a8689611714565b611abc565b915081925061124e8883611714565b905061127e8361127961126a8461122b8c8b611714565b611a90565b61123a888b611714565b611abc565b611a1f565b92505b505095945050505050565b60066020526000908152604090205481565b600160a060020a03821660009081526003602052604081208054829190849081101561000057906000526020600020906003020160005b50805490925033600160a060020a039081169116146112f357610000565b6040805160a0810182528354600160a060020a0316815260018401546020820152600284015467ffffffffffffffff80821693830193909352604060020a810483166060830152608060020a900490911660808201526113539042611af9565b600160a060020a0385166000908152600360205260409020805491925090849081101561000057906000526020600020906003020160005b508054600160a060020a031916815560006001820181905560029091018054600160c060020a0319169055600160a060020a0385168152600360205260409020805460001981019081101561000057906000526020600020906003020160005b50600160a060020a03851660009081526003602052604090208054859081101561000057906000526020600020906003020160005b5081548154600160a060020a031916600160a060020a03918216178255600180840154908301556002928301805493909201805467ffffffffffffffff191667ffffffffffffffff948516178082558354604060020a908190048616026fffffffffffffffff000000000000000019909116178082559254608060020a9081900490941690930277ffffffffffffffff00000000000000000000000000000000199092169190911790915584166000908152600360205260409020805460001981018083559190829080158290116115485760030281600302836000526020600020918201910161154891905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050600160a060020a033316600090815260016020526040902054611570915082611a1f565b600160a060020a03338116600090815260016020526040808220939093559086168152205461159f9082611714565b600160a060020a038086166000818152600160209081526040918290209490945580518581529051339093169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35b50505050565b600160a060020a0383161561166e576116466008600061161f86610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611714565b6008600061165386610ead565b600160a060020a031681526020810191909152604001600020555b600160a060020a038216156116dc576116b46008600061168d85610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611a1f565b600860006116c185610ead565b600160a060020a031681526020810191909152604001600020555b5b505050565b600083826116f082426110fa565b8111156116fc57610000565b611707868686611b1b565b92505b5b50509392505050565b600061172283831115611b4d565b508082035b92915050565b6000610fd983602001518367ffffffffffffffff16856080015167ffffffffffffffff16866040015167ffffffffffffffff16876060015167ffffffffffffffff166111f3565b90505b92915050565b60008167ffffffffffffffff168367ffffffffffffffff1610156117a15781610fd9565b825b90505b92915050565b600033826117ba82426110fa565b8111156117c657610000565b6117d08585611b5d565b92505b5b505092915050565b6117e582610ef9565b6117ee83610c21565b11156117f957610000565b600160a060020a03811660009081526009602052604090205460ff16158015611834575081600160a060020a031681600160a060020a031614155b1561183e57610000565b61184782611070565b1561185157610000565b611864828261185f85610ef9565b611600565b600160a060020a0382811660009081526007602052604090208054600160a060020a031916918316918217905561189a82610ead565b600160a060020a031614610cc357610000565b5b5050565b600160a060020a038216600090815260036020526040812054815b818110156119885761197d836112796003600089600160a060020a0316600160a060020a0316815260200190815260200160002084815481101561000057906000526020600020906003020160005b506040805160a0810182528254600160a060020a031681526001830154602082015260029092015467ffffffffffffffff80821692840192909252604060020a810482166060840152608060020a900416608082015287611af9565b611a1f565b92505b6001016118cd565b600160a060020a0385166000908152600160205260409020546117d09084611714565b92505b505092915050565b600060006119c384611070565b80156119d157506000600d54115b90506119fb816119e9576119e485610ef9565b6119ec565b60005b6111138686611b7b565b611a05565b91505b5092915050565b60008183106117a15781610fd9565b825b90505b92915050565b6000828201611a3c848210801590611a375750838210155b611b4d565b8091505b5092915050565b611a508161104c565b15611a5a57610b85565b6005805460009081526004602052604090208054600160a060020a031916600160a060020a038416179055805460010190555b50565b6000828202611a3c841580611a37575083858381156100005704145b611b4d565b8091505b5092915050565b60006000611acc60008411611b4d565b8284811561000057049050611a3c838581156100005706828502018514611b4d565b8091505b5092915050565b6000610fd98360200151611b0d858561172d565b611714565b90505b92915050565b60008382611b2982426110fa565b811115611b3557610000565b611707868686611b8f565b92505b5b50509392505050565b801515610b8557610000565b5b50565b6000611b6883611a47565b610fd98383611c92565b90505b92915050565b6000610fd983610ef9565b90505b92915050565b600160a060020a038084166000908152600260209081526040808320338516845282528083205493861683526001909152812054909190611bd09084611a1f565b600160a060020a038086166000908152600160205260408082209390935590871681522054611bff9084611714565b600160a060020a038616600090815260016020526040902055611c228184611714565b600160a060020a038087166000818152600260209081526040808320338616845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600191505b509392505050565b60003382611ca082426110fa565b811115611cac57610000565b6117d08585611cc2565b92505b5b505092915050565b600160a060020a033316600090815260016020526040812054611ce59083611714565b600160a060020a033381166000908152600160205260408082209390935590851681522054611d149083611a1f565b600160a060020a038085166000818152600160209081526040918290209490945580518681529051919333909316927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b929150505600a165627a7a72305820bfa5ddd3fecf3f43aed25385ec7ec3ef79638c2e58d99f85d9a3cc494183bf160029a165627a7a723058200e78a5f7e0f91739035d0fbf5eca02f79377210b722f63431f29a22e2880b3bd0029", + "nonce": "789", + "storage": { + "0xfe9ec0542a1c009be8b1f3acf43af97100ffff42eb736850fb038fa1151ad4d9": "0x000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e8" + } + }, + "0x5cb4a6b902fcb21588c86c3517e797b07cdaadb9": { + "balance": "0x0", + "code": "0x", + "nonce": "0", + "storage": {} + }, + "0xe4a13bc304682a903e9472f469c33801dd18d9e8": { + "balance": "0x33c763c929f62c4f", + "code": "0x", + "nonce": "14", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3451177886", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4713874", + "hash": "0x5d52a672417cd1269bf4f7095e25dcbf837747bba908cd5ef809dc1bd06144b5", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x01a12845ed546b94a038a7a03e8df8d7952024ed41ccb3db7a7ade4abc290ce1", + "nonce": "0x28c446f1cb9748c1", + "number": "2290743", + "stateRoot": "0x4898aceede76739daef76448a367d10015a2c022c9e7909b99a10fbf6fb16708", + "timestamp": "1513616414", + "totalDifficulty": "7146523769022564" + }, + "input": "0xf8aa0e8509502f9000830493e0941d3ddf7caf024f253487e18bc4a15b1a360c170a80b8443b91f506000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e829a0524564944fa419f5c189b5074044f89210c6d6b2d77ee8f7f12a927d59b636dfa0015b28986807a424b18b186ee6642d76739df36cad802d20e8c00e79a61d7281", + "result": [ + { + "action": { + "callType": "call", + "from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8", + "gas": "0x493e0", + "input": "0x3b91f506000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e8", + "to": "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a", + "value": "0x0" + }, + "blockNumber": 2290744, + "error": "invalid jump destination", + "result": {}, + "subtraces": 1, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "from": "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a", + "gas": "0x39ff0", + "init": "0x606060405234620000005760405160208062001fd283398101604052515b805b600a8054600160a060020a031916600160a060020a0383161790555b506001600d819055600e81905560408051808201909152600c8082527f566f74696e672053746f636b00000000000000000000000000000000000000006020928301908152600b805460008290528251601860ff1990911617825590947f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9600291831615610100026000190190921604601f0193909304830192906200010c565b828001600101855582156200010c579182015b828111156200010c578251825591602001919060010190620000ef565b5b50620001309291505b808211156200012c576000815560010162000116565b5090565b50506040805180820190915260038082527f43565300000000000000000000000000000000000000000000000000000000006020928301908152600c805460008290528251600660ff1990911617825590937fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c760026001841615610100026000190190931692909204601f010481019291620001f7565b82800160010185558215620001f7579182015b82811115620001f7578251825591602001919060010190620001da565b5b506200021b9291505b808211156200012c576000815560010162000116565b5090565b50505b505b611da280620002306000396000f3006060604052361561019a5763ffffffff60e060020a600035041662e1986d811461019f57806302a72a4c146101d657806306eb4e421461020157806306fdde0314610220578063095ea7b3146102ad578063158ccb99146102dd57806318160ddd146102f85780631cf65a781461031757806323b872dd146103365780632c71e60a1461036c57806333148fd6146103ca578063435ebc2c146103f55780635eeb6e451461041e578063600e85b71461043c5780636103d70b146104a157806362c1e46a146104b05780636c182e99146104ba578063706dc87c146104f057806370a082311461052557806377174f851461055057806395d89b411461056f578063a7771ee3146105fc578063a9059cbb14610629578063ab377daa14610659578063b25dbb5e14610685578063b89a73cb14610699578063ca5eb5e1146106c6578063cbcf2e5a146106e1578063d21f05ba1461070e578063d347c2051461072d578063d96831e114610765578063dd62ed3e14610777578063df3c211b146107a8578063e2982c21146107d6578063eb944e4c14610801575b610000565b34610000576101d4600160a060020a036004351660243567ffffffffffffffff6044358116906064358116906084351661081f565b005b34610000576101ef600160a060020a0360043516610a30565b60408051918252519081900360200190f35b34610000576101ef610a4f565b60408051918252519081900360200190f35b346100005761022d610a55565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516602435610ae3565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516610b4e565b005b34610000576101ef610b89565b60408051918252519081900360200190f35b34610000576101ef610b8f565b60408051918252519081900360200190f35b34610000576102c9600160a060020a0360043581169060243516604435610b95565b604080519115158252519081900360200190f35b3461000057610388600160a060020a0360043516602435610bb7565b60408051600160a060020a039096168652602086019490945267ffffffffffffffff928316858501529082166060850152166080830152519081900360a00190f35b34610000576101ef600160a060020a0360043516610c21565b60408051918252519081900360200190f35b3461000057610402610c40565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d4600160a060020a0360043516602435610c4f565b005b3461000057610458600160a060020a0360043516602435610cc9565b60408051600160a060020a03909716875260208701959095528585019390935267ffffffffffffffff9182166060860152811660808501521660a0830152519081900360c00190f35b34610000576101d4610d9e565b005b6101d4610e1e565b005b34610000576104d3600160a060020a0360043516610e21565b6040805167ffffffffffffffff9092168252519081900360200190f35b3461000057610402600160a060020a0360043516610ead565b60408051600160a060020a039092168252519081900360200190f35b34610000576101ef600160a060020a0360043516610ef9565b60408051918252519081900360200190f35b34610000576101ef610f18565b60408051918252519081900360200190f35b346100005761022d610f1e565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516610fac565b604080519115158252519081900360200190f35b34610000576102c9600160a060020a0360043516602435610fc2565b604080519115158252519081900360200190f35b3461000057610402600435610fe2565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d46004351515610ffd565b005b34610000576102c9600160a060020a036004351661104c565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516611062565b005b34610000576102c9600160a060020a0360043516611070565b604080519115158252519081900360200190f35b34610000576101ef6110f4565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351667ffffffffffffffff602435166110fa565b60408051918252519081900360200190f35b34610000576101d4600435611121565b005b34610000576101ef600160a060020a03600435811690602435166111c6565b60408051918252519081900360200190f35b34610000576101ef6004356024356044356064356084356111f3565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351661128c565b60408051918252519081900360200190f35b34610000576101d4600160a060020a036004351660243561129e565b005b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915267ffffffffffffffff848116908416101561086457610000565b8367ffffffffffffffff168267ffffffffffffffff16101561088557610000565b8267ffffffffffffffff168267ffffffffffffffff1610156108a657610000565b506040805160a081018252600160a060020a033381168252602080830188905267ffffffffffffffff80871684860152858116606085015287166080840152908816600090815260039091529190912080546001810180835582818380158290116109615760030281600302836000526020600020918201910161096191905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050916000526020600020906003020160005b5082518154600160a060020a031916600160a060020a03909116178155602083015160018201556040830151600290910180546060850151608086015167ffffffffffffffff1990921667ffffffffffffffff948516176fffffffffffffffff00000000000000001916604060020a918516919091021777ffffffffffffffff000000000000000000000000000000001916608060020a939091169290920291909117905550610a268686610fc2565b505b505050505050565b600160a060020a0381166000908152600360205260409020545b919050565b60055481565b600b805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b600160a060020a03338116600081815260026020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b600a5433600160a060020a03908116911614610b6957610000565b600a8054600160a060020a031916600160a060020a0383161790555b5b50565b60005481565b60005b90565b6000610ba2848484611600565b610bad8484846116e2565b90505b9392505050565b600360205281600052604060002081815481101561000057906000526020600020906003020160005b5080546001820154600290920154600160a060020a03909116935090915067ffffffffffffffff80821691604060020a8104821691608060020a9091041685565b600160a060020a0381166000908152600860205260409020545b919050565b600a54600160a060020a031681565b600a5433600160a060020a03908116911614610c6a57610000565b610c7660005482611714565b6000908155600160a060020a038316815260016020526040902054610c9b9082611714565b600160a060020a038316600090815260016020526040812091909155610cc390839083611600565b5b5b5050565b6000600060006000600060006000600360008a600160a060020a0316600160a060020a0316815260200190815260200160002088815481101561000057906000526020600020906003020160005b508054600182015460028301546040805160a081018252600160a060020a039094168085526020850184905267ffffffffffffffff808416928601839052604060020a8404811660608701819052608060020a9094041660808601819052909c50929a509197509095509350909150610d90904261172d565b94505b509295509295509295565b33600160a060020a038116600090815260066020526040902054801515610dc457610000565b8030600160a060020a0316311015610ddb57610000565b600160a060020a0382166000818152600660205260408082208290555183156108fc0291849190818181858888f193505050501515610cc357610000565b5b5050565b5b565b600160a060020a03811660009081526003602052604081205442915b81811015610ea557600160a060020a03841660009081526003602052604090208054610e9a9190839081101561000057906000526020600020906003020160005b5060020154604060020a900467ffffffffffffffff168461177d565b92505b600101610e3d565b5b5050919050565b600160a060020a0380821660009081526007602052604081205490911615610eef57600160a060020a0380831660009081526007602052604090205416610ef1565b815b90505b919050565b600160a060020a0381166000908152600160205260409020545b919050565b600d5481565b600c805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b60006000610fb983610c21565b1190505b919050565b6000610fcf338484611600565b610fd983836117ac565b90505b92915050565b600460205260009081526040902054600160a060020a031681565b8015801561101a575061100f33610ef9565b61101833610c21565b115b1561102457610000565b33600160a060020a03166000908152600960205260409020805460ff19168215151790555b50565b60006000610fb983610ef9565b1190505b919050565b610b8533826117dc565b5b50565b600a54604080516000602091820181905282517fcbcf2e5a000000000000000000000000000000000000000000000000000000008152600160a060020a03868116600483015293519194939093169263cbcf2e5a92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b600e5481565b6000610fd961110984846118b2565b61111385856119b6565b611a05565b90505b92915050565b600a5433600160a060020a0390811691161461113c57610000565b61114860005482611a1f565b600055600554600190101561116c57600a5461116c90600160a060020a0316611a47565b5b600a54600160a060020a03166000908152600160205260409020546111929082611a1f565b600a8054600160a060020a039081166000908152600160205260408120939093559054610b8592911683611600565b5b5b50565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b6000600060008487101561120a5760009250611281565b8387111561121a57879250611281565b61123f6112308961122b888a611714565b611a90565b61123a8689611714565b611abc565b915081925061124e8883611714565b905061127e8361127961126a8461122b8c8b611714565b611a90565b61123a888b611714565b611abc565b611a1f565b92505b505095945050505050565b60066020526000908152604090205481565b600160a060020a03821660009081526003602052604081208054829190849081101561000057906000526020600020906003020160005b50805490925033600160a060020a039081169116146112f357610000565b6040805160a0810182528354600160a060020a0316815260018401546020820152600284015467ffffffffffffffff80821693830193909352604060020a810483166060830152608060020a900490911660808201526113539042611af9565b600160a060020a0385166000908152600360205260409020805491925090849081101561000057906000526020600020906003020160005b508054600160a060020a031916815560006001820181905560029091018054600160c060020a0319169055600160a060020a0385168152600360205260409020805460001981019081101561000057906000526020600020906003020160005b50600160a060020a03851660009081526003602052604090208054859081101561000057906000526020600020906003020160005b5081548154600160a060020a031916600160a060020a03918216178255600180840154908301556002928301805493909201805467ffffffffffffffff191667ffffffffffffffff948516178082558354604060020a908190048616026fffffffffffffffff000000000000000019909116178082559254608060020a9081900490941690930277ffffffffffffffff00000000000000000000000000000000199092169190911790915584166000908152600360205260409020805460001981018083559190829080158290116115485760030281600302836000526020600020918201910161154891905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050600160a060020a033316600090815260016020526040902054611570915082611a1f565b600160a060020a03338116600090815260016020526040808220939093559086168152205461159f9082611714565b600160a060020a038086166000818152600160209081526040918290209490945580518581529051339093169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35b50505050565b600160a060020a0383161561166e576116466008600061161f86610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611714565b6008600061165386610ead565b600160a060020a031681526020810191909152604001600020555b600160a060020a038216156116dc576116b46008600061168d85610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611a1f565b600860006116c185610ead565b600160a060020a031681526020810191909152604001600020555b5b505050565b600083826116f082426110fa565b8111156116fc57610000565b611707868686611b1b565b92505b5b50509392505050565b600061172283831115611b4d565b508082035b92915050565b6000610fd983602001518367ffffffffffffffff16856080015167ffffffffffffffff16866040015167ffffffffffffffff16876060015167ffffffffffffffff166111f3565b90505b92915050565b60008167ffffffffffffffff168367ffffffffffffffff1610156117a15781610fd9565b825b90505b92915050565b600033826117ba82426110fa565b8111156117c657610000565b6117d08585611b5d565b92505b5b505092915050565b6117e582610ef9565b6117ee83610c21565b11156117f957610000565b600160a060020a03811660009081526009602052604090205460ff16158015611834575081600160a060020a031681600160a060020a031614155b1561183e57610000565b61184782611070565b1561185157610000565b611864828261185f85610ef9565b611600565b600160a060020a0382811660009081526007602052604090208054600160a060020a031916918316918217905561189a82610ead565b600160a060020a031614610cc357610000565b5b5050565b600160a060020a038216600090815260036020526040812054815b818110156119885761197d836112796003600089600160a060020a0316600160a060020a0316815260200190815260200160002084815481101561000057906000526020600020906003020160005b506040805160a0810182528254600160a060020a031681526001830154602082015260029092015467ffffffffffffffff80821692840192909252604060020a810482166060840152608060020a900416608082015287611af9565b611a1f565b92505b6001016118cd565b600160a060020a0385166000908152600160205260409020546117d09084611714565b92505b505092915050565b600060006119c384611070565b80156119d157506000600d54115b90506119fb816119e9576119e485610ef9565b6119ec565b60005b6111138686611b7b565b611a05565b91505b5092915050565b60008183106117a15781610fd9565b825b90505b92915050565b6000828201611a3c848210801590611a375750838210155b611b4d565b8091505b5092915050565b611a508161104c565b15611a5a57610b85565b6005805460009081526004602052604090208054600160a060020a031916600160a060020a038416179055805460010190555b50565b6000828202611a3c841580611a37575083858381156100005704145b611b4d565b8091505b5092915050565b60006000611acc60008411611b4d565b8284811561000057049050611a3c838581156100005706828502018514611b4d565b8091505b5092915050565b6000610fd98360200151611b0d858561172d565b611714565b90505b92915050565b60008382611b2982426110fa565b811115611b3557610000565b611707868686611b8f565b92505b5b50509392505050565b801515610b8557610000565b5b50565b6000611b6883611a47565b610fd98383611c92565b90505b92915050565b6000610fd983610ef9565b90505b92915050565b600160a060020a038084166000908152600260209081526040808320338516845282528083205493861683526001909152812054909190611bd09084611a1f565b600160a060020a038086166000908152600160205260408082209390935590871681522054611bff9084611714565b600160a060020a038616600090815260016020526040902055611c228184611714565b600160a060020a038087166000818152600260209081526040808320338616845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600191505b509392505050565b60003382611ca082426110fa565b811115611cac57610000565b6117d08585611cc2565b92505b5b505092915050565b600160a060020a033316600090815260016020526040812054611ce59083611714565b600160a060020a033381166000908152600160205260408082209390935590851681522054611d149083611a1f565b600160a060020a038085166000818152600160209081526040918290209490945580518681529051919333909316927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b929150505600a165627a7a72305820bfa5ddd3fecf3f43aed25385ec7ec3ef79638c2e58d99f85d9a3cc494183bf160029000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182", + "value": "0x0" + }, + "blockNumber": 0, + "error": "contract creation code storage out of gas", + "result": {}, + "subtraces": 0, + "traceAddress": [0], + "type": "create" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_instafail.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_instafail.json new file mode 100644 index 0000000..611e50e --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_instafail.json @@ -0,0 +1,86 @@ +{ + "genesis": { + "difficulty": "117067574", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712380", + "hash": "0xe05db05eeb3f288041ecb10a787df121c0ed69499355716e17c307de313a4486", + "miner": "0x0c062b329265c965deef1eede55183b3acb8f611", + "mixHash": "0xb669ae39118a53d2c65fd3b1e1d3850dd3f8c6842030698ed846a2762d68b61d", + "nonce": "0x2b469722b8e28c45", + "number": "24973", + "stateRoot": "0x532a5c3f75453a696428db078e32ae283c85cb97e4d8560dbdf022adac6df369", + "timestamp": "1479891145", + "totalDifficulty": "1892250259406", + "alloc": { + "0x6c06b16512b332e6cd8293a2974872674716ce18": { + "balance": "0x0", + "nonce": "1", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900480632e1a7d4d146036575b6000565b34600057604e60048080359060200190919050506050565b005b3373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f19350505050505b5056", + "storage": {} + }, + "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31": { + "balance": "0x229ebbb36c3e0f20", + "nonce": "3", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 3, + "homesteadBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "byzantiumBlock": 1700000, + "constantinopleBlock": 4230000, + "petersburgBlock": 4939394, + "istanbulBlock": 6485846, + "muirGlacierBlock": 7117117, + "ethash": {} + } + }, + "context": { + "number": "24974", + "difficulty": "117067574", + "timestamp": "1479891162", + "gasLimit": "4712388", + "miner": "0xc822ef32e6d26e170b70cf761e204c1806265914" + }, + "input": "0xf889038504a81557008301f97e946c06b16512b332e6cd8293a2974872674716ce1880a42e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b1600002aa0e2a6558040c5d72bc59f2fb62a38993a314c849cd22fb393018d2c5af3112095a01bdb6d7ba32263ccc2ecc880d38c49d9f0c5a72d8b7908e3122b31356d349745", + "result": [ + { + "action": { + "callType": "call", + "from": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31", + "gas": "0x1f97e", + "input": "0x2e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b160000", + "to": "0x6c06b16512b332e6cd8293a2974872674716ce18", + "value": "0x0" + }, + "blockNumber": 24974, + "result": { + "gasUsed": "0x72de", + "output": "0x" + }, + "subtraces": 1, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x6c06b16512b332e6cd8293a2974872674716ce18", + "gas": "0x8fc", + "to": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31", + "value": "0x14d1120d7b160000" + }, + "error": "insufficient balance for transfer", + "result": {}, + "subtraces": 0, + "traceAddress": [0], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_precompiled_wrong_gas.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_precompiled_wrong_gas.json new file mode 100644 index 0000000..70442fd --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_precompiled_wrong_gas.json @@ -0,0 +1,219 @@ +{ + "genesis": { + "number": "559197", + "hash": "0x0742a2bfab0452e2c634f3685b7e49ceb065c7000609b2b73f086e01fd1dfb58", + "nonce": "0x3060ad521440e1c2", + "mixHash": "0x59e7d4ae6cc3c38d23dac3f869b21984c7ba8f38070f4116a4941d9c403b6299", + "stateRoot": "0x68418fb5cf4afa9b807dc079e8cdde0e148ac2c8afb378e675465b5bed1fbd02", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "difficulty": "1813945", + "totalDifficulty": "469107641961", + "extraData": "0xd883010906846765746888676f312e31332e35856c696e7578", + "gasLimit": "6321166", + "timestamp": "1577471202", + "alloc": { + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0xc5e6fdae52af83f7e28", + "nonce": "77947", + "code": "0x", + "storage": {} + }, + "0x774c398d763161f55b66a646f17edda4addad2ca": { + "balance": "0xf09ef316eff819ee488", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc": { + "balance": "0x0", + "nonce": "1", + "code": "0x60006121df537c01000000000000000000000000000000000000000000000000000000006000350463b041b2858114156100d257600435604052780100000000000000000000000000000000000000000000000060606060599059016000905260038152604051816020015260008160400152809050205404606052606051151561008f57600060a052602060a0f35b604051601c604459905901600090520163e0e9a17b601c82035260605160048201526020610100602483600030602d5a03f1506101005190501460c052602060c0f35b632cce81aa81141561019957600435610120526001610120511280156100f85780610143565b78010000000000000000000000000000000000000000000000006060606059905901600090526003815266040000000000025481602001526000816040015280905020540461012051135b905015610157576000610180526020610180f35b601c604459905901600090520163e0e9a17b601c82035261012051600482015260206101c0602483600030602d5a03f1506101c05190506101a05260206101a0f35b63e0e9a17b8114156102e957600435610120526604000000000002546101e0526007610200525b610120517801000000000000000000000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540413156102da575b6102005160050a610120517801000000000000000000000000000000000000000000000000606060605990590160009052600381526101e051816020015260008160400152809050205404031215610269576000610200511361026c565b60005b1561028157600161020051036102005261020b565b7c01000000000000000000000000000000000000000000000000000000006102005160200260020a606060605990590160009052600381526101e05181602001526001816040015280905020540204546101e0526101c0565b6101e051610280526020610280f35b63cef887b08114156103e757365990590160009052366004823760043560208201016102c0526024356102e052506060601c61014c5990590160009052016390fa337d601c8203526102c0516020601f602083035101046020026020018360048401526020820360648401528060c8840152808401935050506102e051602482015233604482015281600401599059016000905260648160648460006004601cf161039057fe5b60648101925060c882015180808582606487015160006004600a8705601201f16103b657fe5b5080840193505080830360206103a08284600030602d5a03f1506103a0519050905090509050610300526020610300f35b6390fa337d81141561065f57365990590160009052366004823760043560208201016102c0526024356102e0526044356103e052505a610400526020601c608c599059016000905201632b861629601c8203526102c0516020601f6020830351010460200260200183600484015260208203602484015280604884015280840193505050816004015990590160009052602481602484600060046015f161048a57fe5b602481019250604882015180808582602487015160006004600a8705601201f16104b057fe5b5080840193505080830360206104408284600030602d5a03f15061044051905090509050905061042052610420511561065e576102c05160208103516020599059016000905260208183856000600287604801f150805190509050905061046052602059905901600090526020816020610460600060026068f1508051905060005b6020811215610552578181601f031a816105400153600181019050610532565b5050610540516101e0526102e0516c010000000000000000000000006103e0510217606060605990590160009052600381526101e05181602001526003816040015280905020555a61058052700100000000000000000000000000000000660400000000000154046105a0526104006105a0516103ff02056105c0526104006105a05161040102056105e0526105c0513a12156105f6576105c05161060052610615565b6105e0513a131561060e576105e05161060052610614565b3a610600525b5b6105805161040051036106005160020202610620526106205170010000000000000000000000000000000061060051021766040000000000015561042051610640526020610640f35b5b63d467ae0381141561073257600435604052602435610660526106605134121515610725576000341315610718576c01000000000000000000000000606060605990590160009052600381526040518160200152600381604001528090502054046103e0526000600060006000346103e051611388f115156106dd57fe5b601c60405990590160009052013481526103e0517f15e746bf513b8a58e4265cc1162d7fc445da5c9b1928d7cfcde2582735d4677f602083a2505b60016106a05260206106a0f35b60006106c05260206106c0f35b63ea4971ee811415610851576004356101e0526024356102e0526044356103e052601c606459905901600090520163d467ae03601c8203526101e05160048201526604000000000001546fffffffffffffffffffffffffffffffff16602482015260206106e060448334306123555a03f1506106e051905015156107bd576000610700526020610700f35b606060605990590160009052600381526101e05181602001526003816040015280905020546bffffffffffffffffffffffff166102e0511215610844576102e0516c010000000000000000000000006103e0510217606060605990590160009052600381526101e05181602001526003816040015280905020556001610760526020610760f35b6000610780526020610780f35b6387def0818114156108a3576004356101e0526c01000000000000000000000000606060605990590160009052600381526101e0518160200152600381604001528090502054046107a05260206107a0f35b630aece23c8114156108f4576004356101e052606060605990590160009052600381526101e05181602001526003816040015280905020546bffffffffffffffffffffffff166107e05260206107e0f35b63fa14df6b811415610926576604000000000001546fffffffffffffffffffffffffffffffff16610820526020610820f35b63b8c48f8c811415610b1b576004356101e0526024356108405260443561086052600066040000000000035414151561096a576000610880526020610880f3610976565b60016604000000000003555b6101e051660400000000000255606060605990590160009052600381526101e05181602001526000816040015280905020546108a0526108a0610840518060181a82538060191a600183015380601a1a600283015380601b1a600383015380601c1a600483015380601d1a600583015380601e1a600683015380601f1a600783015350506108a051606060605990590160009052600381526101e0518160200152600081604001528090502055606060605990590160009052600381526101e051816020015260008160400152809050205461094052601061094001610860518060101a82538060111a60018301538060121a60028301538060131a60038301538060141a60048301538060151a60058301538060161a60068301538060171a60078301538060181a60088301538060191a600983015380601a1a600a83015380601b1a600b83015380601c1a600c83015380601d1a600d83015380601e1a600e83015380601f1a600f830153505061094051606060605990590160009052600381526101e051816020015260008160400152809050205560016109e05260206109e0f35b632b86162981141561179457365990590160009052366004823760043560208201016102c0525060483560005b6020811215610b68578181601f031a81610a600153600181019050610b48565b5050610a6051610a00526102c05160208103516020599059016000905260208183856000600287604801f1508051905090509050610a8052602059905901600090526020816020610a80600060026068f1508051905060005b6020811215610be1578181601f031a81610b600153600181019050610bc1565b5050610b60516101e05270010000000000000000000000000000000070010000000000000000000000000000000060606060599059016000905260038152610a005181602001526000816040015280905020540204610b8052610b80511515610c8b57601c602059905901600090520161272e6101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a3506000610bc0526020610bc0f35b700100000000000000000000000000000000700100000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540204610be0526000610be051141515610d2e57601c60205990590160009052016127386101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a3506000610c20526020610c20f35b608c35610c40526301000000610c405160031a0262010000610c405160021a02610100610c405160011a02610c405160001a010101610c60526301000000610c605104610ca05262ffffff610c605116610cc0526003610ca051036101000a610cc05102610c805260006101e0511315610db057610c80516101e05112610db3565b60005b1561174d57780100000000000000000000000000000000000000000000000060606060599059016000905260038152610a00518160200152600081604001528090502054046001016101205260806080599059016000905260038152610a005181602001526002816040015260008160600152809050206002810154610d405250610d405160081a610d405160091a61010002610d4051600a1a6201000002610d4051600b1a630100000002010101610d005260006107e0610120510614158015610e7e5780610e8b565b6001660400000000000054145b905015610f0257610d0051610c6051141515610eae576000610d00511415610eb1565b60005b15610efd57601c602059905901600090520161271a6101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a3506000610da0526020610da0f35b6111b4565b6301000000610d005104610de05262ffffff610d005116610e00526003610de051036101000a610e005102610dc05260806080599059016000905260038152610a005181602001526002816040015260008160600152809050206002810154610e605250610e605160041a610e605160051a61010002610e605160061a6201000002610e605160071a630100000002010101610e2052601c604459905901600090520163e0e9a17b601c8203526107e0610120510360048201526020610ec0602483600030602d5a03f150610ec0519050610ea05260806080599059016000905260038152610ea05181602001526002816040015260008160600152809050206002810154610f205250610f205160041a610f205160051a61010002610f205160061a6201000002610f205160071a630100000002010101610ee052610ee051610e20510362049d408112156110595762049d4090505b6249d40081131561106b576249d40090505b62127500610dc0518202047bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8113156110ba577bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90505b600860076000835b80156110d9576002810490506001820191506110c2565b5080905001046000600382131515611103578160030360080260020a62ffffff841602905061111a565b6003820360080260020a8304905062ffffff811690505b6280000081161561113357610100810490506001820191505b6301000000820281179050905090509050610f6052610f6051610c6051141515611164576000610f60511415611167565b60005b156111b357601c60205990590160009052016127246101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a3506000611040526020611040f35b5b6101e0516101e0516101e05166040000000000005455606060605990590160009052600381526101e0518160200152600081604001528090502054611060526008611060016604000000000000548060181a82538060191a600183015380601a1a600283015380601b1a600383015380601c1a600483015380601d1a600583015380601e1a600683015380601f1a6007830153505061106051606060605990590160009052600381526101e0518160200152600081604001528090502055600166040000000000005401660400000000000055606060605990590160009052600381526101e0518160200152600081604001528090502054611100526111006001780100000000000000000000000000000000000000000000000060606060599059016000905260038152610a0051816020015260008160400152809050205404018060181a82538060191a600183015380601a1a600283015380601b1a600383015380601c1a600483015380601d1a600583015380601e1a600683015380601f1a6007830153505061110051606060605990590160009052600381526101e051816020015260008160400152809050205560006111c05278010000000000000000000000000000000000000000000000006801000000000000000060606060599059016000905260038152610a0051816020015260008160400152809050205402046111e0526111c06111e05180601c1a825380601d1a600183015380601e1a600283015380601f1a600383015350506001611260525b6008611260511215611515576112605160050a611280526001611280517801000000000000000000000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540407141561148757611260516004026111c0016111e05180601c1a825380601d1a600183015380601e1a600283015380601f1a60038301535050611505565b611260516004026111c0017c01000000000000000000000000000000000000000000000000000000006112605160200260020a60606060599059016000905260038152610a00518160200152600181604001528090502054020480601c1a825380601d1a600183015380601e1a600283015380601f1a600383015350505b60016112605101611260526113ec565b6111c051606060605990590160009052600381526101e05181602001526001816040015280905020555050608060805990590160009052600381526101e051816020015260028160400152600081606001528090502060005b600281121561159057806020026102c05101518282015560018101905061156e565b700100000000000000000000000000000000600003816020026102c051015116828201555050610c80517bffff0000000000000000000000000000000000000000000000000000056113e0526113e051610b805101610be052606060605990590160009052600381526101e051816020015260008160400152809050205461140052601061140001610be0518060101a82538060111a60018301538060121a60028301538060131a60038301538060141a60048301538060151a60058301538060161a60068301538060171a60078301538060181a60088301538060191a600983015380601a1a600a83015380601b1a600b83015380601c1a600c83015380601d1a600d83015380601e1a600e83015380601f1a600f830153505061140051606060605990590160009052600381526101e0518160200152600081604001528090502055660400000000000354610be051121515611703576101e051660400000000000255610be0516604000000000003555b601c6020599059016000905201610120516101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a350610120516114a05260206114a0f35b601c602059905901600090520161276a6101e0517f055e4f8dd3a534789b3feb8e0681afa2aee8713fdd6472f25b2c30dc7bf4e0f4600084a35060006114c05260206114c0f35b630f5995ce8114156119a157365990590160009052366004823760043560208201016114e05260243561150052604435602082010161152052606435604052506114e05160208103516020599059016000905260208183856000600287604801f150805190509050905061156052602059905901600090526020816020611560600060026068f1508051905060005b6020811215611843578181601f031a816116400153600181019050611823565b50506116405161154052604060206114e051035114156118a457601c6020599059016000905201614e52611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a3506000611660526020611660f35b6080601c6101ac59905901600090520163bd136cb3601c8203526115405160048201526115005160248201526115205160208103516020026020018360448401526020820360c48401528061014884015280840193505050604051606482015281600401599059016000905260848160848460006004601ff161192357fe5b6084810192506101488201518080858260c487015160006004600a8705601201f161194a57fe5b508084019350508083036020611680828434306123555a03f15061168051905090509050905061042052600161042051141561199357611540516116a05260206116a0f36119a0565b60006116c05260206116c0f35b5b63bd136cb3811415611d8c573659905901600090523660048237600435611540526024356115005260443560208201016115205260643560405250601c606459905901600090520163d467ae03601c82035260405160048201526060606059905901600090526003815260405181602001526003816040015280905020546bffffffffffffffffffffffff166024820152602061170060448334306123555a03f1506117005190501515611a9757601c6020599059016000905201614e2a611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a350614e2a611720526020611720f35b601c6044599059016000905201633d73b705601c82035260405160048201526020611740602483600030602d5a03f15061174051905015611b1a57601c6020599059016000905201614e34611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a350614e34611760526020611760f35b601c604459905901600090520163b041b285601c82035260405160048201526020611780602483600030602d5a03f1506117805190501515611b9e57601c6020599059016000905201614e3e611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a350614e3e6117a05260206117a0f35b6060601c61014c59905901600090520163b7129afb601c8203526115405160048201526115005160248201526115205160208103516020026020018360448401526020820360a4840152806101088401528084019350505081600401599059016000905260648160648460006004601cf1611c1557fe5b6064810192506101088201518080858260a487015160006004600a8705601201f1611c3c57fe5b5080840193505080830360206117e08284600030602d5a03f1506117e05190509050905090506117c0526080608059905901600090526003815260405181602001526002816040015260008160600152809050207c01000000000000000000000000000000000000000000000000000000006002820154046401000000006001830154020160005b6020811215611ce4578181601f031a816118a00153600181019050611cc4565b50506118a051905061180052611800516117c0511415611d4457601c60205990590160009052016001611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a35060016118c05260206118c0f35b601c6020599059016000905201614e48611540517fd008620948a1ed10f4fed82dc43cf79acad36dc6b7c2c924e27c9813193b83ad600084a350614e486118e05260206118e0f35b63318a3fee81141561205657365990590160009052366004823760043560208201016114e0526024356115005260443560208201016115205260643560405260843561190052506080601c6101ac599059016000905201630f5995ce601c8203526114e0516020601f6020830351010460200260200183600484015260208203608484015280610108840152808401935050506115005160248201526115205160208103516020026020018360448401526020820360c48401528061014884015280840193505050604051606482015281600401599059016000905260848160848460006004601ff1611e7b57fe5b60848101925061010882015180808582608487015160006004600a8705601201f1611ea257fe5b508084019350506101488201518080858260c487015160006004600a8705601201f1611eca57fe5b508084019350508083036020611920828434306123555a03f15061192051905090509050905061154052600061154051141515612010576040601c60ec599059016000905201631c0b6367601c8203526114e0516020601f6020830351010460200260200183600484015260208203604484015280608884015280840193505050611540516024820152816004015990590160009052604481604484600060046018f1611f7357fe5b604481019250608882015180808582604487015160006004600a8705601201f1611f9957fe5b5080840193505080830360206119608284600061190051602d5a03f15061196051905090509050905061194052601c602059905901600090520161194051611540517f2d0d11d0f27e21fab56a8712078721096066b7faaa8540a3ea566e70b97de2d4600084a35061194051611980526020611980f35b601c602059905901600090520161753a60007f2d0d11d0f27e21fab56a8712078721096066b7faaa8540a3ea566e70b97de2d4600084a35061753a6119a05260206119a0f35b6309dd0e81811415612076576604000000000002546119c05260206119c0f35b63023948728114156120d2577801000000000000000000000000000000000000000000000000606060605990590160009052600381526604000000000002548160200152600081604001528090502054046119e05260206119e0f35b632c181929811415612139577001000000000000000000000000000000007001000000000000000000000000000000006060606059905901600090526003815266040000000000025481602001526000816040015280905020540204611a20526020611a20f35b637ca823d58114156122af576604000000000002546101e052700100000000000000000000000000000000700100000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540204611a60526000611260525b600a61126051121561224c57608060805990590160009052600381526101e05181602001526002816040015260008160600152809050207c01000000000000000000000000000000000000000000000000000000006001820154046401000000008254020160005b6020811215612230578181601f031a81611b200153600181019050612210565b5050611b205190506101e05260016112605101611260526121a8565b700100000000000000000000000000000000700100000000000000000000000000000000606060605990590160009052600381526101e05181602001526000816040015280905020540204611b4052611b4051611a605103611b80526020611b80f35b63b7129afb81141561246a57365990590160009052366004823760043561154052602435611500526044356020820101611520525061154051611ba0526020611520510351611bc0526000611260525b611bc05161126051121561245b5761126051602002611520510151611be05260026115005107611c00526001611c0051141561234a57611be051611c2052611ba051611c4052612368565b6000611c0051141561236757611ba051611c2052611be051611c40525b5b60405990590160009052611c205160005b6020811215612399578181601f031a81611ca00153600181019050612379565b5050611ca0518152611c405160005b60208112156123c8578181601f031a81611d2001536001810190506123a8565b5050611d2051602082015260205990590160009052602081604084600060026088f15080519050611d4052602059905901600090526020816020611d40600060026068f1508051905060005b6020811215612434578181601f031a81611de00153600181019050612414565b5050611de0519050611ba052600261150051056115005260016112605101611260526122ff565b611ba051611e00526020611e00f35b633d73b70581141561255b576004356040526604000000000002546101e0526000611260525b600661126051121561254e576101e05160405114156124b6576001611e20526020611e20f35b608060805990590160009052600381526101e05181602001526002816040015260008160600152809050207c01000000000000000000000000000000000000000000000000000000006001820154046401000000008254020160005b6020811215612532578181601f031a81611ec00153600181019050612512565b5050611ec05190506101e0526001611260510161126052612490565b6000611ee0526020611ee0f35b631f794436811415612737576004356101e052601c606459905901600090520163d467ae03601c8203526101e0516004820152606060605990590160009052600381526101e05181602001526003816040015280905020546bffffffffffffffffffffffff1660248201526020611f2060448334306123555a03f150611f20519050151561265657601c602059905901600090520160006101e0517f60ab231f060fa320acea170017564b7ee77f477e6465a8c964380cffb270aaf4600084a350602159905901600090526001815260006020820152602081019050602060408203526020601f6020830351604001010460200260408203f3505b601c602059905901600090520160016101e0517f60ab231f060fa320acea170017564b7ee77f477e6465a8c964380cffb270aaf4600084a350608060805990590160009052600381526101e0518160200152600281604001526000816060015280905020607059905901600090526050815260208101905060005b60028112156126f05780830154816020028301526001810190506126d1565b70010000000000000000000000000000000060000381840154168160200283015281905090509050602060408203526020601f6020830351604001010460200260408203f3505b6313f955e18114156128ca573659905901600090523660048237600435602082010161204052602435612060525060506120805260006120a052612080516120c0526000611260525b612060516112605112156128bb576120a051806120c051038080602001599059016000905281815260208101905090508180828286612040510160006004600a8705601201f16127cc57fe5b50809050905090506120e0526020601c608c599059016000905201632b861629601c8203526120e0516020601f6020830351010460200260200183600484015260208203602484015280604884015280840193505050816004015990590160009052602481602484600060046015f161284157fe5b602481019250604882015180808582602487015160006004600a8705601201f161286757fe5b5080840193505080830360206121a08284600030602d5a03f1506121a051905090509050905061042052612080516120a051016120a052612080516120c051016120c0526001611260510161126052612780565b610420516121c05260206121c0f35b50", + "storage": { + "0x292b7a8d467a95cffd303c7edd99875892cdb3eaee87e5ca29057dc88a09ffbd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4d2fcf8ac901ad7dcf5b1c3979801430d9979c87157230ae066a0276984c6ac7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xdf951a5d1d9283b06d4f1de58542f1e1e310d8d17aada46586ddb9598bc42894": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x9c8d09d387f3ba5dd4733e24c63e4d549864a7cd57a1bdf1fdd831a2a0184815": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4ab3b783bb170e11b0932a5ce8f5f343f67058b3925da271001a75ae498bd655": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x0000000000000000000000000000000000000004": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x0000000000000000000000000000000000000002": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "559198", + "difficulty": "1814830", + "timestamp": "1577471205", + "gasLimit": "6327338", + "miner": "0x774c398d763161f55b66a646f17edda4addad2ca" + }, + "input": "0xf9026f8301307b85746a52880083124f80946cc68eb482a757c690dd151d2bd5e774ada38bdc80b9020413f955e100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000019004000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a67040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae704000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc30304000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de04000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e00000000000000000000000000000000081a1a01c9e9d742c8e69daba2a026ccafdde618f2e44c96db281c2209c22f183ad03a2a049a61d267d22226896d4c065525819c238784c439dc2afa7d17fce76595730d1", + "result": [ + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "gas": "0x124f80", + "input": "0x13f955e100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000019004000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a67040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae704000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc30304000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de04000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e000000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1c6ff", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 5, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x114243", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005004000000afbe013b4a83b2f91f3d9b6627cf382394c4914fd2b7510700000000000000008621196eb526a0e02430b6dd5c72fd368e768977f3a8364861e5a471a8ae61a1028f745609c40b185f537a6700000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 0 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x110d3b", + "input": "0x2b86162900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050040000005b53875b0f1381589859adcf938980f4a8fb0af4c8845007000000000000000075289d1c48c8f71deee521a76c8d92948cbe14343991998dfaea6b08596d97dcc891745609c40b18ae825ae700000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 1 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x10d833", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005004000000abbacd8711f647ab97c6c9b9658eb9bef081e2cedb630f010000000000000000549bcab22422baef6c34af382b227e4b1a27bec3312e04dbb62fc315203c67f30f9d745609c40b180fdfc30300000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 2 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x10a328", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005004000000e93433dde5128942e47e8722d37ec4dcc1c8a78cf9c4a4030000000000000000bf92c09e8e37b2c8ffbb4b9cadfccc563e474c4feae6997f52d56236fedafce20a9f745609c40b1840cc27de00000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 3 + ], + "type": "call" + }, + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "gas": "0x106e1d", + "input": "0x2b8616290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005004000000f2e372a0b5b837116eee8f968840393d85975a1531346807000000000000000076bc91399edda1de98976ee0774e2ad3b21dd38ad9f5f34d2c816a832747fe7f4c9e745609c40b18e290e9e000000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x6cc68eb482a757c690dd151d2bd5e774ada38bdc", + "value": "0x0" + }, + "result": { + "address": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x27c3", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [ + 4 + ], + "type": "call" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_throw_outer_revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_throw_outer_revert.json new file mode 100644 index 0000000..bc94708 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/inner_throw_outer_revert.json @@ -0,0 +1,95 @@ +{ + "context": { + "difficulty": "3956606365", + "gasLimit": "5413248", + "miner": "0x00d8ae40d9a06d0e7a2877b62e32eb959afbe16d", + "number": "2295104", + "timestamp": "1513681256" + }, + "genesis": { + "alloc": { + "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76": { + "balance": "0x0", + "code": "0x60606040526004361061015e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680625b4487146101a257806311df9995146101cb578063278ecde11461022057806330adce0e146102435780633197cbb61461026c5780634bb278f3146102955780636103d70b146102aa57806363a599a4146102bf5780636a2d1cb8146102d457806375f12b21146102fd57806378e979251461032a578063801db9cc1461035357806386d1a69f1461037c5780638da5cb5b146103915780638ef26a71146103e65780639890220b1461040f5780639b39caef14610424578063b85dfb801461044d578063be9a6555146104a1578063ccb07cef146104b6578063d06c91e4146104e3578063d669e1d414610538578063df40503c14610561578063e2982c2114610576578063f02e030d146105c3578063f2fde38b146105d8578063f3283fba14610611575b600060149054906101000a900460ff1615151561017a57600080fd5b60075442108061018b575060085442115b15151561019757600080fd5b6101a03361064a565b005b34156101ad57600080fd5b6101b5610925565b6040518082815260200191505060405180910390f35b34156101d657600080fd5b6101de61092b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561022b57600080fd5b6102416004808035906020019091905050610951565b005b341561024e57600080fd5b610256610c48565b6040518082815260200191505060405180910390f35b341561027757600080fd5b61027f610c4e565b6040518082815260200191505060405180910390f35b34156102a057600080fd5b6102a8610c54565b005b34156102b557600080fd5b6102bd610f3e565b005b34156102ca57600080fd5b6102d261105d565b005b34156102df57600080fd5b6102e76110d5565b6040518082815260200191505060405180910390f35b341561030857600080fd5b6103106110e1565b604051808215151515815260200191505060405180910390f35b341561033557600080fd5b61033d6110f4565b6040518082815260200191505060405180910390f35b341561035e57600080fd5b6103666110fa565b6040518082815260200191505060405180910390f35b341561038757600080fd5b61038f611104565b005b341561039c57600080fd5b6103a4611196565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156103f157600080fd5b6103f96111bb565b6040518082815260200191505060405180910390f35b341561041a57600080fd5b6104226111c1565b005b341561042f57600080fd5b610437611296565b6040518082815260200191505060405180910390f35b341561045857600080fd5b610484600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061129c565b604051808381526020018281526020019250505060405180910390f35b34156104ac57600080fd5b6104b46112c0565b005b34156104c157600080fd5b6104c9611341565b604051808215151515815260200191505060405180910390f35b34156104ee57600080fd5b6104f6611354565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561054357600080fd5b61054b61137a565b6040518082815260200191505060405180910390f35b341561056c57600080fd5b610574611385565b005b341561058157600080fd5b6105ad600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506116c3565b6040518082815260200191505060405180910390f35b34156105ce57600080fd5b6105d66116db565b005b34156105e357600080fd5b61060f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611829565b005b341561061c57600080fd5b610648600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506118fe565b005b600080670de0b6b3a7640000341015151561066457600080fd5b61069b610696670de0b6b3a7640000610688610258346119d990919063ffffffff16565b611a0c90919063ffffffff16565b611a27565b9150660221b262dd80006106ba60065484611a7e90919063ffffffff16565b111515156106c757600080fd5b600a60008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84846000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b15156107d557600080fd5b6102c65a03f115156107e657600080fd5b5050506040518051905050610808828260010154611a7e90919063ffffffff16565b8160010181905550610827348260000154611a7e90919063ffffffff16565b816000018190555061084434600554611a7e90919063ffffffff16565b60058190555061085f82600654611a7e90919063ffffffff16565b6006819055503373ffffffffffffffffffffffffffffffffffffffff167ff3c1c7c0eb1328ddc834c4c9e579c06d35f443bf1102b034653624a239c7a40c836040518082815260200191505060405180910390a27fd1dc370699ae69fb860ed754789a4327413ec1cd379b93f2cbedf449a26b0e8583600554604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1505050565b60025481565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600060085442108061096b5750651b48eb57e00060065410155b15151561097757600080fd5b600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154821415156109c757600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166323b872dd3330856000604051602001526040518463ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b1515610ac857600080fd5b6102c65a03f11515610ad957600080fd5b5050506040518051905050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166342966c68836000604051602001526040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b1515610b7d57600080fd5b6102c65a03f11515610b8e57600080fd5b505050604051805190501515610ba357600080fd5b600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000015490506000600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001819055506000811115610c4457610c433382611a9c565b5b5050565b60055481565b60085481565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610cb157600080fd5b600854421015610cd357660221b262dd8000600654141515610cd257600080fd5b5b651b48eb57e000600654108015610cf057506213c6806008540142105b151515610cfc57600080fd5b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f193505050501515610d7557600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231306000604051602001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b1515610e3a57600080fd5b6102c65a03f11515610e4b57600080fd5b5050506040518051905090506000811115610f2057600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166342966c68826000604051602001526040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b1515610ef957600080fd5b6102c65a03f11515610f0a57600080fd5b505050604051805190501515610f1f57600080fd5b5b6001600960006101000a81548160ff02191690831515021790555050565b600080339150600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008114151515610f9657600080fd5b803073ffffffffffffffffffffffffffffffffffffffff163110151515610fbc57600080fd5b610fd181600254611b5090919063ffffffff16565b6002819055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561105957fe5b5050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156110b857600080fd5b6001600060146101000a81548160ff021916908315150217905550565b670de0b6b3a764000081565b600060149054906101000a900460ff1681565b60075481565b651b48eb57e00081565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561115f57600080fd5b600060149054906101000a900460ff16151561117a57600080fd5b60008060146101000a81548160ff021916908315150217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60065481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561121c57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151561129457600080fd5b565b61025881565b600a6020528060005260406000206000915090508060000154908060010154905082565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561131b57600080fd5b600060075414151561132c57600080fd5b4260078190555062278d004201600881905550565b600960009054906101000a900460ff1681565b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b660221b262dd800081565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156113e557600080fd5b600654660221b262dd800003925061142b670de0b6b3a764000061141c610258670de0b6b3a76400006119d990919063ffffffff16565b81151561142557fe5b04611a27565b915081831115151561143c57600080fd5b600a60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16856000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b151561158c57600080fd5b6102c65a03f1151561159d57600080fd5b50505060405180519050506115bf838260010154611a7e90919063ffffffff16565b81600101819055506115dc83600654611a7e90919063ffffffff16565b6006819055503073ffffffffffffffffffffffffffffffffffffffff167ff3c1c7c0eb1328ddc834c4c9e579c06d35f443bf1102b034653624a239c7a40c846040518082815260200191505060405180910390a27fd1dc370699ae69fb860ed754789a4327413ec1cd379b93f2cbedf449a26b0e856000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600554604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1505050565b60016020528060005260406000206000915090505481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561173657600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2fde38b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b151561181357600080fd5b6102c65a03f1151561182457600080fd5b505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561188457600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415156118fb57806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561195957600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415151561199557600080fd5b80600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600080828402905060008414806119fa57508284828115156119f757fe5b04145b1515611a0257fe5b8091505092915050565b6000808284811515611a1a57fe5b0490508091505092915050565b6000611a416202a300600754611a7e90919063ffffffff16565b421015611a7557611a6e611a5f600584611a0c90919063ffffffff16565b83611a7e90919063ffffffff16565b9050611a79565b8190505b919050565b6000808284019050838110151515611a9257fe5b8091505092915050565b611aee81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054611a7e90919063ffffffff16565b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611b4681600254611a7e90919063ffffffff16565b6002819055505050565b6000828211151515611b5e57fe5b8183039050929150505600a165627a7a72305820ec0d82a406896ccf20989b3d6e650abe4dc104e400837f1f58e67ef499493ae90029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000008d69d00910d0b2afb2a99ed6c16c8129fa8e1751", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000e819f024b41358d2c08e3a868a5c5dd0566078d4", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x000000000000000000000000000000000000000000000000000000005a388981", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x000000000000000000000000000000000000000000000000000000005a3b38e6" + } + }, + "0xd4fcab9f0a6dc0493af47c864f6f17a8a5e2e826": { + "balance": "0x2a2dd979a35cf000", + "code": "0x", + "nonce": "0", + "storage": {} + }, + "0xe819f024b41358d2c08e3a868a5c5dd0566078d4": { + "balance": "0x0", + "code": "0x6060604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100bf578063095ea7b31461014d57806318160ddd146101a757806323b872dd146101d0578063313ce5671461024957806342966c681461027257806370a08231146102ad5780638da5cb5b146102fa57806395d89b411461034f578063a9059cbb146103dd578063dd62ed3e14610437578063f2fde38b146104a3575b600080fd5b34156100ca57600080fd5b6100d26104dc565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101125780820151818401526020810190506100f7565b50505050905090810190601f16801561013f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015857600080fd5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610515565b604051808215151515815260200191505060405180910390f35b34156101b257600080fd5b6101ba61069c565b6040518082815260200191505060405180910390f35b34156101db57600080fd5b61022f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506106a2565b604051808215151515815260200191505060405180910390f35b341561025457600080fd5b61025c610952565b6040518082815260200191505060405180910390f35b341561027d57600080fd5b6102936004808035906020019091905050610957565b604051808215151515815260200191505060405180910390f35b34156102b857600080fd5b6102e4600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610abe565b6040518082815260200191505060405180910390f35b341561030557600080fd5b61030d610b07565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561035a57600080fd5b610362610b2d565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103a2578082015181840152602081019050610387565b50505050905090810190601f1680156103cf5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156103e857600080fd5b61041d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610b66565b604051808215151515815260200191505060405180910390f35b341561044257600080fd5b61048d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610d01565b6040518082815260200191505060405180910390f35b34156104ae57600080fd5b6104da600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610d88565b005b6040805190810160405280600b81526020017f416c6c436f6465436f696e00000000000000000000000000000000000000000081525081565b6000808214806105a157506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054145b15156105ac57600080fd5b81600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60005481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905061077683600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e5f90919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061080b83600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506108618382610e7d90919063ffffffff16565b600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a360019150509392505050565b600681565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156109b557600080fd5b610a0782600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610a5f82600054610e7d90919063ffffffff16565b60008190555060003373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050919050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6040805190810160405280600481526020017f414c4c430000000000000000000000000000000000000000000000000000000081525081565b6000610bba82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610c4f82600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e5f90919063ffffffff16565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610de457600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141515610e5c5780600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50565b6000808284019050838110151515610e7357fe5b8091505092915050565b6000828211151515610e8b57fe5b8183039050929150505600a165627a7a7230582059f3ea3df0b054e9ab711f37969684ba83fe38f255ffe2c8d850d951121c51100029", + "nonce": "1", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3956606365", + "extraData": "0x566961425443", + "gasLimit": "5418523", + "hash": "0x6f37eb930a25da673ea1bb80fd9e32ddac19cdf7cd4bb2eac62cc13598624077", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "mixHash": "0x10971cde68c587c750c23b8589ae868ce82c2c646636b97e7d9856470c5297c7", + "nonce": "0x810f923ff4b450a1", + "number": "2295103", + "stateRoot": "0xff403612573d76dfdaf4fea2429b77dbe9764021ae0e38dc8ac79a3cf551179e", + "timestamp": "1513681246", + "totalDifficulty": "7162347056825919" + }, + "input": "0xf86d808504e3b292008307dfa69433056b5dcac09a9b4becad0e1dcf92c19bd0af76880e92596fd62900008029a0e5f27bb66431f7081bb7f1f242003056d7f3f35414c352cd3d1848b52716dac2a07d0be78980edb0bd2a0678fc53aa90ea9558ce346b0d947967216918ac74ccea", + "result": [ + { + "action": { + "callType": "call", + "from": "0xd4fcab9f0a6dc0493af47c864f6f17a8a5e2e826", + "gas": "0x7dfa6", + "input": "0x", + "to": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76", + "value": "0xe92596fd6290000" + }, + "blockNumber": 2295104, + "error": "execution reverted", + "result": { + "gasUsed": "0x7c1c8" + }, + "subtraces": 1, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76", + "gas": "0x75fe3", + "input": "0xa9059cbb000000000000000000000000d4fcab9f0a6dc0493af47c864f6f17a8a5e2e82600000000000000000000000000000000000000000000000000000000000002f4", + "to": "0xe819f024b41358d2c08e3a868a5c5dd0566078d4", + "value": "0x0" + }, + "blockNumber": 0, + "error": "invalid opcode: INVALID", + "result": {}, + "subtraces": 0, + "traceAddress": [0], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create.json new file mode 100644 index 0000000..3fcc61f --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create.json @@ -0,0 +1,94 @@ +{ + "genesis": { + "difficulty": "1808543", + "extraData": "0xd883010906846765746888676f312e31332e35856c696e7578", + "gasLimit": "4875092", + "hash": "0x3851fdc18bd5f2314cf0c90439356f9a1fe157d7fb06c20e20b77954da903671", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "mixHash": "0x3d4e702d6058acf94c9547560f05536d45d515bd4f9014564ec41b5b4ff9578b", + "nonce": "0x1695153e7b16c1e7", + "number": "555461", + "stateRoot": "0xba8272acd0dfeb5f04376328e8bfc5b276b177697000c204a060f6f7b629ae32", + "timestamp": "1577423350", + "totalDifficulty": "462222992438", + "alloc": { + "0xcf5b3467dfa45cdc8e5358a7a1ba4deb02e5faed": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x16c102a3b09c02abdace", + "nonce": "19049", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "555462", + "difficulty": "1808543", + "timestamp": "1577423360", + "gasLimit": "4873701", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf90451824a6985746a52880083053e908080b903fb60606040525b60405161015b806102a0833901809050604051809103906000f0600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b610247806100596000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480632ef9db1314610044578063e37678761461007157610042565b005b61005b6004803590602001803590602001506100ad565b6040518082815260200191505060405180910390f35b61008860048035906020018035906020015061008a565b005b8060006000506000848152602001908152602001600020600050819055505b5050565b6000600060008484604051808381526020018281526020019250505060405180910390209150610120600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff167f6164640000000000000000000000000000000000000000000000000000000000846101e3565b9050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681868660405180807f616464000000000000000000000000000000000000000000000000000000000081526020015060200184815260200183815260200182815260200193505050506000604051808303816000866161da5a03f191505050600060005060008281526020019081526020016000206000505492506101db565b505092915050565b60004340848484604051808581526020018473ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140183815260200182815260200194505050505060405180910390209050610240565b9392505050566060604052610148806100136000396000f30060606040526000357c010000000000000000000000000000000000000000000000000000000090048063471407e614610044578063e37678761461007757610042565b005b6100616004803590602001803590602001803590602001506100b3565b6040518082815260200191505060405180910390f35b61008e600480359060200180359060200150610090565b005b8060006000506000848152602001908152602001600020600050819055505b5050565b6000818301905080506100c684826100d5565b8090506100ce565b9392505050565b3373ffffffffffffffffffffffffffffffffffffffff16828260405180807f7265676973746572496e74000000000000000000000000000000000000000000815260200150602001838152602001828152602001925050506000604051808303816000866161da5a03f1915050505b50505681a1a0b9a85df655d3b6aa081e52d8c3db52c50c2bf97d9d993a980113b2262649c125a00d51e63880ca8ef4705914a71e7ff906834a9cdcff0cbd063ff4e43a5905890d", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x53e90", + "init": "0x60606040525b60405161015b806102a0833901809050604051809103906000f0600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b610247806100596000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480632ef9db1314610044578063e37678761461007157610042565b005b61005b6004803590602001803590602001506100ad565b6040518082815260200191505060405180910390f35b61008860048035906020018035906020015061008a565b005b8060006000506000848152602001908152602001600020600050819055505b5050565b6000600060008484604051808381526020018281526020019250505060405180910390209150610120600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff167f6164640000000000000000000000000000000000000000000000000000000000846101e3565b9050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681868660405180807f616464000000000000000000000000000000000000000000000000000000000081526020015060200184815260200183815260200182815260200193505050506000604051808303816000866161da5a03f191505050600060005060008281526020019081526020016000206000505492506101db565b505092915050565b60004340848484604051808581526020018473ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140183815260200182815260200194505050505060405180910390209050610240565b9392505050566060604052610148806100136000396000f30060606040526000357c010000000000000000000000000000000000000000000000000000000090048063471407e614610044578063e37678761461007757610042565b005b6100616004803590602001803590602001803590602001506100b3565b6040518082815260200191505060405180910390f35b61008e600480359060200180359060200150610090565b005b8060006000506000848152602001908152602001600020600050819055505b5050565b6000818301905080506100c684826100d5565b8090506100ce565b9392505050565b3373ffffffffffffffffffffffffffffffffffffffff16828260405180807f7265676973746572496e74000000000000000000000000000000000000000000815260200150602001838152602001828152602001925050506000604051808303816000866161da5a03f1915050505b505056" + }, + "result": { + "gasUsed": "0x53e90", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900480632ef9db1314610044578063e37678761461007157610042565b005b61005b6004803590602001803590602001506100ad565b6040518082815260200191505060405180910390f35b61008860048035906020018035906020015061008a565b005b8060006000506000848152602001908152602001600020600050819055505b5050565b6000600060008484604051808381526020018281526020019250505060405180910390209150610120600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff167f6164640000000000000000000000000000000000000000000000000000000000846101e3565b9050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681868660405180807f616464000000000000000000000000000000000000000000000000000000000081526020015060200184815260200183815260200182815260200193505050506000604051808303816000866161da5a03f191505050600060005060008281526020019081526020016000206000505492506101db565b505092915050565b60004340848484604051808581526020018473ffffffffffffffffffffffffffffffffffffffff166c0100000000000000000000000002815260140183815260200182815260200194505050505060405180910390209050610240565b939250505056", + "address": "0x9db7a1baf185a865ffee3824946ccd8958191e5e" + }, + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 23, + "transactionHash": "0xe267552ce8437a5bc7081385c99f912de5723ad34b958db215dbc41abd5f6c03", + "blockNumber": 555462, + "blockHash": "0x38bba9e3965b57205097ea5ec53fc403cf3941bec2e4c933faae244de5ca4ba1", + "time": "1.147715ms" + }, + { + "type": "create", + "action": { + "from": "0x9db7a1baf185a865ffee3824946ccd8958191e5e", + "value": "0x0", + "gas": "0x30b34", + "init": "0x6060604052610148806100136000396000f30060606040526000357c010000000000000000000000000000000000000000000000000000000090048063471407e614610044578063e37678761461007757610042565b005b6100616004803590602001803590602001803590602001506100b3565b6040518082815260200191505060405180910390f35b61008e600480359060200180359060200150610090565b005b8060006000506000848152602001908152602001600020600050819055505b5050565b6000818301905080506100c684826100d5565b8090506100ce565b9392505050565b3373ffffffffffffffffffffffffffffffffffffffff16828260405180807f7265676973746572496e74000000000000000000000000000000000000000000815260200150602001838152602001828152602001925050506000604051808303816000866161da5a03f1915050505b505056" + }, + "result": { + "gasUsed": "0x1009d", + "code": "0x60606040526000357c010000000000000000000000000000000000000000000000000000000090048063471407e614610044578063e37678761461007757610042565b005b6100616004803590602001803590602001803590602001506100b3565b6040518082815260200191505060405180910390f35b61008e600480359060200180359060200150610090565b005b8060006000506000848152602001908152602001600020600050819055505b5050565b6000818301905080506100c684826100d5565b8090506100ce565b9392505050565b3373ffffffffffffffffffffffffffffffffffffffff16828260405180807f7265676973746572496e74000000000000000000000000000000000000000000815260200150602001838152602001828152602001925050506000604051808303816000866161da5a03f1915050505b505056", + "address": "0xcf5b3467dfa45cdc8e5358a7a1ba4deb02e5faed" + }, + "traceAddress": [0], + "subtraces": 0, + "transactionPosition": 23, + "transactionHash": "0xe267552ce8437a5bc7081385c99f912de5723ad34b958db215dbc41abd5f6c03", + "blockNumber": 555462, + "blockHash": "0x38bba9e3965b57205097ea5ec53fc403cf3941bec2e4c933faae244de5ca4ba1" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create2_action_gas.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create2_action_gas.json new file mode 100644 index 0000000..0eaa3f8 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create2_action_gas.json @@ -0,0 +1,94 @@ +{ + "genesis": { + "difficulty": "4635413", + "extraData": "0xd683010b05846765746886676f312e3133856c696e7578", + "gasLimit": "9289294", + "hash": "0x359775cf1a2ae2400e26ec68bf33bcfe38b7979c76b7e616f42c4ca7e7605e39", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "mixHash": "0x4b2a0ef121a9c7d732fa0fbd4166a0e1041d2da2b8cb677c61edabf8b7183b64", + "nonce": "0x2a8a64ad9757be55", + "number": "1555160", + "stateRoot": "0x95067c12148e2362fcd4a89df286ff0b1739ef097a40ca42ae7f698af9a9d913", + "timestamp": "1590793999", + "totalDifficulty": "2242063623471", + "alloc": { + "0x8785e369f0ef0a4e5c5a5f929680427dc75273a5": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x623145b285b3f551fa3f", + "nonce": "260617", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "1555161", + "difficulty": "4633150", + "timestamp": "1590794020", + "gasLimit": "9298364", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf85e8303fa09843b9aca0083019ed880808a6000600060006000f50081a2a0485ea410e210740eef8e6f6de11c530f46f8da80eecb02afbb6c5f61749ac015a068d72f1b0f1d3cb4e214d5def79b49a73e6ee91db2df83499a54c656c144600f", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x19ed8", + "init": "0x6000600060006000f500" + }, + "result": { + "gasUsed": "0x14c78", + "code": "0x", + "address": "0x2e8eded627eead210cb6143eb39ef7a3e44e4f00" + }, + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 31, + "transactionHash": "0x1257b698c5833c54ce786734087002b097275abc3877af082b5c2a538e894a41", + "blockNumber": 1555161, + "blockHash": "0xb0793dd508dd106a19794b8ce1dfc0ff8d98c76aab61bf32a11799854149a171", + "time": "889.048µs" + }, + { + "type": "create", + "action": { + "from": "0x2e8eded627eead210cb6143eb39ef7a3e44e4f00", + "value": "0x0", + "gas": "0x5117", + "init": "0x" + }, + "result": { + "gasUsed": "0x0", + "code": "0x", + "address": "0x8785e369f0ef0a4e5c5a5f929680427dc75273a5" + }, + "traceAddress": [0], + "subtraces": 0, + "transactionPosition": 31, + "transactionHash": "0x1257b698c5833c54ce786734087002b097275abc3877af082b5c2a538e894a41", + "blockNumber": 1555161, + "blockHash": "0xb0793dd508dd106a19794b8ce1dfc0ff8d98c76aab61bf32a11799854149a171" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_action_gas.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_action_gas.json new file mode 100644 index 0000000..a00ea7a --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_action_gas.json @@ -0,0 +1,90 @@ +{ + "genesis": { + "difficulty": "4639933", + "extraData": "0xd883010b05846765746888676f312e31342e33856c696e7578", + "gasLimit": "9280188", + "hash": "0x9a5f3a98eb1c60f6e3f450658a9cea190157e7021d04f927b752ad6482cf9194", + "miner": "0x73f26d124436b0791169d63a3af29c2ae47765a3", + "mixHash": "0x6b6f8fcaa54b8565c4c1ae7cf0a020e938a53007f4561e758b17bc05c9044d78", + "nonce": "0x773aba50dc51b462", + "number": "1555169", + "stateRoot": "0xc4b9703de3e59ff795baae2c3afa010cf039c37244a7a6af7f3f491a10601348", + "timestamp": "1590794111", + "totalDifficulty": "2242105342155", + "alloc": { + "0x5ac5599fc9df172c89ee7ec55ad9104ccbfed40d": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x62325b40cbbd0915c4b9", + "nonce": "260875", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "1555170", + "difficulty": "4642198", + "timestamp": "1590794112", + "gasLimit": "9289249", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf8658303fb0b843b9aca0083019ee48080915a600055600060006000f0505a6001550081a2a01a7deb3a16d967b766459ef486b00656c6581e5ad58968184a33701e27e0eb8aa07162ccdfe2018d64360a605310a62c399dd586c7282dd42a88c54f02f51d451f", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x19ee4", + "init": "0x5a600055600060006000f0505a60015500" + }, + "error": "out of gas: not enough gas for reentrancy sentry", + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 63, + "transactionHash": "0x60e881fae3884657b5430925c5d0053535b45cce0b8188f2a6be1feee8bcc650", + "blockNumber": 1555170, + "blockHash": "0xea46fbf941d51bf1e4180fbf26d22fda3896f49c7f371d109c226de95dd7b02e", + "time": "952.736µs" + }, + { + "type": "create", + "action": { + "from": "0x9c5cfe45b15eaff4ad617af4250189e26024a4f8", + "value": "0x0", + "gas": "0x3cb", + "init": "0x" + }, + "result": { + "gasUsed": "0x0", + "code": "0x", + "address": "0x5ac5599fc9df172c89ee7ec55ad9104ccbfed40d" + }, + "traceAddress": [0], + "subtraces": 0, + "transactionPosition": 63, + "transactionHash": "0x60e881fae3884657b5430925c5d0053535b45cce0b8188f2a6be1feee8bcc650", + "blockNumber": 1555170, + "blockHash": "0xea46fbf941d51bf1e4180fbf26d22fda3896f49c7f371d109c226de95dd7b02e" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_inerror.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_inerror.json new file mode 100644 index 0000000..f3a7d9a --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_create_inerror.json @@ -0,0 +1,94 @@ +{ + "genesis": { + "difficulty": "3244991", + "extraData": "0x", + "gasLimit": "7968787", + "hash": "0x62bbf18c203068a8793af8d8360d054f95a63bc62b87ade550861ed490af3f15", + "miner": "0x9f2659ffe7b3b467e46dcec3623392cf51635079", + "mixHash": "0xc8dec711fd1e03972b6a279a09dc0cd29c5171b60f42c4ce37c7c51ff445f776", + "nonce": "0x40b1bbcc25ddb804", + "number": "839246", + "stateRoot": "0x4bb3b02ec70b837651233957fb61a6ea3fc6a4244c1f55df7a713c154829ec0a", + "timestamp": "1581179375", + "totalDifficulty": "1023985623933", + "alloc": { + "0x76554b33410b6d90b7dc889bfed0451ad195f27e": { + "balance": "0x0", + "nonce": "1", + "code": "0x6080604052348015600f57600080fd5b506004361060505760003560e01c8063391521f414605557806355313dea14605d5780636d3d14161460655780638da5cb5b14606d578063b9d1e5aa1460b5575b600080fd5b605b60bd565b005b606360c8565b005b606b60ca565b005b607360cf565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60bb60f4565b005b6020610123600af050565b005b600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565bfefea165627a7a723058202094d5aa5dbbd493e9a2c64c50b62eba4b109b2a12d2bb73a5d0d54982651fc80029", + "storage": {} + }, + "0xed69ab7145a9bae7152406d062c077c6ecc6ae18": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0xa3b31cbd5168d3c99756660d4b7625d679e12573": { + "balance": "0x569bc6535d3083fce", + "nonce": "26", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "839247", + "difficulty": "3213311", + "timestamp": "1581179571", + "gasLimit": "7961006", + "miner": "0x9f2659ffe7b3b467e46dcec3623392cf51635079" + }, + "input": "0xf86a1a8509502f9000830334509476554b33410b6d90b7dc889bfed0451ad195f27e8084391521f481a2a02e4ff0d171a860c8c7de2283978e2f225f9ba3ed4dec446b773c6b2d73ef22dea02a6a517528b491cb71b204f534db11a1c8059035f54d5bae347d1cab536bde2c", + "result": [ + { + "type": "call", + "action": { + "from": "0xa3b31cbd5168d3c99756660d4b7625d679e12573", + "to": "0x76554b33410b6d90b7dc889bfed0451ad195f27e", + "value": "0x0", + "gas": "0x33450", + "input": "0x391521f4", + "callType": "call" + }, + "result": { + "gasUsed": "0xd0b5", + "output": "0x" + }, + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 26, + "transactionHash": "0xcb1090fa85d2a3da8326b75333e92b3dca89963c895d9c981bfdaa64643135e4", + "blockNumber": 839247, + "blockHash": "0xce7ff7d84ca97f0f89d6065e2c12409a795c9f607cdb14aef0713cad5d7e311c", + "time": "182.267µs" + }, + { + "action": { + "from": "0x76554b33410b6d90b7dc889bfed0451ad195f27e", + "gas": "0x25a18", + "init": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0xa" + }, + "error": "insufficient balance for transfer", + "result": {}, + "subtraces": 0, + "traceAddress": [0], + "type": "create" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_pointer_issue.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_pointer_issue.json new file mode 100644 index 0000000..c3191d6 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/nested_pointer_issue.json @@ -0,0 +1,189 @@ +{ + "genesis": { + "number": "13535", + "hash": "0x6f706fe8026edb51577b57685574dc152dba4e2ebfc8a50bb63a8c95a4f8818d", + "nonce": "0x0000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "stateRoot": "0x7f54db248a004ca182fe87fdfa6efda97163908b4f0cc84b36a6d60699d5d1be", + "miner": "0x0000000000000000000000000000000000000000", + "difficulty": "1", + "totalDifficulty": "24766", + "extraData": "0xf09f928e20407072796c616273206e6f64652d3020f09f928e000000000000001d32ac3baf238e163e18ed6d77b67b0b54b08ad9781dc4ffd93c5ede1ca12c5f21b36ac39c7ebb88dff65da91f5b9461f19873a02602230b931ba388a809119f00", + "gasLimit": "8000000", + "timestamp": "1549153003", + "alloc": { + "0x0b1ba0af832d7c05fd64161e0db78e85978e8082": { + "balance": "0x0", + "nonce": "1", + "code": "0x6080604052600436106100ae5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde0381146100b8578063095ea7b31461014257806318160ddd1461018757806323b872dd146101ae5780632e1a7d4d146101e5578063313ce567146101fd57806370a082311461022857806395d89b4114610256578063a9059cbb1461026b578063d0e30db0146100ae578063dd62ed3e1461029c575b6100b66102d0565b005b3480156100c457600080fd5b506100cd61031f565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101075781810151838201526020016100ef565b50505050905090810190601f1680156101345780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014e57600080fd5b5061017373ffffffffffffffffffffffffffffffffffffffff600435166024356103cb565b604080519115158252519081900360200190f35b34801561019357600080fd5b5061019c61043e565b60408051918252519081900360200190f35b3480156101ba57600080fd5b5061017373ffffffffffffffffffffffffffffffffffffffff60043581169060243516604435610443565b3480156101f157600080fd5b506100b66004356105e3565b34801561020957600080fd5b50610212610678565b6040805160ff9092168252519081900360200190f35b34801561023457600080fd5b5061019c73ffffffffffffffffffffffffffffffffffffffff60043516610681565b34801561026257600080fd5b506100cd610693565b34801561027757600080fd5b5061017373ffffffffffffffffffffffffffffffffffffffff6004351660243561070b565b3480156102a857600080fd5b5061019c73ffffffffffffffffffffffffffffffffffffffff6004358116906024351661071f565b33600081815260036020908152604091829020805434908101909155825190815291517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c9281900390910190a2565b6000805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156103c35780601f10610398576101008083540402835291602001916103c3565b820191906000526020600020905b8154815290600101906020018083116103a657829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b303190565b73ffffffffffffffffffffffffffffffffffffffff831660009081526003602052604081205482111561047557600080fd5b73ffffffffffffffffffffffffffffffffffffffff841633148015906104eb575073ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14155b156105655773ffffffffffffffffffffffffffffffffffffffff8416600090815260046020908152604080832033845290915290205482111561052d57600080fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081526004602090815260408083203384529091529020805483900390555b73ffffffffffffffffffffffffffffffffffffffff808516600081815260036020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060019392505050565b336000908152600360205260409020548111156105ff57600080fd5b33600081815260036020526040808220805485900390555183156108fc0291849190818181858888f1935050505015801561063e573d6000803e3d6000fd5b5060408051828152905133917f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65919081900360200190a250565b60025460ff1681565b60036020526000908152604090205481565b60018054604080516020600284861615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156103c35780601f10610398576101008083540402835291602001916103c3565b6000610718338484610443565b9392505050565b6004602090815260009283526040808420909152908252902054815600a165627a7a72305820228981f11f47ad9630080069b0a81423fcfba5aa8e0f478a579c4bc080ba7e820029", + "storage": { + "0xbe8a6e3827dad84a671edac41a02b0f5b47b9d0339adb1e9411b9ba4e2118738": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x48bacb9266a570d521063ef5dd96e61686dbe788": { + "balance": "0x0", + "nonce": "1", + "code": "0x6080604052600436106101b65763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663288cdc9181146101bb578063297bb70b146101f15780632ac126221461021e5780633683ef8e1461024b5780633c28d8611461026d5780633e228bae1461029a5780633fd3c997146102ba5780634ac14782146102e75780634d0ae546146103075780634f9559b11461032757806350dde190146103475780636070410814610367578063642f2eaf1461039457806364a3bc15146103b457806377fcce68146103d45780637b8e3514146103f45780637e1d9808146104145780637e9d74dc1461043457806382c174d0146104615780638da5cb5b146104815780639363470214610496578063a3e20380146104b6578063b4be83d5146104d6578063bfc8bfce146104f6578063c585bb9314610516578063c75e0a8114610536578063d46b02c314610563578063d9bfa73e14610583578063db123b1a146105a3578063dd1c7d18146105c5578063e306f779146105e5578063e5fa431b146105fa578063eea086ba1461061a578063f2fde38b1461062f578063ffa1ad741461064f575b600080fd5b3480156101c757600080fd5b506101db6101d63660046148ee565b610664565b6040516101e89190615513565b60405180910390f35b3480156101fd57600080fd5b5061021161020c366004614811565b610676565b6040516101e891906157ed565b34801561022a57600080fd5b5061023e6102393660046148ee565b6107a1565b6040516101e89190615505565b34801561025757600080fd5b5061026b61026636600461492b565b6107b6565b005b34801561027957600080fd5b5061028d610288366004614a5f565b6108a3565b6040516101e891906157fb565b3480156102a657600080fd5b506102116102b5366004614b1f565b610a3a565b3480156102c657600080fd5b506102da6102d53660046149ee565b610a90565b6040516101e891906155cf565b3480156102f357600080fd5b5061026b6103023660046147dc565b610ab8565b34801561031357600080fd5b50610211610322366004614811565b610b85565b34801561033357600080fd5b5061026b6103423660046148ee565b610c75565b34801561035357600080fd5b50610211610362366004614811565b610e2a565b34801561037357600080fd5b506103876103823660046149ee565b610ebe565b6040516101e89190615425565b3480156103a057600080fd5b5061023e6103af3660046148ee565b610f0c565b3480156103c057600080fd5b506102116103cf366004614b1f565b610f21565b3480156103e057600080fd5b5061026b6103ef3660046147ac565b610fcc565b34801561040057600080fd5b5061023e61040f366004614772565b611106565b34801561042057600080fd5b5061021161042f3660046148a5565b611126565b34801561044057600080fd5b5061045461044f3660046147dc565b61128a565b6040516101e891906154f4565b34801561046d57600080fd5b5061023e61047c36600461490c565b61131f565b34801561048d57600080fd5b5061038761133f565b3480156104a257600080fd5b5061023e6104b1366004614993565b61135b565b3480156104c257600080fd5b506102116104d13660046148a5565b6118de565b3480156104e257600080fd5b506102116104f1366004614b1f565b6119f1565b34801561050257600080fd5b5061026b610511366004614b68565b611a6c565b34801561052257600080fd5b5061026b610531366004614754565b611d05565b34801561054257600080fd5b50610556610551366004614a2a565b611f30565b6040516101e8919061580a565b34801561056f57600080fd5b5061026b61057e366004614a2a565b61202a565b34801561058f57600080fd5b506101db61059e366004614772565b6120c6565b3480156105af57600080fd5b506105b86120e3565b6040516101e891906155be565b3480156105d157600080fd5b506102116105e03660046148a5565b61218e565b3480156105f157600080fd5b506101db612263565b34801561060657600080fd5b506102116106153660046148a5565b612269565b34801561062657600080fd5b506103876123db565b34801561063b57600080fd5b5061026b61064a366004614754565b6123f7565b34801561065b57600080fd5b506105b86124a8565b60046020526000908152604090205481565b61067e614386565b600080610689614386565b60005460ff16156106cf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b60405180910390fd5b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011781558751935091505b81831461076f57610758878381518110151561071957fe5b90602001906020020151878481518110151561073157fe5b90602001906020020151878581518110151561074957fe5b906020019060200201516124df565b9050610764848261257d565b600190910190610701565b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055509392505050565b60056020526000908152604090205460ff1681565b73ffffffffffffffffffffffffffffffffffffffff831633146108465761080e848484848080601f0160208091040260200160405190810160405280939291908181526020018383808284375061135b945050505050565b1515610846576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061569d565b5050600091825260076020908152604080842073ffffffffffffffffffffffffffffffffffffffff9093168452919052902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6108ab6143af565b6108b36143de565b6108bb6143de565b6000805460ff16156108f9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905561016080890151610140808a01919091528901519088015261094588611f30565b925061095087611f30565b915061095a6125df565b905061096888848389612611565b61097487838388612611565b61097e88886127a9565b610992888885604001518560400151612809565b8051602081015190519195506109ad918a9186918190612990565b6020808501519081015190516109c99189918591908190612990565b6109e28882856020015186604001518860000151612aa9565b6109fb8782846020015185604001518860200151612aa9565b610a0788888387612b55565b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905550949350505050565b610a42614386565b6060610a4f858585612d2d565b9050608081825160208401305af48015610a8657815183526020820151602084015260408201516040840152606082015160608401525b505b509392505050565b600b6020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b60008054819060ff1615610af8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011781558151905b808214610b5857610b508382815181101515610b4157fe5b90602001906020020151612eff565b600101610b29565b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905550565b610b8d614386565b600080610b98614386565b60005460ff1615610bd5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011781558751935091505b81831461076f57610c5e8783815181101515610c1f57fe5b906020019060200201518784815181101515610c3757fe5b906020019060200201518785815181101515610c4f57fe5b90602001906020020151612f2a565b9050610c6a848261257d565b600190910190610c07565b6000805481908190819060ff1615610cb9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055610cec6125df565b935073ffffffffffffffffffffffffffffffffffffffff84163314610d115733610d14565b60005b73ffffffffffffffffffffffffffffffffffffffff8086166000908152600660209081526040808320938516835292905220549093506001860192509050808211610d8b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061572d565b73ffffffffffffffffffffffffffffffffffffffff80851660008181526006602090815260408083209488168084529490915290819020859055517f82af639571738f4ebd4268fb0363d8957ebe1bbb9e78dba5ebd69eed39b154f090610df3908690615513565b60405180910390a35050600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055505050565b610e32614386565b600080610e3d614386565b86519250600091505b818314610eb457610e9d8783815181101515610e5e57fe5b906020019060200201518784815181101515610e7657fe5b906020019060200201518785815181101515610e8e57fe5b90602001906020020151610a3a565b9050610ea9848261257d565b600190910190610e46565b5050509392505050565b7fffffffff0000000000000000000000000000000000000000000000000000000081166000908152600b602052604090205473ffffffffffffffffffffffffffffffffffffffff165b919050565b60096020526000908152604090205460ff1681565b610f29614386565b60005460ff1615610f66576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055610f9c848484612f2a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055949350505050565b6000805460ff161561100a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905561103d6125df565b73ffffffffffffffffffffffffffffffffffffffff8181166000818152600860209081526040808320948916808452949091529081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168715151790555192935090917fa8656e308026eeabce8f0bc18048433252318ab80ac79da0b3d3d8697dfba891906110d1908690615505565b60405180910390a35050600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905550565b600860209081526000928352604080842090915290825290205460ff1681565b61112e614386565b6060600080600061113d614386565b60005460ff161561117a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117815589518a919081106111b257fe5b906020019060200201516101600151945088519350600092505b828414611255578489848151811015156111e257fe5b906020019060200201516101600181905250611202888760200151612f7d565b915061122e898481518110151561121557fe5b9060200190602002015183898681518110151561074957fe5b905061123a868261257d565b6020860151881161124a57611255565b6001909201916111cc565b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055509195945050505050565b606060006060600084519250826040519080825280602002602001820160405280156112d057816020015b6112bd6143de565b8152602001906001900390816112b55790505b509150600090505b808314610a88576112ff85828151811015156112f057fe5b90602001906020020151611f30565b828281518110151561130d57fe5b602090810290910101526001016112d8565b600760209081526000928352604080842090915290825290205460ff1681565b60035473ffffffffffffffffffffffffffffffffffffffff1681565b600080600080600080600080600089511115156113a4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061571d565b6113ad89612fc4565b7f010000000000000000000000000000000000000000000000000000000000000090049650600760ff88161061140f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061563d565b8660ff16600781111561141e57fe5b9550600086600781111561142e57fe5b1415611466576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061570d565b600186600781111561147457fe5b14156114bc578851156114b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906157dd565b600097506118d0565b60028660078111156114ca57fe5b141561160557885160411461150b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906155dd565b88600081518110151561151a57fe5b01602001517f010000000000000000000000000000000000000000000000000000000000000090819004810204945061155a89600163ffffffff61308816565b935061156d89602163ffffffff61308816565b925060018b86868660405160008152602001604052604051611592949392919061556e565b60206040516020810390808403906000865af11580156115b6573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015173ffffffffffffffffffffffffffffffffffffffff8c811690821614995092506118d09050565b600386600781111561161357fe5b14156117b9578851604114611654576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906155dd565b88600081518110151561166357fe5b01602001517f01000000000000000000000000000000000000000000000000000000000000009081900481020494506116a389600163ffffffff61308816565b93506116b689602163ffffffff61308816565b925060018b60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c0182600019166000191681526020019150506040516020818303038152906040526040518082805190602001908083835b6020831061175757805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161171a565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040805192909401829003822060008352910192839052611592945092508991899150889061556e565b60048660078111156117c757fe5b14156117df576117d88b8b8b6130d3565b97506118d0565b60058660078111156117ed57fe5b1415611850576117fc89613228565b73ffffffffffffffffffffffffffffffffffffffff808c1660009081526008602090815260408083209385168352929052205490915060ff16151561184457600097506118d0565b6117d8818c8c8c6132a1565b600686600781111561185e57fe5b141561189e5760008b815260076020908152604080832073ffffffffffffffffffffffffffffffffffffffff8e16845290915290205460ff1697506118d0565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061563d565b505050505050509392505050565b6118e6614386565b60606000806000806118f6614386565b89600081518110151561190557fe5b906020019060200201516101400151955089519450600093505b8385146119e457858a8581518110151561193557fe5b6020908102909101015161014001528651611951908a90612f7d565b92506119948a8581518110151561196457fe5b9060200190602002015160a001518b8681518110151561198057fe5b9060200190602002015160800151856133fd565b91506119c08a858151811015156119a757fe5b90602001906020020151838a87815181101515610e8e57fe5b90506119cc878261257d565b865189116119d9576119e4565b60019093019261191f565b5050505050509392505050565b6119f9614386565b60005460ff1615611a36576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055610f9c8484846124df565b600a5460009073ffffffffffffffffffffffffffffffffffffffff1615611abf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b611b02611afd888888888080601f01602080910402602001604051908101604052809392919081815260200183838082843750613453945050505050565b613694565b60008181526009602052604090205490915060ff1615611b4e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061568d565b73ffffffffffffffffffffffffffffffffffffffff86163314611c1f57611ba6818785858080601f0160208091040260200160405190810160405280939291908181526020018383808284375061135b945050505050565b1515611bde576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906157cd565b600a80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88161790555b6000818152600960205260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555130908690869080838380828437820191505092505050600060405180830381855af49150501515611cb6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906156bd565b73ffffffffffffffffffffffffffffffffffffffff86163314611cfc57600a80547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555b50505050505050565b6003546000908190819073ffffffffffffffffffffffffffffffffffffffff163314611d5d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061577d565b8392508273ffffffffffffffffffffffffffffffffffffffff1663ae25532e6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b158015611dc457600080fd5b505af1158015611dd8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250611dfc9190810190614a0c565b7fffffffff0000000000000000000000000000000000000000000000000000000081166000908152600b602052604090205490925073ffffffffffffffffffffffffffffffffffffffff1690508015611e81576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061561d565b7fffffffff0000000000000000000000000000000000000000000000000000000082166000908152600b60205260409081902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8616179055517fd2c6b762299c609bdb96520b58a49bfb80186934d4f71a86a367571a15c0319490611f2290849087906155a3565b60405180910390a150505050565b611f386143de565b611f41826136d1565b6020808301829052600091825260049052604090819020549082015260808201511515611f755760015b60ff168152610f07565b60a08201511515611f87576002611f6b565b60a0820151604082015110611f9d576005611f6b565b6101008201514210611fb0576004611f6b565b60208082015160009081526005909152604090205460ff1615611fd4576006611f6b565b610120820151825173ffffffffffffffffffffffffffffffffffffffff90811660009081526006602090815260408083206060880151909416835292905220541115612021576006611f6b565b60038152919050565b60005460ff1615612067576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905561209b81612eff565b50600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055565b600660209081526000928352604080842090915290825290205481565b60018054604080516020600284861615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156121865780601f1061215b57610100808354040283529160200191612186565b820191906000526020600020905b81548152906001019060200180831161216957829003601f168201915b505050505081565b612196614386565b606060008060006121a5614386565b8860008151811015156121b457fe5b906020019060200201516101600151945088519350600092505b828414612257578489848151811015156121e457fe5b906020019060200201516101600181905250612204888760200151612f7d565b9150612230898481518110151561221757fe5b90602001906020020151838986815181101515610e8e57fe5b905061223c868261257d565b6020860151881161224c57612257565b6001909201916121ce565b50505050509392505050565b60025481565b612271614386565b6060600080600080612281614386565b60005460ff16156122be576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061576d565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011781558a518b919081106122f657fe5b906020019060200201516101400151955089519450600093505b8385146123a557858a8581518110151561232657fe5b6020908102909101015161014001528651612342908a90612f7d565b92506123558a8581518110151561196457fe5b91506123818a8581518110151561236857fe5b90602001906020020151838a8781518110151561074957fe5b905061238d878261257d565b8651891161239a576123a5565b600190930192612310565b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905550929695505050505050565b600a5473ffffffffffffffffffffffffffffffffffffffff1681565b60035473ffffffffffffffffffffffffffffffffffffffff163314612448576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061577d565b73ffffffffffffffffffffffffffffffffffffffff8116156124a557600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b60408051808201909152600581527f322e302e30000000000000000000000000000000000000000000000000000000602082015281565b6124e7614386565b6124ef6143de565b60008060006124fd88611f30565b93506125076125df565b925061251588858589612611565b6125278860a001518560400151612f7d565b915061253387836136df565b9050612546888589848960000151612990565b61255088826136f5565b945061256788848660200151876040015189612aa9565b612572888487613756565b505050509392505050565b8151815161258b9190613864565b8252602080830151908201516125a19190613864565b6020830152604080830151908201516125ba9190613864565b6040830152606080830151908201516125d39190613864565b60609092019190915250565b600a5460009073ffffffffffffffffffffffffffffffffffffffff16818115612608578161260a565b335b9392505050565b825160ff1660031461264f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061579d565b606084015173ffffffffffffffffffffffffffffffffffffffff16156126c257606084015173ffffffffffffffffffffffffffffffffffffffff1633146126c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906157ad565b602084015173ffffffffffffffffffffffffffffffffffffffff161561274d578173ffffffffffffffffffffffffffffffffffffffff16846020015173ffffffffffffffffffffffffffffffffffffffff1614151561274d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906155ed565b604083015115156127a35761276b836020015185600001518361135b565b15156127a3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061565d565b50505050565b6127bb8260a001518260a001516138ae565b6127cd836080015183608001516138ae565b1015612805576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906157bd565b5050565b6128116143af565b6000806000806128258960a0015188612f7d565b935061283a89608001518a60a0015186613909565b925061284a8860a0015187612f7d565b915061285f88608001518960a0015184613909565b90508084106128a25760208086018051839052805182018490525151865182015260808a015160a08b015187519092015161289a9290613909565b8551526128df565b845183905284516020908101859052855181015190860180519190915260a089015160808a01519151516128d69290613986565b60208087015101525b84515160208087015101516128f49190612f7d565b604086015284515160808a015160c08b0151612911929190613909565b85516040015284516020015160a08a015160e08b0151612932929190613909565b855160600152602085015151608089015160c08a0151612953929190613909565b8560200151604001818152505061297b8560200151602001518960a001518a60e00151613909565b60208601516060015250505050949350505050565b8215156129c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906156dd565b82821115612a03576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906156cd565b8460a00151612a16856040015184613864565b1115612a4e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906155fd565b612a5c8560800151836138ae565b612a6a828760a001516138ae565b1115612aa2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061575d565b5050505050565b612ab7828260200151613864565b600084815260046020908152604091829020929092558681015187518451938501518584015160608701516101408c01516101608d015196518b9873ffffffffffffffffffffffffffffffffffffffff9788169897909616967f0bcc4c97732e47d9946f229edb95f5b6323f601300e4690de719993f3c37112996612b46968f96339692959194909390615433565b60405180910390a45050505050565b60018054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101008789161502019095169490940493840181900481028201810190925282815260609390929091830182828015612bfe5780601f10612bd357610100808354040283529160200191612bfe565b820191906000526020600020905b815481529060010190602001808311612be157829003601f168201915b50505050509050612c2685610140015186600001518660000151856020015160200151613a23565b61014084015184518651845160200151612c4293929190613a23565b612c5b8561014001518660000151858560400151613a23565b612c778186600001518760400151856000015160400151613a23565b612c938185600001518660400151856020015160400151613a23565b836040015173ffffffffffffffffffffffffffffffffffffffff16856040015173ffffffffffffffffffffffffffffffffffffffff161415612cfd57612cf881848760400151612cf3866000015160600151876020015160600151613864565b613a23565b612aa2565b612d1581848760400151856000015160600151613a23565b612aa281848660400151856020015160600151613a23565b604080517fb4be83d5000000000000000000000000000000000000000000000000000000006020808301919091526060602483018181528751608485019081528884015160a48601529488015160c48501529087015160e4840152608087015161010484015260a087015161012484015260c087015161014484015260e08701516101648401526101008701516101848401526101208701516101a4840152610140870180516101c485019081526101608901516101e4860152610180905251805161020485018190529394919384936044870192849261022489019291820191601f82010460005b81811015612e34578351855260209485019490930192600101612e16565b50505050818103610160808401919091528a0151805180835260209283019291820191601f82010460005b81811015612e7d578351855260209485019490930192600101612e5f565b50505089845250848103602093840190815288518083529093918201918981019190601f82010460005b81811015612ec5578351855260209485019490930192600101612ea7565b5050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08883030188525060405250505050509392505050565b612f076143de565b612f1082611f30565b9050612f1c8282613bed565b612805828260200151613d04565b612f32614386565b612f3d8484846124df565b6020810151909150831461260a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061574d565b600082821115612fb9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061560d565b508082035b92915050565b6000808251111515613002576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906156fd565b815182907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190811061303257fe5b016020015182517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01909252507f0100000000000000000000000000000000000000000000000000000000000000908190040290565b6000816020018351101515156130ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061562d565b50016020015190565b6040516000906060907f1626ba7e000000000000000000000000000000000000000000000000000000009061310e908790869060240161554e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152919052602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909416939093178352815191935090829081885afa8080156131ab576001811461321c57612572565b7f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0c57414c4c45545f4552524f5200000000000000000000000000000000604052600060605260646000fd5b50505195945050505050565b60006014825110151515613268576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061578d565b613276826014845103613dab565b82517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec019092525090565b6040516000906060907f9363470200000000000000000000000000000000000000000000000000000000906132de90879087908790602401615521565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152919052602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931783528151919350908290818a5afa80801561337b57600181146133ec576133f1565b7f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0f56414c494441544f525f4552524f5200000000000000000000000000604052600060605260646000fd5b825194505b50505050949350505050565b6000808311613438576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061564d565b61344b61344585846138ae565b84613e0c565b949350505050565b604080517f5a65726f45785472616e73616374696f6e2800000000000000000000000000006020808301919091527f75696e743235362073616c742c0000000000000000000000000000000000000060328301527f61646472657373207369676e6572416464726573732c00000000000000000000603f8301527f627974657320646174610000000000000000000000000000000000000000000060558301527f2900000000000000000000000000000000000000000000000000000000000000605f830152825180830384018152606090920192839052815160009384938493909282918401908083835b6020831061357c57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161353f565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260405191909301819003812089519097508995509093508392850191508083835b6020831061361257805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016135d5565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040805192909401829003822097825281019a909a525073ffffffffffffffffffffffffffffffffffffffff97909716968801969096525050606085015250506080909120919050565b600280546040517f190100000000000000000000000000000000000000000000000000000000000081529182015260228101919091526042902090565b6000612fbe611afd83613e23565b60008183106136ee578161260a565b5090919050565b6136fd614386565b6020810182905260a08301516080840151613719918491613909565b808252608084015160c0850151613731929190613909565b604082015260a083015160e084015161374b918491613909565b606082015292915050565b60018054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61010087891615020190951694909404938401819004810282018101909252828152606093909290918301828280156137ff5780601f106137d4576101008083540402835291602001916137ff565b820191906000526020600020905b8154815290600101906020018083116137e257829003601f168201915b5050505050905061381f8461014001518560000151858560000151613a23565b6138388461016001518486600001518560200151613a23565b61385081856000015186604001518560400151613a23565b6127a3818486604001518560600151613a23565b6000828201838110156138a3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061567d565b8091505b5092915050565b6000808315156138c157600091506138a7565b508282028284828115156138d157fe5b04146138a3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061567d565b6000808311613944576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061564d565b61394f84848461427c565b15613438576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906156ad565b60008083116139c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061564d565b6139cc848484614301565b15613a03576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906156ad565b61344b613445613a1386856138ae565b613a1e866001612f7d565b613864565b600080600083118015613a6257508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b15613be5578551600310613aa2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061573d565b50506020848101517fffffffff00000000000000000000000000000000000000000000000000000000166000818152600b90925260409091205473ffffffffffffffffffffffffffffffffffffffff16801515613b2b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906156ed565b604051660fffffffffffe0603f885101168060840182017fa85e59e40000000000000000000000000000000000000000000000000000000083526080600484015273ffffffffffffffffffffffffffffffffffffffff8816602484015273ffffffffffffffffffffffffffffffffffffffff87166044840152856064840152608483015b81811015613bc757895181526020998a019901613baf565b61020084858403866000895af1801515613bdf573d85fd5b50505050505b505050505050565b805160009060ff16600314613c2e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061579d565b606083015173ffffffffffffffffffffffffffffffffffffffff1615613ca157606083015173ffffffffffffffffffffffffffffffffffffffff163314613ca1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c6906157ad565b613ca96125df565b835190915073ffffffffffffffffffffffffffffffffffffffff808316911614613cff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061566d565b505050565b6000818152600560205260409081902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558281015183516101408501516101608601519351859473ffffffffffffffffffffffffffffffffffffffff9485169493909316927fdc47b3613d9fe400085f6dbdc99453462279057e6207385042827ed6b1a62cf792613d9f923392906154b7565b60405180910390a45050565b600081601401835110151515613ded576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061578d565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b6000808284811515613e1a57fe5b04949350505050565b604080517f4f726465722800000000000000000000000000000000000000000000000000006020808301919091527f61646472657373206d616b6572416464726573732c000000000000000000000060268301527f616464726573732074616b6572416464726573732c0000000000000000000000603b8301527f6164647265737320666565526563697069656e74416464726573732c0000000060508301527f616464726573732073656e646572416464726573732c00000000000000000000606c8301527f75696e74323536206d616b65724173736574416d6f756e742c0000000000000060828301527f75696e743235362074616b65724173736574416d6f756e742c00000000000000609b8301527f75696e74323536206d616b65724665652c00000000000000000000000000000060b48301527f75696e743235362074616b65724665652c00000000000000000000000000000060c58301527f75696e743235362065787069726174696f6e54696d655365636f6e64732c000060d68301527f75696e743235362073616c742c0000000000000000000000000000000000000060f48301527f6279746573206d616b65724173736574446174612c00000000000000000000006101018301527f62797465732074616b65724173736574446174610000000000000000000000006101168301527f290000000000000000000000000000000000000000000000000000000000000061012a830152825161010b81840301815261012b90920192839052815160009384938493849391929182918401908083835b602083106140ab57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161406e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930181900381206101408b0151805191995095509093508392850191508083835b6020831061414657805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101614109565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930181900381206101608b0151805191985095509093508392850191508083835b602083106141e157805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016141a4565b5181516020939093036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909116921691909117905260405192018290039091207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0890180516101408b018051610160909c0180519a84529881529288526101a0822091529890525050509190525090919050565b6000808084116142b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061564d565b8215806142c3575084155b156142d15760009150610a88565b838015156142db57fe5b85840990506142ea85846138ae565b6142f66103e8836138ae565b101595945050505050565b60008080841161433d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c69061564d565b821580614348575084155b156143565760009150610a88565b8380151561436057fe5b8584099050836143708583612f7d565b81151561437957fe5b0690506142ea85846138ae565b608060405190810160405280600081526020016000815260200160008152602001600081525090565b610120604051908101604052806143c4614386565b81526020016143d1614386565b8152602001600081525090565b604080516060810182526000808252602082018190529181019190915290565b600061260a82356158b0565b6000601f8201831361441b57600080fd5b813561442e6144298261583f565b615818565b81815260209384019390925082018360005b8381101561446c578135860161445688826145bc565b8452506020928301929190910190600101614440565b5050505092915050565b6000601f8201831361448757600080fd5b81356144956144298261583f565b81815260209384019390925082018360005b8381101561446c57813586016144bd888261460b565b84525060209283019291909101906001016144a7565b6000601f820183136144e457600080fd5b81356144f26144298261583f565b9150818183526020840193506020810190508385602084028201111561451757600080fd5b60005b8381101561446c578161452d888261454f565b845250602092830192919091019060010161451a565b600061260a82356158c9565b600061260a82356158ce565b600061260a82356158d1565b600061260a82516158d1565b600080601f8301841361458557600080fd5b50813567ffffffffffffffff81111561459d57600080fd5b6020830191508360018202830111156145b557600080fd5b9250929050565b6000601f820183136145cd57600080fd5b81356145db61442982615860565b915080825260208301602083018583830111156145f757600080fd5b614602838284615907565b50505092915050565b6000610180828403121561461e57600080fd5b614629610180615818565b9050600061463784846143fe565b8252506020614648848483016143fe565b602083015250604061465c848285016143fe565b6040830152506060614670848285016143fe565b60608301525060806146848482850161454f565b60808301525060a06146988482850161454f565b60a08301525060c06146ac8482850161454f565b60c08301525060e06146c08482850161454f565b60e0830152506101006146d58482850161454f565b610100830152506101206146eb8482850161454f565b6101208301525061014082013567ffffffffffffffff81111561470d57600080fd5b614719848285016145bc565b6101408301525061016082013567ffffffffffffffff81111561473b57600080fd5b614747848285016145bc565b6101608301525092915050565b60006020828403121561476657600080fd5b600061344b84846143fe565b6000806040838503121561478557600080fd5b600061479185856143fe565b92505060206147a2858286016143fe565b9150509250929050565b600080604083850312156147bf57600080fd5b60006147cb85856143fe565b92505060206147a285828601614543565b6000602082840312156147ee57600080fd5b813567ffffffffffffffff81111561480557600080fd5b61344b84828501614476565b60008060006060848603121561482657600080fd5b833567ffffffffffffffff81111561483d57600080fd5b61484986828701614476565b935050602084013567ffffffffffffffff81111561486657600080fd5b614872868287016144d3565b925050604084013567ffffffffffffffff81111561488f57600080fd5b61489b8682870161440a565b9150509250925092565b6000806000606084860312156148ba57600080fd5b833567ffffffffffffffff8111156148d157600080fd5b6148dd86828701614476565b93505060206148728682870161454f565b60006020828403121561490057600080fd5b600061344b848461454f565b6000806040838503121561491f57600080fd5b6000614791858561454f565b6000806000806060858703121561494157600080fd5b600061494d878761454f565b945050602061495e878288016143fe565b935050604085013567ffffffffffffffff81111561497b57600080fd5b61498787828801614573565b95989497509550505050565b6000806000606084860312156149a857600080fd5b60006149b4868661454f565b93505060206149c5868287016143fe565b925050604084013567ffffffffffffffff8111156149e257600080fd5b61489b868287016145bc565b600060208284031215614a0057600080fd5b600061344b848461455b565b600060208284031215614a1e57600080fd5b600061344b8484614567565b600060208284031215614a3c57600080fd5b813567ffffffffffffffff811115614a5357600080fd5b61344b8482850161460b565b60008060008060808587031215614a7557600080fd5b843567ffffffffffffffff811115614a8c57600080fd5b614a988782880161460b565b945050602085013567ffffffffffffffff811115614ab557600080fd5b614ac18782880161460b565b935050604085013567ffffffffffffffff811115614ade57600080fd5b614aea878288016145bc565b925050606085013567ffffffffffffffff811115614b0757600080fd5b614b13878288016145bc565b91505092959194509250565b600080600060608486031215614b3457600080fd5b833567ffffffffffffffff811115614b4b57600080fd5b614b578682870161460b565b93505060206149c58682870161454f565b60008060008060008060808789031215614b8157600080fd5b6000614b8d898961454f565b9650506020614b9e89828a016143fe565b955050604087013567ffffffffffffffff811115614bbb57600080fd5b614bc789828a01614573565b9450945050606087013567ffffffffffffffff811115614be657600080fd5b614bf289828a01614573565b92509250509295509295509295565b614c0a816158b0565b82525050565b6000614c1b826158ac565b808452602084019350614c2d836158a6565b60005b82811015614c5d57614c438683516153e5565b614c4c826158a6565b606096909601959150600101614c30565b5093949350505050565b614c0a816158c9565b614c0a816158ce565b614c0a816158d1565b6000614c8d826158ac565b808452614ca1816020860160208601615913565b614caa8161593f565b9093016020019392505050565b614c0a816158fc565b601281527f4c454e4754485f36355f52455155495245440000000000000000000000000000602082015260400190565b600d81527f494e56414c49445f54414b455200000000000000000000000000000000000000602082015260400190565b600e81527f4f524445525f4f56455246494c4c000000000000000000000000000000000000602082015260400190565b601181527f55494e543235365f554e444552464c4f57000000000000000000000000000000602082015260400190565b601a81527f41535345545f50524f58595f414c52454144595f455849535453000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f33325f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601581527f5349474e41545552455f554e535550504f525445440000000000000000000000602082015260400190565b601081527f4449564953494f4e5f42595f5a45524f00000000000000000000000000000000602082015260400190565b601781527f494e56414c49445f4f524445525f5349474e4154555245000000000000000000602082015260400190565b600d81527f494e56414c49445f4d414b455200000000000000000000000000000000000000602082015260400190565b601081527f55494e543235365f4f564552464c4f5700000000000000000000000000000000602082015260400190565b600f81527f494e56414c49445f54585f484153480000000000000000000000000000000000602082015260400190565b601181527f494e56414c49445f5349474e4154555245000000000000000000000000000000602082015260400190565b600e81527f524f554e44494e475f4552524f52000000000000000000000000000000000000602082015260400190565b601081527f4641494c45445f455845435554494f4e00000000000000000000000000000000602082015260400190565b600d81527f54414b45525f4f56455250415900000000000000000000000000000000000000602082015260400190565b601481527f494e56414c49445f54414b45525f414d4f554e54000000000000000000000000602082015260400190565b601a81527f41535345545f50524f58595f444f45535f4e4f545f4558495354000000000000602082015260400190565b602181527f475245415445525f5448414e5f5a45524f5f4c454e4754485f5245515549524560208201527f4400000000000000000000000000000000000000000000000000000000000000604082015260600190565b601181527f5349474e41545552455f494c4c4547414c000000000000000000000000000000602082015260400190565b601e81527f4c454e4754485f475245415445525f5448414e5f305f52455155495245440000602082015260400190565b601781527f494e56414c49445f4e45575f4f524445525f45504f4348000000000000000000602082015260400190565b601e81527f4c454e4754485f475245415445525f5448414e5f335f52455155495245440000602082015260400190565b601481527f434f4d504c4554455f46494c4c5f4641494c4544000000000000000000000000602082015260400190565b601281527f494e56414c49445f46494c4c5f50524943450000000000000000000000000000602082015260400190565b601281527f5245454e5452414e43595f494c4c4547414c0000000000000000000000000000602082015260400190565b601381527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601081527f4f524445525f554e46494c4c41424c4500000000000000000000000000000000602082015260400190565b600e81527f494e56414c49445f53454e444552000000000000000000000000000000000000602082015260400190565b601881527f4e454741544956455f5350524541445f52455155495245440000000000000000602082015260400190565b601481527f494e56414c49445f54585f5349474e4154555245000000000000000000000000602082015260400190565b601181527f4c454e4754485f305f5245515549524544000000000000000000000000000000602082015260400190565b805160808301906153738482614c70565b5060208201516153866020850182614c70565b5060408201516153996040850182614c70565b5060608201516127a36060850182614c70565b80516101208301906153be8482615362565b5060208201516153d16080850182615362565b5060408201516127a3610100850182614c70565b805160608301906153f6848261541c565b5060208201516154096020850182614c70565b5060408201516127a36040850182614c70565b614c0a816158f6565b60208101612fbe8284614c01565b6101008101615442828b614c01565b61544f602083018a614c01565b61545c6040830189614c70565b6154696060830188614c70565b6154766080830187614c70565b61548360a0830186614c70565b81810360c08301526154958185614c82565b905081810360e08301526154a98184614c82565b9a9950505050505050505050565b606081016154c58286614c01565b81810360208301526154d78185614c82565b905081810360408301526154eb8184614c82565b95945050505050565b6020808252810161260a8184614c10565b60208101612fbe8284614c67565b60208101612fbe8284614c70565b6060810161552f8286614c70565b61553c6020830185614c01565b81810360408301526154eb8184614c82565b6040810161555c8285614c70565b818103602083015261344b8184614c82565b6080810161557c8287614c70565b615589602083018661541c565b6155966040830185614c70565b6154eb6060830184614c70565b604081016155b18285614c79565b61260a6020830184614c01565b6020808252810161260a8184614c82565b60208101612fbe8284614cb7565b60208082528101612fbe81614cc0565b60208082528101612fbe81614cf0565b60208082528101612fbe81614d20565b60208082528101612fbe81614d50565b60208082528101612fbe81614d80565b60208082528101612fbe81614db0565b60208082528101612fbe81614e06565b60208082528101612fbe81614e36565b60208082528101612fbe81614e66565b60208082528101612fbe81614e96565b60208082528101612fbe81614ec6565b60208082528101612fbe81614ef6565b60208082528101612fbe81614f26565b60208082528101612fbe81614f56565b60208082528101612fbe81614f86565b60208082528101612fbe81614fb6565b60208082528101612fbe81614fe6565b60208082528101612fbe81615016565b60208082528101612fbe81615046565b60208082528101612fbe8161509c565b60208082528101612fbe816150cc565b60208082528101612fbe816150fc565b60208082528101612fbe8161512c565b60208082528101612fbe8161515c565b60208082528101612fbe8161518c565b60208082528101612fbe816151bc565b60208082528101612fbe816151ec565b60208082528101612fbe8161521c565b60208082528101612fbe81615272565b60208082528101612fbe816152a2565b60208082528101612fbe816152d2565b60208082528101612fbe81615302565b60208082528101612fbe81615332565b60808101612fbe8284615362565b6101208101612fbe82846153ac565b60608101612fbe82846153e5565b60405181810167ffffffffffffffff8111828210171561583757600080fd5b604052919050565b600067ffffffffffffffff82111561585657600080fd5b5060209081020190565b600067ffffffffffffffff82111561587757600080fd5b506020601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160190565b60200190565b5190565b73ffffffffffffffffffffffffffffffffffffffff1690565b151590565b90565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b60ff1690565b6000612fbe826158b0565b82818337506000910152565b60005b8381101561592e578181015183820152602001615916565b838111156127a35750506000910152565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016905600a265627a7a72305820d41ee66f45c4d1637cb6e5f109447c6d5d7fef3204a685dc442151c0f029b7da6c6578706572696d656e74616cf50037", + "storage": { + "0x1458d05345aa0372fb580f207529f32cbb6e9242890d36a93225785d4496083e": "0x0000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48" + } + }, + "0x5409ed021d9299bf6814279a6a1411a7e866a631": { + "balance": "0xac6bd1cc338c2000", + "nonce": "22", + "code": "0x", + "storage": {} + }, + "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c": { + "balance": "0x0", + "nonce": "1", + "code": "0x606060405236156100965763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde038114610098578063095ea7b31461014657806318160ddd1461018657806323b872dd146101a8578063313ce567146101ee57806370a082311461021457806395d89b411461024f578063a9059cbb146102fd578063dd62ed3e1461033d575bfe5b34156100a057fe5b6100a861037e565b60408051602080825283518183015283519192839290830191850190808383821561010c575b80518252602083111561010c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016100ce565b505050905090810190601f1680156101385780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561014e57fe5b61017273ffffffffffffffffffffffffffffffffffffffff600435166024356103b5565b604080519115158252519081900360200190f35b341561018e57fe5b61019661042d565b60408051918252519081900360200190f35b34156101b057fe5b61017273ffffffffffffffffffffffffffffffffffffffff60043581169060243516604435610433565b604080519115158252519081900360200190f35b34156101f657fe5b6101fe6105d4565b6040805160ff9092168252519081900360200190f35b341561021c57fe5b61019673ffffffffffffffffffffffffffffffffffffffff600435166105d9565b60408051918252519081900360200190f35b341561025757fe5b6100a8610605565b60408051602080825283518183015283519192839290830191850190808383821561010c575b80518252602083111561010c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016100ce565b505050905090810190601f1680156101385780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561030557fe5b61017273ffffffffffffffffffffffffffffffffffffffff6004351660243561063c565b604080519115158252519081900360200190f35b341561034557fe5b61019673ffffffffffffffffffffffffffffffffffffffff60043581169060243516610727565b60408051918252519081900360200190f35b60408051808201909152601181527f30782050726f746f636f6c20546f6b656e000000000000000000000000000000602082015281565b73ffffffffffffffffffffffffffffffffffffffff338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60035481565b73ffffffffffffffffffffffffffffffffffffffff808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906104835750828110155b80156104b6575073ffffffffffffffffffffffffffffffffffffffff841660009081526020819052604090205483810110155b156105c65773ffffffffffffffffffffffffffffffffffffffff808516600090815260208190526040808220805487019055918716815220805484900390557fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110156105585773ffffffffffffffffffffffffffffffffffffffff808616600090815260016020908152604080832033909416835292905220805484900390555b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3600191506105cb565b600091505b5b509392505050565b601281565b73ffffffffffffffffffffffffffffffffffffffff81166000908152602081905260409020545b919050565b60408051808201909152600381527f5a52580000000000000000000000000000000000000000000000000000000000602082015281565b73ffffffffffffffffffffffffffffffffffffffff3316600090815260208190526040812054829010801590610699575073ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205482810110155b156107185773ffffffffffffffffffffffffffffffffffffffff33811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610427565b506000610427565b5b92915050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152600160209081526040808320938516835292905220545b929150505600a165627a7a723058201b5b70cf82a73dec658c2e60ab9a0f8e2ba01a74b66a6f5b0402f56d2ea0ffcf0029", + "storage": { + "0xd37b858806ebf992fe75c1dd1a61cc7625ea52328d19005ba6b8b62506ae5306": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + } + }, + "config": { + "chainId": 5, + "supportedProtocolVersions": [ + 67, + 66 + ], + "homesteadBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 1561651, + "berlinBlock": 4460644, + "londonBlock": 5062605, + "terminalTotalDifficulty": 10790000, + "terminalTotalDifficultyPassed": true, + "clique": { + "period": 15, + "epoch": 30000 + }, + "trustedCheckpoint": { + "sectionIndex": 210, + "sectionHead": "0xbb11eaf551a6c06f74a6c7bbfe1699cbf64b8f248b64691da916dd443176db2f", + "chtRoot": "0x9934ae326d00d9c7de2e074c0e51689efb7fa7fcba18929ff4279c27259c45e6", + "bloomRoot": "0x7fe3bd4fd45194aa8a5cfe5ac590edff1f870d3d98d3c310494e7f67613a87ff" + }, + "trustedCheckpointOracle": { + "address": "0x18ca0e045f0d772a851bc7e48357bcaab0a0795d", + "signers": [ + "0x4769bcad07e3b938b7f43eb7d278bc7cb9effb38", + "0x78d1ad571a1a09d60d9bbf25894b44e4c8859595", + "0x286834935f4a8cfb4ff4c77d5770c2775ae2b0e7", + "0xb86e2b0ab5a4b1373e40c51a7c712c70ba2f9f8e", + "0x0df8fa387c602ae62559cc4afa4972a7045d6707" + ], + "threshold": 2 + } + } + }, + "context": { + "number": "13536", + "difficulty": "1", + "timestamp": "1549153018", + "gasLimit": "8000000", + "miner": "0x0000000000000000000000000000000000000000", + "transactionHash": "0x6974f745a004f030bebb1c01d4595edbda2fafcf01c0bfbd5d335711e2a7b04e" + }, + "input": "0xf92e9e1684ee6b2800832c8c7f8080b92e4c60806040523480156200001157600080fd5b5060405162002d2c38038062002d2c83398101806040526200003791908101906200051d565b6000805433600160a060020a031991821617825560018054909116600160a060020a0386161790558251849084908490849081906200007e906004906020870190620003d0565b50825162000094906005906020860190620003d0565b50620000b0836010640100000000620019476200036f82021704565b9150620000cd846010640100000000620019476200036f82021704565b60028054600160a060020a03948516600160a060020a031991821617909155600380549285169290911691909117905550600154604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130181207f6070410800000000000000000000000000000000000000000000000000000000825291909216945063607041089350620001739250906004016200068e565b602060405180830381600087803b1580156200018e57600080fd5b505af1158015620001a3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620001c99190810190620004f4565b9050600160a060020a038116151562000219576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200021090620006b0565b60405180910390fd5b6002546040517f095ea7b3000000000000000000000000000000000000000000000000000000008152600160a060020a039091169063095ea7b39062000268908490600019906004016200066f565b602060405180830381600087803b1580156200028357600080fd5b505af115801562000298573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620002be9190810190620005a1565b506003546040517f095ea7b3000000000000000000000000000000000000000000000000000000008152600160a060020a039091169063095ea7b3906200030e908490600019906004016200066f565b602060405180830381600087803b1580156200032957600080fd5b505af11580156200033e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620003649190810190620005a1565b50505050506200077a565b600081601401835110151515620003b4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000210906200069e565b506014818301810151910190600160a060020a03165b92915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200041357805160ff191683800117855562000443565b8280016001018555821562000443579182015b828111156200044357825182559160200191906001019062000426565b506200045192915062000455565b5090565b6200047291905b808211156200045157600081556001016200045c565b90565b600062000483825162000711565b9392505050565b600062000483825162000742565b6000601f82018313620004aa57600080fd5b8151620004c1620004bb82620006e9565b620006c2565b91508082526020830160208301858383011115620004de57600080fd5b620004eb83828462000747565b50505092915050565b6000602082840312156200050757600080fd5b600062000515848462000475565b949350505050565b6000806000606084860312156200053357600080fd5b600062000541868662000475565b93505060208401516001604060020a038111156200055e57600080fd5b6200056c8682870162000498565b92505060408401516001604060020a038111156200058957600080fd5b620005978682870162000498565b9150509250925092565b600060208284031215620005b457600080fd5b60006200051584846200048a565b620005cd8162000711565b82525050565b620005cd816200071d565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601881527f554e524547495354455245445f41535345545f50524f58590000000000000000602082015260400190565b620005cd8162000472565b604081016200067f8285620005c2565b62000483602083018462000664565b60208101620003ca8284620005d3565b60208082528101620003ca81620005de565b60208082528101620003ca8162000634565b6040518181016001604060020a0381118282101715620006e157600080fd5b604052919050565b60006001604060020a038211156200070057600080fd5b506020601f91909101601f19160190565b600160a060020a031690565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b151590565b60005b83811015620007645781810151838201526020016200074a565b8381111562000774576000848401525b50505050565b6125a2806200078a6000396000f30060806040526004361061006c5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166318978e8281146100c8578063630f1e6c146100f25780638da5cb5b146101125780639395525c14610134578063f2fde38b14610147575b60025473ffffffffffffffffffffffffffffffffffffffff1633146100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612388565b60405180910390fd5b005b6100db6100d6366004611df1565b610167565b6040516100e9929190612488565b60405180910390f35b3480156100fe57600080fd5b506100c661010d366004611eec565b6102f7565b34801561011e57600080fd5b50610127610388565b6040516100e99190612337565b6100db610142366004611d0b565b6103a4565b34801561015357600080fd5b506100c6610162366004611ce5565b61050a565b61016f6119fa565b6101776119fa565b6000806101826105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610100600188161502019095169490940493840181900481028201810190925282815261025c939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b820191906000526020600020905b81548152906001019060200180831161021057829003601f168201915b50505050508c600081518110151561024157fe5b6020908102909101015161014001519063ffffffff61069616565b156102875761026c8b8b8b6107c3565b935061028084600001518560600151610ac1565b90506102ae565b6102928b8b8b610b03565b9350836060015191506102a68883896107c3565b845190935090505b6102c2846020015184602001518888610d15565b6102e98b60008151811015156102d457fe5b90602001906020020151610140015182610f29565b505097509795505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610348576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612438565b61038383838080601f01602080910402602001604051908101604052809392919081815260200183838082843750879450610f299350505050565b505050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b6103ac6119fa565b6103b46119fa565b60008060006103c16105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152610441939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b156104925761046a670de0b6b3a7640000610464670de0b6b3a76400008a611045565b3461108f565b92506104778b848c6110e7565b945061048b85600001518660600151610ac1565b90506104d6565b6104ad670d2f13f7789f0000670de0b6b3a76400003461108f565b92506104ba8b848c6110e7565b9450846060015191506104ce89838a6107c3565b855190945090505b6104ea856020015185602001518989610d15565b6104fc8b60008151811015156102d457fe5b505050965096945050505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461055b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612438565b73ffffffffffffffffffffffffffffffffffffffff8116156105b857600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b600034116105f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612398565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561067b57600080fd5b505af115801561068f573d6000803e3d6000fd5b5050505050565b6000815183511480156107ba5750816040518082805190602001908083835b602083106106f257805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016106b5565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0180199092169116179052604051919093018190038120885190955088945090928392508401908083835b6020831061078757805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161074a565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902060001916145b90505b92915050565b6107cb6119fa565b60608060008060008060006107de6119fa565b8a15156107ea57610ab2565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561088e5780601f106108635761010080835404028352916020019161088e565b820191906000526020600020905b81548152906001019060200180831161087157829003601f168201915b505060058054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152969e509194509250840190508282801561093d5780601f106109125761010080835404028352916020019161093d565b820191906000526020600020905b81548152906001019060200180831161092057829003601f168201915b50505050509650600095508b519450600093505b838514610a7857878c8581518110151561096757fe5b6020908102909101015161014001528b5187908d908690811061098657fe5b60209081029091010151610160015261099f8b87610ac1565b9250610a068c858151811015156109b257fe5b9060200190602002015160a00151610a008e878151811015156109d157fe5b90602001906020020151608001518f888151811015156109ed57fe5b9060200190602002015160e00151610ac1565b8561128b565b9150610a418c85815181101515610a1957fe5b90602001906020020151838c87815181101515610a3257fe5b906020019060200201516112e6565b9050610a4d898261135e565b610a5f89600001518a60600151610ac1565b95508a8610610a6d57610a78565b600190930192610951565b8a861015610ab2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612418565b50505050505050509392505050565b600082821115610afd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123b8565b50900390565b610b0b6119fa565b606080600080600080610b1c6119fa565b60008b6000815181101515610b2d57fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929b5092909190830182828015610be55780601f10610bba57610100808354040283529160200191610be5565b820191906000526020600020905b815481529060010190602001808311610bc857829003601f168201915b505050505096508b519550600094505b848614610cdb57878c86815181101515610c0b57fe5b6020908102909101015161014001528b5187908d9087908110610c2a57fe5b6020908102909101015161016001528851610c46908c90610ac1565b9350610c898c86815181101515610c5957fe5b9060200190602002015160a001518d87815181101515610c7557fe5b90602001906020020151608001518661128b565b9250610cb58c86815181101515610c9c57fe5b90602001906020020151848c88815181101515610a3257fe5b9150610cc1898361135e565b5087518a8110610cd057610cdb565b600190940193610bf5565b8a811015610ab2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612418565b600080808066b1a2bc2ec50000861115610d5b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612448565b610d658888611045565b935034841115610da1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123a8565b610dab3485610ac1565b9250610dc086670de0b6b3a76400008a61108f565b915082821115610dfc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612428565b6000831115610f1f576002546040517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911690632e1a7d4d90610e5b9086906004016124a4565b600060405180830381600087803b158015610e7557600080fd5b505af1158015610e89573d6000803e3d6000fd5b505050506000821115610edb5760405173ffffffffffffffffffffffffffffffffffffffff86169083156108fc029084906000818181858888f19350505050158015610ed9573d6000803e3d6000fd5b505b610ee58383610ac1565b90506000811115610f1f57604051339082156108fc029083906000818181858888f19350505050158015610f1d573d6000803e3d6000fd5b505b5050505050505050565b6000610f3b838263ffffffff6113c016565b604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130190209091507fffffffff0000000000000000000000000000000000000000000000000000000080831691161415610fab57610fa6838361142d565b610383565b604080517f455243373231546f6b656e28616464726573732c75696e7432353629000000008152905190819003601c0190207fffffffff000000000000000000000000000000000000000000000000000000008281169116141561101357610fa6838361161b565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123f8565b600082820183811015611084576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e8565b8091505b5092915050565b60008083116110ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d8565b6110dd6110d78584611703565b8461175e565b90505b9392505050565b6110ef6119fa565b60608060008060006110ff6119fa565b89600081518110151561110e57fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929950929091908301828280156111c65780601f1061119b576101008083540402835291602001916111c6565b820191906000526020600020905b8154815290600101906020018083116111a957829003601f168201915b5050505050945089519350600092505b82841461127e57858a848151811015156111ec57fe5b602090810290910101516101400152895185908b908590811061120b57fe5b90602001906020020151610160018190525061122b898860200151610ac1565b91506112578a8481518110151561123e57fe5b90602001906020020151838a86815181101515610a3257fe5b9050611263878261135e565b602087015189116112735761127e565b6001909201916111d6565b5050505050509392505050565b60008083116112c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d8565b6110dd6110d76112d68685611703565b6112e1866001610ac1565b611045565b6112ee6119fa565b606060006112fd868686611775565b600154815191935073ffffffffffffffffffffffffffffffffffffffff1691506080908390602082016000855af1801561135457825184526020830151602085015260408301516040850152606083015160608501525b5050509392505050565b8151815161136c9190611045565b8252602080830151908201516113829190611045565b60208301526040808301519082015161139b9190611045565b6040830152606080830151908201516113b49190611045565b60609092019190915250565b600081600401835110151515611402576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612468565b5001602001517fffffffff000000000000000000000000000000000000000000000000000000001690565b60008061144184601063ffffffff61194716565b604080517f7472616e7366657228616464726573732c75696e7432353629000000000000008152905190819003601901812091935073ffffffffffffffffffffffffffffffffffffffff8416919061149f903390879060240161236d565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931783525181519192909182919080838360005b8381101561154357818101518382015260200161152b565b50505050905090810190601f1680156115705780820380516001836020036101000a031916815260200191505b509150506000604051808303816000865af1925050508015156115bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612408565b3d156115dc575060003d602014156115dc5760206000803e506000515b801515611615576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612408565b50505050565b60008060018314611658576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612478565b61166984601063ffffffff61194716565b915061167c84602463ffffffff6119a816565b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff8316906323b872dd906116d590309033908690600401612345565b600060405180830381600087803b1580156116ef57600080fd5b505af1158015610f1f573d6000803e3d6000fd5b6000808315156117165760009150611088565b5082820282848281151561172657fe5b0414611084576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e8565b600080828481151561176c57fe5b04949350505050565b604080517fb4be83d5000000000000000000000000000000000000000000000000000000006020808301919091526060602483018181528751608485019081528884015160a48601529488015160c48501529087015160e4840152608087015161010484015260a087015161012484015260c087015161014484015260e08701516101648401526101008701516101848401526101208701516101a4840152610140870180516101c485019081526101608901516101e4860152610180905251805161020485018190529394919384936044870192849261022489019291820191601f82010460005b8181101561187c57835185526020948501949093019260010161185e565b50505050818103610160808401919091528a0151805180835260209283019291820191601f82010460005b818110156118c55783518552602094850194909301926001016118a7565b50505089845250848103602093840190815288518083529093918201918981019190601f82010460005b8181101561190d5783518552602094850194909301926001016118ef565b5050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08883030188525060405250505050509392505050565b600081601401835110151515611989576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612458565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b60006107ba83836000816020018351101515156119f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123c8565b50016020015190565b608060405190810160405280600081526020016000815260200160008152602001600081525090565b60006107ba8235612540565b6000601f82018313611a4057600080fd5b8135611a53611a4e826124d9565b6124b2565b81815260209384019390925082018360005b83811015611a915781358601611a7b8882611b41565b8452506020928301929190910190600101611a65565b5050505092915050565b6000601f82018313611aac57600080fd5b8135611aba611a4e826124d9565b81815260209384019390925082018360005b83811015611a915781358601611ae28882611b90565b8452506020928301929190910190600101611acc565b600080601f83018413611b0a57600080fd5b50813567ffffffffffffffff811115611b2257600080fd5b602083019150836001820283011115611b3a57600080fd5b9250929050565b6000601f82018313611b5257600080fd5b8135611b60611a4e826124fa565b91508082526020830160208301858383011115611b7c57600080fd5b611b8783828461255c565b50505092915050565b60006101808284031215611ba357600080fd5b611bae6101806124b2565b90506000611bbc8484611a23565b8252506020611bcd84848301611a23565b6020830152506040611be184828501611a23565b6040830152506060611bf584828501611a23565b6060830152506080611c0984828501611cd9565b60808301525060a0611c1d84828501611cd9565b60a08301525060c0611c3184828501611cd9565b60c08301525060e0611c4584828501611cd9565b60e083015250610100611c5a84828501611cd9565b61010083015250610120611c7084828501611cd9565b6101208301525061014082013567ffffffffffffffff811115611c9257600080fd5b611c9e84828501611b41565b6101408301525061016082013567ffffffffffffffff811115611cc057600080fd5b611ccc84828501611b41565b6101608301525092915050565b60006107ba8235612559565b600060208284031215611cf757600080fd5b6000611d038484611a23565b949350505050565b60008060008060008060c08789031215611d2457600080fd5b863567ffffffffffffffff811115611d3b57600080fd5b611d4789828a01611a9b565b965050602087013567ffffffffffffffff811115611d6457600080fd5b611d7089828a01611a2f565b955050604087013567ffffffffffffffff811115611d8d57600080fd5b611d9989828a01611a9b565b945050606087013567ffffffffffffffff811115611db657600080fd5b611dc289828a01611a2f565b9350506080611dd389828a01611cd9565b92505060a0611de489828a01611a23565b9150509295509295509295565b600080600080600080600060e0888a031215611e0c57600080fd5b873567ffffffffffffffff811115611e2357600080fd5b611e2f8a828b01611a9b565b9750506020611e408a828b01611cd9565b965050604088013567ffffffffffffffff811115611e5d57600080fd5b611e698a828b01611a2f565b955050606088013567ffffffffffffffff811115611e8657600080fd5b611e928a828b01611a9b565b945050608088013567ffffffffffffffff811115611eaf57600080fd5b611ebb8a828b01611a2f565b93505060a0611ecc8a828b01611cd9565b92505060c0611edd8a828b01611a23565b91505092959891949750929550565b600080600060408486031215611f0157600080fd5b833567ffffffffffffffff811115611f1857600080fd5b611f2486828701611af8565b93509350506020611f3786828701611cd9565b9150509250925092565b611f4a81612540565b82525050565b602381527f44454641554c545f46554e4354494f4e5f574554485f434f4e54524143545f4f60208201527f4e4c590000000000000000000000000000000000000000000000000000000000604082015260600190565b601181527f494e56414c49445f4d53475f56414c5545000000000000000000000000000000602082015260400190565b600d81527f4f564552534f4c445f5745544800000000000000000000000000000000000000602082015260400190565b601181527f55494e543235365f554e444552464c4f57000000000000000000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f33325f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601081527f4449564953494f4e5f42595f5a45524f00000000000000000000000000000000602082015260400190565b601081527f55494e543235365f4f564552464c4f5700000000000000000000000000000000602082015260400190565b601781527f554e535550504f525445445f41535345545f50524f5859000000000000000000602082015260400190565b600f81527f5452414e534645525f4641494c45440000000000000000000000000000000000602082015260400190565b601481527f434f4d504c4554455f46494c4c5f4641494c4544000000000000000000000000602082015260400190565b601a81527f494e53554646494349454e545f4554485f52454d41494e494e47000000000000602082015260400190565b601381527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000602082015260400190565b601881527f4645455f50455243454e544147455f544f4f5f4c415247450000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b602581527f475245415445525f4f525f455155414c5f544f5f345f4c454e4754485f52455160208201527f5549524544000000000000000000000000000000000000000000000000000000604082015260600190565b600e81527f494e56414c49445f414d4f554e54000000000000000000000000000000000000602082015260400190565b805160808301906122f9848261232e565b50602082015161230c602085018261232e565b50604082015161231f604085018261232e565b50606082015161161560608501825b611f4a81612559565b602081016107bd8284611f41565b606081016123538286611f41565b6123606020830185611f41565b611d03604083018461232e565b6040810161237b8285611f41565b6110e0602083018461232e565b602080825281016107bd81611f50565b602080825281016107bd81611fa6565b602080825281016107bd81611fd6565b602080825281016107bd81612006565b602080825281016107bd81612036565b602080825281016107bd8161208c565b602080825281016107bd816120bc565b602080825281016107bd816120ec565b602080825281016107bd8161211c565b602080825281016107bd8161214c565b602080825281016107bd8161217c565b602080825281016107bd816121ac565b602080825281016107bd816121dc565b602080825281016107bd8161220c565b602080825281016107bd81612262565b602080825281016107bd816122b8565b610100810161249782856122e8565b6110e060808301846122e8565b602081016107bd828461232e565b60405181810167ffffffffffffffff811182821017156124d157600080fd5b604052919050565b600067ffffffffffffffff8211156124f057600080fd5b5060209081020190565b600067ffffffffffffffff82111561251157600080fd5b506020601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160190565b73ffffffffffffffffffffffffffffffffffffffff1690565b90565b828183375060009101525600a265627a7a72305820d9f418f11e0f91f06f6f9d22924be0add925495eeb76a6388b5417adb505eeb36c6578706572696d656e74616cf5003700000000000000000000000048bacb9266a570d521063ef5dd96e61686dbe788000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082000000000000000000000000000000000000000000000000000000001ba0a7c6b0c9a5cb47eb4a8449556851a943353640d4fe93a64eb89eff56245c27f1a00e0d13877bfb8842dc394fd206d041b1f76be95a371eff128c8c34812a1b24c8", + "result": [ + { + "action": { + "from": "0x5409ed021d9299bf6814279a6a1411a7e866a631", + "gas": "0x2c8c7f", + "init": "0x60806040523480156200001157600080fd5b5060405162002d2c38038062002d2c83398101806040526200003791908101906200051d565b6000805433600160a060020a031991821617825560018054909116600160a060020a0386161790558251849084908490849081906200007e906004906020870190620003d0565b50825162000094906005906020860190620003d0565b50620000b0836010640100000000620019476200036f82021704565b9150620000cd846010640100000000620019476200036f82021704565b60028054600160a060020a03948516600160a060020a031991821617909155600380549285169290911691909117905550600154604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130181207f6070410800000000000000000000000000000000000000000000000000000000825291909216945063607041089350620001739250906004016200068e565b602060405180830381600087803b1580156200018e57600080fd5b505af1158015620001a3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620001c99190810190620004f4565b9050600160a060020a038116151562000219576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200021090620006b0565b60405180910390fd5b6002546040517f095ea7b3000000000000000000000000000000000000000000000000000000008152600160a060020a039091169063095ea7b39062000268908490600019906004016200066f565b602060405180830381600087803b1580156200028357600080fd5b505af115801562000298573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620002be9190810190620005a1565b506003546040517f095ea7b3000000000000000000000000000000000000000000000000000000008152600160a060020a039091169063095ea7b3906200030e908490600019906004016200066f565b602060405180830381600087803b1580156200032957600080fd5b505af11580156200033e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250620003649190810190620005a1565b50505050506200077a565b600081601401835110151515620003b4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000210906200069e565b506014818301810151910190600160a060020a03165b92915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200041357805160ff191683800117855562000443565b8280016001018555821562000443579182015b828111156200044357825182559160200191906001019062000426565b506200045192915062000455565b5090565b6200047291905b808211156200045157600081556001016200045c565b90565b600062000483825162000711565b9392505050565b600062000483825162000742565b6000601f82018313620004aa57600080fd5b8151620004c1620004bb82620006e9565b620006c2565b91508082526020830160208301858383011115620004de57600080fd5b620004eb83828462000747565b50505092915050565b6000602082840312156200050757600080fd5b600062000515848462000475565b949350505050565b6000806000606084860312156200053357600080fd5b600062000541868662000475565b93505060208401516001604060020a038111156200055e57600080fd5b6200056c8682870162000498565b92505060408401516001604060020a038111156200058957600080fd5b620005978682870162000498565b9150509250925092565b600060208284031215620005b457600080fd5b60006200051584846200048a565b620005cd8162000711565b82525050565b620005cd816200071d565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601881527f554e524547495354455245445f41535345545f50524f58590000000000000000602082015260400190565b620005cd8162000472565b604081016200067f8285620005c2565b62000483602083018462000664565b60208101620003ca8284620005d3565b60208082528101620003ca81620005de565b60208082528101620003ca8162000634565b6040518181016001604060020a0381118282101715620006e157600080fd5b604052919050565b60006001604060020a038211156200070057600080fd5b506020601f91909101601f19160190565b600160a060020a031690565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b151590565b60005b83811015620007645781810151838201526020016200074a565b8381111562000774576000848401525b50505050565b6125a2806200078a6000396000f30060806040526004361061006c5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166318978e8281146100c8578063630f1e6c146100f25780638da5cb5b146101125780639395525c14610134578063f2fde38b14610147575b60025473ffffffffffffffffffffffffffffffffffffffff1633146100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612388565b60405180910390fd5b005b6100db6100d6366004611df1565b610167565b6040516100e9929190612488565b60405180910390f35b3480156100fe57600080fd5b506100c661010d366004611eec565b6102f7565b34801561011e57600080fd5b50610127610388565b6040516100e99190612337565b6100db610142366004611d0b565b6103a4565b34801561015357600080fd5b506100c6610162366004611ce5565b61050a565b61016f6119fa565b6101776119fa565b6000806101826105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610100600188161502019095169490940493840181900481028201810190925282815261025c939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b820191906000526020600020905b81548152906001019060200180831161021057829003601f168201915b50505050508c600081518110151561024157fe5b6020908102909101015161014001519063ffffffff61069616565b156102875761026c8b8b8b6107c3565b935061028084600001518560600151610ac1565b90506102ae565b6102928b8b8b610b03565b9350836060015191506102a68883896107c3565b845190935090505b6102c2846020015184602001518888610d15565b6102e98b60008151811015156102d457fe5b90602001906020020151610140015182610f29565b505097509795505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610348576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612438565b61038383838080601f01602080910402602001604051908101604052809392919081815260200183838082843750879450610f299350505050565b505050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b6103ac6119fa565b6103b46119fa565b60008060006103c16105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152610441939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b156104925761046a670de0b6b3a7640000610464670de0b6b3a76400008a611045565b3461108f565b92506104778b848c6110e7565b945061048b85600001518660600151610ac1565b90506104d6565b6104ad670d2f13f7789f0000670de0b6b3a76400003461108f565b92506104ba8b848c6110e7565b9450846060015191506104ce89838a6107c3565b855190945090505b6104ea856020015185602001518989610d15565b6104fc8b60008151811015156102d457fe5b505050965096945050505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461055b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612438565b73ffffffffffffffffffffffffffffffffffffffff8116156105b857600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b600034116105f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612398565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561067b57600080fd5b505af115801561068f573d6000803e3d6000fd5b5050505050565b6000815183511480156107ba5750816040518082805190602001908083835b602083106106f257805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016106b5565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0180199092169116179052604051919093018190038120885190955088945090928392508401908083835b6020831061078757805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161074a565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902060001916145b90505b92915050565b6107cb6119fa565b60608060008060008060006107de6119fa565b8a15156107ea57610ab2565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561088e5780601f106108635761010080835404028352916020019161088e565b820191906000526020600020905b81548152906001019060200180831161087157829003601f168201915b505060058054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152969e509194509250840190508282801561093d5780601f106109125761010080835404028352916020019161093d565b820191906000526020600020905b81548152906001019060200180831161092057829003601f168201915b50505050509650600095508b519450600093505b838514610a7857878c8581518110151561096757fe5b6020908102909101015161014001528b5187908d908690811061098657fe5b60209081029091010151610160015261099f8b87610ac1565b9250610a068c858151811015156109b257fe5b9060200190602002015160a00151610a008e878151811015156109d157fe5b90602001906020020151608001518f888151811015156109ed57fe5b9060200190602002015160e00151610ac1565b8561128b565b9150610a418c85815181101515610a1957fe5b90602001906020020151838c87815181101515610a3257fe5b906020019060200201516112e6565b9050610a4d898261135e565b610a5f89600001518a60600151610ac1565b95508a8610610a6d57610a78565b600190930192610951565b8a861015610ab2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612418565b50505050505050509392505050565b600082821115610afd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123b8565b50900390565b610b0b6119fa565b606080600080600080610b1c6119fa565b60008b6000815181101515610b2d57fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929b5092909190830182828015610be55780601f10610bba57610100808354040283529160200191610be5565b820191906000526020600020905b815481529060010190602001808311610bc857829003601f168201915b505050505096508b519550600094505b848614610cdb57878c86815181101515610c0b57fe5b6020908102909101015161014001528b5187908d9087908110610c2a57fe5b6020908102909101015161016001528851610c46908c90610ac1565b9350610c898c86815181101515610c5957fe5b9060200190602002015160a001518d87815181101515610c7557fe5b90602001906020020151608001518661128b565b9250610cb58c86815181101515610c9c57fe5b90602001906020020151848c88815181101515610a3257fe5b9150610cc1898361135e565b5087518a8110610cd057610cdb565b600190940193610bf5565b8a811015610ab2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612418565b600080808066b1a2bc2ec50000861115610d5b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612448565b610d658888611045565b935034841115610da1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123a8565b610dab3485610ac1565b9250610dc086670de0b6b3a76400008a61108f565b915082821115610dfc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612428565b6000831115610f1f576002546040517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911690632e1a7d4d90610e5b9086906004016124a4565b600060405180830381600087803b158015610e7557600080fd5b505af1158015610e89573d6000803e3d6000fd5b505050506000821115610edb5760405173ffffffffffffffffffffffffffffffffffffffff86169083156108fc029084906000818181858888f19350505050158015610ed9573d6000803e3d6000fd5b505b610ee58383610ac1565b90506000811115610f1f57604051339082156108fc029083906000818181858888f19350505050158015610f1d573d6000803e3d6000fd5b505b5050505050505050565b6000610f3b838263ffffffff6113c016565b604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130190209091507fffffffff0000000000000000000000000000000000000000000000000000000080831691161415610fab57610fa6838361142d565b610383565b604080517f455243373231546f6b656e28616464726573732c75696e7432353629000000008152905190819003601c0190207fffffffff000000000000000000000000000000000000000000000000000000008281169116141561101357610fa6838361161b565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123f8565b600082820183811015611084576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e8565b8091505b5092915050565b60008083116110ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d8565b6110dd6110d78584611703565b8461175e565b90505b9392505050565b6110ef6119fa565b60608060008060006110ff6119fa565b89600081518110151561110e57fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929950929091908301828280156111c65780601f1061119b576101008083540402835291602001916111c6565b820191906000526020600020905b8154815290600101906020018083116111a957829003601f168201915b5050505050945089519350600092505b82841461127e57858a848151811015156111ec57fe5b602090810290910101516101400152895185908b908590811061120b57fe5b90602001906020020151610160018190525061122b898860200151610ac1565b91506112578a8481518110151561123e57fe5b90602001906020020151838a86815181101515610a3257fe5b9050611263878261135e565b602087015189116112735761127e565b6001909201916111d6565b5050505050509392505050565b60008083116112c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d8565b6110dd6110d76112d68685611703565b6112e1866001610ac1565b611045565b6112ee6119fa565b606060006112fd868686611775565b600154815191935073ffffffffffffffffffffffffffffffffffffffff1691506080908390602082016000855af1801561135457825184526020830151602085015260408301516040850152606083015160608501525b5050509392505050565b8151815161136c9190611045565b8252602080830151908201516113829190611045565b60208301526040808301519082015161139b9190611045565b6040830152606080830151908201516113b49190611045565b60609092019190915250565b600081600401835110151515611402576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612468565b5001602001517fffffffff000000000000000000000000000000000000000000000000000000001690565b60008061144184601063ffffffff61194716565b604080517f7472616e7366657228616464726573732c75696e7432353629000000000000008152905190819003601901812091935073ffffffffffffffffffffffffffffffffffffffff8416919061149f903390879060240161236d565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931783525181519192909182919080838360005b8381101561154357818101518382015260200161152b565b50505050905090810190601f1680156115705780820380516001836020036101000a031916815260200191505b509150506000604051808303816000865af1925050508015156115bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612408565b3d156115dc575060003d602014156115dc5760206000803e506000515b801515611615576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612408565b50505050565b60008060018314611658576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612478565b61166984601063ffffffff61194716565b915061167c84602463ffffffff6119a816565b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff8316906323b872dd906116d590309033908690600401612345565b600060405180830381600087803b1580156116ef57600080fd5b505af1158015610f1f573d6000803e3d6000fd5b6000808315156117165760009150611088565b5082820282848281151561172657fe5b0414611084576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e8565b600080828481151561176c57fe5b04949350505050565b604080517fb4be83d5000000000000000000000000000000000000000000000000000000006020808301919091526060602483018181528751608485019081528884015160a48601529488015160c48501529087015160e4840152608087015161010484015260a087015161012484015260c087015161014484015260e08701516101648401526101008701516101848401526101208701516101a4840152610140870180516101c485019081526101608901516101e4860152610180905251805161020485018190529394919384936044870192849261022489019291820191601f82010460005b8181101561187c57835185526020948501949093019260010161185e565b50505050818103610160808401919091528a0151805180835260209283019291820191601f82010460005b818110156118c55783518552602094850194909301926001016118a7565b50505089845250848103602093840190815288518083529093918201918981019190601f82010460005b8181101561190d5783518552602094850194909301926001016118ef565b5050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08883030188525060405250505050509392505050565b600081601401835110151515611989576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612458565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b60006107ba83836000816020018351101515156119f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123c8565b50016020015190565b608060405190810160405280600081526020016000815260200160008152602001600081525090565b60006107ba8235612540565b6000601f82018313611a4057600080fd5b8135611a53611a4e826124d9565b6124b2565b81815260209384019390925082018360005b83811015611a915781358601611a7b8882611b41565b8452506020928301929190910190600101611a65565b5050505092915050565b6000601f82018313611aac57600080fd5b8135611aba611a4e826124d9565b81815260209384019390925082018360005b83811015611a915781358601611ae28882611b90565b8452506020928301929190910190600101611acc565b600080601f83018413611b0a57600080fd5b50813567ffffffffffffffff811115611b2257600080fd5b602083019150836001820283011115611b3a57600080fd5b9250929050565b6000601f82018313611b5257600080fd5b8135611b60611a4e826124fa565b91508082526020830160208301858383011115611b7c57600080fd5b611b8783828461255c565b50505092915050565b60006101808284031215611ba357600080fd5b611bae6101806124b2565b90506000611bbc8484611a23565b8252506020611bcd84848301611a23565b6020830152506040611be184828501611a23565b6040830152506060611bf584828501611a23565b6060830152506080611c0984828501611cd9565b60808301525060a0611c1d84828501611cd9565b60a08301525060c0611c3184828501611cd9565b60c08301525060e0611c4584828501611cd9565b60e083015250610100611c5a84828501611cd9565b61010083015250610120611c7084828501611cd9565b6101208301525061014082013567ffffffffffffffff811115611c9257600080fd5b611c9e84828501611b41565b6101408301525061016082013567ffffffffffffffff811115611cc057600080fd5b611ccc84828501611b41565b6101608301525092915050565b60006107ba8235612559565b600060208284031215611cf757600080fd5b6000611d038484611a23565b949350505050565b60008060008060008060c08789031215611d2457600080fd5b863567ffffffffffffffff811115611d3b57600080fd5b611d4789828a01611a9b565b965050602087013567ffffffffffffffff811115611d6457600080fd5b611d7089828a01611a2f565b955050604087013567ffffffffffffffff811115611d8d57600080fd5b611d9989828a01611a9b565b945050606087013567ffffffffffffffff811115611db657600080fd5b611dc289828a01611a2f565b9350506080611dd389828a01611cd9565b92505060a0611de489828a01611a23565b9150509295509295509295565b600080600080600080600060e0888a031215611e0c57600080fd5b873567ffffffffffffffff811115611e2357600080fd5b611e2f8a828b01611a9b565b9750506020611e408a828b01611cd9565b965050604088013567ffffffffffffffff811115611e5d57600080fd5b611e698a828b01611a2f565b955050606088013567ffffffffffffffff811115611e8657600080fd5b611e928a828b01611a9b565b945050608088013567ffffffffffffffff811115611eaf57600080fd5b611ebb8a828b01611a2f565b93505060a0611ecc8a828b01611cd9565b92505060c0611edd8a828b01611a23565b91505092959891949750929550565b600080600060408486031215611f0157600080fd5b833567ffffffffffffffff811115611f1857600080fd5b611f2486828701611af8565b93509350506020611f3786828701611cd9565b9150509250925092565b611f4a81612540565b82525050565b602381527f44454641554c545f46554e4354494f4e5f574554485f434f4e54524143545f4f60208201527f4e4c590000000000000000000000000000000000000000000000000000000000604082015260600190565b601181527f494e56414c49445f4d53475f56414c5545000000000000000000000000000000602082015260400190565b600d81527f4f564552534f4c445f5745544800000000000000000000000000000000000000602082015260400190565b601181527f55494e543235365f554e444552464c4f57000000000000000000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f33325f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601081527f4449564953494f4e5f42595f5a45524f00000000000000000000000000000000602082015260400190565b601081527f55494e543235365f4f564552464c4f5700000000000000000000000000000000602082015260400190565b601781527f554e535550504f525445445f41535345545f50524f5859000000000000000000602082015260400190565b600f81527f5452414e534645525f4641494c45440000000000000000000000000000000000602082015260400190565b601481527f434f4d504c4554455f46494c4c5f4641494c4544000000000000000000000000602082015260400190565b601a81527f494e53554646494349454e545f4554485f52454d41494e494e47000000000000602082015260400190565b601381527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000602082015260400190565b601881527f4645455f50455243454e544147455f544f4f5f4c415247450000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b602581527f475245415445525f4f525f455155414c5f544f5f345f4c454e4754485f52455160208201527f5549524544000000000000000000000000000000000000000000000000000000604082015260600190565b600e81527f494e56414c49445f414d4f554e54000000000000000000000000000000000000602082015260400190565b805160808301906122f9848261232e565b50602082015161230c602085018261232e565b50604082015161231f604085018261232e565b50606082015161161560608501825b611f4a81612559565b602081016107bd8284611f41565b606081016123538286611f41565b6123606020830185611f41565b611d03604083018461232e565b6040810161237b8285611f41565b6110e0602083018461232e565b602080825281016107bd81611f50565b602080825281016107bd81611fa6565b602080825281016107bd81611fd6565b602080825281016107bd81612006565b602080825281016107bd81612036565b602080825281016107bd8161208c565b602080825281016107bd816120bc565b602080825281016107bd816120ec565b602080825281016107bd8161211c565b602080825281016107bd8161214c565b602080825281016107bd8161217c565b602080825281016107bd816121ac565b602080825281016107bd816121dc565b602080825281016107bd8161220c565b602080825281016107bd81612262565b602080825281016107bd816122b8565b610100810161249782856122e8565b6110e060808301846122e8565b602081016107bd828461232e565b60405181810167ffffffffffffffff811182821017156124d157600080fd5b604052919050565b600067ffffffffffffffff8211156124f057600080fd5b5060209081020190565b600067ffffffffffffffff82111561251157600080fd5b506020601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160190565b73ffffffffffffffffffffffffffffffffffffffff1690565b90565b828183375060009101525600a265627a7a72305820d9f418f11e0f91f06f6f9d22924be0add925495eeb76a6388b5417adb505eeb36c6578706572696d656e74616cf5003700000000000000000000000048bacb9266a570d521063ef5dd96e61686dbe788000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e808200000000000000000000000000000000000000000000000000000000", + "value": "0x0" + }, + "blockHash": "0x6456fbd35a3a69a1709c324fad114d68507d2c8ab391e9adb128f9734c8e4ae8", + "blockNumber": 13536, + "result": { + "address": "0x6000eca38b8b5bba64986182fe2a69c57f6b5414", + "code": "0x60806040526004361061006c5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166318978e8281146100c8578063630f1e6c146100f25780638da5cb5b146101125780639395525c14610134578063f2fde38b14610147575b60025473ffffffffffffffffffffffffffffffffffffffff1633146100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612388565b60405180910390fd5b005b6100db6100d6366004611df1565b610167565b6040516100e9929190612488565b60405180910390f35b3480156100fe57600080fd5b506100c661010d366004611eec565b6102f7565b34801561011e57600080fd5b50610127610388565b6040516100e99190612337565b6100db610142366004611d0b565b6103a4565b34801561015357600080fd5b506100c6610162366004611ce5565b61050a565b61016f6119fa565b6101776119fa565b6000806101826105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610100600188161502019095169490940493840181900481028201810190925282815261025c939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b820191906000526020600020905b81548152906001019060200180831161021057829003601f168201915b50505050508c600081518110151561024157fe5b6020908102909101015161014001519063ffffffff61069616565b156102875761026c8b8b8b6107c3565b935061028084600001518560600151610ac1565b90506102ae565b6102928b8b8b610b03565b9350836060015191506102a68883896107c3565b845190935090505b6102c2846020015184602001518888610d15565b6102e98b60008151811015156102d457fe5b90602001906020020151610140015182610f29565b505097509795505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610348576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612438565b61038383838080601f01602080910402602001604051908101604052809392919081815260200183838082843750879450610f299350505050565b505050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b6103ac6119fa565b6103b46119fa565b60008060006103c16105bb565b60048054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152610441939092909183018282801561022d5780601f106102025761010080835404028352916020019161022d565b156104925761046a670de0b6b3a7640000610464670de0b6b3a76400008a611045565b3461108f565b92506104778b848c6110e7565b945061048b85600001518660600151610ac1565b90506104d6565b6104ad670d2f13f7789f0000670de0b6b3a76400003461108f565b92506104ba8b848c6110e7565b9450846060015191506104ce89838a6107c3565b855190945090505b6104ea856020015185602001518989610d15565b6104fc8b60008151811015156102d457fe5b505050965096945050505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461055b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612438565b73ffffffffffffffffffffffffffffffffffffffff8116156105b857600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b600034116105f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612398565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004016000604051808303818588803b15801561067b57600080fd5b505af115801561068f573d6000803e3d6000fd5b5050505050565b6000815183511480156107ba5750816040518082805190602001908083835b602083106106f257805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016106b5565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0180199092169116179052604051919093018190038120885190955088945090928392508401908083835b6020831061078757805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161074a565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902060001916145b90505b92915050565b6107cb6119fa565b60608060008060008060006107de6119fa565b8a15156107ea57610ab2565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152929183018282801561088e5780601f106108635761010080835404028352916020019161088e565b820191906000526020600020905b81548152906001019060200180831161087157829003601f168201915b505060058054604080516020601f60027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101006001881615020190951694909404938401819004810282018101909252828152969e509194509250840190508282801561093d5780601f106109125761010080835404028352916020019161093d565b820191906000526020600020905b81548152906001019060200180831161092057829003601f168201915b50505050509650600095508b519450600093505b838514610a7857878c8581518110151561096757fe5b6020908102909101015161014001528b5187908d908690811061098657fe5b60209081029091010151610160015261099f8b87610ac1565b9250610a068c858151811015156109b257fe5b9060200190602002015160a00151610a008e878151811015156109d157fe5b90602001906020020151608001518f888151811015156109ed57fe5b9060200190602002015160e00151610ac1565b8561128b565b9150610a418c85815181101515610a1957fe5b90602001906020020151838c87815181101515610a3257fe5b906020019060200201516112e6565b9050610a4d898261135e565b610a5f89600001518a60600151610ac1565b95508a8610610a6d57610a78565b600190930192610951565b8a861015610ab2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612418565b50505050505050509392505050565b600082821115610afd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123b8565b50900390565b610b0b6119fa565b606080600080600080610b1c6119fa565b60008b6000815181101515610b2d57fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929b5092909190830182828015610be55780601f10610bba57610100808354040283529160200191610be5565b820191906000526020600020905b815481529060010190602001808311610bc857829003601f168201915b505050505096508b519550600094505b848614610cdb57878c86815181101515610c0b57fe5b6020908102909101015161014001528b5187908d9087908110610c2a57fe5b6020908102909101015161016001528851610c46908c90610ac1565b9350610c898c86815181101515610c5957fe5b9060200190602002015160a001518d87815181101515610c7557fe5b90602001906020020151608001518661128b565b9250610cb58c86815181101515610c9c57fe5b90602001906020020151848c88815181101515610a3257fe5b9150610cc1898361135e565b5087518a8110610cd057610cdb565b600190940193610bf5565b8a811015610ab2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612418565b600080808066b1a2bc2ec50000861115610d5b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612448565b610d658888611045565b935034841115610da1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123a8565b610dab3485610ac1565b9250610dc086670de0b6b3a76400008a61108f565b915082821115610dfc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612428565b6000831115610f1f576002546040517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911690632e1a7d4d90610e5b9086906004016124a4565b600060405180830381600087803b158015610e7557600080fd5b505af1158015610e89573d6000803e3d6000fd5b505050506000821115610edb5760405173ffffffffffffffffffffffffffffffffffffffff86169083156108fc029084906000818181858888f19350505050158015610ed9573d6000803e3d6000fd5b505b610ee58383610ac1565b90506000811115610f1f57604051339082156108fc029083906000818181858888f19350505050158015610f1d573d6000803e3d6000fd5b505b5050505050505050565b6000610f3b838263ffffffff6113c016565b604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130190209091507fffffffff0000000000000000000000000000000000000000000000000000000080831691161415610fab57610fa6838361142d565b610383565b604080517f455243373231546f6b656e28616464726573732c75696e7432353629000000008152905190819003601c0190207fffffffff000000000000000000000000000000000000000000000000000000008281169116141561101357610fa6838361161b565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123f8565b600082820183811015611084576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e8565b8091505b5092915050565b60008083116110ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d8565b6110dd6110d78584611703565b8461175e565b90505b9392505050565b6110ef6119fa565b60608060008060006110ff6119fa565b89600081518110151561110e57fe5b6020908102919091018101516101400151600580546040805160026001841615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190931692909204601f8101869004860283018601909152808252929950929091908301828280156111c65780601f1061119b576101008083540402835291602001916111c6565b820191906000526020600020905b8154815290600101906020018083116111a957829003601f168201915b5050505050945089519350600092505b82841461127e57858a848151811015156111ec57fe5b602090810290910101516101400152895185908b908590811061120b57fe5b90602001906020020151610160018190525061122b898860200151610ac1565b91506112578a8481518110151561123e57fe5b90602001906020020151838a86815181101515610a3257fe5b9050611263878261135e565b602087015189116112735761127e565b6001909201916111d6565b5050505050509392505050565b60008083116112c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123d8565b6110dd6110d76112d68685611703565b6112e1866001610ac1565b611045565b6112ee6119fa565b606060006112fd868686611775565b600154815191935073ffffffffffffffffffffffffffffffffffffffff1691506080908390602082016000855af1801561135457825184526020830151602085015260408301516040850152606083015160608501525b5050509392505050565b8151815161136c9190611045565b8252602080830151908201516113829190611045565b60208301526040808301519082015161139b9190611045565b6040830152606080830151908201516113b49190611045565b60609092019190915250565b600081600401835110151515611402576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612468565b5001602001517fffffffff000000000000000000000000000000000000000000000000000000001690565b60008061144184601063ffffffff61194716565b604080517f7472616e7366657228616464726573732c75696e7432353629000000000000008152905190819003601901812091935073ffffffffffffffffffffffffffffffffffffffff8416919061149f903390879060240161236d565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931783525181519192909182919080838360005b8381101561154357818101518382015260200161152b565b50505050905090810190601f1680156115705780820380516001836020036101000a031916815260200191505b509150506000604051808303816000865af1925050508015156115bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612408565b3d156115dc575060003d602014156115dc5760206000803e506000515b801515611615576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612408565b50505050565b60008060018314611658576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612478565b61166984601063ffffffff61194716565b915061167c84602463ffffffff6119a816565b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815290915073ffffffffffffffffffffffffffffffffffffffff8316906323b872dd906116d590309033908690600401612345565b600060405180830381600087803b1580156116ef57600080fd5b505af1158015610f1f573d6000803e3d6000fd5b6000808315156117165760009150611088565b5082820282848281151561172657fe5b0414611084576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123e8565b600080828481151561176c57fe5b04949350505050565b604080517fb4be83d5000000000000000000000000000000000000000000000000000000006020808301919091526060602483018181528751608485019081528884015160a48601529488015160c48501529087015160e4840152608087015161010484015260a087015161012484015260c087015161014484015260e08701516101648401526101008701516101848401526101208701516101a4840152610140870180516101c485019081526101608901516101e4860152610180905251805161020485018190529394919384936044870192849261022489019291820191601f82010460005b8181101561187c57835185526020948501949093019260010161185e565b50505050818103610160808401919091528a0151805180835260209283019291820191601f82010460005b818110156118c55783518552602094850194909301926001016118a7565b50505089845250848103602093840190815288518083529093918201918981019190601f82010460005b8181101561190d5783518552602094850194909301926001016118ef565b5050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08883030188525060405250505050509392505050565b600081601401835110151515611989576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd90612458565b50016014015173ffffffffffffffffffffffffffffffffffffffff1690565b60006107ba83836000816020018351101515156119f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906123c8565b50016020015190565b608060405190810160405280600081526020016000815260200160008152602001600081525090565b60006107ba8235612540565b6000601f82018313611a4057600080fd5b8135611a53611a4e826124d9565b6124b2565b81815260209384019390925082018360005b83811015611a915781358601611a7b8882611b41565b8452506020928301929190910190600101611a65565b5050505092915050565b6000601f82018313611aac57600080fd5b8135611aba611a4e826124d9565b81815260209384019390925082018360005b83811015611a915781358601611ae28882611b90565b8452506020928301929190910190600101611acc565b600080601f83018413611b0a57600080fd5b50813567ffffffffffffffff811115611b2257600080fd5b602083019150836001820283011115611b3a57600080fd5b9250929050565b6000601f82018313611b5257600080fd5b8135611b60611a4e826124fa565b91508082526020830160208301858383011115611b7c57600080fd5b611b8783828461255c565b50505092915050565b60006101808284031215611ba357600080fd5b611bae6101806124b2565b90506000611bbc8484611a23565b8252506020611bcd84848301611a23565b6020830152506040611be184828501611a23565b6040830152506060611bf584828501611a23565b6060830152506080611c0984828501611cd9565b60808301525060a0611c1d84828501611cd9565b60a08301525060c0611c3184828501611cd9565b60c08301525060e0611c4584828501611cd9565b60e083015250610100611c5a84828501611cd9565b61010083015250610120611c7084828501611cd9565b6101208301525061014082013567ffffffffffffffff811115611c9257600080fd5b611c9e84828501611b41565b6101408301525061016082013567ffffffffffffffff811115611cc057600080fd5b611ccc84828501611b41565b6101608301525092915050565b60006107ba8235612559565b600060208284031215611cf757600080fd5b6000611d038484611a23565b949350505050565b60008060008060008060c08789031215611d2457600080fd5b863567ffffffffffffffff811115611d3b57600080fd5b611d4789828a01611a9b565b965050602087013567ffffffffffffffff811115611d6457600080fd5b611d7089828a01611a2f565b955050604087013567ffffffffffffffff811115611d8d57600080fd5b611d9989828a01611a9b565b945050606087013567ffffffffffffffff811115611db657600080fd5b611dc289828a01611a2f565b9350506080611dd389828a01611cd9565b92505060a0611de489828a01611a23565b9150509295509295509295565b600080600080600080600060e0888a031215611e0c57600080fd5b873567ffffffffffffffff811115611e2357600080fd5b611e2f8a828b01611a9b565b9750506020611e408a828b01611cd9565b965050604088013567ffffffffffffffff811115611e5d57600080fd5b611e698a828b01611a2f565b955050606088013567ffffffffffffffff811115611e8657600080fd5b611e928a828b01611a9b565b945050608088013567ffffffffffffffff811115611eaf57600080fd5b611ebb8a828b01611a2f565b93505060a0611ecc8a828b01611cd9565b92505060c0611edd8a828b01611a23565b91505092959891949750929550565b600080600060408486031215611f0157600080fd5b833567ffffffffffffffff811115611f1857600080fd5b611f2486828701611af8565b93509350506020611f3786828701611cd9565b9150509250925092565b611f4a81612540565b82525050565b602381527f44454641554c545f46554e4354494f4e5f574554485f434f4e54524143545f4f60208201527f4e4c590000000000000000000000000000000000000000000000000000000000604082015260600190565b601181527f494e56414c49445f4d53475f56414c5545000000000000000000000000000000602082015260400190565b600d81527f4f564552534f4c445f5745544800000000000000000000000000000000000000602082015260400190565b601181527f55494e543235365f554e444552464c4f57000000000000000000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f33325f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b601081527f4449564953494f4e5f42595f5a45524f00000000000000000000000000000000602082015260400190565b601081527f55494e543235365f4f564552464c4f5700000000000000000000000000000000602082015260400190565b601781527f554e535550504f525445445f41535345545f50524f5859000000000000000000602082015260400190565b600f81527f5452414e534645525f4641494c45440000000000000000000000000000000000602082015260400190565b601481527f434f4d504c4554455f46494c4c5f4641494c4544000000000000000000000000602082015260400190565b601a81527f494e53554646494349454e545f4554485f52454d41494e494e47000000000000602082015260400190565b601381527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000602082015260400190565b601881527f4645455f50455243454e544147455f544f4f5f4c415247450000000000000000602082015260400190565b602681527f475245415445525f4f525f455155414c5f544f5f32305f4c454e4754485f524560208201527f5155495245440000000000000000000000000000000000000000000000000000604082015260600190565b602581527f475245415445525f4f525f455155414c5f544f5f345f4c454e4754485f52455160208201527f5549524544000000000000000000000000000000000000000000000000000000604082015260600190565b600e81527f494e56414c49445f414d4f554e54000000000000000000000000000000000000602082015260400190565b805160808301906122f9848261232e565b50602082015161230c602085018261232e565b50604082015161231f604085018261232e565b50606082015161161560608501825b611f4a81612559565b602081016107bd8284611f41565b606081016123538286611f41565b6123606020830185611f41565b611d03604083018461232e565b6040810161237b8285611f41565b6110e0602083018461232e565b602080825281016107bd81611f50565b602080825281016107bd81611fa6565b602080825281016107bd81611fd6565b602080825281016107bd81612006565b602080825281016107bd81612036565b602080825281016107bd8161208c565b602080825281016107bd816120bc565b602080825281016107bd816120ec565b602080825281016107bd8161211c565b602080825281016107bd8161214c565b602080825281016107bd8161217c565b602080825281016107bd816121ac565b602080825281016107bd816121dc565b602080825281016107bd8161220c565b602080825281016107bd81612262565b602080825281016107bd816122b8565b610100810161249782856122e8565b6110e060808301846122e8565b602081016107bd828461232e565b60405181810167ffffffffffffffff811182821017156124d157600080fd5b604052919050565b600067ffffffffffffffff8211156124f057600080fd5b5060209081020190565b600067ffffffffffffffff82111561251157600080fd5b506020601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160190565b73ffffffffffffffffffffffffffffffffffffffff1690565b90565b828183375060009101525600a265627a7a72305820d9f418f11e0f91f06f6f9d22924be0add925495eeb76a6388b5417adb505eeb36c6578706572696d656e74616cf50037", + "gasUsed": "0x2c8c7f" + }, + "subtraces": 3, + "traceAddress": [], + "transactionHash": "0x6974f745a004f030bebb1c01d4595edbda2fafcf01c0bfbd5d335711e2a7b04e", + "transactionPosition": 0, + "type": "create" + }, + { + "action": { + "callType": "call", + "from": "0x6000eca38b8b5bba64986182fe2a69c57f6b5414", + "gas": "0x1dba84", + "input": "0x60704108f47261b000000000000000000000000000000000000000000000000000000000", + "to": "0x48bacb9266a570d521063ef5dd96e61686dbe788", + "value": "0x0" + }, + "blockHash": "0x6456fbd35a3a69a1709c324fad114d68507d2c8ab391e9adb128f9734c8e4ae8", + "blockNumber": 13536, + "result": { + "gasUsed": "0x3d9", + "output": "0x0000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48" + }, + "subtraces": 0, + "traceAddress": [ + 0 + ], + "transactionHash": "0x6974f745a004f030bebb1c01d4595edbda2fafcf01c0bfbd5d335711e2a7b04e", + "transactionPosition": 0, + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x6000eca38b8b5bba64986182fe2a69c57f6b5414", + "gas": "0x1dad2e", + "input": "0x095ea7b30000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "to": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082", + "value": "0x0" + }, + "blockHash": "0x6456fbd35a3a69a1709c324fad114d68507d2c8ab391e9adb128f9734c8e4ae8", + "blockNumber": 13536, + "result": { + "gasUsed": "0x56c8", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [ + 1 + ], + "transactionHash": "0x6974f745a004f030bebb1c01d4595edbda2fafcf01c0bfbd5d335711e2a7b04e", + "transactionPosition": 0, + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x6000eca38b8b5bba64986182fe2a69c57f6b5414", + "gas": "0x1d4ee1", + "input": "0x095ea7b30000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "to": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c", + "value": "0x0" + }, + "blockHash": "0x6456fbd35a3a69a1709c324fad114d68507d2c8ab391e9adb128f9734c8e4ae8", + "blockNumber": 13536, + "result": { + "gasUsed": "0x56ca", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [ + 2 + ], + "transactionHash": "0x6974f745a004f030bebb1c01d4595edbda2fafcf01c0bfbd5d335711e2a7b04e", + "transactionPosition": 0, + "type": "call" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/oog.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/oog.json new file mode 100644 index 0000000..bd6059f --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/oog.json @@ -0,0 +1,68 @@ +{ + "context": { + "difficulty": "3699098917", + "gasLimit": "5258985", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "number": "2294631", + "timestamp": "1513675366" + }, + "genesis": { + "alloc": { + "0x43064693d3d38ad6a7cb579e0d6d9718c8aa6b62": { + "balance": "0x0", + "code": "0x6060604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100bf578063095ea7b31461014d57806318160ddd146101a757806323b872dd146101d0578063313ce5671461024957806342966c68146102785780635a3b7e42146102b357806370a082311461034157806379cc67901461038e57806395d89b41146103e8578063a9059cbb14610476578063dd62ed3e146104b8575b600080fd5b34156100ca57600080fd5b6100d2610524565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101125780820151818401526020810190506100f7565b50505050905090810190601f16801561013f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015857600080fd5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061055d565b604051808215151515815260200191505060405180910390f35b34156101b257600080fd5b6101ba6105ea565b6040518082815260200191505060405180910390f35b34156101db57600080fd5b61022f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506105f0565b604051808215151515815260200191505060405180910390f35b341561025457600080fd5b61025c610910565b604051808260ff1660ff16815260200191505060405180910390f35b341561028357600080fd5b6102996004808035906020019091905050610915565b604051808215151515815260200191505060405180910390f35b34156102be57600080fd5b6102c6610a18565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103065780820151818401526020810190506102eb565b50505050905090810190601f1680156103335780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561034c57600080fd5b610378600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a51565b6040518082815260200191505060405180910390f35b341561039957600080fd5b6103ce600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610a69565b604051808215151515815260200191505060405180910390f35b34156103f357600080fd5b6103fb610bf8565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561043b578082015181840152602081019050610420565b50505050905090810190601f1680156104685780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561048157600080fd5b6104b6600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610c31565b005b34156104c357600080fd5b61050e600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610e34565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f446f70616d696e6500000000000000000000000000000000000000000000000081525081565b600081600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506001905092915050565b60005481565b6000808373ffffffffffffffffffffffffffffffffffffffff161415151561061757600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561066557600080fd5b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401101515156106f157fe5b600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115151561077c57600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555081600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b601281565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561096557600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5836040518082815260200191505060405180910390a260019050919050565b6040805190810160405280600981526020017f446f706d6e20302e32000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b600081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ab957600080fd5b600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151515610b4457600080fd5b81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160008082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5836040518082815260200191505060405180910390a26001905092915050565b6040805190810160405280600581526020017f444f504d4e00000000000000000000000000000000000000000000000000000081525081565b60008273ffffffffffffffffffffffffffffffffffffffff1614151515610c5757600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ca557600080fd5b600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205481600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020540110151515610d3157fe5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b60026020528160005260406000206020528060005260406000206000915091505054815600a165627a7a723058206d93424f4e7b11929b8276a269038402c10c0ddf21800e999916ddd9dff4a7630029", + "nonce": "1", + "storage": { + "0x296b66049cc4f9c8bf3d4f14752add261d1a980b39bdd194a7897baf39ac7579": "0x0000000000000000000000000000000000000000033b2e3c9fc9653f9e72b1e0" + } + }, + "0x94194bc2aaf494501d7880b61274a169f6502a54": { + "balance": "0xea8c39a876d19888d", + "code": "0x", + "nonce": "265", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3699098917", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "5263953", + "hash": "0x03a0f62a8106793dafcfae7b75fd2654322062d585a19cea568314d7205790dc", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x15482cc64b7c00a947f5bf015dfc010db1a6a668c74df61974d6a7848c174408", + "nonce": "0xd1bdb150f6fd170e", + "number": "2294630", + "stateRoot": "0x1ab1a534e84cc787cda1db21e0d5920ab06017948075b759166cfea7274657a1", + "timestamp": "1513675347", + "totalDifficulty": "7160543502214733" + }, + "input": "0xf8ab820109855d21dba00082ca1d9443064693d3d38ad6a7cb579e0d6d9718c8aa6b6280b844a9059cbb000000000000000000000000e77b1ac803616503510bed0086e3a7be2627a69900000000000000000000000000000000000000000000000000000009502f90001ba0ce3ad83f5530136467b7c2bb225f406bd170f4ad59c254e5103c34eeabb5bd69a0455154527224a42ab405cacf0fe92918a75641ce4152f8db292019a5527aa956", + "result": [ + { + "action": { + "callType": "call", + "from": "0x94194bc2aaf494501d7880b61274a169f6502a54", + "gas": "0xca1d", + "input": "0xa9059cbb000000000000000000000000e77b1ac803616503510bed0086e3a7be2627a69900000000000000000000000000000000000000000000000000000009502f9000", + "to": "0x43064693d3d38ad6a7cb579e0d6d9718c8aa6b62", + "value": "0x0" + }, + "blockNumber": 2294631, + "error": "out of gas", + "result": {}, + "subtraces": 0, + "traceAddress": [], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/option_convert_parity_errors.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/option_convert_parity_errors.json new file mode 100644 index 0000000..8888d3e --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/option_convert_parity_errors.json @@ -0,0 +1,71 @@ +{ + "context": { + "difficulty": "3699098917", + "gasLimit": "5258985", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "number": "2294631", + "timestamp": "1513675366" + }, + "genesis": { + "alloc": { + "0x43064693d3d38ad6a7cb579e0d6d9718c8aa6b62": { + "balance": "0x0", + "code": "0x6060604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100bf578063095ea7b31461014d57806318160ddd146101a757806323b872dd146101d0578063313ce5671461024957806342966c68146102785780635a3b7e42146102b357806370a082311461034157806379cc67901461038e57806395d89b41146103e8578063a9059cbb14610476578063dd62ed3e146104b8575b600080fd5b34156100ca57600080fd5b6100d2610524565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101125780820151818401526020810190506100f7565b50505050905090810190601f16801561013f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015857600080fd5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061055d565b604051808215151515815260200191505060405180910390f35b34156101b257600080fd5b6101ba6105ea565b6040518082815260200191505060405180910390f35b34156101db57600080fd5b61022f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506105f0565b604051808215151515815260200191505060405180910390f35b341561025457600080fd5b61025c610910565b604051808260ff1660ff16815260200191505060405180910390f35b341561028357600080fd5b6102996004808035906020019091905050610915565b604051808215151515815260200191505060405180910390f35b34156102be57600080fd5b6102c6610a18565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103065780820151818401526020810190506102eb565b50505050905090810190601f1680156103335780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561034c57600080fd5b610378600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a51565b6040518082815260200191505060405180910390f35b341561039957600080fd5b6103ce600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610a69565b604051808215151515815260200191505060405180910390f35b34156103f357600080fd5b6103fb610bf8565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561043b578082015181840152602081019050610420565b50505050905090810190601f1680156104685780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561048157600080fd5b6104b6600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610c31565b005b34156104c357600080fd5b61050e600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610e34565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f446f70616d696e6500000000000000000000000000000000000000000000000081525081565b600081600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506001905092915050565b60005481565b6000808373ffffffffffffffffffffffffffffffffffffffff161415151561061757600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561066557600080fd5b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401101515156106f157fe5b600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115151561077c57600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555081600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b601281565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561096557600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5836040518082815260200191505060405180910390a260019050919050565b6040805190810160405280600981526020017f446f706d6e20302e32000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b600081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ab957600080fd5b600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151515610b4457600080fd5b81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160008082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5836040518082815260200191505060405180910390a26001905092915050565b6040805190810160405280600581526020017f444f504d4e00000000000000000000000000000000000000000000000000000081525081565b60008273ffffffffffffffffffffffffffffffffffffffff1614151515610c5757600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ca557600080fd5b600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205481600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020540110151515610d3157fe5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b60026020528160005260406000206020528060005260406000206000915091505054815600a165627a7a723058206d93424f4e7b11929b8276a269038402c10c0ddf21800e999916ddd9dff4a7630029", + "nonce": "1", + "storage": { + "0x296b66049cc4f9c8bf3d4f14752add261d1a980b39bdd194a7897baf39ac7579": "0x0000000000000000000000000000000000000000033b2e3c9fc9653f9e72b1e0" + } + }, + "0x94194bc2aaf494501d7880b61274a169f6502a54": { + "balance": "0xea8c39a876d19888d", + "code": "0x", + "nonce": "265", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3699098917", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "5263953", + "hash": "0x03a0f62a8106793dafcfae7b75fd2654322062d585a19cea568314d7205790dc", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x15482cc64b7c00a947f5bf015dfc010db1a6a668c74df61974d6a7848c174408", + "nonce": "0xd1bdb150f6fd170e", + "number": "2294630", + "stateRoot": "0x1ab1a534e84cc787cda1db21e0d5920ab06017948075b759166cfea7274657a1", + "timestamp": "1513675347", + "totalDifficulty": "7160543502214733" + }, + "tracerConfig": { + "convertParityErrors": true + }, + "input": "0xf8ab820109855d21dba00082ca1d9443064693d3d38ad6a7cb579e0d6d9718c8aa6b6280b844a9059cbb000000000000000000000000e77b1ac803616503510bed0086e3a7be2627a69900000000000000000000000000000000000000000000000000000009502f90001ba0ce3ad83f5530136467b7c2bb225f406bd170f4ad59c254e5103c34eeabb5bd69a0455154527224a42ab405cacf0fe92918a75641ce4152f8db292019a5527aa956", + "result": [ + { + "action": { + "callType": "call", + "from": "0x94194bc2aaf494501d7880b61274a169f6502a54", + "gas": "0xca1d", + "input": "0xa9059cbb000000000000000000000000e77b1ac803616503510bed0086e3a7be2627a69900000000000000000000000000000000000000000000000000000009502f9000", + "to": "0x43064693d3d38ad6a7cb579e0d6d9718c8aa6b62", + "value": "0x0" + }, + "blockNumber": 2294631, + "error": "Out of gas", + "result": {}, + "subtraces": 0, + "traceAddress": [], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/result_output.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/result_output.json new file mode 100644 index 0000000..62baf33 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/result_output.json @@ -0,0 +1,111 @@ +{ + "genesis": { + "difficulty": "1911202", + "extraData": "0xd883010906846765746888676f312e31332e35856c696e7578", + "gasLimit": "7842876", + "hash": "0x4d7bc82e0d56307094378e1a8fbfa6260986f621de95b5fe68a95248b3ba8efe", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "mixHash": "0xc102ad52677c391edab82cc895ca7a7e9fff3eed4fa966ecf7fb61ec1e84bb6b", + "nonce": "0x39f5b074e3437f3f", + "number": "553415", + "stateRoot": "0x8f89e79109c19fa00e72b400502448540dc4773ad92dddd341dbba20c710a3b5", + "timestamp": "1577396195", + "totalDifficulty": "458361299240", + "alloc": { + "0x531f76bad925f6a925474996c7d738c1008045f6": { + "balance": "0x0", + "nonce": "1", + "code": "0x6060604052361561008a576000357c01000000000000000000000000000000000000000000000000000000009004806301cb3b20146102bf57806329dcb0cf146102cc57806338af3eed146102ed5780636e66f6e9146103245780637a3a0e841461035b5780637b3e5e7b1461037c578063a035b1fe1461039d578063dc0d3dff146103be5761008a565b6102bd5b60003490506040604051908101604052803381526020018281526020015060066000506006600050805480919060010190908154818355818115116101365760020281600202836000526020600020918201910161013591906100ec565b808211156101315760006000820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001820160005060009055506001016100ec565b5090565b5b505050815481101561000257906000526020600020906002020160005060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff0219169083021790555060208201518160010160005055905050806002600082828250540192505081905550600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166390b98a11336004600050548404604051837c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506020604051808303816000876161da5a03f1156100025750505060405151507fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf633826001604051808473ffffffffffffffffffffffffffffffffffffffff168152602001838152602001828152602001935050505060405180910390a15b50565b005b6102ca6004506104c8565b005b6102d760045061043a565b6040518082815260200191505060405180910390f35b6102f8600450610402565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61032f60045061044c565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610366600450610428565b6040518082815260200191505060405180910390f35b610387600450610431565b6040518082815260200191505060405180910390f35b6103a8600450610443565b6040518082815260200191505060405180910390f35b6103cf600480359060200150610472565b604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60016000505481565b60026000505481565b60036000505481565b60046000505481565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60066000508181548110156100025790600052602060002090600202016000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010160005054905082565b6000600360005054421015156107d8576001600050546002600050541015156105cf57600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166000600260005054604051809050600060405180830381858888f19350505050507fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf6600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166002600050546000604051808473ffffffffffffffffffffffffffffffffffffffff168152602001838152602001828152602001935050505060405180910390a161079d565b7fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf66000600b600060405180848152602001838152602001828152602001935050505060405180910390a1600090505b60066000505481101561079c57600660005081815481101561000257906000526020600020906002020160005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166000600660005083815481101561000257906000526020600020906002020160005060010160005054604051809050600060405180830381858888f19350505050507fe842aea7a5f1b01049d752008c53c52890b1a6daf660cf39e8eec506112bbdf6600660005082815481101561000257906000526020600020906002020160005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff166006600050838154811015610002579060005260206000209060020201600050600101600050546000604051808473ffffffffffffffffffffffffffffffffffffffff168152602001838152602001828152602001935050505060405180910390a15b806001019050805061061e565b5b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b5056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d40": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x000000000000000000000000b49180d443dc4ca6028de0031ac09337891fd8ce", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" + } + }, + "0xb49180d443dc4ca6028de0031ac09337891fd8ce": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x193e9986e2e3f0c58988", + "nonce": "2585", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "553416", + "difficulty": "1909336", + "timestamp": "1577396224", + "gasLimit": "7835218", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf870820a1985e8d4a5100083040b2894531f76bad925f6a925474996c7d738c1008045f6880de0b6b3a76400008081a2a08693170f040d9501b831b404d9e40fba040c5aef4b8974aedc20b3844aea7c32a0476861058ff9b8030c58bcba8be320acc855e4694a633c493fb50fbdb9455489", + "result": [ + { + "type": "call", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "to": "0x531f76bad925f6a925474996c7d738c1008045f6", + "value": "0xde0b6b3a7640000", + "gas": "0x40b28", + "input": "0x", + "callType": "call" + }, + "result": { + "gasUsed": "0x19c3e", + "output": "0x" + }, + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 5, + "transactionHash": "0x04d2029a5cbbed30969cdc0a2ca9e9fc6b719e323af0802b52466f07ee0ecada", + "blockNumber": 553416, + "blockHash": "0x8df024322173d225a09681d35edeaa528aca60743a11a70f854c158862bf5282", + "time": "617.42µs" + }, + { + "type": "call", + "action": { + "from": "0x531f76bad925f6a925474996c7d738c1008045f6", + "to": "0xb49180d443dc4ca6028de0031ac09337891fd8ce", + "value": "0x0", + "gas": "0x2164e", + "input": "0x90b98a11000000000000000000000000877bd459c9b7d8576b44e59e09d076c25946f4430000000000000000000000000000000000000000000000000000000000000001", + "callType": "call" + }, + "result": { + "gasUsed": "0x0", + "output": "0x" + }, + "traceAddress": [ + 0 + ], + "subtraces": 0, + "transactionPosition": 5, + "transactionHash": "0x04d2029a5cbbed30969cdc0a2ca9e9fc6b719e323af0802b52466f07ee0ecada", + "blockNumber": 553416, + "blockHash": "0x8df024322173d225a09681d35edeaa528aca60743a11a70f854c158862bf5282" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/revert.json new file mode 100644 index 0000000..b0346d8 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/revert.json @@ -0,0 +1,68 @@ +{ + "context": { + "difficulty": "3665057456", + "gasLimit": "5232723", + "miner": "0xf4d8e706cfb25c0decbbdd4d2e2cc10c66376a3f", + "number": "2294501", + "timestamp": "1513673601" + }, + "genesis": { + "alloc": { + "0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9": { + "balance": "0x2a3fc32bcc019283", + "code": "0x", + "nonce": "10", + "storage": {} + }, + "0xabbcd5b340c80b5f1c0545c04c987b87310296ae": { + "balance": "0x0", + "code": "0x606060405236156100755763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416632d0335ab811461007a578063548db174146100ab5780637f649783146100fc578063b092145e1461014d578063c3f44c0a14610186578063c47cf5de14610203575b600080fd5b341561008557600080fd5b610099600160a060020a0360043516610270565b60405190815260200160405180910390f35b34156100b657600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061028f95505050505050565b005b341561010757600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061029e95505050505050565b005b341561015857600080fd5b610172600160a060020a03600435811690602435166102ad565b604051901515815260200160405180910390f35b341561019157600080fd5b6100fa6004803560ff1690602480359160443591606435600160a060020a0316919060a49060843590810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965050509235600160a060020a031692506102cd915050565b005b341561020e57600080fd5b61025460046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061056a95505050505050565b604051600160a060020a03909116815260200160405180910390f35b600160a060020a0381166000908152602081905260409020545b919050565b61029a816000610594565b5b50565b61029a816001610594565b5b50565b600160209081526000928352604080842090915290825290205460ff1681565b60008080600160a060020a038416158061030d5750600160a060020a038085166000908152600160209081526040808320339094168352929052205460ff165b151561031857600080fd5b6103218561056a565b600160a060020a038116600090815260208190526040808220549295507f19000000000000000000000000000000000000000000000000000000000000009230918891908b908b90517fff000000000000000000000000000000000000000000000000000000000000008089168252871660018201526c01000000000000000000000000600160a060020a038088168202600284015286811682026016840152602a8301869052841602604a820152605e810182805190602001908083835b6020831061040057805182525b601f1990920191602091820191016103e0565b6001836020036101000a0380198251168184511617909252505050919091019850604097505050505050505051809103902091506001828a8a8a6040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f1151561049957600080fd5b5050602060405103519050600160a060020a03838116908216146104bc57600080fd5b600160a060020a0380841660009081526020819052604090819020805460010190559087169086905180828051906020019080838360005b8381101561050d5780820151818401525b6020016104f4565b50505050905090810190601f16801561053a5780820380516001836020036101000a031916815260200191505b5091505060006040518083038160008661646e5a03f1915050151561055e57600080fd5b5b505050505050505050565b600060248251101561057e5750600061028a565b600160a060020a0360248301511690505b919050565b60005b825181101561060157600160a060020a033316600090815260016020526040812083918584815181106105c657fe5b90602001906020020151600160a060020a031681526020810191909152604001600020805460ff19169115159190911790555b600101610597565b5b5050505600a165627a7a723058200027e8b695e9d2dea9f3629519022a69f3a1d23055ce86406e686ea54f31ee9c0029", + "nonce": "1", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3672229776", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "5227619", + "hash": "0xa07b3d6c6bf63f5f981016db9f2d1d93033833f2c17e8bf7209e85f1faf08076", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x806e151ce2817be922e93e8d5921fa0f0d0fd213d6b2b9a3fa17458e74a163d0", + "nonce": "0xbc5d43adc2c30c7d", + "number": "2294500", + "stateRoot": "0xca645b335888352ef9d8b1ef083e9019648180b259026572e3139717270de97d", + "timestamp": "1513673552", + "totalDifficulty": "7160066586979149" + }, + "input": "0xf9018b0a8505d21dba00832dc6c094abbcd5b340c80b5f1c0545c04c987b87310296ae80b9012473b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988000000000000000000000000000000000000000000000000000000000000000000000000000000001ba0fd659d76a4edbd2a823e324c93f78ad6803b30ff4a9c8bce71ba82798975c70ca06571eecc0b765688ec6c78942c5ee8b585e00988c0141b518287e9be919bc48a", + "result": [ + { + "action": { + "callType": "call", + "from": "0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9", + "gas": "0x2dc6c0", + "input": "0x73b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a98800000000000000000000000000000000000000000000000000000000000000000000000000000000", + "to": "0xabbcd5b340c80b5f1c0545c04c987b87310296ae", + "value": "0x0" + }, + "blockNumber": 2294501, + "error": "execution reverted", + "result": { + "gasUsed": "0x719b" + }, + "subtraces": 0, + "traceAddress": [], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/revert_reason.json new file mode 100644 index 0000000..6759b05 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/revert_reason.json @@ -0,0 +1,74 @@ +{ + "context": { + "difficulty": "2", + "gasLimit": "8000000", + "miner": "0x0000000000000000000000000000000000000000", + "number": "3212651", + "timestamp": "1597246515" + }, + "genesis": { + "alloc": { + "0xf58833cf0c791881b494eb79d461e08a1f043f52": { + "balance": "0x0", + "code": "0x608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063609ff1bd11610078578063609ff1bd146101af5780639e7b8d61146101cd578063a3ec138d14610211578063e2ba53f0146102ae576100a5565b80630121b93f146100aa578063013cf08b146100d85780632e4176cf146101215780635c19a95c1461016b575b600080fd5b6100d6600480360360208110156100c057600080fd5b81019080803590602001909291905050506102cc565b005b610104600480360360208110156100ee57600080fd5b8101908080359060200190929190505050610469565b604051808381526020018281526020019250505060405180910390f35b61012961049a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101ad6004803603602081101561018157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506104bf565b005b6101b76108db565b6040518082815260200191505060405180910390f35b61020f600480360360208110156101e357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610952565b005b6102536004803603602081101561022757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b53565b60405180858152602001841515151581526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200194505050505060405180910390f35b6102b6610bb0565b6040518082815260200191505060405180910390f35b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160000154141561038a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f486173206e6f20726967687420746f20766f746500000000000000000000000081525060200191505060405180910390fd5b8060010160009054906101000a900460ff161561040f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f416c726561647920766f7465642e00000000000000000000000000000000000081525060200191505060405180910390fd5b60018160010160006101000a81548160ff02191690831515021790555081816002018190555080600001546002838154811061044757fe5b9060005260206000209060020201600101600082825401925050819055505050565b6002818154811061047657fe5b90600052602060002090600202016000915090508060000154908060010154905082565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060010160009054906101000a900460ff1615610587576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f596f7520616c726561647920766f7465642e000000000000000000000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610629576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e000081525060200191505060405180910390fd5b5b600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146107cc57600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691503373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156107c7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f466f756e64206c6f6f7020696e2064656c65676174696f6e2e0000000000000081525060200191505060405180910390fd5b61062a565b60018160010160006101000a81548160ff021916908315150217905550818160010160016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060010160009054906101000a900460ff16156108bf578160000154600282600201548154811061089c57fe5b9060005260206000209060020201600101600082825401925050819055506108d6565b816000015481600001600082825401925050819055505b505050565b6000806000905060008090505b60028054905081101561094d57816002828154811061090357fe5b9060005260206000209060020201600101541115610940576002818154811061092857fe5b90600052602060002090600202016001015491508092505b80806001019150506108e8565b505090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146109f7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180610bde6028913960400191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160009054906101000a900460ff1615610aba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f54686520766f74657220616c726561647920766f7465642e000000000000000081525060200191505060405180910390fd5b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000015414610b0957600080fd5b60018060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018190555050565b60016020528060005260406000206000915090508060000154908060010160009054906101000a900460ff16908060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060020154905084565b60006002610bbc6108db565b81548110610bc657fe5b90600052602060002090600202016000015490509056fe4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652ea26469706673582212201d282819f8f06fed792100d60a8b08809b081a34a1ecd225e83a4b41122165ed64736f6c63430006060033", + "nonce": "1", + "storage": { + "0x6200beec95762de01ce05f2a0e58ce3299dbb53c68c9f3254a242121223cdf58": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0xf7579c3d8a669c89d5ed246a22eb6db8f6fedbf1": { + "balance": "0x57af9d6b3df812900", + "code": "0x", + "nonce": "6", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "IstanbulBlock": 1561651, + "chainId": 5, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf888068449504f80832dc6c094f58833cf0c791881b494eb79d461e08a1f043f5280a45c19a95c000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf12da0264664db3e71fae1dbdaf2f53954be149ad3b7ba8a5054b4d89c70febfacc8b1a0212e8398757963f419681839ae8c5a54b411e252473c82d93dda68405ca63294", + "result": [ + { + "action": { + "callType": "call", + "from": "0xf7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "gas": "0x2dc6c0", + "input": "0x5c19a95c000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "to": "0xf58833cf0c791881b494eb79d461e08a1f043f52", + "value": "0x0" + }, + "blockNumber": 3212651, + "error": "execution reverted", + "result": { + "gasUsed": "0x5940", + "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e0000" + }, + "subtraces": 0, + "traceAddress": [], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/selfdestruct.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/selfdestruct.json new file mode 100644 index 0000000..3c5d6d9 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/selfdestruct.json @@ -0,0 +1,104 @@ +{ + "genesis": { + "difficulty": "4628640", + "extraData": "0xd883010b05846765746888676f312e31342e33856c696e7578", + "gasLimit": "9244120", + "hash": "0x5a1f551897cc91265225b0453136ad8c7eef1c1c8b06139da4f2e6e710c1f4df", + "miner": "0x73f26d124436b0791169d63a3af29c2ae47765a3", + "mixHash": "0xd6735e63f8937fe0c5491e0d5836ec28467363be7ada5a2f979f9d107e2c831e", + "nonce": "0x7c35e34d2e328d7d", + "number": "1555145", + "stateRoot": "0x565873b05f71b98595133e37a52d79c3476ce820c05ebedaddd35541b0e894a3", + "timestamp": "1590793819", + "totalDifficulty": "2241994078605", + "alloc": { + "0x119f569a45e9d0089d51d7f9529f5ea9bf5785e2": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x622e8fced69d43eb8d97", + "nonce": "260140", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "1555146", + "difficulty": "4630900", + "timestamp": "1590793820", + "gasLimit": "9253146", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf8628303f82c843b9aca0083019ecc80808e605a600053600160006001f0ff0081a2a077f539ae2a58746bbfa6370fc423f946870efa32753d697d3729d361a428623aa0384ef9a5650d6630f5c1ddef616bffa5fc72a95a9314361d0918de066aa4475a", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x19ecc", + "init": "0x605a600053600160006001f0ff00" + }, + "result": { + "gasUsed": "0x102a1", + "code": "0x", + "address": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca" + }, + "traceAddress": [], + "subtraces": 2, + "transactionPosition": 14, + "transactionHash": "0xdd76f02407e2f8329303ba688e111cae4f7008ad0d14d6e42c5698424ea36d79", + "blockNumber": 1555146, + "blockHash": "0xafb4f1dd27b9054c805acb81a88ed04384788cb31d84164c21874935c81e5c7e", + "time": "187.145µs" + }, + { + "action": { + "from": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca", + "gas": "0x50ac", + "init": "0x5a", + "value": "0x1" + }, + "error": "insufficient balance for transfer", + "result": {}, + "subtraces": 0, + "traceAddress": [0], + "type": "create" + }, + { + "type": "suicide", + "action": { + "address": "0x1d99a1a3efa9181f540f9e24fa6e4e08eb7844ca", + "refundAddress": "0x0000000000000000000000000000000000000000", + "balance": "0x0" + }, + "result": null, + "traceAddress": [ + 1 + ], + "subtraces": 0, + "transactionPosition": 14, + "transactionHash": "0xdd76f02407e2f8329303ba688e111cae4f7008ad0d14d6e42c5698424ea36d79", + "blockNumber": 1555146, + "blockHash": "0xafb4f1dd27b9054c805acb81a88ed04384788cb31d84164c21874935c81e5c7e" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/simple.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/simple.json new file mode 100644 index 0000000..a7244e9 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/simple.json @@ -0,0 +1,97 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "result": [ + { + "action": { + "callType": "call", + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x15f90", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "value": "0x0" + }, + "blockNumber": 2289806, + "result": { + "gasUsed": "0x9751", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 1, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "value": "0x6f05b59d3b20000" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x0", + "output": "0x" + }, + "subtraces": 0, + "traceAddress": [0], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/simple_onlytop.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/simple_onlytop.json new file mode 100644 index 0000000..5fbdf55 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/simple_onlytop.json @@ -0,0 +1,100 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "tracerConfig": { + "onlyTopCall": true + }, + "result": [ + { + "action": { + "callType": "call", + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x15f90", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "value": "0x0" + }, + "blockNumber": 2289806, + "result": { + "gasUsed": "0x9751", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 1, + "traceAddress": [], + "type": "call" + }, + { + "action": { + "callType": "call", + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "gas": "0x6d05", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "value": "0x6f05b59d3b20000" + }, + "blockNumber": 0, + "result": { + "gasUsed": "0x0", + "output": "0x" + }, + "subtraces": 0, + "traceAddress": [0], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/skip_no_balance_error.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/skip_no_balance_error.json new file mode 100644 index 0000000..6911ed4 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/skip_no_balance_error.json @@ -0,0 +1,83 @@ +{ + "genesis": { + "difficulty": "4673862", + "extraData": "0xd683010b05846765746886676f312e3133856c696e7578", + "gasLimit": "9471919", + "hash": "0x7f072150c5905c214966e3432d418910badcdbe510aceaac295b1d7059cc0ffc", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "mixHash": "0x113ced8fedb939fdc862008da7bdddde726f997c0e6dfba0e55613994757b489", + "nonce": "0x0f411a2e5552c5b7", + "number": "1555284", + "stateRoot": "0x9fe125b361b72d5479b24ad9be9964b74228c73a2dfb0065060a79b4a6dfaa1e", + "timestamp": "1590795374", + "totalDifficulty": "2242642335405", + "alloc": { + "0xe85df1413eebe1b191c26260e19783a274a6b041": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x6244c985ef1e48e84531", + "nonce": "265775", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "1555285", + "difficulty": "4676144", + "timestamp": "1590795378", + "gasLimit": "9481167", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf9014083040e2f843b9aca008301aab08080b8eb7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5547f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000037f05581a2a09db45e7846f193471f6d897fb6ff58b7ec41a9c6f63d10aca47d821c365981cba052ec320875625e16141a1a9e8b7993de863698fb699f93ae2cab26149bbb144f", + "result": [ + { + "type": "create", + "action": { + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "value": "0x0", + "gas": "0x1aab0", + "init": "0x7f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b57f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000945304eb96065b2a98b57a48a06ae28d285a71b5547f000000000000000000000000000000000000000000000000000000000000c3507f000000000000000000000000000000000000000000000000000000000000c3507f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000037f055" + }, + "error": "out of gas", + "traceAddress": [], + "subtraces": 1, + "transactionPosition": 16, + "transactionHash": "0x384487e5ae8d2997aece8e28403d393cb9752425e6de358891bed981c5af1c05", + "blockNumber": 1555285, + "blockHash": "0x93231d8e9662adb4c5c703583a92c7b3112cd5448f43ab4fa1f0f00a0183ed3f", + "time": "665.278µs" + }, + { + "action": { + "from": "0xf84bf5189ccd19f5897739756d214fa0dc099e0d", + "gas": "0x1d5c", + "init": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "value": "0xc350" + }, + "error": "insufficient balance for transfer", + "result": {}, + "subtraces": 0, + "traceAddress": [0], + "type": "create" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/staticcall_precompiled.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/staticcall_precompiled.json new file mode 100644 index 0000000..45ffbe2 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/staticcall_precompiled.json @@ -0,0 +1,83 @@ +{ + "genesis": { + "difficulty": "2028219", + "extraData": "0xd883010906846765746888676f312e31332e35856c696e7578", + "gasLimit": "23481547", + "hash": "0x3c06114e88c26b52decfe4e5f6d4d51cfaaea0317b646017fac32fadbe7df9f5", + "miner": "0x2a1442b4fbabf7b5507c13ccf076a547abfaeb1b", + "mixHash": "0x46108f74220c5ab23651f93912b14fea37ed1380d22e10639a1f5651c98cb949", + "nonce": "0x426a5267e0b636fe", + "number": "567687", + "stateRoot": "0x7b4b193fe73ef87101c7c325954681861cc240c299d03459784b2b11c9c522ae", + "timestamp": "1577578008", + "totalDifficulty": "485254950048", + "alloc": { + "0x8521f13dd5e4bc3dab3cf0f01a195a5af899e851": { + "balance": "0x0", + "nonce": "1", + "code": "0x608060405260043610610251576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806301ffc9a7146102565780630519ce79146102c857806306fdde031461031f578063095ea7b3146103af5780630a0f81681461040a5780631155dfe51461046157806318160ddd1461048c5780631b57cd44146104b7578063200b1e641461050657806327d7874c146105cb5780632ba73c151461061c5780633108e4d71461066d578063317676bf146106bc5780633f4ba83a1461071557806342842e0e1461072c57806346cb96fa146107a75780634e0a3379146107f65780635501d42d146108475780635c975abb146108a05780635fd8c710146108cf5780636352211e146108e65780636af04a571461096157806370a08231146109b85780637158798814610a1d5780637866928014610a6e5780638456cb5914610ae95780638462151c14610b0057806385ac788214610ba657806395787d2614610c2c57806395d89b4114610c6e57806396b5d99214610cfe578063990581b614610d795780639db797f014610e2d578063ab8f933a14610e80578063ad84202814610eab578063b047fb5014610ed6578063b355752214610f2d578063b9db15b414610f7c578063bc4006f514610fd2578063ca083be214611029578063cdd22c9314611082578063cec21acb146110d1578063e078d8b114611136578063e17b25af14611182578063e52ab74b146111d3578063f010432314611222578063fac9c51f1461129d578063fdb33429146112ec578063fffb147914611367575b600080fd5b34801561026257600080fd5b506102ae6004803603602081101561027957600080fd5b8101908080357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690602001909291905050506113e2565b604051808215151515815260200191505060405180910390f35b3480156102d457600080fd5b506102dd6116cb565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561032b57600080fd5b506103346116f1565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610374578082015181840152602081019050610359565b50505050905090810190601f1680156103a15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156103bb57600080fd5b50610408600480360360408110156103d257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061172a565b005b34801561041657600080fd5b5061041f6117c4565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561046d57600080fd5b506104766117e9565b6040518082815260200191505060405180910390f35b34801561049857600080fd5b506104a16117f6565b6040518082815260200191505060405180910390f35b3480156104c357600080fd5b506104f0600480360360208110156104da57600080fd5b8101908080359060200190929190505050611806565b6040518082815260200191505060405180910390f35b34801561051257600080fd5b506105b5600480360360a081101561052957600080fd5b81019080803590602001909291908035906020019064010000000081111561055057600080fd5b82018360208201111561056257600080fd5b8035906020019184600183028401116401000000008311171561058457600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919050505061181e565b6040518082815260200191505060405180910390f35b3480156105d757600080fd5b5061061a600480360360208110156105ee57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611cac565b005b34801561062857600080fd5b5061066b6004803603602081101561063f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611d86565b005b34801561067957600080fd5b506106a66004803603602081101561069057600080fd5b8101908080359060200190929190505050611e61565b6040518082815260200191505060405180910390f35b3480156106c857600080fd5b506106ff600480360360408110156106df57600080fd5b810190808035906020019092919080359060200190929190505050611e79565b6040518082815260200191505060405180910390f35b34801561072157600080fd5b5061072a611ea9565b005b34801561073857600080fd5b506107a56004803603606081101561074f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050611f86565b005b3480156107b357600080fd5b506107e0600480360360208110156107ca57600080fd5b8101908080359060200190929190505050612053565b6040518082815260200191505060405180910390f35b34801561080257600080fd5b506108456004803603602081101561081957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061206b565b005b34801561085357600080fd5b5061088a6004803603604081101561086a57600080fd5b810190808035906020019092919080359060200190929190505050612146565b6040518082815260200191505060405180910390f35b3480156108ac57600080fd5b506108b5612176565b604051808215151515815260200191505060405180910390f35b3480156108db57600080fd5b506108e4612189565b005b3480156108f257600080fd5b5061091f6004803603602081101561090957600080fd5b810190808035906020019092919050505061226d565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561096d57600080fd5b506109766122e6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156109c457600080fd5b50610a07600480360360208110156109db57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061230c565b6040518082815260200191505060405180910390f35b348015610a2957600080fd5b50610a6c60048036036020811015610a4057600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612355565b005b348015610a7a57600080fd5b50610aa760048036036020811015610a9157600080fd5b8101908080359060200190929190505050612472565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b348015610af557600080fd5b50610afe6124a5565b005b348015610b0c57600080fd5b50610b4f60048036036020811015610b2357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506125e9565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610b92578082015181840152602081019050610b77565b505050509050019250505060405180910390f35b348015610bb257600080fd5b50610c16600480360360c0811015610bc957600080fd5b810190808035906020019092919080359060200190929190803515159060200190929190803560ff1690602001909291908035906020019092919080359060200190929190505050612737565b6040518082815260200191505060405180910390f35b610c5860048036036020811015610c4257600080fd5b8101908080359060200190929190505050612c0c565b6040518082815260200191505060405180910390f35b348015610c7a57600080fd5b50610c8361304b565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610cc3578082015181840152602081019050610ca8565b50505050905090810190601f168015610cf05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b348015610d0a57600080fd5b50610d3760048036036020811015610d2157600080fd5b8101908080359060200190929190505050613084565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b348015610d8557600080fd5b50610db260048036036020811015610d9c57600080fd5b81019080803590602001909291905050506130b7565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610df2578082015181840152602081019050610dd7565b50505050905090810190601f168015610e1f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b348015610e3957600080fd5b50610e6660048036036020811015610e5057600080fd5b810190808035906020019092919050505061317b565b604051808215151515815260200191505060405180910390f35b348015610e8c57600080fd5b50610e956131b3565b6040518082815260200191505060405180910390f35b348015610eb757600080fd5b50610ec06131b9565b6040518082815260200191505060405180910390f35b348015610ee257600080fd5b50610eeb6131bf565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b348015610f3957600080fd5b50610f6660048036036020811015610f5057600080fd5b81019080803590602001909291905050506131e5565b6040518082815260200191505060405180910390f35b348015610f8857600080fd5b50610fb560048036036020811015610f9f57600080fd5b81019080803590602001909291905050506131fd565b604051808381526020018281526020019250505060405180910390f35b348015610fde57600080fd5b50610fe7613235565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561103557600080fd5b5061106c6004803603604081101561104c57600080fd5b81019080803590602001909291908035906020019092919050505061325b565b6040518082815260200191505060405180910390f35b34801561108e57600080fd5b506110bb600480360360208110156110a557600080fd5b810190808035906020019092919050505061328b565b6040518082815260200191505060405180910390f35b3480156110dd57600080fd5b50611120600480360360208110156110f457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506132ab565b6040518082815260200191505060405180910390f35b61116c6004803603604081101561114c57600080fd5b8101908080359060200190929190803590602001909291905050506132c3565b6040518082815260200191505060405180910390f35b34801561118e57600080fd5b506111d1600480360360208110156111a557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506134ee565b005b3480156111df57600080fd5b5061120c600480360360208110156111f657600080fd5b810190808035906020019092919050505061358d565b6040518082815260200191505060405180910390f35b34801561122e57600080fd5b5061125b6004803603602081101561124557600080fd5b81019080803590602001909291905050506135ad565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3480156112a957600080fd5b506112d6600480360360208110156112c057600080fd5b81019080803590602001909291905050506135e0565b6040518082815260200191505060405180910390f35b3480156112f857600080fd5b506113256004803603602081101561130f57600080fd5b81019080803590602001909291905050506135f8565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561137357600080fd5b506113a06004803603602081101561138a57600080fd5b810190808035906020019092919050505061362b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600060405180807f737570706f727473496e74657266616365286279746573342900000000000000815250601901905060405180910390207bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161480611610575060405180807f746f6b656e734f664f776e6572286164647265737329000000000000000000008152506016019050604051809103902060405180807f736166655472616e7366657246726f6d28616464726573732c6164647265737381526020017f2c75696e743235362900000000000000000000000000000000000000000000008152506029019050604051809103902060405180807f617070726f766528616464726573732c75696e743235362900000000000000008152506018019050604051809103902060405180807f6f776e65724f662875696e7432353629000000000000000000000000000000008152506010019050604051809103902060405180807f62616c616e63654f6628616464726573732900000000000000000000000000008152506012019050604051809103902060405180807f746f74616c537570706c79282900000000000000000000000000000000000000815250600d019050604051809103902018181818187bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806116c4575060405180807f73796d626f6c28290000000000000000000000000000000000000000000000008152506008019050604051809103902060405180807f6e616d652829000000000000000000000000000000000000000000000000000081525060060190506040518091039020187bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b9050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6040805190810160405280600781526020017f426974766965770000000000000000000000000000000000000000000000000081525081565b600260149054906101000a900460ff1615151561174657600080fd5b611750338261365e565b151561175b57600080fd5b61176581836136ca565b808273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600480549050905090565b6000600160048054905003905090565b60166020528060005260406000206000915090505481565b600085858080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506101006000825111801561187a575080825111155b151561188557600080fd5b33896005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141515156118f557600080fd5b6000878760405160200180838152602001828152602001925050506040516020818303038152906040528051906020012090506000600e6000838152602001908152602001600020541415156119b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f5369676e617475726520416c726561647920557365640000000000000000000081525060200191505060405180910390fd5b600560008d815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660018d60405160200180828152602001915050604051602081830303815290604052805190602001208b8b8b60405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611a80573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff16141515611aac57600080fd5b611ab4613a21565b6020604051908101604052808d8d8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050815250905060006001600a839080600181540180825580915050906001820390600052602060002001600090919290919091506000820151816000019080519060200190611b54929190613a35565b5050500390508063ffffffff1681141515611b6e57600080fd5b7fe819187a0cf517f3c23c7bd6e6b11a3aec56ec3f2784dc69ac56ebac668748ee3382604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a133600b600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508d600c600083815260200190815260200160002081905550600860008f81526020019081526020016000208190806001815401808255809150509060018203906000526020600020016000909192909190915055508d600e600085815260200190815260200160002081905550809750505050505050509695505050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515611d0757600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151515611d4357600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515611de157600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151515611e1d57600080fd5b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60116020528060005260406000206000915090505481565b600860205281600052604060002081815481101515611e9457fe5b90600052602060002001600091509150505481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515611f0457600080fd5b600260149054906101000a900460ff161515611f1f57600080fd5b600073ffffffffffffffffffffffffffffffffffffffff16601860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515611f7c57600080fd5b611f84613720565b565b600260149054906101000a900460ff16151515611fa257600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515611fde57600080fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415151561201957600080fd5b61202333826137b3565b151561202e57600080fd5b612038838261365e565b151561204357600080fd5b61204e83838361381f565b505050565b600e6020528060005260406000206000915090505481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156120c657600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415151561210257600080fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600d6020528160005260406000208181548110151561216157fe5b90600052602060002001600091509150505481565b600260149054906101000a900460ff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156121e557600080fd5b60003073ffffffffffffffffffffffffffffffffffffffff16319050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050158015612269573d6000803e3d6000fd5b5050565b60006005600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141515156122e157600080fd5b919050565b601860009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600660008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156123b057600080fd5b600260149054906101000a900460ff1615156123cb57600080fd5b80601860006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f450db8da6efbe9c22f2347f7c2021231df1fc58d3ae9a2fa75d39fa44619930581604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b600b6020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061254d57506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b806125a55750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156125b057600080fd5b600260149054906101000a900460ff161515156125cc57600080fd5b6001600260146101000a81548160ff021916908315150217905550565b606060006125f68361230c565b9050600081141561263a5760006040519080825280602002602001820160405280156126315781602001602082028038833980820191505090505b50915050612732565b60608160405190808252806020026020018201604052801561266b5781602001602082028038833980820191505090505b50905060006126786117f6565b905060008090506000600190505b8281111515612729578673ffffffffffffffffffffffffffffffffffffffff166005600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561271c5780848381518110151561270557fe5b906020019060200201818152505081806001019250505b8080600101915050612686565b83955050505050505b919050565b600033876005600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141515156127a957600080fd5b60008585604051602001808381526020018281526020019250505060405160208183030381529060405280519060200120905060008911156128715788601260008381526020019081526020016000205414151515612870576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f5369676e617475726520416c726561647920557365640000000000000000000081525060200191505060405180910390fd5b5b600560008b815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660018b604051602001808281526020019150506040516020818303038152906040528051906020012089898960405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa15801561293e573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff1614151561296a57600080fd5b6000339050600073ffffffffffffffffffffffffffffffffffffffff16600b60008c815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141515156129de57600080fd5b8073ffffffffffffffffffffffffffffffffffffffff16600b60008c815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151515612a4c57600080fd5b612a54613ab5565b6020604051908101604052808b1515815250905060006001600f8390806001815401808255809150509060018203906000526020600020016000909192909190915060008201518160000160006101000a81548160ff02191690831515021790555050500390508063ffffffff1681141515612acf57600080fd5b7fa10f25ef783c24056e27eb55eb6c0ac1c4863cd5eab7e657cd067926b3dce0648382604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1826010600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600d60008d81526020019081526020016000208190806001815401808255809150509060018203906000526020600020016000909192909190915055508b60116000838152602001908152602001600020819055508b60126000868152602001908152602001600020819055508096505050505050509695505050505050565b600034601354808210151515612c2157600080fd5b60003390506000600b600087815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614151515612c9a57600080fd5b8173ffffffffffffffffffffffffffffffffffffffff16600b600088815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614151515612d0857600080fd5b612d10613acb565b602060405190810160405280348152509050600060016014839080600181540180825580915050906001820390600052602060002001600090919290919091506000820151816000015550500390508063ffffffff1681141515612d7357600080fd5b836015600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508760166000838152602001908152602001600020819055506000606434604602811515612dee57fe5b0490506000600d60008b815260200190815260200160002080549050823403811515612e1657fe5b049050600b60008b815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050158015612e92573d6000803e3d6000fd5b5060008090505b600d60008c815260200190815260200160002080549050811015612fcf5760106000600d60008e815260200190815260200160002083815481101515612edb57fe5b9060005260206000200154815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050158015612f5a573d6000803e3d6000fd5b5060176000858152602001908152602001600020600d60008d815260200190815260200160002082815481101515612f8e57fe5b906000526020600020015490806001815401808255809150509060018203906000526020600020016000909192909190915055508080600101915050612e99565b507f6ea1e5e03071ff9bad53b614eafcc00d29db646e9c351fcc00d45a4118d7c51a8684604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a18298505050505050505050919050565b6040805190810160405280600281526020017f425600000000000000000000000000000000000000000000000000000000000081525081565b60056020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60606000600a838154811015156130ca57fe5b906000526020600020019050806000018054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561316e5780601f106131435761010080835404028352916020019161316e565b820191906000526020600020905b81548152906001019060200180831161315157829003601f168201915b5050505050915050919050565b600080600f8381548110151561318d57fe5b9060005260206000200190508060000160009054906101000a900460ff16915050919050565b60035481565b60135481565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60126020528060005260406000206000915090505481565b600080600060048481548110151561321157fe5b90600052602060002090600202019050806000015492508060010154915050915091565b600960009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60176020528160005260406000208181548110151561327657fe5b90600052602060002001600091509150505481565b600060086000838152602001908152602001600020805490509050919050565b60066020528060005260406000206000915090505481565b6000346003548082101515156132d857600080fd5b8460007f01000000000000000000000000000000000000000000000000000000000000000281600060208110151561330c57fe5b1a7f0100000000000000000000000000000000000000000000000000000000000000027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161415151561335e57600080fd5b8460007f01000000000000000000000000000000000000000000000000000000000000000281600060208110151561339257fe5b1a7f0100000000000000000000000000000000000000000000000000000000000000027effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916141515156133e457600080fd5b60003390506133f1613adf565b60408051908101604052808a81526020018981525090506000600160048390806001815401808255809150509060018203906000526020600020906002020160009091929091909150600082015181600001556020820151816001015550500390508063ffffffff168114151561346757600080fd5b7f982bb66d9aa60573bc0a2066122e1466ecbc4c179a5e7c1c5b589345008ce69a8382604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a16134de6000848361381f565b8097505050505050505092915050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561354957600080fd5b80600960006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000600d6000838152602001908152602001600020805490509050919050565b60156020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600c6020528060005260406000206000915090505481565b60076020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60106020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008273ffffffffffffffffffffffffffffffffffffffff166005600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614905092915050565b806007600084815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561377b57600080fd5b600260149054906101000a900460ff16151561379657600080fd5b6000600260146101000a81548160ff021916908315150217905550565b60008273ffffffffffffffffffffffffffffffffffffffff166007600084815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614905092915050565b600660008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008154809291906001019190505550816005600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151561397d57600660008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008154809291906001900391905055506007600082815260200190815260200160002060006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690555b7f70a295484349ac4c2073cdca8ba026869fff31e0d35e268f820e44c9d25f4a2e838383604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a1505050565b602060405190810160405280606081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613a7657805160ff1916838001178555613aa4565b82800160010185558215613aa4579182015b82811115613aa3578251825591602001919060010190613a88565b5b509050613ab19190613aff565b5090565b6020604051908101604052806000151581525090565b602060405190810160405280600081525090565b604080519081016040528060008019168152602001600080191681525090565b613b2191905b80821115613b1d576000816000905550600101613b05565b5090565b9056fea165627a7a72305820b73bf81476c95567782e45ebae5220573d46c55a9004c11243c470bc91f2d26d0029", + "storage": { + "0x05b8ccbb9d4d8fb16ea74ce3c29a41f1b461fbdaff4714a0d9a8eb05499746bc": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa54c2b4154b4f221d71d6d5bc0ec905c931a021bb6fb138fc0495bb0373e2276": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x0000000000000000000000000000000000000001": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0xcec3d4daf44926cc41e", + "nonce": "147795", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "567688", + "difficulty": "2028219", + "timestamp": "1577578023", + "gasLimit": "23504477", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf9018f8302415385746a52880083048196948521f13dd5e4bc3dab3cf0f01a195a5af899e85180b90124200b1e64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001b9af799918107e9a339eba0584b8b60b35aae6f087c74f6bfc00c9301849b204d094ed65e09c76c2597f5516f9440aad2921e50dde096e7caaa65a536d4d9265e00000000000000000000000000000000000000000000000000000000000000504269747669657720697320616e20616d617a696e6720776562736974652e20596f752073686f756c6420646566696e6974656c792061646420796f75722070726f6475637420746f2069742e20e282bf0000000000000000000000000000000081a2a0686e4a69e1fa6cac6b4f751a3935ca5a371d720c34d3a7136988aa017a528ed5a07d993e607b665c24557d0eae166c21fe744e618ed3430902ac6206c63a331dc0", + "result": [ + { + "action": { + "author": "0x0000000000000000000000000000000000000000", + "address": "0x0000000000000000000000000000000000000000", + "balance": "0x0", + "callType": "call", + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "gas": "0x48196", + "input": "0x200b1e64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001b9af799918107e9a339eba0584b8b60b35aae6f087c74f6bfc00c9301849b204d094ed65e09c76c2597f5516f9440aad2921e50dde096e7caaa65a536d4d9265e00000000000000000000000000000000000000000000000000000000000000504269747669657720697320616e20616d617a696e6720776562736974652e20596f752073686f756c6420646566696e6974656c792061646420796f75722070726f6475637420746f2069742e20e282bf00000000000000000000000000000000", + "refundAddress": "0x0000000000000000000000000000000000000000", + "to": "0x8521f13dd5e4bc3dab3cf0f01a195a5af899e851", + "value": "0x0" + }, + "error": "execution reverted", + "result": { + "gasUsed": "0x947c" + }, + "subtraces": 0, + "traceAddress": [], + "type": "call" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/suicide.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/suicide.json new file mode 100644 index 0000000..16d4376 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/suicide.json @@ -0,0 +1,92 @@ +{ + "genesis": { + "number": "553153", + "hash": "0x88bde20840880a1f3fba92121912a3cc0d3b26d76e4d914fbd85fc2e43da3b3f", + "nonce": "0x7be554ffe4b82fc2", + "mixHash": "0xf73d2ff3c16599c3b8a24b9ebde6c09583b5ee3f747d3cd37845d564f4c8d87a", + "stateRoot": "0x40b5f53d610108947688a04fb68838ff9c0aa0dd6e54156b682537192171ff5c", + "miner": "0x774c398d763161f55b66a646f17edda4addad2ca", + "difficulty": "1928226", + "totalDifficulty": "457857582215", + "extraData": "0xd983010907846765746888676f312e31332e358664617277696e", + "gasLimit": "7999473", + "timestamp": "1577392669", + "alloc": { + "0x877bd459c9b7d8576b44e59e09d076c25946f443": { + "balance": "0x19bb4ac611ca7a1fc881", + "nonce": "701", + "code": "0x", + "storage": {} + }, + "0x8ee79c5b3f6e1d214d2c4fcf7ea4092a32e26e91": { + "balance": "0x0", + "nonce": "1", + "code": "0x60606040526000357c01000000000000000000000000000000000000000000000000000000009004806341c0e1b514610044578063cfae32171461005157610042565b005b61004f6004506100ca565b005b61005c60045061015e565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561015b57600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b565b60206040519081016040528060008152602001506001600050805480601f016020809104026020016040519081016040528092919081815260200182805480156101cd57820191906000526020600020905b8154815290600101906020018083116101b057829003601f168201915b505050505090506101d9565b9056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000877bd459c9b7d8576b44e59e09d076c25946f443" + } + } + }, + "config": { + "chainId": 63, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 0, + "eip158Block": 0, + "ethash": {}, + "homesteadBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 301243, + "petersburgBlock": 999983, + "istanbulBlock": 999983 + } + }, + "context": { + "number": "553154", + "difficulty": "1929167", + "timestamp": "1577392670", + "gasLimit": "8000000", + "miner": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "input": "0xf86c8202bd850ee6b280008344aa20948ee79c5b3f6e1d214d2c4fcf7ea4092a32e26e91808441c0e1b581a2a03f95ca5cdf7fd727630341c4c6aa1b64ccd9949bd9ecc72cfdd7ce17a2013a69a06d34795ef7fb0108a6dbee4ae0a1bdc48dcd2a4ee53bb6a33d45515af07bb9a8", + "result": [ + { + "action": { + "callType": "call", + "from": "0x877bd459c9b7d8576b44e59e09d076c25946f443", + "gas": "0x44aa20", + "input": "0x41c0e1b5", + "to": "0x8ee79c5b3f6e1d214d2c4fcf7ea4092a32e26e91", + "value": "0x0" + }, + "blockHash": "0xf641c3b0f82b07cd3a528adb9927dd83eeb4f1682e2bd523ed36888e0d82c9a9", + "blockNumber": 553154, + "result": { + "gasUsed": "0x347a", + "output": "0x" + }, + "subtraces": 1, + "traceAddress": [], + "transactionHash": "0x6af0a5c3188ffacae4d340d4a17e14fdb5a54187683a80ef241bde248189882b", + "transactionPosition": 15, + "type": "call" + }, + { + "action": { + "address": "0x8ee79c5b3f6e1d214d2c4fcf7ea4092a32e26e91", + "balance": "0x0", + "refundAddress": "0x877bd459c9b7d8576b44e59e09d076c25946f443" + }, + "blockHash": "0xf641c3b0f82b07cd3a528adb9927dd83eeb4f1682e2bd523ed36888e0d82c9a9", + "blockNumber": 553154, + "subtraces": 0, + "traceAddress": [ + 0 + ], + "transactionHash": "0x6af0a5c3188ffacae4d340d4a17e14fdb5a54187683a80ef241bde248189882b", + "transactionPosition": 15, + "type": "suicide" + } + ] +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_flat/throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/throw.json new file mode 100644 index 0000000..a001178 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_flat/throw.json @@ -0,0 +1,70 @@ +{ + "context": { + "difficulty": "117009631", + "gasLimit": "4712388", + "miner": "0x294e5d6c39a36ce38af1dca70c1060f78dee8070", + "number": "25009", + "timestamp": "1479891666" + }, + "genesis": { + "alloc": { + "0x70c9217d814985faef62b124420f8dfbddd96433": { + "balance": "0x4ecd70668f5d854a", + "code": "0x", + "nonce": "1638", + "storage": {} + }, + "0xc212e03b9e060e36facad5fd8f4435412ca22e6b": { + "balance": "0x0", + "code": "0x606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000000000000061a9", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433" + } + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "117066792", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712388", + "hash": "0xe23e8d4562a1045b70cbc99fefb20c101a8f0fc8559a80d65fea8896e2f1d46e", + "miner": "0x71842f946b98800fe6feb49f0ae4e253259031c9", + "mixHash": "0x0aada9d6e93dd4db0d09c0488dc0a048fca2ccdc1f3fc7b83ba2a8d393a3a4ff", + "nonce": "0x70849d5838dee2e9", + "number": "25008", + "stateRoot": "0x1e01d2161794768c5b917069e73d86e8dca80cd7f3168c0597de420ab93a3b7b", + "timestamp": "1479891641", + "totalDifficulty": "1896347038589" + }, + "input": "0xf88b8206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d59750", + "result": [ + { + "action": { + "callType": "call", + "from": "0x70c9217d814985faef62b124420f8dfbddd96433", + "gas": "0x3d090", + "input": "0x51a34eb8000000000000000000000000000000000000000000000027fad02094277c0000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "value": "0x0" + }, + "blockNumber": 25009, + "error": "invalid jump destination", + "result": {}, + "subtraces": 0, + "traceAddress": [], + "type": "call" + } + ] +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/create.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/create.json new file mode 100644 index 0000000..df0b287 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/create.json @@ -0,0 +1,58 @@ +{ + "context": { + "difficulty": "3755480783", + "gasLimit": "5401723", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "number": "2294702", + "timestamp": "1513676146" + }, + "genesis": { + "alloc": { + "0x13e4acefe6a6700604929946e70e6443e4e73447": { + "balance": "0xcf3e0938579f000", + "code": "0x", + "nonce": "9", + "storage": {} + }, + "0x7dc9c9730689ff0b0fd506c67db815f12d90a448": { + "balance": "0x0", + "code": "0x", + "nonce": "0", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3757315409", + "extraData": "0x566961425443", + "gasLimit": "5406414", + "hash": "0xae107f592eebdd9ff8d6ba00363676096e6afb0e1007a7d3d0af88173077378d", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "mixHash": "0xc927aa05a38bc3de864e95c33b3ae559d3f39c4ccd51cef6f113f9c50ba0caf1", + "nonce": "0x93363bbd2c95f410", + "number": "2294701", + "stateRoot": "0x6b6737d5bde8058990483e915866bd1578014baeff57bd5e4ed228a2bfad635c", + "timestamp": "1513676127", + "totalDifficulty": "7160808139332585" + }, + "input": "0xf907ef098504e3b29200830897be8080b9079c606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a1129a01060f46676a5dff6f407f0f51eb6f37f5c8c54e238c70221e18e65fc29d3ea65a0557b01c50ff4ffaac8ed6e5d31237a4ecbac843ab1bfe8bb0165a0060df7c54f", + "result": { + "from": "0x13e4acefe6a6700604929946e70e6443e4e73447", + "gas": "0x897be", + "gasUsed": "0x897be", + "input": "0x606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a11", + "output": "0x606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029", + "to": "0x7dc9c9730689ff0b0fd506c67db815f12d90a448", + "type": "CREATE", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/deep_calls.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/deep_calls.json new file mode 100644 index 0000000..80fc0b0 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/deep_calls.json @@ -0,0 +1,415 @@ +{ + "context": { + "difficulty": "117066904", + "gasLimit": "4712384", + "miner": "0x1977c248e1014cc103929dd7f154199c916e39ec", + "number": "25001", + "timestamp": "1479891545" + }, + "genesis": { + "alloc": { + "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38": { + "balance": "0x0", + "code": "0x606060405236156100825760e060020a600035046302d05d3f811461008a5780630accce061461009c5780631ab9075a146100c757806331ed274614610102578063645a3b7214610133578063772fdae314610155578063a7f4377914610180578063ae5f80801461019e578063c9bded21146101ea578063f905c15a14610231575b61023a610002565b61023c600054600160a060020a031681565b61023a600435602435604435606435608435600254600160a060020a03166000141561024657610002565b61023a600435600254600160a060020a03166000148015906100f8575060025433600160a060020a03908116911614155b156102f457610002565b61023a60043560243560443560643560843560a43560c435600254600160a060020a03166000141561031657610002565b61023a600435602435600254600160a060020a0316600014156103d057610002565b61023a600435602435604435606435608435600254600160a060020a03166000141561046157610002565b61023a60025433600160a060020a0390811691161461051657610002565b61023a6004356024356044356060828152600160a060020a0382169060ff8516907fa6c2f0913db6f79ff0a4365762c61718973b3413d6e40382e704782a9a5099f690602090a3505050565b61023a600435602435600160a060020a038116606090815260ff8316907fee6348a7ec70f74e3d6cba55a53e9f9110d180d7698e9117fc466ae29a43e34790602090a25050565b61023c60035481565b005b6060908152602090f35b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061029d57610002565b60408051858152602081018390528151600160a060020a03858116939087169260ff8a16927f5a690ecd0cb15c1c1fd6b6f8a32df0d4f56cb41a54fea7e94020f013595de796929181900390910190a45050505050565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061036d57610002565b6040805186815260208101869052808201859052606081018490529051600160a060020a03831691889160ff8b16917fd65d9ddafbad8824e2bbd6f56cc9f4ac27ba60737035c10a321ea2f681c94d47919081900360800190a450505050505050565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f115610002575050604051511515905061042757610002565b60408051828152905183917fa9c6cbc4bd352a6940479f6d802a1001550581858b310d7f68f7bea51218cda6919081900360200190a25050565b60025460e060020a6313bc6d4b02606090815233600160a060020a0390811660645291909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506104b857610002565b80600160a060020a031684600160a060020a03168660ff167f69bdaf789251e1d3a0151259c0c715315496a7404bce9fd0b714674685c2cab78686604051808381526020018281526020019250505060405180910390a45050505050565b600254600160a060020a0316ff", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0x2cccf5e0538493c235d1c5ef6580f77d99e91396": { + "balance": "0x0", + "code": "0x606060405236156100775760e060020a600035046302d05d3f811461007f57806313bc6d4b146100915780633688a877146100b95780635188f9961461012f5780637eadc976146101545780638ad79680146101d3578063a43e04d814610238578063a7f437791461025e578063e16c7d981461027c575b61029f610002565b6102a1600054600160a060020a031681565b6102be600435600160a060020a03811660009081526002602052604090205460ff165b919050565b6102d26004356040805160208181018352600080835284815260038252835190849020805460026001821615610100026000190190911604601f8101849004840283018401909552848252929390929183018282801561037d5780601f106103525761010080835404028352916020019161037d565b61029f6004356024356000805433600160a060020a039081169116146104a957610002565b61034060043560008181526001602090815260408083205481517ff905c15a0000000000000000000000000000000000000000000000000000000081529151600160a060020a03909116928392839263f905c15a92600483810193919291829003018189876161da5a03f1156100025750506040515195945050505050565b60408051602060248035600481810135601f810185900485028601850190965285855261029f9581359591946044949293909201918190840183828082843750949650505050505050600054600160a060020a0390811633909116146104f657610002565b61029f6004355b600080548190600160a060020a0390811633909116146105a457610002565b61029f60005433600160a060020a0390811691161461072957610002565b6102a1600435600081815260016020526040902054600160a060020a03166100b4565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156103325780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161036057829003601f168201915b505050505090506100b4565b506000828152600160208181526040808420805473ffffffffffffffffffffffffffffffffffffffff191686179055600160a060020a038581168086526002909352818520805460ff191690941790935580517f1ab9075a0000000000000000000000000000000000000000000000000000000081523090931660048401525184939192631ab9075a926024828101939192829003018183876161da5a03f11561000257505060408051602081018690528082019290925243606083015260808083526003908301527f414444000000000000000000000000000000000000000000000000000000000060a0830152517f8ac68d4e97d65912f220b4c5f87978b8186320a5e378c1369850b5b5f90323d39181900360c00190a15b505050565b600083815260016020526040902054600160a060020a03838116911614156104d0576104a4565b600083815260016020526040812054600160a060020a031614610389576103898361023f565b600082815260036020908152604082208054845182855293839020919360026001831615610100026000190190921691909104601f90810184900483019391929186019083901061056a57805160ff19168380011785555b5061059a9291505b808211156105a05760008155600101610556565b8280016001018555821561054e579182015b8281111561054e57825182600050559160200191906001019061057c565b50505050565b5090565b600083815260016020526040812054600160a060020a031614156105c757610002565b50506000818152600160205260408082205481517fa7f437790000000000000000000000000000000000000000000000000000000081529151600160a060020a0391909116928392839263a7f4377992600483810193919291829003018183876161da5a03f11561000257505050600160005060008460001916815260200190815260200160002060006101000a815490600160a060020a0302191690556002600050600083600160a060020a0316815260200190815260200160002060006101000a81549060ff02191690557f8ac68d4e97d65912f220b4c5f87978b8186320a5e378c1369850b5b5f90323d383834360405180806020018560001916815260200184600160a060020a03168152602001838152602001828103825260038152602001807f44454c000000000000000000000000000000000000000000000000000000000081526020015060200194505050505060405180910390a1505050565b600054600160a060020a0316ff", + "nonce": "1", + "storage": { + "0x0684ac65a9fa32414dda56996f4183597d695987fdb82b145d722743891a6fe8": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "0x1cd76f78169a420d99346e3501dd3e541622c38a226f9b63e01cfebc69879dc7": "0x000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f", + "0x8e54a4494fe5da016bfc01363f4f6cdc91013bb5434bd2a4a3359f13a23afa2f": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf", + "0x94edf7f600ba56655fd65fca1f1424334ce369326c1dc3e53151dcd1ad06bc13": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xbbee47108b275f55f98482c6800f6372165e88b0330d3f5dae6419df4734366c": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "0xd38c0c4e84de118cfdcc775130155d83b8bbaaf23dc7f3c83a626b10473213bd": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xfb3aa5c655c2ec9d40609401f88d505d1da61afaa550e36ef5da0509ada257ba": "0x0000000000000000000000007986bad81f4cbd9317f5a46861437dae58d69113" + } + }, + "0x3e9286eafa2db8101246c2131c09b49080d00690": { + "balance": "0x0", + "code": "0x606060405236156100cf5760e060020a600035046302d05d3f81146100d7578063056d4470146100e957806316c66cc61461010c5780631ab9075a146101935780633ae1005c146101ce57806358541662146101fe5780635ed61af014610231578063644e3b791461025457806384dbac3b146102db578063949ae479146102fd5780639859387b14610321578063a7f4377914610340578063ab03fc261461035e578063e8161b7814610385578063e964d4e114610395578063f905c15a146103a5578063f92eb774146103ae575b6103be610002565b6103c0600054600160a060020a031681565b6103be6004356002546000908190600160a060020a031681141561040357610002565b6103dd60043560006108365b6040805160025460e360020a631c2d8fb30282527f636f6e747261637464620000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b6103be600435600254600160a060020a03166000148015906101c4575060025433600160a060020a03908116911614155b1561088d57610002565b6103be600435602435604435606435600254600090819081908190600160a060020a03168114156108af57610002565b6103c0600435602435604435606435608435600254600090819081908190600160a060020a03168114156110e857610002565b6103be6004356002546000908190600160a060020a03168114156115ec57610002565b6103c06004356000611b635b6040805160025460e360020a631c2d8fb30282527f6d61726b6574646200000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b6103be600435602435600254600160a060020a031660001415611bb557610002565b6103be600435602435600254600090600160a060020a0316811415611d2e57610002565b6103be600435600254600160a060020a031660001415611fc657610002565b6103be60025433600160a060020a0390811691161461207e57610002565b6103be600435602435604435600254600090600160a060020a031681141561208c57610002565b6103dd60043560006124b8610260565b6103c0600435600061250a610118565b6103f160035481565b6103f16004356000612561610260565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061046557610002565b8291506104e55b6040805160025460e360020a631c2d8fb30282527f63706f6f6c00000000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f115610002575050604051519150505b90565b600160a060020a031663b2206e6d83600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fb2206e6d0000000000000000000000000000000000000000000000000000000082526004820152600160a060020a038816602482015290516044808301935060209282900301816000876161da5a03f11561000257505060405151915061059b90506106ba565b600160a060020a031663d5b205ce83600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a636ad902e7028252600160a060020a0390811660048301526024820187905288166044820152905160648281019350600092829003018183876161da5a03f115610002575050506107355b6040805160025460e360020a631c2d8fb30282527f6c6f676d6772000000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b50826120ee5b6040805160025460e360020a631c2d8fb30282527f6163636f756e7463746c0000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b600160a060020a0316630accce06600684600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d02825291519192899290916336da446891600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150866040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050505050565b600160a060020a03166316c66cc6836040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051519150505b919050565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061091157610002565b87935061091c610260565b600160a060020a031663bdbdb08685600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fbdbdb0860000000000000000000000000000000000000000000000000000000082526004820152602481018a905290516044808301935060209282900301816000876161da5a03f1156100025750506040515193506109ca90506106ba565b600160a060020a03166381982a7a8885876040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f11561000257505050610a3661046c565b600160a060020a03166308636bdb85600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517f08636bdb000000000000000000000000000000000000000000000000000000008252600482015260248101889052604481019290925251606482810192602092919082900301816000876161da5a03f11561000257505060408051805160e160020a630a5d50db028252600482018190529151919450600160a060020a03871692506314baa1b6916024828101926000929190829003018183876161da5a03f11561000257505050610b3561046c565b600160a060020a0316630a3b6ede85600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63051db76f0282526004820152600160a060020a038d16602482015290516044808301935060209282900301816000876161da5a03f115610002575050604051519150610bd590506106ba565b600160a060020a031663d5b205ce87838b6040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f11561000257505050610c41610118565b600160a060020a031663988db79c888a6040518360e060020a0281526004018083600160a060020a0316815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050610ca5610260565b600160a060020a031663f4f2821b896040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050610d6f5b6040805160025460e360020a631c2d8fb30282527f747261646564620000000000000000000000000000000000000000000000000060048301529151600092600160a060020a03169163e16c7d98916024828101926020929190829003018187876161da5a03f1156100025750506040515191506104e29050565b600160a060020a0316635f539d69896040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050610dc2610639565b600160a060020a0316630accce06600386600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6315b1ea01028252915191928e928e9263ad8f500891600482810192602092919082900301816000876161da5a03f11561000257505050604051805190602001506040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050610ec5610639565b600160a060020a0316630accce06600386600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6315b1ea01028252915191928e928d9263ad8f500891600482810192602092919082900301816000876161da5a03f11561000257505050604051805190602001506040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050610fc8610639565b600160a060020a031663645a3b7285600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151905061101e610260565b600160a060020a031663f92eb77488600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f115610002575050505050505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061114a57610002565b604051600254600160a060020a0316908a908a908a908a908a90611579806125b38339018087600160a060020a0316815260200186600160a060020a03168152602001856000191681526020018481526020018381526020018281526020019650505050505050604051809103906000f092506111c5610118565b600160a060020a031663b9858a288a856040518360e060020a0281526004018083600160a060020a0316815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611229610260565b600160a060020a0316635188f99689856040518360e060020a028152600401808360001916815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611288610260565b600160a060020a031663bdbdb08689896040518360e060020a0281526004018083600019168152602001828152602001925050506020604051808303816000876161da5a03f1156100025750506040515192506112e590506106ba565b600160a060020a03166346d88e7d8a858a6040518460e060020a0281526004018084600160a060020a0316815260200183600160a060020a0316815260200182815260200193505050506000604051808303816000876161da5a03f115610002575050506113516106ba565b600160a060020a03166381982a7a8a84866040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f115610002575050506113bd61046c565b600160a060020a0316632b58469689856040518360e060020a028152600401808360001916815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f1156100025750505061141c61046c565b600160a060020a03166308636bdb8984866040518460e060020a028152600401808460001916815260200183815260200182600160a060020a0316815260200193505050506020604051808303816000876161da5a03f11561000257505060408051805160e160020a630a5d50db028252600482018190529151919350600160a060020a03861692506314baa1b6916024828101926000929190829003018183876161da5a03f115610002575050506114d3610639565b6040805160e160020a630566670302815260016004820152602481018b9052600160a060020a0386811660448301528c811660648301526000608483018190529251931692630accce069260a480840193919291829003018183876161da5a03f11561000257505050611544610639565b600160a060020a031663645a3b728961155b610260565b600160a060020a031663f92eb7748c6040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448084019360009350829003018183876161da5a03f1156100025750939a9950505050505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061164e57610002565b82915061165961046c565b600160a060020a0316630a3b6ede83600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63051db76f0282526004820152600160a060020a038816602482015290516044808301935060209282900301816000876161da5a03f1156100025750506040515191506116f990506106ba565b600160a060020a031663d5b205ce83600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a636ad902e7028252600160a060020a0390811660048301526024820187905288166044820152905160648281019350600092829003018183876161da5a03f1156100025750505061179b6106ba565b600160a060020a031663d653078983600160a060020a03166336da44686040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517ff1ff78a0000000000000000000000000000000000000000000000000000000008252915191929163f1ff78a09160048181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150866040518460e060020a0281526004018084600160a060020a0316815260200183815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f1156100025750505061189f610260565b600160a060020a031663f4f2821b846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506118f2610118565b600160a060020a031663f4f2821b846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050611945610639565b600160a060020a0316630accce06600484600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d02825291519192899290916336da44689181870191602091908190038801816000876161da5a03f115610002575050506040518051906020015060006040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f11561000257505050611a48610639565b600160a060020a031663645a3b7283600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519050611a9e610260565b600160a060020a031663f92eb77486600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f11561000257505050505050565b600160a060020a03166381738c59836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f1156100025750506040515115159050611c1757610002565b611c1f610260565b600160a060020a03166338a699a4836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f11561000257505060405151159050611c7457610002565b611c7c610260565b600160a060020a0316632243118a836040518260e060020a02815260040180826000191681526020019150506000604051808303816000876161da5a03f11561000257505050611cca610639565b600160a060020a031663ae5f8080600184846040518460e060020a028152600401808481526020018360001916815260200182600160a060020a0316815260200193505050506000604051808303816000876161da5a03f115610002575050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f1156100025750506040515115159050611d9057610002565b5081611d9a610260565b600160a060020a031663581d5d6084846040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506000604051808303816000876161da5a03f11561000257505050611df5610639565b600160a060020a0316630accce06600283600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a630566670302825260048201949094526024810193909352600160a060020a038816604484015260006064840181905260848401819052905160a4808501949293509091829003018183876161da5a03f11561000257505050611eab610639565b600160a060020a031663645a3b7282600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519050611f01610260565b600160a060020a031663f92eb77485600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a633e4baddd028252600482015290516024828101935060209282900301816000876161da5a03f11561000257505060408051805160e060020a86028252600482019490945260248101939093525160448381019360009350829003018183876161da5a03f11561000257505050505050565b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061202857610002565b612030610118565b600160a060020a0316639859387b826040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f1156100025750505050565b600254600160a060020a0316ff5b6040805160025460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f11561000257505060405151151590506106b457610002565b600160a060020a031663d65307898383600160a060020a031663f1ff78a06040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fd6530789000000000000000000000000000000000000000000000000000000008252600160a060020a039485166004830152602482015292891660448401525160648381019360009350829003018183876161da5a03f115610002575050506121a5610118565b600160a060020a031663f4f2821b856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506121f8610cf4565b600160a060020a031663f4f2821b856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f1156100025750505061224b610639565b600160a060020a0316630accce06600583600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e360020a6306db488d028252915191928a9290916336da446891600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150886040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f1156100025750505080600160a060020a031663ea71b02d6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151600160a060020a031660001490506124b25761239f610639565b600160a060020a0316630accce06600583600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fea71b02d000000000000000000000000000000000000000000000000000000008252915191928a92909163ea71b02d91600482810192602092919082900301816000876161da5a03f1156100025750505060405180519060200150886040518660e060020a028152600401808681526020018560001916815260200184600160a060020a0316815260200183600160a060020a03168152602001828152602001955050505050506000604051808303816000876161da5a03f115610002575050505b50505050565b600160a060020a03166338a699a4836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b600160a060020a031663213fe2b7836040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515191506108889050565b600160a060020a031663f92eb774836040518260e060020a02815260040180826000191681526020019150506020604051808303816000876161da5a03f115610002575050604051519150610888905056606060405260405160c08061157983396101206040819052825160805160a051935160e0516101005160008054600160a060020a03199081163317909155600180546005805484168817905560048a90556006869055600b8590556008849055909116861760a060020a60ff02191690554360038190556002558686526101408390526101608190529396929594919390929091600160a060020a033016917f76885d242fb71c6f74a7e717416e42eff4d96faf54f6de75c6a0a6bbd8890c6b91a230600160a060020a03167fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff600b600050546040518082815260200191505060405180910390a250505050505061145e8061011b6000396000f3606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "16", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0x70c9217d814985faef62b124420f8dfbddd96433": { + "balance": "0x4ef436dcbda6cd4a", + "code": "0x", + "nonce": "1634", + "storage": {} + }, + "0x7986bad81f4cbd9317f5a46861437dae58d69113": { + "balance": "0x0", + "code": "0x6060604052361561008d5760e060020a600035046302d05d3f811461009557806316c66cc6146100a75780631ab9075a146100d7578063213fe2b7146101125780639859387b1461013f578063988db79c1461015e578063a7f4377914610180578063b9858a281461019e578063c8e40fbf146101c0578063f4f2821b146101e8578063f905c15a14610209575b610212610002565b610214600054600160a060020a031681565b600160a060020a0360043581811660009081526005602052604081205461023193168114610257575060016101e3565b610212600435600254600160a060020a0316600014801590610108575060025433600160a060020a03908116911614155b1561025f57610002565b610214600435600160a060020a03811660009081526004602052604081205460ff16151561027557610002565b610212600435600254600160a060020a03166000141561029b57610002565b610212600435602435600254600160a060020a03166000141561050457610002565b61021260025433600160a060020a0390811691161461056757610002565b610212600435602435600254600160a060020a03166000141561057557610002565b610231600435600160a060020a03811660009081526004602052604090205460ff165b919050565b610212600435600254600090600160a060020a031681141561072057610002565b61024560035481565b005b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060006101e3565b60028054600160a060020a031916821790555b50565b50600160a060020a038181166000908152600460205260409020546101009004166101e3565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506102fe57610002565b600160a060020a03811660009081526004602052604090205460ff161515610272576040516104028061092e833901809050604051809103906000f06004600050600083600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600083600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555050565b600160a060020a03821660009081526004602052604090205460ff1615156104725760405161040280610d30833901809050604051809103906000f06004600050600084600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff021916908302179055505b600160a060020a03828116600090815260046020819052604080518184205460e060020a630a3b0a4f02825286861693820193909352905161010090920490931692630a3b0a4f926024828101939192829003018183876161da5a03f11561000257505050600160a060020a03811660009081526006602052604090208054600160a060020a031916831790555b5050565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506103b957610002565b600254600160a060020a0316ff5b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f11561000257505060405151151590506105d857610002565b600160a060020a03821660009081526004602052604090205460ff1615156106915760405161040280611132833901809050604051809103906000f06004600050600084600160a060020a0316815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555060016004600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff021916908302179055505b600160a060020a03828116600090815260046020819052604080518184205460e060020a630a3b0a4f02825286861693820193909352905161010090920490931692630a3b0a4f926024828101939192829003018183876161da5a03f11561000257505050600160a060020a031660009081526005602052604090208054600160a060020a0319169091179055565b6002546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602482810192602092919082900301816000876161da5a03f115610002575050604051511515905061078357610002565b50600160a060020a0381811660009081526005602090815260408083205490931680835260049091529190205460ff161561080f576040600081812054825160e260020a632e72bafd028152600160a060020a03868116600483015293516101009092049093169263b9caebf4926024828101939192829003018183876161da5a03f115610002575050505b600160a060020a03828116600090815260056020526040812054909116146108545760406000908120600160a060020a0384169091528054600160a060020a03191690555b50600160a060020a0381811660009081526006602090815260408083205490931680835260049091529190205460ff16156108e657600160a060020a038181166000908152604080518183205460e260020a632e72bafd028252868516600483015291516101009092049093169263b9caebf4926024828101939192829003018183876161da5a03f115610002575050505b600160a060020a03828116600090815260066020526040812054909116146105005760406000908120600160a060020a0384169091528054600160a060020a0319169055505056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056", + "nonce": "7", + "storage": { + "0xffc4df2d4f3d2cffad590bed6296406ab7926ca9e74784f74a95191fa069a174": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433" + } + }, + "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f": { + "balance": "0x0", + "code": "0x606060405236156100ae5760e060020a600035046302d05d3f81146100b65780631ab9075a146100c85780632b68bb2d146101035780634cc927d7146101c557806351a34eb81461028e57806356ccb6f0146103545780635928d37f1461041d578063599efa6b146104e9578063759297bb146105b2578063771d50e11461067e578063a7f4377914610740578063f905c15a1461075e578063f92eb77414610767578063febf661214610836575b610902610002565b610904600054600160a060020a031681565b610902600435600254600160a060020a03166000148015906100f9575060025433600160a060020a03908116911614155b1561092057610002565b60025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b02606452610902916000918291600160a060020a03169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051511515905061094257610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610a0d57610002565b61090260043560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610ae957610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610bbc57610002565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610c9657610002565b61090260043560243560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610de057610002565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610ebb57610002565b60025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b02606452610902916000918291600160a060020a03169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f9e57610002565b61090260025433600160a060020a0390811691161461106957610002565b61090e60035481565b61090e60043560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750506040805180517ff92eb774000000000000000000000000000000000000000000000000000000008252600482018790529151919350600160a060020a038416925063f92eb774916024828101926020929190829003018188876161da5a03f11561000257505060405151949350505050565b61090260043560243560443560025460e360020a631c2d8fb302606090815260aa60020a6a18dbdb9d1c9858dd18dd1b026064526000918291600160a060020a039091169063e16c7d989060849060209060248187876161da5a03f1156100025750505060405180519060200150905080600160a060020a03166316c66cc6336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051511515905061107757610002565b005b6060908152602090f35b60408051918252519081900360200190f35b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5ed61af000000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152925190959286169350635ed61af092602483810193919291829003018183876161da5a03f115610002575050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517fab03fc2600000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015260248301899052808816604484015292519095928616935063ab03fc2692606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f949ae47900000000000000000000000000000000000000000000000000000000825233600160a060020a0390811660048401526024830188905292519095928616935063949ae47992604483810193919291829003018183876161da5a03f11561000257505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f46d88e7d000000000000000000000000000000000000000000000000000000008252600160a060020a0380891660048401523381166024840152604483018890529251909592861693506346d88e7d92606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5315cdde00000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152808a16602484015260448301889052925190959286169350635315cdde92606483810193919291829003018183876161da5a03f115610002575050604080517f5928d37f00000000000000000000000000000000000000000000000000000000815233600160a060020a03908116600483015287166024820152604481018690529051635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517fe68e401c00000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015280891660248401526044830188905292519095928616935063e68e401c92606483810193919291829003018183876161da5a03f1156100025750505050505050565b6040805160025460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f5152f381000000000000000000000000000000000000000000000000000000008252600160a060020a03808a1660048401528089166024840152604483018890523381166064840152925190959286169350635152f38192608483810193919291829003018183876161da5a03f115610002575050505050505050565b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f056d447000000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015292519095928616935063056d447092602483810193919291829003018183876161da5a03f115610002575050505050565b600254600160a060020a0316ff5b6040805160025460e360020a631c2d8fb302825260aa60020a6a18dbdb9d1c9858dd18dd1b0260048301529151600160a060020a03929092169163e16c7d9891602481810192602092909190829003018188876161da5a03f1156100025750506040805180517f3ae1005c00000000000000000000000000000000000000000000000000000000825233600160a060020a039081166004840152808a166024840152808916604484015260648301889052925190959286169350633ae1005c92608483810193919291829003018183876161da5a03f11561000257505050505050505056", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396" + } + }, + "0xc212e03b9e060e36facad5fd8f4435412ca22e6b": { + "balance": "0x0", + "code": "0x606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000006195", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x5842545553440000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000008ac7230489e80000", + "0x000000000000000000000000000000000000000000000000000000000000000b": "0x0000000000000000000000000000000000000000000000283c7b9181eca20000" + } + }, + "0xcf00ffd997ad14939736f026006498e3f099baaf": { + "balance": "0x0", + "code": "0x606060405236156100cf5760e060020a600035046302d05d3f81146100d7578063031e7f5d146100e95780631ab9075a1461010b5780632243118a1461014657806327aad68a1461016557806338a699a4146101da5780635188f996146101f8578063581d5d601461021e57806381738c5914610246578063977da54014610269578063a07421ce14610288578063a7f43779146102be578063bdbdb086146102dc578063e1c7111914610303578063f4f2821b14610325578063f905c15a1461034a578063f92eb77414610353575b610387610002565b610389600054600160a060020a031681565b610387600435602435600254600160a060020a0316600014156103a857610002565b610387600435600254600160a060020a031660001480159061013c575060025433600160a060020a03908116911614155b1561042957610002565b610387600435600254600160a060020a03166000141561044b57610002565b6102ac60043560008181526004602081815260408320547f524d81d3000000000000000000000000000000000000000000000000000000006060908152610100909104600160a060020a031692839263524d81d3926064928188876161da5a03f1156100025750506040515192506103819050565b61039c60043560008181526004602052604090205460ff165b919050565b6103876004356024356002546000908190600160a060020a031681141561079457610002565b61038760043560243560025460009081908190600160a060020a031681141561080457610002565b61038960043560008181526004602052604081205460ff1615156109e357610002565b610387600435600254600160a060020a0316600014156109fb57610002565b600435600090815260096020526040902054670de0b6b3a764000090810360243502045b60408051918252519081900360200190f35b61038760025433600160a060020a03908116911614610a9257610002565b600435600090815260086020526040902054670de0b6b3a7640000602435909102046102ac565b610387600435602435600254600160a060020a031660001415610aa057610002565b61038760043560025460009081908190600160a060020a0316811415610b3657610002565b6102ac60035481565b6102ac600435600081815260076020908152604080832054600690925290912054670de0b6b3a76400000204805b50919050565b005b600160a060020a03166060908152602090f35b15156060908152602090f35b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506103fe57610002565b60008281526004602052604090205460ff16151561041b57610002565b600860205260406000205550565b6002805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f11561000257505060405151151590506104a157610002565b604080516000838152600460205291909120805460ff1916600117905561040280610de2833901809050604051809103906000f0600460005060008360001916815260200190815260200160002060005060000160016101000a815481600160a060020a030219169083021790555066470de4df8200006008600050600083600019168152602001908152602001600020600050819055506703782dace9d9000060096000506000836000191681526020019081526020016000206000508190555050565b600460005060008560001916815260200190815260200160002060005060000160019054906101000a9004600160a060020a0316915081600160a060020a031663524d81d36040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151821415905061060057838152600660209081526040808320839055600790915281208190555b81600160a060020a0316630a3b0a4f846040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f11561000257505050600160a060020a038316808252600560209081526040808420879055805160e160020a6364a81ff102815290518694670de0b6b3a7640000949363c9503fe29360048181019492939183900301908290876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b939160048181019260209290919082900301816000876161da5a03f11561000257505050604051805190602001500204600660005060008660001916815260200190815260200160002060008282825054019250508190555080600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050506040518051906020015060076000506000866000191681526020019081526020016000206000828282505401925050819055505b50505050565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f11561000257505060405151151590506107e957610002565b8381526004602052604081205460ff16151561056657610002565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f115610002575050604051511515905061085957610002565b849250670de0b6b3a764000083600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575060408051805160e160020a6364a81ff102825291519189028590049650600481810192602092909190829003018188876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b9391600481810192602092909190829003018189876161da5a03f115610002575050506040518051906020015002049050806006600050600085600160a060020a0316632e94420f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750604080518051855260208681528286208054989098039097557f2e94420f00000000000000000000000000000000000000000000000000000000815290518896600483810193919291829003018187876161da5a03f115610002575050604080515183526020939093525020805490910190555050505050565b60409020546101009004600160a060020a03166101f3565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f1156100025750506040515115159050610a5157610002565b60008181526004602052604090205460ff161515610a6e57610002565b6040600020805474ffffffffffffffffffffffffffffffffffffffffff1916905550565b600254600160a060020a0316ff5b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b906084906020906024816000876161da5a03f1156100025750506040515115159050610af657610002565b60008281526004602052604090205460ff161515610b1357610002565b670de0b6b3a7640000811115610b2857610002565b600960205260406000205550565b60025460e060020a6313bc6d4b02606090815233600160a060020a03908116606452909116906313bc6d4b9060849060209060248187876161da5a03f1156100025750506040515115159050610b8b57610002565b600160a060020a038416815260056020908152604080832054808452600490925282205490935060ff161515610bc057610002565b600460005060008460001916815260200190815260200160002060005060000160019054906101000a9004600160a060020a0316915081600160a060020a031663b9caebf4856040518260e060020a0281526004018082600160a060020a031681526020019150506000604051808303816000876161da5a03f115610002575050506005600050600085600160a060020a0316815260200190815260200160002060005060009055839050600082600160a060020a031663524d81d36040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519190911115905061078e57670de0b6b3a764000081600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e060020a636f265b930282529151919291636f265b939160048181019260209290919082900301816000876161da5a03f11561000257505050604051805190602001500204600660005060008560001916815260200190815260200160002060008282825054039250508190555080600160a060020a031663c9503fe26040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050506040518051906020015060076000506000856000191681526020019081526020016000206000828282505403925050819055505050505056606060405260008054600160a060020a031916331790556103de806100246000396000f3606060405236156100615760e060020a600035046302d05d3f81146100695780630a3b0a4f1461007b5780630d327fa7146100f6578063524d81d314610109578063a7f4377914610114578063b9caebf414610132578063bbec3bae14610296575b6102ce610002565b6102d0600054600160a060020a031681565b6102ce600435600254600090600160a060020a03168114156102ed5760028054600160a060020a03199081168417808355600160a060020a03808616855260036020526040852060018101805493831694909316939093179091559154815461010060a860020a031916921661010002919091179055610372565b6102d0600254600160a060020a03165b90565b6102e3600154610106565b6102ce60005433600160a060020a039081169116146103c657610002565b6102ce600435600160a060020a038116600090815260036020526040812054819060ff16801561016457506001548190115b1561029157506040808220600180820154915461010090819004600160a060020a039081168087528587209093018054600160a060020a031916948216948517905583865293909420805461010060a860020a03191694820294909417909355600254909190811690841614156101e85760028054600160a060020a031916821790555b600254600160a060020a0390811690841614156102105760028054600160a060020a03191690555b6003600050600084600160a060020a0316815260200190815260200160002060006000820160006101000a81549060ff02191690556000820160016101000a815490600160a060020a0302191690556001820160006101000a815490600160a060020a03021916905550506001600081815054809291906001900391905055505b505050565b600160a060020a036004358181166000908152600360205260408120600101546002546102d09491821691168114156103d4576103d8565b005b600160a060020a03166060908152602090f35b6060908152602090f35b60028054600160a060020a03908116835260036020526040808420805461010060a860020a0319808216610100808a029190911790935590829004841680875283872060019081018054600160a060020a03199081168b179091559654868a168952949097209687018054949095169390951692909217909255835416908202179091555b60016003600050600084600160a060020a0316815260200190815260200160002060005060000160006101000a81548160ff0219169083021790555060016000818150548092919060010191905055505050565b600054600160a060020a0316ff5b8091505b5091905056", + "nonce": "3", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x3571d73f14f31a1463bd0a2f92f7fde1653d4e1ead7aedf4b0a5df02f16092ab": "0x0000000000000000000000000000000000000000000007d634e4c55188be0000", + "0x4e64fe2d1b72d95a0a31945cc6e4f4e524ac5ad56d6bd44a85ec7bc9cc0462c0": "0x000000000000000000000000000000000000000000000002b5e3af16b1880000" + } + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "117124093", + "extraData": "0xd5830105008650617269747986312e31322e31826d61", + "gasLimit": "4707788", + "hash": "0xad325e4c49145fb7a4058a68ac741cc8607a71114e23fc88083c7e881dd653e7", + "miner": "0x00714b9ac97fd6bd9325a059a70c9b9fa94ce050", + "mixHash": "0x0af918f65cb4af04b608fc1f14a849707696986a0e7049e97ef3981808bcc65f", + "nonce": "0x38dee147326a8d40", + "number": "25000", + "stateRoot": "0xc5d6bbcd46236fcdcc80b332ffaaa5476b980b01608f9708408cfef01b58bd5b", + "timestamp": "1479891517", + "totalDifficulty": "1895410389427" + }, + "input": "0xf88b8206628504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb80000000000000000000000000000000000000000000000280faf689c35ac00002aa0a7ee5b7877811bf671d121b40569462e722657044808dc1d6c4f1e4233ec145ba0417e7543d52b65738d9df419cbe40a708424f4d54b0fc145c0a64545a2bb1065", + "result": { + "calls": [ + { + "from": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "gas": "0x31217", + "gasUsed": "0x334", + "input": "0xe16c7d98636f6e7472616374617069000000000000000000000000000000000000000000", + "output": "0x000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x2a68d", + "gasUsed": "0x334", + "input": "0xe16c7d98636f6e747261637463746c000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x23ac9", + "gasUsed": "0x334", + "input": "0xe16c7d98636f6e7472616374646200000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000007986bad81f4cbd9317f5a46861437dae58d69113", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x23366", + "gasUsed": "0x273", + "input": "0x16c66cc6000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x7986bad81f4cbd9317f5a46861437dae58d69113", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x29f35", + "gasUsed": "0xf8d", + "input": "0x16c66cc6000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x28a9e", + "gasUsed": "0x334", + "input": "0xe16c7d98636f6e747261637463746c000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x21d79", + "gasUsed": "0x24d", + "input": "0x13bc6d4b000000000000000000000000b4fe7aa695b326c9d219158d2ca50db77b39f99f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x2165b", + "gasUsed": "0x334", + "input": "0xe16c7d986d61726b65746462000000000000000000000000000000000000000000000000", + "output": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1a8e8", + "gasUsed": "0x24d", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1a2c6", + "gasUsed": "0x3cb", + "input": "0xc9503fe2", + "output": "0x0000000000000000000000000000000000000000000000008ac7230489e80000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x19b72", + "gasUsed": "0x3cb", + "input": "0xc9503fe2", + "output": "0x0000000000000000000000000000000000000000000000008ac7230489e80000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x19428", + "gasUsed": "0x305", + "input": "0x6f265b93", + "output": "0x0000000000000000000000000000000000000000000000283c7b9181eca20000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x18d45", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "gas": "0x1734e", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x20ee1", + "gasUsed": "0x5374", + "input": "0x581d5d60000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b0000000000000000000000000000000000000000000000280faf689c35ac0000", + "output": "0x", + "to": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1b6c1", + "gasUsed": "0x334", + "input": "0xe16c7d986c6f676d67720000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1af69", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "gas": "0x143a5", + "gasUsed": "0x24d", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1a91d", + "gasUsed": "0x12fa", + "input": "0x0accce0600000000000000000000000000000000000000000000000000000000000000025842545553440000000000000000000000000000000000000000000000000000000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x", + "to": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x19177", + "gasUsed": "0x334", + "input": "0xe16c7d986c6f676d67720000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000002a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x18a22", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x18341", + "gasUsed": "0x334", + "input": "0xe16c7d986d61726b65746462000000000000000000000000000000000000000000000000", + "output": "0x000000000000000000000000cf00ffd997ad14939736f026006498e3f099baaf", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x17bec", + "gasUsed": "0x229", + "input": "0x2e94420f", + "output": "0x5842545553440000000000000000000000000000000000000000000000000000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + }, + { + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x1764e", + "gasUsed": "0x45c", + "input": "0xf92eb7745842545553440000000000000000000000000000000000000000000000000000", + "output": "0x00000000000000000000000000000000000000000000002816d180e30c390000", + "to": "0xcf00ffd997ad14939736f026006498e3f099baaf", + "type": "CALL", + "value": "0x0" + }, + { + "calls": [ + { + "from": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "gas": "0x108ba", + "gasUsed": "0x24d", + "input": "0x13bc6d4b0000000000000000000000003e9286eafa2db8101246c2131c09b49080d00690", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x2cccf5e0538493c235d1c5ef6580f77d99e91396", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "gas": "0x16e62", + "gasUsed": "0xebb", + "input": "0x645a3b72584254555344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002816d180e30c390000", + "output": "0x", + "to": "0x2a98c5f40bfa3dee83431103c535f6fae9a8ad38", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "gas": "0x283b9", + "gasUsed": "0xc51c", + "input": "0x949ae479000000000000000000000000c212e03b9e060e36facad5fd8f4435412ca22e6b0000000000000000000000000000000000000000000000280faf689c35ac0000", + "output": "0x", + "to": "0x3e9286eafa2db8101246c2131c09b49080d00690", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "gas": "0x30b4a", + "gasUsed": "0xedb7", + "input": "0x51a34eb80000000000000000000000000000000000000000000000280faf689c35ac0000", + "output": "0x", + "to": "0xb4fe7aa695b326c9d219158d2ca50db77b39f99f", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0x70c9217d814985faef62b124420f8dfbddd96433", + "gas": "0x3d090", + "gasUsed": "0x1810b", + "input": "0x51a34eb80000000000000000000000000000000000000000000000280faf689c35ac0000", + "output": "0x", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/delegatecall.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/delegatecall.json new file mode 100644 index 0000000..2cd28ba --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/delegatecall.json @@ -0,0 +1,97 @@ +{ + "context": { + "difficulty": "31927752", + "gasLimit": "4707788", + "miner": "0x5659922ce141eedbc2733678f9806c77b4eebee8", + "number": "11495", + "timestamp": "1479735917" + }, + "genesis": { + "alloc": { + "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff": { + "balance": "0x0", + "code": "0x606060405236156100825760e060020a60003504630a0313a981146100875780630a3b0a4f146101095780630cd40fea1461021257806329092d0e1461021f5780634cd06a5f146103295780635dbe47e8146103395780637a9e5410146103d9578063825db5f7146103e6578063a820b44d146103f3578063efa52fb31461047a575b610002565b34610002576104fc600435600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a26333556e849091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f415610002575050604051519150505b919050565b346100025761051060043560006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a2637d65837a9091336000604051602001526040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515115905061008257604080517f21ce24d4000000000000000000000000000000000000000000000000000000008152600060048201819052600160a060020a038416602483015291517342b02b5deeb78f34cd5ac896473b63e6c99a71a2926321ce24d49260448082019391829003018186803b156100025760325a03f415610002575050505b50565b3461000257610512600181565b346100025761051060043560006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a2637d65837a9091336000604051602001526040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515115905061008257604080517f89489a87000000000000000000000000000000000000000000000000000000008152600060048201819052600160a060020a038416602483015291517342b02b5deeb78f34cd5ac896473b63e6c99a71a2926389489a879260448082019391829003018186803b156100025760325a03f4156100025750505061020f565b3461000257610528600435610403565b34610002576104fc600435604080516000602091820181905282517f7d65837a00000000000000000000000000000000000000000000000000000000815260048101829052600160a060020a0385166024820152925190927342b02b5deeb78f34cd5ac896473b63e6c99a71a292637d65837a92604480840193829003018186803b156100025760325a03f4156100025750506040515191506101049050565b3461000257610512600c81565b3461000257610512600081565b3461000257610528600061055660005b600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a263685a1f3c9091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515191506101049050565b346100025761053a600435600060006000507342b02b5deeb78f34cd5ac896473b63e6c99a71a263f775b6b59091846000604051602001526040518360e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100025760325a03f4156100025750506040515191506101049050565b604080519115158252519081900360200190f35b005b6040805160ff9092168252519081900360200190f35b60408051918252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b90509056", + "nonce": "1", + "storage": { + "0x4d140b25abf3c71052885c66f73ce07cff141c1afabffdaf5cba04d625b7ebcc": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + }, + "0x269296dddce321a6bcbaa2f0181127593d732cba": { + "balance": "0x0", + "code": "0x606060405236156101275760e060020a60003504630cd40fea811461012c578063173825d9146101395780631849cb5a146101c7578063285791371461030f5780632a58b3301461033f5780632cb0d48a146103565780632f54bf6e1461036a578063332b9f061461039d5780633ca8b002146103c55780633df4ddf4146103d557806341c0e1b5146103f457806347799da81461040557806362a51eee1461042457806366907d13146104575780637065cb48146104825780637a9e541014610496578063825db5f7146104a3578063949d225d146104b0578063a51687df146104c7578063b4da4e37146104e6578063b4e6850b146104ff578063bd7474ca14610541578063e75623d814610541578063e9938e1114610555578063f5d241d314610643575b610002565b3461000257610682600181565b34610002576106986004356106ff335b60006001600a9054906101000a9004600160a060020a0316600160a060020a0316635dbe47e8836000604051602001526040518260e060020a0281526004018082600160a060020a03168152602001915050602060405180830381600087803b156100025760325a03f1156100025750506040515191506103989050565b3461000257604080516101008082018352600080835260208084018290528385018290526060808501839052608080860184905260a080870185905260c080880186905260e09788018690526001605060020a0360043581168752600586529589902089519788018a528054808816808a52605060020a91829004600160a060020a0316978a01889052600183015463ffffffff8082169d8c018e905264010000000082048116988c01899052604060020a90910416958a018690526002830154948a01859052600390920154808916938a01849052049096169690970186905293969495949293604080516001605060020a03998a16815297891660208901529590971686860152600160a060020a03909316606086015263ffffffff9182166080860152811660a08501521660c083015260e08201929092529051908190036101000190f35b346100025761069a60043560018054600091829160ff60f060020a909104161515141561063d5761072833610376565b34610002576106ae6004546001605060020a031681565b34610002576106986004356108b333610149565b346100025761069a6004355b600160a060020a03811660009081526002602052604090205460ff1615156001145b919050565b34610002576106986001805460ff60f060020a9091041615151415610913576108ed33610376565b346100025761069a600435610149565b34610002576106ae6003546001605060020a03605060020a9091041681565b346100025761069861091533610149565b34610002576106ae6003546001605060020a0360a060020a9091041681565b346100025761069a60043560243560018054600091829160ff60f060020a909104161515141561095e5761092633610376565b34610002576106986004356001805460ff60f060020a909104161515141561072557610a8b33610376565b3461000257610698600435610aa533610149565b3461000257610682600c81565b3461000257610682600081565b34610002576106ae6003546001605060020a031681565b34610002576106ca600154600160a060020a03605060020a9091041681565b346100025761069a60015460ff60f060020a9091041681565b346100025761069a60043560243560443560643560843560a43560c43560018054600091829160ff60f060020a9091041615151415610b5857610ad233610376565b3461000257610698600435610bd633610149565b34610002576106e6600435604080516101008181018352600080835260208084018290528385018290526060808501839052608080860184905260a080870185905260c080880186905260e09788018690526001605060020a03808b168752600586529589902089519788018a5280548088168952600160a060020a03605060020a918290041696890196909652600181015463ffffffff8082169b8a019b909b5264010000000081048b1695890195909552604060020a90940490981691860182905260028301549086015260039091015480841696850196909652940416918101919091525b50919050565b346100025761069a60043560243560443560643560843560a43560018054600091829160ff60f060020a9091041615151415610c8e57610bfb33610376565b6040805160ff9092168252519081900360200190f35b005b604080519115158252519081900360200190f35b604080516001605060020a039092168252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b6040805163ffffffff9092168252519081900360200190f35b1561012757600160a060020a0381166000908152600260205260409020805460ff191690555b50565b1561063d57506001605060020a0380831660009081526005602052604090208054909116151561075b576000915061063d565b604080516101008101825282546001605060020a038082168352600160a060020a03605060020a92839004166020840152600185015463ffffffff80821695850195909552640100000000810485166060850152604060020a90049093166080830152600284015460a0830152600384015480841660c08401520490911660e0820152610817905b8051600354600090819060016001605060020a0390911611610c995760038054605060020a60f060020a0319169055610ddf565b600380546001605060020a031981166000196001605060020a03928316011782558416600090815260056020526040812080547fffff000000000000000000000000000000000000000000000000000000000000168155600181810180546bffffffffffffffffffffffff191690556002820192909255909101805473ffffffffffffffffffffffffffffffffffffffff19169055915061063d565b1561012757600180547fff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1660f060020a8302179055610725565b1561091357600480546001605060020a031981166001605060020a039091166001011790555b565b156101275733600160a060020a0316ff5b1561095e57506001605060020a03808416600090815260056020526040902080549091161515610965576000915061095e565b600191505b5092915050565b60038101546001605060020a0384811691161415610986576001915061095e565b604080516101008101825282546001605060020a038082168352600160a060020a03605060020a92839004166020840152600185015463ffffffff80821695850195909552640100000000810485166060850152604060020a90049093166080830152600284015460a0830152600384015480841660c08401520490911660e0820152610a12906107e3565b61095983825b80546003546001605060020a0391821691600091161515610de55760038054605060020a60a060020a031916605060020a84021760a060020a69ffffffffffffffffffff02191660a060020a84021781558301805473ffffffffffffffffffffffffffffffffffffffff19169055610ddf565b1561072557600480546001605060020a0319168217905550565b1561012757600160a060020a0381166000908152600260205260409020805460ff19166001179055610725565b15610b5857506001605060020a038088166000908152600560205260409020805490911615610b645760009150610b58565b6004546001605060020a0390811690891610610b3057600480546001605060020a03191660018a011790555b6003805460016001605060020a03821681016001605060020a03199092169190911790915591505b50979650505050505050565b80546001605060020a0319168817605060020a60f060020a031916605060020a880217815560018101805463ffffffff1916871767ffffffff0000000019166401000000008702176bffffffff00000000000000001916604060020a860217905560028101839055610b048982610a18565b156101275760018054605060020a60f060020a031916605060020a8302179055610725565b15610c8e57506001605060020a03808816600090815260056020526040902080549091161515610c2e5760009150610c8e565b8054605060020a60f060020a031916605060020a88021781556001808201805463ffffffff1916881767ffffffff0000000019166401000000008802176bffffffff00000000000000001916604060020a87021790556002820184905591505b509695505050505050565b6003546001605060020a03848116605060020a909204161415610d095760e084015160038054605060020a928302605060020a60a060020a031990911617808255919091046001605060020a031660009081526005602052604090200180546001605060020a0319169055610ddf565b6003546001605060020a0384811660a060020a909204161415610d825760c08401516003805460a060020a92830260a060020a69ffffffffffffffffffff021990911617808255919091046001605060020a03166000908152600560205260409020018054605060020a60a060020a0319169055610ddf565b505060c082015160e08301516001605060020a0380831660009081526005602052604080822060039081018054605060020a60a060020a031916605060020a8702179055928416825290200180546001605060020a031916831790555b50505050565b6001605060020a0384161515610e6457600380546001605060020a03605060020a9182900481166000908152600560205260409020830180546001605060020a0319908116871790915583548785018054918590049093168402605060020a60a060020a03199182161790911690915582549185029116179055610ddf565b506001605060020a038381166000908152600560205260409020600390810180549185018054605060020a60a060020a0319908116605060020a94859004909516808502959095176001605060020a0319168817909155815416918402919091179055801515610ef4576003805460a060020a69ffffffffffffffffffff02191660a060020a8402179055610ddf565b6003808401546001605060020a03605060020a9091041660009081526005602052604090200180546001605060020a031916831790555050505056", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000113204f5d64c28326fd7bd05fd4ea855302d7f2ff00000000000000000000" + } + }, + "0x42b02b5deeb78f34cd5ac896473b63e6c99a71a2": { + "balance": "0x0", + "code": "0x6504032353da7150606060405236156100695760e060020a60003504631bf7509d811461006e57806321ce24d41461008157806333556e84146100ec578063685a1f3c146101035780637d65837a1461011757806389489a8714610140578063f775b6b5146101fc575b610007565b61023460043560006100fd82600061010d565b610246600435602435600160a060020a03811660009081526020839052604081205415156102cb57826001016000508054806001018281815481835581811511610278576000838152602090206102789181019083015b808211156102d057600081556001016100d8565b610248600435602435600182015481105b92915050565b6102346004356024355b60018101906100fd565b610248600435602435600160a060020a03811660009081526020839052604090205415156100fd565b61024660043560243580600160a060020a031632600160a060020a03161415156101f857600160a060020a038116600090815260208390526040902054156101f857600160a060020a038116600090815260208390526040902054600183018054909160001901908110156100075760009182526020808320909101805473ffffffffffffffffffffffffffffffffffffffff19169055600160a060020a038316825283905260408120556002820180546000190190555b5050565b61025c60043560243560008260010160005082815481101561000757600091825260209091200154600160a060020a03169392505050565b60408051918252519081900360200190f35b005b604080519115158252519081900360200190f35b60408051600160a060020a039092168252519081900360200190f35b50505060009283526020808420909201805473ffffffffffffffffffffffffffffffffffffffff191686179055600160a060020a0385168352908590526040909120819055600284018054600101905590505b505050565b509056", + "nonce": "1", + "storage": {} + }, + "0xa529806c67cc6486d4d62024471772f47f6fd672": { + "balance": "0x67820e39ac8fe9800", + "code": "0x", + "nonce": "68", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "31912170", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712388", + "hash": "0x0855914bdc581bccdc62591fd438498386ffb59ea4d5361ed5c3702e26e2c72f", + "miner": "0x334391aa808257952a462d1475562ee2106a6c90", + "mixHash": "0x64bb70b8ca883cadb8fbbda2c70a861612407864089ed87b98e5de20acceada6", + "nonce": "0x684129f283aaef18", + "number": "11494", + "stateRoot": "0x7057f31fe3dab1d620771adad35224aae43eb70e94861208bc84c557ff5b9d10", + "timestamp": "1479735912", + "totalDifficulty": "90744064339" + }, + "input": "0xf889448504a817c800832dc6c094269296dddce321a6bcbaa2f0181127593d732cba80a47065cb480000000000000000000000001523e55a1ca4efbae03355775ae89f8d7699ad9e29a080ed81e4c5e9971a730efab4885566e2c868cd80bd4166d0ed8c287fdf181650a069d7c49215e3d4416ad239cd09dbb71b9f04c16b33b385d14f40b618a7a65115", + "result": { + "calls": [ + { + "calls": [ + { + "from": "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff", + "gas": "0x2bf459", + "gasUsed": "0x2aa", + "input": "0x7d65837a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a529806c67cc6486d4d62024471772f47f6fd672", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x42b02b5deeb78f34cd5ac896473b63e6c99a71a2", + "type": "DELEGATECALL" + } + ], + "from": "0x269296dddce321a6bcbaa2f0181127593d732cba", + "gas": "0x2cae73", + "gasUsed": "0xa9d", + "input": "0x5dbe47e8000000000000000000000000a529806c67cc6486d4d62024471772f47f6fd672", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x13204f5d64c28326fd7bd05fd4ea855302d7f2ff", + "type": "CALL", + "value": "0x0" + } + ], + "from": "0xa529806c67cc6486d4d62024471772f47f6fd672", + "gas": "0x2dc6c0", + "gasUsed": "0xbd55", + "input": "0x7065cb480000000000000000000000001523e55a1ca4efbae03355775ae89f8d7699ad9e", + "output": "0x", + "to": "0x269296dddce321a6bcbaa2f0181127593d732cba", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_create_oog_outer_throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_create_oog_outer_throw.json new file mode 100644 index 0000000..07fda21 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_create_oog_outer_throw.json @@ -0,0 +1,77 @@ +{ + "context": { + "difficulty": "3451177886", + "gasLimit": "4709286", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2290744", + "timestamp": "1513616439" + }, + "genesis": { + "alloc": { + "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a": { + "balance": "0x0", + "code": "0x606060405263ffffffff60e060020a6000350416633b91f50681146100505780635bb47808146100715780635f51fca01461008c578063bc7647a9146100ad578063f1bd0d7a146100c8575b610000565b346100005761006f600160a060020a03600435811690602435166100e9565b005b346100005761006f600160a060020a0360043516610152565b005b346100005761006f600160a060020a036004358116906024351661019c565b005b346100005761006f600160a060020a03600435166101fa565b005b346100005761006f600160a060020a0360043581169060243516610db8565b005b600160a060020a038083166000908152602081905260408120549091908116903316811461011657610000565b839150600160a060020a038316151561012d573392505b6101378284610e2e565b6101418284610db8565b61014a826101fa565b5b5b50505050565b600154600160a060020a03908116903316811461016e57610000565b6002805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0384161790555b5b5050565b600254600160a060020a0390811690331681146101b857610000565b600160a060020a038381166000908152602081905260409020805473ffffffffffffffffffffffffffffffffffffffff19169184169190911790555b5b505050565b6040805160e260020a631a481fc102815260016024820181905260026044830152606482015262093a8060848201819052600060a4830181905260c06004840152601e60c48401527f736574456e7469747953746174757328616464726573732c75696e743829000060e484015292519091600160a060020a038516916369207f049161010480820192879290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526000602482018190526001604483015260606004830152602360648301527f626567696e506f6c6c28616464726573732c75696e7436342c626f6f6c2c626f60848301527f6f6c29000000000000000000000000000000000000000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601960c48201527f61646453746f636b28616464726573732c75696e74323536290000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601960c48201527f697373756553746f636b2875696e74382c75696e74323536290000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152602160648301527f6772616e7453746f636b2875696e74382c75696e743235362c61646472657373608483015260f860020a60290260a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f115610000575050604080517f010555b8000000000000000000000000000000000000000000000000000000008152600160a060020a03338116602483015260006044830181905260606004840152603c60648401527f6772616e7456657374656453746f636b2875696e74382c75696e743235362c6160848401527f6464726573732c75696e7436342c75696e7436342c75696e743634290000000060a48401529251908716935063010555b89260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152601260c48201527f626567696e53616c65286164647265737329000000000000000000000000000060e48201529051600160a060020a03861692506369207f04916101048082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152601a60648301527f7472616e7366657253616c6546756e64732875696e743235362900000000000060848301529151600160a060020a038716935063de64e15c9260a48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260016024820181905260026044830152606482015267ffffffffffffffff8416608482015260ff851660a482015260c06004820152602d60c48201527f7365744163636f756e74696e6753657474696e67732875696e743235362c756960e48201527f6e7436342c75696e7432353629000000000000000000000000000000000000006101048201529051600160a060020a03861692506369207f04916101248082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152603460648301527f637265617465526563757272696e6752657761726428616464726573732c756960848301527f6e743235362c75696e7436342c737472696e672900000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152601b60648301527f72656d6f7665526563757272696e675265776172642875696e7429000000000060848301529151600160a060020a038716935063de64e15c9260a48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a63379938570281526002602482015260006044820181905260606004830152602360648301527f697373756552657761726428616464726573732c75696e743235362c7374726960848301527f6e6729000000000000000000000000000000000000000000000000000000000060a48301529151600160a060020a038716935063de64e15c9260c48084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a6337993857028152600160248201819052604482015260606004820152602260648201527f61737369676e53746f636b2875696e74382c616464726573732c75696e743235608482015260f060020a6136290260a48201529051600160a060020a038616925063de64e15c9160c48082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a6337993857028152600160248201819052604482015260606004820152602260648201527f72656d6f766553746f636b2875696e74382c616464726573732c75696e743235608482015260f060020a6136290260a48201529051600160a060020a038616925063de64e15c9160c48082019260009290919082900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc102815260026024808301919091526003604483015260006064830181905267ffffffffffffffff8616608484015260ff871660a484015260c0600484015260c48301919091527f7365744164647265737342796c617728737472696e672c616464726573732c6260e48301527f6f6f6c29000000000000000000000000000000000000000000000000000000006101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc1028152600260248201526003604482015260006064820181905267ffffffffffffffff8516608483015260ff861660a483015260c06004830152602160c48301527f73657453746174757342796c617728737472696e672c75696e74382c626f6f6c60e483015260f860020a6029026101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f1156100005750506040805160e260020a631a481fc1028152600260248201526003604482015260006064820181905267ffffffffffffffff8516608483015260ff861660a483015260c06004830152603860c48301527f736574566f74696e6742796c617728737472696e672c75696e743235362c756960e48301527f6e743235362c626f6f6c2c75696e7436342c75696e74382900000000000000006101048301529151600160a060020a03871693506369207f04926101248084019391929182900301818387803b156100005760325a03f115610000575050505b505050565b604080517f225553a4000000000000000000000000000000000000000000000000000000008152600160a060020a0383811660048301526002602483015291519184169163225553a49160448082019260009290919082900301818387803b156100005760325a03f115610000575050505b5050565b600082604051611fd280610f488339600160a060020a03909216910190815260405190819003602001906000f0801561000057905082600160a060020a03166308b027418260016040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a0316815260200182815260200192505050600060405180830381600087803b156100005760325a03f115610000575050604080517fa14e3ee300000000000000000000000000000000000000000000000000000000815260006004820181905260016024830152600160a060020a0386811660448401529251928716935063a14e3ee39260648084019382900301818387803b156100005760325a03f115610000575050505b5050505600606060405234620000005760405160208062001fd283398101604052515b805b600a8054600160a060020a031916600160a060020a0383161790555b506001600d819055600e81905560408051808201909152600c8082527f566f74696e672053746f636b00000000000000000000000000000000000000006020928301908152600b805460008290528251601860ff1990911617825590947f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9600291831615610100026000190190921604601f0193909304830192906200010c565b828001600101855582156200010c579182015b828111156200010c578251825591602001919060010190620000ef565b5b50620001309291505b808211156200012c576000815560010162000116565b5090565b50506040805180820190915260038082527f43565300000000000000000000000000000000000000000000000000000000006020928301908152600c805460008290528251600660ff1990911617825590937fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c760026001841615610100026000190190931692909204601f010481019291620001f7565b82800160010185558215620001f7579182015b82811115620001f7578251825591602001919060010190620001da565b5b506200021b9291505b808211156200012c576000815560010162000116565b5090565b50505b505b611da280620002306000396000f3006060604052361561019a5763ffffffff60e060020a600035041662e1986d811461019f57806302a72a4c146101d657806306eb4e421461020157806306fdde0314610220578063095ea7b3146102ad578063158ccb99146102dd57806318160ddd146102f85780631cf65a781461031757806323b872dd146103365780632c71e60a1461036c57806333148fd6146103ca578063435ebc2c146103f55780635eeb6e451461041e578063600e85b71461043c5780636103d70b146104a157806362c1e46a146104b05780636c182e99146104ba578063706dc87c146104f057806370a082311461052557806377174f851461055057806395d89b411461056f578063a7771ee3146105fc578063a9059cbb14610629578063ab377daa14610659578063b25dbb5e14610685578063b89a73cb14610699578063ca5eb5e1146106c6578063cbcf2e5a146106e1578063d21f05ba1461070e578063d347c2051461072d578063d96831e114610765578063dd62ed3e14610777578063df3c211b146107a8578063e2982c21146107d6578063eb944e4c14610801575b610000565b34610000576101d4600160a060020a036004351660243567ffffffffffffffff6044358116906064358116906084351661081f565b005b34610000576101ef600160a060020a0360043516610a30565b60408051918252519081900360200190f35b34610000576101ef610a4f565b60408051918252519081900360200190f35b346100005761022d610a55565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516602435610ae3565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516610b4e565b005b34610000576101ef610b89565b60408051918252519081900360200190f35b34610000576101ef610b8f565b60408051918252519081900360200190f35b34610000576102c9600160a060020a0360043581169060243516604435610b95565b604080519115158252519081900360200190f35b3461000057610388600160a060020a0360043516602435610bb7565b60408051600160a060020a039096168652602086019490945267ffffffffffffffff928316858501529082166060850152166080830152519081900360a00190f35b34610000576101ef600160a060020a0360043516610c21565b60408051918252519081900360200190f35b3461000057610402610c40565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d4600160a060020a0360043516602435610c4f565b005b3461000057610458600160a060020a0360043516602435610cc9565b60408051600160a060020a03909716875260208701959095528585019390935267ffffffffffffffff9182166060860152811660808501521660a0830152519081900360c00190f35b34610000576101d4610d9e565b005b6101d4610e1e565b005b34610000576104d3600160a060020a0360043516610e21565b6040805167ffffffffffffffff9092168252519081900360200190f35b3461000057610402600160a060020a0360043516610ead565b60408051600160a060020a039092168252519081900360200190f35b34610000576101ef600160a060020a0360043516610ef9565b60408051918252519081900360200190f35b34610000576101ef610f18565b60408051918252519081900360200190f35b346100005761022d610f1e565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516610fac565b604080519115158252519081900360200190f35b34610000576102c9600160a060020a0360043516602435610fc2565b604080519115158252519081900360200190f35b3461000057610402600435610fe2565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d46004351515610ffd565b005b34610000576102c9600160a060020a036004351661104c565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516611062565b005b34610000576102c9600160a060020a0360043516611070565b604080519115158252519081900360200190f35b34610000576101ef6110f4565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351667ffffffffffffffff602435166110fa565b60408051918252519081900360200190f35b34610000576101d4600435611121565b005b34610000576101ef600160a060020a03600435811690602435166111c6565b60408051918252519081900360200190f35b34610000576101ef6004356024356044356064356084356111f3565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351661128c565b60408051918252519081900360200190f35b34610000576101d4600160a060020a036004351660243561129e565b005b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915267ffffffffffffffff848116908416101561086457610000565b8367ffffffffffffffff168267ffffffffffffffff16101561088557610000565b8267ffffffffffffffff168267ffffffffffffffff1610156108a657610000565b506040805160a081018252600160a060020a033381168252602080830188905267ffffffffffffffff80871684860152858116606085015287166080840152908816600090815260039091529190912080546001810180835582818380158290116109615760030281600302836000526020600020918201910161096191905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050916000526020600020906003020160005b5082518154600160a060020a031916600160a060020a03909116178155602083015160018201556040830151600290910180546060850151608086015167ffffffffffffffff1990921667ffffffffffffffff948516176fffffffffffffffff00000000000000001916604060020a918516919091021777ffffffffffffffff000000000000000000000000000000001916608060020a939091169290920291909117905550610a268686610fc2565b505b505050505050565b600160a060020a0381166000908152600360205260409020545b919050565b60055481565b600b805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b600160a060020a03338116600081815260026020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b600a5433600160a060020a03908116911614610b6957610000565b600a8054600160a060020a031916600160a060020a0383161790555b5b50565b60005481565b60005b90565b6000610ba2848484611600565b610bad8484846116e2565b90505b9392505050565b600360205281600052604060002081815481101561000057906000526020600020906003020160005b5080546001820154600290920154600160a060020a03909116935090915067ffffffffffffffff80821691604060020a8104821691608060020a9091041685565b600160a060020a0381166000908152600860205260409020545b919050565b600a54600160a060020a031681565b600a5433600160a060020a03908116911614610c6a57610000565b610c7660005482611714565b6000908155600160a060020a038316815260016020526040902054610c9b9082611714565b600160a060020a038316600090815260016020526040812091909155610cc390839083611600565b5b5b5050565b6000600060006000600060006000600360008a600160a060020a0316600160a060020a0316815260200190815260200160002088815481101561000057906000526020600020906003020160005b508054600182015460028301546040805160a081018252600160a060020a039094168085526020850184905267ffffffffffffffff808416928601839052604060020a8404811660608701819052608060020a9094041660808601819052909c50929a509197509095509350909150610d90904261172d565b94505b509295509295509295565b33600160a060020a038116600090815260066020526040902054801515610dc457610000565b8030600160a060020a0316311015610ddb57610000565b600160a060020a0382166000818152600660205260408082208290555183156108fc0291849190818181858888f193505050501515610cc357610000565b5b5050565b5b565b600160a060020a03811660009081526003602052604081205442915b81811015610ea557600160a060020a03841660009081526003602052604090208054610e9a9190839081101561000057906000526020600020906003020160005b5060020154604060020a900467ffffffffffffffff168461177d565b92505b600101610e3d565b5b5050919050565b600160a060020a0380821660009081526007602052604081205490911615610eef57600160a060020a0380831660009081526007602052604090205416610ef1565b815b90505b919050565b600160a060020a0381166000908152600160205260409020545b919050565b600d5481565b600c805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b60006000610fb983610c21565b1190505b919050565b6000610fcf338484611600565b610fd983836117ac565b90505b92915050565b600460205260009081526040902054600160a060020a031681565b8015801561101a575061100f33610ef9565b61101833610c21565b115b1561102457610000565b33600160a060020a03166000908152600960205260409020805460ff19168215151790555b50565b60006000610fb983610ef9565b1190505b919050565b610b8533826117dc565b5b50565b600a54604080516000602091820181905282517fcbcf2e5a000000000000000000000000000000000000000000000000000000008152600160a060020a03868116600483015293519194939093169263cbcf2e5a92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b600e5481565b6000610fd961110984846118b2565b61111385856119b6565b611a05565b90505b92915050565b600a5433600160a060020a0390811691161461113c57610000565b61114860005482611a1f565b600055600554600190101561116c57600a5461116c90600160a060020a0316611a47565b5b600a54600160a060020a03166000908152600160205260409020546111929082611a1f565b600a8054600160a060020a039081166000908152600160205260408120939093559054610b8592911683611600565b5b5b50565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b6000600060008487101561120a5760009250611281565b8387111561121a57879250611281565b61123f6112308961122b888a611714565b611a90565b61123a8689611714565b611abc565b915081925061124e8883611714565b905061127e8361127961126a8461122b8c8b611714565b611a90565b61123a888b611714565b611abc565b611a1f565b92505b505095945050505050565b60066020526000908152604090205481565b600160a060020a03821660009081526003602052604081208054829190849081101561000057906000526020600020906003020160005b50805490925033600160a060020a039081169116146112f357610000565b6040805160a0810182528354600160a060020a0316815260018401546020820152600284015467ffffffffffffffff80821693830193909352604060020a810483166060830152608060020a900490911660808201526113539042611af9565b600160a060020a0385166000908152600360205260409020805491925090849081101561000057906000526020600020906003020160005b508054600160a060020a031916815560006001820181905560029091018054600160c060020a0319169055600160a060020a0385168152600360205260409020805460001981019081101561000057906000526020600020906003020160005b50600160a060020a03851660009081526003602052604090208054859081101561000057906000526020600020906003020160005b5081548154600160a060020a031916600160a060020a03918216178255600180840154908301556002928301805493909201805467ffffffffffffffff191667ffffffffffffffff948516178082558354604060020a908190048616026fffffffffffffffff000000000000000019909116178082559254608060020a9081900490941690930277ffffffffffffffff00000000000000000000000000000000199092169190911790915584166000908152600360205260409020805460001981018083559190829080158290116115485760030281600302836000526020600020918201910161154891905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050600160a060020a033316600090815260016020526040902054611570915082611a1f565b600160a060020a03338116600090815260016020526040808220939093559086168152205461159f9082611714565b600160a060020a038086166000818152600160209081526040918290209490945580518581529051339093169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35b50505050565b600160a060020a0383161561166e576116466008600061161f86610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611714565b6008600061165386610ead565b600160a060020a031681526020810191909152604001600020555b600160a060020a038216156116dc576116b46008600061168d85610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611a1f565b600860006116c185610ead565b600160a060020a031681526020810191909152604001600020555b5b505050565b600083826116f082426110fa565b8111156116fc57610000565b611707868686611b1b565b92505b5b50509392505050565b600061172283831115611b4d565b508082035b92915050565b6000610fd983602001518367ffffffffffffffff16856080015167ffffffffffffffff16866040015167ffffffffffffffff16876060015167ffffffffffffffff166111f3565b90505b92915050565b60008167ffffffffffffffff168367ffffffffffffffff1610156117a15781610fd9565b825b90505b92915050565b600033826117ba82426110fa565b8111156117c657610000565b6117d08585611b5d565b92505b5b505092915050565b6117e582610ef9565b6117ee83610c21565b11156117f957610000565b600160a060020a03811660009081526009602052604090205460ff16158015611834575081600160a060020a031681600160a060020a031614155b1561183e57610000565b61184782611070565b1561185157610000565b611864828261185f85610ef9565b611600565b600160a060020a0382811660009081526007602052604090208054600160a060020a031916918316918217905561189a82610ead565b600160a060020a031614610cc357610000565b5b5050565b600160a060020a038216600090815260036020526040812054815b818110156119885761197d836112796003600089600160a060020a0316600160a060020a0316815260200190815260200160002084815481101561000057906000526020600020906003020160005b506040805160a0810182528254600160a060020a031681526001830154602082015260029092015467ffffffffffffffff80821692840192909252604060020a810482166060840152608060020a900416608082015287611af9565b611a1f565b92505b6001016118cd565b600160a060020a0385166000908152600160205260409020546117d09084611714565b92505b505092915050565b600060006119c384611070565b80156119d157506000600d54115b90506119fb816119e9576119e485610ef9565b6119ec565b60005b6111138686611b7b565b611a05565b91505b5092915050565b60008183106117a15781610fd9565b825b90505b92915050565b6000828201611a3c848210801590611a375750838210155b611b4d565b8091505b5092915050565b611a508161104c565b15611a5a57610b85565b6005805460009081526004602052604090208054600160a060020a031916600160a060020a038416179055805460010190555b50565b6000828202611a3c841580611a37575083858381156100005704145b611b4d565b8091505b5092915050565b60006000611acc60008411611b4d565b8284811561000057049050611a3c838581156100005706828502018514611b4d565b8091505b5092915050565b6000610fd98360200151611b0d858561172d565b611714565b90505b92915050565b60008382611b2982426110fa565b811115611b3557610000565b611707868686611b8f565b92505b5b50509392505050565b801515610b8557610000565b5b50565b6000611b6883611a47565b610fd98383611c92565b90505b92915050565b6000610fd983610ef9565b90505b92915050565b600160a060020a038084166000908152600260209081526040808320338516845282528083205493861683526001909152812054909190611bd09084611a1f565b600160a060020a038086166000908152600160205260408082209390935590871681522054611bff9084611714565b600160a060020a038616600090815260016020526040902055611c228184611714565b600160a060020a038087166000818152600260209081526040808320338616845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600191505b509392505050565b60003382611ca082426110fa565b811115611cac57610000565b6117d08585611cc2565b92505b5b505092915050565b600160a060020a033316600090815260016020526040812054611ce59083611714565b600160a060020a033381166000908152600160205260408082209390935590851681522054611d149083611a1f565b600160a060020a038085166000818152600160209081526040918290209490945580518681529051919333909316927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b929150505600a165627a7a72305820bfa5ddd3fecf3f43aed25385ec7ec3ef79638c2e58d99f85d9a3cc494183bf160029a165627a7a723058200e78a5f7e0f91739035d0fbf5eca02f79377210b722f63431f29a22e2880b3bd0029", + "nonce": "789", + "storage": { + "0xfe9ec0542a1c009be8b1f3acf43af97100ffff42eb736850fb038fa1151ad4d9": "0x000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e8" + } + }, + "0x5cb4a6b902fcb21588c86c3517e797b07cdaadb9": { + "balance": "0x0", + "code": "0x", + "nonce": "0", + "storage": {} + }, + "0xe4a13bc304682a903e9472f469c33801dd18d9e8": { + "balance": "0x33c763c929f62c4f", + "code": "0x", + "nonce": "14", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3451177886", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4713874", + "hash": "0x5d52a672417cd1269bf4f7095e25dcbf837747bba908cd5ef809dc1bd06144b5", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x01a12845ed546b94a038a7a03e8df8d7952024ed41ccb3db7a7ade4abc290ce1", + "nonce": "0x28c446f1cb9748c1", + "number": "2290743", + "stateRoot": "0x4898aceede76739daef76448a367d10015a2c022c9e7909b99a10fbf6fb16708", + "timestamp": "1513616414", + "totalDifficulty": "7146523769022564" + }, + "input": "0xf8aa0e8509502f9000830493e0941d3ddf7caf024f253487e18bc4a15b1a360c170a80b8443b91f506000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e829a0524564944fa419f5c189b5074044f89210c6d6b2d77ee8f7f12a927d59b636dfa0015b28986807a424b18b186ee6642d76739df36cad802d20e8c00e79a61d7281", + "result": { + "calls": [ + { + "error": "internal failure", + "from": "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a", + "gas": "0x39ff0", + "gasUsed": "0x39ff0", + "input": "0x606060405234620000005760405160208062001fd283398101604052515b805b600a8054600160a060020a031916600160a060020a0383161790555b506001600d819055600e81905560408051808201909152600c8082527f566f74696e672053746f636b00000000000000000000000000000000000000006020928301908152600b805460008290528251601860ff1990911617825590947f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9600291831615610100026000190190921604601f0193909304830192906200010c565b828001600101855582156200010c579182015b828111156200010c578251825591602001919060010190620000ef565b5b50620001309291505b808211156200012c576000815560010162000116565b5090565b50506040805180820190915260038082527f43565300000000000000000000000000000000000000000000000000000000006020928301908152600c805460008290528251600660ff1990911617825590937fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c760026001841615610100026000190190931692909204601f010481019291620001f7565b82800160010185558215620001f7579182015b82811115620001f7578251825591602001919060010190620001da565b5b506200021b9291505b808211156200012c576000815560010162000116565b5090565b50505b505b611da280620002306000396000f3006060604052361561019a5763ffffffff60e060020a600035041662e1986d811461019f57806302a72a4c146101d657806306eb4e421461020157806306fdde0314610220578063095ea7b3146102ad578063158ccb99146102dd57806318160ddd146102f85780631cf65a781461031757806323b872dd146103365780632c71e60a1461036c57806333148fd6146103ca578063435ebc2c146103f55780635eeb6e451461041e578063600e85b71461043c5780636103d70b146104a157806362c1e46a146104b05780636c182e99146104ba578063706dc87c146104f057806370a082311461052557806377174f851461055057806395d89b411461056f578063a7771ee3146105fc578063a9059cbb14610629578063ab377daa14610659578063b25dbb5e14610685578063b89a73cb14610699578063ca5eb5e1146106c6578063cbcf2e5a146106e1578063d21f05ba1461070e578063d347c2051461072d578063d96831e114610765578063dd62ed3e14610777578063df3c211b146107a8578063e2982c21146107d6578063eb944e4c14610801575b610000565b34610000576101d4600160a060020a036004351660243567ffffffffffffffff6044358116906064358116906084351661081f565b005b34610000576101ef600160a060020a0360043516610a30565b60408051918252519081900360200190f35b34610000576101ef610a4f565b60408051918252519081900360200190f35b346100005761022d610a55565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516602435610ae3565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516610b4e565b005b34610000576101ef610b89565b60408051918252519081900360200190f35b34610000576101ef610b8f565b60408051918252519081900360200190f35b34610000576102c9600160a060020a0360043581169060243516604435610b95565b604080519115158252519081900360200190f35b3461000057610388600160a060020a0360043516602435610bb7565b60408051600160a060020a039096168652602086019490945267ffffffffffffffff928316858501529082166060850152166080830152519081900360a00190f35b34610000576101ef600160a060020a0360043516610c21565b60408051918252519081900360200190f35b3461000057610402610c40565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d4600160a060020a0360043516602435610c4f565b005b3461000057610458600160a060020a0360043516602435610cc9565b60408051600160a060020a03909716875260208701959095528585019390935267ffffffffffffffff9182166060860152811660808501521660a0830152519081900360c00190f35b34610000576101d4610d9e565b005b6101d4610e1e565b005b34610000576104d3600160a060020a0360043516610e21565b6040805167ffffffffffffffff9092168252519081900360200190f35b3461000057610402600160a060020a0360043516610ead565b60408051600160a060020a039092168252519081900360200190f35b34610000576101ef600160a060020a0360043516610ef9565b60408051918252519081900360200190f35b34610000576101ef610f18565b60408051918252519081900360200190f35b346100005761022d610f1e565b604080516020808252835181830152835191928392908301918501908083838215610273575b80518252602083111561027357601f199092019160209182019101610253565b505050905090810190601f16801561029f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34610000576102c9600160a060020a0360043516610fac565b604080519115158252519081900360200190f35b34610000576102c9600160a060020a0360043516602435610fc2565b604080519115158252519081900360200190f35b3461000057610402600435610fe2565b60408051600160a060020a039092168252519081900360200190f35b34610000576101d46004351515610ffd565b005b34610000576102c9600160a060020a036004351661104c565b604080519115158252519081900360200190f35b34610000576101d4600160a060020a0360043516611062565b005b34610000576102c9600160a060020a0360043516611070565b604080519115158252519081900360200190f35b34610000576101ef6110f4565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351667ffffffffffffffff602435166110fa565b60408051918252519081900360200190f35b34610000576101d4600435611121565b005b34610000576101ef600160a060020a03600435811690602435166111c6565b60408051918252519081900360200190f35b34610000576101ef6004356024356044356064356084356111f3565b60408051918252519081900360200190f35b34610000576101ef600160a060020a036004351661128c565b60408051918252519081900360200190f35b34610000576101d4600160a060020a036004351660243561129e565b005b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915267ffffffffffffffff848116908416101561086457610000565b8367ffffffffffffffff168267ffffffffffffffff16101561088557610000565b8267ffffffffffffffff168267ffffffffffffffff1610156108a657610000565b506040805160a081018252600160a060020a033381168252602080830188905267ffffffffffffffff80871684860152858116606085015287166080840152908816600090815260039091529190912080546001810180835582818380158290116109615760030281600302836000526020600020918201910161096191905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050916000526020600020906003020160005b5082518154600160a060020a031916600160a060020a03909116178155602083015160018201556040830151600290910180546060850151608086015167ffffffffffffffff1990921667ffffffffffffffff948516176fffffffffffffffff00000000000000001916604060020a918516919091021777ffffffffffffffff000000000000000000000000000000001916608060020a939091169290920291909117905550610a268686610fc2565b505b505050505050565b600160a060020a0381166000908152600360205260409020545b919050565b60055481565b600b805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b600160a060020a03338116600081815260026020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b600a5433600160a060020a03908116911614610b6957610000565b600a8054600160a060020a031916600160a060020a0383161790555b5b50565b60005481565b60005b90565b6000610ba2848484611600565b610bad8484846116e2565b90505b9392505050565b600360205281600052604060002081815481101561000057906000526020600020906003020160005b5080546001820154600290920154600160a060020a03909116935090915067ffffffffffffffff80821691604060020a8104821691608060020a9091041685565b600160a060020a0381166000908152600860205260409020545b919050565b600a54600160a060020a031681565b600a5433600160a060020a03908116911614610c6a57610000565b610c7660005482611714565b6000908155600160a060020a038316815260016020526040902054610c9b9082611714565b600160a060020a038316600090815260016020526040812091909155610cc390839083611600565b5b5b5050565b6000600060006000600060006000600360008a600160a060020a0316600160a060020a0316815260200190815260200160002088815481101561000057906000526020600020906003020160005b508054600182015460028301546040805160a081018252600160a060020a039094168085526020850184905267ffffffffffffffff808416928601839052604060020a8404811660608701819052608060020a9094041660808601819052909c50929a509197509095509350909150610d90904261172d565b94505b509295509295509295565b33600160a060020a038116600090815260066020526040902054801515610dc457610000565b8030600160a060020a0316311015610ddb57610000565b600160a060020a0382166000818152600660205260408082208290555183156108fc0291849190818181858888f193505050501515610cc357610000565b5b5050565b5b565b600160a060020a03811660009081526003602052604081205442915b81811015610ea557600160a060020a03841660009081526003602052604090208054610e9a9190839081101561000057906000526020600020906003020160005b5060020154604060020a900467ffffffffffffffff168461177d565b92505b600101610e3d565b5b5050919050565b600160a060020a0380821660009081526007602052604081205490911615610eef57600160a060020a0380831660009081526007602052604090205416610ef1565b815b90505b919050565b600160a060020a0381166000908152600160205260409020545b919050565b600d5481565b600c805460408051602060026001851615610100026000190190941693909304601f81018490048402820184019092528181529291830182828015610adb5780601f10610ab057610100808354040283529160200191610adb565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505050505081565b60006000610fb983610c21565b1190505b919050565b6000610fcf338484611600565b610fd983836117ac565b90505b92915050565b600460205260009081526040902054600160a060020a031681565b8015801561101a575061100f33610ef9565b61101833610c21565b115b1561102457610000565b33600160a060020a03166000908152600960205260409020805460ff19168215151790555b50565b60006000610fb983610ef9565b1190505b919050565b610b8533826117dc565b5b50565b600a54604080516000602091820181905282517fcbcf2e5a000000000000000000000000000000000000000000000000000000008152600160a060020a03868116600483015293519194939093169263cbcf2e5a92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b600e5481565b6000610fd961110984846118b2565b61111385856119b6565b611a05565b90505b92915050565b600a5433600160a060020a0390811691161461113c57610000565b61114860005482611a1f565b600055600554600190101561116c57600a5461116c90600160a060020a0316611a47565b5b600a54600160a060020a03166000908152600160205260409020546111929082611a1f565b600a8054600160a060020a039081166000908152600160205260408120939093559054610b8592911683611600565b5b5b50565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b6000600060008487101561120a5760009250611281565b8387111561121a57879250611281565b61123f6112308961122b888a611714565b611a90565b61123a8689611714565b611abc565b915081925061124e8883611714565b905061127e8361127961126a8461122b8c8b611714565b611a90565b61123a888b611714565b611abc565b611a1f565b92505b505095945050505050565b60066020526000908152604090205481565b600160a060020a03821660009081526003602052604081208054829190849081101561000057906000526020600020906003020160005b50805490925033600160a060020a039081169116146112f357610000565b6040805160a0810182528354600160a060020a0316815260018401546020820152600284015467ffffffffffffffff80821693830193909352604060020a810483166060830152608060020a900490911660808201526113539042611af9565b600160a060020a0385166000908152600360205260409020805491925090849081101561000057906000526020600020906003020160005b508054600160a060020a031916815560006001820181905560029091018054600160c060020a0319169055600160a060020a0385168152600360205260409020805460001981019081101561000057906000526020600020906003020160005b50600160a060020a03851660009081526003602052604090208054859081101561000057906000526020600020906003020160005b5081548154600160a060020a031916600160a060020a03918216178255600180840154908301556002928301805493909201805467ffffffffffffffff191667ffffffffffffffff948516178082558354604060020a908190048616026fffffffffffffffff000000000000000019909116178082559254608060020a9081900490941690930277ffffffffffffffff00000000000000000000000000000000199092169190911790915584166000908152600360205260409020805460001981018083559190829080158290116115485760030281600302836000526020600020918201910161154891905b8082111561095d578054600160a060020a031916815560006001820155600281018054600160c060020a0319169055600301610926565b5090565b5b505050600160a060020a033316600090815260016020526040902054611570915082611a1f565b600160a060020a03338116600090815260016020526040808220939093559086168152205461159f9082611714565b600160a060020a038086166000818152600160209081526040918290209490945580518581529051339093169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35b50505050565b600160a060020a0383161561166e576116466008600061161f86610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611714565b6008600061165386610ead565b600160a060020a031681526020810191909152604001600020555b600160a060020a038216156116dc576116b46008600061168d85610ead565b600160a060020a0316600160a060020a031681526020019081526020016000205482611a1f565b600860006116c185610ead565b600160a060020a031681526020810191909152604001600020555b5b505050565b600083826116f082426110fa565b8111156116fc57610000565b611707868686611b1b565b92505b5b50509392505050565b600061172283831115611b4d565b508082035b92915050565b6000610fd983602001518367ffffffffffffffff16856080015167ffffffffffffffff16866040015167ffffffffffffffff16876060015167ffffffffffffffff166111f3565b90505b92915050565b60008167ffffffffffffffff168367ffffffffffffffff1610156117a15781610fd9565b825b90505b92915050565b600033826117ba82426110fa565b8111156117c657610000565b6117d08585611b5d565b92505b5b505092915050565b6117e582610ef9565b6117ee83610c21565b11156117f957610000565b600160a060020a03811660009081526009602052604090205460ff16158015611834575081600160a060020a031681600160a060020a031614155b1561183e57610000565b61184782611070565b1561185157610000565b611864828261185f85610ef9565b611600565b600160a060020a0382811660009081526007602052604090208054600160a060020a031916918316918217905561189a82610ead565b600160a060020a031614610cc357610000565b5b5050565b600160a060020a038216600090815260036020526040812054815b818110156119885761197d836112796003600089600160a060020a0316600160a060020a0316815260200190815260200160002084815481101561000057906000526020600020906003020160005b506040805160a0810182528254600160a060020a031681526001830154602082015260029092015467ffffffffffffffff80821692840192909252604060020a810482166060840152608060020a900416608082015287611af9565b611a1f565b92505b6001016118cd565b600160a060020a0385166000908152600160205260409020546117d09084611714565b92505b505092915050565b600060006119c384611070565b80156119d157506000600d54115b90506119fb816119e9576119e485610ef9565b6119ec565b60005b6111138686611b7b565b611a05565b91505b5092915050565b60008183106117a15781610fd9565b825b90505b92915050565b6000828201611a3c848210801590611a375750838210155b611b4d565b8091505b5092915050565b611a508161104c565b15611a5a57610b85565b6005805460009081526004602052604090208054600160a060020a031916600160a060020a038416179055805460010190555b50565b6000828202611a3c841580611a37575083858381156100005704145b611b4d565b8091505b5092915050565b60006000611acc60008411611b4d565b8284811561000057049050611a3c838581156100005706828502018514611b4d565b8091505b5092915050565b6000610fd98360200151611b0d858561172d565b611714565b90505b92915050565b60008382611b2982426110fa565b811115611b3557610000565b611707868686611b8f565b92505b5b50509392505050565b801515610b8557610000565b5b50565b6000611b6883611a47565b610fd98383611c92565b90505b92915050565b6000610fd983610ef9565b90505b92915050565b600160a060020a038084166000908152600260209081526040808320338516845282528083205493861683526001909152812054909190611bd09084611a1f565b600160a060020a038086166000908152600160205260408082209390935590871681522054611bff9084611714565b600160a060020a038616600090815260016020526040902055611c228184611714565b600160a060020a038087166000818152600260209081526040808320338616845282529182902094909455805187815290519288169391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a3600191505b509392505050565b60003382611ca082426110fa565b811115611cac57610000565b6117d08585611cc2565b92505b5b505092915050565b600160a060020a033316600090815260016020526040812054611ce59083611714565b600160a060020a033381166000908152600160205260408082209390935590851681522054611d149083611a1f565b600160a060020a038085166000818152600160209081526040918290209490945580518681529051919333909316927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b929150505600a165627a7a72305820bfa5ddd3fecf3f43aed25385ec7ec3ef79638c2e58d99f85d9a3cc494183bf160029000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182", + "type": "CREATE", + "value": "0x0" + } + ], + "error": "invalid jump destination", + "from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8", + "gas": "0x493e0", + "gasUsed": "0x493e0", + "input": "0x3b91f506000000000000000000000000a14bdd7e5666d784dcce98ad24d383a6b1cd4182000000000000000000000000e4a13bc304682a903e9472f469c33801dd18d9e8", + "to": "0x1d3ddf7caf024f253487e18bc4a15b1a360c170a", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_instafail.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_instafail.json new file mode 100644 index 0000000..16e4136 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_instafail.json @@ -0,0 +1,72 @@ +{ + "genesis": { + "difficulty": "117067574", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712380", + "hash": "0xe05db05eeb3f288041ecb10a787df121c0ed69499355716e17c307de313a4486", + "miner": "0x0c062b329265c965deef1eede55183b3acb8f611", + "mixHash": "0xb669ae39118a53d2c65fd3b1e1d3850dd3f8c6842030698ed846a2762d68b61d", + "nonce": "0x2b469722b8e28c45", + "number": "24973", + "stateRoot": "0x532a5c3f75453a696428db078e32ae283c85cb97e4d8560dbdf022adac6df369", + "timestamp": "1479891145", + "totalDifficulty": "1892250259406", + "alloc": { + "0x6c06b16512b332e6cd8293a2974872674716ce18": { + "balance": "0x0", + "nonce": "1", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900480632e1a7d4d146036575b6000565b34600057604e60048080359060200190919050506050565b005b3373ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f19350505050505b5056", + "storage": {} + }, + "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31": { + "balance": "0x229ebbb36c3e0f20", + "nonce": "3", + "code": "0x", + "storage": {} + } + }, + "config": { + "chainId": 3, + "homesteadBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "byzantiumBlock": 1700000, + "constantinopleBlock": 4230000, + "petersburgBlock": 4939394, + "istanbulBlock": 6485846, + "muirGlacierBlock": 7117117, + "ethash": {} + } + }, + "context": { + "number": "24974", + "difficulty": "117067574", + "timestamp": "1479891162", + "gasLimit": "4712388", + "miner": "0xc822ef32e6d26e170b70cf761e204c1806265914" + }, + "input": "0xf889038504a81557008301f97e946c06b16512b332e6cd8293a2974872674716ce1880a42e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b1600002aa0e2a6558040c5d72bc59f2fb62a38993a314c849cd22fb393018d2c5af3112095a01bdb6d7ba32263ccc2ecc880d38c49d9f0c5a72d8b7908e3122b31356d349745", + "result": { + "type": "CALL", + "from": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31", + "to": "0x6c06b16512b332e6cd8293a2974872674716ce18", + "value": "0x0", + "gas": "0x1f97e", + "gasUsed": "0x72de", + "input": "0x2e1a7d4d00000000000000000000000000000000000000000000000014d1120d7b160000", + "output": "0x", + "calls": [ + { + "type": "CALL", + "from": "0x6c06b16512b332e6cd8293a2974872674716ce18", + "to": "0x66fdfd05e46126a07465ad24e40cc0597bc1ef31", + "value": "0x14d1120d7b160000", + "error": "internal failure", + "input": "0x" + } + ] + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_throw_outer_revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_throw_outer_revert.json new file mode 100644 index 0000000..a023ed6 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/inner_throw_outer_revert.json @@ -0,0 +1,81 @@ +{ + "context": { + "difficulty": "3956606365", + "gasLimit": "5413248", + "miner": "0x00d8ae40d9a06d0e7a2877b62e32eb959afbe16d", + "number": "2295104", + "timestamp": "1513681256" + }, + "genesis": { + "alloc": { + "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76": { + "balance": "0x0", + "code": "0x60606040526004361061015e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680625b4487146101a257806311df9995146101cb578063278ecde11461022057806330adce0e146102435780633197cbb61461026c5780634bb278f3146102955780636103d70b146102aa57806363a599a4146102bf5780636a2d1cb8146102d457806375f12b21146102fd57806378e979251461032a578063801db9cc1461035357806386d1a69f1461037c5780638da5cb5b146103915780638ef26a71146103e65780639890220b1461040f5780639b39caef14610424578063b85dfb801461044d578063be9a6555146104a1578063ccb07cef146104b6578063d06c91e4146104e3578063d669e1d414610538578063df40503c14610561578063e2982c2114610576578063f02e030d146105c3578063f2fde38b146105d8578063f3283fba14610611575b600060149054906101000a900460ff1615151561017a57600080fd5b60075442108061018b575060085442115b15151561019757600080fd5b6101a03361064a565b005b34156101ad57600080fd5b6101b5610925565b6040518082815260200191505060405180910390f35b34156101d657600080fd5b6101de61092b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561022b57600080fd5b6102416004808035906020019091905050610951565b005b341561024e57600080fd5b610256610c48565b6040518082815260200191505060405180910390f35b341561027757600080fd5b61027f610c4e565b6040518082815260200191505060405180910390f35b34156102a057600080fd5b6102a8610c54565b005b34156102b557600080fd5b6102bd610f3e565b005b34156102ca57600080fd5b6102d261105d565b005b34156102df57600080fd5b6102e76110d5565b6040518082815260200191505060405180910390f35b341561030857600080fd5b6103106110e1565b604051808215151515815260200191505060405180910390f35b341561033557600080fd5b61033d6110f4565b6040518082815260200191505060405180910390f35b341561035e57600080fd5b6103666110fa565b6040518082815260200191505060405180910390f35b341561038757600080fd5b61038f611104565b005b341561039c57600080fd5b6103a4611196565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156103f157600080fd5b6103f96111bb565b6040518082815260200191505060405180910390f35b341561041a57600080fd5b6104226111c1565b005b341561042f57600080fd5b610437611296565b6040518082815260200191505060405180910390f35b341561045857600080fd5b610484600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061129c565b604051808381526020018281526020019250505060405180910390f35b34156104ac57600080fd5b6104b46112c0565b005b34156104c157600080fd5b6104c9611341565b604051808215151515815260200191505060405180910390f35b34156104ee57600080fd5b6104f6611354565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561054357600080fd5b61054b61137a565b6040518082815260200191505060405180910390f35b341561056c57600080fd5b610574611385565b005b341561058157600080fd5b6105ad600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506116c3565b6040518082815260200191505060405180910390f35b34156105ce57600080fd5b6105d66116db565b005b34156105e357600080fd5b61060f600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611829565b005b341561061c57600080fd5b610648600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506118fe565b005b600080670de0b6b3a7640000341015151561066457600080fd5b61069b610696670de0b6b3a7640000610688610258346119d990919063ffffffff16565b611a0c90919063ffffffff16565b611a27565b9150660221b262dd80006106ba60065484611a7e90919063ffffffff16565b111515156106c757600080fd5b600a60008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84846000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b15156107d557600080fd5b6102c65a03f115156107e657600080fd5b5050506040518051905050610808828260010154611a7e90919063ffffffff16565b8160010181905550610827348260000154611a7e90919063ffffffff16565b816000018190555061084434600554611a7e90919063ffffffff16565b60058190555061085f82600654611a7e90919063ffffffff16565b6006819055503373ffffffffffffffffffffffffffffffffffffffff167ff3c1c7c0eb1328ddc834c4c9e579c06d35f443bf1102b034653624a239c7a40c836040518082815260200191505060405180910390a27fd1dc370699ae69fb860ed754789a4327413ec1cd379b93f2cbedf449a26b0e8583600554604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1505050565b60025481565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600060085442108061096b5750651b48eb57e00060065410155b15151561097757600080fd5b600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010154821415156109c757600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166323b872dd3330856000604051602001526040518463ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b1515610ac857600080fd5b6102c65a03f11515610ad957600080fd5b5050506040518051905050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166342966c68836000604051602001526040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b1515610b7d57600080fd5b6102c65a03f11515610b8e57600080fd5b505050604051805190501515610ba357600080fd5b600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000015490506000600a60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001819055506000811115610c4457610c433382611a9c565b5b5050565b60055481565b60085481565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610cb157600080fd5b600854421015610cd357660221b262dd8000600654141515610cd257600080fd5b5b651b48eb57e000600654108015610cf057506213c6806008540142105b151515610cfc57600080fd5b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f193505050501515610d7557600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231306000604051602001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b1515610e3a57600080fd5b6102c65a03f11515610e4b57600080fd5b5050506040518051905090506000811115610f2057600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166342966c68826000604051602001526040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050602060405180830381600087803b1515610ef957600080fd5b6102c65a03f11515610f0a57600080fd5b505050604051805190501515610f1f57600080fd5b5b6001600960006101000a81548160ff02191690831515021790555050565b600080339150600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008114151515610f9657600080fd5b803073ffffffffffffffffffffffffffffffffffffffff163110151515610fbc57600080fd5b610fd181600254611b5090919063ffffffff16565b6002819055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561105957fe5b5050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156110b857600080fd5b6001600060146101000a81548160ff021916908315150217905550565b670de0b6b3a764000081565b600060149054906101000a900460ff1681565b60075481565b651b48eb57e00081565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561115f57600080fd5b600060149054906101000a900460ff16151561117a57600080fd5b60008060146101000a81548160ff021916908315150217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60065481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561121c57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151561129457600080fd5b565b61025881565b600a6020528060005260406000206000915090508060000154908060010154905082565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561131b57600080fd5b600060075414151561132c57600080fd5b4260078190555062278d004201600881905550565b600960009054906101000a900460ff1681565b600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b660221b262dd800081565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156113e557600080fd5b600654660221b262dd800003925061142b670de0b6b3a764000061141c610258670de0b6b3a76400006119d990919063ffffffff16565b81151561142557fe5b04611a27565b915081831115151561143c57600080fd5b600a60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16856000604051602001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b151561158c57600080fd5b6102c65a03f1151561159d57600080fd5b50505060405180519050506115bf838260010154611a7e90919063ffffffff16565b81600101819055506115dc83600654611a7e90919063ffffffff16565b6006819055503073ffffffffffffffffffffffffffffffffffffffff167ff3c1c7c0eb1328ddc834c4c9e579c06d35f443bf1102b034653624a239c7a40c846040518082815260200191505060405180910390a27fd1dc370699ae69fb860ed754789a4327413ec1cd379b93f2cbedf449a26b0e856000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600554604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1505050565b60016020528060005260406000206000915090505481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561173657600080fd5b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2fde38b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b151561181357600080fd5b6102c65a03f1151561182457600080fd5b505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561188457600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415156118fb57806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561195957600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415151561199557600080fd5b80600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600080828402905060008414806119fa57508284828115156119f757fe5b04145b1515611a0257fe5b8091505092915050565b6000808284811515611a1a57fe5b0490508091505092915050565b6000611a416202a300600754611a7e90919063ffffffff16565b421015611a7557611a6e611a5f600584611a0c90919063ffffffff16565b83611a7e90919063ffffffff16565b9050611a79565b8190505b919050565b6000808284019050838110151515611a9257fe5b8091505092915050565b611aee81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054611a7e90919063ffffffff16565b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611b4681600254611a7e90919063ffffffff16565b6002819055505050565b6000828211151515611b5e57fe5b8183039050929150505600a165627a7a72305820ec0d82a406896ccf20989b3d6e650abe4dc104e400837f1f58e67ef499493ae90029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000008d69d00910d0b2afb2a99ed6c16c8129fa8e1751", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000e819f024b41358d2c08e3a868a5c5dd0566078d4", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x000000000000000000000000000000000000000000000000000000005a388981", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x000000000000000000000000000000000000000000000000000000005a3b38e6" + } + }, + "0xd4fcab9f0a6dc0493af47c864f6f17a8a5e2e826": { + "balance": "0x2a2dd979a35cf000", + "code": "0x", + "nonce": "0", + "storage": {} + }, + "0xe819f024b41358d2c08e3a868a5c5dd0566078d4": { + "balance": "0x0", + "code": "0x6060604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100bf578063095ea7b31461014d57806318160ddd146101a757806323b872dd146101d0578063313ce5671461024957806342966c681461027257806370a08231146102ad5780638da5cb5b146102fa57806395d89b411461034f578063a9059cbb146103dd578063dd62ed3e14610437578063f2fde38b146104a3575b600080fd5b34156100ca57600080fd5b6100d26104dc565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101125780820151818401526020810190506100f7565b50505050905090810190601f16801561013f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015857600080fd5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610515565b604051808215151515815260200191505060405180910390f35b34156101b257600080fd5b6101ba61069c565b6040518082815260200191505060405180910390f35b34156101db57600080fd5b61022f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506106a2565b604051808215151515815260200191505060405180910390f35b341561025457600080fd5b61025c610952565b6040518082815260200191505060405180910390f35b341561027d57600080fd5b6102936004808035906020019091905050610957565b604051808215151515815260200191505060405180910390f35b34156102b857600080fd5b6102e4600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610abe565b6040518082815260200191505060405180910390f35b341561030557600080fd5b61030d610b07565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561035a57600080fd5b610362610b2d565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103a2578082015181840152602081019050610387565b50505050905090810190601f1680156103cf5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156103e857600080fd5b61041d600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610b66565b604051808215151515815260200191505060405180910390f35b341561044257600080fd5b61048d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610d01565b6040518082815260200191505060405180910390f35b34156104ae57600080fd5b6104da600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610d88565b005b6040805190810160405280600b81526020017f416c6c436f6465436f696e00000000000000000000000000000000000000000081525081565b6000808214806105a157506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054145b15156105ac57600080fd5b81600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60005481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905061077683600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e5f90919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061080b83600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506108618382610e7d90919063ffffffff16565b600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a360019150509392505050565b600681565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156109b557600080fd5b610a0782600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610a5f82600054610e7d90919063ffffffff16565b60008190555060003373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a360019050919050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6040805190810160405280600481526020017f414c4c430000000000000000000000000000000000000000000000000000000081525081565b6000610bba82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e7d90919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610c4f82600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610e5f90919063ffffffff16565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141515610de457600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141515610e5c5780600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b50565b6000808284019050838110151515610e7357fe5b8091505092915050565b6000828211151515610e8b57fe5b8183039050929150505600a165627a7a7230582059f3ea3df0b054e9ab711f37969684ba83fe38f255ffe2c8d850d951121c51100029", + "nonce": "1", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3956606365", + "extraData": "0x566961425443", + "gasLimit": "5418523", + "hash": "0x6f37eb930a25da673ea1bb80fd9e32ddac19cdf7cd4bb2eac62cc13598624077", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "mixHash": "0x10971cde68c587c750c23b8589ae868ce82c2c646636b97e7d9856470c5297c7", + "nonce": "0x810f923ff4b450a1", + "number": "2295103", + "stateRoot": "0xff403612573d76dfdaf4fea2429b77dbe9764021ae0e38dc8ac79a3cf551179e", + "timestamp": "1513681246", + "totalDifficulty": "7162347056825919" + }, + "input": "0xf86d808504e3b292008307dfa69433056b5dcac09a9b4becad0e1dcf92c19bd0af76880e92596fd62900008029a0e5f27bb66431f7081bb7f1f242003056d7f3f35414c352cd3d1848b52716dac2a07d0be78980edb0bd2a0678fc53aa90ea9558ce346b0d947967216918ac74ccea", + "result": { + "calls": [ + { + "error": "invalid opcode: INVALID", + "from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76", + "gas": "0x75fe3", + "gasUsed": "0x75fe3", + "input": "0xa9059cbb000000000000000000000000d4fcab9f0a6dc0493af47c864f6f17a8a5e2e82600000000000000000000000000000000000000000000000000000000000002f4", + "to": "0xe819f024b41358d2c08e3a868a5c5dd0566078d4", + "type": "CALL", + "value": "0x0" + } + ], + "error": "execution reverted", + "from": "0xd4fcab9f0a6dc0493af47c864f6f17a8a5e2e826", + "gas": "0x7dfa6", + "gasUsed": "0x7c1c8", + "input": "0x", + "to": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76", + "type": "CALL", + "value": "0xe92596fd6290000" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/oog.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/oog.json new file mode 100644 index 0000000..333bdd0 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/oog.json @@ -0,0 +1,60 @@ +{ + "context": { + "difficulty": "3699098917", + "gasLimit": "5258985", + "miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511", + "number": "2294631", + "timestamp": "1513675366" + }, + "genesis": { + "alloc": { + "0x43064693d3d38ad6a7cb579e0d6d9718c8aa6b62": { + "balance": "0x0", + "code": "0x6060604052600436106100ba576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100bf578063095ea7b31461014d57806318160ddd146101a757806323b872dd146101d0578063313ce5671461024957806342966c68146102785780635a3b7e42146102b357806370a082311461034157806379cc67901461038e57806395d89b41146103e8578063a9059cbb14610476578063dd62ed3e146104b8575b600080fd5b34156100ca57600080fd5b6100d2610524565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101125780820151818401526020810190506100f7565b50505050905090810190601f16801561013f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015857600080fd5b61018d600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061055d565b604051808215151515815260200191505060405180910390f35b34156101b257600080fd5b6101ba6105ea565b6040518082815260200191505060405180910390f35b34156101db57600080fd5b61022f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506105f0565b604051808215151515815260200191505060405180910390f35b341561025457600080fd5b61025c610910565b604051808260ff1660ff16815260200191505060405180910390f35b341561028357600080fd5b6102996004808035906020019091905050610915565b604051808215151515815260200191505060405180910390f35b34156102be57600080fd5b6102c6610a18565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103065780820151818401526020810190506102eb565b50505050905090810190601f1680156103335780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561034c57600080fd5b610378600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610a51565b6040518082815260200191505060405180910390f35b341561039957600080fd5b6103ce600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610a69565b604051808215151515815260200191505060405180910390f35b34156103f357600080fd5b6103fb610bf8565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561043b578082015181840152602081019050610420565b50505050905090810190601f1680156104685780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561048157600080fd5b6104b6600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610c31565b005b34156104c357600080fd5b61050e600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610e34565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f446f70616d696e6500000000000000000000000000000000000000000000000081525081565b600081600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506001905092915050565b60005481565b6000808373ffffffffffffffffffffffffffffffffffffffff161415151561061757600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561066557600080fd5b600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205482600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401101515156106f157fe5b600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054821115151561077c57600080fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555081600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509392505050565b601281565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561096557600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5836040518082815260200191505060405180910390a260019050919050565b6040805190810160405280600981526020017f446f706d6e20302e32000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b600081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ab957600080fd5b600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548211151515610b4457600080fd5b81600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055508160008082825403925050819055508273ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5836040518082815260200191505060405180910390a26001905092915050565b6040805190810160405280600581526020017f444f504d4e00000000000000000000000000000000000000000000000000000081525081565b60008273ffffffffffffffffffffffffffffffffffffffff1614151515610c5757600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ca557600080fd5b600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205481600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020540110151515610d3157fe5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b60026020528160005260406000206020528060005260406000206000915091505054815600a165627a7a723058206d93424f4e7b11929b8276a269038402c10c0ddf21800e999916ddd9dff4a7630029", + "nonce": "1", + "storage": { + "0x296b66049cc4f9c8bf3d4f14752add261d1a980b39bdd194a7897baf39ac7579": "0x0000000000000000000000000000000000000000033b2e3c9fc9653f9e72b1e0" + } + }, + "0x94194bc2aaf494501d7880b61274a169f6502a54": { + "balance": "0xea8c39a876d19888d", + "code": "0x", + "nonce": "265", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3699098917", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "5263953", + "hash": "0x03a0f62a8106793dafcfae7b75fd2654322062d585a19cea568314d7205790dc", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x15482cc64b7c00a947f5bf015dfc010db1a6a668c74df61974d6a7848c174408", + "nonce": "0xd1bdb150f6fd170e", + "number": "2294630", + "stateRoot": "0x1ab1a534e84cc787cda1db21e0d5920ab06017948075b759166cfea7274657a1", + "timestamp": "1513675347", + "totalDifficulty": "7160543502214733" + }, + "input": "0xf8ab820109855d21dba00082ca1d9443064693d3d38ad6a7cb579e0d6d9718c8aa6b6280b844a9059cbb000000000000000000000000e77b1ac803616503510bed0086e3a7be2627a69900000000000000000000000000000000000000000000000000000009502f90001ba0ce3ad83f5530136467b7c2bb225f406bd170f4ad59c254e5103c34eeabb5bd69a0455154527224a42ab405cacf0fe92918a75641ce4152f8db292019a5527aa956", + "result": { + "error": "out of gas", + "from": "0x94194bc2aaf494501d7880b61274a169f6502a54", + "gas": "0xca1d", + "gasUsed": "0xca1d", + "input": "0xa9059cbb000000000000000000000000e77b1ac803616503510bed0086e3a7be2627a69900000000000000000000000000000000000000000000000000000009502f9000", + "to": "0x43064693d3d38ad6a7cb579e0d6d9718c8aa6b62", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert.json new file mode 100644 index 0000000..3207a29 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert.json @@ -0,0 +1,58 @@ +{ + "context": { + "difficulty": "3665057456", + "gasLimit": "5232723", + "miner": "0xf4d8e706cfb25c0decbbdd4d2e2cc10c66376a3f", + "number": "2294501", + "timestamp": "1513673601" + }, + "genesis": { + "alloc": { + "0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9": { + "balance": "0x2a3fc32bcc019283", + "code": "0x", + "nonce": "10", + "storage": {} + }, + "0xabbcd5b340c80b5f1c0545c04c987b87310296ae": { + "balance": "0x0", + "code": "0x606060405236156100755763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416632d0335ab811461007a578063548db174146100ab5780637f649783146100fc578063b092145e1461014d578063c3f44c0a14610186578063c47cf5de14610203575b600080fd5b341561008557600080fd5b610099600160a060020a0360043516610270565b60405190815260200160405180910390f35b34156100b657600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061028f95505050505050565b005b341561010757600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061029e95505050505050565b005b341561015857600080fd5b610172600160a060020a03600435811690602435166102ad565b604051901515815260200160405180910390f35b341561019157600080fd5b6100fa6004803560ff1690602480359160443591606435600160a060020a0316919060a49060843590810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965050509235600160a060020a031692506102cd915050565b005b341561020e57600080fd5b61025460046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061056a95505050505050565b604051600160a060020a03909116815260200160405180910390f35b600160a060020a0381166000908152602081905260409020545b919050565b61029a816000610594565b5b50565b61029a816001610594565b5b50565b600160209081526000928352604080842090915290825290205460ff1681565b60008080600160a060020a038416158061030d5750600160a060020a038085166000908152600160209081526040808320339094168352929052205460ff165b151561031857600080fd5b6103218561056a565b600160a060020a038116600090815260208190526040808220549295507f19000000000000000000000000000000000000000000000000000000000000009230918891908b908b90517fff000000000000000000000000000000000000000000000000000000000000008089168252871660018201526c01000000000000000000000000600160a060020a038088168202600284015286811682026016840152602a8301869052841602604a820152605e810182805190602001908083835b6020831061040057805182525b601f1990920191602091820191016103e0565b6001836020036101000a0380198251168184511617909252505050919091019850604097505050505050505051809103902091506001828a8a8a6040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f1151561049957600080fd5b5050602060405103519050600160a060020a03838116908216146104bc57600080fd5b600160a060020a0380841660009081526020819052604090819020805460010190559087169086905180828051906020019080838360005b8381101561050d5780820151818401525b6020016104f4565b50505050905090810190601f16801561053a5780820380516001836020036101000a031916815260200191505b5091505060006040518083038160008661646e5a03f1915050151561055e57600080fd5b5b505050505050505050565b600060248251101561057e5750600061028a565b600160a060020a0360248301511690505b919050565b60005b825181101561060157600160a060020a033316600090815260016020526040812083918584815181106105c657fe5b90602001906020020151600160a060020a031681526020810191909152604001600020805460ff19169115159190911790555b600101610597565b5b5050505600a165627a7a723058200027e8b695e9d2dea9f3629519022a69f3a1d23055ce86406e686ea54f31ee9c0029", + "nonce": "1", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3672229776", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "5227619", + "hash": "0xa07b3d6c6bf63f5f981016db9f2d1d93033833f2c17e8bf7209e85f1faf08076", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0x806e151ce2817be922e93e8d5921fa0f0d0fd213d6b2b9a3fa17458e74a163d0", + "nonce": "0xbc5d43adc2c30c7d", + "number": "2294500", + "stateRoot": "0xca645b335888352ef9d8b1ef083e9019648180b259026572e3139717270de97d", + "timestamp": "1513673552", + "totalDifficulty": "7160066586979149" + }, + "input": "0xf9018b0a8505d21dba00832dc6c094abbcd5b340c80b5f1c0545c04c987b87310296ae80b9012473b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988000000000000000000000000000000000000000000000000000000000000000000000000000000001ba0fd659d76a4edbd2a823e324c93f78ad6803b30ff4a9c8bce71ba82798975c70ca06571eecc0b765688ec6c78942c5ee8b585e00988c0141b518287e9be919bc48a", + "result": { + "error": "execution reverted", + "from": "0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9", + "gas": "0x2dc6c0", + "gasUsed": "0x719b", + "input": "0x73b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a98800000000000000000000000000000000000000000000000000000000000000000000000000000000", + "to": "0xabbcd5b340c80b5f1c0545c04c987b87310296ae", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert_reason.json new file mode 100644 index 0000000..5c7e562 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/revert_reason.json @@ -0,0 +1,64 @@ +{ + "context": { + "difficulty": "2", + "gasLimit": "8000000", + "miner": "0x0000000000000000000000000000000000000000", + "number": "3212651", + "timestamp": "1597246515" + }, + "genesis": { + "alloc": { + "0xf58833cf0c791881b494eb79d461e08a1f043f52": { + "balance": "0x0", + "code": "0x608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063609ff1bd11610078578063609ff1bd146101af5780639e7b8d61146101cd578063a3ec138d14610211578063e2ba53f0146102ae576100a5565b80630121b93f146100aa578063013cf08b146100d85780632e4176cf146101215780635c19a95c1461016b575b600080fd5b6100d6600480360360208110156100c057600080fd5b81019080803590602001909291905050506102cc565b005b610104600480360360208110156100ee57600080fd5b8101908080359060200190929190505050610469565b604051808381526020018281526020019250505060405180910390f35b61012961049a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6101ad6004803603602081101561018157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506104bf565b005b6101b76108db565b6040518082815260200191505060405180910390f35b61020f600480360360208110156101e357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610952565b005b6102536004803603602081101561022757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b53565b60405180858152602001841515151581526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200194505050505060405180910390f35b6102b6610bb0565b6040518082815260200191505060405180910390f35b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160000154141561038a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f486173206e6f20726967687420746f20766f746500000000000000000000000081525060200191505060405180910390fd5b8060010160009054906101000a900460ff161561040f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f416c726561647920766f7465642e00000000000000000000000000000000000081525060200191505060405180910390fd5b60018160010160006101000a81548160ff02191690831515021790555081816002018190555080600001546002838154811061044757fe5b9060005260206000209060020201600101600082825401925050819055505050565b6002818154811061047657fe5b90600052602060002090600202016000915090508060000154908060010154905082565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060010160009054906101000a900460ff1615610587576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f596f7520616c726561647920766f7465642e000000000000000000000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610629576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601e8152602001807f53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e000081525060200191505060405180910390fd5b5b600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146107cc57600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691503373ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156107c7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f466f756e64206c6f6f7020696e2064656c65676174696f6e2e0000000000000081525060200191505060405180910390fd5b61062a565b60018160010160006101000a81548160ff021916908315150217905550818160010160016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090508060010160009054906101000a900460ff16156108bf578160000154600282600201548154811061089c57fe5b9060005260206000209060020201600101600082825401925050819055506108d6565b816000015481600001600082825401925050819055505b505050565b6000806000905060008090505b60028054905081101561094d57816002828154811061090357fe5b9060005260206000209060020201600101541115610940576002818154811061092857fe5b90600052602060002090600202016001015491508092505b80806001019150506108e8565b505090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146109f7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180610bde6028913960400191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060010160009054906101000a900460ff1615610aba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f54686520766f74657220616c726561647920766f7465642e000000000000000081525060200191505060405180910390fd5b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000015414610b0957600080fd5b60018060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018190555050565b60016020528060005260406000206000915090508060000154908060010160009054906101000a900460ff16908060010160019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060020154905084565b60006002610bbc6108db565b81548110610bc657fe5b90600052602060002090600202016000015490509056fe4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652ea26469706673582212201d282819f8f06fed792100d60a8b08809b081a34a1ecd225e83a4b41122165ed64736f6c63430006060033", + "nonce": "1", + "storage": { + "0x6200beec95762de01ce05f2a0e58ce3299dbb53c68c9f3254a242121223cdf58": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0xf7579c3d8a669c89d5ed246a22eb6db8f6fedbf1": { + "balance": "0x57af9d6b3df812900", + "code": "0x", + "nonce": "6", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "IstanbulBlock":1561651, + "chainId": 5, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf888068449504f80832dc6c094f58833cf0c791881b494eb79d461e08a1f043f5280a45c19a95c000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf12da0264664db3e71fae1dbdaf2f53954be149ad3b7ba8a5054b4d89c70febfacc8b1a0212e8398757963f419681839ae8c5a54b411e252473c82d93dda68405ca63294", + "result": { + "error": "execution reverted", + "from": "0xf7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "gas": "0x2dc6c0", + "gasUsed": "0x5940", + "input": "0x5c19a95c000000000000000000000000f7579c3d8a669c89d5ed246a22eb6db8f6fedbf1", + "to": "0xf58833cf0c791881b494eb79d461e08a1f043f52", + "type": "CALL", + "value": "0x0", + "output": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e53656c662d64656c65676174696f6e20697320646973616c6c6f7765642e0000" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/selfdestruct.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/selfdestruct.json new file mode 100644 index 0000000..11b23a9 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/selfdestruct.json @@ -0,0 +1,73 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x61deadff", + "nonce": "1", + "storage": {} + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "result": { + "calls": [ + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "input": "0x", + "to": "0x000000000000000000000000000000000000dEaD", + "type": "SELFDESTRUCT", + "value": "0x4d87094125a369d9bd5" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x15f90", + "gasUsed": "0x6fcb", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/simple.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/simple.json new file mode 100644 index 0000000..37723f1 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/simple.json @@ -0,0 +1,78 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "result": { + "calls": [ + { + "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "input": "0x", + "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "type": "CALL", + "value": "0x6f05b59d3b20000" + } + ], + "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb", + "gas": "0x15f90", + "gasUsed": "0x9751", + "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/throw.json new file mode 100644 index 0000000..499b449 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_legacy/throw.json @@ -0,0 +1,62 @@ +{ + "context": { + "difficulty": "117009631", + "gasLimit": "4712388", + "miner": "0x294e5d6c39a36ce38af1dca70c1060f78dee8070", + "number": "25009", + "timestamp": "1479891666" + }, + "genesis": { + "alloc": { + "0x70c9217d814985faef62b124420f8dfbddd96433": { + "balance": "0x4ecd70668f5d854a", + "code": "0x", + "nonce": "1638", + "storage": {} + }, + "0xc212e03b9e060e36facad5fd8f4435412ca22e6b": { + "balance": "0x0", + "code": "0x606060405236156101745760e060020a600035046302d05d3f811461017c57806304a7fdbc1461018e5780630e90f957146101fb5780630fb5a6b41461021257806314baa1b61461021b57806317fc45e21461023a5780632b096926146102435780632e94420f1461025b578063325a19f11461026457806336da44681461026d5780633f81a2c01461027f5780633fc306821461029757806345ecd3d7146102d45780634665096d146102dd5780634e71d92d146102e657806351a34eb8146103085780636111bb951461032d5780636f265b93146103445780637e9014e11461034d57806390ba009114610360578063927df5e014610393578063a7f437791461046c578063ad8f50081461046e578063bc6d909414610477578063bdec3ad114610557578063c19d93fb1461059a578063c9503fe2146105ad578063e0a73a93146105b6578063ea71b02d146105bf578063ea8a1af0146105d1578063ee4a96f9146105f3578063f1ff78a01461065c575b61046c610002565b610665600054600160a060020a031681565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600554600090600160a060020a0390811633909116146106a857610002565b61068260015460a060020a900460ff166000145b90565b61069660085481565b61046c600435600154600160a060020a03166000141561072157610002565b610696600d5481565b610696600435600f8160068110156100025750015481565b61069660045481565b61069660035481565b610665600554600160a060020a031681565b61069660043560158160068110156100025750015481565b6106966004355b600b54600f5460009160028202808203928083039290810191018386101561078357601054840186900394505b50505050919050565b61069660025481565b61069660095481565b61046c600554600090600160a060020a03908116339091161461085857610002565b61046c600435600554600090600160a060020a03908116339091161461092e57610002565b6106826001805460a060020a900460ff161461020f565b610696600b5481565b61068260075460a060020a900460ff1681565b6106966004355b600b54601554600091600282028082039280830392908101910183861015610a6c5760165494506102cb565b61046c6004356024356044356040805160015460e360020a631c2d8fb302825260b260020a691858d8dbdd5b9d18dd1b02600483015291516000928392600160a060020a03919091169163e16c7d9891602481810192602092909190829003018187876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610b4657610002565b005b610696600a5481565b61046c60006000600060006000600160009054906101000a9004600160a060020a0316600160a060020a031663e16c7d986040518160e060020a028152600401808060b260020a691858d8dbdd5b9d18dd1b0281526020015060200190506020604051808303816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663c4b0c96a336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515115159050610f1757610002565b61046c5b60015b60058160ff16101561071e57600f6001820160ff166006811015610002578101549060ff83166006811015610002570154101561129057610002565b61069660015460a060020a900460ff1681565b61069660065481565b610696600c5481565b610665600754600160a060020a031681565b61046c600554600090600160a060020a0390811633909116146112c857610002565b6040805160c081810190925261046c9160049160c4918390600690839083908082843760408051808301909152929750909561018495509193509091908390839080828437509095505050505050600154600090600160a060020a03168114156113fb57610002565b610696600e5481565b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60408051918252519081900360200190f35b5060005b60068160ff16101561070857828160ff166006811015610002576020020151600f60ff831660068110156100025701558160ff82166006811015610002576020020151601560ff831660068110156100025701556001016106ac565b61071061055b565b505050565b600e8054820190555b50565b6040805160015460e060020a6313bc6d4b02825233600160a060020a03908116600484015292519216916313bc6d4b9160248181019260209290919082900301816000876161da5a03f115610002575050604051511515905061071557610002565b83861015801561079257508286105b156107b457600f546010546011548689039082030291909104900394506102cb565b8286101580156107c55750600b5486105b156107e757600f546011546012548589039082030291909104900394506102cb565b600b5486108015906107f857508186105b1561081d57600b54600f546012546013549289039281039290920204900394506102cb565b81861015801561082c57508086105b1561084e57600f546013546014548489039082030291909104900394506102cb565b60145494506102cb565b60015460a060020a900460ff1660001461087157610002565b600254600a01431161088257610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150905080600160a060020a031663771d50e16040518160e060020a0281526004018090506000604051808303816000876161da5a03f1156100025750505050565b60015460a060020a900460ff1660001461094757610002565b600254600a01431161095857610002565b6040805160015460e360020a631c2d8fb302825260a860020a6a636f6e74726163746170690260048301529151600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180517f51a34eb8000000000000000000000000000000000000000000000000000000008252600482018690529151919350600160a060020a03841692506351a34eb8916024808301926000929190829003018183876161da5a03f11561000257505050600b8290554360025560408051838152905130600160a060020a0316917fa609f6bd4ad0b4f419ddad4ac9f0d02c2b9295c5e6891469055cf73c2b568fff919081900360200190a25050565b838610158015610a7b57508286105b15610a9d576015546016546017548689039082900302919091040194506102cb565b828610158015610aae5750600b5486105b15610ad0576015546017546018548589039082900302919091040194506102cb565b600b548610801590610ae157508186105b15610b0657600b546015546018546019549289039281900392909202040194506102cb565b818610158015610b1557508086105b15610b3757601554601954601a548489039082900302919091040194506102cb565b601a54860181900394506102cb565b60015460a060020a900460ff16600014610b5f57610002565b6001805460a060020a60ff02191660a060020a17908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919450600160a060020a038516925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604080518051600a556005547ffebf661200000000000000000000000000000000000000000000000000000000825233600160a060020a03908116600484015216602482015260448101879052905163febf661291606480820192600092909190829003018183876161da5a03f115610002575050508215610cc7576007805473ffffffffffffffffffffffffffffffffffffffff191633179055610dbb565b6040805160055460065460e060020a63599efa6b028352600160a060020a039182166004840152602483015291519184169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050604080516006547f56ccb6f000000000000000000000000000000000000000000000000000000000825233600160a060020a03166004830152602482015290516356ccb6f091604480820192600092909190829003018183876161da5a03f115610002575050600580546007805473ffffffffffffffffffffffffffffffffffffffff19908116600160a060020a038416179091551633179055505b6007805460a060020a60ff02191660a060020a87810291909117918290556008544301600955900460ff1615610df757600a54610e039061029e565b600a54610e0b90610367565b600c55610e0f565b600c555b600c54670de0b6b3a7640000850204600d55600754600554604080517f759297bb000000000000000000000000000000000000000000000000000000008152600160a060020a039384166004820152918316602483015260448201879052519184169163759297bb91606481810192600092909190829003018183876161da5a03f11561000257505060408051600754600a54600d54600554600c5460a060020a850460ff161515865260208601929092528486019290925260608401529251600160a060020a0391821694509281169230909116917f3b3d1986083d191be01d28623dc19604728e29ae28bdb9ba52757fdee1a18de2919081900360800190a45050505050565b600954431015610f2657610002565b6001805460a060020a900460ff1614610f3e57610002565b6001805460a060020a60ff0219167402000000000000000000000000000000000000000017908190556040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f1156100025750506040805180516004805460e260020a633e4baddd028452908301529151919750600160a060020a038816925063f92eb77491602482810192602092919082900301816000876161da5a03f115610002575050604051516007549095506000945060a060020a900460ff1615905061105c57600a5484111561105757600a54600d54670de0b6b3a7640000918603020492505b61107e565b600a5484101561107e57600a54600d54670de0b6b3a764000091869003020492505b60065483111561108e5760065492505b6006548390039150600083111561111857604080516005546007547f5928d37f000000000000000000000000000000000000000000000000000000008352600160a060020a0391821660048401528116602483015260448201869052915191871691635928d37f91606481810192600092909190829003018183876161da5a03f115610002575050505b600082111561117a576040805160055460e060020a63599efa6b028252600160a060020a0390811660048301526024820185905291519187169163599efa6b91604481810192600092909190829003018183876161da5a03f115610002575050505b6040805185815260208101849052808201859052905130600160a060020a0316917f89e690b1d5aaae14f3e85f108dc92d9ab3763a58d45aed8b59daedbbae8fe794919081900360600190a260008311156112285784600160a060020a0316634cc927d785336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f11561000257505050611282565b84600160a060020a0316634cc927d7600a60005054336040518360e060020a0281526004018083815260200182600160a060020a03168152602001925050506000604051808303816000876161da5a03f115610002575050505b600054600160a060020a0316ff5b60156001820160ff166006811015610002578101549060ff8316600681101561000257015411156112c057610002565b60010161055e565b60015460a060020a900460ff166000146112e157610002565b600254600a0143116112f257610002565b6001546040805160e360020a631c2d8fb302815260a860020a6a636f6e74726163746170690260048201529051600160a060020a03929092169163e16c7d989160248181019260209290919082900301816000876161da5a03f11561000257505060408051805160055460065460e060020a63599efa6b028452600160a060020a03918216600485015260248401529251909450918416925063599efa6b916044808301926000929190829003018183876161da5a03f1156100025750505080600160a060020a0316632b68bb2d6040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050600054600160a060020a03169050ff5b6001546040805160e060020a6313bc6d4b02815233600160a060020a039081166004830152915191909216916313bc6d4b91602480830192602092919082900301816000876161da5a03f11561000257505060405151151590506106a85761000256", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000002cccf5e0538493c235d1c5ef6580f77d99e91396", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000000000000061a9", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000070c9217d814985faef62b124420f8dfbddd96433" + } + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "117066792", + "extraData": "0xd783010502846765746887676f312e372e33856c696e7578", + "gasLimit": "4712388", + "hash": "0xe23e8d4562a1045b70cbc99fefb20c101a8f0fc8559a80d65fea8896e2f1d46e", + "miner": "0x71842f946b98800fe6feb49f0ae4e253259031c9", + "mixHash": "0x0aada9d6e93dd4db0d09c0488dc0a048fca2ccdc1f3fc7b83ba2a8d393a3a4ff", + "nonce": "0x70849d5838dee2e9", + "number": "25008", + "stateRoot": "0x1e01d2161794768c5b917069e73d86e8dca80cd7f3168c0597de420ab93a3b7b", + "timestamp": "1479891641", + "totalDifficulty": "1896347038589" + }, + "input": "0xf88b8206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d59750", + "result": { + "error": "invalid jump destination", + "from": "0x70c9217d814985faef62b124420f8dfbddd96433", + "gas": "0x3d090", + "gasUsed": "0x3d090", + "input": "0x51a34eb8000000000000000000000000000000000000000000000027fad02094277c0000", + "to": "0xc212e03b9e060e36facad5fd8f4435412ca22e6b", + "type": "CALL", + "value": "0x0" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json new file mode 100644 index 0000000..dbece72 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json @@ -0,0 +1,117 @@ +{ + "genesis": { + "difficulty": "11934798510088", + "extraData": "0xd983010302844765746887676f312e342e328777696e646f7773", + "gasLimit": "3141592", + "hash": "0xfc543a4a551afbd4a6c5d6d49041371e6bb58b1108c12aaec7f487ce656bb97f", + "miner": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069", + "mixHash": "0xa6a1e67fc68da76b8d9cc3ce1c45d5e1f4bbd96b5dcfddbe0017d7fa99903ead", + "nonce": "0x5f00c600268b4659", + "number": "995200", + "stateRoot": "0x3579328470dd2aef5b9da69f5480cbe0d375e653b530ab3c1aee0da5e1ff4c94", + "timestamp": "1455322761", + "totalDifficulty": "7077231809278509672", + "alloc": { + "0x200edd17f30485a8735878661960cd7a9a95733f": { + "balance": "0x0", + "code": "0x3660008037602060003660003473273930d21e01ee25e4c219b63259d214872220a261235a5a03f21560015760206000f3", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000104": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8ba1097eb3abe3dc1b51faa48445d593bf968f722e20b67bb62a87495836bf04": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8ba1097eb3abe3dc1b51faa48445d593bf968f722e20b67bb62a87495836bf05": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8ba1097eb3abe3dc1b51faa48445d593bf968f722e20b67bb62a87495836bf06": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa611e7c895a426c0477bc9e280db9c3b1e456dc6310ffcf23926ef5186c1facc": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0xac682d343707aadf06c2c4c3692831d9e7ba711099ef36f9efb8bb29be8c410e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xac682d343707aadf06c2c4c3692831d9e7ba711099ef36f9efb8bb29be8c410f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xac682d343707aadf06c2c4c3692831d9e7ba711099ef36f9efb8bb29be8c4110": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x273930d21e01ee25e4c219b63259d214872220a2": { + "balance": "0x0", + "code": "0x606060405236156100da5760e060020a6000350463173825d9811461012c5780632f54bf6e146101875780634123cb6b146101af57806352375093146101b857806354fd4d50146101c25780635c52c2f5146101cc578063659010e7146101fd5780637065cb4814610207578063746c91711461023b578063797af62714610244578063b20d30a914610257578063b61d27f61461028b578063b75c7dc6146102ac578063ba51a6df146102db578063c2cf73261461030f578063cbf0b0c01461034d578063f00d4b5d14610381578063f1736d86146103ba575b6103c4600034111561012a5760408051600160a060020a033216815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b565b6103c46004356000600036436040518084848082843750505090910190815260405190819003602001902090506106c9815b600160a060020a03321660009081526101026020526040812054818082811415610c3f57610d97565b6103c66004355b600160a060020a03811660009081526101026020526040812054115b919050565b6103c660015481565b6103c66101075481565b6103c66101085481565b6103c46000364360405180848480828437505050909101908152604051908190036020019020905061081a8161015e565b6103c66101065481565b6103c4600435600036436040518084848082843750505090910190815260405190819003602001902090506106418161015e565b6103c660005481565b6103c66004355b600081610a7d8161015e565b6103c46004356000364360405180848480828437505050909101908152604051908190036020019020905061080e8161015e565b6103c66004803590602480359160443591820191013560006108393261018e565b6103c4600435600160a060020a033216600090815261010260205260408120549080828114156103d857610457565b6103c4600435600036436040518084848082843750505090910190815260405190819003602001902090506107888161015e565b6103c6600435602435600082815261010360209081526040808320600160a060020a038516845261010290925282205482818114156107e157610805565b6103c4600435600036436040518084848082843750505090910190815260405190819003602001902090506108288161015e565b6103c46004356024356000600036436040518084848082843750505090910190815260405190819003602001902090506104e28161015e565b6103c66101055481565b005b60408051918252519081900360200190f35b50506000828152610103602052604081206001810154600284900a9290831611156104575780546001828101805492909101835590839003905560408051600160a060020a03321681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b50505050565b600160a060020a03831660028361010081101561000257508301819055600160a060020a03851660008181526101026020908152604080832083905584835291829020869055815192835282019290925281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a1505b505050565b15610457576104f08361018e565b156104fb57506104dd565b600160a060020a03841660009081526101026020526040812054925082141561052457506104dd565b61045d5b6101045460005b81811015610ee457610104805461010991600091849081101561000257600080516020610f9f83398151915201548252506020918252604081208054600160a060020a0319168155600181018290556002810180548382559083528383209193610f6992601f9290920104810190610a65565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005081905550600160005054610102600050600084600160a060020a03168152602001908152602001600020600050819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3826040518082600160a060020a0316815260200191505060405180910390a15b505b50565b1561063c5761064f8261018e565b1561065a575061063e565b610662610528565b60015460fa90106106775761067561068c565b505b60015460fa90106105a2575061063e565b6107465b600060015b600154811015610a79575b600154811080156106bc5750600281610100811015610002570154600014155b15610d9f5760010161069c565b156104dd57600160a060020a0383166000908152610102602052604081205492508214156106f7575061063c565b6001600160005054036000600050541115610712575061063c565b600060028361010081101561000257508301819055600160a060020a03841681526101026020526040812055610688610528565b5060408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b1561063c5760015482111561079d575061063e565b60008290556107aa610528565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001820154600282900a908116600014156108005760009350610805565b600193505b50505092915050565b1561063c575061010555565b1561063e5760006101065550565b1561063c5781600160a060020a0316ff5b15610a555761084d846000610e793261018e565b15610909577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00432858786866040518086600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284378201915050965050505050505060405180910390a184600160a060020a03168484846040518083838082843750505090810191506000908083038185876185025a03f15060009350610a5592505050565b6000364360405180848480828437505050909101908152604051908190036020019020915061093990508161024b565b15801561095c575060008181526101096020526040812054600160a060020a0316145b15610a555760008181526101096020908152604082208054600160a060020a03191688178155600181018790556002018054858255818452928290209092601f01919091048101908490868215610a5d579182015b82811115610a5d5782358260005055916020019190600101906109b1565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328132868887876040518087815260200186600160a060020a0316815260200185815260200184600160a060020a03168152602001806020018281038252848482818152602001925080828437820191505097505050505050505060405180910390a15b949350505050565b506109cf9291505b80821115610a795760008155600101610a65565b5090565b15610c2c5760008381526101096020526040812054600160a060020a031614610c2c5760408051600091909120805460018201546002929092018054600160a060020a0392909216939091819083908015610afd57820191906000526020600020905b815481529060010190602001808311610ae057829003601f168201915b505091505060006040518083038185876185025a03f150505060008481526101096020908152604080519281902080546001820154600160a060020a033281811688529587018b905293860181905292166060850181905260a06080860181815260029390930180549187018290527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a959293909160c083019084908015610bcf57820191906000526020600020905b815481529060010190602001808311610bb257829003601f168201915b5050965050505050505060405180910390a160008381526101096020908152604082208054600160a060020a031916815560018101839055600281018054848255908452828420919392610c3292601f9290920104810190610a65565b50919050565b50505060019150506101aa565b60008581526101036020526040812080549093501415610cc7576000805483556001838101919091556101048054918201808255828015829011610c9657818360005260206000209182019101610c969190610a65565b50505060028301819055610104805487929081101561000257600091909152600080516020610f9f83398151915201555b506001810154600283900a90811660001415610d975760408051600160a060020a03321681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610d84576000858152610103602052604090206002015461010480549091908110156100025760406000908120600080516020610f9f8339815191529290920181905580825560018083018290556002909201559450610d979050565b8154600019018255600182018054821790555b505050919050565b5b60018054118015610dc257506001546002906101008110156100025701546000145b15610dd65760018054600019019055610da0565b60015481108015610df95750600154600290610100811015610002570154600014155b8015610e1357506002816101008110156100025701546000145b15610e7457600154600290610100811015610002578101549082610100811015610002578101919091558190610102906000908361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b610691565b156101aa5761010754610e8f5b62015180420490565b1115610ea857600061010655610ea3610e86565b610107555b6101065480830110801590610ec65750610106546101055490830111155b15610edc575061010680548201905560016101aa565b5060006101aa565b61063c6101045460005b81811015610f745761010480548290811015610002576000918252600080516020610f9f833981519152015414610f6157610104805461010391600091849081101561000257600080516020610f9f83398151915201548252506020919091526040812081815560018101829055600201555b600101610eee565b50505060010161052f565b61010480546000808355919091526104dd90600080516020610f9f83398151915290810190610a6556004c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe" + }, + "0x4f5777744b500616697cb655dcb02ee6cd51deb5": { + "balance": "0xb0983f1b83eec290", + "nonce": "2" + }, + "0xf8b483dba2c3b7176a3da549ad41a48bb3121069": { + "balance": "0x16969a0ba2c2d384d07", + "nonce": "67521" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "995201", + "difficulty": "11940626048551", + "timestamp": "1455322773", + "gasLimit": "3141592", + "miner": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069" + }, + "input": "0xf89102850a954d522e8303308594200edd17f30485a8735878661960cd7a9a95733f888ac7230489e80000a4ba51a6df00000000000000000000000000000000000000000000000000000000000000001ca04f2cc45b96f965296382b2e9b657e90808301d5179035a5d91a2de7b912def20a056e19271ea4e19e4e034f38e925e312beed4d300c267160eeb2f565c42deb578", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0x4f5777744b500616697cb655dcb02ee6cd51deb5", + "gas": "0x33085", + "gasUsed": "0x1a9e5", + "to": "0x200edd17f30485a8735878661960cd7a9a95733f", + "input": "0xba51a6df0000000000000000000000000000000000000000000000000000000000000000", + "output": "0xba51a6df00000000000000000000000000000000000000000000000000000000", + "calls": [ + { + "from": "0x200edd17f30485a8735878661960cd7a9a95733f", + "gas": "0x2c263", + "gasUsed": "0x1b0e4", + "to": "0x273930d21e01ee25e4c219b63259d214872220a2", + "input": "0xba51a6df0000000000000000000000000000000000000000000000000000000000000000", + "logs": [ + { + "address": "0x200edd17f30485a8735878661960cd7a9a95733f", + "topics": [ + "0xe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda" + ], + "data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598", + "position": "0x0" + }, + { + "address": "0x200edd17f30485a8735878661960cd7a9a95733f", + "topics": [ + "0xacbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "position": "0x0" + } + ], + "value": "0x8ac7230489e80000", + "type": "CALLCODE" + } + ], + "value": "0x8ac7230489e80000", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json new file mode 100644 index 0000000..2b03dbb --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json @@ -0,0 +1,413 @@ +{ + "genesis": { + "difficulty": "80344740444880", + "extraData": "0x7777772e62772e636f6d", + "gasLimit": "1498600", + "hash": "0xf5d85a80bdbc5d28a16b8eb0d1b9dd18316ddc3655c7d5c901b67acdb7700037", + "miner": "0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1", + "mixHash": "0x433ae590edf0e7ba9aac698bb7d3be2300e3e79d175db13528ff3e79a3f93910", + "nonce": "0x084adce0020c6fd8", + "number": "2340152", + "stateRoot": "0x38295a2634c9c62d48bcbf2ef2ae83768b9055c1f5e6469d17a5d1bcb052072e", + "timestamp": "1475034708", + "totalDifficulty": "66488249547380413902", + "alloc": { + "0x01e60b511fced1eb2b5b40991eb1dfd171a6df42": { + "balance": "0x0", + "code": "0x6060604052361561008d5760e060020a600035046306fdde03811461008f578063095ea7b3146100a557806318160ddd1461012457806323b872dd1461012f578063313ce567146101dc578063475a9fa9146101f057806370a0823114610215578063721a37d21461024357806395d89b411461008f578063a9059cbb14610268578063dd62ed3e146102e7575b005b61031d6040805160208101909152600081525b90565b61038b60043560243560007319ee743d2e356d5f0e4d97cc09b96d06e933d0db63c6605267600160005085856040518460e060020a0281526004018084815260200183600160a060020a0316815260200182815260200193505050506020604051808303818660325a03f4156100025750506040515191506103179050565b6102316003546100a2565b61038b60043560243560443560008054604080517fa00bfa1100000000000000000000000000000000000000000000000000000000815260016004820152600160a060020a038781166024830152868116604483015260648201869052929092166084830152517319ee743d2e356d5f0e4d97cc09b96d06e933d0db9163a00bfa119160a482810192602092919082900301818660325a03f4156100025750506040515195945050505050565b604080516000815290519081900360200190f35b61038b6004356024356000805433600160a060020a0390811691161461039f57610002565b600160a060020a03600435166000908152600160205260409020545b60408051918252519081900360200190f35b61038b6004356024356000805433600160a060020a039081169116146103ce57610002565b61038b60043560243560007319ee743d2e356d5f0e4d97cc09b96d06e933d0db6388d5fecb600160005085856040518460e060020a0281526004018084815260200183600160a060020a0316815260200182815260200193505050506020604051808303818660325a03f4156100025750506040515191506103179050565b610231600435602435600160a060020a038281166000908152600260209081526040808320938516835292905220545b92915050565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561037d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b50600160a060020a03821660009081526001602081905260409091208054830190556003805483019055610317565b600160a060020a038316600090815260016020526040902054821161040a57506040600020805482900390556003805482900390556001610317565b50600061031756", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000012098a4651fb262f7", + "0xfae22198212900725daa5db635d1fda7b0fa195adaabdc806a7267959c3d8ae4": "0x00000000000000000000000000000000000000000000000026cbcbc35aaa62f7" + } + }, + "0x19ee743d2e356d5f0e4d97cc09b96d06e933d0db": { + "balance": "0x0", + "code": "0x6503060000000050606060405260e060020a600035046388d5fecb811461003c578063a00bfa11146100e3578063c6605267146102dc575b610007565b610356600435602435604435600160a060020a0333166000908152602084905260408120548290108015906100715750600082115b1561036a57600160a060020a0333811660008181526020878152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a350600161034f565b610356600435602435604435606435608435600160a060020a03841660009081526020869052604081205483901080159061011e5750600083115b80156101bb5750600160a060020a0385811660009081526001880160209081526040808320339094168352929052205483901015806101bb575081600160a060020a0316631934d55a86336040518360e060020a0281526004018083600160a060020a0316815260200182600160a060020a03168152602001925050506020604051808303816000876161da5a03f1156100075750506040515190505b1561037257600160a060020a038481166000908152602088815260408083208054880190558884168084528184208054899003905581517f1934d55a00000000000000000000000000000000000000000000000000000000815260048101919091523385166024820152905193861693631934d55a936044838101949383900301908290876161da5a03f115610007575050604051511515905061028957600160a060020a038581166000908152600188016020908152604080832033909416835292905220805484900390555b83600160a060020a031685600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3506001610376565b610356600435602435604435600160a060020a033381166000818152600186016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b9392505050565b604080519115158252519081900360200190f35b50600061034f565b5060005b9594505050505056" + }, + "0x3de712784baf97260455ae25fb74f574ec9c1add": { + "balance": "0x23c8352f33854625", + "nonce": "80" + }, + "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd": { + "balance": "0x0", + "nonce": "29", + "code": "0x606060405236156100cf5760e060020a600035046307d5b82681146100d157806315e812ad146101775780631934d55a1461018d5780631d007f5f146101c65780631f0c1e0c146101ee5780633983d5c41461022b5780634025b29314610243578063428d64bd1461030f578063481b659d146104b557806357bcccb6146104f45780638c172fa21461052f5780639ba5b4e9146105ea578063a4a7cf5c146106ca578063b11e3b82146106ed578063c51cf179146107a6578063d6911046146107c2578063eff6be2f146109cb575b005b6109f2600435602435600082815260016020908152604080832060049081015482517f23b872dd00000000000000000000000000000000000000000000000000000000815233600160a060020a0390811693820193909352308316602482015260448101879052925185948594859493909316926323b872dd9260648281019392829003018187876161da5a03f1156100025750506040515115159050610a6d57610002565b6004545b60408051918252519081900360200190f35b6109f2600435602435600160a060020a0382811660009081526003602090815260408083209385168352929052205460ff165b92915050565b6109f2600435600080546101009004600160a060020a039081163390911614610be757610002565b610a066004356024356000828152600160205260408120600901805483908110156100025750815260209020810154600160a060020a03166101c0565b61017b6004355b600454620f4240908202045b919050565b6109f26004356024356000805b600084815260016020526040902060090154811015610c13576040600090812090859052600160205260090180548290811015610002576000918252604080516020808520909301547f721a37d2000000000000000000000000000000000000000000000000000000008252600160a060020a03338116600484015260248301899052925192169363721a37d293604483810194919391929183900301908290876161da5a03f1156100025750506040515115159050610c8d57610002565b604080516024803560048181013560208181028087018201909752818652610a2396833596939560449501929182919085019084908082843750949650505050505050604080516020818101835260008083528351918201909352828152909190819081905b8551831015610c9f57600091505b600160005060008785815181101561000257602090810290910181015182528101919091526040016000206009015460ff831610156104a957600060016000506000888681518110156100025760209081029091018101518252810191909152604001600020600901805460ff85169081101561000257906000526020600020900160009054906101000a9004600160a060020a0316600160a060020a03166370a08231896040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515191909111159050610f59576001600050600087858151811015610002576020908102909101810151825281019190915260400160002060090154909301600201925b60019290920191610375565b6109f260043533600160a060020a039081166000908152600360209081526040808320938516835292905220805460ff1916600190811790915561023e565b6109f260043533600160a060020a039081166000908152600360209081526040808320938516835292905220805460ff19169055600161023e565b60048035600090815260016020818152604092839020600981015481548551968301546002840154600385015460088601546005870154600688015499880154600790980154958c52600160a060020a03888116998d019990995260a060020a90970460ff90811615158c8c015260608c019390935260808b019190915260a08a019490945290851660c08901529290931660e087015261010086019390935216151561012084015261014083015251908190036101600190f35b60408051600480358082013560208181028086018201909652818552610a23959394602494909385019291829190850190849080828437509496505050505050506040805160208181018352600080835283519182019093528281529091908190815b8551831015610f93576000600260005060008886815181101561000257602090810290910181015182528101919091526040016000205411156106be576002600050600087858151811015610002576020908102909101810151825281019190915260400160002054909301600201925b6001929092019161064d565b61017b6004356000805481908190819081908190819060ff161561115757610002565b6040805160e4356004818101356020818102808601820190965281855261017b95833595602480359660443596606435966084359660a4359660c4359693956101049501929182919085019084908082843750949650505050505050600080808080808d81148061076657508c801561076657508a8c12155b80610774575060028a60ff16105b80610788575087600160a060020a03166000145b8061079c575088600160a060020a03166000145b1561177157611760565b61017b600435600454620f42409081039082020481900361023e565b60408051600480358082013560208181028086018201909652818552610a23959394602494909385019291829190850190849080828437509496505093359350506044359150506064356040805160208181018352600080835283519182019093528281529091908190815b8851831015611cee576000600102600160005060008b8681518110156100025760209081029091018101518252810191909152604001600020541180156108c7575087600160a060020a0316600014806108c7575087600160a060020a0316600160005060008b868151811015610002576020908102909101810151825281019190915260400160002060050154600160a060020a0316145b8015610925575086600160a060020a031660001480610925575086600160a060020a0316600160005060008b868151811015610002576020908102909101810151825281019190915260400160002060040154600160a060020a0316145b8015610983575085600160a060020a031660001480610983575085600160a060020a0316600160005060008b868151811015610002576020908102909101810151825281019190915260400160002060010154600160a060020a0316145b156109bf57600160005060008a858151811015610002576020908102909101810151825281019190915260400160002060090154909301600c01925b6001929092019161082e565b6109f26004356000805433600160a060020a03908116610100909204161461234c57610002565b604080519115158252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190602002808383829060006004602084601f0104600302600f01f1509050019250505060405180910390f35b610a7685610232565b92508285039150600083118015610b00575060008681526001602090815260408083206004908101548251855460e060020a63a9059cbb0282526101009004600160a060020a039081169382019390935260248101899052925191169363a9059cbb936044848101949193929183900301908290876161da5a03f115610002575050604051511590505b15610b0a57610002565b5060005b60008681526001602052604090206009015460ff82161015610bd35760406000908120908790526001602052600901805460ff831690811015610002576000918252604080516020808520909301547f475a9fa9000000000000000000000000000000000000000000000000000000008252600160a060020a03338116600484015260248301889052925192169363475a9fa993604483810194919391929183900301908290876161da5a03f1156100025750506040515115159050610bdf57610002565b50600195945050505050565b600101610b0e565b506000805474ffffffffffffffffffffffffffffffffffffffff0019166101008302179055600161023e565b6000848152600160209081526040808320600490810154825160e060020a63a9059cbb028152600160a060020a033381169382019390935260248101899052925191169363a9059cbb936044848101949193929183900301908290876161da5a03f1156100025750506040515115159050610c9557610002565b600101610250565b5060019392505050565b83604051805910610cad5750595b908082528060200260200182016040528015610cc4575b506000945084935090505b8551831015610f6557600091505b600160005060008785815181101561000257602090810290910181015182528101919091526040016000206009015460ff83161015610f7b57600060016000506000888681518110156100025760209081029091018101518252810191909152604001600020600901805460ff85169081101561000257906000526020600020900160009054906101000a9004600160a060020a0316600160a060020a03166370a08231896040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515191909111159050610f8757858381518110156100025790602001906020020151600190048185815181101561000257602090810290910101528551600190600090889086908110156100025760209081029091018101518252810191909152604001600020600901548151829060018701908110156100025760209081029091010152600091505b600160005060008785815181101561000257602090810290910181015182528101919091526040016000206009015460ff83161015610f6f5760016000506000878581518110156100025760209081029091018101518252810191909152604001600020600901805460ff84169081101561000257906000526020600020900160009054906101000a9004600160a060020a0316600160a060020a03166370a08231886040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051518251909150829060ff8516870160020190811015610002576020908102909101015260019190910190610e49565b60019190910190610383565b9695505050505050565b6002820160ff16909301925b60019290920191610ccf565b60019190910190610cdd565b83604051805910610fa15750595b908082528060200260200182016040528015610fb8575b506000945084935091505b85518310156111125760006002600050600088868151811015610002576020908102909101810151825281019190915260400160002054111561114b578583815181101561000257906020019060200201516001900482858151811015610002576020908102909101015285516002906000908890869081101561000257602090810290910181015182528101919091526040016000205482518390600187019081101561000257602090810290910101525060005b600260005060008785815181101561000257602090810290910181015182528101919091526040016000205481101561111b5760026000506000878581518110156100025760209081029091018101518252810191909152604001600020805482908110156100025760009182526020909120015482518390868401600201908110156100025760209081029091010152600101611079565b50949350505050565b60026000506000878581518110156100025750506020858102890181015182528290526040902054909401909301925b60019290920191610fc3565b6000805460ff191660019081178255898252602052604090206007015460ff1615156112e85760406000818120600581015483516006909201547f5101770200000000000000000000000000000000000000000000000000000000835260048301529251600160a060020a0393909316926351017702926024838101936020939290839003909101908290876161da5a03f115610002575050604051511515905061120457611338611347565b6000888152600160209081526040808320815160058201546006909201547f5d1a3b8200000000000000000000000000000000000000000000000000000000825260048201529151600160a060020a039190911693635d1a3b82936024808501949193929183900301908290876161da5a03f1156100025750505060405180519060200150600160005060008a600019168152602001908152602001600020600050600801600050819055506001600160005060008a60001916815260200190815260200160002060005060070160006101000a81548160ff021916908302179055505b6000888152600160208190526040909120015460a060020a900460ff16156113535760406000908120908990526001602052600281015460089091015412156115435760009450611598565b8596505b505050505050919050565b6113345b6000805460ff19169055565b6000888152600160205260409020600981018054600890920154909181101561000257600091825260208083206040805193909101547f70a08231000000000000000000000000000000000000000000000000000000008452600160a060020a03338116600486015291519116936370a082319360248181019493929183900301908290876161da5a03f115610002575050604051519650505b600091505b60008881526001602052604090206009015460ff831610156116d65760406000908120908990526001602052600901805460ff84169081101561000257906000526020600020900160009054906101000a9004600160a060020a0316600160a060020a03166370a08231336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604080515160008b81526001602052919091206009018054919350915060ff84169081101561000257906000526020600020900160009054906101000a9004600160a060020a0316600160a060020a031663721a37d233836040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511515905061175057610002565b60008881526001602052604090206003810154600890910154131561156c576127109450611598565b600088815260016020526040902060028101546003820154600890920154918190039103612710020594505b6000888152600160208190526040909120600901805461271088810361ffff16975087810396509286929181101561000257906000526020600020900160009054906101000a9004600160a060020a0316600160a060020a03166370a08231336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604080515160008d815260016020529182206009018054919094029389935091908110156100025790815260208120909054906101000a9004600160a060020a0316600160a060020a03166370a08231336040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750505060405180519060200150020104955085506113ed565b6000888152600160209081526040808320600490810154825160e060020a63a9059cbb028152600160a060020a0333811693820193909352602481018c9052925191169363a9059cbb936044848101949193929183900301908290876161da5a03f115610002575050604051511515905061134357610002565b600191909101906113f2565b8495505b505050505098975050505050505050565b8d8d8d8d8d8d8d8d604051808960001916815260200188151560f860020a0281526001018781526020018681526020018560ff1660f860020a02815260010184600160a060020a03166c0100000000000000000000000002815260140183600160a060020a03166c010000000000000000000000000281526014018280519060200190602002808383829060006004602084601f0104600302600f01f1509050019850505050505050505060405180910390209450600060010260016000506000876000191681526020019081526020016000206000506000016000505460001916111561185e57611760565b87600160a060020a031663c91d7e9c886040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190602002808383829060006004602084601f0104600302600f01f150905001925050506040604051808303816000876161da5a03f1156100025750506040518051602091909101519095509350506000841180156119bd575082600160a060020a03166323b872dd3330876040518460e060020a0281526004018084600160a060020a0316815260200183600160a060020a0316815260200182815260200193505050506020604051808303816000876161da5a03f11561000257505060405151159050806119bd575082600160a060020a031663095ea7b389866040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511590505b156119c757610002565b87600160a060020a031663c1b06513886040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190602002808383829060006004602084601f0104600302600f01f150905001925050506020604051808303816000876161da5a03f115610002575050604051519250506000821415611a5257610002565b60008e81526002602052604090208054600181018083558281838015829011611a9e57818360005260206000209182019101611a9e91905b80821115611c975760008155600101611a8a565b50505091909060005260206000209001600087909190915055508d60016000506000876000191681526020019081526020016000206000506000016000508190555087600160005060008760001916815260200190815260200160002060005060050160006101000a815481600160a060020a0302191690830217905550816001600050600087600019168152602001908152602001600020600050600601600050819055508c600160005060008760001916815260200190815260200160002060005060010160146101000a81548160ff021916908302179055508b6001600050600087600019168152602001908152602001600020600050600201600050819055508a60016000506000876000191681526020019081526020016000206000506003016000508190555088600160005060008760001916815260200190815260200160002060005060040160006101000a815481600160a060020a030219169083021790555033600160005060008760001916815260200190815260200160002060005060010160006101000a815481600160a060020a0302191690830217905550600090505b8960ff168160ff16101561175c57600085815260016020819052604090912060090180549182018082559091908281838015829011611c9b57600083815260209020611c9b918101908301611a8a565b5090565b5050509190906000526020600020900160006040516104368061236c833901809050604051809103906000f0825473ffffffffffffffffffffffffffffffffffffffff1916179091555050600101611c47565b83604051805910611cfc5750595b908082528060200260200182016040528015611d13575b506000945084935091505b8851831015611fa2576000600102600160005060008b868151811015610002576020908102909101810151825281019190915260400160002054118015611db7575087600160a060020a031660001480611db7575087600160a060020a0316600160005060008b868151811015610002576020908102909101810151825281019190915260400160002060050154600160a060020a0316145b8015611e15575086600160a060020a031660001480611e15575086600160a060020a0316600160005060008b868151811015610002576020908102909101810151825281019190915260400160002060040154600160a060020a0316145b8015611e73575085600160a060020a031660001480611e73575085600160a060020a0316600160005060008b868151811015610002576020908102909101810151825281019190915260400160002060010154600160a060020a0316145b15611fe5578883815181101561000257906020019060200201516001900482858151811015610002576020908102909101015288516001906000908b908690811015610002576020908102909101810151825281019190915260400160002054825183906001870190811015610002576020908102909101015288516001906000908b9086908110156100025760209081029091018101518252810191909152604001600020600101548251600160a060020a03919091169083906002870190811015610002576020908102909101015288516001906000908b90869081101561000257602090810290910181015182528101919091526040016000206001015460a060020a900460ff1615611ff157600182856003018151811015610002576020908102909101015261200c565b50979650505050505050565b600160005060008a858151811015610002576020908102909101810151825281019190915260400160002060090154909301600c01925b60019290920191611d1e565b60008285600301815181101561000257602090810290910101525b600160005060008a858151811015610002576020908102909101810151825281019190915260400160002060020154825183906004870190811015610002576020908102909101015288516001906000908b908690811015610002576020908102909101810151825281019190915260400160002060030154825183906005870190811015610002576020908102909101015288516001906000908b9086908110156100025760209081029091018101518252810191909152604001600020600401548251600160a060020a03919091169083906006870190811015610002576020908102909101015288516001906000908b9086908110156100025760209081029091018101518252810191909152604001600020600501548251600160a060020a03919091169083906007870190811015610002576020908102909101015288516001906000908b908690811015610002576020908102909101810151825281019190915260400160002060060154825183906008870190811015610002576020908102909101015288516001906000908b90869081101561000257602090810290910181015182528101919091526040016000206007015460ff16156121ee576001828560090181518110156100025760209081029091010152612209565b60008285600901815181101561000257602090810290910101525b600160005060008a85815181101561000257602090810290910181015182528101919091526040016000206008015482518390600a870190811015610002576020908102909101015288516001906000908b90869081101561000257602090810290910181015182528101919091526040016000206009015482518390600b87019081101561000257602090810290910101525060005b600160005060008a858151811015610002576020908102909101810151825281019190915260400160002060090154811015611fae57600160005060008a858151811015610002576020908102909101810151825281019190915260400160002060090180548290811015610002576000918252602090912001548251600160a060020a0391909116908390868401600c019081101561000257602090810290910101526001016122a0565b620f424082101561236457506004819055600161023e565b50600061023e56606060405260008054600160a060020a03191633179055610412806100246000396000f36060604052361561008d5760e060020a600035046306fdde03811461008f578063095ea7b3146100a557806318160ddd1461012457806323b872dd1461012f578063313ce567146101dc578063475a9fa9146101f057806370a0823114610215578063721a37d21461024357806395d89b411461008f578063a9059cbb14610268578063dd62ed3e146102e7575b005b61031d6040805160208101909152600081525b90565b61038b60043560243560007319ee743d2e356d5f0e4d97cc09b96d06e933d0db63c6605267600160005085856040518460e060020a0281526004018084815260200183600160a060020a0316815260200182815260200193505050506020604051808303818660325a03f4156100025750506040515191506103179050565b6102316003546100a2565b61038b60043560243560443560008054604080517fa00bfa1100000000000000000000000000000000000000000000000000000000815260016004820152600160a060020a038781166024830152868116604483015260648201869052929092166084830152517319ee743d2e356d5f0e4d97cc09b96d06e933d0db9163a00bfa119160a482810192602092919082900301818660325a03f4156100025750506040515195945050505050565b604080516000815290519081900360200190f35b61038b6004356024356000805433600160a060020a0390811691161461039f57610002565b600160a060020a03600435166000908152600160205260409020545b60408051918252519081900360200190f35b61038b6004356024356000805433600160a060020a039081169116146103ce57610002565b61038b60043560243560007319ee743d2e356d5f0e4d97cc09b96d06e933d0db6388d5fecb600160005085856040518460e060020a0281526004018084815260200183600160a060020a0316815260200182815260200193505050506020604051808303818660325a03f4156100025750506040515191506103179050565b610231600435602435600160a060020a038281166000908152600260209081526040808320938516835292905220545b92915050565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561037d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b50600160a060020a03821660009081526001602081905260409091208054830190556003805483019055610317565b600160a060020a038316600090815260016020526040902054821161040a57506040600020805482900390556003805482900390556001610317565b50600061031756", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000950ca4a06c78934a148b7a3ff3ea8fc366f77a0600", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x6b8ad191d0fa8204d4eafca22ce4ec42425fde2eecf25ce484ecc76765b9a937": "0x00000000000000000000000001e60b511fced1eb2b5b40991eb1dfd171a6df42", + "0x6b8ad191d0fa8204d4eafca22ce4ec42425fde2eecf25ce484ecc76765b9a938": "0x000000000000000000000000f4cbd7e037b80c2e67b80512d482685f15b1fb28", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e854": "0x446374989d279847d0dbc6708a9c76a419fe9831d42c78bc89473f559a00d915", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e855": "0x00000000000000000000000061d76c05cd2aa9ed5135e21e52fff188b02089d4", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e856": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e857": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e858": "0x00000000000000000000000092f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e859": "0x000000000000000000000000529c4cb814029b8bb32acb516ea3a4b07fdae350", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e85a": "0x846fd373887ade3ab7703750294876afa61cf56303f5f014a4d80d04f508a1f1", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e85b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e85c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x71dbd1e5cfc57324881ede454ea48ef3502c5c0b0454ccd622624a7061c2e85d": "0x0000000000000000000000000000000000000000000000000000000000000002" + } + }, + "0x61c808d82a3ac53231750dadc13c777b59310bd9": { + "balance": "0x90a7af5d4755984561", + "nonce": "197408" + }, + "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5": { + "balance": "0x0", + "code": "0x606060405236156100a35760e060020a6000350463031d973e81146100a557806316181bb7146100da5780635aa97eeb146103b1578063674cc1f5146104f75780636da84ec0146105d7578063929e626e146105fe578063a0bde7e8146106ac578063bbd4f8541461078b578063c1fd43391461098e578063c3c95c7b14610a88578063db833e3a14610afe578063df6c13c314610cfe578063ebb7119414610d13575b005b610d4960043560008181526020819052604081206004015481908390600160a060020a039081163390911614610dcb57610002565b610d0160043560243560443560643560008481526020819052604080822054815160e160020a63460b97d1028152600481018290529151909183918291829182916000805160206123dd83398151915291638c172fa29160248181019261016092909190829003018187876161da5a03f1156100025750506040805160a081015160c08201517fc51cf179000000000000000000000000000000000000000000000000000000008352600483018d90529251909750919550600160a060020a038616926323b872dd92339230929163c51cf1799160248181019260209290919082900301818b876161da5a03f11561000257505060408051805160e060020a6323b872dd028252600160a060020a039586166004830152939094166024850152918d01604484015250516064828101926020929190829003018187876161da5a03f11561000257505060405151159050806102e8575082600160a060020a031663095ea7b36000805160206123dd8339815191526000805160206123dd833981519152600160a060020a031663c51cf1798c6040518260e060020a028152600401808281526020019150506020604051808303816000876161da5a03f11561000257505060408051805160e060020a63095ea7b30282526004820193909352918d0160248301525160448281019350602092829003018187876161da5a03f115610002575050604051511590505b806103a757506000805160206123dd833981519152600160a060020a03166307d5b826866000805160206123dd833981519152600160a060020a031663c51cf1798c6040518260e060020a028152600401808281526020019150506020604051808303816000876161da5a03f11561000257505060408051805160e160020a6303eadc130282526004820194909452928d016024840152516044838101936020935082900301816000876161da5a03f115610002575050604051511590505b15610fcd57610002565b60408051600480358082013560208181028086018201909652818552610d5d9593946024949093850192918291908501908490808284375094965050933593505050506040805160208181018352600080835283519182019093528281529091908190815b86518310156112c757600060010260006000506000898681518110156100025760209081029091018101518252810191909152604001600020541180156104af575085600160a060020a0316600014806104af575085600160a060020a03166000600050600089868151811015610002576020908102909101810151825281019190915260400160002060040154600160a060020a0316145b156104eb576000600050600088858151811015610002576020908102909101810151825281019190915260400160002060070154909301600901925b60019290920191610416565b60408051600480358082013560208181028086018201909652818552610d5d959394602494909385019291829190850190849080828437509496505050505050506040805160208181018352600080835283519182019093528281529091908190815b8551831015611713576000600160005060008886815181101561000257602090810290910181015182528101919091526040016000205411156105cb576001600050600087858151811015610002576020908102909101810151825281019190915260400160002054909301600201925b6001929092019161055a565b610d016004356024355b60009182526020829052604090912060010154620f424091020490565b610da760043561200060405190810160405280610100905b6000815260200190600190039081610616575050604080516120008101909152610100815b600081526020019060019003908161063b5750600090505b60008481526020819052604090206007015460ff821610156118d8576040600020600701805460ff8316908110156100025760009182526020909120810154908390610100811015610002576020020152600101610653565b610d5d600435604080516020818101835260008083528351808301855281815285825291819052835193812060070154929391929091600191909101908059106106f35750595b90808252806020026020018201604052801561070a575b509150428260008151811015610002576020919091019190915290505b60008481526020819052604090206007015460ff821610156118d8576040600020600701805460ff83169081101561000257906000526020600020900160005054828260010160ff1681518110156100025760209081029091010152600101610727565b610d0160043560243560443560643560008481526020819052604080822054815160e160020a63460b97d102815260048101919091529051829182918291829182916000805160206123dd83398151915291638c172fa29160248181019261016092909190829003018187876161da5a03f1156100025750505060405180519060200180519060200180519060200180519060200180519060200180519060200180519060200180519060200180519060200180519060200180519060200150505050509a50505050505050600060005060008b60001916815260200190815260200160002060005060050160009054906101000a9004600160a060020a0316600160a060020a0316630439978d8b600060005060008e60001916815260200190815260200160002060005060030160005054600060005060008f6000191681526020019081526020016000206000506007016000508d8d6040518660e060020a0281526004018086600019168152602001858152602001806020018460ff168152602001838152602001828103825285818154815260200191508054801561095657602002820191906000526020600020905b81600050548152602001906001019080831161093f575b505096505050505050506020604051808303816000876161da5a03f1156100025750506040515194505060008414156118e357610fc0565b610d01600435602435604435606435600060006000600060006000805160206123dd833981519152600160a060020a0316638c172fa28a6040518260e060020a0281526004018082600019168152602001915050610160604051808303816000876161da5a03f1156100025750506040805160a081015160c08201518d83526c01000000000000000000000000600160a060020a033381168202602086810191909152908d16909102603485015284516048948190039490940190932080875292869052928520600301549097509195509350821415905080610a7357506207a12088115b80610a7e5750836000145b15611cc857611cbc565b60048035600090815260208181526040918290206002810154815484516001840154600385015497850154600586015460069096015493835295820152808601929092526060820195909552600160a060020a039283166080820152911660a082015260c0810192909252519081900360e00190f35b610d0160043560243560443560643560008481526020819052604080822054815160e160020a63460b97d10281526004810191909152905182918291829182916000805160206123dd83398151915291638c172fa291602482810192610160929190829003018187876161da5a03f1156100025750505060405180519060200180519060200180519060200180519060200180519060200180519060200180519060200180519060200180519060200180519060200180519060200150505050509950505050505050600060005060008a60001916815260200190815260200160002060005060050160009054906101000a9004600160a060020a0316600160a060020a031663f47cd6718a600060005060008d60001916815260200190815260200160002060005060030160005054600060005060008e6000191681526020019081526020016000206000506007016000508c8c6040518660e060020a0281526004018086600019168152602001858152602001806020018460ff1681526020018381526020018281038252858181548152602001915080548015610cc657602002820191906000526020600020905b816000505481526020019060010190808311610caf575b505096505050505050506020604051808303816000876161da5a03f11561000257505060405151935050600083141561201957611cbc565b60005b60408051918252519081900360200190f35b610d0160043560008181526020819052604081206004015481908190849033600160a060020a039081169116146122df57610002565b604080519115158252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190602002808383829060006004602084601f0104600302600f01f1509050019250505060405180910390f35b6040518082612000808381846000600461030ff15090500191505060405180910390f35b600091505b60008481526020819052604090206007015460ff83161015610eed576040600081812086825260208281528351915460e260020a6307c30783028352600483015260ff8616602483015292516000805160206123dd83398151915293631f0c1e0c9360448481019492939283900301908290876161da5a03f115610002575050604080515160008781526020819052919091206007018054600160a060020a0392909216925063a9059cbb9133919060ff871690811015610002579060005260206000209001600050546040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f1156100025750506040515115159050610f7357610002565b60406000908120602082815282825560018201839055600282018390556003820183905560048201805473ffffffffffffffffffffffffffffffffffffffff19908116909155600583018054909116905560068201839055600782018054848255908452908320919291610fa7918101905b80821115610fb45760008155600101610f5f565b6000848152602081905260408120600701805460ff8516908110156100025790825260208220015560019190910190610dd0565b5060019695505050505050565b5090565b818385010195505b5050505050949350505050565b6040805160e260020a6307c307830281526004810187905260ff8b16602482015290516000805160206123dd83398151915291631f0c1e0c91604482810192602092919082900301816000876161da5a03f11561000257505060408051805160e060020a63095ea7b302825230600160a060020a039081166004840152602483018d905292519216925063095ea7b391604482810192602092919082900301816000876161da5a03f115610002575050604051511515905061108e57610002565b604080517fdb833e3a000000000000000000000000000000000000000000000000000000008152600481018c905260ff8b166024820152604481018a905260648101899052905130600160a060020a03169163db833e3a91608482810192602092919082900301816000876161da5a03f11561000257505060405151925050600082141561111b57610002565b5060005b838160ff1610156111f75760ff808a169082161461125b576040805160e260020a6307c307830281526004810187905260ff8316602482015290516000805160206123dd83398151915291631f0c1e0c91604482810192602092919082900301816000876161da5a03f11561000257505060408051805160e060020a63a9059cbb028252600160a060020a033381166004840152602483018d905292519216925063a9059cbb91604482810192602092919082900301816000876161da5a03f115610002575050604051511515905061125b57610002565b82600160a060020a031663a9059cbb33846040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511515905061126357610002565b60010161111f565b816000805160206123dd833981519152600160a060020a031663c51cf1798a6040518260e060020a028152600401808281526020019150506020604051808303816000876161da5a03f115610002575050604051518a01919091039650610fc09050565b836040518059106112d55750595b9080825280602002602001820160405280156112ec575b506000945084935091505b86518310156116c65760006001026000600050600089868151811015610002576020908102909101810151825281019190915260400160002054118015611390575085600160a060020a031660001480611390575085600160a060020a03166000600050600089868151811015610002576020908102909101810151825281019190915260400160002060040154600160a060020a0316145b1561170757868381518110156100025790602001906020020151600190048285815181101561000257602090810290910101528651600090819089908690811015610002576020908102909101810151825281019190915260400160002054825183906001870190811015610002576020908102909101015286516000908190899086908110156100025760209081029091018101518252810191909152604001600020600101548251839060028701908110156100025760209081029091010152865160009081908990869081101561000257602090810290910181015182528101919091526040016000206002015482518390600387019081101561000257602090810290910101528651600090819089908690811015610002576020908102909101810151825281019190915260400160002060030154825183906004870190811015610002576020908102909101015286516000908190899086908110156100025760209081029091018101518252810191909152604001600020600401548251600160a060020a03919091169083906005870190811015610002576020908102909101015286516000908190899086908110156100025760209081029091018101518252810191909152604001600020600501548251600160a060020a03919091169083906006870190811015610002576020908102909101015286516000908190899086908110156100025760209081029091018101518252810191909152604001600020600601548251839060078701908110156100025760209081029091010152865160009081908990869081101561000257602090810290910181015182528101919091526040016000206007015482518390600887019081101561000257602090810290910101525060005b60006000506000888581518110156100025760209081029091018101518252810191909152604001600020600701548110156116d0576000600050600088858151811015610002576020908102909101810151825281019190915260400160002060070180548290811015610002579060005260206000209001600050548282866009010181518110156100025760209081029091010152600101611626565b5095945050505050565b6000600050600088858151811015610002576020908102909101810151825281019190915260400160002060070154909301600901925b600192909201916112f7565b836040518059106117215750595b908082528060200260200182016040528015611738575b506000945084935091505b8551831015611892576000600160005060008886815181101561000257602090810290910181015182528101919091526040016000205411156118cc578583815181101561000257906020019060200201516001900482858151811015610002576020908102909101015285516001906000908890869081101561000257602090810290910181015182528101919091526040016000205482518390600187019081101561000257602090810290910101525060005b600160005060008785815181101561000257602090810290910181015182528101919091526040016000205481101561189b57600160005060008785815181101561000257602090810290910181015182528101919091526040016000208054829081101561000257600091825260209091200154825183908684016002019081101561000257602090810290910101526001016117f9565b50949350505050565b6001600050600087858151811015610002575050602085810289018101518252919091526040902054909301600201925b60019290920191611743565b8192505b5050919050565b6118ed8a856105e1565b92506000805160206123dd833981519152600160a060020a031663c51cf179896040518260e060020a028152600401808281526020019150506020604051808303816000876161da5a03f1156100025750506040515192505083830182018790111561195857610fc0565b84600160a060020a03166323b872dd333085878901016040518460e060020a0281526004018084600160a060020a0316815260200183600160a060020a0316815260200182815260200193505050506020604051808303816000876161da5a03f1156100025750506040515115905080611a3557506040805160e060020a63095ea7b30281526000805160206123dd833981519152600482015285840160248201529051600160a060020a0387169163095ea7b391604482810192602092919082900301816000876161da5a03f115610002575050604051511590505b80611aa9575060008a81526020818152604080832054815160e160020a6303eadc130281526004810191909152878601602482015290516000805160206123dd833981519152936307d5b826936044848101949193929183900301908290876161da5a03f115610002575050604051511590505b15611ab357610002565b5060005b60008a81526020819052604090206007015460ff82161015611b06576040600020600701805485919060ff84169081101561000257600091825260209091200180549091019055600101611ab7565b604060009081208b8252602091909152600701805460ff8b169081101561000257600091825260209091200154881115611b3f57610002565b60008a815260208190526040902060028101805485019055600701805489919060ff8c1690811015610002579060005260206000209001600050805491909103905560008a81526020818152604080832054815160e260020a6307c30783028152600481019190915260ff8d16602482015290516000805160206123dd83398151915293631f0c1e0c936044848101949193929183900301908290876161da5a03f11561000257505060408051805160e060020a63a9059cbb028252600160a060020a033381166004840152602483018d905292519216925063a9059cbb91604482810192602092919082900301816000876161da5a03f1156100025750506040515115159050610fb857610002565b505050600092835250602080832090910184905583825281905260409020600181018990556003810188905589815560048101805473ffffffffffffffffffffffffffffffffffffffff199081163317909155600582018054909116881790554360069091015590935083905b50505050949350505050565b82600160a060020a03166323b872dd33306000805160206123dd833981519152600160a060020a031663c51cf1798c6040518260e060020a028152600401808281526020019150506020604051808303816000876161da5a03f11561000257505060408051805160e060020a6323b872dd028252600160a060020a039586166004830152939094166024850152918c0160448401525051606482810192602092919082900301816000876161da5a03f1156100025750506040515115905080611e45575082600160a060020a031663095ea7b36000805160206123dd8339815191526000805160206123dd833981519152600160a060020a031663c51cf1798b6040518260e060020a028152600401808281526020019150506020604051808303816000876161da5a03f11561000257505060408051805160e060020a63095ea7b30282526004820193909352918c016024830152516044828101935060209282900301816000876161da5a03f115610002575050604051511590505b80611f0457506000805160206123dd833981519152600160a060020a03166307d5b8268a6000805160206123dd833981519152600160a060020a031663c51cf1798b6040518260e060020a028152600401808281526020019150506020604051808303816000876161da5a03f11561000257505060408051805160e160020a6303eadc130282526004820194909452928c016024840152516044838101936020935082900301816000876161da5a03f115610002575050604051511590505b15611f0e57610002565b83604051805910611f1c5750595b908082528060200260200182016040528015611f33575b506000838152602081815260408220600701805484518083558285529383902091949082019392018215611f86579160200282015b82811115611f86578251826000505591602001919060010190611f68565b50611f92929150610f5f565b5050600090505b838160ff161015611fda576000828152602081905260409020600701805488919060ff84169081101561000257600091825260209091200155600101611f99565b600089815260016020819052604090912080549182018082559091908281838015829011611c4f57600083815260209020611c4f918101908301610f5f565b61202389846105e1565b915085828403101561203457611cbc565b60008981526020818152604080832054815160e260020a6307c30783028152600481019190915260ff8c16602482015290516000805160206123dd83398151915293631f0c1e0c936044848101949193929183900301908290876161da5a03f11561000257505060408051805160e060020a6323b872dd028252600160a060020a0333811660048401523081166024840152604483018c90529251921692506323b872dd91606482810192602092919082900301816000876161da5a03f115610002575050604051511590508061218d57506000805160206123dd833981519152600160a060020a0316634025b293600060005060008c60001916815260200190815260200160002060005060000160005054856040518360e060020a0281526004018083600019168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511590505b1561219757610002565b6000898152602081905260409020600701805488919060ff8b16908110156100025760009182526020822001805490920190915590505b60008981526020819052604090206007015460ff82161015612254576040600020600701805484919060ff84169081101561000257600091825260209091200154106122d0576000898152602081905260409020600701805484919060ff84169081101561000257906000526020600020900160005080549190910390556001016121ce565b600089815260208181526040808320600201805486019055805160e060020a63a9059cbb028152600160a060020a033381166004830152868803602483015291519188169363a9059cbb93604483810194919391929183900301908290876161da5a03f11561000257505060405151151590506122d557610002565b610002565b8183039450611cbc565b60008581526020819052604080822054815160e160020a63460b97d1028152600481019190915290516000805160206123dd83398151915292638c172fa292602481810193610160939092839003909101908290876161da5a03f1156100025750506040805160c001516000888152602081905291822060020180549083905590955093508311905080156123ca575082600160a060020a031663a9059cbb33846040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511590505b156123d457610002565b819350506118dc560000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd", + "storage": { + "0x50ff25f5e9a51687bca1c50f3544d5eef8202f228d3de791691a137aecb6b360": "0x00000000000000000000000000000000000000000000000026566ea1ec2f6a9b", + "0x50ff25f5e9a51687bca1c50f3544d5eef8202f228d3de791691a137aecb6b361": "0x00000000000000000000000000000000000000000000000072aa5b7e04d56a9b", + "0x642f3c12d3cd25d9b946d8c2ec97f080f4efcff18301a6dcade5a6be0c5ed86c": "0xd9a4ffe21d19763887176173d08241e8393c1dfd208f29193dfecdf854b664ac", + "0x642f3c12d3cd25d9b946d8c2ec97f080f4efcff18301a6dcade5a6be0c5ed86d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x642f3c12d3cd25d9b946d8c2ec97f080f4efcff18301a6dcade5a6be0c5ed86e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x642f3c12d3cd25d9b946d8c2ec97f080f4efcff18301a6dcade5a6be0c5ed86f": "0x0000000000000000000000000000000000000000000000004563918244f40000", + "0x642f3c12d3cd25d9b946d8c2ec97f080f4efcff18301a6dcade5a6be0c5ed871": "0x0000000000000000000000008695e5e79dab06fbbb05f445316fa4edb0da30f0", + "0x642f3c12d3cd25d9b946d8c2ec97f080f4efcff18301a6dcade5a6be0c5ed873": "0x0000000000000000000000000000000000000000000000000000000000000002" + } + }, + "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0": { + "balance": "0x0", + "code": "0x606060405260e060020a60003504630439978d811461003157806308f028d514610115578063f47cd6711461026e575b005b60408051604435600481810135602081810285810182019096528185526103509583359560248035966064959294910192829185019084908082843750949650509335935050608435915050600060006040604051908101604052806002905b60008152602001906001900390816100915790505060006000600061271073ef3487d24a0702703e04a26cef479e313c8fc7ae6324d4e90a604060020a8c51026040518260e060020a028152600401808281526020019150506020604051808303818660325a03f41561000257505060405151919091049550610398905089610157565b60408051600480358082013560208181028086018201909652818552610362959394602494909385019291829190850190849080828437509496505050505050505b6040604051908101604052806002905b6000815260200190600190039081610167575050604080518082019091526002815b600081526020019060019003908161018957905050600083600081518110156100025760209081029091010151825283518490600090811015610002576020908102909101810151908301525060005b83518160ff1610156104a7578351825190859060ff8416908110156100025790602001906020020151101561022357838160ff168151811015610002576020908102909101015182525b60208201518451859060ff8416908110156100025790602001906020020151111561026657838160ff168151811015610002576020908102909101810151908301525b6001016101d9565b60408051604435600481810135602081810285810182019096528185526103509583359560248035966064959294910192829185019084908082843750949650509335935050608435915050600060006040604051908101604052806002905b60008152602001906001900390816102ce579050506000600061271073ef3487d24a0702703e04a26cef479e313c8fc7ae6324d4e90a604060020a8b51026040518260e060020a028152600401808281526020019150506020604051808303818660325a03f415610002575050604051519190910494506104ae905088610157565b60408051918252519081900360200190f35b60408051908190839080838184600060046015f15090500191505060405180910390f35b8095505b505050505095945050505050565b935061044d85858b8d5b6000806127108304815b85518160ff16101561051d5773ef3487d24a0702703e04a26cef479e313c8fc7ae63872fb2b589848a6000909060200201518a8660ff1681518110156100025760209081029091010151038b600060200201518c600190906020020151030304026040518260e060020a028152600401808281526020019150506020604051808303818660325a03f4156100025750506040515190930192506001016103ac565b925086898960ff16815181101561000257602090810290910101805191909103905261047b85858b8d6103a2565b915050604060020a620186a0620186a28484036127108d0402020404868111156103865786955061038a565b5092915050565b6020810180518801905292506104c684848a8c6103a2565b915085888860ff16815181101561000257602090810290910101805190910190526104f384848a8c6103a2565b9050604060020a620186a06127108b04838503026201869e02040494505050505095945050505050565b87604060020a73ef3487d24a0702703e04a26cef479e313c8fc7ae6324d4e90a866040518260e060020a028152600401808281526020019150506020604051808303818660325a03f4156100025750506040515190910291909104999850505050505050505056" + }, + "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2": { + "balance": "0xa6e361612cc228000", + "code": "0x6060604052361561008d5760e060020a600035046306fdde03811461008f578063095ea7b3146100ed57806318160ddd1461016257806323b872dd1461016b578063313ce567146102565780636c11bcd31461026257806370a08231146102d057806395d89b41146102f5578063a9059cbb14610353578063d0febe4c146103f8578063dd62ed3e14610439575b005b6040805160038054602060026001831615610100026000190190921691909104601f810182900482028401820190945283835261046d939083018282801561052e5780601f106105035761010080835404028352916020019161052e565b61042560043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6104db60025481565b610425600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101be575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101ca5750600082115b1561053657600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a350600161053a565b6104ed60055460ff1681565b61042560043533600160a060020a0316600090815260208190526040812054821161054157604081208054839003905560028054839003905580821180156102c6575060405133600160a060020a0316908290849082818181858883f19350505050155b1561054957610002565b6104db600435600160a060020a0381166000908152602081905260409020545b919050565b61046d6004805460408051602060026000196101006001871615020190941693909304601f8101849004840282018401909252818152929183018282801561052e5780601f106105035761010080835404028352916020019161052e565b61042560043560243533600160a060020a03166000908152602081905260408120548290108015906103855750600082115b156105515733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a350600161015c565b33600160a060020a0316600090815260208190526040902080543490810190915560028054909101905560015b604080519115158252519081900360200190f35b6104db600435602435600160a060020a0382811660009081526001602090815260408083209385168352929052205461015c565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156104cd5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051918252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161051157829003601f168201915b505050505081565b5060005b9392505050565b5060006102f0565b5060016102f0565b50600061015c56", + "storage": { + "0x3830062b39ca7888048a385f112e36aef7258a27d84eb6e31312c298e5954da3": "0x0000000000000000000000000000000000000000000000035fe3763f1973ab3b", + "0x527b1dd758d53f706730e0fb37a8de5c38d8b4cd17fbe1cfa285480a00f55bf4": "0x000000000000000000000000000000000000000000000003ab97b2fc29ad66c6", + "0x52cb6de4baff82acfb6977b64d52b9ac011f8af34631d933997b7649a84d716f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8f0cfa08792bcd3de052a3bb7bd54f8a62c44b02ba16ff336e9a881c348cca21": "0x000000000000000000000000000000000000000000046ba103abb9d1301f1b2e", + "0xa29249eda6f9f8d0c67b7a4f954f6ba7a9f1bb3f216b2fedc6db8def03c47746": "0x00000000000000000000000000000000000000000000000007a93ebd870d6684", + "0xbe1e23f4b08159a01ee61379749e9b484f5947aaeeb008ce7c97d1c56d3eeb8b": "0x0000000000000000000000000000000000000000000000000dfecc50c6f7d5cd" + } + }, + "0xef3487d24a0702703e04a26cef479e313c8fc7ae": { + "balance": "0x0", + "code": "0x6503060000000050606060405260e060020a600035046324d4e90a8114610031578063872fb2b514610078575b610007565b61013c6004356000680171547652b82fe177818080808061014e886000604060020a82048160bf605f5b6001830182146103c4578060020a8410156103ce579050806103d2565b61013c600435604060020a67b17217f7d1cf79ac81830281900482810460020a680100000016aee6e8ef67b172182739bc0e46858406908102869004673d7f78a624cfb9b582800288900490810288900491909101670e359bcfeb6e45319183028890049182028890040167027601df2fc048dc91830288900491820288900401665808a728816ee89183028890049182028890040166095dedef350bc991830288900491820297909704969096019190910182810295905b505050505050919050565b60408051918252519081900360200190f35b94508460020a88049350604060020a9250604060020a600a029150819050604060020a83680443b9c5adb08cc45f0204810390508050604060020a8484020492508250604060020a83680f0a52590f17c71a3f0204810190508050604060020a8484020492508250604060020a83682478f22e787502b0230204810390508050604060020a8484020492508250604060020a836848c6de1480526b8d4c0204810190508050604060020a8484020492508250604060020a836870c18cae824656408c0204810390508050604060020a8484020492508250604060020a8368883c81ec0ce7abebb20204810190508050604060020a8484020492508250604060020a836881814da94fe52ca9f50204810390508050604060020a8484020492508250604060020a8368616361924625d1acf50204810190508050604060020a8484020492508250604060020a836839f9a16fb9292a608d0204810390508050604060020a8484020492508250604060020a83681b3049a5740b21d65f0204810190508050604060020a8484020492508250604060020a836809ee1408bd5ad96f3e0204810390508050604060020a8484020492508250604060020a836802c465c91703b7a7f40204810190508050604060020a8484020492508250604060020a8367918d2d5f045a4d630204810390508050604060020a8484020492508250604060020a836714ca095145f44f780204810190508050604060020a8484020492508250604060020a836701d806fc412c1b990204810390508050604060020a8484020492508250604060020a836613950b4e1e89cc020481019050805085604060020a8383604060020a8902010302049650610131565b5090949350505050565b9150815b5060028282010461005b56" + }, + "0xf4cbd7e037b80c2e67b80512d482685f15b1fb28": { + "balance": "0x0", + "code": "0x6060604052361561008d5760e060020a600035046306fdde03811461008f578063095ea7b3146100a557806318160ddd1461012457806323b872dd1461012f578063313ce567146101dc578063475a9fa9146101f057806370a0823114610215578063721a37d21461024357806395d89b411461008f578063a9059cbb14610268578063dd62ed3e146102e7575b005b61031d6040805160208101909152600081525b90565b61038b60043560243560007319ee743d2e356d5f0e4d97cc09b96d06e933d0db63c6605267600160005085856040518460e060020a0281526004018084815260200183600160a060020a0316815260200182815260200193505050506020604051808303818660325a03f4156100025750506040515191506103179050565b6102316003546100a2565b61038b60043560243560443560008054604080517fa00bfa1100000000000000000000000000000000000000000000000000000000815260016004820152600160a060020a038781166024830152868116604483015260648201869052929092166084830152517319ee743d2e356d5f0e4d97cc09b96d06e933d0db9163a00bfa119160a482810192602092919082900301818660325a03f4156100025750506040515195945050505050565b604080516000815290519081900360200190f35b61038b6004356024356000805433600160a060020a0390811691161461039f57610002565b600160a060020a03600435166000908152600160205260409020545b60408051918252519081900360200190f35b61038b6004356024356000805433600160a060020a039081169116146103ce57610002565b61038b60043560243560007319ee743d2e356d5f0e4d97cc09b96d06e933d0db6388d5fecb600160005085856040518460e060020a0281526004018084815260200183600160a060020a0316815260200182815260200193505050506020604051808303818660325a03f4156100025750506040515191506103179050565b610231600435602435600160a060020a038281166000908152600260209081526040808320938516835292905220545b92915050565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561037d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b50600160a060020a03821660009081526001602081905260409091208054830190556003805483019055610317565b600160a060020a038316600090815260016020526040902054821161040a57506040600020805482900390556003805482900390556001610317565b50600061031756", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000012098a4651fb262f7", + "0xfae22198212900725daa5db635d1fda7b0fa195adaabdc806a7267959c3d8ae4": "0x000000000000000000000000000000000000000000000000731fb89f735062f7", + "0xfd73dc2251dc113619c6fcc1c142e797f06e77a178cc37fe300a56823b741ef7": "0x0000000000000000000000000000000000000000000000008ac7230489e80000" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "2340153", + "difficulty": "80383973372327", + "timestamp": "1475034716", + "gasLimit": "1500062", + "miner": "0x61c808d82a3ac53231750dadc13c777b59310bd9" + }, + "input": "0xf8ea508504a817c80083084398946ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba580b884bbd4f854e9efd3ab89acad6a3edf9828c3b00ed1c4a74e974d05d32d3b2fb15aa16fc3770000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000080d29fa5cccfadac1ba0690ce7a4cf8590c636a1799ebf2cc52229714c47da72ee406fb9bd7d29e52440a017b6ce39e8876965afa2a1c579a592eb1af146506ccdbfc2c9ea422b13dca438", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0x3de712784baf97260455ae25fb74f574ec9c1add", + "gas": "0x84398", + "gasUsed": "0x27ec3", + "to": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "input": "0xbbd4f854e9efd3ab89acad6a3edf9828c3b00ed1c4a74e974d05d32d3b2fb15aa16fc3770000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000080d29fa5cccfadac", + "output": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "calls": [ + { + "from": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "gas": "0x77e82", + "gasUsed": "0x54c", + "to": "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd", + "input": "0x8c172fa2d9a4ffe21d19763887176173d08241e8393c1dfd208f29193dfecdf854b664ac", + "output": "0x446374989d279847d0dbc6708a9c76a419fe9831d42c78bc89473f559a00d91500000000000000000000000061d76c05cd2aa9ed5135e21e52fff188b02089d4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000092f1dbea03ce08225e31e95cc926ddbe0198e6f2000000000000000000000000529c4cb814029b8bb32acb516ea3a4b07fdae350846fd373887ade3ab7703750294876afa61cf56303f5f014a4d80d04f508a1f100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "gas": "0x7737b", + "gasUsed": "0x3fe1", + "to": "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0", + "input": "0x0439978de9efd3ab89acad6a3edf9828c3b00ed1c4a74e974d05d32d3b2fb15aa16fc3770000000000000000000000000000000000000000000000004563918244f4000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000026566ea1ec2f6a9b00000000000000000000000000000000000000000000000072aa5b7e04d56a9b", + "output": "0x0000000000000000000000000000000000000000000000008060b57e2e0c99aa", + "calls": [ + { + "from": "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0", + "gas": "0x770ef", + "gasUsed": "0xc24", + "to": "0xef3487d24a0702703e04a26cef479e313c8fc7ae", + "input": "0x24d4e90a0000000000000000000000000000000000000000000000020000000000000000", + "output": "0x000000000000000000000000000000000000000000000000b17217f7d1cf79ab", + "type": "DELEGATECALL", + "value": "0x0" + }, + { + "from": "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0", + "gas": "0x75eb2", + "gasUsed": "0x265", + "to": "0xef3487d24a0702703e04a26cef479e313c8fc7ae", + "input": "0x872fb2b5000000000000000000000000000000000000000000000000c330b3f7006420b8", + "output": "0x00000000000000000000000000000000000000000000000224bf7df2c80f0878", + "type": "DELEGATECALL", + "value": "0x0" + }, + { + "from": "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0", + "gas": "0x75aad", + "gasUsed": "0x25b", + "to": "0xef3487d24a0702703e04a26cef479e313c8fc7ae", + "input": "0x872fb2b50000000000000000000000000000000000000000000000000000000000000000", + "output": "0x00000000000000000000000000000000000000000000000100000016aee6e8ef", + "type": "DELEGATECALL", + "value": "0x0" + }, + { + "from": "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0", + "gas": "0x75737", + "gasUsed": "0xc24", + "to": "0xef3487d24a0702703e04a26cef479e313c8fc7ae", + "input": "0x24d4e90a00000000000000000000000000000000000000000000000324bf7e0976f5f167", + "output": "0x0000000000000000000000000000000000000000000000012535c5e5f87ee0d2", + "type": "DELEGATECALL", + "value": "0x0" + }, + { + "from": "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0", + "gas": "0x748c7", + "gasUsed": "0x265", + "to": "0xef3487d24a0702703e04a26cef479e313c8fc7ae", + "input": "0x872fb2b5000000000000000000000000000000000000000000000000c330b3f7006420b8", + "output": "0x00000000000000000000000000000000000000000000000224bf7df2c80f0878", + "type": "DELEGATECALL", + "value": "0x0" + }, + { + "from": "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0", + "gas": "0x744c2", + "gasUsed": "0x265", + "to": "0xef3487d24a0702703e04a26cef479e313c8fc7ae", + "input": "0x872fb2b500000000000000000000000000000000000000000000000237d37fe5d297a500", + "output": "0x0000000000000000000000000000000000000000000000093088c407fcbbce38", + "type": "DELEGATECALL", + "value": "0x0" + }, + { + "from": "0x8695e5e79dab06fbbb05f445316fa4edb0da30f0", + "gas": "0x74142", + "gasUsed": "0xc99", + "to": "0xef3487d24a0702703e04a26cef479e313c8fc7ae", + "input": "0x24d4e90a00000000000000000000000000000000000000000000000b554841fac4cad6b0", + "output": "0x0000000000000000000000000000000000000000000000026d7fc130d6a74cbe", + "type": "DELEGATECALL", + "value": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "gas": "0x731be", + "gasUsed": "0x241", + "to": "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd", + "input": "0xc51cf179000000000000000000000000000000000000000000000000de0b6b3a76400000", + "output": "0x0000000000000000000000000000000000000000000000000071ea279ec31402", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "gas": "0x72df4", + "gasUsed": "0x468b", + "to": "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "input": "0x23b872dd0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba500000000000000000000000000000000000000000000000080d29fa5cccfadac", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add", + "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5" + ], + "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "gas": "0x6e627", + "gasUsed": "0x56d6", + "to": "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "input": "0x095ea7b30000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd" + ], + "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "gas": "0x68dae", + "gasUsed": "0xd6f0", + "to": "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd", + "input": "0x07d5b826d9a4ffe21d19763887176173d08241e8393c1dfd208f29193dfecdf854b664ac00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd", + "gas": "0x629ff", + "gasUsed": "0x468b", + "to": "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "input": "0x23b872dd0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba50000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd" + ], + "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd", + "gas": "0x5e0df", + "gasUsed": "0x31af", + "to": "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "input": "0xa9059cbb000000000000000000000000950ca4a06c78934a148b7a3ff3ea8fc366f77a060000000000000000000000000000000000000000000000000041f50e27d56848", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x92f1dbea03ce08225e31e95cc926ddbe0198e6f2", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd", + "0x000000000000000000000000950ca4a06c78934a148b7a3ff3ea8fc366f77a06" + ], + "data": "0x0000000000000000000000000000000000000000000000000041f50e27d56848", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd", + "gas": "0x5ac6b", + "gasUsed": "0x29ae", + "to": "0x01e60b511fced1eb2b5b40991eb1dfd171a6df42", + "input": "0x475a9fa90000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba50000000000000000000000000000000000000000000000008090aa97a4fa4564", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd", + "gas": "0x57fed", + "gasUsed": "0x29ae", + "to": "0xf4cbd7e037b80c2e67b80512d482685f15b1fb28", + "input": "0x475a9fa90000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba50000000000000000000000000000000000000000000000008090aa97a4fa4564", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "gas": "0x56030", + "gasUsed": "0x265", + "to": "0x5aae5c59d642e5fd45b427df6ed478b49d55fefd", + "input": "0x1f0c1e0cd9a4ffe21d19763887176173d08241e8393c1dfd208f29193dfecdf854b664ac0000000000000000000000000000000000000000000000000000000000000001", + "output": "0x000000000000000000000000f4cbd7e037b80c2e67b80512d482685f15b1fb28", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "gas": "0x55cc3", + "gasUsed": "0x339f", + "to": "0xf4cbd7e037b80c2e67b80512d482685f15b1fb28", + "input": "0xa9059cbb0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add000000000000000000000000000000000000000000000000de0b6b3a76400000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0xf4cbd7e037b80c2e67b80512d482685f15b1fb28", + "gas": "0x55a8a", + "gasUsed": "0x30f7", + "to": "0x19ee743d2e356d5f0e4d97cc09b96d06e933d0db", + "input": "0x88d5fecb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add000000000000000000000000000000000000000000000000de0b6b3a76400000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0xf4cbd7e037b80c2e67b80512d482685f15b1fb28", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", + "0x0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add" + ], + "data": "0x000000000000000000000000000000000000000000000000de0b6b3a76400000", + "position": "0x0" + } + ], + "type": "DELEGATECALL", + "value": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json new file mode 100644 index 0000000..a9092bb --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json @@ -0,0 +1,190 @@ +{ + "genesis": { + "difficulty": "7797655526461", + "extraData": "0xd583010203844765746885676f312e35856c696e7578", + "gasLimit": "3141592", + "hash": "0x4ad333086cb86a6d261329504c9e1ca4d571212f56d6635dd213b700e1e85a6f", + "miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226", + "mixHash": "0xdaca4c8bd9a6e6707059736633543ebf50f97c07700a9ed55859b97275c19ea5", + "nonce": "0x894c15d74e8ae8bd", + "number": "469666", + "stateRoot": "0xf9c50965ffae3f99310483a7836c545a025cc680303adaf3671dbeef99edf03a", + "timestamp": "1446318401", + "totalDifficulty": "2462705215747880313", + "alloc": { + "0x0000000000000000000000000000000000000004": { + "balance": "0x0" + }, + "0x0047a8033cc6d6ca2ed5044674fd421f44884de8": { + "balance": "0x44f5ced08fe37cf7", + "nonce": "872" + }, + "0x1d11e5eae3112dbd44f99266872ff1d07c77dce8": { + "balance": "0x0", + "code": "0x60606040526000357c01000000000000000000000000000000000000000000000000000000009004806338cc48311461004f578063767800de14610088578063d1d80fdf146100c15761004d565b005b61005c60048050506100ff565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61009560048050506100d9565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100d7600480803590602001909190505061012e565b005b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905061012b565b90565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561018a57610002565b80600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b5056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f631e3b3aafa084bc51c714825aacf505d2059be" + } + }, + "0xe48430c4e88a929bba0ee3dce284866a9937b609": { + "balance": "0x26758774d51d8677a", + "nonce": "261" + }, + "0xf631e3b3aafa084bc51c714825aacf505d2059be": { + "balance": "0x0", + "code": "0x606060405236156100da5760e060020a600035046323dc42e781146100ff5780632ef3accc146101a5578063385928321461021d57806345362978146102b65780634c7737951461034a578063524f38891461035c5780635c242c59146103ad578063772286591461044a5780637e1c42051461052457806381ade30714610601578063a2ec191a14610696578063adf59f991461071b578063ae815843146107b5578063bf1fe4201461084f578063de4b326214610890578063e8025731146108d4578063e839e65e14610970578063fbf8041814610a44575b610b20604051600160a060020a03331690600090349082818181858883f15050505050565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897606497919650602491909101945090925082915084018382808284375094965050505050505060008260006000610c0b83335b60006113fd8362030d40846101f5565b6040805160206004803580820135601f8101849004840285018401909552848452610b22949193602493909291840191908190840183828082843750949650509335935050505060006113fd8383335b600160a060020a03811660009081526003602052604081205460ff168114156114b557610b57565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897606497919650602491909101945090925082915084018382808284375094965050933593505050506000610d5885858585610841565b6040805160206004803580820135601f8101849004840285018401909552848452610b2294919360249390929184019190819084018382808284375050604080516020601f8935808c01359182018390048302840183019094528083529799986044989297509290920194509250829150840183828082843750949650505050505050600082600060006114068333610195565b610b34600154600160a060020a031681565b6040805160206004803580820135601f8101849004840285018401909552848452610b2294919360249390929184019190819084018382808284375094965050505050505060006114008233610195565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897606497919650602491909101945090925082915084018382808284375094965050933593505050505b60008360006000610d5f8333610195565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897608497919650602491909101945090925082915084018382808284375094965050505050505060008360006000610cb88333610195565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897608497919650602491909101945090925082915084018382808284375094965050933593505050505b60008460006000610f288333610195565b6040805160206004803580820135601f8101849004840285018401909552848452610b2294919360249390929184019190819084018382808284375050604080516020601f8935808c0135918201839004830284018301909452808352979998604498929750929092019450925082915084018382808284375094965050505050505060006113fd6000848462030d40610439565b6040805160206004803580820135601f8101849004840285018401909552848452610b209491936024939092918401919081908401838280828437509496505093359350505050600254600090600160a060020a039081163391909116148015906107115750600154600160a060020a039081163390911614155b156111fd57610002565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a0190935282825296989760649791965060249190910194509092508291508401838280828437509496505050505050506000610c0484848462030d40610439565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897606497919650602491909101945090925082915084018382808284375094965050933593505050505b6000610d5885858585610439565b610b20600435600254600160a060020a039081163391909116148015906108865750600154600160a060020a039081163390911614155b156114b057610002565b610b20600435600254600090600160a060020a039081163391909116148015906108ca5750600154600160a060020a039081163390911614155b1561134d57610002565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a0190935282825296989760649791965060249190910194509092508291508401838280828437509496505093359350505050600083600060006111618333610195565b6040805160206004803580820135601f8101849004840285018401909552848452610b2294919360249390929184019190819084018382808284375050604080516020601f8935808c01359182018390048302840183019094528083529799986044989297509290920194509250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897606497919650602491909101945090925082915084018382808284375094965050505050505060008360006000610b5e8333610195565b60408051602060046024803582810135601f8101859004850286018501909652858552610b229583359593946044949392909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a0190935282825296989760849791965060249190910194509092508291508401838280828437509496505093359350505050600084600060006112a68333610195565b005b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b93505050505b9392505050565b91508160001415610b8d57600160a060020a0333166000908152600360205260409020805460ff191660011790555b34829010610bff5760405173f65b3b60010d57d0bb8478aa6ced15fe720621b490600090849082818181858883f15050503484900392508211159050610bee57604051600160a060020a03331690600090839082818181858883f150505050505b610b51600088888862030d406105f0565b610002565b9050610b57565b91508160001415610c3a57600160a060020a0333166000908152600360205260409020805460ff191660011790555b34829010610bff5760405173f65b3b60010d57d0bb8478aa6ced15fe720621b490600090849082818181858883f15050503484900392508211159050610c9b57604051600160a060020a03331690600090839082818181858883f150505050505b610b5187878762030d40610439565b93505050505b949350505050565b91508160001415610ce757600160a060020a0333166000908152600360205260409020805460ff191660011790555b34829010610bff5760405173f65b3b60010d57d0bb8478aa6ced15fe720621b490600090849082818181858883f15050503484900392508211159050610d4857604051600160a060020a03331690600090839082818181858883f150505050505b610caa8888888862030d406105f0565b9050610cb0565b91508160001415610d8e57600160a060020a0333166000908152600360205260409020805460ff191660011790555b34829010610bff5760405173f65b3b60010d57d0bb8478aa6ced15fe720621b490600090849082818181858883f15050503484900392508211159050610def57604051600160a060020a03331690600090839082818181858883f150505050505b604080516000805442810183528351928390036020908101842060019290920183558184528381018d9052608084018a905260a09484018581528c51958501959095528b519198507f1f28d876aff267c3302a63cd25ebcca53e6f60691049df42275b6d06ab455c679489948e948e948e948e9492606085019260c086019289810192829185918391869190600490601f850104600302600f01f150905090810190601f168015610eb45780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610f0d5780820380516001836020036101000a031916815260200191505b5097505050505050505060405180910390a150610cb0915050565b91508160001415610f5757600160a060020a0333166000908152600360205260409020805460ff191660011790555b34829010610bff5760405173f65b3b60010d57d0bb8478aa6ced15fe720621b490600090849082818181858883f15050503484900392508211159050610fb857604051600160a060020a03331690600090839082818181858883f150505050505b60006000505442016040518082815260200191505060405180910390209350835060006000818150548092919060010191905055507f4e65aab8959da44521dc50a6ce3dfbd65016d8cfab70a47ea7541458206c4d5b848a8a8a8a8a604051808781526020018681526020018060200180602001806020018581526020018481038452888181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561108e5780820380516001836020036101000a031916815260200191505b508481038352878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156110e75780820380516001836020036101000a031916815260200191505b508481038252868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156111405780820380516001836020036101000a031916815260200191505b50995050505050505050505060405180910390a15050505b95945050505050565b9150816000141561119057600160a060020a0333166000908152600360205260409020805460ff191660011790555b34829010610bff5760405173f65b3b60010d57d0bb8478aa6ced15fe720621b490600090849082818181858883f150505034849003925082111590506111f157604051600160a060020a03331690600090839082818181858883f150505050505b610caa88888888610439565b82604051808280519060200190808383829060006004602084601f0104600302600f01f1506007805491909301849003909320600184018084559095508594509192918391508280158290116112765781836000526020600020918201910161127691905b808211156112a25760008155600101611262565b505050815481101561000257600091825260208083209091019290925591825260069052604090205550565b5090565b915081600014156112d557600160a060020a0333166000908152600360205260409020805460ff191660011790555b34829010610bff5760405173f65b3b60010d57d0bb8478aa6ced15fe720621b490600090849082818181858883f1505050348490039250821115905061133657604051600160a060020a03331690600090839082818181858883f150505050505b61134389898989896105f0565b9350505050611158565b50600481905560005b6007548110156113f957600780546006916000918490811015610002575080547fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c6888501548352602093909352604082205485029260059291908590811015610002579082527fa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688018150548152602081019190915260400160002055600101611356565b5050565b90505b92915050565b9150816000141561143557600160a060020a0333166000908152600360205260409020805460ff191660011790555b34829010610bff5760405173f65b3b60010d57d0bb8478aa6ced15fe720621b490600090849082818181858883f1505050348490039250821115905061149657604051600160a060020a03331690600090839082818181858883f150505050505b6114a66000878762030d40610439565b9350505050611400565b600855565b6005600050600085604051808280519060200190808383829060006004602084601f0104600302600f01f1509091018290039091208352505060209190915260409020546008548402019050610b5756", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000004", + "0xde5cab2c6836c23f6388364c9a0e20bd1c8c7e6c3b5d0339cd8a2f7c4b36208c": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0xf65b3b60010d57d0bb8478aa6ced15fe720621b4": { + "balance": "0x2c52a97273d2164" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "shanghaiTime": 1681338455, + "terminalTotalDifficulty": 7797655526461000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "469667", + "difficulty": "7793848077478", + "timestamp": "1446318425", + "gasLimit": "3141592", + "miner": "0xe48430c4e88a929bba0ee3dce284866a9937b609" + }, + "input": "0xf91ec7820368850ba43b7400831b77408080b91e72606060405260018054600160a060020a0319163317905561036f600360609081527f55524c0000000000000000000000000000000000000000000000000000000000608052610120604052604c60a09081527f6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f707560c0527f626c69632f5469636b65723f706169723d455448584254292e726573756c742e60e0527f58455448585842542e632e3000000000000000000000000000000000000000006101005261037d919062030d417f38cc483100000000000000000000000000000000000000000000000000000000610120908152600090731d11e5eae3112dbd44f99266872ff1d07c77dce89081906338cc4831906101249060209060048188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166338592832600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc8887604051837c010000000000000000000000000000000000000000000000000000000002815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156102255780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f11561000257505060405180517f385928320000000000000000000000000000000000000000000000000000000082526004828101888152606484018a90526080602485018181528d5160848701528d519496508a958e958e958e9594604484019360a40192909181908490829085908e906020601f850104600302600f01f150905090810190601f1680156102e65780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561033f5780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038185886185025a03f11561000257505060405151979650505050505050565b611af2806103806000396000f35b5056606060405236156100985760e060020a6000350463056e1059811461009a57806327dc297e14610391578063346b306a146103e257806341c0e1b51461075e578063489306eb146107855780635731f35714610a5e57806365a4dfb314610de05780637975c56e14611179578063a2e6204514611458578063ae152cf414611528578063b77644751461181b578063d594877014611876575b005b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750949650509335935050505060006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166338592832600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc88876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561025e5780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f1156100025750505060405180519060200150888888886040518660e060020a0281526004018085815260200180602001806020018481526020018381038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156103085780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156103615780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038185886185025a03f115610002575050604051519350610dd892505050565b60408051602060248035600481810135601f81018590048502860185019096528585526100989581359591946044949293909201918190840183828082843750949650505050505050611a2761187a565b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f8101839004830284018301909452838352979998604498929750919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166377228659600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889886040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156105d15780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f115610002575050506040518051906020015060008888886040518660e060020a028152600401808581526020018060200180602001806020018481038452878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156106795780820380516001836020036101000a031916815260200191505b508481038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156106d25780820380516001836020036101000a031916815260200191505b508481038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561072b5780820380516001836020036101000a031916815260200191505b5097505050505050505060206040518083038185886185025a03f1156100025750506040515193505050505b9392505050565b610098600154600160a060020a03908116339091161415611a255733600160a060020a0316ff5b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f8101839004830284018301909452838352979998604498929750919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663adf59f99600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889876040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109345780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f1156100025750505060405180519060200150600087876040518560e060020a0281526004018084815260200180602001806020018381038352858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109d75780820380516001836020036101000a031916815260200191505b508381038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610a305780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038185886185025a03f115610002575050604051519695505050505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166377228659600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889886040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610c535780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f1156100025750505060405180519060200150888888886040518660e060020a028152600401808581526020018060200180602001806020018481038452878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610cfa5780820380516001836020036101000a031916815260200191505b508481038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610d535780820380516001836020036101000a031916815260200191505b508481038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610dac5780820380516001836020036101000a031916815260200191505b5097505050505050505060206040518083038185886185025a03f1156100025750506040515193505050505b949350505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024919091019450909250829150840183828082843750949650509335935050505060006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663fbf80418600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc89876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610fe45780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f115610002575050506040518051906020015089898989896040518760e060020a028152600401808681526020018060200180602001806020018581526020018481038452888181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156110935780820380516001836020036101000a031916815260200191505b508481038352878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156110ec5780820380516001836020036101000a031916815260200191505b508481038252868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156111455780820380516001836020036101000a031916815260200191505b509850505050505050505060206040518083038185886185025a03f115610002575050604051519998505050505050505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663adf59f99600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889876040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561132e5780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f11561000257505050604051805190602001508787876040518560e060020a0281526004018084815260200180602001806020018381038352858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156113d05780820380516001836020036101000a031916815260200191505b508381038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156114295780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038185886185025a03f11561000257505060405151935061075792505050565b6100985b611aef604060405190810160405280600381526020017f55524c0000000000000000000000000000000000000000000000000000000000815260200150608060405190810160405280604c81526020017f6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f707581526020017f626c69632f5469636b65723f706169723d455448584254292e726573756c742e81526020017f58455448585842542e632e30000000000000000000000000000000000000000081526020015062030d416115ae565b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f810183900483028401830190945283835297999860449892975091909101945090925082915084018382808284375094965050933593505050505b60006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166338592832600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc88876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156116e75780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f115610002575050506040518051906020015060008888886040518660e060020a0281526004018085815260200180602001806020018481526020018381038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156117925780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156117eb5780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038185886185025a03f11561000257505060405151935061075792505050565b6040805160028054602060018216156101000260001901909116829004601f81018290048202840182019094528383526119679390830182828015611a1d5780601f106119f257610100808354040283529160200191611a1d565b6119d55b60006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604080518051855473ffffffffffffffffffffffffffffffffffffffff1916178086557f4c7737950000000000000000000000000000000000000000000000000000000082529151600160a060020a03929092169250634c773795916004828101926020929190829003018188876161da5a03f115610002575050604051519250505090565b60408051918252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156119c75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b820191906000526020600020905b815481529060010190602001808311611a0057829003601f168201915b505050505081565b565b600160a060020a031633600160a060020a0316141515611a4657610002565b8060026000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611aad57805160ff19168380011785555b50611add9291505b80821115611ae75760008155600101611a99565b82800160010185558215611a91579182015b82811115611a91578251826000505591602001919060010190611abf565b5050611aeb61145c565b5090565b5050565b50561ca083d25971e732af3acb0a223dea6258f37407bf2d075fd852a83312238fca7cdea01f5a1189b054e947a0a140c565c4fc829b584e7348c9a7f65a890fe688e8b67f", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0x0047a8033cc6d6ca2ed5044674fd421f44884de8", + "gas": "0x1b7740", + "gasUsed": "0x9274f", + "to": "0xc24431c1a1147456414355b1f1769de450e524da", + "input": "0x606060405260018054600160a060020a0319163317905561036f600360609081527f55524c0000000000000000000000000000000000000000000000000000000000608052610120604052604c60a09081527f6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f707560c0527f626c69632f5469636b65723f706169723d455448584254292e726573756c742e60e0527f58455448585842542e632e3000000000000000000000000000000000000000006101005261037d919062030d417f38cc483100000000000000000000000000000000000000000000000000000000610120908152600090731d11e5eae3112dbd44f99266872ff1d07c77dce89081906338cc4831906101249060209060048188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166338592832600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc8887604051837c010000000000000000000000000000000000000000000000000000000002815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156102255780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f11561000257505060405180517f385928320000000000000000000000000000000000000000000000000000000082526004828101888152606484018a90526080602485018181528d5160848701528d519496508a958e958e958e9594604484019360a40192909181908490829085908e906020601f850104600302600f01f150905090810190601f1680156102e65780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561033f5780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038185886185025a03f11561000257505060405151979650505050505050565b611af2806103806000396000f35b5056606060405236156100985760e060020a6000350463056e1059811461009a57806327dc297e14610391578063346b306a146103e257806341c0e1b51461075e578063489306eb146107855780635731f35714610a5e57806365a4dfb314610de05780637975c56e14611179578063a2e6204514611458578063ae152cf414611528578063b77644751461181b578063d594877014611876575b005b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750949650509335935050505060006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166338592832600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc88876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561025e5780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f1156100025750505060405180519060200150888888886040518660e060020a0281526004018085815260200180602001806020018481526020018381038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156103085780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156103615780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038185886185025a03f115610002575050604051519350610dd892505050565b60408051602060248035600481810135601f81018590048502860185019096528585526100989581359591946044949293909201918190840183828082843750949650505050505050611a2761187a565b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f8101839004830284018301909452838352979998604498929750919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166377228659600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889886040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156105d15780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f115610002575050506040518051906020015060008888886040518660e060020a028152600401808581526020018060200180602001806020018481038452878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156106795780820380516001836020036101000a031916815260200191505b508481038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156106d25780820380516001836020036101000a031916815260200191505b508481038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561072b5780820380516001836020036101000a031916815260200191505b5097505050505050505060206040518083038185886185025a03f1156100025750506040515193505050505b9392505050565b610098600154600160a060020a03908116339091161415611a255733600160a060020a0316ff5b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f8101839004830284018301909452838352979998604498929750919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663adf59f99600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889876040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109345780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f1156100025750505060405180519060200150600087876040518560e060020a0281526004018084815260200180602001806020018381038352858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109d75780820380516001836020036101000a031916815260200191505b508381038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610a305780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038185886185025a03f115610002575050604051519695505050505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166377228659600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889886040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610c535780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f1156100025750505060405180519060200150888888886040518660e060020a028152600401808581526020018060200180602001806020018481038452878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610cfa5780820380516001836020036101000a031916815260200191505b508481038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610d535780820380516001836020036101000a031916815260200191505b508481038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610dac5780820380516001836020036101000a031916815260200191505b5097505050505050505060206040518083038185886185025a03f1156100025750506040515193505050505b949350505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024919091019450909250829150840183828082843750949650509335935050505060006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663fbf80418600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc89876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610fe45780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f115610002575050506040518051906020015089898989896040518760e060020a028152600401808681526020018060200180602001806020018581526020018481038452888181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156110935780820380516001836020036101000a031916815260200191505b508481038352878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156110ec5780820380516001836020036101000a031916815260200191505b508481038252868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156111455780820380516001836020036101000a031916815260200191505b509850505050505050505060206040518083038185886185025a03f115610002575050604051519998505050505050505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663adf59f99600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889876040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561132e5780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f11561000257505050604051805190602001508787876040518560e060020a0281526004018084815260200180602001806020018381038352858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156113d05780820380516001836020036101000a031916815260200191505b508381038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156114295780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038185886185025a03f11561000257505060405151935061075792505050565b6100985b611aef604060405190810160405280600381526020017f55524c0000000000000000000000000000000000000000000000000000000000815260200150608060405190810160405280604c81526020017f6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f707581526020017f626c69632f5469636b65723f706169723d455448584254292e726573756c742e81526020017f58455448585842542e632e30000000000000000000000000000000000000000081526020015062030d416115ae565b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f810183900483028401830190945283835297999860449892975091909101945090925082915084018382808284375094965050933593505050505b60006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166338592832600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc88876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156116e75780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f115610002575050506040518051906020015060008888886040518660e060020a0281526004018085815260200180602001806020018481526020018381038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156117925780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156117eb5780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038185886185025a03f11561000257505060405151935061075792505050565b6040805160028054602060018216156101000260001901909116829004601f81018290048202840182019094528383526119679390830182828015611a1d5780601f106119f257610100808354040283529160200191611a1d565b6119d55b60006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604080518051855473ffffffffffffffffffffffffffffffffffffffff1916178086557f4c7737950000000000000000000000000000000000000000000000000000000082529151600160a060020a03929092169250634c773795916004828101926020929190829003018188876161da5a03f115610002575050604051519250505090565b60408051918252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156119c75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b820191906000526020600020905b815481529060010190602001808311611a0057829003601f168201915b505050505081565b565b600160a060020a031633600160a060020a0316141515611a4657610002565b8060026000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611aad57805160ff19168380011785555b50611add9291505b80821115611ae75760008155600101611a99565b82800160010185558215611a91579182015b82811115611a91578251826000505591602001919060010190611abf565b5050611aeb61145c565b5090565b5050565b5056", + "output": "0x606060405236156100985760e060020a6000350463056e1059811461009a57806327dc297e14610391578063346b306a146103e257806341c0e1b51461075e578063489306eb146107855780635731f35714610a5e57806365a4dfb314610de05780637975c56e14611179578063a2e6204514611458578063ae152cf414611528578063b77644751461181b578063d594877014611876575b005b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750949650509335935050505060006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166338592832600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc88876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561025e5780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f1156100025750505060405180519060200150888888886040518660e060020a0281526004018085815260200180602001806020018481526020018381038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156103085780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156103615780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038185886185025a03f115610002575050604051519350610dd892505050565b60408051602060248035600481810135601f81018590048502860185019096528585526100989581359591946044949293909201918190840183828082843750949650505050505050611a2761187a565b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f8101839004830284018301909452838352979998604498929750919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166377228659600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889886040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156105d15780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f115610002575050506040518051906020015060008888886040518660e060020a028152600401808581526020018060200180602001806020018481038452878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156106795780820380516001836020036101000a031916815260200191505b508481038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156106d25780820380516001836020036101000a031916815260200191505b508481038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561072b5780820380516001836020036101000a031916815260200191505b5097505050505050505060206040518083038185886185025a03f1156100025750506040515193505050505b9392505050565b610098600154600160a060020a03908116339091161415611a255733600160a060020a0316ff5b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f8101839004830284018301909452838352979998604498929750919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663adf59f99600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889876040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109345780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f1156100025750505060405180519060200150600087876040518560e060020a0281526004018084815260200180602001806020018381038352858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156109d75780820380516001836020036101000a031916815260200191505b508381038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610a305780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038185886185025a03f115610002575050604051519695505050505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166377228659600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889886040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610c535780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f1156100025750505060405180519060200150888888886040518660e060020a028152600401808581526020018060200180602001806020018481038452878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610cfa5780820380516001836020036101000a031916815260200191505b508481038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610d535780820380516001836020036101000a031916815260200191505b508481038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610dac5780820380516001836020036101000a031916815260200191505b5097505050505050505060206040518083038185886185025a03f1156100025750506040515193505050505b949350505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976084979196506024919091019450909250829150840183828082843750949650509335935050505060006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663fbf80418600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc89876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015610fe45780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f115610002575050506040518051906020015089898989896040518760e060020a028152600401808681526020018060200180602001806020018581526020018481038452888181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156110935780820380516001836020036101000a031916815260200191505b508481038352878181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156110ec5780820380516001836020036101000a031916815260200191505b508481038252868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156111455780820380516001836020036101000a031916815260200191505b509850505050505050505060206040518083038185886185025a03f115610002575050604051519998505050505050505050565b60408051602060248035600481810135601f81018590048502860185019096528585526119559581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160e060020a6338cc48310281529051959760009750731d11e5eae3112dbd44f99266872ff1d07c77dce8968796506338cc4831955082820194506020935091829003018188876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a031663adf59f99600060009054906101000a9004600160a060020a0316600160a060020a031663524f3889876040518260e060020a02815260040180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561132e5780820380516001836020036101000a031916815260200191505b50925050506020604051808303816000876161da5a03f11561000257505050604051805190602001508787876040518560e060020a0281526004018084815260200180602001806020018381038352858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156113d05780820380516001836020036101000a031916815260200191505b508381038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156114295780820380516001836020036101000a031916815260200191505b509550505050505060206040518083038185886185025a03f11561000257505060405151935061075792505050565b6100985b611aef604060405190810160405280600381526020017f55524c0000000000000000000000000000000000000000000000000000000000815260200150608060405190810160405280604c81526020017f6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f707581526020017f626c69632f5469636b65723f706169723d455448584254292e726573756c742e81526020017f58455448585842542e632e30000000000000000000000000000000000000000081526020015062030d416115ae565b6040805160206004803580820135601f8101849004840285018401909552848452611955949193602493909291840191908190840183828082843750506040805160208835808b0135601f810183900483028401830190945283835297999860449892975091909101945090925082915084018382808284375094965050933593505050505b60006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150600060006101000a815481600160a060020a0302191690830217905550600060009054906101000a9004600160a060020a0316600160a060020a03166338592832600060009054906101000a9004600160a060020a0316600160a060020a0316632ef3accc88876040518360e060020a02815260040180806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156116e75780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f115610002575050506040518051906020015060008888886040518660e060020a0281526004018085815260200180602001806020018481526020018381038352868181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156117925780820380516001836020036101000a031916815260200191505b508381038252858181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156117eb5780820380516001836020036101000a031916815260200191505b50965050505050505060206040518083038185886185025a03f11561000257505060405151935061075792505050565b6040805160028054602060018216156101000260001901909116829004601f81018290048202840182019094528383526119679390830182828015611a1d5780601f106119f257610100808354040283529160200191611a1d565b6119d55b60006000731d11e5eae3112dbd44f99266872ff1d07c77dce8905080600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604080518051855473ffffffffffffffffffffffffffffffffffffffff1916178086557f4c7737950000000000000000000000000000000000000000000000000000000082529151600160a060020a03929092169250634c773795916004828101926020929190829003018188876161da5a03f115610002575050604051519250505090565b60408051918252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156119c75780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b820191906000526020600020905b815481529060010190602001808311611a0057829003601f168201915b505050505081565b565b600160a060020a031633600160a060020a0316141515611a4657610002565b8060026000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10611aad57805160ff19168380011785555b50611add9291505b80821115611ae75760008155600101611a99565b82800160010185558215611a91579182015b82811115611a91578251826000505591602001919060010190611abf565b5050611aeb61145c565b5090565b5050565b5056", + "calls": [ + { + "from": "0xc24431c1a1147456414355b1f1769de450e524da", + "gas": "0x12c54b", + "gasUsed": "0x106", + "to": "0x1d11e5eae3112dbd44f99266872ff1d07c77dce8", + "input": "0x38cc4831", + "output": "0x000000000000000000000000f631e3b3aafa084bc51c714825aacf505d2059be", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0xc24431c1a1147456414355b1f1769de450e524da", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0xc24431c1a1147456414355b1f1769de450e524da", + "gas": "0x127270", + "gasUsed": "0x26b", + "to": "0xf631e3b3aafa084bc51c714825aacf505d2059be", + "input": "0x2ef3accc00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000030d41000000000000000000000000000000000000000000000000000000000000000355524c0000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0xc24431c1a1147456414355b1f1769de450e524da", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0xc24431c1a1147456414355b1f1769de450e524da", + "gas": "0x18", + "gasUsed": "0x18", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f7075626c69632f5469636b65723f706169723d455448584254292e726573756c742e58455448585842542e632e30", + "output": "0x6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f7075626c69632f5469636b65723f706169723d455448584254292e726573756c742e58455448585842542e632e30", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0xc24431c1a1147456414355b1f1769de450e524da", + "gas": "0x124995", + "gasUsed": "0x78f5", + "to": "0xf631e3b3aafa084bc51c714825aacf505d2059be", + "input": "0x385928320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000030d41000000000000000000000000000000000000000000000000000000000000000355524c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f7075626c69632f5469636b65723f706169723d455448584254292e726573756c742e58455448585842542e632e300000000000000000000000000000000000000000", + "output": "0x55bc8431ce52389ac668a9b14a0943290cb7263732251186e960bc8b249b5f32", + "calls": [ + { + "from": "0xf631e3b3aafa084bc51c714825aacf505d2059be", + "gas": "0x0", + "gasUsed": "0x0", + "to": "0xf65b3b60010d57d0bb8478aa6ced15fe720621b4", + "input": "0x", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0xf631e3b3aafa084bc51c714825aacf505d2059be", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0xf631e3b3aafa084bc51c714825aacf505d2059be", + "gas": "0x18", + "gasUsed": "0x18", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f7075626c69632f5469636b65723f706169723d455448584254292e726573756c742e58455448585842542e632e30", + "output": "0x6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f7075626c69632f5469636b65723f706169723d455448584254292e726573756c742e58455448585842542e632e30", + "value": "0x0", + "type": "CALL" + } + ], + "logs":[ + { + "address": "0xf631e3b3aafa084bc51c714825aacf505d2059be", + "topics": ["0x1f28d876aff267c3302a63cd25ebcca53e6f60691049df42275b6d06ab455c67"], + "data":"0x55bc8431ce52389ac668a9b14a0943290cb7263732251186e960bc8b249b5f32000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000030d41000000000000000000000000000000000000000000000000000000000000000355524c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c6a736f6e2868747470733a2f2f6170692e6b72616b656e2e636f6d2f302f7075626c69632f5469636b65723f706169723d455448584254292e726573756c742e58455448585842542e632e300000000000000000000000000000000000000000", + "position":"0x3" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CREATE" + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..263e88d --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multi_contracts.json @@ -0,0 +1,2327 @@ +{ + "genesis": { + "difficulty": "59917798787272", + "extraData": "0xe4b883e5bda9e7a59ee4bb99e9b1bc", + "gasLimit": "4712380", + "hash": "0xae82afe3630b001a34ad4c51695dacb17872ebee4dadd2de88b1a16671871da4", + "miner": "0x61c808d82a3ac53231750dadc13c777b59310bd9", + "mixHash": "0x23c2289cdee8a397cf36db9ffa3419503bed54eb09e988b3c7a3587a090e6fc1", + "nonce": "0x94dc83e0044f49c8", + "number": "1881283", + "stateRoot": "0x6e3832bc2e4e66170a1e716449083e08fbb70e7b2a9f1f34e0c57e66ce40c50f", + "timestamp": "1468467284", + "totalDifficulty": "37186898441932102239", + "alloc": { + "0x0000000000000000000000000000000000000004": { + "balance": "0x0" + }, + "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e": { + "balance": "0x0", + "code": "0x606060405236156100f05760e060020a600035046303afc23581146100f257806313af4035146101145780631838e26614610136578063186ef9621461014d57806327df8c501461016f578063295d5866146101915780634162169f146101b35780634dfc3db6146101c55780636637b882146101e8578063839d3f7f1461020a57806386c9b5361461021d5780638da5cb5b1461022f5780639093a5e714610241578063b199efb514610263578063b262b9ae14610275578063b9f34aa114610297578063be9a6555146102a9578063d1c3c84a146102c7578063e26fc92b146102d9578063e8d9f074146102eb575b005b6100f0600435600054600160a060020a03908116339091161461034057610002565b6100f0600435600054600160a060020a03908116339091161461035557610002565b6102fd60006000600060006000600061036a6101c9565b6100f0600435600054600160a060020a0390811633909116146108a157610002565b6100f0600435600054600160a060020a0390811633909116146108b657610002565b6100f0600435600054600160a060020a0390811633909116146108cb57610002565b61030f600154600160a060020a031681565b6102fd5b60008054600160a060020a0390811633909116146108e0575060015b90565b6100f0600435600054600160a060020a03908116339091161461098857610002565b61032c60075460a060020a900460ff1681565b61030f600454600160a060020a031681565b61030f600054600160a060020a031681565b6100f0600435600054600160a060020a03908116339091161461099d57610002565b61030f600254600160a060020a031681565b6100f0600435600054600160a060020a0390811633909116146109b257610002565b61030f600754600160a060020a031681565b6100f060005433600160a060020a03908116911614610a1a57610002565b61030f600354600160a060020a031681565b61030f600554600160a060020a031681565b61030f600654600160a060020a031681565b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b604080519115158252519081900360200190f35b60048054600160a060020a0319168217905550565b60008054600160a060020a0319168217905550565b945060008514610380578495505b505050505090565b6002546040805160015460e060020a634162169f0282529151600160a060020a039283169390921691634162169f9160048181019260209290919082900301816000876161da5a03f11561000257505060405151600160a060020a031690911490506103ef5760649550610378565b600260009054906101000a9004600160a060020a0316600160a060020a0316634dfc3db66040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519450506000841461045857836064019550610378565b6040805160015460035460e060020a634162169f0283529251600160a060020a039182169390911691634162169f91600482810192602092919082900301816000876161da5a03f11561000257505060405151600160a060020a031690911490506104c65760c89550610378565b600360009054906101000a9004600160a060020a0316600160a060020a0316634dfc3db66040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519350506000831461052f578260c8019550610378565b604080516001546004805460e060020a634162169f0284529351600160a060020a039283169490921692634162169f928183019260209282900301816000876161da5a03f11561000257505060405151600160a060020a0316909114905061059b5761012c9550610378565b60408051600480547f4dfc3db60000000000000000000000000000000000000000000000000000000083529251600160a060020a039390931692634dfc3db692808301926020929182900301816000876161da5a03f1156100025750506040515192505060008214610613578161012c019550610378565b6040805160015460055460e060020a634162169f0283529251600160a060020a039182169390911691634162169f91600482810192602092919082900301816000876161da5a03f11561000257505060405151600160a060020a03169091149050610682576101909550610378565b600560009054906101000a9004600160a060020a0316600160a060020a0316634dfc3db66040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151915050600081146106ec5780610190019550610378565b6040805160015460065460e060020a634162169f0283529251600160a060020a039182169390911691634162169f91600482810192602092919082900301816000876161da5a03f11561000257505060405151600160a060020a0316909114905061075b576101f49550610378565b6040805160065460e060020a638da5cb5b028252915130600160a060020a03908116931691638da5cb5b91600482810192602092919082900301816000876161da5a03f11561000257505060405151600160a060020a031690911490506107c6576101f59550610378565b6040805160075460015460e060020a634162169f0283529251600160a060020a03938416939190911691634162169f91600482810192602092919082900301816000876161da5a03f11561000257505060405151600160a060020a03169091149050610836576102589550610378565b6040805160075460e060020a638da5cb5b028252915130600160a060020a03908116931691638da5cb5b91600482810192602092919082900301816000876161da5a03f11561000257505060405151600160a060020a03169091149050610378576102599550610378565b60038054600160a060020a0319168217905550565b60058054600160a060020a0319168217905550565b60068054600160a060020a0319168217905550565b600154600160a060020a0316600014156108fc575060026101e5565b600654600160a060020a031660001415610918575060036101e5565b600754600160a060020a031660001415610934575060046101e5565b600254600160a060020a031660001415610950575060056101e5565b600354600160a060020a03166000141561096c575060066101e5565b600454600160a060020a0316600014156101e5575060076101e5565b60018054600160a060020a0319168217905550565b60078054600160a060020a0319168217905550565b60028054600160a060020a0319168217905550565b600260009054906101000a9004600160a060020a0316600160a060020a031663975057e76040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050505b565b610a8a600154604080517f4b6753bc0000000000000000000000000000000000000000000000000000000081529051600092600160a060020a031691634b6753bc916004828101926020929190829003018187876161da5a03f11561000257505060405151421191506101e59050565b1515610a9557610a18565b60075460a060020a900460ff1615610e93576111556040805160015460065460e060020a6370a08231028352600160a060020a039081166004840152925160009384939216916370a08231916024808301926020929190829003018187876161da5a03f1156100025750506040515191909111159050610c61576040805160065460025460015460e060020a6370a08231028452600160a060020a0392831660048501819052945163a9059cbb949284169391909116916370a0823191602482810192602092919082900301818a876161da5a03f11561000257505060408051805160e060020a63a9059cbb028252600482019490945260248101939093525160448084019360209350829003018187876161da5a03f1156100025750506040805160025460e060020a63a8618f71028252600160a060020a031660048201819052915191925063a8618f71916024828101926020929190829003018187876161da5a03f11561000257505060405151159050610c6157600260009054906101000a9004600160a060020a0316600160a060020a031663975057e76040518160e060020a0281526004018090506000604051808303816000876161da5a03f11561000257506001925050505b6001546007546040805160e060020a6370a08231028152600160a060020a0392831660048201529051600093909216916370a0823191602481810192602092909190829003018187876161da5a03f1156100025750506040515191909111159050610e1b576040805160075460025460015460e060020a6370a08231028452600160a060020a0392831660048501819052945163a9059cbb949284169391909116916370a0823191602482810192602092919082900301816000876161da5a03f11561000257505060408051805160e060020a63a9059cbb02825260048201949094526024810193909352516044838101936020935082900301816000876161da5a03f1156100025750506040805160025460e060020a63a8618f71028252600160a060020a031660048201819052915191925063a8618f7191602482810192602092919082900301816000876161da5a03f11561000257505060405151159050610e1b57600260009054906101000a9004600160a060020a0316600160a060020a031663975057e76040518160e060020a0281526004018090506000604051808303816000876161da5a03f11561000257506001925050505b8015610e7257600260009054906101000a9004600160a060020a0316600160a060020a0316632e64cec16040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050505b6007805474ff00000000000000000000000000000000000000001916905550565b600260009054906101000a9004600160a060020a0316600160a060020a0316632e64cec16040518160e060020a0281526004018090506000604051808303816000876161da5a03f115610002575050505b60048054604080517ffc3407160000000000000000000000000000000000000000000000000000000081529051600160a060020a03929092169263fc340716928282019260009290829003018183876161da5a03f115610002575050600354604080517fd95f98ce0000000000000000000000000000000000000000000000000000000081529051600160a060020a0392909216925063d95f98ce916004828101926000929190829003018183876161da5a03f11561000257505050620f42405a11156109c7576109c76001546005546040805160e060020a6370a08231028152600160a060020a039283166004820152905192909116916370a082319160248181019260209290919082900301816000876161da5a03f1156100025750506040515160001415905061108357604080516002546005547fd0679d34000000000000000000000000000000000000000000000000000000008352600160a060020a03908116600484015260016024840152925192169163d0679d349160448181019260209290919082900301816000876161da5a03f115610002575050505b5b600554604080517f400e39490000000000000000000000000000000000000000000000000000000081529051600a92600160a060020a03169163400e394991600482810192602092919082900301816000876161da5a03f1156100025750506040515191909110905080156110fb5750620aae605a115b15610a1857600560009054906101000a9004600160a060020a0316600160a060020a031663ff2f4bd26040518160e060020a0281526004018090506000604051808303816000876161da5a03f11561000257505050611084565b610ee456", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000304a554a310c7e546dfe434669c62820b7d83490", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x0000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x000000000000000000000000c0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x000000000000000000000001f835a0247b0063c04ef22006ebe57c5f11977cc4" + } + }, + "0x304a554a310c7e546dfe434669c62820b7d83490": { + "balance": "0x3034f5ca7d45e17df1d83", + "nonce": "3", + "code": "0x6060604052361561020e5760e060020a6000350463013cf08b8114610247578063095ea7b3146102d05780630c3b7b96146103455780630e7082031461034e578063149acf9a1461036057806318160ddd146103725780631f2dc5ef1461037b57806321b5b8dd1461039b578063237e9492146103ad57806323b872dd1461040e5780632632bf2014610441578063341458081461047257806339d1f9081461047b5780634b6753bc146104935780634df6d6cc1461049c5780634e10c3ee146104b7578063590e1ae3146104ca578063612e45a3146104db578063643f7cdd1461057a578063674ed066146105925780636837ff1e1461059b57806370a08231146105e5578063749f98891461060b57806378524b2e1461062457806381f03fcb1461067e57806382661dc41461069657806382bf6464146106b75780638b15a605146106c95780638d7af473146106d257806396d7f3f5146106e1578063a1da2fb9146106ea578063a3912ec814610704578063a9059cbb1461070f578063b7bc2c841461073f578063baac53001461074b578063be7c29c1146107b1578063c9d27afe14610817578063cc9ae3f61461082d578063cdef91d014610841578063dbde198814610859578063dd62ed3e1461087e578063e33734fd146108b2578063e5962195146108c6578063e66f53b7146108de578063eceb2945146108f0578063f8c80d261461094f575b610966600f546000906234bc000142108015610239575060125433600160a060020a03908116911614155b156109785761098033610752565b6109866004356000805482908110156100025750808052600e8202600080516020612a3683398151915201905060038101546004820154600683015460018401548454600786015460058701546009880154600a890154600d8a0154600160a060020a039586169b509599600201989760ff81811698610100909204811697949691951693168c565b61096660043560243533600160a060020a03908116600081815260156020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b61096660105481565b610a7d600754600160a060020a031681565b610a7d600e54600160a060020a031681565b61096660165481565b6109665b60004262127500600f60005054031115610de557506014610983565b610a7d601254600160a060020a031681565b60408051602060248035600481810135601f810185900485028601850190965285855261096695813595919460449492939092019181908401838280828437509496505050505050506000600060006000600060003411156116a857610002565b6109666004356024356044355b60115460009060ff1680156104315750600f5442115b80156124e957506124e78461044b565b6109666000610980335b600160a060020a0381166000908152600b602052604081205481908114156129cb57610b99565b61096660065481565b6109665b600d5430600160a060020a03163103610983565b610966600f5481565b61096660043560046020526000908152604090205460ff1681565b61096660043560243560006124cb610831565b610a9a6000341115610ba457610002565b604080516020604435600481810135601f8101849004840285018401909552848452610966948135946024803595939460649492939101918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897608497919650602491909101945090925082915084018382808284375094965050933593505060a435915050600060006110c1336105ec565b61096660043560096020526000908152604090205481565b61096660015481565b610a9a60043530600160a060020a031633600160a060020a03161415806105db5750600160a060020a03811660009081526004602052604090205460ff16155b156121cb576121c8565b6109666004355b600160a060020a0381166000908152601460205260409020545b919050565b6109666004356024356000600034111561259957610002565b610966600062e6b680420360026000505410806106505750600354600160a060020a0390811633909116145b80156106645750600254621274ff19420190105b156126145750426002908155600180549091028155610983565b610966600435600a6020526000908152604090205481565b610966600435602435600060006000600060006000341115611ba157610002565b610a7d600854600160a060020a031681565b610966600c5481565b61096660005460001901610983565b61096660025481565b61096660043560006000600060003411156121fc57610002565b6109665b6001610983565b6109666004356024355b60115460009060ff16801561072f5750600f5442115b801561248757506124853361044b565b61096660115460ff1681565b6109666004355b60006000600f600050544210801561076a5750600034115b80156107a457506011546101009004600160a060020a0316600014806107a457506011546101009004600160a060020a0390811633909116145b15610b9f57610a9c61037f565b610a7d600435600060006000508281548110156100025750508080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56b600e83020180548290811015610002575081526020902060030154600160a060020a0316610606565b61096660043560243560006000610e1b336105ec565b6109665b6000600034111561247c57610002565b61096660043560056020526000908152604090205481565b610966600435602435604435600061252f845b6000600060003411156127ac57610002565b610966600435602435600160a060020a0382811660009081526015602090815260408083209385168352929052205461033f565b610a9a600435600034111561254557610002565b610966600435600b6020526000908152604090205481565b610a7d600354600160a060020a031681565b604080516020606435600481810135601f81018490048402850184019095528484526109669481359460248035956044359560849492019190819084018382808284375094965050505050505060006000600034111561103257610002565b610a7d6011546101009004600160a060020a031681565b60408051918252519081900360200190f35b610980610708565b90505b90565b604051808d600160a060020a031681526020018c8152602001806020018b81526020018a815260200189815260200188815260200187815260200186815260200185815260200184815260200183600160a060020a0316815260200182810382528c818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610a635780601f10610a3857610100808354040283529160200191610a63565b820191906000526020600020905b815481529060010190602001808311610a4657829003601f168201915b50509d505050505050505050505050505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b005b604051601254601434908102939093049350600160a060020a03169183900390600081818185876185025a03f150505050600160a060020a038316600081815260146020908152604080832080548601905560168054860190556013825291829020805434019055815184815291517fdbccb92686efceafb9bb7e0394df7f58f71b954061b81afb57109bf247d3d75a9281900390910190a260105460165410801590610b4c575060115460ff16155b15610b94576011805460ff1916600117905560165460408051918252517ff381a3e2428fdda36615919e8d9c35878d9eb0cf85ac6edf575088e80e4c147e9181900360200190a15b600191505b50919050565b610002565b600f5442118015610bb8575060115460ff16155b15610de357601260009054906101000a9004600160a060020a0316600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040516012549051600160a060020a039190911631109050610cc9576040805160125460e060020a63d2cc718f0282529151600160a060020a039290921691630221038a913091849163d2cc718f91600482810192602092919082900301816000876161da5a03f11561000257505060408051805160e160020a63011081c5028252600160a060020a039490941660048201526024810193909352516044838101936020935082900301816000876161da5a03f115610002575050505b33600160a060020a0316600081815260136020526040808220549051909181818185876185025a03f19250505015610de35733600160a060020a03167fbb28353e4598c3b9199101a66e0989549b659a59a54d2c27fbb183f1932c8e6d6013600050600033600160a060020a03168152602001908152602001600020600050546040518082815260200191505060405180910390a26014600050600033600160a060020a0316815260200190815260200160002060005054601660008282825054039250508190555060006014600050600033600160a060020a031681526020019081526020016000206000508190555060006013600050600033600160a060020a03168152602001908152602001600020600050819055505b565b4262054600600f60005054031115610e13576201518062127500600f60005054034203046014019050610983565b50601e610983565b60001415610e2857610002565b6000341115610e3657610002565b6000805485908110156100025750600160a060020a03331681527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56e600e8602908101602052604090912054600080516020612a3683398151915291909101915060ff1680610eb05750600c810160205260406000205460ff165b80610ebf575060038101544210155b15610ec957610002565b8215610f0f5733600160a060020a03166000908152601460209081526040808320546009850180549091019055600b84019091529020805460ff19166001179055610f4b565b33600160a060020a0316600090815260146020908152604080832054600a850180549091019055600c84019091529020805460ff191660011790555b33600160a060020a03166000908152600b60205260408120541415610f77576040600020849055610feb565b33600160a060020a03166000908152600b60205260408120548154811015610002579080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566600e909102015460038201541115610feb5733600160a060020a03166000908152600b602052604090208490555b60408051848152905133600160a060020a03169186917f86abfce99b7dd908bec0169288797f85049ec73cbe046ed9de818fab3a497ae09181900360200190a35092915050565b6000805487908110156100025750808052600e8702600080516020612a3683398151915201905090508484846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020816005016000505414915050949350505050565b600014156110ce57610002565b82801561111857508660001415806110e857508451600014155b806111005750600354600160a060020a038981169116145b8061110b5750600034115b80611118575062093a8084105b1561112257610002565b8215801561114257506111348861115c565b158061114257506212750084105b156111fe57610002565b83546118e590600160a060020a03165b600160a060020a03811660009081526004602052604081205460ff16806111f15750601254600160a060020a039081169083161480156111f15750601260009054906101000a9004600160a060020a0316600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051516006541190505b156129a157506001610606565b6249d40084111561120e57610002565b60115460ff1615806112215750600f5442105b806112365750600c5434108015611236575082155b1561124057610002565b42844201101561124f57610002565b30600160a060020a031633600160a060020a0316141561126e57610002565b60008054600181018083559091908280158290116112a557600e0281600e0283600052602060002091820191016112a5919061136a565b505060008054929450918491508110156100025750808052600e8302600080516020612a368339815191520190508054600160a060020a031916891781556001818101899055875160028084018054600082815260209081902096975091959481161561010002600019011691909104601f908101829004840193918b019083901061146257805160ff19168380011785555b5061149292915061144a565b5050600060098201819055600a820155600d81018054600160a060020a03191690556001015b8082111561145e578054600160a060020a03191681556000600182810182905560028084018054848255909281161561010002600019011604601f81901061143057505b506000600383018190556004808401805461ffff19169055600584018290556006840182905560078401805460ff191690556008840180548382559083526020909220611344929091028101905b8082111561145e57600080825560018201818155600283019190915560039091018054600160a060020a03191690556113fc565b601f0160209004906000526020600020908101906113ae91905b8082111561145e576000815560010161144a565b5090565b82800160010185558215611338579182015b82811115611338578251826000505591602001919060010190611474565b50508787866040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160050160005081905550834201816003016000508190555060018160040160006101000a81548160ff02191690830217905550828160070160006101000a81548160ff02191690830217905550821561157857600881018054600181018083559091908280158290116115735760040281600402836000526020600020918201910161157391906113fc565b505050505b600d8082018054600160a060020a031916331790553460068301819055815401905560408051600160a060020a038a16815260208181018a9052918101859052608060608201818152895191830191909152885185937f5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f938d938d938a938e93929160a084019185810191908190849082908590600090600490601f850104600f02600301f150905090810190601f1680156116485780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a2509695505050505050565b6040805186815260208101839052815189927fdfc78bdca8e3e0b18c16c5c99323c6cb9eb5e00afde190b4e7273f5158702b07928290030190a25b5050505092915050565b6000805488908110156100025750808052600e8802600080516020612a36833981519152019050600781015490945060ff166116e757620d2f006116ec565b622398805b600485015490935060ff16801561170857506003840154830142115b15611716576117b887611890565b600384015442108061172d5750600484015460ff16155b806117ae57508360000160009054906101000a9004600160a060020a03168460010160005054876040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020846005016000505414155b1561114c57610002565b61169e565b60048401805461ff001916610100179055835460019550600160a060020a03908116309091161480159061180157508354600754600160a060020a03908116911614155b801561181d57506008548454600160a060020a03908116911614155b801561183957508354601254600160a060020a03908116911614155b801561185557506003548454600160a060020a03908116911614155b1561188b5760018401805430600160a060020a031660009081526005602052604090208054919091019055546006805490910190555b611663875b6000600060005082815481101561000257908052600e02600080516020612a36833981519152018150600481015490915060ff16156118d757600d80546006830154900390555b600401805460ff1916905550565b15156118f45761190087611890565b6001915061193161047f565b604051600d8501546006860154600160a060020a0391909116916000919082818181858883f193505050505061169e565b6001850154111561194157600091505b50600a8301546009840154865191019060049010801590611986575085600081518110156100025790602001015160f860020a900460f860020a02606860f860020a02145b80156119b6575085600181518110156100025790602001015160f860020a900460f860020a02603760f860020a02145b80156119e6575085600281518110156100025790602001015160f860020a900460f860020a0260ff60f860020a02145b8015611a16575085600381518110156100025790602001015160f860020a900460f860020a02601e60f860020a02145b8015611a45575030600160a060020a0316600090815260056020526040902054611a4290611a5d61047f565b81105b15611a4f57600091505b6001840154611a8090611a5f565b015b30600160a060020a03166000908152600560205260408120546129a961047f565b8110611ad457604051600d8501546006860154600160a060020a0391909116916000919082818181858883f193505050501515611abc57610002565b4260025560165460059004811115611ad45760056001555b6001840154611ae290611a5f565b8110158015611af85750600a8401546009850154115b8015611b015750815b1561188b578360000160009054906101000a9004600160a060020a0316600160a060020a0316846001016000505487604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611b7d5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f19250505015156117bd57610002565b611baa336105ec565b60001415611bb757610002565b60008054889081101561000257508052600e87027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566810154600080516020612a36833981519152919091019450421080611c1957506003840154622398800142115b80611c3257508354600160a060020a0390811690871614155b80611c425750600784015460ff16155b80611c68575033600160a060020a03166000908152600b8501602052604090205460ff16155b80611c9c575033600160a060020a03166000908152600b60205260409020548714801590611c9c5750604060009081205414155b15611ca657610002565b600884018054600090811015610002579081526020812060030154600160a060020a03161415611e1257611efc86604051600090600160a060020a038316907f9046fefd66f538ab35263248a44217dcb70e2eb2cd136629e141b8b8f9f03b60908390a260408051600e547fe2faf044000000000000000000000000000000000000000000000000000000008252600160a060020a03858116600484015260248301859052604483018590526223988042016064840152925192169163e2faf04491608480820192602092909190829003018187876161da5a03f1156100025750506040515191506106069050565b6008850180546000908110156100025781815260208082209390935530600160a060020a031681526005909252604082205481549092908110156100025790815260208120905060020155601654600885018054600090811015610002579081526020812090506001015560048401805461ff0019166101001790555b6008840180546000908110156100025781548282526020822060010154929190811015610002579081526020812090505433600160a060020a031660009081526014602052604081205460088801805493909102939093049550908110156100025790815260208120905060030154604080517fbaac530000000000000000000000000000000000000000000000000000000000815233600160a060020a0390811660048301529151929091169163baac53009186916024808301926020929190829003018185886185025a03f11561000257505060405151600014159150611f78905057610002565b60088501805460009081101561000257818152602081206003018054600160a060020a03191690931790925580549091908110156100025790815260208120905060030154600160a060020a031660001415611f5757610002565b600d5430600160a060020a0316311015611f7057610002565b611d9561047f565b6008840180546000908110156100025781548282526020822060010154929190811015610002579081526020812090506002015433600160a060020a0390811660009081526014602090815260408083205430909416835260058083528184205460099093529083205460088b018054969095029690960497509487020494508593929091908290811015610002575260208120815060030154600160a060020a0390811682526020828101939093526040918201600090812080549095019094553016835260059091529020548290101561205357610002565b30600160a060020a031660009081526005602052604081208054849003905560088501805483926009929091829081101561000257508152602080822060030154600160a060020a039081168352929052604080822080549094019093553090911681522054819010156120c657610002565b30600160a060020a0390811660009081526009602090815260408083208054869003905533909316808352601482528383205484519081529351929390927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a36121383361086c565b5033600160a060020a03166000908152601460209081526040808320805460168054919091039055839055600a9091528120556001945061169e565b30600160a060020a0390811660008181526005602090815260408083208054958716808552828520805490970190965584845283905560099091528082208054948352908220805490940190935590815290555b50565b604051600160a060020a0382811691309091163190600081818185876185025a03f192505050151561217457610002565b33600160a060020a03818116600090815260096020908152604080832054815160065460085460e060020a63d2cc718f028352935197995091969195929092169363d2cc718f936004848101949193929183900301908290876161da5a03f11561000257505050604051805190602001506005600050600033600160a060020a03168152602001908152602001600020600050540204101561229d57610002565b600160a060020a03338116600090815260096020908152604080832054815160065460085460e060020a63d2cc718f02835293519296909593169363d2cc718f93600483810194929383900301908290876161da5a03f11561000257505050604051805190602001506005600050600033600160a060020a0316815260200190815260200160002060005054020403905083156123ec57600860009054906101000a9004600160a060020a0316600160a060020a0316630221038a83600160a060020a0316630e7082036040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63011081c5028252600160a060020a031660048201526024810186905290516044808301935060209282900301816000876161da5a03f115610002575050604051511515905061245457610002565b6040805160085460e160020a63011081c5028252600160a060020a038581166004840152602483018590529251921691630221038a9160448082019260209290919082900301816000876161da5a03f115610002575050604051511515905061245457610002565b600160a060020a03331660009081526009602052604090208054909101905550600192915050565b6109803361086c565b155b80156124a257506124a23384845b6000600061293a856105ec565b80156124be57506124be83836000600034111561261c57610002565b15610b9f5750600161033f565b15156124d657610002565b6124e08383610719565b905061033f565b155b80156124fb57506124fb848484612495565b80156125185750612518848484600060003411156126c157610002565b15610b9f57506001612528565b90505b9392505050565b151561253a57610002565b61252584848461041b565b30600160a060020a031633600160a060020a031614158061258a575030600160a060020a031660009081526005602052604090205460649061258561047f565b010481115b1561259457610002565b600c55565b600354600160a060020a0390811633909116146125b557610002565b600160a060020a038316600081815260046020908152604091829020805460ff191686179055815185815291517f73ad2a153c8b67991df9459024950b318a609782cee8c7eeda47b905f9baa91f9281900390910190a250600161033f565b506000610983565b33600160a060020a03166000908152601460205260409020548290108015906126455750600082115b156126b957600160a060020a03338116600081815260146020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a350600161033f565b50600061033f565b600160a060020a03841660009081526014602052604090205482901080159061270a5750601560209081526040600081812033600160a060020a03168252909252902054829010155b80156127165750600082115b156127a457600160a060020a03838116600081815260146020908152604080832080548801905588851680845281842080548990039055601583528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a3506001612528565b506000612528565b600160a060020a038381166000908152600a6020908152604080832054601654600754835160e060020a63d2cc718f02815293519296919591169363d2cc718f9360048181019492939183900301908290876161da5a03f11561000257505060405151905061281a866105ec565b0204101561282757610002565b600160a060020a038381166000908152600a6020908152604080832054601654600754835160e060020a63d2cc718f02815293519296919591169363d2cc718f9360048181019492939183900301908290876161da5a03f115610002575050604051519050612895866105ec565b0204039050600760009054906101000a9004600160a060020a0316600160a060020a0316630221038a84836040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511515905061291357610002565b600160a060020a0383166000908152600a6020526040902080548201905560019150610b99565b600160a060020a0386166000908152600a602052604090205480850291909104915081111561296857610002565b600160a060020a038581166000908152600a60205260408082208054859003905591861681522080548201905560019150509392505050565b506000610606565b0160030260166000505483020460016000505460166000505404019050610606565b600160a060020a0383166000908152600b6020526040812054815481101561000257818052600e02600080516020612a368339815191520190506003810154909150421115610b9457600160a060020a0383166000908152600b602052604081208190559150610b9956290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000b656b2a9c3b2416437a811e07466ca712f5a5b5a", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x000000000000000000000000ad3ecf23c0c8983b07163708be6d763b5f056193", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000f": "0x0000000000000000000000000000000000000000000000000000000057870858", + "0x0000000000000000000000000000000000000000000000000000000000000011": "0x0000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c18941301", + "0x0000000000000000000000000000000000000000000000000000000000000016": "0x00000000000000000000000000000000000000000003034f5ca7d45e17df199b", + "0x0421a2c4dbea98e8df669bb77238b62677daa210c5fbc46600627f90c03d0f08": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e571": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e572": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e573": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e574": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e575": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e576": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e577": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e578": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e579": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e57e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e57f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e580": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e581": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e582": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e583": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e584": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e585": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e586": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e587": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e58c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e58d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e58e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e58f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e590": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e591": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e592": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e593": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e594": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e595": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e59a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e59b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e59c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e59d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e59e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e59f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5a0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5a1": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5a2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5a3": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5a8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5a9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5aa": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5ab": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5ac": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5ad": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5ae": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5af": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5b0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5b1": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5b6": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5b7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5b8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5b9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5ba": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5bb": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5bc": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5bd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5be": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5bf": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5c4": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5c5": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5c6": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5c7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5c8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5c9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5ca": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5cb": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5cc": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5cd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5d2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5d3": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5d4": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5d5": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5d6": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5d7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5d8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5d9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5da": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5db": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e1": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e3": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e4": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e5": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e6": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5e9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5ee": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5ef": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5f0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5f1": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5f2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5f3": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5f4": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5f5": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5f6": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5f7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5fc": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x330b9432081afd3b64172d5df1f72ca72fc17e7e729ceb8b7529f91eee8b3f23": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x33f9bdb745e7edb1789dd1d68f40f693940aa8313b4f6bdc543be443dbc85e63": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4830270ad35536baba417a92ea24656430586a37c90999b53c4d72ef1090cc9d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4b16ba88f291613070529c10c8bdc41e973e2e2aa412ed92254cdca71ccfbc89": "0x00000000000000000000000000000000000000000001819451f999d617dafa76", + "0x6546a4760869a51d07a75a31f00531836c32152c06dc88ac342da52fac5d939e": "0x000000000000000000000000000000000000000000000026b8b4a0b1e8292492", + "0x6796d25b854f17a11b940a9ff2822335f7d9bd1b85fbcd9d1f5cf742099d477a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x711886c99bc7a6e316551823dca012bd5b4381b57cec388f72c4b8105c1ed4ad": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x74024021ec74dc59b0fa1b66e9f430163a5e1128785ec9495f9686628ca7cc2b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x79a0e9ff42282e7cbcb539e164f024ab90021633de05f600fff6d16305888d26": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x81ffe0a69ee20c37e3f3ba834da8b20475846fcde1f4a39fdfc628a2560076aa": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8f85b96a91f601f62149f5dd6a35d6168f6de5bc047a18e3cf7b97a3843c6ffd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x946f68a04a200ebe87f2f896f7f6c08f4e22813db910c8a6d6abf17611ce3ffb": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x9c1ad2f16775f94ffd360e8bc716f86016a3fcf90992b5b4f3312353efd1bd61": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa66ae63934365a757bf33d70ca0a28352da4c2fe6cb147bf29d69fbea3a706e0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa7653edcf1403f7ce2f75784d5f34ca5f57ff110bd0c3abbdcc5a84f101dc83a": "0x00000000000000000000000000000000000000000001819451f999d617dafa93", + "0xa87317e3ffd5ed16f357bd31427bd97cbb35fc51ad1e00feec89bdfe82c5dba4": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xaa535eb427f7963e49601f9032ee6b62a9f72b6b3c610a5f11faf8dc68a97e2a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xaade287f2b81ac58dcc6ee0c381cde85b6aa7a9a769be73605f1af9453a340a0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xb56a086d82c3921c13c13d1d53f18bbbe36d9d1c4862be8339a5171feb94c164": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xb6ab9f1541f42dc4feba15ccd18bc3af7c8f99cafb184ab65539883a68c7a1a9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xbad9e5f7dc3001078ea6433993a2f145c2ef9af1c5137a35e9c173c208990249": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xc319152db8781ef1f12090aad94325d650e39c8a20285c7e02959817118f3f28": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xde65b6d76ea4a5547af9707e6e099bba6f16dbc7b5cf97fb8fedc82583b38de0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xdf71c8506c3cf85e2e677b60ec28fe60eb820775001bdce289e3253f304f22e8": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x4fd27b205895e698fa350f7ea57cec8a21927fcd": { + "balance": "0x0", + "nonce": "11", + "code": "0x606060405236156100f05760e060020a600035046303afc23581146100f257806313af403514610114578063186ef962146101365780632e64cec11461015857806331962cdc146101d2578063365a86fc146101f45780634162169f146102065780634dfc3db61461021857806365f13792146102585780636637b88214610441578063715d832f146104625780637452c2e61461048457806386c9b536146105005780638da5cb5b14610512578063975057e714610524578063a8618f711461059f578063d0679d341461061b578063d1c3c84a14610698578063d9d35966146106aa578063f3273907146106c9575b005b6100f0600435600054600160a060020a03908116339091161461072e57610002565b6100f0600435600054600160a060020a03908116339091161461074457610002565b6100f0600435600054600160a060020a03908116339091161461075957610002565b6100f06000805481908190600160a060020a0390811633909116148015906101905750600154600160a060020a039081163390911614155b80156101ac5750600254600160a060020a039081163390911614155b80156101c85750600354600160a060020a039081163390911614155b1561076e57610002565b6100f0600435600054600160a060020a039081163390911614610a5d57610002565b6106eb600154600160a060020a031681565b6106eb600454600160a060020a031681565b61070860008054600160a060020a03908116339091161480159061024c5750600154600160a060020a039081163390911614155b15610a72575060015b90565b6107086004355b600060006000600060006000600460009054906101000a9004600160a060020a0316600160a060020a0316630e7082036040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150945084600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604080518051600480547f81f03fcb000000000000000000000000000000000000000000000000000000008452600160a060020a038d81169285019290925293519198509290921692506381f03fcb916024828101926020929190829003018187876161da5a03f115610002575050604080518051600480547f18160ddd0000000000000000000000000000000000000000000000000000000084529351919750600160a060020a039390931693506318160ddd92828101926020929190829003018187876161da5a03f1156100025750506040805180516004805460e060020a6370a08231028452600160a060020a038d81169285019290925293519196509290921692506370a08231916024828101926020929190829003018187876161da5a03f11561000257505060405151909402919091049695505050505050565b6100f060043560005433600160a060020a03908116911614610ad957610002565b6100f06004356000805433600160a060020a03908116911614610aff57610002565b61071a600435600080548190819033600160a060020a039081169116148015906104be5750600154600160a060020a039081163390911614155b80156104da5750600254600160a060020a039081163390911614155b80156104f65750600354600160a060020a039081163390911614155b15610be657610002565b6106eb600354600160a060020a031681565b6106eb600054600160a060020a031681565b6100f06000805481908190819033600160a060020a0390811691161480159061055d5750600154600160a060020a039081163390911614155b80156105795750600254600160a060020a039081163390911614155b80156105955750600354600160a060020a039081163390911614155b15610c4457610002565b61071a6004355b60006000600460009054906101000a9004600160a060020a0316600160a060020a03166381f03fcb846040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051519150610da490508361025f565b61071a60043560243560008054819033600160a060020a039081169116148015906106565750600154600160a060020a039081163390911614155b80156106725750600254600160a060020a039081163390911614155b801561068e5750600354600160a060020a039081163390911614155b15610dac57610002565b6106eb600254600160a060020a031681565b6100f06000805433600160a060020a03908116911614610ec457610002565b6106eb6004356000805433600160a060020a03908116911614610fd357610002565b60408051600160a060020a03929092168252519081900360200190f35b60408051918252519081900360200190f35b604080519115158252519081900360200190f35b60038054600160a060020a031916821790555b50565b60008054600160a060020a0319168217905550565b60028054600160a060020a0319168217905550565b30925061077a836105a6565b1561082557600480546006546040805160e060020a6370a08231028152600160a060020a038881169582019590955290519284169363a9059cbb9392169184916370a0823191602482810192602092919082900301816000876161da5a03f11561000257505060408051805160e060020a63a9059cbb02825260048201949094526024810193909352516044838101936020935082900301816000876161da5a03f115610002575050505b600091505b6005548210156108f45760058054600454600160a060020a0316916370a0823191859081101561000257600091825260408051600080516020611089833981519152929092015460e060020a6370a08231028352600160a060020a0316600483015251602482810193602093839003909101908290876161da5a03f115610002575050604051519150506000811115610a51576108f96005600050838154811015610002576000919091526000805160206110898339815191520154600160a060020a03166105a6565b505050565b15156109c25760058054600454600160a060020a0316916323b872dd918590811015610002575060009081526040805160008051602061108983398151915287015460e060020a6323b872dd028252600160a060020a03908116600483015230166024820152604481018690529051606482810193602093839003909101908290876161da5a03f1156100025750506040805183815290517f92da44f6982cd1ca7a9c851f8c39b26c80c235d7bb9fd59bce334fa634a1728b92509081900360200190a1610a51565b60058054600454600160a060020a0316916323b872dd918590811015610002575060009081526006546040805160008051602061108983398151915288015460e060020a6323b872dd028252600160a060020a0390811660048301529290921660248301526044820186905251606482810193602093839003909101908290876161da5a03f115610002575050505b6001919091019061082a565b60018054600160a060020a0319168217905550565b600454600160a060020a031660001415610a8e57506002610255565b600354600160a060020a031660001415610aaa57506003610255565b600254600160a060020a031660001415610ac657506004610255565b6005546000141561025557506005610255565b6005546000901115610aea57610002565b60048054600160a060020a0319168217905550565b5060005b81811015610b5a57600580546001818101808455930192909190828015829011610b5e576000839052610b5e906000805160206110898339815191529081019083015b80821115610bd65760008155600101610b46565b5050565b5050604051600454600160a060020a0316925090506082806110078339018082600160a060020a03168152602001915050604051809103906000f06005805460001981019081101561000257600091909152600080516020611089833981519152018054600160a060020a0319169091179055610b03565b5090565b600092505b5050919050565b600091505b600554821015610bda57600580548390811015610002576000919091526000805160206110898339815191520154600160a060020a0390811691508416811415610c385760019250610bdf565b60019190910190610beb565b600460009054906101000a9004600160a060020a0316600160a060020a03166370a08231306040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051519450506000841415610cbb575b50505050565b600554600093506004900460010191505b81831015610cb557506005805460045442850182900692600160a060020a03919091169163a9059cbb9190849081101561000257600091825260408051600080516020611089833981519152929092015460e060020a63a9059cbb028352600160a060020a03166004830152868904602483015251604482810193602093839003909101908290876161da5a03f11561000257505060408051848704815290517fc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b92509081900360200190a160019290920191610ccc565b901192915050565b600460009054906101000a9004600160a060020a0316600160a060020a03166370a08231306040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f1156100025750506040515191505082811015610e5f57604080516020810185905281517f703690365b2d63b5b9ec4471a919cdd5924f745170399a5d24927fd07d81a04d929181900390910190a1600091505b5092915050565b604080516004805460e060020a63a9059cbb028352600160a060020a038881169284019290925260248301879052925192169163a9059cbb9160448082019260209290919082900301816000876161da5a03f115610002575060019350610e58915050565b600480546006546040805160e060020a6370a08231028152600160a060020a0392831694810194909452519116916370a0823191602482810192602092919082900301816000876161da5a03f11561000257505060405151915050600081111561074157604080516004805460065460e060020a6323b872dd028452600160a060020a039081169284019290925230821660248401526044830185905292519216916323b872dd9160648181019260209290919082900301816000876161da5a03f1156100025750505060405180519060200150507f32e95f921f72e9e736ccad1cc1c0ef6e3c3c08204eb74e9ee4ae8f98e195e3f0816040518082815260200191505060405180910390a150565b600580548390811015610002576000919091526000805160206110898339815191520154600160a060020a03169291505056006060604052604051602080608283396080604081905291517f095ea7b3000000000000000000000000000000000000000000000000000000008352600160a060020a0333811660845260001960a4819052919384939184169163095ea7b39160c491906044816000876161da5a03f115600257505033600160a060020a03169050ff036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000003e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x000000000000000000000000304a554a310c7e546dfe434669c62820b7d83490", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db6": "0x0000000000000000000000007ccbc69292c7a6d7b538c91f3b283de97906cf30", + "0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db7": "0x0000000000000000000000001b9ec8ba24630b75a7a958153ffff56dd6d4b6a2", + "0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db8": "0x000000000000000000000000c3a2c744ad1f5253c736875b93bacce5b01b060b" + } + }, + "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f": { + "balance": "0x0", + "code": "0x606060405236156100da5760e060020a600035046303afc23581146100dc57806309f180f9146100fe5780630a39ce021461016c57806310aa1caa146101a057806313af40351461021e57806331962cdc14610240578063365a86fc146102625780634162169f146102745780634dfc3db6146102865780636637b882146102c65780636de45dee146102e85780638da5cb5b146103285780639137c1a71461033a578063b199efb51461035c578063b3a69f861461036e578063d5d7ff3c1461040b578063d95f98ce1461044b578063fe39084c146104b5575b005b6100da600435600054600160a060020a0390811633909116146104f657610002565b6104c76004355b6002546040805160e060020a6381f03fcb028152600160a060020a0384811660048301529151600093849384939116916381f03fcb91602481810192602092909190829003018187876161da5a03f1156100025750506040515192506105199050846101a7565b6104d960043560068054829081101561000257506000526000805160206110cf8339815191520154600160a060020a031681565b6104c76004355b604080516003547f65f13792000000000000000000000000000000000000000000000000000000008252600160a060020a038481166004840152925160009391909116916365f13792916024828101926020929190829003018187876161da5a03f115610002575050604051516001019392505050565b6100da600435600054600160a060020a03908116339091161461052c57610002565b6100da600435600054600160a060020a03908116339091161461054157610002565b6104d9600154600160a060020a031681565b6104d9600254600160a060020a031681565b6104c760008054600160a060020a0390811633909116148015906102ba5750600154600160a060020a039081163390911614155b15610556575060015b90565b6100da600435600054600160a060020a03908116339091161461063c57610002565b6100da600435600054600160a060020a03908116339091161480159061031e5750600154600160a060020a039081163390911614155b1561065157610002565b6104d9600054600160a060020a031681565b6100da600435600054600160a060020a03908116339091161461081b57610002565b6104d9600354600160a060020a031681565b6104c75b6000805b6006548110156108175760068054600254600160a060020a0316916370a08231918490811015610002576000918252604080516000805160206110cf833981519152929092015460e060020a6370a08231028352600160a060020a0316600483015251602482810193602093839003909101908290876161da5a03f11561000257505060405151929092019150600101610376565b6100da6004356000805433600160a060020a039081169116148015906104415750600154600160a060020a039081163390911614155b1561083057610002565b6100da60006000600060006000600060006000600060006000600060009054906101000a9004600160a060020a0316600160a060020a031633600160a060020a0316141580156104ab5750600154600160a060020a039081163390911614155b1561092d57610002565b6104d9600454600160a060020a031681565b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b60048054600160a060020a031916821790555b50565b81810392505b5050919050565b90508082111561050c5760009250610512565b60008054600160a060020a0319168217905550565b60018054600160a060020a0319168217905550565b600254600160a060020a031660001415610572575060026102c3565b600354600160a060020a03166000141561058e575060036102c3565b600354604080517fd1c3c84a000000000000000000000000000000000000000000000000000000008152905130600160a060020a0390811693169163d1c3c84a91600482810192602092919082900301816000876161da5a03f11561000257505060405151600160a060020a0316909114905061060d575060046102c3565b60065460001415610620575060056102c3565b600454600160a060020a0316600014156102c3575060066102c3565b60028054600160a060020a0319168217905550565b6106ab816000805b6006548110156110bc5782600160a060020a03166006600050828154811015610002576000919091526000805160206110cf8339815191520154600160a060020a031614156110c757600191506110c1565b156106b557610509565b600354604080517f7452c2e6000000000000000000000000000000000000000000000000000000008152600160a060020a03848116600483015291519290911691637452c2e69160248181019260209290919082900301816000876161da5a03f1156100025750506040515115905061072d57610509565b30600160a060020a031681600160a060020a0316148061075b5750600354600160a060020a03908116908216145b806107745750600154600160a060020a03908116908216145b1561077e57610509565b60068054600181018083559091908280158290116107bf578183600052602060002091820191016107bf91905b8082111561081757600081556001016107ab565b505060068054849350909150600019810190811015610002575080546000919091527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3e018054600160a060020a031916909117905550565b5090565b60038054600160a060020a0319168217905550565b5060005b6006548110156109215781600160a060020a03166006600050828154811015610002576000919091526000805160206110cf8339815191520154600160a060020a0316141561092557600680546000198101908110156100025760009182526000805160206110cf83398151915201909054906101000a9004600160a060020a03166006600050828154811015610002576000805160206110cf833981519152018054600160a060020a0319169092179091558054600019810180835590919082801582901161091c5761091c906000805160206110cf8339815191529081019083016107ab565b505050505b5050565b600101610834565b6002546040805160e060020a6381f03fcb02815230600160a060020a0381811660048401529251909e5092909116916381f03fcb9160248181019260209290919082900301816000876161da5a03f115610002575050604051519a505060008a14156109c1576040517f044c61dab36644651a1f82d87d6494a3a6450a6edde20b9baf45e374fb2d0bb990600090a1610e04565b6109c9610372565b6040805160025460e060020a6370a08231028252600160a060020a038f811660048401529251939c50909116916370a082319160248181019260209290919082900301816000876161da5a03f115610002575050604051519850606497505086881015610ade57604080516003547fd0679d34000000000000000000000000000000000000000000000000000000008252600160a060020a038e811660048401528b8b036024840152925192169163d0679d349160448082019260209290919082900301816000876161da5a03f1156100025750506040515115159050610ad8576040517f044c61dab36644651a1f82d87d6494a3a6450a6edde20b9baf45e374fb2d0bb990600090a1610e04565b86975087505b600095505b600654861015610b2457610d8e6006600050878154811015610002576000919091526000805160206110cf8339815191520154600160a060020a0316610105565b6040805160025460e060020a6381f03fcb028252600160a060020a038e8116600484015292519216916381f03fcb9160248181019260209290919082900301816000876161da5a03f11561000257505050604051805190602001509250600260009054906101000a9004600160a060020a0316600160a060020a03166370a082318c6040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f11561000257505060405151600097509250505b600654861015610e045760068054600254600160a060020a0316916370a082319189908110156100025760009182526000805160206110cf83398151915201546040805160e060020a6370a08231028152600160a060020a0392909216600483015251602482810193602093839003909101908290876161da5a03f115610002575050604051518084028b900495509150506000841115610d82577ff340c079d598119636d42046c6a2d2faf7a68c04aecee516f0e0b8a9e79b86666006600050878154811015610002576000919091526000805160206110cf833981519152015460408051600160a060020a03929092168252602082018790528386048c900482820152519081900360600190a160025460068054600160a060020a03929092169163a9059cbb919089908110156100025760009182526000805160206110cf83398151915201546040805160e060020a63a9059cbb028152600160a060020a039290921660048301526024820189905251604482810193602093839003909101908290876161da5a03f115610002575050505b60019590950194610bed565b9450898589020493508760001415610e11577fdb0f19c627ca59a2db73b1e1e8c4853f34a58afa92b29331e56c75144fa0c84c6006600050878154811015610002576000919091526000805160206110cf833981519152015460408051600160a060020a03929092168252519081900360200190a15b5050505050505050505050565b87841115610e84577f211d59fc569e166e12f7ca82135d85b1f178f636fefe40d168f0113cf07f818f6006600050878154811015610002576000919091526000805160206110cf833981519152015460408051600160a060020a03929092168252519081900360200190a1879350610ee8565b7f4b0bc4f25f8d0b92d2e12b686ba96cd75e4e69325e6cf7b1f3119d14eaf2cbdf6006600050878154811015610002576000919091526000805160206110cf833981519152015460408051600160a060020a03929092168252519081900360200190a15b60008411156110b05760068054998501997ff340c079d598119636d42046c6a2d2faf7a68c04aecee516f0e0b8a9e79b86669190889081101561000257600091909152604080516000805160206110cf8339815191529290920154600160a060020a0316825260208201879052818101889052519081900360600190a160025460068054600160a060020a03929092169163a9059cbb91908990811015610002576000918252604080516000805160206110cf833981519152929092015460e060020a63a9059cbb028352600160a060020a031660048301526024820189905251604482810193602093839003909101908290876161da5a03f1156100025750506040805160025460e060020a6370a08231028252600160a060020a038f811660048401529251921692506370a0823191602482810192602092919082900301816000876161da5a03f115610002575050506040518051906020015097508750600260009054906101000a9004600160a060020a0316600160a060020a03166381f03fcb8c6040518260e060020a0281526004018082600160a060020a031681526020019150506020604051808303816000876161da5a03f115610002575050604051519a50505b60019590950194610ae3565b600091505b50919050565b60010161065956f652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000003e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000304a554a310c7e546dfe434669c62820b7d83490", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f": "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526" + } + }, + "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc": { + "balance": "0x4563918244f400000", + "code": "0x606060405236156100da5760e060020a600035046313af40358114610145578063186ef9621461016757806331962cdc14610189578063365a86fc146101ab5780634162169f146101bd57806348c981e2146101cf5780634dfc3db61461020f57806361bc221a146102505780636637b882146102595780636c0e29601461027b5780638da5cb5b146104795780638f2b29a71461048b5780639137c1a714610602578063b199efb514610624578063b826c4fd14610636578063d1c3c84a1461063f578063d2f0ad9214610651578063fc340716146106bf575b6107236002546040805160e060020a630e7082030281529051600092600160a060020a031691630e708203916004828101926020929190829003018187876161da5a03f1156100025750506040515133600160a060020a039081169116149050610737575060015b90565b610861600435600054600160a060020a03908116339091161461089257610002565b610861600435600054600160a060020a0390811633909116146108a757610002565b610861600435600054600160a060020a0390811633909116146108bc57610002565b610863600154600160a060020a031681565b610863600254600160a060020a031681565b610861600435600054600160a060020a0390811633909116148015906102055750600154600160a060020a039081163390911614155b156108d157610002565b61088060008054600160a060020a0390811633909116148015906102435750600154600160a060020a039081163390911614155b156108fa57506001610142565b61088060055481565b610861600435600054600160a060020a0390811633909116146109e857610002565b6108805b6000600060006000600060006000600060006000600260009054906101000a9004600160a060020a0316600160a060020a0316630e7082036040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150985088600160a060020a03163130600160a060020a03163101975088600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160025460035460e060020a6381f03fcb028452600160a060020a0390811660048501529351919b5090921692506381f03fcb916024828101926020929190829003018187876161da5a03f11561000257505060408051805160025460e060020a6318160ddd0283529251909950600160a060020a039290921692506318160ddd916004828101926020929190829003018187876161da5a03f11561000257505060408051805160025460035460e060020a6370a08231028452600160a060020a039081166004850152935191995090921692506370a08231916024828101926020929190829003018187876161da5a03f115610002575050506040518051906020015093508784860202925088600160a060020a03163188880103840291508585029050808210156109fd57610a05565b610863600054600160a060020a031681565b61088060043560006000600060006000600260009054906101000a9004600160a060020a0316600160a060020a0316630e7082036040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750505060405180519060200150935083600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051600254815160035460e060020a6381f03fcb028452600160a060020a0390811660048501529351909750921692506381f03fcb916024808301926020929190829003018187876161da5a03f11561000257505060408051805160025460e060020a6318160ddd0283529251909550600160a060020a039290921692506318160ddd916004828101926020929190829003018187876161da5a03f115610002575050506040518051906020015090508581038183020486820387850204039450845084945050505050919050565b610861600435600054600160a060020a039081163390911614610a1157610002565b610863600354600160a060020a031681565b61088060065481565b610863600454600160a060020a031681565b6108806004355b6040805160025460035460e060020a6370a08231028352600160a060020a039081166004840152925160009384939216916370a08231916024828101926020929190829003018187876161da5a03f115610002575050604051519093046001019392505050565b61086160006000600060006000600060006000600060009054906101000a9004600160a060020a0316600160a060020a031633600160a060020a0316141580156107195750600154600160a060020a039081163390911614155b15610cbc57610002565b604080519115158252519081900360200190f35b600654600554600019909101901115610803576040805160025460035460e060020a6370a0823102835230600160a060020a03908116600485015293519184169363a9059cbb9391169160019185916370a082319160248082019260209290919082900301816000876161da5a03f11561000257505060408051805160e060020a63a9059cbb028252600482019590955260001994909401602485015251604480850194602094509192509082900301816000876161da5a03f115610002575060019250610142915050565b6005805460010190556040805160025460e160020a63664d71fb0282529151600160a060020a03929092169163cc9ae3f69160048181019260209290919082900301816000876161da5a03f115610002575060019250610142915050565b005b60408051600160a060020a03929092168252519081900360200190f35b60408051918252519081900360200190f35b60008054600160a060020a0319168217905550565b60048054600160a060020a0319168217905550565b60018054600160a060020a0319168217905550565b604051600160a060020a0382811691309091163190600081818185876185025a03f15050505050565b600254600160a060020a03166000141561091657506002610142565b600354600160a060020a03166000141561093257506003610142565b600454600160a060020a03166000141561094e57506004610142565b600354604080517f86c9b536000000000000000000000000000000000000000000000000000000008152905130600160a060020a039081169316916386c9b53691600482810192602092919082900301816000876161da5a03f11561000257505060405151600160a060020a031690911490506109cd57506005610142565b30600160a060020a0316316000141561014257506006610142565b60028054600160a060020a0319168217905550565b808203830499505b50505050505050505090565b60038054600160a060020a0319168217905550565b6006819055600354604080517fd0679d34000000000000000000000000000000000000000000000000000000008152600160a060020a038981166004830152938b04602482018190529151919750919092169163d0679d349160448181019260209290919082900301816000876161da5a03f11561000257505060408051600160055530600160a060020a031631815290517f7027eecbd2a688fc1fa281702b311ed7168571514adfd17014a55d828cb4338292509081900360200190a1604051600160a060020a0389811691309091163190600081818185876185025a03f15050604080517fd2cc718f000000000000000000000000000000000000000000000000000000008152905163d2cc718f9250600482810192602092919082900301816000876161da5a03f11561000257505060408051805160025460e060020a6370a08231028352600160a060020a038a81166004850152935191975090921692506370a0823191602482810192602092919082900301816000876161da5a03f11561000257505060408051805160025460e060020a6381f03fcb028352600160a060020a038a81166004850152935191965090921692506381f03fcb91602482810192602092919082900301816000876161da5a03f11561000257505060408051805160025460e160020a63664d71fb0283529251909450600160a060020a0392909216925063cc9ae3f691600482810192602092919082900301816000876161da5a03f115610002575050604080516002546004805460e060020a63a9059cbb028452600160a060020a03908116918401919091526001602484015292519216925063a9059cbb91604482810192602092919082900301816000876161da5a03f115610002575050505b5050505050505050565b6002546040805160e060020a630e7082030281529051600160a060020a0390921691630e7082039160048181019260209290919082900301816000876161da5a03f115610002575050604051519850610d15905061027f565b60408051600160a060020a038b1631815290519198507f07cf7e805770612a8b2ee8e0bcbba8aa908df5f85fbc4f9e2ef384cf75315038919081900360200190a187600160a060020a03163130600160a060020a0316310195508660001480610d7e5750856000145b15610db1576040517f30090d86c52e12fbc1213c1ecf7e193d6ce4a5c838c8c41d06c1a9daea8a2cec90600090a1610cb2565b309450610a268761065856", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000003e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000304a554a310c7e546dfe434669c62820b7d83490", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b": { + "balance": "0x0", + "code": "0x606060405236156100985760e060020a6000350463013cf08b811461009a57806313af4035146100d757806331962cdc146100f9578063365a86fc1461011b578063400e39491461012d5780634162169f146101375780634dfc3db6146101495780636637b8821461018a5780638da5cb5b146101ac578063e66f53b7146101be578063e90956cf146101d0578063ff2f4bd2146101f2575b005b61024460043560048054829081101561000257506000527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b015481565b610098600435600054600160a060020a03908116339091161461026157610002565b610098600435600054600160a060020a03908116339091161461027657610002565b61024e600154600160a060020a031681565b6102446004545b90565b61024e600254600160a060020a031681565b61024460008054600160a060020a03908116339091161480159061017d5750600154600160a060020a039081163390911614155b1561028b57506001610134565b610098600435600054600160a060020a0390811633909116146102c157610002565b61024e600054600160a060020a031681565b61024e600354600160a060020a031681565b610098600435600054600160a060020a0390811633909116146102d657610002565b6100986000606081815260a06040526080828152825491929091819033600160a060020a0390811691161480159061023a5750600154600160a060020a039081163390911614155b1561031857610002565b6060908152602090f35b600160a060020a03166060908152602090f35b60008054600160a060020a0319168217905550565b60018054600160a060020a0319168217905550565b600254600160a060020a03168114156102a657506002610134565b600354600160a060020a031681141561013457506003610134565b60028054600160a060020a0319168217905550565b60038054600160a060020a0319168217905550565b50508054839250600019810190811015610002579060005260206000209001600050819055505b50505050565b6002547f70a082310000000000000000000000000000000000000000000000000000000060a090815230600160a060020a0390811660a45291909116906370a082319060c49060209060248187876161da5a03f11561000257505060405151821415905061038557610312565b60006040518059106103945750595b9080825280602002602001820160405250935062093a809150600260009054906101000a9004600160a060020a0316600160a060020a031663612e45a3600360009054906101000a9004600160a060020a0316600086888760016040518760e060020a0281526004018087600160a060020a03168152602001868152602001806020018060200185815260200184151581526020018381038352878181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156104815780820380516001836020036101000a031916815260200191505b508381038252868181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156104da5780820380516001836020036101000a031916815260200191505b50985050505050505050506020604051808303816000876161da5a03f1156100025750506040515160048054600181018083559294509250908280158290116102eb578183600052602060002091820191016102eb91905b808211156105465760008155600101610532565b509056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000003e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000304a554a310c7e546dfe434669c62820b7d83490", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd1a0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd1a1": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd1a2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd1a3": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd1a4": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0xad3ecf23c0c8983b07163708be6d763b5f056193": { + "balance": "0x0", + "code": "0x606060405236156100405760e060020a60003504630221038a811461004d57806318bdc79a146100aa5780638da5cb5b146100be578063d2cc718f146100d0575b6100d96001805434019055565b6100db6004356024356000805433600160a060020a0390811691161415806100755750600034115b806100a05750805460a060020a900460ff1680156100a057508054600160a060020a03848116911614155b156100f757610002565b6100db60005460ff60a060020a9091041681565b6100ed600054600160a060020a031681565b6100db60015481565b005b60408051918252519081900360200190f35b6060908152602090f35b600160a060020a0383168260608381818185876185025a03f1925050501561015c57604080518381529051600160a060020a038516917f9735b0cb909f3d21d5c16bbcccd272d85fa11446f6d679f6ecb170d2dabfecfc919081900360200190a25060015b9291505056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000304a554a310c7e546dfe434669c62820b7d83490", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0xbe3ae5cb97c253dda67181c6e34e43f5c275e08b": { + "balance": "0x167d285b38143c04f", + "nonce": "68" + }, + "0xc0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89": { + "balance": "0x9651c71936", + "code": "0x606060405236156100b95760e060020a600035046313af4035811461019e57806326f5a8c9146101c1578063371fa854146101ca5780634162169f146101d35780634c8fe526146101e55780635970c915146101f757806361bc221a14610209578063625e847d146102125780636637b882146102325780637f9f519f146102555780638da5cb5b14610278578063a9059cbb1461028a578063c4463c80146102b0578063c9d27afe146102df578063e66f53b714610305575b6103176002547f0e708203000000000000000000000000000000000000000000000000000000006060908152600091600160a060020a031690630e7082039060649060209060048187876161da5a03f1156100025750506040515133600160a060020a039081169116149050610329576040805133600160a060020a03166020820152818152600f818301527f636f6e73747563746f72206661696c0000000000000000000000000000000000606082015290517fa6af7265d7ede5fbf0ee375956b52b362800d4f92e268809bef5fdf2a57924b89181900360800190a15060015b90565b61031760043560008054600160a060020a03908116339091161461049257610002565b61047560055481565b61047560045481565b61047f600254600160a060020a031681565b61047f600654600160a060020a031681565b61047f600754600160a060020a031681565b61047560035481565b61031760008054600160a060020a0390811633909116146104ef57610002565b61031760043560008054600160a060020a03908116339091161461057a57610002565b61031760043560008054600160a060020a0390811633909116146105d757610002565b61047f600054600160a060020a031681565b61031760043560243560008054600160a060020a03908116339091161461060f57610002565b61031760043560243560443560643560843560008054600160a060020a0390811633909116146106a657610002565b61031760043560243560008054600160a060020a0390811633909116146107bb57610002565b61047f600154600160a060020a031681565b60408051918252519081900360200190f35b60055460035460001990910190111561040257604080516002546006547f70a0823100000000000000000000000000000000000000000000000000000000835230600160a060020a03908116600485015293519184169363a9059cbb9391169184916370a0823191602480830192602092919082900301818a876161da5a03f11561000257505060408051805160e060020a63a9059cbb028252600482019490945260248101939093525160448084019360209350829003018187876161da5a03f11561000257505060016003819055915061019b9050565b6040805160038054600190810190915560025460048054925460e260020a632099877102855290840192909252600160a060020a03918216602484015292519216916382661dc491604480820192602092909190829003018187876161da5a03f11561000257506001925061019b915050565b6060908152602090f35b600160a060020a03166060908152602090f35b600160a060020a03821660609081527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc90602090a181600060006101000a815481600160a060020a0302191690830217905550600190505b919050565b6001600355600754600160a060020a03908116908290301631606082818181858883f15050604080516002546001546004805460e260020a632099877102855290840152600160a060020a0390811660248401529251921694506382661dc493506044808201935060209291829003018187876161da5a03f11561000257506001925061019b915050565b6002805473ffffffffffffffffffffffffffffffffffffffff1916831790819055600160a060020a031660609081527fce6a5015a40a2ec38ce912a63bca374d85386207c6927d284292449f1431082290602090a15060016104ea565b600582905560608281527fbab6859bc098da798dbdc4860f0fee7467d703dadd975799e8c258b46a37d3de90602090a15060016104ea565b60025460e060020a63a9059cbb026060908152600160a060020a0385811660645260848590529091169063a9059cbb9060a49060209060448187876161da5a03f11561000257505060408051600160a060020a03861681526020810185905281517f69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de293509081900390910190a15060015b92915050565b6006805473ffffffffffffffffffffffffffffffffffffffff1990811686179091556001600381905580548216871790556004879055600584905560078054909116831790819055600160a060020a03908116908290301631606082818181858883f15050604080516002546004805460015460e260020a632099877102855291840152600160a060020a0390811660248401529251921694506382661dc493506044808201935060209291829003018187876161da5a03f11561000257505060408051600454600654908252600160a060020a0316602082015281517fa1ab731770d71027cd294cc0af5c8f5ec3c2ff5dbe6b75d68963d17192f8377b93509081900390910190a150600195945050505050565b6002547fc9d27afe0000000000000000000000000000000000000000000000000000000060609081526064859052831515608452600160a060020a039091169063c9d27afe9060a49060209060448187876161da5a03f11561000257505060408051858152841515602082015281517f8bfa1f40665434b48e7becc865cc0586ce3d6d2388521c05d4db87536ac8279993509081900390910190a15060016106a056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000003e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000304a554a310c7e546dfe434669c62820b7d83490" + } + }, + "0xea674fdde714fd979de3edf0f56aa9716b898ec8": { + "balance": "0x4ab3566739e7b24371", + "nonce": "286339" + }, + "0xf835a0247b0063c04ef22006ebe57c5f11977cc4": { + "balance": "0x9645db5736", + "code": "0x606060405236156100b95760e060020a600035046313af4035811461019e57806326f5a8c9146101c1578063371fa854146101ca5780634162169f146101d35780634c8fe526146101e55780635970c915146101f757806361bc221a14610209578063625e847d146102125780636637b882146102325780637f9f519f146102555780638da5cb5b14610278578063a9059cbb1461028a578063c4463c80146102b0578063c9d27afe146102df578063e66f53b714610305575b6103176002547f0e708203000000000000000000000000000000000000000000000000000000006060908152600091600160a060020a031690630e7082039060649060209060048187876161da5a03f1156100025750506040515133600160a060020a039081169116149050610329576040805133600160a060020a03166020820152818152600f818301527f636f6e73747563746f72206661696c0000000000000000000000000000000000606082015290517fa6af7265d7ede5fbf0ee375956b52b362800d4f92e268809bef5fdf2a57924b89181900360800190a15060015b90565b61031760043560008054600160a060020a03908116339091161461049257610002565b61047560055481565b61047560045481565b61047f600254600160a060020a031681565b61047f600654600160a060020a031681565b61047f600754600160a060020a031681565b61047560035481565b61031760008054600160a060020a0390811633909116146104ef57610002565b61031760043560008054600160a060020a03908116339091161461057a57610002565b61031760043560008054600160a060020a0390811633909116146105d757610002565b61047f600054600160a060020a031681565b61031760043560243560008054600160a060020a03908116339091161461060f57610002565b61031760043560243560443560643560843560008054600160a060020a0390811633909116146106a657610002565b61031760043560243560008054600160a060020a0390811633909116146107bb57610002565b61047f600154600160a060020a031681565b60408051918252519081900360200190f35b60055460035460001990910190111561040257604080516002546006547f70a0823100000000000000000000000000000000000000000000000000000000835230600160a060020a03908116600485015293519184169363a9059cbb9391169184916370a0823191602480830192602092919082900301818a876161da5a03f11561000257505060408051805160e060020a63a9059cbb028252600482019490945260248101939093525160448084019360209350829003018187876161da5a03f11561000257505060016003819055915061019b9050565b6040805160038054600190810190915560025460048054925460e260020a632099877102855290840192909252600160a060020a03918216602484015292519216916382661dc491604480820192602092909190829003018187876161da5a03f11561000257506001925061019b915050565b6060908152602090f35b600160a060020a03166060908152602090f35b600160a060020a03821660609081527f3edd90e7770f06fafde38004653b33870066c33bfc923ff6102acd601f85dfbc90602090a181600060006101000a815481600160a060020a0302191690830217905550600190505b919050565b6001600355600754600160a060020a03908116908290301631606082818181858883f15050604080516002546001546004805460e260020a632099877102855290840152600160a060020a0390811660248401529251921694506382661dc493506044808201935060209291829003018187876161da5a03f11561000257506001925061019b915050565b6002805473ffffffffffffffffffffffffffffffffffffffff1916831790819055600160a060020a031660609081527fce6a5015a40a2ec38ce912a63bca374d85386207c6927d284292449f1431082290602090a15060016104ea565b600582905560608281527fbab6859bc098da798dbdc4860f0fee7467d703dadd975799e8c258b46a37d3de90602090a15060016104ea565b60025460e060020a63a9059cbb026060908152600160a060020a0385811660645260848590529091169063a9059cbb9060a49060209060448187876161da5a03f11561000257505060408051600160a060020a03861681526020810185905281517f69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de293509081900390910190a15060015b92915050565b6006805473ffffffffffffffffffffffffffffffffffffffff1990811686179091556001600381905580548216871790556004879055600584905560078054909116831790819055600160a060020a03908116908290301631606082818181858883f15050604080516002546004805460015460e260020a632099877102855291840152600160a060020a0390811660248401529251921694506382661dc493506044808201935060209291829003018187876161da5a03f11561000257505060408051600454600654908252600160a060020a0316602082015281517fa1ab731770d71027cd294cc0af5c8f5ec3c2ff5dbe6b75d68963d17192f8377b93509081900390910190a150600195945050505050565b6002547fc9d27afe0000000000000000000000000000000000000000000000000000000060609081526064859052831515608452600160a060020a039091169063c9d27afe9060a49060209060448187876161da5a03f11561000257505060408051858152841515602082015281517f8bfa1f40665434b48e7becc865cc0586ce3d6d2388521c05d4db87536ac8279993509081900390910190a15060016106a056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000003e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000304a554a310c7e546dfe434669c62820b7d83490" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "1881284", + "difficulty": "59917798852808", + "timestamp": "1468467296", + "gasLimit": "4712388", + "miner": "0xea674fdde714fd979de3edf0f56aa9716b898ec8" + }, + "input": "0xf869448505d21dba00833567e09403e3d4561a8f8e975fdcd798d32857a20cf25e7e8084be9a65551ba0d4dd5fff30e83fbe630bb0fd67eeefe9e3aad0c3ee870a2b6e80fc40191bc7d4a074f93b546bfad60f3cae8e4aafef835237095d6618334154a24df4b4d49d9359", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0xbe3ae5cb97c253dda67181c6e34e43f5c275e08b", + "gas": "0x3567e0", + "gasUsed": "0x26e1ef", + "to": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "input": "0xbe9a6555", + "calls": [ + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x34affa", + "gasUsed": "0x1ef", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x4b6753bc", + "output": "0x0000000000000000000000000000000000000000000000000000000057870858", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x34abef", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a08231000000000000000000000000c0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89", + "output": "0x00000000000000000000000000000000000000000001819451f999d617dafa93", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x34a705", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a08231000000000000000000000000c0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89", + "output": "0x00000000000000000000000000000000000000000001819451f999d617dafa93", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x34a31a", + "gasUsed": "0xa2f3", + "to": "0xc0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89", + "input": "0xa9059cbb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa93", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0xc0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89", + "gas": "0x343e8c", + "gasUsed": "0x9a62", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa93", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000c0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd" + ], + "data": "0x00000000000000000000000000000000000000000001819451f999d617dafa93", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0xc0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89", + "topics": [ + "0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2" + ], + "data": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa93", + "position": "0x1" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x33ff04", + "gasUsed": "0x168e", + "to": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "input": "0xa8618f710000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "calls": [ + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x339a3b", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x3395a4", + "gasUsed": "0x15f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x0e708203", + "output": "0x000000000000000000000000ad3ecf23c0c8983b07163708be6d763b5f056193", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x339363", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x339129", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x338cfa", + "gasUsed": "0x13f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x18160ddd", + "output": "0x00000000000000000000000000000000000000000003034f5ca7d45e17df199b", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x338a75", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x00000000000000000000000000000000000000000001819451f999d617dafa93", + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x33e6f2", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a08231000000000000000000000000f835a0247b0063c04ef22006ebe57c5f11977cc4", + "output": "0x00000000000000000000000000000000000000000001819451f999d617dafa76", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x33e208", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a08231000000000000000000000000f835a0247b0063c04ef22006ebe57c5f11977cc4", + "output": "0x00000000000000000000000000000000000000000001819451f999d617dafa76", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x33de20", + "gasUsed": "0x685b", + "to": "0xf835a0247b0063c04ef22006ebe57c5f11977cc4", + "input": "0xa9059cbb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa76", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0xf835a0247b0063c04ef22006ebe57c5f11977cc4", + "gas": "0x337992", + "gasUsed": "0x5fca", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa76", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000f835a0247b0063c04ef22006ebe57c5f11977cc4", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd" + ], + "data": "0x00000000000000000000000000000000000000000001819451f999d617dafa76", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0xf835a0247b0063c04ef22006ebe57c5f11977cc4", + "topics": [ + "0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2" + ], + "data": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa76", + "position": "0x1" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x3374a2", + "gasUsed": "0x168e", + "to": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "input": "0xa8618f710000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "calls": [ + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x330fd9", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x330b42", + "gasUsed": "0x15f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x0e708203", + "output": "0x000000000000000000000000ad3ecf23c0c8983b07163708be6d763b5f056193", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x330901", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x3306c7", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x330298", + "gasUsed": "0x13f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x18160ddd", + "output": "0x00000000000000000000000000000000000000000003034f5ca7d45e17df199b", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x330013", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x000000000000000000000000000000000000000000030328a3f333ac2fb5f509", + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x33490b", + "gasUsed": "0x3f781", + "to": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "input": "0xfc340716", + "calls": [ + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x32e30d", + "gasUsed": "0x15f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x0e708203", + "output": "0x000000000000000000000000ad3ecf23c0c8983b07163708be6d763b5f056193", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x32e037", + "gasUsed": "0x15f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x0e708203", + "output": "0x000000000000000000000000ad3ecf23c0c8983b07163708be6d763b5f056193", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x32dd7b", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x32daf9", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x32d6ab", + "gasUsed": "0x13f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x18160ddd", + "output": "0x00000000000000000000000000000000000000000003034f5ca7d45e17df199b", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x32d400", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x000000000000000000000000000000000000000000030328a3f333ac2fb5f509", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x32c975", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x000000000000000000000000000000000000000000030328a3f333ac2fb5f509", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x3276d3", + "gasUsed": "0xa49d", + "to": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "input": "0xd0679d340000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x320fe1", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x000000000000000000000000000000000000000000030328a3f333ac2fb5f509", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x320b5b", + "gasUsed": "0x9a62", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccd", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc" + ], + "data": "0x0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccd", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x3164e1", + "gasUsed": "0x4e91", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0x", + "value": "0x4563918244f400000", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x3115cc", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x000000000000000000000000000000000000000000000004563918244f400000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x311382", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", + "output": "0x0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccd", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x310f37", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x310ae9", + "gasUsed": "0x1446e", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xcc9ae3f6", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x30a397", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x000000000000000000000000000000000000000000000004563918244f400000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x309fc1", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x000000000000000000000000000000000000000000000004563918244f400000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x309c45", + "gasUsed": "0x122af", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0x0221038a0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc0000000000000000000000000000000000000000000000022b1c8c12279fffff", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "gas": "0x301e6f", + "gasUsed": "0x10068", + "to": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "input": "0x", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x2fbb97", + "gasUsed": "0x15f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x0e708203", + "output": "0x000000000000000000000000ad3ecf23c0c8983b07163708be6d763b5f056193", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x2fa477", + "gasUsed": "0xe7b6", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xcc9ae3f6", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x2f3d25", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x000000000000000000000000000000000000000000000004563918244f400000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x2f394f", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x000000000000000000000000000000000000000000000004563918244f400000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x2f35d3", + "gasUsed": "0x8b5f", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0x0221038a0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc0000000000000000000000000000000000000000000000022b1c8c12279fffff", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "gas": "0x2eb7fd", + "gasUsed": "0x6918", + "to": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "input": "0x", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x2e5525", + "gasUsed": "0x15f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x0e708203", + "output": "0x000000000000000000000000ad3ecf23c0c8983b07163708be6d763b5f056193", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x2e5168", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", + "output": "0x0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccd", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x2e4d69", + "gasUsed": "0x5fca", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccc", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd" + ], + "data": "0x0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccc", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x22b1c8c12279fffff", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "topics": [ + "0x9735b0cb909f3d21d5c16bbcccd272d85fa11446f6d679f6ecb170d2dabfecfc", + "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc" + ], + "data": "0x0000000000000000000000000000000000000000000000022b1c8c12279fffff", + "position": "0x1" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x22b1c8c12279fffff", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "topics": [ + "0x9735b0cb909f3d21d5c16bbcccd272d85fa11446f6d679f6ecb170d2dabfecfc", + "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc" + ], + "data": "0x0000000000000000000000000000000000000000000000022b1c8c12279fffff", + "position": "0x1" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "gas": "0x2fc505", + "gasUsed": "0xd4fa", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f0000000000000000000000000000000000000000000000000000000000000001", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", + "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "topics": [ + "0x07cf7e805770612a8b2ee8e0bcbba8aa908df5f85fbc4f9e2ef384cf75315038" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "position": "0x6" + }, + { + "address": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", + "topics": [ + "0x7027eecbd2a688fc1fa281702b311ed7168571514adfd17014a55d828cb43382" + ], + "data": "0x000000000000000000000000000000000000000000000004563918244f400000", + "position": "0x8" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x2f5092", + "gasUsed": "0x14e37", + "to": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "input": "0xd95f98ce", + "calls": [ + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2eea7c", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", + "output": "0x000000000000000000000000000000000000000000000004563918244f3ffffe", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2ee4cb", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a08231000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526", + "output": "0x000000000000000000000000000000000000000000000026b8b4a0b1e8292492", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2edfff", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2edb9a", + "gasUsed": "0x6994", + "to": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "input": "0xd0679d340000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f0000000000000000000000000000000000000000000000000000000000000063", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2e7519", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x000000000000000000000000000000000000000000030328a3f333ac2fb5f508", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2e7093", + "gasUsed": "0x5fca", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f0000000000000000000000000000000000000000000000000000000000000063", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000063", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2e6f59", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2e6afa", + "gasUsed": "0x1113", + "to": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "input": "0x65f13792000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526", + "output": "0x0000000000000000000000000000000000000000000000000037bc5737aa7ba8", + "calls": [ + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2e06f9", + "gasUsed": "0x15f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x0e708203", + "output": "0x000000000000000000000000ad3ecf23c0c8983b07163708be6d763b5f056193", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2e04b8", + "gasUsed": "0x113", + "to": "0xad3ecf23c0c8983b07163708be6d763b5f056193", + "input": "0xd2cc718f", + "output": "0x000000000000000000000000000000000000000000000004563918244f400000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2e027b", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2dfe4c", + "gasUsed": "0x13f", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x18160ddd", + "output": "0x00000000000000000000000000000000000000000003034f5ca7d45e17df199b", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2dfbc7", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a08231000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526", + "output": "0x000000000000000000000000000000000000000000000026b8b4a0b1e8292492", + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2e5281", + "gasUsed": "0x329", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x81f03fcb0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", + "output": "0x000000000000000000000000000000000000000000000004563918244f3ffffe", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2e4dcc", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", + "output": "0x0000000000000000000000000000000000000000000000000000000000000064", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2e4857", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a08231000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526", + "output": "0x000000000000000000000000000000000000000000000026b8b4a0b1e8292492", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "gas": "0x2e3bae", + "gasUsed": "0x9a62", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb000000000000000000000000da4a4626d3e16e094de3225a751aab7128e965260000000000000000000000000000000000000000000000000000000000000064", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", + "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "topics": [ + "0x4b0bc4f25f8d0b92d2e12b686ba96cd75e4e69325e6cf7b1f3119d14eaf2cbdf" + ], + "data": "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526", + "position": "0x6" + }, + { + "address": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", + "topics": [ + "0xf340c079d598119636d42046c6a2d2faf7a68c04aecee516f0e0b8a9e79b8666" + ], + "data": "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e9652600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000", + "position": "0x9" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x2e00dc", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x2dfc58", + "gasUsed": "0xa3bb", + "to": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "input": "0xd0679d340000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b0000000000000000000000000000000000000000000000000000000000000001", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2d9648", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x000000000000000000000000000000000000000000030328a3f333ac2fb5f4a5", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0x2d91c2", + "gasUsed": "0x9a62", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b0000000000000000000000000000000000000000000000000000000000000001", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x0000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x2d57a6", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x2d5515", + "gasUsed": "0x3478d", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x2ceffb", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x2ce86e", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x2a0c87", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x2a09f6", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x29a4dc", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x299d4f", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000002", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000002" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x26fc00", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000002", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x26f96f", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x269455", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x268cc8", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000003", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x23eb79", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000003", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x23e8e8", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x2383ce", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x237c41", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000004", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000004" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x20daf2", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000004", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x20d861", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x207347", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x206bba", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000005", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000005" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x1dca6b", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000005", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x1dc7da", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x1d62c0", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x1d5b33", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000006", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000006" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x1ab9e4", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000006", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x1ab753", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x1a5239", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x1a4aac", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000007", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x17a95d", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000007", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x17a6cc", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x1741b2", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x173a25", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000008", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000008" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x1498d6", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000008", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x149645", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x14312b", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x14299e", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000009", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x0000000000000000000000000000000000000000000000000000000000000009" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x11884f", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x0000000000000000000000000000000000000000000000000000000000000009", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0x1185be", + "gasUsed": "0x30cf5", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0xff2f4bd2", + "calls": [ + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x1120a4", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "gas": "0x111917", + "gasUsed": "0x29e8d", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x612e45a3000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000093a80000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "output": "0x000000000000000000000000000000000000000000000000000000000000000a", + "calls": [ + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x304a554a310c7e546dfe434669c62820b7d83490", + "gas": "0x3", + "gasUsed": "0x3", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x", + "error": "out of gas", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", + "0x000000000000000000000000000000000000000000000000000000000000000a" + ], + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0xe77c8", + "gasUsed": "0x112", + "to": "0x7498bb5749c9801f1f7e490baf5f966dbfe4e97b", + "input": "0x400e3949", + "output": "0x000000000000000000000000000000000000000000000000000000000000000a", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x03e3d4561a8f8e975fdcd798d32857a20cf25e7e", + "gas": "0xe7537", + "gasUsed": "0x1eafd", + "to": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "input": "0x975057e7", + "calls": [ + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0xe0f53", + "gasUsed": "0x314", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0x70a082310000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "output": "0x000000000000000000000000000000000000000000030328a3f333ac2fb5f4a4", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0xe096d", + "gasUsed": "0x9a62", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000007ccbc69292c7a6d7b538c91f3b283de97906cf3000000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x0000000000000000000000007ccbc69292c7a6d7b538c91f3b283de97906cf30" + ], + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0xd6871", + "gasUsed": "0x9a62", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb0000000000000000000000001b9ec8ba24630b75a7a958153ffff56dd6d4b6a200000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x0000000000000000000000001b9ec8ba24630b75a7a958153ffff56dd6d4b6a2" + ], + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "gas": "0xcc775", + "gasUsed": "0x9a62", + "to": "0x304a554a310c7e546dfe434669c62820b7d83490", + "input": "0xa9059cbb000000000000000000000000c3a2c744ad1f5253c736875b93bacce5b01b060b00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x304a554a310c7e546dfe434669c62820b7d83490", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", + "0x000000000000000000000000c3a2c744ad1f5253c736875b93bacce5b01b060b" + ], + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "topics": [ + "0xc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b" + ], + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x2" + }, + { + "address": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "topics": [ + "0xc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b" + ], + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x3" + }, + { + "address": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", + "topics": [ + "0xc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b" + ], + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x4" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json new file mode 100644 index 0000000..66d4582 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json @@ -0,0 +1,580 @@ +{ + "genesis": { + "difficulty": "7507253814130", + "extraData": "0xd783010400844765746887676f312e352e31856c696e7578", + "gasLimit": "3141592", + "hash": "0x3d9d19618f67bbb7708403fe9bda131fbade0449d2ac12bf3b140b4269112826", + "miner": "0x63a9975ba31b0b9626b34300f7f627147df1f526", + "mixHash": "0x50aaa8973eadd4bbfc7f5b59d5be52f6a1be2d38f40b5a0786a24b90257520da", + "nonce": "0x3547956c62c256b9", + "number": "595531", + "stateRoot": "0x79d00dd270bffc48d89fa55842f63f840981121378da8c6de4d479535f25ed6a", + "timestamp": "1448471472", + "totalDifficulty": "3448100174991667199", + "alloc": { + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x44dc051cccdfd2e132", + "nonce": "39602" + }, + "0x350e0ffc780a6a75b44cc52e1ff9092870668945": { + "balance": "0xe37111b7c79406c0", + "code": "0x606060405236156100f05760e060020a60003504631ff6c70581146100f257806347980c0d146100fd57806353ba9c2f146101085780635ea8cd12146101c957806369d640fd146101f05780637ce3489b146102405780637d1bb97a1461026b5780637fd6f15c146103e55780638bf50628146103f057806390a248f814610411578063a8f37bb214610438578063b019e0171461046a578063b4c70cea1461059b578063cf955f34146106a1578063d229b54b146106bd578063d54b4a04146106e4578063e021fadb146106f1578063e45be8eb14610858578063eddfa7c814610863578063f2a75fe41461095d575b005b6109a5621e84845481565b6109a5621e84865481565b6109b76004356024356000808080806003876103e881101561000257506107d0880201866103e8811015610002579090600202016000505461ffff168152602081019190915260400160002054600160a060020a031692506003856103e881101561000257506107d0860201846103e88110156100025790906002020160005054620100009004600390810b9250856103e881101561000257506107d0860201846103e8811015610002579090600202016000506001015490509250925092565b6100f0600435621e848354600160a060020a0390811633909116141561026857621e848755565b6109e36004356024356003826103e881101561000257506107d0830201816103e88110156100025790906002020160005080546001919091015461ffff821693506201000090910460030b915083565b6100f0600435621e848354600160a060020a0390811633909116141561026857621e84858190555b50565b610a0a600435617d00604051908101604052806103e8905b600081526020019060019003908161028357505060408051617d0081019091526103e8815b60008152602001906001900390816102a857505060408051617d0081019091526103e8815b60008152602001906001900390816102cd5750600090505b6103e861ffff82161015610d09576000806003836103e8811015610002576107d002018150876103e881101561000257600202016000505461ffff168152602081019190915260400160002054600160a060020a031684826103e8811015610002575050602082028501526003816103e8811015610002576107d00201600050856103e8811015610002579090600202016000505462010000900460030b83826103e8811015610002575050600390810b60208302850152816103e8811015610002576107d00201600050856103e8811015610002579090600202016000506001015482826103e8811015610002575050602082028301526001016102e5565b6109a5621e84855481565b610a59600435600060208190529081526040902054600160a060020a031681565b6100f0600435621e848354600160a060020a0390811633909116141561026857621e848655565b6100f060043560243560443560643560843560a435610a8e868684866101000288620100000286607f02010101610870565b604080516004803580820135602081810280860182019096528185526100f09593946024949093850192918291908501908490808284375050604080518735808a013560208181028085018201909552818452989a996044999398509190910195509350839250850190849080828437505060408051606435808a013560208181028085018201909552818452989a9935999860849850929650602491909101945092508291908501908490808284375050604080519635808901356020818102808b018201909452818a5297999860a4989097506024929092019550935083925085019084908082843750949650505050505050621e848354600090600160a060020a03908116339091161415610a8e575b8551811015610a8e57606060405190810160405280610d1186610ebc565b60408051602060248035600481810135601f81018590048502860185019096528585526109a5958135959194604494929390920191819084018382808284375094965050933593505050505b6000831515610699577ffd33e90d0eac940755277aa91045b95664988beeeafc4ed7d1281a6d83afbc003384846040518084600160a060020a03168152602001806020018381526020018281038252848181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156106895780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a15b509192915050565b610a7660043560016020526000908152604090205461ffff1681565b6100f0600435621e848354600160a060020a0390811633909116141561026857621e848455565b610a7660025461ffff1681565b604080516004803580820135602081810280860182019096528185526109a59593946024949093850192918291908501908490808284375050604080518735808a013560208181028085018201909552818452989a9960449993985091909101955093508392508501908490808284375050604080519635808901356020818102808b018201909452818a529799986064989097506024929092019550935083925085019084908082843750506040805196358089013560208181028a81018201909452818a5297999860849890975060249290920195509350839250850190849080828437509496505050505050506000600060006000610ad68751895114606060405190810160405280602381526020017f446966666572656e74206e756d626572206f66207856616c732061732079566181526020017f6c732e00000000000000000000000000000000000000000000000000000000008152602001508a516105e7565b6109a5621e84875481565b6100f06004356024356044355b6000610a96848484345b6000808080808060038a6103e8811015610002576107d00201896103e88110156100025760020201805461ffff16825260208290526040822054621e8484546001830154621e848654939850600160a060020a03928316975060649181028290049650929092029190910492503316841415610f4d57610fbc82341015606060405190810160405280602e81526020017f4368616e67696e6720796f7572206f776e20706978656c20636f73747320313081526020017f25206f66206974732076616c7565000000000000000000000000000000000000815260200150846105e7565b6100f0621e848354600160a060020a039081163390911614156109a357604051621e848354600160a060020a03908116916000913016319082818181858883f150505050505b565b60408051918252519081900360200190f35b60408051600160a060020a0394909416845260039290920b602084015282820152519081900360600190f35b6040805161ffff94909416845260039290920b602084015282820152519081900360600190f35b6040518084617d008083818460006004610bc7f150918201918591508083818460006004610bc7f15061fa00840192508491508083818460006004610bc7f15062017700965092945050505050f35b60408051600160a060020a03929092168252519081900360200190f35b6040805161ffff929092168252519081900360200190f35b505050505050565b90506000811115610ac25760405133600160a060020a031690600090839082818181858883f150505050505b50505050565b93505b505050949350505050565b1580610b4e5750610b4c8651895114606060405190810160405280602481526020017f446966666572656e74206e756d626572206f66207856616c7320617320636f6c81526020017f6f72732e000000000000000000000000000000000000000000000000000000008152602001508a516105e7565b155b80610bc55750610bc38551895114606060405190810160405280602481526020017f446966666572656e74206e756d626572206f66207856616c732061732070726981526020017f6365732e000000000000000000000000000000000000000000000000000000008152602001508a516105e7565b155b15610bd35760009350610acb565b5034915060009050805b8751811015610c63578481815181101561000257602090810290910101519092039160008310610d0157610cfb88828151811015610002579060200190602002015188838151811015610002579060200190602002015188848151811015610002579060200190602002015188858151811015610002579060200190602002015161087a565b6000821115610c8d5760405133600160a060020a031690600090849082818181858883f150505050505b610ac86000841015606060405190810160405280602181526020017f56616c756520776173206c657373207468616e2073756d206f6620707269636581526020017f7300000000000000000000000000000000000000000000000000000000000000815260200150856105e7565b91909101905b600101610bdd565b509193909250565b8152602001848381518110156100025790602001906020020151815260200183838151811015610002579060200190602002015181526020015060036000508783815181101561000257906020019060200201516103e8811015610002576107d002016000508683815181101561000257906020019060200201516103e8811015610002576002020160005081518154602084015160e060020a90810204620100000261ffff199190911690911765ffffffff00001916178155604091909101516001919091015560010161057d565b8454600061ffff919091161115610e225750604051621e84855460649088020490600160a060020a03851690600090838a039082818181858883f150505050505b845460018601546040805161ffff8e811682528d166020820152600160a060020a038881168284015262010000909404600390810b810b606083015260808201939093523390931660a0840152908a900b60c083015260e08201899052517fcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42918190036101000190a1606060405190810160405280611143335b600160a060020a03811660009081526001602052604081205461ffff1690811415610f485750604060008181206002805461ffff1981811661ffff928316600190810191821790945591821685526020858152958520805473ffffffffffffffffffffffffffffffffffffffff191688179055600160a060020a03871690945293528054909116821790555b919050565b60408051621e848754606082018352602182527f4d696e696d756d20706978656c2070726963652069732035302066696e6e657960208301527f2e00000000000000000000000000000000000000000000000000000000000000928201929092526110c29134101590896105e7565b1515610fca578695506110b5565b33600160a060020a031684600160a060020a03161415610de157604080518654600188015461ffff8e811684528d166020840152600160a060020a03881683850181905262010000909204600390810b810b60608501526080840182905260a0840192909252908b900b60c083015260e082015290517fcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42918190036101000190a18760038b6103e8811015610002576107d002018a6103e88110156100025760020201805465ffffffff0000191660e060020a92830292909204620100000291909117905581870395505b5050505050949350505050565b15806111365750610fbc83341015606060405190810160405280603281526020017f56616c7565206d7573742062652031302520686967686572207468616e20637581526020017f7272656e7420706978656c2070726963652e0000000000000000000000000000815260200150856105e7565b15610fca578695506110b5565b8152602081018a905260400188905260038b6103e8811015610002576107d002018a6103e8811015610002576002020160005081518154602084015160e060020a90810204620100000261ffff199190911690911765ffffffff000019161781556040919091015160019190910155600095506110b556", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000175901": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000175902": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000175903": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000175904": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760c7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760c8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760c9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760ca": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760cb": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760cc": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760cd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760ce": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760cf": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d1": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d3": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d4": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d5": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d6": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760d9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760da": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760db": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760dc": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760dd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760de": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760df": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001760e0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000176897": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000176898": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000176899": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000017689a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000017689b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000017689c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000017689d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000017689e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000017689f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768a0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768a7": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768a8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768a9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768aa": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768ab": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768ac": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768ad": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768ae": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768af": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001768b0": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c37": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c38": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c39": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c3a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c3b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c3c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c3d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c3e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c3f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c40": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c45": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c46": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c47": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c48": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c49": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c4a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c4b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c4c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c4d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c4e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c4f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000196c50": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197407": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197408": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197409": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019740a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019740b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019740c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019740d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019740e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019740f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197410": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197411": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197412": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197413": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197414": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197415": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197416": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197417": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197418": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197419": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019741a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019741b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019741c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019741d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019741e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000019741f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197420": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197be3": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000197be4": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000001e8484": "0x000000000000000000000000000000000000000000000000000000000000006e", + "0x00000000000000000000000000000000000000000000000000000000001e8486": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00000000000000000000000000000000000000000000000000000000001e8487": "0x0000000000000000000000000000000000000000000000000011c37937e08000", + "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe1723559c995b1804c0512df6fe6d061eeb47aff37a3ced3b93f0c1bef247540": "0x0000000000000000000000000000000000000000000000000000000000000007" + } + }, + "0x3fcb0342353c541e210013aaddc2e740b9a33d08": { + "balance": "0x6a0e4be198f18400", + "nonce": "17" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "595532", + "difficulty": "7503588162862", + "timestamp": "1448471495", + "gasLimit": "3141592", + "miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226" + }, + "input": "0xf91a7311850ba43b7400832dc6c094350e0ffc780a6a75b44cc52e1ff90928706689458803782dace9d90000b91a04e021fadb000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000006e00000000000000000000000000000000000000000000000000000000000000d4000000000000000000000000000000000000000000000000000000000000013a00000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fd000000000000000000000000000000000000000000000000000000000000034300000000000000000000000000000000000000000000000000000000000002fd0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003900000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003b00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003d00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003700000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003900000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003b00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003d00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000032fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffebebebffffffffffffffffffffffffffffffffffffffffffffffffffffffffff888888ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb3b3b3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3e3e3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e3e3effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbdbdbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f4f4fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0b0b0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a0a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5b5bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaeaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa9a9a9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb9b9b9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababaffffffffffffffffffffffffffffffffffffffffffffffffffffffffff636363fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9f9f9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaeaffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c9c9cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8f8f8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4d4e53ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f494b00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080001ca0e8a879dd98a39d735b866ff64d84e9c144a17bcab106cf2f1327b1272db06aaca02ab279a2459b5e30dfea0bc8a888c7d2a190740090352b4a7aded30c45490af9", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0x3fcb0342353c541e210013aaddc2e740b9a33d08", + "gas": "0x2dc6c0", + "gasUsed": "0x2570bf", + "to": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "input": "0xe021fadb000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000006e00000000000000000000000000000000000000000000000000000000000000d4000000000000000000000000000000000000000000000000000000000000013a00000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000002fd000000000000000000000000000000000000000000000000000000000000034300000000000000000000000000000000000000000000000000000000000002fd0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003900000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003b00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003d00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003700000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003900000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003b00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003d00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000032fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffebebebffffffffffffffffffffffffffffffffffffffffffffffffffffffffff888888ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb3b3b3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3e3e3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e3e3effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbdbdbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f4f4fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0b0b0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a0a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5b5bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaeaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa9a9a9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb9b9b9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababaffffffffffffffffffffffffffffffffffffffffffffffffffffffffff636363fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9f9f9ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaeaffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c9c9cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8f8f8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfcfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4d4e53ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f494b00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000000000000000000000000000011c37937e08000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logs": [ + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffebebeb0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8888880000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb3b3b30000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfc0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3e3e30000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e3e3e0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbdbdb0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f4f40000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfb0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0b0b00000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a0a00000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5b5b0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababa0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaea0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa9a9a90000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb9b9b90000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfb0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababa0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6363630000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9f9f90000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaea0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c9c9c0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8f8f80000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfc0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fd00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4d4e530000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000034300000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + }, + { + "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", + "topics": [ + "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000002fd00000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f494b0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" + } + ], + "value": "0x3782dace9d90000", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json new file mode 100644 index 0000000..762ccbe --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json @@ -0,0 +1,288 @@ +{ + "genesis": { + "difficulty": "45944156141275", + "extraData": "0xd783010406844765746887676f312e342e32856c696e7578", + "gasLimit": "4714680", + "hash": "0x3c41811ab60f232565db6cfafb939d96255b9f678a203181c6f537d6c22d7e6f", + "miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5", + "mixHash": "0x8b736c63e05d381ae593d584b63fef5c31b04a3cea72bd5a3c92f95f4f7040e8", + "nonce": "0xce8ffb5c1ad942ec", + "number": "1725115", + "stateRoot": "0xca08a341c1f95fcba0821c4a27662ef162d39e1f9f5722717531f510d54112b0", + "timestamp": "1466232982", + "totalDifficulty": "28554024908214037524", + "alloc": { + "0x0000000000000000000000000000000000000004": { + "balance": "0x0" + }, + "0x1d3b2638a7cc9f2cb3d298a3da7a90b67e5506ed": { + "balance": "0x0", + "code": "0x606060405260e060020a600035046338cc483181146038578063767800de14604f578063a6f9dae1146060578063d1d80fdf14607e575b005b600054600160a060020a03165b6060908152602090f35b6045600054600160a060020a031681565b603660043560015433600160a060020a03908116911614609c576002565b603660043560015433600160a060020a0390811691161460be576002565b6001805473ffffffffffffffffffffffffffffffffffffffff19168217905550565b6000805473ffffffffffffffffffffffffffffffffffffffff1916821790555056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000088e1315687aec48a72786c6b3b3f075208b62713" + } + }, + "0x50739060a2c32dc076e507ae1a893aab28ecfe68": { + "balance": "0x6a8ecefb09f7c4141", + "code": "0x606060405236156101745760e060020a6000350463058aace1811461017f578063061e494f146101905780630d1fce421461021e57806311610c251461029157806312253a6c146102b5578063132ae5e9146102d357806316d190e3146102dc57806329e206bd146102e5578063337b68ba1461030a57806338bbfa50146103135780633f683b6a146104115780634dc6b523146104245780634e69d5601461042d57806366d16cc31461044a578063724ae9d014610453578063758971e81461046f5780637cf0ffcb146104965780638ca17995146104a35780639619367d146104b7578063a96a5a5b146104c0578063adc2c98a146104c9578063b70d0b3b146104d2578063bc99cc37146104db578063c4bc5da5146104e4578063cafb220214610502578063d28442ef1461050b578063d4c80edf14610514578063df06f9061461051d578063e8b5e51f14610527578063f738e5ca14610546578063f8b2cb4f14610553578063fa968eea14610594575b610661610663610295565b6106616000341115610eab57610002565b61066560043560006000600060006000600f6000508054905086101561021657600f8054879081101561000257505050507f8d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac80284015490819052600e602052604090912080546001820154600283015460039390930154600160a060020a03929092169450925b509193509193565b6106965b601254601354601154600c5460009391019091010330600160a060020a0316318190101561028957604080517f62616e6b726f6c6c5f6d69736d61746368000000000000000000000000000000815290519081900360110190a05030600160a060020a0316315b8091505b5090565b6106615b600060006000600d60149054906101000a900460ff16156106ef57610002565b610661600d5433600160a060020a03908116911614610fd657610002565b610696600a5481565b61069660045481565b6106616004355b600d5460009033600160a060020a0390811691161461101c57610002565b61069660125481565b60408051602060248035600481810135601f81018590048502860185019096528585526106619581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a0190935282825296989760649791965060249190910194509092508291508401838280828437509496505050505050506000600060006000610a18600080546040805160e060020a6338cc483102815290518392600160a060020a0316916338cc4831916004828101926020929190829003018187876161da5a03f11561000257505060405151915050600160a060020a0381168214156114635761140b6000610939565b610696600d5460a060020a900460ff1681565b61069660085481565b6106a8600060006000600060006000600060006000610f8d610222565b61069660115481565b6106965b600a54600654600091829182911015610ef857610f33565b6106616004355b600d54600090819033600160a060020a0390811691161461108657610002565b61066161066360006102ec565b6106616004356000341115610e7957610002565b61069660055481565b61069660025481565b61069660035481565b61069660075481565b61069660065481565b610661600d5433600160a060020a03908116911614610ffc57610002565b610696600c5481565b61069660135481565b61069660105481565b610696600f545b90565b610661600d54600090819060a060020a900460ff1615610c8057610002565b6106616106636000610476565b6106966004355b600160a060020a0381166000908152600b602052604081205481901180156105845750600c548190115b15610ebd57600c54610ec6610222565b610696600080546040805160e060020a6338cc483102815290518392600160a060020a0316916338cc4831916004828101926020929190829003018187876161da5a03f11561000257505060408051805160e260020a630bbceb33028252620249f06024830152600482018390526003604483015260ea60020a621554930260648301529151600160a060020a03929092169250632ef3accc916084828101926020929190829003018187876161da5a03f1156100025750506040515160055481019350915061028d9050565b005b565b60408051600160a060020a039590951685526020850193909352838301919091526060830152519081900360800190f35b60408051918252519081900360200190f35b60408051998a5260208a0198909852888801969096526060880194909452608087019290925260a086015260c085015260e084015261010083015251908190036101200190f35b600060009054906101000a9004600160a060020a0316600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e260020a630bbceb33028252620249f06024830152600482018390526003604483015260ea60020a621554930260648301529151600160a060020a03929092169250632ef3accc91608480830192602092919082900301816000876161da5a03f1156100025750506040515193505034839010156107c257610002565b82340391506127106107d2610222565b600460005054020460026000505460026000505460036000505461271003038402041115801561080457506005548210155b1561095a576040805180820182526003815260ea60020a62155493026020828101919091528251608081018452604381527f6a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e818301527f2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174818501527f612e30000000000000000000000000000000000000000000000000000000000060608201528351610160810190945261012c80855261095f94919261175690830139620249f0600060006000600060009054906101000a9004600160a060020a0316600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151915050600160a060020a03811682141561118c5761113460005b600060006115ef731d3b2638a7cc9f2cb3d298a3da7a90b67e5506ed5b3b90565b610002565b6040805160808101825233815260208181018681526000838501818152606085018a8152878352600e90945293519490208054600160a060020a031916909417845551600184810191909155915160028401555160039290920191909155600f8054918201808255929350918281838015829011610a0057818360005260206000209182019101610a0091905b8082111561028d57600081556001016109ec565b5050506000928352506020909120018190555b505050565b600160a060020a031633600160a060020a0316141515610a3757610002565b6000878152600e6020526040812060018101549095501115610c5257600d5460a060020a900460ff166000148015610aa15750612710610a75610222565b600460005054020460026000505460026000505460036000505461271003038660010160005054020411155b15610b4957610b968660006114e28260006040805160208101909152600090819052828180805b8351811015610b3e57603060f860020a02848281518110156100025790602001015160f860020a900460f860020a0210158015610b295750603960f860020a02848281518110156100025790602001015160f860020a900460f860020a0211155b156116cb578115611722578560001415611719575b509095945050505050565b60018401548454610c5291600160a060020a0391909116905b604051600160a060020a038316906161a89083906000818181858888f193505050501515610d005760138054820190555050565b92506001831080610ba8575061271083115b15610bc75783546001850154610c5291600160a060020a031690610b62565b6000878152600e6020526040902060029081018490555460001984011015610c5b57506002546003546001850154855461271092909203029190910490610c7190600160a060020a031682610b62565b60018401546000190191505b601380546007546127109085020590810190915560118054918403909101905560018401546010805490910190555b50505050505050565b8354610c1790600160a060020a03166001610b62565b60018401548190039150610c23565b33600160a060020a03166000908152600b60205260408120541115610ca757610cc5610cab565b610d045b6011546012546000918291829114610a13576114e9610222565b33600160a060020a03166000908152600b6020908152604080832054835260099091529020600101805434908101909155600c805490910190555b5050565b600a5460065460009350901015610d6557600a80546001019081905591505b600082111561095a576000828152600960205260408120600101541115610deb576040600020805460019190910154610dc591600160a060020a031690610e7f565b5060015b600a548111610d23576000818152600960205260409020600101543490108015610db457508160001480610db457506040600081812060019081015485835292822001549083905290105b15610dbd579050805b600101610d69565b600082815260096020908152604080832054600160a060020a03168352600b9091528120555b600082815260096020526040812060010154148015610e2357506040600081812054600160a060020a03168152600b60205290812054145b1561095a5760008281526009602090815260408083208054600160a060020a03191633908117825534600192909201829055600c8054909201909155600160a060020a03168352600b9091529020829055610d00565b610ea833825b600160a060020a0382166000908152600b602052604081205481901115610a1357611578610cab565b50565b61066333610eb83361055a565b610e7f565b5060005b919050565b600160a060020a0384166000908152600b60209081526040808320548352600990915290206001015402049050610ec1565b5060015b600a548111610f38578160001480610f5b5750600082815260096020526040902054610f6c90600160a060020a031661055a565b92505b505090565b600082815260096020526040902054610f3090600160a060020a031661055a565b105b15610f64579050805b600101610efc565b600082815260096020526040902054610f5990600160a060020a031661055a565b601154600254600354600454600554601054939492939192909190610fb0610457565b600f60005080549050985098509850985098509850985098509850909192939495969798565b600d805474ff0000000000000000000000000000000000000000191660a060020a179055565b600d805474ff000000000000000000000000000000000000000019169055565b5060015b600a54811161104e5760008181526009602052604090205461107e90600160a060020a0316610eb88161055a565b8115610d0057604051600d54600160a060020a03908116916000913016319082818181858883f150505050505050565b600101611020565b82156110bb57506000905060015b600a5481116110e55760008181526009602052604090206001015490910190600101611094565b601354604051600d54600160a060020a03169160009182818181858883f150505060135550505050565b816000148015611101575060135430600160a060020a03163114155b1561112f57604051600d54600160a060020a03908116916000913016319082818181858883f1505050601355505b610a13565b50600060009054906101000a9004600160a060020a0316600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519150505b60018054600160a060020a0319168217908190556040805160e260020a630bbceb330281526024810187905260048181019283528a5160448301528a51600160a060020a039490941693632ef3accc938c938a939192839260649290920191602087810192918291859183918691600091601f850104600f02600301f150905090810190601f1680156112335780820380516001836020036101000a031916815260200191505b5093505050506020604051808303816000876161da5a03f115610002575050604051519250503a8402670de0b6b3a76400000182111561127c5750600091505b50949350505050565b600160009054906101000a9004600160a060020a0316600160a060020a03166385dee34c8360008a8a8a8a6040518760e060020a028152600401808681526020018060200180602001806020018581526020018481038452888181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156113275780820380516001836020036101000a031916815260200191505b508481038352878181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156113805780820380516001836020036101000a031916815260200191505b508481038252868181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156113d95780820380516001836020036101000a031916815260200191505b509850505050505050505060206040518083038185886185025a03f11561000257505060405151945061127392505050565b50600060009054906101000a9004600160a060020a0316600160a060020a03166338cc48316040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051519150505b60018054600160a060020a031916821790819055604080517fc281d19e0000000000000000000000000000000000000000000000000000000081529051600160a060020a03929092169163c281d19e9160048181019260209290919082900301816000876161da5a03f115610002575050604051519250610524915050565b9050610ec1565b9150600190505b600a54811161151a5760008181526009602052604090205461155990600160a060020a031661055a565b600c8390558282148015906115325750600a54600090115b1561154e5760138054848403908101909155600c805490910190555b601154601255505050565b60008281526009602052604090206001908101829055930192016114f0565b6115818361055a565b821115611594576115918361055a565b91505b50600160a060020a0382166000908152600b602090815260408083205483526009909152902060010180548290039055600c8054829003905560085460138054612710928402929092049182019055610a1383828403610b62565b1115611623575060008054600160a060020a031916731d3b2638a7cc9f2cb3d298a3da7a90b67e5506ed1790556001610ec1565b6000611642739efbea6358bed926b293d2ce63a730d6d98d43dd610956565b1115611678575060008054739efbea6358bed926b293d2ce63a730d6d98d43dd600160a060020a03199091161790556001610ec1565b60006116977320e12a1f859b3feae5fb2a0a32c18f5a65555bbf610956565b1115610ebd575060008054600160a060020a0319167320e12a1f859b3feae5fb2a0a32c18f5a65555bbf1790556001610ec1565b8381815181101561000257016020015160f860020a90819004027f2e00000000000000000000000000000000000000000000000000000000000000141561171157600191505b600101610ac8565b60001995909501945b600a83029250825060308482815181101561000257016020015160f860020a90819004810204909301602f19019250611711564244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000001d3b2638a7cc9f2cb3d298a3da7a90b67e5506ed", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000088e1315687aec48a72786c6b3b3f075208b62713", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000000000000009c4", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x00000000000000000000000000000000000000000000000000000000000000be", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000000000000000000000000000000000000000000064", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000000000000000000000000000002c68af0bb140000", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x000000000000000000000000000000000000000000000006ad2ff8ba84afdcdc", + "0x000000000000000000000000000000000000000000000000000000000000000d": "0x000000000000000000000000a1b5f95be71ffa2f86adefcaa0028c46fe825161", + "0x000000000000000000000000000000000000000000000000000000000000000f": "0x0000000000000000000000000000000000000000000000000000000000000022", + "0x0000000000000000000000000000000000000000000000000000000000000011": "0xffffffffffffffffffffffffffffffffffffffffffffffffd14ae0a37b4cc1d4", + "0x0000000000000000000000000000000000000000000000000000000000000012": "0xffffffffffffffffffffffffffffffffffffffffffffffffd5ab72be30cb5f50", + "0x0000000000000000000000000000000000000000000000000000000000000013": "0xffffffffffffffffd5bbd8ce9d1eb44232ca20eb5b4319ac5e1982d2c94bc3cb", + "0x8d1108e10bcb7c27dddfc02ed9d693a074039d026cf4ea4240b40f7d581ac824": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe950f1be9a49788ef79ea4e854ed56155a7f60661724f41e3af5f799203a1eb9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe950f1be9a49788ef79ea4e854ed56155a7f60661724f41e3af5f799203a1eba": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe950f1be9a49788ef79ea4e854ed56155a7f60661724f41e3af5f799203a1ebb": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xe950f1be9a49788ef79ea4e854ed56155a7f60661724f41e3af5f799203a1ebc": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x61c808d82a3ac53231750dadc13c777b59310bd9": { + "balance": "0x12f621ea72fef44f848", + "nonce": "51830" + }, + "0x6412becf35cc7e2a9e7e47966e443f295e1e4f4a": { + "balance": "0xfb5dbfc0d448e70", + "nonce": "6" + }, + "0x88e1315687aec48a72786c6b3b3f075208b62713": { + "balance": "0x24b9f2c5dc266dc6", + "code": "0x606060405236156101535760e060020a60003504630f825673811461018f57806323dc42e7146102135780632ef3accc146102ad578063453629781461033b578063480a434d146103d5578063524f3889146103de5780635c242c591461043f57806360f66701146104de57806362b3b8331461056757806368742da6146105eb578063688dcfd71461062b578063757004371461065857806377228659146106f25780637d242ae5146107cd5780637e1c42051461085357806381ade3071461033b57806385dee34c14610932578063a2ec191a14610a0c578063adf59f9914610213578063ae81584314610658578063b5bfdd7314610a64578063bf1fe42014610af2578063c281d19e14610b32578063c51be90f14610b44578063ca6ad1e414610bdd578063d959701614610bff578063db37e42f14610cb6578063de4b326214610d6d578063e839e65e14610daf575b61065660025433600160a060020a039081169116148015906101855750600154600160a060020a039081163390911614155b15610e8a57610002565b6106566004808035906020019082018035906020019191908080601f01602080910402602001604051908101604052809392919081815260200183838082843750949650505050505050600254600160a060020a0390811633909116148015906102095750600154600160a060020a039081163390911614155b15610ebb57610002565b60408051602060248035600481810135601f8101859004850286018501909652858552610e8c9581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a0190935282825296989760649791965060249190910194509092508291508401838280828437509496505050505050506000610f2084848462030d406104cb565b610e8c6004808035906020019082018035906020019191908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965050933593505050506000610f288383335b6000600062030d40841115801561032d5750600160a060020a03831681526020819052604081205481145b1561184e5760009150611846565b610e8c6004808035906020019082018035906020019191908080601f01602080910402602001604051908101604052809392919081815260200183838082843750506040805160208835808b0135601f81018390048302840183019094528383529799986044989297509190910194509092508291508401838280828437509496505050505050506000610f286000848462030d406104cb565b610e8c60085481565b610e8c6004808035906020019082018035906020019191908080601f016020809104026020016040519081016040528093929190818152602001838380828437509496505050505050506000610f2f82336000610f288362030d4084610302565b60408051602060248035600481810135601f8101859004850286018501909652858552610e8c9581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897606497919650602491909101945090925082915084018382808284375094965050933593505050505b600083826000600061113d848433610302565b6106566004808035906020019082018035906020019191908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965050505050505080604051808280519060200190808383829060006004602084601f0104600f02600301f150905001915050604051809103902060046000508190555050565b6106566004808035906020019082018035906020019191908080601f01602080910402602001604051908101604052809392919081815260200183838082843750949650505050505050600254600160a060020a0390811633909116148015906105e15750600154600160a060020a039081163390911614155b1561119757610002565b610656600435600254600160a060020a0390811633909116148015906106215750600154600160a060020a039081163390911614155b156111f957610002565b600160a060020a0333166000908152600660205260409020805460ff191660f860020a600435041790555b005b60408051602060248035600481810135601f8101859004850286018501909652858552610e8c9581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897606497919650602491909101945090925082915084018382808284375094965050933593505050505b6000610f1d858585856104cb565b60408051602060248035600481810135601f8101859004850286018501909652858552610e8c9581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a0190935282825296989760849791965060249190910194509092508291508401838280828437509496505050505050506000610f1d8585858562030d4061091f565b60408051602060248035600481810135601f81018590048502860185019096528585526106569581359591946044949293909201918190840183828082843750949650505050505050600254600090600160a060020a0390811633909116148015906108495750600154600160a060020a039081163390911614155b1561121f57610002565b60408051602060248035600481810135601f8101859004850286018501909652858552610e8c9581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897608497919650602491909101945090925082915084018382808284375094965050933593505050505b6000848260006000611516848433610302565b60408051602060248035600481810135601f8101859004850286018501909652858552610e8c9581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a019093528282529698976064979196506024919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a0190935282825296989760849791965060249190910194509092508291508401838280828437509496505093359350505050600061156b868686868661091f565b6106566004808035906020019082018035906020019191908080601f01602080910402602001604051908101604052809392919081815260200183838082843750949650509335935050505061157282600083610ab5565b6106566004808035906020019082018035906020019191908080601f016020809104026020016040519081016040528093929190818152602001838380828437509496505093359350506044359150505b600254600090600160a060020a039081163390911614801590610ae85750600154600160a060020a039081163390911614155b1561157657610002565b610656600435600254600160a060020a039081163390911614801590610b285750600154600160a060020a039081163390911614155b1561162f57610002565b610e9e600154600160a060020a031681565b60408051602060248035600481810135601f8101859004850286018501909652858552610e8c9581359591946044949293909201918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897606497919650602491909101945090925082915084018382808284375094965050933593505050506000610f1d858585856106e4565b600160a060020a03331660009081526007602052604090206004359055610656565b604080516004803580820135602081810285810182019096528185526106569593946024949093850192918291908501908490808284375050604080518735808a013560208181028085018201909552818452989a99604499939850919091019550935083925085019084908082843750949650505050505050600254600090600160a060020a039081163390911614801590610cac5750600154600160a060020a039081163390911614155b1561163457610002565b604080516004803580820135602081810285810182019096528185526106569593946024949093850192918291908501908490808284375050604080518735808a013560208181028085018201909552818452989a99604499939850919091019550935083925085019084908082843750949650505050505050600254600090600160a060020a039081163390911614801590610d635750600154600160a060020a039081163390911614155b1561168f57610002565b61065660043560025460009033600160a060020a03908116911614801590610da55750600154600160a060020a039081163390911614155b1561170557610002565b610e8c6004808035906020019082018035906020019191908080601f01602080910402602001604051908101604052809392919081815260200183838082843750506040805160208835808b0135601f8101839004830284018301909452838352979998604498929750919091019450909250829150840183828082843750506040805160209735808a0135601f81018a90048a0283018a0190935282825296989760649791965060249190910194509092508291508401838280828437509496505050505050506000610f20600085858562030d4061091f565b565b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b60006003600050600083604051808280519060200190808383829060006004602084601f0104600f02600301f1509050019150506040518091039020815260200190815260200160002060006101000a81548160ff0219169083021790555050565b90505b949350505050565b9392505050565b92915050565b6000600050600033600160a060020a031681526020019081526020016000206000505433600160a060020a031630600160a060020a03160101604051808281526020019150506040518091039020945084506000600050600033600160a060020a031681526020019081526020016000206000818150548092919060010191905055507fb76d0edd90c6a07aa3ff7a222d7f5933e29c6acc660c059c97837f05c4ca1a8433868b8b8b8b6006600050600033600160a060020a0316815260200190815260200160002060009054906101000a900460f860020a026007600050600033600160a060020a03168152602001908152602001600020600050546040518089600160a060020a0316815260200188815260200187815260200180602001806020018681526020018581526020018481526020018381038352888181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156110c35780820380516001836020036101000a031916815260200191505b508381038252878181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561111c5780820380516001836020036101000a031916815260200191505b509a505050505050505050505060405180910390a150505050949350505050565b91503482901061119257813403905060008111156111765760405133600160a060020a031690600090839082818181858883f150505050505b42624f1a000189118061118857504586115b15610f3557610002565b610002565b60016003600050600083604051808280519060200190808383829060006004602084601f0104600f02600301f1509050019150506040518091039020815260200190815260200160002060006101000a81548160ff0219169083021790555050565b604051600160a060020a03828116916000913016319082818181858883f1505050505050565b50600882905560005b600b548110156112a757600b8054600a91600091849081101561000257508054600080516020611883833981519152850154835260209390935260408220548602926009929190859081101561000257908252600080516020611883833981519152018150548152602081019190915260400160002055600101611228565b505050565b6000600050600033600160a060020a031681526020019081526020016000206000505433600160a060020a031630600160a060020a03160101604051808281526020019150506040518091039020945084506000600050600033600160a060020a031681526020019081526020016000206000818150548092919060010191905055507faf30e4d66b2f1f23e63ef4591058a897f67e6867233e33ca3508b982dcc4129b33868c8c8c8c8c6006600050600033600160a060020a0316815260200190815260200160002060009054906101000a900460f860020a026007600050600033600160a060020a0316815260200190815260200160002060005054604051808a600160a060020a0316815260200189815260200188815260200180602001806020018060200187815260200186815260200185815260200184810384528a8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561143f5780820380516001836020036101000a031916815260200191505b508481038352898181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156114985780820380516001836020036101000a031916815260200191505b508481038252888181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156114f15780820380516001836020036101000a031916815260200191505b509c5050505050505050505050505060405180910390a1505050505b95945050505050565b915034829010611192578134039050600081111561154f5760405133600160a060020a031690600090839082818181858883f150505050505b42624f1a00018a118061156157504586115b156112ac57610002565b905061150d565b5050565b8383604051808380519060200190808383829060006004602084601f0104600f02600301f150905001828152600101925050506040518091039020905080600b600050600b600050805480919060010190908154818355818115116115fe578183600052602060002091820191016115fe91905b8082111561162b57600081556001016115ea565b5050508154811015610002576000918252602080832090910192909255918252600a905260409020555050565b5090565b600555565b5060005b81518110156112a7578281815181101561000257906020019060200201516007600050600084848151811015610002576020908102909101810151600160a060020a03168252919091526040902055600101611638565b5060005b81518110156112a75782818151811015610002579060200190602002015160f860020a026006600050600084848151811015610002576020908102909101810151600160a060020a031682529190915260409020805460f860020a90920460ff19909216919091179055600101611693565b50600881905560005b600b5481101561157257600b8054600a91600091849081101561000257600080516020611883833981519152015482526020929092526040812054825490850292600992918590811015610002576000805160206118838339815191520154825250602091909152604090205560010161170e565b60096000506000866006600050600087600160a060020a0316815260200190815260200160002060009054906101000a900460f860020a02604051808380519060200190808383829060006004602084601f0104600f02600301f150905001828152600101925050506040518091039020815260200190815260200160002060005054915081506007600050600084600160a060020a03168152602001908152602001600020600050549050806000141561183d57506005545b83810291909101905b509392505050565b600454600014801590611875575060045460009081526003602052604090205460ff166001145b156117835760009150611846560175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000004": "0xbb130806898f085471286ecb4f3966fcbe090ba29e4f9d194ee9e9062f6b61ae", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x00000000000000000000000000000000000000000000000000000004a817c800", + "0x797fdd0f6c82412493cfa2aacdc9999c10e5d0c9aa3f05a8a289b1b3918c6db8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8d90a37db271d62339ebfe84641d1ebdaf56fd5d50861d795eacb410dbb57630": "0x000000000000000000000000000000000000000000000000000cf4e712e8d654", + "0x9864048b6d6c99ecd7fcaecf663fbe1036a6e1fc00cec0a3eb25684dd08184c2": "0x0000000000000000000000000000000000000000000000000000000000000011", + "0xca9ea8077ddc97a21c029df4b19819e51903e11d4bfc7564a622a192cefd6356": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf34e44a0672ef76b852374cc47d9772eb4e5e41fa79fba61dcfc9cf7d50418d5": "0x0000000000000000000000000000000000000000000000000000000000000022" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "1725116", + "difficulty": "45966589844033", + "timestamp": "1466232988", + "gasLimit": "4716972", + "miner": "0x61c808d82a3ac53231750dadc13c777b59310bd9" + }, + "input": "0xf86d068504e3b2920083030d409450739060a2c32dc076e507ae1a893aab28ecfe68880429d069189e0000801ca04e403b46022c2098e41d3a0e561881ac368cd330637239da85759c1b4f44ab24a072a88235d98959283c00af411bd663b0da8703e05a94d3673aca37d0a39b7e07", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0x6412becf35cc7e2a9e7e47966e443f295e1e4f4a", + "gas": "0x30d40", + "gasUsed": "0x249eb", + "to": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "input": "0x", + "calls": [ + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x257af", + "gasUsed": "0xbc", + "to": "0x1d3b2638a7cc9f2cb3d298a3da7a90b67e5506ed", + "input": "0x38cc4831", + "output": "0x00000000000000000000000088e1315687aec48a72786c6b3b3f075208b62713", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x255a1", + "gasUsed": "0x73a", + "to": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "input": "0x2ef3accc000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000249f0000000000000000000000000000000000000000000000000000000000000000355524c0000000000000000000000000000000000000000000000000000000000", + "output": "0x00000000000000000000000000000000000000000000000000179d63013c5654", + "calls": [ + { + "from": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x24680", + "gasUsed": "0xbc", + "to": "0x1d3b2638a7cc9f2cb3d298a3da7a90b67e5506ed", + "input": "0x38cc4831", + "output": "0x00000000000000000000000088e1315687aec48a72786c6b3b3f075208b62713", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x22f3b", + "gasUsed": "0x73a", + "to": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "input": "0x2ef3accc000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000249f0000000000000000000000000000000000000000000000000000000000000000355524c0000000000000000000000000000000000000000000000000000000000", + "output": "0x00000000000000000000000000000000000000000000000000179d63013c5654", + "calls": [ + { + "from": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x30", + "gasUsed": "0x18", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x6a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174612e30", + "output": "0x6a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174612e30", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x99", + "gasUsed": "0x2d", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x4244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d", + "output": "0x4244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "gas": "0x2083e", + "gasUsed": "0x4417", + "to": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "input": "0x85dee34c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000249f0000000000000000000000000000000000000000000000000000000000000000355524c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000436a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174612e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c4244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d0000000000000000000000000000000000000000", + "output": "0xd1b13c1538a940417bf0e73b2498634436753c854c7fb971224d971bd2ae3e88", + "calls": [ + { + "from": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "gas": "0x12", + "gasUsed": "0x12", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x55524c", + "output": "0x55524c", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "gas": "0x30", + "gasUsed": "0x18", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x6a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174612e30", + "output": "0x6a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174612e30", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "gas": "0x99", + "gasUsed": "0x2d", + "to": "0x0000000000000000000000000000000000000004", + "input": "0x4244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d", + "output": "0x4244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x88e1315687aec48a72786c6b3b3f075208b62713", + "topics": [ + "0xaf30e4d66b2f1f23e63ef4591058a897f67e6867233e33ca3508b982dcc4129b" + ], + "data": "0x00000000000000000000000050739060a2c32dc076e507ae1a893aab28ecfe68d1b13c1538a940417bf0e73b2498634436753c854c7fb971224d971bd2ae3e8800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000249f011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000355524c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000436a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174612e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c4244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d0000000000000000000000000000000000000000", + "position": "0x4" + } + ], + "value": "0x179d63013c5654", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", + "topics": [], + "data": "0x62616e6b726f6c6c5f6d69736d61746368", + "position": "0x2" + } + ], + "value": "0x429d069189e0000", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json new file mode 100644 index 0000000..64941dd --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json @@ -0,0 +1,85 @@ +{ + "genesis": { + "difficulty": "8430028481555", + "extraData": "0xd783010302844765746887676f312e352e31856c696e7578", + "gasLimit": "3141592", + "hash": "0xde66937783697293f2e529d2034887c531535d78afa8c9051511ae12ba48fbea", + "miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226", + "mixHash": "0xba28a43bfbca4a2effbb76bb70d03482a8a0c92e2883ff36cbac3d7c6dbb7df5", + "nonce": "0xa3827ec0a82fe823", + "number": "765824", + "stateRoot": "0x8d96cb027a29f8ca0ccd6d31f9ea0656136ec8030ecda70bb9231849ed6f41a2", + "timestamp": "1451389443", + "totalDifficulty": "4838314986494741271", + "alloc": { + "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb": { + "balance": "0x14203bee2ea6fbe8c", + "nonce": "34" + }, + "0xe2fe6b13287f28e193333fdfe7fedf2f6df6124a": { + "balance": "0x2717a9c870a286f4350" + }, + "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd": { + "balance": "0x0", + "code": "0x606060405260e060020a600035046306fdde038114610047578063313ce567146100a457806370a08231146100b057806395d89b41146100c8578063a9059cbb14610123575b005b61015260008054602060026001831615610100026000190190921691909104601f810182900490910260809081016040526060828152929190828280156101f55780601f106101ca576101008083540402835291602001916101f5565b6101c060025460ff1681565b6101c060043560036020526000908152604090205481565b610152600180546020601f6002600019610100858716150201909316929092049182018190040260809081016040526060828152929190828280156101f55780601f106101ca576101008083540402835291602001916101f5565b610045600435602435600160a060020a033316600090815260036020526040902054819010156101fd57610002565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156101b25780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6060908152602090f35b820191906000526020600020905b8154815290600101906020018083116101d857829003601f168201915b505050505081565b600160a060020a03821660009081526040902054808201101561021f57610002565b806003600050600033600160a060020a03168152602001908152602001600020600082828250540392505081905550806003600050600084600160a060020a0316815260200190815260200160002060008282825054019250508190555081600160a060020a031633600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505056", + "storage": { + "0x1dae8253445d3a5edbe8200da9fc39bc4f11db9362181dc1b640d08c3c2fb4d6": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8ba52aac7f255d80a49abcf003d6af4752aba5a9531cae94fde7ac8d72191d67": "0x000000000000000000000000000000000000000000000000000000000178e460" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "765825", + "difficulty": "8425912256743", + "timestamp": "1451389488", + "gasLimit": "3141592", + "miner": "0xe2fe6b13287f28e193333fdfe7fedf2f6df6124a" + }, + "input": "0xf8aa22850ba43b740083024d4594f4eced2f682ce333f96f2d8966c613ded8fc95dd80b844a9059cbb000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb00000000000000000000000000000000000000000000000000000000009896801ca067da548a2e0f381a957b9b51f086073375d6bfc7312cbc9540b3647ccab7db11a042c6e5b34bc7ba821e9c25b166fa13d82ad4b0d044d16174d5587d4f04ecfcd1", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb", + "gas": "0x24d45", + "gasUsed": "0xc6a5", + "to": "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd", + "input": "0xa9059cbb000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb0000000000000000000000000000000000000000000000000000000000989680", + "logs": [ + { + "address": "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000d1220a0cf47c7b9be7a2e6ba89f429762e7b9adb", + "0x000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000989680", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_failed.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_failed.json new file mode 100644 index 0000000..30346d0 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_failed.json @@ -0,0 +1,244 @@ +{ + "genesis": { + "difficulty": "56311715121637", + "extraData": "0x7777772e62772e636f6d", + "gasLimit": "4712388", + "hash": "0x20d3b8daa046f2f10564d84ccbe6d0a8842d8d52bc6d623e23c38050a8f73776", + "miner": "0xbcdfc35b86bedf72f0cda046a3c16829a2ef41d1", + "mixHash": "0x75029f90d7de3f9e3d5eac4a25019f9ac5d0041641d1ef17e7759e45699d4224", + "nonce": "0x54ff3b34fa1d9c97", + "number": "1968179", + "stateRoot": "0x6420003b1779cca3bcdab698c239bbc63623c0a7e4deeedbdb8190b9e7fd7520", + "timestamp": "1469713675", + "totalDifficulty": "42284028928878034360", + "alloc": { + "0x10abb5efecdc09581f8b7cb95791fe2936790b4e": { + "balance": "0x81f158e2814b4ab624c", + "code": "0x6060604052361561020e5760e060020a6000350463013cf08b8114610247578063095ea7b3146102d05780630c3b7b96146103455780630e7082031461034e578063149acf9a1461036057806318160ddd146103725780631f2dc5ef1461037b57806321b5b8dd1461039b578063237e9492146103ad57806323b872dd1461040e5780632632bf2014610441578063341458081461047257806339d1f9081461047b5780634b6753bc146104935780634df6d6cc1461049c5780634e10c3ee146104b7578063590e1ae3146104ca578063612e45a3146104db578063643f7cdd1461057a578063674ed066146105925780636837ff1e1461059b57806370a08231146105e5578063749f98891461060b57806378524b2e1461062457806381f03fcb1461067e57806382661dc41461069657806382bf6464146106b75780638b15a605146106c95780638d7af473146106d257806396d7f3f5146106e1578063a1da2fb9146106ea578063a3912ec814610704578063a9059cbb1461070f578063b7bc2c841461073f578063baac53001461074b578063be7c29c1146107b1578063c9d27afe14610817578063cc9ae3f61461082d578063cdef91d014610841578063dbde198814610859578063dd62ed3e1461087e578063e33734fd146108b2578063e5962195146108c6578063e66f53b7146108de578063eceb2945146108f0578063f8c80d261461094f575b610966600f546000906234bc000142108015610239575060125433600160a060020a03908116911614155b156109785761098033610752565b6109866004356000805482908110156100025750808052600e8202600080516020612a3683398151915201905060038101546004820154600683015460018401548454600786015460058701546009880154600a890154600d8a0154600160a060020a039586169b509599600201989760ff81811698610100909204811697949691951693168c565b61096660043560243533600160a060020a03908116600081815260156020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b61096660105481565b610a7d600754600160a060020a031681565b610a7d600e54600160a060020a031681565b61096660165481565b6109665b60004262127500600f60005054031115610de557506014610983565b610a7d601254600160a060020a031681565b60408051602060248035600481810135601f810185900485028601850190965285855261096695813595919460449492939092019181908401838280828437509496505050505050506000600060006000600060003411156116a857610002565b6109666004356024356044355b60115460009060ff1680156104315750600f5442115b80156124e957506124e78461044b565b6109666000610980335b600160a060020a0381166000908152600b602052604081205481908114156129cb57610b99565b61096660065481565b6109665b600d5430600160a060020a03163103610983565b610966600f5481565b61096660043560046020526000908152604090205460ff1681565b61096660043560243560006124cb610831565b610a9a6000341115610ba457610002565b604080516020604435600481810135601f8101849004840285018401909552848452610966948135946024803595939460649492939101918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897608497919650602491909101945090925082915084018382808284375094965050933593505060a435915050600060006110c1336105ec565b61096660043560096020526000908152604090205481565b61096660015481565b610a9a60043530600160a060020a031633600160a060020a03161415806105db5750600160a060020a03811660009081526004602052604090205460ff16155b156121cb576121c8565b6109666004355b600160a060020a0381166000908152601460205260409020545b919050565b6109666004356024356000600034111561259957610002565b610966600062e6b680420360026000505410806106505750600354600160a060020a0390811633909116145b80156106645750600254621274ff19420190105b156126145750426002908155600180549091028155610983565b610966600435600a6020526000908152604090205481565b610966600435602435600060006000600060006000341115611ba157610002565b610a7d600854600160a060020a031681565b610966600c5481565b61096660005460001901610983565b61096660025481565b61096660043560006000600060003411156121fc57610002565b6109665b6001610983565b6109666004356024355b60115460009060ff16801561072f5750600f5442115b801561248757506124853361044b565b61096660115460ff1681565b6109666004355b60006000600f600050544210801561076a5750600034115b80156107a457506011546101009004600160a060020a0316600014806107a457506011546101009004600160a060020a0390811633909116145b15610b9f57610a9c61037f565b610a7d600435600060006000508281548110156100025750508080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56b600e83020180548290811015610002575081526020902060030154600160a060020a0316610606565b61096660043560243560006000610e1b336105ec565b6109665b6000600034111561247c57610002565b61096660043560056020526000908152604090205481565b610966600435602435604435600061252f845b6000600060003411156127ac57610002565b610966600435602435600160a060020a0382811660009081526015602090815260408083209385168352929052205461033f565b610a9a600435600034111561254557610002565b610966600435600b6020526000908152604090205481565b610a7d600354600160a060020a031681565b604080516020606435600481810135601f81018490048402850184019095528484526109669481359460248035956044359560849492019190819084018382808284375094965050505050505060006000600034111561103257610002565b610a7d6011546101009004600160a060020a031681565b60408051918252519081900360200190f35b610980610708565b90505b90565b604051808d600160a060020a031681526020018c8152602001806020018b81526020018a815260200189815260200188815260200187815260200186815260200185815260200184815260200183600160a060020a0316815260200182810382528c818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610a635780601f10610a3857610100808354040283529160200191610a63565b820191906000526020600020905b815481529060010190602001808311610a4657829003601f168201915b50509d505050505050505050505050505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b005b604051601254601434908102939093049350600160a060020a03169183900390600081818185876185025a03f150505050600160a060020a038316600081815260146020908152604080832080548601905560168054860190556013825291829020805434019055815184815291517fdbccb92686efceafb9bb7e0394df7f58f71b954061b81afb57109bf247d3d75a9281900390910190a260105460165410801590610b4c575060115460ff16155b15610b94576011805460ff1916600117905560165460408051918252517ff381a3e2428fdda36615919e8d9c35878d9eb0cf85ac6edf575088e80e4c147e9181900360200190a15b600191505b50919050565b610002565b600f5442118015610bb8575060115460ff16155b15610de357601260009054906101000a9004600160a060020a0316600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040516012549051600160a060020a039190911631109050610cc9576040805160125460e060020a63d2cc718f0282529151600160a060020a039290921691630221038a913091849163d2cc718f91600482810192602092919082900301816000876161da5a03f11561000257505060408051805160e160020a63011081c5028252600160a060020a039490941660048201526024810193909352516044838101936020935082900301816000876161da5a03f115610002575050505b33600160a060020a0316600081815260136020526040808220549051909181818185876185025a03f19250505015610de35733600160a060020a03167fbb28353e4598c3b9199101a66e0989549b659a59a54d2c27fbb183f1932c8e6d6013600050600033600160a060020a03168152602001908152602001600020600050546040518082815260200191505060405180910390a26014600050600033600160a060020a0316815260200190815260200160002060005054601660008282825054039250508190555060006014600050600033600160a060020a031681526020019081526020016000206000508190555060006013600050600033600160a060020a03168152602001908152602001600020600050819055505b565b4262054600600f60005054031115610e13576201518062127500600f60005054034203046014019050610983565b50601e610983565b60001415610e2857610002565b6000341115610e3657610002565b6000805485908110156100025750600160a060020a03331681527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56e600e8602908101602052604090912054600080516020612a3683398151915291909101915060ff1680610eb05750600c810160205260406000205460ff165b80610ebf575060038101544210155b15610ec957610002565b8215610f0f5733600160a060020a03166000908152601460209081526040808320546009850180549091019055600b84019091529020805460ff19166001179055610f4b565b33600160a060020a0316600090815260146020908152604080832054600a850180549091019055600c84019091529020805460ff191660011790555b33600160a060020a03166000908152600b60205260408120541415610f77576040600020849055610feb565b33600160a060020a03166000908152600b60205260408120548154811015610002579080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566600e909102015460038201541115610feb5733600160a060020a03166000908152600b602052604090208490555b60408051848152905133600160a060020a03169186917f86abfce99b7dd908bec0169288797f85049ec73cbe046ed9de818fab3a497ae09181900360200190a35092915050565b6000805487908110156100025750808052600e8702600080516020612a3683398151915201905090508484846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020816005016000505414915050949350505050565b600014156110ce57610002565b82801561111857508660001415806110e857508451600014155b806111005750600354600160a060020a038981169116145b8061110b5750600034115b80611118575062093a8084105b1561112257610002565b8215801561114257506111348861115c565b158061114257506212750084105b156111fe57610002565b83546118e590600160a060020a03165b600160a060020a03811660009081526004602052604081205460ff16806111f15750601254600160a060020a039081169083161480156111f15750601260009054906101000a9004600160a060020a0316600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051516006541190505b156129a157506001610606565b6249d40084111561120e57610002565b60115460ff1615806112215750600f5442105b806112365750600c5434108015611236575082155b1561124057610002565b42844201101561124f57610002565b30600160a060020a031633600160a060020a0316141561126e57610002565b60008054600181018083559091908280158290116112a557600e0281600e0283600052602060002091820191016112a5919061136a565b505060008054929450918491508110156100025750808052600e8302600080516020612a368339815191520190508054600160a060020a031916891781556001818101899055875160028084018054600082815260209081902096975091959481161561010002600019011691909104601f908101829004840193918b019083901061146257805160ff19168380011785555b5061149292915061144a565b5050600060098201819055600a820155600d81018054600160a060020a03191690556001015b8082111561145e578054600160a060020a03191681556000600182810182905560028084018054848255909281161561010002600019011604601f81901061143057505b506000600383018190556004808401805461ffff19169055600584018290556006840182905560078401805460ff191690556008840180548382559083526020909220611344929091028101905b8082111561145e57600080825560018201818155600283019190915560039091018054600160a060020a03191690556113fc565b601f0160209004906000526020600020908101906113ae91905b8082111561145e576000815560010161144a565b5090565b82800160010185558215611338579182015b82811115611338578251826000505591602001919060010190611474565b50508787866040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160050160005081905550834201816003016000508190555060018160040160006101000a81548160ff02191690830217905550828160070160006101000a81548160ff02191690830217905550821561157857600881018054600181018083559091908280158290116115735760040281600402836000526020600020918201910161157391906113fc565b505050505b600d8082018054600160a060020a031916331790553460068301819055815401905560408051600160a060020a038a16815260208181018a9052918101859052608060608201818152895191830191909152885185937f5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f938d938d938a938e93929160a084019185810191908190849082908590600090600490601f850104600f02600301f150905090810190601f1680156116485780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a2509695505050505050565b6040805186815260208101839052815189927fdfc78bdca8e3e0b18c16c5c99323c6cb9eb5e00afde190b4e7273f5158702b07928290030190a25b5050505092915050565b6000805488908110156100025750808052600e8802600080516020612a36833981519152019050600781015490945060ff166116e757620d2f006116ec565b622398805b600485015490935060ff16801561170857506003840154830142115b15611716576117b887611890565b600384015442108061172d5750600484015460ff16155b806117ae57508360000160009054906101000a9004600160a060020a03168460010160005054876040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020846005016000505414155b1561114c57610002565b61169e565b60048401805461ff001916610100179055835460019550600160a060020a03908116309091161480159061180157508354600754600160a060020a03908116911614155b801561181d57506008548454600160a060020a03908116911614155b801561183957508354601254600160a060020a03908116911614155b801561185557506003548454600160a060020a03908116911614155b1561188b5760018401805430600160a060020a031660009081526005602052604090208054919091019055546006805490910190555b611663875b6000600060005082815481101561000257908052600e02600080516020612a36833981519152018150600481015490915060ff16156118d757600d80546006830154900390555b600401805460ff1916905550565b15156118f45761190087611890565b6001915061193161047f565b604051600d8501546006860154600160a060020a0391909116916000919082818181858883f193505050505061169e565b6001850154111561194157600091505b50600a8301546009840154865191019060049010801590611986575085600081518110156100025790602001015160f860020a900460f860020a02606860f860020a02145b80156119b6575085600181518110156100025790602001015160f860020a900460f860020a02603760f860020a02145b80156119e6575085600281518110156100025790602001015160f860020a900460f860020a0260ff60f860020a02145b8015611a16575085600381518110156100025790602001015160f860020a900460f860020a02601e60f860020a02145b8015611a45575030600160a060020a0316600090815260056020526040902054611a4290611a5d61047f565b81105b15611a4f57600091505b6001840154611a8090611a5f565b015b30600160a060020a03166000908152600560205260408120546129a961047f565b8110611ad457604051600d8501546006860154600160a060020a0391909116916000919082818181858883f193505050501515611abc57610002565b4260025560165460059004811115611ad45760056001555b6001840154611ae290611a5f565b8110158015611af85750600a8401546009850154115b8015611b015750815b1561188b578360000160009054906101000a9004600160a060020a0316600160a060020a0316846001016000505487604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611b7d5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f19250505015156117bd57610002565b611baa336105ec565b60001415611bb757610002565b60008054889081101561000257508052600e87027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566810154600080516020612a36833981519152919091019450421080611c1957506003840154622398800142115b80611c3257508354600160a060020a0390811690871614155b80611c425750600784015460ff16155b80611c68575033600160a060020a03166000908152600b8501602052604090205460ff16155b80611c9c575033600160a060020a03166000908152600b60205260409020548714801590611c9c5750604060009081205414155b15611ca657610002565b600884018054600090811015610002579081526020812060030154600160a060020a03161415611e1257611efc86604051600090600160a060020a038316907f9046fefd66f538ab35263248a44217dcb70e2eb2cd136629e141b8b8f9f03b60908390a260408051600e547fe2faf044000000000000000000000000000000000000000000000000000000008252600160a060020a03858116600484015260248301859052604483018590526223988042016064840152925192169163e2faf04491608480820192602092909190829003018187876161da5a03f1156100025750506040515191506106069050565b6008850180546000908110156100025781815260208082209390935530600160a060020a031681526005909252604082205481549092908110156100025790815260208120905060020155601654600885018054600090811015610002579081526020812090506001015560048401805461ff0019166101001790555b6008840180546000908110156100025781548282526020822060010154929190811015610002579081526020812090505433600160a060020a031660009081526014602052604081205460088801805493909102939093049550908110156100025790815260208120905060030154604080517fbaac530000000000000000000000000000000000000000000000000000000000815233600160a060020a0390811660048301529151929091169163baac53009186916024808301926020929190829003018185886185025a03f11561000257505060405151600014159150611f78905057610002565b60088501805460009081101561000257818152602081206003018054600160a060020a03191690931790925580549091908110156100025790815260208120905060030154600160a060020a031660001415611f5757610002565b600d5430600160a060020a0316311015611f7057610002565b611d9561047f565b6008840180546000908110156100025781548282526020822060010154929190811015610002579081526020812090506002015433600160a060020a0390811660009081526014602090815260408083205430909416835260058083528184205460099093529083205460088b018054969095029690960497509487020494508593929091908290811015610002575260208120815060030154600160a060020a0390811682526020828101939093526040918201600090812080549095019094553016835260059091529020548290101561205357610002565b30600160a060020a031660009081526005602052604081208054849003905560088501805483926009929091829081101561000257508152602080822060030154600160a060020a039081168352929052604080822080549094019093553090911681522054819010156120c657610002565b30600160a060020a0390811660009081526009602090815260408083208054869003905533909316808352601482528383205484519081529351929390927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a36121383361086c565b5033600160a060020a03166000908152601460209081526040808320805460168054919091039055839055600a9091528120556001945061169e565b30600160a060020a0390811660008181526005602090815260408083208054958716808552828520805490970190965584845283905560099091528082208054948352908220805490940190935590815290555b50565b604051600160a060020a0382811691309091163190600081818185876185025a03f192505050151561217457610002565b33600160a060020a03818116600090815260096020908152604080832054815160065460085460e060020a63d2cc718f028352935197995091969195929092169363d2cc718f936004848101949193929183900301908290876161da5a03f11561000257505050604051805190602001506005600050600033600160a060020a03168152602001908152602001600020600050540204101561229d57610002565b600160a060020a03338116600090815260096020908152604080832054815160065460085460e060020a63d2cc718f02835293519296909593169363d2cc718f93600483810194929383900301908290876161da5a03f11561000257505050604051805190602001506005600050600033600160a060020a0316815260200190815260200160002060005054020403905083156123ec57600860009054906101000a9004600160a060020a0316600160a060020a0316630221038a83600160a060020a0316630e7082036040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63011081c5028252600160a060020a031660048201526024810186905290516044808301935060209282900301816000876161da5a03f115610002575050604051511515905061245457610002565b6040805160085460e160020a63011081c5028252600160a060020a038581166004840152602483018590529251921691630221038a9160448082019260209290919082900301816000876161da5a03f115610002575050604051511515905061245457610002565b600160a060020a03331660009081526009602052604090208054909101905550600192915050565b6109803361086c565b155b80156124a257506124a23384845b6000600061293a856105ec565b80156124be57506124be83836000600034111561261c57610002565b15610b9f5750600161033f565b15156124d657610002565b6124e08383610719565b905061033f565b155b80156124fb57506124fb848484612495565b80156125185750612518848484600060003411156126c157610002565b15610b9f57506001612528565b90505b9392505050565b151561253a57610002565b61252584848461041b565b30600160a060020a031633600160a060020a031614158061258a575030600160a060020a031660009081526005602052604090205460649061258561047f565b010481115b1561259457610002565b600c55565b600354600160a060020a0390811633909116146125b557610002565b600160a060020a038316600081815260046020908152604091829020805460ff191686179055815185815291517f73ad2a153c8b67991df9459024950b318a609782cee8c7eeda47b905f9baa91f9281900390910190a250600161033f565b506000610983565b33600160a060020a03166000908152601460205260409020548290108015906126455750600082115b156126b957600160a060020a03338116600081815260146020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a350600161033f565b50600061033f565b600160a060020a03841660009081526014602052604090205482901080159061270a5750601560209081526040600081812033600160a060020a03168252909252902054829010155b80156127165750600082115b156127a457600160a060020a03838116600081815260146020908152604080832080548801905588851680845281842080548990039055601583528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a3506001612528565b506000612528565b600160a060020a038381166000908152600a6020908152604080832054601654600754835160e060020a63d2cc718f02815293519296919591169363d2cc718f9360048181019492939183900301908290876161da5a03f11561000257505060405151905061281a866105ec565b0204101561282757610002565b600160a060020a038381166000908152600a6020908152604080832054601654600754835160e060020a63d2cc718f02815293519296919591169363d2cc718f9360048181019492939183900301908290876161da5a03f115610002575050604051519050612895866105ec565b0204039050600760009054906101000a9004600160a060020a0316600160a060020a0316630221038a84836040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511515905061291357610002565b600160a060020a0383166000908152600a6020526040902080548201905560019150610b99565b600160a060020a0386166000908152600a602052604090205480850291909104915081111561296857610002565b600160a060020a038581166000908152600a60205260408082208054859003905591861681522080548201905560019150509392505050565b506000610606565b0160030260166000505483020460016000505460166000505404019050610606565b600160a060020a0383166000908152600b6020526040812054815481101561000257818052600e02600080516020612a368339815191520190506003810154909150421115610b9457600160a060020a0383166000908152600b602052604081208190559150610b9956290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", + "nonce": "3", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000000f": "0x0000000000000000000000000000000000000000000000000000000057bda071", + "0x0000000000000000000000000000000000000000000000000000000000000010": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000011": "0x0000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c18941301", + "0x0000000000000000000000000000000000000000000000000000000000000012": "0x000000000000000000000000fde8d5f77ef48bb7bf5766c7404691b9ee1dfca7", + "0x0000000000000000000000000000000000000000000000000000000000000016": "0x00000000000000000000000000000000000000000000081f158e2814b4ab624c", + "0x7ffc832d0c7f56b16d03bf3ff14bc4dd6a6cb1ec75841f7397362f4a9be4d392": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xaccfa2662c944e8eae80b7720d9d232eb6809c18f6c8da65189acbb38069d869": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x630a0cd35d5bd57e61410fda76fea850225cda18": { + "balance": "0x0", + "code": "0x6060604052361561006c5760e060020a60003504630121b93f81146100e15780636637b882146101615780636dbf2fa0146101935780638da5cb5b1461026a578063a6f9dae11461027c578063beabacc8146102ae578063d979f5aa14610322578063e1fa763814610354575b61050b600060006000600460005054111561051d576004805460001901905560015460035460055460e260020a6320998771026060908152606492909252600160a060020a03908116608452909116906382661dc49060a49060209060448187876161da5a03f11561000257506105c3915050565b6105cb60043560005433600160a060020a039081169116141561015e57600180547fc9d27afe0000000000000000000000000000000000000000000000000000000060609081526064849052608492909252600160a060020a03169063c9d27afe9060a4906020906044816000876161da5a03f115610002575050505b50565b6105cb60043560005433600160a060020a039081169116141561015e5760018054600160a060020a0319168217905550565b60806020604435600481810135601f8101849004909302840160405260608381526105cb9482359460248035956064949391019190819083828082843750949650505050505050600054600160a060020a039081163390911614156102655782600160a060020a03168282604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561024b5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f1505050505b505050565b6105cd600054600160a060020a031681565b6105cb60043560005433600160a060020a039081169116141561015e5760008054600160a060020a0319168217905550565b6105cb6004356024356044356000805433600160a060020a039081169116141561031c5760e060020a63a9059cbb026060908152600160a060020a03848116606452608484905285929083169163a9059cbb9160a4916020916044908290876161da5a03f115610002575050505b50505050565b6105cb60043560005433600160a060020a039081169116141561015e5760028054600160a060020a0319168217905550565b6105cb60043560243560005433600160a060020a03908116911614156105075760015460e060020a6370a0823102606090815230600160a060020a0390811660645291909116906370a08231906084906020906024816000876161da5a03f1156100025750506040805180516006556002546001547f1a695230000000000000000000000000000000000000000000000000000000008352600160a060020a039081166004840152925192169250631a695230916024828101926000929190829003018183876161da5a03f1156100025750505060048181556003839055600154604080517f013cf08b00000000000000000000000000000000000000000000000000000000815292830185905251600160a060020a03919091169163013cf08b91602482810192602092919082900301816000876161da5a03f11561000257505060408051805160058054600160a060020a0319169091179081905560015460035460e260020a63209987710284526004840152600160a060020a0391821660248401529251921692506382661dc491604482810192602092919082900301816000876161da5a03f115610002575050505b5050565b60408051918252519081900360200190f35b60015460e060020a6370a0823102606090815230600160a060020a0390811660645291909116906370a082319060849060209060248187876161da5a03f11561000257505060408051805160015460025460e060020a63a9059cbb028452600160a060020a039081166004850152602484018390529351919550909216925063a9059cbb916044828101926020929190829003018188876161da5a03f115610002575050505b600191505090565b005b6060908152602090f3", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000e6002189a74b43e6868b20c1311bc108e38aac57", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c189413", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000006e073c0e1bd5af550239025dffcfb37175acedd3", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x6e073c0e1bd5af550239025dffcfb37175acedd3": { + "balance": "0x0", + "code": "0x606060405260e060020a60003504631a69523081146100475780636dbf2fa01461006d5780638da5cb5b14610144578063a6f9dae114610156578063beabacc814610196575b005b610045600435600080548190819032600160a060020a0390811691161461022957610002565b60806020604435600481810135601f8101849004909302840160405260608381526100459482359460248035956064949391019190819083828082843750949650505050505050600054600160a060020a0390811633909116141561013f5782600160a060020a03168282604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156101255780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f1505050505b505050565b61021f600054600160a060020a031681565b61004560043560005433600160a060020a0390811691161415610193576000805473ffffffffffffffffffffffffffffffffffffffff1916821790555b50565b6100456004356024356044356000805433600160a060020a0390811691161415610343577fa9059cbb000000000000000000000000000000000000000000000000000000006060908152600160a060020a03808516606452608484905285929083169163a9059cbb9160a4916020916044908290876161da5a03f1156100025750505050505050565b6060908152602090f35b7f70a0823100000000000000000000000000000000000000000000000000000000606090815230600160a060020a039081166064528594508416906370a082319060849060209060248187876161da5a03f1156100025750506040805180517f18160ddd00000000000000000000000000000000000000000000000000000000825291519194506318160ddd916004828101926020929190829003018187876161da5a03f11561000257505050604051805190602001509050808211156102ee579050805b82600160a060020a031663a9059cbb33846040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050505b5050505056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000e6002189a74b43e6868b20c1311bc108e38aac57" + } + }, + "0xbb9bc244d798123fde783fcc1c72d3bb8c189413": { + "balance": "0x53d2c8df046dd3db5", + "code": "0x6060604052361561020e5760e060020a6000350463013cf08b8114610247578063095ea7b3146102d05780630c3b7b96146103455780630e7082031461034e578063149acf9a1461036057806318160ddd146103725780631f2dc5ef1461037b57806321b5b8dd1461039b578063237e9492146103ad57806323b872dd1461040e5780632632bf2014610441578063341458081461047257806339d1f9081461047b5780634b6753bc146104935780634df6d6cc1461049c5780634e10c3ee146104b7578063590e1ae3146104ca578063612e45a3146104db578063643f7cdd1461057a578063674ed066146105925780636837ff1e1461059b57806370a08231146105e5578063749f98891461060b57806378524b2e1461062457806381f03fcb1461067e57806382661dc41461069657806382bf6464146106b75780638b15a605146106c95780638d7af473146106d257806396d7f3f5146106e1578063a1da2fb9146106ea578063a3912ec814610704578063a9059cbb1461070f578063b7bc2c841461073f578063baac53001461074b578063be7c29c1146107b1578063c9d27afe14610817578063cc9ae3f61461082d578063cdef91d014610841578063dbde198814610859578063dd62ed3e1461087e578063e33734fd146108b2578063e5962195146108c6578063e66f53b7146108de578063eceb2945146108f0578063f8c80d261461094f575b610966600f546000906234bc000142108015610239575060125433600160a060020a03908116911614155b156109785761098033610752565b6109866004356000805482908110156100025750808052600e8202600080516020612a3683398151915201905060038101546004820154600683015460018401548454600786015460058701546009880154600a890154600d8a0154600160a060020a039586169b509599600201989760ff81811698610100909204811697949691951693168c565b61096660043560243533600160a060020a03908116600081815260156020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b61096660105481565b610a7d600754600160a060020a031681565b610a7d600e54600160a060020a031681565b61096660165481565b6109665b60004262127500600f60005054031115610de557506014610983565b610a7d601254600160a060020a031681565b60408051602060248035600481810135601f810185900485028601850190965285855261096695813595919460449492939092019181908401838280828437509496505050505050506000600060006000600060003411156116a857610002565b6109666004356024356044355b60115460009060ff1680156104315750600f5442115b80156124e957506124e78461044b565b6109666000610980335b600160a060020a0381166000908152600b602052604081205481908114156129cb57610b99565b61096660065481565b6109665b600d5430600160a060020a03163103610983565b610966600f5481565b61096660043560046020526000908152604090205460ff1681565b61096660043560243560006124cb610831565b610a9a6000341115610ba457610002565b604080516020604435600481810135601f8101849004840285018401909552848452610966948135946024803595939460649492939101918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897608497919650602491909101945090925082915084018382808284375094965050933593505060a435915050600060006110c1336105ec565b61096660043560096020526000908152604090205481565b61096660015481565b610a9a60043530600160a060020a031633600160a060020a03161415806105db5750600160a060020a03811660009081526004602052604090205460ff16155b156121cb576121c8565b6109666004355b600160a060020a0381166000908152601460205260409020545b919050565b6109666004356024356000600034111561259957610002565b610966600062e6b680420360026000505410806106505750600354600160a060020a0390811633909116145b80156106645750600254621274ff19420190105b156126145750426002908155600180549091028155610983565b610966600435600a6020526000908152604090205481565b610966600435602435600060006000600060006000341115611ba157610002565b610a7d600854600160a060020a031681565b610966600c5481565b61096660005460001901610983565b61096660025481565b61096660043560006000600060003411156121fc57610002565b6109665b6001610983565b6109666004356024355b60115460009060ff16801561072f5750600f5442115b801561248757506124853361044b565b61096660115460ff1681565b6109666004355b60006000600f600050544210801561076a5750600034115b80156107a457506011546101009004600160a060020a0316600014806107a457506011546101009004600160a060020a0390811633909116145b15610b9f57610a9c61037f565b610a7d600435600060006000508281548110156100025750508080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56b600e83020180548290811015610002575081526020902060030154600160a060020a0316610606565b61096660043560243560006000610e1b336105ec565b6109665b6000600034111561247c57610002565b61096660043560056020526000908152604090205481565b610966600435602435604435600061252f845b6000600060003411156127ac57610002565b610966600435602435600160a060020a0382811660009081526015602090815260408083209385168352929052205461033f565b610a9a600435600034111561254557610002565b610966600435600b6020526000908152604090205481565b610a7d600354600160a060020a031681565b604080516020606435600481810135601f81018490048402850184019095528484526109669481359460248035956044359560849492019190819084018382808284375094965050505050505060006000600034111561103257610002565b610a7d6011546101009004600160a060020a031681565b60408051918252519081900360200190f35b610980610708565b90505b90565b604051808d600160a060020a031681526020018c8152602001806020018b81526020018a815260200189815260200188815260200187815260200186815260200185815260200184815260200183600160a060020a0316815260200182810382528c818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610a635780601f10610a3857610100808354040283529160200191610a63565b820191906000526020600020905b815481529060010190602001808311610a4657829003601f168201915b50509d505050505050505050505050505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b005b604051601254601434908102939093049350600160a060020a03169183900390600081818185876185025a03f150505050600160a060020a038316600081815260146020908152604080832080548601905560168054860190556013825291829020805434019055815184815291517fdbccb92686efceafb9bb7e0394df7f58f71b954061b81afb57109bf247d3d75a9281900390910190a260105460165410801590610b4c575060115460ff16155b15610b94576011805460ff1916600117905560165460408051918252517ff381a3e2428fdda36615919e8d9c35878d9eb0cf85ac6edf575088e80e4c147e9181900360200190a15b600191505b50919050565b610002565b600f5442118015610bb8575060115460ff16155b15610de357601260009054906101000a9004600160a060020a0316600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040516012549051600160a060020a039190911631109050610cc9576040805160125460e060020a63d2cc718f0282529151600160a060020a039290921691630221038a913091849163d2cc718f91600482810192602092919082900301816000876161da5a03f11561000257505060408051805160e160020a63011081c5028252600160a060020a039490941660048201526024810193909352516044838101936020935082900301816000876161da5a03f115610002575050505b33600160a060020a0316600081815260136020526040808220549051909181818185876185025a03f19250505015610de35733600160a060020a03167fbb28353e4598c3b9199101a66e0989549b659a59a54d2c27fbb183f1932c8e6d6013600050600033600160a060020a03168152602001908152602001600020600050546040518082815260200191505060405180910390a26014600050600033600160a060020a0316815260200190815260200160002060005054601660008282825054039250508190555060006014600050600033600160a060020a031681526020019081526020016000206000508190555060006013600050600033600160a060020a03168152602001908152602001600020600050819055505b565b4262054600600f60005054031115610e13576201518062127500600f60005054034203046014019050610983565b50601e610983565b60001415610e2857610002565b6000341115610e3657610002565b6000805485908110156100025750600160a060020a03331681527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56e600e8602908101602052604090912054600080516020612a3683398151915291909101915060ff1680610eb05750600c810160205260406000205460ff165b80610ebf575060038101544210155b15610ec957610002565b8215610f0f5733600160a060020a03166000908152601460209081526040808320546009850180549091019055600b84019091529020805460ff19166001179055610f4b565b33600160a060020a0316600090815260146020908152604080832054600a850180549091019055600c84019091529020805460ff191660011790555b33600160a060020a03166000908152600b60205260408120541415610f77576040600020849055610feb565b33600160a060020a03166000908152600b60205260408120548154811015610002579080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566600e909102015460038201541115610feb5733600160a060020a03166000908152600b602052604090208490555b60408051848152905133600160a060020a03169186917f86abfce99b7dd908bec0169288797f85049ec73cbe046ed9de818fab3a497ae09181900360200190a35092915050565b6000805487908110156100025750808052600e8702600080516020612a3683398151915201905090508484846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020816005016000505414915050949350505050565b600014156110ce57610002565b82801561111857508660001415806110e857508451600014155b806111005750600354600160a060020a038981169116145b8061110b5750600034115b80611118575062093a8084105b1561112257610002565b8215801561114257506111348861115c565b158061114257506212750084105b156111fe57610002565b83546118e590600160a060020a03165b600160a060020a03811660009081526004602052604081205460ff16806111f15750601254600160a060020a039081169083161480156111f15750601260009054906101000a9004600160a060020a0316600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051516006541190505b156129a157506001610606565b6249d40084111561120e57610002565b60115460ff1615806112215750600f5442105b806112365750600c5434108015611236575082155b1561124057610002565b42844201101561124f57610002565b30600160a060020a031633600160a060020a0316141561126e57610002565b60008054600181018083559091908280158290116112a557600e0281600e0283600052602060002091820191016112a5919061136a565b505060008054929450918491508110156100025750808052600e8302600080516020612a368339815191520190508054600160a060020a031916891781556001818101899055875160028084018054600082815260209081902096975091959481161561010002600019011691909104601f908101829004840193918b019083901061146257805160ff19168380011785555b5061149292915061144a565b5050600060098201819055600a820155600d81018054600160a060020a03191690556001015b8082111561145e578054600160a060020a03191681556000600182810182905560028084018054848255909281161561010002600019011604601f81901061143057505b506000600383018190556004808401805461ffff19169055600584018290556006840182905560078401805460ff191690556008840180548382559083526020909220611344929091028101905b8082111561145e57600080825560018201818155600283019190915560039091018054600160a060020a03191690556113fc565b601f0160209004906000526020600020908101906113ae91905b8082111561145e576000815560010161144a565b5090565b82800160010185558215611338579182015b82811115611338578251826000505591602001919060010190611474565b50508787866040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160050160005081905550834201816003016000508190555060018160040160006101000a81548160ff02191690830217905550828160070160006101000a81548160ff02191690830217905550821561157857600881018054600181018083559091908280158290116115735760040281600402836000526020600020918201910161157391906113fc565b505050505b600d8082018054600160a060020a031916331790553460068301819055815401905560408051600160a060020a038a16815260208181018a9052918101859052608060608201818152895191830191909152885185937f5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f938d938d938a938e93929160a084019185810191908190849082908590600090600490601f850104600f02600301f150905090810190601f1680156116485780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a2509695505050505050565b6040805186815260208101839052815189927fdfc78bdca8e3e0b18c16c5c99323c6cb9eb5e00afde190b4e7273f5158702b07928290030190a25b5050505092915050565b6000805488908110156100025750808052600e8802600080516020612a36833981519152019050600781015490945060ff166116e757620d2f006116ec565b622398805b600485015490935060ff16801561170857506003840154830142115b15611716576117b887611890565b600384015442108061172d5750600484015460ff16155b806117ae57508360000160009054906101000a9004600160a060020a03168460010160005054876040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020846005016000505414155b1561114c57610002565b61169e565b60048401805461ff001916610100179055835460019550600160a060020a03908116309091161480159061180157508354600754600160a060020a03908116911614155b801561181d57506008548454600160a060020a03908116911614155b801561183957508354601254600160a060020a03908116911614155b801561185557506003548454600160a060020a03908116911614155b1561188b5760018401805430600160a060020a031660009081526005602052604090208054919091019055546006805490910190555b611663875b6000600060005082815481101561000257908052600e02600080516020612a36833981519152018150600481015490915060ff16156118d757600d80546006830154900390555b600401805460ff1916905550565b15156118f45761190087611890565b6001915061193161047f565b604051600d8501546006860154600160a060020a0391909116916000919082818181858883f193505050505061169e565b6001850154111561194157600091505b50600a8301546009840154865191019060049010801590611986575085600081518110156100025790602001015160f860020a900460f860020a02606860f860020a02145b80156119b6575085600181518110156100025790602001015160f860020a900460f860020a02603760f860020a02145b80156119e6575085600281518110156100025790602001015160f860020a900460f860020a0260ff60f860020a02145b8015611a16575085600381518110156100025790602001015160f860020a900460f860020a02601e60f860020a02145b8015611a45575030600160a060020a0316600090815260056020526040902054611a4290611a5d61047f565b81105b15611a4f57600091505b6001840154611a8090611a5f565b015b30600160a060020a03166000908152600560205260408120546129a961047f565b8110611ad457604051600d8501546006860154600160a060020a0391909116916000919082818181858883f193505050501515611abc57610002565b4260025560165460059004811115611ad45760056001555b6001840154611ae290611a5f565b8110158015611af85750600a8401546009850154115b8015611b015750815b1561188b578360000160009054906101000a9004600160a060020a0316600160a060020a0316846001016000505487604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611b7d5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f19250505015156117bd57610002565b611baa336105ec565b60001415611bb757610002565b60008054889081101561000257508052600e87027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566810154600080516020612a36833981519152919091019450421080611c1957506003840154622398800142115b80611c3257508354600160a060020a0390811690871614155b80611c425750600784015460ff16155b80611c68575033600160a060020a03166000908152600b8501602052604090205460ff16155b80611c9c575033600160a060020a03166000908152600b60205260409020548714801590611c9c5750604060009081205414155b15611ca657610002565b600884018054600090811015610002579081526020812060030154600160a060020a03161415611e1257611efc86604051600090600160a060020a038316907f9046fefd66f538ab35263248a44217dcb70e2eb2cd136629e141b8b8f9f03b60908390a260408051600e547fe2faf044000000000000000000000000000000000000000000000000000000008252600160a060020a03858116600484015260248301859052604483018590526223988042016064840152925192169163e2faf04491608480820192602092909190829003018187876161da5a03f1156100025750506040515191506106069050565b6008850180546000908110156100025781815260208082209390935530600160a060020a031681526005909252604082205481549092908110156100025790815260208120905060020155601654600885018054600090811015610002579081526020812090506001015560048401805461ff0019166101001790555b6008840180546000908110156100025781548282526020822060010154929190811015610002579081526020812090505433600160a060020a031660009081526014602052604081205460088801805493909102939093049550908110156100025790815260208120905060030154604080517fbaac530000000000000000000000000000000000000000000000000000000000815233600160a060020a0390811660048301529151929091169163baac53009186916024808301926020929190829003018185886185025a03f11561000257505060405151600014159150611f78905057610002565b60088501805460009081101561000257818152602081206003018054600160a060020a03191690931790925580549091908110156100025790815260208120905060030154600160a060020a031660001415611f5757610002565b600d5430600160a060020a0316311015611f7057610002565b611d9561047f565b6008840180546000908110156100025781548282526020822060010154929190811015610002579081526020812090506002015433600160a060020a0390811660009081526014602090815260408083205430909416835260058083528184205460099093529083205460088b018054969095029690960497509487020494508593929091908290811015610002575260208120815060030154600160a060020a0390811682526020828101939093526040918201600090812080549095019094553016835260059091529020548290101561205357610002565b30600160a060020a031660009081526005602052604081208054849003905560088501805483926009929091829081101561000257508152602080822060030154600160a060020a039081168352929052604080822080549094019093553090911681522054819010156120c657610002565b30600160a060020a0390811660009081526009602090815260408083208054869003905533909316808352601482528383205484519081529351929390927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a36121383361086c565b5033600160a060020a03166000908152601460209081526040808320805460168054919091039055839055600a9091528120556001945061169e565b30600160a060020a0390811660008181526005602090815260408083208054958716808552828520805490970190965584845283905560099091528082208054948352908220805490940190935590815290555b50565b604051600160a060020a0382811691309091163190600081818185876185025a03f192505050151561217457610002565b33600160a060020a03818116600090815260096020908152604080832054815160065460085460e060020a63d2cc718f028352935197995091969195929092169363d2cc718f936004848101949193929183900301908290876161da5a03f11561000257505050604051805190602001506005600050600033600160a060020a03168152602001908152602001600020600050540204101561229d57610002565b600160a060020a03338116600090815260096020908152604080832054815160065460085460e060020a63d2cc718f02835293519296909593169363d2cc718f93600483810194929383900301908290876161da5a03f11561000257505050604051805190602001506005600050600033600160a060020a0316815260200190815260200160002060005054020403905083156123ec57600860009054906101000a9004600160a060020a0316600160a060020a0316630221038a83600160a060020a0316630e7082036040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63011081c5028252600160a060020a031660048201526024810186905290516044808301935060209282900301816000876161da5a03f115610002575050604051511515905061245457610002565b6040805160085460e160020a63011081c5028252600160a060020a038581166004840152602483018590529251921691630221038a9160448082019260209290919082900301816000876161da5a03f115610002575050604051511515905061245457610002565b600160a060020a03331660009081526009602052604090208054909101905550600192915050565b6109803361086c565b155b80156124a257506124a23384845b6000600061293a856105ec565b80156124be57506124be83836000600034111561261c57610002565b15610b9f5750600161033f565b15156124d657610002565b6124e08383610719565b905061033f565b155b80156124fb57506124fb848484612495565b80156125185750612518848484600060003411156126c157610002565b15610b9f57506001612528565b90505b9392505050565b151561253a57610002565b61252584848461041b565b30600160a060020a031633600160a060020a031614158061258a575030600160a060020a031660009081526005602052604090205460649061258561047f565b010481115b1561259457610002565b600c55565b600354600160a060020a0390811633909116146125b557610002565b600160a060020a038316600081815260046020908152604091829020805460ff191686179055815185815291517f73ad2a153c8b67991df9459024950b318a609782cee8c7eeda47b905f9baa91f9281900390910190a250600161033f565b506000610983565b33600160a060020a03166000908152601460205260409020548290108015906126455750600082115b156126b957600160a060020a03338116600081815260146020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a350600161033f565b50600061033f565b600160a060020a03841660009081526014602052604090205482901080159061270a5750601560209081526040600081812033600160a060020a03168252909252902054829010155b80156127165750600082115b156127a457600160a060020a03838116600081815260146020908152604080832080548801905588851680845281842080548990039055601583528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a3506001612528565b506000612528565b600160a060020a038381166000908152600a6020908152604080832054601654600754835160e060020a63d2cc718f02815293519296919591169363d2cc718f9360048181019492939183900301908290876161da5a03f11561000257505060405151905061281a866105ec565b0204101561282757610002565b600160a060020a038381166000908152600a6020908152604080832054601654600754835160e060020a63d2cc718f02815293519296919591169363d2cc718f9360048181019492939183900301908290876161da5a03f115610002575050604051519050612895866105ec565b0204039050600760009054906101000a9004600160a060020a0316600160a060020a0316630221038a84836040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511515905061291357610002565b600160a060020a0383166000908152600a6020526040902080548201905560019150610b99565b600160a060020a0386166000908152600a602052604090205480850291909104915081111561296857610002565b600160a060020a038581166000908152600a60205260408082208054859003905591861681522080548201905560019150509392505050565b506000610606565b0160030260166000505483020460016000505460166000505404019050610606565b600160a060020a0383166000908152600b6020526040812054815481101561000257818052600e02600080516020612a368339815191520190506003810154909150421115610b9457600160a060020a0383166000908152600b602052604081208190559150610b9956290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", + "nonce": "3", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000120", + "0x000000000000000000000000000000000000000000000000000000000000000f": "0x0000000000000000000000000000000000000000000000000000000057495e10", + "0x0000000000000000000000000000000000000000000000000000000000000011": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000016": "0x000000000000000000000000000000000000000000098b4d3b425f8c368391b2", + "0x29066f14bd0b438bb3db8771a65febf0be7574be7528f87e7ae11aafc2b2c3ac": "0x000000000000000000000000000000000000000000000025d57ab057892050fc", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f443": "0x000000000000000000000000b3b10eff47b9c0b3e5579bf1c25872111667e650", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f444": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f445": "0x0000000000000000000000000000000000000000000000000000000000000093", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f446": "0x00000000000000000000000000000000000000000000000000000000579a07ea", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f447": "0x0000000000000000000000000000000000000000000000000000000000000101", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f448": "0x63c103e1feea47a9bf6c0dce1349da1a95b96532661d43063ab8e52b3e2a844b", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f449": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f44a": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f44b": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f44c": "0x00000000000000000000000000000000000000000000000001620725a3de2009", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f44d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3f450": "0x000000000000000000000000b3b10eff47b9c0b3e5579bf1c25872111667e650", + "0x3987ba2457a57cc6778cce06d8c58970029977d834f0de345c7a495612cbb060": "0x00000000000000000000000000000000000000000000081f2acc2a62590de041", + "0x3987ba2457a57cc6778cce06d8c58970029977d834f0de345c7a495612cbb061": "0x000000000000000000000000000000000000000000098b4d3b425f8c368391b2", + "0x3987ba2457a57cc6778cce06d8c58970029977d834f0de345c7a495612cbb062": "0x00000000000000000000000000000000000000000000003635c9adc5dea00000", + "0x3987ba2457a57cc6778cce06d8c58970029977d834f0de345c7a495612cbb063": "0x00000000000000000000000010abb5efecdc09581f8b7cb95791fe2936790b4e", + "0x6f125332c6f598e8798f0c277f4b1052ac80cd02ff2eebe0c7f362d63b6959ef": "0x000000000000000000000000000000000000000000000000008dc9007b27b5a9", + "0x793bebaf0ea12c858c08547e9aa88b849bba94bb6933c7bdb0fecbb707ecf5c7": "0x00000000000000000000000000000000000000000000076d52eebfbfbfc172e5", + "0xaccfa2662c944e8eae80b7720d9d232eb6809c18f6c8da65189acbb38069d869": "0x000000000000000000000000000000000000000000000000000289739e60e3e2", + "0xb6e4d5c52e0c64fb49c5a97cacdbcf8bd94b5bd4d490590326a19d27eaf543ae": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xbe273e24e8bd646e29d1fb5a924a12a8585095b9f45a317fc708165a127fdd70": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xc34fc4bc1a730d3f836c9ac5124865056e88f3776b63662e34976bdb47549077": "0x000000000000000000000000000000000000000000000036353be4c563784a57", + "0xe2112d92b8a1d00a569b85fbe7a384a5c9f74f5ff8478647397cb58dde254ffa": "0x53706c697420666f722070656f706c652077686f2073656e74206d6f6e657920", + "0xe2112d92b8a1d00a569b85fbe7a384a5c9f74f5ff8478647397cb58dde254ffb": "0x746f207468652044414f20616674657220746865204861726420466f726b2062", + "0xe2112d92b8a1d00a569b85fbe7a384a5c9f74f5ff8478647397cb58dde254ffc": "0x79206d697374616b650000000000000000000000000000000000000000000000", + "0xf60322aa1a2e769d412b36e4a9def4300f7540bf1bc9e0f4691786a9100145fa": "0x0000000000000000000000000000000000000000000000000000000062188dd2", + "0xf735edeea40e4ec771f49da7f7b854b398a1ad43f8a9617d43e53d3093e9fdc0": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xf7905fa5d54027d5d59f4678dda481331babad2d3d0fdefd552afbce2e74c07e": "0x0000000000000000000000000000000000000000000000000000000000000110" + } + }, + "0xe6002189a74b43e6868b20c1311bc108e38aac57": { + "balance": "0x29129264d1ae4848b", + "nonce": "45" + }, + "0xea674fdde714fd979de3edf0f56aa9716b898ec8": { + "balance": "0x1601bbe4c58ec73210", + "nonce": "337736" + }, + "0xfde8d5f77ef48bb7bf5766c7404691b9ee1dfca7": { + "balance": "0x0", + "code": "0x606060405236156100405760e060020a60003504630221038a811461004d57806318bdc79a146100aa5780638da5cb5b146100be578063d2cc718f146100d0575b6100d96001805434019055565b6100db6004356024356000805433600160a060020a0390811691161415806100755750600034115b806100a05750805460a060020a900460ff1680156100a057508054600160a060020a03848116911614155b156100f757610002565b6100db60005460ff60a060020a9091041681565b6100ed600054600160a060020a031681565b6100db60015481565b005b60408051918252519081900360200190f35b6060908152602090f35b600160a060020a0383168260608381818185876185025a03f1925050501561015c57604080518381529051600160a060020a038516917f9735b0cb909f3d21d5c16bbcccd272d85fa11446f6d679f6ecb170d2dabfecfc919081900360200190a25060015b9291505056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "1968180", + "difficulty": "56311715252709", + "timestamp": "1469713694", + "gasLimit": "4712388", + "miner": "0xea674fdde714fd979de3edf0f56aa9716b898ec8" + }, + "input": "0xf8aa2d850c2b6f9f7e830aae6094630a0cd35d5bd57e61410fda76fea850225cda1880b844e1fa7638000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000000001ba0563f81ca66b2c618bf4be9470fab88fff1b44eb5c33a9c73a68e8b26fbaa7c8da041464789c49fee77d2e053ff0705bc845fe2a78a35e478132371f294bb594021", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0xe6002189a74b43e6868b20c1311bc108e38aac57", + "gas": "0xaae60", + "gasUsed": "0xaae60", + "to": "0x630a0cd35d5bd57e61410fda76fea850225cda18", + "input": "0xe1fa763800000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000000", + "error": "invalid jump destination", + "calls": [ + { + "from": "0x630a0cd35d5bd57e61410fda76fea850225cda18", + "gas": "0x9f5a0", + "gasUsed": "0x314", + "to": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "input": "0x70a08231000000000000000000000000630a0cd35d5bd57e61410fda76fea850225cda18", + "output": "0x000000000000000000000000000000000000000000000000000289739e60e3e2", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x630a0cd35d5bd57e61410fda76fea850225cda18", + "gas": "0x9a327", + "gasUsed": "0x67b0", + "to": "0x6e073c0e1bd5af550239025dffcfb37175acedd3", + "input": "0x1a695230000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c189413", + "calls": [ + { + "from": "0x6e073c0e1bd5af550239025dffcfb37175acedd3", + "gas": "0x93ff6", + "gasUsed": "0x314", + "to": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "input": "0x70a082310000000000000000000000006e073c0e1bd5af550239025dffcfb37175acedd3", + "output": "0x000000000000000000000000000000000000000000000025d57ab057892050fc", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e073c0e1bd5af550239025dffcfb37175acedd3", + "gas": "0x93c42", + "gasUsed": "0x13f", + "to": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "input": "0x18160ddd", + "output": "0x000000000000000000000000000000000000000000098b4d3b425f8c368391b2", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x6e073c0e1bd5af550239025dffcfb37175acedd3", + "gas": "0x939ba", + "gasUsed": "0x5fca", + "to": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "input": "0xa9059cbb000000000000000000000000630a0cd35d5bd57e61410fda76fea850225cda18000000000000000000000000000000000000000000000025d57ab057892050fc", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x630a0cd35d5bd57e61410fda76fea850225cda18", + "gas": "0x8d8b6", + "gasUsed": "0x7be", + "to": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "input": "0x013cf08b0000000000000000000000000000000000000000000000000000000000000110", + "output": "0x000000000000000000000000b3b10eff47b9c0b3e5579bf1c25872111667e6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000579a07ea0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000163c103e1feea47a9bf6c0dce1349da1a95b96532661d43063ab8e52b3e2a844b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000001620725a3de20090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b3b10eff47b9c0b3e5579bf1c25872111667e650000000000000000000000000000000000000000000000000000000000000004953706c697420666f722070656f706c652077686f2073656e74206d6f6e657920746f207468652044414f20616674657220746865204861726420466f726b206279206d697374616b650000000000000000000000000000000000000000000000", + "value": "0x0", + "type": "CALL" + }, + { + "from": "0x630a0cd35d5bd57e61410fda76fea850225cda18", + "gas": "0x880f8", + "gasUsed": "0x880f8", + "to": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "input": "0x82661dc40000000000000000000000000000000000000000000000000000000000000110000000000000000000000000b3b10eff47b9c0b3e5579bf1c25872111667e650", + "error": "invalid jump destination", + "calls": [ + { + "from": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "gas": "0x7f910", + "gasUsed": "0xd20f", + "to": "0x10abb5efecdc09581f8b7cb95791fe2936790b4e", + "input": "0xbaac5300000000000000000000000000630a0cd35d5bd57e61410fda76fea850225cda18", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001", + "calls": [ + { + "from": "0x10abb5efecdc09581f8b7cb95791fe2936790b4e", + "gas": "0x76e12", + "gasUsed": "0x13f9", + "to": "0xfde8d5f77ef48bb7bf5766c7404691b9ee1dfca7", + "input": "0x", + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x20320625e3126cb0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + } + ], + "value": "0x0", + "type": "CALL" + } +} 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 new file mode 100644 index 0000000..6faf898 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_partial_failed.json @@ -0,0 +1,108 @@ +{ + "genesis": { + "difficulty": "45372803248884", + "extraData": "0x65746865726d696e652e6f7267202855533129", + "gasLimit": "4712388", + "hash": "0xa2b18cc64ec062676680f2bb2d880205dcd372f4396722f2294d3fceece96193", + "miner": "0xea674fdde714fd979de3edf0f56aa9716b898ec8", + "mixHash": "0xce7c26a9238b249edcdcd51f0ea1ad0e632e872daf9a09f039d918bcaeb7194f", + "nonce": "0x849d49e634e93bb5", + "number": "1646451", + "stateRoot": "0x2bd193b9911caf43204960cc7661ce864bf0bac7f9b60191aa02bbff24f061fb", + "timestamp": "1465103859", + "totalDifficulty": "24813742796574158431", + "alloc": { + "0x01115b41bd2731353dd3e6abf44818fdc035aaf1": { + "balance": "0x16d99e16e809000", + "nonce": "23" + }, + "0x61c808d82a3ac53231750dadc13c777b59310bd9": { + "balance": "0x6a636960e34bd696f4", + "nonce": "36888" + }, + "0xbb9bc244d798123fde783fcc1c72d3bb8c189413": { + "balance": "0x9b37460cdbcba74181f81", + "code": "0x6060604052361561020e5760e060020a6000350463013cf08b8114610247578063095ea7b3146102d05780630c3b7b96146103455780630e7082031461034e578063149acf9a1461036057806318160ddd146103725780631f2dc5ef1461037b57806321b5b8dd1461039b578063237e9492146103ad57806323b872dd1461040e5780632632bf2014610441578063341458081461047257806339d1f9081461047b5780634b6753bc146104935780634df6d6cc1461049c5780634e10c3ee146104b7578063590e1ae3146104ca578063612e45a3146104db578063643f7cdd1461057a578063674ed066146105925780636837ff1e1461059b57806370a08231146105e5578063749f98891461060b57806378524b2e1461062457806381f03fcb1461067e57806382661dc41461069657806382bf6464146106b75780638b15a605146106c95780638d7af473146106d257806396d7f3f5146106e1578063a1da2fb9146106ea578063a3912ec814610704578063a9059cbb1461070f578063b7bc2c841461073f578063baac53001461074b578063be7c29c1146107b1578063c9d27afe14610817578063cc9ae3f61461082d578063cdef91d014610841578063dbde198814610859578063dd62ed3e1461087e578063e33734fd146108b2578063e5962195146108c6578063e66f53b7146108de578063eceb2945146108f0578063f8c80d261461094f575b610966600f546000906234bc000142108015610239575060125433600160a060020a03908116911614155b156109785761098033610752565b6109866004356000805482908110156100025750808052600e8202600080516020612a3683398151915201905060038101546004820154600683015460018401548454600786015460058701546009880154600a890154600d8a0154600160a060020a039586169b509599600201989760ff81811698610100909204811697949691951693168c565b61096660043560243533600160a060020a03908116600081815260156020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b61096660105481565b610a7d600754600160a060020a031681565b610a7d600e54600160a060020a031681565b61096660165481565b6109665b60004262127500600f60005054031115610de557506014610983565b610a7d601254600160a060020a031681565b60408051602060248035600481810135601f810185900485028601850190965285855261096695813595919460449492939092019181908401838280828437509496505050505050506000600060006000600060003411156116a857610002565b6109666004356024356044355b60115460009060ff1680156104315750600f5442115b80156124e957506124e78461044b565b6109666000610980335b600160a060020a0381166000908152600b602052604081205481908114156129cb57610b99565b61096660065481565b6109665b600d5430600160a060020a03163103610983565b610966600f5481565b61096660043560046020526000908152604090205460ff1681565b61096660043560243560006124cb610831565b610a9a6000341115610ba457610002565b604080516020604435600481810135601f8101849004840285018401909552848452610966948135946024803595939460649492939101918190840183828082843750506040805160209735808a0135601f81018a90048a0283018a01909352828252969897608497919650602491909101945090925082915084018382808284375094965050933593505060a435915050600060006110c1336105ec565b61096660043560096020526000908152604090205481565b61096660015481565b610a9a60043530600160a060020a031633600160a060020a03161415806105db5750600160a060020a03811660009081526004602052604090205460ff16155b156121cb576121c8565b6109666004355b600160a060020a0381166000908152601460205260409020545b919050565b6109666004356024356000600034111561259957610002565b610966600062e6b680420360026000505410806106505750600354600160a060020a0390811633909116145b80156106645750600254621274ff19420190105b156126145750426002908155600180549091028155610983565b610966600435600a6020526000908152604090205481565b610966600435602435600060006000600060006000341115611ba157610002565b610a7d600854600160a060020a031681565b610966600c5481565b61096660005460001901610983565b61096660025481565b61096660043560006000600060003411156121fc57610002565b6109665b6001610983565b6109666004356024355b60115460009060ff16801561072f5750600f5442115b801561248757506124853361044b565b61096660115460ff1681565b6109666004355b60006000600f600050544210801561076a5750600034115b80156107a457506011546101009004600160a060020a0316600014806107a457506011546101009004600160a060020a0390811633909116145b15610b9f57610a9c61037f565b610a7d600435600060006000508281548110156100025750508080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56b600e83020180548290811015610002575081526020902060030154600160a060020a0316610606565b61096660043560243560006000610e1b336105ec565b6109665b6000600034111561247c57610002565b61096660043560056020526000908152604090205481565b610966600435602435604435600061252f845b6000600060003411156127ac57610002565b610966600435602435600160a060020a0382811660009081526015602090815260408083209385168352929052205461033f565b610a9a600435600034111561254557610002565b610966600435600b6020526000908152604090205481565b610a7d600354600160a060020a031681565b604080516020606435600481810135601f81018490048402850184019095528484526109669481359460248035956044359560849492019190819084018382808284375094965050505050505060006000600034111561103257610002565b610a7d6011546101009004600160a060020a031681565b60408051918252519081900360200190f35b610980610708565b90505b90565b604051808d600160a060020a031681526020018c8152602001806020018b81526020018a815260200189815260200188815260200187815260200186815260200185815260200184815260200183600160a060020a0316815260200182810382528c818154600181600116156101000203166002900481526020019150805460018160011615610100020316600290048015610a635780601f10610a3857610100808354040283529160200191610a63565b820191906000526020600020905b815481529060010190602001808311610a4657829003601f168201915b50509d505050505050505050505050505060405180910390f35b60408051600160a060020a03929092168252519081900360200190f35b005b604051601254601434908102939093049350600160a060020a03169183900390600081818185876185025a03f150505050600160a060020a038316600081815260146020908152604080832080548601905560168054860190556013825291829020805434019055815184815291517fdbccb92686efceafb9bb7e0394df7f58f71b954061b81afb57109bf247d3d75a9281900390910190a260105460165410801590610b4c575060115460ff16155b15610b94576011805460ff1916600117905560165460408051918252517ff381a3e2428fdda36615919e8d9c35878d9eb0cf85ac6edf575088e80e4c147e9181900360200190a15b600191505b50919050565b610002565b600f5442118015610bb8575060115460ff16155b15610de357601260009054906101000a9004600160a060020a0316600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040516012549051600160a060020a039190911631109050610cc9576040805160125460e060020a63d2cc718f0282529151600160a060020a039290921691630221038a913091849163d2cc718f91600482810192602092919082900301816000876161da5a03f11561000257505060408051805160e160020a63011081c5028252600160a060020a039490941660048201526024810193909352516044838101936020935082900301816000876161da5a03f115610002575050505b33600160a060020a0316600081815260136020526040808220549051909181818185876185025a03f19250505015610de35733600160a060020a03167fbb28353e4598c3b9199101a66e0989549b659a59a54d2c27fbb183f1932c8e6d6013600050600033600160a060020a03168152602001908152602001600020600050546040518082815260200191505060405180910390a26014600050600033600160a060020a0316815260200190815260200160002060005054601660008282825054039250508190555060006014600050600033600160a060020a031681526020019081526020016000206000508190555060006013600050600033600160a060020a03168152602001908152602001600020600050819055505b565b4262054600600f60005054031115610e13576201518062127500600f60005054034203046014019050610983565b50601e610983565b60001415610e2857610002565b6000341115610e3657610002565b6000805485908110156100025750600160a060020a03331681527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56e600e8602908101602052604090912054600080516020612a3683398151915291909101915060ff1680610eb05750600c810160205260406000205460ff165b80610ebf575060038101544210155b15610ec957610002565b8215610f0f5733600160a060020a03166000908152601460209081526040808320546009850180549091019055600b84019091529020805460ff19166001179055610f4b565b33600160a060020a0316600090815260146020908152604080832054600a850180549091019055600c84019091529020805460ff191660011790555b33600160a060020a03166000908152600b60205260408120541415610f77576040600020849055610feb565b33600160a060020a03166000908152600b60205260408120548154811015610002579080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566600e909102015460038201541115610feb5733600160a060020a03166000908152600b602052604090208490555b60408051848152905133600160a060020a03169186917f86abfce99b7dd908bec0169288797f85049ec73cbe046ed9de818fab3a497ae09181900360200190a35092915050565b6000805487908110156100025750808052600e8702600080516020612a3683398151915201905090508484846040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020816005016000505414915050949350505050565b600014156110ce57610002565b82801561111857508660001415806110e857508451600014155b806111005750600354600160a060020a038981169116145b8061110b5750600034115b80611118575062093a8084105b1561112257610002565b8215801561114257506111348861115c565b158061114257506212750084105b156111fe57610002565b83546118e590600160a060020a03165b600160a060020a03811660009081526004602052604081205460ff16806111f15750601254600160a060020a039081169083161480156111f15750601260009054906101000a9004600160a060020a0316600160a060020a031663d2cc718f6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610002575050604051516006541190505b156129a157506001610606565b6249d40084111561120e57610002565b60115460ff1615806112215750600f5442105b806112365750600c5434108015611236575082155b1561124057610002565b42844201101561124f57610002565b30600160a060020a031633600160a060020a0316141561126e57610002565b60008054600181018083559091908280158290116112a557600e0281600e0283600052602060002091820191016112a5919061136a565b505060008054929450918491508110156100025750808052600e8302600080516020612a368339815191520190508054600160a060020a031916891781556001818101899055875160028084018054600082815260209081902096975091959481161561010002600019011691909104601f908101829004840193918b019083901061146257805160ff19168380011785555b5061149292915061144a565b5050600060098201819055600a820155600d81018054600160a060020a03191690556001015b8082111561145e578054600160a060020a03191681556000600182810182905560028084018054848255909281161561010002600019011604601f81901061143057505b506000600383018190556004808401805461ffff19169055600584018290556006840182905560078401805460ff191690556008840180548382559083526020909220611344929091028101905b8082111561145e57600080825560018201818155600283019190915560039091018054600160a060020a03191690556113fc565b601f0160209004906000526020600020908101906113ae91905b8082111561145e576000815560010161144a565b5090565b82800160010185558215611338579182015b82811115611338578251826000505591602001919060010190611474565b50508787866040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f150905001935050505060405180910390208160050160005081905550834201816003016000508190555060018160040160006101000a81548160ff02191690830217905550828160070160006101000a81548160ff02191690830217905550821561157857600881018054600181018083559091908280158290116115735760040281600402836000526020600020918201910161157391906113fc565b505050505b600d8082018054600160a060020a031916331790553460068301819055815401905560408051600160a060020a038a16815260208181018a9052918101859052608060608201818152895191830191909152885185937f5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f938d938d938a938e93929160a084019185810191908190849082908590600090600490601f850104600f02600301f150905090810190601f1680156116485780820380516001836020036101000a031916815260200191505b509550505050505060405180910390a2509695505050505050565b6040805186815260208101839052815189927fdfc78bdca8e3e0b18c16c5c99323c6cb9eb5e00afde190b4e7273f5158702b07928290030190a25b5050505092915050565b6000805488908110156100025750808052600e8802600080516020612a36833981519152019050600781015490945060ff166116e757620d2f006116ec565b622398805b600485015490935060ff16801561170857506003840154830142115b15611716576117b887611890565b600384015442108061172d5750600484015460ff16155b806117ae57508360000160009054906101000a9004600160a060020a03168460010160005054876040518084600160a060020a0316606060020a0281526014018381526020018280519060200190808383829060006004602084601f0104600f02600301f15090500193505050506040518091039020846005016000505414155b1561114c57610002565b61169e565b60048401805461ff001916610100179055835460019550600160a060020a03908116309091161480159061180157508354600754600160a060020a03908116911614155b801561181d57506008548454600160a060020a03908116911614155b801561183957508354601254600160a060020a03908116911614155b801561185557506003548454600160a060020a03908116911614155b1561188b5760018401805430600160a060020a031660009081526005602052604090208054919091019055546006805490910190555b611663875b6000600060005082815481101561000257908052600e02600080516020612a36833981519152018150600481015490915060ff16156118d757600d80546006830154900390555b600401805460ff1916905550565b15156118f45761190087611890565b6001915061193161047f565b604051600d8501546006860154600160a060020a0391909116916000919082818181858883f193505050505061169e565b6001850154111561194157600091505b50600a8301546009840154865191019060049010801590611986575085600081518110156100025790602001015160f860020a900460f860020a02606860f860020a02145b80156119b6575085600181518110156100025790602001015160f860020a900460f860020a02603760f860020a02145b80156119e6575085600281518110156100025790602001015160f860020a900460f860020a0260ff60f860020a02145b8015611a16575085600381518110156100025790602001015160f860020a900460f860020a02601e60f860020a02145b8015611a45575030600160a060020a0316600090815260056020526040902054611a4290611a5d61047f565b81105b15611a4f57600091505b6001840154611a8090611a5f565b015b30600160a060020a03166000908152600560205260408120546129a961047f565b8110611ad457604051600d8501546006860154600160a060020a0391909116916000919082818181858883f193505050501515611abc57610002565b4260025560165460059004811115611ad45760056001555b6001840154611ae290611a5f565b8110158015611af85750600a8401546009850154115b8015611b015750815b1561188b578360000160009054906101000a9004600160a060020a0316600160a060020a0316846001016000505487604051808280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611b7d5780820380516001836020036101000a031916815260200191505b5091505060006040518083038185876185025a03f19250505015156117bd57610002565b611baa336105ec565b60001415611bb757610002565b60008054889081101561000257508052600e87027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566810154600080516020612a36833981519152919091019450421080611c1957506003840154622398800142115b80611c3257508354600160a060020a0390811690871614155b80611c425750600784015460ff16155b80611c68575033600160a060020a03166000908152600b8501602052604090205460ff16155b80611c9c575033600160a060020a03166000908152600b60205260409020548714801590611c9c5750604060009081205414155b15611ca657610002565b600884018054600090811015610002579081526020812060030154600160a060020a03161415611e1257611efc86604051600090600160a060020a038316907f9046fefd66f538ab35263248a44217dcb70e2eb2cd136629e141b8b8f9f03b60908390a260408051600e547fe2faf044000000000000000000000000000000000000000000000000000000008252600160a060020a03858116600484015260248301859052604483018590526223988042016064840152925192169163e2faf04491608480820192602092909190829003018187876161da5a03f1156100025750506040515191506106069050565b6008850180546000908110156100025781815260208082209390935530600160a060020a031681526005909252604082205481549092908110156100025790815260208120905060020155601654600885018054600090811015610002579081526020812090506001015560048401805461ff0019166101001790555b6008840180546000908110156100025781548282526020822060010154929190811015610002579081526020812090505433600160a060020a031660009081526014602052604081205460088801805493909102939093049550908110156100025790815260208120905060030154604080517fbaac530000000000000000000000000000000000000000000000000000000000815233600160a060020a0390811660048301529151929091169163baac53009186916024808301926020929190829003018185886185025a03f11561000257505060405151600014159150611f78905057610002565b60088501805460009081101561000257818152602081206003018054600160a060020a03191690931790925580549091908110156100025790815260208120905060030154600160a060020a031660001415611f5757610002565b600d5430600160a060020a0316311015611f7057610002565b611d9561047f565b6008840180546000908110156100025781548282526020822060010154929190811015610002579081526020812090506002015433600160a060020a0390811660009081526014602090815260408083205430909416835260058083528184205460099093529083205460088b018054969095029690960497509487020494508593929091908290811015610002575260208120815060030154600160a060020a0390811682526020828101939093526040918201600090812080549095019094553016835260059091529020548290101561205357610002565b30600160a060020a031660009081526005602052604081208054849003905560088501805483926009929091829081101561000257508152602080822060030154600160a060020a039081168352929052604080822080549094019093553090911681522054819010156120c657610002565b30600160a060020a0390811660009081526009602090815260408083208054869003905533909316808352601482528383205484519081529351929390927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a36121383361086c565b5033600160a060020a03166000908152601460209081526040808320805460168054919091039055839055600a9091528120556001945061169e565b30600160a060020a0390811660008181526005602090815260408083208054958716808552828520805490970190965584845283905560099091528082208054948352908220805490940190935590815290555b50565b604051600160a060020a0382811691309091163190600081818185876185025a03f192505050151561217457610002565b33600160a060020a03818116600090815260096020908152604080832054815160065460085460e060020a63d2cc718f028352935197995091969195929092169363d2cc718f936004848101949193929183900301908290876161da5a03f11561000257505050604051805190602001506005600050600033600160a060020a03168152602001908152602001600020600050540204101561229d57610002565b600160a060020a03338116600090815260096020908152604080832054815160065460085460e060020a63d2cc718f02835293519296909593169363d2cc718f93600483810194929383900301908290876161da5a03f11561000257505050604051805190602001506005600050600033600160a060020a0316815260200190815260200160002060005054020403905083156123ec57600860009054906101000a9004600160a060020a0316600160a060020a0316630221038a83600160a060020a0316630e7082036040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060408051805160e160020a63011081c5028252600160a060020a031660048201526024810186905290516044808301935060209282900301816000876161da5a03f115610002575050604051511515905061245457610002565b6040805160085460e160020a63011081c5028252600160a060020a038581166004840152602483018590529251921691630221038a9160448082019260209290919082900301816000876161da5a03f115610002575050604051511515905061245457610002565b600160a060020a03331660009081526009602052604090208054909101905550600192915050565b6109803361086c565b155b80156124a257506124a23384845b6000600061293a856105ec565b80156124be57506124be83836000600034111561261c57610002565b15610b9f5750600161033f565b15156124d657610002565b6124e08383610719565b905061033f565b155b80156124fb57506124fb848484612495565b80156125185750612518848484600060003411156126c157610002565b15610b9f57506001612528565b90505b9392505050565b151561253a57610002565b61252584848461041b565b30600160a060020a031633600160a060020a031614158061258a575030600160a060020a031660009081526005602052604090205460649061258561047f565b010481115b1561259457610002565b600c55565b600354600160a060020a0390811633909116146125b557610002565b600160a060020a038316600081815260046020908152604091829020805460ff191686179055815185815291517f73ad2a153c8b67991df9459024950b318a609782cee8c7eeda47b905f9baa91f9281900390910190a250600161033f565b506000610983565b33600160a060020a03166000908152601460205260409020548290108015906126455750600082115b156126b957600160a060020a03338116600081815260146020908152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a350600161033f565b50600061033f565b600160a060020a03841660009081526014602052604090205482901080159061270a5750601560209081526040600081812033600160a060020a03168252909252902054829010155b80156127165750600082115b156127a457600160a060020a03838116600081815260146020908152604080832080548801905588851680845281842080548990039055601583528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a3506001612528565b506000612528565b600160a060020a038381166000908152600a6020908152604080832054601654600754835160e060020a63d2cc718f02815293519296919591169363d2cc718f9360048181019492939183900301908290876161da5a03f11561000257505060405151905061281a866105ec565b0204101561282757610002565b600160a060020a038381166000908152600a6020908152604080832054601654600754835160e060020a63d2cc718f02815293519296919591169363d2cc718f9360048181019492939183900301908290876161da5a03f115610002575050604051519050612895866105ec565b0204039050600760009054906101000a9004600160a060020a0316600160a060020a0316630221038a84836040518360e060020a0281526004018083600160a060020a03168152602001828152602001925050506020604051808303816000876161da5a03f115610002575050604051511515905061291357610002565b600160a060020a0383166000908152600a6020526040902080548201905560019150610b99565b600160a060020a0386166000908152600a602052604090205480850291909104915081111561296857610002565b600160a060020a038581166000908152600a60205260408082208054859003905591861681522080548201905560019150509392505050565b506000610606565b0160030260166000505483020460016000505460166000505404019050610606565b600160a060020a0383166000908152600b6020526040812054815481101561000257818052600e02600080516020612a368339815191520190506003810154909150421115610b9457600160a060020a0383166000908152600b602052604081208190559150610b9956290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", + "nonce": "3", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000000f": "0x0000000000000000000000000000000000000000000000000000000057495e10", + "0x0000000000000000000000000000000000000000000000000000000000000012": "0x000000000000000000000000807640a13483f8ac783c557fcdf27be11ea4ac7a" + } + }, + "0xcf1476387d780169410d4e936d75a206fda2a68c": { + "balance": "0x15fd0ad66ea7000", + "code": "0x606060405236156100b95760e060020a6000350463173825d9811461010b5780632f54bf6e1461015f5780634123cb6b146101875780635c52c2f5146101905780637065cb48146101ba578063746c9171146101e7578063797af627146101f0578063b20d30a914610203578063b61d27f614610230578063b75c7dc614610251578063ba51a6df14610280578063c2cf7326146102ad578063cbf0b0c0146102eb578063f00d4b5d14610318578063f1736d861461034a575b61035460003411156101095760408051600160a060020a033316815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b565b610354600435600060003660405180838380828437820191505092505050604051809103902061064a815b600160a060020a03331660009081526101026020526040812054818082811415610c6657610dbf565b6103566004355b600160a060020a03811660009081526101026020526040812054115b919050565b61035660015481565b61035460003660405180838380828437820191505092505050604051809103902061078b81610136565b6103546004356000366040518083838082843782019150509250505060405180910390206105c681610136565b61035660005481565b6103566004355b600081610a2781610136565b61035460043560003660405180838380828437820191505092505050604051809103902061077f81610136565b6103566004803590602480359160443591820191013560006107aa33610166565b610354600435600160a060020a03331660009081526101026020526040812054908082811415610368576103e7565b61035460043560003660405180838380828437820191505092505050604051809103902061070881610136565b610356600435602435600082815261010360209081526040808320600160a060020a0385168452610102909252822054828181141561076157610776565b61035460043560003660405180838380828437820191505092505050604051809103902061079981610136565b610354600435602435600060003660405180838380828437820191505092505050604051809103902061047281610136565b6103566101055481565b005b60408051918252519081900360200190f35b50506000828152610103602052604081206001810154600284900a9290831611156103e75780546001828101805492909101835590839003905560408051600160a060020a03331681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b50505050565b600160a060020a03831660028361010081101561000257508301819055600160a060020a03851660008181526101026020908152604080832083905584835291829020869055815192835282019290925281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a1505b505050565b156103e75761048083610166565b1561048b575061046d565b600160a060020a0384166000908152610102602052604081205492508214156104b4575061046d565b6103ed5b6101045460005b81811015610f0b57610104805461010891600091849081101561000257600080516020610fd88339815191520154825250602091909152604081208054600160a060020a0319168155600181810183905560028281018054858255939493909281161561010002600019011604601f819010610f9057505b5050506001016104bf565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005055600154600160a060020a03831660008181526101026020908152604091829020939093558051918252517f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3929181900390910190a15b505b50565b156105c1576105d482610166565b156105df57506105c3565b6105e76104b8565b60015460fa90106105fa576105fa61060f565b60015460fa901061054257506105c3565b6106c75b60015b6001548110156105c3575b6001548110801561063d5750600281610100811015610002570154600014155b15610dc75760010161061d565b1561046d57600160a060020a03831660009081526101026020526040812054925082141561067857506105c1565b600160016000505403600060005054111561069357506105c1565b600060028361010081101561000257508301819055600160a060020a0384168152610102602052604081205561060b6104b8565b60408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b156105c15760015482111561071d57506105c3565b600082905561072a6104b8565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001820154600282900a9081166000141593505b50505092915050565b156105c1575061010555565b156105c35760006101065550565b156105c15781600160a060020a0316ff5b156109eb576107be846000610ea133610166565b1561087d577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00433858786866040518086600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284378201915050965050505050505060405180910390a184600160a060020a03168484846040518083838082843782019150509250505060006040518083038185876185025a03f150600093506109eb92505050565b6000364360405180848480828437820191505082815260200193505050506040518091039020905080506108b0816101f7565b1580156108d3575060008181526101086020526040812054600160a060020a0316145b156109eb5760008181526101086020908152604082208054600160a060020a0319168817815560018181018890556002918201805481865294849020909491821615610100026000190190911691909104601f9081019290920481019185919087908390106109f35760ff198135168380011785555b506109659291505b80821115610a235760008155600101610951565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328133868887876040518087815260200186600160a060020a0316815260200185815260200184600160a060020a03168152602001806020018281038252848482818152602001925080828437820191505097505050505050505060405180910390a15b949350505050565b82800160010185558215610949579182015b82811115610949578235826000505591602001919060010190610a05565b5090565b15610aaa5760008381526101086020526040812054600160a060020a031614610aaa5760408051600091909120805460018281015460029384018054600160a060020a0394909416959194909391928392859291811615610100026000190116048015610adb5780601f10610ab057610100808354040283529160200191610adb565b50919050565b820191906000526020600020905b815481529060010190602001808311610abe57829003601f168201915b505091505060006040518083038185876185025a03f1505050600084815261010860209081526040918290208054600180830154855133600160a060020a0381811683529682018c9052968101829052929094166060830181905260a06080840181815260029586018054948516156101000260001901909416959095049084018190527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a97508a95949193919060c083019084908015610bdd5780601f10610bb257610100808354040283529160200191610bdd565b820191906000526020600020905b815481529060010190602001808311610bc057829003601f168201915b5050965050505050505060405180910390a16000838152610108602052604081208054600160a060020a0319168155600181810183905560028281018054858255939493909281161561010002600019011604601f819010610c4857505b5050506001915050610182565b601f016020900490600052602060002090810190610c3b9190610951565b60008581526101036020526040812080549093501415610cee576000805483556001838101919091556101048054918201808255828015829011610cbd57818360005260206000209182019101610cbd9190610951565b50505060028301819055610104805487929081101561000257600091909152600080516020610fd883398151915201555b506001810154600283900a90811660001415610dbf5760408051600160a060020a03331681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610dac576000858152610103602052604090206002015461010480549091908110156100025760406000908120600080516020610fd8833981519152929092018190558082556001828101829055600292909201559450610dbf9050565b8154600019018255600182018054821790555b505050919050565b5b60018054118015610dea57506001546002906101008110156100025701546000145b15610dfe5760018054600019019055610dc8565b60015481108015610e215750600154600290610100811015610002570154600014155b8015610e3b57506002816101008110156100025701546000145b15610e9c57600154600290610100811015610002578101549082610100811015610002579090016000505580610102600060028361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b610612565b156101825761010754610eb75b62015180420490565b1115610ed057600061010655610ecb610eae565b610107555b6101065480830110801590610eed57506101055461010654830111155b15610f0357506101068054820190556001610182565b506000610182565b6105c16101045460005b81811015610fae5761010480548290811015610002576000918252600080516020610fd8833981519152015414610f8857610104805461010391600091849081101561000257600080516020610fd883398151915201548252506020919091526040812081815560018101829055600201555b600101610f15565b601f0160209004906000526020600020908101906105379190610951565b610104805460008083559190915261046d90600080516020610fd883398151915290810190610951564c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000105": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000106": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000107": "0x000000000000000000000000000000000000000000000000000000000000423d", + "0xcabd288dcb1ace4f49c34e8ac2d843772952b4226b3c832bdb4ac1ddca0f7c05": "0x0000000000000000000000000000000000000000000000000000000000000002" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "1646452", + "difficulty": "45328493887096", + "timestamp": "1465103894", + "gasLimit": "4712388", + "miner": "0x61c808d82a3ac53231750dadc13c777b59310bd9" + }, + "input": "0xf9018b178504a817c80083030d4094cf1476387d780169410d4e936d75a206fda2a68c80b90124b61d27f6000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c189413000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000088613930353963626230303030303030303030303030303030303030303030303039306433633138313264653236363962663830376264373735386365623165333439376163376534303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030316336626635323633343030300000000000000000000000000000000000000000000000001ca0f1ae5ea07b1d00eb5e06fc854124ee0234ec61c8b393147f9d030804a75c98daa01d045d7633012cca74e30e975c3d00d11b4243dd8648f2e78d652f3a8aaafceb", + "tracerConfig": { + "withLog": true + }, + "result": { + "from": "0x01115b41bd2731353dd3e6abf44818fdc035aaf1", + "gas": "0x30d40", + "gasUsed": "0x288c9", + "to": "0xcf1476387d780169410d4e936d75a206fda2a68c", + "input": "0xb61d27f6000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c18941300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000008861393035396362623030303030303030303030303030303030303030303030303930643363313831326465323636396266383037626437373538636562316533343937616337653430303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303031633662663532363334303030000000000000000000000000000000000000000000000000", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000", + "calls": [ + { + "from": "0xcf1476387d780169410d4e936d75a206fda2a68c", + "gas": "0x1e30b", + "gasUsed": "0x1e30b", + "to": "0xbb9bc244d798123fde783fcc1c72d3bb8c189413", + "input": "0x61393035396362623030303030303030303030303030303030303030303030303930643363313831326465323636396266383037626437373538636562316533343937616337653430303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303031633662663532363334303030", + "error": "invalid jump destination", + "value": "0x0", + "type": "CALL" + } + ], + "logs": [ + { + "address": "0xcf1476387d780169410d4e936d75a206fda2a68c", + "topics": [ + "0x92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004" + ], + "data": "0x00000000000000000000000001115b41bd2731353dd3e6abf44818fdc035aaf10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c1894130000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008861393035396362623030303030303030303030303030303030303030303030303930643363313831326465323636396266383037626437373538636562316533343937616337653430303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303031633662663532363334303030", + "position": "0x0" + } + ], + "value": "0x0", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/with_onlyTopCall.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/with_onlyTopCall.json new file mode 100644 index 0000000..e730811 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/with_onlyTopCall.json @@ -0,0 +1,89 @@ +{ + "genesis": { + "difficulty": "11934798510088", + "extraData": "0xd983010302844765746887676f312e342e328777696e646f7773", + "gasLimit": "3141592", + "hash": "0xfc543a4a551afbd4a6c5d6d49041371e6bb58b1108c12aaec7f487ce656bb97f", + "miner": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069", + "mixHash": "0xa6a1e67fc68da76b8d9cc3ce1c45d5e1f4bbd96b5dcfddbe0017d7fa99903ead", + "nonce": "0x5f00c600268b4659", + "number": "995200", + "stateRoot": "0x3579328470dd2aef5b9da69f5480cbe0d375e653b530ab3c1aee0da5e1ff4c94", + "timestamp": "1455322761", + "totalDifficulty": "7077231809278509672", + "alloc": { + "0x200edd17f30485a8735878661960cd7a9a95733f": { + "balance": "0x0", + "code": "0x3660008037602060003660003473273930d21e01ee25e4c219b63259d214872220a261235a5a03f21560015760206000f3", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000104": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x4c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8ba1097eb3abe3dc1b51faa48445d593bf968f722e20b67bb62a87495836bf04": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8ba1097eb3abe3dc1b51faa48445d593bf968f722e20b67bb62a87495836bf05": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8ba1097eb3abe3dc1b51faa48445d593bf968f722e20b67bb62a87495836bf06": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa611e7c895a426c0477bc9e280db9c3b1e456dc6310ffcf23926ef5186c1facc": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0xac682d343707aadf06c2c4c3692831d9e7ba711099ef36f9efb8bb29be8c410e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xac682d343707aadf06c2c4c3692831d9e7ba711099ef36f9efb8bb29be8c410f": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xac682d343707aadf06c2c4c3692831d9e7ba711099ef36f9efb8bb29be8c4110": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x273930d21e01ee25e4c219b63259d214872220a2": { + "balance": "0x0", + "code": "0x606060405236156100da5760e060020a6000350463173825d9811461012c5780632f54bf6e146101875780634123cb6b146101af57806352375093146101b857806354fd4d50146101c25780635c52c2f5146101cc578063659010e7146101fd5780637065cb4814610207578063746c91711461023b578063797af62714610244578063b20d30a914610257578063b61d27f61461028b578063b75c7dc6146102ac578063ba51a6df146102db578063c2cf73261461030f578063cbf0b0c01461034d578063f00d4b5d14610381578063f1736d86146103ba575b6103c4600034111561012a5760408051600160a060020a033216815234602082015281517fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c929181900390910190a15b565b6103c46004356000600036436040518084848082843750505090910190815260405190819003602001902090506106c9815b600160a060020a03321660009081526101026020526040812054818082811415610c3f57610d97565b6103c66004355b600160a060020a03811660009081526101026020526040812054115b919050565b6103c660015481565b6103c66101075481565b6103c66101085481565b6103c46000364360405180848480828437505050909101908152604051908190036020019020905061081a8161015e565b6103c66101065481565b6103c4600435600036436040518084848082843750505090910190815260405190819003602001902090506106418161015e565b6103c660005481565b6103c66004355b600081610a7d8161015e565b6103c46004356000364360405180848480828437505050909101908152604051908190036020019020905061080e8161015e565b6103c66004803590602480359160443591820191013560006108393261018e565b6103c4600435600160a060020a033216600090815261010260205260408120549080828114156103d857610457565b6103c4600435600036436040518084848082843750505090910190815260405190819003602001902090506107888161015e565b6103c6600435602435600082815261010360209081526040808320600160a060020a038516845261010290925282205482818114156107e157610805565b6103c4600435600036436040518084848082843750505090910190815260405190819003602001902090506108288161015e565b6103c46004356024356000600036436040518084848082843750505090910190815260405190819003602001902090506104e28161015e565b6103c66101055481565b005b60408051918252519081900360200190f35b50506000828152610103602052604081206001810154600284900a9290831611156104575780546001828101805492909101835590839003905560408051600160a060020a03321681526020810186905281517fc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b929181900390910190a15b50505050565b600160a060020a03831660028361010081101561000257508301819055600160a060020a03851660008181526101026020908152604080832083905584835291829020869055815192835282019290925281517fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c929181900390910190a1505b505050565b15610457576104f08361018e565b156104fb57506104dd565b600160a060020a03841660009081526101026020526040812054925082141561052457506104dd565b61045d5b6101045460005b81811015610ee457610104805461010991600091849081101561000257600080516020610f9f83398151915201548252506020918252604081208054600160a060020a0319168155600181018290556002810180548382559083528383209193610f6992601f9290920104810190610a65565b60018054810190819055600160a060020a038316906002906101008110156100025790900160005081905550600160005054610102600050600084600160a060020a03168152602001908152602001600020600050819055507f994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3826040518082600160a060020a0316815260200191505060405180910390a15b505b50565b1561063c5761064f8261018e565b1561065a575061063e565b610662610528565b60015460fa90106106775761067561068c565b505b60015460fa90106105a2575061063e565b6107465b600060015b600154811015610a79575b600154811080156106bc5750600281610100811015610002570154600014155b15610d9f5760010161069c565b156104dd57600160a060020a0383166000908152610102602052604081205492508214156106f7575061063c565b6001600160005054036000600050541115610712575061063c565b600060028361010081101561000257508301819055600160a060020a03841681526101026020526040812055610688610528565b5060408051600160a060020a038516815290517f58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da9181900360200190a1505050565b1561063c5760015482111561079d575061063e565b60008290556107aa610528565b6040805183815290517facbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da9181900360200190a15050565b506001820154600282900a908116600014156108005760009350610805565b600193505b50505092915050565b1561063c575061010555565b1561063e5760006101065550565b1561063c5781600160a060020a0316ff5b15610a555761084d846000610e793261018e565b15610909577f92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd00432858786866040518086600160a060020a0316815260200185815260200184600160a060020a031681526020018060200182810382528484828181526020019250808284378201915050965050505050505060405180910390a184600160a060020a03168484846040518083838082843750505090810191506000908083038185876185025a03f15060009350610a5592505050565b6000364360405180848480828437505050909101908152604051908190036020019020915061093990508161024b565b15801561095c575060008181526101096020526040812054600160a060020a0316145b15610a555760008181526101096020908152604082208054600160a060020a03191688178155600181018790556002018054858255818452928290209092601f01919091048101908490868215610a5d579182015b82811115610a5d5782358260005055916020019190600101906109b1565b50507f1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf328132868887876040518087815260200186600160a060020a0316815260200185815260200184600160a060020a03168152602001806020018281038252848482818152602001925080828437820191505097505050505050505060405180910390a15b949350505050565b506109cf9291505b80821115610a795760008155600101610a65565b5090565b15610c2c5760008381526101096020526040812054600160a060020a031614610c2c5760408051600091909120805460018201546002929092018054600160a060020a0392909216939091819083908015610afd57820191906000526020600020905b815481529060010190602001808311610ae057829003601f168201915b505091505060006040518083038185876185025a03f150505060008481526101096020908152604080519281902080546001820154600160a060020a033281811688529587018b905293860181905292166060850181905260a06080860181815260029390930180549187018290527fe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a975094958a959293909160c083019084908015610bcf57820191906000526020600020905b815481529060010190602001808311610bb257829003601f168201915b5050965050505050505060405180910390a160008381526101096020908152604082208054600160a060020a031916815560018101839055600281018054848255908452828420919392610c3292601f9290920104810190610a65565b50919050565b50505060019150506101aa565b60008581526101036020526040812080549093501415610cc7576000805483556001838101919091556101048054918201808255828015829011610c9657818360005260206000209182019101610c969190610a65565b50505060028301819055610104805487929081101561000257600091909152600080516020610f9f83398151915201555b506001810154600283900a90811660001415610d975760408051600160a060020a03321681526020810187905281517fe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda929181900390910190a1815460019011610d84576000858152610103602052604090206002015461010480549091908110156100025760406000908120600080516020610f9f8339815191529290920181905580825560018083018290556002909201559450610d979050565b8154600019018255600182018054821790555b505050919050565b5b60018054118015610dc257506001546002906101008110156100025701546000145b15610dd65760018054600019019055610da0565b60015481108015610df95750600154600290610100811015610002570154600014155b8015610e1357506002816101008110156100025701546000145b15610e7457600154600290610100811015610002578101549082610100811015610002578101919091558190610102906000908361010081101561000257810154825260209290925260408120929092556001546101008110156100025701555b610691565b156101aa5761010754610e8f5b62015180420490565b1115610ea857600061010655610ea3610e86565b610107555b6101065480830110801590610ec65750610106546101055490830111155b15610edc575061010680548201905560016101aa565b5060006101aa565b61063c6101045460005b81811015610f745761010480548290811015610002576000918252600080516020610f9f833981519152015414610f6157610104805461010391600091849081101561000257600080516020610f9f83398151915201548252506020919091526040812081815560018101829055600201555b600101610eee565b50505060010161052f565b61010480546000808355919091526104dd90600080516020610f9f83398151915290810190610a6556004c0be60200faa20559308cb7b5a1bb3255c16cb1cab91f525b5ae7a03d02fabe" + }, + "0x4f5777744b500616697cb655dcb02ee6cd51deb5": { + "balance": "0xb0983f1b83eec290", + "nonce": "2" + }, + "0xf8b483dba2c3b7176a3da549ad41a48bb3121069": { + "balance": "0x16969a0ba2c2d384d07", + "nonce": "67521" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "995201", + "difficulty": "11940626048551", + "timestamp": "1455322773", + "gasLimit": "3141592", + "miner": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069" + }, + "input": "0xf89102850a954d522e8303308594200edd17f30485a8735878661960cd7a9a95733f888ac7230489e80000a4ba51a6df00000000000000000000000000000000000000000000000000000000000000001ca04f2cc45b96f965296382b2e9b657e90808301d5179035a5d91a2de7b912def20a056e19271ea4e19e4e034f38e925e312beed4d300c267160eeb2f565c42deb578", + "tracerConfig": { + "withLog": true, + "onlyTopCall": true + }, + "result": { + "from": "0x4f5777744b500616697cb655dcb02ee6cd51deb5", + "gas": "0x33085", + "gasUsed": "0x1a9e5", + "to": "0x200edd17f30485a8735878661960cd7a9a95733f", + "input": "0xba51a6df0000000000000000000000000000000000000000000000000000000000000000", + "output": "0xba51a6df00000000000000000000000000000000000000000000000000000000", + "value": "0x8ac7230489e80000", + "type": "CALL" + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json new file mode 100644 index 0000000..c3e7387 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json @@ -0,0 +1,64 @@ +{ + "genesis": { + "baseFeePerGas": "7", + "blobGasUsed": "0", + "difficulty": "0", + "excessBlobGas": "36306944", + "extraData": "0xd983010e00846765746888676f312e32312e308664617277696e", + "gasLimit": "15639172", + "hash": "0xc682259fda061bb9ce8ccb491d5b2d436cb73daf04e1025dd116d045ce4ad28c", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0xae1a5ba939a4c9ac38aabeff361169fb55a6fc2c9511457e0be6eff9514faec0", + "nonce": "0x0000000000000000", + "number": "315", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "stateRoot": "0x577f42ab21ccfd946511c57869ace0bdf7c217c36f02b7cd3459df0ed1cffc1a", + "timestamp": "1709626771", + "totalDifficulty": "1", + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "alloc": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x272e0528" + }, + "0x0c2c51a0990aee1d73c1228de158688341557508": { + "balance": "0xde0b6b3a7640000" + } + }, + "config": { + "chainId": 1337, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true + } + }, + "context": { + "number": "316", + "difficulty": "0", + "timestamp": "1709626785", + "gasLimit": "15654443", + "miner": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "7" + }, + "input": "0x03f8b1820539806485174876e800825208940c2c51a0990aee1d73c1228de1586883415575088080c083020000f842a00100c9fbdf97f747e85847b4f3fff408f89c26842f77c882858bf2c89923849aa00138e3896f3c27f2389147507f8bcec52028b0efca6ee842ed83c9158873943880a0dbac3f97a532c9b00e6239b29036245a5bfbb96940b9d848634661abee98b945a03eec8525f261c2e79798f7b45a5d6ccaefa24576d53ba5023e919b86841c0675", + "result": { + "0x0000000000000000000000000000000000000000": { "balance": "0x272e0528" }, + "0x0c2c51a0990aee1d73c1228de158688341557508": { + "balance": "0xde0b6b3a7640000" + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_create.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_create.json new file mode 100644 index 0000000..893c604 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_create.json @@ -0,0 +1,63 @@ +{ + "genesis": { + "baseFeePerGas": "875000000", + "difficulty": "0", + "extraData": "0xd983010d05846765746888676f312e32312e318664617277696e", + "gasLimit": "11511229", + "hash": "0xd462585c6c5a3b3bf14850ebcde71b6615b9aaf6541403f9a0457212dd0502e0", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0xfa51e868d6a7c0728f18800e4cc8d4cc1c87430cc9975e947eb6c9c03599b4e2", + "nonce": "0x0000000000000000", + "number": "1", + "stateRoot": "0xd2ebe0a7f3572ffe3e5b4c78147376d3fca767f236e4dd23f9151acfec7cb0d1", + "timestamp": "1699617692", + "totalDifficulty": "0", + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "alloc": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x5208" + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "balance": "0x8ac7230489e80000" + } + }, + "config": { + "chainId": 1337, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "shanghaiTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "isDev": true + } + }, + "context": { + "number": "2", + "difficulty": "0", + "timestamp": "1699617847", + "gasLimit": "11522469", + "miner": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "765625000" + }, + "input": "0x02f902b48205398084b2d05e0085011b1f3f8083031ca88080b90258608060405234801561001057600080fd5b5060405161001d906100e3565b604051809103906000f080158015610039573d6000803e3d6000fd5b50600080546001600160a01b0319166001600160a01b039290921691821781556040517fc66247bafd1305823857fb4c3e651e684d918df8554ef560bbbcb025fdd017039190a26000546040516360fe47b160e01b8152600560048201526001600160a01b03909116906360fe47b190602401600060405180830381600087803b1580156100c657600080fd5b505af11580156100da573d6000803e3d6000fd5b505050506100ef565b60ca8061018e83390190565b6091806100fd6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806380de699314602d575b600080fd5b600054603f906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f3fea2646970667358221220dab781465e7f4cf20304cc388130a763508e20edd25b4bc8ea8f57743a0de8da64736f6c634300081700336080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146049575b600080fd5b60476042366004605e565b600055565b005b60005460405190815260200160405180910390f35b600060208284031215606f57600080fd5b503591905056fea264697066735822122049e09da6320793487d58eaa7b97f802618a062cbc35f08ca1ce92c17349141f864736f6c63430008170033c080a01d4fce93ad08bf413052645721f20e6136830cf5a2759fa57e76a134e90899a7a0399a72832d52118991dc04c4f9e1c0fec3d5e441ad7d4b055f0cf03130d8f815", + "result": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x5208" + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "balance": "0x8ac7230489e80000" + } + } +} \ No newline at end of file diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_existing_contract.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_existing_contract.json new file mode 100644 index 0000000..a34d3b7 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_existing_contract.json @@ -0,0 +1,85 @@ +{ + "genesis": { + "difficulty": "6217248151198", + "extraData": "0xd783010103844765746887676f312e342e32856c696e7578", + "gasLimit": "3141592", + "hash": "0xe8bff55fe3e61936ef321cf3afaeb1ba2f7234e1e89535fa8ae39963caebe9c3", + "miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5", + "mixHash": "0x03da00d5a15a064e5ebddf53cd0aaeb9a8aff0f40c0fb031a74f463d11ec83b8", + "nonce": "0x6575fe08c4167044", + "number": "243825", + "stateRoot": "0x47182fe2e6e740b8a76f82fe5c527d6ad548f805274f21792cf4047235b24fbf", + "timestamp": "1442424328", + "totalDifficulty": "1035061827427752845", + "alloc": { + "0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": { + "balance": "0xc820f93200f4000", + "nonce": "0x5E", + "code": "0x" + }, + "0x332b656504f4eabb44c8617a42af37461a34e9dc": { + "balance": "0x11faea4f35e5af80000", + "code": "0x" + }, + "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": { + "balance": "0xbf681825be002ac452", + "nonce": "0x70FA", + "code": "0x" + }, + "0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": { + "balance": "0xb3d0ac5cb94df6f6b0", + "nonce": "0x1", + "code": "0x" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "243826", + "difficulty": "6214212385501", + "timestamp": "1442424353", + "gasLimit": "3141592", + "miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5" + }, + "input": "0xf8e85e850ba43b7400830f42408080b89660606040527382effbaaaf28614e55b2ba440fb198e0e5789b0f600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b600a80608c6000396000f30060606040526008565b001ca0340b21661e5bb85a46319a15f33a362e5c0f02faa7cdbf9c5808b2134da968eaa0226e6788f8c20e211d436ab7f6298ef32fa4c23a509eeeaac0880d115c17bc3f", + "result": { + "0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": { + "balance": "0xc820f93200f4000", + "nonce": 94 + }, + "0x332b656504f4eabb44c8617a42af37461a34e9dc": { + "balance": "0x11faea4f35e5af80000", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": { + "balance": "0xbf681825be002ac452", + "nonce": 28922 + }, + "0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": { + "balance": "0xb3d0ac5cb94df6f6b0", + "nonce": 1 + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_post_eip158.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_post_eip158.json new file mode 100644 index 0000000..fb85b31 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/create_post_eip158.json @@ -0,0 +1,65 @@ +{ + "genesis": { + "baseFeePerGas": "7", + "difficulty": "2", + "extraData": "0xd983010d0e846765746888676f312e32312e318664617277696e0000000000001713699f05f79a59abec177c7a87b90ceda79b72ff5edc9197dd7627a447cde45b079bbc3765a236cdf680e2d4d2247135d0e6bb6fd92b50638b92504ddb274f00", + "gasLimit": "30000000", + "hash": "0x6ad5258175c66f4e883d238a92a08428d8ebcbeac631ab7b972634cc05effab3", + "miner": "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "39137", + "stateRoot": "0x715f00df764dbadd4863247a215ac44b5420beafde3ec458b15db7aafa89be0c", + "timestamp": "1709022192", + "totalDifficulty": "78275", + "alloc": { + "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": { + "balance": "0x10f06447a8d44dba190", + "nonce": "2" + }, + "0x82211934c340b29561381392348d48413e15adc8": { + "balance": "0x6abd7a808913ed2", + "nonce": "64" + } + }, + "config": { + "chainId": 12345, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "clique": { + "period": 5, + "epoch": 30000 + } + } + }, + "context": { + "number": "39138", + "difficulty": "2", + "timestamp": "1709022197", + "gasLimit": "30000000", + "miner": "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0", + "baseFeePerGas": "7" + }, + "input": "0x02f902af823039408459682f008459682f088302b3538080b90254608060405234801561001057600080fd5b50610234806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806309ce9ccb1461003b5780633fb5c1cb14610059575b600080fd5b610043610075565b60405161005091906100e2565b60405180910390f35b610073600480360381019061006e919061012e565b61007b565b005b60005481565b80600081905550600a8111156100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906101de565b60405180910390fd5b50565b6000819050919050565b6100dc816100c9565b82525050565b60006020820190506100f760008301846100d3565b92915050565b600080fd5b61010b816100c9565b811461011657600080fd5b50565b60008135905061012881610102565b92915050565b600060208284031215610144576101436100fd565b5b600061015284828501610119565b91505092915050565b600082825260208201905092915050565b7f4e756d6265722069732067726561746572207468616e2031302c207472616e7360008201527f616374696f6e2072657665727465642e00000000000000000000000000000000602082015250565b60006101c860308361015b565b91506101d38261016c565b604082019050919050565b600060208201905081810360008301526101f7816101bb565b905091905056fea264697066735822122069018995fecf03bda91a88b6eafe41641709dee8b4a706fe301c8a569fe8c1b364736f6c63430008130033c001a0a8cf4729b7e4664687abb3e2559853d7d489eb441519be2a17493061fb4c3a03a04b5a904ba8a6e59c6c40049c4d14a73233aeb8a45b38403199f304630dc0d453", + "result": { + "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": { + "balance": "0x10f06447a8d44dba190", + "nonce": 2 + }, + "0x82211934c340b29561381392348d48413e15adc8": { + "balance": "0x6abd7a808913ed2", + "nonce": 64 + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/simple.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/simple.json new file mode 100644 index 0000000..7204bfc --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/simple.json @@ -0,0 +1,83 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "result": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "nonce": 22 + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "nonce": 1, + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "nonce": 29072 + }, + "0x1585936b53834b021f68cc13eeefdec2efc8e724": { + "balance": "0x0" + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_legacy/simple.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_legacy/simple.json new file mode 100644 index 0000000..44b1f08 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_legacy/simple.json @@ -0,0 +1,84 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "result": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": 22, + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": 1, + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": 29072, + "storage": {} + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create.json new file mode 100644 index 0000000..1b09622 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create.json @@ -0,0 +1,102 @@ +{ + "genesis": { + "difficulty": "13756228101629", + "extraData": "0xd983010302844765746887676f312e342e328777696e646f7773", + "gasLimit": "3141592", + "hash": "0x58b7a87b6ba10b46b4e251d64ebc3d9822dd82218eaf24dff6796f6f1f687251", + "miner": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069", + "mixHash": "0x5984b9a316116bd890e6e5f4c52d655184b0d7aa74821e1382d7760f9803c1dd", + "nonce": "0xea4bb4997242c681", + "number": "1061221", + "stateRoot": "0x5402c04d481414248d824c3b61e924e0c9307adbc9fbaae774a74cce30a4163d", + "timestamp": "1456458069", + "totalDifficulty": "7930751135586064334", + "alloc": { + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x9fb6b81e112638b886", + "nonce": "217865", + "code": "0x" + }, + "0xf0c5cef39b17c213cfe090a46b8c7760ffb7928a": { + "balance": "0x15b6828e22bb12188", + "nonce": "747", + "code": "0x" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "ethash": {} + } + }, + "context": { + "number": "1061222", + "difficulty": "13749511193633", + "timestamp": "1456458097", + "gasLimit": "3141592", + "miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226" + }, + "input": "0xf905498202eb850ba43b7400830f42408080b904f460606040526040516102b43803806102b48339016040526060805160600190602001505b5b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b806001600050908051906020019082805482825590600052602060002090601f01602090048101928215609e579182015b82811115609d5782518260005055916020019190600101906081565b5b50905060c5919060a9565b8082111560c1576000818150600090555060010160a9565b5090565b50505b506101dc806100d86000396000f30060606040526000357c01000000000000000000000000000000000000000000000000000000009004806341c0e1b514610044578063cfae32171461005157610042565b005b61004f6004506100ca565b005b61005c60045061015e565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561015b57600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b565b60206040519081016040528060008152602001506001600050805480601f016020809104026020016040519081016040528092919081815260200182805480156101cd57820191906000526020600020905b8154815290600101906020018083116101b057829003601f168201915b505050505090506101d9565b9056000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001ee7b225f6964223a225a473466784a7245323639384866623839222c22666f726d5f736f75726365223a22434c54523031222c22636f6d6d69746d656e745f64617465223a22222c22626f72726f7765725f6e616d65223a22222c22626f72726f7765725f616464726573735f6c696e6531223a22222c22626f72726f7765725f616464726573735f6c696e6532223a22222c22626f72726f7765725f636f6e74616374223a22222c22626f72726f7765725f7374617465223a22222c22626f72726f7765725f74797065223a22222c2270726f70657274795f61646472657373223a22222c226c6f616e5f616d6f756e745f7772697474656e223a22222c226c6f616e5f616d6f756e74223a22222c224c54565f7772697474656e223a22222c224c5456223a22222c2244534352223a22222c2270726f70657274795f74797065223a22222c2270726f70657274795f6465736372697074696f6e223a22222c226c656e646572223a22222c2267756172616e746f7273223a22222c226c696d69746564223a22222c226361705f616d6f756e74223a22222c226361705f70657263656e745f7772697474656e223a22222c226361705f70657263656e74616765223a22222c227465726d5f7772697474656e223a22222c227465726d223a22222c22657874656e64223a22227d0000000000000000000000000000000000001ba027d54712289af34f0ec0f06092745104d68e5801cd17097bc1104111f855258da070ec9f1c942d9bedf89f9660a684d3bb8cd9c2ac7f6dd883cb3e26a193180244", + "tracerConfig": { + "diffMode": true + }, + "result": { + "pre": { + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x9fb6b81e112638b886", + "nonce": 217865 + }, + "0xf0c5cef39b17c213cfe090a46b8c7760ffb7928a": { + "balance": "0x15b6828e22bb12188", + "nonce": 747 + } + }, + "post": { + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x9fb71abdd2621d8886" + }, + "0x40f2f445da6c9047554683fb382fba6769717116": { + "code": "0x60606040526000357c01000000000000000000000000000000000000000000000000000000009004806341c0e1b514610044578063cfae32171461005157610042565b005b61004f6004506100ca565b005b61005c60045061015e565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561015b57600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b5b565b60206040519081016040528060008152602001506001600050805480601f016020809104026020016040519081016040528092919081815260200182805480156101cd57820191906000526020600020905b8154815290600101906020018083116101b057829003601f168201915b505050505090506101d9565b9056", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f0c5cef39b17c213cfe090a46b8c7760ffb7928a", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000000000000000001ee", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "0x7b225f6964223a225a473466784a7245323639384866623839222c22666f726d", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7": "0x5f736f75726365223a22434c54523031222c22636f6d6d69746d656e745f6461", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf8": "0x7465223a22222c22626f72726f7765725f6e616d65223a22222c22626f72726f", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf9": "0x7765725f616464726573735f6c696e6531223a22222c22626f72726f7765725f", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfa": "0x616464726573735f6c696e6532223a22222c22626f72726f7765725f636f6e74", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfb": "0x616374223a22222c22626f72726f7765725f7374617465223a22222c22626f72", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfc": "0x726f7765725f74797065223a22222c2270726f70657274795f61646472657373", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfd": "0x223a22222c226c6f616e5f616d6f756e745f7772697474656e223a22222c226c", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cfe": "0x6f616e5f616d6f756e74223a22222c224c54565f7772697474656e223a22222c", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cff": "0x224c5456223a22222c2244534352223a22222c2270726f70657274795f747970", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d00": "0x65223a22222c2270726f70657274795f6465736372697074696f6e223a22222c", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d01": "0x226c656e646572223a22222c2267756172616e746f7273223a22222c226c696d", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d02": "0x69746564223a22222c226361705f616d6f756e74223a22222c226361705f7065", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d03": "0x7263656e745f7772697474656e223a22222c226361705f70657263656e746167", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d04": "0x65223a22222c227465726d5f7772697474656e223a22222c227465726d223a22", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0d05": "0x222c22657874656e64223a22227d000000000000000000000000000000000000" + } + }, + "0xf0c5cef39b17c213cfe090a46b8c7760ffb7928a": { + "balance": "0x15b058920efcc5188", + "nonce": 748 + } + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_failed.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_failed.json new file mode 100644 index 0000000..ae7f7e9 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_failed.json @@ -0,0 +1,95 @@ +{ + "genesis": { + "baseFeePerGas": "4573728117", + "difficulty": "14315558652874667", + "extraData": "0xd883010a10846765746888676f312e31362e35856c696e7578", + "gasLimit": "30058590", + "hash": "0xdf6b95183f99054fb6541e3b482c0109c9f6be40553cff24efa3ac76736adbf5", + "miner": "0xb7e390864a90b7b923c9f9310c6f98aafe43f707", + "mixHash": "0x8d76b0d32e42ab277dbf00836eabef76674cd70ae2bb53718175069ad6b6147e", + "nonce": "0x8d3a1c010ad2c687", + "number": "14707767", + "stateRoot": "0x8a50c896a6f7eb1f3479337db981fa10ce316281cb4dd2f07487be9ca27dae6b", + "timestamp": "1651623275", + "alloc": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x268fd0b894b8c4f6d1f" + }, + "0x13b152c9f50878ffaf3de41e192653bda545d889": { + "balance": "0x0", + "nonce": "1", + "code": "0x363d3d373d3d3d363d73059ffafdc6ef594230de44f824e2bd0a51ca5ded5af43d82803e903d91602b57fd5bf3" + }, + "0x808b4da0be6c9512e948521452227efc619bea52": { + "balance": "0x2cdb96c56db040b43", + "nonce": "1223932" + }, + "0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7": { + "balance": "0x38079b28689d40240e", + "nonce": "44" + }, + "0xffa397285ce46fb78c588a9e993286aac68c37cd": { + "balance": "0x0", + "nonce": "747319", + "code": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063b97a23191461003b578063fb90b3201461006f575b600080fd5b6100436100bd565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100bb6004803603604081101561008557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506100e1565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008282604051602001808373ffffffffffffffffffffffffffffffffffffffff1660601b815260140182815260200192505050604051602081830303815290604052805190602001209050600061015960008054906101000a900473ffffffffffffffffffffffffffffffffffffffff168361024d565b90508073ffffffffffffffffffffffffffffffffffffffff166319ab453c856040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b1580156101c457600080fd5b505af11580156101d8573d6000803e3d6000fd5b505050507fa35ea2cc726861482a50a162c72aad60965cc64641d419cd4d675036238b52048185604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a150505050565b6000808360601b90506040517f3d602d80600a3d3981f3363d3d373d3d3d363d7300000000000000000000000081528160148201527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006028820152836037826000f5925050509291505056fea2646970667358221220c87b2492828fdd7dad3175a32a98ff07fc0eedf106536f2eddd9a016971c56a764736f6c63430007050033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000059ffafdc6ef594230de44f824e2bd0a51ca5ded" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficultyPassed": true, + "ethash": {} + } + }, + "context": { + "number": "14707768", + "difficulty": "14322823549655084", + "timestamp": "1651623279", + "gasLimit": "30029237", + "miner": "0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7", + "baseFeePerGas": "4002012103" + }, + "input": "0x02f8b4018312acfc8459682f00851a46bcf47a8302b1a194ffa397285ce46fb78c588a9e993286aac68c37cd80b844fb90b3200000000000000000000000002a549b4af9ec39b03142da6dc32221fc390b553300000000000000000000000000000000000000000000000000000000000cb3d5c001a03002079d2873f7963c4278200c43aa71efad262b2150bc8524480acfc38b5faaa077d44aa09d56b9cf99443c7f55aaad1bbae9cfb5bbb9de31eaf7a8f9e623e980", + "tracerConfig": { + "diffMode": true + }, + "result": { + "pre": { + "0x808b4da0be6c9512e948521452227efc619bea52": { + "balance": "0x2cdb96c56db040b43", + "nonce": 1223932 + }, + "0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7": { + "balance": "0x38079b28689d40240e", + "nonce": 44 + } + }, + "post": { + "0x808b4da0be6c9512e948521452227efc619bea52": { + "balance": "0x2cdb5f8e62cc9ad1c", + "nonce": 1223933 + }, + "0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7": { + "balance": "0x38079c19423e44b30e" + } + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_post_eip158.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_post_eip158.json new file mode 100644 index 0000000..f5adb1a --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_post_eip158.json @@ -0,0 +1,83 @@ +{ + "genesis": { + "baseFeePerGas": "7", + "difficulty": "2", + "extraData": "0xd983010d0e846765746888676f312e32312e318664617277696e0000000000001713699f05f79a59abec177c7a87b90ceda79b72ff5edc9197dd7627a447cde45b079bbc3765a236cdf680e2d4d2247135d0e6bb6fd92b50638b92504ddb274f00", + "gasLimit": "30000000", + "hash": "0x6ad5258175c66f4e883d238a92a08428d8ebcbeac631ab7b972634cc05effab3", + "miner": "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "39137", + "stateRoot": "0x715f00df764dbadd4863247a215ac44b5420beafde3ec458b15db7aafa89be0c", + "timestamp": "1709022192", + "totalDifficulty": "78275", + "alloc": { + "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": { + "balance": "0x10f06447a8d44dba190", + "nonce": "2" + }, + "0x82211934c340b29561381392348d48413e15adc8": { + "balance": "0x6abd7a808913ed2", + "nonce": "64" + } + }, + "config": { + "chainId": 12345, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "clique": { + "period": 5, + "epoch": 30000 + } + } + }, + "context": { + "number": "39138", + "difficulty": "2", + "timestamp": "1709022197", + "gasLimit": "30000000", + "miner": "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0", + "baseFeePerGas": "7" + }, + "input": "0x02f902af823039408459682f008459682f088302b3538080b90254608060405234801561001057600080fd5b50610234806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806309ce9ccb1461003b5780633fb5c1cb14610059575b600080fd5b610043610075565b60405161005091906100e2565b60405180910390f35b610073600480360381019061006e919061012e565b61007b565b005b60005481565b80600081905550600a8111156100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906101de565b60405180910390fd5b50565b6000819050919050565b6100dc816100c9565b82525050565b60006020820190506100f760008301846100d3565b92915050565b600080fd5b61010b816100c9565b811461011657600080fd5b50565b60008135905061012881610102565b92915050565b600060208284031215610144576101436100fd565b5b600061015284828501610119565b91505092915050565b600082825260208201905092915050565b7f4e756d6265722069732067726561746572207468616e2031302c207472616e7360008201527f616374696f6e2072657665727465642e00000000000000000000000000000000602082015250565b60006101c860308361015b565b91506101d38261016c565b604082019050919050565b600060208201905081810360008301526101f7816101bb565b905091905056fea264697066735822122069018995fecf03bda91a88b6eafe41641709dee8b4a706fe301c8a569fe8c1b364736f6c63430008130033c001a0a8cf4729b7e4664687abb3e2559853d7d489eb441519be2a17493061fb4c3a03a04b5a904ba8a6e59c6c40049c4d14a73233aeb8a45b38403199f304630dc0d453", + "tracerConfig": { + "diffMode": true + }, + "result": { + "post": { + "0x1bda2f8e4735507930bd6cfe873bf0bf0f4ab1de": { + "code": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806309ce9ccb1461003b5780633fb5c1cb14610059575b600080fd5b610043610075565b60405161005091906100e2565b60405180910390f35b610073600480360381019061006e919061012e565b61007b565b005b60005481565b80600081905550600a8111156100c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100bd906101de565b60405180910390fd5b50565b6000819050919050565b6100dc816100c9565b82525050565b60006020820190506100f760008301846100d3565b92915050565b600080fd5b61010b816100c9565b811461011657600080fd5b50565b60008135905061012881610102565b92915050565b600060208284031215610144576101436100fd565b5b600061015284828501610119565b91505092915050565b600082825260208201905092915050565b7f4e756d6265722069732067726561746572207468616e2031302c207472616e7360008201527f616374696f6e2072657665727465642e00000000000000000000000000000000602082015250565b60006101c860308361015b565b91506101d38261016c565b604082019050919050565b600060208201905081810360008301526101f7816101bb565b905091905056fea264697066735822122069018995fecf03bda91a88b6eafe41641709dee8b4a706fe301c8a569fe8c1b364736f6c63430008130033", + "nonce": 1 + }, + "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": { + "balance": "0x10f0645688331eb5690" + }, + "0x82211934c340b29561381392348d48413e15adc8": { + "balance": "0x6aae9b21b6ee855", + "nonce": 65 + } + }, + "pre": { + "0x2445e8c26a2bf3d1e59f1bb9b1d442caf90768e0": { + "balance": "0x10f06447a8d44dba190", + "nonce": 2 + }, + "0x82211934c340b29561381392348d48413e15adc8": { + "balance": "0x6abd7a808913ed2", + "nonce": 64 + } + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_suicide.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_suicide.json new file mode 100644 index 0000000..fdeb0e5 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_suicide.json @@ -0,0 +1,104 @@ +{ + "genesis": { + "difficulty": "6217248151198", + "extraData": "0xd783010103844765746887676f312e342e32856c696e7578", + "gasLimit": "3141592", + "hash": "0xe8bff55fe3e61936ef321cf3afaeb1ba2f7234e1e89535fa8ae39963caebe9c3", + "miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5", + "mixHash": "0x03da00d5a15a064e5ebddf53cd0aaeb9a8aff0f40c0fb031a74f463d11ec83b8", + "nonce": "0x6575fe08c4167044", + "number": "243825", + "stateRoot": "0x47182fe2e6e740b8a76f82fe5c527d6ad548f805274f21792cf4047235b24fbf", + "timestamp": "1442424328", + "totalDifficulty": "1035061827427752845", + "alloc": { + "0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": { + "balance": "0xc820f93200f4000", + "nonce": "0x5E", + "code": "0x" + }, + "0x332b656504f4eabb44c8617a42af37461a34e9dc": { + "balance": "0x11faea4f35e5af80000", + "code": "0x", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": { + "balance": "0xbf681825be002ac452", + "nonce": "0x70FA", + "code": "0x" + }, + "0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": { + "balance": "0xb3d0ac5cb94df6f6b0", + "nonce": "0x1", + "code": "0x" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "ethash": {} + } + }, + "context": { + "number": "243826", + "difficulty": "6214212385501", + "timestamp": "1442424353", + "gasLimit": "3141592", + "miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5" + }, + "input": "0xf8e85e850ba43b7400830f42408080b89660606040527382effbaaaf28614e55b2ba440fb198e0e5789b0f600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b600a80608c6000396000f30060606040526008565b001ca0340b21661e5bb85a46319a15f33a362e5c0f02faa7cdbf9c5808b2134da968eaa0226e6788f8c20e211d436ab7f6298ef32fa4c23a509eeeaac0880d115c17bc3f", + "tracerConfig": { + "diffMode": true + }, + "result": { + "pre": { + "0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": { + "balance": "0xc820f93200f4000", + "nonce": 94 + }, + "0x332b656504f4eabb44c8617a42af37461a34e9dc": { + "balance": "0x11faea4f35e5af80000", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": { + "balance": "0xbf681825be002ac452", + "nonce": 28922 + }, + "0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": { + "balance": "0xb3d0ac5cb94df6f6b0", + "nonce": 1 + } + }, + "post": { + "0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": { + "balance": "0xc7d4d88af8b4c00", + "nonce": 95 + }, + "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": { + "balance": "0xbf681ce7c870aeb852" + }, + "0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": { + "balance": "0x1d37f515017a8eef6b0" + } + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/inner_create.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/inner_create.json new file mode 100644 index 0000000..9c0030a --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/inner_create.json @@ -0,0 +1,312 @@ +{ + "genesis": { + "difficulty": "13707196986889", + "extraData": "0xd983010302844765746887676f312e342e328777696e646f7773", + "gasLimit": "3141592", + "hash": "0x607b38fe7e94427ee8f3b9a62375c67f953f8d49e05dbfd0145f9d3bac142193", + "miner": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069", + "mixHash": "0x98c74c9e76fd0078157e1696e4334a7e787396459693a84536d8b96414dafd5d", + "nonce": "0x77a5a0a73ad8745e", + "number": "1062502", + "stateRoot": "0x1df615df5fdbc8d5397bf3574f462f6d9696428eb8796d8e9252bccc8e3a8996", + "timestamp": "1456480432", + "totalDifficulty": "7948153536501153741", + "alloc": { + "0x0000000000000000000000000000000000000004": { + "balance": "0x0", + "code": "0x" + }, + "0x1deeda36e15ec9e80f3d7414d67a4803ae45fc80": { + "balance": "0x0", + "code": "0x650200d2f18c7350606060405236156100c15760e060020a60003504630bd295e681146100c65780630fd1f94e1461017d5780630fee183d1461018c578063349501b7146101ad5780635054d98a146101c75780637c0278fc146101ef5780637e92656214610287578063a0943154146102f6578063a1873db61461030e578063a9d2293d14610355578063b5d0f16e146103ad578063c17e6817146103ce578063cc3471af1461046a578063da46be0a1461047a578063f55627531461052a575b610007565b6105d36004356024356044355b60006000600030915081600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100075750506040515191505080841080610173575081600160a060020a031663a06db7dc6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610007575050506040518051906020015060ff16810184115b1561100d57610007565b6105d3600060f0610f6d61046e565b6105d3600435602435604435606435600081600202831015610ff657610fee565b6105d36004355b600081600014156109115750600161098f565b6105d36004355b6008810154600090819062010000900460ff16156105f257600691506105ec565b60408051602060248035600481810135601f81018590048502860185019096528585526105e5958135959194604494929390920191819084018382808284375094965050505050505060006004825103836001016000508181546001816001161561010002031660029004825481601f106108005782601f1061083a575b826008026101000360020a80910402828001178355610851565b6105e5600435602435604051600090600160a060020a038316907f398bd6b21ae4164ec322fb0eb8c2eb6277f36fd41903fbbed594dfe125591281908390a26007830154819010610e415760078301546005840154610e3f9162010000909104600160a060020a0316906103d8565b6105d3600435602435600060006000611064856101ce565b6105d36004356024356044356004830154600090819030908410156110e4577f4e4f545f454e4f5547485f47415300000000000000000000000000000000000091506112dd565b6105d35b60006000309050600a81600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100075750506040515160091901935050505b5090565b6105d36004356024355b60008282111561099e578183606402049050610998565b6105d36004356024355b600030600160a060020a0316318211156103fa57600160a060020a0330163191505b6000821115610994577389efe605e9ecbe22849cd85d5449cc946c26f8f36312c82bcc84846040518360e060020a0281526004018083600160a060020a031681526020018281526020019250505060206040518083038160008760325a03f2156100075750839250610998915050565b6105d35b6000600f610f6d610359565b6105e560043560243560443560643560843560088501805461ff00191661010017905584543090600090819081908190819060a060020a900460e060020a02811480156104db575060018b8101546002918116156101000260001901160481145b156109b3578a5460028c0154600160a060020a039190911690895a60405191900391906000818181858888f193505050508b60080160006101000a81548160ff02191690830217905550610bfa565b6105d36004355b6000600060006000309250600a83600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000757505060405151600919019350505081851115610eb05782600160a060020a031663c6502da86040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610007575050604051519450610ea89050565b60408051918252519081900360200190f35b005b600291505b50919050565b6008830154610100900460ff161561060d57600591506105ec565b30905080600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100075750506040515143610109011015905061066457600091506105ec565b80600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100075750506040515143600a01101590506106d3576005830154620100009004600160a060020a0316600014156105e757600191506105ec565b80600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000757505060405151431015905061072357600391506105ec565b80600160a060020a031663a06db7dc6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610007575050506040518051906020015060ff1681600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610007575050604051519190910143101590506107bf57600491506105ec565b600791506105ec565b5081800160010183558181151161085157601f016020900481601f016020900483600052602060002091820191016108519190610826565b82601f106107c8575082600052602060002080549082601f016020900481019061090691905b808211156103a95760008155600101610826565b60ff19168360005260206000205581800160010183555b5050505060048251111561090c575060005b6001838101546002918116156101000260001901160481101561090c57818160040181518110156100075790602001015160f860020a900460f860020a02836001016000508281546001816001161561010002031660029004811015610007578154600116156108e25790600052602060002090602091828204019190065b601f036101000a81548160ff0219169060f860020a84040217905550600101610863565b5061026d565b505050565b604080517f5f5f6469672875696e74323536290000000000000000000000000000000000008152815190819003600e01812060e060020a9081900481028190049081028252600019850160048301529151600160a060020a03301692916102bc86029160248281019260009291908290030181838887f19450505050505b919050565b5060005b92915050565b818360020203836064020460c8039050610998565b8a5460a060020a900460e060020a0260001415610a23578a5460028c0154600160a060020a039190911690895a03908d6001016000506040518082805460018160011615610100020316600290048015610ae55780601f10610aba57610100808354040283529160200191610ae5565b60018b8101546002918116156101000260001901160460001415610b1a578a5460028c0154600160a060020a039190911690895a03908d60000160149054906101000a900460e060020a0260e060020a900491906040518360e060020a028152600401809050600060405180830381858988f19450505050508b60080160006101000a81548160ff02191690830217905550610bfa565b820191906000526020600020905b815481529060010190602001808311610ac857829003601f168201915b5050915050600060405180830381858888f193505050508b60080160006101000a81548160ff02191690830217905550610bfa565b8a5460028c0154600160a060020a039190911690895a03908d60000160149054906101000a900460e060020a0260e060020a900491908e6001016000506040518460e060020a0281526004018082805460018160011615610100020316600290048015610bc85780601f10610b9d57610100808354040283529160200191610bc8565b820191906000526020600020905b815481529060010190602001808311610bab57829003601f168201915b5050915050600060405180830381858988f19450505050508b60080160006101000a81548160ff021916908302179055505b85600160a060020a031663938b5f326040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100075750505060405180519060200150600160a060020a031660405180807f75706461746544656661756c745061796d656e742829000000000000000000008152602001506016019050604051809103902060e060020a8091040260e060020a90046040518160e060020a0281526004018090506000604051808303816000876161da5a03f15050505060038b0154610cc8903a6103b7565b60058c0154909550620100009004600160a060020a03908116908a161415610cf65760068b01549350610d38565b85600160a060020a031663c6502da86040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610007575050604051519450505b6064858502048b6007016000505401925060648587600160a060020a031663625cc4656040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000757505050604051805190602001500204915060008b60070160005081905550865a8b03013a029050610db7898285016103d8565b9250610dd773d3cda913deb6f67967b99d67acdfa1712c293601836103d8565b6040805160088e01548482526020820187905281830184905260ff1660608201529051919350600160a060020a038b16917f4538b7ec91dae8fada01e66a052482086d3e690c3db5a80457fbcd55457b4ae19181900360800190a25050505050505050505050565b505b309050610e8c81600160a060020a031663ae45850b6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100075750506040515190316103d8565b505050600801805462ff0000191662010000179055565b600093505b505050919050565b600e19919091019081851115610f075782600160a060020a031663c6502da86040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610007575050604051519450610ea89050565b60ef19919091019081851115610ea357818503905060f08184600160a060020a031663c6502da86040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610007575050506040518051906020015002049350610ea8565b03905090565b6006860181905560058601805475ffffffffffffffffffffffffffffffffffffffff000019166201000087021790556007860184905560408051600160a060020a0387168152602081019290925280517fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a9281900390910190a15b949350505050565b610f7343610531565b600192505b50509392505050565b60108185031015610fff576005860154620100009004600160a060020a03166000148061105057506005860154620100009004600160a060020a03908116908616145b9250611004565b600092505b505092915050565b91503090506000821480156110c4575080600160a060020a031663ae45850b6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000757505060405151600160a060020a039081169086161490505b156110d2576001925061105c565b6007821415611057576001925061105c565b6008860154610100900460ff161561111e577f414c52454144595f43414c4c454400000000000000000000000000000000000091506112dd565b80600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f115610007575050604051514310905080611206575080600160a060020a031663a06db7dc6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100075750506040805180517f0a16697a000000000000000000000000000000000000000000000000000000008252915160ff9092169291630a16697a9160048181019260209290919082900301816000876161da5a03f1156100075750506040515191909101431190505b15611233577f4e4f545f494e5f43414c4c5f57494e444f57000000000000000000000000000091506112dd565b61123e8686436100d3565b151561126c577f4e4f545f415554484f52495a454400000000000000000000000000000000000091506112dd565b6005860154600061ffff91909116118015611299575032600160a060020a031685600160a060020a031614155b80156112b4575060058601546112b29061ffff166101b4565b155b156112dd577f535441434b5f544f4f5f4445455000000000000000000000000000000000000091505b60008214610fff5760408051600160a060020a03871681526020810184905281517fdcb278834ca505ad219cf8e4b5d11f026080abef6ec68e249ea5e4d9bb3dc7b2929181900390910190a16000925061100456" + }, + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x98e1c608601c2496b2", + "nonce": "218916", + "code": "0x" + }, + "0x651913977e8140c323997fce5e03c19e0015eebf": { + "balance": "0x0", + "code": "0x", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000d": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x6c8f2a135f6ed072de4503bd7c4999a1a17f824b": { + "balance": "0x0", + "nonce": "237", + "code": "0x6060604052361561027c5760e060020a600035046301991313811461027e57806303d22885146102ca5780630450991814610323578063049ae734146103705780630ce46c43146103c35780630e85023914610602578063112e39a8146106755780631b4fa6ab146106c25780631e74a2d3146106d057806326a7985a146106fd5780633017fe2414610753578063346cabbc1461075c578063373a1bc3146107d55780633a9e74331461081e5780633c2c21a01461086e5780633d9ce89b146108ba578063480b70bd1461092f578063481078431461097e57806348f0518714610a0e5780634c471cde14610a865780634db3da8314610b09578063523ccfa814610b4f578063586a69fa14610be05780635a9f2def14610c3657806364ee49fe14610caf57806367beaccb14610d055780636840246014610d74578063795b9a6f14610dca5780637b55c8b514610e415780637c73f84614610ee15780638c0e156d14610f145780638c1d01c814610f605780638e46afa914610f69578063938c430714610fc0578063971c803f146111555780639772c982146111ac57806398c9cdf41461122857806398e00e541461127f5780639f927be7146112d5578063a00aede914611383578063a1c0539d146113d3578063aff21c6514611449578063b152f19e14611474578063b549793d146114cb578063b5b33eda1461154b578063bbc6eb1f1461159b578063c0f68859146115ab578063c3a2c0c314611601578063c43d05751461164b578063d8e5c04814611694578063dbfef71014611228578063e29fb547146116e7578063e6470fbe1461173a578063ea27a8811461174c578063ee77fe86146117d1578063f158458c14611851575b005b611882600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503387876020604051908101604052806000815260200150612225610f6d565b61188260043560243560443560643560843560a43560c435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001600050338b8a6020604051908101604052806000815260200150896125196106c6565b611882600435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503385600060e060020a026020604051908101604052806000815260200150611e4a610f6d565b611882600435602435604435606435608435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503389896020604051908101604052806000815260200150886124e86106c6565b604080516020604435600481810135601f8101849004840285018401909552848452611882948135946024803595939460649492939101918190840183828082843750506040805160a08082019092529597963596608435969095506101449450925060a491506005908390839080828437509095505050505050604080518082018252600160a060020a03338116825288166020820152815160c0810190925260009173e54d323f9ef17c1f0dede47ecc86a9718fe5ea349163e3042c0f91600191908a908a9089908b90808b8b9090602002015181526020018b60016005811015610002579090602002015181526020018b60026005811015610002579090602002015181526020018b60036005811015610002579090602002015181526020018b6004600581101561000257909060200201518152602001348152602001506040518860e060020a02815260040180888152602001876002602002808383829060006004602084601f0104600f02600301f150905001868152602001806020018560ff1681526020018461ffff168152602001836006602002808383829060006004602084601f0104600f02600301f1509050018281038252868181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105d25780820380516001836020036101000a031916815260200191505b509850505050505050505060206040518083038160008760325a03f2156100025750506040515191506124cd9050565b60408051602060248035600481810135601f81018590048502860185019096528585526118829581359591946044949293909201918190840183828082843750949650505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808787611e64610f6d565b611882600435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333600060e060020a026020604051908101604052806000815260200150611d28610f6d565b61189f5b6000611bf8611159565b6118b7600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a881600060005054611a9561159f565b6118b7600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346326a7985a6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b6118b760075b90565b604080516020606435600481810135601f8101849004840285018401909552848452611882948135946024803595604435956084949201919081908401838280828437509496505093359350505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160013389898861224b610f6d565b611882600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503386866020604051908101604052806000815260200150611e64610f6d565b611882600435602435604435606435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333896020604051908101604052806000815260200150886123bc6106c6565b611882600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503387866020604051908101604052806000815260200150611f8d610f6d565b60408051602060248035600481810135601f810185900485028601850190965285855261188295813595919460449492939092019181908401838280828437509496505093359350505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808888612225610f6d565b611882600435602435604435606435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503388886020604051908101604052806000815260200150612388610f6d565b611882600435604080517fc4144b2600000000000000000000000000000000000000000000000000000000815260016004820152600160a060020a03831660248201529051600091737c1eb207c07e7ab13cf245585bd03d0fa478d0349163c4144b26916044818101926020929091908290030181878760325a03f215610002575050604051519150611b409050565b604080516020604435600481810135601f81018490048402850184019095528484526118829481359460248035959394606494929391019181908401838280828437509496505093359350505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133888888612238610f6d565b604080516020604435600481810135601f810184900484028501840190955284845261188294813594602480359593946064949293910191819084018382808284375094965050933593505060843591505060a43560c435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338b8b8b896126536106c6565b611882600435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333866020604051908101604052806000815260200150611e4a610f6d565b6118b76004355b604080517fed5bd7ea00000000000000000000000000000000000000000000000000000000815260016004820152600160a060020a03831660248201529051600091737c1eb207c07e7ab13cf245585bd03d0fa478d0349163ed5bd7ea916044818101926020929091908290030181878760325a03f215610002575050604051519150611b409050565b61189f600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463586a69fa6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b60408051602060248035600481810135601f81018590048502860185019096528585526118829581359591946044949293909201918190840183828082843750949650509335935050606435915050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808989612388610f6d565b61188260043560243560443560643560843560a435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001600050338a896020604051908101604052806000815260200150886124d76106c6565b6040805160206004803580820135601f8101849004840285018401909552848452611882949193602493909291840191908190840183828082843750949650505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808587611e4a610f6d565b61188260043560243560443560643560843560a435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001600050338a8a60206040519081016040528060008152602001508961262d6106c6565b604080516020606435600481810135601f810184900484028501840190955284845261188294813594602480359560443595608494920191908190840183828082843750949650505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338888876120c7610f6d565b604080516020604435600481810135601f81018490048402850184019095528484526118829481359460248035959394606494929391019181908401838280828437505060408051608080820190925295979635969561010495509350608492508591508390839080828437509095505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338989898961263a6106c6565b6118b7600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a881858585611ba361122c565b611882600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001600050333388602060405190810160405280600081526020015061236e610f6d565b6118b760005481565b6118c95b600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea34638e46afa96040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b60408051602060248035600481810135601f8101859004850286018501909652858552611882958135959194604494929390920191819084018382808284375094965050933593505060643591505060843560a43560c43560e43561010435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600160005033338e8e8d8f8e8e8e8e8e346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156111195780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519b9a5050505050505050505050565b61189f5b600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463971c803f6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b604080516020604435600481810135601f8101849004840285018401909552848452611882948135946024803595939460649492939101918190840183828082843750949650509335935050608435915050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338989896123a2610f6d565b6118b75b600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346398c9cdf46040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b6118b7600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346398e00e546040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b611882600435604080517fe6ce3a6a000000000000000000000000000000000000000000000000000000008152600160048201527f3e3d0000000000000000000000000000000000000000000000000000000000006024820152604481018390529051600091737c1eb207c07e7ab13cf245585bd03d0fa478d0349163e6ce3a6a916064818101926020929091908290030181878760325a03f215610002575050604051519150611b409050565b611882600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503385600060e060020a0260206040519081016040528060008152602001506121ef610f6d565b604080516020604435600481810135601f8101849004840285018401909552848452611882948135946024803595939460649492939101918190840183828082843750949650505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338787876120b5610f6d565b6118b7600435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a88183611b4561159f565b6118b75b600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463b152f19e6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b60408051602060248035600481810135601f8101859004850286018501909652858552611882958135959194604494929390920191819084018382808284375094965050933593505060643591505060843560a435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808b8b8961262d6106c6565b611882600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503386600060e060020a026020604051908101604052806000815260200150612200610f6d565b6118b75b60005460649004610759565b6118b7600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463c0f688596040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b611882600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333600060e060020a026020604051908101604052806000815260200150611bff610f6d565b611882600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333876020604051908101604052806000815260200150612200610f6d565b611882600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503387600060e060020a026020604051908101604052806000815260200150612213610f6d565b611882600435602435604435606435608435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600160005033338a60206040519081016040528060008152602001508961250c6106c6565b61027c6000600060006118e033610b56565b6118b7600435602435604435606435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a881868686866040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f215610002575050604051519150505b949350505050565b604080516020604435600481810135601f810184900484028501840190955284845261188294813594602480359593946064949293910191819084018382808284375094965050933593505060843591505060a435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338a8a8a886124fa6106c6565b6118b7600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a88184846000611b4f61122c565b60408051600160a060020a03929092168252519081900360200190f35b6040805161ffff929092168252519081900360200190f35b60408051918252519081900360200190f35b6040805160ff929092168252519081900360200190f35b15611a905733925082600160a060020a031663c6502da86040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fc6803622000000000000000000000000000000000000000000000000000000008252915191945063c680362291600482810192602092919082900301816000876161da5a03f11561000257505060405151905080156119d1575082600160a060020a031663d379be236040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151600160a060020a03166000141590505b80156119dd5750600082115b80156119ec5750600054600190115b15611a90578183600160a060020a031663830953ab6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040515160640291909104915050604281118015611a4d5750600054829011155b15611a675760008054612710612711909102049055611a90565b602181108015611a7a5750600054829010155b15611a90576000805461271061270f9091020490555b505050565b6000611a9f61122c565b6040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f2156100025750506040515191506107599050565b6040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f215610002575050604051519150505b919050565b6000611af261122c565b6040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f215610002575050604051519150505b92915050565b6040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f215610002575050604051519150505b9392505050565b9050610759565b611c076106c6565b6000611c11611478565b611c1961122c565b600054611c2461159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611cf25780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f2156100025750506040515191506107599050565b611d306106c6565b60008b611d3b61122c565b600054611d4661159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611e145780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519150611b409050565b611e526106c6565b6000611e5c611478565b611d3b61122c565b611e6c6106c6565b6000611e76611478565b611e7e61122c565b600054611e8961159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611f575780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519150611b9d9050565b611f956106c6565b8b611f9e611478565b611fa661122c565b600054611fb161159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561207f5780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519150611bf19050565b6120bd6106c6565b6000611f9e611478565b6120cf6106c6565b8b6120d8611478565b6120e061122c565b6000546120eb61159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156121b95780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f2156100025750506040515191506117c99050565b6121f76106c6565b8b611e76611478565b6122086106c6565b60008b611e7e61122c565b61221b6106c6565b8a8c611fa661122c565b61222d6106c6565b60008b611fa661122c565b6122406106c6565b60008b6120e061122c565b6122536106c6565b8c8b61225d61122c565b60005461226861159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156123365780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f21561000257505060405151979650505050505050565b6123766106c6565b60008c8c600060005054611fb161159f565b6123906106c6565b60008c8c6000600050546120eb61159f565b6123aa6106c6565b60008c8c60006000505461226861159f565b60008d8d6000600050546120eb61159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561249c5780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519150505b9695505050505050565b8e8d8d6000600050546123ce61159f565b60008d8d60006000505461226861159f565b60008d8d6000600050546123ce61159f565b60008e8e8d61226861159f565b8f8e8e8d61252561159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156125f35780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519998505050505050505050565b60008e8e8d6123ce61159f565b8a5160208c015160408d015160608e015161226861159f565b60008e8e8d61252561159f56", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000011f8119429ed3a", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000f5d861791e76fa01433e0d7421aee565290e4afe", + "0x031b9ec274101cc3ccff4d6d98ef4513742dadbaadba538bff48b88403253234": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x20ef51bb8ea9e8e8d5e2c17d28e47285698893c1017db4b4e40b792358a3dbc7": "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x26cba0705aade77fa0f9275b68d01fb71206a44abd3a4f5a838f7241efbc8abd": "0x000000000000000000000000c9a2bfd279fe57e7651e5d9f29bb1793c9a1cf01", + "0x26cba0705aade77fa0f9275b68d01fb71206a44abd3a4f5a838f7241efbc8abf": "0x00000000000000000000000042e69cd0a17ae9992f9ad93d136c4bb0d95e3230", + "0x26cba0705aade77fa0f9275b68d01fb71206a44abd3a4f5a838f7241efbc8ac2": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x37a551428681c06e6f97b79bb6c8c325935dc1a51b31a982594f40f2dd794dfb": "0x000000000000000000000000f5d861791e76fa01433e0d7421aee565290e4afe", + "0x37a551428681c06e6f97b79bb6c8c325935dc1a51b31a982594f40f2dd794dfc": "0x00000000000000000000000000000000000000000000000000000000000f6897", + "0x37a551428681c06e6f97b79bb6c8c325935dc1a51b31a982594f40f2dd794dfd": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x37a551428681c06e6f97b79bb6c8c325935dc1a51b31a982594f40f2dd794dfe": "0x0000000000000000000000002859ddf2877c46d54e67b6becdb1cafb8ef4a458", + "0x37a551428681c06e6f97b79bb6c8c325935dc1a51b31a982594f40f2dd794dff": "0x000000000000000000000000b7df3c43a8b13ecf45777c267404e15c7cdb04c9", + "0x37a551428681c06e6f97b79bb6c8c325935dc1a51b31a982594f40f2dd794e00": "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x3b20a4b931bc4ae9450774ee52b8f5da1b248d23e61cd20c09b25662f73894fd": "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x3b99aee1e3090227401ac2055c861246ca6ec62f426b4b4d74df88510f841b89": "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x49f03a2c2f4fd666a32141fb324283b6f84a1d07b5fa435669fdb55766aef711": "0x000000000000000000000000a4d91b341f0e9a7000be916a668408b463f4c38c", + "0x49f03a2c2f4fd666a32141fb324283b6f84a1d07b5fa435669fdb55766aef712": "0x0000000000000000000000000000000000000000000000000000000000102ce9", + "0x49f03a2c2f4fd666a32141fb324283b6f84a1d07b5fa435669fdb55766aef713": "0x000000000000000000000000fd97a0d81cc92eecd52452831930b27889925ef0", + "0x49f03a2c2f4fd666a32141fb324283b6f84a1d07b5fa435669fdb55766aef714": "0x00000000000000000000000016917c151bb1399852a0741eb7b317b443e2cfa3", + "0x49f03a2c2f4fd666a32141fb324283b6f84a1d07b5fa435669fdb55766aef715": "0x000000000000000000000000d7b0e93fa8386b17fb5d1cf934076203dcc122f3", + "0x49f03a2c2f4fd666a32141fb324283b6f84a1d07b5fa435669fdb55766aef716": "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x5d866e5ddc53cb4c50f232302c51f03204d70c867baf663c9211cc229676a3fe": "0x000000000000000000000000c5ef24ec3bf0e3522cfc8e53f3e076b043547ce1", + "0x5d866e5ddc53cb4c50f232302c51f03204d70c867baf663c9211cc229676a3ff": "0x00000000000000000000000000000000000000000000000000000000000fff67", + "0x5d866e5ddc53cb4c50f232302c51f03204d70c867baf663c9211cc229676a400": "0x000000000000000000000000b7df3c43a8b13ecf45777c267404e15c7cdb04c9", + "0x5d866e5ddc53cb4c50f232302c51f03204d70c867baf663c9211cc229676a401": "0x00000000000000000000000010fc2e8ba5f40336c3576ffaa25177f1cdedf836", + "0x5d866e5ddc53cb4c50f232302c51f03204d70c867baf663c9211cc229676a402": "0x000000000000000000000000fd97a0d81cc92eecd52452831930b27889925ef0", + "0x5d866e5ddc53cb4c50f232302c51f03204d70c867baf663c9211cc229676a403": "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5ba": "0x00000000000000000000000042e69cd0a17ae9992f9ad93d136c4bb0d95e3230", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bb": "0x000000000000000000000000000000000000000000000000000000000010347b", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bc": "0x000000000000000000000000d7b0e93fa8386b17fb5d1cf934076203dcc122f3", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bd": "0x000000000000000000000000c9a2bfd279fe57e7651e5d9f29bb1793c9a1cf01", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5be": "0x000000000000000000000000741467b251fca923d6229c4b439078b55dca233b", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bf": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2751": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2752": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2753": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2754": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2755": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2756": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xa9e249fecbfa0518be95c32972ad551c71206081844335006bb2a349490826a7": "0x000000000000000000000000b7df3c43a8b13ecf45777c267404e15c7cdb04c9", + "0xa9e249fecbfa0518be95c32972ad551c71206081844335006bb2a349490826a8": "0x00000000000000000000000000000000000000000000000000000000000fe13d", + "0xa9e249fecbfa0518be95c32972ad551c71206081844335006bb2a349490826a9": "0x000000000000000000000000f5d861791e76fa01433e0d7421aee565290e4afe", + "0xa9e249fecbfa0518be95c32972ad551c71206081844335006bb2a349490826aa": "0x00000000000000000000000063110531142fb314118164ff579ba52746504408", + "0xa9e249fecbfa0518be95c32972ad551c71206081844335006bb2a349490826ab": "0x000000000000000000000000c5ef24ec3bf0e3522cfc8e53f3e076b043547ce1", + "0xa9e249fecbfa0518be95c32972ad551c71206081844335006bb2a349490826ac": "0x0000000000000000000000000000000000000000000000000000000000000007", + "0xac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c890780": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xccd2cbc946692be8ade97db99353304e3af0fa6202f93649d4e185ad8b1f385c": "0x0000000000000000000000000000000000000000000000000000000000000004", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4ef": "0x000000000000000000000000d7b0e93fa8386b17fb5d1cf934076203dcc122f3", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f0": "0x00000000000000000000000000000000000000000000000000000000001030b3", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f1": "0x000000000000000000000000a4d91b341f0e9a7000be916a668408b463f4c38c", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f2": "0x000000000000000000000000dd87a67740c2acf48a31829783a095a81c3628d9", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f3": "0x00000000000000000000000042e69cd0a17ae9992f9ad93d136c4bb0d95e3230", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f4": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0xdabde47554d6a6cfcff3c968abb145f298585fafa9e24c10fc526269794bd626": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64db7": "0x000000000000000000000000741467b251fca923d6229c4b439078b55dca233b", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64db8": "0x000000000000000000000000000000000000000000000000000000000010365c", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64db9": "0x00000000000000000000000042e69cd0a17ae9992f9ad93d136c4bb0d95e3230", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64dba": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64dbb": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64dbc": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xfbba286dd5525a6ed3322411df4f261c98e43b123fef71777adc2b44d705bdec": "0x000000000000000000000000fd97a0d81cc92eecd52452831930b27889925ef0", + "0xfbba286dd5525a6ed3322411df4f261c98e43b123fef71777adc2b44d705bded": "0x0000000000000000000000000000000000000000000000000000000000101dc2", + "0xfbba286dd5525a6ed3322411df4f261c98e43b123fef71777adc2b44d705bdee": "0x000000000000000000000000c5ef24ec3bf0e3522cfc8e53f3e076b043547ce1", + "0xfbba286dd5525a6ed3322411df4f261c98e43b123fef71777adc2b44d705bdef": "0x000000000000000000000000173243e117a6382211b1ac91eeb262f4a7021c16", + "0xfbba286dd5525a6ed3322411df4f261c98e43b123fef71777adc2b44d705bdf0": "0x000000000000000000000000a4d91b341f0e9a7000be916a668408b463f4c38c", + "0xfbba286dd5525a6ed3322411df4f261c98e43b123fef71777adc2b44d705bdf1": "0x0000000000000000000000000000000000000000000000000000000000000005" + } + }, + "0x741467b251fca923d6229c4b439078b55dca233b": { + "balance": "0x29c613529e8218f8", + "code": "0x606060405236156101a05760e060020a60003504630924120081146101c25780630a16697a146101cf5780630fd1f94e146101d8578063137c638b1461022e57806321835af61461023b57806324032866146102545780632f95b833146102d65780633017fe24146102e55780633233c686146102ef57806337f4c00e146102fa5780634500054f146103055780634e417a98146103785780634e71d92d146103e15780634f059a43146103f35780636146195414610451578063625cc4651461046157806367ce940d1461046a5780637d298ee314610477578063830953ab146104f9578063938b5f321461050457806395ee122114610516578063974654f41461052a578063a06db7dc14610535578063a9d2293d14610541578063ae45850b14610597578063b0f07e44146105a9578063c19d93fb146105cb578063c6502da81461062e578063c680362214610637578063ca94692d1461064a578063cc3471af14610673578063d379be23146106c9578063d62457f6146106e3578063ea8a1af0146106ee578063f5562753146107f3578063f6b4dfb414610854575b610868600080548190600160a060020a03908116339091161461087a57610994565b610868600b5460ff165b90565b610868600d5481565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc80630fd1f94e6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b6108685b62012cc86101cc565b61086860043560008160001415610dc65750600161084f565b6108686004356024356000731deeda36e15ec9e80f3d7414d67a4803ae45fc80630bd295e6600360005085856040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f215610002575050604051519150505b92915050565b61099860085461ffff166101cc565b61086860026101cc565b610868600a546101cc565b6108686006546101cc565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a09431546003600050336040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b6109af60408051602081810183526000825282516004805460026001821615610100026000190190911604601f81018490048402830184019095528482529293909291830182828015610a7d5780601f10610a5257610100808354040283529160200191610a7d565b61086860006000600180610b7b6105cf565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063f5562753436040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1d6000600480610c986105cf565b61086860025481565b6108685b620186a06101cc565b6108686004356024355b6000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a1873db6600360005085856040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f2156100025750506040515191506102d09050565b6108686009546101cc565b610a1f600c54600160a060020a031681565b610868600b5462010000900460ff166101cc565b6108686007546101cc565b610a3c600e5460ff1681565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a9d2293d6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1f600054600160a060020a031681565b610868600080548190600160a060020a039081163390911614610a8957610994565b6108685b6000731deeda36e15ec9e80f3d7414d67a4803ae45fc80635054d98a60036000506040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b61086860015481565b610868600b54610100900460ff166101cc565b61086860035474010000000000000000000000000000000000000000900460e060020a026101cc565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063cc3471af6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1f600854620100009004600160a060020a03166101cc565b6108686005546101cc565b610a1d604080517fa09431540000000000000000000000000000000000000000000000000000000081526003600482015233600160a060020a031660248201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc809163a0943154916044808301926020929190829003018160008760325a03f215610002575050604051511590506107f157604080517f7e9265620000000000000000000000000000000000000000000000000000000081526003600482015233600160a060020a031660248201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc8091637e9265629160448083019260009291908290030181838760325a03f215610002575050505b565b6108686004356000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063f5562753836040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f215610002575050604051519150505b919050565b610a1f600354600160a060020a03166101cc565b60408051918252519081900360200190f35b60045460006002600183161561010002600019019092169190910411156108a45760009150610994565b6108ac6105cf565b9050600081141580156108c0575060018114155b80156108cd575060028114155b156108db5760009150610994565b600480546000828152602060026001841615610100026000190190931692909204601f908101929092047f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b9081019236929083901061095d5782800160ff198235161785555b5061098d9291505b808211156109945760008155600101610949565b82800160010185558215610941579182015b8281111561094157823582600050559160200191906001019061096f565b5050600191505b5090565b6040805161ffff9092168252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015610a0f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b005b60408051600160a060020a03929092168252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b815481529060010190602001808311610a6057829003601f168201915b505050505090506101cc565b6004546000600260018316156101000260001901909216919091041115610ab35760009150610994565b610abb6105cf565b905060008114158015610acf575060018114155b8015610adc575060028114155b15610aea5760009150610994565b604080517f7c0278fc000000000000000000000000000000000000000000000000000000008152600360048201818152602483019384523660448401819052731deeda36e15ec9e80f3d7414d67a4803ae45fc8094637c0278fc946000939190606401848480828437820191505094505050505060006040518083038160008760325a03f215610002575050505090565b1415610c8557604080516001547f0fee183d0000000000000000000000000000000000000000000000000000000082526003600483015233600160a060020a0316602483015234604483015260648201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc8091630fee183d916084828101926020929190829003018160008760325a03f21561000257505060405151925050811515610c8a577389efe605e9ecbe22849cd85d5449cc946c26f8f36312c82bcc33346040518360e060020a0281526004018083600160a060020a031681526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515115159050610c8a57610002565b505090565b81925050610994565b505b50565b1415610c93575a9150610cab3383610481565b1515610cb75750610c95565b731deeda36e15ec9e80f3d7414d67a4803ae45fc8063da46be0a60038433610cdd61046e565b610ce5610232565b6040518660e060020a0281526004018086815260200185815260200184600160a060020a031681526020018381526020018281526020019550505050505060006040518083038160008760325a03f21561000257505050610c933360408051600080547fc17e6817000000000000000000000000000000000000000000000000000000008352600160a060020a03908116600484015230163160248301529151731deeda36e15ec9e80f3d7414d67a4803ae45fc809263c17e68179260448082019360209390928390039091019082908760325a03f2156100025750505050565b30600160a060020a031660405180807f5f5f6469672875696e7432353629000000000000000000000000000000000000815260200150600e019050604051809103902060e060020a8091040260e060020a9004600184036040518260e060020a0281526004018082815260200191505060006040518083038160008760325a03f292505050151561084f5761000256", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000007dd677b54fc954824a7bc49bd26cbdfa12c75adf", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000011f79bd42b0c7c", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000002dfeff8fca5d", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x00000000000000003defb9627dd677b54fc954824a7bc49bd26cbdfa12c75adf", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000000000000ba43b7400", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x00000000000000000000000000000000000000000000000000000000001e8480", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x000000000000000000000000000000000000000000000000000000000000000a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x0000000000000000000000006c8f2a135f6ed072de4503bd7c4999a1a17f824b", + "0x000000000000000000000000000000000000000000000000000000000000000d": "0x000000000000000000000000000000000000000000000000000000000010365c", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0x00000000000000000000000000000000000000000000000000000000000000ff" + } + }, + "0x7c1eb207c07e7ab13cf245585bd03d0fa478d034": { + "balance": "0x0", + "code": "0x650200d2f18c7350606060405236156100a05760e060020a60003504630e9f1a3c81146100a55780632b4096b4146100c95780636ec13982146100eb578063a3119e571461010d578063a749f19b1461012f578063ab7366f714610151578063bacd69581461017f578063bfdf87c0146101c2578063c4144b26146101e1578063caa46c9c1461023c578063e6ce3a6a14610297578063ed5bd7ea146102b6575b610007565b6102d960043560243560008181526001830160205260409020600401545b92915050565b6102d960043560243560008181526001830160205260409020600301546100c3565b6102d960043560243560008181526001830160205260409020600201546100c3565b6102d960043560243560008181526001838101602052604090912001546100c3565b6102d960043560243560008181526001830160205260409020600501546100c3565b6102eb6004356024355b600081815260018301602052604081208054829182918291908614610790576101b9565b6102eb600435602435604435600082815260018401602052604081205481908190819086141561068a576040812060010154851415610680575b50505050505050565b6102d960043560243560008181526001830160205260409020546100c3565b6102d96004356024355b6040805160c08101825260008082526020828101829052828401829052606083018290526080830182905260a08301829052848252600186019052918220805490919083908114156102fb576102f2565b6102d96004356024355b6040805160c08101825260008082526020828101829052828401829052606083018290526080830182905260a08301829052848252600186019052918220805490919083908114156104c0576102f2565b6102d960043560243560443582546000908181811415610a6557610a8c565b6102d96004356024356000818152600183016020526040812060050154116100c3565b60408051918252519081900360200190f35b005b815193505b50505092915050565b60048301546000146103d257600483810154600090815260018881016020908152604092839020835160c081018552815481529281015491830191909152600281015492820192909252600382015460608201529181015460808301526005015460a082015291505b60608201516000146102ed57606091820151600090815260018781016020908152604092839020835160c081018552815481529281015491830191909152600281015492820192909252600382015493810193909352600481015460808401526005015460a0830152610364565b600283015460001461045b5750506002810154600081815260018681016020908152604092839020835160c081018552865481529286015491830191909152918101929092526003830154606083015260048301546080830152600583015460a08301525b81516003820154141561044d57805493506102f2565b600281015460001415610464575b600093506102f2565b6040805160c08101825282548152600183810154602083810191909152600285015483850181905260038601546060850152600486015460808501526005959095015460a0840152600094855290890190529120909150610437565b600383015460001461059757600383810154600090815260018881016020908152604092839020835160c081018552815481529281015491830191909152600281015492820192909252918101546060830152600481015460808301526005015460a082015291505b60808201516000146102ed57608091820151600090815260018781016020908152604092839020835160c081018552815481529281015491830191909152600281015492820192909252600382015460608201526004820154938101939093526005015460a0830152610529565b600283015460001461045b5750506002810154600081815260018681016020908152604092839020835160c081018552865481529286015491830191909152918101929092526003830154606083015260048301546080830152600583015460a08301525b81516004820154141561061257805493506102f2565b6002810154600014156106245761045b565b6040805160c08101825282548152600183810154602083810191909152600285015483850181905260038601546060850152600486015460808501526005959095015460a08401526000948552908901905291209091506105fc565b61068a878761015b565b86546000925082141561069b578587555b508554600090815260018701602052604090205b8054600014156107255785815560028101829055600181018590556101b987875b60008181526001830160205260408120905b8154610d8e9085905b60008181526001830160205260408082206004810154835281832060059081015460038301548552929093209092015403905b5092915050565b60018101548154925085126107625760048101546000141561074957600481018690555b60040154600090815260018701602052604090206106af565b60038101546000141561077757600381018690555b60030154600090815260018701602052604090206106af565b600381015460001415806107a957506004810154600014155b156107cf576003810154600014610826578054600188019060009061083b908a90610246565b6002810154600014610a285760028101546000908152600188016020526040902060038101548254919550141561080857600060038501555b60048401548154141561081d57600060048501555b83549150610a2d565b80546001880190600090610852908a906101eb565b815260208101919091526040016000209450610865565b8152602081019190915260400160002094505b600285015460009081526001880160205260409020600381015486549195509092508214156108b9576004850154600385018190556000146108b95760048501546000908152604090208454600282015592505b60048401548554141561091357600385015460048501819055600014610913578660010160005060008660030160005054815260200190815260200160002060005092508250836000016000505483600201600050819055505b60028082015490860181905560001461098457866001016000506000826002016000505481526020019081526020016000206000509350835080600001600050548460030160005054141561096a57845460038501555b60048401548154141561097f57845460048501555b610989565b845487555b6003818101549086018190556000146109d6578660010160005060008260030160005054815260200190815260200160002060005092508250846000016000505483600201600050819055505b600481810154908601819055600014610a23578660010160005060008260040160005054815260200190815260200160002060005092508250846000016000505483600201600050819055505b610a2d565b600087555b6000808255600182018190556002820181905560038201819055600482018190556005820181905582146101b9576101b987836106d0565b50600081815260018601602052604090205b6001810154610a95908686610ad4565b805492505b50509392505050565b15610b915760fa60020a600f02851480610ab6575060f060020a613c3d0285145b15610af157600481015460001415610b3a5780549250610a8c565b86865b600060f960020a601f02831415610ce357508083135b9392505050565b60f960020a601f02851480610b0d575060f060020a613e3d0285145b80610b1f575060f060020a613d3d0285145b15610b9157600381015460001415610bc85780549250610a8c565b610b73610ad1878360040160005054600081815260018301602052604081205b600381015460001415610d61576001810154915061071e565b15610a87576004015460009081526001860160205260409020610a77565b60fa60020a600f02851480610bad575060f060020a613c3d0285145b15610c1f57600381015460001415610c565760009250610a8c565b610c01610ad1878360030160005054600081815260018301602052604081205b600481015460001415610d48576001810154915061071e565b15610a87576003015460009081526001860160205260409020610a77565b60f960020a601f02851480610c3b575060f060020a613e3d0285145b15610c6f57600481015460001415610ca25760009250610a8c565b6003015460009081526001860160205260409020610a77565b60f060020a613d3d02851415610cde57600181015484901215610cbb57600481015460001415610ca25760009250610a8c565b6004015460009081526001860160205260409020610a77565b600181015484901315610cde57600381015460001415610c565760009250610a8c565b610a77565b60fa60020a600f02831415610cfb5750808312610aea565b60f060020a613e3d02831415610d15575080831215610aea565b60f060020a613c3d02831415610d2f575080831315610aea565b60f060020a613d3d028314156100a05750828114610aea565b6004015460009081526001840160205260409020610be8565b6003015460009081526001840160205260409020610b5a565b600282015460001415610fbd575b50505050565b90508060021415610e2657610daa8483600301600050546106eb565b6000191415610dc457610dc4848360030160005054610dfe565b8154610e269085905b60008181526001830160205260408120600381015490919081908190811415610ffb57610007565b8154610e5a9085905b60008181526001830160205260408120600481015490919081908190811415610e7f57610007565b806001191415610e5a57610e418483600401600050546106eb565b60011415610df557610df5848360040160005054610dcd565b8060001913158015610e6d575060018113155b15610d7a578154610d7a908590610f7a565b6004840180546000908152600188016020526040812060028088015490820181905592829055945014610f0f57856001016000506000856002016000505481526020019081526020016000206000509150836000016000505482600301600050541415610efa57826000016000505482600301600050819055505b835460048301541415610f0f57825460048301555b6003830154600014610f40575060038201546000908152600186016020526040902080546004850155835460028201555b82546002808601919091558454600385015583015460001415610f7157826000016000505486600001600050819055505b8354610fe69087905b6000818152600183016020526040808220600381015483528183206005908101546004830154855292842001549092610fd99291908183106110fa5750816100c3565b60029091015460009081526001840160205260409020906106e2565b6001016005820155505050565b8254610ff3908790610f7a565b505050505050565b600384018054600090815260018801602052604081206002808801549082018190559282905594501461108b5785600101600050600085600201600050548152602001908152602001600020600050915083600001600050548260030160005054141561107657826000016000505482600301600050819055505b83546004830154141561108b57825460048301555b60048301546000146110bd57506004820154600081815260018701602052604090206003850191909155835460028201555b82546002808601919091558454600485015583015460001415610f7157826000016000505486600001600050819055508354610fe6908790610f7a565b50806100c356" + }, + "0x7dd677b54fc954824a7bc49bd26cbdfa12c75adf": { + "balance": "0xd7a58f5b73b4b6c4", + "code": "0x606060405236156100985760e060020a60003504633896002781146100e15780633defb962146100ea5780633f4be8891461010c5780634136aa351461011f5780634a420138146101a057806369c1a7121461028c5780638129fc1c146102955780638da5cb5b146102a6578063ae45850b146102b8578063af3309d8146102cc578063ea8a1af0146102d5578063ead50da3146102f4575b610308671bc16d674ec8000030600160a060020a03163110156100df57600554604051600160a060020a03918216916000913091909116319082818181858883f150505050505b565b61030a60005481565b610308671bc16d674ec8000030600160a060020a031631101561040f576100df565b61031c600454600160a060020a03165b90565b61030a5b600080548190118015610199575060408051600480547f0a16697a0000000000000000000000000000000000000000000000000000000083529251600160a060020a039390931692630a16697a928083019260209291829003018187876161da5a03f1156100025750506040515160ff01431090505b905061011c565b6103085b600354600554604080517f8c0e156d0000000000000000000000000000000000000000000000000000000081527f3defb96200000000000000000000000000000000000000000000000000000000600482015260a060020a90920461ffff1643016024830152621e8480604483015251600092600160a060020a031691638c0e156d916729a2241af62c000091606481810192602092909190829003018185886185025a03f1156100025750506040515192600160a060020a0384161491506102899050576004805473ffffffffffffffffffffffffffffffffffffffff1916821790555b50565b61030a60015481565b61030860008054146103f2576100df565b61031c600554600160a060020a031681565b61031c600354600160a060020a031661011c565b61030a60025481565b610308600554600160a060020a03908116339091161461035157610002565b61033960055460a060020a900461ffff1681565b005b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b6040805161ffff929092168252519081900360200190f35b6004546000600160a060020a03919091163111156103c75760408051600480547fea8a1af00000000000000000000000000000000000000000000000000000000083529251600160a060020a03939093169263ea8a1af0928083019260009291829003018183876161da5a03f115610002575050505b600554604051600160a060020a03918216916000913091909116319082818181858883f15050505050565b426000556100df6101a4565b600280546001908101909155429055565b600454600160a060020a03908116339091161461042b576100df565b610433610123565b151561043e576100df565b6103fe6101a456", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000056be5b99", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000056d0009b", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000008b", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000006c8f2a135f6ed072de4503bd7c4999a1a17f824b", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x000000000000000000000000741467b251fca923d6229c4b439078b55dca233b", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x0000000000000000000001e0d3cda913deb6f67967b99d67acdfa1712c293601" + } + }, + "0x89efe605e9ecbe22849cd85d5449cc946c26f8f3": { + "balance": "0x0", + "code": "0x650200d2f18c73506060604052361561007f5760e060020a600035046312c82bcc81146100845780635548c837146100a55780635c54305e146101015780636b103966146101555780637fcf532c14610189578063b1df3d80146101d5578063b5bc6dbb146101ee578063c6ab451414610225578063e62af6c114610293575b610007565b6102c56004356024356000620186a05a10156103855761030083835a610232565b6102d760043560243560443581600160a060020a031683600160a060020a03167f47a08955ce2b7f21ea62ff0024e1ea0ad87430953554a87e6bc65d777f18e639836040518082815260200191505060405180910390a3505050565b6102d760043560243560443560408051838152602081018390528151600160a060020a038616927f9b24879829bed3003de08d5c5d7e18dcbb8dc76faebd95cafc5d4dec8c61a3a5928290030190a2505050565b6102d76004356024356044355b600160a060020a03821660009081526020849052604090205480820110156102d957610007565b6102d7600435602435604080518281529051600160a060020a038416917fd0c5cf41ee8ebf084ad0bce53de7cbc6e4693d9b53a4019ca36a2f91cdc20b3a919081900360200190a25050565b6102c560043560243560443560006102fc848484610162565b6102c5600435602435604435600160a060020a03821660009081526020849052604081205482901061032b576103338484846102a0565b6102c56004356024356044355b60006000831180156102605750604051600160a060020a038516908290859082818181858883f19350505050155b156102fc57604051600160a060020a03851690839085906000818181858888f1935050505015156102fc57506000610300565b6102d76004356024356044355b600160a060020a03821660009081526020849052604090205481111561030757610007565b60408051918252519081900360200190f35b005b600160a060020a0382166000908152602084905260409020805482019055505050565b5060015b9392505050565b600160a060020a038216600090815260208490526040902080548290039055505050565b506000610300565b604051600160a060020a03841690600090849082818181858883f1935050505015156102fc57604051600160a060020a038416908390600081818185876185025a03f19250505015156102fc57610007565b6103008383620186a061023256" + }, + "0xb834e3edfc1a927bdcecb67a9d0eccbd752a5bb3": { + "balance": "0xffe9b09a5c474dca", + "nonce": "975", + "code": "0x" + }, + "0xd3cda913deb6f67967b99d67acdfa1712c293601": { + "balance": "0x4f5807198e238f13e", + "nonce": "283", + "code": "0x" + }, + "0xe54d323f9ef17c1f0dede47ecc86a9718fe5ea34": { + "balance": "0x0", + "code": "0x650200d2f18c7350606060405236156100ab5760e060020a600035046326a7985a81146100b057806350d4e411146100be57806354fd4d501461023d578063586a69fa1461025d5780638e46afa91461026857806396cff3df14610272578063971c803f1461029657806398c9cdf4146102a157806398e00e54146102ae578063b152f19e146102b8578063c0f68859146102c4578063e3042c0f146102cf578063ea27a88114610461575b610007565b6102845b60006104cb6102a5565b604080516020601f60843560048181013592830184900484028501840190955281845261047f948035946024803595604435956064359560a494930191819084018382808284375094965050933593505060c43591505060e435610104356101243561014435610164356101843560006101806040519081016040528060008152602001600081526020016000815260200160206040519081016040528060008152602001508152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200150610180604051908101604052808f81526020018e81526020018d81526020018c81526020018981526020018b81526020018a81526020018881526020018781526020018681526020018581526020018481526020015090506104d48f825b600060006000600a43018460e0015110156105de577f544f4f5f534f4f4e0000000000000000000000000000000000000000000000009150610524565b604080516000808252600760208301528183015290519081900360600190f35b61049c5b6103e85b90565b6104b460ff610265565b62030d403a0260026024356004350102015b60408051918252519081900360200190f35b61049c5b600a610265565b6102845b62030d40610265565b6102846010610265565b61028443600a01610265565b6102845b6020610265565b60408051808201825261047f916004803592909160649190602490600290839083908082843780516020601f608435808c01359182018390048302840183019094528083529499983598975060a49650909450910191908190840183828082843750506040805160c0818101909252959796359660c435969095506101a49450925060e491506006908390839080828437509095505050505050604080516101808181018352600080835260208381018290528385018290528451908101855281815260608401526080830181905260a0830181905260c0830181905260e0830181905261010083018190526101208301819052610140830181905261016083018190528351918201909352808984505181526020018960015060209081015182528101899052604081018890526060018484505181526020810187905260408101869052606001846001506020908101518252018460025060400151815260200184600350606001518152602001846004506080015181526020018460055060a00151905290506104e78982610200565b6102846004356024356044356064355b3a0291909201600202010190565b60408051600160a060020a03929092168252519081900360200190f35b6040805161ffff929092168252519081900360200190f35b6040805160ff929092168252519081900360200190f35b45039050610265565b9f9e505050505050505050505050505050565b9998505050505050505050565b8461016001511015610524577f494e53554646494349454e545f46554e4453000000000000000000000000000091505b600082146106ed576040805185518482529151600160a060020a0392909216917f513485fc54ef019ef1bc1ea683ef7d5d522f2865224ae10871ff992749c0ba4f9181900360200190a27389efe605e9ecbe22849cd85d5449cc946c26f8f36312c82bcc85600001518661016001516040518360e060020a0281526004018083600160a060020a031681526020018281526020019250505060206040518083038160008760325a03f215610007575050505b505092915050565b8360c0015161ffff166105ef61029a565b61ffff1611806106115750610602610261565b61ffff168460c0015161ffff16115b1561063e577f535441434b5f434845434b5f4f55545f4f465f52414e474500000000000000009150610524565b6106466102c8565b8460a0015160ff16101561067c577f47524143455f544f4f5f53484f525400000000000000000000000000000000009150610524565b6106846102a5565b84610100015110806106a157506106996100b4565b846101000151115b156106ce577f52455155495245445f4741535f4f55545f4f465f52414e4745000000000000009150610524565b6104f48461012001518561014001518660800151876101000151610471565b83610160015184600001518560e001518660a001518760200151886040015189606001518a608001518b61010001518c60c001518d61012001518e6101400151604051611078806108fa833901808c600160a060020a031681526020018b81526020018a60ff16815260200189600160a060020a03168152602001888152602001806020018781526020018681526020018561ffff1681526020018481526020018381526020018281038252888181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156107ec5780820380516001836020036101000a031916815260200191505b509c505050505050505050505050506040518091039082f090509050737c1eb207c07e7ab13cf245585bd03d0fa478d03463bacd69588683600160a060020a031660010284600160a060020a0316630a16697a6040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000757505050604051805190602001506040518460e060020a02815260040180848152602001838152602001828152602001935050505060006040518083038160008760325a03f21561000757505060408051600160a060020a038416815290517f2b05d346f0b0b9fd470024751c52d3b5dac5c37796f077c1a66241f2eada44b792509081900360200190a18092506105d656606060405260405161107838038061107883398101604052805160805160a05160c05160e05161010051610120516101405161016051610180516101a051999a98999798969795969490940194929391929091908a84848a8a8a8a88886101008051600c8054600160a060020a031990811633179091556000805482168d1781556001868155600286815560078e90556008805461ffff19168e1790553a600655600380547c01000000000000000000000000000000000000000000000000000000008d04740100000000000000000000000000000000000000000260a060020a63ffffffff0219919096168e17169490941790935588516004805493819052956020601f9385161590910260001901909316939093048101919091047f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b908101939091608091909101908390106101ee57805160ff19168380011785555b5061017a9291505b8082111561021e5760008155600101610166565b5050826003600050600201600050819055505050505050505050508a600060006101000a815481600160a060020a030219169083021790555089600d6000508190555088600e60006101000a81548160ff021916908302179055505050505050505050505050610e56806102226000396000f35b8280016001018555821561015e579182015b8281111561015e578251826000505591602001919060010190610200565b509056606060405236156101a05760e060020a60003504630924120081146101c25780630a16697a146101cf5780630fd1f94e146101d8578063137c638b1461022e57806321835af61461023b57806324032866146102545780632f95b833146102d65780633017fe24146102e55780633233c686146102ef57806337f4c00e146102fa5780634500054f146103055780634e417a98146103785780634e71d92d146103e15780634f059a43146103f35780636146195414610451578063625cc4651461046157806367ce940d1461046a5780637d298ee314610477578063830953ab146104f9578063938b5f321461050457806395ee122114610516578063974654f41461052a578063a06db7dc14610535578063a9d2293d14610541578063ae45850b14610597578063b0f07e44146105a9578063c19d93fb146105cb578063c6502da81461062e578063c680362214610637578063ca94692d1461064a578063cc3471af14610673578063d379be23146106c9578063d62457f6146106e3578063ea8a1af0146106ee578063f5562753146107f3578063f6b4dfb414610854575b610868600080548190600160a060020a03908116339091161461087a57610994565b610868600b5460ff165b90565b610868600d5481565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc80630fd1f94e6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b6108685b62012cc86101cc565b61086860043560008160001415610dc65750600161084f565b6108686004356024356000731deeda36e15ec9e80f3d7414d67a4803ae45fc80630bd295e6600360005085856040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f215610002575050604051519150505b92915050565b61099860085461ffff166101cc565b61086860026101cc565b610868600a546101cc565b6108686006546101cc565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a09431546003600050336040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b6109af60408051602081810183526000825282516004805460026001821615610100026000190190911604601f81018490048402830184019095528482529293909291830182828015610a7d5780601f10610a5257610100808354040283529160200191610a7d565b61086860006000600180610b7b6105cf565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063f5562753436040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1d6000600480610c986105cf565b61086860025481565b6108685b620186a06101cc565b6108686004356024355b6000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a1873db6600360005085856040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f2156100025750506040515191506102d09050565b6108686009546101cc565b610a1f600c54600160a060020a031681565b610868600b5462010000900460ff166101cc565b6108686007546101cc565b610a3c600e5460ff1681565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a9d2293d6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1f600054600160a060020a031681565b610868600080548190600160a060020a039081163390911614610a8957610994565b6108685b6000731deeda36e15ec9e80f3d7414d67a4803ae45fc80635054d98a60036000506040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b61086860015481565b610868600b54610100900460ff166101cc565b61086860035474010000000000000000000000000000000000000000900460e060020a026101cc565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063cc3471af6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1f600854620100009004600160a060020a03166101cc565b6108686005546101cc565b610a1d604080517fa09431540000000000000000000000000000000000000000000000000000000081526003600482015233600160a060020a031660248201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc809163a0943154916044808301926020929190829003018160008760325a03f215610002575050604051511590506107f157604080517f7e9265620000000000000000000000000000000000000000000000000000000081526003600482015233600160a060020a031660248201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc8091637e9265629160448083019260009291908290030181838760325a03f215610002575050505b565b6108686004356000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063f5562753836040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f215610002575050604051519150505b919050565b610a1f600354600160a060020a03166101cc565b60408051918252519081900360200190f35b60045460006002600183161561010002600019019092169190910411156108a45760009150610994565b6108ac6105cf565b9050600081141580156108c0575060018114155b80156108cd575060028114155b156108db5760009150610994565b600480546000828152602060026001841615610100026000190190931692909204601f908101929092047f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b9081019236929083901061095d5782800160ff198235161785555b5061098d9291505b808211156109945760008155600101610949565b82800160010185558215610941579182015b8281111561094157823582600050559160200191906001019061096f565b5050600191505b5090565b6040805161ffff9092168252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015610a0f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b005b60408051600160a060020a03929092168252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b815481529060010190602001808311610a6057829003601f168201915b505050505090506101cc565b6004546000600260018316156101000260001901909216919091041115610ab35760009150610994565b610abb6105cf565b905060008114158015610acf575060018114155b8015610adc575060028114155b15610aea5760009150610994565b604080517f7c0278fc000000000000000000000000000000000000000000000000000000008152600360048201818152602483019384523660448401819052731deeda36e15ec9e80f3d7414d67a4803ae45fc8094637c0278fc946000939190606401848480828437820191505094505050505060006040518083038160008760325a03f215610002575050505090565b1415610c8557604080516001547f0fee183d0000000000000000000000000000000000000000000000000000000082526003600483015233600160a060020a0316602483015234604483015260648201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc8091630fee183d916084828101926020929190829003018160008760325a03f21561000257505060405151925050811515610c8a577389efe605e9ecbe22849cd85d5449cc946c26f8f36312c82bcc33346040518360e060020a0281526004018083600160a060020a031681526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515115159050610c8a57610002565b505090565b81925050610994565b505b50565b1415610c93575a9150610cab3383610481565b1515610cb75750610c95565b731deeda36e15ec9e80f3d7414d67a4803ae45fc8063da46be0a60038433610cdd61046e565b610ce5610232565b6040518660e060020a0281526004018086815260200185815260200184600160a060020a031681526020018381526020018281526020019550505050505060006040518083038160008760325a03f21561000257505050610c933360408051600080547fc17e6817000000000000000000000000000000000000000000000000000000008352600160a060020a03908116600484015230163160248301529151731deeda36e15ec9e80f3d7414d67a4803ae45fc809263c17e68179260448082019360209390928390039091019082908760325a03f2156100025750505050565b30600160a060020a031660405180807f5f5f6469672875696e7432353629000000000000000000000000000000000000815260200150600e019050604051809103902060e060020a8091040260e060020a9004600184036040518260e060020a0281526004018082815260200191505060006040518083038160008760325a03f292505050151561084f5761000256" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "ethash": {} + } + }, + "context": { + "number": "1062503", + "difficulty": "13700504019867", + "timestamp": "1456480446", + "gasLimit": "3141592", + "miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226" + }, + "input": "0xf86b8203cf850ba43b740083200b2094741467b251fca923d6229c4b439078b55dca233b8084614619541ca078293714f69a810356f1ee29dc686ec2ca3a0e5448e1ef6322c77369ebdd26c2a01c3836fa363548959554ee5360361be9db4aea9eb7c31f61550f0e9a10138adf", + "tracerConfig": { + "diffMode": true + }, + "result": { + "pre": { + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x98e1c608601c2496b2", + "nonce": 218916 + }, + "0x6c8f2a135f6ed072de4503bd7c4999a1a17f824b": { + "balance": "0x0", + "nonce": 237, + "code": "0x6060604052361561027c5760e060020a600035046301991313811461027e57806303d22885146102ca5780630450991814610323578063049ae734146103705780630ce46c43146103c35780630e85023914610602578063112e39a8146106755780631b4fa6ab146106c25780631e74a2d3146106d057806326a7985a146106fd5780633017fe2414610753578063346cabbc1461075c578063373a1bc3146107d55780633a9e74331461081e5780633c2c21a01461086e5780633d9ce89b146108ba578063480b70bd1461092f578063481078431461097e57806348f0518714610a0e5780634c471cde14610a865780634db3da8314610b09578063523ccfa814610b4f578063586a69fa14610be05780635a9f2def14610c3657806364ee49fe14610caf57806367beaccb14610d055780636840246014610d74578063795b9a6f14610dca5780637b55c8b514610e415780637c73f84614610ee15780638c0e156d14610f145780638c1d01c814610f605780638e46afa914610f69578063938c430714610fc0578063971c803f146111555780639772c982146111ac57806398c9cdf41461122857806398e00e541461127f5780639f927be7146112d5578063a00aede914611383578063a1c0539d146113d3578063aff21c6514611449578063b152f19e14611474578063b549793d146114cb578063b5b33eda1461154b578063bbc6eb1f1461159b578063c0f68859146115ab578063c3a2c0c314611601578063c43d05751461164b578063d8e5c04814611694578063dbfef71014611228578063e29fb547146116e7578063e6470fbe1461173a578063ea27a8811461174c578063ee77fe86146117d1578063f158458c14611851575b005b611882600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503387876020604051908101604052806000815260200150612225610f6d565b61188260043560243560443560643560843560a43560c435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001600050338b8a6020604051908101604052806000815260200150896125196106c6565b611882600435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503385600060e060020a026020604051908101604052806000815260200150611e4a610f6d565b611882600435602435604435606435608435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503389896020604051908101604052806000815260200150886124e86106c6565b604080516020604435600481810135601f8101849004840285018401909552848452611882948135946024803595939460649492939101918190840183828082843750506040805160a08082019092529597963596608435969095506101449450925060a491506005908390839080828437509095505050505050604080518082018252600160a060020a03338116825288166020820152815160c0810190925260009173e54d323f9ef17c1f0dede47ecc86a9718fe5ea349163e3042c0f91600191908a908a9089908b90808b8b9090602002015181526020018b60016005811015610002579090602002015181526020018b60026005811015610002579090602002015181526020018b60036005811015610002579090602002015181526020018b6004600581101561000257909060200201518152602001348152602001506040518860e060020a02815260040180888152602001876002602002808383829060006004602084601f0104600f02600301f150905001868152602001806020018560ff1681526020018461ffff168152602001836006602002808383829060006004602084601f0104600f02600301f1509050018281038252868181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105d25780820380516001836020036101000a031916815260200191505b509850505050505050505060206040518083038160008760325a03f2156100025750506040515191506124cd9050565b60408051602060248035600481810135601f81018590048502860185019096528585526118829581359591946044949293909201918190840183828082843750949650505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808787611e64610f6d565b611882600435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333600060e060020a026020604051908101604052806000815260200150611d28610f6d565b61189f5b6000611bf8611159565b6118b7600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a881600060005054611a9561159f565b6118b7600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346326a7985a6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b6118b760075b90565b604080516020606435600481810135601f8101849004840285018401909552848452611882948135946024803595604435956084949201919081908401838280828437509496505093359350505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160013389898861224b610f6d565b611882600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503386866020604051908101604052806000815260200150611e64610f6d565b611882600435602435604435606435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333896020604051908101604052806000815260200150886123bc6106c6565b611882600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503387866020604051908101604052806000815260200150611f8d610f6d565b60408051602060248035600481810135601f810185900485028601850190965285855261188295813595919460449492939092019181908401838280828437509496505093359350505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808888612225610f6d565b611882600435602435604435606435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503388886020604051908101604052806000815260200150612388610f6d565b611882600435604080517fc4144b2600000000000000000000000000000000000000000000000000000000815260016004820152600160a060020a03831660248201529051600091737c1eb207c07e7ab13cf245585bd03d0fa478d0349163c4144b26916044818101926020929091908290030181878760325a03f215610002575050604051519150611b409050565b604080516020604435600481810135601f81018490048402850184019095528484526118829481359460248035959394606494929391019181908401838280828437509496505093359350505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133888888612238610f6d565b604080516020604435600481810135601f810184900484028501840190955284845261188294813594602480359593946064949293910191819084018382808284375094965050933593505060843591505060a43560c435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338b8b8b896126536106c6565b611882600435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333866020604051908101604052806000815260200150611e4a610f6d565b6118b76004355b604080517fed5bd7ea00000000000000000000000000000000000000000000000000000000815260016004820152600160a060020a03831660248201529051600091737c1eb207c07e7ab13cf245585bd03d0fa478d0349163ed5bd7ea916044818101926020929091908290030181878760325a03f215610002575050604051519150611b409050565b61189f600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463586a69fa6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b60408051602060248035600481810135601f81018590048502860185019096528585526118829581359591946044949293909201918190840183828082843750949650509335935050606435915050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808989612388610f6d565b61188260043560243560443560643560843560a435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001600050338a896020604051908101604052806000815260200150886124d76106c6565b6040805160206004803580820135601f8101849004840285018401909552848452611882949193602493909291840191908190840183828082843750949650505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808587611e4a610f6d565b61188260043560243560443560643560843560a435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001600050338a8a60206040519081016040528060008152602001508961262d6106c6565b604080516020606435600481810135601f810184900484028501840190955284845261188294813594602480359560443595608494920191908190840183828082843750949650505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338888876120c7610f6d565b604080516020604435600481810135601f81018490048402850184019095528484526118829481359460248035959394606494929391019181908401838280828437505060408051608080820190925295979635969561010495509350608492508591508390839080828437509095505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338989898961263a6106c6565b6118b7600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a881858585611ba361122c565b611882600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001600050333388602060405190810160405280600081526020015061236e610f6d565b6118b760005481565b6118c95b600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea34638e46afa96040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b60408051602060248035600481810135601f8101859004850286018501909652858552611882958135959194604494929390920191819084018382808284375094965050933593505060643591505060843560a43560c43560e43561010435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600160005033338e8e8d8f8e8e8e8e8e346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156111195780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519b9a5050505050505050505050565b61189f5b600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463971c803f6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b604080516020604435600481810135601f8101849004840285018401909552848452611882948135946024803595939460649492939101918190840183828082843750949650509335935050608435915050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338989896123a2610f6d565b6118b75b600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346398c9cdf46040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b6118b7600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346398e00e546040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b611882600435604080517fe6ce3a6a000000000000000000000000000000000000000000000000000000008152600160048201527f3e3d0000000000000000000000000000000000000000000000000000000000006024820152604481018390529051600091737c1eb207c07e7ab13cf245585bd03d0fa478d0349163e6ce3a6a916064818101926020929091908290030181878760325a03f215610002575050604051519150611b409050565b611882600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503385600060e060020a0260206040519081016040528060008152602001506121ef610f6d565b604080516020604435600481810135601f8101849004840285018401909552848452611882948135946024803595939460649492939101918190840183828082843750949650505050505050600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338787876120b5610f6d565b6118b7600435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a88183611b4561159f565b6118b75b600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463b152f19e6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b60408051602060248035600481810135601f8101859004850286018501909652858552611882958135959194604494929390920191819084018382808284375094965050933593505060643591505060843560a435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600133808b8b8961262d6106c6565b611882600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503386600060e060020a026020604051908101604052806000815260200150612200610f6d565b6118b75b60005460649004610759565b6118b7600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463c0f688596040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506107599050565b611882600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333600060e060020a026020604051908101604052806000815260200150611bff610f6d565b611882600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503333876020604051908101604052806000815260200150612200610f6d565b611882600435602435604435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e41160016000503387600060e060020a026020604051908101604052806000815260200150612213610f6d565b611882600435602435604435606435608435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e411600160005033338a60206040519081016040528060008152602001508961250c6106c6565b61027c6000600060006118e033610b56565b6118b7600435602435604435606435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a881868686866040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f215610002575050604051519150505b949350505050565b604080516020604435600481810135601f810184900484028501840190955284845261188294813594602480359593946064949293910191819084018382808284375094965050933593505060843591505060a435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea346350d4e4116001338a8a8a886124fa6106c6565b6118b7600435602435600073e54d323f9ef17c1f0dede47ecc86a9718fe5ea3463ea27a88184846000611b4f61122c565b60408051600160a060020a03929092168252519081900360200190f35b6040805161ffff929092168252519081900360200190f35b60408051918252519081900360200190f35b6040805160ff929092168252519081900360200190f35b15611a905733925082600160a060020a031663c6502da86040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040805180517fc6803622000000000000000000000000000000000000000000000000000000008252915191945063c680362291600482810192602092919082900301816000876161da5a03f11561000257505060405151905080156119d1575082600160a060020a031663d379be236040518160e060020a0281526004018090506020604051808303816000876161da5a03f11561000257505060405151600160a060020a03166000141590505b80156119dd5750600082115b80156119ec5750600054600190115b15611a90578183600160a060020a031663830953ab6040518160e060020a0281526004018090506020604051808303816000876161da5a03f1156100025750506040515160640291909104915050604281118015611a4d5750600054829011155b15611a675760008054612710612711909102049055611a90565b602181108015611a7a5750600054829010155b15611a90576000805461271061270f9091020490555b505050565b6000611a9f61122c565b6040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f2156100025750506040515191506107599050565b6040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f215610002575050604051519150505b919050565b6000611af261122c565b6040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f215610002575050604051519150505b92915050565b6040518560e060020a0281526004018085815260200184815260200183815260200182815260200194505050505060206040518083038160008760325a03f215610002575050604051519150505b9392505050565b9050610759565b611c076106c6565b6000611c11611478565b611c1961122c565b600054611c2461159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611cf25780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f2156100025750506040515191506107599050565b611d306106c6565b60008b611d3b61122c565b600054611d4661159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611e145780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519150611b409050565b611e526106c6565b6000611e5c611478565b611d3b61122c565b611e6c6106c6565b6000611e76611478565b611e7e61122c565b600054611e8961159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015611f575780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519150611b9d9050565b611f956106c6565b8b611f9e611478565b611fa661122c565b600054611fb161159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561207f5780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519150611bf19050565b6120bd6106c6565b6000611f9e611478565b6120cf6106c6565b8b6120d8611478565b6120e061122c565b6000546120eb61159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156121b95780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f2156100025750506040515191506117c99050565b6121f76106c6565b8b611e76611478565b6122086106c6565b60008b611e7e61122c565b61221b6106c6565b8a8c611fa661122c565b61222d6106c6565b60008b611fa661122c565b6122406106c6565b60008b6120e061122c565b6122536106c6565b8c8b61225d61122c565b60005461226861159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156123365780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f21561000257505060405151979650505050505050565b6123766106c6565b60008c8c600060005054611fb161159f565b6123906106c6565b60008c8c6000600050546120eb61159f565b6123aa6106c6565b60008c8c60006000505461226861159f565b60008d8d6000600050546120eb61159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f16801561249c5780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519150505b9695505050505050565b8e8d8d6000600050546123ce61159f565b60008d8d60006000505461226861159f565b60008d8d6000600050546123ce61159f565b60008e8e8d61226861159f565b8f8e8e8d61252561159f565b346040518e60e060020a028152600401808e81526020018d600160a060020a031681526020018c600160a060020a031681526020018b8152602001806020018a60ff1681526020018961ffff16815260200188815260200187815260200186815260200185815260200184815260200183815260200182810382528b8181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156125f35780820380516001836020036101000a031916815260200191505b509e50505050505050505050505050505060206040518083038160008760325a03f215610002575050604051519998505050505050505050565b60008e8e8d6123ce61159f565b8a5160208c015160408d015160608e015161226861159f565b60008e8e8d61252561159f56", + "storage": { + "0x26cba0705aade77fa0f9275b68d01fb71206a44abd3a4f5a838f7241efbc8abf": "0x00000000000000000000000042e69cd0a17ae9992f9ad93d136c4bb0d95e3230", + "0x49f03a2c2f4fd666a32141fb324283b6f84a1d07b5fa435669fdb55766aef715": "0x000000000000000000000000d7b0e93fa8386b17fb5d1cf934076203dcc122f3", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bc": "0x000000000000000000000000d7b0e93fa8386b17fb5d1cf934076203dcc122f3", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bd": "0x000000000000000000000000c9a2bfd279fe57e7651e5d9f29bb1793c9a1cf01", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bf": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f1": "0x000000000000000000000000a4d91b341f0e9a7000be916a668408b463f4c38c", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f3": "0x00000000000000000000000042e69cd0a17ae9992f9ad93d136c4bb0d95e3230", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f4": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64dbc": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + }, + "0x741467b251fca923d6229c4b439078b55dca233b": { + "balance": "0x29c613529e8218f8", + "code": "0x606060405236156101a05760e060020a60003504630924120081146101c25780630a16697a146101cf5780630fd1f94e146101d8578063137c638b1461022e57806321835af61461023b57806324032866146102545780632f95b833146102d65780633017fe24146102e55780633233c686146102ef57806337f4c00e146102fa5780634500054f146103055780634e417a98146103785780634e71d92d146103e15780634f059a43146103f35780636146195414610451578063625cc4651461046157806367ce940d1461046a5780637d298ee314610477578063830953ab146104f9578063938b5f321461050457806395ee122114610516578063974654f41461052a578063a06db7dc14610535578063a9d2293d14610541578063ae45850b14610597578063b0f07e44146105a9578063c19d93fb146105cb578063c6502da81461062e578063c680362214610637578063ca94692d1461064a578063cc3471af14610673578063d379be23146106c9578063d62457f6146106e3578063ea8a1af0146106ee578063f5562753146107f3578063f6b4dfb414610854575b610868600080548190600160a060020a03908116339091161461087a57610994565b610868600b5460ff165b90565b610868600d5481565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc80630fd1f94e6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b6108685b62012cc86101cc565b61086860043560008160001415610dc65750600161084f565b6108686004356024356000731deeda36e15ec9e80f3d7414d67a4803ae45fc80630bd295e6600360005085856040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f215610002575050604051519150505b92915050565b61099860085461ffff166101cc565b61086860026101cc565b610868600a546101cc565b6108686006546101cc565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a09431546003600050336040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b6109af60408051602081810183526000825282516004805460026001821615610100026000190190911604601f81018490048402830184019095528482529293909291830182828015610a7d5780601f10610a5257610100808354040283529160200191610a7d565b61086860006000600180610b7b6105cf565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063f5562753436040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1d6000600480610c986105cf565b61086860025481565b6108685b620186a06101cc565b6108686004356024355b6000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a1873db6600360005085856040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f2156100025750506040515191506102d09050565b6108686009546101cc565b610a1f600c54600160a060020a031681565b610868600b5462010000900460ff166101cc565b6108686007546101cc565b610a3c600e5460ff1681565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a9d2293d6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1f600054600160a060020a031681565b610868600080548190600160a060020a039081163390911614610a8957610994565b6108685b6000731deeda36e15ec9e80f3d7414d67a4803ae45fc80635054d98a60036000506040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b61086860015481565b610868600b54610100900460ff166101cc565b61086860035474010000000000000000000000000000000000000000900460e060020a026101cc565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063cc3471af6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1f600854620100009004600160a060020a03166101cc565b6108686005546101cc565b610a1d604080517fa09431540000000000000000000000000000000000000000000000000000000081526003600482015233600160a060020a031660248201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc809163a0943154916044808301926020929190829003018160008760325a03f215610002575050604051511590506107f157604080517f7e9265620000000000000000000000000000000000000000000000000000000081526003600482015233600160a060020a031660248201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc8091637e9265629160448083019260009291908290030181838760325a03f215610002575050505b565b6108686004356000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063f5562753836040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f215610002575050604051519150505b919050565b610a1f600354600160a060020a03166101cc565b60408051918252519081900360200190f35b60045460006002600183161561010002600019019092169190910411156108a45760009150610994565b6108ac6105cf565b9050600081141580156108c0575060018114155b80156108cd575060028114155b156108db5760009150610994565b600480546000828152602060026001841615610100026000190190931692909204601f908101929092047f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b9081019236929083901061095d5782800160ff198235161785555b5061098d9291505b808211156109945760008155600101610949565b82800160010185558215610941579182015b8281111561094157823582600050559160200191906001019061096f565b5050600191505b5090565b6040805161ffff9092168252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015610a0f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b005b60408051600160a060020a03929092168252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b815481529060010190602001808311610a6057829003601f168201915b505050505090506101cc565b6004546000600260018316156101000260001901909216919091041115610ab35760009150610994565b610abb6105cf565b905060008114158015610acf575060018114155b8015610adc575060028114155b15610aea5760009150610994565b604080517f7c0278fc000000000000000000000000000000000000000000000000000000008152600360048201818152602483019384523660448401819052731deeda36e15ec9e80f3d7414d67a4803ae45fc8094637c0278fc946000939190606401848480828437820191505094505050505060006040518083038160008760325a03f215610002575050505090565b1415610c8557604080516001547f0fee183d0000000000000000000000000000000000000000000000000000000082526003600483015233600160a060020a0316602483015234604483015260648201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc8091630fee183d916084828101926020929190829003018160008760325a03f21561000257505060405151925050811515610c8a577389efe605e9ecbe22849cd85d5449cc946c26f8f36312c82bcc33346040518360e060020a0281526004018083600160a060020a031681526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515115159050610c8a57610002565b505090565b81925050610994565b505b50565b1415610c93575a9150610cab3383610481565b1515610cb75750610c95565b731deeda36e15ec9e80f3d7414d67a4803ae45fc8063da46be0a60038433610cdd61046e565b610ce5610232565b6040518660e060020a0281526004018086815260200185815260200184600160a060020a031681526020018381526020018281526020019550505050505060006040518083038160008760325a03f21561000257505050610c933360408051600080547fc17e6817000000000000000000000000000000000000000000000000000000008352600160a060020a03908116600484015230163160248301529151731deeda36e15ec9e80f3d7414d67a4803ae45fc809263c17e68179260448082019360209390928390039091019082908760325a03f2156100025750505050565b30600160a060020a031660405180807f5f5f6469672875696e7432353629000000000000000000000000000000000000815260200150600e019050604051809103902060e060020a8091040260e060020a9004600184036040518260e060020a0281526004018082815260200191505060006040518083038160008760325a03f292505050151561084f5761000256" + }, + "0x7dd677b54fc954824a7bc49bd26cbdfa12c75adf": { + "balance": "0xd7a58f5b73b4b6c4", + "code": "0x606060405236156100985760e060020a60003504633896002781146100e15780633defb962146100ea5780633f4be8891461010c5780634136aa351461011f5780634a420138146101a057806369c1a7121461028c5780638129fc1c146102955780638da5cb5b146102a6578063ae45850b146102b8578063af3309d8146102cc578063ea8a1af0146102d5578063ead50da3146102f4575b610308671bc16d674ec8000030600160a060020a03163110156100df57600554604051600160a060020a03918216916000913091909116319082818181858883f150505050505b565b61030a60005481565b610308671bc16d674ec8000030600160a060020a031631101561040f576100df565b61031c600454600160a060020a03165b90565b61030a5b600080548190118015610199575060408051600480547f0a16697a0000000000000000000000000000000000000000000000000000000083529251600160a060020a039390931692630a16697a928083019260209291829003018187876161da5a03f1156100025750506040515160ff01431090505b905061011c565b6103085b600354600554604080517f8c0e156d0000000000000000000000000000000000000000000000000000000081527f3defb96200000000000000000000000000000000000000000000000000000000600482015260a060020a90920461ffff1643016024830152621e8480604483015251600092600160a060020a031691638c0e156d916729a2241af62c000091606481810192602092909190829003018185886185025a03f1156100025750506040515192600160a060020a0384161491506102899050576004805473ffffffffffffffffffffffffffffffffffffffff1916821790555b50565b61030a60015481565b61030860008054146103f2576100df565b61031c600554600160a060020a031681565b61031c600354600160a060020a031661011c565b61030a60025481565b610308600554600160a060020a03908116339091161461035157610002565b61033960055460a060020a900461ffff1681565b005b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b6040805161ffff929092168252519081900360200190f35b6004546000600160a060020a03919091163111156103c75760408051600480547fea8a1af00000000000000000000000000000000000000000000000000000000083529251600160a060020a03939093169263ea8a1af0928083019260009291829003018183876161da5a03f115610002575050505b600554604051600160a060020a03918216916000913091909116319082818181858883f15050505050565b426000556100df6101a4565b600280546001908101909155429055565b600454600160a060020a03908116339091161461042b576100df565b610433610123565b151561043e576100df565b6103fe6101a456", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000056d0009b", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000008b", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x000000000000000000000000741467b251fca923d6229c4b439078b55dca233b" + } + }, + "0xb834e3edfc1a927bdcecb67a9d0eccbd752a5bb3": { + "balance": "0xffe9b09a5c474dca", + "nonce": 975 + }, + "0xd3cda913deb6f67967b99d67acdfa1712c293601": { + "balance": "0x4f5807198e238f13e", + "nonce": 283 + } + }, + "post": { + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x98e2b02f14529b1eb2" + }, + "0x651913977e8140c323997fce5e03c19e0015eebf": { + "balance": "0x29a2241af62c0000", + "code": "0x606060405236156101a05760e060020a60003504630924120081146101c25780630a16697a146101cf5780630fd1f94e146101d8578063137c638b1461022e57806321835af61461023b57806324032866146102545780632f95b833146102d65780633017fe24146102e55780633233c686146102ef57806337f4c00e146102fa5780634500054f146103055780634e417a98146103785780634e71d92d146103e15780634f059a43146103f35780636146195414610451578063625cc4651461046157806367ce940d1461046a5780637d298ee314610477578063830953ab146104f9578063938b5f321461050457806395ee122114610516578063974654f41461052a578063a06db7dc14610535578063a9d2293d14610541578063ae45850b14610597578063b0f07e44146105a9578063c19d93fb146105cb578063c6502da81461062e578063c680362214610637578063ca94692d1461064a578063cc3471af14610673578063d379be23146106c9578063d62457f6146106e3578063ea8a1af0146106ee578063f5562753146107f3578063f6b4dfb414610854575b610868600080548190600160a060020a03908116339091161461087a57610994565b610868600b5460ff165b90565b610868600d5481565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc80630fd1f94e6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b6108685b62012cc86101cc565b61086860043560008160001415610dc65750600161084f565b6108686004356024356000731deeda36e15ec9e80f3d7414d67a4803ae45fc80630bd295e6600360005085856040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f215610002575050604051519150505b92915050565b61099860085461ffff166101cc565b61086860026101cc565b610868600a546101cc565b6108686006546101cc565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a09431546003600050336040518360e060020a0281526004018083815260200182600160a060020a031681526020019250505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b6109af60408051602081810183526000825282516004805460026001821615610100026000190190911604601f81018490048402830184019095528482529293909291830182828015610a7d5780601f10610a5257610100808354040283529160200191610a7d565b61086860006000600180610b7b6105cf565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063f5562753436040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1d6000600480610c986105cf565b61086860025481565b6108685b620186a06101cc565b6108686004356024355b6000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a1873db6600360005085856040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f2156100025750506040515191506102d09050565b6108686009546101cc565b610a1f600c54600160a060020a031681565b610868600b5462010000900460ff166101cc565b6108686007546101cc565b610a3c600e5460ff1681565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063a9d2293d6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1f600054600160a060020a031681565b610868600080548190600160a060020a039081163390911614610a8957610994565b6108685b6000731deeda36e15ec9e80f3d7414d67a4803ae45fc80635054d98a60036000506040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b61086860015481565b610868600b54610100900460ff166101cc565b61086860035474010000000000000000000000000000000000000000900460e060020a026101cc565b6108686000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063cc3471af6040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506101cc9050565b610a1f600854620100009004600160a060020a03166101cc565b6108686005546101cc565b610a1d604080517fa09431540000000000000000000000000000000000000000000000000000000081526003600482015233600160a060020a031660248201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc809163a0943154916044808301926020929190829003018160008760325a03f215610002575050604051511590506107f157604080517f7e9265620000000000000000000000000000000000000000000000000000000081526003600482015233600160a060020a031660248201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc8091637e9265629160448083019260009291908290030181838760325a03f215610002575050505b565b6108686004356000731deeda36e15ec9e80f3d7414d67a4803ae45fc8063f5562753836040518260e060020a0281526004018082815260200191505060206040518083038160008760325a03f215610002575050604051519150505b919050565b610a1f600354600160a060020a03166101cc565b60408051918252519081900360200190f35b60045460006002600183161561010002600019019092169190910411156108a45760009150610994565b6108ac6105cf565b9050600081141580156108c0575060018114155b80156108cd575060028114155b156108db5760009150610994565b600480546000828152602060026001841615610100026000190190931692909204601f908101929092047f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b9081019236929083901061095d5782800160ff198235161785555b5061098d9291505b808211156109945760008155600101610949565b82800160010185558215610941579182015b8281111561094157823582600050559160200191906001019061096f565b5050600191505b5090565b6040805161ffff9092168252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f168015610a0f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b005b60408051600160a060020a03929092168252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b815481529060010190602001808311610a6057829003601f168201915b505050505090506101cc565b6004546000600260018316156101000260001901909216919091041115610ab35760009150610994565b610abb6105cf565b905060008114158015610acf575060018114155b8015610adc575060028114155b15610aea5760009150610994565b604080517f7c0278fc000000000000000000000000000000000000000000000000000000008152600360048201818152602483019384523660448401819052731deeda36e15ec9e80f3d7414d67a4803ae45fc8094637c0278fc946000939190606401848480828437820191505094505050505060006040518083038160008760325a03f215610002575050505090565b1415610c8557604080516001547f0fee183d0000000000000000000000000000000000000000000000000000000082526003600483015233600160a060020a0316602483015234604483015260648201529051731deeda36e15ec9e80f3d7414d67a4803ae45fc8091630fee183d916084828101926020929190829003018160008760325a03f21561000257505060405151925050811515610c8a577389efe605e9ecbe22849cd85d5449cc946c26f8f36312c82bcc33346040518360e060020a0281526004018083600160a060020a031681526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515115159050610c8a57610002565b505090565b81925050610994565b505b50565b1415610c93575a9150610cab3383610481565b1515610cb75750610c95565b731deeda36e15ec9e80f3d7414d67a4803ae45fc8063da46be0a60038433610cdd61046e565b610ce5610232565b6040518660e060020a0281526004018086815260200185815260200184600160a060020a031681526020018381526020018281526020019550505050505060006040518083038160008760325a03f21561000257505050610c933360408051600080547fc17e6817000000000000000000000000000000000000000000000000000000008352600160a060020a03908116600484015230163160248301529151731deeda36e15ec9e80f3d7414d67a4803ae45fc809263c17e68179260448082019360209390928390039091019082908760325a03f2156100025750505050565b30600160a060020a031660405180807f5f5f6469672875696e7432353629000000000000000000000000000000000000815260200150600e019050604051809103902060e060020a8091040260e060020a9004600184036040518260e060020a0281526004018082815260200191505060006040518083038160008760325a03f292505050151561084f5761000256", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000007dd677b54fc954824a7bc49bd26cbdfa12c75adf", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000011f8119429ed3a", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000000000002e002d006b55", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x00000000000000003defb9627dd677b54fc954824a7bc49bd26cbdfa12c75adf", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000000000000ba43b7400", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x00000000000000000000000000000000000000000000000000000000001e8480", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x0000000000000000000000006c8f2a135f6ed072de4503bd7c4999a1a17f824b", + "0x000000000000000000000000000000000000000000000000000000000000000d": "0x0000000000000000000000000000000000000000000000000000000000103847", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0x00000000000000000000000000000000000000000000000000000000000000ff" + } + }, + "0x6c8f2a135f6ed072de4503bd7c4999a1a17f824b": { + "nonce": 238, + "storage": { + "0x26cba0705aade77fa0f9275b68d01fb71206a44abd3a4f5a838f7241efbc8abf": "0x000000000000000000000000d7b0e93fa8386b17fb5d1cf934076203dcc122f3", + "0x49f03a2c2f4fd666a32141fb324283b6f84a1d07b5fa435669fdb55766aef715": "0x00000000000000000000000042e69cd0a17ae9992f9ad93d136c4bb0d95e3230", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bc": "0x000000000000000000000000a4d91b341f0e9a7000be916a668408b463f4c38c", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bd": "0x000000000000000000000000d7b0e93fa8386b17fb5d1cf934076203dcc122f3", + "0x95e05d02b91af970cb4998107e8613455258880676e00b819c12d675e60de5bf": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2751": "0x000000000000000000000000651913977e8140c323997fce5e03c19e0015eebf", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2752": "0x0000000000000000000000000000000000000000000000000000000000103847", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2753": "0x000000000000000000000000741467b251fca923d6229c4b439078b55dca233b", + "0x99d5294a34e2d6d560a223237786adc8b5651c09094b9ecd56e6ae7abc2a2756": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f1": "0x00000000000000000000000042e69cd0a17ae9992f9ad93d136c4bb0d95e3230", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f3": "0x000000000000000000000000c9a2bfd279fe57e7651e5d9f29bb1793c9a1cf01", + "0xd3a5582b3eff6ef8ee90f3962e9d598a3f4b7d07840356c9b8fd7b494879b4f4": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64dbb": "0x000000000000000000000000651913977e8140c323997fce5e03c19e0015eebf", + "0xf7518490c515b9fc8e7fe713b647fe88eacefc92d616fa9454e61fe9aab64dbc": "0x0000000000000000000000000000000000000000000000000000000000000002" + } + }, + "0x741467b251fca923d6229c4b439078b55dca233b": { + "balance": "0x0", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000000b": "0x0000000000000000000000000000000000000000000000000000000000000101" + } + }, + "0x7dd677b54fc954824a7bc49bd26cbdfa12c75adf": { + "balance": "0xd6c5f42b8502a0e3", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000056d020be", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000008c", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x000000000000000000000000651913977e8140c323997fce5e03c19e0015eebf" + } + }, + "0xb834e3edfc1a927bdcecb67a9d0eccbd752a5bb3": { + "balance": "0x10002e64ebd492a46", + "nonce": 976 + }, + "0xd3cda913deb6f67967b99d67acdfa1712c293601": { + "balance": "0x4f5809f97e1c8bb9b" + } + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/simple.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/simple.json new file mode 100644 index 0000000..01cc3c5 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/simple.json @@ -0,0 +1,103 @@ +{ + "context": { + "difficulty": "3502894804", + "gasLimit": "4722976", + "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724", + "number": "2289806", + "timestamp": "1513601314" + }, + "genesis": { + "alloc": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "code": "0x", + "nonce": "22", + "storage": {} + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "nonce": "1", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "code": "0x", + "nonce": "29072", + "storage": {} + } + }, + "config": { + "byzantiumBlock": 1700000, + "chainId": 3, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", + "eip155Block": 10, + "eip158Block": 10, + "ethash": {}, + "homesteadBlock": 0 + }, + "difficulty": "3509749784", + "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444", + "gasLimit": "4727564", + "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440", + "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3", + "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada", + "nonce": "0x4eb12e19c16d43da", + "number": "2289805", + "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f", + "timestamp": "1513601261", + "totalDifficulty": "7143276353481064" + }, + "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4", + "tracerConfig": { + "diffMode": true + }, + "result": { + "pre": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x0", + "nonce": 22 + }, + "0x1585936b53834b021f68cc13eeefdec2efc8e724": { + "balance": "0x0" + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d87094125a369d9bd5", + "nonce": 1, + "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d77678137ac1b775", + "nonce": 29072 + } + }, + "post": { + "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": { + "balance": "0x6f05b59d3b20000" + }, + "0x1585936b53834b021f68cc13eeefdec2efc8e724": { + "balance": "0x420eed1bd6c00" + }, + "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": { + "balance": "0x4d869a3b70062eb9bd5", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b95e" + } + }, + "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": { + "balance": "0x1780d7725724a9044b75", + "nonce": 29073 + } + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/suicide.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/suicide.json new file mode 100644 index 0000000..5021bda --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/suicide.json @@ -0,0 +1,107 @@ +{ + "genesis": { + "difficulty": "5697691613344", + "extraData": "0xd783010202844765746887676f312e342e32856c696e7578", + "gasLimit": "3141592", + "hash": "0x2004021ae3545cf8abba1ec97a7e401157cee9e847131e2f4c75ce38610040cc", + "miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5", + "mixHash": "0x651f01d13fb801c602e1544ab80b3bc32888ea40ef298efa52ec3df983b558ee", + "nonce": "0xdf23f0da925518a6", + "number": "422908", + "stateRoot": "0xd914c6440edf9f4a6f997a9b3ecb6e1a9ca2310f74b0b6890c0d0d4a3c28e4d3", + "timestamp": "1445530335", + "totalDifficulty": "2148894717741690476", + "alloc": { + "0x2861bf89b6c640c79040d357c1e9513693ef5d3f": { + "balance": "0x0", + "code": "0x606060405236156100825760e060020a600035046312055e8f8114610084578063185061da146100b157806322beb9b9146100d5578063245a03ec146101865780633fa4f245146102a657806341c0e1b5146102af578063890eba68146102cb578063b29f0835146102de578063d6b4485914610308578063dd012a15146103b9575b005b6001805474ff0000000000000000000000000000000000000000191660a060020a60043502179055610082565b6100826001805475ff00000000000000000000000000000000000000000019169055565b61008260043560015460e060020a6352afbc3302606090815230600160a060020a039081166064527fb29f0835000000000000000000000000000000000000000000000000000000006084527fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060a45243840160c490815260ff60a060020a85041660e452600061010481905291909316926352afbc339261012492918183876161da5a03f1156100025750505050565b6100826004356024356001547fb0f07e440000000000000000000000000000000000000000000000000000000060609081526064839052600160a060020a039091169063b0f07e449060849060009060248183876161da5a03f150604080516001547f73657449742875696e74323536290000000000000000000000000000000000008252825191829003600e018220878352835192839003602001832060e060020a6352afbc33028452600160a060020a03308116600486015260e060020a9283900490920260248501526044840152438901606484015260a060020a820460ff1694830194909452600060a483018190529251931694506352afbc33935060c48181019391829003018183876161da5a03f115610002575050505050565b6103c460025481565b61008260005433600160a060020a039081169116146103ce575b565b6103c460015460a860020a900460ff1681565b6100826001805475ff000000000000000000000000000000000000000000191660a860020a179055565b61008260043560015460e060020a6352afbc3302606090815230600160a060020a039081166064527f185061da000000000000000000000000000000000000000000000000000000006084527fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060a45243840160c490815260ff60a060020a85041660e452600061010481905291909316926352afbc339261012492918183876161da5a03f1156100025750505050565b600435600255610082565b6060908152602090f35b6001547f6ff96d17000000000000000000000000000000000000000000000000000000006060908152600160a060020a0330811660645290911690632e1a7d4d908290636ff96d17906084906020906024816000876161da5a03f1156100025750506040805180517f2e1a7d4d0000000000000000000000000000000000000000000000000000000082526004820152905160248281019350600092829003018183876161da5a03f115610002575050600054600160a060020a03169050ff", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000d3cda913deb6f67967b99d67acdfa1712c293601", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000ff30c9e568f133adce1f1ea91e189613223fc461b9" + } + }, + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x326601cc6cf364f6b9", + "nonce": "12122", + "code": "0x" + }, + "0x30c9e568f133adce1f1ea91e189613223fc461b9": { + "balance": "0x8b83c417dd78000", + "nonce": "2", + "code": "0x606060405236156102ea5760e060020a6000350463022bc71f81146102f757806303d6d7b61461037f578063086ae9e4146103ec57806309c975df146104595780631145a20f146104c657806312d67c5f146104e75780631302188c146104f15780631ae460e5146104fc57806323306ed614610573578063234917d4146105ca57806329917954146106375780632a472ae81461071d5780632e1a7d4d1461078a578063306b031d1461087f57806333613cbe1461089d57806334c19b93146108c257806335b281531461092f5780633664a0ea146109b85780633c941423146109c35780633cbfed7414610a3b57806350a3bd3914610a4957806352afbc3314610a735780635539d40014610c2a5780635a5383ac14610c3e57806360b831e514610cb55780636164947214610d7f578063685c234a14610d8a5780636ffc089614610de0578063741b3c3914610e4d5780637542861514610ed25780637772a38014610f5557806377b19cd514610ff057806378bc64601461105d5780638b37e656146110ca5780638baced64146111375780638dd5e298146111b157806393423e9c146111de57806394d2b21b1461120257806394f3f81d1461121657806398e00e54146112665780639f927be7146112bc578063a502aae81461136a578063a6c01cfd146113e8578063a9743c68146113fa578063aa4cc01f14611467578063b010d94a146114d4578063b0171fa41461154e578063b0ac4c8c146115cc578063b0f07e4414611635578063b35594601461171c578063c0f6885914611739578063c3daab961461178f578063c630f92b146117bb578063c831391d146117e5578063cd062734146117f0578063d0e30db01461185d578063db681e5414611865578063e40986551461190c578063e850f3ae14611979578063ed2b8e0b146119e6578063f340fa01146119f1578063f828c3fa14611ae8578063f8b1185314611b07578063f9f447eb14611b24578063fc30052214611b91578063fcf3691814611bfe575b6112645b611c86336119f8565b611c88600435604080517fc4144b260000000000000000000000000000000000000000000000000000000081526010600482015260248101839052905160009173ce642b6a82e72147ceade0e72c786ba8eaeb31d79163c4144b26916044818101926020929091908290030181878760325a03f2156100025750506040515191506108989050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b637d613b346000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63da40fd616000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c9a60043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63c68efc486000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b61126460043560243560443560643560843561200185858585856000610a89565b611c886004545b90565b611c886005546104ee565b611c886040805160e160020a6333f8a36702815260066004820152600160a060020a0333166024820152905160009173c895c144d0b0f88417cf9e14e03e6abc82c0af3f916367f146ce916044818101926020929091908290030181878760325a03f2156100025750506040515191506104ee9050565b611c885b60007327b1b436e4699a012cc8698e33c8f3e1c035c28b6323306ed66040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506104ee9050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63e99a66856000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611264604080517f317c152d00000000000000000000000000000000000000000000000000000000815260066004820152600160a060020a0333166024820152905160009173c895c144d0b0f88417cf9e14e03e6abc82c0af3f9163317c152d916044818101926020929091908290030181878760325a03f2156100025750506040805180517ff1173928000000000000000000000000000000000000000000000000000000008252600160a060020a0333166004830152602482018190529151919363f1173928926044838101938290030181838760325a03f2156100025750505050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63707378396000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611264600435604080517fb5bc6dbb00000000000000000000000000000000000000000000000000000000815260126004820152600160a060020a033316602482015260448101839052905173d3cb18959b0435864ff33010fa83be60afc04b229163b5bc6dbb916064828101926020929190829003018160008760325a03f21561000257505060405151159050611d255773d3cb18959b0435864ff33010fa83be60afc04b22637fcf532c33836040518360e060020a0281526004018083600160a060020a031681526020018281526020019250505060006040518083038160008760325a03f21561000257505050611ae5565b611c886004356000818152600e60205260409020600201545b919050565b611c886004355b600160a060020a0381166000908152600f6020526040902054610898565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63fc4730126000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611264600435604080517fa95d3e76000000000000000000000000000000000000000000000000000000008152600060048201819052600160a060020a0384811660248401523316604483015291517327b1b436e4699a012cc8698e33c8f3e1c035c28b9263a95d3e7692606481810193918290030181838760325a03f2156100025750505050565b611c886002546104ee565b611c9a60043560243560007327b1b436e4699a012cc8698e33c8f3e1c035c28b6398213db6600060005085856040518460e060020a02815260040180848152602001838152602001828152602001935050505060206040518083038160008760325a03f215610002575050604051519150610dda9050565b611c886000611e0a336108a4565b611264600073c895c144d0b0f88417cf9e14e03e6abc82c0af3f635748147e600633611ec2610577565b61126460043560243560443560643560843560a4355b604080517ff1924efb000000000000000000000000000000000000000000000000000000008152600060048201819052600160a060020a03338116602484015289166044830152606482018890526084820187905260a4820186905260ff851660c483015260e48201849052915182917327b1b436e4699a012cc8698e33c8f3e1c035c28b9163f1924efb91610104818101926020929091908290030181878760325a03f2156100025750506040805180517f5a1230bf000000000000000000000000000000000000000000000000000000008252600160a060020a0333811660048401528c166024830152604482018b9052606482018a90526084820189905260ff881660a483015260c482018790529151919450635a1230bf9160e48083019260209291908290030181878760325a03f215610002575050604051519183149050612008577327b1b436e4699a012cc8698e33c8f3e1c035c28b6318b753ab82846040518360e060020a028152600401808381526020018281526020019250505060006040518083038160008760325a03f21561000257505050612056565b611c9a600154600160a060020a03166104ee565b611c886040805160e560020a6304b47bb902815260066004820152600160a060020a0333166024820152905160009173c895c144d0b0f88417cf9e14e03e6abc82c0af3f9163968f7720916044818101926020929091908290030181878760325a03f2156100025750506040515191506104ee9050565b6112646004357327b1b436e4699a012cc8698e33c8f3e1c035c28b637e853f3d600060005083336040518460e060020a0281526004018084815260200183815260200182600160a060020a03168152602001935050505060206040518083038160008760325a03f21561000257505060405151159050611ae5577327b1b436e4699a012cc8698e33c8f3e1c035c28b63ab2af349826040518260e060020a0281526004018082815260200191505060006040518083038160008760325a03f2156100025750505050565b611c886008546104ee565b611c88600435602435604080516c01000000000000000000000000600160a060020a03858116820283528416026014820152815160289181900391909101902060009081526015602052205460ff165b92915050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63b506054f6000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611264604080517f068e3ef100000000000000000000000000000000000000000000000000000000815260066004820152600160a060020a0333166024820152346044820152905173c895c144d0b0f88417cf9e14e03e6abc82c0af3f9163068e3ef19160648281019260009291908290030181838760325a03f21561000257505050565b611cb76004356040805160208181018352600080835284815260138252838120600d0154815260148252835190849020805460026001821615610100026000190190911604601f81018490048402830184019095528482529293909291830182828015611fb85780601f10611f8d57610100808354040283529160200191611fb8565b611c886004356024355b604080517fa163a32500000000000000000000000000000000000000000000000000000000815260066004820152600160a060020a038416602482015260448101839052905160009173c895c144d0b0f88417cf9e14e03e6abc82c0af3f9163a163a325916064818101926020929091908290030181878760325a03f215610002575050604051519150610dda9050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63775f20f96000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b637517a7c96000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c9a60043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63250687836000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c886004356040805160e160020a6333f8a36702815260066004820152600160a060020a0383166024820152905160009173c895c144d0b0f88417cf9e14e03e6abc82c0af3f916367f146ce916044818101926020929091908290030181878760325a03f2156100025750506040515191506108989050565b611c88600435600073c895c144d0b0f88417cf9e14e03e6abc82c0af3f6354e37911600684611e6d610577565b611c88600435600160a060020a038116600090815260126020526040902054610898565b611c9a600054600160a060020a03166104ee565b604080516c01000000000000000000000000600435600160a060020a0390811682028352331602601482015281516028918190039190910190206000908152601560205220805460ff191690555b005b611c8860007327b1b436e4699a012cc8698e33c8f3e1c035c28b6398e00e546040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506104ee9050565b611c88600435604080517fe6ce3a6a000000000000000000000000000000000000000000000000000000008152601060048201527f3e3d000000000000000000000000000000000000000000000000000000000000602482015260448101839052905160009173ce642b6a82e72147ceade0e72c786ba8eaeb31d79163e6ce3a6a916064818101926020929091908290030181878760325a03f2156100025750506040515191506108989050565b611c88604080517f8f00e61a00000000000000000000000000000000000000000000000000000000815260066004820152905160009173c895c144d0b0f88417cf9e14e03e6abc82c0af3f91638f00e61a916024818101926020929091908290030181878760325a03f2156100025750506040515191506104ee9050565b611c886004356000611e113383610f5f565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63dd382dd36000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63aebd65476000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c886004356040805160e560020a6304b47bb902815260066004820152600160a060020a0383166024820152905160009173c895c144d0b0f88417cf9e14e03e6abc82c0af3f9163968f7720916044818101926020929091908290030181878760325a03f2156100025750506040515191506108989050565b611c88604080517fc75e8f8800000000000000000000000000000000000000000000000000000000815260066004820152905160009173c895c144d0b0f88417cf9e14e03e6abc82c0af3f9163c75e8f88916024818101926020929091908290030181878760325a03f2156100025750506040515191506104ee9050565b611cb760408051602081810183526000825282516003805460026000196001831615610100020190911604601f81018490048402830184019095528482529293909291830182828015611fef5780601f10611fc457610100808354040283529160200191611fef565b611264604080517fa89713750000000000000000000000000000000000000000000000000000000081526000600482018181526024830193845236604484018190527327b1b436e4699a012cc8698e33c8f3e1c035c28b9463a89713759484939190606401848480828437820191505094505050505060006040518083038160008760325a03f215610002575050604080516005547f321f45840000000000000000000000000000000000000000000000000000000082526004820152905163321f4584916024818101926000929091908290030181838760325a03f21561000257505050565b611c886004356000818152600e6020526040902060030154610898565b611c8860007327b1b436e4699a012cc8698e33c8f3e1c035c28b63c0f688596040518160e060020a02815260040180905060206040518083038160008760325a03f2156100025750506040515191506104ee9050565b61126460043573c895c144d0b0f88417cf9e14e03e6abc82c0af3f63dd8abb6c60063384611db7610577565b611c88600073c895c144d0b0f88417cf9e14e03e6abc82c0af3f6354e37911600633611e18610577565b611c886007546104ee565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63125935846000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b6112646102ee565b611c886004356000818152601360209081526040805181842060038101546004828101547f38f4c9eb0000000000000000000000000000000000000000000000000000000085526006918501919091526024840182905260ff160160448301529151919273c895c144d0b0f88417cf9e14e03e6abc82c0af3f926338f4c9eb9260648181019392918290030181888760325a03f21561000257505060405151949350505050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63fae644646000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63b3a5e2556000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c886006546104ee565b6112646004355b604080517fb1df3d8000000000000000000000000000000000000000000000000000000000815260126004820152600160a060020a0383166024820152346044820152905173d3cb18959b0435864ff33010fa83be60afc04b229163b1df3d80916064828101926020929190829003018160008760325a03f215610002575050604080517f5548c837000000000000000000000000000000000000000000000000000000008152600160a060020a033381166004830152841660248201523460448201529051635548c837916064818101926000929091908290030181838760325a03f215610002575050505b50565b611264600435602435604435606435611ffb8484848460ff6000610a89565b611c886004356000818152600e6020526040902060010154610898565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b63c9abdb7c6000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611c8860043560007327b1b436e4699a012cc8698e33c8f3e1c035c28b6386b0aac96000600050846040518360e060020a028152600401808381526020018281526020019250505060206040518083038160008760325a03f2156100025750506040515191506108989050565b611264600435604080517f25fea09900000000000000000000000000000000000000000000000000000000815260006004820181905260248201849052600160a060020a033316604483015291517327b1b436e4699a012cc8698e33c8f3e1c035c28b926325fea09992606481810193918290030181838760325a03f2156100025750505050565b565b60408051918252519081900360200190f35b60408051600160a060020a03929092168252519081900360200190f35b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f168015611d175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b600160a060020a0333166000818152601260205260408051818320547f5c54305e00000000000000000000000000000000000000000000000000000000825260048201949094526024810185905260448101939093525173d3cb18959b0435864ff33010fa83be60afc04b2292635c54305e9260648281019391928290030181838760325a03f2156100025750505050565b6040518560e060020a0281526004018085815260200184600160a060020a0316815260200183815260200182815260200194505050505060006040518083038160008760325a03f2156100025750505050565b90506104ee565b9050610898565b6040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f2156100025750506040515191506104ee9050565b6040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f2156100025750506040515191506108989050565b6040518460e060020a0281526004018084815260200183600160a060020a03168152602001828152602001935050505060206040518083038160008760325a03f2156100025750506040805180517f6a704d7b000000000000000000000000000000000000000000000000000000008252600160a060020a033316600483015260248201819052915191935073c895c144d0b0f88417cf9e14e03e6abc82c0af3f9250636a704d7b9160448281019260009291908290030181838760325a03f2156100025750505050565b820191906000526020600020905b815481529060010190602001808311611f9b57829003601f168201915b50505050509050610898565b820191906000526020600020905b815481529060010190602001808311611fd257829003601f168201915b505050505090506104ee565b50505050565b5050505050565b7327b1b436e4699a012cc8698e33c8f3e1c035c28b635ca1bad5826040518260e060020a0281526004018082815260200191505060006040518083038160008760325a03f215610002575050505b505050505050505056", + "storage": { + "0x18b039f13c5f33908f0960616cb3e44029c716366508c54d555096d6e1fa5145": "0x00000000000000000000000000000000000000000000000008b83c417dd78000" + } + }, + "0xd3cb18959b0435864ff33010fa83be60afc04b22": { + "balance": "0x0", + "code": "0x650105e11e10f850606060405236156100695760e060020a60003504635548c837811461006e5780635c54305e146100ca5780636b1039661461011e5780637fcf532c14610152578063b1df3d801461019e578063b5bc6dbb146101b7578063e62af6c1146101ee575b610007565b61022060043560243560443581600160a060020a031683600160a060020a03167f47a08955ce2b7f21ea62ff0024e1ea0ad87430953554a87e6bc65d777f18e639836040518082815260200191505060405180910390a3505050565b61022060043560243560443560408051838152602081018390528151600160a060020a038616927f9b24879829bed3003de08d5c5d7e18dcbb8dc76faebd95cafc5d4dec8c61a3a5928290030190a2505050565b6102206004356024356044355b600160a060020a038216600090815260208490526040902054808201101561023457610007565b610220600435602435604080518281529051600160a060020a038416917fd0c5cf41ee8ebf084ad0bce53de7cbc6e4693d9b53a4019ca36a2f91cdc20b3a919081900360200190a25050565b610222600435602435604435600061025784848461012b565b610222600435602435604435600160a060020a0382166000908152602084905260408120548290106102865761028e8484846101fb565b6102206004356024356044355b600160a060020a03821660009081526020849052604090205481111561026257610007565b005b60408051918252519081900360200190f35b600160a060020a0382166000908152602084905260409020805482019055505050565b5060015b9392505050565b600160a060020a038216600090815260208490526040902080548290039055505050565b50600061025b565b604051600160a060020a03841690600090849082818181858883f19350505050151561025757604051600160a060020a038416908390600081818185876185025a03f19250505015156102575761000756" + }, + "0xd3cda913deb6f67967b99d67acdfa1712c293601": { + "balance": "0x1ff0509d9d6821e26", + "nonce": "138", + "code": "0x" + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "ethash": {} + } + }, + "context": { + "number": "422909", + "difficulty": "5694909537365", + "timestamp": "1445530357", + "gasLimit": "3141592", + "miner": "0x2a65aca4d5fc5b5c859090a6c34d164135398226" + }, + "input": "0xf86a818a850ba43b7400832d8a40942861bf89b6c640c79040d357c1e9513693ef5d3f808441c0e1b51ca0b8de64a9a04d699f5938efa5431ca7c80500f6accb329da43aadabd4eab84f17a035b969c198f694be991a2a5b287250e19e852efd0ccba30bd50707277bfbc9aa", + "tracerConfig": { + "diffMode": true + }, + "result": { + "pre": { + "0x2861bf89b6c640c79040d357c1e9513693ef5d3f": { + "balance": "0x0", + "code": "0x606060405236156100825760e060020a600035046312055e8f8114610084578063185061da146100b157806322beb9b9146100d5578063245a03ec146101865780633fa4f245146102a657806341c0e1b5146102af578063890eba68146102cb578063b29f0835146102de578063d6b4485914610308578063dd012a15146103b9575b005b6001805474ff0000000000000000000000000000000000000000191660a060020a60043502179055610082565b6100826001805475ff00000000000000000000000000000000000000000019169055565b61008260043560015460e060020a6352afbc3302606090815230600160a060020a039081166064527fb29f0835000000000000000000000000000000000000000000000000000000006084527fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060a45243840160c490815260ff60a060020a85041660e452600061010481905291909316926352afbc339261012492918183876161da5a03f1156100025750505050565b6100826004356024356001547fb0f07e440000000000000000000000000000000000000000000000000000000060609081526064839052600160a060020a039091169063b0f07e449060849060009060248183876161da5a03f150604080516001547f73657449742875696e74323536290000000000000000000000000000000000008252825191829003600e018220878352835192839003602001832060e060020a6352afbc33028452600160a060020a03308116600486015260e060020a9283900490920260248501526044840152438901606484015260a060020a820460ff1694830194909452600060a483018190529251931694506352afbc33935060c48181019391829003018183876161da5a03f115610002575050505050565b6103c460025481565b61008260005433600160a060020a039081169116146103ce575b565b6103c460015460a860020a900460ff1681565b6100826001805475ff000000000000000000000000000000000000000000191660a860020a179055565b61008260043560015460e060020a6352afbc3302606090815230600160a060020a039081166064527f185061da000000000000000000000000000000000000000000000000000000006084527fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060a45243840160c490815260ff60a060020a85041660e452600061010481905291909316926352afbc339261012492918183876161da5a03f1156100025750505050565b600435600255610082565b6060908152602090f35b6001547f6ff96d17000000000000000000000000000000000000000000000000000000006060908152600160a060020a0330811660645290911690632e1a7d4d908290636ff96d17906084906020906024816000876161da5a03f1156100025750506040805180517f2e1a7d4d0000000000000000000000000000000000000000000000000000000082526004820152905160248281019350600092829003018183876161da5a03f115610002575050600054600160a060020a03169050ff", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000d3cda913deb6f67967b99d67acdfa1712c293601", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000ff30c9e568f133adce1f1ea91e189613223fc461b9" + } + }, + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x326601cc6cf364f6b9", + "nonce": 12122 + }, + "0xd3cda913deb6f67967b99d67acdfa1712c293601": { + "balance": "0x1ff0509d9d6821e26", + "nonce": 138 + } + }, + "post": { + "0x2a65aca4d5fc5b5c859090a6c34d164135398226": { + "balance": "0x326604ee5f5eecd2b9" + }, + "0xd3cda913deb6f67967b99d67acdfa1712c293601": { + "balance": "0x1ff01e7e76afa4226", + "nonce": 139 + } + } + } +} diff --git a/eth/tracers/internal/tracetest/util.go b/eth/tracers/internal/tracetest/util.go new file mode 100644 index 0000000..a74a96f --- /dev/null +++ b/eth/tracers/internal/tracetest/util.go @@ -0,0 +1,55 @@ +package tracetest + +import ( + "math/big" + "strings" + "unicode" + + "github.com/ethereum/go-ethereum/common" + "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/vm" + + // Force-load native and js packages, to trigger registration + _ "github.com/ethereum/go-ethereum/eth/tracers/js" + _ "github.com/ethereum/go-ethereum/eth/tracers/native" +) + +// camel converts a snake cased input string into a camel cased output. +func camel(str string) string { + pieces := strings.Split(str, "_") + for i := 1; i < len(pieces); i++ { + pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:] + } + return strings.Join(pieces, "") +} + +type callContext struct { + Number math.HexOrDecimal64 `json:"number"` + Difficulty *math.HexOrDecimal256 `json:"difficulty"` + Time math.HexOrDecimal64 `json:"timestamp"` + GasLimit math.HexOrDecimal64 `json:"gasLimit"` + Miner common.Address `json:"miner"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` +} + +func (c *callContext) toBlockContext(genesis *core.Genesis) vm.BlockContext { + context := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: c.Miner, + BlockNumber: new(big.Int).SetUint64(uint64(c.Number)), + Time: uint64(c.Time), + Difficulty: (*big.Int)(c.Difficulty), + GasLimit: uint64(c.GasLimit), + } + if genesis.Config.IsLondon(context.BlockNumber) { + context.BaseFee = (*big.Int)(c.BaseFee) + } + if genesis.ExcessBlobGas != nil && genesis.BlobGasUsed != nil { + excessBlobGas := eip4844.CalcExcessBlobGas(*genesis.ExcessBlobGas, *genesis.BlobGasUsed) + context.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + } + return context +} diff --git a/eth/tracers/internal/util.go b/eth/tracers/internal/util.go new file mode 100644 index 0000000..347af43 --- /dev/null +++ b/eth/tracers/internal/util.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 internal + +import ( + "errors" + "fmt" + + "github.com/holiman/uint256" +) + +const ( + memoryPadLimit = 1024 * 1024 +) + +// GetMemoryCopyPadded returns offset + size as a new slice. +// It zero-pads the slice if it extends beyond memory bounds. +func GetMemoryCopyPadded(m []byte, offset, size int64) ([]byte, error) { + if offset < 0 || size < 0 { + return nil, errors.New("offset or size must not be negative") + } + length := int64(len(m)) + if offset+size < length { // slice fully inside memory + return memoryCopy(m, offset, size), nil + } + paddingNeeded := offset + size - length + if paddingNeeded > memoryPadLimit { + return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded) + } + cpy := make([]byte, size) + if overlap := length - offset; overlap > 0 { + copy(cpy, MemoryPtr(m, offset, overlap)) + } + return cpy, nil +} + +func memoryCopy(m []byte, offset, size int64) (cpy []byte) { + if size == 0 { + return nil + } + + if len(m) > int(offset) { + cpy = make([]byte, size) + copy(cpy, m[offset:offset+size]) + + return + } + + return +} + +// MemoryPtr returns a pointer to a slice of memory. +func MemoryPtr(m []byte, offset, size int64) []byte { + if size == 0 { + return nil + } + + if len(m) > int(offset) { + return m[offset : offset+size] + } + + return nil +} + +// StackBack returns the n'th item in stack +func StackBack(st []uint256.Int, n int) *uint256.Int { + return &st[len(st)-n-1] +} diff --git a/eth/tracers/internal/util_test.go b/eth/tracers/internal/util_test.go new file mode 100644 index 0000000..6a46731 --- /dev/null +++ b/eth/tracers/internal/util_test.go @@ -0,0 +1,60 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . +package internal + +import ( + "testing" + + "github.com/ethereum/go-ethereum/core/vm" +) + +func TestMemCopying(t *testing.T) { + for i, tc := range []struct { + memsize int64 + offset int64 + size int64 + wantErr string + wantSize int + }{ + {0, 0, 100, "", 100}, // Should pad up to 100 + {0, 100, 0, "", 0}, // No need to pad (0 size) + {100, 50, 100, "", 100}, // Should pad 100-150 + {100, 50, 5, "", 5}, // Wanted range fully within memory + {100, -50, 0, "offset or size must not be negative", 0}, // Error + {0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Error + {10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Error + + } { + mem := vm.NewMemory() + mem.Resize(uint64(tc.memsize)) + cpy, err := GetMemoryCopyPadded(mem.Data(), tc.offset, tc.size) + if want := tc.wantErr; want != "" { + if err == nil { + t.Fatalf("test %d: want '%v' have no error", i, want) + } + if have := err.Error(); want != have { + t.Fatalf("test %d: want '%v' have '%v'", i, want, have) + } + continue + } + if err != nil { + t.Fatalf("test %d: unexpected error: %v", i, err) + } + if want, have := tc.wantSize, len(cpy); have != want { + t.Fatalf("test %d: want %v have %v", i, want, have) + } + } +} diff --git a/eth/tracers/js/bigint.go b/eth/tracers/js/bigint.go new file mode 100644 index 0000000..9aeb330 --- /dev/null +++ b/eth/tracers/js/bigint.go @@ -0,0 +1,20 @@ +// 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 js + +// bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js. +const bigIntegerJS = `var bigInt=function(undefined){"use strict";var BASE=1e7,LOG_BASE=7,MAX_INT=9007199254740992,MAX_INT_ARR=smallToArray(MAX_INT),LOG_MAX_INT=Math.log(MAX_INT);function Integer(v,radix){if(typeof v==="undefined")return Integer[0];if(typeof radix!=="undefined")return+radix===10?parseValue(v):parseBase(v,radix);return parseValue(v)}function BigInteger(value,sign){this.value=value;this.sign=sign;this.isSmall=false}BigInteger.prototype=Object.create(Integer.prototype);function SmallInteger(value){this.value=value;this.sign=value<0;this.isSmall=true}SmallInteger.prototype=Object.create(Integer.prototype);function isPrecise(n){return-MAX_INT0)return Math.floor(n);return Math.ceil(n)}function add(a,b){var l_a=a.length,l_b=b.length,r=new Array(l_a),carry=0,base=BASE,sum,i;for(i=0;i=base?1:0;r[i]=sum-carry*base}while(i0)r.push(carry);return r}function addAny(a,b){if(a.length>=b.length)return add(a,b);return add(b,a)}function addSmall(a,carry){var l=a.length,r=new Array(l),base=BASE,sum,i;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}BigInteger.prototype.add=function(v){var n=parseValue(v);if(this.sign!==n.sign){return this.subtract(n.negate())}var a=this.value,b=n.value;if(n.isSmall){return new BigInteger(addSmall(a,Math.abs(b)),this.sign)}return new BigInteger(addAny(a,b),this.sign)};BigInteger.prototype.plus=BigInteger.prototype.add;SmallInteger.prototype.add=function(v){var n=parseValue(v);var a=this.value;if(a<0!==n.sign){return this.subtract(n.negate())}var b=n.value;if(n.isSmall){if(isPrecise(a+b))return new SmallInteger(a+b);b=smallToArray(Math.abs(b))}return new BigInteger(addSmall(b,Math.abs(a)),a<0)};SmallInteger.prototype.plus=SmallInteger.prototype.add;function subtract(a,b){var a_l=a.length,b_l=b.length,r=new Array(a_l),borrow=0,base=BASE,i,difference;for(i=0;i=0){value=subtract(a,b)}else{value=subtract(b,a);sign=!sign}value=arrayToSmall(value);if(typeof value==="number"){if(sign)value=-value;return new SmallInteger(value)}return new BigInteger(value,sign)}function subtractSmall(a,b,sign){var l=a.length,r=new Array(l),carry=-b,base=BASE,i,difference;for(i=0;i=0)};SmallInteger.prototype.minus=SmallInteger.prototype.subtract;BigInteger.prototype.negate=function(){return new BigInteger(this.value,!this.sign)};SmallInteger.prototype.negate=function(){var sign=this.sign;var small=new SmallInteger(-this.value);small.sign=!sign;return small};BigInteger.prototype.abs=function(){return new BigInteger(this.value,false)};SmallInteger.prototype.abs=function(){return new SmallInteger(Math.abs(this.value))};function multiplyLong(a,b){var a_l=a.length,b_l=b.length,l=a_l+b_l,r=createArray(l),base=BASE,product,carry,i,a_i,b_j;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}function shiftLeft(x,n){var r=[];while(n-- >0)r.push(0);return r.concat(x)}function multiplyKaratsuba(x,y){var n=Math.max(x.length,y.length);if(n<=30)return multiplyLong(x,y);n=Math.ceil(n/2);var b=x.slice(n),a=x.slice(0,n),d=y.slice(n),c=y.slice(0,n);var ac=multiplyKaratsuba(a,c),bd=multiplyKaratsuba(b,d),abcd=multiplyKaratsuba(addAny(a,b),addAny(c,d));var product=addAny(addAny(ac,shiftLeft(subtract(subtract(abcd,ac),bd),n)),shiftLeft(bd,2*n));trim(product);return product}function useKaratsuba(l1,l2){return-.012*l1-.012*l2+15e-6*l1*l2>0}BigInteger.prototype.multiply=function(v){var n=parseValue(v),a=this.value,b=n.value,sign=this.sign!==n.sign,abs;if(n.isSmall){if(b===0)return Integer[0];if(b===1)return this;if(b===-1)return this.negate();abs=Math.abs(b);if(abs=0;shift--){quotientDigit=base-1;if(remainder[shift+b_l]!==divisorMostSignificantDigit){quotientDigit=Math.floor((remainder[shift+b_l]*base+remainder[shift+b_l-1])/divisorMostSignificantDigit)}carry=0;borrow=0;l=divisor.length;for(i=0;ib_l){highx=(highx+1)*base}guess=Math.ceil(highx/highy);do{check=multiplySmall(b,guess);if(compareAbs(check,part)<=0)break;guess--}while(guess);result.push(guess);part=subtract(part,check)}result.reverse();return[arrayToSmall(result),arrayToSmall(part)]}function divModSmall(value,lambda){var length=value.length,quotient=createArray(length),base=BASE,i,q,remainder,divisor;remainder=0;for(i=length-1;i>=0;--i){divisor=remainder*base+value[i];q=truncate(divisor/lambda);remainder=divisor-q*lambda;quotient[i]=q|0}return[quotient,remainder|0]}function divModAny(self,v){var value,n=parseValue(v);var a=self.value,b=n.value;var quotient;if(b===0)throw new Error("Cannot divide by zero");if(self.isSmall){if(n.isSmall){return[new SmallInteger(truncate(a/b)),new SmallInteger(a%b)]}return[Integer[0],self]}if(n.isSmall){if(b===1)return[self,Integer[0]];if(b==-1)return[self.negate(),Integer[0]];var abs=Math.abs(b);if(absb.length?1:-1}for(var i=a.length-1;i>=0;i--){if(a[i]!==b[i])return a[i]>b[i]?1:-1}return 0}BigInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall)return 1;return compareAbs(a,b)};SmallInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=Math.abs(this.value),b=n.value;if(n.isSmall){b=Math.abs(b);return a===b?0:a>b?1:-1}return-1};BigInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(this.sign!==n.sign){return n.sign?1:-1}if(n.isSmall){return this.sign?-1:1}return compareAbs(a,b)*(this.sign?-1:1)};BigInteger.prototype.compareTo=BigInteger.prototype.compare;SmallInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall){return a==b?0:a>b?1:-1}if(a<0!==n.sign){return a<0?-1:1}return a<0?1:-1};SmallInteger.prototype.compareTo=SmallInteger.prototype.compare;BigInteger.prototype.equals=function(v){return this.compare(v)===0};SmallInteger.prototype.eq=SmallInteger.prototype.equals=BigInteger.prototype.eq=BigInteger.prototype.equals;BigInteger.prototype.notEquals=function(v){return this.compare(v)!==0};SmallInteger.prototype.neq=SmallInteger.prototype.notEquals=BigInteger.prototype.neq=BigInteger.prototype.notEquals;BigInteger.prototype.greater=function(v){return this.compare(v)>0};SmallInteger.prototype.gt=SmallInteger.prototype.greater=BigInteger.prototype.gt=BigInteger.prototype.greater;BigInteger.prototype.lesser=function(v){return this.compare(v)<0};SmallInteger.prototype.lt=SmallInteger.prototype.lesser=BigInteger.prototype.lt=BigInteger.prototype.lesser;BigInteger.prototype.greaterOrEquals=function(v){return this.compare(v)>=0};SmallInteger.prototype.geq=SmallInteger.prototype.greaterOrEquals=BigInteger.prototype.geq=BigInteger.prototype.greaterOrEquals;BigInteger.prototype.lesserOrEquals=function(v){return this.compare(v)<=0};SmallInteger.prototype.leq=SmallInteger.prototype.lesserOrEquals=BigInteger.prototype.leq=BigInteger.prototype.lesserOrEquals;BigInteger.prototype.isEven=function(){return(this.value[0]&1)===0};SmallInteger.prototype.isEven=function(){return(this.value&1)===0};BigInteger.prototype.isOdd=function(){return(this.value[0]&1)===1};SmallInteger.prototype.isOdd=function(){return(this.value&1)===1};BigInteger.prototype.isPositive=function(){return!this.sign};SmallInteger.prototype.isPositive=function(){return this.value>0};BigInteger.prototype.isNegative=function(){return this.sign};SmallInteger.prototype.isNegative=function(){return this.value<0};BigInteger.prototype.isUnit=function(){return false};SmallInteger.prototype.isUnit=function(){return Math.abs(this.value)===1};BigInteger.prototype.isZero=function(){return false};SmallInteger.prototype.isZero=function(){return this.value===0};BigInteger.prototype.isDivisibleBy=function(v){var n=parseValue(v);var value=n.value;if(value===0)return false;if(value===1)return true;if(value===2)return this.isEven();return this.mod(n).equals(Integer[0])};SmallInteger.prototype.isDivisibleBy=BigInteger.prototype.isDivisibleBy;function isBasicPrime(v){var n=v.abs();if(n.isUnit())return false;if(n.equals(2)||n.equals(3)||n.equals(5))return true;if(n.isEven()||n.isDivisibleBy(3)||n.isDivisibleBy(5))return false;if(n.lesser(25))return true}BigInteger.prototype.isPrime=function(){var isPrime=isBasicPrime(this);if(isPrime!==undefined)return isPrime;var n=this.abs(),nPrev=n.prev();var a=[2,3,5,7,11,13,17,19],b=nPrev,d,t,i,x;while(b.isEven())b=b.divide(2);for(i=0;i-MAX_INT)return new SmallInteger(value-1);return new BigInteger(MAX_INT_ARR,true)};var powersOfTwo=[1];while(2*powersOfTwo[powersOfTwo.length-1]<=BASE)powersOfTwo.push(2*powersOfTwo[powersOfTwo.length-1]);var powers2Length=powersOfTwo.length,highestPower2=powersOfTwo[powers2Length-1];function shift_isSmall(n){return(typeof n==="number"||typeof n==="string")&&+Math.abs(n)<=BASE||n instanceof BigInteger&&n.value.length<=1}BigInteger.prototype.shiftLeft=function(n){if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftRight(-n);var result=this;while(n>=powers2Length){result=result.multiply(highestPower2);n-=powers2Length-1}return result.multiply(powersOfTwo[n])};SmallInteger.prototype.shiftLeft=BigInteger.prototype.shiftLeft;BigInteger.prototype.shiftRight=function(n){var remQuo;if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftLeft(-n);var result=this;while(n>=powers2Length){if(result.isZero())return result;remQuo=divModAny(result,highestPower2);result=remQuo[1].isNegative()?remQuo[0].prev():remQuo[0];n-=powers2Length-1}remQuo=divModAny(result,powersOfTwo[n]);return remQuo[1].isNegative()?remQuo[0].prev():remQuo[0]};SmallInteger.prototype.shiftRight=BigInteger.prototype.shiftRight;function bitwise(x,y,fn){y=parseValue(y);var xSign=x.isNegative(),ySign=y.isNegative();var xRem=xSign?x.not():x,yRem=ySign?y.not():y;var xDigit=0,yDigit=0;var xDivMod=null,yDivMod=null;var result=[];while(!xRem.isZero()||!yRem.isZero()){xDivMod=divModAny(xRem,highestPower2);xDigit=xDivMod[1].toJSNumber();if(xSign){xDigit=highestPower2-1-xDigit}yDivMod=divModAny(yRem,highestPower2);yDigit=yDivMod[1].toJSNumber();if(ySign){yDigit=highestPower2-1-yDigit}xRem=xDivMod[0];yRem=yDivMod[0];result.push(fn(xDigit,yDigit))}var sum=fn(xSign?1:0,ySign?1:0)!==0?bigInt(-1):bigInt(0);for(var i=result.length-1;i>=0;i-=1){sum=sum.multiply(highestPower2).add(bigInt(result[i]))}return sum}BigInteger.prototype.not=function(){return this.negate().prev()};SmallInteger.prototype.not=BigInteger.prototype.not;BigInteger.prototype.and=function(n){return bitwise(this,n,function(a,b){return a&b})};SmallInteger.prototype.and=BigInteger.prototype.and;BigInteger.prototype.or=function(n){return bitwise(this,n,function(a,b){return a|b})};SmallInteger.prototype.or=BigInteger.prototype.or;BigInteger.prototype.xor=function(n){return bitwise(this,n,function(a,b){return a^b})};SmallInteger.prototype.xor=BigInteger.prototype.xor;var LOBMASK_I=1<<30,LOBMASK_BI=(BASE&-BASE)*(BASE&-BASE)|LOBMASK_I;function roughLOB(n){var v=n.value,x=typeof v==="number"?v|LOBMASK_I:v[0]+v[1]*BASE|LOBMASK_BI;return x&-x}function max(a,b){a=parseValue(a);b=parseValue(b);return a.greater(b)?a:b}function min(a,b){a=parseValue(a);b=parseValue(b);return a.lesser(b)?a:b}function gcd(a,b){a=parseValue(a).abs();b=parseValue(b).abs();if(a.equals(b))return a;if(a.isZero())return b;if(b.isZero())return a;var c=Integer[1],d,t;while(a.isEven()&&b.isEven()){d=Math.min(roughLOB(a),roughLOB(b));a=a.divide(d);b=b.divide(d);c=c.multiply(d)}while(a.isEven()){a=a.divide(roughLOB(a))}do{while(b.isEven()){b=b.divide(roughLOB(b))}if(a.greater(b)){t=b;b=a;a=t}b=b.subtract(a)}while(!b.isZero());return c.isUnit()?a:a.multiply(c)}function lcm(a,b){a=parseValue(a).abs();b=parseValue(b).abs();return a.divide(gcd(a,b)).multiply(b)}function randBetween(a,b){a=parseValue(a);b=parseValue(b);var low=min(a,b),high=max(a,b);var range=high.subtract(low).add(1);if(range.isSmall)return low.add(Math.floor(Math.random()*range));var length=range.value.length-1;var result=[],restricted=true;for(var i=length;i>=0;i--){var top=restricted?range.value[i]:BASE;var digit=truncate(Math.random()*top);result.unshift(digit);if(digit=absBase){if(c==="1"&&absBase===1)continue;throw new Error(c+" is not a valid digit in base "+base+".")}else if(c.charCodeAt(0)-87>=absBase){throw new Error(c+" is not a valid digit in base "+base+".")}}}if(2<=base&&base<=36){if(length<=LOG_MAX_INT/Math.log(base)){var result=parseInt(text,base);if(isNaN(result)){throw new Error(c+" is not a valid digit in base "+base+".")}return new SmallInteger(parseInt(text,base))}}base=parseValue(base);var digits=[];var isNegative=text[0]==="-";for(i=isNegative?1:0;i");digits.push(parseValue(text.slice(start+1,i)))}else throw new Error(c+" is not a valid character")}return parseBaseFromArray(digits,base,isNegative)};function parseBaseFromArray(digits,base,isNegative){var val=Integer[0],pow=Integer[1],i;for(i=digits.length-1;i>=0;i--){val=val.add(digits[i].times(pow));pow=pow.times(base)}return isNegative?val.negate():val}function stringify(digit){var v=digit.value;if(typeof v==="number")v=[v];if(v.length===1&&v[0]<=35){return"0123456789abcdefghijklmnopqrstuvwxyz".charAt(v[0])}return"<"+v+">"}function toBase(n,base){base=bigInt(base);if(base.isZero()){if(n.isZero())return"0";throw new Error("Cannot convert nonzero numbers to base 0.")}if(base.equals(-1)){if(n.isZero())return"0";if(n.isNegative())return new Array(1-n).join("10");return"1"+new Array(+n).join("01")}var minusSign="";if(n.isNegative()&&base.isPositive()){minusSign="-";n=n.abs()}if(base.equals(1)){if(n.isZero())return"0";return minusSign+new Array(+n+1).join(1)}var out=[];var left=n,divmod;while(left.isNegative()||left.compareAbs(base)>=0){divmod=left.divmod(base);left=divmod.quotient;var digit=divmod.remainder;if(digit.isNegative()){digit=base.minus(digit).abs();left=left.next()}out.push(stringify(digit))}out.push(stringify(left));return minusSign+out.reverse().join("")}BigInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!==10)return toBase(this,radix);var v=this.value,l=v.length,str=String(v[--l]),zeros="0000000",digit;while(--l>=0){digit=String(v[l]);str+=zeros.slice(digit.length)+digit}var sign=this.sign?"-":"";return sign+str};SmallInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!=10)return toBase(this,radix);return String(this.value)};BigInteger.prototype.toJSON=SmallInteger.prototype.toJSON=function(){return this.toString()};BigInteger.prototype.valueOf=function(){return+this.toString()};BigInteger.prototype.toJSNumber=BigInteger.prototype.valueOf;SmallInteger.prototype.valueOf=function(){return this.value};SmallInteger.prototype.toJSNumber=SmallInteger.prototype.valueOf;function parseStringValue(v){if(isPrecise(+v)){var x=+v;if(x===truncate(x))return new SmallInteger(x);throw"Invalid integer: "+v}var sign=v[0]==="-";if(sign)v=v.slice(1);var split=v.split(/e/i);if(split.length>2)throw new Error("Invalid integer: "+split.join("e"));if(split.length===2){var exp=split[1];if(exp[0]==="+")exp=exp.slice(1);exp=+exp;if(exp!==truncate(exp)||!isPrecise(exp))throw new Error("Invalid integer: "+exp+" is not a valid exponent.");var text=split[0];var decimalPlace=text.indexOf(".");if(decimalPlace>=0){exp-=text.length-decimalPlace-1;text=text.slice(0,decimalPlace)+text.slice(decimalPlace+1)}if(exp<0)throw new Error("Cannot include negative exponent part for integers");text+=new Array(exp+1).join("0");v=text}var isValid=/^([0-9][0-9]*)$/.test(v);if(!isValid)throw new Error("Invalid integer: "+v);var r=[],max=v.length,l=LOG_BASE,min=max-l;while(max>0){r.push(+v.slice(min,max));min-=l;if(min<0)min=0;max-=l}trim(r);return new BigInteger(r,sign)}function parseNumberValue(v){if(isPrecise(v)){if(v!==truncate(v))throw new Error(v+" is not an integer.");return new SmallInteger(v)}return parseStringValue(v.toString())}function parseValue(v){if(typeof v==="number"){return parseNumberValue(v)}if(typeof v==="string"){return parseStringValue(v)}return v}for(var i=0;i<1e3;i++){Integer[i]=new SmallInteger(i);if(i>0)Integer[-i]=new SmallInteger(-i)}Integer.one=Integer[1];Integer.zero=Integer[0];Integer.minusOne=Integer[-1];Integer.max=max;Integer.min=min;Integer.gcd=gcd;Integer.lcm=lcm;Integer.isInstance=function(x){return x instanceof BigInteger||x instanceof SmallInteger};Integer.randBetween=randBetween;Integer.fromArray=function(digits,base,isNegative){return parseBaseFromArray(digits.map(parseValue),parseValue(base||10),isNegative)};return Integer}();if(typeof module!=="undefined"&&module.hasOwnProperty("exports")){module.exports=bigInt}if(typeof define==="function"&&define.amd){define("big-integer",[],function(){return bigInt})}; bigInt` diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go new file mode 100644 index 0000000..5290d4f --- /dev/null +++ b/eth/tracers/js/goja.go @@ -0,0 +1,1025 @@ +// 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 js + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "slices" + + "github.com/dop251/goja" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/internal" + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + jsassets "github.com/ethereum/go-ethereum/eth/tracers/js/internal/tracers" +) + +var assetTracers = make(map[string]string) + +// init retrieves the JavaScript transaction tracers included in go-ethereum. +func init() { + var err error + assetTracers, err = jsassets.Load() + if err != nil { + panic(err) + } + type ctorFn = func(*tracers.Context, json.RawMessage) (*tracers.Tracer, error) + lookup := func(code string) ctorFn { + return func(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + return newJsTracer(code, ctx, cfg) + } + } + for name, code := range assetTracers { + tracers.DefaultDirectory.Register(name, lookup(code), true) + } + tracers.DefaultDirectory.RegisterJSEval(newJsTracer) +} + +// bigIntProgram is compiled once and the exported function mostly invoked to convert +// hex strings into big ints. +var bigIntProgram = goja.MustCompile("bigInt", bigIntegerJS, false) + +type toBigFn = func(vm *goja.Runtime, val string) (goja.Value, error) +type toBufFn = func(vm *goja.Runtime, val []byte) (goja.Value, error) +type fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error) + +func toBuf(vm *goja.Runtime, bufType goja.Value, val []byte) (goja.Value, error) { + // bufType is usually Uint8Array. This is equivalent to `new Uint8Array(val)` in JS. + return vm.New(bufType, vm.ToValue(vm.NewArrayBuffer(val))) +} + +func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString bool) ([]byte, error) { + obj := buf.ToObject(vm) + switch obj.ClassName() { + case "String": + if !allowString { + break + } + return common.FromHex(obj.String()), nil + + case "Array": + var b []byte + if err := vm.ExportTo(buf, &b); err != nil { + return nil, err + } + return b, nil + + case "Object": + if !obj.Get("constructor").SameAs(bufType) { + break + } + b := obj.Export().([]byte) + return b, nil + } + return nil, errors.New("invalid buffer type") +} + +// jsTracer is an implementation of the Tracer interface which evaluates +// JS functions on the relevant EVM hooks. It uses Goja as its JS engine. +type jsTracer struct { + vm *goja.Runtime + env *tracing.VMContext + toBig toBigFn // Converts a hex string into a JS bigint + toBuf toBufFn // Converts a []byte into a JS buffer + fromBuf fromBufFn // Converts an array, hex string or Uint8Array to a []byte + ctx map[string]goja.Value // KV-bag passed to JS in `result` + activePrecompiles []common.Address // List of active precompiles at current block + traceStep bool // True if tracer object exposes a `step()` method + traceFrame bool // True if tracer object exposes the `enter()` and `exit()` methods + err error // Any error that should stop tracing + obj *goja.Object // Trace object + + // Methods exposed by tracer + result goja.Callable + fault goja.Callable + step goja.Callable + enter goja.Callable + exit goja.Callable + + // Underlying structs being passed into JS + log *steplog + frame *callframe + frameResult *callframeResult + + // Goja-wrapping of types prepared for JS consumption + logValue goja.Value + dbValue goja.Value + frameValue goja.Value + frameResultValue goja.Value +} + +// newJsTracer instantiates a new JS tracer instance. code is a +// Javascript snippet which evaluates to an expression returning +// an object with certain methods: +// +// The methods `result` and `fault` are required to be present. +// The methods `step`, `enter`, and `exit` are optional, but note that +// `enter` and `exit` always go together. +func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + vm := goja.New() + // By default field names are exported to JS as is, i.e. capitalized. + vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) + t := &jsTracer{ + vm: vm, + ctx: make(map[string]goja.Value), + } + + t.setTypeConverters() + t.setBuiltinFunctions() + + if ctx == nil { + ctx = new(tracers.Context) + } + if ctx.BlockHash != (common.Hash{}) { + 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) + txHash, err := t.toBuf(vm, ctx.TxHash.Bytes()) + if err != nil { + return nil, err + } + t.ctx["txHash"] = txHash + } + } + + ret, err := vm.RunString("(" + code + ")") + if err != nil { + return nil, err + } + // Check tracer's interface for required and optional methods. + obj := ret.ToObject(vm) + result, ok := goja.AssertFunction(obj.Get("result")) + if !ok { + return nil, errors.New("trace object must expose a function result()") + } + fault, ok := goja.AssertFunction(obj.Get("fault")) + if !ok { + return nil, errors.New("trace object must expose a function fault()") + } + step, ok := goja.AssertFunction(obj.Get("step")) + t.traceStep = ok + enter, hasEnter := goja.AssertFunction(obj.Get("enter")) + exit, hasExit := goja.AssertFunction(obj.Get("exit")) + if hasEnter != hasExit { + return nil, errors.New("trace object must expose either both or none of enter() and exit()") + } + t.traceFrame = hasEnter + t.obj = obj + t.step = step + t.enter = enter + t.exit = exit + t.result = result + t.fault = fault + + // Pass in config + if setup, ok := goja.AssertFunction(obj.Get("setup")); ok { + cfgStr := "{}" + if cfg != nil { + cfgStr = string(cfg) + } + if _, err := setup(obj, vm.ToValue(cfgStr)); err != nil { + return nil, err + } + } + // Setup objects carrying data to JS. These are created once and re-used. + t.log = &steplog{ + vm: vm, + op: &opObj{vm: vm}, + memory: &memoryObj{vm: vm, toBig: t.toBig, toBuf: t.toBuf}, + stack: &stackObj{vm: vm, toBig: t.toBig}, + contract: &contractObj{vm: vm, toBig: t.toBig, toBuf: t.toBuf}, + } + t.frame = &callframe{vm: vm, toBig: t.toBig, toBuf: t.toBuf} + t.frameResult = &callframeResult{vm: vm, toBuf: t.toBuf} + t.frameValue = t.frame.setupObject() + t.frameResultValue = t.frameResult.setupObject() + t.logValue = t.log.setupObject() + + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +// OnTxStart implements the Tracer interface and is invoked at the beginning of +// transaction processing. +func (t *jsTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + t.env = env + // Need statedb access for db object + db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf} + t.dbValue = db.setupObject() + // Update list of precompiles based on current block + rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) + t.activePrecompiles = vm.ActivePrecompiles(rules) + t.ctx["block"] = t.vm.ToValue(t.env.BlockNumber.Uint64()) + t.ctx["gas"] = t.vm.ToValue(tx.Gas()) + gasPriceBig, err := t.toBig(t.vm, env.GasPrice.String()) + if err != nil { + t.err = err + return + } + t.ctx["gasPrice"] = gasPriceBig +} + +// OnTxEnd implements the Tracer interface and is invoked at the end of +// transaction processing. +func (t *jsTracer) OnTxEnd(receipt *types.Receipt, err error) { + if t.err != nil { + return + } + if err != nil { + // Don't override vm error + if _, ok := t.ctx["error"]; !ok { + t.ctx["error"] = t.vm.ToValue(err.Error()) + } + return + } + t.ctx["gasUsed"] = t.vm.ToValue(receipt.GasUsed) +} + +// onStart implements the Tracer interface to initialize the tracing operation. +func (t *jsTracer) onStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + if t.err != nil { + return + } + if create { + t.ctx["type"] = t.vm.ToValue("CREATE") + } else { + t.ctx["type"] = t.vm.ToValue("CALL") + } + fromVal, err := t.toBuf(t.vm, from.Bytes()) + if err != nil { + t.err = err + return + } + t.ctx["from"] = fromVal + toVal, err := t.toBuf(t.vm, to.Bytes()) + if err != nil { + t.err = err + return + } + t.ctx["to"] = toVal + inputVal, err := t.toBuf(t.vm, input) + if err != nil { + t.err = err + return + } + t.ctx["input"] = inputVal + valueBig, err := t.toBig(t.vm, value.String()) + if err != nil { + t.err = err + return + } + t.ctx["value"] = valueBig +} + +// OnOpcode implements the Tracer interface to trace a single step of VM execution. +func (t *jsTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + if !t.traceStep { + return + } + if t.err != nil { + return + } + + log := t.log + log.op.op = vm.OpCode(op) + log.memory.memory = scope.MemoryData() + log.stack.stack = scope.StackData() + log.contract.scope = scope + log.pc = pc + log.gas = gas + log.cost = cost + log.refund = t.env.StateDB.GetRefund() + log.depth = depth + log.err = err + if _, err := t.step(t.obj, t.logValue, t.dbValue); err != nil { + t.onError("step", err) + } +} + +// OnFault implements the Tracer interface to trace an execution fault +func (t *jsTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { + if t.err != nil { + return + } + // Other log fields have been already set as part of the last OnOpcode. + t.log.err = err + if _, err := t.fault(t.obj, t.logValue, t.dbValue); err != nil { + t.onError("fault", err) + } +} + +// onEnd is called after the call finishes to finalize the tracing. +func (t *jsTracer) onEnd(output []byte, gasUsed uint64, err error, reverted bool) { + if t.err != nil { + return + } + 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 +} + +// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *jsTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if t.err != nil { + return + } + if depth == 0 { + t.onStart(from, to, vm.OpCode(typ) == vm.CREATE, input, gas, value) + return + } + if !t.traceFrame { + return + } + + t.frame.typ = vm.OpCode(typ).String() + t.frame.from = from + t.frame.to = to + t.frame.input = common.CopyBytes(input) + t.frame.gas = uint(gas) + t.frame.value = nil + if value != nil { + t.frame.value = new(big.Int).SetBytes(value.Bytes()) + } + + if _, err := t.enter(t.obj, t.frameValue); err != nil { + t.onError("enter", err) + } +} + +// OnExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *jsTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if t.err != nil { + return + } + if depth == 0 { + t.onEnd(output, gasUsed, err, reverted) + return + } + if !t.traceFrame { + return + } + + t.frameResult.gasUsed = uint(gasUsed) + t.frameResult.output = common.CopyBytes(output) + t.frameResult.err = err + + if _, err := t.exit(t.obj, t.frameResultValue); err != nil { + t.onError("exit", err) + } +} + +// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error +func (t *jsTracer) GetResult() (json.RawMessage, error) { + if t.err != nil { + return nil, t.err + } + ctx := t.vm.ToValue(t.ctx) + res, err := t.result(t.obj, ctx, t.dbValue) + if err != nil { + return nil, wrapError("result", err) + } + encoded, err := json.Marshal(res) + if err != nil { + return nil, err + } + return encoded, t.err +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *jsTracer) Stop(err error) { + t.vm.Interrupt(err) +} + +// onError is called anytime the running JS code is interrupted +// and returns an error. It in turn pings the EVM to cancel its +// execution. +func (t *jsTracer) onError(context string, err error) { + t.err = wrapError(context, err) +} + +func wrapError(context string, err error) error { + return fmt.Errorf("%v in server-side tracer function '%v'", err, context) +} + +// setBuiltinFunctions injects Go functions which are available to tracers into the environment. +// It depends on type converters having been set up. +func (t *jsTracer) setBuiltinFunctions() { + vm := t.vm + // TODO: load console from goja-nodejs + vm.Set("toHex", func(v goja.Value) string { + b, err := t.fromBuf(vm, v, false) + if err != nil { + vm.Interrupt(err) + return "" + } + return hexutil.Encode(b) + }) + vm.Set("toWord", func(v goja.Value) goja.Value { + // TODO: add test with []byte len < 32 or > 32 + b, err := t.fromBuf(vm, v, true) + if err != nil { + vm.Interrupt(err) + return nil + } + b = common.BytesToHash(b).Bytes() + res, err := t.toBuf(vm, b) + if err != nil { + vm.Interrupt(err) + return nil + } + return res + }) + vm.Set("toAddress", func(v goja.Value) goja.Value { + a, err := t.fromBuf(vm, v, true) + if err != nil { + vm.Interrupt(err) + return nil + } + a = common.BytesToAddress(a).Bytes() + res, err := t.toBuf(vm, a) + if err != nil { + vm.Interrupt(err) + return nil + } + return res + }) + vm.Set("toContract", func(from goja.Value, nonce uint) goja.Value { + a, err := t.fromBuf(vm, from, true) + if err != nil { + vm.Interrupt(err) + return nil + } + addr := common.BytesToAddress(a) + b := crypto.CreateAddress(addr, uint64(nonce)).Bytes() + res, err := t.toBuf(vm, b) + if err != nil { + vm.Interrupt(err) + return nil + } + return res + }) + vm.Set("toContract2", func(from goja.Value, salt string, initcode goja.Value) goja.Value { + a, err := t.fromBuf(vm, from, true) + if err != nil { + vm.Interrupt(err) + return nil + } + addr := common.BytesToAddress(a) + code, err := t.fromBuf(vm, initcode, true) + if err != nil { + vm.Interrupt(err) + return nil + } + code = common.CopyBytes(code) + codeHash := crypto.Keccak256(code) + b := crypto.CreateAddress2(addr, common.HexToHash(salt), codeHash).Bytes() + res, err := t.toBuf(vm, b) + if err != nil { + vm.Interrupt(err) + return nil + } + return res + }) + vm.Set("isPrecompiled", func(v goja.Value) bool { + a, err := t.fromBuf(vm, v, true) + if err != nil { + vm.Interrupt(err) + return false + } + return slices.Contains(t.activePrecompiles, common.BytesToAddress(a)) + }) + 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 > 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 + } + res, err := t.toBuf(vm, b[start:end]) + if err != nil { + vm.Interrupt(err) + return nil + } + return res + }) +} + +// setTypeConverters sets up utilities for converting Go types into those +// suitable for JS consumption. +func (t *jsTracer) setTypeConverters() error { + // Inject bigint logic. + // TODO: To be replaced after goja adds support for native JS bigint. + toBigCode, err := t.vm.RunProgram(bigIntProgram) + if err != nil { + return err + } + // Used to create JS bigint objects from go. + toBigFn, ok := goja.AssertFunction(toBigCode) + if !ok { + return errors.New("failed to bind bigInt func") + } + toBigWrapper := func(vm *goja.Runtime, val string) (goja.Value, error) { + return toBigFn(goja.Undefined(), vm.ToValue(val)) + } + t.toBig = toBigWrapper + // NOTE: We need this workaround to create JS buffers because + // goja doesn't at the moment expose constructors for typed arrays. + // + // Cache uint8ArrayType once to be used every time for less overhead. + uint8ArrayType := t.vm.Get("Uint8Array") + toBufWrapper := func(vm *goja.Runtime, val []byte) (goja.Value, error) { + return toBuf(vm, uint8ArrayType, val) + } + t.toBuf = toBufWrapper + fromBufWrapper := func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error) { + return fromBuf(vm, uint8ArrayType, buf, allowString) + } + t.fromBuf = fromBufWrapper + return nil +} + +type opObj struct { + vm *goja.Runtime + op vm.OpCode +} + +func (o *opObj) ToNumber() int { + return int(o.op) +} + +func (o *opObj) ToString() string { + return o.op.String() +} + +func (o *opObj) IsPush() bool { + return o.op.IsPush() +} + +func (o *opObj) setupObject() *goja.Object { + obj := o.vm.NewObject() + obj.Set("toNumber", o.vm.ToValue(o.ToNumber)) + obj.Set("toString", o.vm.ToValue(o.ToString)) + obj.Set("isPush", o.vm.ToValue(o.IsPush)) + return obj +} + +type memoryObj struct { + memory []byte + vm *goja.Runtime + toBig toBigFn + toBuf toBufFn +} + +func (mo *memoryObj) Slice(begin, end int64) goja.Value { + b, err := mo.slice(begin, end) + if err != nil { + mo.vm.Interrupt(err) + return nil + } + res, err := mo.toBuf(mo.vm, b) + if err != nil { + mo.vm.Interrupt(err) + return nil + } + return res +} + +// slice returns the requested range of memory as a byte slice. +func (mo *memoryObj) slice(begin, end int64) ([]byte, error) { + if end == begin { + return []byte{}, nil + } + if end < begin || begin < 0 { + return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end) + } + slice, err := internal.GetMemoryCopyPadded(mo.memory, begin, end-begin) + if err != nil { + return nil, err + } + return slice, nil +} + +func (mo *memoryObj) GetUint(addr int64) goja.Value { + value, err := mo.getUint(addr) + if err != nil { + mo.vm.Interrupt(err) + return nil + } + res, err := mo.toBig(mo.vm, value.String()) + if err != nil { + mo.vm.Interrupt(err) + return nil + } + return res +} + +// getUint returns the 32 bytes at the specified address interpreted as a uint. +func (mo *memoryObj) getUint(addr int64) (*big.Int, error) { + if len(mo.memory) < int(addr)+32 || addr < 0 { + return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", len(mo.memory), addr, 32) + } + return new(big.Int).SetBytes(internal.MemoryPtr(mo.memory, addr, 32)), nil +} + +func (mo *memoryObj) Length() int { + return len(mo.memory) +} + +func (mo *memoryObj) setupObject() *goja.Object { + o := mo.vm.NewObject() + o.Set("slice", mo.vm.ToValue(mo.Slice)) + o.Set("getUint", mo.vm.ToValue(mo.GetUint)) + o.Set("length", mo.vm.ToValue(mo.Length)) + return o +} + +type stackObj struct { + stack []uint256.Int + vm *goja.Runtime + toBig toBigFn +} + +func (s *stackObj) Peek(idx int) goja.Value { + value, err := s.peek(idx) + if err != nil { + s.vm.Interrupt(err) + return nil + } + res, err := s.toBig(s.vm, value.String()) + if err != nil { + s.vm.Interrupt(err) + return nil + } + return res +} + +// peek returns the nth-from-the-top element of the stack. +func (s *stackObj) peek(idx int) (*big.Int, error) { + if len(s.stack) <= idx || idx < 0 { + return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack), idx) + } + return internal.StackBack(s.stack, idx).ToBig(), nil +} + +func (s *stackObj) Length() int { + return len(s.stack) +} + +func (s *stackObj) setupObject() *goja.Object { + o := s.vm.NewObject() + o.Set("peek", s.vm.ToValue(s.Peek)) + o.Set("length", s.vm.ToValue(s.Length)) + return o +} + +type dbObj struct { + db tracing.StateDB + vm *goja.Runtime + toBig toBigFn + toBuf toBufFn + fromBuf fromBufFn +} + +func (do *dbObj) GetBalance(addrSlice goja.Value) goja.Value { + a, err := do.fromBuf(do.vm, addrSlice, false) + if err != nil { + do.vm.Interrupt(err) + return nil + } + addr := common.BytesToAddress(a) + value := do.db.GetBalance(addr) + res, err := do.toBig(do.vm, value.String()) + if err != nil { + do.vm.Interrupt(err) + return nil + } + return res +} + +func (do *dbObj) GetNonce(addrSlice goja.Value) uint64 { + a, err := do.fromBuf(do.vm, addrSlice, false) + if err != nil { + do.vm.Interrupt(err) + return 0 + } + addr := common.BytesToAddress(a) + return do.db.GetNonce(addr) +} + +func (do *dbObj) GetCode(addrSlice goja.Value) goja.Value { + a, err := do.fromBuf(do.vm, addrSlice, false) + if err != nil { + do.vm.Interrupt(err) + return nil + } + addr := common.BytesToAddress(a) + code := do.db.GetCode(addr) + res, err := do.toBuf(do.vm, code) + if err != nil { + do.vm.Interrupt(err) + return nil + } + return res +} + +func (do *dbObj) GetState(addrSlice goja.Value, hashSlice goja.Value) goja.Value { + a, err := do.fromBuf(do.vm, addrSlice, false) + if err != nil { + do.vm.Interrupt(err) + return nil + } + addr := common.BytesToAddress(a) + h, err := do.fromBuf(do.vm, hashSlice, false) + if err != nil { + do.vm.Interrupt(err) + return nil + } + hash := common.BytesToHash(h) + state := do.db.GetState(addr, hash).Bytes() + res, err := do.toBuf(do.vm, state) + if err != nil { + do.vm.Interrupt(err) + return nil + } + return res +} + +func (do *dbObj) Exists(addrSlice goja.Value) bool { + a, err := do.fromBuf(do.vm, addrSlice, false) + if err != nil { + do.vm.Interrupt(err) + return false + } + addr := common.BytesToAddress(a) + return do.db.Exist(addr) +} + +func (do *dbObj) setupObject() *goja.Object { + o := do.vm.NewObject() + o.Set("getBalance", do.vm.ToValue(do.GetBalance)) + o.Set("getNonce", do.vm.ToValue(do.GetNonce)) + o.Set("getCode", do.vm.ToValue(do.GetCode)) + o.Set("getState", do.vm.ToValue(do.GetState)) + o.Set("exists", do.vm.ToValue(do.Exists)) + return o +} + +type contractObj struct { + scope tracing.OpContext + vm *goja.Runtime + toBig toBigFn + toBuf toBufFn +} + +func (co *contractObj) GetCaller() goja.Value { + caller := co.scope.Caller().Bytes() + res, err := co.toBuf(co.vm, caller) + if err != nil { + co.vm.Interrupt(err) + return nil + } + return res +} + +func (co *contractObj) GetAddress() goja.Value { + addr := co.scope.Address().Bytes() + res, err := co.toBuf(co.vm, addr) + if err != nil { + co.vm.Interrupt(err) + return nil + } + return res +} + +func (co *contractObj) GetValue() goja.Value { + value := co.scope.CallValue() + res, err := co.toBig(co.vm, value.String()) + if err != nil { + co.vm.Interrupt(err) + return nil + } + return res +} + +func (co *contractObj) GetInput() goja.Value { + input := common.CopyBytes(co.scope.CallInput()) + res, err := co.toBuf(co.vm, input) + if err != nil { + co.vm.Interrupt(err) + return nil + } + return res +} + +func (co *contractObj) setupObject() *goja.Object { + o := co.vm.NewObject() + o.Set("getCaller", co.vm.ToValue(co.GetCaller)) + o.Set("getAddress", co.vm.ToValue(co.GetAddress)) + o.Set("getValue", co.vm.ToValue(co.GetValue)) + o.Set("getInput", co.vm.ToValue(co.GetInput)) + return o +} + +type callframe struct { + vm *goja.Runtime + toBig toBigFn + toBuf toBufFn + + typ string + from common.Address + to common.Address + input []byte + gas uint + value *big.Int +} + +func (f *callframe) GetType() string { + return f.typ +} + +func (f *callframe) GetFrom() goja.Value { + from := f.from.Bytes() + res, err := f.toBuf(f.vm, from) + if err != nil { + f.vm.Interrupt(err) + return nil + } + return res +} + +func (f *callframe) GetTo() goja.Value { + to := f.to.Bytes() + res, err := f.toBuf(f.vm, to) + if err != nil { + f.vm.Interrupt(err) + return nil + } + return res +} + +func (f *callframe) GetInput() goja.Value { + input := f.input + res, err := f.toBuf(f.vm, input) + if err != nil { + f.vm.Interrupt(err) + return nil + } + return res +} + +func (f *callframe) GetGas() uint { + return f.gas +} + +func (f *callframe) GetValue() goja.Value { + if f.value == nil { + return goja.Undefined() + } + res, err := f.toBig(f.vm, f.value.String()) + if err != nil { + f.vm.Interrupt(err) + return nil + } + return res +} + +func (f *callframe) setupObject() *goja.Object { + o := f.vm.NewObject() + o.Set("getType", f.vm.ToValue(f.GetType)) + o.Set("getFrom", f.vm.ToValue(f.GetFrom)) + o.Set("getTo", f.vm.ToValue(f.GetTo)) + o.Set("getInput", f.vm.ToValue(f.GetInput)) + o.Set("getGas", f.vm.ToValue(f.GetGas)) + o.Set("getValue", f.vm.ToValue(f.GetValue)) + return o +} + +type callframeResult struct { + vm *goja.Runtime + toBuf toBufFn + + gasUsed uint + output []byte + err error +} + +func (r *callframeResult) GetGasUsed() uint { + return r.gasUsed +} + +func (r *callframeResult) GetOutput() goja.Value { + res, err := r.toBuf(r.vm, r.output) + if err != nil { + r.vm.Interrupt(err) + return nil + } + return res +} + +func (r *callframeResult) GetError() goja.Value { + if r.err != nil { + return r.vm.ToValue(r.err.Error()) + } + return goja.Undefined() +} + +func (r *callframeResult) setupObject() *goja.Object { + o := r.vm.NewObject() + o.Set("getGasUsed", r.vm.ToValue(r.GetGasUsed)) + o.Set("getOutput", r.vm.ToValue(r.GetOutput)) + o.Set("getError", r.vm.ToValue(r.GetError)) + return o +} + +type steplog struct { + vm *goja.Runtime + + op *opObj + memory *memoryObj + stack *stackObj + contract *contractObj + + pc uint64 + gas uint64 + cost uint64 + depth int + refund uint64 + err error +} + +func (l *steplog) GetPC() uint64 { return l.pc } +func (l *steplog) GetGas() uint64 { return l.gas } +func (l *steplog) GetCost() uint64 { return l.cost } +func (l *steplog) GetDepth() int { return l.depth } +func (l *steplog) GetRefund() uint64 { return l.refund } + +func (l *steplog) GetError() goja.Value { + if l.err != nil { + return l.vm.ToValue(l.err.Error()) + } + return goja.Undefined() +} + +func (l *steplog) setupObject() *goja.Object { + o := l.vm.NewObject() + // Setup basic fields. + o.Set("getPC", l.vm.ToValue(l.GetPC)) + o.Set("getGas", l.vm.ToValue(l.GetGas)) + o.Set("getCost", l.vm.ToValue(l.GetCost)) + o.Set("getDepth", l.vm.ToValue(l.GetDepth)) + o.Set("getRefund", l.vm.ToValue(l.GetRefund)) + o.Set("getError", l.vm.ToValue(l.GetError)) + // Setup nested objects. + o.Set("op", l.op.setupObject()) + o.Set("stack", l.stack.setupObject()) + o.Set("memory", l.memory.setupObject()) + o.Set("contract", l.contract.setupObject()) + return o +} diff --git a/eth/tracers/js/internal/tracers/4byte_tracer_legacy.js b/eth/tracers/js/internal/tracers/4byte_tracer_legacy.js new file mode 100644 index 0000000..e4714b8 --- /dev/null +++ b/eth/tracers/js/internal/tracers/4byte_tracer_legacy.js @@ -0,0 +1,86 @@ +// 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 . + +// 4byteTracer searches for 4byte-identifiers, and collects them for post-processing. +// It collects the methods identifiers along with the size of the supplied data, so +// a reversed signature can be matched against the size of the data. +// +// Example: +// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"}) +// { +// 0x27dc297e-128: 1, +// 0x38cc4831-0: 2, +// 0x524f3889-96: 1, +// 0xadf59f99-288: 1, +// 0xc281d19e-0: 1 +// } +{ + // ids aggregates the 4byte ids found. + ids : {}, + + // callType returns 'false' for non-calls, or the peek-index for the first param + // after 'value', i.e. meminstart. + callType: function(opstr){ + switch(opstr){ + case "CALL": case "CALLCODE": + // gas, addr, val, memin, meminsz, memout, memoutsz + return 3; // stack ptr to memin + + case "DELEGATECALL": case "STATICCALL": + // gas, addr, memin, meminsz, memout, memoutsz + return 2; // stack ptr to memin + } + return false; + }, + + // store save the given identifier and datasize. + store: function(id, size){ + var key = "" + toHex(id) + "-" + size; + this.ids[key] = this.ids[key] + 1 || 1; + }, + + // step is invoked for every opcode that the VM executes. + step: function(log, db) { + // Skip any opcodes that are not internal calls + var ct = this.callType(log.op.toString()); + if (!ct) { + return; + } + // Skip any pre-compile invocations, those are just fancy opcodes + if (isPrecompiled(toAddress(log.stack.peek(1).toString(16)))) { + return; + } + // Gather internal call details + var inSz = log.stack.peek(ct + 1).valueOf(); + if (inSz >= 4) { + var inOff = log.stack.peek(ct).valueOf(); + this.store(log.memory.slice(inOff, inOff + 4), inSz-4); + } + }, + + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) { }, + + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function(ctx) { + // Save the outer calldata also + if (ctx.input.length >= 4) { + this.store(slice(ctx.input, 0, 4), ctx.input.length-4) + } + return this.ids; + }, +} diff --git a/eth/tracers/js/internal/tracers/bigram_tracer.js b/eth/tracers/js/internal/tracers/bigram_tracer.js new file mode 100644 index 0000000..421c360 --- /dev/null +++ b/eth/tracers/js/internal/tracers/bigram_tracer.js @@ -0,0 +1,47 @@ +// 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 . + +{ + // hist is the counters of opcode bigrams + hist: {}, + // lastOp is last operation + lastOp: '', + // execution depth of last op + lastDepth: 0, + // step is invoked for every opcode that the VM executes. + step: function(log, db) { + var op = log.op.toString(); + var depth = log.getDepth(); + if (depth == this.lastDepth){ + var key = this.lastOp+'-'+op; + if (this.hist[key]){ + this.hist[key]++; + } + else { + this.hist[key] = 1; + } + } + this.lastOp = op; + this.lastDepth = depth; + }, + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) {}, + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function(ctx) { + return this.hist; + }, +} diff --git a/eth/tracers/js/internal/tracers/call_tracer_legacy.js b/eth/tracers/js/internal/tracers/call_tracer_legacy.js new file mode 100644 index 0000000..0760bb1 --- /dev/null +++ b/eth/tracers/js/internal/tracers/call_tracer_legacy.js @@ -0,0 +1,250 @@ +// 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 . + +// callTracer is a full blown transaction tracer that extracts and reports all +// the internal calls made by a transaction, along with any useful information. +{ + // callstack is the current recursive call stack of the EVM execution. + callstack: [{}], + + // descended tracks whether we've just descended from an outer transaction into + // an inner call. + descended: false, + + // step is invoked for every opcode that the VM executes. + step: function(log, db) { + // Capture any errors immediately + var error = log.getError(); + if (error !== undefined) { + this.fault(log, db); + return; + } + // We only care about system opcodes, faster if we pre-check once + var syscall = (log.op.toNumber() & 0xf0) == 0xf0; + if (syscall) { + var op = log.op.toString(); + } + // If a new contract is being created, add to the call stack + if (syscall && (op == 'CREATE' || op == "CREATE2")) { + var inOff = log.stack.peek(1).valueOf(); + var inEnd = inOff + log.stack.peek(2).valueOf(); + + // Assemble the internal call report and store for completion + var call = { + type: op, + from: toHex(log.contract.getAddress()), + input: toHex(log.memory.slice(inOff, inEnd)), + gasIn: log.getGas(), + gasCost: log.getCost(), + value: '0x' + log.stack.peek(0).toString(16) + }; + this.callstack.push(call); + this.descended = true + return; + } + // If a contract is being self destructed, gather that as a subcall too + if (syscall && op == 'SELFDESTRUCT') { + var left = this.callstack.length; + if (this.callstack[left-1].calls === undefined) { + this.callstack[left-1].calls = []; + } + this.callstack[left-1].calls.push({ + type: op, + from: toHex(log.contract.getAddress()), + to: toHex(toAddress(log.stack.peek(0).toString(16))), + gasIn: log.getGas(), + gasCost: log.getCost(), + value: '0x' + db.getBalance(log.contract.getAddress()).toString(16) + }); + return + } + // If a new method invocation is being done, add to the call stack + if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) { + // Skip any pre-compile invocations, those are just fancy opcodes + var to = toAddress(log.stack.peek(1).toString(16)); + if (isPrecompiled(to)) { + return + } + var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1); + + var inOff = log.stack.peek(2 + off).valueOf(); + var inEnd = inOff + log.stack.peek(3 + off).valueOf(); + + // Assemble the internal call report and store for completion + var call = { + type: op, + from: toHex(log.contract.getAddress()), + to: toHex(to), + input: toHex(log.memory.slice(inOff, inEnd)), + gasIn: log.getGas(), + gasCost: log.getCost(), + outOff: log.stack.peek(4 + off).valueOf(), + outLen: log.stack.peek(5 + off).valueOf() + }; + if (op != 'DELEGATECALL' && op != 'STATICCALL') { + call.value = '0x' + log.stack.peek(2).toString(16); + } + this.callstack.push(call); + this.descended = true + return; + } + // If we've just descended into an inner call, retrieve it's true allowance. We + // need to extract if from within the call as there may be funky gas dynamics + // with regard to requested and actually given gas (2300 stipend, 63/64 rule). + if (this.descended) { + if (log.getDepth() >= this.callstack.length) { + this.callstack[this.callstack.length - 1].gas = log.getGas(); + } else { + // TODO(karalabe): The call was made to a plain account. We currently don't + // have access to the true gas amount inside the call and so any amount will + // mostly be wrong since it depends on a lot of input args. Skip gas for now. + } + this.descended = false; + } + // If an existing call is returning, pop off the call stack + if (syscall && op == 'REVERT') { + this.callstack[this.callstack.length - 1].error = "execution reverted"; + return; + } + if (log.getDepth() == this.callstack.length - 1) { + // Pop off the last call and get the execution results + var call = this.callstack.pop(); + + if (call.type == 'CREATE' || call.type == "CREATE2") { + // If the call was a CREATE, retrieve the contract address and output code + call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16); + delete call.gasIn; delete call.gasCost; + + var ret = log.stack.peek(0); + if (!ret.equals(0)) { + call.to = toHex(toAddress(ret.toString(16))); + call.output = toHex(db.getCode(toAddress(ret.toString(16)))); + } else if (call.error === undefined) { + call.error = "internal failure"; // TODO(karalabe): surface these faults somehow + } + } else { + // If the call was a contract call, retrieve the gas usage and output + if (call.gas !== undefined) { + call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16); + } + var ret = log.stack.peek(0); + if (!ret.equals(0)) { + call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen)); + } else if (call.error === undefined) { + call.error = "internal failure"; // TODO(karalabe): surface these faults somehow + } + delete call.gasIn; delete call.gasCost; + delete call.outOff; delete call.outLen; + } + if (call.gas !== undefined) { + call.gas = '0x' + bigInt(call.gas).toString(16); + } + // Inject the call into the previous one + var left = this.callstack.length; + if (this.callstack[left-1].calls === undefined) { + this.callstack[left-1].calls = []; + } + this.callstack[left-1].calls.push(call); + } + }, + + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) { + // If the topmost call already reverted, don't handle the additional fault again + if (this.callstack[this.callstack.length - 1].error !== undefined) { + return; + } + // Pop off the just failed call + var call = this.callstack.pop(); + call.error = log.getError(); + + // Consume all available gas and clean any leftovers + if (call.gas !== undefined) { + call.gas = '0x' + bigInt(call.gas).toString(16); + call.gasUsed = call.gas + } + delete call.gasIn; delete call.gasCost; + delete call.outOff; delete call.outLen; + + // Flatten the failed call into its parent + var left = this.callstack.length; + if (left > 0) { + if (this.callstack[left-1].calls === undefined) { + this.callstack[left-1].calls = []; + } + this.callstack[left-1].calls.push(call); + return; + } + // Last call failed too, leave it in the stack + this.callstack.push(call); + }, + + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function(ctx, db) { + var result = { + type: ctx.type, + from: toHex(ctx.from), + to: toHex(ctx.to), + value: '0x' + ctx.value.toString(16), + gas: '0x' + bigInt(ctx.gas).toString(16), + gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16), + input: toHex(ctx.input), + output: toHex(ctx.output), + }; + if (this.callstack[0].calls !== undefined) { + result.calls = this.callstack[0].calls; + } + if (this.callstack[0].error !== undefined) { + result.error = this.callstack[0].error; + } else if (ctx.error !== undefined) { + result.error = ctx.error; + } + if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) { + delete result.output; + } + return this.finalize(result); + }, + + // finalize recreates a call object using the final desired field order for json + // serialization. This is a nicety feature to pass meaningfully ordered results + // to users who don't interpret it, just display it. + finalize: function(call) { + var sorted = { + type: call.type, + from: call.from, + to: call.to, + value: call.value, + gas: call.gas, + gasUsed: call.gasUsed, + input: call.input, + output: call.output, + error: call.error, + calls: call.calls, + } + for (var key in sorted) { + if (sorted[key] === undefined) { + delete sorted[key]; + } + } + if (sorted.calls !== undefined) { + for (var i=0; i. + +// evmdisTracer returns sufficient information from a trace to perform evmdis-style +// disassembly. +{ + stack: [{ops: []}], + + npushes: {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 32: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 0, 56: 1, 57: 0, 58: 1, 59: 1, 60: 0, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 80: 0, 81: 1, 82: 0, 83: 0, 84: 1, 85: 0, 86: 0, 87: 0, 88: 1, 89: 1, 90: 1, 91: 0, 96: 1, 97: 1, 98: 1, 99: 1, 100: 1, 101: 1, 102: 1, 103: 1, 104: 1, 105: 1, 106: 1, 107: 1, 108: 1, 109: 1, 110: 1, 111: 1, 112: 1, 113: 1, 114: 1, 115: 1, 116: 1, 117: 1, 118: 1, 119: 1, 120: 1, 121: 1, 122: 1, 123: 1, 124: 1, 125: 1, 126: 1, 127: 1, 128: 2, 129: 3, 130: 4, 131: 5, 132: 6, 133: 7, 134: 8, 135: 9, 136: 10, 137: 11, 138: 12, 139: 13, 140: 14, 141: 15, 142: 16, 143: 17, 144: 2, 145: 3, 146: 4, 147: 5, 148: 6, 149: 7, 150: 8, 151: 9, 152: 10, 153: 11, 154: 12, 155: 13, 156: 14, 157: 15, 158: 16, 159: 17, 160: 0, 161: 0, 162: 0, 163: 0, 164: 0, 240: 1, 241: 1, 242: 1, 243: 0, 244: 0, 255: 0}, + + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function() { return this.stack[0].ops; }, + + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) { }, + + // step is invoked for every opcode that the VM executes. + step: function(log, db) { + var frame = this.stack[this.stack.length - 1]; + + var error = log.getError(); + if (error) { + frame["error"] = error; + } else if (log.getDepth() == this.stack.length) { + opinfo = { + op: log.op.toNumber(), + depth : log.getDepth(), + result: [], + }; + if (frame.ops.length > 0) { + var prevop = frame.ops[frame.ops.length - 1]; + for(var i = 0; i < this.npushes[prevop.op]; i++) + prevop.result.push(log.stack.peek(i).toString(16)); + } + switch(log.op.toString()) { + case "CALL": case "CALLCODE": + var instart = log.stack.peek(3).valueOf(); + var insize = log.stack.peek(4).valueOf(); + opinfo["gas"] = log.stack.peek(0).valueOf(); + opinfo["to"] = log.stack.peek(1).toString(16); + opinfo["value"] = log.stack.peek(2).toString(); + opinfo["input"] = log.memory.slice(instart, instart + insize); + opinfo["error"] = null; + opinfo["return"] = null; + opinfo["ops"] = []; + this.stack.push(opinfo); + break; + case "DELEGATECALL": case "STATICCALL": + var instart = log.stack.peek(2).valueOf(); + var insize = log.stack.peek(3).valueOf(); + opinfo["op"] = log.op.toString(); + opinfo["gas"] = log.stack.peek(0).valueOf(); + opinfo["to"] = log.stack.peek(1).toString(16); + opinfo["input"] = log.memory.slice(instart, instart + insize); + opinfo["error"] = null; + opinfo["return"] = null; + opinfo["ops"] = []; + this.stack.push(opinfo); + break; + case "RETURN": case "REVERT": + var out = log.stack.peek(0).valueOf(); + var outsize = log.stack.peek(1).valueOf(); + frame.return = log.memory.slice(out, out + outsize); + break; + case "STOP": case "SELFDESTRUCT": + frame.return = log.memory.slice(0, 0); + break; + case "JUMPDEST": + opinfo["pc"] = log.getPC(); + } + if(log.op.isPush()) { + opinfo["len"] = log.op.toNumber() - 0x5e; + } + frame.ops.push(opinfo); + } else { + this.stack = this.stack.slice(0, log.getDepth()); + } + } +} diff --git a/eth/tracers/js/internal/tracers/noop_tracer_legacy.js b/eth/tracers/js/internal/tracers/noop_tracer_legacy.js new file mode 100644 index 0000000..fe7ddc8 --- /dev/null +++ b/eth/tracers/js/internal/tracers/noop_tracer_legacy.js @@ -0,0 +1,29 @@ +// 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 . + +// noopTracer is just the barebone boilerplate code required from a JavaScript +// object to be usable as a transaction tracer. +{ + // step is invoked for every opcode that the VM executes. + step: function(log, db) { }, + + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) { }, + + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function(ctx, db) { return {}; } +} diff --git a/eth/tracers/js/internal/tracers/opcount_tracer.js b/eth/tracers/js/internal/tracers/opcount_tracer.js new file mode 100644 index 0000000..f7984c7 --- /dev/null +++ b/eth/tracers/js/internal/tracers/opcount_tracer.js @@ -0,0 +1,32 @@ +// 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 . + +// opcountTracer is a sample tracer that just counts the number of instructions +// executed by the EVM before the transaction terminated. +{ + // count tracks the number of EVM instructions executed. + count: 0, + + // step is invoked for every opcode that the VM executes. + step: function(log, db) { this.count++ }, + + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) { }, + + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function(ctx, db) { return this.count } +} diff --git a/eth/tracers/js/internal/tracers/prestate_tracer_legacy.js b/eth/tracers/js/internal/tracers/prestate_tracer_legacy.js new file mode 100644 index 0000000..2757b8b --- /dev/null +++ b/eth/tracers/js/internal/tracers/prestate_tracer_legacy.js @@ -0,0 +1,115 @@ +// 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 . + +// prestateTracer outputs sufficient information to create a local execution of +// the transaction from a custom assembled genesis block. +{ + // prestate is the genesis that we're building. + prestate: null, + + // lookupAccount injects the specified account into the prestate object. + lookupAccount: function(addr, db){ + var acc = toHex(addr); + if (this.prestate[acc] === undefined) { + this.prestate[acc] = { + balance: '0x' + db.getBalance(addr).toString(16), + nonce: db.getNonce(addr), + code: toHex(db.getCode(addr)), + storage: {} + }; + } + }, + + // lookupStorage injects the specified storage entry of the given account into + // the prestate object. + lookupStorage: function(addr, key, db){ + var acc = toHex(addr); + var idx = toHex(key); + + if (this.prestate[acc].storage[idx] === undefined) { + this.prestate[acc].storage[idx] = toHex(db.getState(addr, key)); + } + }, + + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function(ctx, db) { + if (this.prestate === null) { + this.prestate = {}; + // If tx is transfer-only, the recipient account + // hasn't been populated. + this.lookupAccount(ctx.to, db); + } + + // At this point, we need to deduct the 'value' from the + // outer transaction, and move it back to the origin + this.lookupAccount(ctx.from, db); + + var fromBal = bigInt(this.prestate[toHex(ctx.from)].balance.slice(2), 16); + var toBal = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16); + + this.prestate[toHex(ctx.to)].balance = '0x'+toBal.subtract(ctx.value).toString(16); + this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).add(ctx.gasUsed * ctx.gasPrice).toString(16); + + // Decrement the caller's nonce, and remove empty create targets + this.prestate[toHex(ctx.from)].nonce--; + if (ctx.type == 'CREATE') { + // We can blibdly delete the contract prestate, as any existing state would + // have caused the transaction to be rejected as invalid in the first place. + delete this.prestate[toHex(ctx.to)]; + } + // Return the assembled allocations (prestate) + return this.prestate; + }, + + // step is invoked for every opcode that the VM executes. + step: function(log, db) { + // Add the current account if we just started tracing + if (this.prestate === null){ + this.prestate = {}; + // Balance will potentially be wrong here, since this will include the value + // sent along with the message. We fix that in 'result()'. + this.lookupAccount(log.contract.getAddress(), db); + } + // Whenever new state is accessed, add it to the prestate + switch (log.op.toString()) { + case "EXTCODECOPY": case "EXTCODESIZE": case "EXTCODEHASH": case "BALANCE": + this.lookupAccount(toAddress(log.stack.peek(0).toString(16)), db); + break; + case "CREATE": + var from = log.contract.getAddress(); + this.lookupAccount(toContract(from, db.getNonce(from)), db); + break; + case "CREATE2": + var from = log.contract.getAddress(); + // stack: salt, size, offset, endowment + var offset = log.stack.peek(1).valueOf() + var size = log.stack.peek(2).valueOf() + var end = offset + size + this.lookupAccount(toContract2(from, log.stack.peek(3).toString(16), log.memory.slice(offset, end)), db); + break; + case "CALL": case "CALLCODE": case "DELEGATECALL": case "STATICCALL": + this.lookupAccount(toAddress(log.stack.peek(1).toString(16)), db); + break; + case 'SSTORE':case 'SLOAD': + this.lookupStorage(log.contract.getAddress(), toWord(log.stack.peek(0).toString(16)), db); + break; + } + }, + + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) {} +} diff --git a/eth/tracers/js/internal/tracers/tracers.go b/eth/tracers/js/internal/tracers/tracers.go new file mode 100644 index 0000000..6547f1b --- /dev/null +++ b/eth/tracers/js/internal/tracers/tracers.go @@ -0,0 +1,59 @@ +// 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 tracers contains the actual JavaScript tracer assets. +package tracers + +import ( + "embed" + "io/fs" + "strings" + "unicode" +) + +//go:embed *.js +var files embed.FS + +// Load reads the built-in JS tracer files embedded in the binary and +// returns a mapping of tracer name to source. +func Load() (map[string]string, error) { + var assetTracers = make(map[string]string) + err := fs.WalkDir(files, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + b, err := fs.ReadFile(files, path) + if err != nil { + return err + } + name := camel(strings.TrimSuffix(path, ".js")) + assetTracers[name] = string(b) + return nil + }) + return assetTracers, err +} + +// camel converts a snake cased input string into a camel cased output. +func camel(str string) string { + pieces := strings.Split(str, "_") + for i := 1; i < len(pieces); i++ { + pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:] + } + return strings.Join(pieces, "") +} diff --git a/eth/tracers/js/internal/tracers/trigram_tracer.js b/eth/tracers/js/internal/tracers/trigram_tracer.js new file mode 100644 index 0000000..8756490 --- /dev/null +++ b/eth/tracers/js/internal/tracers/trigram_tracer.js @@ -0,0 +1,49 @@ +// 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 . + +{ + // hist is the map of trigram counters + hist: {}, + // lastOp is last operation + lastOps: ['',''], + lastDepth: 0, + // step is invoked for every opcode that the VM executes. + step: function(log, db) { + var depth = log.getDepth(); + if (depth != this.lastDepth){ + this.lastOps = ['','']; + this.lastDepth = depth; + return; + } + var op = log.op.toString(); + var key = this.lastOps[0]+'-'+this.lastOps[1]+'-'+op; + if (this.hist[key]){ + this.hist[key]++; + } + else { + this.hist[key] = 1; + } + this.lastOps[0] = this.lastOps[1]; + this.lastOps[1] = op; + }, + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) {}, + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function(ctx) { + return this.hist; + }, +} diff --git a/eth/tracers/js/internal/tracers/unigram_tracer.js b/eth/tracers/js/internal/tracers/unigram_tracer.js new file mode 100644 index 0000000..51107d8 --- /dev/null +++ b/eth/tracers/js/internal/tracers/unigram_tracer.js @@ -0,0 +1,41 @@ +// 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 . + +{ + // hist is the map of opcodes to counters + hist: {}, + // nops counts number of ops + nops: 0, + // step is invoked for every opcode that the VM executes. + step: function(log, db) { + var op = log.op.toString(); + if (this.hist[op]){ + this.hist[op]++; + } + else { + this.hist[op] = 1; + } + this.nops++; + }, + // fault is invoked when the actual execution of an opcode fails. + fault: function(log, db) {}, + + // result is invoked when all the opcodes have been iterated over and returns + // the final result of the tracing. + result: function(ctx) { + return this.hist; + }, +} diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go new file mode 100644 index 0000000..05adedf --- /dev/null +++ b/eth/tracers/js/tracer_test.go @@ -0,0 +1,323 @@ +// 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 js + +import ( + "encoding/json" + "errors" + "math/big" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +type account struct{} + +func (account) SubBalance(amount *big.Int) {} +func (account) AddBalance(amount *big.Int) {} +func (account) SetAddress(common.Address) {} +func (account) Value() *big.Int { return nil } +func (account) SetBalance(*uint256.Int) {} +func (account) SetNonce(uint64) {} +func (account) Balance() *uint256.Int { return nil } +func (account) Address() common.Address { return common.Address{} } +func (account) SetCode(common.Hash, []byte) {} +func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} + +type dummyStatedb struct { + state.StateDB +} + +func (*dummyStatedb) GetRefund() uint64 { return 1337 } +func (*dummyStatedb) GetBalance(addr common.Address) *uint256.Int { return new(uint256.Int) } + +type vmContext struct { + blockCtx vm.BlockContext + txCtx vm.TxContext +} + +func testCtx() *vmContext { + return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} +} + +func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainConfig, contractCode []byte) (json.RawMessage, error) { + var ( + env = vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Tracer: tracer.Hooks}) + gasLimit uint64 = 31000 + startGas uint64 = 10000 + value = uint256.NewInt(0) + contract = vm.NewContract(account{}, account{}, value, startGas) + ) + contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} + if contractCode != nil { + contract.Code = contractCode + } + + tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller()) + tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig()) + ret, err := env.Interpreter().Run(contract, []byte{}, false) + tracer.OnExit(0, ret, startGas-contract.Gas, err, true) + // Rest gas assumes no refund + tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) + if err != nil { + return nil, err + } + return tracer.GetResult() +} + +func TestTracer(t *testing.T) { + execTracer := func(code string, contract []byte) ([]byte, string) { + t.Helper() + tracer, err := newJsTracer(code, nil, nil) + if err != nil { + t.Fatal(err) + } + ret, err := runTrace(tracer, testCtx(), params.TestChainConfig, contract) + if err != nil { + return nil, err.Error() // Stringify to allow comparison without nil checks + } + return ret, "" + } + for i, tt := range []struct { + code string + want string + fail string + contract []byte + }{ + { // tests that we don't panic on bad arguments to memory access + code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", + want: ``, + fail: "tracer accessed out of bound memory: offset -1, end -2 at step (:1:53(13)) in server-side tracer function 'step'", + }, { // tests that we don't panic on bad arguments to stack peeks + code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", + want: ``, + fail: "tracer accessed out of bound stack: size 0, index -1 at step (:1:53(11)) in server-side tracer function 'step'", + }, { // tests that we don't panic on bad arguments to memory getUint + code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", + want: ``, + fail: "tracer accessed out of bound memory: available 0, offset -64, size 32 at step (:1:58(11)) in server-side tracer function 'step'", + }, { // tests some general counting + code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", + want: `3`, + }, { // tests that depth is reported correctly + code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", + want: `[0,1,2]`, + }, { // tests memory length + code: "{lengths: [], step: function(log) { this.lengths.push(log.memory.length()); }, fault: function() {}, result: function() { return this.lengths; }}", + want: `[0,0,0]`, + }, { // tests to-string of opcodes + code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", + want: `["PUSH1","PUSH1","STOP"]`, + }, { // tests gasUsed + code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed; }}", + want: `"100000.21006"`, + }, { + code: "{res: null, step: function(log) {}, fault: function() {}, result: function() { return toWord('0xffaa') }}", + want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":255,"31":170}`, + }, { // test feeding a buffer back into go + code: "{res: null, step: function(log) { var address = log.contract.getAddress(); this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}", + want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`, + }, { + code: "{res: null, step: function(log) { var address = '0x0000000000000000000000000000000000000000'; this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}", + want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`, + }, { + code: "{res: null, step: function(log) { var address = Array.prototype.slice.call(log.contract.getAddress()); this.res = toAddress(address); }, fault: function() {}, result: function() { return this.res }}", + want: `{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0}`, + }, { + code: "{res: [], step: function(log) { var op = log.op.toString(); if (op === 'MSTORE8' || op === 'STOP') { this.res.push(log.memory.slice(0, 2)) } }, fault: function() {}, result: function() { return this.res }}", + want: `[{"0":0,"1":0},{"0":255,"1":0}]`, + contract: []byte{byte(vm.PUSH1), byte(0xff), byte(vm.PUSH1), byte(0x00), byte(vm.MSTORE8), byte(vm.STOP)}, + }, { + code: "{res: [], step: function(log) { if (log.op.toString() === 'STOP') { this.res.push(log.memory.slice(5, 1025 * 1024)) } }, fault: function() {}, result: function() { return this.res }}", + want: "", + fail: "reached limit for padding memory slice: 1049568 at step (:1:83(20)) in server-side tracer function 'step'", + contract: []byte{byte(vm.PUSH1), byte(0xff), byte(vm.PUSH1), byte(0x00), byte(vm.MSTORE8), byte(vm.STOP)}, + }, + } { + if have, err := execTracer(tt.code, tt.contract); tt.want != string(have) || tt.fail != err { + t.Errorf("testcase %d: expected return value to be \n'%s'\n\tgot\n'%s'\nerror to be\n'%s'\n\tgot\n'%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code) + } + } +} + +func TestHalt(t *testing.T) { + timeout := errors.New("stahp") + tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil, nil) + if err != nil { + t.Fatal(err) + } + go func() { + time.Sleep(1 * time.Second) + tracer.Stop(timeout) + }() + if _, err = runTrace(tracer, testCtx(), params.TestChainConfig, nil); !strings.Contains(err.Error(), "stahp") { + t.Errorf("Expected timeout error, got %v", err) + } +} + +func TestHaltBetweenSteps(t *testing.T) { + tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil, nil) + if err != nil { + t.Fatal(err) + } + scope := &vm.ScopeContext{ + Contract: vm.NewContract(&account{}, &account{}, uint256.NewInt(0), 0), + } + env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer.Hooks}) + tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{}) + tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 0, big.NewInt(0)) + tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil) + timeout := errors.New("stahp") + tracer.Stop(timeout) + tracer.OnOpcode(0, 0, 0, 0, scope, nil, 0, nil) + + if _, err := tracer.GetResult(); !strings.Contains(err.Error(), timeout.Error()) { + t.Errorf("Expected timeout error, got %v", err) + } +} + +// TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb +// in 'result' +func TestNoStepExec(t *testing.T) { + execTracer := func(code string) []byte { + t.Helper() + tracer, err := newJsTracer(code, nil, nil) + if err != nil { + t.Fatal(err) + } + env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer.Hooks}) + tracer.OnTxStart(env.GetVMContext(), types.NewTx(&types.LegacyTx{}), common.Address{}) + tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, []byte{}, 1000, big.NewInt(0)) + tracer.OnExit(0, nil, 0, nil, false) + ret, err := tracer.GetResult() + if err != nil { + t.Fatal(err) + } + return ret + } + for i, tt := range []struct { + code string + want string + }{ + { // tests that we don't panic on accessing the db methods + code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx, db){ return db.getBalance(ctx.to)} }", + want: `"0"`, + }, + } { + if have := execTracer(tt.code); tt.want != string(have) { + t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code) + } + } +} + +func TestIsPrecompile(t *testing.T) { + chaincfg := ¶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(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(300), LondonBlock: big.NewInt(0), TerminalTotalDifficulty: nil, Ethash: new(params.EthashConfig), Clique: nil} + chaincfg.ByzantiumBlock = big.NewInt(100) + chaincfg.IstanbulBlock = big.NewInt(200) + chaincfg.BerlinBlock = big.NewInt(300) + txCtx := vm.TxContext{GasPrice: big.NewInt(100000)} + tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil) + if err != nil { + t.Fatal(err) + } + + blockCtx := vm.BlockContext{BlockNumber: big.NewInt(150)} + res, err := runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg, nil) + if err != nil { + t.Error(err) + } + if string(res) != "false" { + t.Errorf("tracer should not consider blake2f as precompile in byzantium") + } + + tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil) + blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)} + res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg, nil) + if err != nil { + t.Error(err) + } + if string(res) != "true" { + t.Errorf("tracer should consider blake2f as precompile in istanbul") + } +} + +func TestEnterExit(t *testing.T) { + // test that either both or none of enter() and exit() are defined + if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context), nil); err == nil { + t.Fatal("tracer creation should've failed without exit() definition") + } + if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context), nil); err != nil { + t.Fatal(err) + } + // test that the enter and exit method are correctly invoked and the values passed + tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context), nil) + if err != nil { + t.Fatal(err) + } + scope := &vm.ScopeContext{ + Contract: vm.NewContract(&account{}, &account{}, uint256.NewInt(0), 0), + } + tracer.OnEnter(1, byte(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) + tracer.OnExit(1, []byte{}, 400, nil, false) + + have, err := tracer.GetResult() + if err != nil { + t.Fatal(err) + } + want := `{"enters":1,"exits":1,"enterGas":1000,"gasUsed":400}` + if string(have) != want { + t.Errorf("Number of invocations of enter() and exit() is wrong. Have %s, want %s\n", have, want) + } +} + +func TestSetup(t *testing.T) { + // Test empty config + _, err := newJsTracer(`{setup: function(cfg) { if (cfg !== "{}") { throw("invalid empty config") } }, fault: function() {}, result: function() {}}`, new(tracers.Context), nil) + if err != nil { + t.Error(err) + } + + cfg, err := json.Marshal(map[string]string{"foo": "bar"}) + if err != nil { + t.Fatal(err) + } + // Test no setup func + _, err = newJsTracer(`{fault: function() {}, result: function() {}}`, new(tracers.Context), cfg) + if err != nil { + t.Fatal(err) + } + // Test config value + tracer, err := newJsTracer("{config: null, setup: function(cfg) { this.config = JSON.parse(cfg) }, step: function() {}, fault: function() {}, result: function() { return this.config.foo }}", new(tracers.Context), cfg) + if err != nil { + t.Fatal(err) + } + have, err := tracer.GetResult() + if err != nil { + t.Fatal(err) + } + if string(have) != `"bar"` { + t.Errorf("tracer returned wrong result. have: %s, want: \"bar\"\n", string(have)) + } +} diff --git a/eth/tracers/live.go b/eth/tracers/live.go new file mode 100644 index 0000000..ffb2303 --- /dev/null +++ b/eth/tracers/live.go @@ -0,0 +1,31 @@ +package tracers + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/core/tracing" +) + +type ctorFunc func(config json.RawMessage) (*tracing.Hooks, error) + +// LiveDirectory is the collection of tracers which can be used +// during normal block import operations. +var LiveDirectory = liveDirectory{elems: make(map[string]ctorFunc)} + +type liveDirectory struct { + elems map[string]ctorFunc +} + +// Register registers a tracer constructor by name. +func (d *liveDirectory) Register(name string, f ctorFunc) { + d.elems[name] = f +} + +// New instantiates a tracer by name. +func (d *liveDirectory) New(name string, config json.RawMessage) (*tracing.Hooks, error) { + if f, ok := d.elems[name]; ok { + return f(config) + } + return nil, errors.New("not found") +} diff --git a/eth/tracers/live/gen_supplyinfoburn.go b/eth/tracers/live/gen_supplyinfoburn.go new file mode 100644 index 0000000..d01eda3 --- /dev/null +++ b/eth/tracers/live/gen_supplyinfoburn.go @@ -0,0 +1,49 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package live + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*supplyInfoBurnMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s supplyInfoBurn) MarshalJSON() ([]byte, error) { + type supplyInfoBurn struct { + EIP1559 *hexutil.Big `json:"1559,omitempty"` + Blob *hexutil.Big `json:"blob,omitempty"` + Misc *hexutil.Big `json:"misc,omitempty"` + } + var enc supplyInfoBurn + enc.EIP1559 = (*hexutil.Big)(s.EIP1559) + enc.Blob = (*hexutil.Big)(s.Blob) + enc.Misc = (*hexutil.Big)(s.Misc) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *supplyInfoBurn) UnmarshalJSON(input []byte) error { + type supplyInfoBurn struct { + EIP1559 *hexutil.Big `json:"1559,omitempty"` + Blob *hexutil.Big `json:"blob,omitempty"` + Misc *hexutil.Big `json:"misc,omitempty"` + } + var dec supplyInfoBurn + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.EIP1559 != nil { + s.EIP1559 = (*big.Int)(dec.EIP1559) + } + if dec.Blob != nil { + s.Blob = (*big.Int)(dec.Blob) + } + if dec.Misc != nil { + s.Misc = (*big.Int)(dec.Misc) + } + return nil +} diff --git a/eth/tracers/live/gen_supplyinfoissuance.go b/eth/tracers/live/gen_supplyinfoissuance.go new file mode 100644 index 0000000..e2536ee --- /dev/null +++ b/eth/tracers/live/gen_supplyinfoissuance.go @@ -0,0 +1,49 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package live + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*supplyInfoIssuanceMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s supplyInfoIssuance) MarshalJSON() ([]byte, error) { + type supplyInfoIssuance struct { + GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"` + Reward *hexutil.Big `json:"reward,omitempty"` + Withdrawals *hexutil.Big `json:"withdrawals,omitempty"` + } + var enc supplyInfoIssuance + enc.GenesisAlloc = (*hexutil.Big)(s.GenesisAlloc) + enc.Reward = (*hexutil.Big)(s.Reward) + enc.Withdrawals = (*hexutil.Big)(s.Withdrawals) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *supplyInfoIssuance) UnmarshalJSON(input []byte) error { + type supplyInfoIssuance struct { + GenesisAlloc *hexutil.Big `json:"genesisAlloc,omitempty"` + Reward *hexutil.Big `json:"reward,omitempty"` + Withdrawals *hexutil.Big `json:"withdrawals,omitempty"` + } + var dec supplyInfoIssuance + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.GenesisAlloc != nil { + s.GenesisAlloc = (*big.Int)(dec.GenesisAlloc) + } + if dec.Reward != nil { + s.Reward = (*big.Int)(dec.Reward) + } + if dec.Withdrawals != nil { + s.Withdrawals = (*big.Int)(dec.Withdrawals) + } + return nil +} diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go new file mode 100644 index 0000000..7433c28 --- /dev/null +++ b/eth/tracers/live/noop.go @@ -0,0 +1,96 @@ +package live + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/params" +) + +func init() { + tracers.LiveDirectory.Register("noop", newNoopTracer) +} + +// noop is a no-op live tracer. It's there to +// catch changes in the tracing interface, as well as +// for testing live tracing performance. Can be removed +// as soon as we have a real live tracer. +type noop struct{} + +func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { + t := &noop{} + return &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + OnGasChange: t.OnGasChange, + OnBlockchainInit: t.OnBlockchainInit, + OnBlockStart: t.OnBlockStart, + OnBlockEnd: t.OnBlockEnd, + OnSkippedBlock: t.OnSkippedBlock, + OnGenesisBlock: t.OnGenesisBlock, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, nil +} + +func (t *noop) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { +} + +func (t *noop) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) { +} + +func (t *noop) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +func (t *noop) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { +} + +func (t *noop) OnTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) { +} + +func (t *noop) OnTxEnd(receipt *types.Receipt, err error) { +} + +func (t *noop) OnBlockStart(ev tracing.BlockEvent) { +} + +func (t *noop) OnBlockEnd(err error) { +} + +func (t *noop) OnSkippedBlock(ev tracing.BlockEvent) {} + +func (t *noop) OnBlockchainInit(chainConfig *params.ChainConfig) { +} + +func (t *noop) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { +} + +func (t *noop) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { +} + +func (t *noop) OnNonceChange(a common.Address, prev, new uint64) { +} + +func (t *noop) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { +} + +func (t *noop) OnStorageChange(a common.Address, k, prev, new common.Hash) { +} + +func (t *noop) OnLog(l *types.Log) { + +} + +func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { +} diff --git a/eth/tracers/live/supply.go b/eth/tracers/live/supply.go new file mode 100644 index 0000000..96f7059 --- /dev/null +++ b/eth/tracers/live/supply.go @@ -0,0 +1,308 @@ +package live + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/tracing" + "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/log" + "gopkg.in/natefinch/lumberjack.v2" +) + +func init() { + tracers.LiveDirectory.Register("supply", newSupply) +} + +type supplyInfoIssuance struct { + GenesisAlloc *big.Int `json:"genesisAlloc,omitempty"` + Reward *big.Int `json:"reward,omitempty"` + Withdrawals *big.Int `json:"withdrawals,omitempty"` +} + +//go:generate go run github.com/fjl/gencodec -type supplyInfoIssuance -field-override supplyInfoIssuanceMarshaling -out gen_supplyinfoissuance.go +type supplyInfoIssuanceMarshaling struct { + GenesisAlloc *hexutil.Big + Reward *hexutil.Big + Withdrawals *hexutil.Big +} + +type supplyInfoBurn struct { + EIP1559 *big.Int `json:"1559,omitempty"` + Blob *big.Int `json:"blob,omitempty"` + Misc *big.Int `json:"misc,omitempty"` +} + +//go:generate go run github.com/fjl/gencodec -type supplyInfoBurn -field-override supplyInfoBurnMarshaling -out gen_supplyinfoburn.go +type supplyInfoBurnMarshaling struct { + EIP1559 *hexutil.Big + Blob *hexutil.Big + Misc *hexutil.Big +} + +type supplyInfo struct { + Issuance *supplyInfoIssuance `json:"issuance,omitempty"` + Burn *supplyInfoBurn `json:"burn,omitempty"` + + // Block info + Number uint64 `json:"blockNumber"` + Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash"` +} + +type supplyTxCallstack struct { + calls []supplyTxCallstack + burn *big.Int +} + +type supply struct { + delta supplyInfo + txCallstack []supplyTxCallstack // Callstack for current transaction + logger *lumberjack.Logger +} + +type supplyTracerConfig struct { + Path string `json:"path"` // Path to the directory where the tracer logs will be stored + MaxSize int `json:"maxSize"` // MaxSize is the maximum size in megabytes of the tracer log file before it gets rotated. It defaults to 100 megabytes. +} + +func newSupply(cfg json.RawMessage) (*tracing.Hooks, error) { + var config supplyTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, fmt.Errorf("failed to parse config: %v", err) + } + } + if config.Path == "" { + return nil, errors.New("supply tracer output path is required") + } + + // Store traces in a rotating file + logger := &lumberjack.Logger{ + Filename: filepath.Join(config.Path, "supply.jsonl"), + } + if config.MaxSize > 0 { + logger.MaxSize = config.MaxSize + } + + t := &supply{ + delta: newSupplyInfo(), + logger: logger, + } + return &tracing.Hooks{ + OnBlockStart: t.OnBlockStart, + OnBlockEnd: t.OnBlockEnd, + OnGenesisBlock: t.OnGenesisBlock, + OnTxStart: t.OnTxStart, + OnBalanceChange: t.OnBalanceChange, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnClose: t.OnClose, + }, nil +} + +func newSupplyInfo() supplyInfo { + return supplyInfo{ + Issuance: &supplyInfoIssuance{ + GenesisAlloc: big.NewInt(0), + Reward: big.NewInt(0), + Withdrawals: big.NewInt(0), + }, + Burn: &supplyInfoBurn{ + EIP1559: big.NewInt(0), + Blob: big.NewInt(0), + Misc: big.NewInt(0), + }, + + Number: 0, + Hash: common.Hash{}, + ParentHash: common.Hash{}, + } +} + +func (s *supply) resetDelta() { + s.delta = newSupplyInfo() +} + +func (s *supply) OnBlockStart(ev tracing.BlockEvent) { + s.resetDelta() + + s.delta.Number = ev.Block.NumberU64() + s.delta.Hash = ev.Block.Hash() + s.delta.ParentHash = ev.Block.ParentHash() + + // Calculate Burn for this block + if ev.Block.BaseFee() != nil { + burn := new(big.Int).Mul(new(big.Int).SetUint64(ev.Block.GasUsed()), ev.Block.BaseFee()) + s.delta.Burn.EIP1559 = burn + } + // Blob burnt gas + if blobGas := ev.Block.BlobGasUsed(); blobGas != nil && *blobGas > 0 && ev.Block.ExcessBlobGas() != nil { + var ( + excess = *ev.Block.ExcessBlobGas() + baseFee = eip4844.CalcBlobFee(excess) + burn = new(big.Int).Mul(new(big.Int).SetUint64(*blobGas), baseFee) + ) + s.delta.Burn.Blob = burn + } +} + +func (s *supply) OnBlockEnd(err error) { + s.write(s.delta) +} + +func (s *supply) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { + s.resetDelta() + + s.delta.Number = b.NumberU64() + s.delta.Hash = b.Hash() + s.delta.ParentHash = b.ParentHash() + + // Initialize supply with total allocation in genesis block + for _, account := range alloc { + s.delta.Issuance.GenesisAlloc.Add(s.delta.Issuance.GenesisAlloc, account.Balance) + } + + s.write(s.delta) +} + +func (s *supply) OnBalanceChange(a common.Address, prevBalance, newBalance *big.Int, reason tracing.BalanceChangeReason) { + diff := new(big.Int).Sub(newBalance, prevBalance) + + // NOTE: don't handle "BalanceIncreaseGenesisBalance" because it is handled in OnGenesisBlock + switch reason { + case tracing.BalanceIncreaseRewardMineUncle: + case tracing.BalanceIncreaseRewardMineBlock: + s.delta.Issuance.Reward.Add(s.delta.Issuance.Reward, diff) + case tracing.BalanceIncreaseWithdrawal: + s.delta.Issuance.Withdrawals.Add(s.delta.Issuance.Withdrawals, diff) + case tracing.BalanceDecreaseSelfdestructBurn: + // BalanceDecreaseSelfdestructBurn is non-reversible as it happens + // at the end of the transaction. + s.delta.Burn.Misc.Sub(s.delta.Burn.Misc, diff) + default: + return + } +} + +func (s *supply) OnTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) { + s.txCallstack = make([]supplyTxCallstack, 0, 1) +} + +// internalTxsHandler handles internal transactions burned amount +func (s *supply) internalTxsHandler(call *supplyTxCallstack) { + // Handle Burned amount + if call.burn != nil { + s.delta.Burn.Misc.Add(s.delta.Burn.Misc, call.burn) + } + + // Recursively handle internal calls + for _, call := range call.calls { + callCopy := call + s.internalTxsHandler(&callCopy) + } +} + +func (s *supply) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + call := supplyTxCallstack{ + calls: make([]supplyTxCallstack, 0), + } + + // This is a special case of burned amount which has to be handled here + // which happens when type == selfdestruct and from == to. + if vm.OpCode(typ) == vm.SELFDESTRUCT && from == to && value.Cmp(common.Big0) == 1 { + call.burn = value + } + + // Append call to the callstack, so we can fill the details in CaptureExit + s.txCallstack = append(s.txCallstack, call) +} + +func (s *supply) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth == 0 { + // No need to handle Burned amount if transaction is reverted + if !reverted { + s.internalTxsHandler(&s.txCallstack[0]) + } + return + } + + size := len(s.txCallstack) + if size <= 1 { + return + } + // Pop call + call := s.txCallstack[size-1] + s.txCallstack = s.txCallstack[:size-1] + size -= 1 + + // In case of a revert, we can drop the call and all its subcalls. + // Caution, that this has to happen after popping the call from the stack. + if reverted { + return + } + s.txCallstack[size-1].calls = append(s.txCallstack[size-1].calls, call) +} + +func (s *supply) OnClose() { + if err := s.logger.Close(); err != nil { + log.Warn("failed to close supply tracer log file", "error", err) + } +} + +func (s *supply) write(data any) { + supply, ok := data.(supplyInfo) + if !ok { + log.Warn("failed to cast supply tracer data on write to log file") + return + } + + // Remove empty fields + if supply.Issuance.GenesisAlloc.Sign() == 0 { + supply.Issuance.GenesisAlloc = nil + } + + if supply.Issuance.Reward.Sign() == 0 { + supply.Issuance.Reward = nil + } + + if supply.Issuance.Withdrawals.Sign() == 0 { + supply.Issuance.Withdrawals = nil + } + + if supply.Issuance.GenesisAlloc == nil && supply.Issuance.Reward == nil && supply.Issuance.Withdrawals == nil { + supply.Issuance = nil + } + + if supply.Burn.EIP1559.Sign() == 0 { + supply.Burn.EIP1559 = nil + } + + if supply.Burn.Blob.Sign() == 0 { + supply.Burn.Blob = nil + } + + if supply.Burn.Misc.Sign() == 0 { + supply.Burn.Misc = nil + } + + if supply.Burn.EIP1559 == nil && supply.Burn.Blob == nil && supply.Burn.Misc == nil { + supply.Burn = nil + } + + out, _ := json.Marshal(supply) + if _, err := s.logger.Write(out); err != nil { + log.Warn("failed to write to supply tracer log file", "error", err) + } + if _, err := s.logger.Write([]byte{'\n'}); err != nil { + log.Warn("failed to write to supply tracer log file", "error", err) + } +} diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go new file mode 100644 index 0000000..e823146 --- /dev/null +++ b/eth/tracers/logger/access_list_tracer.go @@ -0,0 +1,165 @@ +// 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 logger + +import ( + "maps" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +// accessList is an accumulator for the set of accounts and storage slots an EVM +// contract execution touches. +type accessList map[common.Address]accessListSlots + +// accessListSlots is an accumulator for the set of storage slots within a single +// contract that an EVM contract execution touches. +type accessListSlots map[common.Hash]struct{} + +// newAccessList creates a new accessList. +func newAccessList() accessList { + return make(map[common.Address]accessListSlots) +} + +// addAddress adds an address to the accesslist. +func (al accessList) addAddress(address common.Address) { + // Set address if not previously present + if _, present := al[address]; !present { + al[address] = make(map[common.Hash]struct{}) + } +} + +// addSlot adds a storage slot to the accesslist. +func (al accessList) addSlot(address common.Address, slot common.Hash) { + // Set address if not previously present + al.addAddress(address) + + // Set the slot on the surely existent storage set + al[address][slot] = struct{}{} +} + +// equal checks if the content of the current access list is the same as the +// content of the other one. +func (al accessList) equal(other accessList) bool { + // Cross reference the accounts first + if len(al) != len(other) { + return false + } + // Given that len(al) == len(other), we only need to check that + // all the items from al are in other. + for addr := range al { + if _, ok := other[addr]; !ok { + return false + } + } + + // Accounts match, cross reference the storage slots too + for addr, slots := range al { + otherslots := other[addr] + if !maps.Equal(slots, otherslots) { + return false + } + } + return true +} + +// accessList converts the accesslist to a types.AccessList. +func (al accessList) accessList() types.AccessList { + acl := make(types.AccessList, 0, len(al)) + for addr, slots := range al { + tuple := types.AccessTuple{Address: addr, StorageKeys: []common.Hash{}} + for slot := range slots { + tuple.StorageKeys = append(tuple.StorageKeys, slot) + } + acl = append(acl, tuple) + } + return acl +} + +// AccessListTracer is a tracer that accumulates touched accounts and storage +// slots into an internal set. +type AccessListTracer struct { + excl map[common.Address]struct{} // Set of account to exclude from the list + list accessList // Set of accounts and storage slots touched +} + +// NewAccessListTracer creates a new tracer that can generate AccessLists. +// An optional AccessList can be specified to occupy slots and addresses in +// the resulting accesslist. +func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer { + excl := map[common.Address]struct{}{ + from: {}, to: {}, + } + for _, addr := range precompiles { + excl[addr] = struct{}{} + } + list := newAccessList() + for _, al := range acl { + if _, ok := excl[al.Address]; !ok { + list.addAddress(al.Address) + } + for _, slot := range al.StorageKeys { + list.addSlot(al.Address, slot) + } + } + return &AccessListTracer{ + excl: excl, + list: list, + } +} + +func (a *AccessListTracer) Hooks() *tracing.Hooks { + return &tracing.Hooks{ + OnOpcode: a.OnOpcode, + } +} + +// OnOpcode captures all opcodes that touch storage or addresses and adds them to the accesslist. +func (a *AccessListTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + stackData := scope.StackData() + stackLen := len(stackData) + op := vm.OpCode(opcode) + if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 { + slot := common.Hash(stackData[stackLen-1].Bytes32()) + a.list.addSlot(scope.Address(), slot) + } + if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 { + addr := common.Address(stackData[stackLen-1].Bytes20()) + if _, ok := a.excl[addr]; !ok { + a.list.addAddress(addr) + } + } + if (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE) && stackLen >= 5 { + addr := common.Address(stackData[stackLen-2].Bytes20()) + if _, ok := a.excl[addr]; !ok { + a.list.addAddress(addr) + } + } +} + +// AccessList returns the current accesslist maintained by the tracer. +func (a *AccessListTracer) AccessList() types.AccessList { + return a.list.accessList() +} + +// Equal returns if the content of two access list traces are equal. +func (a *AccessListTracer) Equal(other *AccessListTracer) bool { + return a.list.equal(other.list) +} diff --git a/eth/tracers/logger/gen_callframe.go b/eth/tracers/logger/gen_callframe.go new file mode 100644 index 0000000..b7b2cc2 --- /dev/null +++ b/eth/tracers/logger/gen_callframe.go @@ -0,0 +1,65 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package logger + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ = (*callFrameMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (c callFrame) MarshalJSON() ([]byte, error) { + type callFrame struct { + From common.Address `json:"from"` + To common.Address `json:"to"` + Input hexutil.Bytes `json:"input,omitempty"` + Gas math.HexOrDecimal64 `json:"gas"` + Value *hexutil.Big `json:"value"` + Type string `json:"type"` + } + var enc callFrame + enc.From = c.From + enc.To = c.To + enc.Input = c.Input + enc.Gas = math.HexOrDecimal64(c.Gas) + enc.Value = (*hexutil.Big)(c.Value) + enc.Type = c.Type() + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (c *callFrame) UnmarshalJSON(input []byte) error { + type callFrame struct { + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Input *hexutil.Bytes `json:"input,omitempty"` + Gas *math.HexOrDecimal64 `json:"gas"` + Value *hexutil.Big `json:"value"` + } + var dec callFrame + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.From != nil { + c.From = *dec.From + } + if dec.To != nil { + c.To = *dec.To + } + if dec.Input != nil { + c.Input = *dec.Input + } + if dec.Gas != nil { + c.Gas = uint64(*dec.Gas) + } + if dec.Value != nil { + c.Value = (*big.Int)(dec.Value) + } + return nil +} diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go new file mode 100644 index 0000000..b406cb3 --- /dev/null +++ b/eth/tracers/logger/gen_structlog.go @@ -0,0 +1,118 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package logger + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/holiman/uint256" +) + +var _ = (*structLogMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s StructLog) MarshalJSON() ([]byte, error) { + type StructLog struct { + Pc uint64 `json:"pc"` + Op vm.OpCode `json:"op"` + Gas math.HexOrDecimal64 `json:"gas"` + GasCost math.HexOrDecimal64 `json:"gasCost"` + Memory hexutil.Bytes `json:"memory,omitempty"` + MemorySize int `json:"memSize"` + Stack []hexutil.U256 `json:"stack"` + ReturnData hexutil.Bytes `json:"returnData,omitempty"` + Storage map[common.Hash]common.Hash `json:"-"` + Depth int `json:"depth"` + RefundCounter uint64 `json:"refund"` + Err error `json:"-"` + OpName string `json:"opName"` + ErrorString string `json:"error,omitempty"` + } + var enc StructLog + enc.Pc = s.Pc + enc.Op = s.Op + enc.Gas = math.HexOrDecimal64(s.Gas) + enc.GasCost = math.HexOrDecimal64(s.GasCost) + enc.Memory = s.Memory + enc.MemorySize = s.MemorySize + 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 + enc.RefundCounter = s.RefundCounter + enc.Err = s.Err + enc.OpName = s.OpName() + enc.ErrorString = s.ErrorString() + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *StructLog) UnmarshalJSON(input []byte) error { + type StructLog struct { + Pc *uint64 `json:"pc"` + Op *vm.OpCode `json:"op"` + Gas *math.HexOrDecimal64 `json:"gas"` + GasCost *math.HexOrDecimal64 `json:"gasCost"` + Memory *hexutil.Bytes `json:"memory,omitempty"` + MemorySize *int `json:"memSize"` + Stack []hexutil.U256 `json:"stack"` + ReturnData *hexutil.Bytes `json:"returnData,omitempty"` + Storage map[common.Hash]common.Hash `json:"-"` + Depth *int `json:"depth"` + RefundCounter *uint64 `json:"refund"` + Err error `json:"-"` + } + var dec StructLog + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Pc != nil { + s.Pc = *dec.Pc + } + if dec.Op != nil { + s.Op = *dec.Op + } + if dec.Gas != nil { + s.Gas = uint64(*dec.Gas) + } + if dec.GasCost != nil { + s.GasCost = uint64(*dec.GasCost) + } + if dec.Memory != nil { + s.Memory = *dec.Memory + } + if dec.MemorySize != nil { + s.MemorySize = *dec.MemorySize + } + if dec.Stack != nil { + 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 + } + if dec.Storage != nil { + s.Storage = dec.Storage + } + if dec.Depth != nil { + s.Depth = *dec.Depth + } + if dec.RefundCounter != nil { + s.RefundCounter = *dec.RefundCounter + } + if dec.Err != nil { + s.Err = dec.Err + } + return nil +} diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go new file mode 100644 index 0000000..ef1d471 --- /dev/null +++ b/eth/tracers/logger/logger.go @@ -0,0 +1,478 @@ +// 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 logger + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io" + "math/big" + "strings" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// Storage represents a contract's storage. +type Storage map[common.Hash]common.Hash + +// Copy duplicates the current storage. +func (s Storage) Copy() Storage { + cpy := make(Storage, len(s)) + for key, value := range s { + cpy[key] = value + } + return cpy +} + +// Config are the configuration options for structured logger the EVM +type Config struct { + EnableMemory bool // enable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + EnableReturnData bool // enable return data capture + Debug bool // print output during capture end + Limit int // maximum length of output, but zero means unlimited + // Chain overrides, can be used to execute a trace using future fork rules + Overrides *params.ChainConfig `json:"overrides,omitempty"` +} + +//go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go + +// StructLog is emitted to the EVM each cycle and lists information about the current internal state +// prior to the execution of the statement. +type StructLog struct { + Pc uint64 `json:"pc"` + Op vm.OpCode `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Memory []byte `json:"memory,omitempty"` + MemorySize int `json:"memSize"` + Stack []uint256.Int `json:"stack"` + ReturnData []byte `json:"returnData,omitempty"` + Storage map[common.Hash]common.Hash `json:"-"` + Depth int `json:"depth"` + RefundCounter uint64 `json:"refund"` + Err error `json:"-"` +} + +// overrides for gencodec +type structLogMarshaling struct { + Gas math.HexOrDecimal64 + 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 +} + +// OpName formats the operand name in a human-readable format. +func (s *StructLog) OpName() string { + return s.Op.String() +} + +// ErrorString formats the log's error as a string. +func (s *StructLog) ErrorString() string { + if s.Err != nil { + return s.Err.Error() + } + return "" +} + +// StructLogger is an EVM state logger and implements EVMLogger. +// +// StructLogger can capture state based on the given Log configuration and also keeps +// a track record of modified storage which is used in reporting snapshots of the +// contract their storage. +type StructLogger struct { + cfg Config + env *tracing.VMContext + + storage map[common.Address]Storage + logs []StructLog + output []byte + err error + usedGas uint64 + + interrupt atomic.Bool // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption +} + +// NewStructLogger returns a new logger +func NewStructLogger(cfg *Config) *StructLogger { + logger := &StructLogger{ + storage: make(map[common.Address]Storage), + } + if cfg != nil { + logger.cfg = *cfg + } + return logger +} + +func (l *StructLogger) Hooks() *tracing.Hooks { + return &tracing.Hooks{ + OnTxStart: l.OnTxStart, + OnTxEnd: l.OnTxEnd, + OnExit: l.OnExit, + OnOpcode: l.OnOpcode, + } +} + +// Reset clears the data held by the logger. +func (l *StructLogger) Reset() { + l.storage = make(map[common.Address]Storage) + l.output = make([]byte, 0) + l.logs = l.logs[:0] + l.err = nil +} + +// OnOpcode logs a new structured log message and pushes it out to the environment +// +// OnOpcode also tracks SLOAD/SSTORE ops to track storage change. +func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + // If tracing was interrupted, set the error and stop + if l.interrupt.Load() { + return + } + // check if already accumulated the specified number of logs + if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { + return + } + + op := vm.OpCode(opcode) + memory := scope.MemoryData() + stack := scope.StackData() + // Copy a snapshot of the current memory state to a new buffer + var mem []byte + if l.cfg.EnableMemory { + mem = make([]byte, len(memory)) + copy(mem, memory) + } + // Copy a snapshot of the current stack state to a new buffer + var stck []uint256.Int + if !l.cfg.DisableStack { + stck = make([]uint256.Int, len(stack)) + copy(stck, stack) + } + contractAddr := scope.Address() + stackLen := len(stack) + // Copy a snapshot of the current storage to a new container + var storage Storage + if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) { + // initialise new changed values storage container for this contract + // if not present. + if l.storage[contractAddr] == nil { + l.storage[contractAddr] = make(Storage) + } + // capture SLOAD opcodes and record the read entry in the local storage + if op == vm.SLOAD && stackLen >= 1 { + var ( + address = common.Hash(stack[stackLen-1].Bytes32()) + value = l.env.StateDB.GetState(contractAddr, address) + ) + l.storage[contractAddr][address] = value + storage = l.storage[contractAddr].Copy() + } else if op == vm.SSTORE && stackLen >= 2 { + // capture SSTORE opcodes and record the written entry in the local storage. + var ( + value = common.Hash(stack[stackLen-2].Bytes32()) + address = common.Hash(stack[stackLen-1].Bytes32()) + ) + l.storage[contractAddr][address] = value + storage = l.storage[contractAddr].Copy() + } + } + var rdata []byte + if l.cfg.EnableReturnData { + rdata = make([]byte, len(rData)) + copy(rdata, rData) + } + // create a new snapshot of the EVM. + log := StructLog{pc, op, gas, cost, mem, len(memory), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} + l.logs = append(l.logs, log) +} + +// OnExit is called a call frame finishes processing. +func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth != 0 { + return + } + l.output = output + l.err = err + if l.cfg.Debug { + fmt.Printf("%#x\n", output) + if err != nil { + fmt.Printf(" error: %v\n", err) + } + } +} + +func (l *StructLogger) GetResult() (json.RawMessage, error) { + // Tracing aborted + if l.reason != nil { + return nil, l.reason + } + failed := l.err != nil + returnData := common.CopyBytes(l.output) + // Return data when successful and revert reason when reverted, otherwise empty. + returnVal := fmt.Sprintf("%x", returnData) + if failed && l.err != vm.ErrExecutionReverted { + returnVal = "" + } + return json.Marshal(&ExecutionResult{ + Gas: l.usedGas, + Failed: failed, + ReturnValue: returnVal, + StructLogs: formatLogs(l.StructLogs()), + }) +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (l *StructLogger) Stop(err error) { + l.reason = err + l.interrupt.Store(true) +} + +func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + l.env = env +} + +func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) { + if err != nil { + // Don't override vm error + if l.err == nil { + l.err = err + } + return + } + l.usedGas = receipt.GasUsed +} + +// StructLogs returns the captured log entries. +func (l *StructLogger) StructLogs() []StructLog { return l.logs } + +// Error returns the VM error captured by the trace. +func (l *StructLogger) Error() error { return l.err } + +// Output returns the VM return value captured by the trace. +func (l *StructLogger) Output() []byte { return l.output } + +// WriteTrace writes a formatted trace to the given writer +func WriteTrace(writer io.Writer, logs []StructLog) { + for _, log := range logs { + fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost) + if log.Err != nil { + fmt.Fprintf(writer, " ERROR: %v", log.Err) + } + fmt.Fprintln(writer) + + if len(log.Stack) > 0 { + fmt.Fprintln(writer, "Stack:") + for i := len(log.Stack) - 1; i >= 0; i-- { + fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex()) + } + } + if len(log.Memory) > 0 { + fmt.Fprintln(writer, "Memory:") + fmt.Fprint(writer, hex.Dump(log.Memory)) + } + if len(log.Storage) > 0 { + fmt.Fprintln(writer, "Storage:") + for h, item := range log.Storage { + fmt.Fprintf(writer, "%x: %x\n", h, item) + } + } + if len(log.ReturnData) > 0 { + fmt.Fprintln(writer, "ReturnData:") + fmt.Fprint(writer, hex.Dump(log.ReturnData)) + } + fmt.Fprintln(writer) + } +} + +// WriteLogs writes vm logs in a readable format to the given writer +func WriteLogs(writer io.Writer, logs []*types.Log) { + for _, log := range logs { + fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex) + + for i, topic := range log.Topics { + fmt.Fprintf(writer, "%08d %x\n", i, topic) + } + + fmt.Fprint(writer, hex.Dump(log.Data)) + fmt.Fprintln(writer) + } +} + +type mdLogger struct { + out io.Writer + cfg *Config + env *tracing.VMContext +} + +// NewMarkdownLogger creates a logger which outputs information in a format adapted +// for human readability, and is also a valid markdown table +func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger { + l := &mdLogger{out: writer, cfg: cfg} + if l.cfg == nil { + l.cfg = &Config{} + } + return l +} + +func (t *mdLogger) Hooks() *tracing.Hooks { + return &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + } +} + +func (t *mdLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + t.env = env +} + +func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if depth != 0 { + return + } + create := vm.OpCode(typ) == vm.CREATE + if !create { + fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", + from.String(), to.String(), + input, gas, value) + } else { + fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", + from.String(), to.String(), + input, gas, value) + } + + fmt.Fprintf(t.out, ` +| Pc | Op | Cost | Stack | RStack | Refund | +|-------|-------------|------|-----------|-----------|---------| +`) +} + +func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth == 0 { + fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", + output, gasUsed, err) + } +} + +// OnOpcode also tracks SLOAD/SSTORE ops to track storage change. +func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + stack := scope.StackData() + fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) + + if !t.cfg.DisableStack { + // format stack + var a []string + for _, elem := range stack { + a = append(a, elem.Hex()) + } + b := fmt.Sprintf("[%v]", strings.Join(a, ",")) + fmt.Fprintf(t.out, "%10v |", b) + } + fmt.Fprintf(t.out, "%10v |", t.env.StateDB.GetRefund()) + fmt.Fprintln(t.out, "") + if err != nil { + fmt.Fprintf(t.out, "Error: %v\n", err) + } +} + +func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { + fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) +} + +// ExecutionResult groups all structured logs emitted by the EVM +// while replaying a transaction in debug mode as well as transaction +// execution status, the amount of gas used and the return value +type ExecutionResult struct { + Gas uint64 `json:"gas"` + Failed bool `json:"failed"` + ReturnValue string `json:"returnValue"` + StructLogs []StructLogRes `json:"structLogs"` +} + +// StructLogRes stores a structured log emitted by the EVM while replaying a +// transaction in debug mode +type StructLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Depth int `json:"depth"` + Error string `json:"error,omitempty"` + Stack *[]string `json:"stack,omitempty"` + ReturnData string `json:"returnData,omitempty"` + Memory *[]string `json:"memory,omitempty"` + Storage *map[string]string `json:"storage,omitempty"` + RefundCounter uint64 `json:"refund,omitempty"` +} + +// formatLogs formats EVM returned structured logs for json output +func formatLogs(logs []StructLog) []StructLogRes { + formatted := make([]StructLogRes, len(logs)) + for index, trace := range logs { + formatted[index] = StructLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Depth: trace.Depth, + Error: trace.ErrorString(), + RefundCounter: trace.RefundCounter, + } + if trace.Stack != nil { + stack := make([]string, len(trace.Stack)) + for i, stackValue := range trace.Stack { + stack[i] = stackValue.Hex() + } + formatted[index].Stack = &stack + } + if trace.ReturnData != nil && len(trace.ReturnData) > 0 { + formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData).String() + } + if trace.Memory != nil { + memory := make([]string, 0, (len(trace.Memory)+31)/32) + for i := 0; i+32 <= len(trace.Memory); i += 32 { + memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) + } + formatted[index].Memory = &memory + } + if trace.Storage != nil { + storage := make(map[string]string) + for i, storageValue := range trace.Storage { + storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) + } + formatted[index].Storage = &storage + } + } + return formatted +} diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go new file mode 100644 index 0000000..797f7ac --- /dev/null +++ b/eth/tracers/logger/logger_json.go @@ -0,0 +1,177 @@ +// 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 logger + +import ( + "encoding/json" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe.go + +// overrides for gencodec +type callFrameMarshaling struct { + Input hexutil.Bytes + Gas math.HexOrDecimal64 + Value *hexutil.Big + Type string `json:"type"` // adds call to Type() in MarshalJSON +} + +// callFrame is emitted every call frame entered. +type callFrame struct { + op vm.OpCode + From common.Address `json:"from"` + To common.Address `json:"to"` + Input []byte `json:"input,omitempty"` + Gas uint64 `json:"gas"` + Value *big.Int `json:"value"` +} + +// Type formats the call type in a human-readable format. +func (c *callFrame) Type() string { + return c.op.String() +} + +type jsonLogger struct { + encoder *json.Encoder + cfg *Config + env *tracing.VMContext + hooks *tracing.Hooks +} + +// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects +// into the provided stream. +func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks { + l := &jsonLogger{encoder: json.NewEncoder(writer), cfg: cfg} + if l.cfg == nil { + l.cfg = &Config{} + } + l.hooks = &tracing.Hooks{ + OnTxStart: l.OnTxStart, + OnSystemCallStart: l.onSystemCallStart, + OnExit: l.OnEnd, + OnOpcode: l.OnOpcode, + OnFault: l.OnFault, + } + return l.hooks +} + +// NewJSONLoggerWithCallFrames creates a new EVM tracer that prints execution steps as JSON objects +// into the provided stream. It also includes call frames in the output. +func NewJSONLoggerWithCallFrames(cfg *Config, writer io.Writer) *tracing.Hooks { + l := &jsonLogger{encoder: json.NewEncoder(writer), cfg: cfg} + if l.cfg == nil { + l.cfg = &Config{} + } + l.hooks = &tracing.Hooks{ + OnTxStart: l.OnTxStart, + OnSystemCallStart: l.onSystemCallStart, + OnEnter: l.OnEnter, + OnExit: l.OnExit, + OnOpcode: l.OnOpcode, + OnFault: l.OnFault, + } + return l.hooks +} + +func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) { + // TODO: Add rData to this interface as well + l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err) +} + +func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + memory := scope.MemoryData() + stack := scope.StackData() + + log := StructLog{ + Pc: pc, + Op: vm.OpCode(op), + Gas: gas, + GasCost: cost, + MemorySize: len(memory), + Depth: depth, + RefundCounter: l.env.StateDB.GetRefund(), + Err: err, + } + if l.cfg.EnableMemory { + log.Memory = memory + } + if !l.cfg.DisableStack { + log.Stack = stack + } + if l.cfg.EnableReturnData { + log.ReturnData = rData + } + l.encoder.Encode(log) +} + +func (l *jsonLogger) onSystemCallStart() { + // Process no events while in system call. + hooks := *l.hooks + *l.hooks = tracing.Hooks{ + OnSystemCallEnd: func() { + *l.hooks = hooks + }, + } +} + +// OnEnter is not enabled by default. +func (l *jsonLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + frame := callFrame{ + op: vm.OpCode(typ), + From: from, + To: to, + Gas: gas, + Value: value, + } + if l.cfg.EnableMemory { + frame.Input = input + } + l.encoder.Encode(frame) +} + +func (l *jsonLogger) OnEnd(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth > 0 { + return + } + l.OnExit(depth, output, gasUsed, err, false) +} + +func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + type endLog struct { + Output string `json:"output"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + Err string `json:"error,omitempty"` + } + var errMsg string + if err != nil { + errMsg = err.Error() + } + l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) +} + +func (l *jsonLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + l.env = env +} diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go new file mode 100644 index 0000000..137608f --- /dev/null +++ b/eth/tracers/logger/logger_test.go @@ -0,0 +1,108 @@ +// 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 logger + +import ( + "encoding/json" + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +type dummyContractRef struct { + calledForEach bool +} + +func (dummyContractRef) Address() common.Address { return common.Address{} } +func (dummyContractRef) Value() *big.Int { return new(big.Int) } +func (dummyContractRef) SetCode(common.Hash, []byte) {} +func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { + d.calledForEach = true +} +func (d *dummyContractRef) SubBalance(amount *big.Int) {} +func (d *dummyContractRef) AddBalance(amount *big.Int) {} +func (d *dummyContractRef) SetBalance(*big.Int) {} +func (d *dummyContractRef) SetNonce(uint64) {} +func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } + +type dummyStatedb struct { + state.StateDB +} + +func (*dummyStatedb) GetRefund() uint64 { return 1337 } +func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} } +func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {} + +func TestStoreCapture(t *testing.T) { + var ( + logger = NewStructLogger(nil) + env = vm.NewEVM(vm.BlockContext{}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()}) + contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(uint256.Int), 100000) + ) + contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} + var index common.Hash + logger.OnTxStart(env.GetVMContext(), nil, common.Address{}) + _, err := env.Interpreter().Run(contract, []byte{}, false) + if err != nil { + t.Fatal(err) + } + if len(logger.storage[contract.Address()]) == 0 { + t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), + len(logger.storage[contract.Address()])) + } + exp := common.BigToHash(big.NewInt(1)) + if logger.storage[contract.Address()][index] != exp { + t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index]) + } +} + +// Tests that blank fields don't appear in logs when JSON marshalled, to reduce +// logs bloat and confusion. See https://github.com/ethereum/go-ethereum/issues/24487 +func TestStructLogMarshalingOmitEmpty(t *testing.T) { + tests := []struct { + name string + log *StructLog + want string + }{ + {"empty err and no fields", &StructLog{}, + `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`}, + {"with err", &StructLog{Err: errors.New("this failed")}, + `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP","error":"this failed"}`}, + {"with mem", &StructLog{Memory: make([]byte, 2), MemorySize: 2}, + `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memory":"0x0000","memSize":2,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`}, + {"with 0-size mem", &StructLog{Memory: make([]byte, 0)}, + `{"pc":0,"op":0,"gas":"0x0","gasCost":"0x0","memSize":0,"stack":null,"depth":0,"refund":0,"opName":"STOP"}`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + blob, err := json.Marshal(tt.log) + if err != nil { + t.Fatal(err) + } + if have, want := string(blob), tt.want; have != want { + t.Fatalf("mismatched results\n\thave: %v\n\twant: %v", have, want) + } + }) + } +} diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go new file mode 100644 index 0000000..6cb0e43 --- /dev/null +++ b/eth/tracers/native/4byte.go @@ -0,0 +1,135 @@ +// 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 native + +import ( + "encoding/json" + "math/big" + "strconv" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +func init() { + tracers.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false) +} + +// fourByteTracer searches for 4byte-identifiers, and collects them for post-processing. +// It collects the methods identifiers along with the size of the supplied data, so +// a reversed signature can be matched against the size of the data. +// +// Example: +// +// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"}) +// { +// 0x27dc297e-128: 1, +// 0x38cc4831-0: 2, +// 0x524f3889-96: 1, +// 0xadf59f99-288: 1, +// 0xc281d19e-0: 1 +// } +type fourByteTracer struct { + ids map[string]int // ids aggregates the 4byte ids found + interrupt atomic.Bool // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption + activePrecompiles []common.Address // Updated on tx start based on given rules +} + +// newFourByteTracer returns a native go tracer which collects +// 4 byte-identifiers of a tx, and implements vm.EVMLogger. +func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) { + t := &fourByteTracer{ + ids: make(map[string]int), + } + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnEnter: t.OnEnter, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go +func (t *fourByteTracer) isPrecompiled(addr common.Address) bool { + for _, p := range t.activePrecompiles { + if p == addr { + return true + } + } + return false +} + +// store saves the given identifier and datasize. +func (t *fourByteTracer) store(id []byte, size int) { + key := bytesToHex(id) + "-" + strconv.Itoa(size) + t.ids[key] += 1 +} + +func (t *fourByteTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + // Update list of precompiles based on current block + rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) + t.activePrecompiles = vm.ActivePrecompiles(rules) +} + +// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *fourByteTracer) OnEnter(depth int, opcode byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + // Skip if tracing was interrupted + if t.interrupt.Load() { + return + } + if len(input) < 4 { + return + } + op := vm.OpCode(opcode) + // primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT + if op != vm.DELEGATECALL && op != vm.STATICCALL && + op != vm.CALL && op != vm.CALLCODE { + return + } + // Skip any pre-compile invocations, those are just fancy opcodes + if t.isPrecompiled(to) { + return + } + t.store(input[0:4], len(input)-4) +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *fourByteTracer) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.ids) + if err != nil { + return nil, err + } + return res, t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *fourByteTracer) Stop(err error) { + t.reason = err + t.interrupt.Store(true) +} + +func bytesToHex(s []byte) string { + return "0x" + common.Bytes2Hex(s) +} diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go new file mode 100644 index 0000000..2b84eca --- /dev/null +++ b/eth/tracers/native/call.go @@ -0,0 +1,288 @@ +// 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 native + +import ( + "encoding/json" + "errors" + "math/big" + "sync/atomic" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go + +func init() { + tracers.DefaultDirectory.Register("callTracer", newCallTracer, false) +} + +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 { + Type vm.OpCode `json:"-"` + From common.Address `json:"from"` + Gas uint64 `json:"gas"` + GasUsed uint64 `json:"gasUsed"` + To *common.Address `json:"to,omitempty" rlp:"optional"` + Input []byte `json:"input" rlp:"optional"` + Output []byte `json:"output,omitempty" rlp:"optional"` + Error string `json:"error,omitempty" rlp:"optional"` + RevertReason string `json:"revertReason,omitempty"` + Calls []callFrame `json:"calls,omitempty" rlp:"optional"` + Logs []callLog `json:"logs,omitempty" rlp:"optional"` + // Placed at end on purpose. The RLP will be decoded to 0 instead of + // nil if there are non-empty elements after in the struct. + Value *big.Int `json:"value,omitempty" rlp:"optional"` + revertedSnapshot bool +} + +func (f callFrame) TypeString() string { + return f.Type.String() +} + +func (f callFrame) failed() bool { + return len(f.Error) > 0 && f.revertedSnapshot +} + +func (f *callFrame) processOutput(output []byte, err error, reverted bool) { + output = common.CopyBytes(output) + // Clear error if tx wasn't reverted. This happened + // for pre-homestead contract storage OOG. + if err != nil && !reverted { + err = nil + } + if err == nil { + f.Output = output + return + } + f.Error = err.Error() + f.revertedSnapshot = reverted + if f.Type == vm.CREATE || f.Type == vm.CREATE2 { + f.To = nil + } + if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 { + return + } + f.Output = output + if len(output) < 4 { + return + } + if unpacked, err := abi.UnpackRevert(output); err == nil { + f.RevertReason = unpacked + } +} + +type callFrameMarshaling struct { + TypeString string `json:"type"` + Gas hexutil.Uint64 + GasUsed hexutil.Uint64 + Value *hexutil.Big + Input hexutil.Bytes + Output hexutil.Bytes +} + +type callTracer struct { + callstack []callFrame + config callTracerConfig + gasLimit uint64 + depth int + interrupt atomic.Bool // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption +} + +type callTracerConfig struct { + OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls + WithLog bool `json:"withLog"` // If true, call tracer will collect event logs +} + +// newCallTracer returns a native go tracer which tracks +// call frames of a tx, and implements vm.EVMLogger. +func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + t, err := newCallTracerObject(ctx, cfg) + if err != nil { + return nil, err + } + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +func newCallTracerObject(ctx *tracers.Context, cfg json.RawMessage) (*callTracer, error) { + var config callTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + // First callframe contains tx context info + // and is populated on start and end. + return &callTracer{callstack: make([]callFrame, 0, 1), config: config}, nil +} + +// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *callTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + t.depth = depth + if t.config.OnlyTopCall && depth > 0 { + return + } + // Skip if tracing was interrupted + if t.interrupt.Load() { + return + } + + toCopy := to + call := callFrame{ + Type: vm.OpCode(typ), + From: from, + To: &toCopy, + Input: common.CopyBytes(input), + Gas: gas, + Value: value, + } + if depth == 0 { + call.Gas = t.gasLimit + } + t.callstack = append(t.callstack, call) +} + +// OnExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *callTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if depth == 0 { + t.captureEnd(output, gasUsed, err, reverted) + return + } + + t.depth = depth - 1 + if t.config.OnlyTopCall { + return + } + + size := len(t.callstack) + if size <= 1 { + return + } + // Pop call. + call := t.callstack[size-1] + t.callstack = t.callstack[:size-1] + size -= 1 + + call.GasUsed = gasUsed + call.processOutput(output, err, reverted) + // Nest call into parent. + t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) +} + +func (t *callTracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) { + if len(t.callstack) != 1 { + return + } + t.callstack[0].processOutput(output, err, reverted) +} + +func (t *callTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + t.gasLimit = tx.Gas() +} + +func (t *callTracer) OnTxEnd(receipt *types.Receipt, err error) { + // Error happened during tx validation. + if err != nil { + return + } + t.callstack[0].GasUsed = receipt.GasUsed + if t.config.WithLog { + // Logs are not emitted when the call fails + clearFailedLogs(&t.callstack[0], false) + } +} + +func (t *callTracer) OnLog(log *types.Log) { + // Only logs need to be captured via opcode processing + if !t.config.WithLog { + return + } + // Avoid processing nested calls when only caring about top call + if t.config.OnlyTopCall && t.depth > 0 { + return + } + // Skip if tracing was interrupted + if t.interrupt.Load() { + return + } + l := callLog{ + Address: log.Address, + Topics: log.Topics, + Data: log.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, l) +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *callTracer) GetResult() (json.RawMessage, error) { + if len(t.callstack) != 1 { + return nil, errors.New("incorrect number of top-level calls") + } + + res, err := json.Marshal(t.callstack[0]) + if err != nil { + return nil, err + } + return res, t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *callTracer) Stop(err error) { + t.reason = err + t.interrupt.Store(true) +} + +// clearFailedLogs clears the logs of a callframe and all its children +// in case of execution failure. +func clearFailedLogs(cf *callFrame, parentFailed bool) { + failed := cf.failed() || parentFailed + // Clear own logs + if failed { + cf.Logs = nil + } + for i := range cf.Calls { + clearFailedLogs(&cf.Calls[i], failed) + } +} diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go new file mode 100644 index 0000000..a47b79f --- /dev/null +++ b/eth/tracers/native/call_flat.go @@ -0,0 +1,383 @@ +// 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 native + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "slices" + "strings" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +//go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go +//go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go + +func init() { + tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false) +} + +var parityErrorMapping = map[string]string{ + "contract creation code storage out of gas": "Out of gas", + "out of gas": "Out of gas", + "gas uint64 overflow": "Out of gas", + "max code size exceeded": "Out of gas", + "invalid jump destination": "Bad jump destination", + "execution reverted": "Reverted", + "return data out of bounds": "Out of bounds", + "stack limit reached 1024 (1023)": "Out of stack", + "precompiled failed": "Built-in failed", + "invalid input length": "Built-in failed", +} + +var parityErrorMappingStartingWith = map[string]string{ + "invalid opcode:": "Bad instruction", + "stack underflow": "Stack underflow", +} + +// flatCallFrame is a standalone callframe. +type flatCallFrame struct { + Action flatCallAction `json:"action"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber uint64 `json:"blockNumber"` + Error string `json:"error,omitempty"` + Result *flatCallResult `json:"result,omitempty"` + Subtraces int `json:"subtraces"` + TraceAddress []int `json:"traceAddress"` + TransactionHash *common.Hash `json:"transactionHash"` + TransactionPosition uint64 `json:"transactionPosition"` + Type string `json:"type"` +} + +type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *big.Int `json:"balance,omitempty"` + CallType string `json:"callType,omitempty"` + CreationMethod string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *uint64 `json:"gas,omitempty"` + Init *[]byte `json:"init,omitempty"` + Input *[]byte `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *big.Int `json:"value,omitempty"` +} + +type flatCallActionMarshaling struct { + Balance *hexutil.Big + Gas *hexutil.Uint64 + Init *hexutil.Bytes + Input *hexutil.Bytes + Value *hexutil.Big +} + +type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *[]byte `json:"code,omitempty"` + GasUsed *uint64 `json:"gasUsed,omitempty"` + Output *[]byte `json:"output,omitempty"` +} + +type flatCallResultMarshaling struct { + Code *hexutil.Bytes + GasUsed *hexutil.Uint64 + Output *hexutil.Bytes +} + +// flatCallTracer reports call frame information of a tx in a flat format, i.e. +// as opposed to the nested format of `callTracer`. +type flatCallTracer struct { + tracer *callTracer + config flatCallTracerConfig + ctx *tracers.Context // Holds tracer context data + interrupt atomic.Bool // Atomic flag to signal execution interruption + activePrecompiles []common.Address // Updated on tx start based on given rules +} + +type flatCallTracerConfig struct { + ConvertParityErrors bool `json:"convertParityErrors"` // If true, call tracer converts errors to parity format + IncludePrecompiles bool `json:"includePrecompiles"` // If true, call tracer includes calls to precompiled contracts +} + +// newFlatCallTracer returns a new flatCallTracer. +func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + var config flatCallTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + + // Create inner call tracer with default configuration, don't forward + // the OnlyTopCall or WithLog to inner for now + t, err := newCallTracerObject(ctx, nil) + if err != nil { + return nil, err + } + + ft := &flatCallTracer{tracer: t, ctx: ctx, config: config} + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: ft.OnTxStart, + OnTxEnd: ft.OnTxEnd, + OnEnter: ft.OnEnter, + OnExit: ft.OnExit, + }, + Stop: ft.Stop, + GetResult: ft.GetResult, + }, nil +} + +// OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if t.interrupt.Load() { + return + } + t.tracer.OnEnter(depth, typ, from, to, input, gas, value) + + if depth == 0 { + return + } + // Child calls must have a value, even if it's zero. + // Practically speaking, only STATICCALL has nil value. Set it to zero. + if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil { + t.tracer.callstack[len(t.tracer.callstack)-1].Value = big.NewInt(0) + } +} + +// OnExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if t.interrupt.Load() { + return + } + t.tracer.OnExit(depth, output, gasUsed, err, reverted) + + if depth == 0 { + return + } + // Parity traces don't include CALL/STATICCALLs to precompiles. + // By default we remove them from the callstack. + if t.config.IncludePrecompiles { + return + } + var ( + // call has been nested in parent + parent = t.tracer.callstack[len(t.tracer.callstack)-1] + call = parent.Calls[len(parent.Calls)-1] + typ = call.Type + to = call.To + ) + if typ == vm.CALL || typ == vm.STATICCALL { + if t.isPrecompiled(*to) { + t.tracer.callstack[len(t.tracer.callstack)-1].Calls = parent.Calls[:len(parent.Calls)-1] + } + } +} + +func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + if t.interrupt.Load() { + return + } + t.tracer.OnTxStart(env, tx, from) + // Update list of precompiles based on current block + rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) + t.activePrecompiles = vm.ActivePrecompiles(rules) +} + +func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) { + if t.interrupt.Load() { + return + } + t.tracer.OnTxEnd(receipt, err) +} + +// GetResult returns an empty json object. +func (t *flatCallTracer) GetResult() (json.RawMessage, error) { + if len(t.tracer.callstack) < 1 { + return nil, errors.New("invalid number of calls") + } + + flat, err := flatFromNested(&t.tracer.callstack[0], []int{}, t.config.ConvertParityErrors, t.ctx) + if err != nil { + return nil, err + } + + res, err := json.Marshal(flat) + if err != nil { + return nil, err + } + return res, t.tracer.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *flatCallTracer) Stop(err error) { + t.tracer.Stop(err) + t.interrupt.Store(true) +} + +// isPrecompiled returns whether the addr is a precompile. +func (t *flatCallTracer) isPrecompiled(addr common.Address) bool { + return slices.Contains(t.activePrecompiles, addr) +} + +func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) { + var frame *flatCallFrame + switch input.Type { + case vm.CREATE, vm.CREATE2: + frame = newFlatCreate(input) + case vm.SELFDESTRUCT: + frame = newFlatSelfdestruct(input) + case vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.DELEGATECALL: + frame = newFlatCall(input) + default: + return nil, fmt.Errorf("unrecognized call frame type: %s", input.Type) + } + + frame.TraceAddress = traceAddress + frame.Error = input.Error + frame.Subtraces = len(input.Calls) + fillCallFrameFromContext(frame, ctx) + if convertErrs { + convertErrorToParity(frame) + } + + // Revert output contains useful information (revert reason). + // Otherwise discard result. + if input.Error != "" && input.Error != vm.ErrExecutionReverted.Error() { + frame.Result = nil + } + + output = append(output, *frame) + for i, childCall := range input.Calls { + childAddr := childTraceAddress(traceAddress, i) + childCallCopy := childCall + flat, err := flatFromNested(&childCallCopy, childAddr, convertErrs, ctx) + if err != nil { + return nil, err + } + output = append(output, flat...) + } + + return output, nil +} + +func newFlatCreate(input *callFrame) *flatCallFrame { + var ( + actionInit = input.Input[:] + resultCode = input.Output[:] + ) + + return &flatCallFrame{ + Type: strings.ToLower(vm.CREATE.String()), + Action: flatCallAction{ + From: &input.From, + Gas: &input.Gas, + Value: input.Value, + Init: &actionInit, + }, + Result: &flatCallResult{ + GasUsed: &input.GasUsed, + Address: input.To, + Code: &resultCode, + }, + } +} + +func newFlatCall(input *callFrame) *flatCallFrame { + var ( + actionInput = input.Input[:] + resultOutput = input.Output[:] + ) + + return &flatCallFrame{ + Type: strings.ToLower(vm.CALL.String()), + Action: flatCallAction{ + From: &input.From, + To: input.To, + Gas: &input.Gas, + Value: input.Value, + CallType: strings.ToLower(input.Type.String()), + Input: &actionInput, + }, + Result: &flatCallResult{ + GasUsed: &input.GasUsed, + Output: &resultOutput, + }, + } +} + +func newFlatSelfdestruct(input *callFrame) *flatCallFrame { + return &flatCallFrame{ + Type: "suicide", + Action: flatCallAction{ + SelfDestructed: &input.From, + Balance: input.Value, + RefundAddress: input.To, + }, + } +} + +func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) { + if ctx == nil { + return + } + if ctx.BlockHash != (common.Hash{}) { + callFrame.BlockHash = &ctx.BlockHash + } + if ctx.BlockNumber != nil { + callFrame.BlockNumber = ctx.BlockNumber.Uint64() + } + if ctx.TxHash != (common.Hash{}) { + callFrame.TransactionHash = &ctx.TxHash + } + callFrame.TransactionPosition = uint64(ctx.TxIndex) +} + +func convertErrorToParity(call *flatCallFrame) { + if call.Error == "" { + return + } + + if parityError, ok := parityErrorMapping[call.Error]; ok { + call.Error = parityError + } else { + for gethError, parityError := range parityErrorMappingStartingWith { + if strings.HasPrefix(call.Error, gethError) { + call.Error = parityError + } + } + } +} + +func childTraceAddress(a []int, i int) []int { + child := make([]int, 0, len(a)+1) + child = append(child, a...) + child = append(child, i) + return child +} diff --git a/eth/tracers/native/call_flat_test.go b/eth/tracers/native/call_flat_test.go new file mode 100644 index 0000000..d5481b8 --- /dev/null +++ b/eth/tracers/native/call_flat_test.go @@ -0,0 +1,64 @@ +// Copyright 2024 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 native_test + +import ( + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "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/params" + "github.com/stretchr/testify/require" +) + +func TestCallFlatStop(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("flatCallTracer", &tracers.Context{}, nil) + require.NoError(t, err) + + // this error should be returned by GetResult + stopError := errors.New("stop error") + + // simulate a transaction + tx := types.NewTx(&types.LegacyTx{ + Nonce: 0, + To: &common.Address{}, + Value: big.NewInt(0), + Gas: 0, + GasPrice: big.NewInt(0), + Data: nil, + }) + + tracer.OnTxStart(&tracing.VMContext{ + ChainConfig: params.MainnetChainConfig, + }, tx, common.Address{}) + + tracer.OnEnter(0, byte(vm.CALL), common.Address{}, common.Address{}, nil, 0, big.NewInt(0)) + + // stop before the transaction is finished + tracer.Stop(stopError) + + tracer.OnTxEnd(&types.Receipt{GasUsed: 0}, nil) + + // check that the error is returned by GetResult + _, tracerError := tracer.GetResult() + require.Equal(t, stopError, tracerError) +} diff --git a/eth/tracers/native/gen_account_json.go b/eth/tracers/native/gen_account_json.go new file mode 100644 index 0000000..4c39cbc --- /dev/null +++ b/eth/tracers/native/gen_account_json.go @@ -0,0 +1,56 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*accountMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (a account) MarshalJSON() ([]byte, error) { + type account struct { + Balance *hexutil.Big `json:"balance,omitempty"` + Code hexutil.Bytes `json:"code,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + } + var enc account + enc.Balance = (*hexutil.Big)(a.Balance) + enc.Code = a.Code + enc.Nonce = a.Nonce + enc.Storage = a.Storage + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *account) UnmarshalJSON(input []byte) error { + type account struct { + Balance *hexutil.Big `json:"balance,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + Nonce *uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + } + var dec account + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Balance != nil { + a.Balance = (*big.Int)(dec.Balance) + } + if dec.Code != nil { + a.Code = *dec.Code + } + if dec.Nonce != nil { + a.Nonce = *dec.Nonce + } + if dec.Storage != nil { + a.Storage = dec.Storage + } + return nil +} diff --git a/eth/tracers/native/gen_callframe_json.go b/eth/tracers/native/gen_callframe_json.go new file mode 100644 index 0000000..c44f383 --- /dev/null +++ b/eth/tracers/native/gen_callframe_json.go @@ -0,0 +1,107 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/vm" +) + +var _ = (*callFrameMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (c callFrame) MarshalJSON() ([]byte, error) { + type callFrame0 struct { + Type vm.OpCode `json:"-"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + To *common.Address `json:"to,omitempty" rlp:"optional"` + Input hexutil.Bytes `json:"input" rlp:"optional"` + Output hexutil.Bytes `json:"output,omitempty" rlp:"optional"` + Error string `json:"error,omitempty" rlp:"optional"` + RevertReason string `json:"revertReason,omitempty"` + Calls []callFrame `json:"calls,omitempty" rlp:"optional"` + Logs []callLog `json:"logs,omitempty" rlp:"optional"` + Value *hexutil.Big `json:"value,omitempty" rlp:"optional"` + TypeString string `json:"type"` + } + var enc callFrame0 + enc.Type = c.Type + enc.From = c.From + enc.Gas = hexutil.Uint64(c.Gas) + enc.GasUsed = hexutil.Uint64(c.GasUsed) + enc.To = c.To + enc.Input = c.Input + enc.Output = c.Output + enc.Error = c.Error + enc.RevertReason = c.RevertReason + enc.Calls = c.Calls + enc.Logs = c.Logs + enc.Value = (*hexutil.Big)(c.Value) + enc.TypeString = c.TypeString() + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (c *callFrame) UnmarshalJSON(input []byte) error { + type callFrame0 struct { + Type *vm.OpCode `json:"-"` + From *common.Address `json:"from"` + Gas *hexutil.Uint64 `json:"gas"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + To *common.Address `json:"to,omitempty" rlp:"optional"` + Input *hexutil.Bytes `json:"input" rlp:"optional"` + Output *hexutil.Bytes `json:"output,omitempty" rlp:"optional"` + Error *string `json:"error,omitempty" rlp:"optional"` + RevertReason *string `json:"revertReason,omitempty"` + Calls []callFrame `json:"calls,omitempty" rlp:"optional"` + Logs []callLog `json:"logs,omitempty" rlp:"optional"` + Value *hexutil.Big `json:"value,omitempty" rlp:"optional"` + } + var dec callFrame0 + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Type != nil { + c.Type = *dec.Type + } + if dec.From != nil { + c.From = *dec.From + } + if dec.Gas != nil { + c.Gas = uint64(*dec.Gas) + } + if dec.GasUsed != nil { + c.GasUsed = uint64(*dec.GasUsed) + } + if dec.To != nil { + c.To = dec.To + } + if dec.Input != nil { + c.Input = *dec.Input + } + if dec.Output != nil { + c.Output = *dec.Output + } + if dec.Error != nil { + c.Error = *dec.Error + } + if dec.RevertReason != nil { + c.RevertReason = *dec.RevertReason + } + if dec.Calls != nil { + c.Calls = dec.Calls + } + if dec.Logs != nil { + c.Logs = dec.Logs + } + if dec.Value != nil { + c.Value = (*big.Int)(dec.Value) + } + return nil +} diff --git a/eth/tracers/native/gen_flatcallaction_json.go b/eth/tracers/native/gen_flatcallaction_json.go new file mode 100644 index 0000000..c075606 --- /dev/null +++ b/eth/tracers/native/gen_flatcallaction_json.go @@ -0,0 +1,110 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*flatCallActionMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (f flatCallAction) MarshalJSON() ([]byte, error) { + type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + CallType string `json:"callType,omitempty"` + CreationMethod string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *hexutil.Uint64 `json:"gas,omitempty"` + Init *hexutil.Bytes `json:"init,omitempty"` + Input *hexutil.Bytes `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + } + var enc flatCallAction + enc.Author = f.Author + enc.RewardType = f.RewardType + enc.SelfDestructed = f.SelfDestructed + enc.Balance = (*hexutil.Big)(f.Balance) + enc.CallType = f.CallType + enc.CreationMethod = f.CreationMethod + enc.From = f.From + enc.Gas = (*hexutil.Uint64)(f.Gas) + enc.Init = (*hexutil.Bytes)(f.Init) + enc.Input = (*hexutil.Bytes)(f.Input) + enc.RefundAddress = f.RefundAddress + enc.To = f.To + enc.Value = (*hexutil.Big)(f.Value) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (f *flatCallAction) UnmarshalJSON(input []byte) error { + type flatCallAction struct { + Author *common.Address `json:"author,omitempty"` + RewardType *string `json:"rewardType,omitempty"` + SelfDestructed *common.Address `json:"address,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + CallType *string `json:"callType,omitempty"` + CreationMethod *string `json:"creationMethod,omitempty"` + From *common.Address `json:"from,omitempty"` + Gas *hexutil.Uint64 `json:"gas,omitempty"` + Init *hexutil.Bytes `json:"init,omitempty"` + Input *hexutil.Bytes `json:"input,omitempty"` + RefundAddress *common.Address `json:"refundAddress,omitempty"` + To *common.Address `json:"to,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + } + var dec flatCallAction + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Author != nil { + f.Author = dec.Author + } + if dec.RewardType != nil { + f.RewardType = *dec.RewardType + } + if dec.SelfDestructed != nil { + f.SelfDestructed = dec.SelfDestructed + } + if dec.Balance != nil { + f.Balance = (*big.Int)(dec.Balance) + } + if dec.CallType != nil { + f.CallType = *dec.CallType + } + if dec.CreationMethod != nil { + f.CreationMethod = *dec.CreationMethod + } + if dec.From != nil { + f.From = dec.From + } + if dec.Gas != nil { + f.Gas = (*uint64)(dec.Gas) + } + if dec.Init != nil { + f.Init = (*[]byte)(dec.Init) + } + if dec.Input != nil { + f.Input = (*[]byte)(dec.Input) + } + if dec.RefundAddress != nil { + f.RefundAddress = dec.RefundAddress + } + if dec.To != nil { + f.To = dec.To + } + if dec.Value != nil { + f.Value = (*big.Int)(dec.Value) + } + return nil +} diff --git a/eth/tracers/native/gen_flatcallresult_json.go b/eth/tracers/native/gen_flatcallresult_json.go new file mode 100644 index 0000000..e9fa5e4 --- /dev/null +++ b/eth/tracers/native/gen_flatcallresult_json.go @@ -0,0 +1,55 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package native + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*flatCallResultMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (f flatCallResult) MarshalJSON() ([]byte, error) { + type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` + Output *hexutil.Bytes `json:"output,omitempty"` + } + var enc flatCallResult + enc.Address = f.Address + enc.Code = (*hexutil.Bytes)(f.Code) + enc.GasUsed = (*hexutil.Uint64)(f.GasUsed) + enc.Output = (*hexutil.Bytes)(f.Output) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (f *flatCallResult) UnmarshalJSON(input []byte) error { + type flatCallResult struct { + Address *common.Address `json:"address,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` + GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` + Output *hexutil.Bytes `json:"output,omitempty"` + } + var dec flatCallResult + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Address != nil { + f.Address = dec.Address + } + if dec.Code != nil { + f.Code = (*[]byte)(dec.Code) + } + if dec.GasUsed != nil { + f.GasUsed = (*uint64)(dec.GasUsed) + } + if dec.Output != nil { + f.Output = (*[]byte)(dec.Output) + } + return nil +} diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go new file mode 100644 index 0000000..c3b1d9f --- /dev/null +++ b/eth/tracers/native/mux.go @@ -0,0 +1,198 @@ +// 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 native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +func init() { + tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false) +} + +// muxTracer is a go implementation of the Tracer interface which +// runs multiple tracers in one go. +type muxTracer struct { + names []string + tracers []*tracers.Tracer +} + +// newMuxTracer returns a new mux tracer. +func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + var config map[string]json.RawMessage + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + objects := make([]*tracers.Tracer, 0, len(config)) + names := make([]string, 0, len(config)) + for k, v := range config { + t, err := tracers.DefaultDirectory.New(k, ctx, v) + if err != nil { + return nil, err + } + objects = append(objects, t) + names = append(names, k) + } + + t := &muxTracer{names: names, tracers: objects} + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + OnGasChange: t.OnGasChange, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +func (t *muxTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + for _, t := range t.tracers { + if t.OnOpcode != nil { + t.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) + } + } +} + +func (t *muxTracer) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { + for _, t := range t.tracers { + if t.OnFault != nil { + t.OnFault(pc, op, gas, cost, scope, depth, err) + } + } +} + +func (t *muxTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { + for _, t := range t.tracers { + if t.OnGasChange != nil { + t.OnGasChange(old, new, reason) + } + } +} + +func (t *muxTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + for _, t := range t.tracers { + if t.OnEnter != nil { + t.OnEnter(depth, typ, from, to, input, gas, value) + } + } +} + +func (t *muxTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + for _, t := range t.tracers { + if t.OnExit != nil { + t.OnExit(depth, output, gasUsed, err, reverted) + } + } +} + +func (t *muxTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + for _, t := range t.tracers { + if t.OnTxStart != nil { + t.OnTxStart(env, tx, from) + } + } +} + +func (t *muxTracer) OnTxEnd(receipt *types.Receipt, err error) { + for _, t := range t.tracers { + if t.OnTxEnd != nil { + t.OnTxEnd(receipt, err) + } + } +} + +func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { + for _, t := range t.tracers { + if t.OnBalanceChange != nil { + t.OnBalanceChange(a, prev, new, reason) + } + } +} + +func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) { + for _, t := range t.tracers { + if t.OnNonceChange != nil { + t.OnNonceChange(a, prev, new) + } + } +} + +func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { + for _, t := range t.tracers { + if t.OnCodeChange != nil { + t.OnCodeChange(a, prevCodeHash, prev, codeHash, code) + } + } +} + +func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) { + for _, t := range t.tracers { + if t.OnStorageChange != nil { + t.OnStorageChange(a, k, prev, new) + } + } +} + +func (t *muxTracer) OnLog(log *types.Log) { + for _, t := range t.tracers { + if t.OnLog != nil { + t.OnLog(log) + } + } +} + +// GetResult returns an empty json object. +func (t *muxTracer) GetResult() (json.RawMessage, error) { + resObject := make(map[string]json.RawMessage) + for i, tt := range t.tracers { + r, err := tt.GetResult() + if err != nil { + return nil, err + } + resObject[t.names[i]] = r + } + res, err := json.Marshal(resObject) + if err != nil { + return nil, err + } + return res, nil +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *muxTracer) Stop(err error) { + for _, t := range t.tracers { + t.Stop(err) + } +} diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go new file mode 100644 index 0000000..f147134 --- /dev/null +++ b/eth/tracers/native/noop.go @@ -0,0 +1,98 @@ +// 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 native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +func init() { + tracers.DefaultDirectory.Register("noopTracer", newNoopTracer, false) +} + +// noopTracer is a go implementation of the Tracer interface which +// performs no action. It's mostly useful for testing purposes. +type noopTracer struct{} + +// newNoopTracer returns a new noop tracer. +func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) { + t := &noopTracer{} + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnEnter: t.OnEnter, + OnExit: t.OnExit, + OnOpcode: t.OnOpcode, + OnFault: t.OnFault, + OnGasChange: t.OnGasChange, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +func (t *noopTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { +} + +func (t *noopTracer) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) { +} + +func (t *noopTracer) OnGasChange(old, new uint64, reason tracing.GasChangeReason) {} + +func (t *noopTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +func (t *noopTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { +} + +func (*noopTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { +} + +func (*noopTracer) OnTxEnd(receipt *types.Receipt, err error) {} + +func (*noopTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { +} + +func (*noopTracer) OnNonceChange(a common.Address, prev, new uint64) {} + +func (*noopTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { +} + +func (*noopTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) {} + +func (*noopTracer) OnLog(log *types.Log) {} + +// GetResult returns an empty json object. +func (t *noopTracer) GetResult() (json.RawMessage, error) { + return json.RawMessage(`{}`), nil +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *noopTracer) Stop(err error) { +} diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go new file mode 100644 index 0000000..b353c06 --- /dev/null +++ b/eth/tracers/native/prestate.go @@ -0,0 +1,283 @@ +// 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 native + +import ( + "bytes" + "encoding/json" + "math/big" + "sync/atomic" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/tracing" + "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/internal" + "github.com/ethereum/go-ethereum/log" +) + +//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go + +func init() { + tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false) +} + +type stateMap = map[common.Address]*account + +type account struct { + Balance *big.Int `json:"balance,omitempty"` + Code []byte `json:"code,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Storage map[common.Hash]common.Hash `json:"storage,omitempty"` + empty bool +} + +func (a *account) exists() bool { + return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0) +} + +type accountMarshaling struct { + Balance *hexutil.Big + Code hexutil.Bytes +} + +type prestateTracer struct { + env *tracing.VMContext + pre stateMap + post stateMap + to common.Address + config prestateTracerConfig + interrupt atomic.Bool // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption + created map[common.Address]bool + deleted map[common.Address]bool +} + +type prestateTracerConfig struct { + DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications +} + +func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (*tracers.Tracer, error) { + var config prestateTracerConfig + if cfg != nil { + if err := json.Unmarshal(cfg, &config); err != nil { + return nil, err + } + } + t := &prestateTracer{ + pre: stateMap{}, + post: stateMap{}, + config: config, + created: make(map[common.Address]bool), + deleted: make(map[common.Address]bool), + } + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnTxStart: t.OnTxStart, + OnTxEnd: t.OnTxEnd, + OnOpcode: t.OnOpcode, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +// OnOpcode implements the EVMLogger interface to trace a single step of VM execution. +func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + if err != nil { + return + } + // Skip if tracing was interrupted + if t.interrupt.Load() { + return + } + op := vm.OpCode(opcode) + stackData := scope.StackData() + stackLen := len(stackData) + caller := scope.Address() + switch { + case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): + slot := common.Hash(stackData[stackLen-1].Bytes32()) + t.lookupStorage(caller, slot) + case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): + addr := common.Address(stackData[stackLen-1].Bytes20()) + t.lookupAccount(addr) + if op == vm.SELFDESTRUCT { + t.deleted[caller] = true + } + case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): + addr := common.Address(stackData[stackLen-2].Bytes20()) + t.lookupAccount(addr) + case op == vm.CREATE: + nonce := t.env.StateDB.GetNonce(caller) + addr := crypto.CreateAddress(caller, nonce) + t.lookupAccount(addr) + t.created[addr] = true + case stackLen >= 4 && op == vm.CREATE2: + offset := stackData[stackLen-2] + size := stackData[stackLen-3] + init, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(offset.Uint64()), int64(size.Uint64())) + if err != nil { + log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size) + return + } + inithash := crypto.Keccak256(init) + salt := stackData[stackLen-4] + addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) + t.lookupAccount(addr) + t.created[addr] = true + } +} + +func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + t.env = env + if tx.To() == nil { + t.to = crypto.CreateAddress(from, env.StateDB.GetNonce(from)) + t.created[t.to] = true + } else { + t.to = *tx.To() + } + + t.lookupAccount(from) + t.lookupAccount(t.to) + t.lookupAccount(env.Coinbase) +} + +func (t *prestateTracer) OnTxEnd(receipt *types.Receipt, err error) { + if err != nil { + return + } + if t.config.DiffMode { + t.processDiffState() + } + // the new created contracts' prestate were empty, so delete them + for a := range t.created { + // the created contract maybe exists in statedb before the creating tx + if s := t.pre[a]; s != nil && s.empty { + delete(t.pre, a) + } + } +} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *prestateTracer) GetResult() (json.RawMessage, error) { + var res []byte + var err error + if t.config.DiffMode { + res, err = json.Marshal(struct { + Post stateMap `json:"post"` + Pre stateMap `json:"pre"` + }{t.post, t.pre}) + } else { + res, err = json.Marshal(t.pre) + } + if err != nil { + return nil, err + } + return json.RawMessage(res), t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *prestateTracer) Stop(err error) { + t.reason = err + t.interrupt.Store(true) +} + +func (t *prestateTracer) processDiffState() { + for addr, state := range t.pre { + // The deleted account's state is pruned from `post` but kept in `pre` + if _, ok := t.deleted[addr]; ok { + continue + } + modified := false + postAccount := &account{Storage: make(map[common.Hash]common.Hash)} + newBalance := t.env.StateDB.GetBalance(addr).ToBig() + newNonce := t.env.StateDB.GetNonce(addr) + newCode := t.env.StateDB.GetCode(addr) + + if newBalance.Cmp(t.pre[addr].Balance) != 0 { + modified = true + postAccount.Balance = newBalance + } + if newNonce != t.pre[addr].Nonce { + modified = true + postAccount.Nonce = newNonce + } + if !bytes.Equal(newCode, t.pre[addr].Code) { + modified = true + postAccount.Code = newCode + } + + for key, val := range state.Storage { + // don't include the empty slot + if val == (common.Hash{}) { + delete(t.pre[addr].Storage, key) + } + + newVal := t.env.StateDB.GetState(addr, key) + if val == newVal { + // Omit unchanged slots + delete(t.pre[addr].Storage, key) + } else { + modified = true + if newVal != (common.Hash{}) { + postAccount.Storage[key] = newVal + } + } + } + + if modified { + t.post[addr] = postAccount + } else { + // if state is not modified, then no need to include into the pre state + delete(t.pre, addr) + } + } +} + +// lookupAccount fetches details of an account and adds it to the prestate +// if it doesn't exist there. +func (t *prestateTracer) lookupAccount(addr common.Address) { + if _, ok := t.pre[addr]; ok { + return + } + + acc := &account{ + Balance: t.env.StateDB.GetBalance(addr).ToBig(), + Nonce: t.env.StateDB.GetNonce(addr), + Code: t.env.StateDB.GetCode(addr), + Storage: make(map[common.Hash]common.Hash), + } + if !acc.exists() { + acc.empty = true + } + t.pre[addr] = acc +} + +// lookupStorage fetches the requested storage slot and adds +// it to the prestate of the given contract. It assumes `lookupAccount` +// has been performed on the contract before. +func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) { + if _, ok := t.pre[addr].Storage[key]; ok { + return + } + t.pre[addr].Storage[key] = t.env.StateDB.GetState(addr, key) +} diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go new file mode 100644 index 0000000..3cce7bf --- /dev/null +++ b/eth/tracers/tracers_test.go @@ -0,0 +1,113 @@ +// 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 tracers + +import ( + "math/big" + "testing" + + "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/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/tests" +) + +func BenchmarkTransactionTrace(b *testing.B) { + key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + from := crypto.PubkeyToAddress(key.PublicKey) + gas := uint64(1000000) // 1M gas + to := common.HexToAddress("0x00000000000000000000000000000000deadbeef") + signer := types.LatestSignerForChainID(big.NewInt(1337)) + tx, err := types.SignNewTx(key, signer, + &types.LegacyTx{ + Nonce: 1, + GasPrice: big.NewInt(500), + Gas: gas, + To: &to, + }) + if err != nil { + b.Fatal(err) + } + txContext := vm.TxContext{ + Origin: from, + GasPrice: tx.GasPrice(), + } + context := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: common.Address{}, + BlockNumber: new(big.Int).SetUint64(uint64(5)), + Time: 5, + Difficulty: big.NewInt(0xffffffff), + GasLimit: gas, + BaseFee: big.NewInt(8), + } + alloc := types.GenesisAlloc{} + // The code pushes 'deadbeef' into memory, then the other params, and calls CREATE2, then returns + // the address + loop := []byte{ + byte(vm.JUMPDEST), // [ count ] + byte(vm.PUSH1), 0, // jumpdestination + byte(vm.JUMP), + } + alloc[common.HexToAddress("0x00000000000000000000000000000000deadbeef")] = types.Account{ + Nonce: 1, + Code: loop, + Balance: big.NewInt(1), + } + alloc[from] = types.Account{ + Nonce: 1, + Code: []byte{}, + Balance: big.NewInt(500000000000000), + } + state := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false, rawdb.HashScheme) + defer state.Close() + + // Create the tracer, the EVM environment and run it + tracer := logger.NewStructLogger(&logger.Config{ + Debug: false, + //DisableStorage: true, + //EnableMemory: false, + //EnableReturnData: false, + }) + evm := vm.NewEVM(context, txContext, state.StateDB, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer.Hooks()}) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee) + if err != nil { + b.Fatalf("failed to prepare transaction for tracing: %v", err) + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + snap := state.StateDB.Snapshot() + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + _, err = st.TransitionDb() + if err != nil { + b.Fatal(err) + } + state.StateDB.RevertToSnapshot(snap) + if have, want := len(tracer.StructLogs()), 244752; have != want { + b.Fatalf("trace wrong, want %d steps, have %d", want, have) + } + tracer.Reset() + } +} diff --git a/eth/tracers/tracker.go b/eth/tracers/tracker.go new file mode 100644 index 0000000..136be37 --- /dev/null +++ b/eth/tracers/tracker.go @@ -0,0 +1,109 @@ +// 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 tracers + +import ( + "fmt" + "sync" +) + +// stateTracker is an auxiliary tool used to cache the release functions of all +// used trace states, and to determine whether the creation of trace state needs +// to be paused in case there are too many states waiting for tracing. +type stateTracker struct { + limit int // Maximum number of states allowed waiting for tracing + oldest uint64 // The number of the oldest state which is still using for trace + used []bool // List of flags indicating whether the trace state has been used up + releases []StateReleaseFunc // List of trace state release functions waiting to be called + cond *sync.Cond + lock *sync.RWMutex +} + +// newStateTracker initializes the tracker with provided state limits and +// the number of the first state that will be used. +func newStateTracker(limit int, oldest uint64) *stateTracker { + lock := new(sync.RWMutex) + return &stateTracker{ + limit: limit, + oldest: oldest, + used: make([]bool, limit), + cond: sync.NewCond(lock), + lock: lock, + } +} + +// releaseState marks the state specified by the number as released and caches +// the corresponding release functions internally. +func (t *stateTracker) releaseState(number uint64, release StateReleaseFunc) { + t.lock.Lock() + defer t.lock.Unlock() + + // Set the state as used, the corresponding flag is indexed by + // the distance between the specified state and the oldest state + // which is still using for trace. + t.used[int(number-t.oldest)] = true + + // If the oldest state is used up, update the oldest marker by moving + // it to the next state which is not used up. + if number == t.oldest { + var count int + for _, used := range t.used { + if !used { + break + } + count += 1 + } + t.oldest += uint64(count) + copy(t.used, t.used[count:]) + + // Clean up the array tail since they are useless now. + for i := t.limit - count; i < t.limit; i++ { + t.used[i] = false + } + // Fire the signal to all waiters that oldest marker is updated. + t.cond.Broadcast() + } + t.releases = append(t.releases, release) +} + +// callReleases invokes all cached release functions. +func (t *stateTracker) callReleases() { + t.lock.Lock() + defer t.lock.Unlock() + + for _, release := range t.releases { + release() + } + t.releases = t.releases[:0] +} + +// wait blocks until the accumulated trace states are less than the limit. +func (t *stateTracker) wait(number uint64) error { + t.lock.Lock() + defer t.lock.Unlock() + + for { + if number < t.oldest { + return fmt.Errorf("invalid state number %d head %d", number, t.oldest) + } + if number < t.oldest+uint64(t.limit) { + // number is now within limit, wait over + return nil + } + t.cond.Wait() + } +} diff --git a/eth/tracers/tracker_test.go b/eth/tracers/tracker_test.go new file mode 100644 index 0000000..46f6ac8 --- /dev/null +++ b/eth/tracers/tracker_test.go @@ -0,0 +1,171 @@ +// 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 tracers + +import ( + "reflect" + "testing" + "time" +) + +func TestTracker(t *testing.T) { + var cases = []struct { + limit int + calls []uint64 + expHead uint64 + }{ + // Release in order + { + limit: 3, + calls: []uint64{0, 1, 2}, + expHead: 3, + }, + { + limit: 3, + calls: []uint64{0, 1, 2, 3, 4, 5}, + expHead: 6, + }, + + // Release out of order + { + limit: 3, + calls: []uint64{1, 2, 0}, + expHead: 3, + }, + { + limit: 3, + calls: []uint64{1, 2, 0, 5, 4, 3}, + expHead: 6, + }, + } + for _, c := range cases { + tracker := newStateTracker(c.limit, 0) + for _, call := range c.calls { + tracker.releaseState(call, func() {}) + } + tracker.lock.RLock() + head := tracker.oldest + tracker.lock.RUnlock() + + if head != c.expHead { + t.Fatalf("Unexpected head want %d got %d", c.expHead, head) + } + } + + var calls = []struct { + number uint64 + expUsed []bool + expHead uint64 + }{ + // Release the first one, update the oldest flag + { + number: 0, + expUsed: []bool{false, false, false, false, false}, + expHead: 1, + }, + // Release the second one, oldest shouldn't be updated + { + number: 2, + expUsed: []bool{false, true, false, false, false}, + expHead: 1, + }, + // Release the forth one, oldest shouldn't be updated + { + number: 4, + expUsed: []bool{false, true, false, true, false}, + expHead: 1, + }, + // Release the first one, the first two should all be cleaned, + // and the remaining flags should all be left-shifted. + { + number: 1, + expUsed: []bool{false, true, false, false, false}, + expHead: 3, + }, + // Release the first one, the first two should all be cleaned + { + number: 3, + expUsed: []bool{false, false, false, false, false}, + expHead: 5, + }, + } + tracker := newStateTracker(5, 0) // limit = 5, oldest = 0 + for _, call := range calls { + tracker.releaseState(call.number, nil) + tracker.lock.RLock() + if !reflect.DeepEqual(tracker.used, call.expUsed) { + t.Fatalf("Unexpected used array") + } + if tracker.oldest != call.expHead { + t.Fatalf("Unexpected head") + } + tracker.lock.RUnlock() + } +} + +func TestTrackerWait(t *testing.T) { + var ( + tracker = newStateTracker(5, 0) // limit = 5, oldest = 0 + result = make(chan error, 1) + doCall = func(number uint64) { + go func() { + result <- tracker.wait(number) + }() + } + checkNoWait = func() { + select { + case <-result: + return + case <-time.NewTimer(time.Second).C: + t.Fatal("No signal fired") + } + } + checkWait = func() { + select { + case <-result: + t.Fatal("Unexpected signal") + case <-time.NewTimer(time.Millisecond * 100).C: + } + } + ) + // States [0, 5) should all be available + doCall(0) + checkNoWait() + + doCall(4) + checkNoWait() + + // State 5 is not available + doCall(5) + checkWait() + + // States [1, 6) are available + tracker.releaseState(0, nil) + checkNoWait() + + // States [1, 6) are available + doCall(7) + checkWait() + + // States [2, 7) are available + tracker.releaseState(1, nil) + checkWait() + + // States [3, 8) are available + tracker.releaseState(2, nil) + checkNoWait() +} diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go new file mode 100644 index 0000000..390f085 --- /dev/null +++ b/ethclient/ethclient.go @@ -0,0 +1,732 @@ +// 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 ethclient provides a client for the Ethereum RPC API. +package ethclient + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "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/rpc" +) + +// Client defines typed wrappers for the Ethereum RPC API. +type Client struct { + c *rpc.Client +} + +// Dial connects a client to the given URL. +func Dial(rawurl string) (*Client, error) { + return DialContext(context.Background(), rawurl) +} + +// DialContext connects a client to the given URL with context. +func DialContext(ctx context.Context, rawurl string) (*Client, error) { + c, err := rpc.DialContext(ctx, rawurl) + if err != nil { + return nil, err + } + return NewClient(c), nil +} + +// NewClient creates a client that uses the given RPC client. +func NewClient(c *rpc.Client) *Client { + return &Client{c} +} + +// Close closes the underlying RPC connection. +func (ec *Client) Close() { + ec.c.Close() +} + +// Client gets the underlying RPC client. +func (ec *Client) Client() *rpc.Client { + return ec.c +} + +// Blockchain Access + +// ChainID retrieves the current chain ID for transaction replay protection. +func (ec *Client) ChainID(ctx context.Context) (*big.Int, error) { + var result hexutil.Big + err := ec.c.CallContext(ctx, &result, "eth_chainId") + if err != nil { + return nil, err + } + return (*big.Int)(&result), err +} + +// BlockByHash returns the given full block. +// +// Note that loading full blocks requires two requests. Use HeaderByHash +// if you don't need all transactions or uncle headers. +func (ec *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return ec.getBlock(ctx, "eth_getBlockByHash", hash, true) +} + +// BlockByNumber returns a block from the current canonical chain. If number is nil, the +// latest known block is returned. +// +// Note that loading full blocks requires two requests. Use HeaderByNumber +// if you don't need all transactions or uncle headers. +func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + return ec.getBlock(ctx, "eth_getBlockByNumber", toBlockNumArg(number), true) +} + +// BlockNumber returns the most recent block number +func (ec *Client) BlockNumber(ctx context.Context) (uint64, error) { + var result hexutil.Uint64 + err := ec.c.CallContext(ctx, &result, "eth_blockNumber") + return uint64(result), err +} + +// PeerCount returns the number of p2p peers as reported by the net_peerCount method. +func (ec *Client) PeerCount(ctx context.Context) (uint64, error) { + var result hexutil.Uint64 + err := ec.c.CallContext(ctx, &result, "net_peerCount") + 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"` + UncleHashes []common.Hash `json:"uncles"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` +} + +func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { + var raw json.RawMessage + err := ec.c.CallContext(ctx, &raw, method, args...) + if err != nil { + return nil, err + } + + // Decode header and transactions. + var head *types.Header + if err := json.Unmarshal(raw, &head); err != nil { + return nil, err + } + // When the block is not found, the API returns JSON null. + if head == nil { + return nil, ethereum.NotFound + } + + var body rpcBlock + if err := json.Unmarshal(raw, &body); err != nil { + return nil, err + } + // Quick-verify transaction and uncle lists. This mostly helps with debugging the server. + if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 { + return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles") + } + if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 { + return nil, errors.New("server returned empty uncle list but block header indicates uncles") + } + if head.TxHash == types.EmptyTxsHash && len(body.Transactions) > 0 { + return nil, errors.New("server returned non-empty transaction list but block header indicates no transactions") + } + if head.TxHash != types.EmptyTxsHash && len(body.Transactions) == 0 { + return nil, errors.New("server returned empty transaction list but block header indicates transactions") + } + // Load uncles because they are not included in the block response. + var uncles []*types.Header + if len(body.UncleHashes) > 0 { + uncles = make([]*types.Header, len(body.UncleHashes)) + reqs := make([]rpc.BatchElem, len(body.UncleHashes)) + for i := range reqs { + reqs[i] = rpc.BatchElem{ + Method: "eth_getUncleByBlockHashAndIndex", + Args: []interface{}{body.Hash, hexutil.EncodeUint64(uint64(i))}, + Result: &uncles[i], + } + } + if err := ec.c.BatchCallContext(ctx, reqs); err != nil { + return nil, err + } + for i := range reqs { + if reqs[i].Error != nil { + return nil, reqs[i].Error + } + if uncles[i] == nil { + return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:]) + } + } + } + // Fill the sender cache of transactions in the block. + txs := make([]*types.Transaction, len(body.Transactions)) + for i, tx := range body.Transactions { + if tx.From != nil { + setSenderFromServer(tx.tx, *tx.From, body.Hash) + } + txs[i] = tx.tx + } + return types.NewBlockWithHeader(head).WithBody( + types.Body{ + Transactions: txs, + Uncles: uncles, + Withdrawals: body.Withdrawals, + }), nil +} + +// HeaderByHash returns the block header with the given hash. +func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + var head *types.Header + err := ec.c.CallContext(ctx, &head, "eth_getBlockByHash", hash, false) + if err == nil && head == nil { + err = ethereum.NotFound + } + return head, err +} + +// HeaderByNumber returns a block header from the current canonical chain. If number is +// nil, the latest known header is returned. +func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + var head *types.Header + err := ec.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) + if err == nil && head == nil { + err = ethereum.NotFound + } + return head, err +} + +type rpcTransaction struct { + tx *types.Transaction + txExtraInfo +} + +type txExtraInfo struct { + BlockNumber *string `json:"blockNumber,omitempty"` + BlockHash *common.Hash `json:"blockHash,omitempty"` + From *common.Address `json:"from,omitempty"` +} + +func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error { + if err := json.Unmarshal(msg, &tx.tx); err != nil { + return err + } + return json.Unmarshal(msg, &tx.txExtraInfo) +} + +// TransactionByHash returns the transaction with the given hash. +func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { + var json *rpcTransaction + err = ec.c.CallContext(ctx, &json, "eth_getTransactionByHash", hash) + if err != nil { + return nil, false, err + } else if json == nil { + return nil, false, ethereum.NotFound + } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { + return nil, false, errors.New("server returned transaction without signature") + } + if json.From != nil && json.BlockHash != nil { + setSenderFromServer(json.tx, *json.From, *json.BlockHash) + } + return json.tx, json.BlockNumber == nil, nil +} + +// TransactionSender returns the sender address of the given transaction. The transaction +// must be known to the remote node and included in the blockchain at the given block and +// index. The sender is the one derived by the protocol at the time of inclusion. +// +// There is a fast-path for transactions retrieved by TransactionByHash and +// TransactionInBlock. Getting their sender address can be done without an RPC interaction. +func (ec *Client) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { + // Try to load the address from the cache. + sender, err := types.Sender(&senderFromServer{blockhash: block}, tx) + if err == nil { + return sender, nil + } + + // It was not found in cache, ask the server. + var meta struct { + Hash common.Hash + From common.Address + } + if err = ec.c.CallContext(ctx, &meta, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index)); err != nil { + return common.Address{}, err + } + if meta.Hash == (common.Hash{}) || meta.Hash != tx.Hash() { + return common.Address{}, errors.New("wrong inclusion block/index") + } + return meta.From, nil +} + +// TransactionCount returns the total number of transactions in the given block. +func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { + var num hexutil.Uint + err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByHash", blockHash) + return uint(num), err +} + +// TransactionInBlock returns a single transaction at index in the given block. +func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { + var json *rpcTransaction + err := ec.c.CallContext(ctx, &json, "eth_getTransactionByBlockHashAndIndex", blockHash, hexutil.Uint64(index)) + if err != nil { + return nil, err + } + if json == nil { + return nil, ethereum.NotFound + } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { + return nil, errors.New("server returned transaction without signature") + } + if json.From != nil && json.BlockHash != nil { + setSenderFromServer(json.tx, *json.From, *json.BlockHash) + } + return json.tx, err +} + +// TransactionReceipt returns the receipt of a transaction by transaction hash. +// Note that the receipt is not available for pending transactions. +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 && r == nil { + return nil, ethereum.NotFound + } + return r, err +} + +// SyncProgress retrieves the current progress of the sync algorithm. If there's +// no sync currently running, it returns nil. +func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { + var raw json.RawMessage + if err := ec.c.CallContext(ctx, &raw, "eth_syncing"); err != nil { + return nil, err + } + // Handle the possible response types + var syncing bool + if err := json.Unmarshal(raw, &syncing); err == nil { + return nil, nil // Not syncing (always false) + } + var p *rpcProgress + if err := json.Unmarshal(raw, &p); err != nil { + return nil, err + } + return p.toSyncProgress(), nil +} + +// SubscribeNewHead subscribes to notifications about the current blockchain head +// on the given channel. +func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + sub, err := ec.c.EthSubscribe(ctx, ch, "newHeads") + if err != nil { + // Defensively prefer returning nil interface explicitly on error-path, instead + // of letting default golang behavior wrap it with non-nil interface that stores + // nil concrete type value. + return nil, err + } + return sub, nil +} + +// State Access + +// NetworkID returns the network ID for this client. +func (ec *Client) NetworkID(ctx context.Context) (*big.Int, error) { + version := new(big.Int) + var ver string + if err := ec.c.CallContext(ctx, &ver, "net_version"); err != nil { + return nil, err + } + if _, ok := version.SetString(ver, 10); !ok { + return nil, fmt.Errorf("invalid net_version result %q", ver) + } + return version, nil +} + +// BalanceAt returns the wei balance of the given account. +// The block number can be nil, in which case the balance is taken from the latest known block. +func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { + var result hexutil.Big + err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, toBlockNumArg(blockNumber)) + 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) { + var result hexutil.Bytes + err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, toBlockNumArg(blockNumber)) + 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) { + var result hexutil.Bytes + err := ec.c.CallContext(ctx, &result, "eth_getCode", account, toBlockNumArg(blockNumber)) + 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) { + var result hexutil.Uint64 + err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, toBlockNumArg(blockNumber)) + 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. +func (ec *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + var result []types.Log + arg, err := toFilterArg(q) + if err != nil { + return nil, err + } + err = ec.c.CallContext(ctx, &result, "eth_getLogs", arg) + return result, err +} + +// SubscribeFilterLogs subscribes to the results of a streaming filter query. +func (ec *Client) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + arg, err := toFilterArg(q) + if err != nil { + return nil, err + } + sub, err := ec.c.EthSubscribe(ctx, ch, "logs", arg) + if err != nil { + // Defensively prefer returning nil interface explicitly on error-path, instead + // of letting default golang behavior wrap it with non-nil interface that stores + // nil concrete type value. + return nil, err + } + return sub, nil +} + +func toFilterArg(q ethereum.FilterQuery) (interface{}, error) { + arg := map[string]interface{}{ + "address": q.Addresses, + "topics": q.Topics, + } + if q.BlockHash != nil { + arg["blockHash"] = *q.BlockHash + if q.FromBlock != nil || q.ToBlock != nil { + return nil, errors.New("cannot specify both BlockHash and FromBlock/ToBlock") + } + } else { + if q.FromBlock == nil { + arg["fromBlock"] = "0x0" + } else { + arg["fromBlock"] = toBlockNumArg(q.FromBlock) + } + arg["toBlock"] = toBlockNumArg(q.ToBlock) + } + return arg, nil +} + +// Pending State + +// PendingBalanceAt returns the wei balance of the given account in the pending state. +func (ec *Client) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { + var result hexutil.Big + err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, "pending") + return (*big.Int)(&result), err +} + +// PendingStorageAt returns the value of key in the contract storage of the given account in the pending state. +func (ec *Client) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { + var result hexutil.Bytes + err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, "pending") + return result, err +} + +// PendingCodeAt returns the contract code of the given account in the pending state. +func (ec *Client) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + var result hexutil.Bytes + err := ec.c.CallContext(ctx, &result, "eth_getCode", account, "pending") + return result, err +} + +// PendingNonceAt returns the account nonce of the given account in the pending state. +// This is the nonce that should be used for the next transaction. +func (ec *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + var result hexutil.Uint64 + err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, "pending") + return uint64(result), err +} + +// PendingTransactionCount returns the total number of transactions in the pending state. +func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) { + var num hexutil.Uint + err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByNumber", "pending") + return uint(num), err +} + +// Contract Calling + +// CallContract executes a message call transaction, which is directly executed in the VM +// of the node, but never mined into the blockchain. +// +// blockNumber selects the block height at which the call runs. It can be nil, in which +// case the code is taken from the latest known block. Note that state from very old +// blocks might not be available. +func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + var hex hexutil.Bytes + err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber)) + if err != nil { + return nil, err + } + return hex, nil +} + +// CallContractAtHash is almost the same as CallContract except that it selects +// the block by block hash instead of block height. +func (ec *Client) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { + var hex hexutil.Bytes + err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), rpc.BlockNumberOrHashWithHash(blockHash, false)) + if err != nil { + return nil, err + } + return hex, nil +} + +// PendingCallContract executes a message call transaction using the EVM. +// The state seen by the contract call is the pending state. +func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + var hex hexutil.Bytes + err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "pending") + if err != nil { + return nil, err + } + return hex, nil +} + +// SuggestGasPrice retrieves the currently suggested gas price to allow a timely +// execution of a transaction. +func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + var hex hexutil.Big + if err := ec.c.CallContext(ctx, &hex, "eth_gasPrice"); err != nil { + return nil, err + } + return (*big.Int)(&hex), nil +} + +// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to +// allow a timely execution of a transaction. +func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + var hex hexutil.Big + if err := ec.c.CallContext(ctx, &hex, "eth_maxPriorityFeePerGas"); err != nil { + return nil, err + } + return (*big.Int)(&hex), nil +} + +type feeHistoryResultMarshaling struct { + OldestBlock *hexutil.Big `json:"oldestBlock"` + Reward [][]*hexutil.Big `json:"reward,omitempty"` + BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"` + GasUsedRatio []float64 `json:"gasUsedRatio"` +} + +// FeeHistory retrieves the fee market history. +func (ec *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + var res feeHistoryResultMarshaling + if err := ec.c.CallContext(ctx, &res, "eth_feeHistory", hexutil.Uint(blockCount), toBlockNumArg(lastBlock), rewardPercentiles); err != nil { + return nil, err + } + reward := make([][]*big.Int, len(res.Reward)) + for i, r := range res.Reward { + reward[i] = make([]*big.Int, len(r)) + for j, r := range r { + reward[i][j] = (*big.Int)(r) + } + } + baseFee := make([]*big.Int, len(res.BaseFee)) + for i, b := range res.BaseFee { + baseFee[i] = (*big.Int)(b) + } + return ðereum.FeeHistory{ + OldestBlock: (*big.Int)(res.OldestBlock), + Reward: reward, + BaseFee: baseFee, + GasUsedRatio: res.GasUsedRatio, + }, nil +} + +// 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. +func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { + var hex hexutil.Uint64 + err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg)) + if err != nil { + return 0, err + } + return uint64(hex), nil +} + +// SendTransaction injects a signed transaction into the pending pool for execution. +// +// If the transaction was a contract creation use the TransactionReceipt method to get the +// contract address after the transaction has been mined. +func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { + data, err := tx.MarshalBinary() + if err != nil { + return err + } + return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) +} + +func toBlockNumArg(number *big.Int) string { + if number == nil { + return "latest" + } + if number.Sign() >= 0 { + return hexutil.EncodeBig(number) + } + // It's negative. + if number.IsInt64() { + return rpc.BlockNumber(number.Int64()).String() + } + // It's negative and large, which is invalid. + return fmt.Sprintf("", number) +} + +func toCallArg(msg ethereum.CallMsg) interface{} { + arg := map[string]interface{}{ + "from": msg.From, + "to": msg.To, + } + if len(msg.Data) > 0 { + arg["input"] = hexutil.Bytes(msg.Data) + } + if msg.Value != nil { + arg["value"] = (*hexutil.Big)(msg.Value) + } + if msg.Gas != 0 { + arg["gas"] = hexutil.Uint64(msg.Gas) + } + 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) + } + if msg.AccessList != nil { + arg["accessList"] = msg.AccessList + } + if msg.BlobGasFeeCap != nil { + arg["maxFeePerBlobGas"] = (*hexutil.Big)(msg.BlobGasFeeCap) + } + if msg.BlobHashes != nil { + arg["blobVersionedHashes"] = msg.BlobHashes + } + return arg +} + +// rpcProgress is a copy of SyncProgress with hex-encoded fields. +type rpcProgress struct { + StartingBlock hexutil.Uint64 + CurrentBlock hexutil.Uint64 + HighestBlock hexutil.Uint64 + + PulledStates hexutil.Uint64 + KnownStates hexutil.Uint64 + + SyncedAccounts hexutil.Uint64 + SyncedAccountBytes hexutil.Uint64 + SyncedBytecodes hexutil.Uint64 + SyncedBytecodeBytes hexutil.Uint64 + SyncedStorage hexutil.Uint64 + SyncedStorageBytes hexutil.Uint64 + HealedTrienodes hexutil.Uint64 + HealedTrienodeBytes hexutil.Uint64 + HealedBytecodes hexutil.Uint64 + HealedBytecodeBytes hexutil.Uint64 + HealingTrienodes hexutil.Uint64 + HealingBytecode hexutil.Uint64 + TxIndexFinishedBlocks hexutil.Uint64 + TxIndexRemainingBlocks hexutil.Uint64 +} + +func (p *rpcProgress) toSyncProgress() *ethereum.SyncProgress { + if p == nil { + return nil + } + return ðereum.SyncProgress{ + StartingBlock: uint64(p.StartingBlock), + CurrentBlock: uint64(p.CurrentBlock), + HighestBlock: uint64(p.HighestBlock), + PulledStates: uint64(p.PulledStates), + KnownStates: uint64(p.KnownStates), + SyncedAccounts: uint64(p.SyncedAccounts), + SyncedAccountBytes: uint64(p.SyncedAccountBytes), + SyncedBytecodes: uint64(p.SyncedBytecodes), + SyncedBytecodeBytes: uint64(p.SyncedBytecodeBytes), + SyncedStorage: uint64(p.SyncedStorage), + SyncedStorageBytes: uint64(p.SyncedStorageBytes), + HealedTrienodes: uint64(p.HealedTrienodes), + HealedTrienodeBytes: uint64(p.HealedTrienodeBytes), + HealedBytecodes: uint64(p.HealedBytecodes), + HealedBytecodeBytes: uint64(p.HealedBytecodeBytes), + HealingTrienodes: uint64(p.HealingTrienodes), + HealingBytecode: uint64(p.HealingBytecode), + TxIndexFinishedBlocks: uint64(p.TxIndexFinishedBlocks), + TxIndexRemainingBlocks: uint64(p.TxIndexRemainingBlocks), + } +} diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go new file mode 100644 index 0000000..2f3229c --- /dev/null +++ b/ethclient/ethclient_test.go @@ -0,0 +1,762 @@ +// 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 ethclient + +import ( + "bytes" + "context" + "errors" + "math/big" + "reflect" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +// Verify that Client implements the ethereum interfaces. +var ( + _ = ethereum.ChainReader(&Client{}) + _ = ethereum.TransactionReader(&Client{}) + _ = ethereum.ChainStateReader(&Client{}) + _ = ethereum.ChainSyncReader(&Client{}) + _ = ethereum.ContractCaller(&Client{}) + _ = ethereum.GasEstimator(&Client{}) + _ = ethereum.GasPricer(&Client{}) + _ = ethereum.LogFilterer(&Client{}) + _ = ethereum.PendingStateReader(&Client{}) + // _ = ethereum.PendingStateEventer(&Client{}) + _ = ethereum.PendingContractCaller(&Client{}) +) + +func TestToFilterArg(t *testing.T) { + blockHashErr := errors.New("cannot specify both BlockHash and FromBlock/ToBlock") + addresses := []common.Address{ + common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462"), + } + blockHash := common.HexToHash( + "0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb", + ) + + for _, testCase := range []struct { + name string + input ethereum.FilterQuery + output interface{} + err error + }{ + { + "without BlockHash", + ethereum.FilterQuery{ + Addresses: addresses, + FromBlock: big.NewInt(1), + ToBlock: big.NewInt(2), + Topics: [][]common.Hash{}, + }, + map[string]interface{}{ + "address": addresses, + "fromBlock": "0x1", + "toBlock": "0x2", + "topics": [][]common.Hash{}, + }, + nil, + }, + { + "with nil fromBlock and nil toBlock", + ethereum.FilterQuery{ + Addresses: addresses, + Topics: [][]common.Hash{}, + }, + map[string]interface{}{ + "address": addresses, + "fromBlock": "0x0", + "toBlock": "latest", + "topics": [][]common.Hash{}, + }, + nil, + }, + { + "with negative fromBlock and negative toBlock", + ethereum.FilterQuery{ + Addresses: addresses, + FromBlock: big.NewInt(-1), + ToBlock: big.NewInt(-1), + Topics: [][]common.Hash{}, + }, + map[string]interface{}{ + "address": addresses, + "fromBlock": "pending", + "toBlock": "pending", + "topics": [][]common.Hash{}, + }, + nil, + }, + { + "with blockhash", + ethereum.FilterQuery{ + Addresses: addresses, + BlockHash: &blockHash, + Topics: [][]common.Hash{}, + }, + map[string]interface{}{ + "address": addresses, + "blockHash": blockHash, + "topics": [][]common.Hash{}, + }, + nil, + }, + { + "with blockhash and from block", + ethereum.FilterQuery{ + Addresses: addresses, + BlockHash: &blockHash, + FromBlock: big.NewInt(1), + Topics: [][]common.Hash{}, + }, + nil, + blockHashErr, + }, + { + "with blockhash and to block", + ethereum.FilterQuery{ + Addresses: addresses, + BlockHash: &blockHash, + ToBlock: big.NewInt(1), + Topics: [][]common.Hash{}, + }, + nil, + blockHashErr, + }, + { + "with blockhash and both from / to block", + ethereum.FilterQuery{ + Addresses: addresses, + BlockHash: &blockHash, + FromBlock: big.NewInt(1), + ToBlock: big.NewInt(2), + Topics: [][]common.Hash{}, + }, + nil, + blockHashErr, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + output, err := toFilterArg(testCase.input) + if (testCase.err == nil) != (err == nil) { + t.Fatalf("expected error %v but got %v", testCase.err, err) + } + if testCase.err != nil { + if testCase.err.Error() != err.Error() { + t.Fatalf("expected error %v but got %v", testCase.err, err) + } + } else if !reflect.DeepEqual(testCase.output, output) { + t.Fatalf("expected filter arg %v but got %v", testCase.output, output) + } + }) + } +} + +var ( + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + testBalance = big.NewInt(2e15) +) + +var genesis = &core.Genesis{ + Config: params.AllEthashProtocolChanges, + Alloc: types.GenesisAlloc{testAddr: {Balance: testBalance}}, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + BaseFee: big.NewInt(params.InitialBaseFee), +} + +var testTx1 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{ + Nonce: 0, + Value: big.NewInt(12), + GasPrice: big.NewInt(params.InitialBaseFee), + Gas: params.TxGas, + To: &common.Address{2}, +}) + +var testTx2 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{ + Nonce: 1, + Value: big.NewInt(8), + GasPrice: big.NewInt(params.InitialBaseFee), + Gas: params.TxGas, + To: &common.Address{2}, +}) + +func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { + // Generate test chain. + blocks := generateTestChain() + + // Create node + n, err := node.New(&node.Config{}) + if err != nil { + t.Fatalf("can't create new node: %v", err) + } + // Create Ethereum Service + config := ðconfig.Config{Genesis: genesis} + ethservice, err := eth.New(n, config) + if err != nil { + t.Fatalf("can't create new ethereum service: %v", err) + } + // Import the test chain. + if err := n.Start(); err != nil { + t.Fatalf("can't start test node: %v", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil { + t.Fatalf("can't import test blocks: %v", err) + } + // Ensure the tx indexing is fully generated + for ; ; time.Sleep(time.Millisecond * 100) { + progress, err := ethservice.BlockChain().TxIndexProgress() + if err == nil && progress.Done() { + break + } + } + return n, blocks +} + +func generateTestChain() []*types.Block { + generate := func(i int, g *core.BlockGen) { + g.OffsetTime(5) + g.SetExtra([]byte("test")) + if i == 1 { + // Test transactions are included in block #2. + g.AddTx(testTx1) + g.AddTx(testTx2) + } + } + _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 2, generate) + return append([]*types.Block{genesis.ToBlock()}, blocks...) +} + +func TestEthClient(t *testing.T) { + backend, chain := newTestBackend(t) + client := backend.Attach() + defer backend.Close() + defer client.Close() + + tests := map[string]struct { + test func(t *testing.T) + }{ + "Header": { + func(t *testing.T) { testHeader(t, chain, client) }, + }, + "BalanceAt": { + func(t *testing.T) { testBalanceAt(t, client) }, + }, + "TxInBlockInterrupted": { + func(t *testing.T) { testTransactionInBlock(t, client) }, + }, + "ChainID": { + func(t *testing.T) { testChainID(t, client) }, + }, + "GetBlock": { + func(t *testing.T) { testGetBlock(t, client) }, + }, + "StatusFunctions": { + func(t *testing.T) { testStatusFunctions(t, client) }, + }, + "CallContract": { + func(t *testing.T) { testCallContract(t, client) }, + }, + "CallContractAtHash": { + func(t *testing.T) { testCallContractAtHash(t, client) }, + }, + "AtFunctions": { + func(t *testing.T) { testAtFunctions(t, client) }, + }, + "TransactionSender": { + func(t *testing.T) { testTransactionSender(t, client) }, + }, + } + + t.Parallel() + for name, tt := range tests { + t.Run(name, tt.test) + } +} + +func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { + tests := map[string]struct { + block *big.Int + want *types.Header + wantErr error + }{ + "genesis": { + block: big.NewInt(0), + want: chain[0].Header(), + }, + "first_block": { + block: big.NewInt(1), + want: chain[1].Header(), + }, + "future_block": { + block: big.NewInt(1000000000), + want: nil, + wantErr: ethereum.NotFound, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + ec := NewClient(client) + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + got, err := ec.HeaderByNumber(ctx, tt.block) + if !errors.Is(err, tt.wantErr) { + t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr) + } + if got != nil && got.Number != nil && got.Number.Sign() == 0 { + got.Number = big.NewInt(0) // hack to make DeepEqual work + } + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("HeaderByNumber(%v) got = %v, want %v", tt.block, got, tt.want) + } + }) + } +} + +func testBalanceAt(t *testing.T, client *rpc.Client) { + tests := map[string]struct { + account common.Address + block *big.Int + want *big.Int + wantErr error + }{ + "valid_account_genesis": { + account: testAddr, + block: big.NewInt(0), + want: testBalance, + }, + "valid_account": { + account: testAddr, + block: big.NewInt(1), + want: testBalance, + }, + "non_existent_account": { + account: common.Address{1}, + block: big.NewInt(1), + want: big.NewInt(0), + }, + "future_block": { + account: testAddr, + block: big.NewInt(1000000000), + want: big.NewInt(0), + wantErr: errors.New("header not found"), + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + ec := NewClient(client) + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + got, err := ec.BalanceAt(ctx, tt.account, tt.block) + if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) { + t.Fatalf("BalanceAt(%x, %v) error = %q, want %q", tt.account, tt.block, err, tt.wantErr) + } + if got.Cmp(tt.want) != 0 { + t.Fatalf("BalanceAt(%x, %v) = %v, want %v", tt.account, tt.block, got, tt.want) + } + }) + } +} + +func testTransactionInBlock(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // Get current block by number. + block, err := ec.BlockByNumber(context.Background(), nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Test tx in block not found. + if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 20); err != ethereum.NotFound { + t.Fatal("error should be ethereum.NotFound") + } + + // Test tx in block found. + tx, err := ec.TransactionInBlock(context.Background(), block.Hash(), 0) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tx.Hash() != testTx1.Hash() { + t.Fatalf("unexpected transaction: %v", tx) + } + + tx, err = ec.TransactionInBlock(context.Background(), block.Hash(), 1) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tx.Hash() != testTx2.Hash() { + t.Fatalf("unexpected transaction: %v", tx) + } +} + +func testChainID(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + id, err := ec.ChainID(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if id == nil || id.Cmp(params.AllEthashProtocolChanges.ChainID) != 0 { + t.Fatalf("ChainID returned wrong number: %+v", id) + } +} + +func testGetBlock(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // Get current block number + blockNumber, err := ec.BlockNumber(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if blockNumber != 2 { + t.Fatalf("BlockNumber returned wrong number: %d", blockNumber) + } + // Get current block by number + block, err := ec.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.NumberU64() != blockNumber { + t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64()) + } + // Get current block by hash + blockH, err := ec.BlockByHash(context.Background(), block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Hash() != blockH.Hash() { + t.Fatalf("BlockByHash returned wrong block: want %v got %v", block.Hash().Hex(), blockH.Hash().Hex()) + } + // Get header by number + header, err := ec.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Header().Hash() != header.Hash() { + t.Fatalf("HeaderByNumber returned wrong header: want %v got %v", block.Header().Hash().Hex(), header.Hash().Hex()) + } + // Get header by hash + headerH, err := ec.HeaderByHash(context.Background(), block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Header().Hash() != headerH.Hash() { + t.Fatalf("HeaderByHash returned wrong header: want %v got %v", block.Header().Hash().Hex(), headerH.Hash().Hex()) + } +} + +func testStatusFunctions(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // Sync progress + progress, err := ec.SyncProgress(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if progress != nil { + t.Fatalf("unexpected progress: %v", progress) + } + + // NetworkID + networkID, err := ec.NetworkID(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if networkID.Cmp(big.NewInt(1337)) != 0 { + t.Fatalf("unexpected networkID: %v", networkID) + } + + // SuggestGasPrice + gasPrice, err := ec.SuggestGasPrice(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gasPrice.Cmp(big.NewInt(1000000000)) != 0 { + t.Fatalf("unexpected gas price: %v", gasPrice) + } + + // SuggestGasTipCap + gasTipCap, err := ec.SuggestGasTipCap(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gasTipCap.Cmp(big.NewInt(234375000)) != 0 { + t.Fatalf("unexpected gas tip cap: %v", gasTipCap) + } + + // FeeHistory + history, err := ec.FeeHistory(context.Background(), 1, big.NewInt(2), []float64{95, 99}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := ðereum.FeeHistory{ + OldestBlock: big.NewInt(2), + Reward: [][]*big.Int{ + { + big.NewInt(234375000), + big.NewInt(234375000), + }, + }, + BaseFee: []*big.Int{ + big.NewInt(765625000), + big.NewInt(671627818), + }, + GasUsedRatio: []float64{0.008912678667376286}, + } + if !reflect.DeepEqual(history, want) { + t.Fatalf("FeeHistory result doesn't match expected: (got: %v, want: %v)", history, want) + } +} + +func testCallContractAtHash(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // EstimateGas + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + Value: big.NewInt(1), + } + gas, err := ec.EstimateGas(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gas != 21000 { + t.Fatalf("unexpected gas price: %v", gas) + } + block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1)) + if err != nil { + t.Fatalf("BlockByNumber error: %v", err) + } + // CallContract + if _, err := ec.CallContractAtHash(context.Background(), msg, block.Hash()); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func testCallContract(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // EstimateGas + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + Value: big.NewInt(1), + } + gas, err := ec.EstimateGas(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gas != 21000 { + t.Fatalf("unexpected gas price: %v", gas) + } + // CallContract + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil { + t.Fatalf("unexpected error: %v", err) + } + // PendingCallContract + if _, err := ec.PendingCallContract(context.Background(), msg); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +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 + // and wait for the transaction to be included in the pending block + sendTransaction(ec) + + // wait for the transaction to be included in the pending block + for { + // Check pending transaction count + pending, err := ec.PendingTransactionCount(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if pending == 1 { + break + } + time.Sleep(100 * time.Millisecond) + } + + // Query balance + balance, err := ec.BalanceAt(context.Background(), testAddr, nil) + 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) + } + if balance.Cmp(penBalance) == 0 { + t.Fatalf("unexpected balance: %v %v", balance, penBalance) + } + // NonceAt + nonce, err := ec.NonceAt(context.Background(), testAddr, nil) + 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) + } + if penNonce != nonce+1 { + t.Fatalf("unexpected nonce: %v %v", nonce, penNonce) + } + // StorageAt + storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil) + 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) + } + if !bytes.Equal(storage, penStorage) { + t.Fatalf("unexpected storage: %v %v", storage, penStorage) + } + // CodeAt + code, err := ec.CodeAt(context.Background(), testAddr, nil) + 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) + } + if !bytes.Equal(code, penCode) { + t.Fatalf("unexpected code: %v %v", code, penCode) + } +} + +func testTransactionSender(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + ctx := context.Background() + + // Retrieve testTx1 via RPC. + block2, err := ec.HeaderByNumber(ctx, big.NewInt(2)) + if err != nil { + t.Fatal("can't get block 1:", err) + } + tx1, err := ec.TransactionInBlock(ctx, block2.Hash(), 0) + if err != nil { + t.Fatal("can't get tx:", err) + } + if tx1.Hash() != testTx1.Hash() { + t.Fatalf("wrong tx hash %v, want %v", tx1.Hash(), testTx1.Hash()) + } + + // The sender address is cached in tx1, so no additional RPC should be required in + // 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) + } + if sender1 != testAddr { + t.Fatal("wrong sender:", sender1) + } + + // Now try to get the sender of testTx2, which was not fetched through RPC. + // TransactionSender should query the server here. + sender2, err := ec.TransactionSender(ctx, testTx2, block2.Hash(), 1) + if err != nil { + t.Fatal(err) + } + if sender2 != testAddr { + t.Fatal("wrong sender:", sender2) + } +} + +func sendTransaction(ec *Client) error { + chainID, err := ec.ChainID(context.Background()) + if err != nil { + return err + } + nonce, err := ec.NonceAt(context.Background(), testAddr, nil) + if err != nil { + return err + } + + signer := types.LatestSignerForChainID(chainID) + tx, err := types.SignNewTx(testKey, signer, &types.LegacyTx{ + Nonce: nonce, + To: &common.Address{2}, + Value: big.NewInt(1), + Gas: 22000, + GasPrice: big.NewInt(params.InitialBaseFee), + }) + if err != nil { + return err + } + return ec.SendTransaction(context.Background(), tx) +} diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go new file mode 100644 index 0000000..b1678b6 --- /dev/null +++ b/ethclient/gethclient/gethclient.go @@ -0,0 +1,350 @@ +// 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 gethclient provides an RPC client for geth-specific APIs. +package gethclient + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "runtime" + "runtime/debug" + + "github.com/ethereum/go-ethereum" + "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/p2p" + "github.com/ethereum/go-ethereum/rpc" +) + +// Client is a wrapper around rpc.Client that implements geth-specific functionality. +// +// If you want to use the standardized Ethereum RPC functionality, use ethclient.Client instead. +type Client struct { + c *rpc.Client +} + +// New creates a client that uses the given RPC client. +func New(c *rpc.Client) *Client { + return &Client{c} +} + +// CreateAccessList tries to create an access list for a specific transaction based on the +// current pending state of the blockchain. +func (ec *Client) CreateAccessList(ctx context.Context, msg ethereum.CallMsg) (*types.AccessList, uint64, string, error) { + type accessListResult struct { + Accesslist *types.AccessList `json:"accessList"` + Error string `json:"error,omitempty"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + } + var result accessListResult + if err := ec.c.CallContext(ctx, &result, "eth_createAccessList", toCallArg(msg)); err != nil { + return nil, 0, "", err + } + return result.Accesslist, uint64(result.GasUsed), result.Error, nil +} + +// AccountResult is the result of a GetProof operation. +type AccountResult struct { + Address common.Address `json:"address"` + AccountProof []string `json:"accountProof"` + Balance *big.Int `json:"balance"` + CodeHash common.Hash `json:"codeHash"` + Nonce uint64 `json:"nonce"` + StorageHash common.Hash `json:"storageHash"` + StorageProof []StorageResult `json:"storageProof"` +} + +// StorageResult provides a proof for a key-value pair. +type StorageResult struct { + Key string `json:"key"` + Value *big.Int `json:"value"` + Proof []string `json:"proof"` +} + +// GetProof returns the account and storage values of the specified account including the Merkle-proof. +// The block number can be nil, in which case the value is taken from the latest known block. +func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []string, blockNumber *big.Int) (*AccountResult, error) { + type storageResult struct { + Key string `json:"key"` + Value *hexutil.Big `json:"value"` + Proof []string `json:"proof"` + } + + type accountResult struct { + Address common.Address `json:"address"` + AccountProof []string `json:"accountProof"` + Balance *hexutil.Big `json:"balance"` + CodeHash common.Hash `json:"codeHash"` + Nonce hexutil.Uint64 `json:"nonce"` + StorageHash common.Hash `json:"storageHash"` + StorageProof []storageResult `json:"storageProof"` + } + + // Avoid keys being 'null'. + if keys == nil { + keys = []string{} + } + + var res accountResult + err := ec.c.CallContext(ctx, &res, "eth_getProof", account, keys, toBlockNumArg(blockNumber)) + // Turn hexutils back to normal datatypes + storageResults := make([]StorageResult, 0, len(res.StorageProof)) + for _, st := range res.StorageProof { + storageResults = append(storageResults, StorageResult{ + Key: st.Key, + Value: st.Value.ToInt(), + Proof: st.Proof, + }) + } + result := AccountResult{ + Address: res.Address, + AccountProof: res.AccountProof, + Balance: res.Balance.ToInt(), + Nonce: uint64(res.Nonce), + CodeHash: res.CodeHash, + StorageHash: res.StorageHash, + StorageProof: storageResults, + } + return &result, err +} + +// CallContract executes a message call transaction, which is directly executed in the VM +// of the node, but never mined into the blockchain. +// +// blockNumber selects the block height at which the call runs. It can be nil, in which +// case the code is taken from the latest known block. Note that state from very old +// blocks might not be available. +// +// overrides specifies a map of contract states that should be overwritten before executing +// the message call. +// Please use ethclient.CallContract instead if you don't need the override functionality. +func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount) ([]byte, error) { + var hex hexutil.Bytes + err := ec.c.CallContext( + ctx, &hex, "eth_call", toCallArg(msg), + toBlockNumArg(blockNumber), overrides, + ) + return hex, err +} + +// CallContractWithBlockOverrides executes a message call transaction, which is directly executed +// in the VM of the node, but never mined into the blockchain. +// +// blockNumber selects the block height at which the call runs. It can be nil, in which +// case the code is taken from the latest known block. Note that state from very old +// blocks might not be available. +// +// overrides specifies a map of contract states that should be overwritten before executing +// the message call. +// +// blockOverrides specifies block fields exposed to the EVM that can be overridden for the call. +// +// Please use ethclient.CallContract instead if you don't need the override functionality. +func (ec *Client) CallContractWithBlockOverrides(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount, blockOverrides BlockOverrides) ([]byte, error) { + var hex hexutil.Bytes + err := ec.c.CallContext( + ctx, &hex, "eth_call", toCallArg(msg), + toBlockNumArg(blockNumber), overrides, blockOverrides, + ) + return hex, err +} + +// GCStats retrieves the current garbage collection stats from a geth node. +func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) { + var result debug.GCStats + err := ec.c.CallContext(ctx, &result, "debug_gcStats") + return &result, err +} + +// MemStats retrieves the current memory stats from a geth node. +func (ec *Client) MemStats(ctx context.Context) (*runtime.MemStats, error) { + var result runtime.MemStats + err := ec.c.CallContext(ctx, &result, "debug_memStats") + return &result, err +} + +// SetHead sets the current head of the local chain by block number. +// Note, this is a destructive action and may severely damage your chain. +// Use with extreme caution. +func (ec *Client) SetHead(ctx context.Context, number *big.Int) error { + return ec.c.CallContext(ctx, nil, "debug_setHead", toBlockNumArg(number)) +} + +// GetNodeInfo retrieves the node info of a geth node. +func (ec *Client) GetNodeInfo(ctx context.Context) (*p2p.NodeInfo, error) { + var result p2p.NodeInfo + err := ec.c.CallContext(ctx, &result, "admin_nodeInfo") + return &result, err +} + +// SubscribeFullPendingTransactions subscribes to new pending transactions. +func (ec *Client) SubscribeFullPendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (*rpc.ClientSubscription, error) { + return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions", true) +} + +// SubscribePendingTransactions subscribes to new pending transaction hashes. +func (ec *Client) SubscribePendingTransactions(ctx context.Context, ch chan<- common.Hash) (*rpc.ClientSubscription, error) { + return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions") +} + +func toBlockNumArg(number *big.Int) string { + if number == nil { + return "latest" + } + if number.Sign() >= 0 { + return hexutil.EncodeBig(number) + } + // It's negative. + if number.IsInt64() { + return rpc.BlockNumber(number.Int64()).String() + } + // It's negative and large, which is invalid. + return fmt.Sprintf("", number) +} + +func toCallArg(msg ethereum.CallMsg) interface{} { + arg := map[string]interface{}{ + "from": msg.From, + "to": msg.To, + } + if len(msg.Data) > 0 { + arg["input"] = hexutil.Bytes(msg.Data) + } + if msg.Value != nil { + arg["value"] = (*hexutil.Big)(msg.Value) + } + if msg.Gas != 0 { + arg["gas"] = hexutil.Uint64(msg.Gas) + } + 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) + } + if msg.AccessList != nil { + arg["accessList"] = msg.AccessList + } + if msg.BlobGasFeeCap != nil { + arg["maxFeePerBlobGas"] = (*hexutil.Big)(msg.BlobGasFeeCap) + } + if msg.BlobHashes != nil { + arg["blobVersionedHashes"] = msg.BlobHashes + } + return arg +} + +// OverrideAccount specifies the state of an account to be overridden. +type OverrideAccount struct { + // Nonce sets nonce of the account. Note: the nonce override will only + // be applied when it is set to a non-zero value. + Nonce uint64 + + // Code sets the contract code. The override will be applied + // when the code is non-nil, i.e. setting empty code is possible + // using an empty slice. + Code []byte + + // Balance sets the account balance. + Balance *big.Int + + // State sets the complete storage. The override will be applied + // when the given map is non-nil. Using an empty map wipes the + // entire contract storage during the call. + State map[common.Hash]common.Hash + + // StateDiff allows overriding individual storage slots. + StateDiff map[common.Hash]common.Hash +} + +func (a OverrideAccount) MarshalJSON() ([]byte, error) { + type acc struct { + Nonce hexutil.Uint64 `json:"nonce,omitempty"` + Code string `json:"code,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + State interface{} `json:"state,omitempty"` + StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"` + } + + output := acc{ + Nonce: hexutil.Uint64(a.Nonce), + Balance: (*hexutil.Big)(a.Balance), + StateDiff: a.StateDiff, + } + if a.Code != nil { + output.Code = hexutil.Encode(a.Code) + } + if a.State != nil { + output.State = a.State + } + return json.Marshal(output) +} + +// BlockOverrides specifies the set of header fields to override. +type BlockOverrides struct { + // Number overrides the block number. + Number *big.Int + // Difficulty overrides the block difficulty. + Difficulty *big.Int + // Time overrides the block timestamp. Time is applied only when + // it is non-zero. + Time uint64 + // GasLimit overrides the block gas limit. GasLimit is applied only when + // it is non-zero. + GasLimit uint64 + // Coinbase overrides the block coinbase. Coinbase is applied only when + // it is different from the zero address. + Coinbase common.Address + // Random overrides the block extra data which feeds into the RANDOM opcode. + // Random is applied only when it is a non-zero hash. + Random common.Hash + // BaseFee overrides the block base fee. + BaseFee *big.Int +} + +func (o BlockOverrides) MarshalJSON() ([]byte, error) { + type override struct { + Number *hexutil.Big `json:"number,omitempty"` + Difficulty *hexutil.Big `json:"difficulty,omitempty"` + Time hexutil.Uint64 `json:"time,omitempty"` + GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"` + Coinbase *common.Address `json:"coinbase,omitempty"` + Random *common.Hash `json:"random,omitempty"` + BaseFee *hexutil.Big `json:"baseFee,omitempty"` + } + + output := override{ + Number: (*hexutil.Big)(o.Number), + Difficulty: (*hexutil.Big)(o.Difficulty), + Time: hexutil.Uint64(o.Time), + GasLimit: hexutil.Uint64(o.GasLimit), + BaseFee: (*hexutil.Big)(o.BaseFee), + } + if o.Coinbase != (common.Address{}) { + output.Coinbase = &o.Coinbase + } + if o.Random != (common.Hash{}) { + output.Random = &o.Random + } + return json.Marshal(output) +} diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go new file mode 100644 index 0000000..59ad370 --- /dev/null +++ b/ethclient/gethclient/gethclient_test.go @@ -0,0 +1,570 @@ +// 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 gethclient + +import ( + "bytes" + "context" + "encoding/json" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "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/params" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + 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) { + // Generate test chain. + genesis, blocks := generateTestChain() + // Create node + n, err := node.New(&node.Config{}) + if err != nil { + t.Fatalf("can't create new node: %v", err) + } + // Create Ethereum Service + config := ðconfig.Config{Genesis: genesis} + ethservice, err := eth.New(n, config) + if err != nil { + t.Fatalf("can't create new ethereum service: %v", err) + } + filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{}) + n.RegisterAPIs([]rpc.API{{ + Namespace: "eth", + Service: filters.NewFilterAPI(filterSystem), + }}) + + // Import the test chain. + if err := n.Start(); err != nil { + t.Fatalf("can't start test node: %v", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil { + t.Fatalf("can't import test blocks: %v", err) + } + return n, blocks +} + +func generateTestChain() (*core.Genesis, []*types.Block) { + genesis := &core.Genesis{ + Config: params.AllEthashProtocolChanges, + Alloc: types.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, + } + generate := func(i int, g *core.BlockGen) { + g.OffsetTime(5) + g.SetExtra([]byte("test")) + } + _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate) + blocks = append([]*types.Block{genesis.ToBlock()}, blocks...) + return genesis, blocks +} + +func TestGethClient(t *testing.T) { + backend, _ := newTestBackend(t) + client := backend.Attach() + defer backend.Close() + defer client.Close() + + tests := []struct { + name string + test func(t *testing.T) + }{ + { + "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) }, + }, { + "TestGCStats", + func(t *testing.T) { testGCStats(t, client) }, + }, { + "TestMemStats", + func(t *testing.T) { testMemStats(t, client) }, + }, { + "TestGetNodeInfo", + func(t *testing.T) { testGetNodeInfo(t, client) }, + }, { + "TestSubscribePendingTxHashes", + func(t *testing.T) { testSubscribePendingTransactions(t, client) }, + }, { + "TestSubscribePendingTxs", + func(t *testing.T) { testSubscribeFullPendingTransactions(t, client) }, + }, { + "TestCallContract", + func(t *testing.T) { testCallContract(t, client) }, + }, { + "TestCallContractWithBlockOverrides", + func(t *testing.T) { testCallContractWithBlockOverrides(t, client) }, + }, + // The testaccesslist is a bit time-sensitive: the newTestBackend imports + // one block. The `testAccessList` fails if the miner has not yet created a + // new pending-block after the import event. + // Hence: this test should be last, execute the tests serially. + { + "TestAccessList", + func(t *testing.T) { testAccessList(t, client) }, + }, { + "TestSetHead", + func(t *testing.T) { testSetHead(t, client) }, + }, + } + for _, tt := range tests { + t.Run(tt.name, tt.test) + } +} + +func testAccessList(t *testing.T, client *rpc.Client) { + ec := New(client) + // Test transfer + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: big.NewInt(875000000), + Value: big.NewInt(1), + } + al, gas, vmErr, err := ec.CreateAccessList(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if vmErr != "" { + t.Fatalf("unexpected vm error: %v", vmErr) + } + if gas != 21000 { + t.Fatalf("unexpected gas used: %v", gas) + } + if len(*al) != 0 { + t.Fatalf("unexpected length of accesslist: %v", len(*al)) + } + // Test reverting transaction + msg = ethereum.CallMsg{ + From: testAddr, + To: nil, + Gas: 100000, + GasPrice: big.NewInt(1000000000), + Value: big.NewInt(1), + Data: common.FromHex("0x608060806080608155fd"), + } + al, gas, vmErr, err = ec.CreateAccessList(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if vmErr == "" { + t.Fatalf("wanted vmErr, got none") + } + if gas == 21000 { + t.Fatalf("unexpected gas used: %v", gas) + } + if len(*al) != 1 || al.StorageKeys() != 1 { + t.Fatalf("unexpected length of accesslist: %v", len(*al)) + } + // address changes between calls, so we can't test for it. + if (*al)[0].Address == common.HexToAddress("0x0") { + t.Fatalf("unexpected address: %v", (*al)[0].Address) + } + if (*al)[0].StorageKeys[0] != common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000081") { + t.Fatalf("unexpected storage key: %v", (*al)[0].StorageKeys[0]) + } +} + +func testGetProof(t *testing.T, client *rpc.Client, addr common.Address) { + ec := New(client) + ethcl := ethclient.NewClient(client) + result, err := ec.GetProof(context.Background(), addr, []string{testSlot.String()}, 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 nonce, _ := ethcl.NonceAt(context.Background(), addr, nil); result.Nonce != nonce { + t.Fatalf("invalid nonce, want: %v got: %v", nonce, result.Nonce) + } + // test balance + 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)) + } + 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) + } + } + // 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) + } +} + +func testGetProofCanonicalizeKeys(t *testing.T, client *rpc.Client) { + ec := New(client) + + // Tests with non-canon input for storage keys. + // Here we check that the storage key is canonicalized. + result, err := ec.GetProof(context.Background(), testAddr, []string{"0x0dEadbeef"}, nil) + if err != nil { + t.Fatal(err) + } + if result.StorageProof[0].Key != "0xdeadbeef" { + t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key) + } + if result, err = ec.GetProof(context.Background(), testAddr, []string{"0x000deadbeef"}, nil); err != nil { + t.Fatal(err) + } + if result.StorageProof[0].Key != "0xdeadbeef" { + t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key) + } + + // If the requested storage key is 32 bytes long, it will be returned as is. + hashSizedKey := "0x00000000000000000000000000000000000000000000000000000000deadbeef" + result, err = ec.GetProof(context.Background(), testAddr, []string{hashSizedKey}, nil) + if err != nil { + t.Fatal(err) + } + if result.StorageProof[0].Key != hashSizedKey { + t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key) + } +} + +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.Sign() != 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()) + if err != nil { + t.Fatal(err) + } +} + +func testMemStats(t *testing.T, client *rpc.Client) { + ec := New(client) + stats, err := ec.MemStats(context.Background()) + if err != nil { + t.Fatal(err) + } + if stats.Alloc == 0 { + t.Fatal("Invalid mem stats retrieved") + } +} + +func testGetNodeInfo(t *testing.T, client *rpc.Client) { + ec := New(client) + info, err := ec.GetNodeInfo(context.Background()) + if err != nil { + t.Fatal(err) + } + + if info.Name == "" { + t.Fatal("Invalid node info retrieved") + } +} + +func testSetHead(t *testing.T, client *rpc.Client) { + ec := New(client) + err := ec.SetHead(context.Background(), big.NewInt(0)) + if err != nil { + t.Fatal(err) + } +} + +func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) { + ec := New(client) + ethcl := ethclient.NewClient(client) + // Subscribe to Transactions + ch := make(chan common.Hash) + ec.SubscribePendingTransactions(context.Background(), ch) + // Send a transaction + chainID, err := ethcl.ChainID(context.Background()) + if err != nil { + t.Fatal(err) + } + // Create transaction + tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) + signer := types.LatestSignerForChainID(chainID) + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) + if err != nil { + t.Fatal(err) + } + signedTx, err := tx.WithSignature(signer, signature) + if err != nil { + t.Fatal(err) + } + // Send transaction + err = ethcl.SendTransaction(context.Background(), signedTx) + if err != nil { + t.Fatal(err) + } + // Check that the transaction was sent over the channel + hash := <-ch + if hash != signedTx.Hash() { + t.Fatalf("Invalid tx hash received, got %v, want %v", hash, signedTx.Hash()) + } +} + +func testSubscribeFullPendingTransactions(t *testing.T, client *rpc.Client) { + ec := New(client) + ethcl := ethclient.NewClient(client) + // Subscribe to Transactions + ch := make(chan *types.Transaction) + ec.SubscribeFullPendingTransactions(context.Background(), ch) + // Send a transaction + chainID, err := ethcl.ChainID(context.Background()) + if err != nil { + t.Fatal(err) + } + // Create transaction + tx := types.NewTransaction(1, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) + signer := types.LatestSignerForChainID(chainID) + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) + if err != nil { + t.Fatal(err) + } + signedTx, err := tx.WithSignature(signer, signature) + if err != nil { + t.Fatal(err) + } + // Send transaction + err = ethcl.SendTransaction(context.Background(), signedTx) + if err != nil { + t.Fatal(err) + } + // Check that the transaction was sent over the channel + tx = <-ch + if tx.Hash() != signedTx.Hash() { + t.Fatalf("Invalid tx hash received, got %v, want %v", tx.Hash(), signedTx.Hash()) + } +} + +func testCallContract(t *testing.T, client *rpc.Client) { + ec := New(client) + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: big.NewInt(1000000000), + Value: big.NewInt(1), + } + // CallContract without override + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } + // CallContract with override + override := OverrideAccount{ + Nonce: 1, + } + mapAcc := make(map[common.Address]OverrideAccount) + mapAcc[testAddr] = override + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestOverrideAccountMarshal(t *testing.T) { + om := map[common.Address]OverrideAccount{ + {0x11}: { + // Zero-valued nonce is not overridden, but simply dropped by the encoder. + Nonce: 0, + }, + {0xaa}: { + Nonce: 5, + }, + {0xbb}: { + Code: []byte{1}, + }, + {0xcc}: { + // 'code', 'balance', 'state' should be set when input is + // a non-nil but empty value. + Code: []byte{}, + Balance: big.NewInt(0), + State: map[common.Hash]common.Hash{}, + // For 'stateDiff' the behavior is different, empty map + // is ignored because it makes no difference. + StateDiff: map[common.Hash]common.Hash{}, + }, + } + + marshalled, err := json.MarshalIndent(&om, "", " ") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expected := `{ + "0x1100000000000000000000000000000000000000": {}, + "0xaa00000000000000000000000000000000000000": { + "nonce": "0x5" + }, + "0xbb00000000000000000000000000000000000000": { + "code": "0x01" + }, + "0xcc00000000000000000000000000000000000000": { + "code": "0x", + "balance": "0x0", + "state": {} + } +}` + + if string(marshalled) != expected { + t.Error("wrong output:", string(marshalled)) + t.Error("want:", expected) + } +} + +func TestBlockOverridesMarshal(t *testing.T) { + for i, tt := range []struct { + bo BlockOverrides + want string + }{ + { + bo: BlockOverrides{}, + want: `{}`, + }, + { + bo: BlockOverrides{ + Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"), + }, + want: `{"coinbase":"0x1111111111111111111111111111111111111111"}`, + }, + { + bo: BlockOverrides{ + Number: big.NewInt(1), + Difficulty: big.NewInt(2), + Time: 3, + GasLimit: 4, + BaseFee: big.NewInt(5), + }, + want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFee":"0x5"}`, + }, + } { + marshalled, err := json.Marshal(&tt.bo) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(marshalled) != tt.want { + t.Errorf("Testcase #%d failed. expected\n%s\ngot\n%s", i, tt.want, string(marshalled)) + } + } +} + +func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) { + ec := New(client) + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 50000, + GasPrice: big.NewInt(1000000000), + Value: big.NewInt(1), + } + override := OverrideAccount{ + // Returns coinbase address. + Code: common.FromHex("0x41806000526014600cf3"), + } + mapAcc := make(map[common.Address]OverrideAccount) + mapAcc[common.Address{}] = override + res, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(res, common.FromHex("0x0000000000000000000000000000000000000000")) { + t.Fatalf("unexpected result: %x", res) + } + + // Now test with block overrides + bo := BlockOverrides{ + Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"), + } + res, err = ec.CallContractWithBlockOverrides(context.Background(), msg, big.NewInt(0), &mapAcc, bo) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(res, common.FromHex("0x1111111111111111111111111111111111111111")) { + t.Fatalf("unexpected result: %x", res) + } +} diff --git a/ethclient/signer.go b/ethclient/signer.go new file mode 100644 index 0000000..f827d4e --- /dev/null +++ b/ethclient/signer.go @@ -0,0 +1,62 @@ +// 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 ethclient + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// senderFromServer is a types.Signer that remembers the sender address returned by the RPC +// server. It is stored in the transaction's sender address cache to avoid an additional +// request in TransactionSender. +type senderFromServer struct { + addr common.Address + blockhash common.Hash +} + +var errNotCached = errors.New("sender not cached") + +func setSenderFromServer(tx *types.Transaction, addr common.Address, block common.Hash) { + // Use types.Sender for side-effect to store our signer into the cache. + types.Sender(&senderFromServer{addr, block}, tx) +} + +func (s *senderFromServer) Equal(other types.Signer) bool { + os, ok := other.(*senderFromServer) + return ok && os.blockhash == s.blockhash +} + +func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) { + if s.addr == (common.Address{}) { + return common.Address{}, errNotCached + } + return s.addr, nil +} + +func (s *senderFromServer) ChainID() *big.Int { + panic("can't sign with senderFromServer") +} +func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash { + panic("can't sign with senderFromServer") +} +func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) { + panic("can't sign with senderFromServer") +} diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go new file mode 100644 index 0000000..6e07aa6 --- /dev/null +++ b/ethclient/simulated/backend.go @@ -0,0 +1,193 @@ +// 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 ( + "errors" + "time" + + "github.com/ethereum/go-ethereum" + "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/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" +) + +// 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 +} + +// simClient wraps ethclient. This exists to prevent extracting ethclient.Client +// from the Client interface returned by Backend. +type simClient struct { + *ethclient.Client +} + +// 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 { + node *node.Node + beacon *catalyst.SimulatedBeacon + client simClient +} + +// NewBackend creates a new simulated blockchain that can be used as a backend for +// contract bindings in unit tests. +// +// A simulated backend always uses chainID 1337. +func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config, ethConf *ethconfig.Config)) *Backend { + // Create the default configurations for the outer node shell and the Ethereum + // service to mutate with the options afterwards + nodeConf := node.DefaultConfig + nodeConf.DataDir = "" + nodeConf.P2P = p2p.Config{NoDiscovery: true} + + ethConf := ethconfig.Defaults + ethConf.Genesis = &core.Genesis{ + Config: params.AllDevChainProtocolChanges, + GasLimit: ethconfig.Defaults.Miner.GasCeil, + Alloc: alloc, + } + ethConf.SyncMode = downloader.FullSync + ethConf.TxPool.NoLocals = true + + for _, option := range options { + option(&nodeConf, ðConf) + } + // Assemble the Ethereum stack to run the chain with + stack, err := node.New(&nodeConf) + if err != nil { + panic(err) // this should never happen + } + sim, err := newWithNode(stack, ðConf, 0) + if err != nil { + panic(err) // this should never happen + } + return sim +} + +// newWithNode sets up a simulated backend on an existing node. The provided node +// must not be started and will be started by this method. +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), + }}) + // 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{ + node: stack, + 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{} + } + var err error + if n.beacon != nil { + err = n.beacon.Stop() + n.beacon = nil + } + if n.node != nil { + err = errors.Join(err, n.node.Close()) + n.node = nil + } + return err +} + +// 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 0000000..a8fd791 --- /dev/null +++ b/ethclient/simulated/backend_test.go @@ -0,0 +1,308 @@ +// 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/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 NewBackend( + types.GenesisAlloc{ + testAddr: {Balance: big.NewInt(10000000000000000)}, + }, + ) +} + +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(params.GWei)) + 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(params.GWei), + GasFeeCap: gasPrice, + Gas: 21000, + To: &addr, + }) + return types.SignTx(tx, types.LatestSignerForChainID(chainid), key) +} + +func TestNewBackend(t *testing.T) { + sim := NewBackend(types.GenesisAlloc{}) + 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 := NewBackend(types.GenesisAlloc{}) + 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/ethclient/simulated/options.go b/ethclient/simulated/options.go new file mode 100644 index 0000000..40bcb37 --- /dev/null +++ b/ethclient/simulated/options.go @@ -0,0 +1,55 @@ +// Copyright 2024 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 ( + "math/big" + + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/node" +) + +// WithBlockGasLimit configures the simulated backend to target a specific gas limit +// when producing blocks. +func WithBlockGasLimit(gaslimit uint64) func(nodeConf *node.Config, ethConf *ethconfig.Config) { + return func(nodeConf *node.Config, ethConf *ethconfig.Config) { + ethConf.Genesis.GasLimit = gaslimit + ethConf.Miner.GasCeil = gaslimit + } +} + +// WithCallGasLimit configures the simulated backend to cap eth_calls to a specific +// gas limit when running client operations. +func WithCallGasLimit(gaslimit uint64) func(nodeConf *node.Config, ethConf *ethconfig.Config) { + return func(nodeConf *node.Config, ethConf *ethconfig.Config) { + ethConf.RPCGasCap = gaslimit + } +} + +// WithMinerMinTip configures the simulated backend to require a specific minimum +// gas tip for a transaction to be included. +// +// 0 is not possible as a live Geth node would reject that due to DoS protection, +// so the simulated backend will replicate that behavior for consistency. +func WithMinerMinTip(tip *big.Int) func(nodeConf *node.Config, ethConf *ethconfig.Config) { + if tip == nil || tip.Sign() <= 0 { + panic("invalid miner minimum tip") + } + return func(nodeConf *node.Config, ethConf *ethconfig.Config) { + ethConf.Miner.GasPrice = tip + } +} diff --git a/ethclient/simulated/options_test.go b/ethclient/simulated/options_test.go new file mode 100644 index 0000000..9ff2be5 --- /dev/null +++ b/ethclient/simulated/options_test.go @@ -0,0 +1,74 @@ +// Copyright 2024 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" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +// Tests that the simulator starts with the initial gas limit in the genesis block, +// and that it keeps the same target value. +func TestWithBlockGasLimitOption(t *testing.T) { + // Construct a simulator, targeting a different gas limit + sim := NewBackend(types.GenesisAlloc{}, WithBlockGasLimit(12_345_678)) + defer sim.Close() + + client := sim.Client() + genesis, err := client.BlockByNumber(context.Background(), big.NewInt(0)) + if err != nil { + t.Fatalf("failed to retrieve genesis block: %v", err) + } + if genesis.GasLimit() != 12_345_678 { + t.Errorf("genesis gas limit mismatch: have %v, want %v", genesis.GasLimit(), 12_345_678) + } + // Produce a number of blocks and verify the locked in gas target + sim.Commit() + head, err := client.BlockByNumber(context.Background(), big.NewInt(1)) + if err != nil { + t.Fatalf("failed to retrieve head block: %v", err) + } + if head.GasLimit() != 12_345_678 { + t.Errorf("head gas limit mismatch: have %v, want %v", head.GasLimit(), 12_345_678) + } +} + +// Tests that the simulator honors the RPC call caps set by the options. +func TestWithCallGasLimitOption(t *testing.T) { + // Construct a simulator, targeting a different gas limit + sim := NewBackend(types.GenesisAlloc{ + testAddr: {Balance: big.NewInt(10000000000000000)}, + }, WithCallGasLimit(params.TxGas-1)) + defer sim.Close() + + client := sim.Client() + _, err := client.CallContract(context.Background(), ethereum.CallMsg{ + From: testAddr, + To: &testAddr, + Gas: 21000, + }, nil) + if !strings.Contains(err.Error(), core.ErrIntrinsicGas.Error()) { + t.Fatalf("error mismatch: have %v, want %v", err, core.ErrIntrinsicGas) + } +} diff --git a/ethdb/batch.go b/ethdb/batch.go new file mode 100644 index 0000000..541f40c --- /dev/null +++ b/ethdb/batch.go @@ -0,0 +1,74 @@ +// 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 ethdb + +// IdealBatchSize defines the size of the data batches should ideally add in one +// write. +const IdealBatchSize = 100 * 1024 + +// Batch is a write-only database that commits changes to its host database +// when Write is called. A batch cannot be used concurrently. +type Batch interface { + KeyValueWriter + + // ValueSize retrieves the amount of data queued up for writing. + ValueSize() int + + // Write flushes any accumulated data to disk. + Write() error + + // Reset resets the batch for reuse. + Reset() + + // Replay replays the batch contents. + Replay(w KeyValueWriter) error +} + +// Batcher wraps the NewBatch method of a backing data store. +type Batcher interface { + // NewBatch creates a write-only database that buffers changes to its host db + // until a final write is called. + NewBatch() Batch + + // NewBatchWithSize creates a write-only database batch with pre-allocated buffer. + NewBatchWithSize(size int) Batch +} + +// HookedBatch wraps an arbitrary batch where each operation may be hooked into +// to monitor from black box code. +type HookedBatch struct { + Batch + + OnPut func(key []byte, value []byte) // Callback if a key is inserted + OnDelete func(key []byte) // Callback if a key is deleted +} + +// Put inserts the given value into the key-value data store. +func (b HookedBatch) Put(key []byte, value []byte) error { + if b.OnPut != nil { + b.OnPut(key, value) + } + return b.Batch.Put(key, value) +} + +// Delete removes the key from the key-value data store. +func (b HookedBatch) Delete(key []byte) error { + if b.OnDelete != nil { + b.OnDelete(key) + } + return b.Batch.Delete(key) +} diff --git a/ethdb/database.go b/ethdb/database.go new file mode 100644 index 0000000..9ef5942 --- /dev/null +++ b/ethdb/database.go @@ -0,0 +1,204 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package ethdb defines the interfaces for an Ethereum data store. +package ethdb + +import "io" + +// KeyValueReader wraps the Has and Get method of a backing data store. +type KeyValueReader interface { + // Has retrieves if a key is present in the key-value data store. + Has(key []byte) (bool, error) + + // Get retrieves the given key if it's present in the key-value data store. + Get(key []byte) ([]byte, error) +} + +// KeyValueWriter wraps the Put method of a backing data store. +type KeyValueWriter interface { + // Put inserts the given value into the key-value data store. + Put(key []byte, value []byte) error + + // Delete removes the key from the key-value data store. + Delete(key []byte) error +} + +// KeyValueStater wraps the Stat method of a backing data store. +type KeyValueStater interface { + // Stat returns the statistic data of the database. + Stat() (string, error) +} + +// Compacter wraps the Compact method of a backing data store. +type Compacter interface { + // Compact flattens the underlying data store for the given key range. In essence, + // deleted and overwritten versions are discarded, and the data is rearranged to + // reduce the cost of operations needed to access them. + // + // A nil start is treated as a key before all keys in the data store; a nil limit + // is treated as a key after all keys in the data store. If both is nil then it + // will compact entire data store. + Compact(start []byte, limit []byte) error +} + +// KeyValueStore contains all the methods required to allow handling different +// key-value data stores backing the high level database. +type KeyValueStore interface { + KeyValueReader + KeyValueWriter + KeyValueStater + Batcher + Iteratee + Compacter + Snapshotter + io.Closer +} + +// AncientReaderOp contains the methods required to read from immutable ancient data. +type AncientReaderOp interface { + // HasAncient returns an indicator whether the specified data exists in the + // ancient store. + HasAncient(kind string, number uint64) (bool, error) + + // Ancient retrieves an ancient binary blob from the append-only immutable files. + Ancient(kind string, number uint64) ([]byte, error) + + // AncientRange retrieves multiple items in sequence, starting from the index 'start'. + // It will return + // - at most 'count' items, + // - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), + // but will otherwise return as many items as fit into maxByteSize. + // - if maxBytes is not specified, 'count' items will be returned if they are present + AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) + + // Ancients returns the ancient item numbers in the ancient store. + Ancients() (uint64, error) + + // Tail returns the number of first stored item in the ancient store. + // This number can also be interpreted as the total deleted items. + Tail() (uint64, error) + + // AncientSize returns the ancient size of the specified category. + AncientSize(kind string) (uint64, error) +} + +// AncientReader is the extended ancient reader interface including 'batched' or 'atomic' reading. +type AncientReader interface { + AncientReaderOp + + // ReadAncients runs the given read operation while ensuring that no writes take place + // on the underlying ancient store. + ReadAncients(fn func(AncientReaderOp) error) (err error) +} + +// AncientWriter contains the methods required to write to immutable ancient data. +type AncientWriter interface { + // ModifyAncients runs a write operation on the ancient store. + // If the function returns an error, any changes to the underlying store are reverted. + // The integer return value is the total size of the written data. + ModifyAncients(func(AncientWriteOp) error) (int64, error) + + // TruncateHead discards all but the first n ancient data from the ancient store. + // After the truncation, the latest item can be accessed it item_n-1(start from 0). + TruncateHead(n uint64) (uint64, error) + + // TruncateTail discards the first n ancient data from the ancient store. The already + // deleted items are ignored. After the truncation, the earliest item can be accessed + // is item_n(start from 0). The deleted items may not be removed from the ancient store + // immediately, but only when the accumulated deleted data reach the threshold then + // will be removed all together. + TruncateTail(n uint64) (uint64, error) + + // Sync flushes all in-memory ancient store data to disk. + Sync() error + + // MigrateTable processes and migrates entries of a given table to a new format. + // The second argument is a function that takes a raw entry and returns it + // in the newest format. + MigrateTable(string, func([]byte) ([]byte, error)) error +} + +// AncientWriteOp is given to the function argument of ModifyAncients. +type AncientWriteOp interface { + // Append adds an RLP-encoded item. + Append(kind string, number uint64, item interface{}) error + + // AppendRaw adds an item without RLP-encoding it. + AppendRaw(kind string, number uint64, item []byte) error +} + +// AncientStater wraps the Stat method of a backing ancient store. +type AncientStater interface { + // AncientDatadir returns the path of the ancient store directory. + // + // If the ancient store is not activated, an error is returned. + // If an ephemeral ancient store is used, an empty path is returned. + // + // The path returned by AncientDatadir can be used as the root path + // of the ancient store to construct paths for other sub ancient stores. + AncientDatadir() (string, error) +} + +// Reader contains the methods required to read data from both key-value as well as +// immutable ancient data. +type Reader interface { + KeyValueReader + AncientReader +} + +// Writer contains the methods required to write data to both key-value as well as +// immutable ancient data. +type Writer interface { + KeyValueWriter + AncientWriter +} + +// Stater contains the methods required to retrieve states from both key-value as well as +// immutable ancient data. +type Stater interface { + KeyValueStater + AncientStater +} + +// AncientStore contains all the methods required to allow handling different +// ancient data stores backing immutable data store. +type AncientStore interface { + AncientReader + AncientWriter + io.Closer +} + +// ResettableAncientStore extends the AncientStore interface by adding a Reset method. +type ResettableAncientStore interface { + AncientStore + + // Reset is designed to reset the entire ancient store to its default state. + Reset() error +} + +// Database contains all the methods required by the high level database to not +// only access the key-value data store but also the ancient chain store. +type Database interface { + Reader + Writer + Batcher + Iteratee + Stater + Compacter + Snapshotter + io.Closer +} diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go new file mode 100644 index 0000000..29a773c --- /dev/null +++ b/ethdb/dbtest/testsuite.go @@ -0,0 +1,536 @@ +// 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 dbtest + +import ( + "bytes" + "crypto/rand" + "slices" + "sort" + "testing" + + "github.com/ethereum/go-ethereum/ethdb" +) + +// TestDatabaseSuite runs a suite of tests against a KeyValueStore database +// implementation. +func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { + t.Run("Iterator", func(t *testing.T) { + tests := []struct { + content map[string]string + prefix string + start string + order []string + }{ + // Empty databases should be iterable + {map[string]string{}, "", "", nil}, + {map[string]string{}, "non-existent-prefix", "", nil}, + + // Single-item databases should be iterable + {map[string]string{"key": "val"}, "", "", []string{"key"}}, + {map[string]string{"key": "val"}, "k", "", []string{"key"}}, + {map[string]string{"key": "val"}, "l", "", nil}, + + // Multi-item databases should be fully iterable + { + map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, + "", "", + []string{"k1", "k2", "k3", "k4", "k5"}, + }, + { + map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, + "k", "", + []string{"k1", "k2", "k3", "k4", "k5"}, + }, + { + map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, + "l", "", + nil, + }, + // Multi-item databases should be prefix-iterable + { + map[string]string{ + "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", + "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", + }, + "ka", "", + []string{"ka1", "ka2", "ka3", "ka4", "ka5"}, + }, + { + map[string]string{ + "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", + "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", + }, + "kc", "", + nil, + }, + // Multi-item databases should be prefix-iterable with start position + { + map[string]string{ + "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", + "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", + }, + "ka", "3", + []string{"ka3", "ka4", "ka5"}, + }, + { + map[string]string{ + "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", + "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", + }, + "ka", "8", + nil, + }, + } + for i, tt := range tests { + // Create the key-value data store + db := New() + for key, val := range tt.content { + if err := db.Put([]byte(key), []byte(val)); err != nil { + t.Fatalf("test %d: failed to insert item %s:%s into database: %v", i, key, val, err) + } + } + // Iterate over the database with the given configs and verify the results + it, idx := db.NewIterator([]byte(tt.prefix), []byte(tt.start)), 0 + for it.Next() { + if len(tt.order) <= idx { + t.Errorf("test %d: prefix=%q more items than expected: checking idx=%d (key %q), expecting len=%d", i, tt.prefix, idx, it.Key(), len(tt.order)) + break + } + if !bytes.Equal(it.Key(), []byte(tt.order[idx])) { + t.Errorf("test %d: item %d: key mismatch: have %s, want %s", i, idx, string(it.Key()), tt.order[idx]) + } + if !bytes.Equal(it.Value(), []byte(tt.content[tt.order[idx]])) { + t.Errorf("test %d: item %d: value mismatch: have %s, want %s", i, idx, string(it.Value()), tt.content[tt.order[idx]]) + } + idx++ + } + if err := it.Error(); err != nil { + t.Errorf("test %d: iteration failed: %v", i, err) + } + if idx != len(tt.order) { + t.Errorf("test %d: iteration terminated prematurely: have %d, want %d", i, idx, len(tt.order)) + } + db.Close() + } + }) + + t.Run("IteratorWith", func(t *testing.T) { + db := New() + defer db.Close() + + keys := []string{"1", "2", "3", "4", "6", "10", "11", "12", "20", "21", "22"} + sort.Strings(keys) // 1, 10, 11, etc + + for _, k := range keys { + if err := db.Put([]byte(k), nil); err != nil { + t.Fatal(err) + } + } + + { + it := db.NewIterator(nil, nil) + got, want := iterateKeys(it), keys + if err := it.Error(); err != nil { + t.Fatal(err) + } + if !slices.Equal(got, want) { + t.Errorf("Iterator: got: %s; want: %s", got, want) + } + } + + { + it := db.NewIterator([]byte("1"), nil) + got, want := iterateKeys(it), []string{"1", "10", "11", "12"} + if err := it.Error(); err != nil { + t.Fatal(err) + } + if !slices.Equal(got, want) { + t.Errorf("IteratorWith(1,nil): got: %s; want: %s", got, want) + } + } + + { + it := db.NewIterator([]byte("5"), nil) + got, want := iterateKeys(it), []string{} + if err := it.Error(); err != nil { + t.Fatal(err) + } + if !slices.Equal(got, want) { + t.Errorf("IteratorWith(5,nil): got: %s; want: %s", got, want) + } + } + + { + it := db.NewIterator(nil, []byte("2")) + got, want := iterateKeys(it), []string{"2", "20", "21", "22", "3", "4", "6"} + if err := it.Error(); err != nil { + t.Fatal(err) + } + if !slices.Equal(got, want) { + t.Errorf("IteratorWith(nil,2): got: %s; want: %s", got, want) + } + } + + { + it := db.NewIterator(nil, []byte("5")) + got, want := iterateKeys(it), []string{"6"} + if err := it.Error(); err != nil { + t.Fatal(err) + } + if !slices.Equal(got, want) { + t.Errorf("IteratorWith(nil,5): got: %s; want: %s", got, want) + } + } + }) + + t.Run("KeyValueOperations", func(t *testing.T) { + db := New() + defer db.Close() + + key := []byte("foo") + + if got, err := db.Has(key); err != nil { + t.Error(err) + } else if got { + t.Errorf("wrong value: %t", got) + } + + value := []byte("hello world") + if err := db.Put(key, value); err != nil { + t.Error(err) + } + + if got, err := db.Has(key); err != nil { + t.Error(err) + } else if !got { + t.Errorf("wrong value: %t", got) + } + + if got, err := db.Get(key); err != nil { + t.Error(err) + } else if !bytes.Equal(got, value) { + t.Errorf("wrong value: %q", got) + } + + if err := db.Delete(key); err != nil { + t.Error(err) + } + + if got, err := db.Has(key); err != nil { + t.Error(err) + } else if got { + t.Errorf("wrong value: %t", got) + } + }) + + t.Run("Batch", func(t *testing.T) { + db := New() + defer db.Close() + + b := db.NewBatch() + for _, k := range []string{"1", "2", "3", "4"} { + if err := b.Put([]byte(k), nil); err != nil { + t.Fatal(err) + } + } + + if has, err := db.Has([]byte("1")); err != nil { + t.Fatal(err) + } else if has { + t.Error("db contains element before batch write") + } + + if err := b.Write(); err != nil { + t.Fatal(err) + } + + { + it := db.NewIterator(nil, nil) + if got, want := iterateKeys(it), []string{"1", "2", "3", "4"}; !slices.Equal(got, want) { + t.Errorf("got: %s; want: %s", got, want) + } + } + + b.Reset() + + // Mix writes and deletes in batch + b.Put([]byte("5"), nil) + b.Delete([]byte("1")) + b.Put([]byte("6"), nil) + + 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) + } + + { + it := db.NewIterator(nil, nil) + if got, want := iterateKeys(it), []string{"2", "3", "4", "5", "6"}; !slices.Equal(got, want) { + t.Errorf("got: %s; want: %s", got, want) + } + } + }) + + t.Run("BatchReplay", func(t *testing.T) { + db := New() + defer db.Close() + + want := []string{"1", "2", "3", "4"} + b := db.NewBatch() + for _, k := range want { + if err := b.Put([]byte(k), nil); err != nil { + t.Fatal(err) + } + } + + b2 := db.NewBatch() + if err := b.Replay(b2); err != nil { + t.Fatal(err) + } + + if err := b2.Replay(db); err != nil { + t.Fatal(err) + } + + it := db.NewIterator(nil, nil) + if got := iterateKeys(it); !slices.Equal(got, want) { + t.Errorf("got: %s; want: %s", got, want) + } + }) + + t.Run("Snapshot", func(t *testing.T) { + db := New() + defer db.Close() + + initial := map[string]string{ + "k1": "v1", "k2": "v2", "k3": "", "k4": "", + } + for k, v := range initial { + db.Put([]byte(k), []byte(v)) + } + snapshot, err := db.NewSnapshot() + if err != nil { + t.Fatal(err) + } + for k, v := range initial { + got, err := snapshot.Get([]byte(k)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, []byte(v)) { + t.Fatalf("Unexpected value want: %v, got %v", v, got) + } + } + + // Flush more modifications into the database, ensure the snapshot + // isn't affected. + var ( + update = map[string]string{"k1": "v1-b", "k3": "v3-b"} + insert = map[string]string{"k5": "v5-b"} + delete = map[string]string{"k2": ""} + ) + for k, v := range update { + db.Put([]byte(k), []byte(v)) + } + for k, v := range insert { + db.Put([]byte(k), []byte(v)) + } + for k := range delete { + db.Delete([]byte(k)) + } + for k, v := range initial { + got, err := snapshot.Get([]byte(k)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, []byte(v)) { + t.Fatalf("Unexpected value want: %v, got %v", v, got) + } + } + for k := range insert { + got, err := snapshot.Get([]byte(k)) + if err == nil || len(got) != 0 { + t.Fatal("Unexpected value") + } + } + for k := range delete { + got, err := snapshot.Get([]byte(k)) + if err != nil || len(got) == 0 { + t.Fatal("Unexpected deletion") + } + } + }) + + t.Run("OperationsAfterClose", func(t *testing.T) { + db := New() + db.Put([]byte("key"), []byte("value")) + db.Close() + if _, err := db.Get([]byte("key")); err == nil { + t.Fatalf("expected error on Get after Close") + } + if _, err := db.Has([]byte("key")); err == nil { + t.Fatalf("expected error on Get after Close") + } + if err := db.Put([]byte("key2"), []byte("value2")); err == nil { + t.Fatalf("expected error on Put after Close") + } + if err := db.Delete([]byte("key")); err == nil { + t.Fatalf("expected error on Delete after Close") + } + + b := db.NewBatch() + if err := b.Put([]byte("batchkey"), []byte("batchval")); err != nil { + t.Fatalf("expected no error on batch.Put after Close, got %v", err) + } + if err := b.Write(); err == nil { + t.Fatalf("expected error on batch.Write after Close") + } + }) +} + +// BenchDatabaseSuite runs a suite of benchmarks against a KeyValueStore database +// implementation. +func BenchDatabaseSuite(b *testing.B, New func() ethdb.KeyValueStore) { + var ( + keys, vals = makeDataset(1_000_000, 32, 32, false) + sKeys, sVals = makeDataset(1_000_000, 32, 32, true) + ) + // Run benchmarks sequentially + b.Run("Write", func(b *testing.B) { + benchWrite := func(b *testing.B, keys, vals [][]byte) { + b.ResetTimer() + b.ReportAllocs() + + db := New() + defer db.Close() + + for i := 0; i < len(keys); i++ { + db.Put(keys[i], vals[i]) + } + } + b.Run("WriteSorted", func(b *testing.B) { + benchWrite(b, sKeys, sVals) + }) + b.Run("WriteRandom", func(b *testing.B) { + benchWrite(b, keys, vals) + }) + }) + b.Run("Read", func(b *testing.B) { + benchRead := func(b *testing.B, keys, vals [][]byte) { + db := New() + defer db.Close() + + for i := 0; i < len(keys); i++ { + db.Put(keys[i], vals[i]) + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < len(keys); i++ { + db.Get(keys[i]) + } + } + b.Run("ReadSorted", func(b *testing.B) { + benchRead(b, sKeys, sVals) + }) + b.Run("ReadRandom", func(b *testing.B) { + benchRead(b, keys, vals) + }) + }) + b.Run("Iteration", func(b *testing.B) { + benchIteration := func(b *testing.B, keys, vals [][]byte) { + db := New() + defer db.Close() + + for i := 0; i < len(keys); i++ { + db.Put(keys[i], vals[i]) + } + b.ResetTimer() + b.ReportAllocs() + + it := db.NewIterator(nil, nil) + for it.Next() { + } + it.Release() + } + b.Run("IterationSorted", func(b *testing.B) { + benchIteration(b, sKeys, sVals) + }) + b.Run("IterationRandom", func(b *testing.B) { + benchIteration(b, keys, vals) + }) + }) + b.Run("BatchWrite", func(b *testing.B) { + benchBatchWrite := func(b *testing.B, keys, vals [][]byte) { + b.ResetTimer() + b.ReportAllocs() + + db := New() + defer db.Close() + + batch := db.NewBatch() + for i := 0; i < len(keys); i++ { + batch.Put(keys[i], vals[i]) + } + batch.Write() + } + b.Run("BenchWriteSorted", func(b *testing.B) { + benchBatchWrite(b, sKeys, sVals) + }) + b.Run("BenchWriteRandom", func(b *testing.B) { + benchBatchWrite(b, keys, vals) + }) + }) +} + +func iterateKeys(it ethdb.Iterator) []string { + keys := []string{} + for it.Next() { + keys = append(keys, string(it.Key())) + } + sort.Strings(keys) + it.Release() + return keys +} + +// randBytes generates a random blob of data. +func randBytes(len int) []byte { + buf := make([]byte, len) + if n, err := rand.Read(buf); n != len || err != nil { + panic(err) + } + return buf +} + +func makeDataset(size, ksize, vsize int, order bool) ([][]byte, [][]byte) { + var keys [][]byte + var vals [][]byte + for i := 0; i < size; i += 1 { + keys = append(keys, randBytes(ksize)) + vals = append(vals, randBytes(vsize)) + } + if order { + slices.SortFunc(keys, bytes.Compare) + } + return keys, vals +} diff --git a/ethdb/iterator.go b/ethdb/iterator.go new file mode 100644 index 0000000..2b49c93 --- /dev/null +++ b/ethdb/iterator.go @@ -0,0 +1,61 @@ +// 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 ethdb + +// Iterator iterates over a database's key/value pairs in ascending key order. +// +// When it encounters an error any seek will return false and will yield no key/ +// value pairs. The error can be queried by calling the Error method. Calling +// Release is still necessary. +// +// An iterator must be released after use, but it is not necessary to read an +// iterator until exhaustion. An iterator is not safe for concurrent use, but it +// is safe to use multiple iterators concurrently. +type Iterator interface { + // Next moves the iterator to the next key/value pair. It returns whether the + // iterator is exhausted. + Next() bool + + // Error returns any accumulated error. Exhausting all the key/value pairs + // is not considered to be an error. + Error() error + + // Key returns the key of the current key/value pair, or nil if done. The caller + // should not modify the contents of the returned slice, and its contents may + // change on the next call to Next. + Key() []byte + + // Value returns the value of the current key/value pair, or nil if done. The + // caller should not modify the contents of the returned slice, and its contents + // may change on the next call to Next. + Value() []byte + + // Release releases associated resources. Release should always succeed and can + // be called multiple times without causing error. + Release() +} + +// Iteratee wraps the NewIterator methods of a backing data store. +type Iteratee interface { + // NewIterator creates a binary-alphabetical iterator over a subset + // of database content with a particular key prefix, starting at a particular + // initial key (or after, if it does not exist). + // + // Note: This method assumes that the prefix is NOT part of the start, so there's + // no need for the caller to prepend the prefix to the start + NewIterator(prefix []byte, start []byte) Iterator +} diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go new file mode 100644 index 0000000..92838ad --- /dev/null +++ b/ethdb/leveldb/leveldb.go @@ -0,0 +1,523 @@ +// 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 . + +//go:build !js +// +build !js + +// Package leveldb implements the key-value database layer based on LevelDB. +package leveldb + +import ( + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/errors" + "github.com/syndtr/goleveldb/leveldb/filter" + "github.com/syndtr/goleveldb/leveldb/opt" + "github.com/syndtr/goleveldb/leveldb/util" +) + +const ( + // degradationWarnInterval specifies how often warning should be printed if the + // leveldb database cannot keep up with requested writes. + degradationWarnInterval = time.Minute + + // minCache is the minimum amount of memory in megabytes to allocate to leveldb + // read and write caching, split half and half. + minCache = 16 + + // minHandles is the minimum number of files handles to allocate to the open + // database files. + minHandles = 16 + + // metricsGatheringInterval specifies the interval to retrieve leveldb database + // compaction, io and pause stats to report to the user. + metricsGatheringInterval = 3 * time.Second +) + +// Database is a persistent key-value store. Apart from basic data storage +// functionality it also supports batch writes and iterating over the keyspace in +// binary-alphabetical order. +type Database struct { + fn string // filename for reporting + db *leveldb.DB // LevelDB instance + + compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction + compReadMeter metrics.Meter // Meter for measuring the data read during compaction + compWriteMeter metrics.Meter // Meter for measuring the data written during compaction + writeDelayNMeter metrics.Meter // Meter for measuring the write delay number due to database compaction + writeDelayMeter metrics.Meter // Meter for measuring the write delay duration due to database compaction + diskSizeGauge metrics.Gauge // Gauge for tracking the size of all the levels in the database + diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read + diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written + memCompGauge metrics.Gauge // Gauge for tracking the number of memory compaction + level0CompGauge metrics.Gauge // Gauge for tracking the number of table compaction in level0 + nonlevel0CompGauge metrics.Gauge // Gauge for tracking the number of table compaction in non0 level + seekCompGauge metrics.Gauge // Gauge for tracking the number of table compaction caused by read opt + manualMemAllocGauge metrics.Gauge // Gauge to track the amount of memory that has been manually allocated (not a part of runtime/GC) + + levelsGauge []metrics.Gauge // Gauge for tracking the number of tables in levels + + quitLock sync.Mutex // Mutex protecting the quit channel access + quitChan chan chan error // Quit channel to stop the metrics collection before closing the database + + log log.Logger // Contextual logger tracking the database path +} + +// New returns a wrapped LevelDB 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) { + return NewCustom(file, namespace, func(options *opt.Options) { + // Ensure we have some minimal caching and file guarantees + if cache < minCache { + cache = minCache + } + if handles < minHandles { + handles = minHandles + } + // Set default options + options.OpenFilesCacheCapacity = handles + options.BlockCacheCapacity = cache / 2 * opt.MiB + options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally + if readonly { + options.ReadOnly = true + } + }) +} + +// NewCustom returns a wrapped LevelDB object. The namespace is the prefix that the +// metrics reporting should use for surfacing internal stats. +// The customize function allows the caller to modify the leveldb options. +func NewCustom(file string, namespace string, customize func(options *opt.Options)) (*Database, error) { + options := configureOptions(customize) + logger := log.New("database", file) + usedCache := options.GetBlockCacheCapacity() + options.GetWriteBuffer()*2 + logCtx := []interface{}{"cache", common.StorageSize(usedCache), "handles", options.GetOpenFilesCacheCapacity()} + if options.ReadOnly { + logCtx = append(logCtx, "readonly", "true") + } + logger.Info("Allocated cache and file handles", logCtx...) + + // Open the db and recover any potential corruptions + db, err := leveldb.OpenFile(file, options) + if _, corrupted := err.(*errors.ErrCorrupted); corrupted { + db, err = leveldb.RecoverFile(file, nil) + } + if err != nil { + return nil, err + } + // Assemble the wrapper with all the registered metrics + ldb := &Database{ + fn: file, + db: db, + log: logger, + quitChan: make(chan chan error), + } + ldb.compTimeMeter = metrics.NewRegisteredMeter(namespace+"compact/time", nil) + ldb.compReadMeter = metrics.NewRegisteredMeter(namespace+"compact/input", nil) + ldb.compWriteMeter = metrics.NewRegisteredMeter(namespace+"compact/output", nil) + ldb.diskSizeGauge = metrics.NewRegisteredGauge(namespace+"disk/size", nil) + ldb.diskReadMeter = metrics.NewRegisteredMeter(namespace+"disk/read", nil) + ldb.diskWriteMeter = metrics.NewRegisteredMeter(namespace+"disk/write", nil) + ldb.writeDelayMeter = metrics.NewRegisteredMeter(namespace+"compact/writedelay/duration", nil) + ldb.writeDelayNMeter = metrics.NewRegisteredMeter(namespace+"compact/writedelay/counter", nil) + ldb.memCompGauge = metrics.NewRegisteredGauge(namespace+"compact/memory", nil) + ldb.level0CompGauge = metrics.NewRegisteredGauge(namespace+"compact/level0", nil) + ldb.nonlevel0CompGauge = metrics.NewRegisteredGauge(namespace+"compact/nonlevel0", nil) + ldb.seekCompGauge = metrics.NewRegisteredGauge(namespace+"compact/seek", nil) + ldb.manualMemAllocGauge = metrics.NewRegisteredGauge(namespace+"memory/manualalloc", nil) + + // Start up the metrics gathering and return + go ldb.meter(metricsGatheringInterval, namespace) + return ldb, nil +} + +// configureOptions sets some default options, then runs the provided setter. +func configureOptions(customizeFn func(*opt.Options)) *opt.Options { + // Set default options + options := &opt.Options{ + Filter: filter.NewBloomFilter(10), + DisableSeeksCompaction: true, + } + // Allow caller to make custom modifications to the options + if customizeFn != nil { + customizeFn(options) + } + return options +} + +// Close stops the metrics collection, flushes any pending data to disk and closes +// all io accesses to the underlying key-value store. +func (db *Database) Close() error { + db.quitLock.Lock() + defer db.quitLock.Unlock() + + if db.quitChan != nil { + errc := make(chan error) + db.quitChan <- errc + if err := <-errc; err != nil { + db.log.Error("Metrics collection failed", "err", err) + } + db.quitChan = nil + } + return db.db.Close() +} + +// Has retrieves if a key is present in the key-value store. +func (db *Database) Has(key []byte) (bool, error) { + return db.db.Has(key, nil) +} + +// Get retrieves the given key if it's present in the key-value store. +func (db *Database) Get(key []byte) ([]byte, error) { + dat, err := db.db.Get(key, nil) + if err != nil { + return nil, err + } + return dat, nil +} + +// Put inserts the given value into the key-value store. +func (db *Database) Put(key []byte, value []byte) error { + return db.db.Put(key, value, nil) +} + +// Delete removes the key from the key-value store. +func (db *Database) Delete(key []byte) error { + return db.db.Delete(key, nil) +} + +// NewBatch creates a write-only key-value store that buffers changes to its host +// database until a final write is called. +func (db *Database) NewBatch() ethdb.Batch { + return &batch{ + db: db.db, + b: new(leveldb.Batch), + } +} + +// NewBatchWithSize creates a write-only database batch with pre-allocated buffer. +func (db *Database) NewBatchWithSize(size int) ethdb.Batch { + return &batch{ + db: db.db, + b: leveldb.MakeBatch(size), + } +} + +// NewIterator creates a binary-alphabetical iterator over a subset +// of database content with a particular key prefix, starting at a particular +// initial key (or after, if it does not exist). +func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + return db.db.NewIterator(bytesPrefixRange(prefix, start), nil) +} + +// NewSnapshot creates a database snapshot based on the current state. +// The created snapshot will not be affected by all following mutations +// happened on the database. +// Note don't forget to release the snapshot once it's used up, otherwise +// the stale data will never be cleaned up by the underlying compactor. +func (db *Database) NewSnapshot() (ethdb.Snapshot, error) { + snap, err := db.db.GetSnapshot() + if err != nil { + return nil, err + } + return &snapshot{db: snap}, nil +} + +// Stat returns the statistic data of the database. +func (db *Database) Stat() (string, error) { + var stats leveldb.DBStats + if err := db.db.Stats(&stats); err != nil { + return "", err + } + var ( + message string + totalRead int64 + totalWrite int64 + totalSize int64 + totalTables int + totalDuration time.Duration + ) + if len(stats.LevelSizes) > 0 { + message += " Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB)\n" + + "-------+------------+---------------+---------------+---------------+---------------\n" + for level, size := range stats.LevelSizes { + read := stats.LevelRead[level] + write := stats.LevelWrite[level] + duration := stats.LevelDurations[level] + tables := stats.LevelTablesCounts[level] + + if tables == 0 && duration == 0 { + continue + } + totalTables += tables + totalSize += size + totalRead += read + totalWrite += write + totalDuration += duration + message += fmt.Sprintf(" %3d | %10d | %13.5f | %13.5f | %13.5f | %13.5f\n", + level, tables, float64(size)/1048576.0, duration.Seconds(), + float64(read)/1048576.0, float64(write)/1048576.0) + } + message += "-------+------------+---------------+---------------+---------------+---------------\n" + message += fmt.Sprintf(" Total | %10d | %13.5f | %13.5f | %13.5f | %13.5f\n", + totalTables, float64(totalSize)/1048576.0, totalDuration.Seconds(), + float64(totalRead)/1048576.0, float64(totalWrite)/1048576.0) + message += "-------+------------+---------------+---------------+---------------+---------------\n\n" + } + message += fmt.Sprintf("Read(MB):%.5f Write(MB):%.5f\n", float64(stats.IORead)/1048576.0, float64(stats.IOWrite)/1048576.0) + message += fmt.Sprintf("BlockCache(MB):%.5f FileCache:%d\n", float64(stats.BlockCacheSize)/1048576.0, stats.OpenedTablesCount) + message += fmt.Sprintf("MemoryCompaction:%d Level0Compaction:%d NonLevel0Compaction:%d SeekCompaction:%d\n", stats.MemComp, stats.Level0Comp, stats.NonLevel0Comp, stats.SeekComp) + message += fmt.Sprintf("WriteDelayCount:%d WriteDelayDuration:%s Paused:%t\n", stats.WriteDelayCount, common.PrettyDuration(stats.WriteDelayDuration), stats.WritePaused) + message += fmt.Sprintf("Snapshots:%d Iterators:%d\n", stats.AliveSnapshots, stats.AliveIterators) + return message, nil +} + +// Compact flattens the underlying data store for the given key range. In essence, +// deleted and overwritten versions are discarded, and the data is rearranged to +// reduce the cost of operations needed to access them. +// +// A nil start is treated as a key before all keys in the data store; a nil limit +// is treated as a key after all keys in the data store. If both is nil then it +// will compact entire data store. +func (db *Database) Compact(start []byte, limit []byte) error { + return db.db.CompactRange(util.Range{Start: start, Limit: limit}) +} + +// Path returns the path to the database directory. +func (db *Database) Path() string { + return db.fn +} + +// meter periodically retrieves internal leveldb counters and reports them to +// the metrics subsystem. +func (db *Database) meter(refresh time.Duration, namespace string) { + // Create the counters to store current and previous compaction values + compactions := make([][]int64, 2) + for i := 0; i < 2; i++ { + compactions[i] = make([]int64, 4) + } + // Create storages for states and warning log tracer. + var ( + errc chan error + merr error + + stats leveldb.DBStats + iostats [2]int64 + delaystats [2]int64 + lastWritePaused time.Time + ) + timer := time.NewTimer(refresh) + defer timer.Stop() + + // Iterate ad infinitum and collect the stats + for i := 1; errc == nil && merr == nil; i++ { + // Retrieve the database stats + // Stats method resets buffers inside therefore it's okay to just pass the struct. + err := db.db.Stats(&stats) + if err != nil { + db.log.Error("Failed to read database stats", "err", err) + merr = err + continue + } + // Iterate over all the leveldbTable rows, and accumulate the entries + for j := 0; j < len(compactions[i%2]); j++ { + compactions[i%2][j] = 0 + } + compactions[i%2][0] = stats.LevelSizes.Sum() + for _, t := range stats.LevelDurations { + compactions[i%2][1] += t.Nanoseconds() + } + compactions[i%2][2] = stats.LevelRead.Sum() + compactions[i%2][3] = stats.LevelWrite.Sum() + // Update all the requested meters + if db.diskSizeGauge != nil { + db.diskSizeGauge.Update(compactions[i%2][0]) + } + if db.compTimeMeter != nil { + db.compTimeMeter.Mark(compactions[i%2][1] - compactions[(i-1)%2][1]) + } + if db.compReadMeter != nil { + db.compReadMeter.Mark(compactions[i%2][2] - compactions[(i-1)%2][2]) + } + if db.compWriteMeter != nil { + db.compWriteMeter.Mark(compactions[i%2][3] - compactions[(i-1)%2][3]) + } + var ( + delayN = int64(stats.WriteDelayCount) + duration = stats.WriteDelayDuration + paused = stats.WritePaused + ) + if db.writeDelayNMeter != nil { + db.writeDelayNMeter.Mark(delayN - delaystats[0]) + } + if db.writeDelayMeter != nil { + db.writeDelayMeter.Mark(duration.Nanoseconds() - delaystats[1]) + } + // If a warning that db is performing compaction has been displayed, any subsequent + // warnings will be withheld for one minute not to overwhelm the user. + if paused && delayN-delaystats[0] == 0 && duration.Nanoseconds()-delaystats[1] == 0 && + time.Now().After(lastWritePaused.Add(degradationWarnInterval)) { + db.log.Warn("Database compacting, degraded performance") + lastWritePaused = time.Now() + } + delaystats[0], delaystats[1] = delayN, duration.Nanoseconds() + + var ( + nRead = int64(stats.IORead) + nWrite = int64(stats.IOWrite) + ) + if db.diskReadMeter != nil { + db.diskReadMeter.Mark(nRead - iostats[0]) + } + if db.diskWriteMeter != nil { + db.diskWriteMeter.Mark(nWrite - iostats[1]) + } + iostats[0], iostats[1] = nRead, nWrite + + db.memCompGauge.Update(int64(stats.MemComp)) + db.level0CompGauge.Update(int64(stats.Level0Comp)) + db.nonlevel0CompGauge.Update(int64(stats.NonLevel0Comp)) + db.seekCompGauge.Update(int64(stats.SeekComp)) + + for i, tables := range stats.LevelTablesCounts { + // Append metrics for additional layers + if i >= len(db.levelsGauge) { + db.levelsGauge = append(db.levelsGauge, metrics.NewRegisteredGauge(namespace+fmt.Sprintf("tables/level%v", i), nil)) + } + db.levelsGauge[i].Update(int64(tables)) + } + + // Sleep a bit, then repeat the stats collection + select { + case errc = <-db.quitChan: + // Quit requesting, stop hammering the database + case <-timer.C: + timer.Reset(refresh) + // Timeout, gather a new set of stats + } + } + + if errc == nil { + errc = <-db.quitChan + } + errc <- merr +} + +// batch is a write-only leveldb batch that commits changes to its host database +// when Write is called. A batch cannot be used concurrently. +type batch struct { + db *leveldb.DB + b *leveldb.Batch + size int +} + +// Put inserts the given value into the batch for later committing. +func (b *batch) Put(key, value []byte) error { + b.b.Put(key, value) + b.size += len(key) + len(value) + return nil +} + +// Delete inserts the key removal into the batch for later committing. +func (b *batch) Delete(key []byte) error { + b.b.Delete(key) + b.size += len(key) + return nil +} + +// ValueSize retrieves the amount of data queued up for writing. +func (b *batch) ValueSize() int { + return b.size +} + +// Write flushes any accumulated data to disk. +func (b *batch) Write() error { + return b.db.Write(b.b, nil) +} + +// Reset resets the batch for reuse. +func (b *batch) Reset() { + b.b.Reset() + b.size = 0 +} + +// Replay replays the batch contents. +func (b *batch) Replay(w ethdb.KeyValueWriter) error { + return b.b.Replay(&replayer{writer: w}) +} + +// replayer is a small wrapper to implement the correct replay methods. +type replayer struct { + writer ethdb.KeyValueWriter + failure error +} + +// Put inserts the given value into the key-value data store. +func (r *replayer) Put(key, value []byte) { + // If the replay already failed, stop executing ops + if r.failure != nil { + return + } + r.failure = r.writer.Put(key, value) +} + +// Delete removes the key from the key-value data store. +func (r *replayer) Delete(key []byte) { + // If the replay already failed, stop executing ops + if r.failure != nil { + return + } + r.failure = r.writer.Delete(key) +} + +// bytesPrefixRange returns key range that satisfy +// - the given prefix, and +// - the given seek position +func bytesPrefixRange(prefix, start []byte) *util.Range { + r := util.BytesPrefix(prefix) + r.Start = append(r.Start, start...) + return r +} + +// snapshot wraps a leveldb snapshot for implementing the Snapshot interface. +type snapshot struct { + db *leveldb.Snapshot +} + +// Has retrieves if a key is present in the snapshot backing by a key-value +// data store. +func (snap *snapshot) Has(key []byte) (bool, error) { + return snap.db.Has(key, nil) +} + +// Get retrieves the given key if it's present in the snapshot backing by +// key-value data store. +func (snap *snapshot) Get(key []byte) ([]byte, error) { + return snap.db.Get(key, nil) +} + +// Release releases associated resources. Release should always succeed and can +// be called multiple times without causing error. +func (snap *snapshot) Release() { + snap.db.Release() +} diff --git a/ethdb/leveldb/leveldb_test.go b/ethdb/leveldb/leveldb_test.go new file mode 100644 index 0000000..d8c6386 --- /dev/null +++ b/ethdb/leveldb/leveldb_test.go @@ -0,0 +1,52 @@ +// 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 leveldb + +import ( + "testing" + + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/dbtest" + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/storage" +) + +func TestLevelDB(t *testing.T) { + t.Run("DatabaseSuite", func(t *testing.T) { + dbtest.TestDatabaseSuite(t, func() ethdb.KeyValueStore { + db, err := leveldb.Open(storage.NewMemStorage(), nil) + if err != nil { + t.Fatal(err) + } + return &Database{ + db: db, + } + }) + }) +} + +func BenchmarkLevelDB(b *testing.B) { + dbtest.BenchDatabaseSuite(b, func() ethdb.KeyValueStore { + db, err := leveldb.Open(storage.NewMemStorage(), nil) + if err != nil { + b.Fatal(err) + } + return &Database{ + db: db, + } + }) +} diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go new file mode 100644 index 0000000..9b0872f --- /dev/null +++ b/ethdb/memorydb/memorydb.go @@ -0,0 +1,390 @@ +// 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 memorydb implements the key-value database layer based on memory maps. +package memorydb + +import ( + "errors" + "sort" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +var ( + // errMemorydbClosed is returned if a memory database was already closed at the + // invocation of a data access operation. + errMemorydbClosed = errors.New("database closed") + + // errMemorydbNotFound is returned if a key is requested that is not found in + // the provided memory database. + errMemorydbNotFound = errors.New("not found") + + // errSnapshotReleased is returned if callers want to retrieve data from a + // released snapshot. + errSnapshotReleased = errors.New("snapshot released") +) + +// Database is an ephemeral key-value store. Apart from basic data storage +// functionality it also supports batch writes and iterating over the keyspace in +// binary-alphabetical order. +type Database struct { + db map[string][]byte + lock sync.RWMutex +} + +// New returns a wrapped map with all the required database interface methods +// implemented. +func New() *Database { + return &Database{ + db: make(map[string][]byte), + } +} + +// NewWithCap returns a wrapped map pre-allocated to the provided capacity with +// all the required database interface methods implemented. +func NewWithCap(size int) *Database { + return &Database{ + db: make(map[string][]byte, size), + } +} + +// Close deallocates the internal map and ensures any consecutive data access op +// fails with an error. +func (db *Database) Close() error { + db.lock.Lock() + defer db.lock.Unlock() + + db.db = nil + return nil +} + +// Has retrieves if a key is present in the key-value store. +func (db *Database) Has(key []byte) (bool, error) { + db.lock.RLock() + defer db.lock.RUnlock() + + if db.db == nil { + return false, errMemorydbClosed + } + _, ok := db.db[string(key)] + return ok, nil +} + +// Get retrieves the given key if it's present in the key-value store. +func (db *Database) Get(key []byte) ([]byte, error) { + db.lock.RLock() + defer db.lock.RUnlock() + + if db.db == nil { + return nil, errMemorydbClosed + } + if entry, ok := db.db[string(key)]; ok { + return common.CopyBytes(entry), nil + } + return nil, errMemorydbNotFound +} + +// Put inserts the given value into the key-value store. +func (db *Database) Put(key []byte, value []byte) error { + db.lock.Lock() + defer db.lock.Unlock() + + if db.db == nil { + return errMemorydbClosed + } + db.db[string(key)] = common.CopyBytes(value) + return nil +} + +// Delete removes the key from the key-value store. +func (db *Database) Delete(key []byte) error { + db.lock.Lock() + defer db.lock.Unlock() + + if db.db == nil { + return errMemorydbClosed + } + delete(db.db, string(key)) + return nil +} + +// NewBatch creates a write-only key-value store that buffers changes to its host +// database until a final write is called. +func (db *Database) NewBatch() ethdb.Batch { + return &batch{ + db: db, + } +} + +// NewBatchWithSize creates a write-only database batch with pre-allocated buffer. +func (db *Database) NewBatchWithSize(size int) ethdb.Batch { + return &batch{ + db: db, + } +} + +// NewIterator creates a binary-alphabetical iterator over a subset +// of database content with a particular key prefix, starting at a particular +// initial key (or after, if it does not exist). +func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + db.lock.RLock() + defer db.lock.RUnlock() + + var ( + pr = string(prefix) + st = string(append(prefix, start...)) + keys = make([]string, 0, len(db.db)) + values = make([][]byte, 0, len(db.db)) + ) + // Collect the keys from the memory database corresponding to the given prefix + // and start + for key := range db.db { + if !strings.HasPrefix(key, pr) { + continue + } + if key >= st { + keys = append(keys, key) + } + } + // Sort the items and retrieve the associated values + sort.Strings(keys) + for _, key := range keys { + values = append(values, db.db[key]) + } + return &iterator{ + index: -1, + keys: keys, + values: values, + } +} + +// NewSnapshot creates a database snapshot based on the current state. +// The created snapshot will not be affected by all following mutations +// happened on the database. +func (db *Database) NewSnapshot() (ethdb.Snapshot, error) { + return newSnapshot(db), nil +} + +// Stat returns the statistic data of the database. +func (db *Database) Stat() (string, error) { + return "", nil +} + +// Compact is not supported on a memory database, but there's no need either as +// a memory database doesn't waste space anyway. +func (db *Database) Compact(start []byte, limit []byte) error { + return nil +} + +// Len returns the number of entries currently present in the memory database. +// +// Note, this method is only used for testing (i.e. not public in general) and +// does not have explicit checks for closed-ness to allow simpler testing code. +func (db *Database) Len() int { + db.lock.RLock() + defer db.lock.RUnlock() + + return len(db.db) +} + +// keyvalue is a key-value tuple tagged with a deletion field to allow creating +// memory-database write batches. +type keyvalue struct { + key string + value []byte + delete bool +} + +// batch is a write-only memory batch that commits changes to its host +// database when Write is called. A batch cannot be used concurrently. +type batch struct { + db *Database + writes []keyvalue + size int +} + +// 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{string(key), common.CopyBytes(value), false}) + b.size += len(key) + len(value) + return nil +} + +// Delete inserts the key removal into the batch for later committing. +func (b *batch) Delete(key []byte) error { + b.writes = append(b.writes, keyvalue{string(key), nil, true}) + b.size += len(key) + return nil +} + +// ValueSize retrieves the amount of data queued up for writing. +func (b *batch) ValueSize() int { + return b.size +} + +// Write flushes any accumulated data to the memory database. +func (b *batch) Write() error { + b.db.lock.Lock() + defer b.db.lock.Unlock() + + if b.db.db == nil { + return errMemorydbClosed + } + for _, keyvalue := range b.writes { + if keyvalue.delete { + delete(b.db.db, keyvalue.key) + continue + } + b.db.db[keyvalue.key] = keyvalue.value + } + return nil +} + +// Reset resets the batch for reuse. +func (b *batch) Reset() { + b.writes = b.writes[:0] + b.size = 0 +} + +// Replay replays the batch contents. +func (b *batch) Replay(w ethdb.KeyValueWriter) error { + for _, keyvalue := range b.writes { + if keyvalue.delete { + if err := w.Delete([]byte(keyvalue.key)); err != nil { + return err + } + continue + } + if err := w.Put([]byte(keyvalue.key), keyvalue.value); err != nil { + return err + } + } + return nil +} + +// iterator can walk over the (potentially partial) keyspace of a memory key +// value store. Internally it is a deep copy of the entire iterated state, +// sorted by keys. +type iterator struct { + index int + keys []string + values [][]byte +} + +// Next moves the iterator to the next key/value pair. It returns whether the +// iterator is exhausted. +func (it *iterator) Next() bool { + // Short circuit if iterator is already exhausted in the forward direction. + if it.index >= len(it.keys) { + return false + } + it.index += 1 + return it.index < len(it.keys) +} + +// Error returns any accumulated error. Exhausting all the key/value pairs +// is not considered to be an error. A memory iterator cannot encounter errors. +func (it *iterator) Error() error { + return nil +} + +// Key returns the key of the current key/value pair, or nil if done. The caller +// should not modify the contents of the returned slice, and its contents may +// change on the next call to Next. +func (it *iterator) Key() []byte { + // Short circuit if iterator is not in a valid position + if it.index < 0 || it.index >= len(it.keys) { + return nil + } + return []byte(it.keys[it.index]) +} + +// Value returns the value of the current key/value pair, or nil if done. The +// caller should not modify the contents of the returned slice, and its contents +// may change on the next call to Next. +func (it *iterator) Value() []byte { + // Short circuit if iterator is not in a valid position + if it.index < 0 || it.index >= len(it.keys) { + return nil + } + return it.values[it.index] +} + +// Release releases associated resources. Release should always succeed and can +// be called multiple times without causing error. +func (it *iterator) Release() { + it.index, it.keys, it.values = -1, nil, nil +} + +// snapshot wraps a batch of key-value entries deep copied from the in-memory +// database for implementing the Snapshot interface. +type snapshot struct { + db map[string][]byte + lock sync.RWMutex +} + +// newSnapshot initializes the snapshot with the given database instance. +func newSnapshot(db *Database) *snapshot { + db.lock.RLock() + defer db.lock.RUnlock() + + copied := make(map[string][]byte, len(db.db)) + for key, val := range db.db { + copied[key] = common.CopyBytes(val) + } + return &snapshot{db: copied} +} + +// Has retrieves if a key is present in the snapshot backing by a key-value +// data store. +func (snap *snapshot) Has(key []byte) (bool, error) { + snap.lock.RLock() + defer snap.lock.RUnlock() + + if snap.db == nil { + return false, errSnapshotReleased + } + _, ok := snap.db[string(key)] + return ok, nil +} + +// Get retrieves the given key if it's present in the snapshot backing by +// key-value data store. +func (snap *snapshot) Get(key []byte) ([]byte, error) { + snap.lock.RLock() + defer snap.lock.RUnlock() + + if snap.db == nil { + return nil, errSnapshotReleased + } + if entry, ok := snap.db[string(key)]; ok { + return common.CopyBytes(entry), nil + } + return nil, errMemorydbNotFound +} + +// Release releases associated resources. Release should always succeed and can +// be called multiple times without causing error. +func (snap *snapshot) Release() { + snap.lock.Lock() + defer snap.lock.Unlock() + + snap.db = nil +} diff --git a/ethdb/memorydb/memorydb_test.go b/ethdb/memorydb/memorydb_test.go new file mode 100644 index 0000000..51499c3 --- /dev/null +++ b/ethdb/memorydb/memorydb_test.go @@ -0,0 +1,50 @@ +// 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 memorydb + +import ( + "encoding/binary" + "testing" + + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/dbtest" +) + +func TestMemoryDB(t *testing.T) { + t.Run("DatabaseSuite", func(t *testing.T) { + dbtest.TestDatabaseSuite(t, func() ethdb.KeyValueStore { + return New() + }) + }) +} + +// 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 new file mode 100644 index 0000000..130d661 --- /dev/null +++ b/ethdb/pebble/pebble.go @@ -0,0 +1,684 @@ +// 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 pebble implements the key-value database layer based on pebble. +package pebble + +import ( + "bytes" + "fmt" + "runtime" + "sync" + "sync/atomic" + "time" + + "github.com/cockroachdb/pebble" + "github.com/cockroachdb/pebble/bloom" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +const ( + // minCache is the minimum amount of memory in megabytes to allocate to pebble + // read and write caching, split half and half. + minCache = 16 + + // minHandles is the minimum number of files handles to allocate to the open + // database files. + minHandles = 16 + + // metricsGatheringInterval specifies the interval to retrieve pebble database + // compaction, io and pause stats to report to the user. + metricsGatheringInterval = 3 * time.Second + + // degradationWarnInterval specifies how often warning should be printed if the + // leveldb database cannot keep up with requested writes. + degradationWarnInterval = time.Minute +) + +// Database is a persistent key-value store based on the pebble storage engine. +// Apart from basic data storage functionality it also supports batch writes and +// iterating over the keyspace in binary-alphabetical order. +type Database struct { + fn string // filename for reporting + db *pebble.DB // Underlying pebble storage engine + + compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction + compReadMeter metrics.Meter // Meter for measuring the data read during compaction + compWriteMeter metrics.Meter // Meter for measuring the data written during compaction + writeDelayNMeter metrics.Meter // Meter for measuring the write delay number due to database compaction + writeDelayMeter metrics.Meter // Meter for measuring the write delay duration due to database compaction + diskSizeGauge metrics.Gauge // Gauge for tracking the size of all the levels in the database + diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read + diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written + memCompGauge metrics.Gauge // Gauge for tracking the number of memory compaction + level0CompGauge metrics.Gauge // Gauge for tracking the number of table compaction in level0 + nonlevel0CompGauge metrics.Gauge // Gauge for tracking the number of table compaction in non0 level + 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 + + log log.Logger // Contextual logger tracking the database path + + activeComp int // Current number of active compactions + compStartTime time.Time // The start time of the earliest currently-active compaction + compTime atomic.Int64 // Total time spent in compaction in ns + level0Comp atomic.Uint32 // Total number of level-zero compactions + nonLevel0Comp atomic.Uint32 // Total number of non level-zero compactions + + writeStalled atomic.Bool // Flag whether the write is stalled + 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) { + if d.activeComp == 0 { + d.compStartTime = time.Now() + } + l0 := info.Input[0] + if l0.Level == 0 { + d.level0Comp.Add(1) + } else { + d.nonLevel0Comp.Add(1) + } + d.activeComp++ +} + +func (d *Database) onCompactionEnd(info pebble.CompactionInfo) { + if d.activeComp == 1 { + d.compTime.Add(int64(time.Since(d.compStartTime))) + } else if d.activeComp == 0 { + panic("should not happen") + } + d.activeComp-- +} + +func (d *Database) onWriteStallBegin(b pebble.WriteStallBeginInfo) { + d.writeDelayStartTime = time.Now() + d.writeDelayCount.Add(1) + d.writeStalled.Store(true) +} + +func (d *Database) onWriteStallEnd() { + d.writeDelayTime.Add(int64(time.Since(d.writeDelayStartTime))) + d.writeStalled.Store(false) +} + +// 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, ephemeral bool) (*Database, error) { + // Ensure we have some minimal caching and file guarantees + if cache < minCache { + cache = minCache + } + if handles < minHandles { + handles = minHandles + } + logger := log.New("database", file) + logger.Info("Allocated cache and file handles", "cache", common.StorageSize(cache*1024*1024), "handles", handles) + + // The max memtable size is limited by the uint32 offsets stored in + // internal/arenaskl.node, DeferredBatchOp, and flushableBatchEntry. + // + // - 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 + + // 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), + writeOptions: &pebble.WriteOptions{Sync: !ephemeral}, + } + opt := &pebble.Options{ + // Pebble has a single combined cache area and the write + // buffers are taken from this too. Assign all available + // memory allowance for cache. + Cache: pebble.NewCache(int64(cache * 1024 * 1024)), + MaxOpenFiles: handles, + + // The size of memory table(as well as the write buffer). + // Note, there may have more than two memory tables in the system. + MemTableSize: uint64(memTableSize), + + // MemTableStopWritesThreshold places a hard limit on the size + // of the existent MemTables(including the frozen one). + // Note, this must be the number of tables not the size of all memtables + // according to https://github.com/cockroachdb/pebble/blob/master/options.go#L738-L742 + // and to https://github.com/cockroachdb/pebble/blob/master/db.go#L1892-L1903. + MemTableStopWritesThreshold: memTableLimit, + + // The default compaction concurrency(1 thread), + // Here use all available CPUs for faster compaction. + MaxConcurrentCompactions: runtime.NumCPU, + + // Per-level options. Options for at least one level must be specified. The + // options for the last level are used for all subsequent levels. + Levels: []pebble.LevelOptions{ + {TargetFileSize: 2 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)}, + {TargetFileSize: 2 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)}, + {TargetFileSize: 2 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)}, + {TargetFileSize: 2 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)}, + {TargetFileSize: 2 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)}, + {TargetFileSize: 2 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)}, + {TargetFileSize: 2 * 1024 * 1024, FilterPolicy: bloom.FilterPolicy(10)}, + }, + ReadOnly: readonly, + EventListener: &pebble.EventListener{ + CompactionBegin: db.onCompactionBegin, + CompactionEnd: db.onCompactionEnd, + 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. + opt.Experimental.ReadSamplingMultiplier = -1 + + // Open the db and recover any potential corruptions + innerDB, err := pebble.Open(file, opt) + if err != nil { + return nil, err + } + db.db = innerDB + + db.compTimeMeter = metrics.GetOrRegisterMeter(namespace+"compact/time", nil) + db.compReadMeter = metrics.GetOrRegisterMeter(namespace+"compact/input", nil) + db.compWriteMeter = metrics.GetOrRegisterMeter(namespace+"compact/output", nil) + db.diskSizeGauge = metrics.GetOrRegisterGauge(namespace+"disk/size", nil) + db.diskReadMeter = metrics.GetOrRegisterMeter(namespace+"disk/read", nil) + db.diskWriteMeter = metrics.GetOrRegisterMeter(namespace+"disk/write", nil) + db.writeDelayMeter = metrics.GetOrRegisterMeter(namespace+"compact/writedelay/duration", nil) + db.writeDelayNMeter = metrics.GetOrRegisterMeter(namespace+"compact/writedelay/counter", nil) + db.memCompGauge = metrics.GetOrRegisterGauge(namespace+"compact/memory", nil) + db.level0CompGauge = metrics.GetOrRegisterGauge(namespace+"compact/level0", nil) + db.nonlevel0CompGauge = metrics.GetOrRegisterGauge(namespace+"compact/nonlevel0", nil) + db.seekCompGauge = metrics.GetOrRegisterGauge(namespace+"compact/seek", nil) + db.manualMemAllocGauge = metrics.GetOrRegisterGauge(namespace+"memory/manualalloc", nil) + + // Start up the metrics gathering and return + go db.meter(metricsGatheringInterval, namespace) + return db, nil +} + +// Close stops the metrics collection, flushes any pending data to disk and closes +// all io accesses to the underlying key-value store. +func (d *Database) Close() error { + d.quitLock.Lock() + defer d.quitLock.Unlock() + // Allow double closing, simplifies things + if d.closed { + return nil + } + d.closed = true + if d.quitChan != nil { + errc := make(chan error) + d.quitChan <- errc + if err := <-errc; err != nil { + d.log.Error("Metrics collection failed", "err", err) + } + d.quitChan = nil + } + return d.db.Close() +} + +// Has retrieves if a key is present in the key-value store. +func (d *Database) Has(key []byte) (bool, error) { + d.quitLock.RLock() + defer d.quitLock.RUnlock() + if d.closed { + return false, pebble.ErrClosed + } + _, closer, err := d.db.Get(key) + if err == pebble.ErrNotFound { + return false, nil + } else if err != nil { + return false, err + } + closer.Close() + return true, nil +} + +// Get retrieves the given key if it's present in the key-value store. +func (d *Database) Get(key []byte) ([]byte, error) { + d.quitLock.RLock() + defer d.quitLock.RUnlock() + if d.closed { + return nil, pebble.ErrClosed + } + dat, closer, err := d.db.Get(key) + if err != nil { + return nil, err + } + ret := make([]byte, len(dat)) + copy(ret, dat) + closer.Close() + return ret, nil +} + +// Put inserts the given value into the key-value store. +func (d *Database) Put(key []byte, value []byte) error { + d.quitLock.RLock() + defer d.quitLock.RUnlock() + if d.closed { + return pebble.ErrClosed + } + return d.db.Set(key, value, d.writeOptions) +} + +// Delete removes the key from the key-value store. +func (d *Database) Delete(key []byte) error { + d.quitLock.RLock() + defer d.quitLock.RUnlock() + if d.closed { + return pebble.ErrClosed + } + return d.db.Delete(key, nil) +} + +// NewBatch creates a write-only key-value store that buffers changes to its host +// database until a final write is called. +func (d *Database) NewBatch() ethdb.Batch { + return &batch{ + b: d.db.NewBatch(), + db: d, + } +} + +// NewBatchWithSize creates a write-only database batch with pre-allocated buffer. +func (d *Database) NewBatchWithSize(size int) ethdb.Batch { + return &batch{ + b: d.db.NewBatchWithSize(size), + db: d, + } +} + +// snapshot wraps a pebble snapshot for implementing the Snapshot interface. +type snapshot struct { + db *pebble.Snapshot +} + +// NewSnapshot creates a database snapshot based on the current state. +// The created snapshot will not be affected by all following mutations +// happened on the database. +// Note don't forget to release the snapshot once it's used up, otherwise +// the stale data will never be cleaned up by the underlying compactor. +func (d *Database) NewSnapshot() (ethdb.Snapshot, error) { + snap := d.db.NewSnapshot() + return &snapshot{db: snap}, nil +} + +// Has retrieves if a key is present in the snapshot backing by a key-value +// data store. +func (snap *snapshot) Has(key []byte) (bool, error) { + _, closer, err := snap.db.Get(key) + if err != nil { + if err != pebble.ErrNotFound { + return false, err + } else { + return false, nil + } + } + closer.Close() + return true, nil +} + +// Get retrieves the given key if it's present in the snapshot backing by +// key-value data store. +func (snap *snapshot) Get(key []byte) ([]byte, error) { + dat, closer, err := snap.db.Get(key) + if err != nil { + return nil, err + } + ret := make([]byte, len(dat)) + copy(ret, dat) + closer.Close() + return ret, nil +} + +// Release releases associated resources. Release should always succeed and can +// be called multiple times without causing error. +func (snap *snapshot) Release() { + snap.db.Close() +} + +// upperBound returns the upper bound for the given prefix +func upperBound(prefix []byte) (limit []byte) { + for i := len(prefix) - 1; i >= 0; i-- { + c := prefix[i] + if c == 0xff { + continue + } + limit = make([]byte, i+1) + copy(limit, prefix) + limit[i] = c + 1 + break + } + return limit +} + +// 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. +func (d *Database) Stat() (string, error) { + return d.db.Metrics().String(), nil +} + +// Compact flattens the underlying data store for the given key range. In essence, +// deleted and overwritten versions are discarded, and the data is rearranged to +// reduce the cost of operations needed to access them. +// +// A nil start is treated as a key before all keys in the data store; a nil limit +// is treated as a key after all keys in the data store. If both is nil then it +// will compact entire data store. +func (d *Database) Compact(start []byte, limit []byte) error { + // There is no special flag to represent the end of key range + // in pebble(nil in leveldb). Use an ugly hack to construct a + // large key to represent it. + // Note any prefixed database entry will be smaller than this + // flag, as for trie nodes we need the 32 byte 0xff because + // there might be a shared prefix starting with a number of + // 0xff-s, so 32 ensures than only a hash collision could touch it. + // https://github.com/cockroachdb/pebble/issues/2359#issuecomment-1443995833 + if limit == nil { + limit = bytes.Repeat([]byte{0xff}, 32) + } + return d.db.Compact(start, limit, true) // Parallelization is preferred +} + +// Path returns the path to the database directory. +func (d *Database) Path() string { + return d.fn +} + +// meter periodically retrieves internal pebble counters and reports them to +// the metrics subsystem. +func (d *Database) meter(refresh time.Duration, namespace string) { + var errc chan error + timer := time.NewTimer(refresh) + defer timer.Stop() + + // Create storage and warning log tracer for write delay. + var ( + compTimes [2]int64 + compWrites [2]int64 + compReads [2]int64 + + nWrites [2]int64 + + writeDelayTimes [2]int64 + writeDelayCounts [2]int64 + lastWriteStallReport time.Time + ) + + // Iterate ad infinitum and collect the stats + for i := 1; errc == nil; i++ { + var ( + compWrite int64 + compRead int64 + nWrite int64 + + stats = d.db.Metrics() + compTime = d.compTime.Load() + writeDelayCount = d.writeDelayCount.Load() + writeDelayTime = d.writeDelayTime.Load() + nonLevel0CompCount = int64(d.nonLevel0Comp.Load()) + level0CompCount = int64(d.level0Comp.Load()) + ) + writeDelayTimes[i%2] = writeDelayTime + writeDelayCounts[i%2] = writeDelayCount + compTimes[i%2] = compTime + + for _, levelMetrics := range stats.Levels { + nWrite += int64(levelMetrics.BytesCompacted) + nWrite += int64(levelMetrics.BytesFlushed) + compWrite += int64(levelMetrics.BytesCompacted) + compRead += int64(levelMetrics.BytesRead) + } + + nWrite += int64(stats.WAL.BytesWritten) + + compWrites[i%2] = compWrite + compReads[i%2] = compRead + nWrites[i%2] = nWrite + + if d.writeDelayNMeter != nil { + d.writeDelayNMeter.Mark(writeDelayCounts[i%2] - writeDelayCounts[(i-1)%2]) + } + if d.writeDelayMeter != nil { + d.writeDelayMeter.Mark(writeDelayTimes[i%2] - writeDelayTimes[(i-1)%2]) + } + // Print a warning log if writing has been stalled for a while. The log will + // be printed per minute to avoid overwhelming users. + if d.writeStalled.Load() && writeDelayCounts[i%2] == writeDelayCounts[(i-1)%2] && + time.Now().After(lastWriteStallReport.Add(degradationWarnInterval)) { + d.log.Warn("Database compacting, degraded performance") + lastWriteStallReport = time.Now() + } + if d.compTimeMeter != nil { + d.compTimeMeter.Mark(compTimes[i%2] - compTimes[(i-1)%2]) + } + if d.compReadMeter != nil { + d.compReadMeter.Mark(compReads[i%2] - compReads[(i-1)%2]) + } + if d.compWriteMeter != nil { + d.compWriteMeter.Mark(compWrites[i%2] - compWrites[(i-1)%2]) + } + if d.diskSizeGauge != nil { + d.diskSizeGauge.Update(int64(stats.DiskSpaceUsage())) + } + if d.diskReadMeter != nil { + d.diskReadMeter.Mark(0) // pebble doesn't track non-compaction reads + } + if d.diskWriteMeter != nil { + d.diskWriteMeter.Mark(nWrites[i%2] - nWrites[(i-1)%2]) + } + // See https://github.com/cockroachdb/pebble/pull/1628#pullrequestreview-1026664054 + manuallyAllocated := stats.BlockCache.Size + int64(stats.MemTable.Size) + int64(stats.MemTable.ZombieSize) + d.manualMemAllocGauge.Update(manuallyAllocated) + d.memCompGauge.Update(stats.Flush.Count) + d.nonlevel0CompGauge.Update(nonLevel0CompCount) + d.level0CompGauge.Update(level0CompCount) + 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.GetOrRegisterGauge(namespace+fmt.Sprintf("tables/level%v", i), nil)) + } + d.levelsGauge[i].Update(level.NumFiles) + } + + // Sleep a bit, then repeat the stats collection + select { + case errc = <-d.quitChan: + // Quit requesting, stop hammering the database + case <-timer.C: + timer.Reset(refresh) + // Timeout, gather a new set of stats + } + } + errc <- nil +} + +// batch is a write-only batch that commits changes to its host database +// when Write is called. A batch cannot be used concurrently. +type batch struct { + b *pebble.Batch + db *Database + size int +} + +// Put inserts the given value into the batch for later committing. +func (b *batch) Put(key, value []byte) error { + b.b.Set(key, value, nil) + b.size += len(key) + len(value) + return nil +} + +// Delete inserts the key removal into the batch for later committing. +func (b *batch) Delete(key []byte) error { + b.b.Delete(key, nil) + b.size += len(key) + return nil +} + +// ValueSize retrieves the amount of data queued up for writing. +func (b *batch) ValueSize() int { + return b.size +} + +// Write flushes any accumulated data to disk. +func (b *batch) Write() error { + b.db.quitLock.RLock() + defer b.db.quitLock.RUnlock() + if b.db.closed { + return pebble.ErrClosed + } + return b.b.Commit(b.db.writeOptions) +} + +// Reset resets the batch for reuse. +func (b *batch) Reset() { + b.b.Reset() + b.size = 0 +} + +// Replay replays the batch contents. +func (b *batch) Replay(w ethdb.KeyValueWriter) error { + reader := b.b.Reader() + for { + kind, k, v, ok, err := reader.Next() + if !ok || err != nil { + break + } + // The (k,v) slices might be overwritten if the batch is reset/reused, + // and the receiver should copy them if they are to be retained long-term. + if kind == pebble.InternalKeyKindSet { + w.Put(k, v) + } else if kind == pebble.InternalKeyKindDelete { + w.Delete(k) + } else { + return fmt.Errorf("unhandled operation, keytype: %v", kind) + } + } + return nil +} + +// 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 + 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{ + LowerBound: append(prefix, start...), + UpperBound: upperBound(prefix), + }) + iter.First() + return &pebbleIterator{iter: iter, moved: true, released: false} +} + +// Next moves the iterator to the next key/value pair. It returns whether the +// iterator is exhausted. +func (iter *pebbleIterator) Next() bool { + if iter.moved { + iter.moved = false + return iter.iter.Valid() + } + return iter.iter.Next() +} + +// Error returns any accumulated error. Exhausting all the key/value pairs +// is not considered to be an error. +func (iter *pebbleIterator) Error() error { + return iter.iter.Error() +} + +// Key returns the key of the current key/value pair, or nil if done. The caller +// should not modify the contents of the returned slice, and its contents may +// change on the next call to Next. +func (iter *pebbleIterator) Key() []byte { + return iter.iter.Key() +} + +// Value returns the value of the current key/value pair, or nil if done. The +// caller should not modify the contents of the returned slice, and its contents +// may change on the next call to Next. +func (iter *pebbleIterator) Value() []byte { + return iter.iter.Value() +} + +// Release releases associated resources. Release should always succeed and can +// be called multiple times without causing error. +func (iter *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 new file mode 100644 index 0000000..1d5611f --- /dev/null +++ b/ethdb/pebble/pebble_test.go @@ -0,0 +1,56 @@ +// 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 pebble + +import ( + "testing" + + "github.com/cockroachdb/pebble" + "github.com/cockroachdb/pebble/vfs" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/dbtest" +) + +func TestPebbleDB(t *testing.T) { + t.Run("DatabaseSuite", func(t *testing.T) { + dbtest.TestDatabaseSuite(t, func() ethdb.KeyValueStore { + db, err := pebble.Open("", &pebble.Options{ + FS: vfs.NewMem(), + }) + if err != nil { + t.Fatal(err) + } + return &Database{ + db: db, + } + }) + }) +} + +func BenchmarkPebbleDB(b *testing.B) { + dbtest.BenchDatabaseSuite(b, func() ethdb.KeyValueStore { + db, err := pebble.Open("", &pebble.Options{ + FS: vfs.NewMem(), + }) + if err != nil { + b.Fatal(err) + } + return &Database{ + db: db, + } + }) +} diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go new file mode 100644 index 0000000..bfecde4 --- /dev/null +++ b/ethdb/remotedb/remotedb.go @@ -0,0 +1,154 @@ +// 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 remotedb implements the key-value database layer based on a remote geth +// node. Under the hood, it utilises the `debug_dbGet` method to implement a +// read-only database. +// There really are no guarantees in this database, since the local geth does not +// exclusive access, but it can be used for basic diagnostics of a remote node. +package remotedb + +import ( + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rpc" +) + +// Database is a key-value lookup for a remote database via debug_dbGet. +type Database struct { + remote *rpc.Client +} + +func (db *Database) Has(key []byte) (bool, error) { + if _, err := db.Get(key); err != nil { + return false, nil + } + return true, nil +} + +func (db *Database) Get(key []byte) ([]byte, error) { + var resp hexutil.Bytes + err := db.remote.Call(&resp, "debug_dbGet", hexutil.Bytes(key)) + if err != nil { + return nil, err + } + return resp, nil +} + +func (db *Database) HasAncient(kind string, number uint64) (bool, error) { + if _, err := db.Ancient(kind, number); err != nil { + return false, nil + } + return true, nil +} + +func (db *Database) Ancient(kind string, number uint64) ([]byte, error) { + var resp hexutil.Bytes + err := db.remote.Call(&resp, "debug_dbAncient", kind, number) + if err != nil { + return nil, err + } + return resp, nil +} + +func (db *Database) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { + panic("not supported") +} + +func (db *Database) Ancients() (uint64, error) { + var resp uint64 + err := db.remote.Call(&resp, "debug_dbAncients") + return resp, err +} + +func (db *Database) Tail() (uint64, error) { + panic("not supported") +} + +func (db *Database) AncientSize(kind string) (uint64, error) { + panic("not supported") +} + +func (db *Database) ReadAncients(fn func(op ethdb.AncientReaderOp) error) (err error) { + return fn(db) +} + +func (db *Database) Put(key []byte, value []byte) error { + panic("not supported") +} + +func (db *Database) Delete(key []byte) error { + panic("not supported") +} + +func (db *Database) ModifyAncients(f func(ethdb.AncientWriteOp) error) (int64, error) { + panic("not supported") +} + +func (db *Database) TruncateHead(n uint64) (uint64, error) { + panic("not supported") +} + +func (db *Database) TruncateTail(n uint64) (uint64, error) { + panic("not supported") +} + +func (db *Database) Sync() error { + return nil +} + +func (db *Database) MigrateTable(s string, f func([]byte) ([]byte, error)) error { + panic("not supported") +} + +func (db *Database) NewBatch() ethdb.Batch { + panic("not supported") +} + +func (db *Database) NewBatchWithSize(size int) ethdb.Batch { + panic("not supported") +} + +func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + panic("not supported") +} + +func (db *Database) Stat() (string, error) { + return "", nil +} + +func (db *Database) AncientDatadir() (string, error) { + panic("not supported") +} + +func (db *Database) Compact(start []byte, limit []byte) error { + return nil +} + +func (db *Database) NewSnapshot() (ethdb.Snapshot, error) { + panic("not supported") +} + +func (db *Database) Close() error { + db.remote.Close() + return nil +} + +func New(client *rpc.Client) ethdb.Database { + return &Database{ + remote: client, + } +} diff --git a/ethdb/snapshot.go b/ethdb/snapshot.go new file mode 100644 index 0000000..03b7794 --- /dev/null +++ b/ethdb/snapshot.go @@ -0,0 +1,41 @@ +// 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 ethdb + +type Snapshot interface { + // Has retrieves if a key is present in the snapshot backing by a key-value + // data store. + Has(key []byte) (bool, error) + + // Get retrieves the given key if it's present in the snapshot backing by + // key-value data store. + Get(key []byte) ([]byte, error) + + // Release releases associated resources. Release should always succeed and can + // be called multiple times without causing error. + Release() +} + +// Snapshotter wraps the Snapshot method of a backing data store. +type Snapshotter interface { + // NewSnapshot creates a database snapshot based on the current state. + // The created snapshot will not be affected by all following mutations + // happened on the database. + // Note don't forget to release the snapshot once it's used up, otherwise + // the stale data will never be cleaned up by the underlying compactor. + NewSnapshot() (Snapshot, error) +} diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go new file mode 100644 index 0000000..c845db1 --- /dev/null +++ b/ethstats/ethstats.go @@ -0,0 +1,819 @@ +// 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 ethstats implements the network stats reporting service. +package ethstats + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/big" + "net/http" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "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/types" + ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + "github.com/gorilla/websocket" +) + +const ( + // historyUpdateRange is the number of blocks a node should report upon login or + // history request. + historyUpdateRange = 50 + + // txChanSize is the size of channel listening to NewTxsEvent. + // The number is referenced from the size of tx pool. + txChanSize = 4096 + // chainHeadChanSize is the size of channel listening to ChainHeadEvent. + chainHeadChanSize = 10 + + messageSizeLimit = 15 * 1024 * 1024 +) + +// backend encompasses the bare-minimum functionality needed for ethstats reporting +type backend interface { + SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription + SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription + CurrentHeader() *types.Header + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + GetTd(ctx context.Context, hash common.Hash) *big.Int + Stats() (pending int, queued int) + SyncProgress() ethereum.SyncProgress +} + +// fullNodeBackend encompasses the functionality necessary for a full node +// reporting to ethstats +type fullNodeBackend interface { + backend + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + CurrentBlock() *types.Header + SuggestGasTipCap(ctx context.Context) (*big.Int, error) +} + +// Service implements an Ethereum netstats reporting daemon that pushes local +// chain statistics up to a monitoring server. +type Service struct { + server *p2p.Server // Peer-to-peer server to retrieve networking infos + backend backend + engine consensus.Engine // Consensus engine to retrieve variadic block fields + + node string // Name of the node to display on the monitoring page + pass string // Password to authorize access to the monitoring page + host string // Remote address of the monitoring service + + pongCh chan struct{} // Pong notifications are fed into this channel + histCh chan []uint64 // History request block numbers are fed into this channel + + headSub event.Subscription + txSub event.Subscription +} + +// connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the +// websocket. +// +// From Gorilla websocket docs: +// +// Connections support one concurrent reader and one concurrent writer. Applications are +// responsible for ensuring that +// - no more than one goroutine calls the write methods +// NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, +// SetCompressionLevel concurrently; and +// - that no more than one goroutine calls the +// read methods NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, +// SetPingHandler concurrently. +// +// The Close and WriteControl methods can be called concurrently with all other methods. +type connWrapper struct { + conn *websocket.Conn + + rlock sync.Mutex + wlock sync.Mutex +} + +func newConnectionWrapper(conn *websocket.Conn) *connWrapper { + conn.SetReadLimit(messageSizeLimit) + return &connWrapper{conn: conn} +} + +// WriteJSON wraps corresponding method on the websocket but is safe for concurrent calling +func (w *connWrapper) WriteJSON(v interface{}) error { + w.wlock.Lock() + defer w.wlock.Unlock() + + return w.conn.WriteJSON(v) +} + +// ReadJSON wraps corresponding method on the websocket but is safe for concurrent calling +func (w *connWrapper) ReadJSON(v interface{}) error { + w.rlock.Lock() + defer w.rlock.Unlock() + + return w.conn.ReadJSON(v) +} + +// Close wraps corresponding method on the websocket but is safe for concurrent calling +func (w *connWrapper) Close() error { + // The Close and WriteControl methods can be called concurrently with all other methods, + // so the mutex is not used here + return w.conn.Close() +} + +// parseEthstatsURL parses the netstats connection url. +// URL argument should be of the form +// If non-erroring, the returned slice contains 3 elements: [nodename, pass, host] +func parseEthstatsURL(url string) (parts []string, err error) { + err = fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) + + hostIndex := strings.LastIndex(url, "@") + if hostIndex == -1 || hostIndex == len(url)-1 { + return nil, err + } + preHost, host := url[:hostIndex], url[hostIndex+1:] + + passIndex := strings.LastIndex(preHost, ":") + if passIndex == -1 { + return []string{preHost, "", host}, nil + } + nodename, pass := preHost[:passIndex], "" + if passIndex != len(preHost)-1 { + pass = preHost[passIndex+1:] + } + + return []string{nodename, pass, host}, nil +} + +// New returns a monitoring service ready for stats reporting. +func New(node *node.Node, backend backend, engine consensus.Engine, url string) error { + parts, err := parseEthstatsURL(url) + if err != nil { + return err + } + ethstats := &Service{ + backend: backend, + engine: engine, + server: node.Server(), + node: parts[0], + pass: parts[1], + host: parts[2], + pongCh: make(chan struct{}), + histCh: make(chan []uint64, 1), + } + + node.RegisterLifecycle(ethstats) + return nil +} + +// Start implements node.Lifecycle, starting up the monitoring and reporting daemon. +func (s *Service) Start() error { + // Subscribe to chain events to execute updates on + chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize) + s.headSub = s.backend.SubscribeChainHeadEvent(chainHeadCh) + txEventCh := make(chan core.NewTxsEvent, txChanSize) + s.txSub = s.backend.SubscribeNewTxsEvent(txEventCh) + go s.loop(chainHeadCh, txEventCh) + + log.Info("Stats daemon started") + return nil +} + +// Stop implements node.Lifecycle, terminating the monitoring and reporting daemon. +func (s *Service) Stop() error { + s.headSub.Unsubscribe() + s.txSub.Unsubscribe() + log.Info("Stats daemon stopped") + return nil +} + +// loop keeps trying to connect to the netstats server, reporting chain events +// until termination. +func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, txEventCh chan core.NewTxsEvent) { + // Start a goroutine that exhausts the subscriptions to avoid events piling up + var ( + quitCh = make(chan struct{}) + headCh = make(chan *types.Block, 1) + txCh = make(chan struct{}, 1) + ) + go func() { + var lastTx mclock.AbsTime + + HandleLoop: + for { + select { + // Notify of chain head events, but drop if too frequent + case head := <-chainHeadCh: + select { + case headCh <- head.Block: + default: + } + + // Notify of new transaction events, but drop if too frequent + case <-txEventCh: + if time.Duration(mclock.Now()-lastTx) < time.Second { + continue + } + lastTx = mclock.Now() + + select { + case txCh <- struct{}{}: + default: + } + + // node stopped + case <-s.txSub.Err(): + break HandleLoop + case <-s.headSub.Err(): + break HandleLoop + } + } + close(quitCh) + }() + + // Resolve the URL, defaulting to TLS, but falling back to none too + path := fmt.Sprintf("%s/api", s.host) + urls := []string{path} + + // url.Parse and url.IsAbs is unsuitable (https://github.com/golang/go/issues/19779) + if !strings.Contains(path, "://") { + urls = []string{"wss://" + path, "ws://" + path} + } + + errTimer := time.NewTimer(0) + defer errTimer.Stop() + // Loop reporting until termination + for { + select { + case <-quitCh: + return + case <-errTimer.C: + // Establish a websocket connection to the server on any supported URL + var ( + conn *connWrapper + err error + ) + dialer := websocket.Dialer{HandshakeTimeout: 5 * time.Second} + header := make(http.Header) + header.Set("origin", "http://localhost") + for _, url := range urls { + c, _, e := dialer.Dial(url, header) + err = e + if err == nil { + conn = newConnectionWrapper(c) + break + } + } + if err != nil { + log.Warn("Stats server unreachable", "err", err) + errTimer.Reset(10 * time.Second) + continue + } + // Authenticate the client with the server + if err = s.login(conn); err != nil { + log.Warn("Stats login failed", "err", err) + conn.Close() + errTimer.Reset(10 * time.Second) + continue + } + go s.readLoop(conn) + + // Send the initial stats so our node looks decent from the get go + if err = s.report(conn); err != nil { + log.Warn("Initial stats report failed", "err", err) + conn.Close() + errTimer.Reset(0) + continue + } + // Keep sending status updates until the connection breaks + fullReport := time.NewTicker(15 * time.Second) + + for err == nil { + select { + case <-quitCh: + fullReport.Stop() + // Make sure the connection is closed + conn.Close() + return + + case <-fullReport.C: + if err = s.report(conn); err != nil { + log.Warn("Full stats report failed", "err", err) + } + case list := <-s.histCh: + if err = s.reportHistory(conn, list); err != nil { + log.Warn("Requested history report failed", "err", err) + } + case head := <-headCh: + if err = s.reportBlock(conn, head); err != nil { + log.Warn("Block stats report failed", "err", err) + } + if err = s.reportPending(conn); err != nil { + log.Warn("Post-block transaction stats report failed", "err", err) + } + case <-txCh: + if err = s.reportPending(conn); err != nil { + log.Warn("Transaction stats report failed", "err", err) + } + } + } + fullReport.Stop() + + // Close the current connection and establish a new one + conn.Close() + errTimer.Reset(0) + } + } +} + +// readLoop loops as long as the connection is alive and retrieves data packets +// from the network socket. If any of them match an active request, it forwards +// it, if they themselves are requests it initiates a reply, and lastly it drops +// unknown packets. +func (s *Service) readLoop(conn *connWrapper) { + // If the read loop exits, close the connection + defer conn.Close() + + for { + // Retrieve the next generic network packet and bail out on error + var blob json.RawMessage + if err := conn.ReadJSON(&blob); err != nil { + log.Warn("Failed to retrieve stats server message", "err", err) + return + } + // If the network packet is a system ping, respond to it directly + var ping string + if err := json.Unmarshal(blob, &ping); err == nil && strings.HasPrefix(ping, "primus::ping::") { + if err := conn.WriteJSON(strings.ReplaceAll(ping, "ping", "pong")); err != nil { + log.Warn("Failed to respond to system ping message", "err", err) + return + } + continue + } + // Not a system ping, try to decode an actual state message + var msg map[string][]interface{} + if err := json.Unmarshal(blob, &msg); err != nil { + log.Warn("Failed to decode stats server message", "err", err) + return + } + log.Trace("Received message from stats server", "msg", msg) + if len(msg["emit"]) == 0 { + log.Warn("Stats server sent non-broadcast", "msg", msg) + return + } + command, ok := msg["emit"][0].(string) + if !ok { + log.Warn("Invalid stats server message type", "type", msg["emit"][0]) + return + } + // If the message is a ping reply, deliver (someone must be listening!) + if len(msg["emit"]) == 2 && command == "node-pong" { + select { + case s.pongCh <- struct{}{}: + // Pong delivered, continue listening + continue + default: + // Ping routine dead, abort + log.Warn("Stats server pinger seems to have died") + return + } + } + // If the message is a history request, forward to the event processor + if len(msg["emit"]) == 2 && command == "history" { + // Make sure the request is valid and doesn't crash us + request, ok := msg["emit"][1].(map[string]interface{}) + if !ok { + log.Warn("Invalid stats history request", "msg", msg["emit"][1]) + select { + case s.histCh <- nil: // Treat it as an no indexes request + default: + } + continue + } + list, ok := request["list"].([]interface{}) + if !ok { + log.Warn("Invalid stats history block list", "list", request["list"]) + return + } + // Convert the block number list to an integer list + numbers := make([]uint64, len(list)) + for i, num := range list { + n, ok := num.(float64) + if !ok { + log.Warn("Invalid stats history block number", "number", num) + return + } + numbers[i] = uint64(n) + } + select { + case s.histCh <- numbers: + continue + default: + } + } + // Report anything else and continue + log.Info("Unknown stats message", "msg", msg) + } +} + +// nodeInfo is the collection of meta information about a node that is displayed +// on the monitoring page. +type nodeInfo struct { + Name string `json:"name"` + Node string `json:"node"` + Port int `json:"port"` + Network string `json:"net"` + Protocol string `json:"protocol"` + API string `json:"api"` + Os string `json:"os"` + OsVer string `json:"os_v"` + Client string `json:"client"` + History bool `json:"canUpdateHistory"` +} + +// authMsg is the authentication infos needed to login to a monitoring server. +type authMsg struct { + ID string `json:"id"` + Info nodeInfo `json:"info"` + Secret string `json:"secret"` +} + +// login tries to authorize the client at the remote server. +func (s *Service) login(conn *connWrapper) error { + // Construct and send the login authentication + infos := s.server.NodeInfo() + + var protocols []string + for _, proto := range s.server.Protocols { + protocols = append(protocols, fmt.Sprintf("%s/%d", proto.Name, proto.Version)) + } + var network string + if info := infos.Protocols["eth"]; info != nil { + network = fmt.Sprintf("%d", info.(*ethproto.NodeInfo).Network) + } else { + return errors.New("no eth protocol available") + } + auth := &authMsg{ + ID: s.node, + Info: nodeInfo{ + Name: s.node, + Node: infos.Name, + Port: infos.Ports.Listener, + Network: network, + Protocol: strings.Join(protocols, ", "), + API: "No", + Os: runtime.GOOS, + OsVer: runtime.GOARCH, + Client: "0.1.1", + History: true, + }, + Secret: s.pass, + } + login := map[string][]interface{}{ + "emit": {"hello", auth}, + } + if err := conn.WriteJSON(login); err != nil { + return err + } + // Retrieve the remote ack or connection termination + var ack map[string][]string + if err := conn.ReadJSON(&ack); err != nil || len(ack["emit"]) != 1 || ack["emit"][0] != "ready" { + return errors.New("unauthorized") + } + return nil +} + +// report collects all possible data to report and send it to the stats server. +// This should only be used on reconnects or rarely to avoid overloading the +// server. Use the individual methods for reporting subscribed events. +func (s *Service) report(conn *connWrapper) error { + if err := s.reportLatency(conn); err != nil { + return err + } + if err := s.reportBlock(conn, nil); err != nil { + return err + } + if err := s.reportPending(conn); err != nil { + return err + } + if err := s.reportStats(conn); err != nil { + return err + } + return nil +} + +// reportLatency sends a ping request to the server, measures the RTT time and +// finally sends a latency update. +func (s *Service) reportLatency(conn *connWrapper) error { + // Send the current time to the ethstats server + start := time.Now() + + ping := map[string][]interface{}{ + "emit": {"node-ping", map[string]string{ + "id": s.node, + "clientTime": start.String(), + }}, + } + if err := conn.WriteJSON(ping); err != nil { + return err + } + // Wait for the pong request to arrive back + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + + select { + case <-s.pongCh: + // Pong delivered, report the latency + case <-timer.C: + // Ping timeout, abort + return errors.New("ping timed out") + } + latency := strconv.Itoa(int((time.Since(start) / time.Duration(2)).Nanoseconds() / 1000000)) + + // Send back the measured latency + log.Trace("Sending measured latency to ethstats", "latency", latency) + + stats := map[string][]interface{}{ + "emit": {"latency", map[string]string{ + "id": s.node, + "latency": latency, + }}, + } + return conn.WriteJSON(stats) +} + +// blockStats is the information to report about individual blocks. +type blockStats struct { + Number *big.Int `json:"number"` + Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash"` + Timestamp *big.Int `json:"timestamp"` + Miner common.Address `json:"miner"` + GasUsed uint64 `json:"gasUsed"` + GasLimit uint64 `json:"gasLimit"` + Diff string `json:"difficulty"` + TotalDiff string `json:"totalDifficulty"` + Txs []txStats `json:"transactions"` + TxHash common.Hash `json:"transactionsRoot"` + Root common.Hash `json:"stateRoot"` + Uncles uncleStats `json:"uncles"` +} + +// txStats is the information to report about individual transactions. +type txStats struct { + Hash common.Hash `json:"hash"` +} + +// uncleStats is a custom wrapper around an uncle array to force serializing +// empty arrays instead of returning null for them. +type uncleStats []*types.Header + +func (s uncleStats) MarshalJSON() ([]byte, error) { + if uncles := ([]*types.Header)(s); len(uncles) > 0 { + return json.Marshal(uncles) + } + return []byte("[]"), nil +} + +// reportBlock retrieves the current chain head and reports it to the stats server. +func (s *Service) reportBlock(conn *connWrapper, block *types.Block) error { + // Gather the block details from the header or block chain + details := s.assembleBlockStats(block) + + // Short circuit if the block detail is not available. + if details == nil { + return nil + } + // Assemble the block report and send it to the server + log.Trace("Sending new block to ethstats", "number", details.Number, "hash", details.Hash) + + stats := map[string]interface{}{ + "id": s.node, + "block": details, + } + report := map[string][]interface{}{ + "emit": {"block", stats}, + } + return conn.WriteJSON(report) +} + +// assembleBlockStats retrieves any required metadata to report a single block +// and assembles the block stats. If block is nil, the current head is processed. +func (s *Service) assembleBlockStats(block *types.Block) *blockStats { + // Gather the block infos from the local blockchain + var ( + header *types.Header + td *big.Int + txs []txStats + uncles []*types.Header + ) + + // check if backend is a full node + fullBackend, ok := s.backend.(fullNodeBackend) + if ok { + // Retrieve current chain head if no block is given. + if block == nil { + head := fullBackend.CurrentBlock() + block, _ = fullBackend.BlockByNumber(context.Background(), rpc.BlockNumber(head.Number.Uint64())) + } + // Short circuit if no block is available. It might happen when + // the blockchain is reorging. + if block == nil { + return nil + } + header = block.Header() + td = fullBackend.GetTd(context.Background(), header.Hash()) + + txs = make([]txStats, len(block.Transactions())) + for i, tx := range block.Transactions() { + txs[i].Hash = tx.Hash() + } + uncles = block.Uncles() + } else { + // Light nodes would need on-demand lookups for transactions/uncles, skip + if block != nil { + header = block.Header() + } else { + header = s.backend.CurrentHeader() + } + td = s.backend.GetTd(context.Background(), header.Hash()) + txs = []txStats{} + } + + // Assemble and return the block stats + author, _ := s.engine.Author(header) + + return &blockStats{ + Number: header.Number, + Hash: header.Hash(), + ParentHash: header.ParentHash, + Timestamp: new(big.Int).SetUint64(header.Time), + Miner: author, + GasUsed: header.GasUsed, + GasLimit: header.GasLimit, + Diff: header.Difficulty.String(), + TotalDiff: td.String(), + Txs: txs, + TxHash: header.TxHash, + Root: header.Root, + Uncles: uncles, + } +} + +// reportHistory retrieves the most recent batch of blocks and reports it to the +// stats server. +func (s *Service) reportHistory(conn *connWrapper, list []uint64) error { + // Figure out the indexes that need reporting + indexes := make([]uint64, 0, historyUpdateRange) + if len(list) > 0 { + // Specific indexes requested, send them back in particular + indexes = append(indexes, list...) + } else { + // No indexes requested, send back the top ones + head := s.backend.CurrentHeader().Number.Int64() + start := head - historyUpdateRange + 1 + if start < 0 { + start = 0 + } + for i := uint64(start); i <= uint64(head); i++ { + indexes = append(indexes, i) + } + } + // Gather the batch of blocks to report + history := make([]*blockStats, len(indexes)) + for i, number := range indexes { + fullBackend, ok := s.backend.(fullNodeBackend) + // Retrieve the next block if it's known to us + var block *types.Block + if ok { + block, _ = fullBackend.BlockByNumber(context.Background(), rpc.BlockNumber(number)) // TODO ignore error here ? + } else { + if header, _ := s.backend.HeaderByNumber(context.Background(), rpc.BlockNumber(number)); header != nil { + block = types.NewBlockWithHeader(header) + } + } + // If we do have the block, add to the history and continue + if block != nil { + history[len(history)-1-i] = s.assembleBlockStats(block) + continue + } + // Ran out of blocks, cut the report short and send + history = history[len(history)-i:] + break + } + // Assemble the history report and send it to the server + if len(history) > 0 { + log.Trace("Sending historical blocks to ethstats", "first", history[0].Number, "last", history[len(history)-1].Number) + } else { + log.Trace("No history to send to stats server") + } + stats := map[string]interface{}{ + "id": s.node, + "history": history, + } + report := map[string][]interface{}{ + "emit": {"history", stats}, + } + return conn.WriteJSON(report) +} + +// pendStats is the information to report about pending transactions. +type pendStats struct { + Pending int `json:"pending"` +} + +// reportPending retrieves the current number of pending transactions and reports +// it to the stats server. +func (s *Service) reportPending(conn *connWrapper) error { + // Retrieve the pending count from the local blockchain + pending, _ := s.backend.Stats() + // Assemble the transaction stats and send it to the server + log.Trace("Sending pending transactions to ethstats", "count", pending) + + stats := map[string]interface{}{ + "id": s.node, + "stats": &pendStats{ + Pending: pending, + }, + } + report := map[string][]interface{}{ + "emit": {"pending", stats}, + } + return conn.WriteJSON(report) +} + +// nodeStats is the information to report about the local node. +type nodeStats struct { + Active bool `json:"active"` + Syncing bool `json:"syncing"` + Peers int `json:"peers"` + GasPrice int `json:"gasPrice"` + Uptime int `json:"uptime"` +} + +// reportStats retrieves various stats about the node at the networking layer +// and reports it to the stats server. +func (s *Service) reportStats(conn *connWrapper) error { + // Gather the syncing infos from the local miner instance + var ( + syncing bool + gasprice int + ) + // check if backend is a full node + if fullBackend, ok := s.backend.(fullNodeBackend); ok { + sync := fullBackend.SyncProgress() + syncing = !sync.Done() + + price, _ := fullBackend.SuggestGasTipCap(context.Background()) + gasprice = int(price.Uint64()) + if basefee := fullBackend.CurrentHeader().BaseFee; basefee != nil { + gasprice += int(basefee.Uint64()) + } + } else { + sync := s.backend.SyncProgress() + syncing = !sync.Done() + } + // Assemble the node stats and send it to the server + log.Trace("Sending node details to ethstats") + + stats := map[string]interface{}{ + "id": s.node, + "stats": &nodeStats{ + Active: true, + Peers: s.server.PeerCount(), + GasPrice: gasprice, + Syncing: syncing, + Uptime: 100, + }, + } + report := map[string][]interface{}{ + "emit": {"stats", stats}, + } + return conn.WriteJSON(report) +} diff --git a/ethstats/ethstats_test.go b/ethstats/ethstats_test.go new file mode 100644 index 0000000..60322f7 --- /dev/null +++ b/ethstats/ethstats_test.go @@ -0,0 +1,82 @@ +// 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 ethstats + +import ( + "strconv" + "testing" +) + +func TestParseEthstatsURL(t *testing.T) { + cases := []struct { + url string + node, pass, host string + }{ + { + url: `"debug meowsbits":mypass@ws://mordor.dash.fault.dev:3000`, + node: "debug meowsbits", pass: "mypass", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `"debug @meowsbits":mypass@ws://mordor.dash.fault.dev:3000`, + node: "debug @meowsbits", pass: "mypass", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `"debug: @meowsbits":mypass@ws://mordor.dash.fault.dev:3000`, + node: "debug: @meowsbits", pass: "mypass", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `name:@ws://mordor.dash.fault.dev:3000`, + node: "name", pass: "", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `name@ws://mordor.dash.fault.dev:3000`, + node: "name", pass: "", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `:mypass@ws://mordor.dash.fault.dev:3000`, + node: "", pass: "mypass", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `:@ws://mordor.dash.fault.dev:3000`, + node: "", pass: "", host: "ws://mordor.dash.fault.dev:3000", + }, + } + + for i, c := range cases { + parts, err := parseEthstatsURL(c.url) + if err != nil { + t.Fatal(err) + } + node, pass, host := parts[0], parts[1], parts[2] + + // unquote because the value provided will be used as a CLI flag value, so unescaped quotes will be removed + nodeUnquote, err := strconv.Unquote(node) + if err == nil { + node = nodeUnquote + } + + if node != c.node { + t.Errorf("case=%d mismatch node value, got: %v ,want: %v", i, node, c.node) + } + if pass != c.pass { + t.Errorf("case=%d mismatch pass value, got: %v ,want: %v", i, pass, c.pass) + } + if host != c.host { + t.Errorf("case=%d mismatch host value, got: %v ,want: %v", i, host, c.host) + } + } +} diff --git a/event/event.go b/event/event.go new file mode 100644 index 0000000..25a2c2e --- /dev/null +++ b/event/event.go @@ -0,0 +1,217 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package event deals with subscriptions to real-time events. +package event + +import ( + "errors" + "fmt" + "reflect" + "sync" + "time" +) + +// TypeMuxEvent is a time-tagged notification pushed to subscribers. +type TypeMuxEvent struct { + Time time.Time + Data interface{} +} + +// A TypeMux dispatches events to registered receivers. Receivers can be +// registered to handle events of certain type. Any operation +// called after mux is stopped will return ErrMuxClosed. +// +// The zero value is ready to use. +// +// Deprecated: use Feed +type TypeMux struct { + mutex sync.RWMutex + subm map[reflect.Type][]*TypeMuxSubscription + stopped bool +} + +// ErrMuxClosed is returned when Posting on a closed TypeMux. +var ErrMuxClosed = errors.New("event: mux closed") + +// Subscribe creates a subscription for events of the given types. The +// subscription's channel is closed when it is unsubscribed +// or the mux is closed. +func (mux *TypeMux) Subscribe(types ...interface{}) *TypeMuxSubscription { + sub := newsub(mux) + mux.mutex.Lock() + defer mux.mutex.Unlock() + if mux.stopped { + // set the status to closed so that calling Unsubscribe after this + // call will short circuit. + sub.closed = true + close(sub.postC) + } else { + if mux.subm == nil { + mux.subm = make(map[reflect.Type][]*TypeMuxSubscription, len(types)) + } + for _, t := range types { + rtyp := reflect.TypeOf(t) + oldsubs := mux.subm[rtyp] + if find(oldsubs, sub) != -1 { + panic(fmt.Sprintf("event: duplicate type %s in Subscribe", rtyp)) + } + subs := make([]*TypeMuxSubscription, len(oldsubs)+1) + copy(subs, oldsubs) + subs[len(oldsubs)] = sub + mux.subm[rtyp] = subs + } + } + return sub +} + +// Post sends an event to all receivers registered for the given type. +// It returns ErrMuxClosed if the mux has been stopped. +func (mux *TypeMux) Post(ev interface{}) error { + event := &TypeMuxEvent{ + Time: time.Now(), + Data: ev, + } + rtyp := reflect.TypeOf(ev) + mux.mutex.RLock() + if mux.stopped { + mux.mutex.RUnlock() + return ErrMuxClosed + } + subs := mux.subm[rtyp] + mux.mutex.RUnlock() + for _, sub := range subs { + sub.deliver(event) + } + return nil +} + +// Stop closes a mux. The mux can no longer be used. +// Future Post calls will fail with ErrMuxClosed. +// Stop blocks until all current deliveries have finished. +func (mux *TypeMux) Stop() { + mux.mutex.Lock() + defer mux.mutex.Unlock() + for _, subs := range mux.subm { + for _, sub := range subs { + sub.closewait() + } + } + mux.subm = nil + mux.stopped = true +} + +func (mux *TypeMux) del(s *TypeMuxSubscription) { + mux.mutex.Lock() + defer mux.mutex.Unlock() + for typ, subs := range mux.subm { + if pos := find(subs, s); pos >= 0 { + if len(subs) == 1 { + delete(mux.subm, typ) + } else { + mux.subm[typ] = posdelete(subs, pos) + } + } + } +} + +func find(slice []*TypeMuxSubscription, item *TypeMuxSubscription) int { + for i, v := range slice { + if v == item { + return i + } + } + return -1 +} + +func posdelete(slice []*TypeMuxSubscription, pos int) []*TypeMuxSubscription { + news := make([]*TypeMuxSubscription, len(slice)-1) + copy(news[:pos], slice[:pos]) + copy(news[pos:], slice[pos+1:]) + return news +} + +// TypeMuxSubscription is a subscription established through TypeMux. +type TypeMuxSubscription struct { + mux *TypeMux + created time.Time + closeMu sync.Mutex + closing chan struct{} + closed bool + + // these two are the same channel. they are stored separately so + // postC can be set to nil without affecting the return value of + // Chan. + postMu sync.RWMutex + readC <-chan *TypeMuxEvent + postC chan<- *TypeMuxEvent +} + +func newsub(mux *TypeMux) *TypeMuxSubscription { + c := make(chan *TypeMuxEvent) + return &TypeMuxSubscription{ + mux: mux, + created: time.Now(), + readC: c, + postC: c, + closing: make(chan struct{}), + } +} + +func (s *TypeMuxSubscription) Chan() <-chan *TypeMuxEvent { + return s.readC +} + +func (s *TypeMuxSubscription) Unsubscribe() { + s.mux.del(s) + s.closewait() +} + +func (s *TypeMuxSubscription) Closed() bool { + s.closeMu.Lock() + defer s.closeMu.Unlock() + return s.closed +} + +func (s *TypeMuxSubscription) closewait() { + s.closeMu.Lock() + defer s.closeMu.Unlock() + if s.closed { + return + } + close(s.closing) + s.closed = true + + s.postMu.Lock() + defer s.postMu.Unlock() + close(s.postC) + s.postC = nil +} + +func (s *TypeMuxSubscription) deliver(event *TypeMuxEvent) { + // Short circuit delivery if stale event + if s.created.After(event.Time) { + return + } + // Otherwise deliver the event + s.postMu.RLock() + defer s.postMu.RUnlock() + + select { + case s.postC <- event: + case <-s.closing: + } +} diff --git a/event/event_test.go b/event/event_test.go new file mode 100644 index 0000000..84b37ec --- /dev/null +++ b/event/event_test.go @@ -0,0 +1,218 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package event + +import ( + "math/rand" + "sync" + "testing" + "time" +) + +type testEvent int + +func TestSubCloseUnsub(t *testing.T) { + // the point of this test is **not** to panic + var mux TypeMux + mux.Stop() + sub := mux.Subscribe(0) + sub.Unsubscribe() +} + +func TestSub(t *testing.T) { + mux := new(TypeMux) + defer mux.Stop() + + sub := mux.Subscribe(testEvent(0)) + go func() { + if err := mux.Post(testEvent(5)); err != nil { + t.Errorf("Post returned unexpected error: %v", err) + } + }() + ev := <-sub.Chan() + + if ev.Data.(testEvent) != testEvent(5) { + t.Errorf("Got %v (%T), expected event %v (%T)", + ev, ev, testEvent(5), testEvent(5)) + } +} + +func TestMuxErrorAfterStop(t *testing.T) { + mux := new(TypeMux) + mux.Stop() + + sub := mux.Subscribe(testEvent(0)) + if _, isopen := <-sub.Chan(); isopen { + t.Errorf("subscription channel was not closed") + } + if err := mux.Post(testEvent(0)); err != ErrMuxClosed { + t.Errorf("Post error mismatch, got: %s, expected: %s", err, ErrMuxClosed) + } +} + +func TestUnsubscribeUnblockPost(t *testing.T) { + mux := new(TypeMux) + defer mux.Stop() + + sub := mux.Subscribe(testEvent(0)) + unblocked := make(chan bool) + go func() { + mux.Post(testEvent(5)) + unblocked <- true + }() + + select { + case <-unblocked: + t.Errorf("Post returned before Unsubscribe") + default: + sub.Unsubscribe() + <-unblocked + } +} + +func TestSubscribeDuplicateType(t *testing.T) { + mux := new(TypeMux) + expected := "event: duplicate type event.testEvent in Subscribe" + + defer func() { + err := recover() + if err == nil { + t.Errorf("Subscribe didn't panic for duplicate type") + } else if err != expected { + t.Errorf("panic mismatch: got %#v, expected %#v", err, expected) + } + }() + mux.Subscribe(testEvent(1), testEvent(2)) +} + +func TestMuxConcurrent(t *testing.T) { + mux := new(TypeMux) + defer mux.Stop() + + recv := make(chan int) + poster := func() { + for { + err := mux.Post(testEvent(0)) + if err != nil { + return + } + } + } + sub := func(i int) { + time.Sleep(time.Duration(rand.Intn(99)) * time.Millisecond) + sub := mux.Subscribe(testEvent(0)) + <-sub.Chan() + sub.Unsubscribe() + recv <- i + } + + go poster() + go poster() + go poster() + nsubs := 1000 + for i := 0; i < nsubs; i++ { + go sub(i) + } + + // wait until everyone has been served + counts := make(map[int]int, nsubs) + for i := 0; i < nsubs; i++ { + counts[<-recv]++ + } + for i, count := range counts { + if count != 1 { + t.Errorf("receiver %d called %d times, expected only 1 call", i, count) + } + } +} + +func emptySubscriber(mux *TypeMux) { + s := mux.Subscribe(testEvent(0)) + go func() { + for range s.Chan() { + } + }() +} + +func BenchmarkPost1000(b *testing.B) { + var ( + mux = new(TypeMux) + subscribed, done sync.WaitGroup + nsubs = 1000 + ) + subscribed.Add(nsubs) + done.Add(nsubs) + for i := 0; i < nsubs; i++ { + go func() { + s := mux.Subscribe(testEvent(0)) + subscribed.Done() + for range s.Chan() { + } + done.Done() + }() + } + subscribed.Wait() + + // The actual benchmark. + b.ResetTimer() + for i := 0; i < b.N; i++ { + mux.Post(testEvent(0)) + } + + b.StopTimer() + mux.Stop() + done.Wait() +} + +func BenchmarkPostConcurrent(b *testing.B) { + var mux = new(TypeMux) + defer mux.Stop() + emptySubscriber(mux) + emptySubscriber(mux) + emptySubscriber(mux) + + var wg sync.WaitGroup + poster := func() { + for i := 0; i < b.N; i++ { + mux.Post(testEvent(0)) + } + wg.Done() + } + wg.Add(5) + for i := 0; i < 5; i++ { + go poster() + } + wg.Wait() +} + +// for comparison +func BenchmarkChanSend(b *testing.B) { + c := make(chan interface{}) + defer close(c) + closed := make(chan struct{}) + go func() { + for range c { + } + }() + + for i := 0; i < b.N; i++ { + select { + case c <- i: + case <-closed: + } + } +} diff --git a/event/example_feed_test.go b/event/example_feed_test.go new file mode 100644 index 0000000..9b5ad50 --- /dev/null +++ b/event/example_feed_test.go @@ -0,0 +1,73 @@ +// 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 event_test + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/event" +) + +func ExampleFeed_acknowledgedEvents() { + // This example shows how the return value of Send can be used for request/reply + // interaction between event consumers and producers. + var feed event.Feed + type ackedEvent struct { + i int + ack chan<- struct{} + } + + // Consumers wait for events on the feed and acknowledge processing. + done := make(chan struct{}) + defer close(done) + for i := 0; i < 3; i++ { + ch := make(chan ackedEvent, 100) + sub := feed.Subscribe(ch) + go func() { + defer sub.Unsubscribe() + for { + select { + case ev := <-ch: + fmt.Println(ev.i) // "process" the event + ev.ack <- struct{}{} + case <-done: + return + } + } + }() + } + + // The producer sends values of type ackedEvent with increasing values of i. + // It waits for all consumers to acknowledge before sending the next event. + for i := 0; i < 3; i++ { + acksignal := make(chan struct{}) + n := feed.Send(ackedEvent{i, acksignal}) + for ack := 0; ack < n; ack++ { + <-acksignal + } + } + // Output: + // 0 + // 0 + // 0 + // 1 + // 1 + // 1 + // 2 + // 2 + // 2 +} diff --git a/event/example_scope_test.go b/event/example_scope_test.go new file mode 100644 index 0000000..825a8de --- /dev/null +++ b/event/example_scope_test.go @@ -0,0 +1,128 @@ +// 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 event_test + +import ( + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/event" +) + +// This example demonstrates how SubscriptionScope can be used to control the lifetime of +// subscriptions. +// +// Our example program consists of two servers, each of which performs a calculation when +// requested. The servers also allow subscribing to results of all computations. +type divServer struct{ results event.Feed } +type mulServer struct{ results event.Feed } + +func (s *divServer) do(a, b int) int { + r := a / b + s.results.Send(r) + return r +} + +func (s *mulServer) do(a, b int) int { + r := a * b + s.results.Send(r) + return r +} + +// The servers are contained in an App. The app controls the servers and exposes them +// through its API. +type App struct { + divServer + mulServer + scope event.SubscriptionScope +} + +func (s *App) Calc(op byte, a, b int) int { + switch op { + case '/': + return s.divServer.do(a, b) + case '*': + return s.mulServer.do(a, b) + default: + panic("invalid op") + } +} + +// The app's SubscribeResults method starts sending calculation results to the given +// channel. Subscriptions created through this method are tied to the lifetime of the App +// because they are registered in the scope. +func (s *App) SubscribeResults(op byte, ch chan<- int) event.Subscription { + switch op { + case '/': + return s.scope.Track(s.divServer.results.Subscribe(ch)) + case '*': + return s.scope.Track(s.mulServer.results.Subscribe(ch)) + default: + panic("invalid op") + } +} + +// Stop stops the App, closing all subscriptions created through SubscribeResults. +func (s *App) Stop() { + s.scope.Close() +} + +func ExampleSubscriptionScope() { + // Create the app. + var ( + app App + wg sync.WaitGroup + divs = make(chan int) + muls = make(chan int) + ) + + // Run a subscriber in the background. + divsub := app.SubscribeResults('/', divs) + mulsub := app.SubscribeResults('*', muls) + wg.Add(1) + go func() { + defer wg.Done() + defer fmt.Println("subscriber exited") + defer divsub.Unsubscribe() + defer mulsub.Unsubscribe() + for { + select { + case result := <-divs: + fmt.Println("division happened:", result) + case result := <-muls: + fmt.Println("multiplication happened:", result) + case <-divsub.Err(): + return + case <-mulsub.Err(): + return + } + } + }() + + // Interact with the app. + app.Calc('/', 22, 11) + app.Calc('*', 3, 4) + + // Stop the app. This shuts down the subscriptions, causing the subscriber to exit. + app.Stop() + wg.Wait() + + // Output: + // division happened: 2 + // multiplication happened: 12 + // subscriber exited +} diff --git a/event/example_subscription_test.go b/event/example_subscription_test.go new file mode 100644 index 0000000..5c76b55 --- /dev/null +++ b/event/example_subscription_test.go @@ -0,0 +1,56 @@ +// 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 event_test + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/event" +) + +func ExampleNewSubscription() { + // Create a subscription that sends 10 integers on ch. + ch := make(chan int) + sub := event.NewSubscription(func(quit <-chan struct{}) error { + for i := 0; i < 10; i++ { + select { + case ch <- i: + case <-quit: + fmt.Println("unsubscribed") + return nil + } + } + return nil + }) + + // This is the consumer. It reads 5 integers, then aborts the subscription. + // Note that Unsubscribe waits until the producer has shut down. + for i := range ch { + fmt.Println(i) + if i == 4 { + sub.Unsubscribe() + break + } + } + // Output: + // 0 + // 1 + // 2 + // 3 + // 4 + // unsubscribed +} diff --git a/event/example_test.go b/event/example_test.go new file mode 100644 index 0000000..29938e8 --- /dev/null +++ b/event/example_test.go @@ -0,0 +1,58 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package event + +import "fmt" + +func ExampleTypeMux() { + type someEvent struct{ I int } + type otherEvent struct{ S string } + type yetAnotherEvent struct{ X, Y int } + + var mux TypeMux + + // Start a subscriber. + done := make(chan struct{}) + sub := mux.Subscribe(someEvent{}, otherEvent{}) + go func() { + for event := range sub.Chan() { + fmt.Printf("Received: %#v\n", event.Data) + } + fmt.Println("done") + close(done) + }() + + // Post some events. + mux.Post(someEvent{5}) + mux.Post(yetAnotherEvent{X: 3, Y: 4}) + mux.Post(someEvent{6}) + mux.Post(otherEvent{"whoa"}) + + // Stop closes all subscription channels. + // The subscriber goroutine will print "done" + // and exit. + mux.Stop() + + // Wait for subscriber to return. + <-done + + // Output: + // Received: event.someEvent{I:5} + // Received: event.someEvent{I:6} + // Received: event.otherEvent{S:"whoa"} + // done +} diff --git a/event/feed.go b/event/feed.go new file mode 100644 index 0000000..d94bd82 --- /dev/null +++ b/event/feed.go @@ -0,0 +1,238 @@ +// 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 event + +import ( + "errors" + "reflect" + "sync" +) + +var errBadChannel = errors.New("event: Subscribe argument does not have sendable channel type") + +// Feed implements one-to-many subscriptions where the carrier of events is a channel. +// Values sent to a Feed are delivered to all subscribed channels simultaneously. +// +// Feeds can only be used with a single type. The type is determined by the first Send or +// Subscribe operation. Subsequent calls to these methods panic if the type does not +// match. +// +// The zero value is ready to use. +type Feed struct { + once sync.Once // ensures that init only runs once + sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases. + removeSub chan interface{} // interrupts Send + sendCases caseList // the active set of select cases used by Send + + // The inbox holds newly subscribed channels until they are added to sendCases. + mu sync.Mutex + inbox caseList + etype reflect.Type +} + +// This is the index of the first actual subscription channel in sendCases. +// sendCases[0] is a SelectRecv case for the removeSub channel. +const firstSubSendCase = 1 + +type feedTypeError struct { + got, want reflect.Type + op string +} + +func (e feedTypeError) Error() string { + return "event: wrong type in " + e.op + " got " + e.got.String() + ", want " + e.want.String() +} + +func (f *Feed) init(etype reflect.Type) { + f.etype = etype + f.removeSub = make(chan interface{}) + f.sendLock = make(chan struct{}, 1) + f.sendLock <- struct{}{} + f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}} +} + +// Subscribe adds a channel to the feed. Future sends will be delivered on the channel +// until the subscription is canceled. All channels added must have the same element type. +// +// The channel should have ample buffer space to avoid blocking other subscribers. +// Slow subscribers are not dropped. +func (f *Feed) Subscribe(channel interface{}) Subscription { + chanval := reflect.ValueOf(channel) + chantyp := chanval.Type() + if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 { + panic(errBadChannel) + } + sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)} + + f.once.Do(func() { f.init(chantyp.Elem()) }) + if f.etype != chantyp.Elem() { + panic(feedTypeError{op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)}) + } + + f.mu.Lock() + defer f.mu.Unlock() + // Add the select case to the inbox. + // The next Send will add it to f.sendCases. + cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval} + f.inbox = append(f.inbox, cas) + return sub +} + +func (f *Feed) remove(sub *feedSub) { + // Delete from inbox first, which covers channels + // that have not been added to f.sendCases yet. + ch := sub.channel.Interface() + f.mu.Lock() + index := f.inbox.find(ch) + if index != -1 { + f.inbox = f.inbox.delete(index) + f.mu.Unlock() + return + } + f.mu.Unlock() + + select { + case f.removeSub <- ch: + // Send will remove the channel from f.sendCases. + case <-f.sendLock: + // No Send is in progress, delete the channel now that we have the send lock. + f.sendCases = f.sendCases.delete(f.sendCases.find(ch)) + f.sendLock <- struct{}{} + } +} + +// Send delivers to all subscribed channels simultaneously. +// It returns the number of subscribers that the value was sent to. +func (f *Feed) Send(value interface{}) (nsent int) { + rvalue := reflect.ValueOf(value) + + f.once.Do(func() { f.init(rvalue.Type()) }) + if f.etype != rvalue.Type() { + panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype}) + } + + <-f.sendLock + + // Add new cases from the inbox after taking the send lock. + f.mu.Lock() + f.sendCases = append(f.sendCases, f.inbox...) + f.inbox = nil + f.mu.Unlock() + + // Set the sent value on all channels. + for i := firstSubSendCase; i < len(f.sendCases); i++ { + f.sendCases[i].Send = rvalue + } + + // Send until all channels except removeSub have been chosen. 'cases' tracks a prefix + // of sendCases. When a send succeeds, the corresponding case moves to the end of + // 'cases' and it shrinks by one element. + cases := f.sendCases + for { + // Fast path: try sending without blocking before adding to the select set. + // This should usually succeed if subscribers are fast enough and have free + // buffer space. + for i := firstSubSendCase; i < len(cases); i++ { + if cases[i].Chan.TrySend(rvalue) { + nsent++ + cases = cases.deactivate(i) + i-- + } + } + if len(cases) == firstSubSendCase { + break + } + // Select on all the receivers, waiting for them to unblock. + chosen, recv, _ := reflect.Select(cases) + if chosen == 0 /* <-f.removeSub */ { + index := f.sendCases.find(recv.Interface()) + f.sendCases = f.sendCases.delete(index) + if index >= 0 && index < len(cases) { + // Shrink 'cases' too because the removed case was still active. + cases = f.sendCases[:len(cases)-1] + } + } else { + cases = cases.deactivate(chosen) + nsent++ + } + } + + // Forget about the sent value and hand off the send lock. + for i := firstSubSendCase; i < len(f.sendCases); i++ { + f.sendCases[i].Send = reflect.Value{} + } + f.sendLock <- struct{}{} + return nsent +} + +type feedSub struct { + feed *Feed + channel reflect.Value + errOnce sync.Once + err chan error +} + +func (sub *feedSub) Unsubscribe() { + sub.errOnce.Do(func() { + sub.feed.remove(sub) + close(sub.err) + }) +} + +func (sub *feedSub) Err() <-chan error { + return sub.err +} + +type caseList []reflect.SelectCase + +// find returns the index of a case containing the given channel. +func (cs caseList) find(channel interface{}) int { + for i, cas := range cs { + if cas.Chan.Interface() == channel { + return i + } + } + return -1 +} + +// delete removes the given case from cs. +func (cs caseList) delete(index int) caseList { + return append(cs[:index], cs[index+1:]...) +} + +// deactivate moves the case at index into the non-accessible portion of the cs slice. +func (cs caseList) deactivate(index int) caseList { + last := len(cs) - 1 + cs[index], cs[last] = cs[last], cs[index] + return cs[:last] +} + +// func (cs caseList) String() string { +// s := "[" +// for i, cas := range cs { +// if i != 0 { +// s += ", " +// } +// switch cas.Dir { +// case reflect.SelectSend: +// s += fmt.Sprintf("%v<-", cas.Chan.Interface()) +// case reflect.SelectRecv: +// s += fmt.Sprintf("<-%v", cas.Chan.Interface()) +// } +// } +// return s + "]" +// } diff --git a/event/feed_test.go b/event/feed_test.go new file mode 100644 index 0000000..74e8587 --- /dev/null +++ b/event/feed_test.go @@ -0,0 +1,335 @@ +// 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 event + +import ( + "errors" + "fmt" + "reflect" + "sync" + "testing" + "time" +) + +func TestFeedPanics(t *testing.T) { + { + var f Feed + f.Send(2) + want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)} + if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil { + t.Error(err) + } + } + { + var f Feed + ch := make(chan int) + f.Subscribe(ch) + want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)} + if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil { + t.Error(err) + } + } + { + var f Feed + f.Send(2) + want := feedTypeError{op: "Subscribe", got: reflect.TypeOf(make(chan uint64)), want: reflect.TypeOf(make(chan<- int))} + if err := checkPanic(want, func() { f.Subscribe(make(chan uint64)) }); err != nil { + t.Error(err) + } + } + { + var f Feed + if err := checkPanic(errBadChannel, func() { f.Subscribe(make(<-chan int)) }); err != nil { + t.Error(err) + } + } + { + var f Feed + if err := checkPanic(errBadChannel, func() { f.Subscribe(0) }); err != nil { + t.Error(err) + } + } +} + +func checkPanic(want error, fn func()) (err error) { + defer func() { + panic := recover() + if panic == nil { + err = errors.New("didn't panic") + } else if !reflect.DeepEqual(panic, want) { + err = fmt.Errorf("panicked with wrong error: got %q, want %q", panic, want) + } + }() + fn() + return nil +} + +func TestFeed(t *testing.T) { + var feed Feed + var done, subscribed sync.WaitGroup + subscriber := func(i int) { + defer done.Done() + + subchan := make(chan int) + sub := feed.Subscribe(subchan) + timeout := time.NewTimer(2 * time.Second) + defer timeout.Stop() + subscribed.Done() + + select { + case v := <-subchan: + if v != 1 { + t.Errorf("%d: received value %d, want 1", i, v) + } + case <-timeout.C: + t.Errorf("%d: receive timeout", i) + } + + sub.Unsubscribe() + select { + case _, ok := <-sub.Err(): + if ok { + t.Errorf("%d: error channel not closed after unsubscribe", i) + } + case <-timeout.C: + t.Errorf("%d: unsubscribe timeout", i) + } + } + + const n = 1000 + done.Add(n) + subscribed.Add(n) + for i := 0; i < n; i++ { + go subscriber(i) + } + subscribed.Wait() + if nsent := feed.Send(1); nsent != n { + t.Errorf("first send delivered %d times, want %d", nsent, n) + } + if nsent := feed.Send(2); nsent != 0 { + t.Errorf("second send delivered %d times, want 0", nsent) + } + done.Wait() +} + +func TestFeedSubscribeSameChannel(t *testing.T) { + var ( + feed Feed + done sync.WaitGroup + ch = make(chan int) + sub1 = feed.Subscribe(ch) + sub2 = feed.Subscribe(ch) + _ = feed.Subscribe(ch) + ) + expectSends := func(value, n int) { + if nsent := feed.Send(value); nsent != n { + t.Errorf("send delivered %d times, want %d", nsent, n) + } + done.Done() + } + expectRecv := func(wantValue, n int) { + for i := 0; i < n; i++ { + if v := <-ch; v != wantValue { + t.Errorf("received %d, want %d", v, wantValue) + } + } + } + + done.Add(1) + go expectSends(1, 3) + expectRecv(1, 3) + done.Wait() + + sub1.Unsubscribe() + + done.Add(1) + go expectSends(2, 2) + expectRecv(2, 2) + done.Wait() + + sub2.Unsubscribe() + + done.Add(1) + go expectSends(3, 1) + expectRecv(3, 1) + done.Wait() +} + +func TestFeedSubscribeBlockedPost(t *testing.T) { + var ( + feed Feed + nsends = 2000 + ch1 = make(chan int) + ch2 = make(chan int) + wg sync.WaitGroup + ) + defer wg.Wait() + + feed.Subscribe(ch1) + wg.Add(nsends) + for i := 0; i < nsends; i++ { + go func() { + feed.Send(99) + wg.Done() + }() + } + + sub2 := feed.Subscribe(ch2) + defer sub2.Unsubscribe() + + // We're done when ch1 has received N times. + // The number of receives on ch2 depends on scheduling. + for i := 0; i < nsends; { + select { + case <-ch1: + i++ + case <-ch2: + } + } +} + +func TestFeedUnsubscribeBlockedPost(t *testing.T) { + var ( + feed Feed + nsends = 200 + chans = make([]chan int, 2000) + subs = make([]Subscription, len(chans)) + bchan = make(chan int) + bsub = feed.Subscribe(bchan) + wg sync.WaitGroup + ) + for i := range chans { + chans[i] = make(chan int, nsends) + } + + // Queue up some Sends. None of these can make progress while bchan isn't read. + wg.Add(nsends) + for i := 0; i < nsends; i++ { + go func() { + feed.Send(99) + wg.Done() + }() + } + // Subscribe the other channels. + for i, ch := range chans { + subs[i] = feed.Subscribe(ch) + } + // Unsubscribe them again. + for _, sub := range subs { + sub.Unsubscribe() + } + // Unblock the Sends. + bsub.Unsubscribe() + wg.Wait() +} + +// Checks that unsubscribing a channel during Send works even if that +// channel has already been sent on. +func TestFeedUnsubscribeSentChan(t *testing.T) { + var ( + feed Feed + ch1 = make(chan int) + ch2 = make(chan int) + sub1 = feed.Subscribe(ch1) + sub2 = feed.Subscribe(ch2) + wg sync.WaitGroup + ) + defer sub2.Unsubscribe() + + wg.Add(1) + go func() { + feed.Send(0) + wg.Done() + }() + + // Wait for the value on ch1. + <-ch1 + // Unsubscribe ch1, removing it from the send cases. + sub1.Unsubscribe() + + // Receive ch2, finishing Send. + <-ch2 + wg.Wait() + + // Send again. This should send to ch2 only, so the wait group will unblock + // as soon as a value is received on ch2. + wg.Add(1) + go func() { + feed.Send(0) + wg.Done() + }() + <-ch2 + wg.Wait() +} + +func TestFeedUnsubscribeFromInbox(t *testing.T) { + var ( + feed Feed + ch1 = make(chan int) + ch2 = make(chan int) + sub1 = feed.Subscribe(ch1) + sub2 = feed.Subscribe(ch1) + sub3 = feed.Subscribe(ch2) + ) + if len(feed.inbox) != 3 { + t.Errorf("inbox length != 3 after subscribe") + } + if len(feed.sendCases) != 1 { + t.Errorf("sendCases is non-empty after unsubscribe") + } + + sub1.Unsubscribe() + sub2.Unsubscribe() + sub3.Unsubscribe() + if len(feed.inbox) != 0 { + t.Errorf("inbox is non-empty after unsubscribe") + } + if len(feed.sendCases) != 1 { + t.Errorf("sendCases is non-empty after unsubscribe") + } +} + +func BenchmarkFeedSend1000(b *testing.B) { + var ( + done sync.WaitGroup + feed Feed + nsubs = 1000 + ) + subscriber := func(ch <-chan int) { + for i := 0; i < b.N; i++ { + <-ch + } + done.Done() + } + done.Add(nsubs) + for i := 0; i < nsubs; i++ { + ch := make(chan int, 200) + feed.Subscribe(ch) + go subscriber(ch) + } + + // The actual benchmark. + b.ResetTimer() + for i := 0; i < b.N; i++ { + if feed.Send(i) != nsubs { + panic("wrong number of sends") + } + } + + b.StopTimer() + done.Wait() +} diff --git a/event/feedof.go b/event/feedof.go new file mode 100644 index 0000000..4a24e37 --- /dev/null +++ b/event/feedof.go @@ -0,0 +1,164 @@ +// 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 event + +import ( + "reflect" + "sync" +) + +// FeedOf implements one-to-many subscriptions where the carrier of events is a channel. +// Values sent to a Feed are delivered to all subscribed channels simultaneously. +// +// The zero value is ready to use. +type FeedOf[T any] struct { + once sync.Once // ensures that init only runs once + sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases. + removeSub chan chan<- T // interrupts Send + sendCases caseList // the active set of select cases used by Send + + // The inbox holds newly subscribed channels until they are added to sendCases. + mu sync.Mutex + inbox caseList +} + +func (f *FeedOf[T]) init() { + f.removeSub = make(chan chan<- T) + f.sendLock = make(chan struct{}, 1) + f.sendLock <- struct{}{} + f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}} +} + +// Subscribe adds a channel to the feed. Future sends will be delivered on the channel +// until the subscription is canceled. +// +// The channel should have ample buffer space to avoid blocking other subscribers. Slow +// subscribers are not dropped. +func (f *FeedOf[T]) Subscribe(channel chan<- T) Subscription { + f.once.Do(f.init) + + chanval := reflect.ValueOf(channel) + sub := &feedOfSub[T]{feed: f, channel: channel, err: make(chan error, 1)} + + // Add the select case to the inbox. + // The next Send will add it to f.sendCases. + f.mu.Lock() + defer f.mu.Unlock() + cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval} + f.inbox = append(f.inbox, cas) + return sub +} + +func (f *FeedOf[T]) remove(sub *feedOfSub[T]) { + // Delete from inbox first, which covers channels + // that have not been added to f.sendCases yet. + f.mu.Lock() + index := f.inbox.find(sub.channel) + if index != -1 { + f.inbox = f.inbox.delete(index) + f.mu.Unlock() + return + } + f.mu.Unlock() + + select { + case f.removeSub <- sub.channel: + // Send will remove the channel from f.sendCases. + case <-f.sendLock: + // No Send is in progress, delete the channel now that we have the send lock. + f.sendCases = f.sendCases.delete(f.sendCases.find(sub.channel)) + f.sendLock <- struct{}{} + } +} + +// Send delivers to all subscribed channels simultaneously. +// It returns the number of subscribers that the value was sent to. +func (f *FeedOf[T]) Send(value T) (nsent int) { + rvalue := reflect.ValueOf(value) + + f.once.Do(f.init) + <-f.sendLock + + // Add new cases from the inbox after taking the send lock. + f.mu.Lock() + f.sendCases = append(f.sendCases, f.inbox...) + f.inbox = nil + f.mu.Unlock() + + // Set the sent value on all channels. + for i := firstSubSendCase; i < len(f.sendCases); i++ { + f.sendCases[i].Send = rvalue + } + + // Send until all channels except removeSub have been chosen. 'cases' tracks a prefix + // of sendCases. When a send succeeds, the corresponding case moves to the end of + // 'cases' and it shrinks by one element. + cases := f.sendCases + for { + // Fast path: try sending without blocking before adding to the select set. + // This should usually succeed if subscribers are fast enough and have free + // buffer space. + for i := firstSubSendCase; i < len(cases); i++ { + if cases[i].Chan.TrySend(rvalue) { + nsent++ + cases = cases.deactivate(i) + i-- + } + } + if len(cases) == firstSubSendCase { + break + } + // Select on all the receivers, waiting for them to unblock. + chosen, recv, _ := reflect.Select(cases) + if chosen == 0 /* <-f.removeSub */ { + index := f.sendCases.find(recv.Interface()) + f.sendCases = f.sendCases.delete(index) + if index >= 0 && index < len(cases) { + // Shrink 'cases' too because the removed case was still active. + cases = f.sendCases[:len(cases)-1] + } + } else { + cases = cases.deactivate(chosen) + nsent++ + } + } + + // Forget about the sent value and hand off the send lock. + for i := firstSubSendCase; i < len(f.sendCases); i++ { + f.sendCases[i].Send = reflect.Value{} + } + f.sendLock <- struct{}{} + return nsent +} + +type feedOfSub[T any] struct { + feed *FeedOf[T] + channel chan<- T + errOnce sync.Once + err chan error +} + +func (sub *feedOfSub[T]) Unsubscribe() { + sub.errOnce.Do(func() { + sub.feed.remove(sub) + close(sub.err) + }) +} + +func (sub *feedOfSub[T]) Err() <-chan error { + return sub.err +} diff --git a/event/feedof_test.go b/event/feedof_test.go new file mode 100644 index 0000000..846afc9 --- /dev/null +++ b/event/feedof_test.go @@ -0,0 +1,279 @@ +// 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 event + +import ( + "sync" + "testing" + "time" +) + +func TestFeedOf(t *testing.T) { + var feed FeedOf[int] + var done, subscribed sync.WaitGroup + subscriber := func(i int) { + defer done.Done() + + subchan := make(chan int) + sub := feed.Subscribe(subchan) + timeout := time.NewTimer(2 * time.Second) + defer timeout.Stop() + subscribed.Done() + + select { + case v := <-subchan: + if v != 1 { + t.Errorf("%d: received value %d, want 1", i, v) + } + case <-timeout.C: + t.Errorf("%d: receive timeout", i) + } + + sub.Unsubscribe() + select { + case _, ok := <-sub.Err(): + if ok { + t.Errorf("%d: error channel not closed after unsubscribe", i) + } + case <-timeout.C: + t.Errorf("%d: unsubscribe timeout", i) + } + } + + const n = 1000 + done.Add(n) + subscribed.Add(n) + for i := 0; i < n; i++ { + go subscriber(i) + } + subscribed.Wait() + if nsent := feed.Send(1); nsent != n { + t.Errorf("first send delivered %d times, want %d", nsent, n) + } + if nsent := feed.Send(2); nsent != 0 { + t.Errorf("second send delivered %d times, want 0", nsent) + } + done.Wait() +} + +func TestFeedOfSubscribeSameChannel(t *testing.T) { + var ( + feed FeedOf[int] + done sync.WaitGroup + ch = make(chan int) + sub1 = feed.Subscribe(ch) + sub2 = feed.Subscribe(ch) + _ = feed.Subscribe(ch) + ) + expectSends := func(value, n int) { + if nsent := feed.Send(value); nsent != n { + t.Errorf("send delivered %d times, want %d", nsent, n) + } + done.Done() + } + expectRecv := func(wantValue, n int) { + for i := 0; i < n; i++ { + if v := <-ch; v != wantValue { + t.Errorf("received %d, want %d", v, wantValue) + } + } + } + + done.Add(1) + go expectSends(1, 3) + expectRecv(1, 3) + done.Wait() + + sub1.Unsubscribe() + + done.Add(1) + go expectSends(2, 2) + expectRecv(2, 2) + done.Wait() + + sub2.Unsubscribe() + + done.Add(1) + go expectSends(3, 1) + expectRecv(3, 1) + done.Wait() +} + +func TestFeedOfSubscribeBlockedPost(t *testing.T) { + var ( + feed FeedOf[int] + nsends = 2000 + ch1 = make(chan int) + ch2 = make(chan int) + wg sync.WaitGroup + ) + defer wg.Wait() + + feed.Subscribe(ch1) + wg.Add(nsends) + for i := 0; i < nsends; i++ { + go func() { + feed.Send(99) + wg.Done() + }() + } + + sub2 := feed.Subscribe(ch2) + defer sub2.Unsubscribe() + + // We're done when ch1 has received N times. + // The number of receives on ch2 depends on scheduling. + for i := 0; i < nsends; { + select { + case <-ch1: + i++ + case <-ch2: + } + } +} + +func TestFeedOfUnsubscribeBlockedPost(t *testing.T) { + var ( + feed FeedOf[int] + nsends = 200 + chans = make([]chan int, 2000) + subs = make([]Subscription, len(chans)) + bchan = make(chan int) + bsub = feed.Subscribe(bchan) + wg sync.WaitGroup + ) + for i := range chans { + chans[i] = make(chan int, nsends) + } + + // Queue up some Sends. None of these can make progress while bchan isn't read. + wg.Add(nsends) + for i := 0; i < nsends; i++ { + go func() { + feed.Send(99) + wg.Done() + }() + } + // Subscribe the other channels. + for i, ch := range chans { + subs[i] = feed.Subscribe(ch) + } + // Unsubscribe them again. + for _, sub := range subs { + sub.Unsubscribe() + } + // Unblock the Sends. + bsub.Unsubscribe() + wg.Wait() +} + +// Checks that unsubscribing a channel during Send works even if that +// channel has already been sent on. +func TestFeedOfUnsubscribeSentChan(t *testing.T) { + var ( + feed FeedOf[int] + ch1 = make(chan int) + ch2 = make(chan int) + sub1 = feed.Subscribe(ch1) + sub2 = feed.Subscribe(ch2) + wg sync.WaitGroup + ) + defer sub2.Unsubscribe() + + wg.Add(1) + go func() { + feed.Send(0) + wg.Done() + }() + + // Wait for the value on ch1. + <-ch1 + // Unsubscribe ch1, removing it from the send cases. + sub1.Unsubscribe() + + // Receive ch2, finishing Send. + <-ch2 + wg.Wait() + + // Send again. This should send to ch2 only, so the wait group will unblock + // as soon as a value is received on ch2. + wg.Add(1) + go func() { + feed.Send(0) + wg.Done() + }() + <-ch2 + wg.Wait() +} + +func TestFeedOfUnsubscribeFromInbox(t *testing.T) { + var ( + feed FeedOf[int] + ch1 = make(chan int) + ch2 = make(chan int) + sub1 = feed.Subscribe(ch1) + sub2 = feed.Subscribe(ch1) + sub3 = feed.Subscribe(ch2) + ) + if len(feed.inbox) != 3 { + t.Errorf("inbox length != 3 after subscribe") + } + if len(feed.sendCases) != 1 { + t.Errorf("sendCases is non-empty after unsubscribe") + } + + sub1.Unsubscribe() + sub2.Unsubscribe() + sub3.Unsubscribe() + if len(feed.inbox) != 0 { + t.Errorf("inbox is non-empty after unsubscribe") + } + if len(feed.sendCases) != 1 { + t.Errorf("sendCases is non-empty after unsubscribe") + } +} + +func BenchmarkFeedOfSend1000(b *testing.B) { + var ( + done sync.WaitGroup + feed FeedOf[int] + nsubs = 1000 + ) + subscriber := func(ch <-chan int) { + for i := 0; i < b.N; i++ { + <-ch + } + done.Done() + } + done.Add(nsubs) + for i := 0; i < nsubs; i++ { + ch := make(chan int, 200) + feed.Subscribe(ch) + go subscriber(ch) + } + + // The actual benchmark. + b.ResetTimer() + for i := 0; i < b.N; i++ { + if feed.Send(i) != nsubs { + panic("wrong number of sends") + } + } + + b.StopTimer() + done.Wait() +} diff --git a/event/multisub.go b/event/multisub.go new file mode 100644 index 0000000..1f0af2a --- /dev/null +++ b/event/multisub.go @@ -0,0 +1,50 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package event + +// JoinSubscriptions joins multiple subscriptions to be able to track them as +// one entity and collectively cancel them or consume any errors from them. +func JoinSubscriptions(subs ...Subscription) Subscription { + return NewSubscription(func(unsubbed <-chan struct{}) error { + // Unsubscribe all subscriptions before returning + defer func() { + for _, sub := range subs { + sub.Unsubscribe() + } + }() + // Wait for an error on any of the subscriptions and propagate up + errc := make(chan error, len(subs)) + for i := range subs { + go func(sub Subscription) { + select { + case err := <-sub.Err(): + if err != nil { + errc <- err + } + case <-unsubbed: + } + }(subs[i]) + } + + select { + case err := <-errc: + return err + case <-unsubbed: + return nil + } + }) +} diff --git a/event/multisub_test.go b/event/multisub_test.go new file mode 100644 index 0000000..c92bcfa --- /dev/null +++ b/event/multisub_test.go @@ -0,0 +1,175 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package event + +import ( + "testing" + "time" +) + +func TestMultisub(t *testing.T) { + // Create a double subscription and ensure events propagate through + var ( + feed1 Feed + feed2 Feed + ) + sink1 := make(chan int, 1) + sink2 := make(chan int, 1) + + sub1 := feed1.Subscribe(sink1) + sub2 := feed2.Subscribe(sink2) + + sub := JoinSubscriptions(sub1, sub2) + + feed1.Send(1) + select { + case n := <-sink1: + if n != 1 { + t.Errorf("sink 1 delivery mismatch: have %d, want %d", n, 1) + } + default: + t.Error("sink 1 missing delivery") + } + + feed2.Send(2) + select { + case n := <-sink2: + if n != 2 { + t.Errorf("sink 2 delivery mismatch: have %d, want %d", n, 2) + } + default: + t.Error("sink 2 missing delivery") + } + // Unsubscribe and ensure no more events are delivered + sub.Unsubscribe() + select { + case <-sub.Err(): + case <-time.After(50 * time.Millisecond): + t.Error("multisub didn't propagate closure") + } + + feed1.Send(11) + select { + case n := <-sink1: + t.Errorf("sink 1 unexpected delivery: %d", n) + default: + } + + feed2.Send(22) + select { + case n := <-sink2: + t.Errorf("sink 2 unexpected delivery: %d", n) + default: + } +} + +func TestMutisubPartialUnsubscribe(t *testing.T) { + // Create a double subscription but terminate one half, ensuring no error + // is propagated yet up to the outer subscription + var ( + feed1 Feed + feed2 Feed + ) + sink1 := make(chan int, 1) + sink2 := make(chan int, 1) + + sub1 := feed1.Subscribe(sink1) + sub2 := feed2.Subscribe(sink2) + + sub := JoinSubscriptions(sub1, sub2) + + sub1.Unsubscribe() + select { + case <-sub.Err(): + t.Error("multisub propagated closure") + case <-time.After(50 * time.Millisecond): + } + // Ensure that events cross only the second feed + feed1.Send(1) + select { + case n := <-sink1: + t.Errorf("sink 1 unexpected delivery: %d", n) + default: + } + + feed2.Send(2) + select { + case n := <-sink2: + if n != 2 { + t.Errorf("sink 2 delivery mismatch: have %d, want %d", n, 2) + } + default: + t.Error("sink 2 missing delivery") + } + // Unsubscribe and ensure no more events are delivered + sub.Unsubscribe() + select { + case <-sub.Err(): + case <-time.After(50 * time.Millisecond): + t.Error("multisub didn't propagate closure") + } + + feed1.Send(11) + select { + case n := <-sink1: + t.Errorf("sink 1 unexpected delivery: %d", n) + default: + } + + feed2.Send(22) + select { + case n := <-sink2: + t.Errorf("sink 2 unexpected delivery: %d", n) + default: + } +} + +func TestMultisubFullUnsubscribe(t *testing.T) { + // Create a double subscription and terminate the multi sub, ensuring an + // error is propagated up. + var ( + feed1 Feed + feed2 Feed + ) + sink1 := make(chan int, 1) + sink2 := make(chan int, 1) + + sub1 := feed1.Subscribe(sink1) + sub2 := feed2.Subscribe(sink2) + + sub := JoinSubscriptions(sub1, sub2) + sub.Unsubscribe() + select { + case <-sub.Err(): + case <-time.After(50 * time.Millisecond): + t.Error("multisub didn't propagate closure") + } + // Ensure no more events are delivered + feed1.Send(1) + select { + case n := <-sink1: + t.Errorf("sink 1 unexpected delivery: %d", n) + default: + } + + feed2.Send(2) + select { + case n := <-sink2: + t.Errorf("sink 2 unexpected delivery: %d", n) + default: + } +} diff --git a/event/subscription.go b/event/subscription.go new file mode 100644 index 0000000..07e059c --- /dev/null +++ b/event/subscription.go @@ -0,0 +1,298 @@ +// 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 event + +import ( + "context" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" +) + +// Subscription represents a stream of events. The carrier of the events is typically a +// channel, but isn't part of the interface. +// +// Subscriptions can fail while established. Failures are reported through an error +// channel. It receives a value if there is an issue with the subscription (e.g. the +// network connection delivering the events has been closed). Only one value will ever be +// sent. +// +// The error channel is closed when the subscription ends successfully (i.e. when the +// source of events is closed). It is also closed when Unsubscribe is called. +// +// The Unsubscribe method cancels the sending of events. You must call Unsubscribe in all +// cases to ensure that resources related to the subscription are released. It can be +// called any number of times. +type Subscription interface { + Err() <-chan error // returns the error channel + Unsubscribe() // cancels sending of events, closing the error channel +} + +// NewSubscription runs a producer function as a subscription in a new goroutine. The +// channel given to the producer is closed when Unsubscribe is called. If fn returns an +// error, it is sent on the subscription's error channel. +func NewSubscription(producer func(<-chan struct{}) error) Subscription { + s := &funcSub{unsub: make(chan struct{}), err: make(chan error, 1)} + go func() { + defer close(s.err) + err := producer(s.unsub) + s.mu.Lock() + defer s.mu.Unlock() + if !s.unsubscribed { + if err != nil { + s.err <- err + } + s.unsubscribed = true + } + }() + return s +} + +type funcSub struct { + unsub chan struct{} + err chan error + mu sync.Mutex + unsubscribed bool +} + +func (s *funcSub) Unsubscribe() { + s.mu.Lock() + if s.unsubscribed { + s.mu.Unlock() + return + } + s.unsubscribed = true + close(s.unsub) + s.mu.Unlock() + // Wait for producer shutdown. + <-s.err +} + +func (s *funcSub) Err() <-chan error { + return s.err +} + +// Resubscribe calls fn repeatedly to keep a subscription established. When the +// subscription is established, Resubscribe waits for it to fail and calls fn again. This +// process repeats until Unsubscribe is called or the active subscription ends +// successfully. +// +// Resubscribe applies backoff between calls to fn. The time between calls is adapted +// based on the error rate, but will never exceed backoffMax. +func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription { + return ResubscribeErr(backoffMax, func(ctx context.Context, _ error) (Subscription, error) { + return fn(ctx) + }) +} + +// A ResubscribeFunc attempts to establish a subscription. +type ResubscribeFunc func(context.Context) (Subscription, error) + +// ResubscribeErr calls fn repeatedly to keep a subscription established. When the +// subscription is established, ResubscribeErr waits for it to fail and calls fn again. This +// process repeats until Unsubscribe is called or the active subscription ends +// successfully. +// +// The difference between Resubscribe and ResubscribeErr is that with ResubscribeErr, +// the error of the failing subscription is available to the callback for logging +// purposes. +// +// ResubscribeErr applies backoff between calls to fn. The time between calls is adapted +// based on the error rate, but will never exceed backoffMax. +func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscription { + s := &resubscribeSub{ + waitTime: backoffMax / 10, + backoffMax: backoffMax, + fn: fn, + err: make(chan error), + unsub: make(chan struct{}, 1), + } + go s.loop() + return s +} + +// A ResubscribeErrFunc attempts to establish a subscription. +// For every call but the first, the second argument to this function is +// the error that occurred with the previous subscription. +type ResubscribeErrFunc func(context.Context, error) (Subscription, error) + +type resubscribeSub struct { + fn ResubscribeErrFunc + err chan error + unsub chan struct{} + unsubOnce sync.Once + lastTry mclock.AbsTime + lastSubErr error + waitTime, backoffMax time.Duration +} + +func (s *resubscribeSub) Unsubscribe() { + s.unsubOnce.Do(func() { + s.unsub <- struct{}{} + <-s.err + }) +} + +func (s *resubscribeSub) Err() <-chan error { + return s.err +} + +func (s *resubscribeSub) loop() { + defer close(s.err) + var done bool + for !done { + sub := s.subscribe() + if sub == nil { + break + } + done = s.waitForError(sub) + sub.Unsubscribe() + } +} + +func (s *resubscribeSub) subscribe() Subscription { + subscribed := make(chan error) + var sub Subscription + for { + s.lastTry = mclock.Now() + ctx, cancel := context.WithCancel(context.Background()) + go func() { + rsub, err := s.fn(ctx, s.lastSubErr) + sub = rsub + subscribed <- err + }() + select { + case err := <-subscribed: + cancel() + if err == nil { + if sub == nil { + panic("event: ResubscribeFunc returned nil subscription and no error") + } + return sub + } + // Subscribing failed, wait before launching the next try. + if s.backoffWait() { + return nil // unsubscribed during wait + } + case <-s.unsub: + cancel() + <-subscribed // avoid leaking the s.fn goroutine. + return nil + } + } +} + +func (s *resubscribeSub) waitForError(sub Subscription) bool { + defer sub.Unsubscribe() + select { + case err := <-sub.Err(): + s.lastSubErr = err + return err == nil + case <-s.unsub: + return true + } +} + +func (s *resubscribeSub) backoffWait() bool { + if time.Duration(mclock.Now()-s.lastTry) > s.backoffMax { + s.waitTime = s.backoffMax / 10 + } else { + s.waitTime *= 2 + if s.waitTime > s.backoffMax { + s.waitTime = s.backoffMax + } + } + + t := time.NewTimer(s.waitTime) + defer t.Stop() + select { + case <-t.C: + return false + case <-s.unsub: + return true + } +} + +// SubscriptionScope provides a facility to unsubscribe multiple subscriptions at once. +// +// For code that handle more than one subscription, a scope can be used to conveniently +// unsubscribe all of them with a single call. The example demonstrates a typical use in a +// larger program. +// +// The zero value is ready to use. +type SubscriptionScope struct { + mu sync.Mutex + subs map[*scopeSub]struct{} + closed bool +} + +type scopeSub struct { + sc *SubscriptionScope + s Subscription +} + +// Track starts tracking a subscription. If the scope is closed, Track returns nil. The +// returned subscription is a wrapper. Unsubscribing the wrapper removes it from the +// scope. +func (sc *SubscriptionScope) Track(s Subscription) Subscription { + sc.mu.Lock() + defer sc.mu.Unlock() + if sc.closed { + return nil + } + if sc.subs == nil { + sc.subs = make(map[*scopeSub]struct{}) + } + ss := &scopeSub{sc, s} + sc.subs[ss] = struct{}{} + return ss +} + +// Close calls Unsubscribe on all tracked subscriptions and prevents further additions to +// the tracked set. Calls to Track after Close return nil. +func (sc *SubscriptionScope) Close() { + sc.mu.Lock() + defer sc.mu.Unlock() + if sc.closed { + return + } + sc.closed = true + for s := range sc.subs { + s.s.Unsubscribe() + } + sc.subs = nil +} + +// Count returns the number of tracked subscriptions. +// It is meant to be used for debugging. +func (sc *SubscriptionScope) Count() int { + sc.mu.Lock() + defer sc.mu.Unlock() + return len(sc.subs) +} + +func (s *scopeSub) Unsubscribe() { + s.s.Unsubscribe() + s.sc.mu.Lock() + defer s.sc.mu.Unlock() + delete(s.sc.subs, s) +} + +func (s *scopeSub) Err() <-chan error { + return s.s.Err() +} diff --git a/event/subscription_test.go b/event/subscription_test.go new file mode 100644 index 0000000..743d0bf --- /dev/null +++ b/event/subscription_test.go @@ -0,0 +1,180 @@ +// 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 event + +import ( + "context" + "errors" + "fmt" + "reflect" + "testing" + "time" +) + +var errInts = errors.New("error in subscribeInts") + +func subscribeInts(max, fail int, c chan<- int) Subscription { + return NewSubscription(func(quit <-chan struct{}) error { + for i := 0; i < max; i++ { + if i >= fail { + return errInts + } + select { + case c <- i: + case <-quit: + return nil + } + } + return nil + }) +} + +func TestNewSubscriptionError(t *testing.T) { + t.Parallel() + + channel := make(chan int) + sub := subscribeInts(10, 2, channel) +loop: + for want := 0; want < 10; want++ { + select { + case got := <-channel: + if got != want { + t.Fatalf("wrong int %d, want %d", got, want) + } + case err := <-sub.Err(): + if err != errInts { + t.Fatalf("wrong error: got %q, want %q", err, errInts) + } + if want != 2 { + t.Fatalf("got errInts at int %d, should be received at 2", want) + } + break loop + } + } + sub.Unsubscribe() + + err, ok := <-sub.Err() + if err != nil { + t.Fatal("got non-nil error after Unsubscribe") + } + if ok { + t.Fatal("channel still open after Unsubscribe") + } +} + +func TestResubscribe(t *testing.T) { + t.Parallel() + + var i int + nfails := 6 + sub := Resubscribe(100*time.Millisecond, func(ctx context.Context) (Subscription, error) { + // fmt.Printf("call #%d @ %v\n", i, time.Now()) + i++ + if i == 2 { + // Delay the second failure a bit to reset the resubscribe interval. + time.Sleep(200 * time.Millisecond) + } + if i < nfails { + return nil, errors.New("oops") + } + sub := NewSubscription(func(unsubscribed <-chan struct{}) error { return nil }) + return sub, nil + }) + + <-sub.Err() + if i != nfails { + t.Fatalf("resubscribe function called %d times, want %d times", i, nfails) + } +} + +func TestResubscribeAbort(t *testing.T) { + t.Parallel() + + done := make(chan error, 1) + sub := Resubscribe(0, func(ctx context.Context) (Subscription, error) { + select { + case <-ctx.Done(): + done <- nil + case <-time.After(2 * time.Second): + done <- errors.New("context given to resubscribe function not canceled within 2s") + } + return nil, nil + }) + + sub.Unsubscribe() + if err := <-done; err != nil { + t.Fatal(err) + } +} + +func TestResubscribeWithErrorHandler(t *testing.T) { + t.Parallel() + + var i int + nfails := 6 + subErrs := make([]string, 0) + sub := ResubscribeErr(100*time.Millisecond, func(ctx context.Context, lastErr error) (Subscription, error) { + i++ + var lastErrVal string + if lastErr != nil { + lastErrVal = lastErr.Error() + } + subErrs = append(subErrs, lastErrVal) + sub := NewSubscription(func(unsubscribed <-chan struct{}) error { + if i < nfails { + return fmt.Errorf("err-%v", i) + } else { + return nil + } + }) + return sub, nil + }) + + <-sub.Err() + if i != nfails { + t.Fatalf("resubscribe function called %d times, want %d times", i, nfails) + } + + expectedSubErrs := []string{"", "err-1", "err-2", "err-3", "err-4", "err-5"} + if !reflect.DeepEqual(subErrs, expectedSubErrs) { + 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 new file mode 100644 index 0000000..a757b67 --- /dev/null +++ b/go.mod @@ -0,0 +1,149 @@ +module github.com/ethereum/go-ethereum + +go 1.21 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 + github.com/Microsoft/go-winio v0.6.2 + github.com/VictoriaMetrics/fastcache v1.12.2 + 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.79.0 + github.com/cockroachdb/pebble v1.1.1 + github.com/consensys/gnark-crypto v0.12.1 + github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c + github.com/crate-crypto/go-kzg-4844 v1.0.0 + github.com/davecgh/go-spew v1.1.1 + github.com/deckarep/golang-set/v2 v2.6.0 + github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 + github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 + github.com/ethereum/c-kzg-4844 v1.0.0 + github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 + github.com/fatih/color v1.16.0 + github.com/ferranbt/fastssz v0.1.2 + github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e + github.com/fjl/memsize v0.0.2 + github.com/fsnotify/fsnotify v1.6.0 + github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff + github.com/gofrs/flock v0.8.1 + github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/golang/protobuf v1.5.4 + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb + 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-20240216141850-2abb0c79d3c4 + github.com/holiman/bloomfilter/v2 v2.0.3 + github.com/holiman/uint256 v1.3.0 + 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-20230811132847-661be99b8267 + github.com/julienschmidt/httprouter v1.3.0 + github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52 + github.com/kilic/bls12-381 v0.1.0 + github.com/kylelemons/godebug v1.1.0 + github.com/mattn/go-colorable v0.1.13 + github.com/mattn/go-isatty v0.0.20 + github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 + github.com/olekukonko/tablewriter v0.0.5 + github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 + github.com/protolambda/bls12-381-util v0.1.0 + github.com/protolambda/zrnt v0.32.2 + github.com/protolambda/ztyp v0.2.2 + 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.9.0 + 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.25.7 + go.uber.org/automaxprocs v1.5.2 + golang.org/x/crypto v0.22.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.20.0 + golang.org/x/text v0.14.0 + golang.org/x/time v0.5.0 + golang.org/x/tools v0.20.0 + google.golang.org/protobuf v1.33.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + 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.10.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // 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/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // 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.27.0 // indirect + github.com/go-ole/go-ole v1.3.0 // 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-querystring v1.1.0 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // 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/klauspost/compress v1.16.0 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // 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/minio/sha256-simd v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/naoina/go-stringutil v0.1.0 // indirect + github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.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.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.17.0 // indirect + golang.org/x/net v0.24.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..826f0f6 --- /dev/null +++ b/go.sum @@ -0,0 +1,879 @@ +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/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/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +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/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= +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/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +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.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +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/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/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.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/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +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/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.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.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.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +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 v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw= +github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +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/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +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-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= +github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= +github.com/crate-crypto/go-kzg-4844 v1.0.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= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +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.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao= +github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= +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_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/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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= +github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk= +github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= +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.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +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= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc= +github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= +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/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +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-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/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/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +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= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/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.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.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.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/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +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-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +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/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/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/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.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.3.0 h1:4wdcm/tnd0xXdu7iS3ruNvxkWwrb4aeBQv19ayYn8F4= +github.com/holiman/uint256 v1.3.0/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +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/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/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-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.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/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/karalabe/hid v1.0.1-0.20240306101548-573246063e52 h1:msKODTL1m0wigztaqILOtla9HeW1ciscYG4xjLtvk5I= +github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52/go.mod h1:qk1sX/IBgppQNcGCRoj90u6EGC056EBoIc1oEjCWla8= +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.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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +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= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +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/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +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/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/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +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/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= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/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/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= +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/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +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/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +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.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.1.0 h1:05DU2wJN7DTU7z28+Q+zejXkIsA/MF8JZQGhtBZZiWk= +github.com/protolambda/bls12-381-util v0.1.0/go.mod h1:cdkysJTRpeFeuUVx/TXGDQNMTiRAalk1vQw3TYTHcE4= +github.com/protolambda/zrnt v0.32.2 h1:KZ48T+3UhsPXNdtE/5QEvGc9DGjUaRI17nJaoznoIaM= +github.com/protolambda/zrnt v0.32.2/go.mod h1:A0fezkp9Tt3GBLATSPIbuY4ywYESyAuc/FFmPKg8Lqs= +github.com/protolambda/ztyp v0.2.2 h1:rVcL3vBu9W/aV646zF6caLS/dyn9BN8NYiuJzicLNyY= +github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= +github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw= +github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= +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.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/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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/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/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.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/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/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +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.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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +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-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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.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-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-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-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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.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-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-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.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.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.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.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.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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +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= +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-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-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.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.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= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +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.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-20200313102051-9f266ea9e77c/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= +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/graphiql.go b/graphql/graphiql.go new file mode 100644 index 0000000..823df0c --- /dev/null +++ b/graphql/graphiql.go @@ -0,0 +1,88 @@ +// The MIT License (MIT) +// +// Copyright (c) 2016 Muhammed Thanish +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package graphql + +import ( + "encoding/json" + "net/http" + "path/filepath" + + "github.com/ethereum/go-ethereum/graphql/internal/graphiql" + "github.com/ethereum/go-ethereum/log" +) + +// GraphiQL is an in-browser IDE for exploring GraphiQL APIs. +// This handler returns GraphiQL when requested. +// +// For more information, see https://github.com/graphql/graphiql. +type GraphiQL struct{} + +func respOk(w http.ResponseWriter, body []byte, ctype string) { + w.Header().Set("Content-Type", ctype) + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Write(body) +} + +func respErr(w http.ResponseWriter, msg string, code int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + errMsg, _ := json.Marshal(struct { + Error string + }{Error: msg}) + w.Write(errMsg) +} + +func (h GraphiQL) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + respErr(w, "only GET allowed", http.StatusMethodNotAllowed) + return + } + switch r.URL.Path { + case "/graphql/ui/graphiql.min.css": + data, err := graphiql.Assets.ReadFile(filepath.Base(r.URL.Path)) + if err != nil { + log.Warn("Error loading graphiql asset", "err", err) + respErr(w, "internal error", http.StatusInternalServerError) + return + } + respOk(w, data, "text/css") + case "/graphql/ui/graphiql.min.js", + "/graphql/ui/react.production.min.js", + "/graphql/ui/react-dom.production.min.js": + data, err := graphiql.Assets.ReadFile(filepath.Base(r.URL.Path)) + if err != nil { + log.Warn("Error loading graphiql asset", "err", err) + respErr(w, "internal error", http.StatusInternalServerError) + return + } + respOk(w, data, "application/javascript; charset=utf-8") + default: + data, err := graphiql.Assets.ReadFile("index.html") + if err != nil { + log.Warn("Error loading graphiql asset", "err", err) + respErr(w, "internal error", http.StatusInternalServerError) + return + } + respOk(w, data, "text/html") + } +} diff --git a/graphql/graphql.go b/graphql/graphql.go new file mode 100644 index 0000000..f7cf164 --- /dev/null +++ b/graphql/graphql.go @@ -0,0 +1,1547 @@ +// 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 graphql provides a GraphQL interface to Ethereum node data. +package graphql + +import ( + "context" + "errors" + "fmt" + "math/big" + "sort" + "strconv" + "strings" + "sync" + + "github.com/ethereum/go-ethereum" + "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/eip1559" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + 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 + +// ImplementsGraphQLType returns true if Long implements the provided GraphQL type. +func (b Long) ImplementsGraphQLType(name string) bool { return name == "Long" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Long) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + // uncomment to support hex values + if strings.HasPrefix(input, "0x") { + // apply leniency and support hex representations of longs. + value, err := hexutil.DecodeUint64(input) + *b = Long(value) + return err + } else { + value, err := strconv.ParseInt(input, 10, 64) + *b = Long(value) + return err + } + case int32: + *b = Long(input) + case int64: + *b = Long(input) + case float64: + *b = Long(input) + default: + err = fmt.Errorf("unexpected type %T for Long", input) + } + return err +} + +// Account represents an Ethereum account at a particular block. +type Account struct { + r *Resolver + address common.Address + blockNrOrHash rpc.BlockNumberOrHash +} + +// getState fetches the StateDB object for an account. +func (a *Account) getState(ctx context.Context) (*state.StateDB, error) { + state, _, err := a.r.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash) + return state, err +} + +func (a *Account) Address(ctx context.Context) (common.Address, error) { + return a.address, nil +} + +func (a *Account) Balance(ctx context.Context) (hexutil.Big, error) { + state, err := a.getState(ctx) + if err != nil { + return hexutil.Big{}, err + } + balance := state.GetBalance(a.address).ToBig() + if balance == nil { + return hexutil.Big{}, fmt.Errorf("failed to load balance %x", a.address) + } + return hexutil.Big(*balance), nil +} + +func (a *Account) TransactionCount(ctx context.Context) (hexutil.Uint64, error) { + // Ask transaction pool for the nonce which includes pending transactions + if blockNr, ok := a.blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber { + nonce, err := a.r.backend.GetPoolNonce(ctx, a.address) + if err != nil { + return 0, err + } + return hexutil.Uint64(nonce), nil + } + state, err := a.getState(ctx) + if err != nil { + return 0, err + } + return hexutil.Uint64(state.GetNonce(a.address)), nil +} + +func (a *Account) Code(ctx context.Context) (hexutil.Bytes, error) { + state, err := a.getState(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return state.GetCode(a.address), nil +} + +func (a *Account) Storage(ctx context.Context, args struct{ Slot common.Hash }) (common.Hash, error) { + state, err := a.getState(ctx) + if err != nil { + return common.Hash{}, err + } + return state.GetState(a.address, args.Slot), nil +} + +// Log represents an individual log message. All arguments are mandatory. +type Log struct { + r *Resolver + transaction *Transaction + log *types.Log +} + +func (l *Log) Transaction(ctx context.Context) *Transaction { + return l.transaction +} + +func (l *Log) Account(ctx context.Context, args BlockNumberArgs) *Account { + return &Account{ + r: l.r, + address: l.log.Address, + blockNrOrHash: args.NumberOrLatest(), + } +} + +func (l *Log) Index(ctx context.Context) hexutil.Uint64 { + return hexutil.Uint64(l.log.Index) +} + +func (l *Log) Topics(ctx context.Context) []common.Hash { + return l.log.Topics +} + +func (l *Log) Data(ctx context.Context) hexutil.Bytes { + return l.log.Data +} + +// AccessTuple represents EIP-2930 +type AccessTuple struct { + address common.Address + storageKeys []common.Hash +} + +func (at *AccessTuple) Address(ctx context.Context) common.Address { + return at.address +} + +func (at *AccessTuple) StorageKeys(ctx context.Context) []common.Hash { + return at.storageKeys +} + +// Withdrawal represents a withdrawal of value from the beacon chain +// by a validator. For details see EIP-4895. +type Withdrawal struct { + index uint64 + validator uint64 + address common.Address + amount uint64 +} + +func (w *Withdrawal) Index(ctx context.Context) hexutil.Uint64 { + return hexutil.Uint64(w.index) +} + +func (w *Withdrawal) Validator(ctx context.Context) hexutil.Uint64 { + return hexutil.Uint64(w.validator) +} + +func (w *Withdrawal) Address(ctx context.Context) common.Address { + return w.address +} + +func (w *Withdrawal) Amount(ctx context.Context) hexutil.Uint64 { + return hexutil.Uint64(w.amount) +} + +// Transaction represents an Ethereum transaction. +// backend and hash are mandatory; all others will be fetched when required. +type Transaction struct { + r *Resolver + hash common.Hash // Must be present after initialization + mu sync.Mutex + // mu protects following resources + tx *types.Transaction + block *Block + index uint64 +} + +// resolve returns the internal transaction object, fetching it if needed. +// It also returns the block the tx belongs to, unless it is a pending tx. +func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block) { + t.mu.Lock() + defer t.mu.Unlock() + if t.tx != nil { + return t.tx, t.block + } + // Try to return an already finalized transaction + found, tx, blockHash, _, index, _ := t.r.backend.GetTransaction(ctx, t.hash) + if found { + t.tx = tx + blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false) + t.block = &Block{ + r: t.r, + numberOrHash: &blockNrOrHash, + hash: blockHash, + } + t.index = index + return t.tx, t.block + } + // No finalized transaction, try to retrieve it from the pool + t.tx = t.r.backend.GetPoolTransaction(t.hash) + return t.tx, nil +} + +func (t *Transaction) Hash(ctx context.Context) common.Hash { + return t.hash +} + +func (t *Transaction) InputData(ctx context.Context) hexutil.Bytes { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Bytes{} + } + return tx.Data() +} + +func (t *Transaction) Gas(ctx context.Context) hexutil.Uint64 { + tx, _ := t.resolve(ctx) + if tx == nil { + return 0 + } + return hexutil.Uint64(tx.Gas()) +} + +func (t *Transaction) GasPrice(ctx context.Context) hexutil.Big { + tx, block := t.resolve(ctx) + if tx == nil { + return hexutil.Big{} + } + switch tx.Type() { + case types.DynamicFeeTxType: + if block != nil { + if baseFee, _ := block.BaseFeePerGas(ctx); baseFee != nil { + // price = min(gasTipCap + baseFee, gasFeeCap) + return (hexutil.Big)(*math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee.ToInt()), tx.GasFeeCap())) + } + } + return hexutil.Big(*tx.GasPrice()) + default: + return hexutil.Big(*tx.GasPrice()) + } +} + +func (t *Transaction) EffectiveGasPrice(ctx context.Context) (*hexutil.Big, error) { + tx, block := t.resolve(ctx) + if tx == nil { + return nil, nil + } + // Pending tx + if block == nil { + return nil, nil + } + header, err := block.resolveHeader(ctx) + if err != nil || header == nil { + return nil, err + } + if header.BaseFee == nil { + return (*hexutil.Big)(tx.GasPrice()), nil + } + return (*hexutil.Big)(math.BigMin(new(big.Int).Add(tx.GasTipCap(), header.BaseFee), tx.GasFeeCap())), nil +} + +func (t *Transaction) MaxFeePerGas(ctx context.Context) *hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil + } + switch tx.Type() { + case types.DynamicFeeTxType, types.BlobTxType: + return (*hexutil.Big)(tx.GasFeeCap()) + default: + return nil + } +} + +func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) *hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil + } + switch tx.Type() { + 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 { + return nil, nil + } + // Pending tx + if block == nil { + return nil, nil + } + header, err := block.resolveHeader(ctx) + if err != nil || header == nil { + return nil, err + } + if header.BaseFee == nil { + return (*hexutil.Big)(tx.GasPrice()), nil + } + + tip, err := tx.EffectiveGasTip(header.BaseFee) + if err != nil { + return nil, err + } + return (*hexutil.Big)(tip), nil +} + +func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Big{}, nil + } + if tx.Value() == nil { + return hexutil.Big{}, fmt.Errorf("invalid transaction value %x", t.hash) + } + return hexutil.Big(*tx.Value()), nil +} + +func (t *Transaction) Nonce(ctx context.Context) hexutil.Uint64 { + tx, _ := t.resolve(ctx) + if tx == nil { + return 0 + } + return hexutil.Uint64(tx.Nonce()) +} + +func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) *Account { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil + } + to := tx.To() + if to == nil { + return nil + } + return &Account{ + r: t.r, + address: *to, + blockNrOrHash: args.NumberOrLatest(), + } +} + +func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) *Account { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil + } + signer := types.LatestSigner(t.r.backend.ChainConfig()) + from, _ := types.Sender(signer, tx) + return &Account{ + r: t.r, + address: from, + blockNrOrHash: args.NumberOrLatest(), + } +} + +func (t *Transaction) Block(ctx context.Context) *Block { + _, block := t.resolve(ctx) + return block +} + +func (t *Transaction) Index(ctx context.Context) *hexutil.Uint64 { + _, block := t.resolve(ctx) + // Pending tx + if block == nil { + return nil + } + index := hexutil.Uint64(t.index) + return &index +} + +// getReceipt returns the receipt associated with this transaction, if any. +func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) { + _, block := t.resolve(ctx) + // Pending tx + if block == nil { + return nil, nil + } + receipts, err := block.resolveReceipts(ctx) + if err != nil { + return nil, err + } + return receipts[t.index], nil +} + +func (t *Transaction) Status(ctx context.Context) (*hexutil.Uint64, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + if len(receipt.PostState) != 0 { + return nil, nil + } + ret := hexutil.Uint64(receipt.Status) + return &ret, nil +} + +func (t *Transaction) GasUsed(ctx context.Context) (*hexutil.Uint64, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + ret := hexutil.Uint64(receipt.GasUsed) + return &ret, nil +} + +func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*hexutil.Uint64, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + ret := hexutil.Uint64(receipt.CumulativeGasUsed) + 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{}) { + return nil, err + } + return &Account{ + r: t.r, + address: receipt.ContractAddress, + blockNrOrHash: args.NumberOrLatest(), + }, nil +} + +func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) { + _, block := t.resolve(ctx) + // Pending tx + if block == nil { + return nil, nil + } + h, err := block.Hash(ctx) + if err != nil { + return nil, err + } + return t.getLogs(ctx, h) +} + +// getLogs returns log objects for the given tx. +// Assumes block hash is resolved. +func (t *Transaction) getLogs(ctx context.Context, hash common.Hash) (*[]*Log, error) { + var ( + filter = t.r.filterSystem.NewBlockFilter(hash, nil, nil) + logs, err = filter.Logs(ctx) + ) + if err != nil { + return nil, err + } + var ret []*Log + // Select tx logs from all block logs + ix := sort.Search(len(logs), func(i int) bool { return uint64(logs[i].TxIndex) >= t.index }) + for ix < len(logs) && uint64(logs[ix].TxIndex) == t.index { + ret = append(ret, &Log{ + r: t.r, + transaction: t, + log: logs[ix], + }) + ix++ + } + return &ret, nil +} + +func (t *Transaction) Type(ctx context.Context) *hexutil.Uint64 { + tx, _ := t.resolve(ctx) + txType := hexutil.Uint64(tx.Type()) + return &txType +} + +func (t *Transaction) AccessList(ctx context.Context) *[]*AccessTuple { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil + } + accessList := tx.AccessList() + ret := make([]*AccessTuple, 0, len(accessList)) + for _, al := range accessList { + ret = append(ret, &AccessTuple{ + address: al.Address, + storageKeys: al.StorageKeys, + }) + } + return &ret +} + +func (t *Transaction) R(ctx context.Context) hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Big{} + } + _, r, _ := tx.RawSignatureValues() + return hexutil.Big(*r) +} + +func (t *Transaction) S(ctx context.Context) hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Big{} + } + _, _, s := tx.RawSignatureValues() + return hexutil.Big(*s) +} + +func (t *Transaction) V(ctx context.Context) hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Big{} + } + v, _, _ := tx.RawSignatureValues() + return hexutil.Big(*v) +} + +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.Big(*v) + return &ret, nil +} + +func (t *Transaction) Raw(ctx context.Context) (hexutil.Bytes, error) { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Bytes{}, nil + } + return tx.MarshalBinary() +} + +func (t *Transaction) RawReceipt(ctx context.Context) (hexutil.Bytes, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return hexutil.Bytes{}, err + } + return receipt.MarshalBinary() +} + +type BlockType int + +// Block represents an Ethereum block. +// backend, and numberOrHash are mandatory. All other fields are lazily fetched +// when required. +type Block struct { + r *Resolver + numberOrHash *rpc.BlockNumberOrHash // Field resolvers assume numberOrHash is always present + mu sync.Mutex + // mu protects following resources + hash common.Hash // Must be resolved during initialization + header *types.Header + block *types.Block + receipts []*types.Receipt +} + +// resolve returns the internal Block object representing this block, fetching +// it if necessary. +func (b *Block) resolve(ctx context.Context) (*types.Block, error) { + b.mu.Lock() + defer b.mu.Unlock() + if b.block != nil { + return b.block, nil + } + if b.numberOrHash == nil { + latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + b.numberOrHash = &latest + } + var err error + b.block, err = b.r.backend.BlockByNumberOrHash(ctx, *b.numberOrHash) + if b.block != nil { + b.hash = b.block.Hash() + if b.header == nil { + b.header = b.block.Header() + } + } + return b.block, err +} + +// resolveHeader returns the internal Header object for this block, fetching it +// if necessary. Call this function instead of `resolve` unless you need the +// additional data (transactions and uncles). +func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) { + b.mu.Lock() + defer b.mu.Unlock() + if b.header != nil { + return b.header, nil + } + if b.numberOrHash == nil && b.hash == (common.Hash{}) { + return nil, errBlockInvariant + } + var err error + b.header, err = b.r.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash) + if err != nil { + return nil, err + } + if b.hash == (common.Hash{}) { + b.hash = b.header.Hash() + } + return b.header, nil +} + +// resolveReceipts returns the list of receipts for this block, fetching them +// if necessary. +func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) { + b.mu.Lock() + defer b.mu.Unlock() + if b.receipts != nil { + return b.receipts, nil + } + receipts, err := b.r.backend.GetReceipts(ctx, b.hash) + if err != nil { + return nil, err + } + b.receipts = receipts + return receipts, nil +} + +func (b *Block) Number(ctx context.Context) (hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return 0, err + } + + return hexutil.Uint64(header.Number.Uint64()), nil +} + +func (b *Block) Hash(ctx context.Context) (common.Hash, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.hash, nil +} + +func (b *Block) GasLimit(ctx context.Context) (hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return 0, err + } + return hexutil.Uint64(header.GasLimit), nil +} + +func (b *Block) GasUsed(ctx context.Context) (hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return 0, err + } + return hexutil.Uint64(header.GasUsed), nil +} + +func (b *Block) BaseFeePerGas(ctx context.Context) (*hexutil.Big, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + if header.BaseFee == nil { + return nil, nil + } + return (*hexutil.Big)(header.BaseFee), nil +} + +func (b *Block) NextBaseFeePerGas(ctx context.Context) (*hexutil.Big, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + chaincfg := b.r.backend.ChainConfig() + if header.BaseFee == nil { + // Make sure next block doesn't enable EIP-1559 + if !chaincfg.IsLondon(new(big.Int).Add(header.Number, common.Big1)) { + return nil, nil + } + } + nextBaseFee := eip1559.CalcBaseFee(chaincfg, header) + return (*hexutil.Big)(nextBaseFee), nil +} + +func (b *Block) Parent(ctx context.Context) (*Block, error) { + if _, err := b.resolveHeader(ctx); err != nil { + return nil, err + } + if b.header == nil || b.header.Number.Uint64() < 1 { + return nil, nil + } + var ( + num = rpc.BlockNumber(b.header.Number.Uint64() - 1) + hash = b.header.ParentHash + numOrHash = rpc.BlockNumberOrHash{ + BlockNumber: &num, + BlockHash: &hash, + } + ) + return &Block{ + r: b.r, + numberOrHash: &numOrHash, + hash: hash, + }, nil +} + +func (b *Block) Difficulty(ctx context.Context) (hexutil.Big, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Big{}, err + } + return hexutil.Big(*header.Difficulty), nil +} + +func (b *Block) Timestamp(ctx context.Context) (hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return 0, err + } + return hexutil.Uint64(header.Time), nil +} + +func (b *Block) Nonce(ctx context.Context) (hexutil.Bytes, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return header.Nonce[:], nil +} + +func (b *Block) MixHash(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.MixDigest, nil +} + +func (b *Block) TransactionsRoot(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.TxHash, nil +} + +func (b *Block) StateRoot(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.Root, nil +} + +func (b *Block) ReceiptsRoot(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.ReceiptHash, nil +} + +func (b *Block) OmmerHash(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.UncleHash, nil +} + +func (b *Block) OmmerCount(ctx context.Context) (*hexutil.Uint64, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + count := hexutil.Uint64(len(block.Uncles())) + return &count, err +} + +func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + ret := make([]*Block, 0, len(block.Uncles())) + for _, uncle := range block.Uncles() { + blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false) + ret = append(ret, &Block{ + r: b.r, + numberOrHash: &blockNumberOrHash, + header: uncle, + hash: uncle.Hash(), + }) + } + return &ret, nil +} + +func (b *Block) ExtraData(ctx context.Context) (hexutil.Bytes, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return header.Extra, nil +} + +func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return header.Bloom.Bytes(), nil +} + +func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) { + hash, err := b.Hash(ctx) + if err != nil { + return hexutil.Big{}, err + } + td := b.r.backend.GetTd(ctx, hash) + if td == nil { + return hexutil.Big{}, fmt.Errorf("total difficulty not found %x", hash) + } + return hexutil.Big(*td), nil +} + +func (b *Block) RawHeader(ctx context.Context) (hexutil.Bytes, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return rlp.EncodeToBytes(header) +} + +func (b *Block) Raw(ctx context.Context) (hexutil.Bytes, error) { + block, err := b.resolve(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return rlp.EncodeToBytes(block) +} + +// BlockNumberArgs encapsulates arguments to accessors that specify a block number. +type BlockNumberArgs struct { + // TODO: Ideally we could use input unions to allow the query to specify the + // block parameter by hash, block number, or tag but input unions aren't part of the + // standard GraphQL schema SDL yet, see: https://github.com/graphql/graphql-spec/issues/488 + Block *Long +} + +// NumberOr returns the provided block number argument, or the "current" block number or hash if none +// was provided. +func (a BlockNumberArgs) NumberOr(current rpc.BlockNumberOrHash) rpc.BlockNumberOrHash { + if a.Block != nil { + blockNr := rpc.BlockNumber(*a.Block) + return rpc.BlockNumberOrHashWithNumber(blockNr) + } + return current +} + +// NumberOrLatest returns the provided block number argument, or the "latest" block number if none +// was provided. +func (a BlockNumberArgs) NumberOrLatest() rpc.BlockNumberOrHash { + return a.NumberOr(rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)) +} + +func (b *Block) Miner(ctx context.Context, args BlockNumberArgs) (*Account, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + return &Account{ + r: b.r, + address: header.Coinbase, + blockNrOrHash: args.NumberOrLatest(), + }, nil +} + +func (b *Block) TransactionCount(ctx context.Context) (*hexutil.Uint64, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + count := hexutil.Uint64(len(block.Transactions())) + return &count, err +} + +func (b *Block) Transactions(ctx context.Context) (*[]*Transaction, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + ret := make([]*Transaction, 0, len(block.Transactions())) + for i, tx := range block.Transactions() { + ret = append(ret, &Transaction{ + r: b.r, + hash: tx.Hash(), + tx: tx, + block: b, + index: uint64(i), + }) + } + return &ret, nil +} + +func (b *Block) TransactionAt(ctx context.Context, args struct{ Index Long }) (*Transaction, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + txs := block.Transactions() + if args.Index < 0 || int(args.Index) >= len(txs) { + return nil, nil + } + tx := txs[args.Index] + return &Transaction{ + r: b.r, + hash: tx.Hash(), + tx: tx, + block: b, + index: uint64(args.Index), + }, nil +} + +func (b *Block) OmmerAt(ctx context.Context, args struct{ Index Long }) (*Block, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + uncles := block.Uncles() + if args.Index < 0 || int(args.Index) >= len(uncles) { + return nil, nil + } + uncle := uncles[args.Index] + blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false) + return &Block{ + r: b.r, + numberOrHash: &blockNumberOrHash, + header: uncle, + hash: uncle.Hash(), + }, nil +} + +func (b *Block) WithdrawalsRoot(ctx context.Context) (*common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + // Pre-shanghai blocks + if header.WithdrawalsHash == nil { + return nil, nil + } + return header.WithdrawalsHash, nil +} + +func (b *Block) Withdrawals(ctx context.Context) (*[]*Withdrawal, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + // Pre-shanghai blocks + if block.Header().WithdrawalsHash == nil { + return nil, nil + } + ret := make([]*Withdrawal, 0, len(block.Withdrawals())) + for _, w := range block.Withdrawals() { + ret = append(ret, &Withdrawal{ + index: w.Index, + validator: w.Validator, + address: w.Address, + amount: w.Amount, + }) + } + return &ret, nil +} + +func (b *Block) BlobGasUsed(ctx context.Context) (*hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + if header.BlobGasUsed == nil { + return nil, nil + } + ret := hexutil.Uint64(*header.BlobGasUsed) + return &ret, nil +} + +func (b *Block) ExcessBlobGas(ctx context.Context) (*hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + if header.ExcessBlobGas == nil { + return nil, nil + } + ret := hexutil.Uint64(*header.ExcessBlobGas) + return &ret, nil +} + +// BlockFilterCriteria encapsulates criteria passed to a `logs` accessor inside +// a block. +type BlockFilterCriteria struct { + Addresses *[]common.Address // restricts matches to events created by specific contracts + + // The Topic list restricts matches to particular event topics. Each event has a list + // of topics. Topics matches a prefix of that list. An empty element slice 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 *[][]common.Hash +} + +// runFilter accepts a filter and executes it, returning all its results as +// `Log` objects. +func runFilter(ctx context.Context, r *Resolver, filter *filters.Filter) ([]*Log, error) { + logs, err := filter.Logs(ctx) + if err != nil || logs == nil { + return nil, err + } + ret := make([]*Log, 0, len(logs)) + for _, log := range logs { + ret = append(ret, &Log{ + r: r, + transaction: &Transaction{r: r, hash: log.TxHash}, + log: log, + }) + } + return ret, nil +} + +func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteria }) ([]*Log, error) { + var addresses []common.Address + if args.Filter.Addresses != nil { + addresses = *args.Filter.Addresses + } + var topics [][]common.Hash + if args.Filter.Topics != nil { + topics = *args.Filter.Topics + } + // Construct the range filter + hash, err := b.Hash(ctx) + if err != nil { + return nil, err + } + filter := b.r.filterSystem.NewBlockFilter(hash, addresses, topics) + + // Run the filter and return all the logs + return runFilter(ctx, b.r, filter) +} + +func (b *Block) Account(ctx context.Context, args struct { + Address common.Address +}) (*Account, error) { + return &Account{ + r: b.r, + address: args.Address, + blockNrOrHash: *b.numberOrHash, + }, nil +} + +// CallData encapsulates arguments to `call` or `estimateGas`. +// All arguments are optional. +type CallData struct { + From *common.Address // The Ethereum address the call is from. + To *common.Address // The Ethereum address the call is to. + Gas *Long // The amount of gas provided for the call. + GasPrice *hexutil.Big // The price of each unit of gas, in wei. + MaxFeePerGas *hexutil.Big // The max price of each unit of gas, in wei (1559). + MaxPriorityFeePerGas *hexutil.Big // The max tip of each unit of gas, in wei (1559). + Value *hexutil.Big // The value sent along with the call. + Data *hexutil.Bytes // Any data sent with the call. +} + +// CallResult encapsulates the result of an invocation of the `call` accessor. +type CallResult struct { + data hexutil.Bytes // The return data from the call + gasUsed hexutil.Uint64 // The amount of gas used + status hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success. +} + +func (c *CallResult) Data() hexutil.Bytes { + return c.data +} + +func (c *CallResult) GasUsed() hexutil.Uint64 { + return c.gasUsed +} + +func (c *CallResult) Status() hexutil.Uint64 { + return c.status +} + +func (b *Block) Call(ctx context.Context, args struct { + Data ethapi.TransactionArgs +}) (*CallResult, error) { + result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap()) + if err != nil { + return nil, err + } + status := hexutil.Uint64(1) + if result.Failed() { + status = 0 + } + + return &CallResult{ + data: result.ReturnData, + gasUsed: hexutil.Uint64(result.UsedGas), + status: status, + }, nil +} + +func (b *Block) EstimateGas(ctx context.Context, args struct { + Data ethapi.TransactionArgs +}) (hexutil.Uint64, error) { + return ethapi.DoEstimateGas(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCGasCap()) +} + +type Pending struct { + r *Resolver +} + +func (p *Pending) TransactionCount(ctx context.Context) (hexutil.Uint64, error) { + txs, err := p.r.backend.GetPoolTransactions() + return hexutil.Uint64(len(txs)), err +} + +func (p *Pending) Transactions(ctx context.Context) (*[]*Transaction, error) { + txs, err := p.r.backend.GetPoolTransactions() + if err != nil { + return nil, err + } + ret := make([]*Transaction, 0, len(txs)) + for i, tx := range txs { + ret = append(ret, &Transaction{ + r: p.r, + hash: tx.Hash(), + tx: tx, + index: uint64(i), + }) + } + return &ret, nil +} + +func (p *Pending) Account(ctx context.Context, args struct { + Address common.Address +}) *Account { + pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + return &Account{ + r: p.r, + address: args.Address, + blockNrOrHash: pendingBlockNr, + } +} + +func (p *Pending) Call(ctx context.Context, args struct { + Data ethapi.TransactionArgs +}) (*CallResult, error) { + pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap()) + if err != nil { + return nil, err + } + status := hexutil.Uint64(1) + if result.Failed() { + status = 0 + } + + return &CallResult{ + data: result.ReturnData, + gasUsed: hexutil.Uint64(result.UsedGas), + status: status, + }, nil +} + +func (p *Pending) EstimateGas(ctx context.Context, args struct { + Data ethapi.TransactionArgs +}) (hexutil.Uint64, error) { + latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + return ethapi.DoEstimateGas(ctx, p.r.backend, args.Data, latestBlockNr, nil, p.r.backend.RPCGasCap()) +} + +// Resolver is the top-level object in the GraphQL hierarchy. +type Resolver struct { + backend ethapi.Backend + filterSystem *filters.FilterSystem +} + +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 { + return nil, nil + } + number := rpc.BlockNumber(*args.Number) + numberOrHash = rpc.BlockNumberOrHashWithNumber(number) + } else if args.Hash != nil { + numberOrHash = rpc.BlockNumberOrHashWithHash(*args.Hash, false) + } else { + numberOrHash = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + } + block := &Block{ + r: r, + numberOrHash: &numberOrHash, + } + // Resolve the header, return nil if it doesn't exist. + // Note we don't resolve block directly here since it will require an + // additional network request for light client. + h, err := block.resolveHeader(ctx) + if err != nil { + return nil, err + } else if h == nil { + return nil, nil + } + return block, nil +} + +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 + if args.To != nil { + to = rpc.BlockNumber(*args.To) + } else { + to = rpc.BlockNumber(r.backend.CurrentBlock().Number.Int64()) + } + if to < from { + return nil, errInvalidBlockRange + } + var ret []*Block + for i := from; i <= to; i++ { + numberOrHash := rpc.BlockNumberOrHashWithNumber(i) + block := &Block{ + r: r, + numberOrHash: &numberOrHash, + } + // Resolve the header to check for existence. + // Note we don't resolve block directly here since it will require an + // additional network request for light client. + h, err := block.resolveHeader(ctx) + if err != nil { + return nil, err + } else if h == nil { + // Blocks after must be non-existent too, break. + break + } + ret = append(ret, block) + if err := ctx.Err(); err != nil { + return nil, err + } + } + return ret, nil +} + +func (r *Resolver) Pending(ctx context.Context) *Pending { + return &Pending{r} +} + +func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Hash }) *Transaction { + tx := &Transaction{ + r: r, + hash: args.Hash, + } + // Resolve the transaction; if it doesn't exist, return nil. + t, _ := tx.resolve(ctx) + if t == nil { + return nil + } + return tx +} + +func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hexutil.Bytes }) (common.Hash, error) { + tx := new(types.Transaction) + if err := tx.UnmarshalBinary(args.Data); err != nil { + return common.Hash{}, err + } + hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx) + return hash, err +} + +// FilterCriteria encapsulates the arguments to `logs` on the root resolver object. +type FilterCriteria struct { + FromBlock *Long // beginning of the queried range, nil means genesis block + ToBlock *Long // end of the range, nil means latest block + Addresses *[]common.Address // restricts matches to events created by specific contracts + + // The Topic list restricts matches to particular event topics. Each event has a list + // of topics. Topics matches a prefix of that list. An empty element slice 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 *[][]common.Hash +} + +func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria }) ([]*Log, error) { + // Convert the RPC block numbers into internal representations + begin := rpc.LatestBlockNumber.Int64() + if args.Filter.FromBlock != nil { + begin = int64(*args.Filter.FromBlock) + } + end := rpc.LatestBlockNumber.Int64() + 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 + } + var topics [][]common.Hash + if args.Filter.Topics != nil { + topics = *args.Filter.Topics + } + // Construct the range filter + filter := r.filterSystem.NewRangeFilter(begin, end, addresses, topics) + return runFilter(ctx, r, filter) +} + +func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) { + tipcap, err := r.backend.SuggestGasTipCap(ctx) + if err != nil { + return hexutil.Big{}, err + } + if head := r.backend.CurrentHeader(); head.BaseFee != nil { + tipcap.Add(tipcap, head.BaseFee) + } + return (hexutil.Big)(*tipcap), nil +} + +func (r *Resolver) MaxPriorityFeePerGas(ctx context.Context) (hexutil.Big, error) { + tipcap, err := r.backend.SuggestGasTipCap(ctx) + if err != nil { + return hexutil.Big{}, err + } + return (hexutil.Big)(*tipcap), nil +} + +func (r *Resolver) ChainID(ctx context.Context) (hexutil.Big, error) { + return hexutil.Big(*r.backend.ChainConfig().ChainID), nil +} + +// SyncState represents the synchronisation status returned from the `syncing` accessor. +type SyncState struct { + progress ethereum.SyncProgress +} + +func (s *SyncState) StartingBlock() hexutil.Uint64 { + return hexutil.Uint64(s.progress.StartingBlock) +} +func (s *SyncState) CurrentBlock() hexutil.Uint64 { + return hexutil.Uint64(s.progress.CurrentBlock) +} +func (s *SyncState) HighestBlock() hexutil.Uint64 { + return hexutil.Uint64(s.progress.HighestBlock) +} +func (s *SyncState) SyncedAccounts() hexutil.Uint64 { + return hexutil.Uint64(s.progress.SyncedAccounts) +} +func (s *SyncState) SyncedAccountBytes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.SyncedAccountBytes) +} +func (s *SyncState) SyncedBytecodes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.SyncedBytecodes) +} +func (s *SyncState) SyncedBytecodeBytes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.SyncedBytecodeBytes) +} +func (s *SyncState) SyncedStorage() hexutil.Uint64 { + return hexutil.Uint64(s.progress.SyncedStorage) +} +func (s *SyncState) SyncedStorageBytes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.SyncedStorageBytes) +} +func (s *SyncState) HealedTrienodes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.HealedTrienodes) +} +func (s *SyncState) HealedTrienodeBytes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.HealedTrienodeBytes) +} +func (s *SyncState) HealedBytecodes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.HealedBytecodes) +} +func (s *SyncState) HealedBytecodeBytes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.HealedBytecodeBytes) +} +func (s *SyncState) HealingTrienodes() hexutil.Uint64 { + return hexutil.Uint64(s.progress.HealingTrienodes) +} +func (s *SyncState) HealingBytecode() hexutil.Uint64 { + return hexutil.Uint64(s.progress.HealingBytecode) +} +func (s *SyncState) TxIndexFinishedBlocks() hexutil.Uint64 { + return hexutil.Uint64(s.progress.TxIndexFinishedBlocks) +} +func (s *SyncState) TxIndexRemainingBlocks() hexutil.Uint64 { + return hexutil.Uint64(s.progress.TxIndexRemainingBlocks) +} + +// Syncing returns false in case the node is currently not syncing with the network. It can be up-to-date or has not +// yet received the latest block headers from its peers. In case it is synchronizing: +// - startingBlock: block number this node started to synchronize from +// - currentBlock: block number this node is currently importing +// - highestBlock: block number of the highest block header this node has received from peers +// - syncedAccounts: number of accounts downloaded +// - syncedAccountBytes: number of account trie bytes persisted to disk +// - syncedBytecodes: number of bytecodes downloaded +// - syncedBytecodeBytes: number of bytecode bytes downloaded +// - syncedStorage: number of storage slots downloaded +// - syncedStorageBytes: number of storage trie bytes persisted to disk +// - healedTrienodes: number of state trie nodes downloaded +// - healedTrienodeBytes: number of state trie bytes persisted to disk +// - healedBytecodes: number of bytecodes downloaded +// - healedBytecodeBytes: number of bytecodes persisted to disk +// - healingTrienodes: number of state trie nodes pending +// - healingBytecode: number of bytecodes pending +// - txIndexFinishedBlocks: number of blocks whose transactions are indexed +// - txIndexRemainingBlocks: number of blocks whose transactions are not indexed yet +func (r *Resolver) Syncing() (*SyncState, error) { + progress := r.backend.SyncProgress() + + // Return not syncing if the synchronisation already completed + if progress.Done() { + return nil, nil + } + // Otherwise gather the block sync stats + return &SyncState{progress}, nil +} diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go new file mode 100644 index 0000000..f3f9d17 --- /dev/null +++ b/graphql/graphql_test.go @@ -0,0 +1,487 @@ +// 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 graphql + +import ( + "context" + "encoding/json" + "fmt" + "io" + "math/big" + "net/http" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + + "github.com/stretchr/testify/assert" +) + +func TestBuildSchema(t *testing.T) { + ddir := t.TempDir() + // Copy config + conf := node.DefaultConfig + conf.DataDir = ddir + stack, err := node.New(&conf) + if err != nil { + t.Fatalf("could not create new node: %v", err) + } + defer stack.Close() + // Make sure the schema can be parsed and matched up to the object model. + if _, err := newHandler(stack, nil, nil, []string{}, []string{}); err != nil { + t.Errorf("Could not construct GraphQL handler: %v", err) + } +} + +// Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint +func TestGraphQLBlockSerialization(t *testing.T) { + stack := createNode(t) + defer stack.Close() + genesis := &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: big.NewInt(1048576), + } + newGQLService(t, stack, false, genesis, 10, func(i int, gen *core.BlockGen) {}) + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + + for i, tt := range []struct { + body string + want string + code int + }{ + { // Should return latest block + body: `{"query": "{block{number}}","variables": null}`, + want: `{"data":{"block":{"number":"0xa"}}}`, + code: 200, + }, + { // Should return info about latest block + body: `{"query": "{block{number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":"0xa","gasUsed":"0x0","gasLimit":"0xaf79e0"}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:0){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":"0x0","gasUsed":"0x0","gasLimit":"0xaf79e0"}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:-1){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:-500){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"0\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":"0x0","gasUsed":"0x0","gasLimit":"0xaf79e0"}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"-33\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"1337\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"0x0\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":"0x0","gasUsed":"0x0","gasLimit":"0xaf79e0"}}}`, + //want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0x0\": invalid syntax"}],"data":{}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"a\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"errors":[{"message":"strconv.ParseInt: parsing \"a\": invalid syntax"}],"data":{}}`, + code: 400, + }, + { + body: `{"query": "{bleh{number}}","variables": null}"`, + want: `{"errors":[{"message":"Cannot query field \"bleh\" on type \"Query\".","locations":[{"line":1,"column":2}]}]}`, + code: 400, + }, + // should return `estimateGas` as decimal + { + body: `{"query": "{block{ estimateGas(data:{}) }}"}`, + want: `{"data":{"block":{"estimateGas":"0xd221"}}}`, + code: 200, + }, + // should return `status` as decimal + { + body: `{"query": "{block {number call (data : {from : \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"}`, + 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 { + t.Fatalf("could not post: %v", err) + } + bodyBytes, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + if have := string(bodyBytes); have != tt.want { + t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want) + } + 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") + } + } +} + +func TestGraphQLBlockSerializationEIP2718(t *testing.T) { + // Account for signing txes + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + dad = common.HexToAddress("0x0000000000000000000000000000000000000dad") + ) + stack := createNode(t) + defer stack.Close() + genesis := &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: big.NewInt(1048576), + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + // The address 0xdad sloads 0x00 and 0x01 + dad: { + Code: []byte{byte(vm.PC), byte(vm.PC), byte(vm.SLOAD), byte(vm.SLOAD)}, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + BaseFee: big.NewInt(params.InitialBaseFee), + } + signer := types.LatestSigner(genesis.Config) + newGQLService(t, stack, false, genesis, 1, func(i int, gen *core.BlockGen) { + gen.SetCoinbase(common.Address{1}) + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: uint64(0), + To: &dad, + Value: big.NewInt(100), + Gas: 50000, + GasPrice: big.NewInt(params.InitialBaseFee), + }) + gen.AddTx(tx) + tx, _ = types.SignNewTx(key, signer, &types.AccessListTx{ + ChainID: genesis.Config.ChainID, + Nonce: uint64(1), + To: &dad, + Gas: 30000, + GasPrice: big.NewInt(params.InitialBaseFee), + Value: big.NewInt(50), + AccessList: types.AccessList{{ + Address: dad, + StorageKeys: []common.Hash{{0}}, + }}, + }) + gen.AddTx(tx) + }) + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + + for i, tt := range []struct { + body string + want string + code int + }{ + { + body: `{"query": "{block {number transactions { from { address } to { address } value hash type accessList { address storageKeys } index}}}"}`, + want: `{"data":{"block":{"number":"0x1","transactions":[{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x64","hash":"0xd864c9d7d37fade6b70164740540c06dd58bb9c3f6b46101908d6339db6a6a7b","type":"0x0","accessList":[],"index":"0x0"},{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x32","hash":"0x19b35f8187b4e15fb59a9af469dca5dfa3cd363c11d372058c12f6482477b474","type":"0x1","accessList":[{"address":"0x0000000000000000000000000000000000000dad","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000"]}],"index":"0x1"}]}}}`, + code: 200, + }, + } { + resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body)) + if err != nil { + t.Fatalf("could not post: %v", err) + } + bodyBytes, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + if have := string(bodyBytes); have != tt.want { + t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want) + } + if tt.code != resp.StatusCode { + t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code) + } + } +} + +// Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint +func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { + stack := createNode(t) + defer stack.Close() + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + body := strings.NewReader(`{"query": "{block{number}}","variables": null}`) + resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", body) + if err != nil { + t.Fatalf("could not post: %v", err) + } + resp.Body.Close() + // make sure the request is not handled successfully + assert.Equal(t, http.StatusNotFound, resp.StatusCode) +} + +func TestGraphQLConcurrentResolvers(t *testing.T) { + var ( + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + dadStr = "0x0000000000000000000000000000000000000dad" + dad = common.HexToAddress(dadStr) + genesis = &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: big.NewInt(1048576), + Alloc: types.GenesisAlloc{ + addr: {Balance: big.NewInt(params.Ether)}, + dad: { + // LOG0(0, 0), LOG0(0, 0), RETURN(0, 0) + Code: common.Hex2Bytes("60006000a060006000a060006000f3"), + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + signer = types.LatestSigner(genesis.Config) + stack = createNode(t) + ) + defer stack.Close() + + var tx *types.Transaction + handler, chain := newGQLService(t, stack, false, genesis, 1, func(i int, gen *core.BlockGen) { + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) + gen.AddTx(tx) + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Nonce: 1, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) + gen.AddTx(tx) + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Nonce: 2, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) + gen.AddTx(tx) + }) + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + + for i, tt := range []struct { + body string + want string + }{ + // Multiple txes race to get/set the block hash. + { + body: "{block { transactions { logs { account { address } } } } }", + want: fmt.Sprintf(`{"block":{"transactions":[{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]},{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]},{"logs":[{"account":{"address":"%s"}},{"account":{"address":"%s"}}]}]}}`, dadStr, dadStr, dadStr, dadStr, dadStr, dadStr), + }, + // Multiple fields of a tx race to resolve it. Happens in this case + // because resolving the tx body belonging to a log is delayed. + { + body: `{block { logs(filter: {}) { transaction { nonce value gasPrice }}}}`, + want: `{"block":{"logs":[{"transaction":{"nonce":"0x0","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x0","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x1","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x1","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x2","value":"0x0","gasPrice":"0x3b9aca00"}},{"transaction":{"nonce":"0x2","value":"0x0","gasPrice":"0x3b9aca00"}}]}}`, + }, + // Multiple txes of a block race to set/retrieve receipts of a block. + { + body: "{block { transactions { status gasUsed } } }", + want: `{"block":{"transactions":[{"status":"0x1","gasUsed":"0x5508"},{"status":"0x1","gasUsed":"0x5508"},{"status":"0x1","gasUsed":"0x5508"}]}}`, + }, + // Multiple fields of block race to resolve header and body. + { + body: "{ block { number hash gasLimit ommerCount transactionCount totalDifficulty } }", + want: fmt.Sprintf(`{"block":{"number":"0x1","hash":"%s","gasLimit":"0xaf79e0","ommerCount":"0x0","transactionCount":"0x3","totalDifficulty":"0x200000"}}`, chain[len(chain)-1].Hash()), + }, + // Multiple fields of a block race to resolve the header and body. + { + body: fmt.Sprintf(`{ transaction(hash: "%s") { block { number hash gasLimit ommerCount transactionCount } } }`, tx.Hash()), + want: fmt.Sprintf(`{"transaction":{"block":{"number":"0x1","hash":"%s","gasLimit":"0xaf79e0","ommerCount":"0x0","transactionCount":"0x3"}}}`, chain[len(chain)-1].Hash()), + }, + // Account fields race the resolve the state object. + { + body: fmt.Sprintf(`{ block { account(address: "%s") { balance transactionCount code } } }`, dadStr), + want: `{"block":{"account":{"balance":"0x0","transactionCount":"0x0","code":"0x60006000a060006000a060006000f3"}}}`, + }, + // Test values for a non-existent account. + { + body: fmt.Sprintf(`{ block { account(address: "%s") { balance transactionCount code } } }`, "0x1111111111111111111111111111111111111111"), + want: `{"block":{"account":{"balance":"0x0","transactionCount":"0x0","code":"0x"}}}`, + }, + } { + res := handler.Schema.Exec(context.Background(), tt.body, "", map[string]interface{}{}) + if res.Errors != nil { + t.Fatalf("failed to execute query for testcase #%d: %v", i, res.Errors) + } + have, err := json.Marshal(res.Data) + if err != nil { + t.Fatalf("failed to encode graphql response for testcase #%d: %s", i, err) + } + if string(have) != tt.want { + t.Errorf("response unmatch for testcase #%d.\nExpected:\n%s\nGot:\n%s\n", i, tt.want, have) + } + } +} + +func TestWithdrawals(t *testing.T) { + var ( + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + + genesis = &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: common.Big1, + Alloc: types.GenesisAlloc{ + addr: {Balance: big.NewInt(params.Ether)}, + }, + } + signer = types.LatestSigner(genesis.Config) + stack = createNode(t) + ) + defer stack.Close() + + handler, _ := newGQLService(t, stack, true, genesis, 1, func(i int, gen *core.BlockGen) { + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{To: &common.Address{}, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) + gen.AddTx(tx) + gen.AddWithdrawal(&types.Withdrawal{ + Validator: 5, + Address: common.Address{}, + Amount: 10, + }) + }) + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + + for i, tt := range []struct { + body string + want string + }{ + // Genesis block has no withdrawals. + { + body: "{block(number: 0) { withdrawalsRoot withdrawals { index } } }", + want: `{"block":{"withdrawalsRoot":null,"withdrawals":null}}`, + }, + { + body: "{block(number: 1) { withdrawalsRoot withdrawals { validator amount } } }", + want: `{"block":{"withdrawalsRoot":"0x8418fc1a48818928f6692f148e9b10e99a88edc093b095cb8ca97950284b553d","withdrawals":[{"validator":"0x5","amount":"0xa"}]}}`, + }, + } { + res := handler.Schema.Exec(context.Background(), tt.body, "", map[string]interface{}{}) + if res.Errors != nil { + t.Fatalf("failed to execute query for testcase #%d: %v", i, res.Errors) + } + have, err := json.Marshal(res.Data) + if err != nil { + t.Fatalf("failed to encode graphql response for testcase #%d: %s", i, err) + } + if string(have) != tt.want { + t.Errorf("response unmatch for testcase #%d.\nhave:\n%s\nwant:\n%s", i, have, tt.want) + } + } +} + +func createNode(t *testing.T) *node.Node { + stack, err := node.New(&node.Config{ + HTTPHost: "127.0.0.1", + HTTPPort: 0, + WSHost: "127.0.0.1", + WSPort: 0, + HTTPTimeouts: node.DefaultConfig.HTTPTimeouts, + }) + if err != nil { + t.Fatalf("could not create node: %v", err) + } + return stack +} + +func newGQLService(t *testing.T, stack *node.Node, shanghai bool, gspec *core.Genesis, genBlocks int, genfunc func(i int, gen *core.BlockGen)) (*handler, []*types.Block) { + ethConf := ðconfig.Config{ + Genesis: gspec, + NetworkId: 1337, + TrieCleanCache: 5, + TrieDirtyCache: 5, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 5, + StateScheme: rawdb.HashScheme, + } + var engine consensus.Engine = ethash.NewFaker() + if shanghai { + engine = beacon.NewFaker() + chainCfg := gspec.Config + chainCfg.TerminalTotalDifficultyPassed = true + chainCfg.TerminalTotalDifficulty = common.Big0 + // GenerateChain will increment timestamps by 10. + // Shanghai upgrade at block 1. + shanghaiTime := uint64(5) + chainCfg.ShanghaiTime = &shanghaiTime + } + ethBackend, err := eth.New(stack, ethConf) + if err != nil { + t.Fatalf("could not create eth backend: %v", err) + } + // Create some blocks and import them + chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(), + engine, ethBackend.ChainDb(), genBlocks, genfunc) + _, err = ethBackend.BlockChain().InsertChain(chain) + if err != nil { + t.Fatalf("could not create import blocks: %v", err) + } + // Set up handler + filterSystem := filters.NewFilterSystem(ethBackend.APIBackend, filters.Config{}) + handler, err := newHandler(stack, ethBackend.APIBackend, filterSystem, []string{}, []string{}) + if err != nil { + t.Fatalf("could not create graphql service: %v", err) + } + return handler, chain +} diff --git a/graphql/internal/graphiql/build.go b/graphql/internal/graphiql/build.go new file mode 100644 index 0000000..0065c11 --- /dev/null +++ b/graphql/internal/graphiql/build.go @@ -0,0 +1,8 @@ +package graphiql + +import ( + "embed" +) + +//go:embed *.js *.css *.html +var Assets embed.FS diff --git a/graphql/internal/graphiql/graphiql.min.css b/graphql/internal/graphiql/graphiql.min.css new file mode 100644 index 0000000..5214522 --- /dev/null +++ b/graphql/internal/graphiql/graphiql.min.css @@ -0,0 +1,337 @@ +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,d09GMgABAAAAAAMwAA4AAAAABZgAAALdAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiYbIBw2BmAANBEMCoI4ghsLEAABNgIkAxwEIAWDCgcgG3YEyI7DdHsjE9IUV+CFDh74vPL9/MmgO0un0soqjWt7En2kQoCMtXsRxyxkMqP9iO6NfSiUaLJuoRIKnhI0+ImbcWOB5XOAFVmCgxZQQmuBJRhZtsUCXm/492Dyuk2YZJdkdApZeOzyEQgKOwDgRjASBEEBVmAlgACtOHEhpjLyyrACMAB0vaLa6cAw5bc5bvhA2uwO7zXAyKPmkYNnAJgBxLEMDxFLqVBPI6EQ/daTr/QOAgfCngRoZc4UZiL623qCkf/oHVsfRCOuAIbJyF4ajQQKQLmQhNBAA4aygH9b19Xw4iAC8DkKM6WrYw/ABMAOWEAamA7sgBWACgAUSlc3SCmlc95o45idYD92Qt/+5gF19v3FALtB9+7dq/h6/Ljyu/zzYfnngwdlHxO+k39nOcO/e7nPf2vCoo3HVlmNTdnWwW3JZffuVU6cQX14kb3qUGOOJ+mjP9iMeb1Nivq5gXpJUWm+cmVK56e6PjI2uce23hHlG48vyDvym5/5q+wbkjq90rN+z53D6zXqmVUPVshZoVtrZgc4vleS1NNrni6VR8I/vTrpzpPwu1+1Pel4xBIzK16W3KcLNnVGl2RGZHbPXBAvhw4M02Ci/t0BBfw/p79XS9V7CKAMF0++DK9rtI/7MXvGATjz0TEA4K4oef476t9dS555BAoLBYCA6ei/FSzVgvg/cIR45gpTaLWeLiB+oa4xJuTks7r7/xwCmCzlpoJKALCDQmkyEsCsN0mELUADghGsGgAF6c9IXkabDYyqg6WMkZd9z7BT5gaphhhqnOH66aOvkTQhggQLpsk0xBB9DNSLJttgPQTQJBtoIE0JEY2wb+1lhF6GG62XngKUGKLFECMNkW2kZgP10+M31GZUwfojwkU0uAcQkISKFNtqGMlau3vIjjRUjMANjYkDNKeouYh7CRBmuD4CHQgHG6GXET8oT7ZU6QqUStddiABBJPSv6P315AAA) + format('woff2'); + unicode-range: U+1F00-1FFF; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0370-03FF; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,d09GMgABAAAAAANUAA4AAAAABbwAAAMBAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiYbIBw2BmAANBEMCoI0ghgLEAABNgIkAxwEIAWDMgcgG5sECK4GbGM62A+KOMNGmZWUwcdhKI9l4Sh/WwYP/3af9w0W4ERa2bOg405uoSptTooGKkF8HniO5b+Iojvye4dReBbNtVHwcLQTG2gBzQfYOqjJ/XYU/jItwgxa4I3czM4Fj9LAAnlHz+dzgSO71Jqn2QML8H66dROj0qAFLYnRhtm0b89/erW/v8l/LA6we9gCizDBtQzSf4EtkcwDT6RtmgYEQXnDKGQslZyX/CkQSFgBAE4ERggEAgmwACwQgADMsONAJKVkFWEBgAJgwMz1NlLWec3G+jtZu+rXO1i7rx/sZi0AEwB5WVY28FUE1CORQAjvtSPftAwCQQjGAbTUfm4qwrvbNmDEf5pjR4JoxElAiYiMWjQyIAEy4EBGAA4UNKCgIMC7a5Cej2sCAA+SMEEyYA2AMQBWgCmQAObACrAAQAUAJCSDMEDmo7CztfXoRGu7SUeVdbvosOq6N6PHnZ2yf9l3eXPj/q2qXdkjBL+qrix1cYsqzItOvXfRPaMXkUvPeFWoxr7tZB8gfxIhMauBapmSUhO8d3O8wUt0MoI7UAxLzt0/zhCwJnVHrsPYXenm8suPeLYORWqn/3wwK6Qp+frDiYGvxHSXFzoXfpihfmlODl9oFbOqKa8nXbZgd6axNivh4JS8xEZKChij/nuDBPx/MrxQA/WBACCtK44947xa66g/k0YcALjxaesDuBuQP/7x/3bTwmQACVMkAAQYd/7HYBqK1H97hriqWIzlN7cD8Qu1mY6Ql7eR9v8qAcCY/apKqAgArEBCCmOEAExoJiOUENTgBAI3NSBhwSjIbLboV0Blo3PIiN06hxVFfmrr0WtMvzYtWg3SBPDjz58mVY8eLTrpNOm6NfKhidepk6ZAbgbym+oG6PoN0zXxUaBHgx6Demiy6Zq0GdIl3aB6ndo04r7WvSV0/Qa0Nd2+yKcNFCrSvh/6dNKO3xV33aBeEXxNZKTyQUaverfOR49+LZno1XUboBt4oSzpEiXLUSjZDgF8+JHBMIY0KQAA) + format('woff2'); + unicode-range: U+1F00-1FFF; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0370-03FF; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,d09GMgABAAAAAA9MAA4AAAAAIFwAAA72AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGjQbhlocNgZgAIEAEQwKqiylBguCFgABNgIkA4QoBCAFgzIHIBupGwPuMGwckGFhtxH8MyEbMsSab4QwqaKI5gOnPv8mF8P+xTyVHcbb5D/Pr61z3/vv/5mhhlDCwrGwajAac1aMRiyiyobexbESjDUKI3sjjYx5BK2t2ePAUgRLEzGL1RLeoK0rV4zZVi3+ry715RzSN4Z5LeAENJW/pADAeO6pPAXXIk0EK+HU9yQrhHO3WHh6KWVg8D9jA9WohGXbCoM7tWba29vd/w3NdFO4SQp4swVUtYCSXZW4bO9CmyvwPVOoRPmU2BEI06lQAOwA2FeRUxWmuta9rNAVztY3f+o9z3bjghCqcYziKvP++18RCOMIAID6GM6NG1KdJ+KjGCEMYA+wRwACGNTXjDKMA0eg4ZyVHIuGe3JYDBqeQanxaIiONTkeRsSRGwAgAAMwLswgJQhAvlMADuGVJoNJ46glGwMyQV1AhbxPLkTy2TzyO1ks38vPd7gsX8loF2C+ceEXpSYjgEM+TC9P5ca9mxs+jXhj+ZSyjsh75ZP8W0bLY/K5rMDKBXHQWGttteero8666q4nP330Qzz+lxI9H00BzVOvipYCCIG9tjJetNaSaXdptIeM5J5mKNLrKoqgRAUk6gB6Gr38ypFXqP7J9hGOVBi0qXP9g6Kn/QSkuhQMARQuV1B7CKWFj15+5agABDGyDM+gALgu7vqH1JGNJww3hLWhCZq2MIF9NinPzvM0ek+AKKItQM18cf7aEoB9Sd6r2K88oH7T4H6gYN4bVdggvCoM3ugBAKUXVfDmjVdy384NRx6K2LtfnRGnBidnakxRYbiSqmq/qf2u9hfvjVICxMhIPhRJFbS1dkXtt7Xf89ckGwGS207Z0m1Rd6x3ut4pv3WzeZpJtg/c7JRksZRw8gBUQkDXAnQF9oG4ALEAr+8GiByGrodRZLAADQlRAP1kf/Y/2BR+m3T8q7DMdC891TRLIR2yU03L9zI8M9828/1cN78g1c50LRNycoybnGGbtr+ITM/1HeEGorc/ZaDR7Y8MpEM4tZaAs6Tfbn6Jc9ETPs5jbCJgKJzMycK5Oa6p2sgV09MoBcW5kHwLKkYTVIhArjO048UCAklfXmzADhpJS9we8rgvSD24d8ulNFGvAeX3ivapQNRax5MqrMX7W3LalT7I2bjEbLXoOT6BtkBA+K+L2MNy2n4ib/ic2BaecszW4hlEZ4O2bQ4ZD2vb8u8VJX74o9Zf1kd/KmOqPPQtbFqhFMrpwFv4FrnW6fxy+KmtahmNVLVA4+3CXecQEJCeATtA0Q/Gd1QsFAdhdxJBdPlihB81yFPvwAEhuF96qV7zNMyuNYfpVmWiL2ghWOL0AxkH1cQSt6TEOB2n14XjZg8MtC9YAvWiz4vGv32IkIcEaxwy9Yx45eGEMYoh5vWAkLL4CJUwoctxs2T8wx9/KiQyrel7taNS8zjfpcsfMTPfsYIyrxyYWSIc7u4ksbmo4u1AiSg7YkgEreULCR3QSuohSyxMW4J7NqXMko1hfvqi8EPFt7A/mFDvq3/y/YPfK7Wfm0GyUsR36eJ2lCojRctCDXLfJxwPt+9a8L6j2hUtaCHlQdomVmYQ5fQyWU6opRNrXFf/y8JqoeabIV59i3Y1GiLZv3I4/T/E1h5EI02jkaaosevfmdLnpw1bKl8t+k9efX7j7/YAo+vW8UP+H5+aft9xv7+6Vu/vvcPWw2i66apXm2DpUwnh5dhH7XbSub3Hrqb1smdTd6M6apTCphC7941b++HhAduWOKzy0EWJ2NZ70yeNZXn8+LzM1vqH+t0zrs3gm5TbDqb3GPahyjD8Ut3HFten/G/+XepLDQzDL380DL/iXJK2JJsX8B2LPMoNKb8hWR7YWtun3pqxhs8T67umlAo8h3PqHs5Bg9Bru/5oYcOcPTXzcxfzMtpbJQq1De4nni8ihwGjhrrGZLOfKHmIvd9zUkOmzL8xPI2q+KmLxpXDvmoBTdzp5mYLTel/rv7FRBSsCDWM1npZBsKvluuvpfpL0/PYaj4uPaLpS+Nu/OaUkFe0ns+nnffVQ83HPu6n5oy1BlARDykacrVFbgEv5Gs+4YtrGbtcGPzMbpaP8+ql6pPCInaen2/g8cwhYr1uatayaFqoTC3OyPOb9H80vVt5QIx3Oop2cYGGvgFDYf/C7mSnF+fdfPv5H7MOtJg7WgZYp/n3R39v4/KF/NXPVl5C58rHfXFY6LRxsfa6bDYvprO/jP9sP+9ZihIZOjmAZbHVx9zWiqCpYdZJfAEfvbDdOIdMbTg2RWdP38sjqSSk03a7zNQDL9IOtzPpc5KVpWLSDN0Mwwu7nZ1uYs/44f+qPm4f8uU/bGhvZ9cDq0ayhL4NLB0S7EY0+ogao1Crc4vLGLzz7HqHEWd/c0qYXLiOB2N+5IhTPKORNtq1skx/eVouW8XHp7V5+6HW+neeP7/w+HlDtx1RwwxRAVOGUxEPLR5ytUVOIU9jy/fB6cwbOvRz/YXdmJr9UatQ87oNXugcM2pD0f88nU6O7jV4qGPoFJeZu+oMdejrFq6EKvldglfWTx29OtvJz0MXpd85/Uo+36jcdza9L9ciRWy7A+mTxrDV6h3Z6C2G1HFesVS8LplDQbSlf9eB4T5eOQ4/VTqUJ6+La+jYj/Wlvlr/+o7t2/6n3BC32rnff5LMIoMnj+FZbO0x93VqEMsNnhtEPsQ1xz02akMwvEFVo5tRhvQityWb4PL7b3cu2sUE1n3U1/kVn8v+zQu/Z5x1H3uKU5flStvlWd9wlNtcx82r1q2207dtfdPtooDULtWcNGWZmPCXULtkqP3QQOdsdHz/0nkvS128adFRTs2ci2A+9Ug/c9+iAj6Dli+cuhVKaabfT/4H0WXeE7v0qaUTPC5Fd2lzdBDzCp2r6ZOmzZ9Ir+eNcZ06hNUIg2n1Qwfr/QmG4iXR3GjMSbKrxipY7opa+j4w44PZ0t8aNNjPt+OA3pXWgX3Q+m5haa31pfBds02L2JlRykrYigwKWU88fgrlk1dyi4sr/Y/EwdTgzrJXX/ZNK9tW9tBsXf8IUr8BnWb+c2Aq88vzoM+XZZmBJZWGM+i0+tHaWRVnK66iw+fda1MMuS4B+uD4gcLqGJXOpg5DPxZd6FGGTnMfrZlbdrLshuV5+YObOr8RYzvXi+vSwdlUp1eAu77fsIAudZO7asYZNXrDd02VwgZ91hjzP90vHcepQ+UwP9imi65KKaTpVJlGYWuIx+TRrNHt/r7ioU97M0qUl0zgs+wn9eN/umSycfPdS+FbrUqL3pZRQjOpIpvC1hKPy6WZ5JV00Kgfvu16H/Ip8k9eWXt4mJdu8PjovtVjn/RpmLy99jD0SSzdU2v97risYuxWd6Z1q37EMKjW2Ytmv43Hl5f+73/MitPK1/r/eS5QE3Wz5q/K53th2XwTrCEUABqIWpGZRPYeFAFQbctyGnXD1ahZfkU6D16RL3CW1AljKQm9INuQqbFwATVTAJWoVx6B94x6pS60T+ZENerCnBIHVU14RnWjKpLfc8cy3lJTJVs+soLn5KqU3jdZxTMSTavf1QNrBC+8JbPefTSEl0W12qgmtYqqaKnfXN+xzwh6plnpqWCDvKlL/shUlQ2/BrUSja5WyqcpSLoOBuyYnw5ImFP+Jz/mlFFQVcZZ6hZVwT0psYQd5KOkZs9Zxn5qo+S2H1nBTvJSSvObrGIH2btrs6uG/Vvsp66D6Fil7ThIdfB5qFo5t0gpaev5RKimE0l7w2BqpsCPphF0prSZ2h0Im2EjjEaagxgyyj2Q5iA9Msr9kOYgjoxyT6Q5iCGj3ANpDtIH9OpYpZ9qWL2tZSq1he5RS2MBydCGYoY2uJkTDagjc0oWVJXJSO2iKjiUkuqV2wAnaZr8hHX0IoCdocnUdRWKtdgZJpgeg1AH6oU96Uj5HHusnCxRDDb9eoH+2DM7Vb6F7qk7+SFP28QX2EO81o49YQzW09UwRlzgEZrMQXqH8h92kTsavh3jDPnqXRvVJwiH69m2Dv3PeiVorDIOkyGmyA/xKCBXA8oWrRZM8jF/Lx6hPcAtWhu4AUyKlwiUD0VLrSks8rHSWnxAJSD8NbPcZeujuKj4V9vmKltEFUy2hfw/ZUhb+YBG29V8r+qhbSsViWquDG5xv1WzvGKqdrOl8pe6Hv6e81yt6OPQfLd8olIb8DK9d+i6Nb2r6aB77lf1TltYi499ska2Jcp+UYXONqvClKGOAEQ7TuRTl5oP27gN4oNX3Nb2looANVdm7qoTWXD31x60VI6p6/F/kYq+Tq1bLyphBtj1k5sAVqhOltK2gPmIKnlf3hHTi78Qc1BRV5xFR1u50kgZRhP5iGgHiHxsV/O9akttW6mIU3M93iKy0HiBdjP3d3U98O+Rij5OzbdAJSz8V6M21NrCLB8KocLjvTgf+RDxgdisRG1BbEV2ZV2MaCmqYEGp0lrpdF+hA0abrM1aLz86Ikg8R2dcahLyJeIOsRURlRGb9RqUuai0VQp/USV32ewVF6XTfYsPmPlATV8r8UG+ti3CUwUIAKvncistaMtEpy4fdJ46AMDJ184tAOB3Gvb6a88fv+szdSlgUJgAAARosTZ7QO8rstmC94DYgUk3JXw+QvFF0xdAtJOrlTg0Yp3RXoQjRngiUDmFSl4is1gJzitdYVJi0Flph85MIChp6KiMhYVfk7uYFWeVa+jM3GASUQhU8mEWMxCo/AELv06Mx8DGT+Im8OMP4HsF/xVzeDkp/CP+K4Er+Ev8yWkAoloRSTtJqc3dFSZvcoMb78318f5+2W8557bwsVeI0/XzMRKkZEKu28vtW75zw9plg2FTAMa1WBYEbK0fL6ZYvkeAEuWqG0UgAOAIDOugIoBOOI6yHsAEoFTiZYLK2MtUOR8z+1RUoaFNQMXXb9XRCJ/5SZAoS7IoESKl8tZGK62Ltt76SdB4Gius0wHihWgR6smA2HHDqkUKaYVJKa1k6dkK1YKxEgQ7kJrtzZ+Nj5ImzoBkBYkl1zZEvKp3FqN6WCmiIOL1ghbRtnx1Vr+qb9O1a96ba49PlaiTlgXMCLUQNU4UZIVp4axkEdArs8PEDxlKQfZAA/7rSR5kuD6aK/pOrXCQ70FGCzUBAA==) + format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +@font-face { + font-family: Roboto; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,d09GMgABAAAAAALsAA4AAAAABWAAAAKbAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiYbIBw2BmAANBEMCoIYgXsLEAABNgIkAxwEIAWCdAcgG0AEAB6HcYyyEjO2Dy0eKLv4XvfsrGs+wIhEBOHOERRRTI2158fc/aln0WYmSJq8uTRSIgUyIVMqpfa/7uYHCqzWDuHREj0f5UuuL+ZAokTaYgiIs5sF5aUutjO7QhBlgMaYvCAIIqqoCggoq0+HjRlX70MGclDLyR3Z8fb0q/ectzCv30obmLesvO5hBhRhcp7kToaLpaRXpL0htKmb5C3rIgzUIwA1fnqrhHSbqXhA3v+sK1wRtcWuhdyg9E5tGXERkaAhroCGeNqCnJxAm6m1Sb58SICvFhXFWnVAAWQoYRjYADJUQQqIYm0uSZKkfpYv1sv21dm9b7kWbV6i3BQ2Z/sOf/hl+ezXH88LRz75pnLuq4/MO/Zx+eyHc3x9VDn3yfx9n1ILyusq3ps75y90fVZ657PJ2iXgF+odHbvzv7Lrm+uTsPR0WJqYcelN7180rHDDnbeWbrx0QHht49uXjCzffOsd5RsvGvHe4yF5o+Ej97/ZMP62+Z+3Wz/08CtZ/FezhpdvG/nb6PMhC9vNvHFx3Du9X47etewROuONg4L0v2eI+L9X7dt0evq+gNihfvWttiuWK4f8VmxWBM/+WK8b8F6Y9evfLf57r9SjuA2URBAobPm/Smni3y3+n1TqgQEACsl5awAI/5AetjNp65A+/38vDAUXaayPL4CMKHYkEFC0DlfIlbAMegyqlmGU2eSTO58TTHX2xLyWvlczc/wY7eDo5WxlYenKyMvNg9Go5MAatqis2Jty2oytLaPupFxOlsgFObsjM05dBxMHVwcMbeFma4xFh8jZxUr2e62Th09I7Bd96I2RI3gzYzqKcsHjqZzGjsamlojTwdmCy9bKFNm7IBcudRU5BU09BQ5eTm5coMaMAw==) + format('woff2'); + unicode-range: U+1F00-1FFF; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,d09GMgABAAAAABMAAA4AAAAAIkQAABKpAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGmQbjEocNgZgAIFkEQwKqTygfguBSAABNgIkA4MMBCAFgnQHIBtLHFWHQtg4AAgt+xD8f52gxWG1uR5EatWEsKGGtrrROAfbhgbsqkcTXk+8cSb2t2LbKz7fybPEC/ukeYa3NyHy/D9ptl4bLoAhSAAYADqGVSx0WQHh8fA07v9/zew9c855UgO/QqKTM9GVxCaWLiSi/R+i08U+4Of29xZE90hzRJVRRI2MqR/4UtI5wcAcNqPDApToUSUYjSpcT+QXXn5a+zaz/t9buUVDpmsnSVyZE7W9V3YRW6gkIqFwHZOEz8yZNyAkBtwZfVEjWAD/BrYL002IehYA///at/ruuWv2EJXQqGQIjZBoM3fW3rxv6/Pmr9n8VURk8MZm0uZNVBEb8CpidRMVQqs0Ks39/d7Xgqlu7zjk2DtDHDX28bUfHg0KCwA3QGEkSBBCijSEPHkIRYoQODgINWoQxx2HOOkUBJ4+hKFzEBe4QyBQwDZgGwRowBZSlGAuvdzKCWRuiw0LAJm7wrz8QeZ+t4ggkIHcd0dYELBBsOACaEAHOg5XQDmgtY9ggGOdJj4KarR21W7Qz/TrvSATe1mvCVRcGIQsiPhIjudoTloJ9TammqzPCWpOKuQ6axSCCp8HA/KFIYINo9VM94B67NppH7YAxm/eIPgij8SuR9/C0+8g3w7F39v8Khj8omzm0JiaZ7l444qvMsAnstouq7pYcvKt26TYqlOZOp/mJ234mjCY7oC4/Q72ir1cq9LY7kUvhugtCr+ZRfcFBtgx2lKDfxZa1hkGB1THTUvPyMzKyc0rKCpWonSZsuUrVqpWq56+kamFtY2tnb2jh5cfistNTLY41vTWc0Tlt1JiorKd6v7UNokwHGZi9R6uH6IMq1ydMgn1rlpfRdJRmagylrRQ9X8wSrX7wf57xx+gdCNMI/I+t4wYHQHKxAGV7JALzIgsitkVtyrpMGVL2oas/Zw1BTOKZpQsK5tVMapqTM200xmXh7ezHie8Lvqe9TvhfxYvsB+ZkbItEy9nU8F+0X5Jt7I9FWtO92/3vM743vO/hxLpkbIrk1DOthIxZQe3B689vg/+D1CBNZl4BWuKtouuAZWi0czWdTk4ZkdOQ2FdrEOKceLJHzd+0wWMrsyKIltHLuRXgyFRKyTrHWXsjlU/FIkacrKon6Kntufn0ETrkHjtUzZx0OTqC6s5ahb0BMBjGGDX48uHpcSXF6uKK0JchdfXpeg0wFjTPqXa6SsWQFiDFb6Luektmdq8Z4N7KWCGjUUnqNY6taI0wwYMwVS4D8YXV8Vobo5NszGGXZSBIBHg1IxjKHIstSPR0KKPlhFHzFwyLuwcF3GBi7rSqWIQgkywQkGgLEkLqWlaJt0CsSUNvS5YEjCWsAQUMwYImNwr842jowi8Y0JM0ECRu8FuAChFDxQ923Z0unuLcwCxjCQA8YcZJC5aBgzsP0q0DIqgBEpsLDHu+aMk8qmWAwvGG0MDtMOyI/ED7w5w6K5Hip6vuNrWFPTiRkxM+Atw56KsgxjkXUCePcgnLgYd7oDlvukRcYy33g9gg0YTz0VG5AUpyNEYAzEa72Oi/hVP1PefFflRGw1BicF4d5pl/fn6M0AiIr/QgnXf9XgDCB4AABE8gAPE94GPX0tAW0dXUMjE1EzY3ELE0krUWsxG3NZOwl5SysHRydnF9cxZ5fMXVM6pqqlrHDt+4uL/Pd3HoagcekDvhbgCTP6+eLs90q6MoH0XWoC+krZxS+EoCYJFlnB3fDNhsjLv3F6rHRznZNCbKlonoDXRTkarIDSk1xxI0hACMNKSaDkhRJiO8/HtVemw6+9IFsLMf/H6jjqkCdNzYE55UXgcEqNlGh71xtqjUT4WUtgMhAUsBp1IQS1Z/FgqgwWjVjmi+W3f/f3MKgU+hVbE2IjswKEiAju0NnCsyMZA2kupofZawvnCLDaexe5ahpUONJt+mt5el9lAKtf24NHBRs6rzUOs99eZy/8b8GgtZY9MltWmGGuqj+p9Fg9n7M5yyy8gvzv8NNEfh0dgdBjGRnFpDJctsFewLwYJITYh7PBN0BrrYwbxY7/h0QnPSolGWtH63Ue/y4Z4EKp+1e/Kt4/e9xUUWRKeRdCiB3lzJEcBdb2ZjENDUI400MCh/mHC5jzQvUVwyqpzwwIoJjIWK31xHDHkUc/VTp2lebQ898VFDAKRlbHESclgpk5H+xb3iviP8hg4P5KLcqj6lG1B1KtVaZGdLcf5Umbu77GiUrmjP5L+yG204DQDTJEXhbzQG07pacEr9XiMQfxkxrYhqKY4rzY11lJf+JFPKTImoiOXyHnnZrg5BR0L3d4MduY6f4S5Ar246Lkw5lRVaT1wuCWp83bSKgdeEHPftgFmimisMyfUZvGLuxp3hlw0i3MTEx03iOW+Ic3EXcoVrwRk8k2qJWNISIsyMjKGMSK7fUxrNZ5lcpxFlebvufLghpowjgyFnLLWmsyDxh/UChbdWgt5G61X1rjeMh5x2yMGsrD48ScfBTnlD6yvOH8rk5YsyosXLxnL7PnxlMo7l4Hy1a9w0eUVuQFmw0navrwA8XHJL1Ot6PaQyD4MlRkRrLHSt/9yWN8BF/hpYvp6lpVr8CjHgFtpvfx47sCIA9uQ6DYk1JjXevTO1RRv0eRL1EHqelsRLT/g5eRbJefedI6L5bbPYyLm1kVzqnMoUbeOqubEM+Rsiuy3UzTtY6a7GqJ2x+yuJZ6rOkak0a2y+3nqY5po5NDaJxkb+kp70Fj05xbbMG8L4hcnpjUqbgqjiZ5bo6PDUH2us5/S/GLntZp13empNkvqa4E9+m6fcRm6h9UEEjanZT+VYOA0rFyaxlzEiIWozs524XDLVyWK9Pl1fl9ah4FaFUOaa7luwJI/mAPtbNDGicZR/xiXDklopOMBv2gyrXdXex9Qr0QP+Z7EOLlnlX/v2716wJK3/vx9/2Zw7lmfQqRY6uv47v/z61fvMWl7dsllN+NoRXRLJa4XXQuISQ/IFgIdFCkaM1tZCVhyftWHsWiwi4cO0hypHbDk9rC5sA6ILo0FAnUNr7eP/Db5zbpWokwtbhUEuMnC3XVr88cFez/J7iFMLc8XHivhuHLyN8amDm7M3b3jrBXu5JGPTxvY5dVPZOvQ3iU/pL+XdwoZ8Xufq89w/+EThnvZeuOtCPoNV9PLt1yoL/6/3os0UoZYUL/B9zSevPLvsRwOjNFRv7lUnC2rzUlLrC3PQnmCeSTHGGA52vLb86HKG+QMEy/globeTcxSvU76nFz+ODv8bhE8x4hTU6IeuaLtoumWzMCpCv1KqRw1aiJ71bdMOCdTffXPXFr2LJvaX+aqmJ8L6XkzpTvxu5Hu+Z3JjMzbM31P781kpN2dhP2fbF26LXxG+Ey+G/gWoHE+jwsIuHqOGOD/SAEXGHBtecGA+xg+Fm55l0f0aReLUfB36cIuJN/PtzMbbwTsFOR9Us0Oe6Kq8jgsC1qH/UcoeMrg+YyB+S6mNaUNYJnQfRxuFwIiPKnNnrQpulJ9pjhRb4jlaIWcZvvt/QdyXuT7UsfJznqArbDiL5ADLVQ+tgR7OmE8S5u2vuGwd0N7NwePjLYynPv9fCvaVC5fl8a/9jwqLk1+KH6c/AaiK+or67Hhup8rP2M1WAqqCsCODTpIjOZ0X54mWzgYaVZlrfyXvWC+YJIzWjVDUYRjUt9qUJCW/aOiKuvH39Ra9JPOJz/RJ5X3C67uhJvddHmJauw8Pvu6o68BTf8M3TaAz3nxon2g+J9F6yCouTOW8zyauM/cwVZ9/Wg7r4qF0EFY5WGTR23ztbPDrbqJAr66DlggpQmUCqI2ktc6vji0/VgJ3a+QzRG8tV056+cVrX4rmJIh+aeKVPO7PFMQ9SyxJlrdz2umkgo6VLwwkm7DSeVJPbDIl64j1L1rXxY4YqVb1OoeItSwZWgYP8ntTHlk39jq1HQvuWAJpMe7OzanHp93K3bFxSkldiaOfN8deRF9aYgC2IaA2KZRgvcN75Rk/4DCTCBoP8vWuZRcWp0QlV4XgCoqcY65FgX0nOz/y7TwPkcmKQu8XT9bgHnsS+pg1ZP0pBNIdRH+qounqU4ApWSUCdMlWxr5eepG7hyNzGfm20202RIYdxlCunYFuWYwLbV6oDf13tRVvtTaYRBWsc5ziwotC7RvLP/7unf4GzmfMqzvKukWa16wenuQ8v1pVqNJlqd/SPI5i5qj7oKFDSxoHSfHXLyfVuNFTTpncMWe76upHa+Jqw1i5P/A4LibI1XdCWekYe3qrXSuJCExV/d6oZDBtRLgvIFnSIku72991A1DFxrtU/2J8RcSXMSt2Sl40JeI199ymJ/esURrjGhvWc/PbRqi1ecUpU8u39xPTU7fX5YalZZdyf2BydhDloC3Gy+vG6yn6g9FxhzmP2TEgM151z3aVuySwHNn9V5JB2yxpoK1tZS2s5Dtih37MuMoXx328qaPNW4RMsvhpDTd/5JumdXeztPWSSVFL5De8tqQ7AoWPaLUoY2qn57PHVMtgmM2o46sJW5F/Z5+lK9eSXBu7WAhLlI+sfhKNfKamhssA6acpIosveN6+n5+EUjJJTWS6kvNQBpj8+aQn+EP6O/P87Z1hRLpKNSqkK3h/+gMTznkPUgp7OwayZlPisz+WA+SYzYtq2PPnwQlJQbfKJt6JobRdU+SdhOyvWwn4n7HXNvNaYXRRNFYwZljS+MbfFAoifo5kQqmz0hCffns7BmxmzMpGVP0yv9MSeTBp5R00DvBIf+qeuJmetWnoYc1I+lpVUOgnV8XXpzkp0gvn2CpQbgWkQe5+eeLUoGrAJ+iNpBQ/+MlZjVSrCtkn5cWdKY6++aRiWLwZ/vXZfVf9+Jprrt43qhJpz969Jx6m3/YL+1qaOJCRsK3wkNxOQzXSONrr3rurtk6zL26j4kGDqDWjX96n7eT+hSzFivQGbnFixZSoefqaxz4y485zrlK+Yx03F4m8TWAkBE+TYBmdyh0iRAQ8vAOrkkdakPq/Qmhi8M0u2kCXcmHPJyjqs37TjtyEbUx0c2jqpyiyZtgmhf+0oHuDvKeutM/9PXrR9NGxC47vexqREJuyZ1PIkz8kzWvKEXVDd1PL1NNOfztk0jNacK+mJ78gm6QMKRZ+KngTnB1NcNLFvXJmkjayKXi27Rkk2VsDGX7JAs1Tc8QHOUvgNszUqrugx72JvUHBw67Drv795tVuNp0GyJKL7IBQo+uN+81tuhD3xu6vHTGL+QOQqJtokVIIXcILpcXgUnK/LFrW4HDX3TT5beTB1r/GaIETDHKldelz0df1E4ihfLpdfNpsN1NNHvpb/gsMZB/CQcw8YB+CgyN8yUADVvYm2FSNC2Ph4qm65UMkci0r3epgES22xM3L/qlEKluhrjZ+UuhtjtNV00kwiINsiMt0iE9MiAjMiEzsiAbY81y6HBVyBmoUWy9dbYTKD2Yr0XWr2h5rlg/oxWlCQI4NnPOWI3yuJbLf9Q58iIHcjPOrLZuXI9sE8MD1GCYo6H/uJorUZ++UzRZd6xl4Ii1s+Ae/gS82P1bbJgTAuPg1C15kJdLdvKYYzkvKm3QHph6tVrbmOBiOAwb8Mfc5Y/6oxlh03uQ1fufCXA5uPge1uPHcvgr0B7wDdpxXofNGVXbg358YQOfgBq8KlgZ3ofT7Nu4Gq/uNy5o62c8f/GsrYyeeB61HdvztNxNt9jXF+2qo245pWWT83VGKGurvyDxznOvPJY2vTevxG69OIj3OKdWuFvQaNClgedPvN5rSot7RCb/lIAA/fgek3NTiS5Wrf/p+JcA+OKvoAzAL83hv5/zn/GV6jIcWEEBNLC4f5MJYHUVFPfXgj5XXY13W2TwtHBbA+NMQilHrc8M9eP5KB3n1cDkz9/6LCNe1GDCVC+1utfTOYo1v+SSOc7HAvE4wytTlXUe+RkelmT2KhmFdt5wZg2jjugI5TN0qGeumPHCU7q7xqOJ9UhzbjgIzSSe2aImUZQz1ZW045HSAjNVbmaJ68W6Moh0bPPKbvJBWGvUcrVK7POi7FHLdZS5PIvFJUlsGtTUNGMx5tfIKPnxvE52XGmPglod6sU1vGujF1f5HGi8dZoFMc1DQ3NrXKMRyDd5I7/kieZBc6L5GLOyvpFHEmqF6iTJ732AALfJxsMJFgKwA3SoE2ggwJI3NCRXwI1AG45gcmk4CgvCxuiwMYaGY8mIGU4Ti1CVVxZOFMPgkNgwPx/fCDF1VbVssJhpsMY8wGt08yAPZaFfgYCgQ7MMV5VXeK7CopLyVK6oYHeGCIKUT2S7cAOlC67C/UgG9QblFo2Tmk7cJ202gUvUXU9OCF4lw2ihDIiQXHhAwktVwWGNoCL8amGvIJ8inPdkZW5obOMoJM5HlSraakb/CJ4AAA==) + format('woff2'); + unicode-range: U+0370-03FF; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,d09GMgABAAAAAAMAAA4AAAAABWwAAAKuAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiYbIBw2BmAANBEMCoIYgXkLEAABNgIkAxwEIAWDAAcgG0oEAB6D426JQgSiDJGrY+EepR5ejwf4/fWd+/C1EBKYZDS7sRFxHTf9uCJn/m9Of4qsOwRQBbqEex0QSbKziM9Pj42dA85/tYTLU84Cj+f+PIAlq3AtV5GCrQWUqr11TNFedSEUjKs7rSju46fX7RWCSHFAeYQcQRBEKIqiAgIKlGZBdO5a3w4akEBWj6orkgSzThrq5iF0WjfiKGe7e/0dAHkwOR8nW+GblHR72hyEGmzEl02NcDPu9oBKt35NVVBcoyEuIJNhau72SE3EHkhapkdqCiZGhBhliQWUJVETSCQCNfr8o/boWoBjI3miLHqQC4ojH22AaUBxFAUpIBJlJeIVGIvLFI6PlFi4hGYVs0brZ4ZZlT0rbz1SLT+50xlW3X269vh2x+CpO/n7bw02ebvIys0wMkpteMHUIq4PGfxCRBdKjxXGaDRIc42rK+a/qgeebsfBvjGMiQ14cnJjW8fSe6fHlr2NIrgbeH2jS+k9X+md9WJP/5IvZ8LRg1cQ3gz+dJMePnr2/6ZSiy3c9rHc87Zj4tqOx0WLe1U0VR2OOEt9kq4gV/r/NBEyVbPvpL70poCoTunu3LVVZ4nW3xWV8gAKP5VqBMD10Pruq+7/52x5c4B8EQjkzs5oyJ/1JzxT0mgEACA3XjUZACFDut7UuAEqPZepikCuTcprJBVAcSJREzIBeaYSC4kSGAs2BJU5IFLcQjt+sxNAqr55kwOx947iBrvVCRYwpBuDQusVLFWyFCmCVcEwCg8JVsPPK1GwEjxesNZJv6dyHtID6dYP8UnUCvPAemHBGiA+jD6CVgilD8+tWyfSPRiYXwVJDNNkydPUzvrRmeBZvFdArqSTDSCJ3ALcvDp0JBHWjTK8pb0Qvx7N35CkXo0yFRq1qZAgVaJkYiA7H3AA) + format('woff2'); + unicode-range: U+1F00-1FFF; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0370-03FF; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, + U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,d09GMgABAAAAAB38AA4AAAAAQFAAAB2lAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGkAbjgwcgTAGYACDFBEMCtpgyyoLg3oAATYCJAOHcAQgBYMAByAbrzVFB2LYOABo7N+XKCoG0eD/OoEbQ/R9SCk6Co0tw5CRuS8arZIo5VZbrrY7musceT/cbsXfaJajqVAAOHS7rE8Nn8E0r4xcj9HQSGLyENo9/J/JJtkHuhJYwShF1IA6foB35wd+br2/gj4YtEodZQCDdvSQBQNGiaBUW0hECBYl9qgQBtJtn2AVZZEzThmyRLewajg+hAIAdLoB5bmyit47tW/GLfGMZG+h//8rgFZ49FiVpWy2tGZniPyORbvwKuEd0KOOc6348XObtI1W8dDIX5AUyVXE7t+boXK2LbWT3F8dhkf+XpfZ6vt/TbSGQreO4Vg3o8h3IegPpt+bpGiAi2r11tJK+v4m2tzISLthXVAO6JBCXDGsfcBcB6Ho0lRpytRpey7aMh2wOd/POiNw2t4rRgif8IlggjHafX/fcy1BZNpqHogH+uw11Nr+nq4NgppcfiAEFEEA1oaCpc8AgsgMgoQC4acE4ootCAQKmAeYBwIEMBdFB2C233H3/SkfGXvGSZSPDTv6RMoneZ91CmXIiUefcQohCEGiAAEUoMBTBXeihZZ/wgB96MMypQZqmKdZPXzQjEIQPkzdzMx5F7pHSX7VYxqc2zyfPbE+8nv+gzX0A9fMMYTOgwm9iCQbTxy5blecK0pwLZNcmpRFOid1I3yi2E2ImXRhM5dfHFde8kMgF+c243zuLR90nqpa9gtDHPabzAjD54QfJ2UuaDdD1rhQmwT3snJ0sSlgAULZ5lgR50/VSVufLiyNLqnKlQiMN+nZzUzOr4S+lsfmY/BYlEMQN4k8Raaf1L6M0QqQD7GuOOe7yOjzgTUNOBRBQpxwyiqsZ8n2pUYbiI1+/LN4xKFcDcKdGVmhjHU+xJRLbX3Mte3Hed3P+6WmpeefO3+xoKjkyrUbt8oqqqprauvqGxpvNzWzWu60d44MRpPZYrXZESMIozg5HG+P1+f7L0krVq1Zt2ET23c/IMx0QABYXLHzFjiO/g/hy4oADVd3mIlKhDkJcxnfQkynKhgIdDpYoFt458GozIkWFufGnS5IQAdbGJpbGyqCgjN1gTv5mDaoWdzhu3k7LhkdBRkVGBHq1uEcWVDeAAUNBXML3Pl8+JHOC85+Ttg8oamjf3QAxleWquPcAxwu/ZnIa2F1rIW1ovSgTjr1yFZISQZQCB7iSZe0x167r8Bsz20OXIHBvow9LG2SImEhOoUyVXyCMs9RhhAc2yYKBUUcxv9++2MLAqVPPwTmvrFuKVKh6+3xHRa0O5s2iOXphOFzAQVAjXH3s2XmaMEB2mmvvXZiFiC/MA7+gmPGqwXkIPcB6qaNRY4c9L9CQ+si0BAtYuKyT8aOzGDhYv5YMJRCJQihH/SwD88IjKRIjgtREGXBivXYQZVFv7guFzJbyWQCW+a3nJxcJdVTA7VQD/WzyM4OAVkg8KEcqqEVBmEdTuEVQXEiM5r9f4rkqclsKZMCmzLf/RVU3aeb+qLyhEAGiTNA/0B66bGt3g39bbnmK7/i2wowzb/9x4/VjjVdfS+/PnDea8P3z53pp7pT+ansZG0hwPaMsC3xUTywhz/VvTf0Pob8v0433HQLU5lyFSoZMrprr4sxE0OGjRk3YVKAwOfEN/+d9z74aMCgEaN+cYJA4YbKHfMD/B8Q/wbuB3MuAua9EYzPg3o7uHto12931YRQbR6l6zDc/ToounKPdAly+el2BMWezuzCY3QXQmvw5u7CKFAJAd9lCe183x74zk/iw4zvRrHiVoHTX8veWNrQa2KAVmorCRbigTVraLwTs8ZeOyYCsO6d6S04BBPEVCIAbVRU6hTb3GSSF9vaEylmcQmAUpbUVgG83+2vA1QZU37EUbZZShnT3x5eciZ3dfr+SzVh13mjxaSs5ehkeLpWnuBpIcVICTfqQW9Id6fp9TeLbfw/h0dFPdtNZMCbcko4Fh0uv0JL8A9Nhr/iY8skRVTCgiyCDlolCZXi7hxY8Nnr2lxb0W+pZy506FhhKZTKRHFSpqxltXDmjRFGtlmDjyYSinWH+q5Ru27iszSiG4o3a5qsP4a05nC1pslZwtKDz/p8+bUybYQCGuoUVGKUOcinJnMM6kEHlFsluef/bG+3Nw5mBtQmrJL5b9fyV3pIayJqSLnCZcn8naZPHHA2j3p2ByIMato33Ag/nuo6oXSidxdhCaXAZWgWcFHoQC9+ozpv6rCY8X751GLOwVSRl3AR8BaGYF1m2+gK1dfE2L4Eb9aI8s02Ti0y5Yb05kduAiWFi3Fu4xDeWsIIitnf1VVHE3udxp5vIo6HmS6y7np8qMshc/+5klDq5+JFRsKacj5oEQx4OjbkCkcVJfz2rCwf/04Pm4WyyN6xqmdrNfeDjFHT2kZmnVLtd5JL5awo3/S+9lG94VOvxcqbKoFn5nerXGKx0fz0bbT6lnFwveYIMZ6tXcRAid9yyEJHT25KyLEIDsaUE79YPeAhySbXtLFGE15XWg43df1LjLHvBDg30ZiLxccCF0Hihevc3W96kQJL0Xu0+7r7HAuoWCcLYzVS8C9cKT9ePtEb0IxRhlzvPoQq4TCzSu2l9BitPW9VXZG6Zqo6lBwDzkIx62UIoa7WhzcxAe8jdRmgUmPUlmBuw3T+UnPcUvPy9Cd41LTq6MfiFNMQOjRGxEsjISMD1ygoYNgFYlp54ZwclTHXJRZgqDikSBiRXAd9dKzEgUlKWEgNupR/ZHRLG6QgV2IjQZkg4mYCYQQUcZ5qvvkOndY/f3rGuNjfOD6w7835+RGNGtNGq0i6mDJDBZ+bYA3iCGuZjgAegPI5gezJzKSxGuYDrWS5PwvlAPaGixmYGG9CeHV2JxlZQKmmTudk2EXZkkt4gP4r2WmEWHawYbfzm5Aslc46A1lDeMjiGPboAFk8PTFyIB7puqAMoTuzhfHgZZAsDYA6PxQr0BRq+W/5rP8uk4160NsehfdozCOq/qCgr9z5JnNto6WN3ZjYObD1nIht4AzhW6cyGijUMUda1EsvSrOE/D3wTUK2H+0WzwSsqjQokISBICOiA2XF9QmByLevVc3cumBct9zNeISa8ToylJDoYCqbGfESgtsqEl7lEQOZ2r9GG9leVIx5Zaf5iB2do2lm5lEvSJYM0iVQ3DKpjPIm5UST2qrYcJrQwLe4ZbhUDPTyBQOtrMbhqwLKC90rta9AhzrNkmleWBKVJ5bRZzh/RU+5RYGOzgB1E+thYgYHZs2SORBl9lgBwp5tQmlHoEX//nLIoljzgqYL6CRno0Af9HI+Zew8DDpeBjBZQ7PW2tD+lm2PpqKyc40MFOKeB7IhU1luS/sSTRupOrGF0Eqt3mxNV2xSFBJQVe5MKOJgjQ0iQlm5omKFy6AMuVFzb9a4cI3vTBpCozXeQhh1nITLWecm76kuvtAmwtV4brGVGJ/4x531T7vu2Ml9uWS+Mx6f0j0lbz6Rxyds0I3Sv2i4VccA+/wY2t8NsKNwmmXUGl/0fBkacc9B3NFgpOmoE+nApeDPmleIZHH7ylT/dwxsW16KfdqP+f0sd+UFDdRUzoNLB4Xq7mwoYSVWOcLXC86er2KtI59Sv9X+qiguzhS5BkWAfb5peF9DheE92sPKg4S6cV6/Bemqydn/kU/2K/d/j4FJ2Fnnod6ZLsA+33KvrcAZjFuDrYK3Afv8jXvMFitgQL9tgERwa6dUVakO6n6YlWHYLvaetd0f/t+L46pnfUd9C/02gWkZsT+y58CQKtinACc7L9vMvtv2yPPgwC0OYJ/ngHomi7P9GPPjm4Vfi/c5EWERJwNisqJBN6KyaUJqLRryGuu2tXZn/Du6/wBcnC6eKfizJ9gzzpI+5Cat40bR1/N7yVTpBZ926VlvyZT3FsYG+1DYVi3i4TF1VFXbBAS22H9sfVpIwjfeaRFtLDGFRw5zJZb4Rj98fbEZzHIwm68itZVdgPzWab0HW13btvOzniCtef+/bsAR/vC0IH8sUYfsIfCP8RYm5UJKaGRGcjrCBwaPo72yAj2DA80mEqZZMvOLpSunsx8kccLOp2Qm5AR72hWGOPrdT/GsDu0Qf7p2kzui4H7udkJF9pWMjBCgYxYmFrYWRu6lA32Odf+TquCv/yrxrtzjPCgovHJRUWcC7MqCBDHULTEsa1PYSUW4TYUthmVtCSqShf3Is3Bq27ZFUia9VPKvpExhqRSkTvPOGFVqiJp9uyfLhIMpg8WDxSBX9HhGQF0M0NPcluExtRX3u3NvQ9daMcXJ3c/LMdjBjO0aeXXmSOLAhwFU46cCVWdhVBM1yfLPvfTsbHdnspsDGNw+Fh2MtllE+0U2TftHzvMooaV+cakuDG++x3Ysot2iot2ikuvhtgorqRFsFf8sq482BkfvYwPOa77TJ9I7Br5obm5UJXVFFh/KeEBKLY5K7gEXkWUZhU2Z8oS/H87lvVmXQvmM8mZevxZdE5SVlmDm9TyE1+KWX1yeUMJDPFfsmQSwV+R8OzDWHZzCe+KV1Bz3jx+jP/oQGWGXTmdUxualJdOCIpoH1tU2flRk9EQVkhNfH4orjMnoB/HRsajcjqOYs6PsnlAvN48CSiqWDYcNyWwiG5E0INMyKDQDfQo1g0wFiUri1erKplsWj4ZcCLGo9ArRf7a+enj8lPdj71F0j312ipdG+qKkIPmP3/5AXJSICz2TMfGCURVZ9fRO0zgyNMkeCnT1DHIMchGlwCJ7CjMwUGAUJcQmgtgCEZcQfXHUAZt2l90f6OLjX0jJQLE3BVvlW4l/53OKXglJ8X7iZsZtLeSWLOIJfze5a3L7fuYMdlfmD8ZG5/XBfm23X9o1B5MX2MRP2Jgj+dd19sBLJfMQi1/aDirtR2ryv/Z2jKwOXmGTA92c7fxoJgbuxntMyp1tY48UbLSNZT70DK/x/oY5HO3m6+VLBek5c67BtkE3E5zpvro+B3EbSV3/1rZWLiAMhYQkjrPa7o/2s3seNLQYJ/GwN10EC01Gw5cVfARxanlpfmkKn0Fcafr45mMn/Dz26g1aeuGtj9CK7kbff25uJGlbBTeJMV0cJA+bjZy6pfh01xjjKmC/dtYiWURZWPhZWESRLKYIP759QKeKv/lmM4jogZio+igYo6qKpQuCGyKv4XJIZPV9amQFBkb2LESGQpqg489ORwUdXdb78Syhy4rju0WmL9trBsZKZ4ODQvfvy7bKdKujxXUXV0ZGAi3mii1EmlrHz/s5n68p2Lw+BEaGQ/SH5GRZX6KzUzYb9DjAVb3/jEyhoo1ucB0nvLdtvUS385hm1nOOWazJ5us3Vxo+D1KOeQS4HAtzIW3gCzhd4+9OZaRlTSKzK6ivuZ3cZy/fyMoNOThMrbLUf2Sql9JFzCbOPB4LRKI9yOZutlqty75Juf8kjcmcORFb+/mFHJEnn7/k/3C01Kz9Te6ueygFg7gP7hdv6l439d7ntXjw2wTu6qKDbiouTO34nEGgK041T/Ub4+rCL2tzq37rPPt8sz7ah36x9gtNyeXJ/EP52hz+hPIEFKfk1btl4zCPvJ48SGMT2bDacLpxk7jJOsxoPnCTv+uALkiLBH4mF9IpeItnCrJTlQtPWbINUhWxhToFWZbZFzPVC7bhLRvsilmA/XVn/3gdmSUwEU+M79JU+S4mxvnBzveRqCiIjRH5i8Pqxlhtc/B4sa1nuNryosB4vGEC60WM2+ngS1YBcmwi5F3vGB5hmbqISnZd1aroKYVOEUWSJy33Eebd27V7NSXaWoRxwWbKS2JIBO34aJmRdFPtk5L+F8J9j2W7uwdA1SJr+i6rbbCSaic44GPBg49pmqlqq/LpGB5pMT4qKtnrangDGgOnwR4FknFYi2GDW3bKamz56WlpvZUxj+IVnKvRbznCPzu3l0Tdty6eWmgcFOWyBM58TtGH3CKSRnBYTdaR1gBFkwTkxh5m3NZSbvG8iBqyQd0+Nfl9wPdf3esTPO6pZe0LPXNj3Me4/0t3yChsPV9Zxqu5iA2m3/vzcgrOzBxDR+ggpUOMh5bO4RpyqODACWLC0AmQwzAWRPb/lL0a9+dFfibMrcJKTj1v9nlmtPNZZRsd2xuWxo9JPCJM5+hz+PB2qdOhsaCj85VvtPha0bVhAUGRC7BHKeDS1Ue84uIlohI8D0CjfSmp+ZpyufikDpIVNYNGJQH3oq66FuQkN1hXx8Iy6S1BLGCfe3JcfUK0l3dYfH1SnNBDDXMzdQ0zU4K6CckHfq5AvrM+zV3zEOXAU9Fz1P1unuEnj7Wzj4Nu5OdTSZe8VFKCDBuklanqRVynkoo9DzJddZRdNEA5c2c1Vxu/oPb5jVo3pK7QgnxsacFedKtgd5ptkKcfRX5bQf6eguJDeYUdOL4v4S5RMWa7/qWW4OLq6gNdjGxsKDyWML+uSyZnUMghFMsMsiWYz4fFhLHDwqfCo9hRMaAtP0vYk23q1AXTUjMOQftOHROvusREx1y/eBnDnPn9uWT5RdcPz6AgT5eA1CAs0/QiEROjC0fCx58zn1+GuKvbeiuOq5zVJ8wnl92B+srR+XLk65YkW6HoMru0ZNWj5EJeKl3D7en+fRbgq5016GYsYar8ecAezphdjeyeadTNXX8A+3z+LGdEojWSa3MctBJ2LPgOvxaxTDBS3PfEOJPDyMxh1sqVTTO/RFJ+u1MSPEVTFGWeOTpavXJmqm3mlknmC6PMDyOTYVJl1TZlJyGj7FsZ9ciKCOBkxkztenb3GAJhjNh7exCZobNJJ119gh2i2ESpIuJTtohdiIsXBDZ9r4Pe1dnXMLd7z7ZsF7OLyu8XHrXbkG2YssDsF0P6mB90E35n9IsOq5CoFqTldUviGcSAPfZdXzMejIt+v9SyEvSb0Wy/LFb5qmlK6LGcgCzHDkq3Q9PcxOjSWu3zhKvPBXTvNoElfmcFHxcb4etbj+eJuL9yniQul5vKYsh59t51ysq9HEEXbB3SsvW/DWilh7xTRZ1Eiwyyu2AsZfXM3hJ2ceje1M3JFnYPSgR9+u2+x2zQJiyTljnL9+/eP46/fkypbcj+eTQrvM5GGR0nmeuq5VxITAzNPxePMoKXoh++fVn0wnv1entKfEYNtMxdzWm4c0359lPnlgCb84GxJ55YWFs53w3Ya9os54xqgbHSZGtqGCrOb5oBbg7doPVf9o36G7Bronjp+3Bx6hvbk7621sf9bKyCfBj2Id4+VkoEJcV1JZVNRSUtwAfsT3MwOYHEQ+aTTFendmjN763vjduA92CStzhScXeWs06+fjUtTYugIjq5jN687My7o/WjF9gXlsGwEP8Qv4V/Uv9EdeRe+r0J1Ycr/PFVz+ufC6zxVvH/6v+rWuXPRrOdpRDJMunJ9nNF3mHUg0Ul7t9Lh4on4C+ulv/QjnEC+zTfSX4k1y5SO1BM4LRMY1aWx8ljxrMxZXZRg0O1hL/CAIb9A34MHvuUuGecmnh4swg8+wUflGbMJxpN2broa4W9xGHdQ6DI9/X+/XZCH8/wEJe8MN7vPIvd2ANYDR4Y7a1hoJgYI/mER+wmuxp9ymWPTDAQxM6OsDOmyFZ+hh5QTAEYK2nGUND53d69TKcaNjo8a4lMj5pwAthCeGRumufdibRtGE4yAsMY3QPJqyL1/5hLIkgPcyxjEzbHQLHSG8bpVmeR6XEqyGDaKngYSHMrkXYw4zkdHiCynq0l0MpGutWZZHpUhhOI2g57FK+Yn/Il31CRxHiPpB+HYXKmKBHumE+yzYNlwh+0lfwjCiG1ylwhpIzbslWGlDEg4uxvwOiizR9xOfJW2bfQezW63UFmSvxlW4DlIwqFb/WEvyiCMoPJEjVVfcsETizemN6wf0VUm6awYETT3n6mCFs6LnkUrzg5XY94EYIGpfDWpwyKc5Wj0GNmNivRw2/WzIQSS78eS5TrwwEQIL6eSomyEOZh2LRA9z+uo53An5lebGNhiWAuiFjFJuyDcQyxCoHYMNtslAs8gYzw9TO8w3i/ZpzBqumabsOo+FSOKgW8Ydo0uf01He2dwkSC8Xmyd64gklSqC8AA1M0UrbgBFK04lL9kr8idCsC0CVMO56apDk6k7ctERYyeism+AlNRuihakQcta3kNQLjSPP2Zcb8lYjHJ1p3QR/tbOtt9wqEtCDeS/Qm7ErEkC/x+Ow14FOsgR4hibYHO3Iwgip/hORO/LnAtOVAUvCQSSXKQGtc9ixe/hjtMckE03eTV7V1AFHqEhKlCDxQem+Zaf01HW69gbUmz9AaJ6Yp4BkJ0MuN9pPB6NiH/nipQunCL0hGie9I1Sw3Qy4N0jXgC8OpOI1Dap0TpczFZoqWpb8k/SeUiU4KH+Xwbhl3EQWej0W1cxwxxqBOEstHYyBnvUezrTBjJ9tUVDpKEzxK1kiXjCRS9Ou/ILKTSLOVKnnRS7r5O7wy74MECbSJNtNGui2wTZnjBnBpjd5YA/8/cSt+nrs6fFeW3b9RY8KBtO7Y4avefrZ6Q3BeSW1PKuLt8SYCO4utIx8CxPzrw1jxC9k6/vfUNWwTqF6NJ7R7rKAzevX/l2B++9mzK+C//S34X/x0xqe4hRG66PlpzmJzhB9FMab/k93LfCTN2chsr7E/E+toSS44Fw79Hj7wTKNeP2nmLQy5qa3k/s3/Nbum4VpPvpKPHf/Pulu/T3pGYXOpWY4Fp37rY5twA8dC4S0V+e8rtvokTfQw1yULDqJ/tBX28v7VoOrSSvlYNjF6H88VbbdRzFpQjxksQ0ZjVjjs8oZFLM1uLfPar+QHANn8HOE/q4qMeUJjtCI0lTOiSakteP4JklbbQa5JWpi+ow7g1Scq4m1/idekOHN+NehJAyQGMi77jGPWol6utT9RnYP5XkJV5tk+i57eZybaJPogwmQttTJgMhGpbPPuNxNmau1xbbcaB1Vi4/VUd1syZPB3qO23TVQJQibibVHq6RB1F/3hANFN/tZ8pfYE1+fjdbAmkKKV7JOhuAeptB9YG/RejPnnQPuoILlC/+VD4p93maQWKnQy+etTjUD+81gFENKW9Zfqy40j+BONBIwk1v72MjgjOslUYUzAyGuP293heb2KABBXctHGY3njlsNOiCzs8f3Wgn7BGXz9fWmg6uSTp6HRmtsq5pof7fY3FzV9SiXF8L8u0yYHrtJ8YUxOtkAqo64zBT4djsatUNLlh3ew4OcDHw48AZeWFbvw/jDbnN/oHt9QcAHjrz8LqAHwdDr//o7g9x+M2RzgwJxRAgPGkiR9gzhNdwl/zO4HYnej/Qz4/axATaPvBt4MCGlFRzao5/zVoYUJas6JCUlHPUGt8bc6pYEQ8ZhONrD5f/ds8y6q+8m25vsSRF6G+x1U/Zzdchy4306xOjlYCRs3gmtE51lwO9YzYwiexINmOml4yn/z+U0INF1vPY5RH1p9ByaOXOtz1DNFtk/ywiL92DkMm9+GVa+Wa0CLk5JiZP1uG4D6MWnMw6gpGY5Et0i7UUuerH4XCIN8KXaw5kgq/vJbDvjzKhT3Lpd7EaJUS66boopztGHEdlhQNLGFDgsjCJ7W0iik29g7PxQ2yaOWENDDbEmC2DMadWW3n2UPJ9y6lcxQq6qrke76E9oN81aFay8k3D4yWSHX4yDo2WA7dLpZWJQWrqLnkr3ohZ3lFrdTlp3WEr06OAlYGs711HExU1KRDK71HdI6AlcN6bhUhD6HVRZPyTkvnLaL7qBu94+4ORaLwAeeNfkdF5ZeYHZgr5AdWDRlSveysxof9ZfK5ZcgW5MCVwbowqzIH+XAVyCFkRqNuU4Ns3jN5dIbmPi1ucI8h05C/24WQf8gqXAOQV/1agNy6agBkFrIL1CN07RpZU1bLlmsPrhM9B7rHXV/9QYzqD+XXZRkQ4P8uEGcLa+4o84ECtTYcBJhDADSkzgkcAoqMkOYhowiK8aLbXgxkLGVZJg58o0OQkwkW/nMBxS4pWKAgEeRoIdCsJDkUp4MUT/AfmuYUX+qmeQOdyHPopuGm6a+b/YWJKtf1o87BaT4FRUTk2DRbg0U62RMdKNIJ3n3IWQoTLpieGgSpd2rTZzjWuPqhw6sBoyOEItKocHSzOm+hm+nrOrU/daeFCTRPiOnboKdGNsMRzxqNBUu2HBVVG6KWAG13fhkSPwA=) + format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(data:font/woff2;base64,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) + format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} + +@font-face { + font-family: Fira Code; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff;base64,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) + format('woff'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, + U+FE2E-FE2F; +} +@font-face { + font-family: Fira Code; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff;base64,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) + format('woff'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +@font-face { + font-family: Fira Code; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff;base64,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) + format('woff'); + unicode-range: U+1F00-1FFF; +} +@font-face { + font-family: Fira Code; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff;base64,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) + format('woff'); + unicode-range: U+0370-03FF; +} +@font-face { + font-family: Fira Code; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff;base64,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) + format('woff'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, + U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +@font-face { + font-family: Fira Code; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(data:font/woff;base64,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) + format('woff'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} + +.graphiql-container *{box-sizing:border-box}.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal{--color-primary: 320, 95%, 43%;--color-secondary: 242, 51%, 61%;--color-tertiary: 188, 100%, 36%;--color-info: 208, 100%, 46%;--color-success: 158, 60%, 42%;--color-warning: 36, 100%, 41%;--color-error: 13, 93%, 58%;--color-neutral: 219, 28%, 32%;--color-base: 219, 28%, 100%;--alpha-secondary: .76;--alpha-tertiary: .5;--alpha-background-heavy: .15;--alpha-background-medium: .1;--alpha-background-light: .07;--font-family: "Roboto", sans-serif;--font-family-mono: "Fira Code", monospace;--font-size-hint:.75rem;--font-size-inline-code:.8125rem;--font-size-body:.9375rem;--font-size-h4:1.125rem;--font-size-h3:1.375rem;--font-size-h2:1.8125rem;--font-weight-regular: 400;--font-weight-medium: 500;--line-height: 1.5;--px-2: 2px;--px-4: 4px;--px-6: 6px;--px-8: 8px;--px-10: 10px;--px-12: 12px;--px-16: 16px;--px-20: 20px;--px-24: 24px;--border-radius-2: 2px;--border-radius-4: 4px;--border-radius-8: 8px;--border-radius-12: 12px;--popover-box-shadow: 0px 6px 20px rgba(59, 76, 106, .13), 0px 1.34018px 4.46726px rgba(59, 76, 106, .0774939), 0px .399006px 1.33002px rgba(59, 76, 106, .0525061);--popover-border: none;--sidebar-width: 60px;--toolbar-width: 40px;--session-header-height: 51px}@media (prefers-color-scheme: dark){body:not(.graphiql-light) .graphiql-container,body:not(.graphiql-light) .CodeMirror-info,body:not(.graphiql-light) .CodeMirror-lint-tooltip,body:not(.graphiql-light) reach-portal{--color-primary: 338, 100%, 67%;--color-secondary: 243, 100%, 77%;--color-tertiary: 188, 100%, 44%;--color-info: 208, 100%, 72%;--color-success: 158, 100%, 42%;--color-warning: 30, 100%, 80%;--color-error: 13, 100%, 58%;--color-neutral: 219, 29%, 78%;--color-base: 219, 29%, 18%;--popover-box-shadow: none;--popover-border: 1px solid hsl(var(--color-neutral))}}body.graphiql-dark .graphiql-container,body.graphiql-dark .CodeMirror-info,body.graphiql-dark .CodeMirror-lint-tooltip,body.graphiql-dark reach-portal{--color-primary: 338, 100%, 67%;--color-secondary: 243, 100%, 77%;--color-tertiary: 188, 100%, 44%;--color-info: 208, 100%, 72%;--color-success: 158, 100%, 42%;--color-warning: 30, 100%, 80%;--color-error: 13, 100%, 58%;--color-neutral: 219, 29%, 78%;--color-base: 219, 29%, 18%;--popover-box-shadow: none;--popover-border: 1px solid hsl(var(--color-neutral))}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal),:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal):is(button){color:hsla(var(--color-neutral),1);font-family:var(--font-family);font-size:var(--font-size-body);font-weight:var(----font-weight-regular);line-height:var(--line-height)}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal) input{color:hsla(var(--color-neutral),1);font-family:var(--font-family);font-size:var(--font-size-caption)}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal) input::placeholder{color:hsla(var(--color-neutral),var(--alpha-secondary))}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal) a{color:hsl(var(--color-primary))}:is(.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal) a:focus{outline:hsl(var(--color-primary)) auto 1px}.graphiql-un-styled,button.graphiql-un-styled{all:unset;border-radius:var(--border-radius-4);cursor:pointer}:is(.graphiql-un-styled,button.graphiql-un-styled):hover{background-color:hsla(var(--color-neutral),var(--alpha-background-light))}:is(.graphiql-un-styled,button.graphiql-un-styled):active{background-color:hsla(var(--color-neutral),var(--alpha-background-medium))}:is(.graphiql-un-styled,button.graphiql-un-styled):focus{outline:hsla(var(--color-neutral),var(--alpha-background-heavy)) auto 1px}.graphiql-button,button.graphiql-button{background-color:hsla(var(--color-neutral),var(--alpha-background-light));border:none;border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),1);cursor:pointer;font-size:var(--font-size-body);padding:var(--px-8) var(--px-12)}:is(.graphiql-button,button.graphiql-button):hover,:is(.graphiql-button,button.graphiql-button):active{background-color:hsla(var(--color-neutral),var(--alpha-background-medium))}:is(.graphiql-button,button.graphiql-button):focus{outline:hsla(var(--color-neutral),var(--alpha-background-heavy)) auto 1px}.graphiql-button-success:is(.graphiql-button,button.graphiql-button){background-color:hsla(var(--color-success),var(--alpha-background-heavy))}.graphiql-button-error:is(.graphiql-button,button.graphiql-button){background-color:hsla(var(--color-error),var(--alpha-background-heavy))}.graphiql-button-group{background-color:hsla(var(--color-neutral),var(--alpha-background-light));border-radius:calc(var(--border-radius-4) + var(--px-4));display:flex;padding:var(--px-4)}.graphiql-button-group>button.graphiql-button{background-color:transparent}.graphiql-button-group>button.graphiql-button:hover{background-color:hsla(var(--color-neutral),var(--alpha-background-light))}.graphiql-button-group>button.graphiql-button.active{background-color:hsl(var(--color-base));cursor:default}.graphiql-button-group>*+*{margin-left:var(--px-8)}:root{--reach-dialog: 1}[data-reach-dialog-overlay]{background:hsla(0,0%,0%,.33);position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto}[data-reach-dialog-content]{width:50vw;margin:10vh auto;background:white;padding:2rem;outline:none}[data-reach-dialog-overlay]{align-items:center;background-color:hsla(var(--color-neutral),var(--alpha-background-heavy));display:flex;justify-content:center;z-index:10}[data-reach-dialog-content]{background-color:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-12);box-shadow:var(--popover-box-shadow);margin:0;max-height:80vh;max-width:80vw;overflow:auto;padding:0;width:unset}.graphiql-dialog-close>svg{color:hsla(var(--color-neutral),var(--alpha-secondary));display:block;height:var(--px-12);padding:var(--px-12);width:var(--px-12)}:root{--reach-listbox: 1}[data-reach-listbox-popover]{display:block;position:absolute;min-width:-moz-fit-content;min-width:-webkit-min-content;min-width:min-content;padding:.25rem 0;background:hsl(0,0%,100%);outline:none;border:solid 1px hsla(0,0%,0%,.25)}[data-reach-listbox-popover]:focus-within{box-shadow:0 0 4px Highlight;outline:-webkit-focus-ring-color auto 4px}[data-reach-listbox-popover][hidden]{display:none}[data-reach-listbox-list]{margin:0;padding:0;list-style:none}[data-reach-listbox-list]:focus{box-shadow:none;outline:none}[data-reach-listbox-option]{display:block;margin:0;padding:.25rem .5rem;white-space:nowrap;user-select:none}[data-reach-listbox-option][data-current-nav]{background:hsl(211,81%,46%);color:#fff}[data-reach-listbox-option][data-current-selected]{font-weight:bolder}[data-reach-listbox-option][data-current-selected][data-confirming]{animation:flash .1s;animation-iteration-count:1}[data-reach-listbox-option][aria-disabled=true]{opacity:.5}[data-reach-listbox-button]{display:inline-flex;align-items:center;justify-content:space-between;padding:1px 10px 2px;border:1px solid;border-color:rgb(216,216,216) rgb(209,209,209) rgb(186,186,186);cursor:default;user-select:none}[data-reach-listbox-button][aria-disabled=true]{opacity:.5}[data-reach-listbox-arrow]{margin-left:.5rem;display:block;font-size:.5em}[data-reach-listbox-group-label]{display:block;margin:0;padding:.25rem .5rem;white-space:nowrap;user-select:none;font-weight:bolder}@keyframes flash{0%{background:hsla(211,81%,36%,1);color:#fff;opacity:1}50%{opacity:.5;background:inherit;color:inherit}to{background:hsla(211,81%,36%,1);color:#fff;opacity:1}}:root{--reach-menu-button: 1}[data-reach-menu]{position:relative}[data-reach-menu-popover]{display:block;position:absolute}[data-reach-menu-popover][hidden]{display:none}[data-reach-menu-list],[data-reach-menu-items]{display:block;white-space:nowrap;border:solid 1px hsla(0,0%,0%,.25);background:hsla(0,100%,100%,.99);outline:none;padding:1rem 0;font-size:85%}[data-reach-menu-item]{display:block;user-select:none}[data-reach-menu-item]{cursor:pointer;display:block;color:inherit;font:inherit;text-decoration:initial;padding:5px 20px}[data-reach-menu-item][data-selected]{background:hsl(211,81%,36%);color:#fff;outline:none}[data-reach-menu-item][aria-disabled]{opacity:.5;cursor:not-allowed}[data-reach-listbox-popover],[data-reach-menu-list]{background-color:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-8);box-shadow:var(--popover-box-shadow);font-size:inherit;max-width:250px;padding:var(--px-4)}[data-reach-listbox-option],[data-reach-menu-item]{border-radius:var(--border-radius-4);font-size:inherit;margin:var(--px-4);overflow:hidden;padding:var(--px-6) var(--px-8);text-overflow:ellipsis;white-space:nowrap}[data-reach-listbox-option][data-selected],[data-reach-menu-item][data-selected],[data-reach-listbox-option][data-current-nav],[data-reach-menu-item][data-current-nav],[data-reach-listbox-option]:hover,[data-reach-menu-item]:hover{background-color:hsla(var(--color-neutral),var(--alpha-background-light));color:inherit}[data-reach-listbox-option]:not(:first-child),[data-reach-menu-item]:not(:first-child){margin-top:0}[data-reach-listbox-button]{border:none;cursor:pointer;padding:0}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) blockquote{margin-left:0;margin-right:0;padding-left:var(--px-8)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) code,:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) pre{border-radius:var(--border-radius-4);font-family:var(--font-family-mono);font-size:var(--font-size-inline-code)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) code{padding:var(--px-2)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) pre{overflow:auto;padding:var(--px-6) var(--px-8)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) pre code{background-color:initial;border-radius:0;padding:0}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) ol,:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) ul{padding-left:var(--px-16)}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) ol{list-style-type:decimal}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) ul{list-style-type:disc}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation) img{border-radius:var(--border-radius-4);max-height:120px;max-width:100%}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation)>:first-child{margin-top:0}:is(.graphiql-markdown-description,.graphiql-markdown-deprecation,.CodeMirror-hint-information-description,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-description,.CodeMirror-info .info-deprecation)>:last-child{margin-bottom:0}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) a{color:hsl(var(--color-primary));text-decoration:none}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) a:hover{text-decoration:underline}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) blockquote{border-left:1.5px solid hsla(var(--color-neutral),var(--alpha-tertiary))}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) code,:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description) pre{background-color:hsla(var(--color-neutral),var(--alpha-background-light));color:hsla(var(--color-neutral),1)}:is(.graphiql-markdown-description,.CodeMirror-hint-information-description,.CodeMirror-info .info-description)>*{margin:var(--px-12) 0}:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation) a{color:hsl(var(--color-warning));text-decoration:underline}:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation) blockquote{border-left:1.5px solid hsl(var(--color-warning))}:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation) code,:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation) pre{background-color:hsla(var(--color-warning),var(--alpha-background-heavy))}:is(.graphiql-markdown-deprecation,.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation)>*{margin:var(--px-8) 0}.graphiql-markdown-preview>:not(:first-child){display:none}.CodeMirror-hint-information-deprecation,.CodeMirror-info .info-deprecation{background-color:hsla(var(--color-warning),var(--alpha-background-light));border:1px solid hsl(var(--color-warning));border-radius:var(--border-radius-4);color:hsl(var(--color-warning));margin-top:var(--px-12);padding:var(--px-6) var(--px-8)}.CodeMirror-hint-information-deprecation-label,.CodeMirror-info .info-deprecation-label{font-size:var(--font-size-hint);font-weight:var(--font-weight-medium)}.CodeMirror-hint-information-deprecation-reason,.CodeMirror-info .info-deprecation-reason{margin-top:var(--px-6)}.graphiql-spinner{height:56px;margin:auto;margin-top:var(--px-16);width:56px}.graphiql-spinner:after{animation:rotation .8s linear 0s infinite;border:4px solid transparent;border-radius:100%;border-top:4px solid hsla(var(--color-neutral),var(--alpha-tertiary));content:"";display:inline-block;height:46px;vertical-align:middle;width:46px}@keyframes rotation{0%{transform:rotate(0)}to{transform:rotate(360deg)}}:root{--reach-tooltip: 1}[data-reach-tooltip]{z-index:1;pointer-events:none;position:absolute;padding:.25em .5em;box-shadow:2px 2px 10px #0000001a;white-space:nowrap;font-size:85%;background:#f0f0f0;color:#444;border:solid 1px #ccc}[data-reach-tooltip]{background:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-4);box-shadow:var(--popover-box-shadow);color:hsl(var(--color-neutral));font-size:inherit;padding:var(--px-4) var(--px-6)}.graphiql-tabs{display:flex;overflow-x:auto;padding:var(--px-12)}.graphiql-tabs>:not(:first-child){margin-left:var(--px-12)}.graphiql-tab{align-items:stretch;border-radius:var(--border-radius-8);color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex}.graphiql-tab>button.graphiql-tab-close{visibility:hidden}.graphiql-tab.graphiql-tab-active>button.graphiql-tab-close,.graphiql-tab:hover>button.graphiql-tab-close,.graphiql-tab:focus-within>button.graphiql-tab-close{visibility:unset}.graphiql-tab.graphiql-tab-active{background-color:hsla(var(--color-neutral),var(--alpha-background-heavy));color:hsla(var(--color-neutral),1)}button.graphiql-tab-button{padding:var(--px-4) 0 var(--px-4) var(--px-8)}button.graphiql-tab-close{align-items:center;display:flex;padding:var(--px-4) var(--px-8)}button.graphiql-tab-close>svg{height:var(--px-8);width:var(--px-8)}.graphiql-history-header{font-size:var(--font-size-h2);font-weight:var(--font-weight-medium)}.graphiql-history-items{margin:var(--px-16) 0 0;list-style:none;padding:0}.graphiql-history-item{border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex;font-size:var(--font-size-inline-code);font-family:var(--font-family-mono);height:34px}.graphiql-history-item:hover{color:hsla(var(--color-neutral),1);background-color:hsla(var(--color-neutral),var(--alpha-background-light))}.graphiql-history-item:not(:first-child){margin-top:var(--px-4)}.graphiql-history-item.editable{background-color:hsla(var(--color-primary),var(--alpha-background-medium))}.graphiql-history-item.editable>input{background:transparent;border:none;flex:1;margin:0;outline:none;padding:0 var(--px-10);width:100%}.graphiql-history-item.editable>input::placeholder{color:hsla(var(--color-neutral),var(--alpha-secondary))}.graphiql-history-item.editable>button{color:hsl(var(--color-primary));padding:0 var(--px-10)}.graphiql-history-item.editable>button:active{background-color:hsla(var(--color-primary),var(--alpha-background-heavy))}.graphiql-history-item.editable>button:focus{outline:hsl(var(--color-primary)) auto 1px}.graphiql-history-item.editable>button>svg{display:block}button.graphiql-history-item-label{flex:1;padding:var(--px-8) var(--px-10);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}button.graphiql-history-item-action{align-items:center;color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex;padding:var(--px-8) var(--px-6)}button.graphiql-history-item-action:hover{color:hsla(var(--color-neutral),1)}button.graphiql-history-item-action>svg{height:14px;width:14px}.graphiql-history-item-spacer{height:var(--px-16)}.graphiql-doc-explorer-default-value{color:hsl(var(--color-success))}a.graphiql-doc-explorer-type-name{color:hsl(var(--color-warning));text-decoration:none}a.graphiql-doc-explorer-type-name:hover{text-decoration:underline}a.graphiql-doc-explorer-type-name:focus{outline:hsl(var(--color-warning)) auto 1px}.graphiql-doc-explorer-argument>*+*{margin-top:var(--px-12)}.graphiql-doc-explorer-argument-name{color:hsl(var(--color-secondary))}.graphiql-doc-explorer-argument-deprecation{background-color:hsla(var(--color-warning),var(--alpha-background-light));border:1px solid hsl(var(--color-warning));border-radius:var(--border-radius-4);color:hsl(var(--color-warning));padding:var(--px-8)}.graphiql-doc-explorer-argument-deprecation-label{font-size:var(--font-size-hint);font-weight:var(--font-weight-medium)}.graphiql-doc-explorer-deprecation{background-color:hsla(var(--color-warning),var(--alpha-background-light));border:1px solid hsl(var(--color-warning));border-radius:var(--px-4);color:hsl(var(--color-warning));padding:var(--px-8)}.graphiql-doc-explorer-deprecation-label{font-size:var(--font-size-hint);font-weight:var(--font-weight-medium)}.graphiql-doc-explorer-directive{color:hsl(var(--color-secondary))}.graphiql-doc-explorer-section-title{align-items:center;display:flex;font-size:var(--font-size-hint);font-weight:var(--font-weight-medium);line-height:1}.graphiql-doc-explorer-section-title>svg{height:var(--px-16);margin-right:var(--px-8);width:var(--px-16)}.graphiql-doc-explorer-section-content{margin-left:var(--px-8);margin-top:var(--px-16)}.graphiql-doc-explorer-section-content>*+*{margin-top:var(--px-16)}.graphiql-doc-explorer-root-type{color:hsl(var(--color-info))}:root{--reach-combobox: 1}[data-reach-combobox-popover]{border:solid 1px hsla(0,0%,0%,.25);background:hsla(0,100%,100%,.99);font-size:85%}[data-reach-combobox-list]{list-style:none;margin:0;padding:0;user-select:none}[data-reach-combobox-option]{cursor:pointer;margin:0;padding:.25rem .5rem}[data-reach-combobox-option][aria-selected=true]{background:hsl(211,10%,95%)}[data-reach-combobox-option]:hover{background:hsl(211,10%,92%)}[data-reach-combobox-option][aria-selected=true]:hover{background:hsl(211,10%,90%)}[data-suggested-value]{font-weight:700}[data-reach-combobox]{color:hsla(var(--color-neutral),var(--alpha-secondary))}[data-reach-combobox]:not([data-state="idle"]){border:var(--popover-border);border-radius:var(--border-radius-4);box-shadow:var(--popover-box-shadow);color:hsla(var(--color-neutral),1)}[data-reach-combobox]:not([data-state="idle"]) .graphiql-doc-explorer-search-input{background:hsl(var(--color-base));border-bottom-left-radius:0;border-bottom-right-radius:0}.graphiql-doc-explorer-search-input{align-items:center;background-color:hsla(var(--color-neutral),var(--alpha-background-light));border-radius:var(--border-radius-4);display:flex;padding:var(--px-8) var(--px-12)}[data-reach-combobox-input]{border:none;background-color:transparent;margin-left:var(--px-4);width:100%}[data-reach-combobox-input]:focus{outline:none}[data-reach-combobox-popover]{background-color:hsl(var(--color-base));border:none;border-bottom-left-radius:var(--border-radius-4);border-bottom-right-radius:var(--border-radius-4);border-top:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));max-height:400px;overflow-y:auto;position:relative}[data-reach-combobox-list]{font-size:var(--font-size-body);padding:var(--px-4)}[data-reach-combobox-option]{border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));overflow-x:hidden;padding:var(--px-8) var(--px-12);text-overflow:ellipsis;white-space:nowrap}[data-reach-combobox-option][data-highlighted]{background-color:hsla(var(--color-neutral),var(--alpha-background-light))}[data-reach-combobox-option]:hover{background-color:hsla(var(--color-neutral),var(--alpha-background-medium))}[data-reach-combobox-option][data-highlighted]:hover{background-color:hsla(var(--color-neutral),var(--alpha-background-heavy))}[data-reach-combobox-option]+[data-reach-combobox-option]{margin-top:var(--px-4)}.graphiql-doc-explorer-search-type{color:hsl(var(--color-info))}.graphiql-doc-explorer-search-field{color:hsl(var(--color-warning))}.graphiql-doc-explorer-search-argument{color:hsl(var(--color-secondary))}.graphiql-doc-explorer-search-divider{color:hsla(var(--color-neutral),var(--alpha-secondary));font-size:var(--font-size-hint);font-weight:var(--font-weight-medium);margin-top:var(--px-8);padding:var(--px-8) var(--px-12)}.graphiql-doc-explorer-search-empty{color:hsla(var(--color-neutral),var(--alpha-secondary));padding:var(--px-8) var(--px-12)}a.graphiql-doc-explorer-field-name{color:hsl(var(--color-info));text-decoration:none}a.graphiql-doc-explorer-field-name:hover{text-decoration:underline}a.graphiql-doc-explorer-field-name:focus{outline:hsl(var(--color-info)) auto 1px}.graphiql-doc-explorer-item>:not(:first-child){margin-top:var(--px-12)}.graphiql-doc-explorer-argument-multiple{margin-left:var(--px-8)}.graphiql-doc-explorer-enum-value{color:hsl(var(--color-info))}.graphiql-doc-explorer-header{display:flex;justify-content:space-between;position:relative}.graphiql-doc-explorer-header:focus-within .graphiql-doc-explorer-title{visibility:hidden}.graphiql-doc-explorer-header:focus-within .graphiql-doc-explorer-back:not(:focus){color:transparent}.graphiql-doc-explorer-header-content{display:flex;flex-direction:column;min-width:0}.graphiql-doc-explorer-search{height:100%;position:absolute;right:0;top:0}.graphiql-doc-explorer-search:focus-within{left:0}.graphiql-doc-explorer-search [data-reach-combobox-input]{height:24px;width:4ch}.graphiql-doc-explorer-search [data-reach-combobox-input]:focus{width:100%}a.graphiql-doc-explorer-back{align-items:center;color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex;text-decoration:none}a.graphiql-doc-explorer-back:hover{text-decoration:underline}a.graphiql-doc-explorer-back:focus{outline:hsla(var(--color-neutral),var(--alpha-secondary)) auto 1px}a.graphiql-doc-explorer-back:focus+.graphiql-doc-explorer-title{visibility:unset}a.graphiql-doc-explorer-back>svg{height:var(--px-8);margin-right:var(--px-8);width:var(--px-8)}.graphiql-doc-explorer-title{font-weight:var(--font-weight-medium);font-size:var(--font-size-h2);overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.graphiql-doc-explorer-title:not(:first-child){font-size:var(--font-size-h3);margin-top:var(--px-8)}.graphiql-doc-explorer-content>*{color:hsla(var(--color-neutral),var(--alpha-secondary));margin-top:var(--px-20)}.graphiql-doc-explorer-error{background-color:hsla(var(--color-error),var(--alpha-background-heavy));border:1px solid hsl(var(--color-error));border-radius:var(--border-radius-8);color:hsl(var(--color-error));padding:var(--px-8) var(--px-12)}.CodeMirror{font-family:monospace;height:300px;color:#000;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid black;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor .CodeMirror-line::selection,.cm-fat-cursor .CodeMirror-line>span::selection,.cm-fat-cursor .CodeMirror-line>span>span::selection{background:transparent}.cm-fat-cursor .CodeMirror-line::-moz-selection,.cm-fat-cursor .CodeMirror-line>span::-moz-selection,.cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:transparent}.cm-fat-cursor{caret-color:transparent}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3,.cm-s-default .cm-type{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error,.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-50px;margin-right:-50px;padding-bottom:50px;height:100%;outline:none;position:relative;z-index:0}.CodeMirror-sizer{position:relative;border-right:50px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none;outline:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-50px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:none}.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}div.CodeMirror-dragcursors,.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:#ff06}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:""}span.CodeMirror-selectedtext{background:none}.CodeMirror{height:100%;position:absolute;width:100%}.CodeMirror{font-family:var(--font-family-mono)}.CodeMirror,.CodeMirror-gutters{background:none;background-color:var(--editor-background, hsl(var(--color-base)))}.CodeMirror-linenumber{padding:0}.CodeMirror-gutters{border:none}.cm-s-graphiql{color:hsla(var(--color-neutral),var(--alpha-tertiary))}.cm-s-graphiql .cm-keyword{color:hsl(var(--color-primary))}.cm-s-graphiql .cm-def{color:hsl(var(--color-tertiary))}.cm-s-graphiql .cm-punctuation{color:hsla(var(--color-neutral),var(--alpha-tertiary))}.cm-s-graphiql .cm-variable{color:hsl(var(--color-secondary))}.cm-s-graphiql .cm-atom{color:hsl(var(--color-tertiary))}.cm-s-graphiql .cm-number{color:hsl(var(--color-success))}.cm-s-graphiql .cm-string{color:hsl(var(--color-warning))}.cm-s-graphiql .cm-builtin{color:hsl(var(--color-success))}.cm-s-graphiql .cm-string-2{color:hsl(var(--color-secondary))}.cm-s-graphiql .cm-attribute,.cm-s-graphiql .cm-meta{color:hsl(var(--color-tertiary))}.cm-s-graphiql .cm-property{color:hsl(var(--color-info))}.cm-s-graphiql .cm-qualifier{color:hsl(var(--color-secondary))}.cm-s-graphiql .cm-comment{color:hsla(var(--color-neutral),var(--alpha-secondary))}.cm-s-graphiql .cm-ws{color:hsla(var(--color-neutral),var(--alpha-tertiary))}.cm-s-graphiql .cm-invalidchar{color:hsl(var(--color-error))}.cm-s-graphiql .CodeMirror-cursor{border-left:2px solid hsla(var(--color-neutral),var(--alpha-secondary))}.cm-s-graphiql .CodeMirror-linenumber{color:hsla(var(--color-neutral),var(--alpha-tertiary))}div.CodeMirror span.CodeMirror-matchingbracket,div.CodeMirror span.CodeMirror-nonmatchingbracket{color:hsl(var(--color-warning))}.CodeMirror-selected,.CodeMirror-focused .CodeMirror-selected{background:hsla(var(--color-neutral),var(--alpha-background-heavy))}.CodeMirror-dialog{background:inherit;color:inherit;left:0;right:0;overflow:hidden;padding:var(--px-2) var(--px-6);position:absolute;z-index:6}.CodeMirror-dialog-top{border-bottom:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));padding-bottom:var(--px-12);top:0}.CodeMirror-dialog-bottom{border-top:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));bottom:0;padding-top:var(--px-12)}.CodeMirror-search-hint{display:none}.CodeMirror-dialog input{border:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));border-radius:var(--border-radius-4);padding:var(--px-4)}.CodeMirror-dialog input:focus{outline:hsl(var(--color-primary)) solid 2px}.cm-searching{background-color:hsla(var(--color-warning),var(--alpha-background-light));padding-bottom:1.5px;padding-top:.5px}.CodeMirror-foldmarker{color:#00f;text-shadow:#b9f 1px 1px 2px,#b9f -1px -1px 2px,#b9f 1px -1px 2px,#b9f -1px 1px 2px;font-family:arial;line-height:.3;cursor:pointer}.CodeMirror-foldgutter{width:.7em}.CodeMirror-foldgutter-open,.CodeMirror-foldgutter-folded{cursor:pointer}.CodeMirror-foldgutter-open:after{content:"\25be"}.CodeMirror-foldgutter-folded:after{content:"\25b8"}.CodeMirror-foldgutter{width:var(--px-12)}.CodeMirror-foldmarker{background-color:hsl(var(--color-info));border-radius:var(--border-radius-4);color:hsl(var(--color-base));font-family:inherit;margin:0 var(--px-4);padding:0 var(--px-8);text-shadow:none}.CodeMirror-foldgutter-open,.CodeMirror-foldgutter-folded{color:hsla(var(--color-neutral),var(--alpha-tertiary))}.CodeMirror-foldgutter-open:after,.CodeMirror-foldgutter-folded:after{margin:0 var(--px-2)}.graphiql-editor{height:100%;position:relative;width:100%}.graphiql-editor.hidden{left:-9999px;position:absolute;top:-9999px;visibility:hidden}.CodeMirror-lint-markers{width:16px}.CodeMirror-lint-tooltip{background-color:#ffd;border:1px solid black;border-radius:4px;color:#000;font-family:monospace;font-size:10pt;overflow:hidden;padding:2px 5px;position:fixed;white-space:pre;white-space:pre-wrap;z-index:100;max-width:600px;opacity:0;transition:opacity .4s;-moz-transition:opacity .4s;-webkit-transition:opacity .4s;-o-transition:opacity .4s;-ms-transition:opacity .4s}.CodeMirror-lint-mark{background-position:left bottom;background-repeat:repeat-x}.CodeMirror-lint-mark-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=)}.CodeMirror-lint-mark-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==)}.CodeMirror-lint-marker{background-position:center center;background-repeat:no-repeat;cursor:pointer;display:inline-block;height:16px;width:16px;vertical-align:middle;position:relative}.CodeMirror-lint-message{padding-left:18px;background-position:top left;background-repeat:no-repeat}.CodeMirror-lint-marker-warning,.CodeMirror-lint-message-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=)}.CodeMirror-lint-marker-error,.CodeMirror-lint-message-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=)}.CodeMirror-lint-marker-multiple{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:right bottom;width:100%;height:100%}.CodeMirror-lint-line-error{background-color:#b74c5114}.CodeMirror-lint-line-warning{background-color:#ffd3001a}.CodeMirror-lint-mark-error,.CodeMirror-lint-mark-warning{background-repeat:repeat-x;background-size:10px 3px;background-position:0 95%}.cm-s-graphiql .CodeMirror-lint-mark-error{color:hsl(var(--color-error))}.CodeMirror-lint-mark-error{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--color-error)) 80%,transparent 90%),linear-gradient(135deg,transparent 5%,hsl(var(--color-error)) 15%,transparent 25%),linear-gradient(135deg,transparent 45%,hsl(var(--color-error)) 55%,transparent 65%),linear-gradient(45deg,transparent 25%,hsl(var(--color-error)) 35%,transparent 50%)}.cm-s-graphiql .CodeMirror-lint-mark-warning{color:hsl(var(--color-warning))}.CodeMirror-lint-mark-warning{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--color-warning)) 80%,transparent 90%),linear-gradient(135deg,transparent 5%,hsl(var(--color-warning)) 15%,transparent 25%),linear-gradient(135deg,transparent 45%,hsl(var(--color-warning)) 55%,transparent 65%),linear-gradient(45deg,transparent 25%,hsl(var(--color-warning)) 35%,transparent 50%)}.CodeMirror-lint-tooltip{background-color:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-8);box-shadow:var(--popover-box-shadow);font-size:var(--font-size-body);font-family:var(--font-family);max-width:600px;overflow:hidden;padding:var(--px-12)}.CodeMirror-lint-message-error,.CodeMirror-lint-message-warning{background-image:none;padding:0}.CodeMirror-lint-message-error{color:hsl(var(--color-error))}.CodeMirror-lint-message-warning{color:hsl(var(--color-warning))}.CodeMirror-hints{position:absolute;z-index:10;overflow:hidden;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px #0003;border-radius:3px;border:1px solid silver;background:white;font-size:90%;font-family:monospace;max-height:20em;overflow-y:auto}.CodeMirror-hint{margin:0;padding:0 4px;border-radius:2px;white-space:pre;color:#000;cursor:pointer}li.CodeMirror-hint-active{background:#08f;color:#fff}.CodeMirror-hints{background:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-8);box-shadow:var(--popover-box-shadow);display:grid;font-family:var(--font-family);font-size:var(--font-size-body);grid-template-columns:auto fit-content(300px);max-height:264px;padding:0}.CodeMirror-hint{border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));grid-column:1 / 2;margin:var(--px-4);padding:var(--px-6) var(--px-8)!important}.CodeMirror-hint:not(:first-child){margin-top:0}li.CodeMirror-hint-active{background:hsla(var(--color-primary),var(--alpha-background-medium));color:hsl(var(--color-primary))}.CodeMirror-hint-information{border-left:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));grid-column:2 / 3;grid-row:1 / 99999;max-height:264px;overflow:auto;padding:var(--px-12)}.CodeMirror-hint-information-header{display:flex;align-items:baseline}.CodeMirror-hint-information-field-name{font-size:var(--font-size-h4);font-weight:var(--font-weight-medium)}.CodeMirror-hint-information-type-name-pill{border:1px solid hsla(var(--color-neutral),var(--alpha-tertiary));border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));margin-left:var(--px-6);padding:var(--px-4)}.CodeMirror-hint-information-type-name{color:inherit;text-decoration:none}.CodeMirror-hint-information-type-name:hover{text-decoration:underline dotted}.CodeMirror-hint-information-description{color:hsla(var(--color-neutral),var(--alpha-secondary));margin-top:var(--px-12)}.CodeMirror-info{background-color:hsl(var(--color-base));border:var(--popover-border);border-radius:var(--border-radius-8);box-shadow:var(--popover-box-shadow);color:hsla(var(--color-neutral),1);max-height:300px;max-width:400px;opacity:0;overflow:auto;padding:var(--px-12);position:fixed;transition:opacity .15s;z-index:10}.CodeMirror-info a{color:inherit;text-decoration:none}.CodeMirror-info a:hover{text-decoration:underline dotted}.CodeMirror-info .CodeMirror-info-header{display:flex;align-items:baseline}.CodeMirror-info .CodeMirror-info-header>.type-name,.CodeMirror-info .CodeMirror-info-header>.field-name,.CodeMirror-info .CodeMirror-info-header>.arg-name,.CodeMirror-info .CodeMirror-info-header>.directive-name,.CodeMirror-info .CodeMirror-info-header>.enum-value{font-size:var(--font-size-h4);font-weight:var(--font-weight-medium)}.CodeMirror-info .type-name-pill{border:1px solid hsla(var(--color-neutral),var(--alpha-tertiary));border-radius:var(--border-radius-4);color:hsla(var(--color-neutral),var(--alpha-secondary));margin-left:var(--px-6);padding:var(--px-4)}.CodeMirror-info .info-description{color:hsla(var(--color-neutral),var(--alpha-secondary));margin-top:var(--px-12);overflow:hidden}.CodeMirror-jump-token{text-decoration:underline dotted;cursor:pointer}.auto-inserted-leaf.cm-property{animation-duration:6s;animation-name:insertionFade;border-radius:var(--border-radius-4);padding:var(--px-2)}@keyframes insertionFade{0%,to{background-color:none}15%,85%{background-color:hsla(var(--color-warning),var(--alpha-background-light))}}button.graphiql-toolbar-button{display:flex;align-items:center;justify-content:center;height:var(--toolbar-width);width:var(--toolbar-width)}button.graphiql-toolbar-button.error{background:hsla(var(--color-error),var(--alpha-background-heavy))}.graphiql-execute-button-wrapper{position:relative}button.graphiql-execute-button{background-color:hsl(var(--color-primary));border:none;border-radius:var(--border-radius-8);cursor:pointer;height:var(--toolbar-width);padding:0;width:var(--toolbar-width)}button.graphiql-execute-button:hover{background-color:hsla(var(--color-primary),.9)}button.graphiql-execute-button:active{background-color:hsla(var(--color-primary),.8)}button.graphiql-execute-button:focus{outline:hsla(var(--color-primary),.8) auto 1px}button.graphiql-execute-button>svg{color:#fff;display:block;height:var(--px-16);margin:auto;width:var(--px-16)}.graphiql-toolbar-listbox,button.graphiql-toolbar-menu{display:block;height:var(--toolbar-width);width:var(--toolbar-width)} + +.graphiql-container{background-color:hsl(var(--color-base));display:flex;height:100%;margin:0;overflow:hidden;width:100%}.graphiql-container .graphiql-sidebar{display:flex;flex-direction:column;justify-content:space-between;padding:var(--px-8);width:var(--sidebar-width)}.graphiql-container .graphiql-sidebar .graphiql-sidebar-section{display:flex;flex-direction:column;gap:var(--px-8)}.graphiql-container .graphiql-sidebar button{align-items:center;color:hsla(var(--color-neutral),var(--alpha-secondary));display:flex;height:calc(var(--sidebar-width) - var(--px-8)*2);justify-content:center;width:calc(var(--sidebar-width) - var(--px-8)*2)}.graphiql-container .graphiql-sidebar button.active{color:hsla(var(--color-neutral),1)}.graphiql-container .graphiql-sidebar button:not(:first-child){margin-top:var(--px-4)}.graphiql-container .graphiql-sidebar button>svg{height:var(--px-20);width:var(--px-20)}.graphiql-container .graphiql-main{display:flex;flex:1;min-width:0}.graphiql-container .graphiql-sessions{background-color:hsla(var(--color-neutral),var(--alpha-background-light));border-radius:calc(var(--border-radius-12) + var(--px-8));display:flex;flex:1;flex-direction:column;margin:var(--px-16);margin-left:0;max-height:100%;min-width:0}.graphiql-container .graphiql-session-header{align-items:center;display:flex;height:var(--session-header-height);justify-content:space-between}button.graphiql-tab-add{height:100%;padding:0 var(--px-4)}button.graphiql-tab-add>svg{color:hsla(var(--color-neutral),var(--alpha-secondary));display:block;height:var(--px-16);width:var(--px-16)}.graphiql-add-tab-wrapper{padding:var(--px-12) 0}.graphiql-container .graphiql-session-header-right{align-items:stretch;display:flex}.graphiql-container .graphiql-logo{color:hsla(var(--color-neutral),var(--alpha-secondary));font-size:var(--font-size-h4);font-weight:var(--font-weight-medium);padding:var(--px-12) var(--px-16)}.graphiql-container .graphiql-logo .graphiql-logo-link{color:hsla(var(--color-neutral),var(--alpha-secondary));text-decoration:none}.graphiql-container .graphiql-session{display:flex;flex:1;padding:0 var(--px-8) var(--px-8)}.graphiql-container .graphiql-editors{background-color:hsl(var(--color-base));border-radius:calc(var(--border-radius-12));box-shadow:var(--popover-box-shadow);display:flex;flex:1;flex-direction:column}.graphiql-container .graphiql-editors.full-height{margin-top:calc(var(--px-8) - var(--session-header-height))}.graphiql-container .graphiql-query-editor{border-bottom:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));display:flex;flex:1;padding:var(--px-16)}.graphiql-container .graphiql-query-editor-wrapper{display:flex;flex:1}.graphiql-container .graphiql-toolbar{margin-left:var(--px-16);width:var(--toolbar-width)}.graphiql-container .graphiql-toolbar>*+*{margin-top:var(--px-8)}.graphiql-toolbar-icon{color:hsla(var(--color-neutral),var(--alpha-tertiary));display:block;height:calc(var(--toolbar-width) - var(--px-8)*2);width:calc(var(--toolbar-width) - var(--px-8)*2)}.graphiql-container .graphiql-editor-tools{align-items:center;cursor:row-resize;display:flex;justify-content:space-between;padding:var(--px-8)}.graphiql-container .graphiql-editor-tools button{color:hsla(var(--color-neutral),var(--alpha-secondary))}.graphiql-container .graphiql-editor-tools button.active{color:hsla(var(--color-neutral),1)}.graphiql-container .graphiql-editor-tools-tabs{cursor:auto;display:flex}.graphiql-container .graphiql-editor-tools-tabs>button{padding:var(--px-8) var(--px-12)}.graphiql-container .graphiql-editor-tools-tabs>button+button{margin-left:var(--px-8)}.graphiql-container .graphiql-editor-tool{flex:1;padding:var(--px-16)}.graphiql-container .graphiql-editor-tool,.graphiql-container .graphiql-editor-tools,.graphiql-container .graphiql-toolbar{position:relative}.graphiql-container .graphiql-response{--editor-background:transparent;display:flex;flex:1;flex-direction:column;position:relative}.graphiql-container .graphiql-response .result-window{flex:1;position:relative}.graphiql-container .graphiql-footer{border-top:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy))}.graphiql-container .graphiql-plugin{border-left:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));flex:1;overflow-y:auto;padding:var(--px-16)}.graphiql-container .graphiql-horizontal-drag-bar{cursor:col-resize;width:var(--px-12)}.graphiql-container .graphiql-horizontal-drag-bar:hover:after{border:var(--px-2) solid hsla(var(--color-neutral),var(--alpha-background-heavy));border-radius:var(--border-radius-2);content:"";display:block;height:25%;margin:0 auto;position:relative;top:37.5%;width:0}.graphiql-container .graphiql-chevron-icon{color:hsla(var(--color-neutral),var(--alpha-tertiary));display:block;height:var(--px-12);margin:var(--px-12);width:var(--px-12)}.graphiql-spin{animation:spin .8s linear 0s infinite}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}reach-portal .graphiql-dialog-header{align-items:center;display:flex;justify-content:space-between;padding:var(--px-24)}reach-portal .graphiql-dialog-title{font-size:var(--font-size-h3);font-weight:var(--font-weight-medium)}reach-portal .graphiql-dialog-section{align-items:center;border-top:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));display:flex;justify-content:space-between;padding:var(--px-24)}reach-portal .graphiql-dialog-section>:not(:first-child){margin-left:var(--px-24)}reach-portal .graphiql-dialog-section-title{font-size:var(--font-size-h4);font-weight:var(--font-weight-medium)}reach-portal .graphiql-dialog-section-caption{color:hsla(var(--color-neutral),var(--alpha-secondary))}reach-portal .graphiql-warning-text{color:hsl(var(--color-warning));font-weight:var(--font-weight-medium)}reach-portal .graphiql-table{border-collapse:collapse;width:100%}reach-portal .graphiql-table :is(th,td){border:1px solid hsla(var(--color-neutral),var(--alpha-background-heavy));padding:var(--px-8) var(--px-12)}reach-portal .graphiql-key{background-color:hsla(var(--color-neutral),var(--alpha-background-medium));border-radius:var(--border-radius-4);padding:var(--px-4)}.graphiql-container svg{pointer-events:none} + +/*# sourceMappingURL=graphiql.min.css.map*/ \ No newline at end of file diff --git a/graphql/internal/graphiql/graphiql.min.js b/graphql/internal/graphiql/graphiql.min.js new file mode 100644 index 0000000..73c32f3 --- /dev/null +++ b/graphql/internal/graphiql/graphiql.min.js @@ -0,0 +1,3 @@ +/*! For license information please see graphiql.min.js.LICENSE.txt */ +!function(){var e,t,n={8042:function(e,t){var n,r;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,n=function(){"use strict";function e(){const e={};return e.promise=new Promise(((t,n)=>{e.resolve=t,e.reject=n})),e}Object.defineProperty(t,"__esModule",{value:!0});const n=Symbol(),r=Symbol();function i(){let t=!0;const i=[];let o=e();const a=e(),s=async function*(){for(;;)if(i.length>0)yield i.shift();else{const e=await Promise.race([o.promise,a.promise]);if(e===n)break;if(e!==r)throw e}}();const l=s.return.bind(s);s.return=function(){return t=!1,a.resolve(n),l(...arguments)};const u=s.throw.bind(s);return s.throw=e=>(t=!1,a.resolve(e),u(e)),{pushValue:function(n){!1!==t&&(i.push(n),o.resolve(r),o=e())},asyncIterableIterator:s}}t.applyAsyncIterableIteratorToSink=function(e,t){return(async()=>{try{for await(const n of e)t.next(n);t.complete()}catch(e){t.error(e)}})(),()=>{var t;null===(t=e.return)||void 0===t||t.call(e)}},t.isAsyncIterable=function(e){return"object"==typeof e&&null!==e&&("AsyncGenerator"===e[Symbol.toStringTag]||Symbol.asyncIterator&&Symbol.asyncIterator in e)},t.makeAsyncIterableIteratorFromSink=e=>{const{pushValue:t,asyncIterableIterator:n}=i(),r=e({next:e=>{t(e)},complete:()=>{n.return()},error:e=>{n.throw(e)}}),o=n.return;let a;return n.return=()=>(void 0===a&&(r(),a=o()),a),n},t.makePushPullAsyncIterableIterator=i},void 0===(r=n.apply(t,[]))||(e.exports=r)},7674:function(e,t,n){var r,i;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,void 0===(i="function"==typeof(r=function(){"use strict";var e=Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]},r=function(t,n){for(var r in t)"default"===r||Object.prototype.hasOwnProperty.call(n,r)||e(n,t,r)};Object.defineProperty(t,"__esModule",{value:!0}),r(n(5313),t),r(n(6718),t),r(n(863),t)})?r.apply(t,[]):r)||(e.exports=i)},5609:function(e,t){var n,r;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,void 0===(r="function"==typeof(n=function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.R=e.P=void 0;var t=Object.defineProperty,n=(e,n)=>t(e,"name",{value:n,configurable:!0});class r{constructor(e,t){this.containsPosition=e=>this.start.line===e.line?this.start.character<=e.character:this.end.line===e.line?this.end.character>=e.character:this.start.line<=e.line&&this.end.line>=e.line,this.start=e,this.end=t}setStart(e,t){this.start=new i(e,t)}setEnd(e,t){this.end=new i(e,t)}}e.R=r,n(r,"Range");class i{constructor(e,t){this.lessThanOrEqualTo=e=>this.linei(e,"name",{value:t,configurable:!0});function a(e,n){const i={schema:e,type:null,parentType:null,inputType:null,directiveDef:null,fieldDef:null,argDef:null,argDefs:null,objectFieldDefs:null};return(0,r.f)(n,(n=>{var r,o;switch(n.kind){case"Query":case"ShortQuery":i.type=e.getQueryType();break;case"Mutation":i.type=e.getMutationType();break;case"Subscription":i.type=e.getSubscriptionType();break;case"InlineFragment":case"FragmentDefinition":n.type&&(i.type=e.getType(n.type));break;case"Field":case"AliasedField":i.fieldDef=i.type&&n.name?s(e,i.parentType,n.name):null,i.type=null===(r=i.fieldDef)||void 0===r?void 0:r.type;break;case"SelectionSet":i.parentType=i.type?(0,t.getNamedType)(i.type):null;break;case"Directive":i.directiveDef=n.name?e.getDirective(n.name):null;break;case"Arguments":const a=n.prevState?"Field"===n.prevState.kind?i.fieldDef:"Directive"===n.prevState.kind?i.directiveDef:"AliasedField"===n.prevState.kind?n.prevState.name&&s(e,i.parentType,n.prevState.name):null:null;i.argDefs=a?a.args:null;break;case"Argument":if(i.argDef=null,i.argDefs)for(let e=0;ee.value===n.name)):null;break;case"ListValue":const c=i.inputType?(0,t.getNullableType)(i.inputType):null;i.inputType=c instanceof t.GraphQLList?c.ofType:null;break;case"ObjectValue":const d=i.inputType?(0,t.getNamedType)(i.inputType):null;i.objectFieldDefs=d instanceof t.GraphQLInputObjectType?d.getFields():null;break;case"ObjectField":const f=n.name&&i.objectFieldDefs?i.objectFieldDefs[n.name]:null;i.inputType=null==f?void 0:f.type;break;case"NamedType":i.type=n.name?e.getType(n.name):null}})),i}function s(e,r,i){return i===n.S.name&&e.getQueryType()===r?n.S:i===n.T.name&&e.getQueryType()===r?n.T:i===n.a.name&&(0,t.isCompositeType)(r)?n.a:r&&r.getFields?r.getFields()[i]:void 0}function l(e,t){for(let n=0;nn(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};!function(e){function t(t){return function(n,i){var o=i.line,a=n.getLine(o);function s(t){for(var r,s=i.ch,l=0;;){var u=s<=0?-1:a.lastIndexOf(t[0],s-1);if(-1!=u){if(1==l&&ut.lastLine())return null;var r=t.getTokenAt(e.Pos(n,1));if(/\S/.test(r.string)||(r=t.getTokenAt(e.Pos(n,r.end+1))),"keyword"!=r.type||"import"!=r.string)return null;for(var i=n,o=Math.min(t.lastLine(),n+10);i<=o;++i){var a=t.getLine(i).indexOf(";");if(-1!=a)return{startCh:r.end,end:e.Pos(i,a)}}}r(i,"hasImport");var o,a=n.line,s=i(a);if(!s||i(a-1)||(o=i(a-2))&&o.end.line==a-1)return null;for(var l=s.end;;){var u=i(l.line+1);if(null==u)break;l=u.end}return{from:t.clipPos(e.Pos(a,s.startCh+1)),to:l}})),e.registerHelper("fold","include",(function(t,n){function i(n){if(nt.lastLine())return null;var r=t.getTokenAt(e.Pos(n,1));return/\S/.test(r.string)||(r=t.getTokenAt(e.Pos(n,r.end+1))),"meta"==r.type&&"#include"==r.string.slice(0,8)?r.start+8:void 0}r(i,"hasInclude");var o=n.line,a=i(o);if(null==a||null!=i(o-1))return null;for(var s=o;null!=i(s+1);)++s;return{from:e.Pos(o,a+1),to:t.clipPos(e.Pos(s))}}))}(t.a.exports);var a=i({__proto__:null,default:o.exports},[o.exports]);e.b=a})?r.apply(t,i):r)||(e.exports=o)},5728:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535)],void 0===(o="function"==typeof(r=function(e,t){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.c=void 0;var n=Object.defineProperty,r=(e,t)=>n(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};!function(e){var t={pairs:"()[]{}''\"\"",closeBefore:")]}'\":;>",triples:"",explode:"[]{}"},n=e.Pos;function i(e,n){return"pairs"==n&&"string"==typeof e?e:"object"==typeof e&&null!=e[n]?e[n]:t[n]}e.defineOption("autoCloseBrackets",!1,(function(t,n,r){r&&r!=e.Init&&(t.removeKeyMap(o),t.state.closeBrackets=null),n&&(a(i(n,"pairs")),t.state.closeBrackets=n,t.addKeyMap(o))})),r(i,"getOption");var o={Backspace:u,Enter:c};function a(e){for(var t=0;t=0;s--){var c=a[s].head;t.replaceRange("",n(c.line,c.ch-1),n(c.line,c.ch+1),"+delete")}}function c(t){var n=l(t),r=n&&i(n,"explode");if(!r||t.getOption("disableInput"))return e.Pass;for(var o=t.listSelections(),a=0;a0?{line:a.head.line,ch:a.head.ch+t}:{line:a.head.line-1};n.push({anchor:s,head:s})}e.setSelections(n,i)}function f(t){var r=e.cmpPos(t.anchor,t.head)>0;return{anchor:new n(t.anchor.line,t.anchor.ch+(r?-1:1)),head:new n(t.head.line,t.head.ch+(r?1:-1))}}function p(t,r){var o=l(t);if(!o||t.getOption("disableInput"))return e.Pass;var a=i(o,"pairs"),s=a.indexOf(r);if(-1==s)return e.Pass;for(var u,c=i(o,"closeBefore"),p=i(o,"triples"),h=a.charAt(s+1)==r,g=t.listSelections(),v=s%2==0,y=0;y1&&p.indexOf(r)>=0&&t.getRange(n(T.line,T.ch-2),T)==r+r){if(T.ch>2&&/\bstring/.test(t.getTokenTypeAt(n(T.line,T.ch-2))))return e.Pass;b="addFour"}else if(h){var C=0==T.ch?" ":t.getRange(n(T.line,T.ch-1),T);if(e.isWordChar(w)||C==r||e.isWordChar(C))return e.Pass;b="both"}else{if(!v||!(0===w.length||/\s/.test(w)||c.indexOf(w)>-1))return e.Pass;b="both"}else b=h&&m(t,T)?"both":p.indexOf(r)>=0&&t.getRange(T,n(T.line,T.ch+3))==r+r+r?"skipThree":"skip";if(u){if(u!=b)return e.Pass}else u=b}var S=s%2?a.charAt(s-1):r,x=s%2?r:a.charAt(s+1);t.operation((function(){if("skip"==u)d(t,1);else if("skipThree"==u)d(t,3);else if("surround"==u){for(var e=t.getSelections(),n=0;nn(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};e.a=o,function(e,n){t.c,e.exports=function(){var e=navigator.userAgent,t=navigator.platform,n=/gecko\/\d/i.test(e),i=/MSIE \d/.test(e),o=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e),a=/Edge\/(\d+)/.exec(e),s=i||o||a,l=s&&(i?document.documentMode||6:+(a||o)[1]),u=!a&&/WebKit\//.test(e),c=u&&/Qt\/\d+\.\d+/.test(e),d=!a&&/Chrome\//.test(e),f=/Opera\//.test(e),p=/Apple Computer/.test(navigator.vendor),h=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e),m=/PhantomJS/.test(e),g=p&&(/Mobile\/\w+/.test(e)||navigator.maxTouchPoints>2),v=/Android/.test(e),y=g||v||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e),b=g||/Mac/.test(t),E=/\bCrOS\b/.test(e),T=/win/i.test(t),w=f&&e.match(/Version\/(\d*\.\d*)/);w&&(w=Number(w[1])),w&&w>=15&&(f=!1,u=!0);var C=b&&(c||f&&(null==w||w<12.11)),S=n||s&&l>=9;function x(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}r(x,"classTest");var k,N=r((function(e,t){var n=e.className,r=x(t).exec(n);if(r){var i=n.slice(r.index+r[0].length);e.className=n.slice(0,r.index)+(i?r[1]+i:"")}}),"rmClass");function _(e){for(var t=e.childNodes.length;t>0;--t)e.removeChild(e.firstChild);return e}function O(e,t){return _(e).appendChild(t)}function I(e,t,n,r){var i=document.createElement(e);if(n&&(i.className=n),r&&(i.style.cssText=r),"string"==typeof t)i.appendChild(document.createTextNode(t));else if(t)for(var o=0;o=t)return a+(t-o);a+=s-o,a+=n-a%n,o=s+1}}g?F=r((function(e){e.selectionStart=0,e.selectionEnd=e.value.length}),"selectInput"):s&&(F=r((function(e){try{e.select()}catch(e){}}),"selectInput")),r(P,"bind"),r(j,"copyObj"),r(V,"countColumn");var U=r((function(){this.id=null,this.f=null,this.time=0,this.handler=P(this.onTimeout,this)}),"Delayed");function B(e,t){for(var n=0;n=t)return r+Math.min(a,t-i);if(i+=o-r,r=o+1,(i+=n-i%n)>=t)return r}}r(W,"findColumn");var K=[""];function Q(e){for(;K.length<=e;)K.push(X(K)+" ");return K[e]}function X(e){return e[e.length-1]}function Y(e,t){for(var n=[],r=0;r"€"&&(e.toUpperCase()!=e.toLowerCase()||te.test(e))}function re(e,t){return t?!!(t.source.indexOf("\\w")>-1&&ne(e))||t.test(e):ne(e)}function ie(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}r(ne,"isWordCharBasic"),r(re,"isWordChar"),r(ie,"isEmpty");var oe=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function ae(e){return e.charCodeAt(0)>=768&&oe.test(e)}function se(e,t,n){for(;(n<0?t>0:tn?-1:1;;){if(t==n)return t;var i=(t+n)/2,o=r<0?Math.ceil(i):Math.floor(i);if(o==t)return e(o)?t:n;e(o)?n=o:t=o+r}}function ue(e,t,n,r){if(!e)return r(t,n,"ltr",0);for(var i=!1,o=0;ot||t==n&&a.to==t)&&(r(Math.max(a.from,t),Math.min(a.to,n),1==a.level?"rtl":"ltr",o),i=!0)}i||r(t,n,"ltr")}r(ae,"isExtendingChar"),r(se,"skipExtendingChars"),r(le,"findFirst"),r(ue,"iterateBidiSections");var ce=null;function de(e,t,n){var r;ce=null;for(var i=0;it)return i;o.to==t&&(o.from!=o.to&&"before"==n?r=i:ce=i),o.from==t&&(o.from!=o.to&&"before"!=n?r=i:ce=i)}return null!=r?r:ce}r(de,"getBidiPartAt");var fe=function(){var e="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN",t="nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";function n(n){return n<=247?e.charAt(n):1424<=n&&n<=1524?"R":1536<=n&&n<=1785?t.charAt(n-1536):1774<=n&&n<=2220?"r":8192<=n&&n<=8203?"w":8204==n?"b":"L"}r(n,"charType");var i=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,o=/[stwN]/,a=/[LRr]/,s=/[Lb1n]/,l=/[1n]/;function u(e,t,n){this.level=e,this.from=t,this.to=n}return r(u,"BidiSpan"),function(e,t){var r="ltr"==t?"L":"R";if(0==e.length||"ltr"==t&&!i.test(e))return!1;for(var c=e.length,d=[],f=0;f-1&&(r[t]=i.slice(0,o).concat(i.slice(o+1)))}}}function ye(e,t){var n=ge(e,t);if(n.length)for(var r=Array.prototype.slice.call(arguments,2),i=0;i0}function we(e){e.prototype.on=function(e,t){me(this,e,t)},e.prototype.off=function(e,t){ve(this,e,t)}}function Ce(e){e.preventDefault?e.preventDefault():e.returnValue=!1}function Se(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}function xe(e){return null!=e.defaultPrevented?e.defaultPrevented:0==e.returnValue}function ke(e){Ce(e),Se(e)}function Ne(e){return e.target||e.srcElement}function _e(e){var t=e.which;return null==t&&(1&e.button?t=1:2&e.button?t=3:4&e.button&&(t=2)),b&&e.ctrlKey&&1==t&&(t=3),t}r(ge,"getHandlers"),r(ve,"off"),r(ye,"signal"),r(be,"signalDOMEvent"),r(Ee,"signalCursorActivity"),r(Te,"hasHandler"),r(we,"eventMixin"),r(Ce,"e_preventDefault"),r(Se,"e_stopPropagation"),r(xe,"e_defaultPrevented"),r(ke,"e_stop"),r(Ne,"e_target"),r(_e,"e_button");var Oe,Ie,De=function(){if(s&&l<9)return!1;var e=I("div");return"draggable"in e||"dragDrop"in e}();function Le(e){if(null==Oe){var t=I("span","​");O(e,I("span",[t,document.createTextNode("x")])),0!=e.firstChild.offsetHeight&&(Oe=t.offsetWidth<=1&&t.offsetHeight>2&&!(s&&l<8))}var n=Oe?I("span","​"):I("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");return n.setAttribute("cm-text",""),n}function Ae(e){if(null!=Ie)return Ie;var t=O(e,document.createTextNode("AØ®A")),n=k(t,0,1).getBoundingClientRect(),r=k(t,1,2).getBoundingClientRect();return _(e),!(!n||n.left==n.right)&&(Ie=r.right-n.right<3)}r(Le,"zeroWidthElement"),r(Ae,"hasBadBidiRects");var Me,Re=3!="\n\nb".split(/\n/).length?function(e){for(var t=0,n=[],r=e.length;t<=r;){var i=e.indexOf("\n",t);-1==i&&(i=e.length);var o=e.slice(t,"\r"==e.charAt(i-1)?i-1:i),a=o.indexOf("\r");-1!=a?(n.push(o.slice(0,a)),t+=a+1):(n.push(o),t=i+1)}return n}:function(e){return e.split(/\r\n?|\n/)},Fe=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch(e){return!1}}:function(e){var t;try{t=e.ownerDocument.selection.createRange()}catch(e){}return!(!t||t.parentElement()!=e)&&0!=t.compareEndPoints("StartToEnd",t)},Pe="oncopy"in(Me=I("div"))||(Me.setAttribute("oncopy","return;"),"function"==typeof Me.oncopy),je=null;function Ve(e){if(null!=je)return je;var t=O(e,I("span","x")),n=t.getBoundingClientRect(),r=k(t,0,1).getBoundingClientRect();return je=Math.abs(n.left-r.left)>1}r(Ve,"hasBadZoomedRects");var Ue={},Be={};function $e(e,t){arguments.length>2&&(t.dependencies=Array.prototype.slice.call(arguments,2)),Ue[e]=t}function qe(e,t){Be[e]=t}function He(e){if("string"==typeof e&&Be.hasOwnProperty(e))e=Be[e];else if(e&&"string"==typeof e.name&&Be.hasOwnProperty(e.name)){var t=Be[e.name];"string"==typeof t&&(t={name:t}),(e=ee(t,e)).name=t.name}else{if("string"==typeof e&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e))return He("application/xml");if("string"==typeof e&&/^[\w\-]+\/[\w\-]+\+json$/.test(e))return He("application/json")}return"string"==typeof e?{name:e}:e||{name:"null"}}function Ge(e,t){t=He(t);var n=Ue[t.name];if(!n)return Ge(e,"text/plain");var r=n(e,t);if(ze.hasOwnProperty(t.name)){var i=ze[t.name];for(var o in i)i.hasOwnProperty(o)&&(r.hasOwnProperty(o)&&(r["_"+o]=r[o]),r[o]=i[o])}if(r.name=t.name,t.helperType&&(r.helperType=t.helperType),t.modeProps)for(var a in t.modeProps)r[a]=t.modeProps[a];return r}r($e,"defineMode"),r(qe,"defineMIME"),r(He,"resolveMode"),r(Ge,"getMode");var ze={};function We(e,t){j(t,ze.hasOwnProperty(e)?ze[e]:ze[e]={})}function Ke(e,t){if(!0===t)return t;if(e.copyState)return e.copyState(t);var n={};for(var r in t){var i=t[r];i instanceof Array&&(i=i.concat([])),n[r]=i}return n}function Qe(e,t){for(var n;e.innerMode&&(n=e.innerMode(t))&&n.mode!=e;)t=n.state,e=n.mode;return n||{mode:e,state:t}}function Xe(e,t,n){return!e.startState||e.startState(t,n)}r(We,"extendMode"),r(Ke,"copyState"),r(Qe,"innerMode"),r(Xe,"startState");var Ye=r((function(e,t,n){this.pos=this.start=0,this.string=e,this.tabSize=t||8,this.lastColumnPos=this.lastColumnValue=0,this.lineStart=0,this.lineOracle=n}),"StringStream");function Je(e,t){if((t-=e.first)<0||t>=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var r=0;;++r){var i=n.children[r],o=i.chunkSize();if(t=e.first&&tn?at(n,Je(e,n).text.length):ht(t,Je(e,t.line).text.length)}function ht(e,t){var n=e.ch;return null==n||n>t?at(e.line,t):n<0?at(e.line,0):e}function mt(e,t){for(var n=[],r=0;r=this.string.length},Ye.prototype.sol=function(){return this.pos==this.lineStart},Ye.prototype.peek=function(){return this.string.charAt(this.pos)||void 0},Ye.prototype.next=function(){if(this.post},Ye.prototype.eatSpace=function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},Ye.prototype.skipToEnd=function(){this.pos=this.string.length},Ye.prototype.skipTo=function(e){var t=this.string.indexOf(e,this.pos);if(t>-1)return this.pos=t,!0},Ye.prototype.backUp=function(e){this.pos-=e},Ye.prototype.column=function(){return this.lastColumnPos0?null:(i&&!1!==t&&(this.pos+=i[0].length),i)}var o=r((function(e){return n?e.toLowerCase():e}),"cased");if(o(this.string.substr(this.pos,e.length))==o(e))return!1!==t&&(this.pos+=e.length),!0},Ye.prototype.current=function(){return this.string.slice(this.start,this.pos)},Ye.prototype.hideFirstChars=function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}},Ye.prototype.lookAhead=function(e){var t=this.lineOracle;return t&&t.lookAhead(e)},Ye.prototype.baseToken=function(){var e=this.lineOracle;return e&&e.baseToken(this.pos)},r(Je,"getLine"),r(Ze,"getBetween"),r(et,"getLines"),r(tt,"updateLineHeight"),r(nt,"lineNo"),r(rt,"lineAtHeight"),r(it,"isLine"),r(ot,"lineNumberFor"),r(at,"Pos"),r(st,"cmp"),r(lt,"equalCursorPos"),r(ut,"copyPos"),r(ct,"maxPos"),r(dt,"minPos"),r(ft,"clipLine"),r(pt,"clipPos"),r(ht,"clipToLen"),r(mt,"clipPosArray");var gt=r((function(e,t){this.state=e,this.lookAhead=t}),"SavedContext"),vt=r((function(e,t,n,r){this.state=t,this.doc=e,this.line=n,this.maxLookAhead=r||0,this.baseTokens=null,this.baseTokenPos=1}),"Context");function yt(e,t,n,i){var o=[e.state.modeGen],a={};Nt(e,t.text,e.doc.mode,n,(function(e,t){return o.push(e,t)}),a,i);for(var s=n.state,l=r((function(r){n.baseTokens=o;var i=e.state.overlays[r],l=1,u=0;n.state=!0,Nt(e,t.text,i.mode,n,(function(e,t){for(var n=l;ue&&o.splice(l,1,e,o[l+1],r),l+=2,u=Math.min(e,r)}if(t)if(i.opaque)o.splice(n,l-n,e,"overlay "+t),l=n+2;else for(;ne.options.maxHighlightLength&&Ke(e.doc.mode,r.state),o=yt(e,t,r);i&&(r.state=i),t.stateAfter=r.save(!i),t.styles=o.styles,o.classes?t.styleClasses=o.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))}return t.styles}function Et(e,t,n){var r=e.doc,i=e.display;if(!r.mode.startState)return new vt(r,!0,t);var o=_t(e,t,n),a=o>r.first&&Je(r,o-1).stateAfter,s=a?vt.fromSaved(r,a,o):new vt(r,Xe(r.mode),o);return r.iter(o,t,(function(n){Tt(e,n.text,s);var r=s.line;n.stateAfter=r==t-1||r%5==0||r>=i.viewFrom&&rt.start)return o}throw new Error("Mode "+e.name+" failed to advance stream.")}vt.prototype.lookAhead=function(e){var t=this.doc.getLine(this.line+e);return null!=t&&e>this.maxLookAhead&&(this.maxLookAhead=e),t},vt.prototype.baseToken=function(e){if(!this.baseTokens)return null;for(;this.baseTokens[this.baseTokenPos]<=e;)this.baseTokenPos+=2;var t=this.baseTokens[this.baseTokenPos+1];return{type:t&&t.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}},vt.prototype.nextLine=function(){this.line++,this.maxLookAhead>0&&this.maxLookAhead--},vt.fromSaved=function(e,t,n){return t instanceof gt?new vt(e,Ke(e.mode,t.state),n,t.lookAhead):new vt(e,Ke(e.mode,t),n)},vt.prototype.save=function(e){var t=!1!==e?Ke(this.doc.mode,this.state):this.state;return this.maxLookAhead>0?new gt(t,this.maxLookAhead):t},r(yt,"highlightLine"),r(bt,"getLineStyles"),r(Et,"getContextBefore"),r(Tt,"processLine"),r(wt,"callBlankLine"),r(Ct,"readToken");var St=r((function(e,t,n){this.start=e.start,this.end=e.pos,this.string=e.current(),this.type=t||null,this.state=n}),"Token");function xt(e,t,n,r){var i,o,a=e.doc,s=a.mode,l=Je(a,(t=pt(a,t)).line),u=Et(e,t.line,n),c=new Ye(l.text,e.options.tabSize,u);for(r&&(o=[]);(r||c.pose.options.maxHighlightLength?(s=!1,a&&Tt(e,t,r,d.pos),d.pos=t.length,l=null):l=kt(Ct(n,d,r.state,f),o),f){var p=f[0].name;p&&(l="m-"+(l?p+" "+l:p))}if(!s||c!=l){for(;ua;--s){if(s<=o.first)return o.first;var l=Je(o,s-1),u=l.stateAfter;if(u&&(!n||s+(u instanceof gt?u.lookAhead:0)<=o.modeFrontier))return s;var c=V(l.text,null,e.options.tabSize);(null==i||r>c)&&(i=s-1,r=c)}return i}function Ot(e,t){if(e.modeFrontier=Math.min(e.modeFrontier,t),!(e.highlightFrontiern;r--){var i=Je(e,r).stateAfter;if(i&&(!(i instanceof gt)||r+i.lookAhead=t:o.to>t);(r||(r=[])).push(new Mt(a,o.from,s?null:o.to))}}return r}function Vt(e,t,n){var r;if(e)for(var i=0;i=t:o.to>t)||o.from==t&&"bookmark"==a.type&&(!n||o.marker.insertLeft)){var s=null==o.from||(a.inclusiveLeft?o.from<=t:o.from0&&s)for(var b=0;b0)){var c=[l,1],d=st(u.from,s.from),f=st(u.to,s.to);(d<0||!a.inclusiveLeft&&!d)&&c.push({from:u.from,to:s.from}),(f>0||!a.inclusiveRight&&!f)&&c.push({from:s.to,to:u.to}),i.splice.apply(i,c),l+=c.length-3}}return i}function qt(e){var t=e.markedSpans;if(t){for(var n=0;nt)&&(!n||Wt(n,o.marker)<0)&&(n=o.marker)}return n}function Jt(e,t,n,r,i){var o=Je(e,t),a=Dt&&o.markedSpans;if(a)for(var s=0;s=0&&d<=0||c<=0&&d>=0)&&(c<=0&&(l.marker.inclusiveRight&&i.inclusiveLeft?st(u.to,n)>=0:st(u.to,n)>0)||c>=0&&(l.marker.inclusiveRight&&i.inclusiveLeft?st(u.from,r)<=0:st(u.from,r)<0)))return!0}}}function Zt(e){for(var t;t=Qt(e);)e=t.find(-1,!0).line;return e}function en(e){for(var t;t=Xt(e);)e=t.find(1,!0).line;return e}function tn(e){for(var t,n;t=Xt(e);)e=t.find(1,!0).line,(n||(n=[])).push(e);return n}function nn(e,t){var n=Je(e,t),r=Zt(n);return n==r?t:nt(r)}function rn(e,t){if(t>e.lastLine())return t;var n,r=Je(e,t);if(!on(e,r))return t;for(;n=Xt(r);)r=n.find(1,!0).line;return nt(r)+1}function on(e,t){var n=Dt&&t.markedSpans;if(n)for(var r=void 0,i=0;it.maxLineLength&&(t.maxLineLength=n,t.maxLine=e)}))}r(Lt,"seeReadOnlySpans"),r(At,"seeCollapsedSpans"),r(Mt,"MarkedSpan"),r(Rt,"getMarkedSpanFor"),r(Ft,"removeMarkedSpan"),r(Pt,"addMarkedSpan"),r(jt,"markedSpansBefore"),r(Vt,"markedSpansAfter"),r(Ut,"stretchSpansOverChange"),r(Bt,"clearEmptySpans"),r($t,"removeReadOnlyRanges"),r(qt,"detachMarkedSpans"),r(Ht,"attachMarkedSpans"),r(Gt,"extraLeft"),r(zt,"extraRight"),r(Wt,"compareCollapsedMarkers"),r(Kt,"collapsedSpanAtSide"),r(Qt,"collapsedSpanAtStart"),r(Xt,"collapsedSpanAtEnd"),r(Yt,"collapsedSpanAround"),r(Jt,"conflictingCollapsedRange"),r(Zt,"visualLine"),r(en,"visualLineEnd"),r(tn,"visualLineContinued"),r(nn,"visualLineNo"),r(rn,"visualLineEndNo"),r(on,"lineIsHidden"),r(an,"lineIsHiddenInner"),r(sn,"heightAtLine"),r(ln,"lineLength"),r(un,"findMaxLine");var cn=r((function(e,t,n){this.text=e,Ht(this,t),this.height=n?n(this):1}),"Line");function dn(e,t,n,r){e.text=t,e.stateAfter&&(e.stateAfter=null),e.styles&&(e.styles=null),null!=e.order&&(e.order=null),qt(e),Ht(e,n);var i=r?r(e):1;i!=e.height&&tt(e,i)}function fn(e){e.parent=null,qt(e)}cn.prototype.lineNo=function(){return nt(this)},we(cn),r(dn,"updateLine"),r(fn,"cleanUpLine");var pn={},hn={};function mn(e,t){if(!e||/^\s*$/.test(e))return null;var n=t.addModeClass?hn:pn;return n[e]||(n[e]=e.replace(/\S+/g,"cm-$&"))}function gn(e,t){var n=D("span",null,null,u?"padding-right: .1px":null),r={pre:D("pre",[n],"CodeMirror-line"),content:n,col:0,pos:0,cm:e,trailingSpace:!1,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var i=0;i<=(t.rest?t.rest.length:0);i++){var o=i?t.rest[i-1]:t.line,a=void 0;r.pos=0,r.addToken=yn,Ae(e.display.measure)&&(a=pe(o,e.doc.direction))&&(r.addToken=En(r.addToken,a)),r.map=[],wn(o,r,bt(e,o,t!=e.display.externalMeasured&&nt(o))),o.styleClasses&&(o.styleClasses.bgClass&&(r.bgClass=R(o.styleClasses.bgClass,r.bgClass||"")),o.styleClasses.textClass&&(r.textClass=R(o.styleClasses.textClass,r.textClass||""))),0==r.map.length&&r.map.push(0,0,r.content.appendChild(Le(e.display.measure))),0==i?(t.measure.map=r.map,t.measure.cache={}):((t.measure.maps||(t.measure.maps=[])).push(r.map),(t.measure.caches||(t.measure.caches=[])).push({}))}if(u){var s=r.content.lastChild;(/\bcm-tab\b/.test(s.className)||s.querySelector&&s.querySelector(".cm-tab"))&&(r.content.className="cm-tab-wrap-hack")}return ye(e,"renderLine",e,t.line,r.pre),r.pre.className&&(r.textClass=R(r.pre.className,r.textClass||"")),r}function vn(e){var t=I("span","•","cm-invalidchar");return t.title="\\u"+e.charCodeAt(0).toString(16),t.setAttribute("aria-label",t.title),t}function yn(e,t,n,r,i,o,a){if(t){var u,c=e.splitSpaces?bn(t,e.trailingSpace):t,d=e.cm.state.specialChars,f=!1;if(d.test(t)){u=document.createDocumentFragment();for(var p=0;;){d.lastIndex=p;var h=d.exec(t),m=h?h.index-p:t.length-p;if(m){var g=document.createTextNode(c.slice(p,p+m));s&&l<9?u.appendChild(I("span",[g])):u.appendChild(g),e.map.push(e.pos,e.pos+m,g),e.col+=m,e.pos+=m}if(!h)break;p+=m+1;var v=void 0;if("\t"==h[0]){var y=e.cm.options.tabSize,b=y-e.col%y;(v=u.appendChild(I("span",Q(b),"cm-tab"))).setAttribute("role","presentation"),v.setAttribute("cm-text","\t"),e.col+=b}else"\r"==h[0]||"\n"==h[0]?((v=u.appendChild(I("span","\r"==h[0]?"â":"â¤","cm-invalidchar"))).setAttribute("cm-text",h[0]),e.col+=1):((v=e.cm.options.specialCharPlaceholder(h[0])).setAttribute("cm-text",h[0]),s&&l<9?u.appendChild(I("span",[v])):u.appendChild(v),e.col+=1);e.map.push(e.pos,e.pos+1,v),e.pos++}}else e.col+=t.length,u=document.createTextNode(c),e.map.push(e.pos,e.pos+t.length,u),s&&l<9&&(f=!0),e.pos+=t.length;if(e.trailingSpace=32==c.charCodeAt(t.length-1),n||r||i||f||o||a){var E=n||"";r&&(E+=r),i&&(E+=i);var T=I("span",[u],E,o);if(a)for(var w in a)a.hasOwnProperty(w)&&"style"!=w&&"class"!=w&&T.setAttribute(w,a[w]);return e.content.appendChild(T)}e.content.appendChild(u)}}function bn(e,t){if(e.length>1&&!/ /.test(e))return e;for(var n=t,r="",i=0;iu&&d.from<=u);f++);if(d.to>=c)return e(n,r,i,o,a,s,l);e(n,r.slice(0,d.to-u),i,o,null,s,l),o=null,r=r.slice(d.to-u),u=d.to}}}function Tn(e,t,n,r){var i=!r&&n.widgetNode;i&&e.map.push(e.pos,e.pos+t,i),!r&&e.cm.display.input.needsContentAttribute&&(i||(i=e.content.appendChild(document.createElement("span"))),i.setAttribute("cm-marker",n.id)),i&&(e.cm.display.input.setUneditable(i),e.content.appendChild(i)),e.pos+=t,e.trailingSpace=!1}function wn(e,t,n){var r=e.markedSpans,i=e.text,o=0;if(r)for(var a,s,l,u,c,d,f,p=i.length,h=0,m=1,g="",v=0;;){if(v==h){l=u=c=s="",f=null,d=null,v=1/0;for(var y=[],b=void 0,E=0;Eh||w.collapsed&&T.to==h&&T.from==h)){if(null!=T.to&&T.to!=h&&v>T.to&&(v=T.to,u=""),w.className&&(l+=" "+w.className),w.css&&(s=(s?s+";":"")+w.css),w.startStyle&&T.from==h&&(c+=" "+w.startStyle),w.endStyle&&T.to==v&&(b||(b=[])).push(w.endStyle,T.to),w.title&&((f||(f={})).title=w.title),w.attributes)for(var C in w.attributes)(f||(f={}))[C]=w.attributes[C];w.collapsed&&(!d||Wt(d.marker,w)<0)&&(d=T)}else T.from>h&&v>T.from&&(v=T.from)}if(b)for(var S=0;S=p)break;for(var k=Math.min(p,v);;){if(g){var N=h+g.length;if(!d){var _=N>k?g.slice(0,k-h):g;t.addToken(t,_,a?a+l:l,c,h+_.length==v?u:"",s,f)}if(N>=k){g=g.slice(k-h),h=k;break}h=N,c=""}g=i.slice(o,o=n[m++]),a=mn(n[m++],t.cm.options)}}else for(var O=1;O2&&o.push((l.bottom+u.top)/2-n.top)}}o.push(n.bottom-n.top)}}function Zn(e,t,n){if(e.line==t)return{map:e.measure.map,cache:e.measure.cache};if(e.rest){for(var r=0;rn)return{map:e.measure.maps[i],cache:e.measure.caches[i],before:!0}}}function er(e,t){var n=nt(t=Zt(t)),r=e.display.externalMeasured=new Cn(e.doc,t,n);r.lineN=n;var i=r.built=gn(e,r);return r.text=i.pre,O(e.display.lineMeasure,i.pre),r}function tr(e,t,n,r){return ir(e,rr(e,t),n,r)}function nr(e,t){if(t>=e.display.viewFrom&&t=n.lineN&&tt)&&(i=(o=l-s)-1,t>=l&&(a="right")),null!=i){if(r=e[u+2],s==l&&n==(r.insertLeft?"left":"right")&&(a=n),"left"==n&&0==i)for(;u&&e[u-2]==e[u-3]&&e[u-1].insertLeft;)r=e[2+(u-=3)],a="left";if("right"==n&&i==l-s)for(;u=0&&(n=e[i]).left==n.right;i--);return n}function ur(e,t,n,r){var i,o=sr(t.map,n,r),a=o.node,u=o.start,c=o.end,d=o.collapse;if(3==a.nodeType){for(var f=0;f<4;f++){for(;u&&ae(t.line.text.charAt(o.coverStart+u));)--u;for(;o.coverStart+c0&&(d=r="right"),i=e.options.lineWrapping&&(p=a.getClientRects()).length>1?p["right"==r?p.length-1:0]:a.getBoundingClientRect()}if(s&&l<9&&!u&&(!i||!i.left&&!i.right)){var h=a.parentNode.getClientRects()[0];i=h?{left:h.left,right:h.left+Dr(e.display),top:h.top,bottom:h.bottom}:ar}for(var m=i.top-t.rect.top,g=i.bottom-t.rect.top,v=(m+g)/2,y=t.view.measure.heights,b=0;b=i.text.length?(u=i.text.length,c="before"):u<=0&&(u=0,c="after"),!l)return s("before"==c?u-1:u,"before"==c);function d(e,t,n){return s(n?e-1:e,1==l[t].level!=n)}r(d,"getBidi");var f=de(l,u,c),p=ce,h=d(u,f,"before"==c);return null!=p&&(h.other=d(u,p,"before"!=c)),h}function Tr(e,t){var n=0;t=pt(e.doc,t),e.options.lineWrapping||(n=Dr(e.display)*t.ch);var r=Je(e.doc,t.line),i=sn(r)+zn(e.display);return{left:n,right:n,top:i,bottom:i+r.height}}function wr(e,t,n,r,i){var o=at(e,t,n);return o.xRel=i,r&&(o.outside=r),o}function Cr(e,t,n){var r=e.doc;if((n+=e.display.viewOffset)<0)return wr(r.first,0,null,-1,-1);var i=rt(r,n),o=r.first+r.size-1;if(i>o)return wr(r.first+r.size-1,Je(r,o).text.length,null,1,1);t<0&&(t=0);for(var a=Je(r,i);;){var s=Nr(e,a,i,t,n),l=Yt(a,s.ch+(s.xRel>0||s.outside>0?1:0));if(!l)return s;var u=l.find(1);if(u.line==i)return u;a=Je(r,i=u.line)}}function Sr(e,t,n,r){r-=gr(t);var i=t.text.length,o=le((function(t){return ir(e,n,t-1).bottom<=r}),i,0);return{begin:o,end:i=le((function(t){return ir(e,n,t).top>r}),o,i)}}function xr(e,t,n,r){return n||(n=rr(e,t)),Sr(e,t,n,vr(e,t,ir(e,n,r),"line").top)}function kr(e,t,n,r){return!(e.bottom<=n)&&(e.top>n||(r?e.left:e.right)>t)}function Nr(e,t,n,r,i){i-=sn(t);var o=rr(e,t),a=gr(t),s=0,l=t.text.length,u=!0,c=pe(t,e.doc.direction);if(c){var d=(e.options.lineWrapping?Or:_r)(e,t,n,o,c,r,i);s=(u=1!=d.level)?d.from:d.to-1,l=u?d.to:d.from-1}var f,p,h=null,m=null,g=le((function(t){var n=ir(e,o,t);return n.top+=a,n.bottom+=a,!!kr(n,r,i,!1)&&(n.top<=i&&n.left<=r&&(h=t,m=n),!0)}),s,l),v=!1;if(m){var y=r-m.left=E.bottom?1:0}return wr(n,g=se(t.text,g,1),p,v,r-f)}function _r(e,t,n,r,i,o,a){var s=le((function(s){var l=i[s],u=1!=l.level;return kr(Er(e,at(n,u?l.to:l.from,u?"before":"after"),"line",t,r),o,a,!0)}),0,i.length-1),l=i[s];if(s>0){var u=1!=l.level,c=Er(e,at(n,u?l.from:l.to,u?"after":"before"),"line",t,r);kr(c,o,a,!0)&&c.top>a&&(l=i[s-1])}return l}function Or(e,t,n,r,i,o,a){var s=Sr(e,t,r,a),l=s.begin,u=s.end;/\s/.test(t.text.charAt(u-1))&&u--;for(var c=null,d=null,f=0;f=u||p.to<=l)){var h=ir(e,r,1!=p.level?Math.min(u,p.to)-1:Math.max(l,p.from)).right,m=hm)&&(c=p,d=m)}}return c||(c=i[i.length-1]),c.fromu&&(c={from:c.from,to:u,level:c.level}),c}function Ir(e){if(null!=e.cachedTextHeight)return e.cachedTextHeight;if(null==or){or=I("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t)or.appendChild(document.createTextNode("x")),or.appendChild(I("br"));or.appendChild(document.createTextNode("x"))}O(e.measure,or);var n=or.offsetHeight/50;return n>3&&(e.cachedTextHeight=n),_(e.measure),n||1}function Dr(e){if(null!=e.cachedCharWidth)return e.cachedCharWidth;var t=I("span","xxxxxxxxxx"),n=I("pre",[t],"CodeMirror-line-like");O(e.measure,n);var r=t.getBoundingClientRect(),i=(r.right-r.left)/10;return i>2&&(e.cachedCharWidth=i),i||10}function Lr(e){for(var t=e.display,n={},r={},i=t.gutters.clientLeft,o=t.gutters.firstChild,a=0;o;o=o.nextSibling,++a){var s=e.display.gutterSpecs[a].className;n[s]=o.offsetLeft+o.clientLeft+i,r[s]=o.clientWidth}return{fixedPos:Ar(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:n,gutterWidth:r,wrapperWidth:t.wrapper.clientWidth}}function Ar(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function Mr(e){var t=Ir(e.display),n=e.options.lineWrapping,r=n&&Math.max(5,e.display.scroller.clientWidth/Dr(e.display)-3);return function(i){if(on(e.doc,i))return 0;var o=0;if(i.widgets)for(var a=0;a0&&(l=Je(e.doc,u.line).text).length==u.ch){var c=V(l,l.length,e.options.tabSize)-l.length;u=at(u.line,Math.max(0,Math.round((o-Kn(e.display).left)/Dr(e.display))-c))}return u}function Pr(e,t){if(t>=e.display.viewTo)return null;if((t-=e.display.viewFrom)<0)return null;for(var n=e.display.view,r=0;rt)&&(i.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=i.viewTo)Dt&&nn(e.doc,t)i.viewFrom?Ur(e):(i.viewFrom+=r,i.viewTo+=r);else if(t<=i.viewFrom&&n>=i.viewTo)Ur(e);else if(t<=i.viewFrom){var o=Br(e,n,n+r,1);o?(i.view=i.view.slice(o.index),i.viewFrom=o.lineN,i.viewTo+=r):Ur(e)}else if(n>=i.viewTo){var a=Br(e,t,t,-1);a?(i.view=i.view.slice(0,a.index),i.viewTo=a.lineN):Ur(e)}else{var s=Br(e,t,t,-1),l=Br(e,n,n+r,1);s&&l?(i.view=i.view.slice(0,s.index).concat(Sn(e,s.lineN,l.lineN)).concat(i.view.slice(l.index)),i.viewTo+=r):Ur(e)}var u=i.externalMeasured;u&&(n=i.lineN&&t=r.viewTo)){var o=r.view[Pr(e,t)];if(null!=o.node){var a=o.changes||(o.changes=[]);-1==B(a,n)&&a.push(n)}}}function Ur(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function Br(e,t,n,r){var i,o=Pr(e,t),a=e.display.view;if(!Dt||n==e.doc.first+e.doc.size)return{index:o,lineN:n};for(var s=e.display.viewFrom,l=0;l0){if(o==a.length-1)return null;i=s+a[o].size-t,o++}else i=s-t;t+=i,n+=i}for(;nn(e.doc,n)!=n;){if(o==(r<0?0:a.length-1))return null;n+=r*a[o-(r<0?1:0)].size,o+=r}return{index:o,lineN:n}}function $r(e,t,n){var r=e.display;0==r.view.length||t>=r.viewTo||n<=r.viewFrom?(r.view=Sn(e,t,n),r.viewFrom=t):(r.viewFrom>t?r.view=Sn(e,t,r.viewFrom).concat(r.view):r.viewFromn&&(r.view=r.view.slice(0,Pr(e,n)))),r.viewTo=n}function qr(e){for(var t=e.display.view,n=0,r=0;r=e.display.viewTo||l.to().line0?a:e.defaultCharWidth())+"px"}if(r.other){var s=n.appendChild(I("div"," ","CodeMirror-cursor CodeMirror-secondarycursor"));s.style.display="",s.style.left=r.other.left+"px",s.style.top=r.other.top+"px",s.style.height=.85*(r.other.bottom-r.other.top)+"px"}}function Wr(e,t){return e.top-t.top||e.left-t.left}function Kr(e,t,n){var i=e.display,o=e.doc,a=document.createDocumentFragment(),s=Kn(e.display),l=s.left,u=Math.max(i.sizerWidth,Xn(e)-i.sizer.offsetLeft)-s.right,c="ltr"==o.direction;function d(e,t,n,r){t<0&&(t=0),t=Math.round(t),r=Math.round(r),a.appendChild(I("div",null,"CodeMirror-selected","position: absolute; left: "+e+"px;\n top: "+t+"px; width: "+(null==n?u-e:n)+"px;\n height: "+(r-t)+"px"))}function f(t,n,i){var a,s,f=Je(o,t),p=f.text.length;function h(n,r){return br(e,at(t,n),"div",f,r)}function m(t,n,r){var i=xr(e,f,null,t),o="ltr"==n==("after"==r)?"left":"right";return h("after"==r?i.begin:i.end-(/\s/.test(f.text.charAt(i.end-1))?2:1),o)[o]}r(h,"coords"),r(m,"wrapX");var g=pe(f,o.direction);return ue(g,n||0,null==i?p:i,(function(e,t,r,o){var f="ltr"==r,v=h(e,f?"left":"right"),y=h(t-1,f?"right":"left"),b=null==n&&0==e,E=null==i&&t==p,T=0==o,w=!g||o==g.length-1;if(y.top-v.top<=3){var C=(c?E:b)&&w,S=(c?b:E)&&T?l:(f?v:y).left,x=C?u:(f?y:v).right;d(S,v.top,x-S,v.bottom)}else{var k,N,_,O;f?(k=c&&b&&T?l:v.left,N=c?u:m(e,r,"before"),_=c?l:m(t,r,"after"),O=c&&E&&w?u:y.right):(k=c?m(e,r,"before"):l,N=!c&&b&&T?u:v.right,_=!c&&E&&w?l:y.left,O=c?m(t,r,"after"):u),d(k,v.top,N-k,v.bottom),v.bottom0?t.blinker=setInterval((function(){e.hasFocus()||Zr(e),t.cursorDiv.style.visibility=(n=!n)?"":"hidden"}),e.options.cursorBlinkRate):e.options.cursorBlinkRate<0&&(t.cursorDiv.style.visibility="hidden")}}function Xr(e){e.hasFocus()||(e.display.input.focus(),e.state.focused||Jr(e))}function Yr(e){e.state.delayingBlurEvent=!0,setTimeout((function(){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1,e.state.focused&&Zr(e))}),100)}function Jr(e,t){e.state.delayingBlurEvent&&!e.state.draggingText&&(e.state.delayingBlurEvent=!1),"nocursor"!=e.options.readOnly&&(e.state.focused||(ye(e,"focus",e,t),e.state.focused=!0,M(e.display.wrapper,"CodeMirror-focused"),e.curOp||e.display.selForContextMenu==e.doc.sel||(e.display.input.reset(),u&&setTimeout((function(){return e.display.input.reset(!0)}),20)),e.display.input.receivedFocus()),Qr(e))}function Zr(e,t){e.state.delayingBlurEvent||(e.state.focused&&(ye(e,"blur",e,t),e.state.focused=!1,N(e.display.wrapper,"CodeMirror-focused")),clearInterval(e.display.blinker),setTimeout((function(){e.state.focused||(e.display.shift=!1)}),150))}function ei(e){for(var t=e.display,n=t.lineDiv.offsetTop,r=Math.max(0,t.scroller.getBoundingClientRect().top),i=t.lineDiv.getBoundingClientRect().top,o=0,a=0;a.005||m<-.005)&&(ie.display.sizerWidth){var v=Math.ceil(f/Dr(e.display));v>e.display.maxLineLength&&(e.display.maxLineLength=v,e.display.maxLine=u.line,e.display.maxLineChanged=!0)}}}Math.abs(o)>2&&(t.scroller.scrollTop+=o)}function ti(e){if(e.widgets)for(var t=0;t=a&&(o=rt(t,sn(Je(t,l))-e.wrapper.clientHeight),a=l)}return{from:o,to:Math.max(a,o+1)}}function ri(e,t){if(!be(e,"scrollCursorIntoView")){var n=e.display,r=n.sizer.getBoundingClientRect(),i=null;if(t.top+r.top<0?i=!0:t.bottom+r.top>(window.innerHeight||document.documentElement.clientHeight)&&(i=!1),null!=i&&!m){var o=I("div","​",null,"position: absolute;\n top: "+(t.top-n.viewOffset-zn(e.display))+"px;\n height: "+(t.bottom-t.top+Qn(e)+n.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(o),o.scrollIntoView(i),e.display.lineSpace.removeChild(o)}}}function ii(e,t,n,r){var i;null==r&&(r=0),e.options.lineWrapping||t!=n||(n="before"==t.sticky?at(t.line,t.ch+1,"before"):t,t=t.ch?at(t.line,"before"==t.sticky?t.ch-1:t.ch,"after"):t);for(var o=0;o<5;o++){var a=!1,s=Er(e,t),l=n&&n!=t?Er(e,n):s,u=ai(e,i={left:Math.min(s.left,l.left),top:Math.min(s.top,l.top)-r,right:Math.max(s.left,l.left),bottom:Math.max(s.bottom,l.bottom)+r}),c=e.doc.scrollTop,d=e.doc.scrollLeft;if(null!=u.scrollTop&&(pi(e,u.scrollTop),Math.abs(e.doc.scrollTop-c)>1&&(a=!0)),null!=u.scrollLeft&&(mi(e,u.scrollLeft),Math.abs(e.doc.scrollLeft-d)>1&&(a=!0)),!a)break}return i}function oi(e,t){var n=ai(e,t);null!=n.scrollTop&&pi(e,n.scrollTop),null!=n.scrollLeft&&mi(e,n.scrollLeft)}function ai(e,t){var n=e.display,r=Ir(e.display);t.top<0&&(t.top=0);var i=e.curOp&&null!=e.curOp.scrollTop?e.curOp.scrollTop:n.scroller.scrollTop,o=Yn(e),a={};t.bottom-t.top>o&&(t.bottom=t.top+o);var s=e.doc.height+Wn(n),l=t.tops-r;if(t.topi+o){var c=Math.min(t.top,(u?s:t.bottom)-o);c!=i&&(a.scrollTop=c)}var d=e.options.fixedGutter?0:n.gutters.offsetWidth,f=e.curOp&&null!=e.curOp.scrollLeft?e.curOp.scrollLeft:n.scroller.scrollLeft-d,p=Xn(e)-n.gutters.offsetWidth,h=t.right-t.left>p;return h&&(t.right=t.left+p),t.left<10?a.scrollLeft=0:t.leftp+f-3&&(a.scrollLeft=t.right+(h?0:10)-p),a}function si(e,t){null!=t&&(di(e),e.curOp.scrollTop=(null==e.curOp.scrollTop?e.doc.scrollTop:e.curOp.scrollTop)+t)}function li(e){di(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function ui(e,t,n){null==t&&null==n||di(e),null!=t&&(e.curOp.scrollLeft=t),null!=n&&(e.curOp.scrollTop=n)}function ci(e,t){di(e),e.curOp.scrollToPos=t}function di(e){var t=e.curOp.scrollToPos;t&&(e.curOp.scrollToPos=null,fi(e,Tr(e,t.from),Tr(e,t.to),t.margin))}function fi(e,t,n,r){var i=ai(e,{left:Math.min(t.left,n.left),top:Math.min(t.top,n.top)-r,right:Math.max(t.right,n.right),bottom:Math.max(t.bottom,n.bottom)+r});ui(e,i.scrollLeft,i.scrollTop)}function pi(e,t){Math.abs(e.doc.scrollTop-t)<2||(n||Hi(e,{top:t}),hi(e,t,!0),n&&Hi(e),Fi(e,100))}function hi(e,t,n){t=Math.max(0,Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t)),(e.display.scroller.scrollTop!=t||n)&&(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop!=t&&(e.display.scroller.scrollTop=t))}function mi(e,t,n,r){t=Math.max(0,Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth)),(n?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!r||(e.doc.scrollLeft=t,Ki(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function gi(e){var t=e.display,n=t.gutters.offsetWidth,r=Math.round(e.doc.height+Wn(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:r,scrollHeight:r+Qn(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}r(sr,"nodeAndOffsetInLineMap"),r(lr,"getUsefulRect"),r(ur,"measureCharInner"),r(cr,"maybeUpdateRectForZooming"),r(dr,"clearLineMeasurementCacheFor"),r(fr,"clearLineMeasurementCache"),r(pr,"clearCaches"),r(hr,"pageScrollX"),r(mr,"pageScrollY"),r(gr,"widgetTopHeight"),r(vr,"intoCoordSystem"),r(yr,"fromCoordSystem"),r(br,"charCoords"),r(Er,"cursorCoords"),r(Tr,"estimateCoords"),r(wr,"PosWithInfo"),r(Cr,"coordsChar"),r(Sr,"wrappedLineExtent"),r(xr,"wrappedLineExtentChar"),r(kr,"boxIsAfter"),r(Nr,"coordsCharInner"),r(_r,"coordsBidiPart"),r(Or,"coordsBidiPartWrapped"),r(Ir,"textHeight"),r(Dr,"charWidth"),r(Lr,"getDimensions"),r(Ar,"compensateForHScroll"),r(Mr,"estimateHeight"),r(Rr,"estimateLineHeights"),r(Fr,"posFromMouse"),r(Pr,"findViewIndex"),r(jr,"regChange"),r(Vr,"regLineChange"),r(Ur,"resetView"),r(Br,"viewCuttingPoint"),r($r,"adjustView"),r(qr,"countDirtyView"),r(Hr,"updateSelection"),r(Gr,"prepareSelection"),r(zr,"drawSelectionCursor"),r(Wr,"cmpCoords"),r(Kr,"drawSelectionRange"),r(Qr,"restartBlink"),r(Xr,"ensureFocus"),r(Yr,"delayBlurEvent"),r(Jr,"onFocus"),r(Zr,"onBlur"),r(ei,"updateHeightsInViewport"),r(ti,"updateWidgetHeight"),r(ni,"visibleLines"),r(ri,"maybeScrollWindow"),r(ii,"scrollPosIntoView"),r(oi,"scrollIntoView"),r(ai,"calculateScrollPos"),r(si,"addToScrollTop"),r(li,"ensureCursorVisible"),r(ui,"scrollToCoords"),r(ci,"scrollToRange"),r(di,"resolveScrollToPos"),r(fi,"scrollToCoordsRange"),r(pi,"updateScrollTop"),r(hi,"setScrollTop"),r(mi,"setScrollLeft"),r(gi,"measureForScrollbars");var vi=r((function(e,t,n){this.cm=n;var r=this.vert=I("div",[I("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),i=this.horiz=I("div",[I("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");r.tabIndex=i.tabIndex=-1,e(r),e(i),me(r,"scroll",(function(){r.clientHeight&&t(r.scrollTop,"vertical")})),me(i,"scroll",(function(){i.clientWidth&&t(i.scrollLeft,"horizontal")})),this.checkedZeroWidth=!1,s&&l<8&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")}),"NativeScrollbars");vi.prototype.update=function(e){var t=e.scrollWidth>e.clientWidth+1,n=e.scrollHeight>e.clientHeight+1,r=e.nativeBarWidth;if(n){this.vert.style.display="block",this.vert.style.bottom=t?r+"px":"0";var i=e.viewHeight-(t?r:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+i)+"px"}else this.vert.scrollTop=0,this.vert.style.display="",this.vert.firstChild.style.height="0";if(t){this.horiz.style.display="block",this.horiz.style.right=n?r+"px":"0",this.horiz.style.left=e.barLeft+"px";var o=e.viewWidth-e.barLeft-(n?r:0);this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+o)+"px"}else this.horiz.style.display="",this.horiz.firstChild.style.width="0";return!this.checkedZeroWidth&&e.clientHeight>0&&(0==r&&this.zeroWidthHack(),this.checkedZeroWidth=!0),{right:n?r:0,bottom:t?r:0}},vi.prototype.setScrollLeft=function(e){this.horiz.scrollLeft!=e&&(this.horiz.scrollLeft=e),this.disableHoriz&&this.enableZeroWidthBar(this.horiz,this.disableHoriz,"horiz")},vi.prototype.setScrollTop=function(e){this.vert.scrollTop!=e&&(this.vert.scrollTop=e),this.disableVert&&this.enableZeroWidthBar(this.vert,this.disableVert,"vert")},vi.prototype.zeroWidthHack=function(){var e=b&&!h?"12px":"18px";this.horiz.style.height=this.vert.style.width=e,this.horiz.style.pointerEvents=this.vert.style.pointerEvents="none",this.disableHoriz=new U,this.disableVert=new U},vi.prototype.enableZeroWidthBar=function(e,t,n){function i(){var r=e.getBoundingClientRect();("vert"==n?document.elementFromPoint(r.right-1,(r.top+r.bottom)/2):document.elementFromPoint((r.right+r.left)/2,r.bottom-1))!=e?e.style.pointerEvents="none":t.set(1e3,i)}e.style.pointerEvents="auto",r(i,"maybeDisable"),t.set(1e3,i)},vi.prototype.clear=function(){var e=this.horiz.parentNode;e.removeChild(this.horiz),e.removeChild(this.vert)};var yi=r((function(){}),"NullScrollbars");function bi(e,t){t||(t=gi(e));var n=e.display.barWidth,r=e.display.barHeight;Ei(e,t);for(var i=0;i<4&&n!=e.display.barWidth||r!=e.display.barHeight;i++)n!=e.display.barWidth&&e.options.lineWrapping&&ei(e),Ei(e,gi(e)),n=e.display.barWidth,r=e.display.barHeight}function Ei(e,t){var n=e.display,r=n.scrollbars.update(t);n.sizer.style.paddingRight=(n.barWidth=r.right)+"px",n.sizer.style.paddingBottom=(n.barHeight=r.bottom)+"px",n.heightForcer.style.borderBottom=r.bottom+"px solid transparent",r.right&&r.bottom?(n.scrollbarFiller.style.display="block",n.scrollbarFiller.style.height=r.bottom+"px",n.scrollbarFiller.style.width=r.right+"px"):n.scrollbarFiller.style.display="",r.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter?(n.gutterFiller.style.display="block",n.gutterFiller.style.height=r.bottom+"px",n.gutterFiller.style.width=t.gutterWidth+"px"):n.gutterFiller.style.display=""}yi.prototype.update=function(){return{bottom:0,right:0}},yi.prototype.setScrollLeft=function(){},yi.prototype.setScrollTop=function(){},yi.prototype.clear=function(){},r(bi,"updateScrollbars"),r(Ei,"updateScrollbarsInner");var Ti={native:vi,null:yi};function wi(e){e.display.scrollbars&&(e.display.scrollbars.clear(),e.display.scrollbars.addClass&&N(e.display.wrapper,e.display.scrollbars.addClass)),e.display.scrollbars=new Ti[e.options.scrollbarStyle]((function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller),me(t,"mousedown",(function(){e.state.focused&&setTimeout((function(){return e.display.input.focus()}),0)})),t.setAttribute("cm-not-content","true")}),(function(t,n){"horizontal"==n?mi(e,t):pi(e,t)}),e),e.display.scrollbars.addClass&&M(e.display.wrapper,e.display.scrollbars.addClass)}r(wi,"initScrollbars");var Ci=0;function Si(e){e.curOp={cm:e,viewChanged:!1,startHeight:e.doc.height,forceUpdate:!1,updateInput:0,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++Ci,markArrays:null},kn(e.curOp)}function xi(e){var t=e.curOp;t&&_n(t,(function(e){for(var t=0;t=n.viewTo)||n.maxLineChanged&&t.options.lineWrapping,e.update=e.mustUpdate&&new ji(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function _i(e){e.updatedDisplay=e.mustUpdate&&$i(e.cm,e.update)}function Oi(e){var t=e.cm,n=t.display;e.updatedDisplay&&ei(t),e.barMeasure=gi(t),n.maxLineChanged&&!t.options.lineWrapping&&(e.adjustWidthTo=tr(t,n.maxLine,n.maxLine.text.length).left+3,t.display.sizerWidth=e.adjustWidthTo,e.barMeasure.scrollWidth=Math.max(n.scroller.clientWidth,n.sizer.offsetLeft+e.adjustWidthTo+Qn(t)+t.display.barWidth),e.maxScrollLeft=Math.max(0,n.sizer.offsetLeft+e.adjustWidthTo-Xn(t))),(e.updatedDisplay||e.selectionChanged)&&(e.preparedSelection=n.input.prepareSelection())}function Ii(e){var t=e.cm;null!=e.adjustWidthTo&&(t.display.sizer.style.minWidth=e.adjustWidthTo+"px",e.maxScrollLeft=e.display.viewTo)){var n=+new Date+e.options.workTime,r=Et(e,t.highlightFrontier),i=[];t.iter(r.line,Math.min(t.first+t.size,e.display.viewTo+500),(function(o){if(r.line>=e.display.viewFrom){var a=o.styles,s=o.text.length>e.options.maxHighlightLength?Ke(t.mode,r.state):null,l=yt(e,o,r,!0);s&&(r.state=s),o.styles=l.styles;var u=o.styleClasses,c=l.classes;c?o.styleClasses=c:u&&(o.styleClasses=null);for(var d=!a||a.length!=o.styles.length||u!=c&&(!u||!c||u.bgClass!=c.bgClass||u.textClass!=c.textClass),f=0;!d&&fn)return Fi(e,e.options.workDelay),!0})),t.highlightFrontier=r.line,t.modeFrontier=Math.max(t.modeFrontier,r.line),i.length&&Li(e,(function(){for(var t=0;t=n.viewFrom&&t.visible.to<=n.viewTo&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo)&&n.renderedView==n.view&&0==qr(e))return!1;Qi(e)&&(Ur(e),t.dims=Lr(e));var i=r.first+r.size,o=Math.max(t.visible.from-e.options.viewportMargin,r.first),a=Math.min(i,t.visible.to+e.options.viewportMargin);n.viewFroma&&n.viewTo-a<20&&(a=Math.min(i,n.viewTo)),Dt&&(o=nn(e.doc,o),a=rn(e.doc,a));var s=o!=n.viewFrom||a!=n.viewTo||n.lastWrapHeight!=t.wrapperHeight||n.lastWrapWidth!=t.wrapperWidth;$r(e,o,a),n.viewOffset=sn(Je(e.doc,n.viewFrom)),e.display.mover.style.top=n.viewOffset+"px";var l=qr(e);if(!s&&0==l&&!t.force&&n.renderedView==n.view&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo))return!1;var u=Ui(e);return l>4&&(n.lineDiv.style.display="none"),Gi(e,n.updateLineNumbers,t.dims),l>4&&(n.lineDiv.style.display=""),n.renderedView=n.view,Bi(u),_(n.cursorDiv),_(n.selectionDiv),n.gutters.style.height=n.sizer.style.minHeight=0,s&&(n.lastWrapHeight=t.wrapperHeight,n.lastWrapWidth=t.wrapperWidth,Fi(e,400)),n.updateLineNumbers=null,!0}function qi(e,t){for(var n=t.viewport,r=!0;;r=!1){if(r&&e.options.lineWrapping&&t.oldDisplayWidth!=Xn(e))r&&(t.visible=ni(e.display,e.doc,n));else if(n&&null!=n.top&&(n={top:Math.min(e.doc.height+Wn(e.display)-Yn(e),n.top)}),t.visible=ni(e.display,e.doc,n),t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo)break;if(!$i(e,t))break;ei(e);var i=gi(e);Hr(e),bi(e,i),Wi(e,i),t.force=!1}t.signal(e,"update",e),e.display.viewFrom==e.display.reportedViewFrom&&e.display.viewTo==e.display.reportedViewTo||(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function Hi(e,t){var n=new ji(e,t);if($i(e,n)){ei(e),qi(e,n);var r=gi(e);Hr(e),bi(e,r),Wi(e,r),n.finish()}}function Gi(e,t,n){var i=e.display,o=e.options.lineNumbers,a=i.lineDiv,s=a.firstChild;function l(t){var n=t.nextSibling;return u&&b&&e.display.currentWheelTarget==t?t.style.display="none":t.parentNode.removeChild(t),n}r(l,"rm");for(var c=i.view,d=i.viewFrom,f=0;f-1&&(h=!1),Ln(e,p,d,n)),h&&(_(p.lineNumber),p.lineNumber.appendChild(document.createTextNode(ot(e.options,d)))),s=p.node.nextSibling}else{var m=Un(e,p,d,n);a.insertBefore(m,s)}d+=p.size}for(;s;)s=l(s)}function zi(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px",In(e,"gutterChanged",e)}function Wi(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+Qn(e)+"px"}function Ki(e){var t=e.display,n=t.view;if(t.alignWidgets||t.gutters.firstChild&&e.options.fixedGutter){for(var r=Ar(t)-t.scroller.scrollLeft+e.doc.scrollLeft,i=t.gutters.offsetWidth,o=r+"px",a=0;al.clientWidth,d=l.scrollHeight>l.clientHeight;if(i&&c||o&&d){if(o&&b&&u)e:for(var p=t.target,h=s.view;p!=l;p=p.parentNode)for(var m=0;m=0&&st(e,r.to())<=0)return n}return-1};var ao=r((function(e,t){this.anchor=e,this.head=t}),"Range");function so(e,t,n){var r=e&&e.options.selectionsMayTouch,i=t[n];t.sort((function(e,t){return st(e.from(),t.from())})),n=B(t,i);for(var o=1;o0:l>=0){var u=dt(s.from(),a.from()),c=ct(s.to(),a.to()),d=s.empty()?a.from()==a.head:s.from()==s.head;o<=n&&--n,t.splice(--o,2,new ao(d?c:u,d?u:c))}}return new oo(t,n)}function lo(e,t){return new oo([new ao(e,t||e)],0)}function uo(e){return e.text?at(e.from.line+e.text.length-1,X(e.text).length+(1==e.text.length?e.from.ch:0)):e.to}function co(e,t){if(st(e,t.from)<0)return e;if(st(e,t.to)<=0)return uo(t);var n=e.line+t.text.length-(t.to.line-t.from.line)-1,r=e.ch;return e.line==t.to.line&&(r+=uo(t).ch-t.to.ch),at(n,r)}function fo(e,t){for(var n=[],r=0;r1&&e.remove(l.line+1,m-1),e.insert(l.line+1,y)}In(e,"change",e,t)}function bo(e,t,n){function i(e,r,o){if(e.linked)for(var a=0;a1&&!e.done[e.done.length-2].ranges?(e.done.pop(),X(e.done)):void 0}function No(e,t,n,r){var i=e.history;i.undone.length=0;var o,a,s=+new Date;if((i.lastOp==r||i.lastOrigin==t.origin&&t.origin&&("+"==t.origin.charAt(0)&&i.lastModTime>s-(e.cm?e.cm.options.historyEventDelay:500)||"*"==t.origin.charAt(0)))&&(o=ko(i,i.lastOp==r)))a=X(o.changes),0==st(t.from,t.to)&&0==st(t.from,a.to)?a.to=uo(t):o.changes.push(So(e,t));else{var l=X(i.done);for(l&&l.ranges||Io(e.sel,i.done),o={changes:[So(e,t)],generation:i.generation},i.done.push(o);i.done.length>i.undoDepth;)i.done.shift(),i.done[0].ranges||i.done.shift()}i.done.push(n),i.generation=++i.maxGeneration,i.lastModTime=i.lastSelTime=s,i.lastOp=i.lastSelOp=r,i.lastOrigin=i.lastSelOrigin=t.origin,a||ye(e,"historyAdded")}function _o(e,t,n,r){var i=t.charAt(0);return"*"==i||"+"==i&&n.ranges.length==r.ranges.length&&n.somethingSelected()==r.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}function Oo(e,t,n,r){var i=e.history,o=r&&r.origin;n==i.lastSelOp||o&&i.lastSelOrigin==o&&(i.lastModTime==i.lastSelTime&&i.lastOrigin==o||_o(e,o,X(i.done),t))?i.done[i.done.length-1]=t:Io(t,i.done),i.lastSelTime=+new Date,i.lastSelOrigin=o,i.lastSelOp=n,r&&!1!==r.clearRedo&&xo(i.undone)}function Io(e,t){var n=X(t);n&&n.ranges&&n.equals(e)||t.push(e)}function Do(e,t,n,r){var i=t["spans_"+e.id],o=0;e.iter(Math.max(e.first,n),Math.min(e.first+e.size,r),(function(n){n.markedSpans&&((i||(i=t["spans_"+e.id]={}))[o]=n.markedSpans),++o}))}function Lo(e){if(!e)return null;for(var t,n=0;n-1&&(X(s)[d]=u[d],delete u[d])}}}return r}function Fo(e,t,n,r){if(r){var i=e.anchor;if(n){var o=st(t,i)<0;o!=st(n,i)<0?(i=t,t=n):o!=st(t,n)<0&&(t=n)}return new ao(i,t)}return new ao(n||t,t)}function Po(e,t,n,r,i){null==i&&(i=e.cm&&(e.cm.display.shift||e.extend)),qo(e,new oo([Fo(e.sel.primary(),t,n,i)],0),r)}function jo(e,t,n){for(var r=[],i=e.cm&&(e.cm.display.shift||e.extend),o=0;o=t.ch:s.to>t.ch))){if(i&&(ye(l,"beforeCursorEnter"),l.explicitlyCleared)){if(o.markedSpans){--a;continue}break}if(!l.atomic)continue;if(n){var d=l.find(r<0?1:-1),f=void 0;if((r<0?c:u)&&(d=Xo(e,d,-r,d&&d.line==t.line?o:null)),d&&d.line==t.line&&(f=st(d,n))&&(r<0?f<0:f>0))return Ko(e,d,t,r,i)}var p=l.find(r<0?-1:1);return(r<0?u:c)&&(p=Xo(e,p,r,p.line==t.line?o:null)),p?Ko(e,p,t,r,i):null}}return t}function Qo(e,t,n,r,i){var o=r||1;return Ko(e,t,n,o,i)||!i&&Ko(e,t,n,o,!0)||Ko(e,t,n,-o,i)||!i&&Ko(e,t,n,-o,!0)||(e.cantEdit=!0,at(e.first,0))}function Xo(e,t,n,r){return n<0&&0==t.ch?t.line>e.first?pt(e,at(t.line-1)):null:n>0&&t.ch==(r||Je(e,t.line)).text.length?t.line=0;--i)ea(e,{from:r[i].from,to:r[i].to,text:i?[""]:t.text,origin:t.origin});else ea(e,t)}}function ea(e,t){if(1!=t.text.length||""!=t.text[0]||0!=st(t.from,t.to)){var n=fo(e,t);No(e,t,n,e.cm?e.cm.curOp.id:NaN),ra(e,t,n,Ut(e,t));var r=[];bo(e,(function(e,n){n||-1!=B(r,e.history)||(la(e.history,t),r.push(e.history)),ra(e,t,null,Ut(e,t))}))}}function ta(e,t,n){var i=e.cm&&e.cm.state.suppressEdits;if(!i||n){for(var o,a=e.history,s=e.sel,l="undo"==t?a.done:a.undone,u="undo"==t?a.undone:a.done,c=0;c=0;--h){var m=p(h);if(m)return m.v}}}}function na(e,t){if(0!=t&&(e.first+=t,e.sel=new oo(Y(e.sel.ranges,(function(e){return new ao(at(e.anchor.line+t,e.anchor.ch),at(e.head.line+t,e.head.ch))})),e.sel.primIndex),e.cm)){jr(e.cm,e.first,e.first-t,t);for(var n=e.cm.display,r=n.viewFrom;re.lastLine())){if(t.from.lineo&&(t={from:t.from,to:at(o,Je(e,o).text.length),text:[t.text[0]],origin:t.origin}),t.removed=Ze(e,t.from,t.to),n||(n=fo(e,t)),e.cm?ia(e.cm,t,r):yo(e,t,r),Ho(e,n,H),e.cantEdit&&Qo(e,at(e.firstLine(),0))&&(e.cantEdit=!1)}}function ia(e,t,n){var r=e.doc,i=e.display,o=t.from,a=t.to,s=!1,l=o.line;e.options.lineWrapping||(l=nt(Zt(Je(r,o.line))),r.iter(l,a.line+1,(function(e){if(e==i.maxLine)return s=!0,!0}))),r.sel.contains(t.from,t.to)>-1&&Ee(e),yo(r,t,n,Mr(e)),e.options.lineWrapping||(r.iter(l,o.line+t.text.length,(function(e){var t=ln(e);t>i.maxLineLength&&(i.maxLine=e,i.maxLineLength=t,i.maxLineChanged=!0,s=!1)})),s&&(e.curOp.updateMaxLine=!0)),Ot(r,o.line),Fi(e,400);var u=t.text.length-(a.line-o.line)-1;t.full?jr(e):o.line!=a.line||1!=t.text.length||vo(e.doc,t)?jr(e,o.line,a.line+1,u):Vr(e,o.line,"text");var c=Te(e,"changes"),d=Te(e,"change");if(d||c){var f={from:o,to:a,text:t.text,removed:t.removed,origin:t.origin};d&&In(e,"change",e,f),c&&(e.curOp.changeObjs||(e.curOp.changeObjs=[])).push(f)}e.display.selForContextMenu=null}function oa(e,t,n,r,i){var o;r||(r=n),st(r,n)<0&&(n=(o=[r,n])[0],r=o[1]),"string"==typeof t&&(t=e.splitLines(t)),Zo(e,{from:n,to:r,text:t,origin:i})}function aa(e,t,n,r){n1||!(this.children[0]instanceof ca))){var s=[];this.collapse(s),this.children=[new ca(s)],this.children[0].parent=this}},collapse:function(e){for(var t=0;t50){for(var a=i.lines.length%25+25,s=a;s10);e.parent.maybeSpill()}},iterN:function(e,t,n){for(var r=0;r0||0==a&&!1!==o.clearWhenEmpty)return o;if(o.replacedWith&&(o.collapsed=!0,o.widgetNode=D("span",[o.replacedWith],"CodeMirror-widget"),r.handleMouseEvents||o.widgetNode.setAttribute("cm-ignore-events","true"),r.insertLeft&&(o.widgetNode.insertLeft=!0)),o.collapsed){if(Jt(e,t.line,t,n,o)||t.line!=n.line&&Jt(e,n.line,t,n,o))throw new Error("Inserting collapsed marker partially overlapping an existing one");At()}o.addToHistory&&No(e,{from:t,to:n,origin:"markText"},e.sel,NaN);var s,l=t.line,u=e.cm;if(e.iter(l,n.line+1,(function(r){u&&o.collapsed&&!u.options.lineWrapping&&Zt(r)==u.display.maxLine&&(s=!0),o.collapsed&&l!=t.line&&tt(r,0),Pt(r,new Mt(o,l==t.line?t.ch:null,l==n.line?n.ch:null),e.cm&&e.cm.curOp),++l})),o.collapsed&&e.iter(t.line,n.line+1,(function(t){on(e,t)&&tt(t,0)})),o.clearOnEnter&&me(o,"beforeCursorEnter",(function(){return o.clear()})),o.readOnly&&(Lt(),(e.history.done.length||e.history.undone.length)&&e.clearHistory()),o.collapsed&&(o.id=++ma,o.atomic=!0),u){if(s&&(u.curOp.updateMaxLine=!0),o.collapsed)jr(u,t.line,n.line+1);else if(o.className||o.startStyle||o.endStyle||o.css||o.attributes||o.title)for(var c=t.line;c<=n.line;c++)Vr(u,c,"text");o.atomic&&zo(u.doc),In(u,"markerAdded",u,o)}return o}ga.prototype.clear=function(){if(!this.explicitlyCleared){var e=this.doc.cm,t=e&&!e.curOp;if(t&&Si(e),Te(this,"clear")){var n=this.find();n&&In(this,"clear",n.from,n.to)}for(var r=null,i=null,o=0;oe.display.maxLineLength&&(e.display.maxLine=u,e.display.maxLineLength=c,e.display.maxLineChanged=!0)}null!=r&&e&&this.collapsed&&jr(e,r,i+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,e&&zo(e.doc)),e&&In(e,"markerCleared",e,this,r,i),t&&xi(e),this.parent&&this.parent.clear()}},ga.prototype.find=function(e,t){var n,r;null==e&&"bookmark"==this.type&&(e=1);for(var i=0;i=0;l--)Zo(this,r[l]);s?$o(this,s):this.cm&&li(this.cm)})),undo:Ri((function(){ta(this,"undo")})),redo:Ri((function(){ta(this,"redo")})),undoSelection:Ri((function(){ta(this,"undo",!0)})),redoSelection:Ri((function(){ta(this,"redo",!0)})),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){for(var e=this.history,t=0,n=0,r=0;r=e.ch)&&t.push(i.marker.parent||i.marker)}return t},findMarks:function(e,t,n){e=pt(this,e),t=pt(this,t);var r=[],i=e.line;return this.iter(e.line,t.line+1,(function(o){var a=o.markedSpans;if(a)for(var s=0;s=l.to||null==l.from&&i!=e.line||null!=l.from&&i==t.line&&l.from>=t.ch||n&&!n(l.marker)||r.push(l.marker.parent||l.marker)}++i})),r},getAllMarks:function(){var e=[];return this.iter((function(t){var n=t.markedSpans;if(n)for(var r=0;re)return t=e,!0;e-=o,++n})),pt(this,at(n,t))},indexFromPos:function(e){var t=(e=pt(this,e)).ch;if(e.linet&&(t=e.from),null!=e.to&&e.to-1)return t.state.draggingText(e),void setTimeout((function(){return t.display.input.focus()}),20);try{var f=e.dataTransfer.getData("Text");if(f){var p;if(t.state.draggingText&&!t.state.draggingText.copy&&(p=t.listSelections()),Ho(t.doc,lo(n,n)),p)for(var h=0;h=0;t--)oa(e.doc,"",r[t].from,r[t].to,"+delete");li(e)}))}function Ka(e,t,n){var r=se(e.text,t+n,n);return r<0||r>e.text.length?null:r}function Qa(e,t,n){var r=Ka(e,t.ch,n);return null==r?null:new at(t.line,r,n<0?"after":"before")}function Xa(e,t,n,r,i){if(e){"rtl"==t.doc.direction&&(i=-i);var o=pe(n,t.doc.direction);if(o){var a,s=i<0?X(o):o[0],l=i<0==(1==s.level)?"after":"before";if(s.level>0||"rtl"==t.doc.direction){var u=rr(t,n);a=i<0?n.text.length-1:0;var c=ir(t,u,a).top;a=le((function(e){return ir(t,u,e).top==c}),i<0==(1==s.level)?s.from:s.to-1,a),"before"==l&&(a=Ka(n,a,1))}else a=i<0?s.to:s.from;return new at(r,a,l)}}return new at(r,i<0?n.text.length:0,i<0?"before":"after")}function Ya(e,t,n,i){var o=pe(t,e.doc.direction);if(!o)return Qa(t,n,i);n.ch>=t.text.length?(n.ch=t.text.length,n.sticky="before"):n.ch<=0&&(n.ch=0,n.sticky="after");var a=de(o,n.ch,n.sticky),s=o[a];if("ltr"==e.doc.direction&&s.level%2==0&&(i>0?s.to>n.ch:s.from=s.from&&p>=d.begin)){var h=f?"before":"after";return new at(n.line,p,h)}}var m=r((function(e,t,i){for(var a=r((function(e,t){return t?new at(n.line,u(e,1),"before"):new at(n.line,e,"after")}),"getRes");e>=0&&e0==(1!=s.level),c=l?i.begin:u(i.end,-1);if(s.from<=c&&c0?d.end:u(d.begin,-1);return null==v||i>0&&v==t.text.length||!(g=m(i>0?0:o.length-1,i,c(v)))?null:g}Va.basic={Left:"goCharLeft",Right:"goCharRight",Up:"goLineUp",Down:"goLineDown",End:"goLineEnd",Home:"goLineStartSmart",PageUp:"goPageUp",PageDown:"goPageDown",Delete:"delCharAfter",Backspace:"delCharBefore","Shift-Backspace":"delCharBefore",Tab:"defaultTab","Shift-Tab":"indentAuto",Enter:"newlineAndIndent",Insert:"toggleOverwrite",Esc:"singleSelection"},Va.pcDefault={"Ctrl-A":"selectAll","Ctrl-D":"deleteLine","Ctrl-Z":"undo","Shift-Ctrl-Z":"redo","Ctrl-Y":"redo","Ctrl-Home":"goDocStart","Ctrl-End":"goDocEnd","Ctrl-Up":"goLineUp","Ctrl-Down":"goLineDown","Ctrl-Left":"goGroupLeft","Ctrl-Right":"goGroupRight","Alt-Left":"goLineStart","Alt-Right":"goLineEnd","Ctrl-Backspace":"delGroupBefore","Ctrl-Delete":"delGroupAfter","Ctrl-S":"save","Ctrl-F":"find","Ctrl-G":"findNext","Shift-Ctrl-G":"findPrev","Shift-Ctrl-F":"replace","Shift-Ctrl-R":"replaceAll","Ctrl-[":"indentLess","Ctrl-]":"indentMore","Ctrl-U":"undoSelection","Shift-Ctrl-U":"redoSelection","Alt-U":"redoSelection",fallthrough:"basic"},Va.emacsy={"Ctrl-F":"goCharRight","Ctrl-B":"goCharLeft","Ctrl-P":"goLineUp","Ctrl-N":"goLineDown","Ctrl-A":"goLineStart","Ctrl-E":"goLineEnd","Ctrl-V":"goPageDown","Shift-Ctrl-V":"goPageUp","Ctrl-D":"delCharAfter","Ctrl-H":"delCharBefore","Alt-Backspace":"delWordBefore","Ctrl-K":"killLine","Ctrl-T":"transposeChars","Ctrl-O":"openLine"},Va.macDefault={"Cmd-A":"selectAll","Cmd-D":"deleteLine","Cmd-Z":"undo","Shift-Cmd-Z":"redo","Cmd-Y":"redo","Cmd-Home":"goDocStart","Cmd-Up":"goDocStart","Cmd-End":"goDocEnd","Cmd-Down":"goDocEnd","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight","Cmd-Left":"goLineLeft","Cmd-Right":"goLineRight","Alt-Backspace":"delGroupBefore","Ctrl-Alt-Backspace":"delGroupAfter","Alt-Delete":"delGroupAfter","Cmd-S":"save","Cmd-F":"find","Cmd-G":"findNext","Shift-Cmd-G":"findPrev","Cmd-Alt-F":"replace","Shift-Cmd-Alt-F":"replaceAll","Cmd-[":"indentLess","Cmd-]":"indentMore","Cmd-Backspace":"delWrappedLineLeft","Cmd-Delete":"delWrappedLineRight","Cmd-U":"undoSelection","Shift-Cmd-U":"redoSelection","Ctrl-Up":"goDocStart","Ctrl-Down":"goDocEnd",fallthrough:["basic","emacsy"]},Va.default=b?Va.macDefault:Va.pcDefault,r(Ua,"normalizeKeyName"),r(Ba,"normalizeKeyMap"),r($a,"lookupKey"),r(qa,"isModifierKey"),r(Ha,"addModifierNames"),r(Ga,"keyName"),r(za,"getKeyMap"),r(Wa,"deleteNearSelection"),r(Ka,"moveCharLogically"),r(Qa,"moveLogically"),r(Xa,"endOfLine"),r(Ya,"moveVisually");var Ja={selectAll:Yo,singleSelection:function(e){return e.setSelection(e.getCursor("anchor"),e.getCursor("head"),H)},killLine:function(e){return Wa(e,(function(t){if(t.empty()){var n=Je(e.doc,t.head.line).text.length;return t.head.ch==n&&t.head.line0)i=new at(i.line,i.ch+1),e.replaceRange(o.charAt(i.ch-1)+o.charAt(i.ch-2),at(i.line,i.ch-2),i,"+transpose");else if(i.line>e.doc.first){var a=Je(e.doc,i.line-1).text;a&&(i=new at(i.line,1),e.replaceRange(o.charAt(0)+e.doc.lineSeparator()+a.charAt(a.length-1),at(i.line-1,a.length-1),i,"+transpose"))}n.push(new ao(i,i))}e.setSelections(n)}))},newlineAndIndent:function(e){return Li(e,(function(){for(var t=e.listSelections(),n=t.length-1;n>=0;n--)e.replaceRange(e.doc.lineSeparator(),t[n].anchor,t[n].head,"+input");t=e.listSelections();for(var r=0;r-1&&(st((i=a.ranges[i]).from(),t)<0||t.xRel>0)&&(st(i.to(),t)>0||t.xRel<0)?Cs(e,r,t,o):xs(e,r,t,o)}function Cs(e,t,n,i){var o=e.display,a=!1,c=Ai(e,(function(t){u&&(o.scroller.draggable=!1),e.state.draggingText=!1,e.state.delayingBlurEvent&&(e.hasFocus()?e.state.delayingBlurEvent=!1:Yr(e)),ve(o.wrapper.ownerDocument,"mouseup",c),ve(o.wrapper.ownerDocument,"mousemove",d),ve(o.scroller,"dragstart",f),ve(o.scroller,"drop",c),a||(Ce(t),i.addNew||Po(e.doc,n,null,null,i.extend),u&&!p||s&&9==l?setTimeout((function(){o.wrapper.ownerDocument.body.focus({preventScroll:!0}),o.input.focus()}),20):o.input.focus())})),d=r((function(e){a=a||Math.abs(t.clientX-e.clientX)+Math.abs(t.clientY-e.clientY)>=10}),"mouseMove"),f=r((function(){return a=!0}),"dragStart");u&&(o.scroller.draggable=!0),e.state.draggingText=c,c.copy=!i.moveOnDrag,me(o.wrapper.ownerDocument,"mouseup",c),me(o.wrapper.ownerDocument,"mousemove",d),me(o.scroller,"dragstart",f),me(o.scroller,"drop",c),e.state.delayingBlurEvent=!0,setTimeout((function(){return o.input.focus()}),20),o.scroller.dragDrop&&o.scroller.dragDrop()}function Ss(e,t,n){if("char"==n)return new ao(t,t);if("word"==n)return e.findWordAt(t);if("line"==n)return new ao(at(t.line,0),pt(e.doc,at(t.line+1,0)));var r=n(e,t);return new ao(r.from,r.to)}function xs(e,t,n,i){s&&Yr(e);var o=e.display,a=e.doc;Ce(t);var l,u,c=a.sel,d=c.ranges;if(i.addNew&&!i.extend?(u=a.sel.contains(n),l=u>-1?d[u]:new ao(n,n)):(l=a.sel.primary(),u=a.sel.primIndex),"rectangle"==i.unit)i.addNew||(l=new ao(n,n)),n=Fr(e,t,!0,!0),u=-1;else{var f=Ss(e,n,i.unit);l=i.extend?Fo(l,f.anchor,f.head,i.extend):f}i.addNew?-1==u?(u=d.length,qo(a,so(e,d.concat([l]),u),{scroll:!1,origin:"*mouse"})):d.length>1&&d[u].empty()&&"char"==i.unit&&!i.extend?(qo(a,so(e,d.slice(0,u).concat(d.slice(u+1)),0),{scroll:!1,origin:"*mouse"}),c=a.sel):Vo(a,u,l,G):(u=0,qo(a,new oo([l],0),G),c=a.sel);var p=n;function h(t){if(0!=st(p,t))if(p=t,"rectangle"==i.unit){for(var r=[],o=e.options.tabSize,s=V(Je(a,n.line).text,n.ch,o),d=V(Je(a,t.line).text,t.ch,o),f=Math.min(s,d),h=Math.max(s,d),m=Math.min(n.line,t.line),g=Math.min(e.lastLine(),Math.max(n.line,t.line));m<=g;m++){var v=Je(a,m).text,y=W(v,f,o);f==h?r.push(new ao(at(m,y),at(m,y))):v.length>y&&r.push(new ao(at(m,y),at(m,W(v,h,o))))}r.length||r.push(new ao(n,n)),qo(a,so(e,c.ranges.slice(0,u).concat(r),u),{origin:"*mouse",scroll:!1}),e.scrollIntoView(t)}else{var b,E=l,T=Ss(e,t,i.unit),w=E.anchor;st(T.anchor,w)>0?(b=T.head,w=dt(E.from(),T.anchor)):(b=T.anchor,w=ct(E.to(),T.head));var C=c.ranges.slice(0);C[u]=ks(e,new ao(pt(a,w),b)),qo(a,so(e,C,u),G)}}r(h,"extendTo");var m=o.wrapper.getBoundingClientRect(),g=0;function v(t){var n=++g,r=Fr(e,t,!0,"rectangle"==i.unit);if(r)if(0!=st(r,p)){e.curOp.focus=A(),h(r);var s=ni(o,a);(r.line>=s.to||r.linem.bottom?20:0;l&&setTimeout(Ai(e,(function(){g==n&&(o.scroller.scrollTop+=l,v(t))})),50)}}function y(t){e.state.selectingText=!1,g=1/0,t&&(Ce(t),o.input.focus()),ve(o.wrapper.ownerDocument,"mousemove",b),ve(o.wrapper.ownerDocument,"mouseup",E),a.history.lastSelOrigin=null}r(v,"extend"),r(y,"done");var b=Ai(e,(function(e){0!==e.buttons&&_e(e)?v(e):y(e)})),E=Ai(e,y);e.state.selectingText=E,me(o.wrapper.ownerDocument,"mousemove",b),me(o.wrapper.ownerDocument,"mouseup",E)}function ks(e,t){var n=t.anchor,r=t.head,i=Je(e.doc,n.line);if(0==st(n,r)&&n.sticky==r.sticky)return t;var o=pe(i);if(!o)return t;var a=de(o,n.ch,n.sticky),s=o[a];if(s.from!=n.ch&&s.to!=n.ch)return t;var l,u=a+(s.from==n.ch==(1!=s.level)?0:1);if(0==u||u==o.length)return t;if(r.line!=n.line)l=(r.line-n.line)*("ltr"==e.doc.direction?1:-1)>0;else{var c=de(o,r.ch,r.sticky),d=c-a||(r.ch-n.ch)*(1==s.level?-1:1);l=c==u-1||c==u?d<0:d>0}var f=o[u+(l?-1:0)],p=l==(1==f.level),h=p?f.from:f.to,m=p?"after":"before";return n.ch==h&&n.sticky==m?t:new ao(new at(n.line,h,m),r)}function Ns(e,t,n,r){var i,o;if(t.touches)i=t.touches[0].clientX,o=t.touches[0].clientY;else try{i=t.clientX,o=t.clientY}catch(e){return!1}if(i>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;r&&Ce(t);var a=e.display,s=a.lineDiv.getBoundingClientRect();if(o>s.bottom||!Te(e,n))return xe(t);o-=s.top-a.viewOffset;for(var l=0;l=i)return ye(e,n,e,rt(e.doc,o),e.display.gutterSpecs[l].className,t),xe(t)}}function _s(e,t){return Ns(e,t,"gutterClick",!0)}function Os(e,t){Gn(e.display,t)||Is(e,t)||be(e,t,"contextmenu")||S||e.display.input.onContextMenu(t)}function Is(e,t){return!!Te(e,"gutterContextMenu")&&Ns(e,t,"gutterContextMenu",!1)}function Ds(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-"),pr(e)}vs.prototype.compare=function(e,t,n){return this.time+gs>e&&0==st(t,this.pos)&&n==this.button},r(ys,"clickRepeat"),r(bs,"onMouseDown"),r(Es,"handleMappedButton"),r(Ts,"configureMouse"),r(ws,"leftButtonDown"),r(Cs,"leftButtonStartDrag"),r(Ss,"rangeForUnit"),r(xs,"leftButtonSelect"),r(ks,"bidiSimplify"),r(Ns,"gutterEvent"),r(_s,"clickInGutter"),r(Os,"onContextMenu"),r(Is,"contextMenuInGutter"),r(Ds,"themeChanged");var Ls={toString:function(){return"CodeMirror.Init"}},As={},Ms={};function Rs(e){var t=e.optionHandlers;function n(n,r,i,o){e.defaults[n]=r,i&&(t[n]=o?function(e,t,n){n!=Ls&&i(e,t,n)}:i)}r(n,"option"),e.defineOption=n,e.Init=Ls,n("value","",(function(e,t){return e.setValue(t)}),!0),n("mode",null,(function(e,t){e.doc.modeOption=t,mo(e)}),!0),n("indentUnit",2,mo,!0),n("indentWithTabs",!1),n("smartIndent",!0),n("tabSize",4,(function(e){go(e),pr(e),jr(e)}),!0),n("lineSeparator",null,(function(e,t){if(e.doc.lineSep=t,t){var n=[],r=e.doc.first;e.doc.iter((function(e){for(var i=0;;){var o=e.text.indexOf(t,i);if(-1==o)break;i=o+t.length,n.push(at(r,o))}r++}));for(var i=n.length-1;i>=0;i--)oa(e.doc,t,n[i],at(n[i].line,n[i].ch+t.length))}})),n("specialChars",/[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g,(function(e,t,n){e.state.specialChars=new RegExp(t.source+(t.test("\t")?"":"|\t"),"g"),n!=Ls&&e.refresh()})),n("specialCharPlaceholder",vn,(function(e){return e.refresh()}),!0),n("electricChars",!0),n("inputStyle",y?"contenteditable":"textarea",(function(){throw new Error("inputStyle can not (yet) be changed in a running editor")}),!0),n("spellcheck",!1,(function(e,t){return e.getInputField().spellcheck=t}),!0),n("autocorrect",!1,(function(e,t){return e.getInputField().autocorrect=t}),!0),n("autocapitalize",!1,(function(e,t){return e.getInputField().autocapitalize=t}),!0),n("rtlMoveVisually",!T),n("wholeLineUpdateBefore",!0),n("theme","default",(function(e){Ds(e),Ji(e)}),!0),n("keyMap","default",(function(e,t,n){var r=za(t),i=n!=Ls&&za(n);i&&i.detach&&i.detach(e,r),r.attach&&r.attach(e,i||null)})),n("extraKeys",null),n("configureMouse",null),n("lineWrapping",!1,Ps,!0),n("gutters",[],(function(e,t){e.display.gutterSpecs=Xi(t,e.options.lineNumbers),Ji(e)}),!0),n("fixedGutter",!0,(function(e,t){e.display.gutters.style.left=t?Ar(e.display)+"px":"0",e.refresh()}),!0),n("coverGutterNextToScrollbar",!1,(function(e){return bi(e)}),!0),n("scrollbarStyle","native",(function(e){wi(e),bi(e),e.display.scrollbars.setScrollTop(e.doc.scrollTop),e.display.scrollbars.setScrollLeft(e.doc.scrollLeft)}),!0),n("lineNumbers",!1,(function(e,t){e.display.gutterSpecs=Xi(e.options.gutters,t),Ji(e)}),!0),n("firstLineNumber",1,Ji,!0),n("lineNumberFormatter",(function(e){return e}),Ji,!0),n("showCursorWhenSelecting",!1,Hr,!0),n("resetSelectionOnContextMenu",!0),n("lineWiseCopyCut",!0),n("pasteLinesPerSelection",!0),n("selectionsMayTouch",!1),n("readOnly",!1,(function(e,t){"nocursor"==t&&(Zr(e),e.display.input.blur()),e.display.input.readOnlyChanged(t)})),n("screenReaderLabel",null,(function(e,t){t=""===t?null:t,e.display.input.screenReaderLabelChanged(t)})),n("disableInput",!1,(function(e,t){t||e.display.input.reset()}),!0),n("dragDrop",!0,Fs),n("allowDropFileTypes",null),n("cursorBlinkRate",530),n("cursorScrollMargin",0),n("cursorHeight",1,Hr,!0),n("singleCursorHeightPerLine",!0,Hr,!0),n("workTime",100),n("workDelay",100),n("flattenSpans",!0,go,!0),n("addModeClass",!1,go,!0),n("pollInterval",100),n("undoDepth",200,(function(e,t){return e.doc.history.undoDepth=t})),n("historyEventDelay",1250),n("viewportMargin",10,(function(e){return e.refresh()}),!0),n("maxHighlightLength",1e4,go,!0),n("moveInputWithCursor",!0,(function(e,t){t||e.display.input.resetPosition()})),n("tabindex",null,(function(e,t){return e.display.input.getField().tabIndex=t||""})),n("autofocus",null),n("direction","ltr",(function(e,t){return e.doc.setDirection(t)}),!0),n("phrases",null)}function Fs(e,t,n){if(!t!=!(n&&n!=Ls)){var r=e.display.dragFunctions,i=t?me:ve;i(e.display.scroller,"dragstart",r.start),i(e.display.scroller,"dragenter",r.enter),i(e.display.scroller,"dragover",r.over),i(e.display.scroller,"dragleave",r.leave),i(e.display.scroller,"drop",r.drop)}}function Ps(e){e.options.lineWrapping?(M(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):(N(e.display.wrapper,"CodeMirror-wrap"),un(e)),Rr(e),jr(e),pr(e),setTimeout((function(){return bi(e)}),100)}function js(e,t){var n=this;if(!(this instanceof js))return new js(e,t);this.options=t=t?j(t):{},j(As,t,!1);var r=t.value;"string"==typeof r?r=new Sa(r,t.mode,null,t.lineSeparator,t.direction):t.mode&&(r.modeOption=t.mode),this.doc=r;var i=new js.inputStyles[t.inputStyle](this),o=this.display=new Zi(e,r,i,t);for(var a in o.wrapper.CodeMirror=this,Ds(this),t.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),wi(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:-1,cutIncoming:-1,selectingText:!1,draggingText:!1,highlight:new U,keySeq:null,specialChars:null},t.autofocus&&!y&&o.input.focus(),s&&l<11&&setTimeout((function(){return n.display.input.reset(!0)}),20),Vs(this),La(),Si(this),this.curOp.forceUpdate=!0,Eo(this,r),t.autofocus&&!y||this.hasFocus()?setTimeout((function(){n.hasFocus()&&!n.state.focused&&Jr(n)}),20):Zr(this),Ms)Ms.hasOwnProperty(a)&&Ms[a](this,t[a],Ls);Qi(this),t.finishInit&&t.finishInit(this);for(var c=0;c400}r(o,"finishTouch"),r(a,"isMouseLikeTouchEvent"),r(u,"farAway"),me(t.scroller,"touchstart",(function(r){if(!be(e,r)&&!a(r)&&!_s(e,r)){t.input.ensurePolled(),clearTimeout(n);var o=+new Date;t.activeTouch={start:o,moved:!1,prev:o-i.end<=300?i:null},1==r.touches.length&&(t.activeTouch.left=r.touches[0].pageX,t.activeTouch.top=r.touches[0].pageY)}})),me(t.scroller,"touchmove",(function(){t.activeTouch&&(t.activeTouch.moved=!0)})),me(t.scroller,"touchend",(function(n){var r=t.activeTouch;if(r&&!Gn(t,n)&&null!=r.left&&!r.moved&&new Date-r.start<300){var i,a=e.coordsChar(t.activeTouch,"page");i=!r.prev||u(r,r.prev)?new ao(a,a):!r.prev.prev||u(r,r.prev.prev)?e.findWordAt(a):new ao(at(a.line,0),pt(e.doc,at(a.line+1,0))),e.setSelection(i.anchor,i.head),e.focus(),Ce(n)}o()})),me(t.scroller,"touchcancel",o),me(t.scroller,"scroll",(function(){t.scroller.clientHeight&&(pi(e,t.scroller.scrollTop),mi(e,t.scroller.scrollLeft,!0),ye(e,"scroll",e))})),me(t.scroller,"mousewheel",(function(t){return io(e,t)})),me(t.scroller,"DOMMouseScroll",(function(t){return io(e,t)})),me(t.wrapper,"scroll",(function(){return t.wrapper.scrollTop=t.wrapper.scrollLeft=0})),t.dragFunctions={enter:function(t){be(e,t)||ke(t)},over:function(t){be(e,t)||(_a(e,t),ke(t))},start:function(t){return Na(e,t)},drop:Ai(e,ka),leave:function(t){be(e,t)||Oa(e)}};var c=t.input.getField();me(c,"keyup",(function(t){return fs.call(e,t)})),me(c,"keydown",Ai(e,cs)),me(c,"keypress",Ai(e,ps)),me(c,"focus",(function(t){return Jr(e,t)})),me(c,"blur",(function(t){return Zr(e,t)}))}r(Rs,"defineOptions"),r(Fs,"dragDropChanged"),r(Ps,"wrappingChanged"),r(js,"CodeMirror"),js.defaults=As,js.optionHandlers=Ms,r(Vs,"registerEventHandlers");var Us=[];function Bs(e,t,n,r){var i,o=e.doc;null==n&&(n="add"),"smart"==n&&(o.mode.indent?i=Et(e,t).state:n="prev");var a=e.options.tabSize,s=Je(o,t),l=V(s.text,null,a);s.stateAfter&&(s.stateAfter=null);var u,c=s.text.match(/^\s*/)[0];if(r||/\S/.test(s.text)){if("smart"==n&&((u=o.mode.indent(i,s.text.slice(c.length),s.text))==q||u>150)){if(!r)return;n="prev"}}else u=0,n="not";"prev"==n?u=t>o.first?V(Je(o,t-1).text,null,a):0:"add"==n?u=l+e.options.indentUnit:"subtract"==n?u=l-e.options.indentUnit:"number"==typeof n&&(u=l+n),u=Math.max(0,u);var d="",f=0;if(e.options.indentWithTabs)for(var p=Math.floor(u/a);p;--p)f+=a,d+="\t";if(fa,l=Re(t),u=null;if(s&&r.ranges.length>1)if($s&&$s.text.join("\n")==t){if(r.ranges.length%$s.text.length==0){u=[];for(var c=0;c<$s.text.length;c++)u.push(o.splitLines($s.text[c]))}}else l.length==r.ranges.length&&e.options.pasteLinesPerSelection&&(u=Y(l,(function(e){return[e]})));for(var d=e.curOp.updateInput,f=r.ranges.length-1;f>=0;f--){var p=r.ranges[f],h=p.from(),m=p.to();p.empty()&&(n&&n>0?h=at(h.line,h.ch-n):e.state.overwrite&&!s?m=at(m.line,Math.min(Je(o,m.line).text.length,m.ch+X(l).length)):s&&$s&&$s.lineWise&&$s.text.join("\n")==l.join("\n")&&(h=m=at(h.line,0)));var g={from:h,to:m,text:u?u[f%u.length]:l,origin:i||(s?"paste":e.state.cutIncoming>a?"cut":"+input")};Zo(e.doc,g),In(e,"inputRead",e,g)}t&&!s&&zs(e,t),li(e),e.curOp.updateInput<2&&(e.curOp.updateInput=d),e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=-1}function Gs(e,t){var n=e.clipboardData&&e.clipboardData.getData("Text");if(n)return e.preventDefault(),t.isReadOnly()||t.options.disableInput||Li(t,(function(){return Hs(t,n,0,null,"paste")})),!0}function zs(e,t){if(e.options.electricChars&&e.options.smartIndent)for(var n=e.doc.sel,r=n.ranges.length-1;r>=0;r--){var i=n.ranges[r];if(!(i.head.ch>100||r&&n.ranges[r-1].head.line==i.head.line)){var o=e.getModeAt(i.head),a=!1;if(o.electricChars){for(var s=0;s-1){a=Bs(e,i.head.line,"smart");break}}else o.electricInput&&o.electricInput.test(Je(e.doc,i.head.line).text.slice(0,i.head.ch))&&(a=Bs(e,i.head.line,"smart"));a&&In(e,"electricInput",e,i.head.line)}}}function Ws(e){for(var t=[],n=[],r=0;rn&&(Bs(this,i.head.line,e,!0),n=i.head.line,r==this.doc.sel.primIndex&&li(this));else{var o=i.from(),a=i.to(),s=Math.max(n,o.line);n=Math.min(this.lastLine(),a.line-(a.ch?0:1))+1;for(var l=s;l0&&Vo(this.doc,r,new ao(o,u[r].to()),H)}}})),getTokenAt:function(e,t){return xt(this,e,t)},getLineTokens:function(e,t){return xt(this,at(e),t,!0)},getTokenTypeAt:function(e){e=pt(this.doc,e);var t,n=bt(this,Je(this.doc,e.line)),r=0,i=(n.length-1)/2,o=e.ch;if(0==o)t=n[2];else for(;;){var a=r+i>>1;if((a?n[2*a-1]:0)>=o)i=a;else{if(!(n[2*a+1]o&&(e=o,i=!0),r=Je(this.doc,e)}else r=e;return vr(this,r,{top:0,left:0},t||"page",n||i).top+(i?this.doc.height-sn(r):0)},defaultTextHeight:function(){return Ir(this.display)},defaultCharWidth:function(){return Dr(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(e,t,n,r,i){var o=this.display,a=(e=Er(this,pt(this.doc,e))).bottom,s=e.left;if(t.style.position="absolute",t.setAttribute("cm-ignore-events","true"),this.display.input.setUneditable(t),o.sizer.appendChild(t),"over"==r)a=e.top;else if("above"==r||"near"==r){var l=Math.max(o.wrapper.clientHeight,this.doc.height),u=Math.max(o.sizer.clientWidth,o.lineSpace.clientWidth);("above"==r||e.bottom+t.offsetHeight>l)&&e.top>t.offsetHeight?a=e.top-t.offsetHeight:e.bottom+t.offsetHeight<=l&&(a=e.bottom),s+t.offsetWidth>u&&(s=u-t.offsetWidth)}t.style.top=a+"px",t.style.left=t.style.right="","right"==i?(s=o.sizer.clientWidth-t.offsetWidth,t.style.right="0px"):("left"==i?s=0:"middle"==i&&(s=(o.sizer.clientWidth-t.offsetWidth)/2),t.style.left=s+"px"),n&&oi(this,{left:s,top:a,right:s+t.offsetWidth,bottom:a+t.offsetHeight})},triggerOnKeyDown:Mi(cs),triggerOnKeyPress:Mi(ps),triggerOnKeyUp:fs,triggerOnMouseDown:Mi(bs),execCommand:function(e){if(Ja.hasOwnProperty(e))return Ja[e].call(null,this)},triggerElectric:Mi((function(e){zs(this,e)})),findPosH:function(e,t,n,r){var i=1;t<0&&(i=-1,t=-t);for(var o=pt(this.doc,e),a=0;a0&&a(t.charAt(n-1));)--n;for(;r.5||this.options.lineWrapping)&&Rr(this),ye(this,"refresh",this)})),swapDoc:Mi((function(e){var t=this.doc;return t.cm=null,this.state.selectingText&&this.state.selectingText(),Eo(this,e),pr(this),this.display.input.reset(),ui(this,e.scrollLeft,e.scrollTop),this.curOp.forceScroll=!0,In(this,"swapDoc",this,t),t})),phrase:function(e){var t=this.options.phrases;return t&&Object.prototype.hasOwnProperty.call(t,e)?t[e]:e},getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},we(e),e.registerHelper=function(t,r,i){n.hasOwnProperty(t)||(n[t]=e[t]={_global:[]}),n[t][r]=i},e.registerGlobalHelper=function(t,r,i,o){e.registerHelper(t,r,o),n[t]._global.push({pred:i,val:o})}}function Ys(e,t,n,i,o){var a=t,s=n,l=Je(e,t.line),u=o&&"rtl"==e.direction?-n:n;function c(){var n=t.line+u;return!(n=e.first+e.size)&&(t=new at(n,t.ch,t.sticky),l=Je(e,n))}function d(r){var a;if("codepoint"==i){var s=l.text.charCodeAt(t.ch+(n>0?0:-1));if(isNaN(s))a=null;else{var d=n>0?s>=55296&&s<56320:s>=56320&&s<57343;a=new at(t.line,Math.max(0,Math.min(l.text.length,t.ch+n*(d?2:1))),-n)}}else a=o?Ya(e.cm,l,t,n):Qa(l,t,n);if(null==a){if(r||!c())return!1;t=Xa(o,e.cm,l,t.line,u)}else t=a;return!0}if(r(c,"findNextLine"),r(d,"moveOnce"),"char"==i||"codepoint"==i)d();else if("column"==i)d(!0);else if("word"==i||"group"==i)for(var f=null,p="group"==i,h=e.cm&&e.cm.getHelper(t,"wordChars"),m=!0;!(n<0)||d(!m);m=!1){var g=l.text.charAt(t.ch)||"\n",v=re(g,h)?"w":p&&"\n"==g?"n":!p||/\s/.test(g)?null:"p";if(!p||m||v||(v="s"),f&&f!=v){n<0&&(n=1,d(),t.sticky="after");break}if(v&&(f=v),n>0&&!d(!m))break}var y=Qo(e,t,a,s,!0);return lt(a,y)&&(y.hitSide=!0),y}function Js(e,t,n,r){var i,o,a=e.doc,s=t.left;if("page"==r){var l=Math.min(e.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight),u=Math.max(l-.5*Ir(e.display),3);i=(n>0?t.bottom:t.top)+n*u}else"line"==r&&(i=n>0?t.bottom+3:t.top-3);for(;(o=Cr(e,s,i)).outside;){if(n<0?i<=0:i>=a.height){o.hitSide=!0;break}i+=5*n}return o}r(qs,"setLastCopied"),r(Hs,"applyTextInput"),r(Gs,"handlePaste"),r(zs,"triggerElectric"),r(Ws,"copyableRanges"),r(Ks,"disableBrowserMagic"),r(Qs,"hiddenTextarea"),r(Xs,"addEditorMethods"),r(Ys,"findPosH"),r(Js,"findPosV");var Zs=r((function(e){this.cm=e,this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null,this.polling=new U,this.composing=null,this.gracePeriod=!1,this.readDOMTimeout=null}),"ContentEditableInput");function el(e,t){var n=nr(e,t.line);if(!n||n.hidden)return null;var r=Je(e.doc,t.line),i=Zn(n,r,t.line),o=pe(r,e.doc.direction),a="left";o&&(a=de(o,t.ch)%2?"right":"left");var s=sr(i.map,t.ch,a);return s.offset="right"==s.collapse?s.end:s.start,s}function tl(e){for(var t=e;t;t=t.parentNode)if(/CodeMirror-gutter-wrapper/.test(t.className))return!0;return!1}function nl(e,t){return t&&(e.bad=!0),e}function rl(e,t,n,i,o){var a="",s=!1,l=e.doc.lineSeparator(),u=!1;function c(e){return function(t){return t.id==e}}function d(){s&&(a+=l,u&&(a+=l),s=u=!1)}function f(e){e&&(d(),a+=e)}function p(t){if(1==t.nodeType){var n=t.getAttribute("cm-text");if(n)return void f(n);var r,a=t.getAttribute("cm-marker");if(a){var h=e.findMarks(at(i,0),at(o+1,0),c(+a));return void(h.length&&(r=h[0].find(0))&&f(Ze(e.doc,r.from,r.to).join(l)))}if("false"==t.getAttribute("contenteditable"))return;var m=/^(pre|div|p|li|table|br)$/i.test(t.nodeName);if(!/^br$/i.test(t.nodeName)&&0==t.textContent.length)return;m&&d();for(var g=0;g=t.display.viewTo||o.line=t.display.viewFrom&&el(t,i)||{node:l[0].measure.map[2],offset:0},c=o.liner.firstLine()&&(a=at(a.line-1,Je(r.doc,a.line-1).length)),s.ch==Je(r.doc,s.line).text.length&&s.linei.viewTo-1)return!1;a.line==i.viewFrom||0==(e=Pr(r,a.line))?(t=nt(i.view[0].line),n=i.view[0].node):(t=nt(i.view[e].line),n=i.view[e-1].node.nextSibling);var l,u,c=Pr(r,s.line);if(c==i.view.length-1?(l=i.viewTo-1,u=i.lineDiv.lastChild):(l=nt(i.view[c+1].line)-1,u=i.view[c+1].node.previousSibling),!n)return!1;for(var d=r.doc.splitLines(rl(r,n,u,t,l)),f=Ze(r.doc,at(t,0),at(l,Je(r.doc,l).text.length));d.length>1&&f.length>1;)if(X(d)==X(f))d.pop(),f.pop(),l--;else{if(d[0]!=f[0])break;d.shift(),f.shift(),t++}for(var p=0,h=0,m=d[0],g=f[0],v=Math.min(m.length,g.length);pa.ch&&y.charCodeAt(y.length-h-1)==b.charCodeAt(b.length-h-1);)p--,h++;d[d.length-1]=y.slice(0,y.length-h).replace(/^\u200b+/,""),d[0]=d[0].slice(p).replace(/\u200b+$/,"");var T=at(t,p),w=at(l,f.length?X(f).length-h:0);return d.length>1||d[0]||st(T,w)?(oa(r.doc,d,T,w,"+input"),!0):void 0},Zs.prototype.ensurePolled=function(){this.forceCompositionEnd()},Zs.prototype.reset=function(){this.forceCompositionEnd()},Zs.prototype.forceCompositionEnd=function(){this.composing&&(clearTimeout(this.readDOMTimeout),this.composing=null,this.updateFromDOM(),this.div.blur(),this.div.focus())},Zs.prototype.readFromDOMSoon=function(){var e=this;null==this.readDOMTimeout&&(this.readDOMTimeout=setTimeout((function(){if(e.readDOMTimeout=null,e.composing){if(!e.composing.done)return;e.composing=null}e.updateFromDOM()}),80))},Zs.prototype.updateFromDOM=function(){var e=this;!this.cm.isReadOnly()&&this.pollContent()||Li(this.cm,(function(){return jr(e.cm)}))},Zs.prototype.setUneditable=function(e){e.contentEditable="false"},Zs.prototype.onKeyPress=function(e){0==e.charCode||this.composing||(e.preventDefault(),this.cm.isReadOnly()||Ai(this.cm,Hs)(this.cm,String.fromCharCode(null==e.charCode?e.keyCode:e.charCode),0))},Zs.prototype.readOnlyChanged=function(e){this.div.contentEditable=String("nocursor"!=e)},Zs.prototype.onContextMenu=function(){},Zs.prototype.resetPosition=function(){},Zs.prototype.needsContentAttribute=!0,r(el,"posToDOM"),r(tl,"isInGutter"),r(nl,"badPos"),r(rl,"domTextBetween"),r(il,"domToPos"),r(ol,"locateNodeInLineView");var al=r((function(e){this.cm=e,this.prevInput="",this.pollingFast=!1,this.polling=new U,this.hasSelection=!1,this.composing=null}),"TextareaInput");function sl(e,t){if((t=t?j(t):{}).value=e.value,!t.tabindex&&e.tabIndex&&(t.tabindex=e.tabIndex),!t.placeholder&&e.placeholder&&(t.placeholder=e.placeholder),null==t.autofocus){var n=A();t.autofocus=n==e||null!=e.getAttribute("autofocus")&&n==document.body}function i(){e.value=l.getValue()}var o;if(r(i,"save"),e.form&&(me(e.form,"submit",i),!t.leaveSubmitMethodAlone)){var a=e.form;o=a.submit;try{var s=a.submit=function(){i(),a.submit=o,a.submit(),a.submit=s}}catch(e){}}t.finishInit=function(n){n.save=i,n.getTextArea=function(){return e},n.toTextArea=function(){n.toTextArea=isNaN,i(),e.parentNode.removeChild(n.getWrapperElement()),e.style.display="",e.form&&(ve(e.form,"submit",i),t.leaveSubmitMethodAlone||"function"!=typeof e.form.submit||(e.form.submit=o))}},e.style.display="none";var l=js((function(t){return e.parentNode.insertBefore(t,e.nextSibling)}),t);return l}function ll(e){e.off=ve,e.on=me,e.wheelEventPixels=ro,e.Doc=Sa,e.splitLines=Re,e.countColumn=V,e.findColumn=W,e.isWordChar=ne,e.Pass=q,e.signal=ye,e.Line=cn,e.changeEnd=uo,e.scrollbarModel=Ti,e.Pos=at,e.cmpPos=st,e.modes=Ue,e.mimeModes=Be,e.resolveMode=He,e.getMode=Ge,e.modeExtensions=ze,e.extendMode=We,e.copyState=Ke,e.startState=Xe,e.innerMode=Qe,e.commands=Ja,e.keyMap=Va,e.keyName=Ga,e.isModifierKey=qa,e.lookupKey=$a,e.normalizeKeyMap=Ba,e.StringStream=Ye,e.SharedTextMarker=ya,e.TextMarker=ga,e.LineWidget=fa,e.e_preventDefault=Ce,e.e_stopPropagation=Se,e.e_stop=ke,e.addClass=M,e.contains=L,e.rmClass=N,e.keyNames=Ra}al.prototype.init=function(e){var t=this,n=this,i=this.cm;this.createField(e);var o=this.textarea;function a(e){if(!be(i,e)){if(i.somethingSelected())qs({lineWise:!1,text:i.getSelections()});else{if(!i.options.lineWiseCopyCut)return;var t=Ws(i);qs({lineWise:!0,text:t.text}),"cut"==e.type?i.setSelections(t.ranges,null,H):(n.prevInput="",o.value=t.text.join("\n"),F(o))}"cut"==e.type&&(i.state.cutIncoming=+new Date)}}e.wrapper.insertBefore(this.wrapper,e.wrapper.firstChild),g&&(o.style.width="0px"),me(o,"input",(function(){s&&l>=9&&t.hasSelection&&(t.hasSelection=null),n.poll()})),me(o,"paste",(function(e){be(i,e)||Gs(e,i)||(i.state.pasteIncoming=+new Date,n.fastPoll())})),r(a,"prepareCopyCut"),me(o,"cut",a),me(o,"copy",a),me(e.scroller,"paste",(function(t){if(!Gn(e,t)&&!be(i,t)){if(!o.dispatchEvent)return i.state.pasteIncoming=+new Date,void n.focus();var r=new Event("paste");r.clipboardData=t.clipboardData,o.dispatchEvent(r)}})),me(e.lineSpace,"selectstart",(function(t){Gn(e,t)||Ce(t)})),me(o,"compositionstart",(function(){var e=i.getCursor("from");n.composing&&n.composing.range.clear(),n.composing={start:e,range:i.markText(e,i.getCursor("to"),{className:"CodeMirror-composing"})}})),me(o,"compositionend",(function(){n.composing&&(n.poll(),n.composing.range.clear(),n.composing=null)}))},al.prototype.createField=function(e){this.wrapper=Qs(),this.textarea=this.wrapper.firstChild},al.prototype.screenReaderLabelChanged=function(e){e?this.textarea.setAttribute("aria-label",e):this.textarea.removeAttribute("aria-label")},al.prototype.prepareSelection=function(){var e=this.cm,t=e.display,n=e.doc,r=Gr(e);if(e.options.moveInputWithCursor){var i=Er(e,n.sel.primary().head,"div"),o=t.wrapper.getBoundingClientRect(),a=t.lineDiv.getBoundingClientRect();r.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,i.top+a.top-o.top)),r.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,i.left+a.left-o.left))}return r},al.prototype.showSelection=function(e){var t=this.cm.display;O(t.cursorDiv,e.cursors),O(t.selectionDiv,e.selection),null!=e.teTop&&(this.wrapper.style.top=e.teTop+"px",this.wrapper.style.left=e.teLeft+"px")},al.prototype.reset=function(e){if(!this.contextMenuPending&&!this.composing){var t=this.cm;if(t.somethingSelected()){this.prevInput="";var n=t.getSelection();this.textarea.value=n,t.state.focused&&F(this.textarea),s&&l>=9&&(this.hasSelection=n)}else e||(this.prevInput=this.textarea.value="",s&&l>=9&&(this.hasSelection=null))}},al.prototype.getField=function(){return this.textarea},al.prototype.supportsTouch=function(){return!1},al.prototype.focus=function(){if("nocursor"!=this.cm.options.readOnly&&(!y||A()!=this.textarea))try{this.textarea.focus()}catch(e){}},al.prototype.blur=function(){this.textarea.blur()},al.prototype.resetPosition=function(){this.wrapper.style.top=this.wrapper.style.left=0},al.prototype.receivedFocus=function(){this.slowPoll()},al.prototype.slowPoll=function(){var e=this;this.pollingFast||this.polling.set(this.cm.options.pollInterval,(function(){e.poll(),e.cm.state.focused&&e.slowPoll()}))},al.prototype.fastPoll=function(){var e=!1,t=this;function n(){t.poll()||e?(t.pollingFast=!1,t.slowPoll()):(e=!0,t.polling.set(60,n))}t.pollingFast=!0,r(n,"p"),t.polling.set(20,n)},al.prototype.poll=function(){var e=this,t=this.cm,n=this.textarea,r=this.prevInput;if(this.contextMenuPending||!t.state.focused||Fe(n)&&!r&&!this.composing||t.isReadOnly()||t.options.disableInput||t.state.keySeq)return!1;var i=n.value;if(i==r&&!t.somethingSelected())return!1;if(s&&l>=9&&this.hasSelection===i||b&&/[\uf700-\uf7ff]/.test(i))return t.display.input.reset(),!1;if(t.doc.sel==t.display.selForContextMenu){var o=i.charCodeAt(0);if(8203!=o||r||(r="​"),8666==o)return this.reset(),this.cm.execCommand("undo")}for(var a=0,u=Math.min(r.length,i.length);a1e3||i.indexOf("\n")>-1?n.value=e.prevInput="":e.prevInput=i,e.composing&&(e.composing.range.clear(),e.composing.range=t.markText(e.composing.start,t.getCursor("to"),{className:"CodeMirror-composing"}))})),!0},al.prototype.ensurePolled=function(){this.pollingFast&&this.poll()&&(this.pollingFast=!1)},al.prototype.onKeyPress=function(){s&&l>=9&&(this.hasSelection=null),this.fastPoll()},al.prototype.onContextMenu=function(e){var t=this,n=t.cm,i=n.display,o=t.textarea;t.contextMenuPending&&t.contextMenuPending();var a=Fr(n,e),c=i.scroller.scrollTop;if(a&&!f){n.options.resetSelectionOnContextMenu&&-1==n.doc.sel.contains(a)&&Ai(n,qo)(n.doc,lo(a),H);var d,p=o.style.cssText,h=t.wrapper.style.cssText,m=t.wrapper.offsetParent.getBoundingClientRect();if(t.wrapper.style.cssText="position: static",o.style.cssText="position: absolute; width: 30px; height: 30px;\n top: "+(e.clientY-m.top-5)+"px; left: "+(e.clientX-m.left-5)+"px;\n z-index: 1000; background: "+(s?"rgba(255, 255, 255, .05)":"transparent")+";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",u&&(d=window.scrollY),i.input.focus(),u&&window.scrollTo(null,d),i.input.reset(),n.somethingSelected()||(o.value=t.prevInput=" "),t.contextMenuPending=y,i.selForContextMenu=n.doc.sel,clearTimeout(i.detectingSelectAll),r(v,"prepareSelectAllHack"),r(y,"rehide"),s&&l>=9&&v(),S){ke(e);var g=r((function(){ve(window,"mouseup",g),setTimeout(y,20)}),"mouseup");me(window,"mouseup",g)}else setTimeout(y,50)}function v(){if(null!=o.selectionStart){var e=n.somethingSelected(),r="​"+(e?o.value:"");o.value="⇚",o.value=r,t.prevInput=e?"":"​",o.selectionStart=1,o.selectionEnd=r.length,i.selForContextMenu=n.doc.sel}}function y(){if(t.contextMenuPending==y&&(t.contextMenuPending=!1,t.wrapper.style.cssText=h,o.style.cssText=p,s&&l<9&&i.scrollbars.setScrollTop(i.scroller.scrollTop=c),null!=o.selectionStart)){(!s||s&&l<9)&&v();var e=0,a=r((function(){i.selForContextMenu==n.doc.sel&&0==o.selectionStart&&o.selectionEnd>0&&"​"==t.prevInput?Ai(n,Yo)(n):e++<10?i.detectingSelectAll=setTimeout(a,500):(i.selForContextMenu=null,i.input.reset())}),"poll");i.detectingSelectAll=setTimeout(a,200)}}},al.prototype.readOnlyChanged=function(e){e||this.reset(),this.textarea.disabled="nocursor"==e,this.textarea.readOnly=!!e},al.prototype.setUneditable=function(){},al.prototype.needsContentAttribute=!1,r(sl,"fromTextArea"),r(ll,"addLegacyProps"),Rs(js),Xs(js);var ul="iter insert remove copy getEditor constructor".split(" ");for(var cl in Sa.prototype)Sa.prototype.hasOwnProperty(cl)&&B(ul,cl)<0&&(js.prototype[cl]=function(e){return function(){return e.apply(this.doc,arguments)}}(Sa.prototype[cl]));return we(Sa),js.inputStyles={textarea:al,contenteditable:Zs},js.defineMode=function(e){js.defaults.mode||"null"==e||(js.defaults.mode=e),$e.apply(this,arguments)},js.defineMIME=qe,js.defineMode("null",(function(){return{token:function(e){return e.skipToEnd()}}})),js.defineMIME("text/plain","null"),js.defineExtension=function(e,t){js.prototype[e]=t},js.defineDocExtension=function(e,t){Sa.prototype[e]=t},js.fromTextArea=sl,ll(js),js.version="5.65.3",js}()}(o);var a=o.exports;e.C=a;var s=i({__proto__:null,default:a},[o.exports]);e.c=s},void 0===(o=r.apply(t,i))||(e.exports=o)},6754:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535)],void 0===(o="function"==typeof(r=function(e,t){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.c=void 0;var n=Object.defineProperty,r=(e,t)=>n(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};!function(e){var t={},n=/[^\s\u00a0]/,i=e.Pos,o=e.cmpPos;function a(e){var t=e.search(n);return-1==t?0:t}function s(e,t,n){return/\bstring\b/.test(e.getTokenTypeAt(i(t.line,0)))&&!/^[\'\"\`]/.test(n)}function l(e,t){var n=e.getMode();return!1!==n.useInnerComments&&n.innerMode?e.getModeAt(t):n}r(a,"firstNonWS"),e.commands.toggleComment=function(e){e.toggleComment()},e.defineExtension("toggleComment",(function(e){e||(e=t);for(var n=this,r=1/0,o=this.listSelections(),a=null,s=o.length-1;s>=0;s--){var l=o[s].from(),u=o[s].to();l.line>=r||(u.line>=r&&(u=i(r,0)),r=l.line,null==a?n.uncomment(l,u,e)?a="un":(n.lineComment(l,u,e),a="line"):"un"==a?n.uncomment(l,u,e):n.lineComment(l,u,e))}})),r(s,"probablyInsideString"),r(l,"getMode"),e.defineExtension("lineComment",(function(e,r,o){o||(o=t);var u=this,c=l(u,e),d=u.getLine(e.line);if(null!=d&&!s(u,e,d)){var f=o.lineComment||c.lineComment;if(f){var p=Math.min(0!=r.ch||r.line==e.line?r.line+1:r.line,u.lastLine()+1),h=null==o.padding?" ":o.padding,m=o.commentBlankLines||e.line==r.line;u.operation((function(){if(o.indent){for(var t=null,r=e.line;rs.length)&&(t=s)}for(r=e.line;rf||s.operation((function(){if(0!=a.fullLines){var t=n.test(s.getLine(f));s.replaceRange(p+d,i(f)),s.replaceRange(c+p,i(e.line,0));var l=a.blockCommentLead||u.blockCommentLead;if(null!=l)for(var h=e.line+1;h<=f;++h)(h!=f||t)&&s.replaceRange(l+p,i(h,0))}else{var m=0==o(s.getCursor("to"),r),g=!s.somethingSelected();s.replaceRange(d,r),m&&s.setSelection(g?r:s.getCursor("from"),r),s.replaceRange(c,e)}}))}}else(a.lineComment||u.lineComment)&&0!=a.fullLines&&s.lineComment(e,r,a)})),e.defineExtension("uncomment",(function(e,r,o){o||(o=t);var a,s=this,u=l(s,e),c=Math.min(0!=r.ch||r.line==e.line?r.line:r.line-1,s.lastLine()),d=Math.min(e.line,c),f=o.lineComment||u.lineComment,p=[],h=null==o.padding?" ":o.padding;e:if(f){for(var m=d;m<=c;++m){var g=s.getLine(m),v=g.indexOf(f);if(v>-1&&!/comment/.test(s.getTokenTypeAt(i(m,v+1)))&&(v=-1),-1==v&&n.test(g))break e;if(v>-1&&n.test(g.slice(0,v)))break e;p.push(g)}if(s.operation((function(){for(var e=d;e<=c;++e){var t=p[e-d],n=t.indexOf(f),r=n+f.length;n<0||(t.slice(r,r+h.length)==h&&(r+=h.length),a=!0,s.replaceRange("",i(e,n),i(e,r)))}})),a)return!0}var y=o.blockCommentStart||u.blockCommentStart,b=o.blockCommentEnd||u.blockCommentEnd;if(!y||!b)return!1;var E=o.blockCommentLead||u.blockCommentLead,T=s.getLine(d),w=T.indexOf(y);if(-1==w)return!1;var C=c==d?T:s.getLine(c),S=C.indexOf(b,c==d?w+y.length:0),x=i(d,w+1),k=i(c,S+1);if(-1==S||!/comment/.test(s.getTokenTypeAt(x))||!/comment/.test(s.getTokenTypeAt(k))||s.getRange(x,k,"\n").indexOf(b)>-1)return!1;var N=T.lastIndexOf(y,e.ch),_=-1==N?-1:T.slice(0,e.ch).indexOf(b,N+y.length);if(-1!=N&&-1!=_&&_+b.length!=e.ch)return!1;_=C.indexOf(b,r.ch);var O=C.slice(r.ch).lastIndexOf(y,_-r.ch);return N=-1==_||-1==O?-1:r.ch+O,(-1==_||-1==N||N==r.ch)&&(s.operation((function(){s.replaceRange("",i(c,S-(h&&C.slice(S-h.length,S)==h?h.length:0)),i(c,S+b.length));var e=w+y.length;if(h&&T.slice(e,e+h.length)==h&&(e+=h.length),s.replaceRange("",i(d,w),i(d,e)),E)for(var t=d+1;t<=c;++t){var r=s.getLine(t),o=r.indexOf(E);if(-1!=o&&!n.test(r.slice(0,o))){var a=o+E.length;h&&r.slice(a,a+h.length)==h&&(a+=h.length),s.replaceRange("",i(t,o),i(t,a))}}})),!0)}))}(t.a.exports);var a=i({__proto__:null,default:o.exports},[o.exports]);e.c=a})?r.apply(t,i):r)||(e.exports=o)},8058:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535)],void 0===(o="function"==typeof(r=function(e,t){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.d=e.a=void 0;var n=Object.defineProperty,r=(e,t)=>n(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};e.a=o,function(e){function t(t,n,r){var i,o=t.getWrapperElement();return(i=o.appendChild(document.createElement("div"))).className=r?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof n?i.innerHTML=n:i.appendChild(n),e.addClass(o,"dialog-opened"),i}function n(e,t){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=t}r(t,"dialogDiv"),r(n,"closeNotification"),e.defineExtension("openDialog",(function(i,o,a){a||(a={}),n(this,null);var s=t(this,i,a.bottom),l=!1,u=this;function c(t){if("string"==typeof t)f.value=t;else{if(l)return;l=!0,e.rmClass(s.parentNode,"dialog-opened"),s.parentNode.removeChild(s),u.focus(),a.onClose&&a.onClose(s)}}r(c,"close");var d,f=s.getElementsByTagName("input")[0];return f?(f.focus(),a.value&&(f.value=a.value,!1!==a.selectValueOnOpen&&f.select()),a.onInput&&e.on(f,"input",(function(e){a.onInput(e,f.value,c)})),a.onKeyUp&&e.on(f,"keyup",(function(e){a.onKeyUp(e,f.value,c)})),e.on(f,"keydown",(function(t){a&&a.onKeyDown&&a.onKeyDown(t,f.value,c)||((27==t.keyCode||!1!==a.closeOnEnter&&13==t.keyCode)&&(f.blur(),e.e_stop(t),c()),13==t.keyCode&&o(f.value,t))})),!1!==a.closeOnBlur&&e.on(s,"focusout",(function(e){null!==e.relatedTarget&&c()}))):(d=s.getElementsByTagName("button")[0])&&(e.on(d,"click",(function(){c(),u.focus()})),!1!==a.closeOnBlur&&e.on(d,"blur",c),d.focus()),c})),e.defineExtension("openConfirm",(function(i,o,a){n(this,null);var s=t(this,i,a&&a.bottom),l=s.getElementsByTagName("button"),u=!1,c=this,d=1;function f(){u||(u=!0,e.rmClass(s.parentNode,"dialog-opened"),s.parentNode.removeChild(s),c.focus())}r(f,"close"),l[0].focus();for(var p=0;pn(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};!function(e){function t(t,i,a,s){if(a&&a.call){var l=a;a=null}else l=o(t,a,"rangeFinder");"number"==typeof i&&(i=e.Pos(i,0));var u=o(t,a,"minFoldSize");function c(e){var n=l(t,i);if(!n||n.to.line-n.from.linet.firstLine();)i=e.Pos(i.line-1,0),d=c(!1);if(d&&!d.cleared&&"unfold"!==s){var f=n(t,a,d);e.on(f,"mousedown",(function(t){p.clear(),e.e_preventDefault(t)}));var p=t.markText(d.from,d.to,{replacedWith:f,clearOnEnter:o(t,a,"clearOnEnter"),__isFold:!0});p.on("clear",(function(n,r){e.signal(t,"unfold",t,n,r)})),e.signal(t,"fold",t,d.from,d.to)}}function n(e,t,n){var r=o(e,t,"widget");if("function"==typeof r&&(r=r(n.from,n.to)),"string"==typeof r){var i=document.createTextNode(r);(r=document.createElement("span")).appendChild(i),r.className="CodeMirror-foldmarker"}else r&&(r=r.cloneNode(!0));return r}r(t,"doFold"),r(n,"makeWidget"),e.newFoldFunction=function(e,n){return function(r,i){t(r,i,{rangeFinder:e,widget:n})}},e.defineExtension("foldCode",(function(e,n,r){t(this,e,n,r)})),e.defineExtension("isFolded",(function(e){for(var t=this.findMarksAt(e),n=0;n=u){if(f&&l&&f.test(l.className))return;r=a(i.indicatorOpen)}}(r||l)&&e.setGutterMarker(n,i.gutter,r)}))}function l(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}function u(e){var t=e.getViewport(),n=e.state.foldGutter;n&&(e.operation((function(){s(e,t.from,t.to)})),n.from=t.from,n.to=t.to)}function c(e,n,r){var i=e.state.foldGutter;if(i){var a=i.options;if(r==a.gutter){var s=o(e,n);s?s.clear():e.foldCode(t(n,0),a)}}}function d(e){var t=e.state.foldGutter;if(t){var n=t.options;t.from=t.to=0,clearTimeout(t.changeUpdate),t.changeUpdate=setTimeout((function(){u(e)}),n.foldOnChangeTimeSpan||600)}}function f(e){var t=e.state.foldGutter;if(t){var n=t.options;clearTimeout(t.changeUpdate),t.changeUpdate=setTimeout((function(){var n=e.getViewport();t.from==t.to||n.from-t.to>20||t.from-n.to>20?u(e):e.operation((function(){n.fromt.to&&(s(e,t.to,n.to),t.to=n.to)}))}),n.updateViewportTimeSpan||400)}}function p(e,t){var n=e.state.foldGutter;if(n){var r=t.line;r>=n.from&&r=0;e--)t(n[e])}Object.defineProperty(e,"__esModule",{value:!0}),e.f=t,(0,Object.defineProperty)(t,"name",{value:"forEachState",configurable:!0})})?n.apply(t,[t]):n)||(e.exports=r)},7139:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(6856),n(9196),n(3573),n(1850)],void 0===(o="function"==typeof(r=function(e,t,n,r,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"Argument",{enumerable:!0,get:function(){return t.A}}),Object.defineProperty(e,"ArgumentIcon",{enumerable:!0,get:function(){return t.ac}}),Object.defineProperty(e,"Button",{enumerable:!0,get:function(){return t.aH}}),Object.defineProperty(e,"ButtonGroup",{enumerable:!0,get:function(){return t.aI}}),Object.defineProperty(e,"ChevronDownIcon",{enumerable:!0,get:function(){return t.ad}}),Object.defineProperty(e,"ChevronLeftIcon",{enumerable:!0,get:function(){return t.ae}}),Object.defineProperty(e,"ChevronUpIcon",{enumerable:!0,get:function(){return t.af}}),Object.defineProperty(e,"CloseIcon",{enumerable:!0,get:function(){return t.ag}}),Object.defineProperty(e,"CopyIcon",{enumerable:!0,get:function(){return t.ah}}),Object.defineProperty(e,"DOC_EXPLORER_PLUGIN",{enumerable:!0,get:function(){return t._}}),Object.defineProperty(e,"DefaultValue",{enumerable:!0,get:function(){return t.D}}),Object.defineProperty(e,"DeprecatedArgumentIcon",{enumerable:!0,get:function(){return t.ai}}),Object.defineProperty(e,"DeprecatedEnumValueIcon",{enumerable:!0,get:function(){return t.aj}}),Object.defineProperty(e,"DeprecatedFieldIcon",{enumerable:!0,get:function(){return t.ak}}),Object.defineProperty(e,"DeprecationReason",{enumerable:!0,get:function(){return t.w}}),Object.defineProperty(e,"Dialog",{enumerable:!0,get:function(){return t.aJ}}),Object.defineProperty(e,"Directive",{enumerable:!0,get:function(){return t.x}}),Object.defineProperty(e,"DirectiveIcon",{enumerable:!0,get:function(){return t.al}}),Object.defineProperty(e,"DocExplorer",{enumerable:!0,get:function(){return t.y}}),Object.defineProperty(e,"DocsFilledIcon",{enumerable:!0,get:function(){return t.am}}),Object.defineProperty(e,"DocsIcon",{enumerable:!0,get:function(){return t.an}}),Object.defineProperty(e,"EditorContext",{enumerable:!0,get:function(){return t.E}}),Object.defineProperty(e,"EditorContextProvider",{enumerable:!0,get:function(){return t.d}}),Object.defineProperty(e,"EnumValueIcon",{enumerable:!0,get:function(){return t.ao}}),Object.defineProperty(e,"ExecuteButton",{enumerable:!0,get:function(){return t.aS}}),Object.defineProperty(e,"ExecutionContext",{enumerable:!0,get:function(){return t.r}}),Object.defineProperty(e,"ExecutionContextProvider",{enumerable:!0,get:function(){return t.s}}),Object.defineProperty(e,"ExplorerContext",{enumerable:!0,get:function(){return t.z}}),Object.defineProperty(e,"ExplorerContextProvider",{enumerable:!0,get:function(){return t.B}}),Object.defineProperty(e,"ExplorerSection",{enumerable:!0,get:function(){return t.F}}),Object.defineProperty(e,"FieldDocumentation",{enumerable:!0,get:function(){return t.G}}),Object.defineProperty(e,"FieldIcon",{enumerable:!0,get:function(){return t.ap}}),Object.defineProperty(e,"FieldLink",{enumerable:!0,get:function(){return t.J}}),Object.defineProperty(e,"GraphiQLProvider",{enumerable:!0,get:function(){return t.a3}}),Object.defineProperty(e,"HISTORY_PLUGIN",{enumerable:!0,get:function(){return t.$}}),Object.defineProperty(e,"HeaderEditor",{enumerable:!0,get:function(){return t.H}}),Object.defineProperty(e,"History",{enumerable:!0,get:function(){return t.W}}),Object.defineProperty(e,"HistoryContext",{enumerable:!0,get:function(){return t.X}}),Object.defineProperty(e,"HistoryContextProvider",{enumerable:!0,get:function(){return t.Y}}),Object.defineProperty(e,"HistoryIcon",{enumerable:!0,get:function(){return t.aq}}),Object.defineProperty(e,"ImagePreview",{enumerable:!0,get:function(){return t.I}}),Object.defineProperty(e,"ImplementsIcon",{enumerable:!0,get:function(){return t.ar}}),Object.defineProperty(e,"KeyboardShortcutIcon",{enumerable:!0,get:function(){return t.as}}),Object.defineProperty(e,"Listbox",{enumerable:!0,get:function(){return t.aL}}),Object.defineProperty(e,"MagnifyingGlassIcon",{enumerable:!0,get:function(){return t.at}}),Object.defineProperty(e,"MarkdownContent",{enumerable:!0,get:function(){return t.aM}}),Object.defineProperty(e,"Menu",{enumerable:!0,get:function(){return t.aK}}),Object.defineProperty(e,"MergeIcon",{enumerable:!0,get:function(){return t.au}}),Object.defineProperty(e,"PenIcon",{enumerable:!0,get:function(){return t.av}}),Object.defineProperty(e,"PlayIcon",{enumerable:!0,get:function(){return t.aw}}),Object.defineProperty(e,"PluginContext",{enumerable:!0,get:function(){return t.a0}}),Object.defineProperty(e,"PluginContextProvider",{enumerable:!0,get:function(){return t.a1}}),Object.defineProperty(e,"PlusIcon",{enumerable:!0,get:function(){return t.ax}}),Object.defineProperty(e,"PrettifyIcon",{enumerable:!0,get:function(){return t.ay}}),Object.defineProperty(e,"QueryEditor",{enumerable:!0,get:function(){return t.Q}}),Object.defineProperty(e,"ReloadIcon",{enumerable:!0,get:function(){return t.az}}),Object.defineProperty(e,"ResponseEditor",{enumerable:!0,get:function(){return t.R}}),Object.defineProperty(e,"RootTypeIcon",{enumerable:!0,get:function(){return t.aA}}),Object.defineProperty(e,"SchemaContext",{enumerable:!0,get:function(){return t.a4}}),Object.defineProperty(e,"SchemaContextProvider",{enumerable:!0,get:function(){return t.a5}}),Object.defineProperty(e,"SchemaDocumentation",{enumerable:!0,get:function(){return t.K}}),Object.defineProperty(e,"Search",{enumerable:!0,get:function(){return t.M}}),Object.defineProperty(e,"SettingsIcon",{enumerable:!0,get:function(){return t.aB}}),Object.defineProperty(e,"Spinner",{enumerable:!0,get:function(){return t.aN}}),Object.defineProperty(e,"StarFilledIcon",{enumerable:!0,get:function(){return t.aC}}),Object.defineProperty(e,"StarIcon",{enumerable:!0,get:function(){return t.aD}}),Object.defineProperty(e,"StopIcon",{enumerable:!0,get:function(){return t.aE}}),Object.defineProperty(e,"StorageContext",{enumerable:!0,get:function(){return t.a7}}),Object.defineProperty(e,"StorageContextProvider",{enumerable:!0,get:function(){return t.a8}}),Object.defineProperty(e,"Tab",{enumerable:!0,get:function(){return t.aO}}),Object.defineProperty(e,"Tabs",{enumerable:!0,get:function(){return t.aP}}),Object.defineProperty(e,"ToolbarButton",{enumerable:!0,get:function(){return t.aR}}),Object.defineProperty(e,"ToolbarListbox",{enumerable:!0,get:function(){return t.aT}}),Object.defineProperty(e,"ToolbarMenu",{enumerable:!0,get:function(){return t.aU}}),Object.defineProperty(e,"Tooltip",{enumerable:!0,get:function(){return t.aQ}}),Object.defineProperty(e,"TypeDocumentation",{enumerable:!0,get:function(){return t.N}}),Object.defineProperty(e,"TypeIcon",{enumerable:!0,get:function(){return t.aF}}),Object.defineProperty(e,"TypeLink",{enumerable:!0,get:function(){return t.O}}),Object.defineProperty(e,"UnStyledButton",{enumerable:!0,get:function(){return t.aG}}),Object.defineProperty(e,"VariableEditor",{enumerable:!0,get:function(){return t.V}}),Object.defineProperty(e,"useAutoCompleteLeafs",{enumerable:!0,get:function(){return t.u}}),Object.defineProperty(e,"useCopyQuery",{enumerable:!0,get:function(){return t.e}}),Object.defineProperty(e,"useDragResize",{enumerable:!0,get:function(){return t.ab}}),Object.defineProperty(e,"useEditorContext",{enumerable:!0,get:function(){return t.f}}),Object.defineProperty(e,"useExecutionContext",{enumerable:!0,get:function(){return t.v}}),Object.defineProperty(e,"useExplorerContext",{enumerable:!0,get:function(){return t.U}}),Object.defineProperty(e,"useHeaderEditor",{enumerable:!0,get:function(){return t.h}}),Object.defineProperty(e,"useHistoryContext",{enumerable:!0,get:function(){return t.Z}}),Object.defineProperty(e,"useMergeQuery",{enumerable:!0,get:function(){return t.j}}),Object.defineProperty(e,"usePluginContext",{enumerable:!0,get:function(){return t.a2}}),Object.defineProperty(e,"usePrettifyEditors",{enumerable:!0,get:function(){return t.k}}),Object.defineProperty(e,"useQueryEditor",{enumerable:!0,get:function(){return t.m}}),Object.defineProperty(e,"useResponseEditor",{enumerable:!0,get:function(){return t.n}}),Object.defineProperty(e,"useSchemaContext",{enumerable:!0,get:function(){return t.a6}}),Object.defineProperty(e,"useStorageContext",{enumerable:!0,get:function(){return t.a9}}),Object.defineProperty(e,"useTheme",{enumerable:!0,get:function(){return t.aa}}),Object.defineProperty(e,"useVariableEditor",{enumerable:!0,get:function(){return t.q}})})?r.apply(t,i):r)||(e.exports=o)},4840:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(535),n(6980),n(3573),n(6856),n(5609),n(9196),n(1850)],void 0===(o="function"==typeof(r=function(e,t,n,r,i,o,a){"use strict";e.C.registerHelper("hint","graphql",((t,n)=>{const{schema:o,externalFragments:a}=n;if(!o)return;const s=t.getCursor(),l=t.getTokenAt(s),u=null!==l.type&&/"|\w/.test(l.string[0])?l.start:l.end,c=new i.P(s.line,u),d={list:(0,r.g)(o,t.getValue(),c,l,a).map((e=>({text:e.label,type:e.type,description:e.documentation,isDeprecated:e.isDeprecated,deprecationReason:e.deprecationReason}))),from:{line:s.line,ch:u},to:{line:s.line,ch:l.end}};return(null==d?void 0:d.list)&&d.list.length>0&&(d.from=e.C.Pos(d.from.line,d.from.ch),d.to=e.C.Pos(d.to.line,d.to.ch),e.C.signal(t,"hasCompletion",t,d,l)),d}))})?r.apply(t,i):r)||(e.exports=o)},4712:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(535),n(3573),n(2635),n(6856),n(9196),n(1850)],void 0===(o="function"==typeof(r=function(e,t,n,r,i,o){"use strict";var a=Object.defineProperty,s=(e,t)=>a(e,"name",{value:t,configurable:!0});function l(e,t,n){const r=u(n,d(t.string));if(!r)return;const i=null!==t.type&&/"|\w/.test(t.string[0])?t.start:t.end;return{list:r,from:{line:e.line,ch:i},to:{line:e.line,ch:t.end}}}function u(e,t){return t?c(c(e.map((e=>({proximity:f(d(e.text),t),entry:e}))),(e=>e.proximity<=2)),(e=>!e.entry.isDeprecated)).sort(((e,t)=>(e.entry.isDeprecated?1:0)-(t.entry.isDeprecated?1:0)||e.proximity-t.proximity||e.entry.text.length-t.entry.text.length)).map((e=>e.entry)):c(e,(e=>!e.isDeprecated))}function c(e,t){const n=e.filter(t);return 0===n.length?e:n}function d(e){return e.toLowerCase().replaceAll(/\W/g,"")}function f(e,t){let n=p(t,e);return e.length>t.length&&(n-=e.length-t.length-1,n+=0===e.indexOf(t)?0:.5),n}function p(e,t){let n,r;const i=[],o=e.length,a=t.length;for(n=0;n<=o;n++)i[n]=[n];for(r=1;r<=a;r++)i[0][r]=r;for(n=1;n<=o;n++)for(r=1;r<=a;r++){const o=e[n-1]===t[r-1]?0:1;i[n][r]=Math.min(i[n-1][r]+1,i[n][r-1]+1,i[n-1][r-1]+o),n>1&&r>1&&e[n-1]===t[r-2]&&e[n-2]===t[r-1]&&(i[n][r]=Math.min(i[n][r],i[n-2][r-2]+o))}return i[o][a]}function h(e,n,r){const i="Invalid"===n.state.kind?n.state.prevState:n.state,{kind:o,step:a}=i;if("Document"===o&&0===a)return l(e,n,[{text:"{"}]);const{variableToType:s}=r;if(!s)return;const u=m(s,n.state);if("Document"===o||"Variable"===o&&0===a)return l(e,n,Object.keys(s).map((e=>({text:`"${e}": `,type:s[e]}))));if(("ObjectValue"===o||"ObjectField"===o&&0===a)&&u.fields)return l(e,n,Object.keys(u.fields).map((e=>u.fields[e])).map((e=>({text:`"${e.name}": `,type:e.type,description:e.description}))));if("StringValue"===o||"NumberValue"===o||"BooleanValue"===o||"NullValue"===o||"ListValue"===o&&1===a||"ObjectField"===o&&2===a||"Variable"===o&&2===a){const r=u.type?(0,t.getNamedType)(u.type):void 0;if(r instanceof t.GraphQLInputObjectType)return l(e,n,[{text:"{"}]);if(r instanceof t.GraphQLEnumType)return l(e,n,r.getValues().map((e=>({text:`"${e.name}"`,type:r,description:e.description}))));if(r===t.GraphQLBoolean)return l(e,n,[{text:"true",type:t.GraphQLBoolean,description:"Not false."},{text:"false",type:t.GraphQLBoolean,description:"Not true."}])}}function m(e,r){const i={type:null,fields:null};return(0,n.f)(r,(n=>{switch(n.kind){case"Variable":i.type=e[n.name];break;case"ListValue":{const e=i.type?(0,t.getNullableType)(i.type):void 0;i.type=e instanceof t.GraphQLList?e.ofType:null;break}case"ObjectValue":{const e=i.type?(0,t.getNamedType)(i.type):void 0;i.fields=e instanceof t.GraphQLInputObjectType?e.getFields():null;break}case"ObjectField":{const e=n.name&&i.fields?i.fields[n.name]:null;i.type=null==e?void 0:e.type;break}}})),i}s(l,"hintList"),s(u,"filterAndSortList"),s(c,"filterNonEmpty"),s(d,"normalizeText"),s(f,"getProximity"),s(p,"lexicalDistance"),e.C.registerHelper("hint","graphql-variables",((t,n)=>{const r=t.getCursor(),i=t.getTokenAt(r),o=h(r,i,n);return(null==o?void 0:o.list)&&o.list.length>0&&(o.from=e.C.Pos(o.from.line,o.from.ch),o.to=e.C.Pos(o.to.line,o.to.ch),e.C.signal(t,"hasCompletion",t,o,i)),o})),s(h,"getVariablesHint"),s(m,"getTypeInfo")})?r.apply(t,i):r)||(e.exports=o)},6856:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(9196),n(3573),n(1850)],r=function(e,t,r,i){"use strict";function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(o=function(e){return e?n:t})(e)}function a(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=o(t);if(n&&n.has(e))return n.get(e);var r={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var s=i?Object.getOwnPropertyDescriptor(e,a):null;s&&(s.get||s.set)?Object.defineProperty(r,a,s):r[a]=e[a]}return r.default=e,n&&n.set(e,r),r}Object.defineProperty(e,"__esModule",{value:!0}),e.$=void 0,e.A=JE,e.B=$E,e.C=void 0,e.D=IE,e.E=void 0,e.F=sT,e.G=dT,e.H=cS,e.I=pS,e.J=Lw,e.K=gT,e.L=void 0,e.M=xw,e.N=Rw,e.O=QE,e.P=void 0,e.Q=yS,e.R=SS,e.U=e.T=e.S=void 0,e.V=NS,e.W=oE,e.X=void 0,e.Y=eE,e.a0=e.a=e._=e.Z=void 0,e.a1=Xw,e.a2=void 0,e.a3=IS,e.a4=void 0,e.a5=ME,e.a7=e.a6=void 0,e.a8=ve,e.aR=e.aQ=e.aP=e.aO=e.aN=e.aM=e.aL=e.aK=e.aJ=e.aI=e.aH=e.aG=e.aF=e.aE=e.aD=e.aC=e.aB=e.aA=e.a9=void 0,e.aS=HS,e.aU=e.aT=void 0,e.aa=AS,e.ab=PS,e.az=e.ay=e.ax=e.aw=e.av=e.au=e.at=e.as=e.ar=e.aq=e.ap=e.ao=e.an=e.am=e.al=e.ak=e.aj=e.ai=e.ah=e.ag=e.af=e.ae=e.ad=e.ac=void 0,e.b=_i,e.c=void 0,e.d=iS,e.e=uC,e.f=void 0,e.g=eo,e.h=mC,e.i=void 0,e.j=cC,e.k=dC,e.l=Oi,e.m=SC,e.n=TS,e.o=Vi,e.p=Li,e.q=ZC,e.r=void 0,e.s=dE,e.t=Di,e.u=fC,e.v=void 0,e.w=tT,e.x=iT,e.y=Hw,e.z=void 0,t=a(t),i=a(i);var s=Object.defineProperty,l=Object.defineProperties,u=Object.getOwnPropertyDescriptors,c=Object.getOwnPropertySymbols,d=Object.prototype.hasOwnProperty,f=Object.prototype.propertyIsEnumerable,p=(e,t,n)=>t in e?s(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,h=(e,t)=>{for(var n in t||(t={}))d.call(t,n)&&p(e,n,t[n]);if(c)for(var n of c(t))f.call(t,n)&&p(e,n,t[n]);return e},m=(e,t)=>l(e,u(t)),g=(e,t)=>s(e,"name",{value:t,configurable:!0}),v=(e,t)=>{var n={};for(var r in e)d.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&c)for(var r of c(e))t.indexOf(r)<0&&f.call(e,r)&&(n[r]=e[r]);return n};function y(e){var t,n,r="";if("string"==typeof e||"number"==typeof e)r+=e;else if("object"==typeof e)if(Array.isArray(e))for(t=0;t{const r=e.subscribe({next:e=>{t(e),r.unsubscribe()},error:n,complete:()=>{n(new Error("no value resolved"))}})}))}function C(e){return"object"==typeof e&&null!==e&&"subscribe"in e&&"function"==typeof e.subscribe}function S(e){return"object"==typeof e&&null!==e&&("AsyncGenerator"===e[Symbol.toStringTag]||Symbol.asyncIterator in e)}function x(e){var t;return E(this,void 0,void 0,(function*(){const n=null===(t=("return"in e?e:e[Symbol.asyncIterator]()).return)||void 0===t?void 0:t.bind(e),r=("next"in e?e:e[Symbol.asyncIterator]()).next.bind(e),i=yield r();return null==n||n(),i.value}))}function k(e){return E(this,void 0,void 0,(function*(){const t=yield e;return S(t)?x(t):C(t)?w(t):t}))}g(T,"isPromise"),g(w,"observableToPromise"),g(C,"isObservable"),g(S,"isAsyncIterable"),g(x,"asyncIterableToPromise"),g(k,"fetcherReturnToPromise"),globalThis&&globalThis.__awaiter;var N=globalThis&&globalThis.__await||function(e){return this instanceof N?(this.v=e,this):new N(e)};function _(e){return JSON.stringify(e,null,2)}function O(e){return Object.assign(Object.assign({},e),{message:e.message,stack:e.stack})}function I(e){return e instanceof Error?O(e):e}function D(e){return Array.isArray(e)?_({errors:e.map((e=>I(e)))}):_({errors:[I(e)]})}function L(e){return _(e)}function A(e,t,n){const i=[];if(!e||!t)return{insertions:i,result:t};let o;try{o=(0,r.parse)(t)}catch(e){return{insertions:i,result:t}}const a=n||M,s=new r.TypeInfo(e);return(0,r.visit)(o,{leave(e){s.leave(e)},enter(e){if(s.enter(e),"Field"===e.kind&&!e.selectionSet){const n=R(j(s.getType()),a);if(n&&e.loc){const o=P(t,e.loc.start);i.push({index:e.loc.end,string:" "+(0,r.print)(n).replaceAll("\n","\n"+o)})}}}}),{insertions:i,result:F(t,i)}}function M(e){if(!("getFields"in e))return[];const t=e.getFields();if(t.id)return["id"];if(t.edges)return["edges"];if(t.node)return["node"];const n=[];for(const e of Object.keys(t))(0,r.isLeafType)(t[e].type)&&n.push(e);return n}function R(e,t){const n=(0,r.getNamedType)(e);if(!e||(0,r.isLeafType)(e))return;const i=t(n);return Array.isArray(i)&&0!==i.length&&"getFields"in n?{kind:r.Kind.SELECTION_SET,selections:i.map((e=>{const i=n.getFields()[e],o=i?i.type:null;return{kind:r.Kind.FIELD,name:{kind:r.Kind.NAME,value:e},selectionSet:R(o,t)}}))}:void 0}function F(e,t){if(0===t.length)return e;let n="",r=0;for(const{index:i,string:o}of t)n+=e.slice(r,i)+o,r=i;return n+=e.slice(r),n}function P(e,t){let n=t,r=t;for(;n;){const t=e.charCodeAt(n-1);if(10===t||13===t||8232===t||8233===t)break;n--,9!==t&&11!==t&&12!==t&&32!==t&&160!==t&&(r=n)}return e.slice(n,r)}function j(e){if(e)return e}function V(e,t){var n;const r=new Map,i=[];for(const o of e)if("Field"===o.kind){const e=t(o),a=r.get(e);if(null===(n=o.directives)||void 0===n?void 0:n.length){const e=Object.assign({},o);i.push(e)}else if((null==a?void 0:a.selectionSet)&&o.selectionSet)a.selectionSet.selections=[...a.selectionSet.selections,...o.selectionSet.selections];else if(!a){const t=Object.assign({},o);r.set(e,t),i.push(t)}}else i.push(o);return i}function U(e,t,n){var i;const o=n?(0,r.getNamedType)(n).name:null,a=[],s=[];for(let l of t){if("FragmentSpread"===l.kind){const t=l.name.value;if(!l.directives||0===l.directives.length){if(s.includes(t))continue;s.push(t)}const n=e[l.name.value];if(n){const{typeCondition:e,directives:t,selectionSet:i}=n;l={kind:r.Kind.INLINE_FRAGMENT,typeCondition:e,directives:t,selectionSet:i}}}if(l.kind===r.Kind.INLINE_FRAGMENT&&(!l.directives||0===(null===(i=l.directives)||void 0===i?void 0:i.length))){const t=l.typeCondition?l.typeCondition.name.value:null;if(!t||t===o){a.push(...U(e,l.selectionSet.selections,n));continue}}a.push(l)}return a}function B(e,t){const n=t?new r.TypeInfo(t):null,i=Object.create(null);for(const t of e.definitions)t.kind===r.Kind.FRAGMENT_DEFINITION&&(i[t.name.value]=t);const o={SelectionSet(e){const t=n?n.getParentType():null;let{selections:r}=e;return r=U(i,r,t),r=V(r,(e=>e.alias?e.alias.value:e.name.value)),Object.assign(Object.assign({},e),{selections:r})},FragmentDefinition(){return null}};return(0,r.visit)(e,n?(0,r.visitWithTypeInfo)(n,o):o)}function $(e,t,n){if(!n||n.length<1)return;const r=n.map((e=>{var t;return null===(t=e.name)||void 0===t?void 0:t.value}));if(t&&r.includes(t))return t;if(t&&e){const n=e.map((e=>{var t;return null===(t=e.name)||void 0===t?void 0:t.value})).indexOf(t);if(-1!==n&&n{for(const e in window.localStorage)0===e.indexOf(`${G}:`)&&window.localStorage.removeItem(e)}}}get(e){if(!this.storage)return null;const t=`${G}:${e}`,n=this.storage.getItem(t);return"null"===n||"undefined"===n?(this.storage.removeItem(t),null):n||null}set(e,t){let n=!1,r=null;if(this.storage){const i=`${G}:${e}`;if(t)try{this.storage.setItem(i,t)}catch(e){r=e instanceof Error?e:new Error(`${e}`),n=q(this.storage,e)}else this.storage.removeItem(i)}return{isQuotaError:n,error:r}}clear(){this.storage&&this.storage.clear()}}g(H,"StorageAPI");const G="graphiql";class z{constructor(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.key=e,this.storage=t,this.maxSize=n,this.items=this.fetchAll()}get length(){return this.items.length}contains(e){return this.items.some((t=>t.query===e.query&&t.variables===e.variables&&t.headers===e.headers&&t.operationName===e.operationName))}edit(e){const t=this.items.findIndex((t=>t.query===e.query&&t.variables===e.variables&&t.headers===e.headers&&t.operationName===e.operationName));-1!==t&&(this.items.splice(t,1,e),this.save())}delete(e){const t=this.items.findIndex((t=>t.query===e.query&&t.variables===e.variables&&t.headers===e.headers&&t.operationName===e.operationName));-1!==t&&(this.items.splice(t,1),this.save())}fetchRecent(){return this.items.at(-1)}fetchAll(){const e=this.storage.get(this.key);return e?JSON.parse(e)[this.key]:[]}push(e){const t=[...this.items,e];this.maxSize&&t.length>this.maxSize&&t.shift();for(let e=0;e<5;e++){const e=this.storage.set(this.key,JSON.stringify({[this.key]:t}));if(null==e?void 0:e.error){if(!e.isQuotaError||!this.maxSize)return;t.shift()}else this.items=t}}save(){this.storage.set(this.key,JSON.stringify({[this.key]:this.items}))}}g(z,"QueryStore");class W{constructor(e,t){this.storage=e,this.maxHistoryLength=t,this.updateHistory=(e,t,n,r)=>{if(this.shouldSaveQuery(e,t,n,this.history.fetchRecent())){this.history.push({query:e,variables:t,headers:n,operationName:r});const i=this.history.items,o=this.favorite.items;this.queries=i.concat(o)}},this.history=new z("queries",this.storage,this.maxHistoryLength),this.favorite=new z("favorites",this.storage,null),this.queries=[...this.history.fetchAll(),...this.favorite.fetchAll()]}shouldSaveQuery(e,t,n,i){if(!e)return!1;try{(0,r.parse)(e)}catch(e){return!1}if(e.length>1e5)return!1;if(!i)return!0;if(JSON.stringify(e)===JSON.stringify(i.query)){if(JSON.stringify(t)===JSON.stringify(i.variables)){if(JSON.stringify(n)===JSON.stringify(i.headers))return!1;if(n&&!i.headers)return!1}if(t&&!i.variables)return!1}return!0}toggleFavorite(e,t,n,r,i,o){const a={query:e,variables:t,headers:n,operationName:r,label:i};this.favorite.contains(a)?o&&(a.favorite=!1,this.favorite.delete(a)):(a.favorite=!0,this.favorite.push(a)),this.queries=[...this.history.items,...this.favorite.items]}editLabel(e,t,n,r,i,o){const a={query:e,variables:t,headers:n,operationName:r,label:i};o?this.favorite.edit(Object.assign(Object.assign({},a),{favorite:o})):this.history.edit(a),this.queries=[...this.history.items,...this.favorite.items]}}g(W,"HistoryStore");var K=Object.defineProperty,Q=g(((e,t)=>K(e,"name",{value:t,configurable:!0})),"__name$G");function X(e){const n=(0,t.createContext)(null);return n.displayName=e,n}function Y(e){function n(r){var i;const o=(0,t.useContext)(e);if(null===o&&(null==r?void 0:r.nonNull))throw new Error(`Tried to use \`${(null==(i=r.caller)?void 0:i.name)||n.caller.name}\` without the necessary context. Make sure to render the \`${e.displayName}Provider\` component higher up the tree.`);return o}return g(n,"useGivenContext"),Q(n,"useGivenContext"),Object.defineProperty(n,"name",{value:`use${e.displayName}`}),n}g(X,"createNullableContext"),Q(X,"createNullableContext"),g(Y,"createContextHook"),Q(Y,"createContextHook");var J="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==n.g?n.g:"undefined"!=typeof self?self:{};function Z(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function ee(e){if(e.__esModule)return e;var t=Object.defineProperty({},"__esModule",{value:!0});return Object.keys(e).forEach((function(n){var r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:function(){return e[n]}})})),t}e.c=J,g(Z,"getDefaultExportFromCjs"),g(ee,"getAugmentedNamespace");var te={exports:{}},ne={};function re(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}Object.getOwnPropertySymbols,Object.prototype.hasOwnProperty,Object.prototype.propertyIsEnumerable,g((function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}),"toObject"),g(re,"shouldUseNative"),re()&&Object.assign;var ie=t.default,oe=60103;if(ne.Fragment=60107,"function"==typeof Symbol&&Symbol.for){var ae=Symbol.for;oe=ae("react.element"),ne.Fragment=ae("react.fragment")}var se=ie.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,le=Object.prototype.hasOwnProperty,ue={key:!0,ref:!0,__self:!0,__source:!0};function ce(e,t,n){var r,i={},o=null,a=null;for(r in void 0!==n&&(o=""+n),void 0!==t.key&&(o=""+t.key),void 0!==t.ref&&(a=t.ref),t)le.call(t,r)&&!ue.hasOwnProperty(r)&&(i[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===i[r]&&(i[r]=t[r]);return{$$typeof:oe,type:e,key:o,ref:a,props:i,_owner:se.current}}g(ce,"q$1"),ne.jsx=ce,ne.jsxs=ce,te.exports=ne;const de=te.exports.jsx,fe=te.exports.jsxs,pe=te.exports.Fragment;var he=Object.defineProperty,me=g(((e,t)=>he(e,"name",{value:t,configurable:!0})),"__name$F");const ge=X("StorageContext");function ve(e){const n=(0,t.useRef)(!0),[r,i]=(0,t.useState)(new H(e.storage));return(0,t.useEffect)((()=>{n.current?n.current=!1:i(new H(e.storage))}),[e.storage]),de(ge.Provider,{value:r,children:e.children})}e.a7=ge,g(ve,"StorageContextProvider"),me(ve,"StorageContextProvider");const ye=Y(ge);e.a9=ye;const be=10,Ee=2;function Te(e){return we(e,[])}function we(e,t){switch(typeof e){case"string":return JSON.stringify(e);case"function":return e.name?`[function ${e.name}]`:"[function]";case"object":return Ce(e,t);default:return String(e)}}function Ce(e,t){if(null===e)return"null";if(t.includes(e))return"[Circular]";const n=[...t,e];if(Se(e)){const t=e.toJSON();if(t!==e)return"string"==typeof t?t:we(t,n)}else if(Array.isArray(e))return ke(e,n);return xe(e,n)}function Se(e){return"function"==typeof e.toJSON}function xe(e,t){const n=Object.entries(e);return 0===n.length?"{}":t.length>Ee?"["+Ne(e)+"]":"{ "+n.map((e=>{let[n,r]=e;return n+": "+we(r,t)})).join(", ")+" }"}function ke(e,t){if(0===e.length)return"[]";if(t.length>Ee)return"[Array]";const n=Math.min(be,e.length),r=e.length-n,i=[];for(let r=0;r1&&i.push(`... ${r} more items`),"["+i.join(", ")+"]"}function Ne(e){const t=Object.prototype.toString.call(e).replace(/^\[object /,"").replace(/]$/,"");if("Object"===t&&"function"==typeof e.constructor){const t=e.constructor.name;if("string"==typeof t&&""!==t)return t}return t}function _e(e,t){if(!Boolean(e))throw new Error(null!=t?t:"Unexpected invariant triggered.")}let Oe;function Ie(e){return 9===e||32===e}function De(e){return e>=48&&e<=57}function Le(e){return e>=97&&e<=122||e>=65&&e<=90}function Ae(e){return Le(e)||95===e}function Me(e){return Le(e)||De(e)||95===e}function Re(e,t){const n=e.replace(/"""/g,'\\"""'),r=n.split(/\r\n|[\n\r]/g),i=1===r.length,o=r.length>1&&r.slice(1).every((e=>0===e.length||Ie(e.charCodeAt(0)))),a=n.endsWith('\\"""'),s=e.endsWith('"')&&!a,l=e.endsWith("\\"),u=s||l,c=!(null!=t&&t.minimize)&&(!i||e.length>70||u||o||a);let d="";const f=i&&Ie(e.charCodeAt(0));return(c&&!f||o)&&(d+="\n"),d+=n,(c||u)&&(d+="\n"),'"""'+d+'"""'}function Fe(e){return`"${e.replace(je,Ve)}"`}var Pe;g(Te,"inspect"),g(we,"formatValue"),g(Ce,"formatObjectValue"),g(Se,"isJSONable"),g(xe,"formatObject"),g(ke,"formatArray"),g(Ne,"getObjectTag"),g(_e,"invariant"),(Pe=Oe||(Oe={})).QUERY="QUERY",Pe.MUTATION="MUTATION",Pe.SUBSCRIPTION="SUBSCRIPTION",Pe.FIELD="FIELD",Pe.FRAGMENT_DEFINITION="FRAGMENT_DEFINITION",Pe.FRAGMENT_SPREAD="FRAGMENT_SPREAD",Pe.INLINE_FRAGMENT="INLINE_FRAGMENT",Pe.VARIABLE_DEFINITION="VARIABLE_DEFINITION",Pe.SCHEMA="SCHEMA",Pe.SCALAR="SCALAR",Pe.OBJECT="OBJECT",Pe.FIELD_DEFINITION="FIELD_DEFINITION",Pe.ARGUMENT_DEFINITION="ARGUMENT_DEFINITION",Pe.INTERFACE="INTERFACE",Pe.UNION="UNION",Pe.ENUM="ENUM",Pe.ENUM_VALUE="ENUM_VALUE",Pe.INPUT_OBJECT="INPUT_OBJECT",Pe.INPUT_FIELD_DEFINITION="INPUT_FIELD_DEFINITION",g(Ie,"isWhiteSpace$2"),g(De,"isDigit$1"),g(Le,"isLetter$1"),g(Ae,"isNameStart"),g(Me,"isNameContinue"),g(Re,"printBlockString"),g(Fe,"printString");const je=/[\x00-\x1f\x22\x5c\x7f-\x9f]/g;function Ve(e){return Ue[e.charCodeAt(0)]}g(Ve,"escapedReplacer");const Ue=["\\u0000","\\u0001","\\u0002","\\u0003","\\u0004","\\u0005","\\u0006","\\u0007","\\b","\\t","\\n","\\u000B","\\f","\\r","\\u000E","\\u000F","\\u0010","\\u0011","\\u0012","\\u0013","\\u0014","\\u0015","\\u0016","\\u0017","\\u0018","\\u0019","\\u001A","\\u001B","\\u001C","\\u001D","\\u001E","\\u001F","","",'\\"',"","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","\\\\","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","\\u007F","\\u0080","\\u0081","\\u0082","\\u0083","\\u0084","\\u0085","\\u0086","\\u0087","\\u0088","\\u0089","\\u008A","\\u008B","\\u008C","\\u008D","\\u008E","\\u008F","\\u0090","\\u0091","\\u0092","\\u0093","\\u0094","\\u0095","\\u0096","\\u0097","\\u0098","\\u0099","\\u009A","\\u009B","\\u009C","\\u009D","\\u009E","\\u009F"];function Be(e,t){if(!Boolean(e))throw new Error(t)}g(Be,"devAssert");const $e={Name:[],Document:["definitions"],OperationDefinition:["name","variableDefinitions","directives","selectionSet"],VariableDefinition:["variable","type","defaultValue","directives"],Variable:["name"],SelectionSet:["selections"],Field:["alias","name","arguments","directives","selectionSet"],Argument:["name","value"],FragmentSpread:["name","directives"],InlineFragment:["typeCondition","directives","selectionSet"],FragmentDefinition:["name","variableDefinitions","typeCondition","directives","selectionSet"],IntValue:[],FloatValue:[],StringValue:[],BooleanValue:[],NullValue:[],EnumValue:[],ListValue:["values"],ObjectValue:["fields"],ObjectField:["name","value"],Directive:["name","arguments"],NamedType:["name"],ListType:["type"],NonNullType:["type"],SchemaDefinition:["description","directives","operationTypes"],OperationTypeDefinition:["type"],ScalarTypeDefinition:["description","name","directives"],ObjectTypeDefinition:["description","name","interfaces","directives","fields"],FieldDefinition:["description","name","arguments","type","directives"],InputValueDefinition:["description","name","type","defaultValue","directives"],InterfaceTypeDefinition:["description","name","interfaces","directives","fields"],UnionTypeDefinition:["description","name","directives","types"],EnumTypeDefinition:["description","name","directives","values"],EnumValueDefinition:["description","name","directives"],InputObjectTypeDefinition:["description","name","directives","fields"],DirectiveDefinition:["description","name","arguments","locations"],SchemaExtension:["directives","operationTypes"],ScalarTypeExtension:["name","directives"],ObjectTypeExtension:["name","interfaces","directives","fields"],InterfaceTypeExtension:["name","interfaces","directives","fields"],UnionTypeExtension:["name","directives","types"],EnumTypeExtension:["name","directives","values"],InputObjectTypeExtension:["name","directives","fields"]},qe=new Set(Object.keys($e));function He(e){const t=null==e?void 0:e.kind;return"string"==typeof t&&qe.has(t)}let Ge,ze;var We,Ke;g(He,"isNode"),(Ke=Ge||(Ge={})).QUERY="query",Ke.MUTATION="mutation",Ke.SUBSCRIPTION="subscription",(We=ze||(ze={})).NAME="Name",We.DOCUMENT="Document",We.OPERATION_DEFINITION="OperationDefinition",We.VARIABLE_DEFINITION="VariableDefinition",We.SELECTION_SET="SelectionSet",We.FIELD="Field",We.ARGUMENT="Argument",We.FRAGMENT_SPREAD="FragmentSpread",We.INLINE_FRAGMENT="InlineFragment",We.FRAGMENT_DEFINITION="FragmentDefinition",We.VARIABLE="Variable",We.INT="IntValue",We.FLOAT="FloatValue",We.STRING="StringValue",We.BOOLEAN="BooleanValue",We.NULL="NullValue",We.ENUM="EnumValue",We.LIST="ListValue",We.OBJECT="ObjectValue",We.OBJECT_FIELD="ObjectField",We.DIRECTIVE="Directive",We.NAMED_TYPE="NamedType",We.LIST_TYPE="ListType",We.NON_NULL_TYPE="NonNullType",We.SCHEMA_DEFINITION="SchemaDefinition",We.OPERATION_TYPE_DEFINITION="OperationTypeDefinition",We.SCALAR_TYPE_DEFINITION="ScalarTypeDefinition",We.OBJECT_TYPE_DEFINITION="ObjectTypeDefinition",We.FIELD_DEFINITION="FieldDefinition",We.INPUT_VALUE_DEFINITION="InputValueDefinition",We.INTERFACE_TYPE_DEFINITION="InterfaceTypeDefinition",We.UNION_TYPE_DEFINITION="UnionTypeDefinition",We.ENUM_TYPE_DEFINITION="EnumTypeDefinition",We.ENUM_VALUE_DEFINITION="EnumValueDefinition",We.INPUT_OBJECT_TYPE_DEFINITION="InputObjectTypeDefinition",We.DIRECTIVE_DEFINITION="DirectiveDefinition",We.SCHEMA_EXTENSION="SchemaExtension",We.SCALAR_TYPE_EXTENSION="ScalarTypeExtension",We.OBJECT_TYPE_EXTENSION="ObjectTypeExtension",We.INTERFACE_TYPE_EXTENSION="InterfaceTypeExtension",We.UNION_TYPE_EXTENSION="UnionTypeExtension",We.ENUM_TYPE_EXTENSION="EnumTypeExtension",We.INPUT_OBJECT_TYPE_EXTENSION="InputObjectTypeExtension";const Qe=Object.freeze({});function Xe(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:$e;const r=new Map;for(const e of Object.values(ze))r.set(e,Ye(t,e));let i,o,a,s=Array.isArray(e),l=[e],u=-1,c=[],d=e;const f=[],p=[];do{u++;const e=u===l.length,v=e&&0!==c.length;if(e){if(o=0===p.length?void 0:f[f.length-1],d=a,a=p.pop(),v)if(s){d=d.slice();let e=0;for(const[t,n]of c){const r=t-e;null===n?(d.splice(r,1),e++):d[r]=n}}else{d=Object.defineProperties({},Object.getOwnPropertyDescriptors(d));for(const[e,t]of c)d[e]=t}u=i.index,l=i.keys,c=i.edits,s=i.inArray,i=i.prev}else if(a){if(o=s?u:l[u],d=a[o],null==d)continue;f.push(o)}let y;if(!Array.isArray(d)){var h,m;He(d)||Be(!1,`Invalid AST Node: ${Te(d)}.`);const n=e?null===(h=r.get(d.kind))||void 0===h?void 0:h.leave:null===(m=r.get(d.kind))||void 0===m?void 0:m.enter;if(y=null==n?void 0:n.call(t,d,o,a,f,p),y===Qe)break;if(!1===y){if(!e){f.pop();continue}}else if(void 0!==y&&(c.push([o,y]),!e)){if(!He(y)){f.pop();continue}d=y}}var g;void 0===y&&v&&c.push([o,d]),e?f.pop():(i={inArray:s,index:u,keys:l,edits:c,prev:i},s=Array.isArray(d),l=s?d:null!==(g=n[d.kind])&&void 0!==g?g:[],u=-1,c=[],a&&p.push(a),a=d)}while(void 0!==i);return 0!==c.length?c[c.length-1][1]:e}function Ye(e,t){const n=e[t];return"object"==typeof n?n:"function"==typeof n?{enter:n,leave:void 0}:{enter:e.enter,leave:e.leave}}function Je(e){return Xe(e,Ze)}g(Xe,"visit"),g(Ye,"getEnterLeaveForKind"),g(Je,"print");const Ze={Name:{leave:e=>e.value},Variable:{leave:e=>"$"+e.name},Document:{leave:e=>et(e.definitions,"\n\n")},OperationDefinition:{leave(e){const t=nt("(",et(e.variableDefinitions,", "),")"),n=et([e.operation,et([e.name,t]),et(e.directives," ")]," ");return("query"===n?"":n+" ")+e.selectionSet}},VariableDefinition:{leave:e=>{let{variable:t,type:n,defaultValue:r,directives:i}=e;return t+": "+n+nt(" = ",r)+nt(" ",et(i," "))}},SelectionSet:{leave:e=>{let{selections:t}=e;return tt(t)}},Field:{leave(e){let{alias:t,name:n,arguments:r,directives:i,selectionSet:o}=e;const a=nt("",t,": ")+n;let s=a+nt("(",et(r,", "),")");return s.length>80&&(s=a+nt("(\n",rt(et(r,"\n")),"\n)")),et([s,et(i," "),o]," ")}},Argument:{leave:e=>{let{name:t,value:n}=e;return t+": "+n}},FragmentSpread:{leave:e=>{let{name:t,directives:n}=e;return"..."+t+nt(" ",et(n," "))}},InlineFragment:{leave:e=>{let{typeCondition:t,directives:n,selectionSet:r}=e;return et(["...",nt("on ",t),et(n," "),r]," ")}},FragmentDefinition:{leave:e=>{let{name:t,typeCondition:n,variableDefinitions:r,directives:i,selectionSet:o}=e;return`fragment ${t}${nt("(",et(r,", "),")")} on ${n} ${nt("",et(i," ")," ")}`+o}},IntValue:{leave:e=>{let{value:t}=e;return t}},FloatValue:{leave:e=>{let{value:t}=e;return t}},StringValue:{leave:e=>{let{value:t,block:n}=e;return n?Re(t):Fe(t)}},BooleanValue:{leave:e=>{let{value:t}=e;return t?"true":"false"}},NullValue:{leave:()=>"null"},EnumValue:{leave:e=>{let{value:t}=e;return t}},ListValue:{leave:e=>{let{values:t}=e;return"["+et(t,", ")+"]"}},ObjectValue:{leave:e=>{let{fields:t}=e;return"{"+et(t,", ")+"}"}},ObjectField:{leave:e=>{let{name:t,value:n}=e;return t+": "+n}},Directive:{leave:e=>{let{name:t,arguments:n}=e;return"@"+t+nt("(",et(n,", "),")")}},NamedType:{leave:e=>{let{name:t}=e;return t}},ListType:{leave:e=>{let{type:t}=e;return"["+t+"]"}},NonNullType:{leave:e=>{let{type:t}=e;return t+"!"}},SchemaDefinition:{leave:e=>{let{description:t,directives:n,operationTypes:r}=e;return nt("",t,"\n")+et(["schema",et(n," "),tt(r)]," ")}},OperationTypeDefinition:{leave:e=>{let{operation:t,type:n}=e;return t+": "+n}},ScalarTypeDefinition:{leave:e=>{let{description:t,name:n,directives:r}=e;return nt("",t,"\n")+et(["scalar",n,et(r," ")]," ")}},ObjectTypeDefinition:{leave:e=>{let{description:t,name:n,interfaces:r,directives:i,fields:o}=e;return nt("",t,"\n")+et(["type",n,nt("implements ",et(r," & ")),et(i," "),tt(o)]," ")}},FieldDefinition:{leave:e=>{let{description:t,name:n,arguments:r,type:i,directives:o}=e;return nt("",t,"\n")+n+(it(r)?nt("(\n",rt(et(r,"\n")),"\n)"):nt("(",et(r,", "),")"))+": "+i+nt(" ",et(o," "))}},InputValueDefinition:{leave:e=>{let{description:t,name:n,type:r,defaultValue:i,directives:o}=e;return nt("",t,"\n")+et([n+": "+r,nt("= ",i),et(o," ")]," ")}},InterfaceTypeDefinition:{leave:e=>{let{description:t,name:n,interfaces:r,directives:i,fields:o}=e;return nt("",t,"\n")+et(["interface",n,nt("implements ",et(r," & ")),et(i," "),tt(o)]," ")}},UnionTypeDefinition:{leave:e=>{let{description:t,name:n,directives:r,types:i}=e;return nt("",t,"\n")+et(["union",n,et(r," "),nt("= ",et(i," | "))]," ")}},EnumTypeDefinition:{leave:e=>{let{description:t,name:n,directives:r,values:i}=e;return nt("",t,"\n")+et(["enum",n,et(r," "),tt(i)]," ")}},EnumValueDefinition:{leave:e=>{let{description:t,name:n,directives:r}=e;return nt("",t,"\n")+et([n,et(r," ")]," ")}},InputObjectTypeDefinition:{leave:e=>{let{description:t,name:n,directives:r,fields:i}=e;return nt("",t,"\n")+et(["input",n,et(r," "),tt(i)]," ")}},DirectiveDefinition:{leave:e=>{let{description:t,name:n,arguments:r,repeatable:i,locations:o}=e;return nt("",t,"\n")+"directive @"+n+(it(r)?nt("(\n",rt(et(r,"\n")),"\n)"):nt("(",et(r,", "),")"))+(i?" repeatable":"")+" on "+et(o," | ")}},SchemaExtension:{leave:e=>{let{directives:t,operationTypes:n}=e;return et(["extend schema",et(t," "),tt(n)]," ")}},ScalarTypeExtension:{leave:e=>{let{name:t,directives:n}=e;return et(["extend scalar",t,et(n," ")]," ")}},ObjectTypeExtension:{leave:e=>{let{name:t,interfaces:n,directives:r,fields:i}=e;return et(["extend type",t,nt("implements ",et(n," & ")),et(r," "),tt(i)]," ")}},InterfaceTypeExtension:{leave:e=>{let{name:t,interfaces:n,directives:r,fields:i}=e;return et(["extend interface",t,nt("implements ",et(n," & ")),et(r," "),tt(i)]," ")}},UnionTypeExtension:{leave:e=>{let{name:t,directives:n,types:r}=e;return et(["extend union",t,et(n," "),nt("= ",et(r," | "))]," ")}},EnumTypeExtension:{leave:e=>{let{name:t,directives:n,values:r}=e;return et(["extend enum",t,et(n," "),tt(r)]," ")}},InputObjectTypeExtension:{leave:e=>{let{name:t,directives:n,fields:r}=e;return et(["extend input",t,et(n," "),tt(r)]," ")}}};function et(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";var n;return null!==(n=null==e?void 0:e.filter((e=>e)).join(t))&&void 0!==n?n:""}function tt(e){return nt("{\n",rt(et(e,"\n")),"\n}")}function nt(e,t){return null!=t&&""!==t?e+t+(arguments.length>2&&void 0!==arguments[2]?arguments[2]:""):""}function rt(e){return nt(" ",e.replace(/\n/g,"\n "))}function it(e){var t;return null!==(t=null==e?void 0:e.some((e=>e.includes("\n"))))&&void 0!==t&&t}function ot(e){return"object"==typeof e&&"function"==typeof(null==e?void 0:e[Symbol.iterator])}function at(e){return"object"==typeof e&&null!==e}g(et,"join"),g(tt,"block$2"),g(nt,"wrap"),g(rt,"indent"),g(it,"hasMultilineItems"),g(ot,"isIterableObject"),g(at,"isObjectLike");const st=5;function lt(e,t){const[n,r]=t?[e,t]:[void 0,e];let i=" Did you mean ";n&&(i+=n+" ");const o=r.map((e=>`"${e}"`));switch(o.length){case 0:return"";case 1:return i+o[0]+"?";case 2:return i+o[0]+" or "+o[1]+"?"}const a=o.slice(0,st),s=a.pop();return i+a.join(", ")+", or "+s+"?"}function ut(e){return e}g(lt,"didYouMean"),g(ut,"identityFunc");const ct=g((function(e,t){return e instanceof t}),"instanceOf");function dt(e,t){const n=Object.create(null);for(const r of e)n[t(r)]=r;return n}function ft(e,t,n){const r=Object.create(null);for(const i of e)r[t(i)]=n(i);return r}function pt(e,t){const n=Object.create(null);for(const r of Object.keys(e))n[r]=t(e[r],r);return n}function ht(e,t){let n=0,r=0;for(;n0);let s=0;do{++r,s=10*s+o-mt,o=t.charCodeAt(r)}while(vt(o)&&s>0);if(as)return 1}else{if(io)return 1;++n,++r}}return e.length-t.length}g(dt,"keyMap"),g(ft,"keyValMap"),g(pt,"mapValue"),g(ht,"naturalCompare");const mt=48,gt=57;function vt(e){return!isNaN(e)&&mt<=e&&e<=gt}function yt(e,t){const n=Object.create(null),r=new bt(e),i=Math.floor(.4*e.length)+1;for(const e of t){const t=r.measure(e,i);void 0!==t&&(n[e]=t)}return Object.keys(n).sort(((e,t)=>{const r=n[e]-n[t];return 0!==r?r:ht(e,t)}))}g(vt,"isDigit"),g(yt,"suggestionList");class bt{constructor(e){this._input=e,this._inputLowerCase=e.toLowerCase(),this._inputArray=Et(this._inputLowerCase),this._rows=[new Array(e.length+1).fill(0),new Array(e.length+1).fill(0),new Array(e.length+1).fill(0)]}measure(e,t){if(this._input===e)return 0;const n=e.toLowerCase();if(this._inputLowerCase===n)return 1;let r=Et(n),i=this._inputArray;if(r.lengtht)return;const s=this._rows;for(let e=0;e<=a;e++)s[0][e]=e;for(let e=1;e<=o;e++){const n=s[(e-1)%3],o=s[e%3];let l=o[0]=e;for(let t=1;t<=a;t++){const a=r[e-1]===i[t-1]?0:1;let u=Math.min(n[t]+1,o[t-1]+1,n[t-1]+a);if(e>1&&t>1&&r[e-1]===i[t-2]&&r[e-2]===i[t-1]){const n=s[(e-2)%3][t-2];u=Math.min(u,n+1)}ut)return}const l=s[o%3][a];return l<=t?l:void 0}}function Et(e){const t=e.length,n=new Array(t);for(let r=0;r=t)break;n=i.index+i[0].length,r+=1}return{line:r,column:t+1-n}}function St(e){return xt(e.source,Ct(e.source,e.start))}function xt(e,t){const n=e.locationOffset.column-1,r="".padStart(n)+e.body,i=t.line-1,o=e.locationOffset.line-1,a=t.line+o,s=1===t.line?n:0,l=t.column+s,u=`${e.name}:${a}:${l}\n`,c=r.split(/\r\n|[\n\r]/g),d=c[i];if(d.length>120){const e=Math.floor(l/80),t=l%80,n=[];for(let e=0;e["|",e])),["|","^".padStart(t)],["|",n[e+1]]])}return u+kt([[a-1+" |",c[i-1]],[`${a} |`,d],["|","^".padStart(l)],[`${a+1} |`,c[i+1]]])}function kt(e){const t=e.filter((e=>{let[t,n]=e;return void 0!==n})),n=Math.max(...t.map((e=>{let[t]=e;return t.length})));return t.map((e=>{let[t,r]=e;return t.padStart(n)+(r?" "+r:"")})).join("\n")}function Nt(e){const t=e[0];return null==t||"kind"in t||"length"in t?{nodes:t,source:e[1],positions:e[2],path:e[3],originalError:e[4],extensions:e[5]}:t}g(Ct,"getLocation"),g(St,"printLocation"),g(xt,"printSourceLocation"),g(kt,"printPrefixedLines"),g(Nt,"toNormalizedOptions");class _t extends Error{constructor(e){for(var t,n,r,i=arguments.length,o=new Array(i>1?i-1:0),a=1;ae.loc)).filter((e=>null!=e)));this.source=null!=l?l:null==p||null===(n=p[0])||void 0===n?void 0:n.source,this.positions=null!=u?u:null==p?void 0:p.map((e=>e.start)),this.locations=u&&l?u.map((e=>Ct(l,e))):null==p?void 0:p.map((e=>Ct(e.source,e.start)));const h=at(null==d?void 0:d.extensions)?null==d?void 0:d.extensions:void 0;this.extensions=null!==(r=null!=f?f:h)&&void 0!==r?r:Object.create(null),Object.defineProperties(this,{message:{writable:!0,enumerable:!0},name:{enumerable:!1},nodes:{enumerable:!1},source:{enumerable:!1},positions:{enumerable:!1},originalError:{enumerable:!1}}),null!=d&&d.stack?Object.defineProperty(this,"stack",{value:d.stack,writable:!0,configurable:!0}):Error.captureStackTrace?Error.captureStackTrace(this,_t):Object.defineProperty(this,"stack",{value:Error().stack,writable:!0,configurable:!0})}get[Symbol.toStringTag](){return"GraphQLError"}toString(){let e=this.message;if(this.nodes)for(const t of this.nodes)t.loc&&(e+="\n\n"+St(t.loc));else if(this.source&&this.locations)for(const t of this.locations)e+="\n\n"+xt(this.source,t);return e}toJSON(){const e={message:this.message};return null!=this.locations&&(e.locations=this.locations),null!=this.path&&(e.path=this.path),null!=this.extensions&&Object.keys(this.extensions).length>0&&(e.extensions=this.extensions),e}}function Ot(e){return void 0===e||0===e.length?void 0:e}function It(e,t){switch(e.kind){case ze.NULL:return null;case ze.INT:return parseInt(e.value,10);case ze.FLOAT:return parseFloat(e.value);case ze.STRING:case ze.ENUM:case ze.BOOLEAN:return e.value;case ze.LIST:return e.values.map((e=>It(e,t)));case ze.OBJECT:return ft(e.fields,(e=>e.name.value),(e=>It(e.value,t)));case ze.VARIABLE:return null==t?void 0:t[e.name.value]}}function Dt(e){if(null!=e||Be(!1,"Must provide name."),"string"==typeof e||Be(!1,"Expected name to be a string."),0===e.length)throw new _t("Expected name to be a non-empty string.");for(let t=1;to(It(e,t)),this.extensions=Tt(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(i=e.extensionASTNodes)&&void 0!==i?i:[],null==e.specifiedByURL||"string"==typeof e.specifiedByURL||Be(!1,`${this.name} must provide "specifiedByURL" as a string, but got: ${Te(e.specifiedByURL)}.`),null==e.serialize||"function"==typeof e.serialize||Be(!1,`${this.name} must provide "serialize" function. If this custom Scalar is also used as an input type, ensure "parseValue" and "parseLiteral" functions are also provided.`),e.parseLiteral&&("function"==typeof e.parseValue&&"function"==typeof e.parseLiteral||Be(!1,`${this.name} must provide both "parseValue" and "parseLiteral" functions.`))}get[Symbol.toStringTag](){return"GraphQLScalarType"}toConfig(){return{name:this.name,description:this.description,specifiedByURL:this.specifiedByURL,serialize:this.serialize,parseValue:this.parseValue,parseLiteral:this.parseLiteral,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}g(Qt,"GraphQLScalarType");class Xt{constructor(e){var t;this.name=Dt(e.name),this.description=e.description,this.isTypeOf=e.isTypeOf,this.extensions=Tt(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._fields=()=>Jt(e),this._interfaces=()=>Yt(e),null==e.isTypeOf||"function"==typeof e.isTypeOf||Be(!1,`${this.name} must provide "isTypeOf" as a function, but got: ${Te(e.isTypeOf)}.`)}get[Symbol.toStringTag](){return"GraphQLObjectType"}getFields(){return"function"==typeof this._fields&&(this._fields=this._fields()),this._fields}getInterfaces(){return"function"==typeof this._interfaces&&(this._interfaces=this._interfaces()),this._interfaces}toConfig(){return{name:this.name,description:this.description,interfaces:this.getInterfaces(),fields:tn(this.getFields()),isTypeOf:this.isTypeOf,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}function Yt(e){var t;const n=Wt(null!==(t=e.interfaces)&&void 0!==t?t:[]);return Array.isArray(n)||Be(!1,`${e.name} interfaces must be an Array or a function which returns an Array.`),n}function Jt(e){const t=Kt(e.fields);return en(t)||Be(!1,`${e.name} fields must be an object with field names as keys or a function which returns such an object.`),pt(t,((t,n)=>{var r;en(t)||Be(!1,`${e.name}.${n} field config must be an object.`),null==t.resolve||"function"==typeof t.resolve||Be(!1,`${e.name}.${n} field resolver must be a function if provided, but got: ${Te(t.resolve)}.`);const i=null!==(r=t.args)&&void 0!==r?r:{};return en(i)||Be(!1,`${e.name}.${n} args must be an object with argument names as keys.`),{name:Dt(n),description:t.description,type:t.type,args:Zt(i),resolve:t.resolve,subscribe:t.subscribe,deprecationReason:t.deprecationReason,extensions:Tt(t.extensions),astNode:t.astNode}}))}function Zt(e){return Object.entries(e).map((e=>{let[t,n]=e;return{name:Dt(t),description:n.description,type:n.type,defaultValue:n.defaultValue,deprecationReason:n.deprecationReason,extensions:Tt(n.extensions),astNode:n.astNode}}))}function en(e){return at(e)&&!Array.isArray(e)}function tn(e){return pt(e,(e=>({description:e.description,type:e.type,args:nn(e.args),resolve:e.resolve,subscribe:e.subscribe,deprecationReason:e.deprecationReason,extensions:e.extensions,astNode:e.astNode})))}function nn(e){return ft(e,(e=>e.name),(e=>({description:e.description,type:e.type,defaultValue:e.defaultValue,deprecationReason:e.deprecationReason,extensions:e.extensions,astNode:e.astNode})))}g(Xt,"GraphQLObjectType"),g(Yt,"defineInterfaces"),g(Jt,"defineFieldMap"),g(Zt,"defineArguments"),g(en,"isPlainObj"),g(tn,"fieldsToFieldsConfig"),g(nn,"argsToArgsConfig");class rn{constructor(e){var t;this.name=Dt(e.name),this.description=e.description,this.resolveType=e.resolveType,this.extensions=Tt(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._fields=Jt.bind(void 0,e),this._interfaces=Yt.bind(void 0,e),null==e.resolveType||"function"==typeof e.resolveType||Be(!1,`${this.name} must provide "resolveType" as a function, but got: ${Te(e.resolveType)}.`)}get[Symbol.toStringTag](){return"GraphQLInterfaceType"}getFields(){return"function"==typeof this._fields&&(this._fields=this._fields()),this._fields}getInterfaces(){return"function"==typeof this._interfaces&&(this._interfaces=this._interfaces()),this._interfaces}toConfig(){return{name:this.name,description:this.description,interfaces:this.getInterfaces(),fields:tn(this.getFields()),resolveType:this.resolveType,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}g(rn,"GraphQLInterfaceType");class on{constructor(e){var t;this.name=Dt(e.name),this.description=e.description,this.resolveType=e.resolveType,this.extensions=Tt(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._types=an.bind(void 0,e),null==e.resolveType||"function"==typeof e.resolveType||Be(!1,`${this.name} must provide "resolveType" as a function, but got: ${Te(e.resolveType)}.`)}get[Symbol.toStringTag](){return"GraphQLUnionType"}getTypes(){return"function"==typeof this._types&&(this._types=this._types()),this._types}toConfig(){return{name:this.name,description:this.description,types:this.getTypes(),resolveType:this.resolveType,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}function an(e){const t=Wt(e.types);return Array.isArray(t)||Be(!1,`Must provide Array of types or a function which returns such an array for Union ${e.name}.`),t}g(on,"GraphQLUnionType"),g(an,"defineTypes");class sn{constructor(e){var t;this.name=Dt(e.name),this.description=e.description,this.extensions=Tt(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._values=un(this.name,e.values),this._valueLookup=new Map(this._values.map((e=>[e.value,e]))),this._nameLookup=dt(this._values,(e=>e.name))}get[Symbol.toStringTag](){return"GraphQLEnumType"}getValues(){return this._values}getValue(e){return this._nameLookup[e]}serialize(e){const t=this._valueLookup.get(e);if(void 0===t)throw new _t(`Enum "${this.name}" cannot represent value: ${Te(e)}`);return t.name}parseValue(e){if("string"!=typeof e){const t=Te(e);throw new _t(`Enum "${this.name}" cannot represent non-string value: ${t}.`+ln(this,t))}const t=this.getValue(e);if(null==t)throw new _t(`Value "${e}" does not exist in "${this.name}" enum.`+ln(this,e));return t.value}parseLiteral(e,t){if(e.kind!==ze.ENUM){const t=Je(e);throw new _t(`Enum "${this.name}" cannot represent non-enum value: ${t}.`+ln(this,t),{nodes:e})}const n=this.getValue(e.value);if(null==n){const t=Je(e);throw new _t(`Value "${t}" does not exist in "${this.name}" enum.`+ln(this,t),{nodes:e})}return n.value}toConfig(){const e=ft(this.getValues(),(e=>e.name),(e=>({description:e.description,value:e.value,deprecationReason:e.deprecationReason,extensions:e.extensions,astNode:e.astNode})));return{name:this.name,description:this.description,values:e,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}function ln(e,t){return lt("the enum value",yt(t,e.getValues().map((e=>e.name))))}function un(e,t){return en(t)||Be(!1,`${e} values must be an object with value names as keys.`),Object.entries(t).map((t=>{let[n,r]=t;return en(r)||Be(!1,`${e}.${n} must refer to an object with a "value" key representing an internal value but got: ${Te(r)}.`),{name:Lt(n),description:r.description,value:void 0!==r.value?r.value:n,deprecationReason:r.deprecationReason,extensions:Tt(r.extensions),astNode:r.astNode}}))}g(sn,"GraphQLEnumType"),g(ln,"didYouMeanEnumValue"),g(un,"defineEnumValues");class cn{constructor(e){var t;this.name=Dt(e.name),this.description=e.description,this.extensions=Tt(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._fields=dn.bind(void 0,e)}get[Symbol.toStringTag](){return"GraphQLInputObjectType"}getFields(){return"function"==typeof this._fields&&(this._fields=this._fields()),this._fields}toConfig(){const e=pt(this.getFields(),(e=>({description:e.description,type:e.type,defaultValue:e.defaultValue,deprecationReason:e.deprecationReason,extensions:e.extensions,astNode:e.astNode})));return{name:this.name,description:this.description,fields:e,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}function dn(e){const t=Kt(e.fields);return en(t)||Be(!1,`${e.name} fields must be an object with field names as keys or a function which returns such an object.`),pt(t,((t,n)=>(!("resolve"in t)||Be(!1,`${e.name}.${n} field has a resolve property, but Input Types cannot define resolvers.`),{name:Dt(n),description:t.description,type:t.type,defaultValue:t.defaultValue,deprecationReason:t.deprecationReason,extensions:Tt(t.extensions),astNode:t.astNode})))}g(cn,"GraphQLInputObjectType"),g(dn,"defineInputFieldMap");const fn=2147483647,pn=-2147483648,hn=new Qt({name:"Int",description:"The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.",serialize(e){const t=bn(e);if("boolean"==typeof t)return t?1:0;let n=t;if("string"==typeof t&&""!==t&&(n=Number(t)),"number"!=typeof n||!Number.isInteger(n))throw new _t(`Int cannot represent non-integer value: ${Te(t)}`);if(n>fn||nfn||efn||t({description:{type:gn,resolve:e=>e.description},types:{description:"A list of all types supported by this server.",type:new Gt(new Ht(new Gt(xn))),resolve(e){return Object.values(e.getTypeMap())}},queryType:{description:"The type that query operations will be rooted at.",type:new Gt(xn),resolve:e=>e.getQueryType()},mutationType:{description:"If this server supports mutation, the type that mutation operations will be rooted at.",type:xn,resolve:e=>e.getMutationType()},subscriptionType:{description:"If this server support subscription, the type that subscription operations will be rooted at.",type:xn,resolve:e=>e.getSubscriptionType()},directives:{description:"A list of all directives supported by this server.",type:new Gt(new Ht(new Gt(Cn))),resolve:e=>e.getDirectives()}})}),Cn=new Xt({name:"__Directive",description:"A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.",fields:()=>({name:{type:new Gt(gn),resolve:e=>e.name},description:{type:gn,resolve:e=>e.description},isRepeatable:{type:new Gt(vn),resolve:e=>e.isRepeatable},locations:{type:new Gt(new Ht(new Gt(Sn))),resolve:e=>e.locations},args:{type:new Gt(new Ht(new Gt(Nn))),args:{includeDeprecated:{type:vn,defaultValue:!1}},resolve(e,t){let{includeDeprecated:n}=t;return n?e.args:e.args.filter((e=>null==e.deprecationReason))}}})}),Sn=new sn({name:"__DirectiveLocation",description:"A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.",values:{QUERY:{value:Oe.QUERY,description:"Location adjacent to a query operation."},MUTATION:{value:Oe.MUTATION,description:"Location adjacent to a mutation operation."},SUBSCRIPTION:{value:Oe.SUBSCRIPTION,description:"Location adjacent to a subscription operation."},FIELD:{value:Oe.FIELD,description:"Location adjacent to a field."},FRAGMENT_DEFINITION:{value:Oe.FRAGMENT_DEFINITION,description:"Location adjacent to a fragment definition."},FRAGMENT_SPREAD:{value:Oe.FRAGMENT_SPREAD,description:"Location adjacent to a fragment spread."},INLINE_FRAGMENT:{value:Oe.INLINE_FRAGMENT,description:"Location adjacent to an inline fragment."},VARIABLE_DEFINITION:{value:Oe.VARIABLE_DEFINITION,description:"Location adjacent to a variable definition."},SCHEMA:{value:Oe.SCHEMA,description:"Location adjacent to a schema definition."},SCALAR:{value:Oe.SCALAR,description:"Location adjacent to a scalar definition."},OBJECT:{value:Oe.OBJECT,description:"Location adjacent to an object type definition."},FIELD_DEFINITION:{value:Oe.FIELD_DEFINITION,description:"Location adjacent to a field definition."},ARGUMENT_DEFINITION:{value:Oe.ARGUMENT_DEFINITION,description:"Location adjacent to an argument definition."},INTERFACE:{value:Oe.INTERFACE,description:"Location adjacent to an interface definition."},UNION:{value:Oe.UNION,description:"Location adjacent to a union definition."},ENUM:{value:Oe.ENUM,description:"Location adjacent to an enum definition."},ENUM_VALUE:{value:Oe.ENUM_VALUE,description:"Location adjacent to an enum value definition."},INPUT_OBJECT:{value:Oe.INPUT_OBJECT,description:"Location adjacent to an input object type definition."},INPUT_FIELD_DEFINITION:{value:Oe.INPUT_FIELD_DEFINITION,description:"Location adjacent to an input object field definition."}}}),xn=new Xt({name:"__Type",description:"The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByURL`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.",fields:()=>({kind:{type:new Gt(Dn),resolve(e){return Mt(e)?On.SCALAR:Rt(e)?On.OBJECT:Ft(e)?On.INTERFACE:Pt(e)?On.UNION:jt(e)?On.ENUM:Vt(e)?On.INPUT_OBJECT:Ut(e)?On.LIST:Bt(e)?On.NON_NULL:void _e(!1,`Unexpected type: "${Te(e)}".`)}},name:{type:gn,resolve:e=>"name"in e?e.name:void 0},description:{type:gn,resolve:e=>"description"in e?e.description:void 0},specifiedByURL:{type:gn,resolve:e=>"specifiedByURL"in e?e.specifiedByURL:void 0},fields:{type:new Ht(new Gt(kn)),args:{includeDeprecated:{type:vn,defaultValue:!1}},resolve(e,t){let{includeDeprecated:n}=t;if(Rt(e)||Ft(e)){const t=Object.values(e.getFields());return n?t:t.filter((e=>null==e.deprecationReason))}}},interfaces:{type:new Ht(new Gt(xn)),resolve(e){if(Rt(e)||Ft(e))return e.getInterfaces()}},possibleTypes:{type:new Ht(new Gt(xn)),resolve(e,t,n,r){let{schema:i}=r;if(qt(e))return i.getPossibleTypes(e)}},enumValues:{type:new Ht(new Gt(_n)),args:{includeDeprecated:{type:vn,defaultValue:!1}},resolve(e,t){let{includeDeprecated:n}=t;if(jt(e)){const t=e.getValues();return n?t:t.filter((e=>null==e.deprecationReason))}}},inputFields:{type:new Ht(new Gt(Nn)),args:{includeDeprecated:{type:vn,defaultValue:!1}},resolve(e,t){let{includeDeprecated:n}=t;if(Vt(e)){const t=Object.values(e.getFields());return n?t:t.filter((e=>null==e.deprecationReason))}}},ofType:{type:xn,resolve:e=>"ofType"in e?e.ofType:void 0}})}),kn=new Xt({name:"__Field",description:"Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.",fields:()=>({name:{type:new Gt(gn),resolve:e=>e.name},description:{type:gn,resolve:e=>e.description},args:{type:new Gt(new Ht(new Gt(Nn))),args:{includeDeprecated:{type:vn,defaultValue:!1}},resolve(e,t){let{includeDeprecated:n}=t;return n?e.args:e.args.filter((e=>null==e.deprecationReason))}},type:{type:new Gt(xn),resolve:e=>e.type},isDeprecated:{type:new Gt(vn),resolve:e=>null!=e.deprecationReason},deprecationReason:{type:gn,resolve:e=>e.deprecationReason}})}),Nn=new Xt({name:"__InputValue",description:"Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",fields:()=>({name:{type:new Gt(gn),resolve:e=>e.name},description:{type:gn,resolve:e=>e.description},type:{type:new Gt(xn),resolve:e=>e.type},defaultValue:{type:gn,description:"A GraphQL-formatted string representing the default value for this input value.",resolve(e){const{type:t,defaultValue:n}=e,r=En(n,t);return r?Je(r):null}},isDeprecated:{type:new Gt(vn),resolve:e=>null!=e.deprecationReason},deprecationReason:{type:gn,resolve:e=>e.deprecationReason}})}),_n=new Xt({name:"__EnumValue",description:"One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.",fields:()=>({name:{type:new Gt(gn),resolve:e=>e.name},description:{type:gn,resolve:e=>e.description},isDeprecated:{type:new Gt(vn),resolve:e=>null!=e.deprecationReason},deprecationReason:{type:gn,resolve:e=>e.deprecationReason}})});let On;var In;(In=On||(On={})).SCALAR="SCALAR",In.OBJECT="OBJECT",In.INTERFACE="INTERFACE",In.UNION="UNION",In.ENUM="ENUM",In.INPUT_OBJECT="INPUT_OBJECT",In.LIST="LIST",In.NON_NULL="NON_NULL";const Dn=new sn({name:"__TypeKind",description:"An enum describing what kind of type a given `__Type` is.",values:{SCALAR:{value:On.SCALAR,description:"Indicates this type is a scalar."},OBJECT:{value:On.OBJECT,description:"Indicates this type is an object. `fields` and `interfaces` are valid fields."},INTERFACE:{value:On.INTERFACE,description:"Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields."},UNION:{value:On.UNION,description:"Indicates this type is a union. `possibleTypes` is a valid field."},ENUM:{value:On.ENUM,description:"Indicates this type is an enum. `enumValues` is a valid field."},INPUT_OBJECT:{value:On.INPUT_OBJECT,description:"Indicates this type is an input object. `inputFields` is a valid field."},LIST:{value:On.LIST,description:"Indicates this type is a list. `ofType` is a valid field."},NON_NULL:{value:On.NON_NULL,description:"Indicates this type is a non-null. `ofType` is a valid field."}}}),Ln={name:"__schema",type:new Gt(wn),description:"Access the current type schema of this server.",args:[],resolve:(e,t,n,r)=>{let{schema:i}=r;return i},deprecationReason:void 0,extensions:Object.create(null),astNode:void 0};e.S=Ln;const An={name:"__type",type:xn,description:"Request the type information of a single type.",args:[{name:"name",description:void 0,type:new Gt(gn),defaultValue:void 0,deprecationReason:void 0,extensions:Object.create(null),astNode:void 0}],resolve:(e,t,n,r)=>{let{name:i}=t,{schema:o}=r;return o.getType(i)},deprecationReason:void 0,extensions:Object.create(null),astNode:void 0};e.T=An;const Mn={name:"__typename",type:new Gt(gn),description:"The name of the current Object type at runtime.",args:[],resolve:(e,t,n,r)=>{let{parentType:i}=r;return i.name},deprecationReason:void 0,extensions:Object.create(null),astNode:void 0};function Rn(e){let t;return Pn(e,(e=>{switch(e.kind){case"Query":case"ShortQuery":case"Mutation":case"Subscription":case"FragmentDefinition":t=e}})),t}function Fn(e,t,n){return n===Ln.name&&e.getQueryType()===t?Ln:n===An.name&&e.getQueryType()===t?An:n===Mn.name&&(0,r.isCompositeType)(t)?Mn:"getFields"in t?t.getFields()[n]:null}function Pn(e,t){const n=[];let r=e;for(;null==r?void 0:r.kind;)n.push(r),r=r.prevState;for(let e=n.length-1;e>=0;e--)t(n[e])}function jn(e){const t=Object.keys(e),n=t.length,r=new Array(n);for(let i=0;i({proximity:qn($n(e.label),t),entry:e}))),(e=>e.proximity<=2)),(e=>!e.entry.isDeprecated)).sort(((e,t)=>(e.entry.isDeprecated?1:0)-(t.entry.isDeprecated?1:0)||e.proximity-t.proximity||e.entry.label.length-t.entry.label.length)).map((e=>e.entry)):Bn(e,(e=>!e.isDeprecated))}function Bn(e,t){const n=e.filter(t);return 0===n.length?e:n}function $n(e){return e.toLowerCase().replaceAll(/\W/g,"")}function qn(e,t){let n=Hn(t,e);return e.length>t.length&&(n-=e.length-t.length-1,n+=0===e.indexOf(t)?0:.5),n}function Hn(e,t){let n,r;const i=[],o=e.length,a=t.length;for(n=0;n<=o;n++)i[n]=[n];for(r=1;r<=a;r++)i[0][r]=r;for(n=1;n<=o;n++)for(r=1;r<=a;r++){const o=e[n-1]===t[r-1]?0:1;i[n][r]=Math.min(i[n-1][r]+1,i[n][r-1]+1,i[n-1][r-1]+o),n>1&&r>1&&e[n-1]===t[r-2]&&e[n-2]===t[r-1]&&(i[n][r]=Math.min(i[n][r],i[n-2][r-2]+o))}return i[o][a]}var Gn,zn,Wn,Kn,Qn,Xn,Yn,Jn,Zn,er,tr,nr,rr,ir,or,ar,sr,lr,ur,cr,dr,fr,pr,hr,mr,gr,vr,yr,br,Er,Tr;e.a=Mn,Object.freeze([wn,Cn,Sn,xn,kn,Nn,_n,Dn]),g(Rn,"getDefinitionState"),g(Fn,"getFieldDef"),g(Pn,"forEachState"),g(jn,"objectValues"),g(Vn,"hintList"),g(Un,"filterAndSortList"),g(Bn,"filterNonEmpty"),g($n,"normalizeText"),g(qn,"getProximity"),g(Hn,"lexicalDistance"),function(e){function t(e){return"string"==typeof e}g(t,"is"),e.is=t}(Gn||(Gn={})),function(e){function t(e){return"string"==typeof e}g(t,"is"),e.is=t}(zn||(zn={})),function(e){function t(t){return"number"==typeof t&&e.MIN_VALUE<=t&&t<=e.MAX_VALUE}e.MIN_VALUE=-2147483648,e.MAX_VALUE=2147483647,g(t,"is"),e.is=t}(Wn||(Wn={})),function(e){function t(t){return"number"==typeof t&&e.MIN_VALUE<=t&&t<=e.MAX_VALUE}e.MIN_VALUE=0,e.MAX_VALUE=2147483647,g(t,"is"),e.is=t}(Kn||(Kn={})),function(e){function t(e,t){return e===Number.MAX_VALUE&&(e=Kn.MAX_VALUE),t===Number.MAX_VALUE&&(t=Kn.MAX_VALUE),{line:e,character:t}}function n(e){var t=e;return Si.objectLiteral(t)&&Si.uinteger(t.line)&&Si.uinteger(t.character)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(Qn||(Qn={})),function(e){function t(e,t,n,r){if(Si.uinteger(e)&&Si.uinteger(t)&&Si.uinteger(n)&&Si.uinteger(r))return{start:Qn.create(e,t),end:Qn.create(n,r)};if(Qn.is(e)&&Qn.is(t))return{start:e,end:t};throw new Error("Range#create called with invalid arguments[".concat(e,", ").concat(t,", ").concat(n,", ").concat(r,"]"))}function n(e){var t=e;return Si.objectLiteral(t)&&Qn.is(t.start)&&Qn.is(t.end)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(Xn||(Xn={})),function(e){function t(e,t){return{uri:e,range:t}}function n(e){var t=e;return Si.defined(t)&&Xn.is(t.range)&&(Si.string(t.uri)||Si.undefined(t.uri))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(Yn||(Yn={})),function(e){function t(e,t,n,r){return{targetUri:e,targetRange:t,targetSelectionRange:n,originSelectionRange:r}}function n(e){var t=e;return Si.defined(t)&&Xn.is(t.targetRange)&&Si.string(t.targetUri)&&Xn.is(t.targetSelectionRange)&&(Xn.is(t.originSelectionRange)||Si.undefined(t.originSelectionRange))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(Jn||(Jn={})),function(e){function t(e,t,n,r){return{red:e,green:t,blue:n,alpha:r}}function n(e){var t=e;return Si.objectLiteral(t)&&Si.numberRange(t.red,0,1)&&Si.numberRange(t.green,0,1)&&Si.numberRange(t.blue,0,1)&&Si.numberRange(t.alpha,0,1)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(Zn||(Zn={})),function(e){function t(e,t){return{range:e,color:t}}function n(e){var t=e;return Si.objectLiteral(t)&&Xn.is(t.range)&&Zn.is(t.color)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(er||(er={})),function(e){function t(e,t,n){return{label:e,textEdit:t,additionalTextEdits:n}}function n(e){var t=e;return Si.objectLiteral(t)&&Si.string(t.label)&&(Si.undefined(t.textEdit)||cr.is(t))&&(Si.undefined(t.additionalTextEdits)||Si.typedArray(t.additionalTextEdits,cr.is))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(tr||(tr={})),(Tr=nr||(nr={})).Comment="comment",Tr.Imports="imports",Tr.Region="region",function(e){function t(e,t,n,r,i,o){var a={startLine:e,endLine:t};return Si.defined(n)&&(a.startCharacter=n),Si.defined(r)&&(a.endCharacter=r),Si.defined(i)&&(a.kind=i),Si.defined(o)&&(a.collapsedText=o),a}function n(e){var t=e;return Si.objectLiteral(t)&&Si.uinteger(t.startLine)&&Si.uinteger(t.startLine)&&(Si.undefined(t.startCharacter)||Si.uinteger(t.startCharacter))&&(Si.undefined(t.endCharacter)||Si.uinteger(t.endCharacter))&&(Si.undefined(t.kind)||Si.string(t.kind))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(rr||(rr={})),function(e){function t(e,t){return{location:e,message:t}}function n(e){var t=e;return Si.defined(t)&&Yn.is(t.location)&&Si.string(t.message)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(ir||(ir={})),(Er=or||(or={})).Error=1,Er.Warning=2,Er.Information=3,Er.Hint=4,(br=ar||(ar={})).Unnecessary=1,br.Deprecated=2,function(e){function t(e){var t=e;return Si.objectLiteral(t)&&Si.string(t.href)}g(t,"is"),e.is=t}(sr||(sr={})),function(e){function t(e,t,n,r,i,o){var a={range:e,message:t};return Si.defined(n)&&(a.severity=n),Si.defined(r)&&(a.code=r),Si.defined(i)&&(a.source=i),Si.defined(o)&&(a.relatedInformation=o),a}function n(e){var t,n=e;return Si.defined(n)&&Xn.is(n.range)&&Si.string(n.message)&&(Si.number(n.severity)||Si.undefined(n.severity))&&(Si.integer(n.code)||Si.string(n.code)||Si.undefined(n.code))&&(Si.undefined(n.codeDescription)||Si.string(null===(t=n.codeDescription)||void 0===t?void 0:t.href))&&(Si.string(n.source)||Si.undefined(n.source))&&(Si.undefined(n.relatedInformation)||Si.typedArray(n.relatedInformation,ir.is))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(lr||(lr={})),function(e){function t(e,t){for(var n=[],r=2;r0&&(i.arguments=n),i}function n(e){var t=e;return Si.defined(t)&&Si.string(t.title)&&Si.string(t.command)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(ur||(ur={})),function(e){function t(e,t){return{range:e,newText:t}}function n(e,t){return{range:{start:e,end:e},newText:t}}function r(e){return{range:e,newText:""}}function i(e){var t=e;return Si.objectLiteral(t)&&Si.string(t.newText)&&Xn.is(t.range)}g(t,"replace"),e.replace=t,g(n,"insert"),e.insert=n,g(r,"del"),e.del=r,g(i,"is"),e.is=i}(cr||(cr={})),function(e){function t(e,t,n){var r={label:e};return void 0!==t&&(r.needsConfirmation=t),void 0!==n&&(r.description=n),r}function n(e){var t=e;return Si.objectLiteral(t)&&Si.string(t.label)&&(Si.boolean(t.needsConfirmation)||void 0===t.needsConfirmation)&&(Si.string(t.description)||void 0===t.description)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(dr||(dr={})),function(e){function t(e){var t=e;return Si.string(t)}g(t,"is"),e.is=t}(fr||(fr={})),function(e){function t(e,t,n){return{range:e,newText:t,annotationId:n}}function n(e,t,n){return{range:{start:e,end:e},newText:t,annotationId:n}}function r(e,t){return{range:e,newText:"",annotationId:t}}function i(e){var t=e;return cr.is(t)&&(dr.is(t.annotationId)||fr.is(t.annotationId))}g(t,"replace"),e.replace=t,g(n,"insert"),e.insert=n,g(r,"del"),e.del=r,g(i,"is"),e.is=i}(pr||(pr={})),function(e){function t(e,t){return{textDocument:e,edits:t}}function n(e){var t=e;return Si.defined(t)&&Sr.is(t.textDocument)&&Array.isArray(t.edits)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(hr||(hr={})),function(e){function t(e,t,n){var r={kind:"create",uri:e};return void 0===t||void 0===t.overwrite&&void 0===t.ignoreIfExists||(r.options=t),void 0!==n&&(r.annotationId=n),r}function n(e){var t=e;return t&&"create"===t.kind&&Si.string(t.uri)&&(void 0===t.options||(void 0===t.options.overwrite||Si.boolean(t.options.overwrite))&&(void 0===t.options.ignoreIfExists||Si.boolean(t.options.ignoreIfExists)))&&(void 0===t.annotationId||fr.is(t.annotationId))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(mr||(mr={})),function(e){function t(e,t,n,r){var i={kind:"rename",oldUri:e,newUri:t};return void 0===n||void 0===n.overwrite&&void 0===n.ignoreIfExists||(i.options=n),void 0!==r&&(i.annotationId=r),i}function n(e){var t=e;return t&&"rename"===t.kind&&Si.string(t.oldUri)&&Si.string(t.newUri)&&(void 0===t.options||(void 0===t.options.overwrite||Si.boolean(t.options.overwrite))&&(void 0===t.options.ignoreIfExists||Si.boolean(t.options.ignoreIfExists)))&&(void 0===t.annotationId||fr.is(t.annotationId))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(gr||(gr={})),function(e){function t(e,t,n){var r={kind:"delete",uri:e};return void 0===t||void 0===t.recursive&&void 0===t.ignoreIfNotExists||(r.options=t),void 0!==n&&(r.annotationId=n),r}function n(e){var t=e;return t&&"delete"===t.kind&&Si.string(t.uri)&&(void 0===t.options||(void 0===t.options.recursive||Si.boolean(t.options.recursive))&&(void 0===t.options.ignoreIfNotExists||Si.boolean(t.options.ignoreIfNotExists)))&&(void 0===t.annotationId||fr.is(t.annotationId))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(vr||(vr={})),function(e){function t(e){var t=e;return t&&(void 0!==t.changes||void 0!==t.documentChanges)&&(void 0===t.documentChanges||t.documentChanges.every((function(e){return Si.string(e.kind)?mr.is(e)||gr.is(e)||vr.is(e):hr.is(e)})))}g(t,"is"),e.is=t}(yr||(yr={}));var wr,Cr,Sr,xr,kr,Nr,_r,Or,Ir,Dr,Lr,Ar,Mr,Rr,Fr,Pr,jr,Vr,Ur,Br,$r,qr,Hr,Gr,zr,Wr,Kr,Qr,Xr,Yr,Jr,Zr,ei,ti,ni,ri,ii,oi,ai,si,li,ui,ci,di,fi,pi,hi,mi,gi,vi,yi,bi,Ei,Ti,wi=function(){function e(e,t){this.edits=e,this.changeAnnotations=t}return g(e,"TextEditChangeImpl"),e.prototype.insert=function(e,t,n){var r,i;if(void 0===n?r=cr.insert(e,t):fr.is(n)?(i=n,r=pr.insert(e,t,n)):(this.assertChangeAnnotations(this.changeAnnotations),i=this.changeAnnotations.manage(n),r=pr.insert(e,t,i)),this.edits.push(r),void 0!==i)return i},e.prototype.replace=function(e,t,n){var r,i;if(void 0===n?r=cr.replace(e,t):fr.is(n)?(i=n,r=pr.replace(e,t,n)):(this.assertChangeAnnotations(this.changeAnnotations),i=this.changeAnnotations.manage(n),r=pr.replace(e,t,i)),this.edits.push(r),void 0!==i)return i},e.prototype.delete=function(e,t){var n,r;if(void 0===t?n=cr.del(e):fr.is(t)?(r=t,n=pr.del(e,t)):(this.assertChangeAnnotations(this.changeAnnotations),r=this.changeAnnotations.manage(t),n=pr.del(e,r)),this.edits.push(n),void 0!==r)return r},e.prototype.add=function(e){this.edits.push(e)},e.prototype.all=function(){return this.edits},e.prototype.clear=function(){this.edits.splice(0,this.edits.length)},e.prototype.assertChangeAnnotations=function(e){if(void 0===e)throw new Error("Text edit change is not configured to manage change annotations.")},e}(),Ci=function(){function e(e){this._annotations=void 0===e?Object.create(null):e,this._counter=0,this._size=0}return g(e,"ChangeAnnotations"),e.prototype.all=function(){return this._annotations},Object.defineProperty(e.prototype,"size",{get:function(){return this._size},enumerable:!1,configurable:!0}),e.prototype.manage=function(e,t){var n;if(fr.is(e)?n=e:(n=this.nextId(),t=e),void 0!==this._annotations[n])throw new Error("Id ".concat(n," is already in use."));if(void 0===t)throw new Error("No annotation provided for id ".concat(n));return this._annotations[n]=t,this._size++,n},e.prototype.nextId=function(){return this._counter++,this._counter.toString()},e}();(function(){function e(e){var t=this;this._textEditChanges=Object.create(null),void 0!==e?(this._workspaceEdit=e,e.documentChanges?(this._changeAnnotations=new Ci(e.changeAnnotations),e.changeAnnotations=this._changeAnnotations.all(),e.documentChanges.forEach((function(e){if(hr.is(e)){var n=new wi(e.edits,t._changeAnnotations);t._textEditChanges[e.textDocument.uri]=n}}))):e.changes&&Object.keys(e.changes).forEach((function(n){var r=new wi(e.changes[n]);t._textEditChanges[n]=r}))):this._workspaceEdit={}}g(e,"WorkspaceChange"),Object.defineProperty(e.prototype,"edit",{get:function(){return this.initDocumentChanges(),void 0!==this._changeAnnotations&&(0===this._changeAnnotations.size?this._workspaceEdit.changeAnnotations=void 0:this._workspaceEdit.changeAnnotations=this._changeAnnotations.all()),this._workspaceEdit},enumerable:!1,configurable:!0}),e.prototype.getTextEditChange=function(e){if(Sr.is(e)){if(this.initDocumentChanges(),void 0===this._workspaceEdit.documentChanges)throw new Error("Workspace edit is not configured for document changes.");var t={uri:e.uri,version:e.version};if(!(r=this._textEditChanges[t.uri])){var n={textDocument:t,edits:i=[]};this._workspaceEdit.documentChanges.push(n),r=new wi(i,this._changeAnnotations),this._textEditChanges[t.uri]=r}return r}if(this.initChanges(),void 0===this._workspaceEdit.changes)throw new Error("Workspace edit is not configured for normal text edit changes.");var r;if(!(r=this._textEditChanges[e])){var i=[];this._workspaceEdit.changes[e]=i,r=new wi(i),this._textEditChanges[e]=r}return r},e.prototype.initDocumentChanges=function(){void 0===this._workspaceEdit.documentChanges&&void 0===this._workspaceEdit.changes&&(this._changeAnnotations=new Ci,this._workspaceEdit.documentChanges=[],this._workspaceEdit.changeAnnotations=this._changeAnnotations.all())},e.prototype.initChanges=function(){void 0===this._workspaceEdit.documentChanges&&void 0===this._workspaceEdit.changes&&(this._workspaceEdit.changes=Object.create(null))},e.prototype.createFile=function(e,t,n){if(this.initDocumentChanges(),void 0===this._workspaceEdit.documentChanges)throw new Error("Workspace edit is not configured for document changes.");var r,i,o;if(dr.is(t)||fr.is(t)?r=t:n=t,void 0===r?i=mr.create(e,n):(o=fr.is(r)?r:this._changeAnnotations.manage(r),i=mr.create(e,n,o)),this._workspaceEdit.documentChanges.push(i),void 0!==o)return o},e.prototype.renameFile=function(e,t,n,r){if(this.initDocumentChanges(),void 0===this._workspaceEdit.documentChanges)throw new Error("Workspace edit is not configured for document changes.");var i,o,a;if(dr.is(n)||fr.is(n)?i=n:r=n,void 0===i?o=gr.create(e,t,r):(a=fr.is(i)?i:this._changeAnnotations.manage(i),o=gr.create(e,t,r,a)),this._workspaceEdit.documentChanges.push(o),void 0!==a)return a},e.prototype.deleteFile=function(e,t,n){if(this.initDocumentChanges(),void 0===this._workspaceEdit.documentChanges)throw new Error("Workspace edit is not configured for document changes.");var r,i,o;if(dr.is(t)||fr.is(t)?r=t:n=t,void 0===r?i=vr.create(e,n):(o=fr.is(r)?r:this._changeAnnotations.manage(r),i=vr.create(e,n,o)),this._workspaceEdit.documentChanges.push(i),void 0!==o)return o}})(),function(e){function t(e){return{uri:e}}function n(e){var t=e;return Si.defined(t)&&Si.string(t.uri)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(wr||(wr={})),function(e){function t(e,t){return{uri:e,version:t}}function n(e){var t=e;return Si.defined(t)&&Si.string(t.uri)&&Si.integer(t.version)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(Cr||(Cr={})),function(e){function t(e,t){return{uri:e,version:t}}function n(e){var t=e;return Si.defined(t)&&Si.string(t.uri)&&(null===t.version||Si.integer(t.version))}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(Sr||(Sr={})),function(e){function t(e,t,n,r){return{uri:e,languageId:t,version:n,text:r}}function n(e){var t=e;return Si.defined(t)&&Si.string(t.uri)&&Si.string(t.languageId)&&Si.integer(t.version)&&Si.string(t.text)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(xr||(xr={})),function(e){function t(t){var n=t;return n===e.PlainText||n===e.Markdown}e.PlainText="plaintext",e.Markdown="markdown",g(t,"is"),e.is=t}(kr||(kr={})),function(e){function t(e){var t=e;return Si.objectLiteral(e)&&kr.is(t.kind)&&Si.string(t.value)}g(t,"is"),e.is=t}(Nr||(Nr={})),(Ti=_r||(_r={})).Text=1,Ti.Method=2,Ti.Function=3,Ti.Constructor=4,Ti.Field=5,Ti.Variable=6,Ti.Class=7,Ti.Interface=8,Ti.Module=9,Ti.Property=10,Ti.Unit=11,Ti.Value=12,Ti.Enum=13,Ti.Keyword=14,Ti.Snippet=15,Ti.Color=16,Ti.File=17,Ti.Reference=18,Ti.Folder=19,Ti.EnumMember=20,Ti.Constant=21,Ti.Struct=22,Ti.Event=23,Ti.Operator=24,Ti.TypeParameter=25,(Ei=Or||(Or={})).PlainText=1,Ei.Snippet=2,(Ir||(Ir={})).Deprecated=1,function(e){function t(e,t,n){return{newText:e,insert:t,replace:n}}function n(e){var t=e;return t&&Si.string(t.newText)&&Xn.is(t.insert)&&Xn.is(t.replace)}g(t,"create"),e.create=t,g(n,"is"),e.is=n}(Dr||(Dr={})),(bi=Lr||(Lr={})).asIs=1,bi.adjustIndentation=2,function(e){function t(e){var t=e;return t&&(Si.string(t.detail)||void 0===t.detail)&&(Si.string(t.description)||void 0===t.description)}g(t,"is"),e.is=t}(Ar||(Ar={})),function(e){function t(e){return{label:e}}g(t,"create"),e.create=t}(Mr||(Mr={})),function(e){function t(e,t){return{items:e||[],isIncomplete:!!t}}g(t,"create"),e.create=t}(Rr||(Rr={})),function(e){function t(e){return e.replace(/[\\`*_{}[\]()#+\-.!]/g,"\\$&")}function n(e){var t=e;return Si.string(t)||Si.objectLiteral(t)&&Si.string(t.language)&&Si.string(t.value)}g(t,"fromPlainText"),e.fromPlainText=t,g(n,"is"),e.is=n}(Fr||(Fr={})),function(e){function t(e){var t=e;return!!t&&Si.objectLiteral(t)&&(Nr.is(t.contents)||Fr.is(t.contents)||Si.typedArray(t.contents,Fr.is))&&(void 0===e.range||Xn.is(e.range))}g(t,"is"),e.is=t}(Pr||(Pr={})),function(e){function t(e,t){return t?{label:e,documentation:t}:{label:e}}g(t,"create"),e.create=t}(jr||(jr={})),function(e){function t(e,t){for(var n=[],r=2;r=0;a--){var s=r[a],l=e.offsetAt(s.range.start),u=e.offsetAt(s.range.end);if(!(u<=o))throw new Error("Overlapping edit");n=n.substring(0,l)+s.newText+n.substring(u,n.length),o=l}return n}function i(e,t){if(e.length<=1)return e;var n=e.length/2|0,r=e.slice(0,n),o=e.slice(n);i(r,t),i(o,t);for(var a=0,s=0,l=0;a0&&e.push(t.length),this._lineOffsets=e}return this._lineOffsets},e.prototype.positionAt=function(e){e=Math.max(Math.min(e,this._content.length),0);var t=this.getLineOffsets(),n=0,r=t.length;if(0===r)return Qn.create(0,e);for(;ne?r=i:n=i+1}var o=n-1;return Qn.create(o,e-t[o])},e.prototype.offsetAt=function(e){var t=this.getLineOffsets();if(e.line>=t.length)return this._content.length;if(e.line<0)return 0;var n=t[e.line],r=e.line+1this._start,this.getCurrentPosition=()=>this._pos,this.eol=()=>this._sourceText.length===this._pos,this.sol=()=>0===this._pos,this.peek=()=>this._sourceText.charAt(this._pos)||null,this.next=()=>{const e=this._sourceText.charAt(this._pos);return this._pos++,e},this.eat=e=>{if(this._testNextCharacter(e))return this._start=this._pos,this._pos++,this._sourceText.charAt(this._pos-1)},this.eatWhile=e=>{let t=this._testNextCharacter(e),n=!1;for(t&&(n=t,this._start=this._pos);t;)this._pos++,t=this._testNextCharacter(e),n=!0;return n},this.eatSpace=()=>this.eatWhile(/[\s\u00a0]/),this.skipToEnd=()=>{this._pos=this._sourceText.length},this.skipTo=e=>{this._pos=e},this.match=function(e){let n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],r=null,i=null;return"string"==typeof e?(i=new RegExp(e,arguments.length>2&&void 0!==arguments[2]&&arguments[2]?"i":"g").test(t._sourceText.slice(t._pos,t._pos+e.length)),r=e):e instanceof RegExp&&(i=t._sourceText.slice(t._pos).match(e),r=null==i?void 0:i[0]),!(null==i||!("string"==typeof e||i instanceof Array&&t._sourceText.startsWith(i[0],t._pos)))&&(n&&(t._start=t._pos,r&&r.length&&(t._pos+=r.length)),i)},this.backUp=e=>{this._pos-=e},this.column=()=>this._pos,this.indentation=()=>{const e=this._sourceText.match(/\s*/);let t=0;if(e&&0!==e.length){const n=e[0];let r=0;for(;n.length>r;)9===n.charCodeAt(r)?t+=2:t++,r++}return t},this.current=()=>this._sourceText.slice(this._start,this._pos),this._start=0,this._pos=0,this._sourceText=e}_testNextCharacter(e){const t=this._sourceText.charAt(this._pos);let n=!1;return n="string"==typeof e?t===e:e instanceof RegExp?e.test(t):e(t),n}}function _i(e){return{ofRule:e}}function Oi(e,t){return{ofRule:e,isList:!0,separator:t}}function Ii(e,t){const n=e.match;return e.match=e=>{let r=!1;return n&&(r=n(e)),r&&t.every((t=>t.match&&!t.match(e)))},e}function Di(e,t){return{style:t,match:t=>t.kind===e}}function Li(e,t){return{style:t||"punctuation",match:t=>"Punctuation"===t.kind&&t.value===e}}e.C=Ni,g(Ni,"CharacterStream"),g(_i,"opt"),g(Oi,"list$1"),g(Ii,"butNot"),g(Di,"t$2"),g(Li,"p$1");const Ai=g((e=>" "===e||"\t"===e||","===e||"\n"===e||"\r"===e||"\ufeff"===e||" "===e),"isIgnored");e.i=Ai;const Mi={Name:/^[_A-Za-z][_0-9A-Za-z]*/,Punctuation:/^(?:!|\$|\(|\)|\.\.\.|:|=|&|@|\[|]|\{|\||\})/,Number:/^-?(?:0|(?:[1-9][0-9]*))(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?/,String:/^(?:"""(?:\\"""|[^"]|"[^"]|""[^"])*(?:""")?|"(?:[^"\\]|\\(?:"|\/|\\|b|f|n|r|t|u[0-9a-fA-F]{4}))*"?)/,Comment:/^#.*/};e.L=Mi;const Ri={Document:[Oi("Definition")],Definition(e){switch(e.value){case"{":return"ShortQuery";case"query":return"Query";case"mutation":return"Mutation";case"subscription":return"Subscription";case"fragment":return r.Kind.FRAGMENT_DEFINITION;case"schema":return"SchemaDef";case"scalar":return"ScalarDef";case"type":return"ObjectTypeDef";case"interface":return"InterfaceDef";case"union":return"UnionDef";case"enum":return"EnumDef";case"input":return"InputDef";case"extend":return"ExtendDef";case"directive":return"DirectiveDef"}},ShortQuery:["SelectionSet"],Query:[Fi("query"),_i(Pi("def")),_i("VariableDefinitions"),Oi("Directive"),"SelectionSet"],Mutation:[Fi("mutation"),_i(Pi("def")),_i("VariableDefinitions"),Oi("Directive"),"SelectionSet"],Subscription:[Fi("subscription"),_i(Pi("def")),_i("VariableDefinitions"),Oi("Directive"),"SelectionSet"],VariableDefinitions:[Li("("),Oi("VariableDefinition"),Li(")")],VariableDefinition:["Variable",Li(":"),"Type",_i("DefaultValue")],Variable:[Li("$","variable"),Pi("variable")],DefaultValue:[Li("="),"Value"],SelectionSet:[Li("{"),Oi("Selection"),Li("}")],Selection(e,t){return"..."===e.value?t.match(/[\s\u00a0,]*(on\b|@|{)/,!1)?"InlineFragment":"FragmentSpread":t.match(/[\s\u00a0,]*:/,!1)?"AliasedField":"Field"},AliasedField:[Pi("property"),Li(":"),Pi("qualifier"),_i("Arguments"),Oi("Directive"),_i("SelectionSet")],Field:[Pi("property"),_i("Arguments"),Oi("Directive"),_i("SelectionSet")],Arguments:[Li("("),Oi("Argument"),Li(")")],Argument:[Pi("attribute"),Li(":"),"Value"],FragmentSpread:[Li("..."),Pi("def"),Oi("Directive")],InlineFragment:[Li("..."),_i("TypeCondition"),Oi("Directive"),"SelectionSet"],FragmentDefinition:[Fi("fragment"),_i(Ii(Pi("def"),[Fi("on")])),"TypeCondition",Oi("Directive"),"SelectionSet"],TypeCondition:[Fi("on"),"NamedType"],Value(e){switch(e.kind){case"Number":return"NumberValue";case"String":return"StringValue";case"Punctuation":switch(e.value){case"[":return"ListValue";case"{":return"ObjectValue";case"$":return"Variable";case"&":return"NamedType"}return null;case"Name":switch(e.value){case"true":case"false":return"BooleanValue"}return"null"===e.value?"NullValue":"EnumValue"}},NumberValue:[Di("Number","number")],StringValue:[{style:"string",match:e=>"String"===e.kind,update(e,t){t.value.startsWith('"""')&&(e.inBlockstring=!t.value.slice(3).endsWith('"""'))}}],BooleanValue:[Di("Name","builtin")],NullValue:[Di("Name","keyword")],EnumValue:[Pi("string-2")],ListValue:[Li("["),Oi("Value"),Li("]")],ObjectValue:[Li("{"),Oi("ObjectField"),Li("}")],ObjectField:[Pi("attribute"),Li(":"),"Value"],Type(e){return"["===e.value?"ListType":"NonNullType"},ListType:[Li("["),"Type",Li("]"),_i(Li("!"))],NonNullType:["NamedType",_i(Li("!"))],NamedType:[ji("atom")],Directive:[Li("@","meta"),Pi("meta"),_i("Arguments")],DirectiveDef:[Fi("directive"),Li("@","meta"),Pi("meta"),_i("ArgumentsDef"),Fi("on"),Oi("DirectiveLocation",Li("|"))],InterfaceDef:[Fi("interface"),Pi("atom"),_i("Implements"),Oi("Directive"),Li("{"),Oi("FieldDef"),Li("}")],Implements:[Fi("implements"),Oi("NamedType",Li("&"))],DirectiveLocation:[Pi("string-2")],SchemaDef:[Fi("schema"),Oi("Directive"),Li("{"),Oi("OperationTypeDef"),Li("}")],OperationTypeDef:[Pi("keyword"),Li(":"),Pi("atom")],ScalarDef:[Fi("scalar"),Pi("atom"),Oi("Directive")],ObjectTypeDef:[Fi("type"),Pi("atom"),_i("Implements"),Oi("Directive"),Li("{"),Oi("FieldDef"),Li("}")],FieldDef:[Pi("property"),_i("ArgumentsDef"),Li(":"),"Type",Oi("Directive")],ArgumentsDef:[Li("("),Oi("InputValueDef"),Li(")")],InputValueDef:[Pi("attribute"),Li(":"),"Type",_i("DefaultValue"),Oi("Directive")],UnionDef:[Fi("union"),Pi("atom"),Oi("Directive"),Li("="),Oi("UnionMember",Li("|"))],UnionMember:["NamedType"],EnumDef:[Fi("enum"),Pi("atom"),Oi("Directive"),Li("{"),Oi("EnumValueDef"),Li("}")],EnumValueDef:[Pi("string-2"),Oi("Directive")],InputDef:[Fi("input"),Pi("atom"),Oi("Directive"),Li("{"),Oi("InputValueDef"),Li("}")],ExtendDef:[Fi("extend"),"ExtensionDefinition"],ExtensionDefinition(e){switch(e.value){case"schema":return r.Kind.SCHEMA_EXTENSION;case"scalar":return r.Kind.SCALAR_TYPE_EXTENSION;case"type":return r.Kind.OBJECT_TYPE_EXTENSION;case"interface":return r.Kind.INTERFACE_TYPE_EXTENSION;case"union":return r.Kind.UNION_TYPE_EXTENSION;case"enum":return r.Kind.ENUM_TYPE_EXTENSION;case"input":return r.Kind.INPUT_OBJECT_TYPE_EXTENSION}},[r.Kind.SCHEMA_EXTENSION]:["SchemaDef"],[r.Kind.SCALAR_TYPE_EXTENSION]:["ScalarDef"],[r.Kind.OBJECT_TYPE_EXTENSION]:["ObjectTypeDef"],[r.Kind.INTERFACE_TYPE_EXTENSION]:["InterfaceDef"],[r.Kind.UNION_TYPE_EXTENSION]:["UnionDef"],[r.Kind.ENUM_TYPE_EXTENSION]:["EnumDef"],[r.Kind.INPUT_OBJECT_TYPE_EXTENSION]:["InputDef"]};function Fi(e){return{style:"keyword",match:t=>"Name"===t.kind&&t.value===e}}function Pi(e){return{style:e,match:e=>"Name"===e.kind,update(e,t){e.name=t.value}}}function ji(e){return{style:e,match:e=>"Name"===e.kind,update(e,t){var n;(null===(n=e.prevState)||void 0===n?void 0:n.prevState)&&(e.name=t.value,e.prevState.prevState.type=t.value)}}}function Vi(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{eatWhitespace:e=>e.eatWhile(Ai),lexRules:Mi,parseRules:Ri,editorConfig:{}};return{startState(){const t={level:0,step:0,name:null,kind:null,type:null,rule:null,needsSeparator:!1,prevState:null};return qi(e.parseRules,t,r.Kind.DOCUMENT),t},token(t,n){return Ui(t,n,e)}}}function Ui(e,t,n){var r;if(t.inBlockstring)return e.match(/.*"""/)?(t.inBlockstring=!1,"string"):(e.skipToEnd(),"string");const{lexRules:i,parseRules:o,eatWhitespace:a,editorConfig:s}=n;if(t.rule&&0===t.rule.length?Hi(t):t.needsAdvance&&(t.needsAdvance=!1,Gi(t,!0)),e.sol()){const n=(null==s?void 0:s.tabSize)||2;t.indentLevel=Math.floor(e.indentation()/n)}if(a(e))return"ws";const l=Ki(i,e);if(!l)return e.match(/\S+/)||e.match(/\s/),qi($i,t,"Invalid"),"invalidchar";if("Comment"===l.kind)return qi($i,t,"Comment"),"comment";const u=Bi({},t);if("Punctuation"===l.kind)if(/^[{([]/.test(l.value))void 0!==t.indentLevel&&(t.levels=(t.levels||[]).concat(t.indentLevel+1));else if(/^[})\]]/.test(l.value)){const e=t.levels=(t.levels||[]).slice(0,-1);t.indentLevel&&e.length>0&&e.at(-1){const t=[];if(e)try{(0,r.visit)((0,r.parse)(e),{FragmentDefinition(e){t.push(e)}})}catch(e){return[]}return t}),"collectFragmentDefs"),Ji=[r.Kind.SCHEMA_DEFINITION,r.Kind.OPERATION_TYPE_DEFINITION,r.Kind.SCALAR_TYPE_DEFINITION,r.Kind.OBJECT_TYPE_DEFINITION,r.Kind.INTERFACE_TYPE_DEFINITION,r.Kind.UNION_TYPE_DEFINITION,r.Kind.ENUM_TYPE_DEFINITION,r.Kind.INPUT_OBJECT_TYPE_DEFINITION,r.Kind.DIRECTIVE_DEFINITION,r.Kind.SCHEMA_EXTENSION,r.Kind.SCALAR_TYPE_EXTENSION,r.Kind.OBJECT_TYPE_EXTENSION,r.Kind.INTERFACE_TYPE_EXTENSION,r.Kind.UNION_TYPE_EXTENSION,r.Kind.ENUM_TYPE_EXTENSION,r.Kind.INPUT_OBJECT_TYPE_EXTENSION],Zi=g((e=>{let t=!1;if(e)try{(0,r.visit)((0,r.parse)(e),{enter(e){if("Document"!==e.kind)return!!Ji.includes(e.kind)&&(t=!0,r.BREAK)}})}catch(e){return t}return t}),"hasTypeSystemDefinitions");function eo(e,t,n,i,o,a){var s;const l=Object.assign(Object.assign({},a),{schema:e}),u=i||vo(t,n),c="Invalid"===u.state.kind?u.state.prevState:u.state,d=(null==a?void 0:a.mode)||Co(t,null==a?void 0:a.uri);if(!c)return[];const{kind:f,step:p,prevState:h}=c,m=Eo(e,u.state);if(f===Qi.DOCUMENT)return d===To.TYPE_SYSTEM?ro(u):io(u);if(f===Qi.EXTEND_DEF)return oo(u);if((null===(s=null==h?void 0:h.prevState)||void 0===s?void 0:s.kind)===Qi.EXTENSION_DEFINITION&&c.name)return Vn(u,[]);if((null==h?void 0:h.kind)===r.Kind.SCALAR_TYPE_EXTENSION)return Vn(u,Object.values(e.getTypeMap()).filter(r.isScalarType).map((e=>({label:e.name,kind:xi.Function}))));if((null==h?void 0:h.kind)===r.Kind.OBJECT_TYPE_EXTENSION)return Vn(u,Object.values(e.getTypeMap()).filter((e=>(0,r.isObjectType)(e)&&!e.name.startsWith("__"))).map((e=>({label:e.name,kind:xi.Function}))));if((null==h?void 0:h.kind)===r.Kind.INTERFACE_TYPE_EXTENSION)return Vn(u,Object.values(e.getTypeMap()).filter(r.isInterfaceType).map((e=>({label:e.name,kind:xi.Function}))));if((null==h?void 0:h.kind)===r.Kind.UNION_TYPE_EXTENSION)return Vn(u,Object.values(e.getTypeMap()).filter(r.isUnionType).map((e=>({label:e.name,kind:xi.Function}))));if((null==h?void 0:h.kind)===r.Kind.ENUM_TYPE_EXTENSION)return Vn(u,Object.values(e.getTypeMap()).filter((e=>(0,r.isEnumType)(e)&&!e.name.startsWith("__"))).map((e=>({label:e.name,kind:xi.Function}))));if((null==h?void 0:h.kind)===r.Kind.INPUT_OBJECT_TYPE_EXTENSION)return Vn(u,Object.values(e.getTypeMap()).filter(r.isInputObjectType).map((e=>({label:e.name,kind:xi.Function}))));if(f===Qi.IMPLEMENTS||f===Qi.NAMED_TYPE&&(null==h?void 0:h.kind)===Qi.IMPLEMENTS)return lo(u,c,e,t,m);if(f===Qi.SELECTION_SET||f===Qi.FIELD||f===Qi.ALIASED_FIELD)return ao(u,m,l);if(f===Qi.ARGUMENTS||f===Qi.ARGUMENT&&0===p){const{argDefs:e}=m;if(e)return Vn(u,e.map((e=>{var t;return{label:e.name,insertText:e.name+": ",command:Xi,detail:String(e.type),documentation:null!==(t=e.description)&&void 0!==t?t:void 0,kind:xi.Variable,type:e.type}})))}if((f===Qi.OBJECT_VALUE||f===Qi.OBJECT_FIELD&&0===p)&&m.objectFieldDefs){const e=jn(m.objectFieldDefs),t=f===Qi.OBJECT_VALUE?xi.Value:xi.Field;return Vn(u,e.map((e=>{var n;return{label:e.name,detail:String(e.type),documentation:null!==(n=e.description)&&void 0!==n?n:void 0,kind:t,type:e.type}})))}if(f===Qi.ENUM_VALUE||f===Qi.LIST_VALUE&&1===p||f===Qi.OBJECT_FIELD&&2===p||f===Qi.ARGUMENT&&2===p)return so(u,m,t,e);if(f===Qi.VARIABLE&&1===p){const n=(0,r.getNamedType)(m.inputType);return Vn(u,po(t,e,u).filter((e=>e.detail===(null==n?void 0:n.name))))}if(f===Qi.TYPE_CONDITION&&1===p||f===Qi.NAMED_TYPE&&null!=h&&h.kind===Qi.TYPE_CONDITION)return uo(u,m,e);if(f===Qi.FRAGMENT_SPREAD&&1===p)return co(u,m,e,t,Array.isArray(o)?o:Yi(o));const g=So(c);if(d===To.TYPE_SYSTEM&&!g.needsAdvance&&f===Qi.NAMED_TYPE||f===Qi.LIST_TYPE){if(g.kind===Qi.FIELD_DEF)return Vn(u,Object.values(e.getTypeMap()).filter((e=>(0,r.isOutputType)(e)&&!e.name.startsWith("__"))).map((e=>({label:e.name,kind:xi.Function}))));if(g.kind===Qi.INPUT_VALUE_DEF)return Vn(u,Object.values(e.getTypeMap()).filter((e=>(0,r.isInputType)(e)&&!e.name.startsWith("__"))).map((e=>({label:e.name,kind:xi.Function}))))}return f===Qi.VARIABLE_DEFINITION&&2===p||f===Qi.LIST_TYPE&&1===p||f===Qi.NAMED_TYPE&&h&&(h.kind===Qi.VARIABLE_DEFINITION||h.kind===Qi.LIST_TYPE||h.kind===Qi.NON_NULL_TYPE)?mo(u,e):f===Qi.DIRECTIVE?go(u,c,e):[]}g(eo,"getAutocompleteSuggestions");const to=" {\n $1\n}",no=g((e=>{const{type:t}=e;if((0,r.isCompositeType)(t))return to;if((0,r.isListType)(t)&&(0,r.isCompositeType)(t.ofType))return to;if((0,r.isNonNullType)(t)){if((0,r.isCompositeType)(t.ofType))return to;if((0,r.isListType)(t.ofType)&&(0,r.isCompositeType)(t.ofType.ofType))return to}return null}),"getInsertText");function ro(e){return Vn(e,[{label:"extend",kind:xi.Function},{label:"type",kind:xi.Function},{label:"interface",kind:xi.Function},{label:"union",kind:xi.Function},{label:"input",kind:xi.Function},{label:"scalar",kind:xi.Function},{label:"schema",kind:xi.Function}])}function io(e){return Vn(e,[{label:"query",kind:xi.Function},{label:"mutation",kind:xi.Function},{label:"subscription",kind:xi.Function},{label:"fragment",kind:xi.Function},{label:"{",kind:xi.Constructor}])}function oo(e){return Vn(e,[{label:"type",kind:xi.Function},{label:"interface",kind:xi.Function},{label:"union",kind:xi.Function},{label:"input",kind:xi.Function},{label:"scalar",kind:xi.Function},{label:"schema",kind:xi.Function}])}function ao(e,t,n){var i;if(t.parentType){const{parentType:o}=t;let a=[];return"getFields"in o&&(a=jn(o.getFields())),(0,r.isCompositeType)(o)&&a.push(r.TypeNameMetaFieldDef),o===(null===(i=null==n?void 0:n.schema)||void 0===i?void 0:i.getQueryType())&&a.push(r.SchemaMetaFieldDef,r.TypeMetaFieldDef),Vn(e,a.map(((e,t)=>{var r;const i={sortText:String(t)+e.name,label:e.name,detail:String(e.type),documentation:null!==(r=e.description)&&void 0!==r?r:void 0,deprecated:Boolean(e.deprecationReason),isDeprecated:Boolean(e.deprecationReason),deprecationReason:e.deprecationReason,kind:xi.Field,type:e.type};if(null==n?void 0:n.fillLeafsOnComplete){const t=no(e);t&&(i.insertText=e.name+t,i.insertTextFormat=Or.Snippet,i.command=Xi)}return i})))}return[]}function so(e,t,n,i){const o=(0,r.getNamedType)(t.inputType),a=po(n,i,e).filter((e=>e.detail===o.name));return o instanceof r.GraphQLEnumType?Vn(e,o.getValues().map((e=>{var t;return{label:e.name,detail:String(o),documentation:null!==(t=e.description)&&void 0!==t?t:void 0,deprecated:Boolean(e.deprecationReason),isDeprecated:Boolean(e.deprecationReason),deprecationReason:e.deprecationReason,kind:xi.EnumMember,type:o}})).concat(a)):o===r.GraphQLBoolean?Vn(e,a.concat([{label:"true",detail:String(r.GraphQLBoolean),documentation:"Not false.",kind:xi.Variable,type:r.GraphQLBoolean},{label:"false",detail:String(r.GraphQLBoolean),documentation:"Not true.",kind:xi.Variable,type:r.GraphQLBoolean}])):a}function lo(e,t,n,i,o){if(t.needsSeparator)return[];const a=jn(n.getTypeMap()).filter(r.isInterfaceType),s=a.map((e=>{let{name:t}=e;return t})),l=new Set;yo(i,((e,t)=>{var i,a,u,c,d;if(t.name&&(t.kind!==Qi.INTERFACE_DEF||s.includes(t.name)||l.add(t.name),t.kind===Qi.NAMED_TYPE&&(null===(i=t.prevState)||void 0===i?void 0:i.kind)===Qi.IMPLEMENTS))if(o.interfaceDef){if(null===(a=o.interfaceDef)||void 0===a?void 0:a.getInterfaces().find((e=>{let{name:n}=e;return n===t.name})))return;const e=n.getType(t.name),i=null===(u=o.interfaceDef)||void 0===u?void 0:u.toConfig();o.interfaceDef=new r.GraphQLInterfaceType(Object.assign(Object.assign({},i),{interfaces:[...i.interfaces,e||new r.GraphQLInterfaceType({name:t.name,fields:{}})]}))}else if(o.objectTypeDef){if(null===(c=o.objectTypeDef)||void 0===c?void 0:c.getInterfaces().find((e=>{let{name:n}=e;return n===t.name})))return;const e=n.getType(t.name),i=null===(d=o.objectTypeDef)||void 0===d?void 0:d.toConfig();o.objectTypeDef=new r.GraphQLObjectType(Object.assign(Object.assign({},i),{interfaces:[...i.interfaces,e||new r.GraphQLInterfaceType({name:t.name,fields:{}})]}))}}));const u=o.interfaceDef||o.objectTypeDef,c=((null==u?void 0:u.getInterfaces())||[]).map((e=>{let{name:t}=e;return t}));return Vn(e,a.concat([...l].map((e=>({name:e})))).filter((e=>{let{name:t}=e;return t!==(null==u?void 0:u.name)&&!c.includes(t)})).map((e=>{const t={label:e.name,kind:xi.Interface,type:e};return(null==e?void 0:e.description)&&(t.documentation=e.description),t})))}function uo(e,t,n,i){let o;if(t.parentType)if((0,r.isAbstractType)(t.parentType)){const e=(0,r.assertAbstractType)(t.parentType),i=n.getPossibleTypes(e),a=Object.create(null);for(const e of i)for(const t of e.getInterfaces())a[t.name]=t;o=i.concat(jn(a))}else o=[t.parentType];else o=jn(n.getTypeMap()).filter((e=>(0,r.isCompositeType)(e)&&!e.name.startsWith("__")));return Vn(e,o.map((e=>{const t=(0,r.getNamedType)(e);return{label:String(e),documentation:(null==t?void 0:t.description)||"",kind:xi.Field}})))}function co(e,t,n,i,o){if(!i)return[];const a=n.getTypeMap(),s=Rn(e.state),l=ho(i);return o&&o.length>0&&l.push(...o),Vn(e,l.filter((e=>a[e.typeCondition.name.value]&&!(s&&s.kind===Qi.FRAGMENT_DEFINITION&&s.name===e.name.value)&&(0,r.isCompositeType)(t.parentType)&&(0,r.isCompositeType)(a[e.typeCondition.name.value])&&(0,r.doTypesOverlap)(n,t.parentType,a[e.typeCondition.name.value]))).map((e=>({label:e.name.value,detail:String(a[e.typeCondition.name.value]),documentation:`fragment ${e.name.value} on ${e.typeCondition.name.value}`,kind:xi.Field,type:a[e.typeCondition.name.value]}))))}g(ro,"getSuggestionsForTypeSystemDefinitions"),g(io,"getSuggestionsForExecutableDefinitions"),g(oo,"getSuggestionsForExtensionDefinitions"),g(ao,"getSuggestionsForFieldNames"),g(so,"getSuggestionsForInputValues"),g(lo,"getSuggestionsForImplements"),g(uo,"getSuggestionsForFragmentTypeConditions"),g(co,"getSuggestionsForFragmentSpread");const fo=g(((e,t)=>{var n,r,i,o,a,s,l,u,c,d;return(null===(n=e.prevState)||void 0===n?void 0:n.kind)===t?e.prevState:(null===(i=null===(r=e.prevState)||void 0===r?void 0:r.prevState)||void 0===i?void 0:i.kind)===t?e.prevState.prevState:(null===(s=null===(a=null===(o=e.prevState)||void 0===o?void 0:o.prevState)||void 0===a?void 0:a.prevState)||void 0===s?void 0:s.kind)===t?e.prevState.prevState.prevState:(null===(d=null===(c=null===(u=null===(l=e.prevState)||void 0===l?void 0:l.prevState)||void 0===u?void 0:u.prevState)||void 0===c?void 0:c.prevState)||void 0===d?void 0:d.kind)===t?e.prevState.prevState.prevState.prevState:void 0}),"getParentDefinition");function po(e,t,n){let r,i=null;const o=Object.create({});return yo(e,((e,a)=>{if((null==a?void 0:a.kind)===Qi.VARIABLE&&a.name&&(i=a.name),(null==a?void 0:a.kind)===Qi.NAMED_TYPE&&i){const e=fo(a,Qi.TYPE);(null==e?void 0:e.type)&&(r=t.getType(null==e?void 0:e.type))}i&&r&&!o[i]&&(o[i]={detail:r.toString(),insertText:"$"===n.string?i:"$"+i,label:i,type:r,kind:xi.Variable},i=null,r=null)})),jn(o)}function ho(e){const t=[];return yo(e,((e,n)=>{n.kind===Qi.FRAGMENT_DEFINITION&&n.name&&n.type&&t.push({kind:Qi.FRAGMENT_DEFINITION,name:{kind:r.Kind.NAME,value:n.name},selectionSet:{kind:Qi.SELECTION_SET,selections:[]},typeCondition:{kind:Qi.NAMED_TYPE,name:{kind:r.Kind.NAME,value:n.type}}})})),t}function mo(e,t,n){return Vn(e,jn(t.getTypeMap()).filter(r.isInputType).map((e=>({label:e.name,documentation:e.description,kind:xi.Variable}))))}function go(e,t,n,r){var i;return(null===(i=t.prevState)||void 0===i?void 0:i.kind)?Vn(e,n.getDirectives().filter((e=>bo(t.prevState,e))).map((e=>({label:e.name,documentation:e.description||"",kind:xi.Function})))):[]}function vo(e,t){let n=null,r=null,i=null;const o=yo(e,((e,o,a,s)=>{if(s===t.line&&e.getCurrentPosition()>=t.character)return n=a,r=Object.assign({},o),i=e.current(),"BREAK"}));return{start:o.start,end:o.end,string:i||o.string,state:r||o.state,style:n||o.style}}function yo(e,t){const n=e.split("\n"),r=Vi();let i=r.startState(),o="",a=new Ni("");for(let e=0;e{var h;switch(t.kind){case Qi.QUERY:case"ShortQuery":f=e.getQueryType();break;case Qi.MUTATION:f=e.getMutationType();break;case Qi.SUBSCRIPTION:f=e.getSubscriptionType();break;case Qi.INLINE_FRAGMENT:case Qi.FRAGMENT_DEFINITION:t.type&&(f=e.getType(t.type));break;case Qi.FIELD:case Qi.ALIASED_FIELD:f&&t.name?(s=d?Fn(e,d,t.name):null,f=s?s.type:null):s=null;break;case Qi.SELECTION_SET:d=(0,r.getNamedType)(f);break;case Qi.DIRECTIVE:o=t.name?e.getDirective(t.name):null;break;case Qi.INTERFACE_DEF:t.name&&(u=null,p=new r.GraphQLInterfaceType({name:t.name,interfaces:[],fields:{}}));break;case Qi.OBJECT_TYPE_DEF:t.name&&(p=null,u=new r.GraphQLObjectType({name:t.name,interfaces:[],fields:{}}));break;case Qi.ARGUMENTS:if(t.prevState)switch(t.prevState.kind){case Qi.FIELD:i=s&&s.args;break;case Qi.DIRECTIVE:i=o&&o.args;break;case Qi.ALIASED_FIELD:{const n=null===(h=t.prevState)||void 0===h?void 0:h.name;if(!n){i=null;break}const r=d?Fn(e,d,n):null;if(!r){i=null;break}i=r.args;break}default:i=null}else i=null;break;case Qi.ARGUMENT:if(i)for(let e=0;ee.value===t.name)):null;break;case Qi.LIST_VALUE:const g=(0,r.getNullableType)(l);l=g instanceof r.GraphQLList?g.ofType:null;break;case Qi.OBJECT_VALUE:const v=(0,r.getNamedType)(l);c=v instanceof r.GraphQLInputObjectType?v.getFields():null;break;case Qi.OBJECT_FIELD:const y=t.name&&c?c[t.name]:null;l=null==y?void 0:y.type;break;case Qi.NAMED_TYPE:t.name&&(f=e.getType(t.name))}})),{argDef:n,argDefs:i,directiveDef:o,enumValue:a,fieldDef:s,inputType:l,objectFieldDefs:c,parentType:d,type:f,interfaceDef:p,objectTypeDef:u}}var To,wo;function Co(e,t){return(null==t?void 0:t.endsWith(".graphqls"))||Zi(e)?To.TYPE_SYSTEM:To.EXECUTABLE}function So(e){return e.prevState&&e.kind&&[Qi.NAMED_TYPE,Qi.LIST_TYPE,Qi.TYPE,Qi.NON_NULL_TYPE].includes(e.kind)?So(e.prevState):e}g(po,"getVariableCompletions"),g(ho,"getFragmentDefinitions"),g(mo,"getSuggestionsForVariableDefinition"),g(go,"getSuggestionsForDirective"),g(vo,"getTokenAtPosition"),g(yo,"runOnlineParser"),g(bo,"canUseDirective"),g(Eo,"getTypeInfo"),(wo=To||(To={})).TYPE_SYSTEM="TYPE_SYSTEM",wo.EXECUTABLE="EXECUTABLE",g(Co,"getDocumentMode"),g(So,"unwrapType");var xo={exports:{}};function ko(e,t){if(null!=e)return e;var n=new Error(void 0!==t?t:"Got unexpected "+e);throw n.framesToPop=1,n}g(ko,"nullthrows"),xo.exports=ko,xo.exports.default=ko,Object.defineProperty(xo.exports,"__esModule",{value:!0});var No=Z(xo.exports);const _o=g(((e,t)=>{if(!t)return[];const n=new Map,i=new Set;(0,r.visit)(e,{FragmentDefinition(e){n.set(e.name.value,!0)},FragmentSpread(e){i.has(e.name.value)||i.add(e.name.value)}});const o=new Set;for(const e of i)!n.has(e)&&t.has(e)&&o.add(No(t.get(e)));const a=[];for(const e of o)(0,r.visit)(e,{FragmentSpread(e){!i.has(e.name.value)&&t.get(e.name.value)&&(o.add(No(t.get(e.name.value))),i.add(e.name.value))}}),n.has(e.name.value)||a.push(e);return a}),"getFragmentDependenciesForAST");function Oo(e,t){const n=Object.create(null);for(const i of t.definitions)if("OperationDefinition"===i.kind){const{variableDefinitions:t}=i;if(t)for(const{variable:i,type:o}of t){const t=(0,r.typeFromAST)(e,o);t?n[i.name.value]=t:o.kind===r.Kind.NAMED_TYPE&&"Float"===o.name.value&&(n[i.name.value]=r.GraphQLFloat)}}return n}function Io(e,t){const n=t?Oo(t,e):void 0,i=[];return(0,r.visit)(e,{OperationDefinition(e){i.push(e)}}),{variableToType:n,operations:i}}function Do(e,t){if(t)try{const n=(0,r.parse)(t);return Object.assign(Object.assign({},Io(n,e)),{documentAST:n})}catch(e){return}}g(Oo,"collectVariables"),g(Io,"getOperationASTFacts"),g(Do,"getOperationFacts"),globalThis&&globalThis.__awaiter;var Lo=g((function(e){return"object"==typeof e?null===e:"function"!=typeof e}),"isPrimitive"),Ao=g((function(e){return null!=e&&"object"==typeof e&&!1===Array.isArray(e)}),"isObject");function Mo(e){return!0===Ao(e)&&"[object Object]"===Object.prototype.toString.call(e)}g(Mo,"isObjectObject");var Ro=g((function(e){var t,n;return!1!==Mo(e)&&"function"==typeof(t=e.constructor)&&!1!==Mo(n=t.prototype)&&!1!==n.hasOwnProperty("isPrototypeOf")}),"isPlainObject");const{deleteProperty:Fo}=Reflect,Po=Lo,jo=Ro,Vo=g((e=>"object"==typeof e&&null!==e||"function"==typeof e),"isObject$1"),Uo=g((e=>"__proto__"===e||"constructor"===e||"prototype"===e),"isUnsafeKey"),Bo=g((e=>{if(!Po(e))throw new TypeError("Object keys must be strings or symbols");if(Uo(e))throw new Error(`Cannot set unsafe key: "${e}"`)}),"validateKey"),$o=g((e=>Array.isArray(e)?e.flat().map(String).join(","):e),"toStringKey"),qo=g(((e,t)=>{if("string"!=typeof e||!t)return e;let n=e+";";return void 0!==t.arrays&&(n+=`arrays=${t.arrays};`),void 0!==t.separator&&(n+=`separator=${t.separator};`),void 0!==t.split&&(n+=`split=${t.split};`),void 0!==t.merge&&(n+=`merge=${t.merge};`),void 0!==t.preservePaths&&(n+=`preservePaths=${t.preservePaths};`),n}),"createMemoKey"),Ho=g(((e,t,n)=>{const r=$o(t?qo(e,t):e);Bo(r);const i=Ko.cache.get(r)||n();return Ko.cache.set(r,i),i}),"memoize"),Go=g((function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=t.separator||".",r="/"!==n&&t.preservePaths;if("string"==typeof e&&!1!==r&&/\//.test(e))return[e];const i=[];let o="";const a=g((e=>{let t;""!==e.trim()&&Number.isInteger(t=Number(e))?i.push(t):i.push(e)}),"push");for(let t=0;tt&&"function"==typeof t.split?t.split(e):"symbol"==typeof e?[e]:Array.isArray(e)?e:Ho(e,t,(()=>Go(e,t)))),"split"),Wo=g(((e,t,n,r)=>{if(Bo(t),void 0===n)Fo(e,t);else if(r&&r.merge){const i="function"===r.merge?r.merge:Object.assign;i&&jo(e[t])&&jo(n)?e[t]=i(e[t],n):e[t]=n}else e[t]=n;return e}),"assignProp"),Ko=g(((e,t,n,r)=>{if(!t||!Vo(e))return e;const i=zo(t,r);let o=e;for(let e=0;e{Ko.cache=new Map};var Qo=Ko;function Xo(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 14 14",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M5.0484 1.40838C6.12624 0.33054 7.87376 0.330541 8.9516 1.40838L12.5916 5.0484C13.6695 6.12624 13.6695 7.87376 12.5916 8.9516L8.9516 12.5916C7.87376 13.6695 6.12624 13.6695 5.0484 12.5916L1.40838 8.9516C0.33054 7.87376 0.330541 6.12624 1.40838 5.0484L5.0484 1.40838Z",stroke:"currentColor",strokeWidth:1.2}),t.createElement("rect",{x:6,y:6,width:2,height:2,rx:1,fill:"currentColor"}))}function Yo(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 14 9",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M1 1L7 7L13 1",stroke:"currentColor",strokeWidth:1.5}))}function Jo(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 7 10",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M6 1.04819L2 5.04819L6 9.04819",stroke:"currentColor",strokeWidth:1.75}))}function Zo(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 14 9",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M13 8L7 2L1 8",stroke:"currentColor",strokeWidth:1.5}))}function ea(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 14 14",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M1 1L12.9998 12.9997",stroke:"currentColor",strokeWidth:1.5}),t.createElement("path",{d:"M13 1L1.00079 13.0003",stroke:"currentColor",strokeWidth:1.5}))}function ta(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"-2 -2 22 22",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M11.25 14.2105V15.235C11.25 16.3479 10.3479 17.25 9.23501 17.25H2.76499C1.65214 17.25 0.75 16.3479 0.75 15.235L0.75 8.76499C0.75 7.65214 1.65214 6.75 2.76499 6.75L3.78947 6.75",stroke:"currentColor",strokeWidth:1.5}),t.createElement("rect",{x:6.75,y:.75,width:10.5,height:10.5,rx:2.2069,stroke:"currentColor",strokeWidth:1.5}))}function na(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 14 14",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M5.0484 1.40838C6.12624 0.33054 7.87376 0.330541 8.9516 1.40838L12.5916 5.0484C13.6695 6.12624 13.6695 7.87376 12.5916 8.9516L8.9516 12.5916C7.87376 13.6695 6.12624 13.6695 5.0484 12.5916L1.40838 8.9516C0.33054 7.87376 0.330541 6.12624 1.40838 5.0484L5.0484 1.40838Z",stroke:"currentColor",strokeWidth:1.2}),t.createElement("path",{d:"M5 9L9 5",stroke:"currentColor",strokeWidth:1.2}),t.createElement("path",{d:"M5 5L9 9",stroke:"currentColor",strokeWidth:1.2}))}function ra(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 12 12",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M4 8L8 4",stroke:"currentColor",strokeWidth:1.2}),t.createElement("path",{d:"M4 4L8 8",stroke:"currentColor",strokeWidth:1.2}),t.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M8.5 1.2H9C9.99411 1.2 10.8 2.00589 10.8 3V9C10.8 9.99411 9.99411 10.8 9 10.8H8.5V12H9C10.6569 12 12 10.6569 12 9V3C12 1.34315 10.6569 0 9 0H8.5V1.2ZM3.5 1.2V0H3C1.34315 0 0 1.34315 0 3V9C0 10.6569 1.34315 12 3 12H3.5V10.8H3C2.00589 10.8 1.2 9.99411 1.2 9V3C1.2 2.00589 2.00589 1.2 3 1.2H3.5Z",fill:"currentColor"}))}function ia(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 12 12",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("rect",{x:.6,y:.6,width:10.8,height:10.8,rx:3.4,stroke:"currentColor",strokeWidth:1.2}),t.createElement("path",{d:"M4 8L8 4",stroke:"currentColor",strokeWidth:1.2}),t.createElement("path",{d:"M4 4L8 8",stroke:"currentColor",strokeWidth:1.2}))}function oa(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0.5 12 12",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("rect",{x:7,y:5.5,width:2,height:2,rx:1,transform:"rotate(90 7 5.5)",fill:"currentColor"}),t.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M10.8 9L10.8 9.5C10.8 10.4941 9.99411 11.3 9 11.3L3 11.3C2.00589 11.3 1.2 10.4941 1.2 9.5L1.2 9L-3.71547e-07 9L-3.93402e-07 9.5C-4.65826e-07 11.1569 1.34314 12.5 3 12.5L9 12.5C10.6569 12.5 12 11.1569 12 9.5L12 9L10.8 9ZM10.8 4L12 4L12 3.5C12 1.84315 10.6569 0.5 9 0.5L3 0.5C1.34315 0.5 -5.87117e-08 1.84315 -1.31135e-07 3.5L-1.5299e-07 4L1.2 4L1.2 3.5C1.2 2.50589 2.00589 1.7 3 1.7L9 1.7C9.99411 1.7 10.8 2.50589 10.8 3.5L10.8 4Z",fill:"currentColor"}))}function aa(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 20 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M0.75 3C0.75 1.75736 1.75736 0.75 3 0.75H17.25C17.8023 0.75 18.25 1.19772 18.25 1.75V5.25",stroke:"currentColor",strokeWidth:1.5}),t.createElement("path",{d:"M0.75 3C0.75 4.24264 1.75736 5.25 3 5.25H18.25C18.8023 5.25 19.25 5.69771 19.25 6.25V22.25C19.25 22.8023 18.8023 23.25 18.25 23.25H3C1.75736 23.25 0.75 22.2426 0.75 21V3Z",stroke:"currentColor",strokeWidth:1.5}),t.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M3 5.25C1.75736 5.25 0.75 4.24264 0.75 3V21C0.75 22.2426 1.75736 23.25 3 23.25H18.25C18.8023 23.25 19.25 22.8023 19.25 22.25V6.25C19.25 5.69771 18.8023 5.25 18.25 5.25H3ZM13 11L6 11V12.5L13 12.5V11Z",fill:"currentColor"}))}function sa(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 20 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M0.75 3C0.75 4.24264 1.75736 5.25 3 5.25H17.25M0.75 3C0.75 1.75736 1.75736 0.75 3 0.75H16.25C16.8023 0.75 17.25 1.19772 17.25 1.75V5.25M0.75 3V21C0.75 22.2426 1.75736 23.25 3 23.25H18.25C18.8023 23.25 19.25 22.8023 19.25 22.25V6.25C19.25 5.69771 18.8023 5.25 18.25 5.25H17.25",stroke:"currentColor",strokeWidth:1.5}),t.createElement("line",{x1:13,y1:11.75,x2:6,y2:11.75,stroke:"currentColor",strokeWidth:1.5}))}function la(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 12 12",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("rect",{x:5,y:5,width:2,height:2,rx:1,fill:"currentColor"}),t.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M8.5 1.2H9C9.99411 1.2 10.8 2.00589 10.8 3V9C10.8 9.99411 9.99411 10.8 9 10.8H8.5V12H9C10.6569 12 12 10.6569 12 9V3C12 1.34315 10.6569 0 9 0H8.5V1.2ZM3.5 1.2V0H3C1.34315 0 0 1.34315 0 3V9C0 10.6569 1.34315 12 3 12H3.5V10.8H3C2.00589 10.8 1.2 9.99411 1.2 9V3C1.2 2.00589 2.00589 1.2 3 1.2H3.5Z",fill:"currentColor"}))}function ua(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 12 13",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("rect",{x:.6,y:1.1,width:10.8,height:10.8,rx:2.4,stroke:"currentColor",strokeWidth:1.2}),t.createElement("rect",{x:5,y:5.5,width:2,height:2,rx:1,fill:"currentColor"}))}function ca(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 24 20",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M1.59375 9.52344L4.87259 12.9944L8.07872 9.41249",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"square"}),t.createElement("path",{d:"M13.75 5.25V10.75H18.75",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"square"}),t.createElement("path",{d:"M4.95427 11.9332C4.55457 10.0629 4.74441 8.11477 5.49765 6.35686C6.25089 4.59894 7.5305 3.11772 9.16034 2.11709C10.7902 1.11647 12.6901 0.645626 14.5986 0.769388C16.5071 0.893151 18.3303 1.60543 19.8172 2.80818C21.3042 4.01093 22.3818 5.64501 22.9017 7.48548C23.4216 9.32595 23.3582 11.2823 22.7203 13.0853C22.0824 14.8883 20.9013 16.4492 19.3396 17.5532C17.778 18.6572 15.9125 19.25 14 19.25",stroke:"currentColor",strokeWidth:1.5}))}function da(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 12 12",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("circle",{cx:6,cy:6,r:5.4,stroke:"currentColor",strokeWidth:1.2,strokeDasharray:"4.241025 4.241025",transform:"rotate(22.5)","transform-origin":"center"}),t.createElement("circle",{cx:6,cy:6,r:1,fill:"currentColor"}))}function fa(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 19 18",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M1.5 14.5653C1.5 15.211 1.75652 15.8303 2.21314 16.2869C2.66975 16.7435 3.28905 17 3.9348 17C4.58054 17 5.19984 16.7435 5.65646 16.2869C6.11307 15.8303 6.36959 15.211 6.36959 14.5653V12.1305H3.9348C3.28905 12.1305 2.66975 12.387 2.21314 12.8437C1.75652 13.3003 1.5 13.9195 1.5 14.5653Z",stroke:"currentColor",strokeWidth:1.125,strokeLinecap:"round",strokeLinejoin:"round"}),t.createElement("path",{d:"M3.9348 1.00063C3.28905 1.00063 2.66975 1.25715 2.21314 1.71375C1.75652 2.17035 1.5 2.78964 1.5 3.43537C1.5 4.0811 1.75652 4.70038 2.21314 5.15698C2.66975 5.61358 3.28905 5.8701 3.9348 5.8701H6.36959V3.43537C6.36959 2.78964 6.11307 2.17035 5.65646 1.71375C5.19984 1.25715 4.58054 1.00063 3.9348 1.00063Z",stroke:"currentColor",strokeWidth:1.125,strokeLinecap:"round",strokeLinejoin:"round"}),t.createElement("path",{d:"M15.0652 12.1305H12.6304V14.5653C12.6304 15.0468 12.7732 15.5175 13.0407 15.9179C13.3083 16.3183 13.6885 16.6304 14.1334 16.8147C14.5783 16.9989 15.0679 17.0472 15.5402 16.9532C16.0125 16.8593 16.4464 16.6274 16.7869 16.2869C17.1274 15.9464 17.3593 15.5126 17.4532 15.0403C17.5472 14.568 17.4989 14.0784 17.3147 13.6335C17.1304 13.1886 16.8183 12.8084 16.4179 12.5409C16.0175 12.2733 15.5468 12.1305 15.0652 12.1305Z",stroke:"currentColor",strokeWidth:1.125,strokeLinecap:"round",strokeLinejoin:"round"}),t.createElement("path",{d:"M12.6318 5.86775H6.36955V12.1285H12.6318V5.86775Z",stroke:"currentColor",strokeWidth:1.125,strokeLinecap:"round",strokeLinejoin:"round"}),t.createElement("path",{d:"M17.5 3.43473C17.5 2.789 17.2435 2.16972 16.7869 1.71312C16.3303 1.25652 15.711 1 15.0652 1C14.4195 1 13.8002 1.25652 13.3435 1.71312C12.8869 2.16972 12.6304 2.789 12.6304 3.43473V5.86946H15.0652C15.711 5.86946 16.3303 5.61295 16.7869 5.15635C17.2435 4.69975 17.5 4.08046 17.5 3.43473Z",stroke:"currentColor",strokeWidth:1.125,strokeLinecap:"round",strokeLinejoin:"round"}))}function pa(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 13 13",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("circle",{cx:5,cy:5,r:4.35,stroke:"currentColor",strokeWidth:1.3}),t.createElement("line",{x1:8.45962,y1:8.54038,x2:11.7525,y2:11.8333,stroke:"currentColor",strokeWidth:1.3}))}function ha(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"-2 -2 22 22",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M17.2492 6V2.9569C17.2492 1.73806 16.2611 0.75 15.0423 0.75L2.9569 0.75C1.73806 0.75 0.75 1.73806 0.75 2.9569L0.75 6",stroke:"currentColor",strokeWidth:1.5}),t.createElement("path",{d:"M0.749873 12V15.0431C0.749873 16.2619 1.73794 17.25 2.95677 17.25H15.0421C16.261 17.25 17.249 16.2619 17.249 15.0431V12",stroke:"currentColor",strokeWidth:1.5}),t.createElement("path",{d:"M6 4.5L9 7.5L12 4.5",stroke:"currentColor",strokeWidth:1.5}),t.createElement("path",{d:"M12 13.5L9 10.5L6 13.5",stroke:"currentColor",strokeWidth:1.5}))}function ma(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 14 14",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M0.75 13.25L0.0554307 12.967C-0.0593528 13.2488 0.00743073 13.5719 0.224488 13.7851C0.441545 13.9983 0.765869 14.0592 1.04549 13.9393L0.75 13.25ZM12.8214 1.83253L12.2911 2.36286L12.2911 2.36286L12.8214 1.83253ZM12.8214 3.90194L13.3517 4.43227L12.8214 3.90194ZM10.0981 1.17859L9.56773 0.648259L10.0981 1.17859ZM12.1675 1.17859L12.6978 0.648258L12.6978 0.648257L12.1675 1.17859ZM2.58049 8.75697L3.27506 9.03994L2.58049 8.75697ZM2.70066 8.57599L3.23099 9.10632L2.70066 8.57599ZM5.2479 11.4195L4.95355 10.7297L5.2479 11.4195ZM5.42036 11.303L4.89003 10.7727L5.42036 11.303ZM4.95355 10.7297C4.08882 11.0987 3.41842 11.362 2.73535 11.6308C2.05146 11.9 1.35588 12.1743 0.454511 12.5607L1.04549 13.9393C1.92476 13.5624 2.60256 13.2951 3.28469 13.0266C3.96762 12.7578 4.65585 12.4876 5.54225 12.1093L4.95355 10.7297ZM1.44457 13.533L3.27506 9.03994L1.88592 8.474L0.0554307 12.967L1.44457 13.533ZM3.23099 9.10632L10.6284 1.70892L9.56773 0.648259L2.17033 8.04566L3.23099 9.10632ZM11.6371 1.70892L12.2911 2.36286L13.3517 1.3022L12.6978 0.648258L11.6371 1.70892ZM12.2911 3.37161L4.89003 10.7727L5.95069 11.8333L13.3517 4.43227L12.2911 3.37161ZM12.2911 2.36286C12.5696 2.64142 12.5696 3.09305 12.2911 3.37161L13.3517 4.43227C14.2161 3.56792 14.2161 2.16654 13.3517 1.3022L12.2911 2.36286ZM10.6284 1.70892C10.9069 1.43036 11.3586 1.43036 11.6371 1.70892L12.6978 0.648257C11.8335 -0.216088 10.4321 -0.216084 9.56773 0.648259L10.6284 1.70892ZM3.27506 9.03994C3.26494 9.06479 3.24996 9.08735 3.23099 9.10632L2.17033 8.04566C2.04793 8.16806 1.95123 8.31369 1.88592 8.474L3.27506 9.03994ZM5.54225 12.1093C5.69431 12.0444 5.83339 11.9506 5.95069 11.8333L4.89003 10.7727C4.90863 10.7541 4.92988 10.7398 4.95355 10.7297L5.54225 12.1093Z",fill:"currentColor"}),t.createElement("path",{d:"M11.5 4.5L9.5 2.5",stroke:"currentColor",strokeWidth:1.4026,strokeLinecap:"round",strokeLinejoin:"round"}),t.createElement("path",{d:"M5.5 10.5L3.5 8.5",stroke:"currentColor",strokeWidth:1.4026,strokeLinecap:"round",strokeLinejoin:"round"}))}function ga(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 16 18",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M1.32226e-07 1.6609C7.22332e-08 0.907329 0.801887 0.424528 1.46789 0.777117L15.3306 8.11621C16.0401 8.49182 16.0401 9.50818 15.3306 9.88379L1.46789 17.2229C0.801886 17.5755 1.36076e-06 17.0927 1.30077e-06 16.3391L1.32226e-07 1.6609Z",fill:"currentColor"}))}function va(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 10 16",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M4.25 9.25V13.5H5.75V9.25L10 9.25V7.75L5.75 7.75V3.5H4.25V7.75L0 7.75V9.25L4.25 9.25Z",fill:"currentColor"}))}function ya(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({width:25,height:25,viewBox:"0 0 25 25",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M10.2852 24.0745L13.7139 18.0742",stroke:"currentColor",strokeWidth:1.5625}),t.createElement("path",{d:"M14.5742 24.0749L17.1457 19.7891",stroke:"currentColor",strokeWidth:1.5625}),t.createElement("path",{d:"M19.4868 24.0735L20.7229 21.7523C21.3259 20.6143 21.5457 19.3122 21.3496 18.0394C21.1535 16.7666 20.5519 15.591 19.6342 14.6874L23.7984 6.87853C24.0123 6.47728 24.0581 6.00748 23.9256 5.57249C23.7932 5.1375 23.4933 4.77294 23.0921 4.55901C22.6908 4.34509 22.221 4.29932 21.7861 4.43178C21.3511 4.56424 20.9865 4.86408 20.7726 5.26533L16.6084 13.0742C15.3474 12.8142 14.0362 12.9683 12.8699 13.5135C11.7035 14.0586 10.7443 14.9658 10.135 16.1L6 24.0735",stroke:"currentColor",strokeWidth:1.5625}),t.createElement("path",{d:"M4 15L5 13L7 12L5 11L4 9L3 11L1 12L3 13L4 15Z",stroke:"currentColor",strokeWidth:1.5625,strokeLinejoin:"round"}),t.createElement("path",{d:"M11.5 8L12.6662 5.6662L15 4.5L12.6662 3.3338L11.5 1L10.3338 3.3338L8 4.5L10.3338 5.6662L11.5 8Z",stroke:"currentColor",strokeWidth:1.5625,strokeLinejoin:"round"}))}function ba(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M4.75 9.25H1.25V12.75",stroke:"currentColor",strokeWidth:1,strokeLinecap:"square"}),t.createElement("path",{d:"M11.25 6.75H14.75V3.25",stroke:"currentColor",strokeWidth:1,strokeLinecap:"square"}),t.createElement("path",{d:"M14.1036 6.65539C13.8 5.27698 13.0387 4.04193 11.9437 3.15131C10.8487 2.26069 9.48447 1.76694 8.0731 1.75043C6.66173 1.73392 5.28633 2.19563 4.17079 3.0604C3.05526 3.92516 2.26529 5.14206 1.92947 6.513",stroke:"currentColor",strokeWidth:1}),t.createElement("path",{d:"M1.89635 9.34461C2.20001 10.723 2.96131 11.9581 4.05631 12.8487C5.15131 13.7393 6.51553 14.2331 7.9269 14.2496C9.33827 14.2661 10.7137 13.8044 11.8292 12.9396C12.9447 12.0748 13.7347 10.8579 14.0705 9.487",stroke:"currentColor",strokeWidth:1}))}function Ea(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 13 13",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("rect",{x:.6,y:.6,width:11.8,height:11.8,rx:5.9,stroke:"currentColor",strokeWidth:1.2}),t.createElement("path",{d:"M4.25 7.5C4.25 6 5.75 5 6.5 6.5C7.25 8 8.75 7 8.75 5.5",stroke:"currentColor",strokeWidth:1.2}))}function Ta(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 21 20",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M9.29186 1.92702C9.06924 1.82745 8.87014 1.68202 8.70757 1.50024L7.86631 0.574931C7.62496 0.309957 7.30773 0.12592 6.95791 0.0479385C6.60809 -0.0300431 6.24274 0.00182978 5.91171 0.139208C5.58068 0.276585 5.3001 0.512774 5.10828 0.815537C4.91645 1.1183 4.82272 1.47288 4.83989 1.83089L4.90388 3.08019C4.91612 3.32348 4.87721 3.56662 4.78968 3.79394C4.70215 4.02126 4.56794 4.2277 4.39571 4.39994C4.22347 4.57219 4.01704 4.7064 3.78974 4.79394C3.56243 4.88147 3.3193 4.92038 3.07603 4.90814L1.8308 4.84414C1.47162 4.82563 1.11553 4.91881 0.811445 5.11086C0.507359 5.30292 0.270203 5.58443 0.132561 5.91671C-0.00508149 6.249 -0.0364554 6.61576 0.0427496 6.9666C0.121955 7.31744 0.307852 7.63514 0.5749 7.87606L1.50016 8.71204C1.68193 8.87461 1.82735 9.07373 1.92692 9.29636C2.02648 9.51898 2.07794 9.76012 2.07794 10.004C2.07794 10.2479 2.02648 10.489 1.92692 10.7116C1.82735 10.9343 1.68193 11.1334 1.50016 11.296L0.5749 12.1319C0.309856 12.3729 0.125575 12.6898 0.0471809 13.0393C-0.0312128 13.3888 9.64098e-05 13.754 0.13684 14.0851C0.273583 14.4162 0.509106 14.6971 0.811296 14.8894C1.11349 15.0817 1.46764 15.1762 1.82546 15.1599L3.0707 15.0959C3.31397 15.0836 3.5571 15.1225 3.7844 15.2101C4.01171 15.2976 4.21814 15.4318 4.39037 15.6041C4.56261 15.7763 4.69682 15.9827 4.78435 16.2101C4.87188 16.4374 4.91078 16.6805 4.89855 16.9238L4.83455 18.1691C4.81605 18.5283 4.90921 18.8844 5.10126 19.1885C5.2933 19.4926 5.5748 19.7298 5.90707 19.8674C6.23934 20.0051 6.60608 20.0365 6.9569 19.9572C7.30772 19.878 7.6254 19.6921 7.86631 19.4251L8.7129 18.4998C8.87547 18.318 9.07458 18.1725 9.29719 18.073C9.51981 17.9734 9.76093 17.9219 10.0048 17.9219C10.2487 17.9219 10.4898 17.9734 10.7124 18.073C10.935 18.1725 11.1341 18.318 11.2967 18.4998L12.1326 19.4251C12.3735 19.6921 12.6912 19.878 13.042 19.9572C13.3929 20.0365 13.7596 20.0051 14.0919 19.8674C14.4241 19.7298 14.7056 19.4926 14.8977 19.1885C15.0897 18.8844 15.1829 18.5283 15.1644 18.1691L15.1004 16.9238C15.0882 16.6805 15.1271 16.4374 15.2146 16.2101C15.3021 15.9827 15.4363 15.7763 15.6086 15.6041C15.7808 15.4318 15.9872 15.2976 16.2145 15.2101C16.4418 15.1225 16.685 15.0836 16.9282 15.0959L18.1735 15.1599C18.5326 15.1784 18.8887 15.0852 19.1928 14.8931C19.4969 14.7011 19.7341 14.4196 19.8717 14.0873C20.0093 13.755 20.0407 13.3882 19.9615 13.0374C19.8823 12.6866 19.6964 12.3689 19.4294 12.1279L18.5041 11.292C18.3223 11.1294 18.1769 10.9303 18.0774 10.7076C17.9778 10.485 17.9263 10.2439 17.9263 10C17.9263 9.75612 17.9778 9.51499 18.0774 9.29236C18.1769 9.06973 18.3223 8.87062 18.5041 8.70804L19.4294 7.87206C19.6964 7.63114 19.8823 7.31344 19.9615 6.9626C20.0407 6.61176 20.0093 6.245 19.8717 5.91271C19.7341 5.58043 19.4969 5.29892 19.1928 5.10686C18.8887 4.91481 18.5326 4.82163 18.1735 4.84014L16.9282 4.90414C16.685 4.91638 16.4418 4.87747 16.2145 4.78994C15.9872 4.7024 15.7808 4.56818 15.6086 4.39594C15.4363 4.2237 15.3021 4.01726 15.2146 3.78994C15.1271 3.56262 15.0882 3.31948 15.1004 3.07619L15.1644 1.83089C15.1829 1.4717 15.0897 1.11559 14.8977 0.811487C14.7056 0.507385 14.4241 0.270217 14.0919 0.132568C13.7596 -0.00508182 13.3929 -0.0364573 13.042 0.0427519C12.6912 0.121961 12.3735 0.307869 12.1326 0.574931L11.2914 1.50024C11.1288 1.68202 10.9297 1.82745 10.7071 1.92702C10.4845 2.02659 10.2433 2.07805 9.99947 2.07805C9.7556 2.07805 9.51448 2.02659 9.29186 1.92702ZM14.3745 10C14.3745 12.4162 12.4159 14.375 9.99977 14.375C7.58365 14.375 5.625 12.4162 5.625 10C5.625 7.58375 7.58365 5.625 9.99977 5.625C12.4159 5.625 14.3745 7.58375 14.3745 10Z",fill:"currentColor"}))}function wa(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 14 14",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M6.5782 1.07092C6.71096 0.643026 7.28904 0.643027 7.4218 1.07092L8.59318 4.84622C8.65255 5.03758 8.82284 5.16714 9.01498 5.16714L12.8056 5.16714C13.2353 5.16714 13.4139 5.74287 13.0663 6.00732L9.99962 8.34058C9.84418 8.45885 9.77913 8.66848 9.83851 8.85984L11.0099 12.6351C11.1426 13.063 10.675 13.4189 10.3274 13.1544L7.26069 10.8211C7.10524 10.7029 6.89476 10.7029 6.73931 10.8211L3.6726 13.1544C3.32502 13.4189 2.85735 13.063 2.99012 12.6351L4.16149 8.85984C4.22087 8.66848 4.15582 8.45885 4.00038 8.34058L0.933671 6.00732C0.586087 5.74287 0.764722 5.16714 1.19436 5.16714L4.98502 5.16714C5.17716 5.16714 5.34745 5.03758 5.40682 4.84622L6.5782 1.07092Z",fill:"currentColor",stroke:"currentColor"}))}function Ca(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 14 14",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("path",{d:"M6.5782 1.07092C6.71096 0.643026 7.28904 0.643027 7.4218 1.07092L8.59318 4.84622C8.65255 5.03758 8.82284 5.16714 9.01498 5.16714L12.8056 5.16714C13.2353 5.16714 13.4139 5.74287 13.0663 6.00732L9.99962 8.34058C9.84418 8.45885 9.77913 8.66848 9.83851 8.85984L11.0099 12.6351C11.1426 13.063 10.675 13.4189 10.3274 13.1544L7.26069 10.8211C7.10524 10.7029 6.89476 10.7029 6.73931 10.8211L3.6726 13.1544C3.32502 13.4189 2.85735 13.063 2.99012 12.6351L4.16149 8.85984C4.22087 8.66848 4.15582 8.45885 4.00038 8.34058L0.933671 6.00732C0.586087 5.74287 0.764722 5.16714 1.19436 5.16714L4.98502 5.16714C5.17716 5.16714 5.34745 5.03758 5.40682 4.84622L6.5782 1.07092Z",stroke:"currentColor",strokeWidth:1.5}))}function Sa(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("rect",{width:16,height:16,rx:2,fill:"currentColor"}))}function xa(e){var n=e,{title:r,titleId:i}=n,o=v(n,["title","titleId"]);return t.createElement("svg",Object.assign({height:"1em",viewBox:"0 0 13 13",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-labelledby":i},o),r?t.createElement("title",{id:i},r):null,t.createElement("rect",{x:.6,y:.6,width:11.8,height:11.8,rx:5.9,stroke:"currentColor",strokeWidth:1.2}),t.createElement("rect",{x:5.5,y:5.5,width:2,height:2,rx:1,fill:"currentColor"}))}g(Xo,"SvgArgument"),g(Yo,"SvgChevronDown"),g(Jo,"SvgChevronLeft"),g(Zo,"SvgChevronUp"),g(ea,"SvgClose"),g(ta,"SvgCopy"),g(na,"SvgDeprecatedArgument"),g(ra,"SvgDeprecatedEnumValue"),g(ia,"SvgDeprecatedField"),g(oa,"SvgDirective"),g(aa,"SvgDocsFilled"),g(sa,"SvgDocs"),g(la,"SvgEnumValue"),g(ua,"SvgField"),g(ca,"SvgHistory"),g(da,"SvgImplements"),g(fa,"SvgKeyboardShortcut"),g(pa,"SvgMagnifyingGlass"),g(ha,"SvgMerge"),g(ma,"SvgPen"),g(ga,"SvgPlay"),g(va,"SvgPlus"),g(ya,"SvgPrettify"),g(ba,"SvgReload"),g(Ea,"SvgRootType"),g(Ta,"SvgSettings"),g(wa,"SvgStarFilled"),g(Ca,"SvgStar"),g(Sa,"SvgStop"),g(xa,"SvgType");var ka=Object.defineProperty,Na=g(((e,t)=>ka(e,"name",{value:t,configurable:!0})),"__name$E");const _a=is(Xo,"argument icon");e.ac=_a;const Oa=is(Yo,"chevron down icon");e.ad=Oa;const Ia=is(Jo,"chevron left icon");e.ae=Ia;const Da=is(Zo,"chevron up icon");e.af=Da;const La=is(ea,"close icon");e.ag=La;const Aa=is(ta,"copy icon");e.ah=Aa;const Ma=is(na,"deprecated argument icon");e.ai=Ma;const Ra=is(ra,"deprecated enum value icon");e.aj=Ra;const Fa=is(ia,"deprecated field icon");e.ak=Fa;const Pa=is(oa,"directive icon");e.al=Pa;const ja=is(aa,"filled docs icon");e.am=ja;const Va=is(sa,"docs icon");e.an=Va;const Ua=is(la,"enum value icon");e.ao=Ua;const Ba=is(ua,"field icon");e.ap=Ba;const $a=is(ca,"history icon");e.aq=$a;const qa=is(da,"implements icon");e.ar=qa;const Ha=is(fa,"keyboard shortcut icon");e.as=Ha;const Ga=is(pa,"magnifying glass icon");e.at=Ga;const za=is(ha,"merge icon");e.au=za;const Wa=is(ma,"pen icon");e.av=Wa;const Ka=is(ga,"play icon");e.aw=Ka;const Qa=is(va,"plus icon");e.ax=Qa;const Xa=is(ya,"prettify icon");e.ay=Xa;const Ya=is(ba,"reload icon");e.az=Ya;const Ja=is(Ea,"root type icon");e.aA=Ja;const Za=is(Ta,"settings icon");e.aB=Za;const es=is(wa,"filled star icon");e.aC=es;const ts=is(Ca,"star icon");e.aD=ts;const ns=is(Sa,"stop icon");e.aE=ns;const rs=is(xa,"type icon");function is(e,t){const n=Na(g((function(n){return de(e,m(h({},n),{title:t}))}),"IconComponent"),"IconComponent");return Object.defineProperty(n,"name",{value:e.name}),n}e.aF=rs,g(is,"generateIcon"),Na(is,"generateIcon");const os=(0,t.forwardRef)(((e,t)=>de("button",m(h({},e),{ref:t,className:b("graphiql-un-styled",e.className)}))));e.aG=os,os.displayName="UnStyledButton";const as=(0,t.forwardRef)(((e,t)=>de("button",m(h({},e),{ref:t,className:b("graphiql-button",{success:"graphiql-button-success",error:"graphiql-button-error"}[e.state],e.className)}))));e.aH=as,as.displayName="Button";const ss=(0,t.forwardRef)(((e,t)=>de("div",m(h({},e),{ref:t,className:b("graphiql-button-group",e.className)}))));function ls(){return!("undefined"==typeof window||!window.document||!window.document.createElement)}e.aI=ss,ss.displayName="ButtonGroup",g(ls,"canUseDOM");var us=ls()?t.useLayoutEffect:t.useEffect;function cs(){var e=(0,t.useState)(Object.create(null))[1];return(0,t.useCallback)((function(){e(Object.create(null))}),[])}function ds(e,t){if(null==e)return{};var n,r,i={},o=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}g(cs,"useForceUpdate"),g(ds,"_objectWithoutPropertiesLoose$b");var fs=["unstable_skipInitialRender"],ps=g((function(e){var n=e.children,r=e.type,o=void 0===r?"reach-portal":r,a=e.containerRef,s=(0,t.useRef)(null),l=(0,t.useRef)(null),u=cs();return us((function(){if(s.current){var e=s.current.ownerDocument,t=(null==a?void 0:a.current)||e.body;return l.current=null==e?void 0:e.createElement(o),t.appendChild(l.current),u(),function(){l.current&&t&&t.removeChild(l.current)}}}),[o,u,a]),l.current?(0,i.createPortal)(n,l.current):(0,t.createElement)("span",{ref:s})}),"PortalImpl"),hs=g((function(e){var n=e.unstable_skipInitialRender,r=ds(e,fs),i=(0,t.useState)(!1),o=i[0],a=i[1];return(0,t.useEffect)((function(){n&&a(!0)}),[n]),n&&!o?null:(0,t.createElement)(ps,r)}),"Portal");function ms(e){return ls()?e?e.ownerDocument:document:null}function gs(e){return"boolean"==typeof e}function vs(e){return!(!e||"[object Function]"!={}.toString.call(e))}function ys(e){return"string"==typeof e}function bs(){}function Es(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}return(n=e[Symbol.iterator]()).next.bind(n)}function Cs(e,t){if(null!=e)if(vs(e))e(t);else try{e.current=t}catch(n){throw new Error('Cannot assign value "'+t+'" to ref "'+e+'"')}}function Ss(){for(var e=arguments.length,n=new Array(e),r=0;r=0||(i[n]=e[n]);return i}function Ns(){return Ns=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0})).sort(Tl)}),"orderByTabIndex"),Cl=["button:enabled","select:enabled","textarea:enabled","input:enabled","a[href]","area[href]","summary","iframe","object","embed","audio[controls]","video[controls]","[tabindex]","[contenteditable]","[autofocus]"].join(","),Sl="".concat(Cl,", [data-focus-guard]"),xl=g((function(e,t){var n;return rl((null===(n=e.shadowRoot)||void 0===n?void 0:n.children)||e.children).reduce((function(e,n){return e.concat(n.matches(t?Sl:Cl)?[n]:[],xl(n))}),[])}),"getFocusablesWithShadowDom"),kl=g((function(e,t){return e.reduce((function(e,n){return e.concat(xl(n,t),n.parentNode?rl(n.parentNode.querySelectorAll(Cl)).filter((function(e){return e===n})):[])}),[])}),"getFocusables"),Nl=g((function(e){var t=e.querySelectorAll("[".concat("data-autofocus-inside","]"));return rl(t).map((function(e){return kl([e])})).reduce((function(e,t){return e.concat(t)}),[])}),"getParentAutofocusables"),_l=g((function(e,t){return rl(e).filter((function(e){return ul(t,e)})).filter((function(e){return gl(e)}))}),"filterFocusable"),Ol=g((function(e,t){return void 0===t&&(t=new Map),rl(e).filter((function(e){return dl(t,e)}))}),"filterAutoFocusable"),Il=g((function(e,t,n){return wl(_l(kl(e,n),t),!0,n)}),"getTabbableNodes"),Dl=g((function(e,t){return wl(_l(kl(e),t),!1)}),"getAllTabbableNodes"),Ll=g((function(e,t){return _l(Nl(e),t)}),"parentAutofocusables"),Al=g((function(e,t){return(e.shadowRoot?Al(e.shadowRoot,t):Object.getPrototypeOf(e).contains.call(e,t))||rl(e.children).some((function(e){return Al(e,t)}))}),"contains"),Ml=g((function(e){for(var t=new Set,n=e.length,r=0;r0&&t.add(i),(o&Node.DOCUMENT_POSITION_CONTAINS)>0&&t.add(r)}return e.filter((function(e,n){return!t.has(n)}))}),"filterNested"),Rl=g((function(e){return e.parentNode?Rl(e.parentNode):e}),"getTopParent"),Fl=g((function(e){return il(e).filter(Boolean).reduce((function(e,t){var n=t.getAttribute(Ds);return e.push.apply(e,n?Ml(rl(Rl(t).querySelectorAll("[".concat(Ds,'="').concat(n,'"]:not([').concat(Ls,'="disabled"])')))):[t]),e}),[])}),"getAllAffectedNodes"),Pl=g((function(e){return e.activeElement?e.activeElement.shadowRoot?Pl(e.activeElement.shadowRoot):e.activeElement:void 0}),"getNestedShadowActiveElement"),jl=g((function(){return document.activeElement?document.activeElement.shadowRoot?Pl(document.activeElement.shadowRoot):document.activeElement:void 0}),"getActiveElement"),Vl=g((function(e){return e===document.activeElement}),"focusInFrame"),Ul=g((function(e){return Boolean(rl(e.querySelectorAll("iframe")).some((function(e){return Vl(e)})))}),"focusInsideIframe"),Bl=g((function(e){var t=document&&jl();return!(!t||t.dataset&&t.dataset.focusGuard)&&Fl(e).some((function(e){return Al(e,t)||Ul(e)}))}),"focusInside"),$l=g((function(){var e=document&&jl();return!!e&&rl(document.querySelectorAll("[".concat("data-no-focus-lock","]"))).some((function(t){return Al(t,e)}))}),"focusIsHidden"),ql=g((function(e,t){return t.filter(ml).filter((function(t){return t.name===e.name})).filter((function(e){return e.checked}))[0]||e}),"findSelectedRadio"),Hl=g((function(e,t){return ml(e)&&e.name?ql(e,t):e}),"correctNode"),Gl=g((function(e){var t=new Set;return e.forEach((function(n){return t.add(Hl(n,e))})),e.filter((function(e){return t.has(e)}))}),"correctNodes"),zl=g((function(e){return e[0]&&e.length>1?Hl(e[0],e):e[0]}),"pickFirstFocus"),Wl=g((function(e,t){return e.length>1?e.indexOf(Hl(e[t],e)):t}),"pickFocusable"),Kl="NEW_FOCUS",Ql=g((function(e,t,n,r){var i=e.length,o=e[0],a=e[i-1],s=yl(n);if(!(n&&e.indexOf(n)>=0)){var l=void 0!==n?t.indexOf(n):-1,u=r?t.indexOf(r):l,c=r?e.indexOf(r):-1,d=l-u,f=t.indexOf(o),p=t.indexOf(a),h=Gl(t),m=(void 0!==n?h.indexOf(n):-1)-(r?h.indexOf(r):l),g=Wl(e,0),v=Wl(e,i-1);return-1===l||-1===c?Kl:!d&&c>=0?c:l<=f&&s&&Math.abs(d)>1?v:l>=p&&s&&Math.abs(d)>1?g:d&&Math.abs(m)>1?c:l<=f?v:l>p?g:d?Math.abs(d)>1?c:(i+c+d)%i:void 0}}),"newFocus"),Xl=g((function(e,t){return void 0===t&&(t=[]),t.push(e),e.parentNode&&Xl(e.parentNode.host||e.parentNode,t),t}),"getParents"),Yl=g((function(e,t){for(var n=Xl(e),r=Xl(t),i=0;i=0)return o}return!1}),"getCommonParent"),Jl=g((function(e,t,n){var r=il(e),i=il(t),o=r[0],a=!1;return i.filter(Boolean).forEach((function(e){a=Yl(a||e,e)||a,n.filter(Boolean).forEach((function(e){var t=Yl(o,e);t&&(a=!a||Al(t,a)?t:Yl(t,a))}))})),a}),"getTopCommonParent"),Zl=g((function(e,t){return e.reduce((function(e,n){return e.concat(Ll(n,t))}),[])}),"allParentAutofocusables"),eu=g((function(e){return function(t){var n;return t.autofocus||!!(null===(n=fl(t))||void 0===n?void 0:n.autofocus)||e.indexOf(t)>=0}}),"findAutoFocused"),tu=g((function(e,t){var n=new Map;return t.forEach((function(e){return n.set(e.node,e)})),e.map((function(e){return n.get(e)})).filter(El)}),"reorderNodes"),nu=g((function(e,t){var n=document&&jl(),r=Fl(e).filter(bl),i=Jl(n||e,e,r),o=new Map,a=Dl(r,o),s=Il(r,o).filter((function(e){var t=e.node;return bl(t)}));if(s[0]||(s=a)[0]){var l=Dl([i],o).map((function(e){return e.node})),u=tu(l,s),c=u.map((function(e){return e.node})),d=Ql(c,l,n,t);if(d===Kl){var f=Ol(a.map((function(e){return e.node}))).filter(eu(Zl(r,o)));return{node:f&&f.length?zl(f):zl(Ol(c))}}return void 0===d?d:u[d]}}),"getFocusMerge"),ru=g((function(e){var t=Fl(e).filter(bl),n=Jl(e,e,t),r=new Map,i=Il([n],r,!0),o=Il(t,r).filter((function(e){var t=e.node;return bl(t)})).map((function(e){return e.node}));return i.map((function(e){var t=e.node;return{node:t,index:e.index,lockItem:o.indexOf(t)>=0,guard:yl(t)}}))}),"getFocusabledIn"),iu=g((function(e,t){"focus"in e&&e.focus(t),"contentWindow"in e&&e.contentWindow&&e.contentWindow.focus()}),"focusOn"),ou=0,au=!1,su=g((function(e,t,n){void 0===n&&(n={});var r=nu(e,t);if(!au&&r){if(ou>2)return console.error("FocusLock: focus-fighting detected. Only one focus management system could be active. See https://github.com/theKashey/focus-lock/#focus-fighting"),au=!0,void setTimeout((function(){au=!1}),1);ou++,iu(r.node,n.focusOptions),ou--}}),"setFocus");function lu(e){var t=window.setImmediate;void 0!==t?t(e):setTimeout(e,1)}g(lu,"deferAction");var uu=g((function(){return document&&document.activeElement===document.body}),"focusOnBody"),cu=g((function(){return uu()||$l()}),"isFreeFocus"),du=null,fu=null,pu=null,hu=!1,mu=g((function(){return!0}),"defaultWhitelist"),gu=g((function(e){return(du.whiteList||mu)(e)}),"focusWhitelisted"),vu=g((function(e,t){pu={observerNode:e,portaledElement:t}}),"recordPortal"),yu=g((function(e){return pu&&pu.portaledElement===e}),"focusIsPortaledPair");function bu(e,t,n,r){var i=null,o=e;do{var a=r[o];if(a.guard)a.node.dataset.focusAutoGuard&&(i=a);else{if(!a.lockItem)break;if(o!==e)return;i=null}}while((o+=n)!==t);i&&(i.node.tabIndex=0)}g(bu,"autoGuard");var Eu=g((function(e){return e&&"current"in e?e.current:e}),"extractRef"),Tu=g((function(e){return e?Boolean(hu):"meanwhile"===hu}),"focusWasOutside"),wu=g((function e(t,n,r){return n&&(n.host===t&&(!n.activeElement||r.contains(n.activeElement))||n.parentNode&&e(t,n.parentNode,r))}),"checkInHost"),Cu=g((function(e,t){return t.some((function(t){return wu(e,t,t)}))}),"withinHost"),Su=g((function(){var e=!1;if(du){var t=du,n=t.observed,r=t.persistentFocus,i=t.autoFocus,o=t.shards,a=t.crossFrame,s=t.focusOptions,l=n||pu&&pu.portaledElement,u=document&&document.activeElement;if(l){var c=[l].concat(o.map(Eu).filter(Boolean));if(u&&!gu(u)||(r||Tu(a)||!cu()||!fu&&i)&&(l&&!(Bl(c)||u&&Cu(u,c)||yu(u))&&(document&&!fu&&u&&!i?(u.blur&&u.blur(),document.body.focus()):(e=su(c,fu,{focusOptions:s}),pu={})),hu=!1,fu=document&&document.activeElement),document){var d=document&&document.activeElement,f=ru(c),p=f.map((function(e){return e.node})).indexOf(d);p>-1&&(f.filter((function(e){var t=e.guard,n=e.node;return t&&n.dataset.focusAutoGuard})).forEach((function(e){return e.node.removeAttribute("tabIndex")})),bu(p,f.length,1,f),bu(p,-1,-1,f))}}}return e}),"activateTrap"),xu=g((function(e){Su()&&e&&(e.stopPropagation(),e.preventDefault())}),"onTrap"),ku=g((function(){return lu(Su)}),"onBlur"),Nu=g((function(e){var t=e.target,n=e.currentTarget;n.contains(t)||vu(n,t)}),"onFocus"),_u=g((function(){return null}),"FocusWatcher"),Ou=g((function(){hu="just",setTimeout((function(){hu="meanwhile"}),0)}),"onWindowBlur"),Iu=g((function(){document.addEventListener("focusin",xu),document.addEventListener("focusout",ku),window.addEventListener("blur",Ou)}),"attachHandler"),Du=g((function(){document.removeEventListener("focusin",xu),document.removeEventListener("focusout",ku),window.removeEventListener("blur",Ou)}),"detachHandler");function Lu(e){return e.filter((function(e){return!e.disabled}))}function Au(e){var t=e.slice(-1)[0];t&&!du&&Iu();var n=du,r=n&&t&&t.id===n.id;du=t,n&&!r&&(n.onDeactivation(),e.filter((function(e){return e.id===n.id})).length||n.returnFocus(!t)),t?(fu=null,r&&n.observed===t.observed||t.onActivation(),Su(),lu(Su)):(Du(),fu=null)}g(Lu,"reducePropsToState"),g(Au,"handleStateChangeOnClient"),zs.assignSyncMedium(Nu),Ws.assignMedium(ku),Ks.assignMedium((function(e){return e({moveFocusInside:su,focusInside:Bl})}));var Mu=nl(Lu,Au)(_u),Ru=t.forwardRef(g((function(e,n){return t.createElement(Js,Ns({sideCar:Mu,ref:n},e))}),"FocusLockUICombination")),Fu=Js.propTypes||{};Fu.sideCar,ks(Fu,["sideCar"]),Ru.propTypes={};var Pu=Ru,ju="right-scroll-bar-position",Vu="width-before-scroll-bar",Uu=qs(),Bu=g((function(){}),"nothing"),$u=t.forwardRef((function(e,n){var r=t.useRef(null),i=t.useState({onScrollCapture:Bu,onWheelCapture:Bu,onTouchMoveCapture:Bu}),o=i[0],a=i[1],s=e.forwardProps,l=e.children,u=e.className,c=e.removeScrollBar,d=e.enabled,f=e.shards,p=e.sideCar,h=e.noIsolation,m=e.inert,g=e.allowPinchZoom,v=e.as,y=void 0===v?"div":v,b=js(e,["forwardProps","children","className","removeScrollBar","enabled","shards","sideCar","noIsolation","inert","allowPinchZoom","as"]),E=p,T=Rs([r,n]),w=Ps(Ps({},b),o);return t.createElement(t.Fragment,null,d&&t.createElement(E,{sideCar:Uu,removeScrollBar:c,shards:f,noIsolation:h,inert:m,setCallbacks:a,allowPinchZoom:!!g,lockRef:r}),s?t.cloneElement(t.Children.only(l),Ps(Ps({},w),{ref:T})):t.createElement(y,Ps({},w,{className:u,ref:T}),l))}));$u.defaultProps={enabled:!0,removeScrollBar:!0,inert:!1},$u.classNames={fullWidth:Vu,zeroRight:ju};var qu=g((function(){return n.nc}),"getNonce");function Hu(){if(!document)return null;var e=document.createElement("style");e.type="text/css";var t=qu();return t&&e.setAttribute("nonce",t),e}function Gu(e,t){e.styleSheet?e.styleSheet.cssText=t:e.appendChild(document.createTextNode(t))}function zu(e){(document.head||document.getElementsByTagName("head")[0]).appendChild(e)}g(Hu,"makeStyleTag"),g(Gu,"injectStyles"),g(zu,"insertStyleTag");var Wu=g((function(){var e=0,t=null;return{add:function(n){0==e&&(t=Hu())&&(Gu(t,n),zu(t)),e++},remove:function(){!--e&&t&&(t.parentNode&&t.parentNode.removeChild(t),t=null)}}}),"stylesheetSingleton"),Ku=g((function(){var e=Wu();return function(n,r){t.useEffect((function(){return e.add(n),function(){e.remove()}}),[n&&r])}}),"styleHookSingleton"),Qu=g((function(){var e=Ku();return g((function(t){var n=t.styles,r=t.dynamic;return e(n,r),null}),"Sheet")}),"styleSingleton"),Xu={left:0,top:0,right:0,gap:0},Yu=g((function(e){return parseInt(e||"",10)||0}),"parse$1"),Ju=g((function(e){var t=window.getComputedStyle(document.body),n=t["padding"===e?"paddingLeft":"marginLeft"],r=t["padding"===e?"paddingTop":"marginTop"],i=t["padding"===e?"paddingRight":"marginRight"];return[Yu(n),Yu(r),Yu(i)]}),"getOffset"),Zu=g((function(e){if(void 0===e&&(e="margin"),"undefined"==typeof window)return Xu;var t=Ju(e),n=document.documentElement.clientWidth,r=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,r-n+t[2]-t[0])}}),"getGapWidth"),ec=Qu(),tc=g((function(e,t,n,r){var i=e.left,o=e.top,a=e.right,s=e.gap;return void 0===n&&(n="margin"),"\n .".concat("with-scroll-bars-hidden"," {\n overflow: hidden ").concat(r,";\n padding-right: ").concat(s,"px ").concat(r,";\n }\n body {\n overflow: hidden ").concat(r,";\n overscroll-behavior: contain;\n ").concat([t&&"position: relative ".concat(r,";"),"margin"===n&&"\n padding-left: ".concat(i,"px;\n padding-top: ").concat(o,"px;\n padding-right: ").concat(a,"px;\n margin-left:0;\n margin-top:0;\n margin-right: ").concat(s,"px ").concat(r,";\n "),"padding"===n&&"padding-right: ".concat(s,"px ").concat(r,";")].filter(Boolean).join(""),"\n }\n \n .").concat(ju," {\n right: ").concat(s,"px ").concat(r,";\n }\n \n .").concat(Vu," {\n margin-right: ").concat(s,"px ").concat(r,";\n }\n \n .").concat(ju," .").concat(ju," {\n right: 0 ").concat(r,";\n }\n \n .").concat(Vu," .").concat(Vu," {\n margin-right: 0 ").concat(r,";\n }\n \n body {\n ").concat("--removed-body-scroll-bar-size",": ").concat(s,"px;\n }\n")}),"getStyles$2"),nc=g((function(e){var n=e.noRelative,r=e.noImportant,i=e.gapMode,o=void 0===i?"margin":i,a=t.useMemo((function(){return Zu(o)}),[o]);return t.createElement(ec,{styles:tc(a,!n,o,r?"":"!important")})}),"RemoveScrollBar"),rc=!1;if("undefined"!=typeof window)try{var ic=Object.defineProperty({},"passive",{get:function(){return rc=!0,!0}});window.addEventListener("test",ic,ic),window.removeEventListener("test",ic,ic)}catch(e){rc=!1}var oc=!!rc&&{passive:!1},ac=g((function(e){return"TEXTAREA"===e.tagName}),"alwaysContainsScroll"),sc=g((function(e,t){var n=window.getComputedStyle(e);return"hidden"!==n[t]&&!(n.overflowY===n.overflowX&&!ac(e)&&"visible"===n[t])}),"elementCanBeScrolled"),lc=g((function(e){return sc(e,"overflowY")}),"elementCouldBeVScrolled"),uc=g((function(e){return sc(e,"overflowX")}),"elementCouldBeHScrolled"),cc=g((function(e,t){var n=t;do{if("undefined"!=typeof ShadowRoot&&n instanceof ShadowRoot&&(n=n.host),pc(e,n)){var r=hc(e,n);if(r[1]>r[2])return!0}n=n.parentNode}while(n&&n!==document.body);return!1}),"locationCouldBeScrolled"),dc=g((function(e){return[e.scrollTop,e.scrollHeight,e.clientHeight]}),"getVScrollVariables"),fc=g((function(e){return[e.scrollLeft,e.scrollWidth,e.clientWidth]}),"getHScrollVariables"),pc=g((function(e,t){return"v"===e?lc(t):uc(t)}),"elementCouldBeScrolled"),hc=g((function(e,t){return"v"===e?dc(t):fc(t)}),"getScrollVariables"),mc=g((function(e,t){return"h"===e&&"rtl"===t?-1:1}),"getDirectionFactor"),gc=g((function(e,t,n,r,i){var o=mc(e,window.getComputedStyle(t).direction),a=o*r,s=n.target,l=t.contains(s),u=!1,c=a>0,d=0,f=0;do{var p=hc(e,s),h=p[0],m=p[1]-p[2]-o*h;(h||m)&&pc(e,s)&&(d+=m,f+=h),s=s.parentNode}while(!l&&s!==document.body||l&&(t.contains(s)||t===s));return(c&&(i&&0===d||!i&&a>d)||!c&&(i&&0===f||!i&&-a>f))&&(u=!0),u}),"handleScroll"),vc=g((function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]}),"getTouchXY"),yc=g((function(e){return[e.deltaX,e.deltaY]}),"getDeltaXY"),bc=g((function(e){return e&&"current"in e?e.current:e}),"extractRef"),Ec=g((function(e,t){return e[0]===t[0]&&e[1]===t[1]}),"deltaCompare"),Tc=g((function(e){return"\n .block-interactivity-".concat(e," {pointer-events: none;}\n .allow-interactivity-").concat(e," {pointer-events: all;}\n")}),"generateStyle"),wc=0,Cc=[];function Sc(e){var n=t.useRef([]),r=t.useRef([0,0]),i=t.useRef(),o=t.useState(wc++)[0],a=t.useState((function(){return Qu()}))[0],s=t.useRef(e);t.useEffect((function(){s.current=e}),[e]),t.useEffect((function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var t=Vs([e.lockRef.current],(e.shards||[]).map(bc),!0).filter(Boolean);return t.forEach((function(e){return e.classList.add("allow-interactivity-".concat(o))})),function(){document.body.classList.remove("block-interactivity-".concat(o)),t.forEach((function(e){return e.classList.remove("allow-interactivity-".concat(o))}))}}}),[e.inert,e.lockRef.current,e.shards]);var l=t.useCallback((function(e,t){if("touches"in e&&2===e.touches.length)return!s.current.allowPinchZoom;var n,o=vc(e),a=r.current,l="deltaX"in e?e.deltaX:a[0]-o[0],u="deltaY"in e?e.deltaY:a[1]-o[1],c=e.target,d=Math.abs(l)>Math.abs(u)?"h":"v";if("touches"in e&&"h"===d&&"range"===c.type)return!1;var f=cc(d,c);if(!f)return!0;if(f?n=d:(n="v"===d?"h":"v",f=cc(d,c)),!f)return!1;if(!i.current&&"changedTouches"in e&&(l||u)&&(i.current=n),!n)return!0;var p=i.current||n;return gc(p,t,e,"h"===p?l:u,!0)}),[]),u=t.useCallback((function(e){var t=e;if(Cc.length&&Cc[Cc.length-1]===a){var r="deltaY"in t?yc(t):vc(t),i=n.current.filter((function(e){return e.name===t.type&&e.target===t.target&&Ec(e.delta,r)}))[0];if(i&&i.should)t.cancelable&&t.preventDefault();else if(!i){var o=(s.current.shards||[]).map(bc).filter(Boolean).filter((function(e){return e.contains(t.target)}));(o.length>0?l(t,o[0]):!s.current.noIsolation)&&t.cancelable&&t.preventDefault()}}}),[]),c=t.useCallback((function(e,t,r,i){var o={name:e,delta:t,target:r,should:i};n.current.push(o),setTimeout((function(){n.current=n.current.filter((function(e){return e!==o}))}),1)}),[]),d=t.useCallback((function(e){r.current=vc(e),i.current=void 0}),[]),f=t.useCallback((function(t){c(t.type,yc(t),t.target,l(t,e.lockRef.current))}),[]),p=t.useCallback((function(t){c(t.type,vc(t),t.target,l(t,e.lockRef.current))}),[]);t.useEffect((function(){return Cc.push(a),e.setCallbacks({onScrollCapture:f,onWheelCapture:f,onTouchMoveCapture:p}),document.addEventListener("wheel",u,oc),document.addEventListener("touchmove",u,oc),document.addEventListener("touchstart",d,oc),function(){Cc=Cc.filter((function(e){return e!==a})),document.removeEventListener("wheel",u,oc),document.removeEventListener("touchmove",u,oc),document.removeEventListener("touchstart",d,oc)}}),[]);var h=e.removeScrollBar,m=e.inert;return t.createElement(t.Fragment,null,m?t.createElement(a,{styles:Tc(o)}):null,h?t.createElement(nc,{gapMode:"margin"}):null)}g(Sc,"RemoveScrollSideCar");var xc=Gs(Uu,Sc),kc=t.forwardRef((function(e,n){return t.createElement($u,Ps({},e,{ref:n,sideCar:xc}))}));kc.classNames=$u.classNames;var Nc=kc,_c={exports:{}},Oc="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";function Ic(){}function Dc(){}g(Ic,"emptyFunction"),g(Dc,"emptyFunctionWithReset"),Dc.resetWarningCache=Ic;var Lc=g((function(){function e(e,t,n,r,i,o){if(o!==Oc){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}g(e,"shim"),e.isRequired=e,g(t,"getShim");var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:Dc,resetWarningCache:Ic};return n.PropTypes=n,n}),"factoryWithThrowingShims");_c.exports=Lc();var Ac=_c.exports;function Mc(){return Mc=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}g(Mc,"_extends$9"),g(Rc,"_objectWithoutPropertiesLoose$9");var Fc=["as","isOpen"],Pc=["allowPinchZoom","as","dangerouslyBypassFocusLock","dangerouslyBypassScrollLock","initialFocusRef","onClick","onDismiss","onKeyDown","onMouseDown","unstable_lockFocusAcrossFrames"],jc=["as","onClick","onKeyDown"],Vc=["allowPinchZoom","initialFocusRef","isOpen","onDismiss"];Ac.bool,Ac.bool,Ac.bool,Ac.func;var Uc=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"div":r,o=e.isOpen,a=void 0===o||o,s=Rc(e,Fc);return(0,t.useEffect)((function(){a?window.__REACH_DISABLE_TOOLTIPS=!0:window.requestAnimationFrame((function(){window.__REACH_DISABLE_TOOLTIPS=!1}))}),[a]),a?(0,t.createElement)(hs,{"data-reach-dialog-wrapper":""},(0,t.createElement)(Bc,Mc({ref:n,as:i},s))):null}),"DialogOverlay")),Bc=(0,t.forwardRef)(g((function(e,n){var r=e.allowPinchZoom,i=e.as,o=void 0===i?"div":i,a=e.dangerouslyBypassFocusLock,s=void 0!==a&&a,l=e.dangerouslyBypassScrollLock,u=void 0!==l&&l,c=e.initialFocusRef,d=e.onClick,f=e.onDismiss,p=void 0===f?bs:f,h=e.onKeyDown,m=e.onMouseDown,v=e.unstable_lockFocusAcrossFrames,y=Rc(e,Pc),b=(0,t.useRef)(null),E=(0,t.useRef)(null),T=Ss(E,n),w=(0,t.useCallback)((function(){c&&c.current&&c.current.focus()}),[c]);function C(e){b.current===e.target&&(e.stopPropagation(),p(e))}function S(e){"Escape"===e.key&&(e.stopPropagation(),p(e))}function x(e){b.current=e.target}return g(C,"handleClick"),g(S,"handleKeyDown"),g(x,"handleMouseDown"),(0,t.useEffect)((function(){return E.current?Hc(E.current):void 0}),[]),(0,t.createElement)(Pu,{autoFocus:!0,returnFocus:!0,onActivation:w,disabled:s,crossFrame:null==v||v},(0,t.createElement)(Nc,{allowPinchZoom:r,enabled:!u},(0,t.createElement)(o,Mc({},y,{ref:T,"data-reach-dialog-overlay":"",onClick:xs(d,C),onKeyDown:xs(h,S),onMouseDown:xs(m,x)}))))}),"DialogInner")),$c=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"div":r,o=e.onClick;e.onKeyDown;var a=Rc(e,jc);return(0,t.createElement)(i,Mc({"aria-modal":"true",role:"dialog",tabIndex:-1},a,{ref:n,"data-reach-dialog-content":"",onClick:xs(o,(function(e){e.stopPropagation()}))}))}),"DialogContent")),qc=(0,t.forwardRef)(g((function(e,n){var r=e.allowPinchZoom,i=void 0!==r&&r,o=e.initialFocusRef,a=e.isOpen,s=e.onDismiss,l=void 0===s?bs:s,u=Rc(e,Vc);return(0,t.createElement)(Uc,{allowPinchZoom:i,initialFocusRef:o,isOpen:a,onDismiss:l},(0,t.createElement)($c,Mc({ref:n},u)))}),"Dialog"));function Hc(e){var t=[],n=[],r=ms(e);return e?(Array.prototype.forEach.call(r.querySelectorAll("body > *"),(function(r){var i,o;if(r!==(null==(i=e.parentNode)||null==(o=i.parentNode)?void 0:o.parentNode)){var a=r.getAttribute("aria-hidden");null!==a&&"false"!==a||(t.push(a),n.push(r),r.setAttribute("aria-hidden","true"))}})),function(){n.forEach((function(e,n){var r=t[n];null===r?e.removeAttribute("aria-hidden"):e.setAttribute("aria-hidden",r)}))}):bs}function Gc(){return Gc=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}g(Hc,"createAriaHider"),g(Gc,"_extends$8"),g(zc,"_objectWithoutPropertiesLoose$8");var Wc=["as","style"],Kc=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"span":r,o=e.style,a=void 0===o?{}:o,s=zc(e,Wc);return(0,t.createElement)(i,Gc({ref:n,style:Gc({border:0,clip:"rect(0 0 0 0)",height:"1px",margin:"-1px",overflow:"hidden",padding:0,position:"absolute",width:"1px",whiteSpace:"nowrap",wordWrap:"normal"},a)},s))}),"VisuallyHidden")),Qc=Object.defineProperty;const Xc=g(((e,t)=>Qc(e,"name",{value:t,configurable:!0})),"__name$D")(((e,t)=>Object.entries(t).reduce(((e,t)=>{let[n,r]=t;return e[n]=r,e}),e)),"createComponentGroup"),Yc=(0,t.forwardRef)(((e,t)=>de(qc,m(h({},e),{ref:t}))));Yc.displayName="Dialog";const Jc=(0,t.forwardRef)(((e,t)=>fe(os,m(h({},e),{ref:t,type:"button",className:b("graphiql-dialog-close",e.className),children:[de(Kc,{children:"Close dialog"}),de(La,{})]}))));Jc.displayName="Dialog.Close";const Zc=Xc(Yc,{Close:Jc});e.aJ=Zc;var ed=!1,td=0;function nd(){return++td}function rd(e){var n;if("function"==typeof t.useId){var r=(0,t.useId)(e);return null!=e?e:r}var i=null!=e?e:ed?nd():null,o=(0,t.useState)(i),a=o[0],s=o[1];return us((function(){null===a&&s(nd())}),[]),(0,t.useEffect)((function(){!1===ed&&(ed=!0)}),[]),null!=(n=null!=e?e:a)?n:void 0}g(nd,"genId"),g(rd,"useId");var id,od=["bottom","height","left","right","top","width"],ad=g((function(e,t){return void 0===e&&(e={}),void 0===t&&(t={}),od.some((function(n){return e[n]!==t[n]}))}),"rectChanged"),sd=new Map,ld=g((function e(){var t=[];sd.forEach((function(e,n){var r=n.getBoundingClientRect();ad(r,e.rect)&&(e.rect=r,t.push(e))})),t.forEach((function(e){e.callbacks.forEach((function(t){return t(e.rect)}))})),id=window.requestAnimationFrame(e)}),"run");function ud(e,t){return{observe:g((function(){var n=0===sd.size;sd.has(e)?sd.get(e).callbacks.push(t):sd.set(e,{rect:void 0,hasRectChanged:!1,callbacks:[t]}),n&&ld()}),"observe"),unobserve:g((function(){var n=sd.get(e);if(n){var r=n.callbacks.indexOf(t);r>=0&&n.callbacks.splice(r,1),n.callbacks.length||sd.delete(e),sd.size||cancelAnimationFrame(id)}}),"unobserve")}}function cd(e,n,r){var i,o,a;gs(n)?i=n:(i=null==(a=null==n?void 0:n.observe)||a,o=null==n?void 0:n.onChange),vs(r)&&(o=r);var s=(0,t.useState)(e.current),l=s[0],u=s[1],c=(0,t.useRef)(!1),d=(0,t.useRef)(!1),f=(0,t.useState)(null),p=f[0],h=f[1],m=(0,t.useRef)(o);return us((function(){m.current=o,e.current!==l&&u(e.current)})),us((function(){l&&!c.current&&(c.current=!0,h(l.getBoundingClientRect()))}),[l]),us((function(){if(i){var t=l;if(d.current||(d.current=!0,t=e.current),t){var n=ud(t,(function(e){null==m.current||m.current(e),h(e)}));return n.observe(),function(){n.unobserve()}}}}),[i,l,e]),p}g(ud,"observeRect"),g(cd,"useRect");var dd=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])'],fd=dd.join(","),pd="undefined"==typeof Element?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector;function hd(e,t){t=t||{};var n,r,i,o=[],a=[],s=e.querySelectorAll(fd);for(t.includeContainer&&pd.call(e,fd)&&(s=Array.prototype.slice.apply(s)).unshift(e),n=0;n=0||(i[n]=e[n]);return i}g(Dd,"_extends$7"),g(Ld,"_objectWithoutPropertiesLoose$7");var Ad=["unstable_skipInitialPortalRender"],Md=["as","targetRef","position","unstable_observableRefs"],Rd=(0,t.forwardRef)(g((function(e,n){var r=e.unstable_skipInitialPortalRender,i=Ld(e,Ad);return(0,t.createElement)(hs,{unstable_skipInitialRender:r},(0,t.createElement)(Fd,Dd({ref:n},i)))}),"Popover")),Fd=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"div":r,o=e.targetRef,a=e.position,s=void 0===a?Vd:a,l=e.unstable_observableRefs,u=void 0===l?[]:l,c=Ld(e,Md),d=(0,t.useRef)(null),f=cd(d,{observe:!c.hidden}),p=cd(o,{observe:!c.hidden}),h=Ss(d,n);return $d(o,d),(0,t.createElement)(i,Dd({"data-reach-popover":"",ref:h},c,{style:Dd({position:"absolute"},Pd.apply(void 0,[s,p,f].concat(u)),c.style)}))}),"PopoverImpl"));function Pd(e,t,n){for(var r=arguments.length,i=new Array(r>3?r-3:0),o=3;o=0||(i[n]=e[n]);return i}function Hd(){return Hd=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}g(zd,"createDescendantContext"),g(Wd,"useDescendant"),g(Kd,"useDescendantsInit"),g(Qd,"useDescendants"),g(Xd,"DescendantProvider"),g(Yd,"useDescendantKeyDown"),g(Jd,"isRightClick"),g(Zd,"createStableCallbackHook"),g(ef,"useStableCallback"),g(tf,"_objectWithoutPropertiesLoose$5");var nf,rf,of=["children"];function af(e,n){return(0,t.createContext)(n)}function sf(e,n){var r=(0,t.createContext)(n);function i(e){var n=e.children,i=tf(e,of),o=(0,t.useMemo)((function(){return i}),Object.values(i));return(0,t.createElement)(r.Provider,{value:o},n)}function o(i){var o=(0,t.useContext)(r);if(o)return o;if(n)return n;throw Error(i+" must be rendered inside of a "+e+" component.")}return g(i,"Provider"),g(o,"useContext$1"),[i,o]}function lf(){for(var e=arguments.length,t=new Array(e),n=0;n0||m,matches:hf(o)}}}}),"x");try{for(var p=function(e){var t="function"==typeof Symbol&&e[Symbol.iterator],n=0;return t?t.call(e):{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}(d),h=p.next();!h.done;h=p.next()){var m=f(h.value);if("object"==typeof m)return m.value}}catch(e){i={error:e}}finally{try{h&&!h.done&&(o=p.return)&&o.call(p)}finally{if(i)throw i.error}}}return gf(s,l)}};return n}g(df,"e$1"),g(ff,"r$1"),g(pf,"i$1"),g(hf,"o"),g(mf,"a"),g(gf,"u"),g(vf,"c$1");var yf=g((function(e,t){return e.actions.forEach((function(n){var r=n.exec;return r&&r(e.context,t)}))}),"s");function bf(e){var t=e.initialState,n=nf.NotStarted,r=new Set,i={_machine:e,send:function(i){n===nf.Running&&(t=e.transition(t,i),yf(t,mf(i)),r.forEach((function(e){return e(t)})))},subscribe:function(e){return r.add(e),e(t),{unsubscribe:function(){return r.delete(e)}}},start:function(){return n=nf.Running,yf(t,cf),i},stop:function(){return n=nf.Stopped,r.clear(),i},get state(){return t},get status(){return n}};return i}function Ef(e){var n=(0,t.useRef)();return n.current||(n.current={v:e()}),n.current.v}function Tf(){return Tf=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}g(_f,"useMachine"),g(Of,"unwrapRefs"),g(If,"useCreateMachine"),g(Df,"_extends$4"),g(Lf,"_objectWithoutPropertiesLoose$4"),(kf=Cf||(Cf={})).Idle="IDLE",kf.Open="OPEN",kf.Navigating="NAVIGATING",kf.Dragging="DRAGGING",kf.Interacting="INTERACTING",(xf=Sf||(Sf={})).ButtonMouseDown="BUTTON_MOUSE_DOWN",xf.ButtonMouseUp="BUTTON_MOUSE_UP",xf.Blur="BLUR",xf.ClearNavSelection="CLEAR_NAV_SELECTION",xf.ClearTypeahead="CLEAR_TYPEAHEAD",xf.GetDerivedData="GET_DERIVED_DATA",xf.KeyDownEscape="KEY_DOWN_ESCAPE",xf.KeyDownEnter="KEY_DOWN_ENTER",xf.KeyDownSpace="KEY_DOWN_SPACE",xf.KeyDownNavigate="KEY_DOWN_NAVIGATE",xf.KeyDownSearch="KEY_DOWN_SEARCH",xf.KeyDownTab="KEY_DOWN_TAB",xf.KeyDownShiftTab="KEY_DOWN_SHIFT_TAB",xf.OptionTouchStart="OPTION_TOUCH_START",xf.OptionMouseMove="OPTION_MOUSE_MOVE",xf.OptionMouseEnter="OPTION_MOUSE_ENTER",xf.OptionMouseDown="OPTION_MOUSE_DOWN",xf.OptionMouseUp="OPTION_MOUSE_UP",xf.OptionClick="OPTION_CLICK",xf.ListMouseUp="LIST_MOUSE_UP",xf.OptionPress="OPTION_PRESS",xf.OutsideMouseDown="OUTSIDE_MOUSE_DOWN",xf.OutsideMouseUp="OUTSIDE_MOUSE_UP",xf.ValueChange="VALUE_CHANGE",xf.PopoverPointerDown="POPOVER_POINTER_DOWN",xf.PopoverPointerUp="POPOVER_POINTER_UP",xf.UpdateAfterTypeahead="UPDATE_AFTER_TYPEAHEAD";var Af=ff({navigationValue:null}),Mf=ff({typeaheadQuery:null}),Rf=ff({value:g((function(e,t){return t.value}),"value")}),Ff=ff({navigationValue:g((function(e,t){return t.value}),"navigationValue")}),Pf=ff({navigationValue:g((function(e){var t,n=tp(e.value,e.options);return n&&!n.disabled?e.value:(null==(t=e.options.find((function(e){return!e.disabled})))?void 0:t.value)||null}),"navigationValue")});function jf(e,t){if(t.type===Sf.Blur){var n=t.refs,r=n.list,i=n.popover,o=t.relatedTarget,a=ms(i);return!((null==a?void 0:a.activeElement)===r||!i||i.contains(o||(null==a?void 0:a.activeElement)))}return!1}function Vf(e,t){if(t.type===Sf.OutsideMouseDown||t.type===Sf.OutsideMouseUp){var n=t.refs,r=n.button,i=n.popover,o=t.relatedTarget;return!(o===r||!r||r.contains(o)||!i||i.contains(o))}return!1}function Uf(e,t){return!!e.options.find((function(t){return t.value===e.navigationValue}))}function Bf(e,t){var n=t.refs,r=n.popover,i=n.list,o=t.relatedTarget;return!(r&&o&&r.contains(o)&&o!==i)&&Uf(e)}function $f(e,t){requestAnimationFrame((function(){t.refs.list&&t.refs.list.focus()}))}function qf(e,t){t.refs.button&&t.refs.button.focus()}function Hf(e,t){return!t.disabled}function Gf(e,t){return t.type!==Sf.OptionTouchStart||!t||!t.disabled}function zf(e,t){return!("disabled"in t&&t.disabled||("value"in t?null==t.value:null==e.navigationValue))}function Wf(e,t){t.callback&&t.callback(t.value)}function Kf(e,t){if(t.type===Sf.KeyDownEnter){var n=t.refs.hiddenInput;if(n&&n.form){var r=n.form.querySelector("button:not([type]),[type='submit']");r&&r.click()}}}g(jf,"listboxLostFocus"),g(Vf,"clickedOutsideOfListbox"),g(Uf,"optionIsActive"),g(Bf,"shouldNavigate"),g($f,"focusList"),g(qf,"focusButton"),g(Hf,"listboxIsNotDisabled"),g(Gf,"optionIsNavigable"),g(zf,"optionIsSelectable"),g(Wf,"selectOption"),g(Kf,"submitForm");var Qf=ff({typeaheadQuery:g((function(e,t){return(e.typeaheadQuery||"")+t.query}),"typeaheadQuery")}),Xf=ff({value:g((function(e,t){if(t.type===Sf.UpdateAfterTypeahead&&t.query){var n=ep(e.options,t.query);if(n&&!n.disabled)return t.callback&&t.callback(n.value),n.value}return e.value}),"value")}),Yf=ff({navigationValue:g((function(e,t){if(t.type===Sf.UpdateAfterTypeahead&&t.query){var n=ep(e.options,t.query);if(n&&!n.disabled)return n.value}return e.navigationValue}),"navigationValue")}),Jf=((wf={})[Sf.GetDerivedData]={actions:ff((function(e,t){return Df({},e,t.data)}))},wf[Sf.ValueChange]={actions:[Rf,Wf]},wf),Zf=g((function(e){var t,n,r,i,o,a,s=e.value;return{id:"listbox",initial:Cf.Idle,context:{value:s,options:[],navigationValue:null,typeaheadQuery:null},states:(a={},a[Cf.Idle]={on:Df({},Jf,(t={},t[Sf.ButtonMouseDown]={target:Cf.Open,actions:[Pf],cond:Hf},t[Sf.KeyDownSpace]={target:Cf.Navigating,actions:[Pf,$f],cond:Hf},t[Sf.KeyDownSearch]={target:Cf.Idle,actions:Qf,cond:Hf},t[Sf.UpdateAfterTypeahead]={target:Cf.Idle,actions:[Xf],cond:Hf},t[Sf.ClearTypeahead]={target:Cf.Idle,actions:Mf},t[Sf.KeyDownNavigate]={target:Cf.Navigating,actions:[Pf,Mf,$f],cond:Hf},t[Sf.KeyDownEnter]={actions:[Kf],cond:Hf},t))},a[Cf.Interacting]={entry:[Af],on:Df({},Jf,(n={},n[Sf.ClearNavSelection]={actions:[Af,$f]},n[Sf.KeyDownEnter]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},n[Sf.KeyDownSpace]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},n[Sf.ButtonMouseDown]={target:Cf.Idle,actions:[qf]},n[Sf.KeyDownEscape]={target:Cf.Idle,actions:[qf]},n[Sf.OptionMouseDown]={target:Cf.Dragging},n[Sf.OutsideMouseDown]=[{target:Cf.Idle,cond:Vf,actions:Mf},{target:Cf.Dragging,actions:Mf,cond:Uf}],n[Sf.OutsideMouseUp]=[{target:Cf.Idle,cond:Vf,actions:Mf},{target:Cf.Navigating,cond:Uf},{target:Cf.Interacting,actions:Mf}],n[Sf.KeyDownEnter]=Cf.Interacting,n[Sf.Blur]=[{target:Cf.Idle,cond:jf,actions:Mf},{target:Cf.Navigating,cond:Bf},{target:Cf.Interacting,actions:Mf}],n[Sf.OptionTouchStart]={target:Cf.Navigating,actions:[Ff,Mf],cond:Gf},n[Sf.OptionClick]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},n[Sf.OptionPress]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},n[Sf.OptionMouseEnter]={target:Cf.Navigating,actions:[Ff,Mf],cond:Gf},n[Sf.KeyDownNavigate]={target:Cf.Navigating,actions:[Ff,Mf,$f]},n))},a[Cf.Open]={on:Df({},Jf,(r={},r[Sf.ClearNavSelection]={actions:[Af]},r[Sf.KeyDownEnter]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},r[Sf.KeyDownSpace]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},r[Sf.ButtonMouseDown]={target:Cf.Idle,actions:[qf]},r[Sf.KeyDownEscape]={target:Cf.Idle,actions:[qf]},r[Sf.OptionMouseDown]={target:Cf.Dragging},r[Sf.OutsideMouseDown]=[{target:Cf.Idle,cond:Vf,actions:Mf},{target:Cf.Dragging,cond:Uf},{target:Cf.Interacting,actions:Mf}],r[Sf.OutsideMouseUp]=[{target:Cf.Idle,cond:Vf,actions:Mf},{target:Cf.Navigating,cond:Uf},{target:Cf.Interacting,actions:Mf}],r[Sf.Blur]=[{target:Cf.Idle,cond:jf,actions:Mf},{target:Cf.Navigating,cond:Bf},{target:Cf.Interacting,actions:Mf}],r[Sf.ButtonMouseUp]={target:Cf.Navigating,actions:[Pf,$f]},r[Sf.ListMouseUp]={target:Cf.Navigating,actions:[Pf,$f]},r[Sf.OptionTouchStart]={target:Cf.Navigating,actions:[Ff,Mf],cond:Gf},r[Sf.OptionClick]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},r[Sf.OptionPress]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},r[Sf.KeyDownNavigate]={target:Cf.Navigating,actions:[Ff,Mf,$f]},r[Sf.KeyDownSearch]={target:Cf.Navigating,actions:Qf},r[Sf.UpdateAfterTypeahead]={actions:[Yf]},r[Sf.ClearTypeahead]={actions:Mf},r[Sf.OptionMouseMove]=[{target:Cf.Dragging,actions:[Ff],cond:Gf},{target:Cf.Dragging}],r))},a[Cf.Dragging]={on:Df({},Jf,(i={},i[Sf.ClearNavSelection]={actions:[Af]},i[Sf.KeyDownEnter]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},i[Sf.KeyDownSpace]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},i[Sf.ButtonMouseDown]={target:Cf.Idle,actions:[qf]},i[Sf.KeyDownEscape]={target:Cf.Idle,actions:[qf]},i[Sf.OptionMouseDown]={target:Cf.Dragging},i[Sf.OutsideMouseDown]=[{target:Cf.Idle,cond:Vf,actions:Mf},{target:Cf.Navigating,cond:Uf},{target:Cf.Interacting,actions:Mf}],i[Sf.OutsideMouseUp]=[{target:Cf.Idle,cond:Vf,actions:Mf},{target:Cf.Navigating,cond:Uf,actions:$f},{target:Cf.Interacting,actions:[Mf,$f]}],i[Sf.Blur]=[{target:Cf.Idle,cond:jf,actions:Mf},{target:Cf.Navigating,cond:Bf},{target:Cf.Interacting,actions:Mf}],i[Sf.ButtonMouseUp]={target:Cf.Navigating,actions:[Pf,$f]},i[Sf.OptionTouchStart]={target:Cf.Navigating,actions:[Ff,Mf],cond:Gf},i[Sf.OptionClick]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},i[Sf.OptionPress]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},i[Sf.OptionMouseEnter]={target:Cf.Dragging,actions:[Ff,Mf],cond:Gf},i[Sf.KeyDownNavigate]={target:Cf.Navigating,actions:[Ff,Mf,$f]},i[Sf.KeyDownSearch]={target:Cf.Navigating,actions:Qf},i[Sf.UpdateAfterTypeahead]={actions:[Yf]},i[Sf.ClearTypeahead]={actions:Mf},i[Sf.OptionMouseMove]=[{target:Cf.Navigating,actions:[Ff],cond:Gf},{target:Cf.Navigating}],i[Sf.OptionMouseUp]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},i))},a[Cf.Navigating]={on:Df({},Jf,(o={},o[Sf.ClearNavSelection]={actions:[Af,$f]},o[Sf.KeyDownEnter]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},o[Sf.KeyDownSpace]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},o[Sf.ButtonMouseDown]={target:Cf.Idle,actions:[qf]},o[Sf.KeyDownEscape]={target:Cf.Idle,actions:[qf]},o[Sf.OptionMouseDown]={target:Cf.Dragging},o[Sf.OutsideMouseDown]=[{target:Cf.Idle,cond:Vf,actions:Mf},{target:Cf.Navigating,cond:Uf},{target:Cf.Interacting,actions:Mf}],o[Sf.OutsideMouseUp]=[{target:Cf.Idle,cond:Vf,actions:Mf},{target:Cf.Navigating,cond:Uf},{target:Cf.Interacting,actions:Mf}],o[Sf.Blur]=[{target:Cf.Idle,cond:jf,actions:Mf},{target:Cf.Navigating,cond:Bf},{target:Cf.Interacting,actions:Mf}],o[Sf.ButtonMouseUp]={target:Cf.Navigating,actions:[Pf,$f]},o[Sf.OptionTouchStart]={target:Cf.Navigating,actions:[Ff,Mf],cond:Gf},o[Sf.OptionClick]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},o[Sf.OptionPress]={target:Cf.Idle,actions:[Rf,Mf,qf,Wf],cond:zf},o[Sf.OptionMouseEnter]={target:Cf.Navigating,actions:[Ff,Mf],cond:Gf},o[Sf.KeyDownNavigate]={target:Cf.Navigating,actions:[Ff,Mf,$f]},o[Sf.KeyDownSearch]={target:Cf.Navigating,actions:Qf},o[Sf.UpdateAfterTypeahead]={actions:[Yf]},o[Sf.ClearTypeahead]={actions:Mf},o[Sf.OptionMouseMove]=[{target:Cf.Navigating,actions:[Ff],cond:Gf},{target:Cf.Navigating}],o))},a)}}),"createMachineDefinition");function ep(e,t){return void 0===t&&(t=""),t&&e.find((function(e){return!e.disabled&&e.label&&e.label.toLowerCase().startsWith(t.toLowerCase())}))||null}function tp(e,t){return e?t.find((function(t){return t.value===e})):void 0}g(ep,"findOptionFromTypeahead"),g(tp,"findOptionFromValue");var np=["as","aria-labelledby","aria-label","children","defaultValue","disabled","form","name","onChange","required","value","__componentName"],rp=["arrow","button","children","portal"],ip=["aria-label","arrow","as","children","onKeyDown","onMouseDown","onMouseUp"],op=["as","children"],ap=["as","position","onBlur","onKeyDown","onMouseUp","portal","unstable_observableRefs"],sp=["as"],lp=["as","children","disabled","index","label","onClick","onMouseDown","onMouseEnter","onMouseLeave","onMouseMove","onMouseUp","onTouchStart","value"],up=zd(),cp=af(0,{}),dp=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"div":r,o=e["aria-labelledby"],a=e["aria-label"],s=e.children,l=e.defaultValue,u=e.disabled,c=void 0!==u&&u,d=e.form,f=e.name,p=e.onChange,h=e.required,m=e.value;e.__componentName;var v=Lf(e,np),y=(0,t.useRef)(null!=m),b=Kd(),E=b[0],T=b[1],w=(0,t.useRef)(null),C=(0,t.useRef)(null),S=(0,t.useRef)(null),x=(0,t.useRef)(null),k=(0,t.useRef)(null),N=(0,t.useRef)(null),_=(0,t.useRef)(null),O=_f(If(Zf({value:(y.current?m:l)||null})),{button:w,hiddenInput:C,highlightedOption:S,input:x,list:k,popover:N,selectedOption:_},false),I=O[0],D=O[1];function L(e){e!==I.context.value&&(null==p||p(e))}g(L,"handleValueChange");var A=rd(v.id),M=v.id||lf("listbox-input",A),R=Ss(x,n),F=(0,t.useMemo)((function(){var e=E.find((function(e){return e.value===I.context.value}));return e?e.label:null}),[E,I.context.value]),P=Tp(I.value),j={ariaLabel:a,ariaLabelledBy:o,buttonRef:w,disabled:c,highlightedOptionRef:S,isExpanded:P,listboxId:M,listboxValueLabel:F,listRef:k,onValueChange:L,popoverRef:N,selectedOptionRef:_,send:D,state:I.value,stateData:I.context},V=(0,t.useRef)(!1);if(!y.current&&null==l&&!V.current&&E.length){V.current=!0;var U=E.find((function(e){return!e.disabled}));U&&U.value&&D({type:Sf.ValueChange,value:U.value})}return xp(m,I.context.value,(function(){D({type:Sf.ValueChange,value:m})})),us((function(){D({type:Sf.GetDerivedData,data:{options:E}})}),[E,D]),(0,t.useEffect)((function(){function e(e){var t=e.target,n=e.relatedTarget;Sp(N.current,t)||D({type:Sf.OutsideMouseDown,relatedTarget:n||t})}return g(e,"handleMouseDown"),P&&window.addEventListener("mousedown",e),function(){window.removeEventListener("mousedown",e)}}),[D,P]),(0,t.useEffect)((function(){function e(e){var t=e.target,n=e.relatedTarget;Sp(N.current,t)||D({type:Sf.OutsideMouseUp,relatedTarget:n||t})}return g(e,"handleMouseUp"),P&&window.addEventListener("mouseup",e),function(){window.removeEventListener("mouseup",e)}}),[D,P]),(0,t.createElement)(i,Df({},v,{ref:R,"data-reach-listbox-input":"","data-state":P?"expanded":"closed","data-value":I.context.value,id:M}),(0,t.createElement)(cp.Provider,{value:j},(0,t.createElement)(Xd,{context:up,items:E,set:T},vs(s)?s({id:M,isExpanded:P,value:I.context.value,selectedOptionRef:_,highlightedOptionRef:S,valueLabel:F,expanded:P}):s,(d||f||h)&&(0,t.createElement)("input",{ref:C,"data-reach-listbox-hidden-input":"",disabled:c,form:d,name:f,readOnly:!0,required:h,tabIndex:-1,type:"hidden",value:I.context.value||""}))))}),"ListboxInput")),fp=(0,t.forwardRef)(g((function(e,n){var r=e.arrow,i=void 0===r?"â–¼":r,o=e.button,a=e.children,s=e.portal,l=void 0===s||s,u=Lf(e,rp);return(0,t.createElement)(dp,Df({},u,{__componentName:"Listbox",ref:n}),(function(e){var n=e.value,r=e.valueLabel;return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(hp,{arrow:i,children:o?vs(o)?o({value:n,label:r}):o:void 0}),(0,t.createElement)(yp,{portal:l},(0,t.createElement)(bp,null,a)))}))}),"Listbox")),pp=(0,t.forwardRef)(g((function(e,n){var r=e["aria-label"],i=e.arrow,o=void 0!==i&&i,a=e.as,s=void 0===a?"span":a,l=e.children,u=e.onKeyDown,c=e.onMouseDown,d=e.onMouseUp,f=Lf(e,ip),p=(0,t.useContext)(cp),h=p.buttonRef,m=p.send,v=p.ariaLabelledBy,y=p.disabled,b=p.isExpanded,E=p.listboxId,T=p.stateData,w=p.listboxValueLabel,C=T.value,S=Ss(h,n),x=wp();function k(e){Jd(e.nativeEvent)||(e.preventDefault(),e.stopPropagation(),m({type:Sf.ButtonMouseDown,disabled:y}))}function N(e){Jd(e.nativeEvent)||(e.preventDefault(),e.stopPropagation(),m({type:Sf.ButtonMouseUp}))}g(k,"handleMouseDown"),g(N,"handleMouseUp");var _=lf("button",E),O=(0,t.useMemo)((function(){return l?vs(l)?l({isExpanded:b,label:w,value:C,expanded:b}):l:w}),[l,w,b,C]);return(0,t.createElement)(s,Df({"aria-disabled":y||void 0,"aria-expanded":b||void 0,"aria-haspopup":"listbox","aria-labelledby":r?void 0:[v,_].filter(Boolean).join(" "),"aria-label":r,role:"button",tabIndex:y?-1:0},f,{ref:S,"data-reach-listbox-button":"",id:_,onKeyDown:xs(u,x),onMouseDown:xs(c,k),onMouseUp:xs(d,N)}),O,o&&(0,t.createElement)(gp,null,gs(o)?null:o))}),"ListboxButton")),hp=(0,t.memo)(pp),mp=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"span":r,o=e.children,a=Lf(e,op),s=(0,t.useContext)(cp).isExpanded;return(0,t.createElement)(i,Df({"aria-hidden":!0},a,{ref:n,"data-reach-listbox-arrow":"","data-expanded":s?"":void 0}),vs(o)?o({isExpanded:s,expanded:s}):o||"â–¼")}),"ListboxArrow")),gp=(0,t.memo)(mp),vp=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"div":r,o=e.position,a=void 0===o?Ud:o,s=e.onBlur,l=e.onKeyDown,u=e.onMouseUp,c=e.portal,d=void 0===c||c,f=e.unstable_observableRefs,p=Lf(e,ap),h=(0,t.useContext)(cp),m=h.isExpanded,v=h.buttonRef,y=h.popoverRef,b=h.send,E=Ss(y,n),T=wp();function w(){b({type:Sf.ListMouseUp})}g(w,"handleMouseUp");var C=Df({hidden:!m,tabIndex:-1},p,{ref:E,"data-reach-listbox-popover":"",onMouseUp:xs(u,w),onBlur:xs(s,S),onKeyDown:xs(l,T)});function S(e){var t=e.nativeEvent;requestAnimationFrame((function(){b({type:Sf.Blur,relatedTarget:t.relatedTarget||t.target})}))}return g(S,"handleBlur"),d?(0,t.createElement)(Rd,Df({},C,{as:i,targetRef:v,position:a,unstable_observableRefs:f,unstable_skipInitialPortalRender:!0})):(0,t.createElement)(i,C)}),"ListboxPopover")),yp=(0,t.memo)(vp),bp=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"ul":r,o=Lf(e,sp),a=(0,t.useContext)(cp),s=a.listRef,l=a.ariaLabel,u=a.ariaLabelledBy,c=a.isExpanded,d=a.listboxId,f=a.stateData,p=f.value,h=f.navigationValue,m=Ss(n,s);return(0,t.createElement)(i,Df({"aria-activedescendant":Cp(c?h:p),"aria-labelledby":l?void 0:u,"aria-label":l,role:"listbox",tabIndex:-1},o,{ref:m,"data-reach-listbox-list":"",id:lf("listbox",d)}))}),"ListboxList")),Ep=(0,t.forwardRef)(g((function(e,n){var r=e.as,i=void 0===r?"li":r,o=e.children,a=e.disabled,s=e.index,l=e.label,u=e.onClick,c=e.onMouseDown,d=e.onMouseEnter,f=e.onMouseLeave,p=e.onMouseMove,h=e.onMouseUp,m=e.onTouchStart,v=e.value,y=Lf(e,lp),b=(0,t.useContext)(cp),E=b.highlightedOptionRef,T=b.selectedOptionRef,w=b.send,C=b.isExpanded,S=b.onValueChange,x=b.state,k=b.stateData,N=k.value,_=k.navigationValue,O=(0,t.useState)(l),I=O[0],D=O[1],L=l||I||"",A=uf((0,t.useRef)(null),null),M=A[0],R=A[1];Wd((0,t.useMemo)((function(){return{element:M,value:v,label:L,disabled:!!a}}),[a,M,L,v]),up,s);var F=(0,t.useCallback)((function(e){!l&&e&&D((function(t){return e.textContent&&t!==e.textContent?e.textContent:t||""}))}),[l]),P=!!_&&_===v,j=N===v,V=Ss(F,n,R,j?T:null,P?E:null);function U(){w({type:Sf.OptionMouseEnter,value:v,disabled:!!a})}function B(){w({type:Sf.OptionTouchStart,value:v,disabled:!!a})}function $(){w({type:Sf.ClearNavSelection})}function q(e){Jd(e.nativeEvent)||(e.preventDefault(),w({type:Sf.OptionMouseDown}))}function H(e){Jd(e.nativeEvent)||w({type:Sf.OptionMouseUp,value:v,callback:S,disabled:!!a})}function G(e){Jd(e.nativeEvent)||w({type:Sf.OptionClick,value:v,callback:S,disabled:!!a})}function z(){x!==Cf.Open&&_===v||w({type:Sf.OptionMouseMove,value:v,disabled:!!a})}return g(U,"handleMouseEnter"),g(B,"handleTouchStart"),g($,"handleMouseLeave"),g(q,"handleMouseDown"),g(H,"handleMouseUp"),g(G,"handleClick"),g(z,"handleMouseMove"),(0,t.createElement)(i,Df({"aria-selected":(C?P:j)||void 0,"aria-disabled":a||void 0,role:"option"},y,{ref:V,id:Cp(v),"data-reach-listbox-option":"","data-current-nav":P?"":void 0,"data-current-selected":j?"":void 0,"data-label":L,"data-value":v,onClick:xs(u,G),onMouseDown:xs(c,q),onMouseEnter:xs(d,U),onMouseLeave:xs(f,$),onMouseMove:xs(p,z),onMouseUp:xs(h,H),onTouchStart:xs(m,B)}),o)}),"ListboxOption"));function Tp(e){return[Cf.Navigating,Cf.Open,Cf.Dragging,Cf.Interacting].includes(e)}function wp(){var e=(0,t.useContext)(cp),n=e.send,r=e.disabled,i=e.onValueChange,o=e.stateData,a=o.navigationValue,s=o.typeaheadQuery,l=Qd(up),u=ef(i);(0,t.useEffect)((function(){s&&n({type:Sf.UpdateAfterTypeahead,query:s,callback:u});var e=window.setTimeout((function(){null!=s&&n({type:Sf.ClearTypeahead})}),1e3);return function(){window.clearTimeout(e)}}),[u,n,s]);var c=l.findIndex((function(e){return e.value===a}));return xs((function(e){var t=e.key,o=ys(t)&&1===t.length,s=l.find((function(e){return e.value===a}));switch(t){case"Enter":return void n({type:Sf.KeyDownEnter,value:a,callback:i,disabled:!!(null!=s&&s.disabled||r)});case" ":return e.preventDefault(),void n({type:Sf.KeyDownSpace,value:a,callback:i,disabled:!!(null!=s&&s.disabled||r)});case"Escape":return void n({type:Sf.KeyDownEscape});case"Tab":var u=e.shiftKey?Sf.KeyDownShiftTab:Sf.KeyDownTab;return void n({type:u});default:return void(o&&n({type:Sf.KeyDownSearch,query:t,disabled:r}))}}),Yd(up,{currentIndex:c,orientation:"vertical",key:"index",rotate:!0,filter:g((function(e){return!e.disabled}),"filter"),callback:g((function(e){n({type:Sf.KeyDownNavigate,value:l[e].value,disabled:r})}),"callback")}))}function Cp(e){var n=(0,t.useContext)(cp).listboxId;return e?lf("option-"+e,n):void 0}function Sp(e,t){return!(!e||!e.contains(t))}function xp(e,n,r){(0,t.useRef)(null!=e).current&&e!==n&&r()}function kp(e){var n=(0,t.useRef)(null);return(0,t.useEffect)((function(){n.current=e}),[e]),n.current}function Np(e,t){if(null==e)return{};var n,r,i={},o=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}function _p(){return _p=Object.assign||function(e){for(var t=1;t8||n>8)&&(C.current=!0)}V||null==j||p||E({type:Up,payload:{index:j,dropdownRef:T}})}function K(){C.current=!0,V||null==j||p||E({type:Up,payload:{index:j}})}function Q(e){Jd(e.nativeEvent)||(C.current?i?M.current?M.current=!1:O.current&&O.current.click():p||B():C.current=!0)}return S.current[j]=f,g(B,"select"),g($,"handleClick"),g(q,"handleDragStart"),g(H,"handleMouseDown"),g(G,"handleMouseEnter"),g(z,"handleMouseLeave"),g(W,"handleMouseMove"),g(K,"handleFocus"),g(Q,"handleMouseUp"),(0,t.useEffect)((function(){if(_){var e=window.setTimeout((function(){C.current=!0}),400);return function(){window.clearTimeout(e)}}C.current=!1}),[_,C]),(0,t.useEffect)((function(){var e=ms(O.current);return e.addEventListener("mouseup",t),function(){e.removeEventListener("mouseup",t)};function t(){M.current=!1}}),[]),{data:{disabled:p},props:_p({id:Zp(j),tabIndex:-1},y,{ref:U,"data-disabled":p?"":void 0,"data-selected":V?"":void 0,"data-valuetext":D,onClick:xs(o,$),onDragStart:xs(a,q),onMouseDown:xs(s,H),onMouseEnter:xs(l,G),onMouseLeave:xs(u,z),onMouseMove:xs(c,W),onFocus:xs(h,K),onMouseUp:xs(d,Q)})}}function Xp(e){e.id;var n=e.onKeyDown,r=e.ref,i=Np(e,Dp),o=Gp("useDropdownItems"),a=o.dispatch,s=o.triggerRef,l=o.dropdownRef,u=o.selectCallbacks,c=o.dropdownId,d=o.state,f=d.isExpanded,p=d.triggerId,h=d.selectionIndex,m=d.typeaheadQuery,v=rh(),y=Ss(l,r);(0,t.useEffect)((function(){var e=Jp(v,m);m&&null!=e&&a({type:Up,payload:{index:e,dropdownRef:l}});var t=window.setTimeout((function(){return m&&a({type:Vp,payload:""})}),1e3);return function(){return window.clearTimeout(t)}}),[a,v,m,l]);var b=kp(v.length),E=kp(v[h]),T=kp(h);(0,t.useEffect)((function(){h>v.length-1?a({type:Up,payload:{index:v.length-1,dropdownRef:l}}):b!==v.length&&h>-1&&E&&T===h&&v[h]!==E&&a({type:Up,payload:{index:v.findIndex((function(e){return e.key===(null==E?void 0:E.key)})),dropdownRef:l}})}),[l,a,v,b,E,T,h]);var w=xs(g((function(e){var t=e.key;if(f)switch(t){case"Enter":case" ":var n=v.find((function(e){return e.index===h}));n&&!n.disabled&&(e.preventDefault(),n.isLink&&n.element?n.element.click():(eh(s.current),u.current[n.index]&&u.current[n.index](),a({type:Mp})));break;case"Escape":eh(s.current),a({type:Rp});break;case"Tab":e.preventDefault();break;default:if(ys(t)&&1===t.length){var r=m+t.toLowerCase();a({type:Vp,payload:r})}}}),"handleKeyDown"),Yd($p,{currentIndex:h,orientation:"vertical",rotate:!1,filter:g((function(e){return!e.disabled}),"filter"),callback:g((function(e){a({type:Up,payload:{index:e,dropdownRef:l}})}),"callback"),key:"index"}));return{data:{activeDescendant:Zp(h)||void 0,triggerId:p},props:_p({tabIndex:-1},i,{ref:y,id:c,onKeyDown:xs(n,w)})}}function Yp(e){var n=e.onBlur,r=e.portal,i=void 0===r||r,o=e.position,a=e.ref,s=Np(e,Lp),l=Gp("useDropdownPopover"),u=l.triggerRef,c=l.triggerClickedRef,d=l.dispatch,f=l.dropdownRef,p=l.popoverRef,h=l.state.isExpanded,m=Ss(p,a);return(0,t.useEffect)((function(){if(h){var e=ms(p.current);return g(t,"listener"),e.addEventListener("mousedown",t),function(){e.removeEventListener("mousedown",t)}}function t(e){c.current?c.current=!1:th(p.current,e.target)||d({type:Rp})}}),[c,u,d,f,p,h]),{data:{portal:i,position:o,targetRef:u,isExpanded:h},props:_p({ref:m,hidden:!h,onBlur:xs(n,(function(e){e.currentTarget.contains(e.relatedTarget)||d({type:Rp})}))},s)}}function Jp(e,t){if(void 0===t&&(t=""),!t)return null;var n=e.find((function(e){var n,r,i;return!e.disabled&&(null==(n=e.element)||null==(r=n.dataset)||null==(i=r.valuetext)?void 0:i.toLowerCase().startsWith(t))}));return n?e.indexOf(n):null}function Zp(e){var t=Gp("useItemId").dropdownId;return null!=e&&e>-1?lf("option-"+e,t):void 0}function eh(e){e&&e.focus()}function th(e,t){return!(!e||!e.contains(t))}function nh(e,t){switch(void 0===t&&(t={}),t.type){case Mp:case Rp:return _p({},e,{isExpanded:!1,selectionIndex:-1});case Fp:return _p({},e,{isExpanded:!0,selectionIndex:0});case Pp:return _p({},e,{isExpanded:!0,selectionIndex:t.payload.index});case jp:return _p({},e,{isExpanded:!0,selectionIndex:-1});case Up:var n=t.payload.dropdownRef,r=void 0===n?{current:null}:n;if(t.payload.index>=0&&t.payload.index!==e.selectionIndex){if(r.current){var i=ms(r.current);r.current!==(null==i?void 0:i.activeElement)&&r.current.focus()}return _p({},e,{selectionIndex:null!=t.payload.max?Math.min(Math.max(t.payload.index,0),t.payload.max):Math.max(t.payload.index,0)})}return e;case Ap:return _p({},e,{selectionIndex:-1});case Bp:return _p({},e,{triggerId:t.payload});case Vp:return void 0!==t.payload?_p({},e,{typeaheadQuery:t.payload}):e;default:return e}}function rh(){return Qd($p)}g(Kp,"useDropdownTrigger"),g(Qp,"useDropdownItem"),g(Xp,"useDropdownItems"),g(Yp,"useDropdownPopover"),g(Jp,"findItemFromTypeahead"),g(Zp,"useItemId"),g(eh,"focus"),g(th,"popoverContainsEventTarget"),g(nh,"reducer$1"),g(rh,"useDropdownDescendants");var ih={exports:{}},oh={},ah="function"==typeof Symbol&&Symbol.for,sh=ah?Symbol.for("react.element"):60103,lh=ah?Symbol.for("react.portal"):60106,uh=ah?Symbol.for("react.fragment"):60107,ch=ah?Symbol.for("react.strict_mode"):60108,dh=ah?Symbol.for("react.profiler"):60114,fh=ah?Symbol.for("react.provider"):60109,ph=ah?Symbol.for("react.context"):60110,hh=ah?Symbol.for("react.async_mode"):60111,mh=ah?Symbol.for("react.concurrent_mode"):60111,gh=ah?Symbol.for("react.forward_ref"):60112,vh=ah?Symbol.for("react.suspense"):60113,yh=ah?Symbol.for("react.suspense_list"):60120,bh=ah?Symbol.for("react.memo"):60115,Eh=ah?Symbol.for("react.lazy"):60116,Th=ah?Symbol.for("react.block"):60121,wh=ah?Symbol.for("react.fundamental"):60117,Ch=ah?Symbol.for("react.responder"):60118,Sh=ah?Symbol.for("react.scope"):60119;function xh(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case sh:switch(e=e.type){case hh:case mh:case uh:case dh:case ch:case vh:return e;default:switch(e=e&&e.$$typeof){case ph:case gh:case Eh:case bh:case fh:return e;default:return t}}case lh:return t}}}function kh(e){return xh(e)===mh}function Nh(){return Nh=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}g(xh,"z"),g(kh,"A"),oh.AsyncMode=hh,oh.ConcurrentMode=mh,oh.ContextConsumer=ph,oh.ContextProvider=fh,oh.Element=sh,oh.ForwardRef=gh,oh.Fragment=uh,oh.Lazy=Eh,oh.Memo=bh,oh.Portal=lh,oh.Profiler=dh,oh.StrictMode=ch,oh.Suspense=vh,oh.isAsyncMode=function(e){return kh(e)||xh(e)===hh},oh.isConcurrentMode=kh,oh.isContextConsumer=function(e){return xh(e)===ph},oh.isContextProvider=function(e){return xh(e)===fh},oh.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===sh},oh.isForwardRef=function(e){return xh(e)===gh},oh.isFragment=function(e){return xh(e)===uh},oh.isLazy=function(e){return xh(e)===Eh},oh.isMemo=function(e){return xh(e)===bh},oh.isPortal=function(e){return xh(e)===lh},oh.isProfiler=function(e){return xh(e)===dh},oh.isStrictMode=function(e){return xh(e)===ch},oh.isSuspense=function(e){return xh(e)===vh},oh.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===uh||e===mh||e===dh||e===ch||e===vh||e===yh||"object"==typeof e&&null!==e&&(e.$$typeof===Eh||e.$$typeof===bh||e.$$typeof===fh||e.$$typeof===ph||e.$$typeof===gh||e.$$typeof===wh||e.$$typeof===Ch||e.$$typeof===Sh||e.$$typeof===Th)},oh.typeOf=xh,ih.exports=oh,g(Nh,"_extends$2"),g(_h,"_objectWithoutPropertiesLoose$2");var Oh=["as","id","children"],Ih=["as"],Dh=["as"],Lh=["as"],Ah=["as"],Mh=["portal"],Rh=["as"],Fh=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?t.Fragment:r,o=e.id,a=e.children,s=_h(e,Oh),l=(0,t.useMemo)((function(){try{return ih.exports.isFragment((0,t.createElement)(i,null))}catch(e){return!1}}),[i])?{}:Nh({ref:n,id:o,"data-reach-menu":""},s);return(0,t.createElement)(i,l,(0,t.createElement)(Wp,{id:o,children:a}))})),Ph=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?"button":r,o=Kp(Nh({},_h(e,Ih),{ref:n})),a=o.data,s=a.isExpanded,l=a.controls,u=o.props;return(0,t.createElement)(i,Nh({"aria-expanded":!!s||void 0,"aria-haspopup":!0,"aria-controls":l},u,{"data-reach-menu-button":""}))})),jh=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?"div":r,o=Qp(Nh({},_h(e,Dh),{ref:n})),a=o.data.disabled,s=o.props;return(0,t.createElement)(i,Nh({role:"menuitem"},s,{"aria-disabled":a||void 0,"data-reach-menu-item":""}))})),Vh=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?"div":r,o=_h(e,Lh);return(0,t.createElement)(jh,Nh({},o,{ref:n,as:i}))})),Uh=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?"div":r,o=Xp(Nh({},_h(e,Ah),{ref:n})),a=o.data,s=a.activeDescendant,l=a.triggerId,u=o.props;return(0,t.createElement)(i,Nh({"aria-activedescendant":s,"aria-labelledby":l||void 0,role:"menu"},u,{"data-reach-menu-items":""}))})),Bh=(0,t.forwardRef)((function(e,n){var r=e.portal,i=void 0===r||r,o=_h(e,Mh);return(0,t.createElement)($h,{portal:i},(0,t.createElement)(Uh,Nh({},o,{ref:n,"data-reach-menu-list":""})))})),$h=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?"div":r,o=Yp(Nh({},_h(e,Rh),{ref:n})),a=o.data,s=a.portal,l=a.targetRef,u=a.position,c=o.props,d={"data-reach-menu-popover":""};return s?(0,t.createElement)(Rd,Nh({},c,d,{as:i,targetRef:l,position:u,unstable_skipInitialPortalRender:!0})):(0,t.createElement)(i,Nh({},c,d))}));const qh=(0,t.forwardRef)(((e,t)=>de(Ph,m(h({},e),{ref:t,className:b("graphiql-un-styled",e.className)}))));qh.displayName="MenuButton";const Hh=Xc(Fh,{Button:qh,Item:Vh,List:Bh});e.aK=Hh;const Gh=(0,t.forwardRef)(((e,t)=>de(hp,m(h({},e),{ref:t,className:b("graphiql-un-styled",e.className)}))));Gh.displayName="ListboxButton";const zh=Xc(fp,{Button:Gh,Input:dp,Option:Ep,Popover:yp});e.aL=zh;var Wh={};var Kh={Aacute:"Ã",aacute:"á",Abreve:"Ä‚",abreve:"ă",ac:"∾",acd:"∿",acE:"∾̳",Acirc:"Â",acirc:"â",acute:"´",Acy:"Ð",acy:"а",AElig:"Æ",aelig:"æ",af:"â¡",Afr:"ð”„",afr:"ð”ž",Agrave:"À",agrave:"à",alefsym:"ℵ",aleph:"ℵ",Alpha:"Α",alpha:"α",Amacr:"Ä€",amacr:"Ä",amalg:"⨿",amp:"&",AMP:"&",andand:"â©•",And:"â©“",and:"∧",andd:"â©œ",andslope:"⩘",andv:"â©š",ang:"∠",ange:"⦤",angle:"∠",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angmsd:"∡",angrt:"∟",angrtvb:"⊾",angrtvbd:"â¦",angsph:"∢",angst:"Ã…",angzarr:"â¼",Aogon:"Ä„",aogon:"Ä…",Aopf:"ð”¸",aopf:"ð•’",apacir:"⩯",ap:"≈",apE:"â©°",ape:"≊",apid:"≋",apos:"'",ApplyFunction:"â¡",approx:"≈",approxeq:"≊",Aring:"Ã…",aring:"Ã¥",Ascr:"ð’œ",ascr:"ð’¶",Assign:"≔",ast:"*",asymp:"≈",asympeq:"â‰",Atilde:"Ã",atilde:"ã",Auml:"Ä",auml:"ä",awconint:"∳",awint:"⨑",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"â‹",Backslash:"∖",Barv:"⫧",barvee:"⊽",barwed:"⌅",Barwed:"⌆",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",Bcy:"Б",bcy:"б",bdquo:"„",becaus:"∵",because:"∵",Because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",Bernoullis:"ℬ",Beta:"Î’",beta:"β",beth:"ℶ",between:"≬",Bfr:"ð”…",bfr:"ð”Ÿ",bigcap:"â‹‚",bigcirc:"â—¯",bigcup:"⋃",bigodot:"⨀",bigoplus:"â¨",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"â–½",bigtriangleup:"â–³",biguplus:"⨄",bigvee:"â‹",bigwedge:"â‹€",bkarow:"â¤",blacklozenge:"⧫",blacksquare:"â–ª",blacktriangle:"â–´",blacktriangledown:"â–¾",blacktriangleleft:"â—‚",blacktriangleright:"â–¸",blank:"â£",blk12:"â–’",blk14:"â–‘",blk34:"â–“",block:"â–ˆ",bne:"=⃥",bnequiv:"≡⃥",bNot:"â«­",bnot:"âŒ",Bopf:"ð”¹",bopf:"ð•“",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxbox:"⧉",boxdl:"â”",boxdL:"â••",boxDl:"â•–",boxDL:"â•—",boxdr:"┌",boxdR:"â•’",boxDr:"â•“",boxDR:"â•”",boxh:"─",boxH:"â•",boxhd:"┬",boxHd:"╤",boxhD:"â•¥",boxHD:"╦",boxhu:"â”´",boxHu:"╧",boxhU:"╨",boxHU:"â•©",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxul:"┘",boxuL:"â•›",boxUl:"â•œ",boxUL:"â•",boxur:"â””",boxuR:"╘",boxUr:"â•™",boxUR:"â•š",boxv:"│",boxV:"â•‘",boxvh:"┼",boxvH:"╪",boxVh:"â•«",boxVH:"╬",boxvl:"┤",boxvL:"â•¡",boxVl:"â•¢",boxVL:"â•£",boxvr:"├",boxvR:"â•ž",boxVr:"â•Ÿ",boxVR:"â• ",bprime:"‵",breve:"˘",Breve:"˘",brvbar:"¦",bscr:"ð’·",Bscr:"ℬ",bsemi:"â",bsim:"∽",bsime:"â‹",bsolb:"⧅",bsol:"\\",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpE:"⪮",bumpe:"â‰",Bumpeq:"≎",bumpeq:"â‰",Cacute:"Ć",cacute:"ć",capand:"â©„",capbrcup:"⩉",capcap:"â©‹",cap:"∩",Cap:"â‹’",capcup:"⩇",capdot:"â©€",CapitalDifferentialD:"â……",caps:"∩︀",caret:"â",caron:"ˇ",Cayleys:"â„­",ccaps:"â©",Ccaron:"ÄŒ",ccaron:"Ä",Ccedil:"Ç",ccedil:"ç",Ccirc:"Ĉ",ccirc:"ĉ",Cconint:"∰",ccups:"â©Œ",ccupssm:"â©",Cdot:"ÄŠ",cdot:"Ä‹",cedil:"¸",Cedilla:"¸",cemptyv:"⦲",cent:"¢",centerdot:"·",CenterDot:"·",cfr:"ð” ",Cfr:"â„­",CHcy:"Ч",chcy:"ч",check:"✓",checkmark:"✓",Chi:"Χ",chi:"χ",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledast:"⊛",circledcirc:"⊚",circleddash:"âŠ",CircleDot:"⊙",circledR:"®",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cir:"â—‹",cirE:"⧃",cire:"≗",cirfnint:"â¨",cirmid:"⫯",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"â€",CloseCurlyQuote:"’",clubs:"♣",clubsuit:"♣",colon:":",Colon:"∷",Colone:"â©´",colone:"≔",coloneq:"≔",comma:",",commat:"@",comp:"âˆ",compfn:"∘",complement:"âˆ",complexes:"â„‚",cong:"≅",congdot:"â©­",Congruent:"≡",conint:"∮",Conint:"∯",ContourIntegral:"∮",copf:"ð•”",Copf:"â„‚",coprod:"âˆ",Coproduct:"âˆ",copy:"©",COPY:"©",copysr:"â„—",CounterClockwiseContourIntegral:"∳",crarr:"↵",cross:"✗",Cross:"⨯",Cscr:"ð’ž",cscr:"ð’¸",csub:"â«",csube:"â«‘",csup:"â«",csupe:"â«’",ctdot:"⋯",cudarrl:"⤸",cudarrr:"⤵",cuepr:"â‹ž",cuesc:"â‹Ÿ",cularr:"↶",cularrp:"⤽",cupbrcap:"⩈",cupcap:"⩆",CupCap:"â‰",cup:"∪",Cup:"â‹“",cupcup:"â©Š",cupdot:"âŠ",cupor:"â©…",cups:"∪︀",curarr:"↷",curarrm:"⤼",curlyeqprec:"â‹ž",curlyeqsucc:"â‹Ÿ",curlyvee:"â‹Ž",curlywedge:"â‹",curren:"¤",curvearrowleft:"↶",curvearrowright:"↷",cuvee:"â‹Ž",cuwed:"â‹",cwconint:"∲",cwint:"∱",cylcty:"⌭",dagger:"†",Dagger:"‡",daleth:"ℸ",darr:"↓",Darr:"↡",dArr:"⇓",dash:"â€",Dashv:"⫤",dashv:"⊣",dbkarow:"â¤",dblac:"Ë",Dcaron:"ÄŽ",dcaron:"Ä",Dcy:"Д",dcy:"д",ddagger:"‡",ddarr:"⇊",DD:"â……",dd:"â…†",DDotrahd:"⤑",ddotseq:"â©·",deg:"°",Del:"∇",Delta:"Δ",delta:"δ",demptyv:"⦱",dfisht:"⥿",Dfr:"ð”‡",dfr:"ð”¡",dHar:"⥥",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"Ë™",DiacriticalDoubleAcute:"Ë",DiacriticalGrave:"`",DiacriticalTilde:"Ëœ",diam:"â‹„",diamond:"â‹„",Diamond:"â‹„",diamondsuit:"♦",diams:"♦",die:"¨",DifferentialD:"â…†",digamma:"Ï",disin:"⋲",div:"÷",divide:"÷",divideontimes:"⋇",divonx:"⋇",DJcy:"Ђ",djcy:"Ñ’",dlcorn:"⌞",dlcrop:"âŒ",dollar:"$",Dopf:"ð”»",dopf:"ð••",Dot:"¨",dot:"Ë™",DotDot:"⃜",doteq:"â‰",doteqdot:"≑",DotEqual:"â‰",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"â‡",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"⫤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"⟺",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"∥",DownArrowBar:"⤓",downarrow:"↓",DownArrow:"↓",Downarrow:"⇓",DownArrowUpArrow:"⇵",DownBreve:"Ì‘",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"â¥",DownLeftTeeVector:"⥞",DownLeftVectorBar:"⥖",DownLeftVector:"↽",DownRightTeeVector:"⥟",DownRightVectorBar:"⥗",DownRightVector:"â‡",DownTeeArrow:"↧",DownTee:"⊤",drbkarow:"â¤",drcorn:"⌟",drcrop:"⌌",Dscr:"ð’Ÿ",dscr:"ð’¹",DScy:"Ð…",dscy:"Ñ•",dsol:"⧶",Dstrok:"Ä",dstrok:"Ä‘",dtdot:"⋱",dtri:"â–¿",dtrif:"â–¾",duarr:"⇵",duhar:"⥯",dwangle:"⦦",DZcy:"Ð",dzcy:"ÑŸ",dzigrarr:"⟿",Eacute:"É",eacute:"é",easter:"â©®",Ecaron:"Äš",ecaron:"Ä›",Ecirc:"Ê",ecirc:"ê",ecir:"≖",ecolon:"≕",Ecy:"Э",ecy:"Ñ",eDDot:"â©·",Edot:"Ä–",edot:"Ä—",eDot:"≑",ee:"â…‡",efDot:"≒",Efr:"ð”ˆ",efr:"ð”¢",eg:"⪚",Egrave:"È",egrave:"è",egs:"⪖",egsdot:"⪘",el:"⪙",Element:"∈",elinters:"â§",ell:"â„“",els:"⪕",elsdot:"⪗",Emacr:"Ä’",emacr:"Ä“",empty:"∅",emptyset:"∅",EmptySmallSquare:"â—»",emptyv:"∅",EmptyVerySmallSquare:"â–«",emsp13:" ",emsp14:" ",emsp:" ",ENG:"ÅŠ",eng:"Å‹",ensp:" ",Eogon:"Ę",eogon:"Ä™",Eopf:"ð”¼",eopf:"ð•–",epar:"â‹•",eparsl:"⧣",eplus:"⩱",epsi:"ε",Epsilon:"Ε",epsilon:"ε",epsiv:"ϵ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"⪖",eqslantless:"⪕",Equal:"⩵",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⩸",eqvparsl:"⧥",erarr:"⥱",erDot:"≓",escr:"ℯ",Escr:"â„°",esdot:"â‰",Esim:"⩳",esim:"≂",Eta:"Η",eta:"η",ETH:"Ã",eth:"ð",Euml:"Ë",euml:"ë",euro:"€",excl:"!",exist:"∃",Exists:"∃",expectation:"â„°",exponentiale:"â…‡",ExponentialE:"â…‡",fallingdotseq:"≒",Fcy:"Ф",fcy:"Ñ„",female:"♀",ffilig:"ffi",fflig:"ff",ffllig:"ffl",Ffr:"ð”‰",ffr:"ð”£",filig:"ï¬",FilledSmallSquare:"â—¼",FilledVerySmallSquare:"â–ª",fjlig:"fj",flat:"â™­",fllig:"fl",fltns:"â–±",fnof:"Æ’",Fopf:"ð”½",fopf:"ð•—",forall:"∀",ForAll:"∀",fork:"â‹”",forkv:"â«™",Fouriertrf:"ℱ",fpartint:"â¨",frac12:"½",frac13:"â…“",frac14:"¼",frac15:"â…•",frac16:"â…™",frac18:"â…›",frac23:"â…”",frac25:"â…–",frac34:"¾",frac35:"â…—",frac38:"â…œ",frac45:"â…˜",frac56:"â…š",frac58:"â…",frac78:"â…ž",frasl:"â„",frown:"⌢",fscr:"ð’»",Fscr:"ℱ",gacute:"ǵ",Gamma:"Γ",gamma:"γ",Gammad:"Ïœ",gammad:"Ï",gap:"⪆",Gbreve:"Äž",gbreve:"ÄŸ",Gcedil:"Ä¢",Gcirc:"Äœ",gcirc:"Ä",Gcy:"Г",gcy:"г",Gdot:"Ä ",gdot:"Ä¡",ge:"≥",gE:"≧",gEl:"⪌",gel:"â‹›",geq:"≥",geqq:"≧",geqslant:"⩾",gescc:"⪩",ges:"⩾",gesdot:"⪀",gesdoto:"⪂",gesdotol:"⪄",gesl:"⋛︀",gesles:"⪔",Gfr:"ð”Š",gfr:"ð”¤",gg:"≫",Gg:"â‹™",ggg:"â‹™",gimel:"â„·",GJcy:"Ѓ",gjcy:"Ñ“",gla:"⪥",gl:"≷",glE:"⪒",glj:"⪤",gnap:"⪊",gnapprox:"⪊",gne:"⪈",gnE:"≩",gneq:"⪈",gneqq:"≩",gnsim:"⋧",Gopf:"ð”¾",gopf:"ð•˜",grave:"`",GreaterEqual:"≥",GreaterEqualLess:"â‹›",GreaterFullEqual:"≧",GreaterGreater:"⪢",GreaterLess:"≷",GreaterSlantEqual:"⩾",GreaterTilde:"≳",Gscr:"ð’¢",gscr:"â„Š",gsim:"≳",gsime:"⪎",gsiml:"âª",gtcc:"⪧",gtcir:"⩺",gt:">",GT:">",Gt:"≫",gtdot:"â‹—",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"â‹—",gtreqless:"â‹›",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",Hacek:"ˇ",hairsp:" ",half:"½",hamilt:"â„‹",HARDcy:"Ъ",hardcy:"ÑŠ",harrcir:"⥈",harr:"↔",hArr:"⇔",harrw:"↭",Hat:"^",hbar:"â„",Hcirc:"Ĥ",hcirc:"Ä¥",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",hfr:"ð”¥",Hfr:"â„Œ",HilbertSpace:"â„‹",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",hopf:"ð•™",Hopf:"â„",horbar:"―",HorizontalLine:"─",hscr:"ð’½",Hscr:"â„‹",hslash:"â„",Hstrok:"Ħ",hstrok:"ħ",HumpDownHump:"≎",HumpEqual:"â‰",hybull:"âƒ",hyphen:"â€",Iacute:"Ã",iacute:"í",ic:"â£",Icirc:"ÃŽ",icirc:"î",Icy:"И",icy:"и",Idot:"Ä°",IEcy:"Е",iecy:"е",iexcl:"¡",iff:"⇔",ifr:"ð”¦",Ifr:"â„‘",Igrave:"ÃŒ",igrave:"ì",ii:"â…ˆ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"â„©",IJlig:"IJ",ijlig:"ij",Imacr:"Ī",imacr:"Ä«",image:"â„‘",ImaginaryI:"â…ˆ",imagline:"â„",imagpart:"â„‘",imath:"ı",Im:"â„‘",imof:"⊷",imped:"Ƶ",Implies:"⇒",incare:"â„…",in:"∈",infin:"∞",infintie:"â§",inodot:"ı",intcal:"⊺",int:"∫",Int:"∬",integers:"ℤ",Integral:"∫",intercal:"⊺",Intersection:"â‹‚",intlarhk:"⨗",intprod:"⨼",InvisibleComma:"â£",InvisibleTimes:"â¢",IOcy:"Ð",iocy:"Ñ‘",Iogon:"Ä®",iogon:"į",Iopf:"ð•€",iopf:"ð•š",Iota:"Ι",iota:"ι",iprod:"⨼",iquest:"¿",iscr:"ð’¾",Iscr:"â„",isin:"∈",isindot:"⋵",isinE:"⋹",isins:"â‹´",isinsv:"⋳",isinv:"∈",it:"â¢",Itilde:"Ĩ",itilde:"Ä©",Iukcy:"І",iukcy:"Ñ–",Iuml:"Ã",iuml:"ï",Jcirc:"Ä´",jcirc:"ĵ",Jcy:"Й",jcy:"й",Jfr:"ð”",jfr:"ð”§",jmath:"È·",Jopf:"ð•",jopf:"ð•›",Jscr:"ð’¥",jscr:"ð’¿",Jsercy:"Ј",jsercy:"ј",Jukcy:"Є",jukcy:"Ñ”",Kappa:"Κ",kappa:"κ",kappav:"Ï°",Kcedil:"Ķ",kcedil:"Ä·",Kcy:"К",kcy:"к",Kfr:"ð”Ž",kfr:"ð”¨",kgreen:"ĸ",KHcy:"Ð¥",khcy:"Ñ…",KJcy:"ÐŒ",kjcy:"Ñœ",Kopf:"ð•‚",kopf:"ð•œ",Kscr:"ð’¦",kscr:"ð“€",lAarr:"⇚",Lacute:"Ĺ",lacute:"ĺ",laemptyv:"⦴",lagran:"â„’",Lambda:"Λ",lambda:"λ",lang:"⟨",Lang:"⟪",langd:"⦑",langle:"⟨",lap:"⪅",Laplacetrf:"â„’",laquo:"«",larrb:"⇤",larrbfs:"⤟",larr:"â†",Larr:"↞",lArr:"â‡",larrfs:"â¤",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",latail:"⤙",lAtail:"⤛",lat:"⪫",late:"⪭",lates:"⪭︀",lbarr:"⤌",lBarr:"⤎",lbbrk:"â²",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"â¦",lbrkslu:"â¦",Lcaron:"Ľ",lcaron:"ľ",Lcedil:"Ä»",lcedil:"ļ",lceil:"⌈",lcub:"{",Lcy:"Л",lcy:"л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",le:"≤",lE:"≦",LeftAngleBracket:"⟨",LeftArrowBar:"⇤",leftarrow:"â†",LeftArrow:"â†",Leftarrow:"â‡",LeftArrowRightArrow:"⇆",leftarrowtail:"↢",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVectorBar:"⥙",LeftDownVector:"⇃",LeftFloor:"⌊",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",leftrightarrow:"↔",LeftRightArrow:"↔",Leftrightarrow:"⇔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"⥎",LeftTeeArrow:"↤",LeftTee:"⊣",LeftTeeVector:"⥚",leftthreetimes:"â‹‹",LeftTriangleBar:"â§",LeftTriangle:"⊲",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVectorBar:"⥘",LeftUpVector:"↿",LeftVectorBar:"⥒",LeftVector:"↼",lEg:"⪋",leg:"â‹š",leq:"≤",leqq:"≦",leqslant:"⩽",lescc:"⪨",les:"⩽",lesdot:"â©¿",lesdoto:"âª",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"â‹–",lesseqgtr:"â‹š",lesseqqgtr:"⪋",LessEqualGreater:"â‹š",LessFullEqual:"≦",LessGreater:"≶",lessgtr:"≶",LessLess:"⪡",lesssim:"≲",LessSlantEqual:"⩽",LessTilde:"≲",lfisht:"⥼",lfloor:"⌊",Lfr:"ð”",lfr:"ð”©",lg:"≶",lgE:"⪑",lHar:"⥢",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"â–„",LJcy:"Љ",ljcy:"Ñ™",llarr:"⇇",ll:"≪",Ll:"⋘",llcorner:"⌞",Lleftarrow:"⇚",llhard:"⥫",lltri:"â—º",Lmidot:"Ä¿",lmidot:"Å€",lmoustache:"⎰",lmoust:"⎰",lnap:"⪉",lnapprox:"⪉",lne:"⪇",lnE:"≨",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",longleftarrow:"⟵",LongLeftArrow:"⟵",Longleftarrow:"⟸",longleftrightarrow:"⟷",LongLeftRightArrow:"⟷",Longleftrightarrow:"⟺",longmapsto:"⟼",longrightarrow:"⟶",LongRightArrow:"⟶",Longrightarrow:"⟹",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",Lopf:"ð•ƒ",lopf:"ð•",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"â—Š",lozenge:"â—Š",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"‎",lrtri:"⊿",lsaquo:"‹",lscr:"ð“",Lscr:"â„’",lsh:"↰",Lsh:"↰",lsim:"≲",lsime:"âª",lsimg:"âª",lsqb:"[",lsquo:"‘",lsquor:"‚",Lstrok:"Å",lstrok:"Å‚",ltcc:"⪦",ltcir:"⩹",lt:"<",LT:"<",Lt:"≪",ltdot:"â‹–",lthree:"â‹‹",ltimes:"⋉",ltlarr:"⥶",ltquest:"â©»",ltri:"â—ƒ",ltrie:"⊴",ltrif:"â—‚",ltrPar:"⦖",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",macr:"¯",male:"♂",malt:"✠",maltese:"✠",Map:"⤅",map:"↦",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"â–®",mcomma:"⨩",Mcy:"Ðœ",mcy:"м",mdash:"—",mDDot:"∺",measuredangle:"∡",MediumSpace:"âŸ",Mellintrf:"ℳ",Mfr:"ð”",mfr:"ð”ª",mho:"℧",micro:"µ",midast:"*",midcir:"â«°",mid:"∣",middot:"·",minusb:"⊟",minus:"−",minusd:"∸",minusdu:"⨪",MinusPlus:"∓",mlcp:"â«›",mldr:"…",mnplus:"∓",models:"⊧",Mopf:"ð•„",mopf:"ð•ž",mp:"∓",mscr:"ð“‚",Mscr:"ℳ",mstpos:"∾",Mu:"Îœ",mu:"μ",multimap:"⊸",mumap:"⊸",nabla:"∇",Nacute:"Ń",nacute:"Å„",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natural:"â™®",naturals:"â„•",natur:"â™®",nbsp:" ",nbump:"≎̸",nbumpe:"â‰Ì¸",ncap:"⩃",Ncaron:"Ň",ncaron:"ň",Ncedil:"Å…",ncedil:"ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"â©‚",Ncy:"Ð",ncy:"н",ndash:"–",nearhk:"⤤",nearr:"↗",neArr:"⇗",nearrow:"↗",ne:"≠",nedot:"â‰Ì¸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"≢",nesear:"⤨",nesim:"≂̸",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",nexist:"∄",nexists:"∄",Nfr:"ð”‘",nfr:"ð”«",ngE:"≧̸",nge:"≱",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",nGg:"⋙̸",ngsim:"≵",nGt:"≫⃒",ngt:"≯",ngtr:"≯",nGtv:"≫̸",nharr:"↮",nhArr:"⇎",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",NJcy:"Њ",njcy:"Ñš",nlarr:"↚",nlArr:"â‡",nldr:"‥",nlE:"≦̸",nle:"≰",nleftarrow:"↚",nLeftarrow:"â‡",nleftrightarrow:"↮",nLeftrightarrow:"⇎",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nLl:"⋘̸",nlsim:"≴",nLt:"≪⃒",nlt:"≮",nltri:"⋪",nltrie:"⋬",nLtv:"≪̸",nmid:"∤",NoBreak:"â ",NonBreakingSpace:" ",nopf:"ð•Ÿ",Nopf:"â„•",Not:"⫬",not:"¬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"â‰Ì¸",notin:"∉",notindot:"⋵̸",notinE:"⋹̸",notinva:"∉",notinvb:"â‹·",notinvc:"⋶",NotLeftTriangleBar:"â§Ì¸",NotLeftTriangle:"⋪",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"â‹ ",NotReverseElement:"∌",NotRightTriangleBar:"â§Ì¸",NotRightTriangle:"â‹«",NotRightTriangleEqual:"â‹­",NotSquareSubset:"âŠÌ¸",NotSquareSubsetEqual:"â‹¢",NotSquareSuperset:"âŠÌ¸",NotSquareSupersetEqual:"â‹£",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"âŠ",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"â‹¡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"â‰",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",nparallel:"∦",npar:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"â‹ ",nprec:"⊀",npreceq:"⪯̸",npre:"⪯̸",nrarrc:"⤳̸",nrarr:"↛",nrArr:"â‡",nrarrw:"â†Ì¸",nrightarrow:"↛",nRightarrow:"â‡",nrtri:"â‹«",nrtrie:"â‹­",nsc:"âŠ",nsccue:"â‹¡",nsce:"⪰̸",Nscr:"ð’©",nscr:"ð“ƒ",nshortmid:"∤",nshortparallel:"∦",nsim:"â‰",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"â‹¢",nsqsupe:"â‹£",nsub:"⊄",nsubE:"⫅̸",nsube:"⊈",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"âŠ",nsucceq:"⪰̸",nsup:"⊅",nsupE:"⫆̸",nsupe:"⊉",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",Ntilde:"Ñ",ntilde:"ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"â‹«",ntrianglerighteq:"â‹­",Nu:"Î",nu:"ν",num:"#",numero:"â„–",numsp:" ",nvap:"â‰âƒ’",nvdash:"⊬",nvDash:"⊭",nVdash:"⊮",nVDash:"⊯",nvge:"≥⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwarhk:"⤣",nwarr:"↖",nwArr:"⇖",nwarrow:"↖",nwnear:"⤧",Oacute:"Ó",oacute:"ó",oast:"⊛",Ocirc:"Ô",ocirc:"ô",ocir:"⊚",Ocy:"О",ocy:"о",odash:"âŠ",Odblac:"Å",odblac:"Å‘",odiv:"⨸",odot:"⊙",odsold:"⦼",OElig:"Å’",oelig:"Å“",ofcir:"⦿",Ofr:"ð”’",ofr:"ð”¬",ogon:"Ë›",Ograve:"Ã’",ograve:"ò",ogt:"â§",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",Omacr:"ÅŒ",omacr:"Å",Omega:"Ω",omega:"ω",Omicron:"Ο",omicron:"ο",omid:"⦶",ominus:"⊖",Oopf:"ð•†",oopf:"ð• ",opar:"⦷",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"⦹",oplus:"⊕",orarr:"↻",Or:"â©”",or:"∨",ord:"â©",order:"â„´",orderof:"â„´",ordf:"ª",ordm:"º",origof:"⊶",oror:"â©–",orslope:"â©—",orv:"â©›",oS:"Ⓢ",Oscr:"ð’ª",oscr:"â„´",Oslash:"Ø",oslash:"ø",osol:"⊘",Otilde:"Õ",otilde:"õ",otimesas:"⨶",Otimes:"⨷",otimes:"⊗",Ouml:"Ö",ouml:"ö",ovbar:"⌽",OverBar:"‾",OverBrace:"âž",OverBracket:"⎴",OverParenthesis:"âœ",para:"¶",parallel:"∥",par:"∥",parsim:"⫳",parsl:"⫽",part:"∂",PartialD:"∂",Pcy:"П",pcy:"п",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",Pfr:"ð”“",pfr:"ð”­",Phi:"Φ",phi:"φ",phiv:"Ï•",phmmat:"ℳ",phone:"☎",Pi:"Π",pi:"Ï€",pitchfork:"â‹”",piv:"Ï–",planck:"â„",planckh:"â„Ž",plankv:"â„",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plus:"+",plusdo:"∔",plusdu:"⨥",pluse:"⩲",PlusMinus:"±",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",Poincareplane:"â„Œ",pointint:"⨕",popf:"ð•¡",Popf:"â„™",pound:"£",prap:"⪷",Pr:"⪻",pr:"≺",prcue:"≼",precapprox:"⪷",prec:"≺",preccurlyeq:"≼",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",pre:"⪯",prE:"⪳",precsim:"≾",prime:"′",Prime:"″",primes:"â„™",prnap:"⪹",prnE:"⪵",prnsim:"⋨",prod:"âˆ",Product:"âˆ",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"âˆ",Proportional:"âˆ",Proportion:"∷",propto:"âˆ",prsim:"≾",prurel:"⊰",Pscr:"ð’«",pscr:"ð“…",Psi:"Ψ",psi:"ψ",puncsp:" ",Qfr:"ð””",qfr:"ð”®",qint:"⨌",qopf:"ð•¢",Qopf:"â„š",qprime:"â—",Qscr:"ð’¬",qscr:"ð“†",quaternions:"â„",quatint:"⨖",quest:"?",questeq:"≟",quot:'"',QUOT:'"',rAarr:"⇛",race:"∽̱",Racute:"Å”",racute:"Å•",radic:"√",raemptyv:"⦳",rang:"⟩",Rang:"⟫",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarr:"→",Rarr:"↠",rArr:"⇒",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",Rarrtl:"⤖",rarrtl:"↣",rarrw:"â†",ratail:"⤚",rAtail:"⤜",ratio:"∶",rationals:"â„š",rbarr:"â¤",rBarr:"â¤",RBarr:"â¤",rbbrk:"â³",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"â¦",Rcaron:"Ř",rcaron:"Å™",Rcedil:"Å–",rcedil:"Å—",rceil:"⌉",rcub:"}",Rcy:"Р",rcy:"Ñ€",rdca:"⤷",rdldhar:"⥩",rdquo:"â€",rdquor:"â€",rdsh:"↳",real:"â„œ",realine:"â„›",realpart:"â„œ",reals:"â„",Re:"â„œ",rect:"â–­",reg:"®",REG:"®",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",rfisht:"⥽",rfloor:"⌋",rfr:"ð”¯",Rfr:"â„œ",rHar:"⥤",rhard:"â‡",rharu:"⇀",rharul:"⥬",Rho:"Ρ",rho:"Ï",rhov:"ϱ",RightAngleBracket:"⟩",RightArrowBar:"⇥",rightarrow:"→",RightArrow:"→",Rightarrow:"⇒",RightArrowLeftArrow:"⇄",rightarrowtail:"↣",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"â¥",RightDownVectorBar:"⥕",RightDownVector:"⇂",RightFloor:"⌋",rightharpoondown:"â‡",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"â†",RightTeeArrow:"↦",RightTee:"⊢",RightTeeVector:"⥛",rightthreetimes:"â‹Œ",RightTriangleBar:"â§",RightTriangle:"⊳",RightTriangleEqual:"⊵",RightUpDownVector:"â¥",RightUpTeeVector:"⥜",RightUpVectorBar:"⥔",RightUpVector:"↾",RightVectorBar:"⥓",RightVector:"⇀",ring:"Ëš",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"â€",rmoustache:"⎱",rmoust:"⎱",rnmid:"â«®",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",ropf:"ð•£",Ropf:"â„",roplus:"⨮",rotimes:"⨵",RoundImplies:"⥰",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"›",rscr:"ð“‡",Rscr:"â„›",rsh:"↱",Rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"â‹Œ",rtimes:"â‹Š",rtri:"â–¹",rtrie:"⊵",rtrif:"â–¸",rtriltri:"⧎",RuleDelayed:"⧴",ruluhar:"⥨",rx:"â„ž",Sacute:"Åš",sacute:"Å›",sbquo:"‚",scap:"⪸",Scaron:"Å ",scaron:"Å¡",Sc:"⪼",sc:"≻",sccue:"≽",sce:"⪰",scE:"⪴",Scedil:"Åž",scedil:"ÅŸ",Scirc:"Åœ",scirc:"Å",scnap:"⪺",scnE:"⪶",scnsim:"â‹©",scpolint:"⨓",scsim:"≿",Scy:"С",scy:"Ñ",sdotb:"⊡",sdot:"â‹…",sdote:"⩦",searhk:"⤥",searr:"↘",seArr:"⇘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",Sfr:"ð”–",sfr:"ð”°",sfrown:"⌢",sharp:"♯",SHCHcy:"Щ",shchcy:"щ",SHcy:"Ш",shcy:"ш",ShortDownArrow:"↓",ShortLeftArrow:"â†",shortmid:"∣",shortparallel:"∥",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",Sigma:"Σ",sigma:"σ",sigmaf:"Ï‚",sigmav:"Ï‚",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"âª",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"â†",SmallCircle:"∘",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",SOFTcy:"Ь",softcy:"ÑŒ",solbar:"⌿",solb:"⧄",sol:"/",Sopf:"ð•Š",sopf:"ð•¤",spades:"â™ ",spadesuit:"â™ ",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",Sqrt:"√",sqsub:"âŠ",sqsube:"⊑",sqsubset:"âŠ",sqsubseteq:"⊑",sqsup:"âŠ",sqsupe:"⊒",sqsupset:"âŠ",sqsupseteq:"⊒",square:"â–¡",Square:"â–¡",SquareIntersection:"⊓",SquareSubset:"âŠ",SquareSubsetEqual:"⊑",SquareSuperset:"âŠ",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"â–ª",squ:"â–¡",squf:"â–ª",srarr:"→",Sscr:"ð’®",sscr:"ð“ˆ",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",Star:"⋆",star:"☆",starf:"★",straightepsilon:"ϵ",straightphi:"Ï•",strns:"¯",sub:"⊂",Sub:"â‹",subdot:"⪽",subE:"â«…",sube:"⊆",subedot:"⫃",submult:"â«",subnE:"â«‹",subne:"⊊",subplus:"⪿",subrarr:"⥹",subset:"⊂",Subset:"â‹",subseteq:"⊆",subseteqq:"â«…",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"â«‹",subsim:"⫇",subsub:"â«•",subsup:"â«“",succapprox:"⪸",succ:"≻",succcurlyeq:"≽",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"â‹©",succsim:"≿",SuchThat:"∋",sum:"∑",Sum:"∑",sung:"♪",sup1:"¹",sup2:"²",sup3:"³",sup:"⊃",Sup:"â‹‘",supdot:"⪾",supdsub:"⫘",supE:"⫆",supe:"⊇",supedot:"â«„",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"â«—",suplarr:"⥻",supmult:"â«‚",supnE:"â«Œ",supne:"⊋",supplus:"â«€",supset:"⊃",Supset:"â‹‘",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"â«Œ",supsim:"⫈",supsub:"â«”",supsup:"â«–",swarhk:"⤦",swarr:"↙",swArr:"⇙",swarrow:"↙",swnwar:"⤪",szlig:"ß",Tab:"\t",target:"⌖",Tau:"Τ",tau:"Ï„",tbrk:"⎴",Tcaron:"Ť",tcaron:"Å¥",Tcedil:"Å¢",tcedil:"Å£",Tcy:"Т",tcy:"Ñ‚",tdot:"⃛",telrec:"⌕",Tfr:"ð”—",tfr:"ð”±",there4:"∴",therefore:"∴",Therefore:"∴",Theta:"Θ",theta:"θ",thetasym:"Ï‘",thetav:"Ï‘",thickapprox:"≈",thicksim:"∼",ThickSpace:"âŸâ€Š",ThinSpace:" ",thinsp:" ",thkap:"≈",thksim:"∼",THORN:"Þ",thorn:"þ",tilde:"Ëœ",Tilde:"∼",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",timesbar:"⨱",timesb:"⊠",times:"×",timesd:"⨰",tint:"∭",toea:"⤨",topbot:"⌶",topcir:"⫱",top:"⊤",Topf:"ð•‹",topf:"ð•¥",topfork:"â«š",tosa:"⤩",tprime:"‴",trade:"â„¢",TRADE:"â„¢",triangle:"â–µ",triangledown:"â–¿",triangleleft:"â—ƒ",trianglelefteq:"⊴",triangleq:"≜",triangleright:"â–¹",trianglerighteq:"⊵",tridot:"â—¬",trie:"≜",triminus:"⨺",TripleDot:"⃛",triplus:"⨹",trisb:"â§",tritime:"⨻",trpezium:"â¢",Tscr:"ð’¯",tscr:"ð“‰",TScy:"Ц",tscy:"ц",TSHcy:"Ћ",tshcy:"Ñ›",Tstrok:"Ŧ",tstrok:"ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",Uacute:"Ú",uacute:"ú",uarr:"↑",Uarr:"↟",uArr:"⇑",Uarrocir:"⥉",Ubrcy:"ÐŽ",ubrcy:"Ñž",Ubreve:"Ŭ",ubreve:"Å­",Ucirc:"Û",ucirc:"û",Ucy:"У",ucy:"у",udarr:"⇅",Udblac:"Å°",udblac:"ű",udhar:"⥮",ufisht:"⥾",Ufr:"ð”˜",ufr:"ð”²",Ugrave:"Ù",ugrave:"ù",uHar:"⥣",uharl:"↿",uharr:"↾",uhblk:"â–€",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"âŒ",ultri:"â—¸",Umacr:"Ū",umacr:"Å«",uml:"¨",UnderBar:"_",UnderBrace:"âŸ",UnderBracket:"⎵",UnderParenthesis:"â",Union:"⋃",UnionPlus:"⊎",Uogon:"Ų",uogon:"ų",Uopf:"ð•Œ",uopf:"ð•¦",UpArrowBar:"⤒",uparrow:"↑",UpArrow:"↑",Uparrow:"⇑",UpArrowDownArrow:"⇅",updownarrow:"↕",UpDownArrow:"↕",Updownarrow:"⇕",UpEquilibrium:"⥮",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",upsi:"Ï…",Upsi:"Ï’",upsih:"Ï’",Upsilon:"Î¥",upsilon:"Ï…",UpTeeArrow:"↥",UpTee:"⊥",upuparrows:"⇈",urcorn:"âŒ",urcorner:"âŒ",urcrop:"⌎",Uring:"Å®",uring:"ů",urtri:"â—¹",Uscr:"ð’°",uscr:"ð“Š",utdot:"â‹°",Utilde:"Ũ",utilde:"Å©",utri:"â–µ",utrif:"â–´",uuarr:"⇈",Uuml:"Ãœ",uuml:"ü",uwangle:"⦧",vangrt:"⦜",varepsilon:"ϵ",varkappa:"Ï°",varnothing:"∅",varphi:"Ï•",varpi:"Ï–",varpropto:"âˆ",varr:"↕",vArr:"⇕",varrho:"ϱ",varsigma:"Ï‚",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"Ï‘",vartriangleleft:"⊲",vartriangleright:"⊳",vBar:"⫨",Vbar:"â««",vBarv:"â«©",Vcy:"Ð’",vcy:"в",vdash:"⊢",vDash:"⊨",Vdash:"⊩",VDash:"⊫",Vdashl:"⫦",veebar:"⊻",vee:"∨",Vee:"â‹",veeeq:"≚",vellip:"â‹®",verbar:"|",Verbar:"‖",vert:"|",Vert:"‖",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"â˜",VerticalTilde:"≀",VeryThinSpace:" ",Vfr:"ð”™",vfr:"ð”³",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",Vopf:"ð•",vopf:"ð•§",vprop:"âˆ",vrtri:"⊳",Vscr:"ð’±",vscr:"ð“‹",vsubnE:"⫋︀",vsubne:"⊊︀",vsupnE:"⫌︀",vsupne:"⊋︀",Vvdash:"⊪",vzigzag:"⦚",Wcirc:"Å´",wcirc:"ŵ",wedbar:"â©Ÿ",wedge:"∧",Wedge:"â‹€",wedgeq:"≙",weierp:"℘",Wfr:"ð”š",wfr:"ð”´",Wopf:"ð•Ž",wopf:"ð•¨",wp:"℘",wr:"≀",wreath:"≀",Wscr:"ð’²",wscr:"ð“Œ",xcap:"â‹‚",xcirc:"â—¯",xcup:"⋃",xdtri:"â–½",Xfr:"ð”›",xfr:"ð”µ",xharr:"⟷",xhArr:"⟺",Xi:"Ξ",xi:"ξ",xlarr:"⟵",xlArr:"⟸",xmap:"⟼",xnis:"â‹»",xodot:"⨀",Xopf:"ð•",xopf:"ð•©",xoplus:"â¨",xotime:"⨂",xrarr:"⟶",xrArr:"⟹",Xscr:"ð’³",xscr:"ð“",xsqcup:"⨆",xuplus:"⨄",xutri:"â–³",xvee:"â‹",xwedge:"â‹€",Yacute:"Ã",yacute:"ý",YAcy:"Я",yacy:"Ñ",Ycirc:"Ŷ",ycirc:"Å·",Ycy:"Ы",ycy:"Ñ‹",yen:"Â¥",Yfr:"ð”œ",yfr:"ð”¶",YIcy:"Ї",yicy:"Ñ—",Yopf:"ð•",yopf:"ð•ª",Yscr:"ð’´",yscr:"ð“Ž",YUcy:"Ю",yucy:"ÑŽ",yuml:"ÿ",Yuml:"Ÿ",Zacute:"Ź",zacute:"ź",Zcaron:"Ž",zcaron:"ž",Zcy:"З",zcy:"з",Zdot:"Å»",zdot:"ż",zeetrf:"ℨ",ZeroWidthSpace:"​",Zeta:"Ζ",zeta:"ζ",zfr:"ð”·",Zfr:"ℨ",ZHcy:"Ж",zhcy:"ж",zigrarr:"â‡",zopf:"ð•«",Zopf:"ℤ",Zscr:"ð’µ",zscr:"ð“",zwj:"â€",zwnj:"‌"},Qh=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4E\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDF55-\uDF59]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDC3B\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/,Xh={},Yh={};function Jh(e){var t,n,r=Yh[e];if(r)return r;for(r=Yh[e]=[],t=0;t<128;t++)n=String.fromCharCode(t),/^[0-9a-z]$/i.test(n)?r.push(n):r.push("%"+("0"+t.toString(16).toUpperCase()).slice(-2));for(t=0;t=55296&&o<=57343){if(o>=55296&&o<=56319&&r+1=56320&&a<=57343){l+=encodeURIComponent(e[r]+e[r+1]),r++;continue}l+="%EF%BF%BD"}else l+=encodeURIComponent(e[r]);return l}g(Jh,"getEncodeCache"),g(Zh,"encode$1"),Zh.defaultChars=";/?:@&=+$,-_.!~*'()#",Zh.componentChars="-_.!~*'()";var em=Zh,tm={};function nm(e){var t,n,r=tm[e];if(r)return r;for(r=tm[e]=[],t=0;t<128;t++)n=String.fromCharCode(t),r.push(n);for(t=0;t=55296&&l<=57343?"���":String.fromCharCode(l),t+=6):240==(248&i)&&t+91114111?u+="����":(l-=65536,u+=String.fromCharCode(55296+(l>>10),56320+(1023&l))),t+=9):u+="�";return u}))}g(nm,"getDecodeCache"),g(rm,"decode$1"),rm.defaultChars=";/?:@&=+$,#",rm.componentChars="";var im=rm,om=g((function(e){var t="";return t+=e.protocol||"",t+=e.slashes?"//":"",t+=e.auth?e.auth+"@":"",e.hostname&&-1!==e.hostname.indexOf(":")?t+="["+e.hostname+"]":t+=e.hostname||"",t+=e.port?":"+e.port:"",t+=e.pathname||"",(t+=e.search||"")+(e.hash||"")}),"format");function am(){this.protocol=null,this.slashes=null,this.auth=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.pathname=null}g(am,"Url");var sm=/^([a-z0-9.+-]+:)/i,lm=/:[0-9]*$/,um=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,cm=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),dm=["'"].concat(cm),fm=["%","/","?",";","#"].concat(dm),pm=["/","?","#"],hm=/^[+a-z0-9A-Z_-]{0,63}$/,mm=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,gm={javascript:!0,"javascript:":!0},vm={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};function ym(e,t){if(e&&e instanceof am)return e;var n=new am;return n.parse(e,t),n}g(ym,"urlParse"),am.prototype.parse=function(e,t){var n,r,i,o,a,s=e;if(s=s.trim(),!t&&1===e.split("#").length){var l=um.exec(s);if(l)return this.pathname=l[1],l[2]&&(this.search=l[2]),this}var u=sm.exec(s);if(u&&(i=(u=u[0]).toLowerCase(),this.protocol=u,s=s.substr(u.length)),(t||u||s.match(/^\/\/[^@\/]+@[^@\/]+/))&&(!(a="//"===s.substr(0,2))||u&&gm[u]||(s=s.substr(2),this.slashes=!0)),!gm[u]&&(a||u&&!vm[u])){var c,d,f=-1;for(n=0;n127?v+="x":v+=g[y];if(!v.match(hm)){var E=m.slice(0,n),T=m.slice(n+1),w=g.match(mm);w&&(E.push(w[1]),T.unshift(w[2])),T.length&&(s=T.join(".")+s),this.hostname=E.join(".");break}}}}this.hostname.length>255&&(this.hostname=""),h&&(this.hostname=this.hostname.substr(1,this.hostname.length-2))}var C=s.indexOf("#");-1!==C&&(this.hash=s.substr(C),s=s.slice(0,C));var S=s.indexOf("?");return-1!==S&&(this.search=s.substr(S),s=s.slice(0,S)),s&&(this.pathname=s),vm[i]&&this.hostname&&!this.pathname&&(this.pathname=""),this},am.prototype.parseHost=function(e){var t=lm.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)};var bm=ym;Xh.encode=em,Xh.decode=im,Xh.format=om,Xh.parse=bm;var Em={},Tm=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,wm=/[\0-\x1F\x7F-\x9F]/,Cm=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/;Em.Any=Tm,Em.Cc=wm,Em.Cf=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/,Em.P=Qh,Em.Z=Cm,function(e){function t(e){return Object.prototype.toString.call(e)}function n(e){return"[object String]"===t(e)}g(t,"_class"),g(n,"isString");var r=Object.prototype.hasOwnProperty;function i(e,t){return r.call(e,t)}function o(e){return Array.prototype.slice.call(arguments,1).forEach((function(t){if(t){if("object"!=typeof t)throw new TypeError(t+"must be object");Object.keys(t).forEach((function(n){e[n]=t[n]}))}})),e}function a(e,t,n){return[].concat(e.slice(0,t),n,e.slice(t+1))}function s(e){return!(e>=55296&&e<=57343||e>=64976&&e<=65007||65535==(65535&e)||65534==(65535&e)||e>=0&&e<=8||11===e||e>=14&&e<=31||e>=127&&e<=159||e>1114111)}function l(e){if(e>65535){var t=55296+((e-=65536)>>10),n=56320+(1023&e);return String.fromCharCode(t,n)}return String.fromCharCode(e)}g(i,"has"),g(o,"assign"),g(a,"arrayReplaceAt"),g(s,"isValidEntityCode"),g(l,"fromCodePoint");var u=/\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g,c=new RegExp(u.source+"|"+/&([a-z#][a-z0-9]{1,31});/gi.source,"gi"),d=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i,f=Kh;function p(e,t){var n=0;return i(f,t)?f[t]:35===t.charCodeAt(0)&&d.test(t)&&s(n="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10))?l(n):e}function h(e){return e.indexOf("\\")<0?e:e.replace(u,"$1")}function m(e){return e.indexOf("\\")<0&&e.indexOf("&")<0?e:e.replace(c,(function(e,t,n){return t||p(e,n)}))}g(p,"replaceEntityPattern"),g(h,"unescapeMd"),g(m,"unescapeAll");var v=/[&<>"]/,y=/[&<>"]/g,b={"&":"&","<":"<",">":">",'"':"""};function E(e){return b[e]}function T(e){return v.test(e)?e.replace(y,E):e}g(E,"replaceUnsafeChar"),g(T,"escapeHtml");var w=/[.?*+^$[\]\\(){}|-]/g;function C(e){return e.replace(w,"\\$&")}function S(e){switch(e){case 9:case 32:return!0}return!1}function x(e){if(e>=8192&&e<=8202)return!0;switch(e){case 9:case 10:case 11:case 12:case 13:case 32:case 160:case 5760:case 8239:case 8287:case 12288:return!0}return!1}g(C,"escapeRE"),g(S,"isSpace"),g(x,"isWhiteSpace");var k=Qh;function N(e){return k.test(e)}function _(e){switch(e){case 33:case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:case 58:case 59:case 60:case 61:case 62:case 63:case 64:case 91:case 92:case 93:case 94:case 95:case 96:case 123:case 124:case 125:case 126:return!0;default:return!1}}function O(e){return e=e.trim().replace(/\s+/g," "),"á¹¾"==="ẞ".toLowerCase()&&(e=e.replace(/ẞ/g,"ß")),e.toLowerCase().toUpperCase()}g(N,"isPunctChar"),g(_,"isMdAsciiPunct"),g(O,"normalizeReference"),e.lib={},e.lib.mdurl=Xh,e.lib.ucmicro=Em,e.assign=o,e.isString=n,e.has=i,e.unescapeMd=h,e.unescapeAll=m,e.isValidEntityCode=s,e.fromCodePoint=l,e.escapeHtml=T,e.arrayReplaceAt=a,e.isSpace=S,e.isWhiteSpace=x,e.isMdAsciiPunct=_,e.isPunctChar=N,e.escapeRE=C,e.normalizeReference=O}(Wh);var Sm={},xm=g((function(e,t,n){var r,i,o,a,s=-1,l=e.posMax,u=e.pos;for(e.pos=t+1,r=1;e.pos32)return a;if(41===r){if(0===i)break;i--}t++}return o===t||0!==i||(a.str=km(e.slice(o,t)),a.lines=0,a.pos=t,a.ok=!0),a}),"parseLinkDestination"),_m=Wh.unescapeAll,Om=g((function(e,t,n){var r,i,o=0,a=t,s={ok:!1,pos:0,lines:0,str:""};if(t>=n)return s;if(34!==(i=e.charCodeAt(t))&&39!==i&&40!==i)return s;for(t++,40===i&&(i=41);t"+Lm(e[t].content)+""},Am.code_block=function(e,t,n,r,i){var o=e[t];return""+Lm(e[t].content)+"\n"},Am.fence=function(e,t,n,r,i){var o,a,s,l,u,c=e[t],d=c.info?Dm(c.info).trim():"",f="",p="";return d&&(f=(s=d.split(/(\s+)/g))[0],p=s.slice(2).join("")),0===(o=n.highlight&&n.highlight(c.content,f,p)||Lm(c.content)).indexOf(""+o+"\n"):"
    "+o+"
    \n"},Am.image=function(e,t,n,r,i){var o=e[t];return o.attrs[o.attrIndex("alt")][1]=i.renderInlineAsText(o.children,n,r),i.renderToken(e,t,n)},Am.hardbreak=function(e,t,n){return n.xhtmlOut?"
    \n":"
    \n"},Am.softbreak=function(e,t,n){return n.breaks?n.xhtmlOut?"
    \n":"
    \n":"\n"},Am.text=function(e,t){return Lm(e[t].content)},Am.html_block=function(e,t){return e[t].content},Am.html_inline=function(e,t){return e[t].content},g(Mm,"Renderer$1"),Mm.prototype.renderAttrs=g((function(e){var t,n,r;if(!e.attrs)return"";for(r="",t=0,n=e.attrs.length;t\n":">")}),"renderToken"),Mm.prototype.renderInline=function(e,t,n){for(var r,i="",o=this.rules,a=0,s=e.length;a\s]/i.test(e)}function Gm(e){return/^<\/a\s*>/i.test(e)}g(Hm,"isLinkOpen"),g(Gm,"isLinkClose");var zm=g((function(e){var t,n,r,i,o,a,s,l,u,c,d,f,p,h,m,g,v,y=e.tokens;if(e.md.options.linkify)for(n=0,r=y.length;n=0;t--)if("link_close"!==(a=i[t]).type){if("html_inline"===a.type&&(Hm(a.content)&&p>0&&p--,Gm(a.content)&&p++),!(p>0)&&"text"===a.type&&e.md.linkify.test(a.content)){for(u=a.content,v=e.md.linkify.match(u),s=[],f=a.level,d=0,l=0;ld&&((o=new e.Token("text","",0)).content=u.slice(d,c),o.level=f,s.push(o)),(o=new e.Token("link_open","a",1)).attrs=[["href",m]],o.level=f++,o.markup="linkify",o.info="auto",s.push(o),(o=new e.Token("text","",0)).content=g,o.level=f,s.push(o),(o=new e.Token("link_close","a",-1)).level=--f,o.markup="linkify",o.info="auto",s.push(o),d=v[l].lastIndex);d=0;t--)"text"!==(n=e[t]).type||r||(n.content=n.content.replace(Qm,Ym)),"link_open"===n.type&&"auto"===n.info&&r--,"link_close"===n.type&&"auto"===n.info&&r++}function Zm(e){var t,n,r=0;for(t=e.length-1;t>=0;t--)"text"!==(n=e[t]).type||r||Wm.test(n.content)&&(n.content=n.content.replace(/\+-/g,"±").replace(/\.{2,}/g,"…").replace(/([?!])…/g,"$1..").replace(/([?!]){4,}/g,"$1$1$1").replace(/,{2,}/g,",").replace(/(^|[^-])---(?=[^-]|$)/gm,"$1—").replace(/(^|\s)--(?=\s|$)/gm,"$1–").replace(/(^|[^-\s])--(?=[^-\s]|$)/gm,"$1–")),"link_open"===n.type&&"auto"===n.info&&r--,"link_close"===n.type&&"auto"===n.info&&r++}g(Ym,"replaceFn"),g(Jm,"replace_scoped"),g(Zm,"replace_rare");var eg=g((function(e){var t;if(e.md.options.typographer)for(t=e.tokens.length-1;t>=0;t--)"inline"===e.tokens[t].type&&(Km.test(e.tokens[t].content)&&Jm(e.tokens[t].children),Wm.test(e.tokens[t].content)&&Zm(e.tokens[t].children))}),"replace"),tg=Wh.isWhiteSpace,ng=Wh.isPunctChar,rg=Wh.isMdAsciiPunct,ig=/['"]/,og=/['"]/g,ag="’";function sg(e,t,n){return e.substr(0,t)+n+e.substr(t+1)}function lg(e,t){var n,r,i,o,a,s,l,u,c,d,f,p,h,m,g,v,y,b,E,T,w;for(E=[],n=0;n=0&&!(E[y].level<=l);y--);if(E.length=y+1,"text"===r.type){a=0,s=(i=r.content).length;e:for(;a=0)c=i.charCodeAt(o.index-1);else for(y=n-1;y>=0&&"softbreak"!==e[y].type&&"hardbreak"!==e[y].type;y--)if(e[y].content){c=e[y].content.charCodeAt(e[y].content.length-1);break}if(d=32,a=48&&c<=57&&(v=g=!1),g&&v&&(g=f,v=p),g||v){if(v)for(y=E.length-1;y>=0&&(u=E[y],!(E[y].level=0;t--)"inline"===e.tokens[t].type&&ig.test(e.tokens[t].content)&&lg(e.tokens[t].children,e)}),"smartquotes");function cg(e,t,n){this.type=e,this.tag=t,this.attrs=null,this.map=null,this.nesting=n,this.level=0,this.children=null,this.content="",this.markup="",this.info="",this.meta=null,this.block=!1,this.hidden=!1}g(cg,"Token$3"),cg.prototype.attrIndex=g((function(e){var t,n,r;if(!this.attrs)return-1;for(n=0,r=(t=this.attrs).length;n=0&&(n=this.attrs[t][1]),n}),"attrGet"),cg.prototype.attrJoin=g((function(e,t){var n=this.attrIndex(e);n<0?this.attrPush([e,t]):this.attrs[n][1]=this.attrs[n][1]+" "+t}),"attrJoin");var dg=cg,fg=dg;function pg(e,t,n){this.src=e,this.env=n,this.tokens=[],this.inlineMode=!1,this.md=t}g(pg,"StateCore"),pg.prototype.Token=fg;var hg=pg,mg=Pm,gg=[["normalize",Um],["block",Bm],["inline",$m],["linkify",zm],["replacements",eg],["smartquotes",ug]];function vg(){this.ruler=new mg;for(var e=0;en)return!1;if(u=t+1,e.sCount[u]=4)return!1;if((a=e.bMarks[u]+e.tShift[u])>=e.eMarks[u])return!1;if(124!==(E=e.src.charCodeAt(a++))&&45!==E&&58!==E)return!1;if(a>=e.eMarks[u])return!1;if(124!==(T=e.src.charCodeAt(a++))&&45!==T&&58!==T&&!bg(T))return!1;if(45===E&&bg(T))return!1;for(;a=4)return!1;if((c=Tg(o)).length&&""===c[0]&&c.shift(),c.length&&""===c[c.length-1]&&c.pop(),0===(d=c.length)||d!==p.length)return!1;if(r)return!0;for(v=e.parentType,e.parentType="table",b=e.md.block.ruler.getRules("blockquote"),(f=e.push("table_open","table",1)).map=m=[t,0],(f=e.push("thead_open","thead",1)).map=[t,t+1],(f=e.push("tr_open","tr",1)).map=[t,t+1],s=0;s=4)break;for((c=Tg(o)).length&&""===c[0]&&c.shift(),c.length&&""===c[c.length-1]&&c.pop(),u===t+2&&((f=e.push("tbody_open","tbody",1)).map=g=[t+2,0]),(f=e.push("tr_open","tr",1)).map=[u,u+1],s=0;s=4))break;i=++r}return e.line=i,(o=e.push("code_block","code",0)).content=e.getLines(t,i,4+e.blkIndent,!1)+"\n",o.map=[t,e.line],!0}),"code"),Sg=g((function(e,t,n,r){var i,o,a,s,l,u,c,d=!1,f=e.bMarks[t]+e.tShift[t],p=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;if(f+3>p)return!1;if(126!==(i=e.src.charCodeAt(f))&&96!==i)return!1;if(l=f,(o=(f=e.skipChars(f,i))-l)<3)return!1;if(c=e.src.slice(l,f),a=e.src.slice(f,p),96===i&&a.indexOf(String.fromCharCode(i))>=0)return!1;if(r)return!0;for(s=t;!(++s>=n||(f=l=e.bMarks[s]+e.tShift[s])<(p=e.eMarks[s])&&e.sCount[s]=4||(f=e.skipChars(f,i))-l=4)return!1;if(62!==e.src.charCodeAt(x++))return!1;if(r)return!0;for(s=f=e.sCount[t]+1,32===e.src.charCodeAt(x)?(x++,s++,f++,i=!1,b=!0):9===e.src.charCodeAt(x)?(b=!0,(e.bsCount[t]+f)%4==3?(x++,s++,f++,i=!1):i=!0):b=!1,p=[e.bMarks[t]],e.bMarks[t]=x;x=k,v=[e.sCount[t]],e.sCount[t]=f-s,y=[e.tShift[t]],e.tShift[t]=x-e.bMarks[t],T=e.md.block.ruler.getRules("blockquote"),g=e.parentType,e.parentType="blockquote",d=t+1;d=(k=e.eMarks[d])));d++)if(62!==e.src.charCodeAt(x++)||C){if(u)break;for(E=!1,a=0,l=T.length;a=k,h.push(e.bsCount[d]),e.bsCount[d]=e.sCount[d]+1+(b?1:0),v.push(e.sCount[d]),e.sCount[d]=f-s,y.push(e.tShift[d]),e.tShift[d]=x-e.bMarks[d]}for(m=e.blkIndent,e.blkIndent=0,(w=e.push("blockquote_open","blockquote",1)).markup=">",w.map=c=[t,0],e.md.block.tokenize(e,t,d),(w=e.push("blockquote_close","blockquote",-1)).markup=">",e.lineMax=S,e.parentType=g,c[1]=e.line,a=0;a=4)return!1;if(42!==(i=e.src.charCodeAt(l++))&&45!==i&&95!==i)return!1;for(o=1;l=o)return-1;if((n=e.src.charCodeAt(i++))<48||n>57)return-1;for(;;){if(i>=o)return-1;if(!((n=e.src.charCodeAt(i++))>=48&&n<=57)){if(41===n||46===n)break;return-1}if(i-r>=10)return-1}return i=4)return!1;if(e.listIndent>=0&&e.sCount[t]-e.listIndent>=4&&e.sCount[t]=e.blkIndent&&(L=!0),(k=Dg(e,t))>=0){if(c=!0,_=e.bMarks[t]+e.tShift[t],g=Number(e.src.slice(_,k-1)),L&&1!==g)return!1}else{if(!((k=Ig(e,t))>=0))return!1;c=!1}if(L&&e.skipSpaces(k)>=e.eMarks[t])return!1;if(m=e.src.charCodeAt(k-1),r)return!0;for(h=e.tokens.length,c?(D=e.push("ordered_list_open","ol",1),1!==g&&(D.attrs=[["start",g]])):D=e.push("bullet_list_open","ul",1),D.map=p=[t,0],D.markup=String.fromCharCode(m),y=t,N=!1,I=e.md.block.ruler.getRules("list"),T=e.parentType,e.parentType="list";y=v?1:b-u)>4&&(l=1),s=u+l,(D=e.push("list_item_open","li",1)).markup=String.fromCharCode(m),D.map=d=[t,0],c&&(D.info=e.src.slice(_,k-1)),S=e.tight,C=e.tShift[t],w=e.sCount[t],E=e.listIndent,e.listIndent=e.blkIndent,e.blkIndent=s,e.tight=!0,e.tShift[t]=o-e.bMarks[t],e.sCount[t]=b,o>=v&&e.isEmpty(t+1)?e.line=Math.min(e.line+2,n):e.md.block.tokenize(e,t,n,!0),e.tight&&!N||(A=!1),N=e.line-t>1&&e.isEmpty(e.line-1),e.blkIndent=e.listIndent,e.listIndent=E,e.tShift[t]=C,e.sCount[t]=w,e.tight=S,(D=e.push("list_item_close","li",-1)).markup=String.fromCharCode(m),y=t=e.line,d[1]=y,o=e.bMarks[t],y>=n)break;if(e.sCount[y]=4)break;for(O=!1,a=0,f=I.length;a=4)return!1;if(91!==e.src.charCodeAt(T))return!1;for(;++T3||e.sCount[C]<0)){for(v=!1,u=0,c=y.length;u`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*\\/?>",Vg="<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>",Ug=new RegExp("^(?:"+jg+"|"+Vg+"|\x3c!----\x3e|\x3c!--(?:-?[^>-])(?:-?[^-])*--\x3e|<[?][\\s\\S]*?[?]>|]*>|)"),Bg=new RegExp("^(?:"+jg+"|"+Vg+")");Pg.HTML_TAG_RE=Ug,Pg.HTML_OPEN_CLOSE_TAG_RE=Bg;var $g=["address","article","aside","base","basefont","blockquote","body","caption","center","col","colgroup","dd","details","dialog","dir","div","dl","dt","fieldset","figcaption","figure","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hr","html","iframe","legend","li","link","main","menu","menuitem","nav","noframes","ol","optgroup","option","p","param","section","source","summary","table","tbody","td","tfoot","th","thead","title","tr","track","ul"],qg=Pg.HTML_OPEN_CLOSE_TAG_RE,Hg=[[/^<(script|pre|style|textarea)(?=(\s|>|$))/i,/<\/(script|pre|style|textarea)>/i,!0],[/^/,!0],[/^<\?/,/\?>/,!0],[/^/,!0],[/^/,!0],[new RegExp("^|$))","i"),/^$/,!0],[new RegExp(qg.source+"\\s*$"),/^$/,!1]],Gg=g((function(e,t,n,r){var i,o,a,s,l=e.bMarks[t]+e.tShift[t],u=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;if(!e.md.options.html)return!1;if(60!==e.src.charCodeAt(l))return!1;for(s=e.src.slice(l,u),i=0;i=4)return!1;if(35!==(i=e.src.charCodeAt(l))||l>=u)return!1;for(o=1,i=e.src.charCodeAt(++l);35===i&&l6||ll&&zg(e.src.charCodeAt(a-1))&&(u=a),e.line=t+1,(s=e.push("heading_open","h"+String(o),1)).markup="########".slice(0,o),s.map=[t,e.line],(s=e.push("inline","",0)).content=e.src.slice(l,u).trim(),s.map=[t,e.line],s.children=[],(s=e.push("heading_close","h"+String(o),-1)).markup="########".slice(0,o)),0))}),"heading"),Kg=g((function(e,t,n){var r,i,o,a,s,l,u,c,d,f,p=t+1,h=e.md.block.ruler.getRules("paragraph");if(e.sCount[t]-e.blkIndent>=4)return!1;for(f=e.parentType,e.parentType="paragraph";p3)){if(e.sCount[p]>=e.blkIndent&&(l=e.bMarks[p]+e.tShift[p])<(u=e.eMarks[p])&&(45===(d=e.src.charCodeAt(l))||61===d)&&(l=e.skipChars(l,d),(l=e.skipSpaces(l))>=u)){c=61===d?1:2;break}if(!(e.sCount[p]<0)){for(i=!1,o=0,a=h.length;o3||e.sCount[l]<0)){for(r=!1,i=0,o=u.length;i0&&this.level++,this.tokens.push(r),r},Jg.prototype.isEmpty=g((function(e){return this.bMarks[e]+this.tShift[e]>=this.eMarks[e]}),"isEmpty"),Jg.prototype.skipEmptyLines=g((function(e){for(var t=this.lineMax;et;)if(!Yg(this.src.charCodeAt(--e)))return e+1;return e}),"skipSpacesBack"),Jg.prototype.skipChars=g((function(e,t){for(var n=this.src.length;en;)if(t!==this.src.charCodeAt(--e))return e+1;return e}),"skipCharsBack"),Jg.prototype.getLines=g((function(e,t,n,r){var i,o,a,s,l,u,c,d=e;if(e>=t)return"";for(u=new Array(t-e),i=0;dn?new Array(o-n+1).join(" ")+this.src.slice(s,l):this.src.slice(s,l)}return u.join("")}),"getLines"),Jg.prototype.Token=Xg;var Zg=Jg,ev=Pm,tv=[["table",wg,["paragraph","reference"]],["code",Cg],["fence",Sg,["paragraph","reference","blockquote","list"]],["blockquote",kg,["paragraph","reference","blockquote","list"]],["hr",_g,["paragraph","reference","blockquote","list"]],["list",Ag,["paragraph","reference","blockquote"]],["reference",Fg],["html_block",Gg,["paragraph","reference","blockquote"]],["heading",Wg,["paragraph","reference","blockquote"]],["lheading",Kg],["paragraph",Qg]];function nv(){this.ruler=new ev;for(var e=0;e=n))&&!(e.sCount[a]=l){e.line=n;break}for(r=0;r=0&&32===e.pending.charCodeAt(n)?n>=1&&32===e.pending.charCodeAt(n-1)?(e.pending=e.pending.replace(/ +$/,""),e.push("hardbreak","br",0)):(e.pending=e.pending.slice(0,-1),e.push("softbreak","br",0)):e.push("softbreak","br",0)),i++;i?@[]^_`{|}~-".split("").forEach((function(e){uv[e.charCodeAt(0)]=1}));var dv=g((function(e,t){var n,r=e.pos,i=e.posMax;if(92!==e.src.charCodeAt(r))return!1;if(++r=0;n--)95!==(r=t[n]).marker&&42!==r.marker||-1!==r.end&&(i=t[r.end],s=n>0&&t[n-1].end===r.end+1&&t[n-1].token===r.token-1&&t[r.end+1].token===i.token+1&&t[n-1].marker===r.marker,a=String.fromCharCode(r.marker),(o=e.tokens[r.token]).type=s?"strong_open":"em_open",o.tag=s?"strong":"em",o.nesting=1,o.markup=s?a+a:a,o.content="",(o=e.tokens[i.token]).type=s?"strong_close":"em_close",o.tag=s?"strong":"em",o.nesting=-1,o.markup=s?a+a:a,o.content="",s&&(e.tokens[t[n-1].token].content="",e.tokens[t[r.end+1].token].content="",n--))}mv.tokenize=g((function(e,t){var n,r,i=e.pos,o=e.src.charCodeAt(i);if(t)return!1;if(95!==o&&42!==o)return!1;for(r=e.scanDelims(e.pos,42===o),n=0;n=p)return!1;if(h=s,(l=e.md.helpers.parseLinkDestination(e.src,s,e.posMax)).ok){for(c=e.md.normalizeLink(l.str),e.md.validateLink(c)?s=l.pos:c="",h=s;s=p||41!==e.src.charCodeAt(s))&&(m=!0),s++}if(m){if(void 0===e.env.references)return!1;if(s=0?i=e.src.slice(h,s++):s=o+1):s=o+1,i||(i=e.src.slice(a,o)),!(u=e.env.references[vv(i)]))return e.pos=f,!1;c=u.href,d=u.title}return t||(e.pos=a,e.posMax=o,e.push("link_open","a",1).attrs=n=[["href",c]],d&&n.push(["title",d]),e.md.inline.tokenize(e),e.push("link_close","a",-1)),e.pos=s,e.posMax=p,!0}),"link"),Ev=Wh.normalizeReference,Tv=Wh.isSpace,wv=g((function(e,t){var n,r,i,o,a,s,l,u,c,d,f,p,h,m="",g=e.pos,v=e.posMax;if(33!==e.src.charCodeAt(e.pos))return!1;if(91!==e.src.charCodeAt(e.pos+1))return!1;if(s=e.pos+2,(a=e.md.helpers.parseLinkLabel(e,e.pos+1,!1))<0)return!1;if((l=a+1)=v)return!1;for(h=l,(c=e.md.helpers.parseLinkDestination(e.src,l,e.posMax)).ok&&(m=e.md.normalizeLink(c.str),e.md.validateLink(m)?l=c.pos:m=""),h=l;l=v||41!==e.src.charCodeAt(l))return e.pos=g,!1;l++}else{if(void 0===e.env.references)return!1;if(l=0?o=e.src.slice(h,l++):l=a+1):l=a+1,o||(o=e.src.slice(s,a)),!(u=e.env.references[Ev(o)]))return e.pos=g,!1;m=u.href,d=u.title}return t||(i=e.src.slice(s,a),e.md.inline.parse(i,e.md,e.env,p=[]),(f=e.push("image","img",0)).attrs=n=[["src",m],["alt",""]],f.children=p,f.content=i,d&&n.push(["title",d])),e.pos=l,e.posMax=v,!0}),"image"),Cv=/^([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/,Sv=/^([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)$/,xv=g((function(e,t){var n,r,i,o,a,s,l=e.pos;if(60!==e.src.charCodeAt(l))return!1;for(a=e.pos,s=e.posMax;;){if(++l>=s)return!1;if(60===(o=e.src.charCodeAt(l)))return!1;if(62===o)break}return n=e.src.slice(a+1,l),Sv.test(n)?(r=e.md.normalizeLink(n),!!e.md.validateLink(r)&&(t||((i=e.push("link_open","a",1)).attrs=[["href",r]],i.markup="autolink",i.info="auto",(i=e.push("text","",0)).content=e.md.normalizeLinkText(n),(i=e.push("link_close","a",-1)).markup="autolink",i.info="auto"),e.pos+=n.length+2,!0)):!!Cv.test(n)&&(r=e.md.normalizeLink("mailto:"+n),!!e.md.validateLink(r)&&(t||((i=e.push("link_open","a",1)).attrs=[["href",r]],i.markup="autolink",i.info="auto",(i=e.push("text","",0)).content=e.md.normalizeLinkText(n),(i=e.push("link_close","a",-1)).markup="autolink",i.info="auto"),e.pos+=n.length+2,!0))}),"autolink"),kv=Pg.HTML_TAG_RE;function Nv(e){var t=32|e;return t>=97&&t<=122}g(Nv,"isLetter");var _v=g((function(e,t){var n,r,i,o=e.pos;return!(!e.md.options.html||(i=e.posMax,60!==e.src.charCodeAt(o)||o+2>=i||33!==(n=e.src.charCodeAt(o+1))&&63!==n&&47!==n&&!Nv(n)||!(r=e.src.slice(o).match(kv))||(t||(e.push("html_inline","",0).content=e.src.slice(o,o+r[0].length)),e.pos+=r[0].length,0)))}),"html_inline"),Ov=Kh,Iv=Wh.has,Dv=Wh.isValidEntityCode,Lv=Wh.fromCodePoint,Av=/^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i,Mv=/^&([a-z][a-z0-9]{1,31});/i,Rv=g((function(e,t){var n,r,i=e.pos,o=e.posMax;if(38!==e.src.charCodeAt(i))return!1;if(i+1a;r-=o.jump+1)if((o=t[r]).marker===i.marker&&o.open&&o.end<0&&(l=!1,(o.close||i.open)&&(o.length+i.length)%3==0&&(o.length%3==0&&i.length%3==0||(l=!0)),!l)){u=r>0&&!t[r-1].open?t[r-1].jump+1:0,i.jump=n-r+u,i.open=!1,o.end=n,o.jump=u,o.close=!1,s=-1;break}-1!==s&&(c[i.marker][(i.open?3:0)+(i.length||0)%3]=s)}}g(Fv,"processDelimiters");var Pv=g((function(e){var t,n=e.tokens_meta,r=e.tokens_meta.length;for(Fv(0,e.delimiters),t=0;t0&&r++,"text"===i[t].type&&t+10&&(this.level++,this._prev_delimiters.push(this.delimiters),this.delimiters=[],i={delimiters:this.delimiters}),this.pendingLevel=this.level,this.tokens.push(r),this.tokens_meta.push(i),r},qv.prototype.scanDelims=function(e,t){var n,r,i,o,a,s,l,u,c,d=e,f=!0,p=!0,h=this.posMax,m=this.src.charCodeAt(e);for(n=e>0?this.src.charCodeAt(e-1):32;d=o)break}else e.pending+=e.src[e.pos++]}e.pending&&e.pushPending()},Kv.prototype.parse=function(e,t,n,r){var i,o,a,s=new this.State(e,t,n,r);for(this.tokenize(s),a=(o=this.ruler2.getRules("")).length,i=0;i|$))",t.tpl_email_fuzzy='(^|[><|]|"|\\(|'+t.src_ZCc+")("+t.src_email_name+"@"+t.tpl_host_fuzzy_strict+")",t.tpl_link_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`||]|"+t.src_ZPCc+"))((?![$+<=>^`||])"+t.tpl_host_port_fuzzy_strict+t.src_path+")",t.tpl_link_no_ip_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`||]|"+t.src_ZPCc+"))((?![$+<=>^`||])"+t.tpl_host_port_no_ip_fuzzy_strict+t.src_path+")",t}),"re");function Yv(e){return Array.prototype.slice.call(arguments,1).forEach((function(t){t&&Object.keys(t).forEach((function(n){e[n]=t[n]}))})),e}function Jv(e){return Object.prototype.toString.call(e)}function Zv(e){return"[object String]"===Jv(e)}function ey(e){return"[object Object]"===Jv(e)}function ty(e){return"[object RegExp]"===Jv(e)}function ny(e){return"[object Function]"===Jv(e)}function ry(e){return e.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}g(Yv,"assign"),g(Jv,"_class"),g(Zv,"isString"),g(ey,"isObject"),g(ty,"isRegExp"),g(ny,"isFunction"),g(ry,"escapeRE");var iy={fuzzyLink:!0,fuzzyEmail:!0,fuzzyIP:!1};function oy(e){return Object.keys(e||{}).reduce((function(e,t){return e||iy.hasOwnProperty(t)}),!1)}g(oy,"isOptionsObj");var ay={"http:":{validate:function(e,t,n){var r=e.slice(t);return n.re.http||(n.re.http=new RegExp("^\\/\\/"+n.re.src_auth+n.re.src_host_port_strict+n.re.src_path,"i")),n.re.http.test(r)?r.match(n.re.http)[0].length:0}},"https:":"http:","ftp:":"http:","//":{validate:function(e,t,n){var r=e.slice(t);return n.re.no_http||(n.re.no_http=new RegExp("^"+n.re.src_auth+"(?:localhost|(?:(?:"+n.re.src_domain+")\\.)+"+n.re.src_domain_root+")"+n.re.src_port+n.re.src_host_terminator+n.re.src_path,"i")),n.re.no_http.test(r)?t>=3&&":"===e[t-3]||t>=3&&"/"===e[t-3]?0:r.match(n.re.no_http)[0].length:0}},"mailto:":{validate:function(e,t,n){var r=e.slice(t);return n.re.mailto||(n.re.mailto=new RegExp("^"+n.re.src_email_name+"@"+n.re.src_host_strict,"i")),n.re.mailto.test(r)?r.match(n.re.mailto)[0].length:0}}},sy="a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]",ly="biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф".split("|");function uy(e){e.__index__=-1,e.__text_cache__=""}function cy(e){return function(t,n){var r=t.slice(n);return e.test(r)?r.match(e)[0].length:0}}function dy(){return function(e,t){t.normalize(e)}}function fy(e){var t=e.re=Xv(e.__opts__),n=e.__tlds__.slice();function r(e){return e.replace("%TLDS%",t.src_tlds)}e.onCompile(),e.__tlds_replaced__||n.push(sy),n.push(t.src_xn),t.src_tlds=n.join("|"),g(r,"untpl"),t.email_fuzzy=RegExp(r(t.tpl_email_fuzzy),"i"),t.link_fuzzy=RegExp(r(t.tpl_link_fuzzy),"i"),t.link_no_ip_fuzzy=RegExp(r(t.tpl_link_no_ip_fuzzy),"i"),t.host_fuzzy_test=RegExp(r(t.tpl_host_fuzzy_test),"i");var i=[];function o(e,t){throw new Error('(LinkifyIt) Invalid schema "'+e+'": '+t)}e.__compiled__={},g(o,"schemaError"),Object.keys(e.__schemas__).forEach((function(t){var n=e.__schemas__[t];if(null!==n){var r={validate:null,link:null};if(e.__compiled__[t]=r,ey(n))return ty(n.validate)?r.validate=cy(n.validate):ny(n.validate)?r.validate=n.validate:o(t,n),void(ny(n.normalize)?r.normalize=n.normalize:n.normalize?o(t,n):r.normalize=function(e,t){t.normalize(e)});Zv(n)?i.push(t):o(t,n)}})),i.forEach((function(t){e.__compiled__[e.__schemas__[t]]&&(e.__compiled__[t].validate=e.__compiled__[e.__schemas__[t]].validate,e.__compiled__[t].normalize=e.__compiled__[e.__schemas__[t]].normalize)})),e.__compiled__[""]={validate:null,normalize:function(e,t){t.normalize(e)}};var a=Object.keys(e.__compiled__).filter((function(t){return t.length>0&&e.__compiled__[t]})).map(ry).join("|");e.re.schema_test=RegExp("(^|(?!_)(?:[><|]|"+t.src_ZPCc+"))("+a+")","i"),e.re.schema_search=RegExp("(^|(?!_)(?:[><|]|"+t.src_ZPCc+"))("+a+")","ig"),e.re.pretest=RegExp("("+e.re.schema_test.source+")|("+e.re.host_fuzzy_test.source+")|@","i"),uy(e)}function py(e,t){var n=e.__index__,r=e.__last_index__,i=e.__text_cache__.slice(n,r);this.schema=e.__schema__.toLowerCase(),this.index=n+t,this.lastIndex=r+t,this.raw=i,this.text=i,this.url=i}function hy(e,t){var n=new py(e,t);return e.__compiled__[n.schema].normalize(n,e),n}function my(e,t){if(!(this instanceof my))return new my(e,t);t||oy(e)&&(t=e,e={}),this.__opts__=Yv({},iy,t),this.__index__=-1,this.__last_index__=-1,this.__schema__="",this.__text_cache__="",this.__schemas__=Yv({},ay,e),this.__compiled__={},this.__tlds__=ly,this.__tlds_replaced__=!1,this.re={},fy(this)}g(uy,"resetScanCache"),g(cy,"createValidator"),g(dy,"createNormalizer"),g(fy,"compile"),g(py,"Match"),g(hy,"createMatch"),g(my,"LinkifyIt$1"),my.prototype.add=g((function(e,t){return this.__schemas__[e]=t,fy(this),this}),"add"),my.prototype.set=g((function(e){return this.__opts__=Yv(this.__opts__,e),this}),"set"),my.prototype.test=g((function(e){if(this.__text_cache__=e,this.__index__=-1,!e.length)return!1;var t,n,r,i,o,a,s,l;if(this.re.schema_test.test(e))for((s=this.re.schema_search).lastIndex=0;null!==(t=s.exec(e));)if(i=this.testSchemaAt(e,t[2],s.lastIndex)){this.__schema__=t[2],this.__index__=t.index+t[1].length,this.__last_index__=t.index+t[0].length+i;break}return this.__opts__.fuzzyLink&&this.__compiled__["http:"]&&(l=e.search(this.re.host_fuzzy_test))>=0&&(this.__index__<0||l=0&&null!==(r=e.match(this.re.email_fuzzy))&&(o=r.index+r[1].length,a=r.index+r[0].length,(this.__index__<0||othis.__last_index__)&&(this.__schema__="mailto:",this.__index__=o,this.__last_index__=a)),this.__index__>=0}),"test"),my.prototype.pretest=g((function(e){return this.re.pretest.test(e)}),"pretest"),my.prototype.testSchemaAt=g((function(e,t,n){return this.__compiled__[t.toLowerCase()]?this.__compiled__[t.toLowerCase()].validate(e,n,this):0}),"testSchemaAt"),my.prototype.match=g((function(e){var t=0,n=[];this.__index__>=0&&this.__text_cache__===e&&(n.push(hy(this,t)),t=this.__last_index__);for(var r=t?e.slice(t):e;this.test(r);)n.push(hy(this,t)),r=r.slice(this.__last_index__),t+=this.__last_index__;return n.length?n:null}),"match"),my.prototype.tlds=g((function(e,t){return e=Array.isArray(e)?e:[e],t?(this.__tlds__=this.__tlds__.concat(e).sort().filter((function(e,t,n){return e!==n[t-1]})).reverse(),fy(this),this):(this.__tlds__=e.slice(),this.__tlds_replaced__=!0,fy(this),this)}),"tlds"),my.prototype.normalize=g((function(e){e.schema||(e.url="http://"+e.url),"mailto:"!==e.schema||/^mailto:/i.test(e.url)||(e.url="mailto:"+e.url)}),"normalize"),my.prototype.onCompile=g((function(){}),"onCompile");var gy=my;const vy=2147483647,yy=36,by=/^xn--/,Ey=/[^\0-\x7E]/,Ty=/[\x2E\u3002\uFF0E\uFF61]/g,wy={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},Cy=Math.floor,Sy=String.fromCharCode;function xy(e){throw new RangeError(wy[e])}function ky(e,t){const n=[];let r=e.length;for(;r--;)n[r]=t(e[r]);return n}function Ny(e,t){const n=e.split("@");let r="";return n.length>1&&(r=n[0]+"@",e=n[1]),r+ky((e=e.replace(Ty,".")).split("."),t).join(".")}function _y(e){const t=[];let n=0;const r=e.length;for(;n=55296&&i<=56319&&nString.fromCodePoint(...e)),"ucs2encode"),Iy=g((function(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:yy}),"basicToDigit"),Dy=g((function(e,t){return e+22+75*(e<26)-((0!=t)<<5)}),"digitToBasic"),Ly=g((function(e,t,n){let r=0;for(e=n?Cy(e/700):e>>1,e+=Cy(e/t);e>455;r+=yy)e=Cy(e/35);return Cy(r+36*e/(e+38))}),"adapt"),Ay=g((function(e){const t=[],n=e.length;let r=0,i=128,o=72,a=e.lastIndexOf("-");a<0&&(a=0);for(let n=0;n=128&&xy("not-basic"),t.push(e.charCodeAt(n));for(let s=a>0?a+1:0;s=n&&xy("invalid-input");const a=Iy(e.charCodeAt(s++));(a>=yy||a>Cy((vy-r)/t))&&xy("overflow"),r+=a*t;const l=i<=o?1:i>=o+26?26:i-o;if(aCy(vy/u)&&xy("overflow"),t*=u}const l=t.length+1;o=Ly(r-a,l,0==a),Cy(r/l)>vy-i&&xy("overflow"),i+=Cy(r/l),r%=l,t.splice(r++,0,i)}return String.fromCodePoint(...t)}),"decode"),My=g((function(e){const t=[];let n=(e=_y(e)).length,r=128,i=0,o=72;for(const n of e)n<128&&t.push(Sy(n));let a=t.length,s=a;for(a&&t.push("-");s=r&&tCy((vy-i)/l)&&xy("overflow"),i+=(n-r)*l,r=n;for(const n of e)if(nvy&&xy("overflow"),n==r){let e=i;for(let n=yy;;n+=yy){const r=n<=o?1:n>=o+26?26:n-o;if(e=0))try{t.hostname=Wy.toASCII(t.hostname)}catch(e){}return zy.encode(zy.format(t))}function eb(e){var t=zy.parse(e,!0);if(t.hostname&&(!t.protocol||Jy.indexOf(t.protocol)>=0))try{t.hostname=Wy.toUnicode(t.hostname)}catch(e){}return zy.decode(zy.format(t),zy.decode.defaultChars+"%")}function tb(e,t){if(!(this instanceof tb))return new tb(e,t);t||Vy.isString(e)||(t=e||{},e="default"),this.inline=new Hy,this.block=new qy,this.core=new $y,this.renderer=new By,this.linkify=new Gy,this.validateLink=Yy,this.normalizeLink=Zy,this.normalizeLinkText=eb,this.utils=Vy,this.helpers=Vy.assign({},Uy),this.options={},this.configure(e),t&&this.set(t)}g(Zy,"normalizeLink"),g(eb,"normalizeLinkText"),g(tb,"MarkdownIt"),tb.prototype.set=function(e){return Vy.assign(this.options,e),this},tb.prototype.configure=function(e){var t,n=this;if(Vy.isString(e)&&!(e=Ky[t=e]))throw new Error('Wrong `markdown-it` preset "'+t+'", check name');if(!e)throw new Error("Wrong `markdown-it` preset, can't be empty");return e.options&&n.set(e.options),e.components&&Object.keys(e.components).forEach((function(t){e.components[t].rules&&n[t].ruler.enableOnly(e.components[t].rules),e.components[t].rules2&&n[t].ruler2.enableOnly(e.components[t].rules2)})),this},tb.prototype.enable=function(e,t){var n=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach((function(t){n=n.concat(this[t].ruler.enable(e,!0))}),this),n=n.concat(this.inline.ruler2.enable(e,!0));var r=e.filter((function(e){return n.indexOf(e)<0}));if(r.length&&!t)throw new Error("MarkdownIt. Failed to enable unknown rule(s): "+r);return this},tb.prototype.disable=function(e,t){var n=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach((function(t){n=n.concat(this[t].ruler.disable(e,!0))}),this),n=n.concat(this.inline.ruler2.disable(e,!0));var r=e.filter((function(e){return n.indexOf(e)<0}));if(r.length&&!t)throw new Error("MarkdownIt. Failed to disable unknown rule(s): "+r);return this},tb.prototype.use=function(e){var t=[this].concat(Array.prototype.slice.call(arguments,1));return e.apply(e,t),this},tb.prototype.parse=function(e,t){if("string"!=typeof e)throw new Error("Input data should be a String");var n=new this.core.State(e,this,t);return this.core.process(n),n.tokens},tb.prototype.render=function(e,t){return t=t||{},this.renderer.render(this.parse(e,t),this.options,t)},tb.prototype.parseInline=function(e,t){var n=new this.core.State(e,this,t);return n.inlineMode=!0,this.core.process(n),n.tokens},tb.prototype.renderInline=function(e,t){return t=t||{},this.renderer.render(this.parseInline(e,t),this.options,t)};const nb=new tb({breaks:!0,linkify:!0}),rb=(0,t.forwardRef)(((e,t)=>{var n=e,{children:r,onlyShowFirstChild:i,type:o}=n,a=v(n,["children","onlyShowFirstChild","type"]);return de("div",m(h({},a),{ref:t,className:b(`graphiql-markdown-${o}`,i&&"graphiql-markdown-preview",a.className),dangerouslySetInnerHTML:{__html:nb.render(r)}}))}));e.aM=rb,rb.displayName="MarkdownContent";const ib=(0,t.forwardRef)(((e,t)=>de("div",m(h({},e),{ref:t,className:b("graphiql-spinner",e.className)}))));function ob(e){var t,n,r=ms(e),i=r.defaultView||window;return r?{width:null!=(t=r.documentElement.clientWidth)?t:i.innerWidth,height:null!=(n=r.documentElement.clientHeight)?n:i.innerHeight}:{width:0,height:0}}function ab(){return ab=Object.assign||function(e){for(var t=1;t=0||(i[n]=e[n]);return i}e.aN=ib,ib.displayName="Spinner",g(ob,"getDocumentDimensions"),g(ab,"_extends$1"),g(sb,"_objectWithoutPropertiesLoose$1");var lb,ub,cb,db,fb,pb,hb,mb,gb,vb,yb=["children","label","ariaLabel","id","DEBUG_STYLE"],bb=["label","ariaLabel","isVisible","id"],Eb=["ariaLabel","aria-label","as","id","isVisible","label","position","style","triggerRect"],Tb=["type"],wb=100,Cb=500;(vb=hb||(hb={})).Idle="IDLE",vb.Focused="FOCUSED",vb.Visible="VISIBLE",vb.LeavingVisible="LEAVING_VISIBLE",vb.Dismissed="DISMISSED",(gb=mb||(mb={})).Blur="BLUR",gb.Focus="FOCUS",gb.GlobalMouseMove="GLOBAL_MOUSE_MOVE",gb.MouseDown="MOUSE_DOWN",gb.MouseEnter="MOUSE_ENTER",gb.MouseLeave="MOUSE_LEAVE",gb.MouseMove="MOUSE_MOVE",gb.Rest="REST",gb.SelectWithKeyboard="SELECT_WITH_KEYBOARD",gb.TimeComplete="TIME_COMPLETE";var Sb,xb,kb={initial:hb.Idle,states:(pb={},pb[hb.Idle]={enter:Rb,on:(lb={},lb[mb.MouseEnter]=hb.Focused,lb[mb.Focus]=hb.Visible,lb)},pb[hb.Focused]={enter:Db,leave:Lb,on:(ub={},ub[mb.MouseMove]=hb.Focused,ub[mb.MouseLeave]=hb.Idle,ub[mb.MouseDown]=hb.Dismissed,ub[mb.Blur]=hb.Idle,ub[mb.Rest]=hb.Visible,ub)},pb[hb.Visible]={on:(cb={},cb[mb.Focus]=hb.Focused,cb[mb.MouseEnter]=hb.Focused,cb[mb.MouseLeave]=hb.LeavingVisible,cb[mb.Blur]=hb.LeavingVisible,cb[mb.MouseDown]=hb.Dismissed,cb[mb.SelectWithKeyboard]=hb.Dismissed,cb[mb.GlobalMouseMove]=hb.LeavingVisible,cb)},pb[hb.LeavingVisible]={enter:Ab,leave:g((function(){Mb(),Rb()}),"leave"),on:(db={},db[mb.MouseEnter]=hb.Visible,db[mb.Focus]=hb.Visible,db[mb.TimeComplete]=hb.Idle,db)},pb[hb.Dismissed]={leave:g((function(){Rb()}),"leave"),on:(fb={},fb[mb.MouseLeave]=hb.Idle,fb[mb.Blur]=hb.Idle,fb)},pb)},Nb={value:kb.initial,context:{id:null}},_b=[];function Ob(e){return _b.push(e),function(){_b.splice(_b.indexOf(e),1)}}function Ib(){_b.forEach((function(e){return e(Nb)}))}function Db(){window.clearTimeout(Sb),Sb=window.setTimeout((function(){qb({type:mb.Rest})}),wb)}function Lb(){window.clearTimeout(Sb)}function Ab(){window.clearTimeout(xb),xb=window.setTimeout((function(){return qb({type:mb.TimeComplete})}),Cb)}function Mb(){window.clearTimeout(xb)}function Rb(){Nb.context.id=null}function Fb(e){var n=void 0===e?{}:e,r=n.id,i=n.onPointerEnter,o=n.onPointerMove,a=n.onPointerLeave,s=n.onPointerDown,l=n.onMouseEnter,u=n.onMouseMove,c=n.onMouseLeave,d=n.onMouseDown,f=n.onFocus,p=n.onBlur,h=n.onKeyDown,m=n.disabled,v=n.ref,y=n.DEBUG_STYLE,b=String(rd(r)),E=(0,t.useState)(!!y||Gb(b,!0)),T=E[0],w=E[1],C=(0,t.useRef)(null),S=Ss(v,C),x=cd(C,{observe:T});function k(e,t){return"undefined"!=typeof window&&"PointerEvent"in window?e:xs(e,t)}function N(e){return g((function(t){"mouse"===t.pointerType&&e(t)}),"onPointerEvent")}function _(){qb({type:mb.MouseEnter,id:b})}function O(){qb({type:mb.MouseMove,id:b})}function I(){qb({type:mb.MouseLeave})}function D(){Nb.context.id===b&&qb({type:mb.MouseDown})}function L(){window.__REACH_DISABLE_TOOLTIPS||qb({type:mb.Focus,id:b})}function A(){Nb.context.id===b&&qb({type:mb.Blur})}function M(e){"Enter"!==e.key&&" "!==e.key||qb({type:mb.SelectWithKeyboard})}return(0,t.useEffect)((function(){return Ob((function(){w(Gb(b))}))}),[b]),(0,t.useEffect)((function(){var e=ms(C.current);function t(e){"Escape"!==e.key&&"Esc"!==e.key||Nb.value!==hb.Visible||qb({type:mb.SelectWithKeyboard})}return g(t,"listener"),e.addEventListener("keydown",t),function(){return e.removeEventListener("keydown",t)}}),[]),$b({disabled:m,isVisible:T,ref:C}),g(k,"wrapMouseEvent"),g(N,"wrapPointerEventHandler"),g(_,"handleMouseEnter"),g(O,"handleMouseMove"),g(I,"handleMouseLeave"),g(D,"handleMouseDown"),g(L,"handleFocus"),g(A,"handleBlur"),g(M,"handleKeyDown"),[{"aria-describedby":T?lf("tooltip",b):void 0,"data-state":T?"tooltip-visible":"tooltip-hidden","data-reach-tooltip-trigger":"",ref:S,onPointerEnter:xs(i,N(_)),onPointerMove:xs(o,N(O)),onPointerLeave:xs(a,N(I)),onPointerDown:xs(s,N(D)),onMouseEnter:k(l,_),onMouseMove:k(u,O),onMouseLeave:k(c,I),onMouseDown:k(d,D),onFocus:xs(f,L),onBlur:xs(p,A),onKeyDown:xs(h,M)},{id:b,triggerRect:x,isVisible:T},T]}g(Ob,"subscribe"),g(Ib,"notify"),g(Db,"startRestTimer"),g(Lb,"clearRestTimer"),g(Ab,"startLeavingVisibleTimer"),g(Mb,"clearLeavingVisibleTimer"),g(Rb,"clearContextId"),g(Fb,"useTooltip");var Pb=(0,t.forwardRef)((function(e,n){var r=e.children,i=e.label,o=e.ariaLabel,a=e.id,s=e.DEBUG_STYLE,l=sb(e,yb),u=t.Children.only(r),c=Fb({id:a,onPointerEnter:u.props.onPointerEnter,onPointerMove:u.props.onPointerMove,onPointerLeave:u.props.onPointerLeave,onPointerDown:u.props.onPointerDown,onMouseEnter:u.props.onMouseEnter,onMouseMove:u.props.onMouseMove,onMouseLeave:u.props.onMouseLeave,onMouseDown:u.props.onMouseDown,onFocus:u.props.onFocus,onBlur:u.props.onBlur,onKeyDown:u.props.onKeyDown,disabled:u.props.disabled,ref:u.ref,DEBUG_STYLE:s}),d=c[0],f=c[1];return(0,t.createElement)(t.Fragment,null,(0,t.cloneElement)(u,d),(0,t.createElement)(jb,ab({ref:n,label:i,"aria-label":o},f,l)))}));e.aQ=Pb;var jb=(0,t.forwardRef)(g((function(e,n){var r=e.label,i=e.ariaLabel,o=e.isVisible,a=e.id,s=sb(e,bb);return o?(0,t.createElement)(hs,null,(0,t.createElement)(Vb,ab({ref:n,label:r,"aria-label":i,isVisible:o},s,{id:lf("tooltip",String(a))}))):null}),"TooltipPopup")),Vb=(0,t.forwardRef)(g((function(e,n){var r=e.ariaLabel,i=e["aria-label"],o=e.as,a=void 0===o?"div":o,s=e.id,l=e.isVisible,u=e.label,c=e.position,d=void 0===c?Bb:c,f=e.style,p=e.triggerRect,h=sb(e,Eb),m=null!=(i||r),g=(0,t.useRef)(null),v=Ss(n,g),y=cd(g,{observe:l});return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(a,ab({role:m?void 0:"tooltip"},h,{ref:v,"data-reach-tooltip":"",id:m?void 0:s,style:ab({},f,Ub(d,p,y))}),u),m&&(0,t.createElement)(Kc,{role:"tooltip",id:s},i||r))}),"TooltipContent"));function Ub(e,t,n){return n?e(t,n):{visibility:"hidden"}}g(Ub,"getStyles");var Bb=g((function(e,t,n){void 0===n&&(n=8);var r=ob(),i=r.width,o=r.height;if(!e||!t)return{};var a={top:e.top-t.height<0,right:i{var n=e,{isActive:r}=n,i=v(n,["isActive"]);return de("div",m(h({},i),{ref:t,role:"tab","aria-selected":r,className:b("graphiql-tab",r&&"graphiql-tab-active",i.className),children:i.children}))}));zb.displayName="Tab";const Wb=(0,t.forwardRef)(((e,t)=>de(os,m(h({},e),{ref:t,type:"button",className:b("graphiql-tab-button",e.className),children:e.children}))));Wb.displayName="Tab.Button";const Kb=(0,t.forwardRef)(((e,t)=>de(Pb,{label:"Close Tab",children:de(os,m(h({"aria-label":"Close Tab"},e),{ref:t,type:"button",className:b("graphiql-tab-close",e.className),children:de(La,{})}))})));Kb.displayName="Tab.Close";const Qb=Xc(zb,{Button:Wb,Close:Kb});e.aO=Qb;const Xb=(0,t.forwardRef)(((e,t)=>de("div",m(h({},e),{ref:t,role:"tablist",className:b("graphiql-tabs",e.className),children:e.children}))));e.aP=Xb,Xb.displayName="Tabs";var Yb=Object.defineProperty,Jb=g(((e,t)=>Yb(e,"name",{value:t,configurable:!0})),"__name$C");const Zb=X("HistoryContext");function eE(e){var n;const r=ye(),i=(0,t.useRef)(new W(r||new H(null),e.maxHistoryLength||nE)),[o,a]=(0,t.useState)((null==(n=i.current)?void 0:n.queries)||[]),s=(0,t.useCallback)((e=>{let{query:t,variables:n,headers:r,operationName:o}=e;var s;null==(s=i.current)||s.updateHistory(t,n,r,o),a(i.current.queries)}),[]),l=(0,t.useCallback)((e=>{let{query:t,variables:n,headers:r,operationName:o,label:s,favorite:l}=e;i.current.editLabel(t,n,r,o,s,l),a(i.current.queries)}),[]),u=(0,t.useCallback)((e=>{let{query:t,variables:n,headers:r,operationName:o,label:s,favorite:l}=e;i.current.toggleFavorite(t,n,r,o,s,l),a(i.current.queries)}),[]),c=(0,t.useMemo)((()=>({addToHistory:s,editLabel:l,items:o,toggleFavorite:u})),[s,l,o,u]);return de(Zb.Provider,{value:c,children:e.children})}e.X=Zb,g(eE,"HistoryContextProvider"),Jb(eE,"HistoryContextProvider");const tE=Y(Zb);e.Z=tE;const nE=20;var rE=Object.defineProperty,iE=g(((e,t)=>rE(e,"name",{value:t,configurable:!0})),"__name$B");function oE(){const{items:e}=tE({nonNull:!0}),n=e.slice().reverse();return fe("section",{"aria-label":"History",className:"graphiql-history",children:[de("div",{className:"graphiql-history-header",children:"History"}),de("ul",{className:"graphiql-history-items",children:n.map(((e,r)=>fe(t.Fragment,{children:[de(aE,{item:e}),e.favorite&&n[r+1]&&!n[r+1].favorite?de("div",{className:"graphiql-history-item-spacer"}):null]},`${r}:${e.label||e.query}`)))})]})}function aE(e){const{editLabel:n,toggleFavorite:r}=tE({nonNull:!0,caller:aE}),{headerEditor:i,queryEditor:o,variableEditor:a}=oS({nonNull:!0,caller:aE}),s=(0,t.useRef)(null),l=(0,t.useRef)(null),[u,c]=(0,t.useState)(!1);(0,t.useEffect)((()=>{u&&s.current&&s.current.focus()}),[u]);const d=e.item.label||e.item.operationName||sE(e.item.query);return de("li",{className:b("graphiql-history-item",u&&"editable"),children:fe(pe,u?{children:[de("input",{type:"text",defaultValue:e.item.label,ref:s,onKeyDown:t=>{"Esc"===t.key?c(!1):"Enter"===t.key&&(c(!1),n(m(h({},e.item),{label:t.currentTarget.value})))},placeholder:"Type a label"}),de(os,{type:"button",ref:l,onClick:()=>{var t;c(!1),n(m(h({},e.item),{label:null==(t=s.current)?void 0:t.value}))},children:"Save"}),de(os,{type:"button",ref:l,onClick:()=>{c(!1)},children:de(La,{})})]}:{children:[de(os,{type:"button",className:"graphiql-history-item-label",onClick:()=>{var t,n,r;null==o||o.setValue(null!=(t=e.item.query)?t:""),null==a||a.setValue(null!=(n=e.item.variables)?n:""),null==i||i.setValue(null!=(r=e.item.headers)?r:"")},children:d}),de(Pb,{label:"Edit label",children:de(os,{type:"button",className:"graphiql-history-item-action",onClick:e=>{e.stopPropagation(),c(!0)},"aria-label":"Edit label",children:de(Wa,{"aria-hidden":"true"})})}),de(Pb,{label:e.item.favorite?"Remove favorite":"Add favorite",children:de(os,{type:"button",className:"graphiql-history-item-action",onClick:t=>{t.stopPropagation(),r(e.item)},"aria-label":e.item.favorite?"Remove favorite":"Add favorite",children:e.item.favorite?de(es,{"aria-hidden":"true"}):de(ts,{"aria-hidden":"true"})})})]})})}function sE(e){return null==e?void 0:e.split("\n").map((e=>e.replace(/#(.*)/,""))).join(" ").replaceAll("{"," { ").replaceAll("}"," } ").replaceAll(/[\s]{2,}/g," ")}g(oE,"History"),iE(oE,"History"),g(aE,"HistoryItem"),iE(aE,"HistoryItem"),g(sE,"formatQuery"),iE(sE,"formatQuery");var lE=Object.defineProperty,uE=g(((e,t)=>lE(e,"name",{value:t,configurable:!0})),"__name$A");const cE=X("ExecutionContext");function dE(e){let{fetcher:n,getDefaultFieldNames:i,children:o,operationName:a}=e;if(!n)throw new TypeError("The `ExecutionContextProvider` component requires a `fetcher` function to be passed as prop.");const{externalFragments:s,headerEditor:l,queryEditor:u,responseEditor:c,variableEditor:d,updateActiveTabValues:f}=oS({nonNull:!0,caller:dE}),p=tE(),m=fC({getDefaultFieldNames:i,caller:dE}),[g,y]=(0,t.useState)(!1),[b,E]=(0,t.useState)(null),T=(0,t.useRef)(0),w=(0,t.useCallback)((()=>{null==b||b.unsubscribe(),y(!1),E(null)}),[b]),x=(0,t.useCallback)((async()=>{var e,t;if(!u||!c)return;if(b)return void w();const i=uE((e=>{c.setValue(e),f({response:e})}),"setResponse");T.current+=1;const o=T.current;let g=m()||u.getValue();const x=null==d?void 0:d.getValue();let k;try{k=pE({json:x,errorMessageParse:"Variables are invalid JSON",errorMessageType:"Variables are not a JSON object."})}catch(e){return void i(e instanceof Error?e.message:`${e}`)}const N=null==l?void 0:l.getValue();let _;try{_=pE({json:N,errorMessageParse:"Headers are invalid JSON",errorMessageType:"Headers are not a JSON object."})}catch(e){return void i(e instanceof Error?e.message:`${e}`)}if(s){const e=u.documentAST?_o(u.documentAST,s):[];e.length>0&&(g+="\n"+e.map((e=>(0,r.print)(e))).join("\n"))}i(""),y(!0);const O=null!=(e=null!=a?a:u.operationName)?e:void 0;null==p||p.addToHistory({query:g,variables:x,headers:N,operationName:O});try{let e={data:{}};const r=uE((t=>{if(o!==T.current)return;let n=!!Array.isArray(t)&&t;if(!n&&"object"==typeof t&&null!==t&&"hasNext"in t&&(n=[t]),n){const t={data:e.data},r=[...(null==e?void 0:e.errors)||[],...n.flatMap((e=>e.errors)).filter(Boolean)];r.length&&(t.errors=r);for(const r of n){const n=r,{path:i,data:o,errors:a}=n,s=v(n,["path","data","errors"]);if(i){if(!o)throw new Error(`Expected part to contain a data property, but got ${r}`);Qo(t.data,i,o,{merge:!0})}else o&&(t.data=o);e=h(h({},t),s)}y(!1),i(L(e))}else{const e=L(t);y(!1),i(e)}}),"handleResponse"),a=n({query:g,variables:k,operationName:O},{headers:null!=_?_:void 0,documentAST:null!=(t=u.documentAST)?t:void 0}),s=await Promise.resolve(a);if(C(s))E(s.subscribe({next(e){r(e)},error(e){y(!1),e&&i(D(e)),E(null)},complete(){y(!1),E(null)}}));else if(S(s)){E({unsubscribe:()=>{var e,t;return null==(t=(e=s[Symbol.asyncIterator]()).return)?void 0:t.call(e)}});for await(const e of s)r(e);y(!1),E(null)}else r(s)}catch(e){y(!1),i(D(e)),E(null)}}),[m,s,n,l,p,a,u,c,w,b,f,d]),k=Boolean(b),N=(0,t.useMemo)((()=>({isFetching:g,isSubscribed:k,operationName:null!=a?a:null,run:x,stop:w})),[g,k,a,x,w]);return de(cE.Provider,{value:N,children:o})}e.r=cE,g(dE,"ExecutionContextProvider"),uE(dE,"ExecutionContextProvider");const fE=Y(cE);function pE(e){let t,{json:n,errorMessageParse:r,errorMessageType:i}=e;try{t=n&&""!==n.trim()?JSON.parse(n):void 0}catch(e){throw new Error(`${r}: ${e instanceof Error?e.message:e}.`)}const o="object"==typeof t&&null!==t&&!Array.isArray(t);if(void 0!==t&&!o)throw new Error(i);return t}e.v=fE,g(pE,"tryParseJsonObject"),uE(pE,"tryParseJsonObject");var hE=Object.defineProperty,mE=g(((e,t)=>hE(e,"name",{value:t,configurable:!0})),"__name$z");const gE="graphiql",vE="sublime";let yE=!1;"object"==typeof window&&(yE=0===window.navigator.platform.toLowerCase().indexOf("mac"));const bE={[yE?"Cmd-F":"Ctrl-F"]:"findPersistent","Cmd-G":"findPersistent","Ctrl-G":"findPersistent","Ctrl-Left":"goSubwordLeft","Ctrl-Right":"goSubwordRight","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight"};async function EE(e,t){const r=await Promise.resolve().then(n.t.bind(n,535,23)).then((function(e){return e.c})).then((e=>"function"==typeof e?e:e.default));return await Promise.all(!1===(null==t?void 0:t.useCommonAddons)?e:[Promise.resolve().then(n.t.bind(n,6980,23)).then((function(e){return e.s})),Promise.resolve().then(n.t.bind(n,9171,23)).then((function(e){return e.m})),Promise.resolve().then(n.t.bind(n,5728,23)).then((function(e){return e.c})),Promise.resolve().then(n.t.bind(n,4468,23)).then((function(e){return e.b})),Promise.resolve().then(n.t.bind(n,8419,23)).then((function(e){return e.f})),Promise.resolve().then(n.t.bind(n,4054,23)).then((function(e){return e.l})),Promise.resolve().then(n.t.bind(n,9407,23)).then((function(e){return e.s})),Promise.resolve().then(n.t.bind(n,4471,23)).then((function(e){return e.j})),Promise.resolve().then(n.t.bind(n,8058,23)).then((function(e){return e.d})),Promise.resolve().then(n.t.bind(n,2568,23)).then((function(e){return e.s})),...e]),r}g(EE,"importCodeMirror"),mE(EE,"importCodeMirror");var TE=g((function(){var e=document.getSelection();if(!e.rangeCount)return function(){};for(var t=document.activeElement,n=[],r=0;rNE(e,"name",{value:t,configurable:!0})),"__name$y");const OE=_E((e=>e?(0,r.print)(e):""),"printDefault");function IE(e){let{field:t}=e;if(!("defaultValue"in t)||void 0===t.defaultValue)return null;const n=(0,r.astFromValue)(t.defaultValue,t.type);return n?fe(pe,{children:[" = ",de("span",{className:"graphiql-doc-explorer-default-value",children:OE(n)})]}):null}g(IE,"DefaultValue"),_E(IE,"DefaultValue");var DE=Object.defineProperty,LE=g(((e,t)=>DE(e,"name",{value:t,configurable:!0})),"__name$x");const AE=X("SchemaContext");function ME(e){if(!e.fetcher)throw new TypeError("The `SchemaContextProvider` component requires a `fetcher` function to be passed as prop.");const{initialHeaders:n,headerEditor:i}=oS({nonNull:!0,caller:ME}),[o,a]=(0,t.useState)(),[s,l]=(0,t.useState)(!1),[u,c]=(0,t.useState)(null),d=(0,t.useRef)(0);(0,t.useEffect)((()=>{a((0,r.isSchema)(e.schema)||null===e.schema||void 0===e.schema?e.schema:void 0),d.current++}),[e.schema]);const f=(0,t.useRef)(n);(0,t.useEffect)((()=>{i&&(f.current=i.getValue())}));const{introspectionQuery:p,introspectionQueryName:h,introspectionQuerySansSubscriptions:m}=FE({inputValueDeprecation:e.inputValueDeprecation,introspectionQueryName:e.introspectionQueryName,schemaDescription:e.schemaDescription}),{fetcher:v,onSchemaChange:y,dangerouslyAssumeSchemaIsValid:b,children:E}=e,w=(0,t.useCallback)((()=>{if((0,r.isSchema)(e.schema)||null===e.schema)return;const t=++d.current,n=e.schema;async function i(){if(n)return n;const e=PE(f.current);if(!e.isValidJSON)return void c("Introspection failed as headers are invalid.");const t=e.headers?{headers:e.headers}:{},r=k(v({query:p,operationName:h},t));if(!T(r))return void c("Fetcher did not return a Promise for introspection.");l(!0),c(null);let i=await r;if("object"!=typeof i||null===i||!("data"in i)){const e=k(v({query:m,operationName:h},t));if(!T(e))throw new Error("Fetcher did not return a Promise for introspection.");i=await e}if(l(!1),(null==i?void 0:i.data)&&"__schema"in i.data)return i.data;const o="string"==typeof i?i:L(i);c(o)}g(i,"fetchIntrospectionData"),LE(i,"fetchIntrospectionData"),i().then((e=>{if(t===d.current&&e)try{const t=(0,r.buildClientSchema)(e);a(t),null==y||y(t)}catch(e){c(D(e))}})).catch((e=>{t===d.current&&(c(D(e)),l(!1))}))}),[v,h,p,m,y,e.schema]);(0,t.useEffect)((()=>{w()}),[w]),(0,t.useEffect)((()=>{function e(e){e.ctrlKey&&"R"===e.key&&w()}return g(e,"triggerIntrospection"),LE(e,"triggerIntrospection"),window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)}));const C=(0,t.useMemo)((()=>!o||b?[]:(0,r.validateSchema)(o)),[o,b]),S=(0,t.useMemo)((()=>({fetchError:u,introspect:w,isFetching:s,schema:o,validationErrors:C})),[u,w,s,o,C]);return de(AE.Provider,{value:S,children:E})}e.a4=AE,g(ME,"SchemaContextProvider"),LE(ME,"SchemaContextProvider");const RE=Y(AE);function FE(e){let{inputValueDeprecation:n,introspectionQueryName:i,schemaDescription:o}=e;return(0,t.useMemo)((()=>{const e=i||"IntrospectionQuery";let t=(0,r.getIntrospectionQuery)({inputValueDeprecation:n,schemaDescription:o});i&&(t=t.replace("query IntrospectionQuery",`query ${e}`));const a=t.replace("subscriptionType { name }","");return{introspectionQueryName:e,introspectionQuery:t,introspectionQuerySansSubscriptions:a}}),[n,i,o])}function PE(e){let t=null,n=!0;try{e&&(t=JSON.parse(e))}catch{n=!1}return{headers:t,isValidJSON:n}}e.a6=RE,g(FE,"useIntrospectionQuery"),LE(FE,"useIntrospectionQuery"),g(PE,"parseHeaderString"),LE(PE,"parseHeaderString");var jE=Object.defineProperty,VE=g(((e,t)=>jE(e,"name",{value:t,configurable:!0})),"__name$w");const UE={name:"Docs"},BE=X("ExplorerContext");function $E(e){const{schema:n,validationErrors:i}=RE({nonNull:!0,caller:$E}),[o,a]=(0,t.useState)([UE]),s=(0,t.useCallback)((e=>{a((t=>t.at(-1).def===e.def?t:[...t,e]))}),[]),l=(0,t.useCallback)((()=>{a((e=>e.length>1?e.slice(0,-1):e))}),[]),u=(0,t.useCallback)((()=>{a((e=>1===e.length?e:[UE]))}),[]);(0,t.useEffect)((()=>{null==n||i.length>0?u():a((e=>{if(1===e.length)return e;const t=[UE];let i=null;for(const o of e)if(o!==UE)if(o.def)if((0,r.isNamedType)(o.def)){const e=n.getType(o.def.name);if(!e)break;t.push({name:o.name,def:e}),i=e}else{if(null===i)break;if((0,r.isObjectType)(i)||(0,r.isInputObjectType)(i)){const e=i.getFields()[o.name];if(!e)break;t.push({name:o.name,def:e})}else{if((0,r.isScalarType)(i)||(0,r.isEnumType)(i)||(0,r.isInterfaceType)(i)||(0,r.isUnionType)(i))break;{const e=i;if(!e.args.find((e=>e.name===o.name)))break;t.push({name:o.name,def:e})}}}else i=null,t.push(o);return t}))}),[u,n,i]);const c=(0,t.useMemo)((()=>({explorerNavStack:o,push:s,pop:l,reset:u})),[o,s,l,u]);return de(BE.Provider,{value:c,children:e.children})}e.z=BE,g($E,"ExplorerContextProvider"),VE($E,"ExplorerContextProvider");const qE=Y(BE);e.U=qE;var HE=Object.defineProperty,GE=g(((e,t)=>HE(e,"name",{value:t,configurable:!0})),"__name$v");function zE(e,t){return(0,r.isNonNullType)(e)?fe(pe,{children:[zE(e.ofType,t),"!"]}):(0,r.isListType)(e)?fe(pe,{children:["[",zE(e.ofType,t),"]"]}):t(e)}g(zE,"renderType"),GE(zE,"renderType");var WE=Object.defineProperty,KE=g(((e,t)=>WE(e,"name",{value:t,configurable:!0})),"__name$u");function QE(e){const{push:t}=qE({nonNull:!0,caller:QE});return e.type?zE(e.type,(e=>de("a",{className:"graphiql-doc-explorer-type-name",onClick:n=>{n.preventDefault(),t({name:e.name,def:e})},href:"#",children:e.name}))):null}g(QE,"TypeLink"),KE(QE,"TypeLink");var XE=Object.defineProperty,YE=g(((e,t)=>XE(e,"name",{value:t,configurable:!0})),"__name$t");function JE(e){let{arg:t,showDefaultValue:n,inline:r}=e;const i=fe("span",{children:[de("span",{className:"graphiql-doc-explorer-argument-name",children:t.name}),": ",de(QE,{type:t.type}),!1!==n&&de(IE,{field:t})]});return r?i:fe("div",{className:"graphiql-doc-explorer-argument",children:[i,t.description?de(rb,{type:"description",children:t.description}):null,t.deprecationReason?fe("div",{className:"graphiql-doc-explorer-argument-deprecation",children:[de("div",{className:"graphiql-doc-explorer-argument-deprecation-label",children:"Deprecated"}),de(rb,{type:"deprecation",children:t.deprecationReason})]}):null]})}g(JE,"Argument"),YE(JE,"Argument");var ZE=Object.defineProperty,eT=g(((e,t)=>ZE(e,"name",{value:t,configurable:!0})),"__name$s");function tT(e){return e.children?fe("div",{className:"graphiql-doc-explorer-deprecation",children:[de("div",{className:"graphiql-doc-explorer-deprecation-label",children:"Deprecated"}),de(rb,{type:"deprecation",onlyShowFirstChild:!0,children:e.children})]}):null}g(tT,"DeprecationReason"),eT(tT,"DeprecationReason");var nT=Object.defineProperty,rT=g(((e,t)=>nT(e,"name",{value:t,configurable:!0})),"__name$r");function iT(e){let{directive:t}=e;return fe("span",{className:"graphiql-doc-explorer-directive",children:["@",t.name.value]})}g(iT,"Directive"),rT(iT,"Directive");var oT=Object.defineProperty,aT=g(((e,t)=>oT(e,"name",{value:t,configurable:!0})),"__name$q");function sT(e){const t=lT[e.title];return fe("div",{children:[fe("div",{className:"graphiql-doc-explorer-section-title",children:[de(t,{}),e.title]}),de("div",{className:"graphiql-doc-explorer-section-content",children:e.children})]})}g(sT,"ExplorerSection"),aT(sT,"ExplorerSection");const lT={Arguments:_a,"Deprecated Arguments":Ma,"Deprecated Enum Values":Ra,"Deprecated Fields":Fa,Directives:Pa,"Enum Values":Ua,Fields:Ba,Implements:qa,Implementations:rs,"Possible Types":rs,"Root Types":Ja,Type:rs,"All Schema Types":rs};var uT=Object.defineProperty,cT=g(((e,t)=>uT(e,"name",{value:t,configurable:!0})),"__name$p");function dT(e){return fe(pe,{children:[e.field.description?de(rb,{type:"description",children:e.field.description}):null,de(tT,{children:e.field.deprecationReason}),de(sT,{title:"Type",children:de(QE,{type:e.field.type})}),de(fT,{field:e.field}),de(pT,{field:e.field})]})}function fT(e){let{field:n}=e;const[r,i]=(0,t.useState)(!1);if(!("args"in n))return null;const o=[],a=[];for(const e of n.args)e.deprecationReason?a.push(e):o.push(e);return fe(pe,{children:[o.length>0?de(sT,{title:"Arguments",children:o.map((e=>de(JE,{arg:e},e.name)))}):null,a.length>0?r||0===o.length?de(sT,{title:"Deprecated Arguments",children:a.map((e=>de(JE,{arg:e},e.name)))}):de(as,{type:"button",onClick:()=>{i(!0)},children:"Show Deprecated Arguments"}):null]})}function pT(e){let{field:t}=e;var n;const r=(null==(n=t.astNode)?void 0:n.directives)||[];return r&&0!==r.length?de(sT,{title:"Directives",children:r.map((e=>de("div",{children:de(iT,{directive:e})},e.name.value)))}):null}g(dT,"FieldDocumentation"),cT(dT,"FieldDocumentation"),g(fT,"Arguments"),cT(fT,"Arguments"),g(pT,"Directives"),cT(pT,"Directives");var hT=Object.defineProperty,mT=g(((e,t)=>hT(e,"name",{value:t,configurable:!0})),"__name$o");function gT(e){var t,n,r,i;const o=e.schema.getQueryType(),a=null==(n=(t=e.schema).getMutationType)?void 0:n.call(t),s=null==(i=(r=e.schema).getSubscriptionType)?void 0:i.call(r),l=e.schema.getTypeMap(),u=[null==o?void 0:o.name,null==a?void 0:a.name,null==s?void 0:s.name];return fe(pe,{children:[de(rb,{type:"description",children:e.schema.description||"A GraphQL schema provides a root type for each kind of operation."}),fe(sT,{title:"Root Types",children:[o?fe("div",{children:[de("span",{className:"graphiql-doc-explorer-root-type",children:"query"}),": ",de(QE,{type:o})]}):null,a&&fe("div",{children:[de("span",{className:"graphiql-doc-explorer-root-type",children:"mutation"}),": ",de(QE,{type:a})]}),s&&fe("div",{children:[de("span",{className:"graphiql-doc-explorer-root-type",children:"subscription"}),": ",de(QE,{type:s})]})]}),de(sT,{title:"All Schema Types",children:l&&de("div",{children:Object.values(l).map((e=>u.includes(e.name)||e.name.startsWith("__")?null:de("div",{children:de(QE,{type:e})},e.name)))})})]})}function vT(e,n){var r=(0,t.useRef)(!1);(0,t.useEffect)((function(){r.current?e():r.current=!0}),n)}function yT(e,t){if(null==e)return{};var n,r,i={},o=Object.keys(e);for(r=0;r=0||(i[n]=e[n]);return i}function bT(){return bT=Object.assign||function(e){for(var t=1;tl&&e.push({highlight:!1,start:l,end:u}),o.index===s.lastIndex&&s.lastIndex++}return e}),[])}function CT(e){var t=e.chunksToHighlight,n=e.totalLength,r=[];if(0===t.length)o(0,n,!1);else{var i=0;t.forEach((function(e){o(i,e.start,!1),o(e.start,e.end,!0),i=e.end})),o(i,n,!1)}return r;function o(e,t,n){t-e>0&&r.push({start:e,end:t,highlight:n})}}function ST(e){return e}function xT(e){return e.replace(/[-[\]/{}()*+?.\\^$|]/g,"\\$&")}g(gT,"SchemaDocumentation"),mT(gT,"SchemaDocumentation"),g(vT,"useUpdateEffect"),g(yT,"_objectWithoutPropertiesLoose"),g(bT,"_extends"),g(ET,"findAll"),g(TT,"combineChunks"),g(wT,"defaultFindChunks"),g(CT,"fillInChunks"),g(ST,"defaultSanitize"),g(xT,"escapeRegExpFn");var kT,NT,_T,OT,IT,DT={combineChunks:TT,fillInChunks:CT,findAll:ET,findChunks:wT},LT=["onSelect","openOnFocus","children","as","aria-label","aria-labelledby"],AT=["as","selectOnClick","autocomplete","onClick","onChange","onKeyDown","onBlur","onFocus","value"],MT=["as","children","portal","onKeyDown","onBlur","position"],RT=["persistSelection","as"],FT=["as","children","index","value","onClick"],PT="IDLE",jT="SUGGESTING",VT="NAVIGATING",UT="INTERACTING",BT="CLEAR",$T="CHANGE",qT="INITIAL_CHANGE",HT="NAVIGATE",GT="SELECT_WITH_KEYBOARD",zT="SELECT_WITH_CLICK",WT="ESCAPE",KT="BLUR",QT="INTERACT",XT="FOCUS",YT="OPEN_WITH_BUTTON",JT="OPEN_WITH_INPUT_CLICK",ZT="CLOSE_WITH_BUTTON",ew={initial:PT,states:(IT={},IT[PT]={on:(kT={},kT[KT]=PT,kT[BT]=PT,kT[$T]=jT,kT[qT]=PT,kT[XT]=jT,kT[HT]=VT,kT[YT]=jT,kT[JT]=jT,kT)},IT[jT]={on:(NT={},NT[$T]=jT,NT[XT]=jT,NT[HT]=VT,NT[BT]=PT,NT[WT]=PT,NT[KT]=PT,NT[zT]=PT,NT[QT]=UT,NT[ZT]=PT,NT)},IT[VT]={on:(_T={},_T[$T]=jT,_T[XT]=jT,_T[BT]=PT,_T[KT]=PT,_T[WT]=PT,_T[HT]=VT,_T[zT]=PT,_T[GT]=PT,_T[ZT]=PT,_T[QT]=UT,_T)},IT[UT]={on:(OT={},OT[BT]=PT,OT[$T]=jT,OT[XT]=jT,OT[KT]=PT,OT[WT]=PT,OT[HT]=VT,OT[ZT]=PT,OT[zT]=PT,OT)},IT)},tw=g((function(e,t){var n=bT({},e,{lastEventType:t.type});switch(t.type){case $T:case qT:return bT({},n,{navigationValue:null,value:t.value});case HT:case YT:case JT:return bT({},n,{navigationValue:rw(n,t)});case BT:return bT({},n,{value:"",navigationValue:null});case KT:case WT:return bT({},n,{navigationValue:null});case zT:return bT({},n,{value:t.isControlled?e.value:t.value,navigationValue:null});case GT:return bT({},n,{value:t.isControlled?e.value:e.navigationValue,navigationValue:null});case ZT:return bT({},n,{navigationValue:null});case QT:return n;case XT:return bT({},n,{navigationValue:rw(n,t)});default:return n}}),"reducer");function nw(e){return[jT,VT,UT].includes(e)}function rw(e,t){return t.value?t.value:t.persistSelection?e.value:null}g(nw,"popoverIsExpanded"),g(rw,"findNavigationValue");var iw=zd(),ow=af(0,{}),aw=af(0,{}),sw=(0,t.forwardRef)((function(e,n){var r,i=e.onSelect,o=e.openOnFocus,a=void 0!==o&&o,s=e.children,l=e.as,u=void 0===l?"div":l,c=e["aria-label"],d=e["aria-labelledby"],f=yT(e,LT),p=Kd(),h=p[0],m=p[1],g=(0,t.useRef)(),v=(0,t.useRef)(),y=(0,t.useRef)(),b=(0,t.useRef)(!1),E=(0,t.useRef)(!1),T=gw(ew,tw,{value:"",navigationValue:null}),w=T[0],C=T[1],S=T[2];pw(C.lastEventType,g);var x=rd(f.id),k=x?lf("listbox",x):"listbox",N=(0,t.useRef)(!1),_=nw(w),O={ariaLabel:c,ariaLabelledby:d,autocompletePropRef:b,buttonRef:y,comboboxId:x,data:C,inputRef:g,isExpanded:_,listboxId:k,onSelect:i||bs,openOnFocus:a,persistSelectionRef:E,popoverRef:v,state:w,transition:S,isControlledRef:N};return(0,t.createElement)(Xd,{context:iw,items:h,set:m},(0,t.createElement)(ow.Provider,{value:O},(0,t.createElement)(u,bT({},f,{"data-reach-combobox":"","data-state":yw(w),"data-expanded":_||void 0,ref:n}),vs(s)?s({id:x,isExpanded:_,navigationValue:null!=(r=C.navigationValue)?r:null,state:w}):s)))})),lw=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?"input":r,o=e.selectOnClick,a=void 0!==o&&o,s=e.autocomplete,l=void 0===s||s,u=e.onClick,c=e.onChange,d=e.onKeyDown,f=e.onBlur,p=e.onFocus,h=e.value,m=yT(e,AT),v=(0,t.useRef)(h).current,y=(0,t.useRef)(!1);vT((function(){y.current=!0}),[h]);var b=(0,t.useContext)(ow),E=b.data,T=E.navigationValue,w=E.value,C=E.lastEventType,S=b.inputRef,x=b.state,k=b.transition,N=b.listboxId,_=b.autocompletePropRef,O=b.openOnFocus,I=b.isExpanded,D=b.ariaLabel,L=b.ariaLabelledby,A=b.persistSelectionRef,M=b.isControlledRef,R=Ss(S,n),F=(0,t.useRef)(!1),P=hw(),j=mw(),V=void 0!==h;(0,t.useEffect)((function(){M.current=V}),[V]),us((function(){_.current=l}),[l,_]);var U=(0,t.useCallback)((function(e){""===e.trim()?k(BT,{isControlled:V}):e!==v||y.current?k($T,{value:e}):k(qT,{value:e})}),[v,k,V]);function B(e){var t=e.target.value;V||U(t)}function $(){a&&(F.current=!0),O&&C!==zT&&k(XT,{persistSelection:A.current})}function q(){var e;F.current&&(F.current=!1,null==(e=S.current)||e.select()),O&&x===PT&&k(JT)}(0,t.useEffect)((function(){!V||h===w||""===h.trim()&&""===(w||"").trim()||U(h)}),[h,U,V,w]),g(B,"handleChange"),g($,"handleFocus"),g(q,"handleClick");var H=!l||x!==VT&&x!==UT?h||w:T||h||w;return(0,t.createElement)(i,bT({"aria-activedescendant":T?String(vw(T)):void 0,"aria-autocomplete":"both","aria-controls":N,"aria-expanded":I,"aria-haspopup":"listbox","aria-label":D,"aria-labelledby":D?void 0:L,role:"combobox"},m,{"data-reach-combobox-input":"","data-state":yw(x),ref:R,onBlur:xs(f,j),onChange:xs(c,B),onClick:xs(u,q),onFocus:xs(p,$),onKeyDown:xs(d,P),value:H||""}))})),uw=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?"div":r,o=e.children,a=e.portal,s=void 0===a||a,l=e.onKeyDown,u=e.onBlur,c=e.position,d=void 0===c?Ud:c,f=yT(e,MT),p=(0,t.useContext)(ow),h=p.popoverRef,m=p.inputRef,g=p.isExpanded,v=p.state,y=Ss(h,n),b=hw(),E=mw(),T={"data-reach-combobox-popover":"","data-state":yw(v),onKeyDown:xs(l,b),onBlur:xs(u,E),hidden:!g,tabIndex:-1,children:o};return s?(0,t.createElement)(Rd,bT({as:i},f,{ref:y,"data-expanded":g||void 0,position:d,targetRef:m,unstable_skipInitialPortalRender:!0},T)):(0,t.createElement)(i,bT({ref:y},f,T))})),cw=(0,t.forwardRef)((function(e,n){var r=e.persistSelection,i=void 0!==r&&r,o=e.as,a=void 0===o?"ul":o,s=yT(e,RT),l=(0,t.useContext)(ow),u=l.persistSelectionRef,c=l.listboxId;return i&&(u.current=!0),(0,t.createElement)(a,bT({role:"listbox"},s,{ref:n,"data-reach-combobox-list":"",id:c}))})),dw=(0,t.forwardRef)((function(e,n){var r=e.as,i=void 0===r?"li":r,o=e.children,a=e.index,s=e.value,l=e.onClick,u=yT(e,FT),c=(0,t.useContext)(ow),d=c.onSelect,f=c.data.navigationValue,p=c.transition,h=c.isControlledRef,m=uf((0,t.useRef)(null),null),v=m[0],y=m[1],b=Wd((0,t.useMemo)((function(){return{element:v,value:s}}),[s,v]),iw,a),E=Ss(n,y),T=f===s,w=g((function(){d&&d(s),p(zT,{value:s,isControlled:h.current})}),"handleClick");return(0,t.createElement)(aw.Provider,{value:{value:s,index:b}},(0,t.createElement)(i,bT({"aria-selected":T,role:"option"},u,{"data-reach-combobox-option":"",ref:E,id:String(vw(s)),"data-highlighted":T?"":void 0,tabIndex:-1,onClick:xs(l,w)}),o?vs(o)?o({value:s,index:b}):o:(0,t.createElement)(fw,null)))}));function fw(){var e=(0,t.useContext)(aw).value,n=(0,t.useContext)(ow).data.value,r=(0,t.useMemo)((function(){return DT.findAll({searchWords:bw(n||"").split(/\s+/),textToHighlight:e})}),[n,e]);return(0,t.createElement)(t.Fragment,null,r.length?r.map((function(n,r){var i=e.slice(n.start,n.end);return(0,t.createElement)("span",{key:r,"data-reach-combobox-option-text":"","data-user-value":!!n.highlight||void 0,"data-suggested-value":!n.highlight||void 0},i)})):e)}function pw(e,t){us((function(){var n;e!==HT&&e!==WT&&e!==zT&&e!==YT||null==(n=t.current)||n.focus()}),[t,e])}function hw(){var e=(0,t.useContext)(ow),n=e.data.navigationValue,r=e.onSelect,i=e.state,o=e.transition,a=e.autocompletePropRef,s=e.persistSelectionRef,l=e.isControlledRef,u=Qd(iw);return g((function(e){var t=u.findIndex((function(e){return e.value===n}));function c(){return t===u.length-1?a.current?null:f():u[(t+1)%u.length]}function d(){return 0===t?a.current?null:p():-1===t?p():u[(t-1+u.length)%u.length]}function f(){return u[0]}function p(){return u[u.length-1]}switch(g(c,"getNextOption"),g(d,"getPreviousOption"),g(f,"getFirstOption"),g(p,"getLastOption"),e.key){case"ArrowDown":if(e.preventDefault(),!u||!u.length)return;if(i===PT)o(HT,{persistSelection:s.current});else{var h=c();o(HT,{value:h?h.value:null})}break;case"ArrowUp":if(e.preventDefault(),!u||0===u.length)return;if(i===PT)o(HT);else{var m=d();o(HT,{value:m?m.value:null})}break;case"Home":case"PageUp":if(e.preventDefault(),!u||0===u.length)return;i===PT?o(HT):o(HT,{value:f().value});break;case"End":case"PageDown":if(e.preventDefault(),!u||0===u.length)return;i===PT?o(HT):o(HT,{value:p().value});break;case"Escape":i!==PT&&o(WT);break;case"Enter":i===VT&&null!==n&&(e.preventDefault(),r&&r(n),o(GT,{isControlled:l.current}))}}),"handleKeyDown")}function mw(){var e=(0,t.useContext)(ow),n=e.state,r=e.transition,i=e.popoverRef,o=e.inputRef,a=e.buttonRef;return g((function(e){var t=i.current,s=o.current,l=a.current,u=e.relatedTarget;u!==s&&u!==l&&t&&(t.contains(u)?n!==UT&&r(QT):r(KT))}),"handleBlur")}function gw(e,n,r){var i=(0,t.useState)(e.initial),o=i[0],a=i[1],s=(0,t.useReducer)(n,r),l=s[0],u=s[1];return[o,l,g((function(t,n){void 0===n&&(n={});var r=e.states[o],i=r&&r.on[t];if(i)return u(bT({type:t,state:o,nextState:o},n)),void a(i)}),"transition")]}function vw(e){var t=0;if(0===e.length)return t;for(var n=0;nEw(e,"name",{value:t,configurable:!0})),"__name$n");function ww(e,t){let n;return function(){for(var r=arguments.length,i=new Array(r),o=0;o{n=null,t(...i)}),e)}}g(ww,"debounce"),Tw(ww,"debounce");var Cw=Object.defineProperty,Sw=g(((e,t)=>Cw(e,"name",{value:t,configurable:!0})),"__name$m");function xw(){const{explorerNavStack:e,push:n}=qE({nonNull:!0,caller:xw}),i=(0,t.useRef)(null),o=(0,t.useRef)(null),a=kw(),[s,l]=(0,t.useState)(""),[u,c]=(0,t.useState)(a(s)),d=(0,t.useMemo)((()=>ww(200,(e=>{c(a(e))}))),[a]);(0,t.useEffect)((()=>{d(s)}),[d,s]),(0,t.useEffect)((()=>{function e(e){var t;e.metaKey&&"k"===e.key&&(null==(t=i.current)||t.focus())}return g(e,"handleKeyDown"),Sw(e,"handleKeyDown"),window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)}),[]);const f=e.at(-1);return 1===e.length||(0,r.isObjectType)(f.def)||(0,r.isInterfaceType)(f.def)||(0,r.isInputObjectType)(f.def)?fe(sw,{"aria-label":`Search ${f.name}...`,onSelect:e=>{const t=e;n("field"in t?{name:t.field.name,def:t.field}:{name:t.type.name,def:t.type})},children:[fe("div",{className:"graphiql-doc-explorer-search-input",onClick:()=>{i.current&&i.current.focus()},children:[de(Ga,{}),de(lw,{autocomplete:!1,onChange:e=>{l(e.target.value)},onKeyDown:e=>{if(!e.isDefaultPrevented()){const e=o.current;if(!e)return;window.requestAnimationFrame((()=>{const t=e.querySelector("[aria-selected=true]");if(!(t instanceof HTMLElement))return;const n=t.offsetTop-e.scrollTop,r=e.scrollTop+e.clientHeight-(t.offsetTop+t.clientHeight);r<0&&(e.scrollTop-=r),n<0&&(e.scrollTop+=n)}))}e.stopPropagation()},placeholder:"⌘ K",ref:i,value:s})]}),de(uw,{portal:!1,ref:o,children:fe(cw,{children:[u.within.map(((e,t)=>de(dw,{index:t,value:e,children:de(Ow,{field:e.field,argument:e.argument})},`within-${t}`))),u.within.length>0&&u.types.length+u.fields.length>0?de("div",{className:"graphiql-doc-explorer-search-divider",children:"Other results"}):null,u.types.map(((e,t)=>de(dw,{index:u.within.length+t,value:e,children:de(_w,{type:e.type})},`type-${t}`))),u.fields.map(((e,t)=>fe(dw,{index:u.within.length+u.types.length+t,value:e,children:[de(_w,{type:e.type}),".",de(Ow,{field:e.field,argument:e.argument})]},`field-${t}`))),u.within.length+u.types.length+u.fields.length===0?de("div",{className:"graphiql-doc-explorer-search-empty",children:"No results found"}):null]})})]}):null}function kw(e){const{explorerNavStack:n}=qE({nonNull:!0,caller:e||kw}),{schema:i}=RE({nonNull:!0,caller:e||kw}),o=n.at(-1);return(0,t.useCallback)((e=>{const t={within:[],types:[],fields:[]};if(!i)return t;const n=o.def,a=i.getTypeMap();let s=Object.keys(a);n&&(s=s.filter((e=>e!==n.name)),s.unshift(n.name));for(const i of s){if(t.within.length+t.types.length+t.fields.length>=100)break;const o=a[i];if(n!==o&&Nw(i,e)&&t.types.push({type:o}),!(0,r.isObjectType)(o)&&!(0,r.isInterfaceType)(o)&&!(0,r.isInputObjectType)(o))continue;const s=o.getFields();for(const r in s){const i=s[r];let a;if(!Nw(r,e)){if(!("args"in i))continue;if(a=i.args.filter((t=>Nw(t.name,e))),0===a.length)continue}t[n===o?"within":"fields"].push(...a?a.map((e=>({type:o,field:i,argument:e}))):[{type:o,field:i}])}}return t}),[o.def,i])}function Nw(e,t){try{const n=t.replaceAll(/[^_0-9A-Za-z]/g,(e=>"\\"+e));return-1!==e.search(new RegExp(n,"i"))}catch{return e.toLowerCase().includes(t.toLowerCase())}}function _w(e){return de("span",{className:"graphiql-doc-explorer-search-type",children:e.type.name})}function Ow(e){return fe(pe,{children:[de("span",{className:"graphiql-doc-explorer-search-field",children:e.field.name}),e.argument?fe(pe,{children:["(",de("span",{className:"graphiql-doc-explorer-search-argument",children:e.argument.name}),":"," ",zE(e.argument.type,(e=>de(_w,{type:e}))),")"]}):null]})}g(xw,"Search"),Sw(xw,"Search"),g(kw,"useSearchResults"),Sw(kw,"useSearchResults"),g(Nw,"isMatch"),Sw(Nw,"isMatch"),g(_w,"Type"),Sw(_w,"Type"),g(Ow,"Field$1"),Sw(Ow,"Field");var Iw=Object.defineProperty,Dw=g(((e,t)=>Iw(e,"name",{value:t,configurable:!0})),"__name$l");function Lw(e){const{push:t}=qE({nonNull:!0});return de("a",{className:"graphiql-doc-explorer-field-name",onClick:n=>{n.preventDefault(),t({name:e.field.name,def:e.field})},href:"#",children:e.field.name})}g(Lw,"FieldLink"),Dw(Lw,"FieldLink");var Aw=Object.defineProperty,Mw=g(((e,t)=>Aw(e,"name",{value:t,configurable:!0})),"__name$k");function Rw(e){return(0,r.isNamedType)(e.type)?fe(pe,{children:[e.type.description?de(rb,{type:"description",children:e.type.description}):null,de(Fw,{type:e.type}),de(Pw,{type:e.type}),de(Vw,{type:e.type}),de(Bw,{type:e.type})]}):null}function Fw(e){let{type:t}=e;return(0,r.isObjectType)(t)&&t.getInterfaces().length>0?de(sT,{title:"Implements",children:t.getInterfaces().map((e=>de("div",{children:de(QE,{type:e})},e.name)))}):null}function Pw(e){let{type:n}=e;const[i,o]=(0,t.useState)(!1);if(!(0,r.isObjectType)(n)&&!(0,r.isInterfaceType)(n)&&!(0,r.isInputObjectType)(n))return null;const a=n.getFields(),s=[],l=[];for(const e of Object.keys(a).map((e=>a[e])))e.deprecationReason?l.push(e):s.push(e);return fe(pe,{children:[s.length>0?de(sT,{title:"Fields",children:s.map((e=>de(jw,{field:e},e.name)))}):null,l.length>0?i||0===s.length?de(sT,{title:"Deprecated Fields",children:l.map((e=>de(jw,{field:e},e.name)))}):de(as,{type:"button",onClick:()=>{o(!0)},children:"Show Deprecated Fields"}):null]})}function jw(e){let{field:t}=e;const n="args"in t?t.args.filter((e=>!e.deprecationReason)):[];return fe("div",{className:"graphiql-doc-explorer-item",children:[fe("div",{children:[de(Lw,{field:t}),n.length>0?fe(pe,{children:["(",de("span",{children:n.map((e=>1===n.length?de(JE,{arg:e,inline:!0},e.name):de("div",{className:"graphiql-doc-explorer-argument-multiple",children:de(JE,{arg:e,inline:!0})},e.name)))}),")"]}):null,": ",de(QE,{type:t.type}),de(IE,{field:t})]}),t.description?de(rb,{type:"description",onlyShowFirstChild:!0,children:t.description}):null,de(tT,{children:t.deprecationReason})]})}function Vw(e){let{type:n}=e;const[i,o]=(0,t.useState)(!1);if(!(0,r.isEnumType)(n))return null;const a=[],s=[];for(const e of n.getValues())e.deprecationReason?s.push(e):a.push(e);return fe(pe,{children:[a.length>0?de(sT,{title:"Enum Values",children:a.map((e=>de(Uw,{value:e},e.name)))}):null,s.length>0?i||0===a.length?de(sT,{title:"Deprecated Enum Values",children:s.map((e=>de(Uw,{value:e},e.name)))}):de(as,{type:"button",onClick:()=>{o(!0)},children:"Show Deprecated Values"}):null]})}function Uw(e){let{value:t}=e;return fe("div",{className:"graphiql-doc-explorer-item",children:[de("div",{className:"graphiql-doc-explorer-enum-value",children:t.name}),t.description?de(rb,{type:"description",children:t.description}):null,t.deprecationReason?de(rb,{type:"deprecation",children:t.deprecationReason}):null]})}function Bw(e){let{type:t}=e;const{schema:n}=RE({nonNull:!0});return n&&(0,r.isAbstractType)(t)?de(sT,{title:(0,r.isInterfaceType)(t)?"Implementations":"Possible Types",children:n.getPossibleTypes(t).map((e=>de("div",{children:de(QE,{type:e})},e.name)))}):null}g(Rw,"TypeDocumentation"),Mw(Rw,"TypeDocumentation"),g(Fw,"ImplementsInterfaces"),Mw(Fw,"ImplementsInterfaces"),g(Pw,"Fields"),Mw(Pw,"Fields"),g(jw,"Field"),Mw(jw,"Field"),g(Vw,"EnumValues"),Mw(Vw,"EnumValues"),g(Uw,"EnumValue"),Mw(Uw,"EnumValue"),g(Bw,"PossibleTypes"),Mw(Bw,"PossibleTypes");var $w=Object.defineProperty,qw=g(((e,t)=>$w(e,"name",{value:t,configurable:!0})),"__name$j");function Hw(){const{fetchError:e,isFetching:t,schema:n,validationErrors:i}=RE({nonNull:!0,caller:Hw}),{explorerNavStack:o,pop:a}=qE({nonNull:!0,caller:Hw}),s=o.at(-1);let l,u=null;return e?u=de("div",{className:"graphiql-doc-explorer-error",children:"Error fetching schema"}):i.length>0?u=fe("div",{className:"graphiql-doc-explorer-error",children:["Schema is invalid: ",i[0].message]}):t?u=de(ib,{}):n?1===o.length?u=de(gT,{schema:n}):(0,r.isType)(s.def)?u=de(Rw,{type:s.def}):s.def&&(u=de(dT,{field:s.def})):u=de("div",{className:"graphiql-doc-explorer-error",children:"No GraphQL schema available"}),o.length>1&&(l=o.at(-2).name),fe("section",{className:"graphiql-doc-explorer","aria-label":"Documentation Explorer",children:[fe("div",{className:"graphiql-doc-explorer-header",children:[fe("div",{className:"graphiql-doc-explorer-header-content",children:[l&&fe("a",{href:"#",className:"graphiql-doc-explorer-back",onClick:e=>{e.preventDefault(),a()},"aria-label":`Go back to ${l}`,children:[de(Ia,{}),l]}),de("div",{className:"graphiql-doc-explorer-title",children:s.name})]}),de("div",{className:"graphiql-doc-explorer-search",children:de(xw,{},s.name)})]}),de("div",{className:"graphiql-doc-explorer-content",children:u})]})}g(Hw,"DocExplorer"),qw(Hw,"DocExplorer");var Gw=Object.defineProperty,zw=g(((e,t)=>Gw(e,"name",{value:t,configurable:!0})),"__name$i");const Ww={title:"Documentation Explorer",icon:zw(g((function(){const e=Yw();return(null==e?void 0:e.visiblePlugin)===Ww?de(ja,{}):de(Va,{})}),"Icon"),"Icon"),content:Hw};e._=Ww;const Kw={title:"History",icon:$a,content:oE};e.$=Kw;const Qw=X("PluginContext");function Xw(e){const n=ye(),r=qE(),i=tE(),o=Boolean(r),a=Boolean(i),s=(0,t.useMemo)((()=>{const t=[],n={};o&&(t.push(Ww),n[Ww.title]=!0),a&&(t.push(Kw),n[Kw.title]=!0);for(const r of e.plugins||[]){if("string"!=typeof r.title||!r.title)throw new Error("All GraphiQL plugins must have a unique title");if(n[r.title])throw new Error(`All GraphiQL plugins must have a unique title, found two plugins with the title '${r.title}'`);t.push(r),n[r.title]=!0}return t}),[o,a,e.plugins]),[l,u]=(0,t.useState)((()=>{const t=null==n?void 0:n.get(Jw);return s.find((e=>e.title===t))||(t&&(null==n||n.set(Jw,"")),e.visiblePlugin&&s.find((t=>("string"==typeof e.visiblePlugin?t.title:t)===e.visiblePlugin))||null)})),{onTogglePluginVisibility:c,children:d}=e,f=(0,t.useCallback)((e=>{const t=e&&s.find((t=>("string"==typeof e?t.title:t)===e))||null;u((e=>t===e?e:(null==c||c(t),t)))}),[c,s]);(0,t.useEffect)((()=>{e.visiblePlugin&&f(e.visiblePlugin)}),[s,e.visiblePlugin,f]);const p=(0,t.useMemo)((()=>({plugins:s,setVisiblePlugin:f,visiblePlugin:l})),[s,f,l]);return de(Qw.Provider,{value:p,children:d})}e.a0=Qw,g(Xw,"PluginContextProvider"),zw(Xw,"PluginContextProvider");const Yw=Y(Qw);e.a2=Yw;const Jw="visiblePlugin";var Zw=Object.defineProperty,eC=g(((e,t)=>Zw(e,"name",{value:t,configurable:!0})),"__name$h");function tC(e,t,n,i,o,a){function s(e){if(!(n&&i&&o&&e.currentTarget instanceof HTMLElement))return;const t=e.currentTarget.innerText,r=n.getType(t);r&&(o.setVisiblePlugin(Ww),i.push({name:r.name,def:r}),null==a||a(r))}EE([],{useCommonAddons:!1}).then((e=>{let n,i,o,a,l,u,c,d,f;e.on(t,"select",((e,t)=>{if(!n){const e=t.parentNode;n=document.createElement("div"),n.className="CodeMirror-hint-information",e.append(n);const r=document.createElement("header");r.className="CodeMirror-hint-information-header",n.append(r),i=document.createElement("span"),i.className="CodeMirror-hint-information-field-name",r.append(i),o=document.createElement("span"),o.className="CodeMirror-hint-information-type-name-pill",r.append(o),a=document.createElement("span"),o.append(a),l=document.createElement("a"),l.className="CodeMirror-hint-information-type-name",l.href="javascript:void 0",l.addEventListener("click",s),o.append(l),u=document.createElement("span"),o.append(u),c=document.createElement("div"),c.className="CodeMirror-hint-information-description",n.append(c),d=document.createElement("div"),d.className="CodeMirror-hint-information-deprecation",n.append(d);const p=document.createElement("span");p.className="CodeMirror-hint-information-deprecation-label",p.innerText="Deprecated",d.append(p),f=document.createElement("div"),f.className="CodeMirror-hint-information-deprecation-reason",d.append(f);const h=parseInt(window.getComputedStyle(n).paddingBottom.replace(/px$/,""),10)||0,m=parseInt(window.getComputedStyle(n).maxHeight.replace(/px$/,""),10)||0,g=eC((()=>{n&&(n.style.paddingTop=e.scrollTop+h+"px",n.style.maxHeight=e.scrollTop+m+"px")}),"handleScroll");let v;e.addEventListener("scroll",g),e.addEventListener("DOMNodeRemoved",v=eC((t=>{t.target===e&&(e.removeEventListener("scroll",g),e.removeEventListener("DOMNodeRemoved",v),n&&n.removeEventListener("click",s),n=null,i=null,o=null,a=null,l=null,u=null,c=null,d=null,f=null,v=null)}),"onRemoveFn"))}if(i&&(i.innerText=e.text),o&&a&&l&&u)if(e.type){o.style.display="inline";const t=eC((e=>{(0,r.isNonNullType)(e)?(u.innerText="!"+u.innerText,t(e.ofType)):(0,r.isListType)(e)?(a.innerText+="[",u.innerText="]"+u.innerText,t(e.ofType)):l.innerText=e.name}),"renderType");a.innerText="",u.innerText="",t(e.type)}else a.innerText="",l.innerText="",u.innerText="",o.style.display="none";c&&(e.description?(c.style.display="block",c.innerHTML=nb.render(e.description)):(c.style.display="none",c.innerHTML="")),d&&f&&(e.deprecationReason?(d.style.display="block",f.innerHTML=nb.render(e.deprecationReason)):(d.style.display="none",f.innerHTML=""))}))})),g(s,"onClickHintInformation"),eC(s,"onClickHintInformation")}g(tC,"onHasCompletion"),eC(tC,"onHasCompletion");var nC=Object.defineProperty,rC=g(((e,t)=>nC(e,"name",{value:t,configurable:!0})),"__name$g");function iC(e,n){(0,t.useEffect)((()=>{e&&"string"==typeof n&&n!==e.getValue()&&e.setValue(n)}),[e,n])}function oC(e,n,r){(0,t.useEffect)((()=>{e&&e.setOption(n,r)}),[e,n,r])}function aC(e,n,r,i,o){const{updateActiveTabValues:a}=oS({nonNull:!0,caller:o}),s=ye();(0,t.useEffect)((()=>{if(!e)return;const t=ww(500,(e=>{s&&null!==r&&s.set(r,e)})),o=ww(100,(e=>{a({[i]:e})})),l=rC(((e,r)=>{if(!r)return;const i=e.getValue();t(i),o(i),null==n||n(i)}),"handleChange");return e.on("change",l),()=>e.off("change",l)}),[n,e,s,r,i,a])}function sC(e,n,r){const{schema:i}=RE({nonNull:!0,caller:r}),o=qE(),a=Yw();(0,t.useEffect)((()=>{if(!e)return;const t=rC(((e,t)=>{tC(0,t,i,o,a,(e=>{null==n||n({kind:"Type",type:e,schema:i||void 0})}))}),"handleCompletion");return e.on("hasCompletion",t),()=>e.off("hasCompletion",t)}),[n,e,o,a,i])}function lC(e,n,r){(0,t.useEffect)((()=>{if(e){for(const t of n)e.removeKeyMap(t);if(r){const t={};for(const e of n)t[e]=()=>r();e.addKeyMap(t)}}}),[e,n,r])}function uC(){let{caller:e,onCopyQuery:n}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{queryEditor:r}=oS({nonNull:!0,caller:e||uC});return(0,t.useCallback)((()=>{if(!r)return;const e=r.getValue();kE(e),null==n||n(e)}),[r,n])}function cC(){let{caller:e}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{queryEditor:n}=oS({nonNull:!0,caller:e||cC}),{schema:i}=RE({nonNull:!0,caller:cC});return(0,t.useCallback)((()=>{const e=null==n?void 0:n.documentAST,t=null==n?void 0:n.getValue();e&&t&&n.setValue((0,r.print)(B(e,i)))}),[n,i])}function dC(){let{caller:e}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{queryEditor:n,headerEditor:i,variableEditor:o}=oS({nonNull:!0,caller:e||dC});return(0,t.useCallback)((()=>{if(o){const e=o.getValue();try{const t=JSON.stringify(JSON.parse(e),null,2);t!==e&&o.setValue(t)}catch{}}if(i){const e=i.getValue();try{const t=JSON.stringify(JSON.parse(e),null,2);t!==e&&i.setValue(t)}catch{}}if(n){const e=n.getValue(),t=(0,r.print)((0,r.parse)(e));t!==e&&n.setValue(t)}}),[n,o,i])}function fC(){let{getDefaultFieldNames:e,caller:n}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{schema:r}=RE({nonNull:!0,caller:n||fC}),{queryEditor:i}=oS({nonNull:!0,caller:n||fC});return(0,t.useCallback)((()=>{if(!i)return;const t=i.getValue(),{insertions:n,result:o}=A(r,t,e);return n&&n.length>0&&i.operation((()=>{const e=i.getCursor(),t=i.indexFromPos(e);i.setValue(o||"");let r=0;const a=n.map((e=>{let{index:t,string:n}=e;return i.markText(i.posFromIndex(t+r),i.posFromIndex(t+(r+=n.length)),{className:"auto-inserted-leaf",clearOnEnter:!0,title:"Automatically added leaf fields"})}));setTimeout((()=>{for(const e of a)e.clear()}),7e3);let s=t;for(const{index:e,string:r}of n)epC(e,"name",{value:t,configurable:!0})),"__name$f");function mC(){let{editorTheme:e=gE,keyMap:r=vE,onEdit:i,readOnly:o=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},a=arguments.length>1?arguments[1]:void 0;const{initialHeaders:s,headerEditor:l,setHeaderEditor:u,shouldPersistHeaders:c}=oS({nonNull:!0,caller:a||mC}),d=fE(),f=cC({caller:a||mC}),p=dC({caller:a||mC}),h=(0,t.useRef)(null);return(0,t.useEffect)((()=>{let t=!0;return EE([Promise.resolve().then(n.t.bind(n,8888,23)).then((function(e){return e.j}))]).then((n=>{if(!t)return;const r=h.current;if(!r)return;const i=n(r,{value:s,lineNumbers:!0,tabSize:2,mode:{name:"javascript",json:!0},theme:e,autoCloseBrackets:!0,matchBrackets:!0,showCursorWhenSelecting:!0,readOnly:!!o&&"nocursor",foldGutter:!0,gutters:["CodeMirror-linenumbers","CodeMirror-foldgutter"],extraKeys:bE});i.addKeyMap({"Cmd-Space"(){i.showHint({completeSingle:!1,container:r})},"Ctrl-Space"(){i.showHint({completeSingle:!1,container:r})},"Alt-Space"(){i.showHint({completeSingle:!1,container:r})},"Shift-Space"(){i.showHint({completeSingle:!1,container:r})}}),i.on("keyup",((e,t)=>{const{keyCode:n,key:r,shiftKey:i}=t;(n>=65&&n<=90||!i&&n>=48&&n<=57||"_"===r||'"'===r)&&e.execCommand("autocomplete")})),u(i)})),()=>{t=!1}}),[e,s,o,u]),oC(l,"keyMap",r),aC(l,i,c?gC:null,"headers",mC),lC(l,["Cmd-Enter","Ctrl-Enter"],null==d?void 0:d.run),lC(l,["Shift-Ctrl-P"],p),lC(l,["Shift-Ctrl-M"],f),h}g(mC,"useHeaderEditor"),hC(mC,"useHeaderEditor");const gC="headers";var vC=Object.defineProperty,yC=g(((e,t)=>vC(e,"name",{value:t,configurable:!0})),"__name$e");const bC=Array.from({length:11},((e,t)=>String.fromCharCode(8192+t))).concat(["\u2028","\u2029"," "," "]),EC=new RegExp("["+bC.join("")+"]","g");function TC(e){return e.replace(EC," ")}g(TC,"normalizeWhitespace"),yC(TC,"normalizeWhitespace");var wC=Object.defineProperty,CC=g(((e,t)=>wC(e,"name",{value:t,configurable:!0})),"__name$d");function SC(){let{editorTheme:e=gE,keyMap:r=vE,onClickReference:i,onCopyQuery:o,onEdit:a,readOnly:s=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},l=arguments.length>1?arguments[1]:void 0;const{schema:u}=RE({nonNull:!0,caller:l||SC}),{externalFragments:c,initialQuery:d,queryEditor:f,setOperationName:p,setQueryEditor:v,validationRules:y,variableEditor:b,updateActiveTabValues:E}=oS({nonNull:!0,caller:l||SC}),T=fE(),w=ye(),C=qE(),S=Yw(),x=uC({caller:l||SC,onCopyQuery:o}),k=cC({caller:l||SC}),N=dC({caller:l||SC}),_=(0,t.useRef)(null),O=(0,t.useRef)(),I=(0,t.useRef)((()=>{}));(0,t.useEffect)((()=>{I.current=e=>{if(C&&S){switch(S.setVisiblePlugin(Ww),e.kind){case"Type":C.push({name:e.type.name,def:e.type});break;case"Field":C.push({name:e.field.name,def:e.field});break;case"Argument":e.field&&C.push({name:e.field.name,def:e.field});break;case"EnumValue":e.type&&C.push({name:e.type.name,def:e.type})}null==i||i(e)}}}),[C,i,S]),(0,t.useEffect)((()=>{let t=!0;return EE([Promise.resolve().then(n.t.bind(n,6754,23)).then((function(e){return e.c})),Promise.resolve().then(n.t.bind(n,9509,23)).then((function(e){return e.s})),Promise.resolve().then(n.t.bind(n,4840,23)),Promise.resolve().then(n.t.bind(n,9444,23)),Promise.resolve().then(n.t.bind(n,9890,23)),Promise.resolve().then(n.t.bind(n,5980,23)),Promise.resolve().then(n.t.bind(n,9863,23))]).then((n=>{if(!t)return;O.current=n;const r=_.current;if(!r)return;const i=n(r,{value:d,lineNumbers:!0,tabSize:2,foldGutter:!0,mode:"graphql",theme:e,autoCloseBrackets:!0,matchBrackets:!0,showCursorWhenSelecting:!0,readOnly:!!s&&"nocursor",lint:{schema:void 0,validationRules:null,externalFragments:void 0},hintOptions:{schema:void 0,closeOnUnfocus:!1,completeSingle:!1,container:r,externalFragments:void 0},info:{schema:void 0,renderDescription:e=>nb.render(e),onClick:e=>{I.current(e)}},jump:{schema:void 0,onClick:e=>{I.current(e)}},gutters:["CodeMirror-linenumbers","CodeMirror-foldgutter"],extraKeys:m(h({},bE),{"Cmd-S"(){},"Ctrl-S"(){}})});i.addKeyMap({"Cmd-Space"(){i.showHint({completeSingle:!0,container:r})},"Ctrl-Space"(){i.showHint({completeSingle:!0,container:r})},"Alt-Space"(){i.showHint({completeSingle:!0,container:r})},"Shift-Space"(){i.showHint({completeSingle:!0,container:r})},"Shift-Alt-Space"(){i.showHint({completeSingle:!0,container:r})}}),i.on("keyup",((e,t)=>{_C.test(t.key)&&e.execCommand("autocomplete")}));let o=!1;i.on("startCompletion",(()=>{o=!0})),i.on("endCompletion",(()=>{o=!1})),i.on("keydown",((e,t)=>{"Escape"===t.key&&o&&t.stopPropagation()})),i.on("beforeChange",((e,t)=>{var n;if("paste"===t.origin){const e=t.text.map(TC);null==(n=t.update)||n.call(t,t.from,t.to,e)}})),i.documentAST=null,i.operationName=null,i.operations=null,i.variableToType=null,v(i)})),()=>{t=!1}}),[e,d,s,v]),oC(f,"keyMap",r),(0,t.useEffect)((()=>{if(!f)return;function e(e){var t,n,r,i,o;const a=Do(u,e.getValue()),s=$(null!=(t=e.operations)?t:void 0,null!=(n=e.operationName)?n:void 0,null==a?void 0:a.operations);return e.documentAST=null!=(r=null==a?void 0:a.documentAST)?r:null,e.operationName=null!=s?s:null,e.operations=null!=(i=null==a?void 0:a.operations)?i:null,b&&(b.state.lint.linterOptions.variableToType=null==a?void 0:a.variableToType,b.options.lint.variableToType=null==a?void 0:a.variableToType,b.options.hintOptions.variableToType=null==a?void 0:a.variableToType,null==(o=O.current)||o.signal(b,"change",b)),a?m(h({},a),{operationName:s}):null}g(e,"getAndUpdateOperationFacts"),CC(e,"getAndUpdateOperationFacts");const t=ww(100,(t=>{var n;const r=t.getValue();null==w||w.set(OC,r);const i=t.operationName,o=e(t);void 0!==(null==o?void 0:o.operationName)&&(null==w||w.set(IC,o.operationName)),null==a||a(r,null==o?void 0:o.documentAST),(null==o?void 0:o.operationName)&&i!==o.operationName&&p(o.operationName),E({query:r,operationName:null!=(n=null==o?void 0:o.operationName)?n:null})}));return e(f),f.on("change",t),()=>f.off("change",t)}),[a,f,u,p,w,b,E]),xC(f,null!=u?u:null,O),kC(f,null!=y?y:null,O),NC(f,c,O),sC(f,i||null,SC);const D=null==T?void 0:T.run,L=(0,t.useCallback)((()=>{var e;if(!(D&&f&&f.operations&&f.hasFocus()))return void(null==D||D());const t=f.indexFromPos(f.getCursor());let n;for(const r of f.operations)r.loc&&r.loc.start<=t&&r.loc.end>=t&&(n=null==(e=r.name)?void 0:e.value);n&&n!==f.operationName&&p(n),D()}),[f,D,p]);return lC(f,["Cmd-Enter","Ctrl-Enter"],L),lC(f,["Shift-Ctrl-C"],x),lC(f,["Shift-Ctrl-P","Shift-Ctrl-F"],N),lC(f,["Shift-Ctrl-M"],k),_}function xC(e,n,r){(0,t.useEffect)((()=>{if(!e)return;const t=e.options.lint.schema!==n;e.state.lint.linterOptions.schema=n,e.options.lint.schema=n,e.options.hintOptions.schema=n,e.options.info.schema=n,e.options.jump.schema=n,t&&r.current&&r.current.signal(e,"change",e)}),[e,n,r])}function kC(e,n,r){(0,t.useEffect)((()=>{if(!e)return;const t=e.options.lint.validationRules!==n;e.state.lint.linterOptions.validationRules=n,e.options.lint.validationRules=n,t&&r.current&&r.current.signal(e,"change",e)}),[e,n,r])}function NC(e,n,r){const i=(0,t.useMemo)((()=>[...n.values()]),[n]);(0,t.useEffect)((()=>{if(!e)return;const t=e.options.lint.externalFragments!==i;e.state.lint.linterOptions.externalFragments=i,e.options.lint.externalFragments=i,e.options.hintOptions.externalFragments=i,t&&r.current&&r.current.signal(e,"change",e)}),[e,i,r])}g(SC,"useQueryEditor"),CC(SC,"useQueryEditor"),g(xC,"useSynchronizeSchema"),CC(xC,"useSynchronizeSchema"),g(kC,"useSynchronizeValidationRules"),CC(kC,"useSynchronizeValidationRules"),g(NC,"useSynchronizeExternalFragments"),CC(NC,"useSynchronizeExternalFragments");const _C=/^[a-zA-Z0-9_@(]$/,OC="query",IC="operationName";var DC=Object.defineProperty,LC=g(((e,t)=>DC(e,"name",{value:t,configurable:!0})),"__name$c");function AC(e){let{defaultQuery:t,defaultHeaders:n,headers:r,defaultTabs:i,query:o,variables:a,storage:s}=e;const l=null==s?void 0:s.get(XC);try{if(!l)throw new Error("Storage for tabs is empty");const e=JSON.parse(l);if(MC(e)){const t=zC({query:o,variables:a,headers:r});let n=-1;for(let r=0;r=0)e.activeTabIndex=n;else{const n=o?WC(o):null;e.tabs.push({id:GC(),hash:t,title:n||QC,query:o,variables:a,headers:r,operationName:n,response:null}),e.activeTabIndex=e.tabs.length-1}return e}throw new Error("Storage for tabs is invalid")}catch{return{activeTabIndex:0,tabs:(i||[{query:null!=o?o:t,variables:a,headers:null!=r?r:n}]).map(qC)}}}function MC(e){return e&&"object"==typeof e&&!Array.isArray(e)&&FC(e,"activeTabIndex")&&"tabs"in e&&Array.isArray(e.tabs)&&e.tabs.every(RC)}function RC(e){return e&&"object"==typeof e&&!Array.isArray(e)&&PC(e,"id")&&PC(e,"title")&&jC(e,"query")&&jC(e,"variables")&&jC(e,"headers")&&jC(e,"operationName")&&jC(e,"response")}function FC(e,t){return t in e&&"number"==typeof e[t]}function PC(e,t){return t in e&&"string"==typeof e[t]}function jC(e,t){return t in e&&("string"==typeof e[t]||null===e[t])}function VC(e){let{queryEditor:n,variableEditor:r,headerEditor:i,responseEditor:o}=e;return(0,t.useCallback)((e=>{var t,a,s,l,u;const c=null!=(t=null==n?void 0:n.getValue())?t:null,d=null!=(a=null==r?void 0:r.getValue())?a:null,f=null!=(s=null==i?void 0:i.getValue())?s:null,p=null!=(l=null==n?void 0:n.operationName)?l:null;return HC(e,{query:c,variables:d,headers:f,response:null!=(u=null==o?void 0:o.getValue())?u:null,operationName:p})}),[n,r,i,o])}function UC(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return JSON.stringify(e,((e,n)=>"hash"===e||"response"===e||!t&&"headers"===e?null:n))}function BC(e){let{storage:n,shouldPersistHeaders:r}=e;const i=(0,t.useMemo)((()=>ww(500,(e=>{null==n||n.set(XC,e)}))),[n]);return(0,t.useCallback)((e=>{i(UC(e,r))}),[r,i])}function $C(e){let{queryEditor:n,variableEditor:r,headerEditor:i,responseEditor:o}=e;return(0,t.useCallback)((e=>{let{query:t,variables:a,headers:s,response:l}=e;null==n||n.setValue(null!=t?t:""),null==r||r.setValue(null!=a?a:""),null==i||i.setValue(null!=s?s:""),null==o||o.setValue(null!=l?l:"")}),[i,n,o,r])}function qC(){let{query:e=null,variables:t=null,headers:n=null}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{id:GC(),hash:zC({query:e,variables:t,headers:n}),title:e&&WC(e)||QC,query:e,variables:t,headers:n,operationName:null,response:null}}function HC(e,t){return m(h({},e),{tabs:e.tabs.map(((n,r)=>{if(r!==e.activeTabIndex)return n;const i=h(h({},n),t);return m(h({},i),{hash:zC(i),title:i.operationName||(i.query?WC(i.query):void 0)||QC})}))})}function GC(){const e=LC((()=>Math.floor(65536*(1+Math.random())).toString(16).slice(1)),"s4");return`${e()}${e()}-${e()}-${e()}-${e()}-${e()}${e()}${e()}`}function zC(e){var t,n,r;return[null!=(t=e.query)?t:"",null!=(n=e.variables)?n:"",null!=(r=e.headers)?r:""].join("|")}function WC(e){var t;const n=/^(?!#).*(query|subscription|mutation)\s+([a-zA-Z0-9_]+)/m.exec(e);return null!=(t=null==n?void 0:n[2])?t:null}function KC(e){const t=null==e?void 0:e.get(XC);if(t){const n=JSON.parse(t);null==e||e.set(XC,JSON.stringify(n,((e,t)=>"headers"===e?null:t)))}}g(AC,"getDefaultTabState"),LC(AC,"getDefaultTabState"),g(MC,"isTabsState"),LC(MC,"isTabsState"),g(RC,"isTabState"),LC(RC,"isTabState"),g(FC,"hasNumberKey"),LC(FC,"hasNumberKey"),g(PC,"hasStringKey"),LC(PC,"hasStringKey"),g(jC,"hasStringOrNullKey"),LC(jC,"hasStringOrNullKey"),g(VC,"useSynchronizeActiveTabValues"),LC(VC,"useSynchronizeActiveTabValues"),g(UC,"serializeTabState"),LC(UC,"serializeTabState"),g(BC,"useStoreTabs"),LC(BC,"useStoreTabs"),g($C,"useSetEditorValues"),LC($C,"useSetEditorValues"),g(qC,"createTab"),LC(qC,"createTab"),g(HC,"setPropertiesInActiveTab"),LC(HC,"setPropertiesInActiveTab"),g(GC,"guid"),LC(GC,"guid"),g(zC,"hashFromTabContents"),LC(zC,"hashFromTabContents"),g(WC,"fuzzyExtractOperationName"),LC(WC,"fuzzyExtractOperationName"),g(KC,"clearHeadersFromTabs"),LC(KC,"clearHeadersFromTabs");const QC="",XC="tabState";var YC=Object.defineProperty,JC=g(((e,t)=>YC(e,"name",{value:t,configurable:!0})),"__name$b");function ZC(){let{editorTheme:e=gE,keyMap:r=vE,onClickReference:i,onEdit:o,readOnly:a=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},s=arguments.length>1?arguments[1]:void 0;const{initialVariables:l,variableEditor:u,setVariableEditor:c}=oS({nonNull:!0,caller:s||ZC}),d=fE(),f=cC({caller:s||ZC}),p=dC({caller:s||ZC}),h=(0,t.useRef)(null),m=(0,t.useRef)();return(0,t.useEffect)((()=>{let t=!0;return EE([Promise.resolve().then(n.t.bind(n,4712,23)),Promise.resolve().then(n.t.bind(n,8535,23)),Promise.resolve().then(n.t.bind(n,3340,23))]).then((n=>{if(!t)return;m.current=n;const r=h.current;if(!r)return;const i=n(r,{value:l,lineNumbers:!0,tabSize:2,mode:"graphql-variables",theme:e,autoCloseBrackets:!0,matchBrackets:!0,showCursorWhenSelecting:!0,readOnly:!!a&&"nocursor",foldGutter:!0,lint:{variableToType:void 0},hintOptions:{closeOnUnfocus:!1,completeSingle:!1,container:r,variableToType:void 0},gutters:["CodeMirror-linenumbers","CodeMirror-foldgutter"],extraKeys:bE});i.addKeyMap({"Cmd-Space"(){i.showHint({completeSingle:!1,container:r})},"Ctrl-Space"(){i.showHint({completeSingle:!1,container:r})},"Alt-Space"(){i.showHint({completeSingle:!1,container:r})},"Shift-Space"(){i.showHint({completeSingle:!1,container:r})}}),i.on("keyup",((e,t)=>{const{keyCode:n,key:r,shiftKey:i}=t;(n>=65&&n<=90||!i&&n>=48&&n<=57||"_"===r||'"'===r)&&e.execCommand("autocomplete")})),c(i)})),()=>{t=!1}}),[e,l,a,c]),oC(u,"keyMap",r),aC(u,o,eS,"variables",ZC),sC(u,i||null,ZC),lC(u,["Cmd-Enter","Ctrl-Enter"],null==d?void 0:d.run),lC(u,["Shift-Ctrl-P"],p),lC(u,["Shift-Ctrl-M"],f),h}g(ZC,"useVariableEditor"),JC(ZC,"useVariableEditor");const eS="variables";var tS=Object.defineProperty,nS=g(((e,t)=>tS(e,"name",{value:t,configurable:!0})),"__name$a");const rS=X("EditorContext");function iS(e){const n=ye(),[i,o]=(0,t.useState)(null),[a,s]=(0,t.useState)(null),[l,u]=(0,t.useState)(null),[c,d]=(0,t.useState)(null),[f,p]=(0,t.useState)((()=>{const t=null!==(null==n?void 0:n.get(aS));return!1!==e.shouldPersistHeaders&&t?"true"===(null==n?void 0:n.get(aS)):Boolean(e.shouldPersistHeaders)}));iC(i,e.headers),iC(a,e.query),iC(l,e.response),iC(c,e.variables);const g=BC({storage:n,shouldPersistHeaders:f}),[v]=(0,t.useState)((()=>{var t,r,i,o,a,s,l,u,c;const d=null!=(r=null!=(t=e.query)?t:null==n?void 0:n.get(OC))?r:null,f=null!=(o=null!=(i=e.variables)?i:null==n?void 0:n.get(eS))?o:null,p=null!=(s=null!=(a=e.headers)?a:null==n?void 0:n.get(gC))?s:null,h=null!=(l=e.response)?l:"",m=AC({query:d,variables:f,headers:p,defaultTabs:e.defaultTabs||e.initialTabs,defaultQuery:e.defaultQuery||sS,defaultHeaders:e.defaultHeaders,storage:n});return g(m),{query:null!=(u=null!=d?d:0===m.activeTabIndex?m.tabs[0].query:null)?u:"",variables:null!=f?f:"",headers:null!=(c=null!=p?p:e.defaultHeaders)?c:"",response:h,tabState:m}})),[y,b]=(0,t.useState)(v.tabState),E=(0,t.useCallback)((e=>{var t;if(e){null==n||n.set(gC,null!=(t=null==i?void 0:i.getValue())?t:"");const e=UC(y,!0);null==n||n.set(XC,e)}else null==n||n.set(gC,""),KC(n);p(e),null==n||n.set(aS,e.toString())}),[n,y,i]),T=(0,t.useRef)();(0,t.useEffect)((()=>{const t=Boolean(e.shouldPersistHeaders);T.current!==t&&(E(t),T.current=t)}),[e.shouldPersistHeaders,E]);const w=VC({queryEditor:a,variableEditor:c,headerEditor:i,responseEditor:l}),C=$C({queryEditor:a,variableEditor:c,headerEditor:i,responseEditor:l}),{onTabChange:S,defaultHeaders:x,children:k}=e,N=(0,t.useCallback)((()=>{b((e=>{const t=w(e),n={tabs:[...t.tabs,qC({headers:x})],activeTabIndex:t.tabs.length};return g(n),C(n.tabs[n.activeTabIndex]),null==S||S(n),n}))}),[x,S,C,g,w]),_=(0,t.useCallback)((e=>{b((t=>{const n=m(h({},w(t)),{activeTabIndex:e});return g(n),C(n.tabs[n.activeTabIndex]),null==S||S(n),n}))}),[S,C,g,w]),O=(0,t.useCallback)((e=>{b((t=>{const n={tabs:t.tabs.filter(((t,n)=>e!==n)),activeTabIndex:Math.max(t.activeTabIndex-1,0)};return g(n),C(n.tabs[n.activeTabIndex]),null==S||S(n),n}))}),[S,C,g]),I=(0,t.useCallback)((e=>{b((t=>{const n=HC(t,e);return g(n),null==S||S(n),n}))}),[S,g]),{onEditOperationName:D}=e,L=(0,t.useCallback)((e=>{a&&(a.operationName=e,I({operationName:e}),null==D||D(e))}),[D,a,I]),A=(0,t.useMemo)((()=>{const t=new Map;if(Array.isArray(e.externalFragments))for(const n of e.externalFragments)t.set(n.name.value,n);else if("string"==typeof e.externalFragments)(0,r.visit)((0,r.parse)(e.externalFragments,{}),{FragmentDefinition(e){t.set(e.name.value,e)}});else if(e.externalFragments)throw new Error("The `externalFragments` prop must either be a string that contains the fragment definitions in SDL or a list of FragmentDefinitionNode objects.");return t}),[e.externalFragments]),M=(0,t.useMemo)((()=>e.validationRules||[]),[e.validationRules]),R=(0,t.useMemo)((()=>m(h({},y),{addTab:N,changeTab:_,closeTab:O,updateActiveTabValues:I,headerEditor:i,queryEditor:a,responseEditor:l,variableEditor:c,setHeaderEditor:o,setQueryEditor:s,setResponseEditor:u,setVariableEditor:d,setOperationName:L,initialQuery:v.query,initialVariables:v.variables,initialHeaders:v.headers,initialResponse:v.response,externalFragments:A,validationRules:M,shouldPersistHeaders:f,setShouldPersistHeaders:E})),[y,N,_,O,I,i,a,l,c,L,v,A,M,f,E]);return de(rS.Provider,{value:R,children:k})}e.E=rS,g(iS,"EditorContextProvider"),nS(iS,"EditorContextProvider");const oS=Y(rS);e.f=oS;const aS="shouldPersistHeaders",sS='# Welcome to GraphiQL\n#\n# GraphiQL is an in-browser tool for writing, validating, and\n# testing GraphQL queries.\n#\n# Type queries into this side of the screen, and you will see intelligent\n# typeaheads aware of the current GraphQL type schema and live syntax and\n# validation errors highlighted within the text.\n#\n# GraphQL queries typically start with a "{" character. Lines that start\n# with a # are ignored.\n#\n# An example GraphQL query might look like:\n#\n# {\n# field(arg: "value") {\n# subField\n# }\n# }\n#\n# Keyboard shortcuts:\n#\n# Prettify query: Shift-Ctrl-P (or press the prettify button)\n#\n# Merge fragments: Shift-Ctrl-M (or press the merge button)\n#\n# Run Query: Ctrl-Enter (or press the play button)\n#\n# Auto Complete: Ctrl-Space (or just start typing)\n#\n\n';var lS=Object.defineProperty,uS=g(((e,t)=>lS(e,"name",{value:t,configurable:!0})),"__name$9");function cS(e){var n=e,{isHidden:r}=n,i=v(n,["isHidden"]);const{headerEditor:o}=oS({nonNull:!0,caller:cS}),a=mC(i,cS);return(0,t.useEffect)((()=>{o&&!r&&o.refresh()}),[o,r]),de("div",{className:b("graphiql-editor",r&&"hidden"),ref:a})}g(cS,"HeaderEditor"),uS(cS,"HeaderEditor");var dS=Object.defineProperty,fS=g(((e,t)=>dS(e,"name",{value:t,configurable:!0})),"__name$8");function pS(e){var n;const[r,i]=(0,t.useState)({width:null,height:null}),[o,a]=(0,t.useState)(null),s=(0,t.useRef)(null),l=null==(n=hS(e.token))?void 0:n.href;(0,t.useEffect)((()=>{if(s.current)return l?void fetch(l,{method:"HEAD"}).then((e=>{a(e.headers.get("Content-Type"))})).catch((()=>{a(null)})):(i({width:null,height:null}),void a(null))}),[l]);const u=null!==r.width&&null!==r.height?fe("div",{children:[r.width,"x",r.height,null===o?null:" "+o]}):null;return fe("div",{children:[de("img",{onLoad:()=>{var e,t,n,r;i({width:null!=(t=null==(e=s.current)?void 0:e.naturalWidth)?t:null,height:null!=(r=null==(n=s.current)?void 0:n.naturalHeight)?r:null})},ref:s,src:l}),u]})}function hS(e){if("string"!==e.type)return;const t=e.string.slice(1).slice(0,-1).trim();try{const{location:e}=window;return new URL(t,e.protocol+"//"+e.host)}catch{return}}function mS(e){return/(bmp|gif|jpeg|jpg|png|svg)$/.test(e.pathname)}g(pS,"ImagePreview"),fS(pS,"ImagePreview"),pS.shouldRender=fS(g((function(e){const t=hS(e);return!!t&&mS(t)}),"shouldRender"),"shouldRender"),g(hS,"tokenToURL"),fS(hS,"tokenToURL"),g(mS,"isImageURL"),fS(mS,"isImageURL");var gS=Object.defineProperty,vS=g(((e,t)=>gS(e,"name",{value:t,configurable:!0})),"__name$7");function yS(e){const t=SC(e,yS);return de("div",{className:"graphiql-editor",ref:t})}g(yS,"QueryEditor"),vS(yS,"QueryEditor");var bS=Object.defineProperty,ES=g(((e,t)=>bS(e,"name",{value:t,configurable:!0})),"__name$6");function TS(){let{responseTooltip:e,editorTheme:r=gE,keyMap:o=vE}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},a=arguments.length>1?arguments[1]:void 0;const{fetchError:s,validationErrors:l}=RE({nonNull:!0,caller:a||TS}),{initialResponse:u,responseEditor:c,setResponseEditor:d}=oS({nonNull:!0,caller:a||TS}),f=(0,t.useRef)(null),p=(0,t.useRef)(e);return(0,t.useEffect)((()=>{p.current=e}),[e]),(0,t.useEffect)((()=>{let e=!0;return EE([Promise.resolve().then(n.t.bind(n,8419,23)).then((function(e){return e.f})),Promise.resolve().then(n.t.bind(n,4468,23)).then((function(e){return e.b})),Promise.resolve().then(n.t.bind(n,8058,23)).then((function(e){return e.d})),Promise.resolve().then(n.t.bind(n,9509,23)).then((function(e){return e.s})),Promise.resolve().then(n.t.bind(n,9407,23)).then((function(e){return e.s})),Promise.resolve().then(n.t.bind(n,4471,23)).then((function(e){return e.j})),Promise.resolve().then(n.t.bind(n,2568,23)).then((function(e){return e.s})),Promise.resolve().then(n.t.bind(n,1430,23)),Promise.resolve().then(n.t.bind(n,7421,23))],{useCommonAddons:!1}).then((t=>{if(!e)return;const n=document.createElement("div");t.registerHelper("info","graphql-results",((e,t,r,o)=>{const a=[],s=p.current;return s&&a.push(de(s,{pos:o,token:e})),pS.shouldRender(e)&&a.push(de(pS,{token:e},"image-preview")),a.length?(i.default.render(a,n),n):(i.default.unmountComponentAtNode(n),null)}));const o=f.current;if(!o)return;const a=t(o,{value:u,lineWrapping:!0,readOnly:!0,theme:r,mode:"graphql-results",foldGutter:!0,gutters:["CodeMirror-foldgutter"],info:!0,extraKeys:bE});d(a)})),()=>{e=!1}}),[r,u,d]),oC(c,"keyMap",o),(0,t.useEffect)((()=>{s&&(null==c||c.setValue(s)),l.length>0&&(null==c||c.setValue(D(l)))}),[c,s,l]),f}g(TS,"useResponseEditor"),ES(TS,"useResponseEditor");var wS=Object.defineProperty,CS=g(((e,t)=>wS(e,"name",{value:t,configurable:!0})),"__name$5");function SS(e){const t=TS(e,SS);return de("section",{className:"result-window","aria-label":"Result Window","aria-live":"polite","aria-atomic":"true",ref:t})}g(SS,"ResponseEditor"),CS(SS,"ResponseEditor");var xS=Object.defineProperty,kS=g(((e,t)=>xS(e,"name",{value:t,configurable:!0})),"__name$4");function NS(e){var n=e,{isHidden:r}=n,i=v(n,["isHidden"]);const{variableEditor:o}=oS({nonNull:!0,caller:NS}),a=ZC(i,NS);return(0,t.useEffect)((()=>{o&&!r&&o.refresh()}),[o,r]),de("div",{className:b("graphiql-editor",r&&"hidden"),ref:a})}g(NS,"VariableEditor"),kS(NS,"VariableEditor");var _S=Object.defineProperty,OS=g(((e,t)=>_S(e,"name",{value:t,configurable:!0})),"__name$3");function IS(e){let{children:t,dangerouslyAssumeSchemaIsValid:n,defaultQuery:r,defaultHeaders:i,defaultTabs:o,externalFragments:a,fetcher:s,getDefaultFieldNames:l,headers:u,initialTabs:c,inputValueDeprecation:d,introspectionQueryName:f,maxHistoryLength:p,onEditOperationName:h,onSchemaChange:m,onTabChange:g,onTogglePluginVisibility:v,operationName:y,plugins:b,query:E,response:T,schema:w,schemaDescription:C,shouldPersistHeaders:S,storage:x,validationRules:k,variables:N,visiblePlugin:_}=e;return de(ve,{storage:x,children:de(eE,{maxHistoryLength:p,children:de(iS,{defaultQuery:r,defaultHeaders:i,defaultTabs:o,externalFragments:a,headers:u,initialTabs:c,onEditOperationName:h,onTabChange:g,query:E,response:T,shouldPersistHeaders:S,validationRules:k,variables:N,children:de(ME,{dangerouslyAssumeSchemaIsValid:n,fetcher:s,inputValueDeprecation:d,introspectionQueryName:f,onSchemaChange:m,schema:w,schemaDescription:C,children:de(dE,{getDefaultFieldNames:l,fetcher:s,operationName:y,children:de($E,{children:de(Xw,{onTogglePluginVisibility:v,plugins:b,visiblePlugin:_,children:t})})})})})})})}g(IS,"GraphiQLProvider"),OS(IS,"GraphiQLProvider");var DS=Object.defineProperty,LS=g(((e,t)=>DS(e,"name",{value:t,configurable:!0})),"__name$2");function AS(){const e=ye(),[n,r]=(0,t.useState)((()=>{if(!e)return null;const t=e.get(MS);switch(t){case"light":return"light";case"dark":return"dark";default:return"string"==typeof t&&e.set(MS,""),null}}));(0,t.useLayoutEffect)((()=>{"undefined"!=typeof window&&(document.body.classList.remove("graphiql-light","graphiql-dark"),n&&document.body.classList.add(`graphiql-${n}`))}),[n]);const i=(0,t.useCallback)((t=>{null==e||e.set(MS,t||""),r(t)}),[e]);return(0,t.useMemo)((()=>({theme:n,setTheme:i})),[n,i])}g(AS,"useTheme"),LS(AS,"useTheme");const MS="theme";var RS=Object.defineProperty,FS=g(((e,t)=>RS(e,"name",{value:t,configurable:!0})),"__name$1");function PS(e){let{defaultSizeRelation:n=jS,direction:r,initiallyHidden:i,onHiddenElementChange:o,sizeThresholdFirst:a=100,sizeThresholdSecond:s=100,storageKey:l}=e;const u=ye(),c=(0,t.useMemo)((()=>ww(500,(e=>{u&&l&&u.set(l,e)}))),[u,l]),[d,f]=(0,t.useState)((()=>{const e=u&&l?u.get(l):null;return e===VS||"first"===i?"first":e===US||"second"===i?"second":null})),p=(0,t.useCallback)((e=>{e!==d&&(f(e),null==o||o(e))}),[d,o]),h=(0,t.useRef)(null),m=(0,t.useRef)(null),v=(0,t.useRef)(null),y=(0,t.useRef)(`${n}`);(0,t.useLayoutEffect)((()=>{const e=u&&l&&u.get(l)||y.current,t="horizontal"===r?"row":"column";h.current&&(h.current.style.display="flex",h.current.style.flexDirection=t,h.current.style.flex=e===VS||e===US?y.current:e),v.current&&(v.current.style.display="flex",v.current.style.flexDirection=t,v.current.style.flex="1"),m.current&&(m.current.style.display="flex",m.current.style.flexDirection=t)}),[r,u,l]);const b=(0,t.useCallback)((e=>{const t="first"===e?h.current:v.current;if(t&&(t.style.left="-1000px",t.style.position="absolute",t.style.opacity="0",t.style.height="500px",t.style.width="500px",h.current)){const e=parseFloat(h.current.style.flex);(!Number.isFinite(e)||e<1)&&(h.current.style.flex="1")}}),[]),E=(0,t.useCallback)((e=>{const t="first"===e?h.current:v.current;if(t&&(t.style.width="",t.style.height="",t.style.opacity="",t.style.position="",t.style.left="",h.current&&u&&l)){const e=null==u?void 0:u.get(l);e!==VS&&e!==US&&(h.current.style.flex=e||y.current)}}),[u,l]);return(0,t.useLayoutEffect)((()=>{"first"===d?b("first"):E("first"),"second"===d?b("second"):E("second")}),[d,b,E]),(0,t.useEffect)((()=>{if(!m.current||!h.current||!v.current)return;const e=m.current,t=h.current,n=t.parentElement,i="horizontal"===r?"clientX":"clientY",o="horizontal"===r?"left":"top",l="horizontal"===r?"right":"bottom",u="horizontal"===r?"clientWidth":"clientHeight";function d(r){r.preventDefault();const d=r[i]-e.getBoundingClientRect()[o];function f(r){if(0===r.buttons)return h();const f=r[i]-n.getBoundingClientRect()[o]-d,m=n.getBoundingClientRect()[l]-r[i]+d-e[u];if(f{e.removeEventListener("mousedown",d),e.removeEventListener("dblclick",f)}}),[r,p,a,s,c]),(0,t.useMemo)((()=>({dragBarRef:m,hiddenElement:d,firstRef:h,setHiddenElement:f,secondRef:v})),[d,f])}g(PS,"useDragResize"),FS(PS,"useDragResize");const jS=1,VS="hide-first",US="hide-second",BS=(0,t.forwardRef)(((e,n)=>{var r=e,{label:i}=r,o=v(r,["label"]);const[a,s]=(0,t.useState)(null);return de(Pb,{label:i,children:de(os,m(h({},o),{ref:n,type:"button",className:b("graphiql-toolbar-button",a&&"error",o.className),onClick:e=>{var t;try{null==(t=o.onClick)||t.call(o,e),s(null)}catch(e){s(e instanceof Error?e:new Error(`Toolbar button click failed: ${e}`))}},"aria-label":a?a.message:i,"aria-invalid":a?"true":o["aria-invalid"]}))})}));e.aR=BS,BS.displayName="ToolbarButton";var $S=Object.defineProperty,qS=g(((e,t)=>$S(e,"name",{value:t,configurable:!0})),"__name");function HS(){const{queryEditor:e,setOperationName:t}=oS({nonNull:!0,caller:HS}),{isFetching:n,isSubscribed:r,operationName:i,run:o,stop:a}=fE({nonNull:!0,caller:HS}),s=(null==e?void 0:e.operations)||[],l=s.length>1&&"string"!=typeof i,u=n||r,c=(u?"Stop":"Execute")+" query (Ctrl-Enter)",d={type:"button",className:"graphiql-execute-button",children:de(u?ns:Ka,{}),"aria-label":c};return l&&!u?fe(Hh,{children:[de(Pb,{label:c,children:de(Hh.Button,h({},d))}),de(Hh.List,{children:s.map(((n,r)=>{const i=n.name?n.name.value:``;return de(Hh.Item,{onSelect:()=>{var r;const i=null==(r=n.name)?void 0:r.value;e&&i&&i!==e.operationName&&t(i),o()},children:i},`${i}-${r}`)}))})]}):de(Pb,{label:c,children:de("button",m(h({},d),{onClick:()=>{u?a():o()}}))})}g(HS,"ExecuteButton"),qS(HS,"ExecuteButton");const GS=(0,t.forwardRef)(((e,t)=>{var n=e,{button:r,children:i,label:o}=n,a=v(n,["button","children","label"]);const s=`${o}${a.value?`: ${a.value}`:""}`;return fe(zh.Input,m(h({},a),{ref:t,className:b("graphiql-toolbar-listbox",a.className),"aria-label":s,children:[de(Pb,{label:s,children:de(zh.Button,{children:r})}),de(zh.Popover,{children:i})]}))}));GS.displayName="ToolbarListbox";const zS=Xc(GS,{Option:zh.Option});e.aT=zS;const WS=(0,t.forwardRef)(((e,t)=>{var n=e,{button:r,children:i,label:o}=n,a=v(n,["button","children","label"]);return fe(Hh,m(h({},a),{ref:t,children:[de(Pb,{label:o,children:de(Hh.Button,{className:b("graphiql-un-styled graphiql-toolbar-menu",a.className),"aria-label":o,children:r})}),de(Hh.List,{children:i})]}))}));WS.displayName="ToolbarMenu";const KS=Xc(WS,{Item:Hh.Item});e.aU=KS},void 0===(o=r.apply(t,i))||(e.exports=o)},7421:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(535),n(6856),n(9196),n(3573),n(1850)],void 0===(o="function"==typeof(r=function(e,t,n,r,i){"use strict";var o=Object.defineProperty,a=(e,t)=>o(e,"name",{value:t,configurable:!0});function s(e){return{options:e instanceof Function?{render:e}:!0===e?{}:e}}function l(e){const{options:t}=e.state.info;return(null==t?void 0:t.hoverTime)||500}function u(t,n){const r=t.state.info,i=n.target||n.srcElement;if(!(i instanceof HTMLElement))return;if("SPAN"!==i.nodeName||void 0!==r.hoverTimeout)return;const o=i.getBoundingClientRect(),s=a((function(){clearTimeout(r.hoverTimeout),r.hoverTimeout=setTimeout(d,f)}),"onMouseMove"),u=a((function(){e.C.off(document,"mousemove",s),e.C.off(t.getWrapperElement(),"mouseout",u),clearTimeout(r.hoverTimeout),r.hoverTimeout=void 0}),"onMouseOut"),d=a((function(){e.C.off(document,"mousemove",s),e.C.off(t.getWrapperElement(),"mouseout",u),r.hoverTimeout=void 0,c(t,o)}),"onHover"),f=l(t);r.hoverTimeout=setTimeout(d,f),e.C.on(document,"mousemove",s),e.C.on(t.getWrapperElement(),"mouseout",u)}function c(e,t){const n=e.coordsChar({left:(t.left+t.right)/2,top:(t.top+t.bottom)/2}),r=e.state.info,{options:i}=r,o=i.render||e.getHelper(n,"info");if(o){const r=e.getTokenAt(n,!0);if(r){const a=o(r,i,e,n);a&&d(e,t,a)}}}function d(t,n,r){const i=document.createElement("div");i.className="CodeMirror-info",i.append(r),document.body.append(i);const o=i.getBoundingClientRect(),s=window.getComputedStyle(i),l=o.right-o.left+parseFloat(s.marginLeft)+parseFloat(s.marginRight),u=o.bottom-o.top+parseFloat(s.marginTop)+parseFloat(s.marginBottom);let c=n.bottom;u>window.innerHeight-n.bottom-15&&n.top>window.innerHeight-n.bottom&&(c=n.top-u),c<0&&(c=n.bottom);let d,f=Math.max(0,window.innerWidth-l-15);f>n.left&&(f=n.left),i.style.opacity="1",i.style.top=c+"px",i.style.left=f+"px";const p=a((function(){clearTimeout(d)}),"onMouseOverPopup"),h=a((function(){clearTimeout(d),d=setTimeout(m,200)}),"onMouseOut"),m=a((function(){e.C.off(i,"mouseover",p),e.C.off(i,"mouseout",h),e.C.off(t.getWrapperElement(),"mouseout",h),i.style.opacity?(i.style.opacity="0",setTimeout((()=>{i.parentNode&&i.remove()}),600)):i.parentNode&&i.remove()}),"hidePopup");e.C.on(i,"mouseover",p),e.C.on(i,"mouseout",h),e.C.on(t.getWrapperElement(),"mouseout",h)}e.C.defineOption("info",!1,((t,n,r)=>{if(r&&r!==e.C.Init){const n=t.state.info.onMouseOver;e.C.off(t.getWrapperElement(),"mouseover",n),clearTimeout(t.state.info.hoverTimeout),delete t.state.info}if(n){const r=t.state.info=s(n);r.onMouseOver=u.bind(null,t),e.C.on(t.getWrapperElement(),"mouseover",r.onMouseOver)}})),a(s,"createState"),a(l,"getHoverTime"),a(u,"onMouseOver"),a(c,"onMouseHover"),a(d,"showPopup")})?r.apply(t,i):r)||(e.exports=o)},9890:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(3573),n(535),n(2645),n(7421),n(6856),n(9196),n(1850),n(2635)],r=function(e,t,n,r,i,o,a,s){"use strict";var l=Object.defineProperty,u=(e,t)=>l(e,"name",{value:t,configurable:!0});function c(e,t,n){d(e,t,n),m(e,t,n,t.type)}function d(e,t,r){var i;b(e,(null===(i=t.fieldDef)||void 0===i?void 0:i.name)||"","field-name",r,(0,n.a)(t))}function f(e,t,r){var i;b(e,"@"+((null===(i=t.directiveDef)||void 0===i?void 0:i.name)||""),"directive-name",r,(0,n.b)(t))}function p(e,t,r){var i;b(e,(null===(i=t.argDef)||void 0===i?void 0:i.name)||"","arg-name",r,(0,n.c)(t)),m(e,t,r,t.inputType)}function h(e,t,r){var i;const o=(null===(i=t.enumValue)||void 0===i?void 0:i.name)||"";g(e,t,r,t.inputType),b(e,"."),b(e,o,"enum-value",r,(0,n.d)(t))}function m(t,r,i,o){const a=document.createElement("span");a.className="type-name-pill",o instanceof e.GraphQLNonNull?(g(a,r,i,o.ofType),b(a,"!")):o instanceof e.GraphQLList?(b(a,"["),g(a,r,i,o.ofType),b(a,"]")):b(a,(null==o?void 0:o.name)||"","type-name",i,(0,n.e)(r,o)),t.append(a)}function g(t,r,i,o){o instanceof e.GraphQLNonNull?(g(t,r,i,o.ofType),b(t,"!")):o instanceof e.GraphQLList?(b(t,"["),g(t,r,i,o.ofType),b(t,"]")):b(t,(null==o?void 0:o.name)||"","type-name",i,(0,n.e)(r,o))}function v(e,t,n){const{description:r}=n;if(r){const n=document.createElement("div");n.className="info-description",t.renderDescription?n.innerHTML=t.renderDescription(r):n.append(document.createTextNode(r)),e.append(n)}y(e,t,n)}function y(e,t,n){const r=n.deprecationReason;if(r){const n=document.createElement("div");n.className="info-deprecation",e.append(n);const i=document.createElement("span");i.className="info-deprecation-label",i.append(document.createTextNode("Deprecated")),n.append(i);const o=document.createElement("div");o.className="info-deprecation-reason",t.renderDescription?o.innerHTML=t.renderDescription(r):o.append(document.createTextNode(r)),n.append(o)}}function b(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{onClick:null},i=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;if(n){const{onClick:o}=r;let a;o?(a=document.createElement("a"),a.href="javascript:void 0",a.addEventListener("click",(e=>{o(i,e)}))):a=document.createElement("span"),a.className=n,a.append(document.createTextNode(t)),e.append(a)}else e.append(document.createTextNode(t))}t.C.registerHelper("info","graphql",((e,t)=>{if(!t.schema||!e.state)return;const{kind:r,step:i}=e.state,o=(0,n.g)(t.schema,e.state);if("Field"===r&&0===i&&o.fieldDef||"AliasedField"===r&&2===i&&o.fieldDef){const e=document.createElement("div");e.className="CodeMirror-info-header",c(e,o,t);const n=document.createElement("div");return n.append(e),v(n,t,o.fieldDef),n}if("Directive"===r&&1===i&&o.directiveDef){const e=document.createElement("div");e.className="CodeMirror-info-header",f(e,o,t);const n=document.createElement("div");return n.append(e),v(n,t,o.directiveDef),n}if("Argument"===r&&0===i&&o.argDef){const e=document.createElement("div");e.className="CodeMirror-info-header",p(e,o,t);const n=document.createElement("div");return n.append(e),v(n,t,o.argDef),n}if("EnumValue"===r&&o.enumValue&&o.enumValue.description){const e=document.createElement("div");e.className="CodeMirror-info-header",h(e,o,t);const n=document.createElement("div");return n.append(e),v(n,t,o.enumValue),n}if("NamedType"===r&&o.type&&o.type.description){const e=document.createElement("div");e.className="CodeMirror-info-header",g(e,o,t,o.type);const n=document.createElement("div");return n.append(e),v(n,t,o.type),n}})),u(c,"renderField"),u(d,"renderQualifiedField"),u(f,"renderDirective"),u(p,"renderArg"),u(h,"renderEnumValue"),u(m,"renderTypeAnnotation"),u(g,"renderType"),u(v,"renderDescription"),u(y,"renderDeprecation"),u(b,"text")},void 0===(o=r.apply(t,i))||(e.exports=o)},8888:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535)],r=function(e,t){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.j=void 0;var n=Object.defineProperty,r=(e,t)=>n(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o,a={exports:{}};(o=t.a.exports).defineMode("javascript",(function(e,t){var n,i,a=e.indentUnit,s=t.statementIndent,l=t.jsonld,u=t.json||l,c=!1!==t.trackScope,d=t.typescript,f=t.wordCharacters||/[\w$\xa1-\uffff]/,p=function(){function e(e){return{type:e,style:"keyword"}}r(e,"kw");var t=e("keyword a"),n=e("keyword b"),i=e("keyword c"),o=e("keyword d"),a=e("operator"),s={type:"atom",style:"atom"};return{if:e("if"),while:t,with:t,else:n,do:n,try:n,finally:n,return:o,break:o,continue:o,new:e("new"),delete:i,void:i,throw:i,debugger:e("debugger"),var:e("var"),const:e("var"),let:e("var"),function:e("function"),catch:e("catch"),for:e("for"),switch:e("switch"),case:e("case"),default:e("default"),in:a,typeof:a,instanceof:a,true:s,false:s,null:s,undefined:s,NaN:s,Infinity:s,this:e("this"),class:e("class"),super:e("atom"),yield:i,export:e("export"),import:e("import"),extends:i,await:i}}(),h=/[+\-*&%=<>!?|~^@]/,m=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;function g(e){for(var t,n=!1,r=!1;null!=(t=e.next());){if(!n){if("/"==t&&!r)return;"["==t?r=!0:r&&"]"==t&&(r=!1)}n=!n&&"\\"==t}}function v(e,t,r){return n=e,i=r,t}function y(e,t){var n=e.next();if('"'==n||"'"==n)return t.tokenize=b(n),t.tokenize(e,t);if("."==n&&e.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/))return v("number","number");if("."==n&&e.match(".."))return v("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(n))return v(n);if("="==n&&e.eat(">"))return v("=>","operator");if("0"==n&&e.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/))return v("number","number");if(/\d/.test(n))return e.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/),v("number","number");if("/"==n)return e.eat("*")?(t.tokenize=E,E(e,t)):e.eat("/")?(e.skipToEnd(),v("comment","comment")):ot(e,t,1)?(g(e),e.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/),v("regexp","string-2")):(e.eat("="),v("operator","operator",e.current()));if("`"==n)return t.tokenize=T,T(e,t);if("#"==n&&"!"==e.peek())return e.skipToEnd(),v("meta","meta");if("#"==n&&e.eatWhile(f))return v("variable","property");if("<"==n&&e.match("!--")||"-"==n&&e.match("->")&&!/\S/.test(e.string.slice(0,e.start)))return e.skipToEnd(),v("comment","comment");if(h.test(n))return">"==n&&t.lexical&&">"==t.lexical.type||(e.eat("=")?"!"!=n&&"="!=n||e.eat("="):/[<>*+\-|&?]/.test(n)&&(e.eat(n),">"==n&&e.eat(n))),"?"==n&&e.eat(".")?v("."):v("operator","operator",e.current());if(f.test(n)){e.eatWhile(f);var r=e.current();if("."!=t.lastType){if(p.propertyIsEnumerable(r)){var i=p[r];return v(i.type,i.style,r)}if("async"==r&&e.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/,!1))return v("async","keyword",r)}return v("variable","variable",r)}}function b(e){return function(t,n){var r,i=!1;if(l&&"@"==t.peek()&&t.match(m))return n.tokenize=y,v("jsonld-keyword","meta");for(;null!=(r=t.next())&&(r!=e||i);)i=!i&&"\\"==r;return i||(n.tokenize=y),v("string","string")}}function E(e,t){for(var n,r=!1;n=e.next();){if("/"==n&&r){t.tokenize=y;break}r="*"==n}return v("comment","comment")}function T(e,t){for(var n,r=!1;null!=(n=e.next());){if(!r&&("`"==n||"$"==n&&e.eat("{"))){t.tokenize=y;break}r=!r&&"\\"==n}return v("quasi","string-2",e.current())}r(g,"readRegexp"),r(v,"ret"),r(y,"tokenBase"),r(b,"tokenString"),r(E,"tokenComment"),r(T,"tokenQuasi");var w="([{}])";function C(e,t){t.fatArrowAt&&(t.fatArrowAt=null);var n=e.string.indexOf("=>",e.start);if(!(n<0)){if(d){var r=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(e.string.slice(e.start,n));r&&(n=r.index)}for(var i=0,o=!1,a=n-1;a>=0;--a){var s=e.string.charAt(a),l=w.indexOf(s);if(l>=0&&l<3){if(!i){++a;break}if(0==--i){"("==s&&(o=!0);break}}else if(l>=3&&l<6)++i;else if(f.test(s))o=!0;else if(/["'\/`]/.test(s))for(;;--a){if(0==a)return;if(e.string.charAt(a-1)==s&&"\\"!=e.string.charAt(a-2)){a--;break}}else if(o&&!i){++a;break}}o&&!i&&(t.fatArrowAt=a)}}r(C,"findFatArrow");var S={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,this:!0,import:!0,"jsonld-keyword":!0};function x(e,t,n,r,i,o){this.indented=e,this.column=t,this.type=n,this.prev=i,this.info=o,null!=r&&(this.align=r)}function k(e,t){if(!c)return!1;for(var n=e.localVars;n;n=n.next)if(n.name==t)return!0;for(var r=e.context;r;r=r.prev)for(n=r.vars;n;n=n.next)if(n.name==t)return!0}function N(e,t,n,r,i){var o=e.cc;for(_.state=e,_.stream=i,_.marked=null,_.cc=o,_.style=t,e.lexical.hasOwnProperty("align")||(e.lexical.align=!0);;)if((o.length?o.pop():u?z:H)(n,r)){for(;o.length&&o[o.length-1].lex;)o.pop()();return _.marked?_.marked:"variable"==n&&k(e,r)?"variable-2":t}}r(x,"JSLexical"),r(k,"inScope"),r(N,"parseJS");var _={state:null,column:null,marked:null,cc:null};function O(){for(var e=arguments.length-1;e>=0;e--)_.cc.push(arguments[e])}function I(){return O.apply(null,arguments),!0}function D(e,t){for(var n=t;n;n=n.next)if(n.name==e)return!0;return!1}function L(e){var n=_.state;if(_.marked="def",c){if(n.context)if("var"==n.lexical.info&&n.context&&n.context.block){var r=A(e,n.context);if(null!=r)return void(n.context=r)}else if(!D(e,n.localVars))return void(n.localVars=new F(e,n.localVars));t.globalVars&&!D(e,n.globalVars)&&(n.globalVars=new F(e,n.globalVars))}}function A(e,t){if(t){if(t.block){var n=A(e,t.prev);return n?n==t.prev?t:new R(n,t.vars,!0):null}return D(e,t.vars)?t:new R(t.prev,new F(e,t.vars),!1)}return null}function M(e){return"public"==e||"private"==e||"protected"==e||"abstract"==e||"readonly"==e}function R(e,t,n){this.prev=e,this.vars=t,this.block=n}function F(e,t){this.name=e,this.next=t}r(O,"pass"),r(I,"cont"),r(D,"inList"),r(L,"register"),r(A,"registerVarScoped"),r(M,"isModifier"),r(R,"Context"),r(F,"Var");var P=new F("this",new F("arguments",null));function j(){_.state.context=new R(_.state.context,_.state.localVars,!1),_.state.localVars=P}function V(){_.state.context=new R(_.state.context,_.state.localVars,!0),_.state.localVars=null}function U(){_.state.localVars=_.state.context.vars,_.state.context=_.state.context.prev}function B(e,t){var n=r((function(){var n=_.state,r=n.indented;if("stat"==n.lexical.type)r=n.lexical.indented;else for(var i=n.lexical;i&&")"==i.type&&i.align;i=i.prev)r=i.indented;n.lexical=new x(r,_.stream.column(),e,null,n.lexical,t)}),"result");return n.lex=!0,n}function $(){var e=_.state;e.lexical.prev&&(")"==e.lexical.type&&(e.indented=e.lexical.indented),e.lexical=e.lexical.prev)}function q(e){function t(n){return n==e?I():";"==e||"}"==n||")"==n||"]"==n?O():I(t)}return r(t,"exp"),t}function H(e,t){return"var"==e?I(B("vardef",t),Oe,q(";"),$):"keyword a"==e?I(B("form"),K,H,$):"keyword b"==e?I(B("form"),H,$):"keyword d"==e?_.stream.match(/^\s*$/,!1)?I():I(B("stat"),X,q(";"),$):"debugger"==e?I(q(";")):"{"==e?I(B("}"),V,pe,$,U):";"==e?I():"if"==e?("else"==_.state.lexical.info&&_.state.cc[_.state.cc.length-1]==$&&_.state.cc.pop()(),I(B("form"),K,H,$,Re)):"function"==e?I(Ve):"for"==e?I(B("form"),V,Fe,H,U,$):"class"==e||d&&"interface"==t?(_.marked="keyword",I(B("form","class"==e?e:t),He,$)):"variable"==e?d&&"declare"==t?(_.marked="keyword",I(H)):d&&("module"==t||"enum"==t||"type"==t)&&_.stream.match(/^\s*\w/,!1)?(_.marked="keyword","enum"==t?I(nt):"type"==t?I(Be,q("operator"),ye,q(";")):I(B("form"),Ie,q("{"),B("}"),pe,$,$)):d&&"namespace"==t?(_.marked="keyword",I(B("form"),z,H,$)):d&&"abstract"==t?(_.marked="keyword",I(H)):I(B("stat"),ae):"switch"==e?I(B("form"),K,q("{"),B("}","switch"),V,pe,$,$,U):"case"==e?I(z,q(":")):"default"==e?I(q(":")):"catch"==e?I(B("form"),j,G,H,$,U):"export"==e?I(B("stat"),Ke,$):"import"==e?I(B("stat"),Xe,$):"async"==e?I(H):"@"==t?I(z,H):O(B("stat"),z,q(";"),$)}function G(e){if("("==e)return I($e,q(")"))}function z(e,t){return Q(e,t,!1)}function W(e,t){return Q(e,t,!0)}function K(e){return"("!=e?O():I(B(")"),X,q(")"),$)}function Q(e,t,n){if(_.state.fatArrowAt==_.stream.start){var r=n?ne:te;if("("==e)return I(j,B(")"),de($e,")"),$,q("=>"),r,U);if("variable"==e)return O(j,Ie,q("=>"),r,U)}var i=n?J:Y;return S.hasOwnProperty(e)?I(i):"function"==e?I(Ve,i):"class"==e||d&&"interface"==t?(_.marked="keyword",I(B("form"),qe,$)):"keyword c"==e||"async"==e?I(n?W:z):"("==e?I(B(")"),X,q(")"),$,i):"operator"==e||"spread"==e?I(n?W:z):"["==e?I(B("]"),tt,$,i):"{"==e?fe(le,"}",null,i):"quasi"==e?O(Z,i):"new"==e?I(re(n)):I()}function X(e){return e.match(/[;\}\)\],]/)?O():O(z)}function Y(e,t){return","==e?I(X):J(e,t,!1)}function J(e,t,n){var r=0==n?Y:J,i=0==n?z:W;return"=>"==e?I(j,n?ne:te,U):"operator"==e?/\+\+|--/.test(t)||d&&"!"==t?I(r):d&&"<"==t&&_.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/,!1)?I(B(">"),de(ye,">"),$,r):"?"==t?I(z,q(":"),i):I(i):"quasi"==e?O(Z,r):";"!=e?"("==e?fe(W,")","call",r):"."==e?I(se,r):"["==e?I(B("]"),X,q("]"),$,r):d&&"as"==t?(_.marked="keyword",I(ye,r)):"regexp"==e?(_.state.lastType=_.marked="operator",_.stream.backUp(_.stream.pos-_.stream.start-1),I(i)):void 0:void 0}function Z(e,t){return"quasi"!=e?O():"${"!=t.slice(t.length-2)?I(Z):I(X,ee)}function ee(e){if("}"==e)return _.marked="string-2",_.state.tokenize=T,I(Z)}function te(e){return C(_.stream,_.state),O("{"==e?H:z)}function ne(e){return C(_.stream,_.state),O("{"==e?H:W)}function re(e){return function(t){return"."==t?I(e?oe:ie):"variable"==t&&d?I(ke,e?J:Y):O(e?W:z)}}function ie(e,t){if("target"==t)return _.marked="keyword",I(Y)}function oe(e,t){if("target"==t)return _.marked="keyword",I(J)}function ae(e){return":"==e?I($,H):O(Y,q(";"),$)}function se(e){if("variable"==e)return _.marked="property",I()}function le(e,t){return"async"==e?(_.marked="property",I(le)):"variable"==e||"keyword"==_.style?(_.marked="property","get"==t||"set"==t?I(ue):(d&&_.state.fatArrowAt==_.stream.start&&(n=_.stream.match(/^\s*:\s*/,!1))&&(_.state.fatArrowAt=_.stream.pos+n[0].length),I(ce))):"number"==e||"string"==e?(_.marked=l?"property":_.style+" property",I(ce)):"jsonld-keyword"==e?I(ce):d&&M(t)?(_.marked="keyword",I(le)):"["==e?I(z,he,q("]"),ce):"spread"==e?I(W,ce):"*"==t?(_.marked="keyword",I(le)):":"==e?O(ce):void 0;var n}function ue(e){return"variable"!=e?O(ce):(_.marked="property",I(Ve))}function ce(e){return":"==e?I(W):"("==e?O(Ve):void 0}function de(e,t,n){function i(r,o){if(n?n.indexOf(r)>-1:","==r){var a=_.state.lexical;return"call"==a.info&&(a.pos=(a.pos||0)+1),I((function(n,r){return n==t||r==t?O():O(e)}),i)}return r==t||o==t?I():n&&n.indexOf(";")>-1?O(e):I(q(t))}return r(i,"proceed"),function(n,r){return n==t||r==t?I():O(e,i)}}function fe(e,t,n){for(var r=3;r"),ye):"quasi"==e?O(we,xe):void 0}function be(e){if("=>"==e)return I(ye)}function Ee(e){return e.match(/[\}\)\]]/)?I():","==e||";"==e?I(Ee):O(Te,Ee)}function Te(e,t){return"variable"==e||"keyword"==_.style?(_.marked="property",I(Te)):"?"==t||"number"==e||"string"==e?I(Te):":"==e?I(ye):"["==e?I(q("variable"),me,q("]"),Te):"("==e?O(Ue,Te):e.match(/[;\}\)\],]/)?void 0:I()}function we(e,t){return"quasi"!=e?O():"${"!=t.slice(t.length-2)?I(we):I(ye,Ce)}function Ce(e){if("}"==e)return _.marked="string-2",_.state.tokenize=T,I(we)}function Se(e,t){return"variable"==e&&_.stream.match(/^\s*[?:]/,!1)||"?"==t?I(Se):":"==e?I(ye):"spread"==e?I(Se):O(ye)}function xe(e,t){return"<"==t?I(B(">"),de(ye,">"),$,xe):"|"==t||"."==e||"&"==t?I(ye):"["==e?I(ye,q("]"),xe):"extends"==t||"implements"==t?(_.marked="keyword",I(ye)):"?"==t?I(ye,q(":"),ye):void 0}function ke(e,t){if("<"==t)return I(B(">"),de(ye,">"),$,xe)}function Ne(){return O(ye,_e)}function _e(e,t){if("="==t)return I(ye)}function Oe(e,t){return"enum"==t?(_.marked="keyword",I(nt)):O(Ie,he,Ae,Me)}function Ie(e,t){return d&&M(t)?(_.marked="keyword",I(Ie)):"variable"==e?(L(t),I()):"spread"==e?I(Ie):"["==e?fe(Le,"]"):"{"==e?fe(De,"}"):void 0}function De(e,t){return"variable"!=e||_.stream.match(/^\s*:/,!1)?("variable"==e&&(_.marked="property"),"spread"==e?I(Ie):"}"==e?O():"["==e?I(z,q("]"),q(":"),De):I(q(":"),Ie,Ae)):(L(t),I(Ae))}function Le(){return O(Ie,Ae)}function Ae(e,t){if("="==t)return I(W)}function Me(e){if(","==e)return I(Oe)}function Re(e,t){if("keyword b"==e&&"else"==t)return I(B("form","else"),H,$)}function Fe(e,t){return"await"==t?I(Fe):"("==e?I(B(")"),Pe,$):void 0}function Pe(e){return"var"==e?I(Oe,je):"variable"==e?I(je):O(je)}function je(e,t){return")"==e?I():";"==e?I(je):"in"==t||"of"==t?(_.marked="keyword",I(z,je)):O(z,je)}function Ve(e,t){return"*"==t?(_.marked="keyword",I(Ve)):"variable"==e?(L(t),I(Ve)):"("==e?I(j,B(")"),de($e,")"),$,ge,H,U):d&&"<"==t?I(B(">"),de(Ne,">"),$,Ve):void 0}function Ue(e,t){return"*"==t?(_.marked="keyword",I(Ue)):"variable"==e?(L(t),I(Ue)):"("==e?I(j,B(")"),de($e,")"),$,ge,U):d&&"<"==t?I(B(">"),de(Ne,">"),$,Ue):void 0}function Be(e,t){return"keyword"==e||"variable"==e?(_.marked="type",I(Be)):"<"==t?I(B(">"),de(Ne,">"),$):void 0}function $e(e,t){return"@"==t&&I(z,$e),"spread"==e?I($e):d&&M(t)?(_.marked="keyword",I($e)):d&&"this"==e?I(he,Ae):O(Ie,he,Ae)}function qe(e,t){return"variable"==e?He(e,t):Ge(e,t)}function He(e,t){if("variable"==e)return L(t),I(Ge)}function Ge(e,t){return"<"==t?I(B(">"),de(Ne,">"),$,Ge):"extends"==t||"implements"==t||d&&","==e?("implements"==t&&(_.marked="keyword"),I(d?ye:z,Ge)):"{"==e?I(B("}"),ze,$):void 0}function ze(e,t){return"async"==e||"variable"==e&&("static"==t||"get"==t||"set"==t||d&&M(t))&&_.stream.match(/^\s+[\w$\xa1-\uffff]/,!1)?(_.marked="keyword",I(ze)):"variable"==e||"keyword"==_.style?(_.marked="property",I(We,ze)):"number"==e||"string"==e?I(We,ze):"["==e?I(z,he,q("]"),We,ze):"*"==t?(_.marked="keyword",I(ze)):d&&"("==e?O(Ue,ze):";"==e||","==e?I(ze):"}"==e?I():"@"==t?I(z,ze):void 0}function We(e,t){if("!"==t)return I(We);if("?"==t)return I(We);if(":"==e)return I(ye,Ae);if("="==t)return I(W);var n=_.state.lexical.prev;return O(n&&"interface"==n.info?Ue:Ve)}function Ke(e,t){return"*"==t?(_.marked="keyword",I(et,q(";"))):"default"==t?(_.marked="keyword",I(z,q(";"))):"{"==e?I(de(Qe,"}"),et,q(";")):O(H)}function Qe(e,t){return"as"==t?(_.marked="keyword",I(q("variable"))):"variable"==e?O(W,Qe):void 0}function Xe(e){return"string"==e?I():"("==e?O(z):"."==e?O(Y):O(Ye,Je,et)}function Ye(e,t){return"{"==e?fe(Ye,"}"):("variable"==e&&L(t),"*"==t&&(_.marked="keyword"),I(Ze))}function Je(e){if(","==e)return I(Ye,Je)}function Ze(e,t){if("as"==t)return _.marked="keyword",I(Ye)}function et(e,t){if("from"==t)return _.marked="keyword",I(z)}function tt(e){return"]"==e?I():O(de(W,"]"))}function nt(){return O(B("form"),Ie,q("{"),B("}"),de(rt,"}"),$,$)}function rt(){return O(Ie,Ae)}function it(e,t){return"operator"==e.lastType||","==e.lastType||h.test(t.charAt(0))||/[,.]/.test(t.charAt(0))}function ot(e,t,n){return t.tokenize==y&&/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(t.lastType)||"quasi"==t.lastType&&/\{\s*$/.test(e.string.slice(0,e.pos-(n||0)))}return r(j,"pushcontext"),r(V,"pushblockcontext"),j.lex=V.lex=!0,r(U,"popcontext"),U.lex=!0,r(B,"pushlex"),r($,"poplex"),$.lex=!0,r(q,"expect"),r(H,"statement"),r(G,"maybeCatchBinding"),r(z,"expression"),r(W,"expressionNoComma"),r(K,"parenExpr"),r(Q,"expressionInner"),r(X,"maybeexpression"),r(Y,"maybeoperatorComma"),r(J,"maybeoperatorNoComma"),r(Z,"quasi"),r(ee,"continueQuasi"),r(te,"arrowBody"),r(ne,"arrowBodyNoComma"),r(re,"maybeTarget"),r(ie,"target"),r(oe,"targetNoComma"),r(ae,"maybelabel"),r(se,"property"),r(le,"objprop"),r(ue,"getterSetter"),r(ce,"afterprop"),r(de,"commasep"),r(fe,"contCommasep"),r(pe,"block"),r(he,"maybetype"),r(me,"maybetypeOrIn"),r(ge,"mayberettype"),r(ve,"isKW"),r(ye,"typeexpr"),r(be,"maybeReturnType"),r(Ee,"typeprops"),r(Te,"typeprop"),r(we,"quasiType"),r(Ce,"continueQuasiType"),r(Se,"typearg"),r(xe,"afterType"),r(ke,"maybeTypeArgs"),r(Ne,"typeparam"),r(_e,"maybeTypeDefault"),r(Oe,"vardef"),r(Ie,"pattern"),r(De,"proppattern"),r(Le,"eltpattern"),r(Ae,"maybeAssign"),r(Me,"vardefCont"),r(Re,"maybeelse"),r(Fe,"forspec"),r(Pe,"forspec1"),r(je,"forspec2"),r(Ve,"functiondef"),r(Ue,"functiondecl"),r(Be,"typename"),r($e,"funarg"),r(qe,"classExpression"),r(He,"className"),r(Ge,"classNameAfter"),r(ze,"classBody"),r(We,"classfield"),r(Ke,"afterExport"),r(Qe,"exportField"),r(Xe,"afterImport"),r(Ye,"importSpec"),r(Je,"maybeMoreImports"),r(Ze,"maybeAs"),r(et,"maybeFrom"),r(tt,"arrayLiteral"),r(nt,"enumdef"),r(rt,"enummember"),r(it,"isContinuedStatement"),r(ot,"expressionAllowed"),{startState:function(e){var n={tokenize:y,lastType:"sof",cc:[],lexical:new x((e||0)-a,0,"block",!1),localVars:t.localVars,context:t.localVars&&new R(null,null,!1),indented:e||0};return t.globalVars&&"object"==typeof t.globalVars&&(n.globalVars=t.globalVars),n},token:function(e,t){if(e.sol()&&(t.lexical.hasOwnProperty("align")||(t.lexical.align=!1),t.indented=e.indentation(),C(e,t)),t.tokenize!=E&&e.eatSpace())return null;var r=t.tokenize(e,t);return"comment"==n?r:(t.lastType="operator"!=n||"++"!=i&&"--"!=i?n:"incdec",N(t,r,n,i,e))},indent:function(e,n){if(e.tokenize==E||e.tokenize==T)return o.Pass;if(e.tokenize!=y)return 0;var r,i=n&&n.charAt(0),l=e.lexical;if(!/^\s*else\b/.test(n))for(var u=e.cc.length-1;u>=0;--u){var c=e.cc[u];if(c==$)l=l.prev;else if(c!=Re&&c!=U)break}for(;("stat"==l.type||"form"==l.type)&&("}"==i||(r=e.cc[e.cc.length-1])&&(r==Y||r==J)&&!/^[,\.=+\-*:?[\(]/.test(n));)l=l.prev;s&&")"==l.type&&"stat"==l.prev.type&&(l=l.prev);var d=l.type,f=i==d;return"vardef"==d?l.indented+("operator"==e.lastType||","==e.lastType?l.info.length+1:0):"form"==d&&"{"==i?l.indented:"form"==d?l.indented+a:"stat"==d?l.indented+(it(e,n)?s||a:0):"switch"!=l.info||f||0==t.doubleIndentSwitch?l.align?l.column+(f?0:1):l.indented+(f?0:a):l.indented+(/^(?:case|default)\b/.test(n)?a:2*a)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:u?null:"/*",blockCommentEnd:u?null:"*/",blockCommentContinue:u?null:" * ",lineComment:u?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:u?"json":"javascript",jsonldMode:l,jsonMode:u,expressionAllowed:ot,skipExpression:function(e){N(e,"atom","atom","true",new o.StringStream("",2,null))}}})),o.registerHelper("wordChars","javascript",/[\w$]/),o.defineMIME("text/javascript","javascript"),o.defineMIME("text/ecmascript","javascript"),o.defineMIME("application/javascript","javascript"),o.defineMIME("application/x-javascript","javascript"),o.defineMIME("application/ecmascript","javascript"),o.defineMIME("application/json",{name:"javascript",json:!0}),o.defineMIME("application/x-json",{name:"javascript",json:!0}),o.defineMIME("application/manifest+json",{name:"javascript",json:!0}),o.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),o.defineMIME("text/typescript",{name:"javascript",typescript:!0}),o.defineMIME("application/typescript",{name:"javascript",typescript:!0});var s=i({__proto__:null,default:a.exports},[a.exports]);e.j=s},void 0===(o=r.apply(t,i))||(e.exports=o)},4471:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535),n(8058)],void 0===(o="function"==typeof(r=function(e,t,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.j=void 0;var r=Object.defineProperty,i=(e,t)=>r(e,"name",{value:t,configurable:!0});function o(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}i(o,"_mergeNamespaces");var a={exports:{}};!function(e){function t(e,t,n,r,i){e.openDialog?e.openDialog(t,i,{value:r,selectValueOnOpen:!0,bottom:e.options.search.bottom}):i(prompt(n,r))}function n(e){return e.phrase("Jump to line:")+' '+e.phrase("(Use line:column or scroll% syntax)")+""}function r(e,t){var n=Number(t);return/^[-+]/.test(t)?e.getCursor().line+n:n-1}e.defineOption("search",{bottom:!1}),i(t,"dialog"),i(n,"getJumpDialog"),i(r,"interpretLine"),e.commands.jumpToLine=function(e){var i=e.getCursor();t(e,n(e),e.phrase("Jump to line:"),i.line+1+":"+i.ch,(function(t){var n;if(t)if(n=/^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(t))e.setCursor(r(e,n[1]),Number(n[2]));else if(n=/^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(t)){var o=Math.round(e.lineCount()*Number(n[1])/100);/^[-+]/.test(n[1])&&(o=i.line+o+1),e.setCursor(o-1,i.ch)}else(n=/^\s*\:?\s*([\+\-]?\d+)\s*/.exec(t))&&e.setCursor(r(e,n[1]),i.ch)}))},e.keyMap.default["Alt-G"]="jumpToLine"}(t.a.exports,n.a.exports);var s=o({__proto__:null,default:a.exports},[a.exports]);e.j=s})?r.apply(t,i):r)||(e.exports=o)},5980:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(535),n(2645),n(6856),n(9196),n(3573),n(1850),n(2635)],void 0===(o="function"==typeof(r=function(e,t,n,r,i,o,a){"use strict";var s=Object.defineProperty,l=(e,t)=>s(e,"name",{value:t,configurable:!0});function u(e,t){const n=t.target||t.srcElement;if(!(n instanceof HTMLElement))return;if("SPAN"!==(null==n?void 0:n.nodeName))return;const r=n.getBoundingClientRect(),i={left:(r.left+r.right)/2,top:(r.top+r.bottom)/2};e.state.jump.cursor=i,e.state.jump.isHoldingModifier&&h(e)}function c(e){e.state.jump.isHoldingModifier||!e.state.jump.cursor?e.state.jump.isHoldingModifier&&e.state.jump.marker&&m(e):e.state.jump.cursor=null}function d(t,n){if(t.state.jump.isHoldingModifier||!p(n.key))return;t.state.jump.isHoldingModifier=!0,t.state.jump.cursor&&h(t);const r=l((a=>{a.code===n.code&&(t.state.jump.isHoldingModifier=!1,t.state.jump.marker&&m(t),e.C.off(document,"keyup",r),e.C.off(document,"click",i),t.off("mousedown",o))}),"onKeyUp"),i=l((e=>{const{destination:n,options:r}=t.state.jump;n&&r.onClick(n,e)}),"onClick"),o=l(((e,n)=>{t.state.jump.destination&&(n.codemirrorIgnore=!0)}),"onMouseDown");e.C.on(document,"keyup",r),e.C.on(document,"click",i),t.on("mousedown",o)}e.C.defineOption("jump",!1,((t,n,r)=>{if(r&&r!==e.C.Init){const n=t.state.jump.onMouseOver;e.C.off(t.getWrapperElement(),"mouseover",n);const r=t.state.jump.onMouseOut;e.C.off(t.getWrapperElement(),"mouseout",r),e.C.off(document,"keydown",t.state.jump.onKeyDown),delete t.state.jump}if(n){const r=t.state.jump={options:n,onMouseOver:u.bind(null,t),onMouseOut:c.bind(null,t),onKeyDown:d.bind(null,t)};e.C.on(t.getWrapperElement(),"mouseover",r.onMouseOver),e.C.on(t.getWrapperElement(),"mouseout",r.onMouseOut),e.C.on(document,"keydown",r.onKeyDown)}})),l(u,"onMouseOver"),l(c,"onMouseOut"),l(d,"onKeyDown");const f="undefined"!=typeof navigator&&navigator&&navigator.appVersion.includes("Mac");function p(e){return e===(f?"Meta":"Control")}function h(e){if(e.state.jump.marker)return;const{cursor:t,options:n}=e.state.jump,r=e.coordsChar(t),i=e.getTokenAt(r,!0),o=n.getDestination||e.getHelper(r,"jump");if(o){const t=o(i,n,e);if(t){const n=e.markText({line:r.line,ch:i.start},{line:r.line,ch:i.end},{className:"CodeMirror-jump-token"});e.state.jump.marker=n,e.state.jump.destination=t}}}function m(e){const{marker:t}=e.state.jump;e.state.jump.marker=null,e.state.jump.destination=null,t.clear()}l(p,"isJumpModifier"),l(h,"enableJumpMode"),l(m,"disableJumpMode"),e.C.registerHelper("jump","graphql",((e,n)=>{if(!n.schema||!n.onClick||!e.state)return;const{state:r}=e,{kind:i,step:o}=r,a=(0,t.g)(n.schema,r);return"Field"===i&&0===o&&a.fieldDef||"AliasedField"===i&&2===o&&a.fieldDef?(0,t.a)(a):"Directive"===i&&1===o&&a.directiveDef?(0,t.b)(a):"Argument"===i&&0===o&&a.argDef?(0,t.c)(a):"EnumValue"===i&&a.enumValue?(0,t.d)(a):"NamedType"===i&&a.type?(0,t.e)(a):void 0}))})?r.apply(t,i):r)||(e.exports=o)},9444:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(535),n(3573),n(6856),n(5609),n(9196),n(1850)],r=function(e,t,n,r,i,o){"use strict";var a=Object.defineProperty,s=(e,t)=>a(e,"name",{value:t,configurable:!0});const l=[t.LoneSchemaDefinitionRule,t.UniqueOperationTypesRule,t.UniqueTypeNamesRule,t.UniqueEnumValueNamesRule,t.UniqueFieldDefinitionNamesRule,t.UniqueDirectiveNamesRule,t.KnownTypeNamesRule,t.KnownDirectivesRule,t.UniqueDirectivesPerLocationRule,t.PossibleTypeExtensionsRule,t.UniqueArgumentNamesRule,t.UniqueInputFieldNamesRule];function u(e,n,r,i,o){const a=t.specifiedRules.filter((e=>e!==t.NoUnusedFragmentsRule&&e!==t.ExecutableDefinitionsRule&&(!i||e!==t.KnownFragmentNamesRule)));return r&&Array.prototype.push.apply(a,r),o&&Array.prototype.push.apply(a,l),(0,t.validate)(e,n,a).filter((e=>{if(e.message.includes("Unknown directive")&&e.nodes){const n=e.nodes[0];if(n&&n.kind===t.Kind.DIRECTIVE){const e=n.name.value;if("arguments"===e||"argumentDefinitions"===e)return!1}}return!0}))}s(u,"validateWithCustomRules");const c={["Error"]:1,["Warning"]:2,["Information"]:3,["Hint"]:4},d=s(((e,t)=>{if(!e)throw new Error(t)}),"invariant");function f(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,r=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0;var a,s;let l=null,u="";o&&(u="string"==typeof o?o:o.reduce(((e,n)=>e+(0,t.print)(n)+"\n\n"),""));const d=u?`${e}\n\n${u}`:e;try{l=(0,t.parse)(d)}catch(e){if(e instanceof t.GraphQLError){const t=m(null!==(s=null===(a=e.locations)||void 0===a?void 0:a[0])&&void 0!==s?s:{line:0,column:0},d);return[{severity:c.Error,message:e.message,source:"GraphQL: Syntax",range:t}]}throw e}return p(l,n,r,i)}function p(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(!n)return[];const r=u(n,e,arguments.length>2?arguments[2]:void 0,arguments.length>3?arguments[3]:void 0).flatMap((e=>h(e,c.Error,"Validation"))),i=(0,t.validate)(n,e,[t.NoDeprecatedCustomRule]).flatMap((e=>h(e,c.Warning,"Deprecation")));return r.concat(i)}function h(e,t,n){if(!e.nodes)return[];const i=[];for(const[o,a]of e.nodes.entries()){const s="Variable"!==a.kind&&"name"in a&&void 0!==a.name?a.name:"variable"in a&&void 0!==a.variable?a.variable:a;if(s){d(e.locations,"GraphQL validation error requires locations.");const a=e.locations[o],l=g(s),u=a.column+(l.end-l.start);i.push({source:`GraphQL: ${n}`,message:e.message,severity:t,range:new r.R(new r.P(a.line-1,a.column-1),new r.P(a.line-1,u))})}}return i}function m(e,t){const i=(0,n.o)(),o=i.startState(),a=t.split("\n");d(a.length>=e.line,"Query text must have more lines than where the error happened");let s=null;for(let t=0;t{const{schema:r,validationRules:i,externalFragments:o}=n;return f(t,r,i,void 0,o).map((t=>({message:t.message,severity:t.severity?v[t.severity-1]:v[0],type:t.source?y[t.source]:void 0,from:e.C.Pos(t.range.start.line,t.range.start.character),to:e.C.Pos(t.range.end.line,t.range.end.character)})))}))},void 0===(o=r.apply(t,i))||(e.exports=o)},8535:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(535),n(3573),n(6856),n(9196),n(1850)],void 0===(o="function"==typeof(r=function(e,t,n,r,i){"use strict";var o=Object.defineProperty,a=(e,t)=>o(e,"name",{value:t,configurable:!0});function s(e){l=e,u=e.length,c=d=f=-1,S(),x();const t=m();return E("EOF"),t}let l,u,c,d,f,p,h;function m(){const e=c,t=[];if(E("{"),!C("}")){do{t.push(g())}while(C(","));E("}")}return{kind:"Object",start:e,end:f,members:t}}function g(){const e=c,t="String"===h?b():null;E("String"),E(":");const n=y();return{kind:"Member",start:e,end:f,key:t,value:n}}function v(){const e=c,t=[];if(E("["),!C("]")){do{t.push(y())}while(C(","));E("]")}return{kind:"Array",start:e,end:f,values:t}}function y(){switch(h){case"[":return v();case"{":return m();case"String":case"Number":case"Boolean":case"Null":const e=b();return x(),e}E("Value")}function b(){return{kind:h,start:c,end:d,value:JSON.parse(l.slice(c,d))}}function E(e){if(h===e)return void x();let t;if("EOF"===h)t="[end of file]";else if(d-c>1)t="`"+l.slice(c,d)+"`";else{const e=l.slice(c).match(/^.+?\b/);t="`"+(e?e[0]:l[c])+"`"}throw w(`Expected ${e} but found ${t}.`)}a(s,"jsonParse"),a(m,"parseObj"),a(g,"parseMember"),a(v,"parseArr"),a(y,"parseVal"),a(b,"curToken"),a(E,"expect");class T extends Error{constructor(e,t){super(e),this.position=t}}function w(e){return new T(e,{start:c,end:d})}function C(e){if(h===e)return x(),!0}function S(){return d31;)if(92===p)switch(p=S(),p){case 34:case 47:case 92:case 98:case 102:case 110:case 114:case 116:S();break;case 117:S(),N(),N(),N(),N();break;default:throw w("Bad character escape sequence.")}else{if(d===u)throw w("Unterminated string.");S()}if(34!==p)throw w("Unterminated string.");S()}function N(){if(p>=48&&p<=57||p>=65&&p<=70||p>=97&&p<=102)return S();throw w("Expected hexadecimal digit.")}function _(){45===p&&S(),48===p?S():O(),46===p&&(S(),O()),69!==p&&101!==p||(p=S(),43!==p&&45!==p||S(),O())}function O(){if(p<48||p>57)throw w("Expected decimal digit.");do{S()}while(p>=48&&p<=57)}function I(e,t,n){var r;const i=[];for(const o of n.members)if(o){const n=null===(r=o.key)||void 0===r?void 0:r.value,a=t[n];if(a)for(const[t,n]of D(a,o.value))i.push(L(e,t,n));else i.push(L(e,o.key,`Variable "$${n}" does not appear in any GraphQL query.`))}return i}function D(e,n){if(!e||!n)return[];if(e instanceof t.GraphQLNonNull)return"Null"===n.kind?[[n,`Type "${e}" is non-nullable and cannot be null.`]]:D(e.ofType,n);if("Null"===n.kind)return[];if(e instanceof t.GraphQLList){const t=e.ofType;return"Array"===n.kind?M(n.values||[],(e=>D(t,e))):D(t,n)}if(e instanceof t.GraphQLInputObjectType){if("Object"!==n.kind)return[[n,`Type "${e}" must be an Object.`]];const r=Object.create(null),i=M(n.members,(t=>{var n;const i=null===(n=null==t?void 0:t.key)||void 0===n?void 0:n.value;r[i]=!0;const o=e.getFields()[i];return o?D(o?o.type:void 0,t.value):[[t.key,`Type "${e}" does not have a field "${i}".`]]}));for(const o of Object.keys(e.getFields())){const a=e.getFields()[o];!r[o]&&a.type instanceof t.GraphQLNonNull&&!a.defaultValue&&i.push([n,`Object of type "${e}" is missing required field "${o}".`])}return i}return"Boolean"===e.name&&"Boolean"!==n.kind||"String"===e.name&&"String"!==n.kind||"ID"===e.name&&"Number"!==n.kind&&"String"!==n.kind||"Float"===e.name&&"Number"!==n.kind||"Int"===e.name&&("Number"!==n.kind||(0|n.value)!==n.value)||(e instanceof t.GraphQLEnumType||e instanceof t.GraphQLScalarType)&&("String"!==n.kind&&"Number"!==n.kind&&"Boolean"!==n.kind&&"Null"!==n.kind||A(e.parseValue(n.value)))?[[n,`Expected value of type "${e}".`]]:[]}function L(e,t,n){return{message:n,severity:"error",type:"validation",from:e.posFromIndex(t.start),to:e.posFromIndex(t.end)}}function A(e){return null==e||e!=e}function M(e,t){return Array.prototype.concat.apply([],e.map(t))}a(T,"JSONSyntaxError"),a(w,"syntaxError"),a(C,"skip"),a(S,"ch"),a(x,"lex"),a(k,"readString"),a(N,"readHex"),a(_,"readNumber"),a(O,"readDigits"),e.C.registerHelper("lint","graphql-variables",((e,t,n)=>{if(!e)return[];let r;try{r=s(e)}catch(e){if(e instanceof T)return[L(n,e.position,e.message)];throw e}const{variableToType:i}=t;return i?I(n,i,r):[]})),a(I,"validateVariables"),a(D,"validateValue"),a(L,"lintError"),a(A,"isNullish"),a(M,"mapCat")})?r.apply(t,i):r)||(e.exports=o)},4054:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535)],void 0===(o="function"==typeof(r=function(e,t){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.l=void 0;var n=Object.defineProperty,r=(e,t)=>n(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};!function(e){var t="CodeMirror-lint-markers",n="CodeMirror-lint-line-";function i(t,n,i){var o=document.createElement("div");function a(t){if(!o.parentNode)return e.off(document,"mousemove",a);o.style.top=Math.max(0,t.clientY-o.offsetHeight-5)+"px",o.style.left=t.clientX+5+"px"}return o.className="CodeMirror-lint-tooltip cm-s-"+t.options.theme,o.appendChild(i.cloneNode(!0)),t.state.lint.options.selfContain?t.getWrapperElement().appendChild(o):document.body.appendChild(o),r(a,"position"),e.on(document,"mousemove",a),a(n),null!=o.style.opacity&&(o.style.opacity=1),o}function o(e){e.parentNode&&e.parentNode.removeChild(e)}function a(e){e.parentNode&&(null==e.style.opacity&&o(e),e.style.opacity=0,setTimeout((function(){o(e)}),600))}function s(t,n,o,s){var l=i(t,n,o);function u(){e.off(s,"mouseout",u),l&&(a(l),l=null)}r(u,"hide");var c=setInterval((function(){if(l)for(var e=s;;e=e.parentNode){if(e&&11==e.nodeType&&(e=e.host),e==document.body)return;if(!e){u();break}}if(!l)return clearInterval(c)}),400);e.on(s,"mouseout",u)}function l(e,t,n){for(var r in this.marked=[],t instanceof Function&&(t={getAnnotations:t}),t&&!0!==t||(t={}),this.options={},this.linterOptions=t.options||{},u)this.options[r]=u[r];for(var r in t)u.hasOwnProperty(r)?null!=t[r]&&(this.options[r]=t[r]):t.options||(this.linterOptions[r]=t[r]);this.timeout=null,this.hasGutter=n,this.onMouseOver=function(t){T(e,t)},this.waitingFor=0}r(i,"showTooltip"),r(o,"rm"),r(a,"hideTooltip"),r(s,"showTooltipFor"),r(l,"LintState");var u={highlightLines:!1,tooltips:!0,delay:500,lintOnChange:!0,getAnnotations:null,async:!1,selfContain:null,formatAnnotation:null,onUpdateLinting:null};function c(e){var n=e.state.lint;n.hasGutter&&e.clearGutter(t),n.options.highlightLines&&d(e);for(var r=0;r-1)&&u.push(e.message)}));for(var d=null,g=i.hasGutter&&document.createDocumentFragment(),v=0;v1,o.tooltips)),o.highlightLines&&e.addLineClass(s,"wrap",n+d)}}o.onUpdateLinting&&o.onUpdateLinting(r,a,e)}}function b(e){var t=e.state.lint;t&&(clearTimeout(t.timeout),t.timeout=setTimeout((function(){v(e)}),t.options.delay))}function E(e,t,n){for(var r=n.target||n.srcElement,i=document.createDocumentFragment(),o=0;on(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};e.a=o,function(e){var t=/MSIE \d/.test(navigator.userAgent)&&(null==document.documentMode||document.documentMode<8),n=e.Pos,i={"(":")>",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<","<":">>",">":"<<"};function o(e){return e&&e.bracketRegex||/[(){}[\]]/}function a(e,t,r){var a=e.getLineHandle(t.line),l=t.ch-1,u=r&&r.afterCursor;null==u&&(u=/(^| )cm-fat-cursor($| )/.test(e.getWrapperElement().className));var c=o(r),d=!u&&l>=0&&c.test(a.text.charAt(l))&&i[a.text.charAt(l)]||c.test(a.text.charAt(l+1))&&i[a.text.charAt(++l)];if(!d)return null;var f=">"==d.charAt(1)?1:-1;if(r&&r.strict&&f>0!=(l==t.ch))return null;var p=e.getTokenTypeAt(n(t.line,l+1)),h=s(e,n(t.line,l+(f>0?1:0)),f,p,r);return null==h?null:{from:n(t.line,l),to:h&&h.pos,match:h&&h.ch==d.charAt(0),forward:f>0}}function s(e,t,r,a,s){for(var l=s&&s.maxScanLineLength||1e4,u=s&&s.maxScanLines||1e3,c=[],d=o(s),f=r>0?Math.min(t.line+u,e.lastLine()+1):Math.max(e.firstLine()-1,t.line-u),p=t.line;p!=f;p+=r){var h=e.getLine(p);if(h){var m=r>0?0:h.length-1,g=r>0?h.length:-1;if(!(h.length>l))for(p==t.line&&(m=t.ch-(r<0?1:0));m!=g;m+=r){var v=h.charAt(m);if(d.test(v)&&(void 0===a||(e.getTokenTypeAt(n(p,m+1))||"")==(a||""))){var y=i[v];if(y&&">"==y.charAt(1)==r>0)c.push(v);else{if(!c.length)return{pos:n(p,m),ch:v};c.pop()}}}}}return p-r!=(r>0?e.lastLine():e.firstLine())&&null}function l(e,i,o){for(var s=e.state.matchBrackets.maxHighlightLineLength||1e3,l=o&&o.highlightNonMatching,u=[],c=e.listSelections(),d=0;da((e=>{const t=(0,n.o)({eatWhitespace:e=>e.eatWhile(n.i),lexRules:n.L,parseRules:n.P,editorConfig:{tabSize:e.tabSize}});return{config:e,startState:t.startState,token:t.token,indent:r.i,electricInput:/^\s*[})\]]/,fold:"brace",lineComment:"#",closeBrackets:{pairs:'()[]{}""',explode:"()[]{}"}}}),"name",{value:"graphqlModeFactory",configurable:!0}))();e.C.defineMode("graphql",s)})?r.apply(t,i):r)||(e.exports=o)},1430:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(535),n(3573),n(6856),n(6592),n(9196),n(1850)],void 0===(o="function"==typeof(r=function(e,t,n,r,i,o){"use strict";e.C.defineMode("graphql-results",(e=>{const t=(0,n.o)({eatWhitespace:e=>e.eatSpace(),lexRules:a,parseRules:s,editorConfig:{tabSize:e.tabSize}});return{config:e,startState:t.startState,token:t.token,indent:r.i,electricInput:/^\s*[}\]]/,fold:"brace",closeBrackets:{pairs:'[]{}""',explode:"[]{}"}}}));const a={Punctuation:/^\[|]|\{|\}|:|,/,Number:/^-?(?:0|(?:[1-9][0-9]*))(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?/,String:/^"(?:[^"\\]|\\(?:"|\/|\\|b|f|n|r|t|u[0-9a-fA-F]{4}))*"?/,Keyword:/^true|false|null/},s={Document:[(0,n.p)("{"),(0,n.l)("Entry",(0,n.p)(",")),(0,n.p)("}")],Entry:[(0,n.t)("String","def"),(0,n.p)(":"),"Value"],Value(e){switch(e.kind){case"Number":return"NumberValue";case"String":return"StringValue";case"Punctuation":switch(e.value){case"[":return"ListValue";case"{":return"ObjectValue"}return null;case"Keyword":switch(e.value){case"true":case"false":return"BooleanValue";case"null":return"NullValue"}return null}},NumberValue:[(0,n.t)("Number","number")],StringValue:[(0,n.t)("String","string")],BooleanValue:[(0,n.t)("Keyword","builtin")],NullValue:[(0,n.t)("Keyword","keyword")],ListValue:[(0,n.p)("["),(0,n.l)("Value",(0,n.p)(",")),(0,n.p)("]")],ObjectValue:[(0,n.p)("{"),(0,n.l)("ObjectField",(0,n.p)(",")),(0,n.p)("}")],ObjectField:[(0,n.t)("String","property"),(0,n.p)(":"),"Value"]}})?r.apply(t,i):r)||(e.exports=o)},3340:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[n(535),n(3573),n(6856),n(6592),n(9196),n(1850)],void 0===(o="function"==typeof(r=function(e,t,n,r,i,o){"use strict";var a=Object.defineProperty;e.C.defineMode("graphql-variables",(e=>{const t=(0,n.o)({eatWhitespace:e=>e.eatSpace(),lexRules:s,parseRules:l,editorConfig:{tabSize:e.tabSize}});return{config:e,startState:t.startState,token:t.token,indent:r.i,electricInput:/^\s*[}\]]/,fold:"brace",closeBrackets:{pairs:'[]{}""',explode:"[]{}"}}}));const s={Punctuation:/^\[|]|\{|\}|:|,/,Number:/^-?(?:0|(?:[1-9][0-9]*))(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?/,String:/^"(?:[^"\\]|\\(?:"|\/|\\|b|f|n|r|t|u[0-9a-fA-F]{4}))*"?/,Keyword:/^true|false|null/},l={Document:[(0,n.p)("{"),(0,n.l)("Variable",(0,n.b)((0,n.p)(","))),(0,n.p)("}")],Variable:[u("variable"),(0,n.p)(":"),"Value"],Value(e){switch(e.kind){case"Number":return"NumberValue";case"String":return"StringValue";case"Punctuation":switch(e.value){case"[":return"ListValue";case"{":return"ObjectValue"}return null;case"Keyword":switch(e.value){case"true":case"false":return"BooleanValue";case"null":return"NullValue"}return null}},NumberValue:[(0,n.t)("Number","number")],StringValue:[(0,n.t)("String","string")],BooleanValue:[(0,n.t)("Keyword","builtin")],NullValue:[(0,n.t)("Keyword","keyword")],ListValue:[(0,n.p)("["),(0,n.l)("Value",(0,n.b)((0,n.p)(","))),(0,n.p)("]")],ObjectValue:[(0,n.p)("{"),(0,n.l)("ObjectField",(0,n.b)((0,n.p)(","))),(0,n.p)("}")],ObjectField:[u("attribute"),(0,n.p)(":"),"Value"]};function u(e){return{style:e,match:e=>"String"===e.kind,update(e,t){e.name=t.value.slice(1,-1)}}}a(u,"name",{value:"namedKey",configurable:!0})})?r.apply(t,i):r)||(e.exports=o)},9509:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535),n(9407),n(8058)],r=function(e,t,n,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.s=void 0;var i=Object.defineProperty,o=(e,t)=>i(e,"name",{value:t,configurable:!0});function a(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}o(a,"_mergeNamespaces");var s={exports:{}};!function(e){function t(e,t){return"string"==typeof e?e=new RegExp(e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&"),t?"gi":"g"):e.global||(e=new RegExp(e.source,e.ignoreCase?"gi":"g")),{token:function(t){e.lastIndex=t.pos;var n=e.exec(t.string);if(n&&n.index==t.pos)return t.pos+=n[0].length||1,"searching";n?t.pos=n.index:t.skipToEnd()}}}function n(){this.posFrom=this.posTo=this.lastQuery=this.query=null,this.overlay=null}function r(e){return e.state.search||(e.state.search=new n)}function i(e){return"string"==typeof e&&e==e.toLowerCase()}function a(e,t,n){return e.getSearchCursor(t,n,{caseFold:i(t),multiline:!0})}function s(e,t,n,r,i){e.openDialog(t,r,{value:n,selectValueOnOpen:!0,closeOnEnter:!1,onClose:function(){m(e)},onKeyDown:i,bottom:e.options.search.bottom})}function l(e,t,n,r,i){e.openDialog?e.openDialog(t,i,{value:r,selectValueOnOpen:!0,bottom:e.options.search.bottom}):i(prompt(n,r))}function u(e,t,n,r){e.openConfirm?e.openConfirm(t,r):confirm(n)&&r[0]()}function c(e){return e.replace(/\\([nrt\\])/g,(function(e,t){return"n"==t?"\n":"r"==t?"\r":"t"==t?"\t":"\\"==t?"\\":e}))}function d(e){var t=e.match(/^\/(.*)\/([a-z]*)$/);if(t)try{e=new RegExp(t[1],-1==t[2].indexOf("i")?"":"i")}catch(e){}else e=c(e);return("string"==typeof e?""==e:e.test(""))&&(e=/x^/),e}function f(e,n,r){n.queryText=r,n.query=d(r),e.removeOverlay(n.overlay,i(n.query)),n.overlay=t(n.query,i(n.query)),e.addOverlay(n.overlay),e.showMatchesOnScrollbar&&(n.annotate&&(n.annotate.clear(),n.annotate=null),n.annotate=e.showMatchesOnScrollbar(n.query,i(n.query)))}function p(t,n,i,a){var u=r(t);if(u.query)return h(t,n);var c=t.getSelection()||u.lastQuery;if(c instanceof RegExp&&"x^"==c.source&&(c=null),i&&t.openDialog){var d=null,p=o((function(n,r){e.e_stop(r),n&&(n!=u.queryText&&(f(t,u,n),u.posFrom=u.posTo=t.getCursor()),d&&(d.style.opacity=1),h(t,r.shiftKey,(function(e,n){var r;n.line<3&&document.querySelector&&(r=t.display.wrapper.querySelector(".CodeMirror-dialog"))&&r.getBoundingClientRect().bottom-4>t.cursorCoords(n,"window").top&&((d=r).style.opacity=.4)})))}),"searchNext");s(t,v(t),c,p,(function(n,i){var o=e.keyName(n),a=t.getOption("extraKeys"),s=a&&a[o]||e.keyMap[t.getOption("keyMap")][o];"findNext"==s||"findPrev"==s||"findPersistentNext"==s||"findPersistentPrev"==s?(e.e_stop(n),f(t,r(t),i),t.execCommand(s)):"find"!=s&&"findPersistent"!=s||(e.e_stop(n),p(i,n))})),a&&c&&(f(t,u,c),h(t,n))}else l(t,v(t),"Search for:",c,(function(e){e&&!u.query&&t.operation((function(){f(t,u,e),u.posFrom=u.posTo=t.getCursor(),h(t,n)}))}))}function h(t,n,i){t.operation((function(){var o=r(t),s=a(t,o.query,n?o.posFrom:o.posTo);(s.find(n)||(s=a(t,o.query,n?e.Pos(t.lastLine()):e.Pos(t.firstLine(),0))).find(n))&&(t.setSelection(s.from(),s.to()),t.scrollIntoView({from:s.from(),to:s.to()},20),o.posFrom=s.from(),o.posTo=s.to(),i&&i(s.from(),s.to()))}))}function m(e){e.operation((function(){var t=r(e);t.lastQuery=t.query,t.query&&(t.query=t.queryText=null,e.removeOverlay(t.overlay),t.annotate&&(t.annotate.clear(),t.annotate=null))}))}function g(e,t){var n=e?document.createElement(e):document.createDocumentFragment();for(var r in t)n[r]=t[r];for(var i=2;in(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};e.a=o,function(e){var t,n,i=e.Pos;function o(e){var t=e.flags;return null!=t?t:(e.ignoreCase?"i":"")+(e.global?"g":"")+(e.multiline?"m":"")}function a(e,t){for(var n=o(e),r=n,i=0;ic);d++){var f=e.getLine(u++);r=null==r?f:r+"\n"+f}o*=2,t.lastIndex=n.ch;var p=t.exec(r);if(p){var h=r.slice(0,p.index).split("\n"),m=p[0].split("\n"),g=n.line+h.length-1,v=h[h.length-1].length;return{from:i(g,v),to:i(g+m.length-1,1==m.length?v+m[0].length:m[m.length-1].length),match:p}}}}function c(e,t,n){for(var r,i=0;i<=e.length;){t.lastIndex=i;var o=t.exec(e);if(!o)break;var a=o.index+o[0].length;if(a>e.length-n)break;(!r||a>r.index+r[0].length)&&(r=o),i=o.index+1}return r}function d(e,t,n){t=a(t,"g");for(var r=n.line,o=n.ch,s=e.firstLine();r>=s;r--,o=-1){var l=e.getLine(r),u=c(l,t,o<0?0:l.length-o);if(u)return{from:i(r,u.index),to:i(r,u.index+u[0].length),match:u}}}function f(e,t,n){if(!s(t))return d(e,t,n);t=a(t,"gm");for(var r,o=1,l=e.getLine(n.line).length-n.ch,u=n.line,f=e.firstLine();u>=f;){for(var p=0;p=f;p++){var h=e.getLine(u--);r=null==r?h:h+"\n"+r}o*=2;var m=c(r,t,l);if(m){var g=r.slice(0,m.index).split("\n"),v=m[0].split("\n"),y=u+g.length,b=g[g.length-1].length;return{from:i(y,b),to:i(y+v.length-1,1==v.length?b+v[0].length:v[v.length-1].length),match:m}}}}function p(e,t,n,r){if(e.length==t.length)return n;for(var i=0,o=n+Math.max(0,e.length-t.length);;){if(i==o)return i;var a=i+o>>1,s=r(e.slice(0,a)).length;if(s==n)return a;s>n?o=a:i=a+1}}function h(e,r,o,a){if(!r.length)return null;var s=a?t:n,l=s(r).split(/\r|\n\r?/);e:for(var u=o.line,c=o.ch,d=e.lastLine()+1-l.length;u<=d;u++,c=0){var f=e.getLine(u).slice(c),h=s(f);if(1==l.length){var m=h.indexOf(l[0]);if(-1==m)continue e;return o=p(f,h,m,s)+c,{from:i(u,p(f,h,m,s)+c),to:i(u,p(f,h,m+l[0].length,s)+c)}}var g=h.length-l[0].length;if(h.slice(g)==l[0]){for(var v=1;v=d;u--,c=-1){var f=e.getLine(u);c>-1&&(f=f.slice(0,c));var h=s(f);if(1==l.length){var m=h.lastIndexOf(l[0]);if(-1==m)continue e;return{from:i(u,p(f,h,m,s)),to:i(u,p(f,h,m+l[0].length,s))}}var g=l[l.length-1];if(h.slice(0,g.length)==g){var v=1;for(o=u-l.length+1;v(this.doc.getLine(n.line)||"").length&&(n.ch=0,n.line++)),0!=e.cmpPos(n,this.doc.clipPos(n))))return this.atOccurrence=!1;var r=this.matches(t,n);if(this.afterEmptyMatch=r&&0==e.cmpPos(r.from,r.to),r)return this.pos=r,this.atOccurrence=!0,this.pos.match||!0;var o=i(t?this.doc.firstLine():this.doc.lastLine()+1,0);return this.pos={from:o,to:o},this.atOccurrence=!1},from:function(){if(this.atOccurrence)return this.pos.from},to:function(){if(this.atOccurrence)return this.pos.to},replace:function(t,n){if(this.atOccurrence){var r=e.splitLines(t);this.doc.replaceRange(r,this.pos.from,this.pos.to,n),this.pos.to=i(this.pos.from.line+r.length-1,r[r.length-1].length+(1==r.length?this.pos.from.ch:0))}}},e.defineExtension("getSearchCursor",(function(e,t,n){return new g(this.doc,e,t,n)})),e.defineDocExtension("getSearchCursor",(function(e,t,n){return new g(this,e,t,n)})),e.defineExtension("selectMatches",(function(t,n){for(var r=[],i=this.getSearchCursor(t,this.getCursor("from"),n);i.findNext()&&!(e.cmpPos(i.to(),this.getCursor("to"))>0);)r.push({anchor:i.from(),head:i.to()});r.length&&this.setSelections(r,0)}))}(t.a.exports);var a=i({__proto__:null,default:o.exports},[o.exports]);e.s=a})?r.apply(t,i):r)||(e.exports=o)},6980:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535)],void 0===(o="function"==typeof(r=function(e,t){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.s=void 0;var n=Object.defineProperty,r=(e,t)=>n(e,"name",{value:t,configurable:!0});function i(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}r(i,"_mergeNamespaces");var o={exports:{}};!function(e){var t="CodeMirror-hint",n="CodeMirror-hint-active";function i(e,t){if(this.cm=e,this.options=t,this.widget=null,this.debounce=0,this.tick=0,this.startPos=this.cm.getCursor("start"),this.startLen=this.cm.getLine(this.startPos.line).length-this.cm.getSelection().length,this.options.updateOnCursorActivity){var n=this;e.on("cursorActivity",this.activityFunc=function(){n.cursorActivity()})}}e.showHint=function(e,t,n){if(!t)return e.showHint(n);n&&n.async&&(t.async=!0);var r={hint:t};if(n)for(var i in n)r[i]=n[i];return e.showHint(r)},e.defineExtension("showHint",(function(t){t=s(this,this.getCursor("start"),t);var n=this.listSelections();if(!(n.length>1)){if(this.somethingSelected()){if(!t.hint.supportsSelection)return;for(var r=0;rf.clientHeight+1;if(setTimeout((function(){D=a.getScrollInfo()})),L.bottom-I>0){var M=L.bottom-L.top;if(E.top-(E.bottom-L.top)-M>0)f.style.top=(w=E.top-M-x)+"px",C=!1;else if(M>I){f.style.height=I-5+"px",f.style.top=(w=E.bottom-L.top-x)+"px";var R=a.getCursor();i.from.ch!=R.ch&&(E=a.cursorCoords(R),f.style.left=(T=E.left-S)+"px",L=f.getBoundingClientRect())}}var F,P=L.right-O;if(A&&(P+=a.display.nativeBarWidth),P>0&&(L.right-L.left>O&&(f.style.width=O-5+"px",P-=L.right-L.left-O),f.style.left=(T=E.left-P-S)+"px"),A)for(var j=f.firstChild;j;j=j.nextSibling)j.style.paddingRight=a.display.nativeBarWidth+"px";a.addKeyMap(this.keyMap=u(r,{moveFocus:function(e,t){o.changeActive(o.selectedHint+e,t)},setFocus:function(e){o.changeActive(e)},menuSize:function(){return o.screenAmount()},length:h.length,close:function(){r.close()},pick:function(){o.pick()},data:i})),r.options.closeOnUnfocus&&(a.on("blur",this.onBlur=function(){F=setTimeout((function(){r.close()}),100)}),a.on("focus",this.onFocus=function(){clearTimeout(F)})),a.on("scroll",this.onScroll=function(){var e=a.getScrollInfo(),t=a.getWrapperElement().getBoundingClientRect();D||(D=a.getScrollInfo());var n=w+D.top-e.top,i=n-(d.pageYOffset||(s.documentElement||s.body).scrollTop);if(C||(i+=f.offsetHeight),i<=t.top||i>=t.bottom)return r.close();f.style.top=n+"px",f.style.left=T+D.left-e.left+"px"}),e.on(f,"dblclick",(function(e){var t=c(f,e.target||e.srcElement);t&&null!=t.hintId&&(o.changeActive(t.hintId),o.pick())})),e.on(f,"click",(function(e){var t=c(f,e.target||e.srcElement);t&&null!=t.hintId&&(o.changeActive(t.hintId),r.options.completeOnSingleClick&&o.pick())})),e.on(f,"mousedown",(function(){setTimeout((function(){a.focus()}),20)}));var V=this.getSelectedHintRange();return 0===V.from&&0===V.to||this.scrollToActive(),e.signal(i,"select",h[this.selectedHint],f.childNodes[this.selectedHint]),!0}function f(e,t){if(!e.somethingSelected())return t;for(var n=[],r=0;r0?t(e):a(r+1)}))}r(a,"run"),a(0)}),"resolved");return a.async=!0,a.supportsSelection=!0,a}return(i=t.getHelper(t.getCursor(),"hintWords"))?function(t){return e.hint.fromList(t,{words:i})}:e.hint.anyword?function(t,n){return e.hint.anyword(t,n)}:function(){}}i.prototype={close:function(){this.active()&&(this.cm.state.completionActive=null,this.tick=null,this.options.updateOnCursorActivity&&this.cm.off("cursorActivity",this.activityFunc),this.widget&&this.data&&e.signal(this.data,"close"),this.widget&&this.widget.close(),e.signal(this.cm,"endCompletion",this.cm))},active:function(){return this.cm.state.completionActive==this},pick:function(t,n){var r=t.list[n],i=this;this.cm.operation((function(){r.hint?r.hint(i.cm,t,r):i.cm.replaceRange(l(r),r.from||t.from,r.to||t.to,"complete"),e.signal(t,"pick",r),i.cm.scrollIntoView()})),this.options.closeOnPick&&this.close()},cursorActivity:function(){this.debounce&&(a(this.debounce),this.debounce=0);var e=this.startPos;this.data&&(e=this.data.from);var t=this.cm.getCursor(),n=this.cm.getLine(t.line);if(t.line!=this.startPos.line||n.length-t.ch!=this.startLen-this.startPos.ch||t.ch=this.data.list.length?t=r?this.data.list.length-1:0:t<0&&(t=r?0:this.data.list.length-1),this.selectedHint!=t){var i=this.hints.childNodes[this.selectedHint];i&&(i.className=i.className.replace(" "+n,""),i.removeAttribute("aria-selected")),(i=this.hints.childNodes[this.selectedHint=t]).className+=" "+n,i.setAttribute("aria-selected","true"),this.completion.cm.getInputField().setAttribute("aria-activedescendant",i.id),this.scrollToActive(),e.signal(this.data,"select",this.data.list[this.selectedHint],i)}},scrollToActive:function(){var e=this.getSelectedHintRange(),t=this.hints.childNodes[e.from],n=this.hints.childNodes[e.to],r=this.hints.firstChild;t.offsetTopthis.hints.scrollTop+this.hints.clientHeight&&(this.hints.scrollTop=n.offsetTop+n.offsetHeight-this.hints.clientHeight+r.offsetTop)},screenAmount:function(){return Math.floor(this.hints.clientHeight/this.hints.firstChild.offsetHeight)||1},getSelectedHintRange:function(){var e=this.completion.options.scrollMargin||0;return{from:Math.max(0,this.selectedHint-e),to:Math.min(this.data.list.length-1,this.selectedHint+e)}}},r(f,"applicableHelpers"),r(p,"fetchHints"),r(h,"resolveAutoHints"),e.registerHelper("hint","auto",{resolve:h}),e.registerHelper("hint","fromList",(function(t,n){var r,i=t.getCursor(),o=t.getTokenAt(i),a=e.Pos(i.line,o.start),s=i;o.start,]/,closeOnPick:!0,closeOnUnfocus:!0,updateOnCursorActivity:!0,completeOnSingleClick:!0,container:null,customKeys:null,extraKeys:null,paddingForScrollbar:!0,moveOnOverlap:!0};e.defineOption("hintOptions",null)}(t.a.exports);var a=i({__proto__:null,default:o.exports},[o.exports]);e.s=a})?r.apply(t,i):r)||(e.exports=o)},2568:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(535),n(9407),n(9171)],void 0===(o="function"==typeof(r=function(e,t,n,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.s=void 0;var i=Object.defineProperty,o=(e,t)=>i(e,"name",{value:t,configurable:!0});function a(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}o(a,"_mergeNamespaces");var s={exports:{}};!function(e){var t=e.commands,n=e.Pos;function r(t,r,i){if(i<0&&0==r.ch)return t.clipPos(n(r.line-1));var o=t.getLine(r.line);if(i>0&&r.ch>=o.length)return t.clipPos(n(r.line+1,0));for(var a,s="start",l=r.ch,u=l,c=i<0?0:o.length,d=0;u!=c;u+=i,d++){var f=o.charAt(i<0?u-1:u),p="_"!=f&&e.isWordChar(f)?"w":"o";if("w"==p&&f.toUpperCase()==f&&(p="W"),"start"==s)"o"!=p?(s="in",a=p):l=u+i;else if("in"==s&&a!=p){if("w"==a&&"W"==p&&i<0&&u--,"W"==a&&"w"==p&&i>0){if(u==l+1){a="w";continue}u--}break}}return n(r.line,u)}function i(e,t){e.extendSelectionsBy((function(n){return e.display.shift||e.doc.extend||n.empty()?r(e.doc,n.head,t):t<0?n.from():n.to()}))}function a(t,r){if(t.isReadOnly())return e.Pass;t.operation((function(){for(var e=t.listSelections().length,i=[],o=-1,a=0;a=n&&e.execCommand("goLineUp")}e.scrollTo(null,t.top-e.defaultTextHeight())},t.scrollLineDown=function(e){var t=e.getScrollInfo();if(!e.somethingSelected()){var n=e.lineAtHeight(t.top,"local")+1;e.getCursor().line<=n&&e.execCommand("goLineDown")}e.scrollTo(null,t.top+e.defaultTextHeight())},t.splitSelectionByLine=function(e){for(var t=e.listSelections(),r=[],i=0;io.line&&s==a.line&&0==a.ch||r.push({anchor:s==o.line?o:n(s,0),head:s==a.line?a:n(s)});e.setSelections(r,0)},t.singleSelectionTop=function(e){var t=e.listSelections()[0];e.setSelection(t.anchor,t.head,{scroll:!1})},t.selectLine=function(e){for(var t=e.listSelections(),r=[],i=0;i=0;a--){var u=r[i[a]];if(!(l&&e.cmpPos(u.head,l)>0)){var c=s(t,u.head);l=c.from,t.replaceRange(n(c.word),c.from,c.to)}}}))}function m(t){var n=t.getCursor("from"),r=t.getCursor("to");if(0==e.cmpPos(n,r)){var i=s(t,n);if(!i.word)return;n=i.from,r=i.to}return{from:n,to:r,query:t.getRange(n,r),word:i}}function g(e,t){var r=m(e);if(r){var i=r.query,o=e.getSearchCursor(i,t?r.to:r.from);(t?o.findNext():o.findPrevious())?e.setSelection(o.from(),o.to()):(o=e.getSearchCursor(i,t?n(e.firstLine(),0):e.clipPos(n(e.lastLine()))),(t?o.findNext():o.findPrevious())?e.setSelection(o.from(),o.to()):r.word&&e.setSelection(r.from,r.to))}}o(d,"selectBetweenBrackets"),t.selectScope=function(e){d(e)||e.execCommand("selectAll")},t.selectBetweenBrackets=function(t){if(!d(t))return e.Pass},o(f,"puncType"),t.goToBracket=function(t){t.extendSelectionsBy((function(r){var i=t.scanForBracket(r.head,1,f(t.getTokenTypeAt(r.head)));if(i&&0!=e.cmpPos(i.pos,r.head))return i.pos;var o=t.scanForBracket(r.head,-1,f(t.getTokenTypeAt(n(r.head.line,r.head.ch+1))));return o&&n(o.pos.line,o.pos.ch+1)||r.head}))},t.swapLineUp=function(t){if(t.isReadOnly())return e.Pass;for(var r=t.listSelections(),i=[],o=t.firstLine()-1,a=[],s=0;so?i.push(u,c):i.length&&(i[i.length-1]=c),o=c}t.operation((function(){for(var e=0;et.lastLine()?t.replaceRange("\n"+s,n(t.lastLine()),null,"+swapLine"):t.replaceRange(s+"\n",n(o,0),null,"+swapLine")}t.setSelections(a),t.scrollIntoView()}))},t.swapLineDown=function(t){if(t.isReadOnly())return e.Pass;for(var r=t.listSelections(),i=[],o=t.lastLine()+1,a=r.length-1;a>=0;a--){var s=r[a],l=s.to().line+1,u=s.from().line;0!=s.to().ch||s.empty()||l--,l=0;e-=2){var r=i[e],o=i[e+1],a=t.getLine(r);r==t.lastLine()?t.replaceRange("",n(r-1),n(r),"+swapLine"):t.replaceRange("",n(r,0),n(r+1,0),"+swapLine"),t.replaceRange(a+"\n",n(o,0),null,"+swapLine")}t.scrollIntoView()}))},t.toggleCommentIndented=function(e){e.toggleComment({indent:!0})},t.joinLines=function(e){for(var t=e.listSelections(),r=[],i=0;i=0;o--){var a=r[o].head,s=t.getRange({line:a.line,ch:0},a),l=e.countColumn(s,null,t.getOption("tabSize")),u=t.findPosH(a,-1,"char",!1);if(s&&!/\S/.test(s)&&l%i==0){var c=new n(a.line,e.findColumn(s,l-i,i));c.ch!=a.ch&&(u=c)}t.replaceRange("",u,a,"+delete")}}))},t.delLineRight=function(e){e.operation((function(){for(var t=e.listSelections(),r=t.length-1;r>=0;r--)e.replaceRange("",t[r].anchor,n(t[r].to().line),"+delete");e.scrollIntoView()}))},t.upcaseAtCursor=function(e){h(e,(function(e){return e.toUpperCase()}))},t.downcaseAtCursor=function(e){h(e,(function(e){return e.toLowerCase()}))},t.setSublimeMark=function(e){e.state.sublimeMark&&e.state.sublimeMark.clear(),e.state.sublimeMark=e.setBookmark(e.getCursor())},t.selectToSublimeMark=function(e){var t=e.state.sublimeMark&&e.state.sublimeMark.find();t&&e.setSelection(e.getCursor(),t)},t.deleteToSublimeMark=function(t){var n=t.state.sublimeMark&&t.state.sublimeMark.find();if(n){var r=t.getCursor(),i=n;if(e.cmpPos(r,i)>0){var o=i;i=r,r=o}t.state.sublimeKilled=t.getRange(r,i),t.replaceRange("",r,i)}},t.swapWithSublimeMark=function(e){var t=e.state.sublimeMark&&e.state.sublimeMark.find();t&&(e.state.sublimeMark.clear(),e.state.sublimeMark=e.setBookmark(e.getCursor()),e.setCursor(t))},t.sublimeYank=function(e){null!=e.state.sublimeKilled&&e.replaceSelection(e.state.sublimeKilled,null,"paste")},t.showInCenter=function(e){var t=e.cursorCoords(null,"local");e.scrollTo(null,(t.top+t.bottom)/2-e.getScrollInfo().clientHeight/2)},o(m,"getTarget"),o(g,"findAndGoTo"),t.findUnder=function(e){g(e,!0)},t.findUnderPrevious=function(e){g(e,!1)},t.findAllUnder=function(e){var t=m(e);if(t){for(var n=e.getSearchCursor(t.query),r=[],i=-1;n.findNext();)r.push({anchor:n.from(),head:n.to()}),n.from().line<=t.from.line&&n.from().ch<=t.from.ch&&i++;e.setSelections(r,i)}};var v=e.keyMap;v.macSublime={"Cmd-Left":"goLineStartSmart","Shift-Tab":"indentLess","Shift-Ctrl-K":"deleteLine","Alt-Q":"wrapLines","Ctrl-Left":"goSubwordLeft","Ctrl-Right":"goSubwordRight","Ctrl-Alt-Up":"scrollLineUp","Ctrl-Alt-Down":"scrollLineDown","Cmd-L":"selectLine","Shift-Cmd-L":"splitSelectionByLine",Esc:"singleSelectionTop","Cmd-Enter":"insertLineAfter","Shift-Cmd-Enter":"insertLineBefore","Cmd-D":"selectNextOccurrence","Shift-Cmd-Space":"selectScope","Shift-Cmd-M":"selectBetweenBrackets","Cmd-M":"goToBracket","Cmd-Ctrl-Up":"swapLineUp","Cmd-Ctrl-Down":"swapLineDown","Cmd-/":"toggleCommentIndented","Cmd-J":"joinLines","Shift-Cmd-D":"duplicateLine",F5:"sortLines","Shift-F5":"reverseSortLines","Cmd-F5":"sortLinesInsensitive","Shift-Cmd-F5":"reverseSortLinesInsensitive",F2:"nextBookmark","Shift-F2":"prevBookmark","Cmd-F2":"toggleBookmark","Shift-Cmd-F2":"clearBookmarks","Alt-F2":"selectBookmarks",Backspace:"smartBackspace","Cmd-K Cmd-D":"skipAndSelectNextOccurrence","Cmd-K Cmd-K":"delLineRight","Cmd-K Cmd-U":"upcaseAtCursor","Cmd-K Cmd-L":"downcaseAtCursor","Cmd-K Cmd-Space":"setSublimeMark","Cmd-K Cmd-A":"selectToSublimeMark","Cmd-K Cmd-W":"deleteToSublimeMark","Cmd-K Cmd-X":"swapWithSublimeMark","Cmd-K Cmd-Y":"sublimeYank","Cmd-K Cmd-C":"showInCenter","Cmd-K Cmd-G":"clearBookmarks","Cmd-K Cmd-Backspace":"delLineLeft","Cmd-K Cmd-1":"foldAll","Cmd-K Cmd-0":"unfoldAll","Cmd-K Cmd-J":"unfoldAll","Ctrl-Shift-Up":"addCursorToPrevLine","Ctrl-Shift-Down":"addCursorToNextLine","Cmd-F3":"findUnder","Shift-Cmd-F3":"findUnderPrevious","Alt-F3":"findAllUnder","Shift-Cmd-[":"fold","Shift-Cmd-]":"unfold","Cmd-I":"findIncremental","Shift-Cmd-I":"findIncrementalReverse","Cmd-H":"replace",F3:"findNext","Shift-F3":"findPrev",fallthrough:"macDefault"},e.normalizeKeyMap(v.macSublime),v.pcSublime={"Shift-Tab":"indentLess","Shift-Ctrl-K":"deleteLine","Alt-Q":"wrapLines","Ctrl-T":"transposeChars","Alt-Left":"goSubwordLeft","Alt-Right":"goSubwordRight","Ctrl-Up":"scrollLineUp","Ctrl-Down":"scrollLineDown","Ctrl-L":"selectLine","Shift-Ctrl-L":"splitSelectionByLine",Esc:"singleSelectionTop","Ctrl-Enter":"insertLineAfter","Shift-Ctrl-Enter":"insertLineBefore","Ctrl-D":"selectNextOccurrence","Shift-Ctrl-Space":"selectScope","Shift-Ctrl-M":"selectBetweenBrackets","Ctrl-M":"goToBracket","Shift-Ctrl-Up":"swapLineUp","Shift-Ctrl-Down":"swapLineDown","Ctrl-/":"toggleCommentIndented","Ctrl-J":"joinLines","Shift-Ctrl-D":"duplicateLine",F9:"sortLines","Shift-F9":"reverseSortLines","Ctrl-F9":"sortLinesInsensitive","Shift-Ctrl-F9":"reverseSortLinesInsensitive",F2:"nextBookmark","Shift-F2":"prevBookmark","Ctrl-F2":"toggleBookmark","Shift-Ctrl-F2":"clearBookmarks","Alt-F2":"selectBookmarks",Backspace:"smartBackspace","Ctrl-K Ctrl-D":"skipAndSelectNextOccurrence","Ctrl-K Ctrl-K":"delLineRight","Ctrl-K Ctrl-U":"upcaseAtCursor","Ctrl-K Ctrl-L":"downcaseAtCursor","Ctrl-K Ctrl-Space":"setSublimeMark","Ctrl-K Ctrl-A":"selectToSublimeMark","Ctrl-K Ctrl-W":"deleteToSublimeMark","Ctrl-K Ctrl-X":"swapWithSublimeMark","Ctrl-K Ctrl-Y":"sublimeYank","Ctrl-K Ctrl-C":"showInCenter","Ctrl-K Ctrl-G":"clearBookmarks","Ctrl-K Ctrl-Backspace":"delLineLeft","Ctrl-K Ctrl-1":"foldAll","Ctrl-K Ctrl-0":"unfoldAll","Ctrl-K Ctrl-J":"unfoldAll","Ctrl-Alt-Up":"addCursorToPrevLine","Ctrl-Alt-Down":"addCursorToNextLine","Ctrl-F3":"findUnder","Shift-Ctrl-F3":"findUnderPrevious","Alt-F3":"findAllUnder","Shift-Ctrl-[":"fold","Shift-Ctrl-]":"unfold","Ctrl-I":"findIncremental","Shift-Ctrl-I":"findIncrementalReverse","Ctrl-H":"replace",F3:"findNext","Shift-F3":"findPrev",fallthrough:"pcDefault"},e.normalizeKeyMap(v.pcSublime);var y=v.default==v.macDefault;v.sublime=y?v.macSublime:v.pcSublime}(t.a.exports,n.a.exports,r.a.exports);var l=a({__proto__:null,default:s.exports},[s.exports]);e.s=l})?r.apply(t,i):r)||(e.exports=o)},3691:function(e,t){var n,r;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,void 0===(r="function"==typeof(n=function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.fetcherReturnToPromise=function(e){return t(this,void 0,void 0,(function*(){const i=yield e;return r(i)?function(e){var n;return t(this,void 0,void 0,(function*(){const t=null===(n=("return"in e?e:e[Symbol.asyncIterator]()).return)||void 0===n?void 0:n.bind(e),r=("next"in e?e:e[Symbol.asyncIterator]()).next.bind(e),i=yield r();return null==t||t(),i.value}))}(i):n(i)?(o=i,new Promise(((e,t)=>{const n=o.subscribe({next:t=>{e(t),n.unsubscribe()},error:t,complete:()=>{t(new Error("no value resolved"))}})}))):i;var o}))},e.isAsyncIterable=r,e.isObservable=n,e.isPromise=function(e){return"object"==typeof e&&null!==e&&"function"==typeof e.then};var t=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function a(e){try{l(r.next(e))}catch(e){o(e)}}function s(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))};function n(e){return"object"==typeof e&&null!==e&&"subscribe"in e&&"function"==typeof e.subscribe}function r(e){return"object"==typeof e&&null!==e&&("AsyncGenerator"===e[Symbol.toStringTag]||Symbol.asyncIterator in e)}})?n.apply(t,[t]):n)||(e.exports=r)},5454:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(2501)],void 0===(o="function"==typeof(r=function(e,t){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createGraphiQLFetcher=function(e){let n;if("undefined"!=typeof window&&window.fetch&&(n=window.fetch),null!==(null==e?void 0:e.enableIncrementalDelivery)&&!1===e.enableIncrementalDelivery||(e.enableIncrementalDelivery=!0),e.fetch&&(n=e.fetch),!n)throw new Error("No valid fetcher implementation available");const r=(0,t.createSimpleFetcher)(e,n),i=e.enableIncrementalDelivery?(0,t.createMultipartFetcher)(e,n):r;return(n,o)=>{if("IntrospectionQuery"===n.operationName)return(e.schemaFetcher||r)(n,o);if((null==o?void 0:o.documentAST)&&(0,t.isSubscriptionWithName)(o.documentAST,n.operationName||void 0)){const r=(0,t.getWsFetcher)(e,o);if(!r)throw new Error("Your GraphiQL createFetcher is not properly configured for websocket subscriptions yet. "+(e.subscriptionUrl?`Provided URL ${e.subscriptionUrl} failed`:"Please provide subscriptionUrl, wsClient or legacyClient option first."));return r(n)}return i(n,o)}}})?r.apply(t,i):r)||(e.exports=o)},1799:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(3080),n(5454)],void 0===(o="function"==typeof(r=function(e,t,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r={createGraphiQLFetcher:!0};Object.defineProperty(e,"createGraphiQLFetcher",{enumerable:!0,get:function(){return n.createGraphiQLFetcher}}),Object.keys(t).forEach((function(n){"default"!==n&&"__esModule"!==n&&(Object.prototype.hasOwnProperty.call(r,n)||n in e&&e[n]===t[n]||Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[n]}}))}))})?r.apply(t,i):r)||(e.exports=o)},2501:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(3573),n(8488),n(8042)],r=function(e,t,r,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.isSubscriptionWithName=e.getWsFetcher=e.createWebsocketsFetcherFromUrl=e.createWebsocketsFetcherFromClient=e.createSimpleFetcher=e.createMultipartFetcher=e.createLegacyWebsocketsFetcher=void 0;var o=function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function a(e){try{l(r.next(e))}catch(e){o(e)}}function s(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,s)}l((r=r.apply(e,t||[])).next())}))},a=function(e){return this instanceof a?(this.v=e,this):new a(e)},s=function(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e="function"==typeof __values?__values(e):e[Symbol.iterator](),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise((function(r,i){!function(e,t,n,r){Promise.resolve(r).then((function(t){e({value:t,done:n})}),t)}(r,i,(t=e[n](t)).done,t.value)}))}}},l=function(e,t,n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var r,i=n.apply(e,t||[]),o=[];return r={},s("next"),s("throw"),s("return"),r[Symbol.asyncIterator]=function(){return this},r;function s(e){i[e]&&(r[e]=function(t){return new Promise((function(n,r){o.push([e,t,n,r])>1||l(e,t)}))})}function l(e,t){try{(n=i[e](t)).value instanceof a?Promise.resolve(n.value.v).then(u,c):d(o[0][2],n)}catch(e){d(o[0][3],e)}var n}function u(e){l("next",e)}function c(e){l("throw",e)}function d(e,t){e(t),o.shift(),o.length&&l(o[0][0],o[0][1])}};e.isSubscriptionWithName=(e,n)=>{let r=!1;return(0,t.visit)(e,{OperationDefinition(e){var t;n===(null===(t=e.name)||void 0===t?void 0:t.value)&&"subscription"===e.operation&&(r=!0)}}),r};e.createSimpleFetcher=(e,t)=>(n,r)=>o(void 0,void 0,void 0,(function*(){return(yield t(e.url,{method:"POST",body:JSON.stringify(n),headers:Object.assign(Object.assign({"content-type":"application/json"},e.headers),null==r?void 0:r.headers)})).json()}));const u=(e,t)=>{let r;try{const{createClient:i}=n(7674);return r=i({url:e,connectionParams:t}),c(r)}catch(t){if((e=>"object"==typeof e&&null!==e&&"code"in e)(t)&&"MODULE_NOT_FOUND"===t.code)throw new Error("You need to install the 'graphql-ws' package to use websockets when passing a 'subscriptionUrl'");console.error(`Error creating websocket client for ${e}`,t)}};e.createWebsocketsFetcherFromUrl=u;const c=e=>t=>(0,i.makeAsyncIterableIteratorFromSink)((n=>e.subscribe(t,Object.assign(Object.assign({},n),{error:e=>{e instanceof CloseEvent?n.error(new Error(`Socket closed with event ${e.code} ${e.reason||""}`.trim())):n.error(e)}}))));e.createWebsocketsFetcherFromClient=c;const d=e=>t=>{const n=e.request(t);return(0,i.makeAsyncIterableIteratorFromSink)((e=>n.subscribe(e).unsubscribe))};e.createLegacyWebsocketsFetcher=d;e.createMultipartFetcher=(e,t)=>function(n,o){return l(this,arguments,(function*(){var l,u;const c=yield a(t(e.url,{method:"POST",body:JSON.stringify(n),headers:Object.assign(Object.assign({"content-type":"application/json",accept:"application/json, multipart/mixed"},e.headers),null==o?void 0:o.headers)}).then((e=>(0,r.meros)(e,{multiple:!0}))));if(!(0,i.isAsyncIterable)(c))return yield a(yield yield a(c.json()));try{for(var d,f=s(c);!(d=yield a(f.next())).done;){const e=d.value;if(e.some((e=>!e.json))){const t=e.map((e=>`Headers::\n${e.headers}\n\nBody::\n${e.body}`));throw new Error(`Expected multipart chunks to be of json type. got:\n${t}`)}yield yield a(e.map((e=>e.body)))}}catch(e){l={error:e}}finally{try{d&&!d.done&&(u=f.return)&&(yield a(u.call(f)))}finally{if(l)throw l.error}}}))};e.getWsFetcher=(e,t)=>{if(e.wsClient)return c(e.wsClient);if(e.subscriptionUrl)return u(e.subscriptionUrl,Object.assign(Object.assign({},e.wsConnectionParams),null==t?void 0:t.headers));const n=e.legacyClient||e.legacyWsClient;return n?d(n):void 0}},void 0===(o=r.apply(t,i))||(e.exports=o)},3080:function(e,t){var n,r;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,void 0===(r="function"==typeof(n=function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0})})?n.apply(t,[t]):n)||(e.exports=r)},4574:function(e,t){var n,r;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,void 0===(r="function"==typeof(n=function(e){"use strict";function t(e){return JSON.stringify(e,null,2)}function n(e){return e instanceof Error?function(e){return Object.assign(Object.assign({},e),{message:e.message,stack:e.stack})}(e):e}Object.defineProperty(e,"__esModule",{value:!0}),e.formatError=function(e){return Array.isArray(e)?t({errors:e.map((e=>n(e)))}):t({errors:[n(e)]})},e.formatResult=function(e){return t(e)}})?n.apply(t,[t]):n)||(e.exports=r)},3738:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(3573)],void 0===(o="function"==typeof(r=function(e,t){"use strict";function n(e){if(!("getFields"in e))return[];const n=e.getFields();if(n.id)return["id"];if(n.edges)return["edges"];if(n.node)return["node"];const r=[];for(const e of Object.keys(n))(0,t.isLeafType)(n[e].type)&&r.push(e);return r}function r(e,n){const i=(0,t.getNamedType)(e);if(!e||(0,t.isLeafType)(e))return;const o=n(i);return Array.isArray(o)&&0!==o.length&&"getFields"in i?{kind:t.Kind.SELECTION_SET,selections:o.map((e=>{const o=i.getFields()[e],a=o?o.type:null;return{kind:t.Kind.FIELD,name:{kind:t.Kind.NAME,value:e},selectionSet:r(a,n)}}))}:void 0}function i(e,t){if(0===t.length)return e;let n="",r=0;for(const{index:i,string:o}of t)n+=e.slice(r,i)+o,r=i;return n+=e.slice(r),n}Object.defineProperty(e,"__esModule",{value:!0}),e.fillLeafs=function(e,o,a){const s=[];if(!e||!o)return{insertions:s,result:o};let l;try{l=(0,t.parse)(o)}catch(e){return{insertions:s,result:o}}const u=a||n,c=new t.TypeInfo(e);return(0,t.visit)(l,{leave(e){c.leave(e)},enter(e){if(c.enter(e),"Field"===e.kind&&!e.selectionSet){const n=r(function(e){if(e)return e}(c.getType()),u);if(n&&e.loc){const r=function(e,t){let n=t,r=t;for(;n;){const t=e.charCodeAt(n-1);if(10===t||13===t||8232===t||8233===t)break;n--,9!==t&&11!==t&&12!==t&&32!==t&&160!==t&&(r=n)}return e.slice(n,r)}(o,e.loc.start);s.push({index:e.loc.end,string:" "+(0,t.print)(n).replaceAll("\n","\n"+r)})}}}}),{insertions:s,result:i(o,s)}}})?r.apply(t,i):r)||(e.exports=o)},7293:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(3738),n(5682),n(1312)],void 0===(o="function"==typeof(r=function(e,t,n,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),Object.keys(t).forEach((function(n){"default"!==n&&"__esModule"!==n&&(n in e&&e[n]===t[n]||Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[n]}}))})),Object.keys(n).forEach((function(t){"default"!==t&&"__esModule"!==t&&(t in e&&e[t]===n[t]||Object.defineProperty(e,t,{enumerable:!0,get:function(){return n[t]}}))})),Object.keys(r).forEach((function(t){"default"!==t&&"__esModule"!==t&&(t in e&&e[t]===r[t]||Object.defineProperty(e,t,{enumerable:!0,get:function(){return r[t]}}))}))})?r.apply(t,i):r)||(e.exports=o)},5682:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(3573)],void 0===(o="function"==typeof(r=function(e,t){"use strict";function n(e,r,i){var o;const a=i?(0,t.getNamedType)(i).name:null,s=[],l=[];for(let u of r){if("FragmentSpread"===u.kind){const n=u.name.value;if(!u.directives||0===u.directives.length){if(l.includes(n))continue;l.push(n)}const r=e[u.name.value];if(r){const{typeCondition:e,directives:n,selectionSet:i}=r;u={kind:t.Kind.INLINE_FRAGMENT,typeCondition:e,directives:n,selectionSet:i}}}if(u.kind===t.Kind.INLINE_FRAGMENT&&(!u.directives||0===(null===(o=u.directives)||void 0===o?void 0:o.length))){const t=u.typeCondition?u.typeCondition.name.value:null;if(!t||t===a){s.push(...n(e,u.selectionSet.selections,i));continue}}s.push(u)}return s}Object.defineProperty(e,"__esModule",{value:!0}),e.mergeAst=function(e,r){const i=r?new t.TypeInfo(r):null,o=Object.create(null);for(const n of e.definitions)n.kind===t.Kind.FRAGMENT_DEFINITION&&(o[n.name.value]=n);const a={SelectionSet(e){const t=i?i.getParentType():null;let{selections:r}=e;return r=n(o,r,t),r=function(e,t){var n;const r=new Map,i=[];for(const o of e)if("Field"===o.kind){const e=t(o),a=r.get(e);if(null===(n=o.directives)||void 0===n?void 0:n.length){const e=Object.assign({},o);i.push(e)}else if((null==a?void 0:a.selectionSet)&&o.selectionSet)a.selectionSet.selections=[...a.selectionSet.selections,...o.selectionSet.selections];else if(!a){const t=Object.assign({},o);r.set(e,t),i.push(t)}}else i.push(o);return i}(r,(e=>e.alias?e.alias.value:e.name.value)),Object.assign(Object.assign({},e),{selections:r})},FragmentDefinition(){return null}};return(0,t.visit)(e,i?(0,t.visitWithTypeInfo)(i,a):a)}})?r.apply(t,i):r)||(e.exports=o)},1312:function(e,t){var n,r;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,void 0===(r="function"==typeof(n=function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.getSelectedOperationName=function(e,t,n){if(!n||n.length<1)return;const r=n.map((e=>{var t;return null===(t=e.name)||void 0===t?void 0:t.value}));if(t&&r.includes(t))return t;if(t&&e){const n=e.map((e=>{var t;return null===(t=e.name)||void 0===t?void 0:t.value})).indexOf(t);if(-1!==n&&n{for(const e in window.localStorage)0===e.indexOf(`${t}:`)&&window.localStorage.removeItem(e)}}}get(e){if(!this.storage)return null;const n=`${t}:${e}`,r=this.storage.getItem(n);return"null"===r||"undefined"===r?(this.storage.removeItem(n),null):r||null}set(e,n){let r=!1,i=null;if(this.storage){const o=`${t}:${e}`;if(n)try{this.storage.setItem(o,n)}catch(e){i=e instanceof Error?e:new Error(`${e}`),r=function(e,t){return t instanceof DOMException&&(22===t.code||1014===t.code||"QuotaExceededError"===t.name||"NS_ERROR_DOM_QUOTA_REACHED"===t.name)&&0!==e.length}(this.storage,e)}else this.storage.removeItem(o)}return{isQuotaError:r,error:i}}clear(){this.storage&&this.storage.clear()}};const t="graphiql"})?n.apply(t,[t]):n)||(e.exports=r)},1925:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(3573),n(6837)],void 0===(o="function"==typeof(r=function(e,t,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.HistoryStore=void 0;e.HistoryStore=class{constructor(e,t){this.storage=e,this.maxHistoryLength=t,this.updateHistory=(e,t,n,r)=>{if(this.shouldSaveQuery(e,t,n,this.history.fetchRecent())){this.history.push({query:e,variables:t,headers:n,operationName:r});const i=this.history.items,o=this.favorite.items;this.queries=i.concat(o)}},this.history=new n.QueryStore("queries",this.storage,this.maxHistoryLength),this.favorite=new n.QueryStore("favorites",this.storage,null),this.queries=[...this.history.fetchAll(),...this.favorite.fetchAll()]}shouldSaveQuery(e,n,r,i){if(!e)return!1;try{(0,t.parse)(e)}catch(e){return!1}if(e.length>1e5)return!1;if(!i)return!0;if(JSON.stringify(e)===JSON.stringify(i.query)){if(JSON.stringify(n)===JSON.stringify(i.variables)){if(JSON.stringify(r)===JSON.stringify(i.headers))return!1;if(r&&!i.headers)return!1}if(n&&!i.variables)return!1}return!0}toggleFavorite(e,t,n,r,i,o){const a={query:e,variables:t,headers:n,operationName:r,label:i};this.favorite.contains(a)?o&&(a.favorite=!1,this.favorite.delete(a)):(a.favorite=!0,this.favorite.push(a)),this.queries=[...this.history.items,...this.favorite.items]}editLabel(e,t,n,r,i,o){const a={query:e,variables:t,headers:n,operationName:r,label:i};o?this.favorite.edit(Object.assign(Object.assign({},a),{favorite:o})):this.history.edit(a),this.queries=[...this.history.items,...this.favorite.items]}}})?r.apply(t,i):r)||(e.exports=o)},920:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(1180),n(1925),n(6837)],void 0===(o="function"==typeof(r=function(e,t,n,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),Object.keys(t).forEach((function(n){"default"!==n&&"__esModule"!==n&&(n in e&&e[n]===t[n]||Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[n]}}))})),Object.keys(n).forEach((function(t){"default"!==t&&"__esModule"!==t&&(t in e&&e[t]===n[t]||Object.defineProperty(e,t,{enumerable:!0,get:function(){return n[t]}}))})),Object.keys(r).forEach((function(t){"default"!==t&&"__esModule"!==t&&(t in e&&e[t]===r[t]||Object.defineProperty(e,t,{enumerable:!0,get:function(){return r[t]}}))}))})?r.apply(t,i):r)||(e.exports=o)},6837:function(e,t){var n,r;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,n=function(e){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.QueryStore=void 0;e.QueryStore=class{constructor(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.key=e,this.storage=t,this.maxSize=n,this.items=this.fetchAll()}get length(){return this.items.length}contains(e){return this.items.some((t=>t.query===e.query&&t.variables===e.variables&&t.headers===e.headers&&t.operationName===e.operationName))}edit(e){const t=this.items.findIndex((t=>t.query===e.query&&t.variables===e.variables&&t.headers===e.headers&&t.operationName===e.operationName));-1!==t&&(this.items.splice(t,1,e),this.save())}delete(e){const t=this.items.findIndex((t=>t.query===e.query&&t.variables===e.variables&&t.headers===e.headers&&t.operationName===e.operationName));-1!==t&&(this.items.splice(t,1),this.save())}fetchRecent(){return this.items.at(-1)}fetchAll(){const e=this.storage.get(this.key);return e?JSON.parse(e)[this.key]:[]}push(e){const t=[...this.items,e];this.maxSize&&t.length>this.maxSize&&t.shift();for(let e=0;e<5;e++){const e=this.storage.set(this.key,JSON.stringify({[this.key]:t}));if(null==e?void 0:e.error){if(!e.isQuotaError||!this.maxSize)return;t.shift()}else this.items=t}}save(){this.storage.set(this.key,JSON.stringify({[this.key]:this.items}))}}},void 0===(r=n.apply(t,[t]))||(e.exports=r)},6676:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(7139),n(7644),n(3573),n(8506),n(5251),n(2162),n(5605),n(6785)],void 0===(o="function"==typeof(r=function(e,t,n,r,i,o,a,s,l){"use strict";function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(u=function(e){return e?n:t})(e)}function c(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=u(t);if(n&&n.has(e))return n.get(e);var r={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var a=i?Object.getOwnPropertyDescriptor(e,o):null;a&&(a.get||a.set)?Object.defineProperty(r,o,a):r[o]=e[o]}return r.default=e,n&&n.set(e,r),r}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0,t=c(t),r=c(r),i.GraphiQL.createFetcher=n.createGraphiQLFetcher,i.GraphiQL.GraphQL=r,i.GraphiQL.React=t;var d=i.GraphiQL;e.default=d})?r.apply(t,i):r)||(e.exports=o)},8506:function(e,t,n){var r,i,o;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self&&self,i=[t,n(9196),n(7139)],r=function(e,t,n){"use strict";function r(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(r=function(e){return e?n:t})(e)}function i(){return i=Object.assign?Object.assign.bind():function(e){for(var t=1;t{"first"===e&&(null==p||p.setVisiblePlugin(null))},sizeThresholdSecond:200,storageKey:"docExplorerFlex"}),T=(0,n.useDragResize)({direction:"horizontal",storageKey:"editorFlex"}),w=(0,n.useDragResize)({defaultSizeRelation:3,direction:"vertical",initiallyHidden:(()=>{if("variables"!==e.defaultEditorToolsVisibility&&"headers"!==e.defaultEditorToolsVisibility)return"boolean"==typeof e.defaultEditorToolsVisibility?e.defaultEditorToolsVisibility?void 0:"second":l.initialVariables||l.initialHeaders?void 0:"second"})(),sizeThresholdSecond:60,storageKey:"secondaryEditorFlex"}),[C,S]=(0,t.useState)((()=>"variables"===e.defaultEditorToolsVisibility||"headers"===e.defaultEditorToolsVisibility?e.defaultEditorToolsVisibility:!l.initialVariables&&l.initialHeaders&&s?"headers":"variables")),[x,k]=(0,t.useState)(null),[N,_]=(0,t.useState)(null),O=t.default.Children.toArray(e.children),I=O.find((e=>c(e,o.Logo)))||t.default.createElement(o.Logo,null),D=O.find((e=>c(e,o.Toolbar)))||t.default.createElement(t.default.Fragment,null,t.default.createElement(n.ToolbarButton,{onClick:()=>g(),label:"Prettify query (Shift-Ctrl-P)"},t.default.createElement(n.PrettifyIcon,{className:"graphiql-toolbar-icon","aria-hidden":"true"})),t.default.createElement(n.ToolbarButton,{onClick:()=>m(),label:"Merge fragments into query (Shift-Ctrl-M)"},t.default.createElement(n.MergeIcon,{className:"graphiql-toolbar-icon","aria-hidden":"true"})),t.default.createElement(n.ToolbarButton,{onClick:()=>h(),label:"Copy query (Shift-Ctrl-C)"},t.default.createElement(n.CopyIcon,{className:"graphiql-toolbar-icon","aria-hidden":"true"})),(null===(a=e.toolbar)||void 0===a?void 0:a.additionalContent)||null),L=O.find((e=>c(e,o.Footer))),A=()=>{"first"===E.hiddenElement&&E.setHiddenElement(null)},M=0===window.navigator.platform.toLowerCase().indexOf("mac")?t.default.createElement("code",{className:"graphiql-key"},"Cmd"):t.default.createElement("code",{className:"graphiql-key"},"Ctrl");return t.default.createElement("div",{"data-testid":"graphiql-container",className:"graphiql-container"},t.default.createElement("div",{className:"graphiql-sidebar"},t.default.createElement("div",{className:"graphiql-sidebar-section"},null==p?void 0:p.plugins.map((e=>{const r=e===p.visiblePlugin,i=`${r?"Hide":"Show"} ${e.title}`,o=e.icon;return t.default.createElement(n.Tooltip,{key:e.title,label:i},t.default.createElement(n.UnStyledButton,{type:"button",className:r?"active":"",onClick:()=>{r?(p.setVisiblePlugin(null),E.setHiddenElement("first")):(p.setVisiblePlugin(e),E.setHiddenElement(null))},"aria-label":i},t.default.createElement(o,{"aria-hidden":"true"})))}))),t.default.createElement("div",{className:"graphiql-sidebar-section"},t.default.createElement(n.Tooltip,{label:"Re-fetch GraphQL schema"},t.default.createElement(n.UnStyledButton,{type:"button",disabled:d.isFetching,onClick:()=>d.introspect(),"aria-label":"Re-fetch GraphQL schema"},t.default.createElement(n.ReloadIcon,{className:d.isFetching?"graphiql-spin":"","aria-hidden":"true"}))),t.default.createElement(n.Tooltip,{label:"Open short keys dialog"},t.default.createElement(n.UnStyledButton,{type:"button",onClick:()=>k("short-keys"),"aria-label":"Open short keys dialog"},t.default.createElement(n.KeyboardShortcutIcon,{"aria-hidden":"true"}))),t.default.createElement(n.Tooltip,{label:"Open settings dialog"},t.default.createElement(n.UnStyledButton,{type:"button",onClick:()=>k("settings"),"aria-label":"Open settings dialog"},t.default.createElement(n.SettingsIcon,{"aria-hidden":"true"}))))),t.default.createElement("div",{className:"graphiql-main"},t.default.createElement("div",{ref:E.firstRef,style:{minWidth:"200px"}},t.default.createElement("div",{className:"graphiql-plugin"},b?t.default.createElement(b,null):null)),t.default.createElement("div",{ref:E.dragBarRef},null!=p&&p.visiblePlugin?t.default.createElement("div",{className:"graphiql-horizontal-drag-bar"}):null),t.default.createElement("div",{ref:E.secondRef,style:{minWidth:0}},t.default.createElement("div",{className:"graphiql-sessions"},t.default.createElement("div",{className:"graphiql-session-header"},t.default.createElement(n.Tabs,{"aria-label":"Select active operation"},l.tabs.length>1?t.default.createElement(t.default.Fragment,null,l.tabs.map(((e,r)=>t.default.createElement(n.Tab,{key:e.id,isActive:r===l.activeTabIndex},t.default.createElement(n.Tab.Button,{"aria-controls":"graphiql-session",id:`graphiql-session-tab-${r}`,onClick:()=>{u.stop(),l.changeTab(r)}},e.title),t.default.createElement(n.Tab.Close,{onClick:()=>{l.activeTabIndex===r&&u.stop(),l.closeTab(r)}})))),t.default.createElement("div",null,t.default.createElement(n.Tooltip,{label:"Add tab"},t.default.createElement(n.UnStyledButton,{type:"button",className:"graphiql-tab-add",onClick:()=>l.addTab(),"aria-label":"Add tab"},t.default.createElement(n.PlusIcon,{"aria-hidden":"true"}))))):null),t.default.createElement("div",{className:"graphiql-session-header-right"},1===l.tabs.length?t.default.createElement("div",{className:"graphiql-add-tab-wrapper"},t.default.createElement(n.Tooltip,{label:"Add tab"},t.default.createElement(n.UnStyledButton,{type:"button",className:"graphiql-tab-add",onClick:()=>l.addTab(),"aria-label":"Add tab"},t.default.createElement(n.PlusIcon,{"aria-hidden":"true"})))):null,I)),t.default.createElement("div",{role:"tabpanel",id:"graphiql-session",className:"graphiql-session","aria-labelledby":`graphiql-session-tab-${l.activeTabIndex}`},t.default.createElement("div",{ref:T.firstRef},t.default.createElement("div",{className:"graphiql-editors"+(1===l.tabs.length?" full-height":"")},t.default.createElement("div",{ref:w.firstRef},t.default.createElement("section",{className:"graphiql-query-editor","aria-label":"Query Editor"},t.default.createElement("div",{className:"graphiql-query-editor-wrapper"},t.default.createElement(n.QueryEditor,{editorTheme:e.editorTheme,keyMap:e.keyMap,onClickReference:A,onCopyQuery:e.onCopyQuery,onEdit:e.onEditQuery,readOnly:e.readOnly})),t.default.createElement("div",{className:"graphiql-toolbar",role:"toolbar","aria-label":"Editor Commands"},t.default.createElement(n.ExecuteButton,null),D))),t.default.createElement("div",{ref:w.dragBarRef},t.default.createElement("div",{className:"graphiql-editor-tools"},t.default.createElement("div",{className:"graphiql-editor-tools-tabs"},t.default.createElement(n.UnStyledButton,{type:"button",className:"variables"===C&&"second"!==w.hiddenElement?"active":"",onClick:()=>{"second"===w.hiddenElement&&w.setHiddenElement(null),S("variables")}},"Variables"),s?t.default.createElement(n.UnStyledButton,{type:"button",className:"headers"===C&&"second"!==w.hiddenElement?"active":"",onClick:()=>{"second"===w.hiddenElement&&w.setHiddenElement(null),S("headers")}},"Headers"):null),t.default.createElement(n.Tooltip,{label:"second"===w.hiddenElement?"Show editor tools":"Hide editor tools"},t.default.createElement(n.UnStyledButton,{type:"button",onClick:()=>{w.setHiddenElement("second"===w.hiddenElement?null:"second")},"aria-label":"second"===w.hiddenElement?"Show editor tools":"Hide editor tools"},"second"===w.hiddenElement?t.default.createElement(n.ChevronUpIcon,{className:"graphiql-chevron-icon","aria-hidden":"true"}):t.default.createElement(n.ChevronDownIcon,{className:"graphiql-chevron-icon","aria-hidden":"true"}))))),t.default.createElement("div",{ref:w.secondRef},t.default.createElement("section",{className:"graphiql-editor-tool","aria-label":"variables"===C?"Variables":"Headers"},t.default.createElement(n.VariableEditor,{editorTheme:e.editorTheme,isHidden:"variables"!==C,keyMap:e.keyMap,onEdit:e.onEditVariables,onClickReference:A,readOnly:e.readOnly}),s&&t.default.createElement(n.HeaderEditor,{editorTheme:e.editorTheme,isHidden:"headers"!==C,keyMap:e.keyMap,onEdit:e.onEditHeaders,readOnly:e.readOnly}))))),t.default.createElement("div",{ref:T.dragBarRef},t.default.createElement("div",{className:"graphiql-horizontal-drag-bar"})),t.default.createElement("div",{ref:T.secondRef},t.default.createElement("div",{className:"graphiql-response"},u.isFetching?t.default.createElement(n.Spinner,null):null,t.default.createElement(n.ResponseEditor,{editorTheme:e.editorTheme,responseTooltip:e.responseTooltip,keyMap:e.keyMap}),L)))))),t.default.createElement(n.Dialog,{isOpen:"short-keys"===x,onDismiss:()=>k(null)},t.default.createElement("div",{className:"graphiql-dialog-header"},t.default.createElement("div",{className:"graphiql-dialog-title"},"Short Keys"),t.default.createElement(n.Dialog.Close,{onClick:()=>k(null)})),t.default.createElement("div",{className:"graphiql-dialog-section"},t.default.createElement("div",null,t.default.createElement("table",{className:"graphiql-table"},t.default.createElement("thead",null,t.default.createElement("tr",null,t.default.createElement("th",null,"Short key"),t.default.createElement("th",null,"Function"))),t.default.createElement("tbody",null,t.default.createElement("tr",null,t.default.createElement("td",null,M," + ",t.default.createElement("code",{className:"graphiql-key"},"F")),t.default.createElement("td",null,"Search in editor")),t.default.createElement("tr",null,t.default.createElement("td",null,M," + ",t.default.createElement("code",{className:"graphiql-key"},"K")),t.default.createElement("td",null,"Search in documentation")),t.default.createElement("tr",null,t.default.createElement("td",null,M," + ",t.default.createElement("code",{className:"graphiql-key"},"Enter")),t.default.createElement("td",null,"Execute query")),t.default.createElement("tr",null,t.default.createElement("td",null,t.default.createElement("code",{className:"graphiql-key"},"Ctrl")," + ",t.default.createElement("code",{className:"graphiql-key"},"Shift")," + ",t.default.createElement("code",{className:"graphiql-key"},"P")),t.default.createElement("td",null,"Prettify editors")),t.default.createElement("tr",null,t.default.createElement("td",null,t.default.createElement("code",{className:"graphiql-key"},"Ctrl")," + ",t.default.createElement("code",{className:"graphiql-key"},"Shift")," + ",t.default.createElement("code",{className:"graphiql-key"},"M")),t.default.createElement("td",null,"Merge fragments definitions into operation definition")),t.default.createElement("tr",null,t.default.createElement("td",null,t.default.createElement("code",{className:"graphiql-key"},"Ctrl")," + ",t.default.createElement("code",{className:"graphiql-key"},"Shift")," + ",t.default.createElement("code",{className:"graphiql-key"},"C")),t.default.createElement("td",null,"Copy query")),t.default.createElement("tr",null,t.default.createElement("td",null,t.default.createElement("code",{className:"graphiql-key"},"Ctrl")," + ",t.default.createElement("code",{className:"graphiql-key"},"Shift")," + ",t.default.createElement("code",{className:"graphiql-key"},"R")),t.default.createElement("td",null,"Re-fetch schema using introspection")))),t.default.createElement("p",null,"The editors use"," ",t.default.createElement("a",{href:"https://codemirror.net/5/doc/manual.html#keymaps",target:"_blank",rel:"noopener noreferrer"},"CodeMirror Key Maps")," ","that add more short keys. This instance of Graph",t.default.createElement("em",null,"i"),"QL uses"," ",t.default.createElement("code",null,e.keyMap||"sublime"),".")))),t.default.createElement(n.Dialog,{isOpen:"settings"===x,onDismiss:()=>{k(null),_(null)}},t.default.createElement("div",{className:"graphiql-dialog-header"},t.default.createElement("div",{className:"graphiql-dialog-title"},"Settings"),t.default.createElement(n.Dialog.Close,{onClick:()=>{k(null),_(null)}})),e.showPersistHeadersSettings?t.default.createElement("div",{className:"graphiql-dialog-section"},t.default.createElement("div",null,t.default.createElement("div",{className:"graphiql-dialog-section-title"},"Persist headers"),t.default.createElement("div",{className:"graphiql-dialog-section-caption"},"Save headers upon reloading."," ",t.default.createElement("span",{className:"graphiql-warning-text"},"Only enable if you trust this device."))),t.default.createElement(n.ButtonGroup,null,t.default.createElement(n.Button,{type:"button",id:"enable-persist-headers",className:l.shouldPersistHeaders?"active":void 0,onClick:()=>{l.setShouldPersistHeaders(!0)}},"On"),t.default.createElement(n.Button,{type:"button",id:"disable-persist-headers",className:l.shouldPersistHeaders?void 0:"active",onClick:()=>{l.setShouldPersistHeaders(!1)}},"Off"))):null,t.default.createElement("div",{className:"graphiql-dialog-section"},t.default.createElement("div",null,t.default.createElement("div",{className:"graphiql-dialog-section-title"},"Theme"),t.default.createElement("div",{className:"graphiql-dialog-section-caption"},"Adjust how the interface looks like.")),t.default.createElement("div",null,t.default.createElement(n.ButtonGroup,null,t.default.createElement(n.Button,{type:"button",className:null===v?"active":"",onClick:()=>y(null)},"System"),t.default.createElement(n.Button,{type:"button",className:"light"===v?"active":"",onClick:()=>y("light")},"Light"),t.default.createElement(n.Button,{type:"button",className:"dark"===v?"active":"",onClick:()=>y("dark")},"Dark")))),f?t.default.createElement("div",{className:"graphiql-dialog-section"},t.default.createElement("div",null,t.default.createElement("div",{className:"graphiql-dialog-section-title"},"Clear storage"),t.default.createElement("div",{className:"graphiql-dialog-section-caption"},"Remove all locally stored data and start fresh.")),t.default.createElement("div",null,t.default.createElement(n.Button,{type:"button",state:N||void 0,disabled:"success"===N,onClick:()=>{try{null==f||f.clear(),_("success")}catch{_("error")}}},"success"===N?"Cleared data":"error"===N?"Failed":"Clear data"))):null))}function s(e){return t.default.createElement("div",{className:"graphiql-logo"},e.children||t.default.createElement("a",{className:"graphiql-logo-link",href:"https://github.com/graphql/graphiql",target:"_blank",rel:"noreferrer"},"Graph",t.default.createElement("em",null,"i"),"QL"))}function l(e){return t.default.createElement(t.default.Fragment,null,e.children)}function u(e){return t.default.createElement("div",{className:"graphiql-footer"},e.children)}function c(e,t){var n;return!(null==e||null===(n=e.type)||void 0===n||!n.displayName||e.type.displayName!==t.displayName)||e.type===t}o.Logo=s,o.Toolbar=l,o.Footer=u,s.displayName="GraphiQLLogo",l.displayName="GraphiQLToolbar",u.displayName="GraphiQLFooter"},void 0===(o=r.apply(t,i))||(e.exports=o)},5313:function(e,t,n){"use strict";n.r(t),n.d(t,{CloseCode:function(){return r.CloseCode},GRAPHQL_TRANSPORT_WS_PROTOCOL:function(){return r.GRAPHQL_TRANSPORT_WS_PROTOCOL},MessageType:function(){return r.MessageType},createClient:function(){return o},isMessage:function(){return r.isMessage},parseMessage:function(){return r.parseMessage},stringifyMessage:function(){return r.stringifyMessage}});var r=n(863),i=n(6764);function o(e){const{url:t,connectionParams:o,lazy:s=!0,onNonLazyError:l=console.error,lazyCloseTimeout:u=0,keepAlive:c=0,disablePong:d,connectionAckWaitTimeout:f=0,retryAttempts:p=5,retryWait:h=async function(e){let t=1e3;for(let n=0;nsetTimeout(e,t+Math.floor(2700*Math.random()+300))))},isFatalConnectionProblem:m=(e=>!a(e)),on:g,webSocketImpl:v,generateID:y=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(e=>{const t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)}))},jsonMessageReplacer:b,jsonMessageReviver:E}=e;let T;if(v){if(!("function"==typeof(w=v)&&"constructor"in w&&"CLOSED"in w&&"CLOSING"in w&&"CONNECTING"in w&&"OPEN"in w))throw new Error("Invalid WebSocket implementation provided");T=v}else"undefined"!=typeof WebSocket?T=WebSocket:void 0!==n.g?T=n.g.WebSocket||n.g.MozWebSocket:"undefined"!=typeof window&&(T=window.WebSocket||window.MozWebSocket);var w;if(!T)throw new Error("WebSocket implementation missing");const C=T,S=(()=>{const e=(()=>{const e={};return{on(t,n){return e[t]=n,()=>{delete e[t]}},emit(t){var n;"id"in t&&(null===(n=e[t.id])||void 0===n||n.call(e,t))}}})(),t={connecting:(null==g?void 0:g.connecting)?[g.connecting]:[],opened:(null==g?void 0:g.opened)?[g.opened]:[],connected:(null==g?void 0:g.connected)?[g.connected]:[],ping:(null==g?void 0:g.ping)?[g.ping]:[],pong:(null==g?void 0:g.pong)?[g.pong]:[],message:(null==g?void 0:g.message)?[e.emit,g.message]:[e.emit],closed:(null==g?void 0:g.closed)?[g.closed]:[],error:(null==g?void 0:g.error)?[g.error]:[]};return{onMessage:e.on,on(e,n){const r=t[e];return r.push(n),()=>{r.splice(r.indexOf(n),1)}},emit(e,...n){for(const r of[...t[e]])r(...n)}}})();function x(e){const t=[S.on("error",(n=>{t.forEach((e=>e())),e(n)})),S.on("closed",(n=>{t.forEach((e=>e())),e(n)}))]}let k,N=0,_=!1,O=0,I=!1;async function D(){const[e,n]=await(null!=k?k:k=new Promise(((e,n)=>(async()=>{if(_){if(await h(O),!N)return k=void 0,n({code:1e3,reason:"All Subscriptions Gone"});O++}S.emit("connecting");const a=new C("function"==typeof t?await t():t,r.GRAPHQL_TRANSPORT_WS_PROTOCOL);let s,l;function u(){isFinite(c)&&c>0&&(clearTimeout(l),l=setTimeout((()=>{a.readyState===C.OPEN&&(a.send((0,r.stringifyMessage)({type:r.MessageType.Ping})),S.emit("ping",!1,void 0))}),c))}x((e=>{k=void 0,clearTimeout(s),clearTimeout(l),n(e)})),a.onerror=e=>S.emit("error",e),a.onclose=e=>S.emit("closed",e),a.onopen=async()=>{try{S.emit("opened",a);const e="function"==typeof o?await o():o;a.send((0,r.stringifyMessage)(e?{type:r.MessageType.ConnectionInit,payload:e}:{type:r.MessageType.ConnectionInit},b)),isFinite(f)&&f>0&&(s=setTimeout((()=>{a.close(r.CloseCode.ConnectionAcknowledgementTimeout,"Connection acknowledgement timeout")}),f)),u()}catch(e){S.emit("error",e),a.close(r.CloseCode.InternalClientError,(0,i.qj)(e instanceof Error?e.message:new Error(e).message,"Internal client error"))}};let p=!1;a.onmessage=({data:t})=>{try{const n=(0,r.parseMessage)(t,E);if(S.emit("message",n),"ping"===n.type||"pong"===n.type)return S.emit(n.type,!0,n.payload),void("pong"===n.type?u():d||(a.send((0,r.stringifyMessage)(n.payload?{type:r.MessageType.Pong,payload:n.payload}:{type:r.MessageType.Pong})),S.emit("pong",!1,n.payload)));if(p)return;if(n.type!==r.MessageType.ConnectionAck)throw new Error(`First message cannot be of type ${n.type}`);clearTimeout(s),p=!0,S.emit("connected",a,n.payload),_=!1,O=0,e([a,new Promise(((e,t)=>x(t)))])}catch(e){a.onmessage=null,S.emit("error",e),a.close(r.CloseCode.BadResponse,(0,i.qj)(e instanceof Error?e.message:new Error(e).message,"Bad response"))}}})())));e.readyState===C.CLOSING&&await n;let a=()=>{};const s=new Promise((e=>a=e));return[e,a,Promise.race([s.then((()=>{if(!N){const t=()=>e.close(1e3,"Normal Closure");isFinite(u)&&u>0?setTimeout((()=>{N||e.readyState!==C.OPEN||t()}),u):t()}})),n])]}function L(e){if(a(e)&&(t=e.code,![1e3,1001,1006,1005,1012,1013,1013].includes(t)&&t>=1e3&&t<=1999||[r.CloseCode.InternalServerError,r.CloseCode.InternalClientError,r.CloseCode.BadRequest,r.CloseCode.BadResponse,r.CloseCode.Unauthorized,r.CloseCode.SubprotocolNotAcceptable,r.CloseCode.SubscriberAlreadyExists,r.CloseCode.TooManyInitialisationRequests].includes(e.code)))throw e;var t;if(I)return!1;if(a(e)&&1e3===e.code)return N>0;if(!p||O>=p)throw e;if(m(e))throw e;return _=!0}return s||(async()=>{for(N++;;)try{const[,,e]=await D();await e}catch(e){try{if(!L(e))return}catch(e){return null==l?void 0:l(e)}}})(),{on:S.on,subscribe(e,t){const n=y();let i=!1,o=!1,a=()=>{N--,i=!0};return(async()=>{for(N++;;)try{const[s,l,u]=await D();if(i)return l();const c=S.onMessage(n,(e=>{switch(e.type){case r.MessageType.Next:return void t.next(e.payload);case r.MessageType.Error:return o=!0,i=!0,t.error(e.payload),void a();case r.MessageType.Complete:return i=!0,void a()}}));return s.send((0,r.stringifyMessage)({id:n,type:r.MessageType.Subscribe,payload:e},b)),a=()=>{i||s.readyState!==C.OPEN||s.send((0,r.stringifyMessage)({id:n,type:r.MessageType.Complete},b)),N--,i=!0,l()},void await u.finally(c)}catch(e){if(!L(e))return}})().then((()=>{o||t.complete()})).catch((e=>{t.error(e)})),()=>{i||a()}},async dispose(){if(I=!0,k){const[e]=await k;e.close(1e3,"Normal Closure")}}}}function a(e){return(0,i.Kn)(e)&&"code"in e&&"reason"in e}},863:function(e,t,n){"use strict";n.r(t),n.d(t,{CloseCode:function(){return o},GRAPHQL_TRANSPORT_WS_PROTOCOL:function(){return i},MessageType:function(){return a},isMessage:function(){return s},parseMessage:function(){return l},stringifyMessage:function(){return u}});var r=n(6764);const i="graphql-transport-ws";var o,a;function s(e){if((0,r.Kn)(e)){if(!(0,r.ND)(e,"type"))return!1;switch(e.type){case a.ConnectionInit:case a.ConnectionAck:case a.Ping:case a.Pong:return!(0,r.nr)(e,"payload")||void 0===e.payload||(0,r.Kn)(e.payload);case a.Subscribe:return(0,r.ND)(e,"id")&&(0,r.O2)(e,"payload")&&(!(0,r.nr)(e.payload,"operationName")||void 0===e.payload.operationName||null===e.payload.operationName||"string"==typeof e.payload.operationName)&&(0,r.ND)(e.payload,"query")&&(!(0,r.nr)(e.payload,"variables")||void 0===e.payload.variables||null===e.payload.variables||(0,r.O2)(e.payload,"variables"))&&(!(0,r.nr)(e.payload,"extensions")||void 0===e.payload.extensions||null===e.payload.extensions||(0,r.O2)(e.payload,"extensions"));case a.Next:return(0,r.ND)(e,"id")&&(0,r.O2)(e,"payload");case a.Error:return(0,r.ND)(e,"id")&&(0,r.Ox)(e.payload);case a.Complete:return(0,r.ND)(e,"id");default:return!1}}return!1}function l(e,t){if(s(e))return e;if("string"!=typeof e)throw new Error("Message not parsable");const n=JSON.parse(e,t);if(!s(n))throw new Error("Invalid message");return n}function u(e,t){if(!s(e))throw new Error("Cannot stringify invalid message");return JSON.stringify(e,t)}!function(e){e[e.InternalServerError=4500]="InternalServerError",e[e.InternalClientError=4005]="InternalClientError",e[e.BadRequest=4400]="BadRequest",e[e.BadResponse=4004]="BadResponse",e[e.Unauthorized=4401]="Unauthorized",e[e.Forbidden=4403]="Forbidden",e[e.SubprotocolNotAcceptable=4406]="SubprotocolNotAcceptable",e[e.ConnectionInitialisationTimeout=4408]="ConnectionInitialisationTimeout",e[e.ConnectionAcknowledgementTimeout=4504]="ConnectionAcknowledgementTimeout",e[e.SubscriberAlreadyExists=4409]="SubscriberAlreadyExists",e[e.TooManyInitialisationRequests=4429]="TooManyInitialisationRequests"}(o||(o={})),function(e){e.ConnectionInit="connection_init",e.ConnectionAck="connection_ack",e.Ping="ping",e.Pong="pong",e.Subscribe="subscribe",e.Next="next",e.Error="error",e.Complete="complete"}(a||(a={}))},6718:function(e,t,n){"use strict";n.r(t),n.d(t,{makeServer:function(){return f}});var r=n(953),i=n(2780),o=n(9458),a=n(4117),s=n(2941),l=n(7180),u=n(863),c=n(6764),d=function(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e="function"==typeof __values?__values(e):e[Symbol.iterator](),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise((function(r,i){!function(e,t,n,r){Promise.resolve(r).then((function(t){e({value:t,done:n})}),t)}(r,i,(t=e[n](t)).done,t.value)}))}}};function f(e){const{schema:t,context:n,roots:f,validate:p,execute:h,subscribe:m,connectionInitWaitTimeout:g=3e3,onConnect:v,onDisconnect:y,onClose:b,onSubscribe:E,onOperation:T,onNext:w,onError:C,onComplete:S,jsonMessageReviver:x,jsonMessageReplacer:k}=e;return{opened(e,N){const _={connectionInitReceived:!1,acknowledged:!1,subscriptions:{},extra:N};if(e.protocol!==u.GRAPHQL_TRANSPORT_WS_PROTOCOL)return e.close(u.CloseCode.SubprotocolNotAcceptable,"Subprotocol not acceptable"),async(e,t)=>{await(null==b?void 0:b(_,e,t))};const O=g>0&&isFinite(g)?setTimeout((()=>{_.connectionInitReceived||e.close(u.CloseCode.ConnectionInitialisationTimeout,"Connection initialisation timeout")}),g):null;return e.onMessage((async function(g){var y,b,N;let O;try{O=(0,u.parseMessage)(g,x)}catch(t){return e.close(u.CloseCode.BadRequest,"Invalid message received")}switch(O.type){case u.MessageType.ConnectionInit:{if(_.connectionInitReceived)return e.close(u.CloseCode.TooManyInitialisationRequests,"Too many initialisation requests");_.connectionInitReceived=!0,(0,c.Kn)(O.payload)&&(_.connectionParams=O.payload);const t=await(null==v?void 0:v(_));return!1===t?e.close(u.CloseCode.Forbidden,"Forbidden"):(await e.send((0,u.stringifyMessage)((0,c.Kn)(t)?{type:u.MessageType.ConnectionAck,payload:t}:{type:u.MessageType.ConnectionAck},k)),void(_.acknowledged=!0))}case u.MessageType.Ping:return e.onPing?await e.onPing(O.payload):void await e.send((0,u.stringifyMessage)(O.payload?{type:u.MessageType.Pong,payload:O.payload}:{type:u.MessageType.Pong}));case u.MessageType.Pong:return await(null===(N=e.onPong)||void 0===N?void 0:N.call(e,O.payload));case u.MessageType.Subscribe:{if(!_.acknowledged)return e.close(u.CloseCode.Unauthorized,"Unauthorized");const{id:g,payload:v}=O;if(g in _.subscriptions)return e.close(u.CloseCode.SubscriberAlreadyExists,`Subscriber for ${g} already exists`);_.subscriptions[g]=null;const x={next:async(t,n)=>{let r={id:g,type:u.MessageType.Next,payload:t};const i=await(null==w?void 0:w(_,r,n,t));i&&(r=Object.assign(Object.assign({},r),{payload:i})),await e.send((0,u.stringifyMessage)(r,k))},error:async t=>{let n={id:g,type:u.MessageType.Error,payload:t};const r=await(null==C?void 0:C(_,n,t));r&&(n=Object.assign(Object.assign({},n),{payload:r})),await e.send((0,u.stringifyMessage)(n,k))},complete:async t=>{const n={id:g,type:u.MessageType.Complete};await(null==S?void 0:S(_,n)),t&&await e.send((0,u.stringifyMessage)(n,k))}};let N;const L=await(null==E?void 0:E(_,O));if(L){if((0,c.Ox)(L))return await x.error(L);if(Array.isArray(L))throw new Error("Invalid return value from onSubscribe hook, expected an array of GraphQLError objects");N=L}else{if(!t)throw new Error("The GraphQL schema is not provided");const e={operationName:v.operationName,document:(0,r.Qc)(v.query),variableValues:v.variables};N=Object.assign(Object.assign({},e),{schema:"function"==typeof t?await t(_,O,e):t});const n=(null!=p?p:i.Gu)(N.schema,N.document);if(n.length>0)return await x.error(n)}const A=(0,o.S)(N.document,N.operationName);if(!A)return await x.error([new a.__("Unable to identify operation")]);let M;"rootValue"in N||(N.rootValue=null==f?void 0:f[A.operation]),"contextValue"in N||(N.contextValue="function"==typeof n?await n(_,O,N):n),M="subscription"===A.operation?await(null!=m?m:s.L)(N):await(null!=h?h:l.ht)(N);const R=await(null==T?void 0:T(_,O,N,M));if(R&&(M=R),(0,c.D0)(M))if(g in _.subscriptions){_.subscriptions[g]=M;try{for(var I,D=d(M);!(I=await D.next()).done;){const e=I.value;await x.next(e,N)}}catch(e){y={error:e}}finally{try{I&&!I.done&&(b=D.return)&&await b.call(D)}finally{if(y)throw y.error}}}else(0,c.Jy)(M)&&M.return(void 0);else g in _.subscriptions&&await x.next(M,N);return await x.complete(g in _.subscriptions),void delete _.subscriptions[g]}case u.MessageType.Complete:{const e=_.subscriptions[O.id];return(0,c.Jy)(e)&&await e.return(void 0),void delete _.subscriptions[O.id]}default:throw new Error(`Unexpected message of type ${O.type} received`)}})),async(e,t)=>{O&&clearTimeout(O);for(const e of Object.values(_.subscriptions))(0,c.Jy)(e)&&await e.return(void 0);_.acknowledged&&await(null==y?void 0:y(_,e,t)),await(null==b?void 0:b(_,e,t))}}}}},6764:function(e,t,n){"use strict";n.d(t,{D0:function(){return o},Jy:function(){return a},Kn:function(){return i},ND:function(){return c},O2:function(){return u},Ox:function(){return s},nr:function(){return l},qj:function(){return d}});const r=Object.prototype.hasOwnProperty;function i(e){return"object"==typeof e&&null!==e}function o(e){return"function"==typeof Object(e)[Symbol.asyncIterator]}function a(e){return i(e)&&"function"==typeof Object(e)[Symbol.asyncIterator]&&"function"==typeof e.return}function s(e){return Array.isArray(e)&&e.length>0&&e.every((e=>"message"in e))}function l(e,t){return r.call(e,t)}function u(e,t){return r.call(e,t)&&i(e[t])}function c(e,t){return r.call(e,t)&&"string"==typeof e[t]}function d(e,t){return e.length<124?e:t}},4117:function(e,t,n){"use strict";n.d(t,{OS:function(){return l},Z:function(){return u},__:function(){return a}});var r=n(1315),i=n(3302),o=n(7026);class a extends Error{constructor(e,...t){var n,o,l;const{nodes:u,source:c,positions:d,path:f,originalError:p,extensions:h}=function(e){const t=e[0];return null==t||"kind"in t||"length"in t?{nodes:t,source:e[1],positions:e[2],path:e[3],originalError:e[4],extensions:e[5]}:t}(t);super(e),this.name="GraphQLError",this.path=null!=f?f:void 0,this.originalError=null!=p?p:void 0,this.nodes=s(Array.isArray(u)?u:u?[u]:void 0);const m=s(null===(n=this.nodes)||void 0===n?void 0:n.map((e=>e.loc)).filter((e=>null!=e)));this.source=null!=c?c:null==m||null===(o=m[0])||void 0===o?void 0:o.source,this.positions=null!=d?d:null==m?void 0:m.map((e=>e.start)),this.locations=d&&c?d.map((e=>(0,i.k)(c,e))):null==m?void 0:m.map((e=>(0,i.k)(e.source,e.start)));const g=(0,r.y)(null==p?void 0:p.extensions)?null==p?void 0:p.extensions:void 0;this.extensions=null!==(l=null!=h?h:g)&&void 0!==l?l:Object.create(null),Object.defineProperties(this,{message:{writable:!0,enumerable:!0},name:{enumerable:!1},nodes:{enumerable:!1},source:{enumerable:!1},positions:{enumerable:!1},originalError:{enumerable:!1}}),null!=p&&p.stack?Object.defineProperty(this,"stack",{value:p.stack,writable:!0,configurable:!0}):Error.captureStackTrace?Error.captureStackTrace(this,a):Object.defineProperty(this,"stack",{value:Error().stack,writable:!0,configurable:!0})}get[Symbol.toStringTag](){return"GraphQLError"}toString(){let e=this.message;if(this.nodes)for(const t of this.nodes)t.loc&&(e+="\n\n"+(0,o.Q)(t.loc));else if(this.source&&this.locations)for(const t of this.locations)e+="\n\n"+(0,o.z)(this.source,t);return e}toJSON(){const e={message:this.message};return null!=this.locations&&(e.locations=this.locations),null!=this.path&&(e.path=this.path),null!=this.extensions&&Object.keys(this.extensions).length>0&&(e.extensions=this.extensions),e}}function s(e){return void 0===e||0===e.length?void 0:e}function l(e){return e.toString()}function u(e){return e.toJSON()}},4647:function(e,t,n){"use strict";n.d(t,{y:function(){return a}});var r=n(5648);class i extends Error{constructor(e){super("Unexpected error value: "+(0,r.X)(e)),this.name="NonErrorThrown",this.thrownValue=e}}var o=n(4117);function a(e,t,n){var r;const a=(s=e)instanceof Error?s:new i(s);var s,l;return l=a,Array.isArray(l.path)?a:new o.__(a.message,{nodes:null!==(r=a.nodes)&&void 0!==r?r:t,source:a.source,positions:a.positions,path:n,originalError:a})}},2024:function(e,t,n){"use strict";n.d(t,{h:function(){return i}});var r=n(4117);function i(e,t,n){return new r.__(`Syntax Error: ${n}`,{source:e,positions:[t]})}},5267:function(e,t,n){"use strict";n.d(t,{g:function(){return l},w:function(){return u}});var r=n(3830),i=n(755),o=n(5946),a=n(5998),s=n(2806);function l(e,t,n,r,i){const o=new Map;return c(e,t,n,r,i,o,new Set),o}function u(e,t,n,r,i){const o=new Map,a=new Set;for(const s of i)s.selectionSet&&c(e,t,n,r,s.selectionSet,o,a);return o}function c(e,t,n,i,o,a,s){for(const u of o.selections)switch(u.kind){case r.h.FIELD:{if(!d(n,u))continue;const e=(l=u).alias?l.alias.value:l.name.value,t=a.get(e);void 0!==t?t.push(u):a.set(e,[u]);break}case r.h.INLINE_FRAGMENT:if(!d(n,u)||!f(e,u,i))continue;c(e,t,n,i,u.selectionSet,a,s);break;case r.h.FRAGMENT_SPREAD:{const r=u.name.value;if(s.has(r)||!d(n,u))continue;s.add(r);const o=t[r];if(!o||!f(e,o,i))continue;c(e,t,n,i,o.selectionSet,a,s);break}}var l}function d(e,t){const n=(0,s.zu)(o.QE,t,e);if(!0===(null==n?void 0:n.if))return!1;const r=(0,s.zu)(o.Yf,t,e);return!1!==(null==r?void 0:r.if)}function f(e,t,n){const r=t.typeCondition;if(!r)return!0;const o=(0,a._)(e,r);return o===n||!!(0,i.m0)(o)&&e.isSubType(o,n)}},7180:function(e,t,n){"use strict";n.d(t,{td:function(){return C},VZ:function(){return S},p$:function(){return N},El:function(){return M},mn:function(){return A},ht:function(){return E},p0:function(){return T},Vm:function(){return R}});var r=n(1172),i=n(5648),o=n(5052),a=n(2910),s=n(1315),l=n(1864),u=n(9878),c=n(4117),d=n(4647),f=n(3526),p=n(3830),h=n(755),m=n(8078),g=n(8555),v=n(5267),y=n(2806);const b=function(e){let t;return function(e,n,r){void 0===t&&(t=new WeakMap);let i=t.get(e);void 0===i&&(i=new WeakMap,t.set(e,i));let o=i.get(n);void 0===o&&(o=new WeakMap,i.set(n,o));let a=o.get(r);return void 0===a&&(s=e,l=n,u=r,a=(0,v.w)(s.schema,s.fragments,s.variableValues,l,u),o.set(r,a)),a;var s,l,u}}();function E(e){arguments.length<2||(0,r.a)(!1,"graphql@16 dropped long-deprecated support for positional arguments, please pass an object instead.");const{schema:t,document:n,variableValues:i,rootValue:o}=e;C(t,n,i);const a=S(e);if(!("schema"in a))return{errors:a};try{const{operation:e}=a,t=function(e,t,n){const r=e.schema.getRootType(t.operation);if(null==r)throw new c.__(`Schema is not configured to execute ${t.operation} operation.`,{nodes:t});const i=(0,v.g)(e.schema,e.fragments,e.variableValues,r,t.selectionSet),o=void 0;switch(t.operation){case f.ku.QUERY:return x(e,r,n,o,i);case f.ku.MUTATION:return function(e,t,n,r,i){return function(e,t,n){let r=Object.create(null);for(const n of e)r=(0,l.t)(r)?r.then((e=>t(e,n))):t(r,n);return r}(i.entries(),((r,[i,o])=>{const a=(0,u.Q)(undefined,i,t.name),s=k(e,t,n,o,a);return void 0===s?r:(0,l.t)(s)?s.then((e=>(r[i]=e,r))):(r[i]=s,r)}))}(e,r,n,0,i);case f.ku.SUBSCRIPTION:return x(e,r,n,o,i)}}(a,e,o);return(0,l.t)(t)?t.then((e=>w(e,a.errors)),(e=>(a.errors.push(e),w(null,a.errors)))):w(t,a.errors)}catch(e){return a.errors.push(e),w(null,a.errors)}}function T(e){const t=E(e);if((0,l.t)(t))throw new Error("GraphQL execution failed to complete synchronously.");return t}function w(e,t){return 0===t.length?{data:e}:{errors:t,data:e}}function C(e,t,n){t||(0,r.a)(!1,"Must provide document."),(0,g.J)(e),null==n||(0,s.y)(n)||(0,r.a)(!1,"Variables must be provided as an Object where each property is a variable value. Perhaps look to see if an unparsed JSON string was provided.")}function S(e){var t,n;const{schema:r,document:i,rootValue:o,contextValue:a,variableValues:s,operationName:l,fieldResolver:u,typeResolver:d,subscribeFieldResolver:f}=e;let h;const m=Object.create(null);for(const e of i.definitions)switch(e.kind){case p.h.OPERATION_DEFINITION:if(null==l){if(void 0!==h)return[new c.__("Must provide operation name if query contains multiple operations.")];h=e}else(null===(t=e.name)||void 0===t?void 0:t.value)===l&&(h=e);break;case p.h.FRAGMENT_DEFINITION:m[e.name.value]=e}if(!h)return null!=l?[new c.__(`Unknown operation named "${l}".`)]:[new c.__("Must provide an operation.")];const g=null!==(n=h.variableDefinitions)&&void 0!==n?n:[],v=(0,y.QF)(r,g,null!=s?s:{},{maxErrors:50});return v.errors?v.errors:{schema:r,fragments:m,rootValue:o,contextValue:a,operation:h,variableValues:v.coerced,fieldResolver:null!=u?u:M,typeResolver:null!=d?d:A,subscribeFieldResolver:null!=f?f:M,errors:[]}}function x(e,t,n,r,i){const o=Object.create(null);let a=!1;for(const[s,c]of i.entries()){const i=k(e,t,n,c,(0,u.Q)(r,s,t.name));void 0!==i&&(o[s]=i,(0,l.t)(i)&&(a=!0))}return a?(s=o,Promise.all(Object.values(s)).then((e=>{const t=Object.create(null);for(const[n,r]of Object.keys(s).entries())t[r]=e[n];return t}))):o;var s}function k(e,t,n,r,i){var o;const a=R(e.schema,t,r[0]);if(!a)return;const s=a.type,c=null!==(o=a.resolve)&&void 0!==o?o:e.fieldResolver,f=N(e,a,r,t,i);try{const t=c(n,(0,y.LX)(a,r[0],e.variableValues),e.contextValue,f);let o;return o=(0,l.t)(t)?t.then((t=>O(e,s,r,f,i,t))):O(e,s,r,f,i,t),(0,l.t)(o)?o.then(void 0,(t=>_((0,d.y)(t,r,(0,u.N)(i)),s,e))):o}catch(t){return _((0,d.y)(t,r,(0,u.N)(i)),s,e)}}function N(e,t,n,r,i){return{fieldName:t.name,fieldNodes:n,returnType:t.type,parentType:r,path:i,schema:e.schema,fragments:e.fragments,rootValue:e.rootValue,operation:e.operation,variableValues:e.variableValues}}function _(e,t,n){if((0,h.zM)(t))throw e;return n.errors.push(e),null}function O(e,t,n,r,s,f){if(f instanceof Error)throw f;if((0,h.zM)(t)){const i=O(e,t.ofType,n,r,s,f);if(null===i)throw new Error(`Cannot return null for non-nullable field ${r.parentType.name}.${r.fieldName}.`);return i}return null==f?null:(0,h.HG)(t)?function(e,t,n,r,i,o){if(!(0,a.i)(o))throw new c.__(`Expected Iterable, but did not find one for field "${r.parentType.name}.${r.fieldName}".`);const s=t.ofType;let f=!1;const p=Array.from(o,((t,o)=>{const a=(0,u.Q)(i,o,void 0);try{let i;return i=(0,l.t)(t)?t.then((t=>O(e,s,n,r,a,t))):O(e,s,n,r,a,t),(0,l.t)(i)?(f=!0,i.then(void 0,(t=>_((0,d.y)(t,n,(0,u.N)(a)),s,e)))):i}catch(t){return _((0,d.y)(t,n,(0,u.N)(a)),s,e)}}));return f?Promise.all(p):p}(e,t,n,r,s,f):(0,h.UT)(t)?function(e,t){const n=e.serialize(t);if(null==n)throw new Error(`Expected \`${(0,i.X)(e)}.serialize(${(0,i.X)(t)})\` to return non-nullable value, returned: ${(0,i.X)(n)}`);return n}(t,f):(0,h.m0)(t)?function(e,t,n,r,i,o){var a;const s=null!==(a=t.resolveType)&&void 0!==a?a:e.typeResolver,u=e.contextValue,c=s(o,u,r,t);return(0,l.t)(c)?c.then((a=>D(e,I(a,e,t,n,r,o),n,r,i,o))):D(e,I(c,e,t,n,r,o),n,r,i,o)}(e,t,n,r,s,f):(0,h.lp)(t)?D(e,t,n,r,s,f):void(0,o.k)(!1,"Cannot complete value of unexpected output type: "+(0,i.X)(t))}function I(e,t,n,r,o,a){if(null==e)throw new c.__(`Abstract type "${n.name}" must resolve to an Object type at runtime for field "${o.parentType.name}.${o.fieldName}". Either the "${n.name}" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.`,r);if((0,h.lp)(e))throw new c.__("Support for returning GraphQLObjectType from resolveType was removed in graphql-js@16.0.0 please return type name instead.");if("string"!=typeof e)throw new c.__(`Abstract type "${n.name}" must resolve to an Object type at runtime for field "${o.parentType.name}.${o.fieldName}" with value ${(0,i.X)(a)}, received "${(0,i.X)(e)}".`);const s=t.schema.getType(e);if(null==s)throw new c.__(`Abstract type "${n.name}" was resolved to a type "${e}" that does not exist inside the schema.`,{nodes:r});if(!(0,h.lp)(s))throw new c.__(`Abstract type "${n.name}" was resolved to a non-object type "${e}".`,{nodes:r});if(!t.schema.isSubType(n,s))throw new c.__(`Runtime Object type "${s.name}" is not a possible type for "${n.name}".`,{nodes:r});return s}function D(e,t,n,r,i,o){const a=b(e,t,n);if(t.isTypeOf){const s=t.isTypeOf(o,e.contextValue,r);if((0,l.t)(s))return s.then((r=>{if(!r)throw L(t,o,n);return x(e,t,o,i,a)}));if(!s)throw L(t,o,n)}return x(e,t,o,i,a)}function L(e,t,n){return new c.__(`Expected value of type "${e.name}" but got: ${(0,i.X)(t)}.`,{nodes:n})}const A=function(e,t,n,r){if((0,s.y)(e)&&"string"==typeof e.__typename)return e.__typename;const i=n.schema.getPossibleTypes(r),o=[];for(let r=0;r{for(let t=0;t(0,c.ht)({schema:t,document:n,rootValue:e,contextValue:a,variableValues:s,operationName:l,fieldResolver:u}))):f}async function p(e,t,n,r,f,p,h){(0,c.td)(e,t,f);const m=(0,c.VZ)({schema:e,document:t,rootValue:n,contextValue:r,variableValues:f,operationName:p,subscribeFieldResolver:h});if(!("schema"in m))return{errors:m};try{const e=await async function(e){const{schema:t,fragments:n,operation:r,variableValues:i,rootValue:o}=e,f=t.getSubscriptionType();if(null==f)throw new s.__("Schema is not configured to execute subscription operation.",{nodes:r});const p=(0,u.g)(t,n,i,f,r.selectionSet),[h,m]=[...p.entries()][0],g=(0,c.Vm)(t,f,m[0]);if(!g){const e=m[0].name.value;throw new s.__(`The subscription field "${e}" is not defined.`,{nodes:m})}const v=(0,a.Q)(void 0,h,f.name),y=(0,c.p$)(e,g,m,f,v);try{var b;const t=(0,d.LX)(g,m[0],i),n=e.contextValue,r=null!==(b=g.subscribe)&&void 0!==b?b:e.subscribeFieldResolver,a=await r(o,t,n,y);if(a instanceof Error)throw a;return a}catch(e){throw(0,l.y)(e,m,(0,a.N)(v))}}(m);if(!o(e))throw new Error(`Subscription field must return Async Iterable. Received: ${(0,i.X)(e)}.`);return e}catch(e){if(e instanceof s.__)return{errors:[e]};throw e}}},2806:function(e,t,n){"use strict";n.d(t,{LX:function(){return h},QF:function(){return p},zu:function(){return m}});var r=n(5648),i=n(9815),o=n(4987),a=n(4117),s=n(3830),l=n(5895),u=n(755),c=n(5925),d=n(5998),f=n(5284);function p(e,t,n,i){const s=[],p=null==i?void 0:i.maxErrors;try{const i=function(e,t,n,i){const s={};for(const p of t){const t=p.variable.name.value,h=(0,d._)(e,p.type);if(!(0,u.j$)(h)){const e=(0,l.S)(p.type);i(new a.__(`Variable "$${t}" expected value of type "${e}" which cannot be used as an input type.`,{nodes:p.type}));continue}if(!g(n,t)){if(p.defaultValue)s[t]=(0,f.u)(p.defaultValue,h);else if((0,u.zM)(h)){const e=(0,r.X)(h);i(new a.__(`Variable "$${t}" of required type "${e}" was not provided.`,{nodes:p}))}continue}const m=n[t];if(null===m&&(0,u.zM)(h)){const e=(0,r.X)(h);i(new a.__(`Variable "$${t}" of non-null type "${e}" must not be null.`,{nodes:p}))}else s[t]=(0,c.K)(m,h,((e,n,s)=>{let l=`Variable "$${t}" got invalid value `+(0,r.X)(n);e.length>0&&(l+=` at "${t}${(0,o.F)(e)}"`),i(new a.__(l+"; "+s.message,{nodes:p,originalError:s.originalError}))}))}return s}(e,t,n,(e=>{if(null!=p&&s.length>=p)throw new a.__("Too many errors processing variables, error limit reached. Execution aborted.");s.push(e)}));if(0===s.length)return{coerced:i}}catch(e){s.push(e)}return{errors:s}}function h(e,t,n){var o;const c={},d=null!==(o=t.arguments)&&void 0!==o?o:[],p=(0,i.P)(d,(e=>e.name.value));for(const i of e.args){const e=i.name,o=i.type,d=p[e];if(!d){if(void 0!==i.defaultValue)c[e]=i.defaultValue;else if((0,u.zM)(o))throw new a.__(`Argument "${e}" of required type "${(0,r.X)(o)}" was not provided.`,{nodes:t});continue}const h=d.value;let m=h.kind===s.h.NULL;if(h.kind===s.h.VARIABLE){const t=h.name.value;if(null==n||!g(n,t)){if(void 0!==i.defaultValue)c[e]=i.defaultValue;else if((0,u.zM)(o))throw new a.__(`Argument "${e}" of required type "${(0,r.X)(o)}" was provided the variable "$${t}" which was not provided a runtime value.`,{nodes:h});continue}m=null==n[t]}if(m&&(0,u.zM)(o))throw new a.__(`Argument "${e}" of non-null type "${(0,r.X)(o)}" must not be null.`,{nodes:h});const v=(0,f.u)(h,o,n);if(void 0===v)throw new a.__(`Argument "${e}" has invalid value ${(0,l.S)(h)}.`,{nodes:h});c[e]=v}return c}function m(e,t,n){var r;const i=null===(r=t.directives)||void 0===r?void 0:r.find((t=>t.name.value===e.name));if(i)return h(e,i,n)}function g(e,t){return Object.prototype.hasOwnProperty.call(e,t)}},3573:function(e,t,n){"use strict";n.r(t),n.d(t,{BREAK:function(){return N.$_},BreakingChangeType:function(){return pt},DEFAULT_DEPRECATION_REASON:function(){return g.SY},DangerousChangeType:function(){return ht},DirectiveLocation:function(){return O.B},ExecutableDefinitionsRule:function(){return U.i},FieldsOnCorrectTypeRule:function(){return B.A},FragmentsOnCompositeTypesRule:function(){return $.T},GRAPHQL_MAX_INT:function(){return v.HI},GRAPHQL_MIN_INT:function(){return v.st},GraphQLBoolean:function(){return v.EZ},GraphQLDeprecatedDirective:function(){return g.fg},GraphQLDirective:function(){return g.NZ},GraphQLEnumType:function(){return h.mR},GraphQLError:function(){return R.__},GraphQLFloat:function(){return v.av},GraphQLID:function(){return v.km},GraphQLIncludeDirective:function(){return g.Yf},GraphQLInputObjectType:function(){return h.sR},GraphQLInt:function(){return v._o},GraphQLInterfaceType:function(){return h.oW},GraphQLList:function(){return h.p2},GraphQLNonNull:function(){return h.bM},GraphQLObjectType:function(){return h.h6},GraphQLScalarType:function(){return h.n2},GraphQLSchema:function(){return m.XO},GraphQLSkipDirective:function(){return g.QE},GraphQLSpecifiedByDirective:function(){return g.df},GraphQLString:function(){return v.kH},GraphQLUnionType:function(){return h.Gp},Kind:function(){return _.h},KnownArgumentNamesRule:function(){return q.e},KnownDirectivesRule:function(){return H.J},KnownFragmentNamesRule:function(){return G.a},KnownTypeNamesRule:function(){return z.I},Lexer:function(){return S.h},Location:function(){return E.Ye},LoneAnonymousOperationRule:function(){return W.F},LoneSchemaDefinitionRule:function(){return fe.t},NoDeprecatedCustomRule:function(){return F},NoFragmentCyclesRule:function(){return K.H},NoSchemaIntrospectionCustomRule:function(){return P},NoUndefinedVariablesRule:function(){return Q.$},NoUnusedFragmentsRule:function(){return X.J},NoUnusedVariablesRule:function(){return Y.p},OperationTypeNode:function(){return E.ku},OverlappingFieldsCanBeMergedRule:function(){return J.y},PossibleFragmentSpreadsRule:function(){return Z.a},PossibleTypeExtensionsRule:function(){return be.g},ProvidedRequiredArgumentsRule:function(){return ee.s},ScalarLeafsRule:function(){return te.O},SchemaMetaFieldDef:function(){return y.Az},SingleFieldSubscriptionsRule:function(){return ne.Z},Source:function(){return T.H},Token:function(){return E.WU},TokenKind:function(){return x.T},TypeInfo:function(){return Mt.a},TypeKind:function(){return y.zU},TypeMetaFieldDef:function(){return y.tF},TypeNameMetaFieldDef:function(){return y.hU},UniqueArgumentDefinitionNamesRule:function(){return ve.L},UniqueArgumentNamesRule:function(){return re.L},UniqueDirectiveNamesRule:function(){return ye.o},UniqueDirectivesPerLocationRule:function(){return ie.k},UniqueEnumValueNamesRule:function(){return me.L},UniqueFieldDefinitionNamesRule:function(){return ge.y},UniqueFragmentNamesRule:function(){return oe.N},UniqueInputFieldNamesRule:function(){return ae.P},UniqueOperationNamesRule:function(){return se.H},UniqueOperationTypesRule:function(){return pe.q},UniqueTypeNamesRule:function(){return he.P},UniqueVariableNamesRule:function(){return le.H},ValidationContext:function(){return j._t},ValuesOfCorrectTypeRule:function(){return ue.j},VariablesAreInputTypesRule:function(){return ce.I},VariablesInAllowedPositionRule:function(){return de.w},__Directive:function(){return y.l3},__DirectiveLocation:function(){return y.x2},__EnumValue:function(){return y.jT},__Field:function(){return y.e_},__InputValue:function(){return y.XQ},__Schema:function(){return y.TK},__Type:function(){return y.qz},__TypeKind:function(){return y.PX},assertAbstractType:function(){return h.fU},assertCompositeType:function(){return h.M_},assertDirective:function(){return g.CO},assertEnumType:function(){return h.Zu},assertEnumValueName:function(){return b.g},assertInputObjectType:function(){return h.U8},assertInputType:function(){return h.qT},assertInterfaceType:function(){return h.k2},assertLeafType:function(){return h.H5},assertListType:function(){return h.kS},assertName:function(){return b.i},assertNamedType:function(){return h.rM},assertNonNullType:function(){return h.E$},assertNullableType:function(){return h.i_},assertObjectType:function(){return h.Z6},assertOutputType:function(){return h.Gt},assertScalarType:function(){return h.Pt},assertSchema:function(){return m.EO},assertType:function(){return h.p_},assertUnionType:function(){return h.rc},assertValidName:function(){return ct},assertValidSchema:function(){return l.J},assertWrappingType:function(){return h.vX},astFromValue:function(){return Ge.J},buildASTSchema:function(){return Pe},buildClientSchema:function(){return Oe},buildSchema:function(){return je},coerceInputValue:function(){return Rt.K},concatAST:function(){return ot},createSourceEventStream:function(){return A.z},defaultFieldResolver:function(){return c.El},defaultTypeResolver:function(){return c.mn},doTypesOverlap:function(){return Ft.zR},execute:function(){return c.ht},executeSync:function(){return c.p0},extendSchema:function(){return Le},findBreakingChanges:function(){return mt},findDangerousChanges:function(){return gt},formatError:function(){return R.Z},getArgumentValues:function(){return L.LX},getDirectiveValues:function(){return L.zu},getEnterLeaveForKind:function(){return N.Eu},getIntrospectionQuery:function(){return we},getLocation:function(){return w.k},getNamedType:function(){return h.xC},getNullableType:function(){return h.tf},getOperationAST:function(){return Dt.S},getOperationRootType:function(){return Ce},getVariableValues:function(){return L.QF},getVisitFn:function(){return N.CK},graphql:function(){return d},graphqlSync:function(){return f},introspectionFromSchema:function(){return Se},introspectionTypes:function(){return y.nL},isAbstractType:function(){return h.m0},isCompositeType:function(){return h.Gv},isConstValueNode:function(){return I.Of},isDefinitionNode:function(){return I.Ir},isDirective:function(){return g.wX},isEnumType:function(){return h.EM},isEqualType:function(){return Ft._7},isExecutableDefinitionNode:function(){return I.Wk},isInputObjectType:function(){return h.hL},isInputType:function(){return h.j$},isInterfaceType:function(){return h.oT},isIntrospectionType:function(){return y.s9},isLeafType:function(){return h.UT},isListType:function(){return h.HG},isNamedType:function(){return h.Zs},isNonNullType:function(){return h.zM},isNullableType:function(){return h.zP},isObjectType:function(){return h.lp},isOutputType:function(){return h.SZ},isRequiredArgument:function(){return h.dK},isRequiredInputField:function(){return h.Wd},isScalarType:function(){return h.KA},isSchema:function(){return m.nN},isSelectionNode:function(){return I.pO},isSpecifiedDirective:function(){return g.xg},isSpecifiedScalarType:function(){return v.u1},isType:function(){return h.P9},isTypeDefinitionNode:function(){return I.zT},isTypeExtensionNode:function(){return I.D$},isTypeNode:function(){return I.VB},isTypeSubTypeOf:function(){return Ft.uJ},isTypeSystemDefinitionNode:function(){return I.G4},isTypeSystemExtensionNode:function(){return I.aU},isUnionType:function(){return h.EN},isValidNameError:function(){return dt},isValueNode:function(){return I.nr},isWrappingType:function(){return h.fw},lexicographicSortSchema:function(){return Ue},locatedError:function(){return Te.y},parse:function(){return s.Qc},parseConstValue:function(){return s.tl},parseType:function(){return s.gZ},parseValue:function(){return s.H2},print:function(){return k.S},printError:function(){return R.OS},printIntrospectionSchema:function(){return We},printLocation:function(){return C.Q},printSchema:function(){return ze},printSourceLocation:function(){return C.z},printType:function(){return Ye},resolveObjMapThunk:function(){return h.WB},resolveReadonlyArrayThunk:function(){return h._9},responsePathAsArray:function(){return D.N},separateOperations:function(){return at},specifiedDirectives:function(){return g.V4},specifiedRules:function(){return V.i},specifiedScalarTypes:function(){return v.HS},stripIgnoredCharacters:function(){return ut},subscribe:function(){return A.L},syntaxError:function(){return Ee.h},typeFromAST:function(){return Lt._},validate:function(){return u.Gu},validateSchema:function(){return l.F},valueFromAST:function(){return _e.u},valueFromASTUntyped:function(){return At.M},version:function(){return r},versionInfo:function(){return i},visit:function(){return N.Vn},visitInParallel:function(){return N.j1},visitWithTypeInfo:function(){return Mt.y}});const r="16.5.0",i=Object.freeze({major:16,minor:5,patch:0,preReleaseTag:null});var o=n(1172),a=n(1864),s=n(953),l=n(8555),u=n(2780),c=n(7180);function d(e){return new Promise((t=>t(p(e))))}function f(e){const t=p(e);if((0,a.t)(t))throw new Error("GraphQL execution failed to complete synchronously.");return t}function p(e){arguments.length<2||(0,o.a)(!1,"graphql@16 dropped long-deprecated support for positional arguments, please pass an object instead.");const{schema:t,source:n,rootValue:r,contextValue:i,variableValues:a,operationName:d,fieldResolver:f,typeResolver:p}=e,h=(0,l.F)(t);if(h.length>0)return{errors:h};let m;try{m=(0,s.Qc)(n)}catch(e){return{errors:[e]}}const g=(0,u.Gu)(t,m);return g.length>0?{errors:g}:(0,c.ht)({schema:t,document:m,rootValue:r,contextValue:i,variableValues:a,operationName:d,fieldResolver:f,typeResolver:p})}var h=n(755),m=n(773),g=n(5946),v=n(1774),y=n(8078),b=n(3228),E=n(3526),T=n(4680),w=n(3302),C=n(7026),S=n(9230),x=n(5685),k=n(5895),N=n(9685),_=n(3830),O=n(3140),I=n(9615),D=n(9878),L=n(2806),A=n(2941),M=n(5052),R=n(4117);function F(e){return{Field(t){const n=e.getFieldDef(),r=null==n?void 0:n.deprecationReason;if(n&&null!=r){const i=e.getParentType();null!=i||(0,M.k)(!1),e.reportError(new R.__(`The field ${i.name}.${n.name} is deprecated. ${r}`,{nodes:t}))}},Argument(t){const n=e.getArgument(),r=null==n?void 0:n.deprecationReason;if(n&&null!=r){const i=e.getDirective();if(null!=i)e.reportError(new R.__(`Directive "@${i.name}" argument "${n.name}" is deprecated. ${r}`,{nodes:t}));else{const i=e.getParentType(),o=e.getFieldDef();null!=i&&null!=o||(0,M.k)(!1),e.reportError(new R.__(`Field "${i.name}.${o.name}" argument "${n.name}" is deprecated. ${r}`,{nodes:t}))}}},ObjectField(t){const n=(0,h.xC)(e.getParentInputType());if((0,h.hL)(n)){const r=n.getFields()[t.name.value],i=null==r?void 0:r.deprecationReason;null!=i&&e.reportError(new R.__(`The input field ${n.name}.${r.name} is deprecated. ${i}`,{nodes:t}))}},EnumValue(t){const n=e.getEnumValue(),r=null==n?void 0:n.deprecationReason;if(n&&null!=r){const i=(0,h.xC)(e.getInputType());null!=i||(0,M.k)(!1),e.reportError(new R.__(`The enum value "${i.name}.${n.name}" is deprecated. ${r}`,{nodes:t}))}}}}function P(e){return{Field(t){const n=(0,h.xC)(e.getType());n&&(0,y.s9)(n)&&e.reportError(new R.__(`GraphQL introspection has been disabled, but the requested query contained the field "${t.name.value}".`,{nodes:t}))}}}var j=n(2716),V=n(9299),U=n(3857),B=n(1870),$=n(5167),q=n(4875),H=n(7513),G=n(1435),z=n(591),W=n(831),K=n(9316),Q=n(9518),X=n(3447),Y=n(7114),J=n(7163),Z=n(5961),ee=n(16),te=n(3989),ne=n(9309),re=n(9168),ie=n(5673),oe=n(614),ae=n(5707),se=n(2355),le=n(7417),ue=n(4697),ce=n(6192),de=n(377),fe=n(3402),pe=n(5427),he=n(4519),me=n(4560),ge=n(5240),ve=n(5947),ye=n(5681),be=n(3721),Ee=n(2024),Te=n(4647);function we(e){const t={descriptions:!0,specifiedByUrl:!1,directiveIsRepeatable:!1,schemaDescription:!1,inputValueDeprecation:!1,...e},n=t.descriptions?"description":"",r=t.specifiedByUrl?"specifiedByURL":"",i=t.directiveIsRepeatable?"isRepeatable":"";function o(e){return t.inputValueDeprecation?e:""}return`\n query IntrospectionQuery {\n __schema {\n ${t.schemaDescription?n:""}\n queryType { name }\n mutationType { name }\n subscriptionType { name }\n types {\n ...FullType\n }\n directives {\n name\n ${n}\n ${i}\n locations\n args${o("(includeDeprecated: true)")} {\n ...InputValue\n }\n }\n }\n }\n\n fragment FullType on __Type {\n kind\n name\n ${n}\n ${r}\n fields(includeDeprecated: true) {\n name\n ${n}\n args${o("(includeDeprecated: true)")} {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields${o("(includeDeprecated: true)")} {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n ${n}\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n }\n\n fragment InputValue on __InputValue {\n name\n ${n}\n type { ...TypeRef }\n defaultValue\n ${o("isDeprecated")}\n ${o("deprecationReason")}\n }\n\n fragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n }\n `}function Ce(e,t){if("query"===t.operation){const n=e.getQueryType();if(!n)throw new R.__("Schema does not define the required query root type.",{nodes:t});return n}if("mutation"===t.operation){const n=e.getMutationType();if(!n)throw new R.__("Schema is not configured for mutations.",{nodes:t});return n}if("subscription"===t.operation){const n=e.getSubscriptionType();if(!n)throw new R.__("Schema is not configured for subscriptions.",{nodes:t});return n}throw new R.__("Can only have query, mutation and subscription operations.",{nodes:t})}function Se(e,t){const n={specifiedByUrl:!0,directiveIsRepeatable:!0,schemaDescription:!0,inputValueDeprecation:!0,...t},r=(0,s.Qc)(we(n)),i=(0,c.p0)({schema:e,document:r});return!i.errors&&i.data||(0,M.k)(!1),i.data}var xe=n(5648),ke=n(1315),Ne=n(8240),_e=n(5284);function Oe(e,t){(0,ke.y)(e)&&(0,ke.y)(e.__schema)||(0,o.a)(!1,`Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ${(0,xe.X)(e)}.`);const n=e.__schema,r=(0,Ne.w)(n.types,(e=>e.name),(e=>function(e){if(null!=e&&null!=e.name&&null!=e.kind)switch(e.kind){case y.zU.SCALAR:return r=e,new h.n2({name:r.name,description:r.description,specifiedByURL:r.specifiedByURL});case y.zU.OBJECT:return n=e,new h.h6({name:n.name,description:n.description,interfaces:()=>b(n),fields:()=>E(n)});case y.zU.INTERFACE:return t=e,new h.oW({name:t.name,description:t.description,interfaces:()=>b(t),fields:()=>E(t)});case y.zU.UNION:return function(e){if(!e.possibleTypes){const t=(0,xe.X)(e);throw new Error(`Introspection result missing possibleTypes: ${t}.`)}return new h.Gp({name:e.name,description:e.description,types:()=>e.possibleTypes.map(f)})}(e);case y.zU.ENUM:return function(e){if(!e.enumValues){const t=(0,xe.X)(e);throw new Error(`Introspection result missing enumValues: ${t}.`)}return new h.mR({name:e.name,description:e.description,values:(0,Ne.w)(e.enumValues,(e=>e.name),(e=>({description:e.description,deprecationReason:e.deprecationReason})))})}(e);case y.zU.INPUT_OBJECT:return function(e){if(!e.inputFields){const t=(0,xe.X)(e);throw new Error(`Introspection result missing inputFields: ${t}.`)}return new h.sR({name:e.name,description:e.description,fields:()=>w(e.inputFields)})}(e)}var t,n,r;const i=(0,xe.X)(e);throw new Error(`Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: ${i}.`)}(e)));for(const e of[...v.HS,...y.nL])r[e.name]&&(r[e.name]=e);const i=n.queryType?f(n.queryType):null,a=n.mutationType?f(n.mutationType):null,l=n.subscriptionType?f(n.subscriptionType):null,u=n.directives?n.directives.map((function(e){if(!e.args){const t=(0,xe.X)(e);throw new Error(`Introspection result missing directive args: ${t}.`)}if(!e.locations){const t=(0,xe.X)(e);throw new Error(`Introspection result missing directive locations: ${t}.`)}return new g.NZ({name:e.name,description:e.description,isRepeatable:e.isRepeatable,locations:e.locations.slice(),args:w(e.args)})})):[];return new m.XO({description:n.description,query:i,mutation:a,subscription:l,types:Object.values(r),directives:u,assumeValid:null==t?void 0:t.assumeValid});function c(e){if(e.kind===y.zU.LIST){const t=e.ofType;if(!t)throw new Error("Decorated type deeper than introspection query.");return new h.p2(c(t))}if(e.kind===y.zU.NON_NULL){const t=e.ofType;if(!t)throw new Error("Decorated type deeper than introspection query.");const n=c(t);return new h.bM((0,h.i_)(n))}return d(e)}function d(e){const t=e.name;if(!t)throw new Error(`Unknown type reference: ${(0,xe.X)(e)}.`);const n=r[t];if(!n)throw new Error(`Invalid or incomplete schema, unknown type: ${t}. Ensure that a full introspection query is used in order to build a client schema.`);return n}function f(e){return(0,h.Z6)(d(e))}function p(e){return(0,h.k2)(d(e))}function b(e){if(null===e.interfaces&&e.kind===y.zU.INTERFACE)return[];if(!e.interfaces){const t=(0,xe.X)(e);throw new Error(`Introspection result missing interfaces: ${t}.`)}return e.interfaces.map(p)}function E(e){if(!e.fields)throw new Error(`Introspection result missing fields: ${(0,xe.X)(e)}.`);return(0,Ne.w)(e.fields,(e=>e.name),T)}function T(e){const t=c(e.type);if(!(0,h.SZ)(t)){const e=(0,xe.X)(t);throw new Error(`Introspection must provide output type for fields, but received: ${e}.`)}if(!e.args){const t=(0,xe.X)(e);throw new Error(`Introspection result missing field args: ${t}.`)}return{description:e.description,deprecationReason:e.deprecationReason,type:t,args:w(e.args)}}function w(e){return(0,Ne.w)(e,(e=>e.name),C)}function C(e){const t=c(e.type);if(!(0,h.j$)(t)){const e=(0,xe.X)(t);throw new Error(`Introspection must provide input type for arguments, but received: ${e}.`)}const n=null!=e.defaultValue?(0,_e.u)((0,s.H2)(e.defaultValue),t):void 0;return{description:e.description,type:t,defaultValue:n,deprecationReason:e.deprecationReason}}}var Ie=n(9815),De=n(9997);function Le(e,t,n){(0,m.EO)(e),null!=t&&t.kind===_.h.DOCUMENT||(0,o.a)(!1,"Must provide valid Document AST."),!0!==(null==n?void 0:n.assumeValid)&&!0!==(null==n?void 0:n.assumeValidSDL)&&(0,u.ED)(t,e);const r=e.toConfig(),i=Ae(r,t,n);return r===i?e:new m.XO(i)}function Ae(e,t,n){var r,i,o,a;const s=[],l=Object.create(null),u=[];let c;const d=[];for(const e of t.definitions)if(e.kind===_.h.SCHEMA_DEFINITION)c=e;else if(e.kind===_.h.SCHEMA_EXTENSION)d.push(e);else if((0,I.zT)(e))s.push(e);else if((0,I.D$)(e)){const t=e.name.value,n=l[t];l[t]=n?n.concat([e]):[e]}else e.kind===_.h.DIRECTIVE_DEFINITION&&u.push(e);if(0===Object.keys(l).length&&0===s.length&&0===u.length&&0===d.length&&null==c)return e;const f=Object.create(null);for(const t of e.types)f[t.name]=(p=t,(0,y.s9)(p)||(0,v.u1)(p)?p:(0,h.KA)(p)?function(e){var t;const n=e.toConfig(),r=null!==(t=l[n.name])&&void 0!==t?t:[];let i=n.specifiedByURL;for(const e of r){var o;i=null!==(o=Fe(e))&&void 0!==o?o:i}return new h.n2({...n,specifiedByURL:i,extensionASTNodes:n.extensionASTNodes.concat(r)})}(p):(0,h.lp)(p)?function(e){var t;const n=e.toConfig(),r=null!==(t=l[n.name])&&void 0!==t?t:[];return new h.h6({...n,interfaces:()=>[...e.getInterfaces().map(T),...A(r)],fields:()=>({...(0,De.j)(n.fields,w),...N(r)}),extensionASTNodes:n.extensionASTNodes.concat(r)})}(p):(0,h.oT)(p)?function(e){var t;const n=e.toConfig(),r=null!==(t=l[n.name])&&void 0!==t?t:[];return new h.oW({...n,interfaces:()=>[...e.getInterfaces().map(T),...A(r)],fields:()=>({...(0,De.j)(n.fields,w),...N(r)}),extensionASTNodes:n.extensionASTNodes.concat(r)})}(p):(0,h.EN)(p)?function(e){var t;const n=e.toConfig(),r=null!==(t=l[n.name])&&void 0!==t?t:[];return new h.Gp({...n,types:()=>[...e.getTypes().map(T),...R(r)],extensionASTNodes:n.extensionASTNodes.concat(r)})}(p):(0,h.EM)(p)?function(e){var t;const n=e.toConfig(),r=null!==(t=l[e.name])&&void 0!==t?t:[];return new h.mR({...n,values:{...n.values,...L(r)},extensionASTNodes:n.extensionASTNodes.concat(r)})}(p):(0,h.hL)(p)?function(e){var t;const n=e.toConfig(),r=null!==(t=l[n.name])&&void 0!==t?t:[];return new h.sR({...n,fields:()=>({...(0,De.j)(n.fields,(e=>({...e,type:E(e.type)}))),...D(r)}),extensionASTNodes:n.extensionASTNodes.concat(r)})}(p):void(0,M.k)(!1,"Unexpected type: "+(0,xe.X)(p)));var p;for(const e of s){var m;const t=e.name.value;f[t]=null!==(m=Me[t])&&void 0!==m?m:F(e)}const b={query:e.query&&T(e.query),mutation:e.mutation&&T(e.mutation),subscription:e.subscription&&T(e.subscription),...c&&S([c]),...S(d)};return{description:null===(r=c)||void 0===r||null===(i=r.description)||void 0===i?void 0:i.value,...b,types:Object.values(f),directives:[...e.directives.map((function(e){const t=e.toConfig();return new g.NZ({...t,args:(0,De.j)(t.args,C)})})),...u.map((function(e){var t;return new g.NZ({name:e.name.value,description:null===(t=e.description)||void 0===t?void 0:t.value,locations:e.locations.map((({value:e})=>e)),isRepeatable:e.repeatable,args:O(e.arguments),astNode:e})}))],extensions:Object.create(null),astNode:null!==(o=c)&&void 0!==o?o:e.astNode,extensionASTNodes:e.extensionASTNodes.concat(d),assumeValid:null!==(a=null==n?void 0:n.assumeValid)&&void 0!==a&&a};function E(e){return(0,h.HG)(e)?new h.p2(E(e.ofType)):(0,h.zM)(e)?new h.bM(E(e.ofType)):T(e)}function T(e){return f[e.name]}function w(e){return{...e,type:E(e.type),args:e.args&&(0,De.j)(e.args,C)}}function C(e){return{...e,type:E(e.type)}}function S(e){const t={};for(const r of e){var n;const e=null!==(n=r.operationTypes)&&void 0!==n?n:[];for(const n of e)t[n.operation]=x(n.type)}return t}function x(e){var t;const n=e.name.value,r=null!==(t=Me[n])&&void 0!==t?t:f[n];if(void 0===r)throw new Error(`Unknown type: "${n}".`);return r}function k(e){return e.kind===_.h.LIST_TYPE?new h.p2(k(e.type)):e.kind===_.h.NON_NULL_TYPE?new h.bM(k(e.type)):x(e)}function N(e){const t=Object.create(null);for(const i of e){var n;const e=null!==(n=i.fields)&&void 0!==n?n:[];for(const n of e){var r;t[n.name.value]={type:k(n.type),description:null===(r=n.description)||void 0===r?void 0:r.value,args:O(n.arguments),deprecationReason:Re(n),astNode:n}}}return t}function O(e){const t=null!=e?e:[],n=Object.create(null);for(const e of t){var r;const t=k(e.type);n[e.name.value]={type:t,description:null===(r=e.description)||void 0===r?void 0:r.value,defaultValue:(0,_e.u)(e.defaultValue,t),deprecationReason:Re(e),astNode:e}}return n}function D(e){const t=Object.create(null);for(const i of e){var n;const e=null!==(n=i.fields)&&void 0!==n?n:[];for(const n of e){var r;const e=k(n.type);t[n.name.value]={type:e,description:null===(r=n.description)||void 0===r?void 0:r.value,defaultValue:(0,_e.u)(n.defaultValue,e),deprecationReason:Re(n),astNode:n}}}return t}function L(e){const t=Object.create(null);for(const i of e){var n;const e=null!==(n=i.values)&&void 0!==n?n:[];for(const n of e){var r;t[n.name.value]={description:null===(r=n.description)||void 0===r?void 0:r.value,deprecationReason:Re(n),astNode:n}}}return t}function A(e){return e.flatMap((e=>{var t,n;return null!==(t=null===(n=e.interfaces)||void 0===n?void 0:n.map(x))&&void 0!==t?t:[]}))}function R(e){return e.flatMap((e=>{var t,n;return null!==(t=null===(n=e.types)||void 0===n?void 0:n.map(x))&&void 0!==t?t:[]}))}function F(e){var t;const n=e.name.value,r=null!==(t=l[n])&&void 0!==t?t:[];switch(e.kind){case _.h.OBJECT_TYPE_DEFINITION:{var i;const t=[e,...r];return new h.h6({name:n,description:null===(i=e.description)||void 0===i?void 0:i.value,interfaces:()=>A(t),fields:()=>N(t),astNode:e,extensionASTNodes:r})}case _.h.INTERFACE_TYPE_DEFINITION:{var o;const t=[e,...r];return new h.oW({name:n,description:null===(o=e.description)||void 0===o?void 0:o.value,interfaces:()=>A(t),fields:()=>N(t),astNode:e,extensionASTNodes:r})}case _.h.ENUM_TYPE_DEFINITION:{var a;const t=[e,...r];return new h.mR({name:n,description:null===(a=e.description)||void 0===a?void 0:a.value,values:L(t),astNode:e,extensionASTNodes:r})}case _.h.UNION_TYPE_DEFINITION:{var s;const t=[e,...r];return new h.Gp({name:n,description:null===(s=e.description)||void 0===s?void 0:s.value,types:()=>R(t),astNode:e,extensionASTNodes:r})}case _.h.SCALAR_TYPE_DEFINITION:var u;return new h.n2({name:n,description:null===(u=e.description)||void 0===u?void 0:u.value,specifiedByURL:Fe(e),astNode:e,extensionASTNodes:r});case _.h.INPUT_OBJECT_TYPE_DEFINITION:{var c;const t=[e,...r];return new h.sR({name:n,description:null===(c=e.description)||void 0===c?void 0:c.value,fields:()=>D(t),astNode:e,extensionASTNodes:r})}}}}const Me=(0,Ie.P)([...v.HS,...y.nL],(e=>e.name));function Re(e){const t=(0,L.zu)(g.fg,e);return null==t?void 0:t.reason}function Fe(e){const t=(0,L.zu)(g.df,e);return null==t?void 0:t.url}function Pe(e,t){null!=e&&e.kind===_.h.DOCUMENT||(0,o.a)(!1,"Must provide valid Document AST."),!0!==(null==t?void 0:t.assumeValid)&&!0!==(null==t?void 0:t.assumeValidSDL)&&(0,u.zo)(e);const n=Ae({description:void 0,types:[],directives:[],extensions:Object.create(null),extensionASTNodes:[],assumeValid:!1},e,t);if(null==n.astNode)for(const e of n.types)switch(e.name){case"Query":n.query=e;break;case"Mutation":n.mutation=e;break;case"Subscription":n.subscription=e}const r=[...n.directives,...g.V4.filter((e=>n.directives.every((t=>t.name!==e.name))))];return new m.XO({...n,directives:r})}function je(e,t){return Pe((0,s.Qc)(e,{noLocation:null==t?void 0:t.noLocation,allowLegacyFragmentVariables:null==t?void 0:t.allowLegacyFragmentVariables}),{assumeValidSDL:null==t?void 0:t.assumeValidSDL,assumeValid:null==t?void 0:t.assumeValid})}var Ve=n(6625);function Ue(e){const t=e.toConfig(),n=(0,Ne.w)($e(t.types),(e=>e.name),(function(e){if((0,h.KA)(e)||(0,y.s9)(e))return e;if((0,h.lp)(e)){const t=e.toConfig();return new h.h6({...t,interfaces:()=>l(t.interfaces),fields:()=>s(t.fields)})}if((0,h.oT)(e)){const t=e.toConfig();return new h.oW({...t,interfaces:()=>l(t.interfaces),fields:()=>s(t.fields)})}if((0,h.EN)(e)){const t=e.toConfig();return new h.Gp({...t,types:()=>l(t.types)})}if((0,h.EM)(e)){const t=e.toConfig();return new h.mR({...t,values:Be(t.values,(e=>e))})}if((0,h.hL)(e)){const t=e.toConfig();return new h.sR({...t,fields:()=>Be(t.fields,(e=>({...e,type:r(e.type)})))})}(0,M.k)(!1,"Unexpected type: "+(0,xe.X)(e))}));return new m.XO({...t,types:Object.values(n),directives:$e(t.directives).map((function(e){const t=e.toConfig();return new g.NZ({...t,locations:qe(t.locations,(e=>e)),args:a(t.args)})})),query:o(t.query),mutation:o(t.mutation),subscription:o(t.subscription)});function r(e){return(0,h.HG)(e)?new h.p2(r(e.ofType)):(0,h.zM)(e)?new h.bM(r(e.ofType)):i(e)}function i(e){return n[e.name]}function o(e){return e&&i(e)}function a(e){return Be(e,(e=>({...e,type:r(e.type)})))}function s(e){return Be(e,(e=>({...e,type:r(e.type),args:e.args&&a(e.args)})))}function l(e){return $e(e).map(i)}}function Be(e,t){const n=Object.create(null);for(const r of Object.keys(e).sort(Ve.K))n[r]=t(e[r]);return n}function $e(e){return qe(e,(e=>e.name))}function qe(e,t){return e.slice().sort(((e,n)=>{const r=t(e),i=t(n);return(0,Ve.K)(r,i)}))}var He=n(6303),Ge=n(3190);function ze(e){return Qe(e,(e=>!(0,g.xg)(e)),Ke)}function We(e){return Qe(e,g.xg,y.s9)}function Ke(e){return!(0,v.u1)(e)&&!(0,y.s9)(e)}function Qe(e,t,n){const r=e.getDirectives().filter(t),i=Object.values(e.getTypeMap()).filter(n);return[Xe(e),...r.map((e=>function(e){return it(e)+"directive @"+e.name+tt(e.args)+(e.isRepeatable?" repeatable":"")+" on "+e.locations.join(" | ")}(e))),...i.map((e=>Ye(e)))].filter(Boolean).join("\n\n")}function Xe(e){if(null==e.description&&function(e){const t=e.getQueryType();if(t&&"Query"!==t.name)return!1;const n=e.getMutationType();if(n&&"Mutation"!==n.name)return!1;const r=e.getSubscriptionType();return!r||"Subscription"===r.name}(e))return;const t=[],n=e.getQueryType();n&&t.push(` query: ${n.name}`);const r=e.getMutationType();r&&t.push(` mutation: ${r.name}`);const i=e.getSubscriptionType();return i&&t.push(` subscription: ${i.name}`),it(e)+`schema {\n${t.join("\n")}\n}`}function Ye(e){return(0,h.KA)(e)?function(e){return it(e)+`scalar ${e.name}`+(null==(t=e).specifiedByURL?"":` @specifiedBy(url: ${(0,k.S)({kind:_.h.STRING,value:t.specifiedByURL})})`);var t}(e):(0,h.lp)(e)?function(e){return it(e)+`type ${e.name}`+Je(e)+Ze(e)}(e):(0,h.oT)(e)?function(e){return it(e)+`interface ${e.name}`+Je(e)+Ze(e)}(e):(0,h.EN)(e)?function(e){const t=e.getTypes(),n=t.length?" = "+t.join(" | "):"";return it(e)+"union "+e.name+n}(e):(0,h.EM)(e)?function(e){const t=e.getValues().map(((e,t)=>it(e," ",!t)+" "+e.name+rt(e.deprecationReason)));return it(e)+`enum ${e.name}`+et(t)}(e):(0,h.hL)(e)?function(e){const t=Object.values(e.getFields()).map(((e,t)=>it(e," ",!t)+" "+nt(e)));return it(e)+`input ${e.name}`+et(t)}(e):void(0,M.k)(!1,"Unexpected type: "+(0,xe.X)(e))}function Je(e){const t=e.getInterfaces();return t.length?" implements "+t.map((e=>e.name)).join(" & "):""}function Ze(e){return et(Object.values(e.getFields()).map(((e,t)=>it(e," ",!t)+" "+e.name+tt(e.args," ")+": "+String(e.type)+rt(e.deprecationReason))))}function et(e){return 0!==e.length?" {\n"+e.join("\n")+"\n}":""}function tt(e,t=""){return 0===e.length?"":e.every((e=>!e.description))?"("+e.map(nt).join(", ")+")":"(\n"+e.map(((e,n)=>it(e," "+t,!n)+" "+t+nt(e))).join("\n")+"\n"+t+")"}function nt(e){const t=(0,Ge.J)(e.defaultValue,e.type);let n=e.name+": "+String(e.type);return t&&(n+=` = ${(0,k.S)(t)}`),n+rt(e.deprecationReason)}function rt(e){return null==e?"":e!==g.SY?` @deprecated(reason: ${(0,k.S)({kind:_.h.STRING,value:e})})`:" @deprecated"}function it(e,t="",n=!0){const{description:r}=e;return null==r?"":(t&&!n?"\n"+t:t)+(0,k.S)({kind:_.h.STRING,value:r,block:(0,He.MZ)(r)}).replace(/\n/g,"\n"+t)+"\n"}function ot(e){const t=[];for(const n of e)t.push(...n.definitions);return{kind:_.h.DOCUMENT,definitions:t}}function at(e){const t=[],n=Object.create(null);for(const r of e.definitions)switch(r.kind){case _.h.OPERATION_DEFINITION:t.push(r);break;case _.h.FRAGMENT_DEFINITION:n[r.name.value]=lt(r.selectionSet)}const r=Object.create(null);for(const i of t){const t=new Set;for(const e of lt(i.selectionSet))st(t,n,e);r[i.name?i.name.value:""]={kind:_.h.DOCUMENT,definitions:e.definitions.filter((e=>e===i||e.kind===_.h.FRAGMENT_DEFINITION&&t.has(e.name.value)))}}return r}function st(e,t,n){if(!e.has(n)){e.add(n);const r=t[n];if(void 0!==r)for(const n of r)st(e,t,n)}}function lt(e){const t=[];return(0,N.Vn)(e,{FragmentSpread(e){t.push(e.name.value)}}),t}function ut(e){const t=(0,T.T)(e)?e:new T.H(e),n=t.body,r=new S.h(t);let i="",o=!1;for(;r.advance().kind!==x.T.EOF;){const e=r.token,t=e.kind,a=!(0,S.u)(e.kind);o&&(a||e.kind===x.T.SPREAD)&&(i+=" ");const s=n.slice(e.start,e.end);t===x.T.BLOCK_STRING?i+=(0,He.LZ)(e.value,{minimize:!0}):i+=s,o=a}return i}function ct(e){const t=dt(e);if(t)throw t;return e}function dt(e){if("string"==typeof e||(0,o.a)(!1,"Expected name to be a string."),e.startsWith("__"))return new R.__(`Name "${e}" must not begin with "__", which is reserved by GraphQL introspection.`);try{(0,b.i)(e)}catch(e){return e}}var ft=n(4034);let pt,ht;function mt(e,t){return vt(e,t).filter((e=>e.type in pt))}function gt(e,t){return vt(e,t).filter((e=>e.type in ht))}function vt(e,t){return[...bt(e,t),...yt(e,t)]}function yt(e,t){const n=[],r=It(e.getDirectives(),t.getDirectives());for(const e of r.removed)n.push({type:pt.DIRECTIVE_REMOVED,description:`${e.name} was removed.`});for(const[e,t]of r.persisted){const r=It(e.args,t.args);for(const t of r.added)(0,h.dK)(t)&&n.push({type:pt.REQUIRED_DIRECTIVE_ARG_ADDED,description:`A required arg ${t.name} on directive ${e.name} was added.`});for(const t of r.removed)n.push({type:pt.DIRECTIVE_ARG_REMOVED,description:`${t.name} was removed from ${e.name}.`});e.isRepeatable&&!t.isRepeatable&&n.push({type:pt.DIRECTIVE_REPEATABLE_REMOVED,description:`Repeatable flag was removed from ${e.name}.`});for(const r of e.locations)t.locations.includes(r)||n.push({type:pt.DIRECTIVE_LOCATION_REMOVED,description:`${r} was removed from ${e.name}.`})}return n}function bt(e,t){const n=[],r=It(Object.values(e.getTypeMap()),Object.values(t.getTypeMap()));for(const e of r.removed)n.push({type:pt.TYPE_REMOVED,description:(0,v.u1)(e)?`Standard scalar ${e.name} was removed because it is not referenced anymore.`:`${e.name} was removed.`});for(const[e,t]of r.persisted)(0,h.EM)(e)&&(0,h.EM)(t)?n.push(...wt(e,t)):(0,h.EN)(e)&&(0,h.EN)(t)?n.push(...Tt(e,t)):(0,h.hL)(e)&&(0,h.hL)(t)?n.push(...Et(e,t)):(0,h.lp)(e)&&(0,h.lp)(t)||(0,h.oT)(e)&&(0,h.oT)(t)?n.push(...St(e,t),...Ct(e,t)):e.constructor!==t.constructor&&n.push({type:pt.TYPE_CHANGED_KIND,description:`${e.name} changed from ${_t(e)} to ${_t(t)}.`});return n}function Et(e,t){const n=[],r=It(Object.values(e.getFields()),Object.values(t.getFields()));for(const t of r.added)(0,h.Wd)(t)?n.push({type:pt.REQUIRED_INPUT_FIELD_ADDED,description:`A required field ${t.name} on input type ${e.name} was added.`}):n.push({type:ht.OPTIONAL_INPUT_FIELD_ADDED,description:`An optional field ${t.name} on input type ${e.name} was added.`});for(const t of r.removed)n.push({type:pt.FIELD_REMOVED,description:`${e.name}.${t.name} was removed.`});for(const[t,i]of r.persisted)Nt(t.type,i.type)||n.push({type:pt.FIELD_CHANGED_KIND,description:`${e.name}.${t.name} changed type from ${String(t.type)} to ${String(i.type)}.`});return n}function Tt(e,t){const n=[],r=It(e.getTypes(),t.getTypes());for(const t of r.added)n.push({type:ht.TYPE_ADDED_TO_UNION,description:`${t.name} was added to union type ${e.name}.`});for(const t of r.removed)n.push({type:pt.TYPE_REMOVED_FROM_UNION,description:`${t.name} was removed from union type ${e.name}.`});return n}function wt(e,t){const n=[],r=It(e.getValues(),t.getValues());for(const t of r.added)n.push({type:ht.VALUE_ADDED_TO_ENUM,description:`${t.name} was added to enum type ${e.name}.`});for(const t of r.removed)n.push({type:pt.VALUE_REMOVED_FROM_ENUM,description:`${t.name} was removed from enum type ${e.name}.`});return n}function Ct(e,t){const n=[],r=It(e.getInterfaces(),t.getInterfaces());for(const t of r.added)n.push({type:ht.IMPLEMENTED_INTERFACE_ADDED,description:`${t.name} added to interfaces implemented by ${e.name}.`});for(const t of r.removed)n.push({type:pt.IMPLEMENTED_INTERFACE_REMOVED,description:`${e.name} no longer implements interface ${t.name}.`});return n}function St(e,t){const n=[],r=It(Object.values(e.getFields()),Object.values(t.getFields()));for(const t of r.removed)n.push({type:pt.FIELD_REMOVED,description:`${e.name}.${t.name} was removed.`});for(const[t,i]of r.persisted)n.push(...xt(e,t,i)),kt(t.type,i.type)||n.push({type:pt.FIELD_CHANGED_KIND,description:`${e.name}.${t.name} changed type from ${String(t.type)} to ${String(i.type)}.`});return n}function xt(e,t,n){const r=[],i=It(t.args,n.args);for(const n of i.removed)r.push({type:pt.ARG_REMOVED,description:`${e.name}.${t.name} arg ${n.name} was removed.`});for(const[n,o]of i.persisted)if(Nt(n.type,o.type)){if(void 0!==n.defaultValue)if(void 0===o.defaultValue)r.push({type:ht.ARG_DEFAULT_VALUE_CHANGE,description:`${e.name}.${t.name} arg ${n.name} defaultValue was removed.`});else{const i=Ot(n.defaultValue,n.type),a=Ot(o.defaultValue,o.type);i!==a&&r.push({type:ht.ARG_DEFAULT_VALUE_CHANGE,description:`${e.name}.${t.name} arg ${n.name} has changed defaultValue from ${i} to ${a}.`})}}else r.push({type:pt.ARG_CHANGED_KIND,description:`${e.name}.${t.name} arg ${n.name} has changed type from ${String(n.type)} to ${String(o.type)}.`});for(const n of i.added)(0,h.dK)(n)?r.push({type:pt.REQUIRED_ARG_ADDED,description:`A required arg ${n.name} on ${e.name}.${t.name} was added.`}):r.push({type:ht.OPTIONAL_ARG_ADDED,description:`An optional arg ${n.name} on ${e.name}.${t.name} was added.`});return r}function kt(e,t){return(0,h.HG)(e)?(0,h.HG)(t)&&kt(e.ofType,t.ofType)||(0,h.zM)(t)&&kt(e,t.ofType):(0,h.zM)(e)?(0,h.zM)(t)&&kt(e.ofType,t.ofType):(0,h.Zs)(t)&&e.name===t.name||(0,h.zM)(t)&&kt(e,t.ofType)}function Nt(e,t){return(0,h.HG)(e)?(0,h.HG)(t)&&Nt(e.ofType,t.ofType):(0,h.zM)(e)?(0,h.zM)(t)&&Nt(e.ofType,t.ofType)||!(0,h.zM)(t)&&Nt(e.ofType,t):(0,h.Zs)(t)&&e.name===t.name}function _t(e){return(0,h.KA)(e)?"a Scalar type":(0,h.lp)(e)?"an Object type":(0,h.oT)(e)?"an Interface type":(0,h.EN)(e)?"a Union type":(0,h.EM)(e)?"an Enum type":(0,h.hL)(e)?"an Input type":void(0,M.k)(!1,"Unexpected type: "+(0,xe.X)(e))}function Ot(e,t){const n=(0,Ge.J)(e,t);return null!=n||(0,M.k)(!1),(0,k.S)((0,ft.n)(n))}function It(e,t){const n=[],r=[],i=[],o=(0,Ie.P)(e,(({name:e})=>e)),a=(0,Ie.P)(t,(({name:e})=>e));for(const t of e){const e=a[t.name];void 0===e?r.push(t):i.push([t,e])}for(const e of t)void 0===o[e.name]&&n.push(e);return{added:n,persisted:i,removed:r}}!function(e){e.TYPE_REMOVED="TYPE_REMOVED",e.TYPE_CHANGED_KIND="TYPE_CHANGED_KIND",e.TYPE_REMOVED_FROM_UNION="TYPE_REMOVED_FROM_UNION",e.VALUE_REMOVED_FROM_ENUM="VALUE_REMOVED_FROM_ENUM",e.REQUIRED_INPUT_FIELD_ADDED="REQUIRED_INPUT_FIELD_ADDED",e.IMPLEMENTED_INTERFACE_REMOVED="IMPLEMENTED_INTERFACE_REMOVED",e.FIELD_REMOVED="FIELD_REMOVED",e.FIELD_CHANGED_KIND="FIELD_CHANGED_KIND",e.REQUIRED_ARG_ADDED="REQUIRED_ARG_ADDED",e.ARG_REMOVED="ARG_REMOVED",e.ARG_CHANGED_KIND="ARG_CHANGED_KIND",e.DIRECTIVE_REMOVED="DIRECTIVE_REMOVED",e.DIRECTIVE_ARG_REMOVED="DIRECTIVE_ARG_REMOVED",e.REQUIRED_DIRECTIVE_ARG_ADDED="REQUIRED_DIRECTIVE_ARG_ADDED",e.DIRECTIVE_REPEATABLE_REMOVED="DIRECTIVE_REPEATABLE_REMOVED",e.DIRECTIVE_LOCATION_REMOVED="DIRECTIVE_LOCATION_REMOVED"}(pt||(pt={})),function(e){e.VALUE_ADDED_TO_ENUM="VALUE_ADDED_TO_ENUM",e.TYPE_ADDED_TO_UNION="TYPE_ADDED_TO_UNION",e.OPTIONAL_INPUT_FIELD_ADDED="OPTIONAL_INPUT_FIELD_ADDED",e.OPTIONAL_ARG_ADDED="OPTIONAL_ARG_ADDED",e.IMPLEMENTED_INTERFACE_ADDED="IMPLEMENTED_INTERFACE_ADDED",e.ARG_DEFAULT_VALUE_CHANGE="ARG_DEFAULT_VALUE_CHANGE"}(ht||(ht={}));var Dt=n(9458),Lt=n(5998),At=n(9426),Mt=n(1409),Rt=n(5925),Ft=n(2984)},9878:function(e,t,n){"use strict";function r(e,t,n){return{prev:e,key:t,typename:n}}function i(e){const t=[];let n=e;for(;n;)t.push(n.key),n=n.prev;return t.reverse()}n.d(t,{N:function(){return i},Q:function(){return r}})},1172:function(e,t,n){"use strict";function r(e,t){if(!Boolean(e))throw new Error(t)}n.d(t,{a:function(){return r}})},8063:function(e,t,n){"use strict";n.d(t,{l:function(){return i}});const r=5;function i(e,t){const[n,i]=t?[e,t]:[void 0,e];let o=" Did you mean ";n&&(o+=n+" ");const a=i.map((e=>`"${e}"`));switch(a.length){case 0:return"";case 1:return o+a[0]+"?";case 2:return o+a[0]+" or "+a[1]+"?"}const s=a.slice(0,r),l=s.pop();return o+s.join(", ")+", or "+l+"?"}},5839:function(e,t,n){"use strict";function r(e,t){const n=new Map;for(const r of e){const e=t(r),i=n.get(e);void 0===i?n.set(e,[r]):i.push(r)}return n}n.d(t,{v:function(){return r}})},5648:function(e,t,n){"use strict";n.d(t,{X:function(){return o}});const r=10,i=2;function o(e){return a(e,[])}function a(e,t){switch(typeof e){case"string":return JSON.stringify(e);case"function":return e.name?`[function ${e.name}]`:"[function]";case"object":return function(e,t){if(null===e)return"null";if(t.includes(e))return"[Circular]";const n=[...t,e];if(function(e){return"function"==typeof e.toJSON}(e)){const t=e.toJSON();if(t!==e)return"string"==typeof t?t:a(t,n)}else if(Array.isArray(e))return function(e,t){if(0===e.length)return"[]";if(t.length>i)return"[Array]";const n=Math.min(r,e.length),o=e.length-n,s=[];for(let r=0;r1&&s.push(`... ${o} more items`),"["+s.join(", ")+"]"}(e,n);return function(e,t){const n=Object.entries(e);if(0===n.length)return"{}";if(t.length>i)return"["+function(e){const t=Object.prototype.toString.call(e).replace(/^\[object /,"").replace(/]$/,"");if("Object"===t&&"function"==typeof e.constructor){const t=e.constructor.name;if("string"==typeof t&&""!==t)return t}return t}(e)+"]";const r=n.map((([e,n])=>e+": "+a(n,t)));return"{ "+r.join(", ")+" }"}(e,n)}(e,t);default:return String(e)}}},1513:function(e,t,n){"use strict";n.d(t,{n:function(){return r}});const r=function(e,t){return e instanceof t}},5052:function(e,t,n){"use strict";function r(e,t){if(!Boolean(e))throw new Error(null!=t?t:"Unexpected invariant triggered.")}n.d(t,{k:function(){return r}})},2910:function(e,t,n){"use strict";function r(e){return"object"==typeof e&&"function"==typeof(null==e?void 0:e[Symbol.iterator])}n.d(t,{i:function(){return r}})},1315:function(e,t,n){"use strict";function r(e){return"object"==typeof e&&null!==e}n.d(t,{y:function(){return r}})},1864:function(e,t,n){"use strict";function r(e){return"function"==typeof(null==e?void 0:e.then)}n.d(t,{t:function(){return r}})},9815:function(e,t,n){"use strict";function r(e,t){const n=Object.create(null);for(const r of e)n[t(r)]=r;return n}n.d(t,{P:function(){return r}})},8240:function(e,t,n){"use strict";function r(e,t,n){const r=Object.create(null);for(const i of e)r[t(i)]=n(i);return r}n.d(t,{w:function(){return r}})},9997:function(e,t,n){"use strict";function r(e,t){const n=Object.create(null);for(const r of Object.keys(e))n[r]=t(e[r],r);return n}n.d(t,{j:function(){return r}})},6625:function(e,t,n){"use strict";function r(e,t){let n=0,r=0;for(;n0);let u=0;do{++r,u=10*u+s-i,s=t.charCodeAt(r)}while(a(s)&&u>0);if(lu)return 1}else{if(os)return 1;++n,++r}}return e.length-t.length}n.d(t,{K:function(){return r}});const i=48,o=57;function a(e){return!isNaN(e)&&i<=e&&e<=o}},4987:function(e,t,n){"use strict";function r(e){return e.map((e=>"number"==typeof e?"["+e.toString()+"]":"."+e)).join("")}n.d(t,{F:function(){return r}})},3492:function(e,t,n){"use strict";n.d(t,{D:function(){return i}});var r=n(6625);function i(e,t){const n=Object.create(null),i=new o(e),a=Math.floor(.4*e.length)+1;for(const e of t){const t=i.measure(e,a);void 0!==t&&(n[e]=t)}return Object.keys(n).sort(((e,t)=>{const i=n[e]-n[t];return 0!==i?i:(0,r.K)(e,t)}))}class o{constructor(e){this._input=e,this._inputLowerCase=e.toLowerCase(),this._inputArray=a(this._inputLowerCase),this._rows=[new Array(e.length+1).fill(0),new Array(e.length+1).fill(0),new Array(e.length+1).fill(0)]}measure(e,t){if(this._input===e)return 0;const n=e.toLowerCase();if(this._inputLowerCase===n)return 1;let r=a(n),i=this._inputArray;if(r.lengtht)return;const l=this._rows;for(let e=0;e<=s;e++)l[0][e]=e;for(let e=1;e<=o;e++){const n=l[(e-1)%3],o=l[e%3];let a=o[0]=e;for(let t=1;t<=s;t++){const s=r[e-1]===i[t-1]?0:1;let u=Math.min(n[t]+1,o[t-1]+1,n[t-1]+s);if(e>1&&t>1&&r[e-1]===i[t-2]&&r[e-2]===i[t-1]){const n=l[(e-2)%3][t-2];u=Math.min(u,n+1)}ut)return}const u=l[o%3][s];return u<=t?u:void 0}}function a(e){const t=e.length,n=new Array(t);for(let r=0;r0===t?e:e.slice(n))).slice(null!==(t=r)&&void 0!==t?t:0,i+1)}function o(e){let t=0;for(;t1&&i.slice(1).every((e=>0===e.length||(0,r.FD)(e.charCodeAt(0)))),s=n.endsWith('\\"""'),l=e.endsWith('"')&&!s,u=e.endsWith("\\"),c=l||u,d=!(null!=t&&t.minimize)&&(!o||e.length>70||c||a||s);let f="";const p=o&&(0,r.FD)(e.charCodeAt(0));return(d&&!p||a)&&(f+="\n"),f+=n,(d||c)&&(f+="\n"),'"""'+f+'"""'}},1117:function(e,t,n){"use strict";function r(e){return 9===e||32===e}function i(e){return e>=48&&e<=57}function o(e){return e>=97&&e<=122||e>=65&&e<=90}function a(e){return o(e)||95===e}function s(e){return o(e)||i(e)||95===e}n.d(t,{FD:function(){return r},HQ:function(){return s},LQ:function(){return a},X1:function(){return i}})},3140:function(e,t,n){"use strict";let r;n.d(t,{B:function(){return r}}),function(e){e.QUERY="QUERY",e.MUTATION="MUTATION",e.SUBSCRIPTION="SUBSCRIPTION",e.FIELD="FIELD",e.FRAGMENT_DEFINITION="FRAGMENT_DEFINITION",e.FRAGMENT_SPREAD="FRAGMENT_SPREAD",e.INLINE_FRAGMENT="INLINE_FRAGMENT",e.VARIABLE_DEFINITION="VARIABLE_DEFINITION",e.SCHEMA="SCHEMA",e.SCALAR="SCALAR",e.OBJECT="OBJECT",e.FIELD_DEFINITION="FIELD_DEFINITION",e.ARGUMENT_DEFINITION="ARGUMENT_DEFINITION",e.INTERFACE="INTERFACE",e.UNION="UNION",e.ENUM="ENUM",e.ENUM_VALUE="ENUM_VALUE",e.INPUT_OBJECT="INPUT_OBJECT",e.INPUT_FIELD_DEFINITION="INPUT_FIELD_DEFINITION"}(r||(r={}))},3830:function(e,t,n){"use strict";let r;n.d(t,{h:function(){return r}}),function(e){e.NAME="Name",e.DOCUMENT="Document",e.OPERATION_DEFINITION="OperationDefinition",e.VARIABLE_DEFINITION="VariableDefinition",e.SELECTION_SET="SelectionSet",e.FIELD="Field",e.ARGUMENT="Argument",e.FRAGMENT_SPREAD="FragmentSpread",e.INLINE_FRAGMENT="InlineFragment",e.FRAGMENT_DEFINITION="FragmentDefinition",e.VARIABLE="Variable",e.INT="IntValue",e.FLOAT="FloatValue",e.STRING="StringValue",e.BOOLEAN="BooleanValue",e.NULL="NullValue",e.ENUM="EnumValue",e.LIST="ListValue",e.OBJECT="ObjectValue",e.OBJECT_FIELD="ObjectField",e.DIRECTIVE="Directive",e.NAMED_TYPE="NamedType",e.LIST_TYPE="ListType",e.NON_NULL_TYPE="NonNullType",e.SCHEMA_DEFINITION="SchemaDefinition",e.OPERATION_TYPE_DEFINITION="OperationTypeDefinition",e.SCALAR_TYPE_DEFINITION="ScalarTypeDefinition",e.OBJECT_TYPE_DEFINITION="ObjectTypeDefinition",e.FIELD_DEFINITION="FieldDefinition",e.INPUT_VALUE_DEFINITION="InputValueDefinition",e.INTERFACE_TYPE_DEFINITION="InterfaceTypeDefinition",e.UNION_TYPE_DEFINITION="UnionTypeDefinition",e.ENUM_TYPE_DEFINITION="EnumTypeDefinition",e.ENUM_VALUE_DEFINITION="EnumValueDefinition",e.INPUT_OBJECT_TYPE_DEFINITION="InputObjectTypeDefinition",e.DIRECTIVE_DEFINITION="DirectiveDefinition",e.SCHEMA_EXTENSION="SchemaExtension",e.SCALAR_TYPE_EXTENSION="ScalarTypeExtension",e.OBJECT_TYPE_EXTENSION="ObjectTypeExtension",e.INTERFACE_TYPE_EXTENSION="InterfaceTypeExtension",e.UNION_TYPE_EXTENSION="UnionTypeExtension",e.ENUM_TYPE_EXTENSION="EnumTypeExtension",e.INPUT_OBJECT_TYPE_EXTENSION="InputObjectTypeExtension"}(r||(r={}))},9230:function(e,t,n){"use strict";n.d(t,{h:function(){return l},u:function(){return u}});var r=n(2024),i=n(3526),o=n(6303),a=n(1117),s=n(5685);class l{constructor(e){const t=new i.WU(s.T.SOF,0,0,0,0);this.source=e,this.lastToken=t,this.token=t,this.line=1,this.lineStart=0}get[Symbol.toStringTag](){return"Lexer"}advance(){return this.lastToken=this.token,this.token=this.lookahead()}lookahead(){let e=this.token;if(e.kind!==s.T.EOF)do{if(e.next)e=e.next;else{const t=g(this,e.end);e.next=t,t.prev=e,e=t}}while(e.kind===s.T.COMMENT);return e}}function u(e){return e===s.T.BANG||e===s.T.DOLLAR||e===s.T.AMP||e===s.T.PAREN_L||e===s.T.PAREN_R||e===s.T.SPREAD||e===s.T.COLON||e===s.T.EQUALS||e===s.T.AT||e===s.T.BRACKET_L||e===s.T.BRACKET_R||e===s.T.BRACE_L||e===s.T.PIPE||e===s.T.BRACE_R}function c(e){return e>=0&&e<=55295||e>=57344&&e<=1114111}function d(e,t){return f(e.charCodeAt(t))&&p(e.charCodeAt(t+1))}function f(e){return e>=55296&&e<=56319}function p(e){return e>=56320&&e<=57343}function h(e,t){const n=e.source.body.codePointAt(t);if(void 0===n)return s.T.EOF;if(n>=32&&n<=126){const e=String.fromCodePoint(n);return'"'===e?"'\"'":`"${e}"`}return"U+"+n.toString(16).toUpperCase().padStart(4,"0")}function m(e,t,n,r,o){const a=e.line,s=1+n-e.lineStart;return new i.WU(t,n,r,a,s,o)}function g(e,t){const n=e.source.body,i=n.length;let o=t;for(;o=48&&e<=57?e-48:e>=65&&e<=70?e-55:e>=97&&e<=102?e-87:-1}function x(e,t){const n=e.source.body;switch(n.charCodeAt(t+1)){case 34:return{value:'"',size:2};case 92:return{value:"\\",size:2};case 47:return{value:"/",size:2};case 98:return{value:"\b",size:2};case 102:return{value:"\f",size:2};case 110:return{value:"\n",size:2};case 114:return{value:"\r",size:2};case 116:return{value:"\t",size:2}}throw(0,r.h)(e.source,t,`Invalid character escape sequence: "${n.slice(t,t+2)}".`)}function k(e,t){const n=e.source.body,i=n.length;let a=e.lineStart,l=t+3,u=l,f="";const p=[];for(;l=t)break;n=a.index+a[0].length,o+=1}return{line:o,column:t+1-n}}},953:function(e,t,n){"use strict";n.d(t,{H2:function(){return d},Qc:function(){return c},gZ:function(){return p},tl:function(){return f}});var r=n(2024),i=n(3526),o=n(3140),a=n(3830),s=n(9230),l=n(4680),u=n(5685);function c(e,t){return new h(e,t).parseDocument()}function d(e,t){const n=new h(e,t);n.expectToken(u.T.SOF);const r=n.parseValueLiteral(!1);return n.expectToken(u.T.EOF),r}function f(e,t){const n=new h(e,t);n.expectToken(u.T.SOF);const r=n.parseConstValueLiteral();return n.expectToken(u.T.EOF),r}function p(e,t){const n=new h(e,t);n.expectToken(u.T.SOF);const r=n.parseTypeReference();return n.expectToken(u.T.EOF),r}class h{constructor(e,t){const n=(0,l.T)(e)?e:new l.H(e);this._lexer=new s.h(n),this._options=t}parseName(){const e=this.expectToken(u.T.NAME);return this.node(e,{kind:a.h.NAME,value:e.value})}parseDocument(){return this.node(this._lexer.token,{kind:a.h.DOCUMENT,definitions:this.many(u.T.SOF,this.parseDefinition,u.T.EOF)})}parseDefinition(){if(this.peek(u.T.BRACE_L))return this.parseOperationDefinition();const e=this.peekDescription(),t=e?this._lexer.lookahead():this._lexer.token;if(t.kind===u.T.NAME){switch(t.value){case"schema":return this.parseSchemaDefinition();case"scalar":return this.parseScalarTypeDefinition();case"type":return this.parseObjectTypeDefinition();case"interface":return this.parseInterfaceTypeDefinition();case"union":return this.parseUnionTypeDefinition();case"enum":return this.parseEnumTypeDefinition();case"input":return this.parseInputObjectTypeDefinition();case"directive":return this.parseDirectiveDefinition()}if(e)throw(0,r.h)(this._lexer.source,this._lexer.token.start,"Unexpected description, descriptions are supported only on type definitions.");switch(t.value){case"query":case"mutation":case"subscription":return this.parseOperationDefinition();case"fragment":return this.parseFragmentDefinition();case"extend":return this.parseTypeSystemExtension()}}throw this.unexpected(t)}parseOperationDefinition(){const e=this._lexer.token;if(this.peek(u.T.BRACE_L))return this.node(e,{kind:a.h.OPERATION_DEFINITION,operation:i.ku.QUERY,name:void 0,variableDefinitions:[],directives:[],selectionSet:this.parseSelectionSet()});const t=this.parseOperationType();let n;return this.peek(u.T.NAME)&&(n=this.parseName()),this.node(e,{kind:a.h.OPERATION_DEFINITION,operation:t,name:n,variableDefinitions:this.parseVariableDefinitions(),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet()})}parseOperationType(){const e=this.expectToken(u.T.NAME);switch(e.value){case"query":return i.ku.QUERY;case"mutation":return i.ku.MUTATION;case"subscription":return i.ku.SUBSCRIPTION}throw this.unexpected(e)}parseVariableDefinitions(){return this.optionalMany(u.T.PAREN_L,this.parseVariableDefinition,u.T.PAREN_R)}parseVariableDefinition(){return this.node(this._lexer.token,{kind:a.h.VARIABLE_DEFINITION,variable:this.parseVariable(),type:(this.expectToken(u.T.COLON),this.parseTypeReference()),defaultValue:this.expectOptionalToken(u.T.EQUALS)?this.parseConstValueLiteral():void 0,directives:this.parseConstDirectives()})}parseVariable(){const e=this._lexer.token;return this.expectToken(u.T.DOLLAR),this.node(e,{kind:a.h.VARIABLE,name:this.parseName()})}parseSelectionSet(){return this.node(this._lexer.token,{kind:a.h.SELECTION_SET,selections:this.many(u.T.BRACE_L,this.parseSelection,u.T.BRACE_R)})}parseSelection(){return this.peek(u.T.SPREAD)?this.parseFragment():this.parseField()}parseField(){const e=this._lexer.token,t=this.parseName();let n,r;return this.expectOptionalToken(u.T.COLON)?(n=t,r=this.parseName()):r=t,this.node(e,{kind:a.h.FIELD,alias:n,name:r,arguments:this.parseArguments(!1),directives:this.parseDirectives(!1),selectionSet:this.peek(u.T.BRACE_L)?this.parseSelectionSet():void 0})}parseArguments(e){const t=e?this.parseConstArgument:this.parseArgument;return this.optionalMany(u.T.PAREN_L,t,u.T.PAREN_R)}parseArgument(e=!1){const t=this._lexer.token,n=this.parseName();return this.expectToken(u.T.COLON),this.node(t,{kind:a.h.ARGUMENT,name:n,value:this.parseValueLiteral(e)})}parseConstArgument(){return this.parseArgument(!0)}parseFragment(){const e=this._lexer.token;this.expectToken(u.T.SPREAD);const t=this.expectOptionalKeyword("on");return!t&&this.peek(u.T.NAME)?this.node(e,{kind:a.h.FRAGMENT_SPREAD,name:this.parseFragmentName(),directives:this.parseDirectives(!1)}):this.node(e,{kind:a.h.INLINE_FRAGMENT,typeCondition:t?this.parseNamedType():void 0,directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet()})}parseFragmentDefinition(){var e;const t=this._lexer.token;return this.expectKeyword("fragment"),!0===(null===(e=this._options)||void 0===e?void 0:e.allowLegacyFragmentVariables)?this.node(t,{kind:a.h.FRAGMENT_DEFINITION,name:this.parseFragmentName(),variableDefinitions:this.parseVariableDefinitions(),typeCondition:(this.expectKeyword("on"),this.parseNamedType()),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet()}):this.node(t,{kind:a.h.FRAGMENT_DEFINITION,name:this.parseFragmentName(),typeCondition:(this.expectKeyword("on"),this.parseNamedType()),directives:this.parseDirectives(!1),selectionSet:this.parseSelectionSet()})}parseFragmentName(){if("on"===this._lexer.token.value)throw this.unexpected();return this.parseName()}parseValueLiteral(e){const t=this._lexer.token;switch(t.kind){case u.T.BRACKET_L:return this.parseList(e);case u.T.BRACE_L:return this.parseObject(e);case u.T.INT:return this._lexer.advance(),this.node(t,{kind:a.h.INT,value:t.value});case u.T.FLOAT:return this._lexer.advance(),this.node(t,{kind:a.h.FLOAT,value:t.value});case u.T.STRING:case u.T.BLOCK_STRING:return this.parseStringLiteral();case u.T.NAME:switch(this._lexer.advance(),t.value){case"true":return this.node(t,{kind:a.h.BOOLEAN,value:!0});case"false":return this.node(t,{kind:a.h.BOOLEAN,value:!1});case"null":return this.node(t,{kind:a.h.NULL});default:return this.node(t,{kind:a.h.ENUM,value:t.value})}case u.T.DOLLAR:if(e){if(this.expectToken(u.T.DOLLAR),this._lexer.token.kind===u.T.NAME){const e=this._lexer.token.value;throw(0,r.h)(this._lexer.source,t.start,`Unexpected variable "$${e}" in constant value.`)}throw this.unexpected(t)}return this.parseVariable();default:throw this.unexpected()}}parseConstValueLiteral(){return this.parseValueLiteral(!0)}parseStringLiteral(){const e=this._lexer.token;return this._lexer.advance(),this.node(e,{kind:a.h.STRING,value:e.value,block:e.kind===u.T.BLOCK_STRING})}parseList(e){return this.node(this._lexer.token,{kind:a.h.LIST,values:this.any(u.T.BRACKET_L,(()=>this.parseValueLiteral(e)),u.T.BRACKET_R)})}parseObject(e){return this.node(this._lexer.token,{kind:a.h.OBJECT,fields:this.any(u.T.BRACE_L,(()=>this.parseObjectField(e)),u.T.BRACE_R)})}parseObjectField(e){const t=this._lexer.token,n=this.parseName();return this.expectToken(u.T.COLON),this.node(t,{kind:a.h.OBJECT_FIELD,name:n,value:this.parseValueLiteral(e)})}parseDirectives(e){const t=[];for(;this.peek(u.T.AT);)t.push(this.parseDirective(e));return t}parseConstDirectives(){return this.parseDirectives(!0)}parseDirective(e){const t=this._lexer.token;return this.expectToken(u.T.AT),this.node(t,{kind:a.h.DIRECTIVE,name:this.parseName(),arguments:this.parseArguments(e)})}parseTypeReference(){const e=this._lexer.token;let t;if(this.expectOptionalToken(u.T.BRACKET_L)){const n=this.parseTypeReference();this.expectToken(u.T.BRACKET_R),t=this.node(e,{kind:a.h.LIST_TYPE,type:n})}else t=this.parseNamedType();return this.expectOptionalToken(u.T.BANG)?this.node(e,{kind:a.h.NON_NULL_TYPE,type:t}):t}parseNamedType(){return this.node(this._lexer.token,{kind:a.h.NAMED_TYPE,name:this.parseName()})}peekDescription(){return this.peek(u.T.STRING)||this.peek(u.T.BLOCK_STRING)}parseDescription(){if(this.peekDescription())return this.parseStringLiteral()}parseSchemaDefinition(){const e=this._lexer.token,t=this.parseDescription();this.expectKeyword("schema");const n=this.parseConstDirectives(),r=this.many(u.T.BRACE_L,this.parseOperationTypeDefinition,u.T.BRACE_R);return this.node(e,{kind:a.h.SCHEMA_DEFINITION,description:t,directives:n,operationTypes:r})}parseOperationTypeDefinition(){const e=this._lexer.token,t=this.parseOperationType();this.expectToken(u.T.COLON);const n=this.parseNamedType();return this.node(e,{kind:a.h.OPERATION_TYPE_DEFINITION,operation:t,type:n})}parseScalarTypeDefinition(){const e=this._lexer.token,t=this.parseDescription();this.expectKeyword("scalar");const n=this.parseName(),r=this.parseConstDirectives();return this.node(e,{kind:a.h.SCALAR_TYPE_DEFINITION,description:t,name:n,directives:r})}parseObjectTypeDefinition(){const e=this._lexer.token,t=this.parseDescription();this.expectKeyword("type");const n=this.parseName(),r=this.parseImplementsInterfaces(),i=this.parseConstDirectives(),o=this.parseFieldsDefinition();return this.node(e,{kind:a.h.OBJECT_TYPE_DEFINITION,description:t,name:n,interfaces:r,directives:i,fields:o})}parseImplementsInterfaces(){return this.expectOptionalKeyword("implements")?this.delimitedMany(u.T.AMP,this.parseNamedType):[]}parseFieldsDefinition(){return this.optionalMany(u.T.BRACE_L,this.parseFieldDefinition,u.T.BRACE_R)}parseFieldDefinition(){const e=this._lexer.token,t=this.parseDescription(),n=this.parseName(),r=this.parseArgumentDefs();this.expectToken(u.T.COLON);const i=this.parseTypeReference(),o=this.parseConstDirectives();return this.node(e,{kind:a.h.FIELD_DEFINITION,description:t,name:n,arguments:r,type:i,directives:o})}parseArgumentDefs(){return this.optionalMany(u.T.PAREN_L,this.parseInputValueDef,u.T.PAREN_R)}parseInputValueDef(){const e=this._lexer.token,t=this.parseDescription(),n=this.parseName();this.expectToken(u.T.COLON);const r=this.parseTypeReference();let i;this.expectOptionalToken(u.T.EQUALS)&&(i=this.parseConstValueLiteral());const o=this.parseConstDirectives();return this.node(e,{kind:a.h.INPUT_VALUE_DEFINITION,description:t,name:n,type:r,defaultValue:i,directives:o})}parseInterfaceTypeDefinition(){const e=this._lexer.token,t=this.parseDescription();this.expectKeyword("interface");const n=this.parseName(),r=this.parseImplementsInterfaces(),i=this.parseConstDirectives(),o=this.parseFieldsDefinition();return this.node(e,{kind:a.h.INTERFACE_TYPE_DEFINITION,description:t,name:n,interfaces:r,directives:i,fields:o})}parseUnionTypeDefinition(){const e=this._lexer.token,t=this.parseDescription();this.expectKeyword("union");const n=this.parseName(),r=this.parseConstDirectives(),i=this.parseUnionMemberTypes();return this.node(e,{kind:a.h.UNION_TYPE_DEFINITION,description:t,name:n,directives:r,types:i})}parseUnionMemberTypes(){return this.expectOptionalToken(u.T.EQUALS)?this.delimitedMany(u.T.PIPE,this.parseNamedType):[]}parseEnumTypeDefinition(){const e=this._lexer.token,t=this.parseDescription();this.expectKeyword("enum");const n=this.parseName(),r=this.parseConstDirectives(),i=this.parseEnumValuesDefinition();return this.node(e,{kind:a.h.ENUM_TYPE_DEFINITION,description:t,name:n,directives:r,values:i})}parseEnumValuesDefinition(){return this.optionalMany(u.T.BRACE_L,this.parseEnumValueDefinition,u.T.BRACE_R)}parseEnumValueDefinition(){const e=this._lexer.token,t=this.parseDescription(),n=this.parseEnumValueName(),r=this.parseConstDirectives();return this.node(e,{kind:a.h.ENUM_VALUE_DEFINITION,description:t,name:n,directives:r})}parseEnumValueName(){if("true"===this._lexer.token.value||"false"===this._lexer.token.value||"null"===this._lexer.token.value)throw(0,r.h)(this._lexer.source,this._lexer.token.start,`${m(this._lexer.token)} is reserved and cannot be used for an enum value.`);return this.parseName()}parseInputObjectTypeDefinition(){const e=this._lexer.token,t=this.parseDescription();this.expectKeyword("input");const n=this.parseName(),r=this.parseConstDirectives(),i=this.parseInputFieldsDefinition();return this.node(e,{kind:a.h.INPUT_OBJECT_TYPE_DEFINITION,description:t,name:n,directives:r,fields:i})}parseInputFieldsDefinition(){return this.optionalMany(u.T.BRACE_L,this.parseInputValueDef,u.T.BRACE_R)}parseTypeSystemExtension(){const e=this._lexer.lookahead();if(e.kind===u.T.NAME)switch(e.value){case"schema":return this.parseSchemaExtension();case"scalar":return this.parseScalarTypeExtension();case"type":return this.parseObjectTypeExtension();case"interface":return this.parseInterfaceTypeExtension();case"union":return this.parseUnionTypeExtension();case"enum":return this.parseEnumTypeExtension();case"input":return this.parseInputObjectTypeExtension()}throw this.unexpected(e)}parseSchemaExtension(){const e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("schema");const t=this.parseConstDirectives(),n=this.optionalMany(u.T.BRACE_L,this.parseOperationTypeDefinition,u.T.BRACE_R);if(0===t.length&&0===n.length)throw this.unexpected();return this.node(e,{kind:a.h.SCHEMA_EXTENSION,directives:t,operationTypes:n})}parseScalarTypeExtension(){const e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("scalar");const t=this.parseName(),n=this.parseConstDirectives();if(0===n.length)throw this.unexpected();return this.node(e,{kind:a.h.SCALAR_TYPE_EXTENSION,name:t,directives:n})}parseObjectTypeExtension(){const e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("type");const t=this.parseName(),n=this.parseImplementsInterfaces(),r=this.parseConstDirectives(),i=this.parseFieldsDefinition();if(0===n.length&&0===r.length&&0===i.length)throw this.unexpected();return this.node(e,{kind:a.h.OBJECT_TYPE_EXTENSION,name:t,interfaces:n,directives:r,fields:i})}parseInterfaceTypeExtension(){const e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("interface");const t=this.parseName(),n=this.parseImplementsInterfaces(),r=this.parseConstDirectives(),i=this.parseFieldsDefinition();if(0===n.length&&0===r.length&&0===i.length)throw this.unexpected();return this.node(e,{kind:a.h.INTERFACE_TYPE_EXTENSION,name:t,interfaces:n,directives:r,fields:i})}parseUnionTypeExtension(){const e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("union");const t=this.parseName(),n=this.parseConstDirectives(),r=this.parseUnionMemberTypes();if(0===n.length&&0===r.length)throw this.unexpected();return this.node(e,{kind:a.h.UNION_TYPE_EXTENSION,name:t,directives:n,types:r})}parseEnumTypeExtension(){const e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("enum");const t=this.parseName(),n=this.parseConstDirectives(),r=this.parseEnumValuesDefinition();if(0===n.length&&0===r.length)throw this.unexpected();return this.node(e,{kind:a.h.ENUM_TYPE_EXTENSION,name:t,directives:n,values:r})}parseInputObjectTypeExtension(){const e=this._lexer.token;this.expectKeyword("extend"),this.expectKeyword("input");const t=this.parseName(),n=this.parseConstDirectives(),r=this.parseInputFieldsDefinition();if(0===n.length&&0===r.length)throw this.unexpected();return this.node(e,{kind:a.h.INPUT_OBJECT_TYPE_EXTENSION,name:t,directives:n,fields:r})}parseDirectiveDefinition(){const e=this._lexer.token,t=this.parseDescription();this.expectKeyword("directive"),this.expectToken(u.T.AT);const n=this.parseName(),r=this.parseArgumentDefs(),i=this.expectOptionalKeyword("repeatable");this.expectKeyword("on");const o=this.parseDirectiveLocations();return this.node(e,{kind:a.h.DIRECTIVE_DEFINITION,description:t,name:n,arguments:r,repeatable:i,locations:o})}parseDirectiveLocations(){return this.delimitedMany(u.T.PIPE,this.parseDirectiveLocation)}parseDirectiveLocation(){const e=this._lexer.token,t=this.parseName();if(Object.prototype.hasOwnProperty.call(o.B,t.value))return t;throw this.unexpected(e)}node(e,t){var n;return!0!==(null===(n=this._options)||void 0===n?void 0:n.noLocation)&&(t.loc=new i.Ye(e,this._lexer.lastToken,this._lexer.source)),t}peek(e){return this._lexer.token.kind===e}expectToken(e){const t=this._lexer.token;if(t.kind===e)return this._lexer.advance(),t;throw(0,r.h)(this._lexer.source,t.start,`Expected ${g(e)}, found ${m(t)}.`)}expectOptionalToken(e){return this._lexer.token.kind===e&&(this._lexer.advance(),!0)}expectKeyword(e){const t=this._lexer.token;if(t.kind!==u.T.NAME||t.value!==e)throw(0,r.h)(this._lexer.source,t.start,`Expected "${e}", found ${m(t)}.`);this._lexer.advance()}expectOptionalKeyword(e){const t=this._lexer.token;return t.kind===u.T.NAME&&t.value===e&&(this._lexer.advance(),!0)}unexpected(e){const t=null!=e?e:this._lexer.token;return(0,r.h)(this._lexer.source,t.start,`Unexpected ${m(t)}.`)}any(e,t,n){this.expectToken(e);const r=[];for(;!this.expectOptionalToken(n);)r.push(t.call(this));return r}optionalMany(e,t,n){if(this.expectOptionalToken(e)){const e=[];do{e.push(t.call(this))}while(!this.expectOptionalToken(n));return e}return[]}many(e,t,n){this.expectToken(e);const r=[];do{r.push(t.call(this))}while(!this.expectOptionalToken(n));return r}delimitedMany(e,t){this.expectOptionalToken(e);const n=[];do{n.push(t.call(this))}while(this.expectOptionalToken(e));return n}}function m(e){const t=e.value;return g(e.kind)+(null!=t?` "${t}"`:"")}function g(e){return(0,s.u)(e)?`"${e}"`:e}},9615:function(e,t,n){"use strict";n.d(t,{D$:function(){return p},G4:function(){return c},Ir:function(){return i},Of:function(){return l},VB:function(){return u},Wk:function(){return o},aU:function(){return f},nr:function(){return s},pO:function(){return a},zT:function(){return d}});var r=n(3830);function i(e){return o(e)||c(e)||f(e)}function o(e){return e.kind===r.h.OPERATION_DEFINITION||e.kind===r.h.FRAGMENT_DEFINITION}function a(e){return e.kind===r.h.FIELD||e.kind===r.h.FRAGMENT_SPREAD||e.kind===r.h.INLINE_FRAGMENT}function s(e){return e.kind===r.h.VARIABLE||e.kind===r.h.INT||e.kind===r.h.FLOAT||e.kind===r.h.STRING||e.kind===r.h.BOOLEAN||e.kind===r.h.NULL||e.kind===r.h.ENUM||e.kind===r.h.LIST||e.kind===r.h.OBJECT}function l(e){return s(e)&&(e.kind===r.h.LIST?e.values.some(l):e.kind===r.h.OBJECT?e.fields.some((e=>l(e.value))):e.kind!==r.h.VARIABLE)}function u(e){return e.kind===r.h.NAMED_TYPE||e.kind===r.h.LIST_TYPE||e.kind===r.h.NON_NULL_TYPE}function c(e){return e.kind===r.h.SCHEMA_DEFINITION||d(e)||e.kind===r.h.DIRECTIVE_DEFINITION}function d(e){return e.kind===r.h.SCALAR_TYPE_DEFINITION||e.kind===r.h.OBJECT_TYPE_DEFINITION||e.kind===r.h.INTERFACE_TYPE_DEFINITION||e.kind===r.h.UNION_TYPE_DEFINITION||e.kind===r.h.ENUM_TYPE_DEFINITION||e.kind===r.h.INPUT_OBJECT_TYPE_DEFINITION}function f(e){return e.kind===r.h.SCHEMA_EXTENSION||p(e)}function p(e){return e.kind===r.h.SCALAR_TYPE_EXTENSION||e.kind===r.h.OBJECT_TYPE_EXTENSION||e.kind===r.h.INTERFACE_TYPE_EXTENSION||e.kind===r.h.UNION_TYPE_EXTENSION||e.kind===r.h.ENUM_TYPE_EXTENSION||e.kind===r.h.INPUT_OBJECT_TYPE_EXTENSION}},7026:function(e,t,n){"use strict";n.d(t,{Q:function(){return i},z:function(){return o}});var r=n(3302);function i(e){return o(e.source,(0,r.k)(e.source,e.start))}function o(e,t){const n=e.locationOffset.column-1,r="".padStart(n)+e.body,i=t.line-1,o=e.locationOffset.line-1,s=t.line+o,l=1===t.line?n:0,u=t.column+l,c=`${e.name}:${s}:${u}\n`,d=r.split(/\r\n|[\n\r]/g),f=d[i];if(f.length>120){const e=Math.floor(u/80),t=u%80,n=[];for(let e=0;e["|",e])),["|","^".padStart(t)],["|",n[e+1]]])}return c+a([[s-1+" |",d[i-1]],[`${s} |`,f],["|","^".padStart(u)],[`${s+1} |`,d[i+1]]])}function a(e){const t=e.filter((([e,t])=>void 0!==t)),n=Math.max(...t.map((([e])=>e.length)));return t.map((([e,t])=>e.padStart(n)+(t?" "+t:""))).join("\n")}},5895:function(e,t,n){"use strict";n.d(t,{S:function(){return l}});var r=n(6303);const i=/[\x00-\x1f\x22\x5c\x7f-\x9f]/g;function o(e){return a[e.charCodeAt(0)]}const a=["\\u0000","\\u0001","\\u0002","\\u0003","\\u0004","\\u0005","\\u0006","\\u0007","\\b","\\t","\\n","\\u000B","\\f","\\r","\\u000E","\\u000F","\\u0010","\\u0011","\\u0012","\\u0013","\\u0014","\\u0015","\\u0016","\\u0017","\\u0018","\\u0019","\\u001A","\\u001B","\\u001C","\\u001D","\\u001E","\\u001F","","",'\\"',"","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","\\\\","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","\\u007F","\\u0080","\\u0081","\\u0082","\\u0083","\\u0084","\\u0085","\\u0086","\\u0087","\\u0088","\\u0089","\\u008A","\\u008B","\\u008C","\\u008D","\\u008E","\\u008F","\\u0090","\\u0091","\\u0092","\\u0093","\\u0094","\\u0095","\\u0096","\\u0097","\\u0098","\\u0099","\\u009A","\\u009B","\\u009C","\\u009D","\\u009E","\\u009F"];var s=n(9685);function l(e){return(0,s.Vn)(e,u)}const u={Name:{leave:e=>e.value},Variable:{leave:e=>"$"+e.name},Document:{leave:e=>c(e.definitions,"\n\n")},OperationDefinition:{leave(e){const t=f("(",c(e.variableDefinitions,", "),")"),n=c([e.operation,c([e.name,t]),c(e.directives," ")]," ");return("query"===n?"":n+" ")+e.selectionSet}},VariableDefinition:{leave:({variable:e,type:t,defaultValue:n,directives:r})=>e+": "+t+f(" = ",n)+f(" ",c(r," "))},SelectionSet:{leave:({selections:e})=>d(e)},Field:{leave({alias:e,name:t,arguments:n,directives:r,selectionSet:i}){const o=f("",e,": ")+t;let a=o+f("(",c(n,", "),")");return a.length>80&&(a=o+f("(\n",p(c(n,"\n")),"\n)")),c([a,c(r," "),i]," ")}},Argument:{leave:({name:e,value:t})=>e+": "+t},FragmentSpread:{leave:({name:e,directives:t})=>"..."+e+f(" ",c(t," "))},InlineFragment:{leave:({typeCondition:e,directives:t,selectionSet:n})=>c(["...",f("on ",e),c(t," "),n]," ")},FragmentDefinition:{leave:({name:e,typeCondition:t,variableDefinitions:n,directives:r,selectionSet:i})=>`fragment ${e}${f("(",c(n,", "),")")} on ${t} ${f("",c(r," ")," ")}`+i},IntValue:{leave:({value:e})=>e},FloatValue:{leave:({value:e})=>e},StringValue:{leave:({value:e,block:t})=>t?(0,r.LZ)(e):`"${e.replace(i,o)}"`},BooleanValue:{leave:({value:e})=>e?"true":"false"},NullValue:{leave:()=>"null"},EnumValue:{leave:({value:e})=>e},ListValue:{leave:({values:e})=>"["+c(e,", ")+"]"},ObjectValue:{leave:({fields:e})=>"{"+c(e,", ")+"}"},ObjectField:{leave:({name:e,value:t})=>e+": "+t},Directive:{leave:({name:e,arguments:t})=>"@"+e+f("(",c(t,", "),")")},NamedType:{leave:({name:e})=>e},ListType:{leave:({type:e})=>"["+e+"]"},NonNullType:{leave:({type:e})=>e+"!"},SchemaDefinition:{leave:({description:e,directives:t,operationTypes:n})=>f("",e,"\n")+c(["schema",c(t," "),d(n)]," ")},OperationTypeDefinition:{leave:({operation:e,type:t})=>e+": "+t},ScalarTypeDefinition:{leave:({description:e,name:t,directives:n})=>f("",e,"\n")+c(["scalar",t,c(n," ")]," ")},ObjectTypeDefinition:{leave:({description:e,name:t,interfaces:n,directives:r,fields:i})=>f("",e,"\n")+c(["type",t,f("implements ",c(n," & ")),c(r," "),d(i)]," ")},FieldDefinition:{leave:({description:e,name:t,arguments:n,type:r,directives:i})=>f("",e,"\n")+t+(h(n)?f("(\n",p(c(n,"\n")),"\n)"):f("(",c(n,", "),")"))+": "+r+f(" ",c(i," "))},InputValueDefinition:{leave:({description:e,name:t,type:n,defaultValue:r,directives:i})=>f("",e,"\n")+c([t+": "+n,f("= ",r),c(i," ")]," ")},InterfaceTypeDefinition:{leave:({description:e,name:t,interfaces:n,directives:r,fields:i})=>f("",e,"\n")+c(["interface",t,f("implements ",c(n," & ")),c(r," "),d(i)]," ")},UnionTypeDefinition:{leave:({description:e,name:t,directives:n,types:r})=>f("",e,"\n")+c(["union",t,c(n," "),f("= ",c(r," | "))]," ")},EnumTypeDefinition:{leave:({description:e,name:t,directives:n,values:r})=>f("",e,"\n")+c(["enum",t,c(n," "),d(r)]," ")},EnumValueDefinition:{leave:({description:e,name:t,directives:n})=>f("",e,"\n")+c([t,c(n," ")]," ")},InputObjectTypeDefinition:{leave:({description:e,name:t,directives:n,fields:r})=>f("",e,"\n")+c(["input",t,c(n," "),d(r)]," ")},DirectiveDefinition:{leave:({description:e,name:t,arguments:n,repeatable:r,locations:i})=>f("",e,"\n")+"directive @"+t+(h(n)?f("(\n",p(c(n,"\n")),"\n)"):f("(",c(n,", "),")"))+(r?" repeatable":"")+" on "+c(i," | ")},SchemaExtension:{leave:({directives:e,operationTypes:t})=>c(["extend schema",c(e," "),d(t)]," ")},ScalarTypeExtension:{leave:({name:e,directives:t})=>c(["extend scalar",e,c(t," ")]," ")},ObjectTypeExtension:{leave:({name:e,interfaces:t,directives:n,fields:r})=>c(["extend type",e,f("implements ",c(t," & ")),c(n," "),d(r)]," ")},InterfaceTypeExtension:{leave:({name:e,interfaces:t,directives:n,fields:r})=>c(["extend interface",e,f("implements ",c(t," & ")),c(n," "),d(r)]," ")},UnionTypeExtension:{leave:({name:e,directives:t,types:n})=>c(["extend union",e,c(t," "),f("= ",c(n," | "))]," ")},EnumTypeExtension:{leave:({name:e,directives:t,values:n})=>c(["extend enum",e,c(t," "),d(n)]," ")},InputObjectTypeExtension:{leave:({name:e,directives:t,fields:n})=>c(["extend input",e,c(t," "),d(n)]," ")}};function c(e,t=""){var n;return null!==(n=null==e?void 0:e.filter((e=>e)).join(t))&&void 0!==n?n:""}function d(e){return f("{\n",p(c(e,"\n")),"\n}")}function f(e,t,n=""){return null!=t&&""!==t?e+t+n:""}function p(e){return f(" ",e.replace(/\n/g,"\n "))}function h(e){var t;return null!==(t=null==e?void 0:e.some((e=>e.includes("\n"))))&&void 0!==t&&t}},4680:function(e,t,n){"use strict";n.d(t,{H:function(){return a},T:function(){return s}});var r=n(1172),i=n(5648),o=n(1513);class a{constructor(e,t="GraphQL request",n={line:1,column:1}){"string"==typeof e||(0,r.a)(!1,`Body must be a string. Received: ${(0,i.X)(e)}.`),this.body=e,this.name=t,this.locationOffset=n,this.locationOffset.line>0||(0,r.a)(!1,"line in locationOffset is 1-indexed and must be positive."),this.locationOffset.column>0||(0,r.a)(!1,"column in locationOffset is 1-indexed and must be positive.")}get[Symbol.toStringTag](){return"Source"}}function s(e){return(0,o.n)(e,a)}},5685:function(e,t,n){"use strict";let r;n.d(t,{T:function(){return r}}),function(e){e.SOF="",e.EOF="",e.BANG="!",e.DOLLAR="$",e.AMP="&",e.PAREN_L="(",e.PAREN_R=")",e.SPREAD="...",e.COLON=":",e.EQUALS="=",e.AT="@",e.BRACKET_L="[",e.BRACKET_R="]",e.BRACE_L="{",e.PIPE="|",e.BRACE_R="}",e.NAME="Name",e.INT="Int",e.FLOAT="Float",e.STRING="String",e.BLOCK_STRING="BlockString",e.COMMENT="Comment"}(r||(r={}))},9685:function(e,t,n){"use strict";n.d(t,{$_:function(){return s},CK:function(){return d},Eu:function(){return c},Vn:function(){return l},j1:function(){return u}});var r=n(1172),i=n(5648),o=n(3526),a=n(3830);const s=Object.freeze({});function l(e,t,n=o.h8){const l=new Map;for(const e of Object.values(a.h))l.set(e,c(t,e));let u,d,f,p=Array.isArray(e),h=[e],m=-1,g=[],v=e;const y=[],b=[];do{m++;const e=m===h.length,a=e&&0!==g.length;if(e){if(d=0===b.length?void 0:y[y.length-1],v=f,f=b.pop(),a)if(p){v=v.slice();let e=0;for(const[t,n]of g){const r=t-e;null===n?(v.splice(r,1),e++):v[r]=n}}else{v=Object.defineProperties({},Object.getOwnPropertyDescriptors(v));for(const[e,t]of g)v[e]=t}m=u.index,h=u.keys,g=u.edits,p=u.inArray,u=u.prev}else if(f){if(d=p?m:h[m],v=f[d],null==v)continue;y.push(d)}let c;if(!Array.isArray(v)){var E,T;(0,o.UG)(v)||(0,r.a)(!1,`Invalid AST Node: ${(0,i.X)(v)}.`);const n=e?null===(E=l.get(v.kind))||void 0===E?void 0:E.leave:null===(T=l.get(v.kind))||void 0===T?void 0:T.enter;if(c=null==n?void 0:n.call(t,v,d,f,y,b),c===s)break;if(!1===c){if(!e){y.pop();continue}}else if(void 0!==c&&(g.push([d,c]),!e)){if(!(0,o.UG)(c)){y.pop();continue}v=c}}var w;void 0===c&&a&&g.push([d,v]),e?y.pop():(u={inArray:p,index:m,keys:h,edits:g,prev:u},p=Array.isArray(v),h=p?v:null!==(w=n[v.kind])&&void 0!==w?w:[],m=-1,g=[],f&&b.push(f),f=v)}while(void 0!==u);return 0!==g.length?g[g.length-1][1]:e}function u(e){const t=new Array(e.length).fill(null),n=Object.create(null);for(const r of Object.values(a.h)){let i=!1;const o=new Array(e.length).fill(void 0),a=new Array(e.length).fill(void 0);for(let t=0;tl((0,v.M)(e,t)),this.extensions=(0,p.u)(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(s=e.extensionASTNodes)&&void 0!==s?s:[],null==e.specifiedByURL||"string"==typeof e.specifiedByURL||(0,r.a)(!1,`${this.name} must provide "specifiedByURL" as a string, but got: ${(0,a.X)(e.specifiedByURL)}.`),null==e.serialize||"function"==typeof e.serialize||(0,r.a)(!1,`${this.name} must provide "serialize" function. If this custom Scalar is also used as an input type, ensure "parseValue" and "parseLiteral" functions are also provided.`),e.parseLiteral&&("function"==typeof e.parseValue&&"function"==typeof e.parseLiteral||(0,r.a)(!1,`${this.name} must provide both "parseValue" and "parseLiteral" functions.`))}get[Symbol.toStringTag](){return"GraphQLScalarType"}toConfig(){return{name:this.name,description:this.description,specifiedByURL:this.specifiedByURL,serialize:this.serialize,parseValue:this.parseValue,parseLiteral:this.parseLiteral,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}class ae{constructor(e){var t;this.name=(0,y.i)(e.name),this.description=e.description,this.isTypeOf=e.isTypeOf,this.extensions=(0,p.u)(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._fields=()=>le(e),this._interfaces=()=>se(e),null==e.isTypeOf||"function"==typeof e.isTypeOf||(0,r.a)(!1,`${this.name} must provide "isTypeOf" as a function, but got: ${(0,a.X)(e.isTypeOf)}.`)}get[Symbol.toStringTag](){return"GraphQLObjectType"}getFields(){return"function"==typeof this._fields&&(this._fields=this._fields()),this._fields}getInterfaces(){return"function"==typeof this._interfaces&&(this._interfaces=this._interfaces()),this._interfaces}toConfig(){return{name:this.name,description:this.description,interfaces:this.getInterfaces(),fields:de(this.getFields()),isTypeOf:this.isTypeOf,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}function se(e){var t;const n=re(null!==(t=e.interfaces)&&void 0!==t?t:[]);return Array.isArray(n)||(0,r.a)(!1,`${e.name} interfaces must be an Array or a function which returns an Array.`),n}function le(e){const t=ie(e.fields);return ce(t)||(0,r.a)(!1,`${e.name} fields must be an object with field names as keys or a function which returns such an object.`),(0,d.j)(t,((t,n)=>{var i;ce(t)||(0,r.a)(!1,`${e.name}.${n} field config must be an object.`),null==t.resolve||"function"==typeof t.resolve||(0,r.a)(!1,`${e.name}.${n} field resolver must be a function if provided, but got: ${(0,a.X)(t.resolve)}.`);const o=null!==(i=t.args)&&void 0!==i?i:{};return ce(o)||(0,r.a)(!1,`${e.name}.${n} args must be an object with argument names as keys.`),{name:(0,y.i)(n),description:t.description,type:t.type,args:ue(o),resolve:t.resolve,subscribe:t.subscribe,deprecationReason:t.deprecationReason,extensions:(0,p.u)(t.extensions),astNode:t.astNode}}))}function ue(e){return Object.entries(e).map((([e,t])=>({name:(0,y.i)(e),description:t.description,type:t.type,defaultValue:t.defaultValue,deprecationReason:t.deprecationReason,extensions:(0,p.u)(t.extensions),astNode:t.astNode})))}function ce(e){return(0,l.y)(e)&&!Array.isArray(e)}function de(e){return(0,d.j)(e,(e=>({description:e.description,type:e.type,args:fe(e.args),resolve:e.resolve,subscribe:e.subscribe,deprecationReason:e.deprecationReason,extensions:e.extensions,astNode:e.astNode})))}function fe(e){return(0,c.w)(e,(e=>e.name),(e=>({description:e.description,type:e.type,defaultValue:e.defaultValue,deprecationReason:e.deprecationReason,extensions:e.extensions,astNode:e.astNode})))}function pe(e){return R(e.type)&&void 0===e.defaultValue}class he{constructor(e){var t;this.name=(0,y.i)(e.name),this.description=e.description,this.resolveType=e.resolveType,this.extensions=(0,p.u)(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._fields=le.bind(void 0,e),this._interfaces=se.bind(void 0,e),null==e.resolveType||"function"==typeof e.resolveType||(0,r.a)(!1,`${this.name} must provide "resolveType" as a function, but got: ${(0,a.X)(e.resolveType)}.`)}get[Symbol.toStringTag](){return"GraphQLInterfaceType"}getFields(){return"function"==typeof this._fields&&(this._fields=this._fields()),this._fields}getInterfaces(){return"function"==typeof this._interfaces&&(this._interfaces=this._interfaces()),this._interfaces}toConfig(){return{name:this.name,description:this.description,interfaces:this.getInterfaces(),fields:de(this.getFields()),resolveType:this.resolveType,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}class me{constructor(e){var t;this.name=(0,y.i)(e.name),this.description=e.description,this.resolveType=e.resolveType,this.extensions=(0,p.u)(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._types=ge.bind(void 0,e),null==e.resolveType||"function"==typeof e.resolveType||(0,r.a)(!1,`${this.name} must provide "resolveType" as a function, but got: ${(0,a.X)(e.resolveType)}.`)}get[Symbol.toStringTag](){return"GraphQLUnionType"}getTypes(){return"function"==typeof this._types&&(this._types=this._types()),this._types}toConfig(){return{name:this.name,description:this.description,types:this.getTypes(),resolveType:this.resolveType,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}function ge(e){const t=re(e.types);return Array.isArray(t)||(0,r.a)(!1,`Must provide Array of types or a function which returns such an array for Union ${e.name}.`),t}class ve{constructor(e){var t,n,i;this.name=(0,y.i)(e.name),this.description=e.description,this.extensions=(0,p.u)(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._values=(n=this.name,ce(i=e.values)||(0,r.a)(!1,`${n} values must be an object with value names as keys.`),Object.entries(i).map((([e,t])=>(ce(t)||(0,r.a)(!1,`${n}.${e} must refer to an object with a "value" key representing an internal value but got: ${(0,a.X)(t)}.`),{name:(0,y.g)(e),description:t.description,value:void 0!==t.value?t.value:e,deprecationReason:t.deprecationReason,extensions:(0,p.u)(t.extensions),astNode:t.astNode})))),this._valueLookup=new Map(this._values.map((e=>[e.value,e]))),this._nameLookup=(0,u.P)(this._values,(e=>e.name))}get[Symbol.toStringTag](){return"GraphQLEnumType"}getValues(){return this._values}getValue(e){return this._nameLookup[e]}serialize(e){const t=this._valueLookup.get(e);if(void 0===t)throw new h.__(`Enum "${this.name}" cannot represent value: ${(0,a.X)(e)}`);return t.name}parseValue(e){if("string"!=typeof e){const t=(0,a.X)(e);throw new h.__(`Enum "${this.name}" cannot represent non-string value: ${t}.`+ye(this,t))}const t=this.getValue(e);if(null==t)throw new h.__(`Value "${e}" does not exist in "${this.name}" enum.`+ye(this,e));return t.value}parseLiteral(e,t){if(e.kind!==m.h.ENUM){const t=(0,g.S)(e);throw new h.__(`Enum "${this.name}" cannot represent non-enum value: ${t}.`+ye(this,t),{nodes:e})}const n=this.getValue(e.value);if(null==n){const t=(0,g.S)(e);throw new h.__(`Value "${t}" does not exist in "${this.name}" enum.`+ye(this,t),{nodes:e})}return n.value}toConfig(){const e=(0,c.w)(this.getValues(),(e=>e.name),(e=>({description:e.description,value:e.value,deprecationReason:e.deprecationReason,extensions:e.extensions,astNode:e.astNode})));return{name:this.name,description:this.description,values:e,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}function ye(e,t){const n=e.getValues().map((e=>e.name)),r=(0,f.D)(t,n);return(0,i.l)("the enum value",r)}class be{constructor(e){var t;this.name=(0,y.i)(e.name),this.description=e.description,this.extensions=(0,p.u)(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._fields=Ee.bind(void 0,e)}get[Symbol.toStringTag](){return"GraphQLInputObjectType"}getFields(){return"function"==typeof this._fields&&(this._fields=this._fields()),this._fields}toConfig(){const e=(0,d.j)(this.getFields(),(e=>({description:e.description,type:e.type,defaultValue:e.defaultValue,deprecationReason:e.deprecationReason,extensions:e.extensions,astNode:e.astNode})));return{name:this.name,description:this.description,fields:e,extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes}}toString(){return this.name}toJSON(){return this.toString()}}function Ee(e){const t=ie(e.fields);return ce(t)||(0,r.a)(!1,`${e.name} fields must be an object with field names as keys or a function which returns such an object.`),(0,d.j)(t,((t,n)=>(!("resolve"in t)||(0,r.a)(!1,`${e.name}.${n} field has a resolve property, but Input Types cannot define resolvers.`),{name:(0,y.i)(n),description:t.description,type:t.type,defaultValue:t.defaultValue,deprecationReason:t.deprecationReason,extensions:(0,p.u)(t.extensions),astNode:t.astNode})))}function Te(e){return R(e.type)&&void 0===e.defaultValue}},5946:function(e,t,n){"use strict";n.d(t,{CO:function(){return p},NZ:function(){return h},QE:function(){return g},SY:function(){return v},V4:function(){return E},Yf:function(){return m},df:function(){return b},fg:function(){return y},wX:function(){return f},xg:function(){return T}});var r=n(1172),i=n(5648),o=n(1513),a=n(1315),s=n(1140),l=n(3140),u=n(3228),c=n(755),d=n(1774);function f(e){return(0,o.n)(e,h)}function p(e){if(!f(e))throw new Error(`Expected ${(0,i.X)(e)} to be a GraphQL directive.`);return e}class h{constructor(e){var t,n;this.name=(0,u.i)(e.name),this.description=e.description,this.locations=e.locations,this.isRepeatable=null!==(t=e.isRepeatable)&&void 0!==t&&t,this.extensions=(0,s.u)(e.extensions),this.astNode=e.astNode,Array.isArray(e.locations)||(0,r.a)(!1,`@${e.name} locations must be an Array.`);const i=null!==(n=e.args)&&void 0!==n?n:{};(0,a.y)(i)&&!Array.isArray(i)||(0,r.a)(!1,`@${e.name} args must be an object with argument names as keys.`),this.args=(0,c.WO)(i)}get[Symbol.toStringTag](){return"GraphQLDirective"}toConfig(){return{name:this.name,description:this.description,locations:this.locations,args:(0,c.DM)(this.args),isRepeatable:this.isRepeatable,extensions:this.extensions,astNode:this.astNode}}toString(){return"@"+this.name}toJSON(){return this.toString()}}const m=new h({name:"include",description:"Directs the executor to include this field or fragment only when the `if` argument is true.",locations:[l.B.FIELD,l.B.FRAGMENT_SPREAD,l.B.INLINE_FRAGMENT],args:{if:{type:new c.bM(d.EZ),description:"Included when true."}}}),g=new h({name:"skip",description:"Directs the executor to skip this field or fragment when the `if` argument is true.",locations:[l.B.FIELD,l.B.FRAGMENT_SPREAD,l.B.INLINE_FRAGMENT],args:{if:{type:new c.bM(d.EZ),description:"Skipped when true."}}}),v="No longer supported",y=new h({name:"deprecated",description:"Marks an element of a GraphQL schema as no longer supported.",locations:[l.B.FIELD_DEFINITION,l.B.ARGUMENT_DEFINITION,l.B.INPUT_FIELD_DEFINITION,l.B.ENUM_VALUE],args:{reason:{type:d.kH,description:"Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).",defaultValue:v}}}),b=new h({name:"specifiedBy",description:"Exposes a URL that specifies the behavior of this scalar.",locations:[l.B.SCALAR],args:{url:{type:new c.bM(d.kH),description:"The URL that specifies the behavior of this scalar."}}}),E=Object.freeze([m,g,y,b]);function T(e){return E.some((({name:t})=>t===e.name))}},8078:function(e,t,n){"use strict";n.d(t,{Az:function(){return b},PX:function(){return y},TK:function(){return c},XQ:function(){return m},e_:function(){return h},hU:function(){return T},jT:function(){return g},l3:function(){return d},nL:function(){return w},qz:function(){return p},s9:function(){return C},tF:function(){return E},x2:function(){return f},zU:function(){return v}});var r=n(5648),i=n(5052),o=n(3140),a=n(5895),s=n(3190),l=n(755),u=n(1774);const c=new l.h6({name:"__Schema",description:"A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.",fields:()=>({description:{type:u.kH,resolve:e=>e.description},types:{description:"A list of all types supported by this server.",type:new l.bM(new l.p2(new l.bM(p))),resolve(e){return Object.values(e.getTypeMap())}},queryType:{description:"The type that query operations will be rooted at.",type:new l.bM(p),resolve:e=>e.getQueryType()},mutationType:{description:"If this server supports mutation, the type that mutation operations will be rooted at.",type:p,resolve:e=>e.getMutationType()},subscriptionType:{description:"If this server support subscription, the type that subscription operations will be rooted at.",type:p,resolve:e=>e.getSubscriptionType()},directives:{description:"A list of all directives supported by this server.",type:new l.bM(new l.p2(new l.bM(d))),resolve:e=>e.getDirectives()}})}),d=new l.h6({name:"__Directive",description:"A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.",fields:()=>({name:{type:new l.bM(u.kH),resolve:e=>e.name},description:{type:u.kH,resolve:e=>e.description},isRepeatable:{type:new l.bM(u.EZ),resolve:e=>e.isRepeatable},locations:{type:new l.bM(new l.p2(new l.bM(f))),resolve:e=>e.locations},args:{type:new l.bM(new l.p2(new l.bM(m))),args:{includeDeprecated:{type:u.EZ,defaultValue:!1}},resolve(e,{includeDeprecated:t}){return t?e.args:e.args.filter((e=>null==e.deprecationReason))}}})}),f=new l.mR({name:"__DirectiveLocation",description:"A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.",values:{QUERY:{value:o.B.QUERY,description:"Location adjacent to a query operation."},MUTATION:{value:o.B.MUTATION,description:"Location adjacent to a mutation operation."},SUBSCRIPTION:{value:o.B.SUBSCRIPTION,description:"Location adjacent to a subscription operation."},FIELD:{value:o.B.FIELD,description:"Location adjacent to a field."},FRAGMENT_DEFINITION:{value:o.B.FRAGMENT_DEFINITION,description:"Location adjacent to a fragment definition."},FRAGMENT_SPREAD:{value:o.B.FRAGMENT_SPREAD,description:"Location adjacent to a fragment spread."},INLINE_FRAGMENT:{value:o.B.INLINE_FRAGMENT,description:"Location adjacent to an inline fragment."},VARIABLE_DEFINITION:{value:o.B.VARIABLE_DEFINITION,description:"Location adjacent to a variable definition."},SCHEMA:{value:o.B.SCHEMA,description:"Location adjacent to a schema definition."},SCALAR:{value:o.B.SCALAR,description:"Location adjacent to a scalar definition."},OBJECT:{value:o.B.OBJECT,description:"Location adjacent to an object type definition."},FIELD_DEFINITION:{value:o.B.FIELD_DEFINITION,description:"Location adjacent to a field definition."},ARGUMENT_DEFINITION:{value:o.B.ARGUMENT_DEFINITION,description:"Location adjacent to an argument definition."},INTERFACE:{value:o.B.INTERFACE,description:"Location adjacent to an interface definition."},UNION:{value:o.B.UNION,description:"Location adjacent to a union definition."},ENUM:{value:o.B.ENUM,description:"Location adjacent to an enum definition."},ENUM_VALUE:{value:o.B.ENUM_VALUE,description:"Location adjacent to an enum value definition."},INPUT_OBJECT:{value:o.B.INPUT_OBJECT,description:"Location adjacent to an input object type definition."},INPUT_FIELD_DEFINITION:{value:o.B.INPUT_FIELD_DEFINITION,description:"Location adjacent to an input object field definition."}}}),p=new l.h6({name:"__Type",description:"The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByURL`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.",fields:()=>({kind:{type:new l.bM(y),resolve(e){return(0,l.KA)(e)?v.SCALAR:(0,l.lp)(e)?v.OBJECT:(0,l.oT)(e)?v.INTERFACE:(0,l.EN)(e)?v.UNION:(0,l.EM)(e)?v.ENUM:(0,l.hL)(e)?v.INPUT_OBJECT:(0,l.HG)(e)?v.LIST:(0,l.zM)(e)?v.NON_NULL:void(0,i.k)(!1,`Unexpected type: "${(0,r.X)(e)}".`)}},name:{type:u.kH,resolve:e=>"name"in e?e.name:void 0},description:{type:u.kH,resolve:e=>"description"in e?e.description:void 0},specifiedByURL:{type:u.kH,resolve:e=>"specifiedByURL"in e?e.specifiedByURL:void 0},fields:{type:new l.p2(new l.bM(h)),args:{includeDeprecated:{type:u.EZ,defaultValue:!1}},resolve(e,{includeDeprecated:t}){if((0,l.lp)(e)||(0,l.oT)(e)){const n=Object.values(e.getFields());return t?n:n.filter((e=>null==e.deprecationReason))}}},interfaces:{type:new l.p2(new l.bM(p)),resolve(e){if((0,l.lp)(e)||(0,l.oT)(e))return e.getInterfaces()}},possibleTypes:{type:new l.p2(new l.bM(p)),resolve(e,t,n,{schema:r}){if((0,l.m0)(e))return r.getPossibleTypes(e)}},enumValues:{type:new l.p2(new l.bM(g)),args:{includeDeprecated:{type:u.EZ,defaultValue:!1}},resolve(e,{includeDeprecated:t}){if((0,l.EM)(e)){const n=e.getValues();return t?n:n.filter((e=>null==e.deprecationReason))}}},inputFields:{type:new l.p2(new l.bM(m)),args:{includeDeprecated:{type:u.EZ,defaultValue:!1}},resolve(e,{includeDeprecated:t}){if((0,l.hL)(e)){const n=Object.values(e.getFields());return t?n:n.filter((e=>null==e.deprecationReason))}}},ofType:{type:p,resolve:e=>"ofType"in e?e.ofType:void 0}})}),h=new l.h6({name:"__Field",description:"Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.",fields:()=>({name:{type:new l.bM(u.kH),resolve:e=>e.name},description:{type:u.kH,resolve:e=>e.description},args:{type:new l.bM(new l.p2(new l.bM(m))),args:{includeDeprecated:{type:u.EZ,defaultValue:!1}},resolve(e,{includeDeprecated:t}){return t?e.args:e.args.filter((e=>null==e.deprecationReason))}},type:{type:new l.bM(p),resolve:e=>e.type},isDeprecated:{type:new l.bM(u.EZ),resolve:e=>null!=e.deprecationReason},deprecationReason:{type:u.kH,resolve:e=>e.deprecationReason}})}),m=new l.h6({name:"__InputValue",description:"Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",fields:()=>({name:{type:new l.bM(u.kH),resolve:e=>e.name},description:{type:u.kH,resolve:e=>e.description},type:{type:new l.bM(p),resolve:e=>e.type},defaultValue:{type:u.kH,description:"A GraphQL-formatted string representing the default value for this input value.",resolve(e){const{type:t,defaultValue:n}=e,r=(0,s.J)(n,t);return r?(0,a.S)(r):null}},isDeprecated:{type:new l.bM(u.EZ),resolve:e=>null!=e.deprecationReason},deprecationReason:{type:u.kH,resolve:e=>e.deprecationReason}})}),g=new l.h6({name:"__EnumValue",description:"One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.",fields:()=>({name:{type:new l.bM(u.kH),resolve:e=>e.name},description:{type:u.kH,resolve:e=>e.description},isDeprecated:{type:new l.bM(u.EZ),resolve:e=>null!=e.deprecationReason},deprecationReason:{type:u.kH,resolve:e=>e.deprecationReason}})});let v;!function(e){e.SCALAR="SCALAR",e.OBJECT="OBJECT",e.INTERFACE="INTERFACE",e.UNION="UNION",e.ENUM="ENUM",e.INPUT_OBJECT="INPUT_OBJECT",e.LIST="LIST",e.NON_NULL="NON_NULL"}(v||(v={}));const y=new l.mR({name:"__TypeKind",description:"An enum describing what kind of type a given `__Type` is.",values:{SCALAR:{value:v.SCALAR,description:"Indicates this type is a scalar."},OBJECT:{value:v.OBJECT,description:"Indicates this type is an object. `fields` and `interfaces` are valid fields."},INTERFACE:{value:v.INTERFACE,description:"Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields."},UNION:{value:v.UNION,description:"Indicates this type is a union. `possibleTypes` is a valid field."},ENUM:{value:v.ENUM,description:"Indicates this type is an enum. `enumValues` is a valid field."},INPUT_OBJECT:{value:v.INPUT_OBJECT,description:"Indicates this type is an input object. `inputFields` is a valid field."},LIST:{value:v.LIST,description:"Indicates this type is a list. `ofType` is a valid field."},NON_NULL:{value:v.NON_NULL,description:"Indicates this type is a non-null. `ofType` is a valid field."}}}),b={name:"__schema",type:new l.bM(c),description:"Access the current type schema of this server.",args:[],resolve:(e,t,n,{schema:r})=>r,deprecationReason:void 0,extensions:Object.create(null),astNode:void 0},E={name:"__type",type:p,description:"Request the type information of a single type.",args:[{name:"name",description:void 0,type:new l.bM(u.kH),defaultValue:void 0,deprecationReason:void 0,extensions:Object.create(null),astNode:void 0}],resolve:(e,{name:t},n,{schema:r})=>r.getType(t),deprecationReason:void 0,extensions:Object.create(null),astNode:void 0},T={name:"__typename",type:new l.bM(u.kH),description:"The name of the current Object type at runtime.",args:[],resolve:(e,t,n,{parentType:r})=>r.name,deprecationReason:void 0,extensions:Object.create(null),astNode:void 0},w=Object.freeze([c,d,f,p,h,m,g,y]);function C(e){return w.some((({name:t})=>e.name===t))}},1774:function(e,t,n){"use strict";n.d(t,{EZ:function(){return h},HI:function(){return u},HS:function(){return g},_o:function(){return d},av:function(){return f},kH:function(){return p},km:function(){return m},st:function(){return c},u1:function(){return v}});var r=n(5648),i=n(1315),o=n(4117),a=n(3830),s=n(5895),l=n(755);const u=2147483647,c=-2147483648,d=new l.n2({name:"Int",description:"The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.",serialize(e){const t=y(e);if("boolean"==typeof t)return t?1:0;let n=t;if("string"==typeof t&&""!==t&&(n=Number(t)),"number"!=typeof n||!Number.isInteger(n))throw new o.__(`Int cannot represent non-integer value: ${(0,r.X)(t)}`);if(n>u||nu||eu||te.name===t))}function y(e){if((0,i.y)(e)){if("function"==typeof e.valueOf){const t=e.valueOf();if(!(0,i.y)(t))return t}if("function"==typeof e.toJSON)return e.toJSON()}return e}},773:function(e,t,n){"use strict";n.d(t,{EO:function(){return p},XO:function(){return h},nN:function(){return f}});var r=n(1172),i=n(5648),o=n(1513),a=n(1315),s=n(1140),l=n(3526),u=n(755),c=n(5946),d=n(8078);function f(e){return(0,o.n)(e,h)}function p(e){if(!f(e))throw new Error(`Expected ${(0,i.X)(e)} to be a GraphQL schema.`);return e}class h{constructor(e){var t,n;this.__validationErrors=!0===e.assumeValid?[]:void 0,(0,a.y)(e)||(0,r.a)(!1,"Must provide configuration object."),!e.types||Array.isArray(e.types)||(0,r.a)(!1,`"types" must be Array if provided but got: ${(0,i.X)(e.types)}.`),!e.directives||Array.isArray(e.directives)||(0,r.a)(!1,`"directives" must be Array if provided but got: ${(0,i.X)(e.directives)}.`),this.description=e.description,this.extensions=(0,s.u)(e.extensions),this.astNode=e.astNode,this.extensionASTNodes=null!==(t=e.extensionASTNodes)&&void 0!==t?t:[],this._queryType=e.query,this._mutationType=e.mutation,this._subscriptionType=e.subscription,this._directives=null!==(n=e.directives)&&void 0!==n?n:c.V4;const o=new Set(e.types);if(null!=e.types)for(const t of e.types)o.delete(t),m(t,o);null!=this._queryType&&m(this._queryType,o),null!=this._mutationType&&m(this._mutationType,o),null!=this._subscriptionType&&m(this._subscriptionType,o);for(const e of this._directives)if((0,c.wX)(e))for(const t of e.args)m(t.type,o);m(d.TK,o),this._typeMap=Object.create(null),this._subTypeMap=Object.create(null),this._implementationsMap=Object.create(null);for(const e of o){if(null==e)continue;const t=e.name;if(t||(0,r.a)(!1,"One of the provided types for building the Schema is missing a name."),void 0!==this._typeMap[t])throw new Error(`Schema must contain uniquely named types but contains multiple types named "${t}".`);if(this._typeMap[t]=e,(0,u.oT)(e)){for(const t of e.getInterfaces())if((0,u.oT)(t)){let n=this._implementationsMap[t.name];void 0===n&&(n=this._implementationsMap[t.name]={objects:[],interfaces:[]}),n.interfaces.push(e)}}else if((0,u.lp)(e))for(const t of e.getInterfaces())if((0,u.oT)(t)){let n=this._implementationsMap[t.name];void 0===n&&(n=this._implementationsMap[t.name]={objects:[],interfaces:[]}),n.objects.push(e)}}}get[Symbol.toStringTag](){return"GraphQLSchema"}getQueryType(){return this._queryType}getMutationType(){return this._mutationType}getSubscriptionType(){return this._subscriptionType}getRootType(e){switch(e){case l.ku.QUERY:return this.getQueryType();case l.ku.MUTATION:return this.getMutationType();case l.ku.SUBSCRIPTION:return this.getSubscriptionType()}}getTypeMap(){return this._typeMap}getType(e){return this.getTypeMap()[e]}getPossibleTypes(e){return(0,u.EN)(e)?e.getTypes():this.getImplementations(e).objects}getImplementations(e){const t=this._implementationsMap[e.name];return null!=t?t:{objects:[],interfaces:[]}}isSubType(e,t){let n=this._subTypeMap[e.name];if(void 0===n){if(n=Object.create(null),(0,u.EN)(e))for(const t of e.getTypes())n[t.name]=!0;else{const t=this.getImplementations(e);for(const e of t.objects)n[e.name]=!0;for(const e of t.interfaces)n[e.name]=!0}this._subTypeMap[e.name]=n}return void 0!==n[t.name]}getDirectives(){return this._directives}getDirective(e){return this.getDirectives().find((t=>t.name===e))}toConfig(){return{description:this.description,query:this.getQueryType(),mutation:this.getMutationType(),subscription:this.getSubscriptionType(),types:Object.values(this.getTypeMap()),directives:this.getDirectives(),extensions:this.extensions,astNode:this.astNode,extensionASTNodes:this.extensionASTNodes,assumeValid:void 0!==this.__validationErrors}}}function m(e,t){const n=(0,u.xC)(e);if(!t.has(n))if(t.add(n),(0,u.EN)(n))for(const e of n.getTypes())m(e,t);else if((0,u.lp)(n)||(0,u.oT)(n)){for(const e of n.getInterfaces())m(e,t);for(const e of Object.values(n.getFields())){m(e.type,t);for(const n of e.args)m(n.type,t)}}else if((0,u.hL)(n))for(const e of Object.values(n.getFields()))m(e.type,t);return t}},8555:function(e,t,n){"use strict";n.d(t,{F:function(){return d},J:function(){return f}});var r=n(5648),i=n(4117),o=n(3526),a=n(2984),s=n(755),l=n(5946),u=n(8078),c=n(773);function d(e){if((0,c.EO)(e),e.__validationErrors)return e.__validationErrors;const t=new p(e);!function(e){const t=e.schema,n=t.getQueryType();if(n){if(!(0,s.lp)(n)){var i;e.reportError(`Query root type must be Object type, it cannot be ${(0,r.X)(n)}.`,null!==(i=h(t,o.ku.QUERY))&&void 0!==i?i:n.astNode)}}else e.reportError("Query root type must be provided.",t.astNode);const a=t.getMutationType();var l;a&&!(0,s.lp)(a)&&e.reportError(`Mutation root type must be Object type if provided, it cannot be ${(0,r.X)(a)}.`,null!==(l=h(t,o.ku.MUTATION))&&void 0!==l?l:a.astNode);const u=t.getSubscriptionType();var c;u&&!(0,s.lp)(u)&&e.reportError(`Subscription root type must be Object type if provided, it cannot be ${(0,r.X)(u)}.`,null!==(c=h(t,o.ku.SUBSCRIPTION))&&void 0!==c?c:u.astNode)}(t),function(e){for(const n of e.schema.getDirectives())if((0,l.wX)(n)){m(e,n);for(const i of n.args){var t;m(e,i),(0,s.j$)(i.type)||e.reportError(`The type of @${n.name}(${i.name}:) must be Input Type but got: ${(0,r.X)(i.type)}.`,i.astNode),(0,s.dK)(i)&&null!=i.deprecationReason&&e.reportError(`Required argument @${n.name}(${i.name}:) cannot be deprecated.`,[x(i.astNode),null===(t=i.astNode)||void 0===t?void 0:t.type])}}else e.reportError(`Expected directive but got: ${(0,r.X)(n)}.`,null==n?void 0:n.astNode)}(t),function(e){const t=function(e){const t=Object.create(null),n=[],r=Object.create(null);return function i(o){if(t[o.name])return;t[o.name]=!0,r[o.name]=n.length;const a=Object.values(o.getFields());for(const t of a)if((0,s.zM)(t.type)&&(0,s.hL)(t.type.ofType)){const o=t.type.ofType,a=r[o.name];if(n.push(t),void 0===a)i(o);else{const t=n.slice(a),r=t.map((e=>e.name)).join(".");e.reportError(`Cannot reference Input Object "${o.name}" within itself through a series of non-null fields: "${r}".`,t.map((e=>e.astNode)))}n.pop()}r[o.name]=void 0}}(e),n=e.schema.getTypeMap();for(const i of Object.values(n))(0,s.Zs)(i)?((0,u.s9)(i)||m(e,i),(0,s.lp)(i)||(0,s.oT)(i)?(g(e,i),v(e,i)):(0,s.EN)(i)?E(e,i):(0,s.EM)(i)?T(e,i):(0,s.hL)(i)&&(w(e,i),t(i))):e.reportError(`Expected GraphQL named type but got: ${(0,r.X)(i)}.`,i.astNode)}(t);const n=t.getErrors();return e.__validationErrors=n,n}function f(e){const t=d(e);if(0!==t.length)throw new Error(t.map((e=>e.message)).join("\n\n"))}class p{constructor(e){this._errors=[],this.schema=e}reportError(e,t){const n=Array.isArray(t)?t.filter(Boolean):t;this._errors.push(new i.__(e,{nodes:n}))}getErrors(){return this._errors}}function h(e,t){var n;return null===(n=[e.astNode,...e.extensionASTNodes].flatMap((e=>{var t;return null!==(t=null==e?void 0:e.operationTypes)&&void 0!==t?t:[]})).find((e=>e.operation===t)))||void 0===n?void 0:n.type}function m(e,t){t.name.startsWith("__")&&e.reportError(`Name "${t.name}" must not begin with "__", which is reserved by GraphQL introspection.`,t.astNode)}function g(e,t){const n=Object.values(t.getFields());0===n.length&&e.reportError(`Type ${t.name} must define one or more fields.`,[t.astNode,...t.extensionASTNodes]);for(const l of n){var i;m(e,l),(0,s.SZ)(l.type)||e.reportError(`The type of ${t.name}.${l.name} must be Output Type but got: ${(0,r.X)(l.type)}.`,null===(i=l.astNode)||void 0===i?void 0:i.type);for(const n of l.args){const i=n.name;var o,a;m(e,n),(0,s.j$)(n.type)||e.reportError(`The type of ${t.name}.${l.name}(${i}:) must be Input Type but got: ${(0,r.X)(n.type)}.`,null===(o=n.astNode)||void 0===o?void 0:o.type),(0,s.dK)(n)&&null!=n.deprecationReason&&e.reportError(`Required argument ${t.name}.${l.name}(${i}:) cannot be deprecated.`,[x(n.astNode),null===(a=n.astNode)||void 0===a?void 0:a.type])}}}function v(e,t){const n=Object.create(null);for(const i of t.getInterfaces())(0,s.oT)(i)?t!==i?n[i.name]?e.reportError(`Type ${t.name} can only implement ${i.name} once.`,C(t,i)):(n[i.name]=!0,b(e,t,i),y(e,t,i)):e.reportError(`Type ${t.name} cannot implement itself because it would create a circular reference.`,C(t,i)):e.reportError(`Type ${(0,r.X)(t)} must only implement Interface types, it cannot implement ${(0,r.X)(i)}.`,C(t,i))}function y(e,t,n){const i=t.getFields();for(const d of Object.values(n.getFields())){const f=d.name,p=i[f];if(p){var o,l;(0,a.uJ)(e.schema,p.type,d.type)||e.reportError(`Interface field ${n.name}.${f} expects type ${(0,r.X)(d.type)} but ${t.name}.${f} is type ${(0,r.X)(p.type)}.`,[null===(o=d.astNode)||void 0===o?void 0:o.type,null===(l=p.astNode)||void 0===l?void 0:l.type]);for(const i of d.args){const o=i.name,s=p.args.find((e=>e.name===o));var u,c;s?(0,a._7)(i.type,s.type)||e.reportError(`Interface field argument ${n.name}.${f}(${o}:) expects type ${(0,r.X)(i.type)} but ${t.name}.${f}(${o}:) is type ${(0,r.X)(s.type)}.`,[null===(u=i.astNode)||void 0===u?void 0:u.type,null===(c=s.astNode)||void 0===c?void 0:c.type]):e.reportError(`Interface field argument ${n.name}.${f}(${o}:) expected but ${t.name}.${f} does not provide it.`,[i.astNode,p.astNode])}for(const r of p.args){const i=r.name;!d.args.find((e=>e.name===i))&&(0,s.dK)(r)&&e.reportError(`Object field ${t.name}.${f} includes required argument ${i} that is missing from the Interface field ${n.name}.${f}.`,[r.astNode,d.astNode])}}else e.reportError(`Interface field ${n.name}.${f} expected but ${t.name} does not provide it.`,[d.astNode,t.astNode,...t.extensionASTNodes])}}function b(e,t,n){const r=t.getInterfaces();for(const i of n.getInterfaces())r.includes(i)||e.reportError(i===t?`Type ${t.name} cannot implement ${n.name} because it would create a circular reference.`:`Type ${t.name} must implement ${i.name} because it is implemented by ${n.name}.`,[...C(n,i),...C(t,n)])}function E(e,t){const n=t.getTypes();0===n.length&&e.reportError(`Union type ${t.name} must define one or more member types.`,[t.astNode,...t.extensionASTNodes]);const i=Object.create(null);for(const o of n)i[o.name]?e.reportError(`Union type ${t.name} can only include type ${o.name} once.`,S(t,o.name)):(i[o.name]=!0,(0,s.lp)(o)||e.reportError(`Union type ${t.name} can only include Object types, it cannot include ${(0,r.X)(o)}.`,S(t,String(o))))}function T(e,t){const n=t.getValues();0===n.length&&e.reportError(`Enum type ${t.name} must define one or more values.`,[t.astNode,...t.extensionASTNodes]);for(const t of n)m(e,t)}function w(e,t){const n=Object.values(t.getFields());0===n.length&&e.reportError(`Input Object type ${t.name} must define one or more fields.`,[t.astNode,...t.extensionASTNodes]);for(const a of n){var i,o;m(e,a),(0,s.j$)(a.type)||e.reportError(`The type of ${t.name}.${a.name} must be Input Type but got: ${(0,r.X)(a.type)}.`,null===(i=a.astNode)||void 0===i?void 0:i.type),(0,s.Wd)(a)&&null!=a.deprecationReason&&e.reportError(`Required input field ${t.name}.${a.name} cannot be deprecated.`,[x(a.astNode),null===(o=a.astNode)||void 0===o?void 0:o.type])}}function C(e,t){const{astNode:n,extensionASTNodes:r}=e;return(null!=n?[n,...r]:r).flatMap((e=>{var t;return null!==(t=e.interfaces)&&void 0!==t?t:[]})).filter((e=>e.name.value===t.name))}function S(e,t){const{astNode:n,extensionASTNodes:r}=e;return(null!=n?[n,...r]:r).flatMap((e=>{var t;return null!==(t=e.types)&&void 0!==t?t:[]})).filter((e=>e.name.value===t))}function x(e){var t;return null==e||null===(t=e.directives)||void 0===t?void 0:t.find((e=>e.name.value===l.fg.name))}},1409:function(e,t,n){"use strict";n.d(t,{a:function(){return u},y:function(){return d}});var r=n(3526),i=n(3830),o=n(9685),a=n(755),s=n(8078),l=n(5998);class u{constructor(e,t,n){this._schema=e,this._typeStack=[],this._parentTypeStack=[],this._inputTypeStack=[],this._fieldDefStack=[],this._defaultValueStack=[],this._directive=null,this._argument=null,this._enumValue=null,this._getFieldDef=null!=n?n:c,t&&((0,a.j$)(t)&&this._inputTypeStack.push(t),(0,a.Gv)(t)&&this._parentTypeStack.push(t),(0,a.SZ)(t)&&this._typeStack.push(t))}get[Symbol.toStringTag](){return"TypeInfo"}getType(){if(this._typeStack.length>0)return this._typeStack[this._typeStack.length-1]}getParentType(){if(this._parentTypeStack.length>0)return this._parentTypeStack[this._parentTypeStack.length-1]}getInputType(){if(this._inputTypeStack.length>0)return this._inputTypeStack[this._inputTypeStack.length-1]}getParentInputType(){if(this._inputTypeStack.length>1)return this._inputTypeStack[this._inputTypeStack.length-2]}getFieldDef(){if(this._fieldDefStack.length>0)return this._fieldDefStack[this._fieldDefStack.length-1]}getDefaultValue(){if(this._defaultValueStack.length>0)return this._defaultValueStack[this._defaultValueStack.length-1]}getDirective(){return this._directive}getArgument(){return this._argument}getEnumValue(){return this._enumValue}enter(e){const t=this._schema;switch(e.kind){case i.h.SELECTION_SET:{const e=(0,a.xC)(this.getType());this._parentTypeStack.push((0,a.Gv)(e)?e:void 0);break}case i.h.FIELD:{const n=this.getParentType();let r,i;n&&(r=this._getFieldDef(t,n,e),r&&(i=r.type)),this._fieldDefStack.push(r),this._typeStack.push((0,a.SZ)(i)?i:void 0);break}case i.h.DIRECTIVE:this._directive=t.getDirective(e.name.value);break;case i.h.OPERATION_DEFINITION:{const n=t.getRootType(e.operation);this._typeStack.push((0,a.lp)(n)?n:void 0);break}case i.h.INLINE_FRAGMENT:case i.h.FRAGMENT_DEFINITION:{const n=e.typeCondition,r=n?(0,l._)(t,n):(0,a.xC)(this.getType());this._typeStack.push((0,a.SZ)(r)?r:void 0);break}case i.h.VARIABLE_DEFINITION:{const n=(0,l._)(t,e.type);this._inputTypeStack.push((0,a.j$)(n)?n:void 0);break}case i.h.ARGUMENT:{var n;let t,r;const i=null!==(n=this.getDirective())&&void 0!==n?n:this.getFieldDef();i&&(t=i.args.find((t=>t.name===e.name.value)),t&&(r=t.type)),this._argument=t,this._defaultValueStack.push(t?t.defaultValue:void 0),this._inputTypeStack.push((0,a.j$)(r)?r:void 0);break}case i.h.LIST:{const e=(0,a.tf)(this.getInputType()),t=(0,a.HG)(e)?e.ofType:e;this._defaultValueStack.push(void 0),this._inputTypeStack.push((0,a.j$)(t)?t:void 0);break}case i.h.OBJECT_FIELD:{const t=(0,a.xC)(this.getInputType());let n,r;(0,a.hL)(t)&&(r=t.getFields()[e.name.value],r&&(n=r.type)),this._defaultValueStack.push(r?r.defaultValue:void 0),this._inputTypeStack.push((0,a.j$)(n)?n:void 0);break}case i.h.ENUM:{const t=(0,a.xC)(this.getInputType());let n;(0,a.EM)(t)&&(n=t.getValue(e.value)),this._enumValue=n;break}}}leave(e){switch(e.kind){case i.h.SELECTION_SET:this._parentTypeStack.pop();break;case i.h.FIELD:this._fieldDefStack.pop(),this._typeStack.pop();break;case i.h.DIRECTIVE:this._directive=null;break;case i.h.OPERATION_DEFINITION:case i.h.INLINE_FRAGMENT:case i.h.FRAGMENT_DEFINITION:this._typeStack.pop();break;case i.h.VARIABLE_DEFINITION:this._inputTypeStack.pop();break;case i.h.ARGUMENT:this._argument=null,this._defaultValueStack.pop(),this._inputTypeStack.pop();break;case i.h.LIST:case i.h.OBJECT_FIELD:this._defaultValueStack.pop(),this._inputTypeStack.pop();break;case i.h.ENUM:this._enumValue=null}}}function c(e,t,n){const r=n.name.value;return r===s.Az.name&&e.getQueryType()===t?s.Az:r===s.tF.name&&e.getQueryType()===t?s.tF:r===s.hU.name&&(0,a.Gv)(t)?s.hU:(0,a.lp)(t)||(0,a.oT)(t)?t.getFields()[r]:void 0}function d(e,t){return{enter(...n){const i=n[0];e.enter(i);const a=(0,o.Eu)(t,i.kind).enter;if(a){const o=a.apply(t,n);return void 0!==o&&(e.leave(i),(0,r.UG)(o)&&e.enter(o)),o}},leave(...n){const r=n[0],i=(0,o.Eu)(t,r.kind).leave;let a;return i&&(a=i.apply(t,n)),e.leave(r),a}}}},3190:function(e,t,n){"use strict";n.d(t,{J:function(){return c}});var r=n(5648),i=n(5052),o=n(2910),a=n(1315),s=n(3830),l=n(755),u=n(1774);function c(e,t){if((0,l.zM)(t)){const n=c(e,t.ofType);return(null==n?void 0:n.kind)===s.h.NULL?null:n}if(null===e)return{kind:s.h.NULL};if(void 0===e)return null;if((0,l.HG)(t)){const n=t.ofType;if((0,o.i)(e)){const t=[];for(const r of e){const e=c(r,n);null!=e&&t.push(e)}return{kind:s.h.LIST,values:t}}return c(e,n)}if((0,l.hL)(t)){if(!(0,a.y)(e))return null;const n=[];for(const r of Object.values(t.getFields())){const t=c(e[r.name],r.type);t&&n.push({kind:s.h.OBJECT_FIELD,name:{kind:s.h.NAME,value:r.name},value:t})}return{kind:s.h.OBJECT,fields:n}}if((0,l.UT)(t)){const n=t.serialize(e);if(null==n)return null;if("boolean"==typeof n)return{kind:s.h.BOOLEAN,value:n};if("number"==typeof n&&Number.isFinite(n)){const e=String(n);return d.test(e)?{kind:s.h.INT,value:e}:{kind:s.h.FLOAT,value:e}}if("string"==typeof n)return(0,l.EM)(t)?{kind:s.h.ENUM,value:n}:t===u.km&&d.test(n)?{kind:s.h.INT,value:n}:{kind:s.h.STRING,value:n};throw new TypeError(`Cannot convert value to AST: ${(0,r.X)(n)}.`)}(0,i.k)(!1,"Unexpected input type: "+(0,r.X)(t))}const d=/^-?(?:0|[1-9][0-9]*)$/},5925:function(e,t,n){"use strict";n.d(t,{K:function(){return p}});var r=n(8063),i=n(5648),o=n(5052),a=n(2910),s=n(1315),l=n(9878),u=n(4987),c=n(3492),d=n(4117),f=n(755);function p(e,t,n=h){return m(e,t,n,void 0)}function h(e,t,n){let r="Invalid value "+(0,i.X)(t);throw e.length>0&&(r+=` at "value${(0,u.F)(e)}"`),n.message=r+": "+n.message,n}function m(e,t,n,u){if((0,f.zM)(t))return null!=e?m(e,t.ofType,n,u):void n((0,l.N)(u),e,new d.__(`Expected non-nullable type "${(0,i.X)(t)}" not to be null.`));if(null==e)return null;if((0,f.HG)(t)){const r=t.ofType;return(0,a.i)(e)?Array.from(e,((e,t)=>{const i=(0,l.Q)(u,t,void 0);return m(e,r,n,i)})):[m(e,r,n,u)]}if((0,f.hL)(t)){if(!(0,s.y)(e))return void n((0,l.N)(u),e,new d.__(`Expected type "${t.name}" to be an object.`));const o={},a=t.getFields();for(const r of Object.values(a)){const a=e[r.name];if(void 0!==a)o[r.name]=m(a,r.type,n,(0,l.Q)(u,r.name,t.name));else if(void 0!==r.defaultValue)o[r.name]=r.defaultValue;else if((0,f.zM)(r.type)){const t=(0,i.X)(r.type);n((0,l.N)(u),e,new d.__(`Field "${r.name}" of required type "${t}" was not provided.`))}}for(const i of Object.keys(e))if(!a[i]){const o=(0,c.D)(i,Object.keys(t.getFields()));n((0,l.N)(u),e,new d.__(`Field "${i}" is not defined by type "${t.name}".`+(0,r.l)(o)))}return o}if((0,f.UT)(t)){let r;try{r=t.parseValue(e)}catch(r){return void(r instanceof d.__?n((0,l.N)(u),e,r):n((0,l.N)(u),e,new d.__(`Expected type "${t.name}". `+r.message,{originalError:r})))}return void 0===r&&n((0,l.N)(u),e,new d.__(`Expected type "${t.name}".`)),r}(0,o.k)(!1,"Unexpected input type: "+(0,i.X)(t))}},9458:function(e,t,n){"use strict";n.d(t,{S:function(){return i}});var r=n(3830);function i(e,t){let n=null;for(const o of e.definitions){var i;if(o.kind===r.h.OPERATION_DEFINITION)if(null==t){if(n)return null;n=o}else if((null===(i=o.name)||void 0===i?void 0:i.value)===t)return o}return n}},4034:function(e,t,n){"use strict";n.d(t,{n:function(){return o}});var r=n(6625),i=n(3830);function o(e){switch(e.kind){case i.h.OBJECT:return{...e,fields:(t=e.fields,t.map((e=>({...e,value:o(e.value)}))).sort(((e,t)=>(0,r.K)(e.name.value,t.name.value))))};case i.h.LIST:return{...e,values:e.values.map(o)};case i.h.INT:case i.h.FLOAT:case i.h.STRING:case i.h.BOOLEAN:case i.h.NULL:case i.h.ENUM:case i.h.VARIABLE:return e}var t}},2984:function(e,t,n){"use strict";n.d(t,{_7:function(){return i},uJ:function(){return o},zR:function(){return a}});var r=n(755);function i(e,t){return e===t||((0,r.zM)(e)&&(0,r.zM)(t)||!(!(0,r.HG)(e)||!(0,r.HG)(t)))&&i(e.ofType,t.ofType)}function o(e,t,n){return t===n||((0,r.zM)(n)?!!(0,r.zM)(t)&&o(e,t.ofType,n.ofType):(0,r.zM)(t)?o(e,t.ofType,n):(0,r.HG)(n)?!!(0,r.HG)(t)&&o(e,t.ofType,n.ofType):!(0,r.HG)(t)&&(0,r.m0)(n)&&((0,r.oT)(t)||(0,r.lp)(t))&&e.isSubType(n,t))}function a(e,t,n){return t===n||((0,r.m0)(t)?(0,r.m0)(n)?e.getPossibleTypes(t).some((t=>e.isSubType(n,t))):e.isSubType(t,n):!!(0,r.m0)(n)&&e.isSubType(n,t))}},5998:function(e,t,n){"use strict";n.d(t,{_:function(){return o}});var r=n(3830),i=n(755);function o(e,t){switch(t.kind){case r.h.LIST_TYPE:{const n=o(e,t.type);return n&&new i.p2(n)}case r.h.NON_NULL_TYPE:{const n=o(e,t.type);return n&&new i.bM(n)}case r.h.NAMED_TYPE:return e.getType(t.name.value)}}},5284:function(e,t,n){"use strict";n.d(t,{u:function(){return l}});var r=n(5648),i=n(5052),o=n(9815),a=n(3830),s=n(755);function l(e,t,n){if(e){if(e.kind===a.h.VARIABLE){const r=e.name.value;if(null==n||void 0===n[r])return;const i=n[r];if(null===i&&(0,s.zM)(t))return;return i}if((0,s.zM)(t)){if(e.kind===a.h.NULL)return;return l(e,t.ofType,n)}if(e.kind===a.h.NULL)return null;if((0,s.HG)(t)){const r=t.ofType;if(e.kind===a.h.LIST){const t=[];for(const i of e.values)if(u(i,n)){if((0,s.zM)(r))return;t.push(null)}else{const e=l(i,r,n);if(void 0===e)return;t.push(e)}return t}const i=l(e,r,n);if(void 0===i)return;return[i]}if((0,s.hL)(t)){if(e.kind!==a.h.OBJECT)return;const r=Object.create(null),i=(0,o.P)(e.fields,(e=>e.name.value));for(const e of Object.values(t.getFields())){const t=i[e.name];if(!t||u(t.value,n)){if(void 0!==e.defaultValue)r[e.name]=e.defaultValue;else if((0,s.zM)(e.type))return;continue}const o=l(t.value,e.type,n);if(void 0===o)return;r[e.name]=o}return r}if((0,s.UT)(t)){let r;try{r=t.parseLiteral(e,n)}catch(e){return}if(void 0===r)return;return r}(0,i.k)(!1,"Unexpected input type: "+(0,r.X)(t))}}function u(e,t){return e.kind===a.h.VARIABLE&&(null==t||void 0===t[e.name.value])}},9426:function(e,t,n){"use strict";n.d(t,{M:function(){return o}});var r=n(8240),i=n(3830);function o(e,t){switch(e.kind){case i.h.NULL:return null;case i.h.INT:return parseInt(e.value,10);case i.h.FLOAT:return parseFloat(e.value);case i.h.STRING:case i.h.ENUM:case i.h.BOOLEAN:return e.value;case i.h.LIST:return e.values.map((e=>o(e,t)));case i.h.OBJECT:return(0,r.w)(e.fields,(e=>e.name.value),(e=>o(e.value,t)));case i.h.VARIABLE:return null==t?void 0:t[e.name.value]}}},2716:function(e,t,n){"use strict";n.d(t,{_t:function(){return l},yv:function(){return s}});var r=n(3830),i=n(9685),o=n(1409);class a{constructor(e,t){this._ast=e,this._fragments=void 0,this._fragmentSpreads=new Map,this._recursivelyReferencedFragments=new Map,this._onError=t}get[Symbol.toStringTag](){return"ASTValidationContext"}reportError(e){this._onError(e)}getDocument(){return this._ast}getFragment(e){let t;if(this._fragments)t=this._fragments;else{t=Object.create(null);for(const e of this.getDocument().definitions)e.kind===r.h.FRAGMENT_DEFINITION&&(t[e.name.value]=e);this._fragments=t}return t[e]}getFragmentSpreads(e){let t=this._fragmentSpreads.get(e);if(!t){t=[];const n=[e];let i;for(;i=n.pop();)for(const e of i.selections)e.kind===r.h.FRAGMENT_SPREAD?t.push(e):e.selectionSet&&n.push(e.selectionSet);this._fragmentSpreads.set(e,t)}return t}getRecursivelyReferencedFragments(e){let t=this._recursivelyReferencedFragments.get(e);if(!t){t=[];const n=Object.create(null),r=[e.selectionSet];let i;for(;i=r.pop();)for(const e of this.getFragmentSpreads(i)){const i=e.name.value;if(!0!==n[i]){n[i]=!0;const e=this.getFragment(i);e&&(t.push(e),r.push(e.selectionSet))}}this._recursivelyReferencedFragments.set(e,t)}return t}}class s extends a{constructor(e,t,n){super(e,n),this._schema=t}get[Symbol.toStringTag](){return"SDLValidationContext"}getSchema(){return this._schema}}class l extends a{constructor(e,t,n,r){super(t,r),this._schema=e,this._typeInfo=n,this._variableUsages=new Map,this._recursiveVariableUsages=new Map}get[Symbol.toStringTag](){return"ValidationContext"}getSchema(){return this._schema}getVariableUsages(e){let t=this._variableUsages.get(e);if(!t){const n=[],r=new o.a(this._schema);(0,i.Vn)(e,(0,o.y)(r,{VariableDefinition:()=>!1,Variable(e){n.push({node:e,type:r.getInputType(),defaultValue:r.getDefaultValue()})}})),t=n,this._variableUsages.set(e,t)}return t}getRecursiveVariableUsages(e){let t=this._recursiveVariableUsages.get(e);if(!t){t=this.getVariableUsages(e);for(const n of this.getRecursivelyReferencedFragments(e))t=t.concat(this.getVariableUsages(n));this._recursiveVariableUsages.set(e,t)}return t}getType(){return this._typeInfo.getType()}getParentType(){return this._typeInfo.getParentType()}getInputType(){return this._typeInfo.getInputType()}getParentInputType(){return this._typeInfo.getParentInputType()}getFieldDef(){return this._typeInfo.getFieldDef()}getDirective(){return this._typeInfo.getDirective()}getArgument(){return this._typeInfo.getArgument()}getEnumValue(){return this._typeInfo.getEnumValue()}}},3857:function(e,t,n){"use strict";n.d(t,{i:function(){return a}});var r=n(4117),i=n(3830),o=n(9615);function a(e){return{Document(t){for(const n of t.definitions)if(!(0,o.Wk)(n)){const t=n.kind===i.h.SCHEMA_DEFINITION||n.kind===i.h.SCHEMA_EXTENSION?"schema":'"'+n.name.value+'"';e.reportError(new r.__(`The ${t} definition is not executable.`,{nodes:n}))}return!1}}}},1870:function(e,t,n){"use strict";n.d(t,{A:function(){return l}});var r=n(8063),i=n(6625),o=n(3492),a=n(4117),s=n(755);function l(e){return{Field(t){const n=e.getParentType();if(n&&!e.getFieldDef()){const l=e.getSchema(),u=t.name.value;let c=(0,r.l)("to use an inline fragment on",function(e,t,n){if(!(0,s.m0)(t))return[];const r=new Set,o=Object.create(null);for(const i of e.getPossibleTypes(t))if(i.getFields()[n]){r.add(i),o[i.name]=1;for(const e of i.getInterfaces()){var a;e.getFields()[n]&&(r.add(e),o[e.name]=(null!==(a=o[e.name])&&void 0!==a?a:0)+1)}}return[...r].sort(((t,n)=>{const r=o[n.name]-o[t.name];return 0!==r?r:(0,s.oT)(t)&&e.isSubType(t,n)?-1:(0,s.oT)(n)&&e.isSubType(n,t)?1:(0,i.K)(t.name,n.name)})).map((e=>e.name))}(l,n,u));""===c&&(c=(0,r.l)(function(e,t){if((0,s.lp)(e)||(0,s.oT)(e)){const n=Object.keys(e.getFields());return(0,o.D)(t,n)}return[]}(n,u))),e.reportError(new a.__(`Cannot query field "${u}" on type "${n.name}".`+c,{nodes:t}))}}}}},5167:function(e,t,n){"use strict";n.d(t,{T:function(){return s}});var r=n(4117),i=n(5895),o=n(755),a=n(5998);function s(e){return{InlineFragment(t){const n=t.typeCondition;if(n){const t=(0,a._)(e.getSchema(),n);if(t&&!(0,o.Gv)(t)){const t=(0,i.S)(n);e.reportError(new r.__(`Fragment cannot condition on non composite type "${t}".`,{nodes:n}))}}},FragmentDefinition(t){const n=(0,a._)(e.getSchema(),t.typeCondition);if(n&&!(0,o.Gv)(n)){const n=(0,i.S)(t.typeCondition);e.reportError(new r.__(`Fragment "${t.name.value}" cannot condition on non composite type "${n}".`,{nodes:t.typeCondition}))}}}}},4875:function(e,t,n){"use strict";n.d(t,{e:function(){return l},o:function(){return u}});var r=n(8063),i=n(3492),o=n(4117),a=n(3830),s=n(5946);function l(e){return{...u(e),Argument(t){const n=e.getArgument(),a=e.getFieldDef(),s=e.getParentType();if(!n&&a&&s){const n=t.name.value,l=a.args.map((e=>e.name)),u=(0,i.D)(n,l);e.reportError(new o.__(`Unknown argument "${n}" on field "${s.name}.${a.name}".`+(0,r.l)(u),{nodes:t}))}}}}function u(e){const t=Object.create(null),n=e.getSchema(),l=n?n.getDirectives():s.V4;for(const e of l)t[e.name]=e.args.map((e=>e.name));const u=e.getDocument().definitions;for(const e of u)if(e.kind===a.h.DIRECTIVE_DEFINITION){var c;const n=null!==(c=e.arguments)&&void 0!==c?c:[];t[e.name.value]=n.map((e=>e.name.value))}return{Directive(n){const a=n.name.value,s=t[a];if(n.arguments&&s)for(const t of n.arguments){const n=t.name.value;if(!s.includes(n)){const l=(0,i.D)(n,s);e.reportError(new o.__(`Unknown argument "${n}" on directive "@${a}".`+(0,r.l)(l),{nodes:t}))}}return!1}}}},7513:function(e,t,n){"use strict";n.d(t,{J:function(){return c}});var r=n(5648),i=n(5052),o=n(4117),a=n(3526),s=n(3140),l=n(3830),u=n(5946);function c(e){const t=Object.create(null),n=e.getSchema(),c=n?n.getDirectives():u.V4;for(const e of c)t[e.name]=e.locations;const d=e.getDocument().definitions;for(const e of d)e.kind===l.h.DIRECTIVE_DEFINITION&&(t[e.name.value]=e.locations.map((e=>e.value)));return{Directive(n,u,c,d,f){const p=n.name.value,h=t[p];if(!h)return void e.reportError(new o.__(`Unknown directive "@${p}".`,{nodes:n}));const m=function(e){const t=e[e.length-1];switch("kind"in t||(0,i.k)(!1),t.kind){case l.h.OPERATION_DEFINITION:return function(e){switch(e){case a.ku.QUERY:return s.B.QUERY;case a.ku.MUTATION:return s.B.MUTATION;case a.ku.SUBSCRIPTION:return s.B.SUBSCRIPTION}}(t.operation);case l.h.FIELD:return s.B.FIELD;case l.h.FRAGMENT_SPREAD:return s.B.FRAGMENT_SPREAD;case l.h.INLINE_FRAGMENT:return s.B.INLINE_FRAGMENT;case l.h.FRAGMENT_DEFINITION:return s.B.FRAGMENT_DEFINITION;case l.h.VARIABLE_DEFINITION:return s.B.VARIABLE_DEFINITION;case l.h.SCHEMA_DEFINITION:case l.h.SCHEMA_EXTENSION:return s.B.SCHEMA;case l.h.SCALAR_TYPE_DEFINITION:case l.h.SCALAR_TYPE_EXTENSION:return s.B.SCALAR;case l.h.OBJECT_TYPE_DEFINITION:case l.h.OBJECT_TYPE_EXTENSION:return s.B.OBJECT;case l.h.FIELD_DEFINITION:return s.B.FIELD_DEFINITION;case l.h.INTERFACE_TYPE_DEFINITION:case l.h.INTERFACE_TYPE_EXTENSION:return s.B.INTERFACE;case l.h.UNION_TYPE_DEFINITION:case l.h.UNION_TYPE_EXTENSION:return s.B.UNION;case l.h.ENUM_TYPE_DEFINITION:case l.h.ENUM_TYPE_EXTENSION:return s.B.ENUM;case l.h.ENUM_VALUE_DEFINITION:return s.B.ENUM_VALUE;case l.h.INPUT_OBJECT_TYPE_DEFINITION:case l.h.INPUT_OBJECT_TYPE_EXTENSION:return s.B.INPUT_OBJECT;case l.h.INPUT_VALUE_DEFINITION:{const t=e[e.length-3];return"kind"in t||(0,i.k)(!1),t.kind===l.h.INPUT_OBJECT_TYPE_DEFINITION?s.B.INPUT_FIELD_DEFINITION:s.B.ARGUMENT_DEFINITION}default:(0,i.k)(!1,"Unexpected kind: "+(0,r.X)(t.kind))}}(f);m&&!h.includes(m)&&e.reportError(new o.__(`Directive "@${p}" may not be used on ${m}.`,{nodes:n}))}}}},1435:function(e,t,n){"use strict";n.d(t,{a:function(){return i}});var r=n(4117);function i(e){return{FragmentSpread(t){const n=t.name.value;e.getFragment(n)||e.reportError(new r.__(`Unknown fragment "${n}".`,{nodes:t.name}))}}}},591:function(e,t,n){"use strict";n.d(t,{I:function(){return l}});var r=n(8063),i=n(3492),o=n(4117),a=n(9615),s=n(8078);function l(e){const t=e.getSchema(),n=t?t.getTypeMap():Object.create(null),s=Object.create(null);for(const t of e.getDocument().definitions)(0,a.zT)(t)&&(s[t.name.value]=!0);const l=[...Object.keys(n),...Object.keys(s)];return{NamedType(t,c,d,f,p){const h=t.name.value;if(!n[h]&&!s[h]){var m;const n=null!==(m=p[2])&&void 0!==m?m:d,s=null!=n&&"kind"in(g=n)&&((0,a.G4)(g)||(0,a.aU)(g));if(s&&u.includes(h))return;const c=(0,i.D)(h,s?u.concat(l):l);e.reportError(new o.__(`Unknown type "${h}".`+(0,r.l)(c),{nodes:t}))}var g}}}const u=[...n(1774).HS,...s.nL].map((e=>e.name))},831:function(e,t,n){"use strict";n.d(t,{F:function(){return o}});var r=n(4117),i=n(3830);function o(e){let t=0;return{Document(e){t=e.definitions.filter((e=>e.kind===i.h.OPERATION_DEFINITION)).length},OperationDefinition(n){!n.name&&t>1&&e.reportError(new r.__("This anonymous operation must be the only defined operation.",{nodes:n}))}}}},3402:function(e,t,n){"use strict";n.d(t,{t:function(){return i}});var r=n(4117);function i(e){var t,n,i;const o=e.getSchema(),a=null!==(t=null!==(n=null!==(i=null==o?void 0:o.astNode)&&void 0!==i?i:null==o?void 0:o.getQueryType())&&void 0!==n?n:null==o?void 0:o.getMutationType())&&void 0!==t?t:null==o?void 0:o.getSubscriptionType();let s=0;return{SchemaDefinition(t){a?e.reportError(new r.__("Cannot define a new schema within a schema extension.",{nodes:t})):(s>0&&e.reportError(new r.__("Must provide only one schema definition.",{nodes:t})),++s)}}}},9316:function(e,t,n){"use strict";n.d(t,{H:function(){return i}});var r=n(4117);function i(e){const t=Object.create(null),n=[],i=Object.create(null);return{OperationDefinition:()=>!1,FragmentDefinition(e){return o(e),!1}};function o(a){if(t[a.name.value])return;const s=a.name.value;t[s]=!0;const l=e.getFragmentSpreads(a.selectionSet);if(0!==l.length){i[s]=n.length;for(const t of l){const a=t.name.value,s=i[a];if(n.push(t),void 0===s){const t=e.getFragment(a);t&&o(t)}else{const t=n.slice(s),i=t.slice(0,-1).map((e=>'"'+e.name.value+'"')).join(", ");e.reportError(new r.__(`Cannot spread fragment "${a}" within itself`+(""!==i?` via ${i}.`:"."),{nodes:t}))}n.pop()}i[s]=void 0}}}},9518:function(e,t,n){"use strict";n.d(t,{$:function(){return i}});var r=n(4117);function i(e){let t=Object.create(null);return{OperationDefinition:{enter(){t=Object.create(null)},leave(n){const i=e.getRecursiveVariableUsages(n);for(const{node:o}of i){const i=o.name.value;!0!==t[i]&&e.reportError(new r.__(n.name?`Variable "$${i}" is not defined by operation "${n.name.value}".`:`Variable "$${i}" is not defined.`,{nodes:[o,n]}))}}},VariableDefinition(e){t[e.variable.name.value]=!0}}}},3447:function(e,t,n){"use strict";n.d(t,{J:function(){return i}});var r=n(4117);function i(e){const t=[],n=[];return{OperationDefinition(e){return t.push(e),!1},FragmentDefinition(e){return n.push(e),!1},Document:{leave(){const i=Object.create(null);for(const n of t)for(const t of e.getRecursivelyReferencedFragments(n))i[t.name.value]=!0;for(const t of n){const n=t.name.value;!0!==i[n]&&e.reportError(new r.__(`Fragment "${n}" is never used.`,{nodes:t}))}}}}}},7114:function(e,t,n){"use strict";n.d(t,{p:function(){return i}});var r=n(4117);function i(e){let t=[];return{OperationDefinition:{enter(){t=[]},leave(n){const i=Object.create(null),o=e.getRecursiveVariableUsages(n);for(const{node:e}of o)i[e.name.value]=!0;for(const o of t){const t=o.variable.name.value;!0!==i[t]&&e.reportError(new r.__(n.name?`Variable "$${t}" is never used in operation "${n.name.value}".`:`Variable "$${t}" is never used.`,{nodes:o}))}}},VariableDefinition(e){t.push(e)}}}},7163:function(e,t,n){"use strict";n.d(t,{y:function(){return d}});var r=n(5648),i=n(4117),o=n(3830),a=n(5895),s=n(755),l=n(4034),u=n(5998);function c(e){return Array.isArray(e)?e.map((([e,t])=>`subfields "${e}" conflict because `+c(t))).join(" and "):e}function d(e){const t=new T,n=new Map;return{SelectionSet(r){const o=function(e,t,n,r,i){const o=[],[a,s]=y(e,t,r,i);if(function(e,t,n,r,i){for(const[o,a]of Object.entries(i))if(a.length>1)for(let i=0;i0)return[[t,e.map((([e])=>e))],[n,...e.map((([,e])=>e)).flat()],[r,...e.map((([,,e])=>e)).flat()]]}(r,o,c,b)}}function g(e){var t;const n=null!==(t=e.arguments)&&void 0!==t?t:[],r={kind:o.h.OBJECT,fields:n.map((e=>({kind:o.h.OBJECT_FIELD,name:e.name,value:e.value})))};return(0,a.S)((0,l.n)(r))}function v(e,t){return(0,s.HG)(e)?!(0,s.HG)(t)||v(e.ofType,t.ofType):!!(0,s.HG)(t)||((0,s.zM)(e)?!(0,s.zM)(t)||v(e.ofType,t.ofType):!!(0,s.zM)(t)||!(!(0,s.UT)(e)&&!(0,s.UT)(t))&&e!==t)}function y(e,t,n,r){const i=t.get(r);if(i)return i;const o=Object.create(null),a=Object.create(null);E(e,n,r,o,a);const s=[o,Object.keys(a)];return t.set(r,s),s}function b(e,t,n){const r=t.get(n.selectionSet);if(r)return r;const i=(0,u._)(e.getSchema(),n.typeCondition);return y(e,t,i,n.selectionSet)}function E(e,t,n,r,i){for(const a of n.selections)switch(a.kind){case o.h.FIELD:{const e=a.name.value;let n;((0,s.lp)(t)||(0,s.oT)(t))&&(n=t.getFields()[e]);const i=a.alias?a.alias.value:e;r[i]||(r[i]=[]),r[i].push([t,a,n]);break}case o.h.FRAGMENT_SPREAD:i[a.name.value]=!0;break;case o.h.INLINE_FRAGMENT:{const n=a.typeCondition,o=n?(0,u._)(e.getSchema(),n):t;E(e,o,a.selectionSet,r,i);break}}}class T{constructor(){this._data=new Map}has(e,t,n){var r;const[i,o]=ee.name.value)));for(const n of i.args)if(!a.has(n.name)&&(0,l.dK)(n)){const a=(0,r.X)(n.type);e.reportError(new o.__(`Field "${i.name}" argument "${n.name}" of type "${a}" is required, but it was not provided.`,{nodes:t}))}}}}}function d(e){var t;const n=Object.create(null),c=e.getSchema(),d=null!==(t=null==c?void 0:c.getDirectives())&&void 0!==t?t:u.V4;for(const e of d)n[e.name]=(0,i.P)(e.args.filter(l.dK),(e=>e.name));const p=e.getDocument().definitions;for(const e of p)if(e.kind===a.h.DIRECTIVE_DEFINITION){var h;const t=null!==(h=e.arguments)&&void 0!==h?h:[];n[e.name.value]=(0,i.P)(t.filter(f),(e=>e.name.value))}return{Directive:{leave(t){const i=t.name.value,a=n[i];if(a){var u;const n=null!==(u=t.arguments)&&void 0!==u?u:[],c=new Set(n.map((e=>e.name.value)));for(const[n,u]of Object.entries(a))if(!c.has(n)){const a=(0,l.P9)(u.type)?(0,r.X)(u.type):(0,s.S)(u.type);e.reportError(new o.__(`Directive "@${i}" argument "${n}" of type "${a}" is required, but it was not provided.`,{nodes:t}))}}}}}}function f(e){return e.type.kind===a.h.NON_NULL_TYPE&&null==e.defaultValue}},3989:function(e,t,n){"use strict";n.d(t,{O:function(){return a}});var r=n(5648),i=n(4117),o=n(755);function a(e){return{Field(t){const n=e.getType(),a=t.selectionSet;if(n)if((0,o.UT)((0,o.xC)(n))){if(a){const o=t.name.value,s=(0,r.X)(n);e.reportError(new i.__(`Field "${o}" must not have a selection since type "${s}" has no subfields.`,{nodes:a}))}}else if(!a){const o=t.name.value,a=(0,r.X)(n);e.reportError(new i.__(`Field "${o}" of type "${a}" must have a selection of subfields. Did you mean "${o} { ... }"?`,{nodes:t}))}}}}},9309:function(e,t,n){"use strict";n.d(t,{Z:function(){return a}});var r=n(4117),i=n(3830),o=n(5267);function a(e){return{OperationDefinition(t){if("subscription"===t.operation){const n=e.getSchema(),a=n.getSubscriptionType();if(a){const s=t.name?t.name.value:null,l=Object.create(null),u=e.getDocument(),c=Object.create(null);for(const e of u.definitions)e.kind===i.h.FRAGMENT_DEFINITION&&(c[e.name.value]=e);const d=(0,o.g)(n,c,l,a,t.selectionSet);if(d.size>1){const t=[...d.values()].slice(1).flat();e.reportError(new r.__(null!=s?`Subscription "${s}" must select only one top level field.`:"Anonymous Subscription must select only one top level field.",{nodes:t}))}for(const t of d.values())t[0].name.value.startsWith("__")&&e.reportError(new r.__(null!=s?`Subscription "${s}" must not select an introspection top level field.`:"Anonymous Subscription must not select an introspection top level field.",{nodes:t}))}}}}}},5947:function(e,t,n){"use strict";n.d(t,{L:function(){return o}});var r=n(5839),i=n(4117);function o(e){return{DirectiveDefinition(e){var t;const r=null!==(t=e.arguments)&&void 0!==t?t:[];return n(`@${e.name.value}`,r)},InterfaceTypeDefinition:t,InterfaceTypeExtension:t,ObjectTypeDefinition:t,ObjectTypeExtension:t};function t(e){var t;const r=e.name.value,i=null!==(t=e.fields)&&void 0!==t?t:[];for(const e of i){var o;n(`${r}.${e.name.value}`,null!==(o=e.arguments)&&void 0!==o?o:[])}return!1}function n(t,n){const o=(0,r.v)(n,(e=>e.name.value));for(const[n,r]of o)r.length>1&&e.reportError(new i.__(`Argument "${t}(${n}:)" can only be defined once.`,{nodes:r.map((e=>e.name))}));return!1}}},9168:function(e,t,n){"use strict";n.d(t,{L:function(){return o}});var r=n(5839),i=n(4117);function o(e){return{Field:t,Directive:t};function t(t){var n;const o=null!==(n=t.arguments)&&void 0!==n?n:[],a=(0,r.v)(o,(e=>e.name.value));for(const[t,n]of a)n.length>1&&e.reportError(new i.__(`There can be only one argument named "${t}".`,{nodes:n.map((e=>e.name))}))}}},5681:function(e,t,n){"use strict";n.d(t,{o:function(){return i}});var r=n(4117);function i(e){const t=Object.create(null),n=e.getSchema();return{DirectiveDefinition(i){const o=i.name.value;if(null==n||!n.getDirective(o))return t[o]?e.reportError(new r.__(`There can be only one directive named "@${o}".`,{nodes:[t[o],i.name]})):t[o]=i.name,!1;e.reportError(new r.__(`Directive "@${o}" already exists in the schema. It cannot be redefined.`,{nodes:i.name}))}}}},5673:function(e,t,n){"use strict";n.d(t,{k:function(){return s}});var r=n(4117),i=n(3830),o=n(9615),a=n(5946);function s(e){const t=Object.create(null),n=e.getSchema(),s=n?n.getDirectives():a.V4;for(const e of s)t[e.name]=!e.isRepeatable;const l=e.getDocument().definitions;for(const e of l)e.kind===i.h.DIRECTIVE_DEFINITION&&(t[e.name.value]=!e.repeatable);const u=Object.create(null),c=Object.create(null);return{enter(n){if(!("directives"in n)||!n.directives)return;let a;if(n.kind===i.h.SCHEMA_DEFINITION||n.kind===i.h.SCHEMA_EXTENSION)a=u;else if((0,o.zT)(n)||(0,o.D$)(n)){const e=n.name.value;a=c[e],void 0===a&&(c[e]=a=Object.create(null))}else a=Object.create(null);for(const i of n.directives){const n=i.name.value;t[n]&&(a[n]?e.reportError(new r.__(`The directive "@${n}" can only be used once at this location.`,{nodes:[a[n],i]})):a[n]=i)}}}}},4560:function(e,t,n){"use strict";n.d(t,{L:function(){return o}});var r=n(4117),i=n(755);function o(e){const t=e.getSchema(),n=t?t.getTypeMap():Object.create(null),o=Object.create(null);return{EnumTypeDefinition:a,EnumTypeExtension:a};function a(t){var a;const s=t.name.value;o[s]||(o[s]=Object.create(null));const l=null!==(a=t.values)&&void 0!==a?a:[],u=o[s];for(const t of l){const o=t.name.value,a=n[s];(0,i.EM)(a)&&a.getValue(o)?e.reportError(new r.__(`Enum value "${s}.${o}" already exists in the schema. It cannot also be defined in this type extension.`,{nodes:t.name})):u[o]?e.reportError(new r.__(`Enum value "${s}.${o}" can only be defined once.`,{nodes:[u[o],t.name]})):u[o]=t.name}return!1}}},5240:function(e,t,n){"use strict";n.d(t,{y:function(){return o}});var r=n(4117),i=n(755);function o(e){const t=e.getSchema(),n=t?t.getTypeMap():Object.create(null),i=Object.create(null);return{InputObjectTypeDefinition:o,InputObjectTypeExtension:o,InterfaceTypeDefinition:o,InterfaceTypeExtension:o,ObjectTypeDefinition:o,ObjectTypeExtension:o};function o(t){var o;const s=t.name.value;i[s]||(i[s]=Object.create(null));const l=null!==(o=t.fields)&&void 0!==o?o:[],u=i[s];for(const t of l){const i=t.name.value;a(n[s],i)?e.reportError(new r.__(`Field "${s}.${i}" already exists in the schema. It cannot also be defined in this type extension.`,{nodes:t.name})):u[i]?e.reportError(new r.__(`Field "${s}.${i}" can only be defined once.`,{nodes:[u[i],t.name]})):u[i]=t.name}return!1}}function a(e,t){return!!((0,i.lp)(e)||(0,i.oT)(e)||(0,i.hL)(e))&&null!=e.getFields()[t]}},614:function(e,t,n){"use strict";n.d(t,{N:function(){return i}});var r=n(4117);function i(e){const t=Object.create(null);return{OperationDefinition:()=>!1,FragmentDefinition(n){const i=n.name.value;return t[i]?e.reportError(new r.__(`There can be only one fragment named "${i}".`,{nodes:[t[i],n.name]})):t[i]=n.name,!1}}}},5707:function(e,t,n){"use strict";n.d(t,{P:function(){return o}});var r=n(5052),i=n(4117);function o(e){const t=[];let n=Object.create(null);return{ObjectValue:{enter(){t.push(n),n=Object.create(null)},leave(){const e=t.pop();e||(0,r.k)(!1),n=e}},ObjectField(t){const r=t.name.value;n[r]?e.reportError(new i.__(`There can be only one input field named "${r}".`,{nodes:[n[r],t.name]})):n[r]=t.name}}}},2355:function(e,t,n){"use strict";n.d(t,{H:function(){return i}});var r=n(4117);function i(e){const t=Object.create(null);return{OperationDefinition(n){const i=n.name;return i&&(t[i.value]?e.reportError(new r.__(`There can be only one operation named "${i.value}".`,{nodes:[t[i.value],i]})):t[i.value]=i),!1},FragmentDefinition:()=>!1}}},5427:function(e,t,n){"use strict";n.d(t,{q:function(){return i}});var r=n(4117);function i(e){const t=e.getSchema(),n=Object.create(null),i=t?{query:t.getQueryType(),mutation:t.getMutationType(),subscription:t.getSubscriptionType()}:{};return{SchemaDefinition:o,SchemaExtension:o};function o(t){var o;const a=null!==(o=t.operationTypes)&&void 0!==o?o:[];for(const t of a){const o=t.operation,a=n[o];i[o]?e.reportError(new r.__(`Type for ${o} already defined in the schema. It cannot be redefined.`,{nodes:t})):a?e.reportError(new r.__(`There can be only one ${o} type in schema.`,{nodes:[a,t]})):n[o]=t}return!1}}},4519:function(e,t,n){"use strict";n.d(t,{P:function(){return i}});var r=n(4117);function i(e){const t=Object.create(null),n=e.getSchema();return{ScalarTypeDefinition:i,ObjectTypeDefinition:i,InterfaceTypeDefinition:i,UnionTypeDefinition:i,EnumTypeDefinition:i,InputObjectTypeDefinition:i};function i(i){const o=i.name.value;if(null==n||!n.getType(o))return t[o]?e.reportError(new r.__(`There can be only one type named "${o}".`,{nodes:[t[o],i.name]})):t[o]=i.name,!1;e.reportError(new r.__(`Type "${o}" already exists in the schema. It cannot also be defined in this type definition.`,{nodes:i.name}))}}},7417:function(e,t,n){"use strict";n.d(t,{H:function(){return o}});var r=n(5839),i=n(4117);function o(e){return{OperationDefinition(t){var n;const o=null!==(n=t.variableDefinitions)&&void 0!==n?n:[],a=(0,r.v)(o,(e=>e.variable.name.value));for(const[t,n]of a)n.length>1&&e.reportError(new i.__(`There can be only one variable named "$${t}".`,{nodes:n.map((e=>e.variable.name))}))}}}},4697:function(e,t,n){"use strict";n.d(t,{j:function(){return c}});var r=n(8063),i=n(5648),o=n(9815),a=n(3492),s=n(4117),l=n(5895),u=n(755);function c(e){return{ListValue(t){const n=(0,u.tf)(e.getParentInputType());if(!(0,u.HG)(n))return d(e,t),!1},ObjectValue(t){const n=(0,u.xC)(e.getInputType());if(!(0,u.hL)(n))return d(e,t),!1;const r=(0,o.P)(t.fields,(e=>e.name.value));for(const o of Object.values(n.getFields()))if(!r[o.name]&&(0,u.Wd)(o)){const r=(0,i.X)(o.type);e.reportError(new s.__(`Field "${n.name}.${o.name}" of required type "${r}" was not provided.`,{nodes:t}))}},ObjectField(t){const n=(0,u.xC)(e.getParentInputType());if(!e.getInputType()&&(0,u.hL)(n)){const i=(0,a.D)(t.name.value,Object.keys(n.getFields()));e.reportError(new s.__(`Field "${t.name.value}" is not defined by type "${n.name}".`+(0,r.l)(i),{nodes:t}))}},NullValue(t){const n=e.getInputType();(0,u.zM)(n)&&e.reportError(new s.__(`Expected value of type "${(0,i.X)(n)}", found ${(0,l.S)(t)}.`,{nodes:t}))},EnumValue:t=>d(e,t),IntValue:t=>d(e,t),FloatValue:t=>d(e,t),StringValue:t=>d(e,t),BooleanValue:t=>d(e,t)}}function d(e,t){const n=e.getInputType();if(!n)return;const r=(0,u.xC)(n);if((0,u.UT)(r))try{if(void 0===r.parseLiteral(t,void 0)){const r=(0,i.X)(n);e.reportError(new s.__(`Expected value of type "${r}", found ${(0,l.S)(t)}.`,{nodes:t}))}}catch(r){const o=(0,i.X)(n);r instanceof s.__?e.reportError(r):e.reportError(new s.__(`Expected value of type "${o}", found ${(0,l.S)(t)}; `+r.message,{nodes:t,originalError:r}))}else{const r=(0,i.X)(n);e.reportError(new s.__(`Expected value of type "${r}", found ${(0,l.S)(t)}.`,{nodes:t}))}}},6192:function(e,t,n){"use strict";n.d(t,{I:function(){return s}});var r=n(4117),i=n(5895),o=n(755),a=n(5998);function s(e){return{VariableDefinition(t){const n=(0,a._)(e.getSchema(),t.type);if(void 0!==n&&!(0,o.j$)(n)){const n=t.variable.name.value,o=(0,i.S)(t.type);e.reportError(new r.__(`Variable "$${n}" cannot be non-input type "${o}".`,{nodes:t.type}))}}}}},377:function(e,t,n){"use strict";n.d(t,{w:function(){return u}});var r=n(5648),i=n(4117),o=n(3830),a=n(755),s=n(2984),l=n(5998);function u(e){let t=Object.create(null);return{OperationDefinition:{enter(){t=Object.create(null)},leave(n){const o=e.getRecursiveVariableUsages(n);for(const{node:n,type:a,defaultValue:s}of o){const o=n.name.value,u=t[o];if(u&&a){const t=e.getSchema(),d=(0,l._)(t,u.type);if(d&&!c(t,d,u.defaultValue,a,s)){const t=(0,r.X)(d),s=(0,r.X)(a);e.reportError(new i.__(`Variable "$${o}" of type "${t}" used in position expecting type "${s}".`,{nodes:[u,n]}))}}}}},VariableDefinition(e){t[e.variable.name.value]=e}}}function c(e,t,n,r,i){if((0,a.zM)(r)&&!(0,a.zM)(t)){if((null==n||n.kind===o.h.NULL)&&void 0===i)return!1;const a=r.ofType;return(0,s.uJ)(e,t,a)}return(0,s.uJ)(e,t,r)}},9299:function(e,t,n){"use strict";n.d(t,{M:function(){return j},i:function(){return P}});var r=n(3857),i=n(1870),o=n(5167),a=n(4875),s=n(7513),l=n(1435),u=n(591),c=n(831),d=n(3402),f=n(9316),p=n(9518),h=n(3447),m=n(7114),g=n(7163),v=n(5961),y=n(3721),b=n(16),E=n(3989),T=n(9309),w=n(5947),C=n(9168),S=n(5681),x=n(5673),k=n(4560),N=n(5240),_=n(614),O=n(5707),I=n(2355),D=n(5427),L=n(4519),A=n(7417),M=n(4697),R=n(6192),F=n(377);const P=Object.freeze([r.i,I.H,c.F,T.Z,u.I,o.T,R.I,E.O,i.A,_.N,l.a,h.J,v.a,f.H,A.H,p.$,m.p,s.J,x.k,a.e,C.L,M.j,b.s,F.w,g.y,O.P]),j=Object.freeze([d.t,D.q,L.P,k.L,N.y,w.L,S.o,u.I,s.J,x.k,y.g,a.o,C.L,O.P,b.c])},2780:function(e,t,n){"use strict";n.d(t,{ED:function(){return p},Gu:function(){return c},zo:function(){return f}});var r=n(1172),i=n(4117),o=n(9685),a=n(8555),s=n(1409),l=n(9299),u=n(2716);function c(e,t,n=l.i,c,d=new s.a(e)){var f;const p=null!==(f=null==c?void 0:c.maxErrors)&&void 0!==f?f:100;t||(0,r.a)(!1,"Must provide document."),(0,a.J)(e);const h=Object.freeze({}),m=[],g=new u._t(e,t,d,(e=>{if(m.length>=p)throw m.push(new i.__("Too many validation errors, error limit reached. Validation aborted.")),h;m.push(e)})),v=(0,o.j1)(n.map((e=>e(g))));try{(0,o.Vn)(t,(0,s.y)(d,v))}catch(e){if(e!==h)throw e}return m}function d(e,t,n=l.M){const r=[],i=new u.yv(e,t,(e=>{r.push(e)})),a=n.map((e=>e(i)));return(0,o.Vn)(e,(0,o.j1)(a)),r}function f(e){const t=d(e);if(0!==t.length)throw new Error(t.map((e=>e.message)).join("\n\n"))}function p(e,t){const n=d(e,t);if(0!==n.length)throw new Error(n.map((e=>e.message)).join("\n\n"))}},8488:function(e,t,n){"use strict";n.r(t),n.d(t,{meros:function(){return o}});const r="\r\n\r\n",i=new TextDecoder;async function o(e,t){if(!e.ok||!e.body||e.bodyUsed)return e;const n=e.headers.get("content-type");if(!n||!~n.indexOf("multipart/mixed"))return e;const o=n.indexOf("boundary=");return async function*(e,t,n){const o=e.getReader(),a=!n||!n.multiple;let s="",l=!0,u=[];try{let e;e:for(;!(e=await o.read()).done;){const n=i.decode(e.value),o=n.indexOf(t);let c=s.length;for(s+=n,~o?c+=o:c=s.indexOf(t),u=[];~c;){const e=s.substring(0,c),n=s.substring(c+t.length);if(l)l=!1;else{const t={},i=e.indexOf(r),o=s.slice(0,i).toString().trim().split(/\r\n/);let l;for(;l=o.shift();)l=l.split(": "),t[l.shift().toLowerCase()]=l.join(": ");let c=e.substring(i+r.length,e.lastIndexOf("\r\n")),d=!1;if(l=t["content-type"],l&&~l.indexOf("application/json"))try{c=JSON.parse(c),d=!0}catch(e){}if(l={headers:t,body:c,json:d},a?yield l:u.push(l),"--"===n.substring(0,2))break e}s=n,c=s.indexOf(t)}u.length&&(yield u)}}finally{u.length&&(yield u),o.releaseLock()}}(e.body,`--${~o?n.substring(o+9).trim().replace(/['"]/g,""):"-"}`,t)}},6785:function(e,t,n){"use strict";n.r(t)},5605:function(e,t,n){"use strict";n.r(t)},2162:function(e,t,n){"use strict";n.r(t)},5251:function(e,t,n){"use strict";n.r(t)},9196:function(e){"use strict";e.exports=window.React},1850:function(e){"use strict";e.exports=window.ReactDOM}},r={};function i(e){var t=r[e];if(void 0!==t)return t.exports;var o=r[e]={exports:{}};return n[e].call(o.exports,o,o.exports,i),o.exports}t=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},i.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var o=Object.create(null);i.r(o);var a={};e=e||[null,t({}),t([]),t(t)];for(var s=2&r&&n;"object"==typeof s&&!~e.indexOf(s);s=t(s))Object.getOwnPropertyNames(s).forEach((function(e){a[e]=function(){return n[e]}}));return a.default=function(){return n},i.d(o,a),o},i.d=function(e,t){for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.nc=void 0;var o=i(6676);window.GraphiQL=o.default}(); +//# sourceMappingURL=graphiql.min.js.map \ No newline at end of file diff --git a/graphql/internal/graphiql/index.html b/graphql/internal/graphiql/index.html new file mode 100644 index 0000000..fabe61e --- /dev/null +++ b/graphql/internal/graphiql/index.html @@ -0,0 +1,46 @@ + + + + + GraphiQL + + + + + + + + + +
    Loading...
    + + + + \ No newline at end of file diff --git a/graphql/internal/graphiql/react-dom.production.min.js b/graphql/internal/graphiql/react-dom.production.min.js new file mode 100644 index 0000000..e38d120 --- /dev/null +++ b/graphql/internal/graphiql/react-dom.production.min.js @@ -0,0 +1,245 @@ +/** @license React v17.0.2 + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +(function(){/* + Modernizr 3.0.0pre (Custom Build) | MIT +*/ +'use strict';(function(M,ha){"object"===typeof exports&&"undefined"!==typeof module?ha(exports,require("react")):"function"===typeof define&&define.amd?define(["exports","react"],ha):(M=M||self,ha(M.ReactDOM={},M.React))})(this,function(M,ha){function m(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;cb}return!1}function Q(a,b,c,d,e,f,g){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f;this.removeEmptyString=g}function Ed(a,b,c,d){var e=I.hasOwnProperty(b)?I[b]:null;var f=null!==e?0===e.type:d?!1:!(2h||e[g]!==f[h])return"\n"+e[g].replace(" at new "," at ");while(1<=g&&0<=h)}break}}}finally{Gd=!1,Error.prepareStackTrace=c}return(a=a?a.displayName||a.name:"")?Kb(a):""}function pi(a){switch(a.tag){case 5:return Kb(a.type);case 16:return Kb("Lazy");case 13:return Kb("Suspense"); +case 19:return Kb("SuspenseList");case 0:case 2:case 15:return a=Bc(a.type,!1),a;case 11:return a=Bc(a.type.render,!1),a;case 22:return a=Bc(a.type._render,!1),a;case 1:return a=Bc(a.type,!0),a;default:return""}}function hb(a){if(null==a)return null;if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case wa:return"Fragment";case Ua:return"Portal";case Lb:return"Profiler";case Hd:return"StrictMode";case Mb:return"Suspense";case Cc:return"SuspenseList"}if("object"=== +typeof a)switch(a.$$typeof){case Id:return(a.displayName||"Context")+".Consumer";case Jd:return(a._context.displayName||"Context")+".Provider";case Dc:var b=a.render;b=b.displayName||b.name||"";return a.displayName||(""!==b?"ForwardRef("+b+")":"ForwardRef");case Ec:return hb(a.type);case Kd:return hb(a._render);case Ld:b=a._payload;a=a._init;try{return hb(a(b))}catch(c){}}return null}function xa(a){switch(typeof a){case "boolean":case "number":case "object":case "string":case "undefined":return a; +default:return""}}function Ef(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===b)}function qi(a){var b=Ef(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a, +b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=null;delete a[b]}}}}function Fc(a){a._valueTracker||(a._valueTracker=qi(a))}function Ff(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=Ef(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Gc(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement|| +a.body}catch(b){return a.body}}function Md(a,b){var c=b.checked;return B({},b,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=c?c:a._wrapperState.initialChecked})}function Gf(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=xa(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function Hf(a,b){b=b.checked;null!=b&&Ed(a,"checked", +b,!1)}function Nd(a,b){Hf(a,b);var c=xa(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=c)a.value=""+c}else a.value!==""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?Od(a,b.type,c):b.hasOwnProperty("defaultValue")&&Od(a,b.type,xa(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function If(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d= +b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;c||b===a.value||(a.value=b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function Od(a,b,c){if("number"!==b||Gc(a.ownerDocument)!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function ri(a){var b="";ha.Children.forEach(a,function(a){null!=a&&(b+=a)});return b} +function Pd(a,b){a=B({children:void 0},b);if(b=ri(b.children))a.children=b;return a}function ib(a,b,c,d){a=a.options;if(b){b={};for(var e=0;e=c.length))throw Error(m(93));c=c[0]}b=c}null==b&&(b="");c=b}a._wrapperState={initialValue:xa(c)}}function Kf(a,b){var c=xa(b.value),d=xa(b.defaultValue);null!=c&&(c=""+c,c!==a.value&&(a.value=c),null==b.defaultValue&&a.defaultValue!== +c&&(a.defaultValue=c));null!=d&&(a.defaultValue=""+d)}function Lf(a,b){b=a.textContent;b===a._wrapperState.initialValue&&""!==b&&null!==b&&(a.value=b)}function Mf(a){switch(a){case "svg":return"http://www.w3.org/2000/svg";case "math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function Rd(a,b){return null==a||"http://www.w3.org/1999/xhtml"===a?Mf(b):"http://www.w3.org/2000/svg"===a&&"foreignObject"===b?"http://www.w3.org/1999/xhtml":a}function Nf(a,b,c){return null== +b||"boolean"===typeof b||""===b?"":c||"number"!==typeof b||0===b||Nb.hasOwnProperty(a)&&Nb[a]?(""+b).trim():b+"px"}function Of(a,b){a=a.style;for(var c in b)if(b.hasOwnProperty(c)){var d=0===c.indexOf("--"),e=Nf(c,b[c],d);"float"===c&&(c="cssFloat");d?a.setProperty(c,e):a[c]=e}}function Sd(a,b){if(b){if(si[a]&&(null!=b.children||null!=b.dangerouslySetInnerHTML))throw Error(m(137,a));if(null!=b.dangerouslySetInnerHTML){if(null!=b.children)throw Error(m(60));if(!("object"===typeof b.dangerouslySetInnerHTML&& +"__html"in b.dangerouslySetInnerHTML))throw Error(m(61));}if(null!=b.style&&"object"!==typeof b.style)throw Error(m(62));}}function Td(a,b){if(-1===a.indexOf("-"))return"string"===typeof b.is;switch(a){case "annotation-xml":case "color-profile":case "font-face":case "font-face-src":case "font-face-uri":case "font-face-format":case "font-face-name":case "missing-glyph":return!1;default:return!0}}function Ud(a){a=a.target||a.srcElement||window;a.correspondingUseElement&&(a=a.correspondingUseElement); +return 3===a.nodeType?a.parentNode:a}function Pf(a){if(a=Ob(a)){if("function"!==typeof Vd)throw Error(m(280));var b=a.stateNode;b&&(b=Hc(b),Vd(a.stateNode,a.type,b))}}function Qf(a){jb?kb?kb.push(a):kb=[a]:jb=a}function Rf(){if(jb){var a=jb,b=kb;kb=jb=null;Pf(a);if(b)for(a=0;ad?0:1<c;c++)b.push(a);return b}function Oc(a,b,c){a.pendingLanes|=b;var d=b-1;a.suspendedLanes&=d;a.pingedLanes&=d;a=a.eventTimes;b=31-Ba(b);a[b]=c}function Hi(a){return 0===a?32:31-(Ii(a)/Ji|0)|0}function Ki(a,b,c,d){Xa||Xd();var e=he,f=Xa;Xa=!0;try{fg(e,a,b,c,d)}finally{(Xa=f)||Wd()}}function Li(a,b,c,d){Mi(Ni,he.bind(null,a,b,c,d))}function he(a, +b,c,d){if(Pc){var e;if((e=0===(b&4))&&0=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=ug(c)}}function wg(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?wg(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function xg(){for(var a=window,b=Gc();b instanceof a.HTMLIFrameElement;){try{var c= +"string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;b=Gc(a.document)}return b}function ne(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function yg(a,b,c){var d=c.window===c?c.document:9===c.nodeType?c:c.ownerDocument;oe||null==qb||qb!==Gc(d)||(d=qb,"selectionStart"in d&&ne(d)?d={start:d.selectionStart, +end:d.selectionEnd}:(d=(d.ownerDocument&&d.ownerDocument.defaultView||window).getSelection(),d={anchorNode:d.anchorNode,anchorOffset:d.anchorOffset,focusNode:d.focusNode,focusOffset:d.focusOffset}),$b&&Zb($b,d)||($b=d,d=Tc(pe,"onSelect"),0ub||(a.current=ve[ub],ve[ub]=null,ub--)}function A(a,b,c){ub++;ve[ub]=a.current;a.current=b}function vb(a,b){var c=a.type.contextTypes;if(!c)return Ha;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext= +b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function S(a){a=a.childContextTypes;return null!==a&&void 0!==a}function Sg(a,b,c){if(D.current!==Ha)throw Error(m(168));A(D,b);A(J,c)}function Tg(a,b,c){var d=a.stateNode;a=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in a))throw Error(m(108,hb(b)||"Unknown",e));return B({},c,d)}function Xc(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||Ha;Ya=D.current; +A(D,a);A(J,J.current);return!0}function Ug(a,b,c){var d=a.stateNode;if(!d)throw Error(m(169));c?(a=Tg(a,b,Ya),d.__reactInternalMemoizedMergedChildContext=a,t(J),t(D),A(D,a)):t(J);A(J,c)}function wb(){switch(oj()){case Yc:return 99;case Vg:return 98;case Wg:return 97;case Xg:return 96;case Yg:return 95;default:throw Error(m(332));}}function Zg(a){switch(a){case 99:return Yc;case 98:return Vg;case 97:return Wg;case 96:return Xg;case 95:return Yg;default:throw Error(m(332));}}function Za(a,b){a=Zg(a); +return pj(a,b)}function bc(a,b,c){a=Zg(a);return we(a,b,c)}function ja(){if(null!==Zc){var a=Zc;Zc=null;xe(a)}$g()}function $g(){if(!ye&&null!==pa){ye=!0;var a=0;try{var b=pa;Za(99,function(){for(;ap?(x=l,l=null):x=l.sibling;var C=r(e,l,h[p],k);if(null===C){null===l&&(l=x);break}a&&l&&null=== +C.alternate&&b(e,l);g=f(C,g,p);null===v?m=C:v.sibling=C;v=C;l=x}if(p===h.length)return c(e,l),m;if(null===l){for(;px?(C=p,p=null):C=p.sibling;var Da=r(e,p,q.value,k);if(null===Da){null===p&&(p=C);break}a&&p&&null===Da.alternate&&b(e,p);g=f(Da,g,x);null===v?l=Da:v.sibling=Da;v=Da;p=C}if(q.done)return c(e,p),l;if(null===p){for(;!q.done;x++,q=h.next())q=n(e,q.value,k),null!==q&&(g=f(q,g,x),null===v?l=q:v.sibling=q,v=q);return l}for(p=d(e,p);!q.done;x++,q=h.next())q=t(p,e,x,q.value,k),null!==q&&(a&&null!== +q.alternate&&p.delete(null===q.key?x:q.key),g=f(q,g,x),null===v?l=q:v.sibling=q,v=q);a&&p.forEach(function(a){return b(e,a)});return l}return function(a,d,f,h){var k="object"===typeof f&&null!==f&&f.type===wa&&null===f.key;k&&(f=f.props.children);var l="object"===typeof f&&null!==f;if(l)switch(f.$$typeof){case ec:a:{l=f.key;for(k=d;null!==k;){if(k.key===l){switch(k.tag){case 7:if(f.type===wa){c(a,k.sibling);d=e(k,f.props.children);d.return=a;a=d;break a}break;default:if(k.elementType===f.type){c(a, +k.sibling);d=e(k,f.props);d.ref=dc(a,k,f);d.return=a;a=d;break a}}c(a,k);break}else b(a,k);k=k.sibling}f.type===wa?(d=zb(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=fd(f.type,f.key,f.props,null,a.mode,h),h.ref=dc(a,d,f),h.return=a,a=h)}return g(a);case Ua:a:{for(k=f.key;null!==d;){if(d.key===k)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d= +d.sibling}d=Ee(f,a.mode,h);d.return=a;a=d}return g(a)}if("string"===typeof f||"number"===typeof f)return f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=De(f,a.mode,h),d.return=a,a=d),g(a);if(gd(f))return w(a,d,f,h);if(Jb(f))return z(a,d,f,h);l&&ed(a,f);if("undefined"===typeof f&&!k)switch(a.tag){case 1:case 22:case 0:case 11:case 15:throw Error(m(152,hb(a.type)||"Component"));}return c(a,d)}}function $a(a){if(a===fc)throw Error(m(174));return a}function Fe(a,b){A(gc, +b);A(hc,a);A(ka,fc);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:Rd(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=Rd(b,a)}t(ka);A(ka,b)}function Ab(a){t(ka);t(hc);t(gc)}function jh(a){$a(gc.current);var b=$a(ka.current);var c=Rd(b,a.type);b!==c&&(A(hc,a),A(ka,c))}function Ge(a){hc.current===a&&(t(ka),t(hc))}function hd(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||"$?"===c.data|| +"$!"===c.data))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.flags&64))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}return null}function kh(a,b){var c=Z(5,null,null,0);c.elementType="DELETED";c.type="DELETED";c.stateNode=b;c.return=a;c.flags=8;null!==a.lastEffect?(a.lastEffect.nextEffect=c,a.lastEffect=c):a.firstEffect= +a.lastEffect=c}function lh(a,b){switch(a.tag){case 5:var c=a.type;b=1!==b.nodeType||c.toLowerCase()!==b.nodeName.toLowerCase()?null:b;return null!==b?(a.stateNode=b,!0):!1;case 6:return b=""===a.pendingProps||3!==b.nodeType?null:b,null!==b?(a.stateNode=b,!0):!1;case 13:return!1;default:return!1}}function He(a){if(la){var b=Na;if(b){var c=b;if(!lh(a,b)){b=tb(c.nextSibling);if(!b||!lh(a,b)){a.flags=a.flags&-1025|2;la=!1;ra=a;return}kh(ra,c)}ra=a;Na=tb(b.firstChild)}else a.flags=a.flags&-1025|2,la=!1, +ra=a}}function mh(a){for(a=a.return;null!==a&&5!==a.tag&&3!==a.tag&&13!==a.tag;)a=a.return;ra=a}function id(a){if(a!==ra)return!1;if(!la)return mh(a),la=!0,!1;var b=a.type;if(5!==a.tag||"head"!==b&&"body"!==b&&!se(b,a.memoizedProps))for(b=Na;b;)kh(a,b),b=tb(b.nextSibling);mh(a);if(13===a.tag){a=a.memoizedState;a=null!==a?a.dehydrated:null;if(!a)throw Error(m(317));a:{a=a.nextSibling;for(b=0;a;){if(8===a.nodeType){var c=a.data;if("/$"===c){if(0===b){Na=tb(a.nextSibling);break a}b--}else"$"!==c&&"$!"!== +c&&"$?"!==c||b++}a=a.nextSibling}Na=null}}else Na=ra?tb(a.stateNode.nextSibling):null;return!0}function Ie(){Na=ra=null;la=!1}function Je(){for(var a=0;af))throw Error(m(301));f+=1;K=N=null;b.updateQueue=null;jc.current=sj;a=c(d,e)}while(kc)}jc.current=jd;b=null!==N&&null!==N.next;ic=0;K=N=y=null;kd=!1;if(b)throw Error(m(300));return a}function ab(){var a={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};null===K?y.memoizedState=K=a:K=K.next=a;return K}function bb(){if(null===N){var a=y.alternate;a=null!==a?a.memoizedState:null}else a=N.next;var b=null===K?y.memoizedState:K.next;if(null!== +b)K=b,N=a;else{if(null===a)throw Error(m(310));N=a;a={memoizedState:N.memoizedState,baseState:N.baseState,baseQueue:N.baseQueue,queue:N.queue,next:null};null===K?y.memoizedState=K=a:K=K.next=a}return K}function ma(a,b){return"function"===typeof b?b(a):b}function lc(a,b,c){b=bb();c=b.queue;if(null===c)throw Error(m(311));c.lastRenderedReducer=a;var d=N,e=d.baseQueue,f=c.pending;if(null!==f){if(null!==e){var g=e.next;e.next=f.next;f.next=g}d.baseQueue=e=f;c.pending=null}if(null!==e){e=e.next;d=d.baseState; +var h=g=f=null,k=e;do{var l=k.lane;if((ic&l)===l)null!==h&&(h=h.next={lane:0,action:k.action,eagerReducer:k.eagerReducer,eagerState:k.eagerState,next:null}),d=k.eagerReducer===a?k.eagerState:a(d,k.action);else{var n={lane:l,action:k.action,eagerReducer:k.eagerReducer,eagerState:k.eagerState,next:null};null===h?(g=h=n,f=d):h=h.next=n;y.lanes|=l;La|=l}k=k.next}while(null!==k&&k!==e);null===h?f=d:h.next=g;X(d,b.memoizedState)||(fa=!0);b.memoizedState=d;b.baseState=f;b.baseQueue=h;c.lastRenderedState= +d}return[b.memoizedState,c.dispatch]}function mc(a,b,c){b=bb();c=b.queue;if(null===c)throw Error(m(311));c.lastRenderedReducer=a;var d=c.dispatch,e=c.pending,f=b.memoizedState;if(null!==e){c.pending=null;var g=e=e.next;do f=a(f,g.action),g=g.next;while(g!==e);X(f,b.memoizedState)||(fa=!0);b.memoizedState=f;null===b.baseQueue&&(b.baseState=f);c.lastRenderedState=f}return[f,d]}function nh(a,b,c){var d=b._getVersion;d=d(b._source);var e=b._workInProgressVersionPrimary;if(null!==e)a=e===d;else if(a=a.mutableReadLanes, +a=(ic&a)===a)b._workInProgressVersionPrimary=d,Bb.push(b);if(a)return c(b._source);Bb.push(b);throw Error(m(350));}function oh(a,b,c,d){var e=R;if(null===e)throw Error(m(349));var f=b._getVersion,g=f(b._source),h=jc.current,k=h.useState(function(){return nh(e,b,c)}),l=k[1],n=k[0];k=K;var t=a.memoizedState,r=t.refs,w=r.getSnapshot,z=t.source;t=t.subscribe;var B=y;a.memoizedState={refs:r,source:b,subscribe:d};h.useEffect(function(){r.getSnapshot=c;r.setSnapshot=l;var a=f(b._source);if(!X(g,a)){a=c(b._source); +X(n,a)||(l(a),a=Oa(B),e.mutableReadLanes|=a&e.pendingLanes);a=e.mutableReadLanes;e.entangledLanes|=a;for(var d=e.entanglements,h=a;0c?98:c,function(){a(!0)});Za(97\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(c,{is:d.is}):(a=g.createElement(c),"select"===c&&(g=a,d.multiple? +g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,c);a[Fa]=b;a[Wc]=d;xj(a,b,!1,!1);b.stateNode=a;g=Td(c,d);switch(c){case "dialog":z("cancel",a);z("close",a);e=d;break;case "iframe":case "object":case "embed":z("load",a);e=d;break;case "video":case "audio":for(e=0;eWe&&(b.flags|=64,f=!0,oc(d,!1),b.lanes=33554432)}else{if(!f)if(a=hd(g),null!==a){if(b.flags|=64,f=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.flags|=4),oc(d,!0),null===d.tail&&"hidden"===d.tailMode&& +!g.alternate&&!la)return b=b.lastEffect=d.lastEffect,null!==b&&(b.nextEffect=null),null}else 2*P()-d.renderingStartTime>We&&1073741824!==c&&(b.flags|=64,f=!0,oc(d,!1),b.lanes=33554432);d.isBackwards?(g.sibling=b.child,b.child=g):(c=d.last,null!==c?c.sibling=g:b.child=g,d.last=g)}return null!==d.tail?(c=d.tail,d.rendering=c,d.tail=c.sibling,d.lastEffect=b.lastEffect,d.renderingStartTime=P(),c.sibling=null,b=E.current,A(E,f?b&1|2:b&1),c):null;case 23:case 24:return ta=cb.current,t(cb),null!==a&&null!== +a.memoizedState!==(null!==b.memoizedState)&&"unstable-defer-without-hiding"!==d.mode&&(b.flags|=4),null}throw Error(m(156,b.tag));}function zj(a,b){switch(a.tag){case 1:return S(a.type)&&(t(J),t(D)),b=a.flags,b&4096?(a.flags=b&-4097|64,a):null;case 3:Ab();t(J);t(D);Je();b=a.flags;if(0!==(b&64))throw Error(m(285));a.flags=b&-4097|64;return a;case 5:return Ge(a),null;case 13:return t(E),b=a.flags,b&4096?(a.flags=b&-4097|64,a):null;case 19:return t(E),null;case 4:return Ab(),null;case 10:return Ae(a), +null;case 23:case 24:return ta=cb.current,t(cb),null;default:return null}}function Xe(a,b){try{var c="",d=b;do c+=pi(d),d=d.return;while(d);var e=c}catch(f){e="\nError generating stack: "+f.message+"\n"+f.stack}return{value:a,source:b,stack:e}}function Ye(a,b){try{console.error(b.value)}catch(c){setTimeout(function(){throw c;})}}function Mh(a,b,c){c=Ia(-1,c);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){rd||(rd=!0,Ze=d);Ye(a,b)};return c}function Nh(a,b,c){c=Ia(-1,c);c.tag= +3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){Ye(a,b);return d(e)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){"function"!==typeof d&&(null===na?na=new Set([this]):na.add(this),Ye(a,b));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function Oh(a){var b=a.ref;if(null!==b)if("function"===typeof b)try{b(null)}catch(c){Qa(a,c)}else b.current=null}function Aj(a, +b){switch(b.tag){case 0:case 11:case 15:case 22:return;case 1:if(b.flags&256&&null!==a){var c=a.memoizedProps,d=a.memoizedState;a=b.stateNode;b=a.getSnapshotBeforeUpdate(b.elementType===b.type?c:ea(b.type,c),d);a.__reactInternalSnapshotBeforeUpdate=b}return;case 3:b.flags&256&&te(b.stateNode.containerInfo);return;case 5:case 6:case 4:case 17:return}throw Error(m(163));}function Bj(a,b,c,d){switch(c.tag){case 0:case 11:case 15:case 22:b=c.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){a=b=b.next; +do 3===(a.tag&3)&&(d=a.create,a.destroy=d()),a=a.next;while(a!==b)}b=c.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){a=b=b.next;do{var e=a;d=e.next;e=e.tag;0!==(e&4)&&0!==(e&1)&&(Ph(c,a),Cj(c,a));a=d}while(a!==b)}return;case 1:a=c.stateNode;c.flags&4&&(null===b?a.componentDidMount():(d=c.elementType===c.type?b.memoizedProps:ea(c.type,b.memoizedProps),a.componentDidUpdate(d,b.memoizedState,a.__reactInternalSnapshotBeforeUpdate)));b=c.updateQueue;null!==b&&dh(c,b,a);return;case 3:b=c.updateQueue; +if(null!==b){a=null;if(null!==c.child)switch(c.child.tag){case 5:a=c.child.stateNode;break;case 1:a=c.child.stateNode}dh(c,b,a)}return;case 5:a=c.stateNode;null===b&&c.flags&4&&Pg(c.type,c.memoizedProps)&&a.focus();return;case 6:return;case 4:return;case 12:return;case 13:null===c.memoizedState&&(c=c.alternate,null!==c&&(c=c.memoizedState,null!==c&&(c=c.dehydrated,null!==c&&bg(c))));return;case 19:case 17:case 20:case 21:case 23:case 24:return}throw Error(m(163));}function Qh(a,b){for(var c=a;;){if(5=== +c.tag){var d=c.stateNode;if(b)d=d.style,"function"===typeof d.setProperty?d.setProperty("display","none","important"):d.display="none";else{d=c.stateNode;var e=c.memoizedProps.style;e=void 0!==e&&null!==e&&e.hasOwnProperty("display")?e.display:null;d.style.display=Nf("display",e)}}else if(6===c.tag)c.stateNode.nodeValue=b?"":c.memoizedProps;else if((23!==c.tag&&24!==c.tag||null===c.memoizedState||c===a)&&null!==c.child){c.child.return=c;c=c.child;continue}if(c===a)break;for(;null===c.sibling;){if(null=== +c.return||c.return===a)return;c=c.return}c.sibling.return=c.return;c=c.sibling}}function Rh(a,b,c){if(db&&"function"===typeof db.onCommitFiberUnmount)try{db.onCommitFiberUnmount($e,b)}catch(f){}switch(b.tag){case 0:case 11:case 14:case 15:case 22:a=b.updateQueue;if(null!==a&&(a=a.lastEffect,null!==a)){c=a=a.next;do{var d=c,e=d.destroy;d=d.tag;if(void 0!==e)if(0!==(d&4))Ph(b,c);else{d=b;try{e()}catch(f){Qa(d,f)}}c=c.next}while(c!==a)}break;case 1:Oh(b);a=b.stateNode;if("function"===typeof a.componentWillUnmount)try{a.props= +b.memoizedProps,a.state=b.memoizedState,a.componentWillUnmount()}catch(f){Qa(b,f)}break;case 5:Oh(b);break;case 4:Sh(a,b)}}function Th(a){a.alternate=null;a.child=null;a.dependencies=null;a.firstEffect=null;a.lastEffect=null;a.memoizedProps=null;a.memoizedState=null;a.pendingProps=null;a.return=null;a.updateQueue=null}function Uh(a){return 5===a.tag||3===a.tag||4===a.tag}function Vh(a){a:{for(var b=a.return;null!==b;){if(Uh(b))break a;b=b.return}throw Error(m(160));}var c=b;b=c.stateNode;switch(c.tag){case 5:var d= +!1;break;case 3:b=b.containerInfo;d=!0;break;case 4:b=b.containerInfo;d=!0;break;default:throw Error(m(161));}c.flags&16&&(qc(b,""),c.flags&=-17);a:b:for(c=a;;){for(;null===c.sibling;){if(null===c.return||Uh(c.return)){c=null;break a}c=c.return}c.sibling.return=c.return;for(c=c.sibling;5!==c.tag&&6!==c.tag&&18!==c.tag;){if(c.flags&2)continue b;if(null===c.child||4===c.tag)continue b;else c.child.return=c,c=c.child}if(!(c.flags&2)){c=c.stateNode;break a}}d?af(a,c,b):bf(a,c,b)}function af(a,b,c){var d= +a.tag,e=5===d||6===d;if(e)a=e?a.stateNode:a.stateNode.instance,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=Vc));else if(4!==d&&(a=a.child,null!==a))for(af(a,b,c),a=a.sibling;null!==a;)af(a,b,c),a=a.sibling}function bf(a,b,c){var d=a.tag,e=5===d||6===d;if(e)a=e?a.stateNode:a.stateNode.instance,b?c.insertBefore(a,b):c.appendChild(a); +else if(4!==d&&(a=a.child,null!==a))for(bf(a,b,c),a=a.sibling;null!==a;)bf(a,b,c),a=a.sibling}function Sh(a,b,c){c=b;for(var d=!1,e,f;;){if(!d){e=c.return;a:for(;;){if(null===e)throw Error(m(160));f=e.stateNode;switch(e.tag){case 5:e=f;f=!1;break a;case 3:e=f.containerInfo;f=!0;break a;case 4:e=f.containerInfo;f=!0;break a}e=e.return}d=!0}if(5===c.tag||6===c.tag){a:for(var g=a,h=c,k=h;;)if(Rh(g,k),null!==k.child&&4!==k.tag)k.child.return=k,k=k.child;else{if(k===h)break a;for(;null===k.sibling;){if(null=== +k.return||k.return===h)break a;k=k.return}k.sibling.return=k.return;k=k.sibling}f?(g=e,h=c.stateNode,8===g.nodeType?g.parentNode.removeChild(h):g.removeChild(h)):e.removeChild(c.stateNode)}else if(4===c.tag){if(null!==c.child){e=c.stateNode.containerInfo;f=!0;c.child.return=c;c=c.child;continue}}else if(Rh(a,c),null!==c.child){c.child.return=c;c=c.child;continue}if(c===b)break;for(;null===c.sibling;){if(null===c.return||c.return===b)return;c=c.return;4===c.tag&&(d=!1)}c.sibling.return=c.return;c= +c.sibling}}function cf(a,b){switch(b.tag){case 0:case 11:case 14:case 15:case 22:var c=b.updateQueue;c=null!==c?c.lastEffect:null;if(null!==c){var d=c=c.next;do 3===(d.tag&3)&&(a=d.destroy,d.destroy=void 0,void 0!==a&&a()),d=d.next;while(d!==c)}return;case 1:return;case 5:c=b.stateNode;if(null!=c){d=b.memoizedProps;var e=null!==a?a.memoizedProps:d;a=b.type;var f=b.updateQueue;b.updateQueue=null;if(null!==f){c[Wc]=d;"input"===a&&"radio"===d.type&&null!=d.name&&Hf(c,d);Td(a,e);b=Td(a,d);for(e=0;ee&&(e=g);c&=~f}c=e;c=P()-c;c=(120>c?120:480>c?480:1080>c?1080:1920>c?1920:3E3>c?3E3:4320>c?4320:1960*Ij(c/1960))-c;if(10 component higher in the tree to provide a loading indicator or placeholder to display.")}5!== +L&&(L=2);k=Xe(k,h);r=g;do{switch(r.tag){case 3:f=k;r.flags|=4096;b&=-b;r.lanes|=b;var B=Mh(r,f,b);ch(r,B);break a;case 1:f=k;var A=r.type,D=r.stateNode;if(0===(r.flags&64)&&("function"===typeof A.getDerivedStateFromError||null!==D&&"function"===typeof D.componentDidCatch&&(null===na||!na.has(D)))){r.flags|=4096;b&=-b;r.lanes|=b;var F=Nh(r,f,b);ch(r,F);break a}}r=r.return}while(null!==r)}ci(c)}catch(qa){b=qa;G===c&&null!==c&&(G=c=c.return);continue}break}while(1)}function Yh(){var a=vd.current;vd.current= +jd;return null===a?jd:a}function sc(a,b){var c=n;n|=16;var d=Yh();R===a&&O===b||Gb(a,b);do try{Nj();break}catch(e){Zh(a,e)}while(1);ze();n=c;vd.current=d;if(null!==G)throw Error(m(261));R=null;O=0;return L}function Nj(){for(;null!==G;)di(G)}function Hj(){for(;null!==G&&!Oj();)di(G)}function di(a){var b=Pj(a.alternate,a,ta);a.memoizedProps=a.pendingProps;null===b?ci(a):G=b;kf.current=null}function ci(a){var b=a;do{var c=b.alternate;a=b.return;if(0===(b.flags&2048)){c=vj(c,b,ta);if(null!==c){G=c;return}c= +b;if(24!==c.tag&&23!==c.tag||null===c.memoizedState||0!==(ta&1073741824)||0===(c.mode&4)){for(var d=0,e=c.child;null!==e;)d|=e.lanes|e.childLanes,e=e.sibling;c.childLanes=d}null!==a&&0===(a.flags&2048)&&(null===a.firstEffect&&(a.firstEffect=b.firstEffect),null!==b.lastEffect&&(null!==a.lastEffect&&(a.lastEffect.nextEffect=b.firstEffect),a.lastEffect=b.lastEffect),1g&&(h=g,g=A,A=h),h=vg(p,A),f=vg(p,g),h&&f&&(1!==u.rangeCount||u.anchorNode!==h.node||u.anchorOffset!==h.offset||u.focusNode!==f.node||u.focusOffset!==f.offset)&&(q=q.createRange(),q.setStart(h.node,h.offset),u.removeAllRanges(),A>g?(u.addRange(q),u.extend(f.node, +f.offset)):(q.setEnd(f.node,f.offset),u.addRange(q))))));q=[];for(u=p;u=u.parentNode;)1===u.nodeType&&q.push({element:u,left:u.scrollLeft,top:u.scrollTop});"function"===typeof p.focus&&p.focus();for(p=0;pP()-df?Gb(a,0):jf|=c);ba(a,b)}function Ej(a,b){var c=a.stateNode;null!==c&&c.delete(b);b=0;0===b&&(b=a.mode,0===(b&2)?b=1:0===(b&4)?b=99===wb()?1:2:(0===ua&&(ua=Fb),b=nb(62914560&~ua),0===b&&(b=4194304))); +c=W();a=ud(a,b);null!==a&&(Oc(a,b,c),ba(a,c))}function Uj(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.flags=0;this.lastEffect=this.firstEffect=this.nextEffect=null;this.childLanes=this.lanes=0;this.alternate=null}function Qe(a){a=a.prototype;return!(!a||!a.isReactComponent)}function Vj(a){if("function"=== +typeof a)return Qe(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===Dc)return 11;if(a===Ec)return 14}return 2}function Ma(a,b){var c=a.alternate;null===c?(c=Z(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.type=a.type,c.flags=0,c.nextEffect=null,c.firstEffect=null,c.lastEffect=null);c.childLanes=a.childLanes;c.lanes=a.lanes;c.child=a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue= +a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{lanes:b.lanes,firstContext:b.firstContext};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function fd(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)Qe(a)&&(g=1);else if("string"===typeof a)g=5;else a:switch(a){case wa:return zb(c.children,e,f,b);case fi:g=8;e|=16;break;case Hd:g=8;e|=1;break;case Lb:return a=Z(12,c,b,e|8),a.elementType=Lb,a.type=Lb,a.lanes=f,a;case Mb:return a=Z(13,c,b,e),a.type=Mb,a.elementType=Mb,a.lanes= +f,a;case Cc:return a=Z(19,c,b,e),a.elementType=Cc,a.lanes=f,a;case pf:return Ue(c,e,f,b);case qf:return a=Z(24,c,b,e),a.elementType=qf,a.lanes=f,a;default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case Jd:g=10;break a;case Id:g=9;break a;case Dc:g=11;break a;case Ec:g=14;break a;case Ld:g=16;d=null;break a;case Kd:g=22;break a}throw Error(m(130,null==a?a:typeof a,""));}b=Z(g,c,b,e);b.elementType=a;b.type=d;b.lanes=f;return b}function zb(a,b,c,d){a=Z(7,a,d,b);a.lanes=c;return a}function Ue(a, +b,c,d){a=Z(23,a,d,b);a.elementType=pf;a.lanes=c;return a}function De(a,b,c){a=Z(6,a,null,b);a.lanes=c;return a}function Ee(a,b,c){b=Z(4,null!==a.children?a.children:[],a.key,b);b.lanes=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Wj(a,b,c){this.tag=b;this.containerInfo=a;this.finishedWork=this.pingCache=this.current=this.pendingChildren=null;this.timeoutHandle=-1;this.pendingContext=this.context=null;this.hydrate=c;this.callbackNode= +null;this.callbackPriority=0;this.eventTimes=ge(0);this.expirationTimes=ge(-1);this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0;this.entanglements=ge(0);this.mutableSourceEagerHydrationData=null}function Xj(a,b,c){var d=3
    F4AmF;!*voB?*}?WEqb5iNYdx3PyW3VP_6Og4dqVUclAz2jgPK#50% z0Tz};BEcYoNu-m=1Pn}YgsuGGs(#nzS~h1&1%JE4#J>H0e{8R$YfFWK@?n_<|Mxu` zm+sV`Ayqa!y50M57cC~|x&L-}=*n*lkaXMHE2kuNeY`$5XTH9DYonfLMtu9lUxi`d*;uT(O#)sS=d?ZIvVHY=W+eC#mMT8PU}hOuU^gF@BS6f&D?#&ef(%s z*(GY(MnxZ2Bdvqcx_Mm_wjF=946Ksjs^Shgd8Mk#^GE+qD{4Z}#wS1yiz7kv1}Yku zT|n!e0yjPegQLL{&&Cn46b5_w7h%zd^apC&sgJ}={iEky@=5xR?XBsX7qSv5VX|AB z7L6>+N^;j^B=)$3_m8e@)v>vNb;JG%v)VoTp7oWOc?T?9ChK%W56r#zvb5OUnC$mbe&cuc}93DPU=l^zV?#rl~Ez+~jXKT3)&Q4~hC(eA?Zfj`nH@9`HMPU+( zn}3U%MKARgpPa5~ezL{0=F}^%xhY!pN1X<>R}YIzA6s5mEh(WMR=!rp4L5)J!1tzq z4+d}eW@q7VzJIzKk5zl#y4JDEDYSJ&q4Zl?VxGP0d$}zvd%obmh5j=xXx|eUR00kf z*h~h20A~P-TM~xOrsK#MA{j>4(A9zAC6y`kt*f#&v`AO%AAfmSDw5^@* z#+tRVPA(%OA{7*(ZJYl{wQr+*>5^MQ$ECz#&E-a3qxX%l k`oqG#csO_!mjxc7| zM%tC;&m~l!(U0oEwmT(249Qb;U8Ca`N#h(D0(^r`gT+TJnIxTk%h-&U~EYyV&R3S&?tBY1&Vqa zlTH-c%bG9Lz;ExVs&LlTl2Ki}IH$U0lhndV@ABHWV#RxVFku;olx^=%`@LWMw5}mX zWI)fnb$L{sp6hDrUo=%z)Fdu?pRcD<)~)Q&ab0I9%i)#b}qyO$ce7$ zjXXLxQRl|Z&Y!+esBP(UxOQ-2K*2w^xXAHyqorbOjlwPRj6P`%e{x?jxr-oZ<7424 zhtdXSQzXdZp@+wy5$Q}E6Q=Vt=<<;%1R9IN#0Y)s|66U$jn{8pPYd73c5saOnE&8` zv?SM)e&)TOeXVOO4P`3o0|+fn866CR$~N5?KD2Q}?*(#=^N)irHiFCXx(JyB|Q>L#-b zhwbuU;r1+T6kX19iWhUJZQuKrs0K&IyLC#^T6VQKX{vO%7Jf{4<~}*o>1W#+JRT8z zOph5mbur+uUW8Ps!sb5D8RtOz%YkIr&3^&%f!NcFEsd&^*ZDlr88En#ks(; zV43m*9o5Ok`R*+vf84_BUb2^sUYc6GzA-`fZxdsaecpSY9L-2vZskQ3Q*KyywY|F< zbARfK>Xr?6T0d0SnK?2>4tZqA*h_p<$}twtl_yf4+e zdRIIwFHNvh{odXAaXBDBHQ_VIJJQ6EX z;#L`nw@nY9EA%PPjSG&4fs>ydrh>>?fA zj0OH)R_3N|aY2PG+;SKa9*P^d+o560z)`Rah?q%ZGnq6P=3rrdMQ37!&aE^}~tvAof zNySv@jX5m3_+Gh|@8YvP=;u|QhNUHMV;XmsjsMX|VM%FB1ZcLHkn^AV4ID}4;O>fN zKDM{Cnl*?#uuVBw@6>y@_M4o|5uactxAXL&OJhVB&4H#P>2iucoc>$woEJ znckO_9IS(^(oc9>S65YN85uw~=a-eoCKi=4YSTEE=(gz^3Fil++~#d7It)ftO}q!* z`T1w6h}3esVumju0Cyzn$R7-l|Bw8Si&~_ioj+S&AGgQ;0 zUiaB=&bRL8l?|^7y6s3fCKWO~0+mgHu_QFGuxvckIZR++Vqtv0{KiuWa198%?FOgI zN^B_P(Wbp_qO&U2EiotB9+ct=!~glDBYnuOprD~`$SJ|%YDmS?38IMhx}Jt+}IcGY(Af^8!$=`jm<4P&#j7Bs);Fh zBjx^A(?xDllE`l%iJBT6OBc+{e(K10GV*i%ymBI5$A!O@r4#63CTQ1V2{4wVv+zWC z;)w)0aL`GR*3xMt3JxxM3{V&dL^_5bG`T-QZCQNWKe5w6Z%xR}*p#WUWUt1Wa;Lq+ zI^^J4E_*Skp;GFydy$pA;gnPTQm%n6_3eMsJ1#X=YTVp$W~s4S-muiSJ?cOCGULe# z2lFLo%c~O#9Hu5eU9Rud*Lu_KZnMpPDuH^|)BEaW_1rq!>*1j<;_^z>H6ub5q+5Hu zE=-+PYmz*rIwH%z>)A!cVgi?*O~unm(6s|LCY4MF!a48~=nO0dI-4-EgYOB6Mq>yc z*|ioCvkUE=njJJ}pDz`k@4K#iTaNQ`kT|kv|Hemd(Y`mUXXCAh9vWeTonbA$l3N(X z*Aim>JTvuv`=Yj)<;~nW&vWNuM;9C3TNb@=e(2LZcU!gY?W~cTHa$=8o%_Ito8C<_ z_-bEMN)FffnOR$DYvs5|Nqheu$Kzl6uWZjc{d2D<$LR5h!#}e}`8NK=|5%F%iXJEm zOd7N;@xZ%<>x~I5dje4HanR%eW(pBQrqD1%Amj_%_&c*>*G5NY>YtwsP@T0Zrsrk_ zUw4t!Y<+b;lq+**bh7q?>WwlDh2rnZ=a#;Vx80S~b6kGuOt%EZMBiE3wsdCxl-zfH zYGL4T`Bk#KY-8HSn5Kb-EbgZQt%0vAQ};@~b{rqhz3A~Qr+dnYqbkexRWUP~eeylj zp-$uW-8C))wSDuAj{iOh^5idV9^05^3flNA8btp;4;qb1$J5AU5;S$lcpQU5g6JWy zUuL92jV~920+D&2_eUZ}@THk=>Hb`1Bf2KK8k{iPlL$ z!Q-hw%ArGHkHh1!1PqA==^Ui^6lmT;2NMXY7#5rbtkB^9TScv|>(t?l$++ngkL;8l z7EZc^bZJH9=G?C+jvttE3_RhL&wu5u7JsK9D0nDkpn4(G@N_brNrnW64m>nAH1*(n zLI8FSmPn)kM^9)`+d2}Ul)F{_o#ypBGF@Bs7Cz1Vp7GI{{&^v^-p9!=-9gK?xTyW} zWM>@trio$c@xIS5!eQT2IK^IOp+4^Gtb?vE3`d;`SWGn-|E))cqxfMDiz)B;s>kRV z*6rxXz~tr)ZzX*t)>RB{OPj3q-qr7N^X0*}3k_Rt3`p1a&fckB^sv8D^!MKb8vG;; z_o1jTLBW$jQv%~DDgh6K93~c8)fgHH&tyRxj{yP{3j*aiQVlsG>=U7?(nc}cS!HiFB?(%a|_%5?13om#cNTM8rN&X z>fE+&`lncl@pkdu$d<@;o(=Mr$#bsizwzC8>-9eG78E>8VX=_U0|S=|EOasr#&cvm z1GG1g*+DA@OQ4hJFuV|2*tAyYc8_#FS-s9L<4LgccjuH|Kc~$_;)L5O9VNTw6DTQP z|ETNED}6e!FiYoTN1Eiv1;OZI^5Nr*lOh_o-t}j%^E41wg(g))Qqtg-mmT4?ZwK@p z&$PSWnROf2v1?+=r(ZXnvTQN-Y@Ae(wovetHp?GxsH;`$8sV0;cbWQp)i~Wc6>_jJ zoGB0(xg!2%zPO_0Y4DUA?6k0~HM8`9mABD7HQnlS zUbeI(GUK93<7C-su4Ij?%*Bw{(bnm#uC%6@`BCbK0yTUt{hY~#ulMa!XAXG;$8|Sc z_Aq$4uK6X^Z*gnG*~yVgr9Iz!t{vGD`pst*U+_84d?Ez}&m^;OkkK*eSOx_q7Le3~ z?2OI;?kQvdc%bay$V57hAhe?2GbiFVUtH`rF>Ij!%T~_U(du7KSW~W(T&0rAJ1;a| zOlckz!_F=G%gMdez?>hY>S;slL0jZsH!26YxC~DfGfNi+Iv;ecb5&k4)%U#j#b^FW zQf+OKB9>$D{a~(JQS~*aL$^g`dzTD!JPV7hLf#o}n4B(L6>aC?{r2FjT%}wuedooT zzZjeNf=`xXJrxu@un_SyCJu@^0vm%Rz_AC34cz*W^20OF#4gu7EIL_eRrlYz?cG~~ z6f}I+rA9jUE(RY9r7%RV#H})^%<-K$UB`L8c`|ncU+~v0J<svf zGN|n61UwrDSw4}1g?f*UU*;(Y7u$q%;5Ta3h?O>YxGxxu-22Jj(D(KWdQI3aW#0#i z?|oC+hkf@esK$mAcHhP<%tVVf4UYpW4I|f|&3R0I<-bruJ6~SWAuXe&bKz}Xzu2y* zkz%iUr5i1OmOF_o4!_G9XipJ$E4k`WpJAHrB-%03u{0c#eClJ?e!cg%{C_L-l`Y;= z>7%dg{ooFXFM8?vswzQC4*^mkdIE+>191ft15#3;p)+7J(4mzL=`LJ}z}*l!v#Roq zwvuZ5r!*-n{-Nae;J%#FEZM@no&(8evWdE?@~2FuSVyX=VwSd^EqHmick6F?deFfA zyx5V@?{43owYH2?OTS%wQ~cB_#iH--rybb-rx{aEOgH6*5vTL{Jq&|b9(>YE$r2UJ-^lzMe{HHn++cX-gsof|4>t)rX>*> zG(2v3R0mJpa#@D~N(vJ=_$^evcRp>K(GoetBy_;b?t0_QF=B&kCK4oN>ye zC%1I^{V)}~-F%=}KX2=Aa_=)D3#CWfE|GToHq`nRK9$fLZGnqaGcsH7u{biw@HHYScYmgu94E198uY87MOotX%!GO zzO$*t&AT+!GAK$Fs+}&WPSeGHH5)1oq|`l)#1)bbKcZiBNP1J;(R)Rl>(TDtmX$&F zG9A6EHCGT2Ka(K(x-Q*p!FaLaUxs7!Ohc*qYn^S6+W9Lx(%$>Zf}&?a6$=7G%5vk5 z1+N`Ua6nQ@1Ku_Ca4A#_5sRVXfS@cayEox%h|F1eEUhbs_4M&MZSDD|g`a3C|123) zHH=WF9_@|QGV>X5yJMLc+bCxmlCaRg+z!+nJJIrh=1<)*E<@X-cAUAndcWMSd5)P5 zFIuE)mNJS?w*NYF|ET5LLorcx!w-kD8ovBnu&rEX?%mYa!iy;{R0lR|j`xarvu7OWcp{uz}Sz$8q z!k+pSB0Y+2ls!_r%4(AyPZYoYQnqB4aqHlM<@7ab|DVQMb8UeQyHt8u;e!Ib}2uG_yH?CbCWizZ**J1eM6Yb=aDH6#(6iWTbF1P6sTlbJ3a6Hzhx#GUb*-Qqj{xN? z215l}8X3CwATol{Jm?IU?>|U@*f7Px6#!OYO_( z@ZQL?)pwHVZNJZ4nsU*(9n$!lPXw&13Gj zKi9V__zhm~S4d4UBMp1xB=vF)dRa$QJc2K+lvtRho|c)v;+(cg*VNbT$+yI&kiW71 z(Pti>^J$%N`Z~`{@MEj+UH3nn-my_o@KhQVdNshuAwZ8BGFle!aWOdP+Jf|o38gKO z2IUS3E3})_THGqd${>}rf7`sJFrv5ID)vxzXO~Fru~Y{Kv!bO5C-2EuNwjr}W8<~C zEnz~uD?BQ>l zyZ-6*$STNS{r%Ocals{r`@%$`FuSJF&@wj1vpUh^<{qcxArEU774LNOPiqqk( zYk%H*D@H9vIcHFo-eK#_oVvoTY#DCF=me|gIO>K^G_8juueJO@ji+x_=Rln4q3QK5 z+ZOG|Jw93Z*p%PWuW6}zE>7)u6&v$Sw!|thW~;w(OWdTBou-kULP6qmx9E*EBEc$$ z&TYN0FYCBf;Be5+@bdiZ;r@kp{FU7{ee=754n36#87&slSm@qB%Np2N;2psRVI7FA zFeD08z3|%6$wEmA(YdBqu80RE_ZHDaXU!(BOIJM6(rHYR9(lWt6nk%NX|Jb`_tWlx zbpO8*P?p*1)O?MfO2Hig|4y;L%O3S(Kb-VRfirR-S zI$qc=hu{d6`?B*rOC;NHUDX}GTJG(WH@;PM!Q=GOirn&cwc8@TpDIc-?@BH3Ppsxe zdanuE_b{iU((n`}D5!ykhsVQm!|am4#?wetc<^EQsUWmu3%%}BQ`)nq6$+eB7`t{} z+t+jQx&De>u}-)TrQ7zcQw?Y!&yGy}?ci{+C;44);Lg@AABysFGp8r}v0GP4!_BkA zxZ>#c;?CD(b5EIAbGJLzZMVLCwspxpl_5_Vb@$6_i?$U1P(a>!n(kFP__6J=o{vH& z^=5p8Wyk!kX>NJex8LJNuJM%avh zeVTSJ^iGE5OiKKFo5POluC({qYP(UW{_%aL!#krabmEHj+nMJklKt>TrZ-rdH1_k| zc=MfgW`Z7i$Y8-Q0Sl@s7~g=X7HT{A0c=!|m9i)_sC0m^#H0&dd8Lw$;9ojK55`hF z>`s2!o-X1cz1Gg$Fp_xbj`Hcn+p&>u)1xMB)mwh6Rf+nLaA|CIbQLsnR+^cl-8JBR z@8;eRkuMC-p82}(qv(8OcCqw*`BWK_x3~4w2}WOzh1&t;nDJ^#b(q@U_bJDMN4x97 zwuejP&8<3V&)L6ajuSurqIvQ7x>XIz9S``$^d?hd&jg)$939Ar(71sFkHQ3Y5-?9; zVhtHT%&EXo1g?Dyjtz2KVQJ3%NNG}uS-~4C_QK>nJyFL{33p?wb{+a1H@COraz^1j z+wRuC%vQDXu+Vengmn1pwnOlr-0ODK#C~wJt-UQBo4e;@z2l8R-!{XwRbsKyzcqy= zh7nG&=62M#dG4wyD?BvQ{I{vhUdQIz?Iv#y8O&xrADUVG_FBO;>1~nI*pn41hg6iC zxA9;1KgLn)1a14}ES^Oqf=3Pn4t)#;h_0A;DwYNWMFt3t2{>To;;}+C2bvQ1H4bfz zEcX7lGpDE3Ovk>E`(5p64ety` zUi46U)g&mlF@{_x?yIrBpMwjwd&ihHDwFqk>co0`uH|M)@66t}V+vm@v#{l%X}{a& zza8Yd+WmE6&mKHcx$U=zB9k2n!(*56As!=tODphg`}N(AXoBW-3=GIBL0kg#dJ%;nI5-SovA|{t+x9E$y|%VH&on)HQ!Gt)UaPfK*!grgzV+Ex(ukZz zklTr^%#|ab59PY4CLAixbmmTWlznUV`w&~D>~?-3$kv1u-MyOp0&})y;>1j5yo%r@BxbtOxtfEw8Z-B3e?~5dI zu5Hf63R}L=CA-el3Oey*IOfIQ_rlZlf?X|$$mBak$Cwgy5u$cr{ak!v?14^j?=llAI^w;5qHfH^X=209UD>a zPWrCAD#LBi8{1+tmwnjti+@{lu7$#-dOO$nBQ|6F(QnB#m=Uz=p@PLQ;l5vf?bwj# zLkAQ3nJ~Gfg1j1XehhHa>2MN+op`#BiPMXXs)s#0uOB+s7eJ;csK><;|;(#^rFuwJ*fVOikZ7#mqag zEp)Q#VTVfl0$Z*i!jWuax^+q^iL=jb#v$2ZV68;{z+$oP9fgwd;!aw?<3$w%((0*B zDKX$D@~1fO4Wr)(T6zM824oayQUmc4$AU>U78Dpj#lynBC$NEz3w}aWCRu21uUXKt zZ}95|UwiLjgZZ*cag*8uGE24-y5}lCWwoWB9KBLmf4%jmmeL*5X8P6h$~u8gL^$$) zUZ1t#EGnCYY;o42*p$+WTZ-mh_L-57tL{kDTP)76v>*L2#TlXKMPC6n};MY75rnC6&#j4Y2WZPpN&aDs<_-w9zxe=)0?`(|Y4#cb3PO zD_y(1*c~0K%@)5K|33M6a@wUdeuMcTdB^0t&(CgN%=kLLzV6P?nFhh#yA;1Y8IW=_ zKAN-n=h}D|gE%t(+B+yv-wE3IAS?ix6)-X=3?ONcnV>cS-<0K92A+w5fj!iB7$9m0 zRoF$VD9k6!YpKd5p22Z1-Md_P;$7q?nLCnIE}k8eSNoxXbGLoLW1-_@%_qs6NXPig z50;<0eAgvL-YvZ?-?N@JsSN1b_T-jCcN!~pzanoR$uElQdWl~mzCXMLv=yHs2OLCw zOycC89JJ~=R-gLjyZ@$&oWf|=!>>ytOX#E53cmZ2f;9BE@i%k6YCUcj6g*fa;NX%6 zG9LV(EGua#1SV;@Z%v}Ga4gsY3<#bqlz2~9H;55w6N?`6D7baLj9VL;w{^B5+26bK z{F44V&NDf~ntiXmM#9H>I;J0LZS62Ir{93VMgHDJ`_<#7G8umq-dxl@5PV~Ofy6;G zeO-;4dJ(xt{~pMT&G_EwtCp_v*g>T4>)oAX>WM0~uctrVysfr`@AWxn^uqOPMchnf zNAZ9u@j&^+zE`9C!SC;j7Znsd9tM-(8v?pQ8Ugw^Ae<+&FjP7P*!M8Wz`^7SIGMmj z6n5qPx7yb`cBiBr$(GS6lr(Yf%Bx7XK9yqDc9-ouX!G1oz2x7gL3Mn|y{n9&v!LKX zEzYJgh)gOGxEa8|!@%v&24g@NT2b+!LZT3t9R&zNGrS(U;^%I}X)J7u~5M+?`a)!eaX_?x?3lBD-b!_g7Z zM1v4+`gj6$Xm0ecsaB7u$~K>Mw|B*zBklMW?^C#?eIMy(xa>P*4Fh-S>n7Nh)E96+ zUp;DfC*#Qcp#%f3Ze{BG!y`2xTbfl;G}Qy6g=8`V;z;XEjA!C9k%%N%Wb6_|KM%cx%BIR8U;{I8D! z{V+*XJPhf9a8CzQEGCWs1~JQ`a`4i?LL(a}It1us0ckTL#C%CBYmX1BXLb z1%;fq+AVf%{pJNlT(4=4t?Z#vXTIys%IsO7pd1G%IA99~gbWNgVX`S?B87#A7A_M! zGr%AUhrzNS3m`Iu2Ja=dK}S|pnNm4%QSXn&=CU81_N-_r#(kKW8l*lhux7u`pA!r2 zaSSgj7_|PjXcAkLKMMy{BAZHdZhA6nrK zjv=R2>PslM{zGEgugznQ`c<*k#;FW>+byd7Oy>qmi!0SN)~fU=S4DQ3EJxp}fL~1Q zuxZs=el{Na!S@8GpRP%2&3kse^BoP=uy#<{S8(ue%l`gBy zKw3+N^FRPoL^2baTSTFCo#q3xv@%VOMn>B^xssN}b0@r}{Aw)BwRbLrCP?qiPnent z2zZnuVi{5@64_`+s3WQbLXN|zdAO}*xa|n*_B1E0ITw4_N&WK0D7U(?n9MBBw9?~2 zbIB6pT6*98S5L(s8a3`OFnj+oh^Zfu)ugq5@oC< z`EI-z{rH=Lf+s;1%LeKpg+gFJ7mvmO8x2Tnz&HUMWk7QPWF25GP=v0$W`z1Yd5+D> zC@%Tjecg6v)#}9kQAZk2lyH>d9Crtn2Q{P_TAN3Em#SP!%IKP((@w&#|2CI)XaL`A z(Mo8Qiut-H!m0Et&UdnTT51HZqVTQA_xqLk(1^gZPRF%u++3Aqy6*nEXYR^4{XaE| zjS(FQc11a}KP4JINQ`FBCTbW;X#LrrvDK=9pOD2o3ax4XTiZTE&mvX#0EyMKMz+6^!%Fy2Fz`LmCHHLZ>w@a! zh)lIue#-p?2cHo^!LtcOxYxll0EU%7M4*wu5g2I4preItK!l_RSR3%r3!Tu15EpWX zSASX{YO{-7^Ad)*qYFm1Ue%#^Jr39Cw#ub1bKRuM2OC5~v&gpR%`ZK_ct;Fwypr4? zzuSc!gSwmF4P2b?bU)X6nD|1&u|NOO$@fP+JOb=mxzES)>m!ibCW>^BP5@$gLY zAXiTN?Z`D(KG~A(ZJA(j`%O<%EOuK|+(>s?esukz=pw7h>~3!EsecsgaiNFz%vBxz zIMVpyxa3X#%H|hu!}WsJ9%vW@0%*x-aH&H{zpOW6!ivMChllYsRJj-?9uFS5!ovS= zZT!lVu#|w=+yb4Fc;c0mtM?PU13QvD>Nt&!yMFzrUD?B)z_<3E5yig?3LYvNI+$N$ zaWIx=0oR_zTHfct&>=AJ3?N-IiC`i?qzW5Sulg?quOnZ-og+Vy`-|9o-IBe5adeI$ z<(X}{2|Ke%M(oY5y19G&Fa6u4XJ$bH^cFZ8oeUgYpk6Is@MP99BZovK!5t569T-?s zSPUu=+){^&Kj9l7nI>) z*-*nljteGn46qA;#2zRL;0_AT#{}S_(%Fy&06|0Okv|exvop7QO1-wfC?!Yz#LJnW z7ehKNBh!o4XHZ{xb0hAD&v*bJy%F_xA^96$iJ!JYE*nyO|jqOQ!6*q%&5P7`@kekB5>= zNrb70B4(lEie-6<5QQmu;GId_076ojEM#9m%?fL%@*mp&+be4lr|_Rk!Z6~2|pxeecrdG?V-A%LXF$x zngnb2`N?iQ8y)xORkQD~jxX=W>9Z;iht2<1rXzLDre9sd+_CTM$X>pcS1sQqP-?O~ zouk8^237(v6QF{l;s`{@_d!p}0MCCAp0G${(3c6>^Q(U9eX`csdRLrdr$Z_yf2n| z6_C(g5a&cOI;ilwE+Nxf`=V8I;BlE%ZYqTjd@g*5wvh35u|9j*?zqmmO4pgj6xLjk z(~+HbZ`P-JKHtB?IDO|s)1)eXF50(k+N%ZKb_}RjStJG#__r+JTf^N(1=%!>>OK{ki;GL`oBLm>+?4?};S z;QNZ`KSBc?GV%i}c#)6&h>QjO$5)_3Mt)iZFY=#1A(7F4b_6n((e4 z`VVeEhm8E71YYFBEBTO7e{2FeWaMWQ@FG)JA(7F4VgWj2LM>I%K3PJTI~| zT%CN#(CI_t5swZT>B!EDEVT}ajNYFe9Wv4mofmoEdL%N4>rtAcqeDiTk@F(|zJU)J zwKX|9WTX)|FS5i&Br6K-DQogzqN^p@P{kdY49yvW;sMIxj3!$yaU^q=NM z7TttIM(;+A4jJkG%!|BfGZGoS7c@F#r1vr}@`f!)Wb{tV=#Y`N$GphvAz9dA{dx(kVn-s2M;GSX3#7kNYiiHzP~ z6CE+ND_&R-rEu#GSW(t7rFm;BrD%Dx1HT`oQx!U7q%RaNaxn&pjNT;* z9Wv6Bi5IyLi$q56(1Z>dX^6y&oQFdqqqj#whm3S1;zfRnMdC(yvO>lUTQ%Oi<^p-g2kdY2FyvRvpBrUgTIsBra1|sndfy9l$Vi_FUgS_!BrWQRtM;ik=6pd z$p0KgBBM7NK!=Rf_46Y89YZ3cSNWquMvCfrkuM)dBBPhsqeDgt+If-x1GEm_9fn@s zjt&_qC+9`>QbQu67nh?$Mryfvkv&c#kf>PTet!fJHLNEtIPvg0Wv zGI~)nI%K4Vm>1bz1Br}YQH%~5so&*Aw$(%;qgV5yLq=+6d6BKPkjUtjwCIqL!c|`6 z3))Cz^fFd-$Vk;FFEUF9iHu%DiVhhm+2lp0|AjN zk|`1yy)XtHGE#@ai%c~`BBNKOphHILKzNby@PS0UnLc_|2s&h>27?zFdmf35UXg(g x8L5EaMV6x>kfvB)@8sa>Os}c}1q9-7+p7OR*VO|C z2pIGX1PBP!RPc{u z>LM|Hn-+S@!eMlkFLIxZGiwr+eMK;`NpjLZNk#NFKG?%sUgF#H`aBtTVV_89YEu3$DGJ}Z zME$&BR1-H2PgT9cyZ}&dx1h>AOy4Pbi=%x#n5J2uJyw_n=_&%}Ormcvx@{kKJbb_G{cO|MhKI?nMTkT8H9{-mttY}!J@fq=zufg$eQ^geOOKm+KMS)MyLJ~VF za)_je>o;ndaCXm}3=3#`#89eGg&y0YBf$s@m z=EV}GjY^z9{16+b31rjYik~6pmh+DfV(JQJdx`#R38MndoS|vPEn8yarpL zz_4)PRr6fGo;CNpD#!jwv*`i3m#%-Z2>8grNva~MURJVyczabgJcuZrGW135ca1an zWyJz9{q)62q=pmz8K`) ziE>#^!Bl<{j#C={8dyjoX>So~&^|F_w^&_YLlmwmZ!4GQ1V;BfdmQx4z;kohcNK7k zD*-d}{H(h1Z}Z)7n|t*(Do%vYN`13op-9H6RrZym9C@4tkA+lc&j(=ZU((>fs(Ntm zO@C}kPc#R2z_XKd6lv=1uB8HBPui&?)jU~GF(LoHr6$}zKP#nNAwIG=A1p&X zyNEhAB=D z$mbsO$#2aT@2N8JL!!8fifowoa-jd3(JaT&Ft_S5;=Nirqz>Nk{B(P_<`1tK=gl|r z=lk9X?-r}dTEhI)LQ$H0mj=7(Y)mUn_e_|UwR_0Jp1lj2$KMw#r+mRV&Q2yA2&#&6 z;MV`Wg!MM!{vl@anJo%<>CPP8u_Expk(SR=@9UZV8Tq9MRb#R=^btVAGq6{Le*5s5 zyYH+AAvL|t`vB0NuEEpI@B?}!j!h;@&bLZ?<6OWjc2RfmL={S&?vpx5O2T0G(@Xps zh>B^)CV6^gk7GqFqyJnNF11M@mW=N5-QHMbW>7Cox}>C*bD#6U=ZV zVZb&$6fv^gxOTW+$;4zcUkq)%-L>8OK)-g@U6%VlND|$vmVe3^2zFN;Xm5u}9HZCo z`S6cPVGZa=n(_AV8jM;o&if0~`t(!(IeVuJuBrSou%QqetvYvy-=JAoC%lA zE8=BMp29v_6{z0Ubf-c}Gsp)h7HG}K=2SVz@Tdncp0B8bAn1|_PC>V|K83bXR%+It zJuh`I&m#wLVg!#eyTb0N#edKckJ`Rf>juBH1sM z2tgRWPVph+hIqV1$6)Qd)HohUl%84b>oq1uvfO{Gl z*_PhD&mvWkYy$W6R*Y4k83#(fmGGAeZ()EOrT)F9=`< zU5=Q;Vf53B_Fl!*?R*^0V_NX}0I1%Lv#@h~J#~mXC@qmvP!=sSs~rpV!{!xlQfx<& zBkfPr*A69e`GEP>8&Q{#)Cb5>qW8KOgR!WKCkVK>j3)I)e2P+IO=_TP=7(8KZb)eO z5hyW1tmr%=N~>5ANXRA458A=2W<}xxfzQ~NQS*UW#;(JUQdF5)BghbYx?cb5g}TEw zG^OI5BA}n|hAmQkWpS3yD}B(|#max9W;FAhT3ulvkIb5guXQO*Qu%-&)?70oiqwY3 zIn^qCl4L1V!{~I19#=`cm3C@J4SxzR-heYHD-^OfCA?95vM@_CuIy~{@ z>LMbw+0vFtMo0?^3x>3Y9XD-_2H24j=@<1#-2B54&iQTb?OZGf6Q_uZo3RP!|Cz?l zh|9Dg7Mtq=zDTlfllGFoQ=V{bCnlLvJV*mO`_^ED7`fPer2W3W|5<)P^$;9|J5AZj zT@$ho6U~&>i9C^ne#|zxgWsiW^n$4w9xgbIaBsyolf*zs6lOc6fNVddZTN7P#Z3|` z_%37wFQ$K8g%t#g>H4r7#&~)&eknPfD>|Cx-CT@)uVY_JPASHj8DUCMs?;2Y`(3@F z$p3~vJGBQ9SN#y4lz;)BdMYV^Ng8@nGAH6x=@m8RW$U-fSp1Js=}$XIx~4e8iWyYj zP6l=m;6eEUONT_hJMU9wf9K$qj!*enftI6_tqe=*)3&)dS@ zf;Iu)?6l>}-?R^ibdBkfBxvp4UxILqlN}tYsP=udY_OGv)KrpWN_QzX$kCRfk(-mLvRfgc@XGgPAR{QPgm=c#tsRZE!g!OH-CQgA-Q<9O z`GAWj1>-N$3B$A=P`{%Nl+=iydx^xyPf|0TH^Hm^(?^GA&DQd@M$%=4pt72k2ORV*p;`X8Cg z5`z`n)(NeT$AKaVahgAZJ@IH3PMIa$J!I9t_TbZwPVxY387fHICR_bJVfs}nuZS{M zP%owQ95Y%a$9i7v!VYKZf*QyZqr@f$Q68SSp#&m~o7f#_-d9k$rI)u2V$uX=Yr%He z#;r7Jz1h0`8=oc}H1z4;ll*9z&FM%2I}|$NN(DZPl8opdy+Rxs(RmO6PMn2pFie2@ zgTw~DV`iBRNP(NL--F(l`Xn7&SIHmQ`Cmp6e!C>TUp}Eqk7KpCYcEUXz=!Ju=s7;~ z)eS#g6S>K9VQezV6y&Zj~#>#w?16I|f8Z<&q7F+J5^2PnGosxo8trtBDR zn+qz=d7Ib&u3li^O!mc&ep)#N4WG>%(ZJF-bDnW`@%%U?mfuo`B+XG=jV&;2%@~j@ z3CdQR1#6LW9$0UzJYg~uCxCIanfp}^bdvX00(4>}c03F|=e#)o7UlNpEjcUYJrIq~ zgtr3wdBD8A=NGA)o2AMFUx63VwCm3I(t>-z?7w#v(Ezk9Qs8m*L~2K8PTJ%E97Utg z_VA-#DFg!;tO`?EsycodetzEuqprRoFJ>c__)URhamHxd2?oNmC~)H<2Yfrh03 zfYv$$j1+evEN|51RT(wsPOytFea6-A*_A=DYQ(IyV$tc;m5OSyYASv2T8!$o#$+X- zDy(9$YsEsW;tH`8BC~0YYEA9c%_us_DUIXPdl)0~v8(yRnwJJYW?^abf_CewE=m%}-vFU_ z9_%JWO^EqEjpV9P%&s6?ZyB?3}CQP7lHHYe7EzS521`MNhN{XNK5LNSotxGxBKeNb!dk zCuj?7d%@cgD-Ji}BxivWDLhQ-DPiD3_t*Ls*--B{BW7^0CXy!=d}=F!h&}j&D#JYL z%XfyU!~ICRAF11`FlSo-;_rZPJ*ugqw(_C3DZYx|Ez(*7uxs@oK*M&?WT|wd7#8uh zE^J%cvTLykWk3cvoXkta7K1`#Ut7AP3s{nv_;xM21S@-kBxG zp{mt%y8w35Rc>x2Hmvdm4RiY_W9<4?70{|M(JN_O&%HoSyi~0|GMbfK7`+>rMhS~1 zVo^Enz<#SFrO{18*i8-}^sCo-paP^tzdOcZ*r6MCwzN{ZhtKSsxsZu8et<`nLWCm? zzm(j+JX?Ft!g-+UK&=t~))Z*}O;SMoy*GUqW{+2HvOS*5D=4>9f*jT?5RGkN(4DRH zXwWJ;f~XCbPlLMn<)1hZh>2JXQP&%2jZ?AYMft;NiwP~&0I!bl=s^sTD_L1>ay)$@ z)rs{cSxO}SX4RnA$;FUv4;tXlzq`>j~zOk#{lSF8?GIR^Q&HuUB<2FQCu(ts7 zJ~{Ho`X-!emtrJix7!YIkNIOPRA*|u5$*&=a><}y=u|7yH@LERu_oU1)j;jx=WAd( z&mCMq14eyi60HAR=Ewc|m$Iy!@!gZqkG{ZJC+M!4UieO1t@$6+FA>5gf`Ko8*9}>{ zQjc#|MvR79?2Uf+!QlJ9K92bYW7l?lHp&iK=q2j-C=Zv!0) zp-Q=3lB!gI|C`P*Gdwu=oT|YU`*X(=`#Q1QNR{{5o{d-OA}6q`DBL_T@xOLfq|y4h zWSy!?_Ib~io?5=f4AmH@+A^)sx)t&Krl0uyn+T<*it^C!X!sXuVkkB<o2TbL=ORMBNpcjC-AGNKqph27ycQ6kgrGsKGiSmhaww}_DjZo8IU=Nju5v8-i-gw^n~)CwTR8MHX3EaJWZguX^ityBFm+=*MeARB&g9B9_?=pNlf9g}V z&n7`Pp!QBEm!#uxBwHO*6D`p1q7G%omvThndtmw{ot^8v#xO~i`!fk)_r3xFLu>bv zAUeQzrvM{__Q7pfpDHA_>8_zdo!<~!{JC78yF6ledtuoJ!-dyrd<)Z6Xhc4r!~Fn@ z@Av{zv)MbIxwJRU55zp_ z1X+w}ij9_X;*keh+`CJshASaN{a%1HC;Zon&&+qnfz8tJgeA%_iv0DZF2%zNLu-x- zReoo&z8sg2lbt9hWr?hO)dZV8Es&X9u3H^HGHA8R>ZiJel%tH^wdlx8<^oePJ47+upBx}RX5ZyCiOb^o z<>lU0$F5{V6XdFDX4Jp=?vlEE6X0XIq_q#15vZI;d4lL!JiF)5N_JgMyuM~9j#6m&hUy_5Fn{HvoUli%P4@msT2^W2 zfOu=WzMD3{@u8FurQ&UP+Ja>OnGp;x3)P4qZ(Jv7RKhx&IDY7kW)8y^{j*#->{6B$ z$Cn{~3rmWHo(S~kD6jZ*md2FvIgTexq0?mKYco`p=Wp%P;Q6j@s~_zBq%o$IxKZkm zpf0v7x9W1OP4u>&*D~3$Hc=E?zUA9WYtBtr6q{g)rRKLOeL1eSn-6B7Q?QdO8rxu?2xn|q4T&njQwiUaL z`dj!=kKP8b&H^*57WCgoS;$RvFxnX=C1Bnx|5xxDYp zHaP7uUi-2SPiJJj;|a?4KL+&O(?5+}+^w>j9-CC>dlbY9VP(O?+>pU-Z{7nWeKn$X zcG;K8MxC&}@j`2f*tke(E&1u)t@&fP^Dcey;;`WZ1{iEEyqepHK6xeXIU{+jL z91G|NHeL5x#V{2Rdy>51mc0dO&#!cRPZ!WUlii3oK6^!I<3J+d1gYw()oUl0I2D{N zfxA$}Dn`Fuv>LmKa%wIIyK&K^f781x%2}QK=pv8C-~8EnJQO?Rn%c{oCp7*FEyVCxK497Bh zS~zc`ZHxSho*PC?fTk3^`z`{Lhj?G*y@5M^l&)6ag2mlo@(UiyO`^Z_HzY~1P0A!e zopFPaMf9rd>*N~|`&?413(T7MlwFg#UY)D`e5tv=DyLRRXz1jb{q}a=sd#3;_ge4n z>i56pFeU@RE>RXRptVkGfH@YZr^k!wOKqbqkEuP;**bXd_bXEILKeQPo_xG;%K>JM@y5ciha*0 z@jrD5B^Z^qacSzMnKnhIj&iF>ptk-iBiEP1q1xej;xhi4+baY?h)&iWA2CJaS8Gz8Pg)jaN)yMpqg%{=Yd$-Q>V7#i`qpIqzDSxNIW7D-rHAP2WlU=ZMMCT zjMCL=ocBvn8t&-KAQqntY{gvFr+)urjx#H|ny=po$deJ!YoH5_VVwAk=rjZVE)-!MH@aZ)q?ijy_2 zCL-w1{lW5Hfio%@#Y6<-VED$U=pIdapPjsU0V?ScIJM}GfrrF_U7 zPui9Q2#7%U|5GV9Vc|41Gc;l{HZfvnV=`f7GBjpoGh#M2W;15yU^3z4G-Ne1=3rv_ z|53_SlKikC9K^ynv^x6d;Q|>%I;>gBT-;k(pp)Vg5m6DvDxW9>vChRvz(g?M^l2=^QG7Et^&{5hGd^bQ zeV?Z9Rr+C!TgW;9i zrvs`Q3mk0TKce6kI!L_G0<;jl1GvT;r?BMqbDZ=R2l+f?38?RfB+e>1UUT>3`H~fa z6k?fQz!Bc_C~ zZj1{ZtuM@xT;rX$J}luOeViHm9+U}f27GliyglWP&;9HEms2648AJpq`wCww^>sOW zzAR?B;LlY=9XL?vIMOR|f1CdLR8N=7TZSk#wxo)v$6Zg!!|KedZzrlaZf?Qu( z_KqOg8f;!1tL@;pcz+}hl4Sz17~;4S{^g@N!)*X*s&xu3?pCGI4}7@w?WiB2Y%8*s z-urfNx!6?7n;rSCFHV1ch5rWRj~Rr#J^i;(uw{JwR=i&D2U1?Q&di5IN9R6aaFoFu z*)Ytwp~~6q`%eJP6wh{Q7kG1*$0=@PcRJQ4^k?&h-{fU@_YJm72W$}hWa3s1U;U54QXJ02!lW!t{!D}z6 zTQj895!0!!Z7g!t>~43I=z3^Ro3zBD64qlL6a3VgN6}btRni_C(Y~{oY1JT?7Fop$ zVxIw%?SH7NT9?})evlYwK}Wt*i|3`98|F~@yfb8QC79RNQn+=A z@qm~5z28(1qA9{Fi*=p!zfQEge9f1+m@QMMH=gO38auKOO}7@+R^;Uy7R@Go<^*`? zCya?}zL;Rtibef$w4Poh zb9~BH+X?|_@a`HJxQ_Cb;d|u$^T65Dbo3P|c=+0UZlH5L9*FQ6<+#mk$hsj~%P-m0 zP9DGg&J{*gu?9izG|3Fu7Wl08*=d)xHk~#PsnrJ#TTdX`ZVAN8#6=AaQW$vD)O)Y9 zq(`*+d@8fQ-?AK8TMh`@<9W=s^z;?!;}Dbvgd8_(KpW^-zae-`;|fJ+{z3!p&7>gb zCWpJB-6~W6h%cPZ;YxruUcGp@n+fVHQigrX3%2KdyWL492CZ0H9f``CRP}+a(Jl1L z#{le?eTD}SdWgrnGKbPPRl&2_Tm7sIL04YwP1hRvfQD{5c{>t4onFI4>)(FF$7pElu$a4ig2ec(&kfO8#v1Q)_y3Y}NcTGx2 zLoK1^8rk{91CV0#MB|1DO#NWNq#$6fy0DBBIAe3$osIf(oF#`P+kq$ zS2UUebAKwB*y+u^y`bR_t1xrqg7nG8=T;&4N2^9nVbhIQ39+ikBZJ#jz4)d5yrJ2e z0k7#|eB&idL5s=An=}1?$h8i92+bE@yJ$rvg=*9?C%1`t4SmbLm(q_^SJ{3SP`FZ)N5FKu>2=EGcc|YUsQ#dVS7uwAX z$NC75?E9M?#qFHrwWn?yjxNclEAYud+>KiNBeR;ENtf&M(1!ZYA7y4(q#ZN*rOCDY ziJoTbVZ1aU_u%S@?fxcu@mVpM%T5m$&AkV^DS^s$*PGNNt5ir{fN1)u(hyY?l+eCt zWNNW`{N(g=C!Fygn=B1=>#9?b=xUXQ)tRpFW;%@*Di{8(u7FH&&A%2vm>G*fzZU%> z@m<5wIE;Sr3-#p9D5$185o9Bz$bDiHw&hgAsr~k^A~*TpuxwRU?dGizmyw^Rw;`%b zA-ttM*Back28(Wm*Fqaxb<+oSHY?LK6|gR}=nV4ETrf`b>t?;4A4m6yo;!Rw_*UFs zdcT*Id^^{#FyA7D3`LD|V(wws1T4wlF|e^{Rb*5hZfhjXL~^%)7=92B2we0PS0^g4BIw{tY~ z;M>xVX8h0?Og4Pv%`xBX4;o?X-WhSVy^q1pAdT4$APB*#Cwok47ezROD zO-xL_mY3)wcK65wYq4*Cj;yJ>>lJL;!7uLi?hs6xMSWV5!GF40!u8}mOxo-E+P)Ft z0KB&wBhGQR=1q(TO2VS{%a4_uePIH?7nBj$M~m_1U~tC(-yB9 zi1Cxly;14(TYwV(T|QE_Q>H)FOo&84RB!kE{EYVaVFI(`?j27aCman1S&TkAByXoY ztNiQ{wI^D_qag+p| zRj)p#ZDR9f5771V6w><5!%6;9QcNNj`1In64t(&~W{SQM;dwj@5yym6%os$zVKNqG znwGZMn+)E&k=dI z{2Va@9Hq({6ZiQg4-AXj^%Ex9))d*L40%G0%dft6D2#6L@&zqG)pHn+Wo$O)^^w|X z^>@Jz(&HBF8fz7qp(WFZ_1-K4wcyLj0qV*3xgr$SM^=KXk=oU5E!RH(nYh{O#Z7sc z5B$qcGiV1Gn8&9{i#SK?-1;#im286?6Um zCe*A56x*$Yz}PDfe5h#z;y-$qZ`6$c0j$L2_9Ba}F=GWArWDg~j&lfWVqY0oTc-}h zzniQ&oE7aCd6ufOQJB$+Aejd$?18M?;oyDx(4_351kC@tP%aIzPlK#gl`5aMZBh+Q zFDB8-x9w{XZ&zx{Cx`Us?bY&41AT&!-k!hCyG;flwu%8ALw?yk8O`pfLms7v{tMGY zU)Cg?^UY#w6EsE_m|{IUM+TIpyzQ2XIrAunPrH5oAN406t$h-3oG#*>Jpwd9D(lAp zSSg@jijW9=Fgh#Bl;N9ddji#ui4;BG8hc5ij@>)+q@F>dizmxf^>K;8H{;Umb@P6# zcbBvWZo&x*02Ln)kxo)CURvi|dtiT}_?h7PZM5K8T65`tt@T?zcS8md0(mG^vw?A# z$~ocm^M10+4@bQ`E{>K%t{b76~_bKGx zK=J|Ckf0&Q%a`5BH^E=EqrV66Bt#Y|(kx19EWKB;0O$UZDssO3Om%1A>Y((8Nna!c zJo2Rm9>&N0JcszQC7VO9bv#6CU0%81Eh~vC&p5F-o$ZI>Kth<<)0Q0Fe&SIw8YTkI zuAWriG*ar_sC78BN7t}(qE3A*6K(jDtmiQSM_@yrmo6AV^nNz(xJPe;`Y0T^*Ry!w zP{j6b)xyip8h=0jz*!!qI1K%|06>ugms$jp1xn8yOjXFU#JPk68d4;)!RhrhG}e=s(-D z9Jimqp$4Y7OD~ni6Hzz4w;EFlMN6XE9IUJ0n~2PB9NGZ-;)d`(P-JF5`giQ({w^yZ z-KyHG*SEec(~cQH)q!`g?C2<13oJ^E$EX^NU_?o+3GU><7@vl^IE-fG0|_s;x4bmt zzbh<7zg%i7m5YCi3+Eh02pcOPUp_|io~8WmIJ)|4=r$h!;FXeJBRpf+=FMP$s|Nxn1qm_XQW!mP!Wi<$lcz%vnQX-etiJ_ zUh5p^9Keg#=MLsh9MIA+Q*pbt;fPWmPh{P+_%oNgdng!T(n;~tj%=61Vvu#urLMzZ z$7@z&bQ)>gGQ(GGv5lK6b*jc12|PX?69q%{x{ZgA*M8A#%Cx$jIRiaMe^v^tImhkz zgrBbI*rc;|EdTyYT6f!*)_EXuF`|z-Pj#&5u}cX4l+p`Z|0a%`^628Y>0(8I9lV>6 zRy0SQkZ<@Ijcra&x}~K$w`O(boTcoW2K+MF?2V4?x})BjL8osA{$X|DZ?zJREKWp3 z{9WBQS=q{L3Y?aPq^T+5Z$)7JuR(OCI{^jK%Ub6c%~O$G8XOuXQsQ0JTL;>Jm&df; zkPA%>F823%Chvn~u^9LP`MoXgu&PF?|7a%DP6eFi#c|Eg-R;ge70;vM@K9+~mervP z;3I-usn^;9gKtIKqv5-ze^K_#uYsX(c)atg?xDm^CM{J2A8(t8w3S!HA|)l@`KKt5 zJw22+0mCq2bp@vZd5w1oW)tbsf(0>M7(GR_m}i77FY1BVX+!_G9{hz^;Dr}TBmwxB zWY);^dna|)3PF_;z2#j8E+m4Snsv}Ta8oGU;HUbHgniCc)CbL}EjzbmFq*05J)K|k zJBSpZ)0?X;bIp=>adB$L#0PZj)>T{hk7Uuo$q`vl@5T!>@v`+A7=07F z&lr9ipve;JH&*VfYS!r%?d&+xF`2=F;{bOk;HcJ;sINo`hq=>LgksrUJt|HCyM>@Q z&dLFR2Ie{cyqTPHR^K7==&)qCHl0QzE)v%nIfA!%2J*XJ!WpQrLpt9KeL4tMPcLVK zM0kcSA$?wYLnSBD{U&g7o#>)Lu>TCYN`qvIc%I8X#ovJ#1H}a2>xp+hy1@7>UJv++ z|BcHGBEf#rs8YL=6I^T#VS%9PIaeDX9}qa*wr>fZ44 z{XrqZZj!e{|BRY6F>_MJd{=&yIzj(I3-?y_zYlj|{*}+9S@B&0fSmGvY*@dW5;Ymx zO#!~dH7ceXcqi2sOy-3fEOLUTmH=#Ou^=d>W;^cBA2&2^->HH(H*tv{C^aMKL6 z2_tN0&lYJq3=8B~{SMkK`e!5@Fn=UzB$L<+>!)9vH(7{NMsth0(l)(z^tu-pSU}r#gPz6lRRu!smtmkGiZg+23q9tcU zv#(+016NpYjlcDr@$w~zQun^J=w!mxF6kj4hL)Nvr4>FRj$-VG+=yTQ<(tz+i=)x z^S&u_8(f!rUpn>nb^Pb+GB>WfwpE0u4$yM7oO{gTU3Yhrg2aGptuuNCPD>xOzC7rv z)uCm>-jP{Lned`(WfK;^Wh0$S)1dgG48X5g&?6-A?pxk#trN9_x}p4+(p`I@jU~C6 zg--sTi{6fZGiK;G@#P06YbTYRC0`(iiP7D`a7Qh>VCfsV^5Ax*JmF%tVGlS_v4&Ut zV}ikIW@*I-gDYB=oD;T1U~6%MPKPq)q7kWaAt<*>eQ=vW}0R)PU5|MabTg~A&;<0q={q2S?rVoM?-Yd zMXJ#?om`EDx~vU4iUhgDXM0W9q z(-8%OO;bPeyI+h@x>K}8*-onmGfNd(|37* zuDhOq7sg3)Q?p&d6q6@O#xaR|I7c~e>Vzz@d|&hTvt=M=_l?y29S#CHgg`&#AfBp* z*X^ymww>_T>2gFU)mT0Wdbf|w>aC~cp}F5OZqMoHEEXZ?>8n9|V(m$DVTKUxapRsj z7|q(rWAX=*!^(q7Su;`D6R1B<8H16mW5)01?lL7v#Wz97vrn?oONX7mZG9_}VVG-F zRZ4cJB_wc%5P6IprB)@ACdTD^q2dR8A?i4&q7DVZIW~IvLX8^Yx-8DcDVKG>V27at z0{CvgZ+O?P|DHy?EV}UnV2d99NrW}(agLu|bBp&Oi1^kddr5)YEM>z{NcqT=*t_-F zooJzUO;K0Ckw%myrMX71eqE_K8PbOC6ambM8@Z$>vJ6q5ppZTtvB*$hg(ym-H zXl}Hk|HgZa!EKJC<+636W9^T4fVMT^$aQoDjEZXpp-_}0l!P-0WR*Dt|-*7)JA z-2#P6aEEF`Z^zmjUSbAxMW?Hz5AtwGBfHCH?vrZ&^T2z^o>f~F9>$b)Pwvm5c5t0g z8dS4Nl zm0UXT6mWvpnjLU8QiJ^YxLeugmG^aedv zzZV~;?KioGJC8xF`f*m)XQeCU*rc~#53w8*s%+*o71XT}!1qQ0fl-9B>dns=aPzY_ z^0C0x*GZ6=9Nfa53z?Wo(HH?j#2wA;7kI)z) zE(ybx=$4!E%epnbNfn5Ql*SLzCKi#{a$~Lv(u4F)DMNlY+!#`j*nlmzcM*fIrlk=^ zgqpbZ$>FcQt%g;xR+CqH_{v>44&(^hqdHjgOvpn*nz5OPjTDy(|Kxzv*4MUs*n8sL z_@y?r#{Fw?Rqw})4^ei;vUGkrv>Pt5kbI{K3hiyfCji#AOp-*C!%{3IqhP-<|#= z;UEdd?5h#KJH;ySM#yzmno7kE)JIEF_yUBFjx8p?V0&Zx-GgmOQdzDun5)u^yF zS$TxqAH2w>VaC*Sq7O4whm2;aVW5{i_oPh}3(%NMv3On5Q zj|KSCMjh4U`5~pDw>viAX-LXokm1Zv-Hi2wyeF@<^)t@#U4++Jn=FJl)w)a#8<1D( z#mp%rmgKY>u=Lz4tJ4oj-bD);-gAlgF~mMVCmNpGZlX8g*B{q8d`4hCvZSB?2nO_( zeqQWq@3?INHA%nri-qV`!^#Xj^q>#kKAYej&rj2X#3AeFy_>Qpgg9bfBG}-GeV|b0 z-yGtijS7~bvGU6>01AEC3n9|6<;43)?JAfFKTL~%Fo)|`2e9wn{jadf~1w_NA3Z6H9zFB>{=T_<`>Kquqy>HCm zF)hhi^DAbrl&NYo-^jUo3lZ4-ULifDTy6sgn#OCM^9U#&Iy<#9BQIv%e~rIO{MIVU zFpknmRw{@57qBu2yc`{E$)_@96mc`}>($Am=eMZ*EExjb_ zYPaNmfsO-ZT-oEhQc_rf{-NyYBm_l0Kcnlmd>?)9#&hl1`A--{*E_)!l&zZs#OL+- z$#cUf)B?6^c{G7~03V z-m_*ehdkkp#lkCQd1oB#@^CrYs4O5wwNVko{}@CCGf6X!JpLY~0OOQB{3GRl zf*RV1r@)UL?4tp;alx*^Z}be3092C$=z+W>HB0a|LjE>hFkjZW_HZBLU5BW1`)Byb z`44+6HJi5l;>bZuGz2W|uDZ?*45aEQJ{Md+`QfQwX8GuYu5n(b5PTVeEiE)SGG+ROe9xu7EQ5pz_7d{a-1 zU(1ut$CZ1V)e$AabUG?3z>O6(!qE>Rw$9~uNbTh9+$8_jB=mxPqrAY9f4!l~*9~Iv ze-U<0!Iebq8lFiqnb@{%+fF7nC$=V<*tTuk=ESybd&fJ;KDj-A{dLY=Uv+hLRj*q8 zt>=B8?wh1p=h=P#Ys~kmsk9;F)SF9bfiUq5xD4azH!{TIJoo72^EX$hoxqnR%2C5> zjl^@d)+>KiB~g;(H|YS}zP3|eYk)+zcGLbSV#UK!B<@=e*-Qr?w*zXmxgW*$2Pwi1 z9ERP+7~Q3Mg1#%IJmS^qbSCDfmQ0M>KU^am3(1*Se-K5xH^E&@7p^p!tVxCuC;$T| zQePdxos$B>1@8eby!g97E`2KVH-l$x-Hw5K@CpCJ)(>b%V2d!sZzNNeZb2i;`=1BU zUUC&$?GYLL*<+f#VZVD_yG%C;Z5lniN88PZ6CHIFsL-TdnV8UJ1URXY7|%39JDQc##T0_DOiu(r@r12z;aG@Cxdf`Hf4JX(CfJOI_U3e zkhUC{e6+NYUJ7VsIc3mKR`L*h3I3j1$<~+xEqiga{@uVgZ^5HEo%i++w$lMaI){If z_$O%c@iE1*&F=GIq|s-&tLE!I%Wv*WI<=Co%S#HC>D`okpc0q?SQj7Esb4U%$6=>4 z{aC$5@B8y{_iu-dmOBlDyxGrRM*y!AU3+d*sgFwQS{fJLB-sSC2J1c|MCbEeDO9e~ z`cZ&6Ec=cfhq;q9mXX`YQ|LVO`$+Y&q$xG+g1=OCfqPgAw=C4zsgWF;oCiP-#|!HN zt-Ey{zS8Ul&83>#Q z4~x`smqhlReb9*g(NC_@l}~-POmQ7AYhRytodtT&ZASV;p_Q3_di3_Qxe7Jl8}Qd> z2*)(?{rQde!u^CD##jmGfghxsEVFoXN)TUL#T&3E5Lwo7C_YnQo%(6u zHg=gJJ=I$aSCpX;dVbf51(bE&KkM8zz~R zb4R_x0knC8m>U8M?a-)J^+{{>`#8RQz*%_;;0v|gkK}PS(2KmV%gWEsq(%b$o=Jay zI8Q%`hELLg%xT2V$-?#33}VX0`G3+)YhOJepaDn+Z60z7X}72Zj3UWYC~%0P zqMuTvMD*4DQfFAiPzfZ|L!N@6CnNmbs&AkA(#YBwiMFZPlpM z%78KWcDdmm9IRBuZ%iTg?yU{j-L0}m_mGV`wKfTk>3Resl?8?L#qc5?d8TT9URS(HZ5ag;og?hzTnXNL6cQopZPZuP-F$^5{-FknIW=$O5AikS z&~{a$>R2GD#Zom$cX@T`Hgmg@SqX|OZ(o#?lS#hQXs_IW6T{9myjAT1<~8MadMUIf zvp7FS3Sh^@{|Vw2{MCC-ISo+)Soj;Q!-b`WvLHb2It-3?sueKSH_^PSozmqJL8MS8 z+w9Uq#NIh7coid}evS$WIax0&pmHbD02$?d!8T2X+VJ&Ia8=kA#-?!=`lrY>{hyZp zP8|*u1HHkl@SIlXqe!ATgG^UVAJU35M)ivkFFn6sv6`QDzG@xpQW+KHQ@tQHa|2NW^1XqmmH14@qFc-6{tw3 zEDteBYyUl5LU&?tsr(Q6eBp|?zxD@m#=300sk9JqaJ>xnu3+Wa;qF~e; z#cO?@H`z_<_G~tXHYfSWq5`_gfzDMu=ZivniqddBW;-fAY7MHmAxCe!IgFJ1W~f>t z8`{+=Y0js?L>EQU#Di{zlTi3D6c4nsf39@up=gL7jA3ShS*%4=TD%Lh{BLl~m?jpd zt<_-F8j!?SL7s``MYFml9Q9%se%m*xl5{z405LG+NKg`yVT~62NzUY{S5r<6F*lji z@CuYgZsW!|@_HfD4-my{{#QXjHA|;m{OPA09DjaN2GeO3&TLPJkwI@$5ac8Hwekg7 zEhS0SHlYGn^7*2)qcKMsgix>FlMBqB7F&t<>6UsY3{jwfKm}g|wtUU-@1`=J1M|6p zHyDc@DrJZQ%1MG%-UH7;N;P8`YGaL$v@oTitC4; zfJO7q>U*Z?uy(>FNvU{m-7}ae_O}2q&x?epI`cocHCvc%Je?{41HG5?1YWpS!diC{ z%Q^6}46yD=knNvIsk>^C8Z*ufX$7N9l8TG{n=C1?SpM$73S`*MdlG?v|O%*qwHi zljwW5ige;7Gf0Wdq-YA#tVOY56b{ObcC;6?Yr8YgaI1Yvln{xluhP$nGaQ;w6GHUF7E@{yQ@DbmRt z`}eEPf{<(;z`W{&dHRjmVY9-RWe>Rf?-;CdcXQrN3gnV+S57|DwN^y{ipdJNh{>_s zQ3UGwrha#7!+P4Z=6`=a{2-fi@-C_3#@|3GW-ZcgwQe2f>`G!fE*5jW{VWWb-sBWh zOh!~)78*lk8Jl7|43{lY86#0~u~n$ismJ&zc_f@7Cy6wZwh8=wJ;4rL#Z7(Qqik>( z>zK2O^ePY8iDNng`LRdi1K7JFCZ7)LdZQEHR2;Rd*O2LG{kCDyB$P9gg#ES=56F0~ z>N=EJ&;D-ZHuA*=#_b&zj|Bz09A2RMo?T#mHDZ+3AL@Yf%wLaBEjpYK7FqTrL7)H- z85+{WVMlfe3^#><`zQ@~24O>;=MeUU{kPLq>z!K z|JvKB(ZdtDDbn=iO5wD^Zv2mt_%5T9AB!Gf39eeg?ERtGJ@hFpU#wVDV%!_W$qr+v zSC)LMs4jbI52t2@ycA(2^K6G~NyU0C_^ekCPbynU zAYOP{)BGp)^Pz#tV0tuW!Yoi26Yj?hTI=?Zawf8^@9$V zd=2(3z-qbz%n}w8*wqrlQ6D3dciPG7(a}pXBQ>HGfzuj)#P74Gf{hC)1E)Wm+h?+> zqK@>PGquu3l(^Q$N`M0&J!_}y;TDfhO~~lLaidZ9cX`=&4U5K<;}1+?f-0KvVGwsGok+}h_a5DpJb8mv(d7fd~4$RPtNQX^_vN+T)iXUB$H&1E+J|Nv-Hk> zMcS~g8w2W>ct`4UdAc+FDbddpjb&ZNKql`Ka1V;R8i!x%dxT)znY>`(%bpw0IWwOt zE1XOfA2P4kQtX1r{tiat`V7{Z12_N*tk9)%Efjh-SMV!bPWL;RY=+nV!=+J^oE-7BE9HivniwKLuXZh<{k| zTTZ7Q4k!&Ey^()J(U&j70`~Z0mnBdM0Z|{Cgt#+IzM%uFrLu+A*JC~mU{IoFi+mQn zPnv%6bq-Dn8wZ*e@KtR;p{sk;SUpaAYiz%}OvoJmS*wweQ}g6t<)V~#_`uc3S}&#j zuD?-Lq4by_Uu}JOAPghBACEEN!$oj33-g8Ebph-trfE~l3*cenYr}nR{21qEby#a_ zaeH~POcGeXe}V!dth~ktlrPL*?TW6{z-`W7<3nX01aq71UD;on7)Cv3K%W3WH3}tL zoJ+K8H4X`B+_9}rQ&j?!(O*zWdVp@4+^0N*#SWrb(Sy~9_^F_KqFu1F6R8TH|AOx0 zLmpOhB4mx;qt>E_h=ESBljIi@INz-l@z{l!Xv}D@%>{?TAKtS_jYsw?m$Z?(qW4** zd^w)nVD+vx|2X`BiMG~fn3}w{Cv<&#`>Bjx<<~6%gsxhSLIXdwv3FXWhcIk`X%HP8 z=XQ|%jTHU9a@Ks$0!zq=nCp}vdUm_4We8o-hwj*6xaR+*KAMR{6Wk$qu2%^+KR_aACa50&69xzw%$ae(x0sZ z{77{n;LBtFM8lkGqxN{@miMo>N+z23T8qaiiTg2^B)?6of#cQvw*COo|XZ?dSii`t~SE|txUIE|*-yT@|{)4^-CTR1HM)%+L zEjQ|mY0!XzP`N#VJq2CZ)+_b~1Z(lDUr*0ow0|Q5?nXd6>Br*GQo)U^S6fs*^X^s} z)DCC7UK~T7cco>6IQhVB-F0pC__+KyqAcw?s-{3iG*_HGaD2?i66q+e_% zN%syTZbC~by%fv9KIxC9TLf0m7|&-(P&1;hUyRmvuW<}eyKF&Or-K84EWw1YVs z_rF8Mr7Tyzu`7V9lyQ9oFO5I4RRkhr8e7{I4A=jTkBtO>Nvo%RH@62yBrK3nnl?uCP~Lp26=*x?^?`W95Avp#5owvYU_BXxXs zYCiLpvP?F`{TXfvld?3jfkN32k#GC5ut8O&^Hf+-4|FaqkM!%V4y53q**+Tb&5%V#${ARAo^V#H9u7;o3LX(^9 zJ)Z6e$zg~sFCP@#`)%!04L_rXF*(m0QwQL*)Aab5%{WG}eV$=}<(*{n9bEn2?L!c7 zq0Wo;G9{H>sF%*uVf<=Em**|NDNPQ&KHf1BIoywX#`smp;&^*BrTE{{JsTf^ferw_ zR5!De0r{VXnw*}4x3$dg1x_!|fSkfz@sh5d#06t)c^Wg%N?xt>BF((qA(q;SjH_`= zZs7e-I9El7I4?lp$DC8&H}wZ8_*`9H;fYYB!qq^bbT0jS$Aai=`SCDS8`w7 z0Rn7^qPQH!xzSdGW*>|JdhMYk>ev0*Z)|o8#mlNz&pr=o4h^~<9DMvW8g|1a(wtkF zXU?=|ka#z)6GnkF{BIZgS|`AtzE2G0yaBu$3GaOpS;r${_gq0h^x((GK}5oiS2l!1~y{ej1(DaAMe9 zIc;9;?d_TOARPJClMltB6GXYlFrP?l0`;gke-c2NZ`vD-%xtvj$o;Fy9`h~l81aX9 zTu6tuNO`1_pA?dmpgD8+?kq%GuM^tCTb)x}4Su56=3X)M zbHM!&6_hx`9fHNvqCuOWQ0ZQyAm=d4j)B}6lTEmwmPg%oGuLqBO5uP)=f;gJJ!;l( zi0@>gxEqq(C*lWfimqjHbKiPK_^M)?8i<-#*|~i?huG&BKkH}XcaiZ7wDIm{8|GFs zvDJ)XHA{BCx#eMR9(CMc=FDaWT`Ojk6B6;~W(RD2|E_$wImq=nxRxlxgl9+mVk8N3 z=eXu$#kwFw@IxVXnptC(mPe43SaMO%u)BUhqs*lvZKi&`?EBx4e{J3qSlNuVJ;|ogWR+7;B$&%F|!^p2nL&v?tX6eYe=S$82k)_B&&t)KS!=kKs!LkfJv0uzaJjab_ zcr_r*TGg20CzqDinoI2IZA$A_E1@rQiJpXXSHwT-YVADT)mZ2b=;Ao=?4#JVfnIx4 zMuS<`Lfjem=-e7Bny7m%=s+_y{DZ&mQ%@vi`8--9+#+C{S=5^qg5`u}7}S^fw}kx$ zsfs>72x)TfL)r||^n1{2l5h15&*FQhECTqpL*Bj#{gypgS;5sd=F*EFUR0^*+{Wn2 z%3l^fCN5O?mZi4Et@+3-YX(y7o;q?f6%Ye+bD5}Xtc|zDE+7$#JgUQh0Thh$wJH@o zg9iVWr|$5@ZLY2gQUu=$fMzZ*0|y(MXP7t9hc9L^s$##{+R9nwglnAmtPj^nD=H#1 zJT_zCEBj93Pb#>X<8v<{Ehj=`WKPqQz*1 z$GMYeDrzO}*duq+f*VE1AqX9@2~lc`W~6X+CuEG#;ZI`*ZkN0P02EDN(>W!e~ZJBGYWQ(u1jNOhI z_^LkDy@`ZqcV>3|YivDz$t7V~qfbqs(jouu+1yE`psaDNvQB%=10G*JN!ucVst;D$ zGngHtCFq+wZ@wl=4~8N>4C61%h5Rmf$x#iYef!~X91j9tU?KZ%aY5&MNybgXrLb(< zyK8R^*dJ`Lyd2);L|no|yNMLH&T|RUqM`0`?0P&U?);9E02#7NC>_e=v2RNaT18^9 za)-#*lA!fo(T1Vn3P;`K0jS6Xi?gO>rQ5B4hdlW+p#VwHS* zVXHYJFtEw{QP^n*oL51cp9AWr?r;250>V6lpIN zw6m!P?Ah{{udjiAFI}jSPCZOTy_1|>&(8dYJc>-i4eENwMOx~h1_x--rx22}h2x9x z2K}ek-%I96%M`lDIA!@Rx9_WX#zU(i58js+<|W#EXavWa7%=TxW)uP4{cxYTKLX}{ zs9ch2SGa8J<$;X!bSpzg1ht4i_z13g;04SCwi^+d$6k_pZMPXw)>o8VCU3VZ2C--@ zk0hLA8;`qMmB^zI@gSms2Ry?IxEoj35%)WN>YA){+70#l^_uaPbDVje;ArZGPR=u_t3`Px!-Xd)$AZv{}7w=8tEYK4FT{pzwp~p+)$uXs;p5 zfG@Jw>}1-#9*q-$lSx3!G><;|kh-`~ESxF6v0o=D7k$H1wn60t8iU+ei$34#JNw3w zD`gw!KY+^hcEHX9up|$fTiN2@q{ieHU5zE6(O*f?+xnA;!qyDBJV++eFC_LPw1{iu z4C>V&>W#}3`Zt_fRcPT}RfjveqtGJ~y!|oi9Q?`S^Cop7gyD&_p=dNr&+Nl7Mh0m? z(={LR7>T`|IZ6j_gXPd-1D6jFQtm*ZeP}F0Vfk;2@#E~?8Ct(c<^n9<$!gbrUONc{N|bG&BjG9b-O z?TgjzaX@_mqy7WApD{{F=sko4Ib$*jcziV@H9MOz^?tiPv@2nau}Y~EIHNFb9pO4f zp1Zr;BBWRuqQgZC?Qh8v>Fbjbc-wejtoxjkI?Lil^|Wud6#}1?wfoxDnD}JJ&I!s3 z&#zVi`M!g2p7N6|*rw_xL1z&X8E`bkM@z(pO%d;vG`kv#ypp*b1`XMq0umz|v*!nv zHW;e8(@DDSQe5e?^2mcpqJKD0;C&@1?A4?;>Y(_$WJ(d=liZb=e*z?Z%cH$zC+K=c z^bysT>#tz{^56dLnEIRrE$WaSGJZDqpeR}hqT{=a#~?wWN*#dqu^OHgU)&n-VP5T! z4CI3xeC9GZuG(T|4_ep@F0m!1-f&&o8SIDC-7>Iqg2LwYSx2_u7}sk;-}X{+60Gz` zi4SXM$r##1pbeJU=Sq$h!^EMKbLYa_{;eC?InFnuGHyT!??KQDuGQYd=4lt#3Or8n zPKo#GTn=(wh>>!;{_UV+U~CE%Ec$TYE-fg0d$!MFf&lGz`H6Z7@5dOXd5yHy!E>)} z*X7{kw?CtqrEB%&uK0uhej+kOSjaUyy6^QtWa&F#ZZ2~9NCxt^J}4I%6^xzJw*zfy z&qy?^mnk5n{cJu0l;~4-D{o8b49CoB7r7RIGLfL(5!cux(T}PVd_~5eMCP6tg+Ji- zv?Wx#Fq>y~U!|WWYo+BsVJQrXbcppRz!3}5mP5miEL!VW8ah)=9Ye-;3{A_m*9R=x z$LDh|!Uqn@#~srQn-xc?lw?ZZ{&XqlLyV41HQ`)1#CX`jwnkYF`=sq?(XcZ0}eqb2#4fD4+#U~6kD zkss}&nf9#p4w${kxbKeBB@;oVlFb58p=0h;eC<1Pb4k4=)77^}WW(yWKCLCwMsMWU z3iEw6!10nPZ#^ugiZsld(A^y$d2ACi#x1U0d1{%0BWbZF7zKj(ehp8_E`P6ueN|h^ zzycM~6b_J-uI_>xzo}Q&(^aX5Subn`#;B(ru7{tx@Xq;jk35f(!0#Wm+m-(S2b?ut zc5%Nrs?Gau#8%FHe*ZE-=T%^<(<8aK#pSu64%-=OXB4D8N}>P`Ghh>^2j|uLQ8|UK z9zx!>mnW^%=~Hr*Of7jAMxz$z4@%`B#NkMaPc3y{?A~wjD3Q@ed^>Mzk`!Ldu_Gyv zZry0nzkFrY>d>Z=IeyXutb*sdA@+l!>u?rwYm~#2p$k}}p!rDwAkEa2V=K^uj4#&u z=!1wAB`;s3-tXTKEk1F-%X#-Bn0&$8h9ib@Xd1@HPN|#O*)uX!a%GGJm5o&h`O4W> z%$-6&Q!VuP|9;VheQ2WuOo#9<>Z6vkwMNo#4>2-P?mQ%%=CZi}U`6}NxE zYWxC%iI=gPBP|zGu0pJMNV<}vn2{A%U0000gLTgM!#Y^*X>g+ z;CJIgheT`na3l<{8mftwRV~QF7lIq>Uj=}U;vnZUu3~e`i9aw132_pwk z!9Unqw{7%PsbNbsHhR&wH#djTK5|sDIha|98PNMyby=ZQ@2o;1o&oI3P>=14+YIC! zSnd`-p-1`@%DiYB(toM?p>uKo6~$;GZM^64**ev(H`XJ+3aejPy8Fkp_;Q&voB`0p zq6Lb0T?i%@te*HS^`}jV$MM(vQWz#^`SZxS6H?UH9e6DbJ^i_S4}$-@*+rka-vWgC zr9@%*uj~U8GbK8CX7&Cjm*V;J^0!|v-aDC zLjM;Wd-;U&+RxYUS6N5crOn0|be#=hdHsvi@|?i|ky<)YE0)?iQ`^foP%9%oCgQoA zB>!oX10q`lRWi{Exz_s=vJ)9bW`X22kP6O%kj~pPmePqrtD?V4{?}sc1!erY)h0}4 zTJj{!$2Qzo@Gz%5-&tBI9rClY@l^2@l6k(o(&YxJm}#q@V3P3dGF-oKtIl>I?z$g+ z-Hq>rdzFw^L0R88;lk&;Q#9kARQKOQ6;2<^ARZD%*~!9;Nv!+?`0W{aI4Yl&Oe%9v z15(4&z8{Pq>Y)VtI@TwAzjQ%)tTnfu!=d3=u*97)#YC{j)|Wo>i%_cQcSHj1#}%%Z zl33RJKMSP5jba6RO-+f+qO5$LT!^$eUE2$+oMsw!&pb#j`OszN|JKk0uhW1XGf4949B=NQz+b&4-Jt=O=(Yg7Kwa8F6u;(b$n zPyOqaLy_yJp3bjT_pB=TQ*{mPni*%2IRndau#X4kNO~^~Tc72>2kpAFxFi!{()rFIZ=eo1tNAVI+u$CR|dD%Qw$4mnu6ifH4xn*S_Y#t zx8?CHY=PRmdo85#t&&gamBOu2dS)8?J5_~Z&%w;WOtTA704&&*XvE`BIQGq0*w4G* z(0KzW$lb>7h*eccg_HCCg84a3Q&XMk^!~bSCMF@GfI}hA6s2DS2 z7N_G)l|1~$|K7ytg0?Ap`>ovgG>Oh*VrYE#j+UZkzL)QZ8tR-&>9LxN5Rhl@DQ9$e zh^cV~3+vapS#?RV%WF^HR`qox%u;dE>R0|1YO}3n5{!9>p7l^VuZW%DWX(nX4^MM3 z&sz#Iyw^VUY>!e~zQy#(&tm51^`-&t00DG-R1%CRECL{-yYOka!8_p9R7UmYPQ=!b zFLdJ+rVl^Aa9@S25!TVjy=lANcJ(}lIK5E2++~BeFoLhRM&oGd{)u01dY!Z?3IFn`!GKJiQSKxSIAK+>4O-9Z z?%)_`Zd~w;4qh)33rGpz&-3O)Y2-Rm(>OngQ9-Mlx^fe(d7s@gwltC6fGdxE*~4?? z4;O|Aad}0$DwVWCxcYh^Kv12{@3X5M~@xuVI37J2`%0|r4pCI!; zu))=P7#Q{okIZo4|2iJlEEn==6uv)p39+M`zPekih06`|*`s|v=+R$5+Iyfi+*vdZ zcgp78UX|vm@YV|fS8=;KNvH4(3>25;|ALHqWp+;9xXW-@Ld5=+T+@h^8m$~$MBCV6 zMl7Zx-C_VSUys7*d9o{QMc0*|;!y0>meCPO>;+V#gE=aC3TixYrc8pfe}+;XxTMA< zI(kQ`N9ahx^}us22hmvZG|C_BQ`3HU5q*#g9RNk#A__z!-E~NnUmFWgiH*dlOo^A*zPu{#4p@pcZdwC!WYnVlMr#92MfqhT2mhRJ0>W z50@hdDoThufUpC59I#hkIOl*MSkdsfWIGgdP~y8WN-?gIg+YQ)YnJze z@Mj0b%n=lf`#g(lWaJgx4opvNH}V#hB|Xz2jM5u)w0zBzSJj>(!EAo=>=c{867paE zW-|5Lh_jK?zXH@A*r%_L_;Cgu3z1CF<7@72#My3D{llKd=TM?hvy~YX-rRnmPg5~1 ze%HM`!|0Ax-dif80j*X3>gh%3-HkIOp^RHCA-^ZfhCAZ4ReK%s6uBkl;@WkbwDH$4 zx`TFm!NB5d)wpw`mB5uREtJq(pf$8+_{Y~~ctOTpugm46)uob+kt{ZIM}yN1Qs`yR zblz>bg&`Vu>ho@>da6&59kv9D`Z-j?KKd1|j6oSx&N-QjaMwkC+2z_O{Mhi^f_c{{ zh7wM?ed{M>XH1V$=u~+5R*S{Xx)B=81U8HgN=Kz0P|83gT>pFp`O*B#2q62k6%3J< zrT`|!PfN9drwZ6JPYu`cl#h=)-3bI|>DsYT6xkMu`(;zAg(rQGjTaZM-p`8? zn`7LGxUq@OZndX!V{@hmQMbJ4JH);MVjvFkwV$&I=TRdQ(;2dL>uq6sWxYd$MWnn$r+CK)>aYv9N z-;cc4X{_66I8}0O1^>j0crwwdrDI=i&Kv@EZCoTR*aGI5!7lTN)|c(PV&;((w(~dr zZM|Fg7p6SySEJhG?Cj4zd-l3ol?&REM5Y0PeG+@T5WX1OTcxgSe)@bF1JB-mb%@&Yy!v=h4O#ES>jh-D&DF?^*bA80iGe-nsNsl#GiEufl z?&#7YQnrTC$4;+5RCrg@r*k6&66AtrzFh;Pr;qE;w)>0t_r7Txgo7GmcBjm;gA6f|wc5u`1 z^6-b|cju(u6Ppal*c2(bX*}~DvWE5ztue9D{yBttOp)(gu#7>vhEk$MZY;~h8xxlD z6YyKN{!QQ-WT=>(Ig3tCjBy@|=0_}w#;X{;Bj8LHT+VF7mkNIp9W%Qk{k z<6gg!x_B;yfBi3%+meV$7INuXD?3dJ8l)oyl%b%ol|U8jqUmk0blg%Izxu?##F1|% z`yj>7E$ZHP^j7HzNT9_o@Bd5oYfnfhu2MTfjEX#v{lsVaer4FVQ`?|<%xt7G)nU)5 z4kR{2Af+Y=!K6*rwp1<+@XzM`4VmXnHW7jsyG?bcxF2jj;j?y~lp3usF*h~{dC|S& zB+8Zsb_Kgg*>!s-Q+!MZ#Q?l<8`V|SDOtn==Y5lLbIP_gra`qJl;@z3Em8+FbJ20L zra;l4J~-kmbLu}f^4B4K^g7k(n~7~2(R|dAXnjXqX&Anq;WS)*aem8)ub0mtZVd1I z&RF61#O#2m(GkT7WwH7M4;j=HqkGc&a^~HRuS~8sy{fd1@bZbNw4Xc_x%W6Uu<9gm z4~>|*ANGJ_*uwU=FjjK<;%wVr&zE*J_p}traM7zZW2ZlL<>YGGH}k}ET66>Qsjwq$ zl^YWymwIrgpwTfcSB2OJ(`k4*kxrNA_QXZ8Fu|_Hg7|tgCXML6(BtQKNFHdC48}=^ zfuC_zNbj?GLxLr`I6c=%Lz%&Q=?{lm<8?#3VP$WdzwesfC#o(nv1Qk(eF_BvS3l%f zDOCgrK@QkBH;SzdO__OHrHER+wI|4a8)7<+7zv9bm#PY%aW&pwwo<80H&Tp|UbZT9qdmf+V2r+kj(~&_o2RM>H(@UriIt!mGk4p8L2DQYX zrY})Fbek-;7k>TpA%5>D5F4>2(AlCZAsGH!ko*o&OS*@23otnHJP@po)02u9>^WtZMMi#kr^xL2HA{n1V^E2u(m4^ek6x&*XYC!QC=G*gH9Mpx#Q#^Shww40`c+ z+gyz-?LTUqy7$WeHgy&do&b&NRpKj*1?N;xEvH+<%}y)D_gpCsge zd|zs1_ImDhm7Yn(8CR?bk!T%@qvMkzn?=X@4eyP&Jt`gsC?P~ z&avU=DG-85y3@;JS~cH$yIy}Iui<2l*aU>Dc?&|HZ2r`&X?)x`4tVCM&^Ue<{BQ(h z=;$V9V-jZY>|U&NozGZS)S7SVBP(gI(lTg%2|O$<{2r<N z8kKkQQ}l4ZsB>g`HM^^Y_d#)4C(?L7WTv8@Ggmgv&H>KY8^Rz=++cFLC6jkc&_iI_ z%Q;$lSZ74eaDkOPSTX|<08%R&)8DdNSEBLmVzQOA{v`+1;q%G*^O({L#v6b8=oXOG zxYx|PNnGmd;bb7_wW#U3gfS(~Dw`Ui|1s-*w>=+{aV8gS|7^qmK0R~!Ff#$W4Kp4|L?3-x~XQOJz)qZ8rjcz>ZlaOS!5Iz=rd_f z<-$(fP@*X^atT_ga6dMwiCovHc#>kcA_xdFi0zvLVCM~EKgsdb@Vq&&t@m1$_uYfH5R>9<9M$~PwaC{kA?+xu5o;hl2?N zOWc3^NA7`-7Ra1M<{xVs#$K6nu=x-mQG7q3YW<);sba*?>%jp9R&c~WmY+%zx%Kb_ zyO_b(u1d7Wjr|k)^NR@cb5S**QN!W~FTDBrbR<2Qc zxO$7in!5#I#bgIJ;dCS*Ytg%66zgDh8Z|ON3$`E1lVF}6t!>^%*3>WCo%~`n!N!&U zL2%e*Y&SKY+CeuaLPk%?kujt^)@DjNydKPdI*4@fBk1WMX*p`z^JREx$MRO}m=;|3 zq~7?}6tJUWv2}Y+KV^^{^u+4qm?=$WcwPEv$GcR{=hf7sKH`AB1CFLercRDnlGZ=6 zfCdkip6}bY$$2OJH?xBy zfMdCyaIjLi@$u7Fp5yl03$f|si#_t&;NAs(m88ix__M|7Sbc(UM9EXH+aSm^XwkimR0qq zzeNRknnek|(}mU-p#W=2ucQoO97dPkB|d_Zh;UCOjHYUZ)F-4S9z|NPAL`g^vDi5p z6BH|sj(4mEI=O#2Qaj`VT|Y2;g7)Wj)c8 z2sb~zx#7|K4pLYlH3RW1!z46drSIK69fAIEE1J?Z%lo<`-$kA|0hquMr2;8eWQi@C zayWH&@of|LG$Dh8jiC5XhRLb0SjvU6jPG(sy5Xwp7QMb@-_0^IRu+7qr8q8XVU{3s9Ie^FUo(9^NRPCuot z+OjN{z4B-R$s%IZ^@2$+oS+R!k!8(B%Hh!DBqm<|j>9<-{rWbj;YcH0^U-wA{R5ei zR%fS>r6a@$GQ|%v_p3I22)}dPD9UjFy*Lpz>BW8iWFwM&^=I9y+a_Ksg~w0iJIRGt ztNZ3ga>;~M7@VhrV{G5)hOhX{2g!3-X`;3^dia3JAW4RyO8XU0mfDew6>ENWxB7Ax zIAdx&lBB4}b1RttLVqyfN4x=W|zi+m9$y+YF6_2%un{sO+7i`S)^w{t}8dJ}HpM=%(!EqhY7(tp@ zW|^RbOHnlKxFc#PSImab$<3O?imp7l|?Nxx?}jCJ-< z)`U<)0ADF%(K#D0QQvBjUhRCpdRctOH^R1yJ7k_QyhAYplrMFHVqv@6)oUhKO zK^5=onW0EP1C&P(Hn(IxOfir9>u>5x>`Ys6}o%8a)S}whz@sKeaa%JVL?t_Q>&&Ar=D4ytdw$N+ZsBQBL@5cTc}{xcYtv zi*AzH8i48U0E>)zb#zC&rMo|_!Ai5i3 zSumf(`vt&Z{jKJMQ=TcXagU5Bq-1r_>N`<^)Tjz)lJL2Cua$f2{mD?(vf3e{@0^>_ zM4{OC+tdw4Rd7A2)f9ACx&nWOz|(iw{5gC^2B&Yb(Cr#|-}n8^ykbYD*j1e+#2kGd z2nUoKAXY4(@GLY|g7d{QgUnyW()PAS3nQ*|1FV|T@Esyf;Duxucj?ar2sYlo2hS&x5 z5uVcLjF>4qUN|p5&*RLyt(V`4X<8*&q7mlsRDnIR@$gYLGv$12|HIcgMQ0W*Ycw6( zwrx8d+v?c1ZJQmtW81cE{jqK5<~*J;&bZI}z4ofAujW)QsclzfpM@(ZfIKd;C@d-* z=|0DttsvysPbTtslNT5xgIGgY#P8Xtd!p$mV%z>xRvt57q7LZoyj%f zfzl)DkbvaR*<045z$IPv!vTG;Yd;1e!N6Tr!T$9crNoGp)Kx^Hyh!Qoy} zIZ6sR4J5ElEiUTXJcSd}$;{ozF;>i|DKWf0kya;27nwqH|JRU$W=o1O2pe%*QTxRh z?@y%boB7m$lzW{1G6Xg>11KC-p#cuhf>7qSl`#1&T%uQMnK>4*GpVmz-UEGp9!@sB zEVxfQ!0-%gLNGr>yBU)IeFI-Qb?8!Kfkw1t$_~7e`H0&<*XhgznY0LI=FP9Mn)nZO zsPJU^Z8@i7ZkfwPLponCqe0M>e96d_|!OC#Zwir{i`?r4xXs}EGI+Z?+UO~znE zM4ows@W>|_hnJq=mx$qFA(jP&r zbH+{IeR2q>M!r6fXP*=USU%&>Ob>)S^t_|cSon1bKRN^~SoBccv*u$#^kZ#rfU-j)KikjHJZzCR z=8i)MtDCayyOwCLXX#c%T1UNSW9 zM(m&N6FqDG#KyNTG?FcbY=b=Eo}$o}`%bf&La1x4?h_PK>;$Abn_1SLIwoW`*7l5C zpakru?msGxE5aoO@XYDg!ila+w+VRK{S2S-XsWcSa@cPd{ITs1Eb6cO?9jmcmqF!e zGAH541ed%T^IyCzf3eWS?o5pD(;1W%hjkRZ#vx0$!9$cMNwW1cX=y%4U+#Zh+6Am%9XXY z8Tz@Foz%XKV1M{(G!8P9ye#E@(jmStEzI4BYKJaYQXvI=$?TeipfuC8y?h; zkFq`ki!UE5#d%Y`IO}%VvE1Nj70)5k^jJ?be zFe{&HG_Cc`cMwynb6+xHZM^q=Sml&x;9Tx#FII@WElu|i2<+x=Brj zM|QwT*B9mAs3GmwhqBS;we@!7OKxxee`YdPd66$GF&XeHE7+?kHYkZQYPuHV`^Tr3=sl2Or2XRn5i7>`)s*pu-!9i>y*z@H46Sez z+7`XxY5E(OvzxF?HF8wArd=;QCtWSD3#9Bl@ZYq)_kL!YUB?e zYnSWbhF?ylDB!1nrfZ+l;^PGQbiGx4Zt&dHZ6;p&p^=3nO&=N{Et0Yz{pF{=uw=H& zSEjVn6ydfk!9KF{np_{kSfZec78P9C{z>bXu9@m7y)g+~xkt0ex`UuuM)zUarbk!; zvByn5NwllFQowoW#@Kww;omMSPEpm24A^_$l?0(bq*cLc(eh-bkDu9jIZ|*uG4Bcn z0UF&oUIW0|FW!g*94H}8N)V{Gqz`f=?3BQ>Y2ou9$9x^JQjIQ74r!>!8OYpL<+|>j z^sP_Kw72VD&2dHF(1vx-2hGoRO`W4?>&#bIxKiv_2Gwi0$K!**o`-VJ6)Qp-i2LDJ zA64FPo`QGs1Vi9FUe?*Ybyt*5Wpp&3=z1+YUdDr-n({qzsVLu1s?b?*K#_e-(7nTGlp6c(G@z z2%0qSP$CrNL7y@v+pJwPB8GkRhMGMsU~wf+^7>oc99#SAMGVv)4-;#@&j2OKeUG;| z>#c;ZA~e%WJ6z}s&FB?6hqu~nzE+e2V|5h#N$Op?hhF*?*V$X4HYrb}ctyX*6GHpEe=OjE@ealz%G6@+VSVhE#+ zZ0$j?_WR_k+-1+bVZ`TxV5CdfI`?-!ex6n{hDqKn2=V}5A{h9tv z5LZSL${*}wPw{i)*}?V_OAqHTU0fLE)+aSgE7e zy}?;=V^fElrk=Q8sm=j)7gwt{mZj}IYcL$HhgGG(kI@Qt1kp&@ClM_3WACaHTypk3Sqnw4LxUg)Y*jrm3pmY&g-?d`lw@6IZ{}UVVp|P+B4-U=)SfzfYVw z>*co5eBi20-9F8r$KDtCT};Y>y1Z)(6c^5B1{&tXqmjHabe%vMWiY#fGG)S#Dx2fm zi$?~?L)<7>hGrh$dFAq5Iqt(Gt3D`~2P-+;QK7n=y$;VG%U<lP$soTn+%dV2X_Za;Sp+&_D}GgZi^JLyR+Z$?2sG4IA}r-|iXd;6gpLI2koxwv zgMF1V__nvywE*WuoXaF%#Bkb7e!;c`48>SMmB^<2^@ay0Bd~PU9Vjw=% z?B(BiV6HN6*duq=$N@1(oXABs;}q*t7Dvh4Gpx#AsJ~IY*r0nZ?j#wv^@h={3gB~| z^U(oLIUurj2xrS%T5&m@=Q6Rei!Gf`-xJwB>&3TBIxW`kS}l&&1Bn>ZrDRecn9v|c z2iw)`-I;9n(XFuK-b|3N@0mw;_~)NySHvjb;{d>F3%Sj)fy-IEiOM_jW04WHuOdGV+n)@pH~Bs_ zh>4nDNw`5 z0vu+Udg;MTl{4QivgI>iC}unKi$VONF=C23!BdN&Q%c&2!d5$I+062|AID>08rI`L@y4$VYP zIjy?-cX3qqIw$}2vTI^UaUOpTG8{b4U-6VM1T~ByyKg|sKlkC;oOnhqC|*`lo95HD zyC(e8^i&Yc?@8ad+Cl%tOyvk|+xyNedVLod^|nvf$WQyySp7hgyoD2DzlK8;${2%p z4K^>k{xi+QZ|!KmfZEh1Z0a3lV46}FsI%^OH%j3LAadKEOE~T)N*tW~J{#j)4ZmIi z$DT2Hyf>ug63|B_sz-~hp!nElMMV}VmNRi2zEN-8>d4;+dL5Z`T3crlrSB^IaLLi5i^M=ZlPQ(#mEyeJU1fUL#0r@?S-F9uny(< zv*}pAi$xdb7u2YzHw2_4AiPb_#Zy4N90$CVoE)`J1mf$SI?Pw`x(TL#DzZyT8}4g2 zH__!>dr_}#`iAxuP^Uz40VdgKw*!;NBHn1xrEs~Dt-;cPtjv{S7=@nF?PmglqC|GR zYZMqN2!T38VB?C8*}1aS@BCc=$H`Z8cr73y0d$c(Ga;za5kPPG1FnSVR8*C{kpDIN zPJR&}?cUGIA*Zmg7_4l$x9f!YNG2)R8xD=&@#riQ*xv{*B{O^%r<>DRVzV+Cxypdh z7%n@@m<@vo#Vcm>xGi0HOG`%M;*Ivw&&;SELTq7rjS-Nj2Hz>v{WQs@S3b=Mx0P!T zdRp!!Nksc5xfTd@wda4pU(4%zXAuyjm>R@n&Qv-S+GW90BlF#n)cj>v&_Xcx2>YlE zR`xJnk)&emml|~?D%@@PvRQqzogt3ap8D(RyohobP@A<9afPe2>Tf!|GOlMjLwGM> z0ZMVAV3vh_bbDeN+1#}m%Y^ILZ93r{p$PE%`~t6)lw6J5sR!!0>!ycRaDhyhN5i-> z_SiJMyD&_UyM*2FX6(3MEN1>UUZjCeQlkKj*0)YRM5tI+p5M(vS!D+==69!e|LAfn z{eG>B43pkJM$*Qj@sbunTSv@mSGJAO&#?V|QD&9r4TKTHGFR_saFEz6L zB^#KqNNM}Bsz$t*a&%lNW~(YJys)~lB#3#R@4gNI4!#1l2c7LB`F4S?`@;T0GMGW!k)M%+&Gb*1xs3K2DGJEIV^y;kPw1OwHEchgsB&= zdb1QE{u8*Zx!=&60esZfeq9Mf4ez}?R$1LwU*pH}apCY~3LPQOXnD?EQmt-Vu&n}; zd46A|S8*&Iwz40?1_E!kJLL(MZO3RzB|2vmzVy2({yaM;lomfpXCI?^Tjbt876vZk z&aKv(OkFW4{d1ZSe#!c7n+DY4o%64;V62(1Vkf!5jRpg8 z1#v)sI$wlEOQp-yeo+v=gI|8exY>JI4Av7+#L0U08Sq>S(z`GNg5cnJwJa;LQ$EyH zHO%}0QwQ7ZbJIM_%$Q_JY)#XTa;fV}2y_g-i!RDV--3ZOzkY?s^LnBG`CSVWC>;V( z6xsEay!~~n@{&!Yue+->{|9m_=O?3G#t;b^&%hv>>h6iQO-&gr^ukcgvu@jRJbTYj zt|H;A45Eq=q72yjtgHMT+2uCE_%_behu%e!Y%TDS82Zd41ksmP}Oifmia)}n76Y&hG;_E z-E$a58NqlV=g=mCLKifR=n8R%zvM+f&|<%e`T12L-Hd=HOFl$b4U!)_DTuGgd{#a< zeRZD4@%4q8DBCI5PMKk8QiJX`E(>GE(4@yAnKCcNa`Mt^bF|G+X1Ha!6~)-fyKL!k z@*r|^;b_aJ-@BF=2Iuhvc*CCr+>6q=8nAeeN5$*QP#sUx9v*dhfSuWBI`i4`$Z2yj zU3_poC@~FYJ|ZYJ^^OtKh%T{&-M8${vfZORqTyqOMm}`70#y?MVMOcy_<^@A^#U8- z*5c_ixhRcBgzDZ_NT1=E_S^Uno~X`ou25bDK>ty#IrqAuSo zFOhgT7NhjhpxL&3e)uZnj01zrYdF@O)g&~7=WzX{2z07fGXo>|o(L%*X4<+jE|V;* zA|=NZmdf1(RlV5(>d7+b3^*6f(&Rbi1)-YCOjm9|Gwc&ny9#1PuJ^w`+x0&sz7zn0 zItoUi#>!qiQ$=!+aA4}L-yG|F>$lc?zAWdJSGKp}a>%Epc?UrSGE?P>*n*!KRRn7* zV2Ncr6b${f*?@hc2wH*(CPko0NASdQ4M7+QHiWYgcovyZ{7NKRS`$I`!{ zSaMmCrpHfy88ZF8a4HK&_6X>}vU{>36=}0n1jHrP-r1|Xxft{SI%WEuIlQlHjvZx; zZal2&*d8(Cyf2@#1*^A|d8Mt7-DEUI`T*Qzb+m?je<#{!fXrA+rX$G+YR$qQ8?*fl zvU9BWa(v#2oM=n9EXd^*IHO(khgMf&x^PmB=Uau?5Us0 zEOSrE*8Qo?DedNIX5+-gS$j5IWFFs+$j@0Ynd=fFK3Ty2U z9tfurjZp+RQSJ3!r+o(bWdZz)z5a!c$zZ2dr0LlYwn+wbEbgm_gp7NaEom|S!P9$N_qOQ18U^CU0%O(y9ZQ`hs`=J4CdHBR$;u;sa2LKsMaGUS{Vd2&=sV-=yUiQsDp znc6n7uOP7m?r&L}W?kQE94R1RimTjr{}`t`f;_s|8)S=D{10E}Lu`-^T1k;qQ+zgs zUKe$bvj*HVSYVu(@jgFJ*E$#3iI{Rgq^3*9@Mi z81gOm&f62a&{)snrbcm+ZF2ecUohJq3lGjd2?yXiO_qHRK8)9YW<9Wmzk?jorWBnn zZ`x^`o1@v=Q04`Qy+!Ab{6Kv)=Ko0vY;*L^`!Jw>am$%cD40WZp5CNmTb(< zzQR_5A3yu}@H^d%F}!(!bM9#-m29t+S#|Gm9}n8LM%>|@!S6U%kKQX2uXTC-;>IXZ z*kwt5=y_RZU4zf%s}8G52WxeF`hMv1d9M-A3BH6`pvCDBf#HIig?d@7Z+_wK92|P_ z{HOG1LM;>@Yb?0g!x=7}g-eMOVb2#-aL=!^1`X8~Td$D|Di!c#9A9<+ERiG!o7`c2 zDo4y5+bje>mrnF2o!;UrkYDz@=r;RsUZze3iF_w28_)`J_66@0ba z)PdTShftXcyJdh_WY>mDeS<8SAmES^i;|_ma!jM*D0p8$%%;Q%aXywWPY=qTM$+!a z!8LVUdci1qf=Fb5-B5$w?VBJZ>FLmd`<{gwSq)^2hLi71dpG%!@3g>C5|?Fl zyxRVY_kt+tBF*92*aHPnnN~RI==tEO9HipVNe{>gucxU-sv)*S>-%YtH?6W8@$y>& zA{kXFdfWZjS!PaWT^TxaveY0tnZvYk?B9t|I%ZU&i&~ij<`I{pZYSSu-x-3N?wR+_nR}m^XI{X2 zGj5VC6LEO|(zc0WXy`?mpX6U^Wy=T>E4^D~-el=dL}c5R=6Q2;HFkzDO+IpoIPr75 zA@)y}9ktDtQe?IQIodx0VK23HiIDpnKvd~AAKqm@>hSAI0S?HV5J zsGWOhc;0?+#Er=|uc*NI9Lf9!M{Q-4-cJeB$uXE#L_5{0va{$)Tt{$VtEZH*iUscE zXcR#eVuk#KC`wA4+K9vo(5hls_|2YQgCKbEn$jMj{3r`PGTV7&jMg?*94lE%F?Ag| zUm?9JzS1s36I`b$Ale0_&wx>rD7({kT9a9hSWK@PiRv4x8-X}>Eh<}n1;UHiK@cEt zvIZ1)i@FFs_o}l=!C+~Zl;7e1x4aD7YsYfF8ga(cLXd%1b-3||JiH+Mz|jhk%O^7uh;^?pBzUkML(9DT=+vst|DDdXLEvRLQoQOz zEsI;TX>|E&v|2{&B?yp8srZMZ@4vgu*8(JdOb;Kn0{+^ov^QEC5gJG+428k;EeJe; z`ly`|IJ{m(Ry73z<>>1Q%aFgfpCLdBZf9UYpDc?_C0e2*U54WYzyq4&qF>shn`Mz} zHks)hKzC(P_vG$J*P+UW5qGMD-S!`Rpn7?kXn=8Xiyz)(d<8k${lI2jevTA#STGo_ z-2fdqpO(j}G?u>!UW)LRwOr5pBLMgD$hdAV5pcAQxskVxA=lvh=i^tLQRv%=JoTwp zcZ@9kCe+AebnU@$InLI1uJQG!sJMJm4u^6?zc0Ku6!a}3s#>{CJ8_%YXCTD<_4ElSDI(KdE& zaYC$yb%reX!pK8u-zGnS)0^h@SfM_7ytIv{#TKJQiKK;zm3nWMGoYs0t2o1NX|#~M zzW~LB+%J93wKy~`kYL57{-;qbaU3oqBTL5hi-%s4X?pBkzywyW~sluAe*LYEaWkgaWURr(Q_+?x0@%o%yua9gk2e@3u~PwiS8z^s6{CA zC8?c;;BFMtyZn((Lw9261N@bt>agi}WrNwCt9+k7ek6b^78@TsjvcxIeO|AE-Z-o# z(arK|<7s<63oBL1n|vAYa(GWfndm%Pnr(>AbE95PLvQwcvh_NqK^1?NFuH`5g!oNR zmTDjt(nr8zcJbE)x;+hv5Kg{Wfnd!*r3MMv&%v>`o~H(J@Cf_a!!7<>B@%UL;(%gB zeDxAnt&;W^w+xdj-s@&i!H{LWqp$J)_^_5Wt{cLOil4=Rpoxv(RF1`gTcR zmM@RG=2xfO+4W0mrs#7$5w$z4x!27%k9pK53~eedMB7CBv*C;|fRO!8N+EY$?UJWY z@#V)wZ$a_q_uuEqOlvs!^I&ienICl95mswC^+gc_Khz*`{2s~LtP#K+Scocw1=qvUvE3w3Hs zhKd+}Ws8LkMfFQC;`@bQI@`y0>)iADv$6^Bn3wnQO(aQs^XEr(9=joQipK+Y?K zkLywj+Xl^9Y|#|GDKNg~GV{J~*>zf5BKBE&>UXP`SY2D;sJcPt$#P&6jPp)a`dvWQ z+Ic(n+X2Hp8A1D-Rl2YbNTH{>)vf{m-oE{lFJtdwQFUkJ%xV07`er*qcNr#Ak4gb< zODSmy18Ic77RvBY0QZ#Z(l%Ru>wxCiz(1{H?!w0SZ$@e4;vQ{g-kbYJ>E|}}yHoey zDrqs>`M;II(z#ofsrVmkzJG|yRg<6g#x$HlHANr{lC($+zk06XSnTd+1`E$^ar{Wx z$+Vm@bV$p+l;ZV$z=A|`rdMT&4S7SQy$VJc?n5prpyd_1J?T)HSLZX(B{PHQ#>kQK zR?w_7A%}YI!DOWD|3!#?(}^DZdICwDq1o-d3RIWZbG}BzjFmDy!WlFaxzHiEfjTZM zgPqup>mxCv1361A<>JpLa~{TNaRS5EF_K>XQE`>}wvrHY$8kg~%g1wC?W*YPWPW^;v`U@`?#Si z%Tn{~9F?@Z1F$^;gT~DXu>DXb{AO=R&cw)&ffHyH(iB@8H5A#8#0@Cvsg4)5gIqqx z=%BQ1`MfDjmbMB(F)Hs{h|L|@whZ9bo})SZxM64d`CD_^S4xe4#Dk}J#L zOm-NVe^P$wz1z3~%bPE(uGEGK>A^x4M^hF8T7}(=!u#i z>IGNOJECfhj?mo0=!~-vbM>>`M7oiW%EYi zurx)pm#$g1gt5`Eb`JN2h5syVMN_0kkqw?%h;HBiRwzLIU3j*kp`2GkoAY)eG?d3B^@gcOL#g;+>LTP zsM%gw((!|&jJQA#qB_DoCZ9`8foBR=#)e3p@8eF^2hI=YEqiv?f8h9I4bn7`TnLbV zkaYdkQG5IbfDkybXUJQU;A~o7c8b@MoBIdeb&zfVMt6p!7XHIO?*vFN4a|Wi(Sb2Y zlZ&d;9Gh0!q{%|_zx4ovdNG4?x29RA-L1=GsO)c)|7#E%QjU6+?>?s=>pZs3E-x&+ z95G_OYf4|N8O;_tnBypswfZE)MCl>!eJuiqKIQ@&6pSokv&D#0?@9g{Y5~wmzC84RN?{Kz0Af9 zbe?-EN5K|-{8?0mUIXgM< zL3)!t0EHX5yk$G?1!Bf+7Z^iGOa_r|D53iPnWv^iIC_l3Hgo8s8lCs`LHhe7qtLm^ z+~8M8Lo;Qm0x1?c3Ib8zN)JVKNlJO0TSlix*;MS7RwOJ4nz z_z3lO{?_qb@*lGe`!=Y>aF!;xzTOVYTA4l^FCqmMCOVw5BHp*$x16sBwxMFoaB!Fb zKW@BIVbCU1avuS;BE?Sm*I@Ct)eI*q)D zA=Hbd;elDDdgqY9G3j*3$Z>u6Fnzt8K9>y117QOcQ{zY`{qjV9z85@vPVc^35DC+qmsCdL-V5H@axrM}Pkp6I<_ zf_CN{-W!jMIjJ_Ed*r0LOABKiN-qnQCjwyVbe?QcGvQpF>PRZsO5YIm+=4-7M z74rp9U^8%j(%fb6R{ow78hv7UW661BL~RT>6b>Zrbts_%b+>{9zfp6FjA(?Cb8IHs zkoN~hv?^8ztK+!jwq(?X^#MZa9Op2SP6Y0m_M8WX`rWKotskx+N46i5q4;}NbKz_0 zPxE^lH7ZPJh?C6^8eluZx@T=`e{Zl@OXx369c^O)bI_0OHzkdF{I3~*dCgBEyd~jM zz3$KzJ|d2=N`H+qoj2;wa5r(23^cxJdJ3)%k$kTEL-(hZK*eO`Ggnw*CV?r6ZIGK^ z_9{GY8&M?HJ&O~#2c!riJkf@o@>4}H=R&}wy&r`>4j>K)4}F0I(Vjjnd>CeYoWZjZ z@U`Nr$3eU2%jv|A@V03QA?wF(P0|n?sT%^*7;R)YnH0V(*i2w{9BFW%Ho>3Ka+3ao zaU~tlYx2xMMokbKSN+I9s=;Q$H^T9Xlh`8@U>`22uQ#o0@!fau#x^{_*$M>1LtUp4 z)?b;!5=xU`z8_FMMpm%}O`zC;du?TrLw=qz@eya_5Pt9N-y&koNe7-}et=-m-rU6= zz>;c7Rks_E90vVH1An`1FzcTy0+JtA8uW~yb9h9mxYD34j+FkF|t_ob+&^Z5u#!Bosm&H{Dn{$H|pr2|~JD9NP zX&C{ttUUt+oT}%J*N-%QM;Mg*WbgaMGcg7Ld>n4RNi8^wENQ*t70KlyR>RI@Sb;jEsCR0UK2yODilYe7Y1(6 z4d0%q3gyh(i{B8$H3@4N(*A&j5X?9hpPZz$Tu&_wus`;3gJ}B8nHpUwztEvGu~QSa zXgS8?GdgP{>Z)cF`LogYi#;0&ehE&_tBxGJ$UeZ(m%>B8V(KPqbhC}0Vtjjup4^Jc zJZmA17|Sj!+N{<3v}6^i_&c?}u^CZInY=6Eow+}(j0$a5FgpdH^6;^f2~oSCZHfa3 z*ijq@kRY4$>YmX@IcItJhdg=(Fpm%dB_cPW3 z6hZ9G8~pdW)hFOKZSFen%{tu`H_BNZ>DX$SR{JO>`47OC_(>q90aPH*f;cEzxKG>L z1mAyj5zadksLD*MDO`IuIbt(~cMIMw1JdG_o|P;J;0y0ADR@4yal-l8Bx}@>i@sv6 z!b;%AAdC6gyS2Kt;~Sq+GlQnVzP+?Wa!%MaDb~co#^SFTiCmZNOyDU4C)7Nv>O~%b z_N)_6wG=HshhbfTh!mr1VeY~VZa&g`RnP#S^^a=j*?P(Ue9_G)QSU4uPf-FgyM}e0 zgq}WriELU6)1!4|2zcJ9SHvhEf#<#%N&BpMwXA8cm`iblSqs$YiG1}Yl1t9XuwI(}?l$UWfs{o}J+cjdUd=Dp9yPmnOHh9R1ngnaoeeQc-LDiOpKF*(*U zoeKlkgoP^PPaLENSiPs?+B~ldG%scI?R@!A%svHNO%c++B5@B%5NS+vNq8Tmx};Oy zPCJXfDTLtWcB*)2ORIcUV)SP}LRJ&g959q98!=IZsE`kh1qYtOmGqvH-=9e-!B#PL zUx8$e&zb_VGASDe{ij`{WD4Tnb_Qvy$2F${aZ&x(UROOb$E|*APK4~wtO#p5D3jl& zUIDKZw)h7m)l-`0-84oOFs}-&nC;FDyjhYOwXkt4!ZDky`49BQUGXzFc`Wsd`|xWD z0G{bTKJ|Lymgh~2Ogo?ao)q>0cyX{Rm89j{CrUnM+_2`kohM#rC8@b#;*4jTajMbk zc*HTrd0;*{pWEaUfO9T(|0fO3z!euBn5u@G;IoB!C)o6JxqnPpesdzhz~;uI7!Y|r z9U46}4{5qn77k|n&}fm-sd1ahr3pX_p-@|P>tz$cCd z7|mZwYFgm8v$akn8Po`&1PWLlL=<9^?qfoz`{GahVpssT%Yd-UMKY(g1MMopB; zfqfs5yp5xJ?zv}0O6D@{bz#C{GUSk8IgN3GqF@FxtVyd|jL1?zdp@?w08#U?Z!25p zKU@m=TG|7rDYNGcA65oLW}oYq34=i2;SV{l!DRAok_okU%8lN(jE$I)$Y~KH0+fFC z#1K@+oVIgE_TR$u%a^Z?BqA^F$5`~PXb;k1ERm_PIjoYFC1LXTX+`|ni*xN8 zt;}x{AxzNcH#&zUDG}yTPWRltEn6wjw<08>(AT8S}j0@Zn*a6R0seNQUVQZxBy*7%1|>N0$`xT1(lN&!x-wa?>)r zp_vm@DB??*YY?_C(SIS+2F=sH>RZO}*{mmgn+{&2p>o4F&bS7Xddd2{8n`*YXAfZ0 zVj)|<;(@prI+LEU3Cy7wgRUR*v3QNfo4@SQw|@Uxk(k5T=aa9yOTaP&WXrBzFpVMP z`Dmb0MFn`*Uu6a(w9Tk?!>VKXKTBOE3og~PPkeMHsGw&|yaZ&RllVo~^5r?{D_%lV3N4q4H}>ssxH9sUb; zHMQ{!C&`ybe{fMYD!j?t6hToE(h;9Kuxs(KIKQ?3OU^neKrM*UFH-?8b-YHZyl0XL|`8&EM)YN{!&!hKcFDkqQ&I`x_XzeA$=4 za^=S<{HQz%uatde`&zO@UR#&dl~8v;!8B4;@@cG>MJCPjI`gtb2Obmkuaq?ZfU)Yw z6mKPtSC~GBwPE3Qo|vzKq2^V@9?E(O$sxaa4^D&xd558{iESz*>%fWRU7o76M_tp1n99Ao8VOIsSP08gdp&YFqRLi*{R`&qt+fzKQ?y<8=aaEAcmSj&}8u- zj3))QG~7(O~@2Nk^|h;EfCFV0`S3USu?^9xdB_;2a}EB z;a!BruP=tx4Q;7`0J*FvMsXh1z$H~-OTMEqRGpxl%#cu&ohpXzpJWz zZqB3A62^CRkI{u{!JkYaZHa-%bWMTux}9pT9->VoIRj&d?J8*rND0VjU2d-Mdw^H^ zY&9Retv$i zo`TYNS0w-So%>)U;4`LWGp3$+I$ATuf6)U^5d1`=lo7FL@80p}7oR|yu51o;(Y0HM z0N1W5z=`?{Q%T2L%HR=>(fm74#sbNfPn5=C3~FOg>r=DDVdFb7XG&S3;`sOz;2-wQNM9<$n3jibWA zWeMsm%(Hg2kPm%jU3>+S9GjK&4_?k_memguSuB@j64j*S<6?b5zDg|Dp1dM#TraZ+ znQ6W#Ck_Mh1qv&}U`u9ugn8#I#oyqf-%R+6r338ZI!l5wV)|I<`wi6N%H zvD-RweW-Rs2ItdF--xJ^FI1wiP<9hthPMBcgN)jHgUTbm>WVueml3h;rH*%g-@>4) zzZAQpmCcvhuS+8$G`x|Snxk+2HJ4MkId%s6aooo%Ngf_pCwk;(IGx_A629Y-r!w?q zy?+nxPO0)m=l$7KY$jbw5goS5jCm*eHy6^TAH?3NbOSg@K;Ju@VVMxpD8A(eB!Xp| zMn2OwFJNP!%(GnD#*X{wMX&dz3kvwb0*)?!kBY$fCXD5lHi0PP{i=5B4GQXe*`snc zq^@4$80{{ovioIt*n#WS)fRs}d>olDAw%h9l6xz%XF@5RzfSIk36gb;=09>%8#$9S zk<9Yz=-L~eLKMD4bRKHq_ykyH(3WX1^HQ$8E6%0^eE(q0cDvQCFGAk6MrHi)@gKCw z4F+V4PCpPWF;%TJJKN98;byznk!g@{isT9S$a`HiIEAyJ&M<}hcHdBi0gho$)bvQv#5JffS zkP;kL$8wR{$o|6l))UxLUD3UY+11fz)N_FqDa4*KPGpi&0=5-!lPam&ov_bZB66Uj zg)Wf!@h3xd&cA171mcXC*V&_Nc5Uo5ge3&;Dk5a3$Rh4CqO&2u&Pp>p@_fGX$o2k) zU+HEhX|zkZ50dd-9%YM4Q;9A-q?DB9+vDZ=>=B3;3=Vy0>|Zr{a;-$Is#vcQry|*} zEowgbUV4bGS-mr)*4VA5IUXJ`(u3D4b)KAN;*oG>7rwOEe5)=*wXcORFAR&D>~|US zP8X{Nk#@Iw@b*2x1gmf7@OBdkV3TaV^%H1VpFk=dCS;9Y@KbjXsEKnu?_IXTYz0#+ z?8Hl&xK6uA)NT`E^#PtT8rR;Uz7D=r`u8@E>J}67tCp@f^N!WGk11=?tlmKH-93{C zhe9NkHta5A>W?=N%EAokyu((m!oI8kH_|YFfYnPJN*QF8F(mYvKu`OTkhIc_b5|@a zq-mAl8?RSn5;9CmZYuW>a`_FKsY+KqdekfTj9}1jiLRW$#m|4gm#gI=-vmUd-hsY% zrW|4uReU8
      s$ARQ@I2e_Sj0T?PbA2+e5C^Wf6(9UDmrs$3f>f|%_4Ux_lyfyCI zQrb|B^5j)EoiYMByo>j#!8Kq+Q$p9Y7sXGvk!K%#uKjT#lzBdi6j46&NBNR-n$2yK z!?*7a9vxUE)~b;;is0n-FVlK?KJYzpUU#QC&(;m#+hCR%&~R^iECq?GpRdLR!S~hspo~);DEJjAR<@$XQS4~;^Fj8p;#=;9uVkHif7Of=NR7q?F-G( zN!I9xIZcPuHE&X1BmD|#{C2dDSUPY&ckYR<_G7ku;7O*4w!lA}u)JP+x2#b|e{*S91UgDK!V_7W=Y{9(I4X(cfTB)I_rDe1Gq zbfw?K!}7T1OcLh6w|Irwu^smrbIxNbc@;t?bkZrMKwk} zSJ+uy0{_{538k)*74W$P%lAYRJ@6!^5^RU+K?RKW`7t5iibeem@!wtSDhpWxP-F8=J71a2gqy{(NF&WjAI2|GL=0v>(6@ zfMD35@W31qvCz1dixkx&!4?Wjile~adCZ(tELx%6p;0PTBFx`8<_cWUkov~&li=Eq z=UU+Tk?Z+Y_t-NhDD3$=ik^SP#}jHG?9b3SF0Z>jxu#MkleaFe&&+$O;_#MN>VH(? zks&wT?T~^cU1ZA!ycdTI#Y3_6yvdHUDA1x+BGpR1vE5amWrN4#Dr6k;)jLS*LQuW2 z9Y|=pS;izFzQu$?9~Z7^&R0#Xsj2;?$!%YKz2{u$=&L;naX@qxzb{_C*X0|4L{-O` zeYt|Xd$0sHYyPFZ8O#{r?WsQBJegjEfe~E1XoY1Kkh3l4vshWF06jG}d3*H)^LEBt zz*)5LeeTO4sMBpTL}py$3Yq`oTKMIQ$MM3M8Nheda{LvQAmyD!M2Jz_L{0N3GCj}k zVF1C3%0iu{YdtC|W#C%^(erDWofwr$(C-LY-kHr`4zjf zch@7Hl&xq#KN?uuknbnPx4#@HZU`*bs#&-K6TG0_7+D6{6=ljPk9h z8A}LUdmdF17jc|Obpa>KwW)v%7}Q`+M3q0b{?mRI?+|qh8}Y~`)SXYwSG}}&-uRi? z-+y_1YY(R|b_-Z|KD!PfR;cXU(M2-QZ+bZMel!080f}O7W2Zd5wSgtk z_wH-n$)(lRBe{nShz)pvHUR54;m#pjGf1~F#;ZxRAz7(7YJ08>(U4`poBY=B53i?oE{tB99mJ})(_wD!-5^&Gn_H2YfOW-%75u+RDZ5LHz z(wS#@U~mheP~EkHP}IOkXxbkGIj&T-X(2UrgKikXEafTr86Dkvyk9F7{+1t+Sd#%L zblo$hHTe>2l2bI2FnISpTjc>=Dc^!gr8%!@_;7;7BH&%Xp~}n-*hZ4@bmJm#GG*3X zw%o-C&*GHKuv_qQKt1#5Lo#TbOp5|GCu1WjBcqr8qHs3@9L%|RLx+oMrIP5pU|_OJ zHA;S|EI!G7zrLcJh$@+7cN!&+r3wkQRl^d?AguT>A{dUkl65M65jYUgtz&YK^R>Q3 zkhYK|k$hL#J+R#o)qKjNMn0)7#=;S}E~1lp@%e=xH$G#LJ?{@4>N!f{lDF=z-x!p$ zUL-6jlv^mb{2SaNpx`3ORcqAsz!W8Y`%-x+MmHD%HCpK@=g)iwb7+{_Hu>5^pF_05 zG3}^4-gpn=Y&TBbiNHHOw!jg#DJRtUGrA5+l%vlyuP)ivjfM4@0BV8w3?8Ww&U#L`pm zlJ|a*=`9Jrr29NqdVu;MV>QwN7!YoI_LSuo+*|11E@;_r;SyU2w9`6|{%iN~=D6C} zM9+bM9cI-VNY6jPUP<+_;AQs>2Afwc3g;jeI97FOz*Rz(O|abj_o}r&Z3{Kwga*(T zlgm12VU~3>I$2#5tGP}tt=#>(O)IVg5ENcd{76CWzS@rVW>ely5*me6Dulo{SqLQP zPep6zsy~2Ej#`=q10`OuF20V+>K&hEwxY+eI+8kem5;%VuP_QJgr&Iw@G`QK^43wd z6W~WpE%+9SxAdhz`!|5f`TtfG=^7W5q?J!ZStAANF+04ixX)-D|J~9bPY!&ab2vjj&NH|(iMJog z|5%!#vP@ssZ?u@~o=mq`y@OK&({e9wg1y+JfY?;t_{8@tqqu_&Jw3`kya|Cim{Ihp zx5Y(aGL76y(h*(h#j1Bdp31n4gTxR9_1tM}vfDZ_l^o1FDk@7f3gvtmM?uoy)j)!<2*CUq>WsL!o@>=e*Aaqs2AR;TZuSHYfBm&@{x9jQz%I}RSw^GHR?v-M&T{^XBt7vR!*anMvWAeN{#fwm?c^V z!&XqcVw{vdNz^n8&rs)w{F4>Q(jfH)rKYo>k1r+r(8_uC9d`0k0Ih{`woyl`@JrSq z2Wsa1<5OXiUr=egu4x8euXYYKgN6VwkA4!H{k|u91p`-~F<}@&_}2`B zT2d{&hf#l}hl5kPjlFkIAKQ^+bpY#TIIm3F3#NU&AXQ8+@aT_jlgh25Ot zVrnzo@OMy*5SfQ?W-UfcR$$3i4-iC&v4%c+agg!fUv>UNSyB|Bl#lYm&7^iD3#=!f zWS!0qLo5jL{Ch@T<~FWAxG4s0*CvDHLU+S)SUOWoW zo{&|w;~8jSRB!cjwGHT8S2{qtPXV<1>#;aBg15g!AY1MOEk>D2r^K%L_NXUz7b&w9A>qLmg-lI=nQ-GE=0sMylYw@TYls%`&AMGDab)!<%G~ zhEgGm`ZsL^W>X$KGnBbc*D+eiSIvb$x_IjTxw04^h-ZWxtSe3D0yBsXj2bgq&*bGq zOF07px8kpdteU+NPrfBFiy={_+S@6J!EBL@fjztOv!?oo@I`lO(~8p(wW?Ut+}w(x zKz?PL(T8^pcC`SN5mlOmgmL+aCdH#I@Cjt1f!bcedUv50yXyUq!q^w}$5AfAkQ@-Q z7GN>Mg!oiPf-}|tt|1;c%`LU(=r=54q|K}qlErjB7tdhVi6-UyZx>&~e`v>G_Cjb%>)earBFhsQaE?7No2uVm%kzSahzKh;Np7I%NrGL%2nYTfA zW4j&-Npy3+RR@A_g=UDz}=kSRFw~oz9Ey_1o8I@QZO-! ziFy=*N*1+PM+gXG@@sndJisj;r~PUmG4oPD2Vj8a2&NgkGK|1L3u0h5jmf-lNU**U z!*@pc7j?dft^Mv3pbu!~`?^m+ewtfvD_mQQ#f-H}Y~EX|Irv?NYwai366rN}iZAxC@fG(`p9Dw6{v-u4ap4*-E!Md%vX=Y`#J8sG;*U;OPef-7xsx4m6kYCdKmBco znGZy-SS@qKNvF|7)8cR*?9b;OO}%K69x8#btRgi*fcGN~_qNvB^O@tpVd3nfh+~n3 zy`Ur@c}Cyv41q%HM3ZW`KzF%&eK-GTD%RYT zjKYa+o4z>D>x(JHq{`@uku8%-aI_}o{-b8WTbmkK1HpWE-{WYF22i4ih(8u{K+?^b{R*-8iRU493IY7K8a0 zG3(rB-Q*p$kc96}LkaA;WdEYc_>DgOv=2pc$r|iLoX98{=ix#Bg@WBfAC4`z{eCUP znDgLtJWYc&|2f`F{nO8ve8l?wa0$M5uC^(i23_FIean@JK))!xlwX18Wj1_~>WVP% zI#DMhmUYxtY%VkH%!>N`c{5_*SaOJiXT3+Z0W0C_*NF-tbf6ub-HKy4?4TF)X&s_C z8uF3|Dc?pbNEi~WJ=l|sRgwr&g7fwSyvJQs*1w!HqI$b%2=(6Z_?`)io_hcCVd+b+ z0I)kKO^}UVGHpHP>KzZXH1ti-hpt|SFlzC%@l`v@c|8YSYDl}HtLssWV=zjRI(M;B zF?-lI#gKu7%Lt2AAu1(@sq4$2u*x1wgH#PNrN<7JNO`i9fl4Wf1V~_31pT*@ldKKxZJT!oqo7`ft5d0b&JO8eS%f64oVtGEE2n5L$lG{l5>-576+>{_;n z`J(oVAhElJ@|NR&jY1W=jeShsGs)=;=& zdvMn0>EJHCvOaIw4-EsT;yv=PyS~1YCPEAjYKFn1X)w`wS@H4M{)H~;X~S+G*Jz@N zY0x$zWa==LC=bY+MKEX8i?r%h^$*deaZU=P-@klpR0;-|p}@@J+##~+r$k`(;&FBT zYu-WM9L{QAAbMSJq}mRcPK;ld=&lLU)4T9#%BURTI(9caowV0}Z+aBqbCplOCDO#YS!L&9i=yhWhEgt?zH_L5a~e=jfWC!ol%#@{$dY1q2*7d?#%$9-%b{i zdlhu!xdOkb_JnG{bsH^W!Lh(?ntcev|MAm5S2cVt@!B`2_JLM>YBzp1vp*0vRi^fgpQx0 zIdh^HGRu{Ar+O+=a#%1AUU;EOZH@~{J*(iN_Q6B3~Kqa4>m0T4<5*C zQ`MD2xY*(3w^iAr(j1ts*mD9G;)n@y@<8;Da($aH^jRD*DTfWJ%l@@i2w<)ilD$kv zQK91-qH5uBUptl!$TI0X#1qRSryi(c76Y!{AoR*=g6!&AQlP+fX5RM+nkMT8I)7ZE zhWA2^**=}7FQ()!{7A|3CgQE@bFoW35oHm(>7@J>d`o4$N|?P5+zJEU>uD&mtGy}& ztwd2K?ah^gmhe)OV4o=(vn5n$FXP6~`Ai==RmXI@^O;NT5^vveFOrup=|&rc?^xw#e3a&@ooWDFWtIy} zpFO-|>w+Ks`zV2_7>FT42nVf41&bx}9+=kwfo~fm|oQf0ce8|6l z&V#{%Y121AIT$A^6{snc6LgMkV&vgs9WB9M>DcUUF%*ohF)#-wtup_j4#iSSSaw4p z4n_m2@$G`QI(N|yR-3S{+NQS(QCWJhn<*+ha6KqzP?d4G-FL((Vh0MfTS>xr1WCp` zy?>hk1yl+4I;RxKTG6*+ldk(<`){>1Z}MF^!Q!{*w-?@3D`6g`K72BGw@-KN5!-ys z6db3SQ zWo!t6f2Q&rF}Sf|%`wcNw-kx=hC*t4yYki&WkY}P8mcR<4&-CUdJ&GBDtlub@diL{ z)TKQzjyzS>P-YVpjjZT81rq0@d_q|`R%Z>R_YZ3K;y3v61^xm8Se-DSARl`3zQw|b z@oj))d2`c&)H*(`ouI7(vsJe$PBvx`K0EnFv!$o?gtpS7(6J2uR%YM!6aJA1vHV&G z(;e!9cgtAozRR5<4W-E!kmzy$BvZ&_z+JfW^ty7VDx(O$EVN#Z3VyIemDZe=JxQf2 zTY_~k?S1Tfeai|C-=-Dm8V0d`5$Z&a6xJ;yNixt-wg34Dop?9bAX1gQNtD@~Tdfgd zNGBp_&2}Ec=bl{r3zaT#@ujF%-NYOF#Cw*@K6A?pPL! zuUETh*+)lOimUlI^DTQuB_6lLEKjhR${ztNAR%$ZIzw5cS>%)auHJu~Rk3zO2vupG z&)po*B}@iEMK5WNHd!}lDPz88)X)^t=-Ohs`hGVQ2ZNP8;z}UBk$c2crrGXt+Ga~I zd=eUSgcyfo?>7Q(z?%qM!ie;JLOygogHlJ^Hj|rBuDA2^&>XmQU1I7ZdYcc#p{`+f zqz?WL@yIf8yE1Ce{tfAyV*8oOTw;vGGE~1GmVz}E>f_) zHVR}l2{f~JlhiKb3-uZUqa9kM=`PK#;GhP6`Lyp=f(U>0-3o`_sqo7gnh8rpG&pH; z|2Y@El{LK+WByi!JgsTo)Ig)3^irS^9Cg-~PIx78W|#^XhZv#VrAP%sdN93^B=zo> z5#jmZ1$@gEunLN%>vs-hF|^!{{hMK1FSEQuU58wyK^I3Z{p>J{Un}WT?#;T@&oF^* zr|aGudVNc9J|jIwL1%jV(?h4D8P+#eIRN}qo4ZFZ>3p)%E3;z$VE_H{d2J#xCGu2zR)Wg z1pJZhb&MLrlE&=uq9Uf=i^&D^n8&|(G@?qJF3)_qSjnL@2&%Q!Lz-=sTs3t`1zmP zXK^;3&>}hQpuC=vPzoPwz5_Xd48~~NCt~SLD`wZ0Xov+LQ<-p=2W|9nQl-!iVl$17 zDIi2LSuP#FVKw&xnkh-1VG3j38bm;D(-w)9{|^aLv(DN_?fJYU@#aI+?98_Kwtcf7*HdedegJ)vY(#6D+&yLbH<)LhfE% zZNPc>Hs77xQ7P3Uj69zV$opn{P6s9y+luGc%)C~8Z#GaFC3;yI4gM+~J0t7qfO9(^ z^LrP2y%P=d#LDq@(F`}RFeM*_2RlQ`7**X9KLw!3+JXGY?!cmB@wYtxIVP6sN z)o8T0bMVzYlGI|enYjB46`hT0h`*+14gL@n*6XSiiw=9f+H*V+2|jQe6d2)c3_mvA zf0aO3{Unf5q3gW&Cnun5v~M-mBhi9y`j8?@CK=mCUuz?F(xz)4^Z#kTB7rhJ5-!a> zOo55&E0X0}5m(F5n&cT$(NPTFlv>;uZ^12MPiHoJht(cEKM<^P&gCfE<~Jw+vNLr2_w32ob(>rDC148*zv z2XGOmemvhAw`!oOAuHzjUI~$63{2vq+)8^XE4@7lx<{A9Yd>SM3qR5IRt45>a z!@#t>6TEO#YS-xnGXN$Y#sUsJ*|LXu=-WIJmvF)<5b^2-gF1X3L(}r!55ce}k->Fh zJ1YxIu0*`tVS0N{^Ic>l=8vXHgJbWe*!sP70=7+sAUbZ$^tL;5xpfl=#;8dUnLVii zPUdQ6BmHGzNq4l`!6bNoY8&b^yCEQVtOMC*tmZESyY!mC4oVbQ?lAZG)Q_e=6K)U% zrn7sr%6H7CtZtr3aHjm0~S~4SEm&Li43f*z0V*IS}H+{Xy(n}0#X={N<>i6 zr&`5wZjOZJ<#9hC=Zy*z*8v2Hl9ePMw^E~u`MFrpQ@R(T`a>n`*~^fGGgaq0bOS$F zBOHJALsj2pZB5$irw2)s7R6IdYqaijQ(=XHX9_Dy&gFVDZZWt5a0tuXzX^wYV8{7A zyvF)yudiVIve3pDbsg-0@rC%`fWmW(i7Ph|5RjkJ{{$3%{NjuphRkd%42G;GrtGZj zh8*lh21aZK94w|r%%(<0KkgGZjH0+l|j6Dp7UR3VK^Mwk$-Ms_}o#uUqs2_~yHh`;1)7!S+^u%)q?bN)Oa$<>SJ&r-HDX*o6ufQdt!WT1^RshBx>4-&Dt~ zPj)k9oJruKN)U&?m7&EBxv{&WC?@r>92~D2Xl5!d50zqdP~`pK$oaR3U9Uj_{@%3B=+fNk)ujySTwiC2d83`b$IXI1 zS73c|yPs{9L`hR5DAD{WrF0Kv#9W6x@4AeLI6cAx#`{}gasEs+NCOz% zF$9s;ER9OvpY*T4*2c1#XgHc~cEzw^p7{{XqBGeTn%W&+Z=-hN8%xko-OfLiRra2p zMQO<+tWl4{3Dy%fYG3CySi8(j=i90Em-e9nwc}_+_WG?H;w`yxSk4~rqmWis{SSALr z_w!JSjQce%+G+!v5+kd2e3%B8kmtxMH)WivWwWy35)*|X1&B(HHp+!kP zY@a+2+NnF!))|UzJ|&ebRN|UHFkN%GH(z+yiR*BEpXa_NcGqY>w(@8fXrp!n7kQ-r z#YZi>Ki#L0ystzo+rFFo1Z2^xHm}+YIh$}fKt0Gbme>9|rQGwpKC@`KhHQ$KJa1Ta zm`aZWVFg`j^fTs7eQn5m@BO>RA&e(YslROV?!?;KQDBIy-1PA&Y#42_=+W_W^9AzD z5y~hVFhG*`0(h+MGPd7rur4+_H|r2UyuQls5eKiConRB=%}A8LnfDQS@Gn%~CxauJ zK8@G(T7j_O3NKDVw-yj%+cP8Ew;|Agrimq9-ePsjq|h24E0IVmjqz+=byqL^4t*!T zpbrgz%||5PalyG#;VfdY%d$`K}~n|Bcz?JFItS#&-r&{>g`YWK+m&L+Y$18LDL~U=fvPW zZ@Z*h{%Bx!L8UzJ$j~$wWit|vBhe?UA%cRdVOPDBh>!=4yHP>|*>`?H0l+a<;ERLg zyv))&RmL^2uEY2>t`zRo`GU*Y*f8eDmJ z$EU%R-|pgRHX3-VGfiQwPHs5b_qt1+7`aQor((uWj9ERT|3`zAIoMM)_WnD%b~3)G z4!E1ZBF5SmaYp=<@~gs(4-x}181c@5T)8it=FDokd)n1e01%T*(m%T0usj59jzusy z{3l8d=K%hMu!N7AIxVhk@^_xMs{Wg`%+6c<^1OiGf6Ubw;Z5&-I z(z3FsB!h@(3zo5?vajg*hpBr3d9SzhCOrP)=Rym&_(mLt4#yu{i|T#NIVBFfc*nE1 zoTe`Z`6YU^`gve>tw-^mnRkwn7zf2;MaKNYz?xB6RsXMJCJ*^V4DVPL*K#X7*Vmiy#!ckf&hok^t&8O4xrHTJeS5ylZb#U^q$+u9177 z4svGwMRtquuWQd3?+$fWgRc4gAUsQDuHWA+T%}m5C6Cn3c?FlhDwL!PKRK0h19$1*0KKIVIslJavYLXegEW zZfu#f(($a7yP(=<`!w@sD@lq~$7<)>1w{4E+uq+rzm#alpUtG?C6k6T7a{dS_Xaye z5@w=Z+!L}8Gg8Qo7Ep6I-A|uXx~9)@LH)Upo`TNA{I}^{zDevz1}sMiymsZhCHyy- zF_sdJXLJ#d@H81Ytys1M0AI%Vnay49mT*hEhW$HSmbRd=J7 zfE31?d1->CWyonLzUmRAChO1DkEwpek_p^U@blLI-T35fkT#qwM&BBjR&yYsxP?=7 zdsCiZWp5_HHPx1JcT?$+43C&dAfQBP-6n>zfH%wTb0LC6`YvFor~_Sb(@e*!=0?>%3LJ8J0cW7%mrhtp2ELe}!vxs>(0cWEx9lhPP$F-2YoD z@7~z3@Z?E2Y$N+VT3*}DIpUU+9V$l=0Mr3bGn9Dc98Ha{^Wr_skE3L+m^$Iz+7Ypj zfOUTG8u~vMlM9C2noTuUbReBw_2IFw-4)ctNi7-OTS?1S4ma}Vc`H2?Z5OjU`YJ5r zWqi2>t3)U1KLL`%pW5AlLRmy8i4{6ScCU-cjZgmJ$Yg}5YM%jF-dv*jkl>-fmTK7`<_=pr^ z#H0|9n#yG}lucStQFev{tW)pW0Gmh75Z_}+Ga2M% z<+ae{BZ^Ro2SqAxM=d+|KVpqym;XZiuRCf5gnP;RRV=%TorBI1^@S(bLh_K#Q`t5V2#(-VjRkc6gh{*k|fE&nhTZ;zkE?~ zi*idiSz{p2!B|Qj*;JF#{pt#9lI&gMJeNlV9pzBGN9}d3^h@gZ$9J<{gB+K=xM(RP z4&VB)D`I+xIsL2RDfzvN_$Pqlz#`APe}F_S>m;MS#Fts7ZHEuG=zA?exgaqk()9pcKFZvhcz z73;%E(BYI?n9ZJmD~$dC+Mg$D=!iieC2S|6SlA}6JILz6O8@rfqUd(^Nf6Bq``%I4 zAT*8c_W%M_Ig0`Pl(v2SDS)*TrZUc+YjJ4oV%W8f`$o%gf-7+pUfVFO@LT8<{&y}$ zRXjSo;|Qvy+M{>cIMC^*Wm;=fgfmZdx2<``3~TmAbxJC}lQs2Y;e%G8UUGoZPtwVr zq}?8HV^AnrY1%~%xkS8^sQHGWjx_4B`u;g>g&+Wc;NQOb`?=mhLP(AbE_cFE`)ISG zXD|o}7rP#RkcguPe3i&6vZ9yO*i<}Bl6EVjnZ(moa?0G!;W{9hLfDcxW!mrYgE+rP z1%qa^4a+{Gpg{9kiiQ1~?x?uybjrtoRMKSECJFDKKIbqN$6UFjJ#|fldQhneER@C9 zvSBez$Ov2V!@gHkJ^+V{@@Q#O%jC$nulT_lk_5&(Le7RCB3uF7));ud9mH1Ytxotc zV~b{(U?od~L0s?|ch;}E)x$-=^n1Q$y9Z^S;~q)y+d<&TP#_c%Vpj1Lv#M;whx_fS zE4DW>OD!16(FcM+kA%=Blo9(mSo>$T$(%Im#9v7-Q_tW3Nwf^23BBTRyVYu}gQ%_4 z?2&np&>o25%7c1~ry+x}Y`8O|sYPQR%ffQ0^I&ZlcH8gYI~lO8lAB@=8Nzfo?f80o z6>EmCMXWHnd6$I3q>aCJ1C~M?@Ger;0}Dp7vv>IIK-Wi9HaRusZ7)rq(wRe#SK}s@ zE;FE^o|7+O-Ot^QlvuH5`7i{_103sJBMq-bXGq|vDp5)U*1R*@>zS0g99TQre+d9o zz$Zto;CQN|vuHez54=)9Z((W_6oPPcC@71X!kcl;^&2IBHWXy^@1SluJmhNp{h$5x zVr)FSdJ=`QZD$lHL_afCn>zsUz`xPQgS-X}rx~J4-9w?;y_$Zay=NoxRtCi9i*Xm$ z6QLyn^<6g%E&Ux_pE2$NlfJPzHYMZmyz8z69(digk*l(#n~-YldPJn`@1Hlr;?{Ec z>-Oy==dv49tM#6rblG~7fNJhR%+`jI!I{GZo27)|lH2Eddq-Z;oo9=VI-NH^pw?T< z<`^>>e9BIf>cAwq)cBk}mnDMwd5XbGkA1*!6;#vjSx^|62Oe!_;3oFIG=8Mr9ag}O zw0=(7$#DpYbw8V_#s8U+diG}X*8Bbq@QHQkk5$P+rnZ4tu{X>4+P0z8Du{J^bFyrW ziyx|@{&Tm}=4)V>J1x^{0p3Ktb7NIrDIU{Nk%N ztvy+h;Uwao|eIhgsdMa0R%(|WIzILOBZFHkd`+LG%Sda)v0GE z{pRMw@1*^@@wxq(%QL(AnWTOTnVcn6eVN-E%bxPat|7)(*o|<#OwKLbS5pB3=E zzzr4d$QSE)DWxm0xC<`W)KA4;V$S# zS-#}zFv-qjq%+cNSaC``b7-eLRf<*E-dIZ}Ycx7fsn(ZOek&yVmU#A~JpZXQ^jQP$ zYwwNyk@Hwh@O{#Ot(!~bu1+X3>t;bc16USx4^R2w8>9zLx*z(gm5e_QIDQ&ZMC|Hc zO>S$`cr~O_OR`L&+U@1-C-k^z zA4*b>p-q=MnasYd6EOxRq(1YXdt9x1h^4!MnN7=q7*d5*_Y{7UZe6ZFysry}Uf|g` z__gm~vt~d7tUGNGLj_?T2fHwoCdIh+!k)g65`Py8?y@uE4`-l1mHjOF`pyT)^%k7> z2F+8F;KI#oeCp^wTgmQzlx85qpkU9^qQK?f2ZW|JxL^J^WPMYKoOr7M-DdKSV(IT`3OPQ3OU~~$utb1&@vjDFX?m7 zT+z{ng}Iv2J&c~sITJ-xOsC}y-_Mf*A7GG|Lz;6!8M95ke0R7fnJbtlTZu$+NymtI zzrXc%+O9^96tGPU+3-41ql=)!lP@%#Z=Rwj^s@;pRd2$CS$8b&`>E<&CAO(fU!DpJ zuxCuSAc9%rb-TJ@;5q+$i`5I_`qZZT5Qc@>sNjTmt!YB}(}Zd5vWi%Egf1QHQ(hqDDN|xJuI4hWTRJ( zcC(Ne$imxHo*6=wDGcsZ$IA8~Gq%dSb|Ae`;LEyB*4kNYnvtr{h%*6b1vzjaY)>pL z6)r)KXxx1Ev^LlB!Z2BN-o3!fr|JUUMC^}BB029u7pjGySlxzwn;*mFS!jrVjFqYI z@M<$!hEwrny?Guwe4w@JEJu_C?@!*ZdsVH)!VhcdX2*{WYoF8VqUoL*EYowHGc=1z z{r$j-K6{p7;`0fHw&=!l{2@a>{aLpdqds5KFT~w+h2>!Z7Tu8!u+B=jTm?0l9ooCA z!l$Z=q4ReE?E@S7W=`uwuu9x1jVz46f}OOz+HP3;26n5biKsi!-xiL1<92?{@jU5k zgtk?r#BqU9YJNyu?U^_lNMsF=ZI3EHn-cbv!NktC1z-n)kL2+ZsEvR*T1GF3#yB9x+Nrvm8d@dgwbQsMLbo`^Vk+CON(4yH#I@N8ZOfIC~wSRDp^dPAO zKX0DyK%z{58FRqPtqF{xWLM7@4u=LKuL)Le0qGLaiXt=>Z`RMZT=E0=qJQ4a-sL=^ z0yF=X{ljefqD`7(prPeODECcuqKLugz&bb!!+XqpKZyhzz;i7xd$)~w0(7cQ+m^Se z{Z)o<;a$^>R22STV)kW0Az=2Ih4V_z@r?%m$h>pe`55{$fVd=Z-0h<2|MUKx`fMhk z^kfdhF$lVMEA?OwRMf@_=iH-tJ@FzSvzY$FJX~9a3{E(slGe5p#4wy>P{ix~a0?F; z@jaMt@}J1t9YW{B!83b$FjMCZ2$Z%Mn3+}N(`SQVt+4^Iub!*E2RQ>oh&)6@7vPf|GfEQMm4^Y9YaZi270+WmuKeq`9ChMLEI2=Ot`jgw zNP}~RAX*PN66Up?K0a^6ff5~`p<~Tz-3}G3)cTDhZybuk)7lfKZSS4%3Tw$L1HEIy zAYr;$shrNk6%~?%l*#9@5^jLZRjAV@ab~X=%CJ?uPdu@~wGo zlJ7};9CKzPC_Zk6EIi<{$!PYZCG5|ToRi9$1i>okLH$OkSoE0~Ar;$l&4G!!h4x^W z+&jpJ;B9vjl_n+V^FAH!K1Rt+E>$N@hmlW&Uy>^6Bf^CA7E|X@uoZh^vVf7r$Oud` z6fuC56$_w1iS0CaE6|hpoH>Ortp8OZ3C2Ze_?a&VeMDT9_Vybt)fGBYF1xrHmr#Bocz3fXjkqH zO}QUb%Ck#5>i#1iI}J;^Y^$W@<-z$k)akEHHdO7G4P{hrwt_s*(q*H!d|rY47N5uC zU4r~ks<*qH=o`jMmix`HIThi_&WcSMCRSuNC(!_4t3U-S_SfLYoGoEqa0LYp`YXP5 zdxv6F-dcjr1a2$SC&NEl0=`mSkzOrB`RzK{v6QL9;`-@Xm94#=1-ks^86Z02Fb3HL znsoR5r{`(OYXVpUAoR;@V8m&Nqb%L*#hoe%|rzjuQXm^sJ zi&3fL_|8WKchVLx#Tr}N%S>l8DwUsnXr%9G+p^qwKQCuAZPf4`0dRqpT?Rdrv)j}L zDdc%x1|m(q9)x@VWJXY!i+@RP^ik2!yl$l9UpSJ`>Qbni4>8uAF1nOrX>V|T+(HH~ z`CMc-ef_8H%A#F?J{de98pdWf^VoVJ>Gy;zX&u0qYiKyrjSrNXdDNWcMlsmhpZc3W zFf&ieWh_3Th>-8Y#A1J(k>6<{2^) zW`?}LV!sMjz|OMgo4&G7h}2DJ%c=)E4YBn}9`t_NuWm8}IZ3z{x~eJe8Dgga1w4L? z@qUAY72ju$b4e{<*6(ENEoWH!r1LOKncnor>QI)vjF~y_tTxk_%Jl9}Zy1yGj7QHE zirUdbE^caZyfiDUt|_qBRbWAO;^-bLCyjKX(rEXl&QMg>zkW?Th^5yUp$gXPw0iyCfShvLJ8|xOfpY8)s4bX)b zLJ_j7C@Vi+xGxa*Jj>@Dw0BoTiATUBGwGcHDFo5X!M9bGeM*}Od1M<;bV*{?j|up`Rjb!$y-%}qwK}hIb)v>Yg=}FV7?WeIs@!HM46SPZIaCvzaKR1D2pm3nz(J!h5oG*Cx$8`Z zpSpeWUq0+3Z$W+pT|Dh5|;LlzU()t|5t%` zR^>B7;fLD#SFDtgzPhK)2Fv58^K#HEJhc0uFs~S>UXTIASOTloX=_|h^a&*Yl$1ZN zb+gjt4oNTG3uz{HMpvMQ2V!TcyzXzX3r{L1n<_nHzIT^IM4Oisdj;`cM>~(CZ3_`p z{~9VX)<16q|IKPx3e>-7;{E6sRR1TdVZgy|Y{0_8$ovCka4<15v;2pzbL`F}(6)7K z+v(Ug-`KX(v27W0GU4C3 zeIo50xKwcn0pI7}?%y5HpZA&59@yGD91M_dQTG2ZXPbNZg?b3=H~fxqNCWoGK&F z1)r=+J@OOB5*F13VO;}#`NS9JI2w&jG$0fo$B)VvYHI)$Y_KgYr`q{Gsc@}sjPgsZQMuVNo=2XJ7NppN7ECU5!#+4?27H3N|WLR53OtrThGMV8Z`5vB1c(({gJD4K@GHe zp8W=$UhC*WgnUt$`kSCXZ6;k$;P8|2(TnC4&qqDfL-UcZYU-=U5`=NIo1bN28a>w>(pvFG@Y|ydw7&m{{MLCpsVNu_pwx7v!a`eyG@z z1bl&P8Q+;-F!)NEp&pY*od>z{MVub5*oj%HOI;>-&a#KCT09#BD}*L}He9EhLbrMA zO2s7zC!cyt1FkhW&gI#x|6EC|+j=?q0}!Hq6(ApzKXFCJfL05-1EmsCz)ha{VyTF$p$2nF5;RIO6p|8{HNU0o`_>&OFg<_$ zK0#;r755R$c&rj;qH;aaCzYkcG2I1es6?)b>2*ya`K7wALDr5lce~d`@-^LN+?$^s z>$B;4sdEP2h=@f=r?ZCGNt3cLk}y9 zia9t`8cyM%_XAJMEnYbtWlt~Si)=aO&u!1$MuP;d-4cs-M9q;q)Qp=7aq%1EHu#T* z#jKHWsRTBho?{tgA|32zkV1)>}FizvGg!{N7+m6o3qSn5EK%n?HF z9|$>T@z>Vxj^!OOrE2E?GpJtWqD~t4Hlna^Mkt#8XP$g;uT-!S^n9O6!^_#Eo0nj6r@mm9QYcc>m4T_xsdpy- zDs3;bm4WftdhggC-Ozn`=Z>mvxt!=kRB`o5^sF3D`V%M@BB~6tFqrR$nZtIYSXo;N%@8TpR>${>ftgW@3=(K3}gz(Iyhan)q%Z zzde-MxKmc*6X#EzkuiDZV~!iwCtgRX5E|JKM3Ek;iu8-WZO zy90Mx9>@8RC*Ty=?@P6D8c`Ng=e6&m7en^Y(l96CNCJK0UQGlu`?U4E%;DPmvGx2N zM;g*)#Ew#B03aV+Y@(1>-}IH|Pxs8TYt6Y{oqAXJ+$fWFdRHKaDsn#3bx)EVKO{i& zmD(YaiV(mFF5Gj!4VU6bBEqX6Cy#ig=#Ikz;?I z)srBa`htC_wic0#sjy3Z`yl?jS@Gleyc6WUF^6E8qdPbip#ruTcs}ovp<*kOKMO=^ zI2S-&yOtvjwS&WmV_m9r(ggWw2u&x58;b@#i0{$c7v^R5u_LD(Z3h3Az8AmH2f6ju z=virEY@C8(xx?cm>XgXOggx!rewqJ?Rc|(tCnujzX*giQ;JR`=Xx(StcYwR)2kFuu zmxff;+SsNz=9u5o%68tEmak2q-nj(U)GFS5OwM}t?eU{r**MSHvJn0Xh0CulW>#N9 zG6Nr6(^FyOJ3lP1R@`06b)_vV>LdJXNmD|@_Gjz9j^@&;#xm}DLX>m3o=i7!_#k>; z$zpQw(tdIU(}_uJWwmS2lo}r|S)~?56JPe31%^Ae5A6^i&ev@hF#L+UJHSt(I^3dy zGFL12V0njh@_A=HWFy|yif|(aO?Zf+xe$Dthlt2R??9-A8C>EKWdL+(RVW3Uhrv~I zV>uRvlrJno+D4Mo^UlVd)Z!2{aBI-L{NqFb^E+tTft>y-CmBHXrTZqOcM^@4M}7l_ zVPJ&Y3UqKQSTgz;H(rf{YR|QeSQ9$${p-^fT=YC>mN@PbgD#H11O($qO55SIxnV{M zE1scn8<=-=i!)sQ@SS)5%f^vFfZKVLw22yYdr#Wmg9^Hn3Lj8I&7*gV+Nq0BLj zM#94%8a`R>vx32hKx*I{+2^c#?c3WZ{ zy3Y@ygpelBz))^pI?F7}$iCd=$r?~+Xt&QrwCK)KP`myaTM zn-QY+?ADu*)@>*4=y~G-5efS|Hqab(G9NK4lFO8E)m0PRH@>N5zp68(j#XaZ$JS$g zF;W@ptgIt0e*CC;3@b{w)RQn-IWLBFxHMC(NRBbnO{~>RWPZH0L{+Eyo$|S!<(_}n zaQP3@$0`D#TwRF? zFDqsgwL|8+G)6mrk*1uBu zyinp+g4Wg;%8z@9z+4~gAYLbAv*w?dn(vD?zBs{vk7={Vr_+V~yIn2pJvE5AzF)^y zMLsl~pTQ=Eqm<6pIc3?6I%wlWQrmy4f$S0{k{+qze3=|Z-=0Dw8ek6h@jU+^r|cXJLyxTKAYlN_cI zjRuIL;63eZ_0`@cC|Ck`56DI?s~CTl$-RFDGT$s=qP`jPX*gvU$fMs)KXwqcW=(*5 zu5j#CgLvPX{1xQ1%x>M2T^6EhkH%V^r2HdSD6=O~9Qw&5`1|0}eM|gcAnzFi&8GBE zv7$1aX;9LpkJI=BctPM!=b=u{>GM-UiB(8mLrXx&)eT7`rnJnHuK)LU5!aeoxyp@~ z`*h(+E%~d2BxZ~CxUSS_`wKeZu5a-u(@gh(=?S#3GAl`UOqLZ?o)G7^P>x@j78bdM zpj57+mnqW>|2NLp^3u*87DCWDeiUTy;hMR<{`2s<)~i|dAXy19b?M_lL$tZpN6Xy_ zTcps^y!L9!M5?`Z%i8>Adx`#zskBoE%l)gdjjn`QYER7DWd-sAudjxfiR5(+x(@SY z$7qQu{;6+`KwZ^#ksl`$)FeD1B_+v(&QcFCL8k9G0^W>DK zSY&p3I7%fE9#n=9k_^_yIOcc+<^#GbR747RGH=;0?HoLqqZ+0nd! z4BK&GJ@xa2S@XqC*#%UY?0R9U4Uz|yaUa>wL#%u-<*l3~Mi8#-8}6EvS5E3(!Ce1g zvJ$go53HpQ>znIy{WW5c4}dod63$O+C25*OPJ%YAAr*PJ>*9kQ$ilty0eo2zihRbL z6v@@jdcCcvxES9h#G&923(Y}OW+CF)hAsJBe}4E~&P$^S#mbPkKd-R=Iv(CNq4>kF z?9mn+j_0v*evDX*t$0pY6_^^8M;{6)Yw1hMGL3*MD#6b0CQa(HV7q@@xKe+YKFMeF zfx$rcF|Rf9&zXUMEk;)o8@W~#Pv1homd1(T&!=}642@*lN;??FBF6ewO3fI{ia4u6xEDH)x?8n-hk-q-&$vmap~{vdP;Yya4df)&YKc% zR}5dL)|se;{5{|D4Q~iSI-oaY=*6GyYA62N-jno?(c@UU8&-GStT(BYG522j<=$Fx z`V#wyj)BXN&#O^4zAscVu3tni>UZOL-6SR)tA5V+OzolIZtIdy9i?JfKZl!B8e!iA z^&7#9pMDt5tS)dm14fUy_$FTOt%HuBNH857)PIzy9DddN#-RpFa*r~Z%*#^8N^`=GdW zcXwM3$!DR*2JrQ^Ufa|d8VYJY{wZeJNfE7OxO)#fu*~-H9b_<0p(jB+qj#X zNqIrmf*ajrP;=Tar9WE2>@b46k3XKF@APzHu3|LJ`^=;k-YKpksqIN*IlgeKA4BW* z+j(TqBHnFpvMVyFLoo!i3MI}aDrMtsZhQ5~ioy71J?&j5L4rD-wb@EvmuTFjhelvu;Qi|{c zW`m2slCKtXje!<8y=J$ic)93W_(I{WHm4FteP2rbU4@!bn|DfD%`_1<8)%DAado>s z2n{1wmEFbDoqK>#S3dl+GMeLswNkX5%e3bbl9f-(1y`flw{=j)(MUR_fq(tvih(uZ2oEEPk^|H{H(>=Op`6T-JdE%Y4=Qd z5d{P**Q_w(H0>F>rGs=4Ql7h*If3_oMgLgdD=N9J_6f9>!A+QsM4Dk0OqZJFIA^d(IBZhQ5K*?YK&*{{+Sj)M41i z5WmB}Zy54Ga_R_(oX09=bcr1_V9$8ZC2gwtpsy2q3B8G7ZjEJV1mzuTYQp9w=uGZ* zlNVSB+%IYl{sP?jSP;WMOB5QTWSHv~Ff*!5|5jYp7OT1SHoG$Ve*G#V?qYXmA0G|x z9L*XEn^`yRDnrYvia_&tZ1R;p`a}J5AeEqAw{HW|i4DY>9re%)dWA}4w9WB93reJ= zNuf+DEMNPqRVb>Nq-X?}`P%va>W^9Zs+we(^X`5Jx&7gTBy{@RH5#sp@6F_QyU_c6 zm7N?oRv;p!zhI88rdBmJdb`(JU;ThkR}X>GVVJU)6B8L#F=r%yGROWDHVy~<*JEQN zQ$LlA#Rvq14G$NP|LFNAHPYMLy=(998Ms?d?&H*g`ZqV=sw|nBvD~jrn-;G_qGC_l zQU@--``XoL(QRKH>8*$5o#^}HYoAk#$L=t^<;hKXAluKE;65-TDLyXxDul%`y#8u+ zSKxk97zWWp8y6n7Rz5tNfI`*W%h#i&v2A=fWK2ESj=KbC(u~7t?!?(ThOzhA$$ouL z94sqd^ai?1GfIC$w*AToc>5aeG0a%A0fwTJELEWR_=)09tB~kk_DsrFnZG!Sz}sxRC^oA=vVwx!b9mjjDL^chPfsB1RzLT^xvU#KdEh+ z&ARD|ox!Oy#t65S>cTy+z&-W_UY3eTK9W|Ti*s5RF<%jEA};$U{3*SNQaAt)_QmI4 z{c;{M$I39#7iiWwMZ`u8=aA-RiVE(yC{D)-Vj69I$)(`tT8*-W^U-|N-y!V5G3nvR zhneWGx1w326%Bw9&&cn*?!HVC8@rfVv_+{>B+e?cv$7UOe#RFpiy8LaJL8*BRt&-d zG)DTM@4?owzGoJfJSEOio)^OjW~m8(9IjTD0@!Mfm#xjneN5yZFo!zq1!$-BOl5&V*i+Y7F9L0!D<=xZ+K!{% zks`~0*nJ~kKfWx|q##h(9CeK}x!hQcZgbe;m%&^89r-RrWX&%Ov zf(2^+s$(UJmjMQtmh-4eLFD%L;`Ba3_7@@()K;_VqP=8fJkCL0zI+0S27Old_@2v2o)LfQAS|-w35B!9Q@0GvrvUc~|l^?VS6E^Lv+~~MMXPi81yaj^4*#8(fd;?5Lj9 zUVU>Mz&va18s5$iW`)@{b-3mpQFsiGXlZPS{A7mV zf&BJ?km5gecAMS#sx9@}~`Ol1}WOiWHSoFy!zJr~?3C&I>#V9}hlgIK8Q zTslq4;kRQp3yo^RKEr{k_VDEjnyxeLa%}}Kc+=$Ma^G@QK|=pzZHZl~h#~My310gY z*i7J096Wyq|H8lx(NiN?BZZ&xc$t=wcXNi0<+CCCgd>-CL~dEj4#=GQNVuB3YQLDT zU=vWF35@}dgg-N)3YfA}!afUo+^z+)=uYxkNyQH8W?_zSQF-yDY;4&uFoAdXVcR6& zkH-4QQ={ajsu0~5nJ=WW*7p%&J&-nc!(G4I0!_DjguS&mvTE-KpaxCd88cXs~CQ2Lys0npcUu79f^gf{A$TRu~k)HzKL=0(mw9fJ*6 zPga`UV%*|d?r_~yiQ+l6xsp+2qo7x(cX)26p^%%^3g8ZfE7#}3vs?h&w5APls@&1; zx=YR4tgekF&p6Wr$^4)mM(i>8k%{$hRAI`iud@eq< zsH*pp=e}NXM@>+aq{cc34hL5&?`WEp9Pe%-aW>Ig;T8e|;=ox63sk7AC;DNs^ldmm zwTn@nB&#=v*9)NuN2=_aZJe`CW4_q7)mL2HbOtlFuu!RFaN3TdgB`Tma7K29i|5|&e=E5cvf0UxP(NGu z82?epWo0#HV`ArGHDP5oG2r--Y#CXZ8QBd?nT?rPjab-C84bCZ*cnZj|J&C6V5$c% z>KEbgF|E&Ww~&U0v~AvmtP`%&$K38n6_T{5Q_y4@^WQPmC^)X}?L6!@J6tCpDq6F| z0-3Ai>+PP`9-l8iKEmnY^Uqga{+wY*pqFp5%v)}6L(^-SH{RU9`s&?0RkalHO)A+y z5o?`M9(aR?*(USay_KzJNhzWV79|y+`@eG`()(TA=aAfG1-DLA_nnIt@ki9E){d`g zejfZ4Dhm2TIZj1xMe19SSy;pc^-K?Rj5ypUdHkL!ctP*!l{qF0G>CEAzNeiVrOo`~ zk;iSGbh#(X)GnNEZbw>03Cf!~;Eh*LcM*(TP?)e!heO_^v$Ls z{vJI%#|`wN#JTMPS7UD@VbqJvt{va{^9Y5e=Q@TZG!wZgudhU8QdEEF%u5iJRXmx{ zW5~72OVMswX#UoZBCl4Et?ohB?;T_nC2DXX4$x)GOm zxPXy*;=34nFw#}l?2TLfaYq)>Co+%WI|X~mWqTIW+S9=eb@D(*vaEq^Ey#7a z#sGTQuid2#dJ6qMl%r5#{{rkGlww5cc`Kt}gDXJzw*y_Yx)+-_s$Wl-Y|LEr1_{u3 zR}_XOzUS{RJFa6TRg=&9tkc<&ID%+(p0d^kv`v6@E6N)TT{u)?Zbr*MUa>X&g5Gyp zc(?=)1N}Q&lgmJp6tSPBT)vSPB_f@JymJ{Z*h9YPf6Ya^7k+2%7Ht@TdlvlVO@(oY z+s~LaSPAcJ`0FMr$6irZ{QzPlTbZl`mA)05&8YoJ1-7F(-1!ISZ;GXh<@ucLZIcEU zdxaTA5G_~X&Q*FFWPjzAok{xMhN|%>=8*Q0tqMR}6SU5@8%%{9v*b`3WrZ1f;$`0} z5(jZ8%n;xF?u&@eP|S%PtX_mSvabWRXz!a1PM29{+od2ZR1Oo3W;u(m>*rdkXa*6( zz>cHSMnC4N*$m1=h9$PUQCVSp7og%`9iPW3@!P!e#7g5VcYYN@7^r z{lXP`>esG^VWSjz8M*t-q*3?(;Yw7jO-ad`Or+^k-3dKyc9c$9_6*vD#>Fd>9bb=d z@0}KJf1*f@7v=Bn7(JDN%oRlOVy4~xgG!jE;*I`-UX1%W=ixySaF%%99fVyX*%_-Z z2#!UG(D1@+$`bqLP3IbUR}}9F4_jlI^8|_@ux-8_CX4hcoH|SQcdd7bM@8^Ed}O)l zm$fTQBrqkK=O*N$XHYhRmXrx(UyDG|z{_H8Knp4k92m%S9W0uneqEM8`cfiBeLFy1 zc@rN^#QBFl;qd4>RNzj}`7 zXfY>fJ$;cDSls!iF&W}>#Z?T97_B;>SuYE#JG);F?63ecaj>>fx~Bc%w0m#fl`WcK z7{5%HApMx=hR?5~H-WE^;c3s^XZC5-rHBGs(h|*%sDcs2j0Wk+Tydf3C}ydQP{MY7 zcG%3{lv%q1K$(lJ&_95*oCK3e>G!{yn}keE?z`jwZfAuiEPqvK*{(OJO2meB+eH~k zELbzROoOIvtsy~z^;f?_M9b_o{Z@Mt2Lol{!kaH8nec!8@n^gL21xEt6~!Li?4IcT zXb5F-H&u3Po)7CVBo*wSNO|;LqU!2$Jxk0Q?GJqPMHZ?-j6_$fd-FH3r?AT<4sDG!DJTqe$Uvf>vI@ zxPgq=t{*ev#Soyr*_z&KFjPKC+hTZ9AHL>B zmPGvu(MHwAyboA;sSG8kSmnC>`Hp&Y<01cKD&3AuQ=83 z7Nqs>eZ{?h%D;yXQ8@Tn;tnI?oaLq%QZ>n`h=#v^nIu5;abj+)=hRg`RSdiXp+B0( zD1#HePMM;b&}6I58OxpjkU(6Ykx*Ufe>ZLW2lfHiC3w}E(Ia1M63ZfA-y=>cPPi{0 zaf*$)4Q!eYHAKO-bRMsTe^!@#c_Mbl8M}^F^wGNq4cU)8&2uj%Scq8=S2@=_ESxRV z0vY62O?ddye6_mHRX{j(H-YQw9-FoWyyav3h7@zY0}7BGWE1RhwUIyC?y4HilbM(O z1x^+~_u4@}5Yj-S^2B?UJ^X%gra3RM>J zReV}&y>*kC7&I2ocp8y_!#a)e2bKQG`-l9fw}tjyJi@N_-i2DS`7p8Dn6Mu1fZWt< zzWdZ_?J21t!7m9N?#Z+?@`ID$DP6Q0ql~^ZPIV0)){iu*qo~P^&kH~5-NfUpTa%jo z2KwJq)}Jx-dv-`5AOWQRIAw9NvKlaPaQuI=5&KW}k_i(ht1%PT58#M})xeaAlgpIF zkjuo_)a1WTS;=0!sGfwwC0VQ~Tv%;nadGP-E|YA_p7G~1YMjFk&@3$$Nn@Xh`}KK~ zVZ>!2lWZx0@Fq;ltS$SaI(qc5Z3Or*)7#T;-xtr{@1LGM{M&xMK>xl0P<3b=ObsP* zY>4h0H;0`)4x&c0H|mxlN0+iHapgzAUgPd-dAxZor+EmjE@Bo~|Gux%S}A)FG+Lk@ zStWees;KeXQ0FlZv80VF?``fMNWu_D?oDTIZdHQ^$y zTv9;kb6YX}{8Q#-zt!k5QySN#Am=30`969e&2Wh7u;qafVDtZP743Ft7=ifA8y z$e*isfwk&2j>T^ZQ!on~oE3B5M*gV))+59f{G)JVY0g8a_G@J8Kg;>JmJCV0GcA5d zJ|~&>_X%=2cAgXv$Y^xzFKRPB5n!-a40$XQ=|W`ALE5td^ri2K1M5BxMdbfxQPTR6 zo%OF$>*X7ihY<28J`1uqql4^2y-9`Zgiu`ccTJDmFTpp5O%m~e4@^rVh>!;fwT~9# z>guw3LW8ls?@u!xo#xS51fSW&>GXbF7D##1LKP9ucAX>fGJGkBD@=2Gm_9(mTN+2p z;~RFeU2f&;&VOOIlY73zi!XxpQey}FjxUOXnav|bBlk)wh$D-vNj$9!dQwQrBXUx7 zpg(xg z7wCs;@=OLmlu16wB|@0DMBV6|IpvW)FCqVI)rjC0-C@F8$Z4mo>TxHvCR&}QLw6kZ zp7_MXaX!%pk)L#5l72^=LCSD*h<`6}Whk630C1l7>D4JeMt@ZqPqnw-ougj?1%5=$ zsb%-sb-l!zQ>4r6_jZB=-0z$v0ktNs(M7vrZHbkHBr4vlHxw35mV9(_mcmeO#M7d? z2t-LGB6y4io8z~xj%QH%6N5C;I>S9L7FW;D86OHWc~TV)moKsJ`uxRas!3xoJJxcy zsvPzUi6cWlPB`3jQkw(ZcTdB30!pv6q>P=^aIm%Glbs4^=(`M^anNb$WK}6!%a`cc z`f4EeR{7<6<Fc$~%SLBF!}C;TQeu!0MoTn^-0kF0#n$P)6&<9RHj# z6LJLIw}KzKE_Q(j3?YsKlgNhj8HC~eL2hdsO*#0ooUIq&L)9&{#?)BpFb_ilJcPd^ z_0J=S;r*36|0MS+fK@()0KJfiMnfSq7zgXpZ=S)LGmVg&%I*l`fk4nO;9Ssj1cvuH zbc1`S*X2+H-%N>B=9IWv;hi0etclTnjz8jeEN#(}GuxwxD_-Y><`8_w_v<{-DOLi- z6c*0hOVSKIsKt7VovStDZQG8k*j^zz>Go}b5E8`qa!UAv@lVN4g9LYxf+=c4BTv2CBcp0(uh&7 zM+ppct|9`07S+JDW_Dvr1K^EbS_@y8kLZotlQ$`(>R#bY$!p< zDX$eU4eV126^U$H{JUK4scvkv6J|>|`~E%Z#a10nU}!ZfhB;M%G@kc~!PJuDo4jZvn$ePWFqJ9=OWTJil9vqz$m4&b0l=XKM>x3{Zaw-kx-asZdQ)hlf z+{0_A8Fr$qgKZBt!LRM2j(R5zaC<*&GFg?K1tRNK0?%9D$*WqP=G;KH^NusW^>DLy zN$=BB|9t2n$mr13si78+4X2g7Ve7G0cJNh{BWqPt$NgJ>P4BB+pA|@EOr$#sE7&&# z=B>Nb8~>Z}>3!ZOk|$1Y!@;#)LU{t4;qQ$eSua}J3eRLsN~TrGS5{Yl{lpuuddHGL zfD$=pQJ7nx+J`Tjq)_888y>>k1&Tf4%4Zqw{W zEQTD+>_!}>rpBgBri@00j2yci9RICN52TxD{E!1eQnYlC z%3MyxxR;6yibzFb<55oO1%F$-NcN-j&Cz9nprFhVMFkBI6OmO(NWb~@bpNP_-GFBo z{pmComg7udps#OH?3_>mh8(AXYG6-sKR#A;db<%Ez)uF~1HXIRV4nw{Gi7I?#)D}s z)1PGzyEI!oe~;IQQkF!mi~h?TVPR?YI4e@Ze2b))3I~k_)v(F{dKe$##_#q>KN{8Jr5r<9Q}B- zbCr&SLDQU|+FM%sz>ISQyh$VxsK8?pU0S-5Z7pM!duOAg0PnokzpWz`As2Z~;=wT; zNEr~LVT+qb^-V#|#`>;)_vltwUKI4fl1Pl5^K+FTHSCwQ-f6rZfm~foXBKYED;qSo zA@VnqsQ!zLh1-WP2|u}79rMbPRqqeyHwQ^`X>Exs6GFiH$n~)RyJ(uobI0;vo0(dH5#)h&?@rMG<#WbTj%V$o%wo&t|J;}wGDPkUoPXV$b{!O>N)@Aa?WD`#Yn ziR$Wf_*`NBvc~_@~$p_*F%^7QG-JVDR@~5c77E zXF;t!Fbr_`i2saIJy4z?jzs)?55*Q7E9Fc!ZnPD~fP&xBEdv`IlI1MTqc|B5^!!0r z?J4A|Feirlrm>i@fdNog6hN?vSm$&Y=<%geoSkZF3=2Jgt z@_PdGql@`TEABmSh!8rzKw5cW-xt4ufrMhP({Cc?K)%juw#cX1Qf$%8v#k_FdEu%E zmNtu%2h(?9{RQM0j}_v0cm15jbAzK>&_`ro$f{1k4SZd|@+Gs_nZw{xlawJ~(SLxn zz+<%mAvvIUGN$Yln%SpECX(oa7oUzN!%8$Y>1%rgR(^8r>!ac3 z9FS?9&eGU=&D=$_U_^eif>FaMYRaACF_U5CC(Tf9b?bM&iN}EMxczc7>lBI5ddfvUdPx3};g0x?W z9M}ab#C=D15Abj&(%An^L~N{KeM1w;X-+q`ShRd_I_JJI0YfCsty>i^&_gn`rI4l# z=*}Q~Q1)Iz0{zCNQ2h07-oEA*w#UAFh^Ocu{@3JFkUp#+iz&cCp_0R0nW~0e*Xf## zK#pTg9y{RjYXp{9xD-@Q)#S1nUPw)i9=|p16b#W45Q%f1o^@!sXw^I;?dVXjg6uFxe3-`Bj&w3(tbE$OI7tk{p!ASOKsW1o+xysIN; zH6&M>)EiCR{3#Vpzbn)2dAqev1AzPYx!drJv2~FHJlI(&&gN@tr6Gs?velKWRO#V7jX!5pxcrICO7S3D zAJw}~4>!8jI;nA!X(p_%%n+PE?IG8pTEO2Be3w-9(G6fPpyYg6gO0e+!+6}{yi#3+V92QjGXFG zTcFI&D%@U%(ZctS`+(~N^fwgEhFNNilvHVKjn-sFy zYLlWBBhlEm?dIZeTI|9KIO_D>Zn5n5ojXGR3oKFW2+5F*B6XLovtK; z3HXNl9|~ot|8(u8S@q>FWMYmSUw;`JSK^lpF1-X+TQbZi3#=}hcs4#KE5BR)_o!y9 zEhxO6&-44d&qL>E)%48x!tYn?iZhM8IlZd4894HvpD~OEvx1ub@K8;X>pJ^H*OXSv z^DluL>Z6imvzDg~!yL}IXwbjcKQ`XTB0#;}Y4ktt9?5A-m12uN!5DFPS)+Upnqd7~ zkNVjne!+jVvZIx;Y#jyR?6V&7Lgdh5UW)~FO#>)jboV>TpP=Mu%>Z}r61|HR*Hbzh{W5jbz6Aw%4v znQojXy@LwF6u{780&ZQpD}^MKP~bVB@W{qx(@gozLEmjnqC36YBR9|=^U;|y%+7?2hyzWlH%c>&mi*Hh?rZb zue-#_%P403V7*Hb>XQ10D7&Ia-C=ejWj0C$%jp|ymAlY9Ok*Rw6vkc z?2)*09ppZi3X{Sx|A~c?9+~3kK&j0qyf%4C{n#-MvHr=2CSw1?8xk&vHm4(qcCGud z#62jnjWelK(SOT|-$4A7j%x=PDOP1fGK4YgrRC98pVSV~bK0lnTgdq6tnf(a^Y`g6 ze^-Kt$Tn-6C6B*|uArV>a`t|OAah$*)bmd4kugxH@+?_gR{Jk3q1}oE{>L8R37D2C zDejmOL_M@gpY>d-(-(KXMn%le%aV972J<=po>M)3YYWb##P>rOB;zPiSVC=&r0Y2E z8-90=iK5obbC-piP*AA;^G3Q$Wg@N52h;p@3O2n&mfeNpIr_5@gql z3rE`W!e$$F`Q_^(Jp_YZ9F=M-b1m+y#V#Kh)i6rA2GiJjR~+ynq9DVYH@Es%tWu@h z;`=&U0JcE@(Lvd8Z&C65u0Ne$%H})IIDQu+T*RaorDde|Ln9lacV%Uj7#!y*KWF)wLH0%vQex2{vQK*vE_#LY@&V-T@ zPIN(*+w;MK34a@9EdAkbU{C-VD97fH!~d(Jwmbd;D=P47bOSdpTG6He@wt|-y9ah$ zDFHHKm5LP(sv3gSq{uR;=$z~HJw90#A4aKnvE|AxNuWBG!7`r}tvXbZGPo>mnIp(? zUtFwtjIPmOL8#$Rw%b~JAz5GHhK;C;in9mfJ!#LJ`K^#RClq`JVGAL(5qOFn-au_P=)Tjn z>7b{{cKi32i(faI3ER}LyF#4erb_7jrQJ=d9gcJd&=^Buu`9XC59&f5&FQedk5Hn8 zTeMTohd}Mt+hF(C(rKzWHcVS$!S{(6t6sbIwxf=Cp#;d=sQoWDt53IS$qSNo1Ym+Q zj`vK>X>MV6Gp)MBmuIRgkH6l_%ODdt%0DQPfc(Nr6})hxron;V!7Wago9>e+9(FDK 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%0A=2~WpuGugl5aamXG&;H(Zcgqky0AVHp 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�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 vRbxF$}z(5r;IbW`2%<=F-02(({tz(z>~9; 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%0Dj$HI(9Y=d)JOOCPf6 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..cd5206ca0bcb972dc9008f0e48e5151812b61f17 GIT binary patch literal 65856 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 z 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 TaqXYT@b4mY)ukV;{^|b#ra#C& literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..c205117a4680801cf91db502c153a154d402be49 GIT binary patch literal 1377268 zcmaI6Wl$wN5G9HY?rwuS49>-2aCdiicXuD$oeK=^?(Pik7k77ec;9ZldR6;&Yg0)l zf70his*{{PttbNt1rPQ=+EcWv(*IoizXk=25X{ca)`Hc{)6vAi+0o5~Nlg_F4ASws z)!=^&H%|mGaHtbVFfb_7|F$Up=Lqw^8TNlV>PhUQd`18R%SQ$SL|(iy#K!E{pI~V{hBuu zf&lgQjlyR!CHTjor}iP&-c|_ak{*rZoN3rF`w!wM8pf-~QqYf%-oYQEZ4O=6 zzM)T8@+tRSH zYY&o*;}@oZHiIr$M5*Gg__P{yg;hnBL3yjM@dn1W7Saa>c0DQQGRp&%-Fjlr>%8Aw z=<~;Ze37IwrK_Xf-ZHkv&9%4#Z>YZ&Am2-}r7hJ%<1$iO)UhZYc1u61_2)L;uCU`N zE=~Y);526$axCe+%V0Vo?aP@i^eVk5GWSPHnZELUE}*sqBSTF zR|*X#94fa%$ggRqV|3g8J7V9r__j20%XKdN$~x?Qp4OlFQ4AwIhVe(Max)fG4?%t% zOHsH(PhzFZ3pAabb7%GWdMQP7cJIV1jMRd{DHuxAcSwodA2f-nJAV^?ESjRvVq{y~ z`0yP!4oCY>Z;A{$#_dav+PqgmG3`Q zEoR5KGK-vS|9#Tprwmq26qf%o_`B!|?Sr=VzY}IrAAENhx5t7H#ujhkjYVn^h(=Z` z2l0KMU9U5Ojs}$xZRKJ6A;bs7=Cu!K4J+P*a=+a6)IY3#Y8H8tX}x4pW?3_O>^oZZ z97hhs_T8uhBEA{9eE*bwE69ab`OP_%N1c2XR4D@auli`GAD?G_E}7xshBI?dmuTIX z0Vo#So*l^%=ENgMw%y%pVied@@sJx3)PpBP&p1JQx>zH^x{~(a;~J(vfaA*GZSio$gAJS>CkrB zg%CO)ue`+e%IX?CDp&f-}9)a5p=<8H2p0;tQw&2TW& zCw&YaepY6zm;)vN8+vDoW5iH)|1_VK%D|BDH|Bl5E66NE~B(Co_@*E z;0Yu!)(q5?7a?-h*5%~uK!|_1AXNugGK;G}=z#Xl8Y*8HjAQugx;bP{o;lzj7YjBr z`G#N;S+s8-SGh@1e3;>1Hm3oNvEBY^w!~qbZ&6-Is4kaIX&>p6b#zoCng#8R>zV^u z>4)yVv%M3hLFPj-1A{?s+L8zxa0vyoACzZ{$uWE!)bjWpg-AtoBinobWy3rg{^G;H zO-RP~c%5NlF3AAF5ttEQta8J2g`~p%RAA}ny&P{PSY;X*&=NVt&gW^#_HK6_1a_t} zLtjkr%uU5Jgr!v_<{8{HbsLp>g`9JtgTG{i$SS~c8{Q%MfUK|uKf0cnk)i?kob+|~ zG!axRG8O8rB<2T6*xQL_JdZKDXSm9Xl#3Yh`7*h__EfGq>e7mIFGcn&xHO!DbLLYk zBipWvlbp|ZOZqLfGnGERQoZ{yU$Nm3_vcB65CSq-RO$LwZxpdS<^%o}uAo?dAWY~1y@&qP4J?e1c;>c7h)Tb#A;JRy|Ep zoJE;4*<~{qn5NG3zpHO`NBrhsorKLv8&qiQv+T84tTpoQ>1Gv?H9UQ%n_(_6>UclI z$l3F^yp_4@;Lm)iHu(yDNCl4#l-=RwkAJc8>==F$hfFB#}ZWK21~1WDG$9AH|R z<#Gq6oP5F8p|_uVnTU>)XPYUS5XS$&!rfw9zM(`l`tGKYk1pR~bZmhPO!wP7iO1G)0KYFN# zke$<@Ved{>uyx`YQA7D~emwPC&Id&PUaz0Z(L&8yxql+^p@;$<6Hlg@y3Q7qmX&{1 zfc(+RO_8nR@9(-y;nxVB^#bhrUWK}X>$Y+_13mGtCTwf2`d}UKV`{bV$JL?)mWZXS zUO0nYJ)CU4)o7}qABL9o4I3MQ+UpR>KhYm3uyRuK_tZu`3^p_T6ofsqGu&!sJJw@L zJkmzBD@Gs>r33T&Umy$|VyhC$<0~Wsl`oqzaABmbeNM8u;MD_`*lx0)l{@rzIP!Gj z4c=cVJan>vl+~4?taOY8awyNr)?GfMtry%rzMxJwF2;4Qv0Z{X1q$F+h<2fR?TU0% z!505XZhU`*nLsCYug_u6^1nDJyW@!3|bnXtr6FGbZ&NEGG0 zEbn)An>UvD|BTHR@7{$V%0T9)hOZO4w(_sLXXbU&D&6O`9n$>MkMku^Kg~*Y>_BT9 z)oI;S2uR=`&~+$y#%3u9l<02T8Y3)C#Dc8&c1Em<-R7~+g`1T%61Cmp&=JHqsZukQeM2D;8UJLLXyhoCp@MLh> zd6JO)Dr=(_Hb>`fZ~%72Zu>pbBhGH<@PPmeUR^QjkF}mFXj=Mzm-9_>JinBa1n$~BDS(epM7@--8t)ZhgG^`$(3kd9t8hap3z{zthomP24?sl4f#Lt z3=TF94s#YRBUTP$b5mv(HVzY0W)oI3HdX*TCyOyVJ1Yl(iy6Sq_J89UCWgO%*vi4V zPc|4>mqwP<^wf9ks0Qsx;e7%g)plnTd?zzNl+5gyt3;ion4bC34` z-Uz{7D@VPA$J@_e1M&m>@9y-@zaG08-zp5Iy=JUR9Bev{(|g*!uU0E1cr+LsK>6p# zosDGAQ|%E~vHivRZZ$nB88f)&{!=KvYsIF5Hg8FgOA?}=?yHRtzUZD--8(Ogu`y)s zOEf()&qu?d3K*Y8jdFEPdqB>SQG-vkoi%du4|;y+P2cGifbu)CJhJZl!BE$X_cMh( zfelaUG5r!_3;!lnM}YYwLT0RN?ehkmZP&-Bvj1R&zUyZppmMS1It@rT^Tx}qO#U$t z1v+w&K;Ls1#qytuua5*^g96_7$6xQW(yE<3eTIXmVF$lX;NsnM{$wn4^|)G zX`EJ-cD@jy5Z{`*Uf<$aYRAx7_xAA(x=o{D@+Zku}W39c5!K88&L*$pQ||9?h2Z(9Ycp|74tcNZR0+YJ)^00?p_}bz3t|;>yn9Vxx3j;VX6ubm> z972){?lTZ{&=PHAX7#Vw)b%sCp3H|1TM}yAu1V(4J*|fvasyVtE!%m}6tG>2PQ#)r zQr6#ZL+e($;k9alpWS|NK9Olrb|0CnpEDnJK`Olarl=`#-J@FlPgpumFrWxVeo0%p3q? zE)FA(|653zfy{nmZm+=H6q}@LI~NOebpF>>QpBZCuM(~lwY8P86+1dR2~LrBB|d?z zGgm#|zSmo97DpR+U_0O6vsZ{7xRk$sT!%oEUA~GrT6NGedz!J{yzr4d)**$LkvYSr zEF}y*ZVL0%7(gyF7;%A7Oss8*_v}x!E_P+FmNAirJutadc}o+sMc&4fJxoOEvYom#i-S03p+r_@GsRydCz*R{Y>#e%rrrEOGK+d+G2z9C=# zr)$kVJ2#gBbPQHZ!-O>`Wl)h<={oW<)3PaEPL6?VOq)s}N_VOXn@SR|v^uthSy;rk zS1T`VF)yG1B0HW`;kz+-u^&xowziKTDfKP(xBi&~@}fi>V74N2#p=S{)&W|rE>vEq z%*0IBhzfj}yfoe9dYmwLQiqw53p@c<-5r5eGa1?zdh~BG0xTz_7eD=wIbn*eYhmQ zh@lz3h0Z<5js*fNc5Tki>os$qSrYk1zmB4hr+nYOS6ci0R;rYS+qf77>&uftp6Y)#OF^N%`nkveHZ=Dn*UBESi_fxf8h%(hhV$aDpwK!e|7c|!+# zr({N;!R2!5FHIqdVrc6(3P_V1z8dZIdw+0Ryh%5FWnz&riJ3g#B`s0=&kMIJe&YcQ zWK%d%&uUo_xh~J@yS8ECHM2j2?!x_>ooA{kQ_LdG0(B^sDb)5k{R<@o+;=Q8{k6W& zr9r-umI2&f5Umn9%NZDPeqGB)9JI}hFMa7fQSy&@lPqrhkw z47O=njPn8_b03&XE-9n6tD{;|=~&YhpE7cPbYwxFDWW5^|9&5DOtc+Kl|Vdv@N*?j zIsO! zvDrr4N&0)yVZ9cBVDSWD zg5Ry+Tm;+JH!cb6-Gxc=Y}n&Tlc-qt88&P=+p!a=2gH^l<}P6&yiTNZmhRs@_t2nh zyi^dG1a0nd0mndJ9fua!h~EHnvR8C2dD&sr1DQ_Ih+XTw#24MW{u+)nQ#KFC7ZJ&H zIgh1m*+OcnxdXNm2QG73{m1M}?{5;AU_jha>q^I2Aaz5HJBO)113DE(+iMtSy1A-^ z0LyGw7VWnt8XG_1MBX8_oVFm)H+n7}b28{sh$hQsMp*7t%ieQTOa=NpktHam>PL+n z&tq1@w;vSm)V&{Cg8;C>oeI}ylV(0iXAtL-RRX}Nl{m(av0B&_eBO)tU((|1JWZV< zHV$6i)Zsbyi5W?#Z`^S5nMS0mQdMe6J~_uMkn+AITgrN?^Y&@>8jXe@6#F_573o)Y zY7{$RpTvTIVn**!Y9T_y47>ZaofqH_mfN{d%SxuC5cdwUtMdu1iRs->s}d!j^KB0Z zpX1d*-T`x(vDN@qZN_j`Nzkq&6$UFmfdebIpB4}kt0|~pg5A~U7OA;zj-Dw*urKN` z)4g9E#dOab2o!!Q81YNpiEPfRdfHuzC?wp%D;JEA_M?8L?UCP-&AP$lRxV-(AIw^O z?L_(hYMCZMQ%2MvHsge{RX4-RG@SpJyqR9QNu1Yo*to%8ESp)1r5YCt{FQu{qaQZL zPxgxdH}_(SlH8fkpMzhw+HLZ-fwv4_`$O3O%kUA~_NSP-rTV)ubas55hg3szir)Z@ zjhuTZIl)RiRq1g57G@dVzdr3ZqwmDV8MJTcedrBgjY8$0%#&c};Kvd%W<493P?J6X z{xWEDBr@U}f4Lw$UTPmAI|OiZHzX!1m*36TEOhtZ2tm5p^}iBf73o#qR@*fl+RPaw zfDtLBqds?8GJAcJK`Lp1}zifh;VD{97VyW@c``kWAcpHV04ey&C~ zCv>U)T%RvtV#2S3fb;!)9u)djb3OXn>yy;9PA1N*a4FcYCp|Z0UYDIdP-J zdlD$={K2^JU677K$VQRB$s?<8*PpmsLp3|HW5}HKtB$UOe2@rUV9HeUqtBpgwxMFzslogs#;JLhF?0f7;+c=7+;xF2VczAf zT$rR^iRXfB-(NH(VZ+;ksict8H)#BS=T6jHn}W-Q<0aZ{Z;dSt;tMcybU=Nqw7ER2 z8#*SV{Ue-ZTF6?^FAXi}fD!TC0RH)_y#2G*J=@&y>>0^uK6svWWH(s|7U__a`T7!F0J_c3?FrGjO=))xX%VX zuWFW&SD}hv{p=Q2Q!n}#TX1gye~%-;rP{b2(+gz)NpQxv~nDD2u7H3Ncow5Omo^i`|oead-MsR+=|I| z!m>w!<2_3B=l$+tBEmn5<)tZt01h7!btG@lg2Ibh_I7^=D*NJ)t3t1mRQe=BYK%boT=E# zof?$---V13+g=9O_9t7S?mW-NRPp=^0>5-L(9PagN+Qryk||rqf5^L`6_~{uKiHuk z&w#eB;3kAxwQv;7cR4G&hAMCcHdyN25EiW>APehWVRebDf>(Q~uK*5-%ENpA4t(9_ zktsg}+3J!v>}e;p*{lz6VcB0YhW+qLz{HFs`@GTH?Nz~GEY~@YY#s{%=+xdERSWOd?Gbu+phW#x3-&4+2gZo4xmVulIlO&QsfNx}3z_LLpILY$ zm3jLjFeLyb`5T?!nxc6sfhfGJ)z7so(0TqNL&x2y2PcQRpumH-7AsC`lxWqb*-;D* zPU!he4c6Je_XM%r8jJf!&|z_YZrDGLW#Rh}HyJ3GzV7u-oNGP0P#CS}ls@X&EguU_ zg)Z=Z^<>GSeIp>(bWQG4s;w%Xlp5RagxIR9O#9^vQ|G~|@$1eFDtGS_ESl>Fmi?#t z@Lm>N3i&J(1jItG$?e~-`gBGQYrFO_GWq6a+j+^ywg50gv0HuR*WD z!3M&gk{ROq=;2J}Cl%G*S+zEcA^*H zBbL~RdLv(%`AP_Aow1~s*;JP_o%Qw=)m5y{ww5rxQ$;w9cqlnxD8602*c!^)yYr=z zVA1zSuGg(No&55#kdBV~cZp3yr6TxwR@@w*F<+_J{pWjaO|J}-&tnfdf9RuOYMfk& zV$6m)R#1s6xX0<^u6Vt=lC$KR+hZ!pI?G~g!|us$c%7&FDdQ0)DcdE`SnT>y$iOkM#8nhd!a%SP(SaEEe~-n5`6 zRWiZVDd%47$!uiIfXnd;i3lG;?x~=ql-1rUwB!SqwRVE|80Axi(fzG*gHQG!RQ3Mx z=&Y~Oi@h&?>+kS@huf=w1Z6Ws9Q%RONbfS`CG#XFTT#|!)Y;&3;Ot-p#GuUw%s7c0 z)@yk>7WnM+L*BwBm`xD|BIq*}W9+8y{n(4|PxDDjytd?bI1(mdJ6sVeC@z zK+K{dH{x%3T5!^h=U;myRtD3q?aCijxyROT$qxuCdEVaq!p>Q}`bWxWy0`YL)n48A zuB-)s6LG?%2bnq|oIi@zb*#7}D6@m`$p_c*u2j*oxGpP3>~fekjBDQHMkNOvF_%X~ zA8yd8tj*9rYXtnFP_RXRu$KQNOqD??`B-;vu8K`Xm*}kK%A&*CCbRn1<0Rqe{E;dt zYa|le%CWP&tz`8>D5}tfiwA9d1+5KVml2P78eh`dzZB+Ub=K%H+EVH8eaUeYun~J? zaAG51Ipf|4Dlp!8h}@(yF0Z)=X&^>mb}JmbKCe}vc*Y>(DoX!_)C{aH1d*>2P^B2Gha<3 zEX(Wcd}0-2YvhOK2$@@A$&HF;f$Gn{YiFK#>>Nl}zCq{3wb5s3E6TYpHc(iRgUZw@ zcA(Ff?wZL5;WEH&=Z=$TKQNNEz1BeLsQ&MhVKRj_+=|_vpKt)N(E1j?CES#gqobUFA zd`zD&&1hHexW;i1p6V9P5di6cJD{U2)3PFZEE(`*bGdo-Wr*4NjE|0y%_oGL*hu>< zHGTfCVAZHnkvE`4F_2~o=WtU6>Z%up<%Y`8%U$>zm!Kj3$F0fk+7$+gM-riY|-|T8|!QZZ`mR9>x?~C`_CkH z&|`jkRn>fK={sP^EHLSUK6(2%1Qo5vD@DQ-{jQ+KvlJGo{8NqpI(8ew-t_TWNw`}e zydatqe-l$!xMJ%p&EL$7bSy zpt}7(6b3e(A9zXSZqI3LNN z>aTkpGtPuGXsN>@P+KsrW3q={F&R1RrR~0}>uS4bO$>nO8VXJ#mWk;(Ka}ZJNA#FU$G@7s1yb$5azd$!olex2pnGPJat*Z0VM^{6i!&%4G z4C~y;N1_gM^07XxNhD+XAy#%EyI!S9ksNhY-*3PL&d2L;cB3vP%pvw6wA7o(CV>B9n6`j2XRk0ej=&kKhLL`fKq|$0^Z3#W2v_AFc&0*>g$6& zO_YfoPo18IExjrWWJ)(G;d=*nIoYa4=dN_vU%6MhuBDYi`dM;@filhPRG6{SUX?5~ zej#I(x%o8zh}qxqgjH+GnHdjY&+B{H^(pWKv56#~IK`Ih^De8aR3*Sly?#n8q&IN$ z*;jLhLWIRY-dMG_31lMj756QfRQKmXuiTBm) z4swMyUB467vnR?7NMR!*yv2ZuW|&-mm2{PFkwDb#Aku&FTBI)>@E+T-N}GM&lbQW9 zh2-V#JA(r(13$1``m?;IF(41_&2i3_sv|pK(i@9BoLI^y#%u?cI;DYSRXHDMyi8l3gP|Ql4OW1l zaB&3o1Bfo)Yn;m)LW`#xK%O)g8FpPrEHO@TFv`H~IRY%6*DqEG!J!ZQ>)b82;s!f1 ztL?I%R&7<1Khu9)Q%6(@bd34ab&${Qey8jmn3a8K;FyZvdyjirNPzFc7nW=WNAlr< zW3}O84yW(`WsR%rR~-jgom_b(#k*2A^zIO-O~h5&mz1@#d{lrBkCsRd<*v)4TsmDA z0+k9O8e|AHGu7nA=#Z{Lk#qgE(Uscs$bM)Y5SEmp1q(L1X2v4<4f(%{ZbV$O01p)~ zus-Miqe5WHVs6H6Zfs^^ZpO{Z%4}?AX2fh_%KBfi9}C-ml7ShQ85g%Hw;9*}O(8hU z-8a!wz!)`Im&acR;Qy_+TbJ5rZ%G~zH(7U2W^b|E_xjv;r~bJ6VmsbgnP!E$Os?(=R@=n^ zh8!$8rz05Mx)Kb+U+E23Y-#GPgD;=j5^k1;u&&L}b5jyKJ@nC5!xrDw*NT^n(uEx} z>9%#TDimQ>CEfU0tQWTDy`aKDkmSq2lqHna>sJ*RkDFUqB3#xq?4^wQLgw5LekX~I zGrBxMc{q;->Q6Y81j68pIyvBBq;S=34(VL=&?Cezw(Xb9NeXb~MB%`QSYJ&NHg5$< zY~MN;@904!lCi?jKt<%zMS%J6%_3pw%n6`Px0m2j?p6DO`_Os5?$7#&-k?zxl zv%iTzE3=@P_whvhBSC*j!~p7mdCm5+r={SbSGmOn+eR2IeHJmsqgAon$0Av1z&dWB z+_Cqt_Ae7YCtNbkjp3`Wa1r)KP(GsV_A;kT+h+mVE%nPijmG-bRe-zVz$khJ`2NjWg?UTkFGHYyT0%@Qe;7 z%a98`22e1^R+!f2h^ui(Lu^!kt)-c+>0ENZIT4#}YGQlK4+e0SQA2YFAo-r{UBpZQ zhSCucLVX+#Kb|G#`wth!Zn0PJXvNE@1zDxA+3&yTb#lj@>1_RLF@+m50ZpbrjeoQ; zc6kwEESO`{rCDkfzd+$EoAlqH@eP_NGP{2E2^N74F!$pFp;kP~m1SU?nOGBbOT6b~ z2m^}qOytPCURboF0ygC09wd8zSP~19_zii%NOfolP{NAEUZuTZFTJEvzNz zx!2Ea*$eg+U!9+H{V)N-j_vQftNy)uYolUe5dY3t#2Yq#-hP&2u>e(%-JuK*4f5|` zaOFa-nANRcRp_BgD1+T8Or7Q$!klqqXY8c;tO-4v1~;`X1A9!CM%4&)MVhB6m$?#@ z_jBJr1qtTb)leeJPO%tqRoFXv&(dKo7@5CjCkVg=m{%D2c7 zMzPd@AX)4@ndQx39aOiN0VNQP_<<7vbO4Y{MWl!m5Y;l(21W4j*0J)rWp`cQ7lgs^N z;{+e(3^}`gv%fW-?)Gl`oEF($xb^3brZY+TsF=vc`-u%1oA$i!U~iM#jAVFREWO8n zjBX?Vx1lFKQgZudOek2hFE)+=4t2A{R_APPGsCRgy_KWyeWs2#@&s|9}e<9-Ce5_)O;JHeSv62y!MU&N@8uzppJ&1J60|n9x`_qbZrI z>kS#y-I*5+OsU)1#q6P97z7CsqVk>RWn?nCra0RM6zbA| zwdf<;X%?BbWIQR9DMX#z{o36<{&GcKL-=T0+kAPjgI$@k@kG7NV6v*Z=-QZ+^b8Q> zx@t&CdIw8ynuwBhv*>n8Xj|f(&7uS7VBewLAm^Lz!U{As{~Sf-h|%ajnqB?K=t%!_ zN9~>me@4u;0=y|+=O<77&b#u1?M*G&8kQUIqqRbrOMlN5Z<@uM4I&TStc}1b_an+< zFV1Xd8B$>O4cXf@@2#_j3O_Y-Dw?2#>_}sb?KBh!6;8uH}OVDl3mjXfW zxfR2XkuwgYe^PX&mP!s z{&YK?wW~uq49;Ppq8B-4DCcZ+tOaFy-8DWA#zb$gV$KS{%T9)vt2$iLpjC0~CQTuU zW8}00>){_P#!V~(@b7IP9a4)HR`;nSdv%{~8`u>6!ANW3l>6vkBwFZFJHviH-bj$q zJ}=C#C?d1`sX%jmsZg1lZ?+JSUz2BYP?hQD%zwli8YO{#CUQ&WQX`8+Vy$lqNIN)9 z?$Er-T`NN?X6!mq{zIgozfvj1Ap>Ib@VtgNd3EwI81HYBM{|4^_g?80gr|(zO3x-f zNW8*YGVgjn3rWM4quhSY5g1gV<+jbIj<`ba`JV9i@rk%z?3CW7x=PuEb3am*F5shz z`6|xV0kb5w2LHu2nYtkX_Y8RJvoJzWc^GwBBcM9-Bj@_iQu|c(Qu^oj9T$IJW?OB= z@CW8sLV(IT?6gH987g5B@75CuW-!Fs&0}d>j0A_t4P#p`;Bn@bzt6^G1ilmU;?|C| zCg^3VAzycK{|4SzW3R(XFLmeNr``1m0ZTOmgj5Z|ofN>OHQeHDq?BeTr ziS)B+7+AWf<7tsOzC!4`Dv_|#9;_VE+90fwm`Vh^>P+2-q3AR@@# z^pDkQ(mph3$Wu=5zyuz@`k%46A<vNbJ=BW$<%fLpOEHTLjt31;GNBpUyOzBxGozR%&IFeOp$>qq$S;D0NzpNQ^Z zOhng0rH|$>)<^qfymn^j;on!@6wC3=P6KGQV|~81qGA)72o)L%A1 z)ul_omwIE_iD zJj`&(^;c_jv!A;!hj!lww4#YE(i%93)3g!1b3s*VO=~~K$Y;e@CcfNJ3Z~4qG@W(j zz&I)VzwF)nOs)|`C_eI-s0PdOaeMT6euIWvMoVAbc}e1XL{C*+NTxt})1%i5Z|0r5 zLmhG>O-nJe;P%0ypqB}B7sI!=?fseeRpGN;u{qb5`=yw@&4O4Bqs(Xz3dqlyiNG@M;=;;8g7REqRGo8Uo<_gPwXcI2fmksOdTG&r`5F!^8(9!TK(nE$ zZ#TkZf$A8utykFkS3-Bdm>ic@kTCASsYlPNE~bFW2dr8Uw;>m{&~Ym~L`0&3nKvv24>eV1BtETzo`^SU`--|Z?sR;g>C_pMIttP-@3 z=jyVc6tlV|de;IkE1k`#M&qKtV@3Za(NC0TG7R**!UIQew=z$G2w<$9X=vfF_J{|y z^)5vw;2a63E(*k5>nQiHx0)TGxi?rhfFy(lmAorHSvG3ItTKPjKR$oQGB5FoCD^$Fh3SEd>g|6lroc7V^ za1|%yEY@otm)%n=%kCaK@0LM0`^wj5n|2=7Htd;muvQl_uj_6o_ zi^2{i6p8q*enN37%nuc7PssXiybFn}& z0?5$$G;vmgc!L~T(oC5^FJ`a+M9 z&v>W{lJ@0|uhqYpz-h_wz^{V(Q(m~K;_&dP{wVFcPv36M=$ryE_%)M1#=5&84wR7N z50qPbF8$#I4~LG_hScMIJipcQot;OQqh7IG@~vNRs%!rJd5_C4=e}s!OXnj&iO=D( zI+HK|9w7+{3dXjhVWB1lrZc{<{YqB>{$Lti{R*~BK0}gA!;$T6-fboOgoGE<)M9V1m?XvhUTY( zpTwBqFBqb>L$c|pf)iPop$S*vvA-sAarEj)lFxFa;s)W=M!D+z4#NpJnU<&*1X zz{F3G*pctxmA))+CP&BT*Xf=M-K%r=dbr^3+JTQH^Me-R#UP`@Uo_2c`fh-;E@URF z#IpDpqpYFxyU<4TJl~cX%v9942^asAD{y-7XV0=iwj};l{ktQ=<|TK_mIlIyYRI?P za>L{P{2$r-%cf!K7IuUvvfWl4J=9V3L3Vw|0~ZwP^@%`SF-Rc-^r#WY_PQ0TT1CXp zZKPA9g=8^x{3*PRn#FkkdUF7@q4KbV6ljJfK`CWDY8s&wu)tes50wi1XEm~j%hC0O z|9u2s-HYIr5ux*eA-tajzV>A(fUo zt$2nQZF1Igm04!mqjF5f*w_@Uj?;84;GvVKYE3E|*reAphZWqTxEALxWDVXbVf=*Q z8$BVM75Ed0(N1M?XWBk~Exy6Qga28`&bx&9sZVL^`_suHJDXBg{omdx3r$jTlY zE&l_VzY8sHPmHC>Jj?ml@D4n_sGBNiOfvr5!I>rF$7-R0;*s&W(zN2vh?bGqh6)}9 zN&EpqWlJJnw+Yjb@30Gqk`oW^RRiWTQWTTzU5l=hnZM3w=@^~vuTfi<>$LtC_3Q@> zx8men;oqhE_ErVn?v?*=W8*U7I%`BoOSI^BO^7%J--Jw9O6r<|URZf@h~Ru2(_Uty z{Ur3(k7%0XB_zgome==GK=}D;*8%2F7lp_N#A^?egej=@qFZPnLPnXl(Yv}|OC&f> z^M^}EO6%rgg$V`8y2TYze|BoG=9IzfvsZ7aQ zfBuZodSW570Oj)t`7lU_=`OIt!Ik-krb7Ch3O0z;yhKiASb{!N(#f>8QyebiPC4T; zRtA}&#}|}RH9uW3d4d+^X!Fz%#|9@5ioe8T;w`}Ol#l`f(&^hnZQ8~3t{c11RHNbJ3& z^WQgn5Bvsuk9JQ^X_N5*5+9On$zi=4jTXk%ef4E*M`dqPMJUSmqaXG=CSb@&38QE& zGKhF3oYu;TGg<+t#$+j}A@fWq?R@(NdLNKFXby}HMI8iKVu6Pf3AlL*ONe78f7{OB zw2hdY%Z}a|@C}1pv_ef1zopqEI|reskhsH7ZHvgV8uAF(B1K7KIo{L%yA9JR`#z3x zIZ)5!Uu9oaqaSHH10#L@b??EDwbL}i!yc)oNJmX11J}J&)0Y018Y{mH3_kz z8&PO`n<+^!vDVLBfV$e*^q8MD!4Xjg8C87X7ixtY@@9>UG{H(7~eHE#g76y7-79rQ0r(- zMm6s`XW*zv<$^ii!gsTt16%#cw}%ubf(sjC^s+>o1%sD2oICH!GGb}8C7p^Q|GG$Z z?&@~hJJrYR#=`WnY{)PEe&~a5^iPKA1-+$rF&j4Ws5$ul9Vl!)z6L(nprn;T2jlwb zMrOOsQ)GoU5E{|bk6v&mRM4~9xPlrjXwdNU$NLr16nNb{lI$LbNWTZ|L32o@p2!@$ zfy$>cxaUH-(k+Ta{s(5F6$(W60;Ek8hK~>rs>r9fQi_AT(%_8e>q8||(&%C+X@HMWC>$pd7vBaah_QL31(3z;~f@qiXp$1$|6%;cc zl14UEewJtInyJJ|GRzVX&f(Z*S&h=}FOxM38dDEb{$oi4Y7?rv`LhSG}^#uZIHN9*3FIVp4&`G_X5ZuWgdxS%$-yoFiW8 zQ&(QEE$wDH&vXax1^7e~^RKN##p>lxA$O7)lI{9t*%)vhL2wnm2^p1=eU3S@^=3Rt zylxN16^jiLgpcxF_pgY${1rrU%{-Bu;Bd9vPZFH{x66zJ$=Owp&WrPhvo5(On- z7&9CWWAZ8xHmx7TiePedGHzIHb^(0S>ZKn|jy2N<^Al@#l`6X*iadED%HUXMHFOdE zDY;woUUr8@5EaCRoE;yTO&tc@-rGimf?lXYklX5$+YLxH1@{9>=EU+H5 zmWEe7W5=8o?Y0Tyr=_k6?i)4vL?_IFG%&fdUR3mFk(F5U@-R*D6N}PspWrMor&1H1 z&Iv+x%Y+bOq@5pX42fs{;0Vf=c{J(&4`1iloCyo>M-O>AS5Jh3&gZ95a&wr$(C zZQFM8M0?KHQ@i$G=*t&qeUABI4$Jo*uAubE2zgB^eYd+>h7Y9a!+I5>DDFstdOz4fKToqkCOXkw#mpmt*l zq_99Hj!h!v3MMHD2FAZA40(~^%=T%8qR%(MN}=8Cvp5fJ8QCsn-C%ti(c@i>;PKgx zgT#sdichAVOl0Vp+eN`nsR4M}GlYq3T2BA)0dRd zW7u^iTG)nW?Q6;X%lX|dNJBuX^bux)VAkG+?qm*TO!ViD4h1kq&ZLB~vvKH#;f;Hm zy$XI{LGz>)Kbv`j?s0T)0OHZDxO#t9_dGJnL>x;m?wSoo8kpX`u($eo-FyT5G>S$} zDw57`pe`ALgCD%KXiWSfH+|@Ufdosy0W15C{(Cpj8TDLy^!(p+Q9={&<|}u(ImBrP zF(sVGMCo+o!&cM%-D0)O)8L3l8zOk@-@;%XfeDp$(2FjSAe#+Y`D>7Zi_JEnMZIUL z%949!MNzm-P*f12MAYm6 z^=j~Y`LX(VwQYy9^_ODF3T!ym6WOy6J103F&z@f)H{a4zscXj+c=_{&VB0DXrGCCf z|wrWKK-cIIR!}q7S{h)tFFIlwL8$RE*13&-fQVU}4deqGi>k| z<_Eve2Ffy7ktgl9)r-br?{<+y2|H*@UlWXB9D|)_Xm__OhtGXc5>zGW&9uK10 z@V?m)OL>g}9|_bBP!avY!UWd{bl4mrh=!aZu=ykENkD**no&6v<8oXwK3v+o2yVS| z@C`P?`tlTfsCWf-VG7Om_9>Q<0oZDdYgdb~BsDsljB_U3xpX$5@+os-$Nu{0AKFOh zl|kwbR&;}PrqvJX_p*=4^@F(BOr4nV$R|H96e!v7%22+EcTHcLn#|-+Yz~z zpC2zGC_&E?Y4hL1SXUK#VJhGyqCoANzLUlg!3am^Br3P+1c>{E0(Y-vE1`0G@N_x; zeW+#jk8g%Q^TI{*h?J>DXe{NLT9 zFsFao6D-hv02^#->$Gmfw4FCFA3M+cF5d;T)aWRr75g7wp^$xNv-23#g`dXxbkI}l zn&jZyR6#VupGp7;=V%J?q);vZ6jtu8G4zy|40uB}#lRV=lJ-YHM=v{9Y4KZ+Q+SvF(+rw`Lz-EIWC}*m8Ga z5^E+ixWa7lYw7B4IP(1Wgn*W|l{iF?IOWz8KTSZlSXEz%nt1GdGqt>{BZrr|jUFQ1 z&e@|3BN0lL;9B6@rXHibfEF2lS+^nP?pTVgD$axTzcd=L8BwXrKuObm$1bDXMT=JFvB`@^29Z4m%|KC9;#N<+wUom%Oh>SIXoo}7;r!70LSYya`E!a{@;)7?w=B{u?|&-&K}xQSdneR6XxaNgYLzsG+t%VK$IZu%e%j+Gzl5E++Ct zRv2A5Cuhr{1 zeCzwU^a|wpm5nCOSDWbGv;422K#YJ-7L>SDHxrMUji_Q8{0&#OE8JI^D2tcfm{QdY z#rr9qvjNhRyFYPbwO3I2epmIvBtMFbn-@tY)l1(x%dFAd=6y84z}K1Fs2nP0<*o{K zh*KF89}e`PZa5J$;ncrq+v|m`)xW?4{f`8+bDcIe{^?CwgfDHGWzmh0APE>z*RE7` zer}ZC#q3(5yb%mTcH8wWu8=C-e#^{XOu=TKjChpFUx*K&+`D`izRa0u)tA!LC(_rp z;+EogbKTP~IJjJ-AMp#Ysu4gWjmV3H^iGJs%9r2lkCFd1A)PQRM|HU7NT0I}Et%e5 z_N#P#*?I-ny&nc)J7PaksU1fRpH1)hY~8_5wtAVTr`8&$gdPUz>iiAc>%KpIn=9Lvu%W*^6vg9WP0EI& zTp-9vRlFkrR5>DXo!IE@z3qw7QO9rY_*^gW^)j zlaDV(B>`trhNRY3M@(_|fm6w?J&cwqzrdaMDEbs;vgRMUN>PDgeZ9TVBctdoCh)C`vS{Amo_R23V(L8Bv#} z+Bsqbzu`BlHSsh$o2+DL?wYlrl3VEi9EGkkjGoO{)0?A9oF`E&L^9}n-4Rga;+!YH zOt2?{G$E~YW3~5~VOu4xGmLZ2_iV7%VsUkgwE2wd)9^V<81@IH0HPF&tFGP?e__yD z&mfHTa$<3g`(8kn%q&HiQL42_d-TCL@$$xX;-Wj&W~E>Bc(Aq+huwUG!z-$O38R5! z0GON*$Dr#&Y$RuW1G#VaxuDJZfi$bNbZN!7FpX{@1u5n8@~H#jB2zV~7|v|1|D6ZM zTTscBTGp;vY$8}x(3?yje_Y8?ISq0+nRfAM&w_qP@23)|uB#Vpla0fXJV8Kg^VVP^ zMya(aA*7)@7ghEv)APodUw=z#1aemWl`Vn;87@s(u5%yyI31GsNm4iIMj9TEjU^>) zm?@&GC^e^9j#-&%LpjN9opyg><}z$v6u2asLQ2%$u%rE=dD)eYesUSRpdvn@4=T*m zzz1~fi8)1_LN=v*5nin^@eCt@4?QuhD`7*szzb;d|kRnl_$dfos z*X8V`u#=xmXduE(Ap1pEpZj-hU?~{V!1V{-%>a$|+^>UO`JT)izQROl^$ld>IA9!H z`DYXrvtZe~5~JfY<%hLEFY4S3U)b9{pz7jfTzg4c?HtXyGRYdEZ55|FjbfI?tNW<080NBt47trWf)IgYw-a%bz*VE-bO3m>pZb^BA1@LH7J^Ct^vrUEVm=7>1hDaxCR zVSdN`wl6Vhj$g5d0))p9HI84{wW%7K|NKhuIXKxDrawnFcz1}>!Sg?dGnyH(IPS=B z7v8{Ix|eB)DlH*|kLU+k)8A%NYk1$|7vd=fhA}m1j;~(#AWYdwe!LNk6*DVQWKMc& zXH<;%m-ms2brlk^ZZB#|$4KHFp?mOOcZqHYj(>!`BBd)fS262V4;}eKEl3=}tsYBgf7>J->$2u)Q^uTE>*s$Q#!IcYpEGfAYhQP%-;VFBaJu6)CfqDa#G1O~ zVX|^RB3qR|^v^P(Nf7-r`F$?T=sc-`TG$`{g2J=>3R+WXJPxwpXiTm<>C!gvWmWO& z>4}@NXLPM5zU8g!iRKC&a#*jz#ceQ#Us2K@WWq{B+rJ3Ij7%n?B;+(W;(dM3p+293 zu&mzPWc_F8iWA3{54Tqvqv?f1l$1Y3abNmCR~uf5LRxEaAkF(tPWc(0|3^wfZpT2Z z<&YJR3Wg2Px3(Jgra6PSGptMPRjyWf@Q6h9O2~{2S+Dt~U z@+QVjHC`F4psn$jWCJLHUt&a(zh|jEWeQHSj7|$x`1lDqLDW|dFR*2Q3@LCt)?en? zldnoIdc-(RU;(L3{}4cd{FKS1ix$7c>0_3K5@iIw3fAEpOwgPhcH+FS+xCXFmA;gNn7lY zXG@PUGW^E>c<boS~vRss+~+{Q(UtTz1cNj3SuxSX<)JbtMR;p@IfC zYFK)T6F_{Sh?dga_gzdQQNB5qp_$y?8d*gV0lX7St6>1KBIT(78hNmI+c21 z+!078r8m6%gIT9_d@RmT{HS9?!gGXl_4cH8N7Q8|klfhooG#3_yo4-6K5zn^_+@w= zkmn^Sq`}7uxjf|R==Vk! zUK2n%h@x<`Opy{3E=&XVzSZiN{u8w(DWFW1qIjhs@U`$zbz)Kl8tz5s-N0J}7N0nm z6Pf!)iqd*UC)a!;sFYdKie@J(yto>j8|8k%ooqg*Bi&pOCCT1}&WWyyCjfE(>=2jh zh*qPz)WWHseuO>cmGs+87}kpIc1k~|!AwEb;JTy;3Vc9!@`*M^&@=(i!i!#;)HcCG zt4rl2pwRfOOd_Sp-EEqTdLWFGhof;_}=0p6dUprwA8s|7Qn?CX#B#^Nuy zy>xb`Tn9fz21smBpxuv}H;USy;xBQcDl%%~5y?l_NeM8}9V8QldS-sGlyUqM=76rWBVy3^ ze2iuzkZZC;ha2(QM`jqPD>t$Qj5zyNft7X|}9+EtBAvS)OqEiUp41(oK^YC|vU`+C@HGHW%0e;Ym@i-cCb2!ZV< z@0uFN(=6`{YT!Gb)}eta^Ws9nTvUSn_Vc5slKWtYsWXN0mv*e@k+%D=-%@D#u%jN=2 zW5i$55|<6EjivC7xJ+4zAzSEFe`J+~5I&9{!ctyJZI(jiHze~s;?^Iu88w=hgY!|I z-e)&i?=|Q{+~gGtiT>67hOi=Va!h=0wY*bL+9G4Q=XPlttG_m@AH*lGOm0f+ztvpP zc*Q2`TBQJd2sq*w-w!SSDM`wz7yf}bs$Y{yE~#D>J}AUn1Dn6Fxw8&kCZVgYtP9w1 z9#50EmdmLO%Gmmt_{<&-lJun8H%a>Wv>jxcsZqL?VWUvv=_8V0XkqbMOTzV%rpmej z(ezc&!suq0puqh*%o~MRQpqm0=+ITblv~(2f4H2&-;k_{;x%%+J~fh_J&OWa&1n9g z``_me_o13!Z;|>?+U%>?U2HnwTT7&JC)%2;=$~;a+6ckTzLZ-+bv}is*+a_60{5XB zpdRFF=!t(#X=Q>=YKr0ztX zWi=?&cwzakM}v2SIL+hb0{ft7CzyFxsq?(E7%vf`NSH7q@R=ZDV-eu1ODXN%TIyk1 z3eUHFcl=F^?O+N@6ncVyV*?vT7NcN(yzu)6OHw94>Uax+OyPyphaE5fyy<^FRG)5y zCVH)@xr%*7=aEP2elLTf`bAn01#fU8=6n~|9AgNQvCb123vA4Y=?sNUp{Xx$W?fX# zJ-gjSDjy>|Afwm}q1BiM3RNBu-ugtTcFSrBERmABieM914E?+bgKAxm0(P9mU(n?^ zrC0G^UGs7H?V$_o3c{`zRW(;|Yo|i$XOHq0OZXkyo9{lqmuB^+fX-ldt+|?cB3RRp zUL}C+?{s$&Jb(xF#7qaxIb@wD5qcB;C@iN?5VOARVGeBjHWWW5aO!Zspc&T1)B|ur zy?`O%8sisd;*aR_r`;Fs3wVawiTIU(wujV>3ROrY19Ie$ZZGo**Lrzd(Q15*6(juv zL%GTyZYR^KLcf)IROWm#vK;FcrQaFt9vr{Pz;? zC!@)xfadFaz+D;CL^%A6D-E50u}d)_POKUqtkOLYa@vB0PR2d};#k{VH1_^Ku<_z4 zf-?s;6<=9U{L*y6+d+iD;qrQxkEHT*bn5}Rsxy-C<57MEm?9!UL`9#so^k&RYHeAo z$@_QKrVdd%4cGzV>&4XOVcspsDoL*!0+flLoaICEFH+5t!Ctysz3=gRG^8pwFugTF}2yqJX^6wYFB^L(!Hi<_KtjlrN;=x;I41kRQEa|q? zrH3<{CTkwrXzb>!-;15ZeJ_QPFj3~481_ty(C}T-CTH~U#92M}DPqfX@M1OMGvY2j z^=@v}*|;ow`ikEZ${O-1xO4OK*l@I6QQkhbVf1+hD}~nF4Qj4Q>~8;FdYXn|&iQ+@ zu&Ncd*U%qV|EJir^s2p`{PrUjPh+)0rt{c2FjYJAaY8Y$XeNFXkyE*cN!bP!Jak9w zmxsGvtZk1<57-`$+W;*MhUFrptB>wRV-3;L?2jQFQ6=;1_g+>15hOqbgjlhf`Qzr< zhIN7Zu4~4SD5qEKwYp=^iCt}Pg)^YZv?i?o0SYY7cRhd$WE&7_-A!^akerg}0y-I; zzBnGqNaql${60)2Oa5oad?THa&EbxhrB(J*HMxNU{6p-&F<}SB+RN!w12yT8zo~aR zdA+4Zvqc3<(|8O=othTsjs#|gUu+&$SFtjcST@d95ElUON%l@6>PXv!V{u}F-%gt} zzmdrlv6ElG13yCbn|zNGrO9q<)$@GzP113PZz^SQ8azv&y9HiUKV_QEJ@Al%L;H}5N zw2`vUPs2h0lN0!m#&&U%C|DW8(g{N`(&TQ_^-)o8=TGcZ&sV#?cfbeWRrq8-+VIgn z%TD~)MivK9^y6i5aNCcNObbt@r2lq>QH9k;v=rfa2Z%w9ob!3ifyBHT0B4J}h65dn zpfo>8-7y6SNuO>T-H+HR0(#Lqqj`N7)+80>Hs&aG)AtH;4$Bh@ewX-jlD-T+0D4owgyNndG*5%}gFGYFt={S|Dd?0m%b zDE0}?sZItlI4wDIEAmkG)8tr!q>Wz?R?fo)H>UZE45S0#-w=I4SvRA?cNwE~^|p14 zCJK%;&;_I6@%S=LT69qLM%ooZ0=v9u;W}aOG?e9@ii!81} zln@s6>N6}6f|ev{0?(XF5l}Qn%bKSb*kYKH7Dg_F9rPsd6+FjXz4BK<)UsN;EgB+tHp*k! znxc@U!R^BSpN|b^ud|e_+bd6-ptzRha?6427tjY1UK$fBnB`5$#4IR8JP&nzB%kBU z9BNq5{}h`rN@+393DS5BXZ^w)Li1-c2*H(|U|*jTo#@=(@P#`rkmcxELNDtElkCQ? z7qAk!QPT=`7)qCOHB43>N{a5M%9xu}`kkD$Z?A+(n6>2#RGMi$SxwEpWWyx1v#78qd3+MCQTj^Mk|oF~Ko<~~D2stnt00 zIp6ZqD$0Z=gV^(ZD}PyONjow0K2AN20T7GqMtv5hn5AY=4VI@q3_sHkh#G75m#+D% z(!3o3R^=bDNo#MC<0AmwGd4TZ25ff*#TS)TV|rcHm}$Si=9`}H*GHsYCf-%$N0cD= zjV`>T5O;6>J4QjV*~GNC*ms4in@U<%d!r7Q28Ms1)FM8kgSV(dBeUimR8d$HTfz+< zAb5Jg-{FOR7yfrRD*wSMo=ys24ePv6qyt&!X6!lV2#dk+s}pj;crjVWTT4@gqy zgUr&7nz)++wh+$!AB1K_f)4G4*+=VvY3(Sg(ipZ{dek;tgEtI|idnt#N*}A7H+|kK zCjjf~cNV|M3iNXJZ6Lu#9VlcyOe6bGv5}E2y1{-afld@_YTN9GJ%{k3beG3!VcRlJVcl^xyih_f39+e@ECL6@+x8N z6d3_Ij)f-~Dr=+Bm)JosbpZ#yn?{(5Ap@gXT*)w~f?4q6uV~x^$R0u*Tt34+ipn zB)AFMe;rxLT6ru-15W<=GPr^Wl3PO7M33_ig%>aKHe)|b72&CqawGheaMzy*Jj`7X z$7UzI6b_wduuy{C@J9len#)q%R|-Jf>Pz+%R0#$pHVRwp7T+LKE)8AvV_OBjQ2-)$kbw+-|r<}b~5=f|yeBBkI=G{_(ugpC;Nwpz0 zrFevE{LgZe1gwWPMKvgygQbj%vgzz^sjhIbI>1#VAeVjh?GNTd1<3I(m5}cele!wa zHt7?#X;W~5mB)u~f<8Cv`t7H`S#rcsYvuUKonK4&^`BF3s+E!d7S!_63zNLdeul+I zzNedaG|XbR*J#V|9Vv-42-_-jg4JzBw!vKN!o`E*`~=UD2B)ds<|{64!!tEcxD(K% zH-n41s6>w~iN60?ve)p@8DW)oLmmzHu^7ap$K|kMF}qPCb%u?0L=vfJbewW64_(XB z&jy{X(oC^9gJLC1C0(DPI&%HO213%ywZWI&$GZ>+)+s#;{5Jv)pbNni-F@yR@vU`L zQK}~CMBe)K=S}oRsR*cg!Pt)@o%nT5M-r@yZ&O+4y7}dAnx&>dJDTQ=3POBJtF2uLJ@M_Wxv*#1~4AJKmKkxBj7!E zKitUB>WaYa9NDw-K>3H0SEf1$PD2P8vHxahD(SF3Uu^lu={G&4HQUgV}H z`dJ6TNu+8TWvE!c@Su9k!kKGy^M$ zBw6yqt}zT!WS8<^mdZ3N-bK- z9TB7bxL$eS_}3dOx~*ix+>-9GSr`gwvTT*xHb6!V3z(b4 za-_ZcF7WgxPulnd{R!a^gombM!=~hwocfcB5D(`~nH6n_nSYGtS21m?+Z_f(N(QG(MGwO)xN+;aO; zT!`t{86LR~m5~LUf`y)E}M{{ zfCaiske?w@$fBQqK5;YIbJGTVvi;`{He0+;a zsjlC>hbIOQV@A(+2cM8GH{^I)s%Zgy5I%zp+4^YABOtg)4MHBY)D3rfxu|#QZ zL)wM2almkQO|{2c?*Aq)EBCjtLHnke8)ry`A1e~AIj1=mb}*8JizU<~jw^A^Pt-0} zEbq)*i(Ibt2;krbTf`Xt!&+0wrS2!Rs9zt5P*SB{7S#nF@ftYk?q>xWlWcob9ae@UGSI|U5+O7%X&P`^9pzgJ zKV=3W&eOb)noo3>!3P3qke=qt_oxpxIO3Vr209ja*^OcGv6mnu{bGS(lV5dIdaEB) z2-PZx-!4$E$}PYZQFZf@0-od?5JLTMjvG|a?S(&J!9H*ZQ}Uu5EQV#}w${JCNwiCh zLoH{vRO<2H29yc`{;4J3evLwJdhkT06JDV0+!p5n3B>qlqC1a`*kk?@5C~_gltm(3 z+9y5)rrg`_N>O}Sjgr-J{5&u4M(QeKN^*=-V8i#c2#w>glAzxwA9+jifWd;*9M;gs z)(kPB7w2|h|7>L$!Jp3$&4m34zQ<&)K?@7%k|T4#99jF}iad6CG+K?OFgn6+l|CHP z@Si(dkA(_^1Z4BC=CZ1V0G?zEcdZ=qqsMX9(Xp<&GsJYQ$Ca)fb2RTE=@+h0GU&if z^Yl2~)n91M!7@RR&PaiEqR49k=IR73sKXpP-U59~Ee8V7B z!g-CPUdATYdJf#JuG@`+3iSP(mdy~=mEVXNIcrLvX#C_N?K}X=!qzK%lyfP^OGK%= zIL1>(0P#38;wMAhT0jG#dRVixzfiXaY+H%*w1wchx*R`iDPB7@)Z%944nVD;PL>z;06MZ|4TPAgg>_s(pm;|hPD z?-JP*&4prG1=NSl(pcL^^37rVnaF~nj!+PPfE(ma>i(?lV|$@>q@F4#1yn|)D%mhB zr5)HoiCgZP4ZtaMD@bR@8D+m<&~vRh>Hh8XswWjlz*0XiWop@LJSBU$7OYW&spe>O z?TtBiRRY5U#?H_HEM;|j{J!4>9uvWAst)sv=}cZ&+2$|_6C>6z(k*J#3Xzr3U;Ho=Kf#dU$15WUQ%X6WVr-tW*rXp>C+++Yu+RSa|#Gp^c-f?%q*LEYNKL6xy3A{P1qz>paRnnlZTO_m_ytd99S-JF6=YH)6Y35V+{@@!tgz`$WX1W)QVh5x@}qOpm# z^LKE2f28b#31Y02{m>R$JMBl){X0th%vF%neYa*lc79^g>I`dYbCsJND?i>Woh%U1 zAM*Y%Kki^kXGeo=_P@B(HT4(zff?fvL{$5T_@!eMq>HnvEA~XujP!Xx$c8y=vCqmh zjKq)Fj=sJwMD^nDVfgtLdu-!My%-RVm0E(J_RA`1C`PYRwrbxyzt2F~5~vS_L+?&1 zMU?60Tt102$n9&0@vzzYu)E%T1}=J6^78Z ziRo9Uv~#HTNwa(h_O=DsHqoH=#47-&2OhVHmDCI@6?~hcZJysJoDfO+mm5JQg!gY6 zKJ;F4f97pc@#@Ro5<92}qEec5kISoqfbgy0DC?3)zVf=1{42XrEB#q0O8bq{sgL#r z=RC0L$@LRiNba0Je!Lr$0USTk#GU(KDK9s!u2Ka_P`mQ7w8sin@zZ@VWNF7M*d--| z=FS`w2}^KUKHy%fRq{@ZdMXbYQEH^f@DbO~GkD{BG~z0{vJ5M17R26oxIQs%hVRFU zq5r)(GPLorrQ$CZ+ zBq%ba#!bXz&mZto--GDp8Aj=Wv~Wte29(mYOY#sw9R0AQCV~1L+!~fB%XEAvN&~~M zA{NjgGD)!p^9`F=wrF(o15>oB76{TA67y(&g_L!$^3;z17Z*ywF+)u6zb$2p+&zg? zb=x$mN{zq&)wQ`Bqw`wsrmq|6&79dGSfh~>9(ySo05|YKFNheKfWI-=Rfe*q;LB`I zEKdTDb{|c|l4^8>L=^W8vRyG6{pO7Dxk_t2{Y!1pKaj|so{wa$<1(zEqUBCsv?=+g zzQdfAmaF#Mii7gs8$yMx%0J4r@>2$VeA-U%sgT~A`kbzl&=~WI;7fmgDo*?+axdcC z`H}fP(3+?jL{{M0J*((6EE*5eqnSPscVp=zYlDmS4|?7Yd{TzF@r<8PMAdxudE zFM(6E8U;hdL`ZnHpA>1)3;sRHKxM>!Xko!&DITq&oO1RMr9;pOy?6)U1oXfTnlMJj zZ#euX`a4j^1)c&#=qIBr>3G-=$1f4P(2i4yn1Ji^UEcGuauD~eamgNP{CY}L!>VAj zlQcHrvUV5O(kY9%38_q$<`H8bjb2&9v z#Y`#zB%E&ycyveP_&?q+$hfySYqe5T9>3}MxA<^HK+ZS!%RyOERJw@t7IQN0n!LZV z)qU1-zIvjvkVnEf_7Y?tS~TWJUd9wR>IHGR`np6J4qEGjm{M1{_#Perj|8ygI)QKB zoqmSn`q)5GMJ+U{ULgXBu~jeS{nFZB$G}OTk7;RFDEF@ZPg3Xy&+OA}&6GCAm$NxL z2lx?oKi2|~at;`!m=WkBx%5oLUllt7xfwQ zE9%PoB@9*8gT&sm#2S83t4v_zFL%)|dyv`b>s7K1o9(BnSKFzzoDy-F!M&d{Mb$>D z&k(6RhQQXtfHrl)JlfDis+#UgD_cd&yGx+aZ@19PML@Ni?JIIgh9mlXru|*eN|ln5 z{N#3L!_D^}g2{0t__(esmNWnNa~bqc(g_GYhwVO$zYZe(ntbK`%H^tm~mJyg89gd2je@Ti_(7d&_tzYig5y}@G z-2JiU?MAc%QYem-^7t(;!4lkQcRO|d2{V9J?iISf->iWZMm6$skzhDA@!GasjQD-$ z+jfGwCL)fDp^FoMf|?bc0=pQ5J^6t7`Y!QmE5*Rtl3XW0csclyw?tQ=hz{XBIDb>F zWwnc@2NfYd=7*5Eo>KPDUEpf9U?&fw`A23rzwtNVUm=8iZrs!)?^^UBWn$!<9XggG zsC$jyjh%sl1S)q-Z~Ju-wUOPA4t-!ap=tAzs`p)tb2xasxy$OWjcC3%Xwj+pJ*}nQ zEeM|7kiNlHjHuynhSjnoMO{QWzJRB7*<3H;OS8g(hr9A3+TJVX4_TDPG+GLBxXF36 zG5|zN&8_hr;=F_7K3NFO3=I6Jyeo22gLN74d!a5s@(lPrg{O#^(J*4o0H27d^@(sI z*a~u;+m_n1(&;+Pqv+Ua#NuB-Bw6+7{lS0B@bM&YD3AKK$wQCPY^nZ94sX=W5LF#i zy+mOwb7y-jkr+JerEM7#0UHxBqwECgQTs%x@C7aEUqPmLy#a9gQ-@AJG(7XK$*uA# z2yg3$T50n9KjYQx>4U)#xt=3*J{HVmAxv|hkO5&%w0AFPi%{tDC4Ju5;B5n(wJ>qk=i$MYAGtBuG!p6nI$P-pNaZx&3R2^4S$olnpqC>EL7Mv%fd(Qn8Lq8V~*78Vleb zBnjGjqtC-vZnw+&@R;u1HjrljRPHf>t5Jy#ds)(){F881y)mTaYOu#7ih2wnv14`e zKDPHvt)k7nj4blcTsb$nmrGIH&mgq!!)Ayum+|q`@k~-iCOFz*&^<+xQ32zDKtJH- zK?)i7oVTL7P8iSP#p9h|C!(fMwv?hotJmX)z@?Ugo|K`)-rJ$PsdJ~*^tm@!5g-u% zguYLFkP+Kig1-*oY7D~ zemhDi8mIIbwiTdi_@{_OGjoQTf9_F_u_QGQJyE?^<=1o3dEb-*j2f-bQ18s77|Vq~ z5i8QdC#0{H&Y5;tm5Y7X|MQTvKt4>P+Pr^6k#v=hR zbkNM|?h}k8T{K);^lK zyD)>eaqr%|;xCEeL>*74Mr?l&GZ-Bj9a*F0v(l8u;YTY!i*dpSB4Pw&AB-Tm%3z>8 z5JTuCLt3|-20P*ELWjo#PhrG;`@Zwol6#slBIS=h7l_$5hl)2#c+vfjQdl56$rBAj z^~!IRx;K)gTq}{AkV7JdAy^~m*#WGSP0%MowQa5rhpGJIMUZQ}j;HY@A*MAk$I z3h$;~xb2avZB{88&}t+F)u`~ZomF9FCbP4knzx(WqhZn0!0Q&+iA`g^#N+YQ&G4i_ z&`#BVq@Zt2egRsfomgIxh0v*u!?AUcrxm}Dv&2NpOvpLTo^M3zyoKdA(*Kw9xeYW! z|5xJehw#7Ve6ldJu^X~5v2$@4vl_FpG8-Ck{HyS0;o#;nWn*JAWnnikGBn{dH2uGF zK24T9c^7wypzfxtVVnp?w!%}iY^h08$N1XKjvMO8IbJ`;ir|Xl9eY(B)3+SQ$y3*& zPUPAXt-=hSlHb0*HfMfjS5J9&Zdc#``|rnBoxv;)6synlS@m3UFPrbd8g-d%@>Aq% zi`lhVWeUqaYfAFYN)5`qJOfokPMmeo;lDBu=4N)M|LB(9`I}<|KozC$1Wno#xZKpd z2Ez?qW^IptVR-vLBh6+xVWynrO5FyF7Sgz56$UIOjU?Crb;bj5>!s(i z?0besLNOxe@%`D({Xq5C`N92-Fd3VA;%8T#U%Wg0Baw!z$u*>1xPhp1#&;)@F+%c8 z6XPYEzyxzB9S259x^5o~u`4IT)2gBC-?r$e0?3W-JE)zH(;cod_n38Y&~9@?+>7u) zUN_dLa1@a3FDPNB(r8pQOm?NKl}(7VEE)SxaCCkt)AY6cs?5ulERgqQ|GDzvY!hzu z!i`<<0W?)=cARg5{Ov0iDQvd2eJz%fY45nuN{iA`YesT? z6UdjtPDG(Uc?Xrq@|5F-Hh+5H>5 z<*;v%K!x0tUT_v9Z)Of}QW8T6o#Tr*-mCai&!8wu9fUjZlZ!rcO<_qaLU+bxhW!UI zJq&WUOBv~eD)p_(a#NaM`oogI#aFg zh-_cqrP+CD=4@2)^{*uj26r?ffvEi=_5#EIF_b;J^a1@41^#iLmxghw)&+q7E56Rt z)~G2l4$ncZWu`rS{~{?FAk6b$BvryAaCHl_5TwTg`T%v%D|A>QIbfvJ`F#?gKB3!4 z4|L%Bw(k^gD39d()Ai;L&HK0N!Ea2>eX1D7CyFU(!81j;lgp2Kj_tnzl2Oe4JqJN& zG80uXwr!|>Nh`wROH4KdEwXYHfLT}0 z`dD`H!=sDy?mGP0`w076@c0ie`?s&iKnL&WieUqzSO%JYjbhRJ0#|(97*hfcU$nhs zkTiL@;3r)_dyY#B={1lU;`3F?*}rQ!V*5a5&I4*GO}#>Z(HEY2PpQ}Hd+@WV>sj>2 z*AjWi7l&b$Ww%ornzylb1e2p+Zlodo7}1=9?9WDTcziZ^iFurdhE(c2UVqU!r%wPU zXD=7V@n+LB2^9J!vk+8i8^D14t()o&ZE-1mQ%Vizp*?Pkx1|3pfKyDn+}BSlr%4LDNeUw9NNaE^Ga|S+{l-0EprNDP;pz=HF5p*H-EqEE`r5n z5mc<^knwJ#rCFy%Ve|L@@O4g2fpZ`3>%u&FzI zK-AkSkD!?kJ}q7=fM99T4y9pA{Oa3gp6z0>gaDT(tB7{^LP(5=@6o$+kTsL39nvM{j^H4xLvU@smzI=%TIQ-HP@)CfRray zn?{Gm`HpUnQ`@I7t{9TJ2}(e&MKgR{pvcx%@@X{;?kLg#nd={+mbnKOMro7*$B#yu z!@AQV$Au(~E|EzmIpaDVF<=xwF?R*pXGtHx*f{v4S$TZv3d(1hd>!#y{#>^4%A*9b z!|l17@J3NwgC{g;U8Y34PMZhDhjYodAjnaB`T$$Znnq4y=r~-wmDNw;&M8QPs1J)` zp_!4YUd)&rzP^_;0y5x=fF3CF82r@8c#Ud@3Oxh*q2B3xi-@X6rn~g1Q1VQWwXbY^ zsu}xW3`8;lsnSyD!LYVDbz`z-uf3jul+yV$(C6L7L2sU~hcr%=(Q-bI_VlVmsP_k5 zRoH3|89yl8KX*_Q!(EFSz&&o9zxV5^Nr{yisyo>25El&oI<|=5S#S{P>JMGXszSx^ znK+l_?8a12Gui$y18t@F z+gyjSX!a|i=5p(f@Vd^&vr z-9sGK)iO&p=XQsK7(lTcr^I|a8Vf%=(F$wQvi4hMGejDYXuY~}a=0IY`Wgd-y-fFbJd2}e(i$hz zl_uy%UKS|EnZP?{LQ(}J-K^UEVt=HdKkDi5_%FN~S#0uN{f@+gg?2+uf!%!wgisaJ#D&MI< z5kJGdI=45hg(=VtNgOaE$CRzySCJ5RN1lx zF(;4dak_uZat_|3U!dZLnY$W^iM6xf*$6sXhnZ5=O{`2h(#%~azWB_A!C-ntk~TWn zwx+Rg=47C@;1b^Uh|9Q{LeN&8_$LKHt)^)DxlJP2io8iMu_Zrz5{ z-wR^*x|A=Gkq`RA*7)kpq7i?=sVmO>@B~ARWyEq&J;!{`*2C)%d7Ia$6>+J zNh1?}0bxbTS-oOAvJaJES3ivNA*{dh|HJmbWyU9;6#px1pV^R!mQi1io`qST*^rs# z*KEy1Z$!(eM@P$GK(B91Z=lC&$VAUZ_rHqpqf7&VBCCzy2CI$gG3$COi+Z}XW~+_# z$uZ{Qbjy{)?URd+WtrBG?klYePH7xKfVUqXv@u&NTFI$(6y!oQh+49qkM1G)hzAS( z94Xdwlgaxpg9Mj^IJLAYa>r{iXtmFaKfVFI5d_>ms7P8-uK4t1KN+j~OZo)Xv%#k& z<}BXe>?9{epw5eoriG||>bxGigM{61qXHzvqtg3k3Vm5+EmV7DF75H%xaSNkZ+zG|N8Ln|D=3sb=zAb|I(q%^C0yQ%mU)_6;%My2;jA1ZCjRSz5_0cC~m z7d!H^FKQg{SrdI=vJt^R89rCmSVBcRXcNf2rmX8S>AyXc3__Vqb3R0>pxAPW2*qun za{+L}NcJD0c!yGy&^>xUtm(?nFPUA49szC|?3c`L zw)sn-&J^m$sgyAn!ve`7gL5t$C%R*phq3CL>(zbWz7~G#ZdQyx{~wwC5Q#MdY{j5)+M| zt<|=lkr$_BBt0Sk)*#!lu`CxkkCDsWFNRcYloeF0#w!fEW?=opWW{E z%iPKl6V^V!F{}d?CEHzY(F3B+i{td2yDy=BOo{}=Llo8Z#b)Y;h1?ZDQDz&adKx-Q zzFX9}wEG6|X0XT4HOGTfd1`AY97j;aeT6do8heY>JSxNsN33Gqvh#$NV8rz zvb|-pU*@F(4k@%fd7DWd>xFsN2L%PR%8?^_oT8YagRM75^6t1>Mj)02LCvdp5^;7q zo)nlEVnLynl#1(xNmm@Cj5bOcH?}hPcMUm~itNflN4z*qMB_^U4+~w=+F&az8<+Q)EF#(9B;zP^yJ11P>9U#r@=3pHCQ9iYA7N<` zlXUOf{}0G}6>1G`f9dHM7w28l&s!)V&F-x1cTqn5@?K#_TGhi;r4I!ei6WB>Z@m_# zEN-Jv`OFf##!I|JHr`%6PPt(Dc_dIExrsMu#G771Wvng5OqJW>^P6Igd>z)ZKTK@M z0W%$ct3J*#n866w8kP4U^R(E_i^c*GUm!bScZnlME>Rk`;HY|KC?C9@^#>iusK6d? zGzmViTRFZlZh1_tM7tY!v(M)wgq>NmHh5ubs=HaiP!aC%DZ6LS{f)^G`vpwK_YgTrs|ZquAmi}P zk)vf+$11D8`jS$N9)y7p9&?nI60l*9pxKgbwMLYgnKj4ktoBbW0&YKdsf=uo6AAPx zkG+W7{W2d}x6KgRkn-#Fy(*b6C#XP$vPkz~4ZAIMG`?(w`Z|Z30+hgg>r|OkVlvsxK$IMYke0BFtS176?1{T)4RtW%TNc@l&ta#d9%}19rC2 zuS0zk>GjXnjl1fH_B|*FWHZgtE?v51}~Dn{|(hpeQCsUS_9RdJzsm8YLXaZCdG@B!0Bp#k86yenwQ95?1JYpPPLQ( zbu|pq_*Ngx@^;4^Ps{NS9m*{sHGAw=8A@zZ*ARvK_ML6n=3!g zg`#-h|1*wej7tkFAo_lPWMg7Hg>q2@M+Ev{ z>)V&}ACBK>PRhl^@^_K&LcxU!TlSI)jcwyugg?0Hk6GK9VtnFMK=4@MeFFrx$Q+k5 zFHQ!sAwbNtc@KqL?&=NH2i%4pM@fHKHmZvjjIA%~Ntksjtnl?!{8appe ze+BSDXwG$~L@MtNhsis{*Z{u!v4?NzYd^jguyGjQ&pc#V)P>-7dmS)J7Tn2V<@{fo z3W5#NhCWuWSH{OVlGnOgw1T<+x!7pA7zYm%tJT!ga+`FZugi1+?2 z+%d2`Xw?WL7!~euxUsHU!;-fXb1j!m16p6s;q)=q#g9}k;f&-PKHAfvV?Lf9bV=bxjzMo(WNfB#uOm%KM#D4a$(mlei zff~r9ufkwbXVk*u&imAyC*UzI$>K0d<+lLOw%zilr{u&vPv4%jNT)gm`?Slw*Q!M5 z)0aDzu&bf@rhWO7Ihg!S;y?Fz zCUggb!jd(bm~4I;4nBN3l4-2K%v%-rX~rC=2?Hb(T(9)P8i}BL>k+3q_?+MXc7+lO z@bG*$LRyt{tk;^>>o*~{pQMeoanEn*b~*Yd4HU zA`j}ut)|&HxBN94vqz;b7wrMfU}!tMFXE@SkS5Y(olGoX34c3}0hMJ6U=4N%d1={Ol*I?f9Q?=m3&-sCS2){4oAOl%b7>>kXu$19yG}R-9LmcmcX@bwz-S!!&+9oz zonn`W%Ly|%E)Pz=vJRS`5(eFQH=XXKE*IO1kN$L`!V za$8{cC;~4dN*;V)h}<0vO)u-V`Y%O*_VaMpB0gCYyg2Lmu@NY4#f!lZjwqmLF{1CMe*27kh1A zeN((VwSZa3R7)C&aYWmjO@s?17ykAFHg?lyvgciTOB1I5Am{Zxrg-jnlqFWXin3Yw z>jvDq-}a-QVeG|oKD#xb6e!>o{Hq0e|EcY9t#v6)(T(vbmC-B(OY1~7YKi>%eQ`{% zEvOs&yzfm%3~Cw=;+>fndydzD>fu5`TRhd2BM)b6sw)T{(;Ym#z(~B%H8+)o5F7@` zwsB_aV;`ILuKun?$FM4ZPG8HBrovIk#h)KQAq(7^i{F^vd`y`?1|~FP2{vIhrmFhh zk2-8zuhiDFhtqYfs*Jr#WYsk*lNbUiSph1%q-&KNDyoVnEupZIlpyg7z^PCe`*IGl zW(?XXFGVWD%i+?Hk+zvMr7r(We_8@ycd4VkI4j<$sIu6GR0?Y-P#N+wwTSZK%rzl2 zsBtHV4aOfv2+v&droy4UHQeyY?`Rw^Sq^z_jf7~*Kt#AKH%_?8wH|W-%zG7`a`<3qkt2UdqJn;Ve z@)G4j+ds}7;I~#03ib+1x>m&dc)4T5k>nQ`*^;WJ=|E-y9p4n&id4-3&LrZSl9d&2 zh9eI*Par{*zh1mtZpq&IvqsWA9Fz~D#0Ai-djE%Dqe9}gt;aM1>5~rPE|9LwPeDT0 z9#~k7b(o`Q!Y&{Wd~N9SFvA{xv$OQC9xU#NO7Kv_>>fM-G>I4>(P`zWCQIzM&*i86 zba&hE-wa$}1Q%#?qGsy_uTyYQ#U@6zfE+mD5F3R|ww#;C70>#CJRR>434imK~kTQ@CZUcmaFF z$|Ls8YGg{nO?;blhucA>i&OlA8L-`8nDi?tC?m@Uj?M#qk`1NK<WJi@bW1JG+4YcWGXR{=%YY7t~o9mFD2A@ew}az znj3NfJna#|Q4J_r3Uz&-d37lF`^HuP?@2#fWr-FjF>;0~hv-MeX&*`HJWeBt(fLZM zfUfK0wS0b5kAMcSKh)fLyuZS)cigo2i9KDw zg2rF$YAgw%l{CIaDi%0FF%E|TT`)WN1*b3^Ua!$$-ng_GLfp6ZWQax^AsGH(MS8L& zXtU(Fya*-HBv5e?+LOj(f&@ciVuCzuJ|tgG)-6vEuP;+@A&U>Co8$KU)4%p!RlN>&CqMUr9MDa zSE5rid+aVbgO54{zAq<~KotQC@7QmV3YUaJ^&wWK@4=>)HEy7|MA(=B^Qz~DK7+8x zJ-)~Yn6T*P4^fok&PsuVEJ$6H#E{t`ggQsokiC+^P_6vB>YSXcv5So6dL(IVn9mhI;AU2~s;4n(tV z~O7=}5&DqjGgn>?-BF|7S!Rqij&oB7Q`ED<|&ibJx+ zlgG(CVwo{Z!r4XB1iURnkcR+5od2Qtf{!u-9iN%qCWCsf**Es#8$|GFa!RM9@K+> zX+-`Jiy}u&&&5k{Ue9&}U|hnE6%mh}IE<~iZXA+BRC>(Yupo@UsKeS?-lbSIzF`kf zHqB_5B=#*t$`8l~b%NU(2RWSccVUQ<5+26jBBYC=zznBW@Oe=+>@4kE5AqPuH9ckZ zg^`#(icyf>4CnBf5}{FPSHOPs@mbGM$4Y>CVh_ayh>%BMc?t~EIn+(rT=4{16v-@Y z*tf{!k@TN%A}x(y2y=tpI<=1&mLuz`|5ltBN_-|>m{+yZ_#(!b7x==9y7!YalQwdWGu@E$_(=IL^87| z4Y>LTe3g6U_Y>($Hn!EPw)Chdrk>69yY&6Hcpz^^2X=oml3Go{iJN?^a|+o;+ZC8C zxiCceT^iwSys|Qg{e-@y$AR{ruADAFy$&>C%$(jDD^#Hqy1YM+8)cuwF`$aa@7ck9 zOFs&cU(m=VEMC682%3L?=Sl39WyC#_Xmv(D(T@5T7wfhUJ6;@qPs37ewaSf) z5?j-rX1Ud#bqC5+!^UIBs_=ixFsZW-O$ngFpcyAvNy>ml!mhaw*v6>PPF*bALeDFg zP&#@yGxRh)EFrNG5b-u`M51>4cq^#hRGhboeA-1~L_)MHyaxfahAf_0|cR zWD|g;i9U3u1UbxO<{=pi+1r!t^}tEzDE_vWz^aVK{_N{6#`jYveg3$rk$AjkgYy;` zQqkO_y#Vg!Jg}O{NEQc2JQXjwx;17l>jR6=?IVB0!XhE%S zO*XPX451FVp0IDpUU1WSTD%+d>`hFVl^mM4q(aKm0_I671R53a@d?Ho1@g&LYPHo! z@#r;+UZe647)}nXhRSP;8-x@h0=k>~cb3GIyr-4wwwefbIM@DGs7AdRF&hiTt5uP? z-@^Y1*7QO?^8&1?~d42NP>!A2!G>DAEl^f>Q1bLv5 zg(HjM9^)@QQUu4u8k#_b+eMg5W6q2g-ykLp$fAA0{zPuxlbfKzNZ;<8bwwV3M^m3|LrbfybZppKtPCw*7R zAbJBy-WlL1K}9Xkn-WD+i@6&R?(qX}9#1w*%iPoxwdv7+A=6ZCzA#q(KzlciBe%;j zZ(}c+_2;s24jv9`DEYweWxfM&jYkqk&FeYUaIvL)k7P)t0MEVewM=vwLjnAK$vc2|9F{O}QTv+Rd)Z$B?T-oMFgVh4)%)B=*O42Ol4s_*^_4Usb&$2EM= zKz0(_x#`PTY>|0;2X6oz765*7FnBL(o|)Xm3S7zdxAU!KZN+TlZ<6=Wp$5rf%|${sx1$}g20&Mn!MvQ|0m zAT@>drvhD}={xnXT}9i3CXH!78%A|XW0Ox{?M_77hh@O{A-mQL!Px$Rz3&&mmlOC0 z$}b_|*!JA~JWw>3i+=hYAjFz+z@A&3+fS>k36+DM-?_p(qftcRpPk`pwy7rme7!S) zIkMSa^I0*svO0%2`V;lEGHgyXRFsBSuQRhf02Mr$19ZN0j)TwZ>PId@3Y7?A6r{cp zMn@i~kCj87I|FI@trr4EWmhYAIn>-J1fn(FC?ETH+MNhX(%0`KM1-T-q)vtWm9r zkEgJ?j1t~k#P6W)yRc2msSmrw*XMk!F}xMb7;h(5&A5&94p%wTD|89QRHCmgG9 z04(CeB~=xCM^Fes%V+!f;=aO+Ue@eH!!!YQJBo5Zg`xsO))QW$+tX1SNygoCd_ikc zkq-2vK4?ZLwrBeQ=vVQD*e3m2yS)HvxJM0&&dEPy3J-e*3&!nvaqftI!kOQWmAXC!0%0bvia9K!&37dTKZgGNUaO?2q?v7 zE<$oIi#g^COP>cUeW&z@GBBf)V~B%H)E|g7_n*x&Mi?#oMqPp8vf3KMxI1y$Jy_rk zYi-M?kOA<#B<~9ZAC3dzOUoB}^iD z;|3yy;8qX(uQq`aIiPjaB=2r!i{bVBMo1_#zc6q^Bltu(x$apt6xR>7mXA@7C&?PIKQB!Y$N6d?HnWO4DX0KVl1>F^M+s`dpC`Fg;MgQDnV7>d?6x|K zb@$Op1KZ*D-)&Mk7$ECojoKj~%GAk5@`CsR>*e6xQc=_b8?!CbXE4Ha;|7$}AJ_G)2-uc~iDaW$i@yf-H>B&5gQ>*7QiD_U6`kx? zX_lC~rdKFazr2$hxfuSaQ@e7;v9f6+X|p1D6OG3USXj;Pzu&V;E>cxDFOixpJeHeK zsJ@!Ke?brN$NeYhj)p&5Enkzt@x_?SuPsNW%VE)1qrJcI*>2fi@d z%unwC+$KGvU1X=)X2ihX?*Hz%(k>1YD5wD?qB97hVQo#2B0;=Zqzp6EhCnaOXC4Ha z=Dt9*-raH@Gq|y@dI7ErKr~ zcisS(pR-1dH+TChRF=tR6dTRB#`uG|8p9i@L4xt#DA<1RmuzaB(ySY)&6wJiDHW5R zJs;L_#L8t13tqRHy^-gGBrz7Q=hVNBb0^z5>hp3=zx`$urIQ|kJ!W4X3d|e{uf4K2 z`_Okd3vH57$_nuNESvbusw-FZW_Zr5-hP*XD^Z|}($6fn&N-I)o8PP0ht#YEl5q1Q zEh{v*H^XriI0Avp#_$d|G)$Ywzr(yKePRqKqtoLd;16 z)EL zF@&fLGDMMdiPz;ys7byT1+RCXe0G`Rw~V$nEGti#BkCmVCP7S>ejw;5O5wjHpgsoPK1xn)c}b*fJ!Cu zf%&f{bY$<3`KtrU`K1R2p1IQ~0ZyVSfJN$wc5ppG?6+nE$FN0T*Ol|-(X&sO5|A!A z0fLkV(ap#OV^OkyR%1~3q3hb9d#wG1C3wANg%W(N@f#cU#Dr0`h&a6oqV9w{afSPC zdn~7O=|{S91HoeMYu?J-7Ayv>X$5{+@S6ClGo@`2-{vOA{8p|DGyw&*#4JzL_$J5Z zZp)rui;vwK$B)?i5cq)2@l>ovxi7fHP&-hajz$pP)xg{eUN(9!BZ|ofK!Qpn<}K z+Ofd3f^*JVJbm6dYyQqVAG#S=ZRp@#5_~8bCSsxN2+g?$a_@)}lu7ei5E)ejmi{@? z3y84UPsDdVJox!qMlZXZ!@*`E=fkp3jVo^`$W8CC_N9IbcY;I}dbMgJK*oxLe)Q^` zi6wFRHaqO*mo@Y$gz0_(^?;apKeL51mq0(0Jgp_tm-X~CWifiMuvfnRze(Am9u<-O3XsrB(vLt-pt0ffTo=V4jg>K*LYS&4Ik;z;1wkqYMvcpJuEgh|?klP69n7 zKDy&s(oS?M{|d+8y0y1B2t;G&OaT!0s7?Wg7lUR?Pg(4*X5Tt27$Ft|8t~gD{Rr_+ z3LTJd$ztd$&4L6JE%1WX+c_3=WZ?|O`USy&9(jga?0Y$|;=pEWOL?#Vi$nB$*6zqS zqWXY(5vvLZwMM`~d7-_wKUA;+FyWLC)l=i(0)Sy!yDqBY3`rPHw~O8U{<_}RV?N2; z%W$NceL}0ao83u))L1`aYkIVCI<+DY5C5__8~5T zmSlVdk4ZfkKykhNxR0{j9nmnLQ)dTpu2R0G)N!CLS!+qcuH1F;@K^kP-+#=SS~ZYC zyODr+A3um6&Jy1?EvHN~m^Z1vvm}+CtAc7G4-AcK|4gnEXt8<93vtmC+L4AtiI~z&2j1M6$9)EU{U6DLYE}8SaK1xa=fV1emRWdIR@H1 zlsQ!}Z)0K&w;=gpNE6ZDlHeF)Pv^SQA1ffH-=fhf;MC`3mssMa>*Q7eh%-Ik9)7om zWgiKWJ2U1>Wh8)oyN&)rFI}Nkh>40Qq}?IdeHC|kb^*=DC^BG9-hJhzMM~DV^J^L% z_e&E{#K{k5ssECkwZA-8NZP5Y${s;tLmoY|hcH=P=_>*!&X5V&T(0P#+`p~)pu1Ep zRI)Z^Lv(C$7w@jH9fLY}jovr@{@Phxp!^auW$@h_rKyufeKG3&Z0p}fKnDHO=RLPl zk2!UII=pOy(t1fd{zzZTalLB! zzLS1RoQ^UUI8j7E1ul*aNj@e|!ufT;y0d9sr#a?|4)oaAbsA$hsipP;58`L;gX0^?_#}M;SIyGw&u ziPw<%_z`T%OXUG1pyHkQj*`wx_$~DZlo5V5gpH9eOwjlyqJb|u-A8bgo0e9}77h>F z8K8tDM+nYy?>ZyQZv{NH)%HViV@G2DS$l#%M)Ta*1-LYcxLtIs!-%A7APc0rs#>ZM zJBI3hes(}aBlzHI&_xiwT7xtGlz+u`CMs{t%WMnF2slXjz`(ux(t1cI)yPh{(p|*^ zYg1D54fZq^Mth-o!IK2m>9;^|`p= zGbhAg!4Pj1Ey*7ZEORM@+S`#ERKjO1w-ry(vW7q9x4f7JfZ_&Vr9<}raXU2{nGb4= ztl&tD;-w`FnprhGty45*-olk|HxWYKBkECz1Y#=S{F)=xba!A@ zz^tzS+a@%n&5*mz`3@&Wu8sKpNDUeMfJp!fJ7oXgvUq;~^C(O;zdobXfdS)zo?RzW z_ctHB)_H#81ZEN?P6{c><0pV6mi%RP$Xtu-Hr%o5&jq)~n$w=yCnCR36~O?Qxt_7U z`)CPkt7Qdn6cs^4FLB#M{@*?mn*GzYg&`^Zs=Z+nY8KJMH zbL3u+p-&_?-?J3hx(&tRW~4sxz2=4I;Fb69$ROW&s@PO$;VoiU4DHOBqkb zAVM)y{ChbdS%9;j$|2(e<9e>@t!x$NQnX=?26#K^V&HBMxf(l(KHrpHb7} zXZ(y@KeewRxAe#SfFk{Ymdd{U#Gw!|dvr^&(NV=c@sOZ9CYsoK)sY*{(0In`yvedb zlsq9g1I|&15yau=eap)&#<~+C_v1FyN2{Kzzuuy}PEyK9ET6P%wh?zAh72HBO?=Da zBZ~hfDbt=sjq32_bbo>ih$tiiR8E3Uf2Lo>n3YG3U_9T*==dUGD$0q_u?S}uad*tTpE4WHR0 z1>SwF5WeXIn44At49|SAi+@&_VtQ%Qdtc3OsR56tGS=>;4Ms5_XuqzjB zm|AhJmuGX0C~=hcxn_1oIlFSq3?;{vOUa&E&opy<6!z&hZ(putuK2 zP+D-?nR3`4F?Ix4rB-l)O@{#Kgz}fI9H#7yr-O0cHG2a!UkK`CT{)jCr3UzgMo~+U zPsF7y08$V4_g%)%YTn}K!Q&K!@VPG-8soREe-nf=T!fWuz+Gn$ zYuFy^J}Y>^unNQs*y@MPw4|4cPP%tr94lhj^2{m?_h~(%@4IcmXD%BEmk_Wju$AT2 z_~9HSk?co?9l9;CfSBM-^|w?>@NIQ$GNNezSX?n7GVce&eWkO^P{j@p$BN&>he*IX zgSXqe7QiYSY**$IO9}aJi;l=qTr$+y<_QQqyND3!(Xv;(n*SVC} zQI@$~E+{wNjhwg>tjaS=;uSt%26vLOt>NUf@|> zwCdl=65T$jQe)K5YgYo2HHt>wU~Fdy6z%6~5FH@`LxOM-+i0mOV&rGM%X+z;ttS)}qso zbCQc`&43ooAi%;SIymFH?85eN@(olIg^DhN_lbu#wvRzgE$>XshOdaD61)#PFpVJc zWsRyaoW*Fsd^VACDrTMBm@J1L>Y1G&VbjHzQ@F9fb9<>4SG9U1$K<16?L)9F4zbW+ z2w#f>`o7fP?qtqL?6Idyo!^HCl85!#?|f%&wfauI=ip)=pWEjIawcH=ok{!eTStlJ zf=6x|7?*Mmi9gA#DXBh19v+R9)nr#krWfd0TBxERXUB9JZU4-T#BK`Tx-e*%qWOIr zc?zEFC-~xciCZ;2gDk0kEKHKPh8^prRon4gI|xR+p) zzp|ci<{(|tB~7zY@daR?WD7YwO-ibO)KBkncQj^VUv<$2fNp?e%mZh4RDxnT7QuA0 zpMK}UgREiX!e7^8R8TuxqVvc1FDjw(PEdhBUSd#(&f;3VqDOJ5J}RoXs!o$@__E(< z{Jp@=J!?wTrWM-t`ohlp{OT|(ZA=!6(IUgWjZ*Cp7A5dO2myjT;L7Dvv;3i&b+3S193bS}SaM;F5~zcHacZnsAF7Z?!&rUMkU`K%QkQ`8X&7 z)Dm8W_UH23CN^yyY7_c}?sHwn2{JlKa^Kx7Gnxju2R_$y=w-&z$sItEi}mVcQ7GBo zj4yN}=0qy>-jzxcif5qQ8{Wne^(c9tLPop9kvRSQ$6TB}S+7tJN*Q8P%EMnC@f*@x z-MeUQSUpjJ z0~vy3vD8*|2JLQ9=rxBwX?HTSd~(*9XktfW0tKVFP_oLRKF6|pA~K)~OJjrOavy=) za|*7G z#yaqINCX}&@1rTB816DZT8hm96_$6zLV_mF@VKZLavinxe3A`2H8zdL_k1%ssaIgD!I z@RhICu~+V3kzirsJQteVhgPl_Gv8X^v0rCjP&lvT)_w2$$WRQLz>ig>{tc4S6M*5W z2)qt$J+M%(b3;C zIP|DcyDKC_WqgcJxi!)5LUQoFoca!UZJgWPuevv-RRv-lS(}c3a45_ti0=5|EW|J{ zzlO&9=N6L>d**I?l%j%=*NHYg|w=|r9jDR4n71-P0=chbt4C{VmM z3u9AlYPllXJ0d8w`Z6#gBQn7G0aI2C09~6kNUGUzY}eWQ^V<~u-N8m#2>6~Wzz^AC z^Q7Bx|3G7-83Ox55MX^f!1E=?_i$OZQ-i^T_@7=4(7;qluk}7pDXm`vtLLBo7AIsK>8EO|z(RxIy>X>jFu*4^G*Th`3LA6jhw6YmzgQl@~ zui{wa_w@FJNs7PxQ_T!E@e1%eL_PMQ7JGIyyh7=ma*WvG*ijy-O65td@M%A22G=2R z(6azMzxi|1eLbl6h)*6wQDGR#1n~4`LgRdxDoJ=zwVz1SS*kK8%<^hi{+UPnU0Low0uxw=hXPAxWi- z@#QX>!#Pk7Np>Has&EM_Lt2u0ndx$ZCY(9A3>`J`~waj_~JmoZyqYya;;wkkZF6L$+(Fd|o z`Y;4;xc~V?2jqT!niS%#X!lk$0V?tY1}P%SWY5~A=+60>usJK~05R$EJtQXiCAS)X z-{87;8G^~r=BmnzG8Wy3Wo4p?(Q}%=r^R3$QJDL8 zx{9{|(U8U~3yU-Q_n1NY9G*xEPjM1l+IPakGi0PKo7Up-2BzXp0mI3pSvp%Agw9lI4UO$X}7;BAa~ou|s3B2Vf~ zOfy^K#qGbTCY+Vgq)Qg;wt>TK`rYd*$B)e3d?9po1-aMLK-zri$JcLK{I!cBuCx1E z&;NSG*mjLdnxNYMxcW8WT04zzdr7vC(PhYTFr(&yOdIB{87t1i$v*S7_|TI;ZKKcHF#s_4`F_4_d6=4aXim7;y& z42RR#810`Ma)Dl$IbK*UA6F`d<_--*U&Bf;q_lZ)UbesMKWIK|i>FF%zLxQ+fq}-P z<%ZhKLO5MBBTS2*wAn`=7!rePN(R!>wThK6@M3QLeafGrnib-o^GJ2}JhPjsnU>tg z>CG`MXWg&s`3&5RTf+3zM6}I)(s|l4mAgd%{cW)Xsev{k2jRFUz{ZeQzPE*~<33_( z`Xs7{?IBFRDgQ&*IR$6dh3htdY}>YN+qR94Z5v1I!4E~&5oVyoXb;i4H^VI2@lZ!Y0lndulP9-kLWP=;c zuLLi19a}tsBxD-!+ClX1lXqEsZjO49!t0rpzlmXqXD5I+)00LD~1i6=dS@peB>zE|I5$$GCTkr^JR_{m~keowjd zLqEFc#dJh_s_E^WgNPs=eh*)2OOWoUw4v8Xq@qt$xlzA6pe+b{iJmU>3Z$3|Ya?8@ zC`zJf9)U-Ax-cFi_x_}wmj529Qlo@b!v@9mJqfvrokXL$E;SNR^QaV5?C0v>VC~>G zDHonG7*xJ!BAh{k-BG&>>9dI?52JO1`Cp0BK&G6)<^Qu8Rpsv7IapcSi>z()ju>^X z+onN*uhjMM4Z2G)!EO)0yMab!Up`wFD zHybb@Q=s!8WNU7+j1*4ek+96LDS0TkL%_vK4cx+I=cAKBe(#(DjSn=ES}rRkfBL`; z>j!~PH$lLIA1;P@XF?9ShOm{$k(0v}9rt>XE+G{s#6))1FL)Qgp8r^}GzURjZ_8|C z%eJ@QsOE;hOUL;iG1byt82l=lz5k|0jDA%40sROx< z_3>3cfxo|3p1$}C1n<)LT=up;utw4kVPA|a=gd}zEoNb3pKPF`KK_{&t~g$(vq}Kh z^JI||0-~D)g+I5Dg> z$^E9o6R(K`y+gLGTp+CN(m=BSjxRrwpe(w*ybO)Ssu2H0IoTnLQuqCgGK!FYFcrHO zypJ7>2%7e+@6y_r?b%;0Yrq6mXd5NBRCt#BY2qAuhXxj1GuF8S9I{&_wk1W{Z@6MSUuF(o-kP zA)~@(6Y`<(Wjy`R_2x}W+KGqXu3ao!TgzAq#zUa)gYk0}mwlMm@D={r^6HvUkmApf zMqs_X_%CF~pq~+{kS{}4UXSjj@)YN1T-c;`j5#LQ9e`5*@lZ3lmNEY;>Jh12Tx^`w zr|D!=bDvm-1UNY(mQ)ye?a9J6#Wdr>5l-!rh$R_D^HO8Ffi1o@uF==OB`-OCufwr@ zGGgwsyBxf~b2?cLF7i}zv0n!oxde)7*$_2=k0`|%m#9gUvK3HkZL1Cg$G7_iHy?&XtS4mEPf)zVJCL?F4$uT z=0+p3!N%O1DR_IqV$&85*v$!k^GJ)(bO& zzfCw8fqw?u-Jz+QyR8qmg|7I2N(lY7%}YHH5I^yJq(F2?L&A}o3-M_6hX#4_fE{^0 zI!1|iIHoDy+j1NpxLeK43Uv*(1Jn(LDhHg?03=T+j|P(rI?pL{EQv#(u?KEQg~Y8Q zmv8_BL|Y&pHD@W87$4*3hvtiqyc7{#960-TnAI(IS@!6XuZVWpn%xL&v4#Wul5#vp zJHD)(!40Lo=!59Q6G;KPV=c4sbXCy)LZV`ye>BE!^NGdp?nbIuO8N2R;|{O~4q`L9 zS>HKQaw?uV&p@MZf%YFu`f?>Juo`wwk015b^R&5 zC-Svy$` z%vyy1o|rKk3(l5yXzjSO9PuyjcUGa25GFJ*bj(Y0UkR}HXkhWPg*<<_S9!76#ZL+l zxu8hxiRloVZlzWoi4)ZjMXgbzglq3xX(t^CaQ!A~I#0&GtO*I(N#4RB)vz-<0E3=< z{Q56c(1ku4NB_GKiiyrrA8$1}=<7SmoX?{gg#(vvYPUVN4IXkMWy$d>QYd=`QcVdb z(Q_*8pch7tt<$u)ABz#s?=b^4?Bf^hq z1txqXDqn2rtcCrJ*WnObE?z^&!&Z)=3GC{Lz$f!jCsMl6;oXl$#qxWJZ`9Y|C#i?R zyv~D_eZxA-Te8=ZcR-qcZGMC`F%RpAA5*?$LXrIL+i@(IR_Usc$vC6NP0-tn2=fjr zBEq{8tO@ibWsf*YdF&#hLL^aU@!7p& zy{an?Vd|Xb6~Y8da^~Y&_4>VFi}`TL3*kqT#H39YD<7@JxtM(V&$-sh%6-Kgr74=? z(ENxT{2bF^L`Ad2ZKh}8e=SfFp@MJTGzW6D1}f7-6Oyf*pV;D%zfMxm5c4)04BIk( zSvYrCKuv2(r6tNnZ8iCvlMy49D~h>KV+(ERn{!8HzR?(F6j#d}_Cu~TJQnV|Kx8WU?&f0D_K^Ck+y;>x;2P6k`JZoIi=h(036Xmk33Xg@sfPxo+W+Tn_^iQdj zJ8!*8)u@nwLP5zqk117#$ce0;G^Z&oQ;=T<0bV*_IRA> zHb)n_Ub+P#*}jt`-Wx%&M45W9$H*{{rCOLp@LHp#Imo&3`_Pra!}Gg}!1}@m#k*Ye z3-c2>HgZXx#@OcXM8a?W4yda9e*Qjjo&dVKBXBBpKU~NlzbZF_)+?AdCUDo%O!^Xd<6rl zOeQGHOQv&ZmmWUcnEj2Xm7Q)f22I|?-dq=Vb-r^;`badJ8$$R_sexhy$^kI4eqW%G zm3%%sAZb{9D{uyjitw;)_zzuRIWybgKH3HA`#U;Dmwfi(0QVg*#&b|%LQYwKkMwk+ zZ}bKHBJ&Nv$gqh*m>#MwPVb*Cr*@;f@;pq6(PeMl(`Do8nqegp!ZE>q);<`F3eJ)! zUxn58tmJ-~&1JHY5+j>? zFHuuUh}XY?88uuH+07APE9LSk0kfUWG)C;S+^O?yVn0f9JjkuiTM}OtjCx^mE@7Gv z3jgt-@aH%8Mv+Q=s0YEJY@BZoX7VY*IP7V&BRe-!9{$#$&?TzLapu$+{E0FfnU< z{6Rzh0o&|b=JvGBQ~IHx=^#IH9f>7=44xM-DMcl0W1hmN5(RH&ReONg9p-2aLNocT zskcsB7bXxYO5S9F!}9o~2FSKo4O{OTJAzQm)QtU>o<|xZGttvUAA=lgbn@G2V%lv- zh?~qmd?$Xkpf1~ZB&djOC>p|H@i_bLbh@Zm3gL=J*dUuOkMzB?dP3q>t5ZUE znGphsu;x^imI*eb*3C4P7eAv{=8N8wU`PHmN!O9~>+odkp3G4w{ZH(TR-v1eYvwI+ z(LhOqYZus7e!Z!(PMb&prdITu`T)@6Wp~C_D|WD*H>biyAIE9#LoMQX`lqzsM5hGp zGB3G1{h#+(s_-G3G}KkSq4~^F{t3RIk%y%V(*i4ZUX+dwef9|}7}(f<7m5zG_}(sh za;rK=BJz~JxC0{NaeW&S3mv~;Z4}*6i`hvZJyzgh8uEhPluT1pWBy#*SW9v#uo&>y z-;N+8oc|C?SgYoyMA@GLo5Qu-4PZrd+4tD|-PJP#K$evLn353{Jh?z5>$9wSaQX4m zt@%Pfvu*HCvz#U$OqorXE7bCM&=#q7({nzFRDz{eY zAG3UW9hXgyPyvwTGhaR9olWlTC$VP`F<$U0G4-lRJ1Z*1mfaBbj>BIgoQeC3{tK0~ z?hmrNPBWZ&pOMciGYlAO935;I(-9JV>T>vjB( zpT){3LHwOBbyvQ3YR9r+grT%#{>|ss&_Dw6hp}_51v;w z0FEGj+{f7b4X78Can^9ywqc@-Yii3tx_#-eR0eVqef<$Vv~GvbuIiWUZgwjR($9s@ zj{kZy2?@0#w7^*P6~n(;3;2EG5j3num>x=**CUyJJSRj#jF6x=4Q?JuLv3f{CZp+* z+e#LWhp}eqGYXEd-o|42e2J8uIr?y)y0#&y@+WJyA;*;_X6HWPh|bQbh@VLD@gpJnFDHHy zg*+eSJ6QhBSFOwbntJmrVp@;x7}zV@Xo1Wg-ahw}mkV4CcgM(GMc0PYebQ4a2+kRI zLh#v7_*<#}#GbZ`GD$q1yMSzDHKGG#v{BI9wI_FO=}>epEB41b&T02WuV z3%)#wvG`XTDEC}Ol+~Flhf`kE6mR@QNxq!I%I{k7(L`F;&G0heGXY|MH)gu_p7n)a zW1aZU?apOQab~9J#S#Qn3Nj#~?hnmmjNx;t?~tWMD$!vbz{oA|R^H+iy6M5v{yeJS zLi^zv$hyGl=*;#h$L5JSd@u^+RElz}8;49H6usDguUGsFpx`dV3Wf>(hZyDEnWYzq znswX6KC+Z7kSCz{ePHXMl7MrGq)@glcnLb86r_e|gZ9`C4Y;0{d`H9IjgbU0m8H8S z`}lm`JXG#FQ+gh8kXH^G+}6oi5?AFT%zlK<>&XDShn&}+7hu2RaozLx%jHsk1(=f{ z0+Pc*^YYs6KP9co=Bb!Wm>UUC)Y7o6{YKYHj#XH~1+AE4HrfOrz}lUJ>R*NSmdi28 z9%cE^4+BDN!A+ZSu5r}KY!hKXdzggJHJYq?wpIFWAto7=S`-$-?U$|uL+wzPG&MFe z%i+Bz1*LzQ*@#p~CKCgzhRh^H-5q_|ey5~)#|Hs$0I4)J4Fc=h>gPW&oi_yvfG(~?|5Z) z8RflVTE^+^=_ByIZE7K8qjR|DnA{D^F36;@u4~tEt!3Nn ziW|Ctx*^3tKF*KzKnjhn&d~+SYLqA|#)T6a>U;*Fs)Oz5nZZJA<>Nm0t|UW{qDxMArtHTg!}2gUNnVW#x>JgK@ResKwtG3VB|0>aDNf%>_-@Xl!j`p0{a95C z>wR1#u1c@`MP(^DerO#t^RP&&PX3{Z!f26kDwJzfz%%P!k|36~Q7N~N-Cls9tHwg$ zdQj{_Q!Km_*_aF5QHn@dJ>TvvY8i~H{h2x(a*}@WG=o1G>GPVh7_Ox#aFw$^?UXh! zjvE$yr82j5i#f{gnbngveiM=|K4%Q*yC@KV{#%V;pC*Q-f$*z(^_MB3n`G81+Y2*Y z3Ep<(p0om8yG`EED%2#8m9rkXS9=bhVDrUjU1LS_5P#r?r?2nBmdC=@5tx|6`{joe zk)U~*t(&A=4xunUOyD>_s-8;*WeQuq$xL#d^IRXiPZmXhVIdT=*{*r^sy=5xA|WyN z)>9f7z~)-8>8KK2O~=PZu`kzFBdmB!M^9!N2U6wOe@c;w3S=BrWedL_Pz>7=Uw>MP z9~Aimrz1ss{5VsyQK`TiTTGlBny<#Bi3x29ahPO^H^zb>Aj3K6jzoOMxA_N2zIu?O z+QpxgN>cyzxCDNsl$_)CH8tRQcwyPL?%}_?-K1-2^&db=*othRgJazYwbt`Xv$2|3 zTiJ3xHk?hXR$r{6G$i`4o{WJmu$RoYyUgJ8sNET!9*3knJdcYIn2-e#_=rQ3ZX8U7 zMsxtaPUKA4Abh)C$z}IgTh)tthi|BX)fVo-z&_j*=RXfH$67EJD#md&zU6DKO83)( zn)=T>^(h|$+N!6WQGrZj*b!k_-CAjXG{n>&@%mt)r>d7}>RVSrncaG+`+WPbwreB! zV}`lZ`Oe?nmZ2liXJp;C1cj)156A6$Ntj)&l$AU9I90{X?>ugH%O&_PdHdA4I3lr75O`k+aN<#RPLvSKdnU-hYUP9gAw*#}JX=I!o+G@Anr?VOpvce3BoS7D> zLO$5>XbU&aF_j9%%F$1ZS+Do@rcfYsV;n@|H^f^@7p^Xybv8{x<(B4AI+rK4XpXG7 zNLAvKHITC_s&BT4cdI}I%c3JgBa2=6lNyqnD6B%|%Y~N*@r;#$!OpYa8F#!YNY(|2 zVqIns5@74wb{-!v^c#XkmZ^mhN%`BPo^e5cl|!R2{!b1E#{~ZU(ACG9xq)T9Y*TP? zh{D?AiOwc$>~Ay}9ox=_bhT!%uyzHx2fZHmtN_n(b&W9|0o-_K3e;mWfAhBDUR)L8 zce00ZhD^wG*vRg$qIq5Sqz={(WW8l(huSWMt{?R*G>*W%6(1H?=6*Zucyody#Dq|2 zFN~o7T+D)WiwG)u?C7lk|;bt3@H4Ymwr;yihcaITWib)GK`Y z;VZ>QX=FEPftjDoj1+0E?^e(`pGiQ*L&d$!mVPayzYQoHAX2N6f^M;4pgqw?n zi}nAYteWK-tVEb?*!po!tVNfHojPJ*6>@xlLKSJ8G>@(dpj)EfzXTwlz2=#knnzj|bwS&lrI%*r z1@eOqBe!f)8-BIii1&PpS~cD+vvw^qjd@>m`{U5$NWM4YM}J`Gb4Cf8vU1(@JJytd z7zxQNx>CH{EHuC)f*y)xB6N{jUIbK9p{fRf!X2ox{0z&Jnra){(#ho;aQ31V4V^fu zXwZw}hbP*_h`d(~NoU#y7XV~s&fd(XKQPUBK(pR@;WlfN_?F?cSc<8Y{@~FwLTZ(N zK(Vf$0cy-1F>=>M5aM)*choW8i7*FNZtGwbo~4 zs*flG(8NBjLQplt2bQ@?F{EJ1v-f%v--%Zk z)T6EGB|Ai;YRik2xU*Ilg(mT&fi=QI(U~K4Ilad{}*;`~9xV zE6(n`fHlK?>T|@q*ib_c?T{{ocLMz8%BdAPkw#~C9_az3GypwHU|WHY7=V76OmNzfVRO~?gAp9mB4_4CF~a@qAsN5~&0;B%RDE}HX4z;Ee+AId2-bJ+NwIZ7&)B>P#-GP$#}k7})y0s1sldV`11!%s z={{{I5KAyk>=Y%k<)<|#ecHDR8JI#o&_6sRljDgOLCZVj=>@~dW!V{1`td5qU)^SB ziVauKOTPDGx4aFgq5gO2WVdQ2=M?`)|06m(*~v`ZF$jw9xJX5f3zq|5l*$%0^!-aD zzJB8IZdca<2qzkdjy-ylX|C#98!m-U6F{R6j1)a5v?Sz``PNsb0vR*&8RXyK$u(Wn zvKuRAM#Et=7D6!zU?^_{T`K8F?n&Wt573y%0({5qEXu&6%y3@SA z`0sCF0qC3O+)UtXE$lJEe_-_q0j7PUnj`i8LB_hb_K%N97HMUa5eHSIF>kwgl8vv z4;GUq6-dUGYOs%=!`2}pR8m;tsVI~_zMRUrB!RUfgaz8J&S(q!LkBmjoCuI-VTK=4 zmu!qSTvgaq!mlxLb}EggIDq0M&RB(2AF!j!J|G2+ncHl zP@lTl)XPb5HslvNI9*`*e~yK&yftHB9l}i{XhxW|2+V#B_uu~(*?RU+Fx8IH%$_G0 zG<_C&km0mits8ql8vcmz9inm)g!(;d+_quRMjc!NeVD&~57n=@C`ZrSPtdkloL32s z!*q52rS^E@j;}v>WAL}enYeVs7ZACmH`k$|G&(#B(2Kg$ayC$kWMuFB>P7PDPW14= z=4$FRRwHpnR}FfzrtZ+UK9=|$Pjn}0EEABrIfsjA_+FQx3|m@wKP6Vn=M{^*9>Bc= z{OtE(%}nV8@t(wZ4Z(nG zu~Yu}1C*z=LaLn~LC2(GsFgJ=Q=f0BJ0uge{lb9d&OBT*;*W|Wxb%AskzghUd{o0>^6$xYgfko9!vQEwa zRrLMgHO1!Zs{7O5VTMlkR?L3oZ#80w8VPI;Od|7i z5m0A|&r?W>pR1sX9D&6d;)M`&o{2NZ&@>p^1H1coGL`%?Nzaj5#{697U9qv_ye_te z+x@5I3Hx5AkH-;KrPIBD<`LkI0G$DwIT`TZT_PmEG)BqC*EOaOL%L{m)|07jY50ix zj2GOgmBMa|in$Rwo)haEzN<(1c|%5SBw8oeEJVo8SM-TkMaHx*mfQ@p`uYLyGkI&_9~$?3Oc}2!z-)wQ^HWbvpE_*fk&lnjA0se zH2en#=ugLabRS#)77@oUmGcj3(^7uo;}v}R$CD>v0HieFyf5)!RGD#vPtr1-unYrG z3lm;+Ya2;&c+7ecd360<=2$LCsow|At6xSTg0GMcr%bfiKl}G~qiguDVe`JbT&hrj&kH=u z@UYhsQ{wEqk#rk=huhQTFEdtvC@ukckJ(5NzqEMIbH)Q94~^OqlCRPJVAM4#OWmc# zkeG^s#N#GCG^;m(ZbMk?4Vg@1UpkmCCx|orhKC~-dAZ1~-8(21BFh-sPM7J)b%W!R zXdd8=sf4wm*CurT7WNX z(A%k1E>6K-Rn=87*$=JItLl(+C#kwyEr5J3Zp`Xj@(JVnf^Z#j!>Hn=j8zcqAr4a10b=`q&ggCj%F5PfRF29I22Cr;ei1`J zQP7(1zckHahqMP-HJHkZnY+?N<_en(KaA$KPsSQ3r~BnG)hDAc%op&LOo#g14Y!*v zBC|O)_sy@TpUtPTw^*-Wxk;OFTcL7qv@RolUi6?52pqzbFL z(&^wJN~MaQ;))@%$>(rsg=ESPU^k6`iFn{PP||pDwy>I6RcL8MH^DAQ9E|(5H#os5Oie;Q4+(e1L$2My*vc!}Xt44U! zm3!9O#tb)*Nm?=VzdJE=v#<;M|MO~~;|r#4yj^njWWR$?jdBR}0&9lvIr`jLeudaY z>j`Zdu*#<5DrVUw@ZUakb^kcNr1jrI}CM-bu_f>Z2kS2E+Z*T?2`wjK6-pLBz&k@b|kkh zP&k~dM_u3MHI*!gC`?#-6f?thSuQ77%}`!mmEo9uBN?$t*b7;|zt*MEv~u zm!+3B`>j0YTO5iJ`fMY?8+O{ z0nVnCp*jmoc;#em6rz?g)bIGJK}|5WKDl*38Mk?}nZqtOF|tlR$X&iDE<#n)vfR}N z{Z6`2rE4#$o&HjlX{$POdPnd2Ls)~0!NFHMXBozHr_sbH^6V!juSd%DA8*-kfj5wu zefHdZNHaHEX?ToD}t*O}oGmeQpRF-@TzS z{dWL7QC?rw#qqR{svzjO80MH4C)W|x0*5iqC#jF|sZJq)^DGbsiE4>L;w`>T8*L(K zi6yBprNyUf6SosA^;@LAka?9;5wPJTBkkUN6=P`5J?am##MHEuHBS8~0e?tZ>i6Vr z#*lP-&jhSXng>5jI%Hu}$`lNb_s1^zsED5^ZV^PvBEOrA+$SuX-|6ZZ(Av8Y=Qoz6 zZa=*>A6)ST@s`gO$Fj*1RUTEBy{Q;E`mmW%`}b#VjlyDph!;mse-S`1A!_J9XPPVD z^ohAmGs0o!@4G`K(`AV+sDQfGN@;uKl~P205sPV(_#^=@h!pfh6~H~QFRygAMS_wC z7j5-lA2D^(v)oLlqNUfDOmIjWY(50UAdV?>ZjAuwuij05Vx-$TthFpp=f5FOk~vnp zsbp#XawD6_>}R40Krl_SE7zAUHY}K*V_XCbNDyw#%k06ix(3wx?=0D`P>fMeZYo`LDE0{HZb6w6F;?EnFVD3k? z{wvkLXlkyv|=YS!3bQK;xfUXaZ^X zaw!p(n2Wg3gbDL7XzT1XOFuE;;){}^c*!yZi3rt2r8{IUKgpvAdxV65>-wA*7WZ6p~mp zJV$&*E{w06E`!?ZccG^=1s(d?2?ZApkve;>PGJ?9pKM5jK(H5GB>1Uj-^G{RofLvq zKF!b{ZtFqKsLOJ zaT$`rvhkbW1X6A{hHJ@ktd+P*j~oYh@yk};ZBT+K3Hxc|K!-KuFE;F-ZddOeS>Zrf zWqnLzxJsXcS>gbaUo5A+zMU_p64W0sbsff_*$kpJnj_MT7>5YBzj51Y)FV zo=>~>&@e|u4*n5FnqI*Rfx^eagmBd|FRuV=VW}*C$ zahiZI2DEQ^JO(zC=>K>n0KXhzT!pguAX7Dn-?{iLjh-I2#e&cYSFPBZ#B!ki5k=Th z?9}$&bxsSK%e;}X^vmmJbNIV!I7Cf?0dHnigqQ*}>?;1?t0>dqj5!P({V9sEF&;d8 zO1Acb!LU(a#U{CT!BszoQH7dR8?_Avy^(1(DT2 zB{Q}S{}v|HzALypfjqZXH|`Ocr)%@DNMaT~qGy%Ge-C}3p}3nh)Z<6)0z16gl}JPd zg5aZgV4xjyzRh81D`Wf-a`sLN4&$YX9_^K#`)0x8!YU~?S*v)29tprCo7W7%M1N-i zAL#38>+u;Z6?F7Xieo97t$6}gi?p*an=75uFg-sqQ2VD(4F9+MKauq!OhMa$?df1E zZWl@xO`AjdvK@YdHKyb+aHM&n#q$Am;f$=LrmzZGhv`+91%bW487h0L|NA-G^yPm# zm~FHn^6*48i>kSjepu&AG)h$xW$sh_Yw@Ab>tMtQ*f&n%`5`ZJYU^2TzaCKV6>YPn zSC_YkmgJ39z!2sBBiPyH!<_h@gZb}=wjRFa;SH*bsWSxqX&9&owMH3|8r$$Vk&zE# za<`Petj!3_P3MU~SlXF&PW{L>6~+%&os;%>i8HjU0Iq2sy~fiT4PvA7fCEQ^(9E=E zKa2F9EHJDJvn_O1lHvOAhEWrx_)_GWL8mZ1xo8atG!TEQ2h}3!b8VyQZ4a2g+RG3b zBLGU~yeyDq#gXtDmA?$|6Qv+nj!iVsNxn--g;gKoMy!hZIHwf54mk%D(LLAdgyX5W zEUyzB}NVFYfoGST8#JZfFL)B4r6kU%f-5)m(r6H67>xscV9bJGbfj}E6Uumcl$?r%xv!x z6EgSIE=uIuaC@SP+g+*a?Z8snzaou5~|04*ymthQTwtmlMII)+;BxrU> zZyq`N>!;q{#168Q|0Ry5aRuVTUG$qcg=$j4K*^cwwMwhDh_Sae5Ok3rMhrU}E0ly; z!QH8Gkx5w8xIF&=7^r5T7=$hG<~LXm2m<#D{;oF1;iD8J2oOrgxViW_VTU>wyiK{~~8BAeuDb+E#RsX1@j}MRZ=dFcNB8`)$f+ zU~eP)eY>P8tvC9`&Q5OdoVx0tLjliNWaXH*{i$5l)?NI*e_+cY3 zheb~w2q2QR@GBKGWo%A!!>WmbWM_PQqsetbF?yN^DbVuq{(5(=-2GEJTsOodFZmFd z5>F%r&~ggm9|Q3fS>Q8Zl6|X#!4&$Q2A87ru-ElM**+vl%;6Hi#T8<;V6XSI1*Io!^`V!AheoxECNVqRvv7#q7Q#ZP`ih z07Xal%dTqRnuEE;8#UVQ!#eLVDzh0b7nDxsII`?PHNN|~hUw74aB+^~Y@|;%w&2V_ ztB9Fk5rfGPjx2V>y`(1B91E`Q$F#O_D}wusXD|VE3m7r4mkWxsENZt$+xh!d{^M32 z*1r9S-?-6KrSVDJe$m>9@5l0qYy!JHb8Z>AW@jTt(tI{VaAiF&6ci)=;u{>O(SbMV zB;_yu6VS7= zzGUTL-{@<{33~e{RF?rWk0ouMLhS99qgqh8G?h*{^i<@kWnI!jvGh2h0>J$IWYdxw zW4!t#_G1WI*4m|;Mne>{c@Tt?aT9*=60(L3{7V{0XR%RYHK&d?DB` zsNOd!F_N+kyb({Zf5Uj}XWtMAVcQ-f-F~Z|(f9J4c`T0>uyxAQpg*O47`D045`}!} z3xT6q#u1tFwMVRfyNezW*5y7z8>wEqR)&egiThB zCym>X^$Z|?x(f))T>(<+?}v#Ay@&!h;vs$dNMpEh*=?@n38a)@RgdwSLyAgwE;wRt zlq^o7$Rl)Qx&&($@S7e@r-eSLnT}$is+9I|y@|q01~y0pB%gX9Y@j9?s-LqvG0>w2 z;WbgPC<)Kk$ZhIE3)1n6{p1wo6MbK&^mbL#tnWXnha$m7NG7#&g}o0oGFZM6E1xZ zJwgm_gm<3~M)%P{o757#(n2Q>;*V=33xZuKwf9mF9Ja?{_&HmxtCQ!GeVYt=pY~WJ z=v!lrv3cZ_nM1s*AlEC6)&%jqv-5x#ypzy^`GTweZPz>cM}?l(s{fgAH$7Q;3!jIn zNI8H!p6@8ZQ{e46h>(c>NpAi<{gX`fuIH!+-8dVb5b^Xu>mFgkO0$}R5m)E#e&8=r zQ209)F0_y`*vF!Np@(+Ss z98IVjbLw-gYbUPdZZLge{14PVd__@)M9;S-d9#)rxo_O}j-qRGL!akmG7j%G0CB9C zqSe{s`CslP!lUf+AJNvpSa;fY|H#*Y+9@JP78`j^eckj174VHeRM<~6RxHv;V+RlE zyJPHbGtnJQx>GEGEClbzro(WO;cct|)qBJOn+w$NDh|^ipoA(c=Xo5{e=9TC*H=s~ z-DYyER>%>owO8R2XN(U0?ghZbkF+@H@iE`@&m+S4grqHNbc{47Ih7MXlQ;8mthCbW zr4~IH@ph%$ZN9mpoBN1u3#@CM2Bo0OK)8I3xHt(W9dUhQPj93fE$kco^_l%tR4&a( zU9&dk>*YTRqRveH%C)1cDl_2dLIqC8XGM=Kzbxk`YAa0svLEEYh;W+Z?N-D3^=)=1 z3xql(g5HcBYr0#0rxTxRgAEHc-xq(%)<5O52d-!a5GbOgDWUdg)f5)juGB8`{*mYK zTktBtu&=lNrCm}BEXjnd+8H1rm>xxS_8uX>ctoCMflW8Bt zaQ}A{xntFbe8s~~Ut3!kUt}qm&;IDT9QHgap*cw})lTw|g4Ik!_dMupj)Bbz9pV^#%S0PiMTj9cR!D+Lpt#e1Xb-&+ON4sC2fr=`?x}~+TX#&M zM!7Fz6Nb15bqJ_Q0ApSBh)xn6iFyVi5_zHVH8&ZA$<$3D-);kElP~-~OcS^^JC|gCI1EF0yuG>=O`yr zu!Avn>x!ml#{PA*58`J}lj@03gy_lCrL1+FR-`fSn3X4&uRQ-qNfGA|Qs2d9X$;rR z68jIxrm2|@Jo}r$h2{3G$Iq_>!3D!%*s_?T&E`nx>h0;Gc?!#EYW#B|`|y?P1GGP< z{mo-u2?n!rh^9l>#?4~S^bu~D)Z4cjYI8F`Cm8W!ljX>GLhUFendT47%PRTdD{cDS?XzJFKQ1w9=J&9%(u6v z!7`53FHs4wPCf6?5F@#VdUNj^urL1(4g0r$s{y{m8lT`><(_anb{P0P#VAZ94<3(= zi~7ZrC<7kpWdWNw+Tn)io9RbCl-8o)qyw?@Gv;1v!{?K~q<;W`^F(-khTgur#1YSF2s&2&wn1Q&w-2nM3FIM`~*Qz0B$P&-<4g718`1-OI4FVJ0+5 z<=`clOEG~Vg13eLV5v0j6m)}6MB(Y;NbN)5JakR~uka(mP&RLuHm&20*GMV_+eLvrvRURXX9RVL+_=9OK?x$8wI!Md!f|xyP;b1ZBXH4m1m86n@t4e zA7eM|Ky=CW98^%=jXU4W*aeyf2|#W$KfF@l&XXx(TDIUG=Y#@{GzOAJ6-e%=;!<0J zi*#|S8qKKuA-wy*%*GgOT=uC0H%J^b;(?j-t)=!O?=t&p4xAD&kyNPblAfI`cCLmo z*)v=lgC7$N&VC}Y07qzQx+m{H&3=KgVyem#XWJxS3qqg_x^a` z{2#u~u{jVZ3!*V5b~3ST+qP}nwr$&XGO=yjw(Vr|b*r}aU%Yqw^l7oow7rsn0aftE zgDJdhd@rkX8?HZ-@Dl)epMYu`0iDE{^*f>!cR(I>P2w)pw44ThiNspH)|R`CQ8z|f zixHdkzHW(W@-HXi`ajQZ96TDq5fRw0?DIj46CQcFP@Q>QMeo{2&}lD6u6f^Nn>b9= z;U`tyCgM}YX--QAp%28qzJZHT=3^0p;~)dq8dUQOHHE4SqCI|pSM8wl-f3*yA*xl= zFs6=K1vn$j5K=iA8$ey+MP467YVtaRvnJfE&LC+el8Qb^F&$Gu(@cK0-K2^K-90sSuUD|8ZeAmQg-@j<_`bEBjJ#j{*y}N1 zaH_Y_4|kEA3m>)@C?7&`&(b@ar0yi2od2DZc;937&|iB0S>_j&JW2|kqm!dgUS1dY zKsvJ@QKB9dA0fWHsSxBwgI)CXZ$#=BLI+}DDLLwsM#Oi16jB2I2r_2n-WrcJ0)tJV z37+FPu!6)<9Q&$~jVgjz^aULFmczklIpK2FvwOJKU<_Jq+_GH>X$hsQc$U3!aM6pg zrf`2(tnF!IdecAP0U+ZAgUshYeR;rJ8XMILbzs6~IRy?k2M7I zsdza?FMW6dB26^ssL;@}hZiva7(1Tb^H1C2s7hq0X>ag7oY$ZHhkNiv(K2d9csWEb zSL6}5jr|`dGVDf}BFCyT1q$S6i#p`HDZsu7jnYXtOBy5gcUB?LMc#@Ho8S-?OT&4wVprt&p^DzXE!(%X)yeJQa%1;TV%oZnx#) z-1>BC3kZL^QGgZo`4=K*r#|JNYi9R<@~EM<`kWYrRsYL*$2 z+|M?3=^vnw^V&}uSwP7BuxmA^9DzMV;#4&-*yV%VHTxk_YQ^eoqo|LEW&dYtF;G8`@7N`r}N)TSB_J_UF9 z$-Z_*lvkI5WXSScN!cQE!Bs-@xblAODpnRUp&yvH+remZLEw1U9Fssca>-|s!U>{m z(1KSZ8x*+)^h-;qhZ9H16^c#PcsD@W#F)~1BIJbZ^rZZ=`r`|_7%HYNj3If0r!}R+ zu>!O070-UZuu!xUDtE#FDflgwzoxiC!8Q-2jvxom8GhymbnZKJ*6Li^)7`F5jAGjo z;-o}x#;3rNxw`O4(i(&L<{xK^hh3J>N>mlPtHjE;lWYJnN@xT}9pCc&!?=MTrKgng zUH`Uk!r3irhu`DbKB9w+o=Ll{b~cKGRP0-%_wXT%T=pHwuuiTJ+YHM4p!!hQ0Je?a z8nAz2uwUuC=)wv8zgo$VjccHe;)%X6nH6O6OpE~ZwU z|3vdciCm6xO`rCFS=j{=U&^tJvQd^vhmKZxhk-w{FCOr{946rM$RQ0WGM(B}CR~4W zn>N47!w5a%RJj^{H41Jw>D$Oy0QP3g8`mSU_^l$}voMY3n&{Wq$XjF~SQUDNz7lAg zFX;ClFz4ehF=3uZ%|EC>h~%4K)UB#c=I~v`Fmk@w$7xisu+>N15EFb=NvJ*PXd-{y z5m{svnR zBUWFl4=#ep5a#Y8d-XQSt5f0^0CeX>rI-a)_f&sh6-puHqS%=)z;1S#Uy z^utvi$*8llNBk#Vth0jm7Gl!(q8J%Bqv4K>qP67W;0UXFJVmr3Z|bbh+-Ec;sG9Z{nG zK0ntddbfG=U3E+;LyxBhI^Ksed|+F~dmdF54`?! zWVRD&UL=Q7U(9@=tz#=XS>cpzrGGIq#efkvqrWPInhE;UfFaRqjoZifdJ^Ve(AgM7 z_EsvU;!a;nTp|3 zn5XI);A!qW7vppQZkyEkiNQ2LOw7g-3Qxf(UN}6V`c9S*=Xv{X9!$5{;}6z_t=72H z?_u3tEo?H$md>r1(g?8_oiY+J2)&ZJB)cukvk?a)-l*0$5c6H0l#}mjCgphho#FEq zz&%KMRQ+}|D^+XApR{-Z+DgQqNHB!6SsTl=HbX8%x`}gZ8~$WC(z+pVh{~2tIqwr6 zy1`V{(ws4{f0q;rs!FNdFag|B_Xu@yu?hDdmu>O7xWW?qDzIy=?MJ)HOOm;hePyyl z=OJsqvIreQo#_1xAO?LzyPvxqyhCL|4&XlmK5wh<*z$ehns0!HSrK03sGfSE9t!*Z z1nTv5E#)fQ;1H??l(eJL4~T5oIZ1>7s2<>ZHf`%WTI<15Us+NLG-YKuJvayN1vokd z1}r)&`W?bXDAUx}h&uw(b9N&b_?>zG`xUd*#lHm2l6hZ-Fsw{{4xk=R@bRg%WFWYI z>LfiM-nhH*Uc`-4thw6@2}HkFvG9MRa+0gV$-O%8*d*jzlCTJ9Wfxh?%Cayka-r-6;TGMUpz}uG$_@w8 z{NCaBP%F|yIZ-ON-(!)457xTaNp*`4-1dTKjrjKY|wB#YhMX0~#DxcZ_Q&}WO;k@q}GFnE@J<6afS zy@+@B{-SO;N%ij%z{HT^1(qm^1o`cl+c|8jYvmhZ(xYFR+lo@^y0d>L*UQ9kE^h~f zKM^SGSTuK`O$^0#d_UUiiIM~7>_Apkv_-|CHw~~4)Vi#Hh9%*6=7fKR9tE;=0vyyt z3w3dB8|Z<_go`nW!qqkbI`IHz-#LeDM+J=A?T;|#f(6oxY8(4v7Go{Q2T=sl5@b5f zmmL>YX|KCum`9T_SlgiJv~<3v@m)7T(V0EI7JZuJb#RP^(lFkQ*C2%g{RsUji#5B+ zTZ)ryH`=mEU;rAJ=BKg!aMkP;D$OCfqyLbpa5ZbH1w(4T$IU`bH{{+yWyXVRMjAm! zv)&QL(JB*F?N3K6mCRbe|FAV?qB^WNK5wB$qBX3LT(cA2t)6USQQ(mC0g@DDpv`G` zJgJkp)AQysWA!rkCArK&t_R$@WujSuEA1G3xrJL{%2dWGR^iTma^l0GASQK2MZ>Q} zWuDjVr(Z%g1amjA(km(oRABco6joc9r5}V&3BZUZ%G%ND<}cN~t`N)?&^>aFah-t5 z79V*aWGzO}L6LT7%4Yclip3)1h4nX3UT7!P#XGFDZ`C(c&9|E+9;DKM!)F9Hk52sv zJ-pR6FEaw3U>h;u+Hi{lI{k*09~vV-AF|66IU+F|twx@mqtlm+Y_*0zieT;ceTC#^ z3VI9rP{%7TqlupN&r*OGJwOd_KBKnF_o$zZD%`)P{fM*^CN@ZyNexB+XQJEYPl4oN zf^39m=K?jQoaEN-3w#*gC+pbhDTGZVLB)o}un1jKImbVPebYJOfZSV2srk#=ce(ay z-s@G(ICTZJ3@NODOL&hm&oef(bzLQirg`(Nkj)pP?c>O--KpyW5e9-?{e85LC&5q+yA1KiWKI)hTUe*{L` zx6~{6A1x3t1c$V<-Y`@nC?kUIhc%}GN<-}yjR(VwAb-J*RrC9C$#f)~Y$%QnQ(@wufgb2aRfFg9fQ|E}FL@@ZmUihN1YxQU)`N9DZqj ze1`Bov=$sKV<*L~@#D6QqL#Zm7uK`;-?6cC(7R5UV*Y`!suD_xltv&K(O}2+@*FYP z;(79fcglLgXYE@tSc1>vIdnxHj->p@4j!C1L5qeEuMFy~OHx_R8`9Gzht+qe!z^Mj z3MC%!H^dkp9>osY{~lA1*}%z1mBTlvnm|HGM@|Bx?a-h?@^uV+jaJ~8y>Us~6H47=ta}$Gy0SrkO;?|$n%i5SAJ;3c>S@k}wRQ1XBd~w@ z;?Ra}Gm{<7AQa)6{hN<1?dw_Qgsfnke?TF$#Tk~HDBqjO|oUtM)oPqNa#TiaF}zdVw~oTqt+ z`lg0IxE^3F_2~ z0!-lsmb; zODK*T4DX>6pu16;!JvB~!b~x%O{CJFM@mXfFk0A;xssWgTGXsQ(I!2UtwuVFH!9ur z&nHKDO?|wdlY@#AaTF~m3I+tcK^AlFIQ0EQr%om*r_-t@qPhGEY;nQu?g_WHW?>hV z8xDYKTojkbq8FCVUN@JyHFe8@Qc%(SM{EsH8^i-FgSi7i@fWx@5i z;&e`J9}=_He3*sTR)YB1c~VJnRFAADi(7|#c8H)ytY7#dpjRE8)09n&^a=RpCE=+M zRPG=w3pJn|RA~Sk#-+sZJw<-T{J^gMHD~>sTq*AzwupLkmt;L)y@tQztP%`snht8b zf^?%aDJ@zmr!jLOnSxcRhr|aeb(~jwJ!ydrnGc5wr%;%8yq^JgLTX_R~!WN~i9xH8E~(`yOE z*cWI7?rG&-k|iBOKgplPvh4wBEgfQ9kqlLHe@}ehFUZ30CHu9NGyt<{>7SR{w!{`G&8%zknedADVOOD_8ZeP`Zg(;+5#`;iz_u~5%n zEWB^bV`Wifyk$rpNzX7Vz|^>S(%}ATLn}b;&`jeNID5+^VUJmSBj*RbS0DD+?dEhR z1)!y`0c!N2Wr$@ez)Z!X4`eN6IKfimgO)r{arbP~q0i|AHZgSjV4#En-}$1c>d7WO zUHQDz5~V=e_6Ny;F#dc4vaxiny}*FS0p6&(i@#7UEHNmRf$9@$w;#NIy zPUK4L`DSoHC^8iYNBz$CHVXYcuJQo)VUK_B53#b>L}biWt^xu!S!D#HH<=D8@(i>2 zn|Oe%3To!?$w7o{kQEkW}6o@g(W_)zmBC`Ou?hlkgO56ly!Gdl{NB?z zWM$xZOXX_PzhV0%hf{{Ii5AYlaNrbBIAh%2QLrx_f>t&NJ`IVQDcfJfM8L0US8>4Q z#|y;nUs2hBmK&KtF7#~Vt3nJp$foIK7!e;jsi`ci!9)FUE>z~yY)JYbM++M=x|8-& z?!v$|yv}Pw$qNPtR*8| z2T!G+Udz*A#+T4dORG@B13SZl_Yf%-139Z*b_`?Myqx`tAVt~G>~2l7DY$XvuRDM? zEK}@`XIhK0g+%0O8Wt!R)dG-;SiZ{cm=J@tjY1_cnF5&;ea?KfqV}K-`(KPYd~tFZ zWFtZU$E0@2PYSKi2-_JNECV+Xk&v?;x@Q7M6NhIBPP=f`$ zlB|$=b!d-{Ge*|JNuKy@6QYCsh^N2kFoAtW7bp=%e-PA4SWWqER1Z^q#VjyF+gf<* zseKnK_JpT>-?xRY80jEG;liHo=+*ztZLw_l%KT}VPHNJ$+yuB+i&~7xgvPQ_+4`93 zdeOg|Pl{-MlUXfzUUL`g&P3QG-+q`1PA-WBQcm}+>k`pujrm%D;#QB_QJJ$$7P+WT zWV&X8Xpf``ZcfdD#8J`y?I-I(xFR?8pGkOF+d^==hzigOFHcIC)4bSt%4e5i$IaHY%pc8@yphUY$_8m!-LjC`gb zAxE{9WXT^iJ3gV?!ySDF!re5Fz|mhuZ;|K34Au}CU*@~$v81&g_iF@eV-J;e>7hI6 z-Gt=UrgV_T)9_owOh}$O4#YU3Ft=#j!K`9!x`5bdJ8BFc6=&G$&WLFPf`Lgg2W zrrY4dmymhoLIypjegAp>5zFl2g%lH?MtCc+XOTd=!G6zi3UmA`)m?9ez$$;V2{^T~ ze_h&FsYk6hh0}=EZx0FMJK?fOUL~wVxAP#ya@*K4bdz9^)9Qa65)gi@Q6Gv9yl?t( z0RqghB(uI0y_A5^&Zp$IQ?{Sik&D!r9Z@6SC9%OZ3E%8Gxq}jet_nNh|NhV`7|a7r z+rl}fjwMpC;qKI>+Z%*D?lt_?^iU%d7ge|Dfr)Q}><(9F*obtY)|vhb=@|4B9m9NN zx4=QSgjVQwXd13dN~*N$a!K{Q%pM%2Cs3bL+4vTTaSlJ&z7c^E{BgtQ9Z1)lo+(c| zLP59epyVqG*0G{evl(}0C?R9>I~MJeXw*^%Jwl4vh(XuR3X8j18a&vhcs2>lS7YU( z=&?=W6i1|=gVa)^XR@j(aRwN2P5RgLr26TuWmq!3YK!lse=dg7zax3m^+2j-x=JN? zL6?IW6|;<+njQFu3(iB)@4j%AGA7D6KcblH^vHXrNV)jC;EHU_1*~9BT*~M{%Je!) z?R6Jc^}`eEw(+F2AwkEF3zZ~v;iM-NWSgelwFZIwVclI4oS!(jgk(zOQ*0R9n@W+V z4D>UN?%x}61KKdhpi-45@=C)@)NHO{mf^TJ*O=NzaJ$C=$tp;nDa+t9-wEd>KmY6a z<}_&Y#3>Y1=29UwoT@@`ThY+zQJ%om+%(`=p=^g?QWP0(wSBf=4_4kM-1Fy|9YK~w zm;ntzuHz^Pg_L-3{LKw`3%D%BW9eD)!Be917y%dm2ch+0g=q_Q-VSNPJqd{aU#(4d z|5e_fQAfECpBV^^j;J2}KGsH%_42Yl6SXURJd&Lu_I128%4IdbZeJlxduWv;z}V~%-SeoxS4tF6o@TyRTpYosO8Bk zJU|6C{z#O!rbfi=wb}*DBFW*}^vL(&K;GyDaP(KeS(9B1ZCq7fcRz9#@}*RV>u%-= zFBDP``k&)SOK#DODQ0lbzNaO#(E}qqj(UKfF|8|%#nQRV;Xstbzz*CL4K1?r{`_6C z9c8w_*B$U*ZZARto@()9=5+4!y6cs7!Wron7-f4iTnvETu_9deC@Z#!Cj+Cvm?%?s z31;Zpln=gJIS2Ui1s=D(Ov<&M#Z3(t##n8h*0vB;h3)GeWc-RjBA|%dR;R)hYi=y7 zr=)Vhd>kO8I(9fqM_iN|hAne7>l!`OetxhBt_u_#h2N{~i6;3__nYRuWqlK-EY%pi zkj&w4QeK#~M@0f9s@q;admli13jPu#%y(l|g#O)DUG4eo!7c?p%B`=lp9n0k=sV4q zCm%h0Jl|adj(*X?cqeu3nV0R@A5NL#nrB;74W|vnJF{vo=Ja!Oq)0EsQ@2lhe%)Az z<->U`hoDiY`WTf-ZFYhH^Mt18Hd!jx)AX61-if1pRp#4?ZB%p&H;J`|kS$RK;D5Oe z7aB&H?yVk0x%P=bS)ScfFz6dI8nGMyKPQXvnlYay_YRm{LZ>&qU)rglV!Q}2hD_lD1pHt8Fwd4i*!-uDk2%~FZxUs$PmZmlHq%ZHf5zuC2z zh^%=UFs4Bd4|rKB{w1OXovbV7Vh_hL=xfOAA66XtL~Lv2b24mZ17i-rwEw#IN6}RL=TOJZ%b_ zGf+229m6w~`vJm+13k6Wt{yb2xrQg`OnBV4@iX{!Upi@3`c3UFYF)e^5vabaz-Q&X zBYIi+qW;7+7?4M(##Va}(mw&owI}-Ex))^A=GWQgIP&WsF9{h!>-!l!ywyX%q0CZW%#xweI;iDv zQajr`+V~s4ypcSYzu*if$VOGs@F-5y=G2V$szfMUt@KLldYFTuPitTs&!U*aJ^LA7mBGkO;@-mihWlm%G3m4rVixV%oRXiQVW*#OjV(|b z6BdEhZ9CVDcsL!1SWt&I>HULg*-?r{79MzR0$fF~GVnm{j|(x1L));^dK-sjig2cF ztD5}+9~XSzEvjbC%0x#f;qevuJhCb?vMMEh@-c*SqIthe3d&bp*Ca3Z#w{h_IOUVi z{41>9cFuqS^Wa-)bMXAyaf?JpsL8t8q4ov%5>t*+xUy%iqTm6hko%@+VX^yD5Q`MZ zY1reek7^DIoL$I@c+Q_Poaltpy_CMC48lVcZEFLhJCUjb0}sSaDofT@iK{@yr4 ze9BQmuTt7XsH)FCB(PJ4yiUToYPKy_G#%pz`WPvT;~c&^L*$JDaqH1bpp);OH#IZg@VK1c zYKWR_vCOEuhuHIOA$jk9iI9COoBy1m!irMdq9jW*E;>R~SAv6Ed?SvGkVM-l8aQrO zMc}h}thnO#Ge(FqB_%ezs@ZvwoI^QDQu=bSQO`C&ef^rWCSK{_+p8phDZ-D|hL!d0 zlKM|GTXWJ#%(60FpCTYMXOQ$*^KZ#3XD~54>?&i}n@ex()9*;274kgtI+FtxBVAF3rn^GkR1_!7haGw>d?xMCuJH%n6_QZj+=wW z6Tty1Xef;S)5%PtvXh@XGE9PcD>(mfh?pb0eAtk#M&zopOefW0j@x zE}P_};TMF}(cPxNv{=+CcM2l0m!ii4(%^XWagB@4N0wY|RUdr;hbTDj8V^Dyd1`Ng zbIwbPFyXp?&G{&?lGX`x5tm^}xEV+FYnO{ks*8j@R@{k7V|-J&E^8ucO^^q(%3i$4 zyRW!4b0u;}g!Tmr%*$=KHHj}$Fge@NMNS#54A5Lt_4lrEIdw~~1OC?Mh?+E^$B9Gf zsZ%$sF|-3e1vbb4UIE&ta7|!e^~CR5Ft2LlZtp--4w>c57I+y9fRR7FM9kX!LCS#?F1uWYwNp}l~ zD!Ux&L}Y!r+`9Ra(OQ$dq}H35 z!=$%T8T{a4!O!B`89oU8qnaz0*UG4TeW1NE+(cb2Cd@0?yr)T0K(N%Dl*pGGY2S89 z!h~F>Ohg4efUug9x>GR-G$e%J(FgqacHY<2`zQ*2H;Ge`vz&6BC4^-r4Lf}=SE!0X zf2#5qvPu~hcZa&A2^mo@9otJnY*!hcp!;E_Te{Sn`BE(0g>*E9;5pYc$1$r;Pcocp;eCY{kp33qb0QZ|8 zTDcW(j)Pme(`6-X(R&%@-&WtHqtCsmXOtGc_} zXP5Ezy{Ma_2pvlxKlsS(#q6&eu_lop-Rz4mmdaW`#@B;AI6+o z_NK(;jg9~y(ql8i!EvS_I58I5)cfJ0V17N+!=js)&z$nW%;uewQvMtth2vP18>q zSct{8{AT>|pc_rJOn*VQNpv@QA`P=YgdRFE4E1nwDJxzdPYF4oR*$tRnGDPZFyYXz zL$oO$^)vr%=@p?vNzA~+&a5n2BEpGF|II(T^Mv!89FaiAJCdlgZS?J?n?X9V&c_xw zxlQu9W^Xj#B;|-{cjJhs0arD;WiX?SF*)GGD$453$z z9tUNFl}4y@o%U9v-MIKfNL4RL8-D|7<`Rj+$wbqxP4#w7)h9gwO}|3gQ%|dj$>n^X zyez;$Evw+4v8qV6%?UuTCaLJ|^qC3=m>=LWS7Dv%P#f>!IJ2K<+69Y+t1+)^Rc$Z0 z%-WVZ>6qXl9#LC9DA<8|3jgCmMTo3x?2ZoWOZ9eEIW9sAV8>-SH8gva6=-kzvp_Hw zf(*386ED8fnh53DLOA)3Zs+#Rt0zga*_+8(Q5}W0N-j~)7z{A5t{F|c#q1Zmd5(f( z@tq&?0Pc29$8Z?oy~s;746(@2@Wole(y3T|HhTE-#q>dZieb^K0cuuPIECE5MDYPI_8cY6%O{NvKF zKGh^1KlX}{(q%D9JIns<9%NO&p(k{??_6|3-LLX9IRCG4nwoV_jr+}Y*h<=mNCHy} zhak`RSc92NBiD%V^*WaU;f3N%oAC;v8zfy;X$a{iqkSLdAK^Fb!%g<(m_!V{Jrtuk zeXZu)7F%XL5!A`{1O42pJ!5i1JZcimnf}_qm)`u0{~u#6O&WfyeEq5T*bPj?tgBEo z6s>sRTc^5B1R$IbZLb9oRbTHO!K<6JhHV$xWgH&H*QJeZh*H4{$#Q?E_02^pTvkuy z)o1BCD#Q=%w7$jw_(5mVoA(1(YsrzZ?RJ}zk&J>I6 zIXIa?u7*VHrAj!GR!fa3Q@mA~lSLye(gjaKY6#iD*Xl-`eQ>OWJ=kC7`d?kGF8B|7 z3*%5bFN&DwX^9o5;vVdAPng{o|Ng8{1uYMi6n(>xjGAChiHA$bYcV6jsOU-sJ@#(X zW~&}o^ck8d@Cvdy_FqaqWUh()h;XNU%mS<@Y}|2lIb-fJLLpHO9)_u09^i8~JQmo| zP>rBAzxD?ZhO{bdW%^3$f(9D^LHAJkoR2~)raj-Ap|J91zRI@e^i-b4<*;4fIBosS zGx{XqYakS}xvUY>?7q+_P^7cpE65lga}uVy#`Vf|lbjb9Y6w{!Ve8jAU!BCQ(*iGk z)s@IrL6X$6?6{tbU!V<`s*9V913CmnIAk zSrK6YzZ-SAsi+w+EtGTEnF9e)P+(s$S+wHkU139uAas56K5hQn+YNelr{O~~_MLD* zGxCLy3}z+?0i;-Kd9K<8IK^wHMOhu~e|NOommgiIn~UJR+R+;thcis46_ZeqoE)z+ zK9SX-Kl8|-fDjLL6aOll+}HUf7Q1y}YY27{)k(nqS!0_>rx0J`>{m3H_;C4C)%$sJWZ{G#P7AL1} zVa12nn19fG^5{u3ged^Gf_T*Kf-cC5iAZ#Y(<NECJkN3WQhU zJ&axd?>Sf_qXz5O-$5)7;4@m##sWb)nPn~Lqajik(w>_qD@8kACUuPETqTBTS#G{I zK8a)$7EHks5PQ-!kboH!GwzBx&P*p1?PnApp)wekv)W>UYf@*sD+5hXV4Ejetr^MjuNBIK*KYdBNj?0INSUrx1>?9xrK<%(W@-L{y z=Pq!19rIiPFNm3he@t7r>2c$%K@?5u_a0^e@Is~2C@X9iLEWRTMNk19+#pu-A~qIh z=@{xF;BTZVu7}$Ze?7hWbh^Qdso3h zCPfPU%4N)LQ$~)v6b0TE@f{pF;wn^1z~i6p#)Jmh&-gcL^YJ2eYGq6(Z$!o)&IVEQ z=Efik%}(w9s`<#9#OcOn?j6i2Sr!2g=p?&U-W@vG`rNwVqps9!;Df=}*=$)YO)A+S zjbgOd&x|3}jHW1PB+u)?e{&BVxnkCFq7P%<-J3yL8e>Hgb)Mh(36Mn#A9nEOm5A3v zP)oeikWd+B>jy!+b0LacijN*OjFjp#z9N*@QJDh*IfcF}6}Iq*`gAv%oN#k*q)+@F zUMt!~|1zsP^a5wb2wd7Q0*e0mjL7eC1`*ZFUr~SBcc?J`6}m0lqe9nyCzvV+mirYl zebmXkI|S7&$6HkQ_cT2di;oXz=YHwzZ^n`q7rGIyZo$uJ^kcBb$P-f;!=8VK{EOJJ z<3%`L<hLT2k8VR6XZ;ro}6XZN?clC})-@5hRN_Wx^2A}%`hNNL-vEvN_$#R&#{2rIF zg~#xrxJ{owhKTy(EoLa%aoHIV4l{T~ci}ht5y|I(>9} zsJG#!TLcZbTyVNv-O9amK3&lW;Q?xH?0d}FU?kC<_OJl7a?Uwd9W&xsrs$=V-A)TY~7-W0MBGTjh?S58R3RA>* z)me3ta_eTc&0{JI2w@Kj3hVGBN>xL*1m0bhzhn$()fo1WWybo+ zf8g74R}K8G%Fr}KXh7FAv04%iam-377HZ*svR&sO#!)4fvmVe&+4+`mL0X3VdUDxJ zetI@gML~rPP)nn%s`;AY;!Y(S&*p#zr0D{0ll5C(hrJ3`;29QwFVPeVWuk-F69o=# z9i1+NJXNI!PQo}8(-HjU;jQ3g< z_%s~zLkbrn^4sX)vftNoFyKbeRu^L99|iz{X^tj4EJkJHaE`@K2w2s{{h0jdE#x2D z2|g;(y9e;`*37Elw7<{2IClX=$3tbL#Q4^Cg~&!mc-+4%rYv;5ZyhcW{Q0DNug>(C zsE5f8VLWfP*?I%v&p!jQbhsdR^(o<-@q8F;o70-ke8it`{=|?uhDBV0cvmtAv7Mje zt38Bpyn$Wy)lx&u0~@NtdCqn5iiDjqeAB(T#GQ7a_-58ipB-A@#4IGK-U7W-k-OiP z1#d+3ZMY{;L!Hg-yjB?cep+h^2oUs|r~X_ULR%C;D&M*RO z?&Lc11g#53+d^BgIkyaZ=W7dyKD`tdUt?}P8w&JN2m+0SOrC|Y?(eNtVt>+aCtVEk zmCkL{E;q#a#X4U0H?*^Q*`6UlM4Vz;_I`*WR=1-G#Q%{k`w{ix%>-PT3Uj-uy zPBjf>3mo6(bEw2g;aDq~-J+;R@sW^@u~r<$%AXmZmjt; zp5eh5l{PP=BtH!&Y`WjoKJJb#PGzQ#ac;5dE&prW4b9(;Y|OZo@hQeh_z-k4&bI(| zKV&WZ$bv)pDd;(VN#{72WQ=1H<4FA2Z=)KAv+(XCQ;>W|$6@fn7nVb~+!AJxm44AJ zu`Q)YcA|@bMxC8@K2yjD`jCaXD_^fep-m!4@!AF(b?cl>Ykd#xcQpZUyUSTj%beyCYqbYr)^XI2X9|5D?qtZ=_wdT-c%Ecu*J0&jeM))NZLERw2echygI94}zi0AIs?f38`{samL{c z7H9c2HVAZehAl+`wNMfB24|cMWvdqjwwn>{gWEHoZEUq|9ARHO*E`S5%uThO(XC`_ z%6*X1gwMIFx5ORrEy*OmJbk>TJfg{rg;~F>2H`$Zp|VJTvBT*CB`axCPEHlZQ$QF# zJr^XWc*h(;<6^IYV+2j=!J$g=)sF>scP)pUPeJv3SzV6{N3uVp~v2fyu_x zd0EJorQ)ASGYmjDruJ##k0fH%$$w0EP;4n%l{-(})r+86OF79}|AqC@IO5C-8Qogpvfs4$`zi;c#jKeZ!HaI5K2t5rfzzIk%K!?J?6 z)82W=7snZUW)Nnxq?JqC(T4Se7(Qb-MJpm*jS!vg(Bd4D3yrQuLuS3FEl4w4D_89I z5nQ;5Og(;7DGytK5t_BO8<&g1p`tiAu|aE#O$KRLIMK=C~^+T$OrxmB^q zJ2GP<_rPeS=$*BdHH|*git6g<&m@GCA8AY(4S_8m$foGulR7ug32ol!5^)@=hLYur zfKN11{(z?PUf<5@yPU8w1rn=)T&^3md*%?$XV)VMRpE?mtiVzvGA9}ZL(KZ1@94*Y zyL)kP=0Rb|u@CQ`QnTIy11(oFOl1pmurWAdNem|4zsMu{pb;_il^V!gjhki14E^M# z+f?j*IS4X!Fi-m?ls&~TM96NvXcxuMqa!{m_Mp@_q*;<|aP6*<62Esz6Gks3Gx*%r z+EtK?&3>r7>OoP*<&s=|fh2L$u>^s~cKF%b*5ya8cyR&b15Uf-f`1z$e05JaD>y@*3WW}Ak$ryR0 z#@+06!Uw=8#yT4Xq#ypNa1{kQUszN!C0*cMn35I^;%b$uLom*W@;7EvsQftu`0}FT z?#u2{ixc|`Q66!fuqi`)#3r>@SC6*nYdYtp-A`}T-qp{K?k>MM5bA9SLu;`E#E7TD zCNTW|-5^(^CfQ`R{8VW-M_=PqPA(Zat|Z>G*`XdGD55|fm{sK|)#7OB+zMl!@Fa|D z7c8E5Pttla3p@^W^N-^Q`phmv=a9^4TV$jeq-0W!re}sFZThoq+bAMmHJsZRphBb1 zRlHZ)c(qNzX)T);)p_co`lHGAKk6;8s8gU<0OH+7T?5b=fD}N6OjxCl7m5Av&SE%X z*TFSNR?MviG-+YF{EBmD&6E#qGPu?FlnCBKaHkl*uH(>k!L2>tDdFq3RWOv=K*{fz zW-w-?5|~q8ww}bW#wEXCW`g<9$&tjjou}`fC}94`t>TWK3`%s2D1V#2;}OCqU@HkT zy`A7B1mTarq3IY)(hp&{3)pcusn?rK-c&DP4~swnB@^_c0_Uk55lqjLpbSC<{9 z0@K@i_%i!-r3#PN`+P8eibfD}(|ggS*&#u#9RQ?}f(#f}jmgl!gq5D1k%gVb$iSF|jsE|| zu}9elhQu=q&xRYTY?vD|EX~%OYpbhjnO0V&hNgxa&DI;OzP7oJIX}Ce+rAbi?Uo)D zm9-VXK=(i2bmZBy4?SlYqKuuAt4#}@Hl4CtYQwWFdILQ5a_=K|u>*avSEfzE>CVw7 z&X>a8;Jy5Dlq1VU7tebIsh)VCj+2t#4w(3M!htUH<{(T1R)sPU;~2Ibrd=0;FV5-< z=~!HV_hlhF80kB(L^!%i0Zg1qjf~Q=MrDCvi-x`dOa{o8poRWs;vk3ovTSawf`(@L zMu7LTD{*5H)K`R}Ys{`DqSnT&(O;p3xJ4!2Fes0puj(S}p<;;{b!u7~c*Vmqw!Cs-s&MhLWb<8DV~ zB#EVd8x(&zv7O`90ls}k!<(*5{ps%`LI?#v0frqNSiBx~Et1C9e^D7(8G-SaK?P?| zHAeWrI0s?&o?C+5cCVJvuovJdv@$Q@R9YYT?M%IHE0=X_(?C8#^`Hzpht#irzIZ20 zp1Dpid9Rj*XoiA|uvn)DN(fL!iBn3|2v60hOkLMUJ zjVNPj2{t_or|{wp4RgMHBxCkCQsBTIAU!Nm>tcVR!+WhhtL16;!B2?ft!n1LpwoVX zHsdq_tmQJkpL5X;(Dkbb>3-co;{=br85!iuTxGmHQyT6g3Iu?9+J_O?7sTVs%)XENJ;|`iV$4j3Tm34@w%|I}M)*Dp}#~n1dztww$si7H!X@!+0aoe}1}83u%>Z_Ht||NVG9%9YH6kw+AeiaGYr z{s-|!R6`JlnTK=^{xp|K!)joI}ILI zt+`Wf0PQ1J_|fLhBE!4nRPY)US-zFH7Zs-R46*A=LaI_c#B;l23s!V4vHh5a4LLB| z#Ht76AzL%+G0b}EC|PnvYR0}n%-cgFVd^Crn9ho{)#a8-FMv!T7x|(FX6INK!#e%} zdOpNo;g?Ouj3nZ}kj&?F!Aqe>VQb5mLQk0&Bi~{G_&~bdzmSsWh*0+KD zCj_(WZTnc^w})(#TLocJ&ZKioKt^l(_Y*P~lz!FcTWngx*;76o)>rmB7bpgZ-Yprp zr){{DPWbFPTk2-~^)Sij(`5F}4IMe>VL3ZtK>G-_RTq?TrV*P(jOMV&RyC>I=H+_O z`Z^z7H7X{!`*F4=?DvF-;)wGWBT^hC;8$V_8j-FC=FL z#pYPo7F=Lz(#eNonz&6L;fedy@nSF5GFu4_vdhC;PT9)`g5&nFaVTNC9 zK=*focYZqqM?vV8l{Rj>Z?&bV-SsO~_!5aUm^Lpg~t+sFokcazW z70|_^v-bH9emH)3@I~a$X&}61;~J31@`G;n zQQ%k`5E@X8$wET(+?U{@X{7X*x^U3J|9zCdyuQMK3EM%(Iwn-X@o#1mAEL>AcQOI1jP$DU=kmw86vL-+%R6OBW9OS5v|nXt zEs)xn2Qp)igg(M0hMRg}DNdv!mF^J1=4y7FWP-*%c1&)`a($0M*F zo;#dX6&}m!ILb{8O@1uuh7~xKF1bKA(@$uJ$-dJ)M1iBeCT<1&aa2Y6Fdbe3w2$E@ z-HpU(LAUHZ53<3I|M3G{>F2?6Hj1HfqLW-PwJiW?vg9bIDh zlLlkNgi!b+cl6hN`QLr<>w2i&(xuqMkzvS)=aBe#qMzcC9vmFeDrG9&5H6V#&e-lp4oN9qw+~#l z3#;rqTD6NW=q>ijqK)$?h@;~H(DUiSvxX0vyd*Z}iW_QQOpvw;C{A}-)5@%PbWI0G z7t|ZJ4E$@{6{T@Qd-9&~D+J_W702)GzyCT15fycKyl8j4R6cKhi8r7$2%2&b4)u-p zMmvdlDIBE6-I!3$ysshvcSU%<0x-Q7FOqHQt=@6E7Qn*9)5A zCgAj{UY8ISEV^(oXxpI$W+!Xuibz& zHpg(*M{YIWk^QL_h%@imHz*V=>zFq1$Uu#O%ldQ7m0hchD8)YwYf%-@K7^mULBhk; zr_di9*a3Eu*Ap^cp@+J~>2yDT$E;iIE$~31wf$E3{Svh-iwCavML-@6!YLybNywo8 z8S68UpQN9C2ZL-c_gP7*gAqNID+ltY(iWG)kffw)utC}*UnnIYk4XKvgQi_bS}WEl zwCN8K)GrC{+LQz#ChfTHHz6AQg1$4&zof{WRIzj1ju6x^9(qX_1lkSpAm6B zFjNq)vMaI&1PI6chq31v`7zzWQ4um)CxHhL#uxKK6BYG z?CW6DS`WDd>tJMH=cWv39|mO8{#u^RtaAModUIEWm8B|Fd0t4U_}1O(ua2#l!?94(ELLYd(bXoC|7>61=aX|lw<4)`Jjyc)_Z{Z7kVtZrijH|$RmvL zke2xklt$_o*|Y~Bk8Ei^*VBB@7bZ2Pu!5^Ve#T^X$<%g~AV2Q4gU5|-7?xy6spg1F{h^qePj0#<^cM>{@3;w^b$~RM^kz^ z>>*~X5|N>|i8SnWd|`eTo6#hRABg>6^-m%XM9FHn35!_9w%&wM7?@>I}1cd%C^DD>!xpXU)FKJ(b` zYnz9tnv5PkhZKHrTOmFwv(~T+$Rm;si=v22j(~Ng4~>pTmjw`830f?;!4gdq5+qdKyTz-QKXt&MdeMzka)Z^Pv`E-@ zjo2Z)@r_wQ6q1*dcX5fMHmOMwVL+RcG?P@++p0JJAwDYxV+>1g|7`pl7}snKXdl|5 zVAn&@wvQywlydSV#zqyT?vr=*52l`7SIL(DY+ZuJw+(CT6&-n&ZA_XtULgO&94%5n zyB(E9d){2oE1V^3d;J{5^OmwjjhtQFe!4ccEs}NMkjs4eLp8l&=-^%k$fHW^J3*NZ z*Tfy9elvKjuGPu>y8(uwd4@=4tPR7%B?Ub!YY4>j9|?GkXYa{=U`d~nNlKz8gy zHuuksEosxgWykTyZqiUI2sOL4No?2EmzZ`wc0l83@xX9njgH?EAwTm@i*-|wJmGnF zg)W7rxwiX*q_tE+Mh8JM5XWW$+Q%){9yDKG-A@#;f+o$-F8)i1yXh0{;k@hM{S~P| zAyJxF&tmK3R@p!P@}$6a013z=JP%DgD&8g_szoiU^)DjZ*hQXD;e?Us7G_2~EM#;v z`9Zh(#B5qo8=>hu{o-H&y8dxe{23!$(FES*FIGR+6~Q51ubiRWNLQ;*aE=+5igF{V zkHSY4glRofQ;iICY(O3-EZh}yPARkPy;PvZVF@-Rivv?{+C)-D>o^V1hlR9r$fzPu zG)AAP{I|q6{{mFlvn%EyKci{fea@ShC3If1avwd%U%C$gebCEFJ6e-~_K^g}>t&X^ z(qT|KKjD`v$mWIDc)+a|KXK?Lu+sjfssAM4smZ7+-FLilrLMmLU0;nE<+a~wmjB{I zB=7vz~HYWQ^=S%fXS9Hm!%_dEVl)C^41tRXO*#K zaZFw2SKsXsI+%mmVCB1XNpK2&cpRwGwiUlD&r%=@dI(jkWGbw9W!+XGaU)v6`m5JV z05#NREAa*Dr);ZrGCELx0!pEm`>fhyCa7=k@3!>SZz)w9kIdN% zM(ZrXj2d%3h6Mrdj(|M$wy-)qNKzCOE|Gh1#c+f6LF{h}!z04a+H57xvW3L?PSs4J zlOI7*dDsWo-$Q}&8whro_F){}xH7K7jMu8OW#UjG`tSJA{gfSKiRl#svEH0x89{Js1k%_^k^gF}^@!R4w2v)mcSXQ?P)p(+ zL3((3l;&kkP$01~u#Gn!%aSm4%i}3nBkCUovBvbBT5HW#4e0v?fen7}TOMD->P~#v zpL*hNrH-d}c?9@4ggLQUs&)Dv6)w}^vfmv9tsKNpLl}Iv z|FVrM<7oasCTJfBxP`ed&*rw|G$=zxylin0gkYfh~t!UcIs9I=auNasHFok zm+ja3I4*(WZxobtx=H#w3)B;JE@ujcp*!P*l9Fm~T512dTeC&+Y9lbw8vpH)3F)m( zp(By6aiHtp$?`6Dcw4@he&tzmz=dDz3NJr_z%mYy(iJdDep6L*@{jrh2j1qo1~pr^ zSq$s0Kj+6b&rfp~C*}?kP!FwHOAq3>dQ{8T%N`5aB_atl{vN-|CZA8YJNih@zT*G( zwd-{*I$+3Uwe=<9`C=z&$O@C~y*9l>1x>wT(FQgb1M)~1X%3~72O-T84P1c{pZ;|s zZQ@u_XbVs}tJG(1q>0@Fo$&n|;=W)`tA1oivyXs0m_0hQ9*xg6UJd)lput>s+3_k$ zBkNe`)qwv~2IA77A2Tm@kqfz4wMuLWjU93<5U=+I|5YQOppCUQ@b;ohftPQQ6=jhU zE%DK<8ltI3sdV|I{d;DXoF4UGzpi)tJcMSTJs}bl`&@$i*)rlCw23CDt_yFo>%W-7 zQxxoP1wi(j%?xtG=-q#EGuoBNeG3zyH8wR$Y|;)(u261dBV(!;Zl3Y(svil5Iae1g z69Rfav2;+0QB!wJ#*W3Su&Nu(A|K07*k?q2<^^qck%=Q1f0Q#yBNhaZ z7W~GFcx^;Uq4%0>BN)Bs(wB4HDB+vvbuab92egkf&S0+cSKaK@)h%0==Yy>g9E~!5 zaRMPjT#qD*9^^CiqxKeMeq;FWvK-h0fBu_^a}yiklPoFQ|d+OPwAj)YLDy! ziht1+F1bv=KUJAJ>bwHs1UasGB|eSAj3GKs7`iFT?ZyOgPA?=6-}t!0TBW@!i=zN} zcpCIU{T~zm@SHbyD^t+wrG7lm6`L7o@z#4a3cebs?kba49?Z{GU^q z)+0VNzq=&(>0>Nl*O!GE8-9L9VD2QaF zE|7zq!fUH##D!$MSNP91WwR+IDWLTW^?z|Gt8@b-RDTSY-w*V%)mg>EHG+HN@`vZ0taVF9qarHz$qR z6${3Fi_&q~|0Y3W{wzqis8@5Q&p4uR7$056uVdB;L+ao{2`B!*R`b3Bs7h3)9owj=46BG z7T7HJ;VDQ>W{wMQG`c;CEK#8X-5;)jHi%JEb3yEn$I+@l$>v}y%hiI_qH1jLA9DNH z=%ns{F#0A}KQ{)!13f8DwgGXJg*1Bc%xx(@*jbxt@#BrYsl2L7O+wu(W>tNLxJgk< z228ISV#xQ#Qzke2b;BY+9(yX=lVrb`dp1AlO2|#2Drk6vajZos%jH#+iQ;aiL=?IV`UUc0$ z+ho%do(McCWJs{WJthgc|59Y=M-Gc?2_?h*G-x-?NL#Fo1=>x++sMg65 zEQ+2CEqOrah2!rM7@++-RD^CP945%sfLM5UNPjjhYTdWl)<0DJ+jB7_{5ECcVEV=g ze?mYFbz*FaR%D;o0eKi{jI8+6I`eT=oT=8T`9U?|yXLz5BC(#WnxRj2=miW+OxSIc z``-yJ5lu)0AE1CdGRS-FxTp4c=hkRBx=Gr&LrGJov`o@Te8bLm?Ope*b*5 zx!gqW9b>^ZF4t84R~$=HjP}@fsO`k`@vffy&9YDIqEQ>@`vu9jTS=8pWIt$K^$sB z8Zbg@x=JH^*zfUg(3`k$7sDh(M%Q0VRPg<4aq2t>kVk7(09C_G2w}cf+OZW6q9m$- zk86KKOFQ|^kll#4zzspa2W=OQJPX0JVijkX)-|I_TuIb8)gHpX08grXBx5^%^^dBw zm~vo0H5x_0Po`K;Vi_lbpxWXZ zaYO<~CO>S+f&9epfBJhS>$MxxAsXUaGqCP5BDoY-$v>3k5m9%JbKtdyVCZ=eDv z$|H5@v}AkEV{|0Ri$B@reyl-QG%<;S>BOjK1+x5j2AY4AUVCxF`0h#r$ZKiO9UJ~N zmhvkCov;&%R<4*X>Jw+v>{=p+qOPDKHP$WvNt0PX9=0pYuIV`PvvGbt#r`;>K+?bJ zenz`*8M54?E-?e7*N5QlR|w<5+4jZ@W}`~lJRpzozk$@LIdL8y%(iA<@QNjqM2m7Y zth|P4K|7@UunC||tRUU%#UIx4HFMXXGq3}?{zO-NSK2( zSVc&}g;|~KknTI(iVgC6jGJG%<;bt+5pj(uj&9DxC`S#=a^hH9Jdsx_gKB*1w2fS8 zcLG76^#d0=Gajr98egIv4Y#m6AMW!w z1t>oX=hFA-o(E$dWK!SuWuw?1{Wz6=BJ?|Q*mc<8_gLsnM3+eZk5e4^u?vd%0k#Vi zpy%r#$(u#8Q&KtH;<0P8qqPXx<_VNuE8koWAy-<_ZO!IkjK$ES3NXDZ}*-=tWp2&C7?0%%F|3`ukAi2 zd(j|Nz@i%@jy|0O-TJ$m_GDn_=mF^d<<759=T+6_ebhOc`jJ`k#Yx&IYy9~dPdI3o zAm=wma#DL^DrGqa%vCjm!ech12nOT+MY5kG(HyW8KvUx~{vj8(Jp8bQXZ;ua!?XnG z{1kbmTJpSvbk%Es`)eftPQ;(~&cu2#mQxGu+pO^A8y0n!c`nygfI?#PP23F6(I(LI zHQePR?Miorp>5b(X=RArwGoJJvyPy~wzrT)uu*?l=vBQSRlvQ!1ZuQ;{^nf+^1mY) z%N1gxU`l!0lBHm@?4)+LXrrZ#Q@5@F$#Sq-uH}Tiv|+QPuLMg7S_I19!GJvI0aFUs z;rZ&lBz-LVw7y)MVp8cpNm@y%mwu6gPza`K1`S0GrS&_BKsu#a59&_3khE#JRrwSd zwvySu<5Qfay-5`Q0Ynm+YFZd`MS(!^6%5JyF+5hg7CL{^xU-(H)<}u`c+%&Fz5122 zfr{Lk7u3dq@^!OM#%ZyxSCk~w3m}i)jO96a3Q1``Aq(@GK6y^qCsZ?FspFSa>fqj* zafWw>hrou4%H6e6L6fWu^)d~}qsZ+vsdOl-=GL*u&|w7{Itoq&K~x|}2qrcfG^^*F zj~yn>W06g)%${jR--q-8oo}NZyGOOA*`X?JC0$S#?+7DJR;AZ?-te^8(H+qG3MD2S@lLAy zbxJGXxJ6r1pz+bTmh92mnTY0mUo*9*Wm0};=jQr;M4DPoaV5h86u+ULOIzz?%Loa< zw^Vk9l}@OoM%jq94q3$UR;b27#2Jtf>dd_=%-M2d?yQwYG)uj!hWQ0H%W1!})>tMc zvXbomPX3aJBypE46!rJr_5#XJ;h>U>`3mqe%#5_}GUb!mVEm`1<1q2w>kEZxA9+uGSAT6AI(aN1*k)Ra;yCw(OV-a z$qjuU=3_vWjC{%KF$c=8!)$vfa2w~ErogR8Ie>uJS8omx;Q9bNYcV^xaHB18L73_@5g^~<>(?p>2Wr!L0H|-&5pY=L@ z#vBi11ccs40{5S!h8e?G%#tpj*=z&#nDb#NRS-xLK>0N+bSmPa4ei2LZai`v zCGQ0{29F%f)*;d@F(v!&2aiLi{I;GV2Jm4g9y7H!X-!%KtbMr2RS3LC`-EAM#c-IX zL&ceRV|-a#0iKR-XO za`~J>xYKkbj~`Tku76ck`am0PbQ%ynDv7af{6f4!4e}s(oSw-JS;tgEUsyT;j}8N| z(EMV24-&1;2*`u&MwWw;3rOmbuU9!H@o|w^#0$HP&UvC(ZgU-H?JiD~FoGpYbgBGq zRg1gNsV5WDb@&Pdfgv~!6#w2=z83Lv35Tg{wB~y74s~1KPi%kJ-$Wr5wF8QIovtSBMy~ z`*H@xw2ca^$*M<;>~D%8dTM&i(y)mz)6}!F&HIAi~8lZPC7p> z#R$@OclPlk73li|LqeL0SE+47fY{cOuw3^`<1M*P`CjyHB~;yE(aYOEB_oYJLrt<8rIZv(UV@Ku{n zPa~87g{l11L~dN=56f%Qz5dd-Z$U!eE}WS8@8KBx&jt9&9czzK<< zcIU`sx1nb$pqA|cEsjix6`y7ez3$QuFmyYGoUDzkJS(tinQP9A> z+JTv=LL?FJjd$MXq=#xX4b9ZHDY!5HsP0~oJ_l%gu86eo1lwNttRoEV780uWX^)Oj zHj&)hQ4fCIaW0*^T#s|+LCLPA^irUILo@#kG`|w3x6qpPwyd|sc`?7&_&e*own3F# zFE&gM^5&-RkxkMc5Pv9IX>H3}{_>yVm%@xN>BQ6hX7}v!seN~=`cBS^eEx=g4KSs7l zkP6QL@^EfYyrK>@N11EJPM4=fbu@~O%9*H*nw{>Ebt6K1QGb%F?>CD^1CegI*w~F* zjezDyT3kR0i%CUT)8^lUy?Hz$rO#}Xx%d_BZgFmY|UVvvRtOj$des%bdHG&g*A?4^{sEJ~)CSG{&2sb%gn{)=J) z;d{rsV}s2fKIR*4^VcCG@_Cd3Y_-Worst*|Xnz16@(3OKYjWb3Ai2H_O#eWoe5+Rj zM&R|YV_C;dx$E?C_J8@DOg(Ogm>rXAE0{p}QAGBe!rrJ>^JN3RE!;bg!PyC$&AHBK z7(pxR+3a)fSQd=mO_ml<8XY_$Jf}R+K=BXum%y=QtCT->W!PFEmBZ4X&PHoC;_Vx8 zM%#3i_9;*4EMp>2R=(UilIVGSEfqdM9*eNO<#RaZ545z5nY9)_{nFo2Ek)7Q=r0^I zlmX9fkcfmGWzx`8QrbHa2uYK)X z^nmtZ{YpaF+~_C7Sn}SI^I;;FQ5SB0S-3YeX&Gi~h$%#C2hNEo5r88iwT4MinH2%8 zuTbK;>NLe`o05TG^dz|Ty2nJh;?~A>2G;Gr=>0iKWlJ|BFGCW5<;HX;TT6+p|j%p|1%Lhwq%3 zTAeR~7*NMv<(m&91vy{+a#N(+GK_Gr*g_2Ep-qZ%CuCYDl)-lNV)bco%Ce`aJ{5A+ zuRJrXsXJ);fil5pqYTGBUTIDErJpfg31}ZC)CVN4^1INY+({);rt0b^Oex3@71wP^ zyI;lb1wrQsRtcPUq+YF}-XrTs`ddKyY*YvJK!-}jkgsWWrkBVdaO|F{yb)MCyE!$; z2*0mbcr=i?%MWsqwaKe!EX{`Ypfzy#z)PnU^ffq^fUGM7nx@ueSOL zN8)2lE#b^lT*EtOSigKjHhJjB&kq!0Xovis5p5Nv&TR&m>Y+%GOP)MB5Ly(`LTT&kOwV~8ak5h84OJlT;JB}GAoHe z3wRm)oluTXq*Iw|^^*mEDa1$U`!#9H#BzPxK(vN5NnEs_EkqSXzqC z9j+{EDTrQYnBg@Dr}J)RDo*<}D9S@F7_;HIsx|RKW60>_yIO96GfGN&OpX*Rl8NOB z9ePDA1Fg?EvZ}Z+lx?}ZZ-&_GZRqb~ZtI@>@s!*wP4= zVeCNqV6bt9?haBS?7mLa^>shW@z6Wdp$eFd+6ez=^`0n&f2eydI=%R9`DVHe7-*oL zK=~)^>3O@;z=w%9<@r;M7Yqb`Lgzw{ggdxGG!Qlznf0+w1REEVCG}?xf|)E1idG=~ z8$>R9yn&`&0m!HO9n%qNqsd)Vg&WrJ8L8G5MOZ@)c2YRI~t-j*xq zIQcOH?G>!Mxp|ridF1Zn=0Z3LEj|iNf%2=kuMS;e&c#7(cZnyhKL+d|yE9X`DoV5TBJ=4T$UN;zY0aNU4C%2XR4V?nya`4`Ijn1*EU}gHSA9b#c{&(v*c#H=xhTGbzim6+KcgEhV~;zygaEh zAP=PsC-kzhbHT!285%*95+G&lU@|F5Y``YUPsrT1y+bGgi0Z5;K zGTOmOXZQ+ZWj#LeS5mV5yi2+KtKys5(2qYecz(f`g~|BP4%k0f_N~6jUFE9O0rKb@ zp*Qc92d!*&!FQGH?r3On+9RD7^Rx93Dk(XY^&A-0&N-^iL#5O;h&hB1mo(Yq^!PYf zkimBOd{3)V%V56-_cUSUS)@f+1rkIXqk!u}ynIozS3XNYUI#5z<*@|`eha#$x2cz* zS;n8EJY-I*|ZVQ=0fM)H>O+d1Y}Hm z$gn*j(PBtu%3U=qMXmiR!I}695P&?Y^VgG5W}tT9lKQuQ-=lg#AZKD`EF~9?V8F85 zgIcCUlm7}z{Z_tXW4pay-US6(zd%6#PUW(jz&JY$BV^=pEplQG<1A_5+pkA#qt&U4 z>D{>fwKw5D7AZNfpKrnIdluH?QseV%tc>4@VHU9#H=l717)GBTRcGd_JnfFa1Jbu) zq>jpl+EdXf4rh|Wgtl|=5=}N(?Ol&OhKEW%vCu<2XD8ZHF`#EqiG5uTFRwuZ@>uxb zc3JW?dkJ%a7Hp?tMEJdOXH4wHUMLArkmR+DMq?UoqHQO>FMc0EmEdS?q<}p9;sr1F z+|{W=$!z+?v`GCAu6C|I;jI2e@_bq1P0eCh@~(J0|17?LNGTp#_^VTZJj~fIW{k-` zt_o&z=ei^w4EGGjQRx;O@SLdvs8HVTt&<)mPE#zBDke(zl(|YM-V5yX0k$yN-q%A| zcgLv|gb9?`r`?52TKtieOzY|cQb6-pT=8VavP?~TtqZ}2K%2?5-yeb0x|7%Dw$~=c z8cl5USl^$oKxGMD(jxr*y#T0w8MHN?pGE02!nmcI5)svbjQ|sRuULoj3SFduaC?$G zc|CIeeCt;mKjvM`6u6!90w9n0Zb!+;TcbfgTR^1fF*L({l~te!cba{~i?n?dUqj=} zE^k){S(me-hUi|%8v^uxf_^0{tKLk((Qx_}LV_P}$pj;V+gOgYKpp!h(&}rcFCw0X zjHXp&ewFNU1VS<5XkUA(!Esq_KqZO&3jcdruXv{#awVn6vk3}MqcC&D1Ze-i7XNtd zqsUA(*L+>-!=E5bNgJBtVOAyYJykP=;lDv95CffGqq!oBFnmw{rB3>$n0{X~HR5qa zY%eqQ{KZoLaLzWfA?pr{{!v?e)CzSv&pdZ`4#=ZqI{Af(5A_N0fugaMR9fEj_lEIn zG+0uy>i~gdyFZf+tUY7R&Gj>6Hh?P2EG-b{ge@nFPbClEW)QOnNIwlniE3Pj{x^n5=&L52jbMZ5XZx!+6AG@- zToMT(!ainxz*0PEZw`5J0g1tE%BROpvnN|mAjIR1I(SD7G$R{YflaTrhZ^^9(qGx! zQK3Nb58|4;Q&`BGu8y~Y8-cTRBXZa{8a<(K$Zd1;U7X|h;e=JrRRjRHyiKh#&5z++>!^4SBh|R`&f_aN=)A2xP-s9nsP={C__;U7b@?;^GQM5#3M3ZVwHkNWX=+CPf-t;+(}$wkU*j5Pe3!4T^; z(t)GyXh`GXC3&jvL7E6T+6UBHQ7M~B$~4am(dj?*Bi0OR+>Ei{=7kNEVC+F zq|&n#r&9uTNvYamipyR> z*_Sz2HYI-SMlS1m-bgp{jh`DWe8>&kO>Xt|-ZKS`!a(|0@UXJK5aY!R4EE%c2a0O{0fHX<`!}E9Tw6P;c8`+ly@49ort%!raWCH1j5C=e?TkX#Sdw7%c;)?wIzbO2W ze|oVvr;gRIF!7T@+=>%g-80oj+p5tgWa8@O|J;q!b!*$k zASppuw>j9{705qd2lN6;sCtKYTY{zAxygATS$VD+limLeH!vzNsU7eAGqhIEdyPwK zSF7+@kWvEVA?fIRsYg*LX6-qMxE{4Pp&Iu8$h4O%LvPidEm9oL+~0!b@Cj)*r2*{EMUUJ`2fhgyx1Qs)i~rN?!Dh=Z@U< z_zxft@mD%SS;`y*$-)1LH#>Op_$1j&`=hAF>;+_DlIRC7^$;SgeR6W13%#`;7l-J-CS%cXST5V&V@| zffO_@mB-j#DtosTt!i5+SL<&f_=$0-RV$lJm_YFd))=+8mG^qF^?CVP6)w255Y4NI zZ-r+lB(Y|Wc20EL9xf~9gVmlXF7C=uh0#8cz8p=tGWSnX2Y2Wo#oL4dp2cA zFe5k_^3VU+`n>=1I62aDDnXE#+K?gB(k7D6 zf_E-u@l@H~2j@qX2hjS8)(Y;n;N46&Fs&d}oQJv52I*S;xz+018nb{TuOgoFCkwhT z(I|0z3KjQAHI4@p==pr}&xa86i!d)YTt;%SmD)S(bA>C3#|X zkcwXT9r>g#36wuZJfmXPR-_ojb4gccWz7@6m3h^FI&yWNhoOH3*@$bln95K${MGrv zA&1loLn;K6KZipTKlXRYSGWdmWqZK56TIN{3d+#`Js01VCXsTbNKSJ~=3f=wS{3s> zjLhYSkp(BSxP-IUx%P;NXMn^c3c6@I>W{B+=yyfBzlYz?fWIFo0fVwnBRX>}CU?2; zvzC;XEz()FBeVwPH+ghLp+r7ZY>cj@xaJ57m`m$35P#zUdFXL^R{DN@rER0X)p?jS zxcHw6)Nv2Bwj1!PX!{A_HE6z$Vby2t8XNg0`@b7c<{mUwD&UA^fLBh0Q9rcn87S@6c zi6EM-6D2_T1zgWaZiSai?q$BavK{18rO|q)L-Swe;yT-mZ((*P?V0?wx(x^}is6e) zwMI!=K=tzw9RD^mGN7?dgT!=4UzHEHAY0TCPhOUb>bxSPc5940N~WVknWS3mBKZZE z`ig-1lc4%_Kx&#maH%{u{5d@R_{whJW*~gA04L}iq$Q+$_aSRC;l{B1bw6@_?(2&J zPtAU54kji8KWn4cLkIJ);$}w*L4h=nVPH>TMtm(z9}Q?9=TS=HB~4D?G^n%g@HgU; zb5lv)9o|A@b(`WMdDP4{Y3hpEhX#5?V~HzLJswjMkpDR^wSpuzw^XTe?a(iBOk0T; z;*sb6p_%kU%`083eOD*Duf#I$>WU02mcH>j0NNj8`-A=NGF8wGUt+_mnRR^50uymQ z^4f2MymkkF(wRLU?=bdQ!g2!HGBm<$p7g2!;sf0svX*12v1(eG-7Qp`#Ey6Um?#(8 z3wd>o8^EG!2}IdAi7e~Zn@qnk{`^RA!P2jXR}W_S^?cL!?Eq4+e21gm5=)7@?*JU2x2HhjrHtx~h#G)`WG)tHbmuN4YdHl2cWQ6)^~Peap@fisX+A6CEzo zUmzYc98+0-;wl}G#;W=XAY4MLLql|#YPHIEHtC|gh&X!hNK>L_V1qDB_T4i4Q+NJxE7l^ewSC3M6 zFoo0EsrA<3&)}s;&+y~nlS7c`G|!qR%VYt0v@E-=*2yLq$sx!g zLPIT3*L-it3MkhGgCYzhgUZtYS)l%7NZt2~Sx}fP%M_UFYs!8cY>qQ&0|)lz;vIF` z3}pAMdzi%D&b!zTa^Io`@5&Tu`pq^7NWj(B*tb4NOoz2Jx*q$5aDrhkU)EYCS31Fg z&JREZWr zC#59~^Nht2p8X=0FC31{d4gv;n}W1Gk6}ZQ_VzD4PE^t_DRr==?lPE`uZXEl(vBB8<*iYz9-k zyjCgNZ4N6j_GPB{RR0?Mx1M;C?e=e=_z=8}sMnJ814IO|H3-U0{j#ByNni32bQiYl zC6cR^VuF4n4m7g&{AsrKN0*kaji- zcoL0S^Cp?k8e!=m{4!5{c=6E#$m95EQFoYmACO

      fBeAX&1aMOt_oh&J^JGC7kd= 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

      jbF{DeUAjq&EZigkVhI#^Sq`BesSD&b?v2F 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\+8q=X6 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

      r~dFfK87I-WWRyMp1 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

      e!QI&E3ms*TLQ4xAz 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 z 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 Z|KH%seSpd3IQZKf=AkYZLQhNXe*iNIRvrKV literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..70382fbe53db97124a7cf3ea50f9ab51924d2f1e GIT binary patch literal 28135 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..67adc5b5e8d6875a6cdde7046339660a699ae210 GIT binary patch literal 31393 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 ZE*emWtPNeW8X7YAyB_$67$?O~{|B67zPJDY literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..e24d2b0a52fef50af7d4ca615ea281951f61aec1 GIT binary patch literal 55784 zcmd43cTm&$`z^k^uDbTmii!@bEtH)6c(7?)vGcU-7_a z+y%bQG2qYN{@2%`<6YnU`SVXd{SAB|{;#h?M8gO~5Euo5!wFC*1Oa{-8tuv9=zAh5( z;p^GWSu)vVMz)i0PMO{9vbFgMMcCZI*#phzROkGQoTi>kIMt?9{Ty7m2aUL{ul+sB1ON% zeR;WH#`u~l%gu%B1Gan2GEJ=C?@<&M)wX)zL&1~mW;kWCI-l=Xb=?rLi!&SEmmqAl z;NI(Ce-2X94{UwETtQLr7Wyw?*;CDa$-pNm85QV-Xe%hrBOLkAh|Ux+HnmjB|5(QT zF7X4c)T-R27$c4tF?GL2=!5~oQXWP7c?({qA_Iupws@f-ASeupLl6MGa3CU-2>gSH zg@eIp6buChf#3)j9)gA;cET&mvL0IO_SCtl)4AvF-NO5i%xyAEOyWLhB-Ts?o$6{8 z(5Gr{7rGDrCGl2>m{mknV{u3vl#SR@Ub;FJf#h>EG z#5^r+MYS_%qR)(^J8pXEWdC)lSQ#a_tXGk@I>e!Br^_&$yz*u3G}~8Rzf*Urs@)yy z5i?I3ZkZ!~WrlT3``f-O7i4eTb{E`8zWjW7$e67EkZEmhE0&oHw~VjtGU6LqR$k~Z zFdDoeadMno^`ohZ?kJk)3i;} zr;gW)s7wu!$!&cNY1OL}XZFQrZ-vWJ`U!h`dJ$kaG#CV6g9a0E5I7P?0As-rC=w1L z;K4`)4u`?Rz(^2gC%7)e4O~7_rAJMhR~zEvI>VOzYQ3qX+8bOmFG=Zab(WX z{C zgZmZy&)6)ACf}J$d5ib$+Cs1Q7RisdrxzRvMPe{`Fc^1h+NY2&K}o2|YTL7hFs^$iExwji1&SDx0e= zj$PYL-#62w!fSqmKUGGF8Q68)DVO?JaDkPj=f>2&=%kc+y+~cU8SCilhP2|g`WG?~ zYiyKKn%Tds&z0%gl11NE-Mk5$0($3Q^*|e4d2v=O?85=@;Ej3oiA>Xj7o?orOuOKL z@3gNnXJ6S2zYVdxaF)@w>ZTMV(cyWRq7+y=*YNmw|4XwYR*bUm$IZ+C)R{I$4%S%D zH$htN(APewxx-zc{V0{QTj07{b2?~ydLe*H2t^?Xcmf`W1ez5AOdt}`NH`Gzhl4>_ zFdRd~V2J1)tD$!2X>pM%-vGH(Zt!${jyB_{_rtM6w^v@fRRRrk++ zFxTvw>##_hy+CMN4gEE@(cMIlv$lFsOH*^?%MtLhRBNJ54cw(MVlF7rp;tIb33qls zno#F*cP{B+eOWLcyh-zTd0F#i%9q)7fmCtnaof(w*!^cAI!yvb?N`5vGN1FOBSi8& zV>9wY#cgvo&N9|p>2>$ujStti?OsR-7K_2aaCis~i9z*BoX{4dXpnm#^N{do% z^YdHaW;v~iWP4ki`xPULU4PV-B5he!kqf-_rJCwzKBWY))nWycge70~=)zi4yL)t_ zpvnMl(?2h|r9Cv5Ete)&6y`aN^#-cCYF-1~PF?fN_~yz(cd`wuS!xtr3FdbO zON-?qO^zc$p?@qrSz*71Ne#S{eB>SdKE?5{8Jp95zqJ4ECplkkwTGCw)vA;$ zZO)Cfa=WADkItkrj*Bz<{hjlD2RmXVob~K%Wkkhn(Rc+z*_i3_ z2%&IHZ&U6Dv$d!jZN0F68N;H|*K0BuB3V<(mc9-*Bkz4;>sh zl-}zX=Q00ohW*tjbbeEMW9f5$)awIImR47jeEulwC_xus3$vX8-jaqMR3)9Qq*dy+_s_wt{n z#tRIuyzT*hsNA>JP_6PotCuo&$qxMjoLX}?;aZh*$X#plCs9_)%l5ur9a}Z#=0m?_ z)~(?iu~Q1oWBby6{y*hB8KxMvJzfYn7>mL|AVAZ?Vc~c*1`R`jP(%!Y2=p@y5&#Z{ z!J;tmo$z{YHb14GdW!EJ5Ki$GHKZ)~W{<>#H(Zd`vugS0D^Ydh0!+CCwAqkz;PoLE zqj@cze5<47U)MK5cgALG;dDlAlWbC!^h{0#*oOLj05`)Oyc$*Ysw}FD|0(2w&8GIE7+<4Iw$*0O=Mq07{gwI1kx|a8 z82JqmIxZ6%JI@LKl}Nj9L$A<2{399ilbb5|7q=4(@2PD{Dd`;-NZ#0Xav?i8l@ z=;I(?R%Fx#zb$xOKC<$7d%U0oIEDa%;_yTODi8{Zf#dNQJdOY$1x3OLP!It`z@dpd z^6S4>5NVv;vaH&=hbnj8CSMprZ~~r_PaThvn=?2kG7ycYl`$3yeTV5GYew?uh~43h z&#R5!&4?qzqAKG0=dl7~cfYWe!q@_d5`kWmbdu#C7DYHtKACE@TlSFcJVjb~(XX%j zGGFi7yBXW&{=3zbV9}c;!c)fEL>+|Eyhki^T_9GHQ`H%|NGSS}(s!qegksA&rTaf9 zH^(Hx$|!kHjmmR!$*$P7tm<86{IiIDjj{OySVjwv)V}#7<51&Z5MkVQe%11!&xEG> zo&@_n;#=yWoU+o4>b4CG0(3A83=2a;!B8R&3?o9J7y=FpA>!ar6bKIniX;&Q#v^y) z*IAhpwlR*Tf+5|-4=qnpatICnA!R}du9wrA=KXmvwXg5l^JX#P;@NSXQEjm5E4k(8 zKTCYQD#0yKETQ`c_Q4-99x5|lZsg2qJHQ4?s&39uC*OZ4NL$lw`{y3yYvqiaOtW|6 zMfbUYuvPnaUDB82HH}{?pg*36kC&D$SmjhPO+9@gc|Vh^bvakb3MM9uJG$h)m2!5f z?oDQ^J()j}E$AC)Jrfx@XgPE6*wyBw;>DHY2ae>`Q8!OWHO2On{JD~SfYa(G`B`$2 z_|?azWN4aQc18;~If0PigrUycRF8;fY<&^suFdiOw)eS- zT3)el?Zst$F1jwfYdaJDb(tpHb40S4p$u18?)$V`Ph4$rf`37xdbr|JVB2iwduzX? z>e`6$t?XKU;1|0+yFd^q7C}HlfffdWz+pdn889^?Q7{k+0V5L7XcPz-$1u2^(6ZQb z#CTePR!B(Kffw|o#!>8RGHx7^V=0n9wH*lk#s4MTKGfaV{Y;awp7#l>e%SDcJRlF1 zJ3_?QGWR(w_{!nZla3aZ{!N!&N{bp?Ev3_=6IpQ4pAGZl<>%4$gl#bRQE}oaEX!z)`SbJB^ zUGPg9SyhJ^yuaejxUU^0368sSSNc_tgc3Jb(%Ra^1751;A2y~tHJpO0 z@$LeLvkKIB2LmT9Q}aKJNp5dy+ZG}@{({nI7u`nWZ>&3Wly0f$wH6&*(!IZ?f-0BU zbvEk^4g9CE)h=nCG_9l7?4fA0s%KthwQYX{v?`+ih;rNr(t0KQ-=PusaRxD~q@bJ+ zf^K2gt!Yuf_VN%7#-Y(@Bmn_MLLg`)3I_&b5FlXqf}+tNBpwTaK>)o7ChmlntXt)C zTa}j$*J3y$T3M=|ad{6p=~?u8uCJJURle01m*Jp~+_vr~BC&kG&%b(TT8O`~v;9sv zU(d&vy;U5lg}>!q5hSgpTKa|!V`*MloMTD>naq=wUAk+7ZtuU3OnbnX3ETlk{}VV#eNE$V;C~9VvNuQy@9``GD(q_2X}= z1$s{KSc{L8^bXTDu~?#8lGAY##JGe{V4B4ZuE?o1_AyFwHw{^C%JtqcIuW0y6~uw= z^^iqc8x*md=H{ZeUI>Uf2v?v59f3FH~UDo%5+NJM&c;#NYWXSxVS&F?K)HbOVrn9 zXLbjx%IOh-c*bkbB~}#wkGhM3)`x?FIoAd)gxtWU>!7JnO*Tld_9|n*RpACx;Q`OK ztSBIwy*KFMqJok_2~R6H*U6Ccp{<1BmNA3dlV+8BquaPuk5e3!>E@_4JrI&Z>o8j* zd`dq%5&xN=QP`#3(^eO2O9<_=hwdums$>UTS%dF)_`iGuHUjBU=t}Zo1=OeeAc1H zxRyrOoGTw;`$T0cqx2Y7>J8R=!S71Q-xRX%3|r+9F<+`T7EGhX+Exn`S{j^(e3B#6 z{)AN#`teM{czuVwoI|W1pGUd}f5&Gths-4mB`CQC1;WIsKhBLk%b`t$`+c09Ou=IfAt%_BJ5Lw2c86*Bq|>ZWxt&f|2@Vrcfjkx zbnVLV0;}8f&a&nezIj1wg}N`)5pFh4?Z>J`CGW zW5~ALR!zY?vn{XQYDs^5U@l&Yd$87AubJ{6x9bghra`8sEY$#T!frqHaXT10W^J7-A+1lH? z0Ub^9@Ba3W?TMYC&oURk`J8obBiTpx_s@3RyJp2K7KIBAZy{J|@iO?vw)zl*#)44@ zz(2&G@i+twjl)1tXe<HKB^oHdfx@DTErKmT;ocoDy4wincRjY;KGseeuU-q*XOWM!zj#sn` zTMO;a6`W4eaa#8;de6fU)@fvf?VvxSY9KRZe)DPh?@{;JxM^@vEVr6g+)AeuW~Syj4f}Q<-czJ*zJ+0b7!N_WXBZv{Ovywn2#3N0 z#vvL9g#ptu6xe#9;1Ga(z+gro5b&5CEyVv~3$rJVv4VU@)adj$?))RmGse4>eia}iENvbDorNKH^RrWY)6H^OM(7Z=Y2CUI#92DO zm(*P*aYA!`|FG{bq8ei@hpD?f=}$~o(Dx~0cY$`2jDHT4q?xff-(y3rA}3r{jP1O3CpVkb{-(s{+b)xI~!YvTyF{jXem zBw$yal2Jttwp5wUh;g&#Jg1|Z{F^5?O3$>21X7L*7bZkfMe3_jdVl$`^amj`Wd=H? zeHpOFP`K>r6>zO=edd8%Kq)!wM8vE_{Hfki#*4|3POZnCI?k+AOy*-*p@ zh1Cz^mh(|aCF9$fV|Jau!cx|nTr4@wZ73(tOlhsg7{8Oq@=Iw)0nAKr z5KmQQ`!A?C<*@afjNb@rk*B)qy(pfF+7(AHpxFbt|2*b+nfZg1zm4!B z~9wT+QLMSdb zl&|5>EuvW+(eiY#dX{5-t5~IWbukv6CZms#T+m^gy5Pc66OWIY*6k9P@_{;?kgbfy zEG+>0mnocLWNAho8|bgX@}=_%!bo1&oNsD^Xe{@c$)M_-{cI=6;t)HoSdxjnKgb{X zR(s8h?x;Nszvhtm^1v3r^1;pH+pjFKAOak)qX59LSR@<^M-X9PEFKC}NGOO1#=>EE z7+@qqc1&^a&{_-b>p zyFcd7BbUkUg6HL_d*+sNnv}{m9Y(ug=&7b=D-EmHgZEsE=DwJZC%6&OynQvDm-m{I zw0cjzQsx>flu_1ner8Etd^hM8b09w@=zd<1do!$K#q^u!9GkK$TwI3v;6yMU)f=O>B^bVwB`!{tQahrND;?pO zK$Nlu#QE=R2}i*={bLd<*v*w;91LFhOQaY%O^}l@S-5%EnzONJF28YRfLKvc!a!WO z&9Q-$QGU-M%jk8c+TH>iXEUVophb2$B^?ert^6v#ax%vx(0-Fn;%_7gYPo)y0}mw* zadI@63N}6-rK39Lp`LF^Lwd%6$E-17=1pIF72eI3lQW{2iFd?&j<3XnpY6A=hj0`I0(ggDC_pbL6p4c&fpr)T2Z912DFUzx10#S46%hyB39tWR zJ)FvLHLrhS;nt78<0VBOgKFhC3^`H8ui2)X-}LTFxfz?9s$9-eS#{9ZbP9_bj^k*5 zTRm_q8f^GJKQ4Cd((W$31UV=3k9os30S!~{Jf~;4v08s2Pb;XtdK0xaYKZ@o7Uz~$ zd7Ifvr9>D$v-d7FuM5=(AgHP&+!_(K^tjWVr`x=aV;GW`R9^MEyc5y7azld?>!?JL z7r?J*^xRzFWwK;zl4#tKT{*TPvV(IIVq;Y8O1fsM$sU7P%%Ha1UgU``G5J})BXhWq zd$$m*LS|iMd%%bYA{H2q!B{jAfd}YB#KDNb_7Yfsp#ac8FgzZM_@O^`0_>NVc2V(Q z)W?G^))Br-Tp=U&L<{U#A0H;DT}v+b!u2DYrBT)D&geV6=G`E%n{3^}lK~gep7+wn zBP2d$-Jtj|IC{LePOLAq#Ei9;og|-0<3?cCerKUt9vCGsi;AaqhZ-=jleRwIef<`4 zwjB8mbOAdfC=KnRX)Kb;PmX2c2!YXlfddvqvaYV9j~bJ<2gu z(>gHk-+<)==If^Z-o*!xCSRw89Vsepe--U3d2UM4U}0uMD)^FM%8M%5_n+rE){!&h zH9g+r+@rgMy>j9+C~&C8@_f0(JyVap1t$VM&VdHxR^*-UO^#4qm`gq9nzUAc@8rHR zv-V)B%k@ZFB|pAPm1~hdwpYxl#X|FkJ zr+5JQw)qaOrYk&K8A_9hW+u{$sDRZ^)CNQu;K9W*Q~B&dG`P zoBhnD$_!oFoNo-hTmX-3rg_tw|Diz{7p&*j+%7uOr`tOa#^PyKU3!2kJ$i{QvP)|0 zsy~;BD7fD^O)bIJyj|)#bFdn77jT8YC|zVC&QU*v#H)c0#9h zGm6_W`oON{`OHM)k*rE_3o+yP4_DOaY{H6si)71-Q0a4g@#d&|zia{Qe#^Y~|3h?O zaTo*;g#uQ}U_jo3pa=+x2nIYwA^`|VL4E)UESmBElbRUV&C}Drv^Dfw#lsgfUktJ? zuM?-!^1B_R6%9P9sYbsh+4e2jDtkAO5v@Ubg?O=S*+hUM&mN zt|q?3W=TiedNiwFJ@!;P*LZ(U15Wi=Oh;cKR~TB9`E7Msf$9*^i)nNRhxG;q+Qofc zv)>~k%E*j&5m{{IIijJnw*|=BCVUYIyGlVU8egBz^+n!(;JRSlDdKj=l z!Jr60761q6L^KSjjc5oQjz+`4DAW%Hxf8+s8RuF(iE)+4uXe5+imq6d&mPR-tc|dO zi}DIXWGjysPrF0DB<$}SYRErxCAHbwnemH`(&Il@J-eG)LZJ8d%siH)fJ_yN7j2#@ zK3;&JYf?uk*x$F+}-iypdNQLR@? zH?35MhMKfRLU~W?Lj#WAt4_|$5JSpq6;Ez1POcic3nHQ2xxxig=9TeO;@x|H3KI+z z)Dmbf;)LXe#|6Z_#`1DSocLv@?wWm^Bdbp;OJ&y!j%@*qiJKaUx?28c+Gm%JoSoKS9tLi zIhl17QZQ^BwchfLE5c=@uPd`BaRv4w5ni3Ckdq|+nnPsROt4O z({%epKOY+;DM+r4HQnVRIc?o~MBqVKYEa*T*KmZB*YMO$!jK`FL7`>RuM(C2`OJ`@O>Z-5$8zXAfE29qSiDSF-^TV|b|0zQRzSSz2p!3-6tTQsv zSF*!jrFjmA1idK`b|tXWo|uYnZD5Ng{ma`ILSI91tE5=#B;Pzo%0>tT3vRU3P5Jpk|~r;un`mrL&95K;ONhKJ~y<~Sn=(VS6)UhhKpiYR!@DD zMOnNh`~^b7F0S4xFi;sdiyn0^VGG#djNgK%X3Z)py!$e>YZI<>Vv=;>enJn{UgrDn zjr83Iy@ZNxT{9ajr1hK1_O28LPe5SsC=3$VctK$}An^Fch$suz*J7(bwDRAf;XIhxk|h}}U|5K}SU z$WxRu^{}(K3TUvUn&aqP^Ok~?M4CX0)B&CMmxAUw1W;OLSWmL0-Mw-{sEJlcUxZWh)NjeO=Q%zPQgmj0_HC($)kpN-Z4Va&j|2e=W(W{ogQ6fnC;%A1a6l}Z06{YVCK%r! z)U+}axadNkl2ao3e&TVeqz%GcixOj&Q+}bIAnJZ}i%xKy_b?c6Nbf(O#OvlVU8PJj zwHw&6hIU zh#YIo7PwrkZ>eqHz6fZ5J#ZKf14V;@!U)9^kO({s10etfk$?mIMI06gc;I#%nKwOE z20~@*+s&6|$c1Kb-Q}2bg|afrquv)rYm%CRI@afVXhLBud$=%LTgqxv)z2Rt@407R zm4v?$ld@n zD`Gk8^-#vAef-Q|R?H#)^Cc@@R;fnU`e!O85@&AOK2Th1J?`h!o#KulEf*vieKMZh zIHi=pG?JNGOKnkd@A`5_@0&{`)x7hwJbURtupi0C4!T_UULeGwcht6dBzrH0=WE5 zxtMcWm+}ch);%9jEoxP3YVxj@Sd&XL%Pqp?;mO`TpCvFsl3%PFT&K-59_LsZPFb?9 z6g)}J?AmzfXLVLptOaP-WKxG8Xc{C8qM!|>xyKs2G?$|Mj_)F}OYk9Lrnw7DU zuF9K1g>C)gWgSBhr6eWB_kcFwOyl|L@6B=0^6)*0F89y#W?TE}fThY4%?rew^5~Zp z?xr{L7B5D<$^WU*GQ8v=!z@L?|0J%Yge#zFk?8jwZ2V|$<_ma9k)gZ5qJrAK`QqTN zR{z18cGi`LDQJ!5S(9-zl3usa>2QDb0VSCt*0{8eRtY7&S64tgjlRgCwRO64Q;$Vu zLjpnzLOCuaPbEdE62~Kwnv&Kl?_$HnkzoTRQnk2e&XxSN~7J z_6-cULBRo$Q8Wf%5dxT4Mmy<^toDO}Z7#?4ujR_Q z?cYtD=dZliQ_NqH^6K4JPHjO4?2U@Gb6l?aOzz;AtXLs(boPD&JjBh(KDO&dW3i6j z!SNP9@Z{#$3bhpC5irWkxzlT9H=$u`W-)EcZ>`|>B@LK^50mTKR-tFfl!=x0f!KTU zq>KB**pdq=9rxa8i#)V1*vncfiP!H22>Q?#$_W{|-|6Jj z6XzmBGkcfE!?RUfI7J4xKMB4EckwS9TZF)t8@gKm!8feC07fF2U?djV|^Ol24tpdIljFmgvd?+?v5GeOd6Spfw_)QUUCk7s_u{V1gPDLvhg8{$(TUMg25# z@WaX|)arZJjZHoIBCG=QMAdVj?_@(e(=YsMCQP6lt|$7d*ryvO)@Ct1l0%0UbfbNW z`x|Tw76zWL_44W}>HD4LiyKrwzxa7%;&?u$UcK!)6Lr6NlwQ-ZBMX3CUEoa?P`1XWUA)K}Mt-z(lN zIDI>-6jAuzFq^BzSIiA{B`;q?(=cX#rzSpHcCU%WTfUVxU}{_-*_)d1>Yq6!NvWda z+p@o3q4mvXWQXL^&fSUHD1Rst^4MNeD(cME&)Eg%fvbs%e-D@Mvpk$uSguB%h+wK& zzEd!}@Sme6TiB|j4E>P;IeNcJm|ARnp-`rbV%)L2%04$+m^`7BR@`;5&l58hvF|_; zmV8L4#_;j(9Y|n6N|J`2ttkF`ib-EzkG{RvU2xP)s+f5y0s7I2N%J1v!!JL@9(!e) z>G}0n4%oy@{yhId#%wCPEBta3eZbG8WV0(X0SMh#^)!!8_g{pOqe4WxbEo!363f&w zIX+9o*6B%6k%YF9ZSF#)W&OW$l~cm&>Tnt@rS{IBM^W?BM6iT-?4ukWmJSSh?8LIv5m)G;U2mR0S!qB=a!-4q3!-1 zRA0p@sV|p|I9KiD`@fPu;^y%jNq}D^qWPiB5}WG2ZWHT-t}4^ZISDevgfHAWTJEAN zT@*aK0G(YF&)xU*m1=JmH{P;xMEJ(MKL0-6fi&GABT>`Vb{WKC8@0T z|EwVrmlI#4r`Y^GxS3gh`M`^F-GCJsp2Gl7iNUn#<;Po#|J z+(4&AYbo_UxgTy_QCcGUgU*^R*aXh@#QI9%^h(v2pUSBhK0oogd`4LHo7~G2Vgd?o zsRb^dBIr-4EerpPWS!O5lf_OlivyN0pV@-~bBn!dy~+tN!8coVbxX$aBd2=KhLLUQ z|KW4{zu*1*QtDca6oT8(5%y3{mwf8C%o+i6vuQqw~yLvPR%Kn_zoW+jrHXiEiQ57VP z50eT^@|G^;goaqdIAsY#2*8f==k&ExWT`cS*6yXF6$6p^cf2ezTAfsu|8T9(zw<)* zo|*;DSb4x@?MeIMxA+W&pGQK!twN!Ge+`wqN@+F0{A*1u^hqvDU1|~=3c;!t4ynbD z+jtM>{+Zuxmjd!y?BV*yuy)s;+%VctX1xrg55k>GzELV%?2JE^J>0dcs#d5Um$Dds zMaP6!%zeCQG4C>`sF}MOYV^mi6)P8tpC(WPOSSrt-wx7--kSrdba$cX?3Sx{_M2l&oX&Q zgL^Hb^!09;Rh0aW=@_ZPwx17O-GbHw+bDMW$k%Yx$|Ld5CU@~^m^@bB|2#^`lmGtdIJD;-1^Qj@LQSs(QWeW(X~d9~{2V zfWs@6gr5c0pME*ZF{zwk{hdP_*6ur3h06Q<{JF(4CFL?*Rn@Q@B@mU(XjF|$u7nuF zS5%95HuJ*N76Umg9sFh8N)4v`#(LHI)6PRIl6C8|QpL8B>&q9JpF`ujznmrWrYwh= zl(HEJimY`~vy0oR{HT7bpM}{;AxE(i?D?fFB`@vQoGYY{(T0|5hGW|GopBXSnzn}K zoJFni3AwAJwsaR^@y1F1z=8ZNc=Kmw6A z1Qr8Cq<+-P9j7nStj%Au*2-Sv^-$A4 zuy~gjE=sIDY6q}OTMDhBeyly*gSeL}x7q|j{@!pn{_Ppp1oiKvziZDJA71ACq7h|& z;OIF@H#PONpZ*?8HD>vV!;;NBYKu1Y_=(NI^_7`BLo#DY;;yVP#us;2HfE3`zMMeH zUm#Y!GA_Kg!0Y`yxnUaq?CG&d&u70SdRP9b)Ov$D-#*cs7uF=lHm9~K|KZ_@>)PaJ zDq6C-3W=JVuUwRRcLNi4Wa~A?igC05;5TVv-|22fh=4#?YL_Cpwot2B0uQE1Y;Re@ z4L!(!cUhmSkHu1z>*TkkfPv#Cy#*=fVCwY zh;v{;JL1)p^mH6>@Kmp^$4mHKvTJyX(>YkDwnR3x^wlEf*o=1-PLN2MvinmIytBW% zYIPPJy8#@XPd^-&8Qz&Thz9-b3x5sq?hmq>@jWDn$l*G(SwPyNw_$_ny28YhOf#> znbU?0fm=;W0{3a`RWDE6sQBpr%#gl(5~*Ou$d)EqaX99=|< zGm$d$eBgdOsME#E`GDfGWMhTbm27urmE!zk6P~1`W^2i}2(M}vczDAuPDk#^o1NtD?h`%6KfYHOUM zE^3iJ$Y-F42NIHORqt~iNTBSv3@?97JW1t*szJ=Xq_>M15dK)|AL{ZFRxXQ0w24WQ zF&|JHqBM_I;aGNuB{Fl!Q_+3t-_N1l4N_XZ$(c_CVIM10^K9ui$5RDg(8Dc?z6B<8 zcIjKi;hT_C9^2yuheEL+FdF*f@Ek+H1L+MA0t>Lju%a-)CUJ z>GQN!<>j36uLC(A;YpS&+Dglt{V&G*xz^hLt}eA<3%je*f2Z6bS-A%~t7+OSx|Xi* zw`ANu$~+cCjW8wB`UWg71)S;42sC-1&EhWvckjPksUxPR+y;`7Z$a2vpU?2nQ<=Yy zE~DJ;`pHNl@0*UJ)UhIju(8xp^vSk8G=+&+`^{_pC8=tBu0!bX_}S!izrJ3>W|(9- zX6r8HN~Oh_?ePM#H6R!ii^rf)P!OOskQgK$hD9O}Xb2W~J_U&c0UH$rc1OJY?ZQc~ zhkU%!lYKXHdNYkqu6WR5j+KxL`UauY$IZK6s0aF8kksjQyK0)o)p;nPHwpv#*M;pV-_|TkWN*0UGQ&YE$fA^bzSkGS4)7G%Tg_-g3~tur;5JhWWFu7d~LUn-?pW4e05dSMzD{XZX0wL_X%nm4ew;-z?d zPZ)&gyVHhaF0UUpG^hwIBDu+bqntF-R*FNMA^hws_X%>wai66D&RXcjVbAt8$^8`CoFtb2z{^q7(Gz z*$((!mVJz@wm%xLCH>x0({7h;&1m{$4-b+*he!f1=$xFS-& zyuH~AJi-R#G=99dfk7Z(K%fnH-Ub5&f$(@J4hi%%;8h0%7Ki?ybfRKXulV3GbrKp~ zJ29)E9j+|&8d=Q>gvh9nj7Y9M(+=c8FJ4WfgLHUOddA$q9JkzkUm0aM?bKPXBp(I5 z=_O^VS?A^Y=0a=*zybG$+#tt<)m%K?a^$2{CH2tO`7JMq4QDk zi|y2v^lYaCFY>uwCjRMTzx(_92CgEkxJr?^%oUesE>$I&x$a+YS#KSe_uM;ie0zd{ z@qnd)M#B+M2pmYW0Xt;`@ctJXg@qysNI3AY6bJ#l?f~D>J(Qh3)aJF$_$s0QdwG1N zM`?rdxY3=u^6q853}N}Y$ZB-V@f;*l)xXcpFdsf#ofqE(-C>|mIp({@Ql%T;Juo+M%JOaw8Znx-s|ew z2F3RX$p%wia|A20YSxT_r73cccudx}Ncq9DWp-sFI)hLJ1n^TdkPo)>oTDqcl@u}aH(AbhBvrPuEk2CYVU%7H$xOhvw=;PVT&Kb%FC8FUH51> z?|CL!;MHf*saw9}$KbuZp{5nYWL8P2RV_SSRSjor98+*i+&FGoZe!LU-XEFG+byt< zIcbb^a~1iwI;SQ3v`+5|$}KrAD;|Yt+U)au?;TbhYmq13?Lyo_u!l<7^zH2pAPV*4 zM1=sj8z2aZfQAwQ#}L?35J#bls-`PKG>KigD%BNdIWk~P4W*{rNn?KhQ zTmA7ywy1ln`_&p*h3Sm2P8=u(c;I zu6XRzknop_?4-?BAg5Y$)s#s-y!@mh?PHUaS>%D6qIQk)hdEg<4)tPB^K&gf6SS5j zgZ_Cj*nQ@xRW|Fy+Q^$k)aQ&hk-M&Qgtmr4^DLkfgp0EyYsKHrs z)j4T2yABUwglf38UdniKpW8ET}FQ4$*_sb@2a>M5c4gG zaHD{?0^mdtaA--u1BWYkI2Z>9Y()eRN8HKYc&#V3n!yTwb*bLr%tG0_6cdifP|0p_ zMZ=}F@v@Xf6?gpGT=A*!ngij&**II(<#8+-U{`>D<&oSbTI^h@b$#mEB~b|FR5Rq~ zswEjZw0P2=+!3A!FFg-hb(>8#rw-}BH+$TUb}kY|7-5MqngaterK$s*iji9 zjHsZ1l!P?usAEBziu7g@5>%S>8W<~M0|5c)B1(}aUAj_4q)G1}U0M(VgqGw!`R={z zK#m^s*ID10wa(0(b(X*NX1{yC&wid?$#3y!V+AZuH6Zz1g>J-6PiJpb?>DkeFQ=Io z1!>=HLW~NT*0lAOCp+U^?J)Niix>E)*O;}9G5jMf7ky@F&#jYooa(IB)O?@iQQakX zRS>RMZ+`su9_eoPhB&%S>EA{jm%z~{UhbZ1JTpBoPE2>s)_4aljRN7WSO+QkqAau zckb_(>tY^?)n5jI%H)Ol#fF6O-PwC|OGmZcX4B3EDt^l`uRAy}w!SE+XkV5Gwm@+z z@AV_K&mGf84oq5wfM?1~r7SO9yeDpB!~^p{m;CU(y!DouIHd_z5@FWxV^dAizI2AQ zZ)rR&M)&kkA}#3Os(H6J8}fgA7?R&G#$RCC1`YxQ z4Cd$6{E7AgH@6Q#sf+J-`9$X+1+BbN%rl%c(z2hs_1+6h7+6ayJm;b(;9_?DSdabR zLH5H2>+Ig>_IpL7(bLtH2b#6qhYaT@i&yK{e{3o_<{s!gp6SS(bGm#A{nsOW=b0>1 z&*N=FqhM#)p65PMP!eSX9U^mDXk><@+D+=1npstRF(FO2j*)Sq>#33rRy+(-w1% z*9KcRkIO?lA5&--Qfa3%{?@4=Gl|%lO{e41`z!C*?e5lhz2;L$PiIsQt;Rf?`H0*p zI`1JX>bisDFdVUOOZjnah-VwZLSeynVIxUPA_zDXD8hhr2)>8lr-wlTT@;Uz{HcXh zue$c$>N&CgvXKvc()N7=?VR1ub)oH z0Mn)i%~!Q+(kvt^On$eSjj*3Os_LnQZHhn&}t@3RdD~h2n(JX&L zDt9PqH1Nr$Ik7hbBO#Bi3KAt2D(&-?EU)F6M%4|RJtL58s@kt$UvAo#p{wkXe*A5g zbY83O>~Fv!jLp^95_E0;_}0#=PuyZYQOXY18?ENOoB6uwbBF(Wdm??WVd%UXNq73D z#kC}!w=Ya#W|K0OdxIX9ZQVRxv5#q$znC+(RP4))AE`Y%^vU_9U1xHOgEhwNqCMmG zy)E_S8cfSiobs>LYmTJ;c)?JT(vk$&RX_>FK*50m-XS=p5O4%=k7Ap|BEe9K0A#73 zeZ49jt=c`R7c!oyvid5vc&yVbe8Py2p?}N|zM3uVKE&+k2rcpunc}C`*Lg8Qov$(v zt%a{whgBm|c}a?*wf|Kj>Xd|I8(A14#fOi2x;Za5NCRDfMd8+ZDOW0YRY z&9EeVT_{~`J0L-FK0X)H)B8J#{%}C)sczlv^VWlY3E}lj{`ri&FEwgOqqc8fCd_!G zo0XQNfD>}~&_~O!j^PbGJAgr{zDdJqzYbfvVK^ON!m8_f)>m;$m4|Om;v6YLysXNA zzX)C42e$@%S8;S&_%PKu$!=PO`EZrBbx_v*#;O^<7`41?`w_p052F6(r%zk0D^a-b z+mO32Rh^T$afmYc_m7Kt92SK{u-6O;g$0@bBzZ7eMv~z2Y{J$v12+JULpUpcwqQ$X zCvEt%TVEFFEH$JRR+u}~%a&QiBe#V45HT=%gsvza?7#bKv$KTyeAU|WUDg2^87GnzVh`_s_C|c*qHaFy zuzLw>+aFBiop(RvJoCCc%45IMj!sec&VV@YIAv1*madsYiaJDH-ITL``k(pZ_PgL0 z0$b?v#_IC8&-n12SEybes#&qpp`Kf&|x?9aBnT;Kb;=NY|y>B(dyWWextXD`k z9gzTLZ@L80HHnpy=F#n@bAdr?5h;&&ynl)%o#3@mC&#@ec$}1ZvUkLgzUoUBL-@EH+Bh_ z?UMV<K9?GE<0+-FH1_-g({bUwZQ&yQ6d*W?51-z!bv z!soL5p%-04J6l!IGXSEor9D<^cae&l6p4l32IF2^IHxtKgdA7!m<#>1RSgz$ydtIX=`QN5kmV<0Q&{Dk z?prU;_I#&sL++2)3+}==poc>55NvzNQYZ`-4Q4k|QgAe~Pf8-R?tvXe`sw}agFMy1Qpbt@#~n(rUM=m4 z0=rvG4v@^ox0`2(+`mY_Mrrx*x1&r*wshSk>K=cUCk(A3qtqUiubB9THm3^xuM0 z->aXd#@JPp7F8h=fRwD3X!sNxalL-l&a}R;!%e;rKQ6L~_wSn@nZLax`+hmwkl|8i|yF)V#`o!jvmS|Lk+FhnDV zpJRW;JeH^b_&|hs2Qc^oPa+fGUQ6Q;@E6;K9)|_R3JEW50{la;1VjFm9aR~lZhq_r zN=E#noGo|)DGWb2tJXV-uWyI$cEP?-J-vk<`s%Vc?OUgxo{HhG^?QnDk1OB2b#0!0 zeq2L6x%-J}@wMkJ=QAxsBA394ul*8E4 zKh2cuEwh>f?vvv*1Yhe1sO1akUTW8F4SQ}uLO8~Se#qCYkjvW{Hqv3k1Iwk3w(k{j z(-gQ6BBNpDIo(E5mm`S8q~-g5aowKjSRD2A&}%K_01>g4jKR&Ot&QDHf#4HnWMFp` z%`$(l`2Oe-R^S7VMuD{Mr?!KtILWH7wT=~OO7j~7>E=Vua2+om`h%sqM)#h4P9`jDnfDc|tMf(&Iwy@XWj!r_xJ|-ZI%o9F|`l z*>V?tyupY_9GK8c!jy{)T@JDo0*?mQQ7IUUp;7GBl0;!7AqeNE^X0L6t8bx_!IHTS zw=Z197gyg`Jn48mHX(#OQyHE6$Jd5E7TKN;lfpOUiTP>IO>5jLrb(`|d!qL&)uMUm zUigH>zVLZtG2H@IMe3K13;Ay_imCe@?uwBe>~#0LSDZeIdUJfHC9wrqpSq?LJ^gR9 zGX>8}HMSR5_RKlAck1sOxVJ;Vklt(ty^1`mTTu^{`t>#AFt~`i5GGB%4X(-wk^r~#OEnwpMteh;p!Z!cwi*c)PnD(e! z1r<#VFJupQTSd6oeCfFxlcs4Fw&*HoamgS3=D;!5t3AbOWCt>HVLZJ2m|McgJ&q0b zI4#Ng#|atd*_z!?b3kL z2l5jgu8b5GBD3?0>zKz1CJl2*IjxC_2P(vMw5mH9MSsT4FpUEiRJS)m7dFN)_QF7w z`hao6>p}PNI=8N7nUl2NYhtLINqui!5Z^|H>7R{bOpgbZ8}4=0weqbx4x^D5eiK7s zmYcm~t0`8#wAbrUQLoVYb5mMNlN1*7MCB=RWUSXmZ_Exgt%T85A7i)kyMQT@^hi5q zh#LFNYck==V&)RxXqedjV;M(I?#OL4lT+nG{L^$jTdqdK&ottR`8AN zYinJGMqq!Anl**uTIFe2N={CvFPWqTyU44z0A)zP3lWrH*18^acbKkiw81poBKcN_ zn|}qVY>n5H(yOYC$2u)YOc$3HqY(W){M1iK0VT)lDif)3p^?zjzsbaRVxUG;zmPBAiLSsp9aZBm zD(77zo>!T);B?+RP|MhLs8mU1e7!IrsOs9(Y_esqQe#KueOVv7+A;C6lq&72#5FcqK%BGsN;y1|B5wFmPT9e+ zL^eKAqT)Q=_>0eW!&z!d-usx<5gm^V3+3yHHDBj)2WIh2XQ3QYY1(b%no1D}X@9?O zu-jAytGTwoz$|H7_S_gwxeRk+Ern+&==!u+e?!$6QpYUYRgh!B9)I&6{_%o=A)7Q* z?Iz$e3RY|=q!bRAy+|YxeCUBJO29}0n+P!->>4 z$eQ(|=DGR|*c5dtvz)WtUhgq3DB0|@d2r+YiST?q5RMm)eV~7uUthiS)NISS4&D@6 zyJLQEzmTk-5^qygTV8^!TJHj1>Cni=8O`^MoBG0`N`E#>=(3FP#UUj^^d37)To@_X8>GrmVWzxJ@H>!B~wd!xs7paOj%SAKZe^4*&bq^IC zsnU|T;WF9N9B?)HP?>w1dVfGlw)xAs+cSo@v%UuHu&RDK%&}ly&FN7;J`r&w=;xw9 zx{Q{D@g#zPA|o(l7=-~58$`@f7$9_@a0Jp%7wo@yWfuIhU7(^T!1RGnME9}d736{3 zu@_T__hjOHXv{*Of}G8E5vF*%w7MW;F5A}6>~NktgwbIB*%oNnbabwBxaF4j+A*60 zrkM4mIE9v|K}Q|!#Bcps&SE?hrO%99_I=HCJ|)PiqyIj8*2G3E{9~@^!oBEG<)ICW z0yR=j*`bTWDXxto@7Fp{CMNOi>)n0aR6%ZhvQd_2_UQt+_1LAiX=k8+m$kW@V6Yx%p<^fZYp9k)wf+QeEj+YH3Gqste>fpn8;d6*5Qd z=Vh2#y066}oF~j}+_S}`#vgm++26bRXHQf1c^^2Z5|U*Wn=R`3W}J#bGR|YNrlQ*) zi{Fb0G{0-+ymg&R#IONspmLv5^;X*>(n1dlq;uf{_MR{v_s_&uR^Wo13Jr;ieOWLP z@^KGRarCKg#$a$$>4u3;KCAhhNXF{o%|m;<8GO-@^RVGjr}&N;DO%gH$l*j`wUL+d zu||7YRH@>e*v5Is7)ldG$Jyqtd(Q6SoZDIUdlUmrIAit>9h_47afJx|Lzt*wrN~lP zA{GNiqgde16TvQiV&#w zPv->UoN_g4Fn|Vz04R>~4pOMKqGJE$=2txwkW(91@F{CU`|O%_-7#{>G6+cXyGFb*sS^aQ5ic9TT*EJOV?&-~_~7Y_&4m z?F-tz2qFqBMv0POm<=T(`$i49q(5D*7egpN2TPe(@d)Shen$SzT7`Gi$(JbIwpo{S z+qb1e_px+>N@YzNs!ICA3i93ZT{c63IC)@TRSc$P(xoUrvj1k2M<(-ap@Q76!gGlS zs)OhZ#l-o`tqIegvI6$ltIydFuFJ*2$(B#Es4~bNv6U0_m~%IF&%Qf(X^d7}kL(Xp zlPI^D`6e?`5iB##kdW<}=&m!4PwE;5zG$4OpYDybx4ex$cVIS7jMh{4TJJaaub;`~ zd-A?NVtV1Ew@q{`v)~BpY5qi2;4^v&D4uJkYf?{yHj+MtkG(Q=%IzwT2`VwaJXY6Y z(BShKeHwqlP}N+elIK_Y1JCnYUne9KfclFN8jzV!kvLVvxBj4h5NiU1>d>i-Dm0u~ zLYB)1j7ltK8}Ox2)50jN;)8G6mJ$X=I8MU@likrjJ`KUe90A=zXhoTTCIzIGQbf=! z@EOGWmOTD7aQQc|?} z-SIkz;lt>bcuuKIDO#JqqTT)iyow-+Lb0%rRP6av_94MLm5b_CyD|EI$t|>Z%(XDS zwE6G4=Q`rAzBI0JrI6KPiH`}nBc9`qwtuxpW2nw-4 z!91swVo91tNtwmETRC)9O3^dn&if@dJPH}jpRisSe7$Zrqfp;$U)ogIJK0mzyZhTO z7wB3da+3mb-RShC(*{{{Ly5c|%w$eOJyD@-+aE6&f&eKT5akP=4rI79lfgVi5?Y63 z$a{e1F9CtZLRKsm{nHEBDqH?$W`&4H7G2hH#xL!XDJjZ6%JH1|=4ZU(#6rW_Vtb+a zUZdNc21AM!XX2(iq=FuRKE;`?rr5MApxszK`wz;A`F^jc?d!wa)x*1D^S?S4MQXP9 zi3}FhUwc}q8)Y~7X9F?WaS==w_SP6>_O{kuz??mEyZ3q2+OV`or%fo>!NR$ZZ^ccM z_pv&!bcVma?@=~%v8S=7&0`u1A z7f$qE;>ES99(+Atj;Ajj%5{W-p6AUQ_diOQwUo1`8m5lz=e6Yt+3LLafeW)M{i>Q? zuatb!ovkv_a`oCt%)vUw!n=vpz&+SOYoEF;CJ!gen`H)+{dh< zF!|Q}#o;`{WR@lvAbu<8`UBY-x_Cw@?vg`5SOcT8dAH6$Sbe_wpw512N`8GFCj3m+go_Nu-$tuxUM@J0KbALE%C%yH zABm^Zzn7=bWA5m-lwS=COV(xlNmHVBQ%uZacIgCAW?!qvU6gr4JsnAS+gujrZhkg7 zRr|Q6%jf(zx3#~jDt#btqKg_z-|$yvh3Mqi3Tl5sPwq(`>r|JEb=5OPvs5OM6V>_4 zw3Uu0x1}M4kjaOp!vlx52&7cO*oX32B-M1?Z%7F5?4xACN} zpLgbEluh0m);$O}-xT|{T&rn@F zG3&0Hd1c&kQWfq*1I(Jw)LO(~ZmpHypqyVSQRk(ooZ(c*rAX~bK7Jz zXGXgB8#gIvJXqrNwpn*Q!sEy5g@YcUG=%y>B`8gRd$A;JG6ck`NSa8q?dY zq@QMT_?D?_y=L%QS2*n_$53-d_6-Glj;ih$U^E{HD;Vrtb`T@Wg9^Er zDD=(!n|l)C1p?*5B782m3fUVPl%?oEanY^D6hUcMa@v?r zOpKS9*65jLiBw0?n9cP2d_1m-a!dmO(`JniG?xvhG$=Xi%UlQc3Z;3}I9ljQt5Sv_ zQ%i!>tbPCaXkbTLM(^8b!?MDitBs{H@>mZCk5(1DF;gu2t1+Xy-tlR4&8T)CPRPzC zdUKD*(6v-EpEsS2xs_YyN)T+b@?g%_Mh=~* z&-aK2=Shp?9PPOrpO~97%(P@PICK{j6GAIP3(BdV<2Ag!)N*UCT=y?C>tp2knRTU< zI=IHb)tAS}`L@zr^O)+de$~GP)O5MZO`0VnNeu1Xqk4Lfu66KX5+A0+z~b4=yMc+@ z^Jx?NhhW6KZ?fmbwXgNU+I!Lxbv`9k<|Wi*>#2V}I1xA(PoA(z98>go!P>8mBFi=h z_w2TDq;S^r9@5wUgS`zJA`ea2sv_uaz~Kjey;9Kb#bOaa(}u+(A@CSH5&r(O6Y;-S zAx0!WqfPad8QT}chu&zDmajc|W|-Aes!dIsz?vOSX?%aWri>k8$AN8Cf&6JC-k2dkjf~t4!K@iH{Tjv6;dN{`L~btyHE~{=Q%M7 ze(WOU!+Kh@UJ9iveEOwX`Btlxq8l3RCbU$CzL$%2t3GH~8d<(gr}nnvK2<+-{B47c zhp#Nyvd|XZW0Ku1x4HQ4S-4;^`Kj&s?s~1+iuSS1zPnot3=54#&VNpn(5ryKbKfsf zjdXD_&iitm!|lNTa9@Tw3I>V|&^usZmV!k?N;bH_60l&R2c&u=3JJp&!p{wSSNXe4 zD9cZKX)SuB>di67N>jVCLbS|BN8Q64^DajcX|w*cJj}+q#zL*Gx0cO8$K-Co7Q2J1 zZsQ*`u-xJD_9ay&ikW^4alSqARfI+TQfyVXHfb6YJIveedrMQJ#9&eT!*MY-7lwG` zs`c|@E+-y&sTsAzwCAlGN*X@4C)~oRtv83FrB#2ka(oqGQm~@AtKB}`Ksuj*$#JKK zlgi9ZJ1Oo)Di3ce#`;%vsvag8=7%1l|trF%2zU9O|Mn$q%7Kv+Us$g zh`t}y@&DmO!~<0xA&rv4g2N~T1W1AdJv5^*ChWLGph)4t84+x5erl#CIGVN^={3Vfz`?HF=~#$A^A zZZ@;2vihVWtCO77ZRPTZ8iH;;8x+24KKxoK^R)78=C^aIk2LTeP0~vJT?QcbpVv0) zpa-i`b7mq%1RrJ9=AS;oblz*WFke(YI!OJ@%DnK00_C)cd6#nHm|tRYnjIL|^Hq89 zZZl<+OjOjHHS=jN+9gE{9&x0qCs$;Et_#CVbjig^yf+WL`fX#{p{(7O$QB{{Q;v!XvX z$h`jJnga|)3D9u@zPt(Czrcx}E!ra>p{;?1FgGwngo+W1_&JB(Eq_8$T0CFKWjr!i zr#c{@thsr1A%4Bl!lcoKFJH!gU+XzN;u?csDKs_B$!k|KjK|p=dcpe44H)-eY}6qJ zb^zimY=i=SELfNOab|)yUCkOIt0n6bq-I_ogS_3cXb&F6I4!fo zbc5s_P8qJjQ>%s^wVp_#No>&3;g_3UNbI~P9>pjeMKsJkIFj*tKO`H4AuOiV=fu7; z&2Orec_@$bCAdjVRF_JOwebXzdPX8sOU)hb6=p{~1!^2K%GHmOV~$AI$-wo5!U^2h5% z0Cy-UxMGpWK=UPIkdX8sg##Tdm;-=e3j7^(U@*D->4jTJ>|6QaY-*-b?U|OT@v%8%leKfuR<(^g<=Hvs-}J97UhY10V$zc_5;n+m za(sM3p-%PB-qExwU+tVqy}qpv?G_iShvdIm6;Im4l*;x&Gcbjx#+R~tVd5^iwa0)q z_}O2nQYunwF>@z#x7D`D@VxG*OZBl1kccz~E1xjtTiL`DniN0rZI&p74 z_=*Z?%{W~W$l~ScyLfwTNZ`EUm{YFF;KTdbl;QE944Je1a;KZCrw|YAZ@x1V!1VP^ z=yaIMAGLS;cK9vc{(9ZbjOdK`u6+xs1-gN|={63@pJEc!7i{2VTC3?bhHld+p?wXI z8hUUp`~s4e7CgM_;lNvJ>iFU=>#u#n#UAF!7nvbq3?VczBAbucfXr`^m zKQe1jpWTu}i(BX%Sje&$V}!15>psRGPegj3Q%U+0QdAZvlV&~N-KGzA^lnSqXx!rV zEnUOl{hh~W7acyRhOm0SZZ0_8egK0CU^uYe2eSd}X_Pbh?sdh!w=qX$Egm*3`F}DBVFniapMEM}SZLDp;>P0d*BmCVO;qny z-K^UhlRO`L>j?G9!mgS=5#i9;wbwSXjN12Fm(W|D+UO3S&4L4QRB>tXggP^!yE%P8 z)iHpvLx$0BKfFHf^WT45>@3DV+!SvVr{JTluvDZW{SRi+yKxK9^~! z{({RgjFZ_uUy|T0iZ;O#rJ)32*NX@?Z?&igJf{n+YR|>#h}Oq;;Nh zoX-t(we8}u%$vLB^|cHeAX`7;5j*mG%b=)-+rVq6+enKy`A+A1YIpes15ce5xJE|{Wzrv#+)arM}8w3XU_OG8!bgE3LqT1 zb!^{_(>mtMJ$3JzT8=pjh0m#9qbkc2_tos_e64Z%fMkcj64|&RoM)Y-^K8v6EdRs~ zgNTIB4NgTy?u%!-uK77K-pDxh`kGd}!remVWU94)u;cuGdBnN8{kT8`R{&r^u}#?| zO(5t2ST`8p_`;P8Nkn6yWiL$xfh7s~(>wE{(6g&UvvV^O6&)KF9$D9xGvAnnj1@+- z*8JgXSeieXmPf0S-Nj2Dy}ZGCuSE9OFdHzSUtRVovE8myMttYjf^bNY9=gBAUo&F* z*?^<)N0#li3&EPbS7`E22VTuDHEWK>TU1MYg#t0hDeQ4O^%srulhxwF-rKsWXoA&Y zU#4x;x({ENKJ_8=#Nokl&FuW#Mch(^Q{}>R$2!BpuZ^taOCr)>{n{8|&~1VkUz=tB zxT$=mek@zLiC?xSH=CaM=K?cz;F|kTmb}9(QU;O~Lp-7brj1Y3FnR@JR4r@5>gUvk z=Jr_1dpQc7-DJ7pYN5-X`HJ`VZg&1rf--ubIU_VTBKxl5GtP82HYy-V6@zX@@Q}9pgMnqwUV=B?)uIpKZ*|G*mG`m4z2%-sX)u_#sttn9 z+WMaFs1s~fT3EOKX}&j#CaUKCPVf)6IjS<`2X|lAepwK{L`lRIi+#v3i@8o+5MT~> z48OMbFFx&H9h$2>HkPrqZBSF?wc>@mX+rIn>Q zDtYt5#|VZKYZix-#YI`cW8BabXs{)qn^9n+bL3^2o_?w4y{h!GzUw{4vI zyc!=eu-6MIS}kJhcX>ML>oMEFuu82-83=`-QPbA$YeK!W(6EyCnMh`iU-c}lJGRv> zuYO3=q0ru6r~jjCx;IAbNTkuJw&B`_Ix%po)evQ_k;}Pkb24?T_l{qr6so(qG(%}W zvh%akNF7rtCF5*Hn7)B=;r;uHI1QN^{gDot0 zF2fKEstCjJ?TjnC5OO2nsh7d72ZHZi$@8-8_+2r@OYpGS=$2 zAx-*HqNrKBd-%D6Ra$!#&tyc^K+9LfX3qMr0r|qBTYC;jP-=onOx>%jqM^>vDn84W z4G$Cw$CD#wl;S3Dl%9I!Nq@n&zY84N%!O9#H&}O4g5$Hs2K#^0n;Iyr=I`mYa2c9z zbaqm=Q_ql2ZrX4xTn|NAG^z2kQ4Qc&uX%U$#~**bz^4*HCL`G4728+f4;;b>~d>juO?hkAj~`;x--($8s;LmRa`8``ohcpez~5-le$dv_VQbXo&65Hr(!Q6kje;d_H2B@?Z|7^tr?46<%Gb{jYEVucfVPoAmib zc|vJVXFLxtCLP%%G^ql*^y=$Q12>YqeUL77^QNYay~15JhA(_?A^0la@&zs$UFVh9`g7fwzp}@HNGf z-4*k0$7b73*Q9|+nCF?>GaKf>fR(6+g;ODMQHw`Bw@<`dOr^=cMH!vC!Fef2^s<{y z(%AgXETib=S;gc`;HEL%P8ck?hupix)IGzK>&5e_`hiJOL{V(Zx3SS+d4a(yi=_dh z;U-;syX*P-ZbcaCR(LU1FXHCHld7)p8{sj2)cAC?)AB;t{U>kmUo5*`FZw<*ouOWhL0SLNLS~may^xz zw3+fdCd?dOvufQpM>xyF?zAiaAy0}-Cg6xTP@99RISET5fdCAPf;=!ZWU80|RRFwu z!Jp&jG3Woq?^mILbVi+np{C`-f9}52{Sb_iODZsFW^?<_rE2qZ!j?1@Ch91UHMaXhHo<$u=P?ciP3t zmmXrxFR4kaK5$#>(>Joo8xDD3w%)-o%)PL9$?93+;%uv+mE-m6a}kB)tdsQ?Cm;3g zyy=l7b(XwqUqtTTNyc`INpp{7o563fKi*(4Lk4jQBa*~9xS>wa)f87q$L+Cne9-M>wGi|<=j#!RdM9dizkZyx z2Y8YMyyInL&VXushmW>^vlP8~> zPOMFC=@?C3NOdxBrgMrc(+npS{zJimmLx(`155(gJzf($9uJl_KmY2X3sQ3P7#yj|DAaC4aJut(RRH*fpk@hi<5 z=hF1A&o+%UTEK!01jyF;Sv}NId zZStR!AC55|6qiGTzO?<}ZH#@|LpL3rzBus~x5?23)N2_TG*5MBj%JO$(w9~RuR}gr z0R1Y?4ReTIZ&B54c>frWYzB5=@9 zKtl4(&%R)8^fj4`HfBw@;)z6Sq1@3z)7CAcJ0>s^x*unVtk!snXTInm2|1K&z`Si@ z{+NT$4rlm+br8CvjKak>8b~L9DdrpYQ$sfWqZ0bzxsA(s-T~TB^S6mNOA&U~6BgC9 zU5-nRVZJOH1b}IGP4vBp81Y+O60MeG9DLXzDK3u?HfO8BFe}`fv{NZpppn%nQ&X~*j#=MjK!qJPJ-ulJuO?Q4< z`f8SwVvi(xg5JbOlj*x}&BH}S%TCgal%v(qtJB8(Xy;DT*_PwTnGgN5e>aON>onwR zT|SO>sbH%DXHFx-ZQbT1nuRoX$9slS;+XVD6?YT|?H3-F>}oDx<^5q9 z_AY1@#AdtY80mJq)?{y-nR(kuG3~nadv)Dn4W-HMk*oIF?-H@xF)z*mIS&jEG6E7uf7+0e zix5{)(!HAb$RZ+d@bm%C>$|uA%E;OrI?-*OkzJhm5z%|f{1iQ7!LM?C4Y-=j6X;g3 z!Nh(wtf@KTDxOdu=5IX^rrt3;*skxPtHsCvO>Ds$H?o%1HK14-FeTnvW9_6IOS#t} z3*l>OdYKo~gv6vntDG{=&CQ%qPs&tJl=5`ec_H-As17brDO6EAVUJO@0FH5~BjNTw zfv8bamrG;Tjd>Rbo68P6&o8L1dn{X7nC~;JR5*R&U0t8rC{FEuoq5omfxV9}Jy11! zSrG-DN@MqZw9uiHPcDJ_5u(M9l3q3%wN8e=(*D|@ROWioiXfOcqG{DX6!B%Sx8TyJ zRJk zOju25SL$@!y#t~fczQ;2#E@l-8E@LFjm@Y_yZlz&*`l^HSx@e5z@vfMu`$xt+_aLvw}_YL28X?(kuTW*_h`8rYg9NNh|N3D_#5meP)jf}U#ug7)M z3|ct@iek+IAOE=CU{mGUo>6f4ktAWYKzR-hh&U(>fiwl;SR|1IEqd(#q5{!)ss32= znzcy}H+Xs^=YGt6erA*+R-AoS^>O%mOCE{sj~63nRFiHEXg5^&H93!GbcNgoouAHp z0RL7+C$sv5RkDe*HMGRz6G?T~46{n!v_$vM`HJk@P;Ty>zJneWD8h6YVRcULHdOS} zR=?d5S9h#asJf7qICA0A)81fgUsR-~Tiu6UCmZQ--KAyCP0PoHICo9GSgjnk7hDtV zJ9d07_o(xs-$%W(oJ~GCc?XkaYSr0BjV;3`$x`a~mOfrTltx3Wdl}qeK@PF*;iULl zwT%6Tc_p~%Awf+9Ms4800Xqw3l~7?oD+>I3kvIZ$=z+>XLi}{S#=1uBsG0X4?EBVK zS=e#a?{|N*tNZc?meRldX>>oDUKWr&ZAkh3?M=q(qqZtbvW!f5s1JXSkgoa?Zg={t zRoE=?o>`gz@?rIp!VmMKi|9yWzcqf;8+vm6`u@}No$1Ce@GN9*TNq4z<3-i~Y4c@8 z)(rG|@fHs7c8jD_=^4yGqre7SzRNA!iO`13it8hO6XCq|lfNk*X2AUGOKOSC38Htl znFMO7#~|GPAtUQaqiAlSr|IbY&T2KBopK>5*?w_)ek#5){OFioZ~_>2kX(v31Ig#eGDb3-|oH3(b&cE*dNN7?E3-=(t{k-l%i&#*C^VU&p3#?bCog|zH zNX+>vq;WIQOCeWyLTuKpDcG%dc2QrV_>rH$aLva~EfH#%Yua`g!Ynmt&;n-7a_qzn zXHIs1bDbyjojHo^`lL8Yx=+=|r6eJMQkd}=@IRrq3WW!vB*tArk(OayEvtv{`9@$t}ahgB3z&Gj; zx%F^_EB@?p?0CH4qS2bEM9O6DynaV*w(Hm(q?{#H(?BHYuou4Yb)$V&7V?wygpox8 zt_V$J?Va@vracr!R_0AUi%%FFYQ@xJM_qE0l)*0Y@Vd))+1KS?SMjgj_zU|#vk&bq z{?9-BzyI3u%kTg1pSJxneuDjvH}KbNu34HAt!!@C+e&C&Ui-`H8xvW^_D(#%{JQ4r z>R*0YV*>xL|NCd|gn#<&fBwuY6PxG%+O+@w&-~?=@tf!V?aw5GB8dzGHc1o;#I_)> zK%t0u;HZMFDG*1n&~7#X!%Vj7mOE@+mImTqFW_?QxnDV-2lN4OnS}Qbu!(TDB*VXu z2|$VkT_RRWQWC`uOTe+KDekZhMeYoJ|G8t}r&{@2!d(a_1&Sl1PYO)2vQ(4 zhDr|f$Dr^BZy_lVMRJS%(%{AI??1QpT25>jvw*!GXpW$f4|OAC;1Hqmm&Sp6EClc& z;SL~$2K74E*hfqDdVc@8*RAKoM#Jw7$-+PmXWPb+$s{SZs|yC2W)S-V1Z42|2G%5w z>)-ssrjxP%>d-A;@66vgu`vh&0X!5SHJ6A1J7X*c-Zdh$@X+kk8JH9jAoCL@g+Qp~ zZoLIBgt&kIxlLh%tz2*D41#|$e6X-s6EqR7R4B9*9-8y8+&It^VZmq_Ee&yd++tIt zWxT$R&9{jY8;z1;r#gc@CVUvdIvtOb#)7pDRHLx|V7G{ulm?P29)yA1J@=8V)3?8m zUACDM8wq?^b|Qia^synCjy*v)!Grt;P}$btU^*hnHi6{&xsS`qj{ZyXvwXcBw{l`5 zV8QXyV2=T19hoQvL7|Wv4|0C6=t5#8O~8Ph2<8kXaJ%L1xoKBj1Hb>=N49ffOClg$ ziUds%Nwg$8?h;lX4jD+f1X{2(usndV2pgU)XW0=)VrXe8I)l2<{e{`&rV zuKNckwh2_sU@eFu65zeYBf$g>yi4$)^ai;wd^@ouNM|MCxUU^jrK$$LUps6Rapmjd@5 z@Kl8uCJcPciNycWQ<3094l(Y~EG0r?n>*}0(K-3=WA7E_#D;+)n2}?p2*7v;-aDQQ zTp1`M;M>jCb4fx2kbq{tdfZ}f>0f;L{j;UwC?_@%15`IG0)U9{mP@jY1t0~O42Kzf zO5k}hKr=-_@;0~F9r4-+zyI765l(D05`~3lhT9^MZHY>TNF~_QQZQphK#nvP@`qvb zL3^58Y@KTAvg!NsGgVxS6B|o_>@6rop+=E{-39^?1QtjWI4~}OA{A6UC?ZrYCS0%g zmtQHw?>~3faZYR?*g)1Y(4Qb28%u&h2}ry^ZG#^lGH}2URvKtg>`lP!b6c5zy7c`$ z<}JaAO@=r>cs`JCvh&6eM3|NW0UW-ZP&z?g92)92vMW9j&PY(*(fYy?yX zKy`rc7MhF#>H`jefL9;#A3-dD!GmfMqUy+Kwm}Scdu&p>o%a3Td;-CV4e}8n|B{P|r)l*~ay6Ze#qH;`i5^iQ&Ws8ZFzzjtJST;4BW@KN1Ek#33&YesvNNj(QM^ z!0njpvt?)xZke2N`TkDAablZ5U1oxTD*+a0hf?5M$yQfF@dDpc(7{TB>Kq5wHb}1P zmX*Qx%dw4RIk6EC91QJ$0G5Vg1xSuCvyp<&7lhQp1%V6?4aRU#!EpPYugq{?j=fEu z6Px=)ckcGs%0Tkv*o6xJhRt<6Id|AA*~4LD9>x1x|8ea~~Vc z9rnu9Mh4sfqp&5b90@t#~t>{h`8n0j7yx@ z+y};ShrKfHY&mw~Wln7F!_T^+8@*xZNTaEHAzxn()_CnHX5 z?sHtY!(N#}vK-sx3MV%AnIzm{uME^!jx9>y#O6L;gFEb%;SkHQ+lidm+($)lhrKfB zU^(_f5+^qIu?O5?uk`F+jxA@(iOs!(KX=$G{pXisk6q=&=H88-JM5Ka>dUc1u5)5@ zZ>`Q9_DW;$<=D#RoY>skgmZ_z(&KtL_L2oBHusL#++nY@YhI3>XvK-my?HZt*ek7v zmtz}R{~I>fM#S7f^IE}*v5}IvAMTV;SPJH z3&?WpEnb}1+hrQC#VmWq!Hzzjt{ubO}uXJZvj&13~iOs!N19#Xfod%X;^ZRjP zbMHIA9rjAu{BrE7r<~Z_i|Dz-Ua0_Ij_n@6iOszpoIC85YUbtG;?Fp-xz{#xhrLpm zyBxbah!Yzm7+lJ9xx-#5GF^`C9m0vtz2uZT?3HrK<=7{}II+1GPjZL7Qv0_Y`&&3C zHup+E?yy(t*p_2If6j@`y{e5n?3JRd<=E;kII+2xXmN+VQedY*RJ~o{zHPlN+f^zWlJ3U-~JB@$~~n3 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bn256/bn256_fuzz.go b/tests/fuzzers/bn256/bn256_fuzz.go new file mode 100644 index 0000000..75f7d59 --- /dev/null +++ b/tests/fuzzers/bn256/bn256_fuzz.go @@ -0,0 +1,183 @@ +// 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 bn256 + +import ( + "bytes" + "fmt" + "io" + "math/big" + + "github.com/consensys/gnark-crypto/ecc/bn254" + cloudflare "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" + google "github.com/ethereum/go-ethereum/crypto/bn256/google" +) + +func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1, *bn254.G1Affine) { + _, xc, err := cloudflare.RandomG1(input) + if err != nil { + // insufficient input + return nil, nil, nil + } + xg := new(google.G1) + if _, err := xg.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) + } + xs := new(bn254.G1Affine) + if err := xs.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> gnark: %v", err)) + } + return xc, xg, xs +} + +func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2, *bn254.G2Affine) { + _, xc, err := cloudflare.RandomG2(input) + if err != nil { + // insufficient input + return nil, nil, nil + } + xg := new(google.G2) + if _, err := xg.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) + } + xs := new(bn254.G2Affine) + if err := xs.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> gnark: %v", err)) + } + return xc, xg, xs +} + +// 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 { + return 0 + } + yc, yg, ys := getG1Points(input) + if yc == nil { + return 0 + } + // Ensure both libs can parse the second curve point + // Add the two points and ensure they result in the same output + rc := new(cloudflare.G1) + rc.Add(xc, yc) + + rg := new(google.G1) + rg.Add(xg, yg) + + tmpX := new(bn254.G1Jac).FromAffine(xs) + tmpY := new(bn254.G1Jac).FromAffine(ys) + rs := new(bn254.G1Affine).FromJacobian(tmpX.AddAssign(tmpY)) + + if !bytes.Equal(rc.Marshal(), rg.Marshal()) { + panic("add mismatch: cloudflare/google") + } + + if !bytes.Equal(rc.Marshal(), rs.Marshal()) { + panic("add mismatch: cloudflare/gnark") + } + return 1 +} + +// fuzzMul fuzzez bn256 scalar multiplication between the Google and Cloudflare +// libraries. +func fuzzMul(data []byte) int { + input := bytes.NewReader(data) + pc, pg, ps := getG1Points(input) + if pc == nil { + return 0 + } + // Add the two points and ensure they result in the same output + remaining := input.Len() + if remaining == 0 { + return 0 + } + if remaining > 128 { + // The evm only ever uses 32 byte integers, we need to cap this otherwise + // we run into slow exec. A 236Kb byte integer cause oss-fuzz to report it as slow. + // 128 bytes should be fine though + return 0 + } + buf := make([]byte, remaining) + input.Read(buf) + + rc := new(cloudflare.G1) + rc.ScalarMult(pc, new(big.Int).SetBytes(buf)) + + rg := new(google.G1) + rg.ScalarMult(pg, new(big.Int).SetBytes(buf)) + + rs := new(bn254.G1Jac) + psJac := new(bn254.G1Jac).FromAffine(ps) + rs.ScalarMultiplication(psJac, new(big.Int).SetBytes(buf)) + rsAffine := new(bn254.G1Affine).FromJacobian(rs) + + if !bytes.Equal(rc.Marshal(), rg.Marshal()) { + panic("scalar mul mismatch: cloudflare/google") + } + if !bytes.Equal(rc.Marshal(), rsAffine.Marshal()) { + panic("scalar mul mismatch: cloudflare/gnark") + } + return 1 +} + +func fuzzPair(data []byte) int { + input := bytes.NewReader(data) + pc, pg, ps := getG1Points(input) + if pc == nil { + return 0 + } + tc, tg, ts := getG2Points(input) + if tc == nil { + return 0 + } + + // Pair the two points and ensure they result in the same output + clPair := cloudflare.Pair(pc, tc).Marshal() + gPair := google.Pair(pg, tg).Marshal() + if !bytes.Equal(clPair, gPair) { + panic("pairing mismatch: cloudflare/google") + } + cPair, err := bn254.Pair([]bn254.G1Affine{*ps}, []bn254.G2Affine{*ts}) + if err != nil { + panic(fmt.Sprintf("gnark/bn254 encountered error: %v", err)) + } + + // gnark uses a different pairing algorithm which might produce + // different but also correct outputs, we need to scale the output by s + + u, _ := new(big.Int).SetString("0x44e992b44a6909f1", 0) + u_exp2 := new(big.Int).Exp(u, big.NewInt(2), nil) // u^2 + u_6_exp2 := new(big.Int).Mul(big.NewInt(6), u_exp2) // 6*u^2 + u_3 := new(big.Int).Mul(big.NewInt(3), u) // 3*u + inner := u_6_exp2.Add(u_6_exp2, u_3) // 6*u^2 + 3*u + inner.Add(inner, big.NewInt(1)) // 6*u^2 + 3*u + 1 + u_2 := new(big.Int).Mul(big.NewInt(2), u) // 2*u + s := u_2.Mul(u_2, inner) // 2*u(6*u^2 + 3*u + 1) + + gRes := new(bn254.GT) + if err := gRes.SetBytes(clPair); err != nil { + panic(err) + } + gRes = gRes.Exp(*gRes, s) + if !bytes.Equal(cPair.Marshal(), gRes.Marshal()) { + panic("pairing mismatch: cloudflare/gnark") + } + + return 1 +} diff --git a/tests/fuzzers/bn256/bn256_test.go b/tests/fuzzers/bn256/bn256_test.go new file mode 100644 index 0000000..8b2f962 --- /dev/null +++ b/tests/fuzzers/bn256/bn256_test.go @@ -0,0 +1,37 @@ +// 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 bn256 + +import "testing" + +func FuzzAdd(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzAdd(data) + }) +} + +func FuzzMul(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzMul(data) + }) +} + +func FuzzPair(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzPair(data) + }) +} diff --git a/tests/fuzzers/difficulty/difficulty-fuzz.go b/tests/fuzzers/difficulty/difficulty-fuzz.go new file mode 100644 index 0000000..fbbd7f6 --- /dev/null +++ b/tests/fuzzers/difficulty/difficulty-fuzz.go @@ -0,0 +1,146 @@ +// 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 difficulty + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/types" +) + +type fuzzer struct { + input io.Reader + exhausted 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 +} + +func (f *fuzzer) readUint64(min, max uint64) uint64 { + if min == max { + return min + } + var a uint64 + if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { + f.exhausted = true + } + a = min + a%(max-min) + return a +} +func (f *fuzzer) readBool() bool { + return f.read(1)[0]&0x1 == 0 +} + +// Fuzz 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() +} + +var minDifficulty = big.NewInt(0x2000) + +type calculator func(time uint64, parent *types.Header) *big.Int + +func (f *fuzzer) fuzz() int { + // A parent header + header := &types.Header{} + if f.readBool() { + header.UncleHash = types.EmptyUncleHash + } + // Difficulty can range between 0x2000 (2 bytes) and up to 32 bytes + { + diff := new(big.Int).SetBytes(f.readSlice(2, 32)) + if diff.Cmp(minDifficulty) < 0 { + diff.Set(minDifficulty) + } + header.Difficulty = diff + } + // Number can range between 0 and up to 32 bytes (but not so that the child exceeds it) + { + // However, if we use astronomic numbers, then the bomb exp karatsuba calculation + // in the legacy methods) + // times out, so we limit it to fit within reasonable bounds + number := new(big.Int).SetBytes(f.readSlice(0, 4)) // 4 bytes: 32 bits: block num max 4 billion + header.Number = number + } + // Both parent and child time must fit within uint64 + var time uint64 + { + childTime := f.readUint64(1, 0xFFFFFFFFFFFFFFFF) + //fmt.Printf("childTime: %x\n",childTime) + delta := f.readUint64(1, childTime) + //fmt.Printf("delta: %v\n", delta) + pTime := childTime - delta + header.Time = pTime + time = childTime + } + // Bomb delay will never exceed uint64 + bombDelay := new(big.Int).SetUint64(f.readUint64(1, 0xFFFFFFFFFFFFFFFe)) + + if f.exhausted { + return 0 + } + + for i, pair := range []struct { + bigFn calculator + u256Fn calculator + }{ + {ethash.FrontierDifficultyCalculator, ethash.CalcDifficultyFrontierU256}, + {ethash.HomesteadDifficultyCalculator, ethash.CalcDifficultyHomesteadU256}, + {ethash.DynamicDifficultyCalculator(bombDelay), ethash.MakeDifficultyCalculatorU256(bombDelay)}, + } { + want := pair.bigFn(time, header) + have := pair.u256Fn(time, header) + if want.Cmp(have) != 0 { + panic(fmt.Sprintf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have, + header.Number, header.Time, time, bombDelay)) + } + } + return 1 +} diff --git a/tests/fuzzers/difficulty/difficulty_test.go b/tests/fuzzers/difficulty/difficulty_test.go new file mode 100644 index 0000000..49beedb --- /dev/null +++ b/tests/fuzzers/difficulty/difficulty_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 difficulty + +import "testing" + +func Fuzz(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(data) + }) +} diff --git a/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 b/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 new file mode 100644 index 0000000000000000000000000000000000000000..31c08bafaff6f7268e7babe1eacae4239d397cf8 GIT binary patch literal 16005 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 b/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 new file mode 100644 index 0000000000000000000000000000000000000000..7bce13ef80e5a67258a89e3a0e4da1c7610e20bb GIT binary patch literal 15965 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 LNurw1vw$0>u6V$y literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 b/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 new file mode 100644 index 0000000000000000000000000000000000000000..613e76a020f5870f2e45687b8bdb97a1b570381a GIT binary patch literal 15976 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

      Pa>BrM`Ck<4mCkN=Y`#_E!Cw7L5Ojm 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 W%DL(vR^B^@dJtVnqMFaMfE%W*cEP3q literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 b/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 new file mode 100644 index 0000000000000000000000000000000000000000..805ad8df77be7ed7802ef8666f0cb5e180b2caaf GIT binary patch literal 14980 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

      Pa>BrM`Ck<4mCkN=Y`#_E!Cw7L5Ojm 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 OdJtVnqMFaMfE%U+^5S^_ literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 b/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 new file mode 100644 index 0000000000000000000000000000000000000000..605acf81c1a9814ac7c3950c4cc47081b4d29f4e GIT binary patch literal 15977 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 X_R6{HA6DKwhk6iQNurw1vw$0>5y--x literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 b/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 new file mode 100644 index 0000000000000000000000000000000000000000..8f32dd775ada5351365dbe3035668944b112046e GIT binary patch literal 16000 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

      tqh-JF4rtl^ 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 b/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 new file mode 100644 index 0000000000000000000000000000000000000000..af96210f204e19513ebde336e6db31430d771a7c GIT binary patch literal 1748 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

      tqh-JF4rtl^ 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go new file mode 100644 index 0000000..4d94d31 --- /dev/null +++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go @@ -0,0 +1,199 @@ +// 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 rangeproof + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "slices" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +type kv struct { + k, v []byte + t bool +} + +type fuzzer struct { + input io.Reader + exhausted bool +} + +func (f *fuzzer) randBytes(n int) []byte { + r := make([]byte, n) + if _, err := f.input.Read(r); err != nil { + f.exhausted = true + } + return r +} + +func (f *fuzzer) readInt() uint64 { + var x uint64 + if err := binary.Read(f.input, binary.LittleEndian, &x); err != nil { + f.exhausted = true + } + return x +} + +func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) { + trie := trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)) + vals := make(map[string]*kv) + size := f.readInt() + // Fill it with some fluff + for i := byte(0); i < byte(size); i++ { + value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false} + trie.MustUpdate(value.k, value.v) + trie.MustUpdate(value2.k, value2.v) + vals[string(value.k)] = value + vals[string(value2.k)] = value2 + } + if f.exhausted { + return nil, nil + } + // And now fill with some random + for i := 0; i < n; i++ { + k := f.randBytes(32) + v := f.randBytes(20) + value := &kv{k, v, false} + trie.MustUpdate(k, v) + vals[string(k)] = value + if f.exhausted { + return nil, nil + } + } + return trie, vals +} + +func (f *fuzzer) fuzz() int { + maxSize := 200 + tr, vals := f.randomTrie(1 + int(f.readInt())%maxSize) + if f.exhausted { + return 0 // input too short + } + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + if len(entries) <= 1 { + return 0 + } + slices.SortFunc(entries, func(a, b *kv) int { + return bytes.Compare(a.k, b.k) + }) + + var ok = 0 + for { + start := int(f.readInt() % uint64(len(entries))) + end := 1 + int(f.readInt()%uint64(len(entries)-1)) + testcase := int(f.readInt() % uint64(6)) + index := int(f.readInt() & 0xFFFFFFFF) + index2 := int(f.readInt() & 0xFFFFFFFF) + if f.exhausted { + break + } + proof := memorydb.New() + if err := tr.Prove(entries[start].k, proof); err != nil { + panic(fmt.Sprintf("Failed to prove the first node %v", err)) + } + if err := tr.Prove(entries[end-1].k, proof); err != nil { + panic(fmt.Sprintf("Failed to prove the last node %v", err)) + } + var keys [][]byte + var vals [][]byte + for i := start; i < end; i++ { + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + if len(keys) == 0 { + return 0 + } + var first = keys[0] + testcase %= 6 + switch testcase { + case 0: + // Modified key + keys[index%len(keys)] = f.randBytes(32) // In theory it can't be same + case 1: + // Modified val + vals[index%len(vals)] = f.randBytes(20) // In theory it can't be same + case 2: + // Gapped entry slice + index = index % len(keys) + keys = append(keys[:index], keys[index+1:]...) + vals = append(vals[:index], vals[index+1:]...) + case 3: + // Out of order + index1 := index % len(keys) + index2 := index2 % len(keys) + keys[index1], keys[index2] = keys[index2], keys[index1] + vals[index1], vals[index2] = vals[index2], vals[index1] + case 4: + // Set random key to nil, do nothing + keys[index%len(keys)] = nil + case 5: + // Set random value to nil, deletion + vals[index%len(vals)] = nil + + // Other cases: + // Modify something in the proof db + // add stuff to proof db + // drop stuff from proof db + } + if f.exhausted { + break + } + ok = 1 + //nodes, subtrie + hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, keys, vals, proof) + if err != nil { + if hasMore { + panic("err != nil && hasMore == true") + } + } + } + return ok +} + +// 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 { + if len(input) < 100 { + return 0 + } + r := bytes.NewReader(input) + f := fuzzer{ + input: r, + exhausted: false, + } + return f.fuzz() +} diff --git a/tests/fuzzers/rangeproof/rangeproof_test.go b/tests/fuzzers/rangeproof/rangeproof_test.go new file mode 100644 index 0000000..bc7badc --- /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/secp256k1/secp_test.go b/tests/fuzzers/secp256k1/secp_test.go new file mode 100644 index 0000000..ca30397 --- /dev/null +++ b/tests/fuzzers/secp256k1/secp_test.go @@ -0,0 +1,53 @@ +// 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 secp256k1 + +import ( + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/ethereum/go-ethereum/crypto/secp256k1" +) + +func TestFuzzer(t *testing.T) { + 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/txfetcher/corpus/0151ee1d0db4c74d3bcdfa4f7396a4c8538748c9-2 b/tests/fuzzers/txfetcher/corpus/0151ee1d0db4c74d3bcdfa4f7396a4c8538748c9-2 new file mode 100644 index 0000000..2c75e9c --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/0151ee1d0db4c74d3bcdfa4f7396a4c8538748c9-2 @@ -0,0 +1 @@ +¿½ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/020dd7b492a6eb34ff0b7d8ee46189422c37e4a7-6 b/tests/fuzzers/txfetcher/corpus/020dd7b492a6eb34ff0b7d8ee46189422c37e4a7-6 new file mode 100644 index 0000000000000000000000000000000000000000..8d3b57789e79a7046916b2c7646f038f443671ad GIT binary patch literal 14 VcmZRuu&`iYU~sUour#;W4*(9%0>A(O literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/021d1144e359233c496e22c3250609b11b213e9f-4 b/tests/fuzzers/txfetcher/corpus/021d1144e359233c496e22c3250609b11b213e9f-4 new file mode 100644 index 0000000..7373189 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/021d1144e359233c496e22c3250609b11b213e9f-4 @@ -0,0 +1,12 @@ + TESTING KEY----- +MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 +SjY1bIw4iAJm2gsvvZhIrCHS3l6afab4pZB +l2+XsDlrKBxKKtDrGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTtqJQIDAQAB +AoGAGRzwwir7XvBOAy5tuV6ef6anZzus1s1Y1Clb6HbnWWF/wbZGOpet +3m4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKZTXtdZrh+k7hx0nTP8Jcb +uqFk541awmMogY/EfbWd6IOkp+4xqjlFBEDytgbIECQQDvH/6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz84SHEg1Ak/7KCxmD/sfgS5TeuNi8DoUBEmiSJwm7FX +ftxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su43sjXNueLKH8+ph2UfQuU9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xl/DoCzjA0CQQDU +y2pGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIáo‡X +qUn3Xh9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JMhNRcVFMO8dDaFo +f9Oeos0UotgiDktdQHxdNEwLjQlJBz+OtwwA=---E RATTIEY- \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/0d28327b1fb52c1ba02a6eb96675c31633921bb2-2 b/tests/fuzzers/txfetcher/corpus/0d28327b1fb52c1ba02a6eb96675c31633921bb2-2 new file mode 100644 index 0000000..8cc3039 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/0d28327b1fb52c1ba02a6eb96675c31633921bb2-2 @@ -0,0 +1,15 @@ +¸&^£áo‡È—-----BEGIN RSA TESTING KEY----- +MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 +SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB +l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB +AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp +jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX +qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo +f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA== +-----END RSA TESTING KEY-----Q_ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/0fcd827b57ded58e91f7ba2ac2b7ea4d25ebedca-7 b/tests/fuzzers/txfetcher/corpus/0fcd827b57ded58e91f7ba2ac2b7ea4d25ebedca-7 new file mode 100644 index 0000000..8ceee16 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/0fcd827b57ded58e91f7ba2ac2b7ea4d25ebedca-7 @@ -0,0 +1 @@ +ð½apï¿ïï��ï¿ï¿¿½½½¿¿½½��¿½ï¿ï¿½ï¿ïÓÌV½¿½ïïï¿ï¿½#ï¿ï¿½&�� \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/109bc9b8fd4fef63493e104c703c79bc4a5e8d34-6 b/tests/fuzzers/txfetcher/corpus/109bc9b8fd4fef63493e104c703c79bc4a5e8d34-6 new file mode 100644 index 0000000000000000000000000000000000000000..df9b986af1005f1e472eaee9d42135411f3cad63 GIT binary patch literal 470 zcmXBPNsgjW0D$4DEvGQ)r7@r&!=e@sdB{8nD6kU{kfE3!lXvU%k@^a4rw;Ic$zQfx zYUxCchXTqX8J;qOlCW-BNUrGREK=HOVq~%x}s(2NsgIr{eR1PWA?-k_w>v}L;@%v7V}lB?YOX7S+`=lZ(i)wVud5Y zY!6BZa8jojm77j=WDEG3q(kN$-N$G=S;6CB{dAPMzB8g9V5#V#Wt=p|pNhFKjy%yC zFpBAMMa)X+HjvSN7$b5hd}DlQ5^HbU{NQ1!jYbc0Xo)I!+*2KCFqw4WsZh_wU=>#O zr1r)kwlZ8qgkQCI1Blc6)Wg<8J2*FT4y<|`;v!3_^cGx#XJ`clF*MUzO#{$fuhoKN zAM^Nc(r*X02+fQKC8*rXv7+O1epiA}A{Chb{qy_R=Z6uCy2&Pra2Fp<%9)R5AkasN o5@|^cIBRywREomC%wfKLePO%ZjwD%%fQGPfz{Yi4`Mgp80htP#Qvd(} literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/163785ab002746452619f31e8dfcb4549e6f8b6e-6 b/tests/fuzzers/txfetcher/corpus/163785ab002746452619f31e8dfcb4549e6f8b6e-6 new file mode 100644 index 0000000000000000000000000000000000000000..55467373d46141a3786f3d38a439ab7fa65d9b1c GIT binary patch literal 30 kcmZShPPx# literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/1adfa6b9ddf5766220c8ff7ede2926ca241bb947-3 b/tests/fuzzers/txfetcher/corpus/1adfa6b9ddf5766220c8ff7ede2926ca241bb947-3 new file mode 100644 index 0000000..4a593aa --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/1adfa6b9ddf5766220c8ff7ede2926ca241bb947-3 @@ -0,0 +1,11 @@ +TAKBgDuLnQA3gey3VBznB39JUtxjeE6myuDkM/uGlfjb +S1w4iA5sBzzh8uxEbi4nW91IJm2gsvvZhICHS3l6ab4pZB +l2DulrKBxKKtD1rGxlG4LncabFn9vLZad2bSysqz/qTAUSTvqJQIDAQAB +AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Z4vMXc7jpTLryzTQIvVdfQbRc6+MUVeLKZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk54MogxEcfbWd6IOkp+4xqFLBEDtgbIECnk+hgN4H +qzzxxr397vWrjrIgbJpQvBv8QeeuNi8DoUBEmiSJwa7FXY +FUtxuvL7XvjwjN5B30pEbc6Iuyt7y4MQJBAIt21su4b3sjphy2tuUE9xblTu14qgHZ6+AiZovGKU--FfYAqVXVlxtIX +qyU3X9ps8ZfjLZ45l6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo +f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA== +-----END RSA T \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/1b9a02e9a48fea1d2fc3fb77946ada278e152079-4 b/tests/fuzzers/txfetcher/corpus/1b9a02e9a48fea1d2fc3fb77946ada278e152079-4 new file mode 100644 index 0000000000000000000000000000000000000000..4a56f93d3ba9dc043d85983f6938371424a18831 GIT binary patch literal 17 ZcmdmX;%zzm%{7eWMGPsad6Su6002*L2o3-M literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/1e14c7ea1faef92890988061b5abe96db7190f98-7 b/tests/fuzzers/txfetcher/corpus/1e14c7ea1faef92890988061b5abe96db7190f98-7 new file mode 100644 index 0000000..d2442fc --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/1e14c7ea1faef92890988061b5abe96db7190f98-7 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/1e7d05f00e99cbf3ff0ef1cd7ea8dd07ad6dff23-6 b/tests/fuzzers/txfetcher/corpus/1e7d05f00e99cbf3ff0ef1cd7ea8dd07ad6dff23-6 new file mode 100644 index 0000000000000000000000000000000000000000..1c342ff53a36366885c5a06494aef83308ed3126 GIT binary patch literal 19 acmZShus5-wASW|9G1$Y=m$7*Nxl#a8k_aaN literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/1ec95e347fd522e6385b5091aa81aa2485be4891-4 b/tests/fuzzers/txfetcher/corpus/1ec95e347fd522e6385b5091aa81aa2485be4891-4 new file mode 100644 index 0000000000000000000000000000000000000000..b0c776bd4d996c4b423b42a871caae498a5bb695 GIT binary patch literal 833 zcmXZb$+DtI0EJvY?uiaW5OC3pLs{H8WBf3>{n2Nad>~2{-XpBKtKy*z7 z+VO+jwx&2CUP!XIdknPs5+C#S?OhhhOc6DhAMLg0*+iY_+G)y&nbovF_e?O(ua@TL z53V#@R+WX6$WV_ED&vV!h4+holrDdwKI_IuW%nq6*C?^Dx@qfOx|0Ugox~tD;mehJ}a=??6)q2HCOwz$325 zp*wt=oTNRZ+sCr=sREM0R}W-Hl{c{bhjYd9bd6;`b{<-^D2sHoI(L^|N=6$OvJI<$ z7^OL`7;Jwyd}`UXMDK@NUHasXIoCPN!xUX=I%YJ9w*(qoqh~Bcc*}BqGwPPF6%qIZ zVNlJ)2&vEF%kO~;ABVP=;sV?8S(JY`Dv!VJqe@8iM`FS#KAp3iGgar|UeiEx&Qhi% zTHa8_;hZ=9)^AApR9+;5r#W-g$;Pm#OjBM1dVeNUKOMpUKXIt;*A@IJ=Id`R@Ha}< B56=Jq literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/1fbfa5d214060d2a0905846a589fd6f78d411451-4 b/tests/fuzzers/txfetcher/corpus/1fbfa5d214060d2a0905846a589fd6f78d411451-4 new file mode 100644 index 0000000000000000000000000000000000000000..75de835c98de4015fd4c9874b4a469e18a3b87d3 GIT binary patch literal 22 ecmdn?ww(Ru8piS>hLqI2$;>Zw^z=$MDF6U-W(gGl literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/1fd84ee194e791783a7f18f0a6deab8efe05fc04-2 b/tests/fuzzers/txfetcher/corpus/1fd84ee194e791783a7f18f0a6deab8efe05fc04-2 new file mode 100644 index 0000000..3b6d256 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/1fd84ee194e791783a7f18f0a6deab8efe05fc04-2 @@ -0,0 +1 @@ +¸& \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/21e76b9fca21d94d97f860c1c82f40697a83471b-8 b/tests/fuzzers/txfetcher/corpus/21e76b9fca21d94d97f860c1c82f40697a83471b-8 new file mode 100644 index 0000000..1d4620f --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/21e76b9fca21d94d97f860c1c82f40697a83471b-8 @@ -0,0 +1,3 @@ +DtQvfQ+MULKZTXk78c +/fWkpxlQQ/+hgNzVtx9vWgJsafG7b0dA4AFjwVbFLmQcj2PprIMmPNQrooX +L \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/220a87fed0c92474923054094eb7aff14289cf5e-4 b/tests/fuzzers/txfetcher/corpus/220a87fed0c92474923054094eb7aff14289cf5e-4 new file mode 100644 index 0000000000000000000000000000000000000000..175f74fd5aa8883bffb6d3d64727d2265f7bee35 GIT binary patch literal 4 LcmZQz*~^ literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/23ddcd66aa92fe3d78b7f5b6e7cddb1b55c5f5df-3 b/tests/fuzzers/txfetcher/corpus/23ddcd66aa92fe3d78b7f5b6e7cddb1b55c5f5df-3 new file mode 100644 index 0000000..95892c7 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/23ddcd66aa92fe3d78b7f5b6e7cddb1b55c5f5df-3 @@ -0,0 +1,12 @@ +4txjeVE6myuDqkM/uGlfjb9 +SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZeIrCHS3l6afab4pZB +l2+XsDlrKBxKKtD1rGxlG4jncdabFn9gvLZad2bSysqz/qTAUSTvqJQIDAQAB +AoGAGRzwwXvBOAy5tM/uV6e+Zf6aZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Z4vD6Mc7pLryzTQIVdfQbRc6+MUVeLKZaTXtdZru+Jk70PJJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+gN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQ2PprIMPcQroo8vpjSHg1Ev14KxmQeDydfsgeuN8UBESJwm7F +UtuL7Xvjw50pNEbc6Iuyty4QJA21su4sjXNueLQphy2U +fQtuUE9txblTu14qN7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6ARYiZPYj1oGUFfYAVVxtI +qyBnu3X9pfLZOAkEAlT4R5Yl6cJQYZHOde3JEhNRcVFMO8dJFo +f9Oeos0UUhgiDkQxdEwLjQf7lJJz5OtwC= +-NRSA TESINGKEY-Q_ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/2441d249faf9a859e38c49f6e305b394280c6ea5-1 b/tests/fuzzers/txfetcher/corpus/2441d249faf9a859e38c49f6e305b394280c6ea5-1 new file mode 100644 index 0000000000000000000000000000000000000000..d76207e992a6e50a53966fa06c881f7c9cab5eb4 GIT binary patch literal 55 zcmV~$fenBl3`IeCEThRMsijI7#|fQ)Fo#p<2;O~ZN#mV^=f=b?TvQR_5T|IO)NpG( K!syDY>)L*r_6#ck literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/2da1f0635e11283b1927974f418aadd8837ad31e-7 b/tests/fuzzers/txfetcher/corpus/2da1f0635e11283b1927974f418aadd8837ad31e-7 new file mode 100644 index 0000000000000000000000000000000000000000..73ae7057014ffcb8c913914888047969958df11c GIT binary patch literal 15 WcmZShus5-wAjgAo?|Zhr`%3{g@&`u% literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/2e1853fbf8efe40098b1583224fe3b5f335e7037-6 b/tests/fuzzers/txfetcher/corpus/2e1853fbf8efe40098b1583224fe3b5f335e7037-6 new file mode 100644 index 0000000000000000000000000000000000000000..692981e614155c59f5ba80f40e832734383d8901 GIT binary patch literal 26 icmZShz~Eha=sv^$|L^zjWze;-urM}dU|?{tumAv_9to%b literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/2f25490dc49c103d653843ed47324b310ee7105e-7 b/tests/fuzzers/txfetcher/corpus/2f25490dc49c103d653843ed47324b310ee7105e-7 new file mode 100644 index 0000000000000000000000000000000000000000..5cf7da75df2dc6c74b3353ec1054ae730ab2a4f6 GIT binary patch literal 23 fcmZShz~Eha=sv^$|L^zjWiVziWnf@%u&@9Cesl=a literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/30494b85bb60ad7f099fa49d427007a761620d8f-5 b/tests/fuzzers/txfetcher/corpus/30494b85bb60ad7f099fa49d427007a761620d8f-5 new file mode 100644 index 0000000..7ff9d39 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/30494b85bb60ad7f099fa49d427007a761620d8f-5 @@ -0,0 +1,10 @@ +jXbnWWF/wbZGOpet +3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp +jy4SHEg1AkEA/v13/5M47K9vCxb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xl/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6Yj013sovGKUFfYAqVXVlxtIX +qyUBnu3Xh9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dDaFeo +f9Oeos0UotgiDktdQHxdNEwLjQfl \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/316024ca3aaf09c1de5258733ff5fe3d799648d3-4 b/tests/fuzzers/txfetcher/corpus/316024ca3aaf09c1de5258733ff5fe3d799648d3-4 new file mode 100644 index 0000000..61f7d78 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/316024ca3aaf09c1de5258733ff5fe3d799648d3-4 @@ -0,0 +1,15 @@ +¸^áo‡È—----BEGIN RA TTING KEY----- +IIXgIBAAKBQDuLnQI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 +SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJmgsvvZhrCHSl6afab4pZB +l2+XsDulrKBxKKtD1rGxlG4LjcdabF9gvLZad2bSysqz/qTAUStTvqJQDAQAB +AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Z4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp +jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj043sovGKUFfYAqVXVlxtIX +qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo +f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA== +-----END RSA TESTING KEY-----Q_ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/32a089e2c439a91f4c1b67a13d52429bcded0dd9-7 b/tests/fuzzers/txfetcher/corpus/32a089e2c439a91f4c1b67a13d52429bcded0dd9-7 new file mode 100644 index 0000000000000000000000000000000000000000..a986a9d8e753a8e307ab9d904781ec872fa2eec0 GIT binary patch literal 1665 zcmeIzM@|DV7>3~lGKAg+QhHf%TnTO?MT0U^PHGqB?TNL+#c z#cNIglKp(YXFNsLXl$@(kIip7o$l-T$>8&HrI*x$7kGmKH1Gi<_<|q!LjVLq5ClUA zghCjELj*)Z6qq0yVjvdcARZFH42h5g7D$E^NQE>=hYZLBD`Y`7js-XsIp$_Vy0UDtRnxO^k&F2J5f^o3I7j;DH_3g+17Z12}{u zIEE8Ag)=yZ3%Jz3UQ7J@zXE@B1?20O){(7pq*Rl_RHH`5I+Y9?jY3qOmpbB7@$&~) zrP5(rkfPl!cw&pRD#;afM)}(f-XEf6f}dH?%sehr%@^(CpEq}szIMSBsdu{6#v!AR h`=_R1$ph)-(xt6h1&?%ISK8aH%b}_?r%Ji{`5V}g$;JQx literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/33ec1dc0bfeb93d16edee3c07125fec6ac1aa17d-2 b/tests/fuzzers/txfetcher/corpus/33ec1dc0bfeb93d16edee3c07125fec6ac1aa17d-2 new file mode 100644 index 0000000..d41771b --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/33ec1dc0bfeb93d16edee3c07125fec6ac1aa17d-2 @@ -0,0 +1 @@ +ï¿ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/37a0d207700b52caa005ec8aeb344dcb13150ed2-5 b/tests/fuzzers/txfetcher/corpus/37a0d207700b52caa005ec8aeb344dcb13150ed2-5 new file mode 100644 index 0000000000000000000000000000000000000000..2f09c6e28f03ccb6b2e40fe56b8d26768544aab6 GIT binary patch literal 55 zcmZShu=hRV-uL_WGO(qVWay`*mLzAS7U`E1CFT_;CYNO9=jkUEu2ZX Lm*?%>|GpFetm7Di literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/382f59c66d0ddb6747d3177263279789ca15c2db-5 b/tests/fuzzers/txfetcher/corpus/382f59c66d0ddb6747d3177263279789ca15c2db-5 new file mode 100644 index 0000000000000000000000000000000000000000..84441ac374622d3d0e6dcb6c31a11adb110de593 GIT binary patch literal 14 VcmZSCtH8S2`8Wdu!~VStr2rs*1dRXy literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/3a010483a4ad8d7215447ce27e0fac3791235c99-4 b/tests/fuzzers/txfetcher/corpus/3a010483a4ad8d7215447ce27e0fac3791235c99-4 new file mode 100644 index 0000000..28f5d99 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/3a010483a4ad8d7215447ce27e0fac3791235c99-4 @@ -0,0 +1,7 @@ + +lGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp +jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5 \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/3a3b717fcfe7ffb000b906e5a76f32248a576bf7-6 b/tests/fuzzers/txfetcher/corpus/3a3b717fcfe7ffb000b906e5a76f32248a576bf7-6 new file mode 100644 index 0000000000000000000000000000000000000000..022de3c61d4bfaa977cfd74616bc29c66c8a8ea3 GIT binary patch literal 1141 zcmc)IS5g&G5CBj|P|OiT6tjXNAZ8I2vx16ZCYuvR5HaiQhj0I;UKR7h7F>iiTd)tV z;gvPFPxnVp_tdTVHS=e(>i1MKHm4|M6%o@vlQ6+tRG<>`FdtP|fQ49u#aM!+Scc_T z5v)|K!fI5b25V4@wWz~7tVcaIU?VnRGqzwWwqZMVpaG58iCt*IZZx9>t=NM$?8QFp z#{nF~AsogLwBsl`a16(B0-ZRCQ#g$?IE!;Qj|;enE_CA(F5?P%a240ki|e?7o4AGB zxP!asLq7&Eh#?GP1ov)j7;^7kVj6m_hh9^u&+GEa^+4;>wl!_zth7@Gy09bpfvi z7cnX2@R_lyU9|*QJ5)>JHWpBo?ymdemqN0iiYN;UD2Hy}!*N}E=E}{_9`$PBK&^G> z+wjUC9HcS<2r^niC>w5yXs7K}vvJZMW2HC?6{oKanP04L9tSt6`O+3S%Gb%@B7`ms z)mkuR)oB|yV7e-Oo&YnTNLPrqq=Vd@f-9{3=?pr?;tIvPfy6B3coR4NuJ;z5It3}u zBNf#@NfF<7BvAhYVsO~#_AyLfO%aSo#8~EaotD^Xh=i1RptFSF#or0*#E3dG1D+px zAm_2rg;7|qqxu*Bi4I-xVWRTd4t$a$BeA)gP)S1V1Md>oZ2Bl)nj0(jo6*eD^3u?> cGZDY>5a6O3Gifo9tL^r<2)|FK(^2sKe=I1K@c;k- literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/3d8b5bf36c80d6f65802280039f85421f32b5055-6 b/tests/fuzzers/txfetcher/corpus/3d8b5bf36c80d6f65802280039f85421f32b5055-6 new file mode 100644 index 0000000000000000000000000000000000000000..eacd269f317b144dc5fdf3eb138007bbd6270192 GIT binary patch literal 23 ecmZShus5-wASW|9G1$Y=l(9H94mgL_|SBMWiSKDl$?WA&`JdB8U{Rp(r+p6h%Oy2ny<$9C-uY&Q4~C zSSU&nq$nJ40r3)e|9NuaHONe!@3-Gha{5E|h6G52J+K#&AQ|?-emDRra8LxTRF*@K2I-IinQ%BfKh>g7Xgk80 zEI10;kOR4JEW&Y?6OacdAs?8hpa2S?2#Vn}l)#yPwo*0+ltDQ-!37mi31{IPoQEod zn&pa-4;vk&^=84Q5=0qFG{Ucex{gdrG)SMVA}U=-fK zTX+Z2JMvyzD8Q*Lx2}Wv)`bFN{!k#KKB;lPFYsA@QD4v literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/608200b402488b3989ec8ec5f4190ccb537b8ea4-4 b/tests/fuzzers/txfetcher/corpus/608200b402488b3989ec8ec5f4190ccb537b8ea4-4 new file mode 100644 index 0000000000000000000000000000000000000000..d37b018515b8c82be6564a0e1f3e772bbefffd89 GIT binary patch literal 24 gcmdmXqMZHa8piS>hLqI2$;>Zw^z^o8mu^x30EF%e5&!@I literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/61e89c3fbdf9eff74bd250ea73cc2e61f8ca0d97-5 b/tests/fuzzers/txfetcher/corpus/61e89c3fbdf9eff74bd250ea73cc2e61f8ca0d97-5 new file mode 100644 index 0000000..155744b --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/61e89c3fbdf9eff74bd250ea73cc2e61f8ca0d97-5 @@ -0,0 +1 @@ +88242871'392752200424491531672177074144720616417147514758635765020556616¿ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/62817a48c78fbf2c12fcdc5ca58e2ca60c43543a-7 b/tests/fuzzers/txfetcher/corpus/62817a48c78fbf2c12fcdc5ca58e2ca60c43543a-7 new file mode 100644 index 0000000000000000000000000000000000000000..795608a789579add52324341abf22fac2b156d73 GIT binary patch literal 27 jcmZShus5;b|Ns9DiH1hHL5#(zDeoEgzGvIJ|6C~m!tV{; literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/6782da8f1a432a77306d60d2ac2470c35b98004f-3 b/tests/fuzzers/txfetcher/corpus/6782da8f1a432a77306d60d2ac2470c35b98004f-3 new file mode 100644 index 0000000..f44949e --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/6782da8f1a432a77306d60d2ac2470c35b98004f-3 @@ -0,0 +1 @@ +21888242871'392752200424452601091531672177074144720616417147514758635765020556616¿½ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/68fb55290cb9d6da5b259017c34bcecf96c944aa-5 b/tests/fuzzers/txfetcher/corpus/68fb55290cb9d6da5b259017c34bcecf96c944aa-5 new file mode 100644 index 0000000000000000000000000000000000000000..23d905b827e2a5c4a04388da93af9bb5dafca84d GIT binary patch literal 49 zcmeZ>axzIaN)AZQ$xO{JOD!sJFAmQ#_G9pk42aTqvnY9J8kyr4WRc?HmY?a85*U~R E0HKW##sB~S literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/6a5059bc86872526241d21ab5dae9f0afd3b9ae1-3 b/tests/fuzzers/txfetcher/corpus/6a5059bc86872526241d21ab5dae9f0afd3b9ae1-3 new file mode 100644 index 0000000..b71d5df --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/6a5059bc86872526241d21ab5dae9f0afd3b9ae1-3 @@ -0,0 +1 @@ +¿½ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/717928e0e2d478c680c6409b173552ca98469ba5-6 b/tests/fuzzers/txfetcher/corpus/717928e0e2d478c680c6409b173552ca98469ba5-6 new file mode 100644 index 0000000..dce5106 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/717928e0e2d478c680c6409b173552ca98469ba5-6 @@ -0,0 +1 @@ +LvhaJcdaFofenogkjQfJB \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/71d22f25419543e437f249ca437823b87ac926b1-6 b/tests/fuzzers/txfetcher/corpus/71d22f25419543e437f249ca437823b87ac926b1-6 new file mode 100644 index 0000000000000000000000000000000000000000..d07a6c2f3244799a3c836c749bfc3fa614936172 GIT binary patch literal 27 jcmZShus5-wAV)X3?EU_|zKq4GDeoEgzGvIJ|6C~mw;2t$ literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/7312a0f31ae5d773ed4fd74abc7521eb14754683-8 b/tests/fuzzers/txfetcher/corpus/7312a0f31ae5d773ed4fd74abc7521eb14754683-8 new file mode 100644 index 0000000..3593ce2 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/7312a0f31ae5d773ed4fd74abc7521eb14754683-8 @@ -0,0 +1,2 @@ +DtQvfQ+MULKZTXk78c +/fWkpxlyEQQ/+hgNzVtx9vWgJsafG7b0dA4AFjwVbFLmQcj2PprIMmPNQg1AkS5TDEmSJwFVlXsjLZ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/76e413a50dc8861e3756e556f796f1737bec2675-4 b/tests/fuzzers/txfetcher/corpus/76e413a50dc8861e3756e556f796f1737bec2675-4 new file mode 100644 index 0000000000000000000000000000000000000000..623fcf9601e516398a6bce4104435dc4556d99a2 GIT binary patch literal 24 gcmdmX;w}5lHH_s&3@NF3lbK)W=;>|GF5RR60FM$1hyVZp literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/78480977d5c07386b06e9b37f5c82f5ed86c2f09-3 b/tests/fuzzers/txfetcher/corpus/78480977d5c07386b06e9b37f5c82f5ed86c2f09-3 new file mode 100644 index 0000000..e92863a --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/78480977d5c07386b06e9b37f5c82f5ed86c2f09-3 @@ -0,0 +1,14 @@ + TESTING KEY----- +MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 +SjY1bIw4iAJm2gsvvZhIrCHS3l6afab4pZB +l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB +AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp +jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xl/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX +qyUBnu3Xh9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dDaFeo +f9Oeos0UotgiDktdQHxdNEwLjQflJJBzV+5OtwswCA=----EN RATESTI EY-----Q \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/7a113cd3c178934cdb64353af86d51462d7080a4-5 b/tests/fuzzers/txfetcher/corpus/7a113cd3c178934cdb64353af86d51462d7080a4-5 new file mode 100644 index 0000000..1681812 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/7a113cd3c178934cdb64353af86d51462d7080a4-5 @@ -0,0 +1,10 @@ +l6afab4pZB +l2+XsDlrKBxKKtDrGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTtqJQIDAQAB +AoGAGRzwwir7XvBOAy5tuV6ef6anZzus1s1Y1Clb6HbnWWF/wbZGOpet +3m4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKZTXtdZrh+k7hx0nTP8Jcb +uqFk541awmMogY/EfbWd6IOkp+4xqjlFBEDytgbIECQQDvH/6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz84SHEg1Ak/7KCxmD/sfgS5TeuNi8DoUBEmiSJwm7FX +ftxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su43sjXNueLKH8+ph2UfQuU9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xl/DoCzjA0CQQDU +y2pGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj13sovGKUFfYAqVXVlxtIáo‡X +qUn3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JMhNRcVFMO8dDaFo +f9Oeos0UotgiDktdQHxdNEwLjQlJBz+OtwwA=---E ATTIEY- \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/7ea9f71020f3eb783f743f744eba8d8ca4b2582f-3 b/tests/fuzzers/txfetcher/corpus/7ea9f71020f3eb783f743f744eba8d8ca4b2582f-3 new file mode 100644 index 0000000..08f5bb9 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/7ea9f71020f3eb783f743f744eba8d8ca4b2582f-3 @@ -0,0 +1,9 @@ + +l2+DulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB +AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet +3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp +jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU diff --git a/tests/fuzzers/txfetcher/corpus/84f8c275f3ffbaf8c32c21782af13de10e7de28b-3 b/tests/fuzzers/txfetcher/corpus/84f8c275f3ffbaf8c32c21782af13de10e7de28b-3 new file mode 100644 index 0000000..2d6060c --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/84f8c275f3ffbaf8c32c21782af13de10e7de28b-3 @@ -0,0 +1 @@ +KKtDlbjVeLKwZatTXtdZrhu+Jk7hx0xxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLQcmPcQETT YQ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/85dfe7ddee0e52aa19115c0ebb9ed28a14e488c6-5 b/tests/fuzzers/txfetcher/corpus/85dfe7ddee0e52aa19115c0ebb9ed28a14e488c6-5 new file mode 100644 index 0000000000000000000000000000000000000000..9b6fe78029e7d3b85115abd651f2bbbff34bbca8 GIT binary patch literal 11 TcmZSh@Sbt+`~746aLQ)#y_k0wLP=ZpFp&S*+VI@{!HP)aKYf*)2 ztP8An*no|w!6t0R7Hq{fY)372pbk4xj|S|*ZtTHc?8AN>KqC&K35ReP&1k_99K|sl z#|fOoDV#T*eh##Wh^V4cx>n+(s|@a0mUki+i|_ z2Y84_7{DNgFpLq5VhoS*1W)k{&+!5;@d~f;25<2W?=g-Ee85LcVhYpvgwObbJig)^ zX7C-e_<^68i_$ox;+Uj`&^t|*N5o-@Nm7`%6iF6RmPBzDg%xrkRuWDjma4NtoTQ;D VPO=bj7Pc!8L%xIiR8uYqk6qWf{iHWl(kl6J{wndw<-Ac6{ii@NU(Cfa={Fu^7* zx5~U7WoYoN>G=4rzJ__}!e_>lb`TcUmZW}!^JJyuKV*IPCxbV1}L-4ze?foTaeL+vV(%Lwzs-QMz~ulq=q0<1aKfIzn}OCL)*E zwHhDtagof)VX{X>sq2AHQS+OUd+CF~Npf8|AP#20+Prve72t2gI`(y6w)oMK38h{* zlPAG5ekJ@PMkkt``&BaowT!_SMq#sKJtD><5W9tq>iLqC$V%>l2;K^4eC~OU8$Q$O zIJw>Pf;AMo&O+6`U-DEfdZN7Ei+(-@J+5Bz8|UtA&mCp6kXtr}A$~&ge8=0oN#MU9 Qxr+1k1is?=`qz);e`P)HIRF3v literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/8ff3bd49f93079e5e1c7f8f2461ba7ee612900c3-5 b/tests/fuzzers/txfetcher/corpus/8ff3bd49f93079e5e1c7f8f2461ba7ee612900c3-5 new file mode 100644 index 0000000000000000000000000000000000000000..a86a66593b4647dd930c1f2c9f07b668e0f6ee50 GIT binary patch literal 40 wcmZShus5-wASW|9G1$Y=)X+#bDA-Xqx$OP^y}pdasVVOn_r7P_yZ>A%07&f-YXATM literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/9034aaf45143996a2b14465c352ab0c6fa26b221-2 b/tests/fuzzers/txfetcher/corpus/9034aaf45143996a2b14465c352ab0c6fa26b221-2 new file mode 100644 index 0000000..9c95a6b --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/9034aaf45143996a2b14465c352ab0c6fa26b221-2 @@ -0,0 +1 @@ +½ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/92cefdc6251d04896349a464b29be03d6bb04c3d-2 b/tests/fuzzers/txfetcher/corpus/92cefdc6251d04896349a464b29be03d6bb04c3d-2 new file mode 100644 index 0000000..9b78e45 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/92cefdc6251d04896349a464b29be03d6bb04c3d-2 @@ -0,0 +1 @@ +ï39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319¿½ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/9613e580ccb69df7c9074f0e2f6886ac6b34ca55-5 b/tests/fuzzers/txfetcher/corpus/9613e580ccb69df7c9074f0e2f6886ac6b34ca55-5 new file mode 100644 index 0000000000000000000000000000000000000000..681adc6a9cd91ac14946e3aa426206220289f31b GIT binary patch literal 446 zcmXBPOOm26007YGEvvahFIj*R0hd7%0U>~jf;&M$5r$s^!Mn}f#;Lx*t9q`1Oj@Ki z+7tv>LdUtXE_=I3opv1cOkhs80*fn}YOg60iK8q}&o|ZfU#$puS60~l2=fmShUREV z>f?uczfY9&URi77x&LnlA2b||;hJx?RK7s~aXFn*w+^#1XnWhMgDmWu=BEgs9MBr8L1 z2}(1S%~YF#7ebpZ29_#A854GQEj<+5$1)P~kg^D^DNm~-TYyd>T4C)CaPb~Enax?> z?0T}yfqv$XQQMx+#g#e|{lH zMKOTPW!FgViBwLj=q}ecwxu-XnYBtgkQd+x-I*ha*f2hI~qI;_h1*kx0-P>Kt^Mwrk=>C!>J)7i) K4MF?hIPM>cYfrWS literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/a2684adccf16e036b051c12f283734fa803746e8-6 b/tests/fuzzers/txfetcher/corpus/a2684adccf16e036b051c12f283734fa803746e8-6 new file mode 100644 index 0000000000000000000000000000000000000000..4e12af2da8e9fc8fbb4f49a1b09323698fdaed26 GIT binary patch literal 25 hcmZShz~HTAUV7+0!`{6N3=S3+#-^6$7W?0q0swI!2@n7P literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/a37305974cf477ecfe65fa92f37b1f51dea25910-4 b/tests/fuzzers/txfetcher/corpus/a37305974cf477ecfe65fa92f37b1f51dea25910-4 new file mode 100644 index 0000000000000000000000000000000000000000..75cb14e8d98ea165366cc682e0b9b72d0510bd70 GIT binary patch literal 15 XcmZShu=hRV-uL_WGO+F4|GpFeM3o4v literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/a7eb43926bd14b1f62a66a33107776e487434d32-7 b/tests/fuzzers/txfetcher/corpus/a7eb43926bd14b1f62a66a33107776e487434d32-7 new file mode 100644 index 0000000000000000000000000000000000000000..88e6127355dd8492243d27d231165c9a7694c32f GIT binary patch literal 516 zcmWmBHBtlt6a-KPTio5<-QC^YT`oYfDF-4Zo7yTOuD~hw3d{@s>+bk}|Hfl~UqSpM zC7B4*qWk#_l3{9KCBk%>flS1Zg>2*?7kS7>0SZwRQLL1p6lEw!1u9X6YSf?>b*M)J z8qtJiw4fDjXh#P+(S>gGpcj4U#{dQ~gkg+e6k{021STAGX#{mv;gkzlG6lXZc1uk)gYuw-#ceuv`9`S@{yaeHE^y4@EJsAdx Ee|Mk`cK`qY literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/a8f7c254eb64a40fd2a77b79979c7bbdac6a760c-4 b/tests/fuzzers/txfetcher/corpus/a8f7c254eb64a40fd2a77b79979c7bbdac6a760c-4 new file mode 100644 index 0000000..da61777 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/a8f7c254eb64a40fd2a77b79979c7bbdac6a760c-4 @@ -0,0 +1,2 @@ +lxtIX +qyU3X9ps8ZfjLZ45l6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFe \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/a9a8f287d6af24e47d8db468e8f967aa44fb5a1f-7 b/tests/fuzzers/txfetcher/corpus/a9a8f287d6af24e47d8db468e8f967aa44fb5a1f-7 new file mode 100644 index 0000000000000000000000000000000000000000..7811921b79e98dab88b68f52779d560f6ef77ced GIT binary patch literal 106 zcmZQkU|sE8oX8UPv~4=`)3zndPj>eJ=`}#SmHBBq5O*{)Kb|}vNbhET+Svxg+nFCv znacdMyB&x-nVC5l7+At{Qxy`674q`)bc<5ca#E8^6x7P}(lS$XQjRl(rymq!V2A<$ D%DO9) literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/aa7444d8e326158046862590a0db993c07aef372-7 b/tests/fuzzers/txfetcher/corpus/aa7444d8e326158046862590a0db993c07aef372-7 new file mode 100644 index 0000000..870e12f --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/aa7444d8e326158046862590a0db993c07aef372-7 @@ -0,0 +1 @@ +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000@0000000000000 \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/ae4593626d8796e079a358c2395a4f6c9ddd6a44-6 b/tests/fuzzers/txfetcher/corpus/ae4593626d8796e079a358c2395a4f6c9ddd6a44-6 new file mode 100644 index 0000000..845deed --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/ae4593626d8796e079a358c2395a4f6c9ddd6a44-6 @@ -0,0 +1,8 @@ +9pmM gY/xEcfbWd6IOkp+4xqjlFLBEDytgbparsing /E6nk+hgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprLANGcQrooz8vp +jy4SHEg1AkEA/v13/@M47K9vCxb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY +fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xl/DoCz� jA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6Yj013sovGKUFfYAqVXVlxtIX +qyUBnu3Xh9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYFZHOde3JEMhNRcVFMO8dDaFeo +f9Oeos0Uot \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/b2942d4413a66939cda7db93020dee79eb17788c-9 b/tests/fuzzers/txfetcher/corpus/b2942d4413a66939cda7db93020dee79eb17788c-9 new file mode 100644 index 0000000000000000000000000000000000000000..10aca6512180bfd927400162598662c4589b3c52 GIT binary patch literal 18 ZcmZQ**qfYaXrvp&Se%;5$+-7D8vr(g1>OJv literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/b4614117cdfd147d38f4e8a4d85f5a2bb99a6a4f-5 b/tests/fuzzers/txfetcher/corpus/b4614117cdfd147d38f4e8a4d85f5a2bb99a6a4f-5 new file mode 100644 index 0000000000000000000000000000000000000000..af69eef9b08634dc5bf91be2dc262c5e7a52f334 GIT binary patch literal 838 zcmXZbNv^9#0EJ;^7)Duw=jaJzFxZ1gDNJKp8yhe%qv-51Jw-dp#-g{Rw}spr<*eZ7 zm%gndJsj0iwz?Gj_#R=Z>embv=IO$C!uqU)y}_I`*JbgHQcvQ_ z&EK2N$D{_O$Phdc4;0W)o!B?f%6;CCWB5|L^X0)5T$DwSZ9;)4_N3p7L(%ENtm{~$ zJ7q2u)<9L9L{P3y`DlbuvWJhQZ&#{yfcwDC*lM;a0viYfs1=~x*J*lpygv$8upIE$ z1&>QNo6R3l6|O^1_jEv?3y7e^U3^$QpE5PUP`NfKCYZ2fN0BrAm`9Ccbj#}4>gXkz z4ev2Wny!}``V>ak@o^G%uYuG){A=D-&TTnrF(d|~l|KiTj--h!&21h~vlM6WnQ-d$ z(~#`kqCz$VNnCIao2cvGRlcPpuKJL#++r_8yRIId=Z-n?{+A-SSn$7^065Cx}ug9?=(84}0Qci=91gMc_sJ~2>*H7blhDSRUsiabvbVNF{TfQt8Y zKd_MLk#7xOO)kfSO*XG(?-H4f2Uj}b85Yh!vn^xs`Q;u8Y-qmZU=f$&S?{cUe9I|$ zT=32>exto2-7rv7e4tqZY1s5x?NV5Yuyj+u% z(IoR_-D6KSug64;1O8om=DlEq-ufU*^P;CIi`7p=rYEk%udu`sob*{1zfv N+dKG=P5r+a`(NZO69fPN literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/b631ef3291fa405cd6517d11f4d1b9b6d02912d4-2 b/tests/fuzzers/txfetcher/corpus/b631ef3291fa405cd6517d11f4d1b9b6d02912d4-2 new file mode 100644 index 0000000..a6b8858 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/b631ef3291fa405cd6517d11f4d1b9b6d02912d4-2 @@ -0,0 +1 @@ +&áo‡ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/b7a91e338cc11f50ebdb2c414610efc4d5be3137-4 b/tests/fuzzers/txfetcher/corpus/b7a91e338cc11f50ebdb2c414610efc4d5be3137-4 new file mode 100644 index 0000000000000000000000000000000000000000..9709a1fcb82b88b52db14626b9d8855646ebbd96 GIT binary patch literal 34 ncmV~$!2tju2m(O2@f(O}`wv!kyA*ejbvZ;A9Pth;45V^@e8~qu literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/b858cb282617fb0956d960215c8e84d1ccf909c6-2 b/tests/fuzzers/txfetcher/corpus/b858cb282617fb0956d960215c8e84d1ccf909c6-2 new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/b858cb282617fb0956d960215c8e84d1ccf909c6-2 @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/bc9d570aacf3acd39600feda8e72a293a4667da4-1 b/tests/fuzzers/txfetcher/corpus/bc9d570aacf3acd39600feda8e72a293a4667da4-1 new file mode 100644 index 0000000..aab27c5 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/bc9d570aacf3acd39600feda8e72a293a4667da4-1 @@ -0,0 +1 @@ +� \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/be7eed35b245b5d5d2adcdb4c67f07794eb86b24-3 b/tests/fuzzers/txfetcher/corpus/be7eed35b245b5d5d2adcdb4c67f07794eb86b24-3 new file mode 100644 index 0000000..47c996d --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/be7eed35b245b5d5d2adcdb4c67f07794eb86b24-3 @@ -0,0 +1,2 @@ +4LZmbRc6+MUVeLKXtdZr+Jk7hhgN4H +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLQcmPcQ SN_ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/c010b0cd70c7edbc5bd332fc9e2e91c6a1cbcdc4-5 b/tests/fuzzers/txfetcher/corpus/c010b0cd70c7edbc5bd332fc9e2e91c6a1cbcdc4-5 new file mode 100644 index 0000000..474f14d --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/c010b0cd70c7edbc5bd332fc9e2e91c6a1cbcdc4-5 @@ -0,0 +1,4 @@ + +Xc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nhgN4H +qzzVtxx7vWrjrIgPbJpvfb \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/c1690698607eb0f4c4244e9f9629968be4beb6bc-8 b/tests/fuzzers/txfetcher/corpus/c1690698607eb0f4c4244e9f9629968be4beb6bc-8 new file mode 100644 index 0000000..d184a2d --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/c1690698607eb0f4c4244e9f9629968be4beb6bc-8 @@ -0,0 +1,2 @@ +&Ƚ�� � +� � � ���ï¿���ÿÿÿ����������� �!�"�#�$�%�&�'�(�)�*�+�,�-�.�/¿½0 \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/c1f435e4f53a9a17578d9e8c4789860f962a1379-6 b/tests/fuzzers/txfetcher/corpus/c1f435e4f53a9a17578d9e8c4789860f962a1379-6 new file mode 100644 index 0000000000000000000000000000000000000000..f2a68ec3de94cc9015080f2c057f1a707aff0269 GIT binary patch literal 1889 zcmeIz>sCx*9LMn)iAW_1MM#BCqMDk?p&1=?PDfE_bRfbcA!*gT>IU4-tk!BG6iG^o zyyybPCG>mx?z^b9_V@o;`+2q3vuB=d`ycT7LX&|oH8pP6w41TZj4(5Hn-Olt9y9iu z5n;wYGxjT8k#GQ_AR1!eAjHBUI1F(R4@clA9D@W%gyV1ml0u7ft%0ovImyZ?I1R~g z22vmu(%>wdgY%FM8ITECkPR0g2QI=T$b~$}2L}{DAvnPWMQ|Cez*V>g#oz`HT!#`U zg)%6I8&Cn2PzBXc1GP{G^>7m!pb?s&8CswfZb2K|hC9#>9dH-!!F}iiFFb%Q=!PEX zg@^D6`rt7bqJ{Kh&c7sg~5T`lVLXZ?&q{)F1U% zt*d`(d+-0o`-y7n@QP12=SZ>792M3l#FLQB_Ly&*b@rEAXMx*hv-$e_y1Y#l9*2Ff Xr#lqLF35Lj)$MfVIb4O#$__5uJO4h75r literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/c4cdbb891f3ee76476b7375d5ed51691fed95421-10 b/tests/fuzzers/txfetcher/corpus/c4cdbb891f3ee76476b7375d5ed51691fed95421-10 new file mode 100644 index 0000000000000000000000000000000000000000..e365cc52623e848cc2ab0c87573b6653e95477e4 GIT binary patch literal 16 XcmZQ**lTE{8^lQPAnG-^4J!*)#!PJ7f|ad}aSP5n z|K1JAnRA}^tiSo5-oO2tj|bWBm404d)ZztiWuXizP(v7mLj-6b5~3g)v=9Ta5C`#) z0Ev(U$)JN2NQE@eLpo$YCS*Z2VFX5D48~ysCSeMu zVFqSl4(4G27GVjNVFgxU4c1`;oZx~@*n(}?fn9LJ9_)h$4&V@uzzfH40zNneKb*li zTtEOW;R>$dMt-{!2^pHeKb?TsesQfzbW%Y1*i1?lwIWu%-6*1x{yq+0@KbWSJooA^ Mv&Hi4_B|SY0k_#*-v9sr literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/cd1d73b4e101bc7b979e3f6f135cb12d4594d348-5 b/tests/fuzzers/txfetcher/corpus/cd1d73b4e101bc7b979e3f6f135cb12d4594d348-5 new file mode 100644 index 0000000..3079de5 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/cd1d73b4e101bc7b979e3f6f135cb12d4594d348-5 @@ -0,0 +1 @@ +822452601031714757585602556 \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/d0acdc8fca32bbd58d368eeac3bd9eaa46f59d27-5 b/tests/fuzzers/txfetcher/corpus/d0acdc8fca32bbd58d368eeac3bd9eaa46f59d27-5 new file mode 100644 index 0000000000000000000000000000000000000000..794d5d86c6a1e18b942a16765778be42f4f583fb GIT binary patch literal 9 QcmZShu$O^t@Ba6t02EdPlmGw# literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/d0e43b715fd00953f7bdd6dfad95811985e81396-4 b/tests/fuzzers/txfetcher/corpus/d0e43b715fd00953f7bdd6dfad95811985e81396-4 new file mode 100644 index 0000000000000000000000000000000000000000..742db5fb3ba97f3f55edce2971d6eabd209d96ef GIT binary patch literal 10 PcmZSC`+h$NF_Z!TB1r}f literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/d925fbd22c8bc0de34d6a9d1258ce3d2928d0927-8 b/tests/fuzzers/txfetcher/corpus/d925fbd22c8bc0de34d6a9d1258ce3d2928d0927-8 new file mode 100644 index 0000000000000000000000000000000000000000..5920dfe6012899e440c8de696128fefd9265fe4e GIT binary patch literal 22 ccmZSS*vmFwS0BO(&rvLx| literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/d9ba78cb7425724185d5fa300cd5c03aec2683bb-7 b/tests/fuzzers/txfetcher/corpus/d9ba78cb7425724185d5fa300cd5c03aec2683bb-7 new file mode 100644 index 0000000000000000000000000000000000000000..c4df1cf210ebbfadf9ae2b676c3950c61f099bd7 GIT binary patch literal 510 zcmWO2OM+uS007WlTg?%AZy`P*c2nXLf<%H~rw9r0uR^(8A8D>I{f_WtJ4thnOvA={bOtbP+p@ezDVmiFAbaF6I24~>UtfRHHrepRPHfB`Kd^$Dc#2^A zc<9Um6XVi95ydBsFsq^yWm47q_DDA0Gv_cbJb24Q+6ls^eO{6j`4P&K@r7xtmR{?w z^{?b$Wst)5px1kWO|^w?obNJbRxK;wBNxp}-P6K4R3F_|G?kzd_USmi+wz;y#I~kO zS2$6_wuNY2M_!iZ$5@SKd4*Q*&B~M(h6Hyx5P)7SA=K^0%PQ{Lk2Y|P%p1FIJIp;* z5#fH)N*2@_U}lzQ{yKtmmgnON58hK{(uw57RNnXg6T9z&+69W%yzkc1q>>)RRG|lG z#k2kUS1pyhSNL7;YA_Q`Oprtb$Fme*B95J~0QsqkT>M*dI_y2Ve-SRA3J>8xlhF!4 zK4yi1cct=lOB6j;$&t$#eU2ElJO2CG9Rsd6a2Y&!8OIHC7U|nN-tkix6d;Vk4>F0; Q@{!z6hbp;93aG*7KfceZ2LJ#7 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709 b/tests/fuzzers/txfetcher/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709 new file mode 100644 index 0000000..e69de29 diff --git a/tests/fuzzers/txfetcher/corpus/dcdb7758b87648b5d766b1b341a65834420cf621-7 b/tests/fuzzers/txfetcher/corpus/dcdb7758b87648b5d766b1b341a65834420cf621-7 new file mode 100644 index 0000000000000000000000000000000000000000..78cf11ae2170686d1cd83f0d1b68c1a83552f9cb GIT binary patch literal 22 ecmZShz~Eha=sv^$|L^zjWiU2nU|?{tumAvdh6vLD literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/dd441bd24581332c9ce19e008260a69287aa3cbc-6 b/tests/fuzzers/txfetcher/corpus/dd441bd24581332c9ce19e008260a69287aa3cbc-6 new file mode 100644 index 0000000..4e0c140 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/dd441bd24581332c9ce19e008260a69287aa3cbc-6 @@ -0,0 +1,2 @@ +Dtf1nWk78c +/fWklyEQQ/+hgNzVtxxmDgS5TDETgeXVlXsjLZ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/def879fe0fd637a745c00c8f1da340518db8688c-2 b/tests/fuzzers/txfetcher/corpus/def879fe0fd637a745c00c8f1da340518db8688c-2 new file mode 100644 index 0000000..555752f --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/def879fe0fd637a745c00c8f1da340518db8688c-2 @@ -0,0 +1 @@ +ù ´ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/df6c30a9781b93bd6d2f5e97e5592d5945210003-7 b/tests/fuzzers/txfetcher/corpus/df6c30a9781b93bd6d2f5e97e5592d5945210003-7 new file mode 100644 index 0000000000000000000000000000000000000000..2a7adb093bcf95dd675fd92635bfc1c3c9f4f75f GIT binary patch literal 1881 zcmeIz>sCy07{~D$Nks@HDLOc|5~-$UoSG4dP>O^cMx}ENDHKcdsvB@Sd(B!6IdxJx z=)j9EV0Q`q{_~x?&|33-KKt2g=FQ$SdmQVQKVXNf7&SWV)O4D$#f%s;wwe)Z#x^tJ z%!oH5!Hn%nuN{yGJ7E{>h9uYndto0WLkjE{AuE;T0Hnb|NQVqKWY3P*>l4}zb0!nA zARBVv2po-YjO93-fRm65%se;+`EVM}KmnYELMVb_a6t)_f*U+=4$i{`xCob^%wTz0 zd~g}c;R;-Z3b+Q9a2;+y72Jeda2u+j2JS#D)WKc22la42az`3i9>7C*1dWk*JZ5+;%FX0vR!E5M;0eAy%;T;UZ5WI&E z5WOQGwS|M6+H~_em~UM;I652-hSg{FMUAPi>YMtmeyE>nTurD+HKnH2FEyiP)o(SY z=GB5)RDaZxT2?D+RsB`})c<|`*FKF@MIT%#x#lra7UuJ$H>U8T?E>}zSZt^AT=kIs7C?jo0`)E%<>Hvsh1Ntyrv literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/dfc1c3a2e3ccdaf6f88c515fd00e8ad08421e431-6 b/tests/fuzzers/txfetcher/corpus/dfc1c3a2e3ccdaf6f88c515fd00e8ad08421e431-6 new file mode 100644 index 0000000000000000000000000000000000000000..59f3442c053c1cbb924c8c024d5eb054e4a74e3c GIT binary patch literal 10 RcmZQkU|sEeoPmL1KL89{0-gW> literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/e1dcc4e7ead6dfd1139ece7bf57d776cb9dac72d-7 b/tests/fuzzers/txfetcher/corpus/e1dcc4e7ead6dfd1139ece7bf57d776cb9dac72d-7 new file mode 100644 index 0000000000000000000000000000000000000000..5ba489f99ddd2944bc6ad575d0f6072858f7e209 GIT binary patch literal 20 ccmZShus5+lH;Az~HRV0y-uG;K_n#{T09*nIoB#j- literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/e39c2de2c8937d2cbd4339b13d6a0ce94d94f8d2-8 b/tests/fuzzers/txfetcher/corpus/e39c2de2c8937d2cbd4339b13d6a0ce94d94f8d2-8 new file mode 100644 index 0000000000000000000000000000000000000000..0e9508938e4ffbe154a5365977bf7aa023fa99c9 GIT binary patch literal 1275 zcmeIwNlF7z6ouhDNu_kI5eESWf+9Fj5J5yx2nl5y`)KTqCb2D>*kC(x;tHG!E~5zO zz%@8?0YYuT^Ybt>S76}Y@7(vQrKo6YH1>9H!b+fuDq|*aQ!G3-$4oe`n3${t850Im z>`kj@7-nG(<{=Azh5=lX*1jFXowHkl{;vY<&Z0yy6WtX zXCo4^SvA#@*F2IDeoMF0uPjy%YWV90pRdh2+K>=Vg2%P1Z6$NxkIx$GlOo<|(q82E hv0pF2@Pc;B>OQrq)FFH|udDia@=|tcOWV}dl^?;h0WSam literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/e72f76b9579c792e545d02fe405d9186f0d6c39b-6 b/tests/fuzzers/txfetcher/corpus/e72f76b9579c792e545d02fe405d9186f0d6c39b-6 new file mode 100644 index 0000000000000000000000000000000000000000..c4d34b1732a239d78e8e6f9e7700ed4b90db1110 GIT binary patch literal 47 zcmZShu=hRV-uL_WGO(qVWay`*mLzASCYNO9=jkUEu2ZXm*?%>|GpFe D|5z1S literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/eb70814d6355a4498b8f301ba8dbc34f895a9947-5 b/tests/fuzzers/txfetcher/corpus/eb70814d6355a4498b8f301ba8dbc34f895a9947-5 new file mode 100644 index 0000000000000000000000000000000000000000..bd57a22fb1e19cda19fcc5ebd923af9d758e9ed0 GIT binary patch literal 57 zcmd0(C`)zG_j2?!GE7SK@h-?P;z|oFDGhbCEU8G!2`Mc!_A^iS2#RttiSl)etaPc! MGS@a`WME(b03AaSMF0Q* literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/ebdc17efe343e412634dca57cecd5a0e1ce1c1c7-5 b/tests/fuzzers/txfetcher/corpus/ebdc17efe343e412634dca57cecd5a0e1ce1c1c7-5 new file mode 100644 index 0000000000000000000000000000000000000000..aaa3f695ab36ddb550af56dbb03615218a420336 GIT binary patch literal 54 zcmd0(C`)zG_j2?!GE7SK@h-?P;z|oFDGhbCEU8G!2`Mc!_A^iS2#RttiSl)etaPc! JGS_Be008Lz5fK0Y literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/ec0a25eba8966b8f628d821b3cfbdf2dfd4bbb4c-3 b/tests/fuzzers/txfetcher/corpus/ec0a25eba8966b8f628d821b3cfbdf2dfd4bbb4c-3 new file mode 100644 index 0000000..65cf0df --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/ec0a25eba8966b8f628d821b3cfbdf2dfd4bbb4c-3 @@ -0,0 +1,13 @@ +¸&^£áo‡È—-----BEGIN RSA TESTING KEY----- +MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9 +SjY1bIw4iA5sBBZzHi3z0h1YV8PuxEbi4nW91IJm2gsvvZhIrHS3l6afab4pZB +l2+XsDulrKBxKKtD1rGxlG4Ljncdabn9vLZad2bSysqz/qTAUStvqJQIDAQAB +AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1K1ClbjbE6HXbnWWF/wbZGOpet +3Zm4vD6MXc7jpTLryzQIvVdfQbRc6+MUVeLKwZatTXtZru+Jk7hx0nTPy8Jcb +uJqFk541aEw+mMogY/xEcfbW6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hg4 +qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLcj2pIMPQroozvjg1AkEA/v13/5M47K9vCxmb8QeD/aydfsgS5TeuNi8DoUBEmiSJwmaXY +fFUtxv7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4bjeLKH8Q+ph2 +fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+AYiZ6PYj013sovGKFYqVXVlxtIX +qyUBnu3X9s8ZfjZO7BAkl4R5Yl6cGhaJQYZHOe3JEMhVFaFf9Oes0UUothgiDktdQxdNLj7+5CWA== +-----END RSASQ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/eebe3b76aeba6deed965d17d2b024f7eae1a43f1-5 b/tests/fuzzers/txfetcher/corpus/eebe3b76aeba6deed965d17d2b024f7eae1a43f1-5 new file mode 100644 index 0000000000000000000000000000000000000000..20d62e15b32dce93e2c88cc2c140831d5f22e7b0 GIT binary patch literal 10 Scmeyc_dVm@_xtxUumJ!i;04S8 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/ef8741a9faf030794d98ff113f556c68a24719a5-6 b/tests/fuzzers/txfetcher/corpus/ef8741a9faf030794d98ff113f556c68a24719a5-6 new file mode 100644 index 0000000000000000000000000000000000000000..09fcd86d77c210e105c543a4eefdbd18e2b25612 GIT binary patch literal 31 lcmZShpkiKn=>Grz@AvOzXxpo6VPRox>Hs1v%`Nu72LSSb4Y>dS literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/efb7410d02418befeba25a43d676cc6124129125-4 b/tests/fuzzers/txfetcher/corpus/efb7410d02418befeba25a43d676cc6124129125-4 new file mode 100644 index 0000000..2191a73 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/efb7410d02418befeba25a43d676cc6124129125-4 @@ -0,0 +1 @@ +88242871'392752200424452601091531672177074144720616417147514758635765020556616¿ \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/f6f97d781a5a749903790e07db8619866cb7c3a1-6 b/tests/fuzzers/txfetcher/corpus/f6f97d781a5a749903790e07db8619866cb7c3a1-6 new file mode 100644 index 0000000000000000000000000000000000000000..219a8d3682f5e40e4c1406ecc1b95909f680975d GIT binary patch literal 22 dcmZShus5-wATc;7*ikpR?EU_|zKq4G?*VUO3J(AP literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/f7a3cd00fa0e57742e7dbbb8283dcaea067eaf7b-5 b/tests/fuzzers/txfetcher/corpus/f7a3cd00fa0e57742e7dbbb8283dcaea067eaf7b-5 new file mode 100644 index 0000000..f01ccd8 --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/f7a3cd00fa0e57742e7dbbb8283dcaea067eaf7b-5 @@ -0,0 +1,2 @@ +Xyt0Xl/DoCzjA0CQQDU +y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYi \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/f94d60a6c556ce485ab60088291760b8be25776c-6 b/tests/fuzzers/txfetcher/corpus/f94d60a6c556ce485ab60088291760b8be25776c-6 new file mode 100644 index 0000000..58d841f --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/f94d60a6c556ce485ab60088291760b8be25776c-6 @@ -0,0 +1,2 @@ +HZB4cQZde3JMNRcVFMO8dDFo +f9OeosiDdQQl \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/f9e627b2cb82ffa1ea5e0c6d7f2802f3000b18a8-6 b/tests/fuzzers/txfetcher/corpus/f9e627b2cb82ffa1ea5e0c6d7f2802f3000b18a8-6 new file mode 100644 index 0000000000000000000000000000000000000000..b5dfecc1e9d1cf74363f1eb9213f1fecebdc11eb GIT binary patch literal 11 ScmZSC%evb6I0FO2{=EPd*aP?g literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/corpus/fb3775aa24e5667e658920c05ba4b7b19ff256fb-5 b/tests/fuzzers/txfetcher/corpus/fb3775aa24e5667e658920c05ba4b7b19ff256fb-5 new file mode 100644 index 0000000..6f4927d --- /dev/null +++ b/tests/fuzzers/txfetcher/corpus/fb3775aa24e5667e658920c05ba4b7b19ff256fb-5 @@ -0,0 +1 @@ +HZB4c2cPclieoverpGsumgUtWj3NMYPZ/F8tá5YlNR8dDFoiDdQQl \ No newline at end of file diff --git a/tests/fuzzers/txfetcher/corpus/fd6386548e119a50db96b2fa406e54924c45a2d5-6 b/tests/fuzzers/txfetcher/corpus/fd6386548e119a50db96b2fa406e54924c45a2d5-6 new file mode 100644 index 0000000000000000000000000000000000000000..6fff60edd4f0d637706fae63e784667804e3d071 GIT binary patch literal 28 kcmZShus7Jl(bUi=DA-Xqx$OP^y}pdasVVOn_r7NX0G`(ir2qf` literal 0 HcmV?d00001 diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go new file mode 100644 index 0000000..51f2fc3 --- /dev/null +++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go @@ -0,0 +1,210 @@ +// 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 txfetcher + +import ( + "bytes" + "fmt" + "math/big" + "math/rand" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/fetcher" +) + +var ( + peers []string + txs []*types.Transaction +) + +func init() { + // Random is nice, but we need it deterministic + rand := rand.New(rand.NewSource(0x3a29)) + + peers = make([]string, 10) + for i := 0; i < len(peers); i++ { + peers[i] = fmt.Sprintf("Peer #%d", i) + } + txs = make([]*types.Transaction, 65536) // We need to bump enough to hit all the limits + for i := 0; i < len(txs); i++ { + txs[i] = types.NewTransaction(rand.Uint64(), common.Address{byte(rand.Intn(256))}, new(big.Int), 0, new(big.Int), nil) + } +} + +func fuzz(input []byte) int { + // Don't generate insanely large test cases, not much value in them + if len(input) > 16*1024 { + return 0 + } + verbose := false + r := bytes.NewReader(input) + + // Reduce the problem space for certain fuzz runs. Small tx space is better + // for testing clashes and in general the fetcher, but we should still run + // some tests with large spaces to hit potential issues on limits. + limit, err := r.ReadByte() + if err != nil { + return 0 + } + switch limit % 4 { + case 0: + txs = txs[:4] + case 1: + txs = txs[:256] + case 2: + txs = txs[:4096] + case 3: + // Full run + } + // Create a fetcher and hook into it's simulated fields + clock := new(mclock.Simulated) + rand := rand.New(rand.NewSource(0x3a29)) // Same used in package tests!!! + + f := fetcher.NewTxFetcherForTests( + func(common.Hash) bool { return false }, + func(txs []*types.Transaction) []error { + return make([]error, len(txs)) + }, + func(string, []common.Hash) error { return nil }, + nil, + clock, rand, + ) + f.Start() + defer f.Stop() + + // Try to throw random junk at the fetcher + for { + // Read the next command and abort if we're done + cmd, err := r.ReadByte() + if err != nil { + return 0 + } + switch cmd % 4 { + case 0: + // Notify a new set of transactions: + // Byte 1: Peer index to announce with + // Byte 2: Number of hashes to announce + // Byte 3-4, 5-6, etc: Transaction indices (2 byte) to announce + peerIdx, err := r.ReadByte() + if err != nil { + return 0 + } + peer := peers[int(peerIdx)%len(peers)] + + announceCnt, err := r.ReadByte() + if err != nil { + return 0 + } + announce := int(announceCnt) % (2 * len(txs)) // No point in generating too many duplicates + + 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) + if n, err := r.Read(annBuf); err != nil || n != 2 { + return 0 + } + 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, types, sizes, announces); err != nil { + panic(err) + } + + case 1: + // Deliver a new set of transactions: + // Byte 1: Peer index to announce with + // Byte 2: Number of hashes to announce + // Byte 3-4, 5-6, etc: Transaction indices (2 byte) to announce + peerIdx, err := r.ReadByte() + if err != nil { + return 0 + } + peer := peers[int(peerIdx)%len(peers)] + + deliverCnt, err := r.ReadByte() + if err != nil { + return 0 + } + deliver := int(deliverCnt) % (2 * len(txs)) // No point in generating too many duplicates + + var ( + deliverIdxs = make([]int, deliver) + deliveries = make([]*types.Transaction, deliver) + ) + for i := 0; i < len(deliveries); i++ { + deliverBuf := make([]byte, 2) + if n, err := r.Read(deliverBuf); err != nil || n != 2 { + return 0 + } + deliverIdxs[i] = (int(deliverBuf[0])*256 + int(deliverBuf[1])) % len(txs) + deliveries[i] = txs[deliverIdxs[i]] + } + directFlag, err := r.ReadByte() + if err != nil { + return 0 + } + direct := (directFlag % 2) == 0 + if verbose { + fmt.Println("Enqueue", peer, deliverIdxs, direct) + } + if err := f.Enqueue(peer, deliveries, direct); err != nil { + panic(err) + } + + case 2: + // Drop a peer: + // Byte 1: Peer index to drop + peerIdx, err := r.ReadByte() + if err != nil { + return 0 + } + peer := peers[int(peerIdx)%len(peers)] + if verbose { + fmt.Println("Drop", peer) + } + if err := f.Drop(peer); err != nil { + panic(err) + } + + case 3: + // Move the simulated clock forward + // Byte 1: 100ms increment to move forward + tickCnt, err := r.ReadByte() + if err != nil { + return 0 + } + tick := time.Duration(tickCnt) * 100 * time.Millisecond + if verbose { + fmt.Println("Sleep", tick) + } + clock.Run(tick) + } + } +} diff --git a/tests/fuzzers/txfetcher/txfetcher_test.go b/tests/fuzzers/txfetcher/txfetcher_test.go new file mode 100644 index 0000000..ac2e6b1 --- /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/gen_btheader.go b/tests/gen_btheader.go new file mode 100644 index 0000000..80ad89e --- /dev/null +++ b/tests/gen_btheader.go @@ -0,0 +1,160 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package tests + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" +) + +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 + BlobGasUsed *math.HexOrDecimal64 + ExcessBlobGas *math.HexOrDecimal64 + ParentBeaconBlockRoot *common.Hash + } + var enc btHeader + enc.Bloom = b.Bloom + enc.Coinbase = b.Coinbase + enc.MixHash = b.MixHash + enc.Nonce = b.Nonce + enc.Number = (*math.HexOrDecimal256)(b.Number) + enc.Hash = b.Hash + enc.ParentHash = b.ParentHash + enc.ReceiptTrie = b.ReceiptTrie + enc.StateRoot = b.StateRoot + enc.TransactionsTrie = b.TransactionsTrie + enc.UncleHash = b.UncleHash + enc.ExtraData = b.ExtraData + enc.Difficulty = (*math.HexOrDecimal256)(b.Difficulty) + enc.GasLimit = math.HexOrDecimal64(b.GasLimit) + enc.GasUsed = math.HexOrDecimal64(b.GasUsed) + 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 + BlobGasUsed *math.HexOrDecimal64 + ExcessBlobGas *math.HexOrDecimal64 + ParentBeaconBlockRoot *common.Hash + } + var dec btHeader + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Bloom != nil { + b.Bloom = *dec.Bloom + } + if dec.Coinbase != nil { + b.Coinbase = *dec.Coinbase + } + if dec.MixHash != nil { + b.MixHash = *dec.MixHash + } + if dec.Nonce != nil { + b.Nonce = *dec.Nonce + } + if dec.Number != nil { + b.Number = (*big.Int)(dec.Number) + } + if dec.Hash != nil { + b.Hash = *dec.Hash + } + if dec.ParentHash != nil { + b.ParentHash = *dec.ParentHash + } + if dec.ReceiptTrie != nil { + b.ReceiptTrie = *dec.ReceiptTrie + } + if dec.StateRoot != nil { + b.StateRoot = *dec.StateRoot + } + if dec.TransactionsTrie != nil { + b.TransactionsTrie = *dec.TransactionsTrie + } + if dec.UncleHash != nil { + b.UncleHash = *dec.UncleHash + } + if dec.ExtraData != nil { + b.ExtraData = *dec.ExtraData + } + if dec.Difficulty != nil { + b.Difficulty = (*big.Int)(dec.Difficulty) + } + if dec.GasLimit != nil { + b.GasLimit = uint64(*dec.GasLimit) + } + if dec.GasUsed != nil { + b.GasUsed = uint64(*dec.GasUsed) + } + if dec.Timestamp != nil { + b.Timestamp = uint64(*dec.Timestamp) + } + if dec.BaseFeePerGas != nil { + b.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) + } + 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_difficultytest.go b/tests/gen_difficultytest.go new file mode 100644 index 0000000..cd15ae3 --- /dev/null +++ b/tests/gen_difficultytest.go @@ -0,0 +1,68 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package tests + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ = (*difficultyTestMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (d DifficultyTest) MarshalJSON() ([]byte, error) { + type DifficultyTest struct { + ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp"` + ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` + UncleHash common.Hash `json:"parentUncles"` + CurrentTimestamp math.HexOrDecimal64 `json:"currentTimestamp"` + CurrentBlockNumber math.HexOrDecimal64 `json:"currentBlockNumber"` + CurrentDifficulty *math.HexOrDecimal256 `json:"currentDifficulty"` + } + var enc DifficultyTest + enc.ParentTimestamp = math.HexOrDecimal64(d.ParentTimestamp) + enc.ParentDifficulty = (*math.HexOrDecimal256)(d.ParentDifficulty) + enc.UncleHash = d.UncleHash + enc.CurrentTimestamp = math.HexOrDecimal64(d.CurrentTimestamp) + enc.CurrentBlockNumber = math.HexOrDecimal64(d.CurrentBlockNumber) + enc.CurrentDifficulty = (*math.HexOrDecimal256)(d.CurrentDifficulty) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (d *DifficultyTest) UnmarshalJSON(input []byte) error { + type DifficultyTest struct { + ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp"` + ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` + UncleHash *common.Hash `json:"parentUncles"` + CurrentTimestamp *math.HexOrDecimal64 `json:"currentTimestamp"` + CurrentBlockNumber *math.HexOrDecimal64 `json:"currentBlockNumber"` + CurrentDifficulty *math.HexOrDecimal256 `json:"currentDifficulty"` + } + var dec DifficultyTest + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ParentTimestamp != nil { + d.ParentTimestamp = uint64(*dec.ParentTimestamp) + } + if dec.ParentDifficulty != nil { + d.ParentDifficulty = (*big.Int)(dec.ParentDifficulty) + } + if dec.UncleHash != nil { + d.UncleHash = *dec.UncleHash + } + if dec.CurrentTimestamp != nil { + d.CurrentTimestamp = uint64(*dec.CurrentTimestamp) + } + if dec.CurrentBlockNumber != nil { + d.CurrentBlockNumber = uint64(*dec.CurrentBlockNumber) + } + if dec.CurrentDifficulty != nil { + d.CurrentDifficulty = (*big.Int)(dec.CurrentDifficulty) + } + return nil +} diff --git a/tests/gen_stenv.go b/tests/gen_stenv.go new file mode 100644 index 0000000..a5bd0d5 --- /dev/null +++ b/tests/gen_stenv.go @@ -0,0 +1,85 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package tests + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +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"` + ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas" gencodec:"optional"` + } + var enc stEnv + enc.Coinbase = common.UnprefixedAddress(s.Coinbase) + enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty) + enc.Random = (*math.HexOrDecimal256)(s.Random) + enc.GasLimit = math.HexOrDecimal64(s.GasLimit) + 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"` + ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas" gencodec:"optional"` + } + var dec stEnv + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Coinbase == nil { + return errors.New("missing required field 'currentCoinbase' for stEnv") + } + s.Coinbase = common.Address(*dec.Coinbase) + if dec.Difficulty != nil { + s.Difficulty = (*big.Int)(dec.Difficulty) + } + if dec.Random != nil { + s.Random = (*big.Int)(dec.Random) + } + if dec.GasLimit == nil { + return errors.New("missing required field 'currentGasLimit' for stEnv") + } + s.GasLimit = uint64(*dec.GasLimit) + if dec.Number == nil { + return errors.New("missing required field 'currentNumber' for stEnv") + } + s.Number = uint64(*dec.Number) + if dec.Timestamp == nil { + return errors.New("missing required field 'currentTimestamp' for stEnv") + } + s.Timestamp = uint64(*dec.Timestamp) + 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 new file mode 100644 index 0000000..9b5aecb --- /dev/null +++ b/tests/gen_sttransaction.go @@ -0,0 +1,120 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package tests + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*stTransactionMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s stTransaction) MarshalJSON() ([]byte, error) { + type stTransaction struct { + GasPrice *math.HexOrDecimal256 `json:"gasPrice"` + MaxFeePerGas *math.HexOrDecimal256 `json:"maxFeePerGas"` + MaxPriorityFeePerGas *math.HexOrDecimal256 `json:"maxPriorityFeePerGas"` + Nonce math.HexOrDecimal64 `json:"nonce"` + To string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []math.HexOrDecimal64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey hexutil.Bytes `json:"secretKey"` + Sender *common.Address `json:"sender"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"` + } + var enc stTransaction + enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) + enc.MaxFeePerGas = (*math.HexOrDecimal256)(s.MaxFeePerGas) + enc.MaxPriorityFeePerGas = (*math.HexOrDecimal256)(s.MaxPriorityFeePerGas) + enc.Nonce = math.HexOrDecimal64(s.Nonce) + enc.To = s.To + enc.Data = s.Data + enc.AccessLists = s.AccessLists + if s.GasLimit != nil { + enc.GasLimit = make([]math.HexOrDecimal64, len(s.GasLimit)) + for k, v := range s.GasLimit { + enc.GasLimit[k] = math.HexOrDecimal64(v) + } + } + 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) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *stTransaction) UnmarshalJSON(input []byte) error { + type stTransaction struct { + GasPrice *math.HexOrDecimal256 `json:"gasPrice"` + MaxFeePerGas *math.HexOrDecimal256 `json:"maxFeePerGas"` + MaxPriorityFeePerGas *math.HexOrDecimal256 `json:"maxPriorityFeePerGas"` + Nonce *math.HexOrDecimal64 `json:"nonce"` + To *string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []math.HexOrDecimal64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey *hexutil.Bytes `json:"secretKey"` + Sender *common.Address `json:"sender"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"` + } + var dec stTransaction + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.GasPrice != nil { + s.GasPrice = (*big.Int)(dec.GasPrice) + } + if dec.MaxFeePerGas != nil { + s.MaxFeePerGas = (*big.Int)(dec.MaxFeePerGas) + } + if dec.MaxPriorityFeePerGas != nil { + s.MaxPriorityFeePerGas = (*big.Int)(dec.MaxPriorityFeePerGas) + } + if dec.Nonce != nil { + s.Nonce = uint64(*dec.Nonce) + } + if dec.To != nil { + s.To = *dec.To + } + if dec.Data != nil { + s.Data = dec.Data + } + if dec.AccessLists != nil { + s.AccessLists = dec.AccessLists + } + if dec.GasLimit != nil { + s.GasLimit = make([]uint64, len(dec.GasLimit)) + for k, v := range dec.GasLimit { + s.GasLimit[k] = uint64(v) + } + } + if dec.Value != nil { + s.Value = dec.Value + } + if dec.PrivateKey != nil { + s.PrivateKey = *dec.PrivateKey + } + if dec.Sender != nil { + s.Sender = dec.Sender + } + if dec.BlobVersionedHashes != nil { + s.BlobVersionedHashes = dec.BlobVersionedHashes + } + if dec.BlobGasFeeCap != nil { + s.BlobGasFeeCap = (*big.Int)(dec.BlobGasFeeCap) + } + return nil +} diff --git a/tests/init.go b/tests/init.go new file mode 100644 index 0000000..c85e714 --- /dev/null +++ b/tests/init.go @@ -0,0 +1,416 @@ +// 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 tests + +import ( + "fmt" + "math/big" + "sort" + + "github.com/ethereum/go-ethereum/params" +) + +func u64(val uint64) *uint64 { return &val } + +// Forks table defines supported forks and their chain config. +var Forks = map[string]*params.ChainConfig{ + "Frontier": { + ChainID: big.NewInt(1), + }, + "Homestead": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + }, + "EIP150": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + }, + "EIP158": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + }, + "Byzantium": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + }, + "Constantinople": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(10000000), + }, + "ConstantinopleFix": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + }, + "Istanbul": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + }, + "MuirGlacier": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + }, + "FrontierToHomesteadAt5": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(5), + }, + "HomesteadToEIP150At5": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(5), + }, + "HomesteadToDaoAt5": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(5), + DAOForkSupport: true, + }, + "EIP158ToByzantiumAt5": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(5), + }, + "ByzantiumToConstantinopleAt5": { + 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(5), + }, + "ByzantiumToConstantinopleFixAt5": { + 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(5), + PetersburgBlock: big.NewInt(5), + }, + "ConstantinopleFixToIstanbulAt5": { + 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(5), + }, + "Berlin": { + 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), + }, + "BerlinToLondonAt5": { + 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(5), + }, + "London": { + 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), + }, + "ArrowGlacier": { + 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), + }, + "ArrowGlacierToParisAtDiffC0000": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + GrayGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0xC0000), + }, + "GrayGlacier": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + GrayGlacierBlock: big.NewInt(0), + }, + "Paris": { + 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), + }, + "Merge": { + 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), + }, + "Shanghai": { + 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), + }, + "ParisToShanghaiAtTime15k": { + 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(15_000), + }, + "Cancun": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + }, + "ShanghaiToCancunAtTime15k": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(15_000), + }, + "Prague": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + }, + "CancunToPragueAtTime15k": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(15_000), + }, +} + +// AvailableForks returns the set of defined fork names +func AvailableForks() []string { + var availableForks []string + for k := range Forks { + availableForks = append(availableForks, k) + } + sort.Strings(availableForks) + return availableForks +} + +// UnsupportedForkError is returned when a test requests a fork that isn't implemented. +type UnsupportedForkError struct { + Name string +} + +func (e UnsupportedForkError) Error() string { + return fmt.Sprintf("unsupported fork %q", e.Name) +} diff --git a/tests/init_test.go b/tests/init_test.go new file mode 100644 index 0000000..effeec2 --- /dev/null +++ b/tests/init_test.go @@ -0,0 +1,292 @@ +// 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 tests + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "reflect" + "regexp" + "runtime" + "sort" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/params" +) + +var ( + baseDir = filepath.Join(".", "testdata") + blockTestDir = filepath.Join(baseDir, "BlockchainTests") + stateTestDir = filepath.Join(baseDir, "GeneralStateTests") + legacyStateTestDir = filepath.Join(baseDir, "LegacyTests", "Constantinople", "GeneralStateTests") + transactionTestDir = filepath.Join(baseDir, "TransactionTests") + rlpTestDir = filepath.Join(baseDir, "RLPTests") + difficultyTestDir = filepath.Join(baseDir, "BasicTests") + executionSpecBlockchainTestDir = filepath.Join(".", "spec-tests", "fixtures", "blockchain_tests") + executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests") + benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks") +) + +func readJSON(reader io.Reader, value interface{}) error { + data, err := io.ReadAll(reader) + if err != nil { + return fmt.Errorf("error reading JSON file: %v", err) + } + if err = json.Unmarshal(data, &value); err != nil { + if syntaxerr, ok := err.(*json.SyntaxError); ok { + line := findLine(data, syntaxerr.Offset) + return fmt.Errorf("JSON syntax error at line %v: %v", line, err) + } + return err + } + return nil +} + +func readJSONFile(fn string, value interface{}) error { + file, err := os.Open(fn) + if err != nil { + return err + } + defer file.Close() + + err = readJSON(file, value) + if err != nil { + return fmt.Errorf("%s in file %s", err.Error(), fn) + } + return nil +} + +// findLine returns the line number for the given offset into data. +func findLine(data []byte, offset int64) (line int) { + line = 1 + for i, r := range string(data) { + if int64(i) >= offset { + return + } + if r == '\n' { + line++ + } + } + return +} + +// testMatcher controls skipping and chain config assignment to tests. +type testMatcher struct { + configpat []testConfig + failpat []testFailure + skiploadpat []*regexp.Regexp + slowpat []*regexp.Regexp + runonlylistpat *regexp.Regexp +} + +type testConfig struct { + p *regexp.Regexp + config params.ChainConfig +} + +type testFailure struct { + p *regexp.Regexp + reason string +} + +// slow adds expected slow tests matching the pattern. +func (tm *testMatcher) slow(pattern string) { + tm.slowpat = append(tm.slowpat, regexp.MustCompile(pattern)) +} + +// skipLoad skips JSON loading of tests matching the pattern. +func (tm *testMatcher) skipLoad(pattern string) { + tm.skiploadpat = append(tm.skiploadpat, regexp.MustCompile(pattern)) +} + +// fails adds an expected failure for tests matching the pattern. +// +//nolint:unused +func (tm *testMatcher) fails(pattern string, reason string) { + if reason == "" { + panic("empty fail reason") + } + tm.failpat = append(tm.failpat, testFailure{regexp.MustCompile(pattern), reason}) +} + +func (tm *testMatcher) runonly(pattern string) { + tm.runonlylistpat = regexp.MustCompile(pattern) +} + +// config defines chain config for tests matching the pattern. +func (tm *testMatcher) config(pattern string, cfg params.ChainConfig) { + tm.configpat = append(tm.configpat, testConfig{regexp.MustCompile(pattern), cfg}) +} + +// findSkip matches name against test skip patterns. +func (tm *testMatcher) findSkip(name string) (reason string, skipload bool) { + isWin32 := runtime.GOARCH == "386" && runtime.GOOS == "windows" + for _, re := range tm.slowpat { + if re.MatchString(name) { + if testing.Short() { + return "skipped in -short mode", false + } + if isWin32 { + return "skipped on 32bit windows", false + } + } + } + for _, re := range tm.skiploadpat { + if re.MatchString(name) { + return "skipped by skipLoad", true + } + } + return "", false +} + +// findConfig returns the chain config matching defined patterns. +func (tm *testMatcher) findConfig(t *testing.T) *params.ChainConfig { + for _, m := range tm.configpat { + if m.p.MatchString(t.Name()) { + return &m.config + } + } + return new(params.ChainConfig) +} + +// checkFailure checks whether a failure is expected. +func (tm *testMatcher) checkFailure(t *testing.T, err error) error { + failReason := "" + for _, m := range tm.failpat { + if m.p.MatchString(t.Name()) { + failReason = m.reason + break + } + } + if failReason != "" { + t.Logf("expected failure: %s", failReason) + if err != nil { + t.Logf("error: %v", err) + return nil + } + return errors.New("test succeeded unexpectedly") + } + return err +} + +// walk invokes its runTest argument for all subtests in the given directory. +// +// runTest should be a function of type func(t *testing.T, name string, x ), +// where TestType is the type of the test contained in test files. +func (tm *testMatcher) walk(t *testing.T, dir string, runTest interface{}) { + // Walk the directory. + dirinfo, err := os.Stat(dir) + if os.IsNotExist(err) || !dirinfo.IsDir() { + fmt.Fprintf(os.Stderr, "can't find test files in %s, did you clone the tests submodule?\n", dir) + t.Skip("missing test files") + } + err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + name := filepath.ToSlash(strings.TrimPrefix(path, dir+string(filepath.Separator))) + if info.IsDir() { + if _, skipload := tm.findSkip(name + "/"); skipload { + return filepath.SkipDir + } + return nil + } + if filepath.Ext(path) == ".json" { + t.Run(name, func(t *testing.T) { tm.runTestFile(t, path, name, runTest) }) + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} + +func (tm *testMatcher) runTestFile(t *testing.T, path, name string, runTest interface{}) { + if r, _ := tm.findSkip(name); r != "" { + t.Skip(r) + } + if tm.runonlylistpat != nil { + if !tm.runonlylistpat.MatchString(name) { + t.Skip("Skipped by runonly") + } + } + t.Parallel() + + // Load the file as map[string]. + m := makeMapFromTestFunc(runTest) + if err := readJSONFile(path, m.Addr().Interface()); err != nil { + t.Fatal(err) + } + + // Run all tests from the map. Don't wrap in a subtest if there is only one test in the file. + keys := sortedMapKeys(m) + if len(keys) == 1 { + runTestFunc(runTest, t, name, m, keys[0]) + } else { + for _, key := range keys { + name := name + "/" + key + t.Run(key, func(t *testing.T) { + if r, _ := tm.findSkip(name); r != "" { + t.Skip(r) + } + runTestFunc(runTest, t, name, m, key) + }) + } + } +} + +func makeMapFromTestFunc(f interface{}) reflect.Value { + stringT := reflect.TypeOf("") + testingT := reflect.TypeOf((*testing.T)(nil)) + ftyp := reflect.TypeOf(f) + if ftyp.Kind() != reflect.Func || ftyp.NumIn() != 3 || ftyp.NumOut() != 0 || ftyp.In(0) != testingT || ftyp.In(1) != stringT { + panic(fmt.Sprintf("bad test function type: want func(*testing.T, string, ), have %s", ftyp)) + } + testType := ftyp.In(2) + mp := reflect.New(reflect.MapOf(stringT, testType)) + return mp.Elem() +} + +func sortedMapKeys(m reflect.Value) []string { + keys := make([]string, m.Len()) + for i, k := range m.MapKeys() { + keys[i] = k.String() + } + sort.Strings(keys) + return keys +} + +func runTestFunc(runTest interface{}, t *testing.T, name string, m reflect.Value, key string) { + reflect.ValueOf(runTest).Call([]reflect.Value{ + reflect.ValueOf(t), + reflect.ValueOf(name), + m.MapIndex(reflect.ValueOf(key)), + }) +} + +func TestMatcherRunonlylist(t *testing.T) { + t.Parallel() + tm := new(testMatcher) + tm.runonly("invalid*") + tm.walk(t, rlpTestDir, func(t *testing.T, name string, test *RLPTest) { + if name[:len("invalidRLPTest.json")] != "invalidRLPTest.json" { + t.Fatalf("invalid test found: %s != invalidRLPTest.json", name) + } + }) +} diff --git a/tests/rlp_test.go b/tests/rlp_test.go new file mode 100644 index 0000000..79a1683 --- /dev/null +++ b/tests/rlp_test.go @@ -0,0 +1,31 @@ +// 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 tests + +import ( + "testing" +) + +func TestRLP(t *testing.T) { + t.Parallel() + tm := new(testMatcher) + tm.walk(t, rlpTestDir, func(t *testing.T, name string, test *RLPTest) { + if err := tm.checkFailure(t, test.Run()); err != nil { + t.Error(err) + } + }) +} diff --git a/tests/rlp_test_util.go b/tests/rlp_test_util.go new file mode 100644 index 0000000..e4bd545 --- /dev/null +++ b/tests/rlp_test_util.go @@ -0,0 +1,172 @@ +// 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 tests + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/rlp" +) + +// RLPTest is the JSON structure of a single RLP test. +type RLPTest struct { + // If the value of In is "INVALID" or "VALID", the test + // checks whether Out can be decoded into a value of + // type interface{}. + // + // For other JSON values, In is treated as a driver for + // calls to rlp.Stream. The test also verifies that encoding + // In produces the bytes in Out. + In interface{} + + // Out is a hex-encoded RLP value. + Out string +} + +// FromHex returns the bytes represented by the hexadecimal string s. +// s may be prefixed with "0x". +// This is copy-pasted from bytes.go, which does not return the error +func FromHex(s string) ([]byte, error) { + if len(s) > 1 && (s[0:2] == "0x" || s[0:2] == "0X") { + s = s[2:] + } + if len(s)%2 == 1 { + s = "0" + s + } + return hex.DecodeString(s) +} + +// Run executes the test. +func (t *RLPTest) Run() error { + outb, err := FromHex(t.Out) + if err != nil { + return errors.New("invalid hex in Out") + } + + // Handle simple decoding tests with no actual In value. + if t.In == "VALID" || t.In == "INVALID" { + return checkDecodeInterface(outb, t.In == "VALID") + } + + // Check whether encoding the value produces the same bytes. + in := translateJSON(t.In) + b, err := rlp.EncodeToBytes(in) + if err != nil { + return fmt.Errorf("encode failed: %v", err) + } + if !bytes.Equal(b, outb) { + return fmt.Errorf("encode produced %x, want %x", b, outb) + } + // Test stream decoding. + s := rlp.NewStream(bytes.NewReader(outb), 0) + return checkDecodeFromJSON(s, in) +} + +func checkDecodeInterface(b []byte, isValid bool) error { + err := rlp.DecodeBytes(b, new(interface{})) + switch { + case isValid && err != nil: + return fmt.Errorf("decoding failed: %v", err) + case !isValid && err == nil: + return errors.New("decoding of invalid value succeeded") + } + return nil +} + +// translateJSON makes test json values encodable with RLP. +func translateJSON(v interface{}) interface{} { + switch v := v.(type) { + case float64: + return uint64(v) + case string: + if len(v) > 0 && v[0] == '#' { // # starts a faux big int. + big, ok := new(big.Int).SetString(v[1:], 10) + if !ok { + panic(fmt.Errorf("bad test: bad big int: %q", v)) + } + return big + } + return []byte(v) + case []interface{}: + new := make([]interface{}, len(v)) + for i := range v { + new[i] = translateJSON(v[i]) + } + return new + default: + panic(fmt.Errorf("can't handle %T", v)) + } +} + +// checkDecodeFromJSON decodes from s guided by exp. exp drives the +// Stream by invoking decoding operations (Uint, Big, List, ...) based +// on the type of each value. The value decoded from the RLP stream +// must match the JSON value. +func checkDecodeFromJSON(s *rlp.Stream, exp interface{}) error { + switch exp := exp.(type) { + case uint64: + i, err := s.Uint64() + if err != nil { + return addStack("Uint", exp, err) + } + if i != exp { + return addStack("Uint", exp, fmt.Errorf("result mismatch: got %d", i)) + } + case *big.Int: + big := new(big.Int) + if err := s.Decode(&big); err != nil { + return addStack("Big", exp, err) + } + if big.Cmp(exp) != 0 { + return addStack("Big", exp, fmt.Errorf("result mismatch: got %d", big)) + } + case []byte: + b, err := s.Bytes() + if err != nil { + return addStack("Bytes", exp, err) + } + if !bytes.Equal(b, exp) { + return addStack("Bytes", exp, fmt.Errorf("result mismatch: got %x", b)) + } + case []interface{}: + if _, err := s.List(); err != nil { + return addStack("List", exp, err) + } + for i, v := range exp { + if err := checkDecodeFromJSON(s, v); err != nil { + return addStack(fmt.Sprintf("[%d]", i), exp, err) + } + } + if err := s.ListEnd(); err != nil { + return addStack("ListEnd", exp, err) + } + default: + panic(fmt.Errorf("unhandled type: %T", exp)) + } + return nil +} + +func addStack(op string, val interface{}, err error) error { + lines := strings.Split(err.Error(), "\n") + lines = append(lines, fmt.Sprintf("\t%s: %v", op, val)) + return errors.New(strings.Join(lines, "\n")) +} diff --git a/tests/solidity/bytecode.js b/tests/solidity/bytecode.js new file mode 100644 index 0000000..1190776 --- /dev/null +++ b/tests/solidity/bytecode.js @@ -0,0 +1,22 @@ +// 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 . + +{ + "linkReferences": {}, + "object": "608060405234801561001057600080fd5b5061001961007a565b604051809103906000f080158015610035573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061008a565b60405161015f8061055c83390190565b6104c3806100996000396000f3fe60806040526004361061005c576000357c01000000000000000000000000000000000000000000000000000000009004806355313dea146100615780636d3d141614610078578063b9d1e5aa1461008f578063f8a8fd6d146100a6575b600080fd5b34801561006d57600080fd5b506100766100bd565b005b34801561008457600080fd5b5061008d6100bf565b005b34801561009b57600080fd5b506100a46100c4565b005b3480156100b257600080fd5b506100bb6100c6565b005b005b600080fd5bfe5b600160021a6002f35b60058110156100e3576001810190506100cf565b5060065b60058111156100fb576001810190506100e7565b5060015b6005811215610113576001810190506100ff565b5060065b600581131561012b57600181019050610117565b5060021561013857600051505b60405160208101602060048337505060405160208101602060048339505060405160208101602060048360003c50503660005b81811015610182576002815260018101905061016b565b505060008020506000602060403e6010608060106040610123612710fa506020610123600af05060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600060405180807f697353616d654164647265737328616464726573732c61646472657373290000815250601e01905060405180910390209050600033905060405182815281600482015281602482015260648101604052602081604483600088611388f1505060405182815281600482015281602482015260648101604052602081604483600088611388f250506040518281528160048201528160248201526064810160405260208160448387611388f4505060006242004290507f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001026040518082815260200191505060405180910390a07f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001027f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001026040518082815260200191505060405180910390a13373ffffffffffffffffffffffffffffffffffffffff166001027f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001027f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001026040518082815260200191505060405180910390a2806001023373ffffffffffffffffffffffffffffffffffffffff166001027f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001027f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001026040518082815260200191505060405180910390a380600102816001023373ffffffffffffffffffffffffffffffffffffffff166001027f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001027f50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb206001026040518082815260200191505060405180910390a46002fffea165627a7a723058200e51baa2b454b47fdf0ef596fa24aff8ed3a3727b7481ebd25349182ce7152a30029608060405234801561001057600080fd5b5061013f806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063161e715014610040575b600080fd5b34801561004c57600080fd5b506100af6004803603604081101561006357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506100c9565b604051808215151515815260200191505060405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610108576001905061010d565b600090505b9291505056fea165627a7a72305820358f67a58c115ea636b0b8e5c4ca7a52b8192d0f3fa98a4434d6ea04596b5d0d0029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x19 PUSH2 0x7A JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 PUSH1 0x0 CREATE DUP1 ISZERO DUP1 ISZERO PUSH2 0x35 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP PUSH1 0x0 DUP1 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP PUSH2 0x8A JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x15F DUP1 PUSH2 0x55C DUP4 CODECOPY ADD SWAP1 JUMP JUMPDEST PUSH2 0x4C3 DUP1 PUSH2 0x99 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x5C JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV DUP1 PUSH4 0x55313DEA EQ PUSH2 0x61 JUMPI DUP1 PUSH4 0x6D3D1416 EQ PUSH2 0x78 JUMPI DUP1 PUSH4 0xB9D1E5AA EQ PUSH2 0x8F JUMPI DUP1 PUSH4 0xF8A8FD6D EQ PUSH2 0xA6 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x6D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x76 PUSH2 0xBD JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x84 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x8D PUSH2 0xBF JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x9B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xA4 PUSH2 0xC4 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xB2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xBB PUSH2 0xC6 JUMP JUMPDEST STOP JUMPDEST STOP JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST INVALID JUMPDEST PUSH1 0x1 PUSH1 0x2 BYTE PUSH1 0x2 RETURN JUMPDEST PUSH1 0x5 DUP2 LT ISZERO PUSH2 0xE3 JUMPI PUSH1 0x1 DUP2 ADD SWAP1 POP PUSH2 0xCF JUMP JUMPDEST POP PUSH1 0x6 JUMPDEST PUSH1 0x5 DUP2 GT ISZERO PUSH2 0xFB JUMPI PUSH1 0x1 DUP2 ADD SWAP1 POP PUSH2 0xE7 JUMP JUMPDEST POP PUSH1 0x1 JUMPDEST PUSH1 0x5 DUP2 SLT ISZERO PUSH2 0x113 JUMPI PUSH1 0x1 DUP2 ADD SWAP1 POP PUSH2 0xFF JUMP JUMPDEST POP PUSH1 0x6 JUMPDEST PUSH1 0x5 DUP2 SGT ISZERO PUSH2 0x12B JUMPI PUSH1 0x1 DUP2 ADD SWAP1 POP PUSH2 0x117 JUMP JUMPDEST POP PUSH1 0x2 ISZERO PUSH2 0x138 JUMPI PUSH1 0x0 MLOAD POP JUMPDEST PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 ADD PUSH1 0x20 PUSH1 0x4 DUP4 CALLDATACOPY POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 ADD PUSH1 0x20 PUSH1 0x4 DUP4 CODECOPY POP POP PUSH1 0x40 MLOAD PUSH1 0x20 DUP2 ADD PUSH1 0x20 PUSH1 0x4 DUP4 PUSH1 0x0 EXTCODECOPY POP POP CALLDATASIZE PUSH1 0x0 JUMPDEST DUP2 DUP2 LT ISZERO PUSH2 0x182 JUMPI PUSH1 0x2 DUP2 MSTORE PUSH1 0x1 DUP2 ADD SWAP1 POP PUSH2 0x16B JUMP JUMPDEST POP POP PUSH1 0x0 DUP1 KECCAK256 POP PUSH1 0x0 PUSH1 0x20 PUSH1 0x40 RETURNDATACOPY PUSH1 0x10 PUSH1 0x80 PUSH1 0x10 PUSH1 0x40 PUSH2 0x123 PUSH2 0x2710 STATICCALL POP PUSH1 0x20 PUSH2 0x123 PUSH1 0xA CREATE POP PUSH1 0x0 DUP1 PUSH1 0x0 SWAP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP PUSH1 0x0 PUSH1 0x40 MLOAD DUP1 DUP1 PUSH32 0x697353616D654164647265737328616464726573732C61646472657373290000 DUP2 MSTORE POP PUSH1 0x1E ADD SWAP1 POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 KECCAK256 SWAP1 POP PUSH1 0x0 CALLER SWAP1 POP PUSH1 0x40 MLOAD DUP3 DUP2 MSTORE DUP2 PUSH1 0x4 DUP3 ADD MSTORE DUP2 PUSH1 0x24 DUP3 ADD MSTORE PUSH1 0x64 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x20 DUP2 PUSH1 0x44 DUP4 PUSH1 0x0 DUP9 PUSH2 0x1388 CALL POP POP PUSH1 0x40 MLOAD DUP3 DUP2 MSTORE DUP2 PUSH1 0x4 DUP3 ADD MSTORE DUP2 PUSH1 0x24 DUP3 ADD MSTORE PUSH1 0x64 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x20 DUP2 PUSH1 0x44 DUP4 PUSH1 0x0 DUP9 PUSH2 0x1388 CALLCODE POP POP PUSH1 0x40 MLOAD DUP3 DUP2 MSTORE DUP2 PUSH1 0x4 DUP3 ADD MSTORE DUP2 PUSH1 0x24 DUP3 ADD MSTORE PUSH1 0x64 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x20 DUP2 PUSH1 0x44 DUP4 DUP8 PUSH2 0x1388 DELEGATECALL POP POP PUSH1 0x0 PUSH3 0x420042 SWAP1 POP PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG0 PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG1 CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x1 MUL PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 DUP1 PUSH1 0x1 MUL CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x1 MUL PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 DUP1 PUSH1 0x1 MUL DUP2 PUSH1 0x1 MUL CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x1 MUL PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH32 0x50CB9FE53DAA9737B786AB3646F04D0150DC50EF4E75F59509D83667AD5ADB20 PUSH1 0x1 MUL PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG4 PUSH1 0x2 SELFDESTRUCT INVALID LOG1 PUSH6 0x627A7A723058 KECCAK256 0xe MLOAD 0xba LOG2 0xb4 SLOAD 0xb4 PUSH32 0xDF0EF596FA24AFF8ED3A3727B7481EBD25349182CE7152A30029608060405234 DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x13F DUP1 PUSH2 0x20 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x3B JUMPI PUSH1 0x0 CALLDATALOAD PUSH29 0x100000000000000000000000000000000000000000000000000000000 SWAP1 DIV DUP1 PUSH4 0x161E7150 EQ PUSH2 0x40 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x4C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xAF PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x40 DUP2 LT ISZERO PUSH2 0x63 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 DUP1 CALLDATALOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 PUSH1 0x20 ADD SWAP1 SWAP3 SWAP2 SWAP1 POP POP POP PUSH2 0xC9 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP3 ISZERO ISZERO ISZERO ISZERO DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x0 DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x108 JUMPI PUSH1 0x1 SWAP1 POP PUSH2 0x10D JUMP JUMPDEST PUSH1 0x0 SWAP1 POP JUMPDEST SWAP3 SWAP2 POP POP JUMP INVALID LOG1 PUSH6 0x627A7A723058 KECCAK256 CALLDATALOAD DUP16 PUSH8 0xA58C115EA636B0B8 0xe5 0xc4 0xca PUSH27 0x52B8192D0F3FA98A4434D6EA04596B5D0D00290000000000000000 ", + "sourceMap": "221:8828:0:-;;;263:110;8:9:-1;5:2;;;30:1;27;20:12;5:2;263:110:0;324:11;;:::i;:::-;;;;;;;;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;324:11:0;316:5;;:19;;;;;;;;;;;;;;;;;;221:8828;;;;;;;;;;;;:::o;:::-;;;;;;;" +} diff --git a/tests/solidity/contracts/Migrations.sol b/tests/solidity/contracts/Migrations.sol new file mode 100644 index 0000000..c378ffb --- /dev/null +++ b/tests/solidity/contracts/Migrations.sol @@ -0,0 +1,23 @@ +pragma solidity >=0.4.21 <0.6.0; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + constructor() public { + owner = msg.sender; + } + + modifier restricted() { + if (msg.sender == owner) _; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/tests/solidity/contracts/OpCodes.sol b/tests/solidity/contracts/OpCodes.sol new file mode 100644 index 0000000..2a41a5f --- /dev/null +++ b/tests/solidity/contracts/OpCodes.sol @@ -0,0 +1,322 @@ +pragma solidity >=0.4.21 <0.6.0; + +contract Test1 { + function isSameAddress(address a, address b) public returns(bool){ //Simply add the two arguments and return + if (a == b) return true; + return false; + } +} + +contract OpCodes { + + Test1 test1; + + constructor() public { //Constructor function + test1 = new Test1(); //Create new "Test1" function + } + + modifier onlyOwner(address _owner) { + require(msg.sender == _owner); + _; + } + // Add a todo to the list + function test() public { + + //simple_instructions + /*assembly { pop(sub(dup1, mul(dup1, dup1))) }*/ + + //keywords + assembly { pop(address) return(2, byte(2,1)) } + + //label_complex + /*assembly { 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) pop } + assembly { pop(jumpi(eq(7, 8), abc)) jump(abc) }*/ + + //functional + /*assembly { let x := 2 add(7, mul(6, x)) mul(7, 8) add =: x }*/ + + //for_statement + assembly { for { let i := 1 } lt(i, 5) { i := add(i, 1) } {} } + assembly { for { let i := 6 } gt(i, 5) { i := add(i, 1) } {} } + assembly { for { let i := 1 } slt(i, 5) { i := add(i, 1) } {} } + assembly { for { let i := 6 } sgt(i, 5) { i := add(i, 1) } {} } + + //no_opcodes_in_strict + assembly { pop(callvalue()) } + + //no_dup_swap_in_strict + /*assembly { swap1() }*/ + + //print_functional + assembly { let x := mul(sload(0x12), 7) } + + //print_if + assembly { if 2 { pop(mload(0)) }} + + //function_definitions_multiple_args + assembly { function f(a, d){ mstore(a, d) } function g(a, d) -> x, y {}} + + //sstore + assembly { function f(a, d){ sstore(a, d) } function g(a, d) -> x, y {}} + + //mstore8 + assembly { function f(a, d){ mstore8(a, d) } function g(a, d) -> x, y {}} + + //calldatacopy + assembly { + let a := mload(0x40) + let b := add(a, 32) + calldatacopy(a, 4, 32) + /*calldatacopy(b, add(4, 32), 32)*/ + /*result := add(mload(a), mload(b))*/ + } + + //codecopy + assembly { + let a := mload(0x40) + let b := add(a, 32) + codecopy(a, 4, 32) + } + + //codecopy + assembly { + let a := mload(0x40) + let b := add(a, 32) + extcodecopy(0, a, 4, 32) + } + + //for_statement + assembly { let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } } + + //keccak256 + assembly { pop(keccak256(0,0)) } + + //returndatasize + assembly { let r := returndatasize } + + //returndatacopy + assembly { returndatacopy(64, 32, 0) } + //byzantium vs const Constantinople + //staticcall + assembly { pop(staticcall(10000, 0x123, 64, 0x10, 128, 0x10)) } + + /*//create2 Constantinople + assembly { pop(create2(10, 0x123, 32, 64)) }*/ + + //create Constantinople + assembly { pop(create(10, 0x123, 32)) } + + //shift Constantinople + /*assembly { pop(shl(10, 32)) } + assembly { pop(shr(10, 32)) } + assembly { pop(sar(10, 32)) }*/ + + + //not + assembly { pop( not(0x1f)) } + + //exp + assembly { pop( exp(2, 226)) } + + //mod + assembly { pop( mod(3, 9)) } + + //smod + assembly { pop( smod(3, 9)) } + + //div + assembly { pop( div(4, 2)) } + + //sdiv + assembly { pop( sdiv(4, 2)) } + + //iszero + assembly { pop(iszero(1)) } + + //and + assembly { pop(and(2,3)) } + + //or + assembly { pop(or(3,3)) } + + //xor + assembly { pop(xor(3,3)) } + + //addmod + assembly { pop(addmod(3,3,6)) } + + //mulmod + assembly { pop(mulmod(3,3,3)) } + + //signextend + assembly { pop(signextend(1, 10)) } + + //sha3 + assembly { pop(calldataload(0)) } + + //blockhash + assembly { pop(blockhash(sub(number(), 1))) } + + //balance + assembly { pop(balance(0x0)) } + + //caller + assembly { pop(caller()) } + + //codesize + assembly { pop(codesize()) } + + //extcodesize + assembly { pop(extcodesize(0x1)) } + + //origin + assembly { pop(origin()) } + + //gas + assembly { pop(gas())} + + //msize + assembly { pop(msize())} + + //pc + assembly { pop(pc())} + + //gasprice + assembly { pop(gasprice())} + + //coinbase + assembly { pop(coinbase())} + + //timestamp + assembly { pop(timestamp())} + + //number + assembly { pop(number())} + + //difficulty + assembly { pop(difficulty())} + + //gaslimit + assembly { pop(gaslimit())} + + //call + address contractAddr = address(test1); + bytes4 sig = bytes4(keccak256("isSameAddress(address,address)")); //Function signature + address a = msg.sender; + + assembly { + let x := mload(0x40) //Find empty storage location using "free memory pointer" + mstore(x,sig) //Place signature at beginning of empty storage + mstore(add(x,0x04),a) // first address parameter. just after signature + mstore(add(x,0x24),a) // 2nd address parameter - first padded. add 32 bytes (not 20 bytes) + mstore(0x40,add(x,0x64)) // this is missing in other examples. Set free pointer before function call. so it is used by called function. + // new free pointer position after the output values of the called function. + + let success := call( + 5000, //5k gas + contractAddr, //To addr + 0, //No wei passed + x, // Inputs are at location x + 0x44, //Inputs size two padded, so 68 bytes + x, //Store output over input + 0x20) //Output is 32 bytes long + } + + //callcode + assembly { + let x := mload(0x40) //Find empty storage location using "free memory pointer" + mstore(x,sig) //Place signature at beginning of empty storage + mstore(add(x,0x04),a) // first address parameter. just after signature + mstore(add(x,0x24),a) // 2nd address parameter - first padded. add 32 bytes (not 20 bytes) + mstore(0x40,add(x,0x64)) // this is missing in other examples. Set free pointer before function call. so it is used by called function. + // new free pointer position after the output values of the called function. + + let success := callcode( + 5000, //5k gas + contractAddr, //To addr + 0, //No wei passed + x, // Inputs are at location x + 0x44, //Inputs size two padded, so 68 bytes + x, //Store output over input + 0x20) //Output is 32 bytes long + } + + //delegatecall + assembly { + let x := mload(0x40) //Find empty storage location using "free memory pointer" + mstore(x,sig) //Place signature at beginning of empty storage + mstore(add(x,0x04),a) // first address parameter. just after signature + mstore(add(x,0x24),a) // 2nd address parameter - first padded. add 32 bytes (not 20 bytes) + mstore(0x40,add(x,0x64)) // this is missing in other examples. Set free pointer before function call. so it is used by called function. + // new free pointer position after the output values of the called function. + + let success := delegatecall( + 5000, //5k gas + contractAddr, //To addr + x, // Inputs are at location x + 0x44, //Inputs size two padded, so 68 bytes + x, //Store output over input + 0x20) //Output is 32 bytes long + } + + uint256 _id = 0x420042; + + //log0 + log0( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20) + ); + + //log1 + log1( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20) + ); + + //log2 + log2( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(uint256(msg.sender)) + ); + + //log3 + log3( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(uint256(msg.sender)), + bytes32(_id) + ); + + //log4 + log4( + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(uint256(msg.sender)), + bytes32(_id), + bytes32(_id) + + ); + + //selfdestruct + assembly { selfdestruct(0x02) } + } + + function test_revert() public { + + //revert + assembly{ revert(0, 0) } + } + + function test_invalid() public { + + //revert + assembly{ invalid() } + } + + function test_stop() public { + + //revert + assembly{ stop() } + } + +} diff --git a/tests/solidity/migrations/1_initial_migration.js b/tests/solidity/migrations/1_initial_migration.js new file mode 100644 index 0000000..5aea2e2 --- /dev/null +++ b/tests/solidity/migrations/1_initial_migration.js @@ -0,0 +1,21 @@ +// 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 . + +const Migrations = artifacts.require("Migrations"); + +module.exports = function(deployer) { + deployer.deploy(Migrations); +}; diff --git a/tests/solidity/migrations/2_opCodes_migration.js b/tests/solidity/migrations/2_opCodes_migration.js new file mode 100644 index 0000000..8ec9726 --- /dev/null +++ b/tests/solidity/migrations/2_opCodes_migration.js @@ -0,0 +1,21 @@ +// 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 . + +var OpCodes = artifacts.require("./OpCodes.sol"); + +module.exports = function(deployer) { + deployer.deploy(OpCodes); +}; diff --git a/tests/solidity/test/opCodes.js b/tests/solidity/test/opCodes.js new file mode 100644 index 0000000..dde80f7 --- /dev/null +++ b/tests/solidity/test/opCodes.js @@ -0,0 +1,50 @@ +// 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 . + +const TodoList = artifacts.require('./OpCodes.sol') +const assert = require('assert') +let contractInstance +const Web3 = require('web3'); +const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); +// const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:9545')); + +contract('OpCodes', (accounts) => { + beforeEach(async () => { + contractInstance = await TodoList.deployed() + }) + it('Should run without errors the majorit of opcodes', async () => { + await contractInstance.test() + await contractInstance.test_stop() + + }) + + it('Should throw invalid op code', async () => { + try{ + await contractInstance.test_invalid() + } + catch(error) { + console.error(error); + } + }) + + it('Should revert', async () => { + try{ + await contractInstance.test_revert() } + catch(error) { + console.error(error); + } + }) +}) diff --git a/tests/solidity/truffle-config.js b/tests/solidity/truffle-config.js new file mode 100644 index 0000000..47a89c8 --- /dev/null +++ b/tests/solidity/truffle-config.js @@ -0,0 +1,124 @@ +// 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 . + +/** + * Use this file to configure your truffle project. It's seeded with some + * common settings for different networks and features like migrations, + * compilation and testing. Uncomment the ones you need or modify + * them to suit your project as necessary. + * + * More information about configuration can be found at: + * + * truffleframework.com/docs/advanced/configuration + * + * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) + * to sign your transactions before they're sent to a remote public node. Infura API + * keys are available for free at: infura.io/register + * + * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate + * public/private key pairs. If you're publishing your code to GitHub make sure you load this + * phrase from a file you've .gitignored so it doesn't accidentally become public. + * + */ + +// const HDWalletProvider = require('truffle-hdwallet-provider'); +// const infuraKey = "fj4jll3k....."; +// +// const fs = require('fs'); +// const mnemonic = fs.readFileSync(".secret").toString().trim(); + +// module.exports = { +// /** +// * Networks define how you connect to your ethereum client and let you set the +// * defaults web3 uses to send transactions. If you don't specify one truffle +// * will spin up a development blockchain for you on port 9545 when you +// * run `develop` or `test`. You can ask a truffle command to use a specific +// * network from the command line, e.g +// * +// * $ truffle test --network +// */ +// +// networks: { +// // Useful for testing. The `development` name is special - truffle uses it by default +// // if it's defined here and no other network is specified at the command line. +// // You should run a client (like ganache-cli, geth or parity) in a separate terminal +// // tab if you use this network and you must also set the `host`, `port` and `network_id` +// // options below to some value. +// // +// // development: { +// // host: "127.0.0.1", // Localhost (default: none) +// // port: 8545, // Standard Ethereum port (default: none) +// // network_id: "*", // Any network (default: none) +// // }, +// +// // Another network with more advanced options... +// // advanced: { +// // port: 8777, // Custom port +// // network_id: 1342, // Custom network +// // gas: 8500000, // Gas sent with each transaction (default: ~6700000) +// // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) +// // from:

      , // Account to send txs from (default: accounts[0]) +// // websockets: true // Enable EventEmitter interface for web3 (default: false) +// // }, +// +// // Useful for deploying to a public network. +// // NB: It's important to wrap the provider as a function. +// // ropsten: { +// // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/${infuraKey}`), +// // network_id: 3, // Ropsten's id +// // gas: 5500000, // Ropsten has a lower block limit than mainnet +// // confirmations: 2, // # of confs to wait between deployments. (default: 0) +// // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) +// // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) +// // }, +// +// // Useful for private networks +// // private: { +// // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), +// // network_id: 2111, // This network is yours, in the cloud. +// // production: true // Treats this network as if it was a public net. (default: false) +// // } +// }, +// +// // Set default mocha options here, use special reporters etc. +// mocha: { +// // timeout: 100000 +// }, +// +// // Configure your compilers +// compilers: { +// solc: { +// // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version) +// // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) +// // settings: { // See the solidity docs for advice about optimization and evmVersion +// // optimizer: { +// // enabled: false, +// // runs: 200 +// // }, +// // evmVersion: "byzantium" +// // } +// } +// } +// } +module.exports = { + networks: { + development: { + host: 'localhost', + port: 8545, + network_id: '*' + } + } +} diff --git a/tests/state_test.go b/tests/state_test.go new file mode 100644 index 0000000..76fec97 --- /dev/null +++ b/tests/state_test.go @@ -0,0 +1,348 @@ +// 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 tests + +import ( + "bufio" + "bytes" + "fmt" + "math/big" + "math/rand" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + "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/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/holiman/uint256" +) + +func initMatcher(st *testMatcher) { + // Long tests: + st.slow(`^stAttackTest/ContractCreationSpam`) + st.slow(`^stBadOpcode/badOpcodes`) + st.slow(`^stPreCompiledContracts/modexp`) + st.slow(`^stQuadraticComplexityTest/`) + st.slow(`^stStaticCall/static_Call50000`) + st.slow(`^stStaticCall/static_Return50000`) + st.slow(`^stSystemOperationsTest/CallRecursiveBomb`) + st.slow(`^stTransactionTest/Opcodes_TransactionInit`) + // Very time consuming + st.skipLoad(`^stTimeConsuming/`) + st.skipLoad(`.*vmPerformance/loop.*`) + // Uses 1GB RAM per tested fork + st.skipLoad(`^stStaticCall/static_Call1MB`) + + // Broken tests: + // EOF is not part of cancun + st.skipLoad(`^stEOF/`) + + // The tests under Pyspecs are the ones that are published as execution-spec tests. + // We run these tests separately, no need to _also_ run them as part of the + // reference tests. + st.skipLoad(`^Pyspecs/`) +} + +func TestState(t *testing.T) { + t.Parallel() + + st := new(testMatcher) + initMatcher(st) + for _, dir := range []string{ + filepath.Join(baseDir, "EIPTests", "StateTests"), + stateTestDir, + benchmarksDir, + } { + st.walk(t, dir, func(t *testing.T, name string, test *StateTest) { + execStateTest(t, st, test) + }) + } +} + +// TestLegacyState tests some older tests, which were moved to the folder +// 'LegacyTests' for the Istanbul fork. +func TestLegacyState(t *testing.T) { + st := new(testMatcher) + initMatcher(st) + st.walk(t, legacyStateTestDir, func(t *testing.T, name string, test *StateTest) { + execStateTest(t, st, test) + }) +} + +// TestExecutionSpecState runs the test fixtures from execution-spec-tests. +func TestExecutionSpecState(t *testing.T) { + if !common.FileExist(executionSpecStateTestDir) { + t.Skipf("directory %s does not exist", executionSpecStateTestDir) + } + st := new(testMatcher) + + st.walk(t, executionSpecStateTestDir, func(t *testing.T, name string, test *StateTest) { + execStateTest(t, st, test) + }) +} + +func execStateTest(t *testing.T, st *testMatcher, test *StateTest) { + for _, subtest := range test.Subtests() { + subtest := subtest + key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) + + // If -short flag is used, we don't execute all four permutations, only + // one. + executionMask := 0xf + if testing.Short() { + executionMask = (1 << (rand.Int63() & 4)) + } + t.Run(key+"/hash/trie", func(t *testing.T) { + if executionMask&0x1 == 0 { + t.Skip("test (randomly) skipped due to short-tag") + } + withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { + var result error + test.Run(subtest, vmconfig, false, rawdb.HashScheme, func(err error, state *StateTestState) { + result = st.checkFailure(t, err) + }) + return result + }) + }) + t.Run(key+"/hash/snap", func(t *testing.T) { + if executionMask&0x2 == 0 { + t.Skip("test (randomly) skipped due to short-tag") + } + withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { + var result error + test.Run(subtest, vmconfig, true, rawdb.HashScheme, func(err error, state *StateTestState) { + if state.Snapshots != nil && state.StateDB != nil { + if _, err := state.Snapshots.Journal(state.StateDB.IntermediateRoot(false)); err != nil { + result = err + return + } + } + result = st.checkFailure(t, err) + }) + return result + }) + }) + t.Run(key+"/path/trie", func(t *testing.T) { + if executionMask&0x4 == 0 { + t.Skip("test (randomly) skipped due to short-tag") + } + withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { + var result error + test.Run(subtest, vmconfig, false, rawdb.PathScheme, func(err error, state *StateTestState) { + result = st.checkFailure(t, err) + }) + return result + }) + }) + t.Run(key+"/path/snap", func(t *testing.T) { + if executionMask&0x8 == 0 { + t.Skip("test (randomly) skipped due to short-tag") + } + withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { + var result error + test.Run(subtest, vmconfig, true, rawdb.PathScheme, func(err error, state *StateTestState) { + if state.Snapshots != nil && state.StateDB != nil { + if _, err := state.Snapshots.Journal(state.StateDB.IntermediateRoot(false)); err != nil { + result = err + return + } + } + result = st.checkFailure(t, err) + }) + return result + }) + }) + } +} + +// Transactions with gasLimit above this value will not get a VM trace on failure. +const traceErrorLimit = 400000 + +func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) { + // Use config from command line arguments. + config := vm.Config{} + err := test(config) + if err == nil { + return + } + + // Test failed, re-run with tracing enabled. + t.Error(err) + if gasLimit > traceErrorLimit { + t.Log("gas limit too high for EVM trace") + return + } + buf := new(bytes.Buffer) + w := bufio.NewWriter(buf) + config.Tracer = logger.NewJSONLogger(&logger.Config{}, w) + err2 := test(config) + if !reflect.DeepEqual(err, err2) { + t.Errorf("different error for second run: %v", err2) + } + w.Flush() + if buf.Len() == 0 { + t.Log("no EVM operation logs generated") + } else { + t.Log("EVM operation log:\n" + buf.String()) + } + // t.Logf("EVM output: 0x%x", tracer.Output()) + // t.Logf("EVM error: %v", tracer.Error()) +} + +func BenchmarkEVM(b *testing.B) { + // Walk the directory. + dir := benchmarksDir + dirinfo, err := os.Stat(dir) + if os.IsNotExist(err) || !dirinfo.IsDir() { + fmt.Fprintf(os.Stderr, "can't find test files in %s, did you clone the evm-benchmarks submodule?\n", dir) + b.Skip("missing test files") + } + err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + if ext := filepath.Ext(path); ext == ".json" { + name := filepath.ToSlash(strings.TrimPrefix(strings.TrimSuffix(path, ext), dir+string(filepath.Separator))) + b.Run(name, func(b *testing.B) { runBenchmarkFile(b, path) }) + } + return nil + }) + if err != nil { + b.Fatal(err) + } +} + +func runBenchmarkFile(b *testing.B, path string) { + m := make(map[string]StateTest) + if err := readJSONFile(path, &m); err != nil { + b.Fatal(err) + return + } + if len(m) != 1 { + b.Fatal("expected single benchmark in a file") + return + } + for _, t := range m { + t := t + runBenchmark(b, &t) + } +} + +func runBenchmark(b *testing.B, t *StateTest) { + for _, subtest := range t.Subtests() { + subtest := subtest + key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) + + b.Run(key, func(b *testing.B) { + vmconfig := vm.Config{} + + config, eips, err := GetChainConfig(subtest.Fork) + if err != nil { + b.Error(err) + return + } + var rules = config.Rules(new(big.Int), false, 0) + + vmconfig.ExtraEips = eips + block := t.genesis(config).ToBlock() + state := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, false, rawdb.HashScheme) + defer state.Close() + + var baseFee *big.Int + if rules.IsLondon { + baseFee = t.json.Env.BaseFee + if baseFee == nil { + // Retesteth uses `0x10` for genesis baseFee. Therefore, it defaults to + // parent - 2 : 0xa as the basefee for 'this' context. + baseFee = big.NewInt(0x0a) + } + } + post := t.json.Post[subtest.Fork][subtest.Index] + msg, err := t.json.Tx.toMessage(post, baseFee) + if err != nil { + b.Error(err) + return + } + + // Try to recover tx with current signer + if len(post.TxBytes) != 0 { + var ttx types.Transaction + err := ttx.UnmarshalBinary(post.TxBytes) + if err != nil { + b.Error(err) + return + } + + if _, err := types.Sender(types.LatestSigner(config), &ttx); err != nil { + b.Error(err) + return + } + } + + // Prepare the EVM. + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) + context.GetHash = vmTestBlockHash + context.BaseFee = baseFee + evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig) + + // Create "contract" for sender to cache code analysis. + sender := vm.NewContract(vm.AccountRef(msg.From), vm.AccountRef(msg.From), + nil, 0) + + var ( + gasUsed uint64 + elapsed uint64 + refund uint64 + ) + b.ResetTimer() + for n := 0; n < b.N; n++ { + snapshot := state.StateDB.Snapshot() + state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + b.StartTimer() + start := time.Now() + + // Execute the message. + _, leftOverGas, err := evm.Call(sender, *msg.To, msg.Data, msg.GasLimit, uint256.MustFromBig(msg.Value)) + if err != nil { + b.Error(err) + return + } + + b.StopTimer() + elapsed += uint64(time.Since(start)) + refund += state.StateDB.GetRefund() + gasUsed += msg.GasLimit - leftOverGas + + state.StateDB.RevertToSnapshot(snapshot) + } + if elapsed < 1 { + elapsed = 1 + } + // Keep it as uint64, multiply 100 to get two digit float later + mgasps := (100 * 1000 * (gasUsed - refund)) / elapsed + b.ReportMetric(float64(mgasps)/100, "mgas/s") + }) + } +} diff --git a/tests/state_test_util.go b/tests/state_test_util.go new file mode 100644 index 0000000..416bab9 --- /dev/null +++ b/tests/state_test_util.go @@ -0,0 +1,503 @@ +// 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 tests + +import ( + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/tracing" + "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" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" +) + +// StateTest checks transaction processing without block context. +// See https://github.com/ethereum/EIPs/issues/176 for the test format specification. +type StateTest struct { + json stJSON +} + +// StateSubtest selects a specific configuration of a General State Test. +type StateSubtest struct { + Fork string + Index int +} + +func (t *StateTest) UnmarshalJSON(in []byte) error { + return json.Unmarshal(in, &t.json) +} + +type stJSON struct { + Env stEnv `json:"env"` + Pre types.GenesisAlloc `json:"pre"` + Tx stTransaction `json:"transaction"` + Out hexutil.Bytes `json:"out"` + Post map[string][]stPostState `json:"post"` +} + +type stPostState struct { + Root common.UnprefixedHash `json:"hash"` + Logs common.UnprefixedHash `json:"logs"` + TxBytes hexutil.Bytes `json:"txbytes"` + ExpectException string `json:"expectException"` + Indexes struct { + Data int `json:"data"` + Gas int `json:"gas"` + Value int `json:"value"` + } +} + +//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"` + 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 + ExcessBlobGas *math.HexOrDecimal64 +} + +//go:generate go run github.com/fjl/gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go + +type stTransaction struct { + GasPrice *big.Int `json:"gasPrice"` + MaxFeePerGas *big.Int `json:"maxFeePerGas"` + MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"` + Nonce uint64 `json:"nonce"` + To string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []uint64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey []byte `json:"secretKey"` + Sender *common.Address `json:"sender"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + BlobGasFeeCap *big.Int `json:"maxFeePerBlobGas,omitempty"` +} + +type stTransactionMarshaling struct { + GasPrice *math.HexOrDecimal256 + MaxFeePerGas *math.HexOrDecimal256 + MaxPriorityFeePerGas *math.HexOrDecimal256 + Nonce math.HexOrDecimal64 + GasLimit []math.HexOrDecimal64 + PrivateKey hexutil.Bytes + BlobGasFeeCap *math.HexOrDecimal256 +} + +// GetChainConfig takes a fork definition and returns a chain config. +// The fork definition can be +// - a plain forkname, e.g. `Byzantium`, +// - a fork basename, and a list of EIPs to enable; e.g. `Byzantium+1884+1283`. +func GetChainConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) { + var ( + splitForks = strings.Split(forkString, "+") + ok bool + baseName, eipsStrings = splitForks[0], splitForks[1:] + ) + if baseConfig, ok = Forks[baseName]; !ok { + return nil, nil, UnsupportedForkError{baseName} + } + for _, eip := range eipsStrings { + if eipNum, err := strconv.Atoi(eip); err != nil { + return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) + } else { + if !vm.ValidEip(eipNum) { + return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) + } + eips = append(eips, eipNum) + } + } + return baseConfig, eips, nil +} + +// Subtests returns all valid subtests of the test. +func (t *StateTest) Subtests() []StateSubtest { + var sub []StateSubtest + for fork, pss := range t.json.Post { + for i := range pss { + sub = append(sub, StateSubtest{fork, i}) + } + } + return sub +} + +// checkError checks if the error returned by the state transition matches any expected error. +// A failing expectation returns a wrapped version of the original error, if any, +// or a new error detailing the failing expectation. +// This function does not return or modify the original error, it only evaluates and returns expectations for the error. +func (t *StateTest) checkError(subtest StateSubtest, err error) error { + expectedError := t.json.Post[subtest.Fork][subtest.Index].ExpectException + if err == nil && expectedError == "" { + return nil + } + if err == nil && expectedError != "" { + return fmt.Errorf("expected error %q, got no error", expectedError) + } + if err != nil && expectedError == "" { + return fmt.Errorf("unexpected error: %w", err) + } + if err != nil && expectedError != "" { + // Ignore expected errors (TODO MariusVanDerWijden check error string) + return nil + } + return nil +} + +// Run executes a specific subtest and verifies the post-state and logs +func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string, postCheck func(err error, st *StateTestState)) (result error) { + st, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter, scheme) + // Invoke the callback at the end of function for further analysis. + defer func() { + postCheck(result, &st) + st.Close() + }() + + checkedErr := t.checkError(subtest, err) + if checkedErr != nil { + return checkedErr + } + // The error has been checked; if it was unexpected, it's already returned. + if err != nil { + // Here, an error exists but it was expected. + // We do not check the post state or logs. + return 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 fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) + } + if logs := rlpHash(st.StateDB.Logs()); logs != common.Hash(post.Logs) { + return fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) + } + st.StateDB, _ = state.New(root, st.StateDB.Database(), st.Snapshots) + return nil +} + +// RunNoVerify runs a specific subtest and returns the statedb and post-state root. +// Remember to call state.Close after verifying the test result! +func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string) (st StateTestState, root common.Hash, err error) { + config, eips, err := GetChainConfig(subtest.Fork) + if err != nil { + return st, common.Hash{}, UnsupportedForkError{subtest.Fork} + } + vmconfig.ExtraEips = eips + + block := t.genesis(config).ToBlock() + st = MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter, scheme) + + var baseFee *big.Int + if config.IsLondon(new(big.Int)) { + baseFee = t.json.Env.BaseFee + if baseFee == nil { + // Retesteth uses `0x10` for genesis baseFee. Therefore, it defaults to + // parent - 2 : 0xa as the basefee for 'this' context. + baseFee = big.NewInt(0x0a) + } + } + post := t.json.Post[subtest.Fork][subtest.Index] + msg, err := t.json.Tx.toMessage(post, baseFee) + if err != nil { + return st, common.Hash{}, err + } + + { // Blob transactions may be present after the Cancun fork. + // In production, + // - the header is verified against the max in eip4844.go:VerifyEIP4844Header + // - the block body is verified against the header in block_validator.go:ValidateBody + // Here, we just do this shortcut smaller fix, since state tests do not + // utilize those codepaths + if len(msg.BlobHashes)*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { + return st, common.Hash{}, errors.New("blob gas exceeds maximum") + } + } + + // Try to recover tx with current signer + if len(post.TxBytes) != 0 { + var ttx types.Transaction + err := ttx.UnmarshalBinary(post.TxBytes) + if err != nil { + return st, common.Hash{}, err + } + if _, err := types.Sender(types.LatestSigner(config), &ttx); err != nil { + return st, common.Hash{}, err + } + } + + // Prepare the EVM. + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) + context.GetHash = vmTestBlockHash + context.BaseFee = baseFee + context.Random = nil + if t.json.Env.Difficulty != nil { + context.Difficulty = new(big.Int).Set(t.json.Env.Difficulty) + } + if config.IsLondon(new(big.Int)) && t.json.Env.Random != nil { + rnd := common.BigToHash(t.json.Env.Random) + 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, st.StateDB, config, vmconfig) + + if tracer := vmconfig.Tracer; tracer != nil && tracer.OnTxStart != nil { + tracer.OnTxStart(evm.GetVMContext(), nil, msg.From) + if evm.Config.Tracer.OnTxEnd != nil { + defer func() { + evm.Config.Tracer.OnTxEnd(nil, err) + }() + } + } + // Execute the message. + snapshot := st.StateDB.Snapshot() + gaspool := new(core.GasPool) + gaspool.AddGas(block.GasLimit()) + _, err = core.ApplyMessage(evm, msg, gaspool) + if err != nil { + st.StateDB.RevertToSnapshot(snapshot) + } + // Add 0-value mining reward. This only makes a difference in the cases + // where + // - the coinbase self-destructed, or + // - there are only 'bad' transactions, which aren't executed. In those cases, + // the coinbase gets no txfee, so isn't created, and thus needs to be touched + st.StateDB.AddBalance(block.Coinbase(), new(uint256.Int), tracing.BalanceChangeUnspecified) + + // Commit state mutations into database. + root, _ = st.StateDB.Commit(block.NumberU64(), config.IsEIP158(block.Number())) + return st, root, err +} + +func (t *StateTest) gasLimit(subtest StateSubtest) uint64 { + return t.json.Tx.GasLimit[t.json.Post[subtest.Fork][subtest.Index].Indexes.Gas] +} + +func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis { + genesis := &core.Genesis{ + Config: config, + Coinbase: t.json.Env.Coinbase, + Difficulty: t.json.Env.Difficulty, + GasLimit: t.json.Env.GasLimit, + Number: t.json.Env.Number, + Timestamp: t.json.Env.Timestamp, + Alloc: t.json.Pre, + } + if t.json.Env.Random != nil { + // Post-Merge + genesis.Mixhash = common.BigToHash(t.json.Env.Random) + genesis.Difficulty = big.NewInt(0) + } + return genesis +} + +func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Message, error) { + var from common.Address + // 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) + } + from = crypto.PubkeyToAddress(key.PublicKey) + } + // Parse recipient if present. + var to *common.Address + if tx.To != "" { + to = new(common.Address) + if err := to.UnmarshalText([]byte(tx.To)); err != nil { + return nil, fmt.Errorf("invalid to address: %v", err) + } + } + + // Get values specific to this post state. + if ps.Indexes.Data > len(tx.Data) { + return nil, fmt.Errorf("tx data index %d out of bounds", ps.Indexes.Data) + } + if ps.Indexes.Value > len(tx.Value) { + return nil, fmt.Errorf("tx value index %d out of bounds", ps.Indexes.Value) + } + if ps.Indexes.Gas > len(tx.GasLimit) { + return nil, fmt.Errorf("tx gas limit index %d out of bounds", ps.Indexes.Gas) + } + dataHex := tx.Data[ps.Indexes.Data] + valueHex := tx.Value[ps.Indexes.Value] + gasLimit := tx.GasLimit[ps.Indexes.Gas] + // Value, Data hex encoding is messy: https://github.com/ethereum/tests/issues/203 + value := new(big.Int) + if valueHex != "0x" { + v, ok := math.ParseBig256(valueHex) + if !ok { + return nil, fmt.Errorf("invalid tx value %q", valueHex) + } + value = v + } + data, err := hex.DecodeString(strings.TrimPrefix(dataHex, "0x")) + if err != nil { + return nil, fmt.Errorf("invalid tx data %q", dataHex) + } + var accessList types.AccessList + if tx.AccessLists != nil && tx.AccessLists[ps.Indexes.Data] != nil { + accessList = *tx.AccessLists[ps.Indexes.Data] + } + // If baseFee provided, set gasPrice to effectiveGasPrice. + gasPrice := tx.GasPrice + if baseFee != nil { + if tx.MaxFeePerGas == nil { + tx.MaxFeePerGas = gasPrice + } + if tx.MaxFeePerGas == nil { + tx.MaxFeePerGas = new(big.Int) + } + if tx.MaxPriorityFeePerGas == nil { + tx.MaxPriorityFeePerGas = tx.MaxFeePerGas + } + gasPrice = math.BigMin(new(big.Int).Add(tx.MaxPriorityFeePerGas, baseFee), + tx.MaxFeePerGas) + } + if gasPrice == nil { + return nil, errors.New("no gas price provided") + } + + msg := &core.Message{ + From: from, + To: to, + Nonce: tx.Nonce, + Value: value, + GasLimit: gasLimit, + GasPrice: gasPrice, + GasFeeCap: tx.MaxFeePerGas, + GasTipCap: tx.MaxPriorityFeePerGas, + Data: data, + AccessList: accessList, + BlobHashes: tx.BlobVersionedHashes, + BlobGasFeeCap: tx.BlobGasFeeCap, + } + return msg, nil +} + +func rlpHash(x interface{}) (h common.Hash) { + hw := sha3.NewLegacyKeccak256() + rlp.Encode(hw, x) + hw.Sum(h[:0]) + return h +} + +func vmTestBlockHash(n uint64) common.Hash { + return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) +} + +// StateTestState groups all the state database objects together for use in tests. +type StateTestState struct { + StateDB *state.StateDB + TrieDB *triedb.Database + Snapshots *snapshot.Tree +} + +// MakePreState creates a state containing the given allocation. +func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bool, scheme string) StateTestState { + tconf := &triedb.Config{Preimages: true} + if scheme == rawdb.HashScheme { + tconf.HashDB = hashdb.Defaults + } else { + tconf.PathDB = pathdb.Defaults + } + triedb := triedb.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) + statedb.SetNonce(addr, a.Nonce) + statedb.SetBalance(addr, uint256.MustFromBig(a.Balance), tracing.BalanceChangeUnspecified) + for k, v := range a.Storage { + statedb.SetState(addr, k, v) + } + } + // Commit and re-open to start with a clean state. + root, _ := statedb.Commit(0, false) + + // If snapshot is requested, initialize the snapshotter and use it in state. + var snaps *snapshot.Tree + if snapshotter { + snapconfig := snapshot.Config{ + CacheSize: 1, + Recovery: false, + NoBuild: false, + AsyncBuild: false, + } + snaps, _ = snapshot.New(snapconfig, db, triedb, root) + } + statedb, _ = state.New(root, sdb, snaps) + return StateTestState{statedb, triedb, snaps} +} + +// Close should be called when the state is no longer needed, ie. after running the test. +func (st *StateTestState) Close() { + if st.TrieDB != nil { + st.TrieDB.Close() + st.TrieDB = nil + } + if st.Snapshots != nil { + // Need to call Disable here to quit the snapshot generator goroutine. + st.Snapshots.Disable() + st.Snapshots.Release() + st.Snapshots = nil + } +} diff --git a/tests/testdata b/tests/testdata new file mode 160000 index 0000000..faf33b4 --- /dev/null +++ b/tests/testdata @@ -0,0 +1 @@ +Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2 diff --git a/tests/transaction_test.go b/tests/transaction_test.go new file mode 100644 index 0000000..cb0f262 --- /dev/null +++ b/tests/transaction_test.go @@ -0,0 +1,54 @@ +// 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 tests + +import ( + "testing" + + "github.com/ethereum/go-ethereum/params" +) + +func TestTransaction(t *testing.T) { + t.Parallel() + + txt := new(testMatcher) + // These can't be parsed, invalid hex in RLP + txt.skipLoad("^ttWrongRLP/.*") + // We don't allow more than uint64 in gas amount + // This is a pseudo-consensus vulnerability, but not in practice + // because of the gas limit + txt.skipLoad("^ttGasLimit/TransactionWithGasLimitxPriceOverflow.json") + // We _do_ allow more than uint64 in gas price, as opposed to the tests + // This is also not a concern, as long as tx.Cost() uses big.Int for + // calculating the final cozt + txt.skipLoad(".*TransactionWithGasPriceOverflow.*") + + // The nonce is too large for uint64. Not a concern, it means geth won't + // accept transactions at a certain point in the distant future + txt.skipLoad("^ttNonce/TransactionWithHighNonce256.json") + + // The value is larger than uint64, which according to the test is invalid. + // Geth accepts it, which is not a consensus issue since we use big.Int's + // internally to calculate the cost + txt.skipLoad("^ttValue/TransactionWithHighValueOverflow.json") + txt.walk(t, transactionTestDir, func(t *testing.T, name string, test *TransactionTest) { + cfg := params.MainnetChainConfig + if err := txt.checkFailure(t, test.Run(cfg)); err != nil { + t.Error(err) + } + }) +} diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go new file mode 100644 index 0000000..391aa57 --- /dev/null +++ b/tests/transaction_test_util.go @@ -0,0 +1,110 @@ +// 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 tests + +import ( + "fmt" + + "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/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// TransactionTest checks RLP decoding and sender derivation of transactions. +type TransactionTest struct { + RLP hexutil.Bytes `json:"rlp"` + Byzantium ttFork + Constantinople ttFork + Istanbul ttFork + EIP150 ttFork + EIP158 ttFork + Frontier ttFork + Homestead ttFork +} + +type ttFork struct { + Sender common.UnprefixedAddress `json:"sender"` + Hash common.UnprefixedHash `json:"hash"` +} + +func (tt *TransactionTest) Run(config *params.ChainConfig) error { + validateTx := func(rlpData hexutil.Bytes, signer types.Signer, isHomestead bool, isIstanbul bool) (*common.Address, *common.Hash, error) { + tx := new(types.Transaction) + if err := rlp.DecodeBytes(rlpData, tx); err != nil { + return nil, nil, err + } + sender, err := types.Sender(signer, tx) + if err != nil { + return nil, nil, err + } + // Intrinsic gas + requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false) + if err != nil { + return nil, nil, err + } + if requiredGas > tx.Gas() { + return nil, nil, fmt.Errorf("insufficient gas ( %d < %d )", tx.Gas(), requiredGas) + } + h := tx.Hash() + return &sender, &h, nil + } + + for _, testcase := range []struct { + name string + signer types.Signer + fork ttFork + isHomestead bool + isIstanbul bool + }{ + {"Frontier", types.FrontierSigner{}, tt.Frontier, false, false}, + {"Homestead", types.HomesteadSigner{}, tt.Homestead, true, false}, + {"EIP150", types.HomesteadSigner{}, tt.EIP150, true, false}, + {"EIP158", types.NewEIP155Signer(config.ChainID), tt.EIP158, true, false}, + {"Byzantium", types.NewEIP155Signer(config.ChainID), tt.Byzantium, true, false}, + {"Constantinople", types.NewEIP155Signer(config.ChainID), tt.Constantinople, true, false}, + {"Istanbul", types.NewEIP155Signer(config.ChainID), tt.Istanbul, true, true}, + } { + sender, txhash, err := validateTx(tt.RLP, testcase.signer, testcase.isHomestead, testcase.isIstanbul) + + if testcase.fork.Sender == (common.UnprefixedAddress{}) { + if err == nil { + return fmt.Errorf("expected error, got none (address %v)[%v]", sender.String(), testcase.name) + } + continue + } + // Should resolve the right address + if err != nil { + return fmt.Errorf("got error, expected none: %v", err) + } + if sender == nil { + return fmt.Errorf("sender was nil, should be %x", common.Address(testcase.fork.Sender)) + } + if *sender != common.Address(testcase.fork.Sender) { + return fmt.Errorf("sender mismatch: got %x, want %x", sender, testcase.fork.Sender) + } + if txhash == nil { + return fmt.Errorf("txhash was nil, should be %x", common.Hash(testcase.fork.Hash)) + } + if *txhash != common.Hash(testcase.fork.Hash) { + return fmt.Errorf("hash mismatch: got %x, want %x", *txhash, testcase.fork.Hash) + } + } + return nil +} diff --git a/trie/committer.go b/trie/committer.go new file mode 100644 index 0000000..4e2f7b8 --- /dev/null +++ b/trie/committer.go @@ -0,0 +1,182 @@ +// 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 ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +// committer is the tool used for the trie Commit operation. The committer will +// capture all dirty nodes during the commit process and keep them cached in +// insertion order. +type committer struct { + nodes *trienode.NodeSet + tracer *tracer + collectLeaf bool +} + +// newCommitter creates a new committer or picks one from the pool. +func newCommitter(nodeset *trienode.NodeSet, tracer *tracer, collectLeaf bool) *committer { + return &committer{ + nodes: nodeset, + tracer: tracer, + collectLeaf: collectLeaf, + } +} + +// Commit collapses a node down into a hash node. +func (c *committer) Commit(n node) hashNode { + return c.commit(nil, n).(hashNode) +} + +// commit collapses a node down into a hash node and returns it. +func (c *committer) commit(path []byte, n node) node { + // if this path is clean, use available cached data + hash, dirty := n.cache() + if hash != nil && !dirty { + return hash + } + // Commit children, then parent, and remove the dirty flag. + switch cn := n.(type) { + case *shortNode: + // Commit child + collapsed := cn.copy() + + // If the child is fullNode, recursively commit, + // otherwise it can only be hashNode or valueNode. + if _, ok := cn.Val.(*fullNode); ok { + collapsed.Val = c.commit(append(path, cn.Key...), cn.Val) + } + // The key needs to be copied, since we're adding it to the + // modified nodeset. + collapsed.Key = hexToCompact(cn.Key) + hashedNode := c.store(path, collapsed) + if hn, ok := hashedNode.(hashNode); ok { + return hn + } + return collapsed + case *fullNode: + hashedKids := c.commitChildren(path, cn) + collapsed := cn.copy() + collapsed.Children = hashedKids + + hashedNode := c.store(path, collapsed) + if hn, ok := hashedNode.(hashNode); ok { + return hn + } + return collapsed + case hashNode: + return cn + default: + // nil, valuenode shouldn't be committed + panic(fmt.Sprintf("%T: invalid node: %v", n, n)) + } +} + +// commitChildren commits the children of the given fullnode +func (c *committer) commitChildren(path []byte, n *fullNode) [17]node { + var children [17]node + for i := 0; i < 16; i++ { + child := n.Children[i] + if child == nil { + continue + } + // If it's the hashed child, save the hash value directly. + // Note: it's impossible that the child in range [0, 15] + // is a valueNode. + if hn, ok := child.(hashNode); ok { + children[i] = hn + continue + } + // Commit the child recursively and store the "hashed" value. + // Note the returned node can be some embedded nodes, so it's + // possible the type is not hashNode. + children[i] = c.commit(append(path, byte(i)), child) + } + // For the 17th child, it's possible the type is valuenode. + if n.Children[16] != nil { + children[16] = n.Children[16] + } + return children +} + +// store hashes the node n and adds it to the modified nodeset. If leaf collection +// is enabled, leaf nodes will be tracked in the modified nodeset as well. +func (c *committer) store(path []byte, n node) node { + // Larger nodes are replaced by their hash and stored in the database. + var hash, _ = n.cache() + + // This was not generated - must be a small node stored in the parent. + // In theory, we should check if the node is leaf here (embedded node + // usually is leaf node). But small value (less than 32bytes) is not + // our target (leaves in account trie only). + if hash == nil { + // The node is embedded in its parent, in other words, this node + // will not be stored in the database independently, mark it as + // deleted only if the node was existent in database before. + _, ok := c.tracer.accessList[string(path)] + if ok { + c.nodes.AddNode(path, trienode.NewDeleted()) + } + return n + } + // Collect the dirty node to nodeset for return. + nhash := common.BytesToHash(hash) + c.nodes.AddNode(path, trienode.New(nhash, nodeToBytes(n))) + + // Collect the corresponding leaf node if it's required. We don't check + // full node since it's impossible to store value in fullNode. The key + // length of leaves should be exactly same. + if c.collectLeaf { + if sn, ok := n.(*shortNode); ok { + if val, ok := sn.Val.(valueNode); ok { + c.nodes.AddLeaf(nhash, val) + } + } + } + return hash +} + +// MerkleResolver the children resolver in merkle-patricia-tree. +type MerkleResolver struct{} + +// ForEach implements childResolver, decodes the provided node and +// traverses the children inside. +func (resolver MerkleResolver) ForEach(node []byte, onChild func(common.Hash)) { + forGatherChildren(mustDecodeNodeUnsafe(nil, node), onChild) +} + +// forGatherChildren traverses the node hierarchy and invokes the callback +// for all the hashnode children. +func forGatherChildren(n node, onChild func(hash common.Hash)) { + switch n := n.(type) { + case *shortNode: + forGatherChildren(n.Val, onChild) + case *fullNode: + for i := 0; i < 16; i++ { + forGatherChildren(n.Children[i], onChild) + } + case hashNode: + onChild(common.BytesToHash(n)) + case valueNode, nil: + default: + panic(fmt.Sprintf("unknown node type: %T", n)) + } +} diff --git a/trie/database_test.go b/trie/database_test.go new file mode 100644 index 0000000..aed508b --- /dev/null +++ b/trie/database_test.go @@ -0,0 +1,152 @@ +// 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 ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb/database" +) + +// testReader implements database.Reader interface, providing function to +// access trie nodes. +type testReader struct { + db ethdb.Database + scheme string + nodes []*trienode.MergedNodeSet // sorted from new to old +} + +// Node implements database.Reader interface, retrieving trie node with +// all available cached layers. +func (r *testReader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { + // Check the node presence with the cached layer, from latest to oldest. + for _, nodes := range r.nodes { + if _, ok := nodes.Sets[owner]; !ok { + continue + } + n, ok := nodes.Sets[owner].Nodes[string(path)] + if !ok { + continue + } + if n.IsDeleted() || n.Hash != hash { + return nil, &MissingNodeError{Owner: owner, Path: path, NodeHash: hash} + } + return n.Blob, nil + } + // Check the node presence in database. + return rawdb.ReadTrieNode(r.db, owner, path, hash, r.scheme), nil +} + +// testDb implements database.Database interface, using for testing purpose. +type testDb struct { + disk ethdb.Database + root common.Hash + scheme string + nodes map[common.Hash]*trienode.MergedNodeSet + parents map[common.Hash]common.Hash +} + +func newTestDatabase(diskdb ethdb.Database, scheme string) *testDb { + return &testDb{ + disk: diskdb, + root: types.EmptyRootHash, + scheme: scheme, + nodes: make(map[common.Hash]*trienode.MergedNodeSet), + parents: make(map[common.Hash]common.Hash), + } +} + +func (db *testDb) Reader(stateRoot common.Hash) (database.Reader, error) { + nodes, _ := db.dirties(stateRoot, true) + return &testReader{db: db.disk, scheme: db.scheme, nodes: nodes}, nil +} + +func (db *testDb) Preimage(hash common.Hash) []byte { + return rawdb.ReadPreimage(db.disk, hash) +} + +func (db *testDb) InsertPreimage(preimages map[common.Hash][]byte) { + rawdb.WritePreimages(db.disk, preimages) +} + +func (db *testDb) Scheme() string { return db.scheme } + +func (db *testDb) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error { + if root == parent { + return nil + } + if _, ok := db.nodes[root]; ok { + return nil + } + db.parents[root] = parent + db.nodes[root] = nodes + return nil +} + +func (db *testDb) dirties(root common.Hash, topToBottom bool) ([]*trienode.MergedNodeSet, []common.Hash) { + var ( + pending []*trienode.MergedNodeSet + roots []common.Hash + ) + for { + if root == db.root { + break + } + nodes, ok := db.nodes[root] + if !ok { + break + } + if topToBottom { + pending = append(pending, nodes) + roots = append(roots, root) + } else { + pending = append([]*trienode.MergedNodeSet{nodes}, pending...) + roots = append([]common.Hash{root}, roots...) + } + root = db.parents[root] + } + return pending, roots +} + +func (db *testDb) Commit(root common.Hash) error { + if root == db.root { + return nil + } + pending, roots := db.dirties(root, false) + for i, nodes := range pending { + for owner, set := range nodes.Sets { + if owner == (common.Hash{}) { + continue + } + set.ForEachWithOrder(func(path string, n *trienode.Node) { + rawdb.WriteTrieNode(db.disk, owner, []byte(path), n.Hash, n.Blob, db.scheme) + }) + } + nodes.Sets[common.Hash{}].ForEachWithOrder(func(path string, n *trienode.Node) { + rawdb.WriteTrieNode(db.disk, common.Hash{}, []byte(path), n.Hash, n.Blob, db.scheme) + }) + db.root = roots[i] + } + for _, root := range roots { + delete(db.nodes, root) + delete(db.parents, root) + } + return nil +} diff --git a/trie/encoding.go b/trie/encoding.go new file mode 100644 index 0000000..3284d3f --- /dev/null +++ b/trie/encoding.go @@ -0,0 +1,144 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +// Trie keys are dealt with in three distinct encodings: +// +// KEYBYTES encoding contains the actual key and nothing else. This encoding is the +// input to most API functions. +// +// HEX encoding contains one byte for each nibble of the key and an optional trailing +// 'terminator' byte of value 0x10 which indicates whether or not the node at the key +// contains a value. Hex key encoding is used for nodes loaded in memory because it's +// convenient to access. +// +// COMPACT encoding is defined by the Ethereum Yellow Paper (it's called "hex prefix +// encoding" there) and contains the bytes of the key and a flag. The high nibble of the +// first byte contains the flag; the lowest bit encoding the oddness of the length and +// the second-lowest encoding whether the node at the key is a value node. The low nibble +// of the first byte is zero in the case of an even number of nibbles and the first nibble +// in the case of an odd number. All remaining nibbles (now an even number) fit properly +// into the remaining bytes. Compact encoding is used for nodes stored on disk. + +func hexToCompact(hex []byte) []byte { + terminator := byte(0) + if hasTerm(hex) { + terminator = 1 + hex = hex[:len(hex)-1] + } + buf := make([]byte, len(hex)/2+1) + buf[0] = terminator << 5 // the flag byte + if len(hex)&1 == 1 { + buf[0] |= 1 << 4 // odd flag + buf[0] |= hex[0] // first nibble is contained in the first byte + hex = hex[1:] + } + decodeNibbles(hex, buf[1:]) + return buf +} + +// 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) + ) + // Check if we have a terminator there + if hexLen > 0 && hex[hexLen-1] == 16 { + firstByte = 1 << 5 + hexLen-- // last part was the terminator, ignore that + } + var ( + binLen = hexLen/2 + 1 + ni = 0 // index in hex + bi = 1 // index in bin (compact) + ) + if hexLen&1 == 1 { + firstByte |= 1 << 4 // odd flag + firstByte |= hex[0] // first nibble is contained in the first byte + ni++ + } + for ; ni < hexLen; bi, ni = bi+1, ni+2 { + hex[bi] = hex[ni]<<4 | hex[ni+1] + } + hex[0] = firstByte + return hex[:binLen] +} + +func compactToHex(compact []byte) []byte { + if len(compact) == 0 { + return compact + } + base := keybytesToHex(compact) + // delete terminator flag + if base[0] < 2 { + base = base[:len(base)-1] + } + // apply odd flag + chop := 2 - base[0]&1 + return base[chop:] +} + +func keybytesToHex(str []byte) []byte { + l := len(str)*2 + 1 + var nibbles = make([]byte, l) + for i, b := range str { + nibbles[i*2] = b / 16 + nibbles[i*2+1] = b % 16 + } + nibbles[l-1] = 16 + return nibbles +} + +// hexToKeybytes turns hex nibbles into key bytes. +// This can only be used for keys of even length. +func hexToKeybytes(hex []byte) []byte { + if hasTerm(hex) { + hex = hex[:len(hex)-1] + } + if len(hex)&1 != 0 { + panic("can't convert hex key of odd length") + } + key := make([]byte, len(hex)/2) + decodeNibbles(hex, key) + return key +} + +func decodeNibbles(nibbles []byte, bytes []byte) { + for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 { + bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1] + } +} + +// prefixLen returns the length of the common prefix of a and b. +func prefixLen(a, b []byte) int { + var i, length = 0, len(a) + if len(b) < length { + length = len(b) + } + for ; i < length; i++ { + if a[i] != b[i] { + break + } + } + return i +} + +// hasTerm returns whether a hex key has the terminator flag. +func hasTerm(s []byte) bool { + return len(s) > 0 && s[len(s)-1] == 16 +} diff --git a/trie/encoding_test.go b/trie/encoding_test.go new file mode 100644 index 0000000..ac50b5d --- /dev/null +++ b/trie/encoding_test.go @@ -0,0 +1,146 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "bytes" + crand "crypto/rand" + "encoding/hex" + "math/rand" + "testing" +) + +func TestHexCompact(t *testing.T) { + tests := []struct{ hex, compact []byte }{ + // empty keys, with and without terminator. + {hex: []byte{}, compact: []byte{0x00}}, + {hex: []byte{16}, compact: []byte{0x20}}, + // odd length, no terminator + {hex: []byte{1, 2, 3, 4, 5}, compact: []byte{0x11, 0x23, 0x45}}, + // even length, no terminator + {hex: []byte{0, 1, 2, 3, 4, 5}, compact: []byte{0x00, 0x01, 0x23, 0x45}}, + // odd length, terminator + {hex: []byte{15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x3f, 0x1c, 0xb8}}, + // even length, terminator + {hex: []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x20, 0x0f, 0x1c, 0xb8}}, + } + for _, test := range tests { + if c := hexToCompact(test.hex); !bytes.Equal(c, test.compact) { + t.Errorf("hexToCompact(%x) -> %x, want %x", test.hex, c, test.compact) + } + if h := compactToHex(test.compact); !bytes.Equal(h, test.hex) { + t.Errorf("compactToHex(%x) -> %x, want %x", test.compact, h, test.hex) + } + } +} + +func TestHexKeybytes(t *testing.T) { + tests := []struct{ key, hexIn, hexOut []byte }{ + {key: []byte{}, hexIn: []byte{16}, hexOut: []byte{16}}, + {key: []byte{}, hexIn: []byte{}, hexOut: []byte{16}}, + { + key: []byte{0x12, 0x34, 0x56}, + hexIn: []byte{1, 2, 3, 4, 5, 6, 16}, + hexOut: []byte{1, 2, 3, 4, 5, 6, 16}, + }, + { + key: []byte{0x12, 0x34, 0x5}, + hexIn: []byte{1, 2, 3, 4, 0, 5, 16}, + hexOut: []byte{1, 2, 3, 4, 0, 5, 16}, + }, + { + key: []byte{0x12, 0x34, 0x56}, + hexIn: []byte{1, 2, 3, 4, 5, 6}, + hexOut: []byte{1, 2, 3, 4, 5, 6, 16}, + }, + } + for _, test := range tests { + if h := keybytesToHex(test.key); !bytes.Equal(h, test.hexOut) { + t.Errorf("keybytesToHex(%x) -> %x, want %x", test.key, h, test.hexOut) + } + if k := hexToKeybytes(test.hexIn); !bytes.Equal(k, test.key) { + t.Errorf("hexToKeybytes(%x) -> %x, want %x", test.hexIn, k, test.key) + } + } +} + +func TestHexToCompactInPlace(t *testing.T) { + for i, key := range []string{ + "00", + "060a040c0f000a090b040803010801010900080d090a0a0d0903000b10", + "10", + } { + hexBytes, _ := hex.DecodeString(key) + exp := hexToCompact(hexBytes) + 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) + } + } +} + +func TestHexToCompactInPlaceRandom(t *testing.T) { + for i := 0; i < 10000; i++ { + l := rand.Intn(128) + key := make([]byte, l) + crand.Read(key) + hexBytes := keybytesToHex(key) + hexOrig := []byte(string(hexBytes)) + exp := hexToCompact(hexBytes) + got := hexToCompactInPlace(hexBytes) + + if !bytes.Equal(exp, got) { + t.Fatalf("encoding err \ncpt %x\nhex %x\ngot %x\nexp %x\n", + key, hexOrig, got, exp) + } + } +} + +func BenchmarkHexToCompact(b *testing.B) { + testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/} + for i := 0; i < b.N; i++ { + hexToCompact(testBytes) + } +} + +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++ { + compactToHex(testBytes) + } +} + +func BenchmarkKeybytesToHex(b *testing.B) { + testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16} + for i := 0; i < b.N; i++ { + keybytesToHex(testBytes) + } +} + +func BenchmarkHexToKeybytes(b *testing.B) { + testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16} + for i := 0; i < b.N; i++ { + hexToKeybytes(testBytes) + } +} diff --git a/trie/errors.go b/trie/errors.go new file mode 100644 index 0000000..ce5cb13 --- /dev/null +++ b/trie/errors.go @@ -0,0 +1,52 @@ +// 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 trie + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// ErrCommitted is returned when an already committed trie is requested for usage. +// The potential usages can be `Get`, `Update`, `Delete`, `NodeIterator`, `Prove` +// and so on. +var ErrCommitted = errors.New("trie is already committed") + +// MissingNodeError is returned by the trie functions (Get, Update, Delete) +// in the case where a trie node is not present in the local database. It contains +// information necessary for retrieving the missing node. +type MissingNodeError struct { + Owner common.Hash // owner of the trie if it's 2-layered trie + NodeHash common.Hash // hash of the missing node + Path []byte // hex-encoded path to the missing node + err error // concrete error for missing trie node +} + +// Unwrap returns the concrete error for missing trie node which +// allows us for further analysis outside. +func (err *MissingNodeError) Unwrap() error { + return err.err +} + +func (err *MissingNodeError) Error() string { + if err.Owner == (common.Hash{}) { + return fmt.Sprintf("missing trie node %x (path %x) %v", err.NodeHash, err.Path, err.err) + } + return fmt.Sprintf("missing trie node %x (owner %x) (path %x) %v", err.NodeHash, err.Owner, err.Path, err.err) +} diff --git a/trie/hasher.go b/trie/hasher.go new file mode 100644 index 0000000..abf654c --- /dev/null +++ b/trie/hasher.go @@ -0,0 +1,207 @@ +// 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 trie + +import ( + "sync" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +// hasher is a type used for the trie Hash operation. A hasher has some +// internal preallocated temp space +type hasher struct { + sha crypto.KeccakState + tmp []byte + encbuf rlp.EncoderBuffer + parallel bool // Whether to use parallel threads when hashing +} + +// hasherPool holds pureHashers +var hasherPool = sync.Pool{ + New: func() interface{} { + return &hasher{ + tmp: make([]byte, 0, 550), // cap is as large as a full fullNode. + sha: crypto.NewKeccakState(), + encbuf: rlp.NewEncoderBuffer(nil), + } + }, +} + +func newHasher(parallel bool) *hasher { + h := hasherPool.Get().(*hasher) + h.parallel = parallel + return h +} + +func returnHasherToPool(h *hasher) { + hasherPool.Put(h) +} + +// hash collapses a node down into a hash node, also returning a copy of the +// original node initialized with the computed hash to replace the original one. +func (h *hasher) hash(n node, force bool) (hashed node, cached node) { + // Return the cached hash if it's available + if hash, _ := n.cache(); hash != nil { + return hash, n + } + // Trie not processed yet, walk the children + switch n := n.(type) { + case *shortNode: + collapsed, cached := h.hashShortNodeChildren(n) + hashed := h.shortnodeToHash(collapsed, force) + // We need to retain the possibly _not_ hashed node, in case it was too + // small to be hashed + if hn, ok := hashed.(hashNode); ok { + cached.flags.hash = hn + } else { + cached.flags.hash = nil + } + return hashed, cached + case *fullNode: + collapsed, cached := h.hashFullNodeChildren(n) + hashed = h.fullnodeToHash(collapsed, force) + if hn, ok := hashed.(hashNode); ok { + cached.flags.hash = hn + } else { + cached.flags.hash = nil + } + return hashed, cached + default: + // 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. +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) + collapsed.Key = hexToCompact(n.Key) + // Unless the child is a valuenode or hashnode, hash it + switch n.Val.(type) { + case *fullNode, *shortNode: + collapsed.Val, cached.Val = h.hash(n.Val, false) + } + return collapsed, cached +} + +func (h *hasher) hashFullNodeChildren(n *fullNode) (collapsed *fullNode, cached *fullNode) { + // Hash the full node's children, caching the newly hashed subtrees + cached = n.copy() + collapsed = n.copy() + if h.parallel { + var wg sync.WaitGroup + wg.Add(16) + for i := 0; i < 16; i++ { + go func(i int) { + hasher := newHasher(false) + if child := n.Children[i]; child != nil { + collapsed.Children[i], cached.Children[i] = hasher.hash(child, false) + } else { + collapsed.Children[i] = nilValueNode + } + returnHasherToPool(hasher) + wg.Done() + }(i) + } + wg.Wait() + } else { + for i := 0; i < 16; i++ { + if child := n.Children[i]; child != nil { + collapsed.Children[i], cached.Children[i] = h.hash(child, false) + } else { + collapsed.Children[i] = nilValueNode + } + } + } + return collapsed, cached +} + +// shortnodeToHash creates a hashNode from a shortNode. The supplied shortnode +// should have hex-type Key, which will be converted (without modification) +// into compact form for RLP encoding. +// If the rlp data is smaller than 32 bytes, `nil` is returned. +func (h *hasher) shortnodeToHash(n *shortNode, force bool) node { + n.encode(h.encbuf) + enc := h.encodedBytes() + + if len(enc) < 32 && !force { + return n // Nodes smaller than 32 bytes are stored inside their parent + } + return h.hashData(enc) +} + +// 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) + enc := h.encodedBytes() + + if len(enc) < 32 && !force { + return n // Nodes smaller than 32 bytes are stored inside their parent + } + return h.hashData(enc) +} + +// encodedBytes returns the result of the last encoding operation on h.encbuf. +// This also resets the encoder buffer. +// +// All node encoding must be done like this: +// +// node.encode(h.encbuf) +// enc := h.encodedBytes() +// +// This convention exists because node.encode can only be inlined/escape-analyzed when +// called on a concrete receiver type. +func (h *hasher) encodedBytes() []byte { + h.tmp = h.encbuf.AppendToBytes(h.tmp[:0]) + h.encbuf.Reset(nil) + return h.tmp +} + +// hashData hashes the provided data +func (h *hasher) hashData(data []byte) hashNode { + n := make(hashNode, 32) + h.sha.Reset() + h.sha.Write(data) + h.sha.Read(n) + return n +} + +// proofHash is used to construct trie proofs, and returns the 'collapsed' +// node (for later RLP encoding) as well as the hashed node -- unless the +// node is smaller than 32 bytes, in which case it will be returned as is. +// This method does not do anything on value- or hash-nodes. +func (h *hasher) proofHash(original node) (collapsed, hashed node) { + switch n := original.(type) { + case *shortNode: + sn, _ := h.hashShortNodeChildren(n) + return sn, h.shortnodeToHash(sn, false) + case *fullNode: + 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 + return n, n + } +} diff --git a/trie/iterator.go b/trie/iterator.go new file mode 100644 index 0000000..fa01611 --- /dev/null +++ b/trie/iterator.go @@ -0,0 +1,838 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "bytes" + "container/heap" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// NodeResolver is used for looking up trie nodes before reaching into the real +// persistent layer. This is not mandatory, rather is an optimization for cases +// where trie nodes can be recovered from some external mechanism without reading +// from disk. In those cases, this resolver allows short circuiting accesses and +// returning them from memory. +type NodeResolver func(owner common.Hash, path []byte, hash common.Hash) []byte + +// Iterator is a key-value trie iterator that traverses a Trie. +type Iterator struct { + nodeIt NodeIterator + + Key []byte // Current data key on which the iterator is positioned on + Value []byte // Current data value on which the iterator is positioned on + Err error +} + +// NewIterator creates a new key-value iterator from a node iterator. +// Note that the value returned by the iterator is raw. If the content is encoded +// (e.g. storage value is RLP-encoded), it's caller's duty to decode it. +func NewIterator(it NodeIterator) *Iterator { + return &Iterator{ + nodeIt: it, + } +} + +// Next moves the iterator forward one key-value entry. +func (it *Iterator) Next() bool { + for it.nodeIt.Next(true) { + if it.nodeIt.Leaf() { + it.Key = it.nodeIt.LeafKey() + it.Value = it.nodeIt.LeafBlob() + return true + } + } + it.Key = nil + it.Value = nil + it.Err = it.nodeIt.Error() + return false +} + +// Prove generates the Merkle proof for the leaf node the iterator is currently +// positioned on. +func (it *Iterator) Prove() [][]byte { + return it.nodeIt.LeafProof() +} + +// NodeIterator is an iterator to traverse the trie pre-order. +type NodeIterator interface { + // Next moves the iterator to the next node. If the parameter is false, any child + // nodes will be skipped. + Next(bool) bool + + // Error returns the error status of the iterator. + Error() error + + // Hash returns the hash of the current node. + Hash() common.Hash + + // Parent returns the hash of the parent of the current node. The hash may be the one + // grandparent if the immediate parent is an internal node with no hash. + Parent() common.Hash + + // Path returns the hex-encoded path to the current node. + // Callers must not retain references to the return value after calling Next. + // For leaf nodes, the last element of the path is the 'terminator symbol' 0x10. + Path() []byte + + // NodeBlob returns the rlp-encoded value of the current iterated node. + // If the node is an embedded node in its parent, nil is returned then. + NodeBlob() []byte + + // Leaf returns true iff the current node is a leaf node. + Leaf() bool + + // LeafKey returns the key of the leaf. The method panics if the iterator is not + // positioned at a leaf. Callers must not retain references to the value after + // calling Next. + LeafKey() []byte + + // LeafBlob returns the content of the leaf. The method panics if the iterator + // is not positioned at a leaf. Callers must not retain references to the value + // after calling Next. + LeafBlob() []byte + + // LeafProof returns the Merkle proof of the leaf. The method panics if the + // iterator is not positioned at a leaf. Callers must not retain references + // to the value after calling Next. + LeafProof() [][]byte + + // AddResolver sets a node resolver to use for looking up trie nodes before + // reaching into the real persistent layer. + // + // This is not required for normal operation, rather is an optimization for + // cases where trie nodes can be recovered from some external mechanism without + // reading from disk. In those cases, this resolver allows short circuiting + // accesses and returning them from memory. + // + // Before adding a similar mechanism to any other place in Geth, consider + // making trie.Database an interface and wrapping at that level. It's a huge + // refactor, but it could be worth it if another occurrence arises. + AddResolver(NodeResolver) +} + +// nodeIteratorState represents the iteration state at one particular node of the +// trie, which can be resumed at a later invocation. +type nodeIteratorState struct { + hash common.Hash // Hash of the node being iterated (nil if not standalone) + node node // Trie node being iterated + parent common.Hash // Hash of the first full ancestor node (nil if current is the root) + index int // Child to be processed next + pathlen int // Length of the path to the parent node +} + +type nodeIterator struct { + trie *Trie // Trie being iterated + stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state + 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 + pool []*nodeIteratorState // local pool for iterator states +} + +// errIteratorEnd is stored in nodeIterator.err when iteration is done. +var errIteratorEnd = errors.New("end of iteration") + +// seekError is stored in nodeIterator.err if the initial seek has failed. +type seekError struct { + key []byte + err error +} + +func (e seekError) Error() string { + return "seek error: " + e.err.Error() +} + +func newNodeIterator(trie *Trie, start []byte) NodeIterator { + if trie.Hash() == types.EmptyRootHash { + return &nodeIterator{ + trie: trie, + err: errIteratorEnd, + } + } + it := &nodeIterator{trie: trie} + it.err = it.seek(start) + 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 +} + +func (it *nodeIterator) Hash() common.Hash { + if len(it.stack) == 0 { + return common.Hash{} + } + return it.stack[len(it.stack)-1].hash +} + +func (it *nodeIterator) Parent() common.Hash { + if len(it.stack) == 0 { + return common.Hash{} + } + return it.stack[len(it.stack)-1].parent +} + +func (it *nodeIterator) Leaf() bool { + return hasTerm(it.path) +} + +func (it *nodeIterator) LeafKey() []byte { + if len(it.stack) > 0 { + if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { + return hexToKeybytes(it.path) + } + } + panic("not at leaf") +} + +func (it *nodeIterator) LeafBlob() []byte { + if len(it.stack) > 0 { + if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { + return node + } + } + panic("not at leaf") +} + +func (it *nodeIterator) LeafProof() [][]byte { + if len(it.stack) > 0 { + if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { + hasher := newHasher(false) + defer returnHasherToPool(hasher) + proofs := make([][]byte, 0, len(it.stack)) + + for i, item := range it.stack[:len(it.stack)-1] { + // Gather nodes that end up as hash nodes (or the root) + node, hashed := hasher.proofHash(item.node) + if _, ok := hashed.(hashNode); ok || i == 0 { + proofs = append(proofs, nodeToBytes(node)) + } + } + return proofs + } + } + panic("not at leaf") +} + +func (it *nodeIterator) Path() []byte { + return it.path +} + +func (it *nodeIterator) NodeBlob() []byte { + if it.Hash() == (common.Hash{}) { + return nil // skip the non-standalone node + } + blob, err := it.resolveBlob(it.Hash().Bytes(), it.Path()) + if err != nil { + it.err = err + return nil + } + return blob +} + +func (it *nodeIterator) Error() error { + if it.err == errIteratorEnd { + return nil + } + if seek, ok := it.err.(seekError); ok { + return seek.err + } + return it.err +} + +// Next moves the iterator to the next node, returning whether there are any +// further nodes. In case of an internal error this method returns false and +// sets the Error field to the encountered failure. If `descend` is false, +// skips iterating over any subnodes of the current node. +func (it *nodeIterator) Next(descend bool) bool { + if it.err == errIteratorEnd { + return false + } + if seek, ok := it.err.(seekError); ok { + if it.err = it.seek(seek.key); it.err != nil { + return false + } + } + // Otherwise step forward with the iterator and report any errors. + state, parentIndex, path, err := it.peek(descend) + it.err = err + if it.err != nil { + return false + } + it.push(state, parentIndex, path) + return true +} + +func (it *nodeIterator) seek(prefix []byte) error { + // The path we're looking for is the hex encoded key without terminator. + key := keybytesToHex(prefix) + key = key[:len(key)-1] + + // Move forward until we're just before the closest match to key. + for { + state, parentIndex, path, err := it.peekSeek(key) + if err == errIteratorEnd { + return errIteratorEnd + } else if err != nil { + return seekError{prefix, err} + } else if reachedPath(path, key) { + return nil + } + it.push(state, parentIndex, path) + } +} + +// init initializes the iterator. +func (it *nodeIterator) init() (*nodeIteratorState, error) { + root := it.trie.Hash() + state := &nodeIteratorState{node: it.trie.root, index: -1} + if root != types.EmptyRootHash { + state.hash = root + } + return state, state.resolve(it, nil) +} + +// peek creates the next state of the iterator. +func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, error) { + // Initialize the iterator if we've just started. + if len(it.stack) == 0 { + state, err := it.init() + return state, nil, nil, err + } + if !descend { + // If we're skipping children, pop the current node first + it.pop() + } + // Continue iteration to the next child + for len(it.stack) > 0 { + parent := it.stack[len(it.stack)-1] + ancestor := parent.hash + if (ancestor == common.Hash{}) { + ancestor = parent.parent + } + state, path, ok := it.nextChild(parent, ancestor) + if ok { + if err := state.resolve(it, path); err != nil { + return parent, &parent.index, path, err + } + return state, &parent.index, path, nil + } + // No more child nodes, move back up. + it.pop() + } + return nil, nil, nil, errIteratorEnd +} + +// peekSeek is like peek, but it also tries to skip resolving hashes by skipping +// over the siblings that do not lead towards the desired seek position. +func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []byte, error) { + // Initialize the iterator if we've just started. + if len(it.stack) == 0 { + state, err := it.init() + return state, nil, nil, err + } + if !bytes.HasPrefix(seekKey, it.path) { + // If we're skipping children, pop the current node first + it.pop() + } + // Continue iteration to the next child + for len(it.stack) > 0 { + parent := it.stack[len(it.stack)-1] + ancestor := parent.hash + if (ancestor == common.Hash{}) { + ancestor = parent.parent + } + state, path, ok := it.nextChildAt(parent, ancestor, seekKey) + if ok { + if err := state.resolve(it, path); err != nil { + return parent, &parent.index, path, err + } + return state, &parent.index, path, nil + } + // No more child nodes, move back up. + it.pop() + } + return nil, nil, nil, errIteratorEnd +} + +func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) { + if it.resolver != nil { + if blob := it.resolver(it.trie.owner, path, common.BytesToHash(hash)); len(blob) > 0 { + if resolved, err := decodeNode(hash, blob); err == nil { + return resolved, nil + } + } + } + // Retrieve the specified node from the underlying node reader. + // it.trie.resolveAndTrack is not used since in that function the + // loaded blob will be tracked, while it's not required here since + // all loaded nodes won't be linked to trie at all and track nodes + // may lead to out-of-memory issue. + blob, err := it.trie.reader.node(path, common.BytesToHash(hash)) + if err != nil { + return nil, err + } + // The raw-blob format nodes are loaded either from the + // clean cache or the database, they are all in their own + // copy and safe to use unsafe decoder. + return mustDecodeNodeUnsafe(hash, blob), nil +} + +func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error) { + if it.resolver != nil { + if blob := it.resolver(it.trie.owner, path, common.BytesToHash(hash)); len(blob) > 0 { + return blob, nil + } + } + // Retrieve the specified node from the underlying node reader. + // it.trie.resolveAndTrack is not used since in that function the + // loaded blob will be tracked, while it's not required here since + // all loaded nodes won't be linked to trie at all and track nodes + // may lead to out-of-memory issue. + return it.trie.reader.node(path, common.BytesToHash(hash)) +} + +func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error { + if hash, ok := st.node.(hashNode); ok { + resolved, err := it.resolveHash(hash, path) + if err != nil { + return err + } + st.node = resolved + st.hash = common.BytesToHash(hash) + } + return nil +} + +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 + ) + for ; index < len(n.Children); index = nextChildIndex(index) { + if n.Children[index] != nil { + child = n.Children[index] + hash, _ := child.cache() + + 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 + } + } + return nil, nil, nil, 0 +} + +func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Hash) (*nodeIteratorState, []byte, bool) { + switch node := parent.node.(type) { + case *fullNode: + // Full node, move to the first non-nil child. + if child, state, path, index := it.findChild(node, nextChildIndex(parent.index), ancestor); child != nil { + parent.index = prevChildIndex(index) + return state, path, true + } + case *shortNode: + // Short node, return the pointer singleton child + if parent.index < 0 { + hash, _ := node.Val.cache() + 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 + } + } + return parent, it.path, false +} + +// nextChildAt is similar to nextChild, except that it targets a child as close to the +// target key as possible, thus skipping siblings. +func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.Hash, key []byte) (*nodeIteratorState, []byte, bool) { + 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 := it.findChild(n, nextChildIndex(parent.index), ancestor) + if child == nil { + // No more children in this fullnode + return parent, it.path, false + } + // If the child we found is already past the seek position, just return it. + if reachedPath(path, key) { + parent.index = prevChildIndex(index) + return state, path, true + } + // The child is before the seek position. Try advancing + for { + nextChild, nextState, nextPath, nextIndex := it.findChild(n, nextChildIndex(index), ancestor) + // If we run out of children, or skipped past the target, return the + // previous one + if nextChild == nil || reachedPath(nextPath, key) { + parent.index = prevChildIndex(index) + return state, path, true + } + // We found a better child closer to the target + state, path, index = nextState, nextPath, nextIndex + } + case *shortNode: + // Short node, return the pointer singleton child + if parent.index < 0 { + hash, _ := n.Val.cache() + 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 + } + } + return parent, it.path, false +} + +func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) { + it.path = path + it.stack = append(it.stack, state) + if parentIndex != nil { + *parentIndex = nextChildIndex(*parentIndex) + } +} + +func (it *nodeIterator) pop() { + last := it.stack[len(it.stack)-1] + it.path = it.path[:last.pathlen] + it.stack[len(it.stack)-1] = nil + it.stack = it.stack[:len(it.stack)-1] + + it.putInPool(last) // last is now unused +} + +// reachedPath normalizes a path by truncating a terminator if present, and +// returns true if it is greater than or equal to the target. Using this, +// the path of a value node embedded a full node will compare less than the +// full node's children. +func reachedPath(path, target []byte) bool { + if hasTerm(path) { + path = path[:len(path)-1] + } + return bytes.Compare(path, target) >= 0 +} + +// A value embedded in a full node occupies the last slot (16) of the array of +// children. In order to produce a pre-order traversal when iterating children, +// we jump to this last slot first, then go back iterate the child nodes (and +// skip the last slot at the end): + +// prevChildIndex returns the index of a child in a full node which precedes +// the given index when performing a pre-order traversal. +func prevChildIndex(index int) int { + switch index { + case 0: // We jumped back to iterate the children, from the value slot + return 16 + case 16: // We jumped to the embedded value slot at the end, from the placeholder index + return -1 + case 17: // We skipped the value slot after iterating all the children + return 15 + default: // We are iterating the children in sequence + return index - 1 + } +} + +// nextChildIndex returns the index of a child in a full node which follows +// the given index when performing a pre-order traversal. +func nextChildIndex(index int) int { + switch index { + case -1: // Jump from the placeholder index to the embedded value slot + return 16 + case 15: // Skip the value slot after iterating the children + return 17 + case 16: // From the embedded value slot, jump back to iterate the children + return 0 + default: // Iterate children in sequence + return index + 1 + } +} + +func compareNodes(a, b NodeIterator) int { + if cmp := bytes.Compare(a.Path(), b.Path()); cmp != 0 { + return cmp + } + if a.Leaf() && !b.Leaf() { + return -1 + } else if b.Leaf() && !a.Leaf() { + return 1 + } + if cmp := bytes.Compare(a.Hash().Bytes(), b.Hash().Bytes()); cmp != 0 { + return cmp + } + if a.Leaf() && b.Leaf() { + return bytes.Compare(a.LeafBlob(), b.LeafBlob()) + } + return 0 +} + +type differenceIterator struct { + a, b NodeIterator // Nodes returned are those in b - a. + eof bool // Indicates a has run out of elements + count int // Number of nodes scanned on either trie +} + +// NewDifferenceIterator constructs a NodeIterator that iterates over elements in b that +// are not in a. Returns the iterator, and a pointer to an integer recording the number +// of nodes seen. +func NewDifferenceIterator(a, b NodeIterator) (NodeIterator, *int) { + a.Next(true) + it := &differenceIterator{ + a: a, + b: b, + } + return it, &it.count +} + +func (it *differenceIterator) Hash() common.Hash { + return it.b.Hash() +} + +func (it *differenceIterator) Parent() common.Hash { + return it.b.Parent() +} + +func (it *differenceIterator) Leaf() bool { + return it.b.Leaf() +} + +func (it *differenceIterator) LeafKey() []byte { + return it.b.LeafKey() +} + +func (it *differenceIterator) LeafBlob() []byte { + return it.b.LeafBlob() +} + +func (it *differenceIterator) LeafProof() [][]byte { + return it.b.LeafProof() +} + +func (it *differenceIterator) Path() []byte { + return it.b.Path() +} + +func (it *differenceIterator) NodeBlob() []byte { + return it.b.NodeBlob() +} + +func (it *differenceIterator) AddResolver(resolver NodeResolver) { + panic("not implemented") +} + +func (it *differenceIterator) Next(bool) bool { + // Invariants: + // - We always advance at least one element in b. + // - At the start of this function, a's path is lexically greater than b's. + if !it.b.Next(true) { + return false + } + it.count++ + + if it.eof { + // a has reached eof, so we just return all elements from b + return true + } + + for { + switch compareNodes(it.a, it.b) { + case -1: + // b jumped past a; advance a + if !it.a.Next(true) { + it.eof = true + return true + } + it.count++ + case 1: + // b is before a + return true + case 0: + // a and b are identical; skip this whole subtree if the nodes have hashes + hasHash := it.a.Hash() == common.Hash{} + if !it.b.Next(hasHash) { + return false + } + it.count++ + if !it.a.Next(hasHash) { + it.eof = true + return true + } + it.count++ + } + } +} + +func (it *differenceIterator) Error() error { + if err := it.a.Error(); err != nil { + return err + } + return it.b.Error() +} + +type nodeIteratorHeap []NodeIterator + +func (h nodeIteratorHeap) Len() int { return len(h) } +func (h nodeIteratorHeap) Less(i, j int) bool { return compareNodes(h[i], h[j]) < 0 } +func (h nodeIteratorHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h *nodeIteratorHeap) Push(x interface{}) { *h = append(*h, x.(NodeIterator)) } +func (h *nodeIteratorHeap) Pop() interface{} { + n := len(*h) + x := (*h)[n-1] + *h = (*h)[0 : n-1] + return x +} + +type unionIterator struct { + items *nodeIteratorHeap // Nodes returned are the union of the ones in these iterators + count int // Number of nodes scanned across all tries +} + +// NewUnionIterator constructs a NodeIterator that iterates over elements in the union +// of the provided NodeIterators. Returns the iterator, and a pointer to an integer +// recording the number of nodes visited. +func NewUnionIterator(iters []NodeIterator) (NodeIterator, *int) { + h := make(nodeIteratorHeap, len(iters)) + copy(h, iters) + heap.Init(&h) + + ui := &unionIterator{items: &h} + return ui, &ui.count +} + +func (it *unionIterator) Hash() common.Hash { + return (*it.items)[0].Hash() +} + +func (it *unionIterator) Parent() common.Hash { + return (*it.items)[0].Parent() +} + +func (it *unionIterator) Leaf() bool { + return (*it.items)[0].Leaf() +} + +func (it *unionIterator) LeafKey() []byte { + return (*it.items)[0].LeafKey() +} + +func (it *unionIterator) LeafBlob() []byte { + return (*it.items)[0].LeafBlob() +} + +func (it *unionIterator) LeafProof() [][]byte { + return (*it.items)[0].LeafProof() +} + +func (it *unionIterator) Path() []byte { + return (*it.items)[0].Path() +} + +func (it *unionIterator) NodeBlob() []byte { + return (*it.items)[0].NodeBlob() +} + +func (it *unionIterator) AddResolver(resolver NodeResolver) { + panic("not implemented") +} + +// Next returns the next node in the union of tries being iterated over. +// +// It does this by maintaining a heap of iterators, sorted by the iteration +// order of their next elements, with one entry for each source trie. Each +// time Next() is called, it takes the least element from the heap to return, +// advancing any other iterators that also point to that same element. These +// iterators are called with descend=false, since we know that any nodes under +// these nodes will also be duplicates, found in the currently selected iterator. +// Whenever an iterator is advanced, it is pushed back into the heap if it still +// has elements remaining. +// +// In the case that descend=false - eg, we're asked to ignore all subnodes of the +// current node - we also advance any iterators in the heap that have the current +// path as a prefix. +func (it *unionIterator) Next(descend bool) bool { + if len(*it.items) == 0 { + return false + } + + // Get the next key from the union + least := heap.Pop(it.items).(NodeIterator) + + // Skip over other nodes as long as they're identical, or, if we're not descending, as + // long as they have the same prefix as the current node. + for len(*it.items) > 0 && ((!descend && bytes.HasPrefix((*it.items)[0].Path(), least.Path())) || compareNodes(least, (*it.items)[0]) == 0) { + skipped := heap.Pop(it.items).(NodeIterator) + // Skip the whole subtree if the nodes have hashes; otherwise just skip this node + if skipped.Next(skipped.Hash() == common.Hash{}) { + it.count++ + // If there are more elements, push the iterator back on the heap + heap.Push(it.items, skipped) + } + } + if least.Next(descend) { + it.count++ + heap.Push(it.items, least) + } + return len(*it.items) > 0 +} + +func (it *unionIterator) Error() error { + for i := 0; i < len(*it.items); i++ { + if err := (*it.items)[i].Error(); err != nil { + return err + } + } + return nil +} diff --git a/trie/iterator_test.go b/trie/iterator_test.go new file mode 100644 index 0000000..b463294 --- /dev/null +++ b/trie/iterator_test.go @@ -0,0 +1,637 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "bytes" + "fmt" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +func TestEmptyIterator(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + iter := trie.MustNodeIterator(nil) + + seen := make(map[string]struct{}) + for iter.Next(true) { + seen[string(iter.Path())] = struct{}{} + } + if len(seen) != 0 { + t.Fatal("Unexpected trie node iterated") + } +} + +func TestIterator(t *testing.T) { + db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie := NewEmpty(db) + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"dog", "puppy"}, + {"somethingveryoddindeedthis is", "myothernodedata"}, + } + all := make(map[string]string) + for _, val := range vals { + all[val.k] = val.v + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } + root, nodes := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + + trie, _ = New(TrieID(root), db) + found := make(map[string]string) + it := NewIterator(trie.MustNodeIterator(nil)) + for it.Next() { + found[string(it.Key)] = string(it.Value) + } + + for k, v := range all { + if found[k] != v { + t.Errorf("iterator value mismatch for %s: got %q want %q", k, found[k], v) + } + } +} + +type kv struct { + k, v []byte + t bool +} + +func (k *kv) cmp(other *kv) int { + return bytes.Compare(k.k, other.k) +} + +func TestIteratorLargeData(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + vals := make(map[string]*kv) + + for i := byte(0); i < 255; i++ { + value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + value2 := &kv{common.LeftPadBytes([]byte{10, i}, 32), []byte{i}, false} + trie.MustUpdate(value.k, value.v) + trie.MustUpdate(value2.k, value2.v) + vals[string(value.k)] = value + vals[string(value2.k)] = value2 + } + + it := NewIterator(trie.MustNodeIterator(nil)) + for it.Next() { + vals[string(it.Key)].t = true + } + + var untouched []*kv + for _, value := range vals { + if !value.t { + untouched = append(untouched, value) + } + } + + if len(untouched) > 0 { + t.Errorf("Missed %d nodes", len(untouched)) + for _, value := range untouched { + t.Error(value) + } + } +} + +type iterationElement struct { + hash common.Hash + path []byte + blob []byte +} + +// 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 trie to iterate + db, nodeDb, trie, _ := makeTestTrie(scheme) + + // Gather all the node hashes found by the iterator + var elements = make(map[common.Hash]iterationElement) + for it := trie.MustNodeIterator(nil); it.Next(true); { + if it.Hash() != (common.Hash{}) { + elements[it.Hash()] = iterationElement{ + hash: it.Hash(), + path: common.CopyBytes(it.Path()), + blob: common.CopyBytes(it.NodeBlob()), + } + } + } + // Cross check the hashes and the database itself + reader, err := nodeDb.Reader(trie.Hash()) + if err != nil { + t.Fatalf("state is not available %x", trie.Hash()) + } + for _, element := range elements { + if blob, err := reader.Node(common.Hash{}, element.path, element.hash); err != nil { + t.Errorf("failed to retrieve reported node %x: %v", element.hash, err) + } else if !bytes.Equal(blob, element.blob) { + t.Errorf("node blob is different, want %v got %v", element.blob, blob) + } + } + var ( + count int + it = db.NewIterator(nil, nil) + ) + for it.Next() { + res, _, _ := isTrieNode(nodeDb.Scheme(), it.Key(), it.Value()) + if !res { + continue + } + count += 1 + if elem, ok := elements[crypto.Keccak256Hash(it.Value())]; !ok { + t.Error("state entry not reported") + } else if !bytes.Equal(it.Value(), elem.blob) { + t.Errorf("node blob is different, want %v got %v", elem.blob, it.Value()) + } + } + it.Release() + if count != len(elements) { + t.Errorf("state entry is mismatched %d %d", count, len(elements)) + } +} + +type kvs struct{ k, v string } + +var testdata1 = []kvs{ + {"bar", "b"}, + {"barb", "ba"}, + {"bard", "bc"}, + {"bars", "bb"}, + {"fab", "z"}, + {"foo", "a"}, + {"food", "ab"}, + {"foos", "aa"}, +} + +var testdata2 = []kvs{ + {"aardvark", "c"}, + {"bar", "b"}, + {"barb", "bd"}, + {"bars", "be"}, + {"fab", "z"}, + {"foo", "a"}, + {"foos", "aa"}, + {"food", "ab"}, + {"jars", "d"}, +} + +func TestIteratorSeek(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + for _, val := range testdata1 { + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } + + // Seek to the middle. + it := NewIterator(trie.MustNodeIterator([]byte("fab"))) + if err := checkIteratorOrder(testdata1[4:], it); err != nil { + t.Fatal(err) + } + + // Seek to a non-existent key. + it = NewIterator(trie.MustNodeIterator([]byte("barc"))) + if err := checkIteratorOrder(testdata1[2:], it); err != nil { + t.Fatal(err) + } + + // Seek beyond the end. + it = NewIterator(trie.MustNodeIterator([]byte("z"))) + if err := checkIteratorOrder(nil, it); err != nil { + t.Fatal(err) + } + + // Seek to a key for which a prefixing key exists. + it = NewIterator(trie.MustNodeIterator([]byte("food"))) + if err := checkIteratorOrder(testdata1[6:], it); err != nil { + t.Fatal(err) + } +} + +func checkIteratorOrder(want []kvs, it *Iterator) error { + for it.Next() { + if len(want) == 0 { + return fmt.Errorf("didn't expect any more values, got key %q", it.Key) + } + if !bytes.Equal(it.Key, []byte(want[0].k)) { + return fmt.Errorf("wrong key: got %q, want %q", it.Key, want[0].k) + } + want = want[1:] + } + if len(want) > 0 { + return fmt.Errorf("iterator ended early, want key %q", want[0]) + } + return nil +} + +func TestDifferenceIterator(t *testing.T) { + dba := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + triea := NewEmpty(dba) + for _, val := range testdata1 { + triea.MustUpdate([]byte(val.k), []byte(val.v)) + } + rootA, nodesA := triea.Commit(false) + dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA)) + triea, _ = New(TrieID(rootA), dba) + + dbb := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trieb := NewEmpty(dbb) + for _, val := range testdata2 { + trieb.MustUpdate([]byte(val.k), []byte(val.v)) + } + rootB, nodesB := trieb.Commit(false) + dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB)) + trieb, _ = New(TrieID(rootB), dbb) + + found := make(map[string]string) + di, _ := NewDifferenceIterator(triea.MustNodeIterator(nil), trieb.MustNodeIterator(nil)) + it := NewIterator(di) + for it.Next() { + found[string(it.Key)] = string(it.Value) + } + + all := []struct{ k, v string }{ + {"aardvark", "c"}, + {"barb", "bd"}, + {"bars", "be"}, + {"jars", "d"}, + } + for _, item := range all { + if found[item.k] != item.v { + t.Errorf("iterator value mismatch for %s: got %v want %v", item.k, found[item.k], item.v) + } + } + if len(found) != len(all) { + t.Errorf("iterator count mismatch: got %d values, want %d", len(found), len(all)) + } +} + +func TestUnionIterator(t *testing.T) { + dba := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + triea := NewEmpty(dba) + for _, val := range testdata1 { + triea.MustUpdate([]byte(val.k), []byte(val.v)) + } + rootA, nodesA := triea.Commit(false) + dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA)) + triea, _ = New(TrieID(rootA), dba) + + dbb := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trieb := NewEmpty(dbb) + for _, val := range testdata2 { + trieb.MustUpdate([]byte(val.k), []byte(val.v)) + } + rootB, nodesB := trieb.Commit(false) + dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB)) + trieb, _ = New(TrieID(rootB), dbb) + + di, _ := NewUnionIterator([]NodeIterator{triea.MustNodeIterator(nil), trieb.MustNodeIterator(nil)}) + it := NewIterator(di) + + all := []struct{ k, v string }{ + {"aardvark", "c"}, + {"bar", "b"}, + {"barb", "ba"}, + {"barb", "bd"}, + {"bard", "bc"}, + {"bars", "bb"}, + {"bars", "be"}, + {"fab", "z"}, + {"foo", "a"}, + {"food", "ab"}, + {"foos", "aa"}, + {"jars", "d"}, + } + + for i, kv := range all { + if !it.Next() { + t.Errorf("Iterator ends prematurely at element %d", i) + } + if kv.k != string(it.Key) { + t.Errorf("iterator value mismatch for element %d: got key %s want %s", i, it.Key, kv.k) + } + if kv.v != string(it.Value) { + t.Errorf("iterator value mismatch for element %d: got value %s want %s", i, it.Value, kv.v) + } + } + if it.Next() { + t.Errorf("Iterator returned extra values.") + } +} + +func TestIteratorNoDups(t *testing.T) { + db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + tr := NewEmpty(db) + for _, val := range testdata1 { + tr.MustUpdate([]byte(val.k), []byte(val.v)) + } + checkIteratorNoDups(t, tr.MustNodeIterator(nil), nil) +} + +// This test checks that nodeIterator.Next can be retried after inserting missing trie nodes. +func TestIteratorContinueAfterError(t *testing.T) { + testIteratorContinueAfterError(t, false, rawdb.HashScheme) + testIteratorContinueAfterError(t, true, rawdb.HashScheme) + testIteratorContinueAfterError(t, false, rawdb.PathScheme) + testIteratorContinueAfterError(t, true, rawdb.PathScheme) +} + +func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) { + diskdb := rawdb.NewMemoryDatabase() + tdb := newTestDatabase(diskdb, scheme) + + tr := NewEmpty(tdb) + for _, val := range testdata1 { + tr.MustUpdate([]byte(val.k), []byte(val.v)) + } + root, nodes := tr.Commit(false) + tdb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + if !memonly { + tdb.Commit(root) + } + tr, _ = New(TrieID(root), tdb) + wantNodeCount := checkIteratorNoDups(t, tr.MustNodeIterator(nil), nil) + + var ( + paths [][]byte + hashes []common.Hash + ) + if memonly { + for path, n := range nodes.Nodes { + paths = append(paths, []byte(path)) + hashes = append(hashes, n.Hash) + } + } else { + it := diskdb.NewIterator(nil, nil) + for it.Next() { + ok, path, hash := isTrieNode(tdb.Scheme(), it.Key(), it.Value()) + if !ok { + continue + } + paths = append(paths, path) + hashes = append(hashes, hash) + } + it.Release() + } + for i := 0; i < 20; i++ { + // Create trie that will load all nodes from DB. + tr, _ := New(TrieID(tr.Hash()), tdb) + + // Remove a random node from the database. It can't be the root node + // because that one is already loaded. + var ( + rval []byte + rpath []byte + rhash common.Hash + ) + for { + if memonly { + rpath = paths[rand.Intn(len(paths))] + n := nodes.Nodes[string(rpath)] + if n == nil { + continue + } + rhash = n.Hash + } else { + index := rand.Intn(len(paths)) + rpath = paths[index] + rhash = hashes[index] + } + if rhash != tr.Hash() { + break + } + } + if memonly { + tr.reader.banned = map[string]struct{}{string(rpath): {}} + } else { + rval = rawdb.ReadTrieNode(diskdb, common.Hash{}, rpath, rhash, tdb.Scheme()) + rawdb.DeleteTrieNode(diskdb, common.Hash{}, rpath, rhash, tdb.Scheme()) + } + // Iterate until the error is hit. + seen := make(map[string]bool) + it := tr.MustNodeIterator(nil) + checkIteratorNoDups(t, it, seen) + missing, ok := it.Error().(*MissingNodeError) + if !ok || missing.NodeHash != rhash { + t.Fatal("didn't hit missing node, got", it.Error()) + } + + // Add the node back and continue iteration. + if memonly { + delete(tr.reader.banned, string(rpath)) + } else { + rawdb.WriteTrieNode(diskdb, common.Hash{}, rpath, rhash, rval, tdb.Scheme()) + } + checkIteratorNoDups(t, it, seen) + if it.Error() != nil { + t.Fatal("unexpected error", it.Error()) + } + if len(seen) != wantNodeCount { + t.Fatal("wrong node iteration count, got", len(seen), "want", wantNodeCount) + } + } +} + +// Similar to the test above, this one checks that failure to create nodeIterator at a +// certain key prefix behaves correctly when Next is called. The expectation is that Next +// should retry seeking before returning true for the first time. +func TestIteratorContinueAfterSeekError(t *testing.T) { + testIteratorContinueAfterSeekError(t, false, rawdb.HashScheme) + testIteratorContinueAfterSeekError(t, true, rawdb.HashScheme) + testIteratorContinueAfterSeekError(t, false, rawdb.PathScheme) + testIteratorContinueAfterSeekError(t, true, rawdb.PathScheme) +} + +func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme string) { + // Commit test trie to db, then remove the node containing "bars". + var ( + barNodePath []byte + barNodeHash = common.HexToHash("05041990364eb72fcb1127652ce40d8bab765f2bfe53225b1170d276cc101c2e") + ) + diskdb := rawdb.NewMemoryDatabase() + triedb := newTestDatabase(diskdb, scheme) + ctr := NewEmpty(triedb) + for _, val := range testdata1 { + ctr.MustUpdate([]byte(val.k), []byte(val.v)) + } + root, nodes := ctr.Commit(false) + for path, n := range nodes.Nodes { + if n.Hash == barNodeHash { + barNodePath = []byte(path) + break + } + } + triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + if !memonly { + triedb.Commit(root) + } + var ( + barNodeBlob []byte + ) + tr, _ := New(TrieID(root), triedb) + if memonly { + tr.reader.banned = map[string]struct{}{string(barNodePath): {}} + } else { + barNodeBlob = rawdb.ReadTrieNode(diskdb, common.Hash{}, barNodePath, barNodeHash, triedb.Scheme()) + rawdb.DeleteTrieNode(diskdb, common.Hash{}, barNodePath, barNodeHash, triedb.Scheme()) + } + // Create a new iterator that seeks to "bars". Seeking can't proceed because + // the node is missing. + it := tr.MustNodeIterator([]byte("bars")) + missing, ok := it.Error().(*MissingNodeError) + if !ok { + t.Fatal("want MissingNodeError, got", it.Error()) + } else if missing.NodeHash != barNodeHash { + t.Fatal("wrong node missing") + } + // Reinsert the missing node. + if memonly { + delete(tr.reader.banned, string(barNodePath)) + } else { + rawdb.WriteTrieNode(diskdb, common.Hash{}, barNodePath, barNodeHash, barNodeBlob, triedb.Scheme()) + } + // Check that iteration produces the right set of values. + if err := checkIteratorOrder(testdata1[3:], NewIterator(it)); err != nil { + t.Fatal(err) + } +} + +func checkIteratorNoDups(t *testing.T, it NodeIterator, seen map[string]bool) int { + if seen == nil { + seen = make(map[string]bool) + } + for it.Next(true) { + if seen[string(it.Path())] { + t.Fatalf("iterator visited node path %x twice", it.Path()) + } + seen[string(it.Path())] = true + } + return len(seen) +} + +func TestIteratorNodeBlob(t *testing.T) { + testIteratorNodeBlob(t, rawdb.HashScheme) + testIteratorNodeBlob(t, rawdb.PathScheme) +} + +func testIteratorNodeBlob(t *testing.T, scheme string) { + var ( + db = rawdb.NewMemoryDatabase() + triedb = newTestDatabase(db, scheme) + trie = NewEmpty(triedb) + ) + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"dog", "puppy"}, + {"somethingveryoddindeedthis is", "myothernodedata"}, + } + all := make(map[string]string) + for _, val := range vals { + all[val.k] = val.v + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } + root, nodes := trie.Commit(false) + triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + triedb.Commit(root) + + var found = make(map[common.Hash][]byte) + trie, _ = New(TrieID(root), triedb) + it := trie.MustNodeIterator(nil) + for it.Next(true) { + if it.Hash() == (common.Hash{}) { + continue + } + found[it.Hash()] = it.NodeBlob() + } + + dbIter := db.NewIterator(nil, nil) + defer dbIter.Release() + + var count int + for dbIter.Next() { + ok, _, _ := isTrieNode(triedb.Scheme(), dbIter.Key(), dbIter.Value()) + if !ok { + continue + } + got, present := found[crypto.Keccak256Hash(dbIter.Value())] + if !present { + t.Fatal("Miss trie node") + } + if !bytes.Equal(got, dbIter.Value()) { + t.Fatalf("Unexpected trie node want %v got %v", dbIter.Value(), got) + } + count += 1 + } + if count != len(found) { + t.Fatal("Find extra trie node via iterator") + } +} + +// isTrieNode is a helper function which reports if the provided +// database entry belongs to a trie node or not. Note in tests +// only single layer trie is used, namely storage trie is not +// considered at all. +func isTrieNode(scheme string, key, val []byte) (bool, []byte, common.Hash) { + var ( + path []byte + hash common.Hash + ) + if scheme == rawdb.HashScheme { + ok := rawdb.IsLegacyTrieNode(key, val) + if !ok { + return false, nil, common.Hash{} + } + hash = common.BytesToHash(key) + } else { + ok, remain := rawdb.ResolveAccountTrieNodeKey(key) + if !ok { + return false, nil, common.Hash{} + } + path = common.CopyBytes(remain) + hash = crypto.Keccak256Hash(val) + } + 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/node.go b/trie/node.go new file mode 100644 index 0000000..15bbf62 --- /dev/null +++ b/trie/node.go @@ -0,0 +1,254 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "fmt" + "io" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "[17]"} + +type node interface { + cache() (hashNode, bool) + encode(w rlp.EncoderBuffer) + fstring(string) string +} + +type ( + fullNode struct { + Children [17]node // Actual trie node data to encode/decode (needs custom encoder) + flags nodeFlag + } + shortNode struct { + Key []byte + Val node + flags nodeFlag + } + hashNode []byte + valueNode []byte +) + +// nilValueNode is used when collapsing internal trie nodes for hashing, since +// unset children need to serialize correctly. +var nilValueNode = valueNode(nil) + +// EncodeRLP encodes a full node into the consensus RLP format. +func (n *fullNode) EncodeRLP(w io.Writer) error { + eb := rlp.NewEncoderBuffer(w) + n.encode(eb) + return eb.Flush() +} + +func (n *fullNode) copy() *fullNode { copy := *n; return © } +func (n *shortNode) copy() *shortNode { copy := *n; return © } + +// nodeFlag contains caching-related metadata about a node. +type nodeFlag struct { + hash hashNode // cached hash of the node (may be nil) + dirty bool // whether the node has changes that must be written to the database +} + +func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } +func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } +func (n hashNode) cache() (hashNode, bool) { return nil, true } +func (n valueNode) cache() (hashNode, bool) { return nil, true } + +// Pretty printing. +func (n *fullNode) String() string { return n.fstring("") } +func (n *shortNode) String() string { return n.fstring("") } +func (n hashNode) String() string { return n.fstring("") } +func (n valueNode) String() string { return n.fstring("") } + +func (n *fullNode) fstring(ind string) string { + resp := fmt.Sprintf("[\n%s ", ind) + for i, node := range &n.Children { + if node == nil { + resp += fmt.Sprintf("%s: ", indices[i]) + } else { + resp += fmt.Sprintf("%s: %v", indices[i], node.fstring(ind+" ")) + } + } + return resp + fmt.Sprintf("\n%s] ", ind) +} +func (n *shortNode) fstring(ind string) string { + return fmt.Sprintf("{%x: %v} ", n.Key, n.Val.fstring(ind+" ")) +} +func (n hashNode) fstring(ind string) string { + return fmt.Sprintf("<%x> ", []byte(n)) +} +func (n valueNode) fstring(ind string) string { + return fmt.Sprintf("%x ", []byte(n)) +} + +// rawNode is a simple binary blob used to differentiate between collapsed trie +// nodes and already encoded RLP binary blobs (while at the same time store them +// in the same cache fields). +type rawNode []byte + +func (n rawNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") } +func (n rawNode) fstring(ind string) string { panic("this should never end up in a live trie") } + +func (n rawNode) EncodeRLP(w io.Writer) error { + _, err := w.Write(n) + return err +} + +// mustDecodeNode is a wrapper of decodeNode and panic if any error is encountered. +func mustDecodeNode(hash, buf []byte) node { + n, err := decodeNode(hash, buf) + if err != nil { + panic(fmt.Sprintf("node %x: %v", hash, err)) + } + return n +} + +// mustDecodeNodeUnsafe is a wrapper of decodeNodeUnsafe and panic if any error is +// encountered. +func mustDecodeNodeUnsafe(hash, buf []byte) node { + n, err := decodeNodeUnsafe(hash, buf) + if err != nil { + panic(fmt.Sprintf("node %x: %v", hash, err)) + } + return n +} + +// decodeNode parses the RLP encoding of a trie node. It will deep-copy the passed +// byte slice for decoding, so it's safe to modify the byte slice afterwards. The- +// decode performance of this function is not optimal, but it is suitable for most +// scenarios with low performance requirements and hard to determine whether the +// byte slice be modified or not. +func decodeNode(hash, buf []byte) (node, error) { + return decodeNodeUnsafe(hash, common.CopyBytes(buf)) +} + +// decodeNodeUnsafe parses the RLP encoding of a trie node. The passed byte slice +// will be directly referenced by node without bytes deep copy, so the input MUST +// not be changed after. +func decodeNodeUnsafe(hash, buf []byte) (node, error) { + if len(buf) == 0 { + return nil, io.ErrUnexpectedEOF + } + elems, _, err := rlp.SplitList(buf) + if err != nil { + return nil, fmt.Errorf("decode error: %v", err) + } + switch c, _ := rlp.CountValues(elems); c { + case 2: + n, err := decodeShort(hash, elems) + return n, wrapError(err, "short") + case 17: + n, err := decodeFull(hash, elems) + return n, wrapError(err, "full") + default: + return nil, fmt.Errorf("invalid number of list elements: %v", c) + } +} + +func decodeShort(hash, elems []byte) (node, error) { + kbuf, rest, err := rlp.SplitString(elems) + if err != nil { + return nil, err + } + flag := nodeFlag{hash: hash} + key := compactToHex(kbuf) + if hasTerm(key) { + // value node + val, _, err := rlp.SplitString(rest) + if err != nil { + return nil, fmt.Errorf("invalid value node: %v", err) + } + return &shortNode{key, valueNode(val), flag}, nil + } + r, _, err := decodeRef(rest) + if err != nil { + return nil, wrapError(err, "val") + } + return &shortNode{key, r, flag}, nil +} + +func decodeFull(hash, elems []byte) (*fullNode, error) { + n := &fullNode{flags: nodeFlag{hash: hash}} + for i := 0; i < 16; i++ { + cld, rest, err := decodeRef(elems) + if err != nil { + return n, wrapError(err, fmt.Sprintf("[%d]", i)) + } + n.Children[i], elems = cld, rest + } + val, _, err := rlp.SplitString(elems) + if err != nil { + return n, err + } + if len(val) > 0 { + n.Children[16] = valueNode(val) + } + return n, nil +} + +const hashLen = len(common.Hash{}) + +func decodeRef(buf []byte) (node, []byte, error) { + kind, val, rest, err := rlp.Split(buf) + if err != nil { + return nil, buf, err + } + switch { + case kind == rlp.List: + // 'embedded' node reference. The encoding must be smaller + // than a hash in order to be valid. + if size := len(buf) - len(rest); size > hashLen { + err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen) + return nil, buf, err + } + n, err := decodeNode(nil, buf) + return n, rest, err + case kind == rlp.String && len(val) == 0: + // empty node + return nil, rest, nil + case kind == rlp.String && len(val) == 32: + return hashNode(val), rest, nil + default: + return nil, nil, fmt.Errorf("invalid RLP string size %d (want 0 or 32)", len(val)) + } +} + +// wraps a decoding error with information about the path to the +// invalid child node (for debugging encoding issues). +type decodeError struct { + what error + stack []string +} + +func wrapError(err error, ctx string) error { + if err == nil { + return nil + } + if decErr, ok := err.(*decodeError); ok { + decErr.stack = append(decErr.stack, ctx) + return decErr + } + return &decodeError{err, []string{ctx}} +} + +func (err *decodeError) Error() string { + return fmt.Sprintf("%v (decode path: %s)", err.what, strings.Join(err.stack, "<-")) +} diff --git a/trie/node_enc.go b/trie/node_enc.go new file mode 100644 index 0000000..1b2eca6 --- /dev/null +++ b/trie/node_enc.go @@ -0,0 +1,64 @@ +// 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 trie + +import ( + "github.com/ethereum/go-ethereum/rlp" +) + +func nodeToBytes(n node) []byte { + w := rlp.NewEncoderBuffer(nil) + n.encode(w) + result := w.ToBytes() + w.Flush() + return result +} + +func (n *fullNode) encode(w rlp.EncoderBuffer) { + offset := w.List() + for _, c := range n.Children { + if c != nil { + c.encode(w) + } else { + w.Write(rlp.EmptyString) + } + } + w.ListEnd(offset) +} + +func (n *shortNode) encode(w rlp.EncoderBuffer) { + offset := w.List() + w.WriteBytes(n.Key) + if n.Val != nil { + n.Val.encode(w) + } else { + w.Write(rlp.EmptyString) + } + w.ListEnd(offset) +} + +func (n hashNode) encode(w rlp.EncoderBuffer) { + w.WriteBytes(n) +} + +func (n valueNode) encode(w rlp.EncoderBuffer) { + w.WriteBytes(n) +} + +func (n rawNode) encode(w rlp.EncoderBuffer) { + w.Write(n) +} diff --git a/trie/node_test.go b/trie/node_test.go new file mode 100644 index 0000000..9b8b337 --- /dev/null +++ b/trie/node_test.go @@ -0,0 +1,215 @@ +// 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 trie + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +func newTestFullNode(v []byte) []interface{} { + fullNodeData := []interface{}{} + for i := 0; i < 16; i++ { + k := bytes.Repeat([]byte{byte(i + 1)}, 32) + fullNodeData = append(fullNodeData, k) + } + fullNodeData = append(fullNodeData, v) + return fullNodeData +} + +func TestDecodeNestedNode(t *testing.T) { + fullNodeData := newTestFullNode([]byte("fullnode")) + + data := [][]byte{} + for i := 0; i < 16; i++ { + data = append(data, nil) + } + data = append(data, []byte("subnode")) + fullNodeData[15] = data + + buf := bytes.NewBuffer([]byte{}) + rlp.Encode(buf, fullNodeData) + + if _, err := decodeNode([]byte("testdecode"), buf.Bytes()); err != nil { + t.Fatalf("decode nested full node err: %v", err) + } +} + +func TestDecodeFullNodeWrongSizeChild(t *testing.T) { + fullNodeData := newTestFullNode([]byte("wrongsizechild")) + fullNodeData[0] = []byte("00") + buf := bytes.NewBuffer([]byte{}) + rlp.Encode(buf, fullNodeData) + + _, err := decodeNode([]byte("testdecode"), buf.Bytes()) + if _, ok := err.(*decodeError); !ok { + t.Fatalf("decodeNode returned wrong err: %v", err) + } +} + +func TestDecodeFullNodeWrongNestedFullNode(t *testing.T) { + fullNodeData := newTestFullNode([]byte("fullnode")) + + data := [][]byte{} + for i := 0; i < 16; i++ { + data = append(data, []byte("123456")) + } + data = append(data, []byte("subnode")) + fullNodeData[15] = data + + buf := bytes.NewBuffer([]byte{}) + rlp.Encode(buf, fullNodeData) + + _, err := decodeNode([]byte("testdecode"), buf.Bytes()) + if _, ok := err.(*decodeError); !ok { + t.Fatalf("decodeNode returned wrong err: %v", err) + } +} + +func TestDecodeFullNode(t *testing.T) { + fullNodeData := newTestFullNode([]byte("decodefullnode")) + buf := bytes.NewBuffer([]byte{}) + rlp.Encode(buf, fullNodeData) + + _, err := decodeNode([]byte("testdecode"), buf.Bytes()) + if err != nil { + t.Fatalf("decode full node err: %v", err) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkEncodeShortNode +// BenchmarkEncodeShortNode-8 16878850 70.81 ns/op 48 B/op 1 allocs/op +func BenchmarkEncodeShortNode(b *testing.B) { + node := &shortNode{ + Key: []byte{0x1, 0x2}, + Val: hashNode(randBytes(32)), + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + nodeToBytes(node) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkEncodeFullNode +// BenchmarkEncodeFullNode-8 4323273 284.4 ns/op 576 B/op 1 allocs/op +func BenchmarkEncodeFullNode(b *testing.B) { + node := &fullNode{} + for i := 0; i < 16; i++ { + node.Children[i] = hashNode(randBytes(32)) + } + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + nodeToBytes(node) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkDecodeShortNode +// BenchmarkDecodeShortNode-8 7925638 151.0 ns/op 157 B/op 4 allocs/op +func BenchmarkDecodeShortNode(b *testing.B) { + node := &shortNode{ + Key: []byte{0x1, 0x2}, + Val: hashNode(randBytes(32)), + } + blob := nodeToBytes(node) + hash := crypto.Keccak256(blob) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + mustDecodeNode(hash, blob) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkDecodeShortNodeUnsafe +// BenchmarkDecodeShortNodeUnsafe-8 9027476 128.6 ns/op 109 B/op 3 allocs/op +func BenchmarkDecodeShortNodeUnsafe(b *testing.B) { + node := &shortNode{ + Key: []byte{0x1, 0x2}, + Val: hashNode(randBytes(32)), + } + blob := nodeToBytes(node) + hash := crypto.Keccak256(blob) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + mustDecodeNodeUnsafe(hash, blob) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkDecodeFullNode +// BenchmarkDecodeFullNode-8 1597462 761.9 ns/op 1280 B/op 18 allocs/op +func BenchmarkDecodeFullNode(b *testing.B) { + node := &fullNode{} + for i := 0; i < 16; i++ { + node.Children[i] = hashNode(randBytes(32)) + } + blob := nodeToBytes(node) + hash := crypto.Keccak256(blob) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + mustDecodeNode(hash, blob) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkDecodeFullNodeUnsafe +// BenchmarkDecodeFullNodeUnsafe-8 1789070 687.1 ns/op 704 B/op 17 allocs/op +func BenchmarkDecodeFullNodeUnsafe(b *testing.B) { + node := &fullNode{} + for i := 0; i < 16; i++ { + node.Children[i] = hashNode(randBytes(32)) + } + blob := nodeToBytes(node) + hash := crypto.Keccak256(blob) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + mustDecodeNodeUnsafe(hash, blob) + } +} diff --git a/trie/proof.go b/trie/proof.go new file mode 100644 index 0000000..a39d6b4 --- /dev/null +++ b/trie/proof.go @@ -0,0 +1,616 @@ +// 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 trie + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// Prove constructs a merkle proof for key. The result contains all encoded nodes +// on the path to the value at key. The value itself is also included in the last +// 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 node), ending +// with the node that proves the absence of the key. +func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return ErrCommitted + } + // Collect all nodes on the path to key. + var ( + prefix []byte + nodes []node + tn = t.root + ) + key = keybytesToHex(key) + for len(key) > 0 && tn != nil { + switch n := tn.(type) { + case *shortNode: + if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { + // The trie doesn't contain the key. + tn = nil + } else { + tn = n.Val + prefix = append(prefix, n.Key...) + key = key[len(n.Key):] + } + nodes = append(nodes, n) + case *fullNode: + tn = n.Children[key[0]] + prefix = append(prefix, key[0]) + key = key[1:] + nodes = append(nodes, n) + case hashNode: + // Retrieve the specified node from the underlying node reader. + // trie.resolveAndTrack is not used since in that function the + // loaded blob will be tracked, while it's not required here since + // all loaded nodes won't be linked to trie at all and track nodes + // may lead to out-of-memory issue. + blob, err := t.reader.node(prefix, common.BytesToHash(n)) + if err != nil { + log.Error("Unhandled trie error in Trie.Prove", "err", err) + return err + } + // The raw-blob format nodes are loaded either from the + // clean cache or the database, they are all in their own + // copy and safe to use unsafe decoder. + tn = mustDecodeNodeUnsafe(n, blob) + default: + panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) + } + } + hasher := newHasher(false) + defer returnHasherToPool(hasher) + + for i, n := range nodes { + var hn node + n, hn = hasher.proofHash(n) + if hash, ok := hn.(hashNode); ok || i == 0 { + // If the node's database encoding is a hash (or is the + // root node), it becomes a proof element. + enc := nodeToBytes(n) + if !ok { + hash = hasher.hashData(enc) + } + proofDb.Put(hash, enc) + } + } + return nil +} + +// Prove constructs a merkle proof for key. The result contains all encoded nodes +// on the path to the value at key. The value itself is also included in the last +// 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 node), ending +// with the node that proves the absence of the key. +func (t *StateTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { + return t.trie.Prove(key, proofDb) +} + +// VerifyProof checks merkle proofs. The given proof must contain the value for +// key in a trie with the given root hash. VerifyProof returns an error if the +// proof contains invalid trie nodes or the wrong value. +func VerifyProof(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueReader) (value []byte, err error) { + key = keybytesToHex(key) + wantHash := rootHash + for i := 0; ; i++ { + buf, _ := proofDb.Get(wantHash[:]) + if buf == nil { + return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash) + } + n, err := decodeNode(wantHash[:], buf) + if err != nil { + return nil, fmt.Errorf("bad proof node %d: %v", i, err) + } + keyrest, cld := get(n, key, true) + switch cld := cld.(type) { + case nil: + // The trie doesn't contain the key. + return nil, nil + case hashNode: + key = keyrest + copy(wantHash[:], cld) + case valueNode: + return cld, nil + } + } +} + +// proofToPath converts a merkle proof to trie node path. The main purpose of +// this function is recovering a node path from the merkle proof stream. All +// necessary nodes will be resolved and leave the remaining as hashnode. +// +// The given edge proof is allowed to be an existent or non-existent proof. +func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyValueReader, allowNonExistent bool) (node, []byte, error) { + // resolveNode retrieves and resolves trie node from merkle proof stream + resolveNode := func(hash common.Hash) (node, error) { + buf, _ := proofDb.Get(hash[:]) + if buf == nil { + return nil, fmt.Errorf("proof node (hash %064x) missing", hash) + } + n, err := decodeNode(hash[:], buf) + if err != nil { + return nil, fmt.Errorf("bad proof node %v", err) + } + return n, err + } + // If the root node is empty, resolve it first. + // Root node must be included in the proof. + if root == nil { + n, err := resolveNode(rootHash) + if err != nil { + return nil, nil, err + } + root = n + } + var ( + err error + child, parent node + keyrest []byte + valnode []byte + ) + key, parent = keybytesToHex(key), root + for { + keyrest, child = get(parent, key, false) + switch cld := child.(type) { + case nil: + // The trie doesn't contain the key. It's possible + // the proof is a non-existing proof, but at least + // we can prove all resolved nodes are correct, it's + // enough for us to prove range. + if allowNonExistent { + return root, nil, nil + } + return nil, nil, errors.New("the node is not contained in trie") + case *shortNode: + key, parent = keyrest, child // Already resolved + continue + case *fullNode: + key, parent = keyrest, child // Already resolved + continue + case hashNode: + child, err = resolveNode(common.BytesToHash(cld)) + if err != nil { + return nil, nil, err + } + case valueNode: + valnode = cld + } + // Link the parent and child. + switch pnode := parent.(type) { + case *shortNode: + pnode.Val = child + case *fullNode: + pnode.Children[key[0]] = child + default: + panic(fmt.Sprintf("%T: invalid node: %v", pnode, pnode)) + } + if len(valnode) > 0 { + return root, valnode, nil // The whole path is resolved + } + key, parent = keyrest, child + } +} + +// unsetInternal removes all internal node references(hashnode, embedded node). +// It should be called after a trie is constructed with two edge paths. Also +// the given boundary keys must be the one used to construct the edge paths. +// +// It's the key step for range proof. All visited nodes should be marked dirty +// since the node content might be modified. Besides it can happen that some +// fullnodes only have one child which is disallowed. But if the proof is valid, +// the missing children will be filled, otherwise it will be thrown anyway. +// +// Note we have the assumption here the given boundary keys are different +// and right is larger than left. +func unsetInternal(n node, left []byte, right []byte) (bool, error) { + left, right = keybytesToHex(left), keybytesToHex(right) + + // Step down to the fork point. There are two scenarios can happen: + // - the fork point is a shortnode: either the key of left proof or + // right proof doesn't match with shortnode's key. + // - the fork point is a fullnode: both two edge proofs are allowed + // to point to a non-existent key. + var ( + pos = 0 + parent node + + // fork indicator, 0 means no fork, -1 means proof is less, 1 means proof is greater + shortForkLeft, shortForkRight int + ) +findFork: + for { + switch rn := (n).(type) { + case *shortNode: + rn.flags = nodeFlag{dirty: true} + + // If either the key of left proof or right proof doesn't match with + // shortnode, stop here and the forkpoint is the shortnode. + if len(left)-pos < len(rn.Key) { + shortForkLeft = bytes.Compare(left[pos:], rn.Key) + } else { + shortForkLeft = bytes.Compare(left[pos:pos+len(rn.Key)], rn.Key) + } + if len(right)-pos < len(rn.Key) { + shortForkRight = bytes.Compare(right[pos:], rn.Key) + } else { + shortForkRight = bytes.Compare(right[pos:pos+len(rn.Key)], rn.Key) + } + if shortForkLeft != 0 || shortForkRight != 0 { + break findFork + } + parent = n + n, pos = rn.Val, pos+len(rn.Key) + case *fullNode: + rn.flags = nodeFlag{dirty: true} + + // If either the node pointed by left proof or right proof is nil, + // stop here and the forkpoint is the fullnode. + leftnode, rightnode := rn.Children[left[pos]], rn.Children[right[pos]] + if leftnode == nil || rightnode == nil || leftnode != rightnode { + break findFork + } + parent = n + n, pos = rn.Children[left[pos]], pos+1 + default: + panic(fmt.Sprintf("%T: invalid node: %v", n, n)) + } + } + switch rn := n.(type) { + case *shortNode: + // There can have these five scenarios: + // - both proofs are less than the trie path => no valid range + // - both proofs are greater than the trie path => no valid range + // - left proof is less and right proof is greater => valid range, unset the shortnode entirely + // - left proof points to the shortnode, but right proof is greater + // - right proof points to the shortnode, but left proof is less + if shortForkLeft == -1 && shortForkRight == -1 { + return false, errors.New("empty range") + } + if shortForkLeft == 1 && shortForkRight == 1 { + return false, errors.New("empty range") + } + if shortForkLeft != 0 && shortForkRight != 0 { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } + parent.(*fullNode).Children[left[pos-1]] = nil + return false, nil + } + // Only one proof points to non-existent key. + if shortForkRight != 0 { + if _, ok := rn.Val.(valueNode); ok { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } + parent.(*fullNode).Children[left[pos-1]] = nil + return false, nil + } + return false, unset(rn, rn.Val, left[pos:], len(rn.Key), false) + } + if shortForkLeft != 0 { + if _, ok := rn.Val.(valueNode); ok { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } + parent.(*fullNode).Children[right[pos-1]] = nil + return false, nil + } + return false, unset(rn, rn.Val, right[pos:], len(rn.Key), true) + } + return false, nil + case *fullNode: + // unset all internal nodes in the forkpoint + for i := left[pos] + 1; i < right[pos]; i++ { + rn.Children[i] = nil + } + if err := unset(rn, rn.Children[left[pos]], left[pos:], 1, false); err != nil { + return false, err + } + if err := unset(rn, rn.Children[right[pos]], right[pos:], 1, true); err != nil { + return false, err + } + return false, nil + default: + panic(fmt.Sprintf("%T: invalid node: %v", n, n)) + } +} + +// unset removes all internal node references either the left most or right most. +// It can meet these scenarios: +// +// - The given path is existent in the trie, unset the associated nodes with the +// specific direction +// - The given path is non-existent in the trie +// - the fork point is a fullnode, the corresponding child pointed by path +// is nil, return +// - the fork point is a shortnode, the shortnode is included in the range, +// keep the entire branch and return. +// - the fork point is a shortnode, the shortnode is excluded in the range, +// unset the entire branch. +func unset(parent node, child node, key []byte, pos int, removeLeft bool) error { + switch cld := child.(type) { + case *fullNode: + if removeLeft { + for i := 0; i < int(key[pos]); i++ { + cld.Children[i] = nil + } + cld.flags = nodeFlag{dirty: true} + } else { + for i := key[pos] + 1; i < 16; i++ { + cld.Children[i] = nil + } + cld.flags = nodeFlag{dirty: true} + } + return unset(cld, cld.Children[key[pos]], key, pos+1, removeLeft) + case *shortNode: + if len(key[pos:]) < len(cld.Key) || !bytes.Equal(cld.Key, key[pos:pos+len(cld.Key)]) { + // Find the fork point, it's a non-existent branch. + if removeLeft { + if bytes.Compare(cld.Key, key[pos:]) < 0 { + // The key of fork shortnode is less than the path + // (it belongs to the range), unset the entire + // branch. The parent must be a fullnode. + fn := parent.(*fullNode) + fn.Children[key[pos-1]] = nil + } + //else { + // The key of fork shortnode is greater than the + // path(it doesn't belong to the range), keep + // it with the cached hash available. + //} + } else { + if bytes.Compare(cld.Key, key[pos:]) > 0 { + // The key of fork shortnode is greater than the + // path(it belongs to the range), unset the entries + // branch. The parent must be a fullnode. + fn := parent.(*fullNode) + fn.Children[key[pos-1]] = nil + } + //else { + // The key of fork shortnode is less than the + // path(it doesn't belong to the range), keep + // it with the cached hash available. + //} + } + return nil + } + if _, ok := cld.Val.(valueNode); ok { + fn := parent.(*fullNode) + fn.Children[key[pos-1]] = nil + return nil + } + cld.flags = nodeFlag{dirty: true} + return unset(cld, cld.Val, key, pos+len(cld.Key), removeLeft) + case nil: + // If the node is nil, then it's a child of the fork point + // fullnode(it's a non-existent branch). + return nil + default: + panic("it shouldn't happen") // hashNode, valueNode + } +} + +// hasRightElement returns the indicator whether there exists more elements +// on the right side of the given path. The given path can point to an existent +// key or a non-existent one. This function has the assumption that the whole +// path should already be resolved. +func hasRightElement(node node, key []byte) bool { + pos, key := 0, keybytesToHex(key) + for node != nil { + switch rn := node.(type) { + case *fullNode: + for i := key[pos] + 1; i < 16; i++ { + if rn.Children[i] != nil { + return true + } + } + node, pos = rn.Children[key[pos]], pos+1 + case *shortNode: + if len(key)-pos < len(rn.Key) || !bytes.Equal(rn.Key, key[pos:pos+len(rn.Key)]) { + return bytes.Compare(rn.Key, key[pos:]) > 0 + } + node, pos = rn.Val, pos+len(rn.Key) + case valueNode: + return false // We have resolved the whole path + default: + panic(fmt.Sprintf("%T: invalid node: %v", node, node)) // hashnode + } + } + return false +} + +// VerifyRangeProof checks whether the given leaf nodes and edge proof +// can prove the given trie leaves range is matched with the specific root. +// Besides, the range should be consecutive (no gap inside) and monotonic +// increasing. +// +// Note the given proof actually contains two edge proofs. Both of them can +// be non-existent proofs. For example the first proof is for a non-existent +// key 0x03, the last proof is for a non-existent key 0x10. The given batch +// leaves are [0x04, 0x05, .. 0x09]. It's still feasible to prove the given +// batch is valid. +// +// The firstKey is paired with firstProof, not necessarily the same as keys[0] +// (unless firstProof is an existent proof). Similarly, lastKey and lastProof +// are paired. +// +// Expect the normal case, this function can also be used to verify the following +// range proofs: +// +// - All elements proof. In this case the proof can be nil, but the range should +// be all the leaves in the trie. +// +// - One element proof. In this case no matter the edge proof is a non-existent +// proof or not, we can always verify the correctness of the proof. +// +// - Zero element proof. In this case a single non-existent proof is enough to prove. +// Besides, if there are still some other leaves available on the right side, then +// an error will be returned. +// +// Except returning the error to indicate the proof is valid or not, the function will +// also return a flag to indicate whether there exists more accounts/slots in the trie. +// +// 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, 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)) + } + // Ensure the received batch is monotonic increasing and contains no deletions + for i := 0; i < len(keys)-1; i++ { + if bytes.Compare(keys[i], keys[i+1]) >= 0 { + return false, errors.New("range is not monotonically increasing") + } + } + for _, value := range values { + if len(value) == 0 { + return false, errors.New("range contains deletion") + } + } + // Special case, there is no edge proof at all. The given range is expected + // to be the whole leaf-set in the trie. + if proof == nil { + tr := NewStackTrie(nil) + for index, key := range keys { + tr.Update(key, values[index]) + } + if have, want := tr.Hash(), rootHash; have != want { + return false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have) + } + return false, nil // No more elements + } + // Special case, there is a provided edge proof but zero key/value + // pairs, ensure there are no more accounts / slots in the trie. + if len(keys) == 0 { + root, val, err := proofToPath(rootHash, nil, firstKey, proof, true) + if err != nil { + return false, err + } + if val != nil || hasRightElement(root, firstKey) { + return false, errors.New("more entries available") + } + 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) { + root, val, err := proofToPath(rootHash, nil, firstKey, proof, false) + if err != nil { + return false, err + } + if !bytes.Equal(firstKey, keys[0]) { + return false, errors.New("correct proof but invalid key") + } + if !bytes.Equal(val, values[0]) { + return false, errors.New("correct proof but invalid data") + } + return hasRightElement(root, firstKey), nil + } + // Ok, in all other cases, we require two edge paths available. + // First check the validity of edge keys. + if bytes.Compare(firstKey, lastKey) >= 0 { + return false, errors.New("invalid edge keys") + } + // todo(rjl493456442) different length edge keys should be supported + if len(firstKey) != len(lastKey) { + return false, errors.New("inconsistent edge keys") + } + // Convert the edge proofs to edge trie paths. Then we can + // have the same tree architecture with the original one. + // For the first edge proof, non-existent proof is allowed. + root, _, err := proofToPath(rootHash, nil, firstKey, proof, true) + if err != nil { + return false, err + } + // Pass the root node here, the second path will be merged + // with the first one. For the last edge proof, non-existent + // proof is also allowed. + root, _, err = proofToPath(rootHash, root, lastKey, proof, true) + if err != nil { + return false, err + } + // Remove all internal references. All the removed parts should + // be re-filled(or re-constructed) by the given leaves range. + empty, err := unsetInternal(root, firstKey, lastKey) + if err != nil { + return false, err + } + // Rebuild the trie with the leaf stream, the shape of trie + // should be same with the original one. + tr := &Trie{root: root, reader: newEmptyReader(), tracer: newTracer()} + if empty { + tr.root = nil + } + for index, key := range keys { + tr.Update(key, values[index]) + } + if tr.Hash() != rootHash { + return false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) + } + return hasRightElement(tr.root, keys[len(keys)-1]), nil +} + +// get returns the child of the given node. Return nil if the +// node with specified key doesn't exist at all. +// +// There is an additional flag `skipResolved`. If it's set then +// all resolved nodes won't be returned. +func get(tn node, key []byte, skipResolved bool) ([]byte, node) { + for { + switch n := tn.(type) { + case *shortNode: + if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { + return nil, nil + } + tn = n.Val + key = key[len(n.Key):] + if !skipResolved { + return key, tn + } + case *fullNode: + tn = n.Children[key[0]] + key = key[1:] + if !skipResolved { + return key, tn + } + case hashNode: + return key, n + case nil: + return key, nil + case valueNode: + return nil, n + default: + panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) + } + } +} diff --git a/trie/proof_test.go b/trie/proof_test.go new file mode 100644 index 0000000..fab3a97 --- /dev/null +++ b/trie/proof_test.go @@ -0,0 +1,1002 @@ +// 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 trie + +import ( + "bytes" + crand "crypto/rand" + "encoding/binary" + "fmt" + mrand "math/rand" + "slices" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +// Prng is a pseudo random number generator seeded by strong randomness. +// The randomness is printed on startup in order to make failures reproducible. +var prng = initRnd() + +func initRnd() *mrand.Rand { + var seed [8]byte + crand.Read(seed[:]) + rnd := mrand.New(mrand.NewSource(int64(binary.LittleEndian.Uint64(seed[:])))) + fmt.Printf("Seed: %x\n", seed) + return rnd +} + +func randBytes(n int) []byte { + r := make([]byte, n) + prng.Read(r) + return r +} + +// makeProvers creates Merkle trie provers based on different implementations to +// test all variations. +func makeProvers(trie *Trie) []func(key []byte) *memorydb.Database { + var provers []func(key []byte) *memorydb.Database + + // Create a direct trie based Merkle prover + provers = append(provers, func(key []byte) *memorydb.Database { + proof := memorydb.New() + trie.Prove(key, proof) + return proof + }) + // Create a leaf iterator based Merkle prover + provers = append(provers, func(key []byte) *memorydb.Database { + proof := memorydb.New() + if it := NewIterator(trie.MustNodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) { + for _, p := range it.Prove() { + proof.Put(crypto.Keccak256(p), p) + } + } + return proof + }) + return provers +} + +func TestProof(t *testing.T) { + trie, vals := randomTrie(500) + root := trie.Hash() + for i, prover := range makeProvers(trie) { + for _, kv := range vals { + proof := prover(kv.k) + if proof == nil { + t.Fatalf("prover %d: missing key %x while constructing proof", i, kv.k) + } + val, err := VerifyProof(root, kv.k, proof) + if err != nil { + t.Fatalf("prover %d: failed to verify proof for key %x: %v\nraw proof: %x", i, kv.k, err, proof) + } + if !bytes.Equal(val, kv.v) { + t.Fatalf("prover %d: verified value mismatch for key %x: have %x, want %x", i, kv.k, val, kv.v) + } + } + } +} + +func TestOneElementProof(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + updateString(trie, "k", "v") + for i, prover := range makeProvers(trie) { + proof := prover([]byte("k")) + if proof == nil { + t.Fatalf("prover %d: nil proof", i) + } + if proof.Len() != 1 { + t.Errorf("prover %d: proof should have one element", i) + } + val, err := VerifyProof(trie.Hash(), []byte("k"), proof) + if err != nil { + t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof) + } + if !bytes.Equal(val, []byte("v")) { + t.Fatalf("prover %d: verified value mismatch: have %x, want 'k'", i, val) + } + } +} + +func TestBadProof(t *testing.T) { + trie, vals := randomTrie(800) + root := trie.Hash() + for i, prover := range makeProvers(trie) { + for _, kv := range vals { + proof := prover(kv.k) + if proof == nil { + t.Fatalf("prover %d: nil proof", i) + } + it := proof.NewIterator(nil, nil) + for i, d := 0, mrand.Intn(proof.Len()); i <= d; i++ { + it.Next() + } + key := it.Key() + val, _ := proof.Get(key) + proof.Delete(key) + it.Release() + + mutateByte(val) + proof.Put(crypto.Keccak256(val), val) + + if _, err := VerifyProof(root, kv.k, proof); err == nil { + t.Fatalf("prover %d: expected proof to fail for key %x", i, kv.k) + } + } + } +} + +// 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(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + updateString(trie, "k", "v") + + for i, key := range []string{"a", "j", "l", "z"} { + proof := memorydb.New() + trie.Prove([]byte(key), proof) + + if proof.Len() != 1 { + t.Errorf("test %d: proof should have one element", i) + } + val, err := VerifyProof(trie.Hash(), []byte(key), proof) + if err != nil { + t.Fatalf("test %d: failed to verify proof: %v\nraw proof: %x", i, err, proof) + } + if val != nil { + t.Fatalf("test %d: verified value mismatch: have %x, want nil", i, val) + } + } +} + +// TestRangeProof tests normal range proof with both edge proofs +// as the existent proof. The test cases are generated randomly. +func TestRangeProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + for i := 0; i < 500; i++ { + start := mrand.Intn(len(entries)) + end := mrand.Intn(len(entries)-start) + start + 1 + + proof := memorydb.New() + if err := trie.Prove(entries[start].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(entries[end-1].k, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + var keys [][]byte + var vals [][]byte + for i := start; i < end; i++ { + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + _, 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) + } + } +} + +// TestRangeProofWithNonExistentProof tests normal range proof with two non-existent proofs. +// The test cases are generated randomly. +func TestRangeProofWithNonExistentProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + for i := 0; i < 500; i++ { + start := mrand.Intn(len(entries)) + end := mrand.Intn(len(entries)-start) + start + 1 + proof := memorydb.New() + + // Short circuit if the decreased key is same with the previous key + first := decreaseKey(common.CopyBytes(entries[start].k)) + if start != 0 && bytes.Equal(first, entries[start-1].k) { + continue + } + // Short circuit if the decreased key is underflow + if bytes.Compare(first, entries[start].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(entries[end-1].k, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + var keys [][]byte + var vals [][]byte + for i := start; i < end; i++ { + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + _, 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) + } + } +} + +// TestRangeProofWithInvalidNonExistentProof tests such scenarios: +// - There exists a gap between the first element and the left edge proof +func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + // Case 1 + start, end := 100, 200 + first := decreaseKey(common.CopyBytes(entries[start].k)) + + proof := memorydb.New() + if err := trie.Prove(first, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(entries[end-1].k, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + start = 105 // Gap created + 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(), first, k, v, proof) + if err == nil { + t.Fatalf("Expected to detect the error, got nil") + } +} + +// TestOneElementRangeProof tests the proof with only one +// element. The first edge proof can be existent one or +// non-existent one. +func TestOneElementRangeProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + // One element with existent edge proof, both edge proofs + // point to the SAME key. + start := 1000 + proof := memorydb.New() + if err := trie.Prove(entries[start].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + _, 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) + } + + // One element with left non-existent edge proof + start = 1000 + first := decreaseKey(common.CopyBytes(entries[start].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(entries[start].k, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + _, 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) + } + + // One element with right non-existent edge proof + start = 1000 + last := increaseKey(common.CopyBytes(entries[start].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) + } + _, 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) + } + + // One element with two non-existent edge proofs + start = 1000 + first, last = decreaseKey(common.CopyBytes(entries[start].k)), increaseKey(common.CopyBytes(entries[start].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) + } + _, 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(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + entry := &kv{randBytes(32), randBytes(20), false} + tinyTrie.MustUpdate(entry.k, entry.v) + + first = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() + last = entry.k + proof = memorydb.New() + if err := tinyTrie.Prove(first, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := tinyTrie.Prove(last, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + _, err = VerifyRangeProof(tinyTrie.Hash(), first, [][]byte{entry.k}, [][]byte{entry.v}, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } +} + +// TestAllElementsProof tests the range proof with all elements. +// The edge proofs can be nil. +func TestAllElementsProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + 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(), nil, k, v, nil) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // With edge proofs, it should still work. + proof := memorydb.New() + if err := trie.Prove(entries[0].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(entries[len(entries)-1].k, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + _, err = VerifyRangeProof(trie.Hash(), k[0], k, v, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Even with non-existent edge proofs, it should still work. + proof = memorydb.New() + first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() + if err := trie.Prove(first, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + 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, k, v, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } +} + +// TestSingleSideRangeProof tests the range starts from zero. +func TestSingleSideRangeProof(t *testing.T) { + for i := 0; i < 64; i++ { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + 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(common.Hash{}.Bytes(), proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(entries[pos].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + k := make([][]byte, 0) + v := make([][]byte, 0) + for i := 0; i <= pos; i++ { + k = append(k, entries[i].k) + v = append(v, entries[i].v) + } + _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k, v, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + } + } +} + +// TestBadRangeProof tests a few cases which the proof is wrong. +// The prover is expected to detect the error. +func TestBadRangeProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + for i := 0; i < 500; i++ { + start := mrand.Intn(len(entries)) + end := mrand.Intn(len(entries)-start) + start + 1 + proof := memorydb.New() + if err := trie.Prove(entries[start].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(entries[end-1].k, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + var keys [][]byte + var vals [][]byte + for i := start; i < end; i++ { + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + var first = keys[0] + testcase := mrand.Intn(6) + var index int + switch testcase { + case 0: + // Modified key + index = mrand.Intn(end - start) + keys[index] = randBytes(32) // In theory it can't be same + case 1: + // Modified val + index = mrand.Intn(end - start) + vals[index] = randBytes(20) // In theory it can't be same + case 2: + // Gapped entry slice + index = mrand.Intn(end - start) + if (index == 0 && start < 100) || (index == end-start-1) { + continue + } + keys = append(keys[:index], keys[index+1:]...) + vals = append(vals[:index], vals[index+1:]...) + case 3: + // Out of order + index1 := mrand.Intn(end - start) + index2 := mrand.Intn(end - start) + if index1 == index2 { + continue + } + keys[index1], keys[index2] = keys[index2], keys[index1] + vals[index1], vals[index2] = vals[index2], vals[index1] + case 4: + // Set random key to nil, do nothing + index = mrand.Intn(end - start) + keys[index] = nil + case 5: + // Set random value to nil, deletion + index = mrand.Intn(end - start) + vals[index] = nil + } + _, 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) + } + } +} + +// 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(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + var entries []*kv // Sorted entries + for i := byte(0); i < 10; i++ { + value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + trie.MustUpdate(value.k, value.v) + entries = append(entries, value) + } + first, last := 2, 8 + proof := memorydb.New() + if err := trie.Prove(entries[first].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(entries[last-1].k, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + var keys [][]byte + var vals [][]byte + for i := first; i < last; i++ { + if i == (first+last)/2 { + continue + } + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof) + if err == nil { + t.Fatal("expect error, got nil") + } +} + +// TestSameSideProofs tests the element is not in the range covered by proofs +func TestSameSideProofs(t *testing.T) { + trie, vals := randomTrie(4096) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + pos := 1000 + 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(entries[2000].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + _, 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(last) + + 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) + } + _, 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(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + 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 = []struct { + start int + end int + hasMore bool + }{ + {-1, 1, true}, // single element with non-existent left proof + {0, 1, true}, // single element with existent left proof + {0, 10, true}, + {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) + {0, len(entries), false}, // The whole set with existent left proof + {-1, len(entries), false}, // The whole set with non-existent left proof + } + for _, c := range cases { + var ( + firstKey []byte + start = c.start + end = c.end + proof = memorydb.New() + ) + if c.start == -1 { + firstKey, start = common.Hash{}.Bytes(), 0 + if err := trie.Prove(firstKey, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + } else { + firstKey = entries[c.start].k + if err := trie.Prove(entries[c.start].k, 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) + for i := start; i < end; i++ { + k = append(k, entries[i].k) + v = append(v, entries[i].v) + } + hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, k, v, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + if hasMore != c.hasMore { + t.Fatalf("Wrong hasMore indicator, want %t, got %t", c.hasMore, hasMore) + } + } +} + +// TestEmptyRangeProof tests the range proof with "no" element. +// The first edge proof must be a non-existent proof. +func TestEmptyRangeProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + var cases = []struct { + pos int + err bool + }{ + {len(entries) - 1, false}, + {500, true}, + } + for _, c := range cases { + proof := memorydb.New() + first := increaseKey(common.CopyBytes(entries[c.pos].k)) + 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, proof) + if c.err && err == nil { + t.Fatalf("Expected error, got nil") + } + if !c.err && err != nil { + t.Fatalf("Expected no error, got %v", err) + } + } +} + +// TestBloatedProof tests a malicious proof, where the proof is more or less the +// whole trie. Previously we didn't accept such packets, but the new APIs do, so +// lets leave this test as a bit weird, but present. +func TestBloatedProof(t *testing.T) { + // Use a small trie + trie, kvs := nonRandomTrie(100) + var entries []*kv + for _, kv := range kvs { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + var keys [][]byte + var vals [][]byte + + proof := memorydb.New() + // In the 'malicious' case, we add proofs for every single item + // (but only one key/value pair used as leaf) + for i, entry := range entries { + trie.Prove(entry.k, proof) + if i == 50 { + keys = append(keys, entry.k) + vals = append(vals, entry.v) + } + } + // For reference, we use the same function, but _only_ prove the first + // and last element + want := memorydb.New() + trie.Prove(keys[0], want) + trie.Prove(keys[len(keys)-1], want) + + if _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof); err != nil { + t.Fatalf("expected bloated proof to succeed, got %v", err) + } +} + +// TestEmptyValueRangeProof tests normal range proof with both edge proofs +// as the existent proof, but with an extra empty value included, which is a +// noop technically, but practically should be rejected. +func TestEmptyValueRangeProof(t *testing.T) { + trie, values := randomTrie(512) + var entries []*kv + for _, kv := range values { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + // Create a new entry with a slightly modified key + mid := len(entries) / 2 + key := common.CopyBytes(entries[mid-1].k) + for n := len(key) - 1; n >= 0; n-- { + if key[n] < 0xff { + key[n]++ + break + } + } + noop := &kv{key, []byte{}, false} + entries = append(append(append([]*kv{}, entries[:mid]...), noop), entries[mid:]...) + + start, end := 1, len(entries)-1 + + 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(entries[end-1].k, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + var keys [][]byte + var vals [][]byte + for i := start; i < end; i++ { + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof) + if err == nil { + t.Fatalf("Expected failure on noop entry") + } +} + +// TestAllElementsEmptyValueRangeProof tests the range proof with all elements, +// but with an extra empty value included, which is a noop technically, but +// practically should be rejected. +func TestAllElementsEmptyValueRangeProof(t *testing.T) { + trie, values := randomTrie(512) + var entries []*kv + for _, kv := range values { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + // Create a new entry with a slightly modified key + mid := len(entries) / 2 + key := common.CopyBytes(entries[mid-1].k) + for n := len(key) - 1; n >= 0; n-- { + if key[n] < 0xff { + key[n]++ + break + } + } + noop := &kv{key, []byte{}, false} + entries = append(append(append([]*kv{}, entries[:mid]...), noop), entries[mid:]...) + + var keys [][]byte + var vals [][]byte + for i := 0; i < len(entries); i++ { + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + _, err := VerifyRangeProof(trie.Hash(), nil, keys, vals, nil) + if err == nil { + t.Fatalf("Expected failure on noop entry") + } +} + +// mutateByte changes one byte in b. +func mutateByte(b []byte) { + for r := mrand.Intn(len(b)); ; { + new := byte(mrand.Intn(255)) + if new != b[r] { + b[r] = new + break + } + } +} + +func increaseKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]++ + if key[i] != 0x0 { + break + } + } + return key +} + +func decreaseKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]-- + if key[i] != 0xff { + break + } + } + return key +} + +func BenchmarkProve(b *testing.B) { + trie, vals := randomTrie(100) + var keys []string + for k := range vals { + keys = append(keys, k) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + kv := vals[keys[i%len(keys)]] + proofs := memorydb.New() + if trie.Prove(kv.k, proofs); proofs.Len() == 0 { + b.Fatalf("zero length proof for %x", kv.k) + } + } +} + +func BenchmarkVerifyProof(b *testing.B) { + trie, vals := randomTrie(100) + root := trie.Hash() + var keys []string + var proofs []*memorydb.Database + for k := range vals { + keys = append(keys, k) + proof := memorydb.New() + trie.Prove([]byte(k), proof) + proofs = append(proofs, proof) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + im := i % len(keys) + if _, err := VerifyProof(root, []byte(keys[im]), proofs[im]); err != nil { + b.Fatalf("key %x: %v", keys[im], err) + } + } +} + +func BenchmarkVerifyRangeProof10(b *testing.B) { benchmarkVerifyRangeProof(b, 10) } +func BenchmarkVerifyRangeProof100(b *testing.B) { benchmarkVerifyRangeProof(b, 100) } +func BenchmarkVerifyRangeProof1000(b *testing.B) { benchmarkVerifyRangeProof(b, 1000) } +func BenchmarkVerifyRangeProof5000(b *testing.B) { benchmarkVerifyRangeProof(b, 5000) } + +func benchmarkVerifyRangeProof(b *testing.B, size int) { + trie, vals := randomTrie(8192) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + start := 2 + end := start + size + proof := memorydb.New() + if err := trie.Prove(entries[start].k, proof); err != nil { + b.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(entries[end-1].k, proof); err != nil { + b.Fatalf("Failed to prove the last node %v", err) + } + var keys [][]byte + var values [][]byte + for i := start; i < end; i++ { + keys = append(keys, entries[i].k) + values = append(values, entries[i].v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, 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) + } + } +} + +func BenchmarkVerifyRangeNoProof10(b *testing.B) { benchmarkVerifyRangeNoProof(b, 100) } +func BenchmarkVerifyRangeNoProof500(b *testing.B) { benchmarkVerifyRangeNoProof(b, 500) } +func BenchmarkVerifyRangeNoProof1000(b *testing.B) { benchmarkVerifyRangeNoProof(b, 1000) } + +func benchmarkVerifyRangeNoProof(b *testing.B, size int) { + trie, vals := randomTrie(size) + var entries []*kv + for _, kv := range vals { + entries = append(entries, kv) + } + slices.SortFunc(entries, (*kv).cmp) + + var keys [][]byte + var values [][]byte + for _, entry := range entries { + keys = append(keys, entry.k) + values = append(values, entry.v) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, values, nil) + if err != nil { + b.Fatalf("Expected no error, got %v", err) + } + } +} + +func randomTrie(n int) (*Trie, map[string]*kv) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + vals := make(map[string]*kv) + for i := byte(0); i < 100; i++ { + value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false} + trie.MustUpdate(value.k, value.v) + trie.MustUpdate(value2.k, value2.v) + vals[string(value.k)] = value + vals[string(value2.k)] = value2 + } + for i := 0; i < n; i++ { + value := &kv{randBytes(32), randBytes(20), false} + trie.MustUpdate(value.k, value.v) + vals[string(value.k)] = value + } + return trie, vals +} + +func nonRandomTrie(n int) (*Trie, map[string]*kv) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + vals := make(map[string]*kv) + max := uint64(0xffffffffffffffff) + for i := uint64(0); i < uint64(n); i++ { + value := make([]byte, 32) + key := make([]byte, 32) + binary.LittleEndian.PutUint64(key, i) + binary.LittleEndian.PutUint64(value, i-max) + //value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + elem := &kv{key, value, false} + trie.MustUpdate(elem.k, elem.v) + vals[string(elem.k)] = elem + } + return trie, vals +} + +func TestRangeProofKeysWithSharedPrefix(t *testing.T) { + keys := [][]byte{ + common.Hex2Bytes("aa10000000000000000000000000000000000000000000000000000000000000"), + common.Hex2Bytes("aa20000000000000000000000000000000000000000000000000000000000000"), + } + vals := [][]byte{ + common.Hex2Bytes("02"), + common.Hex2Bytes("03"), + } + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + for i, key := range keys { + trie.MustUpdate(key, vals[i]) + } + root := trie.Hash() + proof := memorydb.New() + start := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000") + if err := trie.Prove(start, proof); err != nil { + t.Fatalf("failed to prove start: %v", err) + } + if err := trie.Prove(keys[len(keys)-1], proof); err != nil { + t.Fatalf("failed to prove end: %v", err) + } + + more, err := VerifyRangeProof(root, start, keys, vals, proof) + if err != nil { + t.Fatalf("failed to verify range proof: %v", err) + } + if more != false { + t.Error("expected more to be false") + } +} diff --git a/trie/secure_trie.go b/trie/secure_trie.go new file mode 100644 index 0000000..6eb6def --- /dev/null +++ b/trie/secure_trie.go @@ -0,0 +1,318 @@ +// 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 trie + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb/database" +) + +// preimageStore wraps the methods of a backing store for reading and writing +// trie node preimages. +type preimageStore interface { + // Preimage retrieves the preimage of the specified hash. + Preimage(hash common.Hash) []byte + + // InsertPreimage commits a set of preimages along with their hashes. + InsertPreimage(preimages map[common.Hash][]byte) +} + +// SecureTrie is the old name of StateTrie. +// Deprecated: use StateTrie. +type SecureTrie = StateTrie + +// NewSecure creates a new StateTrie. +// Deprecated: use NewStateTrie. +func NewSecure(stateRoot common.Hash, owner common.Hash, root common.Hash, db database.Database) (*SecureTrie, error) { + id := &ID{ + StateRoot: stateRoot, + Owner: owner, + Root: root, + } + return NewStateTrie(id, db) +} + +// StateTrie wraps a trie with key hashing. In a stateTrie trie, all +// access operations hash the key using keccak256. This prevents +// calling code from creating long chains of nodes that +// increase the access time. +// +// Contrary to a regular trie, a StateTrie can only be created with +// New and must have an attached database. The database also stores +// the preimage of each key if preimage recording is enabled. +// +// StateTrie is not safe for concurrent use. +type StateTrie struct { + trie Trie + db database.Database + preimages preimageStore + hashKeyBuf [common.HashLength]byte + secKeyCache map[string][]byte + secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch +} + +// NewStateTrie creates a trie with an existing root node from a backing database. +// +// If root is the zero hash or the sha3 hash of an empty string, the +// trie is initially empty. Otherwise, New will panic if db is nil +// and returns MissingNodeError if the root node cannot be found. +func NewStateTrie(id *ID, db database.Database) (*StateTrie, error) { + if db == nil { + panic("trie.NewStateTrie called without a database") + } + trie, err := New(id, db) + if err != nil { + return nil, err + } + tr := &StateTrie{trie: *trie, db: db} + + // link the preimage store if it's supported + preimages, ok := db.(preimageStore) + if ok { + tr.preimages = preimages + } + return tr, nil +} + +// MustGet returns the value for key stored in the trie. +// The value bytes must not be modified by the caller. +// +// This function will omit any encountered error but just +// print out an error message. +func (t *StateTrie) MustGet(key []byte) []byte { + return t.trie.MustGet(t.hashKey(key)) +} + +// GetStorage attempts to retrieve a storage slot with provided account address +// and slot key. The value bytes must not be modified by the caller. +// If the specified storage slot is not in the trie, nil will be returned. +// If a trie node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { + enc, err := t.trie.Get(t.hashKey(key)) + if err != nil || len(enc) == 0 { + return nil, err + } + _, content, _, err := rlp.Split(enc) + return content, err +} + +// GetAccount attempts to retrieve an account with provided account address. +// If the specified account is not in the trie, nil will be returned. +// If a trie node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) GetAccount(address common.Address) (*types.StateAccount, error) { + res, err := t.trie.Get(t.hashKey(address.Bytes())) + if res == nil || err != nil { + return nil, err + } + ret := new(types.StateAccount) + err = rlp.DecodeBytes(res, ret) + return ret, err +} + +// GetAccountByHash does the same thing as GetAccount, however it expects an +// account hash that is the hash of address. This constitutes an abstraction +// leak, since the client code needs to know the key format. +func (t *StateTrie) GetAccountByHash(addrHash common.Hash) (*types.StateAccount, error) { + res, err := t.trie.Get(addrHash.Bytes()) + if res == nil || err != nil { + return nil, err + } + ret := new(types.StateAccount) + err = rlp.DecodeBytes(res, ret) + return ret, err +} + +// GetNode attempts to retrieve a trie node by compact-encoded path. It is not +// possible to use keybyte-encoding as the path might contain odd nibbles. +// If the specified trie node is not in the trie, nil will be returned. +// If a trie node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) GetNode(path []byte) ([]byte, int, error) { + return t.trie.GetNode(path) +} + +// MustUpdate associates key with value in the trie. Subsequent calls to +// Get will return value. If value has length zero, any existing value +// is deleted from the trie and calls to Get will return nil. +// +// The value bytes must not be modified by the caller while they are +// stored in the trie. +// +// This function will omit any encountered error but just print out an +// error message. +func (t *StateTrie) MustUpdate(key, value []byte) { + hk := t.hashKey(key) + t.trie.MustUpdate(hk, value) + t.getSecKeyCache()[string(hk)] = common.CopyBytes(key) +} + +// UpdateStorage associates key with value in the trie. Subsequent calls to +// Get will return value. If value has length zero, any existing value +// is deleted from the trie and calls to Get will return nil. +// +// The value bytes must not be modified by the caller while they are +// stored in the trie. +// +// If a node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error { + hk := t.hashKey(key) + v, _ := rlp.EncodeToBytes(value) + err := t.trie.Update(hk, v) + if err != nil { + return err + } + t.getSecKeyCache()[string(hk)] = common.CopyBytes(key) + return nil +} + +// UpdateAccount will abstract the write of an account to the secure trie. +func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error { + hk := t.hashKey(address.Bytes()) + data, err := rlp.EncodeToBytes(acc) + if err != nil { + return err + } + if err := t.trie.Update(hk, data); err != nil { + return err + } + t.getSecKeyCache()[string(hk)] = address.Bytes() + return nil +} + +func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error { + return nil +} + +// MustDelete removes any existing value for key from the trie. This function +// will omit any encountered error but just print out an error message. +func (t *StateTrie) MustDelete(key []byte) { + hk := t.hashKey(key) + delete(t.getSecKeyCache(), string(hk)) + t.trie.MustDelete(hk) +} + +// DeleteStorage removes any existing storage slot from the trie. +// If the specified trie node is not in the trie, nothing will be changed. +// If a node is not found in the database, a MissingNodeError is returned. +func (t *StateTrie) DeleteStorage(_ common.Address, key []byte) error { + hk := t.hashKey(key) + delete(t.getSecKeyCache(), string(hk)) + return t.trie.Delete(hk) +} + +// DeleteAccount abstracts an account deletion from the trie. +func (t *StateTrie) DeleteAccount(address common.Address) error { + hk := t.hashKey(address.Bytes()) + delete(t.getSecKeyCache(), string(hk)) + return t.trie.Delete(hk) +} + +// GetKey returns the sha3 preimage of a hashed key that was +// previously used to store a value. +func (t *StateTrie) GetKey(shaKey []byte) []byte { + if key, ok := t.getSecKeyCache()[string(shaKey)]; ok { + return key + } + if t.preimages == nil { + return nil + } + return t.preimages.Preimage(common.BytesToHash(shaKey)) +} + +// Witness returns a set containing all trie nodes that have been accessed. +func (t *StateTrie) Witness() map[string]struct{} { + return t.trie.Witness() +} + +// Commit collects all dirty nodes in the trie and replaces them with the +// corresponding node hash. All collected nodes (including dirty leaves if +// collectLeaf is true) will be encapsulated into a nodeset for return. +// The returned nodeset can be nil if the trie is clean (nothing to commit). +// All cached preimages will be also flushed if preimages recording is enabled. +// Once the trie is committed, it's not usable anymore. A new trie must +// be created with new root and updated trie database for following usage +func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { + // Write all the pre-images to the actual disk database + if len(t.getSecKeyCache()) > 0 { + preimages := make(map[common.Hash][]byte, len(t.secKeyCache)) + for hk, key := range t.secKeyCache { + preimages[common.BytesToHash([]byte(hk))] = key + } + if t.preimages != nil { + t.preimages.InsertPreimage(preimages) + } + t.secKeyCache = make(map[string][]byte) + } + // Commit the trie and return its modified nodeset. + return t.trie.Commit(collectLeaf) +} + +// Hash returns the root hash of StateTrie. It does not write to the +// database and can be used even if the trie doesn't have one. +func (t *StateTrie) Hash() common.Hash { + return t.trie.Hash() +} + +// Copy returns a copy of StateTrie. +func (t *StateTrie) Copy() *StateTrie { + return &StateTrie{ + trie: *t.trie.Copy(), + db: t.db, + secKeyCache: t.secKeyCache, + } +} + +// NodeIterator returns an iterator that returns nodes of the underlying trie. +// Iteration starts at the key after the given start key. +func (t *StateTrie) NodeIterator(start []byte) (NodeIterator, error) { + return t.trie.NodeIterator(start) +} + +// MustNodeIterator is a wrapper of NodeIterator and will omit any encountered +// error but just print out an error message. +func (t *StateTrie) MustNodeIterator(start []byte) NodeIterator { + return t.trie.MustNodeIterator(start) +} + +// hashKey returns the hash of key as an ephemeral buffer. +// The caller must not hold onto the return value because it will become +// invalid on the next call to hashKey or secKey. +func (t *StateTrie) hashKey(key []byte) []byte { + h := newHasher(false) + h.sha.Reset() + h.sha.Write(key) + h.sha.Read(t.hashKeyBuf[:]) + returnHasherToPool(h) + return t.hashKeyBuf[:] +} + +// getSecKeyCache returns the current secure key cache, creating a new one if +// ownership changed (i.e. the current secure trie is a copy of another owning +// the actual cache). +func (t *StateTrie) getSecKeyCache() map[string][]byte { + if t != t.secKeyCacheOwner { + t.secKeyCacheOwner = t + t.secKeyCache = make(map[string][]byte) + } + return t.secKeyCache +} + +func (t *StateTrie) IsVerkle() bool { + return false +} diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go new file mode 100644 index 0000000..59958d3 --- /dev/null +++ b/trie/secure_trie_test.go @@ -0,0 +1,149 @@ +// 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 trie + +import ( + "bytes" + "fmt" + "runtime" + "sync" + "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" +) + +func newEmptySecure() *StateTrie { + trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + return trie +} + +// makeTestStateTrie creates a large enough secure trie for testing. +func makeTestStateTrie() (*testDb, *StateTrie, map[string][]byte) { + // Create an empty trie + triedb := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), triedb) + + // Fill it with some arbitrary data + content := make(map[string][]byte) + for i := byte(0); i < 255; i++ { + // Map the same data under multiple keys + key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i} + content[string(key)] = val + trie.MustUpdate(key, val) + + key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i} + content[string(key)] = val + trie.MustUpdate(key, val) + + // Add some other data to inflate the trie + for j := byte(3); j < 13; j++ { + key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i} + content[string(key)] = val + trie.MustUpdate(key, val) + } + } + root, nodes := trie.Commit(false) + if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)); err != nil { + panic(fmt.Errorf("failed to commit db %v", err)) + } + // Re-create the trie based on the new state + trie, _ = NewStateTrie(TrieID(root), triedb) + return triedb, trie, content +} + +func TestSecureDelete(t *testing.T) { + trie := newEmptySecure() + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"ether", ""}, + {"dog", "puppy"}, + {"shaman", ""}, + } + for _, val := range vals { + if val.v != "" { + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } else { + trie.MustDelete([]byte(val.k)) + } + } + hash := trie.Hash() + exp := common.HexToHash("29b235a58c3c25ab83010c327d5932bcf05324b7d6b1185e650798034783ca9d") + if hash != exp { + t.Errorf("expected %x got %x", exp, hash) + } +} + +func TestSecureGetKey(t *testing.T) { + trie := newEmptySecure() + trie.MustUpdate([]byte("foo"), []byte("bar")) + + key := []byte("foo") + value := []byte("bar") + seckey := crypto.Keccak256(key) + + if !bytes.Equal(trie.MustGet(key), value) { + t.Errorf("Get did not return bar") + } + if k := trie.GetKey(seckey); !bytes.Equal(k, key) { + t.Errorf("GetKey returned %q, want %q", k, key) + } +} + +func TestStateTrieConcurrency(t *testing.T) { + // Create an initial trie and copy if for concurrent access + _, trie, _ := makeTestStateTrie() + + threads := runtime.NumCPU() + tries := make([]*StateTrie, threads) + for i := 0; i < threads; i++ { + tries[i] = trie.Copy() + } + // Start a batch of goroutines interacting with the trie + pend := new(sync.WaitGroup) + pend.Add(threads) + for i := 0; i < threads; i++ { + go func(index int) { + defer pend.Done() + + for j := byte(0); j < 255; j++ { + // Map the same data under multiple keys + key, val := common.LeftPadBytes([]byte{byte(index), 1, j}, 32), []byte{j} + tries[index].MustUpdate(key, val) + + key, val = common.LeftPadBytes([]byte{byte(index), 2, j}, 32), []byte{j} + tries[index].MustUpdate(key, val) + + // Add some other data to inflate the trie + for k := byte(3); k < 13; k++ { + key, val = common.LeftPadBytes([]byte{byte(index), k, j}, 32), []byte{k, j} + tries[index].MustUpdate(key, val) + } + } + tries[index].Commit(false) + }(i) + } + // Wait for all threads to finish + pend.Wait() +} diff --git a/trie/stacktrie.go b/trie/stacktrie.go new file mode 100644 index 0000000..9c574db --- /dev/null +++ b/trie/stacktrie.go @@ -0,0 +1,391 @@ +// 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" + "errors" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +var ( + stPool = sync.Pool{New: func() any { return new(stNode) }} + _ = types.TrieHasher((*StackTrie)(nil)) +) + +// OnTrieNode is a callback method invoked when a trie node is committed +// by the stack trie. The node is only committed if it's considered complete. +// +// The caller should not modify the contents of the returned path and blob +// slice, and their contents may be changed after the call. It is up to the +// `onTrieNode` receiver function to deep-copy the data if it wants to retain +// it after the call ends. +type OnTrieNode func(path []byte, hash common.Hash, blob []byte) + +// StackTrie is a trie implementation that expects keys to be inserted +// in order. Once it determines that a subtree will no longer be inserted +// into, it will hash it and free up the memory it uses. +type StackTrie struct { + root *stNode + h *hasher + last []byte + onTrieNode OnTrieNode +} + +// NewStackTrie allocates and initializes an empty trie. The committed nodes +// will be discarded immediately if no callback is configured. +func NewStackTrie(onTrieNode OnTrieNode) *StackTrie { + return &StackTrie{ + root: stPool.Get().(*stNode), + h: newHasher(false), + onTrieNode: onTrieNode, + } +} + +// 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)") + } + 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") + } + if t.last == nil { + t.last = append([]byte{}, k...) // allocate key slice + } else { + t.last = append(t.last[:0], k...) // reuse key slice + } + t.insert(t.root, k, value, nil) + return nil +} + +// Reset resets the stack trie object to empty state. +func (t *StackTrie) Reset() { + t.root = stPool.Get().(*stNode) + t.last = nil +} + +// 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) +} + +// 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 +} + +// 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 stNode#nodeType can hold +const ( + emptyNode = iota + branchNode + extNode + leafNode + hashedNode +) + +func (n *stNode) reset() *stNode { + n.key = n.key[:0] + n.val = nil + for i := range n.children { + n.children[i] = nil + } + 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 (n *stNode) getDiffIndex(key []byte) int { + for idx, nibble := range n.key { + if nibble != key[idx] { + return idx + } + } + return len(n.key) +} + +// Helper function to that inserts a (key, value) pair into +// the trie. +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].typ != hashedNode { + t.hash(st.children[i], append(path, byte(i))) + } + break + } + } + + // Add new child + if st.children[idx] == nil { + st.children[idx] = newLeaf(key[1:], value) + } else { + t.insert(st.children[idx], key[1:], value, append(path, key[0])) + } + + case extNode: /* Ext */ + // Compare both key chunks and see where they differ + diffidx := st.getDiffIndex(key) + + // Check if chunks are identical. If so, recurse into + // the child node. Otherwise, the key has to be split + // into 1) an optional common prefix, 2) the fullnode + // representing the two differing path, and 3) a leaf + // for each of the differentiated subtrees. + if diffidx == len(st.key) { + // Ext key and key segment are identical, recurse into + // the child node. + 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 *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.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] + t.hash(n, append(path, st.key...)) + } + 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.typ = branchNode + } else { + // the common prefix is at least one byte + // long, insert a new intermediate branch + // node. + st.children[0] = stPool.Get().(*stNode) + st.children[0].typ = branchNode + p = st.children[0] + } + // Create a leaf for the inserted part + o := newLeaf(key[diffidx+1:], value) + + // Insert both child leaves where they belong: + origIdx := st.key[diffidx] + newIdx := key[diffidx] + p.children[origIdx] = n + p.children[newIdx] = o + st.key = st.key[:diffidx] + + case leafNode: /* Leaf */ + // Compare both key chunks and see where they differ + diffidx := st.getDiffIndex(key) + + // Overwriting a key isn't supported, which means that + // the current leaf is expected to be split into 1) an + // optional extension for the common prefix of these 2 + // keys, 2) a fullnode selecting the path on which the + // keys differ, and 3) one leaf for the differentiated + // component of each key. + if diffidx >= len(st.key) { + panic("Trying to insert into existing key") + } + + // 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 *stNode + if diffidx == 0 { + // Convert current leaf into a branch + st.typ = branchNode + p = st + st.children[0] = nil + } else { + // Convert current node into an ext, + // and insert a child branch node. + st.typ = extNode + st.children[0] = stPool.Get().(*stNode) + st.children[0].typ = branchNode + p = st.children[0] + } + + // Create the two child leaves: one containing the original + // 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.key[diffidx+1:], st.val) + t.hash(p.children[origIdx], append(path, st.key[:diffidx+1]...)) + + newIdx := key[diffidx] + p.children[newIdx] = newLeaf(key[diffidx+1:], value) + + // Finally, cut off the key part that has been passed + // over to the children. + st.key = st.key[:diffidx] + st.val = nil + + case emptyNode: /* Empty */ + st.typ = leafNode + st.key = key + st.val = value + + case hashedNode: + panic("trying to insert into hash") + + default: + panic("invalid type") + } +} + +// hash converts st into a 'hashedNode', if possible. Possible outcomes: +// +// 1. The rlp-encoded value was >= 32 bytes: +// - Then the 32-byte `hash` will be accessible in `st.val`. +// - And the 'st.type' will be 'hashedNode' +// +// 2. The rlp-encoded value was < 32 bytes +// - Then the <32 byte rlp-encoded value will be accessible in 'st.val'. +// - And the 'st.type' will be 'hashedNode' AGAIN +// +// This method also sets 'st.type' to hashedNode, and clears 'st.key'. +func (t *StackTrie) hash(st *stNode, path []byte) { + var blob []byte // RLP-encoded node blob + switch st.typ { + case hashedNode: + return + + case emptyNode: + st.val = types.EmptyRootHash.Bytes() + st.key = st.key[:0] + st.typ = hashedNode + return + + case branchNode: + var nodes fullNode + for i, child := range st.children { + if child == nil { + nodes.Children[i] = nilValueNode + continue + } + 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) + } + st.children[i] = nil + stPool.Put(child.reset()) // Release child back to pool. + } + nodes.encode(t.h.encbuf) + blob = t.h.encodedBytes() + + case extNode: + // recursively hash and commit child as the first step + t.hash(st.children[0], append(path, st.key...)) + + // 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() + + 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: hexToCompactInPlace(st.key), Val: valueNode(st.val)} + + n.encode(t.h.encbuf) + blob = t.h.encodedBytes() + + default: + panic("invalid node type") + } + // Convert the node type to hashNode and reset the key slice. + st.typ = hashedNode + st.key = st.key[:0] + + // Skip committing the non-root node if the size is smaller than 32 bytes + // as tiny nodes are always embedded in their parent except root node. + 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 = t.h.hashData(blob) + + // Invoke the callback it's provided. Notably, the path and blob slices are + // volatile, please deep-copy the slices in callback if the contents need + // to be retained. + if t.onTrieNode != nil { + t.onTrieNode(path, common.BytesToHash(st.val), blob) + } +} + +// Hash will firstly hash the entire trie if it's still not hashed and then commit +// all leftover 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. +func (t *StackTrie) Hash() common.Hash { + n := t.root + t.hash(n, nil) + return common.BytesToHash(n.val) +} diff --git a/trie/stacktrie_fuzzer_test.go b/trie/stacktrie_fuzzer_test.go new file mode 100644 index 0000000..df487d1 --- /dev/null +++ b/trie/stacktrie_fuzzer_test.go @@ -0,0 +1,147 @@ +// 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" + "slices" + "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" +) + +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: crypto.NewKeccakState()} + dbA = newTestDatabase(rawdb.NewDatabase(spongeA), rawdb.HashScheme) + trieA = NewEmpty(dbA) + spongeB = &spongeDb{sponge: crypto.NewKeccakState()} + dbB = newTestDatabase(rawdb.NewDatabase(spongeB), rawdb.HashScheme) + trieB = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme()) + }) + 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 := trieA.Commit(false) + if nodes != nil { + dbA.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + } + // Flush memdb -> disk (sponge) + dbA.Commit(rootA) + + // 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.Update(kv.k, kv.v) + } + rootB := trieB.Hash() + 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 = NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + if crypto.Keccak256Hash(blob) != hash { + panic("invalid node blob") + } + nodeset[string(path)] = common.CopyBytes(blob) + }) + checked int + ) + for _, kv := range vals { + trieC.Update(kv.k, kv.v) + } + rootC := trieC.Hash() + 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 new file mode 100644 index 0000000..f053b51 --- /dev/null +++ b/trie/stacktrie_test.go @@ -0,0 +1,400 @@ +// 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" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" +) + +func TestStackTrieInsertAndHash(t *testing.T) { + type KeyValueHash struct { + K string // Hex string for key. + V string // Value, directly converted to bytes. + H string // Expected root hash after insert of (K, V) to an existing trie. + } + tests := [][]KeyValueHash{ + { // {0:0, 7:0, f:0} + {"00", "v_______________________0___0", "5cb26357b95bb9af08475be00243ceb68ade0b66b5cd816b0c18a18c612d2d21"}, + {"70", "v_______________________0___1", "8ff64309574f7a437a7ad1628e690eb7663cfde10676f8a904a8c8291dbc1603"}, + {"f0", "v_______________________0___2", "9e3a01bd8d43efb8e9d4b5506648150b8e3ed1caea596f84ee28e01a72635470"}, + }, + { // {1:0cc, e:{1:fc, e:fc}} + {"10cc", "v_______________________1___0", "233e9b257843f3dfdb1cce6676cdaf9e595ac96ee1b55031434d852bc7ac9185"}, + {"e1fc", "v_______________________1___1", "39c5e908ae83d0c78520c7c7bda0b3782daf594700e44546e93def8f049cca95"}, + {"eefc", "v_______________________1___2", "d789567559fd76fe5b7d9cc42f3750f942502ac1c7f2a466e2f690ec4b6c2a7c"}, + }, + { // {b:{a:ac, b:ac}, d:acc} + {"baac", "v_______________________2___0", "8be1c86ba7ec4c61e14c1a9b75055e0464c2633ae66a055a24e75450156a5d42"}, + {"bbac", "v_______________________2___1", "8495159b9895a7d88d973171d737c0aace6fe6ac02a4769fff1bc43bcccce4cc"}, + {"dacc", "v_______________________2___2", "9bcfc5b220a27328deb9dc6ee2e3d46c9ebc9c69e78acda1fa2c7040602c63ca"}, + }, + { // {0:0cccc, 2:456{0:0, 2:2} + {"00cccc", "v_______________________3___0", "e57dc2785b99ce9205080cb41b32ebea7ac3e158952b44c87d186e6d190a6530"}, + {"245600", "v_______________________3___1", "0335354adbd360a45c1871a842452287721b64b4234dfe08760b243523c998db"}, + {"245622", "v_______________________3___2", "9e6832db0dca2b5cf81c0e0727bfde6afc39d5de33e5720bccacc183c162104e"}, + }, + { // {1:4567{1:1c, 3:3c}, 3:0cccccc} + {"1456711c", "v_______________________4___0", "f2389e78d98fed99f3e63d6d1623c1d4d9e8c91cb1d585de81fbc7c0e60d3529"}, + {"1456733c", "v_______________________4___1", "101189b3fab852be97a0120c03d95eefcf984d3ed639f2328527de6def55a9c0"}, + {"30cccccc", "v_______________________4___2", "3780ce111f98d15751dfde1eb21080efc7d3914b429e5c84c64db637c55405b3"}, + }, + { // 8800{1:f, 2:e, 3:d} + {"88001f", "v_______________________5___0", "e817db50d84f341d443c6f6593cafda093fc85e773a762421d47daa6ac993bd5"}, + {"88002e", "v_______________________5___1", "d6e3e6047bdc110edd296a4d63c030aec451bee9d8075bc5a198eee8cda34f68"}, + {"88003d", "v_______________________5___2", "b6bdf8298c703342188e5f7f84921a402042d0e5fb059969dd53a6b6b1fb989e"}, + }, + { // 0{1:fc, 2:ec, 4:dc} + {"01fc", "v_______________________6___0", "693268f2ca80d32b015f61cd2c4dba5a47a6b52a14c34f8e6945fad684e7a0d5"}, + {"02ec", "v_______________________6___1", "e24ddd44469310c2b785a2044618874bf486d2f7822603a9b8dce58d6524d5de"}, + {"04dc", "v_______________________6___2", "33fc259629187bbe54b92f82f0cd8083b91a12e41a9456b84fc155321e334db7"}, + }, + { // f{0:fccc, f:ff{0:f, f:f}} + {"f0fccc", "v_______________________7___0", "b0966b5aa469a3e292bc5fcfa6c396ae7a657255eef552ea7e12f996de795b90"}, + {"ffff0f", "v_______________________7___1", "3b1ca154ec2a3d96d8d77bddef0abfe40a53a64eb03cecf78da9ec43799fa3d0"}, + {"ffffff", "v_______________________7___2", "e75463041f1be8252781be0ace579a44ea4387bf5b2739f4607af676f7719678"}, + }, + { // ff{0:f{0:f, f:f}, f:fcc} + {"ff0f0f", "v_______________________8___0", "0928af9b14718ec8262ab89df430f1e5fbf66fac0fed037aff2b6767ae8c8684"}, + {"ff0fff", "v_______________________8___1", "d870f4d3ce26b0bf86912810a1960693630c20a48ba56be0ad04bc3e9ddb01e6"}, + {"ffffcc", "v_______________________8___2", "4239f10dd9d9915ecf2e047d6a576bdc1733ed77a30830f1bf29deaf7d8e966f"}, + }, + { + {"123d", "x___________________________0", "fc453d88b6f128a77c448669710497380fa4588abbea9f78f4c20c80daa797d0"}, + {"123e", "x___________________________1", "5af48f2d8a9a015c1ff7fa8b8c7f6b676233bd320e8fb57fd7933622badd2cec"}, + {"123f", "x___________________________2", "1164d7299964e74ac40d761f9189b2a3987fae959800d0f7e29d3aaf3eae9e15"}, + }, + { + {"123d", "x___________________________0", "fc453d88b6f128a77c448669710497380fa4588abbea9f78f4c20c80daa797d0"}, + {"123e", "x___________________________1", "5af48f2d8a9a015c1ff7fa8b8c7f6b676233bd320e8fb57fd7933622badd2cec"}, + {"124a", "x___________________________2", "661a96a669869d76b7231380da0649d013301425fbea9d5c5fae6405aa31cfce"}, + }, + { + {"123d", "x___________________________0", "fc453d88b6f128a77c448669710497380fa4588abbea9f78f4c20c80daa797d0"}, + {"123e", "x___________________________1", "5af48f2d8a9a015c1ff7fa8b8c7f6b676233bd320e8fb57fd7933622badd2cec"}, + {"13aa", "x___________________________2", "6590120e1fd3ffd1a90e8de5bb10750b61079bb0776cca4414dd79a24e4d4356"}, + }, + { + {"123d", "x___________________________0", "fc453d88b6f128a77c448669710497380fa4588abbea9f78f4c20c80daa797d0"}, + {"123e", "x___________________________1", "5af48f2d8a9a015c1ff7fa8b8c7f6b676233bd320e8fb57fd7933622badd2cec"}, + {"2aaa", "x___________________________2", "f869b40e0c55eace1918332ef91563616fbf0755e2b946119679f7ef8e44b514"}, + }, + { + {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"}, + {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"}, + {"1234fa", "x___________________________2", "4f4e368ab367090d5bc3dbf25f7729f8bd60df84de309b4633a6b69ab66142c0"}, + }, + { + {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"}, + {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"}, + {"1235aa", "x___________________________2", "21840121d11a91ac8bbad9a5d06af902a5c8d56a47b85600ba813814b7bfcb9b"}, + }, + { + {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"}, + {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"}, + {"124aaa", "x___________________________2", "ea4040ddf6ae3fbd1524bdec19c0ab1581015996262006632027fa5cf21e441e"}, + }, + { + {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"}, + {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"}, + {"13aaaa", "x___________________________2", "e4beb66c67e44f2dd8ba36036e45a44ff68f8d52942472b1911a45f886a34507"}, + }, + { + {"1234da", "x___________________________0", "1c4b4462e9f56a80ca0f5d77c0d632c41b0102290930343cf1791e971a045a79"}, + {"1234ea", "x___________________________1", "2f502917f3ba7d328c21c8b45ee0f160652e68450332c166d4ad02d1afe31862"}, + {"2aaaaa", "x___________________________2", "5f5989b820ff5d76b7d49e77bb64f26602294f6c42a1a3becc669cd9e0dc8ec9"}, + }, + { + {"000000", "x___________________________0", "3b32b7af0bddc7940e7364ee18b5a59702c1825e469452c8483b9c4e0218b55a"}, + {"1234da", "x___________________________1", "3ab152a1285dca31945566f872c1cc2f17a770440eda32aeee46a5e91033dde2"}, + {"1234ea", "x___________________________2", "0cccc87f96ddef55563c1b3be3c64fff6a644333c3d9cd99852cb53b6412b9b8"}, + {"1234fa", "x___________________________3", "65bb3aafea8121111d693ffe34881c14d27b128fd113fa120961f251fe28428d"}, + }, + { + {"000000", "x___________________________0", "3b32b7af0bddc7940e7364ee18b5a59702c1825e469452c8483b9c4e0218b55a"}, + {"1234da", "x___________________________1", "3ab152a1285dca31945566f872c1cc2f17a770440eda32aeee46a5e91033dde2"}, + {"1234ea", "x___________________________2", "0cccc87f96ddef55563c1b3be3c64fff6a644333c3d9cd99852cb53b6412b9b8"}, + {"1235aa", "x___________________________3", "f670e4d2547c533c5f21e0045442e2ecb733f347ad6d29ef36e0f5ba31bb11a8"}, + }, + { + {"000000", "x___________________________0", "3b32b7af0bddc7940e7364ee18b5a59702c1825e469452c8483b9c4e0218b55a"}, + {"1234da", "x___________________________1", "3ab152a1285dca31945566f872c1cc2f17a770440eda32aeee46a5e91033dde2"}, + {"1234ea", "x___________________________2", "0cccc87f96ddef55563c1b3be3c64fff6a644333c3d9cd99852cb53b6412b9b8"}, + {"124aaa", "x___________________________3", "c17464123050a9a6f29b5574bb2f92f6d305c1794976b475b7fb0316b6335598"}, + }, + { + {"000000", "x___________________________0", "3b32b7af0bddc7940e7364ee18b5a59702c1825e469452c8483b9c4e0218b55a"}, + {"1234da", "x___________________________1", "3ab152a1285dca31945566f872c1cc2f17a770440eda32aeee46a5e91033dde2"}, + {"1234ea", "x___________________________2", "0cccc87f96ddef55563c1b3be3c64fff6a644333c3d9cd99852cb53b6412b9b8"}, + {"13aaaa", "x___________________________3", "aa8301be8cb52ea5cd249f5feb79fb4315ee8de2140c604033f4b3fff78f0105"}, + }, + { + {"0000", "x___________________________0", "cb8c09ad07ae882136f602b3f21f8733a9f5a78f1d2525a8d24d1c13258000b2"}, + {"123d", "x___________________________1", "8f09663deb02f08958136410dc48565e077f76bb6c9d8c84d35fc8913a657d31"}, + {"123e", "x___________________________2", "0d230561e398c579e09a9f7b69ceaf7d3970f5a436fdb28b68b7a37c5bdd6b80"}, + {"123f", "x___________________________3", "80f7bad1893ca57e3443bb3305a517723a74d3ba831bcaca22a170645eb7aafb"}, + }, + { + {"0000", "x___________________________0", "cb8c09ad07ae882136f602b3f21f8733a9f5a78f1d2525a8d24d1c13258000b2"}, + {"123d", "x___________________________1", "8f09663deb02f08958136410dc48565e077f76bb6c9d8c84d35fc8913a657d31"}, + {"123e", "x___________________________2", "0d230561e398c579e09a9f7b69ceaf7d3970f5a436fdb28b68b7a37c5bdd6b80"}, + {"124a", "x___________________________3", "383bc1bb4f019e6bc4da3751509ea709b58dd1ac46081670834bae072f3e9557"}, + }, + { + {"0000", "x___________________________0", "cb8c09ad07ae882136f602b3f21f8733a9f5a78f1d2525a8d24d1c13258000b2"}, + {"123d", "x___________________________1", "8f09663deb02f08958136410dc48565e077f76bb6c9d8c84d35fc8913a657d31"}, + {"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"}, + }, + } + 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 := NewStackTrie(nil) + for j := 0; j < l; j++ { + kv := &test[j] + if err := st.Update(common.FromHex(kv.K), []byte(kv.V)); err != nil { + t.Fatal(err) + } + } + expected := common.HexToHash(test[l-1].H) + if h := st.Hash(); h != expected { + t.Errorf("%d(%d): root hash mismatch: %x, expected %x", i, l, h, expected) + } + } + } +} + +func TestSizeBug(t *testing.T) { + st := NewStackTrie(nil) + nt := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + + leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + + nt.Update(leaf, value) + st.Update(leaf, value) + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +func TestEmptyBug(t *testing.T) { + st := NewStackTrie(nil) + nt := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + + //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + kvs := []struct { + K string + V string + }{ + {K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "9496f4ec2bf9dab484cac6be589e8417d84781be08"}, + {K: "40edb63a35fcf86c08022722aa3287cdd36440d671b4918131b2514795fefa9c", V: "01"}, + {K: "b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", V: "947a30f7736e48d6599356464ba4c150d8da0302ff"}, + {K: "c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b", V: "02"}, + } + + for _, kv := range kvs { + nt.Update(common.FromHex(kv.K), common.FromHex(kv.V)) + st.Update(common.FromHex(kv.K), common.FromHex(kv.V)) + } + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +func TestValLength56(t *testing.T) { + st := NewStackTrie(nil) + nt := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + + //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + kvs := []struct { + K string + V string + }{ + {K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"}, + } + + for _, kv := range kvs { + nt.Update(common.FromHex(kv.K), common.FromHex(kv.V)) + st.Update(common.FromHex(kv.K), common.FromHex(kv.V)) + } + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +// TestUpdateSmallNodes tests a case where the leaves are small (both key and value), +// which causes a lot of node-within-node. This case was found via fuzzing. +func TestUpdateSmallNodes(t *testing.T) { + st := NewStackTrie(nil) + nt := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + kvs := []struct { + K string + V string + }{ + {"63303030", "3041"}, // stacktrie.Update + {"65", "3000"}, // stacktrie.Update + } + for _, kv := range kvs { + nt.Update(common.FromHex(kv.K), common.FromHex(kv.V)) + st.Update(common.FromHex(kv.K), common.FromHex(kv.V)) + } + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +// TestUpdateVariableKeys contains a case which stacktrie fails: when keys of different +// sizes are used, and the second one has the same prefix as the first, then the +// stacktrie fails, since it's unable to 'expand' on an already added leaf. +// For all practical purposes, this is fine, since keys are fixed-size length +// in account and storage tries. +// +// The test is marked as 'skipped', and exists just to have the behaviour documented. +// This case was found via fuzzing. +func TestUpdateVariableKeys(t *testing.T) { + t.SkipNow() + st := NewStackTrie(nil) + nt := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + kvs := []struct { + K string + V string + }{ + {"0x33303534636532393561313031676174", "303030"}, + {"0x3330353463653239356131303167617430", "313131"}, + } + for _, kv := range kvs { + nt.Update(common.FromHex(kv.K), common.FromHex(kv.V)) + st.Update(common.FromHex(kv.K), common.FromHex(kv.V)) + } + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +// TestStacktrieNotModifyValues checks that inserting blobs of data into the +// stacktrie does not mutate the blobs +func TestStacktrieNotModifyValues(t *testing.T) { + st := NewStackTrie(nil) + { // Test a very small trie + // Give it the value as a slice with large backing alloc, + // so if the stacktrie tries to append, it won't have to realloc + value := make([]byte, 1, 100) + value[0] = 0x2 + want := common.CopyBytes(value) + st.Update([]byte{0x01}, value) + st.Hash() + if have := value; !bytes.Equal(have, want) { + t.Fatalf("tiny trie: have %#x want %#x", have, want) + } + st = NewStackTrie(nil) + } + // Test with a larger trie + keyB := big.NewInt(1) + keyDelta := big.NewInt(1) + var vals [][]byte + getValue := func(i int) []byte { + if i%2 == 0 { // large + return crypto.Keccak256(big.NewInt(int64(i)).Bytes()) + } else { //small + return big.NewInt(int64(i)).Bytes() + } + } + for i := 0; i < 1000; i++ { + key := common.BigToHash(keyB) + value := getValue(i) + st.Update(key.Bytes(), value) + vals = append(vals, value) + keyB = keyB.Add(keyB, keyDelta) + keyDelta.Add(keyDelta, common.Big1) + } + st.Hash() + for i := 0; i < 1000; i++ { + want := getValue(i) + + have := vals[i] + if !bytes.Equal(have, want) { + t.Fatalf("item %d, have %#x want %#x", i, 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 new file mode 100644 index 0000000..3b7caae --- /dev/null +++ b/trie/sync.go @@ -0,0 +1,768 @@ +// 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 trie + +import ( + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/prque" + "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/log" + "github.com/ethereum/go-ethereum/metrics" +) + +// ErrNotRequested is returned by the trie sync when it's requested to process a +// node it did not request. +var ErrNotRequested = errors.New("not requested") + +// ErrAlreadyProcessed is returned by the trie sync when it's requested to process a +// node it already processed previously. +var ErrAlreadyProcessed = errors.New("already processed") + +// maxFetchesPerDepth is the maximum number of pending trie nodes per depth. The +// role of this value is to limit the number of trie nodes that get expanded in +// 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). +// +// Content wise the tuple either has 1 element if it addresses a node in a single +// trie or 2 elements if it addresses a node in a stacked trie. +// +// To support aiming arbitrary trie nodes, the path needs to support odd nibble +// lengths. To avoid transferring expanded hex form over the network, the last +// part of the tuple (which needs to index into the middle of a trie) is compact +// encoded. In case of a 2-tuple, the first item is always 32 bytes so that is +// simple binary encoded. +// +// Examples: +// - Path 0x9 -> {0x19} +// - Path 0x99 -> {0x0099} +// - Path 0x01234567890123456789012345678901012345678901234567890123456789019 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x19} +// - Path 0x012345678901234567890123456789010123456789012345678901234567890199 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x0099} +type SyncPath [][]byte + +// NewSyncPath converts an expanded trie path from nibble form into a compact +// version that can be sent over the network. +func NewSyncPath(path []byte) SyncPath { + // If the hash is from the account trie, append a single item, if it + // is from a storage trie, append a tuple. Note, the length 64 is + // clashing between account leaf and storage root. It's fine though + // because having a trie node at 64 depth means a hash collision was + // found and we're long dead. + if len(path) < 64 { + return SyncPath{hexToCompact(path)} + } + return SyncPath{hexToKeybytes(path[:64]), hexToCompact(path[64:])} +} + +// LeafCallback is a callback type invoked when a trie operation reaches a leaf +// node. +// +// The keys is a path tuple identifying a particular trie node either in a single +// trie (account) or a layered trie (account -> storage). Each key in the tuple +// is in the raw format(32 bytes). +// +// The path is a composite hexary path identifying the trie node. All the key +// bytes are converted to the hexary nibbles and composited with the parent path +// if the trie node is in a layered trie. +// +// It's used by state sync and commit to allow handling external references +// between account and storage tries. And also it's used in the state healing +// for extracting the raw states(leaf nodes) with corresponding paths. +type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error + +// nodeRequest represents a scheduled or already in-flight trie node retrieval request. +type nodeRequest struct { + hash common.Hash // Hash of the trie node to retrieve + path []byte // Merkle path leading to this node for prioritization + data []byte // Data content of the node, cached until all subtrees complete + + parent *nodeRequest // Parent state node referencing this entry + deps int // Number of dependencies before allowed to commit this node + callback LeafCallback // Callback to invoke if a leaf node it reached on this branch +} + +// codeRequest represents a scheduled or already in-flight bytecode retrieval request. +type codeRequest struct { + hash common.Hash // Hash of the contract bytecode to retrieve + path []byte // Merkle path leading to this node for prioritization + data []byte // Data content of the node, cached until all subtrees complete + parents []*nodeRequest // Parent state nodes referencing this entry (notify all upon completion) +} + +// NodeSyncResult is a response with requested trie node along with its node path. +type NodeSyncResult struct { + Path string // Path of the originally unknown trie node + Data []byte // Data content of the retrieved trie node +} + +// CodeSyncResult is a response with requested bytecode along with its hash. +type CodeSyncResult struct { + Hash common.Hash // Hash the originally unknown bytecode + 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 { + del bool // flag if op stands for a delete operation + 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) +} + +// valid checks whether the node operation is valid. +func (op *nodeOp) valid() bool { + if op.del && len(op.blob) != 0 { + return false + } + if !op.del && len(op.blob) == 0 { + return false + } + return true +} + +// string returns the node operation in string representation. +func (op *nodeOp) string() string { + var node string + if op.owner == (common.Hash{}) { + node = fmt.Sprintf("node: (%v)", op.path) + } else { + node = fmt.Sprintf("node: (%x-%v)", op.owner, op.path) + } + var blobHex string + if len(op.blob) == 0 { + blobHex = "nil" + } else { + blobHex = hexutil.Encode(op.blob) + } + if op.del { + return fmt.Sprintf("del %s %s %s", node, blobHex, op.hash.Hex()) + } + return fmt.Sprintf("write %s %s %s", node, blobHex, op.hash.Hex()) +} + +// syncMemBatch is an in-memory buffer of successfully downloaded but not yet +// persisted data items. +type syncMemBatch struct { + 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(scheme string) *syncMemBatch { + return &syncMemBatch{ + scheme: scheme, + codes: make(map[common.Hash][]byte), + } +} + +// 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{ + del: true, + 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. +type Sync struct { + scheme string // Node scheme descriptor used in database. + database ethdb.KeyValueReader // Persistent database to check for existing entries + membatch *syncMemBatch // Memory buffer to avoid frequent database writes + nodeReqs map[string]*nodeRequest // Pending requests pertaining to a trie node path + codeReqs map[common.Hash]*codeRequest // Pending requests pertaining to a code hash + queue *prque.Prque[int64, any] // Priority queue with the pending requests + fetches map[int]int // Number of active fetches per trie node depth +} + +// NewSync creates a new trie data download scheduler. +func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallback, scheme string) *Sync { + ts := &Sync{ + scheme: scheme, + database: database, + 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 + fetches: make(map[int]int), + } + ts.AddSubTrie(root, nil, common.Hash{}, nil, callback) + return ts +} + +// AddSubTrie registers a new trie to the sync code, rooted at the designated +// 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) { + if root == types.EmptyRootHash { + return + } + owner, inner := ResolvePath(path) + 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{ + hash: root, + path: path, + callback: callback, + } + // If this sub-trie has a designated parent, link them together + if parent != (common.Hash{}) { + ancestor := s.nodeReqs[string(parentPath)] + if ancestor == nil { + panic(fmt.Sprintf("sub-trie ancestor not found: %x", parent)) + } + ancestor.deps++ + req.parent = ancestor + } + s.scheduleNodeRequest(req) +} + +// AddCodeEntry schedules the direct retrieval of a contract code that should not +// be interpreted as a trie node, but rather accepted and stored into the database +// as is. +func (s *Sync) AddCodeEntry(hash common.Hash, path []byte, parent common.Hash, parentPath []byte) { + // Short circuit if the entry is empty or already known + if hash == types.EmptyCodeHash { + return + } + if s.membatch.hasCode(hash) { + return + } + // If database says duplicate, the blob is present for sure. + // Note we only check the existence with new code scheme, snap + // sync is expected to run with a fresh new node. Even there + // exists the code with legacy format, fetch and store with + // new scheme anyway. + if rawdb.HasCodeWithPrefix(s.database, hash) { + return + } + // Assemble the new sub-trie sync request + req := &codeRequest{ + path: path, + hash: hash, + } + // If this sub-trie has a designated parent, link them together + if parent != (common.Hash{}) { + ancestor := s.nodeReqs[string(parentPath)] // the parent of codereq can ONLY be nodereq + if ancestor == nil { + panic(fmt.Sprintf("raw-entry ancestor not found: %x", parent)) + } + ancestor.deps++ + req.parents = append(req.parents, ancestor) + } + s.scheduleCodeRequest(req) +} + +// Missing retrieves the known missing nodes from the trie for retrieval. To aid +// both eth/6x style fast sync and snap/1x style state sync, the paths of trie +// nodes are returned too, as well as separate hash list for codes. +func (s *Sync) Missing(max int) ([]string, []common.Hash, []common.Hash) { + var ( + nodePaths []string + nodeHashes []common.Hash + codeHashes []common.Hash + ) + for !s.queue.Empty() && (max == 0 || len(nodeHashes)+len(codeHashes) < max) { + // Retrieve the next item in line + item, prio := s.queue.Peek() + + // If we have too many already-pending tasks for this depth, throttle + depth := int(prio >> 56) + if s.fetches[depth] > maxFetchesPerDepth { + break + } + // Item is allowed to be scheduled, add it to the task list + s.queue.Pop() + s.fetches[depth]++ + + switch item := item.(type) { + case common.Hash: + codeHashes = append(codeHashes, item) + case string: + req, ok := s.nodeReqs[item] + if !ok { + log.Error("Missing node request", "path", item) + continue // System very wrong, shouldn't happen + } + nodePaths = append(nodePaths, item) + nodeHashes = append(nodeHashes, req.hash) + } + } + return nodePaths, nodeHashes, codeHashes +} + +// ProcessCode injects the received data for requested item. Note it can +// 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 +// there is no downside. +func (s *Sync) ProcessCode(result CodeSyncResult) error { + // If the code was not requested or it's already processed, bail out + req := s.codeReqs[result.Hash] + if req == nil { + return ErrNotRequested + } + if req.data != nil { + return ErrAlreadyProcessed + } + req.data = result.Data + return s.commitCodeRequest(req) +} + +// ProcessNode injects the received data for requested item. Note it can +// 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 +// there is no downside. +func (s *Sync) ProcessNode(result NodeSyncResult) error { + // If the trie node was not requested or it's already processed, bail out + req := s.nodeReqs[result.Path] + if req == nil { + return ErrNotRequested + } + if req.data != nil { + return ErrAlreadyProcessed + } + // Decode the node data content and update the request + node, err := decodeNode(req.hash.Bytes(), result.Data) + if err != nil { + return err + } + req.data = result.Data + + // Create and schedule a request for all the children nodes + requests, err := s.children(req, node) + if err != nil { + return err + } + if len(requests) == 0 && req.deps == 0 { + s.commitNodeRequest(req) + } else { + req.deps += len(requests) + for _, child := range requests { + s.scheduleNodeRequest(child) + } + } + return nil +} + +// Commit flushes the data stored in the internal membatch out to persistent +// 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 { + // Flush the pending node writes into database batch. + var ( + account int + storage int + ) + for _, op := range s.membatch.nodes { + if !op.valid() { + return fmt.Errorf("invalid op, %s", op.string()) + } + if op.del { + // 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) + } + codeSyncedGauge.Inc(int64(len(s.membatch.codes))) + + s.membatch = newSyncMemBatch(s.scheme) // reset the batch + return nil +} + +// MemSize returns an estimated size (in bytes) of the data held in the membatch. +func (s *Sync) MemSize() uint64 { + return s.membatch.size +} + +// Pending returns the number of state entries currently pending for download. +func (s *Sync) Pending() int { + return len(s.nodeReqs) + len(s.codeReqs) +} + +// 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) { + s.nodeReqs[string(req.path)] = req + + // Schedule the request for future retrieval. This queue is shared + // by both node requests and code requests. + prio := int64(len(req.path)) << 56 // depth >= 128 will never happen, storage leaves will be included in their parents + for i := 0; i < 14 && i < len(req.path); i++ { + prio |= int64(15-req.path[i]) << (52 - i*4) // 15-nibble => lexicographic order + } + s.queue.Push(string(req.path), prio) +} + +// 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) { + // If we're already requesting this node, add a new reference and stop + if old, ok := s.codeReqs[req.hash]; ok { + old.parents = append(old.parents, req.parents...) + return + } + s.codeReqs[req.hash] = req + + // Schedule the request for future retrieval. This queue is shared + // by both node requests and code requests. + prio := int64(len(req.path)) << 56 // depth >= 128 will never happen, storage leaves will be included in their parents + for i := 0; i < 14 && i < len(req.path); i++ { + prio |= int64(15-req.path[i]) << (52 - i*4) // 15-nibble => lexicographic order + } + s.queue.Push(req.hash, prio) +} + +// children retrieves all the missing children of a state trie entry for future +// retrieval scheduling. +func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { + // Gather all the children of the node, irrelevant whether known or not + type childNode struct { + path []byte + node node + } + var children []childNode + + switch node := (object).(type) { + case *shortNode: + key := node.Key + if hasTerm(key) { + key = key[:len(key)-1] + } + children = []childNode{{ + 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.HasAccountTrieNode(s.database, append(inner, key[:i]...)) + } else { + exists = rawdb.HasStorageTrieNode(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 { + children = append(children, childNode{ + node: node.Children[i], + path: append(append([]byte(nil), req.path...), byte(i)), + }) + } + } + default: + panic(fmt.Sprintf("unknown node: %+v", node)) + } + // Iterate over the children, and request all unknown ones + 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 + if req.callback != nil { + if node, ok := (child.node).(valueNode); ok { + var paths [][]byte + if len(child.path) == 2*common.HashLength { + paths = append(paths, hexToKeybytes(child.path)) + } else if len(child.path) == 4*common.HashLength { + paths = append(paths, hexToKeybytes(child.path[:2*common.HashLength])) + paths = append(paths, hexToKeybytes(child.path[2*common.HashLength:])) + } + if err := req.callback(paths, child.path, node, req.hash, req.path); err != nil { + return nil, err + } + } + } + // If the child references another node, resolve or schedule. + // We check all children concurrently. + if node, ok := (child.node).(hashNode); ok { + path := child.path + hash := common.BytesToHash(node) + pending.Add(1) + go func() { + defer pending.Done() + 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: path, + hash: hash, + parent: req, + callback: req.callback, + } + }() + } + } + pending.Wait() + + requests := make([]*nodeRequest, 0, len(children)) + for done := false; !done; { + select { + case miss := <-missing: + requests = append(requests, miss) + default: + done = true + } + } + return requests, nil +} + +// 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 + 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)]-- + + // Check parent for completion + if req.parent != nil { + req.parent.deps-- + if req.parent.deps == 0 { + if err := s.commitNodeRequest(req.parent); err != nil { + return err + } + } + } + return nil +} + +// 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.addCode(req.hash, req.data) + + // Removed the completed code request + delete(s.codeReqs, req.hash) + s.fetches[len(req.path)]-- + + // Check all parents for completion + for _, parent := range req.parents { + parent.deps-- + if parent.deps == 0 { + if err := s.commitNodeRequest(parent); err != nil { + return err + } + } + } + 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 + if owner == (common.Hash{}) { + blob = rawdb.ReadAccountTrieNode(s.database, path) + } else { + blob = rawdb.ReadStorageTrieNode(s.database, owner, path) + } + h := newBlobHasher() + defer h.release() + exists = hash == h.hash(blob) + 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) { + var owner common.Hash + if len(path) >= 2*common.HashLength { + owner = common.BytesToHash(hexToKeybytes(path[:2*common.HashLength])) + path = path[2*common.HashLength:] + } + return owner, path +} + +// blobHasher is used to compute the sha256 hash of the provided data. +type blobHasher struct{ state crypto.KeccakState } + +// blobHasherPool is the pool for reusing pre-allocated hash state. +var blobHasherPool = sync.Pool{ + New: func() interface{} { return &blobHasher{state: crypto.NewKeccakState()} }, +} + +func newBlobHasher() *blobHasher { + return blobHasherPool.Get().(*blobHasher) +} + +func (h *blobHasher) hash(data []byte) common.Hash { + return crypto.HashData(h.state, data) +} + +func (h *blobHasher) release() { + blobHasherPool.Put(h) +} diff --git a/trie/sync_test.go b/trie/sync_test.go new file mode 100644 index 0000000..ccdee7d --- /dev/null +++ b/trie/sync_test.go @@ -0,0 +1,1002 @@ +// 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 trie + +import ( + "bytes" + "fmt" + "maps" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +// makeTestTrie create a sample test trie to test node-wise reconstruction. +func makeTestTrie(scheme string) (ethdb.Database, *testDb, *StateTrie, map[string][]byte) { + // Create an empty trie + db := rawdb.NewMemoryDatabase() + triedb := newTestDatabase(db, scheme) + trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), triedb) + + // Fill it with some arbitrary data + content := make(map[string][]byte) + for i := byte(0); i < 255; i++ { + // Map the same data under multiple keys + key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i} + content[string(key)] = val + trie.MustUpdate(key, val) + + key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i} + content[string(key)] = val + trie.MustUpdate(key, val) + + // Add some other data to inflate the trie + for j := byte(3); j < 13; j++ { + key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i} + content[string(key)] = val + trie.MustUpdate(key, val) + } + } + root, nodes := trie.Commit(false) + if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)); err != nil { + panic(fmt.Errorf("failed to commit db %v", err)) + } + if err := triedb.Commit(root); err != nil { + panic(err) + } + // Re-create the trie based on the new state + trie, _ = NewStateTrie(TrieID(root), triedb) + return db, triedb, trie, content +} + +// 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, rawTrie bool) { + // Check root availability and trie contents + ndb := newTestDatabase(db, scheme) + 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 := 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, rawTrie bool) error { + ndb := newTestDatabase(db, scheme) + 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) + } + for it.Next(true) { + } + return it.Error() +} + +// trieElement represents the element in the state trie(bytecode or trie node). +type trieElement struct { + path string + hash common.Hash + syncPath SyncPath +} + +// Tests that an empty trie is not scheduled for syncing. +func TestEmptySync(t *testing.T) { + dbA := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + dbB := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + dbC := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) + dbD := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) + + emptyA := NewEmpty(dbA) + emptyB, _ := New(TrieID(types.EmptyRootHash), dbB) + emptyC := NewEmpty(dbC) + emptyD, _ := New(TrieID(types.EmptyRootHash), dbD) + + for i, trie := range []*Trie{emptyA, emptyB, emptyC, emptyD} { + sync := NewSync(trie.Hash(), memorydb.New(), nil, []*testDb{dbA, dbB, dbC, dbD}[i].Scheme()) + if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 { + t.Errorf("test %d: content requested for empty trie: %v, %v, %v", i, paths, nodes, codes) + } + } +} + +// 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. +func TestIterativeSync(t *testing.T) { + testIterativeSync(t, 1, false, rawdb.HashScheme) + testIterativeSync(t, 100, false, rawdb.HashScheme) + testIterativeSync(t, 1, true, rawdb.HashScheme) + testIterativeSync(t, 100, true, rawdb.HashScheme) + testIterativeSync(t, 1, false, rawdb.PathScheme) + testIterativeSync(t, 100, false, rawdb.PathScheme) + testIterativeSync(t, 1, true, rawdb.PathScheme) + testIterativeSync(t, 100, true, rawdb.PathScheme) +} + +func testIterativeSync(t *testing.T, count int, bypath bool, scheme string) { + // Create a random trie to copy + _, srcDb, srcTrie, srcData := makeTestTrie(scheme) + + // Create a destination trie and sync with the scheduler + diskdb := rawdb.NewMemoryDatabase() + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) + + // The code requests are ignored here since there is no code + // at the testing trie. + paths, nodes, _ := sched.Missing(count) + var elements []trieElement + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } + for len(elements) > 0 { + results := make([]NodeSyncResult, len(elements)) + if !bypath { + for i, element := range elements { + owner, inner := ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for hash %x: %v", element.hash, err) + } + results[i] = NodeSyncResult{element.path, data} + } + } else { + for i, element := range elements { + data, _, err := srcTrie.GetNode(element.syncPath[len(element.syncPath)-1]) + if err != nil { + t.Fatalf("failed to retrieve node data for path %x: %v", element.path, err) + } + results[i] = NodeSyncResult{element.path, data} + } + } + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + batch := diskdb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + paths, nodes, _ = sched.Missing(count) + elements = elements[:0] + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + } + // Cross check that the two tries are in sync + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) +} + +// Tests that the trie scheduler can correctly reconstruct the state even if only +// partial results are returned, and the others sent only later. +func TestIterativeDelayedSync(t *testing.T) { + testIterativeDelayedSync(t, rawdb.HashScheme) + testIterativeDelayedSync(t, rawdb.PathScheme) +} + +func testIterativeDelayedSync(t *testing.T, scheme string) { + // Create a random trie to copy + _, srcDb, srcTrie, srcData := makeTestTrie(scheme) + + // Create a destination trie and sync with the scheduler + diskdb := rawdb.NewMemoryDatabase() + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) + + // The code requests are ignored here since there is no code + // at the testing trie. + paths, nodes, _ := sched.Missing(10000) + var elements []trieElement + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } + for len(elements) > 0 { + // Sync only half of the scheduled nodes + results := make([]NodeSyncResult, len(elements)/2+1) + for i, element := range elements[:len(results)] { + owner, inner := ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) + } + results[i] = NodeSyncResult{element.path, data} + } + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + batch := diskdb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + paths, nodes, _ = sched.Missing(10000) + elements = elements[len(results):] + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + } + // Cross check that the two tries are in sync + 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, +// requesting retrieval tasks and returning all of them in one go, however in a +// random order. +func TestIterativeRandomSyncIndividual(t *testing.T) { + testIterativeRandomSync(t, 1, rawdb.HashScheme) + testIterativeRandomSync(t, 100, rawdb.HashScheme) + testIterativeRandomSync(t, 1, rawdb.PathScheme) + testIterativeRandomSync(t, 100, rawdb.PathScheme) +} + +func testIterativeRandomSync(t *testing.T, count int, scheme string) { + // Create a random trie to copy + _, srcDb, srcTrie, srcData := makeTestTrie(scheme) + + // Create a destination trie and sync with the scheduler + diskdb := rawdb.NewMemoryDatabase() + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) + + // The code requests are ignored here since there is no code + // at the testing trie. + paths, nodes, _ := sched.Missing(count) + queue := make(map[string]trieElement) + for i, path := range paths { + queue[path] = trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + } + } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } + for len(queue) > 0 { + // Fetch all the queued nodes in a random order + results := make([]NodeSyncResult, 0, len(queue)) + for path, element := range queue { + owner, inner := ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) + } + results = append(results, NodeSyncResult{path, data}) + } + // Feed the retrieved results back and queue new tasks + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + batch := diskdb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + paths, nodes, _ = sched.Missing(count) + queue = make(map[string]trieElement) + for i, path := range paths { + queue[path] = trieElement{ + path: path, + hash: nodes[i], + syncPath: NewSyncPath([]byte(path)), + } + } + } + // Cross check that the two tries are in sync + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) +} + +// Tests that the trie scheduler can correctly reconstruct the state even if only +// partial results are returned (Even those randomly), others sent only later. +func TestIterativeRandomDelayedSync(t *testing.T) { + testIterativeRandomDelayedSync(t, rawdb.HashScheme) + testIterativeRandomDelayedSync(t, rawdb.PathScheme) +} + +func testIterativeRandomDelayedSync(t *testing.T, scheme string) { + // Create a random trie to copy + _, srcDb, srcTrie, srcData := makeTestTrie(scheme) + + // Create a destination trie and sync with the scheduler + diskdb := rawdb.NewMemoryDatabase() + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) + + // The code requests are ignored here since there is no code + // at the testing trie. + paths, nodes, _ := sched.Missing(10000) + queue := make(map[string]trieElement) + for i, path := range paths { + queue[path] = trieElement{ + path: path, + hash: nodes[i], + syncPath: NewSyncPath([]byte(path)), + } + } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } + for len(queue) > 0 { + // Sync only half of the scheduled nodes, even those in random order + results := make([]NodeSyncResult, 0, len(queue)/2+1) + for path, element := range queue { + owner, inner := ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) + } + results = append(results, NodeSyncResult{path, data}) + + if len(results) >= cap(results) { + break + } + } + // Feed the retrieved results back and queue new tasks + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + batch := diskdb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + for _, result := range results { + delete(queue, result.Path) + } + paths, nodes, _ = sched.Missing(10000) + for i, path := range paths { + queue[path] = trieElement{ + path: path, + hash: nodes[i], + syncPath: NewSyncPath([]byte(path)), + } + } + } + // Cross check that the two tries are in sync + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) +} + +// Tests that a trie sync will not request nodes multiple times, even if they +// have such references. +func TestDuplicateAvoidanceSync(t *testing.T) { + testDuplicateAvoidanceSync(t, rawdb.HashScheme) + testDuplicateAvoidanceSync(t, rawdb.PathScheme) +} + +func testDuplicateAvoidanceSync(t *testing.T, scheme string) { + // Create a random trie to copy + _, srcDb, srcTrie, srcData := makeTestTrie(scheme) + + // Create a destination trie and sync with the scheduler + diskdb := rawdb.NewMemoryDatabase() + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) + + // The code requests are ignored here since there is no code + // at the testing trie. + paths, nodes, _ := sched.Missing(0) + var elements []trieElement + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } + requested := make(map[common.Hash]struct{}) + for len(elements) > 0 { + results := make([]NodeSyncResult, len(elements)) + for i, element := range elements { + owner, inner := ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) + } + if _, ok := requested[element.hash]; ok { + t.Errorf("hash %x already requested once", element.hash) + } + requested[element.hash] = struct{}{} + + results[i] = NodeSyncResult{element.path, data} + } + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + batch := diskdb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + paths, nodes, _ = sched.Missing(0) + elements = elements[:0] + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + } + // Cross check that the two tries are in sync + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) +} + +// Tests that at any point in time during a sync, only complete sub-tries are in +// the database. +func TestIncompleteSyncHash(t *testing.T) { + testIncompleteSync(t, rawdb.HashScheme) + testIncompleteSync(t, rawdb.PathScheme) +} + +func testIncompleteSync(t *testing.T, scheme string) { + // Create a random trie to copy + _, srcDb, srcTrie, _ := makeTestTrie(scheme) + + // Create a destination trie and sync with the scheduler + diskdb := rawdb.NewMemoryDatabase() + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) + + // The code requests are ignored here since there is no code + // at the testing trie. + var ( + addedKeys []string + addedHashes []common.Hash + elements []trieElement + root = srcTrie.Hash() + ) + paths, nodes, _ := sched.Missing(1) + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } + for len(elements) > 0 { + // Fetch a batch of trie nodes + results := make([]NodeSyncResult, len(elements)) + for i, element := range elements { + owner, inner := ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) + } + results[i] = NodeSyncResult{element.path, data} + } + // Process each of the trie nodes + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + batch := diskdb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + for _, result := range results { + hash := crypto.Keccak256Hash(result.Data) + if hash != root { + addedKeys = append(addedKeys, result.Path) + addedHashes = append(addedHashes, hash) + } + } + // Fetch the next batch to retrieve + paths, nodes, _ = sched.Missing(1) + elements = elements[:0] + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + } + // 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, false); err == nil { + t.Fatalf("trie inconsistency not caught, missing: %x", path) + } + rawdb.WriteTrieNode(diskdb, owner, inner, nodeHash, value, scheme) + } +} + +// Tests that trie nodes get scheduled lexicographically when having the same +// depth. +func TestSyncOrdering(t *testing.T) { + testSyncOrdering(t, rawdb.HashScheme) + testSyncOrdering(t, rawdb.PathScheme) +} + +func testSyncOrdering(t *testing.T, scheme string) { + // Create a random trie to copy + _, srcDb, srcTrie, srcData := makeTestTrie(scheme) + + // Create a destination trie and sync with the scheduler, tracking the requests + diskdb := rawdb.NewMemoryDatabase() + sched := NewSync(srcTrie.Hash(), diskdb, nil, srcDb.Scheme()) + + // The code requests are ignored here since there is no code + // at the testing trie. + var ( + reqs []SyncPath + elements []trieElement + ) + paths, nodes, _ := sched.Missing(1) + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + reqs = append(reqs, NewSyncPath([]byte(paths[i]))) + } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } + for len(elements) > 0 { + results := make([]NodeSyncResult, len(elements)) + for i, element := range elements { + owner, inner := ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) + } + results[i] = NodeSyncResult{element.path, data} + } + for _, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result %v", err) + } + } + batch := diskdb.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + batch.Write() + + paths, nodes, _ = sched.Missing(1) + elements = elements[:0] + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + reqs = append(reqs, NewSyncPath([]byte(paths[i]))) + } + } + // Cross check that the two tries are in sync + 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++ { + if len(reqs[i]) > 1 || len(reqs[i+1]) > 1 { + // In the case of the trie tests, there's no storage so the tuples + // must always be single items. 2-tuples should be tested in state. + t.Errorf("Invalid request tuples: len(%v) or len(%v) > 1", reqs[i], reqs[i+1]) + } + if bytes.Compare(compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0])) > 0 { + t.Errorf("Invalid request order: %v before %v", compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0])) + } + } +} +func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *testDb) { + syncWithHookWriter(t, root, db, srcDb, nil) +} + +func syncWithHookWriter(t *testing.T, root common.Hash, db ethdb.Database, srcDb *testDb, 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(0) + var elements []trieElement + for i := 0; i < len(paths); i++ { + elements = append(elements, trieElement{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + reader, err := srcDb.Reader(root) + if err != nil { + t.Fatalf("State is not available %x", root) + } + for len(elements) > 0 { + results := make([]NodeSyncResult, len(elements)) + for i, element := range elements { + owner, inner := ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) + if err != nil { + t.Fatalf("failed to retrieve node data for hash %x: %v", element.hash, err) + } + results[i] = NodeSyncResult{element.path, data} + } + for index, result := range results { + if err := sched.ProcessNode(result); err != nil { + t.Fatalf("failed to process result[%d][%v] data %v %v", index, []byte(result.Path), result.Data, err) + } + } + batch := db.NewBatch() + if err := sched.Commit(batch); err != nil { + t.Fatalf("failed to commit data: %v", err) + } + 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{ + path: paths[i], + hash: nodes[i], + syncPath: NewSyncPath([]byte(paths[i])), + }) + } + } +} + +// Tests that the syncing target is keeping moving which may overwrite the stale +// states synced in the last cycle. +func TestSyncMovingTarget(t *testing.T) { + testSyncMovingTarget(t, rawdb.HashScheme) + testSyncMovingTarget(t, rawdb.PathScheme) +} + +func testSyncMovingTarget(t *testing.T, scheme string) { + // Create a random trie to copy + _, srcDb, srcTrie, srcData := makeTestTrie(scheme) + + // 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, false) + + // Push more modifications into the src trie, to see if dest trie can still + // sync with it(overwrite stale states) + var ( + preRoot = srcTrie.Hash() + diff = make(map[string][]byte) + ) + for i := byte(0); i < 10; i++ { + key, val := randBytes(32), randBytes(32) + srcTrie.MustUpdate(key, val) + diff[string(key)] = val + } + root, nodes := srcTrie.Commit(false) + if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes)); err != nil { + panic(err) + } + if err := srcDb.Commit(root); err != nil { + panic(err) + } + preRoot = root + srcTrie, _ = NewStateTrie(TrieID(root), srcDb) + + syncWith(t, srcTrie.Hash(), diskdb, srcDb) + 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) + var reverted = make(map[string][]byte) + for k := range diff { + srcTrie.MustDelete([]byte(k)) + reverted[k] = nil + } + for k := range srcData { + val := randBytes(32) + srcTrie.MustUpdate([]byte(k), val) + reverted[k] = val + } + root, nodes = srcTrie.Commit(false) + if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes)); err != nil { + panic(err) + } + if err := srcDb.Commit(root); err != nil { + panic(err) + } + srcTrie, _ = NewStateTrie(TrieID(root), srcDb) + + syncWith(t, srcTrie.Hash(), diskdb, srcDb) + 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) + } + ) + 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, trienode.NewWithNodeSet(nodesA)); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootA); 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 := maps.Clone(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, trienode.NewWithNodeSet(nodesB)); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootB); err != nil { + panic(err) + } + syncWith(t, rootB, destDisk, srcTrieDB) + checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateB, true) + + // Add elements to expand trie + stateC := maps.Clone(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, trienode.NewWithNodeSet(nodesC)); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootC); 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) + } + ) + 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, trienode.NewWithNodeSet(nodesA)); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootA); 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 := maps.Clone(stateA) + srcTrie, _ = New(TrieID(rootA), srcTrieDB) + deleteFn(key, srcTrie, stateB) + + rootB, nodesB := srcTrie.Commit(false) + if err := srcTrieDB.Update(rootB, rootA, trienode.NewWithNodeSet(nodesB)); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootB); 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 := maps.Clone(stateB) + srcTrie, _ = New(TrieID(rootB), srcTrieDB) + + writeFn(key, val, srcTrie, stateC) + rootC, nodesC := srcTrie.Commit(false) + if err := srcTrieDB.Update(rootC, rootB, trienode.NewWithNodeSet(nodesC)); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootC); err != nil { + panic(err) + } + syncWith(t, rootC, destDisk, srcTrieDB) + checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateC, true) +} diff --git a/trie/tracer.go b/trie/tracer.go new file mode 100644 index 0000000..90b9666 --- /dev/null +++ b/trie/tracer.go @@ -0,0 +1,122 @@ +// 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 trie + +import ( + "maps" + + "github.com/ethereum/go-ethereum/common" +) + +// tracer tracks the changes of trie nodes. During the trie operations, +// some nodes can be deleted from the trie, while these deleted nodes +// won't be captured by trie.Hasher or trie.Committer. Thus, these deleted +// nodes won't be removed from the disk at all. Tracer is an auxiliary tool +// used to track all insert and delete operations of trie and capture all +// deleted nodes eventually. +// +// The changed nodes can be mainly divided into two categories: the leaf +// node and intermediate node. The former is inserted/deleted by callers +// while the latter is inserted/deleted in order to follow the rule of trie. +// This tool can track all of them no matter the node is embedded in its +// parent or not, but valueNode is never tracked. +// +// Besides, it's also used for recording the original value of the nodes +// when they are resolved from the disk. The pre-value of the nodes will +// be used to construct trie history in the future. +// +// Note tracer is not thread-safe, callers should be responsible for handling +// the concurrency issues by themselves. +type tracer struct { + inserts map[string]struct{} + deletes map[string]struct{} + accessList map[string][]byte +} + +// newTracer initializes the tracer for capturing trie changes. +func newTracer() *tracer { + return &tracer{ + inserts: make(map[string]struct{}), + deletes: make(map[string]struct{}), + accessList: make(map[string][]byte), + } +} + +// onRead tracks the newly loaded trie node and caches the rlp-encoded +// blob internally. Don't change the value outside of function since +// it's not deep-copied. +func (t *tracer) onRead(path []byte, val []byte) { + t.accessList[string(path)] = val +} + +// onInsert tracks the newly inserted trie node. If it's already +// in the deletion set (resurrected node), then just wipe it from +// the deletion set as it's "untouched". +func (t *tracer) onInsert(path []byte) { + if _, present := t.deletes[string(path)]; present { + delete(t.deletes, string(path)) + return + } + t.inserts[string(path)] = struct{}{} +} + +// onDelete tracks the newly deleted trie node. If it's already +// in the addition set, then just wipe it from the addition set +// as it's untouched. +func (t *tracer) onDelete(path []byte) { + if _, present := t.inserts[string(path)]; present { + delete(t.inserts, string(path)) + return + } + t.deletes[string(path)] = struct{}{} +} + +// reset clears the content tracked by tracer. +func (t *tracer) reset() { + t.inserts = make(map[string]struct{}) + t.deletes = make(map[string]struct{}) + t.accessList = make(map[string][]byte) +} + +// copy returns a deep copied tracer instance. +func (t *tracer) copy() *tracer { + accessList := make(map[string][]byte, len(t.accessList)) + for path, blob := range t.accessList { + accessList[path] = common.CopyBytes(blob) + } + return &tracer{ + inserts: maps.Clone(t.inserts), + deletes: maps.Clone(t.deletes), + accessList: accessList, + } +} + +// deletedNodes returns a list of node paths which are deleted from the trie. +func (t *tracer) deletedNodes() []string { + var paths []string + for path := range t.deletes { + // It's possible a few deleted nodes were embedded + // in their parent before, the deletions can be no + // effect by deleting nothing, filter them out. + _, ok := t.accessList[path] + if !ok { + continue + } + paths = append(paths, path) + } + return paths +} diff --git a/trie/tracer_test.go b/trie/tracer_test.go new file mode 100644 index 0000000..852a706 --- /dev/null +++ b/trie/tracer_test.go @@ -0,0 +1,376 @@ +// 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 trie + +import ( + "bytes" + "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/trienode" +) + +var ( + tiny = []struct{ k, v string }{ + {"k1", "v1"}, + {"k2", "v2"}, + {"k3", "v3"}, + } + nonAligned = []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"dog", "puppy"}, + {"somethingveryoddindeedthis is", "myothernodedata"}, + } + standard = []struct{ k, v string }{ + {string(randBytes(32)), "verb"}, + {string(randBytes(32)), "wookiedoo"}, + {string(randBytes(32)), "stallion"}, + {string(randBytes(32)), "horse"}, + {string(randBytes(32)), "coin"}, + {string(randBytes(32)), "puppy"}, + {string(randBytes(32)), "myothernodedata"}, + } +) + +func TestTrieTracer(t *testing.T) { + testTrieTracer(t, tiny) + testTrieTracer(t, nonAligned) + testTrieTracer(t, standard) +} + +// 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 := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie := NewEmpty(db) + + // Determine all new nodes are tracked + for _, val := range vals { + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } + insertSet := copySet(trie.tracer.inserts) // copy before commit + deleteSet := copySet(trie.tracer.deletes) // copy before commit + root, nodes := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + + seen := setKeys(iterNodes(db, root)) + if !compareSet(insertSet, seen) { + t.Fatal("Unexpected insertion set") + } + if !compareSet(deleteSet, nil) { + t.Fatal("Unexpected deletion set") + } + + // Determine all deletions are tracked + trie, _ = New(TrieID(root), db) + for _, val := range vals { + trie.MustDelete([]byte(val.k)) + } + insertSet, deleteSet = copySet(trie.tracer.inserts), copySet(trie.tracer.deletes) + if !compareSet(insertSet, nil) { + t.Fatal("Unexpected insertion set") + } + if !compareSet(deleteSet, seen) { + t.Fatal("Unexpected deletion set") + } +} + +// Test that after inserting a new batch of nodes and deleting them immediately, +// the trie tracer should be cleared normally as no operation happened. +func TestTrieTracerNoop(t *testing.T) { + testTrieTracerNoop(t, tiny) + testTrieTracerNoop(t, nonAligned) + testTrieTracerNoop(t, standard) +} + +func testTrieTracerNoop(t *testing.T, vals []struct{ k, v string }) { + db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie := NewEmpty(db) + for _, val := range vals { + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } + for _, val := range vals { + trie.MustDelete([]byte(val.k)) + } + if len(trie.tracer.inserts) != 0 { + t.Fatal("Unexpected insertion set") + } + if len(trie.tracer.deletes) != 0 { + t.Fatal("Unexpected deletion set") + } +} + +// Tests if the accessList is correctly tracked. +func TestAccessList(t *testing.T) { + testAccessList(t, tiny) + testAccessList(t, nonAligned) + testAccessList(t, standard) +} + +func testAccessList(t *testing.T, vals []struct{ k, v string }) { + var ( + db = newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie = NewEmpty(db) + orig = trie.Copy() + ) + // Create trie from scratch + for _, val := range vals { + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } + root, nodes := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + + trie, _ = New(TrieID(root), db) + if err := verifyAccessList(orig, trie, nodes); err != nil { + t.Fatalf("Invalid accessList %v", err) + } + + // Update trie + parent := root + trie, _ = New(TrieID(root), db) + orig = trie.Copy() + for _, val := range vals { + trie.MustUpdate([]byte(val.k), randBytes(32)) + } + root, nodes = trie.Commit(false) + db.Update(root, parent, trienode.NewWithNodeSet(nodes)) + + trie, _ = New(TrieID(root), db) + if err := verifyAccessList(orig, trie, nodes); err != nil { + t.Fatalf("Invalid accessList %v", err) + } + + // Add more new nodes + parent = root + trie, _ = New(TrieID(root), db) + orig = trie.Copy() + var keys []string + for i := 0; i < 30; i++ { + key := randBytes(32) + keys = append(keys, string(key)) + trie.MustUpdate(key, randBytes(32)) + } + root, nodes = trie.Commit(false) + db.Update(root, parent, trienode.NewWithNodeSet(nodes)) + + trie, _ = New(TrieID(root), db) + if err := verifyAccessList(orig, trie, nodes); err != nil { + t.Fatalf("Invalid accessList %v", err) + } + + // Partial deletions + parent = root + trie, _ = New(TrieID(root), db) + orig = trie.Copy() + for _, key := range keys { + trie.MustUpdate([]byte(key), nil) + } + root, nodes = trie.Commit(false) + db.Update(root, parent, trienode.NewWithNodeSet(nodes)) + + trie, _ = New(TrieID(root), db) + if err := verifyAccessList(orig, trie, nodes); err != nil { + t.Fatalf("Invalid accessList %v", err) + } + + // Delete all + parent = root + trie, _ = New(TrieID(root), db) + orig = trie.Copy() + for _, val := range vals { + trie.MustUpdate([]byte(val.k), nil) + } + root, nodes = trie.Commit(false) + db.Update(root, parent, trienode.NewWithNodeSet(nodes)) + + trie, _ = New(TrieID(root), db) + if err := verifyAccessList(orig, trie, nodes); err != nil { + t.Fatalf("Invalid accessList %v", err) + } +} + +// Tests origin values won't be tracked in Iterator or Prover +func TestAccessListLeak(t *testing.T) { + var ( + db = newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie = NewEmpty(db) + ) + // Create trie from scratch + for _, val := range standard { + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } + root, nodes := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + + var cases = []struct { + op func(tr *Trie) + }{ + { + func(tr *Trie) { + it := tr.MustNodeIterator(nil) + for it.Next(true) { + } + }, + }, + { + func(tr *Trie) { + it := NewIterator(tr.MustNodeIterator(nil)) + for it.Next() { + } + }, + }, + { + func(tr *Trie) { + for _, val := range standard { + tr.Prove([]byte(val.k), rawdb.NewMemoryDatabase()) + } + }, + }, + } + for _, c := range cases { + trie, _ = New(TrieID(root), db) + n1 := len(trie.tracer.accessList) + c.op(trie) + n2 := len(trie.tracer.accessList) + + if n1 != n2 { + t.Fatalf("AccessList is leaked, prev %d after %d", n1, n2) + } + } +} + +// Tests whether the original tree node is correctly deleted after being embedded +// in its parent due to the smaller size of the original tree node. +func TestTinyTree(t *testing.T) { + var ( + db = newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie = NewEmpty(db) + ) + for _, val := range tiny { + trie.MustUpdate([]byte(val.k), randBytes(32)) + } + root, set := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(set)) + + parent := root + trie, _ = New(TrieID(root), db) + orig := trie.Copy() + for _, val := range tiny { + trie.MustUpdate([]byte(val.k), []byte(val.v)) + } + root, set = trie.Commit(false) + db.Update(root, parent, trienode.NewWithNodeSet(set)) + + trie, _ = New(TrieID(root), db) + if err := verifyAccessList(orig, trie, set); err != nil { + t.Fatalf("Invalid accessList %v", err) + } +} + +func compareSet(setA, setB map[string]struct{}) bool { + if len(setA) != len(setB) { + return false + } + for key := range setA { + if _, ok := setB[key]; !ok { + return false + } + } + return true +} + +func forNodes(tr *Trie) map[string][]byte { + var ( + it = tr.MustNodeIterator(nil) + nodes = make(map[string][]byte) + ) + for it.Next(true) { + if it.Leaf() { + continue + } + nodes[string(it.Path())] = common.CopyBytes(it.NodeBlob()) + } + return nodes +} + +func iterNodes(db *testDb, root common.Hash) map[string][]byte { + tr, _ := New(TrieID(root), db) + return forNodes(tr) +} + +func forHashedNodes(tr *Trie) map[string][]byte { + var ( + it = tr.MustNodeIterator(nil) + nodes = make(map[string][]byte) + ) + for it.Next(true) { + if it.Hash() == (common.Hash{}) { + continue + } + nodes[string(it.Path())] = common.CopyBytes(it.NodeBlob()) + } + return nodes +} + +func diffTries(trieA, trieB *Trie) (map[string][]byte, map[string][]byte, map[string][]byte) { + var ( + nodesA = forHashedNodes(trieA) + nodesB = forHashedNodes(trieB) + inA = make(map[string][]byte) // hashed nodes in trie a but not b + inB = make(map[string][]byte) // hashed nodes in trie b but not a + both = make(map[string][]byte) // hashed nodes in both tries but different value + ) + for path, blobA := range nodesA { + if blobB, ok := nodesB[path]; ok { + if bytes.Equal(blobA, blobB) { + continue + } + both[path] = blobA + continue + } + inA[path] = blobA + } + for path, blobB := range nodesB { + if _, ok := nodesA[path]; ok { + continue + } + inB[path] = blobB + } + return inA, inB, both +} + +func setKeys(set map[string][]byte) map[string]struct{} { + keys := make(map[string]struct{}) + for k := range set { + keys[k] = struct{}{} + } + return keys +} + +func copySet(set map[string]struct{}) map[string]struct{} { + copied := make(map[string]struct{}) + for k := range set { + copied[k] = struct{}{} + } + return copied +} diff --git a/trie/trie.go b/trie/trie.go new file mode 100644 index 0000000..f44e10b --- /dev/null +++ b/trie/trie.go @@ -0,0 +1,683 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package trie implements Merkle Patricia Tries. +package trie + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb/database" +) + +// Trie is a Merkle Patricia Trie. Use New to create a trie that sits on +// top of a database. Whenever trie performs a commit operation, the generated +// nodes will be gathered and returned in a set. Once the trie is committed, +// it's not usable anymore. Callers have to re-create the trie with new root +// based on the updated trie database. +// +// Trie is not safe for concurrent use. +type Trie struct { + root node + owner common.Hash + + // Flag whether the commit operation is already performed. If so the + // trie is not usable(latest states is invisible). + committed bool + + // Keep track of the number leaves which have been inserted since the last + // hashing operation. This number will not directly map to the number of + // actually unhashed nodes. + unhashed int + + // reader is the handler trie can retrieve nodes from. + reader *trieReader + + // tracer is the tool to track the trie changes. + tracer *tracer +} + +// newFlag returns the cache flag value for a newly created node. +func (t *Trie) newFlag() nodeFlag { + return nodeFlag{dirty: true} +} + +// Copy returns a copy of Trie. +func (t *Trie) Copy() *Trie { + return &Trie{ + root: t.root, + owner: t.owner, + committed: t.committed, + unhashed: t.unhashed, + reader: t.reader, + tracer: t.tracer.copy(), + } +} + +// New creates the trie instance with provided trie id and the read-only +// database. The state specified by trie id must be available, otherwise +// an error will be returned. The trie root specified by trie id can be +// zero hash or the sha3 hash of an empty string, then trie is initially +// empty, otherwise, the root node must be present in database or returns +// a MissingNodeError if not. +func New(id *ID, db database.Database) (*Trie, error) { + reader, err := newTrieReader(id.StateRoot, id.Owner, db) + if err != nil { + return nil, err + } + trie := &Trie{ + owner: id.Owner, + reader: reader, + tracer: newTracer(), + } + if id.Root != (common.Hash{}) && id.Root != types.EmptyRootHash { + rootnode, err := trie.resolveAndTrack(id.Root[:], nil) + if err != nil { + return nil, err + } + trie.root = rootnode + } + return trie, nil +} + +// NewEmpty is a shortcut to create empty tree. It's mostly used in tests. +func NewEmpty(db database.Database) *Trie { + tr, _ := New(TrieID(types.EmptyRootHash), db) + return tr +} + +// MustNodeIterator is a wrapper of NodeIterator and will omit any encountered +// error but just print out an error message. +func (t *Trie) MustNodeIterator(start []byte) NodeIterator { + it, err := t.NodeIterator(start) + if err != nil { + log.Error("Unhandled trie error in Trie.NodeIterator", "err", err) + } + return it +} + +// NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at +// the key after the given start key. +func (t *Trie) NodeIterator(start []byte) (NodeIterator, error) { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return nil, ErrCommitted + } + return newNodeIterator(t, start), nil +} + +// MustGet is a wrapper of Get and will omit any encountered error but just +// print out an error message. +func (t *Trie) MustGet(key []byte) []byte { + res, err := t.Get(key) + if err != nil { + log.Error("Unhandled trie error in Trie.Get", "err", err) + } + return res +} + +// Get returns the value for key stored in the trie. +// The value bytes must not be modified by the caller. +// +// If the requested node is not present in trie, no error will be returned. +// If the trie is corrupted, a MissingNodeError is returned. +func (t *Trie) Get(key []byte) ([]byte, error) { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return nil, ErrCommitted + } + value, newroot, didResolve, err := t.get(t.root, keybytesToHex(key), 0) + if err == nil && didResolve { + t.root = newroot + } + return value, err +} + +func (t *Trie) get(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) { + switch n := (origNode).(type) { + case nil: + return nil, nil, false, nil + case valueNode: + return n, n, false, nil + case *shortNode: + if len(key)-pos < len(n.Key) || !bytes.Equal(n.Key, key[pos:pos+len(n.Key)]) { + // key not found in trie + return nil, n, false, nil + } + value, newnode, didResolve, err = t.get(n.Val, key, pos+len(n.Key)) + if err == nil && didResolve { + n = n.copy() + n.Val = newnode + } + return value, n, didResolve, err + case *fullNode: + value, newnode, didResolve, err = t.get(n.Children[key[pos]], key, pos+1) + if err == nil && didResolve { + n = n.copy() + n.Children[key[pos]] = newnode + } + return value, n, didResolve, err + case hashNode: + child, err := t.resolveAndTrack(n, key[:pos]) + if err != nil { + return nil, n, true, err + } + value, newnode, _, err := t.get(child, key, pos) + return value, newnode, true, err + default: + panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode)) + } +} + +// MustGetNode is a wrapper of GetNode and will omit any encountered error but +// just print out an error message. +func (t *Trie) MustGetNode(path []byte) ([]byte, int) { + item, resolved, err := t.GetNode(path) + if err != nil { + log.Error("Unhandled trie error in Trie.GetNode", "err", err) + } + return item, resolved +} + +// GetNode retrieves a trie node by compact-encoded path. It is not possible +// to use keybyte-encoding as the path might contain odd nibbles. +// +// If the requested node is not present in trie, no error will be returned. +// If the trie is corrupted, a MissingNodeError is returned. +func (t *Trie) GetNode(path []byte) ([]byte, int, error) { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return nil, 0, ErrCommitted + } + item, newroot, resolved, err := t.getNode(t.root, compactToHex(path), 0) + if err != nil { + return nil, resolved, err + } + if resolved > 0 { + t.root = newroot + } + if item == nil { + return nil, resolved, nil + } + return item, resolved, nil +} + +func (t *Trie) getNode(origNode node, path []byte, pos int) (item []byte, newnode node, resolved int, err error) { + // If non-existent path requested, abort + if origNode == nil { + return nil, nil, 0, nil + } + // If we reached the requested path, return the current node + if pos >= len(path) { + // Although we most probably have the original node expanded, encoding + // that into consensus form can be nasty (needs to cascade down) and + // time consuming. Instead, just pull the hash up from disk directly. + var hash hashNode + if node, ok := origNode.(hashNode); ok { + hash = node + } else { + hash, _ = origNode.cache() + } + if hash == nil { + return nil, origNode, 0, errors.New("non-consensus node") + } + blob, err := t.reader.node(path, common.BytesToHash(hash)) + return blob, origNode, 1, err + } + // Path still needs to be traversed, descend into children + switch n := (origNode).(type) { + case valueNode: + // Path prematurely ended, abort + return nil, nil, 0, nil + + case *shortNode: + if len(path)-pos < len(n.Key) || !bytes.Equal(n.Key, path[pos:pos+len(n.Key)]) { + // Path branches off from short node + return nil, n, 0, nil + } + item, newnode, resolved, err = t.getNode(n.Val, path, pos+len(n.Key)) + if err == nil && resolved > 0 { + n = n.copy() + n.Val = newnode + } + return item, n, resolved, err + + case *fullNode: + item, newnode, resolved, err = t.getNode(n.Children[path[pos]], path, pos+1) + if err == nil && resolved > 0 { + n = n.copy() + n.Children[path[pos]] = newnode + } + return item, n, resolved, err + + case hashNode: + child, err := t.resolveAndTrack(n, path[:pos]) + if err != nil { + return nil, n, 1, err + } + item, newnode, resolved, err := t.getNode(child, path, pos) + return item, newnode, resolved + 1, err + + default: + panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode)) + } +} + +// MustUpdate is a wrapper of Update and will omit any encountered error but +// just print out an error message. +func (t *Trie) MustUpdate(key, value []byte) { + if err := t.Update(key, value); err != nil { + log.Error("Unhandled trie error in Trie.Update", "err", err) + } +} + +// Update associates key with value in the trie. Subsequent calls to +// Get will return value. If value has length zero, any existing value +// is deleted from the trie and calls to Get will return nil. +// +// The value bytes must not be modified by the caller while they are +// stored in the trie. +// +// If the requested node is not present in trie, no error will be returned. +// If the trie is corrupted, a MissingNodeError is returned. +func (t *Trie) Update(key, value []byte) error { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return ErrCommitted + } + return t.update(key, value) +} + +func (t *Trie) update(key, value []byte) error { + t.unhashed++ + k := keybytesToHex(key) + if len(value) != 0 { + _, n, err := t.insert(t.root, nil, k, valueNode(value)) + if err != nil { + return err + } + t.root = n + } else { + _, n, err := t.delete(t.root, nil, k) + if err != nil { + return err + } + t.root = n + } + return nil +} + +func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error) { + if len(key) == 0 { + if v, ok := n.(valueNode); ok { + return !bytes.Equal(v, value.(valueNode)), value, nil + } + return true, value, nil + } + switch n := n.(type) { + case *shortNode: + matchlen := prefixLen(key, n.Key) + // If the whole key matches, keep this short node as is + // and only update the value. + if matchlen == len(n.Key) { + dirty, nn, err := t.insert(n.Val, append(prefix, key[:matchlen]...), key[matchlen:], value) + if !dirty || err != nil { + return false, n, err + } + return true, &shortNode{n.Key, nn, t.newFlag()}, nil + } + // Otherwise branch out at the index where they differ. + branch := &fullNode{flags: t.newFlag()} + var err error + _, branch.Children[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val) + if err != nil { + return false, nil, err + } + _, branch.Children[key[matchlen]], err = t.insert(nil, append(prefix, key[:matchlen+1]...), key[matchlen+1:], value) + if err != nil { + return false, nil, err + } + // Replace this shortNode with the branch if it occurs at index 0. + if matchlen == 0 { + return true, branch, nil + } + // New branch node is created as a child of the original short node. + // Track the newly inserted node in the tracer. The node identifier + // passed is the path from the root node. + t.tracer.onInsert(append(prefix, key[:matchlen]...)) + + // Replace it with a short node leading up to the branch. + return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil + + case *fullNode: + dirty, nn, err := t.insert(n.Children[key[0]], append(prefix, key[0]), key[1:], value) + if !dirty || err != nil { + return false, n, err + } + n = n.copy() + n.flags = t.newFlag() + n.Children[key[0]] = nn + return true, n, nil + + case nil: + // New short node is created and track it in the tracer. The node identifier + // passed is the path from the root node. Note the valueNode won't be tracked + // since it's always embedded in its parent. + t.tracer.onInsert(prefix) + + return true, &shortNode{key, value, t.newFlag()}, nil + + case hashNode: + // We've hit a part of the trie that isn't loaded yet. Load + // the node and insert into it. This leaves all child nodes on + // the path to the value in the trie. + rn, err := t.resolveAndTrack(n, prefix) + if err != nil { + return false, nil, err + } + dirty, nn, err := t.insert(rn, prefix, key, value) + if !dirty || err != nil { + return false, rn, err + } + return true, nn, nil + + default: + panic(fmt.Sprintf("%T: invalid node: %v", n, n)) + } +} + +// MustDelete is a wrapper of Delete and will omit any encountered error but +// just print out an error message. +func (t *Trie) MustDelete(key []byte) { + if err := t.Delete(key); err != nil { + log.Error("Unhandled trie error in Trie.Delete", "err", err) + } +} + +// Delete removes any existing value for key from the trie. +// +// If the requested node is not present in trie, no error will be returned. +// If the trie is corrupted, a MissingNodeError is returned. +func (t *Trie) Delete(key []byte) error { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return ErrCommitted + } + t.unhashed++ + k := keybytesToHex(key) + _, n, err := t.delete(t.root, nil, k) + if err != nil { + return err + } + t.root = n + return nil +} + +// delete returns the new root of the trie with key deleted. +// It reduces the trie to minimal form by simplifying +// nodes on the way up after deleting recursively. +func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { + switch n := n.(type) { + case *shortNode: + matchlen := prefixLen(key, n.Key) + if matchlen < len(n.Key) { + return false, n, nil // don't replace n on mismatch + } + if matchlen == len(key) { + // The matched short node is deleted entirely and track + // it in the deletion set. The same the valueNode doesn't + // need to be tracked at all since it's always embedded. + t.tracer.onDelete(prefix) + + return true, nil, nil // remove n entirely for whole matches + } + // The key is longer than n.Key. Remove the remaining suffix + // from the subtrie. Child can never be nil here since the + // subtrie must contain at least two other values with keys + // longer than n.Key. + dirty, child, err := t.delete(n.Val, append(prefix, key[:len(n.Key)]...), key[len(n.Key):]) + if !dirty || err != nil { + return false, n, err + } + switch child := child.(type) { + case *shortNode: + // The child shortNode is merged into its parent, track + // is deleted as well. + t.tracer.onDelete(append(prefix, n.Key...)) + + // Deleting from the subtrie reduced it to another + // short node. Merge the nodes to avoid creating a + // shortNode{..., shortNode{...}}. Use concat (which + // always creates a new slice) instead of append to + // avoid modifying n.Key since it might be shared with + // other nodes. + return true, &shortNode{concat(n.Key, child.Key...), child.Val, t.newFlag()}, nil + default: + return true, &shortNode{n.Key, child, t.newFlag()}, nil + } + + case *fullNode: + dirty, nn, err := t.delete(n.Children[key[0]], append(prefix, key[0]), key[1:]) + if !dirty || err != nil { + return false, n, err + } + n = n.copy() + n.flags = t.newFlag() + n.Children[key[0]] = nn + + // Because n is a full node, it must've contained at least two children + // before the delete operation. If the new child value is non-nil, n still + // has at least two children after the deletion, and cannot be reduced to + // a short node. + if nn != nil { + return true, n, nil + } + // Reduction: + // Check how many non-nil entries are left after deleting and + // reduce the full node to a short node if only one entry is + // left. Since n must've contained at least two children + // before deletion (otherwise it would not be a full node) n + // can never be reduced to nil. + // + // When the loop is done, pos contains the index of the single + // value that is left in n or -2 if n contains at least two + // values. + pos := -1 + for i, cld := range &n.Children { + if cld != nil { + if pos == -1 { + pos = i + } else { + pos = -2 + break + } + } + } + if pos >= 0 { + if pos != 16 { + // If the remaining entry is a short node, it replaces + // n and its key gets the missing nibble tacked to the + // front. This avoids creating an invalid + // shortNode{..., shortNode{...}}. Since the entry + // might not be loaded yet, resolve it just for this + // check. + cnode, err := t.resolve(n.Children[pos], append(prefix, byte(pos))) + if err != nil { + return false, nil, err + } + if cnode, ok := cnode.(*shortNode); ok { + // Replace the entire full node with the short node. + // Mark the original short node as deleted since the + // value is embedded into the parent now. + t.tracer.onDelete(append(prefix, byte(pos))) + + k := append([]byte{byte(pos)}, cnode.Key...) + return true, &shortNode{k, cnode.Val, t.newFlag()}, nil + } + } + // Otherwise, n is replaced by a one-nibble short node + // containing the child. + return true, &shortNode{[]byte{byte(pos)}, n.Children[pos], t.newFlag()}, nil + } + // n still contains at least two values and cannot be reduced. + return true, n, nil + + case valueNode: + return true, nil, nil + + case nil: + return false, nil, nil + + case hashNode: + // We've hit a part of the trie that isn't loaded yet. Load + // the node and delete from it. This leaves all child nodes on + // the path to the value in the trie. + rn, err := t.resolveAndTrack(n, prefix) + if err != nil { + return false, nil, err + } + dirty, nn, err := t.delete(rn, prefix, key) + if !dirty || err != nil { + return false, rn, err + } + return true, nn, nil + + default: + panic(fmt.Sprintf("%T: invalid node: %v (%v)", n, n, key)) + } +} + +func concat(s1 []byte, s2 ...byte) []byte { + r := make([]byte, len(s1)+len(s2)) + copy(r, s1) + copy(r[len(s1):], s2) + return r +} + +func (t *Trie) resolve(n node, prefix []byte) (node, error) { + if n, ok := n.(hashNode); ok { + return t.resolveAndTrack(n, prefix) + } + return n, nil +} + +// resolveAndTrack loads node from the underlying store with the given node hash +// and path prefix and also tracks the loaded node blob in tracer treated as the +// node's original value. The rlp-encoded blob is preferred to be loaded from +// database because it's easy to decode node while complex to encode node to blob. +func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) { + blob, err := t.reader.node(prefix, common.BytesToHash(n)) + if err != nil { + return nil, err + } + t.tracer.onRead(prefix, blob) + return mustDecodeNode(n, blob), nil +} + +// 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. +func (t *Trie) Hash() common.Hash { + hash, cached := t.hashRoot() + t.root = cached + return common.BytesToHash(hash.(hashNode)) +} + +// Commit collects all dirty nodes in the trie and replaces them with the +// corresponding node hash. All collected nodes (including dirty leaves if +// collectLeaf is true) will be encapsulated into a nodeset for return. +// The returned nodeset can be nil if the trie is clean (nothing to commit). +// Once the trie is committed, it's not usable anymore. A new trie must +// be created with new root and updated trie database for following usage +func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { + defer func() { + t.committed = true + }() + // Trie is empty and can be classified into two types of situations: + // (a) The trie was empty and no update happens => return nil + // (b) The trie was non-empty and all nodes are dropped => return + // the node set includes all deleted nodes + if t.root == nil { + paths := t.tracer.deletedNodes() + if len(paths) == 0 { + return types.EmptyRootHash, nil // case (a) + } + nodes := trienode.NewNodeSet(t.owner) + for _, path := range paths { + nodes.AddNode([]byte(path), trienode.NewDeleted()) + } + return types.EmptyRootHash, nodes // case (b) + } + // Derive the hash for all dirty nodes first. We hold the assumption + // in the following procedure that all nodes are hashed. + rootHash := t.Hash() + + // Do a quick check if we really need to commit. This can happen e.g. + // if we load a trie for reading storage values, but don't write to it. + if hashedNode, dirty := t.root.cache(); !dirty { + // Replace the root node with the origin hash in order to + // ensure all resolved nodes are dropped after the commit. + t.root = hashedNode + return rootHash, nil + } + nodes := trienode.NewNodeSet(t.owner) + for _, path := range t.tracer.deletedNodes() { + nodes.AddNode([]byte(path), trienode.NewDeleted()) + } + t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root) + return rootHash, nodes +} + +// hashRoot calculates the root hash of the given trie +func (t *Trie) hashRoot() (node, node) { + if t.root == nil { + return hashNode(types.EmptyRootHash.Bytes()), nil + } + // If the number of changes is below 100, we let one thread handle it + h := newHasher(t.unhashed >= 100) + defer func() { + returnHasherToPool(h) + t.unhashed = 0 + }() + hashed, cached := h.hash(t.root, true) + return hashed, cached +} + +// Witness returns a set containing all trie nodes that have been accessed. +func (t *Trie) Witness() map[string]struct{} { + if len(t.tracer.accessList) == 0 { + return nil + } + witness := make(map[string]struct{}) + for _, node := range t.tracer.accessList { + witness[string(node)] = struct{}{} + } + return witness +} + +// Reset drops the referenced root node and cleans all internal state. +func (t *Trie) Reset() { + t.root = nil + t.owner = common.Hash{} + t.unhashed = 0 + t.tracer.reset() + t.committed = false +} diff --git a/trie/trie_id.go b/trie/trie_id.go new file mode 100644 index 0000000..8ab490c --- /dev/null +++ b/trie/trie_id.go @@ -0,0 +1,55 @@ +// 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 trie + +import "github.com/ethereum/go-ethereum/common" + +// ID is the identifier for uniquely identifying a trie. +type ID struct { + StateRoot common.Hash // The root of the corresponding state(block.root) + Owner common.Hash // The contract address hash which the trie belongs to + Root common.Hash // The root hash of trie +} + +// StateTrieID constructs an identifier for state trie with the provided state root. +func StateTrieID(root common.Hash) *ID { + return &ID{ + StateRoot: root, + Owner: common.Hash{}, + Root: root, + } +} + +// StorageTrieID constructs an identifier for storage trie which belongs to a certain +// state and contract specified by the stateRoot and owner. +func StorageTrieID(stateRoot common.Hash, owner common.Hash, root common.Hash) *ID { + return &ID{ + StateRoot: stateRoot, + Owner: owner, + Root: root, + } +} + +// TrieID constructs an identifier for a standard trie(not a second-layer trie) +// with provided root. It's mostly used in tests and some other tries like CHT trie. +func TrieID(root common.Hash) *ID { + return &ID{ + StateRoot: root, + Owner: common.Hash{}, + Root: root, + } +} diff --git a/trie/trie_reader.go b/trie/trie_reader.go new file mode 100644 index 0000000..adbf43d --- /dev/null +++ b/trie/trie_reader.go @@ -0,0 +1,73 @@ +// 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 trie + +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/triedb/database" +) + +// trieReader is a wrapper of the underlying node reader. It's not safe +// for concurrent usage. +type trieReader struct { + owner common.Hash + reader database.Reader + banned map[string]struct{} // Marker to prevent node from being accessed, for tests +} + +// newTrieReader initializes the trie reader with the given node reader. +func newTrieReader(stateRoot, owner common.Hash, db database.Database) (*trieReader, error) { + if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash { + if stateRoot == (common.Hash{}) { + log.Error("Zero state root hash!") + } + return &trieReader{owner: owner}, nil + } + reader, err := db.Reader(stateRoot) + if err != nil { + return nil, &MissingNodeError{Owner: owner, NodeHash: stateRoot, err: err} + } + return &trieReader{owner: owner, reader: reader}, nil +} + +// newEmptyReader initializes the pure in-memory reader. All read operations +// should be forbidden and returns the MissingNodeError. +func newEmptyReader() *trieReader { + return &trieReader{} +} + +// node retrieves the rlp-encoded trie node with the provided trie node +// information. An MissingNodeError will be returned in case the node is +// not found or any error is encountered. +func (r *trieReader) node(path []byte, hash common.Hash) ([]byte, error) { + // Perform the logics in tests for preventing trie node access. + if r.banned != nil { + if _, ok := r.banned[string(path)]; ok { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path} + } + } + if r.reader == nil { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path} + } + blob, err := r.reader.Node(r.owner, path, hash) + if err != nil || len(blob) == 0 { + return nil, &MissingNodeError{Owner: r.owner, NodeHash: hash, Path: path, err: err} + } + return blob, nil +} diff --git a/trie/trie_test.go b/trie/trie_test.go new file mode 100644 index 0000000..5f706a2 --- /dev/null +++ b/trie/trie_test.go @@ -0,0 +1,1209 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" + "math/rand" + "reflect" + "sort" + "testing" + "testing/quick" + + "github.com/davecgh/go-spew/spew" + "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/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/holiman/uint256" + "golang.org/x/crypto/sha3" +) + +func init() { + spew.Config.Indent = " " + spew.Config.DisableMethods = false +} + +func TestEmptyTrie(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + res := trie.Hash() + exp := types.EmptyRootHash + if res != exp { + t.Errorf("expected %x got %x", exp, res) + } +} + +func TestNull(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + key := make([]byte, 32) + value := []byte("test") + trie.MustUpdate(key, value) + if !bytes.Equal(trie.MustGet(key), value) { + t.Fatal("wrong value") + } +} + +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), newTestDatabase(rawdb.NewMemoryDatabase(), scheme)) + if trie != nil { + t.Error("New returned non-nil trie for invalid root") + } + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("New returned wrong error: %v", err) + } +} + +func TestMissingNode(t *testing.T) { + testMissingNode(t, false, rawdb.HashScheme) + testMissingNode(t, false, rawdb.PathScheme) + testMissingNode(t, true, rawdb.HashScheme) + testMissingNode(t, true, rawdb.PathScheme) +} + +func testMissingNode(t *testing.T, memonly bool, scheme string) { + diskdb := rawdb.NewMemoryDatabase() + triedb := newTestDatabase(diskdb, scheme) + + trie := NewEmpty(triedb) + updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer") + updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") + root, nodes := trie.Commit(false) + triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + + if !memonly { + triedb.Commit(root) + } + + trie, _ = New(TrieID(root), triedb) + _, err := trie.Get([]byte("120000")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + trie, _ = New(TrieID(root), triedb) + _, err = trie.Get([]byte("120099")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + trie, _ = New(TrieID(root), triedb) + _, err = trie.Get([]byte("123456")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + trie, _ = New(TrieID(root), triedb) + err = trie.Update([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + trie, _ = New(TrieID(root), triedb) + err = trie.Delete([]byte("123456")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + var ( + path []byte + hash = common.HexToHash("0xe1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9") + ) + for p, n := range nodes.Nodes { + if n.Hash == hash { + path = common.CopyBytes([]byte(p)) + break + } + } + trie, _ = New(TrieID(root), triedb) + if memonly { + trie.reader.banned = map[string]struct{}{string(path): {}} + } else { + rawdb.DeleteTrieNode(diskdb, common.Hash{}, path, hash, scheme) + } + + _, err = trie.Get([]byte("120000")) + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("Wrong error: %v", err) + } + _, err = trie.Get([]byte("120099")) + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("Wrong error: %v", err) + } + _, err = trie.Get([]byte("123456")) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + err = trie.Update([]byte("120099"), []byte("zxcv")) + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("Wrong error: %v", err) + } + err = trie.Delete([]byte("123456")) + if _, ok := err.(*MissingNodeError); !ok { + t.Errorf("Wrong error: %v", err) + } +} + +func TestInsert(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + + updateString(trie, "doe", "reindeer") + updateString(trie, "dog", "puppy") + updateString(trie, "dogglesworth", "cat") + + exp := common.HexToHash("8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3") + root := trie.Hash() + if root != exp { + t.Errorf("case 1: exp %x got %x", exp, root) + } + + trie = NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + + exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab") + root, _ = trie.Commit(false) + if root != exp { + t.Errorf("case 2: exp %x got %x", exp, root) + } +} + +func TestGet(t *testing.T) { + db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie := NewEmpty(db) + updateString(trie, "doe", "reindeer") + updateString(trie, "dog", "puppy") + updateString(trie, "dogglesworth", "cat") + + for i := 0; i < 2; i++ { + res := getString(trie, "dog") + if !bytes.Equal(res, []byte("puppy")) { + t.Errorf("expected puppy got %x", res) + } + unknown := getString(trie, "unknown") + if unknown != nil { + t.Errorf("expected nil got %x", unknown) + } + if i == 1 { + return + } + root, nodes := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + trie, _ = New(TrieID(root), db) + } +} + +func TestDelete(t *testing.T) { + db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie := NewEmpty(db) + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"ether", ""}, + {"dog", "puppy"}, + {"shaman", ""}, + } + for _, val := range vals { + if val.v != "" { + updateString(trie, val.k, val.v) + } else { + deleteString(trie, val.k) + } + } + + hash := trie.Hash() + exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84") + if hash != exp { + t.Errorf("expected %x got %x", exp, hash) + } +} + +func TestEmptyValues(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"ether", ""}, + {"dog", "puppy"}, + {"shaman", ""}, + } + for _, val := range vals { + updateString(trie, val.k, val.v) + } + + hash := trie.Hash() + exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84") + if hash != exp { + t.Errorf("expected %x got %x", exp, hash) + } +} + +func TestReplication(t *testing.T) { + db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie := NewEmpty(db) + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + {"shaman", "horse"}, + {"doge", "coin"}, + {"dog", "puppy"}, + {"somethingveryoddindeedthis is", "myothernodedata"}, + } + for _, val := range vals { + updateString(trie, val.k, val.v) + } + root, nodes := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + + // create a new trie on top of the database and check that lookups work. + trie2, err := New(TrieID(root), db) + if err != nil { + t.Fatalf("can't recreate trie at %x: %v", root, err) + } + for _, kv := range vals { + if string(getString(trie2, kv.k)) != kv.v { + t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v) + } + } + hash, nodes := trie2.Commit(false) + if hash != root { + t.Errorf("root failure. expected %x got %x", root, hash) + } + + // recreate the trie after commit + if nodes != nil { + db.Update(hash, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + } + trie2, err = New(TrieID(hash), db) + if err != nil { + t.Fatalf("can't recreate trie at %x: %v", hash, err) + } + // perform some insertions on the new trie. + vals2 := []struct{ k, v string }{ + {"do", "verb"}, + {"ether", "wookiedoo"}, + {"horse", "stallion"}, + // {"shaman", "horse"}, + // {"doge", "coin"}, + // {"ether", ""}, + // {"dog", "puppy"}, + // {"somethingveryoddindeedthis is", "myothernodedata"}, + // {"shaman", ""}, + } + for _, val := range vals2 { + updateString(trie2, val.k, val.v) + } + if trie2.Hash() != hash { + t.Errorf("root failure. expected %x got %x", hash, hash) + } +} + +func TestLargeValue(t *testing.T) { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + trie.MustUpdate([]byte("key1"), []byte{99, 99, 99, 99}) + trie.MustUpdate([]byte("key2"), bytes.Repeat([]byte{1}, 32)) + trie.Hash() +} + +// TestRandomCases tests some cases that were found via random fuzzing +func TestRandomCases(t *testing.T) { + var rt = []randTestStep{ + {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 0 + {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 1 + {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000002")}, // step 2 + {op: 2, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("")}, // step 3 + {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 4 + {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 5 + {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 6 + {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 7 + {op: 0, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("0000000000000008")}, // step 8 + {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000009")}, // step 9 + {op: 2, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 10 + {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 11 + {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 12 + {op: 0, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("000000000000000d")}, // step 13 + {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 14 + {op: 1, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("")}, // step 15 + {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 16 + {op: 0, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("0000000000000011")}, // step 17 + {op: 5, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 18 + {op: 3, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 19 + {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000014")}, // step 20 + {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000015")}, // step 21 + {op: 0, key: common.Hex2Bytes("c2a38512b83107d665c65235b0250002882ac2022eb00711552354832c5f1d030d0e408e"), value: common.Hex2Bytes("0000000000000016")}, // step 22 + {op: 5, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 23 + {op: 1, key: common.Hex2Bytes("980c393656413a15c8da01978ed9f89feb80b502f58f2d640e3a2f5f7a99a7018f1b573befd92053ac6f78fca4a87268"), value: common.Hex2Bytes("")}, // step 24 + {op: 1, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 25 + } + 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 + value []byte // for opUpdate + err error // for debugging +} + +const ( + opUpdate = iota + opDelete + opGet + opHash + opCommit + opItercheckhash + opNodeDiff + opProve + opMax // boundary value, not an actual op +) + +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 { + r.Read(one) + if len(allKeys) < 2 || one[0]%100 > 90 { + // new key + size := one[0] % 50 + key := make([]byte, size) + r.Read(key) + allKeys = append(allKeys, key) + return key + } + // use existing key + idx := int(one[0]) % len(allKeys) + return allKeys[idx] + } + var steps randTest + 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(len(steps))) + case opGet, opDelete, opProve: + step.key = genKey() + } + steps = append(steps, step) + } + return steps +} + +func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error { + deletes, inserts, updates := diffTries(old, new) + + // Check insertion set + for path := range inserts { + n, ok := set.Nodes[path] + if !ok || n.IsDeleted() { + return errors.New("expect new node") + } + //if len(n.Prev) > 0 { + // return errors.New("unexpected origin value") + //} + } + // Check deletion set + for path := range deletes { + n, ok := set.Nodes[path] + if !ok || !n.IsDeleted() { + return errors.New("expect deleted node") + } + //if len(n.Prev) == 0 { + // return errors.New("expect origin value") + //} + //if !bytes.Equal(n.Prev, blob) { + // return errors.New("invalid origin value") + //} + } + // Check update set + for path := range updates { + n, ok := set.Nodes[path] + if !ok || n.IsDeleted() { + return errors.New("expect updated node") + } + //if len(n.Prev) == 0 { + // return errors.New("expect origin value") + //} + //if !bytes.Equal(n.Prev, blob) { + // return errors.New("invalid origin value") + //} + } + return nil +} + +// 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 + } + var ( + origin = types.EmptyRootHash + triedb = newTestDatabase(rawdb.NewMemoryDatabase(), scheme) + tr = NewEmpty(triedb) + values = make(map[string]string) // tracks content of the trie + origTrie = NewEmpty(triedb) + ) + for i, step := range rt { + // fmt.Printf("{op: %d, key: common.Hex2Bytes(\"%x\"), value: common.Hex2Bytes(\"%x\")}, // step %d\n", + // step.op, step.key, step.value, i) + + 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 opProve: + hash := tr.Hash() + if hash == types.EmptyRootHash { + continue + } + proofDb := rawdb.NewMemoryDatabase() + err := tr.Prove(step.key, proofDb) + if err != nil { + rt[i].err = fmt.Errorf("failed for proving key %#x, %v", step.key, err) + } + _, err = VerifyProof(hash, step.key, proofDb) + if err != nil { + rt[i].err = fmt.Errorf("failed for verifying key %#x, %v", step.key, err) + } + case opHash: + tr.Hash() + case opCommit: + root, nodes := tr.Commit(true) + if nodes != nil { + triedb.Update(root, origin, trienode.NewWithNodeSet(nodes)) + } + newtr, err := New(TrieID(root), triedb) + if err != nil { + rt[i].err = err + return err + } + if nodes != nil { + if err := verifyAccessList(origTrie, newtr, nodes); err != nil { + rt[i].err = err + return err + } + } + tr = newtr + origTrie = tr.Copy() + origin = root + case opItercheckhash: + checktr := NewEmpty(triedb) + it := NewIterator(tr.MustNodeIterator(nil)) + for it.Next() { + checktr.MustUpdate(it.Key, it.Value) + } + if tr.Hash() != checktr.Hash() { + rt[i].err = errors.New("hash mismatch in opItercheckhash") + } + case opNodeDiff: + var ( + origIter = origTrie.MustNodeIterator(nil) + curIter = tr.MustNodeIterator(nil) + origSeen = make(map[string]struct{}) + curSeen = make(map[string]struct{}) + ) + for origIter.Next(true) { + if origIter.Leaf() { + continue + } + origSeen[string(origIter.Path())] = struct{}{} + } + for curIter.Next(true) { + if curIter.Leaf() { + continue + } + curSeen[string(curIter.Path())] = struct{}{} + } + var ( + insertExp = make(map[string]struct{}) + deleteExp = make(map[string]struct{}) + ) + for path := range curSeen { + _, present := origSeen[path] + if !present { + insertExp[path] = struct{}{} + } + } + for path := range origSeen { + _, present := curSeen[path] + if !present { + deleteExp[path] = struct{}{} + } + } + if len(insertExp) != len(tr.tracer.inserts) { + rt[i].err = errors.New("insert set mismatch") + } + if len(deleteExp) != len(tr.tracer.deletes) { + rt[i].err = errors.New("delete set mismatch") + } + for insert := range tr.tracer.inserts { + if _, present := insertExp[insert]; !present { + rt[i].err = errors.New("missing inserted node") + } + } + for del := range tr.tracer.deletes { + if _, present := deleteExp[del]; !present { + rt[i].err = errors.New("missing deleted node") + } + } + } + // Abort the test on error. + if rt[i].err != nil { + return rt[i].err + } + } + return nil +} + +func TestRandom(t *testing.T) { + 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)) + } + t.Fatal(err) + } +} + +func BenchmarkGet(b *testing.B) { benchGet(b) } +func BenchmarkUpdateBE(b *testing.B) { benchUpdate(b, binary.BigEndian) } +func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) } + +const benchElemCount = 20000 + +func benchGet(b *testing.B) { + triedb := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + trie := NewEmpty(triedb) + k := make([]byte, 32) + for i := 0; i < benchElemCount; i++ { + binary.LittleEndian.PutUint64(k, uint64(i)) + v := make([]byte, 32) + binary.LittleEndian.PutUint64(v, uint64(i)) + trie.MustUpdate(k, v) + } + binary.LittleEndian.PutUint64(k, benchElemCount/2) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + trie.MustGet(k) + } + b.StopTimer() +} + +func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie { + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + k := make([]byte, 32) + b.ReportAllocs() + for i := 0; i < b.N; i++ { + v := make([]byte, 32) + e.PutUint64(k, uint64(i)) + e.PutUint64(v, uint64(i)) + trie.MustUpdate(k, v) + } + return trie +} + +// Benchmarks the trie hashing. Since the trie caches the result of any operation, +// we cannot use b.N as the number of hashing rounds, since all rounds apart from +// the first one will be NOOP. As such, we'll use b.N as the number of account to +// insert into the trie before measuring the hashing. +// BenchmarkHash-6 288680 4561 ns/op 682 B/op 9 allocs/op +// BenchmarkHash-6 275095 4800 ns/op 685 B/op 9 allocs/op +// pure hasher: +// BenchmarkHash-6 319362 4230 ns/op 675 B/op 9 allocs/op +// BenchmarkHash-6 257460 4674 ns/op 689 B/op 9 allocs/op +// With hashing in-between and pure hasher: +// BenchmarkHash-6 225417 7150 ns/op 982 B/op 12 allocs/op +// BenchmarkHash-6 220378 6197 ns/op 983 B/op 12 allocs/op +// same with old hasher +// BenchmarkHash-6 229758 6437 ns/op 981 B/op 12 allocs/op +// BenchmarkHash-6 212610 7137 ns/op 986 B/op 12 allocs/op +func BenchmarkHash(b *testing.B) { + // Create a realistic account trie to hash. We're first adding and hashing N + // entries, then adding N more. + addresses, accounts := makeAccounts(2 * b.N) + // Insert the accounts into the trie and hash it + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + i := 0 + for ; i < len(addresses)/2; i++ { + trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + trie.Hash() + for ; i < len(addresses); i++ { + trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + b.ResetTimer() + b.ReportAllocs() + //trie.hashRoot(nil, nil) + trie.Hash() +} + +// Benchmarks the trie Commit following a Hash. Since the trie caches the result of any operation, +// we cannot use b.N as the number of hashing rounds, since all rounds apart from +// the first one will be NOOP. As such, we'll use b.N as the number of account to +// insert into the trie before measuring the hashing. +func BenchmarkCommitAfterHash(b *testing.B) { + b.Run("no-onleaf", func(b *testing.B) { + benchmarkCommitAfterHash(b, false) + }) + b.Run("with-onleaf", func(b *testing.B) { + benchmarkCommitAfterHash(b, true) + }) +} + +func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) { + // Make the random benchmark deterministic + addresses, accounts := makeAccounts(b.N) + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + for i := 0; i < len(addresses); i++ { + trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + // Insert the accounts into the trie and hash it + trie.Hash() + b.ResetTimer() + b.ReportAllocs() + trie.Commit(collectLeaf) +} + +func TestTinyTrie(t *testing.T) { + // Create a realistic account trie to hash + _, accounts := makeAccounts(5) + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + 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) + } + trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001338"), accounts[4]) + if exp, root := common.HexToHash("ec63b967e98a5720e7f720482151963982890d82c9093c0d486b7eb8883a66b1"), trie.Hash(); exp != root { + t.Errorf("2: got %x, exp %x", root, exp) + } + trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001339"), accounts[4]) + if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root { + t.Errorf("3: got %x, exp %x", root, exp) + } + checktr := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + it := NewIterator(trie.MustNodeIterator(nil)) + for it.Next() { + checktr.MustUpdate(it.Key, it.Value) + } + if troot, itroot := trie.Hash(), checktr.Hash(); troot != itroot { + t.Fatalf("hash mismatch in opItercheckhash, trie: %x, check: %x", troot, itroot) + } +} + +func TestCommitAfterHash(t *testing.T) { + // Create a realistic account trie to hash + addresses, accounts := makeAccounts(1000) + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + for i := 0; i < len(addresses); i++ { + trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + // Insert the accounts into the trie and hash it + trie.Hash() + trie.Commit(false) + root := trie.Hash() + exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6") + if exp != root { + t.Errorf("got %x, exp %x", root, exp) + } + root, _ = trie.Commit(false) + if exp != root { + t.Errorf("got %x, exp %x", root, exp) + } +} + +func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) { + // Make the random benchmark deterministic + random := rand.New(rand.NewSource(0)) + // Create a realistic account trie to hash + addresses = make([][20]byte, size) + for i := 0; i < len(addresses); i++ { + data := make([]byte, 20) + random.Read(data) + copy(addresses[i][:], data) + } + accounts = make([][]byte, len(addresses)) + for i := 0; i < len(accounts); i++ { + var ( + nonce = uint64(random.Int63()) + root = types.EmptyRootHash + code = crypto.Keccak256(nil) + ) + // The big.Rand function is not deterministic with regards to 64 vs 32 bit systems, + // and will consume different amount of data from the rand source. + //balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) + // Therefore, we instead just read via byte buffer + numBytes := random.Uint32() % 33 // [0, 32] bytes + balanceBytes := make([]byte, numBytes) + random.Read(balanceBytes) + balance := new(uint256.Int).SetBytes(balanceBytes) + data, _ := rlp.EncodeToBytes(&types.StateAccount{Nonce: nonce, Balance: balance, Root: root, CodeHash: code}) + accounts[i] = data + } + return addresses, accounts +} + +// spongeDb is a dummy db backend which accumulates writes in a sponge +type spongeDb struct { + sponge hash.Hash + id string + journal []string + keys []string + values map[string]string +} + +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() (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 { + 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, keybrief, len(value), valbrief)) + + if s.values == nil { + s.sponge.Write(key) + s.sponge.Write(value) + } else { + s.keys = append(s.keys, string(key)) + s.values[string(key)] = string(value) + } + return nil +} +func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") } + +func (s *spongeDb) Flush() { + // Bottom-up, the longest path first + sort.Sort(sort.Reverse(sort.StringSlice(s.keys))) + for _, key := range s.keys { + s.sponge.Write([]byte(key)) + s.sponge.Write([]byte(s.values[key])) + } +} + +// 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 } + +// TestCommitSequence tests that the trie.Commit operation writes the elements of the trie +// in the expected order. +// The test data was based on the 'master' code, and is basically random. It can be used +// to check whether changes to the trie modifies the write order or data in any way. +func TestCommitSequence(t *testing.T) { + for i, tc := range []struct { + count int + expWriteSeqHash []byte + }{ + {20, common.FromHex("330b0afae2853d96b9f015791fbe0fb7f239bf65f335f16dfc04b76c7536276d")}, + {200, common.FromHex("5162b3735c06b5d606b043a3ee8adbdbbb408543f4966bca9dcc63da82684eeb")}, + {2000, common.FromHex("4574cd8e6b17f3fe8ad89140d1d0bf4f1bd7a87a8ac3fb623b33550544c77635")}, + } { + addresses, accounts := makeAccounts(tc.count) + // This spongeDb is used to check the sequence of disk-db-writes + s := &spongeDb{sponge: crypto.NewKeccakState()} + db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme) + trie := NewEmpty(db) + // Fill the trie with elements + for i := 0; i < tc.count; i++ { + trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + // Flush trie -> database + root, nodes := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + // Flush memdb -> disk (sponge) + db.Commit(root) + if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { + t.Errorf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) + } + } +} + +// TestCommitSequenceRandomBlobs is identical to TestCommitSequence +// but uses random blobs instead of 'accounts' +func TestCommitSequenceRandomBlobs(t *testing.T) { + for i, tc := range []struct { + count int + expWriteSeqHash []byte + }{ + {20, common.FromHex("8016650c7a50cf88485fd06cde52d634a89711051107f00d21fae98234f2f13d")}, + {200, common.FromHex("dde92ca9812e068e6982d04b40846dc65a61a9fd4996fc0f55f2fde172a8e13c")}, + {2000, common.FromHex("ab553a7f9aff82e3929c382908e30ef7dd17a332933e92ba3fe873fc661ef382")}, + } { + prng := rand.New(rand.NewSource(int64(i))) + // This spongeDb is used to check the sequence of disk-db-writes + s := &spongeDb{sponge: crypto.NewKeccakState()} + db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme) + trie := NewEmpty(db) + // Fill the trie with elements + for i := 0; i < tc.count; i++ { + key := make([]byte, 32) + var val []byte + // 50% short elements, 50% large elements + if prng.Intn(2) == 0 { + val = make([]byte, 1+prng.Intn(32)) + } else { + val = make([]byte, 1+prng.Intn(4096)) + } + prng.Read(key) + prng.Read(val) + trie.MustUpdate(key, val) + } + // Flush trie -> database + root, nodes := trie.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + // Flush memdb -> disk (sponge) + db.Commit(root) + if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) + } + } +} + +func TestCommitSequenceStackTrie(t *testing.T) { + for count := 1; count < 200; count++ { + 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", + values: make(map[string]string), + } + db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme) + trie := NewEmpty(db) + + // Another sponge is used for the stacktrie commits + stackTrieSponge := &spongeDb{ + sponge: sha3.NewLegacyKeccak256(), + id: "b", + values: make(map[string]string), + } + stTrie := NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(stackTrieSponge, common.Hash{}, path, hash, blob, db.Scheme()) + }) + + // Fill the trie with elements + for i := 0; i < count; i++ { + // For the stack trie, we need to do inserts in proper order + key := make([]byte, 32) + binary.BigEndian.PutUint64(key, uint64(i)) + var val []byte + // 50% short elements, 50% large elements + if prng.Intn(2) == 0 { + val = make([]byte, 1+prng.Intn(32)) + } else { + val = make([]byte, 1+prng.Intn(1024)) + } + prng.Read(val) + trie.Update(key, val) + stTrie.Update(key, val) + } + // Flush trie -> database + root, nodes := trie.Commit(false) + // Flush memdb -> disk (sponge) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + db.Commit(root) + s.Flush() + + // And flush stacktrie -> disk + stRoot := stTrie.Hash() + if stRoot != root { + t.Fatalf("root wrong, got %x exp %x", stRoot, root) + } + stackTrieSponge.Flush() + if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) { + // Show the journal + t.Logf("Expected:") + for i, v := range s.journal { + t.Logf("op %d: %v", i, v) + } + t.Logf("Stacktrie:") + for i, v := range stackTrieSponge.journal { + t.Logf("op %d: %v", i, v) + } + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", count, got, exp) + } + } +} + +// TestCommitSequenceSmallRoot tests that a trie which is essentially only a +// small (<32 byte) shortnode with an included value is properly committed to a +// database. +// This case might not matter, since in practice, all keys are 32 bytes, which means +// that even a small trie which contains a leaf will have an extension making it +// 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", + values: make(map[string]string), + } + db := newTestDatabase(rawdb.NewDatabase(s), rawdb.HashScheme) + trie := NewEmpty(db) + + // Another sponge is used for the stacktrie commits + stackTrieSponge := &spongeDb{ + sponge: sha3.NewLegacyKeccak256(), + id: "b", + values: make(map[string]string), + } + stTrie := NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(stackTrieSponge, common.Hash{}, path, hash, blob, db.Scheme()) + }) + // Add a single small-element to the trie(s) + key := make([]byte, 5) + key[0] = 1 + trie.Update(key, []byte{0x1}) + stTrie.Update(key, []byte{0x1}) + + // Flush trie -> database + root, nodes := trie.Commit(false) + // Flush memdb -> disk (sponge) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + db.Commit(root) + + // And flush stacktrie -> disk + stRoot := stTrie.Hash() + if stRoot != root { + t.Fatalf("root wrong, got %x exp %x", stRoot, root) + } + t.Logf("root: %x\n", stRoot) + + s.Flush() + stackTrieSponge.Flush() + if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) { + t.Fatalf("test, disk write sequence wrong:\ngot %x exp %x\n", got, exp) + } +} + +// BenchmarkHashFixedSize benchmarks the hash of a fixed number of updates to a trie. +// This benchmark is meant to capture the difference on efficiency of small versus large changes. Typically, +// storage tries are small (a couple of entries), whereas the full post-block account trie update is large (a couple +// of thousand entries) +func BenchmarkHashFixedSize(b *testing.B) { + b.Run("10", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(20) + for i := 0; i < b.N; i++ { + benchmarkHashFixedSize(b, acc, add) + } + }) + b.Run("100", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(100) + for i := 0; i < b.N; i++ { + benchmarkHashFixedSize(b, acc, add) + } + }) + + b.Run("1K", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(1000) + for i := 0; i < b.N; i++ { + benchmarkHashFixedSize(b, acc, add) + } + }) + b.Run("10K", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(10000) + for i := 0; i < b.N; i++ { + benchmarkHashFixedSize(b, acc, add) + } + }) + b.Run("100K", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(100000) + for i := 0; i < b.N; i++ { + benchmarkHashFixedSize(b, acc, add) + } + }) +} + +func benchmarkHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) { + b.ReportAllocs() + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + for i := 0; i < len(addresses); i++ { + trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + // Insert the accounts into the trie and hash it + b.StartTimer() + trie.Hash() + b.StopTimer() +} + +func BenchmarkCommitAfterHashFixedSize(b *testing.B) { + b.Run("10", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(20) + for i := 0; i < b.N; i++ { + benchmarkCommitAfterHashFixedSize(b, acc, add) + } + }) + b.Run("100", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(100) + for i := 0; i < b.N; i++ { + benchmarkCommitAfterHashFixedSize(b, acc, add) + } + }) + + b.Run("1K", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(1000) + for i := 0; i < b.N; i++ { + benchmarkCommitAfterHashFixedSize(b, acc, add) + } + }) + b.Run("10K", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(10000) + for i := 0; i < b.N; i++ { + benchmarkCommitAfterHashFixedSize(b, acc, add) + } + }) + b.Run("100K", func(b *testing.B) { + b.StopTimer() + acc, add := makeAccounts(100000) + for i := 0; i < b.N; i++ { + benchmarkCommitAfterHashFixedSize(b, acc, add) + } + }) +} + +func benchmarkCommitAfterHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) { + b.ReportAllocs() + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + for i := 0; i < len(addresses); i++ { + trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + // Insert the accounts into the trie and hash it + trie.Hash() + b.StartTimer() + trie.Commit(false) + b.StopTimer() +} + +func getString(trie *Trie, k string) []byte { + return trie.MustGet([]byte(k)) +} + +func updateString(trie *Trie, k, v string) { + trie.MustUpdate([]byte(k), []byte(v)) +} + +func deleteString(trie *Trie, k string) { + trie.MustDelete([]byte(k)) +} + +func TestDecodeNode(t *testing.T) { + t.Parallel() + + var ( + hash = make([]byte, 20) + elems = make([]byte, 20) + ) + for i := 0; i < 5000000; i++ { + prng.Read(hash) + prng.Read(elems) + 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/trienode/node.go b/trie/trienode/node.go new file mode 100644 index 0000000..09f355f --- /dev/null +++ b/trie/trienode/node.go @@ -0,0 +1,192 @@ +// 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 trienode + +import ( + "fmt" + "sort" + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +// Node is a wrapper which contains the encoded blob of the trie node and its +// node hash. It is general enough that can be used to represent trie node +// corresponding to different trie implementations. +type Node struct { + Hash common.Hash // Node hash, empty for deleted node + Blob []byte // Encoded node blob, nil for the deleted node +} + +// Size returns the total memory size used by this node. +func (n *Node) Size() int { + return len(n.Blob) + common.HashLength +} + +// IsDeleted returns the indicator if the node is marked as deleted. +func (n *Node) IsDeleted() bool { + return len(n.Blob) == 0 +} + +// New constructs a node with provided node information. +func New(hash common.Hash, blob []byte) *Node { + return &Node{Hash: hash, Blob: blob} +} + +// NewDeleted constructs a node which is deleted. +func NewDeleted() *Node { return New(common.Hash{}, nil) } + +// leaf represents a trie leaf node +type leaf struct { + Blob []byte // raw blob of leaf + Parent common.Hash // the hash of parent node +} + +// NodeSet contains a set of nodes collected during the commit operation. +// Each node is keyed by path. It's not thread-safe to use. +type NodeSet struct { + Owner common.Hash + Leaves []*leaf + Nodes map[string]*Node + updates int // the count of updated and inserted nodes + deletes int // the count of deleted nodes +} + +// NewNodeSet initializes a node set. The owner is zero for the account trie and +// the owning account address hash for storage tries. +func NewNodeSet(owner common.Hash) *NodeSet { + return &NodeSet{ + Owner: owner, + Nodes: make(map[string]*Node), + } +} + +// ForEachWithOrder iterates the nodes with the order from bottom to top, +// right to left, nodes with the longest path will be iterated first. +func (set *NodeSet) ForEachWithOrder(callback func(path string, n *Node)) { + paths := make([]string, 0, len(set.Nodes)) + for path := range set.Nodes { + paths = append(paths, path) + } + // Bottom-up, the longest path first + sort.Sort(sort.Reverse(sort.StringSlice(paths))) + for _, path := range paths { + callback(path, set.Nodes[path]) + } +} + +// AddNode adds the provided node into set. +func (set *NodeSet) AddNode(path []byte, n *Node) { + if n.IsDeleted() { + set.deletes += 1 + } else { + set.updates += 1 + } + set.Nodes[string(path)] = n +} + +// Merge adds a set of nodes into the set. +func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*Node) error { + if set.Owner != owner { + return fmt.Errorf("nodesets belong to different owner are not mergeable %x-%x", set.Owner, owner) + } + for path, node := range nodes { + prev, ok := set.Nodes[path] + if ok { + // overwrite happens, revoke the counter + if prev.IsDeleted() { + set.deletes -= 1 + } else { + set.updates -= 1 + } + } + if node.IsDeleted() { + set.deletes += 1 + } else { + set.updates += 1 + } + set.Nodes[path] = node + } + return nil +} + +// AddLeaf adds the provided leaf node into set. TODO(rjl493456442) how can +// we get rid of it? +func (set *NodeSet) AddLeaf(parent common.Hash, blob []byte) { + set.Leaves = append(set.Leaves, &leaf{Blob: blob, Parent: parent}) +} + +// Size returns the number of dirty nodes in set. +func (set *NodeSet) Size() (int, int) { + return set.updates, set.deletes +} + +// Summary returns a string-representation of the NodeSet. +func (set *NodeSet) Summary() string { + var out = new(strings.Builder) + fmt.Fprintf(out, "nodeset owner: %v\n", set.Owner) + for path, n := range set.Nodes { + // Deletion + if n.IsDeleted() { + fmt.Fprintf(out, " [-]: %x\n", path) + continue + } + // Insertion or update + fmt.Fprintf(out, " [+/*]: %x -> %v \n", path, n.Hash) + } + for _, n := range set.Leaves { + fmt.Fprintf(out, "[leaf]: %v\n", n) + } + return out.String() +} + +// MergedNodeSet represents a merged node set for a group of tries. +type MergedNodeSet struct { + Sets map[common.Hash]*NodeSet +} + +// NewMergedNodeSet initializes an empty merged set. +func NewMergedNodeSet() *MergedNodeSet { + return &MergedNodeSet{Sets: make(map[common.Hash]*NodeSet)} +} + +// NewWithNodeSet constructs a merged nodeset with the provided single set. +func NewWithNodeSet(set *NodeSet) *MergedNodeSet { + merged := NewMergedNodeSet() + merged.Merge(set) + return merged +} + +// Merge merges the provided dirty nodes of a trie into the set. The assumption +// is held that no duplicated set belonging to the same trie will be merged twice. +func (set *MergedNodeSet) Merge(other *NodeSet) error { + subset, present := set.Sets[other.Owner] + if present { + return subset.Merge(other.Owner, other.Nodes) + } + set.Sets[other.Owner] = other + return nil +} + +// Flatten returns a two-dimensional map for internal nodes. +func (set *MergedNodeSet) Flatten() map[common.Hash]map[string]*Node { + nodes := make(map[common.Hash]map[string]*Node, len(set.Sets)) + for owner, set := range set.Sets { + nodes[owner] = set.Nodes + } + return nodes +} diff --git a/trie/trienode/node_test.go b/trie/trienode/node_test.go new file mode 100644 index 0000000..bcb3a22 --- /dev/null +++ b/trie/trienode/node_test.go @@ -0,0 +1,61 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package trienode + +import ( + "crypto/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func BenchmarkMerge(b *testing.B) { + b.Run("1K", func(b *testing.B) { + benchmarkMerge(b, 1000) + }) + b.Run("10K", func(b *testing.B) { + benchmarkMerge(b, 10_000) + }) +} + +func benchmarkMerge(b *testing.B, count int) { + x := NewNodeSet(common.Hash{}) + y := NewNodeSet(common.Hash{}) + addNode := func(s *NodeSet) { + path := make([]byte, 4) + rand.Read(path) + blob := make([]byte, 32) + rand.Read(blob) + hash := crypto.Keccak256Hash(blob) + s.AddNode(path, New(hash, blob)) + } + for i := 0; i < count; i++ { + // Random path of 4 nibbles + addNode(x) + addNode(y) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Store set x into a backup + z := NewNodeSet(common.Hash{}) + z.Merge(common.Hash{}, x.Nodes) + // Merge y into x + x.Merge(common.Hash{}, y.Nodes) + x = z + } +} diff --git a/trie/trienode/proof.go b/trie/trienode/proof.go new file mode 100644 index 0000000..d3075ec --- /dev/null +++ b/trie/trienode/proof.go @@ -0,0 +1,162 @@ +// 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 trienode + +import ( + "errors" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" +) + +// ProofSet stores a set of trie nodes. It implements trie.Database and can also +// act as a cache for another trie.Database. +type ProofSet struct { + nodes map[string][]byte + order []string + + dataSize int + lock sync.RWMutex +} + +// 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 *ProofSet) Put(key []byte, value []byte) error { + db.lock.Lock() + defer db.lock.Unlock() + + if _, ok := db.nodes[string(key)]; ok { + return nil + } + keystr := string(key) + + db.nodes[keystr] = common.CopyBytes(value) + db.order = append(db.order, keystr) + db.dataSize += len(value) + + return nil +} + +// Delete removes a node from the set +func (db *ProofSet) Delete(key []byte) error { + db.lock.Lock() + defer db.lock.Unlock() + + delete(db.nodes, string(key)) + return nil +} + +// Get returns a stored node +func (db *ProofSet) Get(key []byte) ([]byte, error) { + db.lock.RLock() + defer db.lock.RUnlock() + + if entry, ok := db.nodes[string(key)]; ok { + return entry, nil + } + return nil, errors.New("not found") +} + +// Has returns true if the node set contains the given key +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 *ProofSet) KeyCount() int { + db.lock.RLock() + defer db.lock.RUnlock() + + return len(db.nodes) +} + +// DataSize returns the aggregated data size of nodes in the set +func (db *ProofSet) DataSize() int { + db.lock.RLock() + defer db.lock.RUnlock() + + return db.dataSize +} + +// List converts the node set to a slice of bytes. +func (db *ProofSet) List() [][]byte { + db.lock.RLock() + defer db.lock.RUnlock() + + values := make([][]byte, len(db.order)) + for i, key := range db.order { + values[i] = db.nodes[key] + } + return values +} + +// Store writes the contents of the set to the given database +func (db *ProofSet) Store(target ethdb.KeyValueWriter) { + db.lock.RLock() + defer db.lock.RUnlock() + + for key, value := range db.nodes { + target.Put([]byte(key), value) + } +} + +// 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 ProofList) Store(db ethdb.KeyValueWriter) { + for _, node := range n { + db.Put(crypto.Keccak256(node), node) + } +} + +// 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 *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 *ProofList) Delete(key []byte) error { + panic("not supported") +} + +// DataSize returns the aggregated data size of nodes in the list +func (n ProofList) DataSize() int { + var size int + for _, node := range n { + size += len(node) + } + return size +} diff --git a/trie/triestate/state.go b/trie/triestate/state.go new file mode 100644 index 0000000..62a9043 --- /dev/null +++ b/trie/triestate/state.go @@ -0,0 +1,53 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package triestate + +import "github.com/ethereum/go-ethereum/common" + +// Set represents a collection of mutated states during a state transition. +// The value refers to the original content of state before the transition +// is made. Nil means that the state was not present previously. +type Set struct { + Accounts map[common.Address][]byte // Mutated account set, nil means the account was not present + Storages map[common.Address]map[common.Hash][]byte // Mutated storage set, nil means the slot was not present + size common.StorageSize // Approximate size of set +} + +// New constructs the state set with provided data. +func New(accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte) *Set { + return &Set{ + Accounts: accounts, + Storages: storages, + } +} + +// Size returns the approximate memory size occupied by the set. +func (s *Set) Size() common.StorageSize { + if s.size != 0 { + return s.size + } + for _, account := range s.Accounts { + s.size += common.StorageSize(common.AddressLength + len(account)) + } + for _, slots := range s.Storages { + for _, val := range slots { + s.size += common.StorageSize(common.HashLength + len(val)) + } + s.size += common.StorageSize(common.AddressLength) + } + return s.size +} diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go new file mode 100644 index 0000000..2a4a632 --- /dev/null +++ b/trie/utils/verkle.go @@ -0,0 +1,337 @@ +// 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/ethereum/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, subIndexMod = new(uint256.Int).DivMod(chunkOffset, verkleNodeWidth, new(uint256.Int)) + ) + return treeIndex, byte(subIndexMod.Uint64()) +} + +// 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/ethereum/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 0000000..a457097 --- /dev/null +++ b/trie/verkle.go @@ -0,0 +1,374 @@ +// 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" + + "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/ethereum/go-ethereum/triedb/database" + "github.com/ethereum/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 + cache *utils.PointCache + reader *trieReader +} + +// NewVerkleTrie constructs a verkle tree based on the specified root hash. +func NewVerkleTrie(root common.Hash, db database.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, + 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(uint256.Int).SetBytes32(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() + 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) { + root := t.root.(*verkle.InternalNode) + nodes, err := root.BatchSerialize() + if err != nil { + // Error return from this function indicates error in the code logic + // of BatchSerialize, and we fail catastrophically if this is the case. + panic(fmt.Errorf("BatchSerialize failed: %v", 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 +} + +// 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(), + 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{}) +} + +// Witness returns a set containing all trie nodes that have been accessed. +func (t *VerkleTrie) Witness() map[string]struct{} { + panic("not implemented") +} diff --git a/trie/verkle_test.go b/trie/verkle_test.go new file mode 100644 index 0000000..0cbe28b --- /dev/null +++ b/trie/verkle_test.go @@ -0,0 +1,91 @@ +// 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" + "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/utils" + "github.com/holiman/uint256" +) + +var ( + accounts = map[common.Address]*types.StateAccount{ + {1}: { + Nonce: 100, + Balance: uint256.NewInt(100), + CodeHash: common.Hash{0x1}.Bytes(), + }, + {2}: { + Nonce: 200, + Balance: uint256.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 := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) + 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") + } + } + } +} diff --git a/triedb/database.go b/triedb/database.go new file mode 100644 index 0000000..91386a9 --- /dev/null +++ b/triedb/database.go @@ -0,0 +1,330 @@ +// 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 triedb + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" + "github.com/ethereum/go-ethereum/triedb/database" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" +) + +// Config defines all necessary options for database. +type Config struct { + 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 +} + +// 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 +// state scheme. +type backend interface { + // Initialized returns an indicator if the state data is already initialized + // according to the state scheme. + Initialized(genesisRoot common.Hash) bool + + // Size returns the current storage size of the diff layers on top of the + // disk layer and the storage size of the nodes cached in the disk layer. + // + // For hash scheme, there is no differentiation between diff layer nodes + // and dirty disk layer nodes, so both are merged into the second return. + Size() (common.StorageSize, common.StorageSize) + + // Update performs a state transition by committing dirty nodes contained + // in the given set in order to update state from the specified parent to + // the specified root. + // + // The passed in maps(nodes, states) will be retained to avoid copying + // everything. Therefore, these maps must not be changed afterwards. + Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error + + // Commit writes all relevant trie nodes belonging to the specified state + // to disk. Report specifies whether logs will be displayed in info level. + Commit(root common.Hash, report bool) error + + // Close closes the trie database backend and releases all held resources. + Close() error + + // Reader returns a reader for accessing all trie nodes with provided state + // root. An error will be returned if the requested state is not available. + Reader(root common.Hash) (database.Reader, error) +} + +// Database is the wrapper of the underlying backend which is shared by different +// types of node backend as an entrypoint. It's responsible for all interactions +// relevant with trie nodes and node preimages. +type Database struct { + config *Config // Configuration for trie database + diskdb ethdb.Database // Persistent database to store the snapshot + preimages *preimageStore // The store for caching preimages + backend backend // The backend for managing trie nodes +} + +// 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.Preimages { + preimages = newPreimageStore(diskdb) + } + db := &Database{ + config: config, + diskdb: diskdb, + preimages: preimages, + } + 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, config.IsVerkle) + } else { + var resolver hashdb.ChildResolver + if config.IsVerkle { + // TODO define verkle resolver + log.Crit("verkle does not use a hash db") + } else { + resolver = trie.MerkleResolver{} + } + db.backend = hashdb.New(diskdb, config.HashDB, resolver) + } + return db +} + +// Reader returns a reader for accessing all trie nodes with provided state root. +// An error will be returned if the requested state is not available. +func (db *Database) Reader(blockRoot common.Hash) (database.Reader, error) { + return db.backend.Reader(blockRoot) +} + +// Update performs a state transition by committing dirty nodes contained in the +// given set in order to update state from the specified parent to the specified +// root. The held pre-images accumulated up to this point will be flushed in case +// the size exceeds the threshold. +// +// The passed in maps(nodes, states) will be retained to avoid copying everything. +// Therefore, these maps must not be changed afterwards. +func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { + if db.preimages != nil { + db.preimages.commit(false) + } + return db.backend.Update(root, parent, block, nodes, states) +} + +// Commit iterates over all the children of a particular node, writes them out +// to disk. As a side effect, all pre-images accumulated up to this point are +// also written. +func (db *Database) Commit(root common.Hash, report bool) error { + if db.preimages != nil { + db.preimages.commit(true) + } + return db.backend.Commit(root, report) +} + +// 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 ( + diffs, nodes common.StorageSize + preimages common.StorageSize + ) + diffs, nodes = db.backend.Size() + if db.preimages != nil { + preimages = db.preimages.size() + } + return diffs, nodes, preimages +} + +// Initialized returns an indicator if the state data is already initialized +// according to the state scheme. +func (db *Database) Initialized(genesisRoot common.Hash) bool { + return db.backend.Initialized(genesisRoot) +} + +// Scheme returns the node scheme used in the database. +func (db *Database) Scheme() string { + if db.config.PathDB != nil { + return rawdb.PathScheme + } + return rawdb.HashScheme +} + +// Close flushes the dangling preimages to disk and closes the trie database. +// It is meant to be called when closing the blockchain object, so that all +// resources held can be released correctly. +func (db *Database) Close() error { + db.WritePreimages() + return db.backend.Close() +} + +// WritePreimages flushes all accumulated preimages to disk forcibly. +func (db *Database) WritePreimages() { + if db.preimages != nil { + db.preimages.commit(true) + } +} + +// Preimage retrieves a cached trie node pre-image from preimage store. +func (db *Database) Preimage(hash common.Hash) []byte { + if db.preimages == nil { + return nil + } + return db.preimages.preimage(hash) +} + +// InsertPreimage writes pre-images of trie node to the preimage store. +func (db *Database) InsertPreimage(preimages map[common.Hash][]byte) { + if db.preimages == nil { + return + } + db.preimages.insertPreimage(preimages) +} + +// 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. +// +// It's only supported by hash-based database and will return an error for others. +func (db *Database) Cap(limit common.StorageSize) error { + hdb, ok := db.backend.(*hashdb.Database) + if !ok { + return errors.New("not supported") + } + if db.preimages != nil { + db.preimages.commit(false) + } + return hdb.Cap(limit) +} + +// 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 are referenced together by database itself. +// +// It's only supported by hash-based database and will return an error for others. +func (db *Database) Reference(root common.Hash, parent common.Hash) error { + hdb, ok := db.backend.(*hashdb.Database) + if !ok { + return errors.New("not supported") + } + hdb.Reference(root, parent) + return nil +} + +// Dereference removes an existing reference from a root node. It's only +// supported by hash-based database and will return an error for others. +func (db *Database) Dereference(root common.Hash) error { + hdb, ok := db.backend.(*hashdb.Database) + if !ok { + return errors.New("not supported") + } + hdb.Dereference(root) + return nil +} + +// Recover rollbacks the database to a specified historical point. The state is +// supported as the rollback destination only if it's canonical state and the +// corresponding trie histories are existent. It's only supported by path-based +// database and will return an error for others. +func (db *Database) Recover(target common.Hash) error { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return errors.New("not supported") + } + return pdb.Recover(target) +} + +// 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/triedb/database/database.go b/triedb/database/database.go new file mode 100644 index 0000000..9bd5da0 --- /dev/null +++ b/triedb/database/database.go @@ -0,0 +1,37 @@ +// Copyright 2024 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 database + +import "github.com/ethereum/go-ethereum/common" + +// Reader wraps the Node method of a backing trie reader. +type Reader interface { + // Node retrieves the trie node blob with the provided trie identifier, + // node path and the corresponding node hash. No error will be returned + // if the node is not found. + // + // Don't modify the returned byte slice since it's not deep-copied and + // still be referenced by database. + Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) +} + +// Database wraps the methods of a backing trie store. +type Database interface { + // Reader returns a node reader associated with the specific state. + // An error will be returned if the specified state is not available. + Reader(stateRoot common.Hash) (Reader, error) +} diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go new file mode 100644 index 0000000..bb0deca --- /dev/null +++ b/triedb/hashdb/database.go @@ -0,0 +1,646 @@ +// 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 hashdb + +import ( + "errors" + "fmt" + "reflect" + "sync" + "time" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" + "github.com/ethereum/go-ethereum/triedb/database" +) + +var ( + memcacheCleanHitMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/hit", nil) + memcacheCleanMissMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/miss", nil) + memcacheCleanReadMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/read", nil) + memcacheCleanWriteMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/write", nil) + + memcacheDirtyHitMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/hit", nil) + memcacheDirtyMissMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/miss", nil) + memcacheDirtyReadMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/read", nil) + memcacheDirtyWriteMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/write", nil) + + memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/flush/time", nil) + memcacheFlushNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/flush/nodes", nil) + memcacheFlushBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/flush/bytes", nil) + + memcacheGCTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/gc/time", nil) + memcacheGCNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/gc/nodes", nil) + memcacheGCBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/gc/bytes", nil) + + memcacheCommitTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/commit/time", nil) + memcacheCommitNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/nodes", nil) + memcacheCommitBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/bytes", nil) +) + +// ChildResolver defines the required method to decode the provided +// trie node and iterate the children on top. +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. +type Database struct { + diskdb ethdb.Database // Persistent storage for matured trie nodes + resolver ChildResolver // The handler to resolve children of nodes + + cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs + dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes + oldest common.Hash // Oldest tracked node, flush-list head + newest common.Hash // Newest tracked node, flush-list tail + + gctime time.Duration // Time spent on garbage collection since last commit + gcnodes uint64 // Nodes garbage collected since last commit + gcsize common.StorageSize // Data storage garbage collected since last commit + + flushtime time.Duration // Time spent on data flushing since last commit + flushnodes uint64 // Nodes flushed since last commit + flushsize common.StorageSize // Data storage flushed since last commit + + dirtiesSize common.StorageSize // Storage size of the dirty node cache (exc. metadata) + childrenSize common.StorageSize // Storage size of the external children tracking + + lock sync.RWMutex +} + +// 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, 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 + flushNext common.Hash // Next node in the flush-list +} + +// cachedNodeSize is the raw size of a cachedNode data structure without any +// node data included. It's an approximate size, but should be a lot better +// than not counting them. +var cachedNodeSize = int(reflect.TypeOf(cachedNode{}).Size()) + +// forChildren invokes the callback for all the tracked children of this node, +// both the implicit ones from inside the node as well as the explicit ones +// from outside the node. +func (n *cachedNode) forChildren(resolver ChildResolver, onChild func(hash common.Hash)) { + for child := range n.external { + onChild(child) + } + resolver.ForEach(n.node, onChild) +} + +// New initializes the hash-based node database. +func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Database { + if config == nil { + config = Defaults + } + var cleans *fastcache.Cache + if config.CleanCacheSize > 0 { + cleans = fastcache.New(config.CleanCacheSize) + } + return &Database{ + diskdb: diskdb, + resolver: resolver, + cleans: cleans, + dirties: make(map[common.Hash]*cachedNode), + } +} + +// 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 { + return + } + memcacheDirtyWriteMeter.Mark(int64(len(node))) + + // Create the cached entry for this node + entry := &cachedNode{ + node: node, + flushPrev: db.newest, + } + entry.forChildren(db.resolver, func(child common.Hash) { + if c := db.dirties[child]; c != nil { + c.parents++ + } + }) + db.dirties[hash] = entry + + // Update the flush-list endpoints + if db.oldest == (common.Hash{}) { + db.oldest, db.newest = hash, hash + } else { + db.dirties[db.newest].flushNext, db.newest = hash, hash + } + db.dirtiesSize += common.StorageSize(common.HashLength + len(node)) +} + +// 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) { + // It doesn't make sense to retrieve the metaroot + if hash == (common.Hash{}) { + return nil, errors.New("not found") + } + // Retrieve the node from the clean cache if available + if db.cleans != nil { + if enc := db.cleans.Get(nil, hash[:]); enc != nil { + memcacheCleanHitMeter.Mark(1) + memcacheCleanReadMeter.Mark(int64(len(enc))) + return enc, nil + } + } + // 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))) + return dirty.node, nil + } + memcacheDirtyMissMeter.Mark(1) + + // Content unavailable in memory, attempt to retrieve from disk + enc := rawdb.ReadLegacyTrieNode(db.diskdb, hash) + if len(enc) != 0 { + if db.cleans != nil { + db.cleans.Set(hash[:], enc) + memcacheCleanMissMeter.Mark(1) + memcacheCleanWriteMeter.Mark(int64(len(enc))) + } + return enc, nil + } + return nil, errors.New("not found") +} + +// 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 +// are referenced together by database itself. +func (db *Database) Reference(child common.Hash, parent common.Hash) { + db.lock.Lock() + defer db.lock.Unlock() + + db.reference(child, parent) +} + +// reference is the private locked version of Reference. +func (db *Database) reference(child common.Hash, parent common.Hash) { + // If the node does not exist, it's a node pulled from disk, skip + node, ok := db.dirties[child] + if !ok { + return + } + // The reference is for state root, increase the reference counter. + if parent == (common.Hash{}) { + node.parents += 1 + return + } + // The reference is for external storage trie, don't duplicate if + // the reference is already existent. + if db.dirties[parent].external == nil { + db.dirties[parent].external = make(map[common.Hash]struct{}) + } + if _, ok := db.dirties[parent].external[child]; ok { + return + } + node.parents++ + db.dirties[parent].external[child] = struct{}{} + db.childrenSize += common.HashLength +} + +// Dereference removes an existing reference from a root node. +func (db *Database) Dereference(root common.Hash) { + // Sanity check to ensure that the meta-root is not removed + if root == (common.Hash{}) { + log.Error("Attempted to dereference the trie cache meta root") + return + } + db.lock.Lock() + defer db.lock.Unlock() + + nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now() + db.dereference(root) + + db.gcnodes += uint64(nodes - len(db.dirties)) + db.gcsize += storage - db.dirtiesSize + db.gctime += time.Since(start) + + memcacheGCTimeTimer.Update(time.Since(start)) + memcacheGCBytesMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheGCNodesMeter.Mark(int64(nodes - len(db.dirties))) + + log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start), + "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize) +} + +// dereference is the private locked version of Dereference. +func (db *Database) dereference(hash common.Hash) { + // If the node does not exist, it's a previously committed node. + node, ok := db.dirties[hash] + if !ok { + return + } + // If there are no more references to the node, delete it and cascade + if node.parents > 0 { + // This is a special cornercase where a node loaded from disk (i.e. not in the + // memcache any more) gets reinjected as a new node (short node split into full, + // then reverted into short), causing a cached node to have no parents. That is + // no problem in itself, but don't make maxint parents out of it. + node.parents-- + } + if node.parents == 0 { + // Remove the node from the flush-list + switch hash { + case db.oldest: + db.oldest = node.flushNext + if node.flushNext != (common.Hash{}) { + db.dirties[node.flushNext].flushPrev = common.Hash{} + } + case db.newest: + db.newest = node.flushPrev + if node.flushPrev != (common.Hash{}) { + db.dirties[node.flushPrev].flushNext = common.Hash{} + } + default: + db.dirties[node.flushPrev].flushNext = node.flushNext + db.dirties[node.flushNext].flushPrev = node.flushPrev + } + // Dereference all children and delete the node + node.forChildren(db.resolver, func(child common.Hash) { + db.dereference(child) + }) + delete(db.dirties, hash) + db.dirtiesSize -= common.StorageSize(common.HashLength + len(node.node)) + if node.external != nil { + db.childrenSize -= common.StorageSize(len(node.external) * common.HashLength) + } + } +} + +// Cap iteratively flushes old but still referenced trie nodes until the total +// memory usage goes below the given threshold. +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. + 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 + // counted. + size := db.dirtiesSize + common.StorageSize(len(db.dirties)*cachedNodeSize) + size += db.childrenSize + + // Keep committing nodes from the flush-list until we're below allowance + oldest := db.oldest + for size > limit && oldest != (common.Hash{}) { + // Fetch the oldest referenced node and push into the batch + node := db.dirties[oldest] + rawdb.WriteLegacyTrieNode(batch, oldest, node.node) + + // If we exceeded the ideal batch size, commit and reset + if batch.ValueSize() >= ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Error("Failed to write flush list to disk", "err", err) + return err + } + batch.Reset() + } + // Iterate to the next flush item, or abort if the size cap was achieved. Size + // is the total size, including the useful cached data (hash -> blob), the + // cache item metadata, as well as external children mappings. + size -= common.StorageSize(common.HashLength + len(node.node) + cachedNodeSize) + if node.external != nil { + size -= common.StorageSize(len(node.external) * common.HashLength) + } + oldest = node.flushNext + } + // Flush out any remainder data from the last batch + if err := batch.Write(); err != nil { + log.Error("Failed to write flush list to disk", "err", err) + return err + } + // Write successful, clear out the flushed data + for db.oldest != oldest { + node := db.dirties[db.oldest] + delete(db.dirties, db.oldest) + db.oldest = node.flushNext + + db.dirtiesSize -= common.StorageSize(common.HashLength + len(node.node)) + if node.external != nil { + db.childrenSize -= common.StorageSize(len(node.external) * common.HashLength) + } + } + if db.oldest != (common.Hash{}) { + db.dirties[db.oldest].flushPrev = common.Hash{} + } + db.flushnodes += uint64(nodes - len(db.dirties)) + db.flushsize += storage - db.dirtiesSize + db.flushtime += time.Since(start) + + memcacheFlushTimeTimer.Update(time.Since(start)) + memcacheFlushBytesMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheFlushNodesMeter.Mark(int64(nodes - len(db.dirties))) + + log.Debug("Persisted nodes from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start), + "flushnodes", db.flushnodes, "flushsize", db.flushsize, "flushtime", db.flushtime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize) + + return nil +} + +// 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. +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 + // by only uncaching existing data when the database write finalizes. + start := time.Now() + batch := db.diskdb.NewBatch() + + // Move the trie itself into the batch, flushing if enough data is accumulated + nodes, storage := len(db.dirties), db.dirtiesSize + + uncacher := &cleaner{db} + if err := db.commit(node, batch, uncacher); err != nil { + log.Error("Failed to commit trie from trie database", "err", err) + return err + } + // Trie mostly committed to disk, flush any batch leftovers + if err := batch.Write(); err != nil { + log.Error("Failed to write trie to disk", "err", err) + return err + } + // Uncache any leftovers in the last batch + if err := batch.Replay(uncacher); err != nil { + return err + } + batch.Reset() + + // Reset the storage counters and bumped metrics + memcacheCommitTimeTimer.Update(time.Since(start)) + memcacheCommitBytesMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties))) + + logger := log.Info + if !report { + logger = log.Debug + } + logger("Persisted trie from memory database", "nodes", nodes-len(db.dirties)+int(db.flushnodes), "size", storage-db.dirtiesSize+db.flushsize, "time", time.Since(start)+db.flushtime, + "gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.dirties), "livesize", db.dirtiesSize) + + // Reset the garbage collection statistics + db.gcnodes, db.gcsize, db.gctime = 0, 0, 0 + db.flushnodes, db.flushsize, db.flushtime = 0, 0, 0 + + return nil +} + +// commit is the private locked version of Commit. +func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error { + // If the node does not exist, it's a previously committed node + node, ok := db.dirties[hash] + if !ok { + return nil + } + var err error + + // Dereference all children and delete the node + node.forChildren(db.resolver, func(child common.Hash) { + if err == nil { + err = db.commit(child, batch, uncacher) + } + }) + if err != nil { + return err + } + // If we've reached an optimal batch size, commit and start over + rawdb.WriteLegacyTrieNode(batch, hash, node.node) + if batch.ValueSize() >= ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + err := batch.Replay(uncacher) + if err != nil { + return err + } + batch.Reset() + } + return nil +} + +// cleaner is a database batch replayer that takes a batch of write operations +// and cleans up the trie database from anything written to disk. +type cleaner struct { + db *Database +} + +// Put reacts to database writes and implements dirty data uncaching. This is the +// post-processing step of a commit operation where the already persisted trie is +// removed from the dirty cache and moved into the clean cache. The reason behind +// the two-phase commit is to ensure data availability while moving from memory +// to disk. +func (c *cleaner) Put(key []byte, rlp []byte) error { + hash := common.BytesToHash(key) + + // If the node does not exist, we're done on this path + node, ok := c.db.dirties[hash] + if !ok { + return nil + } + // Node still exists, remove it from the flush-list + switch hash { + case c.db.oldest: + c.db.oldest = node.flushNext + if node.flushNext != (common.Hash{}) { + c.db.dirties[node.flushNext].flushPrev = common.Hash{} + } + case c.db.newest: + c.db.newest = node.flushPrev + if node.flushPrev != (common.Hash{}) { + c.db.dirties[node.flushPrev].flushNext = common.Hash{} + } + default: + c.db.dirties[node.flushPrev].flushNext = node.flushNext + c.db.dirties[node.flushNext].flushPrev = node.flushPrev + } + // Remove the node from the dirty cache + delete(c.db.dirties, hash) + c.db.dirtiesSize -= common.StorageSize(common.HashLength + len(node.node)) + if node.external != nil { + c.db.childrenSize -= common.StorageSize(len(node.external) * common.HashLength) + } + // Move the flushed node into the clean cache to prevent insta-reloads + if c.db.cleans != nil { + c.db.cleans.Set(hash[:], rlp) + memcacheCleanWriteMeter.Mark(int64(len(rlp))) + } + return nil +} + +func (c *cleaner) Delete(key []byte) error { + panic("not implemented") +} + +// Initialized returns an indicator if state data is already initialized +// in hash-based scheme by checking the presence of genesis state. +func (db *Database) Initialized(genesisRoot common.Hash) bool { + return rawdb.HasLegacyTrieNode(db.diskdb, genesisRoot) +} + +// Update inserts the dirty nodes in provided nodeset into database and link the +// account trie with multiple storage tries if necessary. +func (db *Database) Update(root common.Hash, parent common.Hash, 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 { + log.Error("parent state is not present") + } + } + db.lock.Lock() + defer db.lock.Unlock() + + // Insert dirty nodes into the database. In the same tree, it must be + // ensured that children are inserted first, then parent so that children + // can be linked with their parent correctly. + // + // Note, the storage tries must be flushed before the account trie to + // retain the invariant that children go into the dirty cache first. + var order []common.Hash + for owner := range nodes.Sets { + if owner == (common.Hash{}) { + continue + } + order = append(order, owner) + } + if _, ok := nodes.Sets[common.Hash{}]; ok { + order = append(order, common.Hash{}) + } + for _, owner := range order { + subset := nodes.Sets[owner] + subset.ForEachWithOrder(func(path string, n *trienode.Node) { + if n.IsDeleted() { + return // ignore deletion + } + db.insert(n.Hash, n.Blob) + }) + } + // Link up the account trie and storage trie if the node points + // to an account trie leaf. + if set, present := nodes.Sets[common.Hash{}]; present { + for _, n := range set.Leaves { + var account types.StateAccount + if err := rlp.DecodeBytes(n.Blob, &account); err != nil { + return err + } + if account.Root != types.EmptyRootHash { + db.reference(account.Root, n.Parent) + } + } + } + return nil +} + +// Size returns the current storage size of the memory cache in front of the +// persistent database layer. +// +// 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() + + // db.dirtiesSize only contains the useful data in the cache, but when reporting + // the total memory consumption, the maintenance metadata is also needed to be + // counted. + var metadataSize = common.StorageSize(len(db.dirties) * cachedNodeSize) + return 0, db.dirtiesSize + db.childrenSize + metadataSize +} + +// Close closes the trie database and releases all held resources. +func (db *Database) Close() error { + if db.cleans != nil { + db.cleans.Reset() + } + return nil +} + +// 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) (database.Reader, error) { + if _, err := db.node(root); err != nil { + return nil, fmt.Errorf("state %#x is not available, %v", root, err) + } + return &reader{db: db}, nil +} + +// reader is a state reader of Database which implements the Reader interface. +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. +func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { + blob, _ := reader.db.node(hash) + return blob, nil +} diff --git a/triedb/history.go b/triedb/history.go new file mode 100644 index 0000000..f663cdd --- /dev/null +++ b/triedb/history.go @@ -0,0 +1,72 @@ +// 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 triedb + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/triedb/pathdb" +) + +// AccountHistory inspects the account history within the specified range. +// +// Start: State ID of the first history object for the query. 0 implies the first +// available object is selected as the starting point. +// +// End: State ID of the last history for the query. 0 implies the last available +// object is selected as the starting point. Note end is included for query. +// +// This function is only supported by path mode database. +func (db *Database) AccountHistory(address common.Address, start, end uint64) (*pathdb.HistoryStats, error) { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return nil, errors.New("not supported") + } + return pdb.AccountHistory(address, start, end) +} + +// StorageHistory inspects the storage history within the specified range. +// +// Start: State ID of the first history object for the query. 0 implies the first +// available object is selected as the starting point. +// +// End: State ID of the last history for the query. 0 implies the last available +// object is selected as the starting point. Note end is included for query. +// +// Note, slot refers to the hash of the raw slot key. +// +// This function is only supported by path mode database. +func (db *Database) StorageHistory(address common.Address, slot common.Hash, start uint64, end uint64) (*pathdb.HistoryStats, error) { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return nil, errors.New("not supported") + } + return pdb.StorageHistory(address, slot, start, end) +} + +// HistoryRange returns the block numbers associated with earliest and latest +// state history in the local store. +// +// This function is only supported by path mode database. +func (db *Database) HistoryRange() (uint64, uint64, error) { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return 0, 0, errors.New("not supported") + } + return pdb.HistoryRange() +} diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go new file mode 100644 index 0000000..450c3a8 --- /dev/null +++ b/triedb/pathdb/database.go @@ -0,0 +1,526 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pathdb + +import ( + "errors" + "fmt" + "io" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +const ( + // 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 +) + +var ( + // maxDiffLayers is the maximum diff layers allowed in the layer tree. + maxDiffLayers = 128 +) + +// layer is the interface implemented by all state layers which includes some +// public methods and some additional methods for internal usage. +type layer interface { + // node retrieves the trie node with the node info. An error will be returned + // if the read operation exits abnormally. Specifically, if the layer is + // already stale. + // + // Note, no error will be returned if the requested node is not found in database. + node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error) + + // rootHash returns the root hash for which this layer was made. + rootHash() common.Hash + + // stateID returns the associated state id of layer. + stateID() uint64 + + // parentLayer returns the subsequent layer of it, or nil if the disk was reached. + parentLayer() layer + + // update creates a new layer on top of the existing layer diff tree with + // the provided dirty trie nodes along with the state change set. + // + // Note, the maps are retained by the method to avoid copying everything. + update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer + + // journal commits an entire diff hierarchy to disk into a single journal entry. + // This is meant to be used during shutdown to persist the layer without + // flattening everything down (bad for reorgs). + journal(w io.Writer) error +} + +// Config contains the settings for database. +type Config struct { + StateHistory uint64 // Number of recent blocks to maintain state history for + CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes + DirtyCacheSize int // Maximum memory allowance (in bytes) for caching dirty nodes + ReadOnly bool // Flag whether the database is opened in read only mode. +} + +// sanitize checks the provided user configurations and changes anything that's +// unreasonable or unworkable. +func (c *Config) sanitize() *Config { + conf := *c + if conf.DirtyCacheSize > maxBufferSize { + log.Warn("Sanitizing invalid node buffer size", "provided", common.StorageSize(conf.DirtyCacheSize), "updated", common.StorageSize(maxBufferSize)) + conf.DirtyCacheSize = maxBufferSize + } + return &conf +} + +// Defaults contains default settings for Ethereum mainnet. +var Defaults = &Config{ + StateHistory: params.FullImmutabilityThreshold, + CleanCacheSize: defaultCleanSize, + DirtyCacheSize: DefaultBufferSize, +} + +// ReadOnly is the config in order to open database in read only mode. +var ReadOnly = &Config{ReadOnly: true} + +// Database is a multiple-layered structure for maintaining in-memory trie nodes. +// It consists of one persistent base layer backed by a key-value store, on top +// of which arbitrarily many in-memory diff layers are stacked. The memory diffs +// can form a tree with branching, but the disk layer is singleton and common to +// all. If a reorg goes deeper than the disk layer, a batch of reverse diffs can +// be applied to rollback. The deepest reorg that can be handled depends on the +// amount of state histories tracked in the disk. +// +// At most one readable and writable database can be opened at the same time in +// the whole system which ensures that only one database writer can operate disk +// state. Unexpected open operations can cause the system to panic. +type Database struct { + // readOnly is the flag whether the mutation is allowed to be applied. + // It will be set automatically when the database is journaled during + // the shutdown to reject all following unexpected mutations. + readOnly bool // Flag if database is opened in read only mode + waitSync bool // Flag if database is deactivated due to initial state sync + isVerkle bool // Flag if database is used for verkle tree + bufferSize int // Memory allowance (in bytes) for caching dirty nodes + config *Config // Configuration for database + diskdb ethdb.Database // Persistent storage for matured trie nodes + tree *layerTree // The group for all known layers + freezer ethdb.ResettableAncientStore // Freezer for storing trie histories, nil possible in tests + lock sync.RWMutex // Lock to prevent mutations from happening at the same time +} + +// New attempts to load an already existing layer from a persistent key-value +// store (with a number of memory layers from a journal). If the journal is not +// matched with the base persistent layer, all the recorded diff layers are discarded. +func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { + if config == nil { + config = Defaults + } + config = config.sanitize() + + db := &Database{ + readOnly: config.ReadOnly, + isVerkle: isVerkle, + bufferSize: config.DirtyCacheSize, + config: config, + diskdb: diskdb, + } + // Construct the layer tree by resolving the in-disk singleton state + // and in-memory layer journal. + db.tree = newLayerTree(db.loadLayers()) + + // Repair the state history, which might not be aligned with the state + // in the key-value store due to an unclean shutdown. + if err := db.repairHistory(); err != nil { + log.Crit("Failed to repair pathdb", "err", err) + } + // 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 + } + } + return db +} + +// repairHistory truncates leftover state history objects, which may occur due +// to an unclean shutdown or other unexpected reasons. +func (db *Database) repairHistory() error { + // Open the freezer for state history. This mechanism ensures that + // only one database instance can be opened at a time to prevent + // accidental mutation. + ancient, err := db.diskdb.AncientDatadir() + if err != nil { + // TODO error out if ancient store is disabled. A tons of unit tests + // disable the ancient store thus the error here will immediately fail + // all of them. Fix the tests first. + return nil + } + freezer, err := rawdb.NewStateFreezer(ancient, db.readOnly) + if err != nil { + log.Crit("Failed to open state history freezer", "err", err) + } + db.freezer = freezer + + // Reset the entire state histories if the trie database is not initialized + // yet. This action is necessary because these state histories are not + // expected to exist without an initialized trie database. + id := db.tree.bottom().stateID() + if id == 0 { + 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") + } + return nil + } + // Truncate the extra state histories above in freezer in case it's not + // aligned with the disk layer. It might happen after a unclean shutdown. + pruned, err := truncateFromHead(db.diskdb, db.freezer, id) + if err != nil { + log.Crit("Failed to truncate extra state histories", "err", err) + } + if pruned != 0 { + log.Warn("Truncated extra state histories", "number", pruned) + } + return nil +} + +// Update adds a new layer into the tree, if that can be linked to an existing +// old parent. It is disallowed to insert a disk layer (the origin of all). Apart +// from that this function will flatten the extra diff layers at bottom into disk +// to only keep 128 diff layers in memory by default. +// +// The passed in maps(nodes, states) will be retained to avoid copying everything. +// Therefore, these maps must not be changed afterwards. +func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { + // Hold the lock to prevent concurrent mutations. + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the mutation is not allowed. + if err := db.modifyAllowed(); err != nil { + return err + } + if err := db.tree.add(root, parentRoot, block, nodes, states); err != nil { + return err + } + // Keep 128 diff layers in the memory, persistent layer is 129th. + // - head layer is paired with HEAD state + // - head-1 layer is paired with HEAD-1 state + // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state + // - head-128 layer(disk layer) is paired with HEAD-128 state + return db.tree.cap(root, maxDiffLayers) +} + +// Commit traverses downwards the layer tree from a specified layer with the +// provided state root and all the layers below are flattened downwards. It +// can be used alone and mostly for test purposes. +func (db *Database) Commit(root common.Hash, report bool) error { + // Hold the lock to prevent concurrent mutations. + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the mutation is not allowed. + if err := db.modifyAllowed(); err != nil { + return err + } + return db.tree.cap(root, 0) +} + +// Disable deactivates the database and invalidates all available state layers +// as stale to prevent access to the persistent state, which is in the syncing +// stage. +func (db *Database) Disable() error { + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the database is in read only mode. + if db.readOnly { + return errDatabaseReadOnly + } + // Prevent duplicated disable operation. + if db.waitSync { + log.Error("Reject duplicated disable operation") + return nil + } + db.waitSync = true + + // Mark the disk layer as stale to prevent access to persistent state. + db.tree.bottom().markStale() + + // Write the initial sync flag to persist it across restarts. + rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncRunning) + log.Info("Disabled trie database due to state sync") + return nil +} + +// Enable activates database and resets the state tree with the provided persistent +// state root once the state sync is finished. +func (db *Database) Enable(root common.Hash) error { + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the database is in read only mode. + if db.readOnly { + return errDatabaseReadOnly + } + // Ensure the provided state root matches the stored one. + root = types.TrieRootHash(root) + stored := types.EmptyRootHash + if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 { + stored = crypto.Keccak256Hash(blob) + } + if stored != root { + return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root) + } + // Drop the stale state journal in persistent database and + // reset the persistent state id back to zero. + batch := db.diskdb.NewBatch() + rawdb.DeleteTrieJournal(batch) + rawdb.WritePersistentStateID(batch, 0) + if err := batch.Write(); err != nil { + return err + } + // Clean up all state histories in freezer. Theoretically + // all root->id mappings should be removed as well. Since + // mappings can be huge and might take a while to clear + // them, just leave them in disk and wait for overwriting. + if db.freezer != nil { + if err := db.freezer.Reset(); err != nil { + return err + } + } + // Re-construct a new disk layer backed by persistent state + // with **empty clean cache and node buffer**. + db.tree.reset(newDiskLayer(root, 0, db, nil, newNodeBuffer(db.bufferSize, nil, 0))) + + // Re-enable the database as the final step. + db.waitSync = false + rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished) + log.Info("Rebuilt trie database", "root", root) + return nil +} + +// Recover rollbacks the database to a specified historical point. +// The state is supported as the rollback destination only if it's +// canonical state and the corresponding trie histories are existent. +func (db *Database) Recover(root common.Hash) error { + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if rollback operation is not supported. + if err := db.modifyAllowed(); err != nil { + return err + } + if db.freezer == nil { + return errors.New("state rollback is non-supported") + } + // Short circuit if the target state is not recoverable. + root = types.TrieRootHash(root) + if !db.Recoverable(root) { + return errStateUnrecoverable + } + // Apply the state histories upon the disk layer in order. + var ( + start = time.Now() + dl = db.tree.bottom() + ) + for dl.rootHash() != root { + h, err := readHistory(db.freezer, dl.stateID()) + if err != nil { + return err + } + dl, err = dl.revert(h) + if err != nil { + return err + } + // reset layer with newly created disk layer. It must be + // done after each revert operation, otherwise the new + // disk layer won't be accessible from outside. + db.tree.reset(dl) + } + rawdb.DeleteTrieJournal(db.diskdb) + _, err := truncateFromHead(db.diskdb, db.freezer, dl.stateID()) + if err != nil { + return err + } + log.Debug("Recovered state", "root", root, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// Recoverable returns the indicator if the specified state is recoverable. +func (db *Database) Recoverable(root common.Hash) bool { + // Ensure the requested state is a known state. + root = types.TrieRootHash(root) + id := rawdb.ReadStateID(db.diskdb, root) + if id == nil { + return false + } + // Recoverable state must below the disk layer. The recoverable + // state only refers the state that is currently not available, + // but can be restored by applying state history. + dl := db.tree.bottom() + if *id >= dl.stateID() { + return false + } + // This is a temporary workaround for the unavailability of the freezer in + // dev mode. As a consequence, the Pathdb loses the ability for deep reorg + // in certain cases. + // TODO(rjl493456442): Implement the in-memory ancient store. + if db.freezer == nil { + return false + } + // Ensure the requested state is a canonical state and all state + // histories in range [id+1, disklayer.ID] are present and complete. + return checkHistories(db.freezer, *id+1, dl.stateID()-*id, func(m *meta) error { + if m.parent != root { + return errors.New("unexpected state history") + } + root = m.root + return nil + }) == nil +} + +// Close closes the trie database and the held freezer. +func (db *Database) Close() error { + db.lock.Lock() + defer db.lock.Unlock() + + // Set the database to read-only mode to prevent all + // following mutations. + db.readOnly = true + + // Release the memory held by clean cache. + db.tree.bottom().resetCache() + + // Close the attached state history freezer. + if db.freezer == nil { + return nil + } + return db.freezer.Close() +} + +// Size returns the current storage size of the memory cache in front of the +// persistent database layer. +func (db *Database) Size() (diffs common.StorageSize, nodes common.StorageSize) { + db.tree.forEach(func(layer layer) { + if diff, ok := layer.(*diffLayer); ok { + diffs += common.StorageSize(diff.memory) + } + if disk, ok := layer.(*diskLayer); ok { + nodes += disk.size() + } + }) + return diffs, nodes +} + +// Initialized returns an indicator if the state data is already +// initialized in path-based scheme. +func (db *Database) Initialized(genesisRoot common.Hash) bool { + var inited bool + db.tree.forEach(func(layer layer) { + if layer.rootHash() != types.EmptyRootHash { + inited = true + } + }) + if !inited { + inited = rawdb.ReadSnapSyncStatusFlag(db.diskdb) != rawdb.StateSyncUnknown + } + return inited +} + +// SetBufferSize sets the node buffer size to the provided value(in bytes). +func (db *Database) SetBufferSize(size int) error { + db.lock.Lock() + defer db.lock.Unlock() + + if size > maxBufferSize { + log.Info("Capped node buffer size", "provided", common.StorageSize(size), "adjusted", common.StorageSize(maxBufferSize)) + size = maxBufferSize + } + db.bufferSize = size + return db.tree.bottom().setBufferSize(db.bufferSize) +} + +// 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 +} + +// AccountHistory inspects the account history within the specified range. +// +// Start: State ID of the first history object for the query. 0 implies the first +// available object is selected as the starting point. +// +// End: State ID of the last history for the query. 0 implies the last available +// object is selected as the ending point. Note end is included in the query. +func (db *Database) AccountHistory(address common.Address, start, end uint64) (*HistoryStats, error) { + return accountHistory(db.freezer, address, start, end) +} + +// StorageHistory inspects the storage history within the specified range. +// +// Start: State ID of the first history object for the query. 0 implies the first +// available object is selected as the starting point. +// +// End: State ID of the last history for the query. 0 implies the last available +// object is selected as the ending point. Note end is included in the query. +// +// Note, slot refers to the hash of the raw slot key. +func (db *Database) StorageHistory(address common.Address, slot common.Hash, start uint64, end uint64) (*HistoryStats, error) { + return storageHistory(db.freezer, address, slot, start, end) +} + +// HistoryRange returns the block numbers associated with earliest and latest +// state history in the local store. +func (db *Database) HistoryRange() (uint64, uint64, error) { + return historyRange(db.freezer) +} diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go new file mode 100644 index 0000000..f667944 --- /dev/null +++ b/triedb/pathdb/database_test.go @@ -0,0 +1,671 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pathdb + +import ( + "bytes" + "errors" + "fmt" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/testrand" + "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" + "github.com/holiman/uint256" +) + +func updateTrie(db *Database, stateRoot common.Hash, addrHash common.Hash, root common.Hash, dirties map[common.Hash][]byte) (common.Hash, *trienode.NodeSet) { + var id *trie.ID + if addrHash == (common.Hash{}) { + id = trie.StateTrieID(stateRoot) + } else { + id = trie.StorageTrieID(stateRoot, addrHash, root) + } + tr, err := trie.New(id, db) + if err != nil { + panic(fmt.Errorf("failed to load trie, err: %w", err)) + } + for key, val := range dirties { + if len(val) == 0 { + tr.Delete(key.Bytes()) + } else { + tr.Update(key.Bytes(), val) + } + } + return tr.Commit(false) +} + +func generateAccount(storageRoot common.Hash) types.StateAccount { + return types.StateAccount{ + Nonce: uint64(rand.Intn(100)), + Balance: uint256.NewInt(rand.Uint64()), + CodeHash: testrand.Bytes(32), + Root: storageRoot, + } +} + +const ( + createAccountOp int = iota + modifyAccountOp + deleteAccountOp + opLen +) + +type genctx struct { + stateRoot common.Hash + accounts map[common.Hash][]byte + storages map[common.Hash]map[common.Hash][]byte + accountOrigin map[common.Address][]byte + storageOrigin map[common.Address]map[common.Hash][]byte + nodes *trienode.MergedNodeSet +} + +func newCtx(stateRoot common.Hash) *genctx { + return &genctx{ + stateRoot: stateRoot, + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountOrigin: make(map[common.Address][]byte), + storageOrigin: make(map[common.Address]map[common.Hash][]byte), + nodes: trienode.NewMergedNodeSet(), + } +} + +type tester struct { + db *Database + roots []common.Hash + preimages map[common.Hash]common.Address + accounts map[common.Hash][]byte + storages map[common.Hash]map[common.Hash][]byte + + // state snapshots + snapAccounts map[common.Hash]map[common.Hash][]byte + snapStorages map[common.Hash]map[common.Hash]map[common.Hash][]byte +} + +func newTester(t *testing.T, historyLimit uint64) *tester { + var ( + disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + db = New(disk, &Config{ + StateHistory: historyLimit, + CleanCacheSize: 16 * 1024, + DirtyCacheSize: 16 * 1024, + }, false) + obj = &tester{ + db: db, + preimages: make(map[common.Hash]common.Address), + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + snapAccounts: make(map[common.Hash]map[common.Hash][]byte), + snapStorages: make(map[common.Hash]map[common.Hash]map[common.Hash][]byte), + } + ) + for i := 0; i < 12; i++ { + var parent = types.EmptyRootHash + if len(obj.roots) != 0 { + parent = obj.roots[len(obj.roots)-1] + } + root, nodes, states := obj.generate(parent) + if err := db.Update(root, parent, uint64(i), nodes, states); err != nil { + panic(fmt.Errorf("failed to update state changes, err: %w", err)) + } + obj.roots = append(obj.roots, root) + } + return obj +} + +func (t *tester) release() { + t.db.Close() + t.db.diskdb.Close() +} + +func (t *tester) randAccount() (common.Address, []byte) { + for addrHash, account := range t.accounts { + return t.preimages[addrHash], account + } + return common.Address{}, nil +} + +func (t *tester) generateStorage(ctx *genctx, addr common.Address) common.Hash { + var ( + addrHash = crypto.Keccak256Hash(addr.Bytes()) + storage = make(map[common.Hash][]byte) + origin = make(map[common.Hash][]byte) + ) + for i := 0; i < 10; i++ { + v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testrand.Bytes(32))) + hash := testrand.Hash() + + storage[hash] = v + origin[hash] = nil + } + root, set := updateTrie(t.db, ctx.stateRoot, addrHash, types.EmptyRootHash, storage) + + ctx.storages[addrHash] = storage + ctx.storageOrigin[addr] = origin + ctx.nodes.Merge(set) + return root +} + +func (t *tester) mutateStorage(ctx *genctx, addr common.Address, root common.Hash) common.Hash { + var ( + addrHash = crypto.Keccak256Hash(addr.Bytes()) + storage = make(map[common.Hash][]byte) + origin = make(map[common.Hash][]byte) + ) + for hash, val := range t.storages[addrHash] { + origin[hash] = val + storage[hash] = nil + + if len(origin) == 3 { + break + } + } + for i := 0; i < 3; i++ { + v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testrand.Bytes(32))) + hash := testrand.Hash() + + storage[hash] = v + origin[hash] = nil + } + root, set := updateTrie(t.db, ctx.stateRoot, crypto.Keccak256Hash(addr.Bytes()), root, storage) + + ctx.storages[addrHash] = storage + ctx.storageOrigin[addr] = origin + ctx.nodes.Merge(set) + return root +} + +func (t *tester) clearStorage(ctx *genctx, addr common.Address, root common.Hash) common.Hash { + var ( + addrHash = crypto.Keccak256Hash(addr.Bytes()) + storage = make(map[common.Hash][]byte) + origin = make(map[common.Hash][]byte) + ) + for hash, val := range t.storages[addrHash] { + origin[hash] = val + storage[hash] = nil + } + root, set := updateTrie(t.db, ctx.stateRoot, addrHash, root, storage) + if root != types.EmptyRootHash { + panic("failed to clear storage trie") + } + ctx.storages[addrHash] = storage + ctx.storageOrigin[addr] = origin + ctx.nodes.Merge(set) + return root +} + +func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNodeSet, *triestate.Set) { + var ( + ctx = newCtx(parent) + dirties = make(map[common.Hash]struct{}) + ) + for i := 0; i < 20; i++ { + switch rand.Intn(opLen) { + case createAccountOp: + // account creation + addr := testrand.Address() + addrHash := crypto.Keccak256Hash(addr.Bytes()) + if _, ok := t.accounts[addrHash]; ok { + continue + } + if _, ok := dirties[addrHash]; ok { + continue + } + dirties[addrHash] = struct{}{} + + root := t.generateStorage(ctx, addr) + ctx.accounts[addrHash] = types.SlimAccountRLP(generateAccount(root)) + ctx.accountOrigin[addr] = nil + t.preimages[addrHash] = addr + + case modifyAccountOp: + // account mutation + addr, account := t.randAccount() + if addr == (common.Address{}) { + continue + } + addrHash := crypto.Keccak256Hash(addr.Bytes()) + if _, ok := dirties[addrHash]; ok { + continue + } + dirties[addrHash] = struct{}{} + + acct, _ := types.FullAccount(account) + stRoot := t.mutateStorage(ctx, addr, acct.Root) + newAccount := types.SlimAccountRLP(generateAccount(stRoot)) + + ctx.accounts[addrHash] = newAccount + ctx.accountOrigin[addr] = account + + case deleteAccountOp: + // account deletion + addr, account := t.randAccount() + if addr == (common.Address{}) { + continue + } + addrHash := crypto.Keccak256Hash(addr.Bytes()) + if _, ok := dirties[addrHash]; ok { + continue + } + dirties[addrHash] = struct{}{} + + acct, _ := types.FullAccount(account) + if acct.Root != types.EmptyRootHash { + t.clearStorage(ctx, addr, acct.Root) + } + ctx.accounts[addrHash] = nil + ctx.accountOrigin[addr] = account + } + } + root, set := updateTrie(t.db, parent, common.Hash{}, parent, ctx.accounts) + ctx.nodes.Merge(set) + + // Save state snapshot before commit + t.snapAccounts[parent] = copyAccounts(t.accounts) + t.snapStorages[parent] = copyStorages(t.storages) + + // Commit all changes to live state set + for addrHash, account := range ctx.accounts { + if len(account) == 0 { + delete(t.accounts, addrHash) + } else { + t.accounts[addrHash] = account + } + } + for addrHash, slots := range ctx.storages { + if _, ok := t.storages[addrHash]; !ok { + t.storages[addrHash] = make(map[common.Hash][]byte) + } + for sHash, slot := range slots { + if len(slot) == 0 { + delete(t.storages[addrHash], sHash) + } else { + t.storages[addrHash][sHash] = slot + } + } + if len(t.storages[addrHash]) == 0 { + delete(t.storages, addrHash) + } + } + return root, ctx.nodes, triestate.New(ctx.accountOrigin, ctx.storageOrigin) +} + +// lastHash returns the latest root hash, or empty if nothing is cached. +func (t *tester) lastHash() common.Hash { + if len(t.roots) == 0 { + return common.Hash{} + } + return t.roots[len(t.roots)-1] +} + +func (t *tester) verifyState(root common.Hash) error { + tr, err := trie.New(trie.StateTrieID(root), t.db) + if err != nil { + return err + } + for addrHash, account := range t.snapAccounts[root] { + blob, err := tr.Get(addrHash.Bytes()) + if err != nil || !bytes.Equal(blob, account) { + return fmt.Errorf("account is mismatched: %w", err) + } + } + for addrHash, slots := range t.snapStorages[root] { + blob := t.snapAccounts[root][addrHash] + if len(blob) == 0 { + return fmt.Errorf("account %x is missing", addrHash) + } + account := new(types.StateAccount) + if err := rlp.DecodeBytes(blob, account); err != nil { + return err + } + storageIt, err := trie.New(trie.StorageTrieID(root, addrHash, account.Root), t.db) + if err != nil { + return err + } + for hash, slot := range slots { + blob, err := storageIt.Get(hash.Bytes()) + if err != nil || !bytes.Equal(blob, slot) { + return fmt.Errorf("slot is mismatched: %w", err) + } + } + } + return nil +} + +func (t *tester) verifyHistory() error { + bottom := t.bottomIndex() + for i, root := range t.roots { + // The state history related to the state above disk layer should not exist. + if i > bottom { + _, err := readHistory(t.db.freezer, uint64(i+1)) + if err == nil { + return errors.New("unexpected state history") + } + continue + } + // The state history related to the state below or equal to the disk layer + // should exist. + obj, err := readHistory(t.db.freezer, uint64(i+1)) + if err != nil { + return err + } + parent := types.EmptyRootHash + if i != 0 { + parent = t.roots[i-1] + } + if obj.meta.parent != parent { + return fmt.Errorf("unexpected parent, want: %x, got: %x", parent, obj.meta.parent) + } + if obj.meta.root != root { + return fmt.Errorf("unexpected root, want: %x, got: %x", root, obj.meta.root) + } + } + return nil +} + +// bottomIndex returns the index of current disk layer. +func (t *tester) bottomIndex() int { + bottom := t.db.tree.bottom() + for i := 0; i < len(t.roots); i++ { + if t.roots[i] == bottom.rootHash() { + return i + } + } + return -1 +} + +func TestDatabaseRollback(t *testing.T) { + // Redefine the diff layer depth allowance for faster testing. + maxDiffLayers = 4 + defer func() { + maxDiffLayers = 128 + }() + + // Verify state histories + tester := newTester(t, 0) + defer tester.release() + + if err := tester.verifyHistory(); err != nil { + t.Fatalf("Invalid state history, err: %v", err) + } + // Revert database from top to bottom + for i := tester.bottomIndex(); i >= 0; i-- { + parent := types.EmptyRootHash + if i > 0 { + parent = tester.roots[i-1] + } + if err := tester.db.Recover(parent); err != nil { + t.Fatalf("Failed to revert db, err: %v", err) + } + if i > 0 { + if err := tester.verifyState(parent); err != nil { + t.Fatalf("Failed to verify state, err: %v", err) + } + } + } + if tester.db.tree.len() != 1 { + t.Fatal("Only disk layer is expected") + } +} + +func TestDatabaseRecoverable(t *testing.T) { + // Redefine the diff layer depth allowance for faster testing. + maxDiffLayers = 4 + defer func() { + maxDiffLayers = 128 + }() + + var ( + tester = newTester(t, 0) + index = tester.bottomIndex() + ) + defer tester.release() + + var cases = []struct { + root common.Hash + expect bool + }{ + // Unknown state should be unrecoverable + {common.Hash{0x1}, false}, + + // Initial state should be recoverable + {types.EmptyRootHash, true}, + + // Initial state should be recoverable + {common.Hash{}, true}, + + // Layers below current disk layer are recoverable + {tester.roots[index-1], true}, + + // Disklayer itself is not recoverable, since it's + // available for accessing. + {tester.roots[index], false}, + + // Layers above current disk layer are not recoverable + // since they are available for accessing. + {tester.roots[index+1], false}, + } + for i, c := range cases { + result := tester.db.Recoverable(c.root) + if result != c.expect { + t.Fatalf("case: %d, unexpected result, want %t, got %t", i, c.expect, result) + } + } +} + +func TestDisable(t *testing.T) { + // Redefine the diff layer depth allowance for faster testing. + maxDiffLayers = 4 + defer func() { + maxDiffLayers = 128 + }() + + tester := newTester(t, 0) + defer tester.release() + + stored := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)) + if err := tester.db.Disable(); err != nil { + t.Fatalf("Failed to deactivate database: %v", err) + } + if err := tester.db.Enable(types.EmptyRootHash); err == nil { + t.Fatal("Invalid activation should be rejected") + } + if err := tester.db.Enable(stored); err != nil { + t.Fatalf("Failed to activate database: %v", err) + } + + // Ensure journal is deleted from disk + if blob := rawdb.ReadTrieJournal(tester.db.diskdb); len(blob) != 0 { + t.Fatal("Failed to clean journal") + } + // Ensure all trie histories are removed + n, err := tester.db.freezer.Ancients() + if err != nil { + t.Fatal("Failed to clean state history") + } + if n != 0 { + t.Fatal("Failed to clean state history") + } + // Verify layer tree structure, single disk layer is expected + if tester.db.tree.len() != 1 { + t.Fatalf("Extra layer kept %d", tester.db.tree.len()) + } + if tester.db.tree.bottom().rootHash() != stored { + t.Fatalf("Root hash is not matched exp %x got %x", stored, tester.db.tree.bottom().rootHash()) + } +} + +func TestCommit(t *testing.T) { + // Redefine the diff layer depth allowance for faster testing. + maxDiffLayers = 4 + defer func() { + maxDiffLayers = 128 + }() + + tester := newTester(t, 0) + defer tester.release() + + if err := tester.db.Commit(tester.lastHash(), false); err != nil { + t.Fatalf("Failed to cap database, err: %v", err) + } + // Verify layer tree structure, single disk layer is expected + if tester.db.tree.len() != 1 { + t.Fatal("Layer tree structure is invalid") + } + if tester.db.tree.bottom().rootHash() != tester.lastHash() { + t.Fatal("Layer tree structure is invalid") + } + // Verify states + if err := tester.verifyState(tester.lastHash()); err != nil { + t.Fatalf("State is invalid, err: %v", err) + } + // Verify state histories + if err := tester.verifyHistory(); err != nil { + t.Fatalf("State history is invalid, err: %v", err) + } +} + +func TestJournal(t *testing.T) { + // Redefine the diff layer depth allowance for faster testing. + maxDiffLayers = 4 + defer func() { + maxDiffLayers = 128 + }() + + tester := newTester(t, 0) + defer tester.release() + + if err := tester.db.Journal(tester.lastHash()); err != nil { + t.Errorf("Failed to journal, err: %v", err) + } + tester.db.Close() + tester.db = New(tester.db.diskdb, nil, false) + + // Verify states including disk layer and all diff on top. + for i := 0; i < len(tester.roots); i++ { + if i >= tester.bottomIndex() { + if err := tester.verifyState(tester.roots[i]); err != nil { + t.Fatalf("Invalid state, err: %v", err) + } + continue + } + if err := tester.verifyState(tester.roots[i]); err == nil { + t.Fatal("Unexpected state") + } + } +} + +func TestCorruptedJournal(t *testing.T) { + // Redefine the diff layer depth allowance for faster testing. + maxDiffLayers = 4 + defer func() { + maxDiffLayers = 128 + }() + + tester := newTester(t, 0) + defer tester.release() + + if err := tester.db.Journal(tester.lastHash()); err != nil { + t.Errorf("Failed to journal, err: %v", err) + } + tester.db.Close() + root := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)) + + // Mutate the journal in disk, it should be regarded as invalid + blob := rawdb.ReadTrieJournal(tester.db.diskdb) + blob[0] = 0xa + rawdb.WriteTrieJournal(tester.db.diskdb, blob) + + // Verify states, all not-yet-written states should be discarded + tester.db = New(tester.db.diskdb, nil, false) + for i := 0; i < len(tester.roots); i++ { + if tester.roots[i] == root { + if err := tester.verifyState(root); err != nil { + t.Fatalf("Disk state is corrupted, err: %v", err) + } + continue + } + if err := tester.verifyState(tester.roots[i]); err == nil { + t.Fatal("Unexpected state") + } + } +} + +// 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) { + // Redefine the diff layer depth allowance for faster testing. + maxDiffLayers = 4 + defer func() { + maxDiffLayers = 128 + }() + + tester := newTester(t, 10) + defer tester.release() + + tester.db.Close() + tester.db = New(tester.db.diskdb, &Config{StateHistory: 10}, false) + + 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)) + for key, val := range set { + copied[key] = common.CopyBytes(val) + } + return copied +} + +// copyStorages returns a deep-copied storage set of the provided one. +func copyStorages(set map[common.Hash]map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { + copied := make(map[common.Hash]map[common.Hash][]byte, len(set)) + for addrHash, subset := range set { + copied[addrHash] = make(map[common.Hash][]byte, len(subset)) + for key, val := range subset { + copied[addrHash][key] = common.CopyBytes(val) + } + } + return copied +} diff --git a/triedb/pathdb/difflayer.go b/triedb/pathdb/difflayer.go new file mode 100644 index 0000000..6b87883 --- /dev/null +++ b/triedb/pathdb/difflayer.go @@ -0,0 +1,156 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pathdb + +import ( + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +// diffLayer represents a collection of modifications made to the in-memory tries +// along with associated state changes after running a block on top. +// +// The goal of a diff layer is to act as a journal, tracking recent modifications +// made to the state, that have not yet graduated into a semi-immutable state. +type diffLayer struct { + // Immutables + root common.Hash // Root hash to which this layer diff belongs to + id uint64 // Corresponding state id + block uint64 // Associated block number + nodes map[common.Hash]map[string]*trienode.Node // Cached trie nodes indexed by owner and path + states *triestate.Set // Associated state change set for building history + memory uint64 // Approximate guess as to how much memory we use + + parent layer // Parent layer modified by this one, never nil, **can be changed** + lock sync.RWMutex // Lock used to protect parent +} + +// newDiffLayer creates a new diff layer on top of an existing layer. +func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { + var ( + size int64 + count int + ) + dl := &diffLayer{ + root: root, + id: id, + block: block, + nodes: nodes, + states: states, + parent: parent, + } + for _, subset := range nodes { + for path, n := range subset { + dl.memory += uint64(n.Size() + len(path)) + size += int64(len(n.Blob) + len(path)) + } + count += len(subset) + } + if states != nil { + dl.memory += uint64(states.Size()) + } + dirtyWriteMeter.Mark(size) + diffLayerNodesMeter.Mark(int64(count)) + diffLayerBytesMeter.Mark(int64(dl.memory)) + log.Debug("Created new diff layer", "id", id, "block", block, "nodes", count, "size", common.StorageSize(dl.memory)) + return dl +} + +// rootHash implements the layer interface, returning the root hash of +// corresponding state. +func (dl *diffLayer) rootHash() common.Hash { + return dl.root +} + +// stateID implements the layer interface, returning the state id of the layer. +func (dl *diffLayer) stateID() uint64 { + return dl.id +} + +// parentLayer implements the layer interface, returning the subsequent +// layer of the diff layer. +func (dl *diffLayer) parentLayer() layer { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.parent +} + +// node implements the layer interface, retrieving the trie node blob with the +// provided node information. No error will be returned if the node is not found. +func (dl *diffLayer) node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error) { + // Hold the lock, ensure the parent won't be changed during the + // state accessing. + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the trie node is known locally, return it + subset, ok := dl.nodes[owner] + if ok { + n, ok := subset[string(path)] + if ok { + dirtyHitMeter.Mark(1) + dirtyNodeHitDepthHist.Update(int64(depth)) + dirtyReadMeter.Mark(int64(len(n.Blob))) + return n.Blob, n.Hash, &nodeLoc{loc: locDiffLayer, depth: depth}, nil + } + } + // Trie node unknown to this layer, resolve from parent + return dl.parent.node(owner, path, depth+1) +} + +// update implements the layer interface, creating a new layer on top of the +// existing layer tree with the specified data items. +func (dl *diffLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { + return newDiffLayer(dl, root, id, block, nodes, states) +} + +// persist flushes the diff layer and all its parent layers to disk layer. +func (dl *diffLayer) persist(force bool) (layer, error) { + if parent, ok := dl.parentLayer().(*diffLayer); ok { + // Hold the lock to prevent any read operation until the new + // parent is linked correctly. + dl.lock.Lock() + + // The merging of diff layers starts at the bottom-most layer, + // therefore we recurse down here, flattening on the way up + // (diffToDisk). + result, err := parent.persist(force) + if err != nil { + dl.lock.Unlock() + return nil, err + } + dl.parent = result + dl.lock.Unlock() + } + return diffToDisk(dl, force) +} + +// diffToDisk merges a bottom-most diff into the persistent disk layer underneath +// it. The method will panic if called onto a non-bottom-most diff layer. +func diffToDisk(layer *diffLayer, force bool) (layer, error) { + disk, ok := layer.parentLayer().(*diskLayer) + if !ok { + panic(fmt.Sprintf("unknown layer type: %T", layer.parentLayer())) + } + return disk.commit(layer, force) +} diff --git a/triedb/pathdb/difflayer_test.go b/triedb/pathdb/difflayer_test.go new file mode 100644 index 0000000..1e93a3f --- /dev/null +++ b/triedb/pathdb/difflayer_test.go @@ -0,0 +1,172 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pathdb + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/testrand" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +func emptyLayer() *diskLayer { + return &diskLayer{ + db: New(rawdb.NewMemoryDatabase(), nil, false), + buffer: newNodeBuffer(DefaultBufferSize, nil, 0), + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkSearch128Layers +// BenchmarkSearch128Layers-8 243826 4755 ns/op +func BenchmarkSearch128Layers(b *testing.B) { benchmarkSearch(b, 0, 128) } + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkSearch512Layers +// BenchmarkSearch512Layers-8 49686 24256 ns/op +func BenchmarkSearch512Layers(b *testing.B) { benchmarkSearch(b, 0, 512) } + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkSearch1Layer +// BenchmarkSearch1Layer-8 14062725 88.40 ns/op +func BenchmarkSearch1Layer(b *testing.B) { benchmarkSearch(b, 127, 128) } + +func benchmarkSearch(b *testing.B, depth int, total int) { + var ( + npath []byte + nblob []byte + ) + // First, we set up 128 diff layers, with 3K items each + fill := func(parent layer, index int) *diffLayer { + nodes := make(map[common.Hash]map[string]*trienode.Node) + nodes[common.Hash{}] = make(map[string]*trienode.Node) + for i := 0; i < 3000; i++ { + var ( + path = testrand.Bytes(32) + blob = testrand.Bytes(100) + node = trienode.New(crypto.Keccak256Hash(blob), blob) + ) + nodes[common.Hash{}][string(path)] = node + if npath == nil && depth == index { + npath = common.CopyBytes(path) + nblob = common.CopyBytes(blob) + } + } + return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) + } + var layer layer + layer = emptyLayer() + for i := 0; i < total; i++ { + layer = fill(layer, i) + } + b.ResetTimer() + + var ( + have []byte + err error + ) + for i := 0; i < b.N; i++ { + have, _, _, err = layer.node(common.Hash{}, npath, 0) + if err != nil { + b.Fatal(err) + } + } + if !bytes.Equal(have, nblob) { + b.Fatalf("have %x want %x", have, nblob) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkPersist +// BenchmarkPersist-8 10 111252975 ns/op +func BenchmarkPersist(b *testing.B) { + // First, we set up 128 diff layers, with 3K items each + fill := func(parent layer) *diffLayer { + nodes := make(map[common.Hash]map[string]*trienode.Node) + nodes[common.Hash{}] = make(map[string]*trienode.Node) + for i := 0; i < 3000; i++ { + var ( + path = testrand.Bytes(32) + blob = testrand.Bytes(100) + node = trienode.New(crypto.Keccak256Hash(blob), blob) + ) + nodes[common.Hash{}][string(path)] = node + } + return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) + } + for i := 0; i < b.N; i++ { + b.StopTimer() + var layer layer + layer = emptyLayer() + for i := 1; i < 128; i++ { + layer = fill(layer) + } + b.StartTimer() + + dl, ok := layer.(*diffLayer) + if !ok { + break + } + dl.persist(false) + } +} + +// BenchmarkJournal benchmarks the performance for journaling the layers. +// +// BenchmarkJournal +// BenchmarkJournal-8 10 110969279 ns/op +func BenchmarkJournal(b *testing.B) { + b.SkipNow() + + // First, we set up 128 diff layers, with 3K items each + fill := func(parent layer) *diffLayer { + nodes := make(map[common.Hash]map[string]*trienode.Node) + nodes[common.Hash{}] = make(map[string]*trienode.Node) + for i := 0; i < 3000; i++ { + var ( + path = testrand.Bytes(32) + blob = testrand.Bytes(100) + node = trienode.New(crypto.Keccak256Hash(blob), blob) + ) + nodes[common.Hash{}][string(path)] = node + } + // TODO(rjl493456442) a non-nil state set is expected. + return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) + } + var layer layer + layer = emptyLayer() + for i := 0; i < 128; i++ { + layer = fill(layer) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + layer.journal(new(bytes.Buffer)) + } +} diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go new file mode 100644 index 0000000..e538a79 --- /dev/null +++ b/triedb/pathdb/disklayer.go @@ -0,0 +1,316 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pathdb + +import ( + "fmt" + "sync" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +// diskLayer is a low level persistent layer built on top of a key-value store. +type diskLayer struct { + root common.Hash // Immutable, root hash to which this layer was made for + id uint64 // Immutable, corresponding state id + db *Database // Path-based trie database + cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs + buffer *nodebuffer // Node buffer to aggregate writes + stale bool // Signals that the layer became stale (state progressed) + lock sync.RWMutex // Lock used to protect stale flag +} + +// newDiskLayer creates a new disk layer based on the passing arguments. +func newDiskLayer(root common.Hash, id uint64, db *Database, cleans *fastcache.Cache, buffer *nodebuffer) *diskLayer { + // Initialize a clean cache if the memory allowance is not zero + // or reuse the provided cache if it is not nil (inherited from + // the original disk layer). + if cleans == nil && db.config.CleanCacheSize != 0 { + cleans = fastcache.New(db.config.CleanCacheSize) + } + return &diskLayer{ + root: root, + id: id, + db: db, + cleans: cleans, + buffer: buffer, + } +} + +// rootHash implements the layer interface, returning root hash of corresponding state. +func (dl *diskLayer) rootHash() common.Hash { + return dl.root +} + +// stateID implements the layer interface, returning the state id of disk layer. +func (dl *diskLayer) stateID() uint64 { + return dl.id +} + +// parentLayer implements the layer interface, returning nil as there's no layer +// below the disk. +func (dl *diskLayer) parentLayer() layer { + return nil +} + +// isStale return whether this layer has become stale (was flattened across) or if +// it's still live. +func (dl *diskLayer) isStale() bool { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.stale +} + +// markStale sets the stale flag as true. +func (dl *diskLayer) markStale() { + dl.lock.Lock() + defer dl.lock.Unlock() + + if dl.stale { + panic("triedb disk layer is stale") // we've committed into the same base from two children, boom + } + dl.stale = true +} + +// node implements the layer interface, retrieving the trie node with the +// provided node info. No error will be returned if the node is not found. +func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, common.Hash, *nodeLoc, error) { + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return nil, common.Hash{}, nil, errSnapshotStale + } + // Try to retrieve the trie node from the not-yet-written + // node buffer first. Note the buffer is lock free since + // it's impossible to mutate the buffer before tagging the + // layer as stale. + n, found := dl.buffer.node(owner, path) + if found { + dirtyHitMeter.Mark(1) + dirtyReadMeter.Mark(int64(len(n.Blob))) + dirtyNodeHitDepthHist.Update(int64(depth)) + return n.Blob, n.Hash, &nodeLoc{loc: locDirtyCache, depth: depth}, nil + } + dirtyMissMeter.Mark(1) + + // Try to retrieve the trie node from the clean memory cache + h := newHasher() + defer h.release() + + key := cacheKey(owner, path) + if dl.cleans != nil { + if blob := dl.cleans.Get(nil, key); len(blob) > 0 { + cleanHitMeter.Mark(1) + cleanReadMeter.Mark(int64(len(blob))) + return blob, h.hash(blob), &nodeLoc{loc: locCleanCache, depth: depth}, nil + } + cleanMissMeter.Mark(1) + } + // Try to retrieve the trie node from the disk. + var blob []byte + if owner == (common.Hash{}) { + blob = rawdb.ReadAccountTrieNode(dl.db.diskdb, path) + } else { + blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path) + } + if dl.cleans != nil && len(blob) > 0 { + dl.cleans.Set(key, blob) + cleanWriteMeter.Mark(int64(len(blob))) + } + + return blob, h.hash(blob), &nodeLoc{loc: locDiskLayer, depth: depth}, nil +} + +// update implements the layer interface, returning a new diff layer on top +// with the given state set. +func (dl *diskLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { + return newDiffLayer(dl, root, id, block, nodes, states) +} + +// commit merges the given bottom-most diff layer into the node buffer +// and returns a newly constructed disk layer. Note the current disk +// layer must be tagged as stale first to prevent re-access. +func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) { + dl.lock.Lock() + defer dl.lock.Unlock() + + // Construct and store the state history first. If crash happens after storing + // the state history but without flushing the corresponding states(journal), + // the stored state history will be truncated from head in the next restart. + var ( + overflow bool + oldest uint64 + ) + if dl.db.freezer != nil { + 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. + 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. + ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.cleans, dl.buffer.commit(bottom.nodes)) + + // 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 +} + +// revert applies the given state history and return a reverted disk layer. +func (dl *diskLayer) revert(h *history) (*diskLayer, error) { + if h.meta.root != dl.rootHash() { + return nil, errUnexpectedHistory + } + if dl.id == 0 { + return nil, fmt.Errorf("%w: zero state id", errStateUnrecoverable) + } + // Apply the reverse state changes upon the current state. This must + // be done before holding the lock in order to access state in "this" + // layer. + nodes, err := apply(dl.db, h.meta.parent, h.meta.root, h.accounts, h.storages) + if err != nil { + return nil, err + } + // Mark the diskLayer as stale before applying any mutations on top. + dl.lock.Lock() + defer dl.lock.Unlock() + + dl.stale = true + + // State change may be applied to node buffer, or the persistent + // state, depends on if node buffer is empty or not. If the node + // buffer is not empty, it means that the state transition that + // needs to be reverted is not yet flushed and cached in node + // buffer, otherwise, manipulate persistent state directly. + if !dl.buffer.empty() { + err := dl.buffer.revert(dl.db.diskdb, nodes) + if err != nil { + return nil, err + } + } else { + batch := dl.db.diskdb.NewBatch() + writeNodes(batch, nodes, dl.cleans) + rawdb.WritePersistentStateID(batch, dl.id-1) + if err := batch.Write(); err != nil { + log.Crit("Failed to write states", "err", err) + } + } + return newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.cleans, dl.buffer), nil +} + +// setBufferSize sets the node buffer size to the provided value. +func (dl *diskLayer) setBufferSize(size int) error { + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return errSnapshotStale + } + return dl.buffer.setSize(size, dl.db.diskdb, dl.cleans, dl.id) +} + +// size returns the approximate size of cached nodes in the disk layer. +func (dl *diskLayer) size() common.StorageSize { + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return 0 + } + return common.StorageSize(dl.buffer.size) +} + +// resetCache releases the memory held by clean cache to prevent memory leak. +func (dl *diskLayer) resetCache() { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // Stale disk layer loses the ownership of clean cache. + if dl.stale { + return + } + if dl.cleans != nil { + dl.cleans.Reset() + } +} + +// hasher is used to compute the sha256 hash of the provided data. +type hasher struct{ sha crypto.KeccakState } + +var hasherPool = sync.Pool{ + New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} }, +} + +func newHasher() *hasher { + return hasherPool.Get().(*hasher) +} + +func (h *hasher) hash(data []byte) common.Hash { + return crypto.HashData(h.sha, data) +} + +func (h *hasher) release() { + hasherPool.Put(h) +} diff --git a/triedb/pathdb/errors.go b/triedb/pathdb/errors.go new file mode 100644 index 0000000..498bc9e --- /dev/null +++ b/triedb/pathdb/errors.go @@ -0,0 +1,42 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package pathdb + +import "errors" + +var ( + // errDatabaseReadOnly is returned if the database is opened in read only mode + // to prevent any mutation. + errDatabaseReadOnly = errors.New("read only") + + // errDatabaseWaitSync is returned if the initial state sync is not completed + // yet and database is disabled to prevent accessing state. + errDatabaseWaitSync = errors.New("waiting for sync") + + // errSnapshotStale is returned from data accessors if the underlying layer + // had been invalidated due to the chain progressing forward far enough + // to not maintain the layer's original state. + errSnapshotStale = errors.New("layer stale") + + // errUnexpectedHistory is returned if an unmatched state history is applied + // to the database for state rollback. + errUnexpectedHistory = errors.New("unexpected state history") + + // errStateUnrecoverable is returned if state is required to be reverted to + // a destination without associated state history available. + errStateUnrecoverable = errors.New("state is unrecoverable") +) diff --git a/triedb/pathdb/execute.go b/triedb/pathdb/execute.go new file mode 100644 index 0000000..9074e4d --- /dev/null +++ b/triedb/pathdb/execute.go @@ -0,0 +1,186 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package pathdb + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/triedb/database" +) + +// context wraps all fields for executing state diffs. +type context struct { + prevRoot common.Hash + postRoot common.Hash + accounts map[common.Address][]byte + storages map[common.Address]map[common.Hash][]byte + nodes *trienode.MergedNodeSet + + // TODO (rjl493456442) abstract out the state hasher + // for supporting verkle tree. + accountTrie *trie.Trie +} + +// apply processes the given state diffs, updates the corresponding post-state +// and returns the trie nodes that have been modified. +func apply(db database.Database, prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte) (map[common.Hash]map[string]*trienode.Node, error) { + tr, err := trie.New(trie.TrieID(postRoot), db) + if err != nil { + return nil, err + } + ctx := &context{ + prevRoot: prevRoot, + postRoot: postRoot, + accounts: accounts, + storages: storages, + accountTrie: tr, + nodes: trienode.NewMergedNodeSet(), + } + for addr, account := range accounts { + var err error + if len(account) == 0 { + err = deleteAccount(ctx, db, addr) + } else { + err = updateAccount(ctx, db, addr) + } + if err != nil { + return nil, fmt.Errorf("failed to revert state, err: %w", err) + } + } + root, result := tr.Commit(false) + if root != prevRoot { + return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root) + } + if err := ctx.nodes.Merge(result); err != nil { + return nil, err + } + return ctx.nodes.Flatten(), nil +} + +// updateAccount the account was present in prev-state, and may or may not +// existent in post-state. Apply the reverse diff and verify if the storage +// root matches the one in prev-state account. +func updateAccount(ctx *context, db database.Database, addr common.Address) error { + // The account was present in prev-state, decode it from the + // 'slim-rlp' format bytes. + h := newHasher() + defer h.release() + + addrHash := h.hash(addr.Bytes()) + prev, err := types.FullAccount(ctx.accounts[addr]) + if err != nil { + return err + } + // The account may or may not existent in post-state, try to + // load it and decode if it's found. + blob, err := ctx.accountTrie.Get(addrHash.Bytes()) + if err != nil { + return err + } + post := types.NewEmptyStateAccount() + if len(blob) != 0 { + if err := rlp.DecodeBytes(blob, &post); err != nil { + return err + } + } + // Apply all storage changes into the post-state storage trie. + st, err := trie.New(trie.StorageTrieID(ctx.postRoot, addrHash, post.Root), db) + if err != nil { + return err + } + for key, val := range ctx.storages[addr] { + var err error + if len(val) == 0 { + err = st.Delete(key.Bytes()) + } else { + err = st.Update(key.Bytes(), val) + } + if err != nil { + return err + } + } + root, result := st.Commit(false) + if root != prev.Root { + return errors.New("failed to reset storage trie") + } + // The returned set can be nil if storage trie is not changed + // at all. + if result != nil { + if err := ctx.nodes.Merge(result); err != nil { + return err + } + } + // Write the prev-state account into the main trie + full, err := rlp.EncodeToBytes(prev) + if err != nil { + return err + } + return ctx.accountTrie.Update(addrHash.Bytes(), full) +} + +// deleteAccount the account was not present in prev-state, and is expected +// to be existent in post-state. Apply the reverse diff and verify if the +// account and storage is wiped out correctly. +func deleteAccount(ctx *context, db database.Database, addr common.Address) error { + // The account must be existent in post-state, load the account. + h := newHasher() + defer h.release() + + addrHash := h.hash(addr.Bytes()) + blob, err := ctx.accountTrie.Get(addrHash.Bytes()) + if err != nil { + return err + } + if len(blob) == 0 { + return fmt.Errorf("account is non-existent %#x", addrHash) + } + var post types.StateAccount + if err := rlp.DecodeBytes(blob, &post); err != nil { + return err + } + st, err := trie.New(trie.StorageTrieID(ctx.postRoot, addrHash, post.Root), db) + if err != nil { + return err + } + for key, val := range ctx.storages[addr] { + if len(val) != 0 { + return errors.New("expect storage deletion") + } + if err := st.Delete(key.Bytes()); err != nil { + return err + } + } + root, result := st.Commit(false) + if root != types.EmptyRootHash { + return errors.New("failed to clear storage trie") + } + // The returned set can be nil if storage trie is not changed + // at all. + if result != nil { + if err := ctx.nodes.Merge(result); err != nil { + return err + } + } + // Delete the post-state account from the main trie. + return ctx.accountTrie.Delete(addrHash.Bytes()) +} diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go new file mode 100644 index 0000000..d77f7aa --- /dev/null +++ b/triedb/pathdb/history.go @@ -0,0 +1,628 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package pathdb + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "slices" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/triestate" + "golang.org/x/exp/maps" +) + +// State history records the state changes involved in executing a block. The +// state can be reverted to the previous version by applying the associated +// history object (state reverse diff). State history objects are kept to +// guarantee that the system can perform state rollbacks in case of deep reorg. +// +// Each state transition will generate a state history object. Note that not +// every block has a corresponding state history object. If a block performs +// no state changes whatsoever, no state is created for it. Each state history +// will have a sequentially increasing number acting as its unique identifier. +// +// The state history is written to disk (ancient store) when the corresponding +// diff layer is merged into the disk layer. At the same time, system can prune +// the oldest histories according to config. +// +// Disk State +// ^ +// | +// +------------+ +---------+ +---------+ +---------+ +// | Init State |---->| State 1 |---->| ... |---->| State n | +// +------------+ +---------+ +---------+ +---------+ +// +// +-----------+ +------+ +-----------+ +// | History 1 |----> | ... |---->| History n | +// +-----------+ +------+ +-----------+ +// +// # Rollback +// +// If the system wants to roll back to a previous state n, it needs to ensure +// all history objects from n+1 up to the current disk layer are existent. The +// history objects are applied to the state in reverse order, starting from the +// current disk layer. + +const ( + accountIndexSize = common.AddressLength + 13 // The length of encoded account index + slotIndexSize = common.HashLength + 5 // The length of encoded slot index + historyMetaSize = 9 + 2*common.HashLength // The length of encoded history meta + + stateHistoryVersion = uint8(0) // initial version of state history structure. +) + +// Each state history entry is consisted of five elements: +// +// # metadata +// This object contains a few meta fields, such as the associated state root, +// block number, version tag and so on. This object may contain an extra +// accountHash list which means the storage changes belong to these accounts +// are not complete due to large contract destruction. The incomplete history +// can not be used for rollback and serving archive state request. +// +// # account index +// This object contains some index information of account. For example, offset +// and length indicate the location of the data belonging to the account. Besides, +// storageOffset and storageSlots indicate the storage modification location +// belonging to the account. +// +// The size of each account index is *fixed*, and all indexes are sorted +// lexicographically. Thus binary search can be performed to quickly locate a +// specific account. +// +// # account data +// Account data is a concatenated byte stream composed of all account data. +// The account data can be solved by the offset and length info indicated +// by corresponding account index. +// +// fixed size +// ^ ^ +// / \ +// +-----------------+-----------------+----------------+-----------------+ +// | Account index 1 | Account index 2 | ... | Account index N | +// +-----------------+-----------------+----------------+-----------------+ +// | +// | length +// offset |----------------+ +// v v +// +----------------+----------------+----------------+----------------+ +// | Account data 1 | Account data 2 | ... | Account data N | +// +----------------+----------------+----------------+----------------+ +// +// # storage index +// This object is similar with account index. It's also fixed size and contains +// the location info of storage slot data. +// +// # storage data +// Storage data is a concatenated byte stream composed of all storage slot data. +// The storage slot data can be solved by the location info indicated by +// corresponding account index and storage slot index. +// +// fixed size +// ^ ^ +// / \ +// +-----------------+-----------------+----------------+-----------------+ +// | Account index 1 | Account index 2 | ... | Account index N | +// +-----------------+-----------------+----------------+-----------------+ +// | +// | storage slots +// storage offset |-----------------------------------------------------+ +// v v +// +-----------------+-----------------+-----------------+ +// | storage index 1 | storage index 2 | storage index 3 | +// +-----------------+-----------------+-----------------+ +// | length +// offset |-------------+ +// v v +// +-------------+ +// | slot data 1 | +// +-------------+ + +// accountIndex describes the metadata belonging to an account. +type accountIndex struct { + address common.Address // The address of account + length uint8 // The length of account data, size limited by 255 + offset uint32 // The offset of item in account data table + storageOffset uint32 // The offset of storage index in storage index table + storageSlots uint32 // The number of mutated storage slots belonging to the account +} + +// encode packs account index into byte stream. +func (i *accountIndex) encode() []byte { + var buf [accountIndexSize]byte + copy(buf[:], i.address.Bytes()) + buf[common.AddressLength] = i.length + binary.BigEndian.PutUint32(buf[common.AddressLength+1:], i.offset) + binary.BigEndian.PutUint32(buf[common.AddressLength+5:], i.storageOffset) + binary.BigEndian.PutUint32(buf[common.AddressLength+9:], i.storageSlots) + return buf[:] +} + +// decode unpacks account index from byte stream. +func (i *accountIndex) decode(blob []byte) { + i.address = common.BytesToAddress(blob[:common.AddressLength]) + i.length = blob[common.AddressLength] + i.offset = binary.BigEndian.Uint32(blob[common.AddressLength+1:]) + i.storageOffset = binary.BigEndian.Uint32(blob[common.AddressLength+5:]) + i.storageSlots = binary.BigEndian.Uint32(blob[common.AddressLength+9:]) +} + +// slotIndex describes the metadata belonging to a storage slot. +type slotIndex struct { + hash common.Hash // The hash of slot key + length uint8 // The length of storage slot, up to 32 bytes defined in protocol + offset uint32 // The offset of item in storage slot data table +} + +// encode packs slot index into byte stream. +func (i *slotIndex) encode() []byte { + var buf [slotIndexSize]byte + copy(buf[:common.HashLength], i.hash.Bytes()) + buf[common.HashLength] = i.length + binary.BigEndian.PutUint32(buf[common.HashLength+1:], i.offset) + return buf[:] +} + +// decode unpack slot index from the byte stream. +func (i *slotIndex) decode(blob []byte) { + i.hash = common.BytesToHash(blob[:common.HashLength]) + i.length = blob[common.HashLength] + i.offset = binary.BigEndian.Uint32(blob[common.HashLength+1:]) +} + +// meta describes the meta data of state history object. +type meta struct { + version uint8 // version tag of history object + parent common.Hash // prev-state root before the state transition + root common.Hash // post-state root after the state transition + block uint64 // associated block number +} + +// encode packs the meta object into byte stream. +func (m *meta) encode() []byte { + buf := make([]byte, historyMetaSize) + buf[0] = m.version + copy(buf[1:1+common.HashLength], m.parent.Bytes()) + copy(buf[1+common.HashLength:1+2*common.HashLength], m.root.Bytes()) + binary.BigEndian.PutUint64(buf[1+2*common.HashLength:historyMetaSize], m.block) + return buf[:] +} + +// decode unpacks the meta object from byte stream. +func (m *meta) decode(blob []byte) error { + if len(blob) < 1 { + return errors.New("no version tag") + } + switch blob[0] { + case stateHistoryVersion: + if len(blob) != historyMetaSize { + return fmt.Errorf("invalid state history meta, len: %d", len(blob)) + } + m.version = blob[0] + m.parent = common.BytesToHash(blob[1 : 1+common.HashLength]) + m.root = common.BytesToHash(blob[1+common.HashLength : 1+2*common.HashLength]) + m.block = binary.BigEndian.Uint64(blob[1+2*common.HashLength : historyMetaSize]) + return nil + default: + return fmt.Errorf("unknown version %d", blob[0]) + } +} + +// history represents a set of state changes belong to a block along with +// the metadata including the state roots involved in the state transition. +// State history objects in disk are linked with each other by a unique id +// (8-bytes integer), the oldest state history object can be pruned on demand +// in order to control the storage size. +type history struct { + meta *meta // Meta data of history + accounts map[common.Address][]byte // Account data keyed by its address hash + accountList []common.Address // Sorted account hash list + storages map[common.Address]map[common.Hash][]byte // Storage data keyed by its address hash and slot hash + storageList map[common.Address][]common.Hash // Sorted slot hash list +} + +// newHistory constructs the state history object with provided state change set. +func newHistory(root common.Hash, parent common.Hash, block uint64, states *triestate.Set) *history { + var ( + accountList = maps.Keys(states.Accounts) + storageList = make(map[common.Address][]common.Hash) + ) + slices.SortFunc(accountList, common.Address.Cmp) + + for addr, slots := range states.Storages { + slist := maps.Keys(slots) + slices.SortFunc(slist, common.Hash.Cmp) + storageList[addr] = slist + } + return &history{ + meta: &meta{ + version: stateHistoryVersion, + parent: parent, + root: root, + block: block, + }, + accounts: states.Accounts, + accountList: accountList, + storages: states.Storages, + storageList: storageList, + } +} + +// encode serializes the state history and returns four byte streams represent +// concatenated account/storage data, account/storage indexes respectively. +func (h *history) encode() ([]byte, []byte, []byte, []byte) { + var ( + slotNumber uint32 // the number of processed slots + accountData []byte // the buffer for concatenated account data + storageData []byte // the buffer for concatenated storage data + accountIndexes []byte // the buffer for concatenated account index + storageIndexes []byte // the buffer for concatenated storage index + ) + for _, addr := range h.accountList { + accIndex := accountIndex{ + address: addr, + length: uint8(len(h.accounts[addr])), + offset: uint32(len(accountData)), + } + slots, exist := h.storages[addr] + if exist { + // Encode storage slots in order + for _, slotHash := range h.storageList[addr] { + sIndex := slotIndex{ + hash: slotHash, + length: uint8(len(slots[slotHash])), + offset: uint32(len(storageData)), + } + storageData = append(storageData, slots[slotHash]...) + storageIndexes = append(storageIndexes, sIndex.encode()...) + } + // Fill up the storage meta in account index + accIndex.storageOffset = slotNumber + accIndex.storageSlots = uint32(len(slots)) + slotNumber += uint32(len(slots)) + } + accountData = append(accountData, h.accounts[addr]...) + accountIndexes = append(accountIndexes, accIndex.encode()...) + } + return accountData, storageData, accountIndexes, storageIndexes +} + +// decoder wraps the byte streams for decoding with extra meta fields. +type decoder struct { + accountData []byte // the buffer for concatenated account data + storageData []byte // the buffer for concatenated storage data + accountIndexes []byte // the buffer for concatenated account index + storageIndexes []byte // the buffer for concatenated storage index + + lastAccount *common.Address // the address of last resolved account + lastAccountRead uint32 // the read-cursor position of account data + lastSlotIndexRead uint32 // the read-cursor position of storage slot index + lastSlotDataRead uint32 // the read-cursor position of storage slot data +} + +// verify validates the provided byte streams for decoding state history. A few +// checks will be performed to quickly detect data corruption. The byte stream +// is regarded as corrupted if: +// +// - account indexes buffer is empty(empty state set is invalid) +// - account indexes/storage indexer buffer is not aligned +// +// note, these situations are allowed: +// +// - empty account data: all accounts were not present +// - empty storage set: no slots are modified +func (r *decoder) verify() error { + if len(r.accountIndexes)%accountIndexSize != 0 || len(r.accountIndexes) == 0 { + return fmt.Errorf("invalid account index, len: %d", len(r.accountIndexes)) + } + if len(r.storageIndexes)%slotIndexSize != 0 { + return fmt.Errorf("invalid storage index, len: %d", len(r.storageIndexes)) + } + return nil +} + +// readAccount parses the account from the byte stream with specified position. +func (r *decoder) readAccount(pos int) (accountIndex, []byte, error) { + // Decode account index from the index byte stream. + var index accountIndex + if (pos+1)*accountIndexSize > len(r.accountIndexes) { + return accountIndex{}, nil, errors.New("account data buffer is corrupted") + } + index.decode(r.accountIndexes[pos*accountIndexSize : (pos+1)*accountIndexSize]) + + // Perform validation before parsing account data, ensure + // - account is sorted in order in byte stream + // - account data is strictly encoded with no gap inside + // - account data is not out-of-slice + if r.lastAccount != nil { // zero address is possible + if bytes.Compare(r.lastAccount.Bytes(), index.address.Bytes()) >= 0 { + return accountIndex{}, nil, errors.New("account is not in order") + } + } + if index.offset != r.lastAccountRead { + return accountIndex{}, nil, errors.New("account data buffer is gaped") + } + last := index.offset + uint32(index.length) + if uint32(len(r.accountData)) < last { + return accountIndex{}, nil, errors.New("account data buffer is corrupted") + } + data := r.accountData[index.offset:last] + + r.lastAccount = &index.address + r.lastAccountRead = last + + return index, data, nil +} + +// readStorage parses the storage slots from the byte stream with specified account. +func (r *decoder) readStorage(accIndex accountIndex) ([]common.Hash, map[common.Hash][]byte, error) { + var ( + last common.Hash + count = int(accIndex.storageSlots) + list = make([]common.Hash, 0, count) + storage = make(map[common.Hash][]byte, count) + ) + for j := 0; j < count; j++ { + var ( + index slotIndex + start = (accIndex.storageOffset + uint32(j)) * uint32(slotIndexSize) + end = (accIndex.storageOffset + uint32(j+1)) * uint32(slotIndexSize) + ) + // Perform validation before parsing storage slot data, ensure + // - slot index is not out-of-slice + // - slot data is not out-of-slice + // - slot is sorted in order in byte stream + // - slot indexes is strictly encoded with no gap inside + // - slot data is strictly encoded with no gap inside + if start != r.lastSlotIndexRead { + return nil, nil, errors.New("storage index buffer is gapped") + } + if uint32(len(r.storageIndexes)) < end { + return nil, nil, errors.New("storage index buffer is corrupted") + } + index.decode(r.storageIndexes[start:end]) + + if bytes.Compare(last.Bytes(), index.hash.Bytes()) >= 0 { + return nil, nil, errors.New("storage slot is not in order") + } + if index.offset != r.lastSlotDataRead { + return nil, nil, errors.New("storage data buffer is gapped") + } + sEnd := index.offset + uint32(index.length) + if uint32(len(r.storageData)) < sEnd { + return nil, nil, errors.New("storage data buffer is corrupted") + } + storage[index.hash] = r.storageData[r.lastSlotDataRead:sEnd] + list = append(list, index.hash) + + last = index.hash + r.lastSlotIndexRead = end + r.lastSlotDataRead = sEnd + } + return list, storage, nil +} + +// decode deserializes the account and storage data from the provided byte stream. +func (h *history) decode(accountData, storageData, accountIndexes, storageIndexes []byte) error { + var ( + count = len(accountIndexes) / accountIndexSize + accounts = make(map[common.Address][]byte, count) + storages = make(map[common.Address]map[common.Hash][]byte) + accountList = make([]common.Address, 0, count) + storageList = make(map[common.Address][]common.Hash) + + r = &decoder{ + accountData: accountData, + storageData: storageData, + accountIndexes: accountIndexes, + storageIndexes: storageIndexes, + } + ) + if err := r.verify(); err != nil { + return err + } + for i := 0; i < count; i++ { + // Resolve account first + accIndex, accData, err := r.readAccount(i) + if err != nil { + return err + } + accounts[accIndex.address] = accData + accountList = append(accountList, accIndex.address) + + // Resolve storage slots + slotList, slotData, err := r.readStorage(accIndex) + if err != nil { + return err + } + if len(slotList) > 0 { + storageList[accIndex.address] = slotList + storages[accIndex.address] = slotData + } + } + h.accounts = accounts + h.accountList = accountList + h.storages = storages + h.storageList = storageList + return nil +} + +// readHistory reads and decodes the state history object by the given id. +func readHistory(reader ethdb.AncientReader, id uint64) (*history, error) { + blob := rawdb.ReadStateHistoryMeta(reader, id) + if len(blob) == 0 { + return nil, fmt.Errorf("state history not found %d", id) + } + var m meta + if err := m.decode(blob); err != nil { + return nil, err + } + var ( + dec = history{meta: &m} + accountData = rawdb.ReadStateAccountHistory(reader, id) + storageData = rawdb.ReadStateStorageHistory(reader, id) + accountIndexes = rawdb.ReadStateAccountIndex(reader, id) + storageIndexes = rawdb.ReadStateStorageIndex(reader, id) + ) + if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { + return nil, err + } + return &dec, nil +} + +// writeHistory persists the state history with the provided state set. +func writeHistory(writer ethdb.AncientWriter, 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 ( + start = time.Now() + history = newHistory(dl.rootHash(), dl.parentLayer().rootHash(), dl.block, dl.states) + ) + 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(writer, dl.stateID(), history.meta.encode(), accountIndex, storageIndex, accountData, storageData) + + 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, "elapsed", common.PrettyDuration(time.Since(start))) + + return nil +} + +// checkHistories retrieves a batch of meta objects with the specified range +// and performs the callback on each item. +func checkHistories(reader ethdb.AncientReader, start, count uint64, check func(*meta) error) error { + for count > 0 { + number := count + if number > 10000 { + number = 10000 // split the big read into small chunks + } + blobs, err := rawdb.ReadStateHistoryMetaList(reader, start, number) + if err != nil { + return err + } + for _, blob := range blobs { + var dec meta + if err := dec.decode(blob); err != nil { + return err + } + if err := check(&dec); err != nil { + return err + } + } + count -= uint64(len(blobs)) + start += uint64(len(blobs)) + } + return nil +} + +// truncateFromHead removes the extra state histories from the head with the given +// parameters. It returns the number of items removed from the head. +func truncateFromHead(db ethdb.Batcher, store ethdb.AncientStore, nhead uint64) (int, error) { + ohead, err := store.Ancients() + if err != nil { + return 0, err + } + otail, err := store.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] + blobs, err := rawdb.ReadStateHistoryMetaList(store, nhead+1, ohead-nhead) + if err != nil { + return 0, err + } + batch := db.NewBatch() + for _, blob := range blobs { + var m meta + if err := m.decode(blob); err != nil { + return 0, err + } + rawdb.DeleteStateID(batch, m.root) + } + if err := batch.Write(); err != nil { + return 0, err + } + ohead, err = store.TruncateHead(nhead) + if err != nil { + return 0, err + } + return int(ohead - nhead), nil +} + +// truncateFromTail removes the extra state histories from the tail with the given +// parameters. It returns the number of items removed from the tail. +func truncateFromTail(db ethdb.Batcher, store ethdb.AncientStore, ntail uint64) (int, error) { + ohead, err := store.Ancients() + if err != nil { + return 0, err + } + otail, err := store.Tail() + if err != nil { + return 0, err + } + // 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] + blobs, err := rawdb.ReadStateHistoryMetaList(store, otail+1, ntail-otail) + if err != nil { + return 0, err + } + batch := db.NewBatch() + for _, blob := range blobs { + var m meta + if err := m.decode(blob); err != nil { + return 0, err + } + rawdb.DeleteStateID(batch, m.root) + } + if err := batch.Write(); err != nil { + return 0, err + } + otail, err = store.TruncateTail(ntail) + if err != nil { + return 0, err + } + return int(ntail - otail), nil +} diff --git a/triedb/pathdb/history_inspect.go b/triedb/pathdb/history_inspect.go new file mode 100644 index 0000000..240474d --- /dev/null +++ b/triedb/pathdb/history_inspect.go @@ -0,0 +1,151 @@ +// Copyright 2024 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 first { + first = start + } + // Load the id of the last history object in local store. + head, err := freezer.Ancients() + if err != nil { + return 0, 0, err + } + last := head - 1 + if end != 0 && end < last { + last = end + } + // Make sure the range is valid + if first >= last { + return 0, 0, fmt.Errorf("range is invalid, first: %d, last: %d", first, last) + } + return first, last, nil +} + +func inspectHistory(freezer ethdb.AncientReader, start, end uint64, onHistory func(*history, *HistoryStats)) (*HistoryStats, error) { + var ( + stats = &HistoryStats{} + init = time.Now() + logged = time.Now() + ) + start, end, err := sanitizeRange(start, end, freezer) + if err != nil { + return nil, err + } + for id := start; id <= end; id += 1 { + // The entire history object is decoded, although it's unnecessary for + // account inspection. TODO(rjl493456442) optimization is worthwhile. + h, err := readHistory(freezer, id) + if err != nil { + return nil, err + } + if id == start { + stats.Start = h.meta.block + } + if id == end { + stats.End = h.meta.block + } + onHistory(h, stats) + + if time.Since(logged) > time.Second*8 { + logged = time.Now() + eta := float64(time.Since(init)) / float64(id-start+1) * float64(end-id) + log.Info("Inspecting state history", "checked", id-start+1, "left", end-id, "elapsed", common.PrettyDuration(time.Since(init)), "eta", common.PrettyDuration(eta)) + } + } + log.Info("Inspected state history", "total", end-start+1, "elapsed", common.PrettyDuration(time.Since(init))) + return stats, nil +} + +// accountHistory inspects the account history within the range. +func accountHistory(freezer ethdb.AncientReader, address common.Address, start, end uint64) (*HistoryStats, error) { + return inspectHistory(freezer, start, end, func(h *history, stats *HistoryStats) { + blob, exists := h.accounts[address] + if !exists { + return + } + stats.Blocks = append(stats.Blocks, h.meta.block) + stats.Origins = append(stats.Origins, blob) + }) +} + +// storageHistory inspects the storage history within the range. +func storageHistory(freezer ethdb.AncientReader, address common.Address, slot common.Hash, start uint64, end uint64) (*HistoryStats, error) { + return inspectHistory(freezer, start, end, func(h *history, stats *HistoryStats) { + slots, exists := h.storages[address] + if !exists { + return + } + blob, exists := slots[slot] + if !exists { + return + } + stats.Blocks = append(stats.Blocks, h.meta.block) + stats.Origins = append(stats.Origins, blob) + }) +} + +// historyRange returns the block number range of local state histories. +func historyRange(freezer ethdb.AncientReader) (uint64, uint64, error) { + // Load the id of the first history object in local store. + tail, err := freezer.Tail() + if err != nil { + return 0, 0, err + } + first := tail + 1 + + // Load the id of the last history object in local store. + head, err := freezer.Ancients() + if err != nil { + return 0, 0, err + } + last := head - 1 + + fh, err := readHistory(freezer, first) + if err != nil { + return 0, 0, err + } + lh, err := readHistory(freezer, last) + if err != nil { + return 0, 0, err + } + return fh.meta.block, lh.meta.block, nil +} diff --git a/triedb/pathdb/history_test.go b/triedb/pathdb/history_test.go new file mode 100644 index 0000000..4114aa1 --- /dev/null +++ b/triedb/pathdb/history_test.go @@ -0,0 +1,329 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package pathdb + +import ( + "bytes" + "fmt" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/testrand" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +// randomStateSet generates a random state change set. +func randomStateSet(n int) *triestate.Set { + var ( + accounts = make(map[common.Address][]byte) + storages = make(map[common.Address]map[common.Hash][]byte) + ) + for i := 0; i < n; i++ { + addr := testrand.Address() + storages[addr] = make(map[common.Hash][]byte) + for j := 0; j < 3; j++ { + v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testrand.Bytes(32))) + storages[addr][testrand.Hash()] = v + } + account := generateAccount(types.EmptyRootHash) + accounts[addr] = types.SlimAccountRLP(account) + } + return triestate.New(accounts, storages) +} + +func makeHistory() *history { + return newHistory(testrand.Hash(), types.EmptyRootHash, 0, randomStateSet(3)) +} + +func makeHistories(n int) []*history { + var ( + parent = types.EmptyRootHash + result []*history + ) + for i := 0; i < n; i++ { + root := testrand.Hash() + h := newHistory(root, parent, uint64(i), randomStateSet(3)) + parent = root + result = append(result, h) + } + return result +} + +func TestEncodeDecodeHistory(t *testing.T) { + var ( + m meta + dec history + obj = makeHistory() + ) + // check if meta data can be correctly encode/decode + blob := obj.meta.encode() + if err := m.decode(blob); err != nil { + t.Fatalf("Failed to decode %v", err) + } + if !reflect.DeepEqual(&m, obj.meta) { + t.Fatal("meta is mismatched") + } + + // check if account/storage data can be correctly encode/decode + accountData, storageData, accountIndexes, storageIndexes := obj.encode() + if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { + t.Fatalf("Failed to decode, err: %v", err) + } + if !compareSet(dec.accounts, obj.accounts) { + t.Fatal("account data is mismatched") + } + if !compareStorages(dec.storages, obj.storages) { + t.Fatal("storage data is mismatched") + } + if !compareList(dec.accountList, obj.accountList) { + t.Fatal("account list is mismatched") + } + if !compareStorageList(dec.storageList, obj.storageList) { + t.Fatal("storage list is mismatched") + } +} + +func checkHistory(t *testing.T, db ethdb.KeyValueReader, freezer ethdb.AncientReader, id uint64, root common.Hash, exist bool) { + blob := rawdb.ReadStateHistoryMeta(freezer, id) + if exist && len(blob) == 0 { + t.Fatalf("Failed to load trie history, %d", id) + } + if !exist && len(blob) != 0 { + t.Fatalf("Unexpected trie history, %d", id) + } + if exist && rawdb.ReadStateID(db, root) == nil { + t.Fatalf("Root->ID mapping is not found, %d", id) + } + if !exist && rawdb.ReadStateID(db, root) != nil { + t.Fatalf("Unexpected root->ID mapping, %d", id) + } +} + +func checkHistoriesInRange(t *testing.T, db ethdb.KeyValueReader, freezer ethdb.AncientReader, from, to uint64, roots []common.Hash, exist bool) { + for i, j := from, 0; i <= to; i, j = i+1, j+1 { + checkHistory(t, db, freezer, i, roots[j], exist) + } +} + +func TestTruncateHeadHistory(t *testing.T) { + var ( + roots []common.Hash + hs = makeHistories(10) + db = rawdb.NewMemoryDatabase() + freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false) + ) + defer freezer.Close() + + for i := 0; i < len(hs); i++ { + accountData, storageData, accountIndex, storageIndex := hs[i].encode() + rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) + rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) + roots = append(roots, hs[i].meta.root) + } + for size := len(hs); size > 0; size-- { + pruned, err := truncateFromHead(db, freezer, uint64(size-1)) + if err != nil { + t.Fatalf("Failed to truncate from head %v", err) + } + if pruned != 1 { + t.Error("Unexpected pruned items", "want", 1, "got", pruned) + } + checkHistoriesInRange(t, db, freezer, uint64(size), uint64(10), roots[size-1:], false) + checkHistoriesInRange(t, db, freezer, uint64(1), uint64(size-1), roots[:size-1], true) + } +} + +func TestTruncateTailHistory(t *testing.T) { + var ( + roots []common.Hash + hs = makeHistories(10) + db = rawdb.NewMemoryDatabase() + freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false) + ) + defer freezer.Close() + + for i := 0; i < len(hs); i++ { + accountData, storageData, accountIndex, storageIndex := hs[i].encode() + rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) + rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) + roots = append(roots, hs[i].meta.root) + } + for newTail := 1; newTail < len(hs); newTail++ { + pruned, _ := truncateFromTail(db, freezer, uint64(newTail)) + if pruned != 1 { + t.Error("Unexpected pruned items", "want", 1, "got", pruned) + } + checkHistoriesInRange(t, db, freezer, uint64(1), uint64(newTail), roots[:newTail], false) + checkHistoriesInRange(t, db, freezer, uint64(newTail+1), uint64(10), roots[newTail:], true) + } +} + +func TestTruncateTailHistories(t *testing.T) { + var cases = []struct { + limit uint64 + expPruned int + maxPruned uint64 + minUnpruned uint64 + empty bool + }{ + { + 1, 9, 9, 10, false, + }, + { + 0, 10, 10, 0 /* no meaning */, true, + }, + { + 10, 0, 0, 1, false, + }, + } + for i, c := range cases { + var ( + roots []common.Hash + hs = makeHistories(10) + db = rawdb.NewMemoryDatabase() + freezer, _ = rawdb.NewStateFreezer(t.TempDir()+fmt.Sprintf("%d", i), false) + ) + defer freezer.Close() + + for i := 0; i < len(hs); i++ { + accountData, storageData, accountIndex, storageIndex := hs[i].encode() + rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) + rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) + roots = append(roots, hs[i].meta.root) + } + pruned, _ := truncateFromTail(db, freezer, uint64(10)-c.limit) + if pruned != c.expPruned { + t.Error("Unexpected pruned items", "want", c.expPruned, "got", pruned) + } + if c.empty { + checkHistoriesInRange(t, db, freezer, uint64(1), uint64(10), roots, false) + } else { + tail := 10 - int(c.limit) + checkHistoriesInRange(t, db, freezer, uint64(1), c.maxPruned, roots[:tail], false) + checkHistoriesInRange(t, db, freezer, c.minUnpruned, uint64(10), roots[tail:], true) + } + } +} + +func TestTruncateOutOfRange(t *testing.T) { + var ( + hs = makeHistories(10) + db = rawdb.NewMemoryDatabase() + freezer, _ = rawdb.NewStateFreezer(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) + } + } +} + +func compareSet[k comparable](a, b map[k][]byte) bool { + if len(a) != len(b) { + return false + } + for key, valA := range a { + valB, ok := b[key] + if !ok { + return false + } + if !bytes.Equal(valA, valB) { + return false + } + } + return true +} + +func compareList[k comparable](a, b []k) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func compareStorages(a, b map[common.Address]map[common.Hash][]byte) bool { + if len(a) != len(b) { + return false + } + for h, subA := range a { + subB, ok := b[h] + if !ok { + return false + } + if !compareSet(subA, subB) { + return false + } + } + return true +} + +func compareStorageList(a, b map[common.Address][]common.Hash) bool { + if len(a) != len(b) { + return false + } + for h, la := range a { + lb, ok := b[h] + if !ok { + return false + } + if !compareList(la, lb) { + return false + } + } + return true +} diff --git a/triedb/pathdb/journal.go b/triedb/pathdb/journal.go new file mode 100644 index 0000000..1740ec5 --- /dev/null +++ b/triedb/pathdb/journal.go @@ -0,0 +1,385 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pathdb + +import ( + "bytes" + "errors" + "fmt" + "io" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +var ( + errMissJournal = errors.New("journal not found") + errMissVersion = errors.New("version not found") + errUnexpectedVersion = errors.New("unexpected journal version") + errMissDiskRoot = errors.New("disk layer root not found") + errUnmatchedJournal = errors.New("unmatched journal") +) + +// journalVersion ensures that an incompatible journal is detected and discarded. +// +// Changelog: +// +// - Version 0: initial version +// - Version 1: storage.Incomplete field is removed +const journalVersion uint64 = 1 + +// journalNode represents a trie node persisted in the journal. +type journalNode struct { + Path []byte // Path of the node in the trie + Blob []byte // RLP-encoded trie node blob, nil means the node is deleted +} + +// journalNodes represents a list trie nodes belong to a single account +// or the main account trie. +type journalNodes struct { + Owner common.Hash + Nodes []journalNode +} + +// journalAccounts represents a list accounts belong to the layer. +type journalAccounts struct { + Addresses []common.Address + Accounts [][]byte +} + +// journalStorage represents a list of storage slots belong to an account. +type journalStorage struct { + Account common.Address + Hashes []common.Hash + Slots [][]byte +} + +// loadJournal tries to parse the layer journal from the disk. +func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) { + journal := rawdb.ReadTrieJournal(db.diskdb) + if len(journal) == 0 { + return nil, errMissJournal + } + r := rlp.NewStream(bytes.NewReader(journal), 0) + + // Firstly, resolve the first element as the journal version + version, err := r.Uint64() + if err != nil { + return nil, errMissVersion + } + if version != journalVersion { + return nil, fmt.Errorf("%w want %d got %d", errUnexpectedVersion, journalVersion, version) + } + // Secondly, resolve the disk layer root, ensure it's continuous + // with disk layer. Note now we can ensure it's the layer journal + // correct version, so we expect everything can be resolved properly. + var root common.Hash + if err := r.Decode(&root); err != nil { + return nil, errMissDiskRoot + } + // The journal is not matched with persistent state, discard them. + // It can happen that geth crashes without persisting the journal. + if !bytes.Equal(root.Bytes(), diskRoot.Bytes()) { + return nil, fmt.Errorf("%w want %x got %x", errUnmatchedJournal, root, diskRoot) + } + // Load the disk layer from the journal + base, err := db.loadDiskLayer(r) + if err != nil { + return nil, err + } + // Load all the diff layers from the journal + head, err := db.loadDiffLayer(base, r) + if err != nil { + return nil, err + } + log.Debug("Loaded layer journal", "diskroot", diskRoot, "diffhead", head.rootHash()) + return head, nil +} + +// loadLayers loads a pre-existing state layer backed by a key-value store. +func (db *Database) loadLayers() layer { + // Retrieve the root node of persistent state. + var root = types.EmptyRootHash + if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 { + root = crypto.Keccak256Hash(blob) + } + // Load the layers by resolving the journal + head, err := db.loadJournal(root) + if err == nil { + return head + } + // journal is not matched(or missing) with the persistent state, discard + // it. Display log for discarding journal, but try to avoid showing + // useless information when the db is created from scratch. + if !(root == types.EmptyRootHash && errors.Is(err, errMissJournal)) { + log.Info("Failed to load journal, discard it", "err", err) + } + // Return single layer with persistent state. + return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, newNodeBuffer(db.bufferSize, nil, 0)) +} + +// loadDiskLayer reads the binary blob from the layer journal, reconstructing +// a new disk layer on it. +func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) { + // Resolve disk layer root + var root common.Hash + if err := r.Decode(&root); err != nil { + return nil, fmt.Errorf("load disk root: %v", err) + } + // Resolve the state id of disk layer, it can be different + // with the persistent id tracked in disk, the id distance + // is the number of transitions aggregated in disk layer. + var id uint64 + if err := r.Decode(&id); err != nil { + return nil, fmt.Errorf("load state id: %v", err) + } + stored := rawdb.ReadPersistentStateID(db.diskdb) + if stored > id { + return nil, fmt.Errorf("invalid state id: stored %d resolved %d", stored, id) + } + // Resolve nodes cached in node buffer + var encoded []journalNodes + if err := r.Decode(&encoded); err != nil { + return nil, fmt.Errorf("load disk nodes: %v", err) + } + nodes := make(map[common.Hash]map[string]*trienode.Node) + for _, entry := range encoded { + subset := make(map[string]*trienode.Node) + for _, n := range entry.Nodes { + if len(n.Blob) > 0 { + subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob) + } else { + subset[string(n.Path)] = trienode.NewDeleted() + } + } + nodes[entry.Owner] = subset + } + // Calculate the internal state transitions by id difference. + base := newDiskLayer(root, id, db, nil, newNodeBuffer(db.bufferSize, nodes, id-stored)) + return base, nil +} + +// loadDiffLayer reads the next sections of a layer journal, reconstructing a new +// diff and verifying that it can be linked to the requested parent. +func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream) (layer, error) { + // Read the next diff journal entry + var root common.Hash + if err := r.Decode(&root); err != nil { + // The first read may fail with EOF, marking the end of the journal + if err == io.EOF { + return parent, nil + } + return nil, fmt.Errorf("load diff root: %v", err) + } + var block uint64 + if err := r.Decode(&block); err != nil { + return nil, fmt.Errorf("load block number: %v", err) + } + // Read in-memory trie nodes from journal + var encoded []journalNodes + if err := r.Decode(&encoded); err != nil { + return nil, fmt.Errorf("load diff nodes: %v", err) + } + nodes := make(map[common.Hash]map[string]*trienode.Node) + for _, entry := range encoded { + subset := make(map[string]*trienode.Node) + for _, n := range entry.Nodes { + if len(n.Blob) > 0 { + subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob) + } else { + subset[string(n.Path)] = trienode.NewDeleted() + } + } + nodes[entry.Owner] = subset + } + // Read state changes from journal + var ( + jaccounts journalAccounts + jstorages []journalStorage + accounts = make(map[common.Address][]byte) + storages = make(map[common.Address]map[common.Hash][]byte) + ) + if err := r.Decode(&jaccounts); err != nil { + return nil, fmt.Errorf("load diff accounts: %v", err) + } + for i, addr := range jaccounts.Addresses { + accounts[addr] = jaccounts.Accounts[i] + } + if err := r.Decode(&jstorages); err != nil { + return nil, fmt.Errorf("load diff storages: %v", err) + } + for _, entry := range jstorages { + set := make(map[common.Hash][]byte) + for i, h := range entry.Hashes { + if len(entry.Slots[i]) > 0 { + set[h] = entry.Slots[i] + } else { + set[h] = nil + } + } + storages[entry.Account] = set + } + return db.loadDiffLayer(newDiffLayer(parent, root, parent.stateID()+1, block, nodes, triestate.New(accounts, storages)), r) +} + +// journal implements the layer interface, marshaling the un-flushed trie nodes +// along with layer meta data into provided byte buffer. +func (dl *diskLayer) journal(w io.Writer) error { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // Ensure the layer didn't get stale + if dl.stale { + return errSnapshotStale + } + // Step one, write the disk root into the journal. + if err := rlp.Encode(w, dl.root); err != nil { + return err + } + // Step two, write the corresponding state id into the journal + if err := rlp.Encode(w, dl.id); err != nil { + return err + } + // Step three, write all unwritten nodes into the journal + nodes := make([]journalNodes, 0, len(dl.buffer.nodes)) + for owner, subset := range dl.buffer.nodes { + entry := journalNodes{Owner: owner} + for path, node := range subset { + entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob}) + } + nodes = append(nodes, entry) + } + if err := rlp.Encode(w, nodes); err != nil { + return err + } + log.Debug("Journaled pathdb disk layer", "root", dl.root, "nodes", len(dl.buffer.nodes)) + return nil +} + +// journal implements the layer interface, writing the memory layer contents +// into a buffer to be stored in the database as the layer journal. +func (dl *diffLayer) journal(w io.Writer) error { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // journal the parent first + if err := dl.parent.journal(w); err != nil { + return err + } + // Everything below was journaled, persist this layer too + if err := rlp.Encode(w, dl.root); err != nil { + return err + } + if err := rlp.Encode(w, dl.block); err != nil { + return err + } + // Write the accumulated trie nodes into buffer + nodes := make([]journalNodes, 0, len(dl.nodes)) + for owner, subset := range dl.nodes { + entry := journalNodes{Owner: owner} + for path, node := range subset { + entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob}) + } + nodes = append(nodes, entry) + } + if err := rlp.Encode(w, nodes); err != nil { + return err + } + // Write the accumulated state changes into buffer + var jacct journalAccounts + for addr, account := range dl.states.Accounts { + jacct.Addresses = append(jacct.Addresses, addr) + jacct.Accounts = append(jacct.Accounts, account) + } + if err := rlp.Encode(w, jacct); err != nil { + return err + } + storage := make([]journalStorage, 0, len(dl.states.Storages)) + for addr, slots := range dl.states.Storages { + entry := journalStorage{Account: addr} + for slotHash, slot := range slots { + entry.Hashes = append(entry.Hashes, slotHash) + entry.Slots = append(entry.Slots, slot) + } + storage = append(storage, entry) + } + if err := rlp.Encode(w, storage); err != nil { + return err + } + log.Debug("Journaled pathdb diff layer", "root", dl.root, "parent", dl.parent.rootHash(), "id", dl.stateID(), "block", dl.block, "nodes", len(dl.nodes)) + return nil +} + +// Journal commits an entire diff hierarchy to disk into a single journal entry. +// This is meant to be used during shutdown to persist the layer without +// flattening everything down (bad for reorgs). And this function will mark the +// database as read-only to prevent all following mutation to disk. +func (db *Database) Journal(root common.Hash) error { + // Retrieve the head layer to journal from. + l := db.tree.get(root) + if l == nil { + return fmt.Errorf("triedb layer [%#x] missing", root) + } + disk := db.tree.bottom() + if l, ok := l.(*diffLayer); ok { + log.Info("Persisting dirty state to disk", "head", l.block, "root", root, "layers", l.id-disk.id+disk.buffer.layers) + } else { // disk layer only on noop runs (likely) or deep reorgs (unlikely) + log.Info("Persisting dirty state to disk", "root", root, "layers", disk.buffer.layers) + } + start := time.Now() + + // Run the journaling + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the database is in read only mode. + if db.readOnly { + return errDatabaseReadOnly + } + // Firstly write out the metadata of journal + journal := new(bytes.Buffer) + if err := rlp.Encode(journal, journalVersion); err != nil { + return err + } + // Secondly write out the state root in disk, ensure all layers + // on top are continuous with disk. + diskRoot := types.EmptyRootHash + if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 { + diskRoot = crypto.Keccak256Hash(blob) + } + if err := rlp.Encode(journal, diskRoot); err != nil { + return err + } + // Finally write out the journal of each layer in reverse order. + if err := l.journal(journal); err != nil { + return err + } + // Store the journal into the database and return + rawdb.WriteTrieJournal(db.diskdb, journal.Bytes()) + + // Set the db in read only mode to reject all following mutations + db.readOnly = true + log.Info("Persisted dirty state to disk", "size", common.StorageSize(journal.Len()), "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} diff --git a/triedb/pathdb/layertree.go b/triedb/pathdb/layertree.go new file mode 100644 index 0000000..d314779 --- /dev/null +++ b/triedb/pathdb/layertree.go @@ -0,0 +1,214 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package pathdb + +import ( + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +// layerTree is a group of state layers identified by the state root. +// This structure defines a few basic operations for manipulating +// state layers linked with each other in a tree structure. It's +// thread-safe to use. However, callers need to ensure the thread-safety +// of the referenced layer by themselves. +type layerTree struct { + lock sync.RWMutex + layers map[common.Hash]layer +} + +// newLayerTree constructs the layerTree with the given head layer. +func newLayerTree(head layer) *layerTree { + tree := new(layerTree) + tree.reset(head) + return tree +} + +// reset initializes the layerTree by the given head layer. +// All the ancestors will be iterated out and linked in the tree. +func (tree *layerTree) reset(head layer) { + tree.lock.Lock() + defer tree.lock.Unlock() + + var layers = make(map[common.Hash]layer) + for head != nil { + layers[head.rootHash()] = head + head = head.parentLayer() + } + tree.layers = layers +} + +// get retrieves a layer belonging to the given state root. +func (tree *layerTree) get(root common.Hash) layer { + tree.lock.RLock() + defer tree.lock.RUnlock() + + return tree.layers[types.TrieRootHash(root)] +} + +// forEach iterates the stored layers inside and applies the +// given callback on them. +func (tree *layerTree) forEach(onLayer func(layer)) { + tree.lock.RLock() + defer tree.lock.RUnlock() + + for _, layer := range tree.layers { + onLayer(layer) + } +} + +// len returns the number of layers cached. +func (tree *layerTree) len() int { + tree.lock.RLock() + defer tree.lock.RUnlock() + + return len(tree.layers) +} + +// add inserts a new layer into the tree if it can be linked to an existing old parent. +func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { + // Reject noop updates to avoid self-loops. This is a special case that can + // happen for clique networks and proof-of-stake networks where empty blocks + // don't modify the state (0 block subsidy). + // + // Although we could silently ignore this internally, it should be the caller's + // responsibility to avoid even attempting to insert such a layer. + root, parentRoot = types.TrieRootHash(root), types.TrieRootHash(parentRoot) + if root == parentRoot { + return errors.New("layer cycle") + } + parent := tree.get(parentRoot) + if parent == nil { + return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot) + } + l := parent.update(root, parent.stateID()+1, block, nodes.Flatten(), states) + + tree.lock.Lock() + tree.layers[l.rootHash()] = l + tree.lock.Unlock() + return nil +} + +// cap traverses downwards the diff tree until the number of allowed diff layers +// are crossed. All diffs beyond the permitted number are flattened downwards. +func (tree *layerTree) cap(root common.Hash, layers int) error { + // Retrieve the head layer to cap from + root = types.TrieRootHash(root) + l := tree.get(root) + if l == nil { + return fmt.Errorf("triedb layer [%#x] missing", root) + } + diff, ok := l.(*diffLayer) + if !ok { + return fmt.Errorf("triedb layer [%#x] is disk layer", root) + } + tree.lock.Lock() + defer tree.lock.Unlock() + + // If full commit was requested, flatten the diffs and merge onto disk + if layers == 0 { + base, err := diff.persist(true) + if err != nil { + return err + } + // Replace the entire layer tree with the flat base + tree.layers = map[common.Hash]layer{base.rootHash(): base} + return nil + } + // Dive until we run out of layers or reach the persistent database + for i := 0; i < layers-1; i++ { + // If we still have diff layers below, continue down + if parent, ok := diff.parentLayer().(*diffLayer); ok { + diff = parent + } else { + // Diff stack too shallow, return without modifications + return nil + } + } + // We're out of layers, flatten anything below, stopping if it's the disk or if + // the memory limit is not yet exceeded. + switch parent := diff.parentLayer().(type) { + case *diskLayer: + return nil + + case *diffLayer: + // Hold the lock to prevent any read operations until the new + // parent is linked correctly. + diff.lock.Lock() + + base, err := parent.persist(false) + if err != nil { + diff.lock.Unlock() + return err + } + tree.layers[base.rootHash()] = base + diff.parent = base + + diff.lock.Unlock() + + default: + panic(fmt.Sprintf("unknown data layer in triedb: %T", parent)) + } + // Remove any layer that is stale or links into a stale layer + children := make(map[common.Hash][]common.Hash) + for root, layer := range tree.layers { + if dl, ok := layer.(*diffLayer); ok { + parent := dl.parentLayer().rootHash() + children[parent] = append(children[parent], root) + } + } + var remove func(root common.Hash) + remove = func(root common.Hash) { + delete(tree.layers, root) + for _, child := range children[root] { + remove(child) + } + delete(children, root) + } + for root, layer := range tree.layers { + if dl, ok := layer.(*diskLayer); ok && dl.isStale() { + remove(root) + } + } + return nil +} + +// bottom returns the bottom-most disk layer in this tree. +func (tree *layerTree) bottom() *diskLayer { + tree.lock.RLock() + defer tree.lock.RUnlock() + + if len(tree.layers) == 0 { + return nil // Shouldn't happen, empty tree + } + // pick a random one as the entry point + var current layer + for _, layer := range tree.layers { + current = layer + break + } + for current.parentLayer() != nil { + current = current.parentLayer() + } + return current.(*diskLayer) +} diff --git a/triedb/pathdb/metrics.go b/triedb/pathdb/metrics.go new file mode 100644 index 0000000..a250f70 --- /dev/null +++ b/triedb/pathdb/metrics.go @@ -0,0 +1,51 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package pathdb + +import "github.com/ethereum/go-ethereum/metrics" + +var ( + cleanHitMeter = metrics.NewRegisteredMeter("pathdb/clean/hit", nil) + cleanMissMeter = metrics.NewRegisteredMeter("pathdb/clean/miss", nil) + cleanReadMeter = metrics.NewRegisteredMeter("pathdb/clean/read", nil) + cleanWriteMeter = metrics.NewRegisteredMeter("pathdb/clean/write", nil) + + dirtyHitMeter = metrics.NewRegisteredMeter("pathdb/dirty/hit", nil) + dirtyMissMeter = metrics.NewRegisteredMeter("pathdb/dirty/miss", nil) + dirtyReadMeter = metrics.NewRegisteredMeter("pathdb/dirty/read", nil) + dirtyWriteMeter = metrics.NewRegisteredMeter("pathdb/dirty/write", nil) + dirtyNodeHitDepthHist = metrics.NewRegisteredHistogram("pathdb/dirty/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) + + cleanFalseMeter = metrics.NewRegisteredMeter("pathdb/clean/false", nil) + dirtyFalseMeter = metrics.NewRegisteredMeter("pathdb/dirty/false", nil) + diskFalseMeter = metrics.NewRegisteredMeter("pathdb/disk/false", nil) + diffFalseMeter = metrics.NewRegisteredMeter("pathdb/diff/false", nil) + + commitTimeTimer = metrics.NewRegisteredTimer("pathdb/commit/time", nil) + commitNodesMeter = metrics.NewRegisteredMeter("pathdb/commit/nodes", nil) + commitBytesMeter = metrics.NewRegisteredMeter("pathdb/commit/bytes", nil) + + gcNodesMeter = metrics.NewRegisteredMeter("pathdb/gc/nodes", nil) + gcBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/bytes", nil) + + diffLayerBytesMeter = metrics.NewRegisteredMeter("pathdb/diff/bytes", nil) + diffLayerNodesMeter = metrics.NewRegisteredMeter("pathdb/diff/nodes", nil) + + historyBuildTimeMeter = metrics.NewRegisteredTimer("pathdb/history/time", nil) + historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil) + historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil) +) diff --git a/triedb/pathdb/nodebuffer.go b/triedb/pathdb/nodebuffer.go new file mode 100644 index 0000000..d349260 --- /dev/null +++ b/triedb/pathdb/nodebuffer.go @@ -0,0 +1,283 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pathdb + +import ( + "bytes" + "fmt" + "maps" + "time" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +// nodebuffer is a collection of modified trie nodes to aggregate the disk +// write. The content of the nodebuffer must be checked before diving into +// disk (since it basically is not-yet-written data). +type nodebuffer struct { + layers uint64 // The number of diff layers aggregated inside + size uint64 // The size of aggregated writes + limit uint64 // The maximum memory allowance in bytes + nodes map[common.Hash]map[string]*trienode.Node // The dirty node set, mapped by owner and path +} + +// newNodeBuffer initializes the node buffer with the provided nodes. +func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, layers uint64) *nodebuffer { + if nodes == nil { + nodes = make(map[common.Hash]map[string]*trienode.Node) + } + var size uint64 + for _, subset := range nodes { + for path, n := range subset { + size += uint64(len(n.Blob) + len(path)) + } + } + return &nodebuffer{ + layers: layers, + nodes: nodes, + size: size, + limit: uint64(limit), + } +} + +// node retrieves the trie node with given node info. +func (b *nodebuffer) node(owner common.Hash, path []byte) (*trienode.Node, bool) { + subset, ok := b.nodes[owner] + if !ok { + return nil, false + } + n, ok := subset[string(path)] + if !ok { + return nil, false + } + return n, true +} + +// commit merges the dirty nodes into the nodebuffer. This operation won't take +// the ownership of the nodes map which belongs to the bottom-most diff layer. +// It will just hold the node references from the given map which are safe to +// copy. +func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *nodebuffer { + var ( + delta int64 + overwrite int64 + overwriteSize int64 + ) + for owner, subset := range nodes { + current, exist := b.nodes[owner] + if !exist { + // Allocate a new map for the subset instead of claiming it directly + // from the passed map to avoid potential concurrent map read/write. + // The nodes belong to original diff layer are still accessible even + // after merging, thus the ownership of nodes map should still belong + // to original layer and any mutation on it should be prevented. + for path, n := range subset { + delta += int64(len(n.Blob) + len(path)) + } + b.nodes[owner] = maps.Clone(subset) + continue + } + for path, n := range subset { + if orig, exist := current[path]; !exist { + delta += int64(len(n.Blob) + len(path)) + } else { + delta += int64(len(n.Blob) - len(orig.Blob)) + overwrite++ + overwriteSize += int64(len(orig.Blob) + len(path)) + } + current[path] = n + } + b.nodes[owner] = current + } + b.updateSize(delta) + b.layers++ + gcNodesMeter.Mark(overwrite) + gcBytesMeter.Mark(overwriteSize) + return b +} + +// revert is the reverse operation of commit. It also merges the provided nodes +// into the nodebuffer, the difference is that the provided node set should +// revert the changes made by the last state transition. +func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) error { + // Short circuit if no embedded state transition to revert. + if b.layers == 0 { + return errStateUnrecoverable + } + b.layers-- + + // Reset the entire buffer if only a single transition left. + if b.layers == 0 { + b.reset() + return nil + } + var delta int64 + for owner, subset := range nodes { + current, ok := b.nodes[owner] + if !ok { + panic(fmt.Sprintf("non-existent subset (%x)", owner)) + } + for path, n := range subset { + orig, ok := current[path] + if !ok { + // There is a special case in MPT that one child is removed from + // a fullNode which only has two children, and then a new child + // with different position is immediately inserted into the fullNode. + // In this case, the clean child of the fullNode will also be + // marked as dirty because of node collapse and expansion. + // + // In case of database rollback, don't panic if this "clean" + // node occurs which is not present in buffer. + var blob []byte + if owner == (common.Hash{}) { + blob = rawdb.ReadAccountTrieNode(db, []byte(path)) + } else { + blob = rawdb.ReadStorageTrieNode(db, owner, []byte(path)) + } + // Ignore the clean node in the case described above. + if bytes.Equal(blob, n.Blob) { + continue + } + panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex())) + } + current[path] = n + delta += int64(len(n.Blob)) - int64(len(orig.Blob)) + } + } + b.updateSize(delta) + return nil +} + +// updateSize updates the total cache size by the given delta. +func (b *nodebuffer) updateSize(delta int64) { + size := int64(b.size) + delta + if size >= 0 { + b.size = uint64(size) + return + } + s := b.size + b.size = 0 + log.Error("Invalid pathdb buffer size", "prev", common.StorageSize(s), "delta", common.StorageSize(delta)) +} + +// reset cleans up the disk cache. +func (b *nodebuffer) reset() { + b.layers = 0 + b.size = 0 + b.nodes = make(map[common.Hash]map[string]*trienode.Node) +} + +// empty returns an indicator if nodebuffer contains any state transition inside. +func (b *nodebuffer) empty() bool { + return b.layers == 0 +} + +// setSize sets the buffer size to the provided number, and invokes a flush +// operation if the current memory usage exceeds the new limit. +func (b *nodebuffer) setSize(size int, db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64) error { + b.limit = uint64(size) + return b.flush(db, clean, id, false) +} + +// allocBatch returns a database batch with pre-allocated buffer. +func (b *nodebuffer) allocBatch(db ethdb.KeyValueStore) ethdb.Batch { + var metasize int + for owner, nodes := range b.nodes { + if owner == (common.Hash{}) { + metasize += len(nodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix + } else { + metasize += len(nodes) * (len(rawdb.TrieNodeStoragePrefix) + common.HashLength) // database key prefix + owner + } + } + return db.NewBatchWithSize((metasize + int(b.size)) * 11 / 10) // extra 10% for potential pebble internal stuff +} + +// flush persists the in-memory dirty trie node into the disk if the configured +// memory threshold is reached. Note, all data must be written atomically. +func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64, force bool) error { + if b.size <= b.limit && !force { + return nil + } + // Ensure the target state id is aligned with the internal counter. + head := rawdb.ReadPersistentStateID(db) + if head+b.layers != id { + return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id) + } + var ( + start = time.Now() + batch = b.allocBatch(db) + ) + nodes := writeNodes(batch, b.nodes, clean) + rawdb.WritePersistentStateID(batch, id) + + // Flush all mutations in a single batch + size := batch.ValueSize() + if err := batch.Write(); err != nil { + return err + } + commitBytesMeter.Mark(int64(size)) + commitNodesMeter.Mark(int64(nodes)) + commitTimeTimer.UpdateSince(start) + log.Debug("Persisted pathdb nodes", "nodes", len(b.nodes), "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start))) + b.reset() + return nil +} + +// writeNodes writes the trie nodes into the provided database batch. +// Note this function will also inject all the newly written nodes +// into clean cache. +func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *fastcache.Cache) (total int) { + for owner, subset := range nodes { + for path, n := range subset { + if n.IsDeleted() { + if owner == (common.Hash{}) { + rawdb.DeleteAccountTrieNode(batch, []byte(path)) + } else { + rawdb.DeleteStorageTrieNode(batch, owner, []byte(path)) + } + if clean != nil { + clean.Del(cacheKey(owner, []byte(path))) + } + } else { + if owner == (common.Hash{}) { + rawdb.WriteAccountTrieNode(batch, []byte(path), n.Blob) + } else { + rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob) + } + if clean != nil { + clean.Set(cacheKey(owner, []byte(path)), n.Blob) + } + } + } + total += len(subset) + } + return total +} + +// cacheKey constructs the unique key of clean cache. +func cacheKey(owner common.Hash, path []byte) []byte { + if owner == (common.Hash{}) { + return path + } + return append(owner.Bytes(), path...) +} diff --git a/triedb/pathdb/reader.go b/triedb/pathdb/reader.go new file mode 100644 index 0000000..54dc98a --- /dev/null +++ b/triedb/pathdb/reader.go @@ -0,0 +1,94 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package pathdb + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/triedb/database" +) + +// The types of locations where the node is found. +const ( + locDirtyCache = "dirty" // dirty cache + locCleanCache = "clean" // clean cache + locDiskLayer = "disk" // persistent state + locDiffLayer = "diff" // diff layers +) + +// nodeLoc is a helpful structure that contains the location where the node +// is found, as it's useful for debugging purposes. +type nodeLoc struct { + loc string + depth int +} + +// string returns the string representation of node location. +func (loc *nodeLoc) string() string { + return fmt.Sprintf("loc: %s, depth: %d", loc.loc, loc.depth) +} + +// reader implements the database.Reader interface, providing the functionalities to +// retrieve trie nodes by wrapping the internal state layer. +type reader struct { + layer layer + noHashCheck bool +} + +// Node implements database.Reader interface, retrieving the node with specified +// node info. Don't modify the returned byte slice since it's not deep-copied +// and still be referenced by database. +func (r *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { + blob, got, loc, err := r.layer.node(owner, path, 0) + if err != nil { + return nil, err + } + // Error out if the local one is inconsistent with the target. + if !r.noHashCheck && got != hash { + // Location is always available even if the node + // is not found. + switch loc.loc { + case locCleanCache: + cleanFalseMeter.Mark(1) + case locDirtyCache: + dirtyFalseMeter.Mark(1) + case locDiffLayer: + diffFalseMeter.Mark(1) + case locDiskLayer: + diskFalseMeter.Mark(1) + } + blobHex := "nil" + if len(blob) > 0 { + blobHex = hexutil.Encode(blob) + } + log.Error("Unexpected trie node", "location", loc.loc, "owner", owner, "path", path, "expect", hash, "got", got, "blob", blobHex) + return nil, fmt.Errorf("unexpected node: (%x %v), %x!=%x, %s, blob: %s", owner, path, hash, got, loc.string(), blobHex) + } + return blob, nil +} + +// Reader retrieves a layer belonging to the given state root. +func (db *Database) Reader(root common.Hash) (database.Reader, error) { + layer := db.tree.get(root) + if layer == nil { + return nil, fmt.Errorf("state %#x is not available", root) + } + return &reader{layer: layer, noHashCheck: db.isVerkle}, nil +} diff --git a/triedb/preimages.go b/triedb/preimages.go new file mode 100644 index 0000000..a538491 --- /dev/null +++ b/triedb/preimages.go @@ -0,0 +1,95 @@ +// 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 triedb + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" +) + +// preimageStore is the store for caching preimages of node key. +type preimageStore struct { + lock sync.RWMutex + disk ethdb.KeyValueStore + preimages map[common.Hash][]byte // Preimages of nodes from the secure trie + preimagesSize common.StorageSize // Storage size of the preimages cache +} + +// newPreimageStore initializes the store for caching preimages. +func newPreimageStore(disk ethdb.KeyValueStore) *preimageStore { + return &preimageStore{ + disk: disk, + preimages: make(map[common.Hash][]byte), + } +} + +// insertPreimage writes a new trie node pre-image to the memory database if it's +// yet unknown. The method will NOT make a copy of the slice, only use if the +// preimage will NOT be changed later on. +func (store *preimageStore) insertPreimage(preimages map[common.Hash][]byte) { + store.lock.Lock() + defer store.lock.Unlock() + + for hash, preimage := range preimages { + if _, ok := store.preimages[hash]; ok { + continue + } + store.preimages[hash] = preimage + store.preimagesSize += common.StorageSize(common.HashLength + len(preimage)) + } +} + +// 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 (store *preimageStore) preimage(hash common.Hash) []byte { + store.lock.RLock() + preimage := store.preimages[hash] + store.lock.RUnlock() + + if preimage != nil { + return preimage + } + return rawdb.ReadPreimage(store.disk, hash) +} + +// commit flushes the cached preimages into the disk. +func (store *preimageStore) commit(force bool) error { + store.lock.Lock() + defer store.lock.Unlock() + + if store.preimagesSize <= 4*1024*1024 && !force { + return nil + } + batch := store.disk.NewBatch() + rawdb.WritePreimages(batch, store.preimages) + if err := batch.Write(); err != nil { + return err + } + store.preimages, store.preimagesSize = make(map[common.Hash][]byte), 0 + return nil +} + +// size returns the current storage size of accumulated preimages. +func (store *preimageStore) size() common.StorageSize { + store.lock.RLock() + defer store.lock.RUnlock() + + return store.preimagesSize +}